[
  {
    "path": ".gitattributes",
    "content": "*.ipynb linguist-documentation\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": ""
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "If this is a build issue, please fill out the template below.\n\n### System information\n\n* Operating system:\n* Compiler version:\n* CMake version:\n* CMake arguments:\n* Relevant libraries/versions (e.g. CUDA):\n\n### CMake summary output\n\n```\n******** Summary ********\n<please paste summary output here>\n```\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": ""
  },
  {
    "path": ".gitignore",
    "content": "## General\n\n# Compiled Object files\n*.slo\n*.lo\n*.o\n*.cuo\n*.obj\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n*.dll\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n*.lib\n\n# Compiled protocol buffers\n*.pb.h\n*.pb.cc\n*_pb2.py\n\n# Compiled python\n*.pyc\n*.pyd\n\n# Compiled MATLAB\n*.mex*\n\n# IPython notebook checkpoints\n.ipynb_checkpoints\n\n# Editor temporaries\n*.swn\n*.swo\n*.swp\n*~\n\n# Sublime Text settings\n*.sublime-workspace\n*.sublime-project\n\n# Eclipse Project settings\n*.*project\n.settings\n\n# QtCreator files\n*.user\n\n# PyCharm files\n.idea\n\n# Visual Studio Code files\n.vscode\n.vs\n\n# OSX dir files\n.DS_Store\n\n## Caffe2\n\n# build, distribute, and bins (+ python proto bindings)\nbuild\nbuild_host_protoc\nbuild_android\nbuild_ios\nbuild_*\n.build_debug/*\n.build_release/*\ndistribute/*\n*.testbin\n*.bin\ncmake_build\n.cmake_build\ngen\n.setuptools-cmake-build\n.pytest_cache\n\n# Bram\nplsdontbreak\n\n# Generated documentation\ndocs/_site\ndocs/gathered\n_site\ndoxygen\ndocs/dev\n\n# LevelDB files\n*.sst\n*.ldb\nLOCK\nLOG*\nCURRENT\nMANIFEST-*\n\n# generated version file\ncaffe2/version.py\n\n# setup.py intermediates\n.eggs\ncaffe2.egg-info\n\n# Atom/Watchman required file\n.watchmanconfig\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"third_party/pybind11\"]\n\tpath = third_party/pybind11\n\turl = https://github.com/pybind/pybind11.git\n[submodule \"third_party/nccl\"]\n\tpath = third_party/nccl\n\turl = https://github.com/nvidia/nccl.git\n[submodule \"third_party/cub\"]\n\tpath = third_party/cub\n\turl = https://github.com/NVlabs/cub.git\n[submodule \"third_party/eigen\"]\n\tpath = third_party/eigen\n\turl = https://github.com/RLovelett/eigen.git\n[submodule \"third_party/googletest\"]\n\tpath = third_party/googletest\n\turl = https://github.com/google/googletest.git\n[submodule \"third_party/nervanagpu\"]\n\tpath = third_party/nervanagpu\n\turl = https://github.com/NervanaSystems/nervanagpu.git\n[submodule \"third_party/benchmark\"]\n\tpath = third_party/benchmark\n\turl = https://github.com/google/benchmark.git\n[submodule \"third_party/protobuf\"]\n\tpath = third_party/protobuf\n\turl = https://github.com/google/protobuf.git\n[submodule \"third_party/ios-cmake\"]\n\tpath = third_party/ios-cmake\n\turl = https://github.com/Yangqing/ios-cmake.git\n[submodule \"third_party/NNPACK\"]\n\tpath = third_party/NNPACK\n\turl = https://github.com/Maratyszcza/NNPACK.git\n[submodule \"third_party/gloo\"]\n\tpath = third_party/gloo\n\turl = https://github.com/facebookincubator/gloo\n[submodule \"third_party/NNPACK_deps/pthreadpool\"]\n\tpath = third_party/pthreadpool\n\turl = https://github.com/Maratyszcza/pthreadpool.git\n[submodule \"third_party/NNPACK_deps/FXdiv\"]\n\tpath = third_party/FXdiv\n\turl = https://github.com/Maratyszcza/FXdiv.git\n[submodule \"third_party/NNPACK_deps/FP16\"]\n\tpath = third_party/FP16\n\turl = https://github.com/Maratyszcza/FP16.git\n[submodule \"third_party/NNPACK_deps/psimd\"]\n\tpath = third_party/psimd\n\turl = https://github.com/Maratyszcza/psimd.git\n[submodule \"third_party/aten\"]\n\tpath = third_party/aten\n\turl = https://github.com/zdevito/aten\n[submodule \"third_party/zstd\"]\n\tpath = third_party/zstd\n\turl = https://github.com/facebook/zstd.git\n[submodule \"third-party/cpuinfo\"]\n\tpath = third_party/cpuinfo\n\turl = https://github.com/Maratyszcza/cpuinfo.git\n[submodule \"third_party/python-enum\"]\n\tpath = third_party/python-enum\n\turl = https://github.com/PeachPy/enum34.git\n[submodule \"third_party/python-peachpy\"]\n\tpath = third_party/python-peachpy\n\turl = https://github.com/Maratyszcza/PeachPy.git\n[submodule \"third_party/python-six\"]\n\tpath = third_party/python-six\n\turl = https://github.com/benjaminp/six.git\n[submodule \"third_party/ComputeLibrary\"]\n\tpath = third_party/ComputeLibrary\n\turl = https://github.com/ARM-software/ComputeLibrary.git\n[submodule \"third_party/onnx\"]\n\tpath = third_party/onnx\n\turl = https://github.com/onnx/onnx.git\n"
  },
  {
    "path": ".jenkins/README.md",
    "content": "# Jenkins\n\nThe scripts in this directory are the entrypoint for testing Caffe2.\n\nThe environment variable `BUILD_ENVIRONMENT` is expected to be set to\nthe build environment you intend to test. It is a hint for the build\nand test scripts to configure Caffe2 a certain way and include/exclude\ntests. Docker images, they equal the name of the image itself. For\nexample: `py2-cuda9.0-cudnn7-ubuntu16.04`. The Docker images that are\nbuilt on Jenkins and are used in triggered builds already have this\nenvironment variable set in their manifest. Also see\n`./docker/jenkins/*/Dockerfile` and search for `BUILD_ENVIRONMENT`.\n\nOur Jenkins installation is located at https://ci.pytorch.org/jenkins/.\n"
  },
  {
    "path": ".jenkins/build.sh",
    "content": "#!/bin/bash\n\nset -ex\n\nLOCAL_DIR=$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\nROOT_DIR=$(cd \"$LOCAL_DIR\"/.. && pwd)\n\n# Setup sccache if SCCACHE_BUCKET is set\nif [ -n \"${SCCACHE_BUCKET}\" ]; then\n  mkdir -p ./sccache\n\n  SCCACHE=\"$(which sccache)\"\n  if [ -z \"${SCCACHE}\" ]; then\n    echo \"Unable to find sccache...\"\n    exit 1\n  fi\n\n  # Setup wrapper scripts\n  for compiler in cc c++ gcc g++ x86_64-linux-gnu-gcc; do\n    (\n      echo \"#!/bin/sh\"\n      echo \"exec $SCCACHE $(which $compiler) \\\"\\$@\\\"\"\n    ) > \"./sccache/$compiler\"\n    chmod +x \"./sccache/$compiler\"\n  done\n\n  # CMake must find these wrapper scripts\n  export PATH=\"$PWD/sccache:$PATH\"\nfi\n\n# Setup ccache if configured to use it (and not sccache)\nif [ -z \"${SCCACHE}\" ] && which ccache > /dev/null; then\n  mkdir -p ./ccache\n  ln -sf \"$(which ccache)\" ./ccache/cc\n  ln -sf \"$(which ccache)\" ./ccache/c++\n  ln -sf \"$(which ccache)\" ./ccache/gcc\n  ln -sf \"$(which ccache)\" ./ccache/g++\n  ln -sf \"$(which ccache)\" ./ccache/x86_64-linux-gnu-gcc\n  export CCACHE_WRAPPER_DIR=\"$PWD/ccache\"\n  export PATH=\"$CCACHE_WRAPPER_DIR:$PATH\"\nfi\n\nCMAKE_ARGS=(\"-DBUILD_BINARY=ON\")\nCMAKE_ARGS+=(\"-DUSE_OBSERVERS=ON\")\nCMAKE_ARGS+=(\"-DUSE_ZSTD=ON\")\n\n# Run build script from scripts if applicable\nif [[ \"${BUILD_ENVIRONMENT}\" == *-android* ]]; then\n  export ANDROID_NDK=/opt/ndk\n  \"${ROOT_DIR}/scripts/build_android.sh\" ${CMAKE_ARGS[*]} \"$@\"\n  exit 0\nfi\nif [[ \"${BUILD_ENVIRONMENT}\" == conda* ]]; then\n\n  # click (required by onnx) wants these set\n  export LANG=C.UTF-8\n  export LC_ALL=C.UTF-8\n\n  # SKIP_CONDA_TESTS refers to only the 'test' section of the meta.yaml\n  export SKIP_CONDA_TESTS=1\n  export CONDA_INSTALL_LOCALLY=1\n  \"${ROOT_DIR}/scripts/build_anaconda.sh\" \"$@\"\n\n  # The tests all need hypothesis, tabulate, and pydot, which aren't included\n  # in the conda packages\n  conda install -y hypothesis tabulate pydot\n\n  # This build will be tested against onnx tests, which needs onnx installed.\n  # Onnx should be built against the same protobuf that Caffe2 uses, which is\n  # only installed in the conda environment when Caffe2 is.\n  # This path comes from install_anaconda.sh which installs Anaconda into the\n  # docker image\n  PROTOBUF_INCDIR=/opt/conda/include pip install \"${ROOT_DIR}/third_party/onnx\"\n  exit 0\nfi\n\n# Run cmake from ./build directory\nmkdir -p ./build\ncd ./build\n\nINSTALL_PREFIX=\"/usr/local/caffe2\"\nCMAKE_ARGS+=(\"-DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX}\")\n\n# Explicitly set Python executable.\n# On Ubuntu 16.04 the default Python is still 2.7.\nPYTHON=\"$(which python)\"\nif [[ \"${BUILD_ENVIRONMENT}\" == py3* ]]; then\n  PYTHON=/usr/bin/python3\n  CMAKE_ARGS+=(\"-DPYTHON_EXECUTABLE=${PYTHON}\")\nfi\n\ncase \"${BUILD_ENVIRONMENT}\" in\n  *-mkl*)\n    CMAKE_ARGS+=(\"-DBLAS=MKL\")\n    ;;\n  *-cuda*)\n    CMAKE_ARGS+=(\"-DUSE_CUDA=ON\")\n    CMAKE_ARGS+=(\"-DCUDA_ARCH_NAME=Maxwell\")\n    CMAKE_ARGS+=(\"-DUSE_NNPACK=OFF\")\n\n    # Add ccache symlink for nvcc\n    ln -sf \"$(which ccache)\" \"${CCACHE_WRAPPER_DIR}/nvcc\"\n\n    # Explicitly set path to NVCC such that the symlink to ccache is used\n    CMAKE_ARGS+=(\"-DCUDA_NVCC_EXECUTABLE=${CCACHE_WRAPPER_DIR}/nvcc\")\n\n    # Ensure FindCUDA.cmake can infer the right path to the CUDA toolkit.\n    # Setting PATH to resolve to the right nvcc alone isn't enough.\n    # See /usr/share/cmake-3.5/Modules/FindCUDA.cmake, block at line 589.\n    export CUDA_PATH=\"/usr/local/cuda\"\n\n    # Ensure the ccache symlink can still find the real nvcc binary.\n    export PATH=\"/usr/local/cuda/bin:$PATH\"\n    ;;\nesac\n\n# Try to include Redis support for Linux builds\nif [ \"$(uname)\" == \"Linux\" ]; then\n  CMAKE_ARGS+=(\"-DUSE_REDIS=ON\")\nfi\n\n# Currently, on Jenkins mac os, we will use custom protobuf. Mac OS\n# contbuild at the moment is minimal dependency - it doesn't use glog\n# or gflags either.\nif [ \"$(uname)\" == \"Darwin\" ]; then\n  CMAKE_ARGS+=(\"-DBUILD_CUSTOM_PROTOBUF=ON\")\nfi\n\n# We test the presence of cmake3 (for platforms like Centos and Ubuntu 14.04)\n# and use that if so.\nif [[ -x \"$(command -v cmake3)\" ]]; then\n    CMAKE_BINARY=cmake3\nelse\n    CMAKE_BINARY=cmake\nfi\n\n# Configure\n${CMAKE_BINARY} \"${ROOT_DIR}\" ${CMAKE_ARGS[*]} \"$@\"\n\n# Build\nif [ \"$(uname)\" == \"Linux\" ]; then\n  make \"-j$(nproc)\" install\nelse\n  echo \"Don't know how to build on $(uname)\"\n  exit 1\nfi\n\n# Install ONNX into a local directory\nONNX_INSTALL_PATH=\"/usr/local/onnx\"\npip install \"${ROOT_DIR}/third_party/onnx\" -t \"${ONNX_INSTALL_PATH}\"\n\n# Symlink the caffe2 base python path into the system python path,\n# so that we can import caffe2 without having to change $PYTHONPATH.\n# Run in a subshell to contain environment set by /etc/os-release.\n#\n# This is only done when running on Jenkins!  We don't want to pollute\n# the user environment with Python symlinks and ld.so.conf.d hacks.\n#\nif [ -n \"${JENKINS_URL}\" ]; then\n  (\n    source /etc/os-release\n\n    function python_version() {\n      \"$PYTHON\" -c 'import sys; print(\"python%d.%d\" % sys.version_info[0:2])'\n    }\n\n    # Debian/Ubuntu\n    if [[ \"$ID_LIKE\" == *debian* ]]; then\n      python_path=\"/usr/local/lib/$(python_version)/dist-packages\"\n      sudo ln -sf \"${INSTALL_PREFIX}/caffe2\" \"${python_path}\"\n      sudo ln -sf \"${ONNX_INSTALL_PATH}/onnx\" \"${python_path}\"\n    fi\n\n    # RHEL/CentOS\n    if [[ \"$ID_LIKE\" == *rhel* ]]; then\n      python_path=\"/usr/lib64/$(python_version)/site-packages/\"\n      sudo ln -sf \"${INSTALL_PREFIX}/caffe2\" \"${python_path}\"\n      sudo ln -sf \"${ONNX_INSTALL_PATH}/onnx\" \"${python_path}\"\n    fi\n\n    # /etc/ld.so.conf.d is used on both Debian and RHEL\n    echo \"${INSTALL_PREFIX}/lib\" | sudo tee /etc/ld.so.conf.d/caffe2.conf\n    sudo ldconfig\n  )\nfi\n"
  },
  {
    "path": ".jenkins/test.sh",
    "content": "#!/bin/bash\n\nset -ex\n\n# Figure out which Python to use\nPYTHON=\"python\"\nif [ -n \"$BUILD_ENVIRONMENT\" ]; then\n  if [[ \"$BUILD_ENVIRONMENT\" == py2* ]]; then\n    PYTHON=\"python2\"\n  elif [[ \"$BUILD_ENVIRONMENT\" == py3* ]]; then\n    PYTHON=\"python3\"\n  fi\nfi\n\n# The prefix must mirror the setting from build.sh\nINSTALL_PREFIX=\"/usr/local/caffe2\"\n\n# Anaconda builds have a special install prefix and python\nif [[ \"$BUILD_ENVIRONMENT\" == conda* ]]; then\n  # This path comes from install_anaconda.sh which installs Anaconda into the\n  # docker image\n  PYTHON=\"/opt/conda/bin/python\"\n  INSTALL_PREFIX=\"/opt/conda/\"\nfi\n\n# Add the site-packages in the caffe2 install prefix to the PYTHONPATH\nSITE_DIR=$($PYTHON -c \"from distutils import sysconfig; print(sysconfig.get_python_lib(prefix=''))\")\nINSTALL_SITE_DIR=\"${INSTALL_PREFIX}/${SITE_DIR}\"\n\nLOCAL_DIR=$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\nROOT_DIR=$(cd \"$LOCAL_DIR\"/.. && pwd)\n\n# Skip tests in environments where they are not built/applicable\nif [[ \"${BUILD_ENVIRONMENT}\" == *-android* ]]; then\n  echo 'Skipping tests'\n  exit 0\nfi\n\n# Set PYTHONPATH and LD_LIBRARY_PATH so that python can find the installed\n# Caffe2. This shouldn't be done on Anaconda, as Anaconda should handle this.\nif [[ \"$BUILD_ENVIRONMENT\" != conda* ]]; then\n  export PYTHONPATH=\"${PYTHONPATH}:$INSTALL_SITE_DIR\"\n  export LD_LIBRARY_PATH=\"${LD_LIBRARY_PATH}:${INSTALL_PREFIX}/lib\"\nfi\n\nexit_code=0\n\ncd \"$ROOT_DIR\"/caffe2/python/tutorials\npython tutorials_to_script_converter.py\ngit status\nif git diff --quiet HEAD; then\n  echo \"Source tree is clean.\"\nelse\n  echo \"After running a tutorial -> script sync there are changes. This probably means you edited an ipython notebook without a proper sync to a script. Please see caffe2/python/tutorials/README.md for more information\"\n  if [ \"$exit_code\" -eq 0 ]; then\n    exit_code=1\n  fi\nfi\n\ncd \"$ROOT_DIR\"\n\nif [ -d ./test ]; then\n  echo \"Directory ./test already exists; please remove it...\"\n  exit 1\nfi\n\nmkdir -p ./test/{cpp,python}\nTEST_DIR=\"$PWD/test\"\n\ncd ${INSTALL_PREFIX}\n\n# Commands below may exit with non-zero status\nset +e\n\n# C++ tests\necho \"Running C++ tests..\"\nfor test in ./test/*; do\n  # Skip tests we know are hanging or bad\n  case \"$(basename \"$test\")\" in\n    mkl_utils_test)\n      continue\n      ;;\n    # TODO investigate conv_op_test failures when using MKL\n    conv_op_test)\n      continue\n      ;;\n  esac\n\n  \"$test\" --gtest_output=xml:\"$TEST_DIR\"/cpp/$(basename \"$test\").xml\n  tmp_exit_code=\"$?\"\n  if [ \"$exit_code\" -eq 0 ]; then\n    exit_code=\"$tmp_exit_code\"\n  fi\ndone\n\n# Get the relative path to where the caffe2 python module was installed\nCAFFE2_PYPATH=\"$INSTALL_SITE_DIR/caffe2\"\n\n# Collect additional tests to run (outside caffe2/python)\nEXTRA_TESTS=()\n\n# CUDA builds always include NCCL support\nif [[ \"$BUILD_ENVIRONMENT\" == *-cuda* ]]; then\n  EXTRA_TESTS+=(\"$CAFFE2_PYPATH/contrib/nccl\")\nfi\n\n# Python tests\necho \"Running Python tests..\"\n\"$PYTHON\" \\\n  -m pytest \\\n  -x \\\n  -v \\\n  --junit-xml=\"$TEST_DIR/python/result.xml\" \\\n  --ignore \"$CAFFE2_PYPATH/python/test/executor_test.py\" \\\n  --ignore \"$CAFFE2_PYPATH/python/operator_test/matmul_op_test.py\" \\\n  --ignore \"$CAFFE2_PYPATH/python/operator_test/pack_ops_test.py\" \\\n  --ignore \"$CAFFE2_PYPATH/python/mkl/mkl_sbn_speed_test.py\" \\\n  \"$CAFFE2_PYPATH/python\" \\\n  \"${EXTRA_TESTS[@]}\"\n\ntmp_exit_code=\"$?\"\nif [ \"$exit_code\" -eq 0 ]; then\n  exit_code=\"$tmp_exit_code\"\nfi\n\n# Exit with the first non-zero status we got\nexit \"$exit_code\"\n"
  },
  {
    "path": ".travis/build.sh",
    "content": "#!/bin/bash\nset -e\nset -x\n\nLOCAL_DIR=$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\nROOT_DIR=$(dirname \"$LOCAL_DIR\")\ncd \"$ROOT_DIR\"\n\nmkdir build\ncd build\n\n# Special cases - run script and exit\nif [ \"$BUILD_ANDROID\" = 'true' ]; then\n    export ANDROID_NDK=/opt/android_ndk\n    \"${ROOT_DIR}/scripts/build_android.sh\"\n    exit 0\nfi\nif [ \"$BUILD_IOS\" = 'true' ]; then\n    \"${ROOT_DIR}/scripts/build_ios.sh\" -DCMAKE_OSX_ARCHITECTURES=arm64\n    exit 0\nfi\n\n# Configure\nCMAKE_ARGS=('-DCMAKE_VERBOSE_MAKEFILE=ON')\nCMAKE_ARGS+=('-DCMAKE_INSTALL_PREFIX=../install')\nif [ \"$BUILD_CUDA\" = 'true' ]; then\n    CMAKE_ARGS+=('-DUSE_CUDA=ON')\n    CMAKE_ARGS+=('-DCUDA_ARCH_NAME=Pascal')\n    CMAKE_ARGS+=('-DCUDA_NVCC_EXECUTABLE=/usr/local/bin/nvcc')\n    export PATH=\"/usr/local/cuda/bin:${PATH}\"\n    CMAKE_ARGS+=('-DUSE_NNPACK=OFF')\nelse\n    CMAKE_ARGS+=('-DUSE_CUDA=OFF')\nfi\nif [ \"$BUILD_MKL\" = 'true' ]; then\n    CMAKE_ARGS+=('-DBLAS=MKL')\nfi\nif [ \"$BUILD_TESTS\" = 'false' ]; then\n    CMAKE_ARGS+=('-DBUILD_TEST=OFF')\nfi\nCMAKE_ARGS+=$(python $ROOT_DIR/scripts/get_python_cmake_flags.py)\ncmake .. ${CMAKE_ARGS[*]}\n\n# Build\nif [ \"$TRAVIS_OS_NAME\" = 'linux' ]; then\n    make \"-j$(nproc)\" install\nelif [ \"$TRAVIS_OS_NAME\" = 'osx' ]; then\n    make \"-j$(sysctl -n hw.ncpu)\" install\nfi\n"
  },
  {
    "path": ".travis/install.sh",
    "content": "#!/bin/bash\nset -e\nset -x\n\nLOCAL_DIR=$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\nROOT_DIR=$(dirname \"$LOCAL_DIR\")\ncd \"$ROOT_DIR\"\n\nAPT_INSTALL_CMD='sudo apt-get install -y --no-install-recommends'\n\nif [ \"$TRAVIS_OS_NAME\" = 'linux' ]; then\n    ####################\n    # apt dependencies #\n    ####################\n    sudo apt-get update\n    $APT_INSTALL_CMD \\\n        asciidoc \\\n        autoconf \\\n        automake \\\n        build-essential \\\n        ca-certificates \\\n        ccache \\\n        docbook-xml \\\n        docbook-xsl \\\n        git \\\n        gperf \\\n        libatlas-base-dev \\\n        libgoogle-glog-dev \\\n        libiomp-dev \\\n        libleveldb-dev \\\n        liblmdb-dev \\\n        libopencv-dev \\\n        libprotobuf-dev \\\n        libpthread-stubs0-dev \\\n        libsnappy-dev \\\n        protobuf-compiler \\\n        python \\\n        python-dev \\\n        python-pip \\\n        python-wheel \\\n        software-properties-common \\\n        xsltproc\n\n    # Install ccache symlink wrappers\n    pushd /usr/local/bin\n    sudo ln -sf \"$(which ccache)\" gcc\n    sudo ln -sf \"$(which ccache)\" g++\n    popd\n\n    if [ \"$BUILD_GCC5\" = 'true' ]; then\n        ################\n        # Install GCC5 #\n        ################\n        sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test\n        sudo apt-get update\n        $APT_INSTALL_CMD g++-5\n        sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 60 \\\n            --slave /usr/bin/g++ g++ /usr/bin/g++-5\n    fi\n\n    if [ \"$BUILD_CUDA\" = 'true' ]; then\n        ##################\n        # Install ccache #\n        ##################\n        # Needs specific branch to work with nvcc (ccache/ccache#145)\n        if [ -e \"${BUILD_CCACHE_DIR}/ccache\" ]; then\n            echo \"Using cached ccache build at \\\"$BUILD_CCACHE_DIR\\\" ...\"\n        else\n            git clone https://github.com/colesbury/ccache -b ccbin \"$BUILD_CCACHE_DIR\"\n            pushd \"$BUILD_CCACHE_DIR\"\n            ./autogen.sh\n            ./configure\n            make \"-j$(nproc)\"\n            popd\n        fi\n\n        # Overwrite ccache symlink wrappers\n        pushd /usr/local/bin\n        sudo ln -sf \"${BUILD_CCACHE_DIR}/ccache\" gcc\n        sudo ln -sf \"${BUILD_CCACHE_DIR}/ccache\" g++\n        sudo ln -sf \"${BUILD_CCACHE_DIR}/ccache\" nvcc\n        popd\n\n        #################\n        # Install CMake #\n        #################\n        # Newer version required to get cmake+ccache+nvcc to work\n        _cmake_installer=/tmp/cmake.sh\n        wget -O \"$_cmake_installer\" https://cmake.org/files/v3.8/cmake-3.8.2-Linux-x86_64.sh\n        sudo bash \"$_cmake_installer\" --prefix=/usr/local --skip-license\n        rm -rf \"$_cmake_installer\"\n\n        ################\n        # Install CUDA #\n        ################\n        CUDA_REPO_PKG='cuda-repo-ubuntu1404_8.0.44-1_amd64.deb'\n        CUDA_PKG_VERSION='8-0'\n        CUDA_VERSION='8.0'\n        wget \"https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1404/x86_64/${CUDA_REPO_PKG}\"\n        sudo dpkg -i \"$CUDA_REPO_PKG\"\n        rm -f \"$CUDA_REPO_PKG\"\n        sudo apt-get update\n        $APT_INSTALL_CMD \\\n            \"cuda-core-${CUDA_PKG_VERSION}\" \\\n            \"cuda-cublas-dev-${CUDA_PKG_VERSION}\" \\\n            \"cuda-cudart-dev-${CUDA_PKG_VERSION}\" \\\n            \"cuda-curand-dev-${CUDA_PKG_VERSION}\" \\\n            \"cuda-driver-dev-${CUDA_PKG_VERSION}\" \\\n            \"cuda-nvrtc-dev-${CUDA_PKG_VERSION}\"\n        # Manually create CUDA symlink\n        sudo ln -sf /usr/local/cuda-$CUDA_VERSION /usr/local/cuda\n\n        #################\n        # Install cuDNN #\n        #################\n        CUDNN_REPO_PKG='nvidia-machine-learning-repo-ubuntu1404_4.0-2_amd64.deb'\n        CUDNN_PKG_VERSION='6.0.20-1+cuda8.0'\n        wget \"https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1404/x86_64/${CUDNN_REPO_PKG}\"\n        sudo dpkg -i \"$CUDNN_REPO_PKG\"\n        rm -f \"$CUDNN_REPO_PKG\"\n        sudo apt-get update\n        $APT_INSTALL_CMD \\\n            \"libcudnn6=${CUDNN_PKG_VERSION}\" \\\n            \"libcudnn6-dev=${CUDNN_PKG_VERSION}\"\n    fi\n\n    if [ \"$BUILD_MKL\" = 'true' ]; then\n        ###############\n        # Install MKL #\n        ###############\n        _mkl_key=/tmp/mkl.pub\n        wget -O \"$_mkl_key\" http://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS-2019.PUB\n        sudo apt-key add \"$_mkl_key\"\n        rm -f \"$_mkl_key\"\n        echo 'deb http://apt.repos.intel.com/mkl all main' | sudo tee /etc/apt/sources.list.d/intel-mkl.list\n        sudo apt-get update\n        $APT_INSTALL_CMD intel-mkl-64bit\n    fi\nelif [ \"$TRAVIS_OS_NAME\" = 'osx' ]; then\n    #####################\n    # brew dependencies #\n    #####################\n    brew update\n    brew install python\n    pip uninstall -y numpy  # use brew version (opencv dependency)\n    brew tap homebrew/science  # for OpenCV\n    brew install \\\n        ccache \\\n        glog \\\n        leveldb \\\n        lmdb \\\n        protobuf\n\n    # Install ccache symlink wrappers\n    pushd /usr/local/bin\n    sudo ln -sf \"$(which ccache)\" clang\n    sudo ln -sf \"$(which ccache)\" clang++\n    popd\nelse\n    echo \"OS \\\"$TRAVIS_OS_NAME\\\" is unknown\"\n    exit 1\nfi\n\n####################\n# pip dependencies #\n####################\nsudo pip install \\\n    future \\\n    hypothesis \\\n    numpy \\\n    protobuf \\\n    pytest \\\n    scikit-image\n\nif [ \"$BUILD_ANDROID\" = 'true' ]; then\n    #######################\n    # Install Android NDK #\n    #######################\n    _ndk_zip=/tmp/ndk.zip\n    if [ \"$TRAVIS_OS_NAME\" = 'linux' ]; then\n        $APT_INSTALL_CMD autotools-dev autoconf\n        wget -O \"$_ndk_zip\" https://dl.google.com/android/repository/android-ndk-r13b-linux-x86_64.zip\n    elif [ \"$TRAVIS_OS_NAME\" = 'osx' ]; then\n        brew install libtool\n        wget -O \"$_ndk_zip\" https://dl.google.com/android/repository/android-ndk-r13b-darwin-x86_64.zip\n    else\n        echo \"OS \\\"$TRAVIS_OS_NAME\\\" is unknown\"\n        exit 1\n    fi\n    _ndk_dir=/opt/android_ndk\n    sudo mkdir -p \"$_ndk_dir\"\n    sudo chmod a+rwx \"$_ndk_dir\"\n    unzip -qo \"$_ndk_zip\" -d \"$_ndk_dir\"\n    rm -f \"$_ndk_zip\"\n    _versioned_dir=$(find $_ndk_dir/ -mindepth 1 -maxdepth 1 -type d)\n    mv \"$_versioned_dir\"/* \"$_ndk_dir\"/\n    rmdir \"$_versioned_dir\"\nfi\n\nif [ \"$BUILD_NNPACK\" = 'true' ]; then\n    #################\n    # Install ninja #\n    #################\n    if [ \"$TRAVIS_OS_NAME\" = 'linux' ]; then\n        # NNPACK needs a recent version\n        if [ -e \"${BUILD_NINJA_DIR}/ninja\" ]; then\n            echo \"Using cached ninja build at \\\"$BUILD_NINJA_DIR\\\" ...\"\n        else\n            git clone https://github.com/ninja-build/ninja.git -b release \"$BUILD_NINJA_DIR\"\n            pushd \"$BUILD_NINJA_DIR\"\n            python configure.py --bootstrap\n            popd\n        fi\n        sudo install -m 755 \"${BUILD_NINJA_DIR}/ninja\" /usr/local/bin/ninja\n    elif [ \"$TRAVIS_OS_NAME\" = 'osx' ]; then\n        brew install ninja\n    else\n        echo \"OS \\\"$TRAVIS_OS_NAME\\\" is unknown\"\n        exit 1\n    fi\n    sudo pip install git+https://github.com/Maratyszcza/PeachPy\n    sudo pip install git+https://github.com/Maratyszcza/confu\nfi\n"
  },
  {
    "path": ".travis/setup.sh",
    "content": "#!/bin/bash\n# This script should be sourced, not executed\nset -e\n\nexport BUILD_ANDROID=false\nexport BUILD_CUDA=false\nexport BUILD_GCC5=false\nexport BUILD_IOS=false\nexport BUILD_MKL=false\nexport BUILD_NNPACK=true\nexport BUILD_TESTS=true\n\nif [ \"$BUILD\" = 'linux' ]; then\n    :\nelif [ \"$BUILD\" = 'linux-gcc5' ]; then\n    export BUILD_GCC5=true\nelif [ \"$BUILD\" = 'linux-cuda' ]; then\n    export BUILD_CUDA=true\n    export BUILD_NNPACK=false\n    export BUILD_TESTS=false\nelif [ \"$BUILD\" = 'linux-mkl' ]; then\n    export BUILD_MKL=true\n    export BUILD_TESTS=false\nelif [ \"$BUILD\" = 'linux-android' ]; then\n    export BUILD_ANDROID=true\n    export BUILD_TESTS=false\nelif [ \"$BUILD\" = 'osx' ]; then\n    # TODO(lukeyeager): enable after caffe2/caffe2#785\n    export BUILD_TESTS=false\n    # Since Python 2.7.14, HomeBrew does not link python and pip in /usr/local/bin/,\n    # but they are available in /usr/local/opt/python/libexec/bin/\n    export PATH=\"/usr/local/opt/python/libexec/bin:${PATH}\"\nelif [ \"$BUILD\" = 'osx-ios' ]; then\n    export BUILD_IOS=true\n    export BUILD_TESTS=false\n    # Since Python 2.7.14, HomeBrew does not link python and pip in /usr/local/bin/,\n    # but they are available in /usr/local/opt/python/libexec/bin/\n    export PATH=\"/usr/local/opt/python/libexec/bin:${PATH}\"\nelif [ \"$BUILD\" = 'osx-android' ]; then\n    export BUILD_ANDROID=true\n    export BUILD_TESTS=false\n    # Since Python 2.7.14, HomeBrew does not link python and pip in /usr/local/bin/,\n    # but they are available in /usr/local/opt/python/libexec/bin/\n    export PATH=\"/usr/local/opt/python/libexec/bin:${PATH}\"\nelse\n    echo \"BUILD \\\"$BUILD\\\" is unknown\"\n    exit 1\nfi\n"
  },
  {
    "path": ".travis/test.sh",
    "content": "#!/bin/bash\nset -e\nset -x\n\nLOCAL_DIR=$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\nROOT_DIR=$(dirname \"$LOCAL_DIR\")\ncd \"$ROOT_DIR\"\n\nif [ \"$BUILD_TESTS\" = 'false' ]; then\n    echo 'Skipping tests'\n    exit 0\nfi\n\n# Ctests\npushd build\nCTEST_OUTPUT_ON_FAILURE=1 make test\npopd\n\n# Python tests\nexport PYTHONPATH=\"${PYTHONPATH}:${ROOT_DIR}/install\"\nexport LD_LIBRARY_PATH=\"${LD_LIBRARY_PATH}:${ROOT_DIR}/install/lib\"\npython -m pytest -v install/caffe2/python\n"
  },
  {
    "path": ".travis.yml",
    "content": "os: linux\ndist: trusty\nsudo: required\nlanguage: cpp\ncompiler: gcc\n\nenv:\n  global:\n    - BUILD_CCACHE_DIR=~/build/ccache\n    - BUILD_NINJA_DIR=~/build/ninja\n  matrix:\n    - BUILD=linux\n    - BUILD=linux-gcc5\n    - BUILD=linux-cuda\n    - BUILD=linux-mkl\n    - BUILD=linux-android\n\nmatrix:\n  include:\n    - env: BUILD=osx\n      os: osx\n      osx_image: xcode8.3\n      compiler: clang\n    - env: BUILD=osx-ios\n      os: osx\n      osx_image: xcode8.3\n      compiler: clang\n    - env: BUILD=osx-android\n      os: osx\n      osx_image: xcode8.3\n      compiler: clang\n\ncache:\n  directories:\n    - $BUILD_CCACHE_DIR\n    - $BUILD_NINJA_DIR\n    - $HOME/.ccache\n\nbefore_install:\n  - source .travis/setup.sh\n\ninstall:\n  - ./.travis/install.sh\n  - ./.travis/build.sh\n\nscript:\n  - ./.travis/test.sh\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.2 FATAL_ERROR)\n#cmake_policy(SET CMP0022 NEW)\n#cmake_policy(SET CMP0023 NEW)\n\n# ---[ Project and semantic versioning.\nproject(Caffe2 CXX C)\n\nset(CAFFE2_VERSION_MAJOR 0)\nset(CAFFE2_VERSION_MINOR 8)\nset(CAFFE2_VERSION_PATCH 2)\nset(CAFFE2_VERSION\n    \"${CAFFE2_VERSION_MAJOR}.${CAFFE2_VERSION_MINOR}.${CAFFE2_VERSION_PATCH}\")\n\n# One variable that determines whether the current cmake process is being run\n# with the main Caffe2 library. This is useful for building modules - if\n# modules are built with the main Caffe2 library then one does not need to do\n# find caffe2 in the cmake script. One can usually guard it in some way like\n#    if (NOT CAFFE2_CMAKE_BUILDING_WITH_MAIN_REPO)\n#      find_package(Caffe2 REQUIRED)\n#    endif()\nset(CAFFE2_CMAKE_BUILDING_WITH_MAIN_REPO ON)\n\n# ---[ Options.\n# Note to developers: if you add an option below, make sure you also add it to\n# cmake/Summary.cmake so that the summary prints out the option values.\ninclude(CMakeDependentOption)\noption(BUILD_BINARY \"Build C++ binaries\" ON)\noption(BUILD_DOCS \"Build documentation\" OFF)\noption(BUILD_CUSTOM_PROTOBUF \"If set, build Caffe2's own protobuf under third_party\" OFF)\noption(BUILD_PYTHON \"Build Python binaries\" ON)\noption(BUILD_SHARED_LIBS \"Build libcaffe2.so\" ON)\ncmake_dependent_option(\n    CAFFE2_USE_MSVC_STATIC_RUNTIME \"Using MSVC static runtime libraries\" ON\n    \"NOT BUILD_SHARED_LIBS\" OFF)\noption(BUILD_TEST \"Build C++ test binaries (need gtest and gbenchmark)\" ON)\noption(USE_ACL \"Use ARM Compute Library\" OFF)\noption(USE_ASAN \"Use Address Sanitizer\" OFF)\noption(USE_ATEN \"Use ATen\" OFF)\noption(USE_CUDA \"Use Cuda\" ON)\noption(USE_FFMPEG \"Use ffmpeg\" OFF)\noption(USE_GFLAGS \"Use GFLAGS\" ON)\noption(USE_GLOG \"Use GLOG\" ON)\noption(USE_GLOO \"Use Gloo\" ON)\noption(USE_LEVELDB \"Use LEVELDB\" ON)\noption(USE_LITE_PROTO \"Use lite protobuf instead of full.\" OFF)\noption(USE_LMDB \"Use LMDB\" ON)\noption(USE_METAL \"Use Metal for iOS build\" ON)\noption(USE_MOBILE_OPENGL \"Use OpenGL for mobile code\" ON)\noption(USE_MPI \"Use MPI\" ON)\noption(USE_NATIVE_ARCH \"Use -march=native\" OFF)\noption(USE_NCCL \"Use NCCL\" ON)\noption(USE_NERVANA_GPU \"Use Nervana GPU backend\" OFF)\noption(USE_NNAPI \"Use NNAPI\" OFF)\noption(USE_NNPACK \"Use NNPACK\" ON)\noption(USE_NUMA \"Use NUMA (only available on Linux)\" ON)\noption(USE_OBSERVERS \"Use observers module.\" OFF)\noption(USE_OPENCV \"Use openCV\" ON)\noption(USE_OPENMP \"Use OpenMP for parallel code\" OFF)\noption(USE_PROF \"Use profiling\" OFF)\noption(USE_REDIS \"Use Redis\" OFF)\noption(USE_ROCKSDB \"Use RocksDB\" OFF)\noption(USE_SNPE \"Use Qualcomm's SNPE library\" OFF)\noption(USE_THREADS \"Use Threads\" ON)\noption(USE_ZMQ \"Use ZMQ\" OFF)\noption(USE_ZSTD \"Use ZSTD\" OFF)\n\n# ---[ CMake scripts + modules\nlist(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)\n\nif (MSVC AND ${BUILD_SHARED_LIBS})\n  set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)\nendif()\n\n# ---[ CMake build directories\nset(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)\nset(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)\n\nenable_testing()\n\n# ---[ Misc checks to cope with various compiler modes\ninclude(cmake/MiscCheck.cmake)\ninclude(cmake/BuildVariables.cmake)\n\n# External projects\ninclude(ExternalProject)\n\n# TODO: merge the following 3 files into cmake/public/utils.cmake.\ninclude(cmake/Utils.cmake)\ninclude(cmake/public/utils.cmake)\n\nset(CAFFE2_WHITELIST \"\" CACHE STRING \"A whitelist file of files that one should build.\")\n\n# Set default build type\nif(NOT CMAKE_BUILD_TYPE)\n    message(STATUS \"Build type not set - defaulting to Release\")\n    set(CMAKE_BUILD_TYPE \"Release\" CACHE STRING \"Choose the type of build from: Debug Release RelWithDebInfo MinSizeRel Coverage.\" FORCE)\nendif()\n\n# ---[ Dependencies\ninclude(cmake/Dependencies.cmake)\n\n# ---[ Whitelist file if whitelist is specified\ninclude(cmake/Whitelist.cmake)\n\n# ---[ Set link flag, handle additional deps for gcc 4.8 and above\nif(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.8.0 AND NOT ANDROID)\n  message(STATUS \"GCC ${CMAKE_CXX_COMPILER_VERSION}: Adding gcc and gcc_s libs to link line\")\n  list(APPEND Caffe2_DEPENDENCY_LIBS gcc_s gcc)\nendif()\n\n# ---[ Build flags\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_CXX_STANDARD 11)\nif(NOT MSVC)\n  set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -O2 -fPIC\")\n  set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-narrowing\")\n  # Eigen fails to build with some versions, so convert this to a warning\n  # Details at http://eigen.tuxfamily.org/bz/show_bug.cgi?id=1459\n  set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-invalid-partial-specialization\")\nelse()\n  foreach(flag_var\n      CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE\n      CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)\n    if (${CAFFE2_USE_MSVC_STATIC_RUNTIME})\n      if(${flag_var} MATCHES \"/MD\")\n        string(REGEX REPLACE \"/MD\" \"/MT\" ${flag_var} \"${${flag_var}}\")\n      endif(${flag_var} MATCHES \"/MD\")\n    else()\n      if(${flag_var} MATCHES \"/MT\")\n        string(REGEX REPLACE \"/MT\" \"/MD\" ${flag_var} \"${${flag_var}}\")\n      endif()\n    endif()\n    set(${flag_var} \"${${flag_var}} /MP /bigobj\")\n  endforeach(flag_var)\nendif()\n\nif(ANDROID)\n  if(CMAKE_COMPILER_IS_GNUCXX)\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -s\")\n  else()\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -s\")\n  endif()\nendif()\n\nif(NOT APPLE AND UNIX)\n  list(APPEND Caffe2_DEPENDENCY_LIBS dl)\nendif()\n\n# Prefix path to Caffe2 headers.\n# If a directory containing installed Caffe2 headers was inadvertently\n# added to the list of include directories, prefixing\n# PROJECT_SOURCE_DIR means this source tree always takes precedence.\ninclude_directories(BEFORE ${PROJECT_SOURCE_DIR})\n\n# Prefix path to generated Caffe2 headers.\n# These need to take precedence over their empty counterparts located\n# in PROJECT_SOURCE_DIR.\ninclude_directories(BEFORE ${PROJECT_BINARY_DIR})\n\n# ---[ Old caffe protobuf.\nadd_subdirectory(caffe/proto)\n\n# ---[ Main build\nadd_subdirectory(caffe2)\n\n# Documentation Option\nif(BUILD_DOCS)\n  # check if Doxygen is installed\n  find_package(Doxygen)\n  if (DOXYGEN_FOUND)\n    message(\"Generating documentation\")\n\n    set(DOXYGEN_C_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/.Doxyfile-c)\n    set(DOXYGEN_C_OUT ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile-c)\n    set(DOXYGEN_P_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/.Doxyfile-python)\n    set(DOXYGEN_P_OUT ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile-python)\n\n    if(EXISTS ${CMAKE_CURRENT_BINARY_DIR}/docs)\n      file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/docs)\n    endif (EXISTS ${CMAKE_CURRENT_BINARY_DIR}/docs)\n\n    file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs)\n    configure_file(${DOXYGEN_C_IN} ${DOXYGEN_C_OUT} @ONLY)\n    configure_file(${DOXYGEN_P_IN} ${DOXYGEN_P_OUT} @ONLY)\n\n    add_custom_target(doc_doxygen_c ALL\n        COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_C_OUT}\n        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n        COMMENT \"Generating C++ API documentation with Doxygen\"\n        VERBATIM)\n\n    add_custom_target(doc_doxygen_python ALL\n        COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_P_OUT}\n        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n        COMMENT \"Generating Python API documentation with Doxygen\"\n        VERBATIM)\n  else (DOXYGEN_FOUND)\n    message(FATAL_ERROR \"Doxygen needs to be installed to generate the documentation\")\n  endif (DOXYGEN_FOUND)\nendif (BUILD_DOCS)\n\n# ---[ CMake related files\n# Uninistall option.\nif(NOT TARGET caffe2_uninstall)\n  configure_file(\n      ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in\n      ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake\n      IMMEDIATE @ONLY)\n\n  add_custom_target(caffe2_uninstall\n      COMMAND ${CMAKE_COMMAND} -P\n      ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)\nendif()\n\n# ---[ Make configuration files for cmake to allow dependent libraries\n# easier access to Caffe2.\n\nif ((NOT USE_GLOG) OR (NOT USE_GFLAGS) OR BUILD_CUSTOM_PROTOBUF)\n  message(WARNING\n      \"Generated cmake files are only fully tested if one builds \"\n      \"with system glog, gflags, and protobuf. Other settings may \"\n      \"generate files that are not well tested.\")\nendif()\n\nif (USE_CUDA)\n  # TODO: check if we should include other cuda dependency libraries\n  # to the interface as well.\n\nendif()\n\n# Note(jiayq): when building static libraries, all PRIVATE dependencies\n# will also become interface libraries, and as a result if there are any\n# dependency libraries that are not exported, the following install export\n# script will fail. As a result, we will only provide the targets cmake\n# files for shared lib installation. For more info, read:\n# https://cmake.org/pipermail/cmake/2016-May/063400.html\nif (BUILD_SHARED_LIBS)\n  configure_file(\n      ${PROJECT_SOURCE_DIR}/cmake/Caffe2ConfigVersion.cmake.in\n      ${PROJECT_BINARY_DIR}/Caffe2ConfigVersion.cmake\n      @ONLY)\n  configure_file(\n      ${PROJECT_SOURCE_DIR}/cmake/Caffe2Config.cmake.in\n      ${PROJECT_BINARY_DIR}/Caffe2Config.cmake\n      @ONLY)\n  install(FILES\n      ${PROJECT_BINARY_DIR}/Caffe2ConfigVersion.cmake\n      ${PROJECT_BINARY_DIR}/Caffe2Config.cmake\n      DESTINATION share/cmake/Caffe2\n      COMPONENT dev)\n  install(FILES\n      ${PROJECT_SOURCE_DIR}/cmake/public/cuda.cmake\n      ${PROJECT_SOURCE_DIR}/cmake/public/glog.cmake\n      ${PROJECT_SOURCE_DIR}/cmake/public/gflags.cmake\n      ${PROJECT_SOURCE_DIR}/cmake/public/protobuf.cmake\n      ${PROJECT_SOURCE_DIR}/cmake/public/threads.cmake\n      ${PROJECT_SOURCE_DIR}/cmake/public/utils.cmake\n      DESTINATION share/cmake/Caffe2/public\n      COMPONENT dev)\n  install(EXPORT Caffe2Targets DESTINATION share/cmake/Caffe2\n      FILE Caffe2Targets.cmake\n      COMPONENT dev)\nelse()\n  message(WARNING\n      \"Generated cmake files are only available when building \"\n      \"shared libs.\")\nendif()\n\n# ---[ Modules\nadd_subdirectory(modules)\n\n# ---[ Binaries\n# Binaries will be built after the Caffe2 main libraries and the modules\n# are built. For the binaries, they will be linked to the Caffe2 main\n# libraries, as well as all the modules that are built with Caffe2 (the ones\n# built in the previous Modules section above).\n\nif (BUILD_BINARY)\n  add_subdirectory(binaries)\nendif()\n\ninclude(cmake/Summary.cmake)\ncaffe2_print_configuration_summary()\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "# This makefile does nothing but delegating the actual building to cmake.\n\nall:\n\t@mkdir -p build && cd build && cmake .. $(shell python ./scripts/get_python_cmake_flags.py) && $(MAKE)\n\nlocal:\n\t@./scripts/build_local.sh\n\nandroid:\n\t@./scripts/build_android.sh\n\nios:\n\t@./scripts/build_ios.sh\n\nclean: # This will remove ALL build folders.\n\t@rm -r build*/\n\nlinecount:\n\t@cloc --read-lang-def=caffe.cloc caffe2 || \\\n\t\techo \"Cloc is not available on the machine. You can install cloc with \" && \\\n\t\techo \"    sudo apt-get install cloc\"\n"
  },
  {
    "path": "NOTICE",
    "content": "Copyright (c) 2016-present, Facebook Inc. All rights reserved.\n\nAll contributions by Facebook:\nCopyright (c) 2016 Facebook Inc.\n \nAll contributions by Google:\nCopyright (c) 2015 Google Inc.\nAll rights reserved.\n \nAll contributions by Yangqing Jia:\nCopyright (c) 2015 Yangqing Jia\nAll rights reserved.\n \nAll contributions from Caffe:\nCopyright(c) 2013, 2014, 2015, the respective contributors\nAll rights reserved.\n \nAll other contributions:\nCopyright(c) 2015, 2016 the respective contributors\nAll rights reserved.\n \nCaffe2 uses a copyright model similar to Caffe: each contributor holds\ncopyright over their contributions to Caffe2. The project versioning records\nall such contribution and copyright details. If a contributor wants to further\nmark their specific copyright on a particular contribution, they should\nindicate their copyright solely in the commit message of the change when it is\ncommitted.\n\n=======================================================================\nSoftware under third_party\n=======================================================================\nSoftware libraries under third_party are provided as github submodule\nlinks, and their content is not part of the Caffe2 codebase. Their\nlicences can be found under the respective software repositories.\n\n=======================================================================\nEarlier BSD License\n=======================================================================\nEarly development of Caffe2 in 2015 and early 2016 is licensed under the\nBSD license. The license is attached below:\n\nAll contributions by Facebook:\nCopyright (c) 2016 Facebook Inc.\n\nAll contributions by Google:\nCopyright (c) 2015 Google Inc.\nAll rights reserved.\n\nAll contributions by Yangqing Jia:\nCopyright (c) 2015 Yangqing Jia\nAll rights reserved.\n\nAll other contributions:\nCopyright(c) 2015, 2016 the respective contributors\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n=======================================================================\nCaffe's BSD License\n=======================================================================\nSome parts of the caffe2 code is derived from the original Caffe code, which is\ncreated by Yangqing Jia and is now a BSD-licensed open-source project. The Caffe\nlicense is as follows:\n\nCOPYRIGHT\n\nAll contributions by the University of California:\nCopyright (c) 2014, The Regents of the University of California (Regents)\nAll rights reserved.\n\nAll other contributions:\nCopyright (c) 2014, the respective contributors\nAll rights reserved.\n\nCaffe uses a shared copyright model: each contributor holds copyright over\ntheir contributions to Caffe. The project versioning records all such\ncontribution and copyright details. If a contributor wants to further mark\ntheir specific copyright on a particular contribution, they should indicate\ntheir copyright solely in the commit message of the change when it is\ncommitted.\n\nLICENSE\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nCONTRIBUTION AGREEMENT\n\nBy contributing to the BVLC/caffe repository through pull-request, comment,\nor otherwise, the contributor releases their content to the\nlicense and copyright terms herein.\n\n"
  },
  {
    "path": "README.md",
    "content": "# Caffe2\n\n[![License](https://img.shields.io/badge/License-Apache%202.0-brightgreen.svg)](https://opensource.org/licenses/Apache-2.0)\n[![Jenkins Build Status](https://ci.pytorch.org/jenkins/job/caffe2-master/badge/icon)](https://ci.pytorch.org/jenkins/job/caffe2-master)\n[![Appveyor Build Status](https://img.shields.io/appveyor/ci/Yangqing/caffe2.svg)](https://ci.appveyor.com/project/Yangqing/caffe2)\n\nCaffe2 is a lightweight, modular, and scalable deep learning framework. Building on the original [Caffe](http://caffe.berkeleyvision.org), Caffe2 is designed with expression, speed, and modularity in mind.\n\n## Questions and Feedback\n\nPlease use Github issues (https://github.com/caffe2/caffe2/issues) to ask questions, report bugs, and request new features.\n\nPlease participate in our survey (https://www.surveymonkey.com/r/caffe2). We will send you information about new releases and special developer events/webinars.\n\n\n## License\n\nCaffe2 is released under the [Apache 2.0 license](https://github.com/caffe2/caffe2/blob/master/LICENSE). See the [NOTICE](https://github.com/caffe2/caffe2/blob/master/NOTICE) file for details.\n\n### Further Resources on [Caffe2.ai](http://caffe2.ai)\n\n* [Installation](http://caffe2.ai/docs/getting-started.html)\n* [Learn More](http://caffe2.ai/docs/learn-more.html)\n* [Upgrading to Caffe2](http://caffe2.ai/docs/caffe-migration.html)\n* [Datasets](http://caffe2.ai/docs/datasets.html)\n* [Model Zoo](http://caffe2.ai/docs/zoo.html)\n* [Tutorials](http://caffe2.ai/docs/tutorials.html)\n* [Operators Catalogue](http://caffe2.ai/docs/operators-catalogue.html)\n* [C++ API](http://caffe2.ai/doxygen-c/html/classes.html)\n* [Python API](http://caffe2.ai/doxygen-python/html/namespaces.html)\n"
  },
  {
    "path": "VERSION_NUMBER",
    "content": "0.8.1"
  },
  {
    "path": "appveyor.yml",
    "content": "version: '{build}'\nclone_folder: c:\\projects\\caffe2\nenvironment:\n  matrix:\n    - USE_CUDA: OFF\n      CMAKE_BUILD_TYPE: Release\n      APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017\n\n    # Building CUDA with Visual Studio 2017 is yet to be supported by\n    # NVidia, so we canot enable it right now.\n    #- USE_CUDA: ON\n    #  CMAKE_BUILD_TYPE: Release\n    #  APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017\n    \n    # Building CUDA currently causes a timeout in appveyor. In the interest\n    # of properly monitoring the rest, we will disable cuda contbuild for now.\n    #- USE_CUDA: ON\n    #  CMAKE_BUILD_TYPE: Release\n    #  APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015\n\n    - USE_CUDA: OFF\n      CMAKE_BUILD_TYPE: Release\n      APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015\n\n    # Debug build is not a top priority for us right now, so in the\n    # interest of contbuild time, we disable it.\n    #- USE_CUDA: OFF\n    #  CMAKE_BUILD_TYPE: Debug\n    #  APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017\n\n    # Currently, CUDA + Debug does not work due to an error of using\n    # std::_Debug_lt in device code. Not sure where this comes from yet,\n    # but it is probably safe to assume that very few are going to build\n    # debug mode with CUDA and Windows.\n    #- USE_CUDA: ON\n    #  CMAKE_BUILD_TYPE: Debug\n\ninstall:\n- cmd: c:\\projects\\caffe2\\scripts\\appveyor\\install.bat\n\nbuild_script:\n- cmd: >-\n    cd c:\\projects\\caffe2\n\n    git submodule update --init\n\n    call scripts\\build_windows.bat\n"
  },
  {
    "path": "binaries/CMakeLists.txt",
    "content": "caffe2_binary_target(\"convert_caffe_image_db.cc\")\ncaffe2_binary_target(\"convert_db.cc\")\ncaffe2_binary_target(\"make_cifar_db.cc\")\ncaffe2_binary_target(\"make_mnist_db.cc\")\ncaffe2_binary_target(\"predictor_verifier.cc\")\ncaffe2_binary_target(\"print_registered_core_operators.cc\")\ncaffe2_binary_target(\"run_plan.cc\")\ncaffe2_binary_target(\"speed_benchmark.cc\")\ncaffe2_binary_target(\"split_db.cc\")\n\ncaffe2_binary_target(\"db_throughput.cc\")\n\nif (USE_CUDA)\n  caffe2_binary_target(\"inspect_gpus.cc\")\n  target_link_libraries(inspect_gpus ${CUDA_LIBRARIES})\n  caffe2_binary_target(\"print_core_object_sizes.cc\")\n\n  if (BUILD_TEST)\n    # Core overhead benchmark\n    caffe2_binary_target(\"core_overhead_benchmark.cc\")\n    target_link_libraries(core_overhead_benchmark benchmark ${CUDA_curand_LIBRARY})\n  endif()\nendif()\n\nif (USE_ZMQ)\n  caffe2_binary_target(\"zmq_feeder.cc\")\n  target_link_libraries(zmq_feeder ${ZMQ_LIBRARIES})\nendif()\n\nif(USE_MPI)\n  caffe2_binary_target(\"run_plan_mpi.cc\")\n  target_link_libraries(run_plan_mpi ${MPI_CXX_LIBRARIES})\nendif()\n\nif (USE_OPENCV AND USE_LEVELDB)\n  caffe2_binary_target(\"convert_encoded_to_raw_leveldb.cc\")\n  target_link_libraries(\n      convert_encoded_to_raw_leveldb\n      ${OpenCV_LIBS} ${LevelDB_LIBRARIES} ${Snappy_LIBRARIES})\nendif()\n\nif (USE_OPENCV)\n  caffe2_binary_target(\"make_image_db.cc\")\n  target_link_libraries(make_image_db ${OpenCV_LIBS})\nendif()\n\nif (USE_OBSERVERS)\n  caffe2_binary_target(\"caffe2_benchmark.cc\")\nendif()\n\n# ---[ tutorials\ncaffe2_binary_target(\"tutorial_blob.cc\")\n"
  },
  {
    "path": "binaries/caffe2_benchmark.cc",
    "content": "#include <fstream>\n#include <iterator>\n#include <string>\n\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include \"caffe2/utils/string_utils.h\"\n\n#include \"observers/observer_config.h\"\n\nCAFFE2_DEFINE_string(\n    backend,\n    \"builtin\",\n    \"The backend to use when running the model. The allowed \"\n    \"backend choices are: builtin, default, nnpack, eigen, mkl\");\nCAFFE2_DEFINE_string(\n    init_net,\n    \"\",\n    \"The given net to initialize any parameters.\");\nCAFFE2_DEFINE_string(\n    input,\n    \"\",\n    \"Input that is needed for running the network. If \"\n    \"multiple input needed, use comma separated string.\");\nCAFFE2_DEFINE_string(\n    input_dims,\n    \"\",\n    \"Alternate to input_files, if all inputs are simple \"\n    \"float TensorCPUs, specify the dimension using comma \"\n    \"separated numbers. If multiple input needed, use \"\n    \"semicolon to separate the dimension of different \"\n    \"tensors.\");\nCAFFE2_DEFINE_string(\n    input_file,\n    \"\",\n    \"Input file that contain the serialized protobuf for \"\n    \"the input blobs. If multiple input needed, use comma \"\n    \"separated string. Must have the same number of items \"\n    \"as input does.\");\nCAFFE2_DEFINE_string(\n    input_type,\n    \"float\",\n    \"Input type when specifying the input dimension.\"\n    \"The supported types are float, uint8_t.\");\nCAFFE2_DEFINE_int(iter, 10, \"The number of iterations to run.\");\nCAFFE2_DEFINE_string(net, \"\", \"The given net to benchmark.\");\nCAFFE2_DEFINE_string(\n    output,\n    \"\",\n    \"Output that should be dumped after the execution \"\n    \"finishes. If multiple outputs are needed, use comma \"\n    \"separated string. If you want to dump everything, pass \"\n    \"'*' as the output value.\");\nCAFFE2_DEFINE_string(\n    output_folder,\n    \"\",\n    \"The folder that the output should be written to. This \"\n    \"folder must already exist in the file system.\");\nCAFFE2_DEFINE_bool(\n    run_individual,\n    false,\n    \"Whether to benchmark individual operators.\");\nCAFFE2_DEFINE_bool(\n    text_output,\n    false,\n    \"Whether to write out output in text format for regression purpose.\");\nCAFFE2_DEFINE_int(warmup, 0, \"The number of iterations to warm up.\");\n\nusing std::string;\nusing std::unique_ptr;\nusing std::vector;\n\nstatic void writeTextOutput(\n    caffe2::TensorCPU* tensor,\n    const string& output_prefix,\n    const string& name) {\n  string output_name = output_prefix + \"/\" + name + \".txt\";\n  caffe2::TensorSerializer<caffe2::CPUContext> ser;\n  caffe2::BlobProto blob_proto;\n  ser.Serialize(\n      *tensor, output_name, blob_proto.mutable_tensor(), 0, tensor->size());\n  blob_proto.set_name(output_name);\n  blob_proto.set_type(\"Tensor\");\n  CAFFE_ENFORCE(blob_proto.has_tensor());\n  caffe2::TensorProto tensor_proto = blob_proto.tensor();\n  vector<float> data;\n  switch (tensor_proto.data_type()) {\n    case caffe2::TensorProto::FLOAT: {\n      std::copy(\n          tensor_proto.float_data().begin(),\n          tensor_proto.float_data().end(),\n          std::back_inserter(data));\n      break;\n    }\n    case caffe2::TensorProto::INT32: {\n      std::copy(\n          tensor_proto.int32_data().begin(),\n          tensor_proto.int32_data().end(),\n          std::back_inserter(data));\n      break;\n    }\n    default:\n      CAFFE_THROW(\"Unimplemented Blob type.\");\n  }\n  std::ofstream output_file(output_name);\n  std::ostream_iterator<float> output_iterator(output_file, \"\\n\");\n  std::copy(data.begin(), data.end(), output_iterator);\n}\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n  caffe2::ShowLogInfoToStderr();\n  unique_ptr<caffe2::Workspace> workspace(new caffe2::Workspace());\n\n  // Run initialization network.\n  caffe2::NetDef init_net_def;\n  CAFFE_ENFORCE(ReadProtoFromFile(caffe2::FLAGS_init_net, &init_net_def));\n  CAFFE_ENFORCE(workspace->RunNetOnce(init_net_def));\n\n  // Load input.\n  if (caffe2::FLAGS_input.size()) {\n    vector<string> input_names = caffe2::split(',', caffe2::FLAGS_input);\n    if (caffe2::FLAGS_input_file.size()) {\n      vector<string> input_files = caffe2::split(',', caffe2::FLAGS_input_file);\n      CAFFE_ENFORCE_EQ(\n          input_names.size(),\n          input_files.size(),\n          \"Input name and file should have the same number.\");\n      for (int i = 0; i < input_names.size(); ++i) {\n        caffe2::BlobProto blob_proto;\n        CAFFE_ENFORCE(caffe2::ReadProtoFromFile(input_files[i], &blob_proto));\n        workspace->CreateBlob(input_names[i])->Deserialize(blob_proto);\n      }\n    } else if (caffe2::FLAGS_input_dims.size()) {\n      vector<string> input_dims_list =\n          caffe2::split(';', caffe2::FLAGS_input_dims);\n      CAFFE_ENFORCE_EQ(\n          input_names.size(),\n          input_dims_list.size(),\n          \"Input name and dims should have the same number of items.\");\n      for (int i = 0; i < input_names.size(); ++i) {\n        vector<string> input_dims_str = caffe2::split(',', input_dims_list[i]);\n        vector<int> input_dims;\n        for (const string& s : input_dims_str) {\n          input_dims.push_back(caffe2::stoi(s));\n        }\n        if (!workspace->HasBlob(input_names[i])) {\n          workspace->CreateBlob(input_names[i]);\n        }\n        caffe2::TensorCPU* tensor =\n            workspace->GetBlob(input_names[i])->GetMutable<caffe2::TensorCPU>();\n        tensor->Resize(input_dims);\n        if (caffe2::FLAGS_input_type == \"float\") {\n          tensor->mutable_data<float>();\n        } else {\n          CAFFE_ENFORCE(\n              caffe2::FLAGS_input_type == \"uint8_t\",\n              \"Only supported input types are: float, uint8_t\");\n          tensor->mutable_data<uint8_t>();\n        }\n      }\n    } else {\n      CAFFE_THROW(\n          \"You requested input tensors, but neither input_file nor \"\n          \"input_dims is set.\");\n    }\n  }\n\n  // Run main network.\n  caffe2::NetDef net_def;\n  CAFFE_ENFORCE(ReadProtoFromFile(caffe2::FLAGS_net, &net_def));\n  if (caffe2::FLAGS_backend != \"builtin\") {\n    std::string engine = caffe2::FLAGS_backend == \"nnpack\" ? \"NNPACK\" :\n                         caffe2::FLAGS_backend == \"eigen\" ? \"EIGEN\" :\n                         caffe2::FLAGS_backend == \"mkl\" ? \"MKLDNN\" :\n                         caffe2::FLAGS_backend == \"default\" ? \"\" : \"NONE\";\n     CAFFE_ENFORCE(engine != \"NONE\", \"Backend is not supported\");\n    for (int i = 0; i < net_def.op_size(); i++) {\n      caffe2::OperatorDef* op_def = net_def.mutable_op(i);\n      op_def->set_engine(engine);\n    }\n  }\n\n  caffe2::NetBase* net = workspace->CreateNet(net_def);\n  CHECK_NOTNULL(net);\n\n  LOG(INFO) << \"Starting benchmark.\";\n  caffe2::ObserverConfig::initSampleRate(\n      1, 1, 1, caffe2::FLAGS_run_individual, caffe2::FLAGS_warmup);\n  LOG(INFO) << \"Running warmup runs.\";\n  for (int i = 0; i < caffe2::FLAGS_warmup; ++i) {\n    CAFFE_ENFORCE(net->Run(), \"Warmup run \", i, \" has failed.\");\n  }\n\n  LOG(INFO) << \"Main runs.\";\n  CAFFE_ENFORCE(\n      caffe2::FLAGS_iter >= 0,\n      \"Number of main runs should be non negative, provided \",\n      caffe2::FLAGS_iter,\n      \".\");\n  for (int i = 0; i < caffe2::FLAGS_iter; ++i) {\n    caffe2::ObserverConfig::initSampleRate(1, 1, 1, 0, caffe2::FLAGS_warmup);\n    CAFFE_ENFORCE(net->Run(), \"Main run \", i, \" has failed.\");\n    if (caffe2::FLAGS_run_individual) {\n      caffe2::ObserverConfig::initSampleRate(1, 1, 1, 1, caffe2::FLAGS_warmup);\n      CAFFE_ENFORCE(net->Run(), \"Main run \", i, \" with operator has failed.\");\n    }\n  }\n\n  string output_prefix = caffe2::FLAGS_output_folder.size()\n      ? caffe2::FLAGS_output_folder + \"/\"\n      : \"\";\n  if (caffe2::FLAGS_output.size()) {\n    vector<string> output_names = caffe2::split(',', caffe2::FLAGS_output);\n    if (caffe2::FLAGS_output == \"*\") {\n      output_names = workspace->Blobs();\n    }\n    for (const string& name : output_names) {\n      CAFFE_ENFORCE(\n          workspace->HasBlob(name),\n          \"You requested a non-existing blob: \",\n          name);\n      if (caffe2::FLAGS_text_output) {\n        auto blob = workspace->GetBlob(name)->GetMutable<caffe2::TensorCPU>();\n        writeTextOutput(blob, output_prefix, name);\n      } else {\n        string serialized = workspace->GetBlob(name)->Serialize(name);\n        string output_filename = output_prefix + name;\n        caffe2::WriteStringToFile(serialized, output_filename.c_str());\n      }\n    }\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "binaries/convert_caffe_image_db.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe/proto/caffe.pb.h\"\n#include \"caffe2/core/logging.h\"\n\nCAFFE2_DEFINE_string(input_db, \"\", \"The input db.\");\nCAFFE2_DEFINE_string(input_db_type, \"\", \"The input db type.\");\nCAFFE2_DEFINE_string(output_db, \"\", \"The output db.\");\nCAFFE2_DEFINE_string(output_db_type, \"\", \"The output db type.\");\nCAFFE2_DEFINE_int(batch_size, 1000, \"The write batch size.\");\n\nusing caffe2::db::Cursor;\nusing caffe2::db::DB;\nusing caffe2::db::Transaction;\nusing caffe2::TensorProto;\nusing caffe2::TensorProtos;\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n\n  std::unique_ptr<DB> in_db(caffe2::db::CreateDB(\n      caffe2::FLAGS_input_db_type, caffe2::FLAGS_input_db, caffe2::db::READ));\n  std::unique_ptr<DB> out_db(caffe2::db::CreateDB(\n      caffe2::FLAGS_output_db_type, caffe2::FLAGS_output_db, caffe2::db::NEW));\n  std::unique_ptr<Cursor> cursor(in_db->NewCursor());\n  std::unique_ptr<Transaction> transaction(out_db->NewTransaction());\n  int count = 0;\n  for (; cursor->Valid(); cursor->Next()) {\n    caffe::Datum datum;\n    CAFFE_ENFORCE(datum.ParseFromString(cursor->value()));\n    TensorProtos protos;\n    TensorProto* data = protos.add_protos();\n    TensorProto* label = protos.add_protos();\n    label->set_data_type(TensorProto::INT32);\n    label->add_dims(1);\n    label->add_int32_data(datum.label());\n    if (datum.encoded()) {\n      // This is an encoded image. we will copy over the data directly.\n      data->set_data_type(TensorProto::STRING);\n      data->add_dims(1);\n      data->add_string_data(datum.data());\n    } else {\n      // float data not supported right now.\n      CAFFE_ENFORCE_EQ(datum.float_data_size(), 0);\n      std::vector<char> buffer_vec(datum.data().size());\n      char* buffer = buffer_vec.data();\n      // swap order from CHW to HWC\n      int channels = datum.channels();\n      int size = datum.height() * datum.width();\n      CAFFE_ENFORCE_EQ(datum.data().size(), channels * size);\n      for (int c = 0; c < channels; ++c) {\n        char* dst = buffer + c;\n        const char* src = datum.data().c_str() + c * size;\n        for (int n = 0; n < size; ++n) {\n          dst[n*channels] = src[n];\n        }\n      }\n      data->set_data_type(TensorProto::BYTE);\n      data->add_dims(datum.height());\n      data->add_dims(datum.width());\n      data->add_dims(datum.channels());\n      data->set_byte_data(buffer, datum.data().size());\n    }\n    transaction->Put(cursor->key(), protos.SerializeAsString());\n    if (++count % caffe2::FLAGS_batch_size == 0) {\n      transaction->Commit();\n      LOG(INFO) << \"Converted \" << count << \" items so far.\";\n    }\n  }\n  LOG(INFO) << \"A total of \" << count << \" items processed.\";\n  return 0;\n}\n\n"
  },
  {
    "path": "binaries/convert_db.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/core/logging.h\"\n\nCAFFE2_DEFINE_string(input_db, \"\", \"The input db.\");\nCAFFE2_DEFINE_string(input_db_type, \"\", \"The input db type.\");\nCAFFE2_DEFINE_string(output_db, \"\", \"The output db.\");\nCAFFE2_DEFINE_string(output_db_type, \"\", \"The output db type.\");\nCAFFE2_DEFINE_int(batch_size, 1000, \"The write batch size.\");\n\nusing caffe2::db::Cursor;\nusing caffe2::db::DB;\nusing caffe2::db::Transaction;\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n\n  std::unique_ptr<DB> in_db(caffe2::db::CreateDB(\n      caffe2::FLAGS_input_db_type, caffe2::FLAGS_input_db, caffe2::db::READ));\n  std::unique_ptr<DB> out_db(caffe2::db::CreateDB(\n      caffe2::FLAGS_output_db_type, caffe2::FLAGS_output_db, caffe2::db::NEW));\n  std::unique_ptr<Cursor> cursor(in_db->NewCursor());\n  std::unique_ptr<Transaction> transaction(out_db->NewTransaction());\n  int count = 0;\n  for (; cursor->Valid(); cursor->Next()) {\n    transaction->Put(cursor->key(), cursor->value());\n    if (++count % caffe2::FLAGS_batch_size == 0) {\n      transaction->Commit();\n      LOG(INFO) << \"Converted \" << count << \" items so far.\";\n    }\n  }\n  LOG(INFO) << \"A total of \" << count << \" items processed.\";\n  return 0;\n}\n"
  },
  {
    "path": "binaries/convert_encoded_to_raw_leveldb.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// This script converts an image dataset to leveldb.\n//\n// caffe2::FLAGS_input_folder is the root folder that holds all the images, and\n// caffe2::FLAGS_list_file should be a list of files as well as their labels, in the\n// format as\n//   subfolder1/file1.JPEG 7\n//   ....\n\n#include <opencv2/opencv.hpp>\n\n#include <fstream>  // NOLINT(readability/streams)\n#include <memory>\n#include <random>\n#include <string>\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/core/logging.h\"\n#include \"leveldb/db.h\"\n#include \"leveldb/write_batch.h\"\n\nCAFFE2_DEFINE_string(input_db_name, \"\", \"The input image file name.\");\nCAFFE2_DEFINE_string(output_db_name, \"\", \"The output training leveldb name.\");\nCAFFE2_DEFINE_bool(color, true, \"If set, load images in color.\");\nCAFFE2_DEFINE_int(scale, 256,\n    \"If caffe2::FLAGS_raw is set, scale all the images' shorter edge to the given \"\n    \"value.\");\nCAFFE2_DEFINE_bool(warp, false, \"If warp is set, warp the images to square.\");\n\n\nnamespace caffe2 {\n\nusing std::string;\nusing std::unique_ptr;\n\nvoid ConvertToRawDataset(\n    const string& input_db_name, const string& output_db_name) {\n  // input leveldb\n  std::unique_ptr<leveldb::DB> input_db;\n  LOG(INFO) << \"Opening input leveldb \" << input_db_name;\n  {\n    leveldb::Options options;\n    options.create_if_missing = false;\n    leveldb::DB* db_temp;\n    leveldb::Status status = leveldb::DB::Open(\n        options, input_db_name, &db_temp);\n    CAFFE_ENFORCE(status.ok(), \"Failed to open leveldb \", input_db_name, \".\");\n    input_db.reset(db_temp);\n  }\n\n  // output leveldb\n  std::unique_ptr<leveldb::DB> output_db;\n  std::unique_ptr<leveldb::WriteBatch> batch;\n  LOG(INFO) << \"Opening leveldb \" << output_db_name;\n  {\n    leveldb::Options options;\n    options.error_if_exists = true;\n    options.create_if_missing = true;\n    options.write_buffer_size = 268435456;\n    leveldb::DB* db_temp;\n    leveldb::Status status = leveldb::DB::Open(\n        options, output_db_name, &db_temp);\n    CAFFE_ENFORCE(\n        status.ok(),\n        \"Failed to open leveldb \",\n        output_db_name,\n        \". Is it already existing?\");\n    output_db.reset(db_temp);\n  }\n  batch.reset(new leveldb::WriteBatch());\n\n  TensorProtos input_protos;\n  TensorProtos output_protos;\n  TensorProto* data = output_protos.add_protos();\n  TensorProto* label = output_protos.add_protos();\n  data->set_data_type(TensorProto::BYTE);\n  data->add_dims(0);\n  data->add_dims(0);\n  if (caffe2::FLAGS_color) {\n    data->add_dims(3);\n  }\n  string value;\n\n  unique_ptr<leveldb::Iterator> iter;\n  iter.reset(input_db->NewIterator(leveldb::ReadOptions()));\n  iter->SeekToFirst();\n  int count = 0;\n  for (; iter->Valid(); iter->Next()) {\n    CAFFE_ENFORCE(input_protos.ParseFromString(iter->value().ToString()));\n    label->CopyFrom(input_protos.protos(1));\n    const string& encoded_image = input_protos.protos(0).string_data(0);\n    int encoded_size = encoded_image.size();\n    cv::Mat img = cv::imdecode(\n        cv::Mat(1, &encoded_size, CV_8UC1,\n        const_cast<char*>(encoded_image.data())),\n        caffe2::FLAGS_color ? CV_LOAD_IMAGE_COLOR : CV_LOAD_IMAGE_GRAYSCALE);\n    cv::Mat resized_img;\n    int scaled_width, scaled_height;\n    if (caffe2::FLAGS_warp) {\n      scaled_width = caffe2::FLAGS_scale;\n      scaled_height = caffe2::FLAGS_scale;\n    } else if (img.rows > img.cols) {\n      scaled_width = caffe2::FLAGS_scale;\n      scaled_height = static_cast<float>(img.rows) * caffe2::FLAGS_scale / img.cols;\n    } else {\n      scaled_height = caffe2::FLAGS_scale;\n      scaled_width = static_cast<float>(img.cols) * caffe2::FLAGS_scale / img.rows;\n    }\n    cv::resize(img, resized_img, cv::Size(scaled_width, scaled_height), 0, 0,\n                 cv::INTER_LINEAR);\n    data->set_dims(0, scaled_height);\n    data->set_dims(1, scaled_width);\n    DCHECK(resized_img.isContinuous());\n    data->set_byte_data(resized_img.ptr(),\n                        scaled_height * scaled_width * (caffe2::FLAGS_color ? 3 : 1));\n    output_protos.SerializeToString(&value);\n    // Put in db\n    batch->Put(iter->key(), value);\n    if (++count % 1000 == 0) {\n      output_db->Write(leveldb::WriteOptions(), batch.get());\n      batch.reset(new leveldb::WriteBatch());\n      LOG(INFO) << \"Processed \" << count << \" files.\";\n    }\n  }\n  // write the last batch\n  if (count % 1000 != 0) {\n    output_db->Write(leveldb::WriteOptions(), batch.get());\n  }\n  LOG(INFO) << \"Processed a total of \" << count << \" files.\";\n}\n\n}  // namespace caffe2\n\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n  caffe2::ConvertToRawDataset(\n      caffe2::FLAGS_input_db_name, caffe2::FLAGS_output_db_name);\n  return 0;\n}\n"
  },
  {
    "path": "binaries/core_overhead_benchmark.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"benchmark/benchmark.h\"\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/operator.h\"\n\n#define CAFFE2_SKIP_IF_NO_GPU                                      \\\n  if (!caffe2::NumCudaDevices()) {                                 \\\n    state.SkipWithError(\"No CUDA available, skipping benchmark.\"); \\\n    return;                                                        \\\n  }\n\nusing namespace caffe2;\n\nstatic void BM_CUDAContextCreation(benchmark::State& state) {\n  CAFFE2_SKIP_IF_NO_GPU;\n  volatile CUDAContext context_so_we_do_initialization_work;\n  while (state.KeepRunning()) {\n    volatile CUDAContext context;\n  }\n}\nBENCHMARK(BM_CUDAContextCreation);\n\nstatic void BM_CUDAContextStreamAccess(benchmark::State& state) {\n  CAFFE2_SKIP_IF_NO_GPU;\n  CUDAContext context;\n  while (state.KeepRunning()) {\n    volatile cudaStream_t stream = context.cuda_stream();\n  }\n}\nBENCHMARK(BM_CUDAContextStreamAccess);\n\nstatic void BM_cudaGetDevice(benchmark::State& state) {\n  CAFFE2_SKIP_IF_NO_GPU;\n  int id;\n  while (state.KeepRunning()) {\n    CUDA_ENFORCE(cudaGetDevice(&id));\n  }\n}\nBENCHMARK(BM_cudaGetDevice);\n\nstatic void BM_cudaSetDevice(benchmark::State& state) {\n  CAFFE2_SKIP_IF_NO_GPU;\n  int total = NumCudaDevices();\n  int i = 0;\n  while (state.KeepRunning()) {\n    CUDA_ENFORCE(cudaSetDevice((i++) % total));\n  }\n}\nBENCHMARK(BM_cudaSetDevice);\n\nstatic void BM_cudaSetAndGetDevice(benchmark::State& state) {\n  CAFFE2_SKIP_IF_NO_GPU;\n  int total = NumCudaDevices();\n  int i = 0;\n  int id;\n  while (state.KeepRunning()) {\n    CUDA_ENFORCE(cudaSetDevice((i++) % total));\n    CUDA_ENFORCE(cudaGetDevice(&id));\n  }\n}\nBENCHMARK(BM_cudaSetAndGetDevice);\n\nstatic void BM_cudaSetSameDevice(benchmark::State& state) {\n  CAFFE2_SKIP_IF_NO_GPU;\n  while (state.KeepRunning()) {\n    CUDA_ENFORCE(cudaSetDevice(0));\n  }\n}\nBENCHMARK(BM_cudaSetSameDevice);\n\nstatic void BM_cudaStreamCreateSyncDelete(benchmark::State& state) {\n  CAFFE2_SKIP_IF_NO_GPU;\n  cudaStream_t stream;\n  while (state.KeepRunning()) {\n    CUDA_ENFORCE(cudaStreamCreate(&stream));\n    CUDA_ENFORCE(cudaStreamSynchronize(stream));\n    CUDA_ENFORCE(cudaStreamDestroy(stream));\n  }\n}\nBENCHMARK(BM_cudaStreamCreateSyncDelete);\n\nstatic void BM_cudaStreamSynchronize(benchmark::State& state) {\n  CAFFE2_SKIP_IF_NO_GPU;\n  cudaStream_t stream;\n  CUDA_ENFORCE(cudaStreamCreate(&stream));\n  while (state.KeepRunning()) {\n    CUDA_ENFORCE(cudaStreamSynchronize(stream));\n  }\n}\nBENCHMARK(BM_cudaStreamSynchronize);\n\nstatic void BM_cudaEventRecord(benchmark::State& state) {\n  CAFFE2_SKIP_IF_NO_GPU;\n  cudaStream_t stream;\n  cudaEvent_t event;\n  CUDA_ENFORCE(cudaStreamCreate(&stream));\n  CUDA_ENFORCE(cudaEventCreateWithFlags(\n      &event, cudaEventDefault | cudaEventDisableTiming));\n  while (state.KeepRunning()) {\n    CUDA_ENFORCE(cudaEventRecord(event, stream));\n  }\n}\nBENCHMARK(BM_cudaEventRecord);\n\nstatic void BM_cudaStreamWaitEventThenStreamSynchronize(\n    benchmark::State& state) {\n  CAFFE2_SKIP_IF_NO_GPU;\n  cudaStream_t stream;\n  cudaEvent_t event;\n  CUDA_ENFORCE(cudaStreamCreate(&stream));\n  CUDA_ENFORCE(cudaEventCreateWithFlags(\n      &event, cudaEventDefault | cudaEventDisableTiming));\n  CUDA_ENFORCE(cudaEventRecord(event, stream));\n  CUDA_ENFORCE(cudaStreamWaitEvent(stream, event, 0));\n  CUDA_ENFORCE(cudaStreamSynchronize(stream));\n  while (state.KeepRunning()) {\n    CUDA_ENFORCE(cudaStreamWaitEvent(stream, event, 0));\n    CUDA_ENFORCE(cudaStreamSynchronize(stream));\n  }\n}\nBENCHMARK(BM_cudaStreamWaitEventThenStreamSynchronize);\n\nstatic void BM_CudaPointerAffinity(benchmark::State& state) {\n  CAFFE2_SKIP_IF_NO_GPU;\n  TensorCUDA tensor(vector<TIndex>{1, 2, 3, 4});\n  float* ptr = tensor.mutable_data<float>();\n  while (state.KeepRunning()) {\n    volatile int id = GetGPUIDForPointer(ptr);\n  }\n}\nBENCHMARK(BM_CudaPointerAffinity);\n\nnamespace {\ntemplate <class Context>\nclass DummyEmptyOp : public Operator<Context> {\n public:\n  DummyEmptyOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n\n  bool RunOnDevice() final { return true; }\n};\n\nREGISTER_CPU_OPERATOR(DummyEmpty, DummyEmptyOp<CPUContext>);\nREGISTER_CUDA_OPERATOR(DummyEmpty, DummyEmptyOp<CUDAContext>);\nOPERATOR_SCHEMA(DummyEmpty);\n}  // namespace\n\nstatic void BM_OperatorCreationCPU(benchmark::State& state) {\n  std::unique_ptr<OperatorBase> op;\n  OperatorDef def;\n  Workspace ws;\n  def.set_type(\"DummyEmpty\");\n  def.mutable_device_option()->set_device_type(CPU);\n  while (state.KeepRunning()) {\n    op = CreateOperator(def, &ws);\n  }\n}\nBENCHMARK(BM_OperatorCreationCPU);\n\nstatic void BM_OperatorCreationCUDA(benchmark::State& state) {\n  CAFFE2_SKIP_IF_NO_GPU;\n  std::unique_ptr<OperatorBase> op;\n  OperatorDef def;\n  Workspace ws;\n  def.set_type(\"DummyEmpty\");\n  def.mutable_device_option()->set_device_type(CUDA);\n  while (state.KeepRunning()) {\n    op = CreateOperator(def, &ws);\n  }\n}\nBENCHMARK(BM_OperatorCreationCUDA);\n\nstatic void BM_RawAllocDeallocCPU(benchmark::State& state) {\n  while (state.KeepRunning()) {\n    // Allocating only 1 byte in order to measure the overhead.\n    auto ptr_and_deleter = GetCPUAllocator()->New(1);\n    // Deallocate.\n    ptr_and_deleter.second(ptr_and_deleter.first);\n  }\n}\nBENCHMARK(BM_RawAllocDeallocCPU);\n\nstatic void BM_TensorAllocDeallocCPU(benchmark::State& state) {\n  Tensor<CPUContext> tensor;\n  // small allocation\n  tensor.Resize(32, 32);\n  while (state.KeepRunning()) {\n    CHECK(tensor.mutable_data<float>());\n    tensor.FreeMemory();\n  }\n}\nBENCHMARK(BM_TensorAllocDeallocCPU);\n\nstatic void BM_TensorAllocDeallocCUDA(benchmark::State& state) {\n  CAFFE2_SKIP_IF_NO_GPU;\n  Tensor<CUDAContext> tensor;\n  // small allocation\n  tensor.Resize(32, 32);\n  while (state.KeepRunning()) {\n    CHECK(tensor.mutable_data<float>());\n    tensor.FreeMemory();\n  }\n}\nBENCHMARK(BM_TensorAllocDeallocCUDA);\n\nBENCHMARK_MAIN()\n"
  },
  {
    "path": "binaries/db_throughput.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdio>\n#include <thread>\n#include <vector>\n\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/core/logging.h\"\n\nCAFFE2_DEFINE_string(input_db, \"\", \"The input db.\");\nCAFFE2_DEFINE_string(input_db_type, \"\", \"The input db type.\");\nCAFFE2_DEFINE_int(report_interval, 1000, \"The report interval.\");\nCAFFE2_DEFINE_int(repeat, 10, \"The number to repeat the throughput test.\");\nCAFFE2_DEFINE_bool(use_reader, false, \"If true, use the reader interface.\");\nCAFFE2_DEFINE_int(num_read_threads, 1,\n                   \"The number of concurrent reading threads.\");\n\nusing caffe2::db::Cursor;\nusing caffe2::db::DB;\nusing caffe2::db::DBReader;\nusing caffe2::string;\n\nvoid TestThroughputWithDB() {\n  std::unique_ptr<DB> in_db(caffe2::db::CreateDB(\n      caffe2::FLAGS_input_db_type, caffe2::FLAGS_input_db, caffe2::db::READ));\n  std::unique_ptr<Cursor> cursor(in_db->NewCursor());\n  for (int iter_id = 0; iter_id < caffe2::FLAGS_repeat; ++iter_id) {\n    caffe2::Timer timer;\n    for (int i = 0; i < caffe2::FLAGS_report_interval; ++i) {\n      string key = cursor->key();\n      string value = cursor->value();\n      //VLOG(1) << \"Key \" << key;\n      cursor->Next();\n      if (!cursor->Valid()) {\n        cursor->SeekToFirst();\n      }\n    }\n    double elapsed_seconds = timer.Seconds();\n    printf(\"Iteration %03d, took %4.5f seconds, throughput %f items/sec.\\n\",\n           iter_id, elapsed_seconds,\n           caffe2::FLAGS_report_interval / elapsed_seconds);\n  }\n}\n\nvoid TestThroughputWithReaderWorker(const DBReader* reader, int thread_id) {\n  string key, value;\n  for (int iter_id = 0; iter_id < caffe2::FLAGS_repeat; ++iter_id) {\n    caffe2::Timer timer;\n    for (int i = 0; i < caffe2::FLAGS_report_interval; ++i) {\n      reader->Read(&key, &value);\n    }\n    double elapsed_seconds = timer.Seconds();\n    printf(\"Thread %03d iteration %03d, took %4.5f seconds, \"\n           \"throughput %f items/sec.\\n\",\n           thread_id, iter_id, elapsed_seconds,\n           caffe2::FLAGS_report_interval / elapsed_seconds);\n  }\n}\n\nvoid TestThroughputWithReader() {\n  caffe2::db::DBReader reader(\n      caffe2::FLAGS_input_db_type, caffe2::FLAGS_input_db);\n  std::vector<std::unique_ptr<std::thread>> reading_threads(\n      caffe2::FLAGS_num_read_threads);\n  for (int i = 0; i < reading_threads.size(); ++i) {\n    reading_threads[i].reset(new std::thread(\n        TestThroughputWithReaderWorker, &reader, i));\n  }\n  for (int i = 0; i < reading_threads.size(); ++i) {\n    reading_threads[i]->join();\n  }\n}\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n  if (caffe2::FLAGS_use_reader) {\n    TestThroughputWithReader();\n  } else {\n    TestThroughputWithDB();\n  }\n  return 0;\n}\n"
  },
  {
    "path": "binaries/inspect_gpus.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cuda_runtime.h>\n\n#include <sstream>\n#include <vector>\n\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/logging.h\"\n\nusing std::vector;\n\nCAFFE2_DECLARE_int(caffe2_log_level);\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n  caffe2::SetUsageMessage(\n      \"Inspects the GPUs on the current machine and prints out their details \"\n      \"provided by cuda.\");\n\n  int gpu_count;\n  CUDA_ENFORCE(cudaGetDeviceCount(&gpu_count));\n  for (int i = 0; i < gpu_count; ++i) {\n    LOG(INFO) << \"Querying device ID = \" << i;\n    caffe2::DeviceQuery(i);\n  }\n\n  vector<vector<bool> > access_pattern;\n  CAFFE_ENFORCE(caffe2::GetCudaPeerAccessPattern(&access_pattern));\n\n  std::stringstream sstream;\n  // Find topology\n  for (int i = 0; i < gpu_count; ++i) {\n    for (int j = 0; j < gpu_count; ++j) {\n      sstream << (access_pattern[i][j] ? \"+\" : \"-\") << \" \";\n    }\n    sstream << std::endl;\n  }\n  LOG(INFO) << \"Access pattern: \" << std::endl << sstream.str();\n\n  return 0;\n}\n"
  },
  {
    "path": "binaries/make_cifar_db.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n//\n// This script converts the CIFAR dataset to the leveldb format used\n// by caffe to perform classification.\n// Usage:\n//    convert_cifar_data input_folder output_db_file\n// The CIFAR dataset could be downloaded at\n//    http://www.cs.toronto.edu/~kriz/cifar.html\n\n#include <array>\n#include <fstream>  // NOLINT(readability/streams)\n#include <sstream>\n#include <string>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/core/logging.h\"\n\nCAFFE2_DEFINE_string(input_folder, \"\", \"The input folder name.\");\nCAFFE2_DEFINE_string(output_train_db_name,\n                     \"\", \"The output training db name.\");\nCAFFE2_DEFINE_string(output_test_db_name,\n                     \"\", \"The output testing db name.\");\nCAFFE2_DEFINE_string(db, \"leveldb\", \"The db type.\");\nCAFFE2_DEFINE_bool(is_cifar100, false,\n            \"If set, convert cifar100. Otherwise do cifar10.\");\n\nnamespace caffe2 {\n\nusing std::stringstream;\n\nconst int kCIFARSize = 32;\nconst int kCIFARImageNBytes = kCIFARSize * kCIFARSize * 3;\nconst int kCIFAR10BatchSize = 10000;\nconst int kCIFAR10TestDataSize = 10000;\nconst int kCIFAR10TrainBatches = 5;\n\nconst int kCIFAR100TrainDataSize = 50000;\nconst int kCIFAR100TestDataSize = 10000;\n\nvoid ReadImage(std::ifstream* file, int* label, char* buffer) {\n  char label_char;\n  if (caffe2::FLAGS_is_cifar100) {\n    // Skip the coarse label.\n    file->read(&label_char, 1);\n  }\n  file->read(&label_char, 1);\n  *label = label_char;\n  // Yes, there are better ways to do it, like in-place swap... but I am too\n  // lazy so let's just write it in a memory-wasteful way.\n  std::array<char, kCIFARImageNBytes> channel_first_storage;\n  file->read(channel_first_storage.data(), kCIFARImageNBytes);\n  for (int c = 0; c < 3; ++c) {\n    for (int i = 0; i < kCIFARSize * kCIFARSize; ++i) {\n      buffer[i * 3 + c] =\n          channel_first_storage[c * kCIFARSize * kCIFARSize + i];\n    }\n  }\n  return;\n}\n\nvoid WriteToDB(const string& filename, const int num_items,\n                    const int& offset, db::DB* db) {\n  TensorProtos protos;\n  TensorProto* data = protos.add_protos();\n  TensorProto* label = protos.add_protos();\n  data->set_data_type(TensorProto::BYTE);\n  data->add_dims(kCIFARSize);\n  data->add_dims(kCIFARSize);\n  data->add_dims(3);\n  label->set_data_type(TensorProto::INT32);\n  label->add_dims(1);\n  label->add_int32_data(0);\n\n  LOG(INFO) << \"Converting file \" << filename;\n  std::ifstream data_file(filename.c_str(),\n      std::ios::in | std::ios::binary);\n  CAFFE_ENFORCE(data_file, \"Unable to open file \", filename);\n  char str_buffer[kCIFARImageNBytes];\n  int label_value;\n  string serialized_protos;\n  std::unique_ptr<db::Transaction> transaction(db->NewTransaction());\n  for (int itemid = 0; itemid < num_items; ++itemid) {\n    ReadImage(&data_file, &label_value, str_buffer);\n    data->set_byte_data(str_buffer, kCIFARImageNBytes);\n    label->set_int32_data(0, label_value);\n    protos.SerializeToString(&serialized_protos);\n    snprintf(str_buffer, kCIFARImageNBytes, \"%05d\",\n        offset + itemid);\n    transaction->Put(string(str_buffer), serialized_protos);\n  }\n}\n\nvoid ConvertCIFAR() {\n  std::unique_ptr<db::DB> train_db(\n      db::CreateDB(caffe2::FLAGS_db, caffe2::FLAGS_output_train_db_name,\n                   db::NEW));\n  std::unique_ptr<db::DB> test_db(\n      db::CreateDB(caffe2::FLAGS_db, caffe2::FLAGS_output_test_db_name,\n                   db::NEW));\n\n  if (!caffe2::FLAGS_is_cifar100) {\n    // This is cifar 10.\n    for (int fileid = 0; fileid < kCIFAR10TrainBatches; ++fileid) {\n      stringstream train_file;\n      train_file << caffe2::FLAGS_input_folder << \"/data_batch_\" << fileid + 1\n                 << \".bin\";\n      WriteToDB(train_file.str(), kCIFAR10BatchSize,\n                fileid * kCIFAR10BatchSize, train_db.get());\n    }\n    stringstream test_file;\n    test_file << caffe2::FLAGS_input_folder << \"/test_batch.bin\";\n    WriteToDB(test_file.str(), kCIFAR10TestDataSize, 0, test_db.get());\n  } else {\n    // This is cifar 100.\n    stringstream train_file;\n    train_file << caffe2::FLAGS_input_folder << \"/train.bin\";\n    WriteToDB(train_file.str(), kCIFAR100TrainDataSize, 0, train_db.get());\n    stringstream test_file;\n    test_file << caffe2::FLAGS_input_folder << \"/test.bin\";\n    WriteToDB(test_file.str(), kCIFAR100TestDataSize, 0, test_db.get());\n  }\n}\n\n}  // namespace caffe2\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n  caffe2::ConvertCIFAR();\n  return 0;\n}\n"
  },
  {
    "path": "binaries/make_image_db.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// This script converts an image dataset to a database.\n//\n// caffe2::FLAGS_input_folder is the root folder that holds all the images\n//\n// caffe2::FLAGS_list_file is the path to a file containing a list of files\n// and their labels, as follows:\n//\n//   subfolder1/file1.JPEG 7\n//   subfolder1/file2.JPEG 7\n//   subfolder2/file1.JPEG 8\n//   ...\n//\n\n#include <opencv2/opencv.hpp>\n\n#include <algorithm>\n#include <fstream>\n#include <queue>\n#include <random>\n#include <string>\n#include <thread>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/core/logging.h\"\n\nCAFFE2_DEFINE_bool(shuffle, false,\n    \"Randomly shuffle the order of images and their labels\");\nCAFFE2_DEFINE_string(input_folder, \"\", \"The input image file name.\");\nCAFFE2_DEFINE_string(\n    list_file,\n    \"\",\n    \"The text file containing the list of images.\");\nCAFFE2_DEFINE_string(output_db_name, \"\", \"The output training leveldb name.\");\nCAFFE2_DEFINE_string(db, \"leveldb\", \"The db type.\");\nCAFFE2_DEFINE_bool(raw, false,\n    \"If set, we pre-read the images and store the raw buffer.\");\nCAFFE2_DEFINE_bool(color, true, \"If set, load images in color.\");\nCAFFE2_DEFINE_int(\n    scale,\n    256,\n    \"If caffe2::FLAGS_raw is set, scale the shorter edge to the given value.\");\nCAFFE2_DEFINE_bool(warp, false, \"If warp is set, warp the images to square.\");\nCAFFE2_DEFINE_int(\n    num_threads,\n    -1,\n    \"Number of image parsing and conversion threads.\");\n\nnamespace caffe2 {\n\nclass Converter {\n public:\n  explicit Converter() {\n    data_ = protos_.add_protos();\n    label_ = protos_.add_protos();\n    if (caffe2::FLAGS_raw) {\n      data_->set_data_type(TensorProto::BYTE);\n      data_->add_dims(0);\n      data_->add_dims(0);\n      if (caffe2::FLAGS_color) {\n        data_->add_dims(3);\n      }\n    } else {\n      data_->set_data_type(TensorProto::STRING);\n      data_->add_dims(1);\n      data_->add_string_data(\"\");\n    }\n    label_->set_data_type(TensorProto::INT32);\n    label_->add_dims(1);\n    label_->add_int32_data(0);\n  }\n\n  ~Converter() {\n    if (thread_.joinable()) {\n      thread_.join();\n    }\n  }\n\n  void queue(const std::pair<std::string, int>& pair) {\n    in_.push(pair);\n  }\n\n  void start() {\n    thread_ = std::thread(&Converter::run, this);\n  }\n\n  std::string get() {\n    std::unique_lock<std::mutex> lock(mutex_);\n    while (out_.empty()) {\n      cv_.wait(lock);\n    }\n\n    auto value = out_.front();\n    out_.pop();\n    cv_.notify_one();\n    return value;\n  }\n\n  void run() {\n    const auto& input_folder = caffe2::FLAGS_input_folder;\n    std::unique_lock<std::mutex> lock(mutex_);\n    std::string value;\n    while (!in_.empty()) {\n      auto pair = in_.front();\n      in_.pop();\n      lock.unlock();\n\n      label_->set_int32_data(0, pair.second);\n\n      // Add raw file contents to DB if !raw\n      if (!caffe2::FLAGS_raw) {\n        std::ifstream image_file_stream(input_folder + pair.first);\n        if (!image_file_stream) {\n          LOG(ERROR) << \"Cannot open \" << input_folder << pair.first\n                     << \". Skipping.\";\n        } else {\n          data_->mutable_string_data(0)->assign(\n              std::istreambuf_iterator<char>(image_file_stream),\n              std::istreambuf_iterator<char>());\n        }\n      } else {\n        // Load image\n        cv::Mat img = cv::imread(\n            input_folder + pair.first,\n            caffe2::FLAGS_color ? CV_LOAD_IMAGE_COLOR\n                                : CV_LOAD_IMAGE_GRAYSCALE);\n\n        // Resize image\n        cv::Mat resized_img;\n        int scaled_width, scaled_height;\n        if (caffe2::FLAGS_warp) {\n          scaled_width = caffe2::FLAGS_scale;\n          scaled_height = caffe2::FLAGS_scale;\n        } else if (img.rows > img.cols) {\n          scaled_width = caffe2::FLAGS_scale;\n          scaled_height =\n              static_cast<float>(img.rows) * caffe2::FLAGS_scale / img.cols;\n        } else {\n          scaled_height = caffe2::FLAGS_scale;\n          scaled_width =\n              static_cast<float>(img.cols) * caffe2::FLAGS_scale / img.rows;\n        }\n        cv::resize(\n            img,\n            resized_img,\n            cv::Size(scaled_width, scaled_height),\n            0,\n            0,\n            cv::INTER_LINEAR);\n        data_->set_dims(0, scaled_height);\n        data_->set_dims(1, scaled_width);\n\n        // Assert we don't have to deal with alignment\n        DCHECK(resized_img.isContinuous());\n        auto nbytes = resized_img.total() * resized_img.elemSize();\n        data_->set_byte_data(resized_img.ptr(), nbytes);\n      }\n\n      protos_.SerializeToString(&value);\n\n      // Add serialized proto to out queue or wait if it is not empty\n      lock.lock();\n      while (!out_.empty()) {\n        cv_.wait(lock);\n      }\n      out_.push(value);\n      cv_.notify_one();\n    }\n  }\n\n protected:\n  TensorProtos protos_;\n  TensorProto* data_;\n  TensorProto* label_;\n  std::queue<std::pair<std::string, int>> in_;\n  std::queue<std::string> out_;\n\n  std::mutex mutex_;\n  std::condition_variable cv_;\n  std::thread thread_;\n};\n\nvoid ConvertImageDataset(\n    const string& input_folder,\n    const string& list_filename,\n    const string& output_db_name,\n    const bool /*shuffle*/) {\n  std::ifstream list_file(list_filename);\n  std::vector<std::pair<std::string, int> > lines;\n  std::string filename;\n  int file_label;\n  while (list_file >> filename >> file_label) {\n    lines.push_back(std::make_pair(filename, file_label));\n  }\n\n  if (caffe2::FLAGS_shuffle) {\n    LOG(INFO) << \"Shuffling data\";\n    std::shuffle(lines.begin(), lines.end(), std::default_random_engine(1701));\n  }\n\n  auto num_threads = caffe2::FLAGS_num_threads;\n  if (num_threads < 1) {\n    num_threads = std::thread::hardware_concurrency();\n  }\n\n  LOG(INFO) << \"Processing \" << lines.size() << \" images...\";\n  LOG(INFO) << \"Opening DB \" << output_db_name;\n\n  auto db = db::CreateDB(caffe2::FLAGS_db, output_db_name, db::NEW);\n  auto transaction = db->NewTransaction();\n\n  LOG(INFO) << \"Using \" << num_threads << \" processing threads...\";\n  std::vector<Converter> converters(num_threads);\n\n  // Queue entries across converters\n  for (auto i = 0; i < lines.size(); i++) {\n    converters[i % converters.size()].queue(lines[i]);\n  }\n\n  // Start all converters\n  for (auto& converter : converters) {\n    converter.start();\n  }\n\n  constexpr auto key_max_length = 256;\n  char key_cstr[key_max_length];\n  string value;\n  int count = 0;\n  for (auto i = 0; i < lines.size(); i++) {\n    // Get serialized proto for this entry\n    auto value = converters[i % converters.size()].get();\n\n    // Synthesize key for this entry\n    auto key_len = snprintf(\n        key_cstr, sizeof(key_cstr), \"%08d_%s\", i, lines[i].first.c_str());\n    DCHECK_LE(key_len, sizeof(key_cstr));\n\n    // Put in db\n    transaction->Put(string(key_cstr), value);\n\n    if (++count % 1000 == 0) {\n      // Commit the current writes.\n      transaction->Commit();\n      LOG(INFO) << \"Processed \" << count << \" files.\";\n    }\n  }\n\n  // Commit final transaction\n  transaction->Commit();\n  LOG(INFO) << \"Processed \" << count << \" files.\";\n}\n\n}  // namespace caffe2\n\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n  caffe2::ConvertImageDataset(\n      caffe2::FLAGS_input_folder, caffe2::FLAGS_list_file,\n      caffe2::FLAGS_output_db_name, caffe2::FLAGS_shuffle);\n  return 0;\n}\n"
  },
  {
    "path": "binaries/make_mnist_db.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// This script converts the MNIST dataset to leveldb.\n// The MNIST dataset could be downloaded at\n//    http://yann.lecun.com/exdb/mnist/\n\n#include <fstream>  // NOLINT(readability/streams)\n#include <string>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/core/logging.h\"\n\nCAFFE2_DEFINE_string(image_file, \"\", \"The input image file name.\");\nCAFFE2_DEFINE_string(label_file, \"\", \"The label file name.\");\nCAFFE2_DEFINE_string(output_file, \"\", \"The output db name.\");\nCAFFE2_DEFINE_string(db, \"leveldb\", \"The db type.\");\nCAFFE2_DEFINE_int(data_limit, -1,\n             \"If set, only output this number of data points.\");\nCAFFE2_DEFINE_bool(channel_first, false,\n            \"If set, write the data as channel-first (CHW order) as the old \"\n            \"Caffe does.\");\n\nnamespace caffe2 {\nuint32_t swap_endian(uint32_t val) {\n    val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF);\n    return (val << 16) | (val >> 16);\n}\n\nvoid convert_dataset(const char* image_filename, const char* label_filename,\n        const char* db_path, const int data_limit) {\n  // Open files\n  std::ifstream image_file(image_filename, std::ios::in | std::ios::binary);\n  std::ifstream label_file(label_filename, std::ios::in | std::ios::binary);\n  CAFFE_ENFORCE(image_file, \"Unable to open file \", image_filename);\n  CAFFE_ENFORCE(label_file, \"Unable to open file \", label_filename);\n  // Read the magic and the meta data\n  uint32_t magic;\n  uint32_t num_items;\n  uint32_t num_labels;\n  uint32_t rows;\n  uint32_t cols;\n\n  image_file.read(reinterpret_cast<char*>(&magic), 4);\n  magic = swap_endian(magic);\n  if (magic == 529205256) {\n    LOG(FATAL) << \n        \"It seems that you forgot to unzip the mnist dataset. You should \"\n        \"first unzip them using e.g. gunzip on Linux.\";\n  }\n  CAFFE_ENFORCE_EQ(magic, 2051, \"Incorrect image file magic.\");\n  label_file.read(reinterpret_cast<char*>(&magic), 4);\n  magic = swap_endian(magic);\n  CAFFE_ENFORCE_EQ(magic, 2049, \"Incorrect label file magic.\");\n  image_file.read(reinterpret_cast<char*>(&num_items), 4);\n  num_items = swap_endian(num_items);\n  label_file.read(reinterpret_cast<char*>(&num_labels), 4);\n  num_labels = swap_endian(num_labels);\n  CAFFE_ENFORCE_EQ(num_items, num_labels);\n  image_file.read(reinterpret_cast<char*>(&rows), 4);\n  rows = swap_endian(rows);\n  image_file.read(reinterpret_cast<char*>(&cols), 4);\n  cols = swap_endian(cols);\n\n  // leveldb\n  std::unique_ptr<db::DB> mnist_db(db::CreateDB(caffe2::FLAGS_db, db_path, db::NEW));\n  std::unique_ptr<db::Transaction> transaction(mnist_db->NewTransaction());\n  // Storing to db\n  char label_value;\n  std::vector<char> pixels(rows * cols);\n  int count = 0;\n  const int kMaxKeyLength = 10;\n  char key_cstr[kMaxKeyLength];\n  string value;\n\n  TensorProtos protos;\n  TensorProto* data = protos.add_protos();\n  TensorProto* label = protos.add_protos();\n  data->set_data_type(TensorProto::BYTE);\n  if (caffe2::FLAGS_channel_first) {\n    data->add_dims(1);\n    data->add_dims(rows);\n    data->add_dims(cols);\n  } else {\n    data->add_dims(rows);\n    data->add_dims(cols);\n    data->add_dims(1);\n  }\n  label->set_data_type(TensorProto::INT32);\n  label->add_int32_data(0);\n\n  LOG(INFO) << \"A total of \" << num_items << \" items.\";\n  LOG(INFO) << \"Rows: \" << rows << \" Cols: \" << cols;\n  for (int item_id = 0; item_id < num_items; ++item_id) {\n    image_file.read(pixels.data(), rows * cols);\n    label_file.read(&label_value, 1);\n    for (int i = 0; i < rows * cols; ++i) {\n      data->set_byte_data(pixels.data(), rows * cols);\n    }\n    label->set_int32_data(0, static_cast<int>(label_value));\n    snprintf(key_cstr, kMaxKeyLength, \"%08d\", item_id);\n    protos.SerializeToString(&value);\n    string keystr(key_cstr);\n\n    // Put in db\n    transaction->Put(keystr, value);\n    if (++count % 1000 == 0) {\n      transaction->Commit();\n    }\n    if (data_limit > 0 && count == data_limit) {\n      LOG(INFO) << \"Reached data limit of \" << data_limit << \", stop.\";\n      break;\n    }\n  }\n}\n}  // namespace caffe2\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n  caffe2::convert_dataset(caffe2::FLAGS_image_file.c_str(), caffe2::FLAGS_label_file.c_str(),\n                          caffe2::FLAGS_output_file.c_str(), caffe2::FLAGS_data_limit);\n  return 0;\n}\n"
  },
  {
    "path": "binaries/predictor_verifier.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/predictor.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nCAFFE2_DEFINE_string(init_net, \"\", \"The given path to the init protobuffer.\");\nCAFFE2_DEFINE_string(\n    predict_net,\n    \"\",\n    \"The given path to the predict protobuffer.\");\n\nnamespace caffe2 {\n\nvoid run() {\n  if (FLAGS_init_net.empty()) {\n    LOG(FATAL) << \"No init net specified. Use --init_net=/path/to/net.\";\n  }\n  if (FLAGS_predict_net.empty()) {\n    LOG(FATAL) << \"No predict net specified. Use --predict_net=/path/to/net.\";\n  }\n  caffe2::NetDef init_net, predict_net;\n  CAFFE_ENFORCE(ReadProtoFromFile(FLAGS_init_net, &init_net));\n  CAFFE_ENFORCE(ReadProtoFromFile(FLAGS_predict_net, &predict_net));\n  // Can be large due to constant fills\n  VLOG(1) << \"Init net: \" << ProtoDebugString(init_net);\n  LOG(INFO) << \"Predict net: \" << ProtoDebugString(predict_net);\n  auto predictor = caffe2::make_unique<Predictor>(init_net, predict_net);\n  LOG(INFO) << \"Checking that a null forward-pass works\";\n  Predictor::TensorVector inputVec, outputVec;\n  predictor->run(inputVec, &outputVec);\n  CAFFE_ENFORCE_GT(outputVec.size(), 0);\n}\n}\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n  caffe2::run();\n  // This is to allow us to use memory leak checks.\n  google::protobuf::ShutdownProtobufLibrary();\n  return 0;\n}\n"
  },
  {
    "path": "binaries/print_core_object_sizes.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\n#define PRINT_SIZE(cls) \\\n  std::cout << \"Size of \" #cls \": \" << sizeof(cls) << \" bytes.\" \\\n            << std::endl;\n\nint main(int /* unused */, char** /* unused */) {\n  PRINT_SIZE(caffe2::Blob);\n  PRINT_SIZE(caffe2::Tensor<caffe2::CPUContext>);\n  PRINT_SIZE(caffe2::Tensor<caffe2::CUDAContext>);\n  PRINT_SIZE(caffe2::CPUContext);\n  PRINT_SIZE(caffe2::CUDAContext);\n  PRINT_SIZE(caffe2::OperatorBase);\n  PRINT_SIZE(caffe2::OperatorDef);\n  PRINT_SIZE(caffe2::Operator<caffe2::CPUContext>);\n  PRINT_SIZE(caffe2::Operator<caffe2::CUDAContext>);\n  PRINT_SIZE(caffe2::TypeMeta);\n  PRINT_SIZE(caffe2::Workspace);\n  return 0;\n}\n"
  },
  {
    "path": "binaries/print_registered_core_operators.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n#include <string>\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/operator_schema.h\"\n\nCAFFE2_DEFINE_string(schema, \"\",\n                     \"Print doc and schema of a particular operator\");\n\nstatic bool HasSchema(const std::string& str) {\n  return caffe2::OpSchemaRegistry::Schema(str);\n}\n\nstatic bool HasDoc(const std::string& str) {\n  const auto* schema = caffe2::OpSchemaRegistry::Schema(str);\n  return (schema != nullptr) && (schema->doc() != nullptr);\n}\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n\n  if (!caffe2::FLAGS_schema.empty()) {\n    const auto* schema = caffe2::OpSchemaRegistry::Schema(\n        caffe2::FLAGS_schema);\n    if (!schema) {\n      std::cerr << \"Operator \" << caffe2::FLAGS_schema\n                << \" doesn't have a schema\" << std::endl;\n      return 1;\n    }\n    std::cout << \"Operator \" << caffe2::FLAGS_schema << \": \" << std::endl\n              << *schema;\n    return 0;\n  }\n\n  for (const auto& pair : *caffe2::gDeviceTypeRegistry()) {\n    std::cout << \"Device type \" << pair.first\n#ifndef CAFFE2_USE_LITE_PROTO\n              << \" (\" << caffe2::DeviceType_Name(\n                             static_cast<caffe2::DeviceType>(pair.first))\n              << \")\"\n#endif\n              << std::endl;\n    for (const auto& key : pair.second->Keys()) {\n      std::cout << \"\\t(schema: \" << HasSchema(key) << \", doc: \" << HasDoc(key)\n                << \")\\t\" << key << std::endl;\n    }\n  }\n\n  std::cout << \"Operators that have gradients registered:\" << std::endl;\n  for (const auto& key : caffe2::GradientRegistry()->Keys()) {\n    std::cout << \"\\t(schema: \" << HasSchema(key) << \", doc: \"\n              << HasDoc(key) << \")\\t\"\n              << key << std::endl;\n  }\n  return 0;\n}\n"
  },
  {
    "path": "binaries/run_plan.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include \"caffe2/core/logging.h\"\n\nCAFFE2_DEFINE_string(plan, \"\", \"The given path to the plan protobuffer.\");\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n  if (caffe2::FLAGS_plan.size() == 0) {\n    LOG(ERROR) << \"No plan specified. Use --plan=/path/to/plan.\";\n    return 0;\n  }\n  LOG(INFO) << \"Loading plan: \" << caffe2::FLAGS_plan;\n  caffe2::PlanDef plan_def;\n  CAFFE_ENFORCE(ReadProtoFromFile(caffe2::FLAGS_plan, &plan_def));\n  std::unique_ptr<caffe2::Workspace> workspace(new caffe2::Workspace());\n  workspace->RunPlan(plan_def);\n\n  // This is to allow us to use memory leak checks.\n  google::protobuf::ShutdownProtobufLibrary();\n  return 0;\n}\n"
  },
  {
    "path": "binaries/run_plan_mpi.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <mpi.h>\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include \"caffe2/core/logging.h\"\n\nCAFFE2_DEFINE_string(plan, \"\", \"The given path to the plan protobuffer.\");\n\nint main(int argc, char** argv) {\n  caffe2::SetUsageMessage(\"Runs a caffe2 plan that has MPI operators in it.\");\n  int mpi_ret;\n  MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &mpi_ret);\n  if (mpi_ret != MPI_THREAD_MULTIPLE &&\n      mpi_ret != MPI_THREAD_SERIALIZED) {\n    std::cerr << \"Caffe2 MPI requires the underlying MPI to support the \"\n                 \"MPI_THREAD_SERIALIZED or MPI_THREAD_MULTIPLE mode.\\n\";\n    return 1;\n  }\n  caffe2::GlobalInit(&argc, &argv);\n  LOG(INFO) << \"Loading plan: \" << caffe2::FLAGS_plan;\n  caffe2::PlanDef plan_def;\n  CAFFE_ENFORCE(ReadProtoFromFile(caffe2::FLAGS_plan, &plan_def));\n  std::unique_ptr<caffe2::Workspace> workspace(new caffe2::Workspace());\n  workspace->RunPlan(plan_def);\n\n  // This is to allow us to use memory leak checks.\n  google::protobuf::ShutdownProtobufLibrary();\n  MPI_Finalize();\n  return 0;\n}\n"
  },
  {
    "path": "binaries/speed_benchmark.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <string>\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include \"caffe2/utils/string_utils.h\"\n\nCAFFE2_DEFINE_string(net, \"\", \"The given net to benchmark.\");\nCAFFE2_DEFINE_string(\n    init_net,\n    \"\",\n    \"The given net to initialize any parameters.\");\nCAFFE2_DEFINE_string(\n    input,\n    \"\",\n    \"Input that is needed for running the network. If \"\n    \"multiple input needed, use comma separated string.\");\nCAFFE2_DEFINE_string(\n    input_file,\n    \"\",\n    \"Input file that contain the serialized protobuf for \"\n    \"the input blobs. If multiple input needed, use comma \"\n    \"separated string. Must have the same number of items \"\n    \"as input does.\");\nCAFFE2_DEFINE_string(\n    input_dims,\n    \"\",\n    \"Alternate to input_files, if all inputs are simple \"\n    \"float TensorCPUs, specify the dimension using comma \"\n    \"separated numbers. If multiple input needed, use \"\n    \"semicolon to separate the dimension of different \"\n    \"tensors.\");\nCAFFE2_DEFINE_string(\n    output,\n    \"\",\n    \"Output that should be dumped after the execution \"\n    \"finishes. If multiple outputs are needed, use comma \"\n    \"separated string. If you want to dump everything, pass \"\n    \"'*' as the output value.\");\nCAFFE2_DEFINE_string(\n    output_folder,\n    \"\",\n    \"The folder that the output should be written to. This \"\n    \"folder must already exist in the file system.\");\nCAFFE2_DEFINE_int(warmup, 0, \"The number of iterations to warm up.\");\nCAFFE2_DEFINE_int(iter, 10, \"The number of iterations to run.\");\nCAFFE2_DEFINE_bool(\n    run_individual,\n    false,\n    \"Whether to benchmark individual operators.\");\n\nCAFFE2_DEFINE_bool(force_engine, false, \"Force engine field for all operators\");\nCAFFE2_DEFINE_string(engine, \"\", \"Forced engine field value\");\nCAFFE2_DEFINE_bool(force_algo, false, \"Force algo arg for all operators\");\nCAFFE2_DEFINE_string(algo, \"\", \"Forced algo arg value\");\n\nusing std::string;\nusing std::unique_ptr;\nusing std::vector;\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n  unique_ptr<caffe2::Workspace> workspace(new caffe2::Workspace());\n\n  // Run initialization network.\n  caffe2::NetDef net_def;\n  CAFFE_ENFORCE(ReadProtoFromFile(caffe2::FLAGS_init_net, &net_def));\n  CAFFE_ENFORCE(workspace->RunNetOnce(net_def));\n\n  // Load input.\n  if (caffe2::FLAGS_input.size()) {\n    vector<string> input_names = caffe2::split(',', caffe2::FLAGS_input);\n    if (caffe2::FLAGS_input_file.size()) {\n      vector<string> input_files = caffe2::split(',', caffe2::FLAGS_input_file);\n      CAFFE_ENFORCE_EQ(\n          input_names.size(),\n          input_files.size(),\n          \"Input name and file should have the same number.\");\n      for (int i = 0; i < input_names.size(); ++i) {\n        caffe2::BlobProto blob_proto;\n        CAFFE_ENFORCE(caffe2::ReadProtoFromFile(input_files[i], &blob_proto));\n        workspace->CreateBlob(input_names[i])->Deserialize(blob_proto);\n      }\n    } else if (caffe2::FLAGS_input_dims.size()) {\n      vector<string> input_dims_list =\n          caffe2::split(';', caffe2::FLAGS_input_dims);\n      CAFFE_ENFORCE_EQ(\n          input_names.size(),\n          input_dims_list.size(),\n          \"Input name and dims should have the same number of items.\");\n      for (int i = 0; i < input_names.size(); ++i) {\n        vector<string> input_dims_str = caffe2::split(',', input_dims_list[i]);\n        vector<int> input_dims;\n        for (const string& s : input_dims_str) {\n          input_dims.push_back(caffe2::stoi(s));\n        }\n        caffe2::TensorCPU* tensor =\n            workspace->GetBlob(input_names[i])->GetMutable<caffe2::TensorCPU>();\n        tensor->Resize(input_dims);\n        tensor->mutable_data<float>();\n      }\n    } else {\n      CAFFE_THROW(\n          \"You requested input tensors, but neither input_file nor \"\n          \"input_dims is set.\");\n    }\n  }\n\n  // Run main network.\n  CAFFE_ENFORCE(ReadProtoFromFile(caffe2::FLAGS_net, &net_def));\n  // force changing engine and algo\n  if (caffe2::FLAGS_force_engine) {\n    LOG(INFO) << \"force engine be: \" << caffe2::FLAGS_engine;\n    for (const auto& op : net_def.op()) {\n      const_cast<caffe2::OperatorDef*>(&op)->set_engine(caffe2::FLAGS_engine);\n    }\n  }\n  if (caffe2::FLAGS_force_algo) {\n    LOG(INFO) << \"force algo be: \" << caffe2::FLAGS_algo;\n    for (const auto& op : net_def.op()) {\n      caffe2::GetMutableArgument(\n          \"algo\", true, const_cast<caffe2::OperatorDef*>(&op))\n          ->set_s(caffe2::FLAGS_algo);\n    }\n  }\n  caffe2::NetBase* net = workspace->CreateNet(net_def);\n  CHECK_NOTNULL(net);\n  net->TEST_Benchmark(\n      caffe2::FLAGS_warmup, caffe2::FLAGS_iter, caffe2::FLAGS_run_individual);\n\n  string output_prefix = caffe2::FLAGS_output_folder.size()\n      ? caffe2::FLAGS_output_folder + \"/\"\n      : \"\";\n  if (caffe2::FLAGS_output.size()) {\n    vector<string> output_names = caffe2::split(',', caffe2::FLAGS_output);\n    if (caffe2::FLAGS_output == \"*\") {\n      output_names = workspace->Blobs();\n    }\n    for (const string& name : output_names) {\n      CAFFE_ENFORCE(\n          workspace->HasBlob(name),\n          \"You requested a non-existing blob: \",\n          name);\n      string serialized = workspace->GetBlob(name)->Serialize(name);\n      string output_filename = output_prefix + name;\n      caffe2::WriteStringToFile(serialized, output_filename.c_str());\n    }\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "binaries/split_db.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <string>\n#include <sstream>\n\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/core/logging.h\"\n\nCAFFE2_DEFINE_string(input_db, \"\", \"The input db.\");\nCAFFE2_DEFINE_int(splits, 0, \"The number of splits.\");\nCAFFE2_DEFINE_string(db_type, \"\", \"The db type.\");\nCAFFE2_DEFINE_int(batch_size, 1000, \"The write batch size.\");\n\nnamespace caffe2 {\n\nstatic int Split(int argc, char** argv) {\n  GlobalInit(&argc, &argv);\n\n  CAFFE_ENFORCE(FLAGS_input_db.size(), \"Must specify --input_db=/path/to/db.\");\n  CAFFE_ENFORCE(FLAGS_splits > 0, \"Must specify a nonnegative split number.\");\n  CAFFE_ENFORCE(FLAGS_db_type.size(), \"Must specify --db_type=[a db type].\");\n\n  unique_ptr<db::DB> in_db(\n      db::CreateDB(FLAGS_db_type, FLAGS_input_db, db::READ));\n  CAFFE_ENFORCE(in_db != nullptr, \"Cannot open input db: \", FLAGS_input_db);\n  unique_ptr<db::Cursor> cursor(in_db->NewCursor());\n  // This usually won't happen, but FWIW.\n  CAFFE_ENFORCE(\n      cursor != nullptr, \"Cannot obtain cursor for input db: \", FLAGS_input_db);\n\n  vector<unique_ptr<db::DB>> out_dbs;\n  vector<unique_ptr<db::Transaction>> transactions;\n  for (int i = 0; i < FLAGS_splits; ++i) {\n    out_dbs.push_back(unique_ptr<db::DB>(db::CreateDB(\n        FLAGS_db_type, FLAGS_input_db + \"_split_\" + to_string(i), db::NEW)));\n    CAFFE_ENFORCE(out_dbs.back().get(), \"Cannot create output db #\", i);\n    transactions.push_back(\n        unique_ptr<db::Transaction>(out_dbs[i]->NewTransaction()));\n    CAFFE_ENFORCE(\n        transactions.back().get(), \"Cannot get transaction for output db #\", i);\n  }\n\n  int count = 0;\n  for (; cursor->Valid(); cursor->Next()) {\n    transactions[count % FLAGS_splits]->Put(cursor->key(), cursor->value());\n    if (++count % FLAGS_batch_size == 0) {\n      for (int i = 0; i < FLAGS_splits; ++i) {\n        transactions[i]->Commit();\n      }\n      LOG(INFO) << \"Split \" << count << \" items so far.\";\n    }\n  }\n  LOG(INFO) << \"A total of \" << count << \" items processed.\";\n  return 0;\n}\n\n} // namespace caffe2\n\nint main(int argc, char** argv) {\n  return caffe2::Split(argc, argv);\n}\n"
  },
  {
    "path": "binaries/tutorial_blob.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/logging.h\"\n\n// We will be lazy and just use the whole namespace.\nusing namespace caffe2;\n\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n  caffe2::ShowLogInfoToStderr();\n\n  LOG(INFO) <<\n      \"This script corresponds to the Blob part of the Caffe2 C++ \"\n      \"tutorial.\";\n\n  LOG(INFO) << \"Let's create a blob myblob.\";\n\n  Blob myblob;\n\n  LOG(INFO) << \"Let's set it to int and set the value to 10.\";\n\n  int* myint = myblob.GetMutable<int>();\n  *myint = 10;\n\n  LOG(INFO)\n      << \"Is the blob type int? \"\n      << myblob.IsType<int>();\n\n  LOG(INFO)\n      << \"Is the blob type float? \"\n      << myblob.IsType<float>();\n               \n  const int& myint_const = myblob.Get<int>();\n  LOG(INFO)\n      << \"The value of the int number stored in the blob is: \"\n      << myint_const;\n\n  LOG(INFO)\n      << \"Let's try to get a float pointer. This will trigger an exception.\";\n\n  try {\n    const float& myfloat = myblob.Get<float>();\n    LOG(FATAL) << \"This line should never happen.\";\n  } catch (std::exception& e) {\n    LOG(INFO)\n        << \"As expected, we got an exception. Its content says: \"\n        << e.what();\n  }\n\n  LOG(INFO) <<\n      \"However, we can change the content type (and destroy the old \"\n      \"content) by calling GetMutable. Let's change it to double.\";\n\n  double* mydouble = myblob.GetMutable<double>();\n  *mydouble = 3.14;\n\n  LOG(INFO) << \"The new content is: \" << myblob.Get<double>();\n\n  LOG(INFO) <<\n      \"If we have a pre-created object, we can use Reset() to transfer the \"\n      \"object to a blob.\";\n\n  std::string* pvec = new std::string();\n  myblob.Reset(pvec); // no need to release pvec, myblob takes ownership.\n  \n  LOG(INFO) << \"Is the blob now of type string? \"\n            << myblob.IsType<std::string>();\n\n  LOG(INFO) << \"This concludes the blob tutorial.\";\n  return 0;\n}\n"
  },
  {
    "path": "binaries/zmq_feeder.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// This binary provides an easy way to open a zeromq server and feeds data to\n// clients connect to it. It uses the Caffe2 db as the backend, thus allowing\n// one to convert any db-compliant storage to a zeromq service.\n\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/utils/zmq_helper.h\"\n\nCAFFE2_DEFINE_string(server, \"tcp://*:5555\", \"The server address.\");\nCAFFE2_DEFINE_string(input_db, \"\", \"The input db.\");\nCAFFE2_DEFINE_string(input_db_type, \"\", \"The input db type.\");\n\nusing caffe2::db::DB;\nusing caffe2::db::Cursor;\nusing caffe2::string;\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n\n  LOG(INFO) << \"Opening DB...\";\n  auto in_db = caffe2::db::CreateDB(\n      caffe2::FLAGS_input_db_type, caffe2::FLAGS_input_db, caffe2::db::READ);\n  CAFFE_ENFORCE(\n      in_db,\n      \"Cannot load input db \" + caffe2::FLAGS_input_db + \" of expected type \" +\n          caffe2::FLAGS_input_db_type);\n  auto cursor = in_db->NewCursor();\n  LOG(INFO) << \"DB opened.\";\n\n  LOG(INFO) << \"Starting ZeroMQ server...\";\n\n  //  Socket to talk to clients\n  caffe2::ZmqSocket sender(ZMQ_PUSH);\n  sender.Bind(caffe2::FLAGS_server);\n  LOG(INFO) << \"Server created at \" << caffe2::FLAGS_server;\n\n  while (1) {\n    VLOG(1) << \"Sending \" << cursor->key();\n    sender.SendTillSuccess(cursor->key(), ZMQ_SNDMORE);\n    sender.SendTillSuccess(cursor->value(), 0);\n    cursor->Next();\n    if (!cursor->Valid()) {\n      cursor->SeekToFirst();\n    }\n  }\n  // We do not do an elegant quit since this binary is going to be terminated by\n  // control+C.\n  return 0;\n}\n"
  },
  {
    "path": "caffe/__init__.py",
    "content": ""
  },
  {
    "path": "caffe/proto/CMakeLists.txt",
    "content": "file(GLOB Caffe_PROTOBUF_FILES \"${CMAKE_CURRENT_SOURCE_DIR}/*.proto\")\n\ncaffe2_protobuf_generate_cpp_py(Caffe_PROTO_SRCS Caffe_PROTO_HEADERS Caffe_PROTO_PY ${Caffe_PROTOBUF_FILES})\n\nadd_library(Caffe_PROTO OBJECT ${Caffe_PROTO_HEADERS} ${Caffe_PROTO_SRCS})\n\nif (MSVC)\n  if(BUILD_SHARED_LIBS)\n    set(Caffe2_API_DEFINE \"-DCAFFE2_API=__declspec(dllexport)\")\n  else()\n    set(Caffe2_API_DEFINE \"-DCAFFE2_API=\")\n  endif()\n  target_compile_definitions(\n      Caffe_PROTO PRIVATE ${Caffe2_API_DEFINE})\nendif()\n\ninstall(FILES ${Caffe_PROTO_HEADERS} DESTINATION include/caffe/proto)\n"
  },
  {
    "path": "caffe/proto/__init__.py",
    "content": ""
  },
  {
    "path": "caffe/proto/caffe.proto",
    "content": "syntax = \"proto2\";\n\npackage caffe;\n\n// Specifies the shape (dimensions) of a Blob.\nmessage BlobShape {\n  repeated int64 dim = 1 [packed = true];\n}\n\nmessage BlobProto {\n  optional BlobShape shape = 7;\n  repeated float data = 5 [packed = true];\n  repeated float diff = 6 [packed = true];\n  repeated double double_data = 8 [packed = true];\n  repeated double double_diff = 9 [packed = true];\n\n  // 4D dimensions -- deprecated.  Use \"shape\" instead.\n  optional int32 num = 1 [default = 0];\n  optional int32 channels = 2 [default = 0];\n  optional int32 height = 3 [default = 0];\n  optional int32 width = 4 [default = 0];\n}\n\n// The BlobProtoVector is simply a way to pass multiple blobproto instances\n// around.\nmessage BlobProtoVector {\n  repeated BlobProto blobs = 1;\n}\n\nmessage Datum {\n  optional int32 channels = 1;\n  optional int32 height = 2;\n  optional int32 width = 3;\n  // the actual image data, in bytes\n  optional bytes data = 4;\n  optional int32 label = 5;\n  // Optionally, the datum could also hold float data.\n  repeated float float_data = 6;\n  // If true data contains an encoded image that need to be decoded\n  optional bool encoded = 7 [default = false];\n}\n\nmessage FillerParameter {\n  // The filler type.\n  optional string type = 1 [default = 'constant'];\n  optional float value = 2 [default = 0]; // the value in constant filler\n  optional float min = 3 [default = 0]; // the min value in uniform filler\n  optional float max = 4 [default = 1]; // the max value in uniform filler\n  optional float mean = 5 [default = 0]; // the mean value in Gaussian filler\n  optional float std = 6 [default = 1]; // the std value in Gaussian filler\n  // The expected number of non-zero output weights for a given input in\n  // Gaussian filler -- the default -1 means don't perform sparsification.\n  optional int32 sparse = 7 [default = -1];\n  // Normalize the filler variance by fan_in, fan_out, or their average.\n  // Applies to 'xavier' and 'msra' fillers.\n  enum VarianceNorm {\n    FAN_IN = 0;\n    FAN_OUT = 1;\n    AVERAGE = 2;\n  }\n  optional VarianceNorm variance_norm = 8 [default = FAN_IN];\n}\n\nmessage NetParameter {\n  optional string name = 1; // consider giving the network a name\n  // DEPRECATED. See InputParameter. The input blobs to the network.\n  repeated string input = 3;\n  // DEPRECATED. See InputParameter. The shape of the input blobs.\n  repeated BlobShape input_shape = 8;\n\n  // 4D input dimensions -- deprecated.  Use \"input_shape\" instead.\n  // If specified, for each input blob there should be four\n  // values specifying the num, channels, height and width of the input blob.\n  // Thus, there should be a total of (4 * #input) numbers.\n  repeated int32 input_dim = 4;\n\n  // Whether the network will force every layer to carry out backward operation.\n  // If set False, then whether to carry out backward is determined\n  // automatically according to the net structure and learning rates.\n  optional bool force_backward = 5 [default = false];\n  // The current \"state\" of the network, including the phase, level, and stage.\n  // Some layers may be included/excluded depending on this state and the states\n  // specified in the layers' include and exclude fields.\n  optional NetState state = 6;\n\n  // Print debugging information about results while running Net::Forward,\n  // Net::Backward, and Net::Update.\n  optional bool debug_info = 7 [default = false];\n\n  // The layers that make up the net.  Each of their configurations, including\n  // connectivity and behavior, is specified as a LayerParameter.\n  repeated LayerParameter layer = 100;  // ID 100 so layers are printed last.\n\n  // DEPRECATED: use 'layer' instead.\n  repeated V1LayerParameter layers = 2;\n}\n\n// NOTE\n// Update the next available ID when you add a new SolverParameter field.\n//\n// SolverParameter next available ID: 41 (last added: type)\nmessage SolverParameter {\n  //////////////////////////////////////////////////////////////////////////////\n  // Specifying the train and test networks\n  //\n  // Exactly one train net must be specified using one of the following fields:\n  //     train_net_param, train_net, net_param, net\n  // One or more test nets may be specified using any of the following fields:\n  //     test_net_param, test_net, net_param, net\n  // If more than one test net field is specified (e.g., both net and\n  // test_net are specified), they will be evaluated in the field order given\n  // above: (1) test_net_param, (2) test_net, (3) net_param/net.\n  // A test_iter must be specified for each test_net.\n  // A test_level and/or a test_stage may also be specified for each test_net.\n  //////////////////////////////////////////////////////////////////////////////\n\n  // Proto filename for the train net, possibly combined with one or more\n  // test nets.\n  optional string net = 24;\n  // Inline train net param, possibly combined with one or more test nets.\n  optional NetParameter net_param = 25;\n\n  optional string train_net = 1; // Proto filename for the train net.\n  repeated string test_net = 2; // Proto filenames for the test nets.\n  optional NetParameter train_net_param = 21; // Inline train net params.\n  repeated NetParameter test_net_param = 22; // Inline test net params.\n\n  // The states for the train/test nets. Must be unspecified or\n  // specified once per net.\n  //\n  // By default, all states will have solver = true;\n  // train_state will have phase = TRAIN,\n  // and all test_state's will have phase = TEST.\n  // Other defaults are set according to the NetState defaults.\n  optional NetState train_state = 26;\n  repeated NetState test_state = 27;\n\n  // The number of iterations for each test net.\n  repeated int32 test_iter = 3;\n\n  // The number of iterations between two testing phases.\n  optional int32 test_interval = 4 [default = 0];\n  optional bool test_compute_loss = 19 [default = false];\n  // If true, run an initial test pass before the first iteration,\n  // ensuring memory availability and printing the starting value of the loss.\n  optional bool test_initialization = 32 [default = true];\n  optional float base_lr = 5; // The base learning rate\n  // the number of iterations between displaying info. If display = 0, no info\n  // will be displayed.\n  optional int32 display = 6;\n  // Display the loss averaged over the last average_loss iterations\n  optional int32 average_loss = 33 [default = 1];\n  optional int32 max_iter = 7; // the maximum number of iterations\n  // accumulate gradients over `iter_size` x `batch_size` instances\n  optional int32 iter_size = 36 [default = 1];\n\n  // The learning rate decay policy. The currently implemented learning rate\n  // policies are as follows:\n  //    - fixed: always return base_lr.\n  //    - step: return base_lr * gamma ^ (floor(iter / step))\n  //    - exp: return base_lr * gamma ^ iter\n  //    - inv: return base_lr * (1 + gamma * iter) ^ (- power)\n  //    - multistep: similar to step but it allows non uniform steps defined by\n  //      stepvalue\n  //    - poly: the effective learning rate follows a polynomial decay, to be\n  //      zero by the max_iter. return base_lr (1 - iter/max_iter) ^ (power)\n  //    - sigmoid: the effective learning rate follows a sigmod decay\n  //      return base_lr ( 1/(1 + exp(-gamma * (iter - stepsize))))\n  //\n  // where base_lr, max_iter, gamma, step, stepvalue and power are defined\n  // in the solver parameter protocol buffer, and iter is the current iteration.\n  optional string lr_policy = 8;\n  optional float gamma = 9; // The parameter to compute the learning rate.\n  optional float power = 10; // The parameter to compute the learning rate.\n  optional float momentum = 11; // The momentum value.\n  optional float weight_decay = 12; // The weight decay.\n  // regularization types supported: L1 and L2\n  // controlled by weight_decay\n  optional string regularization_type = 29 [default = \"L2\"];\n  // the stepsize for learning rate policy \"step\"\n  optional int32 stepsize = 13;\n  // the stepsize for learning rate policy \"multistep\"\n  repeated int32 stepvalue = 34;\n\n  // Set clip_gradients to >= 0 to clip parameter gradients to that L2 norm,\n  // whenever their actual L2 norm is larger.\n  optional float clip_gradients = 35 [default = -1];\n\n  optional int32 snapshot = 14 [default = 0]; // The snapshot interval\n  optional string snapshot_prefix = 15; // The prefix for the snapshot.\n  // whether to snapshot diff in the results or not. Snapshotting diff will help\n  // debugging but the final protocol buffer size will be much larger.\n  optional bool snapshot_diff = 16 [default = false];\n  enum SnapshotFormat {\n    HDF5 = 0;\n    BINARYPROTO = 1;\n  }\n  optional SnapshotFormat snapshot_format = 37 [default = BINARYPROTO];\n  // the mode solver will use: 0 for CPU and 1 for GPU. Use GPU in default.\n  enum SolverMode {\n    CPU = 0;\n    GPU = 1;\n  }\n  optional SolverMode solver_mode = 17 [default = GPU];\n  // the device_id will that be used in GPU mode. Use device_id = 0 in default.\n  optional int32 device_id = 18 [default = 0];\n  // If non-negative, the seed with which the Solver will initialize the Caffe\n  // random number generator -- useful for reproducible results. Otherwise,\n  // (and by default) initialize using a seed derived from the system clock.\n  optional int64 random_seed = 20 [default = -1];\n\n  // type of the solver\n  optional string type = 40 [default = \"SGD\"];\n\n  // numerical stability for RMSProp, AdaGrad and AdaDelta and Adam\n  optional float delta = 31 [default = 1e-8];\n  // parameters for the Adam solver\n  optional float momentum2 = 39 [default = 0.999];\n\n  // RMSProp decay value\n  // MeanSquare(t) = rms_decay*MeanSquare(t-1) + (1-rms_decay)*SquareGradient(t)\n  optional float rms_decay = 38;\n\n  // If true, print information about the state of the net that may help with\n  // debugging learning problems.\n  optional bool debug_info = 23 [default = false];\n\n  // If false, don't save a snapshot after training finishes.\n  optional bool snapshot_after_train = 28 [default = true];\n\n  // DEPRECATED: old solver enum types, use string instead\n  enum SolverType {\n    SGD = 0;\n    NESTEROV = 1;\n    ADAGRAD = 2;\n    RMSPROP = 3;\n    ADADELTA = 4;\n    ADAM = 5;\n  }\n  // DEPRECATED: use type instead of solver_type\n  optional SolverType solver_type = 30 [default = SGD];\n}\n\n// A message that stores the solver snapshots\nmessage SolverState {\n  optional int32 iter = 1; // The current iteration\n  optional string learned_net = 2; // The file that stores the learned net.\n  repeated BlobProto history = 3; // The history for sgd solvers\n  optional int32 current_step = 4 [default = 0]; // The current step for learning rate\n}\n\nenum Phase {\n   TRAIN = 0;\n   TEST = 1;\n}\n\nmessage NetState {\n  optional Phase phase = 1 [default = TEST];\n  optional int32 level = 2 [default = 0];\n  repeated string stage = 3;\n}\n\nmessage NetStateRule {\n  // Set phase to require the NetState have a particular phase (TRAIN or TEST)\n  // to meet this rule.\n  optional Phase phase = 1;\n\n  // Set the minimum and/or maximum levels in which the layer should be used.\n  // Leave undefined to meet the rule regardless of level.\n  optional int32 min_level = 2;\n  optional int32 max_level = 3;\n\n  // Customizable sets of stages to include or exclude.\n  // The net must have ALL of the specified stages and NONE of the specified\n  // \"not_stage\"s to meet the rule.\n  // (Use multiple NetStateRules to specify conjunctions of stages.)\n  repeated string stage = 4;\n  repeated string not_stage = 5;\n}\n\n// Specifies training parameters (multipliers on global learning constants,\n// and the name and other settings used for weight sharing).\nmessage ParamSpec {\n  // The names of the parameter blobs -- useful for sharing parameters among\n  // layers, but never required otherwise.  To share a parameter between two\n  // layers, give it a (non-empty) name.\n  optional string name = 1;\n\n  // Whether to require shared weights to have the same shape, or just the same\n  // count -- defaults to STRICT if unspecified.\n  optional DimCheckMode share_mode = 2;\n  enum DimCheckMode {\n    // STRICT (default) requires that num, channels, height, width each match.\n    STRICT = 0;\n    // PERMISSIVE requires only the count (num*channels*height*width) to match.\n    PERMISSIVE = 1;\n  }\n\n  // The multiplier on the global learning rate for this parameter.\n  optional float lr_mult = 3 [default = 1.0];\n\n  // The multiplier on the global weight decay for this parameter.\n  optional float decay_mult = 4 [default = 1.0];\n}\n\n// NOTE\n// Update the next available ID when you add a new LayerParameter field.\n//\n// LayerParameter next available layer-specific ID: 147 (last added: recurrent_param)\nmessage LayerParameter {\n  optional string name = 1; // the layer name\n  optional string type = 2; // the layer type\n  repeated string bottom = 3; // the name of each bottom blob\n  repeated string top = 4; // the name of each top blob\n\n  // The train / test phase for computation.\n  optional Phase phase = 10;\n\n  // The amount of weight to assign each top blob in the objective.\n  // Each layer assigns a default value, usually of either 0 or 1,\n  // to each top blob.\n  repeated float loss_weight = 5;\n\n  // Specifies training parameters (multipliers on global learning constants,\n  // and the name and other settings used for weight sharing).\n  repeated ParamSpec param = 6;\n\n  // The blobs containing the numeric parameters of the layer.\n  repeated BlobProto blobs = 7;\n\n  // Specifies whether to backpropagate to each bottom. If unspecified,\n  // Caffe will automatically infer whether each input needs backpropagation\n  // to compute parameter gradients. If set to true for some inputs,\n  // backpropagation to those inputs is forced; if set false for some inputs,\n  // backpropagation to those inputs is skipped.\n  //\n  // The size must be either 0 or equal to the number of bottoms.\n  repeated bool propagate_down = 11;\n\n  // Rules controlling whether and when a layer is included in the network,\n  // based on the current NetState.  You may specify a non-zero number of rules\n  // to include OR exclude, but not both.  If no include or exclude rules are\n  // specified, the layer is always included.  If the current NetState meets\n  // ANY (i.e., one or more) of the specified rules, the layer is\n  // included/excluded.\n  repeated NetStateRule include = 8;\n  repeated NetStateRule exclude = 9;\n\n  // Parameters for data pre-processing.\n  optional TransformationParameter transform_param = 100;\n\n  // Parameters shared by loss layers.\n  optional LossParameter loss_param = 101;\n\n  // Layer type-specific parameters.\n  //\n  // Note: certain layers may have more than one computational engine\n  // for their implementation. These layers include an Engine type and\n  // engine parameter for selecting the implementation.\n  // The default for the engine is set by the ENGINE switch at compile-time.\n  optional AccuracyParameter accuracy_param = 102;\n  optional ArgMaxParameter argmax_param = 103;\n  optional BatchNormParameter batch_norm_param = 139;\n  optional BiasParameter bias_param = 141;\n  optional ConcatParameter concat_param = 104;\n  optional ContrastiveLossParameter contrastive_loss_param = 105;\n  optional ConvolutionParameter convolution_param = 106;\n  optional CropParameter crop_param = 144;\n  optional DataParameter data_param = 107;\n  optional DropoutParameter dropout_param = 108;\n  optional DummyDataParameter dummy_data_param = 109;\n  optional EltwiseParameter eltwise_param = 110;\n  optional ELUParameter elu_param = 140;\n  optional EmbedParameter embed_param = 137;\n  optional ExpParameter exp_param = 111;\n  optional FlattenParameter flatten_param = 135;\n  optional HDF5DataParameter hdf5_data_param = 112;\n  optional HDF5OutputParameter hdf5_output_param = 113;\n  optional HingeLossParameter hinge_loss_param = 114;\n  optional ImageDataParameter image_data_param = 115;\n  optional InfogainLossParameter infogain_loss_param = 116;\n  optional InnerProductParameter inner_product_param = 117;\n  optional InputParameter input_param = 143;\n  optional LogParameter log_param = 134;\n  optional LRNParameter lrn_param = 118;\n  optional MemoryDataParameter memory_data_param = 119;\n  optional MVNParameter mvn_param = 120;\n  optional ParameterParameter parameter_param = 145;\n  optional PoolingParameter pooling_param = 121;\n  optional PowerParameter power_param = 122;\n  optional PReLUParameter prelu_param = 131;\n  optional PythonParameter python_param = 130;\n  optional RecurrentParameter recurrent_param = 146;\n  optional ReductionParameter reduction_param = 136;\n  optional ReLUParameter relu_param = 123;\n  optional ReshapeParameter reshape_param = 133;\n  optional ScaleParameter scale_param = 142;\n  optional SigmoidParameter sigmoid_param = 124;\n  optional SoftmaxParameter softmax_param = 125;\n  optional SPPParameter spp_param = 132;\n  optional SliceParameter slice_param = 126;\n  optional TanHParameter tanh_param = 127;\n  optional ThresholdParameter threshold_param = 128;\n  optional TileParameter tile_param = 138;\n  optional WindowDataParameter window_data_param = 129;\n}\n\n// Message that stores parameters used to apply transformation\n// to the data layer's data\nmessage TransformationParameter {\n  // For data pre-processing, we can do simple scaling and subtracting the\n  // data mean, if provided. Note that the mean subtraction is always carried\n  // out before scaling.\n  optional float scale = 1 [default = 1];\n  // Specify if we want to randomly mirror data.\n  optional bool mirror = 2 [default = false];\n  // Specify if we would like to randomly crop an image.\n  optional uint32 crop_size = 3 [default = 0];\n  // mean_file and mean_value cannot be specified at the same time\n  optional string mean_file = 4;\n  // if specified can be repeated once (would substract it from all the channels)\n  // or can be repeated the same number of times as channels\n  // (would subtract them from the corresponding channel)\n  repeated float mean_value = 5;\n  // Force the decoded image to have 3 color channels.\n  optional bool force_color = 6 [default = false];\n  // Force the decoded image to have 1 color channels.\n  optional bool force_gray = 7 [default = false];\n}\n\n// Message that stores parameters shared by loss layers\nmessage LossParameter {\n  // If specified, ignore instances with the given label.\n  optional int32 ignore_label = 1;\n  // How to normalize the loss for loss layers that aggregate across batches,\n  // spatial dimensions, or other dimensions.  Currently only implemented in\n  // SoftmaxWithLoss layer.\n  enum NormalizationMode {\n    // Divide by the number of examples in the batch times spatial dimensions.\n    // Outputs that receive the ignore label will NOT be ignored in computing\n    // the normalization factor.\n    FULL = 0;\n    // Divide by the total number of output locations that do not take the\n    // ignore_label.  If ignore_label is not set, this behaves like FULL.\n    VALID = 1;\n    // Divide by the batch size.\n    BATCH_SIZE = 2;\n    // Do not normalize the loss.\n    NONE = 3;\n  }\n  optional NormalizationMode normalization = 3 [default = VALID];\n  // Deprecated.  Ignored if normalization is specified.  If normalization\n  // is not specified, then setting this to false will be equivalent to\n  // normalization = BATCH_SIZE to be consistent with previous behavior.\n  optional bool normalize = 2;\n}\n\n// Messages that store parameters used by individual layer types follow, in\n// alphabetical order.\n\nmessage AccuracyParameter {\n  // When computing accuracy, count as correct by comparing the true label to\n  // the top k scoring classes.  By default, only compare to the top scoring\n  // class (i.e. argmax).\n  optional uint32 top_k = 1 [default = 1];\n\n  // The \"label\" axis of the prediction blob, whose argmax corresponds to the\n  // predicted label -- may be negative to index from the end (e.g., -1 for the\n  // last axis).  For example, if axis == 1 and the predictions are\n  // (N x C x H x W), the label blob is expected to contain N*H*W ground truth\n  // labels with integer values in {0, 1, ..., C-1}.\n  optional int32 axis = 2 [default = 1];\n\n  // If specified, ignore instances with the given label.\n  optional int32 ignore_label = 3;\n}\n\nmessage ArgMaxParameter {\n  // If true produce pairs (argmax, maxval)\n  optional bool out_max_val = 1 [default = false];\n  optional uint32 top_k = 2 [default = 1];\n  // The axis along which to maximise -- may be negative to index from the\n  // end (e.g., -1 for the last axis).\n  // By default ArgMaxLayer maximizes over the flattened trailing dimensions\n  // for each index of the first / num dimension.\n  optional int32 axis = 3;\n}\n\nmessage ConcatParameter {\n  // The axis along which to concatenate -- may be negative to index from the\n  // end (e.g., -1 for the last axis).  Other axes must have the\n  // same dimension for all the bottom blobs.\n  // By default, ConcatLayer concatenates blobs along the \"channels\" axis (1).\n  optional int32 axis = 2 [default = 1];\n\n  // DEPRECATED: alias for \"axis\" -- does not support negative indexing.\n  optional uint32 concat_dim = 1 [default = 1];\n}\n\nmessage BatchNormParameter {\n  // If false, accumulate global mean/variance values via a moving average. If\n  // true, use those accumulated values instead of computing mean/variance\n  // across the batch.\n  optional bool use_global_stats = 1;\n  // How much does the moving average decay each iteration?\n  optional float moving_average_fraction = 2 [default = .999];\n  // Small value to add to the variance estimate so that we don't divide by\n  // zero.\n  optional float eps = 3 [default = 1e-5];\n}\n\nmessage BiasParameter {\n  // The first axis of bottom[0] (the first input Blob) along which to apply\n  // bottom[1] (the second input Blob).  May be negative to index from the end\n  // (e.g., -1 for the last axis).\n  //\n  // For example, if bottom[0] is 4D with shape 100x3x40x60, the output\n  // top[0] will have the same shape, and bottom[1] may have any of the\n  // following shapes (for the given value of axis):\n  //    (axis == 0 == -4) 100; 100x3; 100x3x40; 100x3x40x60\n  //    (axis == 1 == -3)          3;     3x40;     3x40x60\n  //    (axis == 2 == -2)                   40;       40x60\n  //    (axis == 3 == -1)                                60\n  // Furthermore, bottom[1] may have the empty shape (regardless of the value of\n  // \"axis\") -- a scalar bias.\n  optional int32 axis = 1 [default = 1];\n\n  // (num_axes is ignored unless just one bottom is given and the bias is\n  // a learned parameter of the layer.  Otherwise, num_axes is determined by the\n  // number of axes by the second bottom.)\n  // The number of axes of the input (bottom[0]) covered by the bias\n  // parameter, or -1 to cover all axes of bottom[0] starting from `axis`.\n  // Set num_axes := 0, to add a zero-axis Blob: a scalar.\n  optional int32 num_axes = 2 [default = 1];\n\n  // (filler is ignored unless just one bottom is given and the bias is\n  // a learned parameter of the layer.)\n  // The initialization for the learned bias parameter.\n  // Default is the zero (0) initialization, resulting in the BiasLayer\n  // initially performing the identity operation.\n  optional FillerParameter filler = 3;\n}\n\nmessage ContrastiveLossParameter {\n  // margin for dissimilar pair\n  optional float margin = 1 [default = 1.0];\n  // The first implementation of this cost did not exactly match the cost of\n  // Hadsell et al 2006 -- using (margin - d^2) instead of (margin - d)^2.\n  // legacy_version = false (the default) uses (margin - d)^2 as proposed in the\n  // Hadsell paper. New models should probably use this version.\n  // legacy_version = true uses (margin - d^2). This is kept to support /\n  // reproduce existing models and results\n  optional bool legacy_version = 2 [default = false];\n}\n\nmessage ConvolutionParameter {\n  optional uint32 num_output = 1; // The number of outputs for the layer\n  optional bool bias_term = 2 [default = true]; // whether to have bias terms\n\n  // Pad, kernel size, and stride are all given as a single value for equal\n  // dimensions in all spatial dimensions, or once per spatial dimension.\n  repeated uint32 pad = 3; // The padding size; defaults to 0\n  repeated uint32 kernel_size = 4; // The kernel size\n  repeated uint32 stride = 6; // The stride; defaults to 1\n  // Factor used to dilate the kernel, (implicitly) zero-filling the resulting\n  // holes. (Kernel dilation is sometimes referred to by its use in the\n  // algorithme à trous from Holschneider et al. 1987.)\n  repeated uint32 dilation = 18; // The dilation; defaults to 1\n\n  // For 2D convolution only, the *_h and *_w versions may also be used to\n  // specify both spatial dimensions.\n  optional uint32 pad_h = 9 [default = 0]; // The padding height (2D only)\n  optional uint32 pad_w = 10 [default = 0]; // The padding width (2D only)\n  optional uint32 kernel_h = 11; // The kernel height (2D only)\n  optional uint32 kernel_w = 12; // The kernel width (2D only)\n  optional uint32 stride_h = 13; // The stride height (2D only)\n  optional uint32 stride_w = 14; // The stride width (2D only)\n\n  optional uint32 group = 5 [default = 1]; // The group size for group conv\n\n  optional FillerParameter weight_filler = 7; // The filler for the weight\n  optional FillerParameter bias_filler = 8; // The filler for the bias\n  enum Engine {\n    DEFAULT = 0;\n    CAFFE = 1;\n    CUDNN = 2;\n  }\n  optional Engine engine = 15 [default = DEFAULT];\n\n  // The axis to interpret as \"channels\" when performing convolution.\n  // Preceding dimensions are treated as independent inputs;\n  // succeeding dimensions are treated as \"spatial\".\n  // With (N, C, H, W) inputs, and axis == 1 (the default), we perform\n  // N independent 2D convolutions, sliding C-channel (or (C/g)-channels, for\n  // groups g>1) filters across the spatial axes (H, W) of the input.\n  // With (N, C, D, H, W) inputs, and axis == 1, we perform\n  // N independent 3D convolutions, sliding (C/g)-channels\n  // filters across the spatial axes (D, H, W) of the input.\n  optional int32 axis = 16 [default = 1];\n\n  // Whether to force use of the general ND convolution, even if a specific\n  // implementation for blobs of the appropriate number of spatial dimensions\n  // is available. (Currently, there is only a 2D-specific convolution\n  // implementation; for input blobs with num_axes != 2, this option is\n  // ignored and the ND implementation will be used.)\n  optional bool force_nd_im2col = 17 [default = false];\n}\n\nmessage CropParameter {\n  // To crop, elements of the first bottom are selected to fit the dimensions\n  // of the second, reference bottom. The crop is configured by\n  // - the crop `axis` to pick the dimensions for cropping\n  // - the crop `offset` to set the shift for all/each dimension\n  // to align the cropped bottom with the reference bottom.\n  // All dimensions up to but excluding `axis` are preserved, while\n  // the dimensions including and trailing `axis` are cropped.\n  // If only one `offset` is set, then all dimensions are offset by this amount.\n  // Otherwise, the number of offsets must equal the number of cropped axes to\n  // shift the crop in each dimension accordingly.\n  // Note: standard dimensions are N,C,H,W so the default is a spatial crop,\n  // and `axis` may be negative to index from the end (e.g., -1 for the last\n  // axis).\n  optional int32 axis = 1 [default = 2];\n  repeated uint32 offset = 2;\n}\n\nmessage DataParameter {\n  enum DB {\n    LEVELDB = 0;\n    LMDB = 1;\n  }\n  // Specify the data source.\n  optional string source = 1;\n  // Specify the batch size.\n  optional uint32 batch_size = 4;\n  // The rand_skip variable is for the data layer to skip a few data points\n  // to avoid all asynchronous sgd clients to start at the same point. The skip\n  // point would be set as rand_skip * rand(0,1). Note that rand_skip should not\n  // be larger than the number of keys in the database.\n  // DEPRECATED. Each solver accesses a different subset of the database.\n  optional uint32 rand_skip = 7 [default = 0];\n  optional DB backend = 8 [default = LEVELDB];\n  // DEPRECATED. See TransformationParameter. For data pre-processing, we can do\n  // simple scaling and subtracting the data mean, if provided. Note that the\n  // mean subtraction is always carried out before scaling.\n  optional float scale = 2 [default = 1];\n  optional string mean_file = 3;\n  // DEPRECATED. See TransformationParameter. Specify if we would like to randomly\n  // crop an image.\n  optional uint32 crop_size = 5 [default = 0];\n  // DEPRECATED. See TransformationParameter. Specify if we want to randomly mirror\n  // data.\n  optional bool mirror = 6 [default = false];\n  // Force the encoded image to have 3 color channels\n  optional bool force_encoded_color = 9 [default = false];\n  // Prefetch queue (Number of batches to prefetch to host memory, increase if\n  // data access bandwidth varies).\n  optional uint32 prefetch = 10 [default = 4];\n}\n\nmessage DropoutParameter {\n  optional float dropout_ratio = 1 [default = 0.5]; // dropout ratio\n}\n\n// DummyDataLayer fills any number of arbitrarily shaped blobs with random\n// (or constant) data generated by \"Fillers\" (see \"message FillerParameter\").\nmessage DummyDataParameter {\n  // This layer produces N >= 1 top blobs.  DummyDataParameter must specify 1 or N\n  // shape fields, and 0, 1 or N data_fillers.\n  //\n  // If 0 data_fillers are specified, ConstantFiller with a value of 0 is used.\n  // If 1 data_filler is specified, it is applied to all top blobs.  If N are\n  // specified, the ith is applied to the ith top blob.\n  repeated FillerParameter data_filler = 1;\n  repeated BlobShape shape = 6;\n\n  // 4D dimensions -- deprecated.  Use \"shape\" instead.\n  repeated uint32 num = 2;\n  repeated uint32 channels = 3;\n  repeated uint32 height = 4;\n  repeated uint32 width = 5;\n}\n\nmessage EltwiseParameter {\n  enum EltwiseOp {\n    PROD = 0;\n    SUM = 1;\n    MAX = 2;\n  }\n  optional EltwiseOp operation = 1 [default = SUM]; // element-wise operation\n  repeated float coeff = 2; // blob-wise coefficient for SUM operation\n\n  // Whether to use an asymptotically slower (for >2 inputs) but stabler method\n  // of computing the gradient for the PROD operation. (No effect for SUM op.)\n  optional bool stable_prod_grad = 3 [default = true];\n}\n\n// Message that stores parameters used by ELULayer\nmessage ELUParameter {\n  // Described in:\n  // Clevert, D.-A., Unterthiner, T., & Hochreiter, S. (2015). Fast and Accurate\n  // Deep Network Learning by Exponential Linear Units (ELUs). arXiv\n  optional float alpha = 1 [default = 1];\n}\n\n// Message that stores parameters used by EmbedLayer\nmessage EmbedParameter {\n  optional uint32 num_output = 1; // The number of outputs for the layer\n  // The input is given as integers to be interpreted as one-hot\n  // vector indices with dimension num_input.  Hence num_input should be\n  // 1 greater than the maximum possible input value.\n  optional uint32 input_dim = 2;\n\n  optional bool bias_term = 3 [default = true]; // Whether to use a bias term\n  optional FillerParameter weight_filler = 4; // The filler for the weight\n  optional FillerParameter bias_filler = 5; // The filler for the bias\n\n}\n\n// Message that stores parameters used by ExpLayer\nmessage ExpParameter {\n  // ExpLayer computes outputs y = base ^ (shift + scale * x), for base > 0.\n  // Or if base is set to the default (-1), base is set to e,\n  // so y = exp(shift + scale * x).\n  optional float base = 1 [default = -1.0];\n  optional float scale = 2 [default = 1.0];\n  optional float shift = 3 [default = 0.0];\n}\n\n/// Message that stores parameters used by FlattenLayer\nmessage FlattenParameter {\n  // The first axis to flatten: all preceding axes are retained in the output.\n  // May be negative to index from the end (e.g., -1 for the last axis).\n  optional int32 axis = 1 [default = 1];\n\n  // The last axis to flatten: all following axes are retained in the output.\n  // May be negative to index from the end (e.g., the default -1 for the last\n  // axis).\n  optional int32 end_axis = 2 [default = -1];\n}\n\n// Message that stores parameters used by HDF5DataLayer\nmessage HDF5DataParameter {\n  // Specify the data source.\n  optional string source = 1;\n  // Specify the batch size.\n  optional uint32 batch_size = 2;\n\n  // Specify whether to shuffle the data.\n  // If shuffle == true, the ordering of the HDF5 files is shuffled,\n  // and the ordering of data within any given HDF5 file is shuffled,\n  // but data between different files are not interleaved; all of a file's\n  // data are output (in a random order) before moving onto another file.\n  optional bool shuffle = 3 [default = false];\n}\n\nmessage HDF5OutputParameter {\n  optional string file_name = 1;\n}\n\nmessage HingeLossParameter {\n  enum Norm {\n    L1 = 1;\n    L2 = 2;\n  }\n  // Specify the Norm to use L1 or L2\n  optional Norm norm = 1 [default = L1];\n}\n\nmessage ImageDataParameter {\n  // Specify the data source.\n  optional string source = 1;\n  // Specify the batch size.\n  optional uint32 batch_size = 4 [default = 1];\n  // The rand_skip variable is for the data layer to skip a few data points\n  // to avoid all asynchronous sgd clients to start at the same point. The skip\n  // point would be set as rand_skip * rand(0,1). Note that rand_skip should not\n  // be larger than the number of keys in the database.\n  optional uint32 rand_skip = 7 [default = 0];\n  // Whether or not ImageLayer should shuffle the list of files at every epoch.\n  optional bool shuffle = 8 [default = false];\n  // It will also resize images if new_height or new_width are not zero.\n  optional uint32 new_height = 9 [default = 0];\n  optional uint32 new_width = 10 [default = 0];\n  // Specify if the images are color or gray\n  optional bool is_color = 11 [default = true];\n  // DEPRECATED. See TransformationParameter. For data pre-processing, we can do\n  // simple scaling and subtracting the data mean, if provided. Note that the\n  // mean subtraction is always carried out before scaling.\n  optional float scale = 2 [default = 1];\n  optional string mean_file = 3;\n  // DEPRECATED. See TransformationParameter. Specify if we would like to randomly\n  // crop an image.\n  optional uint32 crop_size = 5 [default = 0];\n  // DEPRECATED. See TransformationParameter. Specify if we want to randomly mirror\n  // data.\n  optional bool mirror = 6 [default = false];\n  optional string root_folder = 12 [default = \"\"];\n}\n\nmessage InfogainLossParameter {\n  // Specify the infogain matrix source.\n  optional string source = 1;\n}\n\nmessage InnerProductParameter {\n  optional uint32 num_output = 1; // The number of outputs for the layer\n  optional bool bias_term = 2 [default = true]; // whether to have bias terms\n  optional FillerParameter weight_filler = 3; // The filler for the weight\n  optional FillerParameter bias_filler = 4; // The filler for the bias\n\n  // The first axis to be lumped into a single inner product computation;\n  // all preceding axes are retained in the output.\n  // May be negative to index from the end (e.g., -1 for the last axis).\n  optional int32 axis = 5 [default = 1];\n  // Specify whether to transpose the weight matrix or not.\n  // If transpose == true, any operations will be performed on the transpose\n  // of the weight matrix. The weight matrix itself is not going to be transposed\n  // but rather the transfer flag of operations will be toggled accordingly.\n  optional bool transpose = 6 [default = false];\n}\n\nmessage InputParameter {\n  // This layer produces N >= 1 top blob(s) to be assigned manually.\n  // Define N shapes to set a shape for each top.\n  // Define 1 shape to set the same shape for every top.\n  // Define no shape to defer to reshaping manually.\n  repeated BlobShape shape = 1;\n}\n\n// Message that stores parameters used by LogLayer\nmessage LogParameter {\n  // LogLayer computes outputs y = log_base(shift + scale * x), for base > 0.\n  // Or if base is set to the default (-1), base is set to e,\n  // so y = ln(shift + scale * x) = log_e(shift + scale * x)\n  optional float base = 1 [default = -1.0];\n  optional float scale = 2 [default = 1.0];\n  optional float shift = 3 [default = 0.0];\n}\n\n// Message that stores parameters used by LRNLayer\nmessage LRNParameter {\n  optional uint32 local_size = 1 [default = 5];\n  optional float alpha = 2 [default = 1.];\n  optional float beta = 3 [default = 0.75];\n  enum NormRegion {\n    ACROSS_CHANNELS = 0;\n    WITHIN_CHANNEL = 1;\n  }\n  optional NormRegion norm_region = 4 [default = ACROSS_CHANNELS];\n  optional float k = 5 [default = 1.];\n  enum Engine {\n    DEFAULT = 0;\n    CAFFE = 1;\n    CUDNN = 2;\n  }\n  optional Engine engine = 6 [default = DEFAULT];\n}\n\nmessage MemoryDataParameter {\n  optional uint32 batch_size = 1;\n  optional uint32 channels = 2;\n  optional uint32 height = 3;\n  optional uint32 width = 4;\n}\n\nmessage MVNParameter {\n  // This parameter can be set to false to normalize mean only\n  optional bool normalize_variance = 1 [default = true];\n\n  // This parameter can be set to true to perform DNN-like MVN\n  optional bool across_channels = 2 [default = false];\n\n  // Epsilon for not dividing by zero while normalizing variance\n  optional float eps = 3 [default = 1e-9];\n}\n\nmessage ParameterParameter {\n  optional BlobShape shape = 1;\n}\n\nmessage PoolingParameter {\n  enum PoolMethod {\n    MAX = 0;\n    AVE = 1;\n    STOCHASTIC = 2;\n  }\n  optional PoolMethod pool = 1 [default = MAX]; // The pooling method\n  // Pad, kernel size, and stride are all given as a single value for equal\n  // dimensions in height and width or as Y, X pairs.\n  optional uint32 pad = 4 [default = 0]; // The padding size (equal in Y, X)\n  optional uint32 pad_h = 9 [default = 0]; // The padding height\n  optional uint32 pad_w = 10 [default = 0]; // The padding width\n  optional uint32 kernel_size = 2; // The kernel size (square)\n  optional uint32 kernel_h = 5; // The kernel height\n  optional uint32 kernel_w = 6; // The kernel width\n  optional uint32 stride = 3 [default = 1]; // The stride (equal in Y, X)\n  optional uint32 stride_h = 7; // The stride height\n  optional uint32 stride_w = 8; // The stride width\n  enum Engine {\n    DEFAULT = 0;\n    CAFFE = 1;\n    CUDNN = 2;\n  }\n  optional Engine engine = 11 [default = DEFAULT];\n  // If global_pooling then it will pool over the size of the bottom by doing\n  // kernel_h = bottom->height and kernel_w = bottom->width\n  optional bool global_pooling = 12 [default = false];\n}\n\nmessage PowerParameter {\n  // PowerLayer computes outputs y = (shift + scale * x) ^ power.\n  optional float power = 1 [default = 1.0];\n  optional float scale = 2 [default = 1.0];\n  optional float shift = 3 [default = 0.0];\n}\n\nmessage PythonParameter {\n  optional string module = 1;\n  optional string layer = 2;\n  // This value is set to the attribute `param_str` of the `PythonLayer` object\n  // in Python before calling the `setup()` method. This could be a number,\n  // string, dictionary in Python dict format, JSON, etc. You may parse this\n  // string in `setup` method and use it in `forward` and `backward`.\n  optional string param_str = 3 [default = ''];\n  // Whether this PythonLayer is shared among worker solvers during data parallelism.\n  // If true, each worker solver sequentially run forward from this layer.\n  // This value should be set true if you are using it as a data layer.\n  optional bool share_in_parallel = 4 [default = false];\n}\n\n// Message that stores parameters used by RecurrentLayer\nmessage RecurrentParameter {\n  // The dimension of the output (and usually hidden state) representation --\n  // must be explicitly set to non-zero.\n  optional uint32 num_output = 1 [default = 0];\n\n  optional FillerParameter weight_filler = 2; // The filler for the weight\n  optional FillerParameter bias_filler = 3; // The filler for the bias\n\n  // Whether to enable displaying debug_info in the unrolled recurrent net.\n  optional bool debug_info = 4 [default = false];\n\n  // Whether to add as additional inputs (bottoms) the initial hidden state\n  // blobs, and add as additional outputs (tops) the final timestep hidden state\n  // blobs.  The number of additional bottom/top blobs required depends on the\n  // recurrent architecture -- e.g., 1 for RNNs, 2 for LSTMs.\n  optional bool expose_hidden = 5 [default = false];\n}\n\n// Message that stores parameters used by ReductionLayer\nmessage ReductionParameter {\n  enum ReductionOp {\n    SUM = 1;\n    ASUM = 2;\n    SUMSQ = 3;\n    MEAN = 4;\n  }\n\n  optional ReductionOp operation = 1 [default = SUM]; // reduction operation\n\n  // The first axis to reduce to a scalar -- may be negative to index from the\n  // end (e.g., -1 for the last axis).\n  // (Currently, only reduction along ALL \"tail\" axes is supported; reduction\n  // of axis M through N, where N < num_axes - 1, is unsupported.)\n  // Suppose we have an n-axis bottom Blob with shape:\n  //     (d0, d1, d2, ..., d(m-1), dm, d(m+1), ..., d(n-1)).\n  // If axis == m, the output Blob will have shape\n  //     (d0, d1, d2, ..., d(m-1)),\n  // and the ReductionOp operation is performed (d0 * d1 * d2 * ... * d(m-1))\n  // times, each including (dm * d(m+1) * ... * d(n-1)) individual data.\n  // If axis == 0 (the default), the output Blob always has the empty shape\n  // (count 1), performing reduction across the entire input --\n  // often useful for creating new loss functions.\n  optional int32 axis = 2 [default = 0];\n\n  optional float coeff = 3 [default = 1.0]; // coefficient for output\n}\n\n// Message that stores parameters used by ReLULayer\nmessage ReLUParameter {\n  // Allow non-zero slope for negative inputs to speed up optimization\n  // Described in:\n  // Maas, A. L., Hannun, A. Y., & Ng, A. Y. (2013). Rectifier nonlinearities\n  // improve neural network acoustic models. In ICML Workshop on Deep Learning\n  // for Audio, Speech, and Language Processing.\n  optional float negative_slope = 1 [default = 0];\n  enum Engine {\n    DEFAULT = 0;\n    CAFFE = 1;\n    CUDNN = 2;\n  }\n  optional Engine engine = 2 [default = DEFAULT];\n}\n\nmessage ReshapeParameter {\n  // Specify the output dimensions. If some of the dimensions are set to 0,\n  // the corresponding dimension from the bottom layer is used (unchanged).\n  // Exactly one dimension may be set to -1, in which case its value is\n  // inferred from the count of the bottom blob and the remaining dimensions.\n  // For example, suppose we want to reshape a 2D blob \"input\" with shape 2 x 8:\n  //\n  //   layer {\n  //     type: \"Reshape\" bottom: \"input\" top: \"output\"\n  //     reshape_param { ... }\n  //   }\n  //\n  // If \"input\" is 2D with shape 2 x 8, then the following reshape_param\n  // specifications are all equivalent, producing a 3D blob \"output\" with shape\n  // 2 x 2 x 4:\n  //\n  //   reshape_param { shape { dim:  2  dim: 2  dim:  4 } }\n  //   reshape_param { shape { dim:  0  dim: 2  dim:  4 } }\n  //   reshape_param { shape { dim:  0  dim: 2  dim: -1 } }\n  //   reshape_param { shape { dim:  0  dim:-1  dim:  4 } }\n  //\n  optional BlobShape shape = 1;\n\n  // axis and num_axes control the portion of the bottom blob's shape that are\n  // replaced by (included in) the reshape. By default (axis == 0 and\n  // num_axes == -1), the entire bottom blob shape is included in the reshape,\n  // and hence the shape field must specify the entire output shape.\n  //\n  // axis may be non-zero to retain some portion of the beginning of the input\n  // shape (and may be negative to index from the end; e.g., -1 to begin the\n  // reshape after the last axis, including nothing in the reshape,\n  // -2 to include only the last axis, etc.).\n  //\n  // For example, suppose \"input\" is a 2D blob with shape 2 x 8.\n  // Then the following ReshapeLayer specifications are all equivalent,\n  // producing a blob \"output\" with shape 2 x 2 x 4:\n  //\n  //   reshape_param { shape { dim: 2  dim: 2  dim: 4 } }\n  //   reshape_param { shape { dim: 2  dim: 4 } axis:  1 }\n  //   reshape_param { shape { dim: 2  dim: 4 } axis: -3 }\n  //\n  // num_axes specifies the extent of the reshape.\n  // If num_axes >= 0 (and axis >= 0), the reshape will be performed only on\n  // input axes in the range [axis, axis+num_axes].\n  // num_axes may also be -1, the default, to include all remaining axes\n  // (starting from axis).\n  //\n  // For example, suppose \"input\" is a 2D blob with shape 2 x 8.\n  // Then the following ReshapeLayer specifications are equivalent,\n  // producing a blob \"output\" with shape 1 x 2 x 8.\n  //\n  //   reshape_param { shape { dim:  1  dim: 2  dim:  8 } }\n  //   reshape_param { shape { dim:  1  dim: 2  }  num_axes: 1 }\n  //   reshape_param { shape { dim:  1  }  num_axes: 0 }\n  //\n  // On the other hand, these would produce output blob shape 2 x 1 x 8:\n  //\n  //   reshape_param { shape { dim: 2  dim: 1  dim: 8  }  }\n  //   reshape_param { shape { dim: 1 }  axis: 1  num_axes: 0 }\n  //\n  optional int32 axis = 2 [default = 0];\n  optional int32 num_axes = 3 [default = -1];\n}\n\nmessage ScaleParameter {\n  // The first axis of bottom[0] (the first input Blob) along which to apply\n  // bottom[1] (the second input Blob).  May be negative to index from the end\n  // (e.g., -1 for the last axis).\n  //\n  // For example, if bottom[0] is 4D with shape 100x3x40x60, the output\n  // top[0] will have the same shape, and bottom[1] may have any of the\n  // following shapes (for the given value of axis):\n  //    (axis == 0 == -4) 100; 100x3; 100x3x40; 100x3x40x60\n  //    (axis == 1 == -3)          3;     3x40;     3x40x60\n  //    (axis == 2 == -2)                   40;       40x60\n  //    (axis == 3 == -1)                                60\n  // Furthermore, bottom[1] may have the empty shape (regardless of the value of\n  // \"axis\") -- a scalar multiplier.\n  optional int32 axis = 1 [default = 1];\n\n  // (num_axes is ignored unless just one bottom is given and the scale is\n  // a learned parameter of the layer.  Otherwise, num_axes is determined by the\n  // number of axes by the second bottom.)\n  // The number of axes of the input (bottom[0]) covered by the scale\n  // parameter, or -1 to cover all axes of bottom[0] starting from `axis`.\n  // Set num_axes := 0, to multiply with a zero-axis Blob: a scalar.\n  optional int32 num_axes = 2 [default = 1];\n\n  // (filler is ignored unless just one bottom is given and the scale is\n  // a learned parameter of the layer.)\n  // The initialization for the learned scale parameter.\n  // Default is the unit (1) initialization, resulting in the ScaleLayer\n  // initially performing the identity operation.\n  optional FillerParameter filler = 3;\n\n  // Whether to also learn a bias (equivalent to a ScaleLayer+BiasLayer, but\n  // may be more efficient).  Initialized with bias_filler (defaults to 0).\n  optional bool bias_term = 4 [default = false];\n  optional FillerParameter bias_filler = 5;\n}\n\nmessage SigmoidParameter {\n  enum Engine {\n    DEFAULT = 0;\n    CAFFE = 1;\n    CUDNN = 2;\n  }\n  optional Engine engine = 1 [default = DEFAULT];\n}\n\nmessage SliceParameter {\n  // The axis along which to slice -- may be negative to index from the end\n  // (e.g., -1 for the last axis).\n  // By default, SliceLayer concatenates blobs along the \"channels\" axis (1).\n  optional int32 axis = 3 [default = 1];\n  repeated uint32 slice_point = 2;\n\n  // DEPRECATED: alias for \"axis\" -- does not support negative indexing.\n  optional uint32 slice_dim = 1 [default = 1];\n}\n\n// Message that stores parameters used by SoftmaxLayer, SoftmaxWithLossLayer\nmessage SoftmaxParameter {\n  enum Engine {\n    DEFAULT = 0;\n    CAFFE = 1;\n    CUDNN = 2;\n  }\n  optional Engine engine = 1 [default = DEFAULT];\n\n  // The axis along which to perform the softmax -- may be negative to index\n  // from the end (e.g., -1 for the last axis).\n  // Any other axes will be evaluated as independent softmaxes.\n  optional int32 axis = 2 [default = 1];\n}\n\nmessage TanHParameter {\n  enum Engine {\n    DEFAULT = 0;\n    CAFFE = 1;\n    CUDNN = 2;\n  }\n  optional Engine engine = 1 [default = DEFAULT];\n}\n\n// Message that stores parameters used by TileLayer\nmessage TileParameter {\n  // The index of the axis to tile.\n  optional int32 axis = 1 [default = 1];\n\n  // The number of copies (tiles) of the blob to output.\n  optional int32 tiles = 2;\n}\n\n// Message that stores parameters used by ThresholdLayer\nmessage ThresholdParameter {\n  optional float threshold = 1 [default = 0]; // Strictly positive values\n}\n\nmessage WindowDataParameter {\n  // Specify the data source.\n  optional string source = 1;\n  // For data pre-processing, we can do simple scaling and subtracting the\n  // data mean, if provided. Note that the mean subtraction is always carried\n  // out before scaling.\n  optional float scale = 2 [default = 1];\n  optional string mean_file = 3;\n  // Specify the batch size.\n  optional uint32 batch_size = 4;\n  // Specify if we would like to randomly crop an image.\n  optional uint32 crop_size = 5 [default = 0];\n  // Specify if we want to randomly mirror data.\n  optional bool mirror = 6 [default = false];\n  // Foreground (object) overlap threshold\n  optional float fg_threshold = 7 [default = 0.5];\n  // Background (non-object) overlap threshold\n  optional float bg_threshold = 8 [default = 0.5];\n  // Fraction of batch that should be foreground objects\n  optional float fg_fraction = 9 [default = 0.25];\n  // Amount of contextual padding to add around a window\n  // (used only by the window_data_layer)\n  optional uint32 context_pad = 10 [default = 0];\n  // Mode for cropping out a detection window\n  // warp: cropped window is warped to a fixed size and aspect ratio\n  // square: the tightest square around the window is cropped\n  optional string crop_mode = 11 [default = \"warp\"];\n  // cache_images: will load all images in memory for faster access\n  optional bool cache_images = 12 [default = false];\n  // append root_folder to locate images\n  optional string root_folder = 13 [default = \"\"];\n}\n\nmessage SPPParameter {\n  enum PoolMethod {\n    MAX = 0;\n    AVE = 1;\n    STOCHASTIC = 2;\n  }\n  optional uint32 pyramid_height = 1;\n  optional PoolMethod pool = 2 [default = MAX]; // The pooling method\n  enum Engine {\n    DEFAULT = 0;\n    CAFFE = 1;\n    CUDNN = 2;\n  }\n  optional Engine engine = 6 [default = DEFAULT];\n}\n\n// DEPRECATED: use LayerParameter.\nmessage V1LayerParameter {\n  repeated string bottom = 2;\n  repeated string top = 3;\n  optional string name = 4;\n  repeated NetStateRule include = 32;\n  repeated NetStateRule exclude = 33;\n  enum LayerType {\n    NONE = 0;\n    ABSVAL = 35;\n    ACCURACY = 1;\n    ARGMAX = 30;\n    BNLL = 2;\n    CONCAT = 3;\n    CONTRASTIVE_LOSS = 37;\n    CONVOLUTION = 4;\n    DATA = 5;\n    DECONVOLUTION = 39;\n    DROPOUT = 6;\n    DUMMY_DATA = 32;\n    EUCLIDEAN_LOSS = 7;\n    ELTWISE = 25;\n    EXP = 38;\n    FLATTEN = 8;\n    HDF5_DATA = 9;\n    HDF5_OUTPUT = 10;\n    HINGE_LOSS = 28;\n    IM2COL = 11;\n    IMAGE_DATA = 12;\n    INFOGAIN_LOSS = 13;\n    INNER_PRODUCT = 14;\n    LRN = 15;\n    MEMORY_DATA = 29;\n    MULTINOMIAL_LOGISTIC_LOSS = 16;\n    MVN = 34;\n    POOLING = 17;\n    POWER = 26;\n    RELU = 18;\n    SIGMOID = 19;\n    SIGMOID_CROSS_ENTROPY_LOSS = 27;\n    SILENCE = 36;\n    SOFTMAX = 20;\n    SOFTMAX_LOSS = 21;\n    SPLIT = 22;\n    SLICE = 33;\n    TANH = 23;\n    WINDOW_DATA = 24;\n    THRESHOLD = 31;\n  }\n  optional LayerType type = 5;\n  repeated BlobProto blobs = 6;\n  repeated string param = 1001;\n  repeated DimCheckMode blob_share_mode = 1002;\n  enum DimCheckMode {\n    STRICT = 0;\n    PERMISSIVE = 1;\n  }\n  repeated float blobs_lr = 7;\n  repeated float weight_decay = 8;\n  repeated float loss_weight = 35;\n  optional AccuracyParameter accuracy_param = 27;\n  optional ArgMaxParameter argmax_param = 23;\n  optional ConcatParameter concat_param = 9;\n  optional ContrastiveLossParameter contrastive_loss_param = 40;\n  optional ConvolutionParameter convolution_param = 10;\n  optional DataParameter data_param = 11;\n  optional DropoutParameter dropout_param = 12;\n  optional DummyDataParameter dummy_data_param = 26;\n  optional EltwiseParameter eltwise_param = 24;\n  optional ExpParameter exp_param = 41;\n  optional HDF5DataParameter hdf5_data_param = 13;\n  optional HDF5OutputParameter hdf5_output_param = 14;\n  optional HingeLossParameter hinge_loss_param = 29;\n  optional ImageDataParameter image_data_param = 15;\n  optional InfogainLossParameter infogain_loss_param = 16;\n  optional InnerProductParameter inner_product_param = 17;\n  optional LRNParameter lrn_param = 18;\n  optional MemoryDataParameter memory_data_param = 22;\n  optional MVNParameter mvn_param = 34;\n  optional PoolingParameter pooling_param = 19;\n  optional PowerParameter power_param = 21;\n  optional ReLUParameter relu_param = 30;\n  optional SigmoidParameter sigmoid_param = 38;\n  optional SoftmaxParameter softmax_param = 39;\n  optional SliceParameter slice_param = 31;\n  optional TanHParameter tanh_param = 37;\n  optional ThresholdParameter threshold_param = 25;\n  optional WindowDataParameter window_data_param = 20;\n  optional TransformationParameter transform_param = 36;\n  optional LossParameter loss_param = 42;\n  optional V0LayerParameter layer = 1;\n}\n\n// DEPRECATED: V0LayerParameter is the old way of specifying layer parameters\n// in Caffe.  We keep this message type around for legacy support.\nmessage V0LayerParameter {\n  optional string name = 1; // the layer name\n  optional string type = 2; // the string to specify the layer type\n\n  // Parameters to specify layers with inner products.\n  optional uint32 num_output = 3; // The number of outputs for the layer\n  optional bool biasterm = 4 [default = true]; // whether to have bias terms\n  optional FillerParameter weight_filler = 5; // The filler for the weight\n  optional FillerParameter bias_filler = 6; // The filler for the bias\n\n  optional uint32 pad = 7 [default = 0]; // The padding size\n  optional uint32 kernelsize = 8; // The kernel size\n  optional uint32 group = 9 [default = 1]; // The group size for group conv\n  optional uint32 stride = 10 [default = 1]; // The stride\n  enum PoolMethod {\n    MAX = 0;\n    AVE = 1;\n    STOCHASTIC = 2;\n  }\n  optional PoolMethod pool = 11 [default = MAX]; // The pooling method\n  optional float dropout_ratio = 12 [default = 0.5]; // dropout ratio\n\n  optional uint32 local_size = 13 [default = 5]; // for local response norm\n  optional float alpha = 14 [default = 1.]; // for local response norm\n  optional float beta = 15 [default = 0.75]; // for local response norm\n  optional float k = 22 [default = 1.];\n\n  // For data layers, specify the data source\n  optional string source = 16;\n  // For data pre-processing, we can do simple scaling and subtracting the\n  // data mean, if provided. Note that the mean subtraction is always carried\n  // out before scaling.\n  optional float scale = 17 [default = 1];\n  optional string meanfile = 18;\n  // For data layers, specify the batch size.\n  optional uint32 batchsize = 19;\n  // For data layers, specify if we would like to randomly crop an image.\n  optional uint32 cropsize = 20 [default = 0];\n  // For data layers, specify if we want to randomly mirror data.\n  optional bool mirror = 21 [default = false];\n\n  // The blobs containing the numeric parameters of the layer\n  repeated BlobProto blobs = 50;\n  // The ratio that is multiplied on the global learning rate. If you want to\n  // set the learning ratio for one blob, you need to set it for all blobs.\n  repeated float blobs_lr = 51;\n  // The weight decay that is multiplied on the global weight decay.\n  repeated float weight_decay = 52;\n\n  // The rand_skip variable is for the data layer to skip a few data points\n  // to avoid all asynchronous sgd clients to start at the same point. The skip\n  // point would be set as rand_skip * rand(0,1). Note that rand_skip should not\n  // be larger than the number of keys in the database.\n  optional uint32 rand_skip = 53 [default = 0];\n\n  // Fields related to detection (det_*)\n  // foreground (object) overlap threshold\n  optional float det_fg_threshold = 54 [default = 0.5];\n  // background (non-object) overlap threshold\n  optional float det_bg_threshold = 55 [default = 0.5];\n  // Fraction of batch that should be foreground objects\n  optional float det_fg_fraction = 56 [default = 0.25];\n\n  // optional bool OBSOLETE_can_clobber = 57 [default = true];\n\n  // Amount of contextual padding to add around a window\n  // (used only by the window_data_layer)\n  optional uint32 det_context_pad = 58 [default = 0];\n\n  // Mode for cropping out a detection window\n  // warp: cropped window is warped to a fixed size and aspect ratio\n  // square: the tightest square around the window is cropped\n  optional string det_crop_mode = 59 [default = \"warp\"];\n\n  // For ReshapeLayer, one needs to specify the new dimensions.\n  optional int32 new_num = 60 [default = 0];\n  optional int32 new_channels = 61 [default = 0];\n  optional int32 new_height = 62 [default = 0];\n  optional int32 new_width = 63 [default = 0];\n\n  // Whether or not ImageLayer should shuffle the list of files at every epoch.\n  // It will also resize images if new_height or new_width are not zero.\n  optional bool shuffle_images = 64 [default = false];\n\n  // For ConcatLayer, one needs to specify the dimension for concatenation, and\n  // the other dimensions must be the same for all the bottom blobs.\n  // By default it will concatenate blobs along the channels dimension.\n  optional uint32 concat_dim = 65 [default = 1];\n\n  optional HDF5OutputParameter hdf5_output_param = 1001;\n}\n\nmessage PReLUParameter {\n  // Parametric ReLU described in K. He et al, Delving Deep into Rectifiers:\n  // Surpassing Human-Level Performance on ImageNet Classification, 2015.\n\n  // Initial value of a_i. Default is a_i=0.25 for all i.\n  optional FillerParameter filler = 1;\n  // Whether or not slope paramters are shared across channels.\n  optional bool channel_shared = 2 [default = false];\n}\n"
  },
  {
    "path": "caffe2/.clang-format",
    "content": "---\nAccessModifierOffset: -1\nAlignAfterOpenBracket: AlwaysBreak\nAlignConsecutiveAssignments: false\nAlignConsecutiveDeclarations: false\nAlignEscapedNewlinesLeft: true\nAlignOperands:   false\nAlignTrailingComments: false\nAllowAllParametersOfDeclarationOnNextLine: false\nAllowShortBlocksOnASingleLine: false\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: Empty\nAllowShortIfStatementsOnASingleLine: false\nAllowShortLoopsOnASingleLine: false\nAlwaysBreakAfterReturnType: None\nAlwaysBreakBeforeMultilineStrings: true\nAlwaysBreakTemplateDeclarations: true\nBinPackArguments: false\nBinPackParameters: false\nBraceWrapping:\n  AfterClass:      false\n  AfterControlStatement: false\n  AfterEnum:       false\n  AfterFunction:   false\n  AfterNamespace:  false\n  AfterObjCDeclaration: false\n  AfterStruct:     false\n  AfterUnion:      false\n  BeforeCatch:     false\n  BeforeElse:      false\n  IndentBraces:    false\nBreakBeforeBinaryOperators: None\nBreakBeforeBraces: Attach\nBreakBeforeTernaryOperators: true\nBreakConstructorInitializersBeforeComma: false\nBreakAfterJavaFieldAnnotations: false\nBreakStringLiterals: false\nColumnLimit:     80\nCommentPragmas:  '^ IWYU pragma:'\nConstructorInitializerAllOnOneLineOrOnePerLine: true\nConstructorInitializerIndentWidth: 4\nContinuationIndentWidth: 4\nCpp11BracedListStyle: true\nDerivePointerAlignment: false\nDisableFormat:   false\nForEachMacros:   [ FOR_EACH_RANGE, FOR_EACH, ]\nIncludeCategories:\n  - Regex:           '^<.*\\.h(pp)?>'\n    Priority:        1\n  - Regex:           '^<.*'\n    Priority:        2\n  - Regex:           '.*'\n    Priority:        3\nIndentCaseLabels: true\nIndentWidth:     2\nIndentWrappedFunctionNames: false\nKeepEmptyLinesAtTheStartOfBlocks: false\nMacroBlockBegin: ''\nMacroBlockEnd:   ''\nMaxEmptyLinesToKeep: 1\nNamespaceIndentation: None\nObjCBlockIndentWidth: 2\nObjCSpaceAfterProperty: false\nObjCSpaceBeforeProtocolList: false\nPenaltyBreakBeforeFirstCallParameter: 1\nPenaltyBreakComment: 300\nPenaltyBreakFirstLessLess: 120\nPenaltyBreakString: 1000\nPenaltyExcessCharacter: 1000000\nPenaltyReturnTypeOnItsOwnLine: 200\nPointerAlignment: Left\nReflowComments:  true\nSortIncludes:    true\nSpaceAfterCStyleCast: false\nSpaceBeforeAssignmentOperators: true\nSpaceBeforeParens: ControlStatements\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 1\nSpacesInAngles:  false\nSpacesInContainerLiterals: true\nSpacesInCStyleCastParentheses: false\nSpacesInParentheses: false\nSpacesInSquareBrackets: false\nStandard:        Cpp11\nTabWidth:        8\nUseTab:          Never\n...\n"
  },
  {
    "path": "caffe2/CMakeLists.txt",
    "content": "# ---[ Declare source file lists\n\n# ---[ Add respective subdirectories\n# Note: the folders that are being commented out have not been properly\n# addressed yet.\n\nadd_subdirectory(proto)\n\nadd_subdirectory(contrib)\nadd_subdirectory(core)\nadd_subdirectory(cuda_rtc)\nadd_subdirectory(db)\nadd_subdirectory(distributed)\n# add_subdirectory(experiments) # note, we may remove this folder at some point\nadd_subdirectory(image)\nadd_subdirectory(video)\nadd_subdirectory(mkl)\nadd_subdirectory(mobile)\nadd_subdirectory(mpi)\nadd_subdirectory(observers)\nadd_subdirectory(onnx)\nadd_subdirectory(operators)\nadd_subdirectory(perfkernels)\nadd_subdirectory(python)\nadd_subdirectory(queue)\nadd_subdirectory(sgd)\nadd_subdirectory(share)\n# add_subdirectory(test) # todo: use caffe2_gtest_main instead of gtest_main because we will need to call GlobalInit\nadd_subdirectory(transforms)\nadd_subdirectory(utils)\n\n# Advanced: if we have white list specified, we will do intersections for all\n# main lib srcs.\nif (CAFFE2_WHITELISTED_FILES)\n  caffe2_do_whitelist(Caffe2_CPU_SRCS CAFFE2_WHITELISTED_FILES)\n  caffe2_do_whitelist(Caffe2_GPU_SRCS CAFFE2_WHITELISTED_FILES)\nendif()\n\n# Debug messages - if you want to get a list of source files, enable the\n# following.\nif (FALSE)\n  message(STATUS \"CPU sources: \")\n  foreach(tmp ${Caffe2_CPU_SRCS})\n    message(STATUS \"  \" ${tmp})\n  endforeach()\n\n  message(STATUS \"GPU sources: \")\n  foreach(tmp ${Caffe2_GPU_SRCS})\n    message(STATUS \"  \" ${tmp})\n  endforeach()\n\n  message(STATUS \"CPU test sources: \")\n  foreach(tmp ${Caffe2_CPU_TEST_SRCS})\n    message(STATUS \"  \" ${tmp})\n  endforeach()\n\n  message(STATUS \"GPU test sources: \")\n  foreach(tmp ${Caffe2_GPU_TEST_SRCS})\n    message(STATUS \"  \" ${tmp})\n  endforeach()\nendif()\n\n# ---[ Generate and install header files.\n\n# Write the macros file.\nconfigure_file(\n    ${PROJECT_SOURCE_DIR}/caffe2/core/macros.h.in\n    ${PROJECT_BINARY_DIR}/caffe2/core/macros.h)\n\n# Installing the header files\ninstall(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}\n        DESTINATION include\n        FILES_MATCHING PATTERN \"*.h\")\ninstall(FILES ${PROJECT_BINARY_DIR}/caffe2/core/macros.h\n        DESTINATION include/caffe2/core)\n\n\n# ---[ List of libraries to link with\n\n# Compile exposed libraries.\nadd_library(caffe2 ${Caffe2_CPU_SRCS} $<TARGET_OBJECTS:Caffe_PROTO> $<TARGET_OBJECTS:Caffe2_PROTO>)\nadd_dependencies(caffe2 Caffe_PROTO Caffe2_PROTO)\ntarget_link_libraries(caffe2 PUBLIC ${Caffe2_PUBLIC_DEPENDENCY_LIBS})\ntarget_link_libraries(caffe2 PRIVATE ${Caffe2_DEPENDENCY_LIBS})\ntarget_link_libraries(caffe2 PRIVATE ${Caffe2_DEPENDENCY_WHOLE_LINK_LIBS})\ntarget_include_directories(caffe2 INTERFACE $<INSTALL_INTERFACE:include>)\ntarget_compile_options(caffe2 INTERFACE \"-std=c++11\")\ntarget_compile_options(caffe2 PRIVATE \"-DCAFFE2_BUILD_MAIN_LIB\")\ninstall(TARGETS caffe2 EXPORT Caffe2Targets DESTINATION lib)\ncaffe2_interface_library(caffe2 caffe2_library)\nlist(APPEND Caffe2_MAIN_LIBS caffe2_library)\n\n# ---[ CUDA library.\nif(USE_CUDA)\n  # A hack to deal with cuda library dependencies and modern CMake: the\n  # CUDA_ADD_LIBRARY includes a target_link_libraries, and as a result,\n  # one cannot use PUBLIC/PRIVATE/INTERFACE for the target anymore. This\n  # hack adds the PRIVATE keywords to CUDA_LIBRARIES so we can deal with\n  # it. We will then manually add the cudart library as interface libs.\n  set(__tmp ${CUDA_LIBRARIES})\n  set(CUDA_LIBRARIES PRIVATE ${CUDA_LIBRARIES})\n  CUDA_ADD_LIBRARY(caffe2_gpu ${Caffe2_GPU_SRCS})\n  set(CUDA_LIBRARIES ${__tmp})\n  target_link_libraries(caffe2_gpu INTERFACE caffe2::cudart)\n\n  target_include_directories(\n      caffe2_gpu INTERFACE $<INSTALL_INTERFACE:include>)\n  target_link_libraries(\n      caffe2_gpu PUBLIC caffe2 ${Caffe2_PUBLIC_CUDA_DEPENDENCY_LIBS})\n  target_link_libraries(\n      caffe2_gpu PRIVATE ${Caffe2_CUDA_DEPENDENCY_LIBS})\n  caffe2_interface_library(caffe2_gpu caffe2_gpu_library)\n  list(APPEND Caffe2_MAIN_LIBS caffe2_gpu_library)\n  install(TARGETS caffe2_gpu EXPORT Caffe2Targets DESTINATION lib)\nendif()\n\n# ---[ Test binaries.\nif (BUILD_TEST)\n  set(Caffe2_ALL_TEST_SRCS ${Caffe2_CPU_TEST_SRCS})\n  if (USE_CUDA)\n    list(APPEND Caffe2_ALL_TEST_SRCS ${Caffe2_GPU_TEST_SRCS})\n  endif()\n\n  foreach(test_src ${Caffe2_ALL_TEST_SRCS})\n    get_filename_component(test_name ${test_src} NAME_WE)\n    add_executable(${test_name} \"${test_src}\")\n    # For tests, some of the test code actually directly call the dependent\n    # libraries even if they are not part of the public dependency libs. As a\n    # result, we will explicitly link the test against the Caffe2 dependency\n    # libs.\n    target_link_libraries(${test_name} ${Caffe2_MAIN_LIBS} gtest_main)\n    if (USE_CUDA)\n      target_link_libraries(${test_name} ${Caffe2_CUDA_DEPENDENCY_LIBS})\n    endif()\n    if (${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} GREATER 3.0)\n      target_compile_features(${test_name} PRIVATE cxx_range_for)\n    endif()\n    add_test(NAME ${test_name} COMMAND $<TARGET_FILE:${test_name}>)\n    install(TARGETS ${test_name} DESTINATION test)\n  endforeach()\nendif()\n\n\nif (BUILD_PYTHON)\n  # Python site-packages\n  # Get canonical directory for python site packages (relative to install\n  # location).  It varys from system to system.\n  pycmd(PYTHON_SITE_PACKAGES \"\n      from distutils import sysconfig\n      print(sysconfig.get_python_lib(prefix=''))\n  \")\n  # ---[ Options.\n  SET(PYTHON_LIB_REL_PATH \"${PYTHON_SITE_PACKAGES}\" CACHE STRING \"Python installation path (relative to CMake installation prefix)\")\n  message(STATUS \"Using ${PYTHON_LIB_REL_PATH} as python relative installation path\")\n  # Python extension suffix\n  # Try to get from python through sysconfig.get_env_var('EXT_SUFFIX') first,\n  # fallback to \".pyd\" if windows and \".so\" for all others.\n  pycmd(PY_EXT_SUFFIX \"\n      from distutils import sysconfig\n      ext_suffix = sysconfig.get_config_var('EXT_SUFFIX')\n      print(ext_suffix if ext_suffix else '')\n  \")\n  if(\"${PY_EXT_SUFFIX}\" STREQUAL \"\")\n    if (MSVC)\n      set(PY_EXT_SUFFIX \".pyd\")\n    else()\n      set(PY_EXT_SUFFIX \".so\")\n    endif()\n  endif()\n\n  # ---[ Python.\n  add_library(caffe2_pybind11_state MODULE ${Caffe2_CPU_PYTHON_SRCS})\n  set_target_properties(caffe2_pybind11_state PROPERTIES COMPILE_FLAGS \"-fvisibility=hidden\")\n  set_target_properties(caffe2_pybind11_state PROPERTIES PREFIX \"\")\n  set_target_properties(caffe2_pybind11_state PROPERTIES SUFFIX ${PY_EXT_SUFFIX})\n  if (APPLE)\n    set_target_properties(caffe2_pybind11_state PROPERTIES LINK_FLAGS \"-undefined dynamic_lookup\")\n  endif()\n  set_target_properties(\n      caffe2_pybind11_state PROPERTIES LIBRARY_OUTPUT_DIRECTORY\n      ${CMAKE_BINARY_DIR}/caffe2/python)\n  target_link_libraries(\n      caffe2_pybind11_state caffe2_library)\n  install(TARGETS caffe2_pybind11_state DESTINATION \"${PYTHON_LIB_REL_PATH}/caffe2/python\")\n\n  if(USE_CUDA)\n    add_library(caffe2_pybind11_state_gpu MODULE ${Caffe2_GPU_PYTHON_SRCS})\n    set_target_properties(caffe2_pybind11_state_gpu PROPERTIES COMPILE_FLAGS \"-fvisibility=hidden\")\n    set_target_properties(caffe2_pybind11_state_gpu PROPERTIES PREFIX \"\")\n    set_target_properties(caffe2_pybind11_state_gpu PROPERTIES SUFFIX ${PY_EXT_SUFFIX})\n    if (APPLE)\n      set_target_properties(caffe2_pybind11_state_gpu PROPERTIES LINK_FLAGS \"-undefined dynamic_lookup\")\n    endif()\n    set_target_properties(\n        caffe2_pybind11_state_gpu PROPERTIES LIBRARY_OUTPUT_DIRECTORY\n        ${CMAKE_BINARY_DIR}/caffe2/python)\n    target_link_libraries(\n        caffe2_pybind11_state_gpu caffe2_library caffe2_gpu_library)\n    install(TARGETS caffe2_pybind11_state_gpu DESTINATION \"${PYTHON_LIB_REL_PATH}/caffe2/python\")\n  endif()\n\n  if (MSVC AND CMAKE_GENERATOR MATCHES \"Visual Studio\")\n    # If we are building under windows, we will copy the file from\n    # build/caffe2/python/{Debug,Release}/caffe2_pybind11_state.pyd\n    # to its parent folder so that we can do in-build execution.\n    add_custom_target(windows_python_copy_lib ALL)\n    add_dependencies(windows_python_copy_lib caffe2_pybind11_state)\n    add_custom_command(\n        TARGET windows_python_copy_lib POST_BUILD\n        COMMAND ${CMAKE_COMMAND} -E copy\n        $<TARGET_FILE:caffe2_pybind11_state>\n        ${CMAKE_BINARY_DIR}/caffe2/python)\n    if (USE_CUDA)\n      add_dependencies(windows_python_copy_lib caffe2_pybind11_state_gpu)\n      add_custom_command(\n          TARGET windows_python_copy_lib POST_BUILD\n          COMMAND ${CMAKE_COMMAND} -E copy\n          $<TARGET_FILE:caffe2_pybind11_state_gpu>\n          ${CMAKE_BINARY_DIR}/caffe2/python)\n    endif()\n  endif()\n\n  # Finally, Copy all python files to build directory\n  # Generate and create all needed __init__.py files, if they aren't already\n  # present in the current source tree.\n  message(STATUS \"Automatically generating missing __init__.py files.\")\n  caffe_autogen_init_py_files()\n\n  # Create a custom target that copies all python files.\n  file(GLOB_RECURSE PYTHON_SRCS RELATIVE ${PROJECT_SOURCE_DIR}\n       \"${PROJECT_SOURCE_DIR}/caffe2/*.py\")\n  add_custom_target(python_copy_files ALL)\n  if(MSVC OR CMAKE_GENERATOR MATCHES \"Ninja\")\n    # ninja fails when the command line is too long so we split\n    # the target into several. This would be beneficial for VS also\n    # since it build targets in parallel but not custom commands\n    foreach(python_src ${PYTHON_SRCS})\n      get_filename_component(dir ${python_src} DIRECTORY)\n      string(SHA1 name_hash \"${python_src}\")\n      # get_filename_component(name_we ${python_src} NAME_WE)\n      add_custom_target(python_copy_files_${name_hash}\n          COMMAND ${CMAKE_COMMAND} -E copy\n          ${PROJECT_SOURCE_DIR}/${python_src} ${CMAKE_BINARY_DIR}/${dir})\n      add_dependencies(python_copy_files python_copy_files_${name_hash})\n    endforeach()\n  else()\n    foreach(python_src ${PYTHON_SRCS})\n      get_filename_component(dir ${python_src} DIRECTORY)\n      add_custom_command(\n          TARGET python_copy_files PRE_BUILD\n          COMMAND ${CMAKE_COMMAND} -E copy\n          ${PROJECT_SOURCE_DIR}/${python_src} ${CMAKE_BINARY_DIR}/${dir})\n    endforeach()\n  endif()\n\n  # Install commands\n  # Pick up static python files\n  install(DIRECTORY ${CMAKE_BINARY_DIR}/caffe2 DESTINATION ${PYTHON_LIB_REL_PATH}\n          FILES_MATCHING PATTERN \"*.py\")\n  # Caffe proto files\n  install(DIRECTORY ${CMAKE_BINARY_DIR}/caffe DESTINATION ${PYTHON_LIB_REL_PATH}\n          FILES_MATCHING PATTERN \"*.py\")\n  # Caffe2 proto files\n  install(DIRECTORY ${CMAKE_BINARY_DIR}/caffe2 DESTINATION ${PYTHON_LIB_REL_PATH}\n          FILES_MATCHING PATTERN \"*.py\")\nendif()\n\n# Finally, set the Caffe2_MAIN_LIBS variable in the parent scope.\nset(Caffe2_MAIN_LIBS ${Caffe2_MAIN_LIBS} PARENT_SCOPE)\n\n\n"
  },
  {
    "path": "caffe2/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/contrib/CMakeLists.txt",
    "content": "add_subdirectory(aten)\nadd_subdirectory(gloo)\nadd_subdirectory(nccl)\nadd_subdirectory(prof)\nadd_subdirectory(shm_mutex)\nadd_subdirectory(script)\n# Finally pass the src lists back to the parent\n\n# CPU source, test sources, binary sources\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_BINARY_SRCS ${Caffe2_CPU_BINARY_SRCS} PARENT_SCOPE)\n\n# GPU source, test sources, binary sources\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_BINARY_SRCS ${Caffe2_GPU_BINARY_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/contrib/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/contrib/aten/CMakeLists.txt",
    "content": "if(USE_ATEN)\n  if(NOT USE_CUDA)\n    set(NO_CUDA ON)\n  endif()\n  set(TORCH_CUDA_ARCH_LIST \"3.5 5.2 6.0 6.1+PTX\")\n  set(TORCH_NVCC_FLAGS \"-Xfatbin -compress-all\")\n  set(CMAKE_POSITION_INDEPENDENT_CODE ON)\n  set(AT_LINK_STYLE STATIC)\n  set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fvisibility=hidden\")\n  set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fvisibility=hidden\")\n  add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/aten aten EXCLUDE_FROM_ALL)\n\n  add_custom_command(OUTPUT aten_op.h\n  COMMAND\n  python ${CMAKE_CURRENT_SOURCE_DIR}/gen_op.py\n  --third_party_root=${PROJECT_SOURCE_DIR}/third_party\n  --template_dir=${PROJECT_SOURCE_DIR}/caffe2/contrib/aten\n  DEPENDS\n  ATen\n  ${CMAKE_CURRENT_SOURCE_DIR}/gen_op.py\n  ${CMAKE_CURRENT_SOURCE_DIR}/aten_op_template.h)\n\n  add_custom_target(__aten_op_header_gen DEPENDS aten_op.h)\n  add_library(aten_op_header_gen INTERFACE)\n  add_dependencies(aten_op_header_gen __aten_op_header_gen)\n\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} \"${CMAKE_CURRENT_SOURCE_DIR}/aten_op.cc\" PARENT_SCOPE)\n  set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} \"${CMAKE_CURRENT_SOURCE_DIR}/aten_op_cuda.cc\" PARENT_SCOPE)\nendif()\n"
  },
  {
    "path": "caffe2/contrib/aten/README.md",
    "content": "# An ATen operator for Caffe2\n\n[ATen](https://github.com/zdevito/aten) is a simple tensor library thats exposes the Tensor operations in Torch\nand PyTorch directly in C++11. This library provides a generated wrapper around the ATen API\nthat makes these functions available in Caffe2 as an operator. It also makes it accessible using the\nToffeeIR.\n\n\n### Example Usage in Caffe2\n\nFirst identify a function in ATen you want to call in [Functions.h](https://github.com/zdevito/ATen/blob/master/doc/Functions.h),\n[Tensor.h](https://github.com/zdevito/ATen/blob/master/doc/Tensor.h), or [Type.h](https://github.com/zdevito/ATen/blob/master/doc/Type.h).\n\nWe will call the `pow` operator:\n\n```\nstatic inline Tensor pow(const Tensor & self, Scalar exponent);\n```\n\nNow create a Caffe2 operator to call this op. The name of the operator is always `\"ATen\"`,\nand there is always a string attribute `operator` that defines which ATen function to call:\n\n\n```\nimport numpy as np\nfrom caffe2.python import core, workspace\n\n\n# create the Caffe2 Op:\nop = core.CreateOperator(\n    \"ATen\",\n    [\"MyInput\"],\n    [\"MyOutput\"],\n    operator=\"pow\", exponent=2.0)\n\n```\n\nEach `Tensor` input becomes an Caffe2 input Blob, and each output becomes a Caffe2 output blob.\nNon-tensor inputs such as `Scalar exponent` become Caffe2 `arg` attributes.\nIn the case of `Scalar` the attributes can be either an integers or floating point numbers.\n\nThe op can now be run like any other Caffe2 operator:\n\n```\nworkspace.FeedBlob(\"MyInput\",np.random.randn(2,3).astype(np.float32))\nworkspace.RunOperatorOnce(op)\nprint(workspace.FetchBlob(\"MyOutput\")\n```\n\nFor methods, the first input is always the `this` Tensor in C++.\nTo call methods of ATen's `Type` objects, you provide an additional string attribute\nthat determines the type:\n\n```\n# create a 2x4 tensor filled with floating point ones\nop = core.CreateOperator(\n    \"ATen\",\n    [],\n    [\"MyOutput\"],\n    operator=\"ones\", type=\"Float\", size={2,4})\n```\n\nGenerally ATen operators are polymorphic across input types, and work on both the CPU and CUDA.\n\n### Example Usage via PyTorch Symbolic\n\nThe ATen operator can also be used to define `symbolic` definitions for PyTorch when an operator is being exported\nto ONNX. In this case, the definition of the operator looks the same but is defined using PyTorch's ONNX API:\n\n```\nclass Add(torch.autograd.Function):\n\n    @staticmethod\n    def symbolic(g, a, b):\n        return g.op(\"ATen\", a, b, operator_s = \"add\")\n\n    @staticmethod\n    def forward(ctx, a, b):\n        return a + b\n```\n"
  },
  {
    "path": "caffe2/contrib/aten/aten_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/contrib/aten/aten_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(ATen, ATenOp<CPUContext>);\ntemplate<>\nat::Backend ATenOp<CPUContext>::backend() const {\n  return at::kCPU;\n}\n\nOPERATOR_SCHEMA(ATen);\nCAFFE_KNOWN_TYPE(at::Half);\n\nnamespace math {\ntemplate<>\nvoid Set<at::Half,CPUContext>(const size_t N, const at::Half h, at::Half* v, CPUContext * c) {\n  Set(0, h.x, (uint16_t*) v, c);\n}\n}\n\n}\n"
  },
  {
    "path": "caffe2/contrib/aten/aten_op.h",
    "content": "#include \"caffe2/caffe2/contrib/aten/gen_aten_op.h\"\n"
  },
  {
    "path": "caffe2/contrib/aten/aten_op_cuda.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/contrib/aten/aten_op.h\"\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(ATen, ATenOp<CUDAContext>);\ntemplate<>\nat::Backend ATenOp<CUDAContext>::backend() const {\n  return at::kCUDA;\n}\n\nnamespace math {\ntemplate<>\nvoid Set<at::Half,CUDAContext>(const size_t N, const at::Half h, at::Half* v, CUDAContext * c) {\n  Set(0, h.x, (uint16_t*) v, c);\n}\n}\n\n}\n"
  },
  {
    "path": "caffe2/contrib/aten/aten_op_template.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n#include <unordered_map>\n#include <string>\n#include <ATen/ATen.h>\n#include <caffe2/core/context.h>\n#include <caffe2/core/operator.h>\n#include <caffe2/utils/math.h>\n#include <google/protobuf/text_format.h>\n#include <iostream>\n\n// a map from descriptor strings (see [DESCRIPTORS])\n// to the key in the switch statement that implements them\nstatic std::unordered_map<std::string, int> op_to_key = {\n  ${mappings}\n};\n\nnamespace caffe2 {\n\nusing at::Half; // for AT_FORALL_SCALAR_TYPES\n\ntemplate <class Context>\nclass ATenOp : public Operator<Context> {\n public:\n  ATenOp(const OperatorDef& operator_def, Workspace* ws)\n  : Operator<Context>(operator_def, ws) {\n    VLOG(2) << \"ATen OpDef: \" << ProtoDebugString(operator_def) << \"\\n\";\n    switch(findImplementation(operator_def)) {\n      ${implementations}\n      default:\n        CAFFE_THROW(\"Unexpected key value for aten operator\");\n    }\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    return run_op();\n  }\nprivate:\n  // actual operator implementation is initialized in ctor.\n  std::function<bool()> run_op;\n  at::Backend backend() const;\n\n  TypeMeta typeMetaFor(const at::Tensor & t) {\n    return typeMetaFor(t.type().scalarType());\n  }\n  TypeMeta typeMetaFor(at::ScalarType st) {\n    #define DEFINE_CASE(ctype,aten_name,_) \\\n      case at::k##aten_name: \\\n        return TypeMeta::Make<ctype>();\n    switch(st) {\n      AT_FORALL_SCALAR_TYPES(DEFINE_CASE)\n      default:\n        CAFFE_THROW(\"Unknown ATen Type\");\n    }\n    #undef DEFINE_CASE\n  }\n\n  at::Type & typeFor(const Tensor<Context> & ten) {\n    return at::getType(backend(), atScalarTypeFor(ten.meta()));\n  }\n  at::Tensor tensorWrapping(const Tensor<Context>& ten_) {\n    auto& ten = const_cast<Tensor<Context>&>(ten_);\n    return typeFor(ten).tensorFromBlob(ten.raw_mutable_data(), ten.dims());\n  }\n  at::Tensor loadInput(size_t i) {\n    return tensorWrapping(Input(i));\n  }\n  std::vector<at::Tensor> loadInputsAtOffset(size_t s) {\n    std::vector<at::Tensor> results;\n    for (size_t i = s; i < InputSize(); i++) {\n      results.push_back(loadInput(i));\n    }\n    return results;\n  }\n  at::ScalarType atScalarTypeFor(const TypeMeta & meta) {\n    #define DEFINE_IF(ctype,aten_name,_) \\\n    if(meta.Match<ctype>()) { \\\n      return at::k##aten_name; \\\n    }\n    AT_FORALL_SCALAR_TYPES(DEFINE_IF)\n    #undef DEFINE_IF\n    CAFFE_THROW(\"Unknown type meta\"); // TODO: improve error message...\n  }\n  void assignTo(Tensor<Context> * dst, const at::Tensor & src_) {\n    at::Tensor src = src_.contiguous();\n    auto at_sizes = src.sizes();\n    std::vector<int64_t> dims(at_sizes.begin(),at_sizes.end());\n    dst->Resize(dims);\n    dst->ShareExternalPointer(\n        src.data_ptr(), typeMetaFor(src), 0, [src](void* ptr) mutable {\n          // return a closure that holds a handle to t until it is called\n          // to keep the aten memory alive\n          return src.reset();\n        });\n  }\n  void assignListStartingAt(\n      size_t offset,\n      const std::vector<at::Tensor>& tensors) {\n    for (size_t i = 0; i < tensors.size(); i++) {\n      assignTo(Output(offset + i), tensors[i]);\n    }\n  }\n\n  // the AT_FORALL_SCALAR_TYPES macro just gives a 'i' or 'd' argument\n  // for each type to specify if it is stored as a integer or a double.\n  // We need this workaround here to extract the value in the scalar losslessly\n  // because in some cases like 'sum' Torch promotes float to double\n  // and will complain if we downcast it with toFloat, causing it\n  // to lose precision\n  double extract_d(const at::Scalar & s) {\n    return s.toDouble();\n  }\n  int64_t extract_i(const at::Scalar & s) {\n    return s.toLong();\n  }\n\n  void assignTo(Tensor<Context> * dst, at::Type & inferred_type, at::Scalar scalar) {\n    switch(inferred_type.scalarType()) {\n      #define DEFINE_CASE(ctype,aten_name,native) \\\n        case at::k##aten_name: { \\\n          auto value = extract_##native(scalar); \\\n          assignToValue<ctype>(dst, at::convert<ctype,decltype(value)>(value)); \\\n        } break;\n      AT_FORALL_SCALAR_TYPES(DEFINE_CASE)\n      #undef DEFINE_CASE\n      default:\n        CAFFE_THROW(\"Unknown ATen Type\");\n    }\n  }\n  template<typename T>\n  void assignToValue(Tensor<Context> * dst, T v) {\n    dst->Resize(std::vector<TIndex>());\n    math::Set(1, v, dst->template mutable_data<T>(), &context_);\n  }\n  int findImplementation(const OperatorDef& operator_def) {\n    CAFFE_ENFORCE(HasArgument(\"operator\"));\n    std::string op = OperatorBase::GetSingleArgument<std::string>(\"operator\", \"\");\n    // construct descriptor string ([DESCRIPTORS]) given the attributes\n    // and inputs of this operator_def, and look up the implementation key\n    // for this variant\n    std::stringstream descriptor;\n    descriptor << op;\n    std::vector<std::string> attrs;\n    for(size_t i = 0; i < operator_def.arg_size(); i++) {\n      auto & attr = operator_def.arg(i);\n      if(attr.name() == \"operator\" || attr.name() == \"type\" )\n        continue;\n      attrs.push_back(attr.name());\n    }\n    std::sort(attrs.begin(), attrs.end());\n    for(auto & a : attrs)\n      descriptor << \"-\" << a;\n\n    std::string descriptor_sized =\n        descriptor.str() + \"-\" + caffe2::to_string(InputSize());\n    std::string descriptor_var_args = descriptor.str() + \"-*\";\n    if (op_to_key.count(descriptor_sized) > 0) {\n      return op_to_key[descriptor_sized];\n    }\n    if (op_to_key.count(descriptor_var_args) > 0) {\n      return op_to_key[descriptor_var_args];\n    }\n    std::stringstream ss;\n    ss << \"Attempting to run unknown ATen operator configuration: \"\n       << descriptor_sized;\n    CAFFE_THROW(ss.str());\n  }\n  at::Scalar readScalarAttribute(const std::string & name) {\n    if(OperatorBase::HasSingleArgumentOfType<int64_t>(name)) {\n      return OperatorBase::GetSingleArgument<int64_t>(name, 0);\n    } else {\n      CAFFE_ENFORCE(OperatorBase::HasSingleArgumentOfType<float>(name));\n      return OperatorBase::GetSingleArgument<float>(name, 0);\n    }\n  }\n  template<typename T>\n  T readAttribute(const std::string & name) {\n    CAFFE_ENFORCE(OperatorBase::HasSingleArgumentOfType<T>(name));\n    return OperatorBase::GetSingleArgument<T>(name, 0);\n  }\n  std::vector<int64_t> readIntList(const std::string & name) {\n    CAFFE_ENFORCE(OperatorBase::HasArgument(name));\n    return OperatorBase::GetRepeatedArgument<int64_t>(name, {});\n  }\n  template <int N>\n  std::array<bool, N> readBoolMask(const std::string& name) {\n    CAFFE_ENFORCE(OperatorBase::HasArgument(name));\n    std::vector<int64_t> ints =\n        OperatorBase::GetRepeatedArgument<int64_t>(name, {});\n    std::array<bool, N> result;\n    for (size_t i = 0; i < N; ++i) {\n      result[i] = ints.at(i);\n    }\n    return result;\n  }\n  at::ScalarType stringToScalarType(const std::string & name) {\n    #define DEFINE_IF(type,aten) \\\n      if(#type == name) \\\n        return at::k##aten;\n    DEFINE_IF(float16, Half)\n    DEFINE_IF(float, Float)\n    DEFINE_IF(double, Double)\n    DEFINE_IF(uint8, Byte)\n    DEFINE_IF(int8, Char)\n    DEFINE_IF(int16, Short)\n    DEFINE_IF(int32, Int)\n    DEFINE_IF(int64, Long)\n    CAFFE_THROW(\"unsupported type annotation: \", name);\n  }\n  at::Type & stringToType(const std::string & name) {\n    return at::getType(backend(), stringToScalarType(name));\n  }\n  at::Type * readTypeAttribute(const std::string & name) {\n    CAFFE_ENFORCE(OperatorBase::HasSingleArgumentOfType<std::string>(name));\n    return &stringToType(OperatorBase::GetSingleArgument<std::string>(name, \"\"));\n  }\n};\n\n}\n"
  },
  {
    "path": "caffe2/contrib/aten/aten_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, dyndep\nfrom hypothesis import given\n\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\ndyndep.InitOpsLibrary('@/caffe2/caffe2/contrib/aten:aten_op')\n\n\nclass TestATen(hu.HypothesisTestCase):\n\n    @given(inputs=hu.tensors(n=2), **hu.gcs)\n    def test_add(self, inputs, gc, dc):\n        op = core.CreateOperator(\n             \"ATen\",\n             [\"X\", \"Y\"],\n             [\"Z\"],\n             operator=\"add\")\n\n        def ref(X, Y):\n            return [X + Y]\n        self.assertReferenceChecks(gc, op, inputs, ref)\n\n    @given(inputs=hu.tensors(n=1), **hu.gcs)\n    def test_pow(self, inputs, gc, dc):\n        op = core.CreateOperator(\n            \"ATen\",\n            [\"S\"],\n            [\"Z\"],\n            operator=\"pow\", exponent=2.0)\n\n        def ref(X):\n            return [np.square(X)]\n\n        self.assertReferenceChecks(gc, op, inputs, ref)\n\n    @given(x=st.integers(min_value=2, max_value=8), **hu.gcs)\n    def test_sort(self, x, gc, dc):\n        inputs = [np.random.permutation(x)]\n        op = core.CreateOperator(\n            \"ATen\",\n            [\"S\"],\n            [\"Z\", \"I\"],\n            operator=\"sort\")\n\n        def ref(X):\n            return [np.sort(X), np.argsort(X)]\n        self.assertReferenceChecks(gc, op, inputs, ref)\n\n    @given(inputs=hu.tensors(n=1), **hu.gcs)\n    def test_sum(self, inputs, gc, dc):\n        op = core.CreateOperator(\n            \"ATen\",\n            [\"S\"],\n            [\"Z\"],\n            operator=\"sum\")\n\n        def ref(X):\n            return [np.sum(X)]\n\n        self.assertReferenceChecks(gc, op, inputs, ref)\n\n    @given(**hu.gcs)\n    def test_ones(self, gc, dc):\n        op = core.CreateOperator(\n            \"ATen\",\n            [],\n            [\"Z\"],\n            operator=\"ones\", type=\"float\", size={2, 4})\n\n        def ref():\n            return [np.ones([2, 4])]\n\n        self.assertReferenceChecks(gc, op, [], ref)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/contrib/aten/docs/pytorch_to_caffe2.md",
    "content": "# Using ONNX and ATen to export models from PyTorch to Caffe2\n\nWhen using ONNX to export a model from PyTorch into Caffe2, you sometimes end up\nhitting operators that are not yet part of the ONNX specification. These may be\noperators that haven't been standardized yet, or custom `torch.autograd.Function` types that\nare specific to a network.\n\nTo bridge this gap, we provide an experimental operator in ONNX that allows you to directly access PyTorch's tensor functions using the ATen library.\n[ATen](https://github.com/zdevito/aten) is the underlying C++ library that PyTorch uses to do tensor operations. Caffe2 has an [ATen operator](https://github.com/caffe2/caffe2/tree/master/caffe2/contrib/aten)\nthat can run these tensor functions in a Caffe2 network after importing them through ONNX.\n\nThis guide explains how to configure Caffe2 and modify your PyTorch program to use\nthis functionality.\n\n### Enable ATen in Caffe2\n\nThe ATen facility in Caffe2 is part of a contrib package and needs to be enabled\nwhen you configure Caffe2 using cmake:\n\n```\ngit clone https://github.com/caffe2/caffe2/\nmkdir caffe2/build\ncd caffe2/build\ncmake -DUSE_ATEN=ON <other build options> ..\nmake install\n```\n\n### Describe How to Export a PyTorch Autograd Function using ATen\n\nTo export a model to ONNX, PyTorch first creates a trace of all the `torch.autograd.Function`s run\nin the forward pass of a network. For each function in the trace, it calls that function's\n`symbolic` method which describes how to construct the part of the ONNX graph\nthat will compute this function (see [basic_ops.py](https://github.com/pytorch/pytorch/blob/master/torch/autograd/_functions/basic_ops.py#L59) for examples).\n\nWhen equivalent ONNX operators do not exist, you can instead call any ATen function.\nAs an example let's assume we have an autograd function which computes `x*x+y`:\n\n```\n  class MyFunction(Function):\n    @staticmethod\n    def forward(ctx, x, y):\n      return x*x + y\n```\n\nWe can add a `symbolic` method to it like so:\n\n```\n  class MyFunction(Function):\n    @staticmethod\n    def forward(ctx, x, y):\n      return x*x + y\n    @staticmethod\n    def symbolic(graph, x, y):\n      x2 = graph.at(\"mul\", x, x)\n      r = graph.at(\"add\", x2, y)\n      # x, y, x2, and r are 'Node' objects\n      # print(r) or print(graph) will print out a textual representation for debugging.\n      # this representation will be converted to ONNX protobufs on export.\n      return r\n```\n\nThe function `graph.at` adds a new ATen op the computation graph.\nYou can call any ATen function using this facility. To do so,\nfirst identify a function in ATen you want to call in [Functions.h](https://github.com/zdevito/ATen/blob/master/doc/Functions.h),\n[Tensor.h](https://github.com/zdevito/ATen/blob/master/doc/Tensor.h), or [Type.h](https://github.com/zdevito/ATen/blob/master/doc/Type.h).\n\nAs an example, we might want to call the `pow` operator:\n\n```\nstatic inline Tensor pow(const Tensor & self, Scalar exponent);\n```\n\nWe can translate this into the equivalent `graph.at` function:\n\n```\n  def symbolic(graph, x):\n    graph.at(\"pow\", x, exponent_f = 2.0) # compute x**2\n```\n\nTensor arguments to ATen functions become arguments to `graph.at`, while a `Scalar`\nlike `exponent` becomes a keyword argument that specify ONNX attributes.\nAttributes are suffixed with their type (`_f` for floats and `_i` for integers, and `_s` for strings).\n\nFor methods, the first input is always the `this` Tensor in C++.\nTo call methods of ATen's `Type` objects, you provide an additional string attribute\nthat determines the type. For instance, `ones` creates a new constant tensor of all ones:\n```\nclass Type {\n\t...\n\tvirtual Tensor ones(IntList size) const;\n\t...\n};\n```\n\nFrom PyTorch it can be created by adding the type as an additional attribute:\n\n```\n  def symbolic(graph, x):\n    return graph.at(\"ones\", type_s=\"float\", size_i=[2,4])\n```\n\n\nGenerally ATen operators are polymorphic across input types, and work on both the CPU and CUDA.\n\n## Putting it together\n\nWith these building blocks we can now write and export networks that include custom operators using `torch.onnx.export`:\n\n```\nclass MyModule(nn.Module):\n    def forward(self, x, y):\n        # you can combine your ATen ops with standard onnx ones\n        x = nn.ReLU()(x)\n        return MyFunction.apply(x, y)\n\ntorch.onnx.export(MyModule(),\n                  (Variable(torch.ones(3,4)), Variable(torch.ones(3,4))),\n                  \"output.onnx\",\n                  verbose=True)\n```\n\nThis exports the following graph, which contains calls the `ATen` operator:\n\n```\ngraph(%1 : Float(3, 4)\n       %2 : Float(3, 4)) {\n   %3 : Float(3, 4) = Relu(%1), uses = [%4.i0, %4.i1];\n   %4 : UNKNOWN_TYPE = ATen[operator=mul](%3, %3), uses = [%5.i0];\n   %5 : Float(3, 4) = ATen[operator=add](%4, %2), uses = [%0.i0];\n   return (%5);\n}\n```\n\nThe graph can then be imported using ONNX and run with Caffe2:\n\n```\nimport onnx\nimport caffe2.python.onnx.backend\nimport numpy as np\n\ngraph = onnx.load(\"output.onnx\")\n\na = np.random.randn(3, 2).astype(np.float32)\nb = np.random.randn(3, 2).astype(np.float32)\n\nprepared_backend = caffe2.python.onnx.backend.prepare(graph)\nW = {graph.graph.input[0].name: a, graph.graph.input[1].name: b}\nc2_out = prepared_backend.run(W)[0]\n\nx = np.maximum(a, 0)\nr = x*x + b\nnp.testing.assert_array_almost_equal(r, c2_out)\n```\n\n### Code\n\nFor the full source code for this tutorial, see [sample.py](sample.py).\n"
  },
  {
    "path": "caffe2/contrib/aten/docs/sample.py",
    "content": "import numpy as np\n\nfrom torch import nn\nfrom torch.autograd import Variable, Function\nimport torch.onnx\n\nimport onnx\nimport caffe2.python.onnx.backend\n\nclass MyFunction(Function):\n    @staticmethod\n    def forward(ctx, x, y):\n        return x*x + y\n    @staticmethod\n    def symbolic(graph, x, y):\n        x2 = graph.at(\"mul\", x, x)\n        r = graph.at(\"add\", x2, y)\n        # x, y, x2, and r are 'Node' objects\n        # print(r) or print(graph) will print out a textual representation for debugging.\n        # this representation will be converted to ONNX protobufs on export.\n        return r\n\nclass MyModule(nn.Module):\n    def forward(self, x, y):\n        # you can combine your ATen ops with standard onnx ones\n        x = nn.ReLU()(x)\n        return MyFunction.apply(x, y)\n\ntorch.onnx.export(MyModule(),\n                  (Variable(torch.ones(3,4)), Variable(torch.ones(3,4))),\n                  \"output.onnx\",\n                  verbose=True)\n\n# prints the graph for debugging:\n# graph(%1 : Float(3, 4)\n#       %2 : Float(3, 4)) {\n#   %3 : Float(3, 4) = Relu(%1), uses = [%4.i0, %4.i1];\n#   %4 : UNKNOWN_TYPE = ATen[operator=mul](%3, %3), uses = [%5.i0];\n#   %5 : Float(3, 4) = ATen[operator=add](%4, %2), uses = [%0.i0];\n#   return (%5);\n# }\n\ngraph = onnx.load(\"output.onnx\")\n\na = np.random.randn(3, 4).astype(np.float32)\nb = np.random.randn(3, 4).astype(np.float32)\n\nprepared_backend = caffe2.python.onnx.backend.prepare(graph)\nW = {graph.graph.input[0].name: a, graph.graph.input[1].name: b}\nc2_out = prepared_backend.run(W)[0]\n\nx = np.maximum(a, 0)\nr = x*x + b\nnp.testing.assert_array_almost_equal(r, c2_out)\n"
  },
  {
    "path": "caffe2/contrib/aten/gen_op.py",
    "content": "#!/bin/env python\n\n# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nimport sys\nimport yaml\nimport argparse\nimport os\nfrom copy import deepcopy\n\nparser = argparse.ArgumentParser()\nparser.add_argument(\"--template_dir\", default=\".\", help=\"where template.h is\")\nparser.add_argument(\"--yaml_dir\", default=\"aten/src/ATen/ATen\",\n                    help=\"where ATen yaml files are\")\nparser.add_argument(\"--output_prefix\", default=\"\", help=\"\")\nparser.add_argument(\n    \"--install_dir\", default=\".\", help=\"where to put generated file\")\nparser.add_argument(\"--third_party_root\", default=\"\", help=\"caffe2 third_party\")\nargs, _ = parser.parse_known_args()\n\nif args.third_party_root:\n    sys.path.append(os.path.join(args.third_party_root, \"aten/src/ATen\"))\n    from code_template import CodeTemplate as CT\nelse:\n    from src.ATen.code_template import CodeTemplate as CT\n\nOP_TEMPLATE = CT.from_file(\n    os.path.join(args.template_dir, 'aten_op_template.h'))\n\n\ntry:\n    # use faster C loader if available\n    from yaml import CLoader as Loader\nexcept ImportError:\n    from yaml import Loader\n\n\ndef write(filename, s):\n    with open(filename, \"w\") as f:\n        f.write(s)\n\n\ndef read(filename):\n    with open(filename, \"r\") as f:\n        return f.read()\n\n\ndef value_has_tensors(v):\n    # Sparse shouldn't appear in public API, seems to be temporary bug\n    return \"Tensor\" in v['dynamic_type'] and \"Sparse\" not in v['dynamic_type']\n\n\ndef value_is_tensor_type(v):\n    return value_has_tensors(v) and v['dynamic_type'] != 'TensorList'\n\n\n# for each aten type, how do we handle a return value of that type?\nRETURN_MAP = {\n    'Tensor': 'assignTo(Output(${offset}),${output});',\n    'Scalar': 'assignTo(Output(${offset}),*inferred_type, ${output});',\n    'bool': 'assignToValue<int64_t>(Output(${offset}),${output});',\n    'int64_t': 'assignToValue<int64_t>(Output(${offset}),${output});',\n    'std::vector<Tensor>': 'assignListStartingAt(${offset}, ${output});',\n}\n\n# for each non-Tensor aten argument, how to we read it from caffe2's\n# attribute list. Most of these call runtime functions defined in the\n# template class.\nARGUMENT_MAP = {\n    'Scalar': 'at::Scalar ${arg} = readScalarAttribute(\"${arg}\");',\n    'bool': 'bool ${arg} = readAttribute<int64_t>(\"${arg}\");',\n    'int': 'int ${arg} = readAttribute<int64_t>(\"${arg}\");',\n    'double': 'double ${arg} = readAttribute<float>(\"${arg}\");',\n    'int64_t': 'int64_t ${arg} = readAttribute<int64_t>(\"${arg}\");',\n    'IntList': 'auto ${arg} = readIntList(\"${arg}\");',\n    'std::array<bool, 2>': 'auto ${arg} = readBoolMask<2>(\"${arg}\");',\n    'std::array<bool, 3>': 'auto ${arg} = readBoolMask<3>(\"${arg}\");',\n}\n\n\ndef expand(o):\n    num_defaults = sum(1 if 'default' in arg else 0 for arg in o['arguments'])\n    results = [o]\n    for i in range(0, num_defaults):\n        # last num_default values should be default\n        assert('default' in o['arguments'][-(i + 1)])\n        v = deepcopy(o)\n        v['arguments'] = v['arguments'][:-(i + 1)]\n        results.append(v)\n    return results\n\n\n# filter the list of declarations removing things we cannot support\ndef supports(o):\n    # skip all in-place operators for now since aten cannot Resize\n    # caffe2 memory inside an operator\n    if o['inplace']:\n        return False\n\n    # _out variants also work in-place on arguments taken as destinations\n    # we also cannot handle these because aten cannot resize caffe2 Tensors\n    if \"_out\" in o['name']:\n        return False\n\n    # skip return types we cannot handle\n    for ret in o['returns']:\n        if not value_has_tensors(ret) and ret['type'] not in RETURN_MAP:\n            print(\"Skipping {} Because of Ret: {} ({})\".format(\n                  o['name'], ret['type'], ret['dynamic_type']))\n            return False\n\n    # skip arguments we cannot handle\n    for arg in o['arguments']:\n        if not value_has_tensors(arg) and arg['type'] not in ARGUMENT_MAP:\n            print(\"Skipping {} Because of Arg: {} ({}) \".format(\n                  o['name'], arg['type'], arg['dynamic_type']))\n            return False\n    return True\n\n\n# template for each potential operator.\n# each operator has an integer 'key' associated with it, and\n# a lambda that defines the operator\n# non-tensor attributes are created in ${initialization}\n# and then saved as arguments to the lambda\n# Inputs/Outputs are read inside the lambda\nOPTION_TEMPLATE = CT(\"\"\"\\\ncase ${key}: { // ${name}\n    ${initialization}\n    run_op = [=] {\n        ${statements}\n        auto the_result = ${invocation};\n        ${assignments}\n        return true;\n    };\n} break;\n\"\"\")\n\n\ndef get_output(o, i):\n    if len(o['returns']) == 1:\n        return 'the_result'\n    else:\n        return 'std::get<{}>(the_result)'.format(i)\n\n\ndef attribute_names(o):\n    return sorted([a['name'] for a in o['arguments'] if not value_has_tensors(a)])\n\n\ndef required_attribute_names(o):\n    return sorted([a['name'] for a in o['arguments'] if not value_has_tensors(a) and 'default' not in a])\n\n\ndef self_as_first_argument(arguments):\n    return ([a for a in arguments if a['name'] == 'self'] +\n            [a for a in arguments if a['name'] != 'self'])\n\n\ndef get_num_inputs(o):\n    args = 0\n    for a in o['arguments']:\n        if a['type'] == 'TensorList':\n            return '*'\n        elif value_has_tensors(a):\n            args += 1\n    return str(args)\n\n\nif __name__ == '__main__':\n    decls = yaml.load(read(os.path.join(args.yaml_dir, 'Declarations.yaml')), Loader=Loader)\n    filtered = [expanded for o in decls for expanded in expand(o) if supports(expanded)]\n    top_env = {\n        'mappings': [],\n        'implementations': [],\n    }\n    seen = set()\n    key = 0\n    for o in filtered:\n        # [DESCRIPTORS]\n        # each option is associated with a descriptor string that is used\n        # to figure out which version of an op is being used:\n        # The format is:\n        #     opname-num_inputs-attribute_1-attribute2\n        # Example:\n        #  lerp-2-weight\n        #  the operator lerp takes 2 arguments and has the attribute weight\n        attr_names = attribute_names(o)\n        num_inputs = get_num_inputs(o)\n        descriptor = '-'.join([o['name']] + attr_names + [num_inputs])\n        if descriptor in seen:\n            continue\n        seen.add(descriptor)\n\n        # map from descriptor string to the integer key in the switch statements\n        # that initializes the operators\n        top_env['mappings'].append('{{ \"{}\", {} }},'.format(descriptor, key))\n        env = {\n            'name': o['name'],\n            'statements': [],\n            'arguments': [],\n            'assignments': [],\n            'initialization': [],\n            'key': str(key),\n        }\n        defined_inferred_type = False\n\n        if 'Tensor' in o['method_of']:\n            # make sure 'self' is the first argument. currently Declarations.yaml\n            # does not always do this. Instead it keeps the argument list the same order\n            # as the Type method.\n            o['arguments'] = self_as_first_argument(o['arguments'])\n        elif 'namespace' not in o['method_of']:\n            # methods on type like 'ones' or 'zeros' always take a\n            # string attribute that is translated into the at::Type object\n            # e.g. \"Float\" is at::kFloat\n            assert('Type' in o['method_of'])\n            defined_inferred_type = True\n            env['initialization'].append(\n                'auto inferred_type = readTypeAttribute(\"type\");')\n\n        i = 0\n        for arg in o['arguments']:\n            env['arguments'].append(arg['name'])\n            if arg['type'] == 'TensorList':\n                env['statements'].append(\n                    'auto {} = loadInputsAtOffset({});'.format(arg['name'], i))\n            elif value_is_tensor_type(arg):\n                assert(i != '*')  # tensor list is not last argument\n                # load tensor inputs from Caffe2\n                env['statements'].append(\n                    \"auto {} = loadInput({});\".format(arg['name'], i))\n                i += 1\n                if arg['dynamic_type'] == 'Tensor' and not defined_inferred_type:\n                    # first tensor input is used to define the output type.\n                    defined_inferred_type = True\n                    env['statements'].append(\n                        'auto inferred_type = &({}.type());'.format(\n                            arg['name']))\n            else:\n                init = CT(ARGUMENT_MAP[arg['type']]).substitute(env, arg=arg['name'])\n                env['initialization'].append(init)\n\n        for i, r in enumerate(o['returns']):\n            t = RETURN_MAP[r['type'] if not value_is_tensor_type(r) else 'Tensor']\n            assignment = CT(t).substitute(env, offset=i, output=get_output(o, i))\n            env['assignments'].append(assignment)\n\n        if 'Tensor' in o['method_of']:\n            env['invocation'] = \"self.{}({})\".format(\n                o['name'], ', '.join(env['arguments'][1:]))\n        elif 'namespace' in o['method_of']:\n            env['invocation'] = CT(\"at::${name}(${arguments})\").substitute(env)\n        else:\n            assert('Type' in o['method_of'])\n            env['invocation'] = CT(\n                'inferred_type->${name}(${arguments})').substitute(env)\n\n        top_env['implementations'].append(OPTION_TEMPLATE.substitute(env))\n        key += 1\n    write(os.path.join(args.install_dir, args.output_prefix + \"aten_op.h\"), OP_TEMPLATE.substitute(top_env))\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/README.md",
    "content": "# cuda-convnet2\nAutomatically exported from code.google.com/p/cuda-convnet2\n\nYou can read the documentation in two ways:\n\n1. On this site: go to branches > wiki.\n2. On Google Code (for now?): https://code.google.com/p/cuda-convnet2/\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/build.sh",
    "content": "#!/bin/sh\n# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n###############################################################################\n\n# Fill in the below environment variables.\n#\n# If you're not sure what these paths should be, \n# you can use the find command to try to locate them.\n# For example, NUMPY_INCLUDE_PATH contains the file\n# arrayobject.h. So you can search for it like this:\n# \n# find /usr -name arrayobject.h\n# \n# (it'll almost certainly be under /usr)\n\n# CUDA toolkit installation directory.\nexport CUDA_INSTALL_PATH=/usr/local/cuda\n\n# Python include directory. This should contain the file Python.h, among others.\nexport PYTHON_INCLUDE_PATH=/usr/include/python2.7\n\n# Numpy include directory. This should contain the file arrayobject.h, among others.\nexport NUMPY_INCLUDE_PATH=/usr/lib/python2.7/dist-packages/numpy/core/include/numpy/\n\n# ATLAS library directory. This should contain the file libcblas.so, among others.\nexport ATLAS_LIB_PATH=/usr/lib/atlas-base\n\n# You don't have to change these:\nexport LD_LIBRARY_PATH=$CUDA_INSTALL_PATH/lib64:$LD_LIBRARY_PATH\nexport CUDA_SDK_PATH=$CUDA_INSTALL_PATH/samples\nexport PATH=$PATH:$CUDA_INSTALL_PATH/bin\n\ncd util && make numpy=1 -j $* && cd ..\ncd nvmatrix && make -j $* && cd ..\ncd cudaconv3 && make -j $* && cd ..\ncd cudaconvnet && make -j $* && cd ..\ncd make-data/pyext && make -j $* && cd ../..\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/convdata.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom python_util.data import *\nimport numpy.random as nr\nimport numpy as n\nimport random as r\nfrom time import time\nfrom threading import Thread\nfrom math import sqrt\nimport sys\n#from matplotlib import pylab as pl\nfrom PIL import Image\nfrom StringIO import StringIO\nfrom time import time\nimport itertools as it\n    \nclass JPEGBatchLoaderThread(Thread):\n    def __init__(self, dp, batch_num, label_offset, list_out):\n        Thread.__init__(self)\n        self.list_out = list_out\n        self.label_offset = label_offset\n        self.dp = dp\n        self.batch_num = batch_num\n        \n    @staticmethod\n    def load_jpeg_batch(rawdics, dp, label_offset):\n        if type(rawdics) != list:\n            rawdics = [rawdics]\n        nc_total = sum(len(r['data']) for r in rawdics)\n\n        jpeg_strs = list(it.chain.from_iterable(rd['data'] for rd in rawdics))\n        labels = list(it.chain.from_iterable(rd['labels'] for rd in rawdics))\n        \n        img_mat = n.empty((nc_total * dp.data_mult, dp.inner_pixels * dp.num_colors), dtype=n.float32)\n        lab_mat = n.zeros((nc_total, dp.get_num_classes()), dtype=n.float32)\n        dp.convnet.libmodel.decodeJpeg(jpeg_strs, img_mat, dp.img_size, dp.inner_size, dp.test, dp.multiview)\n        lab_vec = n.tile(n.asarray([(l[nr.randint(len(l))] if len(l) > 0 else -1) + label_offset for l in labels], dtype=n.single).reshape((nc_total, 1)), (dp.data_mult,1))\n        for c in xrange(nc_total):\n            lab_mat[c, [z + label_offset for z in labels[c]]] = 1\n        lab_mat = n.tile(lab_mat, (dp.data_mult, 1))\n        \n\n        return {'data': img_mat[:nc_total * dp.data_mult,:],\n                'labvec': lab_vec[:nc_total * dp.data_mult,:],\n                'labmat': lab_mat[:nc_total * dp.data_mult,:]}\n    \n    def run(self):\n        rawdics = self.dp.get_batch(self.batch_num)\n        p = JPEGBatchLoaderThread.load_jpeg_batch(rawdics,\n                                                  self.dp,\n                                                  self.label_offset)\n        self.list_out.append(p)\n        \nclass ColorNoiseMakerThread(Thread):\n    def __init__(self, pca_stdevs, pca_vecs, num_noise, list_out):\n        Thread.__init__(self)\n        self.pca_stdevs, self.pca_vecs = pca_stdevs, pca_vecs\n        self.num_noise = num_noise\n        self.list_out = list_out\n        \n    def run(self):\n        noise = n.dot(nr.randn(self.num_noise, 3).astype(n.single) * self.pca_stdevs.T, self.pca_vecs.T)\n        self.list_out.append(noise)\n\nclass ImageDataProvider(LabeledDataProvider):\n    def __init__(self, data_dir, batch_range=None, init_epoch=1, init_batchnum=None, dp_params=None, test=False):\n        LabeledDataProvider.__init__(self, data_dir, batch_range, init_epoch, init_batchnum, dp_params, test)\n        self.data_mean = self.batch_meta['data_mean'].astype(n.single)\n        self.color_eig = self.batch_meta['color_pca'][1].astype(n.single)\n        self.color_stdevs = n.c_[self.batch_meta['color_pca'][0].astype(n.single)]\n        self.color_noise_coeff = dp_params['color_noise']\n        self.num_colors = 3\n        self.img_size = int(sqrt(self.batch_meta['num_vis'] / self.num_colors))\n        self.mini = dp_params['minibatch_size']\n        self.inner_size = dp_params['inner_size'] if dp_params['inner_size'] > 0 else self.img_size\n        self.inner_pixels = self.inner_size **2\n        self.border_size = (self.img_size - self.inner_size) / 2\n        self.multiview = dp_params['multiview_test'] and test\n        self.num_views = 5*2\n        self.data_mult = self.num_views if self.multiview else 1\n        self.batch_size = self.batch_meta['batch_size']\n        self.label_offset = 0 if 'label_offset' not in self.batch_meta else self.batch_meta['label_offset']\n        self.scalar_mean = dp_params['scalar_mean'] \n        # Maintain pointers to previously-returned data matrices so they don't get garbage collected.\n        self.data = [None, None] # These are pointers to previously-returned data matrices\n\n        self.loader_thread, self.color_noise_thread = None, None\n        self.convnet = dp_params['convnet']\n            \n        self.num_noise = self.batch_size\n        self.batches_generated, self.loaders_started = 0, 0\n        self.data_mean_crop = self.data_mean.reshape((self.num_colors,self.img_size,self.img_size))[:,self.border_size:self.border_size+self.inner_size,self.border_size:self.border_size+self.inner_size].reshape((1,3*self.inner_size**2))\n\n        if self.scalar_mean >= 0:\n            self.data_mean_crop = self.scalar_mean\n            \n    def showimg(self, img):\n        from matplotlib import pylab as pl\n        pixels = img.shape[0] / 3\n        size = int(sqrt(pixels))\n        img = img.reshape((3,size,size)).swapaxes(0,2).swapaxes(0,1)\n        pl.imshow(img, interpolation='nearest')\n        pl.show()\n            \n    def get_data_dims(self, idx=0):\n        if idx == 0:\n            return self.inner_size**2 * 3\n        if idx == 2:\n            return self.get_num_classes()\n        return 1\n\n    def start_loader(self, batch_idx):\n        self.load_data = []\n        self.loader_thread = JPEGBatchLoaderThread(self,\n                                                   self.batch_range[batch_idx],\n                                                   self.label_offset,\n                                                   self.load_data)\n        self.loader_thread.start()\n        \n    def start_color_noise_maker(self):\n        color_noise_list = []\n        self.color_noise_thread = ColorNoiseMakerThread(self.color_stdevs, self.color_eig, self.num_noise, color_noise_list)\n        self.color_noise_thread.start()\n        return color_noise_list\n\n    def set_labels(self, datadic):\n        pass\n    \n    def get_data_from_loader(self):\n        if self.loader_thread is None:\n            self.start_loader(self.batch_idx)\n            self.loader_thread.join()\n            self.data[self.d_idx] = self.load_data[0]\n\n            self.start_loader(self.get_next_batch_idx())\n        else:\n            # Set the argument to join to 0 to re-enable batch reuse\n            self.loader_thread.join()\n            if not self.loader_thread.is_alive():\n                self.data[self.d_idx] = self.load_data[0]\n                self.start_loader(self.get_next_batch_idx())\n            #else:\n            #    print \"Re-using batch\"\n        self.advance_batch()\n    \n    def add_color_noise(self):\n        # At this point the data already has 0 mean.\n        # So I'm going to add noise to it, but I'm also going to scale down\n        # the original data. This is so that the overall scale of the training\n        # data doesn't become too different from the test data.\n\n        s = self.data[self.d_idx]['data'].shape\n        cropped_size = self.get_data_dims(0) / 3\n        ncases = s[0]\n\n        if self.color_noise_thread is None:\n            self.color_noise_list = self.start_color_noise_maker()\n            self.color_noise_thread.join()\n            self.color_noise = self.color_noise_list[0]\n            self.color_noise_list = self.start_color_noise_maker()\n        else:\n            self.color_noise_thread.join(0)\n            if not self.color_noise_thread.is_alive():\n                self.color_noise = self.color_noise_list[0]\n                self.color_noise_list = self.start_color_noise_maker()\n\n        self.data[self.d_idx]['data'] = self.data[self.d_idx]['data'].reshape((ncases*3, cropped_size))\n        self.color_noise = self.color_noise[:ncases,:].reshape((3*ncases, 1))\n        self.data[self.d_idx]['data'] += self.color_noise * self.color_noise_coeff\n        self.data[self.d_idx]['data'] = self.data[self.d_idx]['data'].reshape((ncases, 3* cropped_size))\n        self.data[self.d_idx]['data'] *= 1.0 / (1.0 + self.color_noise_coeff) # <--- NOTE: This is the slow line, 0.25sec. Down from 0.75sec when I used division.\n    \n    def get_next_batch(self):\n        self.d_idx = self.batches_generated % 2\n        epoch, batchnum = self.curr_epoch, self.curr_batchnum\n\n        self.get_data_from_loader()\n\n        # Subtract mean\n        self.data[self.d_idx]['data'] -= self.data_mean_crop\n        \n        if self.color_noise_coeff > 0 and not self.test:\n            self.add_color_noise()\n        self.batches_generated += 1\n        \n        return epoch, batchnum, [self.data[self.d_idx]['data'].T, self.data[self.d_idx]['labvec'].T, self.data[self.d_idx]['labmat'].T]\n        \n        \n    # Takes as input an array returned by get_next_batch\n    # Returns a (numCases, imgSize, imgSize, 3) array which can be\n    # fed to pylab for plotting.\n    # This is used by shownet.py to plot test case predictions.\n    def get_plottable_data(self, data, add_mean=True):\n        mean = self.data_mean_crop.reshape((data.shape[0],1)) if data.flags.f_contiguous or self.scalar_mean else self.data_mean_crop.reshape((data.shape[0],1))\n        return n.require((data + (mean if add_mean else 0)).T.reshape(data.shape[1], 3, self.inner_size, self.inner_size).swapaxes(1,3).swapaxes(1,2) / 255.0, dtype=n.single)\n       \nclass CIFARDataProvider(LabeledDataProvider):\n    def __init__(self, data_dir, batch_range=None, init_epoch=1, init_batchnum=None, dp_params=None, test=False):\n        LabeledDataProvider.__init__(self, data_dir, batch_range, init_epoch, init_batchnum, dp_params, test)\n        self.img_size = 32 \n        self.num_colors = 3\n        self.inner_size =  dp_params['inner_size'] if dp_params['inner_size'] > 0 else self.batch_meta['img_size']\n        self.border_size = (self.img_size - self.inner_size) / 2\n        self.multiview = dp_params['multiview_test'] and test\n        self.num_views = 9\n        self.scalar_mean = dp_params['scalar_mean'] \n        self.data_mult = self.num_views if self.multiview else 1\n        self.data_dic = []\n        for i in batch_range:\n            self.data_dic += [unpickle(self.get_data_file_name(i))]\n            self.data_dic[-1][\"labels\"] = n.require(self.data_dic[-1]['labels'], dtype=n.single)\n            self.data_dic[-1][\"labels\"] = n.require(n.tile(self.data_dic[-1][\"labels\"].reshape((1, n.prod(self.data_dic[-1][\"labels\"].shape))), (1, self.data_mult)), requirements='C')\n            self.data_dic[-1]['data'] = n.require(self.data_dic[-1]['data'] - self.scalar_mean, dtype=n.single, requirements='C')\n        \n        self.cropped_data = [n.zeros((self.get_data_dims(), self.data_dic[0]['data'].shape[1]*self.data_mult), dtype=n.single) for x in xrange(2)]\n\n        self.batches_generated = 0\n        self.data_mean = self.batch_meta['data_mean'].reshape((self.num_colors,self.img_size,self.img_size))[:,self.border_size:self.border_size+self.inner_size,self.border_size:self.border_size+self.inner_size].reshape((self.get_data_dims(), 1))\n\n    def get_next_batch(self):\n        epoch, batchnum = self.curr_epoch, self.curr_batchnum\n        self.advance_batch()\n        bidx = batchnum - self.batch_range[0]\n\n        cropped = self.cropped_data[self.batches_generated % 2]\n\n        self.__trim_borders(self.data_dic[bidx]['data'], cropped)\n        cropped -= self.data_mean\n        self.batches_generated += 1\n        return epoch, batchnum, [cropped, self.data_dic[bidx]['labels']]\n        \n    def get_data_dims(self, idx=0):\n        return self.inner_size**2 * self.num_colors if idx == 0 else 1\n\n    # Takes as input an array returned by get_next_batch\n    # Returns a (numCases, imgSize, imgSize, 3) array which can be\n    # fed to pylab for plotting.\n    # This is used by shownet.py to plot test case predictions.\n    def get_plottable_data(self, data):\n        return n.require((data + self.data_mean).T.reshape(data.shape[1], 3, self.inner_size, self.inner_size).swapaxes(1,3).swapaxes(1,2) / 255.0, dtype=n.single)\n    \n    def __trim_borders(self, x, target):\n        y = x.reshape(self.num_colors, self.img_size, self.img_size, x.shape[1])\n\n        if self.test: # don't need to loop over cases\n            if self.multiview:\n                start_positions = [(0,0), (0, self.border_size), (0, self.border_size*2),\n                                  (self.border_size, 0), (self.border_size, self.border_size), (self.border_size, self.border_size*2),\n                                  (self.border_size*2, 0), (self.border_size*2, self.border_size), (self.border_size*2, self.border_size*2)]\n                end_positions = [(sy+self.inner_size, sx+self.inner_size) for (sy,sx) in start_positions]\n                for i in xrange(self.num_views):\n                    target[:,i * x.shape[1]:(i+1)* x.shape[1]] = y[:,start_positions[i][0]:end_positions[i][0],start_positions[i][1]:end_positions[i][1],:].reshape((self.get_data_dims(),x.shape[1]))\n            else:\n                pic = y[:,self.border_size:self.border_size+self.inner_size,self.border_size:self.border_size+self.inner_size, :] # just take the center for now\n                target[:,:] = pic.reshape((self.get_data_dims(), x.shape[1]))\n        else:\n            for c in xrange(x.shape[1]): # loop over cases\n                startY, startX = nr.randint(0,self.border_size*2 + 1), nr.randint(0,self.border_size*2 + 1)\n                endY, endX = startY + self.inner_size, startX + self.inner_size\n                pic = y[:,startY:endY,startX:endX, c]\n                if nr.randint(2) == 0: # also flip the image with 50% probability\n                    pic = pic[:,:,::-1]\n                target[:,c] = pic.reshape((self.get_data_dims(),))\n\nclass DummyConvNetLogRegDataProvider(LabeledDummyDataProvider):\n    def __init__(self, data_dim):\n        LabeledDummyDataProvider.__init__(self, data_dim)\n\n        self.img_size = int(sqrt(data_dim/3))\n        \n    def get_next_batch(self):\n        epoch, batchnum, dic = LabeledDummyDataProvider.get_next_batch(self)\n        dic = {'data': dic[0], 'labels': dic[1]}\n        print dic['data'].shape, dic['labels'].shape\n        return epoch, batchnum, [dic['data'], dic['labels']]\n    \n    # Returns the dimensionality of the two data matrices returned by get_next_batch\n    def get_data_dims(self, idx=0):\n        return self.batch_meta['num_vis'] if idx == 0 else 1\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/convnet.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport numpy as n\nimport numpy.random as nr\nimport random as r\nfrom python_util.util import *\nfrom python_util.data import *\nfrom python_util.options import *\nfrom python_util.gpumodel import *\nimport sys\nimport math as m\nimport layer as lay\nfrom convdata import ImageDataProvider, CIFARDataProvider, DummyConvNetLogRegDataProvider\nfrom os import linesep as NL\nimport copy as cp\nimport os\n\nclass Driver(object):\n    def __init__(self, convnet):\n        self.convnet = convnet\n        \n    def on_start_batch(self, batch_data, train):\n        pass\n    \n    def on_finish_batch(self):\n        pass\n\nclass GradCheckDriver(Driver):\n    def on_start_batch(self, batch_data, train):\n        data = batch_data[2]\n        self.convnet.libmodel.checkGradients(data)\n\nclass TrainingDriver(Driver):\n    def on_start_batch(self, batch_data, train):\n        data = batch_data[2]\n        self.convnet.libmodel.startBatch(data, self.convnet.get_progress(), not train)\n\nclass MultiviewTestDriver(TrainingDriver):\n    def on_start_batch(self, batch_data, train):\n        self.write_output = False\n        if train:\n            TrainingDriver.on_start_batch(self, batch_data, train)\n        else:\n            data = batch_data[2]\n            num_views = self.convnet.test_data_provider.num_views\n            if self.convnet.test_out != \"\" and self.convnet.logreg_name != \"\":\n                self.write_output = True\n                self.test_file_name = os.path.join(self.convnet.test_out, 'test_preds_%d' % batch_data[1])\n                self.probs = n.zeros((data[0].shape[1]/num_views, self.convnet.test_data_provider.get_num_classes()), dtype=n.single)\n                self.convnet.libmodel.startMultiviewTest(data, num_views, self.probs, self.convnet.logreg_name)\n            else:\n                self.convnet.libmodel.startMultiviewTest(data, num_views)\n            \n    def on_finish_batch(self):\n        if self.write_output:\n            if not os.path.exists(self.convnet.test_out):\n                os.makedirs(self.convnet.test_out)\n            pickle(self.test_file_name,  {'data': self.probs,\n                                          'note': 'generated from %s' % self.convnet.save_file})\n\nclass FeatureWriterDriver(Driver):\n    def __init__(self, convnet):\n        Driver.__init__(self, convnet)\n        self.last_batch = convnet.test_batch_range[-1]\n        \n    def on_start_batch(self, batch_data, train):\n        if train:\n            raise ModelStateException(\"FeatureWriter must be used in conjunction with --test-only=1. It writes test data features.\")\n        \n        self.batchnum, self.data = batch_data[1], batch_data[2]\n        \n        if not os.path.exists(self.convnet.feature_path):\n            os.makedirs(self.convnet.feature_path)\n        \n        self.num_ftrs = self.convnet.layers[self.convnet.write_features]['outputs']\n        self.ftrs = n.zeros((self.data[0].shape[1], self.num_ftrs), dtype=n.single)\n        self.convnet.libmodel.startFeatureWriter(self.data, [self.ftrs], [self.convnet.write_features])\n    \n    def on_finish_batch(self):\n        path_out = os.path.join(self.convnet.feature_path, 'data_batch_%d' % self.batchnum)\n        pickle(path_out, {'data': self.ftrs, 'labels': self.data[1]})\n        print \"Wrote feature file %s\" % path_out\n        if self.batchnum == self.last_batch:\n            pickle(os.path.join(self.convnet.feature_path, 'batches.meta'), {'source_model':self.convnet.load_file,\n                                                                             'num_vis':self.num_ftrs,\n                                                                             'batch_size': self.convnet.test_data_provider.batch_meta['batch_size']})\n\nclass ConvNet(IGPUModel):\n    def __init__(self, op, load_dic, dp_params={}):\n        filename_options = []\n        for v in ('color_noise', 'multiview_test', 'inner_size', 'scalar_mean', 'minibatch_size'):\n            dp_params[v] = op.get_value(v)\n\n        IGPUModel.__init__(self, \"ConvNet\", op, load_dic, filename_options, dp_params=dp_params)\n        \n    def import_model(self):\n        lib_name = \"cudaconvnet._ConvNet\"\n        print \"=========================\"\n        print \"Importing %s C++ module\" % lib_name\n        self.libmodel = __import__(lib_name,fromlist=['_ConvNet'])\n        \n    def init_model_lib(self):\n        self.libmodel.initModel(self.layers,\n                                self.device_ids,\n                                self.minibatch_size,\n                                self.conserve_mem)\n        \n    def init_model_state(self):\n        ms = self.model_state\n        layers = ms['layers'] if self.loaded_from_checkpoint else {}\n        ms['layers'] = lay.LayerParser.parse_layers(os.path.join(self.layer_path, self.layer_def),\n                                                    os.path.join(self.layer_path, self.layer_params), self, layers=layers)\n        \n        self.do_decouple_conv()\n        self.do_unshare_weights()\n\n        self.op.set_value('conv_to_local', [], parse=False)\n        self.op.set_value('unshare_weights', [], parse=False)\n        \n        self.set_driver()\n    \n    def do_decouple_conv(self):\n        # Convert convolutional layers to local\n        if len(self.op.get_value('conv_to_local')) > 0:\n            for lname in self.op.get_value('conv_to_local'):\n                if self.model_state['layers'][lname]['type'] == 'conv':\n                    lay.LocalLayerParser.conv_to_local(self.model_state['layers'], lname)\n    \n    def do_unshare_weights(self):\n        # Decouple weight matrices\n        if len(self.op.get_value('unshare_weights')) > 0:\n            for name_str in self.op.get_value('unshare_weights'):\n                if name_str:\n                    name = lay.WeightLayerParser.get_layer_name(name_str)\n                    if name is not None:\n                        name, idx = name[0], name[1]\n                        if name not in self.model_state['layers']:\n                            raise ModelStateException(\"Layer '%s' does not exist; unable to unshare\" % name)\n                        layer = self.model_state['layers'][name]\n                        lay.WeightLayerParser.unshare_weights(layer, self.model_state['layers'], matrix_idx=idx)\n                    else:\n                        raise ModelStateException(\"Invalid layer name '%s'; unable to unshare.\" % name_str)\n    \n    def set_driver(self):\n        if self.op.get_value('check_grads'):\n            self.driver = GradCheckDriver(self)\n        elif self.op.get_value('multiview_test'):\n            self.driver = MultiviewTestDriver(self)\n        elif self.op.get_value('write_features'):\n            self.driver = FeatureWriterDriver(self)\n        else:\n            self.driver = TrainingDriver(self)\n\n    def fill_excused_options(self):\n        if self.op.get_value('check_grads'):\n            self.op.set_value('save_path', '')\n            self.op.set_value('train_batch_range', '0')\n            self.op.set_value('test_batch_range', '0')\n            self.op.set_value('data_path', '')\n            \n    # Make sure the data provider returned data in proper format\n    def parse_batch_data(self, batch_data, train=True):\n        if max(d.dtype != n.single for d in batch_data[2]):\n            raise DataProviderException(\"All matrices returned by data provider must consist of single-precision floats.\")\n        return batch_data\n\n    def start_batch(self, batch_data, train=True):\n        self.driver.on_start_batch(batch_data, train)\n            \n    def finish_batch(self):\n        ret = IGPUModel.finish_batch(self)\n        self.driver.on_finish_batch()\n        return ret\n    \n    def print_iteration(self):\n        print \"%d.%d (%.2f%%)...\" % (self.epoch, self.batchnum, 100 * self.get_progress()),\n        \n    def print_train_time(self, compute_time_py):\n        print \"(%.3f sec)\" % (compute_time_py)\n        \n    def print_costs(self, cost_outputs):\n        costs, num_cases = cost_outputs[0], cost_outputs[1]\n        children = set()\n        for errname in costs:\n            if sum(errname in self.layers[z]['children'] for z in costs) == 0:\n#                print self.layers[errname]['children']\n                for child in set(self.layers[errname]['children']) & set(costs.keys()):\n                    costs[errname] = [v + u for v, u in zip(costs[errname], costs[child])]\n                    children.add(child)\n            \n                filtered_costs = eval(self.layers[errname]['outputFilter'])(costs[errname], num_cases)\n                print \"%s: \" % errname,\n                if 'outputFilterFormatter' not in self.layers[errname]:\n                    print \", \".join(\"%.6f\" % v for v in filtered_costs),\n                else:\n                    print eval(self.layers[errname]['outputFilterFormatter'])(self,filtered_costs),\n                if m.isnan(filtered_costs[0]) or m.isinf(filtered_costs[0]):\n                    print \"<- error nan or inf!\"\n                    sys.exit(1)\n        for c in children:\n            del costs[c]\n        \n    def print_train_results(self):\n        self.print_costs(self.train_outputs[-1])\n        \n    def print_test_status(self):\n        pass\n        \n    def print_test_results(self):\n        print NL + \"======================Test output======================\"\n        self.print_costs(self.test_outputs[-1])\n        if not self.test_only:\n            print NL + \"----------------------Averages-------------------------\"\n            self.print_costs(self.aggregate_test_outputs(self.test_outputs[-len(self.test_batch_range):]))\n        print NL + \"-------------------------------------------------------\",\n        for name,val in sorted(self.layers.items(), key=lambda x: x[1]['id']): # This is kind of hacky but will do for now.\n            l = self.layers[name]\n            if 'weights' in l:\n                wscales = [(l['name'], i, n.mean(n.abs(w)), n.mean(n.abs(wi))) for i,(w,wi) in enumerate(zip(l['weights'],l['weightsInc']))]\n                print \"\"\n                print NL.join(\"Layer '%s' weights[%d]: %e [%e] [%e]\" % (s[0], s[1], s[2], s[3], s[3]/s[2] if s[2] > 0 else 0) for s in wscales),\n                print \"%sLayer '%s' biases: %e [%e]\" % (NL, l['name'], n.mean(n.abs(l['biases'])), n.mean(n.abs(l['biasesInc']))),\n        print \"\"\n        \n    def conditional_save(self):\n        self.save_state()\n        \n    def aggregate_test_outputs(self, test_outputs):\n        test_outputs = cp.deepcopy(test_outputs)\n        num_cases = sum(t[1] for t in test_outputs)\n        for i in xrange(1 ,len(test_outputs)):\n            for k,v in test_outputs[i][0].items():\n                for j in xrange(len(v)):\n                    test_outputs[0][0][k][j] += test_outputs[i][0][k][j]\n        \n        return (test_outputs[0][0], num_cases)\n    \n    @classmethod\n    def get_options_parser(cls):\n        op = IGPUModel.get_options_parser()\n        op.add_option(\"mini\", \"minibatch_size\", IntegerOptionParser, \"Minibatch size\", default=128)\n        op.add_option(\"layer-def\", \"layer_def\", StringOptionParser, \"Layer definition file\", set_once=False)\n        op.add_option(\"layer-params\", \"layer_params\", StringOptionParser, \"Layer parameter file\")\n        op.add_option(\"layer-path\", \"layer_path\", StringOptionParser, \"Layer file path prefix\", default=\"\")\n        op.add_option(\"check-grads\", \"check_grads\", BooleanOptionParser, \"Check gradients and quit?\", default=0, excuses=['data_path','save_path', 'save_file_override', 'train_batch_range','test_batch_range'])\n        op.add_option(\"multiview-test\", \"multiview_test\", BooleanOptionParser, \"Cropped DP: test on multiple patches?\", default=0)\n        op.add_option(\"inner-size\", \"inner_size\", IntegerOptionParser, \"Cropped DP: crop size (0 = don't crop)\", default=0, set_once=True)\n        op.add_option(\"conv-to-local\", \"conv_to_local\", ListOptionParser(StringOptionParser), \"Convert given conv layers to unshared local\", default=[])\n        op.add_option(\"unshare-weights\", \"unshare_weights\", ListOptionParser(StringOptionParser), \"Unshare weight matrices in given layers\", default=[])\n        op.add_option(\"conserve-mem\", \"conserve_mem\", BooleanOptionParser, \"Conserve GPU memory (slower)?\", default=0)\n        op.add_option(\"color-noise\", \"color_noise\", FloatOptionParser, \"Add PCA noise to color channels with given scale\", default=0.0)\n        op.add_option(\"test-out\", \"test_out\", StringOptionParser, \"Output test case predictions to given path\", default=\"\", requires=['logreg_name', 'multiview_test'])\n        op.add_option(\"logreg-name\", \"logreg_name\", StringOptionParser, \"Logreg cost layer name (for --test-out)\", default=\"\")\n        op.add_option(\"scalar-mean\", \"scalar_mean\", FloatOptionParser, \"Subtract this scalar from image (-1 = don't)\", default=-1)\n        \n        op.add_option(\"write-features\", \"write_features\", StringOptionParser, \"Write test data features from given layer\", default=\"\", requires=['feature-path'])\n        op.add_option(\"feature-path\", \"feature_path\", StringOptionParser, \"Write test data features to this path (to be used with --write-features)\", default=\"\")\n\n        op.delete_option('max_test_err')\n        op.options[\"testing_freq\"].default = 57\n        op.options[\"num_epochs\"].default = 50000\n        op.options['dp_type'].default = None\n\n        DataProvider.register_data_provider('dummy-lr-n', 'Dummy ConvNet logistic regression', DummyConvNetLogRegDataProvider)\n        DataProvider.register_data_provider('image', 'JPEG-encoded image data provider', ImageDataProvider)\n        DataProvider.register_data_provider('cifar', 'CIFAR-10 data provider', CIFARDataProvider)\n  \n        return op\n\nif __name__ == \"__main__\":\n#    nr.seed(6)\n\n    op = ConvNet.get_options_parser()\n\n    op, load_dic = IGPUModel.parse_options(op)\n    model = ConvNet(op, load_dic)\n    model.start()\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconv3/Makefile",
    "content": "################################################################################\n#\n# Copyright 1993-2012 NVIDIA Corporation.  All rights reserved.\n#\n# NOTICE TO USER:   \n#\n# This source code is subject to NVIDIA ownership rights under U.S. and \n# international Copyright laws.  \n#\n# NVIDIA MAKES NO REPRESENTATION ABOUT THE SUITABILITY OF THIS SOURCE \n# CODE FOR ANY PURPOSE.  IT IS PROVIDED \"AS IS\" WITHOUT EXPRESS OR \n# IMPLIED WARRANTY OF ANY KIND.  NVIDIA DISCLAIMS ALL WARRANTIES WITH \n# REGARD TO THIS SOURCE CODE, INCLUDING ALL IMPLIED WARRANTIES OF \n# MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE.   \n# IN NO EVENT SHALL NVIDIA BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL, \n# OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS \n# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE \n# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE \n# OR PERFORMANCE OF THIS SOURCE CODE.  \n#\n# U.S. Government End Users.  This source code is a \"commercial item\" as \n# that term is defined at 48 C.F.R. 2.101 (OCT 1995), consisting  of \n# \"commercial computer software\" and \"commercial computer software \n# documentation\" as such terms are used in 48 C.F.R. 12.212 (SEPT 1995) \n# and is provided to the U.S. Government only as a commercial end item.  \n# Consistent with 48 C.F.R.12.212 and 48 C.F.R. 227.7202-1 through \n# 227.7202-4 (JUNE 1995), all U.S. Government End Users acquire the \n# source code with only those rights set forth herein.\n#\n################################################################################\n\n# Location of the CUDA Toolkit binaries and libraries\nCUDA_INC_PATH  = $(CUDA_INSTALL_PATH)/include\nCUDA_BIN_PATH  = $(CUDA_INSTALL_PATH)/bin\nCUDA_LIB_PATH  = $(CUDA_INSTALL_PATH)/lib64\n\n# Common binaries\nNVCC            = $(CUDA_BIN_PATH)/nvcc\nGCC             = g++\nAR\t\t\t\t= ar\n\n# CUDA code generation flags\nGENCODE_SM35    := -gencode arch=compute_35,code=sm_35\nGENCODE_FLAGS   := $(GENCODE_SM35)\n\nLDFLAGS   := -L$(CUDA_LIB_PATH) -lcudart\nCCFLAGS   := -m64\nNVCCFLAGS := -m64\n\n# Debug build flags\nifeq ($(dbg),1)\n      CCFLAGS   += -g\n      NVCCFLAGS += -g -G\n      DBG := debug\nelse\n      DBG := release\n      NVCCFLAGS += -O3\n      CCFLAGS += -O3\nendif\n\n# Add profiler output\nifeq ($(prof),1)\n\tNVCCFLAGS += --ptxas-options=-v\nendif\n\nTARGETDIR := ./bin/$(DBG)\nOBJDIR := ./obj/$(DBG)\n\n########## USER STUFF ###########\nLDFLAGS   \t\t+= -L../util -lutilpy -L../nvmatrix -lnvmatrix -lcublas\nINCLUDES      \t:= -I$(CUDA_INC_PATH) -I $(CUDA_SDK_PATH)/common/inc -I./include\n\nCUFILES\t:= $(shell find . -name \"*.cu\")\nCU_DEPS\t:= $(shell find . -name \"*.cuh\")\nCCFILES\t:= $(shell find . -name \"*.cpp\")\nC_DEPS\t:= $(shell find . -name \"*.h\")\n\nNVCCFLAGS += --compiler-options '-fPIC'\nLDFLAGS += -shared\nCCFLAGS += -fPIC\nTARGET := $(TARGETDIR)/libcudaconv.so\n\n################################################################################\n# Set up target and object files\n################################################################################\nOBJS +=  $(patsubst %.cpp,$(OBJDIR)/%.cpp.o,$(CCFILES))\nOBJS +=  $(patsubst %.c,$(OBJDIR)/%.c.o,$(CFILES))\nOBJS +=  $(patsubst %.cu,$(OBJDIR)/%.cu.o,$(CUFILES))\n\n# Target rules\nall: makedirs $(TARGET)\n\n$(OBJDIR)/%.cu.o : %.cu $(CU_DEPS)\n\t$(NVCC) $(NVCCFLAGS) $(GENCODE_FLAGS) $(INCLUDES) -o $@ -c $<\n\n$(OBJDIR)/%.cpp.o : %.cpp $(C_DEPS)\n\t$(GCC) $(CCFLAGS) $(INCLUDES) -o $@ -c $<\n\n$(TARGET): $(OBJS)\n\t$(GCC) $(CCFLAGS) -o $@ $+ $(LDFLAGS) \n\tln -sf $(TARGET) .\n\nmakedirs:\n\tmkdir -p $(TARGETDIR)\n\tmkdir -p $(OBJDIR)/src\n\nclean:\n\trm -rf ./obj\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconv3/include/conv_util.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CONV_UTIL_CUH\n#define\tCONV_UTIL_CUH\n\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n\n#include \"caffe2/core/context_gpu.h\"\n\n#ifndef MIN\n#define MIN(a, b) ((a) > (b) ? (b) : (a))\n#endif\n#ifndef MAX\n#define MAX(a, b) ((a) > (b) ? (a) : (b))\n#endif\n\nvoid convLocalMaxUndo(NVMatrix& images, NVMatrix& maxGrads, NVMatrix& maxActs, NVMatrix& target,\n                      int subsX, int startX, int strideX, int outputsX);\nvoid convLocalAvgUndo(NVMatrix& avgGrads, NVMatrix& target,\n                      int subsX, int startX, int strideX, int outputsX, int imgSize, bool sum);\n\nvoid convLocalAvgUndo(NVMatrix& avgGrads, NVMatrix& target,\n                      int subsX, int startX, int strideX, int outputsX, int imgSize, bool sum,\n                      float scaleTargets, float scaleOutput);\nvoid convLocalMaxUndo(NVMatrix& images, NVMatrix& maxGrads, NVMatrix& maxActs, NVMatrix& target,\n                      int subsX, int startX, int strideX, int outputsX, float scaleTargets, float scaleOutput);\n\nvoid convResponseNorm(NVMatrix& images, NVMatrix& denoms, NVMatrix& target, int numFilters, int sizeX, float addScale, float powScale, float minDiv);\nvoid convResponseNormUndo(NVMatrix& outGrads, NVMatrix& denoms, NVMatrix& inputs, NVMatrix& acts, NVMatrix& target, int numFilters,\n                         int sizeX, float addScale, float powScale, float scaleTargets, float scaleOutput);\nvoid convContrastNorm(NVMatrix& images, NVMatrix& meanDiffs, NVMatrix& denoms, NVMatrix& target, int numFilters, int sizeX, float addScale, float powScale, float minDiv);\nvoid convContrastNormUndo(NVMatrix& outGrads, NVMatrix& denoms, NVMatrix& meanDiffs, NVMatrix& acts, NVMatrix& target, int numFilters,\n                         int sizeX, float addScale, float powScale, float scaleTargets, float scaleOutput);\n\nvoid convGaussianBlur(NVMatrix& images, NVMatrix& filter, NVMatrix& target, bool horiz, int numChannels,\n                      float scaleTargets, float scaleOutputs);\nvoid convBedOfNails(NVMatrix& images, NVMatrix& target, int numChannels, int imgSize, int startX,\n                    int strideX, float scaleTargets, float scaleOutput);\nvoid convBedOfNailsUndo(NVMatrix& actsGrad, NVMatrix& target, int numChannels, int imgSize,\n                        int startX, int strideX, float scaleTargets, float scaleOutput);\n\nvoid convResizeBilinear(NVMatrix& images, NVMatrix& target, int imgSize, int tgtSize, float scale);\nvoid convRGBToYUV(NVMatrix& images, NVMatrix& target);\nvoid convRGBToLAB(NVMatrix& images, NVMatrix& target, bool center);\nvoid convCrop(NVMatrix& imgs, NVMatrix& target, int imgSize, int tgtSize, int startY, int startX);\nvoid normalizeLocalWeights(NVMatrix& weights, int numModules, float norm);\nvoid convContrastNormCrossMap(NVMatrix& images, NVMatrix& meanDiffs, NVMatrix& target,\n                             int numFilters, int sizeF, float addScale, float powScale, float minDiv, bool blocked);\nvoid convResponseNormCrossMapUndo(NVMatrix& outGrads, NVMatrix& inputs, NVMatrix& acts, NVMatrix& target, int numFilters,\n                         int sizeF, float addScale, float powScale, float minDiv, bool blocked, float scaleTargets, float scaleOutput);\nvoid convResponseNormCrossMap(NVMatrix& images, NVMatrix& target, int numFilters, int sizeF, float addScale,\n                              float powScale, bool blocked);\nvoid convResponseNormCrossMap(NVMatrix& images, NVMatrix& target, int numFilters, int sizeF, float addScale,\n                              float powScale, float minDiv, bool blocked);\nvoid convReflectHorizontal(NVMatrix& images, NVMatrix& targets, int imgSize);\n\nvoid convCrossMapMaxPoolUndo(NVMatrix& images, NVMatrix& maxGrads, NVMatrix& maxActs, NVMatrix& target,\n                             const int imgSize, const int startF, const int poolSize,\n                             const int stride, const float scaleTargets, const float scaleOutputs);\n\ncudaTextureObject_t GetTensorTextureObject(caffe2::TensorCUDA* tensor);\n\ntemplate<bool sum>\nclass AvgPooler {\npublic:\n    __device__ inline float operator()(const float a, const float b) const {\n        return a + b;\n    }\n    __device__ inline float getBaseValue() const {\n        return 0;\n    }\n    __device__ inline float output(const float a, const int regionSize) const {\n        return sum ? a : (a / regionSize);\n    }\n};\n\nclass MaxPooler {\npublic:\n    __device__ inline float operator()(const float a, const float b) const {\n        return fmaxf(a, b);\n    }\n    __device__ inline float getBaseValue() const {\n        return -2e38;\n    }\n    __device__ inline float output(const float a, const int regionSize) const {\n        return a;\n    }\n};\n\nclass MaxAbsPooler {\npublic:\n    __device__ inline float operator()(const float a, const float b) const {\n        return fabsf(a) > fabsf(b) ? a : b;\n    }\n    __device__ inline float getBaseValue() const {\n        return 0.0f;\n    }\n    __device__ inline float output(const float a, const int regionSize) const {\n        return a;\n    }\n};\n\n/*\n * Block size B_YxB_X\n * blockIdx.x determines output.x, image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines output.y, filter idx in batches of B_Y*filtersPerThread\n *\n * So each block does one output for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines filter idx\n *\n * imgs:        (numFilters, imgPixels, numImages)\n * target:      (numFilters, numOutputs, numImages)\n *\n * numImages must be divisible by B_X*imgsPerThread if checkCaseBounds is false\n */\n\ntemplate<class Agg, int B_Y, int B_X, int imgsPerThread, int filtersPerThread, bool checkCaseBounds>\n__global__ void kLocalPool(float* imgs, float* target, const int imgSize, const int numFilters,\n                           const int numImages, const int subsX, const int startX, const int strideX,\n                           const int outputsX, Agg agg) {\n    const int numImgBlocks = DIVUP(numImages,B_X*imgsPerThread);\n    const int numFilterBlocks = DIVUP(numFilters, B_Y*filtersPerThread);\n    const int outputIdxX = blockIdx.x / numImgBlocks;\n    const int outputIdxY = blockIdx.y / numFilterBlocks;\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * B_X * imgsPerThread;\n    const int blockFilterIdx = (blockIdx.y % numFilterBlocks) * B_Y * filtersPerThread;\n    const int myFilterIdx = (blockFilterIdx + threadIdx.y*filtersPerThread);\n    if (myFilterIdx >= numFilters) {\n        return;\n    }\n\n    const int outputIdx = outputIdxY * outputsX + outputIdxX;\n    const int numOutputs = outputsX * outputsX;\n    const int imgPixels = imgSize * imgSize;\n\n    const int startImgPxX = startX + outputIdxX * strideX;\n    const int startImgPxY = startX + outputIdxY * strideX;\n    const int imgIdx = blockImgIdx + threadIdx.x;\n\n    imgs += myFilterIdx * imgPixels * numImages + imgIdx;\n    target += (myFilterIdx * numOutputs + outputIdx) * numImages + imgIdx;\n\n    float prod[filtersPerThread][imgsPerThread];\n    #pragma unroll\n    for (int f = 0; f < filtersPerThread; f++) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            prod[f][i] = agg.getBaseValue();\n        }\n    }\n\n    const int loopStartY = MAX(0, startImgPxY);\n    const int loopStartX = MAX(0, startImgPxX);\n    const int loopEndY = MIN(imgSize, startImgPxY + subsX);\n    const int loopEndX = MIN(imgSize, startImgPxX + subsX);\n    const int regionSize = (loopEndY - loopStartY) * (loopEndX - loopStartX);\n    for (int y = loopStartY; y < loopEndY; y++) {\n        for (int x = loopStartX; x < loopEndX; x++) {\n            const int imgPx = y * imgSize + x;\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        prod[f][i] = agg(prod[f][i], imgs[(f * imgPixels + imgPx) * numImages + i * B_X]);\n                    }\n                }\n            }\n        }\n    }\n\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                target[f * numOutputs * numImages + i * B_X] = agg.output(prod[f][i], regionSize);\n            }\n        }\n    }\n}\n\n\n/*\n * Block size B_YxB_X\n * blockIdx.x determines pixel.x, image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines pixel.y, output idx in batches of B_Y\n *\n * So each block does one pixel for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines output idx\n *\n * imgs:        (numFilters, imgPixels, numImages)\n * target:      (numOutputs, imgPixels, numImages) (out)\n *\n * numImages must be divisible by B_X*imgsPerThread if checkCaseBounds is false\n */\ntemplate<class Agg, int B_Y, int B_X, int imgsPerThread, bool checkCaseBounds>\n__global__ void kPoolCrossMap(float* imgs, float* target, const int imgSize,\n                              const int numFilters, const int numImages, const int startF, const int poolSize,\n                              const int numOutputs, const int stride, Agg agg) {\n    const int imgPixels = imgSize * imgSize;\n    const int numImgBlocks = DIVUP(numImages, B_X*imgsPerThread);\n//    const int numOutputs = DIVUP(numFilters, stride);\n    const int numOutputBlocks = DIVUP(numOutputs,B_Y);\n    const int pxIdxX = blockIdx.x / numImgBlocks;\n    const int pxIdxY = blockIdx.y / numOutputBlocks;\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * B_X * imgsPerThread;\n    const int outputIdx = (blockIdx.y % numOutputBlocks) * B_Y + threadIdx.y;\n//    const int filterIdx = outputIdx * stride;\n\n    const int pxIdx = pxIdxY * imgSize + pxIdxX;\n    const int imgIdx = blockImgIdx + threadIdx.x;\n\n    if (outputIdx < numOutputs) {\n        imgs += (pxIdx) * numImages + imgIdx;\n        target += (outputIdx * imgPixels + pxIdx) * numImages + imgIdx;\n\n        float prod[imgsPerThread];\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                prod[i] = agg.getBaseValue();\n            }\n        }\n\n        const int myStartF = startF + outputIdx * stride;\n        const int loopStartF = max(0, myStartF);\n        const int loopEndF = min(numFilters, myStartF + poolSize);\n\n        for (int f = loopStartF; f < loopEndF; ++f) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                    prod[i] = agg(prod[i], imgs[f * imgPixels * numImages + i * B_X]);\n                }\n            }\n        }\n\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                target[i * B_X] = agg.output(prod[i], poolSize);\n            }\n        }\n    }\n}\n\n/*\n * imgs:        (numFilters, imgPixels, numImages)\n * target:      (numOutputs, imgPixels, numImages)\n */\ntemplate<class Pooler>\nvoid convPoolCrossMap(NVMatrix& images, NVMatrix& target, const int startF, const int poolSize,\n                      const int numOutputs, const int stride, const int imgSize, Pooler pooler) {\n    int numImages = images.getNumCols();\n    int imgPixels = imgSize * imgSize;\n    int numFilters = images.getNumRows() / imgPixels;\n    assert(images.getNumRows() == numFilters * imgPixels);\n\n    assert(!images.isTrans());\n    assert(!target.isTrans());\n    assert(images.isContiguous());\n//    assert(numFilters % 4 == 0);\n//    assert(numImages % 128 == 0);\n    assert(stride <= poolSize);\n    assert(startF <= 0);\n    assert(startF + (numOutputs-1) * stride + poolSize >= numFilters); // All filters must be covered\n\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    target.resize(imgPixels*numOutputs, numImages);\n    int imgsPerThread = numImages % 128 == 0 ? 4 : numImages % 64 == 0 ? 2 : 1;\n\n    dim3 threads(32, 4);\n    dim3 blocks(imgSize * DIVUP(numImages, threads.x * imgsPerThread), imgSize * DIVUP(numOutputs, threads.y));\n    bool checkCaseBounds = numImages % (threads.x*imgsPerThread) != 0;\n    if (!checkCaseBounds) {\n        if (imgsPerThread == 4) {\n            cudaFuncSetCacheConfig(kPoolCrossMap<Pooler, 4, 32, 4, false>, cudaFuncCachePreferShared);\n            kPoolCrossMap<Pooler, 4, 32, 4, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                              imgSize, numFilters, numImages, startF, poolSize, numOutputs, stride, pooler);\n\n        } else if (imgsPerThread == 2) {\n            cudaFuncSetCacheConfig(kPoolCrossMap<Pooler, 4, 32, 2, false>, cudaFuncCachePreferShared);\n            kPoolCrossMap<Pooler, 4, 32, 2, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                              imgSize, numFilters, numImages, startF, poolSize, numOutputs, stride, pooler);\n\n        } else if (imgsPerThread == 1) {\n            cudaFuncSetCacheConfig(kPoolCrossMap<Pooler, 4, 32, 1, false>, cudaFuncCachePreferShared);\n            kPoolCrossMap<Pooler, 4, 32, 1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                              imgSize, numFilters, numImages, startF, poolSize, numOutputs, stride, pooler);\n        }\n    } else {\n        if (imgsPerThread == 1) {\n            cudaFuncSetCacheConfig(kPoolCrossMap<Pooler, 4, 32, 1, true>, cudaFuncCachePreferShared);\n            kPoolCrossMap<Pooler, 4, 32, 1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                              imgSize, numFilters, numImages, startF, poolSize, numOutputs, stride, pooler);\n        } else {\n            assert(false);\n        }\n    }\n    getLastCudaError(\"convPoolCrossMap: kernel execution failed\");\n}\n\n/*\n * Block size 16xB_X\n * blockIdx.x determines 4x4 pixel.x region, image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines 4x4 pixel.y region, filter idx in batches of filtersPerThread\n *\n * So each block does a 4x4 region for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines pixel idx\n *\n * imgs:        (numFilters, imgPixels, numImages)\n * target:      (numFilters, numOutputs, numImages)\n *\n * B_X one of 8, 16, 32\n * imgsPerThread one of 1, 2, 4, 8, 16\n *\n * B_XximgsPerThread MUST be divisible by 32.\n * Number of filters MUST be divisible by filtersPerThread.\n *\n * numImages must be divisible by B_X*imgsPerThread if checkCaseBounds is false\n *\n * Final write-out will not be fully coalesced unless B_X is 32. But there's a lot more\n * reading than writing here, and the reading is all coalesced, so it should be OK.\n *\n * To be used when the stride is 1 and the pooling region is fairly large.\n */\ntemplate<class Agg, int B_X, int imgsPerThread, int filtersPerThread, bool checkCaseBounds>\n__global__ void kLocalPool2(float* imgs, float* target, const int imgSize, const int numFilters,\n                           const int numImages, const int subsX, const int startX,\n                           const int outputsX, Agg agg) {\n    __shared__ float shImgs[filtersPerThread][B_X*imgsPerThread];\n    const int numImgBlocks = DIVUP(numImages,B_X*imgsPerThread);\n    const int numFilterBlocks = numFilters/(filtersPerThread);\n    const int blockOutputX = 4*(blockIdx.x / numImgBlocks);\n    const int blockOutputY = 4*(blockIdx.y / numFilterBlocks);\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * B_X * imgsPerThread;\n    const int blockFilterIdx = (blockIdx.y % numFilterBlocks) * filtersPerThread;\n\n//    const int blockOutputIdx = blockOutputY * outputsX + blockOutputX;\n    const int numOutputs = outputsX * outputsX;\n    const int imgPixels = imgSize * imgSize;\n\n    const int tidx = threadIdx.y * B_X + threadIdx.x;\n    const int loadY = tidx / 32, loadX = tidx % 32;\n\n    const int myX = threadIdx.y % 4;\n    const int myY = threadIdx.y / 4;\n\n    const int myOutputIdxY = blockOutputY + myY;\n    const int myOutputIdxX = blockOutputX + myX;\n    const int myOutputIdx = myOutputIdxY * outputsX + myOutputIdxX;\n\n    const int startImgPxX = startX + blockOutputX;\n    const int startImgPxY = startX + blockOutputY;\n    const int endImgPxX = startImgPxX + subsX;\n    const int endImgPxY = startImgPxY + subsX;\n\n    const int myStartImgPxY = startImgPxY + myY;\n    const int myStartImgPxX = startImgPxX + myX;\n    const int myEndImgPxY = endImgPxY + myY;\n    const int myEndImgPxX = endImgPxX + myX;\n\n    const int loopStartY = MAX(startImgPxY, 0);\n    const int loopStartX = MAX(startImgPxX, 0);\n    const int loopEndY = MIN(imgSize, endImgPxY + 3);\n    const int loopEndX = MIN(imgSize, endImgPxX + 3);\n\n    const int imgIdx = blockImgIdx + threadIdx.x;\n\n    imgs += (blockFilterIdx + loadY) * imgPixels * numImages + blockImgIdx + loadX;\n    target += (blockFilterIdx * numOutputs + myOutputIdx) * numImages + imgIdx;\n\n    float prod[filtersPerThread][imgsPerThread];\n    #pragma unroll\n    for (int f = 0; f < filtersPerThread; f++) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            prod[f][i] = agg.getBaseValue();\n        }\n    }\n    int regionSize = 0;\n    for (int y = loopStartY; y < loopEndY; y++) {\n        const bool isInY = y >= myStartImgPxY && y < myEndImgPxY ;\n        for (int x = loopStartX; x < loopEndX; x++) {\n            // Load a pixel\n            const int px = y * imgSize + x;\n            #pragma unroll\n            for (int ly = 0; ly < filtersPerThread; ly += B_X/2) {\n                if (filtersPerThread % (B_X/2) == 0 || ly + loadY < filtersPerThread) {\n                    #pragma unroll\n                    for (int lx = 0; lx < B_X*imgsPerThread; lx += 32) {\n                        if (!checkCaseBounds || lx + loadX + blockImgIdx < numImages) {\n                            shImgs[ly + loadY][lx + loadX] = imgs[(ly * imgPixels + px) * numImages + lx];\n                        }\n                    }\n                }\n            }\n            __syncthreads();\n\n            // Is this pixel in my region?\n            if (isInY && x >= myStartImgPxX && x < myEndImgPxX) {\n                #pragma unroll\n                for (int i = 0; i < imgsPerThread; i++) {\n                    if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                        #pragma unroll\n                        for (int f = 0; f < filtersPerThread; f++) {\n                            prod[f][i] = agg(prod[f][i], shImgs[f][threadIdx.x + i * B_X]);\n                        }\n                    }\n                }\n                ++regionSize;\n            }\n            __syncthreads();\n\n        }\n    }\n    if (myOutputIdxY < outputsX && myOutputIdxX < outputsX) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                #pragma unroll\n                for (int f = 0; f < filtersPerThread; f++) {\n                    target[f * numOutputs * numImages + i * B_X] = agg.output(prod[f][i], regionSize);\n                }\n            }\n        }\n    }\n}\n\n/*\n * imgs:        (numFilters, imgPixels, numImages)\n * target:      (numFilters, outputs, numImages)\n */\ntemplate<class Pooler>\nvoid convLocalPool(NVMatrix& images, NVMatrix& target, int numFilters,\n                   int subsX, int startX, int strideX, int outputsX, Pooler pooler) {\n    int numImages = images.getNumCols();\n    int imgPixels = images.getNumRows() / numFilters;\n    assert(images.getNumRows() == numFilters * imgPixels);\n    int imgSize = int(sqrt(imgPixels));\n    assert(imgSize * imgSize == imgPixels);\n\n    assert(!images.isTrans());\n    assert(!target.isTrans());\n    assert(images.isContiguous());\n//    assert(numFilters % 4 == 0);\n//    assert(numImages % 128 == 0);\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    int outputs = outputsX * outputsX;\n    target.resize(numFilters*outputs, numImages);\n\n    if (strideX == 1 && subsX >= 6 && outputsX > 1) {\n        // NOTE: this part has not been optimized for Kepler\n        int imgsPerThread = numImages % 128 == 0 ? 8 : 4;\n        int filtersPerThread = numFilters % 4 == 0 ? 4 : numFilters % 3 == 0 ? 3 : numFilters % 2 == 0 ? 2 : 1;\n        int bx = 8;\n        bool checkCaseBounds = numImages % (bx*imgsPerThread) != 0;\n        assert((imgsPerThread * bx) % 32 == 0);\n        assert(numFilters % filtersPerThread == 0);\n        dim3 threads(bx, 16);\n        dim3 blocks(DIVUP(outputsX, 4) * DIVUP(numImages, bx*imgsPerThread), DIVUP(outputsX, 4) * numFilters / filtersPerThread);\n//        printf(\"threads: %dx%d, blocks: %dx%d, imgSize: %d, numFilters: %d, numImages: %d, subsX: %d, startX: %d, outputsX: %d\\n\",\n//                threads.y, threads.x, blocks.y, blocks.x, imgSize, numFilters, numImages, subsX, startX, outputsX);\n        if (imgsPerThread == 8) {\n            if (filtersPerThread == 1) {\n                 if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 8, 1, true>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 8, 1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 8, 1, false>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 8, 1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                }\n            } else if (filtersPerThread == 2) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 8, 2, true>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 8, 2, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 8, 2, false>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 8, 2, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                }\n            } else if (filtersPerThread == 3) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 8, 3, true>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 8, 3, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 8, 3, false>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 8, 3, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                }\n            } else if (filtersPerThread == 4) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 8, 4, true>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 8, 4, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 8, 4, false>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 8, 4, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                }\n            }\n        } else if (imgsPerThread == 4) {\n            if (filtersPerThread == 1) {\n                 if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 4, 1, true>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 4, 1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 4, 1, false>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 4, 1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                }\n            } else if (filtersPerThread == 2) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 4, 2, true>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 4, 2, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 4, 2, false>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 4, 2, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                }\n            } else if (filtersPerThread == 3) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 4, 3, true>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 4, 3, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 4, 3, false>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 4, 3, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                }\n            } else if (filtersPerThread == 4) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 4, 4, true>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 4, 4, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool2<Pooler, 8, 4, 4, false>, cudaFuncCachePreferShared);\n                    kLocalPool2<Pooler, 8, 4, 4, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, outputsX, pooler);\n                }\n            }\n        }\n    } else {\n        int filtersPerThread = numFilters % 16 == 0 ? 4 : 1;\n        int imgsPerThread = numImages % 128 == 0 ? 4 : numImages % 64 == 0 ? 2 : 1;\n        bool checkCaseBounds = numImages % (32*imgsPerThread) != 0;\n        dim3 threads(32, 4);\n        dim3 blocks(DIVUP(numImages,32*imgsPerThread) * outputsX, DIVUP(numFilters, 4 * filtersPerThread) * outputsX);\n        if (imgsPerThread == 4) {\n            if (filtersPerThread == 1) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool<Pooler, 4, 32, 4, 1, true>, cudaFuncCachePreferL1);\n                    kLocalPool<Pooler, 4, 32, 4, 1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool<Pooler, 4, 32, 4, 1, false>, cudaFuncCachePreferL1);\n                    kLocalPool<Pooler, 4, 32, 4, 1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, pooler);\n                }\n            } else {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool<Pooler, 4, 32, 4, 4, true>, cudaFuncCachePreferL1);\n                    kLocalPool<Pooler, 4, 32, 4, 4, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool<Pooler, 4, 32, 4, 4, false>, cudaFuncCachePreferL1);\n                    kLocalPool<Pooler, 4, 32, 4, 4, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, pooler);\n                }\n            }\n        } else if (imgsPerThread == 2) {\n            if (filtersPerThread == 1) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool<Pooler, 4, 32, 2, 1, true>, cudaFuncCachePreferL1);\n                    kLocalPool<Pooler, 4, 32, 2, 1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool<Pooler, 4, 32, 2, 1, false>, cudaFuncCachePreferL1);\n                    kLocalPool<Pooler, 4, 32, 2, 1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, pooler);\n                }\n            } else {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool<Pooler, 4, 32, 2, 4, true>, cudaFuncCachePreferL1);\n                    kLocalPool<Pooler, 4, 32, 2, 4, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool<Pooler, 4, 32, 2, 4, false>, cudaFuncCachePreferL1);\n                    kLocalPool<Pooler, 4, 32, 2, 4, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, pooler);\n                }\n            }\n        } else {\n            if (filtersPerThread == 1) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool<Pooler, 4, 32, 1, 1, true>, cudaFuncCachePreferL1);\n                    kLocalPool<Pooler, 4, 32, 1, 1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool<Pooler, 4, 32, 1, 1, false>, cudaFuncCachePreferL1);\n                    kLocalPool<Pooler, 4, 32, 1, 1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, pooler);\n                }\n            } else {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kLocalPool<Pooler, 4, 32, 1, 4, true>, cudaFuncCachePreferL1);\n                    kLocalPool<Pooler, 4, 32, 1, 4, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, pooler);\n                } else {\n                    cudaFuncSetCacheConfig(kLocalPool<Pooler, 4, 32, 1, 4, false>, cudaFuncCachePreferL1);\n                    kLocalPool<Pooler, 4, 32, 1, 4, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                      imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, pooler);\n                }\n            }\n        }\n    }\n    getLastCudaError(\"convLocalPool: kernel execution failed\");\n}\n\n#endif\t/* CONV_UTIL_CUH */\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconv3/include/cudaconv2.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef COMMON_CUH\n#define COMMON_CUH\n\n#include <helper_cuda.h> // helper functions CUDA error checking and initialization\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n#include \"conv_util.cuh\"\n\n#include \"caffe2/core/context_gpu.h\"\n\nenum FILTER_OUTPUT_ORDER { MODULE_FILTER_IMAGE, FILTER_MODULE_IMAGE };\n\nvoid convFilterActs(\n    caffe2::CUDAContext* context,\n    caffe2::TensorCUDA* images,\n    caffe2::TensorCUDA* filters,\n    caffe2::TensorCUDA* targets,\n    int imgSizeY,\n    int numModulesY,\n    int numModulesX,\n    int paddingStart,\n    int moduleStride,\n    int numImgColors,\n    int numGroups);\nvoid convFilterActs(\n    caffe2::CUDAContext* context,\n    caffe2::TensorCUDA* images,\n    caffe2::TensorCUDA* filters,\n    caffe2::TensorCUDA* targets,\n    int imgSizeY,\n    int numModulesY,\n    int numModulesX,\n    int paddingStart,\n    int moduleStride,\n    int numImgColors,\n    int numGroups,\n    float scaleTargets,\n    float scaleOutput);\n\nvoid localFilterActs(\n    caffe2::CUDAContext* context,\n    caffe2::TensorCUDA* images,\n    caffe2::TensorCUDA* filters,\n    caffe2::TensorCUDA* targets,\n    int imgSizeY,\n    int numModulesY,\n    int numModulesX,\n    int paddingStart,\n    int moduleStride,\n    int numImgColors,\n    int numGroups);\nvoid localFilterActs(\n    caffe2::CUDAContext* context,\n    caffe2::TensorCUDA* images,\n    caffe2::TensorCUDA* filters,\n    caffe2::TensorCUDA* targets,\n    int imgSizeY,\n    int numModulesY,\n    int numModulesX,\n    int paddingStart,\n    int moduleStride,\n    int numImgColors,\n    int numGroups,\n    float scaleTargets,\n    float scaleOutput);\n\nvoid convImgActs(\n    caffe2::CUDAContext* context,\n    caffe2::TensorCUDA* hidActs,\n    caffe2::TensorCUDA* filters,\n    caffe2::TensorCUDA* targets,\n    int imgSizeY,\n    int imgSizeX,\n    int numModulesY,\n    int paddingStart,\n    int moduleStride,\n    int numImgColors,\n    int numGroups);\nvoid convImgActs(\n    caffe2::CUDAContext* context,\n    caffe2::TensorCUDA* hidActs,\n    caffe2::TensorCUDA* filters,\n    caffe2::TensorCUDA* targets,\n    int imgSizeY,\n    int imgSizeX,\n    int numModulesY,\n    int paddingStart,\n    int moduleStride,\n    int numImgColors,\n    int numGroups,\n    float scaleTargets,\n    float scaleOutput);\n\nvoid localImgActs(\n    caffe2::CUDAContext* context,\n    caffe2::TensorCUDA* hidActs,\n    caffe2::TensorCUDA* filters,\n    caffe2::TensorCUDA* targets,\n    int imgSizeY,\n    int imgSizeX,\n    int numModulesY,\n    int paddingStart,\n    int moduleStride,\n    int numImgColors,\n    int numGroups);\nvoid localImgActs(\n    caffe2::CUDAContext* context,\n    caffe2::TensorCUDA* hidActs,\n    caffe2::TensorCUDA* filters,\n    caffe2::TensorCUDA* targets,\n    int imgSizeY,\n    int imgSizeX,\n    int numModulesY,\n    int paddingStart,\n    int moduleStride,\n    int numImgColors,\n    int numGroups,\n    float scaleTargets,\n    float scaleOutput);\n\nvoid convWeightActs(\n    caffe2::CUDAContext* context,\n    caffe2::TensorCUDA* images,\n    caffe2::TensorCUDA* hidActs,\n    caffe2::TensorCUDA* targets,\n    int imgSizeY,\n    int numModulesY,\n    int numModulesX,\n    int filterSize,\n    int paddingStart,\n    int moduleStride,\n    int numImgColors,\n    int numGroups,\n    int sumWidth);\nvoid convWeightActs(\n    caffe2::CUDAContext* context,\n    caffe2::TensorCUDA* images,\n    caffe2::TensorCUDA* hidActs,\n    caffe2::TensorCUDA* targets,\n    int imgSizeY,\n    int numModulesY,\n    int numModulesX,\n    int filterSize,\n    int paddingStart,\n    int moduleStride,\n    int numImgColors,\n    int numGroups,\n    int sumWidth,\n    float scaleTargets,\n    float scaleOutput);\n\nvoid localWeightActs(\n    caffe2::CUDAContext* context,\n    caffe2::TensorCUDA* images,\n    caffe2::TensorCUDA* hidActs,\n    caffe2::TensorCUDA* targets,\n    int imgSizeY,\n    int numModulesY,\n    int numModulesX,\n    int filterSize,\n    int paddingStart,\n    int moduleStride,\n    int numImgColors,\n    int numGroups);\n\nvoid localWeightActs(\n    caffe2::CUDAContext* context,\n    caffe2::TensorCUDA* images,\n    caffe2::TensorCUDA* hidActs,\n    caffe2::TensorCUDA* targets,\n    int imgSizeY,\n    int numModulesY,\n    int numModulesX,\n    int filterSize,\n    int paddingStart,\n    int moduleStride,\n    int numImgColors,\n    int numGroups,\n    float scaleTargets,\n    float scaleOutput);\n\n#endif /* COMMON_CUH */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconv3/src/conv_util.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <assert.h>\n#include <cstring>\n#include <iostream>\n\n#include \"../../nvmatrix/include/nvmatrix_kernels.cuh\"\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n#include \"../include/conv_util.cuh\"\n\nusing namespace std;\n\n__device__ inline float square(const float a) {\n    return a * a;\n}\n\n/*\n * Horizontal reflection.\n * imgs:    (numColors, imgSize, imgSize, numCases)\n * targets: (numColors, imgSize, imgSize, numCases)\n *\n * targets should be a different array from imgs.\n *\n * Block size: (4, 32)\n * blockIdx.y * 4 + threadIdx.y determines pixel\n * blockIdx.x * 32 * imgsPerThread + threadIdx.x determines case batch\n *\n */\ntemplate<int numColors, int imgsPerThread, bool checkCaseBounds>\n__global__ void kReflectH(float * imgs, float * targets,\n                          const int imgSize, const int numCases) {\n    const int pxIdx = blockIdx.y * 4 + threadIdx.y;\n    const int imgPixels = imgSize * imgSize;\n\n    if (pxIdx < imgPixels) {\n        const int caseIdx = blockIdx.x * 32 * imgsPerThread + threadIdx.x;\n        const int pxIdxY = pxIdx / imgSize;\n        const int pxIdxX = pxIdx % imgSize;\n\n        const int pxIdxXR = imgSize - 1 - pxIdxX; // reflected coordinate\n        const int pxIdxR = pxIdxY * imgSize + pxIdxXR;\n\n        imgs += pxIdx * numCases + caseIdx;\n        targets += pxIdxR * numCases + caseIdx;\n\n#pragma unroll\n        for (int i = 0; i < imgsPerThread; ++i) {\n            if (!checkCaseBounds || caseIdx + i * 32 < numCases) {\n#pragma unroll\n                for (int c = 0; c < numColors; ++c) {\n                    targets[c * imgPixels * numCases + i * 32] = imgs[c * imgPixels * numCases + i * 32];\n                }\n            }\n        }\n    }\n}\n/*\n * Horizontal reflection.\n * imgs:    (numColors, imgSize, imgSize, numCases)\n * targets: (numColors, imgSize, imgSize, numCases)\n */\nvoid convReflectHorizontal(NVMatrix& images, NVMatrix& targets, int imgSize) {\n    int numCases = images.getNumCols();\n    int imgPixels = imgSize * imgSize;\n    int numColors = images.getNumRows() / imgPixels;\n    assert(numColors * imgPixels == images.getNumRows());\n    assert(numColors > 0 && numColors <= 3);\n\n    targets.resize(images);\n    int imgsPerThread = numCases % 128 == 0 ? 4 : numCases % 64 == 0 ? 2 : 1;\n    bool checkCaseBounds = numCases % (32 * imgsPerThread) != 0;\n    dim3 threads(32, 4);\n    dim3 blocks(DIVUP(numCases, imgsPerThread * 32), DIVUP(imgPixels, 4));\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (checkCaseBounds) {\n        if (numColors == 1) {\n            if (imgsPerThread == 1) {\n                cudaFuncSetCacheConfig(kReflectH<1, 1, true>, cudaFuncCachePreferL1);\n                kReflectH<1, 1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            } else if (imgsPerThread == 2) {\n                cudaFuncSetCacheConfig(kReflectH<1, 2, true>, cudaFuncCachePreferL1);\n                kReflectH<1, 2, true><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            } else if (imgsPerThread == 4) {\n                cudaFuncSetCacheConfig(kReflectH<1, 4, true>, cudaFuncCachePreferL1);\n                kReflectH<1, 4, true><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            }\n        } else if (numColors == 2) {\n            if (imgsPerThread == 1) {\n                cudaFuncSetCacheConfig(kReflectH<2, 1, true>, cudaFuncCachePreferL1);\n                kReflectH<2, 1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            } else if (imgsPerThread == 2) {\n                cudaFuncSetCacheConfig(kReflectH<2, 2, true>, cudaFuncCachePreferL1);\n                kReflectH<2, 2, true><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            } else if (imgsPerThread == 4) {\n                cudaFuncSetCacheConfig(kReflectH<2, 4, true>, cudaFuncCachePreferL1);\n                kReflectH<2, 4, true><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            }\n        } else if (numColors == 3) {\n            if (imgsPerThread == 1) {\n                cudaFuncSetCacheConfig(kReflectH<3, 1, true>, cudaFuncCachePreferL1);\n                kReflectH<3, 1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            } else if (imgsPerThread == 2) {\n                cudaFuncSetCacheConfig(kReflectH<3, 2, true>, cudaFuncCachePreferL1);\n                kReflectH<3, 2, true><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            } else if (imgsPerThread == 4) {\n                cudaFuncSetCacheConfig(kReflectH<3, 4, true>, cudaFuncCachePreferL1);\n                kReflectH<3, 4, true><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            }\n        }\n    } else {\n        if (numColors == 1) {\n            if (imgsPerThread == 1) {\n                cudaFuncSetCacheConfig(kReflectH<1, 1, false>, cudaFuncCachePreferL1);\n                kReflectH<1, 1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            } else if (imgsPerThread == 2) {\n                cudaFuncSetCacheConfig(kReflectH<1, 2, false>, cudaFuncCachePreferL1);\n                kReflectH<1, 2, false><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            } else if (imgsPerThread == 4) {\n                cudaFuncSetCacheConfig(kReflectH<1, 4, false>, cudaFuncCachePreferL1);\n                kReflectH<1, 4, false><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            }\n        } else if (numColors == 2) {\n            if (imgsPerThread == 1) {\n                cudaFuncSetCacheConfig(kReflectH<2, 1, false>, cudaFuncCachePreferL1);\n                kReflectH<2, 1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            } else if (imgsPerThread == 2) {\n                cudaFuncSetCacheConfig(kReflectH<2, 2, false>, cudaFuncCachePreferL1);\n                kReflectH<2, 2, false><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            } else if (imgsPerThread == 4) {\n                cudaFuncSetCacheConfig(kReflectH<2, 4, false>, cudaFuncCachePreferL1);\n                kReflectH<2, 4, false><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            }\n        } else if (numColors == 3) {\n            if (imgsPerThread == 1) {\n                cudaFuncSetCacheConfig(kReflectH<3, 1, false>, cudaFuncCachePreferL1);\n                kReflectH<3, 1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            } else if (imgsPerThread == 2) {\n                cudaFuncSetCacheConfig(kReflectH<3, 2, false>, cudaFuncCachePreferL1);\n                kReflectH<3, 2, false><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            } else if (imgsPerThread == 4) {\n                cudaFuncSetCacheConfig(kReflectH<3, 4, false>, cudaFuncCachePreferL1);\n                kReflectH<3, 4, false><<<blocks, threads, 0, stream>>>(images.getDevData(), targets.getDevData(), imgSize, numCases);\n            }\n        }\n    }\n    getLastCudaError(\"kReflectH: kernel execution failed\");\n}\n\n/*\n * blockIdx.y determines module in batches of B_Y\n * blockIdx.x determines filter in batches of B_X * filtersPerThread\n *\n * weights: (numModules, numColors, filterPixels, numFilters)\n * Not fully coalesced if B_X < 32, so use cache.\n */\ntemplate <int B_Y, int B_X, int filtersPerThread>\n__global__ void kNormalizeLCWeights(float* weights, const uint numFilters, const int numModules, const uint weightsPerFilter, const float norm) {\n    const uint moduleIdx = B_Y * blockIdx.y + threadIdx.y;\n    const uint filterIdx = B_X * blockIdx.x + threadIdx.x;\n\n    float prod[filtersPerThread];\n    #pragma unroll\n    for (uint i = 0; i < filtersPerThread; ++i) {\n        prod[i] = 0;\n    }\n    if (moduleIdx < numModules) {\n        weights += moduleIdx * weightsPerFilter * numFilters + filterIdx;\n        for (uint p = 0; p < weightsPerFilter; ++p) {\n            #pragma unroll\n            for (uint i = 0; i < filtersPerThread; ++i) {\n                prod[i] += square(weights[p * numFilters + i * B_X]);\n            }\n        }\n\n        #pragma unroll\n        for (uint i = 0; i < filtersPerThread; ++i) {\n            prod[i] = sqrtf(prod[i]);\n            prod[i] = prod[i] > norm ? __fdividef(norm, prod[i]) : 1.0f;\n        }\n\n        for (uint p = 0; p < weightsPerFilter; ++p) {\n            #pragma unroll\n            for (uint i = 0; i < filtersPerThread; ++i) {\n                weights[p * numFilters + i * B_X] *= prod[i];\n            }\n        }\n    }\n}\n\n/*\n * weights: (numModules, numColors, filterPixels, numFilters)\n */\nvoid normalizeLocalWeights(NVMatrix& weights, int numModules, float norm) {\n    int numFilters = weights.getNumCols();\n    int weightsPerFilter = weights.getNumRows() / numModules;\n    assert(numModules * weightsPerFilter == weights.getNumRows());\n\n    assert(!weights.isTrans());\n    assert(weights.isContiguous());\n    assert(numFilters % 16 == 0);\n\n    int bx = numFilters % 32 == 0 ? 32 : 16;\n    int by = bx == 32 ? 4 : 8;\n\n    int filtersPerThread = numFilters % 128 == 0 ? 4 : numFilters % 64 == 0 ? 2 : 1;\n    dim3 blocks(numFilters / (bx * filtersPerThread), DIVUP(numModules, by));\n    dim3 threads(bx, by);\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (filtersPerThread == 4) {\n        cudaFuncSetCacheConfig(kNormalizeLCWeights<4, 32, 4>, cudaFuncCachePreferL1);\n        kNormalizeLCWeights<4, 32, 4><<<blocks, threads, 0, stream>>>(weights.getDevData(), numFilters, numModules, weightsPerFilter, norm);\n    } else if (filtersPerThread == 2) {\n        cudaFuncSetCacheConfig(kNormalizeLCWeights<4, 32, 2>, cudaFuncCachePreferL1);\n        kNormalizeLCWeights<4, 32, 2><<<blocks, threads, 0, stream>>>(weights.getDevData(), numFilters, numModules, weightsPerFilter, norm);\n    } else {\n        if (numFilters % 32 == 0) {\n            cudaFuncSetCacheConfig(kNormalizeLCWeights<4, 32, 1>, cudaFuncCachePreferL1);\n            kNormalizeLCWeights<4, 32, 1><<<blocks, threads, 0, stream>>>(weights.getDevData(), numFilters, numModules, weightsPerFilter, norm);\n        } else {\n            cudaFuncSetCacheConfig(kNormalizeLCWeights<8, 16, 1>, cudaFuncCachePreferL1);\n            kNormalizeLCWeights<8, 16, 1><<<blocks, threads, 0, stream>>>(weights.getDevData(), numFilters, numModules, weightsPerFilter, norm);\n        }\n    }\n}\n\n/*\n * Block size 4x32\n * blockIdx.x determines img idx in batches of 32*imgsPerThread\n * blockIdx.y determines channel idx, pixel idx in batches of 4\n *\n * threadIdx.x determins case idx\n * threadIdx.y determines pixel idx\n *\n * imgs:    (numChannels, imgPixels, numImages) with given imgStride\n * target:  (numChannels, tgtPixels, numImages)\n */\ntemplate <int imgsPerThread, bool checkCaseBounds>\n__global__ void kCrop(float* imgs, float* target, const uint numImages, const int imgStride,\n                      const uint imgSize, const uint tgtSize, const uint startY, const uint startX) {\n    const uint imgPixels = imgSize * imgSize;\n    const uint tgtPixels = tgtSize * tgtSize;\n    const uint caseIdx = blockIdx.x * 32 * imgsPerThread + threadIdx.x;\n    const uint blockChanIdx = blockIdx.y / DIVUP(tgtPixels, 4);\n    const uint tgtPixelIdx = 4*(blockIdx.y % DIVUP(tgtPixels, 4)) + threadIdx.y;\n    const uint tgtPxY = tgtPixelIdx / tgtSize;\n    const uint tgtPxX = tgtPixelIdx % tgtSize;\n    const uint srcPixelIdx = (startY + tgtPxY) * imgSize + startX + tgtPxX;\n\n    if (tgtPixelIdx < tgtPixels) {\n        imgs += (blockChanIdx * imgPixels + srcPixelIdx) * imgStride + caseIdx;\n        target += (blockChanIdx * tgtPixels + tgtPixelIdx) * numImages + caseIdx;\n\n        #pragma unroll\n        for (uint i = 0; i < imgsPerThread; ++i) {\n            if (!checkCaseBounds || (caseIdx + 32 * i < numImages)) {\n                target[i * 32] = imgs[i * 32];\n            }\n        }\n    }\n}\n\n/*\n * Block size 4x32\n * blockIdx.y determines pixel idx in batches of 4\n * blockIdx.x determines case idx in batches of 32*imgsPerThread\n * threadIdx.y determines pixel idx\n * threadIdx.x determines case idx\n *\n * imgs:        (3, imgPixels, numImages) with given imgStride\n * target:      (3, imgPixels, numImages)\n *\n * Each thread produces (y,u,v) values for a particular (r,g,b) pixel\n *\n * The RGB --> YUV transform is (http://en.wikipedia.org/wiki/YUV):\n *\n * [Y]      [ 0.2126     0.7152      0.0722 ][R]\n * [U]  =   [-0.09991   -0.33609     0.436  ][G]\n * [V]      [ 0.615     -0.55861    -0.05639][B]\n */\ntemplate <int imgsPerThread, bool checkCaseBounds>\n__global__ void kRGBToYUV(float* imgs, float* target, const int imgPixels, const int numImages, const int imgStride) {\n    const int caseIdx = blockIdx.x * 32 * imgsPerThread + threadIdx.x;\n    const int pxIdx = blockIdx.y * 4 + threadIdx.y;\n\n    if (pxIdx < imgPixels) {\n        const int imgChannelStride = imgPixels * imgStride;\n        const int tgtChannelStride = imgPixels * numImages;\n        imgs += pxIdx * imgStride + caseIdx;\n        target += pxIdx * numImages + caseIdx;\n\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; ++i) {\n            if (!checkCaseBounds || caseIdx + i * 32 < numImages) {\n                const float R = imgs[0 * imgChannelStride + i * 32];\n                const float G = imgs[1 * imgChannelStride + i * 32];\n                const float B = imgs[2 * imgChannelStride + i * 32];\n                target[0 * tgtChannelStride + i * 32] = 0.2126f * R + 0.7152f * G + 0.0722f * B;      // Y\n                target[1 * tgtChannelStride + i * 32] = -0.09991f * R + -0.33609f * G + 0.436f * B;   // U\n                target[2 * tgtChannelStride + i * 32] = 0.615f * R + -0.55861f * G + -0.05639f * B;   // V\n            }\n        }\n    }\n}\n\n__device__ inline float labf(const float x) {\n    if (x > 0.0088564517f) {\n        return __powf(x, 0.3333f);\n    }\n    return 7.787037f * x + 0.13793103f;\n}\n\n/*\n * Block size 4x32\n * blockIdx.y determines pixel idx in batches of 4\n * blockIdx.x determines case idx in batches of 32*imgsPerThread\n * threadIdx.y determines pixel idx\n * threadIdx.x determines case idx\n *\n * imgs:        (3, imgPixels, numImages) with given imgStride\n * target:      (3, imgPixels, numImages)\n *\n * This proceeds in two steps.\n *\n * - First, RGB values are linearly transformed to XYZ as per\n *   http://en.wikipedia.org/wiki/CIE_XYZ_color_space\n * - Second, XYZ values are nonlinearly transformed to L*a*b* as per\n *   http://en.wikipedia.org/wiki/Lab_color_space#The_forward_transformation\n *\n * Each thread produces (L*,a*,b*) values for a particular (r,g,b) pixel\n *\n * The RGB --> XYZ transform is:\n *\n * [X]                  [0.49       0.31        0.2     ][R]\n * [Y]  =   5.6506753 * [0.17697    0.8124      0.01063 ][G]\n * [Z]                  [0          0.01        0.99    ][B]\n *\n * NOTE: The input should be in the range 0-1. Don't do mean-subtraction beforehand.\n *\n * Then X_max, Y_max, Z_max = 5.6506753.\n *\n * The range of the L* values is [0, 100].\n * If the center flag is given, the range will be [-50, 50].\n *\n */\ntemplate <int imgsPerThread, bool checkCaseBounds, bool center>\n__global__ void kRGBToLAB(float* imgs, float* target, const int imgPixels, const int numImages, const int imgStride) {\n    const int caseIdx = blockIdx.x * 32 * imgsPerThread + threadIdx.x;\n    const int pxIdx = blockIdx.y * 4 + threadIdx.y;\n\n    if (pxIdx < imgPixels) {\n        const int imgChannelStride = imgPixels * imgStride;\n        const int tgtChannelStride = imgPixels * numImages;\n        imgs += pxIdx * imgStride + caseIdx;\n        target += pxIdx * numImages + caseIdx;\n\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; ++i) {\n            if (!checkCaseBounds || caseIdx + i * 32 < numImages) {\n                const float R = imgs[0 * imgChannelStride + i * 32];\n                const float G = imgs[1 * imgChannelStride + i * 32];\n                const float B = imgs[2 * imgChannelStride + i * 32];\n\n                const float X = (0.49f * R + 0.31f * G + 0.2f * B);\n                const float Y = (0.17697f * R + 0.8124f * G + 0.01063f * B);\n                const float Z = (0.01f * G + 0.99f * B);\n\n                const float labX = labf(X);\n                const float labY = labf(Y);\n                const float labZ = labf(Z);\n\n                target[0 * tgtChannelStride + i * 32] = 116.0f * labY - 16.0f - (center ? 50.0f : 0);  // L*\n                target[1 * tgtChannelStride + i * 32] = 500.0f * (labX - labY); // a*\n                target[2 * tgtChannelStride + i * 32] = 200.0f * (labY - labZ); // b*\n            }\n        }\n    }\n}\n\n/*\n * Block size 16x32.\n * Each block produces a 4x4 chunk of the output image.\n * threadIdx.y determines pixel idx in 4x4 chunk.\n * threadIdx.x determines case idx.\n * blockIdx.x determines case idx in batches of 32*imgsPerThread.\n * blockIdx.y determines 4x4 chunk idx, channel idx.\n *\n * imgs:        (numChannels, imgPixels, numImages) with given imgStride\n * target:      (numChannels, tgtPixels, numImages)\n *\n * imgSize = scale * tgtSize (roughly)\n *\n * This is a rather naive kernel that relies on cache for speed. But all it's doing\n * is basic texture manipulation, which is very local in nature, so it should be ok.\n * Also, it will in practice be a tiny fraction of the runtime of a large convnet.\n *\n * So that is my justification for being lazy here.\n */\ntemplate <int imgsPerThread, bool checkCaseBounds>\n__global__ void kResizeBilinear(float* imgs, float* target, const int imgSize, const int tgtSize,\n                                const int numImages, const int imgStride, const float scale,\n                                const float centerScale) {\n    const int numChunksX = DIVUP(tgtSize, 4);\n    const int numChunks = numChunksX * numChunksX;\n    const int channelIdx = blockIdx.y / numChunks;\n    const int chunkIdx = blockIdx.y % numChunks;\n    const int chunkIdxX = chunkIdx % numChunksX;\n    const int chunkIdxY = chunkIdx / numChunksX;\n    const int caseIdx = blockIdx.x * 32 * imgsPerThread + threadIdx.x;\n    const int imgPixels = imgSize * imgSize;\n    const int tgtPixels = tgtSize * tgtSize;\n\n    const int pxX = 4 * chunkIdxX + threadIdx.y % 4;\n    const int pxY = 4 * chunkIdxY + threadIdx.y / 4;\n\n    if (pxY < tgtSize && pxX < tgtSize) {\n        const int pxIdx = pxY * tgtSize + pxX;\n\n        imgs += channelIdx * imgPixels * imgStride + caseIdx;\n        target += channelIdx * tgtPixels * numImages + pxIdx * numImages + caseIdx;\n\n        // This will cause slight distortions at the edges when upsampling in some cases.\n        // But I think that's not a big deal.\n        const float srcPxX = fmaxf(0.0f, fminf(__int2float_rn(imgSize) - 1.01f, __int2float_rn(pxX) * scale + centerScale));\n        const float srcPxY = fmaxf(0.0f, fminf(__int2float_rn(imgSize) - 1.01f, __int2float_rn(pxY) * scale + centerScale));\n\n        const float u = floorf(srcPxX + 1) - srcPxX;\n        const float w = srcPxY - floorf(srcPxY);\n\n        // Consider doing max(0, min(imgSize, x)) here\n        const int srcPx0 = (__float2int_rd(srcPxY) * imgSize + __float2int_rd(srcPxX)); // top-left\n        const int srcPx1 = srcPx0 + 1; // top-right\n        const int srcPx2 = srcPx0 + imgSize; // bottom-left\n        const int srcPx3 = srcPx2 + 1; // bottom-right\n\n        #pragma unroll\n        for (int c = 0; c < imgsPerThread; ++c) {\n            if (!checkCaseBounds || caseIdx + c * 32 < numImages) {\n                const float val0 = imgs[srcPx0 * imgStride + c * 32];\n                const float val1 = imgs[srcPx1 * imgStride + c * 32];\n                const float val2 = imgs[srcPx2 * imgStride + c * 32];\n                const float val3 = imgs[srcPx3 * imgStride + c * 32];\n\n                const float c0 = u * (val0 - val1) + val1;\n                const float c1 = u * (val2 - val3) + val3;\n\n                target[32 * c] = w * (c1 - c0) + c0;\n            }\n        }\n    }\n}\n\n/*\n * Block size B_YxB_X.\n * B_X*imgsPerThread*blockIdx.x + threadIdx.x determines img idx\n * B_Y*blockIdx.y + threadIdx.y determines img row (col if !horiz), channel idx\n *\n * imgs:        (numChannels, imgPixels, numImages) with given imgStride\n * filter:      (1, 2*radius + 1)\n * target:      (numChannels, imgPixels, numImages)\n *\n * target can be the same matrix as imgs.\n * radius must be one of 3, 5, 7, 9.\n *\n * Tried imgsPerThread, slower.\n */\ntemplate<int B_Y, int B_X, int radius>\n__global__ void kGaussianBlur(float* imgs, float* filter, float* target, const int imgSize,\n                              const int numImages, const int imgStride, const int numChannels,\n                              const bool horiz,\n                              const float scaleTargets, const float scaleOutputs) {\n    const int filterWidth = 2*radius+1;\n    __shared__ float shFilter[filterWidth-1];\n\n    const int imgPixels = imgSize * imgSize;\n    const int ty = B_Y * blockIdx.y + threadIdx.y;\n    const int channelIdx = ty / imgSize;\n    const int rowIdx = ty % imgSize;\n    const int imgIdx = B_X*blockIdx.x + threadIdx.x;\n\n//    const int tidx = B_Y * threadIdx.y + threadIdx.x;\n    if (horiz) {\n        imgs += channelIdx * imgPixels * imgStride + rowIdx * imgSize * imgStride + imgIdx;\n        target += channelIdx * imgPixels * numImages + rowIdx * imgSize * numImages + imgIdx;\n    } else {\n        imgs += channelIdx * imgPixels * imgStride + rowIdx * imgStride + imgIdx;\n        target += channelIdx * imgPixels * numImages + rowIdx * numImages + imgIdx;\n    }\n    float outputs[filterWidth-1];\n    #pragma unroll\n    for (int r = 0; r < filterWidth-1; r++) {\n        outputs[r] = 0;\n    }\n    if (threadIdx.x < filterWidth-1) {\n        shFilter[threadIdx.x] = filter[threadIdx.x];\n    }\n    __syncthreads();\n\n    if (imgIdx < numImages && channelIdx < numChannels) {\n        // This writes radius*2 = filterWidth - 1 values to outputs\n        #pragma unroll\n        for (int col = 0; col < radius; col++) {\n            float px = imgs[0];\n            #pragma unroll\n            for (int r = 0; r < radius + 1 + col; r++) {\n                outputs[r] += px * shFilter[radius + col - r];\n            }\n            imgs += horiz ? imgStride : imgStride * imgSize;\n        }\n\n        // Unfortunately this has to be at this level of granularity\n        if (scaleTargets != 0) {\n            for (int col = radius; col < imgSize ; col++) { // loop over img columns\n                float px = imgs[0];\n                target[0] = scaleTargets * target[0] + scaleOutputs * (outputs[0] + px * shFilter[0]);\n\n                #pragma unroll\n                for (int r = 1; r < radius*2; r++) {\n                    outputs[r-1] = outputs[r] + px * shFilter[r];\n                }\n                outputs[filterWidth - 2] = px * shFilter[0];\n\n                imgs += horiz ? imgStride : imgStride * imgSize;\n                target += horiz ? numImages : numImages * imgSize;\n            }\n\n            #pragma unroll\n            for (int r = 0; r < radius; r++) {\n                float* t = &target[0];\n                t[0] = scaleTargets * t[0] + scaleOutputs * outputs[r];\n                target += horiz ? numImages : numImages * imgSize;\n            }\n        } else {\n            for (int col = radius; col < imgSize ; col++) { // loop over img columns\n                float px = imgs[0];\n                target[0] = scaleOutputs * (outputs[0] + px * shFilter[0]);\n                #pragma unroll\n                for (int r = 1; r < radius*2; r++) {\n                    outputs[r-1] = outputs[r] + px * shFilter[r];\n                }\n                outputs[filterWidth - 2] = px * shFilter[0];\n\n                imgs += horiz ? imgStride : imgStride * imgSize;\n                target += horiz ? numImages : numImages * imgSize;\n            }\n\n            #pragma unroll\n            for (int r = 0; r < radius; r++) {\n                target[0] = scaleOutputs * outputs[r];\n                target += horiz ? numImages : numImages * imgSize;\n            }\n        }\n    }\n}\n\n/*\n * Block size B_YxB_X\n * blockIdx.x determines output.x, image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines output.y, filter idx in batches of B_Y*filtersPerThread\n *\n * So each block does one output for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines filter idx\n *\n * imgs:        (numChannels, imgPixels, numImages)\n * target:      (numChannels, numOutputs, numImages)\n *\n * numImages must be divisible by B_X*imgsPerThread if checkCaseBounds is false\n * numFilters must be divisible by filtersPerThread\n */\n\ntemplate<int B_Y, int B_X, int imgsPerThread, int chansPerThread, bool checkCaseBounds>\n__global__ void kBedOfNails(float* imgs, float* target, const int imgSize, const int numChannels,\n                           const int numImages, const int startX, const int strideX, const int outputsX,\n                           const bool reverse, const float scaleTargets, const float scaleOutput) {\n    const int numImgBlocks = DIVUP(numImages,B_X*imgsPerThread);\n    const int numChanBlocks = DIVUP(numChannels, B_Y*chansPerThread);\n    const int outputIdxX = blockIdx.x / numImgBlocks;\n    const int outputIdxY = blockIdx.y / numChanBlocks;\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * B_X * imgsPerThread;\n    const int blockChanIdx = (blockIdx.y % numChanBlocks) * B_Y * chansPerThread;\n    const int myChanIdx = (blockChanIdx + threadIdx.y*chansPerThread);\n    if (myChanIdx >= numChannels) {\n        return;\n    }\n//    if (blockIdx.x != 0 || blockIdx.y != 0) {\n//        return;\n//    }\n    const int outputIdx = outputIdxY * outputsX + outputIdxX;\n    const int numOutputs = outputsX * outputsX;\n    const int imgPixels = imgSize * imgSize;\n\n    const int startImgPxX = startX + outputIdxX * strideX;\n    const int startImgPxY = startX + outputIdxY * strideX;\n    const int imgIdx = blockImgIdx + threadIdx.x;\n    const int imgPx = startImgPxY * imgSize + startImgPxX;\n\n    imgs += myChanIdx * imgPixels * numImages + imgPx * numImages + imgIdx;\n    target += (myChanIdx * numOutputs + outputIdx) * numImages + imgIdx;\n\n    if (scaleTargets != 0) {\n        if (!reverse) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                    #pragma unroll\n                    for (int c = 0; c < chansPerThread; c++) {\n                        target[c * numOutputs * numImages + i * B_X] = scaleTargets * target[c * numOutputs * numImages + i * B_X] + scaleOutput * imgs[c * imgPixels * numImages + i * B_X];\n                    }\n                }\n            }\n        } else {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                    #pragma unroll\n                    for (int c = 0; c < chansPerThread; c++) {\n                        imgs[c * imgPixels * numImages + i * B_X] = scaleTargets * imgs[c * imgPixels * numImages + i * B_X] + scaleOutput * target[c * numOutputs * numImages + i * B_X];\n                    }\n                }\n            }\n        }\n    } else {\n        if (!reverse) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                    #pragma unroll\n                    for (int c = 0; c < chansPerThread; c++) {\n                        target[c * numOutputs * numImages + i * B_X] = scaleOutput * imgs[c * imgPixels * numImages + i * B_X];\n                    }\n                }\n            }\n        } else {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                    #pragma unroll\n                    for (int c = 0; c < chansPerThread; c++) {\n                        imgs[c * imgPixels * numImages + i * B_X] = scaleOutput * target[c * numOutputs * numImages + i * B_X];\n                    }\n                }\n            }\n        }\n    }\n\n}\n\n/*\n * imgs:        (numChannels, imgPixels, numImages)\n * target:      (numChannels, outputs, numImages)\n */\nvoid _convBedOfNails(NVMatrix& images, NVMatrix& target, int numChannels, int imgSize, int startX, int strideX,\n                     bool reverse, float scaleTargets, float scaleOutput) {\n    int numImages = reverse ? target.getNumCols() : images.getNumCols();\n    int imgPixels = imgSize * imgSize;\n\n    assert(!images.isTrans());\n    assert(!target.isTrans());\n    assert(images.isContiguous());\n    assert(target.isContiguous());\n    assert(strideX > 1);\n\n    int outputsX = DIVUP(imgSize, strideX);\n    int outputs = outputsX * outputsX;\n    if (reverse) {\n        assert(target.getNumRows() == numChannels * outputs);\n    } else  {\n        assert(images.getNumRows() == numChannels * imgPixels);\n    }\n\n    if (scaleTargets == 0) {\n        if (reverse) {\n            images.resize(numChannels * imgPixels, numImages);\n            images.apply(NVMatrixOps::Zero());\n        } else {\n            target.resize(numChannels*outputs, numImages);\n        }\n    } else {\n        if (reverse) {\n            assert(images.getNumRows() == numChannels * outputs);\n            assert(images.getNumCols() == numImages);\n        } else {\n            assert(target.getNumRows() == numChannels * outputs);\n            assert(target.getNumCols() == numImages);\n        }\n    }\n\n\n    int imgsPerThread = numImages % 128 == 0 ? 4 : numImages % 64 == 0 ? 2 : 1;\n    bool checkCaseBounds = numImages % (32*imgsPerThread) != 0;\n    int chansPerThread = numChannels % 8 == 0 ? 2 : 1;\n    dim3 threads(32, 4);\n    dim3 blocks(DIVUP(numImages,32*imgsPerThread) * outputsX, DIVUP(numChannels, 4 * chansPerThread) * outputsX);\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (imgsPerThread == 4) {\n        if (chansPerThread == 1) {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kBedOfNails<4, 32, 4, 1, true>, cudaFuncCachePreferL1);\n                kBedOfNails<4, 32, 4, 1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                    imgSize, numChannels, numImages, startX, strideX, outputsX,\n                                                                    reverse, scaleTargets, scaleOutput);\n            } else {\n                cudaFuncSetCacheConfig(kBedOfNails<4, 32, 4, 1, false>, cudaFuncCachePreferL1);\n                kBedOfNails<4, 32, 4, 1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                     imgSize, numChannels, numImages, startX, strideX, outputsX,\n                                                                     reverse, scaleTargets, scaleOutput);\n            }\n        } else {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kBedOfNails<4, 32, 4, 2, true>, cudaFuncCachePreferL1);\n                kBedOfNails<4, 32, 4, 2, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                    imgSize, numChannels, numImages, startX, strideX, outputsX,\n                                                                    reverse, scaleTargets, scaleOutput);\n            } else {\n                cudaFuncSetCacheConfig(kBedOfNails<4, 32, 4, 2, false>, cudaFuncCachePreferL1);\n                kBedOfNails<4, 32, 4, 2, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                     imgSize, numChannels, numImages, startX, strideX, outputsX,\n                                                                     reverse, scaleTargets, scaleOutput);\n            }\n        }\n    } else if (imgsPerThread == 2) {\n        if (chansPerThread == 1) {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kBedOfNails<4, 32, 2, 1, true>, cudaFuncCachePreferL1);\n                kBedOfNails<4, 32, 2, 1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                    imgSize, numChannels, numImages, startX, strideX, outputsX,\n                                                                    reverse, scaleTargets, scaleOutput);\n            } else {\n                cudaFuncSetCacheConfig(kBedOfNails<4, 32, 2, 1, false>, cudaFuncCachePreferL1);\n                kBedOfNails<4, 32, 2, 1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                     imgSize, numChannels, numImages, startX, strideX, outputsX,\n                                                                     reverse, scaleTargets, scaleOutput);\n            }\n        } else {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kBedOfNails<4, 32, 2, 2, true>, cudaFuncCachePreferL1);\n                kBedOfNails<4, 32, 2, 2, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                    imgSize, numChannels, numImages, startX, strideX, outputsX,\n                                                                    reverse, scaleTargets, scaleOutput);\n            } else {\n                cudaFuncSetCacheConfig(kBedOfNails<4, 32, 2, 2, false>, cudaFuncCachePreferL1);\n                kBedOfNails<4, 32, 2, 2, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                     imgSize, numChannels, numImages, startX, strideX, outputsX,\n                                                                     reverse, scaleTargets, scaleOutput);\n            }\n        }\n    } else {\n        if (chansPerThread == 1) {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kBedOfNails<4, 32, 1, 1, true>, cudaFuncCachePreferL1);\n                kBedOfNails<4, 32, 1, 1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                    imgSize, numChannels, numImages, startX, strideX, outputsX,\n                                                                    reverse, scaleTargets, scaleOutput);\n            } else {\n                cudaFuncSetCacheConfig(kBedOfNails<4, 32, 1, 1, false>, cudaFuncCachePreferL1);\n                kBedOfNails<4, 32, 1, 1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                     imgSize, numChannels, numImages, startX, strideX, outputsX,\n                                                                     reverse, scaleTargets, scaleOutput);\n            }\n        } else {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kBedOfNails<4, 32, 1, 2, true>, cudaFuncCachePreferL1);\n                kBedOfNails<4, 32, 1, 2, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                    imgSize, numChannels, numImages, startX, strideX, outputsX,\n                                                                    reverse, scaleTargets, scaleOutput);\n            } else {\n                cudaFuncSetCacheConfig(kBedOfNails<4, 32, 1, 2, false>, cudaFuncCachePreferL1);\n                kBedOfNails<4, 32, 1, 2, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(),\n                                                                     imgSize, numChannels, numImages, startX, strideX, outputsX,\n                                                                     reverse, scaleTargets, scaleOutput);\n            }\n        }\n    }\n}\n\nvoid convBedOfNails(NVMatrix& images, NVMatrix& target, int numChannels, int imgSize, int startX,\n                    int strideX, float scaleTargets, float scaleOutput) {\n    _convBedOfNails(images, target, numChannels, imgSize, startX, strideX, false, scaleTargets, scaleOutput);\n}\n\nvoid convBedOfNailsUndo(NVMatrix& actsGrad, NVMatrix& target, int numChannels, int imgSize,\n                        int startX, int strideX, float scaleTargets, float scaleOutput) {\n\n    _convBedOfNails(target, actsGrad, numChannels, imgSize, startX, strideX, true, scaleTargets, scaleOutput);\n}\n\n\n/*\n * imgs:        (numChannels, imgPixels, numImages) with given imgStride\n * filter:      (1, 2*radius + 1)\n * target:      (numChannels, imgPixels, numImages)\n */\nvoid convGaussianBlur(NVMatrix& images, NVMatrix& filter, NVMatrix& target, bool horiz, int numChannels,\n                      float scaleTargets, float scaleOutputs) {\n    int numImages = images.getNumCols();\n    int radius = filter.getNumCols() / 2;\n    int imgPixels = images.getNumRows() / numChannels;\n    int imgSize = int(sqrt(imgPixels));\n\n    assert(imgPixels == imgSize * imgSize);\n    assert(radius >= 1 && radius <= 4);\n    assert(imgSize >= 2 * radius + 1);\n    assert(filter.getNumRows() == 1);\n    assert(images.getNumRows() == numChannels * imgPixels);\n    assert(!images.isTrans());\n    assert(!filter.isTrans());\n    assert(!target.isTrans());\n    assert(target.isContiguous());\n    if (scaleTargets == 0) {\n        target.resize(images);\n    } else {\n        assert(target.isSameDims(images));\n    }\n\n    dim3 threads(32, 4);\n    dim3 blocks(DIVUP(numImages, threads.x), DIVUP(numChannels*imgSize, threads.y));\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (radius == 1) {\n        cudaFuncSetCacheConfig(kGaussianBlur<4, 32, 1>, cudaFuncCachePreferL1);\n        kGaussianBlur<4, 32, 1><<<blocks, threads, 0, stream>>>(images.getDevData(), filter.getDevData(), target.getDevData(),\n                                                           imgSize, numImages, images.getStride(), numChannels, horiz, scaleTargets, scaleOutputs);\n\n    } else if (radius == 2) {\n        cudaFuncSetCacheConfig(kGaussianBlur<4, 32, 2>, cudaFuncCachePreferL1);\n        kGaussianBlur<4, 32, 2><<<blocks, threads, 0, stream>>>(images.getDevData(), filter.getDevData(), target.getDevData(),\n                                                           imgSize, numImages, images.getStride(), numChannels,horiz, scaleTargets, scaleOutputs);\n\n    } else if (radius == 3) {\n        cudaFuncSetCacheConfig(kGaussianBlur<4, 32, 3>, cudaFuncCachePreferL1);\n        kGaussianBlur<4, 32, 3><<<blocks, threads, 0, stream>>>(images.getDevData(), filter.getDevData(), target.getDevData(),\n                                                           imgSize, numImages, images.getStride(), numChannels,horiz, scaleTargets, scaleOutputs);\n    } else if (radius == 4) {\n        cudaFuncSetCacheConfig(kGaussianBlur<4, 32, 4>, cudaFuncCachePreferL1);\n        kGaussianBlur<4, 32, 4><<<blocks, threads, 0, stream>>>(images.getDevData(), filter.getDevData(), target.getDevData(),\n                                                           imgSize, numImages, images.getStride(), numChannels,horiz, scaleTargets, scaleOutputs);\n    }\n}\n\n/*\n * Block size 1x128\n * blockIdx.x determines pixel.x, image idx in batches of 128*imgsPerThread\n * blockIdx.y determines pixel.y\n *\n * So each block does one output for some number of images and all the fliters.\n *\n * threadIdx.x determines img idx\n *\n * imgs:        (numFilters, imgPixels, numImages)\n * meanDiffs:   (numFilters, imgPixels, numImages)\n * denoms:      (numFilters, imgPixels, numImages) (out)\n * target:      (numFilters, imgPixels, numImages) (out)\n *\n * numImages must be divisible by B_X*imgsPerThread if checkCaseBounds is false\n * numFilters must be divisible by B_Y*filtersPerThread\n */\n\ntemplate<int imgsPerThread, int numFilters, bool checkCaseBounds>\n__global__ void kCNorm_fewfilter(float* imgs, float* meanDiffs, float* denoms, float* target, const int imgSize,\n                                  const int numImages, const int sizeX, const float addScale, const float powScale, const float minDiv) {\n\n    const int imgPixels = imgSize * imgSize;\n    const int numImgBlocks = DIVUP(numImages, 128*imgsPerThread);\n    const int pxIdxX = blockIdx.x / numImgBlocks;\n    const int pxIdxY = blockIdx.y;\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * 128 * imgsPerThread;\n\n    const int pxIdx = pxIdxY * imgSize + pxIdxX;\n\n    const int startPxX = -sizeX/2 + pxIdxX;\n    const int startPxY = -sizeX/2 + pxIdxY;\n    const int imgIdx = blockImgIdx + threadIdx.x;\n\n    imgs += pxIdx * numImages + imgIdx;\n    denoms += pxIdx * numImages + imgIdx;\n    meanDiffs  += imgIdx;\n    target += pxIdx * numImages + imgIdx;\n\n    float prod[numFilters][imgsPerThread];\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        if (!checkCaseBounds || imgIdx + i * 128 < numImages) {\n            #pragma unroll\n            for (int f = 0; f < numFilters; f++) {\n                prod[f][i] = 0;\n            }\n        }\n    }\n    const int loopStartY = MAX(0, startPxY);\n    const int loopStartX = MAX(0, startPxX);\n    const int loopEndY = MIN(imgSize, startPxY + sizeX);\n    const int loopEndX = MIN(imgSize, startPxX + sizeX);\n\n    for (int y = loopStartY; y < loopEndY; y++) {\n        for (int x = loopStartX; x < loopEndX; x++) {\n            const int imgPx = y * imgSize + x;\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || imgIdx + i * 128 < numImages) {\n                    #pragma unroll\n                    for (int f = 0; f < numFilters; f++) {\n                        prod[f][i] += square(meanDiffs[(f * imgPixels + imgPx) * numImages + i * 128]);\n                    }\n                }\n            }\n        }\n    }\n\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        if (!checkCaseBounds || imgIdx + i * 128 < numImages) {\n            #pragma unroll\n            for (int f = 0; f < numFilters; f++) {\n                prod[f][i] = minDiv + addScale * prod[f][i];\n                denoms[f * imgPixels * numImages + i * 128] = prod[f][i];\n                target[f * imgPixels * numImages + i * 128] = imgs[f * imgPixels * numImages + i * 128] * __powf(prod[f][i], -powScale);\n            }\n        }\n    }\n}\n\n/*\n * Block size B_YxB_X\n * blockIdx.x determines image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines filter idx in batches of B_Y*filtersPerThread\n * blockIdx.z determines pixel\n *\n * So each block does one pixel for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines filter idx\n *\n * imgs:        (numFilters, imgPixels, numImages)\n * means:       (numFilters, imgPixels, numImages)\n * denoms:      (numFilters, imgPixels, numImages) (out)\n * target:      (numFilters, imgPixels, numImages) (out)\n *\n * numImages must be divisible by B_X*imgsPerThread if checkCaseBounds is false\n * numFilters must be divisible by B_Y*filtersPerThread\n */\ntemplate<int B_Y, int B_X, int imgsPerThread, int filtersPerThread, bool checkCaseBounds>\n__global__ void kCNorm_manyfilter(float* imgs, float* meanDiffs, float* denoms, float* target, const int imgSize,\n                                  const int numFilters, const int numImages, const int sizeX,\n                                  const float addScale, const float powScale, const float minDiv) {\n    const int imgPixels = imgSize * imgSize;\n\n    const int pxIdxX = blockIdx.z % imgSize;\n    const int pxIdxY = blockIdx.z / imgSize;\n    const int blockImgIdx = blockIdx.x * B_X * imgsPerThread;\n    const int blockFilterIdx = blockIdx.y * B_Y * filtersPerThread;\n\n    const int pxIdx = pxIdxY * imgSize + pxIdxX;\n\n    const int startPxX = -sizeX/2 + pxIdxX;\n    const int startPxY = -sizeX/2 + pxIdxY;\n    const int imgIdx = blockImgIdx + threadIdx.x;\n    imgs += ((blockFilterIdx + threadIdx.y) * imgPixels + pxIdx) * numImages + imgIdx;\n    meanDiffs += (blockFilterIdx + threadIdx.y) * imgPixels * numImages + imgIdx;\n    denoms += ((blockFilterIdx + threadIdx.y) * imgPixels + pxIdx) * numImages + imgIdx;\n    target += ((blockFilterIdx + threadIdx.y) * imgPixels + pxIdx) * numImages + imgIdx;\n\n    float prod[filtersPerThread][imgsPerThread];\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                prod[f][i] = 0;\n            }\n        }\n    }\n\n    const int loopStartY = max(0, startPxY);\n    const int loopStartX = max(0, startPxX);\n    const int loopEndY = min(imgSize, startPxY + sizeX);\n    const int loopEndX = min(imgSize, startPxX + sizeX);\n\n    for (int y = loopStartY; y < loopEndY; y++) {\n        for (int x = loopStartX; x < loopEndX; x++) {\n            const int imgPx = y * imgSize + x;\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                #pragma unroll\n                for (int i = 0; i < imgsPerThread; i++) {\n                    if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                        prod[f][i] += square(meanDiffs[(f * B_Y * imgPixels + imgPx) * numImages + i * B_X]);\n                    }\n                }\n            }\n        }\n    }\n    #pragma unroll\n    for (int f = 0; f < filtersPerThread; f++) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                prod[f][i] = minDiv + addScale * prod[f][i];\n                denoms[f * B_Y * imgPixels * numImages + i * B_X] = prod[f][i];\n                target[f * B_Y * imgPixels * numImages + i * B_X] = imgs[f * B_Y * imgPixels * numImages + i * B_X] * __powf(prod[f][i], -powScale);\n            }\n        }\n    }\n}\n\n\n/*\n * Block size 16xB_X\n * blockIdx.x determines 4x4 pixel.x region, image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines 4x4 pixel.y region, filter idx in batches of filtersPerThread\n *\n * So each block does 4x4 region of pixels for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines pixel idx\n *\n * imgs:        (numFilters, imgPixels, numImages)\n * means:       (numFilters, imgPixels, numImages)\n * denoms:      (numFilters, imgPixels, numImages) (out)\n * target:      (numFilters, imgPixels, numImages) (out)\n *\n * B_X one of 8, 16, 32\n * imgsPerThread one of 1, 2, 4, 8, 16\n *\n * B_XximgsPerThread MUST be divisible by 32.\n * Number of filters MUST be divisible by filtersPerThread.\n *\n * numImages must be divisible by B_X*imgsPerThread if checkCaseBounds is false\n * numFilters must be divisible by filtersPerThread\n *\n * Final write-out will not be fully coalesced unless B_X is 32. But there's a lot more\n * reading than writing here, and the reading is all coalesced, so it should be OK.\n */\ntemplate<int B_X, int imgsPerThread, int filtersPerThread, bool checkCaseBounds>\n__global__ void kCNorm2(float* imgs, float* meanDiffs, float* denoms, float* target, const int imgSize,\n                         const int numFilters, const int numImages, const int sizeX, const float addScale, const float powScale, const float minDiv) {\n    __shared__ float shDiffs[filtersPerThread][B_X*imgsPerThread];\n    const int imgPixels = imgSize * imgSize;\n    const int numImgBlocks = DIVUP(numImages, B_X*imgsPerThread);\n    const int numFilterBlocks = numFilters/(filtersPerThread);\n    const int blockPxX = 4*(blockIdx.x / numImgBlocks);\n    const int blockPxY = 4*(blockIdx.y / numFilterBlocks);\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * B_X * imgsPerThread;\n    const int blockFilterIdx = (blockIdx.y % numFilterBlocks) * filtersPerThread;\n\n    const int tidx = threadIdx.y * B_X + threadIdx.x;\n    const int loadY = tidx / 32, loadX = tidx % 32;\n\n    const int startPxX = MAX(0, -sizeX/2 + blockPxX);\n    const int startPxY = MAX(0, -sizeX/2 + blockPxY);\n    const int endPxX = MIN(imgSize, blockPxX + DIVUP(sizeX, 2) + 3);\n    const int endPxY = MIN(imgSize, blockPxY + DIVUP(sizeX, 2) + 3);\n\n    const int myPxX = blockPxX + threadIdx.y % 4;\n    const int myPxY = blockPxY + threadIdx.y / 4;\n    const int myPxIdx = myPxY * imgSize + myPxX;\n//    const bool doWork = myPxX < imgSize && myPxY < imgSize;\n    const int myStartPxY = -sizeX/2 + myPxY;\n    const int myStartPxX = -sizeX/2 + myPxX;\n    const int myEndPxY = myPxY + DIVUP(sizeX, 2);\n    const int myEndPxX = myPxX + DIVUP(sizeX, 2);\n\n    const int imgIdx = blockImgIdx + threadIdx.x;\n\n    imgs        += (blockFilterIdx * imgPixels + myPxIdx) * numImages + imgIdx;\n    meanDiffs   += (blockFilterIdx + loadY) * imgPixels * numImages + blockImgIdx + loadX;\n    denoms      += (blockFilterIdx * imgPixels + myPxIdx) * numImages + imgIdx;\n    target      += (blockFilterIdx * imgPixels + myPxIdx) * numImages + imgIdx;\n\n    float prod[filtersPerThread][imgsPerThread];\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                prod[f][i] = 0;\n            }\n        }\n    }\n\n    for (int y = startPxY; y < endPxY; y++) {\n        const bool isInY = y >= myStartPxY && y < myEndPxY;\n        for (int x = startPxX; x < endPxX; x++) {\n            const int px = y * imgSize + x;\n            // All the threads load a pixel from memory\n            #pragma unroll\n            for (int ly = 0; ly < filtersPerThread; ly += B_X/2) {\n                if (filtersPerThread % (B_X/2) == 0 || ly + loadY < filtersPerThread) {\n                    #pragma unroll\n                    for (int lx = 0; lx < B_X*imgsPerThread; lx += 32) {\n                        if (!checkCaseBounds || lx + loadX + blockImgIdx < numImages) {\n                            shDiffs[ly + loadY][lx + loadX] = meanDiffs[(ly * imgPixels + px) * numImages + lx];\n                        }\n                    }\n                }\n            }\n            __syncthreads();\n\n            // Each row of threads decides if it's interested in this pixel\n            if (isInY && x >= myStartPxX && x < myEndPxX) {\n                #pragma unroll\n                for (int i = 0; i < imgsPerThread; i++) {\n                    if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                        #pragma unroll\n                        for (int f = 0; f < filtersPerThread; f++) {\n                            prod[f][i] += square(shDiffs[f][threadIdx.x + i * B_X]);\n                        }\n                    }\n                }\n            }\n            __syncthreads();\n        }\n    }\n//    imgs -= (loadY * imgPixels - myPxIdx) * numImages + loadX;\n//    imgs += threadIdx.x;\n    if (myPxX < imgSize && myPxY < imgSize) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                #pragma unroll\n                for (int f = 0; f < filtersPerThread; f++) {\n                    prod[f][i] = minDiv + addScale * prod[f][i];\n                    denoms[f * imgPixels * numImages + i * B_X] = prod[f][i];\n                    target[f * imgPixels * numImages + i * B_X] = imgs[f * imgPixels * numImages + i * B_X] * __powf(prod[f][i], -powScale);\n                }\n            }\n        }\n    }\n}\n\n/*\n * Block size B_YxB_X\n * blockIdx.x determines pixel.x, image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines pixel.y, filter idx in batches of B_Y\n *\n * So each block does one pixel for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines filter idx\n *\n * imgs:        (numFilters, imgPixels, numImages)\n * meanDiffs:   (numFilters, imgPixels, numImages)\n * denoms:      (numFilters, imgPixels, numImages) (out)\n * target:      (numFilters, imgPixels, numImages) (out)\n *\n * numImages must be divisible by B_X*imgsPerThread if checkCaseBounds is false\n * numFilters must be divisible by B_Y\n */\ntemplate<int B_Y, int B_X, int imgsPerThread, bool checkCaseBounds, bool blocked>\n__global__ void kFCNorm(cudaTextureObject_t imgs, cudaTextureObject_t meanDiffs, float* target, const int imgSize,\n                          const int numFilters, const int numImages, const int sizeF,\n                          const float addScale, const float powScale, const float minDiv) {\n    const int imgPixels = imgSize * imgSize;\n    const int numImgBlocks = DIVUP(numImages, B_X*imgsPerThread);\n    const int numFilterBlocks = numFilters/B_Y;\n    const int pxIdxX = blockIdx.x / numImgBlocks;\n    const int pxIdxY = blockIdx.y / numFilterBlocks;\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * B_X * imgsPerThread;\n    const int filterIdx = (blockIdx.y % numFilterBlocks) * B_Y + threadIdx.y;\n\n    const int pxIdx = pxIdxY * imgSize + pxIdxX;\n\n\n    const int imgIdx = blockImgIdx + threadIdx.x;\n    const int imgOffset = ((filterIdx) * imgPixels + pxIdx) * numImages + imgIdx;\n    const int meanDiffsOffset = pxIdx * numImages + imgIdx;\n//    imgs += ((filterIdx) * imgPixels + pxIdx) * numImages + imgIdx;\n//    meanDiffs += pxIdx * numImages + imgIdx;\n    target += ((filterIdx) * imgPixels + pxIdx) * numImages + imgIdx;\n\n    float prod[imgsPerThread];\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n            prod[i] = 0;\n        }\n    }\n\n    const int startF = blocked ? (filterIdx / sizeF) * sizeF : -sizeF/2 + filterIdx;\n    const int loopStartF = blocked ? startF : MAX(0, startF);\n    const int loopEndF = MIN(numFilters, startF + sizeF);\n\n    for (int f = loopStartF; f < loopEndF; ++f) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                prod[i] += square(tex1Dfetch<float>(meanDiffs, meanDiffsOffset + f * imgPixels * numImages + i * B_X));\n            }\n        }\n    }\n\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n            prod[i] = minDiv + addScale * prod[i];\n            target[i * B_X] = tex1Dfetch<float>(imgs, imgOffset + i * B_X) * __powf(prod[i], -powScale);\n        }\n    }\n}\n\n/*\n * Block size B_YxB_X\n * blockIdx.x determines pixel.x, image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines pixel.y, filter idx in batches of B_Y\n *\n * So each block does one output pixel for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines filter idx\n *\n * imgs:                (numFilters, imgPixels, numImages)\n * maxGrads:            (numOutputs, imgPixels, numImages)\n * maxActs:             (numOutputs, imgPixels, numImages)\n * target:              (numFilters, imgPixels, numImages)\n *\n * numImages must be divisible by B_X*imgsPerThread\n * numFilters must be divisible by B_Y\n *\n * TODO: this isn't really ideal\n */\ntemplate<int B_Y, int B_X, int imgsPerThread, bool add, bool checkCaseBounds>\n__global__ void kCrossMapMaxPoolUndo(float* imgs, float* maxGrads, float* maxActs, float* target, const int imgSize, const int numFilters,\n                                     const int numImages, const int startF, const int poolSize,\n                                     const int numOutputs, const int stride, const float scaleTargets, const float scaleOutputs) {\n    const int numImgBlocks = DIVUP(numImages,B_X*imgsPerThread);\n//    const int numOutputs = DIVUP(numFilters, stride);\n    const int numFilterBlocks = numFilters/B_Y;\n\n    const int pxIdxX = blockIdx.x / numImgBlocks;\n    const int pxIdxY = blockIdx.y / numFilterBlocks;\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * B_X * imgsPerThread;\n    const int filterIdx = (blockIdx.y % numFilterBlocks) * B_Y + threadIdx.y;\n\n    const int imgPixels = imgSize * imgSize;\n    const int pxIdx = pxIdxY * imgSize + pxIdxX;\n    const int imgIdx = blockImgIdx + threadIdx.x;\n\n    imgs            += ((filterIdx) * imgPixels + pxIdx) * numImages + imgIdx;\n    maxGrads        += (/*(filterIdx) * imgPixels +*/ pxIdx) * numImages + imgIdx;\n    maxActs         += (/*(filterIdx) * imgPixels +*/ pxIdx) * numImages + imgIdx;\n    target          += ((filterIdx) * imgPixels + pxIdx) * numImages + imgIdx;\n\n    float prod[imgsPerThread];\n//    if (imgIdx != 0 || pxIdx != 0 || filterIdx != 0) {\n//        return;\n//    }\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        prod[i] = 0;\n    }\n\n    if (filterIdx < numFilters) {\n//        const int startOut = max(0, (filterIdx-startF-poolSize)/ stride + 1);\n        const int loopStartOut = max(0, (filterIdx-startF-poolSize)/ stride + 1);\n        const int loopEndOut = min(numOutputs, (filterIdx - startF)/ stride + 1);\n\n        for (int o = loopStartOut; o < loopEndOut; ++o) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                    const float ma = maxActs[o * imgPixels * numImages + i * B_X];\n                    const float mg = maxGrads[o * imgPixels * numImages + i * B_X];\n                    const float img = imgs[i*B_X];\n                    prod[i] += (img == ma) * mg;\n                }\n            }\n        }\n    //    printf(\"gpu f start: %d, end: %d\\n\", loopStartF, loopEndF);\n\n        if (!add) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                    target[i * B_X] = prod[i];\n                }\n            }\n        } else {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                    target[i * B_X] = scaleTargets * target[i * B_X] + scaleOutputs * prod[i];\n                }\n            }\n        }\n    }\n}\n\n/*\n * images:              (numFilters, imgPixels, numImages)\n * maxGrads:            (numOutputs, imgPixels, numImages)\n * maxActs:             (numOutputs, imgPixels, numImages)\n * target:              (numFilters, imgPixels, numImages)\n */\nvoid convCrossMapMaxPoolUndo(NVMatrix& images, NVMatrix& maxGrads, NVMatrix& maxActs, NVMatrix& target,\n                             const int imgSize, const int startF, const int poolSize,\n                             const int stride, const float scaleTargets, const float scaleOutputs) {\n    int numImages = images.getNumCols();\n    int imgPixels = imgSize * imgSize;\n    int numFilters = images.getNumRows() / imgPixels;\n    int numOutputs = maxActs.getNumRows() / imgPixels;\n    assert(images.getNumRows() == numFilters * imgPixels);\n    assert(maxGrads.getNumRows() == numOutputs * imgPixels);\n    assert(maxGrads.getNumCols() == numImages);\n    assert(maxGrads.isSameDims(maxActs));\n\n    assert(images.getNumRows() == numFilters * imgPixels);\n\n    assert(!images.isTrans());\n    assert(!target.isTrans());\n    assert(!maxGrads.isTrans());\n    assert(!maxActs.isTrans());\n    assert(images.isContiguous());\n    assert(maxGrads.isContiguous());\n    assert(maxActs.isContiguous());\n    assert(maxGrads.isSameDims(maxActs));\n//    assert(numFilters % 16 == 0);\n//    assert(numImages % 128 == 0);\n\n    assert(stride <= poolSize);\n    assert(startF <= 0);\n    assert(startF + (numOutputs-1) * stride + poolSize >= numFilters); // All filters must be covered\n\n    dim3 threads(32, 4);\n\n    int imgsPerThread = numImages % 128 == 0 ? 4 : numImages % 64 == 0 ? 2 : 1;\n    dim3 blocks(imgSize * DIVUP(numImages, threads.x * imgsPerThread), imgSize * DIVUP(numFilters, threads.y));\n    bool checkCaseBounds = numImages % (threads.x*imgsPerThread) != 0;\n\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (scaleTargets == 0) {\n        target.resize(images);\n        if (!checkCaseBounds) {\n            if (imgsPerThread == 4) {\n                kCrossMapMaxPoolUndo<4, 32, 4, false, false><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                                             imgSize, numFilters, numImages, startF, poolSize, numOutputs, stride,\n                                                                                             scaleTargets, scaleOutputs);\n            } else if (imgsPerThread == 2) {\n                kCrossMapMaxPoolUndo<4, 32, 2, false, false><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                                             imgSize, numFilters, numImages, startF, poolSize, numOutputs, stride,\n                                                                                             scaleTargets, scaleOutputs);\n            } else {\n                kCrossMapMaxPoolUndo<4, 32, 1, false, false><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                                             imgSize, numFilters, numImages, startF, poolSize, numOutputs, stride,\n                                                                                             scaleTargets, scaleOutputs);\n            }\n        } else {\n            kCrossMapMaxPoolUndo<4, 32, 1, false, true><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                                         imgSize, numFilters, numImages, startF, poolSize, numOutputs, stride,\n                                                                                         scaleTargets, scaleOutputs);\n        }\n    } else {\n        assert(target.isSameDims(images));\n        if (!checkCaseBounds) {\n            if (imgsPerThread == 4) {\n                kCrossMapMaxPoolUndo<4, 32, 4, true, false><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                                             imgSize, numFilters, numImages, startF, poolSize, numOutputs, stride,\n                                                                                             scaleTargets, scaleOutputs);\n            } else if (imgsPerThread == 2) {\n                kCrossMapMaxPoolUndo<4, 32, 2, true, false><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                                             imgSize, numFilters, numImages, startF, poolSize, numOutputs, stride,\n                                                                                             scaleTargets, scaleOutputs);\n            } else {\n                kCrossMapMaxPoolUndo<4, 32, 1, true, false><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                                             imgSize, numFilters, numImages, startF, poolSize, numOutputs, stride,\n                                                                                             scaleTargets, scaleOutputs);\n            }\n        } else {\n            kCrossMapMaxPoolUndo<4, 32, 1, true, true><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                                         imgSize, numFilters, numImages, startF, poolSize, numOutputs, stride,\n                                                                                         scaleTargets, scaleOutputs);\n        }\n    }\n    getLastCudaError(\"convCrossMapMaxPoolUndo: kernel execution failed\");\n}\n\n/*\n * Block size B_YxB_X\n * blockIdx.x determines pixel.x, image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines pixel.y, filter idx in batches of B_Y\n *\n * So each block does one output pixel for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines filter idx\n *\n * outGrads:        (numFilters, imgPixels, numImages)\n * denoms:          (numFilters, imgPixels, numImages)\n * inputs:          (numFilters, imgPixels, numImages)\n * acts:            (numFilters, imgPixels, numImages)\n * target:          (numFilters, imgPixels, numImages)\n *\n * numImages must be divisible by B_X*imgsPerThread\n * numFilters must be divisible by B_Y\n *\n * TODO: this isn't really ideal\n */\ntemplate<int B_Y, int B_X, int imgsPerThread, bool add, bool checkCaseBounds, bool blocked>\n__global__ void kFRNormUndo(cudaTextureObject_t outGrads, cudaTextureObject_t denoms, cudaTextureObject_t inputs, cudaTextureObject_t acts,\n                            float* target, const int imgSize, const int numFilters, const int numImages, const int sizeF, const float powScale,\n                            const float scaleTargets, const float scaleOutputs) {\n    const int numImgBlocks = DIVUP(numImages,B_X*imgsPerThread);\n    const int numFilterBlocks = numFilters/B_Y;\n\n    const int pxIdxX = blockIdx.x / numImgBlocks;\n    const int pxIdxY = blockIdx.y / numFilterBlocks;\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * B_X * imgsPerThread;\n    const int filterIdx = (blockIdx.y % numFilterBlocks) * B_Y + threadIdx.y;\n\n    const int imgPixels = imgSize * imgSize;\n    const int pxIdx = pxIdxY * imgSize + pxIdxX;\n    const int imgIdx = blockImgIdx + threadIdx.x;\n\n    const int actsOffset = pxIdx * numImages + imgIdx;\n    const int inputOffset = ((filterIdx) * imgPixels + pxIdx) * numImages + imgIdx;\n\n    target      += inputOffset;\n    float prod[imgsPerThread];\n\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        prod[i] = 0;\n    }\n\n    const int startF = blocked ? (filterIdx / sizeF) * sizeF : -sizeF + sizeF/2 + 1 + filterIdx;\n    const int loopStartF = blocked ? startF : MAX(0, startF);\n    const int loopEndF = MIN(numFilters, startF + sizeF);\n\n    for (int f = loopStartF; f < loopEndF; ++f) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                prod[i] += tex1Dfetch<float>(acts, actsOffset + f * imgPixels * numImages + i * B_X);\n            }\n        }\n    }\n\n    if (!add) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                const float inp = tex1Dfetch<float>(inputs, inputOffset + i * B_X);\n                const float out = tex1Dfetch<float>(outGrads, inputOffset + i * B_X);\n                const float den = tex1Dfetch<float>(denoms, inputOffset + i * B_X);\n                prod[i] = inp * prod[i] + out * __powf(den, -powScale);\n                target[i * B_X] = prod[i];\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                const float inp = tex1Dfetch<float>(inputs, inputOffset + i * B_X);\n                const float out = tex1Dfetch<float>(outGrads, inputOffset + i * B_X);\n                const float den = tex1Dfetch<float>(denoms, inputOffset + i * B_X);\n                prod[i] = inp * prod[i] + out * __powf(den, -powScale);\n                target[i * B_X] = scaleTargets * target[i * B_X] + scaleOutputs * prod[i];\n            }\n        }\n    }\n}\n\n/*\n * Block size B_YxB_X\n * blockIdx.x determines pixel.x, image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines pixel.y, filter idx in batches of B_Y\n *\n * So each block does one output pixel for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines filter idx\n *\n * outGrads:        (numFilters, imgPixels, numImages)\n * denoms:          (numFilters, imgPixels, numImages)\n * inputs:          (numFilters, imgPixels, numImages)\n * acts:            (numFilters, imgPixels, numImages)\n * target:          (numFilters, imgPixels, numImages)\n *\n * numImages must be divisible by B_X*imgsPerThread\n * numFilters must be divisible by B_Y\n *\n * TODO: this is pretty wasteful of computation. a lot of threads basically compute the same products.\n */\ntemplate<int B_Y, int B_X, int imgsPerThread, bool add, bool checkCaseBounds, bool blocked>\n//__launch_bounds__(128,16)\n__global__ void kFRNormUndo2(cudaTextureObject_t outGrads, cudaTextureObject_t inputs, cudaTextureObject_t acts, float* target, const int imgSize, const int numFilters,\n                            const int numImages, const int sizeF, const float addScale, const float powScale, const float minDiv,\n                            const float scaleTargets, const float scaleOutputs) {\n    const int numImgBlocks = DIVUP(numImages,B_X*imgsPerThread);\n    const int numFilterBlocks = numFilters/B_Y;\n\n    const int pxIdxX = blockIdx.x / numImgBlocks;\n    const int pxIdxY = blockIdx.y / numFilterBlocks;\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * B_X * imgsPerThread;\n    const int filterIdx = (blockIdx.y % numFilterBlocks) * B_Y + threadIdx.y;\n\n    const int imgPixels = imgSize * imgSize;\n    const int pxIdx = pxIdxY * imgSize + pxIdxX;\n    const int imgIdx = blockImgIdx + threadIdx.x;\n\n    const int inpOffset = pxIdx * numImages + imgIdx;\n    const int outOffset = ((filterIdx) * imgPixels + pxIdx) * numImages + imgIdx;\n\n    target      += outOffset;\n\n    float prod[imgsPerThread];\n    float denoms[imgsPerThread];\n\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        prod[i] = 0;\n        denoms[i] = 0;\n    }\n\n    int startF = blocked ? (filterIdx / sizeF) * sizeF : -sizeF + sizeF/2 + 1 + filterIdx;\n    int loopStartF = blocked ? startF : MAX(0, startF);\n    int loopEndF = MIN(numFilters, startF + sizeF);\n\n    for (int f = loopStartF; f < loopEndF; ++f) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                // If an input is zero, then we shuldn't divide by it.\n                const float grad = tex1Dfetch<float>(outGrads, inpOffset + f * imgPixels * numImages + i * B_X);\n                const float act = tex1Dfetch<float>(acts, inpOffset + f * imgPixels * numImages + i * B_X);\n                const float inp = tex1Dfetch<float>(inputs, inpOffset + f * imgPixels * numImages + i * B_X) + (act == 0);\n                prod[i] += grad * act * __powf(__fdividef(act, inp), 1.0f/powScale);\n            }\n        }\n    }\n\n    startF = blocked ? (filterIdx / sizeF) * sizeF : -sizeF/2 + filterIdx;\n    loopStartF = blocked ? startF : MAX(0, startF);\n    loopEndF = MIN(numFilters, startF + sizeF);\n\n    for (int f = loopStartF; f < loopEndF; ++f) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                denoms[i] += square(tex1Dfetch<float>(inputs, inpOffset + f * imgPixels * numImages + i * B_X));\n            }\n        }\n    }\n\n    if (!add) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                const float inp = tex1Dfetch<float>(inputs, outOffset + i * B_X);\n                const float out = tex1Dfetch<float>(outGrads, outOffset + i * B_X);\n                denoms[i] = addScale * denoms[i] + minDiv;\n                prod[i] = (-2 * powScale * addScale * inp * prod[i] + out * __powf(denoms[i], -powScale));\n                target[i * B_X] = prod[i];\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                const float inp = tex1Dfetch<float>(inputs, outOffset + i * B_X);\n                const float out = tex1Dfetch<float>(outGrads, outOffset + i * B_X);\n                denoms[i] = addScale * denoms[i] + minDiv;\n                prod[i] = (-2 * powScale * addScale * inp * prod[i] + out * __powf(denoms[i], -powScale));\n                target[i * B_X] = scaleTargets * target[i * B_X] + scaleOutputs * prod[i];\n            }\n        }\n    }\n}\n\n\n/*\n * Block size B_YxB_X\n * blockIdx.x determines pixel.x, image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines pixel.y, filter idx in batches of B_Y*filtersPerThread\n * \n * So each block does one output pixel for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines filter idx\n *\n * imgs:        (numFilters, imgPixels, numImages)\n * maxGrads:    (numFilters, numOutputs, numImages)\n * rMaxActs:    (numFilters, numOutputs, numImages)\n * target:      (numFilters, imgPixels, numImages)\n *\n * numImages must be divisible by B_X*imgsPerThread\n * numFilters must be divisible by B_Y*filtersPerThread\n */\n\ntemplate<int B_Y, int B_X, int imgsPerThread, int filtersPerThread, bool sum, bool add, bool checkCaseBounds>\n__global__ void kLocalAvgUndo(float* avgGrads, float* target, const int imgSize, const int numFilters,\n                              const int numImages, const int subsX, const int startX, const int strideX, const int outputsX,\n                              const float scaleTargets, const float scaleOutputs) {\n    const int numImgBlocks = DIVUP(numImages,B_X*imgsPerThread);\n    const int blockPxX = blockIdx.x / numImgBlocks;\n    const int blockPxY = blockIdx.y / (numFilters/(B_Y*filtersPerThread));\n\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * B_X * imgsPerThread;\n    const int blockFilterIdx = (blockIdx.y % (numFilters/(B_Y*filtersPerThread))) * B_Y * filtersPerThread;\n\n    const int blockPx = blockPxY * imgSize + blockPxX;\n    const int numOutputs = outputsX * outputsX;\n    const int imgPixels = imgSize * imgSize;\n\n    const int startOutputY = blockPxY - startX < subsX ? 0 : 1 + (blockPxY - startX - subsX) / strideX;\n    const int endOutputY = MIN(outputsX, 1 + (blockPxY - startX) / strideX);\n    const int startOutputX = blockPxX - startX < subsX ? 0 : 1 + (blockPxX - startX - subsX) / strideX;\n    const int endOutputX = MIN(outputsX, 1 + (blockPxX - startX) / strideX);\n\n    const int imgIdx = blockImgIdx + threadIdx.x;\n\n    avgGrads += ((blockFilterIdx + threadIdx.y) * numOutputs) * numImages + imgIdx;\n    target += ((blockFilterIdx + threadIdx.y) * imgPixels + blockPx) * numImages + imgIdx;\n\n    float prod[filtersPerThread][imgsPerThread];\n    #pragma unroll\n    for (int f = 0; f < filtersPerThread; f++) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            prod[f][i] = 0;\n        }\n    }\n\n    if (blockPxX >= startX && blockPxX < startX + strideX * (outputsX-1) + subsX\n            && blockPxY >= startX && blockPxY < startX + strideX * (outputsX-1) + subsX) {\n\n        for (int my = startOutputY; my < endOutputY; my++) {\n            const float regionStartY = fmaxf(0, startX + my * strideX);\n            const float regionEndY = fminf(imgSize, startX + my * strideX + subsX);\n            const float regionSizeY = regionEndY - regionStartY;\n            for (int mx = startOutputX; mx < endOutputX; mx++) {\n                const int outputIdx = my * outputsX + mx;\n                const float regionStartX = fmaxf(0, startX + mx * strideX);\n                const float regionEndX = fminf(imgSize, startX + mx * strideX + subsX);\n                const float regionSizeX = regionEndX - regionStartX;\n                // It's important to do the division here, because pushing division into the below\n                // loops makes the code 4x slower.\n                const float regionSizeInv = sum ? 1.0f : (1.0f / (regionSizeX * regionSizeY));\n                #pragma unroll\n                for (int i = 0; i < imgsPerThread; i++) {\n                    if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                        #pragma unroll\n                        for (int f = 0; f < filtersPerThread; f++) {\n                            prod[f][i] += avgGrads[(f * B_Y * numOutputs + outputIdx) * numImages + i * B_X] * regionSizeInv;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    if (!add) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                #pragma unroll\n                for (int f = 0; f < filtersPerThread; f++) {\n                    target[f * B_Y * imgPixels * numImages + i * B_X] = prod[f][i];\n                }\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                #pragma unroll\n                for (int f = 0; f < filtersPerThread; f++) {\n                    target[f * B_Y * imgPixels * numImages + i * B_X] = scaleTargets * target[f * B_Y * imgPixels * numImages + i * B_X] + scaleOutputs * prod[f][i];\n                }\n            }\n        }\n    }\n}\n\n/*\n * Block size B_YxB_X\n * blockIdx.x determines pixel.x, image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines pixel.y, filter idx in batches of B_Y*filtersPerThread\n *\n * So each block does one output pixel for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines filter idx\n *\n * imgs:        (numFilters, imgPixels, numImages)\n * maxGrads:    (numFilters, numOutputs, numImages)\n * maxActs:    (numFilters, numOutputs, numImages)\n * target:      (numFilters, imgPixels, numImages)\n *\n * numImages must be divisible by B_X*imgsPerThread\n * numFilters must be divisible by B_Y*filtersPerThread\n */\ntemplate<int B_Y, int B_X, int imgsPerThread, int filtersPerThread, bool add, bool checkCaseBounds>\n__global__ void kLocalMaxUndo(float* imgs, float* maxGrads, float* maxActs, float* target, const int imgSize, const int numFilters,\n                              const int numImages, const int subsX, const int startX, const int strideX, const int outputsX,\n                              const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shImgs[B_Y*filtersPerThread][B_X*imgsPerThread];\n    const int numImgBlocks = DIVUP(numImages,B_X*imgsPerThread);\n    const int blockPxX = blockIdx.x / numImgBlocks;\n    const int blockPxY = blockIdx.y / (numFilters/(B_Y*filtersPerThread));\n\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * B_X * imgsPerThread;\n    const int blockFilterIdx = (blockIdx.y % (numFilters/(B_Y*filtersPerThread))) * B_Y * filtersPerThread;\n\n    const int blockPx = blockPxY * imgSize + blockPxX;\n    const int numOutputs = outputsX * outputsX;\n    const int imgPixels = imgSize * imgSize;\n\n    const int startOutputY = blockPxY - startX < subsX ? 0 : 1 + (blockPxY - startX - subsX) / strideX;\n    const int endOutputY = MIN(outputsX, 1 + (blockPxY - startX) / strideX);\n    const int startOutputX = blockPxX - startX < subsX ? 0 : 1 + (blockPxX - startX - subsX) / strideX;\n    const int endOutputX = MIN(outputsX, 1 + (blockPxX - startX) / strideX);\n\n    const int imgIdx = blockImgIdx + threadIdx.x;\n\n    imgs += ((blockFilterIdx + threadIdx.y) * imgPixels + blockPx) * numImages + imgIdx;\n    maxGrads += ((blockFilterIdx + threadIdx.y) * numOutputs) * numImages\n            + imgIdx;\n    maxActs += ((blockFilterIdx + threadIdx.y) * numOutputs) * numImages\n            + imgIdx;\n\n    target += ((blockFilterIdx + threadIdx.y) * imgPixels + blockPx) * numImages + imgIdx;\n\n    float prod[filtersPerThread][imgsPerThread];\n    #pragma unroll\n    for (int f = 0; f < filtersPerThread; f++) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            prod[f][i] = 0;\n        }\n    }\n\n    if  (blockPxX >= startX && blockPxX < startX + strideX * (outputsX-1) + subsX\n         && blockPxY >= startX && blockPxY < startX + strideX * (outputsX-1) + subsX) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                #pragma unroll\n                for (int f = 0; f < filtersPerThread; f++) {\n                    shImgs[threadIdx.y + B_Y * f][threadIdx.x + B_X * i] = imgs[f * B_Y * imgPixels * numImages + i * B_X];\n                }\n            }\n        }\n        for (int my = startOutputY; my < endOutputY; my++) {\n            for (int mx = startOutputX; mx < endOutputX; mx++) {\n                const int outputIdx = my * outputsX + mx;\n                #pragma unroll\n                for (int i = 0; i < imgsPerThread; i++) {\n                    if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                        #pragma unroll\n                        for (int f = 0; f < filtersPerThread; f++) {\n                            const float ma = maxActs[(f * B_Y * numOutputs + outputIdx) * numImages + i * B_X];\n                            const float mg = maxGrads[(f * B_Y * numOutputs + outputIdx) * numImages + i * B_X];\n                            const float img = shImgs[threadIdx.y + B_Y * f][threadIdx.x + B_X * i];\n\n                            prod[f][i] += (img == ma) * mg;\n                        }\n                    }\n                }\n            }\n        }\n    }\n    if (!add) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                #pragma unroll\n                for (int f = 0; f < filtersPerThread; f++) {\n                    target[f * B_Y * imgPixels * numImages + i * B_X] = prod[f][i];\n                }\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                #pragma unroll\n                for (int f = 0; f < filtersPerThread; f++) {\n                    target[f * B_Y * imgPixels * numImages + i * B_X] = scaleTargets * target[f * B_Y * imgPixels * numImages + i * B_X] + scaleOutputs * prod[f][i];\n                }\n            }\n        }\n    }\n}\n\n/*\n * acts := -2 x scale x acts x outGrads / denoms\n */\ntemplate<int B_X, int eltsPerThread>\n__global__ void kRNormUndoPrelims(float* acts, cudaTextureObject_t denoms, cudaTextureObject_t outGrads,\n                                  const uint numElements, const float scale) {\n    const uint e = B_X * blockIdx.x * eltsPerThread + threadIdx.x;\n    const uint numThreads = B_X * gridDim.x;\n    for (uint i = e; i < numElements; i += numThreads*eltsPerThread) {\n        #pragma unroll\n        for (uint k = 0; k < eltsPerThread; k++) {\n            if (i + k * B_X < numElements) {\n                acts[i + k * B_X] = __fdividef(scale * tex1Dfetch<float>(outGrads, i + k * B_X) * acts[i + k * B_X],\n                                               tex1Dfetch<float>(denoms, i + k * B_X));\n            }\n        }\n    }\n}\n\n/*\n * Block size B_YxB_X\n * blockIdx.x determines pixel.x, image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines pixel.y, filter idx in batches of B_Y*filtersPerThread\n *\n * So each block does one output pixel for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines filter idx\n *\n * outGrads:        (numFilters, imgPixels, numImages)\n * denoms:          (numFilters, imgPixels, numImages)\n * inputs:          (numFilters, imgPixels, numImages)\n * acts:            (numFilters, imgPixels, numImages)\n * target:          (numFilters, imgPixels, numImages)\n *\n * numImages must be divisible by B_X*imgsPerThread\n * numFilters must be divisible by B_Y*filtersPerThread\n *\n * TODO: this isn't really ideal\n */\ntemplate<int B_Y, int B_X, int imgsPerThread, int filtersPerThread, bool checkCaseBounds>\n__global__ void kRNormUndo(float* outGrads, float* denoms, float* inputs, float* acts, float* target, const int imgSize, const int numFilters,\n                              const int numImages, const int sizeX, const float powScale, const float scaleTargets, const float scaleOutputs) {\n    const int numImgBlocks = DIVUP(numImages,B_X*imgsPerThread);\n    const int numFilterBlocks = numFilters/(B_Y*filtersPerThread);\n\n    const int blockPxX = blockIdx.x / numImgBlocks;\n    const int blockPxY = blockIdx.y / numFilterBlocks;\n\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * B_X * imgsPerThread;\n    const int blockFilterIdx = (blockIdx.y % numFilterBlocks) * B_Y * filtersPerThread;\n\n    const int blockPx = blockPxY * imgSize + blockPxX;\n    const int imgPixels = imgSize * imgSize;\n\n    const int startY = MAX(0, blockPxY + sizeX/2 - sizeX + 1);\n    const int startX = MAX(0, blockPxX + sizeX/2 - sizeX + 1);\n    const int endY = MIN(imgSize, blockPxY + sizeX/2 + 1);\n    const int endX = MIN(imgSize, blockPxX + sizeX/2 + 1);\n\n    const int imgIdx = blockImgIdx + threadIdx.x;\n\n    acts        += ((blockFilterIdx + threadIdx.y) * imgPixels) * numImages + imgIdx;\n    inputs      += ((blockFilterIdx + threadIdx.y) * imgPixels + blockPx) * numImages + imgIdx;\n    denoms      += ((blockFilterIdx + threadIdx.y) * imgPixels + blockPx) * numImages + imgIdx;\n    outGrads    += ((blockFilterIdx + threadIdx.y) * imgPixels + blockPx) * numImages + imgIdx;\n    target      += ((blockFilterIdx + threadIdx.y) * imgPixels + blockPx) * numImages + imgIdx;\n\n    float prod[filtersPerThread][imgsPerThread];\n    #pragma unroll\n    for (int f = 0; f < filtersPerThread; f++) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            prod[f][i] = 0;\n        }\n    }\n\n    for (int sy = startY; sy < endY; sy++) {\n        for (int sx = startX; sx < endX; sx++) {\n            const int outPx = sy * imgSize + sx;\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        prod[f][i] += acts[(f * B_Y * imgPixels + outPx) * numImages + i * B_X];\n                    }\n                }\n            }\n        }\n    }\n//    outGrads += blockPx * numImages;\n    if (scaleTargets == 0) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                #pragma unroll\n                for (int f = 0; f < filtersPerThread; f++) {\n                    const float inp = inputs[(f * B_Y * imgPixels) * numImages + i * B_X];\n                    const float out = outGrads[(f * B_Y * imgPixels) * numImages + i * B_X];\n                    const float den = denoms[(f * B_Y * imgPixels) * numImages + i * B_X];\n                    prod[f][i] = inp * prod[f][i] + out * __powf(den, -powScale);\n                    target[f * B_Y * imgPixels * numImages + i * B_X] = prod[f][i];\n                }\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                #pragma unroll\n                for (int f = 0; f < filtersPerThread; f++) {\n                    const float inp = inputs[(f * B_Y * imgPixels) * numImages + i * B_X];\n                    const float out = outGrads[(f * B_Y * imgPixels) * numImages + i * B_X];\n                    const float den = denoms[(f * B_Y * imgPixels) * numImages + i * B_X];\n                    prod[f][i] = inp * prod[f][i] + out * __powf(den, -powScale);\n                    target[f * B_Y * imgPixels * numImages + i * B_X] =\n                                                scaleTargets * target[f * B_Y * imgPixels * numImages + i * B_X]\n                                                + scaleOutputs * prod[f][i];\n                }\n            }\n        }\n    }\n}\n\n/*\n * Block size 16xB_X\n * blockIdx.x determines 4x4 pixel.x region, image idx in batches of B_X*imgsPerThread\n * blockIdx.y determines 4x4 pixel.y region, filter idx in batches of filtersPerThread\n *\n * So each block does 4x4 region for some number of images/filters.\n *\n * threadIdx.x determines img idx\n * threadIdx.y determines pixel idx\n *\n * outGrads:        (numFilters, imgPixels, numImages)\n * denoms:          (numFilters, imgPixels, numImages)\n * inputs:          (numFilters, imgPixels, numImages)\n * acts:            (numFilters, imgPixels, numImages)\n * target:          (numFilters, imgPixels, numImages)\n *\n * B_X one of 8, 16, 32\n * imgsPerThread one of 1, 2, 4, 8, 16\n *\n * B_XximgsPerThread MUST be divisible by 32.\n * Number of filters MUST be divisible by filtersPerThread.\n *\n * numImages must be divisible by B_X*imgsPerThread if checkCaseBounds is false\n * numFilters must be divisible by filtersPerThread\n *\n * Final write-out will not be fully coalesced unless B_X is 32. But there's a lot more\n * reading than writing here, and the reading is all coalesced, so it should be OK.\n */\ntemplate<int B_X, int imgsPerThread, int filtersPerThread, bool add, bool checkCaseBounds>\n__global__ void kRNormUndo2(float* outGrads, float* denoms, float* inputs, float* acts, float* target, const int imgSize, const int numFilters,\n                            const int numImages, const int sizeX, const float powScale, const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shActs[filtersPerThread][B_X*imgsPerThread];\n    const int imgPixels = imgSize * imgSize;\n    const int numImgBlocks = DIVUP(numImages, B_X*imgsPerThread);\n    const int numFilterBlocks = numFilters/(filtersPerThread);\n    const int blockPxX = 4*(blockIdx.x / numImgBlocks);\n    const int blockPxY = 4*(blockIdx.y / numFilterBlocks);\n    const int blockImgIdx = (blockIdx.x % numImgBlocks) * B_X * imgsPerThread;\n    const int blockFilterIdx = (blockIdx.y % numFilterBlocks) * filtersPerThread;\n\n    const int tidx = threadIdx.y * B_X + threadIdx.x;\n    const int loadY = tidx / 32, loadX = tidx % 32;\n\n    const int startPxX = MAX(0, -DIVUP(sizeX,2) + blockPxX + 1);\n    const int startPxY = MAX(0, -DIVUP(sizeX,2) + blockPxY + 1);\n    const int endPxX = MIN(imgSize, blockPxX + sizeX/2 + 4);\n    const int endPxY = MIN(imgSize, blockPxY + sizeX/2 + 4);\n\n    const int myPxX = blockPxX + threadIdx.y % 4;\n    const int myPxY = blockPxY + threadIdx.y / 4;\n    const int myPxIdx = myPxY * imgSize + myPxX;\n//    const bool doWork = myPxX < imgSize && myPxY < imgSize;\n    const int myStartPxY = -DIVUP(sizeX,2) + myPxY + 1;\n    const int myStartPxX = -DIVUP(sizeX,2) + myPxX + 1;\n    const int myEndPxY = myPxY + sizeX/2 + 1;\n    const int myEndPxX = myPxX + sizeX/2 + 1;\n\n    const int imgIdx = blockImgIdx + threadIdx.x;\n\n    acts        += (blockFilterIdx + loadY) * imgPixels * numImages + blockImgIdx + loadX;\n    denoms      += (blockFilterIdx * imgPixels + myPxIdx) * numImages + imgIdx;\n    inputs      += (blockFilterIdx * imgPixels + myPxIdx) * numImages + imgIdx;\n    outGrads    += (blockFilterIdx * imgPixels + myPxIdx) * numImages + imgIdx;\n    target      += (blockFilterIdx * imgPixels + myPxIdx) * numImages + imgIdx;\n\n    float prod[filtersPerThread][imgsPerThread];\n    #pragma unroll\n    for (int f = 0; f < filtersPerThread; f++) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            prod[f][i] = 0;\n        }\n    }\n\n    for (int y = startPxY; y < endPxY; y++) {\n        const bool isInY = y >= myStartPxY && y < myEndPxY;\n        for (int x = startPxX; x < endPxX; x++) {\n            const int px = y * imgSize + x;\n            // All the threads load a pixel from memory\n            #pragma unroll\n            for (int ly = 0; ly < filtersPerThread; ly += B_X/2) {\n                if (filtersPerThread % (B_X/2) == 0 || ly + loadY < filtersPerThread) {\n                    #pragma unroll\n                    for (int lx = 0; lx < B_X*imgsPerThread; lx += 32) {\n                        if (!checkCaseBounds || lx + loadX + blockImgIdx < numImages) {\n                            shActs[ly + loadY][lx + loadX] = acts[(ly * imgPixels + px) * numImages + lx];\n                        }\n                    }\n                }\n            }\n            __syncthreads();\n\n            // Each row of threads decides if it's interested in this pixel\n            if (isInY && x >= myStartPxX && x < myEndPxX) {\n                #pragma unroll\n                for (int i = 0; i < imgsPerThread; i++) {\n                    if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                        #pragma unroll\n                        for (int f = 0; f < filtersPerThread; f++) {\n                            prod[f][i] += shActs[f][threadIdx.x + i * B_X];\n                        }\n                    }\n                }\n            }\n            __syncthreads();\n        }\n    }\n    acts -= (loadY * imgPixels - myPxIdx) * numImages + loadX;\n    acts += threadIdx.x;\n    if (myPxX < imgSize && myPxY < imgSize) {\n        if (!add) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        const float out = outGrads[f * imgPixels * numImages + i * B_X];\n                        const float den = denoms[f * imgPixels * numImages + i * B_X];\n                        const float inp = inputs[f * imgPixels * numImages + i * B_X];\n                        prod[f][i] = inp * prod[f][i] + out * __powf(den, -powScale);\n                        target[f * imgPixels * numImages + i * B_X] = prod[f][i];\n                    }\n                }\n            }\n        } else {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || imgIdx + i * B_X < numImages) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        const float out = outGrads[f * imgPixels * numImages + i * B_X];\n                        const float den = denoms[f * imgPixels * numImages + i * B_X];\n                        const float inp = inputs[f * imgPixels * numImages + i * B_X];\n                        prod[f][i] = inp * prod[f][i] + out * __powf(den, -powScale);\n                        target[f * imgPixels * numImages + i * B_X] = scaleTargets * target[f * imgPixels * numImages + i * B_X] + scaleOutputs * prod[f][i];\n                    }\n                }\n            }\n        }\n\n    }\n}\n\nvoid convLocalMaxUndo(NVMatrix& images, NVMatrix& maxGrads, NVMatrix& maxActs, NVMatrix& target,\n                      int subsX, int startX, int strideX, int outputsX) {\n    convLocalMaxUndo(images, maxGrads, maxActs, target, subsX, startX, strideX, outputsX, 0, 1);\n}\n\n/*\n * imgs:        (numFilters, imgPixels, numImages)\n * maxGrads:    (numFilters, numOutputs, numImages)\n * rMaxActs:    (numFilters, numOutputs, numImages)\n * target:      (numFilters, imgPixels, numImages)\n */\nvoid convLocalMaxUndo(NVMatrix& images, NVMatrix& maxGrads, NVMatrix& maxActs, NVMatrix& target,\n                      int subsX, int startX, int strideX, int outputsX, float scaleTargets, float scaleOutput) {\n    int outputs = outputsX * outputsX;\n    int numImages = images.getNumCols();\n    int numFilters = maxGrads.getNumRows() / outputs;\n    int imgPixels = images.getNumRows() / numFilters;\n    assert(images.getNumRows() == numFilters * imgPixels);\n    int imgSize = int(sqrt(imgPixels));\n\n    assert(imgSize * imgSize == imgPixels);\n    assert(maxGrads.getNumRows() == numFilters * outputs);\n    assert(maxGrads.getNumCols() == numImages);\n    assert(!images.isTrans());\n    assert(!target.isTrans());\n    assert(!maxGrads.isTrans());\n    assert(!maxActs.isTrans());\n    assert(images.isContiguous());\n    assert(maxGrads.isContiguous());\n    assert(maxActs.isContiguous());\n    assert(maxGrads.isSameDims(maxActs));\n    assert(numFilters % 16 == 0);\n//    assert(numImages % 128 == 0);\n\n    assert(strideX <= subsX);\n\n    target.resize(images);\n    assert(target.isContiguous());\n    int imgsPerThread = numImages % 128 == 0 ? 4 : numImages % 64 == 0 ? 2 : 1;\n    int checkCaseBounds = numImages % (32*imgsPerThread) != 0;\n    dim3 threads(32, 4);\n    dim3 blocks(DIVUP(numImages,32*imgsPerThread) * imgSize, (numFilters / (4 * 2)) * imgSize);\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (imgsPerThread == 4) {\n        if  (checkCaseBounds) {\n            if (scaleTargets == 0 && scaleOutput == 1) {\n                kLocalMaxUndo<4, 32, 4, 2, false, true><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n            } else {\n                kLocalMaxUndo<4, 32, 4, 2, true, true><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n            }\n        } else {\n            if (scaleTargets == 0 && scaleOutput == 1) {\n                kLocalMaxUndo<4, 32, 4, 2, false, false><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n            } else {\n                kLocalMaxUndo<4, 32, 4, 2, true, false><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n            }\n        }\n    } else if (imgsPerThread == 2) {\n        if  (checkCaseBounds) {\n            if (scaleTargets == 0 && scaleOutput == 1) {\n                kLocalMaxUndo<4, 32, 2, 2, false, true><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n            } else {\n                kLocalMaxUndo<4, 32, 2, 2, true, true><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n            }\n        } else {\n            if (scaleTargets == 0 && scaleOutput == 1) {\n                kLocalMaxUndo<4, 32, 2, 2, false, false><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n            } else {\n                kLocalMaxUndo<4, 32, 2, 2, true, false><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n            }\n        }\n    } else {\n        if  (checkCaseBounds) {\n            if (scaleTargets == 0 && scaleOutput == 1) {\n                kLocalMaxUndo<4, 32, 1, 2, false, true><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n            } else {\n                kLocalMaxUndo<4, 32, 1, 2, true, true><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n            }\n        } else {\n            if (scaleTargets == 0 && scaleOutput == 1) {\n                kLocalMaxUndo<4, 32, 1, 2, false, false><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n            } else {\n                kLocalMaxUndo<4, 32, 1, 2, true, false><<<blocks, threads, 0, stream>>>(images.getDevData(), maxGrads.getDevData(), maxActs.getDevData(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n            }\n        }\n    }\n\n    getLastCudaError(\"convLocalMaxUndo: kernel execution failed\");\n}\n\nvoid convLocalAvgUndo(NVMatrix& avgGrads, NVMatrix& target, int subsX, int startX, int strideX, int outputsX, int imgSize, bool sum) {\n    convLocalAvgUndo(avgGrads, target, subsX, startX, strideX, outputsX, imgSize, sum, 0, 1);\n}\n\n/*\n * avgGrads:    (numFilters, numOutputs, numImages)\n * target:      (numFilters, imgPixels, numImages)\n */\nvoid convLocalAvgUndo(NVMatrix& avgGrads, NVMatrix& target,\n                      int subsX, int startX, int strideX, int outputsX, int imgSize, bool sum,\n                      float scaleTargets, float scaleOutput) {\n    int numImages = avgGrads.getNumCols();\n\n    int outputs = outputsX * outputsX;\n    int imgPixels = imgSize * imgSize;\n    int numFilters = avgGrads.getNumRows() / outputs;\n    assert(avgGrads.getNumRows() == numFilters * outputs);\n\n    assert(!target.isTrans());\n    assert(!avgGrads.isTrans());\n    assert(avgGrads.isContiguous());\n    assert(numFilters % 16 == 0);\n//    assert(numImages % 128 == 0);\n\n    assert(strideX <= subsX);\n\n    target.resize(numFilters * imgPixels, numImages);\n    assert(target.isContiguous());\n    int imgsPerThread = numImages % 128 == 0 ? 4 : numImages % 64 == 0 ? 2 : 1;\n    int checkCaseBounds = numImages % (32*imgsPerThread) != 0;\n    dim3 threads(32, 4);\n    dim3 blocks(DIVUP(numImages,32*imgsPerThread) * imgSize, (numFilters / (4 * 4)) * imgSize);\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    bool scale = !(scaleTargets == 0 && scaleOutput == 1);\n    if (sum) {\n        if (imgsPerThread == 4) {\n            if (checkCaseBounds) {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    kLocalAvgUndo<4, 32, 4, 4, true, false, true> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                } else {\n                    kLocalAvgUndo<4, 32, 4, 4, true, true, true> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                }\n            } else {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    kLocalAvgUndo<4, 32, 4, 4, true, false, false> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                } else {\n                    kLocalAvgUndo<4, 32, 4, 4, true, true, false> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                }\n            }\n        } else if (imgsPerThread == 2) {\n            if (checkCaseBounds) {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    kLocalAvgUndo<4, 32, 2, 4, true, false, true> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                } else {\n                    kLocalAvgUndo<4, 32, 2, 4, true, true, true> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                }\n            } else {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    kLocalAvgUndo<4, 32, 2, 4, true, false, false> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                } else {\n                    kLocalAvgUndo<4, 32, 2, 4, true, true, false> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                }\n            }\n        } else {\n            if (checkCaseBounds) {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    kLocalAvgUndo<4, 32, 1, 4, true, false, true> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                } else {\n                    kLocalAvgUndo<4, 32, 1, 4, true, true, true> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                }\n            } else {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    kLocalAvgUndo<4, 32, 1, 4, true, false, false> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                } else {\n                    kLocalAvgUndo<4, 32, 1, 4, true, true, false> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                }\n            }\n        }\n    } else {\n        if (imgsPerThread == 4) {\n            if (checkCaseBounds) {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    kLocalAvgUndo<4, 32, 4, 4, false, false, true> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                } else {\n                    kLocalAvgUndo<4, 32, 4, 4, false, true, true> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                }\n            } else {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    kLocalAvgUndo<4, 32, 4, 4, false, false, false> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                } else {\n                    kLocalAvgUndo<4, 32, 4, 4, false, true, false> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                }\n            }\n        } else if (imgsPerThread == 2) {\n            if (checkCaseBounds) {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    kLocalAvgUndo<4, 32, 2, 4, false, false, true> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                } else {\n                    kLocalAvgUndo<4, 32, 2, 4, false, true, true> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                }\n            } else {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    kLocalAvgUndo<4, 32, 2, 4, false, false, false> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                } else {\n                    kLocalAvgUndo<4, 32, 2, 4, false, true, false> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                }\n            }\n        } else {\n            if (checkCaseBounds) {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    kLocalAvgUndo<4, 32, 1, 4, false, false, true> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                } else {\n                    kLocalAvgUndo<4, 32, 1, 4, false, true, true> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                }\n            } else {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    kLocalAvgUndo<4, 32, 1, 4, false, false, false> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                } else {\n                    kLocalAvgUndo<4, 32, 1, 4, false, true, false> <<<blocks, threads, 0, stream>>>(avgGrads.getDevData(), target.getDevData(), imgSize,\n                            numFilters, numImages, subsX, startX, strideX, outputsX, scaleTargets, scaleOutput);\n                }\n            }\n        }\n    }\n\n    getLastCudaError(\"convLocalAvgUndo: kernel execution failed\");\n}\n\nvoid convResponseNorm(NVMatrix& images, NVMatrix& denoms, NVMatrix& target, int numFilters, int sizeX, float addScale, float powScale, float minDiv) {\n    convContrastNorm(images, images, denoms, target, numFilters, sizeX, addScale, powScale, minDiv);\n}\n\n/*\n * images:      (numFilters, imgPixels, numImages)\n * meanDiffs:   (numFilters, imgPixels, numImages)\n * denoms:      (numFilters, imgPixels, numImages) (out)\n * target:      (numFilters, imgPixels, numImages) (out)\n */\nvoid convContrastNorm(NVMatrix& images, NVMatrix& meanDiffs, NVMatrix& denoms, NVMatrix& target, int numFilters, int sizeX, float addScale, float powScale, float minDiv) {\n    int numImages = images.getNumCols();\n    int imgPixels = images.getNumRows() / numFilters;\n    assert(images.getNumRows() == numFilters * imgPixels);\n    int imgSize = int(sqrt(imgPixels));\n    assert(imgSize * imgSize == imgPixels);\n    assert(meanDiffs.isSameDims(images));\n\n    assert(!meanDiffs.isTrans());\n    assert(!images.isTrans());\n    assert(images.isContiguous());\n    assert(meanDiffs.isContiguous());\n    assert(numFilters % 16 == 0 || numFilters <= 8);\n\n    target.resize(images);\n    denoms.resize(images);\n    assert(target.isContiguous());\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (sizeX >= 6 && numFilters % 4 == 0) {\n        // This one is faster for large regions (my tests show regions >= 6...)\n        int imgsPerThread = 8;\n        int filtersPerThread = 4;\n        int bx = 8;\n        bool checkCaseBounds = numImages % (bx*imgsPerThread) != 0;\n        assert((imgsPerThread * bx) % 32 == 0);\n        assert(numFilters % filtersPerThread == 0);\n        dim3 threads(bx, 16);\n        dim3 blocks(DIVUP(imgSize, 4) * DIVUP(numImages, bx*imgsPerThread), DIVUP(imgSize, 4) * numFilters / filtersPerThread);\n\n        if (checkCaseBounds) {\n            cudaFuncSetCacheConfig(kCNorm2<8, 8, 4, true>, cudaFuncCachePreferL1); // L1 faster here\n            kCNorm2<8, 8, 4, true><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                           imgSize, numFilters, numImages, sizeX, addScale, powScale, minDiv);\n        } else {\n            cudaFuncSetCacheConfig(kCNorm2<8, 8, 4, false>, cudaFuncCachePreferL1); // L1 faster here\n            kCNorm2<8, 8, 4, false><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                           imgSize, numFilters, numImages, sizeX, addScale, powScale, minDiv);\n        }\n    } else {\n        bool checkCaseBounds = numImages % 128 != 0;\n        if (numFilters <= 8) {\n            dim3 threads(128);\n            dim3 blocks(DIVUP(numImages,128) * imgSize, imgSize);\n            if (numFilters == 1) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 1, true>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                } else {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 1, false>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                }\n            } else  if (numFilters == 2) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 2, true>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 2, true><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                } else {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 2, false>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 2, false><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                }\n            } else  if (numFilters == 3) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 3, true>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 3, true><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                } else {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 3, false>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 3, false><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                }\n            } else  if (numFilters == 4) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 4, true>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 4, true><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                } else {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 4, false>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 4, false><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                }\n            } else  if (numFilters == 5) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 5, true>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 5, true><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                } else {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 5, false>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 5, false><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                }\n            } else  if (numFilters == 6) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 6, true>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 6, true><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                } else {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 6, false>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 6, false><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                }\n            } else  if (numFilters == 7) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 7, true>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 7, true><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                } else {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 7, false>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 7, false><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                }\n            } else  if (numFilters == 8) {\n                if (checkCaseBounds) {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 8, true>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 8, true><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                } else {\n                    cudaFuncSetCacheConfig(kCNorm_fewfilter<1, 8, false>, cudaFuncCachePreferL1);\n                    kCNorm_fewfilter<1, 8, false><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                      imgSize, numImages, sizeX, addScale, powScale, minDiv);\n                }\n            }\n        } else {\n            dim3 threads(32, 4);\n            dim3 blocks(DIVUP(numImages,threads.x*4), (numFilters / (threads.y * 2)), imgPixels);\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kCNorm_manyfilter<4, 32, 4, 2, true>, cudaFuncCachePreferL1);\n                kCNorm_manyfilter<4, 32, 4, 2, true><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                  imgSize, numFilters, numImages, sizeX, addScale, powScale, minDiv);\n            } else {\n                cudaFuncSetCacheConfig(kCNorm_manyfilter<4, 32, 4, 2, false>, cudaFuncCachePreferL1);\n                kCNorm_manyfilter<4, 32, 4, 2, false><<<blocks, threads, 0, stream>>>(images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(),\n                                                                  imgSize, numFilters, numImages, sizeX, addScale, powScale, minDiv);\n            }\n        }\n    }\n    getLastCudaError(\"convResponseNorm: kernel execution failed\");\n}\n\nvoid convContrastNormUndo(NVMatrix& outGrads, NVMatrix& denoms, NVMatrix& meanDiffs, NVMatrix& acts, NVMatrix& target, int numFilters,\n                         int sizeX, float addScale, float powScale, float scaleTargets, float scaleOutput) {\n    convResponseNormUndo(outGrads, denoms, meanDiffs, acts, target, numFilters, sizeX, addScale, powScale, scaleTargets, scaleOutput);\n}\n\n/*\n * outGrads:    (numFilters, imgPixels, numImages)\n * denoms:      (numFilters, imgPixels, numImages)\n * inputs:      (numFilters, imgPixels, numImages)\n * acts:        (numFilters, imgPixels, numImages)\n * target:      (numFilters, imgPixels, numImages)\n *\n * THIS WILL OVERWRITE THE ACTS MATRIX.\n */\nvoid convResponseNormUndo(NVMatrix& outGrads, NVMatrix& denoms, NVMatrix& inputs, NVMatrix& acts, NVMatrix& target, int numFilters,\n                         int sizeX, float addScale, float powScale, float scaleTargets, float scaleOutput) {\n    int numImages = outGrads.getNumCols();\n    int imgPixels = outGrads.getNumRows() / numFilters;\n\n    int imgSize = int(sqrt(imgPixels));\n    assert(imgSize * imgSize == imgPixels);\n\n    assert(outGrads.getNumRows() == numFilters * imgPixels);\n\n    assert(denoms.isSameDims(outGrads));\n    assert(acts.isSameDims(denoms));\n    assert(!denoms.isTrans());\n    assert(!outGrads.isTrans());\n    assert(!acts.isTrans());\n    assert(!target.isTrans());\n    assert(outGrads.isContiguous());\n\n    assert(numFilters % 16 == 0);\n\n    target.resize(outGrads);\n    assert(target.isContiguous());\n    // First do acts := -2 x scale x acts x outGrads / denoms\n    // so that the main routine only has to do an addition in its inner loop.\n    int prelimEltsPerThread = 8;\n    dim3 threads(128);\n    dim3 blocks(DIVUP(outGrads.getNumElements(),(threads.x * prelimEltsPerThread)));\n    bool checkPrelimBounds = outGrads.getNumElements() % (threads.x * prelimEltsPerThread) != 0;\n    //printf(\"num elts: %d, blocks: %d\\n\", outGrads.getNumElements(), blocks.x);\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    kRNormUndoPrelims<128, 8><<<blocks, threads, 0, stream>>>(acts.getDevData(), denoms.getTextureObject(), outGrads.getTextureObject(), outGrads.getNumElements(), -2*addScale*powScale);\n\n    // Now the main routine\n    if (sizeX >= 6 && numFilters % 4 == 0) {\n        // This one is faster for large regions (my tests show regions >= 6...)\n        // NOTE: this stuff is not optimized for Kepler. Only kRNormUndo is.\n        int imgsPerThread = numImages % 128 == 0 ? 8 : numImages % 64 == 0 ? 4 : 2;\n        int filtersPerThread = 4;\n        int bx = 16;\n        bool checkCaseBounds = numImages % (bx*imgsPerThread) != 0;\n        assert((imgsPerThread * bx) % 32 == 0);\n\n        threads = dim3(bx, 16);\n        blocks = dim3(DIVUP(imgSize, 4) * DIVUP(numImages, bx*imgsPerThread), DIVUP(imgSize, 4) * numFilters / filtersPerThread);\n        if (imgsPerThread == 8) {\n            if (checkCaseBounds) {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    cudaFuncSetCacheConfig(kRNormUndo2<16, 8, 4, true, true>, cudaFuncCachePreferL1);\n                    kRNormUndo2<16, 8, 4, true, true><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                                  target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                                  scaleTargets, scaleOutput);\n                } else {\n                    cudaFuncSetCacheConfig(kRNormUndo2<16, 8, 4, false, true>, cudaFuncCachePreferL1);\n                    kRNormUndo2<16, 8, 4, false, true><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                                  target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                                  scaleTargets, scaleOutput);\n                }\n            } else {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    cudaFuncSetCacheConfig(kRNormUndo2<16, 8, 4, true, false>, cudaFuncCachePreferL1);\n                    kRNormUndo2<16, 8, 4, true, false><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                                  target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                                  scaleTargets, scaleOutput);\n                } else {\n                    cudaFuncSetCacheConfig(kRNormUndo2<16, 8, 4, false, false>, cudaFuncCachePreferL1);\n                    kRNormUndo2<16, 8, 4, false, false><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                                  target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                                  scaleTargets, scaleOutput);\n                }\n            }\n        } else if (imgsPerThread == 4) {\n            if (checkCaseBounds) {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    cudaFuncSetCacheConfig(kRNormUndo2<16, 4, 4, true, true>, cudaFuncCachePreferL1);\n                    kRNormUndo2<16, 4, 4, true, true><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                                  target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                                  scaleTargets, scaleOutput);\n                } else {\n                    cudaFuncSetCacheConfig(kRNormUndo2<16, 4, 4, false, true>, cudaFuncCachePreferL1);\n                    kRNormUndo2<16, 4, 4, false, true><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                                  target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                                  scaleTargets, scaleOutput);\n                }\n            } else {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    cudaFuncSetCacheConfig(kRNormUndo2<16, 4, 4, true, false>, cudaFuncCachePreferL1);\n                    kRNormUndo2<16, 4, 4, true, false><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                                  target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                                  scaleTargets, scaleOutput);\n                } else {\n                    cudaFuncSetCacheConfig(kRNormUndo2<16, 4, 4, false, false>, cudaFuncCachePreferL1);\n                    kRNormUndo2<16, 4, 4, false, false><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                                  target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                                  scaleTargets, scaleOutput);\n                }\n            }\n        } else {\n            if (checkCaseBounds) {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    cudaFuncSetCacheConfig(kRNormUndo2<16, 2, 4, true, true>, cudaFuncCachePreferL1);\n                    kRNormUndo2<16, 2, 4, true, true><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                                  target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                                  scaleTargets, scaleOutput);\n                } else {\n                    cudaFuncSetCacheConfig(kRNormUndo2<16, 2, 4, false, true>, cudaFuncCachePreferL1);\n                    kRNormUndo2<16, 2, 4, false, true><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                                  target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                                  scaleTargets, scaleOutput);\n                }\n            } else {\n                if (scaleTargets == 0 && scaleOutput == 1) {\n                    cudaFuncSetCacheConfig(kRNormUndo2<16, 2, 4, true, false>, cudaFuncCachePreferL1);\n                    kRNormUndo2<16, 2, 4, true, false><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                                  target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                                  scaleTargets, scaleOutput);\n                } else {\n                    cudaFuncSetCacheConfig(kRNormUndo2<16, 2, 4, false, false>, cudaFuncCachePreferL1);\n                    kRNormUndo2<16, 2, 4, false, false><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                                  target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                                  scaleTargets, scaleOutput);\n                }\n            }\n        }\n    } else {\n        int imgsPerThread = numImages % 128 == 0 ? 4 : 1;\n        bool checkCaseBounds = numImages % (32*imgsPerThread) != 0;\n        threads = dim3(32, 4);\n        blocks = dim3(DIVUP(numImages,32*imgsPerThread) * imgSize, (numFilters / (4 * 2)) * imgSize);\n\n        if (imgsPerThread == 4) {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kRNormUndo<4, 32, 4, 2, true>, cudaFuncCachePreferL1);\n                kRNormUndo<4, 32, 4, 2, true><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                          target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                          scaleTargets, scaleOutput);\n            } else {\n                cudaFuncSetCacheConfig(kRNormUndo<4, 32, 4, 2, false>, cudaFuncCachePreferL1);\n                kRNormUndo<4, 32, 4, 2, false><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                          target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                          scaleTargets, scaleOutput);\n            }\n        } else {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kRNormUndo<4, 32, 1, 2, true>, cudaFuncCachePreferL1);\n                kRNormUndo<4, 32, 1, 2, true><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                          target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                          scaleTargets, scaleOutput);\n            } else {\n                cudaFuncSetCacheConfig(kRNormUndo<4, 32, 1, 2, false>, cudaFuncCachePreferL1);\n                kRNormUndo<4, 32, 1, 2, false><<<blocks, threads, 0, stream>>>(outGrads.getDevData(), denoms.getDevData(), inputs.getDevData(), acts.getDevData(),\n                                                                          target.getDevData(), imgSize, numFilters, numImages, sizeX, powScale,\n                                                                          scaleTargets, scaleOutput);\n            }\n        }\n    }\n    getLastCudaError(\"kRNormUndo: kernel execution failed\");\n}\n\n/*\n * imgs:        (numChannels, imgPixels, numImages) with given imgStride\n * target:      (numChannels, tgtPixels, numImages)\n *\n * imgSize = scale * tgtSize\n */\nvoid convResizeBilinear(NVMatrix& images, NVMatrix& target, int imgSize, int tgtSize, float scale) {\n    assert(!images.isTrans());\n    assert(!target.isTrans());\n    int imgPixels = imgSize * imgSize;\n    int tgtPixels = tgtSize * tgtSize;\n    int numChannels = images.getNumRows() / imgPixels;\n    int numImages = images.getNumCols();\n    assert(images.getNumRows() == numChannels * imgPixels);\n\n    target.resize(numChannels * tgtPixels, numImages);\n    assert(target.isContiguous());\n    int numChunksX = DIVUP(tgtSize, 4);\n    int numChunks = numChunksX * numChunksX;\n    double imgCenter = imgSize * 0.5;\n    double tgtCenter = tgtSize * 0.5;\n    double centerScale = imgCenter - tgtCenter * scale;\n\n    int imgsPerThread = numImages % 128 == 0 ? 4 : numImages % 64 == 0 ? 2 : 1;\n    bool checkCaseBounds = numImages % (32*imgsPerThread) != 0;\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    dim3 threads(32, 16);\n    dim3 blocks(DIVUP(numImages, imgsPerThread * 32), numChannels * numChunks);\n    if (imgsPerThread == 4) {\n        if (checkCaseBounds) {\n            cudaFuncSetCacheConfig(kResizeBilinear<4, true>, cudaFuncCachePreferL1);\n            kResizeBilinear<4, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgSize, tgtSize, numImages, images.getStride(), scale, centerScale);\n        } else {\n            cudaFuncSetCacheConfig(kResizeBilinear<4, false>, cudaFuncCachePreferL1);\n            kResizeBilinear<4, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgSize, tgtSize, numImages, images.getStride(), scale, centerScale);\n        }\n    } else if (imgsPerThread == 2) {\n        if (checkCaseBounds) {\n            cudaFuncSetCacheConfig(kResizeBilinear<2, true>, cudaFuncCachePreferL1);\n            kResizeBilinear<2, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgSize, tgtSize, numImages, images.getStride(), scale, centerScale);\n        } else {\n            cudaFuncSetCacheConfig(kResizeBilinear<2, false>, cudaFuncCachePreferL1);\n            kResizeBilinear<2, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgSize, tgtSize, numImages, images.getStride(), scale, centerScale);\n        }\n    } else {\n        if (checkCaseBounds) {\n            cudaFuncSetCacheConfig(kResizeBilinear<1, true>, cudaFuncCachePreferL1);\n            kResizeBilinear<1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgSize, tgtSize, numImages, images.getStride(), scale, centerScale);\n        } else {\n            cudaFuncSetCacheConfig(kResizeBilinear<1, false>, cudaFuncCachePreferL1);\n            kResizeBilinear<1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgSize, tgtSize, numImages, images.getStride(), scale, centerScale);\n        }\n    }\n    getLastCudaError(\"convResizeBilinear: kernel execution failed\");\n}\n\n/*\n * imgs:        (3, imgPixels, numImages) with given imgStride\n * target:      (3, imgPixels, numImages)\n */\nvoid convRGBToYUV(NVMatrix& images, NVMatrix& target) {\n    assert(!images.isTrans());\n    assert(!target.isTrans());\n    int imgPixels = images.getNumRows() / 3;\n    int numImages = images.getNumCols();\n    assert(images.getNumRows() == 3 * imgPixels);\n\n    target.resize(3 * imgPixels, numImages);\n    assert(target.isContiguous());\n    int imgsPerThread = numImages % 128 == 0 ? 4 : numImages % 64 == 0 ? 2 : 1;\n    bool checkCaseBounds = numImages % (32*imgsPerThread) != 0;\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    dim3 threads(32, 4);\n    dim3 blocks(DIVUP(numImages, imgsPerThread * 32), DIVUP(imgPixels, 4));\n    if (imgsPerThread == 4) {\n        if (checkCaseBounds) {\n            cudaFuncSetCacheConfig(kRGBToYUV<4, true>, cudaFuncCachePreferL1);\n            kRGBToYUV<4, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n        } else {\n            cudaFuncSetCacheConfig(kRGBToYUV<4, false>, cudaFuncCachePreferL1);\n            kRGBToYUV<4, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n        }\n    } else if (imgsPerThread == 2) {\n        if (checkCaseBounds) {\n            cudaFuncSetCacheConfig(kRGBToYUV<2, true>, cudaFuncCachePreferL1);\n            kRGBToYUV<2, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n        } else {\n            cudaFuncSetCacheConfig(kRGBToYUV<2, false>, cudaFuncCachePreferL1);\n            kRGBToYUV<2, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n        }\n    } else {\n        if (checkCaseBounds) {\n            cudaFuncSetCacheConfig(kRGBToYUV<1, true>, cudaFuncCachePreferL1);\n            kRGBToYUV<1, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n        } else {\n            cudaFuncSetCacheConfig(kRGBToYUV<1, false>, cudaFuncCachePreferL1);\n            kRGBToYUV<1, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n        }\n    }\n    getLastCudaError(\"convRGBToYUV: kernel execution failed\");\n}\n\n/*\n * imgs:        (3, imgPixels, numImages) with given imgStride\n * target:      (3, imgPixels, numImages)\n */\nvoid convRGBToLAB(NVMatrix& images, NVMatrix& target, bool center) {\n    assert(!images.isTrans());\n    assert(!target.isTrans());\n    int imgPixels = images.getNumRows() / 3;\n    int numImages = images.getNumCols();\n    assert(images.getNumRows() == 3 * imgPixels);\n\n    target.resize(3 * imgPixels, numImages);\n    assert(target.isContiguous());\n\n    int imgsPerThread = numImages % 128 == 0 ? 4 : numImages % 64 == 0 ? 2 : 1;\n    bool checkCaseBounds = numImages % (32*imgsPerThread) != 0;\n    dim3 threads(32, 4);\n    dim3 blocks(DIVUP(numImages, imgsPerThread * 32), DIVUP(imgPixels, 4));\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (imgsPerThread == 4) {\n        if (center) {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kRGBToLAB<4, true, true>, cudaFuncCachePreferL1);\n                kRGBToLAB<4, true, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n            } else {\n                cudaFuncSetCacheConfig(kRGBToLAB<4, false, true>, cudaFuncCachePreferL1);\n                kRGBToLAB<4, false, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n            }\n        } else {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kRGBToLAB<4, true, false>, cudaFuncCachePreferL1);\n                kRGBToLAB<4, true, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n            } else {\n                cudaFuncSetCacheConfig(kRGBToLAB<4, false, false>, cudaFuncCachePreferL1);\n                kRGBToLAB<4, false, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n            }\n        }\n    } else if (imgsPerThread == 2) {\n        if (center) {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kRGBToLAB<2, true, true>, cudaFuncCachePreferL1);\n                kRGBToLAB<2, true, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n            } else {\n                cudaFuncSetCacheConfig(kRGBToLAB<2, false, true>, cudaFuncCachePreferL1);\n                kRGBToLAB<2, false, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n            }\n        } else {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kRGBToLAB<2, true, false>, cudaFuncCachePreferL1);\n                kRGBToLAB<2, true, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n            } else {\n                cudaFuncSetCacheConfig(kRGBToLAB<2, false, false>, cudaFuncCachePreferL1);\n                kRGBToLAB<2, false, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n            }\n        }\n    } else {\n        if (center) {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kRGBToLAB<1, true, true>, cudaFuncCachePreferL1);\n                kRGBToLAB<1, true, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n            } else {\n                cudaFuncSetCacheConfig(kRGBToLAB<1, false, true>, cudaFuncCachePreferL1);\n                kRGBToLAB<1, false, true><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n            }\n        } else {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kRGBToLAB<1, true, false>, cudaFuncCachePreferL1);\n                kRGBToLAB<1, true, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n            } else {\n                cudaFuncSetCacheConfig(kRGBToLAB<1, false, false>, cudaFuncCachePreferL1);\n                kRGBToLAB<1, false, false><<<blocks, threads, 0, stream>>>(images.getDevData(), target.getDevData(), imgPixels, numImages, images.getStride());\n            }\n        }\n    }\n    getLastCudaError(\"convRGBToLAB: kernel execution failed\");\n}\n\n/*\n * imgs:    (numChannels, imgPixels, numImages) with given imgStride\n * target:  (numChannels, tgtPixels, numImages)\n */\nvoid convCrop(NVMatrix& imgs, NVMatrix& target, int imgSize, int tgtSize, int startY, int startX) {\n    int numImages = imgs.getNumCols();\n    int imgPixels = imgSize * imgSize;\n    int tgtPixels = tgtSize * tgtSize;\n\n    int numChannels = imgs.getNumRows() / imgPixels;\n    assert(imgs.getNumRows() == imgPixels * numChannels);\n    assert(imgPixels == imgSize * imgSize);\n    assert(imgSize - startY >= tgtSize);\n    assert(imgSize - startX >= tgtSize);\n    assert(startY >= 0);\n    assert(startX >= 0);\n    target.resize(numChannels * tgtPixels, numImages);\n    int imgsPerThread = numImages % 128 == 0 ? 4 : numImages % 64 == 0 ? 2 : 1;\n    bool checkCaseBounds = numImages % (32*imgsPerThread) != 0;\n    dim3 blocks(DIVUP(numImages, 32 * imgsPerThread), numChannels * DIVUP(tgtPixels, 4));\n    dim3 threads(32, 4);\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (imgsPerThread == 4) {\n        if (checkCaseBounds) {\n            kCrop<4, true><<<blocks, threads, 0, stream>>>(imgs.getDevData(), target.getDevData(), numImages, imgs.getStride(), imgSize, tgtSize, startY, startX);\n        } else {\n            kCrop<4, false><<<blocks, threads, 0, stream>>>(imgs.getDevData(), target.getDevData(), numImages, imgs.getStride(), imgSize, tgtSize, startY, startX);\n        }\n    } else if (imgsPerThread == 2) {\n        if (checkCaseBounds) {\n            kCrop<2, true><<<blocks, threads, 0, stream>>>(imgs.getDevData(), target.getDevData(), numImages, imgs.getStride(), imgSize, tgtSize, startY, startX);\n        } else {\n            kCrop<2, false><<<blocks, threads, 0, stream>>>(imgs.getDevData(), target.getDevData(), numImages, imgs.getStride(), imgSize, tgtSize, startY, startX);\n        }\n    } else {\n        if (checkCaseBounds) {\n            kCrop<1, true><<<blocks, threads, 0, stream>>>(imgs.getDevData(), target.getDevData(), numImages, imgs.getStride(), imgSize, tgtSize, startY, startX);\n        } else {\n            kCrop<1, false><<<blocks, threads, 0, stream>>>(imgs.getDevData(), target.getDevData(), numImages, imgs.getStride(), imgSize, tgtSize, startY, startX);\n        }\n    }\n    getLastCudaError(\"convCrop: kernel execution failed\");\n}\n\n/*\n * images:      (numFilters, imgPixels, numImages)\n * meanDiffs:   (numFilters, imgPixels, numImages)\n * denoms:      (numFilters, imgPixels, numImages) (out)\n * target:      (numFilters, imgPixels, numImages) (out)\n\n * Note: at present, I have no code to compute the meanDiffs. So it should be set\n * to be equal to images. In other words, this isn't really doing contrast normalization,\n * just response normalization.\n */\nvoid convContrastNormCrossMap(NVMatrix& images, NVMatrix& meanDiffs, NVMatrix& target,\n                             int numFilters, int sizeF, float addScale, float powScale, float minDiv, bool blocked) {\n    int numImages = images.getNumCols();\n    int imgPixels = images.getNumRows() / numFilters;\n    assert(images.getNumRows() == numFilters * imgPixels);\n    int imgSize = int(sqrt(imgPixels));\n    assert(imgSize * imgSize == imgPixels);\n    assert(meanDiffs.isSameDims(images));\n    assert(sizeF > 0 && sizeF <= numFilters);\n\n    assert(!meanDiffs.isTrans());\n    assert(!images.isTrans());\n    assert(images.isContiguous());\n    assert(meanDiffs.isContiguous());\n    assert(numFilters % 16 == 0);\n\n    target.resize(images);\n//    denoms.resize(images);\n    assert(target.isContiguous());\n\n    bool checkCaseBounds = numImages % 128 != 0;\n\n    dim3 threads(32, 4);\n    dim3 blocks(DIVUP(numImages,32*4) * imgSize, (numFilters / 4) * imgSize);\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n//    printf(\"convContrastNormCrossMap imgs: %p, meanDiffs: %p, denoms: %p, target: %p, imgSize: %d, numFilters: %d, numImages: %d, sizeF: %d, addScale: %f, powScale: %f, minDiv: %f, blocked: %d\\n\",\n//            images.getDevData(), meanDiffs.getDevData(), denoms.getDevData(), target.getDevData(), imgSize, numFilters, numImages, sizeF, addScale, powScale, minDiv, blocked);\n    if (blocked) {\n        if (checkCaseBounds) {\n            cudaFuncSetCacheConfig(kFCNorm<4, 32, 4, true, true>, cudaFuncCachePreferL1);\n            kFCNorm<4, 32, 4, true, true><<<blocks, threads, 0, stream>>>(images.getTextureObject(), meanDiffs.getTextureObject(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, sizeF, addScale, powScale, minDiv);\n        } else {\n            cudaFuncSetCacheConfig(kFCNorm<4, 32, 4, false, true>, cudaFuncCachePreferL1);\n            kFCNorm<4, 32, 4, false, true><<<blocks, threads, 0, stream>>>(images.getTextureObject(), meanDiffs.getTextureObject(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, sizeF, addScale, powScale, minDiv);\n        }\n    } else {\n        if (checkCaseBounds) {\n            cudaFuncSetCacheConfig(kFCNorm<4, 32, 4, true, false>, cudaFuncCachePreferL1);\n            kFCNorm<4, 32, 4, true, false><<<blocks, threads, 0, stream>>>(images.getTextureObject(), meanDiffs.getTextureObject(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, sizeF, addScale, powScale, minDiv);\n        } else {\n            cudaFuncSetCacheConfig(kFCNorm<4, 32, 4, false, false>, cudaFuncCachePreferL1);\n            kFCNorm<4, 32, 4, false, false><<<blocks, threads, 0, stream>>>(images.getTextureObject(), meanDiffs.getTextureObject(), target.getDevData(),\n                                                                imgSize, numFilters, numImages, sizeF, addScale, powScale, minDiv);\n        }\n    }\n\n    getLastCudaError(\"convContrastNormCrossMap: kernel execution failed\");\n}\n\n/*\n * outGrads:    (numFilters, imgPixels, numImages)\n * denoms:      (numFilters, imgPixels, numImages)\n * inputs:      (numFilters, imgPixels, numImages)\n * acts:        (numFilters, imgPixels, numImages)\n * target:      (numFilters, imgPixels, numImages)\n *\n * THIS WILL OVERWRITE THE ACTS MATRIX.\n */\nvoid convResponseNormCrossMapUndo(NVMatrix& outGrads, NVMatrix& inputs, NVMatrix& acts, NVMatrix& target, int numFilters,\n                         int sizeF, float addScale, float powScale, float minDiv, bool blocked, float scaleTargets, float scaleOutput) {\n    int numImages = outGrads.getNumCols();\n    int imgPixels = outGrads.getNumRows() / numFilters;\n\n    int imgSize = int(sqrt(imgPixels));\n    assert(imgSize * imgSize == imgPixels);\n    assert(sizeF > 0 && sizeF <= numFilters);\n    assert(outGrads.getNumRows() == numFilters * imgPixels);\n\n    assert(!outGrads.isTrans());\n    assert(!acts.isTrans());\n    assert(!target.isTrans());\n    assert(outGrads.isContiguous());\n\n    assert(numFilters % 16 == 0);\n\n    target.resize(outGrads);\n    assert(target.isContiguous());\n    // First do acts := -2 x scale x acts x outGrads / denoms\n    // so that the main routine only has to do an addition in its inner loop.\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n\n    dim3 threads2 = dim3(32, 4);\n    dim3 blocks2 = dim3(DIVUP(numImages,32*4) * imgSize, (numFilters / 4) * imgSize);\n\n    bool checkCaseBounds = (numImages % 128) != 0;\n    if (blocked) {\n        if (scaleTargets == 0 && scaleOutput == 1) {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kFRNormUndo2<4, 32, 4, false, true, true>, cudaFuncCachePreferL1);\n                kFRNormUndo2<4, 32, 4, false, true, true><<<blocks2, threads2, 0, stream>>>(outGrads.getTextureObject(), inputs.getTextureObject(), acts.getTextureObject(),\n                                                                        target.getDevData(), imgSize, numFilters, numImages, sizeF, addScale, powScale, minDiv,\n                                                                        scaleTargets, scaleOutput);\n            } else {\n                cudaFuncSetCacheConfig(kFRNormUndo2<4, 32, 4, false, false, true>, cudaFuncCachePreferL1);\n                kFRNormUndo2<4, 32, 4, false, false, true><<<blocks2, threads2, 0, stream>>>(outGrads.getTextureObject(), inputs.getTextureObject(), acts.getTextureObject(),\n                                                                        target.getDevData(), imgSize, numFilters, numImages, sizeF, addScale, powScale, minDiv,\n                                                                        scaleTargets, scaleOutput);\n            }\n        } else {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kFRNormUndo2<4, 32, 4, true, true, true>, cudaFuncCachePreferL1);\n                kFRNormUndo2<4, 32, 4, true, true, true><<<blocks2, threads2, 0, stream>>>(outGrads.getTextureObject(), inputs.getTextureObject(), acts.getTextureObject(),\n                                                                        target.getDevData(), imgSize, numFilters, numImages, sizeF, addScale, powScale, minDiv,\n                                                                        scaleTargets, scaleOutput);\n            } else {\n                cudaFuncSetCacheConfig(kFRNormUndo2<4, 32, 4, true, false, true>, cudaFuncCachePreferL1);\n                kFRNormUndo2<4, 32, 4, true, false, true><<<blocks2, threads2, 0, stream>>>(outGrads.getTextureObject(), inputs.getTextureObject(), acts.getTextureObject(),\n                                                                        target.getDevData(), imgSize, numFilters, numImages, sizeF, addScale, powScale, minDiv,\n                                                                        scaleTargets, scaleOutput);\n            }\n        }\n    } else {\n        if (scaleTargets == 0 && scaleOutput == 1) {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kFRNormUndo2<4, 32, 4, false, true, false>, cudaFuncCachePreferL1);\n                kFRNormUndo2<4, 32, 4, false, true, false><<<blocks2, threads2, 0, stream>>>(outGrads.getTextureObject(), inputs.getTextureObject(), acts.getTextureObject(),\n                                                                        target.getDevData(), imgSize, numFilters, numImages, sizeF, addScale, powScale, minDiv,\n                                                                        scaleTargets, scaleOutput);\n            } else {\n                cudaFuncSetCacheConfig(kFRNormUndo2<4, 32, 4, false, false, false>, cudaFuncCachePreferL1);\n                kFRNormUndo2<4, 32, 4, false, false, false><<<blocks2, threads2, 0, stream>>>(outGrads.getTextureObject(), inputs.getTextureObject(), acts.getTextureObject(),\n                                                                        target.getDevData(), imgSize, numFilters, numImages, sizeF, addScale, powScale, minDiv,\n                                                                        scaleTargets, scaleOutput);\n            }\n        } else {\n            if (checkCaseBounds) {\n                cudaFuncSetCacheConfig(kFRNormUndo2<4, 32, 4, true, true, false>, cudaFuncCachePreferL1);\n                kFRNormUndo2<4, 32, 4, true, true, false><<<blocks2, threads2, 0, stream>>>(outGrads.getTextureObject(), inputs.getTextureObject(), acts.getTextureObject(),\n                                                                        target.getDevData(), imgSize, numFilters, numImages, sizeF, addScale, powScale, minDiv,\n                                                                        scaleTargets, scaleOutput);\n            } else {\n                cudaFuncSetCacheConfig(kFRNormUndo2<4, 32, 4, true, false, false>, cudaFuncCachePreferL1);\n                kFRNormUndo2<4, 32, 4, true, false, false><<<blocks2, threads2, 0, stream>>>(outGrads.getTextureObject(), inputs.getTextureObject(), acts.getTextureObject(),\n                                                                        target.getDevData(), imgSize, numFilters, numImages, sizeF, addScale, powScale, minDiv,\n                                                                        scaleTargets, scaleOutput);\n            }\n        }\n    }\n\n    getLastCudaError(\"convResponseNormCrossMapUndo: kernel execution failed\");\n}\n\nvoid convResponseNormCrossMap(NVMatrix& images, NVMatrix& target, int numFilters, int sizeF, float addScale, float powScale, float minDiv, bool blocked) {\n    convContrastNormCrossMap(images, images, target, numFilters, sizeF, addScale, powScale, minDiv, blocked);\n}\n\n/*\n * images:      (numFilters, imgPixels, numImages)\n * denoms:      (numFilters, imgPixels, numImages) (out)\n * target:      (numFilters, imgPixels, numImages) (out)\n */\nvoid convResponseNormCrossMap(NVMatrix& images, NVMatrix& target, int numFilters, int sizeF, float addScale, float powScale, bool blocked) {\n    convContrastNormCrossMap(images, images, target, numFilters, sizeF, addScale, powScale, 1, blocked);\n}\n\ncudaTextureObject_t GetTensorTextureObject(caffe2::TensorCUDA* tensor) {\n  cudaTextureObject_t tex_obj;\n  cudaResourceDesc res_desc;\n  std::memset(&res_desc, 0, sizeof(res_desc));\n  res_desc.resType = cudaResourceTypeLinear;\n  res_desc.res.linear.devPtr = tensor->mutable_data<float>();\n  res_desc.res.linear.sizeInBytes = tensor->nbytes();\n  res_desc.res.linear.desc =\n      cudaCreateChannelDesc(32, 0, 0, 0, cudaChannelFormatKindFloat);\n  cudaTextureDesc tex_desc;\n  std::memset(&tex_desc, 0, sizeof(tex_desc));\n  CUDA_ENFORCE(\n      cudaCreateTextureObject(&tex_obj, &res_desc, &tex_desc, nullptr));\n  return tex_obj;\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconv3/src/filter_acts.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <vector>\n\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n#include \"../include/cudaconv2.cuh\"\n\n__device__ __forceinline__ void filterActs_YxX_color_preload_ty_4_tx_32_f_16_cc_3_setImgCoords(int fPidx, int imgLoadModPosY, int imgLoadModPosX,\n                                                                                        int imgSizeX, int filterSize, int& iPidx) {\n    int x = imgLoadModPosX + (fPidx) % filterSize;\n    int y = imgLoadModPosY + (fPidx) / filterSize;\n    iPidx = y >= 0 && y < imgSizeX && x >= 0 && x < imgSizeX ? y * imgSizeX + x : -1;\n}\n\n#define FA_COLOR3_IMPRELOAD(c,i) imPreload[c][i] = iPidxNext < 0 || (checkImgBounds && myImgIdx + i * B_X >= numImages) ? 0 : mm[c * imgPixels * imgStride + i * B_X];\n#define FA_COLOR3_IMPRELOAD_TX(c,i) imPreload[c][i] = iPidxNext < 0 || (checkImgBounds && myImgIdx + i * B_X >= numImages) ? 0 : tex1Dfetch<float>(images, imagesOffset2 + c * imgPixels * imgStride + i * B_X);\n\n\n/*\n * images:      (numImgColors, imgSizeY, imgSizeX, numImages) with stride given\n * filters:     (numFilterColors, filterPixels, numFilters) if conv\n *              (numModules, numFilterColors, filterPixels, numFilters) otherwise\n *\n * targets:     (numFilters, numModulesY, numModulesX, numImages)\n *\n */\ntemplate <int B_Y, int B_X, int imgsPerThread, int filtersPerThread, int numColors, int pixelCache,\n          bool scale, bool checkImgBounds>\n//__launch_bounds__(128,3)\n__global__ void filterActs_YxX_color_preload_ty_4_tx_32_i_4_f_16_px_4_cc_3_tex(cudaTextureObject_t images, cudaTextureObject_t filters, float* targets,\n                                       const int numImages, const int numFilters,\n                                       const int imgSizeY, const int imgSizeX, const int filterSize, const int paddingStart,\n                                       const int moduleStride,\n                                       const int numModulesY, const int numModulesX, const int imgStride,\n                                       const float scaleTargets, const float scaleOutputs,\n                                       const bool conv/*, const bool noloads*/) {\n    __shared__ float shFilters[numColors][pixelCache][B_Y * filtersPerThread]; // pre-load 1 pixel from B_Y*filtersPerThread filters\n    __shared__ float shImages[numColors][pixelCache][B_X * imgsPerThread]; // pre-load 1 pixel from B_X*imgsPerThread images\n    const int imgPixels = imgSizeY * imgSizeX;\n    const int filterPixels = filterSize * filterSize;\n    const int blocksPerModule = numFilters / (B_Y*filtersPerThread);\n    const int moduleIdx = blockIdx.y / blocksPerModule;\n    const int blockFilterIdx = filtersPerThread * B_Y * (blockIdx.y % blocksPerModule);\n\n    const int numModules = numModulesX * numModulesY;\n    // Another fun insanity: the % B_X makes things faster, even thought threadIdx.x is\n    // in the range 0..31. It appears that this allows the compiler to optimize?\n    const int tx = threadIdx.x % B_X;\n    const int ty = threadIdx.y % B_Y;\n    const int tidx = ty * B_X + threadIdx.x;\n\n    const int imgLoadModPosY = paddingStart + (moduleIdx / numModulesX) * moduleStride;\n    const int imgLoadModPosX = paddingStart + (moduleIdx % numModulesX) * moduleStride;\n\n    const int shFilterLoadY = tidx / (B_Y * filtersPerThread);\n    const int shFilterLoadX = tidx % (B_Y * filtersPerThread);\n    const int myImgIdx = blockIdx.x * B_X * imgsPerThread + threadIdx.x;\n\n//    images += myImgIdx;\n//    filters += blockFilterIdx\n//            + shFilterLoadY * numFilters + shFilterLoadX;\n//    if (!conv) { // NOTE: UNTESTED!\n//        filters += moduleIdx * numColors * filterPixels * numFilters;\n//    }\n\n    const int imagesOffset = myImgIdx;\n    const int filtersOffset = blockFilterIdx + shFilterLoadY * numFilters + shFilterLoadX\n                            + (conv ? 0 : moduleIdx * numColors * filterPixels * numFilters);\n\n    targets += moduleIdx * numImages\n            + (blockFilterIdx + threadIdx.y * filtersPerThread) * numImages * numModules\n            + myImgIdx;\n\n    float prod[imgsPerThread][filtersPerThread];\n    #pragma unroll\n    for(int i = 0; i < imgsPerThread; i++) {\n        #pragma unroll\n        for(int f = 0; f < filtersPerThread; f++) {\n            prod[i][f] = 0;\n        }\n    }\n\n    int iPidxNext;\n    float imPreload[numColors][imgsPerThread];\n    float fPreload[numColors][pixelCache*filtersPerThread/B_X];\n\n    #pragma unroll\n    for (int c = 0; c < numColors; ++c) {\n        #pragma unroll\n        for (int p = 0; p < pixelCache; p += B_X/filtersPerThread) {\n            if (p + shFilterLoadY < filterPixels) {\n                fPreload[c][p*filtersPerThread/B_X] = tex1Dfetch<float>(filters, filtersOffset + p * numFilters + c * numFilters * filterPixels);\n            } else{\n                fPreload[c][p*filtersPerThread/B_X] = 0;\n            }\n        }\n    }\n\n    filterActs_YxX_color_preload_ty_4_tx_32_f_16_cc_3_setImgCoords(ty, imgLoadModPosY, imgLoadModPosX, imgSizeX, filterSize, iPidxNext);\n\n    #pragma unroll\n    for (int c = 0; c < numColors; ++c) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (iPidxNext >= 0 && (!checkImgBounds || myImgIdx + i * B_X < numImages)) {\n                imPreload[c][i] = tex1Dfetch<float>(images, imagesOffset + (c * imgPixels + iPidxNext) * imgStride + i * B_X);\n            } else {\n                imPreload[c][i] =  0;\n            }\n        }\n    }\n\n    for (int p = 0; p < filterPixels; p += pixelCache) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            #pragma unroll\n            for (int c = 0; c < numColors; ++c) {\n                // NOTE: bank conflicts here!\n                shImages[c][ty][tx * imgsPerThread + i] = imPreload[c][i];\n            }\n        }\n\n        const int fPidxNext = p + pixelCache >= filterPixels ? 0 : p + pixelCache;\n        filterActs_YxX_color_preload_ty_4_tx_32_f_16_cc_3_setImgCoords(fPidxNext + ty, imgLoadModPosY, imgLoadModPosX, imgSizeX, filterSize, iPidxNext);\n\n//        const float* ff = &filters[numFilters * fPidxNext];\n//        const float* mm = &images[imgStride * iPidxNext];\n        const int filtersOffset2 = filtersOffset + numFilters * fPidxNext;\n        const int imagesOffset2 = imagesOffset + imgStride * iPidxNext;\n\n        FA_COLOR3_IMPRELOAD_TX(0,0);\n        FA_COLOR3_IMPRELOAD_TX(0,1);\n        FA_COLOR3_IMPRELOAD_TX(0,2);\n        FA_COLOR3_IMPRELOAD_TX(0,3);\n\n        #pragma unroll\n        for (int c = 0; c < numColors; ++c) {\n            #pragma unroll\n            for (int pp = 0; pp < pixelCache; pp += B_X/filtersPerThread) {\n                shFilters[c][pp + shFilterLoadY][shFilterLoadX] = fPreload[c][pp*filtersPerThread/B_X];\n            }\n        }\n\n        __syncthreads();\n        FA_COLOR3_IMPRELOAD_TX(1,0);\n        FA_COLOR3_IMPRELOAD_TX(1,1);\n        FA_COLOR3_IMPRELOAD_TX(1,2);\n        FA_COLOR3_IMPRELOAD_TX(1,3);\n        FA_COLOR3_IMPRELOAD_TX(2,0);\n        FA_COLOR3_IMPRELOAD_TX(2,1);\n        FA_COLOR3_IMPRELOAD_TX(2,2);\n        FA_COLOR3_IMPRELOAD_TX(2,3);\n        #pragma unroll\n        for (int c = 0; c < numColors; c++) {\n            #pragma unroll\n            for (int pp = 0; pp < pixelCache*filtersPerThread/B_X; pp++) {\n                fPreload[c][pp] = fPidxNext + pp*(B_X/filtersPerThread) + shFilterLoadY >= filterPixels ? 0 : tex1Dfetch<float>(filters, filtersOffset2 + c * numFilters* filterPixels + pp*(B_X/filtersPerThread) * numFilters);\n            }\n        }\n        #pragma unroll\n        for (int pp = 0; pp < pixelCache; pp++) {\n            #pragma unroll\n            for (int c = 0; c < numColors; c++) {\n                #pragma unroll\n                for(int f = 0; f < filtersPerThread; f++) {\n                    #pragma unroll\n                    for(int i = 0; i < imgsPerThread; i++) {\n                        prod[i][f] += shImages[c][pp][tx * imgsPerThread + i] * shFilters[c][pp][ty * filtersPerThread + f];\n                    }\n                }\n            }\n        }\n\n        __syncthreads();\n    }\n\n    if (scale) {\n        #pragma unroll\n        for (int f = 0; f < filtersPerThread; f++) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkImgBounds || myImgIdx + i * B_X < numImages) {\n                    targets[i * B_X + f * numImages * numModules] = scaleTargets * targets[i * B_X + f * numImages * numModules] + scaleOutputs * prod[i][f];\n                }\n            }\n        }\n    } else {\n        // Note: reversing order of these loops saves 2 registers, but costs time\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                if (!checkImgBounds || myImgIdx + i * B_X < numImages) {\n                    targets[i * B_X + f * numImages * numModules] = scaleOutputs * prod[i][f];\n                }\n            }\n        }\n    }\n}\n\n/*\n * images:      (numImgColors, imgSizeY, imgSizeX, numImages) with stride given\n * filters:     (numFilterColors, filterPixels, numFilters) if conv\n *              (numModules, numFilterColors, filterPixels, numFilters) otherwise\n *\n * targets:     (numFilters, numModulesY, numModulesX, numImages)\n *\n * This won't be pretty.\n */\ntemplate <int B_Y, int B_X, int imgsPerThread, int filtersPerThread, int numColors, int pixelCache,\n          bool scale, bool checkImgBounds>\n__global__ void filterActs_YxX_color_preload_ty_4_tx_32_i_4_f_12_px_4_cc_3_tex(cudaTextureObject_t images, cudaTextureObject_t filters, float* targets,\n                                       const int numImages, const int numFilters,\n                                       const int imgSizeY, const int imgSizeX, const int filterSize, const int paddingStart,\n                                       const int moduleStride,\n                                       const int numModulesY, const int numModulesX, const int imgStride,\n                                       const float scaleTargets, const float scaleOutputs,\n                                       const bool conv/*, const bool noloads*/) {\n    __shared__ float shFilters[numColors][pixelCache][B_Y * filtersPerThread]; // pre-load 1 pixel from B_Y*filtersPerThread filters\n    __shared__ float shImages[numColors][pixelCache][B_X * imgsPerThread]; // pre-load 1 pixel from B_X*imgsPerThread images\n    const int imgPixels = imgSizeY * imgSizeX;\n    const int filterPixels = filterSize * filterSize;\n    const int blocksPerModule = numFilters / (B_Y*filtersPerThread);\n    const int moduleIdx = blockIdx.y / blocksPerModule;\n    const int blockFilterIdx = filtersPerThread * B_Y * (blockIdx.y % blocksPerModule);\n\n    const int numModules = numModulesX * numModulesY;\n    // Another fun insanity: the % B_X makes things faster, even though threadIdx.x is\n    // in the range 0..31. It appears that this allows the compiler to optimize?\n    const int tx = threadIdx.x % B_X;\n    const int ty = threadIdx.y % B_Y;\n    const int tidx = ty * B_X + threadIdx.x;\n    const int warp = tidx / 32;\n\n    const int imgLoadModPosY = paddingStart + (moduleIdx / numModulesX) * moduleStride;\n    const int imgLoadModPosX = paddingStart + (moduleIdx % numModulesX) * moduleStride;\n\n    const int shFilterLoadY = tidx / (B_Y * filtersPerThread);\n    const int shFilterLoadX = tidx % (B_Y * filtersPerThread);\n    const int myImgIdx = blockIdx.x * B_X * imgsPerThread + threadIdx.x;\n\n//    images += myImgIdx;\n//    filters += blockFilterIdx\n//            + shFilterLoadY * numFilters + shFilterLoadX;\n//    if (!conv) { // NOTE: UNTESTED!\n//        filters += moduleIdx * numColors * filterPixels * numFilters;\n//    }\n\n    const int imagesOffset = myImgIdx;\n    const int filtersOffset = blockFilterIdx + shFilterLoadY * numFilters + shFilterLoadX\n                            + (conv ? 0 : moduleIdx * numColors * filterPixels * numFilters);\n\n    targets += moduleIdx * numImages\n            + (blockFilterIdx + threadIdx.y * filtersPerThread) * numImages * numModules\n            + myImgIdx;\n\n    float prod[imgsPerThread][filtersPerThread];\n    #pragma unroll\n    for(int i = 0; i < imgsPerThread; i++) {\n        #pragma unroll\n        for(int f = 0; f < filtersPerThread; f++) {\n            prod[i][f] = 0;\n        }\n    }\n\n    int iPidxNext;\n    float imPreload[numColors][imgsPerThread];\n    float fPreload[numColors][DIVUP(pixelCache*filtersPerThread,B_X)];\n\n    if (warp < 3) {\n        #pragma unroll\n        for (int c = 0; c < numColors; ++c) {\n            #pragma unroll\n            for (int p = 0; p < pixelCache; p += 2) {\n                if (p + shFilterLoadY < filterPixels) {\n                    fPreload[c][p/2] = tex1Dfetch<float>(filters, filtersOffset + p * numFilters + c * numFilters * filterPixels);\n                } else {\n                    fPreload[c][p/2] = 0;\n                }\n            }\n        }\n    }\n\n    filterActs_YxX_color_preload_ty_4_tx_32_f_16_cc_3_setImgCoords(ty, imgLoadModPosY, imgLoadModPosX, imgSizeX, filterSize, iPidxNext);\n\n    #pragma unroll\n    for (int c = 0; c < numColors; ++c) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (iPidxNext >= 0 && (!checkImgBounds || myImgIdx + i * B_X < numImages)) {\n                imPreload[c][i] = tex1Dfetch<float>(images, imagesOffset + (c * imgPixels + iPidxNext) * imgStride + i * B_X);\n            } else {\n                imPreload[c][i] =  0;\n            }\n        }\n    }\n\n    for (int p = 0; p < filterPixels; p += pixelCache) {\n        const int fPidxNext = p + pixelCache >= filterPixels ? 0 : p + pixelCache;\n        filterActs_YxX_color_preload_ty_4_tx_32_f_16_cc_3_setImgCoords(fPidxNext + ty, imgLoadModPosY, imgLoadModPosX, imgSizeX, filterSize, iPidxNext);\n\n        #pragma unroll\n        for (int c = 0; c < numColors; ++c) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                // NOTE: bank conflicts here!\n                shImages[c][ty][tx * imgsPerThread + i] = imPreload[c][i];\n            }\n        }\n\n        if (warp < 3) {\n            #pragma unroll\n            for (int c = 0; c < numColors; ++c) {\n                #pragma unroll\n                for (int pp = 0; pp < pixelCache; pp += 2) {\n                    shFilters[c][pp + shFilterLoadY][shFilterLoadX] = fPreload[c][pp/2];\n                }\n            }\n        }\n\n        __syncthreads();\n//        const float* ff = &filters[numFilters * fPidxNext];\n//        const float* mm = &images[imgStride * iPidxNext];\n        const int filtersOffset2 = filtersOffset + numFilters * fPidxNext;\n        const int imagesOffset2 = imagesOffset + imgStride * iPidxNext;\n\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; ++i) {\n            #pragma unroll\n            for (int c = 0; c < numColors; c++) {\n                FA_COLOR3_IMPRELOAD_TX(c,i);\n            }\n        }\n\n        #pragma unroll\n        for (int c = 0; c < numColors; c++) {\n            #pragma unroll\n            for (int pp = 0; pp < 2; pp++) {\n                fPreload[c][pp] = warp >= 3 || fPidxNext + pp*2 + shFilterLoadY >= filterPixels ? 0 : tex1Dfetch<float>(filters, filtersOffset2 +  c * numFilters* filterPixels + pp*2 * numFilters);\n            }\n            #pragma unroll\n            for (int pp = 0; pp < pixelCache; pp++) {\n                #pragma unroll\n                for(int i = 0; i < imgsPerThread; i++) {\n                    #pragma unroll\n                    for(int f = 0; f < filtersPerThread; f++) {\n                        prod[i][f] += shImages[c][pp][tx * imgsPerThread + i] * shFilters[c][pp][ty * filtersPerThread + f];\n                    }\n                }\n            }\n\n        }\n        __syncthreads();\n    }\n\n    if (scale) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                if (!checkImgBounds || myImgIdx + i * B_X < numImages) {\n                    targets[i * B_X + f * numImages * numModules] = scaleTargets * targets[i * B_X + f * numImages * numModules] + scaleOutputs * prod[i][f];\n                }\n            }\n        }\n    } else {\n        // Note: reversing order of these loops costs 2 registers, but saves time\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                if (!checkImgBounds || myImgIdx + i * B_X < numImages) {\n                    targets[i * B_X + f * numImages * numModules] = scaleOutputs * prod[i][f];\n                }\n            }\n        }\n    }\n}\n\n__device__ inline void filterActs_YxX_sparse2_preload_ty_4_tx_32_f_16_c_4_setPixelCoords(int filterSize, int imgSizeX, int imgLoadModPosY, int imgLoadModPosX, int imgY, int imgX, int& fPidx, int& iPidx) {\n    int filterPxY = imgY - imgLoadModPosY;\n    int filterPxX = imgX - imgLoadModPosX;\n    fPidx = filterPxY * filterSize + filterPxX;\n    iPidx = imgY * imgSizeX + imgX; // Pixel index in img\n}\n\n/*\n * images:      (numImgColors, imgSizeY, imgSizeX, numImages) with stride given\n * filters:     (numFilterColors, filterPixels, numFilters) if conv\n *              (numModules, numFilterColors, filterPixels, numFilters) otherwise\n *\n * targets:     (numFilters, numModulesY, numModulesX, numImages)\n *\n * Note: in git there's a 1.5% faster version of this which sues 167 registers instead of 154...\n * it's basically the same thing, but it doesn't do the next-pixel computation. It just avoids\n * pre-loading when it rolls over to the next pixel.\n */\ntemplate <int B_Y, int B_X, int imgsPerThread, int filtersPerThread, int colorCache,\n          bool scale, bool checkImgBounds>\n__global__ void filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4(float* images, float* filters, float* targets,\n                                       const int numImages, const int numFilters,\n                                       const int imgSizeY, const int imgSizeX, const int filterSize, const int paddingStart,\n                                       const int moduleStride,\n                                       const int numModulesY, const int numModulesX, const int imgStride, const int numImgColors,\n                                       const int numGroups,\n                                       const float scaleTargets, const float scaleOutputs,\n                                       const bool conv/*, const bool noloads*/) {\n    __shared__ float shFilters[colorCache][B_Y * filtersPerThread]; // pre-load 1 pixel from B_Y*filtersPerThread filters\n    __shared__ float shImages[colorCache][B_X * imgsPerThread]; // pre-load 1 pixel from B_X*imgsPerThread images\n    const int imgPixels = imgSizeY * imgSizeX;\n    const int filterPixels = filterSize * filterSize;\n    const int numFilterColors = numImgColors / numGroups;\n    const int blocksPerModule = numFilters / (B_Y*filtersPerThread);\n    const int moduleIdx = blockIdx.y / blocksPerModule;\n    const int blockFilterIdx = filtersPerThread * B_Y * (blockIdx.y % blocksPerModule);\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockGroupIdx = blockFilterIdx / numFiltersPerGroup;\n\n    const int numModules = numModulesX * numModulesY;\n    const int blockColorIdx = numFilterColors * blockGroupIdx;\n    // Another fun insanity: the % B_X makes things faster, even thought threadIdx.x is\n    // in the range 0..31. It appears that this allows the compiler to optimize?\n    const int tx = threadIdx.x % B_X;\n    const int ty = threadIdx.y % B_Y;\n    const int tidx = ty * B_X + threadIdx.x;\n\n    const int imgLoadModPosY = paddingStart + (moduleIdx / numModulesX) * moduleStride;\n    const int imgLoadModPosX = paddingStart + (moduleIdx % numModulesX) * moduleStride;\n\n    const int shFilterLoadY = tidx / (B_Y * filtersPerThread);\n    const int shFilterLoadX = tidx % (B_Y * filtersPerThread);\n    const int myImgIdx = blockIdx.x * B_X * imgsPerThread + threadIdx.x;\n\n    images += (blockColorIdx + threadIdx.y) * imgPixels * imgStride + myImgIdx;\n    filters +=blockFilterIdx\n            + shFilterLoadY * numFilters * filterPixels + shFilterLoadX;\n    if (!conv) {\n        filters += moduleIdx * numFilterColors * filterPixels * numFilters;\n    }\n\n    targets += moduleIdx * numImages\n            + (blockFilterIdx + threadIdx.y * filtersPerThread) * numImages * numModules\n            + myImgIdx;\n\n    float prod[imgsPerThread][filtersPerThread];\n//    float fCache[filtersPerThread];\n    #pragma unroll\n    for(int i = 0; i < imgsPerThread; i++) {\n        #pragma unroll\n        for(int f = 0; f < filtersPerThread; f++) {\n            prod[i][f] = 0;\n        }\n    }\n    // NOTE: these max/min functions increase register usage as compared to my macros\n    const int imgStartX = max(0, imgLoadModPosX);\n    const int imgStartY = max(0, imgLoadModPosY);\n    const int imgEndX = min(imgLoadModPosX + filterSize, imgSizeX);\n    const int imgEndY = min(imgLoadModPosY + filterSize, imgSizeY);\n//    __shared__ int imgPos[]\n\n    int fPidx, iPidx;\n    float imPreload[imgsPerThread];\n    float fPreload[colorCache*filtersPerThread/B_X];\n//    float fCache[filtersPerThread];\n\n    filterActs_YxX_sparse2_preload_ty_4_tx_32_f_16_c_4_setPixelCoords(filterSize, imgSizeX, imgLoadModPosY, imgLoadModPosX, imgStartY, imgStartX, fPidx, iPidx);\n\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        if (!checkImgBounds || myImgIdx + i * B_X < numImages) {\n            imPreload[i] = images[imgStride * iPidx + i * B_X];\n        } else {\n            imPreload[i] = 0;\n        }\n    }\n    if (/*B_X % filtersPerThread == 0 ||*/ shFilterLoadY < B_X/filtersPerThread) { // This if statement reduces reg usage..\n        #pragma unroll\n        for (int c = 0; c < colorCache; c += B_X/filtersPerThread) {\n            fPreload[c*filtersPerThread/B_X] = filters[(c * filterPixels + fPidx) * numFilters];\n        }\n    }\n    for (int imgY = imgStartY; imgY < imgEndY; ++imgY) {\n//        const int filterPxY = imgY - imgLoadModPosY;\n        for (int imgX = imgStartX; imgX < imgEndX; ++imgX) {\n//            const int filterPxX = imgX - imgLoadModPosX;\n//            const int p = filterPxY * filterSize + filterPxX;\n//            const int pixIdx = imgY * imgSizeX + imgX;// Pixel index in img\n//            setPixelCoords(filterSize, imgSizeX, imgLoadModPosY, imgLoadModPosX, imgY, imgX, &p, &pixIdx);\n//            float* m = &images[imgStride * pixIdx];\n            const bool lastPixel = imgY == imgEndY - 1 && imgX == imgEndX - 1;\n            int imgYNext = imgY;\n            int imgXNext = imgX;\n            int fPidxNext, iPidxNext;\n            if (!lastPixel) {\n                imgYNext = imgY + (imgX + 1 == imgEndX);\n                imgXNext = imgX + 1 == imgEndX ? imgStartX : imgX + 1;\n            }\n            filterActs_YxX_sparse2_preload_ty_4_tx_32_f_16_c_4_setPixelCoords(filterSize, imgSizeX, imgLoadModPosY, imgLoadModPosX, imgYNext, imgXNext, fPidxNext, iPidxNext);\n            for (int oc = 0; oc < numFilterColors; oc += colorCache) { // oc stands for outer color (loop)\n                const float* ff = &filters[numFilters * ((oc + colorCache) * filterPixels + fPidx)];\n                const float* mm = &images[imgStride * ((oc + colorCache) * imgPixels + iPidx)];\n                if (oc == numFilterColors - colorCache) {\n                    ff = &filters[fPidxNext * numFilters];\n                    mm = &images[iPidxNext * imgStride];\n                    fPidx = fPidxNext;\n                    iPidx = iPidxNext;\n                }\n\n                #pragma unroll\n                for (int c = 0; c < colorCache; c += B_X/filtersPerThread) {\n                    shFilters[c + shFilterLoadY][shFilterLoadX] = fPreload[c*filtersPerThread/B_X];\n                }\n\n                #pragma unroll\n                for (int i = 0; i < imgsPerThread; i++) {\n                    // NOTE: bank conflicts here!\n                    shImages[ty][tx * imgsPerThread + i] = imPreload[i];\n                }\n                imPreload[0] = (checkImgBounds && myImgIdx + 0 * B_X >= numImages) ? 0 : mm[0 * B_X];\n                imPreload[1] = (checkImgBounds && myImgIdx + 1 * B_X >= numImages) ? 0 : mm[1 * B_X];\n                imPreload[2] = (checkImgBounds && myImgIdx + 2 * B_X >= numImages) ? 0 : mm[2 * B_X];\n\n                __syncthreads();\n\n                #pragma unroll\n                for(int i = 0; i < imgsPerThread; i++) {\n                    #pragma unroll\n                    for(int f = 0; f < filtersPerThread; f++) {\n                        prod[i][f] += shImages[0][threadIdx.x * imgsPerThread + i] * shFilters[0][threadIdx.y * filtersPerThread + f];\n                    }\n                }\n\n                fPreload[0] = ff[0];\n\n                #pragma unroll\n                for(int i = 0; i < imgsPerThread; i++) {\n                    #pragma unroll\n                    for(int f = 0; f < filtersPerThread; f++) {\n                        prod[i][f] += shImages[1][threadIdx.x * imgsPerThread + i] * shFilters[1][threadIdx.y * filtersPerThread + f];\n                    }\n                }\n\n                fPreload[1] = ff[(B_X/filtersPerThread * filterPixels) * numFilters];\n\n                #pragma unroll\n                for(int i = 0; i < imgsPerThread; i++) {\n                    #pragma unroll\n                    for(int f = 0; f < filtersPerThread; f++) {\n                        prod[i][f] += shImages[2][threadIdx.x * imgsPerThread + i] * shFilters[2][threadIdx.y * filtersPerThread + f];\n                    }\n                }\n\n                imPreload[3] = (checkImgBounds && myImgIdx + 3 * B_X >= numImages) ? 0 : mm[3 * B_X];\n\n                #pragma unroll\n                for(int i = 0; i < imgsPerThread; i++) {\n                    #pragma unroll\n                    for(int f = 0; f < filtersPerThread; f++) {\n                        prod[i][f] += shImages[3][threadIdx.x * imgsPerThread + i] * shFilters[3][threadIdx.y * filtersPerThread + f];\n                    }\n                }\n                __syncthreads();\n            }\n        }\n    }\n\n    if (scale) {\n        #pragma unroll\n        for (int f = 0; f < filtersPerThread; f++) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkImgBounds || myImgIdx + i * B_X < numImages) {\n                    targets[i * B_X + f * numImages * numModules] = scaleTargets * targets[i * B_X + f * numImages * numModules] + scaleOutputs * prod[i][f];\n                }\n            }\n        }\n    } else {\n        // Note: reversing order of these loops saves 2 registers, but costs time\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                if (!checkImgBounds || myImgIdx + i * B_X < numImages) {\n                    targets[i * B_X + f * numImages * numModules] = scaleOutputs * prod[i][f];\n                }\n            }\n        }\n    }\n}\n\n/*****************************Function Revision Record*****************************\n * Author: Tencent BestImage Team(ankerguo@tencent.com)                           *\n * Date:   2015-05-18                                                             *\n * Reason: Optimizing kernel to get faster speed according to GPU features        *\n * Method:                                                                        *\n *         1. reorganizing data structure to avoid bank conflict;                 *\n *         2. using vectorized data type;                                         *\n *         3. improving instruction-level parallelism;                            *\n *         4. removing redundant 'if' branches;                                   *\n *         5. removing local variables to save registers.                         *\n *********************************************************************************/\n\n/*\n * images:      (numImgColors, imgSizeY, imgSizeX, numImages) with stride given\n * filters:     (numFilterColors, filterPixels, numFilters) if conv\n *              (numModules, numFilterColors, filterPixels, numFilters) otherwise\n *\n * targets:     (numFilters, numModulesY, numModulesX, numImages)\n *\n */\ntemplate <int B_Y, int B_X, int imgsPerThread, int filtersPerThread, int colorCache,\n          bool scale, bool checkImgBounds>\n__global__ void \n__launch_bounds__(128, 4)\nfilterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4_tex(cudaTextureObject_t images, cudaTextureObject_t filters, float* targets,\n                                       const int numImages, const int numFilters,\n                                       const int imgSizeY, const int imgSizeX, const int filterSize, const int paddingStart,\n                                       const int moduleStride,\n                                       const int numModulesY, const int numModulesX, const int imgStride, const int numImgColors,\n                                       const int numGroups,\n                                       const float scaleTargets, const float scaleOutputs,\n                                       const bool conv/*, const bool noloads*/) {\n    // avoid bank conflict by reorganizing the data structure and improve the band width by using 'float2' instead of 'float'\n    __shared__ float2 shFilters[colorCache / 2][B_Y * filtersPerThread]; // pre-load 1 pixel from B_Y*filtersPerThread filters\n    __shared__ float2 shImages[colorCache][B_X * imgsPerThread / 2]; // pre-load 1 pixel from B_X*imgsPerThread images\n    const int imgPixels = imgSizeY * imgSizeX;\n    const int filterPixels = filterSize * filterSize;\n    const int numFilterColors = numImgColors / numGroups;\n    const int blocksPerModule = numFilters / (B_Y*filtersPerThread);\n    const int moduleIdx = blockIdx.y / blocksPerModule;\n    const int blockFilterIdx = filtersPerThread * B_Y * (blockIdx.y % blocksPerModule);\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockGroupIdx = blockFilterIdx / numFiltersPerGroup;\n\n    const int numModules = numModulesX * numModulesY;\n    const int blockColorIdx = numFilterColors * blockGroupIdx;\n    // Another fun insanity: the % B_X makes things faster, even thought threadIdx.x is\n    // in the range 0..31. It appears that this allows the compiler to optimize?\n    const int tx = threadIdx.x % B_X;\n    const int ty = threadIdx.y % B_Y;\n    //const int tidx = ty * B_X + threadIdx.x; // reduce one register\n\n    const int imgLoadModPosY = paddingStart + (moduleIdx / numModulesX) * moduleStride;\n    const int imgLoadModPosX = paddingStart + (moduleIdx % numModulesX) * moduleStride;\n\n    // reduce two registers\n    //const int shFilterLoadY = tidx / (B_Y * filtersPerThread);\n    //const int shFilterLoadX = tidx % (B_Y * filtersPerThread);\n    const int myImgIdx = blockIdx.x * B_X * imgsPerThread + tx;\n    const int imgOffset = (blockColorIdx + ty) * imgPixels * imgStride + myImgIdx;\n\n//    images += (blockColorIdx + threadIdx.y) * imgPixels * imgStride + myImgIdx;\n    const int filterOffset = blockFilterIdx\n            + ((ty * B_X + tx) / (B_Y * filtersPerThread)) * numFilters * filterPixels + ((ty * B_X + tx) % (B_Y * filtersPerThread)) + (conv ? 0 : moduleIdx * numFilterColors * filterPixels * numFilters);\n//    filters +=blockFilterIdx\n//            + shFilterLoadY * numFilters * filterPixels + shFilterLoadX;\n//    if (!conv) {\n//        filters += moduleIdx * numFilterColors * filterPixels * numFilters;\n//    }\n\n    targets += moduleIdx * numImages\n            + (blockFilterIdx + threadIdx.y * filtersPerThread) * numImages * numModules\n            + myImgIdx;\n\n    // combine two registers into one\n    const int numModImages = numModules * numImages;\n    float prod[imgsPerThread][filtersPerThread];\n//    float fCache[filtersPerThread];\n    #pragma unroll\n    for(int i = 0; i < imgsPerThread; i++) {\n        #pragma unroll\n        for(int f = 0; f < filtersPerThread; f++) {\n            prod[i][f] = 0;\n        }\n    }\n    // NOTE: these max/min functions increase register usage as compared to my macros\n    const int imgStartX = max(0, imgLoadModPosX);\n    const int imgStartY = max(0, imgLoadModPosY);\n    const int imgEndX = min(imgLoadModPosX + filterSize, imgSizeX);\n    const int imgEndY = min(imgLoadModPosY + filterSize, imgSizeY);\n//    __shared__ int imgPos[]\n\n    int fPidx, iPidx;\n    float imPreload[imgsPerThread]; // [4]\n    float fPreload[colorCache*filtersPerThread/B_X]; // [2]\n//    float fCache[filtersPerThread];\n\n    filterActs_YxX_sparse2_preload_ty_4_tx_32_f_16_c_4_setPixelCoords(filterSize, imgSizeX, imgLoadModPosY, imgLoadModPosX, imgStartY, imgStartX, fPidx, iPidx);\n\n    // remove redundant conditions\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        imPreload[i]  = tex1Dfetch<float>(images, imgOffset + imgStride * iPidx + i * B_X);\n    }\n\n    #pragma unroll\n    for (int c = 0; c < colorCache; c += B_X/filtersPerThread) {\n        fPreload[c*filtersPerThread/B_X] = tex1Dfetch<float>(filters, filterOffset + (c * filterPixels + fPidx) * numFilters);\n    }\n    for (int imgY = imgStartY; imgY < imgEndY; ++imgY) {\n//        const int filterPxY = imgY - imgLoadModPosY;\n        for (int imgX = imgStartX; imgX < imgEndX; ++imgX) {\n//            const int filterPxX = imgX - imgLoadModPosX;\n//            const int p = filterPxY * filterSize + filterPxX;\n//            const int pixIdx = imgY * imgSizeX + imgX;// Pixel index in img\n//            setPixelCoords(filterSize, imgSizeX, imgLoadModPosY, imgLoadModPosX, imgY, imgX, &p, &pixIdx);\n//            float* m = &images[imgStride * pixIdx];\n            const bool lastPixel = imgY == imgEndY - 1 && imgX == imgEndX - 1;\n            int imgYNext = imgY;\n            int imgXNext = imgX;\n            int fPidxNext, iPidxNext;\n            if (!lastPixel) {\n                imgYNext = imgY + (imgX + 1 == imgEndX);\n                imgXNext = imgX + 1 == imgEndX ? imgStartX : imgX + 1;\n            }\n            filterActs_YxX_sparse2_preload_ty_4_tx_32_f_16_c_4_setPixelCoords(filterSize, imgSizeX, imgLoadModPosY, imgLoadModPosX, imgYNext, imgXNext, fPidxNext, iPidxNext);\n            for (int oc = 0; oc < numFilterColors; oc += colorCache) { // oc stands for outer color (loop)\n                // store the preloaded pixel of filter and image into shared memory\n                shFilters[(ty * B_X + tx) / (B_Y * filtersPerThread)][(ty * B_X + tx) % (B_Y * filtersPerThread)].x = fPreload[0];\n                shFilters[(ty * B_X + tx) / (B_Y * filtersPerThread)][(ty * B_X + tx) % (B_Y * filtersPerThread)].y = fPreload[1];\n                shImages[ty][tx].x = imPreload[0];\n                shImages[ty][tx].y = imPreload[1];                \n                shImages[ty][tx+B_X].x = imPreload[2]; \n                shImages[ty][tx+B_X].y = imPreload[3];\n\n                int imgOffset2 = imgOffset + imgStride * ((oc + colorCache) * imgPixels + iPidx);\n                int filterOffset2 = filterOffset + numFilters * ((oc + colorCache) * filterPixels + fPidx);\n                if (oc == numFilterColors - colorCache) {\n                    filterOffset2 = filterOffset + fPidxNext * numFilters;\n                    imgOffset2 = imgOffset + iPidxNext * imgStride;\n                    fPidx = fPidxNext;\n                    iPidx = iPidxNext;\n                }\n\n                // preload one pixel of filter and image from texture, and no need to check 'checkImgBounds' with all callers setting it as false\n                imPreload[0] = tex1Dfetch<float>(images, imgOffset2);\n                imPreload[1] = tex1Dfetch<float>(images, imgOffset2 + B_X);\n                imPreload[2] = tex1Dfetch<float>(images, imgOffset2 + 2 * B_X);\n                imPreload[3] = tex1Dfetch<float>(images, imgOffset2 + 3 * B_X);\n                fPreload[0] = tex1Dfetch<float>(filters, filterOffset2);\n                fPreload[1] = tex1Dfetch<float>(filters, filterOffset2 + 2 * filterPixels * numFilters);\n\n                __syncthreads();\n\n                // put together the instructions with same type to improve instruction-level parallelism \n                // calculate the convolution between images and filters\n                #pragma unroll \n                for (int f = 0; f < filtersPerThread; f++) {\n                    #pragma unroll\n                    for (int r = 0; r < colorCache / 2; r++) {\n                        prod[0][f] += shImages[r][tx].x      * shFilters[r][ty*filtersPerThread+f].x;\n                        prod[1][f] += shImages[r][tx].y      * shFilters[r][ty*filtersPerThread+f].x;\n                        prod[2][f] += shImages[r][tx+B_X].x   * shFilters[r][ty*filtersPerThread+f].x;\n                        prod[3][f] += shImages[r][tx+B_X].y   * shFilters[r][ty*filtersPerThread+f].x;\n                        prod[0][f] += shImages[r+2][tx].x    * shFilters[r][ty*filtersPerThread+f].y;\n                        prod[1][f] += shImages[r+2][tx].y    * shFilters[r][ty*filtersPerThread+f].y;\n                        prod[2][f] += shImages[r+2][tx+B_X].x * shFilters[r][ty*filtersPerThread+f].y;\n                        prod[3][f] += shImages[r+2][tx+B_X].y * shFilters[r][ty*filtersPerThread+f].y;\n                    }\n                }\n                __syncthreads();\n            }\n        }\n    }\n\n    if (scale) {\n        #pragma unroll\n        for (int f = 0; f < filtersPerThread; f++) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                // remove the redundant condition for less registers\n                targets[i * B_X + f * numModImages] = scaleTargets * targets[i * B_X + f * numModImages] + scaleOutputs * prod[i][f];\n            }\n        }\n    } else {\n        // Note: reversing order of these loops saves 2 registers, but costs time\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                // remove the redundant condition for less registers\n                targets[i * B_X + f * numModImages] = scaleOutputs * prod[i][f];\n            }\n        }\n    }\n}\n\n/*\n * Block size B_YxB_X. Each block applies B_Y * filtersPerThread filters to B_X * imgsPerThread images.\n * threadIdx.x determines image\n * threadIdx.y determines filter\n *\n * blockIdx.x determines image batch of B_X * imgsPerThread\n * blockIdx.y determines filter batch of module and B_Y * filtersPerThread\n *\n * images:      (numColors, imgSizeY, imgSizeX, numImages) with stride given\n * filters:     (numColors, filterPixels, numFilters) if conv\n *              (numModules, numColors, filterPixels, numFilters) otherwise\n *\n * targets:     (numFilters, numModulesY, numModulesX, numImages)\n *\n *\n * Number of filters per module should be divisible by B_Y * filtersPerThread\n * checkImgBounds indicates whether number of images is divisible by B_X * imgsPerThread\n *\n * The imgSize here is the size of the actual image without the padding.\n *\n */\n template <int B_Y, int B_X, int imgsPerThread, int filtersPerThread, int numColors, int pixelCache,\n          bool scale, bool checkImgBounds>\n__global__ void filterActs_YxX_color(float* images, float* filters, float* targets,\n                                   const int numImages, const int numFilters,\n                                   const int imgSizeY, const int imgSizeX, const int filterSize, const int paddingStart,\n                                   const int moduleStride,\n                                   const int numModulesY, const int numModulesX, const int imgStride,\n                                   const float scaleTargets, const float scaleOutputs,\n                                   const bool conv) {\n    __shared__ float shFilters[pixelCache*numColors][B_Y * filtersPerThread]; // pre-load pixelCache pixels from B_Y*filtersPerThread filters\n    __shared__ float shImages[pixelCache*numColors][B_X * imgsPerThread]; // pre-load pixelCache pixels from B_X*imgsPerThread images\n    const int imgPixels = imgSizeY * imgSizeX;\n    const int filterPixels = filterSize * filterSize;\n\n    const int blocksPerModule = numFilters / (B_Y*filtersPerThread);\n    const int moduleIdx = blockIdx.y / blocksPerModule;\n    const int blockFilterIdx = blockIdx.y % blocksPerModule;\n\n    const int tidx = threadIdx.y * B_X + threadIdx.x;\n\n    const int imgLoadModPosY = paddingStart + (moduleIdx / numModulesX) * moduleStride;\n    const int imgLoadModPosX = paddingStart + (moduleIdx % numModulesX) * moduleStride;\n    const int numModules = numModulesY * numModulesX;\n    const int shFilterLoadY = tidx / (B_Y * filtersPerThread);\n    const int shFilterLoadX = tidx % (B_Y * filtersPerThread);\n    const int myImgIdx = blockIdx.x * B_X * imgsPerThread + threadIdx.x;\n    images += myImgIdx;\n    filters += filtersPerThread * B_Y * blockFilterIdx\n             + shFilterLoadY * numFilters + shFilterLoadX;\n    if (!conv) {\n        filters += moduleIdx * numColors * filterPixels * numFilters;\n    }\n\n    targets += moduleIdx * numImages\n            + (blockFilterIdx * B_Y * filtersPerThread + threadIdx.y*filtersPerThread) * numImages * numModulesY * numModulesX\n            + myImgIdx;\n\n\n    float prod[filtersPerThread][imgsPerThread];\n    #pragma unroll\n    for(int f = 0; f < filtersPerThread; f++) {\n        #pragma unroll\n        for(int g = 0; g < imgsPerThread; g++) {\n            prod[f][g] = 0;\n        }\n    }\n    //float* shImgLoad = &shImages[0][threadIdx.x];\n    for (int p = 0; p < filterPixels; p += pixelCache) {\n        /*\n         * Load pixelCache pixels from B_Y*filtersPerThread filters\n         * This condition covers the case when B_X is not divisible by filtersPerThread.\n         * In this case, not all of the threads will participate in the loading operation.\n         * This ensures that in each loop iteration, an integer number of rows of shFilters\n         * are filled, which makes indexing simple.\n         */\n        if (B_X % filtersPerThread == 0 || shFilterLoadY < B_X/filtersPerThread) {\n            #pragma unroll\n            for (int p2 = 0; p2 < pixelCache; p2 += B_X/filtersPerThread) {\n                const bool omit = pixelCache % (B_X / filtersPerThread) == 0;\n                const int preloadPx = shFilterLoadY + p2;\n                if (omit || preloadPx < pixelCache) {\n                    if (p + preloadPx < filterPixels) {\n                        #pragma unroll\n                        for (int c = 0; c < numColors; c++) {\n                            shFilters[shFilterLoadY + p2 + c * pixelCache][shFilterLoadX] = filters[(c * filterPixels + p + p2) * numFilters];\n                        }\n                    } else {\n                        #pragma unroll\n                        for (int c = 0; c < numColors; c++) {\n                            shFilters[shFilterLoadY + p2 + c * pixelCache][shFilterLoadX] = 0;\n                        }\n                    }\n                }\n            }\n        }\n\n        /*\n         * Load pixelCache pixels from B_X*imgsPerThread images.\n         */\n        #pragma unroll\n        for (int ly = 0; ly < pixelCache; ly += B_Y) {\n            const int preloadPx = ly + threadIdx.y;\n            const int pixIdx = p + preloadPx;\n            const bool omit = pixelCache % B_Y == 0; // Compile-time condition\n            /*\n             * Don't load any image pixels corresponding to filter pixels that don't exist.\n             */\n            if (pixIdx < filterPixels && (omit || preloadPx < pixelCache)) {\n                const int x = imgLoadModPosX + pixIdx % filterSize;\n                const int y = imgLoadModPosY + pixIdx / filterSize;\n\n                if (y >= 0 && y < imgSizeY && x >= 0 && x < imgSizeX) {\n                    float* m = &images[imgStride * (y * imgSizeX + x)];\n\n                    #pragma unroll\n                    for (int c = 0; c < numColors; c++) {\n                        #pragma unroll\n                        for (int i = 0; i < imgsPerThread; i++) {\n                            if (!checkImgBounds || myImgIdx + i * B_X < numImages) {\n                                shImages[preloadPx + c * pixelCache][threadIdx.x * imgsPerThread + i] = m[c * imgStride * imgPixels + i * B_X];\n                            } else {\n                                shImages[preloadPx + c * pixelCache][threadIdx.x * imgsPerThread + i] = 0;\n                            }\n                        }\n                    }\n                } else { // Padding\n                    #pragma unroll\n                    for (int i = 0; i < imgsPerThread; i++) {\n                        #pragma unroll\n                        for (int c = 0; c < numColors; c++) {\n                            shImages[preloadPx + c * pixelCache][threadIdx.x * imgsPerThread + i] = 0;\n                        }\n                    }\n                }\n            }\n        }\n\n        __syncthreads();\n\n        #pragma unroll\n        for (int i = 0; i < pixelCache*numColors; i++) {\n            #pragma unroll\n            for(int f = 0; f < filtersPerThread; f++) {\n                #pragma unroll\n                for(int g = 0; g < imgsPerThread; g++) {\n                    prod[f][g] += shImages[i][g + threadIdx.x * imgsPerThread] * shFilters[i][threadIdx.y * filtersPerThread + f];\n                }\n            }\n        }\n        __syncthreads();\n    }\n\n    if (scale) {\n        #pragma unroll\n        for (int f = 0; f < filtersPerThread; f++) {\n            #pragma unroll\n            for (int g = 0; g < imgsPerThread; g++) {\n                if (!checkImgBounds || myImgIdx + g * B_X < numImages) {\n                    targets[g * B_X + f * numImages * numModules] = scaleTargets * targets[g * B_X + f * numImages * numModules] + scaleOutputs * prod[f][g];\n                }\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int g = 0; g < imgsPerThread; g++) {\n            if (!checkImgBounds || myImgIdx + g * B_X < numImages) {\n                #pragma unroll\n                for (int f = 0; f < filtersPerThread; f++) {\n                    targets[g * B_X + f * numImages * numModules] = scaleOutputs * prod[f][g];\n                }\n            }\n        }\n    }\n}\n\n/*\n * Block size B_YxB_X. Each block applies B_Y * filtersPerThread filters to B_X * imgsPerThread images.\n * threadIdx.x determines image\n * threadIdx.y determines filter\n *\n * blockIdx.x determines image batch of B_X * imgsPerThread\n * blockIdx.y determines filter batch of B_Y * filtersPerThread\n *\n * images:      (numImgColors, imgSizeY, imgSizeX, numImages) with stride given\n * filters:     (numFilterColors, filterPixels, numFilters) if conv\n *              (numModules, numFilterColors, filterPixels, numFilters) otherwise\n *\n * targets:     (numFilters, numModulesY, numModulesX, numImages)\n *\n * B_Y one of 4, 8, 16\n * B_X one of 16, 32\n * imgsPerThread one of 1, 2, 4\n * filtersPerThread one of 1, 2, 4, 8\n * colorCache: how many colors to put into shmem\n *\n * numFilters should be divisible by B_Y * filtersPerThread\n * numImages be divisible by B_X * imgsPerThread\n * numFilterColors should be divisible by colorCache.\n * numImgColors must be even.\n * numFilters must be divisible by numGroups.\n * no restrictions on pixelCache\n * The imgSize here is the size of the actual image without the padding.\n * As always, try to make B_X * imgsPerThread == B_Y * filtersPerThread for maximum efficiency.\n *\n */\ntemplate <int B_Y, int B_X, int imgsPerThread, int filtersPerThread, int colorCache,\n          bool scale, bool checkImgBounds>\n__global__ void filterActs_YxX_sparse2(float* images, float* filters, float* targets,\n                                       const int numImages, const int numFilters,\n                                       const int imgSizeY, const int imgSizeX, const int filterSize, const int paddingStart,\n                                       const int moduleStride,\n                                       const int numModulesY, const int numModulesX, const int imgStride, const int numImgColors,\n                                       const int numGroups, \n                                       const float scaleTargets, const float scaleOutputs,\n                                       const bool conv) {\n    __shared__ float shFilters[colorCache][B_Y * filtersPerThread]; // pre-load 1 pixel from B_Y*filtersPerThread filters\n    __shared__ float shImages[colorCache][B_X * imgsPerThread]; // pre-load 1 pixel from B_X*imgsPerThread images\n    const int imgPixels = imgSizeY * imgSizeX;\n    const int filterPixels = filterSize * filterSize;\n    const int numFilterColors = numImgColors / numGroups;\n    const int blocksPerModule = numFilters / (B_Y*filtersPerThread);\n    const int moduleIdx = blockIdx.y / blocksPerModule;\n    const int blockFilterIdx = filtersPerThread * B_Y * (blockIdx.y % blocksPerModule);\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockGroupIdx = blockFilterIdx / numFiltersPerGroup;\n\n    const int numModules = numModulesX * numModulesY;\n    const int blockColorIdx = numFilterColors * blockGroupIdx;\n\n    const int tidx = threadIdx.y * B_X + threadIdx.x;\n\n    const int imgLoadModPosY = paddingStart + (moduleIdx / numModulesX) * moduleStride;\n    const int imgLoadModPosX = paddingStart + (moduleIdx % numModulesX) * moduleStride;\n\n    const int shFilterLoadY = tidx / (B_Y * filtersPerThread);\n    const int shFilterLoadX = tidx % (B_Y * filtersPerThread);\n    const int myImgIdx = blockIdx.x * B_X * imgsPerThread + threadIdx.x;\n\n    images += (blockColorIdx + threadIdx.y) * imgPixels * imgStride + myImgIdx;\n    filters +=blockFilterIdx\n            + shFilterLoadY * numFilters * filterPixels + shFilterLoadX;\n    if (!conv) {\n        filters += moduleIdx * numFilterColors * filterPixels * numFilters;\n    }\n\n    targets += moduleIdx * numImages\n            + (blockFilterIdx + threadIdx.y) * numImages * numModules\n            + myImgIdx;\n\n    float prod[filtersPerThread][imgsPerThread];\n    #pragma unroll\n    for(int f = 0; f < filtersPerThread; f++) {\n        #pragma unroll\n        for(int g = 0; g < imgsPerThread; g++) {\n            prod[f][g] = 0;\n        }\n    }\n    const int imgStartX = MAX(0, imgLoadModPosX);\n    const int imgStartY = MAX(0, imgLoadModPosY);\n    const int imgEndX = MIN(imgLoadModPosX + filterSize, imgSizeX);\n    const int imgEndY = MIN(imgLoadModPosY + filterSize, imgSizeY);\n//    __shared__ int imgPos[]\n\n    for (int imgY = imgStartY; imgY < imgEndY; ++imgY) {\n        const int filterPxY = imgY - imgLoadModPosY;\n        for (int imgX = imgStartX; imgX < imgEndX; ++imgX) {\n            const int filterPxX = imgX - imgLoadModPosX;\n            const int p = filterPxY * filterSize + filterPxX;\n            for (int oc = 0; oc < numFilterColors; oc += colorCache) { // oc stands for outer color (loop)\n\n                /*\n                 * Load a pixel from B_Y*filtersPerThread filters\n                 * This condition covers the case when B_X is not divisible by filtersPerThread.\n                 * In this case, not all of the threads will participate in the loading operation.\n                 * This ensures that in each loop iteration, an integer number of rows of shFilters\n                 * are filled, which makes indexing simple.\n\n                 * nvcc is behaving in a completely insane way: removing this condition under\n                 * template parameters that guarantee it to be true actually slows down\n                 * the computation.\n                 *\n                 */\n                if (/*B_X % filtersPerThread == 0 ||*/ shFilterLoadY < B_X/filtersPerThread) {\n                    #pragma unroll\n                    for (int c = 0; c < colorCache; c += B_X/filtersPerThread) {\n                        if (colorCache % (B_X/filtersPerThread) == 0 || c + shFilterLoadY < colorCache) {\n                            shFilters[c + shFilterLoadY][shFilterLoadX] = filters[((oc+c) * filterPixels + p) * numFilters];\n                        }\n                    }\n                }\n\n                /*\n                 * Load a pixel from B_X*imgsPerThread images.\n                 */\n                const int pixIdx = imgY * imgSizeX + imgX;// Pixel index in img\n\n                float* m = &images[imgStride * (oc * imgPixels + pixIdx)];\n                #pragma unroll\n                for (int c = 0; c < colorCache; c += B_Y) {\n                    if (colorCache % B_Y == 0 || threadIdx.y + c < colorCache) {\n                        #pragma unroll\n                        for (int i = 0; i < imgsPerThread; i++) {\n                            if (!checkImgBounds || myImgIdx + i * B_X < numImages) {\n                                shImages[c + threadIdx.y][threadIdx.x + i * B_X] = m[c * imgStride * imgPixels + i * B_X];\n                            } else {\n                                shImages[c + threadIdx.y][threadIdx.x + i * B_X] = 0;\n                            }\n                        }\n                    }\n                }\n\n                __syncthreads();\n\n                for (int c = 0; c < colorCache; c++) {\n                    #pragma unroll\n                    for(int g = 0; g < imgsPerThread; g++) {\n                        #pragma unroll\n                        for(int f = 0; f < filtersPerThread; f++) {\n                            prod[f][g] += shImages[c][g * B_X + threadIdx.x] * shFilters[c][threadIdx.y + f * B_Y];\n                        }\n                    }\n                }\n                __syncthreads();\n            }\n        }\n    }\n\n    if (scale) {\n        #pragma unroll\n        for (int g = 0; g < imgsPerThread; g++) {\n            if (!checkImgBounds || myImgIdx + g * B_X < numImages) {\n                #pragma unroll\n                for (int f = 0; f < filtersPerThread; f++) {\n                    targets[g * B_X + f * B_Y * numImages * numModules] = scaleTargets * targets[g * B_X + f * B_Y * numImages * numModules] + scaleOutputs * prod[f][g];\n                }\n            }\n        }\n    } else {\n        // Note: reversing order of these loops saves 2 registers, but costs time\n        #pragma unroll\n        for (int f = 0; f < filtersPerThread; f++) {\n            #pragma unroll\n            for (int g = 0; g < imgsPerThread; g++) {\n                if (!checkImgBounds || myImgIdx + g * B_X < numImages) {\n                    targets[g * B_X + f * B_Y * numImages * numModules] = scaleOutputs * prod[f][g];\n                }\n            }\n        }\n    }\n}\n\n\n/*****************************Function Revision Record*****************************\n * Author: Tencent BestImage Team(ankerguo@tencent.com)                           *\n * Date:   2015-05-18                                                             *\n * Reason: Optimizing kernel to get faster speed according to GPU features        *\n * Method:                                                                        *\n *         1. reorganizing data structure to avoid bank conflict;                 *\n *         2. using vectorized data type;                                         *\n * Note:   This function can be used when each thread loads even number of filter *\n *         pixels(filtersPerThread * colorCache / B_X is even), and this can be   *\n *         optimized more when the number of loaded image's pixel is even.        *\n *********************************************************************************/\ntemplate <int B_Y, int B_X, int imgsPerThread, int filtersPerThread, int colorCache,\n          bool scale, bool checkImgBounds>\n__global__ void filterActs_YxX_sparse2_f_vec(float* images, float* filters, float* targets,\n                                       const int numImages, const int numFilters,\n                                       const int imgSizeY, const int imgSizeX, const int filterSize, const int paddingStart,\n                                       const int moduleStride,\n                                       const int numModulesY, const int numModulesX, const int imgStride, const int numImgColors,\n                                       const int numGroups, \n                                       const float scaleTargets, const float scaleOutputs,\n                                       const bool conv) {\n    // improve shared memory's band width by using 'float2' instead of 'float'\n    __shared__ float2 shFilters[colorCache/2][B_Y * filtersPerThread]; // pre-load 1 pixel from B_Y*filtersPerThread filters\n    __shared__ float shImages[colorCache][B_X * imgsPerThread]; // pre-load 1 pixel from B_X*imgsPerThread images\n\n    const int tx = threadIdx.x % B_X, ty = threadIdx.y % B_Y;\n    const int imgPixels = imgSizeY * imgSizeX;\n    const int filterPixels = filterSize * filterSize;\n    const int numFilterColors = numImgColors / numGroups;\n    const int blocksPerModule = numFilters / (B_Y*filtersPerThread);\n    const int moduleIdx = blockIdx.y / blocksPerModule;\n    const int blockFilterIdx = filtersPerThread * B_Y * (blockIdx.y % blocksPerModule);\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockGroupIdx = blockFilterIdx / numFiltersPerGroup;\n\n    const int numModules = numModulesX * numModulesY;\n    const int blockColorIdx = numFilterColors * blockGroupIdx;\n\n    const int tidx = ty * B_X + tx;\n\n    const int imgLoadModPosY = paddingStart + (moduleIdx / numModulesX) * moduleStride;\n    const int imgLoadModPosX = paddingStart + (moduleIdx % numModulesX) * moduleStride;\n\n    // load position of filters' pixels for current thread\n    const int shFilterLoadY = tidx / (B_Y * filtersPerThread);\n    const int shFilterLoadX = tidx % (B_Y * filtersPerThread);\n    // load position of images' pixels for current thread\n    const int shImgLoadY = tidx / (B_X * imgsPerThread);\n    const int shImgLoadX = tidx % (B_X * imgsPerThread);\n\n    const int myImgIdx = blockIdx.x * B_X * imgsPerThread + shImgLoadX;\n    images += (blockColorIdx + shImgLoadY) * imgPixels * imgStride + myImgIdx;\n\n    filters +=blockFilterIdx\n            + shFilterLoadY * numFilters * filterPixels + shFilterLoadX;\n    if (!conv) {\n        filters += moduleIdx * numFilterColors * filterPixels * numFilters;\n    }\n\n    targets += moduleIdx * numImages\n            + (blockFilterIdx + ty) * numImages * numModules\n            + blockIdx.x * B_X * imgsPerThread + tx;\n\n    float prod[filtersPerThread][imgsPerThread];\n    #pragma unroll\n    for(int f = 0; f < filtersPerThread; f++) {\n        #pragma unroll\n        for(int g = 0; g < imgsPerThread; g++) {\n            prod[f][g] = 0;\n        }\n    }\n\n    const int imgStartX = MAX(0, imgLoadModPosX);\n    const int imgStartY = MAX(0, imgLoadModPosY);\n    const int imgEndX = MIN(imgLoadModPosX + filterSize, imgSizeX);\n    const int imgEndY = MIN(imgLoadModPosY + filterSize, imgSizeY);\n\n    // temporary buffer to store the filter's loaded pixels during each loop\n    float fPreload[colorCache * filtersPerThread / B_X];\n    // temporary buffer to store the image's loaded pixels during each loop\n    float iPreload[colorCache * imgsPerThread / B_Y];\n\n    // preload filter's pixels\n    #pragma unroll\n    for (int c = 0; c < colorCache; c += B_X/filtersPerThread) {\n        fPreload[c * filtersPerThread / B_X] = filters[(c * filterPixels + (imgStartY - imgLoadModPosY) * filterSize + (imgStartX - imgLoadModPosX)) * numFilters];\n    }\n\n    // preload image's pixels\n    if (!checkImgBounds || myImgIdx < numImages) {\n        #pragma unroll\n        for (int c = 0; c < colorCache; c += B_Y / imgsPerThread) {\n            iPreload[c * imgsPerThread / B_Y] = images[(c * imgPixels + imgStartY * imgSizeX + imgStartX) * imgStride];\n        }\n    } else {\n        #pragma unroll\n        for (int c = 0; c < colorCache; c += B_Y / imgsPerThread) {\n            iPreload[c * imgsPerThread / B_Y] = 0;\n        }\n    }\n\n    for (int imgY = imgStartY; imgY < imgEndY; ++imgY) {\n        //const int filterPxY = imgY - imgLoadModPosY;\n        for (int imgX = imgStartX; imgX < imgEndX; ++imgX) {\n            for (int oc = 0; oc < numFilterColors; oc += colorCache) { // oc stands for outer color (loop)\n                // store the preloaded filter's pixels into shared memory\n                #pragma unroll\n                for (int c = 0; c < colorCache / 2; c += B_X / filtersPerThread) {\n                    shFilters[c + shFilterLoadY][shFilterLoadX].x = fPreload[c * filtersPerThread / B_X];\n                    shFilters[c + shFilterLoadY][shFilterLoadX].y = fPreload[(c + colorCache / 2) * filtersPerThread / B_X];\n                }\n\n                // store the preloaded image's pixels into shared memory\n                #pragma unroll\n                for (int c = 0; c < colorCache; c += B_Y / imgsPerThread) {\n                    shImages[c + shImgLoadY][shImgLoadX] = iPreload[c * imgsPerThread / B_Y];\n                }\n                /*\n                 * Load a pixel from B_Y*filtersPerThread filters\n                 * This condition covers the case when B_X is not divisible by filtersPerThread.\n                 * In this case, not all of the threads will participate in the loading operation.\n                 * This ensures that in each loop iteration, an integer number of rows of shFilters\n                 * are filled, which makes indexing simple.\n\n                 * nvcc is behaving in a completely insane way: removing this condition under\n                 * template parameters that guarantee it to be true actually slows down\n                 * the computation.\n                 *\n                 */\n\n                /* preload image and filter pixels' data */\n                if ((oc + colorCache) == numFilterColors) { // move to next pixel when all colors of current pixel have been finished\n                   int imgXn = (imgX < (imgEndX - 1)) ? (imgX + 1) : imgStartX; \n                   int imgYn = imgY + (imgXn != (imgX + 1));\n\n                    #pragma unroll\n                    for (int c = 0; c < colorCache; c += B_X/filtersPerThread) {\n                        fPreload[c * filtersPerThread / B_X] = filters[(c * filterPixels + (imgYn - imgLoadModPosY) * filterSize + (imgXn - imgLoadModPosX)) * numFilters];\n                    }\n\n                    if (!checkImgBounds || myImgIdx < numImages) {\n                        #pragma unroll\n                        for (int c = 0; c < colorCache; c += B_Y / imgsPerThread) {\n                            iPreload[c * imgsPerThread / B_Y] = images[(c * imgPixels + imgYn * imgSizeX + imgXn) * imgStride];\n                        }\n                    } else {\n                        #pragma unroll\n                        for (int c = 0; c < colorCache; c += B_Y / imgsPerThread) {\n                            iPreload[c * imgsPerThread / B_Y] = 0;\n                        }\n                    }\n                } else { // move next colorCache\n                    #pragma unroll\n                    for (int c = 0; c < colorCache; c += B_X/filtersPerThread) {\n                        fPreload[c * filtersPerThread / B_X] = filters[((c + oc + colorCache) * filterPixels + (imgY - imgLoadModPosY) * filterSize + (imgX - imgLoadModPosX)) * numFilters];\n                    }\n\n                    if (!checkImgBounds || myImgIdx < numImages) {\n                        #pragma unroll\n                        for (int c = 0; c < colorCache; c += B_Y / imgsPerThread) {\n                            iPreload[c * imgsPerThread / B_Y] = images[((c + oc + colorCache) * imgPixels + imgY * imgSizeX + imgX) * imgStride];\n                        }\n                    } else {\n                        #pragma unroll\n                        for (int c = 0; c < colorCache; c += B_Y / imgsPerThread) {\n                            iPreload[c * imgsPerThread / B_Y] = 0;\n                        }\n                    }\n                }\n\n                __syncthreads();\n\n                // convolution\n                for (int c = 0; c < colorCache / 2; c++) {\n                    #pragma unroll\n                    for(int g = 0; g < imgsPerThread; g++) {\n                        #pragma unroll\n                        for(int f = 0; f < filtersPerThread; f++) {\n                            prod[f][g] += shImages[c][g * B_X + tx]      * shFilters[c][ty + f * B_Y].x;\n                            prod[f][g] += shImages[c + colorCache / 2][g * B_X + tx] * shFilters[c][ty + f * B_Y].y;\n                        }\n                    }\n                }\n                __syncthreads();\n            }\n        }\n    }\n\n    // write convolution result into global memory\n    if (scale) {\n        #pragma unroll\n        for (int g = 0; g < imgsPerThread; g++) {\n            if (!checkImgBounds || myImgIdx + g * B_X < numImages) {\n                #pragma unroll\n                for (int f = 0; f < filtersPerThread; f++) {\n                    targets[g * B_X + f * B_Y * numImages * numModules] = scaleTargets * targets[g * B_X + f * B_Y * numImages * numModules] + scaleOutputs * prod[f][g];\n                }\n            }\n        }\n    } else {\n        // Note: reversing order of these loops saves 2 registers, but costs time\n        #pragma unroll\n        for (int f = 0; f < filtersPerThread; f++) {\n            #pragma unroll\n            for (int g = 0; g < imgsPerThread; g++) {\n                if (!checkImgBounds || myImgIdx + g * B_X < numImages) {\n                    targets[g * B_X + f * B_Y * numImages * numModules] = scaleOutputs * prod[f][g];\n                }\n            }\n        }\n    }\n}\n/*\n * images:      (numImgColors, imgSizeY, imgSizeX, numImages) with stride given\n * filters:     (numFilterColors, filterPixels, numFilters)             if conv\n *              (numModules, numFilterColors, filterPixels, numFilters) otherwise\n *\n * targets:     (numFilters, numModules, numImages)\n *\n * Note: all of these convolution routines are optimized for the case when\n * the number of images (i.e. the minibatch size) is a multiple of 128.\n * Other batch sizes will work, but but I made no attempt whatsoever\n * to make them work fast.\n */\n void _filterActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* images, caffe2::TensorCUDA* filters, caffe2::TensorCUDA* targets,\n                  int imgSizeY, int numModulesY, int numModulesX, int paddingStart, int moduleStride,\n                  int numImgColors, int numGroups,\n                  float scaleTargets, float scaleOutput, bool conv) {\n\n    CAFFE_ENFORCE(images->ndim() == 2);\n    CAFFE_ENFORCE(filters->ndim() == 2);\n    CAFFE_ENFORCE(targets->ndim() == 2);\n\n    int numFilterColors = numImgColors / numGroups;\n    int numFilters = filters->dim32(1);\n    int numModules = numModulesY * numModulesX;\n    int numImages = images->dim32(1);\n    int imgPixels = images->dim32(0) / numImgColors;\n    int imgSizeX = imgPixels / imgSizeY;\n    int filterModuleMult = conv ? 1 : numModules;\n\n    CAFFE_ENFORCE(numGroups > 1 || (numImgColors > 0 && (numImgColors <= 3 || numImgColors % 4 == 0)));\n    CAFFE_ENFORCE(numGroups == 1 || numFilterColors % 4 == 0);\n    CAFFE_ENFORCE(numFilters % (16 * numGroups) == 0);\n    CAFFE_ENFORCE(numImgColors % numGroups == 0);\n    CAFFE_ENFORCE(images->dim32(0) == imgPixels * numImgColors);\n    CAFFE_ENFORCE(imgSizeY * imgSizeX == imgPixels);\n    int numFiltersPerGroup = numFilters / numGroups;\n\n    int imgStride = images->dim32(1);\n\n    int filterPixels = filters->dim32(0) / (filterModuleMult * numFilterColors);\n    int filterSize = int(sqrt(filterPixels));\n    CAFFE_ENFORCE(filterSize * filterSize == filterPixels);\n    CAFFE_ENFORCE(filters->dim32(0) == filterModuleMult * numFilterColors * filterPixels);\n\n    // These routines don't handle the case when only part of the image is visited in the convolution\n    CAFFE_ENFORCE(paddingStart <= 0);\n    CAFFE_ENFORCE(paddingStart + (numModulesX-1)*moduleStride + filterSize >= imgSizeX);\n    CAFFE_ENFORCE(paddingStart + (numModulesY-1)*moduleStride + filterSize >= imgSizeY);\n    CAFFE_ENFORCE(moduleStride <= filterSize);\n\n    int imgsPerThread = numImages % 128 == 0 ? 4 : numImages % 64 == 0 ? 2 : 1;\n    int filtersPerThread, threadsY = 4;\n    if (numImgColors <= 3) {\n        // Special kernels written for colors = 3, filters = 64 and colors = 3, filters = 48 cases.\n        // The remaining cases use the old routines.\n        // TODO: Modernize the remaining cases if you care about them.\n        filtersPerThread = numFiltersPerGroup % 64 == 0 ? 16 : numFiltersPerGroup % 48 == 0 ? 12 : numFiltersPerGroup % 32 == 0 ? 8 : 4;\n    } else {\n        filtersPerThread = numFiltersPerGroup % 64 == 0 ? 16 : numFiltersPerGroup % 32 == 0 ? 8 : 4;\n        threadsY = numFiltersPerGroup % 128 == 0 && numFilterColors % 8 == 0  && imgsPerThread != 4 ?  8 : 4;\n    }\n    int threadsX = 32;\n    dim3 threads(threadsX, threadsY);\n    dim3 blocks = dim3(DIVUP(numImages, threads.x * imgsPerThread), (numModules * numFilters) / (threads.y * filtersPerThread));\n\n    bool checkImgBounds = numImages % (threads.x*imgsPerThread) != 0;\n    bool scale = scaleTargets != 0;\n    if (scaleTargets == 0) {\n        targets->Resize(std::vector<int>{numFilters * numModules, numImages});\n    } else {\n        CAFFE_ENFORCE(targets->dim32(0) == numFilters * numModules);\n        CAFFE_ENFORCE(targets->dim32(1) == numImages);\n    }\n\n    cudaTextureObject_t tex_images = GetTensorTextureObject(images);\n    cudaTextureObject_t tex_filters = GetTensorTextureObject(filters);\n    float* images_data = images->mutable_data<float>();\n    float* filters_data = filters->mutable_data<float>();\n    float* targets_data = targets->mutable_data<float>();\n    const std::size_t images_bytes = images->nbytes();\n\n    cudaStream_t stream = context->cuda_stream();\n\n    checkCudaErrors(cudaDeviceSetSharedMemConfig(cudaSharedMemBankSizeEightByte)); // using wider band width\n    \n    // Auto-generated calling code...\n    // NOTE: The calling code is set up such that if checkImgBounds is true, then imgsPerThread = 1.\n    // In principle it doesn't have to be this way, and you may want to optimize for that case.\n\n    if (scale == false) {\n        if (checkImgBounds == false) {\n            if (numFilterColors % 8 == 0) {\n                if (numImages % 128 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        if (images_bytes < TEXTURE_SIZE_MAX) {\n                            cudaFuncSetCacheConfig(filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4_tex < 4, 32, 4, 16, 4, false, false >, cudaFuncCachePreferL1);\n                            filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4_tex < 4, 32, 4, 16, 4, false, false > <<<blocks, threads, 0, stream>>>(tex_images, tex_filters, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                        } else {\n                            cudaFuncSetCacheConfig(filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4 < 4, 32, 4, 16, 4, false, false >, cudaFuncCachePreferL1);\n                            filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4 < 4, 32, 4, 16, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                        }\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        if (images_bytes < TEXTURE_SIZE_MAX) {\n                            cudaFuncSetCacheConfig(filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4_tex < 4, 32, 4, 16, 4, false, false >, cudaFuncCachePreferL1);\n                            filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4_tex < 4, 32, 4, 16, 4, false, false > <<<blocks, threads, 0, stream>>>(tex_images, tex_filters, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                        } else {\n                            cudaFuncSetCacheConfig(filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4 < 4, 32, 4, 16, 4, false, false >, cudaFuncCachePreferL1);\n                            filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4 < 4, 32, 4, 16, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                        }\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 4, 32, 4, 8, 8, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 4, 32, 4, 8, 8, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 4, 4, 8, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 4, 4, 8, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 64 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 8, 32, 2, 16, 8, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 8, 32, 2, 16, 8, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 4, 32, 2, 16, 8, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 4, 32, 2, 16, 8, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 4, 32, 2, 8, 8, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 4, 32, 2, 8, 8, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 2, 4, 8, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 2, 4, 8, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 32 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 8, 32, 1, 16, 8, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 8, 32, 1, 16, 8, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 4, 32, 1, 16, 8, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 4, 32, 1, 16, 8, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 4, 32, 1, 8, 8, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 4, 32, 1, 8, 8, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 4, 8, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 4, 8, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors % 4 == 0) {\n                if (numImages % 128 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 4, 16, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 4, 16, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 4, 16, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 4, 16, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 4, 8, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 4, 8, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 4, 4, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 4, 4, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 64 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 2, 16, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 2, 16, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 2, 16, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 2, 16, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 2, 8, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 2, 8, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 2, 4, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 2, 4, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 32 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 8, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 8, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 4, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 4, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors == 3) {\n                if (numImages % 128 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color_preload_ty_4_tx_32_i_4_f_16_px_4_cc_3_tex < 4, 32, 4, 16, 3, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color_preload_ty_4_tx_32_i_4_f_16_px_4_cc_3_tex < 4, 32, 4, 16, 3, 4, false, false > <<<blocks, threads, 0, stream>>>(tex_images, tex_filters, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color_preload_ty_4_tx_32_i_4_f_12_px_4_cc_3_tex < 4, 32, 4, 12, 3, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color_preload_ty_4_tx_32_i_4_f_12_px_4_cc_3_tex < 4, 32, 4, 12, 3, 4, false, false > <<<blocks, threads, 0, stream>>>(tex_images, tex_filters, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 8, 3, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 8, 3, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 4, 3, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 4, 3, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 64 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 16, 3, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 16, 3, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 12, 3, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 12, 3, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 8, 3, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 8, 3, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 4, 3, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 4, 3, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 32 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 16, 3, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 16, 3, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 12, 3, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 12, 3, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 8, 3, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 8, 3, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 4, 3, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 4, 3, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors == 2) {\n                if (numImages % 128 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 16, 2, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 16, 2, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 12, 2, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 12, 2, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 8, 2, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 8, 2, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 4, 2, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 4, 2, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 64 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 16, 2, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 16, 2, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 12, 2, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 12, 2, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 8, 2, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 8, 2, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 4, 2, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 4, 2, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 32 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 16, 2, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 16, 2, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 12, 2, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 12, 2, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 8, 2, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 8, 2, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 4, 2, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 4, 2, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors == 1) {\n                if (numImages % 128 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 16, 1, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 16, 1, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 12, 1, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 12, 1, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 8, 1, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 8, 1, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 4, 1, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 4, 1, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 64 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 16, 1, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 16, 1, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 12, 1, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 12, 1, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 8, 1, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 8, 1, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 4, 1, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 4, 1, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 32 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 16, 1, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 16, 1, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 12, 1, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 12, 1, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 8, 1, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 8, 1, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 4, 1, 4, false, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 4, 1, 4, false, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n        }\n        else if (checkImgBounds == true) {\n            if (numFilterColors % 8 == 0) {\n                if (numImages % 1 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 8, 32, 1, 16, 8, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 8, 32, 1, 16, 8, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 16, 8, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 16, 8, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 8, 8, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 8, 8, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 4, 8, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 4, 8, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors % 4 == 0) {\n                if (numImages % 1 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 8, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 8, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 4, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 4, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors == 3) {\n                if (numImages % 1 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 16, 3, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 16, 3, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 12, 3, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 12, 3, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 8, 3, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 8, 3, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 4, 3, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 4, 3, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors == 2) {\n                if (numImages % 1 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 16, 2, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 16, 2, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 12, 2, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 12, 2, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 8, 2, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 8, 2, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 4, 2, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 4, 2, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors == 1) {\n                if (numImages % 1 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 16, 1, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 16, 1, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 12, 1, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 12, 1, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 8, 1, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 8, 1, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 4, 1, 4, false, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 4, 1, 4, false, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n        }\n    }\n    else if (scale == true) {\n        if (checkImgBounds == false) {\n            if (numFilterColors % 8 == 0) {\n                if (numImages % 128 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        if (images_bytes < TEXTURE_SIZE_MAX) {\n                            cudaFuncSetCacheConfig(filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4_tex < 4, 32, 4, 16, 4, true, false >, cudaFuncCachePreferL1);\n                            filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4_tex < 4, 32, 4, 16, 4, true, false > <<<blocks, threads, 0, stream>>>(tex_images, tex_filters, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                        } else {\n                            cudaFuncSetCacheConfig(filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4 < 4, 32, 4, 16, 4, true, false >, cudaFuncCachePreferL1);\n                            filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4 < 4, 32, 4, 16, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                        }\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        if (images_bytes < TEXTURE_SIZE_MAX) {\n                            cudaFuncSetCacheConfig(filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4_tex < 4, 32, 4, 16, 4, true, false >, cudaFuncCachePreferL1);\n                            filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4_tex < 4, 32, 4, 16, 4, true, false > <<<blocks, threads, 0, stream>>>(tex_images, tex_filters, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                        } else {\n                            cudaFuncSetCacheConfig(filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4 < 4, 32, 4, 16, 4, true, false >, cudaFuncCachePreferL1);\n                            filterActs_YxX_sparse2_preload_ty_4_tx_32_i_4_f_16_c_4 < 4, 32, 4, 16, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                        }\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 4, 32, 4, 8, 8, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 4, 32, 4, 8, 8, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 4, 4, 8, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 4, 4, 8, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 64 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 8, 32, 2, 16, 8, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 8, 32, 2, 16, 8, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 4, 32, 2, 16, 8, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 4, 32, 2, 16, 8, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 4, 32, 2, 8, 8, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 4, 32, 2, 8, 8, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 2, 4, 8, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 2, 4, 8, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 32 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 8, 32, 1, 16, 8, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 8, 32, 1, 16, 8, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 4, 32, 1, 16, 8, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 4, 32, 1, 16, 8, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2_f_vec < 4, 32, 1, 8, 8, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2_f_vec < 4, 32, 1, 8, 8, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 4, 8, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 4, 8, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors % 4 == 0) {\n                if (numImages % 128 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 4, 16, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 4, 16, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 4, 16, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 4, 16, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 4, 8, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 4, 8, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 4, 4, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 4, 4, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 64 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 2, 16, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 2, 16, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 2, 16, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 2, 16, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 2, 8, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 2, 8, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 2, 4, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 2, 4, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 32 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 8, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 8, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 4, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 4, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors == 3) {\n                if (numImages % 128 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color_preload_ty_4_tx_32_i_4_f_16_px_4_cc_3_tex < 4, 32, 4, 16, 3, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color_preload_ty_4_tx_32_i_4_f_16_px_4_cc_3_tex < 4, 32, 4, 16, 3, 4, true, false > <<<blocks, threads, 0, stream>>>(tex_images, tex_filters, targets_data,numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color_preload_ty_4_tx_32_i_4_f_12_px_4_cc_3_tex < 4, 32, 4, 12, 3, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color_preload_ty_4_tx_32_i_4_f_12_px_4_cc_3_tex < 4, 32, 4, 12, 3, 4, true, false > <<<blocks, threads, 0, stream>>>(tex_images, tex_filters, targets_data,numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 8, 3, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 8, 3, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 4, 3, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 4, 3, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 64 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 16, 3, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 16, 3, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 12, 3, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 12, 3, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 8, 3, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 8, 3, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 4, 3, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 4, 3, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 32 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 16, 3, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 16, 3, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 12, 3, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 12, 3, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 8, 3, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 8, 3, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 4, 3, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 4, 3, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors == 2) {\n                if (numImages % 128 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 16, 2, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 16, 2, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 12, 2, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 12, 2, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 8, 2, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 8, 2, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 4, 2, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 4, 2, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 64 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 16, 2, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 16, 2, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 12, 2, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 12, 2, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 8, 2, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 8, 2, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 4, 2, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 4, 2, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 32 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 16, 2, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 16, 2, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 12, 2, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 12, 2, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 8, 2, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 8, 2, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 4, 2, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 4, 2, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors == 1) {\n                if (numImages % 128 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 16, 1, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 16, 1, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 12, 1, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 12, 1, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 8, 1, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 8, 1, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 4, 4, 1, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 4, 4, 1, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 64 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 16, 1, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 16, 1, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 12, 1, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 12, 1, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 8, 1, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 8, 1, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 2, 4, 1, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 2, 4, 1, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n                else if (numImages % 32 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 16, 1, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 16, 1, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 12, 1, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 12, 1, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 8, 1, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 8, 1, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 4, 1, 4, true, false >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 4, 1, 4, true, false > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n        }\n        else if (checkImgBounds == true) {\n            if (numFilterColors % 8 == 0) {\n                if (numImages % 1 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 8, 32, 1, 16, 8, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 8, 32, 1, 16, 8, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 16, 8, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 16, 8, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 8, 8, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 8, 8, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 4, 8, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 4, 8, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors % 4 == 0) {\n                if (numImages % 1 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 16, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 8, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 8, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_sparse2 < 4, 32, 1, 4, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_sparse2 < 4, 32, 1, 4, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, numImgColors, numGroups, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors == 3) {\n                if (numImages % 1 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 16, 3, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 16, 3, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 12, 3, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 12, 3, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 8, 3, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 8, 3, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 4, 3, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 4, 3, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors == 2) {\n                if (numImages % 1 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 16, 2, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 16, 2, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 12, 2, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 12, 2, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 8, 2, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 8, 2, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 4, 2, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 4, 2, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n            else if (numFilterColors == 1) {\n                if (numImages % 1 == 0) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 16, 1, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 16, 1, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 12, 1, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 12, 1, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 8, 1, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 8, 1, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                    else if (numFiltersPerGroup % 1 == 0) {\n                        cudaFuncSetCacheConfig(filterActs_YxX_color < 4, 32, 1, 4, 1, 4, true, true >, cudaFuncCachePreferShared);\n                        filterActs_YxX_color < 4, 32, 1, 4, 1, 4, true, true > <<<blocks, threads, 0, stream>>>(images_data, filters_data, targets_data, numImages, numFilters, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, numModulesY, numModulesX, imgStride, scaleTargets, scaleOutput, conv);\n                    }\n                }\n            }\n        }\n    }\n\n    checkCudaErrors(cudaDestroyTextureObject(tex_images));\n    checkCudaErrors(cudaDestroyTextureObject(tex_filters));\n    checkCudaErrors(cudaDeviceSetSharedMemConfig(cudaSharedMemBankSizeFourByte));\n\n    getLastCudaError(\"filterActs: kernel execution failed\");\n}\n\nvoid convFilterActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* images, caffe2::TensorCUDA* filters, caffe2::TensorCUDA* targets,\n                    int imgSizeY, int numModulesY, int numModulesX, int paddingStart, int moduleStride,\n                    int numImgColors, int numGroups) {\n    convFilterActs(context, images, filters, targets, imgSizeY, numModulesY, numModulesX, paddingStart, moduleStride, numImgColors, numGroups, 0, 1);\n}\n\nvoid convFilterActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* images, caffe2::TensorCUDA* filters, caffe2::TensorCUDA* targets,\n                   int imgSizeY, int numModulesY, int numModulesX, int paddingStart, int moduleStride,\n                   int numImgColors, int numGroups,\n                   float scaleTargets, float scaleOutput) {\n     _filterActs(context, images, filters, targets, imgSizeY, numModulesY, numModulesX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput, true);\n}\n\nvoid localFilterActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* images, caffe2::TensorCUDA* filters, caffe2::TensorCUDA* targets,\n                     int imgSizeY, int numModulesY, int numModulesX, int paddingStart, int moduleStride,\n                     int numImgColors, int numGroups) {\n    localFilterActs(context, images, filters, targets, imgSizeY, numModulesY, numModulesX, paddingStart, moduleStride, numImgColors, numGroups, 0, 1);\n}\n\nvoid localFilterActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* images, caffe2::TensorCUDA* filters, caffe2::TensorCUDA* targets,\n                   int imgSizeY, int numModulesY, int numModulesX, int paddingStart, int moduleStride,\n                   int numImgColors, int numGroups,\n                   float scaleTargets, float scaleOutput) {\n     _filterActs(context, images, filters, targets, imgSizeY, numModulesY, numModulesX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput, false);\n}\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconv3/src/img_acts.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <vector>\n\n#include \"../include/cudaconv2.cuh\"\n\n/*\n * Block size: 16x16.\n * blockIdx.x determines case in batches of 16*imgsPerThread.\n * blockIdx.y determines 4x4 image region in target image.\n *\n * threadIdx.x determines case.\n * threadIdx.y determines pixel.\n *\n * hidActs:     (numFilters, numModulesY, numModulesX, numImages)\n * filters:     (numColors, filterPixels, numFilters)                               if conv\n *              (numModulesY, numModulesX, numColors, filterPixels, numFilters)     otherwise\n * targets:     (numColors, imgSizeY, imgSizeX, numImages)\n *\n * Each block reconstructs one 4x4 pixels from 16*imgsPerThread cases.\n *\n * Number of filters must be divisible by 16.\n * Number of images must be divisible by 16*imgsPerThread  if checkCaseBounds is false.\n * 16 * imgsPerThread must be divisible by 32.\n *\n * This version loads 32 cases at a time, so it gets full coalescing on that load.\n * It only loads 16 weights at a time, so those aren't fully coalesced.\n * This version conserves shared memory by loading 16 filters at a time rather than 32.\n */\ntemplate <int imgsPerThread, int numColors, bool scale, bool checkCaseBounds, bool conv>\n__global__ void img_acts_color(const float* hidActs, const float* filters, float* targets,\n                                   const int numModulesY, const int numModulesX, const int numImages, const int numFilters,\n                                   const int filterSize, const int imgSizeY, const int imgSizeX,\n                                   const int paddingStart, const int moduleStride,\n                                   const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shFilters[numColors*16][16 + 1];\n    __shared__ float shHidActs[16][16*imgsPerThread];\n\n    const int blockCaseIdx = blockIdx.x * 16*imgsPerThread;\n    const int numRegionsX = DIVUP(imgSizeX, 4);\n    const int blockRegionIdx = blockIdx.y;\n    const int blockRegionIdxX = blockRegionIdx % numRegionsX;\n    const int blockRegionIdxY = blockRegionIdx / numRegionsX;\n    const int blockRegionLeft = blockRegionIdxX * 4;\n    const int blockRegionTop = blockRegionIdxY * 4;\n    const int pxYInRegion = threadIdx.y / 4, pxXInRegion = threadIdx.y % 4;\n    const int pxY = blockRegionTop + pxYInRegion;\n    const int pxX = blockRegionLeft + pxXInRegion;\n    const int pxIdx = pxY * imgSizeX + pxX;\n    const bool isPxInImg = pxY < imgSizeY && pxX < imgSizeX;\n    const int numModules = numModulesY * numModulesX;\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeX * imgSizeY;\n    const int tidx = threadIdx.y * 16 + threadIdx.x;\n    const int loadY = tidx / 32, loadX = tidx % 32;\n\n    hidActs += blockCaseIdx + loadY * numImages * numModules + loadX;\n    filters += threadIdx.x;\n    targets += pxIdx * numImages + blockCaseIdx + threadIdx.x;\n\n\n    float prod[numColors][imgsPerThread];\n    #pragma unroll\n    for (int c = 0; c < numColors; c++) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            prod[c][i] = 0;\n        }\n    }\n    const int startY = blockRegionTop - paddingStart < filterSize ? 0\n                        : 1 + (blockRegionTop - paddingStart - filterSize) / moduleStride;\n    const int endY = MIN(numModulesY, 1 + (blockRegionTop + 3 - paddingStart) / moduleStride);\n    const int startX = blockRegionLeft - paddingStart < filterSize ? 0\n                        : 1 + (blockRegionLeft - paddingStart - filterSize) / moduleStride;\n    const int endX = MIN(numModulesX, 1 + (blockRegionLeft + 3 - paddingStart) / moduleStride);\n\n    float* shilterLoad = &shFilters[threadIdx.y][threadIdx.x];\n    float* shHidActLoad = &shHidActs[loadY][loadX];\n\n    for (int my = startY; my < endY; my++) {\n        const int moduleTop = paddingStart + my * moduleStride;\n        const int pxInModuleY = pxY - moduleTop;\n\n        for (int mx = startX; mx < endX; mx++) {\n            const int moduleIdx = my * numModulesX + mx;\n            const int moduleLeft = paddingStart + mx * moduleStride;\n            const int pxInModuleX = pxX - moduleLeft;\n\n            const bool isPxInModule = pxInModuleY >= 0 && pxInModuleY < filterSize && pxInModuleX >= 0 && pxInModuleX < filterSize;\n            const int pxIdxInModule = pxInModuleY * filterSize + pxInModuleX;\n\n            for (int f = 0; f < numFilters; f += 16) { // multiply with 16 filters at a time\n                // Now the threads split up into half-warps, and each half-warp decides if it's interested.\n                const float* hLoad = &hidActs[(moduleIdx + f * numModules) * numImages];\n                #pragma unroll\n                for (int i = 0; i < imgsPerThread * 16; i += 32) {\n                    if (!checkCaseBounds || blockCaseIdx + i + loadX < numImages) {\n                        #pragma unroll\n                        for (int j = 0; j < 16; j += 8) { // load 16 rows of imgsPerThread*16 cols, 8 * 32 elements at a time.\n                            shHidActLoad[j * 16 * imgsPerThread + i] = hLoad[j * numModules * numImages + i];\n                        }\n                    } else {\n                        #pragma unroll\n                        for (int j = 0; j < 16; j += 8) { // load 16 rows of imgsPerThread*16 cols, 8 * 32 elements at a time.\n                            shHidActLoad[j * 16 * imgsPerThread + i] = 0;\n                        }\n                    }\n                }\n\n                if (isPxInImg && isPxInModule) {\n                    // This half-warp is interested, so it's going to load the weights from this module to its pixel.\n                    // Not fully coalesced read :(\n                    // But taking out this read entirely only reduces the runtime by ~2.8%, so it isn't costing me much.\n                    const float* fLoad = conv ? &filters[pxIdxInModule * numFilters + f]\n                                              : &filters[(moduleIdx * numColors * filterPixels + pxIdxInModule) * numFilters + f];\n                    #pragma unroll\n                    for (int c = 0; c < numColors; c++) {\n                        shilterLoad[c * 16 * (16 + 1)] = fLoad[c * filterPixels * numFilters];\n                    }\n\n\n                }\n\n                __syncthreads();\n                // Do some actual computation\n                if (isPxInImg && isPxInModule) {\n                    #pragma unroll\n                    for (int c = 0; c < numColors; c++) {\n                        #pragma unroll\n                        for (int w = 0; w < 16; w++) {\n                            #pragma unroll\n                            for (int i = 0; i < imgsPerThread; i++) {\n                                prod[c][i] += shFilters[threadIdx.y + c * 16][w] * shHidActs[w][threadIdx.x + i * 16];\n                            }\n                        }\n                    }\n                }\n                __syncthreads();\n            }\n        }\n    }\n    // Not fully coalesced write :(... shmem (and fully coalesced) version is actually slightly slower, though\n    if (isPxInImg) {\n        if (scale) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || blockCaseIdx + threadIdx.x + i * 16 < numImages) {\n                    #pragma unroll\n                    for (int c = 0; c < numColors; c++) {\n                        targets[c * imgPixels * numImages + i * 16] = scaleTargets * targets[c * imgPixels * numImages + i * 16] + scaleOutputs * prod[c][i];\n                    }\n                }\n            }\n        } else {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || blockCaseIdx + threadIdx.x + i * 16 < numImages) {\n                    #pragma unroll\n                    for (int c = 0; c < numColors; c++) {\n                        targets[c * imgPixels * numImages + i * 16] = scaleOutputs * prod[c][i];\n                    }\n                }\n            }\n        }\n    }\n}\n\n/*\n * Block size: 16x16.\n * blockIdx.x determines case in batches of 16*imgsPerThread, also color in batches of colorsPerThread.\n *  In essence, blockIdx.x.x = 1..numImages/(16*imgsPerThread)\n *              blockIdx.x.y = 1..numImgColors/colorsPerThread\n * blockIdx.y determines 4x4 image region in target image.\n *\n * threadIdx.x determines case.\n * threadIdx.y determines pixel.\n *\n * hidActs:     (numFilters, numModulesY, numModulesX, numImages)\n * filters:     (numFilterColors, filterPixels, numFilters)                             if conv\n *              (numModulesY, numModulesX, numFilterColors, filterPixels, numFilters)   otherwise\n * targets:     (numImageColors, imgSizeY, imgSizeX, numImages)\n *\n * Each block reconstructs one 4x4 pixels from 16*imgsPerThread cases.\n *\n * numImages must be divisible by 16*imgsPerThread if checkCaseBounds is false.\n * 16 * imgsPerThread must be divisible by 32.\n * numImageColors/numGroups must be divisible by colorsPerThread.\n *\n * This version loads 32 cases at a time, so it gets full coalescing on that load.\n * It only loads 16 weights at a time, so those aren't fully coalesced.\n * This version conserves shared memory by loading 16 filters at a time rather than 32.\n *\n * To be used when there are 4-16 color channels.\n */\ntemplate <int imgsPerThread, int colorsPerThread,  bool scale, bool checkCaseBounds, bool conv>\n__global__ void img_acts_mediumcolor(const float* hidActs, const float* filters, float* targets,\n                                       const int numModulesY, const int numModulesX, const int numImages, const int numFilters,\n                                       const int filterSize, const int imgSizeY, const int imgSizeX, const int paddingStart,\n                                       const int moduleStride, const int numImgColors, const int numGroups,\n                                       const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shFilters[colorsPerThread*16][16 + 1];\n    __shared__ float shHidActs[16][16*imgsPerThread];\n\n    const int numImgBlocks = DIVUP(numImages,16*imgsPerThread);\n    const int blockCaseIdx = (blockIdx.x % numImgBlocks) * 16*imgsPerThread;\n\n    const int imgColorIdx = (blockIdx.x / numImgBlocks) * colorsPerThread; // color idx globally\n    const int numFilterColors = numImgColors / numGroups;\n    const int blockGroupIdx = imgColorIdx / numFilterColors;\n    const int filterColorIdx = imgColorIdx % numFilterColors; // color idx within group\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockFilterIdx = blockGroupIdx * numFiltersPerGroup;\n\n    const int numRegionsX = DIVUP(imgSizeX, 4);\n    const int blockRegionIdx = blockIdx.y;\n    const int blockRegionIdxX = blockRegionIdx % numRegionsX;\n    const int blockRegionIdxY = blockRegionIdx / numRegionsX;\n    const int blockRegionLeft = blockRegionIdxX * 4;\n    const int blockRegionTop = blockRegionIdxY * 4;\n    const int pxYInRegion = threadIdx.y / 4, pxXInRegion = threadIdx.y % 4;\n    const int pxY = blockRegionTop + pxYInRegion;\n    const int pxX = blockRegionLeft + pxXInRegion;\n    const int pxIdx = pxY * imgSizeX + pxX;\n    const bool isPxInImg = pxY < imgSizeY && pxX < imgSizeX;\n    const uint numModules = numModulesY * numModulesX;\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n    const int tidx = threadIdx.y * 16 + threadIdx.x;\n    const int loadY = tidx / 32, loadX = tidx % 32;\n\n    hidActs += blockCaseIdx + (blockFilterIdx + loadY) * numImages * numModules + loadX;\n    filters += blockFilterIdx + filterColorIdx * filterPixels * numFilters + threadIdx.x;\n    targets += imgColorIdx * imgPixels * numImages + pxIdx * numImages + blockCaseIdx + threadIdx.x;\n\n    float prod[colorsPerThread][imgsPerThread];\n    #pragma unroll\n    for (int c = 0; c < colorsPerThread; c++) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            prod[c][i] = 0;\n        }\n    }\n    const int startY = blockRegionTop - paddingStart < filterSize ? 0\n                        : 1 + (blockRegionTop - paddingStart - filterSize) / moduleStride;\n    const int endY = MIN(numModulesY, 1 + (blockRegionTop + 3 - paddingStart) / moduleStride);\n    const int startX = blockRegionLeft - paddingStart < filterSize ? 0\n                        : 1 + (blockRegionLeft - paddingStart - filterSize) / moduleStride;\n    const int endX = MIN(numModulesX, 1 + (blockRegionLeft + 3 - paddingStart) / moduleStride);\n\n    float* shFilterLoad = &shFilters[threadIdx.y][threadIdx.x];\n    float* shHidActLoad = &shHidActs[loadY][loadX];\n\n    for (int my = startY; my < endY; my++) {\n        const int moduleTop = paddingStart + my * moduleStride;\n        const int pxInModuleY = pxY - moduleTop;\n\n        for (int mx = startX; mx < endX; mx++) {\n            const int moduleIdx = my * numModulesX + mx;\n            const int moduleLeft = paddingStart + mx * moduleStride;\n            const int pxInModuleX = pxX - moduleLeft;\n\n            const bool isPxInModule = pxInModuleY >= 0 && pxInModuleY < filterSize && pxInModuleX >= 0 && pxInModuleX < filterSize;\n            const int pxIdxInModule = pxInModuleY * filterSize + pxInModuleX;\n\n            for (int f = 0; f < numFiltersPerGroup; f += 16) { // multipply with 16 filters at a time\n                // Now the threads split up into half-warps, and each half-warp decides if it's interested.\n                const float* hLoad = &hidActs[(moduleIdx + f * numModules) * numImages];\n                #pragma unroll\n                for (int i = 0; i < imgsPerThread * 16; i += 32) {\n                    if (!checkCaseBounds || blockCaseIdx + loadX + i < numImages) {\n                        #pragma unroll\n                        for (int j = 0; j < 16; j += 8) { // load 16 rows of imgsPerThread*16 cols, 8 * 32 elements at a time.\n                            shHidActLoad[j * 16 * imgsPerThread + i] = hLoad[j * numModules * numImages + i];\n                        }\n                    } else {\n                        #pragma unroll\n                        for (int j = 0; j < 16; j += 8) { // load 16 rows of imgsPerThread*16 cols, 8 * 32 elements at a time.\n                            shHidActLoad[j * 16 * imgsPerThread + i] = 0;\n                        }\n                    }\n                }\n\n                if (isPxInImg && isPxInModule) {\n                    // This half-warp is interested, so it's going to load the weights from this module to its pixel.\n\n                    // Not fully coalesced read :(\n                    // But taking out this read entirely only reduces the runtime by ~2.8%, so it isn't costing me much.\n                    const float* fLoad = conv ? &filters[pxIdxInModule * numFilters + f]\n                                              : &filters[moduleIdx * numFilterColors * filterPixels * numFilters + pxIdxInModule * numFilters + f];\n                    #pragma unroll\n                    for (int c = 0; c < colorsPerThread; c++) {\n                        shFilterLoad[c * 16 * (16 + 1)] = fLoad[c * filterPixels * numFilters];\n                    }\n                }\n\n                __syncthreads();\n                // Do some actual computation\n                if (isPxInImg && isPxInModule) {\n                    #pragma unroll\n                    for (int c = 0; c < colorsPerThread; c++) {\n                        #pragma unroll\n                        for (int w = 0; w < 16; w++) {\n                            #pragma unroll\n                            for (int i = 0; i < imgsPerThread; i++) {\n                                prod[c][i] += shFilters[threadIdx.y + c * 16][w] * shHidActs[w][threadIdx.x + i * 16];\n                            }\n                        }\n                    }\n                }\n                __syncthreads();\n            }\n        }\n    }\n    // Not fully coalesced write :(... shmem (and fully coalesced) version is actually slightly slower, though\n    if (isPxInImg) {\n        if (scale) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || blockCaseIdx + threadIdx.x + i * 16 < numImages) {\n                    #pragma unroll\n                    for (int c = 0; c < colorsPerThread; c++) {\n                        targets[c * imgPixels * numImages + i * 16] = scaleTargets * targets[c * imgPixels * numImages + i * 16] + scaleOutputs * prod[c][i];\n                    }\n                }\n            }\n        } else {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || blockCaseIdx + threadIdx.x + i * 16 < numImages) {\n                    #pragma unroll\n                    for (int c = 0; c < colorsPerThread; c++) {\n                        targets[c * imgPixels * numImages + i * 16] = scaleOutputs * prod[c][i];\n                    }\n                }\n            }\n        }\n    }\n}\n\n/*\n * Block size: B_YxB_X.\n * blockIdx.x determines case in batches of B_X*imgsPerThread, also color in batches of B_Y*colorsPerThread.\n *  In essence, blockIdx.x.x = 1..numImages/(B_X*imgsPerThread)\n *              blockIdx.x.y = 1..numImgColors/(B_Y*colorsPerThread)\n * blockIdx.y determines image pixel in target image.\n *\n * threadIdx.x determines case.\n * threadIdx.y determines color.\n *\n * hidActs:     (numFilters, numModulesY, numModulesX, numImages)\n * filters:     (numFilterColors, filterPixels, numFilters)                             if conv\n *              (numModulesY, numModulesX, numFilterColors, filterPixels, numFilters)   otherwise\n * targets:     (numImageColors, imgSizeY, imgSizeX, numImages)\n *\n * Each block reconstructs one B_Y*colorsPerThread colors from 1 pixel from B_X*imgsPerThread cases.\n *\n * numImages must be divisible by B_X*imgsPerThread if checkCaseBounds is false.\n * numFiltersPerGroup must be divisible by filterCache.\n *\n * B_X * imgsPerThread must be divisible by 32.\n * numFilterColors must be divisible by B_Y*colorsPerThread.\n * B_X*B_Y must be divisible by 32.\n * filterCache must be divisible by B_X*B_Y/32\n * B_X*B_Y must be divisible by filterCache\n\n * This version loads 32 cases at a time, so it gets full coalescing on that load.\n * It only loads filterCache weights at a time, so those aren't fully coalesced (depending on size of filterCache).\n *\n * To be used when there are >= 16 color channels.\n */\ntemplate <int B_Y, int B_X, int imgsPerThread, int colorsPerThread, int filterCache, bool scale, bool checkCaseBounds, bool conv>\n__global__ void conv_img_acts_manycolor(const float* hidActs, const float* filters, float* targets,\n                                          const int numModulesY, const int numModulesX, const int numImages, const int numFilters,\n                                          const int filterSize, const int imgSizeY, const int imgSizeX, const int paddingStart, const int moduleStride,\n                                          const int numImgColors, const int numGroups,\n                                          const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shFilters[colorsPerThread*B_Y][filterCache + 1];\n    __shared__ float shHidActs[filterCache][B_X*imgsPerThread];\n\n    const int numImgBlocks = DIVUP(numImages,B_X*imgsPerThread);\n    const int blockCaseIdx = (blockIdx.x % numImgBlocks) * B_X*imgsPerThread;\n\n    const int imgColorIdx = (blockIdx.x / numImgBlocks) * B_Y*colorsPerThread; // color idx globally\n    const int numFilterColors = numImgColors / numGroups;\n    const int blockGroupIdx = imgColorIdx / numFilterColors;\n    const int filterColorIdx = imgColorIdx % numFilterColors; // color idx within group\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockFilterIdx = blockGroupIdx * numFiltersPerGroup;\n\n    const int blockPixelIdx = blockIdx.y;\n    const int blockPixelIdxX = blockPixelIdx % imgSizeX;\n    const int blockPixelIdxY = blockPixelIdx / imgSizeX;\n\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n    const int tidx = threadIdx.y * B_X + threadIdx.x;\n    const int hidActLoadY = tidx / 32, hidActLoadX = tidx % 32;\n    const int filtersLoadY = tidx / filterCache, filtersLoadX = tidx % filterCache;\n    const int numModules = numModulesY * numModulesX;\n\n    hidActs += blockCaseIdx + (blockFilterIdx + hidActLoadY) * numImages * numModules + hidActLoadX;\n    filters += blockFilterIdx + (filterColorIdx + filtersLoadY) * filterPixels * numFilters + filtersLoadX;\n    targets += (imgColorIdx + threadIdx.y) * imgPixels * numImages + blockPixelIdx * numImages + blockCaseIdx + threadIdx.x;\n\n    float prod[colorsPerThread][imgsPerThread];\n    #pragma unroll\n    for (int c = 0; c < colorsPerThread; c++) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            prod[c][i] = 0;\n        }\n    }\n\n    const int startY = blockPixelIdxY - paddingStart < filterSize ? 0\n                        : 1 + (blockPixelIdxY - paddingStart - filterSize) / moduleStride;\n    const int endY = MIN(numModulesY, 1 + (blockPixelIdxY - paddingStart) / moduleStride);\n    const int startX = blockPixelIdxX - paddingStart < filterSize ? 0\n                        : 1 + (blockPixelIdxX - paddingStart - filterSize) / moduleStride;\n    const int endX = MIN(numModulesX, 1 + (blockPixelIdxX - paddingStart) / moduleStride);\n\n    float* shFilterLoad = &shFilters[filtersLoadY][filtersLoadX];\n    float* shHidActLoad = &shHidActs[hidActLoadY][hidActLoadX];\n\n    for (int my = startY; my < endY; my++) {\n        const int moduleTop = paddingStart + my * moduleStride;\n        const int pxInFilterY = blockPixelIdxY - moduleTop;\n\n        for (int mx = startX; mx < endX; mx++) {\n            const int moduleIdx = my * numModulesX + mx;\n            const int moduleLeft = paddingStart + mx * moduleStride;\n            const int pxInFilterX = blockPixelIdxX - moduleLeft;\n\n            const int pxIdxInFilter = pxInFilterY * filterSize + pxInFilterX;\n\n            for (int f = 0; f < numFiltersPerGroup; f += filterCache) { // multiply with filterCache filters at a time\n                const float* hLoad = &hidActs[(moduleIdx + f * numModules) * numImages];\n                #pragma unroll\n                for (int i = 0; i < imgsPerThread * B_X; i += 32) {\n                    if (!checkCaseBounds || blockCaseIdx + hidActLoadX + i < numImages) {\n                        #pragma unroll\n                        for (int j = 0; j < filterCache; j += B_X*B_Y/32) { // load filterCache rows of imgsPerThread*B_X cols, 8 * 32 elements at a time.\n                            shHidActLoad[j * B_X * imgsPerThread + i] = hLoad[j * numModules * numImages + i];\n                        }\n                    } else {\n                        #pragma unroll\n                        for (int j = 0; j < filterCache; j += B_X*B_Y/32) { // load filterCache rows of imgsPerThread*B_X cols, 8 * 32 elements at a time.\n                            shHidActLoad[j * B_X * imgsPerThread + i] = 0;\n                        }\n                    }\n                }\n                const float* fLoad = conv ? &filters[pxIdxInFilter * numFilters + f]\n                                          : &filters[moduleIdx * numFilterColors * filterPixels * numFilters + pxIdxInFilter * numFilters + f];\n                #pragma unroll\n                for (int i = 0; i < colorsPerThread*B_Y; i+= B_X*B_Y/filterCache) {\n                    if ((colorsPerThread*B_Y) % (B_X*B_Y/filterCache) == 0 || i + filtersLoadY < colorsPerThread*B_Y) {\n                        shFilterLoad[i * (filterCache + 1)] = fLoad[i * filterPixels * numFilters];\n                    }\n                }\n\n                __syncthreads();\n                // Do some actual computation\n                #pragma unroll\n                for (int i = 0; i < imgsPerThread; i++) {\n                    #pragma unroll\n                    for (int w = 0; w < filterCache; w++) {\n                        #pragma unroll\n                        for (int c = 0; c < colorsPerThread; c++) {\n                            prod[c][i] += shFilters[c * B_Y + threadIdx.y][w] * shHidActs[w][threadIdx.x + i * B_X];\n                        }\n                    }\n                }\n                __syncthreads();\n            }\n        }\n    }\n    if (scale) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || blockCaseIdx + threadIdx.x + i * B_X < numImages) {\n                #pragma unroll\n                for (int c = 0; c < colorsPerThread; c++) {\n                    targets[c * B_Y * imgPixels * numImages + i * B_X] = scaleTargets * targets[c * B_Y * imgPixels * numImages + i * B_X] + scaleOutputs * prod[c][i];\n                }\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || blockCaseIdx + threadIdx.x + i * B_X < numImages) {\n                #pragma unroll\n                for (int c = 0; c < colorsPerThread; c++) {\n                    targets[c * B_Y * imgPixels * numImages + i * B_X] = scaleOutputs * prod[c][i];\n                }\n            }\n        }\n    }\n}\n\n\n/*\n * Block size: B_YxB_X.\n * blockIdx.x determines case in batches of B_X*imgsPerThread, also color in batches of B_Y*colorsPerThread.\n *  In essence, blockIdx.x.x = 1..numImages/(B_X*imgsPerThread)\n *              blockIdx.x.y = 1..numImgColors/(B_Y*colorsPerThread)\n * blockIdx.y determines image pixel in target image.\n *\n * threadIdx.x determines case.\n * threadIdx.y determines color.\n *\n * hidActs:     (numFilters, numModulesY, numModulesX, numImages)\n * filters:     (numFilterColors, filterPixels, numFilters)                             if conv\n *              (numModulesY, numModulesX, numFilterColors, filterPixels, numFilters)   otherwise\n * targets:     (numImageColors, imgSizeY, imgSizeX, numImages)\n *\n * Each block reconstructs one B_Y*colorsPerThread colors from 1 pixel from B_X*imgsPerThread cases.\n *\n * numImages must be divisible by B_X*imgsPerThread if checkCaseBounds is false.\n * numFiltersPerGroup must be divisible by filterCacheF.\n *\n * numFilterColors must be divisible by B_Y*colorsPerThread.\n * B_X*B_Y must be divisible by filterCacheF\n * filterCacheF must be divisible by filterCacheH\n *\n * This version loads 32 cases at a time, so it gets full coalescing on that load.\n * It only loads filterCacheF weights at a time, so those aren't fully coalesced (depending on size of filterCacheF).\n *\n * To be used when there are >= 16 color channels.\n */\ntemplate <int B_Y, int B_X, int imgsPerThread, int colorsPerThread, int filterCacheF, int filterCacheH, bool scale, bool checkCaseBounds, bool conv>\n__global__ void conv_img_acts_manycolor_kepler(const float* hidActs, const float* filters, float* targets,\n                                          const int numModulesY, const int numModulesX, const int numImages, const int numFilters,\n                                          const int filterSize, const int imgSizeY, const int imgSizeX, const int paddingStart, const int moduleStride,\n                                          const int numImgColors, const int numGroups,\n                                          const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shFilters[colorsPerThread*B_Y][filterCacheF];\n    __shared__ float shHidActs[filterCacheH][B_X*imgsPerThread];\n\n    const int numImgBlocks = DIVUP(numImages,B_X*imgsPerThread);\n    const int blockCaseIdx = (blockIdx.x % numImgBlocks) * B_X*imgsPerThread;\n\n    const int imgColorIdx = (blockIdx.x / numImgBlocks) * B_Y*colorsPerThread; // color idx globally\n    const int numFilterColors = numImgColors / numGroups;\n    const int blockGroupIdx = imgColorIdx / numFilterColors;\n    const int filterColorIdx = imgColorIdx % numFilterColors; // color idx within group\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockFilterIdx = blockGroupIdx * numFiltersPerGroup;\n\n    const int blockPixelIdx = blockIdx.y;\n    const int blockPixelIdxX = blockPixelIdx % imgSizeX;\n    const int blockPixelIdxY = blockPixelIdx / imgSizeX;\n\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n    const int tidx = threadIdx.y * B_X + threadIdx.x;\n    const int hidActLoadY = threadIdx.y, hidActLoadX = threadIdx.x;\n    //const int hidActLoadY = tidx / (B_X*imgsPerThread), hidActLoadX = tidx % (B_X*imgsPerThread);\n    const int filtersLoadY = tidx / filterCacheF, filtersLoadX = tidx % filterCacheF;\n    // nvcc is behaving idiotically again, these useless declarations save registers\n    //const int outputY = threadIdx.y, outputX = threadIdx.x;\n    //const int ty = threadIdx.y, tx = threadIdx.x;\n    const int numModules = numModulesY * numModulesX;\n\n    hidActs += blockCaseIdx + (blockFilterIdx + hidActLoadY) * numImages * numModules + hidActLoadX;\n    filters += blockFilterIdx + (filterColorIdx + filtersLoadY) * filterPixels * numFilters + filtersLoadX;\n    targets += (imgColorIdx + threadIdx.y) * imgPixels * numImages + blockPixelIdx * numImages + blockCaseIdx + threadIdx.x;\n\n    float prod[colorsPerThread][imgsPerThread];\n    #pragma unroll\n    for (int c = 0; c < colorsPerThread; c++) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            prod[c][i] = 0;\n        }\n    }\n\n    const int startY = blockPixelIdxY - paddingStart < filterSize ? 0\n                        : 1 + (blockPixelIdxY - paddingStart - filterSize) / moduleStride;\n    const int endY = min(numModulesY, 1 + (blockPixelIdxY - paddingStart) / moduleStride);\n    const int startX = blockPixelIdxX - paddingStart < filterSize ? 0\n                        : 1 + (blockPixelIdxX - paddingStart - filterSize) / moduleStride;\n    const int endX = min(numModulesX, 1 + (blockPixelIdxX - paddingStart) / moduleStride);\n\n    float* shFilterLoad = &shFilters[filtersLoadY][filtersLoadX];\n    float* shHidActLoad = &shHidActs[hidActLoadY][hidActLoadX];\n    //const bool noFLoop = filterCacheF == filterCacheH;\n    for (int my = startY; my < endY; my++) {\n        const int moduleTop = paddingStart + my * moduleStride;\n        const int pxInFilterY = blockPixelIdxY - moduleTop;\n\n        for (int mx = startX; mx < endX; mx++) {\n            const int moduleIdx = my * numModulesX + mx;\n            const int moduleLeft = paddingStart + mx * moduleStride;\n            const int pxInFilterX = blockPixelIdxX - moduleLeft;\n\n            const int pxIdxInFilter = pxInFilterY * filterSize + pxInFilterX;\n\n            for (int f = 0; f < numFiltersPerGroup; f += filterCacheF) { // multiply with filterCacheF filters at a time\n                const float* fLoad = conv ? &filters[pxIdxInFilter * numFilters + f]\n                                          : &filters[moduleIdx * numFilterColors * filterPixels * numFilters + pxIdxInFilter * numFilters + f];\n                #pragma unroll\n                for (int i = 0; i < colorsPerThread*B_Y; i+= B_X*B_Y/filterCacheF) {\n                    if ((colorsPerThread*B_Y) % (B_X*B_Y/filterCacheF) == 0 || i + filtersLoadY < colorsPerThread*B_Y) {\n                        shFilterLoad[i * filterCacheF] = fLoad[i * filterPixels * numFilters];\n                    }\n                }\n                //#pragma unroll\n\n                for (int fh = f; fh < f + filterCacheF; fh += filterCacheH) {\n                    //conv_img_acts_manycolor_dummy_fhLoop<B_Y, B_X, imgsPerThread, colorsPerThread, filterCacheF, filterCacheH, checkCaseBounds>(hidActs, shHidActLoad, shHidActs, shFilters, moduleIdx, numImages, hidActLoadY, hidActLoadX, blockCaseIdx, numModules, f, fh, prod);\n\n                    const float* hLoad = &hidActs[(moduleIdx + fh * numModules) * numImages];\n\n                    #pragma unroll\n                    for (int j = 0; j < filterCacheH; j += B_Y) {\n                        if (filterCacheH % B_Y == 0 || hidActLoadY + j < filterCacheH) {\n                            #pragma unroll\n                            for (int i = 0; i < imgsPerThread*B_X; i += B_X) {\n                                if (!checkCaseBounds || blockCaseIdx + hidActLoadX + i < numImages) {\n                                    shHidActLoad[j * B_X * imgsPerThread + i] = hLoad[j * numModules * numImages + i];\n                                } else {\n                                    shHidActLoad[j * B_X * imgsPerThread + i] = 0;\n                                }\n                            }\n                        }\n                    }\n\n                    __syncthreads();\n\n                    // Do some actual computation\n                    // Using these variables causes register usage to go from 161 --> 123.\n                    // But nonetheless, the high-register version is faster.\n                    //const float* shF = &shFilters[threadIdx.y][fh-f];\n                    //const float* const shF2 = &shFilters[threadIdx.y][fh];\n                    //const float*  shH = &shHidActs[0][threadIdx.x];\n                    #pragma unroll\n                    for (int w = 0; w < filterCacheH; w++) {\n                        #pragma unroll\n                        for (int c = 0; c < colorsPerThread; c++) {\n                            #pragma unroll\n                            for (int i = 0; i < imgsPerThread; i++) {\n                                prod[c][i] += shFilters[c * B_Y + threadIdx.y][fh-f + w] * shHidActs[w][threadIdx.x + i * B_X];\n\n                            }\n                        }\n                    }\n                    __syncthreads();\n\n                }\n            }\n        }\n    }\n    if (scale) {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || blockCaseIdx + threadIdx.x + i * B_X < numImages) {\n                #pragma unroll\n                for (int c = 0; c < colorsPerThread; c++) {\n                    targets[c * B_Y * imgPixels * numImages + i * B_X] = scaleTargets * targets[c * B_Y * imgPixels * numImages + i * B_X] + scaleOutputs * prod[c][i];\n                }\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int i = 0; i < imgsPerThread; i++) {\n            if (!checkCaseBounds || blockCaseIdx + threadIdx.x + i * B_X < numImages) {\n                #pragma unroll\n                for (int c = 0; c < colorsPerThread; c++) {\n                    targets[c * B_Y * imgPixels * numImages + i * B_X] = scaleOutputs * prod[c][i];\n                }\n            }\n        }\n    }\n}\n\n/*\n * New Titan-optimized stuff.\n */\n\n__device__ __forceinline__ void conv_img_acts_manycolor_preload_ty_8_tx_32_c_8_ff_32_fh_16_setCoords(const int my, const int mx, const int numModulesX,\n        const int paddingStart, const int moduleStride, const int blockPixelIdxY, const int blockPixelIdxX, const int filterSize, int &moduleIdx, int &pxIdxInFilter) {\n    const int moduleTop = paddingStart + my * moduleStride;\n    const int pxInFilterY = blockPixelIdxY - moduleTop;\n\n    moduleIdx = my * numModulesX + mx; // out\n    const int moduleLeft = paddingStart + mx * moduleStride;\n    const int pxInFilterX = blockPixelIdxX - moduleLeft;\n\n    pxIdxInFilter = pxInFilterY * filterSize + pxInFilterX; // out\n}\n\n#define IA_PRELOAD_LOOP(w,offset) _Pragma(\"unroll\") \\\nfor (int i = 0; i < imgsPerThread; i++) { \\\n    _Pragma(\"unroll\") \\\n    for (int c = 0; c < colorsPerThread; c++) { \\\n        prod[c][i] += shFilters[c * B_Y + threadIdx.y][(w)+(offset)] * shHidActs[w][threadIdx.x * imgsPerThread + i]; \\\n    } \\\n} \\\n\n/*\n * Same loop as above but inverted.\n */\n#define IA_PRELOAD_LOOP2(w,offset) _Pragma(\"unroll\") \\\nfor (int c = 0; c < colorsPerThread; c++) { \\\n    _Pragma(\"unroll\") \\\n    for (int i = 0; i < imgsPerThread; i++) { \\\n        prod[c][i] += shFilters[c * B_Y + threadIdx.y][(w)+(offset)] * shHidActs[w][threadIdx.x * imgsPerThread + i]; \\\n    } \\\n} \\\n\n#define IA_PRELOAD_LOOP3(i,offset) _Pragma(\"unroll\") \\\nfor (int w = 0; w < filterCacheH; w++) { \\\n    _Pragma(\"unroll\") \\\n    for (int c = 0; c < colorsPerThread; c++) { \\\n        prod[c][i] += shFilters[c * B_Y + threadIdx.y][(w)+(offset)] * shHidActs[w][threadIdx.x * imgsPerThread + i]; \\\n    } \\\n} \\\n\n#define IA_PRELOAD_W(z) wPreload[z] = fLoad[(z) * B_X*B_Y/filterCacheF * filterPixels * numFilters];\n#define IA_PRELOAD_W_TX(z) wPreload[z] = tex1Dfetch<float>(filters, filtersLoadOffset + (z) * B_X*B_Y/filterCacheF * filterPixels * numFilters);\n#define IA_PRELOAD_H(y,x) if (!checkCaseBounds || myCaseIdx + (x) * B_X < numImages) { \\\n    hPreload[y][x] =  hLoad[(y) * B_Y * numModules * numImages + (x) * B_X]; \\\n}\n#define IA_PRELOAD_H_TX(y,x) if (!checkCaseBounds || myCaseIdx + (x) * B_X < numImages) { \\\n    hPreload[y][x] =  tex1Dfetch<float>(hidActs, hidActsLoadOffset + (y) * B_Y * numModules * numImages + (x) * B_X); \\\n}\n\ntemplate <int B_Y, int B_X, int imgsPerThread, int colorsPerThread, int filterCacheF, int filterCacheH, bool scale, bool checkCaseBounds, bool conv>\n__global__ void\n__launch_bounds__(256, 2)   // 256 threads per block, 2 blocks per multiprocessor\n                            // These launch bounds ensure 25% occupancy (128 registers used)\n                            // as oppposed to 13% (130 registers) achieved by defaults.\nconv_img_acts_manycolor_preloadfh_ty_8_tx_32_c_8_ff_32_fh_16_tex(cudaTextureObject_t hidActs, cudaTextureObject_t filters, float* targets,\n                                          const int numModulesY, const int numModulesX, const int numImages, const int numFilters,\n                                          const int filterSize, const int imgSizeY, const int imgSizeX, const int paddingStart, const int moduleStride,\n                                          const int numImgColors, const int numGroups,\n                                          const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shFilters[colorsPerThread*B_Y][filterCacheF];\n    __shared__ float shHidActs[filterCacheH][B_X*imgsPerThread];\n\n    const int numImgBlocks = DIVUP(numImages,B_X*imgsPerThread);\n    const int blockCaseIdx = (blockIdx.x % numImgBlocks) * B_X*imgsPerThread;\n    const int myCaseIdx = blockCaseIdx + threadIdx.x;\n\n    const int imgColorIdx = (blockIdx.x / numImgBlocks) * B_Y*colorsPerThread; // color idx globally\n    const int numFilterColors = numImgColors / numGroups;\n    const int blockGroupIdx = imgColorIdx / numFilterColors;\n    const int filterColorIdx = imgColorIdx % numFilterColors; // color idx within group\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockFilterIdx = blockGroupIdx * numFiltersPerGroup;\n\n    const int blockPixelIdx = blockIdx.y;\n    const int blockPixelIdxX = blockPixelIdx % imgSizeX;\n    const int blockPixelIdxY = blockPixelIdx / imgSizeX;\n\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n    const int tidx = threadIdx.y * B_X + threadIdx.x;\n//    const int hidActLoadY = threadIdx.y % B_Y, hidActLoadX = threadIdx.x % B_X;\n    //const int hidActLoadY = tidx / (B_X*imgsPerThread), hidActLoadX = tidx % (B_X*imgsPerThread);\n    const int filtersLoadY = tidx / filterCacheF, filtersLoadX = tidx % filterCacheF;\n    // nvcc is behaving idiotically again, these useless declarations save registers\n    //const int outputY = threadIdx.y, outputX = threadIdx.x;\n    //const int ty = threadIdx.y, tx = threadIdx.x;\n    const int numModules = numModulesY * numModulesX;\n    const int hidActsOffset = (blockFilterIdx + threadIdx.y) * numImages * numModules + myCaseIdx;\n    const int filtersOffset = blockFilterIdx + (filterColorIdx + filtersLoadY) * filterPixels * numFilters + filtersLoadX;\n//    hidActs += (blockFilterIdx + threadIdx.y) * numImages * numModules + myCaseIdx;\n//    filters += blockFilterIdx + (filterColorIdx + filtersLoadY) * filterPixels * numFilters + filtersLoadX;\n    targets += (imgColorIdx + threadIdx.y) * imgPixels * numImages + blockPixelIdx * numImages + myCaseIdx;\n\n    float prod[colorsPerThread][imgsPerThread];\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            prod[c][i] = 0;\n        }\n    }\n\n    const int startY = blockPixelIdxY - paddingStart < filterSize ? 0\n                        : 1 + (blockPixelIdxY - paddingStart - filterSize) / moduleStride;\n    const int endY = min(numModulesY, 1 + (blockPixelIdxY - paddingStart) / moduleStride);\n    const int startX = blockPixelIdxX - paddingStart < filterSize ? 0\n                        : 1 + (blockPixelIdxX - paddingStart - filterSize) / moduleStride;\n    const int endX = min(numModulesX, 1 + (blockPixelIdxX - paddingStart) / moduleStride);\n\n    float* shFilterLoad = &shFilters[filtersLoadY][filtersLoadX];\n    float* shHidActLoad = &shHidActs[threadIdx.y][threadIdx.x * imgsPerThread];\n    //const bool noFLoop = filterCacheF == filterCacheH;\n\n    /*\n     * Initial preload\n     */\n    float hPreload[filterCacheH/B_Y][imgsPerThread]; // [2][4]\n    float wPreload[filterCacheF*colorsPerThread/B_X]; // [8]\n\n    int moduleIdx, pxIdxInFilter;\n    conv_img_acts_manycolor_preload_ty_8_tx_32_c_8_ff_32_fh_16_setCoords(startY, startX, numModulesX, paddingStart, moduleStride, blockPixelIdxY,\n                                                                         blockPixelIdxX, filterSize, moduleIdx, pxIdxInFilter);\n//    const float* fLoad = conv ? &filters[pxIdxInFilter * numFilters + 0]\n//                              : &filters[moduleIdx * numFilterColors * filterPixels * numFilters + pxIdxInFilter * numFilters + 0];\n    int filtersLoadOffset = filtersOffset + (conv ? pxIdxInFilter * numFilters + 0\n                                                  : moduleIdx * numFilterColors * filterPixels * numFilters + pxIdxInFilter * numFilters);\n    #pragma unroll\n    for (int i = 0; i < colorsPerThread*B_Y; i+= B_X*B_Y/filterCacheF) {\n        if ((colorsPerThread*B_Y) % (B_X*B_Y/filterCacheF) == 0 || i + filtersLoadY < colorsPerThread*B_Y) {\n            wPreload[i * filterCacheF/(B_X*B_Y)] = tex1Dfetch<float>(filters, filtersLoadOffset + i * filterPixels * numFilters);\n        }\n    }\n\n//    const float* hLoad = &hidActs[(moduleIdx + 0 * numModules) * numImages];\n    int hidActsLoadOffset = hidActsOffset + (moduleIdx + 0 * numModules) * numImages;\n    #pragma unroll\n    for (int j = 0; j < filterCacheH; j += B_Y) {\n        if (filterCacheH % B_Y == 0 || threadIdx.y + j < filterCacheH) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || myCaseIdx + i * B_X < numImages) {\n                    hPreload[j/B_Y][i] = tex1Dfetch<float>(hidActs, hidActsLoadOffset + j * numModules * numImages + i * B_X);\n                }\n            }\n        }\n    }\n\n    for (int my = startY; my < endY; my++) {\n        const int moduleTop = paddingStart + my * moduleStride;\n        const int pxInFilterY = blockPixelIdxY - moduleTop;\n\n        for (int mx = startX; mx < endX; mx++) {\n            moduleIdx = my * numModulesX + mx;\n            const int moduleLeft = paddingStart + mx * moduleStride;\n            const int pxInFilterX = blockPixelIdxX - moduleLeft;\n\n            pxIdxInFilter = pxInFilterY * filterSize + pxInFilterX;\n            int myNext = my, mxNext = mx, moduleIdxNext, pxIdxInFilterNext;\n            const bool lastModule = my == endY - 1 && mx == endX - 1;\n            if (!lastModule) {\n                mxNext = mx + 1 == endX ? startX : mx + 1;\n                myNext = my + (mx + 1 == endX);\n            }\n            conv_img_acts_manycolor_preload_ty_8_tx_32_c_8_ff_32_fh_16_setCoords(myNext, mxNext, numModulesX, paddingStart, moduleStride, blockPixelIdxY,\n                                                                                 blockPixelIdxX, filterSize, moduleIdxNext, pxIdxInFilterNext);\n            for (int f = 0; f < numFiltersPerGroup; f += filterCacheF) { // multiply with filterCacheF filters at a time\n                #pragma unroll\n                for (int i = 0; i < colorsPerThread*B_Y; i+= B_X*B_Y/filterCacheF) {\n                    if ((colorsPerThread*B_Y) % (B_X*B_Y/filterCacheF) == 0 || i + filtersLoadY < colorsPerThread*B_Y) {\n                        shFilterLoad[i * filterCacheF] = wPreload[i * filterCacheF/(B_X*B_Y)];\n                    }\n                }\n\n                filtersLoadOffset = filtersOffset + (conv ? pxIdxInFilter * numFilters + f + filterCacheF\n                                                          : moduleIdx * numFilterColors * filterPixels * numFilters + pxIdxInFilter * numFilters + f + filterCacheF);\n                if (f == numFiltersPerGroup - filterCacheF) {\n                    filtersLoadOffset = filtersOffset + (conv ? pxIdxInFilterNext * numFilters\n                                                              : moduleIdxNext * numFilterColors * filterPixels * numFilters + pxIdxInFilterNext * numFilters);\n                }\n\n                #pragma unroll\n                for (int j = 0; j < filterCacheH; j += B_Y) {\n                    if (filterCacheH % B_Y == 0 || threadIdx.y + j < filterCacheH) {\n                        #pragma unroll\n                        for (int i = 0; i < imgsPerThread; i++) {\n                            // NOTE: bank conflicts here!\n                            if (!checkCaseBounds || myCaseIdx + i * B_X < numImages) {\n                                shHidActLoad[j * B_X * imgsPerThread + i] = hPreload[j/B_Y][i];\n                            }\n                        }\n                    }\n                }\n\n                __syncthreads();\n\n                hidActsLoadOffset = hidActsOffset + (moduleIdx + (f + filterCacheH) * numModules) * numImages;\n\n                #pragma unroll\n                for (int z = 0; z < 4; ++z) {\n                    IA_PRELOAD_LOOP(z,0);\n                    IA_PRELOAD_W_TX(z);\n                }\n\n                #pragma unroll\n                for (int z = 4; z < 12; ++z) {\n                    IA_PRELOAD_LOOP(z,0);\n                    IA_PRELOAD_H_TX((z-4)/4,z%4);\n                }\n\n                #pragma unroll\n                for (int z = 12; z < 16; ++z) {\n                    IA_PRELOAD_LOOP(z,0);\n                }\n\n                __syncthreads();\n\n                #pragma unroll\n                for (int j = 0; j < filterCacheH; j += B_Y) {\n                    if (filterCacheH % B_Y == 0 || threadIdx.y + j < filterCacheH) {\n                        #pragma unroll\n                        for (int i = 0; i < imgsPerThread; i++) {\n                            if (!checkCaseBounds || myCaseIdx + i * B_X < numImages) {\n                                shHidActLoad[j * B_X * imgsPerThread + i] = hPreload[j/B_Y][i];\n                            }\n                        }\n                    }\n                }\n\n                __syncthreads();\n\n                hidActsLoadOffset = hidActsOffset + (moduleIdx + (f + filterCacheF) * numModules) * numImages;\n                if (f == numFiltersPerGroup - filterCacheF) {\n                    hidActsLoadOffset = hidActsOffset + moduleIdxNext * numImages;\n                }\n\n                #pragma unroll\n                for (int z = 0; z < 4; ++z) {\n                    IA_PRELOAD_LOOP(z,filterCacheH);\n                    IA_PRELOAD_W_TX(z+4);\n                }\n\n                #pragma unroll\n                for (int z = 4; z < 12; ++z) {\n                    IA_PRELOAD_LOOP(z,filterCacheH);\n                    IA_PRELOAD_H_TX((z-4)/4, z%4);\n                }\n\n                #pragma unroll\n                for (int z = 12; z < 16; ++z) {\n                    IA_PRELOAD_LOOP(z,filterCacheH);\n                }\n\n                __syncthreads();\n            }\n        }\n    }\n    if (scale) {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || myCaseIdx + i * B_X < numImages) {\n                    targets[c * B_Y * imgPixels * numImages + i * B_X] = scaleTargets * targets[c * B_Y * imgPixels * numImages + i * B_X] + scaleOutputs * prod[c][i];\n                }\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || myCaseIdx + i * B_X < numImages) {\n                    targets[c * B_Y * imgPixels * numImages + i * B_X] = scaleOutputs * prod[c][i];\n                }\n            }\n        }\n    }\n}\n\n\ntemplate <int B_Y, int B_X, int imgsPerThread, int colorsPerThread, int filterCacheF, int filterCacheH, bool scale, bool checkCaseBounds, bool conv>\n__global__ void\n//__launch_bounds__(128, 3)   // 128 threads per block, 3 blocks per multiprocessor\nconv_img_acts_manycolor_preloadfh_ty_4_tx_32_c_12_ff_16_fh_16(cudaTextureObject_t hidActs, cudaTextureObject_t filters, float* targets,\n                                          const int numModulesY, const int numModulesX, const int numImages, const int numFilters,\n                                          const int filterSize, const int imgSizeY, const int imgSizeX, const int paddingStart, const int moduleStride,\n                                          const int numImgColors, const int numGroups,\n                                          const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shFilters[colorsPerThread*B_Y][filterCacheF];\n    __shared__ float shHidActs[filterCacheH][B_X*imgsPerThread];\n\n    const int numImgBlocks = DIVUP(numImages,B_X*imgsPerThread);\n    const int blockCaseIdx = (blockIdx.x % numImgBlocks) * B_X*imgsPerThread;\n    const int myCaseIdx = blockCaseIdx + threadIdx.x;\n\n    const int imgColorIdx = (blockIdx.x / numImgBlocks) * B_Y*colorsPerThread; // color idx globally\n    const int numFilterColors = numImgColors / numGroups;\n    const int blockGroupIdx = imgColorIdx / numFilterColors;\n    const int filterColorIdx = imgColorIdx % numFilterColors; // color idx within group\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockFilterIdx = blockGroupIdx * numFiltersPerGroup;\n\n    const int blockPixelIdx = blockIdx.y;\n    const int blockPixelIdxX = blockPixelIdx % imgSizeX;\n    const int blockPixelIdxY = blockPixelIdx / imgSizeX;\n\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n    const int tidx = threadIdx.y * B_X + threadIdx.x;\n//    const int hidActLoadY = threadIdx.y % B_Y, hidActLoadX = threadIdx.x % B_X;\n    //const int hidActLoadY = tidx / (B_X*imgsPerThread), hidActLoadX = tidx % (B_X*imgsPerThread);\n    const int filtersLoadY = tidx / filterCacheF, filtersLoadX = tidx % filterCacheF;\n    // nvcc is behaving idiotically again, these useless declarations save registers\n    //const int outputY = threadIdx.y, outputX = threadIdx.x;\n    //const int ty = threadIdx.y, tx = threadIdx.x;\n    const int numModules = numModulesY * numModulesX;\n\n    const int hidActsOffset = (blockFilterIdx + threadIdx.y) * numImages * numModules + myCaseIdx;\n    const int filtersOffset = blockFilterIdx + (filterColorIdx + filtersLoadY) * filterPixels * numFilters + filtersLoadX;\n\n//    hidActs += (blockFilterIdx + threadIdx.y) * numImages * numModules + myCaseIdx;\n//    filters += blockFilterIdx + (filterColorIdx + filtersLoadY) * filterPixels * numFilters + filtersLoadX;\n    targets += (imgColorIdx + threadIdx.y) * imgPixels * numImages + blockPixelIdx * numImages + myCaseIdx;\n\n    float prod[colorsPerThread][imgsPerThread];\n    #pragma unroll\n    for (int i = 0; i < imgsPerThread; i++) {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            prod[c][i] = 0;\n        }\n    }\n\n    const int startY = blockPixelIdxY - paddingStart < filterSize ? 0\n                        : 1 + (blockPixelIdxY - paddingStart - filterSize) / moduleStride;\n    const int endY = min(numModulesY, 1 + (blockPixelIdxY - paddingStart) / moduleStride);\n    const int startX = blockPixelIdxX - paddingStart < filterSize ? 0\n                        : 1 + (blockPixelIdxX - paddingStart - filterSize) / moduleStride;\n    const int endX = min(numModulesX, 1 + (blockPixelIdxX - paddingStart) / moduleStride);\n\n    float* shFilterLoad = &shFilters[filtersLoadY][filtersLoadX];\n    float* shHidActLoad = &shHidActs[threadIdx.y][threadIdx.x * imgsPerThread];\n    //const bool noFLoop = filterCacheF == filterCacheH;\n\n    /*\n     * Initial preload\n     */\n    float hPreload[filterCacheH/B_Y][imgsPerThread]; // [4][4]\n    float wPreload[filterCacheF*colorsPerThread/B_X]; // [6]\n\n    int moduleIdx, pxIdxInFilter;\n    conv_img_acts_manycolor_preload_ty_8_tx_32_c_8_ff_32_fh_16_setCoords(startY, startX, numModulesX, paddingStart, moduleStride, blockPixelIdxY,\n                                                                         blockPixelIdxX, filterSize, moduleIdx, pxIdxInFilter);\n//    const float* fLoad = conv ? &filters[pxIdxInFilter * numFilters + 0]\n//                              : &filters[moduleIdx * numFilterColors * filterPixels * numFilters + pxIdxInFilter * numFilters + 0];\n    int filtersLoadOffset = filtersOffset + (conv ? pxIdxInFilter * numFilters\n                                                : moduleIdx * numFilterColors * filterPixels * numFilters + pxIdxInFilter * numFilters);\n    #pragma unroll\n    for (int i = 0; i < colorsPerThread*B_Y; i+= B_X*B_Y/filterCacheF) {\n        if ((colorsPerThread*B_Y) % (B_X*B_Y/filterCacheF) == 0 || i + filtersLoadY < colorsPerThread*B_Y) {\n            wPreload[i * filterCacheF/(B_X*B_Y)] = tex1Dfetch<float>(filters, filtersLoadOffset + i * filterPixels * numFilters);\n        }\n    }\n\n//    const float* hLoad = &hidActs[moduleIdx * numImages];\n    int hidActsLoadOffset = hidActsOffset + moduleIdx * numImages;\n    #pragma unroll\n    for (int j = 0; j < filterCacheH; j += B_Y) {\n        if (filterCacheH % B_Y == 0 || threadIdx.y + j < filterCacheH) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || myCaseIdx + i * B_X < numImages) {\n                    hPreload[j/B_Y][i] = tex1Dfetch<float>(hidActs, hidActsLoadOffset + j * numModules * numImages + i * B_X);\n                }\n            }\n        }\n    }\n\n    for (int my = startY; my < endY; my++) {\n        const int moduleTop = paddingStart + my * moduleStride;\n        const int pxInFilterY = blockPixelIdxY - moduleTop;\n\n        for (int mx = startX; mx < endX; mx++) {\n            moduleIdx = my * numModulesX + mx;\n            const int moduleLeft = paddingStart + mx * moduleStride;\n            const int pxInFilterX = blockPixelIdxX - moduleLeft;\n\n            pxIdxInFilter = pxInFilterY * filterSize + pxInFilterX;\n            int myNext = my, mxNext = mx, moduleIdxNext, pxIdxInFilterNext;\n            const bool lastModule = my == endY - 1 && mx == endX - 1;\n            if (!lastModule) {\n                mxNext = mx + 1 == endX ? startX : mx + 1;\n                myNext = my + (mx + 1 == endX);\n            }\n            conv_img_acts_manycolor_preload_ty_8_tx_32_c_8_ff_32_fh_16_setCoords(myNext, mxNext, numModulesX, paddingStart, moduleStride, blockPixelIdxY,\n                                                                                 blockPixelIdxX, filterSize, moduleIdxNext, pxIdxInFilterNext);\n            for (int f = 0; f < numFiltersPerGroup; f += filterCacheF) { // multiply with filterCacheF filters at a time\n                #pragma unroll\n                for (int i = 0; i < colorsPerThread*B_Y; i+= B_X*B_Y/filterCacheF) {\n                    if ((colorsPerThread*B_Y) % (B_X*B_Y/filterCacheF) == 0 || i + filtersLoadY < colorsPerThread*B_Y) {\n                        shFilterLoad[i * filterCacheF] = wPreload[i * filterCacheF/(B_X*B_Y)];\n                    }\n                }\n\n                filtersLoadOffset = filtersOffset + (conv ? pxIdxInFilter * numFilters + f + filterCacheF\n                                                          : moduleIdx * numFilterColors * filterPixels * numFilters + pxIdxInFilter * numFilters + f + filterCacheF);\n                if (f == numFiltersPerGroup - filterCacheF) {\n                    filtersLoadOffset = filtersOffset + (conv ? pxIdxInFilterNext * numFilters\n                                                              : moduleIdxNext * numFilterColors * filterPixels * numFilters + pxIdxInFilterNext * numFilters);\n                }\n\n                #pragma unroll\n                for (int j = 0; j < filterCacheH; j += B_Y) {\n                    if (filterCacheH % B_Y == 0 || threadIdx.y + j < filterCacheH) {\n                        #pragma unroll\n                        for (int i = 0; i < imgsPerThread; i++) {\n                            // NOTE: bank conflicts here!\n                            if (!checkCaseBounds || myCaseIdx + i * B_X < numImages) {\n                                shHidActLoad[j * B_X * imgsPerThread + i] = hPreload[j/B_Y][i];\n                            }\n                        }\n                    }\n                }\n                hidActsLoadOffset = hidActsOffset + (moduleIdx + (f + filterCacheF) * numModules) * numImages;\n                if (f == numFiltersPerGroup - filterCacheF) {\n                    hidActsLoadOffset = hidActsOffset + moduleIdxNext * numImages;\n                }\n\n                __syncthreads();\n\n                // It seems that there is no point explicitly interleaving loads\n                // and computations because the scheduler does that anyway.\n\n                IA_PRELOAD_LOOP2(0,0);\n                IA_PRELOAD_LOOP2(1,0);\n                IA_PRELOAD_LOOP2(2,0);\n                IA_PRELOAD_LOOP2(3,0);\n                IA_PRELOAD_LOOP2(4,0);\n                IA_PRELOAD_LOOP2(5,0);\n                IA_PRELOAD_LOOP2(6,0);\n                IA_PRELOAD_LOOP2(7,0);\n                IA_PRELOAD_LOOP2(8,0);\n                IA_PRELOAD_LOOP2(9,0);\n                IA_PRELOAD_LOOP2(10,0);\n                IA_PRELOAD_LOOP2(11,0);\n                IA_PRELOAD_LOOP2(12,0);\n                IA_PRELOAD_LOOP2(13,0);\n                IA_PRELOAD_LOOP2(14,0);\n                IA_PRELOAD_LOOP2(15,0);\n\n                IA_PRELOAD_W_TX(0);\n                IA_PRELOAD_W_TX(1);\n                IA_PRELOAD_W_TX(2);\n                IA_PRELOAD_W_TX(3);\n                IA_PRELOAD_W_TX(4);\n                IA_PRELOAD_W_TX(5);\n\n                IA_PRELOAD_H_TX(0,0);\n                IA_PRELOAD_H_TX(0,1);\n                IA_PRELOAD_H_TX(0,2);\n                IA_PRELOAD_H_TX(0,3);\n                IA_PRELOAD_H_TX(1,0);\n                IA_PRELOAD_H_TX(1,1);\n                IA_PRELOAD_H_TX(1,2);\n                IA_PRELOAD_H_TX(1,3);\n                IA_PRELOAD_H_TX(2,0);\n                IA_PRELOAD_H_TX(2,1);\n                IA_PRELOAD_H_TX(2,2);\n                IA_PRELOAD_H_TX(2,3);\n                IA_PRELOAD_H_TX(3,0);\n                IA_PRELOAD_H_TX(3,1);\n                IA_PRELOAD_H_TX(3,2);\n                IA_PRELOAD_H_TX(3,3);\n\n                __syncthreads();\n            }\n        }\n    }\n    if (scale) {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || myCaseIdx + i * B_X < numImages) {\n                    targets[c * B_Y * imgPixels * numImages + i * B_X] = scaleTargets * targets[c * B_Y * imgPixels * numImages + i * B_X] + scaleOutputs * prod[c][i];\n                }\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int i = 0; i < imgsPerThread; i++) {\n                if (!checkCaseBounds || myCaseIdx + i * B_X < numImages) {\n                    targets[c * B_Y * imgPixels * numImages + i * B_X] = scaleOutputs * prod[c][i];\n                }\n            }\n        }\n    }\n}\n\n/*\n * hidActs:         (numFilters, numModules, numImages)\n * filters:         (numFilterColors, filterPixels, numFilters)               if conv\n *                  (numModules, numFilterColors, filterPixels, numFilters)   otherwise\n * targets:         (overSample, numImgColors, imgPixels, numImages)\n *\n * Note: all of these convolution routines are optimized for the case when\n * the number of images (i.e. the minibatch size) is a multiple of 128.\n * Other batch sizes will work, but but I made no attempt whatsoever\n * to make them work fast.\n */\nvoid _imgActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* hidActs, caffe2::TensorCUDA* filters, caffe2::TensorCUDA* targets,\n              int imgSizeY, int imgSizeX, int numModulesY, int paddingStart, int moduleStride, int numImgColors, int numGroups,\n              float scaleTargets, float scaleOutput, bool conv) {\n    CAFFE_ENFORCE(hidActs->ndim() == 2);\n    CAFFE_ENFORCE(filters->ndim() == 2);\n    CAFFE_ENFORCE(targets->ndim() == 2);\n\n    int numFilterColors = numImgColors / numGroups;\n    int numImages = hidActs->dim32(1);\n    int numFilters = filters->dim32(1);\n    int numModules = hidActs->dim32(0) / numFilters;\n    int filterModuleMult = conv ? 1 : numModules;\n    int filterPixels = filters->dim32(0) / (filterModuleMult * numFilterColors);\n    int filterSize = sqrt(filterPixels);\n    int imgPixels = imgSizeY * imgSizeX;\n    int numModulesX = numModules / numModulesY;\n\n    CAFFE_ENFORCE(numImgColors % numGroups == 0);\n    CAFFE_ENFORCE(numFilters % (16*numGroups) == 0); // TODO: insisting on 32 filters due to bug in calling code below. fix that.\n    CAFFE_ENFORCE(numGroups > 1 || (numImgColors > 0 && (numImgColors <= 3 || numImgColors % 2 == 0)));\n    CAFFE_ENFORCE(numGroups == 1 || numFilterColors % 4 == 0);\n\n    CAFFE_ENFORCE(filterPixels == filterSize * filterSize);\n    CAFFE_ENFORCE(hidActs->dim32(0) == numModules * numFilters);\n    CAFFE_ENFORCE(filters->dim32(0) == filterModuleMult * numFilterColors * filterPixels);\n    CAFFE_ENFORCE(numModules == numModulesY * numModulesX);\n\n    // These routines don't handle the case when only part of the image is visited in the convolution\n    CAFFE_ENFORCE(paddingStart <= 0);\n    CAFFE_ENFORCE(paddingStart + (numModulesX-1)*moduleStride + filterSize >= imgSizeX);\n    CAFFE_ENFORCE(paddingStart + (numModulesY-1)*moduleStride + filterSize >= imgSizeY);\n    CAFFE_ENFORCE(moduleStride <= filterSize);\n\n    dim3 blocks;\n    dim3 threads;\n    int colorsPerThread, imgsPerThread;\n    if (numFilterColors % 8 == 0) {\n        threads = dim3(32, numFilterColors % 64 == 0 ? 8 : 4);\n        colorsPerThread = numFilterColors % 64 == 0 ? 8\n                        : numFilterColors % 48 == 0 ? 12\n                        : numFilterColors % 32 == 0 ? 8\n                        : numFilterColors % 16 == 0 ? 4\n                        : 2;\n        imgsPerThread = numImages % 128 == 0 ? 4 : numImages % 64 == 0 ? 2 : 1;\n        CAFFE_ENFORCE(numFilterColors % (threads.y * colorsPerThread) == 0);\n\n        blocks = dim3(DIVUP(numImages, threads.x*imgsPerThread) * (numImgColors/(threads.y*colorsPerThread)), imgPixels);\n        // NOTE: the case when channels % 32 == 0 but channels % 48 != 0 and channels % 64 != 0 has not been optimized!!\n    } else if (numFilterColors > 3) {\n        // NOTE: THIS CASE HAS NOT BEEN OPTIMIZED FOR KEPLER!!\n        imgsPerThread = numImages % 128 == 0 ? 8 : numImages % 64 == 0 ? 4 : 2;\n        threads = dim3(16, 16);\n        colorsPerThread = numFilterColors % 4 == 0 ? 4 : 2;\n        blocks = dim3(DIVUP(numImages,threads.x*imgsPerThread) * (numImgColors / colorsPerThread), DIVUP(imgSizeY,4) * DIVUP(imgSizeX,4));\n    } else {\n        // NOTE: THIS CASE HAS NOT BEEN OPTIMIZED FOR KEPLER!!\n        imgsPerThread = numImages % 128 == 0 ? 8 : numImages % 64 == 0 ? 4 : 2;\n        threads = dim3(16, 16);\n        blocks = dim3(DIVUP(numImages,threads.x*imgsPerThread), DIVUP(imgSizeY,4) * DIVUP(imgSizeX,4));\n    }\n    bool checkCaseBounds = numImages % (threads.x * imgsPerThread) != 0;\n\n    if (scaleTargets == 0) { // do not scale or use targets matrix\n        targets->Resize(std::vector<int>{numImgColors*imgPixels, numImages});\n    } else {\n        CAFFE_ENFORCE(targets->dim32(0) == numImgColors * imgPixels);\n        CAFFE_ENFORCE(targets->dim32(1) == numImages);\n    }\n    const bool scale = scaleTargets != 0;\n\n    cudaTextureObject_t tex_hidacts = GetTensorTextureObject(hidActs);\n    cudaTextureObject_t tex_filters = GetTensorTextureObject(filters);\n    float* hidacts_data = hidActs->mutable_data<float>();\n    float* filters_data = filters->mutable_data<float>();\n    float* targets_data = targets->mutable_data<float>();\n\n    cudaStream_t stream = context->cuda_stream();\n//    cudaFuncSetCacheConfig(conv_img_acts_manycolor_preloadfh_ty_4_tx_32_c_12_ff_16_fh_16< 4, 32, 4, 12, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n//    conv_img_acts_manycolor_preloadfh_ty_4_tx_32_c_12_ff_16_fh_16< 4, 32, 4, 12, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(\n//            tex_hidacts, tex_filters, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize,\n//            imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n\n    //return;\n//    printf(\"conv: %d\\n\", conv);\n//    printf(\"scale: %d\\n\", scale);\n//    printf(\"checkCaseBounds: %d\\n\", checkCaseBounds);\n//    printf(\"numFilterColors: %d\\n\", numFilterColors);\n//    printf(\"numImages: %d\\n\", numImages);\n//    cudaStream_t stream = NVMatrix::getDefaultStream();\n\n    if (conv == true) {\n        if (scale == false) {\n            if (checkCaseBounds == false) {\n                if (numFilterColors % 8 == 0) {\n                    if (numFilterColors % 64 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_preloadfh_ty_8_tx_32_c_8_ff_32_fh_16_tex< 8, 32, 4, 8, 32, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_preloadfh_ty_8_tx_32_c_8_ff_32_fh_16_tex< 8, 32, 4, 8, 32, 16, false, false, true ><<<blocks, threads, 0, stream>>>(tex_hidacts, tex_filters, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 32, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 32, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 4, 8, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 4, 8, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 48 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_preloadfh_ty_4_tx_32_c_12_ff_16_fh_16< 4, 32, 4, 12, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_preloadfh_ty_4_tx_32_c_12_ff_16_fh_16< 4, 32, 4, 12, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(tex_hidacts, tex_filters, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 12, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 12, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 32 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 32, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 32, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 32, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 32, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 16 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 4, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 4, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 4, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 4, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 8 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 2, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 2, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 2, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 2, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, false, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors > 3) {\n                    if (numFilterColors == 4) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 8, 4, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 8, 4, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 4, 4, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 4, 4, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 2, 4, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 2, 4, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 2, 4, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 2, 4, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 2, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 2, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 2, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 2, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors <= 3) {\n                    if (numFilterColors == 3) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 3, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 3, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 3, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 3, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 3, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 3, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 3, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 3, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 2, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 2, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 2, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 2, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 1) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 1, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 1, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 1, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 1, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 1, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 1, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 1, false, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 1, false, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n            }\n            else if (checkCaseBounds == true) {\n                if (numFilterColors % 8 == 0) {\n                    if (numFilterColors % 64 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, false, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, false, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, false, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, false, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 48 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, false, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, false, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 32 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, false, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, false, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, false, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, false, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 16 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, false, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, false, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 8 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, false, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, false, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors > 3) {\n                    if (numFilterColors == 4) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 2, 4, false, true, true >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 2, 4, false, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, false, true, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, false, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors <= 3) {\n                    if (numFilterColors == 3) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 3, false, true, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 3, false, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, false, true, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, false, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 1) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 1, false, true, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 1, false, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        else if (scale == true) {\n            if (checkCaseBounds == false) {\n                if (numFilterColors % 8 == 0) {\n                    if (numFilterColors % 64 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_preloadfh_ty_8_tx_32_c_8_ff_32_fh_16_tex< 8, 32, 4, 8, 32, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_preloadfh_ty_8_tx_32_c_8_ff_32_fh_16_tex< 8, 32, 4, 8, 32, 16, true, false, true ><<<blocks, threads, 0, stream>>>(tex_hidacts, tex_filters, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 32, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 32, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 4, 8, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 4, 8, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 48 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_preloadfh_ty_4_tx_32_c_12_ff_16_fh_16< 4, 32, 4, 12, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_preloadfh_ty_4_tx_32_c_12_ff_16_fh_16< 4, 32, 4, 12, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(tex_hidacts, tex_filters, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 12, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 12, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 32 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 32, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 32, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 32, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 32, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 16 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 4, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 4, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 4, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 4, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 8 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 2, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 2, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 2, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 2, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, true, false, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors > 3) {\n                    if (numFilterColors == 4) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 8, 4, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 8, 4, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 4, 4, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 4, 4, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 2, 4, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 2, 4, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 2, 4, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 2, 4, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 2, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 2, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 2, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 2, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors <= 3) {\n                    if (numFilterColors == 3) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 3, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 3, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 3, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 3, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 3, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 3, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 3, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 3, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 2, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 2, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 2, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 2, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 1) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 1, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 1, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 1, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 1, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 1, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 1, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 1, true, false, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 1, true, false, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n            }\n            else if (checkCaseBounds == true) {\n                if (numFilterColors % 8 == 0) {\n                    if (numFilterColors % 64 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, true, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, true, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, true, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, true, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 48 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, true, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, true, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 32 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, true, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, true, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, true, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, true, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 16 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, true, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, true, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 8 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, true, true, true >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, true, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors > 3) {\n                    if (numFilterColors == 4) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 2, 4, true, true, true >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 2, 4, true, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, true, true, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, true, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors <= 3) {\n                    if (numFilterColors == 3) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 3, true, true, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 3, true, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, true, true, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, true, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 1) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 1, true, true, true >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 1, true, true, true ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n    else if (conv == false) {\n        if (scale == false) {\n            if (checkCaseBounds == false) {\n                if (numFilterColors % 8 == 0) {\n                    if (numFilterColors % 64 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_preloadfh_ty_8_tx_32_c_8_ff_32_fh_16_tex< 8, 32, 4, 8, 32, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_preloadfh_ty_8_tx_32_c_8_ff_32_fh_16_tex< 8, 32, 4, 8, 32, 16, false, false, false ><<<blocks, threads, 0, stream>>>(tex_hidacts, tex_filters, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 32, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 32, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 4, 8, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 4, 8, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 48 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_preloadfh_ty_4_tx_32_c_12_ff_16_fh_16< 4, 32, 4, 12, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_preloadfh_ty_4_tx_32_c_12_ff_16_fh_16< 4, 32, 4, 12, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(tex_hidacts, tex_filters, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 12, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 12, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 32 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 32, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 32, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 32, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 32, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 16 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 4, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 4, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 4, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 4, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 8 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 2, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 2, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 2, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 2, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, false, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors > 3) {\n                    if (numFilterColors == 4) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 8, 4, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 8, 4, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 4, 4, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 4, 4, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 2, 4, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 2, 4, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 2, 4, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 2, 4, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 2, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 2, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 2, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 2, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors <= 3) {\n                    if (numFilterColors == 3) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 3, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 3, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 3, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 3, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 3, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 3, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 3, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 3, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 2, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 2, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 2, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 2, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 1) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 1, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 1, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 1, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 1, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 1, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 1, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 1, false, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 1, false, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n            }\n            else if (checkCaseBounds == true) {\n                if (numFilterColors % 8 == 0) {\n                    if (numFilterColors % 64 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, false, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, false, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, false, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, false, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 48 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, false, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, false, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 32 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, false, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, false, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, false, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, false, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 16 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, false, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, false, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 8 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, false, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, false, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors > 3) {\n                    if (numFilterColors == 4) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 2, 4, false, true, false >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 2, 4, false, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, false, true, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, false, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors <= 3) {\n                    if (numFilterColors == 3) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 3, false, true, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 3, false, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, false, true, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, false, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 1) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 1, false, true, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 1, false, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        else if (scale == true) {\n            if (checkCaseBounds == false) {\n                if (numFilterColors % 8 == 0) {\n                    if (numFilterColors % 64 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_preloadfh_ty_8_tx_32_c_8_ff_32_fh_16_tex< 8, 32, 4, 8, 32, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_preloadfh_ty_8_tx_32_c_8_ff_32_fh_16_tex< 8, 32, 4, 8, 32, 16, true, false, false ><<<blocks, threads, 0, stream>>>(tex_hidacts, tex_filters, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 32, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 32, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 4, 8, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 4, 8, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 2, 8, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 48 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_preloadfh_ty_4_tx_32_c_12_ff_16_fh_16< 4, 32, 4, 12, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_preloadfh_ty_4_tx_32_c_12_ff_16_fh_16< 4, 32, 4, 12, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(tex_hidacts, tex_filters, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 12, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 12, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 32 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 32, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 32, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 32, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 32, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 8, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 8, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 16 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 4, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 4, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 4, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 4, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 8 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 4, 2, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 4, 2, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 2, 2, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 2, 2, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, true, false, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors > 3) {\n                    if (numFilterColors == 4) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 8, 4, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 8, 4, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 4, 4, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 4, 4, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 2, 4, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 2, 4, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 2, 4, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 2, 4, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 2, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 2, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 2, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 2, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors <= 3) {\n                    if (numFilterColors == 3) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 3, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 3, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 3, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 3, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 3, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 3, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 3, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 3, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 2, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 2, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 2, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 2, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 1) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 128 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 8, 1, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 8, 1, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 64 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 4, 1, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 4, 1, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 32 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 1, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 1, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                            else if (numImages % 16 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 1, true, false, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 1, true, false, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n            }\n            else if (checkCaseBounds == true) {\n                if (numFilterColors % 8 == 0) {\n                    if (numFilterColors % 64 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, true, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 32, 16, true, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, true, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 8, 32, 1, 8, 16, 16, true, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 48 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, true, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 12, 16, 16, true, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 32 == 0) {\n                        if (numFilters % 32 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, true, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 32, 16, true, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                        else if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, true, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 8, 16, 16, true, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 16 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, true, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 4, 16, 16, true, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors % 8 == 0) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, true, true, false >, cudaFuncCachePreferShared);\n                                conv_img_acts_manycolor_kepler < 4, 32, 1, 2, 16, 16, true, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors > 3) {\n                    if (numFilterColors == 4) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_mediumcolor < 2, 4, true, true, false >, cudaFuncCachePreferShared);\n                                img_acts_mediumcolor < 2, 4, true, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, true, true, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, true, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n                else if (numFilterColors <= 3) {\n                    if (numFilterColors == 3) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 3, true, true, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 3, true, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 2) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 2, true, true, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 2, true, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                    else if (numFilterColors == 1) {\n                        if (numFilters % 16 == 0) {\n                            if (numImages % 1 == 0) {\n                                cudaFuncSetCacheConfig(img_acts_color < 2, 1, true, true, false >, cudaFuncCachePreferShared);\n                                img_acts_color < 2, 1, true, true, false ><<<blocks, threads, 0, stream>>>(hidacts_data, filters_data, targets_data, numModulesY, numModulesX, numImages, numFilters, filterSize, imgSizeY, imgSizeX, paddingStart, moduleStride, scaleTargets, scaleOutput);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    checkCudaErrors(cudaDestroyTextureObject(tex_hidacts));\n    checkCudaErrors(cudaDestroyTextureObject(tex_filters));\n\n    getLastCudaError(\"imgActs: kernel execution failed\");\n}\n\n\nvoid convImgActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* hidActs, caffe2::TensorCUDA* filters, caffe2::TensorCUDA* targets,\n                 int imgSizeY, int imgSizeX, int numModulesY, int paddingStart, int moduleStride, int numImgColors, int numGroups) {\n    _imgActs(context, hidActs, filters, targets, imgSizeY, imgSizeX, numModulesY, paddingStart, moduleStride, numImgColors, numGroups, 0, 1, true);\n}\n\nvoid convImgActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* hidActs, caffe2::TensorCUDA* filters, caffe2::TensorCUDA* targets,\n                 int imgSizeY, int imgSizeX, int numModulesY, int paddingStart, int moduleStride, int numImgColors, int numGroups,\n                 float scaleTargets, float scaleOutput) {\n    _imgActs(context, hidActs, filters, targets, imgSizeY, imgSizeX, numModulesY, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput, true);\n}\n\nvoid localImgActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* hidActs, caffe2::TensorCUDA* filters, caffe2::TensorCUDA* targets,\n                  int imgSizeY, int imgSizeX, int numModulesY, int paddingStart, int moduleStride, int numImgColors, int numGroups) {\n    _imgActs(context, hidActs, filters, targets, imgSizeY, imgSizeX, numModulesY, paddingStart, moduleStride, numImgColors, numGroups, 0, 1, false);\n}\n\nvoid localImgActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* hidActs, caffe2::TensorCUDA* filters, caffe2::TensorCUDA* targets,\n                  int imgSizeY, int imgSizeX, int numModulesY, int paddingStart, int moduleStride, int numImgColors, int numGroups,\n                  float scaleTargets, float scaleOutput) {\n    _imgActs(context, hidActs, filters, targets, imgSizeY, imgSizeX, numModulesY, paddingStart, moduleStride, numImgColors, numGroups, scaleTargets, scaleOutput, false);\n}\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconv3/src/weight_acts.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <vector>\n\n#include \"../include/cudaconv2.cuh\"\n\n#define LO16(x)     ((x) & 0x0000FFFF)\n#define HI16(x)     ((x) >> 16)\n\n#define WA_LOOP(r) _Pragma(\"unroll\") \\\nfor (int c = 0; c < colorsPerThread; c++) { \\\n    _Pragma(\"unroll\") \\\n    for (int f = 0; f < filtersPerThread; f++) { \\\n        prod[f][c] += shImages[threadIdx.y + c * B_Y][(r)] * shHidActs[threadIdx.x + f * B_X][(r)]; \\\n    } \\\n}\n\n#define WA_LOOP2(r) _Pragma(\"unroll\") \\\nfor (int f = 0; f < filtersPerThread; f++) { \\\n    _Pragma(\"unroll\") \\\n    for (int c = 0; c < colorsPerThread; c++) { \\\n        prod[f][c] += shImages[threadIdx.y + c * B_Y][(r)] * shHidActs[threadIdx.x + f * B_X][(r)]; \\\n    } \\\n}\n\n#define WA_IMLOAD(r) imPreload[r] = im[(r) * B_X * B_Y / preloadCases * imgPixels * imgStride];\n#define WA_IMLOAD_TX(r) imPreload[r] = tex1Dfetch<float>(images, imgOffset2 + (r) * B_X * B_Y / preloadCases * imgPixels * imgStride);\n#define WA_HALOAD(r) haPreload[r] = ha[(r) * B_X * B_Y / preloadCases * numImages * numModules];\n#define WA_HALOAD_TX(r) haPreload[r] = tex1Dfetch<float>(hidActs, hidActsOffset2 + (r) * B_X * B_Y / preloadCases * numImages * numModules);\n\n__device__ __forceinline__ void conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_8_r_16_setCoords(\n        const int my, const int mx, const int paddingStart, const int numModulesX, const int moduleStride,\n        const int blockPixelY, const int blockPixelX, const int imgSizeX,\n        const int imgStride, int& pixIdx, int& m) {\n    const int imgLoadModPosY = paddingStart + my * moduleStride;\n    const int imgLoadModPosX = paddingStart + mx * moduleStride;\n    const int pxY = imgLoadModPosY + blockPixelY; // pixel x,y coords in image\n    const int pxX = imgLoadModPosX + blockPixelX;\n    pixIdx = (pxY * imgSizeX + pxX) * imgStride; // pixel idx in image\n    m = my * numModulesX + mx;\n}\n\n/*\n * Each block computes weight gradients for B_Y * pixelsPerThread pixels and B_X filters\n * threadIdx.x determines filter\n * threadIdx.y determines pixel in filter\n *\n * blockIdx.x determines filter batch of B_X * filtersPerThread, module batch of partialSum\n * blockIdx.y determines pixel batch of B_Y * pixelsPerThread\n *\n * Number of filters must be divisible by B_X * filtersPerThread\n * Number of images (cases) should be divisible by preloadCases if checkCaseBounds is false.\n *\n * images:      (numColors, imgSizeY, imgSizeX, numImages), with stride given\n * hidActs:     (numFilters, numModulesY, numModulesX, numImages)\n *\n * targets:     (numModulesY*numModulesX/partialSum, numColors, filterPixels, numFilters)\n *\n * B_Y * B_X should be divisible by preloadCases.\n * preloadCases one of 16, 32.\n * B_X one of 4, 8, 16, 32\n * B_Y arbitrary (satisfying divisibility constraints)\n * numModules must be divisible by partialSum\n * pixelsPerThread must be divisible by pixelCache\n *\n * After adding pixelsPerThread, register usage went from 20 to 23 (when pixelsPerThread = 1)...\n * so the compiler is messing up here somehow. It's unable to optimize that case away.\n */\ntemplate <int B_Y, int B_X, int pixelCache, int pixelsPerThread, int filtersPerThread, int preloadCases, int numColors, bool scale, bool checkCaseBounds>\n__global__ void conv_weight_acts_c_kepler(float* images, float* hidActs, float* targets,\n                                   const int numImages, const int numFilters,\n                                   const int numModulesY, const int numModulesX,\n                                   const int imgSizeY, const int imgSizeX, const int filterSize,\n                                   const int paddingStart, const int moduleStride, const int imgStride,\n                                   const int partialSum,\n                                   const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shImages[pixelCache * B_Y * numColors][preloadCases]; // preload preloadCases cases of B_Y * pixelsPerThread pixels\n    __shared__ float shHidActs[B_X * filtersPerThread][preloadCases + 1]; // preload preloadCases cases of B_X hidActs\n\n    const int tidx = B_X * threadIdx.y + threadIdx.x;\n    const int loadY = tidx / preloadCases, loadX = tidx % preloadCases;\n\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n\n    const int filterBlocksPerModule = numFilters / (B_X*filtersPerThread);\n    const int outputModuleIdx = blockIdx.x / filterBlocksPerModule;\n    const int moduleIdx = partialSum * outputModuleIdx;\n    const int blockFilterIdx = B_X * filtersPerThread* (blockIdx.x % filterBlocksPerModule);\n\n//    const int moduleStride = (imgSize - filterSize + 1) / numModulesX;\n    const int numModules = numModulesY * numModulesX;\n\n    const int blockPixelOffset = blockIdx.y * B_Y * pixelsPerThread;\n\n    images += loadX;\n    hidActs += blockFilterIdx * numImages * numModules\n            + loadY * numImages * numModules\n            + loadX;\n\n    targets += (outputModuleIdx * numFilters) * filterPixels * numColors\n            + blockPixelOffset * numFilters\n            + blockFilterIdx\n            + threadIdx.y * numFilters + threadIdx.x;\n\n    float prod[numColors][pixelsPerThread][filtersPerThread];\n    #pragma unroll\n    for (int c = 0; c < numColors; c++) {\n        #pragma unroll\n        for (int p = 0; p < pixelsPerThread; p++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                prod[c][p][f] = 0;\n            }\n        }\n    }\n\n    __shared__ int pxIdxes[B_Y*pixelsPerThread];\n    //__shared__ bool isPxInImage[B_Y*pixelsPerThread];\n    for (int m = moduleIdx; m < moduleIdx + partialSum; m++) {\n\n        __syncthreads();\n        if (tidx < B_Y * pixelsPerThread) {\n            const int imgLoadModPosY = paddingStart + (m / numModulesX) * moduleStride;\n            const int imgLoadModPosX = paddingStart + (m % numModulesX) * moduleStride;\n            int pxY = (imgLoadModPosY + (blockPixelOffset + tidx) / filterSize);\n            int pxX = (imgLoadModPosX + (blockPixelOffset + tidx) % filterSize);\n            int pixIdx = (pxY * imgSizeX + pxX) * imgStride;\n            pxIdxes[tidx] = pxY >= 0 && pxY < imgSizeY && pxX >= 0 && pxX < imgSizeX ? pixIdx : -1;\n            //isPxInImage[tidx] = ;\n        }\n        __syncthreads();\n        for (int caseIdx = 0; caseIdx < numImages; caseIdx += preloadCases) {\n            if (/*loadY < B_X*filtersPerThread &&*/ (!checkCaseBounds || caseIdx + loadX < numImages)) {\n                #pragma unroll\n                for (int y = 0; y < B_X*filtersPerThread; y += (B_X * B_Y) / preloadCases) {\n                    // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                    if ((B_X*filtersPerThread) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_X*filtersPerThread) {\n                        shHidActs[loadY+y][loadX]= hidActs[caseIdx + y * numImages * numModules + m * numImages];\n                    }\n                }\n            }\n            #pragma unroll\n            for (int pp = 0; pp < pixelsPerThread; pp += pixelCache) {\n                //if (loadY < B_Y * pixelCache) { // This condition is not necessary for correctness, but it speeds things a bit\n                /*\n                 * As long as B_Y * B_X is divisible by preloadCases this will loop the right\n                 * number of times.\n                 *\n                 * This will load some imgGrads from filter pixels that don't exit (it'll set those to 0),\n                 * but the code does not produce any output for those pixels (see last lines).\n                 */\n                #pragma unroll\n                for (int y = 0; y < B_Y * pixelCache; y += (B_X * B_Y) / preloadCases) {\n                    // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                    if ((B_Y * pixelCache) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_Y * pixelCache) {\n                        const int pxIdx = pp * B_Y + loadY + y; // pixel idx in filter\n\n                        if (pxIdx + blockPixelOffset < filterPixels && (!checkCaseBounds || caseIdx + loadX < numImages)) {\n                            const int pixIdx = pxIdxes[pxIdx];//(pxY * imgSizeX + pxX) * imgStride;\n\n                            if (pixIdx >= 0) {\n                                #pragma unroll\n                                for (int c = 0; c < numColors; c++) {\n                                    shImages[loadY+y + c * pixelCache * B_Y][loadX] = images[caseIdx + c * imgPixels * imgStride + pixIdx];\n                                }\n                            } else {\n                                #pragma unroll\n                                for (int c = 0; c < numColors; c++) {\n                                    shImages[loadY+y + c * pixelCache * B_Y][loadX] = 0;\n                                }\n                            }\n                        } else {\n                            #pragma unroll\n                            for (int c = 0; c < numColors; c++) {\n                                shImages[loadY+y + c * pixelCache * B_Y][loadX]= 0;\n                            }\n                        }\n                    }\n                }\n                //}\n\n\n                __syncthreads();\n\n                #pragma unroll\n                for (int i = 0; i < preloadCases; i++) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        #pragma unroll\n                        for (int p = 0; p < pixelCache; p++) {\n                            #pragma unroll\n                            for (int c = 0; c < numColors; c++) {\n                                prod[c][pp + p][f] += shImages[threadIdx.y + p * B_Y + c * pixelCache * B_Y][i] * shHidActs[threadIdx.x + f * B_X][i];\n                            }\n                        }\n                    }\n                }\n\n                __syncthreads();\n            }\n        }\n    }\n\n    if (scale) {\n        #pragma unroll\n        for (int p = 0; p < pixelsPerThread; p++) {\n            if (blockPixelOffset + p * B_Y + threadIdx.y < filterPixels) {\n                #pragma unroll\n                for (int c = 0; c < numColors; c++) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        targets[p * B_Y * numFilters + c * filterPixels * numFilters + f * B_X] = scaleTargets * targets[p * B_Y * numFilters + c * filterPixels * numFilters + f * B_X] + scaleOutputs * prod[c][p][f];\n                    }\n                }\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int p = 0; p < pixelsPerThread; p++) {\n            if (blockPixelOffset + p * B_Y + threadIdx.y < filterPixels) {\n                #pragma unroll\n                for (int c = 0; c < numColors; c++) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        targets[p * B_Y * numFilters + c * filterPixels * numFilters + f * B_X] = scaleOutputs * prod[c][p][f];\n                    }\n                }\n            }\n        }\n    }\n}\n\n/*\n * Each block computes weight gradients for 1 pixel, B_Y * colorsPerThread colors and B_X * filtersPerThread filters\n * threadIdx.x determines filter\n * threadIdx.y determines color\n *\n * blockIdx.x determines filter batch of B_X * filtersPerThread, module batch of partialSum\n * blockIdx.y determines color batch of B_Y * colorsPerThread\n * blockIdx.z determines pixel in filter\n *            NOTE: blockIdx.z is limited to values < 2^16. This means that this routine will\n *                  fail for filters >= 256*256. I'm assuming I won't ever use such large filters.\n\n * images:      (numImgColors, imgSizeY, imgSizeX, numImages), with stride given\n * hidActs:     (numFilters, numModulesY, numModulesX, numImages)\n *\n * targets:     (numModulesY*numModulesX/partialSum, numFilterColors, filterPixels, numFilters)\n\n * B_X * B_Y must be divisible by preloadCases\n */\ntemplate <int B_Y, int B_X, int filtersPerThread, int colorsPerThread, int preloadCases, bool scale>\n__global__ void conv_weight_acts_mc_mf_kepler(float* images, float* hidActs, float* targets,\n                                       const int numImages, const int numFilters,\n                                       const int numModulesY, const int numModulesX,\n                                       const int imgSizeY, const int imgSizeX, const int filterSize,\n                                       const int paddingStart, const int moduleStride, const int imgStride,\n                                       const int numImgColors, const int numGroups, const int partialSum,\n                                       const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shImages[colorsPerThread * B_Y][preloadCases]; // preload preloadCases cases\n    __shared__ float shHidActs[filtersPerThread * B_X][preloadCases + 1]; // preload preloadCases cases of B_X hidacts\n\n    const int tidx = B_X * threadIdx.y + threadIdx.x;\n    const int loadY = tidx / preloadCases, loadX = tidx % preloadCases;\n\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n\n    const int numFilterBlocks = numFilters / (B_X * filtersPerThread);\n    const int outputModuleIdx = blockIdx.x / numFilterBlocks;\n    const int moduleIdx = partialSum * outputModuleIdx;\n    const int blockFilterIdx = filtersPerThread * B_X * (blockIdx.x % numFilterBlocks);\n    const int numModules = numModulesY * numModulesX;\n\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockGroupIdx = blockFilterIdx / numFiltersPerGroup;\n    const int numFilterColors = numImgColors / numGroups;\n\n    const int blockPixelOffset = blockIdx.z; // pixel idx in filter\n    const int blockPixelY = blockPixelOffset / filterSize, blockPixelX = blockPixelOffset % filterSize;\n    const int blockFilterColorIdx = blockIdx.y  * B_Y * colorsPerThread;\n    const int imgColorIdx = blockFilterColorIdx + blockGroupIdx * numFilterColors;\n\n    images += (imgColorIdx + loadY) * imgPixels * imgStride + loadX;\n\n    hidActs +=\n             blockFilterIdx * numImages * numModules\n            + loadY * numImages * numModules\n            + loadX;\n\n    targets += outputModuleIdx * numFilters * filterPixels * numFilterColors\n            + (blockFilterColorIdx + threadIdx.y) * filterPixels * numFilters\n            + blockPixelOffset * numFilters\n            + blockFilterIdx\n            + threadIdx.x;\n    //if (blockIdx.x != 0 || blockIdx.y != 0 || blockIdx.z != 0) return;\n    float* shHidActLoad = &shHidActs[loadY][loadX];\n    float* shImgLoad = &shImages[loadY][loadX];\n    float prod[colorsPerThread][filtersPerThread];\n    #pragma unroll\n    for (int c = 0; c < colorsPerThread; c++) {\n        #pragma unroll\n        for (int f = 0; f < filtersPerThread; f++) {\n            prod[c][f] = 0;\n        }\n    }\n\n    for (int m = moduleIdx; m < moduleIdx + partialSum; m++) {\n        const int imgLoadModPosY = paddingStart + (m / numModulesX) * moduleStride;\n        const int imgLoadModPosX = paddingStart + (m % numModulesX) * moduleStride;\n        const int pxY = imgLoadModPosY + blockPixelY; // pixel x,y coords in image\n        const int pxX = imgLoadModPosX + blockPixelX;\n        const int pixIdx = (pxY * imgSizeX + pxX) * imgStride; // pixel idx in image\n        if (pxY >= 0 && pxY < imgSizeY && pxX >= 0 && pxX < imgSizeX) {\n            for (int caseIdx = 0; caseIdx < numImages; caseIdx += preloadCases) {\n                // Checking this condition actually makes things faster ... :/\n                // So I've removed the !checkCaseBounds flag and just check it all the time.\n                if (caseIdx + loadX < numImages) {\n                    /*\n                     * As long as B_Y * B_X is divisible by preloadCases this will loop the right\n                     * number of times.\n                     *\n                     * This will load some images from filter pixels that don't exist (it'll set those to 0),\n                     * but the code does not produce any output for those pixels (see last lines).\n                     */\n                    if (loadY < B_Y * colorsPerThread) {\n                        #pragma unroll\n                        for (int y = 0; y < B_Y * colorsPerThread; y += (B_X * B_Y) / preloadCases) {\n                            // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                            if ((B_Y*colorsPerThread) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_Y*colorsPerThread) {\n                                shImgLoad[(y) * preloadCases] = images[caseIdx + y * imgPixels * imgStride + pixIdx];\n                            }\n                        }\n                    }\n\n                    if (loadY < B_X * filtersPerThread) {\n                        #pragma unroll\n                        for (int y = 0; y < B_X * filtersPerThread; y += (B_X * B_Y) / preloadCases) {\n                            // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                            if ((B_X * filtersPerThread) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_X * filtersPerThread) {\n                                shHidActLoad[y * (preloadCases + 1)] = hidActs[caseIdx + y * numImages * numModules + m * numImages];\n                            }\n                        }\n                    }\n                } else {\n                    #pragma unroll\n                    for (int y = 0; y < B_Y * colorsPerThread; y += (B_X * B_Y) / preloadCases) {\n                        // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                        if ((B_Y*colorsPerThread) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_Y*colorsPerThread) {\n                            shImgLoad[(y) * preloadCases] = 0;\n                        }\n                    }\n                    #pragma unroll\n                    for (int y = 0; y < B_X * filtersPerThread; y += (B_X * B_Y) / preloadCases) {\n                        // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                        if ((B_X * filtersPerThread) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_X * filtersPerThread) {\n                            shHidActLoad[y * (preloadCases + 1)] = 0;\n                        }\n                    }\n                }\n\n                __syncthreads();\n                #pragma unroll\n                for (int i = 0; i < preloadCases; i++) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        #pragma unroll\n                        for (int c = 0; c < colorsPerThread; c++) {\n                            prod[c][f] += shImages[threadIdx.y + c * B_Y][i] * shHidActs[threadIdx.x + f * B_X][i];\n                        }\n                    }\n                }\n                __syncthreads();\n            }\n        }\n    }\n    if (scale) {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                targets[c * B_Y * filterPixels * numFilters + f * B_X] = scaleTargets * targets[c * B_Y * filterPixels * numFilters + f * B_X] + scaleOutputs * prod[c][f];\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                targets[c * B_Y * filterPixels * numFilters + f * B_X] = scaleOutputs * prod[c][f];\n            }\n        }\n    }\n}\n\n\n/*\n * Each block computes weight gradients for 1 pixel, B_Y * colorsPerThread colors and B_X * filtersPerThread filters\n * threadIdx.x determines filter\n * threadIdx.y determines color\n *\n * blockIdx.x determines filter batch of B_X * filtersPerThread, module batch of partialSum\n * blockIdx.y determines color batch of B_Y * colorsPerThread\n * blockIdx.z determines pixel in filter\n *            NOTE: blockIdx.z is limited to values < 2^16. This means that this routine will\n *                  fail for filters >= 256*256. I'm assuming I won't ever use such large filters.\n\n * images:      (numImgColors, imgSizeY, imgSizeX, numImages), with stride given\n * hidActs:     (numFilters, numModulesY, numModulesX, numImages)\n *\n * targets:     (numModulesY*numModulesX/partialSum, numFilterColors, filterPixels, numFilters)\n\n * B_X * B_Y must be divisible by preloadCases\n */\ntemplate <int B_Y, int B_X, int filtersPerThread, int colorsPerThread, int preloadCases, bool scale>\n__global__ void conv_weight_acts_mc_mf_kepler_sw(float* images, float* hidActs, float* targets,\n                                       const int numImages, const int numFilters,\n                                       const int numModulesY, const int numModulesX,\n                                       const int imgSizeY, const int imgSizeX, const int filterSize,\n                                       const int paddingStart, const int moduleStride, const int imgStride,\n                                       const int numImgColors, const int numGroups, const int sumWidth,\n                                       const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shImages[colorsPerThread * B_Y][preloadCases]; // preload preloadCases cases\n    __shared__ float shHidActs[filtersPerThread * B_X][preloadCases + 1]; // preload preloadCases cases of B_X hidacts\n\n    const int tidx = B_X * threadIdx.y + threadIdx.x;\n    const int loadY = tidx / preloadCases, loadX = tidx % preloadCases;\n\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n\n    const int numFilterBlocks = numFilters / (B_X * filtersPerThread);\n    const int blockModuleChunkIdx = blockIdx.x / numFilterBlocks;\n\n    const int numModuleChunksX = DIVUP(numModulesX, sumWidth);\n//    const int numModuleChunksY = DIVUP(numModulesY, sumWidth);\n\n    const int blockModuleChunkX = blockModuleChunkIdx % numModuleChunksX;\n    const int blockModuleChunkY = blockModuleChunkIdx / numModuleChunksX;\n\n    const int blockModuleStartX = blockModuleChunkX * sumWidth;\n    const int blockModuleStartY = blockModuleChunkY * sumWidth;\n\n    const int blockFilterIdx = filtersPerThread * B_X * (blockIdx.x % numFilterBlocks);\n    const int numModules = numModulesY * numModulesX;\n\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockGroupIdx = blockFilterIdx / numFiltersPerGroup;\n    const int numFilterColors = numImgColors / numGroups;\n\n    const int blockPixelOffset = blockIdx.z; // pixel idx in filter\n    const int blockPixelY = blockPixelOffset / filterSize, blockPixelX = blockPixelOffset % filterSize;\n    const int blockFilterColorIdx = blockIdx.y  * B_Y * colorsPerThread;\n    const int imgColorIdx = blockFilterColorIdx + blockGroupIdx * numFilterColors;\n\n    images += (imgColorIdx + loadY) * imgPixels * imgStride + loadX;\n\n    hidActs +=\n             blockFilterIdx * numImages * numModules\n            + loadY * numImages * numModules\n            + loadX;\n\n    targets += blockModuleChunkIdx * numFilters * filterPixels * numFilterColors\n            + (blockFilterColorIdx + threadIdx.y) * filterPixels * numFilters\n            + blockPixelOffset * numFilters\n            + blockFilterIdx\n            + threadIdx.x;\n    //if (blockIdx.x != 0 || blockIdx.y != 0 || blockIdx.z != 0) return;\n\n    const int mStartX = max(blockModuleStartX, DIVUP(-blockPixelX - paddingStart, moduleStride));\n    const int mStartY = max(blockModuleStartY, DIVUP(-blockPixelY - paddingStart, moduleStride));\n    const int mEndX = min(numModulesX, min(blockModuleStartX + sumWidth, DIVUP(imgSizeX - blockPixelX - paddingStart, moduleStride)));\n    const int mEndY = min(numModulesY, min(blockModuleStartY + sumWidth, DIVUP(imgSizeY - blockPixelY - paddingStart, moduleStride)));\n\n//    if (mStartY == mEndY || mStartX == mEndX) {\n//        return;\n//    }\n\n    float* shHidActLoad = &shHidActs[loadY][loadX];\n    float* shImgLoad = &shImages[loadY][loadX];\n    float prod[colorsPerThread][filtersPerThread];\n    #pragma unroll\n    for (int c = 0; c < colorsPerThread; c++) {\n        #pragma unroll\n        for (int f = 0; f < filtersPerThread; f++) {\n            prod[c][f] = 0;\n        }\n    }\n\n    /*\n     * Note; iterating this way is about 1% slower and uses a few more registers than iterating\n     * over the modules linearly. But it's consistent with the preload routines,\n     * so I'm using it.\n     */\n    for (int my = mStartY; my < mEndY; my++) {\n        const int imgLoadModPosY = paddingStart + my * moduleStride;\n        const int pxY = imgLoadModPosY + blockPixelY; // pixel x,y coords in image\n        for (int mx = mStartX; mx < mEndX; mx++) {\n            const int m = my * numModulesX + mx;\n            const int imgLoadModPosX = paddingStart + mx * moduleStride;\n            const int pxX = imgLoadModPosX + blockPixelX;\n            const int pixIdx = (pxY * imgSizeX + pxX) * imgStride; // pixel idx in image\n            for (int caseIdx = 0; caseIdx < numImages; caseIdx += preloadCases) {\n                // Checking this condition actually makes things faster ... :/\n                // So I've removed the !checkCaseBounds flag and just check it all the time.\n                if (caseIdx + loadX < numImages) {\n                    /*\n                     * As long as B_Y * B_X is divisible by preloadCases this will loop the right\n                     * number of times.\n                     *\n                     * This will load some images from filter pixels that don't exist (it'll set those to 0),\n                     * but the code does not produce any output for those pixels (see last lines).\n                     */\n                    if (loadY < B_Y * colorsPerThread) {\n                        #pragma unroll\n                        for (int y = 0; y < B_Y * colorsPerThread; y += (B_X * B_Y) / preloadCases) {\n                            // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                            if ((B_Y*colorsPerThread) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_Y*colorsPerThread) {\n                                shImgLoad[(y) * preloadCases] = images[caseIdx + y * imgPixels * imgStride + pixIdx];\n                            }\n                        }\n                    }\n\n                    if (loadY < B_X * filtersPerThread) {\n                        #pragma unroll\n                        for (int y = 0; y < B_X * filtersPerThread; y += (B_X * B_Y) / preloadCases) {\n                            // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                            if ((B_X * filtersPerThread) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_X * filtersPerThread) {\n                                shHidActLoad[y * (preloadCases + 1)] = hidActs[caseIdx + y * numImages * numModules + m * numImages];\n                            }\n                        }\n                    }\n                } else {\n                    #pragma unroll\n                    for (int y = 0; y < B_Y * colorsPerThread; y += (B_X * B_Y) / preloadCases) {\n                        // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                        if ((B_Y*colorsPerThread) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_Y*colorsPerThread) {\n                            shImgLoad[(y) * preloadCases] = 0;\n                        }\n                    }\n                    #pragma unroll\n                    for (int y = 0; y < B_X * filtersPerThread; y += (B_X * B_Y) / preloadCases) {\n                        // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                        if ((B_X * filtersPerThread) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_X * filtersPerThread) {\n                            shHidActLoad[y * (preloadCases + 1)] = 0;\n                        }\n                    }\n                }\n\n                __syncthreads();\n                #pragma unroll\n                for (int i = 0; i < preloadCases; i++) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        #pragma unroll\n                        for (int c = 0; c < colorsPerThread; c++) {\n                            prod[c][f] += shImages[threadIdx.y + c * B_Y][i] * shHidActs[threadIdx.x + f * B_X][i];\n                        }\n                    }\n                }\n                __syncthreads();\n            }\n\n        }\n    }\n    if (scale) {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                targets[c * B_Y * filterPixels * numFilters + f * B_X] = scaleTargets * targets[c * B_Y * filterPixels * numFilters + f * B_X] + scaleOutputs * prod[c][f];\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                targets[c * B_Y * filterPixels * numFilters + f * B_X] = scaleOutputs * prod[c][f];\n            }\n        }\n    }\n}\n\n\n/*\n * Each block computes weight gradients for B_Y * pixelsPerThread pixels and B_X filters\n * threadIdx.x determines filter\n * threadIdx.y determines pixel in filter\n *\n * blockIdx.x determines filter batch of B_X * filtersPerThread, module batch of partialSum\n * blockIdx.y determines pixel batch of B_Y * pixelsPerThread\n *\n * Number of filters must be divisible by B_X * filtersPerThread\n * Number of images (cases) should be divisible by preloadCases if checkCaseBounds is false.\n *\n * images:      (numColors, imgSizeY, imgSizeX, numImages), with stride given\n * hidActs:     (numFilters, numModulesY, numModulesX, numImages)\n *\n * targets:     (numModulesY*numModulesX/partialSum, numColors, filterPixels, numFilters)\n *\n * B_Y * B_X should be divisible by preloadCases.\n * preloadCases one of 16, 32.\n * B_X one of 4, 8, 16, 32\n * B_Y arbitrary (satisfying divisibility constraints)\n * numModules must be divisible by partialSum\n * pixelsPerThread must be divisible by pixelCache\n *\n * After adding pixelsPerThread, register usage went from 20 to 23 (when pixelsPerThread = 1)...\n * so the compiler is messing up here somehow. It's unable to optimize that case away.\n */\ntemplate <int B_Y, int B_X, int pixelCache, int pixelsPerThread, int filtersPerThread, int preloadCases, int numColors, bool scale, bool checkCaseBounds>\n__global__ void conv_weight_acts_c_kepler_sw(float* images, float* hidActs, float* targets,\n                                   const int numImages, const int numFilters,\n                                   const int numModulesY, const int numModulesX,\n                                   const int imgSizeY, const int imgSizeX, const int filterSize,\n                                   const int paddingStart, const int moduleStride, const int imgStride,\n                                   const int sumWidth,\n                                   const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shImages[pixelCache * B_Y * numColors][preloadCases]; // preload preloadCases cases of B_Y * pixelsPerThread pixels\n    __shared__ float shHidActs[B_X * filtersPerThread][preloadCases + 1]; // preload preloadCases cases of B_X hidActs\n\n    const int tidx = B_X * threadIdx.y + threadIdx.x;\n    const int loadY = tidx / preloadCases, loadX = tidx % preloadCases;\n\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n\n    const int numFilterBlocks = numFilters / (B_X*filtersPerThread);\n\n    const int blockModuleChunkIdx = blockIdx.x / numFilterBlocks;\n\n    const int numModuleChunksX = DIVUP(numModulesX, sumWidth);\n//    const int numModuleChunksY = DIVUP(numModulesY, sumWidth);\n\n    const int blockModuleChunkX = blockModuleChunkIdx % numModuleChunksX;\n    const int blockModuleChunkY = blockModuleChunkIdx / numModuleChunksX;\n\n    const int blockModuleStartX = blockModuleChunkX * sumWidth;\n    const int blockModuleStartY = blockModuleChunkY * sumWidth;\n\n    const int blockFilterIdx = B_X * filtersPerThread* (blockIdx.x % numFilterBlocks);\n\n//    const int moduleStride = (imgSize - filterSize + 1) / numModulesX;\n    const int numModules = numModulesY * numModulesX;\n\n    const int blockPixelOffset = blockIdx.y * B_Y * pixelsPerThread;\n\n    images += loadX;\n    hidActs += blockFilterIdx * numImages * numModules\n//            + loadY * numImages * numModules\n            + loadX;\n\n    targets += (blockModuleChunkIdx * numFilters) * filterPixels * numColors\n            + blockPixelOffset * numFilters\n            + blockFilterIdx\n            + threadIdx.y * numFilters + threadIdx.x;\n\n    //float* shImgLoad = &shImages[loadY][loadX];\n    //float* shHidActLoad = &shHidActs[loadY][loadX];\n\n    float prod[numColors][pixelsPerThread][filtersPerThread];\n    #pragma unroll\n    for (int c = 0; c < numColors; c++) {\n        #pragma unroll\n        for (int p = 0; p < pixelsPerThread; p++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                prod[c][p][f] = 0;\n            }\n        }\n    }\n    const int mStartX = blockModuleStartX;\n    const int mStartY = blockModuleStartY;\n    const int mEndX = min(numModulesX, blockModuleStartX + sumWidth);\n    const int mEndY = min(numModulesY, blockModuleStartY + sumWidth);\n\n//    if (mStartY == mEndY || mStartX == mEndX) {\n//        return;\n//    }\n\n    const int fYOff = (blockPixelOffset + tidx) / filterSize;\n    const int fXOff = (blockPixelOffset + tidx) % filterSize;\n    __shared__ int pxIdxes[B_Y*pixelsPerThread];\n    for (int my = mStartY; my < mEndY; my++) {\n        const int imgLoadModPosY = paddingStart + my * moduleStride;\n        for (int mx = mStartX; mx < mEndX; mx++) {\n            const int m = my * numModulesX + mx;\n\n            __syncthreads();\n            const int imgLoadModPosX = paddingStart + mx * moduleStride;\n            if (tidx < B_Y * pixelsPerThread) {\n//                const int imgLoadModPosY = paddingStart + my * moduleStride;\n//                const int imgLoadModPosX = paddingStart + mx * moduleStride;\n                int pxY = (imgLoadModPosY + fYOff);\n                int pxX = (imgLoadModPosX + fXOff);\n                int pixIdx = (pxY * imgSizeX + pxX) * imgStride;\n                pxIdxes[tidx] = pxY >= 0 && pxY < imgSizeY && pxX >= 0 && pxX < imgSizeX ? pixIdx : -1;\n            }\n            __syncthreads();\n            for (int caseIdx = 0; caseIdx < numImages; caseIdx += preloadCases) {\n                if (/*loadY < B_X*filtersPerThread &&*/ (!checkCaseBounds || caseIdx + loadX < numImages)) {\n                    #pragma unroll\n                    for (int y = 0; y < B_X*filtersPerThread; y += (B_X * B_Y) / preloadCases) {\n                        const int fIdx = ((loadY + y) % filtersPerThread) * B_X + (loadY + y) / filtersPerThread;\n                        // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                        if ((B_X*filtersPerThread) % (B_X * B_Y / preloadCases) == 0 || loadY+y < B_X*filtersPerThread) {\n                            shHidActs[loadY+y][loadX]= hidActs[caseIdx + fIdx * numImages * numModules + m * numImages];\n                        }\n                    }\n                } else {\n                    #pragma unroll\n                    for (int y = 0; y < B_X*filtersPerThread; y += (B_X * B_Y) / preloadCases) {\n                    //                        const int fIdx = ((loadY + y) % filtersPerThread) * B_X + (loadY + y) / filtersPerThread;\n                        // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                        if ((B_X*filtersPerThread) % (B_X * B_Y / preloadCases) == 0 || loadY+y < B_X*filtersPerThread) {\n                            shHidActs[loadY+y][loadX] = 0;\n                        }\n                    }\n                }\n                #pragma unroll\n                for (int pp = 0; pp < pixelsPerThread; pp += pixelCache) {\n                    //if (loadY < B_Y * pixelCache) { // This condition is not necessary for correctness, but it speeds things a bit\n                    /*\n                     * As long as B_Y * B_X is divisible by preloadCases this will loop the right\n                     * number of times.\n                     *\n                     * This will load some imgGrads from filter pixels that don't exit (it'll set those to 0),\n                     * but the code does not produce any output for those pixels (see last lines).\n                     */\n                    #pragma unroll\n                    for (int y = 0; y < B_Y * pixelCache; y += (B_X * B_Y) / preloadCases) {\n                        // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                        if ((B_Y * pixelCache) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_Y * pixelCache) {\n                            const int pxIdx = pp * B_Y + loadY + y; // pixel idx in filter\n\n                            if (pxIdx + blockPixelOffset < filterPixels && (!checkCaseBounds || caseIdx + loadX < numImages)) {\n                                const int pixIdx = pxIdxes[pxIdx];//(pxY * imgSizeX + pxX) * imgStride;\n\n                                if (pixIdx >= 0) {\n                                    #pragma unroll\n                                    for (int c = 0; c < numColors; c++) {\n                                        shImages[loadY+y + c * pixelCache * B_Y][loadX] = images[caseIdx + c * imgPixels * imgStride + pixIdx];\n                                    }\n                                } else {\n                                    #pragma unroll\n                                    for (int c = 0; c < numColors; c++) {\n                                        shImages[loadY+y + c * pixelCache * B_Y][loadX] = 0;\n                                    }\n                                }\n                            } else {\n                                #pragma unroll\n                                for (int c = 0; c < numColors; c++) {\n                                    shImages[loadY+y + c * pixelCache * B_Y][loadX]= 0;\n                                }\n                            }\n                        }\n                    }\n                    //}\n\n                    __syncthreads();\n\n                    #pragma unroll\n                    for (int c = 0; c < numColors; c++) {\n                        #pragma unroll\n                        for (int i = 0; i < preloadCases; i++) {\n                            #pragma unroll\n                            for (int p = 0; p < pixelCache; p++) {\n                                #pragma unroll\n                                for (int f = 0; f < filtersPerThread; f++) {\n                                    prod[c][pp + p][f] += shImages[threadIdx.y + p * B_Y + c * pixelCache * B_Y][i] * shHidActs[threadIdx.x * filtersPerThread + f][i];\n                                }\n                            }\n                        }\n                    }\n\n                    __syncthreads();\n                }\n            }\n        }\n    }\n\n    if (scale) {\n        #pragma unroll\n        for (int p = 0; p < pixelsPerThread; p++) {\n            if (blockPixelOffset + p * B_Y + threadIdx.y < filterPixels) {\n                #pragma unroll\n                for (int c = 0; c < numColors; c++) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        targets[p * B_Y * numFilters + c * filterPixels * numFilters + f * B_X] = scaleTargets * targets[p * B_Y * numFilters + c * filterPixels * numFilters + f * B_X] + scaleOutputs * prod[c][p][f];\n                    }\n                }\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int p = 0; p < pixelsPerThread; p++) {\n            if (blockPixelOffset + p * B_Y + threadIdx.y < filterPixels) {\n                #pragma unroll\n                for (int c = 0; c < numColors; c++) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        targets[p * B_Y * numFilters + c * filterPixels * numFilters + f * B_X] = scaleOutputs * prod[c][p][f];\n                    }\n                }\n            }\n        }\n    }\n}\n\n\n#define WA_C3_LOOP(pp, c) _Pragma(\"unroll\") \\\nfor (int i = 0; i < preloadCases; i++) { \\\n    _Pragma(\"unroll\") \\\n    for (int p = 0; p < pixelCache; p++) { \\\n        _Pragma(\"unroll\") \\\n        for (int f = 0; f < filtersPerThread; f++) { \\\n            prod[c][(pp) + p][f] += shImages[threadIdx.y + p * B_Y + (c) * pixelCache * B_Y][i] * shHidActs[threadIdx.x * filtersPerThread + f][i]; \\\n        } \\\n    } \\\n}\n\n#define WA_C3_LOOP2(pp) _Pragma(\"unroll\") \\\nfor (int p = 0; p < pixelCache; p++) { \\\n    _Pragma(\"unroll\") \\\n    for (int i = 0; i < preloadCases; i++) { \\\n        _Pragma(\"unroll\") \\\n        for (int f = 0; f < filtersPerThread; f++) { \\\n            _Pragma(\"unroll\") \\\n            for (int c = 0; c < 3; ++c) { \\\n                prod[c][(pp) + p][f] += shImages[threadIdx.y + p * B_Y + (c) * pixelCache * B_Y][i] * shHidActs[threadIdx.x * filtersPerThread + f][i]; \\\n            } \\\n        } \\\n    } \\\n}\n\n#define WA_3_FIDX(y) (((loadY + (y)*B_X*B_Y/preloadCases) % filtersPerThread) * B_X + (loadY + (y)*B_X*B_Y/preloadCases) / filtersPerThread)\n\n\n/*\n * Each block computes weight gradients for B_Y * pixelsPerThread pixels and B_X filters\n * threadIdx.x determines filter\n * threadIdx.y determines pixel in filter\n *\n * blockIdx.x determines filter batch of B_X * filtersPerThread, module batch of partialSum\n * blockIdx.y determines pixel batch of B_Y * pixelsPerThread\n *\n * Number of filters must be divisible by B_X * filtersPerThread\n * Number of images (cases) should be divisible by preloadCases if checkCaseBounds is false.\n *\n * images:      (numColors, imgSizeY, imgSizeX, numImages), with stride given\n * hidActs:     (numFilters, numModulesY, numModulesX, numImages)\n *\n * targets:     (numModulesY*numModulesX/partialSum, numColors, filterPixels, numFilters)\n *\n * B_Y * B_X should be divisible by preloadCases.\n * preloadCases one of 16, 32.\n * B_X one of 4, 8, 16, 32\n * B_Y arbitrary (satisfying divisibility constraints)\n * numModules must be divisible by partialSum\n * pixelsPerThread must be divisible by pixelCache\n *\n * After adding pixelsPerThread, register usage went from 20 to 23 (when pixelsPerThread = 1)...\n * so the compiler is messing up here somehow. It's unable to optimize that case away.\n */\ntemplate <int B_Y, int B_X, int pixelCache, int pixelsPerThread, int filtersPerThread, int preloadCases, int numColors, bool scale, bool checkCaseBounds>\n//__launch_bounds__(256,2)\n__global__ void conv_weight_acts_c_preload_pc_2_pt_2_f_4_r_32_c_3(cudaTextureObject_t images, cudaTextureObject_t hidActs, float* targets,\n                                   const int numImages, const int numFilters,\n                                   const int numModulesY, const int numModulesX,\n                                   const int imgSizeY, const int imgSizeX, const int filterSize,\n                                   const int paddingStart, const int moduleStride, const int imgStride,\n                                   const int sumWidth,\n                                   const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shImages[pixelCache * B_Y * numColors][preloadCases]; // preload preloadCases cases of B_Y * pixelsPerThread pixels\n    __shared__ float shHidActs[B_X * filtersPerThread][preloadCases + 1]; // preload preloadCases cases of B_X hidActs\n\n    const int tidx = B_X * threadIdx.y + threadIdx.x;\n    const int loadY = tidx / preloadCases, loadX = tidx % preloadCases;\n\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n\n    const int numFilterBlocks = numFilters / (B_X*filtersPerThread);\n\n    const int blockModuleChunkIdx = blockIdx.x / numFilterBlocks;\n\n    const int numModuleChunksX = DIVUP(numModulesX, sumWidth);\n//    const int numModuleChunksY = DIVUP(numModulesY, sumWidth);\n\n    const int blockModuleChunkX = blockModuleChunkIdx % numModuleChunksX;\n    const int blockModuleChunkY = blockModuleChunkIdx / numModuleChunksX;\n\n    const int blockModuleStartX = blockModuleChunkX * sumWidth;\n    const int blockModuleStartY = blockModuleChunkY * sumWidth;\n\n    const int blockFilterIdx = B_X * filtersPerThread* (blockIdx.x % numFilterBlocks);\n\n//    const int moduleStride = (imgSize - filterSize + 1) / numModulesX;\n    const int numModules = numModulesY * numModulesX;\n\n    const int blockPixelOffset = blockIdx.y * B_Y * pixelsPerThread;\n    const int imgOffset = loadX;\n    const int hidActsOffset = blockFilterIdx * numImages * numModules + loadX;\n//    images += loadX;\n//    hidActs += blockFilterIdx * numImages * numModules\n//            + loadX;\n\n    targets += (blockModuleChunkIdx * numFilters) * filterPixels * numColors\n            + blockPixelOffset * numFilters\n            + blockFilterIdx\n            + threadIdx.y * numFilters + threadIdx.x;\n\n    //float* shImgLoad = &shImages[loadY][loadX];\n    //float* shHidActLoad = &shHidActs[loadY][loadX];\n\n    float prod[numColors][pixelsPerThread][filtersPerThread];\n    #pragma unroll\n    for (int c = 0; c < numColors; c++) {\n        #pragma unroll\n        for (int p = 0; p < pixelsPerThread; p++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                prod[c][p][f] = 0;\n            }\n        }\n    }\n    const int mStartX = blockModuleStartX;\n    const int mStartY = blockModuleStartY;\n    const int mEndX = min(numModulesX, blockModuleStartX + sumWidth);\n    const int mEndY = min(numModulesY, blockModuleStartY + sumWidth);\n\n    const bool doWork = mStartY < mEndY && mStartX < mEndX;\n//    if (!doWork) {\n//        hidActs -=\n//    }\n//    if (mStartY == mEndY || mStartX == mEndX) {\n//        return;\n//    }\n\n//    float imPreload[pixelCache * numColors * preloadCases / B_X]; // [12]\n    float haPreload[filtersPerThread * preloadCases / B_Y]; // [8]\n//    if (blockIdx.x != 0 || blockIdx.y !=0) {\n//        return;\n//    }\n//    printf(\"mStartX: %d, mStartX: %d, mStartX: %d, mStartX: %d\\n\", mStartX, mStartY, mEndX, mEndY);\n    const int fYOff = (blockPixelOffset + tidx) / filterSize;\n    const int fXOff = (blockPixelOffset + tidx) % filterSize;\n    __shared__ int pxIdxes[B_Y*pixelsPerThread];\n//    __shared__ int fidx[filtersPerThread * preloadCases / B_Y]; // [8]\n\n    int m = mStartY * numModulesX + mStartX;\n\n    int fidx[filtersPerThread * preloadCases / B_Y];\n    if (doWork) {\n        #pragma unroll\n        for (int y = 0; y < filtersPerThread * preloadCases / B_Y; ++y) {\n            const int fIdx = WA_3_FIDX(y);\n//            if (doWork) {\n            haPreload[y] =  tex1Dfetch<float>(hidActs, hidActsOffset + fIdx * numImages * numModules + m * numImages);\n//            }\n            fidx[y] = fIdx * numImages * numModules;\n        }\n    }\n\n    for (int my = mStartY; my < mEndY; my++) {\n        const int imgLoadModPosY = paddingStart + my * moduleStride;\n        for (int mx = mStartX; mx < mEndX; mx++) {\n            m = my * numModulesX + mx;\n\n//            __syncthreads();\n            const int imgLoadModPosX = paddingStart + mx * moduleStride;\n            if (tidx < B_Y * pixelsPerThread) {\n//                const int imgLoadModPosY = paddingStart + my * moduleStride;\n//                const int imgLoadModPosX = paddingStart + mx * moduleStride;\n                const int pxY = (imgLoadModPosY + fYOff);\n                const int pxX = (imgLoadModPosX + fXOff);\n                const int pixIdx = (pxY * imgSizeX + pxX) * imgStride;\n                pxIdxes[tidx] = pxY >= 0 && pxY < imgSizeY && pxX >= 0 && pxX < imgSizeX ? pixIdx : -1;\n            }\n            __syncthreads();\n\n            int myNext = my, mxNext = mx, mNext = m;\n            const bool lastModule = my == mEndY - 1 && mx == mEndX - 1;\n\n            if (!lastModule) {\n                mxNext = mx + 1 == mEndX ? mStartX : mx + 1;\n                myNext = my + (mx + 1 == mEndX);\n                mNext = myNext * numModulesX + mxNext;\n            }\n\n            for (int caseIdx = 0; caseIdx < numImages; caseIdx += preloadCases) {\n                const bool lastBatch = caseIdx + preloadCases == numImages;\n//                const float* im = &images[caseIdx + preloadCases + pixIdx];\n//                const float* ha = &hidActs[caseIdx + preloadCases + m * numImages];\n                int hidActsOffset2 = hidActsOffset + caseIdx + preloadCases + m * numImages;\n\n                if (lastBatch) {\n//                    ha = &hidActs[mNext * numImages];\n                    hidActsOffset2 = hidActsOffset + mNext * numImages;\n                }\n\n                #pragma unroll\n                for (int y = 0; y < B_X*filtersPerThread; y += (B_X * B_Y) / preloadCases) {\n                    shHidActs[loadY+y][loadX] = haPreload[y*preloadCases/(B_X*B_Y)];\n                }\n\n                /* ==================================================================================\n                 * Iteration 0\n                 * ==================================================================================\n                 */\n                #pragma unroll\n                for (int y = 0; y < B_Y * pixelCache; y += (B_X * B_Y) / preloadCases) {\n                    #pragma unroll\n                    for (int c = 0; c < numColors; c++) {\n                        shImages[loadY+y + c * pixelCache * B_Y][loadX] = 0;\n                    }\n                }\n                #pragma unroll\n                for (int y = 0; y < B_Y * pixelCache; y += (B_X * B_Y) / preloadCases) {\n                    const int pxIdx = 0 * B_Y + loadY + y; // pixel idx in filter\n                    if (pxIdx + blockPixelOffset < filterPixels) {\n                        const int pixIdx = pxIdxes[pxIdx];//(pxY * imgSizeX + pxX) * imgStride;\n                        if (pixIdx >= 0) {\n                            #pragma unroll\n                            for (int c = 0; c < numColors; c++) {\n                                shImages[loadY+y + c * pixelCache * B_Y][loadX] = tex1Dfetch<float>(images, imgOffset + caseIdx + c * imgPixels * imgStride + pixIdx);\n                            }\n                        }\n                    }\n                }\n\n                __syncthreads();\n\n                haPreload[0] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[0]);\n                haPreload[1] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[1]);\n                WA_C3_LOOP(0,0);\n                haPreload[2] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[2]);\n                haPreload[3] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[3]);\n                WA_C3_LOOP(0,1);\n                haPreload[4] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[4]);\n                haPreload[5] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[5]);\n                WA_C3_LOOP(0,2);\n                haPreload[6] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[6]);\n                haPreload[7] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[7]);\n\n                __syncthreads();\n            }\n        }\n    }\n\n    if (scale) {\n        #pragma unroll\n        for (int p = 0; p < pixelsPerThread; p++) {\n            if (blockPixelOffset + p * B_Y + threadIdx.y < filterPixels) {\n                #pragma unroll\n                for (int c = 0; c < numColors; c++) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        targets[p * B_Y * numFilters + c * filterPixels * numFilters + f * B_X] = scaleTargets * targets[p * B_Y * numFilters + c * filterPixels * numFilters + f * B_X] + scaleOutputs * prod[c][p][f];\n                    }\n                }\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int p = 0; p < pixelsPerThread; p++) {\n            if (blockPixelOffset + p * B_Y + threadIdx.y < filterPixels) {\n                #pragma unroll\n                for (int c = 0; c < numColors; c++) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n//                        if (threadIdx.x == 3)\n                        targets[p * B_Y * numFilters + c * filterPixels * numFilters + f * B_X] = scaleOutputs * prod[c][p][f];\n                    }\n                }\n            }\n        }\n    }\n}\n\n\n/*\n * Each block computes weight gradients for B_Y * pixelsPerThread pixels and B_X filters\n * threadIdx.x determines filter\n * threadIdx.y determines pixel in filter\n *\n * blockIdx.x determines filter batch of B_X * filtersPerThread, module batch of partialSum\n * blockIdx.y determines pixel batch of B_Y * pixelsPerThread\n *\n * Number of filters must be divisible by B_X * filtersPerThread\n * Number of images (cases) should be divisible by preloadCases if checkCaseBounds is false.\n *\n * images:      (numColors, imgSizeY, imgSizeX, numImages), with stride given\n * hidActs:     (numFilters, numModulesY, numModulesX, numImages)\n *\n * targets:     (numModulesY*numModulesX/partialSum, numColors, filterPixels, numFilters)\n *\n * B_Y * B_X should be divisible by preloadCases.\n * preloadCases one of 16, 32.\n * B_X one of 4, 8, 16, 32\n * B_Y arbitrary (satisfying divisibility constraints)\n * numModules must be divisible by partialSum\n * pixelsPerThread must be divisible by pixelCache\n *\n * After adding pixelsPerThread, register usage went from 20 to 23 (when pixelsPerThread = 1)...\n * so the compiler is messing up here somehow. It's unable to optimize that case away.\n */\ntemplate <int B_Y, int B_X, int pixelCache, int pixelsPerThread, int filtersPerThread, int preloadCases, int numColors, bool scale, bool checkCaseBounds>\n__launch_bounds__(256,2)\n__global__ void conv_weight_acts_c_preload_pc_2_pt_4_f_3_r_32_c_3(cudaTextureObject_t images, cudaTextureObject_t hidActs, float* targets,\n                                   const int numImages, const int numFilters,\n                                   const int numModulesY, const int numModulesX,\n                                   const int imgSizeY, const int imgSizeX, const int filterSize,\n                                   const int paddingStart, const int moduleStride, const int imgStride,\n                                   const int sumWidth,\n                                   const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shImages[pixelCache * B_Y * numColors][preloadCases]; // preload preloadCases cases of B_Y * pixelsPerThread pixels\n    __shared__ float shHidActs[B_X * filtersPerThread][preloadCases + 1]; // preload preloadCases cases of B_X hidActs\n\n    const int tidx = B_X * threadIdx.y + threadIdx.x;\n    const int loadY = tidx / preloadCases, loadX = tidx % preloadCases;\n\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n\n    const int numFilterBlocks = numFilters / (B_X*filtersPerThread);\n\n    const int blockModuleChunkIdx = blockIdx.x / numFilterBlocks;\n\n    const int numModuleChunksX = DIVUP(numModulesX, sumWidth);\n//    const int numModuleChunksY = DIVUP(numModulesY, sumWidth);\n\n    const int blockModuleChunkX = blockModuleChunkIdx % numModuleChunksX;\n    const int blockModuleChunkY = blockModuleChunkIdx / numModuleChunksX;\n\n    const int blockModuleStartX = blockModuleChunkX * sumWidth;\n    const int blockModuleStartY = blockModuleChunkY * sumWidth;\n\n    const int blockFilterIdx = B_X * filtersPerThread* (blockIdx.x % numFilterBlocks);\n\n//    const int moduleStride = (imgSize - filterSize + 1) / numModulesX;\n    const int numModules = numModulesY * numModulesX;\n\n    const int blockPixelOffset = blockIdx.y * B_Y * pixelsPerThread;\n    const int imgOffset = loadX;\n    const int hidActsOffset = blockFilterIdx * numImages * numModules\n                        + loadX;\n//    images += loadX;\n//    hidActs += blockFilterIdx * numImages * numModules\n//            + loadX;\n\n    targets += (blockModuleChunkIdx * numFilters) * filterPixels * numColors\n            + blockPixelOffset * numFilters\n            + blockFilterIdx\n            + threadIdx.y * numFilters + threadIdx.x;\n\n    //float* shImgLoad = &shImages[loadY][loadX];\n    //float* shHidActLoad = &shHidActs[loadY][loadX];\n\n    float prod[numColors][pixelsPerThread][filtersPerThread];\n    #pragma unroll\n    for (int c = 0; c < numColors; c++) {\n        #pragma unroll\n        for (int p = 0; p < pixelsPerThread; p++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                prod[c][p][f] = 0;\n            }\n        }\n    }\n    const int mStartX = blockModuleStartX;\n    const int mStartY = blockModuleStartY;\n    const int mEndX = min(numModulesX, blockModuleStartX + sumWidth);\n    const int mEndY = min(numModulesY, blockModuleStartY + sumWidth);\n\n    const bool doWork = mStartY < mEndY && mStartX < mEndX;\n//    if (mStartY == mEndY || mStartX == mEndX) {\n//        return;\n//    }\n\n//    float imPreload[pixelCache * numColors * preloadCases / B_X]; // [12]\n    float haPreload[filtersPerThread * preloadCases / B_Y]; // [6]\n//    if (blockIdx.x != 0 || blockIdx.y !=0) {\n//        return;\n//    }\n//    printf(\"mStartX: %d, mStartX: %d, mStartX: %d, mStartX: %d\\n\", mStartX, mStartY, mEndX, mEndY);\n    const int fYOff = (blockPixelOffset + tidx) / filterSize;\n    const int fXOff = (blockPixelOffset + tidx) % filterSize;\n    __shared__ int pxIdxes[B_Y*pixelsPerThread];\n//    __shared__ int fidx[filtersPerThread * preloadCases / B_Y]; // [6]\n\n    int m = mStartY * numModulesX + mStartX;\n    int fidx[filtersPerThread * preloadCases / B_Y];\n//    if (doWork) {\n    #pragma unroll\n    for (int y = 0; y < filtersPerThread * preloadCases / B_Y; ++y) {\n        fidx[y] = WA_3_FIDX(y) * numImages * numModules;\n        if (doWork) { // Not actually necessary, I think\n            haPreload[y] = tex1Dfetch<float>(hidActs, hidActsOffset + fidx[y] + m * numImages);\n        }\n    }\n//    }\n    int mNext = mStartY * numModulesX + mStartX;\n    for (int my = mStartY; my < mEndY; my++) {\n//        const int imgLoadModPosY = paddingStart + my * moduleStride;\n        for (int mx = mStartX; mx < mEndX; mx++) {\n            m = mNext;//my * numModulesX + mx;\n\n//            __syncthreads();\n//            const int imgLoadModPosX = paddingStart + mx * moduleStride;\n            if (tidx < B_Y * pixelsPerThread) {\n                const int imgLoadModPosY = paddingStart + my * moduleStride;\n                const int imgLoadModPosX = paddingStart + mx * moduleStride;\n                const int pxY = (imgLoadModPosY + fYOff);\n                const int pxX = (imgLoadModPosX + fXOff);\n                const int pixIdx = (pxY * imgSizeX + pxX) * imgStride;\n                pxIdxes[tidx] = pxY >= 0 && pxY < imgSizeY && pxX >= 0 && pxX < imgSizeX ? pixIdx : -1;\n            }\n            __syncthreads();\n\n\n            const bool lastModule = my == mEndY - 1 && mx == mEndX - 1;\n            mNext = lastModule * m + !lastModule * ((my + (mx + 1 == mEndX)) * numModulesX + (mx + 1 == mEndX ? mStartX : mx + 1));\n//            if (!lastModule) {\n//                const int mxNext = mx + 1 == mEndX ? mStartX : mx + 1;\n//                const int myNext = my + (mx + 1 == mEndX);\n//                mNext = myNext * numModulesX + mxNext;\n//            }\n\n            for (int caseIdx = 0; caseIdx < numImages; caseIdx += preloadCases) {\n                const bool lastBatch = caseIdx + preloadCases == numImages;\n//                const float* im = &images[caseIdx + preloadCases + pixIdx];\n//                const float* ha = hidActs + !lastBatch * (caseIdx + preloadCases + m * numImages) + lastBatch * mNext * numImages;\n                const int hidActsOffset2 = hidActsOffset + !lastBatch * (caseIdx + preloadCases + m * numImages) + lastBatch * mNext * numImages;\n//                if (lastBatch) {\n//                    ha = &hidActs[mNext * numImages];\n//                }\n\n                #pragma unroll\n                for (int y = 0; y < B_X*filtersPerThread; y += (B_X * B_Y) / preloadCases) {\n                    shHidActs[loadY+y][loadX] = haPreload[y*preloadCases/(B_X*B_Y)];\n                }\n\n                /* ==================================================================================\n                 * Iteration 0\n                 * ==================================================================================\n                 */\n                #pragma unroll\n                for (int y = 0; y < B_Y * pixelCache; y += (B_X * B_Y) / preloadCases) {\n                    // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                    if ((B_Y * pixelCache) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_Y * pixelCache) {\n                        #pragma unroll\n                        for (int c = 0; c < numColors; c++) {\n                            shImages[loadY+y + c * pixelCache * B_Y][loadX] = 0;\n                        }\n                    }\n                }\n                #pragma unroll\n                for (int y = 0; y < B_Y * pixelCache; y += (B_X * B_Y) / preloadCases) {\n                    // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                    if ((B_Y * pixelCache) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_Y * pixelCache) {\n                        const int pxIdx = 0 * B_Y + loadY + y; // pixel idx in filter\n                        const int pixIdx = pxIdxes[pxIdx];//(pxY * imgSizeX + pxX) * imgStride;\n                        if (pixIdx >= 0 && pxIdx + blockPixelOffset < filterPixels && (!checkCaseBounds || caseIdx + loadX < numImages)) {\n                            #pragma unroll\n                            for (int c = 0; c < numColors; c++) {\n                                shImages[loadY+y + c * pixelCache * B_Y][loadX] = tex1Dfetch<float>(images, imgOffset + caseIdx + c * imgPixels * imgStride + pixIdx);\n                            }\n                        }\n                    }\n                }\n\n                __syncthreads();\n\n                haPreload[0] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[0]);\n                haPreload[1] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[1]);\n                haPreload[2] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[2]);\n                haPreload[3] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[3]);\n                haPreload[4] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[4]);\n                haPreload[5] = tex1Dfetch<float>(hidActs, hidActsOffset2 + fidx[5]);\n\n                WA_C3_LOOP2(0);\n\n                __syncthreads();\n\n                /* ==================================================================================\n                 * Iteration 1\n                 * ==================================================================================\n                 */\n                #pragma unroll\n                for (int y = 0; y < B_Y * pixelCache; y += (B_X * B_Y) / preloadCases) {\n                    // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                    if ((B_Y * pixelCache) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_Y * pixelCache) {\n//                        const int pxIdx = 2 * B_Y + loadY + y; // pixel idx in filter\n                        #pragma unroll\n                        for (int c = 0; c < numColors; c++) {\n                            shImages[loadY+y + c * pixelCache * B_Y][loadX] = 0;\n                        }\n                    }\n                }\n\n                #pragma unroll\n                for (int y = 0; y < B_Y * pixelCache; y += (B_X * B_Y) / preloadCases) {\n                    // Make sure number of rows in the array is divisible by number of rows filled per iteration\n                    if ((B_Y * pixelCache) % (B_X * B_Y / preloadCases) == 0 || y + loadY < B_Y * pixelCache) {\n                        const int pxIdx = 2 * B_Y + loadY + y; // pixel idx in filter\n                        const int pixIdx = pxIdxes[pxIdx];//(pxY * imgSizeX + pxX) * imgStride;\n                        if (pixIdx >= 0 && pxIdx + blockPixelOffset < filterPixels && (!checkCaseBounds || caseIdx + loadX < numImages)) {\n                            #pragma unroll\n                            for (int c = 0; c < numColors; c++) {\n                                shImages[loadY+y + c * pixelCache * B_Y][loadX] = tex1Dfetch<float>(images, imgOffset + caseIdx + c * imgPixels * imgStride + pixIdx);\n                            }\n                        }\n                    }\n                }\n\n                __syncthreads();\n\n                WA_C3_LOOP2(2);\n\n                __syncthreads();\n\n            }\n        }\n    }\n\n    if (scale) {\n        #pragma unroll\n        for (int p = 0; p < pixelsPerThread; p++) {\n            if (blockPixelOffset + p * B_Y + threadIdx.y < filterPixels) {\n                #pragma unroll\n                for (int c = 0; c < numColors; c++) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        targets[p * B_Y * numFilters + c * filterPixels * numFilters + f * B_X] = scaleTargets * targets[p * B_Y * numFilters + c * filterPixels * numFilters + f * B_X] + scaleOutputs * prod[c][p][f];\n                    }\n                }\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int p = 0; p < pixelsPerThread; p++) {\n            if (blockPixelOffset + p * B_Y + threadIdx.y < filterPixels) {\n                #pragma unroll\n                for (int c = 0; c < numColors; c++) {\n                    #pragma unroll\n                    for (int f = 0; f < filtersPerThread; f++) {\n                        targets[p * B_Y * numFilters + c * filterPixels * numFilters + f * B_X] = scaleOutputs * prod[c][p][f];\n                    }\n                }\n            }\n        }\n    }\n}\n\n/*****************************Function Revision Record*****************************\n * Author: Tencent BestImage Team(ankerguo@tencent.com)                           *\n * Date:   2015-05-18                                                             *\n * Reason: Optimizing kernel to get faster speed according to GPU features        *\n * Method:                                                                        *\n *         1. reorganizing data structure to avoid bank conflict;                 *\n *         2. using vectorized data type;                                         *\n *         3. improving instruction-level parallelism;                            *\n *         4. removing redundant 'if' branches;                                   *\n *         5. removing local variables to save registers.                         *\n *********************************************************************************/\n\n/*\n * images:      (numImgColors, imgSizeY, imgSizeX, numImages), with stride given\n * hidActs:     (numFilters, numModulesY, numModulesX, numImages)\n *\n * targets:     (numModulesY*numModulesX/partialSum, numFilterColors, filterPixels, numFilters)\n */\ntemplate <int B_Y, int B_X, int filtersPerThread, int colorsPerThread, int preloadCases, bool scale>\n__launch_bounds__(128, 4)\n__global__ void conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_16_f_4_c_8_r_16(cudaTextureObject_t images, cudaTextureObject_t hidActs, float* targets,\n                                       const int numImages, const int numFilters,\n                                       const int numModulesY, const int numModulesX,\n                                       const int imgSizeY, const int imgSizeX, const int filterSize,\n                                       const int paddingStart, const int moduleStride, const int imgStride,\n                                       const int numImgColors, const int numGroups, const int sumWidth,\n                                       const float scaleTargets, const float scaleOutputs) {\n    // avoid bank conflict by reorganizing the data structure, and improve the band width by using 'float2'  instead of 'float'\n    __shared__ float2 shImages[preloadCases][colorsPerThread * B_Y / 2 + 2]; // preload preloadCases cases\n    __shared__ float2 shHidActs[preloadCases][filtersPerThread * B_X / 2 + 2]; // preload preloadCases cases of B_X hidacts\n\n    const int tx = threadIdx.x % B_X, ty = threadIdx.y % B_Y;\n    const int tidx = B_X * ty + tx;\n    const int loadY = tidx / preloadCases, loadX = tidx % preloadCases;\n\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n\n    const int numFilterBlocks = numFilters / (B_X * filtersPerThread);\n    const int blockModuleChunkIdx = blockIdx.x / numFilterBlocks;\n\n    const int numModuleChunksX = DIVUP(numModulesX, sumWidth);\n//    const int numModuleChunksY = DIVUP(numModulesY, sumWidth);\n\n    const int blockModuleChunkX = blockModuleChunkIdx % numModuleChunksX;\n    const int blockModuleChunkY = blockModuleChunkIdx / numModuleChunksX;\n\n    const int blockModuleStartX = blockModuleChunkX * sumWidth;\n    const int blockModuleStartY = blockModuleChunkY * sumWidth;\n\n//    const int moduleIdx = partialSum * outputModuleIdx;\n    const int blockFilterIdx = filtersPerThread * B_X * (blockIdx.x % numFilterBlocks);\n    const int numModules = numModulesY * numModulesX;\n\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockGroupIdx = blockFilterIdx / numFiltersPerGroup;\n    const int numFilterColors = numImgColors / numGroups;\n\n    const int blockPixelOffset = blockIdx.z; // pixel idx in filter\n    const int blockPixelY = blockPixelOffset / filterSize, blockPixelX = blockPixelOffset % filterSize;\n    const int blockFilterColorIdx = blockIdx.y  * B_Y * colorsPerThread;\n    const int imgColorIdx = blockFilterColorIdx + blockGroupIdx * numFilterColors;\n    const int imgOffset = (imgColorIdx + loadY) * imgPixels * imgStride + loadX;\n//    images += (imgColorIdx + loadY) * imgPixels * imgStride + loadX;\n    const int hidActsOffset = blockFilterIdx * numImages * numModules\n            + loadY * numImages * numModules\n            + loadX;\n//\n//    hidActs +=\n//             blockFilterIdx * numImages * numModules\n//            + loadY * numImages * numModules\n//            + loadX;\n\n    targets += blockModuleChunkIdx * numFilters * filterPixels * numFilterColors\n            + (blockFilterColorIdx + ty) * filterPixels * numFilters\n            + blockPixelOffset * numFilters\n            + blockFilterIdx\n            + tx;\n    // if (blockIdx.x != 0 || blockIdx.y != 0 || blockIdx.z != 0) return;\n\n    const int mStartX = max(blockModuleStartX, DIVUP(-blockPixelX - paddingStart, moduleStride));\n    const int mStartY = max(blockModuleStartY, DIVUP(-blockPixelY - paddingStart, moduleStride));\n    const int mEndX = min(numModulesX, min(blockModuleStartX + sumWidth, DIVUP(imgSizeX - blockPixelX - paddingStart, moduleStride)));\n    const int mEndY = min(numModulesY, min(blockModuleStartY + sumWidth, DIVUP(imgSizeY - blockPixelY - paddingStart, moduleStride)));\n\n    // if (mStartY == mEndY || mStartX == mEndX) {\n    //     return;\n    // }\n    const bool doWork = mStartY < mEndY && mStartX < mEndX;\n\n    // reduce 2 registers\n    //float* shHidActLoad = &shHidActs[loadY][loadX];\n    //float* shImgLoad = &shImages[loadY][loadX];\n\n    float imPreload[preloadCases*colorsPerThread/B_X]; // [8]\n    float haPreload[preloadCases*filtersPerThread/B_Y]; // [8]\n\n    float prod[filtersPerThread][colorsPerThread];\n\n    #pragma unroll\n    for (int f = 0; f < filtersPerThread; f++) {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            prod[f][c] = 0;\n        }\n    }\n    int pixIdx, pixIdxNext, m, mNext;\n\n    conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_8_r_16_setCoords(\n            mStartY, mStartX, paddingStart, numModulesX, moduleStride,\n            blockPixelY, blockPixelX, imgSizeX, imgStride,\n            pixIdx, m);\n\n    if (doWork) {\n    #pragma unroll\n        for (int y = 0; y < B_Y * colorsPerThread; y += (B_X * B_Y) / preloadCases) {\n            // It's bizarre, but this is the fastest way I've found to get it not to load nonexistent pixels.\n            // All other ways cause crazy excessive register usage.\n            const int idx = (mStartY < mEndY && mStartX < mEndX) * (0 + y * imgPixels * imgStride + pixIdx);\n            imPreload[y * preloadCases/(B_X * B_Y)] = tex1Dfetch<float>(images, imgOffset + idx);\n        }\n    }\n    \n    if (doWork) {\n        #pragma unroll\n        for (int y = 0; y < B_X * filtersPerThread; y += (B_X * B_Y) / preloadCases) {\n            // Almost certainly not necessary here.\n            const int idx = (mStartY < mEndY && mStartX < mEndX) * (0 + y * numImages * numModules + m * numImages);\n            haPreload[y * preloadCases / (B_X * B_Y)] = tex1Dfetch<float>(hidActs, hidActsOffset + idx);\n        }\n    }\n\n\n    for (int my = mStartY; my < mEndY; my++) {\n        for (int mx = mStartX; mx < mEndX; mx++) {\n            int myNext = my, mxNext = mx;\n            const bool lastModule = my == mEndY - 1 && mx == mEndX - 1;\n\n            if (!lastModule) {\n                mxNext = mx + 1 == mEndX ? mStartX : mx + 1;\n                myNext = my + (mx + 1 == mEndX);\n            }\n\n            conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_8_r_16_setCoords(\n                    myNext, mxNext, paddingStart, numModulesX, moduleStride,\n                    blockPixelY, blockPixelX, imgSizeX, imgStride,\n                    pixIdxNext, mNext);\n\n            for (int caseIdx = 0; caseIdx < numImages; caseIdx += preloadCases) {\n                // store the preloaded image's pixel into shared memory\n                #pragma unroll\n                for (int y = 0; y < 4; y++) {\n                    shImages[loadX][loadY+y*8].x = imPreload[y];\n                    shImages[loadX][loadY+y*8].y = imPreload[y+4];\n                }\n                //const float* im = &images[caseIdx + preloadCases + pixIdx];\n                //const float* ha = &hidActs[caseIdx + preloadCases + m * numImages];\n                int imgOffset2 = imgOffset + caseIdx + preloadCases + pixIdx;\n                int hidActsOffset2 = hidActsOffset + caseIdx + preloadCases + m * numImages;\n                if (caseIdx + preloadCases == numImages) {\n                    pixIdx = pixIdxNext;\n                    m = mNext;\n                    imgOffset2 = imgOffset + pixIdxNext;\n                    hidActsOffset2 = hidActsOffset + mNext * numImages;\n                }\n        \n                // store the images and hidActs \n                shHidActs[loadX][loadY].x = haPreload[0];\n                shHidActs[loadX][loadY].y = haPreload[2];\n                shHidActs[loadX][loadY+16].x = haPreload[4];\n                shHidActs[loadX][loadY+16].y = haPreload[6];\n                shHidActs[loadX][loadY+8].x = haPreload[1];\n                shHidActs[loadX][loadY+8].y = haPreload[3];\n                shHidActs[loadX][loadY+24].x = haPreload[5];\n                shHidActs[loadX][loadY+24].y = haPreload[7];\n\n                // preloade the image's and hidAct's pixel\n                #pragma unroll\n                for (int r = 0; r < 8; r++) {\n                    imPreload[r] = tex1Dfetch<float>(images, imgOffset2 + (r) * 8 * imgPixels * imgStride);\n                    haPreload[r] = tex1Dfetch<float>(hidActs, hidActsOffset2 + (r) * 8 * numImages * numModules);\n                }\n\n                __syncthreads();\n                // put together the instructions of same type to improve instruction-level parallelism\n                #pragma unroll\n                for (int r = 0; r < 16; r++) {\n                    for (int c = 0; c < 4; c++) { \n                        prod[0][c] += shImages[r][ty + c * B_Y].x * shHidActs[(r)][tx].x; \n                        prod[1][c] += shImages[r][ty + c * B_Y].x * shHidActs[(r)][tx].y; \n                        prod[2][c] += shImages[r][ty + c * B_Y].x * shHidActs[(r)][tx + B_X].x; \n                        prod[3][c] += shImages[r][ty + c * B_Y].x * shHidActs[(r)][tx + B_X].y; \n                        prod[0][c+4] += shImages[r][ty + c * B_Y].y * shHidActs[(r)][tx].x; \n                        prod[1][c+4] += shImages[r][ty + c * B_Y].y * shHidActs[(r)][tx].y; \n                        prod[2][c+4] += shImages[r][ty + c * B_Y].y * shHidActs[(r)][tx + B_X].x; \n                        prod[3][c+4] += shImages[r][ty + c * B_Y].y * shHidActs[(r)][tx + B_X].y; \n                    }\n                }\n\n                __syncthreads();\n            }\n        }\n    }\n\n    if (scale) {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                targets[c * B_Y * filterPixels * numFilters + f * B_X] = scaleTargets * targets[c * B_Y * filterPixels * numFilters + f * B_X] + scaleOutputs * prod[f][c];\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                targets[c * B_Y * filterPixels * numFilters + f * B_X] = scaleOutputs * prod[f][c];\n            }\n        }\n    }\n}\n\n/*\n * images:      (numImgColors, imgSizeY, imgSizeX, numImages), with stride given\n * hidActs:     (numFilters, numModulesY, numModulesX, numImages)\n *\n * targets:     (numModulesY*numModulesX/partialSum, numFilterColors, filterPixels, numFilters)\n */\ntemplate <int B_Y, int B_X, int filtersPerThread, int colorsPerThread, int preloadCases, bool scale>\n__launch_bounds__(256, 2)\n__global__ void conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_6_r_32(cudaTextureObject_t images, cudaTextureObject_t hidActs, float* targets,\n                                       const int numImages, const int numFilters,\n                                       const int numModulesY, const int numModulesX,\n                                       const int imgSizeY, const int imgSizeX, const int filterSize,\n                                       const int paddingStart, const int moduleStride, const int imgStride,\n                                       const int numImgColors, const int numGroups, const int sumWidth,\n                                       const float scaleTargets, const float scaleOutputs) {\n    __shared__ float shImages[colorsPerThread * B_Y][preloadCases]; // preload preloadCases cases\n    __shared__ float shHidActs[filtersPerThread * B_X][preloadCases + 1]; // preload preloadCases cases of B_X hidacts\n\n    const int tidx = B_X * threadIdx.y + threadIdx.x;\n    const int loadY = tidx / preloadCases, loadX = tidx % preloadCases;\n\n    const int filterPixels = filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n\n    const int numFilterBlocks = numFilters / (B_X * filtersPerThread);\n    const int blockModuleChunkIdx = blockIdx.x / numFilterBlocks;\n\n    const int numModuleChunksX = DIVUP(numModulesX, sumWidth);\n//    const int numModuleChunksY = DIVUP(numModulesY, sumWidth);\n\n    const int blockModuleChunkX = blockModuleChunkIdx % numModuleChunksX;\n    const int blockModuleChunkY = blockModuleChunkIdx / numModuleChunksX;\n\n    const int blockModuleStartX = blockModuleChunkX * sumWidth;\n    const int blockModuleStartY = blockModuleChunkY * sumWidth;\n\n//    const int moduleIdx = partialSum * outputModuleIdx;\n    const int blockFilterIdx = filtersPerThread * B_X * (blockIdx.x % numFilterBlocks);\n    const int numModules = numModulesY * numModulesX;\n\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockGroupIdx = blockFilterIdx / numFiltersPerGroup;\n    const int numFilterColors = numImgColors / numGroups;\n\n    const int blockPixelOffset = blockIdx.z; // pixel idx in filter\n    const int blockPixelY = blockPixelOffset / filterSize, blockPixelX = blockPixelOffset % filterSize;\n    const int blockFilterColorIdx = blockIdx.y  * B_Y * colorsPerThread;\n    const int imgColorIdx = blockFilterColorIdx + blockGroupIdx * numFilterColors;\n\n    const int imgOffset = (imgColorIdx + loadY) * imgPixels * imgStride + loadX;\n    const int hidActsOffset = blockFilterIdx * numImages * numModules\n            + loadY * numImages * numModules\n            + loadX;\n//    images += (imgColorIdx + loadY) * imgPixels * imgStride + loadX;\n//\n//    hidActs +=\n//             blockFilterIdx * numImages * numModules\n//            + loadY * numImages * numModules\n//            + loadX;\n\n    targets += blockModuleChunkIdx * numFilters * filterPixels * numFilterColors\n            + (blockFilterColorIdx + threadIdx.y) * filterPixels * numFilters\n            + blockPixelOffset * numFilters\n            + blockFilterIdx\n            + threadIdx.x;\n//    if (blockIdx.x != 0 || blockIdx.y != 0 || blockIdx.z != 0) return;\n\n    const int mStartX = max(blockModuleStartX, DIVUP(-blockPixelX - paddingStart, moduleStride));\n    const int mStartY = max(blockModuleStartY, DIVUP(-blockPixelY - paddingStart, moduleStride));\n    const int mEndX = min(numModulesX, min(blockModuleStartX + sumWidth, DIVUP(imgSizeX - blockPixelX - paddingStart, moduleStride)));\n    const int mEndY = min(numModulesY, min(blockModuleStartY + sumWidth, DIVUP(imgSizeY - blockPixelY - paddingStart, moduleStride)));\n\n//    if (mStartY == mEndY || mStartX == mEndX) {\n//        return;\n//    }\n    const bool doWork = mStartY < mEndY && mStartX < mEndX;\n\n    float* shHidActLoad = &shHidActs[loadY][loadX];\n    float* shImgLoad = &shImages[loadY][loadX];\n\n    float imPreload[preloadCases*colorsPerThread/B_X]; // [6]\n    float haPreload[preloadCases*filtersPerThread/B_Y]; // [16]\n\n    float prod[filtersPerThread][colorsPerThread];\n\n    #pragma unroll\n    for (int f = 0; f < filtersPerThread; f++) {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            prod[f][c] = 0;\n        }\n    }\n    int pixIdx, pixIdxNext, m, mNext;\n\n    conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_8_r_16_setCoords(\n            mStartY, mStartX, paddingStart, numModulesX, moduleStride,\n            blockPixelY, blockPixelX, imgSizeX, imgStride,\n            pixIdx, m);\n\n    if (doWork) {\n        #pragma unroll\n        for (int y = 0; y < B_Y * colorsPerThread; y += (B_X * B_Y) / preloadCases) {\n            imPreload[y * preloadCases/(B_X * B_Y)] = tex1Dfetch<float>(images, imgOffset + y * imgPixels * imgStride + pixIdx);\n        }\n\n        #pragma unroll\n        for (int y = 0; y < B_X * filtersPerThread; y += (B_X * B_Y) / preloadCases) {\n            haPreload[y * preloadCases / (B_X * B_Y)] = tex1Dfetch<float>(hidActs, hidActsOffset + y * numImages * numModules + m * numImages);\n        }\n    }\n//    if (mStartY > mEndY || mStartX > mEndX) {\n//        printf(\"crzy!!\\n\");\n//    }\n\n    for (int my = mStartY; my < mEndY; my++) {\n        for (int mx = mStartX; mx < mEndX; mx++) {\n            int myNext = my, mxNext = mx;\n            const bool lastModule = my == mEndY - 1 && mx == mEndX - 1;\n\n            if (!lastModule) {\n                mxNext = mx + 1 == mEndX ? mStartX : mx + 1;\n                myNext = my + (mx + 1 == mEndX);\n            }\n\n            conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_8_r_16_setCoords(\n                    myNext, mxNext, paddingStart, numModulesX, moduleStride,\n                    blockPixelY, blockPixelX, imgSizeX, imgStride,\n                    pixIdxNext, mNext);\n\n            for (int caseIdx = 0; caseIdx < numImages; caseIdx += preloadCases) {\n                #pragma unroll\n                for (int y = 0; y < B_Y * colorsPerThread; y += (B_X * B_Y) / preloadCases) {\n                    shImgLoad[(y) * preloadCases] = imPreload[y * preloadCases / (B_X * B_Y)];\n                }\n\n                #pragma unroll\n                for (int y = 0; y < B_X * filtersPerThread; y += (B_X * B_Y) / preloadCases) {\n                    shHidActLoad[y * (preloadCases + 1)] = haPreload[y * preloadCases / (B_X * B_Y)];\n                }\n\n                __syncthreads();\n\n//                const float* im = &images[caseIdx + preloadCases + pixIdx];\n//                const float* ha = &hidActs[caseIdx + preloadCases + m * numImages];\n                int imgOffset2 = imgOffset + caseIdx + preloadCases + pixIdx;\n                int hidActsOffset2 = hidActsOffset + caseIdx + preloadCases + m * numImages;\n                if (caseIdx + preloadCases == numImages) {\n                    pixIdx = pixIdxNext;\n                    m = mNext;\n                    imgOffset2 = imgOffset + pixIdxNext;\n                    hidActsOffset2 = hidActsOffset + mNext * numImages;\n                }\n\n                WA_LOOP(0);\n                WA_LOOP(1);\n                WA_LOOP(2);\n                WA_LOOP(3);\n                WA_LOOP(4);\n\n                WA_LOOP(5);\n                WA_IMLOAD_TX(0);\n                WA_LOOP(6);\n                WA_IMLOAD_TX(1);\n                WA_LOOP(7);\n                WA_IMLOAD_TX(2);\n                WA_LOOP(8);\n                WA_IMLOAD_TX(3);\n                WA_LOOP(9);\n                WA_IMLOAD_TX(4);\n                WA_LOOP(10);\n                WA_IMLOAD_TX(5);\n\n                WA_LOOP(11);\n                WA_HALOAD_TX(0);\n                WA_LOOP(12);\n                WA_HALOAD_TX(1);\n                WA_LOOP(13);\n                WA_HALOAD_TX(2);\n                WA_LOOP(14);\n                WA_HALOAD_TX(3);\n                WA_LOOP(15);\n                WA_HALOAD_TX(4);\n                WA_LOOP(16);\n                WA_HALOAD_TX(5);\n                WA_LOOP(17);\n                WA_HALOAD_TX(6);\n                WA_LOOP(18);\n                WA_HALOAD_TX(7);\n                WA_LOOP(19);\n                WA_HALOAD_TX(8);\n                WA_LOOP(20);\n                WA_HALOAD_TX(9);\n                WA_LOOP(21);\n                WA_HALOAD_TX(10);\n                WA_LOOP(22);\n                WA_HALOAD_TX(11);\n                WA_LOOP(23);\n                WA_HALOAD_TX(12);\n                WA_LOOP(24);\n                WA_HALOAD_TX(13);\n                WA_LOOP(25);\n                WA_HALOAD_TX(14);\n                WA_LOOP(26);\n                WA_HALOAD_TX(15);\n\n                WA_LOOP(27);\n                WA_LOOP(28);\n                WA_LOOP(29);\n                WA_LOOP(30);\n                WA_LOOP(31);\n\n                __syncthreads();\n            }\n        }\n    }\n\n    if (scale) {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                targets[c * B_Y * filterPixels * numFilters + f * B_X] = scaleTargets * targets[c * B_Y * filterPixels * numFilters + f * B_X] + scaleOutputs * prod[f][c];\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                targets[c * B_Y * filterPixels * numFilters + f * B_X] = scaleOutputs * prod[f][c];\n            }\n        }\n    }\n}\n\n/*****************************Function Revision Record*****************************\n * Author: Tencent BestImage Team(ankerguo@tencent.com)                           *\n * Date:   2015-05-18                                                             *\n * Reason: Optimizing kernel to get faster speed according to GPU features        *\n * Method:                                                                        *\n *         1. reorganizing data structure to avoid bank conflict;                 *\n *         2. using vectorized data type;                                         *\n *         3. improving instruction-level parallelism;                            *\n *         4. removing redundant 'if' branches;                                   *\n *         5. removing local variables to save registers.                         *\n *********************************************************************************/\n\n/*\n * images:      (numImgColors, imgSizeY, imgSizeX, numImages), with stride given\n * hidActs:     (numFilters, numModulesY, numModulesX, numImages)\n *\n * targets:     (numModulesY*numModulesX/partialSum, numFilterColors, filterPixels, numFilters)\n */\ntemplate <int B_Y, int B_X, int filtersPerThread, int colorsPerThread, int preloadCases, bool scale>\n__launch_bounds__(256, 2)\n__global__ void conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_8_r_16(cudaTextureObject_t images, cudaTextureObject_t hidActs, float* targets,\n                                       const int numImages, const int numFilters,\n                                       const int numModulesY, const int numModulesX,\n                                       const int imgSizeY, const int imgSizeX, const int filterSize,\n                                       const int paddingStart, const int moduleStride, const int imgStride,\n                                       const int numImgColors, const int numGroups, const int sumWidth,\n                                       const float scaleTargets, const float scaleOutputs) {\n    // avoid bank conflict by re-organizing the data structure, and improve band width by using 'float2' instead of 'float'\n    __shared__ float2 shImages[preloadCases][colorsPerThread * B_Y / 2 + 2]; // preload preloadCases cases\n    __shared__ float2 shHidActs[preloadCases][filtersPerThread * B_X / 2 + 2]; // preload preloadCases cases of B_X hidacts\n    const int tx = threadIdx.x % B_X, ty = threadIdx.y % B_Y;\n    //const int tidx = B_X * threadIdx.y + threadIdx.x;\n    // reduce two registers\n    //const int loadY = tidx / preloadCases, loadX = tidx % preloadCases;\n\n    //const int filterPixels = filterSize * filterSize;\n    // reduce one register\n    const int filterPixelsAll = numFilters * filterSize * filterSize;\n    const int imgPixels = imgSizeY * imgSizeX;\n\n    const int numFilterBlocks = numFilters / (B_X * filtersPerThread);\n    const int blockModuleChunkIdx = blockIdx.x / numFilterBlocks;\n\n    const int numModuleChunksX = DIVUP(numModulesX, sumWidth);\n    // const int numModuleChunksY = DIVUP(numModulesY, sumWidth);\n\n    const int blockModuleChunkX = blockModuleChunkIdx % numModuleChunksX;\n    const int blockModuleChunkY = blockModuleChunkIdx / numModuleChunksX;\n\n    const int blockModuleStartX = blockModuleChunkX * sumWidth;\n    const int blockModuleStartY = blockModuleChunkY * sumWidth;\n\n    // const int moduleIdx = partialSum * outputModuleIdx;\n    const int blockFilterIdx = filtersPerThread * B_X * (blockIdx.x % numFilterBlocks);\n    const int numModules = numModulesY * numModulesX;\n\n    const int numFiltersPerGroup = numFilters / numGroups;\n    const int blockGroupIdx = blockFilterIdx / numFiltersPerGroup;\n    const int numFilterColors = numImgColors / numGroups;\n\n    const int blockPixelOffset = blockIdx.z; // pixel idx in filter\n    const int blockPixelY = blockPixelOffset / filterSize, blockPixelX = blockPixelOffset % filterSize;\n    const int blockFilterColorIdx = blockIdx.y  * B_Y * colorsPerThread;\n    const int imgColorIdx = blockFilterColorIdx + blockGroupIdx * numFilterColors;\n    const int imgOffset = (imgColorIdx + (ty * B_X + tx) / preloadCases) * imgPixels * imgStride + (ty * B_X + tx) % preloadCases;\n    // images += (imgColorIdx + loadY) * imgPixels * imgStride + loadX;\n    const int hidActsOffset = blockFilterIdx * numImages * numModules\n            + ((ty * B_X + tx) / preloadCases) * numImages * numModules\n            + ((ty * B_X + tx) % preloadCases);\n    //\n    // hidActs +=\n    //             blockFilterIdx * numImages * numModules\n    //            + loadY * numImages * numModules\n    //            + loadX;\n\n    // usie one temporary register instead of multiple registers\n    const int pIdxBase = imgStride * ((paddingStart + blockPixelY) * imgSizeX + paddingStart + blockPixelX);\n\n    targets += blockModuleChunkIdx * numFilters * filterSize * filterSize * numFilterColors\n            + (blockFilterColorIdx + ty) * filterSize * filterSize * numFilters\n            + blockPixelOffset * numFilters\n            + blockFilterIdx\n            + tx;\n    // if (blockIdx.x != 0 || blockIdx.y != 0 || blockIdx.z != 0) return;\n\n    const int mStartX = max(blockModuleStartX, DIVUP(-blockPixelX - paddingStart, moduleStride));\n    const int mStartY = max(blockModuleStartY, DIVUP(-blockPixelY - paddingStart, moduleStride));\n    const int mEndX = min(numModulesX, min(blockModuleStartX + sumWidth, DIVUP(imgSizeX - blockPixelX - paddingStart, moduleStride)));\n    const int mEndY = min(numModulesY, min(blockModuleStartY + sumWidth, DIVUP(imgSizeY - blockPixelY - paddingStart, moduleStride)));\n\n    // reduce 3 registers\n    const bool doWork = mStartY < mEndY && mStartX < mEndX;\n\n    //float* shHidActLoad = &shHidActs[loadY][loadX];\n    //float* shImgLoad = &shImages[loadY][loadX];\n\n    float imPreload[preloadCases*colorsPerThread/B_X]; // [4]\n    float haPreload[preloadCases*filtersPerThread/B_Y]; // [8]\n\n    float prod[filtersPerThread][colorsPerThread];\n\n    #pragma unroll\n    for (int f = 0; f < filtersPerThread; f++) {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            prod[f][c] = 0;\n        }\n    }\n    //int pixIdx, pixIdxNext, m, mNext;\n\n    //conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_8_r_16_setCoords(\n    //        mStartY, mStartX, paddingStart, numModulesX, moduleStride,\n    //        blockPixelY, blockPixelX, imgSizeX, imgStride,\n    //        pixIdx, m);\n    \n    const int pixIdx = pIdxBase + (mStartY * imgSizeX + mStartX) * moduleStride * imgStride;\n    const int m = (mStartY * numModulesX + mStartX);\n\n    // preload the image's pixel \n    if (doWork && (ty * B_X + tx) / preloadCases < (B_Y * colorsPerThread / 4)) {\n        #pragma unroll\n        for (int i = 0; i < 4; i++) {\n            imPreload[i] = tex1Dfetch<float>(images, imgOffset + 16 * i * imgPixels * imgStride + pixIdx);\n        }\n    }\n\n    // preload the hidAct's pixel\n    if (doWork && (ty * B_X + tx) / preloadCases < (B_X * filtersPerThread) / 8) {\n        #pragma unroll\n        for (int i = 0; i < 8; i++) {\n            haPreload[i] = tex1Dfetch<float>(hidActs, hidActsOffset + 16 * i * numImages * numModules + m * numImages);\n        }\n    }\n\n    for (int my = mStartY; my < mEndY; my++) {\n        for (int mx = mStartX; mx < mEndX; mx++) {\n\n            for (int caseIdx = 0; caseIdx < numImages; caseIdx += preloadCases) {\n                int imgOffset2 = imgOffset + caseIdx + preloadCases + pIdxBase + (my * imgSizeX + mx) * moduleStride * imgStride;\n                int hidActsOffset2 = hidActsOffset + caseIdx + preloadCases + (my * numModulesX + mx) * numImages;\n\n                if (caseIdx + preloadCases == numImages) {\n                    const int mxNext = mx + 1 == mEndX ? mStartX : mx + 1;\n                    const int myNext = my + (mx + 1 == mEndX);\n\n                    imgOffset2 = imgOffset + + pIdxBase + (myNext * imgSizeX + mxNext) * moduleStride * imgStride;\n                    hidActsOffset2 = hidActsOffset + (myNext * numModulesX + mxNext) * numImages;\n                }\n\n                if ((ty * B_X + tx) / preloadCases < (B_Y * colorsPerThread / 4)) {\n                    // store the previousely preloaded pixel into shared memory\n                    shImages[(ty * B_X + tx) % preloadCases][(ty * B_X + tx) / preloadCases].x = imPreload[0];\n                    shImages[(ty * B_X + tx) % preloadCases][(ty * B_X + tx) / preloadCases].y = imPreload[2];\n                    shImages[(ty * B_X + tx) % preloadCases][(ty * B_X + tx) / preloadCases + 16].x = imPreload[1];\n                    shImages[(ty * B_X + tx) % preloadCases][(ty * B_X + tx) / preloadCases + 16].y = imPreload[3];\n\t\t}\n\n                if ((ty * B_X + tx) / preloadCases < (B_X * filtersPerThread / 8)) {\n                    shHidActs[(ty * B_X + tx) % preloadCases][(ty * B_X + tx) / preloadCases].x = haPreload[0];\n                    shHidActs[(ty * B_X + tx) % preloadCases][(ty * B_X + tx) / preloadCases].y = haPreload[2];\n                    shHidActs[(ty * B_X + tx) % preloadCases][(ty * B_X + tx) / preloadCases + 32].x = haPreload[4];\n                    shHidActs[(ty * B_X + tx) % preloadCases][(ty * B_X + tx) / preloadCases + 32].y = haPreload[6];\n                    shHidActs[(ty * B_X + tx) % preloadCases][(ty * B_X + tx) / preloadCases + 16].x = haPreload[1];\n                    shHidActs[(ty * B_X + tx) % preloadCases][(ty * B_X + tx) / preloadCases + 16].y = haPreload[3];\n                    shHidActs[(ty * B_X + tx) % preloadCases][(ty * B_X + tx) / preloadCases + 48].x = haPreload[5];\n                    shHidActs[(ty * B_X + tx) % preloadCases][(ty * B_X + tx) / preloadCases + 48].y = haPreload[7];\n\t\t}\n\n                #pragma unroll\n                for (int r = 0; r < 8; r++) {\n                    haPreload[r] = tex1Dfetch<float>(hidActs, hidActsOffset2 + r * 16 * numImages * numModules);\n                }\n\n                #pragma unroll\n                for (int r = 0; r < 4; r++) {\n                    imPreload[r] = tex1Dfetch<float>(images, imgOffset2 + r * 16 * imgPixels * imgStride);\n                }\n                __syncthreads();\n\n                // put together the instructions of same type to improve instruction-level parallelism\n                // calculate the derivative of the hidAct with respect to weight\n                #pragma unroll\n                for (int r = 0; r < 16; r++) {\n                    #pragma unroll\n                    for (int c = 0; c < 4; c++) { \n                        prod[0][c] += shImages[r][ty + c * B_Y].x * shHidActs[r][tx].x; \n                        prod[1][c] += shImages[r][ty + c * B_Y].x * shHidActs[r][tx].y; \n                        prod[2][c] += shImages[r][ty + c * B_Y].x * shHidActs[r][tx + B_X].x; \n                        prod[3][c] += shImages[r][ty + c * B_Y].x * shHidActs[r][tx + B_X].y; \n                        prod[0][c+4] += shImages[r][ty + c * B_Y].y * shHidActs[r][tx].x; \n                        prod[1][c+4] += shImages[r][ty + c * B_Y].y * shHidActs[r][tx].y; \n                        prod[2][c+4] += shImages[r][ty + c * B_Y].y * shHidActs[r][tx + B_X].x; \n                        prod[3][c+4] += shImages[r][ty + c * B_Y].y * shHidActs[r][tx + B_X].y; \n                    }\n                }    \n\n                __syncthreads();\n            } \n        } \n    } \n\n    if (scale) {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                targets[c * B_Y * filterPixelsAll + f * B_X] = scaleTargets * targets[c * B_Y * filterPixelsAll + f * B_X] + scaleOutputs * prod[f][c];\n            }\n        }\n    } else {\n        #pragma unroll\n        for (int c = 0; c < colorsPerThread; c++) {\n            #pragma unroll\n            for (int f = 0; f < filtersPerThread; f++) {\n                targets[c * B_Y * filterPixelsAll + f * B_X] = scaleOutputs * prod[f][c];\n            }\n        }\n    }\n}\n\nstd::pair<int,int> getWeightActsOutputSize(int numModulesY, int numModulesX, int numFilterColors,\n                                                  int filterSize, int numFilters, int sumWidth) {\n    const int outputModuleChunksX = DIVUP(numModulesX, sumWidth);\n    const int outputModuleChunksY = DIVUP(numModulesY, sumWidth);\n    const int outputModuleChunks = outputModuleChunksX * outputModuleChunksY;\n    return std::pair<int,int>(outputModuleChunks * numFilterColors * filterSize * filterSize, numFilters);\n}\n\n/*\n * images:      (numImgColors, imgSizeY, imgSizeX, numImages), with stride given\n * hidActs:     (numFilters, numModules, numImages)\n *\n * targets:     (numModuleY*numModulesX/partialSum, numFilterColors, filterPixels, numFilters)\n *\n * TODO: you can get a slight speed boost for local non-convolutional units by writing special\n * routines for partialSum = 1. But I dunno if the code duplication is worth it...\n *\n * Note: all of these convolution routines are optimized for the case when\n * the number of images (i.e. the minibatch size) is a multiple of 128.\n * Other batch sizes will work, but but I made no attempt whatsoever\n * to make them work fast.\n */\nvoid _weightActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* images, caffe2::TensorCUDA* hidActs, caffe2::TensorCUDA* targets,\n                 int imgSizeY, int numModulesY, int numModulesX, int filterSize, int paddingStart, int moduleStride, int numImgColors,\n                 int numGroups, int sumWidth, float scaleTargets, float scaleOutput) {\n    CAFFE_ENFORCE(images->ndim() == 2);\n    CAFFE_ENFORCE(hidActs->ndim() == 2);\n    CAFFE_ENFORCE(targets->ndim() == 2);\n\n    int numFilterColors = numImgColors / numGroups;\n    int imgStride = images->dim32(1);\n    int numImages = images->dim32(1);\n    int imgPixels = images->dim32(0) / numImgColors;\n    int imgSizeX = imgPixels / imgSizeY;\n    int numModules = numModulesY * numModulesX;\n    int numFilters = hidActs->dim32(0) / numModules;\n    int numFiltersPerGroup = numFilters / numGroups;\n\n    CAFFE_ENFORCE(numImgColors % numGroups == 0);\n    CAFFE_ENFORCE(numFilters % (16*numGroups) == 0);\n    CAFFE_ENFORCE(numGroups > 1 || (numImgColors > 0 && (numImgColors <= 3 || numImgColors % 16 == 0)));\n    CAFFE_ENFORCE(numGroups == 1 || numFilterColors % 16 == 0);\n    CAFFE_ENFORCE(imgSizeY * imgSizeX == imgPixels);\n    CAFFE_ENFORCE(images->dim32(0) == imgPixels * numImgColors);\n\n    int filterPixels = filterSize * filterSize;\n    int outputModuleChunksX = DIVUP(numModulesX, sumWidth);\n    int outputModuleChunksY = DIVUP(numModulesY, sumWidth);\n    int outputModuleChunks = outputModuleChunksX * outputModuleChunksY;\n//    partialSum = partialSum == 0 ? numModules : partialSum;\n\n//    CAFFE_ENFORCE(numModules % partialSum == 0);\n    CAFFE_ENFORCE(hidActs->dim32(1) == numImages);\n\n    // These routines don't handle the case when only part of the image is visited in the convolution\n    CAFFE_ENFORCE(paddingStart <= 0);\n    CAFFE_ENFORCE(paddingStart + (numModulesX-1)*moduleStride + filterSize >= imgSizeX);\n    CAFFE_ENFORCE(paddingStart + (numModulesY-1)*moduleStride + filterSize >= imgSizeY);\n    CAFFE_ENFORCE(moduleStride <= filterSize);\n\n    CAFFE_ENFORCE(numModules * numFilters == hidActs->dim32(0));\n\n    int preloadCases = 32;\n\n    dim3 blocks, threads;\n    int bx, by;\n    int pixelsPerThread, filtersPerThread, colorsPerThread;\n    // Worth playing with these parameters to find best values for your problem.\n    // These values work relatively well, but not optimal for all problems.\n    if (numFilterColors > 3) {\n        filtersPerThread = numFiltersPerGroup % 64 == 0 ? 4\n                        : numFiltersPerGroup % 32 == 0 ? 2\n                        : 1;\n        colorsPerThread = numFilterColors % 64 == 0 ? 8\n                        : numFilterColors % 48 == 0 ? 6\n                        : numFilterColors % 32 == 0 ? 8\n                        : 4;\n        by = (numFilterColors / colorsPerThread) % 8 == 0 ? 8 : 4;\n        bx = numFiltersPerGroup % 128 == 0 ? 32 : 16;\n        preloadCases = filtersPerThread * colorsPerThread < 32 ? 32 : 16;\n        blocks = dim3(outputModuleChunks*(numFilters/(bx*filtersPerThread)), numFilterColors / (by*colorsPerThread), filterPixels);\n        CAFFE_ENFORCE(numFilterColors % (by*colorsPerThread) == 0);\n    } else { // This is ugly but it's nice to spell it out clearly\n        CAFFE_ENFORCE(numGroups == 1); // Just for sanity\n        // NOTE: these things are only optimized for colors = 3. I didn't really test other cases.\n        if (numFilters % 64 == 0) { // TODO: having a separate case for 128 would make things faster, but I probably don't care about 128\n            filtersPerThread = 4;\n            pixelsPerThread = 2;\n            by = 16;\n            bx = 16;\n            preloadCases = 32;\n        } else if (numFilters % 48 == 0) {\n            filtersPerThread = 3;\n            pixelsPerThread = 4;\n            by = 16;\n            bx = 16;\n            preloadCases = 32;\n        } else if (numFilters % 32 == 0) {\n            filtersPerThread = 2;\n            pixelsPerThread = 2;\n            by = 8;\n            bx = 16;\n            preloadCases = 16;\n        } else { // This case is completely untested. It might be really slow. But no time now.\n            filtersPerThread = 1;\n            pixelsPerThread = 16;\n            by = 16;\n            bx = 16;\n            preloadCases = 32;\n        }\n        blocks = dim3(outputModuleChunks*(numFilters/(bx*filtersPerThread)), DIVUP(filterPixels, by*pixelsPerThread));\n    }\n    CAFFE_ENFORCE((by * bx) % preloadCases == 0);\n    CAFFE_ENFORCE(numFilters % (bx * filtersPerThread) == 0);\n    threads = dim3(bx, by);\n    bool checkCaseBounds = numImages % preloadCases != 0;\n    bool scale = scaleTargets != 0;\n    std::pair<int,int> targetSize = getWeightActsOutputSize(numModulesY, numModulesX, numFilterColors, filterSize, numFilters, sumWidth);\n    if (!scale) {\n        targets->Resize(std::vector<int>{targetSize.first, targetSize.second});\n    } else {\n        CAFFE_ENFORCE(targets->dim32(0) == targetSize.first);\n        CAFFE_ENFORCE(targets->dim32(1) == targetSize.second);\n    }\n\n    cudaTextureObject_t tex_images = GetTensorTextureObject(images);\n    cudaTextureObject_t tex_hidacts = GetTensorTextureObject(hidActs);\n    float* images_data = images->mutable_data<float>();\n    float* hidacts_data = hidActs->mutable_data<float>();\n    float* targets_data = targets->mutable_data<float>();\n    const std::size_t images_bytes = images->nbytes();\n\n    cudaStream_t stream = context->cuda_stream();\n    \n    checkCudaErrors(cudaDeviceSetSharedMemConfig(cudaSharedMemBankSizeEightByte));\n\n    if (scale == false) {\n        if (checkCaseBounds == false) {\n            if (numFilterColors > 3)  {\n                if (numFilterColors % 64 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_8_r_16< 8, 32, 4, 8, 16, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_8_r_16< 8, 32, 4, 8, 16, false ><<<blocks, threads, 0, stream>>>(tex_images, tex_hidacts, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_16_f_4_c_8_r_16< 8, 16, 4, 8, 16, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_16_f_4_c_8_r_16< 8, 16, 4, 8, 16, false ><<<blocks, threads, 0, stream>>>(tex_images, tex_hidacts, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 8, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 8, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 8, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 8, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors % 48 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_6_r_32< 8, 32, 4, 6, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_6_r_32< 8, 32, 4, 6, 32, false ><<<blocks, threads, 0, stream>>>(tex_images, tex_hidacts, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 4, 6, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 4, 6, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 6, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 6, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 6, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 6, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors % 32 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 8, 16, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 8, 16, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 8, 16, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 8, 16, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 8, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 8, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 8, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 8, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors % 16 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 4, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 4, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 4, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 4, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 4, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 4, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 4, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 4, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n            }\n            else if (numFilterColors <= 3) {\n                if (numFilterColors == 3) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_preload_pc_2_pt_2_f_4_r_32_c_3 < 16, 16, 2, 2, 4, 32, 3, false, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_preload_pc_2_pt_2_f_4_r_32_c_3 < 16, 16, 2, 2, 4, 32, 3, false, false ><<<blocks, threads, 0, stream>>>(tex_images, tex_hidacts, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_preload_pc_2_pt_4_f_3_r_32_c_3 < 16, 16, 2, 4, 3, 32, 3, false, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_preload_pc_2_pt_4_f_3_r_32_c_3 < 16, 16, 2, 4, 3, 32, 3, false, false ><<<blocks, threads, 0, stream>>>(tex_images, tex_hidacts, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 3, false, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 3, false, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 3, false, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 3, false, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors == 2) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 2, false, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 2, false, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 2, false, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 2, false, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 2, false, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 2, false, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 2, false, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 2, false, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors == 1) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 1, false, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 1, false, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 1, false, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 1, false, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 1, false, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 1, false, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 1, false, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 1, false, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n            }\n        }\n        else if (checkCaseBounds == true) {\n            if (numFilterColors > 3) {\n                if (numFilterColors % 64 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 32, 4, 8, 16, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 32, 4, 8, 16, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 4, 8, 16, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 4, 8, 16, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 8, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 8, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 8, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 8, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors % 48 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 32, 4, 6, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 32, 4, 6, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 4, 6, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 4, 6, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 6, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 6, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 6, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 6, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors % 32 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 8, 16, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 8, 16, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 8, 16, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 8, 16, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 8, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 8, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 8, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 8, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors % 16 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 4, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 4, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 4, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 4, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 4, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 4, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 4, 32, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 4, 32, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n            }\n            else if (numFilterColors <= 3) {\n                if (numFilterColors == 3) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 3, false, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 3, false, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 3, false, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 3, false, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 3, false, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 3, false, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 3, false, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 3, false, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors == 2) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 2, false, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 2, false, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 2, false, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 2, false, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 2, false, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 2, false, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 2, false, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 2, false, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors == 1) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 1, false, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 1, false, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 1, false, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 1, false, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 1, false, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 1, false, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 1, false, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 1, false, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n            }\n        }\n    }\n    else if (scale == true) {\n        if (checkCaseBounds == false) {\n            if (numFilterColors > 3) {\n                if (numFilterColors % 64 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_8_r_16< 8, 32, 4, 8, 16, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_8_r_16< 8, 32, 4, 8, 16, true ><<<blocks, threads, 0, stream>>>(tex_images, tex_hidacts, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_16_f_4_c_8_r_16< 8, 16, 4, 8, 16, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_16_f_4_c_8_r_16< 8, 16, 4, 8, 16, true ><<<blocks, threads, 0, stream>>>(tex_images, tex_hidacts, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 8, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 8, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 8, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 8, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors % 48 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_6_r_32< 8, 32, 4, 6, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_preload_ty_8_tx_32_f_4_c_6_r_32< 8, 32, 4, 6, 32, true ><<<blocks, threads, 0, stream>>>(tex_images, tex_hidacts, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 4, 6, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 4, 6, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 6, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 6, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 6, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 6, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors % 32 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 8, 16, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 8, 16, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 8, 16, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 8, 16, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 8, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 8, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 8, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 8, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors % 16 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 4, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 4, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 4, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 4, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 4, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 4, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 4, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 4, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n            }\n            else if (numFilterColors <= 3) {\n                if (numFilterColors == 3) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_preload_pc_2_pt_2_f_4_r_32_c_3 < 16, 16, 2, 2, 4, 32, 3, true, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_preload_pc_2_pt_2_f_4_r_32_c_3 < 16, 16, 2, 2, 4, 32, 3, true, false ><<<blocks, threads, 0, stream>>>(tex_images, tex_hidacts, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_preload_pc_2_pt_4_f_3_r_32_c_3 < 16, 16, 2, 4, 3, 32, 3, true, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_preload_pc_2_pt_4_f_3_r_32_c_3 < 16, 16, 2, 4, 3, 32, 3, true, false ><<<blocks, threads, 0, stream>>>(tex_images, tex_hidacts, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 3, true, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 3, true, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 3, true, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 3, true, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors == 2) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 2, true, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 2, true, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 2, true, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 2, true, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 2, true, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 2, true, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 2, true, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 2, true, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors == 1) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 1, true, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 1, true, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 1, true, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 1, true, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 1, true, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 1, true, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 1, true, false >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 1, true, false ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n            }\n        }\n        else if (checkCaseBounds == true) {\n            if (numFilterColors > 3) {\n                if (numFilterColors % 64 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 32, 4, 8, 16, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 32, 4, 8, 16, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 4, 8, 16, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 4, 8, 16, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 8, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 8, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 8, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 8, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors % 48 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 32, 4, 6, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 32, 4, 6, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 4, 6, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 4, 6, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 6, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 2, 6, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 6, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 8, 16, 1, 6, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors % 32 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 8, 16, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 8, 16, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 8, 16, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 8, 16, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 8, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 8, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 8, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 8, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors % 16 == 0) {\n                    if (numFiltersPerGroup % 128 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 4, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 32, 4, 4, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 4, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 4, 4, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 4, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 2, 4, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 4, 32, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_mc_mf_kepler_sw < 4, 16, 1, 4, 32, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, numImgColors, numGroups, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n            }\n            else if (numFilterColors <= 3) {\n                if (numFilterColors == 3) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 3, true, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 3, true, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 3, true, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 3, true, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 3, true, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 3, true, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 3, true, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 3, true, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors == 2) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 2, true, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 2, true, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 2, true, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 2, true, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 2, true, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 2, true, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 2, true, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 2, true, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n                else if (numFilterColors == 1) {\n                    if (numFiltersPerGroup % 64 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 1, true, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 2, 4, 32, 1, true, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 48 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 1, true, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 4, 3, 32, 1, true, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 32 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 1, true, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 8, 16, 2, 2, 2, 16, 1, true, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                    else if (numFiltersPerGroup % 16 == 0) {\n                        cudaFuncSetCacheConfig(conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 1, true, true >, cudaFuncCachePreferShared);\n                        conv_weight_acts_c_kepler_sw < 16, 16, 2, 16, 1, 32, 1, true, true ><<<blocks, threads, 0, stream>>>(images_data, hidacts_data, targets_data, numImages, numFilters, numModulesY, numModulesX, imgSizeY, imgSizeX, filterSize, paddingStart, moduleStride, imgStride, sumWidth, scaleTargets, scaleOutput);\n                    }\n                }\n            }\n        }\n    }\n    checkCudaErrors(cudaDestroyTextureObject(tex_images));\n    checkCudaErrors(cudaDestroyTextureObject(tex_hidacts));\n    checkCudaErrors(cudaDeviceSetSharedMemConfig(cudaSharedMemBankSizeFourByte));\n    getLastCudaError(\"weightActs: kernel execution failed\");\n}\n\nvoid convWeightActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* images, caffe2::TensorCUDA* hidActs, caffe2::TensorCUDA* targets,\n                    int imgSizeY, int numModulesY, int numModulesX, int filterSize, int paddingStart, int moduleStride, int numImgColors, int numGroups, int partialSum) {\n    _weightActs(context, images, hidActs, targets, imgSizeY, numModulesY, numModulesX, filterSize, paddingStart, moduleStride, numImgColors, numGroups, partialSum, 0, 1);\n}\n\nvoid convWeightActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* images, caffe2::TensorCUDA* hidActs, caffe2::TensorCUDA* targets,\n                    int imgSizeY, int numModulesY, int numModulesX, int filterSize, int paddingStart, int moduleStride, int numImgColors, int numGroups, int partialSum,\n                    float scaleTargets, float scaleOutput) {\n    _weightActs(context, images, hidActs, targets, imgSizeY, numModulesY, numModulesX, filterSize, paddingStart, moduleStride, numImgColors, numGroups, partialSum, scaleTargets, scaleOutput);\n}\n\nvoid localWeightActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* images, caffe2::TensorCUDA* hidActs, caffe2::TensorCUDA* targets,\n                     int imgSizeY, int numModulesY, int numModulesX, int filterSize, int paddingStart, int moduleStride, int numImgColors, int numGroups) {\n    _weightActs(context, images, hidActs, targets, imgSizeY, numModulesY, numModulesX, filterSize, paddingStart, moduleStride, numImgColors, numGroups, 1, 0, 1);\n}\n\nvoid localWeightActs(caffe2::CUDAContext* context, caffe2::TensorCUDA* images, caffe2::TensorCUDA* hidActs, caffe2::TensorCUDA* targets,\n                    int imgSizeY, int numModulesY, int numModulesX, int filterSize, int paddingStart, int moduleStride,\n                    int numImgColors, int numGroups, float scaleTargets, float scaleOutput) {\n    _weightActs(context, images, hidActs, targets, imgSizeY, numModulesY, numModulesX, filterSize, paddingStart, moduleStride, numImgColors, numGroups, 1, scaleTargets, scaleOutput);\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/Makefile",
    "content": "################################################################################\n#\n# Copyright 1993-2012 NVIDIA Corporation.  All rights reserved.\n#\n# NOTICE TO USER:   \n#\n# This source code is subject to NVIDIA ownership rights under U.S. and \n# international Copyright laws.  \n#\n# NVIDIA MAKES NO REPRESENTATION ABOUT THE SUITABILITY OF THIS SOURCE \n# CODE FOR ANY PURPOSE.  IT IS PROVIDED \"AS IS\" WITHOUT EXPRESS OR \n# IMPLIED WARRANTY OF ANY KIND.  NVIDIA DISCLAIMS ALL WARRANTIES WITH \n# REGARD TO THIS SOURCE CODE, INCLUDING ALL IMPLIED WARRANTIES OF \n# MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE.   \n# IN NO EVENT SHALL NVIDIA BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL, \n# OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS \n# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE \n# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE \n# OR PERFORMANCE OF THIS SOURCE CODE.  \n#\n# U.S. Government End Users.  This source code is a \"commercial item\" as \n# that term is defined at 48 C.F.R. 2.101 (OCT 1995), consisting  of \n# \"commercial computer software\" and \"commercial computer software \n# documentation\" as such terms are used in 48 C.F.R. 12.212 (SEPT 1995) \n# and is provided to the U.S. Government only as a commercial end item.  \n# Consistent with 48 C.F.R.12.212 and 48 C.F.R. 227.7202-1 through \n# 227.7202-4 (JUNE 1995), all U.S. Government End Users acquire the \n# source code with only those rights set forth herein.\n#\n################################################################################\n\n# Location of the CUDA Toolkit binaries and libraries\nCUDA_INC_PATH  = $(CUDA_INSTALL_PATH)/include\nCUDA_BIN_PATH  = $(CUDA_INSTALL_PATH)/bin\nCUDA_LIB_PATH  = $(CUDA_INSTALL_PATH)/lib64\n\n# Common binaries\nNVCC            = $(CUDA_BIN_PATH)/nvcc\nGCC             = g++\nAR\t\t\t\t= ar\n\n# CUDA code generation flags\nGENCODE_SM35    := -gencode arch=compute_35,code=sm_35\nGENCODE_FLAGS   := $(GENCODE_SM35)\n\nLDFLAGS   := -L$(CUDA_LIB_PATH) -lcudart\nCCFLAGS   := -m64\nNVCCFLAGS := -m64\n\n# Debug build flags\nifeq ($(dbg),1)\n      CCFLAGS   += -g\n      NVCCFLAGS += -g -G\n      DBG := debug\nelse\n      DBG := release\n      NVCCFLAGS += -O3\n      CCFLAGS += -O3\nendif\n\n# Add profiler output\nifeq ($(prof),1)\n\tNVCCFLAGS += --ptxas-options=-v\nendif\n\nTARGETDIR := ./bin/$(DBG)\nOBJDIR := ./obj/$(DBG)\n\n########## USER STUFF ###########\nPYTHON_VERSION=$(shell python -V 2>&1 | cut -d ' ' -f 2 | cut -d '.' -f 1,2)\nMODELNAME := _ConvNet\nLDFLAGS   += -lpthread -ljpeg -lpython$(PYTHON_VERSION) -L../util -lutilpy -L../nvmatrix -lnvmatrix -L../cudaconv3 -lcudaconv -lcublas -Wl,-rpath=./util -Wl,-rpath=./nvmatrix -Wl,-rpath=./cudaconv3 \nINCLUDES      := -I$(CUDA_INC_PATH) -I $(CUDA_SDK_PATH)/common/inc -I./include -I$(PYTHON_INCLUDE_PATH) -I$(NUMPY_INCLUDE_PATH) \n\nDEFINES := -DNUMPY_INTERFACE\n\nCUFILES\t:= $(shell find . -name \"*.cu\")\nCU_DEPS\t:= $(shell find . -name \"*.cuh\")\nCCFILES\t:= $(shell find . -name \"*.cpp\")\nC_DEPS\t:= $(shell find . -name \"*.h\")\n\nNVCCFLAGS += --compiler-options '-fPIC'\nLDFLAGS += -shared\nCCFLAGS += -fPIC\nTARGET := $(TARGETDIR)/$(MODELNAME).so\n\n################################################################################\n# Set up target and object files\n################################################################################\nOBJS +=  $(patsubst %.cpp,$(OBJDIR)/%.cpp.o,$(CCFILES))\nOBJS +=  $(patsubst %.c,$(OBJDIR)/%.c.o,$(CFILES))\nOBJS +=  $(patsubst %.cu,$(OBJDIR)/%.cu.o,$(CUFILES))\n\n# Target rules\nall: makedirs $(TARGET)\n\n$(OBJDIR)/%.cu.o : %.cu $(CU_DEPS)\n\t$(NVCC) $(DEFINES) $(NVCCFLAGS)  $(GENCODE_FLAGS) $(INCLUDES) -o $@ -c $<\n\n$(OBJDIR)/%.cpp.o : %.cpp $(C_DEPS)\n\t$(GCC) $(DEFINES) $(CCFLAGS) $(INCLUDES) -o $@ -c $<\n\n$(TARGET): $(OBJS)\n\t$(GCC) $(CCFLAGS) -o $@ $+ $(LDFLAGS) $(EXTRA_LDFLAGS)\n\tln -sf $(TARGET) .\n\nmakedirs:\n\tmkdir -p $(TARGETDIR)\n\tmkdir -p $(OBJDIR)/src\n\nclean:\n\trm -rf ./obj\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/actbroadcaster.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef ACTBROADCASTER_CUH_H_\n#define ACTBROADCASTER_CUH_H_\n\n#include <map>\n#include \"streambroadcast.cuh\"\n#include \"copypipeline.cuh\"\n\nclass BroadcastMessage {\npublic:\n    enum MESSAGE_TYPE {\n        BROADCAST,\n        EXIT\n    };\nprotected:\n    int _srcDevice;\n    std::map<int, NVMatrix*> _mats;\n    int _userIdx;\n    Queue<int>* _finishQueue;\n    MESSAGE_TYPE _type;\n    BroadcastMessage(MESSAGE_TYPE type);\npublic:\n    BroadcastMessage(std::map<int, NVMatrix*> mats, int srcDevice, int userIdx, Queue<int>& finishQueue);\n\n    int getSrcDevice();\n    std::map<int, NVMatrix*>& getMatrices();\n    int getUserIdx();\n    Queue<int>& getFinishQueue();\n    MESSAGE_TYPE getMessageType();\n};\n\nclass ExitBroadcastMessage : public BroadcastMessage {\npublic:\n    ExitBroadcastMessage();\n};\n\nclass ActBroadcaster : public Thread {\nprotected:\n    std::map<int,IBroadcastNetwork*> _broadcasters; // src device --> broadcaster\n    Queue<BroadcastMessage*> _messageQueue;\n    int _numUsers;\npublic:\n    ActBroadcaster(int numUsers, intv& cpus);\n    ~ActBroadcaster();\n    Queue<BroadcastMessage*>& getMessageQueue();\n    virtual void* run();\n    void stop();\n};\n\n\n#endif /* ACTBROADCASTER_CUH_H_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/convnet.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CONVNET3\n#define\tCONVNET3\n\n#include <vector>\n#include <string>\n#include <set>\n#include <map>\n#include <helper_cuda.h>\n#include <time.h>\n#include \"../../util/include/queue.h\"\n#include \"../../util/include/thread.h\"\n#include <math.h>\n#include \"../../util/include/sync.h\"\n#include \"messages.cuh\"\n#include \"streambroadcast.cuh\"\n\n#include \"layer.cuh\"\n#include \"data.cuh\"\n#include \"worker.cuh\"\n#include \"weights.cuh\"\n#include \"pipedispenser.cuh\"\n#include \"timer.cuh\"\n\nclass Worker;\nclass WorkResult;\nclass Layer;\nclass DataLayer;\nclass CostLayer;\nclass ConvNetThread;\nclass StreamBroadcast;\nclass Weights;\n\n// name -> device id -> layer*\ntypedef std::map<std::string,std::map<int, Layer*> > NameReplicaLayerMap;\ntypedef std::map<std::string, Layer*> NameLayerMap;\n// name -> ReplicaMap\n//typedef std::map<int,NameLayerMap> ReplicaNameLayerMap;\ntypedef std::vector<ConvNetThread*> ConvNetThreadV;\ntypedef std::vector<DataLayer*> DataLayerVector;\n//typedef std::map<int,ConvNetThreadV> ReplicaThreadsMap;\n\nclass ConvNet : public Thread {\nprivate:\n    void checkGradient_copyWeightsToGPU(Matrix& weightsCPU, Weights& weights);\nprotected:\n    NameReplicaLayerMap _layerMap;\n    DataLayerVector _dataLayers;\n    // Vector of convnet threads (one thread == one GPU)\n    ConvNetThreadV _convNetThreads;\n\n    DataProvider* _dp;\n    CPUData* _data, *_bufferData;\n    int _bufferMinibatchIdx, _bufferPassIdx;\n    ThreadSynchronizer* _sync;\n    intv _deviceIDs;\n    \n    Queue<Worker*> _workerQueue;\n    Queue<WorkResult*> _resultQueue;\n    Queue<Message*> _msgQueue;\n    \n    int _numFwdTerminal;\n    std::map<int, int> _numBwdTerminal; // pass idx -> #terminal\n    int _totalPassesDone;\n    int _numReplicasMin, _numReplicasMax;\n    // For gradient checking\n    int _numFailures;\n    int _numTests;\n\n    // Training progress (between 0 and 1).\n    // Used to determine learning rate based on ParameterSchedule.\n    double _trainingProgress;\n    double _baseErr;\n    bool _conserveMem;\n    PipeDispenser *_dataCopyPD;\n\n    void waitForTerminals(int numMsgs, MESSAGES msg);\n    void sendMessage(MESSAGES msg, bool sync);\n    void sendMessage(Message* msg, bool sync);\n    void findBwdTerminal(Layer& l, std::set<Layer*>& visited, int& terminal, int passIdx);\n    void connectReplicas();\n    void initDataLayers(PyObjectV* layerList);\n    void initGPUThreads(PyObjectV* layerList);\n    void connectChildren(PyObject* layerParams);\n    void* run();\n    void setData(CPUData& data, int passIdx);\n    void setDataFromBuffer();\n    void setBuffer(CPUData* bufferData, int bufferMinibatchIdx, int bufferPassIdx);\npublic:\n    ConvNet(PyObject* layerParams, intv& deviceIDs,\n            int minibatchSize, bool conserveMem);\n    ~ConvNet();\n    void stop();\n    \n    Queue<Message*>& getMessageQueue();\n    Queue<Worker*>& getWorkerQueue();\n    Queue<WorkResult*>& getResultQueue();\n    DataProvider& getDataProvider();\n    \n    Layer& getLayer(std::string& name, int replicaID);\n    void copyToCPU();\n    void copyToGPU();\n    void updateWeights(int passIdx);\n    void reset(int passIdx);\n    void reset();\n\n    void bprop(int passIdx, PASS_TYPE passType);\n    void fprop(int miniIdx, int passIdx, PASS_TYPE passType);\n    void fprop(CPUData& data, int passIdx, PASS_TYPE passType);\n\n    void setTrainingProgress(double progress);\n    double getTrainingProgress() const;\n\n    bool checkGradient(const std::string& name, float eps, Weights& weights); \n    void checkGradients();\n    Cost& getCost();\n    Cost& getCost(Cost& cost);\n    CPUData& getData(); // Returns last minibatch fpropped\n    double getCostValue();\n    intv& getDeviceIDs();\n    ThreadSynchronizer& getSync();\n    void syncWithChildren();\n    int getMinibatchSize();\n    bool isConserveMemory();\n    int getNumReplicasMax();\n    int getNumReplicasMin();\n    int getNumPasses();\n    int getTotalPassesDone();\n    PipeDispenser& getDataCopyPD();\n};\n\nclass ConvNetThread : public Thread {\nprotected:\n    NameLayerMap _nameLayerMap;\n    std::vector<CostLayer*> _costs;\n    ConvNet* _convNet;\n    int _deviceID;\n    Queue<Message*> _msgQueue;\n    Timer _timer;\n//    StreamBroadcast* _weightSynchronizer;\n    \n    void initCuda();\n    virtual void initLayer(PyObject* paramsDict, int replicaID);\n    void* run();\npublic:\n    ConvNetThread(PyObjectV* layerList, int deviceID, int deviceIdx, ConvNet* convNet);\n    ~ConvNetThread();\n    \n    NameLayerMap& getLayerMap();\n    int getDeviceID();\n    \n    ConvNet& getConvNet();\n    \n    Queue<Message*>& getMessageQueue();\n    std::vector<CostLayer*>& getCostLayers();\n//    StreamBroadcast& getWeightSynchronizer();\n    \n    Cost& getCost();\n    Layer& getLayer(std::string& name);\n    void startTimer();\n    double stopTimer();\n};\n\n#endif\t/* CONVNET */\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/copypipeline.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef COPYPIPELINE_CUH_\n#define COPYPIPELINE_CUH_\n\n#include <set>\n#include \"../../util/include/thread.h\"\n#include \"../../util/include/queue.h\"\n#include <helper_cuda.h>\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n#include \"util.cuh\"\n\n#define COPY_MIN_CHUNK_SIZE                 (1<<18) // 256k\n#define COPY_MAX_CHUNKS                     16\n#define COPY_MIN_CHUNKS                     2\n\nclass CopyPeer;\nclass CopySource;\nclass ICopySegment;\nclass IBroadcastNetwork;\n\nclass CopyMessage {\nprotected:\n    std::map<int,NVMatrix*>* _mats;\n    float _scaleSource, _scaleTargets;\npublic:\n    enum COPY_MESSAGE_TYPE {\n        COPY_CHUNK,\n        COPY_START,\n        EXIT\n    };\n    CopyMessage(COPY_MESSAGE_TYPE msgType, float scaleSource, float scaleTargets, std::map<int, NVMatrix*>& mats)\n        : _msgType(msgType), _scaleSource(scaleSource), _scaleTargets(scaleTargets), _mats(&mats) {\n    }\n    CopyMessage(COPY_MESSAGE_TYPE msgType)\n        : _msgType(msgType), _scaleSource(0), _scaleTargets(0), _mats(NULL) {\n    }\n    inline COPY_MESSAGE_TYPE getType() const {\n        return _msgType;\n    }\n    inline NVMatrix& getMatrix(int deviceID) const {\n        return *_mats->at(deviceID);\n    }\n    inline std::map<int,NVMatrix*>& getMatrices() const {\n        return *_mats;\n    }\n    inline float getScaleSource() const {\n        return _scaleSource;\n    }\n    inline float getScaleTargets() const {\n        return _scaleTargets;\n    }\nprotected:\n    COPY_MESSAGE_TYPE _msgType;\n};\n\nclass CopyChunkMessage : public CopyMessage {\nprotected:\n    int _chunkIdx;\n    int _chunkSize;\n    int _numChunks;\npublic:\n    CopyChunkMessage(int chunkIdx, int chunkSize, int numChunks, float scaleSource, float scaleTargets, std::map<int, NVMatrix*>& mats)\n        : _chunkIdx(chunkIdx), _chunkSize(chunkSize), _numChunks(numChunks), CopyMessage(COPY_CHUNK, scaleSource, scaleTargets, mats) {\n    }\n\n    inline int getChunkIdx() const {\n        return _chunkIdx;\n    }\n    inline int getChunkSize() const {\n        return _chunkSize;\n    }\n    inline int getNumChunks() const {\n        return _numChunks;\n    }\n};\n\nclass CopyStartMessage : public CopyMessage {\npublic:\n    CopyStartMessage(float scaleSource, float scaleTargets, std::map<int,NVMatrix*>& mats) : CopyMessage(COPY_START, scaleSource, scaleTargets, mats) {\n    }\n};\n\nclass ICopySegment : public Thread {\nprotected:\n    int _deviceID, _execDeviceID;\n    cudaStream_t _stream;\n    ICopySegment* _prev;\n    std::vector<CopyPeer*> _next;\n    Queue<CopyMessage*> _queue;\n    Queue<int>* _finishQueue;\n    HostNVMatrix _hmat;\n    IBroadcastNetwork* _parent;\n\n    NVMatrix& getChunk(NVMatrix& mat, int chunkSize, int chunkIdx);\n    void* run();\n    virtual bool processMessage(CopyMessage& msg) = 0;\n\npublic:\n    ICopySegment(IBroadcastNetwork& parent, int deviceID, Queue<int>* finishQueue);\n    virtual ~ICopySegment();\n    inline NVMatrix& getMatrix(CopyMessage& msg);\n    Queue<CopyMessage*>& getQueue();\n    inline int getDeviceID();\n    void addPrev(ICopySegment& c);\n    void addNext(CopyPeer& c);\n    bool isTerminal() const;\n    virtual bool isSource() const = 0;\n};\n\nclass CopySource : public ICopySegment {\nprotected:\n    bool processMessage(CopyMessage& msg);\npublic:\n    CopySource(IBroadcastNetwork& parent, int deviceID);\n    inline bool isSource() const;\n};\n\nclass CopyPeer : public ICopySegment {\nprotected:\n    bool processMessage(CopyMessage& msg);\npublic:\n    CopyPeer(IBroadcastNetwork& parent, int deviceID, Queue<int>* finishQueue);\n    inline bool isSource() const;\n};\n\nclass IBroadcastNetwork {\nprotected:\n    Queue<int> _finishQueue;\n    CopySource* _src;\n    std::vector<CopyPeer*> _peers;\n    int _srcDeviceID, _numTerminal;\n    bool _constructed;\n    std::set<int> _devices;\n    std::pair<std::vector<int>,std::vector<int> > makeGPULists();\n\n    void makePeers(std::pair<std::vector<int>,std::vector<int> >& gpus);\n    virtual void makeConnections() = 0;\n    virtual void _broadcast(std::map<int, NVMatrix*>& mats, float scaleSource, float scaleTargets);\n    IBroadcastNetwork(std::set<int>& devices, int srcDeviceID, int numTerminal);\npublic:\n    virtual IBroadcastNetwork& construct();\n    virtual ~IBroadcastNetwork();\n\n    virtual void broadcast(std::map<int, NVMatrix*>& mats);\n    int getSourceDeviceID() const;\n    static IBroadcastNetwork& make(std::set<int> devices, int srcDeviceID);\n};\n\nclass ISafeBroadcastNetwork : public IBroadcastNetwork {\nprotected:\n    ISafeBroadcastNetwork(std::set<int>& devices, int srcDeviceID, int numTerminal);\npublic:\n    virtual void broadcast(std::map<int, NVMatrix*>& mats, float scaleSource, float scaleTargets);\n    virtual ISafeBroadcastNetwork& construct();\n    static ISafeBroadcastNetwork& make(std::set<int> devices, int srcDeviceID);\n};\n\nclass NullBroadcaster : public ISafeBroadcastNetwork {\nprotected:\n    NullBroadcaster(std::set<int>& devices, int srcDeviceID);\n    void makeConnections();\npublic:\n    NullBroadcaster& construct();\n    void broadcast(std::map<int, NVMatrix*>& mats, float scaleSource, float scaleTargets);\n    void broadcast(std::map<int, NVMatrix*>& mats);\n    friend class IBroadcastNetwork;\n    friend class ISafeBroadcastNetwork;\n};\n\n/*\n * This one goes to host and then to targets.\n */\nclass NaiveBroadcaster : public ISafeBroadcastNetwork {\nprotected:\n    NaiveBroadcaster(std::set<int>& devices, int srcDeviceID);\n    void makeConnections();\n    friend class IBroadcastNetwork;\n    friend class ISafeBroadcastNetwork;\n};\n\nclass EightGPUBroadcaster1 : public IBroadcastNetwork {\nprotected:\n    EightGPUBroadcaster1(std::set<int>& devices, int srcDeviceID);\n    void makeConnections();\n    friend class IBroadcastNetwork;\n};\n\nclass TwoPeeringGPUsBroadcaster : public ISafeBroadcastNetwork {\nprotected:\n    int _tgtDeviceID;\n    cudaStream_t _tgtStream;\n    void makeConnections();\n    void resetDeviceID(int d);\n    void _broadcast(std::map<int, NVMatrix*>& mats, float scaleSource, float scaleTargets);\npublic:\n    TwoPeeringGPUsBroadcaster(std::set<int>& devices, int srcDeviceID);\n    ~TwoPeeringGPUsBroadcaster();\n    ISafeBroadcastNetwork& construct();\n    friend class IBroadcastNetwork;\n    friend class ISafeBroadcastNetwork;\n};\n\n#endif /* COPYPIPELINE_CUH_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/cost.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef COST_CUH\n#define\tCOST_CUH\n\n#include <vector>\n#include <map>\n#include <helper_cuda.h>\n\n#include \"layer.cuh\"\n#include \"util.cuh\"\n\nclass CostLayer;\n\n/*\n * Wrapper for dictionary mapping cost name to vector of returned values.\n */\nclass Cost {\nprotected:\n    std::map<std::string,int> _numCases;\n    CostMap _costMap;\n    CostCoeffMap _costCoeffMap;\n    std::map<std::string,int>& getNumCasesMap();\npublic:\n    Cost();\n    Cost(std::vector<CostLayer*>& costs);\n    doublev& operator [](const std::string s);\n    CostMap& getCostMap();\n    CostCoeffMap& getCostCoeffMap();\n    int getNumCases();\n    /*\n     * Returns sum of first values returned by all the CostLayers, weighted by the cost coefficients.\n     */\n    double getValue();\n    Cost& operator += (Cost& er);\n    virtual ~Cost();\n    void print();\n};\n\n\n#endif\t/* COST_CUH */\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/data.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef DATA_CUH\n#define\tDATA_CUH\n\n#include <vector>\n#include <algorithm>\n#include \"util.cuh\"\n\nclass CPUData {\nprotected:\n    MatrixV* _data;\n    void assertDimensions() {\n        assert(_data->size() > 0);\n        for (int i = 1; i < _data->size(); i++) {\n            assert(_data->at(i-1)->getNumCols() == _data->at(i)->getNumCols());\n            if (_data->at(i-1)->isTrans() != _data->at(i)->isTrans() && _data->at(i)->getNumElements() < 2) {\n                _data->at(i)->setTrans(_data->at(i-1)->isTrans());\n            }\n            assert(_data->at(i-1)->isTrans() == _data->at(i)->isTrans());\n        }\n        assert(_data->at(0)->getNumCols() > 0);\n    }\npublic:\n    typedef typename MatrixV::iterator T_iter;\n    // Cases in columns, but array may be transposed\n    // (so in memory they can really be in rows -- in which case the array is transposed\n    //  during the copy to GPU).\n    CPUData(PyObject* pyData) {\n        _data = getMatrixV(pyData);\n        assertDimensions();\n    }\n    \n    CPUData(MatrixV* data) : _data(data) {\n        assertDimensions();\n    }\n\n    ~CPUData() {\n        for (T_iter it = _data->begin(); it != _data->end(); ++it) {\n            delete *it;\n        }\n        delete _data;\n    }\n    \n    Matrix& operator [](int idx) const {\n        return *_data->at(idx);\n    }\n    \n    int getSize() const {\n        return _data->size();\n    }\n    \n    MatrixV& getData() const {\n        return *_data;\n    }\n    \n    Matrix& getData(int i) const {\n        return *_data->at(i);\n    }\n    \n    bool isTrans() const {\n        return _data->at(0)->isTrans();\n    }\n\n    int getNumCases() const {\n        return _data->at(0)->getNumCols();\n    }\n};\n\nclass DataProvider {\nprotected:\n    CPUData* _hData;\n    NVMatrixV _data;\n    int _minibatchSize;\npublic:\n    DataProvider(int minibatchSize);\n    void setData(CPUData&);\n    void clearData();\n    CPUData& getMinibatch(int idx);\n    CPUData& getDataSlice(int startCase, int endCase);\n    int getNumMinibatches();\n    int getMinibatchSize();\n    int getNumCases();\n};\n\n#endif\t/* DATA_CUH */\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/gradreducer.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef GRADREDUCER_CUH_\n#define GRADREDUCER_CUH_\n\n#include <set>\n#include <algorithm>\n#include \"streambroadcast.cuh\"\n#include \"reducepipeline.cuh\"\n#include \"layer.cuh\"\n#include \"util.cuh\"\n\nclass StreamBroadcast;\nclass Layer;\n\n#define ACT_GRAD_REDUCER_EXIT       (1 << 16)\n\n//class ReduceMessage {\n//    ReduceMessage();\n//    ReduceMessage(bool exit);\n//};\n\nclass IActGradReducer : public Thread {\nprotected:\n    Layer* _parent;\n    Queue<int> _finishQueue;\n    int _numExpectedMsgsTotal;\n    std::map<int,int> _numExpectedMsgs; // map from device id -> num expected msgs\n\n    void* run();\n    virtual bool reduce() = 0;\n    virtual void reset() = 0;\npublic:\n    IActGradReducer(Layer& parent, std::map<int, int> numExpectedMsgs);\n    virtual ~IActGradReducer();\n    int waitForFinish();\n    virtual void enqueueReduction(int deviceID) = 0;\n    virtual void stop() = 0;\n    static IActGradReducer& makeGradReducer(Layer& parent, std::map<int, int> numExpectedMsgs);\n};\n\nclass SequentialActGradReducer : public IActGradReducer {\nprotected:\n\n    std::map<int,int> _numReceivedMsgs; // map from device id -> num received msgs\n\n    std::map<int,Queue<int>* > _messageQueues;\n    intv _deviceIDs;\n    StreamBroadcast* _broadcaster;\n    bool reduce();\n    void reset();\npublic:\n    SequentialActGradReducer(Layer& parent, std::map<int, int> numExpectedMsgs);\n    ~SequentialActGradReducer();\n    void enqueueReduction(int deviceID);\n    void stop();\n};\n\nclass ParallelActGradReducer : public IActGradReducer {\nprotected:\n    IEightGPUReducer* _reducer;\n    int _numReceivedMsgs;\n    float _scaleTarget;\n    Queue<int> _messageQueue;\n    bool reduce();\n    void reset();\npublic:\n    ParallelActGradReducer(Layer& parent, std::map<int, int> numExpectedMsgs);\n    void enqueueReduction(int deviceID);\n    void stop();\n};\n\n\n#endif /* GRADREDUCER_CUH_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/jpeg.h",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef JPEG_MAIN_H\n#define JPEG_MAIN_H\n\n#include <cstdio>\n#include <cstdlib>\n#include <Python.h>\n#include <vector>\n#include <string>\n#include <iostream>\n#include <jpeglib.h>\n//#include <arrayobject.h>\n#include \"../../util/include/thread.h\"\n#include \"../../util/include/matrix.h\"\n\n#ifndef DIVUP\n#define DIVUP(x, y) (((x) + (y) - 1) / (y))\n#endif\n\n#define NUM_JPEG_DECODER_THREADS        4\n\n\nclass DecoderThread : public Thread {\n protected:\n    PyObject* _pyList;\n    Matrix* _target;\n    int64 _start_img, _end_img;\n    int64 _img_size, _inner_size, _inner_pixels;\n    bool _test, _multiview;\n\n    unsigned char* _decodeTarget;\n    int64 _decodeTargetSize;\n    unsigned int _rseed;\n\n    void* run();\n    void decodeJpeg(int idx, int& width, int& height);\n    double randUniform();\n    double randUniform(double min, double max);\n    void crop(int64 i, int64 width, int64 height, bool flip);\n    virtual void crop(int64 i, int64 src_width, int64 src_height, bool flip, int64 crop_start_x, int64 crop_start_y);\n public:\n    DecoderThread(PyObject* pyList, Matrix& target, int start_img, int end_img, int img_size, int inner_size, bool test, bool multiview);\n    virtual ~DecoderThread();\n};\n\n#endif // JPEG_MAIN_H\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/layer.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef LAYER_CUH\n#define    LAYER_CUH\n\n#include <algorithm>\n#include <string>\n#include <vector>\n#include <map>\n#include <assert.h>\n#include <helper_timer.h>\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n//#include \"experimental/akrizhevsky/g3/mactruck-gpu-tests/gpu_util.cuh\"\n\n#include \"weights.cuh\"\n#include \"convnet.cuh\"\n#include \"cost.cuh\"\n#include \"neuron.cuh\"\n#include \"data.cuh\"\n#include \"layer_kernels.cuh\"\n#include \"streambroadcast.cuh\"\n#include \"actbroadcaster.cuh\"\n#include \"gradreducer.cuh\"\n#include \"util.cuh\"\n#include \"timer.cuh\"\n#include \"memorysource.cuh\"\n\nclass Cost;\nclass ConvNet;\nclass ConvNetThread;\nclass CostLayer;\nclass DataLayer;\nclass Layer;\nclass ActBroadcaster;\nclass BroadcastMessage;\nclass IActGradReducer;\nclass Weights;\nclass WeightList;\ntypedef std::vector<Layer*> LayerV;\n\nclass BinomialCrossEntOperator {\nprotected:\n    float _posWeight;\npublic:\n    BinomialCrossEntOperator(float posWeight) : _posWeight(posWeight) {\n    }\n    __device__ inline float operator()(const float t, const float y) const {\n        return _posWeight * t * safelog(y) + (1.0f - t) * safelog(1.0f - y);\n    }\n};\n\nclass CrossEntOperator {\nprotected:\n    float _posWeight;\npublic:\n    CrossEntOperator(float posWeight) : _posWeight(posWeight) {\n    }\n    __device__ inline float operator()(const float t, const float y) const {\n        return _posWeight * t * safelog(y);\n    }\n};\n\n/*\n * Abstract layer.\n */\nclass Layer {\nprotected:\n    ConvNetThread* _convNetThread;\n\n    // This is a vector[#layers_next]\n    std::vector<Layer*> _next;\n    // This is a vector[#replicas_prev][#layers_prev]\n    std::map<int, std::vector<Layer*> > _prev;\n\n    int _rcvdFInputMsgs;\n    std::map<int, int> _numComputedActsGrads;\n    int _rcvdBInputMsgs;\n    int _numOutputs;\n    std::map<int, NVMatrix*> _inputs;                // input idx -> matrix\n    std::map<int, MemoryView*> _memSrcActs;        // device id -> memory source\n    std::map<int, MemoryView*> _memSrcActsGrad;    // device id -> memory source\n\n    bool _gradConsumer, _foundGradConsumers, _trans;\n    std::map<int,bool> _bwdTerminal; // One bool per pass\n    int _numGradProducersNext;\n    int _actsTarget, _actsGradTarget;\n    std::string _name, _type;\n    intv _nextDeviceIDs, _prevDeviceIDs;\n    HostNVMatrix _hostMemFwd;\n\n    // New replica-related stuff:\n    std::map<int,Layer*> _replicas; // NOTE: a layer is its own sibling, too\n    // Previous layers sorted by device ID, in reverse order in which they are procesed by\n    // sequential grad reducer. map from replica -> device id -> layers\n    std::map<int,std::map<int,std::set<Layer*> > > _prevByDevice;\n    std::map<std::string, int> _inputIndices;\n    int _replicaID;\n    int _numReplicas;\n    int _numReplicasPrev, _numReplicasNext;\n\n    Queue<int> _broadcastFinishQueue;\n    Queue<int> _reductionFinishQueue;\n    ActBroadcaster* _actBroadcaster;\n    IActGradReducer* _gradReducer;\n    Timer _timer;\n    bool _initialized;\n\n    virtual void fpropNext(PASS_TYPE passType, int passIdx);\n    virtual void truncBwdActs(); \n    virtual void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) = 0;\n    \n    virtual void bpropCommon(NVMatrix& v, int replicaIdx, PASS_TYPE passType) {\n        // Do nothing by default\n    }\n    virtual void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n        assert(!isGradProducer()); // Only do nothing if not grad producer\n    }\n    virtual void fpropCommon(PASS_TYPE passType) {\n\n    }\n    void bpropActsCall(NVMatrix& v, PASS_TYPE passType, int replicaIdx, int inputIdx);\n\n    ActBroadcaster& getActBroadcaster();\n    IActGradReducer& getGradReducer();\n    int getInputIdx(std::string& parentName);\n    void setInputIdx(std::string& parentName, int idx);\n\npublic:\n    static bool _saveActsGrad, _saveActs;\n    \n    Layer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool trans);\n    virtual ~Layer();\n    \n    virtual bool fprop(PASS_TYPE passType, int passIdx);\n    void fprop(NVMatrix& v, int inpIdx, PASS_TYPE passType, int passIdx);\n    virtual void fprop(std::map<int,NVMatrix*>& v, PASS_TYPE passType, int passIdx);\n    virtual void bprop(PASS_TYPE passType, int passIdx);\n    virtual void bprop(NVMatrix& v, PASS_TYPE passType, int passIdx);\n    virtual void reset();\n    virtual void resetPassIdx();\n    int getNumCases(NVMatrix& v);\n    int& getNumComputedActsGrads(int deviceID);\n    int incRcvdBInputMsgs();\n    bool isGradConsumer();\n    bool hasGradProducerNext(std::string& layerName);\n    // Does this layer produce a gradient for any layer?\n    virtual bool isGradProducer();\n    // Does this layer produce a gradient for layer of given name?\n    virtual bool isGradProducer(std::string& layerName);\n    std::string& getName();\n    std::string& getType();\n    virtual void addNext(Layer& l);\n    virtual void addPrev(Layer& l, int replicaIdx);\n    virtual void addReplica(Layer& l);\n    std::map<int,std::vector<Layer*> >& getPrev();\n    std::vector<Layer*>& getNext();\n    virtual NVMatrix& getActs();\n    virtual NVMatrix& getActs(int deviceID);\n    virtual NVMatrix& getActs(int deviceID, int numCases);\n    virtual NVMatrix& getActsGrad();\n    virtual NVMatrix& getActsGrad(int deviceID);\n    virtual std::map<int,NVMatrix*> getAllActs();\n    virtual std::map<int, NVMatrix*> getAllActsGrads();\n    virtual bool postInit();\n    int getDeviceID();\n    ConvNetThread& getConvNetThread();\n    cudaStream_t getStream();\n    void syncStream();\n    void setBwdTerminal(int passIdx);\n    // Do nothing if this layer has no weights\n    virtual bool updateWeights() {\n        return false;\n    }\n    virtual bool constrainWeights() {\n        return false;\n    }\n    virtual void checkGradient() {\n    }\n    virtual void copyToCPU() {\n    }\n    virtual void copyToGPU()  {\n    }\n    intv& getNextDeviceIDs() {\n        return _nextDeviceIDs;\n    }\n\n    int getReplicaID();\n    int getNumReplicas();\n    int getNumSiblingReplicas();\n    int getNumReplicasPrev();\n    int getNumReplicasNext();\n    int getNumOutputs();\n    void setMemorySourceActs(int deviceID, MemoryView& mem);\n    void setMemorySourceActsGrad(int deviceID, MemoryView& mem);\n    MemoryView& getMemorySourceActs(int deviceID);\n    MemoryView& getMemorySourceActsGrad(int deviceID);\n    int getFwdActiveInputReplicaIdx(int passIdx);\n    int getBwdActiveInputReplicaIdx(int passIdx);\n    int getFwdActiveReplicaIdx(int passIdx);\n    int getNumLayersPrev();\n    virtual int getNumInputReplicas();\n    int getNumExpectedBwdMsgs();\n    int getNumExpectedFwdMsgs();\n    int getReplicaIdx();\n    int getActivePassPeriod();\n    int getNumGradProducersNext();\n    virtual ConvNet& getConvNet();\n};\n\nclass TwoDLayerInterface {\nprotected:\n    int _channels, _imgSize, _imgPixels;\npublic:\n    TwoDLayerInterface(PyObject* paramsDict);\n};\n\nclass NeuronLayer : public Layer {\nprotected:\n    Neuron* _neuron;\n    std::string _neuronType;\n    \n    virtual void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    virtual void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n    virtual bool bpropSpecial(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    class CrossEntLogisticGradientOperator {\n    private:\n        float _coeff, _posWeight;\n    public:\n        CrossEntLogisticGradientOperator(float coeff, float posWeight) : _coeff(coeff), _posWeight(posWeight) {\n        }\n        __device__ inline float operator()(const float y, const float t) const {\n            return _coeff * (_posWeight * t * (1.0f - y) + (t - 1.0f) * y);\n        }\n    };\n    NeuronLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n    ~NeuronLayer();\n    std::string& getNeuronType();\n};\n\nclass WeightLayer : public Layer {\nprotected:\n    WeightList* _weights;\n    Weights *_biases;\n    NVMatrix _norm2;\n    float _wStep, _bStep;\n    int _weightUpdatePassPeriod;\n    void fpropCommon(PASS_TYPE passType);\n    void bpropCommon(NVMatrix& v, int replicaIdx, PASS_TYPE passType);\n    virtual void bpropBiases(NVMatrix& v, PASS_TYPE passType) = 0;\n    virtual void bpropWeights(NVMatrix& v, int replicaIdx, int inpIdx, PASS_TYPE passType) = 0;\n    virtual void _constrainWeights();\n    virtual float getGradScale(int inpIdx, PASS_TYPE passType);\n    virtual float getIncScale(int inpIdx, PASS_TYPE passType);\n    virtual float getBGradScale(PASS_TYPE passType);\n    virtual float getBIncScale();\n    virtual NVMatrix& getGradTarget(int inpIdx);\n    NVMatrix& getWeightMatrix(PASS_TYPE passType, int inpIdx);\n    NVMatrix& getBiasMatrix(PASS_TYPE passType);\npublic:\n    WeightLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool trans, bool useGrad);\n    virtual ~WeightLayer();\n    virtual bool updateWeights();\n    virtual bool constrainWeights();\n    virtual void copyToCPU();\n    virtual void copyToGPU();\n    virtual void checkGradient();\n    Weights& getWeights(int idx);\n    void addReplica(Layer& l);\n    virtual bool postInit();\n};\n\nclass FCLayer : public WeightLayer {\nprotected:\n    virtual void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    virtual void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n    virtual void bpropBiases(NVMatrix& v, PASS_TYPE passType);\n    virtual void bpropWeights(NVMatrix& v, int replicaIdx, int inpIdx, PASS_TYPE passType);\n    virtual void _constrainWeights();\npublic:\n    FCLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool useGrad);\n    FCLayer();\n};\n\nclass SplitFCLayer : public FCLayer {\nprotected:\n    int _numParts;\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n//    void bpropBiases(NVMatrix& v, PASS_TYPE passType);\n    void bpropWeights(NVMatrix& v, int replicaIdx, int inpIdx, PASS_TYPE passType);\n    void splitWeights();\npublic:\n    SplitFCLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool useGrad);\n};\n\nclass SoftmaxLayer : public Layer {\nprotected:\n    bool _doUpperGrad;\n    NVMatrix _max, _sum;\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    SoftmaxLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n    void setDoUpperGrad(bool b);\n};\n\nclass ConcatenationLayer : public Layer {\nprotected:\n    intv* _copyOffsets;\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    ConcatenationLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n    virtual ~ConcatenationLayer();\n};\n\nclass PassThroughLayer : public Layer {\nprotected:\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    PassThroughLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n    virtual bool postInit();\n};\n\nclass EltwiseSumLayer : public Layer {\nprotected:\n    floatv* _coeffs;\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    EltwiseSumLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n    ~EltwiseSumLayer();\n};\n\nclass EltwiseMaxLayer : public Layer {\nprotected:\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    EltwiseMaxLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\nclass SumLayer : public Layer {\nprotected:\n    int _stride;\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    SumLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\nclass DataCopyMessage {\npublic:\n    enum MESSAGE_TYPE {\n        COPY,\n        EXIT\n    };\nprotected:\n    CPUData* _cpuData;\n    int _passIdx;\n    bool _other;\n    DataCopyMessage::MESSAGE_TYPE _type;\n    DataCopyMessage(DataCopyMessage::MESSAGE_TYPE type) : _cpuData(NULL), _other(false), _passIdx(0), _type(type) {\n    }\npublic:\n    DataCopyMessage(CPUData& cpuData, bool other, int passIdx) : _cpuData(&cpuData), _other(other), _passIdx(passIdx), _type(DataCopyMessage::COPY) {\n    }\n    \n    CPUData& getData() const {\n        return *_cpuData;\n    }\n    \n    int getPassIdx() const {\n        return _passIdx;\n    }\n    \n    bool isOther() const {\n        return _other;\n    }\n\n    DataCopyMessage::MESSAGE_TYPE getType() {\n        return _type;\n    }\n};\n\nclass DataCopyExitMessage : public DataCopyMessage {\npublic:\n    DataCopyExitMessage() : DataCopyMessage(DataCopyMessage::EXIT) {\n    }\n};\n\nclass DataCopyThread;\n\nclass DataLayer : public Layer {\nprotected:\n    bool _useBuffer;\n    int _dataIdx;\n    ConvNet* _convNet;\n//    std::map<int, NVMatrix*> _outputs2; // Buffer for copying data during computation\n    std::map<int, MemoryView*> _memSrcActs2;        // // Buffer for copying data during computation\n    std::map<int, cudaStream_t> _copyStreams;\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    Queue<int> _copyFinishQueue;\n    DataCopyThread* _copier;\n    bool _outstandingCopyRequest;\n    int _start, _end;\n    \npublic:\n    void fprop(PASS_TYPE passType, int passIdx, bool fromBuffer);\n    DataLayer(ConvNet* convNet, PyObject* paramsDict, int replicaID);\n    ~DataLayer();\n    NVMatrix& getActs(int deviceID);\n//    NVMatrix& getActs(int deviceID, bool other);\n    NVMatrix& getActs(int deviceID, bool other, int numCases);\n    bool isGradProducer();\n    void toggleBuffer(int passIdx);\n    void copyData(CPUData& data, bool other, int passIdx);\n    bool postInit();\n    ConvNet& getConvNet();\n    int getNumInputReplicas();\n    cudaStream_t getCopyStream(int deviceID);\n    Queue<int>& getCopyFinishQueue() {\n        return _copyFinishQueue;\n    }\n    void waitForCopyFinish();\n    int getDataIdx() const {\n        return _dataIdx;\n    }\n    int getStart() const {\n        return _start;\n    }\n    int getEnd() const {\n        return _end;\n    }\n};\n\n\nclass DataCopyThread : public Thread {\nprotected:\n    DataLayer* _parent;\n    Queue<DataCopyMessage*> _queue;\n    HostNVMatrix _hostMemFwd;\n    Timer _requestTimer;\n    int _sleepUsec;\n    virtual void* run();\n    \npublic:\n    DataCopyThread(DataLayer& parent, intv& cpus);\n    Queue<DataCopyMessage*>& getQueue();\n    void stop();\n};\n\n\nclass LocalLayer : public WeightLayer {\nprotected:\n    intv* _padding, *_stride, *_filterSize, *_channels, *_imgSize, *_groups;\n    intv* _imgPixels, *_filterPixels, *_filterChannels;\n    int _modulesX, _modules, _numFilters;\n    \npublic:\n    LocalLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool useGrad);\n    virtual ~LocalLayer();\n};\n\nclass ConvLayer : public LocalLayer {\nprotected:\n    int _sumWidth;\n    bool _sharedBiases;\n    floatv* _weightContrastNormMin, *_weightContrastNormMax;\n    NVMatrix _weightGradTmp;\n\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n    void bpropBiases(NVMatrix& v, PASS_TYPE passType);\n    void bpropWeights(NVMatrix& v, int replicaIdx, int inpIdx, PASS_TYPE passType);\n    void truncBwdActs();\n    void _constrainWeights();\n\npublic:\n    ConvLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n    virtual ~ConvLayer();\n}; \n\nclass LocalUnsharedLayer : public LocalLayer {\nprotected:\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n    void bpropBiases(NVMatrix& v, PASS_TYPE passType);\n    void bpropWeights(NVMatrix& v, int replicaIdx, int inpIdx, PASS_TYPE passType);\n    void _constrainWeights();\npublic:\n    LocalUnsharedLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n}; \n\nclass PoolLayer : public Layer, public TwoDLayerInterface {\nprotected:\n    int _sizeX, _start, _stride, _outputsX;\n    std::string _pool;\npublic:\n    PoolLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool trans);\n    \n    static PoolLayer& make(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n}; \n\nclass AvgPoolLayer : public PoolLayer {\nprotected:\n    bool _sum;\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    AvgPoolLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n}; \n\nclass MaxPoolLayer : public PoolLayer {\nprotected:\n    bool _abs;\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    MaxPoolLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool abs);\n};\n\nclass CrossMapPoolLayer : public Layer, public TwoDLayerInterface {\nprotected:\n    int _size, _start, _stride, _outputs;\n    std::string _pool;\npublic:\n    CrossMapPoolLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool trans);\n\n    static CrossMapPoolLayer& make(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\nclass CrossMapMaxPoolLayer : public CrossMapPoolLayer {\nprotected:\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    CrossMapMaxPoolLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\nclass RandomScaleLayer : public Layer, public TwoDLayerInterface {\nprotected:\n    int _tgtSize, _minScaledSize;\n    float _maxScale; // should be >= 1\n    NVMatrix _rescaledActs;\n    std::vector<double> _scaleProbs;\npublic:\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n    \n    RandomScaleLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\nclass CropLayer : public Layer, public TwoDLayerInterface {\nprotected:\n    int _tgtSize, _startX, _startY;\npublic:\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n\n    CropLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\nclass NailbedLayer : public Layer, public TwoDLayerInterface {\nprotected:\n    int _start, _stride, _outputsX;\npublic:\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n    \n    NailbedLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\nclass GaussianBlurLayer : public Layer, public TwoDLayerInterface {\nprotected:\n    Matrix* _hFilter;\n    NVMatrix _filter;\n    NVMatrix _actGradsTmp;\npublic:\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n    void copyToGPU();\n    \n    GaussianBlurLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n    ~GaussianBlurLayer();\n};\n\nclass HorizontalReflectionLayer : public Layer, public TwoDLayerInterface {\nprotected:\npublic:\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n    \n    HorizontalReflectionLayer(ConvNetThread* convNet, PyObject* paramsDict, int replicaID);\n};\n\nclass ResizeLayer : public Layer, public TwoDLayerInterface {\nprotected:\n    float _scale;\n    int _tgtSize;\npublic:\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n\n    ResizeLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\nclass DropoutLayer : public Layer {\nprotected:\n    bool _enable;\n    float _keep;\n    NVMatrix _keepMask;\npublic:\n    virtual void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    virtual void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n    void truncBwdActs();\n    DropoutLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n    class DropoutSmallerThanOperator {\n    private:\n        float _keep, _scale;\n    public:\n        DropoutSmallerThanOperator(float keep) : _keep(keep), _scale(1.0f/keep) {\n        }\n        __device__ inline float operator()(const float x) const {\n            return (x < _keep) * _scale;\n        }\n    };\n};\n\nclass Dropout2Layer : public DropoutLayer {\nprotected:\npublic:\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n    Dropout2Layer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\nclass RGBToYUVLayer : public Layer {\npublic:\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n\n    RGBToYUVLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\nclass RGBToLABLayer : public Layer {\nprotected:\n    bool _center;\npublic:\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n\n    RGBToLABLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\nclass ResponseNormLayer : public Layer, public TwoDLayerInterface {\nprotected:\n    int _size;\n    float _scale, _pow;\n    float _minDiv;\n    NVMatrix _denoms;\n\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n    void truncBwdActs();\npublic:\n    ResponseNormLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n}; \n\nclass CrossMapResponseNormLayer : public ResponseNormLayer {\nprotected:\n    bool _blocked;\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    CrossMapResponseNormLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n}; \n\nclass ContrastNormLayer : public ResponseNormLayer {\nprotected:\n    NVMatrix _meanDiffs;\n    \n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\n    void truncBwdActs();\npublic:\n    ContrastNormLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\nclass CostLayer : public Layer {\nprotected:\n    float _coeff;\n    doublev _costv;\n    NVMatrix _tmpbuf; // For error accumulation\n    int _numCases; // number of cases that the values in _costv were computed on\n    bool _aggregated;\n    void fpropCommon(PASS_TYPE passType);\npublic:\n    CostLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool trans);\n    void bprop(NVMatrix& v, PASS_TYPE passType, int passIdx);\n    bool fprop(PASS_TYPE passType, int passIdx);\n    \n    int getNumCases();\n    virtual doublev& getCost();\n    float getCoeff();\n    bool isGradProducer();\n    void setSendTerminalMessages(bool send);\n    void resetPassIdx();\n    \n    static CostLayer& make(ConvNetThread* convNetThread, PyObject* paramsDict, std::string& type, int replicaID);\n};\n\n/*\n * Input 0: labels\n * Input 1: softmax outputs\n */\nclass CrossEntCostLayer : public CostLayer {\nprotected:\n    NVMatrix _trueLabelLogProbs, _correctProbs;\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    CrossEntCostLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\n/*\n * Input 0: labels\n * Input 1: softmax outputs\n */\nclass LogregCostLayer : public CostLayer {\nprotected:\n    NVMatrix _trueLabelLogProbs, _correctProbs, _topkProbs;\n    std::map<int,NVMatrix*> _probsAccum; // input replica idx -> nvmatrix\n    NVMatrix _maxProbs;\n    std::map<int,int> _numAccumed; // input replica idx -> int\n    int _topk;\n    bool _doCompute;\n    virtual void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    LogregCostLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n    NVMatrix& getProbsAccum(int replicaIdx);\n};\n\n/*\n * Input 0: labels\n * Input 1: logistic outputs\n */\nclass BinomialCrossEntropyCostLayer : public CostLayer {\nprotected:\n    bool _computeSoftmaxErrorRate;\n    NVMatrix _tmpProbs, _tmpVec, _correctProbs;\n    float _posWeight;\n    virtual void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    BinomialCrossEntropyCostLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n    float getPosWeight();\n\n    // Only for use with non-logistic units\n    class BinomialCrossEntGradientOperator {\n    private:\n        float _coeff, _posWeight;\n    public:\n        BinomialCrossEntGradientOperator(float coeff, float posWeight) : _coeff(coeff), _posWeight(posWeight) {\n        }\n        __device__ inline float operator()(const float t, const float y) const {\n            return _coeff * (_posWeight * __fdividef(t, y) + __fdividef(t - 1.0f, 1.0f - y));\n        }\n    };\n};\n\n/*\n * Input 0: labels\n * Input 1: logistic outputs\n */\nclass DetectionCrossEntropyCostLayer : public BinomialCrossEntropyCostLayer {\nprotected:\n    Matrix _hNumPositive, _hNumTruePositive, _hNumDeclaredPositive;\n    NVMatrix _numPositive, _numTrueNegative, _numTruePositive, _numDeclaredPositive;\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\npublic:\n    DetectionCrossEntropyCostLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\nclass SumOfSquaresCostLayer : public CostLayer {\nprotected:\n    NVMatrix _tmp;\n    void fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx);\n    void bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType);\npublic:\n    SumOfSquaresCostLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID);\n};\n\n#endif    /* LAYER_CUH */\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/layer_kernels.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef LAYER_KERNELS_CUH\n#define\tLAYER_KERNELS_CUH\n\n#include <vector>\n#include <helper_cuda.h>\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n\n#define LOGREG_GRAD_THREADS_X      32\n#define LOGREG_GRAD_THREADS_Y      4\n\n#define LOGREG_ERR_THREADS_X        128\n#define LOGREG_ERR_THREADS_Y        1\n\n__device__ inline float safelog(const float x) {\n    return x > 0.0f ? __logf(x) : -50.0f;\n}\n\n// The input matrix here is the squared norm.\n// This replaces the squared norm with:\n// 1 if it is below the threshold given by norm2\n// norm/sqrt(a) otherwise -- i.e. the desired norm (not squared)\nclass MaxWeightConstraintOperator {\nprivate:\n    float _norm, _norm2;\npublic:\n    MaxWeightConstraintOperator(float norm) : _norm(norm), _norm2(norm*norm) {\n    }\n    __device__ inline float operator()(const float a) const {\n        return a > _norm2 ? __fdividef(_norm, sqrtf(a)) : 1.0f;\n    }\n};\n\nclass HardWeightConstraintOperator {\nprivate:\n    float _norm, _norm2;\npublic:\n    HardWeightConstraintOperator(float norm) : _norm(norm), _norm2(norm*norm) {\n    }\n    __device__ inline float operator()(const float a) const {\n        return __fdividef(_norm, sqrtf(a));\n    }\n};\n\nclass WeightContrastNormOperator {\nprivate:\n    float _min, _max, _scale;\npublic:\n    WeightContrastNormOperator(float min, float max, float scale) : _min(min), _max(max), _scale(scale) {\n    }\n    __device__ inline float operator()(float a) const {\n        a = sqrtf(a) * _scale;\n        return a < _min ? __fdividef(_min, a) : a > _max ? __fdividef(_max, a) : 1.0f;\n    }\n};\n\nvoid computeCrossEntCost(NVMatrix& labels, NVMatrix& probs, NVMatrix& labelLogProbs_out, NVMatrix& correctProbs_out);\nvoid computeCrossEntGrad(NVMatrix& labels, NVMatrix& probs, NVMatrix& target, bool add, float coeff);\nvoid computeSoftmaxGrad(NVMatrix& acts, NVMatrix& actsGrad, NVMatrix& target, float scaleTarget, float scaleGrad);\n\nvoid computeLogregCost(NVMatrix& labels, NVMatrix& probs, NVMatrix& maxProbs, NVMatrix& labelLogProbs_out, NVMatrix& correctProbs_out);\nvoid computeLogregGrad(NVMatrix& labels, NVMatrix& probs, NVMatrix& target, bool add, float coeff);\n\n\n// Numerical stability optimization: this routine combines computeLogregGrad with computeSoftmaxGrad\n// to avoi dividing and then multiplying by quantities that may be near zero.\nvoid computeCrossEntSoftmaxGrad(NVMatrix& labels, NVMatrix& probs, NVMatrix& target, bool add, float coeff);\nvoid computeLogregSoftmaxGrad(NVMatrix& labels, NVMatrix& probs, NVMatrix& target, bool add, float coeff);\nvoid computeEltwiseMaxGrad(NVMatrix& actGrad, NVMatrix& input, NVMatrix& output, NVMatrix& target, bool add);\nvoid computeMultiSoftmaxCost(NVMatrix& labels, NVMatrix& probs, NVMatrix& maxProbs, NVMatrix& labelLogProbs_out,\n                             NVMatrix& correctProbs_out, NVMatrix& top5Probs_out, int setSize);\n#endif\t/* LAYER_KERNELS_CUH */\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/lr.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef LR_CUH\n#define LR_CUH\n\n#include <string>\n#include <vector>\n#include <iostream>\n#include <helper_cuda.h>\n#include <assert.h>\n#include <Python.h>\n#include \"util.cuh\"\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n#include \"../../util/include/matrix.h\"\n\n/*\n * The maximum learning rate is _baseRate.\n * The minimum learning rate is _baseRate / _tgtFactor.\n *\n * These classes define annealing schedules that interpolate between these\n * two extrema.\n */\nclass ParameterSchedule {\nprotected:\n    double _baseRate;\npublic:\n    ParameterSchedule(double base);\n    virtual double getValue(double progress);\n    double getBaseValue() const;\n    virtual ~ParameterSchedule();\n\n    static ParameterSchedule& make(PyObject* schedDict);\n};\n\nclass LinearParameterSchedule : public ParameterSchedule {\nprotected:\n    double _finalRate;\npublic:\n    LinearParameterSchedule(double base, double tgtFactor);\n    virtual double getValue(double progress);\n};\n\nclass ExpParameterSchedule : public ParameterSchedule {\nprotected:\n    double _powBase;\npublic:\n    ExpParameterSchedule(double baseRate, double tgtFactor);\n    virtual double getValue(double progress);\n};\n\nclass DiscreteExpParameterSchedule : public ParameterSchedule {\nprotected:\n    std::vector<double> _rates;\npublic:\n    DiscreteExpParameterSchedule(double baseRate, double tgtFactor, int numSteps);\n    virtual double getValue(double progress);\n};\n\n\n#endif    /* LR_CUH */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/memorysource.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <map>\n#include <set>\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n\nclass MemorySource;\n\nclass MemoryView {\nprotected:\n    MemorySource* _src;\n    std::string _name;\npublic:\n    MemoryView(MemorySource& src, std::string& name);\n    ~MemoryView();\n    NVMatrix& getMemory(int numCases);\n    NVMatrix& getMemory();\n    MemorySource& getMemorySource();\n    bool isParent();\n    std::string& getName();\n    MemoryView& clone(std::string& name);\n};\n\n// Remember: PassThroughLayer, and therefore MemorySource, exists on a particular GPU.\nclass MemorySource {\nprotected:\n//    int _inputIdx;\n    NVMatrix _memory;\n    int _deviceID;\n    int _size;\n    std::map<std::string, std::pair<int,int> > _viewRanges;\n    std::map<std::string, NVMatrix*> _memoryViews; // input idx --> slice of _memory\n    std::set<std::string> _truncateRequests;\n    Lock _lock;\npublic:\n    MemorySource(int size, int deviceID);\n    ~MemorySource();\n    NVMatrix& getMemory(std::string& name, int numCases);\n    NVMatrix& getMemory(std::string& name);\n    MemoryView& addUser(std::string& name, std::pair<int,int> range);\n    MemoryView& addUser(std::string& name);\n    std::pair<int,int> getRange(std::string& name);\n    int getSize();\n    bool truncate(std::string& name);\n    static MemoryView& make(int size, int deviceID, std::string& parentUser);\n};\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/messages.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef MESSAGES_CUH_\n#define MESSAGES_CUH_\n\n#include <string>\n#include \"layer.cuh\"\n\nclass Layer;\n\nenum MESSAGES { FPROP_TERMINAL,\n                BPROP_TERMINAL,\n                BPROP_READY,\n                FPROP_READY,\n                SYNC,\n                COPY_TO_CPU,\n                COPY_TO_GPU,\n                UPDATE_WEIGHTS,\n                CONSTRAIN_WEIGHTS,\n                RESET,\n                RESET_PASS_IDX,\n                COST_COMPUTED,\n                BPROP_START,\n                EXIT_CONVNET};\n\nclass Message {\nprotected:\n    MESSAGES _messageType;\npublic:\n    MESSAGES getType() {\n        return _messageType;\n    }\n    virtual Message* clone() {\n        return new Message(_messageType);\n    }\n    Message(MESSAGES messageType) : _messageType(messageType) {\n    }\n    virtual ~Message() {\n    }\n};\n\nclass PropMessage : public Message {\nprotected:\n    Layer *_toLayer;\n    PASS_TYPE _passType;\n    int _passIdx;\npublic:\n\n    Layer& getToLayer() {\n        return *_toLayer;\n    }\n\n    PASS_TYPE getPassType() {\n        return _passType;\n    }\n\n    int getPassIdx() {\n        return _passIdx;\n    }\n\n    virtual PropMessage* clone() {\n        return new PropMessage(*_toLayer, _passType, _passIdx, _messageType);\n    }\n\n    PropMessage(Layer& toLayer, PASS_TYPE passType, int passIdx, MESSAGES msgType)\n        : _toLayer(&toLayer), _passType(passType), _passIdx(passIdx), Message(msgType) {\n    }\n};\n\nclass FpropMessage : public PropMessage {\npublic:\n    FpropMessage(Layer& toLayer, PASS_TYPE passType, int passIdx)\n        : PropMessage(toLayer, passType, passIdx, FPROP_READY) {\n    }\n    virtual FpropMessage* clone() {\n        return new FpropMessage(*_toLayer, _passType, _passIdx);\n    }\n};\n\nclass BpropMessage : public PropMessage {\npublic:\n    BpropMessage(Layer& toLayer, PASS_TYPE passType, int passIdx)\n        : PropMessage(toLayer, passType, passIdx, BPROP_READY) {\n    }\n    virtual BpropMessage* clone() {\n        return new BpropMessage(*_toLayer, _passType, _passIdx);\n    }\n};\n\nclass BpropStartMessage : public Message {\nprotected:\n    PASS_TYPE _passType;\n    int _passIdx;\npublic:\n    PASS_TYPE getPassType() {\n        return _passType;\n    }\n\n    int getPassIdx() {\n        return _passIdx;\n    }\n\n    virtual BpropStartMessage* clone() {\n        return new BpropStartMessage(_passType, _passIdx);\n    }\n\n    BpropStartMessage(PASS_TYPE passType, int passIdx)\n        : _passType(passType), Message(BPROP_START), _passIdx(passIdx) {\n    }\n};\n\n\n\n#endif /* MESSAGES_CUH_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/neuron.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef NEURONS_CUH\n#define\tNEURONS_CUH\n\n#include <Python.h>\n#include <assert.h>\n#include <string>\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n#include <helper_cuda.h>\n\ntemplate <class GradientOp>\nclass AddGradientBinaryOperator {\n    GradientOp _op;\npublic:\n    AddGradientBinaryOperator(GradientOp op) : _op(op) {\n    }\n    __device__ inline float operator()(const float unitActGrad, const float unitAct, const float target) const {\n        return _op(unitActGrad, unitAct) + target; \n    }\n};\n\ntemplate <class GradientOp>\nclass AddGradientOperator {\n    GradientOp _op;\npublic:\n    AddGradientOperator(GradientOp op) : _op(op) {\n    }\n    __device__ inline float operator()(const float unitActGrad, const float target) const {\n        return target + _op(unitActGrad); \n    }\n};\n\n/* =======================\n * Neuron\n * -----------------------\n * \n * f(x) = x\n * =======================\n */\nclass Neuron {\nprotected:\n    bool _activated;\n    // Inputs and outputs potentially point to the same matrix, depending on the neuron\n    NVMatrix* _inputs, *_outputs; \n    virtual void _activate() {\n        if (_inputs != _outputs) {\n            _inputs->copy(*_outputs);\n        }\n    }\n    virtual void _computeInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        if (&target != &actsGrad) {\n            actsGrad.copy(target);\n        }\n    }\n    virtual void _addInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        if (&target != &actsGrad) {\n            target.add(actsGrad);\n        }\n    }\npublic:\n    Neuron() : _activated(false), _inputs(NULL), _outputs(NULL) {\n    }\n    virtual void activate(NVMatrix& inputs, NVMatrix& outputs) {\n        _activated = true;\n        _inputs = &inputs;\n        _outputs = &outputs;\n        _activate();\n    }\n\n    virtual void computeInputGrad(NVMatrix& actsGrad, NVMatrix& target, bool add) {\n        assert(_activated);\n        if (!add) {\n            target.resize(actsGrad);\n            _computeInputGrad(actsGrad, target);\n        } else {\n            _addInputGrad(actsGrad, target);\n        }\n    }\n        \n    static Neuron& makeNeuron(PyObject* neuronDict);\n};\n\n/* =======================\n * LogisticNeuron\n * -----------------------\n * \n * f(x) = 1 / (1 + e^-x)\n * =======================\n */\nclass LogisticNeuron : public Neuron {\nprotected:\n    void _activate() {\n        _inputs->apply(NVMatrixOps::Logistic(), *_outputs);\n    }\n\n    void _computeInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyBinary(LogisticGradientOperator(), *_outputs, target);\n    }\n    \n    void _addInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyTernary(AddGradientBinaryOperator<LogisticGradientOperator>(LogisticGradientOperator()), *_outputs, target, target);\n    }\npublic:\n    class LogisticGradientOperator {\n    public:\n        __device__ inline float operator()(float unitActGrad, float unitAct) const {\n            return unitActGrad * unitAct * (1.0f - unitAct); \n        }\n    };\n    \n    LogisticNeuron() : Neuron() {\n    }\n};\n\n/* =======================\n * LogNeuron\n * -----------------------\n *\n * f(x) = log(eps + x)\n * =======================\n */\nclass LogNeuron : public Neuron {\nprotected:\n    float _eps;\n    void _activate() {\n        _inputs->apply(LogOperator(_eps), *_outputs);\n    }\n\n    void _computeInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyBinary(LogGradientOperator(_eps), *_inputs, target);\n    }\n\n    void _addInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyTernary(AddGradientBinaryOperator<LogGradientOperator>(LogGradientOperator(_eps)), *_inputs, target, target);\n    }\npublic:\n    class LogGradientOperator {\n    protected:\n        float _eps;\n    public:\n        __device__ inline float operator()(float unitActGrad, float unitInput) const {\n            return __fdividef(unitActGrad, _eps + unitInput);\n        }\n        LogGradientOperator(float eps) : _eps(eps) {\n\n        }\n    };\n\n    class LogOperator {\n    protected:\n        float _eps;\n    public:\n        __device__ inline float operator()(float x) const {\n            return __logf(_eps + x);\n        }\n        LogOperator(float eps) : _eps(eps) {\n\n        }\n    };\n\n    LogNeuron(float eps) : _eps(eps), Neuron() {\n    }\n};\n\n/* =======================\n * ReluNeuron\n * -----------------------\n * \n * f(x) = max(0, x)\n * =======================\n */\nclass ReluNeuron : public Neuron {\nprotected:\n    virtual void _activate() {\n        _inputs->apply(ReluOperator(), *_outputs);\n    }\n\n    void _computeInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyBinary(ReluGradientOperator(), *_outputs, target);\n    }\n    \n    void _addInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyTernary(AddGradientBinaryOperator<ReluGradientOperator>(ReluGradientOperator()), *_outputs, target, target);\n    }\npublic:\n    class ReluOperator {\n    public:    \n        __device__ inline float operator()(float x) const {\n            return x < 0.0f ? 0.0f : x;\n        }\n    };\n\n    class ReluGradientOperator {\n    public:\n        __device__ inline float operator()(float unitActGrad, float unitAct) const  {\n            return unitActGrad * (unitAct > 0.0f); \n        }\n    };\n    \n    ReluNeuron() : Neuron() {\n    }\n};\n\n\n/* =======================\n * BoundedReluNeuron\n * -----------------------\n * \n * f(x) = min(a, max(0, x))\n * =======================\n */\nclass BoundedReluNeuron : public Neuron {\nprotected:\n    float _a;\n    \n    void _activate() {\n        _inputs->apply(BoundedReluOperator(_a), *_outputs);\n    }\n\n    void _computeInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyBinary(BoundedReluGradientOperator(_a), *_outputs, target);\n    }\n    \n    void _addInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyTernary(AddGradientBinaryOperator<BoundedReluGradientOperator>(BoundedReluGradientOperator(_a)), *_outputs, target, target);\n    }\npublic:\n    class BoundedReluOperator {\n    private:\n        float _a;\n    public:\n        BoundedReluOperator(float a) : _a(a) {\n        }\n        __device__ inline float operator()(float x) const {\n            return x < 0.0f ? 0.0f : x > _a ? _a : x;\n        }\n    };\n\n    class BoundedReluGradientOperator {\n    private:\n        float _a;\n    public:\n        BoundedReluGradientOperator(float a) : _a(a) {\n        }\n        __device__ inline float operator()(float unitActGrad, float unitAct) const  {\n            return unitActGrad * (unitAct > 0.0f) * (unitAct < _a); \n        }\n    };\n    \n    BoundedReluNeuron(float a) : Neuron(), _a(a) {\n    }\n};\n\n/* =======================\n * AbsNeuron\n * -----------------------\n * \n * f(x) = abs(x)\n * =======================\n */\nclass AbsNeuron : public Neuron {\nprotected:\n    void _activate() {\n        assert(_inputs != _outputs);\n        _inputs->apply(NVMatrixOps::Abs(), *_outputs);\n    }\n\n    void _computeInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyBinary(AbsGradientOperator(), *_inputs, target);\n    }\n    \n    void _addInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyTernary(AddGradientBinaryOperator<AbsGradientOperator>(AbsGradientOperator()), *_inputs, target, target);\n    }\npublic:\n    class AbsGradientOperator {\n    public:\n        __device__ inline float operator()(float unitActGrad, float unitInput) const  {\n            return unitActGrad * (unitInput > 0.0f ? 1.0f : -1.0f); \n        }\n    };\n    \n    AbsNeuron() : Neuron() {\n    }\n};\n\n/* =======================\n * TanhNeuron\n * -----------------------\n * \n * f(x) = a*tanh(b*x)\n * =======================\n */\nclass TanhNeuron : public Neuron {\nprotected:\n    float _a, _b;\n\n    void _activate() {\n        _inputs->apply(TanhOperator(_a, _b), *_outputs);\n    }\n\n    void _computeInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyBinary(TanhGradientOperator(_a, _b), *_outputs, target);\n    }\n    \n    void _addInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyTernary(AddGradientBinaryOperator<TanhGradientOperator>(TanhGradientOperator(_a, _b)), *_outputs, target, target);\n    }\npublic:\n    class TanhOperator {\n    private:\n        float _a, _n2b;\n    public:\n        TanhOperator(float a, float b) : _a(a), _n2b(-2*b) {\n        }\n        virtual __device__ inline float operator()(float x) const {\n            return _a * (__fdividef(2.0f, 1.0f + __expf(x * _n2b)) - 1.0f);\n        }\n    };\n\n    class TanhGradientOperator {\n    private:\n        float _b, _a;\n    public:\n        TanhGradientOperator(float a, float b) : _b(b), _a(a) {\n        }\n        __device__ inline float operator()(float unitActGrad, float unitAct) const  {\n//            const float t = (1.0f - __fdividef(unitAct, _a)) / 2.0f;\n//            return unitActGrad * _n4ab * (t * (t - 1.0f));\n            return unitActGrad * _b * (_a - __fdividef(unitAct * unitAct, _a));\n        }\n    };\n    \n    TanhNeuron(float a, float b) : Neuron(), _a(a), _b(b) {\n    }\n};\n\n/* =======================\n * DoubleReluNeuron\n * -----------------------\n * \n * f(x) = x - a*tanh(x/a)\n * =======================\n */\nclass DoubleReluNeuron : public Neuron {\nprotected:\n    float _a;\n\n    void _activate() {\n        assert(_inputs != _outputs);\n        _inputs->apply(DoubleReluOperator(_a), *_outputs);\n    }\n\n    void _computeInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyBinary(DoubleReluGradientOperator(_a), *_inputs, target);\n    }\n    \n    void _addInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyTernary(AddGradientBinaryOperator<DoubleReluGradientOperator>(DoubleReluGradientOperator(_a)), *_inputs, target, target);\n    }\npublic:\n    class DoubleReluOperator {\n    private:\n        float _a, _n2a;\n    public:\n        DoubleReluOperator(float a) : _a(a), _n2a(-2.0f / a) {\n        }\n        virtual __device__ inline float operator()(float x) const {\n            return x - _a * (__fdividef(2.0f, 1.0f + __expf(_n2a * x)) - 1.0f);\n        }\n    };\n\n    class DoubleReluGradientOperator {\n    private:\n        float _n2a;\n    public:\n        DoubleReluGradientOperator(float a) : _n2a(-2.0f / a) {\n        }\n        __device__ inline float operator()(float unitActGrad, float unitInput) const  {\n            const float tanh = __fdividef(2.0f, 1.0f + __expf(_n2a * unitInput)) - 1.0f;\n            return unitActGrad * (tanh*tanh);\n        }\n    };\n    \n    DoubleReluNeuron(float a) : Neuron(), _a(a) {\n    }\n};\n\n/* =======================\n * SoftReluNeuron\n * -----------------------\n * \n * f(x) = log(1 + e^x)\n * =======================\n */\nclass SoftReluNeuron : public Neuron {\nprotected:\n    void _activate() {\n//        assert(_inputs != _outputs);\n        _inputs->apply(SoftReluOperator(), *_outputs);\n    }\n\n    void _computeInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyBinary(SoftReluGradientOperator(), *_outputs, target);\n    }\n    \n    void _addInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyTernary(AddGradientBinaryOperator<SoftReluGradientOperator>(SoftReluGradientOperator()), *_outputs, target, target);\n    }\npublic:\n    class SoftReluOperator {\n    public:    \n        __device__ inline float operator()(float x) const {\n            // This piece-wise implementation has better numerical stability than\n            // simply computing log(1 + e^x).\n            return x > 4.0f ? x : __logf(1.0f + __expf(x));\n        }\n    };\n\n    class SoftReluGradientOperator {\n    public:\n        __device__ inline float operator()(float unitActGrad, float unitOutput) const  {\n            if (unitOutput > 4.0f) {\n                return unitActGrad;\n            }\n            const float f = __expf(-unitOutput);\n            return unitActGrad * (1.0f - f);\n        }\n    };\n    \n    SoftReluNeuron() : Neuron() {\n    }\n};\n\n/* =======================\n * SquareNeuron\n * -----------------------\n * \n * f(x) = x^2\n * =======================\n */\nclass SquareNeuron : public Neuron {\nprotected:\n    void _activate() {\n        assert(_inputs != _outputs);\n        _inputs->apply(NVMatrixOps::Square(), *_outputs);\n    }\n\n    void _computeInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyBinary(SquareGradientOperator(), *_inputs, target);\n    }\n    \n    void _addInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyTernary(AddGradientBinaryOperator<SquareGradientOperator>(SquareGradientOperator()), *_inputs, target, target);\n    }\npublic:\n    class SquareGradientOperator {\n    public:\n        __device__ inline float operator()(float unitActGrad, float unitInput) const {\n            return unitActGrad * 2.0f * unitInput; \n        }\n    };\n    \n    SquareNeuron() : Neuron() {\n    }\n};\n\n/* =======================\n * SqrtNeuron\n * -----------------------\n * \n * f(x) = sqrt(x)\n * =======================\n */\nclass SqrtNeuron : public Neuron {\nprotected:\n    void _activate() {\n        _inputs->apply(NVMatrixOps::Sqrt(), *_outputs);\n    }\n\n    void _computeInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyBinary(SqrtGradientOperator(), *_outputs, target);\n    }\n    \n    void _addInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyTernary(AddGradientBinaryOperator<SqrtGradientOperator>(SqrtGradientOperator()), *_outputs, target, target);\n    }\npublic:\n    class SqrtGradientOperator {\n    public:\n        __device__ inline float operator()(float unitActGrad, float unitAct) const {\n            return __fdividef(unitActGrad, 2.0f * unitAct); \n        }\n    };\n    \n    SqrtNeuron() : Neuron() {\n    }\n};\n\n/* =======================\n * LinearNeuron\n * -----------------------\n * \n * f(x) = a*x + b\n * =======================\n */\nclass LinearNeuron : public Neuron {\nprotected:\n    float _a, _b;\n    void _activate() {\n        _inputs->apply(NVMatrixOps::Linear(_a, _b), *_outputs);\n    }\n\n    void _computeInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.scale(_a, target);\n    }\n    \n    void _addInputGrad(NVMatrix& actsGrad, NVMatrix& target) {\n        actsGrad.applyBinary(AddGradientOperator<NVMatrixOps::MultByScalar>(NVMatrixOps::MultByScalar(_a)), target, target);\n    }\npublic:\n    LinearNeuron(float a, float b) : Neuron(), _a(a), _b(b) {\n    }\n};\n#endif\t/* NEURONS_CUH */\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/pipedispenser.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef PIPEDISPENSER_CUH_\n#define PIPEDISPENSER_CUH_\n\n#include <pthread.h>\n#include <set>\n#include <algorithm>\n#include <iterator>\n#include \"../../util/include/thread.h\"\n#include \"util.cuh\"\n\n/*\n * PipeDispenser interface\n */\nclass PipeDispenser {\nprotected:\n    int _numPipes;\n    seti _pipes;\n    pthread_mutex_t *_mutex;\n\n    void lock() {\n        pthread_mutex_lock(_mutex);\n    }\n\n    void unlock() {\n        pthread_mutex_unlock(_mutex);\n    }\n\n    virtual void init() {\n        _mutex = (pthread_mutex_t*)(malloc(sizeof (pthread_mutex_t)));\n        pthread_mutex_init(_mutex, NULL);\n    }\npublic:\n    PipeDispenser(const seti& pipes) {\n        _pipes.insert(pipes.begin(), pipes.end());\n        init();\n    }\n\n    PipeDispenser(int numPipes) {\n        for (int i = 0; i < numPipes; ++i) {\n            _pipes.insert(i);\n        }\n        init();\n    }\n\n    virtual ~PipeDispenser() {\n        pthread_mutex_destroy(_mutex);\n        free(_mutex);\n    }\n\n    virtual int getPipe(const seti& interested) = 0;\n\n    int getPipe(int interested) {\n        seti tmp;\n        tmp.insert(interested);\n        return getPipe(tmp);\n    }\n\n    virtual void freePipe(int pipe) = 0;\n};\n\n/*\n * This one blocks until there is a free pipe to return.\n */\nclass PipeDispenserBlocking : public PipeDispenser {\nprotected:\n    pthread_cond_t *_cv;\n\n    void wait() {\n        pthread_cond_wait(_cv, _mutex);\n    }\n\n    void broadcast() {\n        pthread_cond_broadcast(_cv);\n    }\n\n    int getAvailablePipes(const seti& interested, intv& available) {\n        available.clear();\n        std::set_intersection(_pipes.begin(), _pipes.end(), interested.begin(), interested.end(), std::back_inserter(available));\n        return available.size();\n    }\n\n    virtual void init() {\n        PipeDispenser::init();\n        _cv = (pthread_cond_t*)(malloc(sizeof (pthread_cond_t)));\n                pthread_cond_init(_cv, NULL);\n    }\npublic:\n    PipeDispenserBlocking(const seti& pipes) : PipeDispenser(pipes) {\n        init();\n    }\n\n    PipeDispenserBlocking(int numPipes) : PipeDispenser(numPipes) {\n        init();\n    }\n\n    ~PipeDispenserBlocking() {\n        pthread_cond_destroy(_cv);\n        free(_cv);\n    }\n\n    int getPipe(const seti& interested) {\n        lock();\n        intv avail;\n        while (getAvailablePipes(interested, avail) == 0) {\n            wait();\n        }\n        int pipe = avail[0];\n        _pipes.erase(pipe);\n        unlock();\n        return pipe;\n    }\n\n    void freePipe(int pipe) {\n        lock();\n        _pipes.insert(pipe);\n        broadcast();\n        unlock();\n    }\n};\n\n/*\n * This one returns the least-occupied pipe.\n */\nclass PipeDispenserNonBlocking : public PipeDispenser  {\nprotected:\n    std::map<int,int> _pipeUsers;\n\npublic:\n    PipeDispenserNonBlocking(const seti& pipes) : PipeDispenser(pipes) {\n        for (seti::iterator it = pipes.begin(); it != pipes.end(); ++it) {\n            _pipeUsers[*it] = 0;\n        }\n    }\n\n    int getPipe(const seti& interested) {\n        lock();\n        int pipe = -1, users = 1 << 30;\n        for (seti::iterator it = _pipes.begin(); it != _pipes.end(); ++it) {\n            if (interested.count(*it) > 0 && _pipeUsers[*it] < users) {\n                pipe = *it;\n                users = _pipeUsers[*it];\n            }\n        }\n        if (pipe >= 0) {\n            _pipeUsers[pipe]++;\n        }\n        unlock();\n        return pipe;\n    }\n\n    void freePipe(int pipe) {\n        lock();\n        _pipeUsers[pipe]--;\n        unlock();\n    }\n};\n\n\n#endif /* PIPEDISPENSER_CUH_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/pyconvnet.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef PYCONVNET3_CUH\n#define\tPYCONVNET3_CUH\n\n#define _QUOTEME(x) #x\n#define QUOTEME(x) _QUOTEME(x)\n\nextern \"C\" void init_ConvNet();\n\nPyObject* initModel(PyObject *self, PyObject *args);\nPyObject* startBatch(PyObject *self, PyObject *args);\nPyObject* finishBatch(PyObject *self, PyObject *args);\nPyObject* checkGradients(PyObject *self, PyObject *args);\nPyObject* syncWithHost(PyObject *self, PyObject *args);\nPyObject* startMultiviewTest(PyObject *self, PyObject *args);\nPyObject* startFeatureWriter(PyObject *self, PyObject *args);\nPyObject* startDataGrad(PyObject *self, PyObject *args);\nPyObject* decodeJpeg(PyObject *self, PyObject *args);\n\n#endif\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/reducepipeline.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef REDUCEPIPELINE_CUH_H_\n#define REDUCEPIPELINE_CUH_H_\n\n#include \"../../util/include/thread.h\"\n#include \"../../util/include/queue.h\"\n#include <helper_cuda.h>\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n#include \"util.cuh\"\n\n#define REDUCE_MIN_CHUNK_SIZE               (1<<18) // 256k\n#define REDUCE_MAX_CHUNKS                   16\n#define REDUCE_MIN_CHUNKS                   2\n\nenum REDUCE_MESSAGE_TYPE {\n    REDUCE_CHUNK,\n    REDUCE_START,\n    EXIT\n};\n\nclass ReducePeer;\nclass ReducerSource;\nclass IReduceSegment;\nclass IEightGPUReducer;\n\nclass ReduceMessage {\nprotected:\n    REDUCE_MESSAGE_TYPE _msgType;\n    float _scaleIntermediates, _scaleTarget;\n    std::map<int,NVMatrix*>* _mats;\npublic:\n    ReduceMessage(REDUCE_MESSAGE_TYPE msgType, float scaleIntermediates, float scaleTarget, std::map<int,NVMatrix*>& mats)\n        : _msgType(msgType), _scaleIntermediates(scaleIntermediates), _scaleTarget(scaleTarget), _mats(&mats) {\n    }\n    ReduceMessage(REDUCE_MESSAGE_TYPE msgType)\n        : _msgType(msgType), _scaleIntermediates(0), _scaleTarget(0), _mats(NULL) {\n    }\n    inline REDUCE_MESSAGE_TYPE getType() const {\n        return _msgType;\n    }\n    inline float getScaleIntermediates() const {\n        return _scaleIntermediates;\n    }\n    inline float getScaleTarget() const {\n        return _scaleTarget;\n    }\n    inline NVMatrix& getMatrix(int deviceID) const {\n        return *_mats->at(deviceID);\n    }\n    inline std::map<int,NVMatrix*>& getMatrices() const {\n        return *_mats;\n    }\n};\n\nclass ReduceChunkMessage : public ReduceMessage {\nprotected:\n    int _chunkIdx;\n    int _chunkSize;\n    int _numChunks;\n\n    IReduceSegment* _src;\npublic:\n    ReduceChunkMessage(IReduceSegment& src, int chunkIdx, int chunkSize, int numChunks, float scaleIntermediates, float scaleTarget, std::map<int,NVMatrix*>& mats)\n        : _src(&src), _chunkIdx(chunkIdx), _chunkSize(chunkSize), _numChunks(numChunks),\n          ReduceMessage(REDUCE_CHUNK, scaleIntermediates, scaleTarget, mats) {\n    }\n\n    inline int getChunkIdx() const {\n        return _chunkIdx;\n    }\n\n    inline int getChunkSize() const {\n        return _chunkSize;\n    }\n\n    inline int getNumChunks() const {\n        return _numChunks;\n    }\n\n    inline IReduceSegment& getSource() const {\n        return *_src;\n    }\n};\n\nclass ReduceStartMessage : public ReduceMessage {\npublic:\n    ReduceStartMessage(float scaleIntermediates, float scaleTarget, std::map<int,NVMatrix*>& mats)\n        : ReduceMessage(REDUCE_START, scaleIntermediates, scaleTarget, mats) {\n    }\n};\n\nclass IReduceSegment : public Thread {\nprotected:\n    int _deviceID;\n    std::vector<IReduceSegment*> _prev;\n    ReducePeer* _next;\n    Queue<ReduceMessage*> _queue;\n    Queue<int>* _finishQueue;\n\n    NVMatrix& getChunk(const NVMatrix& mat, int chunkSize, int chunkIdx);\n    void* run();\n    virtual bool processMessage(ReduceMessage& msg) = 0;\n\npublic:\n    IReduceSegment(IEightGPUReducer& parent, int deviceID, Queue<int>* finishQueue);\n    virtual ~IReduceSegment();\n    inline virtual NVMatrix& getMatrix(ReduceMessage& msg);\n    Queue<ReduceMessage*>& getQueue();\n    int getDeviceID() const;\n    void addPrev(IReduceSegment& c);\n    void addNext(ReducePeer& c);\n    bool isTerminal() const;\n};\n\nclass ReducerSource : public IReduceSegment {\nprotected:\n    bool processMessage(ReduceMessage& msg);\npublic:\n    ReducerSource(IEightGPUReducer& parent, int deviceID);\n};\n\nclass ReducePeer : public IReduceSegment {\nprotected:\n    std::map<int,cudaStream_t> _streams;  // device id -> stream\n    std::map<int,int> _numInputsReceived; // chunk idx -> num inputs\n    int _numInputsFinished;\n    HostNVMatrix _mat;\n    bool _add;\n    bool processMessage(ReduceMessage& msg);\n    inline cudaStream_t getStream(int deviceID);\n    inline NVMatrix& getMatrix(ReduceMessage& msg);\n    void hostAdd(const float* src, float* tgt, const int n, const float scaleTgt);\npublic:\n    ReducePeer(IEightGPUReducer& parent, int deviceID, Queue<int>* finishQueue);\n    ReducePeer(IEightGPUReducer& parent);\n    ~ReducePeer();\n};\n\nclass IEightGPUReducer {\nprotected:\n    std::vector<ReducerSource*> _sources;\n    std::vector<ReducePeer*> _peers;\n    Queue<int> _finishQueue;\n    int _tgtDeviceID;\n    virtual void makeConnections(std::vector<int>& same, std::vector<int>&other) = 0;\npublic:\n    IEightGPUReducer(int tgtDeviceID);\n    virtual ~IEightGPUReducer();\n    IEightGPUReducer& construct();\n    void reduce(std::map<int, NVMatrix*>& mats, float scaleIntermediates, float scaleTarget);\n    void reduce(std::map<int, NVMatrix*>& mats, float scaleIntermediates);\n    void reduce(std::map<int, NVMatrix*>& mats);\n    int getTgtDeviceID() const;\n};\n\nclass EightGPUReducer1 : public IEightGPUReducer {\nprotected:\n    void makeConnections(std::vector<int>& same, std::vector<int>&other);\npublic:\n    EightGPUReducer1(int tgtDeviceID);\n};\n\nclass EightGPUReducer2 : public IEightGPUReducer {\nprotected:\n    void makeConnections(std::vector<int>& same, std::vector<int>&other);\npublic:\n    EightGPUReducer2(int tgtDeviceID);\n};\n\n#endif /* REDUCEPIPELINE_CUH_H_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/streambroadcast.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef STREAMBROADCAST_CUH_\n#define STREAMBROADCAST_CUH_\n\n#include <iostream>\n#include \"../../util/include/queue.h\"\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n#include \"util.cuh\"\n\nclass Layer;\n\n//#define NUM_STREAM_COPY_PARTS       4\n// This is in 4-byte words, not bytes\n#define SB_MIN_CHUNK_SIZE              (1<<17)\n#define SB_MAX_CHUNKS                  16\n\nclass StreamBroadcast {\nprotected:\n    std::map<int,cudaStream_t> _streams;\n    std::set<int> _ownedStreams;\n    HostNVMatrix _hostMem;\n    void toHostMem(NVMatrix& src, NVMatrix& hostmem, int srcDevice);\n    void toTarget(NVMatrix& hostmem, NVMatrix& tgt, int tgtDevice, float scaleTarget, float scaleOutput);\n    void init(std::map<int,cudaStream_t>& streams);\n    void init(std::map<int,NVMatrix*>& mats);\npublic:\n    StreamBroadcast(std::map<int,cudaStream_t>& streams);\n    StreamBroadcast();\n    virtual ~StreamBroadcast();\n\n    void transfer(std::map<int,NVMatrix*>& mats, HostNVMatrix& hostmem, int srcDevice, float scaleTarget, float scaleOutput);\n    void transfer(std::map<int,NVMatrix*>& mats, int srcDevice, float scaleTarget, float scaleOutput);\n    void transfer(std::map<int,NVMatrix*>& mats, int srcDevice);\n    void sync(int deviceID);\n    cudaStream_t getStream(int deviceID);\n};\n\n#endif /* STREAMBROADCAST_CUH_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/timer.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef TIMER_CC_H_\n#define TIMER_CC_H_\n\n#include <helper_timer.h>\n\nclass Timer {\nprotected:\n    StopWatchInterface* _timer;\n    bool _started;\n\npublic:\n    Timer() : _started(false) {\n        sdkCreateTimer(&_timer);\n    }\n\n    ~Timer() {\n        sdkDeleteTimer(&_timer);\n    }\n    inline void start () {\n        _started = true;\n        sdkResetTimer(&_timer);\n        sdkStartTimer(&_timer);\n    }\n\n    inline double stop() {\n        sdkStopTimer(&_timer);\n        _started = false;\n        return sdkGetTimerValue(&_timer);\n    }\n\n    inline bool isStarted() const {\n        return _started;\n    }\n};\n\n#endif /* TIMER_CC_H_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/util.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef UTIL_H\n#define\tUTIL_H\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n#include <vector>\n#include <map>\n#include <set>\n#include <string>\n#include <sstream>\n#include <string>\n#include <Python.h>\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n#include \"../../util/include/matrix.h\"\n\n\n#define PASS_TYPE                   uint\n#define PASS_TRAIN                  0x1\n#define PASS_TEST                   0x2\n#define PASS_GC                     0x4\n#define PASS_MULTIVIEW_TEST         (PASS_TEST | 0x8)\n#define PASS_MULTIVIEW_TEST_START   (PASS_MULTIVIEW_TEST | 0x10)\n#define PASS_MULTIVIEW_TEST_END     (PASS_MULTIVIEW_TEST | 0x20)\n#define PASS_FEATURE_GEN            0x40\n\n#define HAS_FLAG(f, x)              (((x) & (f)) == (f))\n#define IS_MULTIVIEW_TEST(x)        HAS_FLAG(PASS_MULTIVIEW_TEST, x)\n#define IS_MULTIVIEW_TEST_START(x)  HAS_FLAG(PASS_MULTIVIEW_TEST_START, x)\n#define IS_MULTIVIEW_TEST_END(x)    HAS_FLAG(PASS_MULTIVIEW_TEST_END, x)\n#define IS_TEST(x)                  HAS_FLAG(PASS_TEST, x)\n#define IS_TRAIN(x)                 HAS_FLAG(PASS_TRAIN, x)\n\n// For gradient checking\n#define GC_SUPPRESS_PASSES          false\n#define GC_REL_ERR_THRESH           0.02\n\n#ifdef DO_PRINT\n#define PRINT(x, args...) printf(x, ## args);\n#else\n#define PRINT(x, args...) ;\n#endif\n\n/*\n * Generates a random floating point number in the range 0-1.\n */\n#define randf                       ((float)rand() / RAND_MAX)\n\n//typedef std::vector<Matrix*> MatrixV;\n//typedef std::vector<NVMatrix*> NVMatrixV;\ntypedef std::map<std::string,std::vector<double>*> CostMap;\ntypedef std::map<std::string,double> CostCoeffMap;\ntypedef std::vector<double> doublev;\ntypedef std::vector<float> floatv;\ntypedef std::vector<int> intv;\ntypedef std::vector<std::string> stringv;\ntypedef std::set<int> seti;\ntypedef std::vector<PyObject*> PyObjectV;\n\nstringv* getStringV(PyObject* pyList);\nfloatv* getFloatV(PyObject* pyList);\nintv* getIntV(PyObject* pyList);\nMatrixV* getMatrixV(PyObject* pyList);\nMatrixV* getMatrixV(PyObject* pyList, int len);\nint* getIntA(PyObject* pyList);\n\nint pyDictGetInt(PyObject* dict, const char* key);\nintv* pyDictGetIntV(PyObject* dict, const char* key);\nstd::string pyDictGetString(PyObject* dict, const char* key);\nfloat pyDictGetFloat(PyObject* dict, const char* key);\nfloatv* pyDictGetFloatV(PyObject* dict, const char* key);\nMatrix* pyDictGetMatrix(PyObject* dict, const char* key);\nMatrixV* pyDictGetMatrixV(PyObject* dict, const char* key);\nint* pyDictGetIntA(PyObject* dict, const char* key);\nstringv* pyDictGetStringV(PyObject* dict, const char* key);\nbool pyDictHasKey(PyObject* dict, const char* key);\nPyObjectV* pyDictGetValues(PyObject* dict);\n\ntemplate<typename T> std::string tostr(T n);\ntemplate<typename T> void shuffleVector(std::vector<T>& v, int start, int end);\ntemplate<class T> void deleteElements(std::vector<T*>& v);\ntemplate<class T> void deleteElements(std::vector<T*>& v, bool deleteContainer);\n\ntemplate<class T>\nint indexOf(std::vector<T>& v, T e) {\n    int i = 0;\n//    typename vector<T>::iterator it2 = v.begin();\n    for (typename std::vector<T>::const_iterator it = v.begin(); it != v.end(); ++it) {\n        if (*it == e) {\n            return i;\n        }\n        ++i;\n    }\n    return -1;\n}\n\nstd::vector<int>& getDeviceCPUs(int deviceID);\n\ntemplate<typename K, typename V> std::set<K> getKeys(std::map<K,V>& m) {\n    std::set<K> s;\n    for (typename std::map<K,V>::const_iterator it = m.begin(); it != m.end(); ++it) {\n        s.insert(it->first);\n    }\n    return s;\n}\n\nstruct LayerIDComparator {\n    bool operator()(PyObject* i, PyObject* j) {\n        return pyDictGetInt(i, \"id\") < pyDictGetInt(j, \"id\");\n    }\n};\n\n#endif\t/* UTIL_H */\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/weights.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef WEIGHTS_CUH\n#define\tWEIGHTS_CUH\n\n#include <string>\n#include <vector>\n#include <iostream>\n#include <helper_cuda.h>\n#include <assert.h>\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n#include \"../../util/include/matrix.h\"\n#include \"util.cuh\"\n#include \"lr.cuh\"\n#include \"layer.cuh\"\n#include \"copypipeline.cuh\"\n#include \"reducepipeline.cuh\"\n#include \"streambroadcast.cuh\"\n\nclass Layer;\nclass Weights;\nclass StreamBroadcast;\n\nclass IWeightReducer {\nprotected:\n    int _tgtReplicaID;\n    std::map<int,Weights*> _replicas;\n\n    int getDeviceID();\npublic:\n    IWeightReducer(std::map<int,Weights*>& replicas, int srcReplicaID);\n    virtual ~IWeightReducer();\n    static IWeightReducer& make(std::map<int,Weights*>& replicas, int srcReplicaID);\n    virtual void reduce(std::map<int, NVMatrix*> gradShards, float gradScale, bool toInc) = 0;\n};\n\nclass SequentialWeightReducer : public IWeightReducer {\nprotected:\n    StreamBroadcast* _sb;\npublic:\n    SequentialWeightReducer(std::map<int,Weights*>& replicas, int srcReplicaID);\n    ~SequentialWeightReducer();\n    void reduce(std::map<int, NVMatrix*> gradShards, float gradScale, bool toInc);\n};\n\nclass ParallelWeightReducer : public IWeightReducer {\nprotected:\n    IEightGPUReducer* _reducer;\npublic:\n    ParallelWeightReducer(std::map<int,Weights*>& replicas, int srcReplicaID);\n    ~ParallelWeightReducer();\n    void reduce(std::map<int, NVMatrix*> gradShards, float gradScale, bool toInc);\n};\n\nclass Weights {\nprotected:\n    Matrix* _hWeights, *_hWeightsInc;\n    NVMatrix* _weights, *_weightsInc, *_weightsGrad;\n    \n    ParameterSchedule* _lrs;\n\n    float _wc, _mom, _wball;\n    bool _onGPU, _useGrad, _cleanup;\n    int _numUpdates;\n\n    // Note: every layer is its own sibling too\n    std::map<int,Weights*> _replicas;\n    \n    // Non-NULL if these weights are really shared from some other layer\n    Weights* _srcWeights;\n    Layer* _parent;\n    int _shardSize;\n    IWeightReducer* _reducer;\n    ISafeBroadcastNetwork* _broadcaster;\n\n    void aggregateReplicaGradients(float progress);\n\n    // TODO: assert that these retrun contiguous views\n    template<class T> T& getShard(T& mat, int replicaID);\n    template<class T> T& getShard(T& mat);\n    void init(Matrix& hWeights, Matrix& hWeightsInc, ParameterSchedule& lrs, Layer& parent, float wc, float wball, float mom, bool useGrad, bool cleanup);\n\npublic:\n    NVMatrix& operator*() const;\n    \n    Weights(Weights& srcWeights, ParameterSchedule& lrs, Layer& parent);\n    Weights(Matrix& hWeights, Matrix& hWeightsInc, ParameterSchedule& lrs, Layer& parent,\n            float wc, float wball, float mom, bool useGrad);\n        \n    virtual ~Weights();\n\n    virtual NVMatrix& getW() const;\n    virtual NVMatrix& getInc() const;\n    virtual NVMatrix& getGrad() const;\n    virtual Matrix& getCPUW() const;\n    virtual Matrix& getCPUWInc() const;\n    virtual ParameterSchedule& getLearningRateSchedule() const;\n    virtual int getNumRows() const;\n    virtual int getNumCols() const;\n    virtual void copyToCPU();\n    \n    // This function is assumed to be called in the order in which the layers\n    // were defined\n    virtual void copyToGPU();\n    \n    virtual void update(float progress);\n    virtual void addReplica(Weights& sibling);\n    int incNumUpdates();\n    \n    // Returns the number of times a gradient has been computed for this\n    // weight matrix during the current pass (interval between two calls of update())\n    // through the net. This number will only be greater than 1 if this weight matrix\n    // is *shared* by multiple layers in the net.\n    int getNumUpdates() const;\n    float getEps(float progress) const;\n    float getMom() const;\n    float getWC() const;\n    float getWBall() const;\n    bool isUseGrad() const;\n    bool isOwner() const;\n    int getReplicaID();\n    int getDeviceID();\n    Layer& getParent();\n    std::map<int,Weights*>& getReplicas();\n    ISafeBroadcastNetwork& getBroadcaster();\n    IWeightReducer& getReducer();\n};\n\nclass WeightList {\nprivate:\n    std::vector<Weights*> _weightList;\npublic:\n    Weights& operator[](const int idx) const;\n    ~WeightList();\n    WeightList();\n    Weights& at(const int i) const;\n    void addWeights(Weights& w);\n    void addReplica(WeightList& sibling);\n    void update(float progress);\n    void copyToCPU();\n    void copyToGPU();\n    int getSize() const;\n};\n\n#endif\t/* WEIGHTS_CUH */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/include/worker.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef WORKER_CUH\n#define WORKER_CUH\n\n#include \"convnet.cuh\"\n#include \"cost.cuh\"\n#include \"data.cuh\"\n\nclass ConvNet;\nclass Cost;\n\nclass WorkResult {\npublic:\n    enum RESULTS {BATCH_DONE, SYNC_DONE};\nprotected:\n    WorkResult::RESULTS _resultType;\n    Cost* _results;\npublic:\n    WorkResult(WorkResult::RESULTS resultType, Cost& results);\n    WorkResult(WorkResult::RESULTS resultType);\n    virtual ~WorkResult();\n    Cost& getResults() const;\n    WorkResult::RESULTS getResultType() const;\n};\n\nclass Worker {\nprotected:\n    ConvNet* _convNet;\npublic:\n    Worker(ConvNet& convNet);\n    virtual ~Worker();\n    virtual bool run() = 0;\n};\n\nclass DataWorker : public Worker {\nprotected:\n    CPUData* _data;\n    DataProvider* _dp;\npublic:\n    DataWorker(ConvNet& convNet, CPUData& data);\n    virtual ~DataWorker();\n    bool run();\n    virtual void _run() = 0;\n};\n\nclass TrainingWorker : public DataWorker {\nprotected:\n    bool _test;\n    double _progress;\npublic:\n    TrainingWorker(ConvNet& convNet, CPUData& data, double progress, bool test);\n    void _run();\n};\n\nclass SyncWorker : public Worker {\npublic:\n    SyncWorker(ConvNet& convNet);\n    bool run();\n};\n\nclass ExitWorker : public Worker {\npublic:\n    ExitWorker(ConvNet& convNet);\n    bool run();\n};\n\nclass GradCheckWorker : public DataWorker {\npublic:\n    GradCheckWorker(ConvNet& convNet, CPUData& data);\n    void _run();\n};\n\nclass MultiviewTestWorker : public DataWorker {\nprotected:\n    int _numViews;\n    Matrix* _cpuProbs;\n    std::string _logregName;\n    CPUData& getMinibatch(int v, int i);\npublic:\n    MultiviewTestWorker(ConvNet& convNet, CPUData& data, int numViews, Matrix& cpuProbs, const char* softmaxName);\n    MultiviewTestWorker(ConvNet& convNet, CPUData& data, int numViews);\n    ~MultiviewTestWorker();\n    void _run();\n};\n\nclass FeatureWorker : public DataWorker {\nprotected:\n    MatrixV *_ftrs;\n    stringv *_layerNames;\n    bool _deleteFeatures;\npublic:\n    FeatureWorker(ConvNet& convNet, CPUData& data, MatrixV& ftrs, stringv& layerNames, bool deleteFeatures=true);\n    ~FeatureWorker();\n    void _run();\n};\n\nclass DataGradWorker : public DataWorker {\nprotected:\n    Matrix* _dataGrads;\n    int _dataLayerIdx, _softmaxLayerIdx;\npublic:\n    DataGradWorker(ConvNet& convNet, CPUData& data, Matrix& dataGrads, int dataLayerIdx, int softmaxLayerIdx);\n    ~DataGradWorker();\n    void _run();\n};\n\n#endif/* WORKER_CUH */\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/actbroadcaster.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#include \"../include/actbroadcaster.cuh\"\n\nusing namespace std;\n\n/*\n * =====================\n * BroadcastMessage\n * =====================\n */\nBroadcastMessage::BroadcastMessage(map<int, NVMatrix*> mats, int srcDevice, int userIdx, Queue<int>& finishQueue)\n    : _type(BROADCAST), _mats(mats), _srcDevice(srcDevice), _userIdx(userIdx), _finishQueue(&finishQueue) {\n}\n\nBroadcastMessage::BroadcastMessage(MESSAGE_TYPE type)\n    : _type(type), _finishQueue(NULL) {\n}\n\nint BroadcastMessage::getSrcDevice() {\n    return _srcDevice;\n}\n\nmap<int, NVMatrix*>& BroadcastMessage::getMatrices() {\n    return _mats;\n}\n\nint BroadcastMessage::getUserIdx() {\n    return _userIdx;\n}\n\nQueue<int>& BroadcastMessage::getFinishQueue() {\n    return *_finishQueue;\n}\n\nBroadcastMessage::MESSAGE_TYPE BroadcastMessage::getMessageType() {\n    return _type;\n}\n\n/*\n * =====================\n * ExitBroadcastMessage\n * =====================\n */\nExitBroadcastMessage::ExitBroadcastMessage() : BroadcastMessage(BroadcastMessage::EXIT) {\n}\n\n/*\n * =====================\n * ActBroadcaster\n * =====================\n */\nActBroadcaster::ActBroadcaster(int numUsers, intv& cpus) : Thread(true, cpus), _numUsers(numUsers) {\n}\n\nActBroadcaster::~ActBroadcaster() {\n    for (map<int,IBroadcastNetwork*>::const_iterator it = _broadcasters.begin(); it != _broadcasters.end(); ++it) {\n        delete it->second;\n    }\n}\n\nQueue<BroadcastMessage*>& ActBroadcaster::getMessageQueue() {\n    return _messageQueue;\n}\n\nvoid* ActBroadcaster::run() {\n    int nextUserIdx = 0;\n    bool exit = false;\n    while (!exit) {\n        BroadcastMessage& msg = *_messageQueue.dequeue();\n        if (msg.getMessageType() == BroadcastMessage::EXIT) {\n            exit = true;\n            delete &msg;\n        } else {\n            if (msg.getUserIdx() == nextUserIdx) {\n                if (_broadcasters.count(msg.getSrcDevice()) == 0) {\n                    _broadcasters[msg.getSrcDevice()] = &IBroadcastNetwork::make(getKeys(msg.getMatrices()), msg.getSrcDevice());\n                }\n                _broadcasters[msg.getSrcDevice()]->broadcast(msg.getMatrices());\n                msg.getFinishQueue().enqueue(0);\n                delete &msg;\n                nextUserIdx = (nextUserIdx + 1) % _numUsers;\n            } else {\n                _messageQueue.enqueue(&msg);\n            }\n        }\n    }\n    return NULL;\n}\n\nvoid ActBroadcaster::stop() {\n    getMessageQueue().enqueue(new ExitBroadcastMessage());\n    join();\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/convnet.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <vector>\n#include <iostream> \n#include <string>\n#include <set>\n#include <map>\n\n#include \"../../nvmatrix/include/nvmatrix.cuh\"\n#include \"../../nvmatrix/include/nvmatrix_operators.cuh\"\n#include \"../../util/include/matrix.h\"\n#include \"../include/convnet.cuh\"\n#include \"../include/util.cuh\"\n\nusing namespace std;\n\n/* \n * =======================\n * ConvNet\n * =======================\n */\nConvNet::ConvNet(PyObject* layerParams, intv& deviceIDs,\n                 int minibatchSize, bool conserveMem) : Thread(true) {\n    _deviceIDs = deviceIDs;\n    _data = NULL;\n    _bufferData = NULL;\n    _bufferMinibatchIdx = -1;\n    _bufferPassIdx = -1;\n    _trainingProgress = 0;\n    _totalPassesDone = 0;\n    _conserveMem = conserveMem;\n    _sync = new ThreadSynchronizer(deviceIDs.size() + 1);\n    PyObjectV* layerList = pyDictGetValues(layerParams);\n    std::sort(layerList->begin(), layerList->end(), LayerIDComparator());\n\n    \n    _dataCopyPD = new PipeDispenserBlocking(DIVUP(_deviceIDs.size(),2)); // hard-coded for now\n\n    initDataLayers(layerList);\n    initGPUThreads(layerList);\n    connectReplicas();              // Connect replicas to one another\n    connectChildren(layerParams);   // Connect forward/backward links in graph\n    _numFwdTerminal = 0;\n    // Execute post-initialization stuff\n    for (NameReplicaLayerMap::iterator it = _layerMap.begin(); it != _layerMap.end(); ++it) {\n        for (int r = 0; r < it->second.size(); r++) {\n            _numFwdTerminal += it->second[r]->getNext().size() == 0;\n            if (it->second[r]->getNext().size() == 0) {\n                printf(\"Fwd terminal: %s\\n\", it->second[r]->getName().c_str());\n            }\n            it->second[r]->postInit();\n        }\n    }\n\n    // Find and count the terminal nodes in the backward pass\n    for (int p = 0; p < getNumPasses(); p++) {\n        set<Layer*> visited;\n        _numBwdTerminal[p] = 0;\n        for (int t = 0; t < _convNetThreads.size(); t++) {\n            vector<CostLayer*>& cl = _convNetThreads[t]->getCostLayers();\n            for (int c = 0; c < cl.size(); c++) {\n                findBwdTerminal(*cl[c], visited, _numBwdTerminal[p], p);\n            }\n        }\n    }\n\n    _dp = new DataProvider(minibatchSize);\n//    Py_DECREF(layerList);\n    delete layerList;\n}\n\nConvNet::~ConvNet() {\n    for (vector<ConvNetThread*>::const_iterator it = _convNetThreads.begin(); it != _convNetThreads.end(); ++it) {\n        (*it)->getMessageQueue().enqueue(new Message(EXIT_CONVNET));\n        (*it)->join();\n        delete *it;\n    }\n    for (DataLayerVector::const_iterator it = _dataLayers.begin(); it != _dataLayers.end(); ++it) {\n        delete *it;\n    }\n    for (intv::const_iterator it = _deviceIDs.begin(); it != _deviceIDs.end(); ++it) {\n        DEVICE_MEMORY_MANAGER::destroyInstance(*it);\n    }\n    HOST_MEMORY_MANAGER::destroyInstance();\n    delete _sync;\n    delete _dataCopyPD;\n    delete _dp;\n}\n\nvoid ConvNet::stop() {\n    getWorkerQueue().enqueue(new ExitWorker(*this));\n    join();\n}\n\nPipeDispenser& ConvNet::getDataCopyPD() {\n    return *_dataCopyPD;\n}\n\nvoid ConvNet::initDataLayers(PyObjectV* layerList) {\n    for (int i = 0; i < layerList->size(); i++) {\n        PyObject* paramsDict = layerList->at(i);\n        std::string layerType = pyDictGetString(paramsDict, \"type\");\n\n        if (layerType == \"data\") {\n            int numReplicas = pyDictGetInt(paramsDict, \"numReplicas\");\n            for (int r = 0; r < numReplicas; ++r) {\n                DataLayer* dataLayer = new DataLayer(this, paramsDict, r);\n                _dataLayers.push_back(dataLayer);\n                _layerMap[dataLayer->getName()][r] = dataLayer;\n            }\n        }\n    }\n}\n\nvoid ConvNet::initGPUThreads(PyObjectV* layerList) {\n    // Initialize GPU worker threads\n    for (int i = 0; i < _deviceIDs.size(); ++i) {\n        ConvNetThread* cng = new ConvNetThread(layerList, _deviceIDs[i], i, this);\n        _convNetThreads.push_back(cng);\n        for (NameLayerMap::iterator it = cng->getLayerMap().begin(); it != cng->getLayerMap().end(); ++it) {\n            const std::string& name = it->first;\n            Layer* layer = it->second;\n            _layerMap[name][layer->getReplicaID()] = layer;\n        }\n    }\n}\n\nvoid ConvNet::connectReplicas() {\n    _numReplicasMax = 0;\n    _numReplicasMin = 1 << 16;\n    for (NameReplicaLayerMap::iterator it = _layerMap.begin(); it != _layerMap.end(); ++it) {\n        _numReplicasMax = max(_numReplicasMax, int(it->second.size()));\n        _numReplicasMin = min(_numReplicasMin, int(it->second.size()));\n        for (map<int,Layer*>::iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) {\n            Layer& l1 = *it2->second;\n            for (map<int,Layer*>::iterator it3 = it->second.begin(); it3 != it->second.end(); ++it3) {\n                Layer& l2 = *it3->second;\n                l1.addReplica(l2);\n            }\n        }\n    }\n}\n\nvoid ConvNet::connectChildren(PyObject* layerParams) {\n    for (NameReplicaLayerMap::iterator it = _layerMap.begin(); it != _layerMap.end(); ++it) {\n        PyObject* paramsDict = PyDict_GetItemString(layerParams, it->first.c_str());\n        PyObject* inputList = PyDict_GetItemString(paramsDict, \"inputs\");\n        if (inputList != NULL) {\n            // Iterate over \"replicas\" of this layer\n            int numReplicas = _layerMap[it->first].size();\n            for (int i = 0; i < PyList_GET_SIZE(inputList); i++) {\n                std::string inputName = PyString_AsString(PyList_GetItem(inputList, i));\n                int numReplicasPrev = _layerMap[inputName].size();\n                // How many replicas from the previous layer must this layer be connected to?\n                int numInputReplicas = numReplicasPrev / numReplicas;\n                for (int r = 0; r < numReplicas; r++) {\n                    for (int rp = r, ridx = 0; ridx < numInputReplicas; rp += numReplicas, ridx++) {\n                        it->second[r]->addPrev(*_layerMap[inputName][rp], ridx);\n                        _layerMap[inputName][rp]->addNext(*it->second[r]);\n                    }\n                }\n            }\n        }\n    }\n}\n\nvoid ConvNet::findBwdTerminal(Layer& l, set<Layer*>& visited, int& terminal, int passIdx) {\n    if (visited.count(&l) == 0) {\n        visited.insert(&l);\n        if (l.isGradConsumer()) {\n            bool hasPrevConsumer = false;\n            if (l.getPrev().size() > 0) {\n                for (int i = 0; i < l.getPrev()[0].size(); i++) {\n                    // Looking only at 0th replica is fine to see if you have\n                    // grad consumers below you.\n                    hasPrevConsumer |= l.getPrev()[0][i]->isGradConsumer();\n                }\n            }\n            if (!hasPrevConsumer || !l.isGradProducer() || (passIdx + 1 < l.getNumReplicasPrev() && l.getNumReplicasPrev() > l.getNumReplicas())) {\n                terminal++;\n                l.setBwdTerminal(passIdx);\n                printf(\"found bwd terminal %s[%d] in passIdx=%d\\n\", l.getName().c_str(), l.getReplicaID(), passIdx);\n            } else if (l.isGradProducer()) {\n                for (int r = 0; r < l.getPrev().size(); r++) {\n                    for (int i = 0; i < l.getPrev()[r].size(); i++) {\n                        findBwdTerminal(*l.getPrev()[r][i], visited, terminal, passIdx);\n                    }\n                }\n            }\n        }\n    }\n}\n\nvoid* ConvNet::run() {\n    for (vector<ConvNetThread*>::const_iterator it = _convNetThreads.begin(); it != _convNetThreads.end(); ++it) {\n        (*it)->start();\n    }\n    // The manager thread defaults to using the GPU of the first worker.\n    // Put more logic here if this is inappropriate.\n    NVMatrix::setDeviceID(_convNetThreads[0]->getDeviceID());\n    copyToGPU();\n    bool exit = false;\n    while (!exit) {\n        Worker* worker = _workerQueue.dequeue();\n        exit = worker->run();\n        delete worker;\n    }\n\n    return NULL;\n}\n\nQueue<Worker*>& ConvNet::getWorkerQueue() {\n    return _workerQueue;\n}\n\nQueue<WorkResult*>& ConvNet::getResultQueue() {\n    return _resultQueue;\n}\n\nDataProvider& ConvNet::getDataProvider() {\n    return *_dp;\n}\n\nLayer& ConvNet::getLayer(std::string& name, int replicaID) {\n    return *_layerMap[name][replicaID];\n}\n\nvoid ConvNet::sendMessage(MESSAGES msg, bool sync) {\n    sendMessage(new Message(msg), sync);\n}\n\nvoid ConvNet::sendMessage(Message* msg, bool sync) {\n    for (int i = 0; i < _convNetThreads.size(); i++) {\n        _convNetThreads[i]->getMessageQueue().enqueue(msg->clone());\n    }\n\n    delete msg;\n\n    if (sync) {\n        syncWithChildren();\n    }\n}\n\nvoid ConvNet::copyToCPU() {\n    sendMessage(COPY_TO_CPU, true);\n}\n\nvoid ConvNet::copyToGPU() {\n    sendMessage(COPY_TO_GPU, false);\n}\n\nvoid ConvNet::updateWeights(int passIdx) {\n    sendMessage(UPDATE_WEIGHTS, true);\n    sendMessage(CONSTRAIN_WEIGHTS, true);\n}\n\nvoid ConvNet::reset(int passIdx) {\n    sendMessage((passIdx % getNumPasses()) == 0 ? RESET : RESET_PASS_IDX, false);\n}\n\nvoid ConvNet::reset() {\n    reset(0);\n}\n\n// Fprop given data\nvoid ConvNet::fprop(CPUData& data, int passIdx, PASS_TYPE passType) {\n    reset(passIdx);\n    // This is necessary because setData below could delete data. If there's\n    // an outstanding copy request, this'll cause a segfault.\n    for (int i = 0; i < _dataLayers.size(); i++) {\n        _dataLayers[i]->waitForCopyFinish();\n    }\n\n    setData(data, passIdx);\n    for (int i = 0; i < _dataLayers.size(); i++) {\n        _dataLayers[i]->fprop(passType, passIdx, false);\n    }\n    waitForTerminals(_numFwdTerminal, FPROP_TERMINAL);\n}\n\n// Fprop given minibatch idx\nvoid ConvNet::fprop(int miniIdx, int passIdx, PASS_TYPE passType) {\n    reset(passIdx);\n\n    bool fromBuffer = miniIdx == _bufferMinibatchIdx && passIdx == _bufferPassIdx;\n    if (!fromBuffer) {\n        // This is necessary because setData below could delete data. If there's\n        // an outstanding copy request, this'll cause a segfault.\n        for (int i = 0; i < _dataLayers.size(); i++) {\n            _dataLayers[i]->waitForCopyFinish();\n        }\n\n        setData(_dp->getMinibatch(miniIdx), passIdx);\n\n    } else {\n        setDataFromBuffer();\n    }\n    for (int i = 0; i < _dataLayers.size(); i++) {\n        _dataLayers[i]->fprop(passType, passIdx, fromBuffer);\n    }\n\n    if (passIdx == getNumPasses() - 1) {\n        // Do double-buffering from next minibatch from the DataProvider\n        setBuffer(miniIdx == _dp->getNumMinibatches() - 1 ? NULL : &_dp->getMinibatch(miniIdx + 1), miniIdx + 1, 0);\n    } else {\n        // Do double-buffering from next microbatch within current minibatch\n        setBuffer(_data, miniIdx, passIdx + 1);\n    }\n\n    waitForTerminals(_numFwdTerminal, FPROP_TERMINAL);\n}\n\nvoid ConvNet::setDataFromBuffer() {\n    if (_bufferData != _data) {\n        delete _data;\n    }\n    _data = _bufferData;\n    _bufferData = NULL;\n    _bufferMinibatchIdx = -1;\n    _bufferPassIdx = -1;\n}\n\nvoid ConvNet::setData(CPUData& data, int passIdx) {\n    bool same = _data == _bufferData;\n    if (&data != _data) {\n        delete _data;\n    }\n    if (&data != _bufferData && !same) {\n        delete _bufferData;\n        _bufferData = NULL;\n        _bufferMinibatchIdx = -1;\n        _bufferPassIdx = -1;\n    }\n    _data = &data;\n    for (int i = 0; i < _dataLayers.size(); i++) {\n        _dataLayers[i]->copyData(*_data, false, passIdx);\n    }\n}\n\nvoid ConvNet::setBuffer(CPUData* bufferData, int bufferMinibatchIdx, int bufferPassIdx) {\n    _bufferData = bufferData;\n    _bufferMinibatchIdx = bufferMinibatchIdx;\n    _bufferPassIdx = bufferPassIdx;\n    if (bufferData != NULL) {\n        for (int i = 0; i < _dataLayers.size(); i++) {\n            _dataLayers[i]->copyData(*_bufferData, true, bufferPassIdx);\n        }\n    }\n}\n\nCPUData& ConvNet::getData() {\n    assert(_data != NULL);\n    return *_data;\n}\n\nvoid ConvNet::bprop(int passIdx, PASS_TYPE passType) {\n    _totalPassesDone++;\n    sendMessage(new BpropStartMessage(passType, passIdx), false);\n    waitForTerminals(_numBwdTerminal[passIdx], BPROP_TERMINAL);\n    reset(passIdx + 1);\n}\n\nvoid ConvNet::waitForTerminals(int numMsgs, MESSAGES msgType) {\n    for (int rcvd = 0; rcvd < numMsgs; rcvd++) {\n        Message* m = _msgQueue.dequeue();\n        assert(m->getType() == msgType);\n        delete m;\n    }\n}\n\n// Same as getCost() but adds results to given cost and returns it\nCost& ConvNet::getCost(Cost& cost) {\n    Cost &tmp = getCost();\n    cost += tmp;\n    delete &tmp;\n    return cost;\n}\n\nCost& ConvNet::getCost() {\n    Cost& cost = *new Cost();\n    for (int t = 0; t < _convNetThreads.size(); t++) {\n        Cost& tcost = _convNetThreads[t]->getCost();\n        cost += tcost;\n        delete &tcost;\n    }\n    return cost;\n}\n\ndouble ConvNet::getCostValue() {\n    Cost& cost = getCost();\n    double val = cost.getValue();\n    delete &cost;\n    return val;\n}\n\nQueue<Message*>& ConvNet::getMessageQueue() {\n    return _msgQueue;\n}\n\nintv& ConvNet::getDeviceIDs() {\n    return _deviceIDs;\n}\n\nThreadSynchronizer& ConvNet::getSync() {\n    return *_sync;\n}\n\nvoid ConvNet::syncWithChildren() {\n    sendMessage(SYNC, false);\n    _sync->sync();\n}\n\nint ConvNet::getTotalPassesDone() {\n    return _totalPassesDone;\n}\n\nint ConvNet::getMinibatchSize() {\n    return _dp->getMinibatchSize();\n}\n\nint ConvNet::getNumReplicasMax() {\n    return _numReplicasMax;\n}\n\nint ConvNet::getNumReplicasMin() {\n    return _numReplicasMin;\n}\n\nint ConvNet::getNumPasses() {\n    return _numReplicasMax / _numReplicasMin;\n}\n\nvoid ConvNet::setTrainingProgress(double progress) {\n    _trainingProgress = progress;\n}\n\ndouble ConvNet::getTrainingProgress() const {\n    return _trainingProgress;\n}\n\nbool ConvNet::isConserveMemory() {\n    return _conserveMem;\n}\n\n/*\n * Gradient checking stuff\n */\nvoid ConvNet::checkGradients() {\n    _numFailures = 0;\n    _numTests = 0;\n    _baseErr = 0;\n    for (int p = 0; p < getNumPasses(); ++p) {\n        fprop(0, p, PASS_GC);\n        _baseErr += getCostValue();\n        bprop(p, PASS_GC);\n    }\n    // We call grad check only on the first replica,\n    // but because weights are aware of their fellow replicas,\n    // we can simultaneously perturb the weights of all\n    // replicas.\n    for (NameReplicaLayerMap::iterator it = _layerMap.begin(); it != _layerMap.end(); ++it) {\n        map<int, Layer*>& layers = it->second;\n        if (layers[0]->getDeviceID() >= 0 /*&& (layers[0]->getName() == \"fc10\")*/) { // If layer on GPU (data layers aren't)\n            layers[0]->checkGradient();\n        }\n    }\n\n    cout << \"------------------------\" << endl;\n    if (_numFailures > 0) {\n        cout << _numFailures << \"/\" << _numTests << \" TESTS FAILED\" << endl;\n    } else {\n        cout << \"ALL \" << _numTests << \" TESTS PASSED\" << endl;\n    }\n}\n\n// Copies to all replicas\nvoid ConvNet::checkGradient_copyWeightsToGPU(Matrix& weightsCPU, Weights& weights) {\n    int d = NVMatrix::getDeviceID();\n    for (map<int, Weights*>::const_iterator it = weights.getReplicas().begin(); it != weights.getReplicas().end(); ++it) {\n        NVMatrix::setDeviceID(it->second->getDeviceID());\n        it->second->getW().copyFromHost(weightsCPU);\n    }\n    NVMatrix::setDeviceID(d);\n}\n\n/*\n * name: weight matrix name\n * eps: finite difference step\n */\nbool ConvNet::checkGradient(const std::string& name, float eps, Weights& weights) {\n    Matrix numGrad(weights.getNumRows(), weights.getNumCols());\n    Matrix diff(numGrad);\n    numGrad.apply(Matrix::ZERO);\n    Matrix weightsCPU;\n\n    weights.getW().copyToHost(weightsCPU, true);\n\n    for(int i = 0; i < weights.getNumRows(); i++) {\n        for (int j = 0; j < weights.getNumCols(); j++) {\n            float v = weightsCPU(i,j);\n            weightsCPU(i,j) += eps;\n\n            checkGradient_copyWeightsToGPU(weightsCPU, weights);\n\n            weightsCPU(i,j) = v;\n            double err = 0;\n            for (int p = 0; p < getNumPasses(); ++p) {\n//                printf(\"trying fprop %d\\n\", p);\n                fprop(0, p, PASS_GC);\n//                printf(\"    success\\n\");\n                err += getCostValue();\n            }\n            numGrad(i,j) = (err - _baseErr) / (_data->getNumCases() * eps);\n            if (isnan((double)numGrad(i,j)) || isinf((double)numGrad(i,j))) {\n                cout << \"Numerical computation produced nan or inf when checking '\" << name << \"': \" << numGrad(i,j) << endl;\n                cout << \"Consider reducing the sizes of the weights or finite difference steps.\" << endl;\n                cout << \"Exiting.\" << endl;\n                exit(1);\n            }\n            checkGradient_copyWeightsToGPU(weightsCPU, weights);\n        }\n    }\n    Matrix gradCPU;\n    NVMatrix::setDeviceID(weights.getDeviceID());\n    map<int,NVMatrix*> mats;\n    for (map<int, Weights*>::const_iterator it = weights.getReplicas().begin(); it != weights.getReplicas().end(); ++it) {\n        mats[it->first] = &it->second->getGrad();\n    }\n    weights.getReducer().reduce(mats, 1, false);\n\n    weights.getGrad().copyToHost(gradCPU, true);\n    gradCPU.scale(-1.0 / _data->getNumCases());\n    float analNorm = gradCPU.norm();\n    float numNorm = numGrad.norm();\n    numGrad.subtract(gradCPU, diff);\n    float relErr = diff.norm() / analNorm;\n    bool fail = relErr >= GC_REL_ERR_THRESH;\n    if (fail || !GC_SUPPRESS_PASSES) {\n        cout << \"========================\" << endl;\n        printf(\"(%s) %s GRADIENT CHECK\\n\", fail ? \"****FAIL****\" : \"PASS\", name.c_str());\n        cout << \"========================\" << endl;\n        cout << \"Analytic:\" << endl;\n        gradCPU.print(0, 6, 0, 4);\n        cout << \"Numeric:\" << endl;\n        numGrad.print(0, 6, 0, 4);\n        printf(\"Analytic norm: %e\\n\", analNorm);\n        printf(\"Numeric norm:  %e\\n\", numNorm);\n        printf(\"Relative error: %e\\n\", relErr);\n    }\n    _numTests++;\n    _numFailures += fail;\n    return fail;\n}\n\n/* \n * =======================================================================================================\n * ConvNetThread\n * =======================================================================================================\n */\nConvNetThread::ConvNetThread(PyObjectV* layerList, int deviceID, int deviceIdx, ConvNet* convNet)\n    : Thread(true, getDeviceCPUs(deviceID)), _deviceID(deviceID), _convNet(convNet) {\n    try {\n        int numLayers = layerList->size();\n\n        for (int i = 0; i < numLayers; i++) {\n            PyObject* paramsDict = layerList->at(i);\n            std::string layerType = pyDictGetString(paramsDict, \"type\");\n            if (layerType != \"data\") {\n                intv& gpus = *pyDictGetIntV(paramsDict, \"gpu\");\n                int rid = indexOf(gpus, deviceIdx);\n                if (rid >= 0) {\n                    initLayer(paramsDict, rid);\n                }\n                delete &gpus;\n            }\n        }\n    } catch (std::string& s) {\n        cout << \"Error creating ConvNet: \" << s << endl;\n        exit(1);\n    }\n}\n\nConvNetThread::~ConvNetThread() {\n    NVMatrix::setDeviceID(_deviceID);\n    NVMatrix::destroyCublas();\n    NVMatrix::destroyRandom();\n    for (NameLayerMap::const_iterator it = _nameLayerMap.begin(); it != _nameLayerMap.end(); ++it) {\n        delete it->second;\n    }\n    _nameLayerMap.clear();\n}\n\nvoid ConvNetThread::startTimer() {\n    NVMatrix::syncStream();\n    _timer.start();\n}\n\ndouble ConvNetThread::stopTimer() {\n    NVMatrix::syncStream();\n    return _timer.stop();\n}\n\nvoid ConvNetThread::initLayer(PyObject* paramsDict, int replicaID) {\n    std::string type = pyDictGetString(paramsDict, \"type\");\n    std::string name = pyDictGetString(paramsDict, \"name\");\n    if (type == \"fc\") {\n        _nameLayerMap[name] = new FCLayer(this, paramsDict, replicaID, false);\n    } else if (type == \"sfc\") {\n        _nameLayerMap[name] = new SplitFCLayer(this, paramsDict, replicaID, false);\n    } else if (type == \"conv\") {\n        _nameLayerMap[name] = new ConvLayer(this, paramsDict, replicaID);\n    } else if (type == \"local\") {\n        _nameLayerMap[name] = new LocalUnsharedLayer(this, paramsDict, replicaID);\n    } else if (type == \"pool\") {\n        _nameLayerMap[name] = &PoolLayer::make(this, paramsDict, replicaID);\n    } else if (type == \"cmpool\") {\n        _nameLayerMap[name] = &CrossMapPoolLayer::make(this, paramsDict, replicaID);\n    } else if (type == \"rnorm\") {\n        _nameLayerMap[name] = new ResponseNormLayer(this, paramsDict, replicaID);\n    } else if (type == \"cmrnorm\") {\n        _nameLayerMap[name] = new CrossMapResponseNormLayer(this, paramsDict, replicaID);\n    } else if (type == \"cnorm\") {\n        _nameLayerMap[name] = new ContrastNormLayer(this, paramsDict, replicaID);\n    } else if (type == \"softmax\") {\n        _nameLayerMap[name] = new SoftmaxLayer(this, paramsDict, replicaID);\n    } else if (type == \"eltsum\") {\n        _nameLayerMap[name] = new EltwiseSumLayer(this, paramsDict, replicaID);\n    } else if (type == \"eltmax\") {\n        _nameLayerMap[name] = new EltwiseMaxLayer(this, paramsDict, replicaID);\n    } else if (type == \"neuron\") {\n        _nameLayerMap[name] = new NeuronLayer(this, paramsDict, replicaID);\n    } else if (type == \"nailbed\") {\n        _nameLayerMap[name] = new NailbedLayer(this, paramsDict, replicaID);\n    } else if (type == \"blur\") {\n        _nameLayerMap[name] = new GaussianBlurLayer(this, paramsDict, replicaID);\n    } else if (type == \"href\") {\n        _nameLayerMap[name] = new HorizontalReflectionLayer(this, paramsDict, replicaID);\n    } else if (type == \"resize\") {\n        _nameLayerMap[name] = new ResizeLayer(this, paramsDict, replicaID);\n    } else if (type == \"rgb2yuv\") {\n        _nameLayerMap[name] = new RGBToYUVLayer(this, paramsDict, replicaID);\n    } else if (type == \"rgb2lab\") {\n        _nameLayerMap[name] = new RGBToLABLayer(this, paramsDict, replicaID);\n    } else if (type == \"rscale\") {\n        _nameLayerMap[name] = new RandomScaleLayer(this, paramsDict, replicaID);\n    } else if (type == \"crop\") {\n        _nameLayerMap[name] = new CropLayer(this, paramsDict, replicaID);\n    } else if (type == \"concat\") {\n        _nameLayerMap[name] = new ConcatenationLayer(this, paramsDict, replicaID);\n    } else if (type == \"pass\") {\n        _nameLayerMap[name] = new PassThroughLayer(this, paramsDict, replicaID);\n    } else if (type == \"dropout\") {\n        _nameLayerMap[name] = new DropoutLayer(this, paramsDict, replicaID);\n    } else if (type == \"dropout2\") {\n        _nameLayerMap[name] = new Dropout2Layer(this, paramsDict, replicaID);\n    } else if (strncmp(type.c_str(), \"cost.\", 5) == 0) {\n        CostLayer *c = &CostLayer::make(this, paramsDict, type, replicaID);\n        _nameLayerMap[name] = c;\n        _costs.push_back(c);\n    } else {\n        throw std::string(\"Unknown layer type \") + type;\n    }\n}\n\n/*\n * This executes in a new CPU thread so it's OK to initialize CUDA stuff here. \n */\nvoid ConvNetThread::initCuda() { \n    NVMatrix::setDeviceID(_deviceID);\n    checkCudaErrors(cudaDeviceSetCacheConfig(cudaFuncCachePreferShared));\n    for (int i = 0; i < _convNet->getDeviceIDs().size(); i++) {\n        int d = _convNet->getDeviceIDs()[i];\n        if (d != _deviceID) {\n            if (NVMatrix::canAccessPeer(_deviceID, d)) {\n                printf(\"Enabling peer access GPU %d --> GPU %d\\n\", NVMatrix::getDeviceID(), d);\n                checkCudaErrors(cudaDeviceEnablePeerAccess(d, 0));\n            } else {\n                printf(\"No peer access GPU %d -->  GPU %d\\n\", _deviceID, d);\n            }\n        }\n    }\n//    NVMatrix::syncStream();\n    NVMatrix::initCublas();\n    NVMatrix::initRandom(/*7*/);\n    srand(time(0));\n}\n\nvoid* ConvNetThread::run() {\n    initCuda();\n    bool exit = false;\n    while (!exit) {\n        Message* m = _msgQueue.dequeue();\n        if (m->getType() == FPROP_READY) {\n            FpropMessage* msg = static_cast<FpropMessage*>(m);\n            msg->getToLayer().fprop(msg->getPassType(), msg->getPassIdx());\n        } else if (m->getType() == BPROP_READY) {\n            BpropMessage* msg = static_cast<BpropMessage*>(m);\n            msg->getToLayer().incRcvdBInputMsgs();\n            msg->getToLayer().bprop(msg->getPassType(), msg->getPassIdx());\n        } else if (m->getType() == BPROP_START) {\n            BpropStartMessage* msg = static_cast<BpropStartMessage*>(m);\n            for (int i = 0; i < _costs.size(); i++) {\n                dynamic_cast<Layer*>(_costs[i])->bprop(msg->getPassType(), msg->getPassIdx());\n            }\n        } else if (m->getType() == SYNC) {\n            NVMatrix::syncStream();\n            _convNet->getSync().sync();\n        } else if (m->getType() == COPY_TO_CPU) {\n            for (NameLayerMap::iterator it = _nameLayerMap.begin(); it != _nameLayerMap.end(); ++it) {\n                it->second->copyToCPU();\n            }\n        } else if (m->getType() == COPY_TO_GPU) {\n            for (NameLayerMap::iterator it = _nameLayerMap.begin(); it != _nameLayerMap.end(); ++it) {\n                it->second->copyToGPU();\n            }\n        } else if (m->getType() == RESET) {\n            for (NameLayerMap::iterator it = _nameLayerMap.begin(); it != _nameLayerMap.end(); ++it) {\n                it->second->reset();\n            }\n        } else if (m->getType() == RESET_PASS_IDX) {\n            for (NameLayerMap::iterator it = _nameLayerMap.begin(); it != _nameLayerMap.end(); ++it) {\n                it->second->resetPassIdx();\n            }\n        } else if (m->getType() == UPDATE_WEIGHTS) {\n            for (NameLayerMap::iterator it = _nameLayerMap.begin(); it != _nameLayerMap.end(); ++it) {\n                it->second->updateWeights();\n            }\n        } else if (m->getType() == CONSTRAIN_WEIGHTS) {\n            for (NameLayerMap::iterator it = _nameLayerMap.begin(); it != _nameLayerMap.end(); ++it) {\n                it->second->constrainWeights();\n            }\n        } else if (m->getType() == EXIT_CONVNET) {\n            exit = true;\n        }\n        delete m;\n    }\n    return NULL;\n}\n\nCost& ConvNetThread::getCost() {\n    // In a single ConvNetThread, all costs are guaranteed to be different\n    // (i.e. not replicas of one another)\n    return *new Cost(_costs);\n}\n\nLayer& ConvNetThread::getLayer(std::string& name) {\n    return *_nameLayerMap[name];\n}\n\nint ConvNetThread::getDeviceID() {\n    return _deviceID;\n}\n\nQueue<Message*>& ConvNetThread::getMessageQueue() {\n    return _msgQueue;\n}\n\nvector<CostLayer*>& ConvNetThread::getCostLayers() {\n    return _costs;\n}\n\nNameLayerMap& ConvNetThread::getLayerMap() {\n    return _nameLayerMap;\n}\n\nConvNet& ConvNetThread::getConvNet() {\n    return *_convNet;\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/copypipeline.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"../include/copypipeline.cuh\"\n//#include \"gpu_util.cuh\"\n\nusing namespace std;\n\n/* =========================\n * ICopySegment\n * =========================\n */\nICopySegment::ICopySegment(IBroadcastNetwork& parent, int deviceID, Queue<int>* finishQueue)\n    : _parent(&parent), _prev(NULL), _stream(NULL), _deviceID(deviceID), _finishQueue(finishQueue), Thread(true, getDeviceCPUs(parent.getSourceDeviceID())) {\n    _execDeviceID = _deviceID;\n}\n\nICopySegment::~ICopySegment() {\n    if (_stream != NULL) {\n        checkCudaErrors(cudaStreamDestroy(_stream));\n    }\n}\n\nvoid* ICopySegment::run() {\n    assert(_execDeviceID != DEVICE_HOST);\n    NVMatrix::setDeviceID(_execDeviceID);\n    checkCudaErrors(cudaStreamCreateWithFlags(&_stream, cudaStreamNonBlocking));\n    bool exit = false;\n    while (!exit) {\n        CopyMessage& msg = *_queue.dequeue();\n        if (msg.getType() == CopyMessage::EXIT) {\n            exit = true;\n        } else {\n            bool term = processMessage(msg);\n            if (term) {\n                assert(_finishQueue != NULL);\n                _finishQueue->enqueue(1);\n            }\n        }\n        delete &msg;\n    }\n    return NULL;\n}\n\nNVMatrix& ICopySegment::getChunk(NVMatrix& mat, int chunkSize, int chunkIdx) {\n    NVMatrix& line = mat.reshaped(1, mat.getNumElements());\n    int start = chunkIdx * chunkSize;\n    int end = min((chunkIdx+1) * chunkSize, mat.getNumElements());\n    NVMatrix& chunk = line.sliceCols(start, end);\n    delete &line;\n    return chunk;\n}\n\ninline NVMatrix& ICopySegment::getMatrix(CopyMessage& msg) {\n    if (getDeviceID() == DEVICE_HOST) {\n        return _hmat;\n    }\n    return msg.getMatrix(getDeviceID());\n}\n\nQueue<CopyMessage*>& ICopySegment::getQueue() {\n    return _queue;\n}\n\ninline int ICopySegment::getDeviceID() {\n    return _deviceID;\n}\n\nvoid ICopySegment::addPrev(ICopySegment& c) {\n    _prev = &c;\n    if (_deviceID == DEVICE_HOST) {\n        _execDeviceID = c.getDeviceID();\n    }\n}\n\nvoid ICopySegment::addNext(CopyPeer& c) {\n    _next.push_back(&c);\n    c.addPrev(*this);\n}\n\nbool ICopySegment::isTerminal() const {\n    return _next.size() == 0;\n}\n\n/* =========================\n * CopySource\n * =========================\n */\nCopySource::CopySource(IBroadcastNetwork& parent, int deviceID) : ICopySegment(parent, deviceID, NULL) {\n}\n\nbool CopySource::processMessage(CopyMessage& msg) {\n    assert(msg.getType() == CopyMessage::COPY_START);\n    int numChunks = min(getMatrix(msg).getNumElements(), max(COPY_MIN_CHUNKS, min(COPY_MAX_CHUNKS, DIVUP(getMatrix(msg).getNumElements(), COPY_MIN_CHUNK_SIZE))));\n    int chunkSize = DIVUP(getMatrix(msg).getNumElements(), numChunks);\n//                printf(\"num chunks: %d\\n\", numChunks);\n    for (int c = 0; c <= numChunks; ++c) {\n        for (vector<CopyPeer*>::const_iterator it = _next.begin(); it != _next.end(); ++it) {\n            (*it)->getQueue().enqueue(new CopyChunkMessage(c, chunkSize, numChunks, msg.getScaleSource(), msg.getScaleTargets(), msg.getMatrices()));\n        }\n    }\n    return false;\n}\n\ninline bool CopySource::isSource() const {\n    return true;\n}\n\n/* =========================\n * CopyPeer\n * =========================\n */\nCopyPeer::CopyPeer(IBroadcastNetwork& parent, int deviceID, Queue<int>* finishQueue) : ICopySegment(parent, deviceID, finishQueue) {\n}\n\nbool CopyPeer::processMessage(CopyMessage& msg) {\n    assert(msg.getType() == CopyMessage::COPY_CHUNK);\n    CopyChunkMessage& cmsg = *static_cast<CopyChunkMessage*>(&msg);\n    if (cmsg.getChunkIdx() < cmsg.getNumChunks()) {\n        if (!isTerminal() || (isTerminal() && msg.getScaleTargets() == 0)) {\n            getMatrix(msg).resize(_prev->getMatrix(msg));\n        }\n//        getMatrix(msg).printShape(\"getMatrix(msg)\");\n//        _prev->getMatrix(msg).printShape(\"_prev->getMatrix(msg)\");\n        assert(getMatrix(msg).isSameDims(_prev->getMatrix(msg)));\n        const float scaleSelf = isTerminal() ? msg.getScaleTargets() : 0;\n        const float scalePrev = _prev->isSource() ? msg.getScaleSource() : 1;\n        NVMatrix& prevChunk = getChunk(_prev->getMatrix(msg), cmsg.getChunkSize(), cmsg.getChunkIdx());\n        NVMatrix& myChunk = getChunk(getMatrix(msg), cmsg.getChunkSize(), cmsg.getChunkIdx());\n        prevChunk.add(myChunk, scalePrev, scaleSelf, myChunk, _stream);\n        NVMatrix::syncStream(_stream);\n        delete &prevChunk;\n        delete &myChunk;\n    }\n    for (vector<CopyPeer*>::const_iterator it = _next.begin(); it != _next.end(); ++it) {\n        (*it)->getQueue().enqueue(new CopyChunkMessage(cmsg));\n    }\n    return cmsg.getChunkIdx() >= cmsg.getNumChunks() && isTerminal();\n}\n\ninline bool CopyPeer::isSource() const {\n    return false;\n}\n\n/* =========================\n * IBroadcastNetwork\n * =========================\n */\nIBroadcastNetwork& IBroadcastNetwork::make(set<int> devices, int srcDevice) {\n    if (devices.size() == 8) {\n        return (new EightGPUBroadcaster1(devices, srcDevice))->construct();\n    } else if (devices.size() == 1) {\n        return (new NullBroadcaster(devices, srcDevice))->construct();\n    } else if (devices.size() == 2 && NVMatrix::canAccessPeer(*devices.begin(), *(++devices.begin()))) {\n        return (new TwoPeeringGPUsBroadcaster(devices, srcDevice))->construct();\n    }\n    return (new NaiveBroadcaster(devices, srcDevice))->construct();\n}\n\nIBroadcastNetwork::IBroadcastNetwork(set<int>& devices, int srcDeviceID, int numTerminal)\n    : _devices(devices), _srcDeviceID(srcDeviceID), _numTerminal(numTerminal), _constructed(false), _src(NULL) {\n}\n\nIBroadcastNetwork::~IBroadcastNetwork() {\n    vector<ICopySegment*> v;\n    v.insert(v.end(), _peers.begin(), _peers.end());\n    v.insert(v.end(), _src);\n    for (vector<ICopySegment*>::const_iterator it = v.begin(); it != v.end(); ++it) {\n        (*it)->getQueue().enqueue(new CopyMessage(CopyMessage::EXIT));\n        (*it)->join();\n        delete *it;\n    }\n}\n\nIBroadcastNetwork& IBroadcastNetwork::construct() {\n    assert(!_constructed);\n    pair<vector<int>,vector<int> > gpus = makeGPULists();\n    _src = new CopySource(*this, _srcDeviceID);\n    makePeers(gpus);\n    makeConnections();\n    _src->start();\n    for (vector<CopyPeer*>::const_iterator it = _peers.begin(); it != _peers.end(); ++it) {\n        (*it)->start();\n    }\n    _constructed = true;\n    return *this;\n}\n\npair<vector<int>,vector<int> > IBroadcastNetwork::makeGPULists() {\n    vector<int> same, other;\n    for (set<int>::const_iterator it = _devices.begin(); it != _devices.end(); ++it) {\n        if (*it != _srcDeviceID) {\n            if (NVMatrix::canAccessPeer(_srcDeviceID, *it)) {\n                same.insert(same.begin() + rand() % (1 + same.size()), *it);\n            } else {\n                other.insert(other.begin() + rand() % (1 + other.size()), *it);\n            }\n        }\n    }\n    return pair<vector<int>,vector<int> >(same, other);\n}\n\nvoid IBroadcastNetwork::broadcast(std::map<int, NVMatrix*>& mats) {\n    _broadcast(mats, 1, 0);\n}\n\nvoid IBroadcastNetwork::_broadcast(std::map<int, NVMatrix*>& mats, float scaleSource, float scaleTargets) {\n    assert(_constructed);\n    assert(_finishQueue.getNumElements() == 0);\n    assert(mats.size() == _devices.size());\n    assert(mats.size() > 1);\n    if (mats[_srcDeviceID]->getNumElements() == 0) {\n        for (map<int,NVMatrix*>::const_iterator it = mats.begin(); it != mats.end(); ++it) {\n            it->second->resize(*mats[_srcDeviceID]);\n        }\n    } else {\n        _src->getQueue().enqueue(new CopyStartMessage(scaleSource, scaleTargets, mats));\n        for (int i = 0; i < _numTerminal; ++i) {\n            _finishQueue.dequeue();\n        }\n    }\n    assert(_finishQueue.getNumElements() == 0);\n}\n\nint IBroadcastNetwork::getSourceDeviceID() const {\n    return _srcDeviceID;\n}\n\nvoid IBroadcastNetwork::makePeers(pair<vector<int>,vector<int> >& gpus) {\n    vector<int>& same = gpus.first, &other = gpus.second;\n    for (int i = 0; i < same.size(); ++i) {\n        _peers.push_back(new CopyPeer(*this, same[i], &_finishQueue));\n    }\n    for (int i = 0; i < other.size(); ++i) {\n        _peers.push_back(new CopyPeer(*this, other[i], &_finishQueue));\n    }\n    _peers.push_back(new CopyPeer(*this, DEVICE_HOST, &_finishQueue)); // peers[7]\n}\n\n/* =========================\n * ISafeBroadcastNetwork\n * =========================\n */\nISafeBroadcastNetwork& ISafeBroadcastNetwork::make(set<int> devices, int srcDevice) {\n    if (devices.size() == 1) {\n        return (new NullBroadcaster(devices, srcDevice))->construct();\n    } else if (devices.size() == 2 && NVMatrix::canAccessPeer(*devices.begin(), *(++devices.begin()))) {\n        return (new TwoPeeringGPUsBroadcaster(devices, srcDevice))->construct();\n    }\n    return (new NaiveBroadcaster(devices, srcDevice))->construct();\n}\n\nISafeBroadcastNetwork::ISafeBroadcastNetwork(std::set<int>& devices, int srcDeviceID, int numTerminal) : IBroadcastNetwork(devices, srcDeviceID, numTerminal) {\n}\n\nvoid ISafeBroadcastNetwork::broadcast(std::map<int, NVMatrix*>& mats, float scaleSource, float scaleTargets) {\n    _broadcast(mats, scaleSource, scaleTargets);\n}\n\nISafeBroadcastNetwork& ISafeBroadcastNetwork::construct() {\n    IBroadcastNetwork::construct();\n    return *this;\n}\n\n/* =========================\n * NullBroadcaster\n * =========================\n */\nNullBroadcaster::NullBroadcaster(std::set<int>& devices, int srcDeviceID) : ISafeBroadcastNetwork(devices, srcDeviceID, 0) {\n}\n\nvoid NullBroadcaster::makeConnections() {\n}\n\nNullBroadcaster& NullBroadcaster::construct() {\n    _constructed = true;\n    return *this;\n}\n\nvoid NullBroadcaster::broadcast(std::map<int, NVMatrix*>& mats, float scaleSource, float scaleTargets) {\n}\n\nvoid NullBroadcaster::broadcast(std::map<int, NVMatrix*>& mats) {\n}\n\n/* =========================\n * NaiveBroadcaster\n * =========================\n *\n * This one does src -> host -> all\n */\nNaiveBroadcaster::NaiveBroadcaster(std::set<int>& devices, int srcDeviceID) : ISafeBroadcastNetwork(devices, srcDeviceID, devices.size()-1) {\n}\n\nvoid NaiveBroadcaster::makeConnections() {\n    _src->addNext(*_peers.back()); // Make connection src -> host\n    for (int i = 0; i < _peers.size() - 1; ++i) {\n        if (_peers[i]->getDeviceID() != _src->getDeviceID()) {\n            _peers.back()->addNext(*_peers[i]); // Make connection host -> peer\n        }\n    }\n}\n\n/* =========================\n * EightGPUBroadcaster1\n * =========================\n *\n * This one does a fancy graph\n */\nEightGPUBroadcaster1::EightGPUBroadcaster1(set<int>& devices, int srcDeviceID) : IBroadcastNetwork(devices, srcDeviceID, 4) {\n}\n\nvoid EightGPUBroadcaster1::makeConnections() {\n    _src->addNext(*_peers[7]);\n    _peers[7]->addNext(*_peers[0]);\n    _peers[7]->addNext(*_peers[1]);\n    _peers[7]->addNext(*_peers[3]);\n    _peers[7]->addNext(*_peers[4]);\n\n    _peers[1]->addNext(*_peers[2]);\n    _peers[3]->addNext(*_peers[5]);\n    _peers[4]->addNext(*_peers[6]);\n}\n\n/* =========================\n * TwoPeeringGPUsBroadcaster\n * =========================\n */\nTwoPeeringGPUsBroadcaster::TwoPeeringGPUsBroadcaster(std::set<int>& devices, int srcDeviceID) : ISafeBroadcastNetwork(devices, srcDeviceID, 0) {\n    _tgtDeviceID = *devices.begin() == srcDeviceID ? *(++devices.begin()) : *devices.begin();\n}\n\nTwoPeeringGPUsBroadcaster::~TwoPeeringGPUsBroadcaster() {\n    if (_constructed) {\n        checkCudaErrors(cudaStreamDestroy(_tgtStream));\n    }\n}\n\nvoid TwoPeeringGPUsBroadcaster::makeConnections() {\n}\n\nvoid TwoPeeringGPUsBroadcaster::resetDeviceID(int d) {\n    if (d >= 0) {\n        NVMatrix::setDeviceID(d);\n    }\n}\n\nISafeBroadcastNetwork& TwoPeeringGPUsBroadcaster::construct() {\n    assert(!_constructed);\n    int d = NVMatrix::getDeviceID();\n    NVMatrix::setDeviceID(_tgtDeviceID);\n    checkCudaErrors(cudaStreamCreateWithFlags(&_tgtStream, cudaStreamNonBlocking));\n    resetDeviceID(d);\n    _constructed = true;\n    return *this;\n}\n\nvoid TwoPeeringGPUsBroadcaster::_broadcast(std::map<int, NVMatrix*>& mats, float scaleSource, float scaleTargets) {\n    int d = NVMatrix::getDeviceID();\n    NVMatrix::setDeviceID(_tgtDeviceID);\n    mats[_tgtDeviceID]->add(*mats[_srcDeviceID], scaleTargets, scaleSource, *mats[_tgtDeviceID], _tgtStream);\n    NVMatrix::syncStream(_tgtStream);\n    resetDeviceID(d);\n}\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/cost.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n#include \"../include/cost.cuh\"\n\nusing namespace std;\n\n/* \n * =====================\n * Cost\n * =====================\n */\n\nCost::Cost() {\n}\n\nCost::Cost(vector<CostLayer*>& costs) {\n    for (vector<CostLayer*>::iterator it = costs.begin(); it != costs.end(); ++it) {\n        _costMap[(*it)->getName()] = &(*it)->getCost();\n        _costCoeffMap[(*it)->getName()] = (*it)->getCoeff();\n        _numCases[(*it)->getName()] = (*it)->getNumCases();\n    }\n}\n\nint Cost::getNumCases() {\n    return _numCases.size() == 0 ? 0 : _numCases.begin()->second;\n}\n\nmap<std::string,int>& Cost::getNumCasesMap() {\n    return _numCases;\n}\n\ndoublev& Cost::operator [](const std::string s) {\n    return *_costMap[s];\n}\n\nCostMap& Cost::getCostMap() {\n    return _costMap;\n}\n\nCostCoeffMap& Cost::getCostCoeffMap() {\n    return _costCoeffMap;\n}\n\ndouble Cost::getValue() {\n    double val = 0;\n    for (CostMap::iterator it = _costMap.begin(); it != _costMap.end(); ++it) {\n        val += _costCoeffMap[it->first] * (it->second->size() == 0 ? 0 : it->second->at(0));\n    }\n    return val;\n}\n\nCost& Cost::operator += (Cost& er) {\n    CostMap& otherMap = er.getCostMap();\n    CostCoeffMap& otherCoeffMap = er.getCostCoeffMap();\n\n    for (CostMap::const_iterator it = otherMap.begin(); it != otherMap.end(); ++it) {\n        bool newCost = _costMap.count(it->first) == 0;\n        if (newCost) {\n            _costMap[it->first] = new doublev();\n            _costCoeffMap[it->first] = otherCoeffMap[it->first];\n            _numCases[it->first] = er.getNumCasesMap()[it->first];\n        } else {\n            _numCases[it->first] += er.getNumCasesMap()[it->first];\n        }\n        \n        doublev& myVec = *_costMap[it->first];\n        doublev& otherVec = *otherMap[it->first];\n        assert(myVec.size() == 0 || otherVec.size() == 0 || myVec.size() == otherVec.size());\n        // Add costs from otherVec to me\n        for (int i = 0; i < otherVec.size(); i++) {\n            if (myVec.size() <= i) {\n                myVec.push_back(0);\n            }\n            myVec[i] += otherVec[i];\n        }\n    }\n    return *this;\n}\n\nCost::~Cost() {\n    for (CostMap::const_iterator it = _costMap.begin(); it != _costMap.end(); ++it) {\n        delete it->second;\n    }\n}\n\nvoid Cost::print() {\n    for (CostMap::const_iterator it = _costMap.begin(); it != _costMap.end(); ++it) {\n        printf(\"%s (%.3f): \", it->first.c_str(), _costCoeffMap[it->first]);\n        doublev& vec = *_costMap[it->first];\n        for (int z = 0; z < vec.size(); ++z) {\n            printf(\"%.3f\", vec[z]);\n            if (z < vec.size() - 1) {\n                printf(\", \");\n            }\n        }\n        printf(\"\\n\");\n    }\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/data.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <algorithm>\n#include <vector>\n#include \"../../util/include/matrix.h\"\n#include \"../include/data.cuh\"\n#include \"../include/timer.cuh\"\n\nusing namespace std;\n\nDataProvider::DataProvider(int minibatchSize) : \n    _minibatchSize(minibatchSize), _hData(NULL) {\n}\n\nvoid DataProvider::clearData() {\n    delete _hData;\n    _hData = NULL;\n}\n\nvoid DataProvider::setData(CPUData& hData) {\n    // DataWorker calls clearData\n    _hData = &hData;\n    assert(_hData != NULL);\n}\n\nCPUData& DataProvider::getMinibatch(int idx) {\n    assert(idx >= 0 && idx < getNumMinibatches());\n    return getDataSlice(idx * _minibatchSize, (idx + 1) * _minibatchSize);\n}\n\nCPUData& DataProvider::getDataSlice(int startCase, int endCase) {\n    assert(_hData != 0);\n    assert(_hData->getNumCases() > 0);\n    endCase = min(_hData->getNumCases(), endCase);\n    // TODO: maintain these matrices, no point re-creating them all the time\n    MatrixV& miniData = *new MatrixV();\n    \n    for (int i = 0; i < _hData->getData().size(); i++) {\n        // NOTE: if hData is transposed, then the output minibatch matrix\n        // can be a view. No need to allocate new CPU memory here. Might\n        // want to look into optimizing that in the future, though it's \n        // unlikely to be a big deal.\n        if (_hData->isTrans()) {\n            miniData.push_back(&(*_hData)[i].sliceCols(startCase, endCase));\n        } else {\n            miniData.push_back(new Matrix());\n            (*_hData)[i].sliceCols(startCase, endCase, *miniData.back());\n        }\n    }\n    CPUData& cpuData = *new CPUData(&miniData);\n    return *new CPUData(&miniData);\n}\n\nint DataProvider::getNumMinibatches() {\n    assert(_hData != 0);\n    assert(_hData->getNumCases() > 0);\n    return DIVUP(_hData->getNumCases(), _minibatchSize);\n}\n\nint DataProvider::getMinibatchSize() {\n    return _minibatchSize;\n}\n\nint DataProvider::getNumCases() {\n    assert(_hData != 0);\n    assert(_hData->getNumCases() > 0);\n    return _hData->getNumCases();\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/gradreducer.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"../include/util.cuh\"\n#include \"../include/gradreducer.cuh\"\n\nusing namespace std;\n\n/* =====================\n * IGradReducer\n * =====================\n */\nIActGradReducer::IActGradReducer(Layer& parent, map<int, int> numExpectedMsgs)\n    : Thread(true, getDeviceCPUs(parent.getDeviceID())), _parent(&parent), _numExpectedMsgs(numExpectedMsgs) {\n    _numExpectedMsgsTotal = 0;\n    for (map<int,int>::const_iterator it = numExpectedMsgs.begin(); it != numExpectedMsgs.end(); ++it) {\n        _numExpectedMsgsTotal += it->second;\n    }\n//    printf(\"%s[%d] expected %d backward msgs\\n\", parent.getName().c_str(), parent.getReplicaID(), _numExpectedMsgsTotal);\n}\n\nIActGradReducer::~IActGradReducer() {\n\n}\n\nvoid* IActGradReducer::run() {\n    while (true) {\n        reset();\n        if (reduce()) {\n            break;\n        }\n        _finishQueue.enqueue(0);\n    }\n    return NULL;\n}\n\n// Cost layer will have nothing to dequeue, so just return immediately.\nint IActGradReducer::waitForFinish() {\n    if (_numExpectedMsgsTotal > 0) {\n        int i = _finishQueue.dequeue();\n        assert(_finishQueue.getNumElements() == 0);\n        return i;\n    }\n//    printf(\"%s not waiting for finish\\n\", _name.c_str());\n    return 0;\n}\n\nIActGradReducer& IActGradReducer::makeGradReducer(Layer& parent, map<int, int> numExpectedMsgs) {\n    int tgtDeviceID = parent.getDeviceID();\n    if (numExpectedMsgs.count(tgtDeviceID) == 0) {\n        numExpectedMsgs[tgtDeviceID] = 0;\n    }\n    if (numExpectedMsgs.size() == 8) {\n        return *new ParallelActGradReducer(parent, numExpectedMsgs);\n    }\n    return *new SequentialActGradReducer(parent, numExpectedMsgs);\n}\n\n/* =====================\n * SequentialGradReducer\n * =====================\n */\nSequentialActGradReducer::SequentialActGradReducer(Layer& parent, map<int, int> numExpectedMsgs)\n    : IActGradReducer(parent, numExpectedMsgs) {\n    intv deviceIDs;\n    int tgtDeviceID = parent.getDeviceID();\n    for (map<int, int>::const_iterator it = numExpectedMsgs.begin(); it != numExpectedMsgs.end(); ++it) {\n        if (it->first != tgtDeviceID) {\n            deviceIDs.push_back(it->first);\n        }\n    }\n    if (numExpectedMsgs[tgtDeviceID] > 0) {\n        deviceIDs.push_back(tgtDeviceID);\n    }\n\n    sort(deviceIDs.begin(), deviceIDs.end());\n\n    int firstDeviceIdx = 0, firstDeviceID = 1 << 16;\n    for (int i = 0; i < deviceIDs.size(); ++i) {\n        if (deviceIDs[i] >= tgtDeviceID && deviceIDs[i] < firstDeviceID) {\n            firstDeviceIdx = i;\n            firstDeviceID = deviceIDs[i];\n        }\n    }\n\n    // This is the order in which we process devices.\n    for (int i = firstDeviceIdx; _deviceIDs.size() < deviceIDs.size(); i = (i + 1) % deviceIDs.size()) {\n        int d = deviceIDs[i];\n        _deviceIDs.push_back(d);\n        _messageQueues[d] = new Queue<int>();\n    }\n    //shuffleVector(_deviceIDs, 1, _deviceIDs.size()); \n    _broadcaster = new StreamBroadcast();\n\n    // Note that we MUST process the tgtDeviceID first because\n    // we write to it at every iteration, and the computation\n    // thread writes to it too. By processing it first we ensure\n    // that there's no race condition.\n    assert(numExpectedMsgs[tgtDeviceID] == 0 || _deviceIDs[0] == tgtDeviceID);\n    reset();\n}\n\nSequentialActGradReducer::~SequentialActGradReducer() {\n    for(map<int,Queue<int>* >::const_iterator it = _messageQueues.begin(); it != _messageQueues.end(); ++it) {\n        delete it->second;\n    }\n    delete _broadcaster;\n}\n\nvoid SequentialActGradReducer::reset() {\n    for (map<int,int>::iterator it = _numReceivedMsgs.begin(); it != _numReceivedMsgs.end(); ++it) {\n        _numReceivedMsgs[it->first] = 0;\n    }\n}\n\nbool SequentialActGradReducer::reduce() {\n    int tgtDeviceID = _parent->getDeviceID();\n    for (int didx = 0; didx < _deviceIDs.size(); ) {\n        int d = _deviceIDs[didx];\n        _numReceivedMsgs[d] += _messageQueues[d]->dequeue();\n        if (_numReceivedMsgs[d] == _numExpectedMsgs[d]) {\n            if (d != tgtDeviceID) {\n                NVMatrix::setDeviceID(tgtDeviceID);\n\n                _parent->getActsGrad().resize(_parent->getActsGrad(d));\n                map<int, NVMatrix*> mats;\n                mats[d] = &_parent->getActsGrad(d);\n                mats[tgtDeviceID] = &_parent->getActsGrad(tgtDeviceID);\n\n                _broadcaster->transfer(mats, d, didx > 0, 1);\n            }\n            didx++;\n            assert(_messageQueues[d]->getNumElements() == 0);\n        } else if (_numReceivedMsgs[d] >= _numExpectedMsgs[d]) { // exit\n            return true;\n        }\n    }\n    return false;\n}\n\nvoid SequentialActGradReducer::enqueueReduction(int deviceID) {\n    _messageQueues[deviceID]->enqueue(1);\n}\n\nvoid SequentialActGradReducer::stop() {\n    for(map<int,Queue<int>* >::const_iterator it = _messageQueues.begin(); it != _messageQueues.end(); ++it) {\n        it->second->enqueue(ACT_GRAD_REDUCER_EXIT);\n    }\n    join();\n}\n\n/* =====================\n * ParallelActGradReducer\n * =====================\n */\nParallelActGradReducer::ParallelActGradReducer(Layer& parent, map<int, int> numExpectedMsgs)\n    : IActGradReducer(parent, numExpectedMsgs), _numReceivedMsgs(0) {\n    _reducer = &(new EightGPUReducer1(parent.getDeviceID()))->construct();\n\n    _scaleTarget = numExpectedMsgs.count(parent.getDeviceID()) > 0 && numExpectedMsgs[parent.getDeviceID()] > 0;\n}\n\nbool ParallelActGradReducer::reduce() {\n    // TODO: make it so that you can start the reduction before you've received all the messages.\n    while(_numReceivedMsgs < _numExpectedMsgsTotal) {\n        _numReceivedMsgs += _messageQueue.dequeue();\n    }\n    if (_numReceivedMsgs > _numExpectedMsgsTotal) {\n        return true; // exit\n    }\n    map<int,NVMatrix*> mats = _parent->getAllActsGrads();\n    _reducer->reduce(mats, 1, _scaleTarget);\n    assert(_messageQueue.getNumElements() == 0);\n    return false;\n\n}\n\nvoid ParallelActGradReducer::enqueueReduction(int deviceID) {\n    _messageQueue.enqueue(1);\n}\n\nvoid ParallelActGradReducer::stop() {\n    _messageQueue.enqueue(ACT_GRAD_REDUCER_EXIT);\n    join();\n}\n\nvoid ParallelActGradReducer::reset() {\n    _numReceivedMsgs = 0;\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/jpeg.cpp",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"../include/jpeg.h\"\n\nusing namespace std;\n\n/* ========================\n * DecoderThread\n * ========================\n */\nDecoderThread::DecoderThread(PyObject* pyList, Matrix& target, int start_img, int end_img, int img_size, int inner_size, bool test, bool multiview)\n: Thread(true), _pyList(pyList), _target(&target), _start_img(start_img), _end_img(end_img),\n  _img_size(img_size), _inner_size(inner_size), _test(test), _multiview(multiview),\n  _decodeTarget(0), _decodeTargetSize(0) {\n\n    _inner_pixels = _inner_size * _inner_size;\n    _rseed = time(0);\n}\n\nDecoderThread::~DecoderThread(){\n    free(_decodeTarget);\n}\n\nvoid* DecoderThread::run() {\n    int numSrcCases = PyList_GET_SIZE(_pyList);\n    assert(_target->getNumCols() == _inner_pixels * 3);\n    assert(_target->getNumRows() == PyList_GET_SIZE(_pyList) * (_multiview ? 10 : 1));\n\n    int width, height;\n\n    for (int64 i = _start_img; i < _end_img; ++i) {\n        decodeJpeg(i, width, height);\n        assert((width == _img_size && height >= _img_size)\n               || (height == _img_size && width >= _img_size));\n        if (_multiview) {\n            for (int flip = 0; flip < 2; ++flip) {\n                crop(numSrcCases * (flip * 5 + 0) + i, width, height, flip, 0, 0); // top-left\n                crop(numSrcCases * (flip * 5 + 1) + i, width, height, flip, width - _inner_size, 0); // top-right\n                crop(numSrcCases * (flip * 5 + 2) + i, width, height, flip, (width - _inner_size) / 2, (height - _inner_size) / 2); // center\n                crop(numSrcCases * (flip * 5 + 3) + i, width, height, flip, 0, height - _inner_size); // bottom-left\n                crop(numSrcCases * (flip * 5 + 4) + i, width, height, flip, width - _inner_size, height - _inner_size); // bottom-right\n            }\n        } else {\n            crop(i, width, height, !_test && (rand_r(&_rseed) % 2));\n        }\n\n    }\n    return NULL;\n}\n\nvoid DecoderThread::decodeJpeg(int idx, int& width, int& height) {\n    PyObject* pySrc = PyList_GET_ITEM(_pyList, idx);\n    unsigned char* src = (unsigned char*)PyString_AsString(pySrc);\n    size_t src_len = PyString_GET_SIZE(pySrc);\n    \n    struct jpeg_decompress_struct cinf;\n    struct jpeg_error_mgr jerr;\n    cinf.err = jpeg_std_error(&jerr);\n    jpeg_create_decompress(&cinf);\n    jpeg_mem_src(&cinf, src, src_len);\n    assert(jpeg_read_header(&cinf, TRUE));\n    cinf.out_color_space = JCS_RGB;\n    assert(jpeg_start_decompress(&cinf));\n    assert(cinf.num_components == 3 || cinf.num_components == 1);\n    width = cinf.image_width;\n    height = cinf.image_height;\n\n    if (_decodeTargetSize < width * height * 3) {\n        free(_decodeTarget);\n        _decodeTargetSize = width * height * 3 * 3;\n        _decodeTarget = (unsigned char*)malloc(_decodeTargetSize);\n    }\n    \n    while (cinf.output_scanline < cinf.output_height) {\n        JSAMPROW tmp = &_decodeTarget[width * cinf.out_color_components * cinf.output_scanline];\n        assert(jpeg_read_scanlines(&cinf, &tmp, 1) > 0);\n    }\n    assert(jpeg_finish_decompress(&cinf));\n    jpeg_destroy_decompress(&cinf);\n}\n\n/*\n * Uniform in [0,1)\n */\ninline double DecoderThread::randUniform() {\n    return double(rand_r(&_rseed)) / (int64(RAND_MAX) + 1);\n}\n\n/*\n * Uniform in [min, max)\n */\ninline double DecoderThread::randUniform(double min, double max) {\n    return (max - min) * randUniform() + min;\n}\n\nvoid DecoderThread::crop(int64 i, int64 src_width, int64 src_height, bool flip) {\n    crop(i, src_width, src_height, flip, -1, -1);\n}\n\nvoid DecoderThread::crop(int64 i, int64 src_width, int64 src_height, bool flip, int64 crop_start_x, int64 crop_start_y) {\n    const int64 border_size_y = src_height - _inner_size;\n    const int64 border_size_x = src_width - _inner_size;\n    if (crop_start_x < 0) {\n        crop_start_x = _test ? (border_size_x / 2) : (rand_r(&_rseed) % (border_size_x + 1));\n    }\n    if (crop_start_y < 0) {\n        crop_start_y = _test ? (border_size_y / 2) : (rand_r(&_rseed) % (border_size_y + 1));\n    }\n    const int64 src_pixels = src_width * src_height;\n    for (int64 c = 0; c < 3; ++c) {\n        for (int64 y = crop_start_y; y < crop_start_y + _inner_size; ++y) {\n            for (int64 x = crop_start_x; x < crop_start_x + _inner_size; ++x) {\n                assert((y >= 0 && y < src_height && x >= 0 && x < src_width));\n                _target->getCell(i, c * _inner_pixels + (y - crop_start_y) * _inner_size\n                                    + (flip ? (_inner_size - 1 - x + crop_start_x)\n                                        : (x - crop_start_x)))\n                        = _decodeTarget[3 * (y * src_width + x) + c];\n            }\n        }\n    }\n}"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/layer.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <helper_cuda.h>\n#include <iostream>\n#include <set>\n#include \"../../cudaconv3/include/cudaconv2.cuh\"\n#include \"../../util/include/matrix.h\"\n#include \"../include/layer_kernels.cuh\"\n#include \"../include/layer.cuh\"\n#include \"../include/data.cuh\"\n#include \"../include/util.cuh\"\n#include \"../include/weights.cuh\"\n\nusing namespace std;\n\n/*\n * =======================\n * Layer\n * =======================\n */\nLayer::Layer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool trans) :\n             _convNetThread(convNetThread),  _replicaID(replicaID), _trans(trans) {\n    _name = pyDictGetString(paramsDict, \"name\");\n    _type = pyDictGetString(paramsDict, \"type\");\n   \n    _foundGradConsumers = false;\n    _gradConsumer = pyDictGetInt(paramsDict, \"gradConsumer\");\n    _actsTarget = pyDictGetInt(paramsDict, \"actsTarget\");\n    _actsGradTarget = pyDictGetInt(paramsDict, \"actsGradTarget\");\n    _numOutputs = pyDictGetInt(paramsDict, \"outputs\");\n    _numReplicas = pyDictGetInt(paramsDict, \"numReplicas\");\n    _numReplicasPrev = 1;\n    _rcvdBInputMsgs = 0;\n\n    _actBroadcaster = NULL;\n    _gradReducer = NULL;\n    _initialized = false;\n}\n\nLayer::~Layer() {\n    if (_actBroadcaster != NULL) {\n        _actBroadcaster->stop();\n        delete _actBroadcaster;\n    }\n    if (_gradReducer != NULL) {\n        _gradReducer->stop();\n        delete _gradReducer;\n    }\n    // For now, gradReducer doesn't have a destructor\n//    delete _gradReducer;\n    for (std::map<int, MemoryView*>::iterator it = _memSrcActs.begin(); it != _memSrcActs.end(); ++it) {\n        if (it->second->getMemorySource().truncate(_name)) {\n            delete &it->second->getMemorySource();\n        }\n    }\n    for (std::map<int, MemoryView*>::iterator it = _memSrcActsGrad.begin(); it != _memSrcActsGrad.end(); ++it) {\n        if (it->second->getMemorySource().truncate(_name)) {\n            delete &it->second->getMemorySource();\n        }\n    }\n}\n\ncudaStream_t Layer::getStream() {\n    assert(getDeviceID() >= 0);\n    return NVMatrix::getDefaultStream(getDeviceID());\n}\n\nvoid Layer::syncStream() {\n    NVMatrix::syncStream(getStream());\n}\n\nvoid Layer::fpropNext(PASS_TYPE passType, int passIdx) {\n    if (_next.size() > 0) {\n        if (getFwdActiveReplicaIdx(passIdx) == 0/*getReplicaIdx()*/) { // 0 turns on pipelining\n            if (_nextDeviceIDs.size() > 1 || (_nextDeviceIDs.size() == 1 && _nextDeviceIDs[0] != getDeviceID())) {\n                syncStream(); // Make sure I've finished computing before broadcasting\n            }\n            getActBroadcaster().getMessageQueue().enqueue(new BroadcastMessage(getAllActs(), getDeviceID(), getReplicaIdx(), _broadcastFinishQueue));\n        }\n        if (getFwdActiveReplicaIdx(passIdx) == getReplicaIdx()) {\n            _broadcastFinishQueue.dequeue();\n            assert(_broadcastFinishQueue.getNumElements() == 0);\n        }\n    }\n\n    for (int i = 0; i < _next.size(); i++) {\n        _next[i]->getConvNetThread().getMessageQueue().enqueue(new FpropMessage(*_next[i], passType, passIdx));\n    }\n}\n\nbool Layer::fprop(PASS_TYPE passType, int passIdx) {\n    _rcvdFInputMsgs++;\n    // I require messages from *all* input replicas because it makes the propagation easier to think about.\n    // Without this requirement, when all fprop terminal msgs arrive to ConvNet, the forward propagation\n    // might not actually be finished yet.\n    if (_rcvdFInputMsgs == getNumExpectedFwdMsgs()) {\n//        printf(\"Layer %s[%d] fprop\\n\", _name.c_str(), getReplicaID());\n        int ridx = getFwdActiveInputReplicaIdx(passIdx);\n        assert(getDeviceID() == NVMatrix::getDeviceID());\n        map<int, NVMatrix*> v;\n        if (ridx >= 0) {\n            for (int i = 0; i < getNumLayersPrev(); i++) {\n                v[i] = &_prev[ridx][i]->getActs(getDeviceID());\n            }\n        }\n        fprop(v, passType, passIdx);\n        return true;\n    }\n    return false;\n}\n\nvoid Layer::fprop(map<int,NVMatrix*>& v, PASS_TYPE passType, int passIdx) {\n    if (getFwdActiveInputReplicaIdx(passIdx) >= 0) {\n        assert(v.size() == getNumLayersPrev());\n        _inputs.clear();\n        _inputs.insert(v.begin(), v.end());\n\n        int numCases = _inputs[0]->getLeadingDim();\n        for (map<int,MemoryView*>::iterator it = _memSrcActs.begin(); it != _memSrcActs.end(); ++it) {\n            it->second->getMemory(numCases);\n        }\n\n        if (numCases > 0) {\n            //printf(\"layer %s fprop, numcases: %d\\n\", _name.c_str(), numCases);\n            _rcvdFInputMsgs = getNumExpectedFwdMsgs();\n            for (map<int,NVMatrix*>::iterator it = v.begin(); it != v.end(); ++it) {\n                it->second->transpose(_trans);\n            }\n            getActs().transpose(_trans);\n   \n            fpropCommon(passType);\n\n            // First do fprop on the input whose acts matrix I'm sharing, if any\n            if (_actsTarget >= 0) {\n                fpropActs(_actsTarget, 0, passType, passIdx);\n            }\n            // Then add the rest of the inputs to that\n            for (int i = 0; i < getNumLayersPrev(); i++) {\n                if (i != _actsTarget) {\n                    fpropActs(i, _actsTarget >= 0 || i > 0, passType, passIdx);\n                }\n            }\n        }\n    }\n    fpropNext(passType, passIdx);\n}\n\nvoid Layer::truncBwdActs() {\n    // Only truncate actsGrad if I own it\n    if (_actsGradTarget < 0) {\n        for (map<int,MemoryView*>::iterator it = _memSrcActsGrad.begin(); it != _memSrcActsGrad.end(); ++it) {\n            it->second->getMemorySource().truncate(getName());\n        }\n    }\n    if (_actsTarget < 0) {\n        for (map<int,MemoryView*>::iterator it = _memSrcActs.begin(); it != _memSrcActs.end(); ++it) {\n            it->second->getMemorySource().truncate(getName());\n        }\n    }\n}\n\nint Layer::getNumGradProducersNext() {\n    return _numGradProducersNext;\n}\n\nint Layer::getNumExpectedBwdMsgs() {\n    return _numGradProducersNext * getNumSiblingReplicas();\n}\n\nint Layer::getNumExpectedFwdMsgs() {\n    return getNumLayersPrev() * getNumInputReplicas();\n}\n\nvoid Layer::bprop(PASS_TYPE passType, int passIdx) {\n    if (getBwdActiveInputReplicaIdx(passIdx) >= 0 && _rcvdBInputMsgs == getNumExpectedBwdMsgs()) {\n//        printf(\"Layer %s[%d] bprop\\n\", _name.c_str(), getReplicaID());\n        if (_gradReducer != NULL) {\n            _gradReducer->waitForFinish();\n        }\n\n        // This does sync, but only if it has grad consumers below! so we must sync again before sending bprop terminal messages\n        bprop(getActsGrad(), passType, passIdx);\n       \n        if (_bwdTerminal[passIdx]) {\n            syncStream();\n            getConvNet().getMessageQueue().enqueue(new Message(BPROP_TERMINAL));\n        }\n    }\n}\n\nvoid Layer::bpropActsCall(NVMatrix& v, PASS_TYPE passType, int replicaIdx, int inputIdx) {\n    Layer& prev = *_prev[replicaIdx][inputIdx];\n    if (prev.isGradConsumer() && isGradProducer(prev.getName())) {\n        if (v.getLeadingDim() > 0) { // Only do computation if #cases > 0\n            bpropActs(v, replicaIdx, inputIdx, prev.getNumComputedActsGrads(getDeviceID()) > 0, passType);\n        }\n        prev.getNumComputedActsGrads(getDeviceID())++;\n        // Synchronize if the previous layer is going to actually do a reduction.\n        // If the previous layer is on the same GPU as us and has no next layers\n        // on other GPUs then it won't need to do a reduction.\n        if (prev.getNextDeviceIDs().size() > 1 || (prev.getNextDeviceIDs().size() == 1 && getDeviceID() != prev.getDeviceID())) {\n            syncStream();\n        }\n        prev.getGradReducer().enqueueReduction(getDeviceID());\n    }\n}\n\nvoid Layer::bprop(NVMatrix& v, PASS_TYPE passType, int passIdx) {\n\n    v.transpose(_trans);\n    assert(getDeviceID() == NVMatrix::getDeviceID());\n    int ridx = getBwdActiveInputReplicaIdx(passIdx);\n    LayerV& prev = _prev[ridx];\n    map<int, set<Layer*> > prevByDevice = _prevByDevice[ridx];\n\n    for (int i = 0; i < prev.size(); i++) {\n        _inputs[i]->transpose(_trans);\n        prev[i]->getActsGrad().transpose(_trans);\n    }\n    getActs().transpose(_trans);\n    // NOTE: this should be here (before the bpropActs) because if you have a layer\n    // that has a weight matrix AND actsGradTarget >= 0, then the stuff below will overwrite\n    // v which is used in bpropCommon. So bpropCommon must come first.\n    bpropCommon(v, ridx, passType);\n\n    if (isGradProducer()) {\n        // First propagate activity gradient to all layers whose activity\n        // gradient matrix I'm definitely not sharing.\n        for (map<int, set<Layer*> >::const_iterator it = prevByDevice.begin(); it != prevByDevice.end(); ++it) {\n            const set<Layer*>& deviceLayers = it->second;\n            for (set<Layer*>::const_iterator it2 = deviceLayers.begin(); it2 != deviceLayers.end(); ++it2) {\n                if (_actsGradTarget != (*it2)->getInputIdx(_name)) {\n                    bpropActsCall(v, passType, ridx, (*it2)->getInputIdx(_name));\n                }\n            }\n        }\n\n        // Then propagate activity gradient to the layer whose activity gradient\n        // matrix I'm sharing, if any.\n        if (_actsGradTarget >= 0) {\n            bpropActsCall(v, passType, ridx, _actsGradTarget);\n        }\n    }\n\n    // Synchronization is necessary because the kernel calls that compute my backward acts\n    // execute asynchronously. Therefore I don't want to tell other threads that I've\n    // computed bprop activities for them when in fact I've only called a function which\n    // will eventually compute them.\n    if (_prevDeviceIDs.size() > 1 || (_prevDeviceIDs.size() == 1 && _prevDeviceIDs[0] != getDeviceID())) {\n        syncStream();\n    }\n\n    if (getConvNet().isConserveMemory()) {\n        truncBwdActs();\n    }\n\n    if (isGradProducer()) {\n        /*for (int i = 0; i < prev.size(); i++) {\n            if (prev[i]->isGradConsumer() && isGradProducer(prev[i]->getName())) {\n                prev[i]->getGradReducer().enqueueReduction(getDeviceID());\n            }\n        }*/\n\n        // Send backward messages to *all* replicas.\n        // Note that the messages will be dismissed unless the passIdx indicates\n        // that the previous layer should do some work.\n        for (int r = 0; r < getNumInputReplicas(); r++) {\n            for (int i = 0; i < _prev[r].size(); i++) {\n                if (_prev[r][i]->isGradConsumer() && isGradProducer(_prev[r][i]->getName())) {\n                    _prev[r][i]->getConvNetThread().getMessageQueue().enqueue(new BpropMessage(*_prev[r][i], passType, passIdx));\n                }\n            }\n        }\n    }\n}\n\nIActGradReducer& Layer::getGradReducer() {\n    return *_gradReducer;\n}\n\n// This is called between minibatches\nvoid Layer::reset() {\n    _rcvdFInputMsgs = 0;\n    _rcvdBInputMsgs = 0;\n    for (map<int,int>::iterator it = _numComputedActsGrads.begin(); it != _numComputedActsGrads.end(); ++it) {\n        it->second = 0;\n    }\n}\n\n// This is called between microbatches\nvoid Layer::resetPassIdx() {\n    _rcvdFInputMsgs = 0;\n    if (_rcvdBInputMsgs >= getNumExpectedBwdMsgs()) {\n        reset();\n    }\n}\n\n/*\n * Returns number of cases in given matrix.\n */\nint Layer::getNumCases(NVMatrix& v) {\n    return v.getLeadingDim();\n}\n\nint Layer::incRcvdBInputMsgs() {\n    return ++_rcvdBInputMsgs;\n}\n\nstd::string& Layer::getName() {\n    return _name;\n}\n\nstd::string& Layer::getType() {\n    return _type;\n}\n\nint& Layer::getNumComputedActsGrads(int deviceID) {\n    return _numComputedActsGrads[deviceID];\n}\n\nvoid Layer::addNext(Layer& l) {\n    _next.push_back(&l);\n    _numReplicasNext = l.getNumReplicas();\n    if (count(_nextDeviceIDs.begin(), _nextDeviceIDs.end(), l.getDeviceID()) == 0) {\n        int pos = rand() % (_nextDeviceIDs.size() + 1);\n        _nextDeviceIDs.insert(_nextDeviceIDs.begin() + pos, l.getDeviceID());\n    }\n}\n\nvoid Layer::addPrev(Layer& l, int replicaIdx) {\n    _prev[replicaIdx].push_back(&l);\n    _numReplicasPrev = l.getNumReplicas();\n    l.setInputIdx(getName(), _prev[replicaIdx].size() - 1);\n    if (l.getDeviceID() >= 0 && count(_prevDeviceIDs.begin(), _prevDeviceIDs.end(), l.getDeviceID()) == 0) {\n        int pos = rand() % (_prevDeviceIDs.size() + 1);\n        _prevDeviceIDs.insert(_prevDeviceIDs.begin() + pos, l.getDeviceID());\n    }\n}\n\nvoid Layer::addReplica(Layer& l) {\n    assert(_replicas.count(l.getReplicaID()) == 0);\n    _replicas[l.getReplicaID()] = &l;\n}\n\nbool Layer::hasGradProducerNext(std::string& layerName) {\n    bool b = _next.size() == 0;\n    for (int i = 0; i < _next.size(); i++) {\n        b |= _next[i]->hasGradProducerNext(_name);\n    }\n    return b && isGradProducer(layerName);\n}\n\nbool Layer::postInit() {\n    // We choose not to populate _outputs[getDeviceID()] here because we do it instead in fprop().\n    // In fprop(), we can populate it from the _inputs vector, which is a bit more general than populating\n    // it from _prev->getActs()\n//    _outputs = _actsTarget < 0 ? new NVMatrix() : &_prev[_actsTarget]->getActs();\n    if (!_initialized) {\n        _initialized = true;\n        map<int,int> numGradProducersNext;\n        _numGradProducersNext = 0;\n        for (int r = 0; r < getNumInputReplicas(); ++r) {\n            for (vector<Layer*>::const_iterator it = _prev[r].begin(); it != _prev[r].end(); ++it) {\n                (*it)->postInit();\n            }\n        }\n\n        _memSrcActs[getDeviceID()] = _actsTarget < 0 ? &MemorySource::make(_numOutputs, getDeviceID(), getName())\n                                                     : &_prev[0][_actsTarget]->getMemorySourceActs(getDeviceID()).clone(_name);\n\n        // _actsGradTarget will only be >= 0 when the number of replicas is the same in both layers, so this justifies the use of _prev[0]\n\n        _memSrcActsGrad[getDeviceID()] = _actsGradTarget < 0 ? &MemorySource::make(_numOutputs, getDeviceID(), getName())\n                                                             : &_prev[0][_actsGradTarget]->getMemorySourceActsGrad(getDeviceID()).clone(_name);\n        for (int i = 0; i < _next.size(); ++i) {\n            int d = _next[i]->getDeviceID();\n            _numComputedActsGrads[d] = 0;\n            if (_next[i]->hasGradProducerNext(_name)) {\n                if (numGradProducersNext.count(d) == 0) {\n                    numGradProducersNext[d] = 0;\n                }\n                numGradProducersNext[d]++;\n                _numGradProducersNext++;\n                if (_memSrcActsGrad.count(d) == 0) {\n                    _memSrcActsGrad[d] = &MemorySource::make(_numOutputs, d, getName());\n                }\n            }\n            if (_memSrcActs.count(d) == 0) {\n                _memSrcActs[d] = &MemorySource::make(_numOutputs, d, getName());\n            }\n        }\n\n        if (_next.size() == 0) {\n            _numReplicasNext = getNumReplicas();\n        }\n\n        /*\n         * Initialize forward broadcaster. First sibling owns it.\n         */\n        if (getReplicaIdx() == 0 && _convNetThread != NULL) {\n            _actBroadcaster = new ActBroadcaster(getNumSiblingReplicas(), getDeviceCPUs(_convNetThread->getDeviceID()));\n            _actBroadcaster->start();\n        }\n\n        /*\n         * Initialize backward reducer.\n         */\n        if (isGradConsumer() && _numGradProducersNext > 0) {\n            _gradReducer = &IActGradReducer::makeGradReducer(*this, numGradProducersNext);\n            _gradReducer->start();\n        }\n\n        /*\n         * Initialize specially sorted previous array\n         */\n        for (int r = 0; r < _prev.size(); ++r) {\n            for (int i = 0; i < _prev[r].size(); ++i) {\n                // Previous devices in reverse order of processing by (sequential) GradReducer\n                _prevByDevice[r][getDeviceID() - _prev[r][i]->getDeviceID()\n                                 + 16 * (_prev[r][i]->getDeviceID() > getDeviceID())].insert(_prev[r][i]);\n\n            }\n        }\n        return true;\n    }\n    return false;\n}\n\nActBroadcaster& Layer::getActBroadcaster() {\n    return getReplicaIdx() == 0 ? *_actBroadcaster : _replicas[getReplicaID() - getReplicaIdx()]->getActBroadcaster();\n}\n\n// Does this layer, or some layer below it, need the gradient\n// for parameter updates?\n// Only weight layers should be grad consumers themselves.\nbool Layer::isGradConsumer() {\n    if (!_foundGradConsumers && _prev.size() > 0) {\n        for (int i = 0; i < _prev[0].size(); i++) {\n            _gradConsumer |= _prev[0][i]->isGradConsumer();\n        }\n        _foundGradConsumers = true;\n    }\n    return _gradConsumer;\n}\n\n// Does this layer produce gradient for layers below?\nbool Layer::isGradProducer() {\n    return true;\n}\n\nbool Layer::isGradProducer(std::string& layerName) {\n    return isGradProducer();\n}\n\nmap<int,vector<Layer*> >& Layer::getPrev() {\n    return _prev;\n}\n\nvector<Layer*>& Layer::getNext() {\n    return _next;\n}\n\nNVMatrix& Layer::getActs() {\n    return getActs(getDeviceID());\n}\n\nNVMatrix& Layer::getActs(int deviceID) {\n    assert(_memSrcActs.count(deviceID) > 0);\n    return _memSrcActs[deviceID]->getMemory();\n}\n\nNVMatrix& Layer::getActs(int deviceID, int numCases) {\n    assert(_memSrcActs.count(deviceID) > 0);\n    return _memSrcActs[deviceID]->getMemory(numCases);\n}\n\nNVMatrix& Layer::getActsGrad(int deviceID) {\n    assert(_memSrcActsGrad.count(deviceID) > 0);\n    return _memSrcActsGrad[deviceID]->getMemory(getActs(deviceID).getLeadingDim());\n}\n\nNVMatrix& Layer::getActsGrad() {\n    return getActsGrad(NVMatrix::getDeviceID());\n}\n\nmap<int, NVMatrix*> Layer::getAllActs() {\n    map<int, NVMatrix*> m;\n    for (map<int, MemoryView*>::const_iterator it = _memSrcActs.begin(); it != _memSrcActs.end(); ++it) {\n        m[it->first] = &it->second->getMemory();\n    }\n    return m;\n}\n\nmap<int, NVMatrix*> Layer::getAllActsGrads() {\n    map<int, NVMatrix*> m;\n    for (map<int, MemoryView*>::const_iterator it = _memSrcActsGrad.begin(); it != _memSrcActsGrad.end(); ++it) {\n        m[it->first] = &it->second->getMemory();\n    }\n    return m;\n}\n\nint Layer::getDeviceID() {\n    return _convNetThread == NULL ? -1 : _convNetThread->getDeviceID();\n}\n\nConvNetThread& Layer::getConvNetThread() {\n    assert(_convNetThread != NULL);\n    return *_convNetThread;\n}\n\nConvNet& Layer::getConvNet() {\n    return getConvNetThread().getConvNet();\n}\n\nvoid Layer::setBwdTerminal(int passIdx) {\n    _bwdTerminal[passIdx] = true;\n}\n\nint Layer::getReplicaID() {\n    return  _replicaID;\n}\n\nint Layer::getActivePassPeriod() {\n    return getNumReplicas() / getConvNet().getNumReplicasMin();\n}\n\nint Layer::getFwdActiveInputReplicaIdx(int passIdx) {\n    const int edge = (passIdx / getActivePassPeriod()) % getNumInputReplicas();\n    return passIdx % getActivePassPeriod() == 0 ? edge : -1;\n}\n\nint Layer::getBwdActiveInputReplicaIdx(int passIdx) {\n    const int edge = (passIdx / getActivePassPeriod()) % getNumInputReplicas();\n    return (passIdx + 1) % getActivePassPeriod() == 0 ? edge : -1;\n}\n\nint Layer::getFwdActiveReplicaIdx(int passIdx) {\n    assert(_next.size() > 0);\n    return _next[0]->getFwdActiveInputReplicaIdx(passIdx);\n}\n\nint Layer::getNumReplicas() {\n    return _replicas.size();\n}\n\nint Layer::getNumSiblingReplicas() {\n    return getNumReplicas() / getNumReplicasNext();\n}\n\nint Layer::getNumReplicasPrev() {\n    return _numReplicasPrev;\n}\n\nint Layer::getNumReplicasNext() {\n    return _numReplicasNext;\n}\n\nint Layer::getNumInputReplicas() {\n    return _numReplicasPrev / getNumReplicas();\n}\n\nint Layer::getReplicaIdx() {\n    return getReplicaID() % getNumSiblingReplicas();\n}\n\nint Layer::getNumLayersPrev() {\n    return _prev.size() > 0 ? _prev[0].size() : 0;\n}\n\nvoid Layer::setMemorySourceActs(int deviceID, MemoryView& mem) {\n    assert(_memSrcActs[deviceID]->isParent());\n    delete _memSrcActs[deviceID];\n    _memSrcActs[deviceID] = &mem;\n    if (_actsTarget >= 0 && deviceID == getDeviceID()) {\n        assert(getNumInputReplicas() == 1);\n        _prev[0][_actsTarget]->setMemorySourceActs(deviceID, mem.clone(_prev[0][_actsTarget]->getName()));\n    }\n}\n\nvoid Layer::setMemorySourceActsGrad(int deviceID, MemoryView& mem) {\n    assert(_memSrcActsGrad[deviceID]->isParent());\n    delete _memSrcActsGrad[deviceID];\n    _memSrcActsGrad[deviceID] = &mem;\n    if (_actsGradTarget >= 0 && deviceID == getDeviceID()) {\n        assert(getNumInputReplicas() == 1);\n        _prev[0][_actsGradTarget]->setMemorySourceActsGrad(deviceID, mem.clone(_prev[0][_actsGradTarget]->getName()));\n    }\n}\n\nMemoryView& Layer::getMemorySourceActs(int deviceID) {\n    return *_memSrcActs[deviceID];\n}\n\nMemoryView& Layer::getMemorySourceActsGrad(int deviceID) {\n    return *_memSrcActsGrad[deviceID];\n}\n\nint Layer::getNumOutputs() {\n    return _numOutputs;\n}\n\nvoid Layer::setInputIdx(std::string& parentName, int idx) {\n    _inputIndices[parentName] = idx;\n}\n\nint Layer::getInputIdx(std::string& parentName) {\n    return _inputIndices[parentName];\n}\n\n/*\n * =======================\n * NeuronLayer\n * =======================\n */\nNeuronLayer::NeuronLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID)\n    : Layer(convNetThread, paramsDict, replicaID, true) {\n    PyObject* neuronDict = PyDict_GetItemString(paramsDict, \"neuron\");\n    _neuronType = pyDictGetString(neuronDict, \"type\");\n    _neuron = &Neuron::makeNeuron(neuronDict);\n}\n\nNeuronLayer::~NeuronLayer() {\n    delete _neuron;\n}\n\nvoid NeuronLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    assert(inpIdx == 0);\n    if (!bpropSpecial(v, replicaIdx, inpIdx, scaleTargets, passType)) {\n        _neuron->computeInputGrad(v, _prev[replicaIdx][0]->getActsGrad(), scaleTargets > 0);\n    }\n}\n\nbool NeuronLayer::bpropSpecial(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    // Special optimization for cross-entropy objective with logistic units.\n    // Better to just compute the input gradient in one go to avoid division by small numbers.\n    bool doCrossEntGrad = _neuronType == \"logistic\" && _next.size() == 1\n                        && (_next[0]->getType() == \"cost.bce\" || _next[0]->getType() == \"cost.dce\")\n                        && _next[0]->getDeviceID() == getDeviceID()\n                        && _next[0]->getNumReplicas() == getNumReplicas();\n    LayerV& prev = _prev[replicaIdx];\n    if (doCrossEntGrad) {\n        NVMatrix& labels = _next[0]->getPrev()[replicaIdx][0]->getActs(getDeviceID());\n        BinomialCrossEntropyCostLayer& cost = *static_cast<BinomialCrossEntropyCostLayer*>(_next[0]);\n        float gradCoeff = cost.getCoeff();\n        labels.transpose(_trans);\n        if (cost.getPosWeight() == 1) {\n            if (scaleTargets == 0) {\n                getActs().add(labels, -gradCoeff, gradCoeff, prev[0]->getActsGrad());\n            } else {\n                getActs().applyTernary(AddGradientBinaryOperator<NVMatrixBinaryOps::WeightedAdd>(NVMatrixBinaryOps::WeightedAdd(-gradCoeff, gradCoeff)),\n                                       labels, prev[0]->getActsGrad(), prev[0]->getActsGrad());\n            }\n        } else {\n            if (scaleTargets == 0) {\n                getActs().applyBinary(CrossEntLogisticGradientOperator(gradCoeff, cost.getPosWeight()), labels, prev[0]->getActsGrad());\n            } else {\n                getActs().applyTernary(AddGradientBinaryOperator<CrossEntLogisticGradientOperator>(CrossEntLogisticGradientOperator(gradCoeff, cost.getPosWeight())),\n                                       labels, prev[0]->getActsGrad(), prev[0]->getActsGrad());\n            }\n        }\n    }\n    return doCrossEntGrad;\n}\n\nvoid NeuronLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    _neuron->activate(*_inputs[0], getActs());\n}\n\nstd::string& NeuronLayer::getNeuronType() {\n    return _neuronType;\n}\n\n/*\n * =======================\n * WeightLayer\n * =======================\n *\n * The useGrad parameter here merely expresses a preference by the subclass. It may\n * be overridden by the superclass (WeightLayer) and in that case the subclass must follow its wishes.\n * So when computing gradient updates, the subclass must always first check weights.isUseGrad().\n *\n * Note: biases always useGrad.\n */\nWeightLayer::WeightLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool trans, bool useGrad) :\n    Layer(convNetThread, paramsDict, replicaID, trans) {\n    _weightUpdatePassPeriod = pyDictGetInt(paramsDict, \"updatePeriod\");\n\n    MatrixV& hWeights = *pyDictGetMatrixV(paramsDict, \"weights\");\n    MatrixV& hWeightsInc = *pyDictGetMatrixV(paramsDict, \"weightsInc\");\n    Matrix& hBiases = *pyDictGetMatrix(paramsDict, \"biases\");\n    Matrix& hBiasesInc = *pyDictGetMatrix(paramsDict, \"biasesInc\");\n    PyObject* pyEpsWList = PyDict_GetItemString(paramsDict, \"epsW\");\n    PyObject* pyEpsB = PyDict_GetItemString(paramsDict, \"epsB\");\n    floatv& momW = *pyDictGetFloatV(paramsDict, \"momW\");\n    float momB = pyDictGetFloat(paramsDict, \"momB\");\n    floatv& wc = *pyDictGetFloatV(paramsDict, \"wc\");\n    floatv& wball = *pyDictGetFloatV(paramsDict, \"wballNormed\");\n\n    /*\n     * When there are multiple replicas, the present implementation\n     * requires that useGrad is true. This is because weights.update()\n     * performs a simultaneous write to both replicas' weightsInc matrix,\n     * which means that the read should come from somewhere else (i.e. a\n     * grads matrix).\n     */\n    useGrad |= _numReplicas > 1;\n\n    // Source layers for shared weights\n    stringv& weightSourceLayers = *pyDictGetStringV(paramsDict, \"weightSourceLayers\");\n\n    // Weight matrix indices (inside the above source layers) for shared weights\n    intv& weightSourceMatrixIndices = *pyDictGetIntV(paramsDict, \"weightSourceMatrixIndices\");\n    _weights = new WeightList();\n    for (int i = 0; i < weightSourceLayers.size(); i++) {\n        std::string& srcLayerName = weightSourceLayers[i];\n        int matrixIdx = weightSourceMatrixIndices[i];\n        PyObject* pyEpsW = PyList_GetItem(pyEpsWList, i);\n        ParameterSchedule& lrs = ParameterSchedule::make(pyEpsW); // Learning rate schedule\n        if (srcLayerName == _name) { // Current layer\n            _weights->addWeights(*new Weights(_weights->at(matrixIdx), lrs, *this));\n        } else if (srcLayerName != \"\") {\n            WeightLayer& srcLayer = *static_cast<WeightLayer*>(&convNetThread->getLayer(srcLayerName));\n            Weights* srcWeights = &srcLayer.getWeights(matrixIdx);\n            _weights->addWeights(*new Weights(*srcWeights, lrs, *this));\n        } else {\n            _weights->addWeights(*new Weights(*hWeights[i], *hWeightsInc[i], lrs, *this, wc[i], wball[i], momW[i], useGrad));\n        }\n    }\n    _biases = new Weights(hBiases, hBiasesInc, ParameterSchedule::make(pyEpsB), *this, 0, 0, momB, true);\n\n    delete &weightSourceLayers;\n    delete &weightSourceMatrixIndices;\n    delete &hWeights;\n    delete &hWeightsInc;\n    delete &momW;\n    delete &wc;\n    delete &wball;\n\n    _wStep = 0.02;\n    _bStep = 0.005;\n}\n\nWeightLayer::~WeightLayer() {\n    delete _weights;\n    delete _biases;\n}\n\nbool WeightLayer::postInit() {\n    if (Layer::postInit()) {\n        _weightUpdatePassPeriod = max(_weightUpdatePassPeriod, getActivePassPeriod());\n        assert(_weightUpdatePassPeriod % getActivePassPeriod() == 0);\n        return true;\n    }\n    return false;\n}\n\nvoid WeightLayer::fpropCommon(PASS_TYPE passType) {\n}\n\nvoid WeightLayer::bpropCommon(NVMatrix& v, int replicaIdx, PASS_TYPE passType) {\n    if (_biases->getLearningRateSchedule().getBaseValue() > 0) {\n        if (v.getNumElements() > 0) {\n            bpropBiases(v, passType);\n        } else {\n            _biases->getGrad().resize(_biases->getW());\n            _biases->getGrad().scale(getBIncScale());\n        }\n        _biases->incNumUpdates();\n    }\n    for (int i = 0; i < _weights->getSize(); i++) {\n        if (_weights->at(i).getLearningRateSchedule().getBaseValue() > 0) {\n            if (v.getNumElements() > 0) {\n                bpropWeights(v, replicaIdx, i, passType);\n            } else {\n                _weights->at(i).getGrad().resize(_weights->at(i).getW());\n                // This will cause it to forget momentum when shown 0 training cases\n                // and _useGrad = false but it's not too important.\n                _weights->at(i).getGrad().scale(getIncScale(i, passType));\n            }\n            // Increment its number of updates\n            _weights->at(i).incNumUpdates();\n        }\n    }\n}\n\nbool WeightLayer::updateWeights() {\n     if (getConvNet().getTotalPassesDone() % _weightUpdatePassPeriod == 0) {\n        _weights->update(getConvNet().getTrainingProgress());\n        _biases->update(getConvNet().getTrainingProgress());\n//        constrainWeights();\n        return true;\n    }\n    return false;\n}\n\nbool WeightLayer::constrainWeights() {\n    if (getConvNet().getTotalPassesDone() % _weightUpdatePassPeriod == 0) {\n        _constrainWeights();\n        return true;\n    }\n    return false;\n}\n\nvoid WeightLayer::_constrainWeights() {\n}\n\nvoid WeightLayer::copyToCPU() {\n    _weights->copyToCPU();\n    _biases->copyToCPU();\n}\n\nvoid WeightLayer::copyToGPU() {\n    _weights->copyToGPU();\n    _biases->copyToGPU();\n}\n\nvoid WeightLayer::checkGradient() {\n    for (int i = 0; i < _weights->getSize(); i++) {\n        getConvNet().checkGradient(_name + \" weights[\" + tostr(i) + \"]\", _wStep, _weights->at(i));\n    }\n    getConvNet().checkGradient(_name + \" biases\", _bStep, *_biases);\n}\n\nvoid WeightLayer::addReplica(Layer& l) {\n    Layer::addReplica(l);\n    _weights->addReplica(*static_cast<WeightLayer*>(&l)->_weights);\n    _biases->addReplica(*static_cast<WeightLayer*>(&l)->_biases);\n}\n\nWeights& WeightLayer::getWeights(int idx) {\n    return _weights->at(idx);\n}\n\nfloat WeightLayer::getGradScale(int inpIdx, PASS_TYPE passType) {\n    // weight update period must be multiple of activation period\n    // TODO: simply accumulate # of cases seen between weight updates. simpler and more accurate.\n    double numCases = _weightUpdatePassPeriod * (getConvNet().getMinibatchSize() / double(getConvNet().getNumPasses()));\n    if (_weights->at(inpIdx).isUseGrad()) {\n        return passType == PASS_GC ? 1.0f : 1.0f / numCases;\n    }\n    return passType == PASS_GC ? 1.0f : _weights->at(inpIdx).getEps(getConvNet().getTrainingProgress()) / numCases;\n}\n\nfloat WeightLayer::getIncScale(int inpIdx, PASS_TYPE passType) {\n    if (_weights->at(inpIdx).isUseGrad()) {\n        return _weights->at(inpIdx).getNumUpdates() > 0;\n    }\n    return  (passType == PASS_GC ? _weights->at(inpIdx).getNumUpdates() > 0\n                                 : (_weights->at(inpIdx).getNumUpdates() == 0 ? _weights->at(inpIdx).getMom() : 1.0f));\n}\n\nNVMatrix& WeightLayer::getGradTarget(int inpIdx) {\n    return _weights->at(inpIdx).getGrad();\n}\n\nfloat WeightLayer::getBGradScale(PASS_TYPE passType) {\n    int numCases = _weightUpdatePassPeriod * DIVUP(getConvNet().getMinibatchSize(), getConvNet().getNumPasses());\n    return passType == PASS_GC ? 1.0f : 1.0f / numCases;\n}\n\nfloat WeightLayer::getBIncScale() {\n    return _biases->getNumUpdates() > 0;\n}\n\nNVMatrix& WeightLayer::getWeightMatrix(PASS_TYPE passType, int inpIdx) {\n    return _weights->at(inpIdx).getW();\n}\n\nNVMatrix& WeightLayer::getBiasMatrix(PASS_TYPE passType) {\n    return _biases->getW();\n}\n\n/*\n * =======================\n * FCLayer\n * =======================\n */\nFCLayer::FCLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool useGrad)\n    : WeightLayer(convNetThread, paramsDict, replicaID, true, useGrad) {\n    _wStep = 0.01;\n    _bStep = 0.01;\n}\n\nvoid FCLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    getActs().addProduct(*_inputs[inpIdx], getWeightMatrix(passType, inpIdx), scaleTargets, 1);\n    if (scaleTargets == 0) {\n        getActs().addVector(getBiasMatrix(passType), 1, getActs());\n    }\n}\n\nvoid FCLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    NVMatrix& weights_T = getWeightMatrix(passType, inpIdx).getTranspose();\n    _prev[replicaIdx][inpIdx]->getActsGrad().addProduct(v, weights_T, scaleTargets, 1);\n    delete &weights_T;\n}\n\nvoid FCLayer::bpropBiases(NVMatrix& v, PASS_TYPE passType) {\n    _biases->getGrad().addSum(v, 0, getBIncScale(), getBGradScale(passType));\n}\n\nvoid FCLayer::bpropWeights(NVMatrix& v, int replicaIdx, int inpIdx, PASS_TYPE passType) {\n    NVMatrix& prevActs_T = _inputs[inpIdx]->getTranspose();\n    float scaleGrad = getGradScale(inpIdx, passType);\n    float scaleInc = getIncScale(inpIdx, passType);\n    getGradTarget(inpIdx).addProduct(prevActs_T, v, scaleInc, scaleGrad);\n    delete &prevActs_T;\n}\n\nvoid FCLayer::_constrainWeights() {\n    for (int i = 0; i < _weights->getSize(); i++) {\n        if (_weights->at(i).getWBall() > 0 && _weights->at(i).isOwner() && _weights->at(i).getLearningRateSchedule().getBaseValue() > 0) {\n//            NVMatrix norm2; // Unfortunate extra weight matrix...\n            _weights->at(i).getW().sumOfSquares(0, _norm2);\n//            norm2.apply(MaxWeightConstraintOperator(_weights->at(i).getWBall()));\n            _norm2.apply(HardWeightConstraintOperator(_weights->at(i).getWBall()));\n            _weights->at(i).getW().eltwiseMultByVector(_norm2);\n        }\n    }\n}\n\n/*\n * =======================\n * SplitFCLayer\n * =======================\n */\nSplitFCLayer::SplitFCLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool useGrad)\n    : FCLayer(convNetThread, paramsDict, replicaID, useGrad) {\n    _numParts = pyDictGetInt(paramsDict, \"parts\");\n}\n\nvoid SplitFCLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    getActs().resize(_inputs[inpIdx]->getNumRows(), _numOutputs, true);\n    NVMatrixV& splitInput = _inputs[inpIdx]->splitCols(_numParts);\n    NVMatrixV& splitWeights = getWeightMatrix(passType, inpIdx).splitRows(_numParts);\n    NVMatrixV& splitTarget = getActs().splitCols(_numParts);\n\n    NVMatrix::batchedMatrixMultiply(splitInput, splitWeights, splitTarget, scaleTargets, 1);\n    if (scaleTargets == 0) {\n        getActs().addVector(getBiasMatrix(passType), 1, getActs());\n    }\n\n    deleteElements(splitInput, true);\n    deleteElements(splitWeights, true);\n    deleteElements(splitTarget, true);\n}\n\nvoid SplitFCLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    NVMatrix& weights_T = getWeightMatrix(passType, inpIdx).getTranspose();\n    _prev[replicaIdx][inpIdx]->getActsGrad().resize(*_inputs[inpIdx]);\n\n    NVMatrixV& splitV = v.splitCols(_numParts);\n    NVMatrixV& splitWeights_T = weights_T.splitCols(_numParts);\n    NVMatrixV& splitTarget = _prev[replicaIdx][inpIdx]->getActsGrad().splitCols(_numParts);\n\n    NVMatrix::batchedMatrixMultiply(splitV, splitWeights_T, splitTarget, scaleTargets, 1);\n\n    delete &weights_T;\n    deleteElements(splitV, true);\n    deleteElements(splitWeights_T, true);\n    deleteElements(splitTarget, true);\n}\n\nvoid SplitFCLayer::bpropWeights(NVMatrix& v, int replicaIdx, int inpIdx, PASS_TYPE passType) {\n    NVMatrix& prevActs_T = _inputs[inpIdx]->getTranspose();\n    NVMatrixV& splitPrevActs_T = prevActs_T.splitRows(_numParts);\n    NVMatrixV& splitV = v.splitCols(_numParts);\n    NVMatrixV& splitGradTarget = getGradTarget(inpIdx).splitRows(_numParts);\n\n    NVMatrix::batchedMatrixMultiply(splitPrevActs_T, splitV, splitGradTarget, getIncScale(inpIdx, passType), getGradScale(inpIdx, passType));\n\n    delete &prevActs_T;\n    deleteElements(splitPrevActs_T, true);\n    deleteElements(splitV, true);\n    deleteElements(splitGradTarget, true);\n}\n\n/*\n * =======================\n * TwoDLayerInterface\n * =======================\n */\nTwoDLayerInterface::TwoDLayerInterface(PyObject* paramsDict) {\n    _channels = pyDictGetInt(paramsDict, \"channels\");\n    _imgSize = pyDictGetInt(paramsDict, \"imgSize\");\n    _imgPixels = _imgSize * _imgSize;\n}\n\n/*\n * =======================\n * LocalLayer\n * =======================\n */\nLocalLayer::LocalLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool useGrad)\n    : WeightLayer(convNetThread, paramsDict, replicaID, false, useGrad) {\n    _padding = pyDictGetIntV(paramsDict, \"padding\");\n    _stride = pyDictGetIntV(paramsDict, \"stride\");\n    _filterSize = pyDictGetIntV(paramsDict, \"filterSize\");\n    _channels = pyDictGetIntV(paramsDict, \"channels\");\n    _imgSize = pyDictGetIntV(paramsDict, \"imgSize\");\n    _numFilters = pyDictGetInt(paramsDict, \"filters\");\n    _groups = pyDictGetIntV(paramsDict, \"groups\");\n    _filterChannels = pyDictGetIntV(paramsDict, \"filterChannels\");\n    _filterPixels = pyDictGetIntV(paramsDict, \"filterPixels\");\n    _imgPixels = pyDictGetIntV(paramsDict, \"imgPixels\");\n   \n    _modulesX = pyDictGetInt(paramsDict, \"modulesX\");\n    _modules = pyDictGetInt(paramsDict, \"modules\");\n}\n\nLocalLayer::~LocalLayer() {\n    delete _padding;\n    delete _stride;\n    delete _filterSize;\n    delete _channels;\n    delete _imgSize;\n    delete _groups;\n    delete _filterChannels;\n    delete _filterPixels;\n    delete _imgPixels;\n}\n\n/*\n * =======================\n * ConvLayer\n * =======================\n */\nConvLayer::ConvLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID)\n    : LocalLayer(convNetThread, paramsDict, replicaID, true) {\n    _sumWidth = pyDictGetInt(paramsDict, \"sumWidth\");\n    _sharedBiases = pyDictGetInt(paramsDict, \"sharedBiases\");\n    _weightContrastNormMin = pyDictGetFloatV(paramsDict, \"wcNormMin\");\n    _weightContrastNormMax = pyDictGetFloatV(paramsDict, \"wcNormMax\");\n}\n\nConvLayer::~ConvLayer() {\n    delete _weightContrastNormMin;\n    delete _weightContrastNormMax;\n}\n\nvoid ConvLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    convFilterActs(*_inputs[inpIdx], getWeightMatrix(passType, inpIdx), getActs(), _imgSize->at(inpIdx), _modulesX, _modulesX, _padding->at(inpIdx),\n                   _stride->at(inpIdx), _channels->at(inpIdx), _groups->at(inpIdx), scaleTargets, 1);\n\n    if (scaleTargets == 0) {\n        if (_sharedBiases) {\n            getActs().reshape(_numFilters, getActs().getNumElements() / _numFilters);\n            getActs().addVector(getBiasMatrix(passType));\n            getActs().reshape(_numFilters * _modules, getActs().getNumElements() / (_numFilters * _modules));\n        } else {\n            getActs().addVector(getBiasMatrix(passType));\n        }\n    }\n}\n\nvoid ConvLayer::bpropBiases(NVMatrix& v, PASS_TYPE passType) {\n    float scaleBGrad = getBGradScale(passType);\n    float scaleInc = getBIncScale();\n    if (_sharedBiases) {\n        v.reshape(_numFilters, v.getNumElements() / _numFilters);\n        _biases->getGrad().addSum(v, 1, scaleInc, scaleBGrad);\n        v.reshape(_numFilters * _modules, v.getNumElements() / (_numFilters * _modules));\n    } else {\n        _biases->getGrad().addSum(v, 1, scaleInc, scaleBGrad);\n    }\n}\n\nvoid ConvLayer::bpropWeights(NVMatrix& v, int replicaIdx, int inpIdx, PASS_TYPE passType) {\n    assert(_weights->at(inpIdx).isUseGrad());\n    bool doPartialSum = _sumWidth < _modulesX;\n    NVMatrix& tgt = doPartialSum ? _weightGradTmp : _weights->at(inpIdx).getGrad();\n\n    float scaleWGrad = getGradScale(inpIdx, passType);\n    float scaleTargets = getIncScale(inpIdx, passType) * !doPartialSum;\n\n    convWeightActs(*_inputs[inpIdx], v, tgt, _imgSize->at(inpIdx), _modulesX, _modulesX, _filterSize->at(inpIdx), _padding->at(inpIdx),\n                   _stride->at(inpIdx), _channels->at(inpIdx), _groups->at(inpIdx), _sumWidth, scaleTargets, scaleWGrad);\n\n    if (doPartialSum) {\n        scaleTargets = _weights->at(inpIdx).getNumUpdates() > 0;\n        int outWidth = DIVUP(_modulesX, _sumWidth);\n        _weightGradTmp.reshape(outWidth*outWidth, _filterChannels->at(inpIdx) * _filterPixels->at(inpIdx) * _numFilters);\n        _weights->at(inpIdx).getGrad().addSum(_weightGradTmp, 0, scaleTargets, 1);\n        _weights->at(inpIdx).getGrad().reshape(_filterChannels->at(inpIdx) * _filterPixels->at(inpIdx), _numFilters);\n    }\n}\n\nvoid ConvLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    convImgActs(v, getWeightMatrix(passType, inpIdx), _prev[replicaIdx][inpIdx]->getActsGrad(), _imgSize->at(inpIdx), _imgSize->at(inpIdx), _modulesX,\n                _padding->at(inpIdx), _stride->at(inpIdx), _channels->at(inpIdx), _groups->at(inpIdx), scaleTargets, 1);\n}\n\nvoid ConvLayer::truncBwdActs() {\n    LocalLayer::truncBwdActs();\n    _weightGradTmp.truncate();\n}\n\nvoid ConvLayer::_constrainWeights() {\n    for (int i = 0; i < _weights->getSize(); i++) {\n        if (_weightContrastNormMax->at(i) > 0 && _weights->at(i).isOwner() && _weights->at(i).getLearningRateSchedule().getBaseValue() > 0) {\n            float fz = _weights->at(i).getW().getNumRows();\n            NVMatrix tmp;\n            _weights->at(i).getW().sum(0, tmp);\n            _weights->at(i).getW().addVector(tmp, -1.0f / fz, _weights->at(i).getGrad());\n            // Now _weights->at(i).getGrad() contains zero-mean filters\n            _weights->at(i).getGrad().apply(NVMatrixOps::Square());\n            _weights->at(i).getGrad().sum(0, tmp);\n\n            tmp.apply(WeightContrastNormOperator(_weightContrastNormMin->at(i), _weightContrastNormMax->at(i), 1.0f / fz));\n            // Now tmp has the stdev\n            _weights->at(i).getW().eltwiseMultByVector(tmp);\n        }\n        // It's pretty silly to do both these things but whatever\n        if (_weights->at(i).getWBall() > 0 && _weights->at(i).isOwner() && _weights->at(i).getLearningRateSchedule().getBaseValue() > 0) {\n//            NVMatrix norm2;\n            _weights->at(i).getW().sumOfSquares(0, _norm2);\n\n//            norm.apply(MaxWeightConstraintOperator(_weights->at(i).getWBall()));\n            _norm2.apply(HardWeightConstraintOperator(_weights->at(i).getWBall()));\n            _weights->at(i).getW().eltwiseMultByVector(_norm2);\n        }\n    }\n}\n\n/*\n * =======================\n * LocalUnsharedLayer\n * =======================\n */\nLocalUnsharedLayer::LocalUnsharedLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID)\n    : LocalLayer(convNetThread, paramsDict, replicaID, false) {\n}\n\nvoid LocalUnsharedLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    localFilterActs(*_inputs[inpIdx], getWeightMatrix(passType, inpIdx), getActs(), _imgSize->at(inpIdx), _modulesX, _modulesX, _padding->at(inpIdx),\n                    _stride->at(inpIdx), _channels->at(inpIdx), _groups->at(inpIdx), scaleTargets, 1);\n    if (scaleTargets == 0) {\n        getActs().addVector(getBiasMatrix(passType));\n    }\n}\n\nvoid LocalUnsharedLayer::bpropBiases(NVMatrix& v, PASS_TYPE passType) {\n    _biases->getGrad().addSum(v, 1, getBIncScale(), getBGradScale(passType));\n}\n\nvoid LocalUnsharedLayer::bpropWeights(NVMatrix& v, int replicaIdx, int inpIdx, PASS_TYPE passType) {\n    float scaleWGrad = getGradScale(inpIdx, passType);\n    float scaleInc = getIncScale(inpIdx, passType);\n    localWeightActs(*_inputs[inpIdx], v, getGradTarget(inpIdx), _imgSize->at(inpIdx), _modulesX, _modulesX, _filterSize->at(inpIdx), _padding->at(inpIdx),\n                    _stride->at(inpIdx), _channels->at(inpIdx), _groups->at(inpIdx), scaleInc, scaleWGrad);\n}\n\nvoid LocalUnsharedLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    localImgActs(v, getWeightMatrix(passType, inpIdx), _prev[replicaIdx][inpIdx]->getActsGrad(),_imgSize->at(inpIdx), _imgSize->at(inpIdx), _modulesX,\n                 _padding->at(inpIdx), _stride->at(inpIdx), _channels->at(inpIdx), _groups->at(inpIdx), scaleTargets, 1);\n}\n\nvoid LocalUnsharedLayer::_constrainWeights() {\n    for (int i = 0; i < _weights->getSize(); i++) {\n        if (_weights->at(i).getWBall() > 0  && _weights->at(i).isOwner() && _weights->at(i).getLearningRateSchedule().getBaseValue() > 0) {\n            normalizeLocalWeights(*_weights->at(i), _modules, _weights->at(i).getWBall());\n        }\n    }\n}\n\n/*\n * =======================\n * SoftmaxLayer\n * =======================\n */\nSoftmaxLayer::SoftmaxLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID)\n    : Layer(convNetThread, paramsDict, replicaID, true), _doUpperGrad(false) {\n}\n\nvoid SoftmaxLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    NVMatrix& input = *_inputs[0];\n    input.max(1, _max);\n    input.addVector(_max, -1, getActs());\n    getActs().apply(NVMatrixOps::Exp());\n    getActs().sum(1, _sum);\n    getActs().eltwiseDivideByVector(_sum);\n}\n\nvoid SoftmaxLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    assert(inpIdx == 0);\n    LayerV& prev = _prev[replicaIdx];\n    if (_doUpperGrad) {\n        // Todo: rethink replica IDs or idxes... this here doesn't make a huge amount of sense\n        for (int i = 0; i < _next.size(); ++i) {\n            if (_next[i]->isGradProducer(getName())) {\n                NVMatrix& labels = _next[i]->getPrev()[replicaIdx][0]->getActs(getDeviceID()); // Get cost's labels\n                float gradCoeff = dynamic_cast<CostLayer*>(_next[i])->getCoeff();\n\n                computeLogregSoftmaxGrad(labels, getActs(), prev[0]->getActsGrad(), scaleTargets == 1, gradCoeff);\n                break;\n            }\n        }\n\n    } else {\n        computeSoftmaxGrad(getActs(), v, prev[0]->getActsGrad(), scaleTargets, 1);\n    }\n}\n\nvoid SoftmaxLayer::setDoUpperGrad(bool b) {\n    _doUpperGrad = b;\n}\n\n/*\n * =======================\n * ConcatenationLayer\n * =======================\n */\nConcatenationLayer::ConcatenationLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID)\n    : Layer(convNetThread, paramsDict, replicaID, false) {\n    _copyOffsets = pyDictGetIntV(paramsDict, \"copyOffsets\");\n    _copyOffsets->push_back(_numOutputs);\n}\n\nConcatenationLayer::~ConcatenationLayer() {\n    delete _copyOffsets;\n}\n\nvoid ConcatenationLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    getActs().resize(_numOutputs, _inputs[inpIdx]->getNumCols());\n    _inputs[inpIdx]->copy(getActs(), 0, -1, 0, -1, _copyOffsets->at(inpIdx), 0);\n}\n\nvoid ConcatenationLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    NVMatrix& copySrc = v.sliceRows(_copyOffsets->at(inpIdx), _copyOffsets->at(inpIdx + 1)); // view\n    _prev[replicaIdx][inpIdx]->getActsGrad().add(copySrc, scaleTargets, 1);\n    delete &copySrc;\n}\n\n/*\n * =======================\n * PassThroughLayer\n * =======================\n */\nPassThroughLayer::PassThroughLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID)\n    : Layer(convNetThread, paramsDict, replicaID, false) {\n}\n\nvoid PassThroughLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    // No-op\n}\n\nvoid PassThroughLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    // No-op\n}\n\nbool PassThroughLayer::postInit() {\n    if (Layer::postInit()) {\n        assert(getNumInputReplicas() == 1);\n        for (int i = 0, offset = 0; i < _prev[0].size(); offset += _prev[0][i]->getNumOutputs(), i++) {\n            MemoryView& vActs = _memSrcActs[getDeviceID()]->getMemorySource().addUser(_prev[0][i]->getName(), pair<int,int>(offset, offset + _prev[0][i]->getNumOutputs()));\n            MemoryView& vActsGrad = _memSrcActsGrad[getDeviceID()]->getMemorySource().addUser(_prev[0][i]->getName(), pair<int,int>(offset, offset + _prev[0][i]->getNumOutputs()));\n            _prev[0][i]->setMemorySourceActs(getDeviceID(), vActs);\n            _prev[0][i]->setMemorySourceActsGrad(getDeviceID(), vActsGrad);\n        }\n        return true;\n    }\n    return false;\n}\n\n\n/*\n * =======================\n * EltwiseSumLayer\n * =======================\n */\nEltwiseSumLayer::EltwiseSumLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : Layer(convNetThread, paramsDict, replicaID, false) {\n    _coeffs = pyDictGetFloatV(paramsDict, \"coeffs\");\n}\n\nEltwiseSumLayer::~EltwiseSumLayer() {\n    delete _coeffs;\n}\n\nvoid EltwiseSumLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    getActs().add(*_inputs[inpIdx], scaleTargets, _coeffs->at(inpIdx));\n}\n\nvoid EltwiseSumLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    _prev[replicaIdx][inpIdx]->getActsGrad().add(v, scaleTargets, _coeffs->at(inpIdx));\n}\n\n/*\n * =======================\n * EltwiseMaxLayer\n * =======================\n */\nEltwiseMaxLayer::EltwiseMaxLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : Layer(convNetThread, paramsDict, replicaID, false) {\n}\n\nvoid EltwiseMaxLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    if (inpIdx == 1) { // First input, do nothing\n        _inputs[inpIdx]->applyBinary(NVMatrixAggs::Max(), *_inputs[0], getActs());\n    } else if (inpIdx > 1) {\n        getActs().applyBinary(NVMatrixAggs::Max(), *_inputs[inpIdx]);\n    }\n}\n\nvoid EltwiseMaxLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    computeEltwiseMaxGrad(v, *_inputs[inpIdx], getActs(), _prev[replicaIdx][inpIdx]->getActsGrad(), scaleTargets != 0);\n}\n\n\n/*\n * =======================\n * DropoutLayer\n * =======================\n *\n * TODO: optimize away the case when using dopout over relus. Don't need the keepmask.\n */\nDropoutLayer::DropoutLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : Layer(convNetThread, paramsDict, replicaID, false) {\n    _enable = pyDictGetInt(paramsDict, \"enable\");\n    _keep = pyDictGetFloat(paramsDict, \"keep\");\n}\n\nvoid DropoutLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    if (_enable && passType == PASS_TRAIN) {\n        _keepMask.resize(*_inputs[inpIdx]);\n        _keepMask.randomizeUniform();\n        _keepMask.apply(DropoutSmallerThanOperator(_keep));\n        _inputs[inpIdx]->eltwiseMult(_keepMask, getActs());\n    } else {\n        _inputs[inpIdx]->copy(getActs());\n    }\n}\n\nvoid DropoutLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    LayerV& prev = _prev[replicaIdx];\n    if (_enable && passType == PASS_TRAIN) {\n        if (scaleTargets != 0) {\n            v.applyTernary(AddGradientBinaryOperator<NVMatrixBinaryOps::Multiply>(NVMatrixBinaryOps::Multiply()),\n                           _keepMask, prev[inpIdx]->getActsGrad(), prev[inpIdx]->getActsGrad());\n        } else {\n            v.eltwiseMult(_keepMask, prev[inpIdx]->getActsGrad());\n        }\n    } else {\n         prev[inpIdx]->getActsGrad().add(v, scaleTargets, 1);\n    }\n}\n\nvoid DropoutLayer::truncBwdActs() {\n    Layer::truncBwdActs();\n    _keepMask.truncate();\n}\n\n\n/*\n * =======================\n * Dropout2Layer\n * =======================\n *\n * TODO: optimize away the case when using dopout over relus. Don't need the keepmask.\n */\nDropout2Layer::Dropout2Layer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : DropoutLayer(convNetThread, paramsDict, replicaID) {\n}\n\nvoid Dropout2Layer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    if (_enable && passType == PASS_TRAIN) {\n        _keepMask.resize(*_inputs[inpIdx]);\n        _keepMask.randomizeUniform();\n        _keepMask.smallerThanScalar(_keep);\n        _inputs[inpIdx]->eltwiseMult(_keepMask, getActs());\n    } else {\n        _inputs[inpIdx]->scale(_keep, getActs());\n    }\n}\n\nvoid Dropout2Layer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    LayerV& prev = _prev[replicaIdx];\n    if (_enable && passType == PASS_TRAIN) {\n        if (scaleTargets != 0) {\n            v.applyTernary(AddGradientBinaryOperator<NVMatrixBinaryOps::Multiply>(NVMatrixBinaryOps::Multiply()),\n                           _keepMask, prev[inpIdx]->getActsGrad(), prev[inpIdx]->getActsGrad());\n        } else {\n            v.eltwiseMult(_keepMask, prev[inpIdx]->getActsGrad());\n        }\n    } else {\n        if (scaleTargets != 0) {\n             v.applyBinary(AddGradientOperator<NVMatrixOps::MultByScalar>(NVMatrixOps::MultByScalar(_keep)),\n                           prev[inpIdx]->getActsGrad(), prev[inpIdx]->getActsGrad());\n        } else {\n            v.scale(_keep, prev[inpIdx]->getActsGrad());\n        }\n    }\n}\n\n/*\n * =======================\n * DataLayer\n * =======================\n */\nDataLayer::DataLayer(ConvNet* convNet, PyObject* paramsDict, int replicaID) : Layer(NULL, paramsDict, replicaID, false) {\n    _dataIdx = pyDictGetInt(paramsDict, \"dataIdx\");\n    _start = pyDictGetInt(paramsDict, \"start\");\n    _end = pyDictGetInt(paramsDict, \"end\");\n    _useBuffer = false;\n    _outstandingCopyRequest = false;\n    _convNet = convNet;\n}\n\nDataLayer::~DataLayer() {\n    for (map<int,cudaStream_t>::const_iterator it = _copyStreams.begin(); it != _copyStreams.end(); ++it) {\n        checkCudaErrors(cudaStreamDestroy(it->second));\n    }\n    for (std::map<int, MemoryView*>::iterator it = _memSrcActs2.begin(); it != _memSrcActs2.end(); ++it) {\n        if (it->second->getMemorySource().truncate(_name)) {\n            delete &it->second->getMemorySource();\n        }\n    }\n    _copier->stop();\n    delete _copier;\n}\n\nvoid DataLayer::fprop(PASS_TYPE passType, int passIdx, bool fromBuffer) {\n    waitForCopyFinish();\n    if (fromBuffer && getFwdActiveInputReplicaIdx(passIdx) >= 0) {\n        _useBuffer = !_useBuffer;\n    }\n\n    for (int i = 0; i < _next.size(); i++) {\n        _next[i]->getConvNetThread().getMessageQueue().enqueue(new FpropMessage(*_next[i], passType, passIdx));\n    }\n}\n\nvoid DataLayer::waitForCopyFinish() {\n    if (_outstandingCopyRequest) {\n        _copyFinishQueue.dequeue();\n        assert(_copyFinishQueue.getNumElements() == 0);\n        _outstandingCopyRequest = false;\n    }\n}\n\ncudaStream_t DataLayer::getCopyStream(int deviceID) {\n    if (_copyStreams.count(deviceID) == 0) {\n        NVMatrix::setDeviceID(deviceID);\n        checkCudaErrors(cudaStreamCreateWithFlags(&_copyStreams[deviceID], cudaStreamNonBlocking));\n    }\n    return _copyStreams[deviceID];\n}\n\nvoid DataLayer::copyData(CPUData& data, bool other, int passIdx) {\n    assert(!_outstandingCopyRequest);\n    assert(_copyFinishQueue.getNumElements() == 0);\n    _copier->getQueue().enqueue(new DataCopyMessage(data, other, passIdx));\n    _outstandingCopyRequest = true;\n}\n\nint DataLayer::getNumInputReplicas() {\n    return _convNet->getNumReplicasMax() / getNumReplicas();\n}\n\nvoid DataLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n   \n}\n\nNVMatrix& DataLayer::getActs(int deviceID) {\n    return getActs(deviceID, false, -1);\n}\n\nNVMatrix& DataLayer::getActs(int deviceID, bool other, int numCases) {\n//    printf(\"%s[%d] getActs(%d, %d, %d)\\n\", _name.c_str(), getReplicaID(), deviceID, other, numCases);\n    assert(_memSrcActs.count(deviceID) > 0);\n    assert(_memSrcActs2.count(deviceID) > 0);\n    return (_useBuffer != other ? _memSrcActs2[deviceID]->getMemory(numCases) : _memSrcActs[deviceID]->getMemory(numCases));\n}\n\nConvNet& DataLayer::getConvNet() {\n    return *_convNet;\n}\n\nbool DataLayer::postInit() {\n    if (Layer::postInit()) {\n        for (int i = 0; i < _next.size(); ++i) {\n            int d = _next[i]->getDeviceID();\n            if (_memSrcActs2.count(d) == 0) {\n                _memSrcActs2[d] = &MemorySource::make(_numOutputs, d, getName());\n            }\n        }\n        intv cpus = getDeviceCPUs(_next[0]->getDeviceID());\n        _copier = new DataCopyThread(*this, cpus);\n        _copier->start();\n        return true;\n    }\n    return false;\n}\n\nbool DataLayer::isGradProducer() {\n    return false;\n}\n\n/*\n * =======================\n * DataCopyThread\n * =======================\n */\nDataCopyThread::DataCopyThread(DataLayer& parent, intv& cpus) : _parent(&parent), _sleepUsec(0), Thread(true, cpus) {\n}\n\nQueue<DataCopyMessage*>& DataCopyThread::getQueue() {\n    return _queue;\n}\n\nvoid DataCopyThread::stop() {\n    getQueue().enqueue(new DataCopyExitMessage());\n    join();\n}\n\nvoid* DataCopyThread::run() {\n    NVMatrix::setDeviceID(*_parent->getNextDeviceIDs().begin());\n    bool exit = false;\n    while(!exit) {\n        DataCopyMessage& msg = *_queue.dequeue();\n        exit = msg.getType() == DataCopyMessage::EXIT;\n        if (!exit) {\n            CPUData& data = msg.getData();\n            int passIdx = msg.getPassIdx();\n            bool other = msg.isOther();\n\n            Matrix& dataMatrix = data.getData(_parent->getDataIdx());\n            // How many times is this layer going to process microbatches from this minibatch?\n            assert(_parent->getNumReplicasNext() == _parent->getNumReplicas());\n            int microIdx = _parent->getFwdActiveInputReplicaIdx(passIdx);\n\n            if (microIdx >= 0) {\n                if (_requestTimer.isStarted()) {\n                    double requestIntervalMsec = _requestTimer.stop();\n                    // Sleep for up to 1/20th the average request interval\n                    _sleepUsec = int(round(0.95 * _sleepUsec + 0.05 * (_parent->getReplicaID() / double(_parent->getNumReplicas())) * requestIntervalMsec * 1000.0 / 20.0));\n                }\n                _requestTimer.start();\n                if (other) {\n                    // Sleeping a bit is helpful because in typical nets, copying input data\n                    // as soon as it's available will produce contention with other communications\n                    // that are happening at the time. This is very much a hack, so in the future\n                    // it might be good to replace it with something smarter which schedules access\n                    // to communication links.\n                    usleep(_sleepUsec);\n                }\n                microIdx += _parent->getReplicaID() * _parent->getNumInputReplicas();\n                // Safer to divup because this way you won't get a minibatch size of 0\n                int microbatchSize = DIVUP(data.getNumCases(), _parent->getConvNet().getNumReplicasMax());\n                int microStart = microIdx * microbatchSize;\n                int microEnd = min(data.getNumCases(), (microIdx + 1) * microbatchSize);\n                // Check that this replica has some data. This can be false when, for example,\n                // there are only 7 examples in the minibatch but 8 replicas.\n                if (microStart < microEnd) {\n                    assert(dataMatrix.isView() == dataMatrix.isTrans());\n                    int pipe = _parent->getConvNet().getDataCopyPD().getPipe(_parent->getReplicaID()/2);\n                    if (dataMatrix.isTrans()) {\n                        Matrix& replicaDataMatrix = dataMatrix.sliceCols(microStart, microEnd);\n                        // In this case, dataMatrix is a view on memory allocated by Python.\n                        //_hostMemFwd.copyFromHost(replicaDataMatrix, true);\n                        _hostMemFwd.resize(replicaDataMatrix.getNumRows(), replicaDataMatrix.getNumCols(), true);\n                        memcpy(_hostMemFwd.getDevData(), replicaDataMatrix.getData(), replicaDataMatrix.getNumDataBytes());\n                        delete &replicaDataMatrix; // view\n                        NVMatrix& hostMemFwdSlice = _hostMemFwd.sliceRows(_parent->getStart(), _parent->getEnd());\n                        for (intv::iterator it = _parent->getNextDeviceIDs().begin(); it != _parent->getNextDeviceIDs().end(); ++it) {\n                            int deviceID = *it;\n                            // Copy my output to this guy's GPU\n                            NVMatrix::setDeviceID(deviceID);\n                            // Note to self: this is the path that gets executed in practice\n                            // in my models. It does a transpose & copy simultaneously.\n                            hostMemFwdSlice.flipTrans(_parent->getActs(deviceID, other, microEnd - microStart), _parent->getCopyStream(deviceID));\n                        }\n                        delete &hostMemFwdSlice;\n                    } else {\n                        // Hacky way to copy a slice to _hostMemFwd\n                        _hostMemFwd.resize(dataMatrix.getNumRows(), microEnd - microStart);\n                        Matrix tmp(_hostMemFwd.getDevData(), _hostMemFwd.getNumRows(), _hostMemFwd.getNumCols(), _hostMemFwd.isTrans());\n                        dataMatrix.sliceCols(microStart, microEnd, tmp);\n                        NVMatrix& hostMemFwdSlice = _hostMemFwd.sliceRows(_parent->getStart(), _parent->getEnd());\n                        for (intv::iterator it = _parent->getNextDeviceIDs().begin(); it != _parent->getNextDeviceIDs().end(); ++it) {\n                            int deviceID = *it;\n                            // Copy my output to this guy's GPU\n                            NVMatrix::setDeviceID(deviceID);\n                            hostMemFwdSlice.copy(_parent->getActs(deviceID, other, microEnd - microStart), _parent->getCopyStream(deviceID));\n                        }\n                        delete &hostMemFwdSlice;\n                    }\n\n                    for (intv::iterator it = _parent->getNextDeviceIDs().begin(); it != _parent->getNextDeviceIDs().end(); ++it) {\n                        int deviceID = *it;\n                        NVMatrix::setDeviceID(deviceID);\n                        NVMatrix::syncStream(_parent->getCopyStream(deviceID));\n                    }\n                    _parent->getConvNet().getDataCopyPD().freePipe(pipe);\n                } else {\n                    for (intv::iterator it = _parent->getNextDeviceIDs().begin(); it != _parent->getNextDeviceIDs().end(); ++it) {\n                        int deviceID = *it;\n                        _parent->getActs(deviceID, other, 0);\n                    }\n                }\n            }\n            _parent->getCopyFinishQueue().enqueue(1);\n        }\n        delete &msg;\n    }\n    return NULL;\n}\n\n/*\n * =====================\n * PoolLayer\n * =====================\n */\nPoolLayer::PoolLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool trans)\n    : Layer(convNetThread, paramsDict, replicaID, trans), TwoDLayerInterface(paramsDict) {\n    _sizeX = pyDictGetInt(paramsDict, \"sizeX\");\n    _start = pyDictGetInt(paramsDict, \"start\");\n    _stride = pyDictGetInt(paramsDict, \"stride\");\n    _outputsX = pyDictGetInt(paramsDict, \"outputsX\");\n    _pool = pyDictGetString(paramsDict, \"pool\");\n}\n\nPoolLayer& PoolLayer::make(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) {\n    std::string _pool = pyDictGetString(paramsDict, \"pool\");\n    if (_pool == \"max\") {\n        return *new MaxPoolLayer(convNetThread, paramsDict, replicaID, false);\n    } else if(_pool == \"maxabs\") {\n        return *new MaxPoolLayer(convNetThread, paramsDict, replicaID, true);\n    } else if(_pool == \"avg\") {\n        return *new AvgPoolLayer(convNetThread, paramsDict, replicaID);\n    }\n    throw std::string(\"Unknown pooling layer type \") + _pool;\n}\n\n/*\n * =====================\n * AvgPoolLayer\n * =====================\n */\nAvgPoolLayer::AvgPoolLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : PoolLayer(convNetThread, paramsDict, replicaID, false) {\n    _sum = pyDictGetInt(paramsDict, \"sum\");\n}\n\nvoid AvgPoolLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    if (_sum) {\n        convLocalPool(*_inputs[0], getActs(), _channels, _sizeX, _start, _stride, _outputsX, AvgPooler<true>());\n    } else {\n        convLocalPool(*_inputs[0], getActs(), _channels, _sizeX, _start, _stride, _outputsX, AvgPooler<false>());\n    }\n}\n\nvoid AvgPoolLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    convLocalAvgUndo(v, _prev[replicaIdx][0]->getActsGrad(), _sizeX, _start, _stride, _outputsX, _imgSize, _sum, scaleTargets, 1);\n}\n\n/*\n * =====================\n * MaxPoolLayer\n * =====================\n */\nMaxPoolLayer::MaxPoolLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool abs) : PoolLayer(convNetThread, paramsDict, replicaID, false), _abs(abs) {\n}\n\nvoid MaxPoolLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    if (_abs) {\n        convLocalPool(*_inputs[0], getActs(), _channels, _sizeX, _start, _stride, _outputsX, MaxAbsPooler());\n    } else {\n        convLocalPool(*_inputs[0], getActs(), _channels, _sizeX, _start, _stride, _outputsX, MaxPooler());\n    }\n}\n\nvoid MaxPoolLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    assert(inpIdx == 0);\n    convLocalMaxUndo(*_inputs[0], v, getActs(), _prev[replicaIdx][inpIdx]->getActsGrad(), _sizeX, _start, _stride, _outputsX, scaleTargets, 1);\n}\n\n/*\n * =====================\n * CrossMapPoolLayer\n * =====================\n */\nCrossMapPoolLayer::CrossMapPoolLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool trans)\n    : Layer(convNetThread, paramsDict, replicaID, trans), TwoDLayerInterface(paramsDict) {\n    _size = pyDictGetInt(paramsDict, \"size\");\n    _start = pyDictGetInt(paramsDict, \"start\");\n    _stride = pyDictGetInt(paramsDict, \"stride\");\n    _outputs = pyDictGetInt(paramsDict, \"outputChannels\");\n    _pool = pyDictGetString(paramsDict, \"pool\");\n}\n\nCrossMapPoolLayer& CrossMapPoolLayer::make(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) {\n    std::string _pool = pyDictGetString(paramsDict, \"pool\");\n    if (_pool == \"max\") {\n        return *new CrossMapMaxPoolLayer(convNetThread, paramsDict, replicaID);\n    }\n    throw std::string(\"Unknown pooling layer type \") + _pool;\n}\n\n/*\n * =====================\n * CrossMapMaxPoolLayer\n * =====================\n */\nCrossMapMaxPoolLayer::CrossMapMaxPoolLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : CrossMapPoolLayer(convNetThread, paramsDict, replicaID, false) {\n}\n\nvoid CrossMapMaxPoolLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    convPoolCrossMap(*_inputs[0], getActs(), _start, _size, _outputs, _stride, _imgSize, MaxPooler());\n}\n\nvoid CrossMapMaxPoolLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    assert(inpIdx == 0);\n    convCrossMapMaxPoolUndo(*_inputs[0], v, getActs(), _prev[replicaIdx][0]->getActsGrad(), _imgSize, _start, _size, _stride, scaleTargets, 1);\n}\n\n/*\n * =====================\n * RandomScaleLayer\n * =====================\n */\nRandomScaleLayer::RandomScaleLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : Layer(convNetThread, paramsDict, replicaID, false), TwoDLayerInterface(paramsDict) {\n    _maxScale = pyDictGetFloat(paramsDict, \"maxScale\");\n    _tgtSize = pyDictGetInt(paramsDict, \"tgtSize\");\n    // The smallest size the image could be after rescaling\n    _minScaledSize = _imgSize / _maxScale;\n   \n    // The number of discrete scales we're considering\n    int numScales = _imgSize - _minScaledSize + 1;\n   \n    // The total number of squares of size _tgtSize that we can extract\n    // from all these scales\n    double numCrops = numScales * (numScales + 1) * (2 * numScales + 1) / 6;\n   \n    // For each scale, record the fraction of the squares that it has.\n    // This will be the probability of sampling this scale.\n    _scaleProbs.push_back(1.0 / numCrops);\n    for (int s = 1; s < numScales; ++s) {\n        _scaleProbs.push_back(_scaleProbs[s-1] + (s + 1) * (s + 1) / numCrops);\n    }\n}\n\nvoid RandomScaleLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    if (IS_TRAIN(passType)) {\n        // _maxScale is in the range [1, 2)\n        float r = randf;\n        int rescaledSize = _tgtSize;\n        float scaleFactor = _maxScale;\n        // Find which scale we have sampled\n        for (int s = 0; s < _scaleProbs.size(); ++s) {\n            if (r <= _scaleProbs[s]) {\n                rescaledSize += s;\n                float scaleFactorEnd = _imgSize / float(rescaledSize);\n                float scaleFactorStart = max(1.0, _imgSize / (1.0 + rescaledSize));\n                scaleFactor = scaleFactorStart + randf * (scaleFactorEnd - scaleFactorStart);\n                break;\n            }\n        }\n        assert(rescaledSize >= _tgtSize);\n        int maxStart = rescaledSize - _tgtSize;\n        int startY = rand() % (1 + maxStart), startX = rand() % (1 + maxStart);\n        if (rescaledSize  == _imgSize) {\n            convCrop(*_inputs[0], getActs(), rescaledSize, _tgtSize, startY, startX);\n        } else {\n            convResizeBilinear(*_inputs[0], _rescaledActs, _imgSize, rescaledSize, scaleFactor);\n            convCrop(_rescaledActs, getActs(), rescaledSize, _tgtSize, startY, startX);\n        }\n        _rescaledActs.truncate(); // this'll have a different size each time so may as well truncate it.\n    } else if (IS_MULTIVIEW_TEST(passType)) { // for now...\n        _inputs[0]->copy(getActs());\n    } else if (IS_TEST(passType)) { // Test on center patch\n        convResizeBilinear(*_inputs[0], getActs(), _imgSize, _tgtSize, _maxScale);\n    }\n}\n\nvoid RandomScaleLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    assert(false);\n}\n\n/*\n * =====================\n * CropLayer\n * =====================\n */\nCropLayer::CropLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : Layer(convNetThread, paramsDict, replicaID, false), TwoDLayerInterface(paramsDict) {\n    _startX = pyDictGetInt(paramsDict, \"startX\");\n    _startY = pyDictGetInt(paramsDict, \"startY\");\n    _tgtSize = pyDictGetInt(paramsDict, \"sizeX\");\n}\n\nvoid CropLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    convCrop(*_inputs[0], getActs(), _imgSize, _tgtSize, _startY, _startX);\n}\n\nvoid CropLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    assert(false);\n}\n\n/*\n * =====================\n * NailbedLayer\n * =====================\n */\nNailbedLayer::NailbedLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : Layer(convNetThread, paramsDict, replicaID, false), TwoDLayerInterface(paramsDict) {\n    _start = pyDictGetInt(paramsDict, \"start\");\n    _stride = pyDictGetInt(paramsDict, \"stride\");\n    _outputsX = pyDictGetInt(paramsDict, \"outputsX\");\n}\n\nvoid NailbedLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    convBedOfNails(*_inputs[0], getActs(), _channels, _imgSize, _start, _stride, 0, 1);\n}\n\nvoid NailbedLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    convBedOfNailsUndo(v, _prev[replicaIdx][0]->getActsGrad(), _channels, _imgSize, _start, _stride, scaleTargets, 1);\n}\n\n/*\n * =====================\n * GaussianBlurLayer\n * =====================\n */\nGaussianBlurLayer::GaussianBlurLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : Layer(convNetThread, paramsDict, replicaID, false), TwoDLayerInterface(paramsDict) {\n    _hFilter = pyDictGetMatrix(paramsDict, \"filter\");\n}\n\nGaussianBlurLayer::~GaussianBlurLayer() {\n    delete _hFilter;\n}\n\nvoid GaussianBlurLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    convGaussianBlur(*_inputs[0], _filter, getActs(), true, _channels, 0, 1);\n    convGaussianBlur(getActs(), _filter, getActs(), false, _channels, 0, 1);\n}\n\nvoid GaussianBlurLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    NVMatrix& tgt = _prev[replicaIdx][0]->getNumComputedActsGrads(getDeviceID()) > 0 ? _actGradsTmp : _prev[replicaIdx][0]->getActsGrad();\n    convGaussianBlur(v, _filter, tgt, true, _channels, 0, 1);\n    convGaussianBlur(tgt, _filter, _prev[replicaIdx][0]->getActsGrad(), false, _channels, scaleTargets, 1);\n}\n\nvoid GaussianBlurLayer::copyToGPU() {\n    _filter.copyFromHost(*_hFilter, true);\n}\n\n /*\n * =====================\n * HorizontalReflectionLayer\n * =====================\n */\nHorizontalReflectionLayer::HorizontalReflectionLayer(ConvNetThread* convNet, PyObject* paramsDict, int replicaID) : Layer(convNet, paramsDict, replicaID, false), TwoDLayerInterface(paramsDict) {\n    assert(_channels >= 1 && _channels <= 3);\n}\n\nvoid HorizontalReflectionLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    convReflectHorizontal(*_inputs[0], getActs(), _imgSize);\n}\n\nvoid HorizontalReflectionLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    convReflectHorizontal(v, _prev[replicaIdx][0]->getActsGrad(), _imgSize);\n}\n\n/*\n * =====================\n * ResizeLayer\n * =====================\n */\nResizeLayer::ResizeLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : Layer(convNetThread, paramsDict, replicaID, false), TwoDLayerInterface(paramsDict) {\n    _tgtSize = pyDictGetInt(paramsDict, \"tgtSize\");\n    _scale = pyDictGetFloat(paramsDict, \"scale\");\n}\n\nvoid ResizeLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    convResizeBilinear(*_inputs[0], getActs(), _imgSize, _tgtSize, _scale);\n}\n\n// Can't do this\nvoid ResizeLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    assert(false);\n}\n\n/*\n * =====================\n * RGBToYUVLayer\n * =====================\n */\nRGBToYUVLayer::RGBToYUVLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : Layer(convNetThread, paramsDict, replicaID, false) {\n}\n\nvoid RGBToYUVLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    convRGBToYUV(*_inputs[0], getActs());\n}\n\n// Can't do this\nvoid RGBToYUVLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    assert(false);\n}\n\n/*\n * =====================\n * RGBToLABLayer\n * =====================\n */\nRGBToLABLayer::RGBToLABLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : Layer(convNetThread, paramsDict, replicaID, false) {\n    _center = pyDictGetInt(paramsDict, \"center\");\n}\n\nvoid RGBToLABLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    convRGBToLAB(*_inputs[0], getActs(), _center);\n}\n\n// Can't do this\nvoid RGBToLABLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    assert(false);\n}\n\n/*\n * =====================\n * ResponseNormLayer\n * =====================\n */\nResponseNormLayer::ResponseNormLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID)\n: Layer(convNetThread, paramsDict, replicaID, false), TwoDLayerInterface(paramsDict) {\n    _size = pyDictGetInt(paramsDict, \"size\");\n    _scale = pyDictGetFloat(paramsDict, \"scale\");\n    _pow = pyDictGetFloat(paramsDict, \"pow\");\n    _minDiv = pyDictGetFloat(paramsDict, \"minDiv\");\n}\n\nvoid ResponseNormLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    convResponseNorm(*_inputs[0], _denoms, getActs(), _channels, _size, _scale, _pow, _minDiv);\n}\n\nvoid ResponseNormLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    convResponseNormUndo(v, _denoms, *_inputs[0], getActs(), _prev[replicaIdx][0]->getActsGrad(), _channels, _size, _scale, _pow, scaleTargets, 1);\n}\n\nvoid ResponseNormLayer::truncBwdActs() {\n    Layer::truncBwdActs();\n    _denoms.truncate();\n}\n\n/*\n * =====================\n * CrossMapResponseNormLayer\n * =====================\n */\nCrossMapResponseNormLayer::CrossMapResponseNormLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID)\n: ResponseNormLayer(convNetThread, paramsDict, replicaID) {\n    _blocked = pyDictGetInt(paramsDict, \"blocked\");\n}\n\nvoid CrossMapResponseNormLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    assert(inpIdx == 0);\n    convResponseNormCrossMap(*_inputs[0], getActs(), _channels, _size, _scale, _pow, _minDiv, _blocked);\n}\n\nvoid CrossMapResponseNormLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    convResponseNormCrossMapUndo(v, *_inputs[0], getActs(), _prev[replicaIdx][0]->getActsGrad(), _channels, _size, _scale, _pow, _minDiv, _blocked, scaleTargets, 1);\n}\n\n/*\n * =====================\n * ContrastNormLayer\n * =====================\n */\nContrastNormLayer::ContrastNormLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : ResponseNormLayer(convNetThread, paramsDict, replicaID) {\n}\n\nvoid ContrastNormLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    NVMatrix& images = *_inputs[0];\n    convLocalPool(images, _meanDiffs, _channels, _size, -_size/2, 1, _imgSize, AvgPooler<false>());\n    _meanDiffs.add(images, -1, 1);\n    convContrastNorm(images, _meanDiffs, _denoms, getActs(), _channels, _size, _scale, _pow, _minDiv);\n}\n\nvoid ContrastNormLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    convContrastNormUndo(v, _denoms, _meanDiffs, getActs(), _prev[replicaIdx][inpIdx]->getActsGrad(), _channels, _size, _scale, _pow, scaleTargets, 1);\n}\n\nvoid ContrastNormLayer::truncBwdActs() {\n    ResponseNormLayer::truncBwdActs();\n    _meanDiffs.truncate();\n}\n\n/*\n * =====================\n * CostLayer\n * =====================\n */\nCostLayer::CostLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID, bool trans)\n    : Layer(convNetThread, paramsDict, replicaID, trans) {\n    _coeff = pyDictGetFloat(paramsDict, \"coeff\");\n    _numCases = 0;\n    _aggregated = pyDictGetInt(paramsDict, \"aggregated\") != 0;\n}\n\nfloat CostLayer::getCoeff() {\n    return _coeff;\n}\n\nvoid CostLayer::bprop(NVMatrix& v, PASS_TYPE passType, int passIdx) {\n    if (_coeff != 0) {\n        Layer::bprop(v, passType, passIdx);\n    }\n}\n\nbool CostLayer::fprop(PASS_TYPE passType, int passIdx) {\n    if (Layer::fprop(passType, passIdx)) {\n        syncStream();\n        getConvNet().getMessageQueue().enqueue(new Message(FPROP_TERMINAL));\n        return true;\n    }\n    return false;\n}\n\nvoid CostLayer::fpropCommon(PASS_TYPE passType) {\n    _numCases = Layer::getNumCases(*_inputs[0]);\n}\n\nint CostLayer::getNumCases() {\n    return _numCases;\n}\n\nbool CostLayer::isGradProducer() {\n    return _coeff != 0;\n}\n\ndoublev& CostLayer::getCost() {\n    return *new doublev(_costv);\n}\n\n// This is called between microbatches\nvoid CostLayer::resetPassIdx() {\n    Layer::resetPassIdx();\n    _costv.clear();\n}\n\nCostLayer& CostLayer::make(ConvNetThread* convNetThread, PyObject* paramsDict, std::string& type, int replicaID) {\n    if (type == \"cost.crossent\") {\n        return *new CrossEntCostLayer(convNetThread, paramsDict, replicaID);\n    } else if (type == \"cost.bce\") {\n        return *new BinomialCrossEntropyCostLayer(convNetThread, paramsDict, replicaID);\n    } else if (type == \"cost.dce\") {\n        return *new DetectionCrossEntropyCostLayer(convNetThread, paramsDict, replicaID);\n    } else if (type == \"cost.logreg\") {\n        return *new LogregCostLayer(convNetThread, paramsDict, replicaID);\n    } else if (type == \"cost.sum2\") {\n        return *new SumOfSquaresCostLayer(convNetThread, paramsDict, replicaID);\n    }\n    throw std::string(\"Unknown cost layer type \") + type;\n}\n\n/*\n * =====================\n * CrossEntCostLayer\n * =====================\n */\nCrossEntCostLayer::CrossEntCostLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : CostLayer(convNetThread, paramsDict, replicaID, false) {\n}\n\nvoid CrossEntCostLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    // This layer uses its two inputs together\n    if (inpIdx == 0) {\n        NVMatrix& labels = *_inputs[0];\n        NVMatrix& probs = *_inputs[1];\n        int numCases = labels.getLeadingDim();\n        computeCrossEntCost(labels, probs, _trueLabelLogProbs, _correctProbs);\n        _costv.clear();\n        _costv.push_back(-_trueLabelLogProbs.sum());\n        _costv.push_back(numCases - _correctProbs.sum());\n    }\n}\n\nvoid CrossEntCostLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    assert(inpIdx == 1);\n    LayerV& prev = _prev[replicaIdx];\n    NVMatrix& labels = *_inputs[0];\n    NVMatrix& probs = *_inputs[1];\n    NVMatrix& target = prev[1]->getActsGrad();\n    // Numerical stability optimization: if the layer below me is a softmax layer, let it handle\n    // the entire gradient computation to avoid multiplying and dividing by a near-zero quantity.\n    bool doWork = prev[1]->getNext().size() > 1 || prev[1]->getType() != \"softmax\" || prev[1]->getDeviceID() != getDeviceID();\n    if (doWork) {\n        computeCrossEntGrad(labels, probs, target, scaleTargets == 1, _coeff);\n    }\n}\n\n/*\n * =====================\n * BinomialCrossEntropyCostLayer\n * =====================\n */\nBinomialCrossEntropyCostLayer::BinomialCrossEntropyCostLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : CostLayer(convNetThread, paramsDict, replicaID, false) {\n    _computeSoftmaxErrorRate = pyDictGetInt(paramsDict, \"computeSoftmaxErrorRate\");\n    _posWeight = pyDictGetFloat(paramsDict, \"posWeight\");\n}\n\nvoid BinomialCrossEntropyCostLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    // This layer uses its two inputs together\n    if (inpIdx == 0) {\n        NVMatrix& labels = *_inputs[0];\n        NVMatrix& probs = *_inputs[1];\n        int numCases = labels.getLeadingDim();\n        labels.applyBinary(BinomialCrossEntOperator(_posWeight), probs, _tmpProbs);\n        _costv.clear();\n        // Cross-entropy cost\n        _costv.push_back(-_tmpProbs.sum(_tmpbuf));// / labels.getFollowingDim());\n\n        // If aggregated, we don't produce these outputs because they're not additive.\n        // They have no meaning if this is just a partial cost.\n        if (!_aggregated) {\n            // \"Correct\" classifications. To compute these we threshold probs\n            // and just count the number of entries that agree with labels.\n            probs.biggerThanScalar(0.5, _tmpProbs);\n            _tmpProbs.equals(labels);\n            _costv.push_back((_tmpProbs.getNumElements() - _tmpProbs.sum(_tmpbuf)) / double(labels.getFollowingDim()));\n\n            if (_computeSoftmaxErrorRate) {\n                // Also compute top-1 error as if this is softmax and there's only one correct class\n                probs.max(0, _tmpVec);\n                assert(_tmpVec.getNumElements() == numCases); // Make sure we did max on correct axis\n                probs.equalsVector(_tmpVec, _correctProbs);\n                _correctProbs.sum(0, _tmpVec); // Divide by the # of labels that we predict as being present\n                float m = _tmpVec.max();\n\n                _correctProbs.eltwiseDivideByVector(_tmpVec);\n                _correctProbs.eltwiseMult(labels);\n\n                _costv.push_back(numCases - _correctProbs.sum(_tmpbuf));\n            }\n        }\n    }\n}\n\nvoid BinomialCrossEntropyCostLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    assert(inpIdx == 1);\n    LayerV& prev = _prev[replicaIdx];\n    NVMatrix& labels = *_inputs[0];\n    NVMatrix& probs = *_inputs[1];\n    NVMatrix& target = prev[1]->getActsGrad();\n    // Numerical stability optimization: if the layer below me is a logistic neuron layer, let it handle\n    // the entire gradient computation to avoid multiplying and dividing by a near-zero quantity.\n    bool doWork =   prev[1]->getNext().size() > 1\n                    || prev[1]->getType() != \"neuron\"\n                    || static_cast<NeuronLayer*>(prev[1])->getNeuronType() != \"logistic\"\n                    ||  prev[1]->getDeviceID() != getDeviceID()\n                    || prev[1]->getNumReplicas() != getNumReplicas();\n    if (doWork) {\n        printf(\"Computing cross-entropy gradient the stupid way\\n\");\n        if (scaleTargets == 0) {\n            labels.applyBinary(BinomialCrossEntGradientOperator(_coeff, _posWeight), probs, target);\n        } else {\n            labels.applyTernary(AddGradientBinaryOperator<BinomialCrossEntGradientOperator>(BinomialCrossEntGradientOperator(_coeff, _posWeight)), probs, target, target);\n        }\n    }\n}\n\nfloat BinomialCrossEntropyCostLayer::getPosWeight() {\n    return _posWeight;\n}\n/*\n * =====================\n * DetectionCrossEntropyCostLayer\n * =====================\n */\nDetectionCrossEntropyCostLayer::DetectionCrossEntropyCostLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID)\n    : BinomialCrossEntropyCostLayer(convNetThread, paramsDict, replicaID) {\n    assert(!_aggregated);\n}\n\nvoid DetectionCrossEntropyCostLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    BinomialCrossEntropyCostLayer::fpropActs(inpIdx, scaleTargets, passType, passIdx);\n    // This layer uses its two inputs together\n    if (inpIdx == 0) {\n        NVMatrix& labels = *_inputs[0];\n        NVMatrix& probs = *_inputs[1];\n        int numCases = labels.getLeadingDim();\n\n        /*\n         * Add information sufficient to compute precision and recall for each class.\n         */\n        // NOTE: _tmpProbs contains ((probs > 0.5) == labels)\n        labels.sum(1, _numPositive);      // sum(labels, 1)\n\n        _tmpProbs.eltwiseMult(labels); // labels * ((probs > 0.5) == labels)\n        _tmpProbs.sum(1, _numTruePositive);\n\n        probs.biggerThanScalar(0.5, _tmpProbs);\n        _tmpProbs.sum(1, _numDeclaredPositive);\n\n        _numDeclaredPositive.copyToHost(_hNumDeclaredPositive, true);\n        _numPositive.copyToHost(_hNumPositive, true);\n        _numTruePositive.copyToHost(_hNumTruePositive, true);\n\n        for (int i = 0; i < labels.getFollowingDim(); ++i) {\n            _costv.push_back(_hNumDeclaredPositive(i, 0));                  // 2\n            _costv.push_back(_hNumPositive(i, 0));                          // 3\n            _costv.push_back(_hNumTruePositive(i, 0));                      // 4\n        }\n\n    }\n}\n\n/*\n * =====================\n * LogregCostLayer\n * =====================\n */\nLogregCostLayer::LogregCostLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : CostLayer(convNetThread, paramsDict, replicaID, false) {\n    _topk = pyDictGetInt(paramsDict, \"topk\");\n//    _numAccumed = 0;\n}\n\nvoid LogregCostLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    // This layer uses its two inputs together\n    if (inpIdx == 0) {\n        NVMatrix& labels = *_inputs[0];\n        NVMatrix* probs = _inputs[1];\n\n        _doCompute = !IS_MULTIVIEW_TEST(passType);\n        if (!_doCompute) {\n            if (IS_MULTIVIEW_TEST_START(passType)) {\n                if (_probsAccum.count(passIdx) == 0) {\n                    _probsAccum[passIdx] = new NVMatrix(*probs);\n                }\n                probs->copy(*_probsAccum[passIdx]);\n                _numAccumed[passIdx] = 1;\n            } else {\n                _probsAccum[passIdx]->add(*probs);\n                _numAccumed[passIdx] += 1;\n            }\n            if (IS_MULTIVIEW_TEST_END(passType)) {\n                probs = _probsAccum[passIdx];\n                probs->scale(1.0 / _numAccumed[passIdx]);\n                _doCompute = true;\n            }\n        }\n        if (_doCompute) {\n            int numCases = labels.getNumElements();\n            probs->max(0,_maxProbs);\n            if (_topk == 1) {\n                computeLogregCost(labels, *probs, _maxProbs, _trueLabelLogProbs, _correctProbs);\n            } else {\n                computeMultiSoftmaxCost(labels, *probs, _maxProbs, _trueLabelLogProbs, _correctProbs, _topkProbs, _topk);\n            }\n            _costv.clear();\n            double top1 = _correctProbs.sum(_tmpbuf);\n\n            _costv.push_back(-_trueLabelLogProbs.sum(_tmpbuf));\n            _costv.push_back(numCases - top1);\n            _costv.push_back(numCases - (_topk == 1 ? top1 : _topkProbs.sum(_tmpbuf)));\n\n        }\n    }\n}\n\nNVMatrix& LogregCostLayer::getProbsAccum(int replicaIdx) {\n    return *_probsAccum[replicaIdx];\n}\n\nvoid LogregCostLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    if (inpIdx == 1) {\n        LayerV& prev = _prev[replicaIdx];\n        NVMatrix& labels = *_inputs[0];\n        NVMatrix& probs = *_inputs[1];\n        NVMatrix& target = prev[1]->getActsGrad();\n        // Numerical stability optimization: if the layer below me is a softmax layer, let it handle\n        // the entire gradient computation to avoid multiplying and dividing by a near-zero quantity.\n        bool doWork = prev[1]->getNext().size() > 1 || prev[1]->getType() != \"softmax\"\n                    || prev[1]->getDeviceID() != getDeviceID() || prev[1]->getNumReplicas() != getNumReplicas();\n        if (prev[1]->getType() == \"softmax\") {\n            static_cast<SoftmaxLayer*>(prev[1])->setDoUpperGrad(!doWork);\n        }\n        if (doWork) {\n            computeLogregGrad(labels, probs, target, scaleTargets == 1, _coeff);\n        }\n    }\n}\n\n/*\n * =====================\n * SumOfSquaresCostLayer\n * =====================\n */\nSumOfSquaresCostLayer::SumOfSquaresCostLayer(ConvNetThread* convNetThread, PyObject* paramsDict, int replicaID) : CostLayer(convNetThread, paramsDict, replicaID, false) {\n}\n\nvoid SumOfSquaresCostLayer::fpropActs(int inpIdx, float scaleTargets, PASS_TYPE passType, int passIdx) {\n    _inputs[0]->apply(NVMatrixOps::Square(), _tmp);\n    _costv.clear();\n    _costv.push_back(_tmp.sum());\n}\n\nvoid SumOfSquaresCostLayer::bpropActs(NVMatrix& v, int replicaIdx, int inpIdx, float scaleTargets, PASS_TYPE passType) {\n    _prev[replicaIdx][inpIdx]->getActsGrad().add(*_inputs[0], scaleTargets, -2 * _coeff);\n}\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/layer_kernels.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <assert.h>\n#include <vector>\n#include <cmath>\n#include \"../include/layer_kernels.cuh\"\n\nusing namespace std;\n\n/*\n * E = -log(y_t)\n * probs:           (numOut, numCases)\n * labels:          (1, numCases)\n * maxEnergies:     (1, numCases)\n * labelLogProbs:   (1, numCases)   (*out)\n * correctProbs:    (1, numCases)   (*out)\n * top5Probs:       (1, numCases)   (*out)\n * \n * target:          (1, numCases)\n * \n */\n__global__ void kMultiSoftmaxCost(float* probs, float* labels, float* maxProbs,\n                                  float* labelLogProbs, float* correctProbs, float* top5Probs,\n                                  const int numCases, const int numOut, const int setSize) {\n    const int tx = blockIdx.x * LOGREG_ERR_THREADS_X + threadIdx.x;\n\n    if (tx < numCases) {\n        const int label = int(labels[tx]);\n        const float maxp = maxProbs[tx];\n        const float labelp = probs[label * numCases + tx];\n        \n        labelLogProbs[tx] = __logf(labelp);\n        \n        int numBiggerProbs = 0, numEqualsProbs = 0;\n        for (int i = 0; i < numOut; ++i) {\n            numBiggerProbs += probs[i * numCases + tx] > labelp;\n            numEqualsProbs += probs[i * numCases + tx] == labelp;\n        }\n\n        const int slotsLeft = setSize - numBiggerProbs;\n        \n        top5Probs[tx] = slotsLeft <= 0.0f ? 0.0f : (numEqualsProbs <= slotsLeft ? 1.0f : float(slotsLeft) / numEqualsProbs);\n        correctProbs[tx] = labelp != maxp ? 0.0f : 1.0f / float(numEqualsProbs);\n    }\n}\n\n/*\n * E = -log(y_t)\n * probs:           (numOut, numCases)\n * labels:          (1, numCases)\n * maxProbs:        (1, numCases)\n * labelLogProbs:   (1, numCases)   (*out)\n * correctProbs:    (1, numCases)   (*out)\n * top5Probs:       (1, numCases)   (*out)\n * \n * target:          (1, numCases) == log(y_l[labels,:]\n */\nvoid computeMultiSoftmaxCost(NVMatrix& labels, NVMatrix& probs, NVMatrix& maxProbs, NVMatrix& labelLogProbs_out,\n                       NVMatrix& correctProbs_out, NVMatrix& top5Probs_out, int setSize) {\n    int numCases = probs.getNumCols(); \n    int numOut = probs.getNumRows(); \n\n    assert(labels.getNumElements() == numCases);\n    assert(!labels.isTrans());\n    assert(!probs.isTrans());\n    assert(labels.isContiguous());\n    assert(probs.isContiguous());\n    \n//    NVMatrix& maxProbs = probs.max(0);\n    \n    labelLogProbs_out.resize(1, numCases);\n    correctProbs_out.resize(1, numCases);\n    top5Probs_out.resize(1, numCases);\n    dim3 threads(LOGREG_ERR_THREADS_X, 1);\n    dim3 blocks(DIVUP(numCases, LOGREG_ERR_THREADS_X), 1);\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n\n    cudaFuncSetCacheConfig(kMultiSoftmaxCost, cudaFuncCachePreferL1);\n    kMultiSoftmaxCost<<<blocks, threads, 0, stream>>>(probs.getDevData(), labels.getDevData(), maxProbs.getDevData(),\n                                    labelLogProbs_out.getDevData(), correctProbs_out.getDevData(), top5Probs_out.getDevData(),\n                                    numCases, numOut, setSize);\n\n    getLastCudaError(\"kMultiSoftmaxCost: Kernel execution failed\");\n//    cudaThreadSynchronize();\n}\n\n/*\n * E = sum(p_l * log(y_l))\n * probs:           (numOut, numCases)\n * labels:          (numOut, numCases)\n * maxProbs:        (1, numCases)\n * labelLogProbs:   (1, numCases)   (*out)\n * correctProbs:    (1, numCases)   (*out)\n * \n * target:          (1, numCases)\n */\n__global__ void kCrossEntCost(float* probs, float* labels, float* maxProbs, float* labelLogProbs, float* correctProbs,\n                            const int numCases, const int numOut) {\n    const int tx = blockIdx.x * LOGREG_ERR_THREADS_X + threadIdx.x;\n\n    if (tx < numCases) {\n        probs += tx;\n        labels += tx;\n        maxProbs += tx;\n        labelLogProbs += tx;\n        correctProbs += tx;\n        \n        const float maxp = maxProbs[0];\n\n        /*\n         * Compute the probability of guessing the correct case if you take the most-probable label.\n         * \n         * This is done like this:\n         * \n         * - If the most probable label is not equal to the true label, then the probability is zero.\n         * - Otherwise, the probability is 1 / (number of labels whose probability is equal to the maximum).\n         * \n         * This is certainly overkill -- in practice, it's just about impossible for two labels to get assigned\n         * maximum probability. But it's a safety measure to prevent over-estimating your accuracy.\n         * Though it could never happen in reality. Well it could. But it wouldn't. Cool?\n         */\n        float crossEnt = 0.0f;\n        int numMax = 0;\n        bool correctLabel = false;\n        for (int i = 0; i < numOut; i++) {\n            const float label_prob = labels[i * numCases];\n            const float model_prob = probs[i * numCases];\n            numMax += model_prob == maxp;\n            crossEnt += label_prob * safelog(model_prob);\n            correctLabel |= model_prob == maxp && label_prob > 0.0f;\n        }\n        labelLogProbs[0] = crossEnt;\n        if (!correctLabel) {\n            correctProbs[0] = 0.0f;\n        } else {\n            correctProbs[0] = 1.0f / float(numMax);\n        }\n    }\n}\n\n/*\n * E = sum(p_l * log(y_l))\n * y_l:     (numOut, numCases)\n * labels:  (numOut, numCases)\n * \n * dE_dy_l: (numOut, numCases)\n */\ntemplate <bool add>\n__global__ void kCrossEntGrad(float* y_l, float* labels, float* dE_dy_l, const int numCases,\n                                 const int numOut, const float gradCoeff) {\n    const int tx = blockIdx.x * LOGREG_GRAD_THREADS_X + threadIdx.x;\n    const int ty = blockIdx.y * LOGREG_GRAD_THREADS_Y + threadIdx.y;\n    const int tidx = ty * numCases + tx;\n    \n    if (ty < numOut && tx < numCases) {\n        const float label_prob = labels[tidx];\n        const float model_prob = y_l[tidx];\n        const float v = gradCoeff * __fdividef(label_prob, model_prob);\n        if (add) {\n            dE_dy_l[tidx] += v;\n        } else {\n            dE_dy_l[tidx] = v;\n        }\n    }\n}\n\n/*\n * E = sum(p_l * log(y_l))\n * y_l:     (numOut, numCases)\n * labels:  (numOut, numCases)\n * \n * dE_dx_l: (numOut, numCases)\n */\ntemplate <bool add>\n__global__ void kCrossEntSoftmaxGrad(float* y_l, float* labels, float* dE_dx_l, const int numCases,\n                                 const int numOut, const float gradCoeff) {\n    const int tx = blockIdx.x * LOGREG_GRAD_THREADS_X + threadIdx.x;\n    const int ty = blockIdx.y * LOGREG_GRAD_THREADS_Y + threadIdx.y;\n    const int tidx = ty * numCases + tx;\n    \n    if (ty < numOut && tx < numCases) {\n        const float model_prob = y_l[tidx];\n        const float label_prob = labels[tidx];\n        float v = gradCoeff * (label_prob - model_prob);\n        if (add) {\n            dE_dx_l[tidx] += v;\n        } else {\n            dE_dx_l[tidx] = v;\n        }\n    }\n}\n\n/*\n * E = -log(y_t)\n * probs:           (numOut, numCases)\n * labels:          (1, numCases)\n * maxProbs:        (1, numCases)\n * labelLogProbs:   (1, numCases)   (*out)\n * correctProbs:    (1, numCases)   (*out)\n * \n * target:          (1, numCases)\n */\n__global__ void kLogregCost(float* probs, float* labels, float* maxProbs, float* labelLogProbs, float* correctProbs,\n                            const int numCases, const int numOut) {\n    const int tx = blockIdx.x * LOGREG_ERR_THREADS_X + threadIdx.x;\n\n    if (tx < numCases) {\n        const int label = int(labels[tx]);\n        const float maxp = maxProbs[tx];\n        const float labelp = probs[label * numCases + tx];\n        \n        labelLogProbs[tx] = __logf(labelp);\n        \n        /*\n         * Compute the probability of guessing the correct case if you take the most-probable label.\n         * \n         * This is done like this:\n         * \n         * - If the most probable label is not equal to the true label, then the probability is zero.\n         * - Otherwise, the probability is 1 / (number of labels whose probability is equal to the maximum).\n         * \n         * This is certainly overkill -- in practice, it's just about impossible for two labels to get assigned\n         * maximum probability. But it's a safety measure to prevent over-estimating your accuracy.\n         * Though it could never happen in reality. Well it could. But it wouldn't. Cool?\n         */\n        if (labelp != maxp) {\n            correctProbs[tx] = 0;\n        } else {\n            int numMax = 0;\n            for (int i = 0; i < numOut; i++) {\n                numMax += probs[i * numCases + tx] == maxp;\n            }\n            correctProbs[tx] = 1.0f / float(numMax);\n        }\n    }\n}\n\n/*\n * E = -log(y_t)\n * y_l:     (numOut, numCases)\n * labels:  (1, numCases)\n * \n * dE_dy_l: (numOut, numCases)\n */\ntemplate <bool add>\n__global__ void kLogregCostGrad(float* y_l, float* labels, float* dE_dy_l, const int numCases,\n                                 const int numOut, const float gradCoeff) {\n    const int tx = blockIdx.x * LOGREG_GRAD_THREADS_X + threadIdx.x;\n    const int ty = blockIdx.y * LOGREG_GRAD_THREADS_Y + threadIdx.y;\n    const int tidx = ty * numCases + tx;\n    \n    if (ty < numOut && tx < numCases) {\n        const int label = int(labels[tx]);\n        float v = gradCoeff * (label == ty);\n        v = __fdividef(v, y_l[tidx]);\n        if (add) {\n            dE_dy_l[tidx] += v;\n        } else {\n            dE_dy_l[tidx] = v;\n        }\n    }\n}\n\n/*\n * E = -log(y_t)\n * y_l:     (numOut, numCases)\n * labels:  (1, numCases)\n * \n * dE_dx_l: (numOut, numCases)\n */\ntemplate <bool add>\n__global__ void kLogregSoftmaxGrad(float* y_l, float* labels, float* dE_dx_l, const int numCases,\n                                 const int numOut, const float gradCoeff) {\n    const int tx = blockIdx.x * LOGREG_GRAD_THREADS_X + threadIdx.x;\n    const int ty = blockIdx.y * LOGREG_GRAD_THREADS_Y + threadIdx.y;\n    const int tidx = ty * numCases + tx;\n    \n    if (ty < numOut && tx < numCases) {\n        const int label = int(labels[tx]);\n        float v = gradCoeff * ((label == ty) - y_l[tidx]);\n        if (add) {\n            dE_dx_l[tidx] += v;\n        } else {\n            dE_dx_l[tidx] = v;\n        }\n    }\n}\n\n/*\n * dE_dy_l: (numOut, numCases)\n * y_l:     (numOut, numCases)\n * \n * dE_dx_l: (numOut, numCases)\n */\ntemplate <bool add>\n__global__ void kSoftmaxGrad(float* dE_dy_l, float* y_l, float* dE_dx_l, const int numCases, const int numOut, const float scaleTarget, const float scaleGrad) {\n    const int tx = blockIdx.x * LOGREG_GRAD_THREADS_X + threadIdx.x;\n    const int ty = blockIdx.y * LOGREG_GRAD_THREADS_Y + threadIdx.y;\n    const int tidx = ty * numCases + tx;\n    \n    if (ty < numOut && tx < numCases) {\n        float v = 0;\n        for (int j = 0; j < numOut; j++) {\n            v += dE_dy_l[j * numCases + tx] * ((j == ty) - y_l[j * numCases + tx]);\n        }\n        v *= y_l[tidx];\n        \n        if (add) {\n            dE_dx_l[tidx] = scaleTarget * dE_dx_l[tidx] + scaleGrad * v;\n        } else {\n            dE_dx_l[tidx] = scaleGrad * v;\n        }\n    }\n}\n\ntemplate <int B_X, bool add>\n__global__ void kEltwiseMaxGrad(float* actGrad, float* input, float* output, float* target,\n                                const int numElements) {\n    for (int i = B_X * blockIdx.x + threadIdx.x; i < numElements; i += B_X * gridDim.x) {\n        if (add) {\n            target[i] += actGrad[i] * (output[i] == input[i]);\n        } else {\n            target[i] = actGrad[i] * (output[i] == input[i]);\n        }\n    }\n}\n\nvoid computeEltwiseMaxGrad(NVMatrix& actGrad, NVMatrix& input, NVMatrix& output, NVMatrix& target, bool add) {\n    assert(actGrad.isContiguous());\n    assert(output.isContiguous());\n    assert(input.isContiguous());\n    assert(actGrad.isSameDims(input));\n    assert(actGrad.isSameDims(output));\n    \n    dim3 blocks(DIVUP(actGrad.getNumElements(), 128));\n    dim3 threads(128);\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (add) {\n        assert(actGrad.isSameDims(target));\n        cudaFuncSetCacheConfig(kEltwiseMaxGrad<128, true>, cudaFuncCachePreferL1);\n        kEltwiseMaxGrad<128, true><<<blocks, threads, 0, stream>>>(actGrad.getDevData(), input.getDevData(), output.getDevData(), target.getDevData(), actGrad.getNumElements());\n    } else {\n        target.resize(actGrad);\n        cudaFuncSetCacheConfig(kEltwiseMaxGrad<128, false>, cudaFuncCachePreferL1);\n        kEltwiseMaxGrad<128, false><<<blocks, threads, 0, stream>>>(actGrad.getDevData(), input.getDevData(), output.getDevData(), target.getDevData(), actGrad.getNumElements());\n    }\n    \n    getLastCudaError(\"computeEltwiseMaxGrad: Kernel execution failed\");\n}\n\n/*\n * E = sum_i{-p_i*log(y_i)}\n * probs:           (numOut, numCases)\n * labels:          (numOut, numCases)\n * maxProbs:        (1, numCases)\n * labelLogProbs:   (1, numCases)   (*out)\n * correctProbs:    (1, numCases)   (*out)\n * \n * target:          (1, numCases)\n */\nvoid computeCrossEntCost(NVMatrix& labels, NVMatrix& probs, NVMatrix& labelLogProbs_out, NVMatrix& correctProbs_out) {\n    int numCases = probs.getNumCols(); \n    int numOut = probs.getNumRows(); \n\n    assert(labels.isSameDims(probs));\n    assert(!labels.isTrans());\n    assert(!probs.isTrans());\n    assert(labels.isContiguous());\n    assert(probs.isContiguous());\n    \n    NVMatrix& maxProbs = probs.max(0);\n    \n    labelLogProbs_out.resize(1, numCases);\n    correctProbs_out.resize(1, numCases);\n    dim3 threads(LOGREG_ERR_THREADS_X, 1);\n    dim3 blocks(DIVUP(numCases, LOGREG_ERR_THREADS_X), 1);\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    cudaFuncSetCacheConfig(kCrossEntCost, cudaFuncCachePreferL1);\n    kCrossEntCost<<<blocks, threads, 0, stream>>>(probs.getDevData(), labels.getDevData(), maxProbs.getDevData(),\n                                     labelLogProbs_out.getDevData(), correctProbs_out.getDevData(),\n                                     numCases, numOut);\n    getLastCudaError(\"kCrossEntCost: Kernel execution failed\");\n\n    delete &maxProbs;\n}\n\nvoid computeCrossEntGrad(NVMatrix& labels, NVMatrix& probs, NVMatrix& target, bool add, float coeff) {\n    int numCases = probs.getLeadingDim(); \n    int numOut = probs.getFollowingDim(); \n    assert(labels.isSameDims(probs));\n    assert(probs.isContiguous());\n    assert(target.isContiguous());\n    assert(labels.isContiguous());\n    assert(!labels.isTrans());\n    assert(!probs.isTrans());\n    \n    dim3 threads(LOGREG_GRAD_THREADS_X, LOGREG_GRAD_THREADS_Y);\n    dim3 blocks(DIVUP(numCases, LOGREG_GRAD_THREADS_X), DIVUP(numOut, LOGREG_GRAD_THREADS_Y));\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (!add) {\n        target.resize(probs);\n        kCrossEntGrad<false><<<blocks, threads, 0, stream>>>(probs.getDevData(), labels.getDevData(), target.getDevData(),\n                                                     numCases, numOut, coeff);\n    } else {\n        kCrossEntGrad<true><<<blocks, threads, 0, stream>>>(probs.getDevData(), labels.getDevData(), target.getDevData(),\n                                                     numCases, numOut, coeff);\n    }\n\n    getLastCudaError(\"kCrossEntGrad: Kernel execution failed\");\n}\n\nvoid computeSoftmaxGrad(NVMatrix& acts, NVMatrix& actsGrad, NVMatrix& target, float scaleTarget, float scaleGrad) {\n    int numCases = acts.getLeadingDim();\n    int numOut = acts.getFollowingDim();\n\n    assert(acts.isSameDims(actsGrad));\n    assert(acts.isContiguous());\n    assert(actsGrad.isContiguous());\n    assert(target.isContiguous());\n    assert(acts.isTrans());\n    assert(actsGrad.isTrans());\n\n    dim3 threads(LOGREG_GRAD_THREADS_X, LOGREG_GRAD_THREADS_Y);\n    dim3 blocks(DIVUP(numCases, LOGREG_GRAD_THREADS_X), DIVUP(numOut, LOGREG_GRAD_THREADS_Y));\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n\n    if (scaleTarget == 0) {\n        target.resize(acts);\n        kSoftmaxGrad<false><<<blocks, threads, 0, stream>>>(actsGrad.getDevData(), acts.getDevData(), target.getDevData(), numCases, numOut, scaleTarget, scaleGrad);\n    } else {\n        kSoftmaxGrad<true><<<blocks, threads, 0, stream>>>(actsGrad.getDevData(), acts.getDevData(), target.getDevData(), numCases, numOut, scaleTarget, scaleGrad);\n    }\n    getLastCudaError(\"computeSoftmaxGrad: Kernel execution failed\");\n}\n\nvoid computeCrossEntSoftmaxGrad(NVMatrix& labels, NVMatrix& probs, NVMatrix& target, bool add, float coeff) {\n    int numCases = probs.getLeadingDim(); \n    int numOut = probs.getFollowingDim(); \n    assert(labels.getLeadingDim() == probs.getLeadingDim() && labels.getFollowingDim() == probs.getFollowingDim());\n    assert(probs.isContiguous());\n    assert(target.isContiguous());\n    assert(labels.isContiguous());\n    assert(probs.isTrans());\n    assert(!labels.isTrans());\n    \n    dim3 threads(LOGREG_GRAD_THREADS_X, LOGREG_GRAD_THREADS_Y);\n    dim3 blocks(DIVUP(numCases, LOGREG_GRAD_THREADS_X), DIVUP(numOut, LOGREG_GRAD_THREADS_Y));\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (!add) {\n        target.resize(probs);\n        cudaFuncSetCacheConfig(kCrossEntSoftmaxGrad<false>, cudaFuncCachePreferL1);\n        kCrossEntSoftmaxGrad<false><<<blocks, threads, 0, stream>>>(probs.getDevData(), labels.getDevData(), target.getDevData(),\n                                                        numCases, numOut, coeff);\n    } else {\n        cudaFuncSetCacheConfig(kCrossEntSoftmaxGrad<true>, cudaFuncCachePreferL1);\n        kCrossEntSoftmaxGrad<true><<<blocks, threads, 0, stream>>>(probs.getDevData(), labels.getDevData(), target.getDevData(),\n                                                        numCases, numOut, coeff);\n    }\n    getLastCudaError(\"kCrossEntSoftmaxGrad: Kernel execution failed\");\n}\n\n/*\n * E = -log(y_t)\n * probs:           (numOut, numCases)\n * labels:          (1, numCases)\n * maxProbs:        (1, numCases)\n * labelLogProbs:   (1, numCases)   (*out)\n * correctProbs:    (1, numCases)   (*out)\n * \n * target:          (1, numCases) == log(y_l[labels,:]\n */\nvoid computeLogregCost(NVMatrix& labels, NVMatrix& probs, NVMatrix& maxProbs, NVMatrix& labelLogProbs_out, NVMatrix& correctProbs_out) {\n    int numCases = probs.getNumCols(); \n    int numOut = probs.getNumRows(); \n\n    assert(labels.getNumElements() == numCases);\n    assert(!labels.isTrans());\n    assert(!probs.isTrans());\n    assert(labels.isContiguous());\n    assert(probs.isContiguous());\n\n    labelLogProbs_out.resize(1, numCases);\n    correctProbs_out.resize(1, numCases);\n    dim3 threads(LOGREG_ERR_THREADS_X, 1);\n    dim3 blocks(DIVUP(numCases, LOGREG_ERR_THREADS_X), 1);\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    cudaFuncSetCacheConfig(kLogregCost, cudaFuncCachePreferL1);\n    kLogregCost<<<blocks, threads, 0, stream>>>(probs.getDevData(), labels.getDevData(), maxProbs.getDevData(),\n                                     labelLogProbs_out.getDevData(), correctProbs_out.getDevData(),\n                                     numCases, numOut);\n    getLastCudaError(\"computeLogregCost: Kernel execution failed\");\n}\n\nvoid computeLogregGrad(NVMatrix& labels, NVMatrix& probs, NVMatrix& target, bool add, float coeff) {\n    int numCases = probs.getLeadingDim(); \n    int numOut = probs.getFollowingDim(); \n    assert(labels.getNumElements() == numCases);\n    assert(probs.isContiguous());\n    assert(target.isContiguous());\n    assert(labels.isContiguous());\n    assert(!labels.isTrans());\n    assert(!probs.isTrans());\n    \n    dim3 threads(LOGREG_GRAD_THREADS_X, LOGREG_GRAD_THREADS_Y);\n    dim3 blocks(DIVUP(numCases, LOGREG_GRAD_THREADS_X), DIVUP(numOut, LOGREG_GRAD_THREADS_Y));\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (!add) {\n        target.resize(probs);\n        kLogregCostGrad<false><<<blocks, threads, 0, stream>>>(probs.getDevData(), labels.getDevData(), target.getDevData(),\n                                                     numCases, numOut, coeff);\n    } else {\n        kLogregCostGrad<true><<<blocks, threads, 0, stream>>>(probs.getDevData(), labels.getDevData(), target.getDevData(),\n                                                     numCases, numOut, coeff);\n    }\n\n    getLastCudaError(\"computeLogregGrad: Kernel execution failed\");\n}\n\nvoid computeLogregSoftmaxGrad(NVMatrix& labels, NVMatrix& probs, NVMatrix& target, bool add, float coeff) {\n    int numCases = probs.getLeadingDim(); \n    int numOut = probs.getFollowingDim(); \n    assert(labels.getNumElements() == numCases);\n    assert(probs.isContiguous());\n    assert(target.isContiguous());\n    assert(labels.isContiguous());\n    assert(probs.isTrans());\n    \n    dim3 threads(LOGREG_GRAD_THREADS_X, LOGREG_GRAD_THREADS_Y);\n    dim3 blocks(DIVUP(numCases, LOGREG_GRAD_THREADS_X), DIVUP(numOut, LOGREG_GRAD_THREADS_Y));\n    cudaStream_t stream = NVMatrix::getDefaultStream();\n    if (!add) {\n        target.resize(probs);\n        kLogregSoftmaxGrad<false><<<blocks, threads, 0, stream>>>(probs.getDevData(), labels.getDevData(), target.getDevData(),\n                                                     numCases, numOut, coeff);\n    } else {\n        kLogregSoftmaxGrad<true><<<blocks, threads, 0, stream>>>(probs.getDevData(), labels.getDevData(), target.getDevData(),\n                                                     numCases, numOut, coeff);\n    }\n\n    getLastCudaError(\"computeLogregSoftmaxGrad: Kernel execution failed\");\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/lr.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <string>\n#include \"../include/lr.cuh\"\n#include \"../include/util.cuh\"\n\n/*\n * ==================================\n * ParameterSchedule\n * ==================================\n */\nParameterSchedule& ParameterSchedule::make(PyObject* schedDict) {\n    std::string type = pyDictGetString(schedDict, \"type\");\n    PyObject* paramsDict = PyDict_GetItemString(schedDict, \"params\");\n    double base = pyDictGetFloat(paramsDict, \"base\");\n    if (type == \"const\") {\n        return *new ParameterSchedule(base);\n    } else {\n        double tgtFactor = pyDictGetFloat(paramsDict, \"tgtFactor\");\n        if (type == \"linear\") {\n            return *new LinearParameterSchedule(base, tgtFactor);\n        } else if (type == \"exp\") {\n            return *new ExpParameterSchedule(base, tgtFactor);\n        } else if (type == \"dexp\") {\n            double numSteps = pyDictGetInt(paramsDict, \"numSteps\");\n            return *new DiscreteExpParameterSchedule(base, tgtFactor, numSteps);\n        }\n    }\n    throw std::string(\"Unknown learning rate schedule type \") + type;\n}\n\nParameterSchedule::ParameterSchedule(double baseRate)\n    : _baseRate(baseRate) {\n}\n\ndouble ParameterSchedule::getValue(double progress) {\n    return _baseRate;\n}\n\ndouble ParameterSchedule::getBaseValue() const {\n    return _baseRate;\n}\n\nParameterSchedule::~ParameterSchedule() {\n}\n\n/*\n * ==================================\n * LinearParameterSchedule\n * ==================================\n */\nLinearParameterSchedule::LinearParameterSchedule(double baseRate, double tgtFactor)\n: ParameterSchedule(baseRate) {\n    _finalRate = baseRate / tgtFactor;\n}\n\ndouble LinearParameterSchedule::getValue(double progress) {\n    return _baseRate * (1 - progress) + _finalRate * progress;\n}\n\n/*\n * ==================================\n * ExpParameterSchedule\n * ==================================\n */\nExpParameterSchedule::ExpParameterSchedule(double baseRate, double tgtFactor)\n: ParameterSchedule(baseRate) {\n    _powBase = 1.0 / tgtFactor;\n}\n\ndouble ExpParameterSchedule::getValue(double progress) {\n    return _baseRate * std::pow(_powBase, progress);\n}\n\n/*\n * ==================================\n * DiscreteExpParameterSchedule\n * ==================================\n */\nDiscreteExpParameterSchedule::DiscreteExpParameterSchedule(double baseRate, double tgtFactor, int numSteps)\n: ParameterSchedule(baseRate) {\n    ExpParameterSchedule elrs(baseRate, tgtFactor);\n    double finalRate = baseRate / tgtFactor;\n    for (int i = 0; i < numSteps - 1; i++) {\n        double progress = double(i) / (numSteps - 1);\n        _rates.push_back(elrs.getValue(progress));\n    }\n    _rates.push_back(finalRate);\n    //printf(\"initialized base %e, final %e, stpes %d\\n\", baseRate, finalRate, numSteps);\n}\n\ndouble DiscreteExpParameterSchedule::getValue(double progress) {\n    for (int i = 0; i < _rates.size(); ++i) {\n        if (progress <= double(i + 1) / _rates.size()) {\n            return _rates[i];\n        }\n    }\n    return _rates.back();\n}\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/memorysource.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"../include/memorysource.cuh\"\n\nusing namespace std;\n\n/*\n * =======================\n * MemoryView\n * =======================\n */\nMemoryView::MemoryView(MemorySource& src, std::string& name) : _src(&src), _name(name) {\n}\n\nMemoryView::~MemoryView() {\n//    if (_src->truncate(_name)) {\n//        delete _src;\n//    }\n}\n\nNVMatrix& MemoryView::getMemory(int numCases) {\n    return _src->getMemory(_name, numCases);\n}\n\nNVMatrix& MemoryView::getMemory() {\n    return _src->getMemory(_name);\n}\n\nMemorySource& MemoryView::getMemorySource() {\n    return *_src;\n}\n\nbool MemoryView::isParent() {\n    return _src->getRange(_name).first == 0 && _src->getRange(_name).second == _src->getSize();\n}\n\nstd::string& MemoryView::getName() {\n    return _name;\n}\n\nMemoryView& MemoryView::clone(std::string& name) {\n    return _src->addUser(name, _src->getRange(_name));\n}\n\n/*\n * =======================\n * MemorySource\n * =======================\n */\nMemorySource::MemorySource(int size, int deviceID) : _size(size), _deviceID(deviceID) {\n}\n\nMemorySource::~MemorySource() {\n    // Each MemoryView is deleted by owner Layer, and the last one deletes the MemorySource.\n    // So this is a no-op.\n}\n\nNVMatrix& MemorySource::getMemory(std::string& name) {\n    return getMemory(name, _memory.getLeadingDim());\n}\n\n// Deletes old view when appropriate\nNVMatrix& MemorySource::getMemory(std::string& name, int numCases) {\n    numCases = numCases < 0 ? _memory.getLeadingDim() : numCases;\n    _lock.acquire();\n    if (_memory.getLeadingDim() != numCases || _memory.getFollowingDim() != _size) {\n        int d = NVMatrix::getDeviceID();\n        NVMatrix::setDeviceID(_deviceID);\n        _memory.resize(_size, numCases, false);\n        for (map<std::string,NVMatrix*>::const_iterator it = _memoryViews.begin(); it != _memoryViews.end(); ++it) {\n            delete it->second;\n        }\n        _memoryViews.clear();\n        if (d >= 0) {\n            NVMatrix::setDeviceID(d);\n        }\n    }\n    if (_memoryViews.count(name) == 0) {\n        assert(!_memory.isTrans());\n        _memoryViews[name] = &_memory.sliceRows(_viewRanges[name].first, _viewRanges[name].second);\n    }\n    NVMatrix& view = *_memoryViews[name];\n    assert(view.isContiguous());\n    _lock.release();\n    return view;\n}\n\nMemoryView& MemorySource::addUser(std::string& name, std::pair<int,int> range) {\n    assert(_viewRanges.count(name) == 0);\n    _viewRanges[name] = range;\n    return *new MemoryView(*this, name);\n}\n\nMemoryView& MemorySource::addUser(std::string& name) {\n    return addUser(name, std::pair<int,int>(0, _size));\n}\n\nMemoryView& MemorySource::make(int size, int deviceID, std::string& parentUser) {\n    return (new MemorySource(size, deviceID))->addUser(parentUser);\n}\n\npair<int,int> MemorySource::getRange(std::string& name) {\n    return _viewRanges[name];\n}\n\nint MemorySource::getSize() {\n    return _size;\n}\n\nbool MemorySource::truncate(std::string& name) {\n    bool truncated = false;\n    _lock.acquire();\n    _truncateRequests.insert(name);\n    if (_truncateRequests.size() == _viewRanges.size()) {\n        for (map<std::string,NVMatrix*>::const_iterator it = _memoryViews.begin(); it != _memoryViews.end(); ++it) {\n            delete it->second;\n        }\n        _memoryViews.clear();\n        _memory.truncate();\n        _truncateRequests.clear();\n        truncated = true;\n    }\n    _lock.release();\n    return truncated;\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/neuron.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"../include/neuron.cuh\"\n#include \"../include/util.cuh\"\n\nusing namespace std;\n\nNeuron& Neuron::makeNeuron(PyObject* neuronDict) {\n    std::string type = pyDictGetString(neuronDict, \"type\");\n    PyObject* neuronParamsDict = PyDict_GetItemString(neuronDict, \"params\");\n    \n    if (type == \"relu\") {\n        return *new ReluNeuron();\n    }\n    \n    if (type == \"drelu\") {\n        return *new DoubleReluNeuron(pyDictGetFloat(neuronParamsDict, \"a\"));\n    }\n    \n    if (type == \"softrelu\") {\n        return *new SoftReluNeuron();\n    }\n    \n    if (type == \"brelu\") {\n        return *new BoundedReluNeuron(pyDictGetFloat(neuronParamsDict, \"a\"));\n    }\n\n    if (type == \"abs\") {\n        return *new AbsNeuron();\n    }\n\n    if (type == \"logistic\") {\n        return *new LogisticNeuron();\n    }\n    \n    if (type == \"tanh\") {\n        return *new TanhNeuron(pyDictGetFloat(neuronParamsDict, \"a\"), pyDictGetFloat(neuronParamsDict, \"b\"));\n    }\n    \n    if (type == \"square\") {\n        return *new SquareNeuron();\n    }\n    \n    if (type == \"sqrt\") {\n        return *new SqrtNeuron();\n    }\n    \n    if (type == \"linear\") {\n        return *new LinearNeuron(pyDictGetFloat(neuronParamsDict, \"a\"), pyDictGetFloat(neuronParamsDict, \"b\"));\n    }\n\n    if (type == \"log\") {\n        return *new LogNeuron(pyDictGetFloat(neuronParamsDict, \"a\"));\n    }\n\n    if (type == \"ident\") {\n        return *new Neuron();\n    }\n    \n    throw std::string(\"Unknown neuron type: \") + type;\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/pyconvnet.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <Python.h>\n#include <arrayobject.h>\n#include <assert.h>\n#include <helper_cuda.h>\n#include <cublas.h>\n#include <time.h>\n#include <vector>\n#include <execinfo.h>\n#include <signal.h>\n\n#include \"../../util/include/matrix.h\"\n#include \"../../util/include/queue.h\"\n#include \"../include/worker.cuh\"\n#include \"../include/util.cuh\"\n#include \"../include/cost.cuh\"\n\n#include \"../include/pyconvnet.cuh\"\n#include \"../include/convnet.cuh\"\n\n#include \"../include/jpeg.h\"\n\nusing namespace std;\nstatic ConvNet* model = NULL;\n\nstatic PyMethodDef _ConvNetMethods[] = {{ \"initModel\",          initModel,              METH_VARARGS },\n                                        { \"startBatch\",         startBatch,             METH_VARARGS },\n                                        { \"finishBatch\",        finishBatch,            METH_VARARGS },\n                                        { \"checkGradients\",     checkGradients,         METH_VARARGS },\n                                        { \"startMultiviewTest\", startMultiviewTest,     METH_VARARGS },\n                                        { \"startFeatureWriter\", startFeatureWriter,     METH_VARARGS },\n                                        { \"startDataGrad\",      startDataGrad,          METH_VARARGS },\n                                        { \"syncWithHost\",       syncWithHost,           METH_VARARGS },\n                                        { \"decodeJpeg\",         decodeJpeg,             METH_VARARGS },\n                                        { NULL, NULL }\n};\n\nvoid init_ConvNet() {\n    (void) Py_InitModule(\"_ConvNet\", _ConvNetMethods);\n    import_array();\n}\n\nvoid signalHandler(int sig) {\n    const size_t max_trace_size = 40;\n    void *array[max_trace_size];\n    size_t trace_size = backtrace(array, max_trace_size);\n    fprintf(stderr, \"Error signal %d:\\n\", sig);\n    backtrace_symbols_fd(array, trace_size, STDERR_FILENO);\n    exit(1);\n}\n\nPyObject* initModel(PyObject *self, PyObject *args) {\n    assert(model == NULL);\n    signal(SIGSEGV, signalHandler);\n    signal(SIGABRT, signalHandler);\n\n    PyDictObject* pyLayerParams;\n    PyListObject* pyDeviceIDs;\n    int pyMinibatchSize;\n    int conserveMem;\n\n    if (!PyArg_ParseTuple(args, \"O!O!ii\",\n                          &PyDict_Type, &pyLayerParams,\n                          &PyList_Type, &pyDeviceIDs,\n                          &pyMinibatchSize,\n                          &conserveMem)) {\n        return NULL;\n    }\n    intv& deviceIDs = *getIntV((PyObject*)pyDeviceIDs);\n\n    model = new ConvNet((PyObject*)pyLayerParams,\n                        deviceIDs,\n                        pyMinibatchSize,\n                        conserveMem);\n\n    model->start();\n    return Py_BuildValue(\"i\", 0);\n}\n\n/*\n * Starts training/testing on the given batch (asynchronous -- returns immediately).\n */\nPyObject* startBatch(PyObject *self, PyObject *args) {\n    assert(model != NULL);\n//    printf(\"starting next batch\\n\");\n    PyListObject* data;\n    double progress;\n    int test = 0;\n    if (!PyArg_ParseTuple(args, \"O!d|i\",\n        &PyList_Type, &data,\n        &progress,\n        &test)) {\n        return NULL;\n    }\n    CPUData* cpuData = new CPUData((PyObject*)data);\n    \n    TrainingWorker* wr = new TrainingWorker(*model, *cpuData, progress, test);\n    model->getWorkerQueue().enqueue(wr);\n    return Py_BuildValue(\"i\", 0);\n}\n\n/*\n * Starts testing on the given batch (asynchronous -- returns immediately).\n */\nPyObject* startMultiviewTest(PyObject *self, PyObject *args) {\n    assert(model != NULL);\n    PyListObject* data;\n    int numViews;\n    PyArrayObject* pyProbs = NULL;\n    char* logregName = NULL;\n    if (!PyArg_ParseTuple(args, \"O!i|O!s\",\n        &PyList_Type, &data,\n        &numViews,\n        &PyArray_Type, &pyProbs,\n        &logregName)) {\n        return NULL;\n    }\n    CPUData* cpuData = new CPUData((PyObject*)data);\n    MultiviewTestWorker* wr = pyProbs == NULL ? new MultiviewTestWorker(*model, *cpuData, numViews)\n                                              : new MultiviewTestWorker(*model, *cpuData, numViews, *new Matrix(pyProbs), logregName);\n    model->getWorkerQueue().enqueue(wr);\n    return Py_BuildValue(\"i\", 0);\n}\n\nPyObject* startFeatureWriter(PyObject *self, PyObject *args) {\n    assert(model != NULL);\n    PyListObject* data;\n    PyListObject* pyFtrs;\n    PyListObject* pyLayerNames;\n    if (!PyArg_ParseTuple(args, \"O!O!O!\",\n        &PyList_Type, &data,\n        &PyList_Type, &pyFtrs,\n        &PyList_Type, &pyLayerNames)) {\n        return NULL;\n    }\n    stringv* layerNames = getStringV((PyObject*)pyLayerNames);\n    CPUData* cpuData = new CPUData((PyObject*)data);\n    MatrixV* ftrs = getMatrixV((PyObject*)pyFtrs);\n    \n    FeatureWorker* wr = new FeatureWorker(*model, *cpuData, *ftrs, *layerNames);\n    model->getWorkerQueue().enqueue(wr);\n    return Py_BuildValue(\"i\", 0);\n}\n\nPyObject* startDataGrad(PyObject *self, PyObject *args) {\n//    assert(model != NULL);\n//    PyListObject* data;\n//    int dataLayerIdx, softmaxLayerIdx;\n//    if (!PyArg_ParseTuple(args, \"O!ii\",\n//        &PyList_Type, &data,\n//        &dataLayerIdx, &softmaxLayerIdx)) {\n//        return NULL;\n//    }\n//    CPUData* cpuData = new CPUData((PyObject*)data);\n//    Matrix& ftrs = *mvec.back();\n//    mvec.pop_back();\n//    \n//    DataGradWorker* wr = new DataGradWorker(*model, *cpuData, ftrs, dataLayerIdx, softmaxLayerIdx);\n//    model->getWorkerQueue().enqueue(wr);\n    return Py_BuildValue(\"i\", 0);\n}\n\n/*\n * Waits for the trainer to finish training on the batch given to startBatch.\n * This is a blocking call so lets release the GIL.\n */\nPyObject* finishBatch(PyObject *self, PyObject *args) {\n    assert(model != NULL);\n    WorkResult* res = model->getResultQueue().dequeue();\n    assert(res != NULL);\n    assert(res->getResultType() == WorkResult::BATCH_DONE);\n    \n    Cost& cost = res->getResults();\n    PyObject* dict = PyDict_New();\n    CostMap& costMap = cost.getCostMap();\n    for (CostMap::const_iterator it = costMap.begin(); it != costMap.end(); ++it) {\n        PyObject* v = PyList_New(0);\n        for (vector<double>::const_iterator iv = it->second->begin(); iv != it->second->end(); ++iv) {\n            PyObject* f = PyFloat_FromDouble(*iv);\n            PyList_Append(v, f);\n        }\n        PyDict_SetItemString(dict, it->first.c_str(), v);\n    }\n    PyObject* retVal = Py_BuildValue(\"Ni\", dict, cost.getNumCases());\n    delete res; // Deletes cost too\n    \n    return retVal;\n}\n\nPyObject* checkGradients(PyObject *self, PyObject *args) {\n    assert(model != NULL);\n    PyListObject* data;\n    if (!PyArg_ParseTuple(args, \"O!\",\n        &PyList_Type, &data)) {\n        return NULL;\n    }\n    CPUData* cpuData = new CPUData((PyObject*)data);\n    \n    GradCheckWorker* wr = new GradCheckWorker(*model, *cpuData);\n    model->getWorkerQueue().enqueue(wr);\n    WorkResult* res = model->getResultQueue().dequeue();\n    assert(res != NULL);\n    assert(res->getResultType() == WorkResult::BATCH_DONE);\n    delete res;\n    return Py_BuildValue(\"i\", 0);\n}\n\n/*\n * Copies weight matrices from GPU to system memory.\n */\nPyObject* syncWithHost(PyObject *self, PyObject *args) {\n    assert(model != NULL);\n    SyncWorker* wr = new SyncWorker(*model);\n    model->getWorkerQueue().enqueue(wr);\n    WorkResult* res = model->getResultQueue().dequeue();\n    assert(res != NULL);\n    assert(res->getResultType() == WorkResult::SYNC_DONE);\n    \n    delete res;\n    return Py_BuildValue(\"i\", 0);\n}\n\nPyObject* decodeJpeg(PyObject *self, PyObject *args) {\n    PyListObject* pyJpegStrings;\n    PyArrayObject* pyTarget;\n    int img_size, inner_size, test, multiview;\n    if (!PyArg_ParseTuple(args, \"O!O!iiii\",\n        &PyList_Type, &pyJpegStrings,\n        &PyArray_Type, &pyTarget,\n        &img_size,\n        &inner_size,\n        &test,\n        &multiview)) {\n        return NULL;\n    }\n\n    Thread* threads[NUM_JPEG_DECODER_THREADS];\n    int num_imgs = PyList_GET_SIZE(pyJpegStrings);\n    int num_imgs_per_thread = DIVUP(num_imgs, NUM_JPEG_DECODER_THREADS);\n    Matrix& dstMatrix = *new Matrix(pyTarget);\n    for (int t = 0; t < NUM_JPEG_DECODER_THREADS; ++t) {\n        int start_img = t * num_imgs_per_thread;\n        int end_img = min(num_imgs, (t+1) * num_imgs_per_thread);\n\n        threads[t] = new DecoderThread((PyObject*)pyJpegStrings, dstMatrix, start_img, end_img, img_size, inner_size, test, multiview);\n        threads[t]->start();\n    }\n\n    for (int t = 0; t < NUM_JPEG_DECODER_THREADS; ++t) {\n        threads[t]->join();\n        delete threads[t];\n    }\n    assert(dstMatrix.isView());\n    delete &dstMatrix;\n    return Py_BuildValue(\"i\", 0);\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/reducepipeline.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <vector>\n#include <map>\n#include \"../include/reducepipeline.cuh\"\n\nusing namespace std;\n\n/* =========================\n * IReducerSegment\n * =========================\n */\n// Null mat --> reducer on host\nIReduceSegment::IReduceSegment(IEightGPUReducer& parent, int deviceID, Queue<int>* finishQueue)\n: _deviceID(deviceID), _next(NULL), _finishQueue(finishQueue), Thread(true, getDeviceCPUs(parent.getTgtDeviceID())) {\n}\n\nIReduceSegment::~IReduceSegment() {\n}\n\nNVMatrix& IReduceSegment::getChunk(const NVMatrix& mat, int chunkSize, int chunkIdx) {\n        NVMatrix& line = mat.reshaped(1, mat.getNumElements());\n    int start = chunkIdx * chunkSize;\n    int end = min((chunkIdx+1) * chunkSize, mat.getNumElements());\n//        _mat->printShape(\"_mat\");\n    NVMatrix& chunk = line.sliceCols(start, end);\n    delete &line;\n//        chunk.printShape(\"chunk\");\n    return chunk;\n}\n\nvoid* IReduceSegment::run() {\n    bool exit = false;\n    while (!exit) {\n        ReduceMessage& msg = *_queue.dequeue();\n        if (msg.getType() == EXIT) {\n            exit = true;\n        } else {\n            bool term = processMessage(msg);\n            if (term) {\n                assert(_finishQueue);\n                _finishQueue->enqueue(1);\n            }\n        }\n        delete &msg;\n    }\n    return NULL;\n}\n\ninline NVMatrix& IReduceSegment::getMatrix(ReduceMessage& msg) {\n    return msg.getMatrix(getDeviceID());\n}\n\nQueue<ReduceMessage*>& IReduceSegment::getQueue() {\n    return _queue;\n}\n\ninline int IReduceSegment::getDeviceID() const {\n    return _deviceID;\n}\n\nvoid IReduceSegment::addPrev(IReduceSegment& c) {\n    _prev.push_back(&c);\n}\n\nvoid IReduceSegment::addNext(ReducePeer& c) {\n    assert(_next == NULL);\n    _next = &c;\n    c.addPrev(*this);\n}\n\nbool IReduceSegment::isTerminal() const {\n    return _next == NULL;\n}\n\n/* =========================\n * ReducerSource\n * =========================\n */\nReducerSource::ReducerSource(IEightGPUReducer& parent, int deviceID) : IReduceSegment(parent, deviceID, NULL) {\n}\n\nbool ReducerSource::processMessage(ReduceMessage& msg) {\n    assert(msg.getType() == REDUCE_START);\n    int numChunks = min(getMatrix(msg).getNumElements(), max(REDUCE_MIN_CHUNKS, min(REDUCE_MAX_CHUNKS, DIVUP(getMatrix(msg).getNumElements(), REDUCE_MIN_CHUNK_SIZE))));\n    int chunkSize = DIVUP(getMatrix(msg).getNumElements(), numChunks);\n    //printf(\"num chunks: %d\\n\", numChunks);\n    for (int c = 0; c <= numChunks; ++c) {\n        _next->getQueue().enqueue(new ReduceChunkMessage(*this, c, chunkSize, numChunks, msg.getScaleIntermediates(), msg.getScaleTarget(), msg.getMatrices()));\n    }\n    return false;\n}\n\n/* =========================\n * ReducerPeer\n * =========================\n */\nReducePeer::ReducePeer(IEightGPUReducer& parent,int deviceID, Queue<int>* finishQueue) : IReduceSegment(parent, deviceID,  finishQueue), _numInputsFinished(0) {\n    _add = deviceID != DEVICE_HOST;\n}\n\nReducePeer::ReducePeer(IEightGPUReducer& parent) : IReduceSegment(parent, DEVICE_HOST, NULL), _numInputsFinished(0), _add(false) {\n}\n\nReducePeer::~ReducePeer() {\n    for(std::map<int,cudaStream_t>::iterator it = _streams.begin(); it != _streams.end(); ++it) {\n        checkCudaErrors(cudaStreamDestroy(it->second));\n    }\n    _streams.clear();\n}\n\ninline cudaStream_t ReducePeer::getStream(int deviceID) {\n    if (deviceID < 0) {\n        return NULL;\n    }\n    if (_streams.count(deviceID) == 0) {\n        NVMatrix::setDeviceID(deviceID);\n        checkCudaErrors(cudaStreamCreateWithFlags(&_streams[deviceID], cudaStreamNonBlocking));\n    }\n    return _streams[deviceID];\n}\n\nbool ReducePeer::processMessage(ReduceMessage& msg) {\n    assert(msg.getType() == REDUCE_CHUNK);\n\n    ReduceChunkMessage& cmsg = *static_cast<ReduceChunkMessage*>(&msg);\n//    if (_numInputsReceived.count(cmsg.getChunkIdx()) == 0) {\n//        _numInputsReceived[cmsg.getChunkIdx()] = 0;\n//    }\n    int& inputsRcvd = ++_numInputsReceived[cmsg.getChunkIdx()];\n//    printf(\"reducer on device %d got msg chunk idx %d of %d, inputs rcvd for this chunk idx: %d/%d\\n\",\n//            getDeviceID(), cmsg.getChunkIdx(), cmsg.getNumChunks(),_numInputsReceived[cmsg.getChunkIdx()], _prev.size());\n    if (cmsg.getChunkIdx() < cmsg.getNumChunks()) {\n        IReduceSegment& src = cmsg.getSource();\n        float scalePrev = isTerminal() ? cmsg.getScaleIntermediates() : 1;\n        float scaleSelf = inputsRcvd == 1 ? _add * (isTerminal() ? cmsg.getScaleTarget() : 1): 1;\n        if (scaleSelf == 0 || isTerminal()) {\n            if (getDeviceID() >= 0) {\n                NVMatrix::setDeviceID(getDeviceID());\n            }\n            getMatrix(msg).resize(src.getMatrix(msg));\n        }\n        assert(getMatrix(msg).isSameDims(src.getMatrix(msg)));\n        NVMatrix& prevChunk = getChunk(src.getMatrix(msg), cmsg.getChunkSize(), cmsg.getChunkIdx());\n        NVMatrix& myChunk = getChunk(getMatrix(msg), cmsg.getChunkSize(), cmsg.getChunkIdx());\n        int execDeviceID = getDeviceID() >= 0 ? getDeviceID() : src.getDeviceID();\n        if (execDeviceID >= 0) {\n            NVMatrix::setDeviceID(execDeviceID);\n            prevChunk.add(myChunk, scalePrev, scaleSelf, myChunk, getStream(execDeviceID));\n            NVMatrix::syncStream(getStream(execDeviceID));\n        } else {\n            assert(!isTerminal());\n            hostAdd(prevChunk.getDevData(), myChunk.getDevData(), prevChunk.getNumElements(), scaleSelf);\n        }\n\n        delete &prevChunk;\n        delete &myChunk;\n\n    } else {\n        _numInputsFinished++;\n    }\n    if (!isTerminal() && inputsRcvd == _prev.size()) {\n//        printf(\"    device %d enqueueing msg for next on device %d\\n\", getDeviceID(), _next->getDeviceID());\n        _next->getQueue().enqueue(\n                new ReduceChunkMessage(*this, cmsg.getChunkIdx(), cmsg.getChunkSize(), cmsg.getNumChunks(),\n                                        cmsg.getScaleIntermediates(), cmsg.getScaleTarget(), cmsg.getMatrices()));\n    }\n\n    bool finished = _numInputsFinished == _prev.size();\n    if (finished) {\n        _numInputsFinished = 0;\n        _numInputsReceived.clear();\n    }\n    return finished && isTerminal();\n}\n\nvoid ReducePeer::hostAdd(const float* src, float* tgt, const int n, const float scaleTgt) {\n    if (scaleTgt != 0) {\n        for (int i = 0; i < n; ++i) {\n            tgt[i] = scaleTgt * tgt[i] + src[i];\n        }\n    } else {\n        for (int i = 0; i < n; ++i) {\n            tgt[i] = src[i];\n        }\n    }\n}\n\ninline NVMatrix& ReducePeer::getMatrix(ReduceMessage& msg) {\n    if (getDeviceID() != DEVICE_HOST) {\n        return IReduceSegment::getMatrix(msg);\n    }\n    return _mat;\n}\n\n/* =========================\n * EightGPUReducer\n * =========================\n */\nIEightGPUReducer::IEightGPUReducer(int tgtDeviceID) : _tgtDeviceID(tgtDeviceID) {\n}\n\nIEightGPUReducer::~IEightGPUReducer() {\n    vector<IReduceSegment*> v;\n    v.insert(v.end(), _sources.begin(), _sources.end());\n    v.insert(v.end(), _peers.begin(), _peers.end());\n    for (vector<IReduceSegment*>::iterator it = v.begin(); it != v.end(); ++it) {\n        (*it)->getQueue().enqueue(new ReduceMessage(EXIT));\n        (*it)->join();\n        delete *it;\n    }\n}\n\nIEightGPUReducer& IEightGPUReducer::construct() {\n    vector<int> same, other;\n    for (int i = 0; i < 8; ++i) {\n        if (i != _tgtDeviceID) {\n            if (NVMatrix::canAccessPeer(_tgtDeviceID, i)) {\n                same.insert(same.begin() + rand() % (1 + same.size()), i);\n            } else {\n                other.insert(other.begin() + rand() % (1 + other.size()), i);\n            }\n        }\n    }\n    assert(same.size() == 3);\n    assert(other.size() == 4);\n    makeConnections(same, other);\n    for (vector<ReducerSource*>::const_iterator it = _sources.begin(); it != _sources.end(); ++it) {\n        (*it)->start();\n    }\n    for (vector<ReducePeer*>::const_iterator it = _peers.begin(); it != _peers.end(); ++it) {\n        (*it)->start();\n    }\n    return *this;\n}\n\nvoid IEightGPUReducer::reduce(std::map<int, NVMatrix*>& mats, float scaleIntermediates, float scaleTarget) {\n    assert(mats.size() == 8);\n    // Check if source matrices are 0-sized\n    bool zero = true;\n    for (map<int,NVMatrix*>::const_iterator it = mats.begin(); it != mats.end(); ++it) {\n        if (it->first != _tgtDeviceID && it->second->getNumElements() != 0) {\n            zero = false;\n            break;\n        }\n    }\n    if (zero) {\n        mats[_tgtDeviceID]->resize(*mats[(_tgtDeviceID + 1) % 8]);\n    } else {\n        for (vector<ReducerSource*>::const_iterator it = _sources.begin(); it != _sources.end(); ++it) {\n            (*it)->getQueue().enqueue(new ReduceStartMessage(scaleIntermediates, scaleTarget, mats));\n        }\n        _finishQueue.dequeue();\n    }\n    assert(_finishQueue.getNumElements() == 0);\n}\n\nvoid IEightGPUReducer::reduce(std::map<int, NVMatrix*>& mats, float scaleIntermediates) {\n    reduce(mats, scaleIntermediates, 1);\n}\n\nvoid IEightGPUReducer::reduce(std::map<int, NVMatrix*>& mats) {\n    reduce(mats, 1, 1);\n}\n\nint IEightGPUReducer::getTgtDeviceID() const {\n    return _tgtDeviceID;\n}\n\n/* =========================\n * EightGPUReducer1\n * =========================\n */\nEightGPUReducer1::EightGPUReducer1(int tgtDeviceID) : IEightGPUReducer(tgtDeviceID) {\n}\n\nvoid EightGPUReducer1::makeConnections(vector<int>& same, vector<int>&other) {\n    // Setup segments on same truck\n    _peers.push_back(new ReducePeer(*this, _tgtDeviceID, &_finishQueue));         // peers[0] = tgt\n    _peers.push_back(new ReducePeer(*this,same[0], &_finishQueue));               // peers[1] = same truck 1\n    _peers.push_back(new ReducePeer(*this,same[1], &_finishQueue));               // peers[2] = same truck 2\n    _sources.push_back(new ReducerSource(*this,same[2]));                         // sources[0] = same truck 3\n \n    _sources[0]->addNext(*_peers[2]);\n    _peers[2]->addNext(*_peers[1]);\n    _peers[1]->addNext(*_peers[0]);\n\n    // Setup segments on other truck\n    _sources.push_back(new ReducerSource(*this,other[0]));                        // sources[1] = other truck 1\n    _peers.push_back(new ReducePeer(*this,other[1], &_finishQueue));              // peers[3] = other truck 2\n    _peers.push_back(new ReducePeer(*this,other[2], &_finishQueue));              // peers[4] = other truck 3\n    _sources.push_back(new ReducerSource(*this,other[3]));                        // sources[2] = other truck 4\n    _peers.push_back(new ReducePeer(*this));                                      // peers[5] = host 1\n    _peers.push_back(new ReducePeer(*this));                                      // peers[6] = host 2\n    _peers.push_back(new ReducePeer(*this));                                      // peers[7] = host 3\n\n    _sources[1]->addNext(*_peers[3]);\n    _peers[3]->addNext(*_peers[5]);\n    _peers[5]->addNext(*_peers[7]);\n    _peers[7]->addNext(*_peers[0]);\n    _peers[4]->addNext(*_peers[6]);\n    _peers[6]->addNext(*_peers[7]);\n    _sources[2]->addNext(*_peers[4]);\n}\n\n/* =========================\n * EightGPUReducer2\n * =========================\n */\nEightGPUReducer2::EightGPUReducer2(int tgtDeviceID) : IEightGPUReducer(tgtDeviceID) {\n}\n\nvoid EightGPUReducer2::makeConnections(vector<int>& same, vector<int>&other) {\n    // Setup segments on same truck\n    _peers.push_back(new ReducePeer(*this,_tgtDeviceID, &_finishQueue));          // peers[0] = tgt\n    _peers.push_back(new ReducePeer(*this,same[0], &_finishQueue));               // peers[1] = same truck 1\n    _peers.push_back(new ReducePeer(*this,same[1], &_finishQueue));               // peers[2] = same truck 2\n    _sources.push_back(new ReducerSource(*this,same[2]));                         // sources[0] = same truck 3\n\n    _sources[0]->addNext(*_peers[2]);\n    _peers[2]->addNext(*_peers[1]);\n    _peers[1]->addNext(*_peers[0]);\n\n    // Setup segments on other truck\n    _sources.push_back(new ReducerSource(*this,other[0]));                        // sources[1] = other truck 1\n    _peers.push_back(new ReducePeer(*this,other[1], &_finishQueue));              // peers[3] = other truck 2\n    _peers.push_back(new ReducePeer(*this,other[2], &_finishQueue));              // peers[4] = other truck 3\n    _peers.push_back(new ReducePeer(*this,other[3], &_finishQueue));              // peers[5] = other truck 4\n    _peers.push_back(new ReducePeer(*this));                                      // peers[6] = host 1\n\n    _sources[1]->addNext(*_peers[3]);\n    _peers[3]->addNext(*_peers[4]);\n    _peers[4]->addNext(*_peers[5]);\n    _peers[5]->addNext(*_peers[6]);\n    _peers[6]->addNext(*_peers[0]);\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/streambroadcast.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"../include/streambroadcast.cuh\"\n\nusing namespace std;\n\n/*\n * =====================\n * StreamBroadcast\n * =====================\n */\n\nStreamBroadcast::StreamBroadcast(map<int,cudaStream_t>& streams) {\n    _streams = streams;\n}\n\nStreamBroadcast::StreamBroadcast() {\n}\n\nvoid StreamBroadcast::toHostMem(NVMatrix& src, NVMatrix& hostmem, int srcDevice) {\n    src.copy(hostmem, _streams[srcDevice]);\n}\n\nvoid StreamBroadcast::toTarget(NVMatrix& hostmem, NVMatrix& tgt, int tgtDevice, float scaleTarget, float scaleOutput) {\n    tgt.add(hostmem, scaleTarget, scaleOutput, tgt, _streams[tgtDevice]);\n}\n\nvoid StreamBroadcast::init(map<int, NVMatrix*>& mats) {\n    for (map<int, NVMatrix*>::const_iterator it = mats.begin(); it != mats.end(); ++it) {\n        if (_streams.count(it->first) == 0) {\n            _ownedStreams.insert(it->first);\n            NVMatrix::setDeviceID(it->first);\n            checkCudaErrors(cudaStreamCreateWithFlags(&_streams[it->first], cudaStreamNonBlocking));\n        }\n    }\n}\n\nStreamBroadcast::~StreamBroadcast() {\n    for (set<int>::const_iterator it = _ownedStreams.begin(); it != _ownedStreams.end(); ++it) {\n        checkCudaErrors(cudaStreamDestroy(_streams[*it]));\n    }\n}\n\ncudaStream_t StreamBroadcast::getStream(int deviceID) {\n    return _streams[deviceID];\n}\n\n// Sync stream associated with given device id\nvoid StreamBroadcast::sync(int deviceID) {\n    NVMatrix::syncStream(_streams[deviceID]);\n}\n\nvoid StreamBroadcast::transfer(map<int,NVMatrix*>& mats,  int srcDevice) {\n    transfer(mats, _hostMem, srcDevice, 0, 1);\n}\n\nvoid StreamBroadcast::transfer(map<int,NVMatrix*>& mats,  int srcDevice, float scaleTarget, float scaleOutput) {\n    transfer(mats, _hostMem, srcDevice, scaleTarget, scaleOutput);\n}\n\nvoid StreamBroadcast::transfer(map<int,NVMatrix*>& mats, HostNVMatrix& hostbuf, int srcDevice, float scaleTarget, float scaleOutput) {\n    int oldDeviceID = NVMatrix::getDeviceID();\n    assert(mats.count(srcDevice) != 0);\n    init(mats);\n//    assert(_streams.count(srcDevice) != 0);\n    if (mats.size() > 1) {\n        if (mats[srcDevice]->getNumElements() == 0) {\n            for (map<int,NVMatrix*>::const_iterator it = mats.begin(); it != mats.end(); ++it) {\n                it->second->resize(*mats[srcDevice]);\n            }\n        } else {\n            int tgtDevice = mats.begin()->first != srcDevice ? mats.begin()->first : (++mats.begin())->first;\n            // This case is a simple copy\n            if (mats.size() == 2 && NVMatrix::canAccessPeer(tgtDevice, srcDevice)) {\n                NVMatrix::setDeviceID(tgtDevice);\n                mats[tgtDevice]->add(*mats[srcDevice], scaleTarget, scaleOutput, *mats[tgtDevice], _streams[tgtDevice]);\n            } else {\n                NVMatrix& src = *mats[srcDevice];\n                if (hostbuf.getNumElements() < src.getNumElements()) {\n                    hostbuf.resize(1,src.getNumElements());\n                }\n                hostbuf.setTrans(src.isTrans());\n\n                NVMatrix& hostmat = hostbuf.sliceCols(0, src.getNumElements());\n                assert(hostmat.isView());\n                hostmat.reshape(src.getNumRows(), src.getNumCols());\n\n                for (map<int,NVMatrix*>::const_iterator it = mats.begin(); it != mats.end(); ++it) {\n                    assert(it->second->isContiguous());\n                    NVMatrix::setDeviceID(it->first);\n                    it->second->resize(src);\n                    assert(it->second->isTrans() == src.isTrans());\n                }\n                int numChunks = min(DIVUP(src.getNumElements(), SB_MIN_CHUNK_SIZE), SB_MAX_CHUNKS);\n\n                if (numChunks == 1) { // This is a bit faster for small matrices\n                    NVMatrix::setDeviceID(srcDevice);\n                    toHostMem(src, hostmat, srcDevice);\n                    NVMatrix::syncStream(_streams[srcDevice]);\n\n                    for (map<int,NVMatrix*>::const_iterator it = mats.begin(); it != mats.end(); ++it) {\n                        if (it->first != src.getDataDeviceID()) {\n                            NVMatrix::setDeviceID(it->first);\n                            toTarget(hostmat, *it->second, it->first, scaleTarget, scaleOutput);\n                        }\n                    }\n                } else {\n                    int n = src.getNumElements();\n\n                    map<int,NVMatrix*> lines;\n                    for (map<int,NVMatrix*>::const_iterator it = mats.begin(); it != mats.end(); ++it) {\n                        lines[it->first] = &it->second->reshaped(1, n);\n                        lines[it->first]->setTrans(src.isTrans());\n                    }\n                    NVMatrix& srcLine = *lines[srcDevice];\n                    hostmat.reshape(1, n);\n\n                    int chunkSize = DIVUP(n, numChunks);\n                    bool trans = src.isTrans();\n                    for (int i = 0; i < numChunks; ++i) {\n                        int start = i * chunkSize;\n                        int end = min((i+1) * chunkSize, n);\n                        if (start < end) {\n                            NVMatrix& tmpSrc = srcLine.sliceCols(start, end); // view\n                            NVMatrix& tmpHostmem = hostmat.sliceCols(start, end); // view\n\n                            NVMatrix::setDeviceID(srcDevice);\n                            toHostMem(tmpSrc, tmpHostmem, srcDevice);\n                            NVMatrix::syncStream(_streams[srcDevice]);\n\n                            for (map<int,NVMatrix*>::const_iterator it = lines.begin(); it != lines.end(); ++it) {\n                                if (it->first != srcDevice) {\n                                    NVMatrix& tmpTgt = it->second->sliceCols(start, end); // view\n                                    NVMatrix::setDeviceID(it->first);\n                                    toTarget(tmpHostmem, tmpTgt, it->first, scaleTarget, scaleOutput);\n                                    delete &tmpTgt;\n                                }\n                            }\n                            delete &tmpSrc;\n                            delete &tmpHostmem;\n                        }\n                    }\n                    for (map<int,NVMatrix*>::const_iterator it = lines.begin(); it != lines.end(); ++it) {\n                        delete it->second;\n                    }\n                }\n                delete &hostmat;\n            }\n            for(map<int,NVMatrix*>::const_iterator it = mats.begin(); it != mats.end(); ++it) {\n                if (it->first != srcDevice) {\n                    NVMatrix::syncStream(_streams[it->first]);\n                }\n            }\n        }\n    }\n    if (oldDeviceID >= 0) {\n        NVMatrix::setDeviceID(oldDeviceID);\n    }\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/util.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <Python.h>\n#include <arrayobject.h>\n#include <helper_cuda.h>\n#include \"../include/util.cuh\"\n\nusing namespace std;\n\nstringv* getStringV(PyObject* pyList) {\n    if (pyList == NULL) {\n        return NULL;\n    }\n    stringv* vec = new stringv(); \n    for (int i = 0; i < PyList_GET_SIZE(pyList); i++) {\n        vec->push_back(std::string(PyString_AS_STRING(PyList_GET_ITEM(pyList, i))));\n    }\n    return vec;\n}\n\nfloatv* getFloatV(PyObject* pyList) {\n    if (pyList == NULL) {\n        return NULL;\n    }\n    floatv* vec = new floatv(); \n    for (int i = 0; i < PyList_GET_SIZE(pyList); i++) {\n        vec->push_back(PyFloat_AS_DOUBLE(PyList_GET_ITEM(pyList, i)));\n    }\n    return vec;\n}\n\nintv* getIntV(PyObject* pyList) {\n    if (pyList == NULL) {\n        return NULL;\n    }\n    intv* vec = new intv(); \n    for (int i = 0; i < PyList_GET_SIZE(pyList); i++) {\n        vec->push_back(PyInt_AS_LONG(PyList_GET_ITEM(pyList, i)));\n    }\n    return vec;\n}\n\nint* getIntA(PyObject* pyList) {\n    if (pyList == NULL) {\n        return NULL;\n    }\n    int* arr = new int[PyList_GET_SIZE(pyList)];\n    for (int i = 0; i < PyList_GET_SIZE(pyList); i++) {\n        arr[i] = PyInt_AS_LONG(PyList_GET_ITEM(pyList, i));\n    }\n    return arr;\n}\n\nMatrixV* getMatrixV(PyObject* pyList) {\n    return getMatrixV(pyList, PyList_GET_SIZE(pyList));\n}\n\nMatrixV* getMatrixV(PyObject* pyList, int len) {\n    if (pyList == NULL) {\n        return NULL;\n    }\n    MatrixV* vec = new MatrixV(); \n    for (int i = 0; i < len; i++) {\n        vec->push_back(new Matrix((PyArrayObject*)PyList_GET_ITEM(pyList, i)));\n    }\n    return vec;\n}\n\nPyObjectV* pyDictGetValues(PyObject* dict) {\n    PyObjectV* pov = new PyObjectV();\n    PyObject* valuesList = PyDict_Values(dict);\n    int numValues = PyList_GET_SIZE(valuesList);\n\n    for (int i = 0; i < numValues; i++) {\n        pov->push_back(PyList_GET_ITEM(valuesList, i));\n    }\n    Py_DECREF(valuesList);\n    return pov;\n}\n\nint pyDictGetInt(PyObject* dict, const char* key) {\n    return PyInt_AS_LONG(PyDict_GetItemString(dict, key));\n}\n\nintv* pyDictGetIntV(PyObject* dict, const char* key) {\n    return getIntV(PyDict_GetItemString(dict, key));\n}\n\nint* pyDictGetIntA(PyObject* dict, const char* key) {\n    return getIntA(PyDict_GetItemString(dict, key));\n}\n\nstd::string pyDictGetString(PyObject* dict, const char* key) {\n    return std::string(PyString_AS_STRING(PyDict_GetItemString(dict, key)));\n}\n\nfloat pyDictGetFloat(PyObject* dict, const char* key) {\n    return PyFloat_AS_DOUBLE(PyDict_GetItemString(dict, key));\n}\n\nfloatv* pyDictGetFloatV(PyObject* dict, const char* key) {\n    return getFloatV(PyDict_GetItemString(dict, key));\n}\n\nMatrix* pyDictGetMatrix(PyObject* dict, const char* key) {\n    return new Matrix((PyArrayObject*)PyDict_GetItemString(dict, key));\n}\n\nMatrixV* pyDictGetMatrixV(PyObject* dict, const char* key) {\n    return getMatrixV(PyDict_GetItemString(dict, key));\n}\n\nstringv* pyDictGetStringV(PyObject* dict, const char* key) {\n    return getStringV(PyDict_GetItemString(dict, key));\n}\n\nbool pyDictHasKey(PyObject* dict, const char* key) {\n    PyObject* str = PyString_FromString(key);\n    bool b = PyDict_Contains(dict, str);\n    Py_DECREF(str);\n    return b;\n}\n\ntemplate<typename T>\nvoid shuffleVector(vector<T>& v, int start, int end) {\n    const int len = end - start;\n    for (int i = 0; i < len*5; ++i) {\n        int r1 = start + rand() % len;\n        int r2 = start + rand() % len;\n        int tmp = v[r1];\n        v[r1] = v[r2];\n        v[r2] = tmp;\n    }\n}\n\ntemplate<class T>\nstd::string tostr(T n) {\n    ostringstream result;\n    result << n;\n    return result.str();\n}\n\ntemplate<class T>\nvoid deleteElements(vector<T*>& v) {\n    deleteElements(v, false);\n}\n\ntemplate<class T>\nvoid deleteElements(vector<T*>& v, bool deleteContainer) {\n    for (typename vector<T*>::const_iterator it = v.begin(); it != v.end(); ++it) {\n        delete *it;\n    }\n    if (deleteContainer) {\n        delete &v;\n    }\n}\n\nstatic Lock deviceCPULock;\nstatic std::map<int, std::vector<int> > deviceCPUs;\n\nstd::vector<int>& getDeviceCPUs(int deviceID) {\n    deviceCPULock.acquire();\n    if (deviceCPUs.count(deviceID) == 0 && deviceID >= 0) {\n        struct cudaDeviceProp props;\n        checkCudaErrors(cudaGetDeviceProperties(&props, deviceID));\n        char pciString[13];\n\n        sprintf(pciString, \"%04x\", props.pciDomainID);\n        pciString[4] = ':';\n        sprintf(pciString + 5, \"%02x\", props.pciBusID);\n        pciString[7] = ':';\n        sprintf(pciString + 8, \"%02x\", props.pciDeviceID);\n        pciString[10] = '.';\n        pciString[11] = '0';\n        pciString[12] = 0;\n        std::string path = std::string(\"/sys/bus/pci/devices/\") + std::string(pciString) + \"/local_cpulist\";\n        ifstream f(path.c_str());\n\n        if (f.is_open()) {\n            std::string cpuString;\n            while (getline(f, cpuString, ',')) {\n                int start, end;\n                int found = sscanf(cpuString.c_str(), \"%d-%d\", &start, &end);\n                end = found == 1 ? start : end;\n                if (found > 0) {\n                    for (int i = start; i <= end; ++i) {\n                        deviceCPUs[deviceID].push_back(i);\n                    }\n                } \n            }\n            f.close();\n        } else {\n            printf(\"Unable to open %s\\n\", path.c_str());\n        }\n    }\n    vector<int>& ret = deviceCPUs[deviceID];\n    deviceCPULock.release();\n    return ret;\n}\n\ntemplate void shuffleVector<int>(std::vector<int>& v, int start, int end);\ntemplate std::string tostr<int>(int n);\ntemplate void deleteElements<NVMatrix>(std::vector<NVMatrix*>& v, bool deleteContainer);\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/weights.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <map>\n#include <algorithm>\n#include \"../include/weights.cuh\"\n#include \"../include/lr.cuh\"\n#include \"../include/worker.cuh\"\n\nusing namespace std;\n\n/* ========================\n * IWeightReducer\n * ========================\n */\nint IWeightReducer::getDeviceID() {\n    return _replicas[_tgtReplicaID]->getDeviceID();\n}\n\nIWeightReducer::IWeightReducer(std::map<int,Weights*>& replicas, int tgtReplicaID) : _replicas(replicas), _tgtReplicaID(tgtReplicaID) {\n}\n\nIWeightReducer::~IWeightReducer() {\n}\n\nIWeightReducer& IWeightReducer::make(std::map<int,Weights*>& replicas, int tgtReplicaID) {\n    if (replicas.size() == 8) {\n        return *new ParallelWeightReducer(replicas, tgtReplicaID);\n    }\n    return *new SequentialWeightReducer(replicas, tgtReplicaID);\n}\n\n/* ========================\n * SequentialWeightReducer\n * ========================\n */\nSequentialWeightReducer::SequentialWeightReducer(std::map<int,Weights*>& replicas, int tgtReplicaID) : IWeightReducer(replicas, tgtReplicaID) {\n    _sb = new StreamBroadcast();\n}\n\nSequentialWeightReducer::~SequentialWeightReducer() {\n    delete _sb;\n}\n\nvoid SequentialWeightReducer::reduce(std::map<int, NVMatrix*> gradShards, float gradScale, bool toInc) {\n    std::map<int, NVMatrix*> mats; // device id -> grad\n    mats[getDeviceID()] = toInc ? &_replicas[_tgtReplicaID]->getInc() : &_replicas[_tgtReplicaID]->getGrad();\n    for (int i = 0, r = _tgtReplicaID; i < _replicas.size(); ++i, r = (r + 1) % _replicas.size()) {\n        if (r != _tgtReplicaID) {\n            mats[_replicas[r]->getDeviceID()] = gradShards[r];\n            _sb->transfer(mats, _replicas[r]->getDeviceID(), 1, gradScale);\n            mats.erase(_replicas[r]->getDeviceID());\n        }\n    }\n}\n\n/* ========================\n * ParallelWeightReducer\n * ========================\n */\nParallelWeightReducer::ParallelWeightReducer(std::map<int,Weights*>& replicas, int tgtReplicaID) : IWeightReducer(replicas, tgtReplicaID) {\n    _reducer = &(new EightGPUReducer1(getDeviceID()))->construct();\n}\n\nParallelWeightReducer::~ParallelWeightReducer() {\n    delete _reducer;\n}\n\nvoid ParallelWeightReducer::reduce(std::map<int, NVMatrix*> gradShards, float gradScale, bool toInc) {\n    std::map<int, NVMatrix*> mats; // device id -> grad\n    mats[getDeviceID()] = toInc ? &_replicas[_tgtReplicaID]->getInc() : &_replicas[_tgtReplicaID]->getGrad();\n    for (std::map<int,Weights*>::const_iterator it = _replicas.begin(); it != _replicas.end(); ++it) {\n        if (it->first != _tgtReplicaID) {\n            mats[it->second->getDeviceID()] = gradShards[it->first];\n        }\n    }\n    _reducer->reduce(mats, gradScale, 1);\n}\n\n// weights has pointer to layer, layer pointer to thread\n// thread has sync (copy) object for every other thread\n// weights uses copy object to sum grad contributions into inc matrix slice (phase 1)\n// weights broadcasts inc matrix slice to other inc matrix replicas (phase 2)\n\nNVMatrix& Weights::operator*() const {\n    return getW();\n}\n\n/*\n * TODO: get rid of this constructor duplication.\n */\nWeights::Weights(Weights& srcWeights, ParameterSchedule& lrs, Layer& parent) {\n    init(srcWeights.getCPUW(), srcWeights.getCPUWInc(), lrs, parent, 0, 0, srcWeights.getMom(), srcWeights.isUseGrad(), false);\n    _srcWeights = &srcWeights;\n}\n\nWeights::Weights(Matrix& hWeights, Matrix& hWeightsInc, ParameterSchedule& lrs, Layer& parent, float wc,\n                 float wball, float mom, bool useGrad) {\n    init(hWeights, hWeightsInc, lrs, parent, wc, wball, mom, useGrad, true);\n}\n\nvoid Weights::init(Matrix& hWeights, Matrix& hWeightsInc, ParameterSchedule& lrs, Layer& parent, float wc,\n              float wball, float mom, bool useGrad, bool cleanup) {\n    _srcWeights = NULL;\n    _hWeights = &hWeights;\n    _hWeightsInc = &hWeightsInc;\n    _numUpdates = 0;\n    _lrs = &lrs;\n    _parent = &parent;\n    _wc = wc;\n    _wball = wball;\n    _mom = mom;\n    _useGrad = useGrad;\n    _onGPU = false;\n    _weights = NULL;\n    _weightsInc = NULL;\n    _weightsGrad = NULL;\n    _cleanup = cleanup;\n    _reducer = NULL;\n    _broadcaster = NULL;\n}\n\nWeights::~Weights() {\n\tdelete _lrs;\n\tdelete _reducer;\n\tdelete _broadcaster;\n    if (_cleanup) {\n        delete _hWeights;\n        delete _hWeightsInc;\n        if (_srcWeights == NULL) {\n            delete _weights;\n            delete _weightsInc;\n            delete _weightsGrad;\n        }\n    }\n}\n\nNVMatrix& Weights::getW() const {\n    assert(_onGPU);\n    return *_weights;\n}\n\nNVMatrix& Weights::getInc() const {\n    assert(_onGPU);\n    return *_weightsInc;\n}\n\n/*\n * TODO: This seems like pretty nasty behavior, I should change this.\n */\nNVMatrix& Weights::getGrad() const {\n    assert(_onGPU);\n    return _useGrad ? *_weightsGrad : *_weightsInc;\n}\n\nMatrix& Weights::getCPUW() const {\n    return *_hWeights;\n}\n\nMatrix& Weights::getCPUWInc() const {\n    return *_hWeightsInc;\n}\n\nint Weights::getNumRows() const {\n    return _hWeights->getNumRows();\n}\n\nint Weights::getNumCols() const {\n    return _hWeights->getNumCols();\n}\n\nmap<int,Weights*>& Weights::getReplicas() {\n    return _replicas;\n}\n\ntemplate<class T> T& Weights::getShard(T& mat, int replicaID) {\n    const int n = mat.getNumElements();\n    T& line = mat.reshaped(1, n);\n    const int shardStart = min(n, replicaID * _shardSize);\n    const int shardEnd = min(n, (replicaID + 1) * _shardSize);\n    T& slice = line.sliceCols(shardStart, shardEnd);\n    assert(slice.isView());\n    delete &line;\n    return slice;\n}\n\ntemplate<class T> T& Weights::getShard(T& mat) {\n    return getShard(mat, getReplicaID());\n}\n\nISafeBroadcastNetwork& Weights::getBroadcaster() {\n    if (_broadcaster == NULL) {\n        set<int> devices;\n        for (map<int, Weights*>::const_iterator it = _replicas.begin(); it != _replicas.end(); ++it) {\n            devices.insert(it->second->getDeviceID());\n        }\n        // NOTE: we must use safe broadcaster becasue we want to *add* our value to everyone else\n        _broadcaster = &ISafeBroadcastNetwork::make(devices, getDeviceID()); //&(new NaiveBroadcaster(devices, getDeviceID()))->construct();\n    }\n    return *_broadcaster;\n}\n\nIWeightReducer& Weights::getReducer() {\n    if (_reducer == NULL) {\n        _reducer = &IWeightReducer::make(_replicas, getReplicaID());\n    }\n    return *_reducer;\n}\n\nvoid Weights::copyToCPU() {\n    if (_srcWeights == NULL) {\n        assert(_onGPU);\n        NVMatrix::syncStream(); // for safety\n        if (getReplicaID() == 0) {\n            _weights->copyToHost(*_hWeights);\n\n            // Synchronize weights amongst replicas while we're at it.\n            map<int,NVMatrix*> weights;\n            for (map<int,Weights*>::const_iterator it = _replicas.begin(); it != _replicas.end(); ++it) {\n                weights[it->second->getDeviceID()] = &it->second->getW();\n            }\n            // These things sync before returning.\n            getBroadcaster().broadcast(weights, 1, 0);\n        }\n        if (_useGrad) {\n            Matrix& hIncShard = getShard(*_hWeightsInc);\n            _weightsInc->copyToHost(hIncShard);\n            delete &hIncShard;\n        } else { // In this case there's definitely only one replica\n            _weightsInc->copyToHost(*_hWeightsInc);\n        }\n    }\n}\n\n// This function is assumed to be called in the order in which the layers\n// were defined\nvoid Weights::copyToGPU() {\n    assert(!_onGPU);\n    // Copies are performed on the default (computation) stream, so that's fine.\n    if (_srcWeights == NULL) {\n        _weights = _weights == NULL ? new NVMatrix() : _weights;\n        _weightsInc = _weightsInc == NULL ? new NVMatrix() : _weightsInc;\n        _weights->copyFromHost(*_hWeights, true);\n\n        if (_useGrad) {\n            // In this case there is no need to store the entire inc matrix.\n            // Just this replica's shard (for synchronization purposes) will do.\n            Matrix& hIncShard = getShard(*_hWeightsInc);\n            _weightsInc->copyFromHost(hIncShard, true);\n            delete &hIncShard;\n        } else {\n            _weightsInc->copyFromHost(*_hWeightsInc, true);\n        }\n\n        _weightsGrad = _useGrad ? (_weightsGrad == NULL ? new NVMatrix(*_weights) : _weightsGrad) : NULL;\n    } else {\n        _weights = _srcWeights->_weights;\n        _weightsInc = _srcWeights->_weightsInc;\n        _weightsGrad = _srcWeights->_weightsGrad;\n    }\n    _onGPU = true;\n}\n\nvoid Weights::aggregateReplicaGradients(float progress) {\n    map<int, NVMatrix*> gradShards;\n    map<int, NVMatrix*> wShards;\n    for (map<int,Weights*>::const_iterator it = _replicas.begin(); it != _replicas.end(); ++it) {\n        gradShards[it->first] = &getShard(it->second->getGrad(), getReplicaID());\n        wShards[it->first] = &getShard(it->second->getW(), getReplicaID());\n        assert(wShards[it->first]->isContiguous() && gradShards[it->first]->isContiguous());\n    }\n\n    float gradScale = _lrs->getValue(progress);\n    NVMatrix::setDeviceID(getDeviceID());\n\n    if (_wc > 0) {\n        NVMatrixTernaryOps::WeightedAdd wadd = NVMatrixTernaryOps::WeightedAdd(_mom, gradScale, -_wc * _lrs->getValue(progress));\n        _weightsInc->applyTernary(wadd, *gradShards[getReplicaID()], *wShards[getReplicaID()], *_weightsInc);\n    } else {\n        _weightsInc->add(*gradShards[getReplicaID()], _mom, gradScale);\n    }\n\n    // Reduce everyone's gradient into my inc shard\n    NVMatrix::syncStream(); // Crucial since the reducer does everything in its own streams!!\n    getReducer().reduce(gradShards, gradScale, true);\n\n    // Broadcast my inc -> all replicas\n    map<int, NVMatrix*> mats; // device id -> grad\n    mats[getDeviceID()] = _weightsInc;\n    for (map<int, Weights*>::const_iterator it = _replicas.begin(); it != _replicas.end(); ++it) {\n        if (it->first != getReplicaID()) {\n            mats[it->second->getDeviceID()] = wShards[it->first];\n        }\n    }\n    getBroadcaster().broadcast(mats, 1, 1);\n\n    NVMatrix::setDeviceID(getDeviceID());\n    wShards[getReplicaID()]->add(*_weightsInc);\n\n    // Cleanup\n    for (map<int,Weights*>::const_iterator it = _replicas.begin(); it != _replicas.end(); ++it) {\n        delete gradShards[it->first];\n        delete wShards[it->first];\n    }\n}\n\n\n// When _useGrad is false, weightsInc is assumed to contain the \n// entire, properly scaled weight increment.\n// OTHERWISE, scale your gradient by 1 / numCases only.\n// The scaling by epsW will be done in this routine.\nvoid Weights::update(float progress) {\n    // Only true owner of weights updates\n//    printf(\"%s update weights\\n\", _parent->getName().c_str());\n    if (_srcWeights == NULL && _lrs->getBaseValue() > 0) {\n        assert(_onGPU);\n        if (_useGrad) {\n            aggregateReplicaGradients(progress);\n        } else { // Definitely no replicas in this case\n            if (_wc > 0) {\n                _weightsInc->add(*_weights, -_wc * _lrs->getValue(progress));\n            }\n            _weights->add(*_weightsInc);\n        }\n        _numUpdates = 0;\n    }\n}\n\nint Weights::incNumUpdates() {\n    if (_srcWeights != NULL) {\n        return _srcWeights->incNumUpdates();\n    }\n    return _numUpdates++;\n}\n\n// Returns the number of times a gradient has been computed for this\n// weight matrix during the current pass (interval between two calls of update())\n// through the net. This number will only be greater than 1 if this weight matrix\n// is *shared* by multiple layers in the net.\nint Weights::getNumUpdates() const {\n    if (_srcWeights != NULL) {\n        return _srcWeights->getNumUpdates();\n    }\n    return _numUpdates;\n}\n\nfloat Weights::getEps(float progress) const {\n    return _lrs->getValue(progress);\n}\n\nfloat Weights::getMom() const {\n    return _mom;\n}\n\nfloat Weights::getWC() const {\n    return _wc;\n}\n\nfloat Weights::getWBall() const {\n    return _wball;\n}\n\nbool Weights::isUseGrad() const { // is good grammar\n    return _useGrad;\n}\n\nbool Weights::isOwner() const {\n    return _srcWeights == NULL;\n}\n\nParameterSchedule& Weights::getLearningRateSchedule() const {\n\treturn *_lrs;\n}\n\nvoid Weights::addReplica(Weights& replica) {\n    _replicas[replica.getReplicaID()] = &replica;\n\n    const int n = _hWeights->getNumElements();\n    _shardSize = DIVUP(n, _replicas.size());\n}\n\nint Weights::getReplicaID() {\n    return _parent->getReplicaID();\n}\n\nint Weights::getDeviceID() {\n    return _parent->getDeviceID();\n}\n\nLayer& Weights::getParent() {\n    return *_parent;\n}\n\n/* \n * ===============\n * WeightList\n * ===============\n */\nWeights& WeightList::operator[](const int i) const {\n    return *_weightList[i];\n}\n\nWeights& WeightList::at(const int i) const {\n    return *_weightList[i];\n}\n\nWeightList::~WeightList() {\n    for (int i = 0; i < _weightList.size(); i++) {\n        delete _weightList[i];\n    }\n}\n\nWeightList::WeightList() {\n}\n\nvoid WeightList::addWeights(Weights& w) {\n    _weightList.push_back(&w);\n}\n\n\nvoid WeightList::update(float progress) {\n    for (int i = 0; i < getSize(); i++) {\n        _weightList[i]->update(progress);\n    }\n}\n\nvoid WeightList::copyToCPU() {\n    for (int i = 0; i < getSize(); i++) {\n        _weightList[i]->copyToCPU();\n    }\n}\n\nvoid WeightList::copyToGPU() {\n    for (int i = 0; i < getSize(); i++) {\n        _weightList[i]->copyToGPU();\n    }\n}\n\nint WeightList::getSize() const {\n    return _weightList.size();\n}\n\nvoid WeightList::addReplica(WeightList& replica) {\n    for (int i = 0; i < getSize(); i++) {\n        _weightList[i]->addReplica(replica[i]);\n    }\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/cudaconvnet/src/worker.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <algorithm>\n#include \"../include/util.cuh\"\n#include \"../include/worker.cuh\"\n#include \"../include/timer.cuh\"\n\nusing namespace std;\n\n/* \n * ====================\n * WorkResult\n * ====================\n */\nWorkResult::WorkResult(WorkResult::RESULTS resultType, Cost& results) : _resultType(resultType), _results(&results) {\n}\n\nWorkResult::WorkResult(WorkResult::RESULTS resultType) : _resultType(resultType), _results(NULL) {\n}\n\nWorkResult::~WorkResult() {\n    delete _results; // delete NULL is ok\n}\n\nCost& WorkResult::getResults() const {\n    return *_results;\n}\n\nWorkResult::RESULTS WorkResult::getResultType() const {\n    return _resultType;\n}\n\n/* \n * ====================\n * Worker\n * ====================\n */\nWorker::Worker(ConvNet& convNet) : _convNet(&convNet) {\n}\n\nWorker::~Worker() {\n}\n\n/* \n * ====================\n * DataWorker\n * ====================\n */\nDataWorker::DataWorker(ConvNet& convNet, CPUData& data) : Worker(convNet), _data(&data), _dp(NULL) {\n    assert(_data != NULL);\n}\n\nbool DataWorker::run() {\n    _dp = &_convNet->getDataProvider();\n    _dp->setData(*_data);\n    _run();\n    _dp->clearData();\n    return false;\n}\n\nDataWorker::~DataWorker() {\n}\n\n/* \n * ====================\n * TrainingWorker\n * ====================\n */\nTrainingWorker::TrainingWorker(ConvNet& convNet, CPUData& data, double progress, bool test)\n    : DataWorker(convNet, data), _progress(progress), _test(test) {\n}\n\nvoid TrainingWorker::_run() {\n    _convNet->setTrainingProgress(_progress);\n    Cost& batchCost = *new Cost();\n    int numMinibatches = _dp->getNumMinibatches();\n    for (int i = 0; i < numMinibatches; i++) {\n        for (int p = 0; p < _convNet->getNumPasses(); p++) {\n            _convNet->fprop(i, p, _test ? PASS_TEST : PASS_TRAIN);\n            _convNet->getCost(batchCost);\n\n            if (!_test) {\n                _convNet->bprop(p, PASS_TRAIN);\n                _convNet->updateWeights(p);\n            }\n        }\n    }\n    _convNet->getResultQueue().enqueue(new WorkResult(WorkResult::BATCH_DONE, batchCost));\n}\n\n/*\n * ====================\n * SyncWorker\n * ====================\n */\nSyncWorker::SyncWorker(ConvNet& convNet) : Worker(convNet) {\n}\n\nbool SyncWorker::run() {\n    _convNet->copyToCPU();\n    _convNet->getResultQueue().enqueue(new WorkResult(WorkResult::SYNC_DONE));\n    return false;\n}\n\n/*\n * ====================\n * ExitWorker\n * ====================\n */\nExitWorker::ExitWorker(ConvNet& convNet) : Worker(convNet) {\n}\n\nbool ExitWorker::run() {\n    return true;\n}\n\n/* \n * ====================\n * GradCheckWorker\n * ====================\n */\nGradCheckWorker::GradCheckWorker(ConvNet& convNet, CPUData& data) \n    : DataWorker(convNet, data) {\n}\n\nvoid GradCheckWorker::_run() {\n    _convNet->checkGradients();\n    exit(0); // eh\n}\n\n/* \n * ====================\n * MultiviewTestWorker\n * ====================\n */\nMultiviewTestWorker::MultiviewTestWorker(ConvNet& convNet, CPUData& data, int numViews, Matrix& cpuProbs, const char* logregName) \n    : DataWorker(convNet, data), _numViews(numViews), _cpuProbs(&cpuProbs), _logregName(logregName) {\n//    assert(_data->getNumCases() % _numViews == 0);\n//    assert(convNet.getNumReplicas() == 1); // For now?\n}\n\nMultiviewTestWorker::MultiviewTestWorker(ConvNet& convNet, CPUData& data, int numViews) \n    : DataWorker(convNet, data), _numViews(numViews), _cpuProbs(NULL), _logregName(\"\") {\n//    assert(_data->getNumCases() % _numViews == 0);\n}\n\nMultiviewTestWorker::~MultiviewTestWorker() {\n//    delete _cpuProbs;\n}\n\nCPUData& MultiviewTestWorker::getMinibatch(int v, int i) {\n    int numCasesPerView = _dp->getNumCases() / _numViews;\n    int miniStart = v * numCasesPerView + i * _dp->getMinibatchSize();\n    int miniEnd = v * numCasesPerView + min(numCasesPerView, (i + 1) * _dp->getMinibatchSize());\n    CPUData& mini = _dp->getDataSlice(miniStart, miniEnd);\n    return mini;\n}\n\nvoid MultiviewTestWorker::_run() {\n    int numCasesPerView = _dp->getNumCases() / _numViews;\n    int numMiniPerView = DIVUP(numCasesPerView, _dp->getMinibatchSize());\n\n    Cost& batchCost = *new Cost();\n    for (int i = 0; i < numMiniPerView; i++) {\n        for (int v = 0; v < _numViews - 1; v++) {\n            for (int p = 0; p < _convNet->getNumPasses(); p++) {\n                _convNet->fprop(getMinibatch(v, i), p, v == 0 ? PASS_MULTIVIEW_TEST_START : PASS_MULTIVIEW_TEST);\n            }\n        }\n        for (int p = 0; p < _convNet->getNumPasses(); p++) {\n            _convNet->fprop(getMinibatch(_numViews - 1, i), p, PASS_MULTIVIEW_TEST_END);\n            _convNet->getCost(batchCost);\n        }\n//        if (_cpuProbs != NULL) {\n//            LogregCostLayer& logregLayer = *dynamic_cast<LogregCostLayer*>(&_convNet->getLayer(_logregName, 0));\n//            NVMatrix::setDeviceID(logregLayer.getDeviceID());\n//            Matrix& miniProbs = _cpuProbs->sliceRows(i * _dp->getMinibatchSize(),\n//                                                     min(numCasesReal, (i + 1) * _dp->getMinibatchSize()));\n//            NVMatrix& acts = logregLayer.getProbsAccum();\n//            NVMatrix acts_T;\n//            acts.transpose(acts_T);\n//            acts_T.copyToHost(miniProbs);\n//\n//            delete &miniProbs;\n//        }\n    }\n    _convNet->getResultQueue().enqueue(new WorkResult(WorkResult::BATCH_DONE, batchCost));\n}\n\n/* \n * ====================\n * FeatureWorker\n * ====================\n */\nFeatureWorker::FeatureWorker(ConvNet& convNet, CPUData& data, MatrixV& ftrs, stringv& layerNames, bool deleteFeatures)\n    : DataWorker(convNet, data), _ftrs(&ftrs), _layerNames(&layerNames), _deleteFeatures(deleteFeatures) {\n    assert(layerNames.size() == ftrs.size());\n    for (int i = 0; i < layerNames.size(); i++) {\n        assert(ftrs[i]->getNumRows() == data.getNumCases());\n        assert(!ftrs[i]->isTrans());\n    }\n}\n\nFeatureWorker::~FeatureWorker() {\n    if (_deleteFeatures) {\n        for (int i = 0; i < _ftrs->size(); i++) {\n            delete _ftrs->at(i);\n        }\n        delete _ftrs;\n    }\n    delete _layerNames;\n}\n\nvoid FeatureWorker::_run() {\n    Cost& batchCost = *new Cost();\n    map<int,int> repStart; // Feature write start offsets within minibatch\n    for (int i = 0; i < _dp->getNumMinibatches(); i++) {\n        for (int f = 0; f < _layerNames->size(); f++) {\n            repStart[f] = 0;\n        }\n\n        for (int p = 0; p < _convNet->getNumPasses(); p++) {\n            _convNet->fprop(i, p, PASS_FEATURE_GEN);\n            _convNet->getCost(batchCost);\n            for (int f = 0; f < _layerNames->size(); f++) {\n\n                if (_convNet->getLayer(_layerNames->at(f), 0).getFwdActiveInputReplicaIdx(p) >= 0) {\n                    Matrix& miniFtrs = _ftrs->at(f)->sliceRows(i * _dp->getMinibatchSize(),\n                                                               min(_dp->getNumCases(), (i + 1) * _dp->getMinibatchSize()));\n\n                    for (int r = 0; r < _convNet->getLayer(_layerNames->at(f), 0).getNumReplicas(); ++r) {\n                        Layer& ftrLayer = _convNet->getLayer(_layerNames->at(f), r);\n                        int d = ftrLayer.getDeviceID();\n                        NVMatrix::setDeviceID(d);\n                        NVMatrix& acts = ftrLayer.getActs();\n\n                        Matrix& repMiniFtrs = miniFtrs.sliceRows(repStart[f],\n                                                                 min(int(miniFtrs.getNumRows()), repStart[f] + acts.getLeadingDim()));\n\n                        NVMatrix acts_T;\n                        acts.transpose(false);\n                        acts.transpose(acts_T);\n                        acts_T.copyToHost(repMiniFtrs);\n                        NVMatrix::syncStream(); // eh why not\n\n                        delete &repMiniFtrs;\n\n                        repStart[f] += acts.getLeadingDim();\n                    }\n                    delete &miniFtrs;\n                }\n            }\n        }\n    }\n    _convNet->getResultQueue().enqueue(new WorkResult(WorkResult::BATCH_DONE, batchCost));\n}\n\n/* \n * ====================\n * DataGradWorker\n * ====================\n */\nDataGradWorker::DataGradWorker(ConvNet& convNet, CPUData& data, Matrix& dataGrads, int dataLayerIdx, int softmaxLayerIdx)\n    : DataWorker(convNet, data), _dataGrads(&dataGrads), _dataLayerIdx(dataLayerIdx), _softmaxLayerIdx(softmaxLayerIdx) {\n//    assert(dataGrads.getNumRows() == data.getNumCases());\n//    assert(!dataGrads.isTrans());\n}\n\nDataGradWorker::~DataGradWorker() {\n//    delete _dataGrads;\n}\n\nvoid DataGradWorker::_run() {\n//    DataLayer& dataLayer = *dynamic_cast<DataLayer*>(&_convNet->getLayer(_dataLayerIdx));\n//    SoftmaxLayer& softmaxLayer = *dynamic_cast<SoftmaxLayer*>(&_convNet->getLayer(_softmaxLayerIdx));\n//    softmaxLayer.setDoLogregGrad(false);\n//    Cost& batchCost = *new Cost(0);\n//    for (int i = 0; i < _dp->getNumMinibatches(); i++) {\n//        _convNet->fprop(i, PASS_TEST);\n//        _convNet->getCost(batchCost);\n//        softmaxLayer.getActs().apply(NVMatrixOps::Log(), softmaxLayer.getActsGrad());\n//        \n//        softmaxLayer.getActsGrad().addScalar(1);\n//        softmaxLayer.getActsGrad().scale(-1);\n//        softmaxLayer.incRcvdBInputs();\n//        softmaxLayer.bprop(PASS_TEST);\n//        \n//        Matrix& miniDataGrads = _dataGrads->sliceRows(i * _dp->getMinibatchSize(),\n//                                                      min(_dp->getNumCases(), (i + 1) * _dp->getMinibatchSize()));\n//        NVMatrix& grads = dataLayer.getActsGrad();\n//        NVMatrix grads_T;\n//        if (grads.isTrans()) {\n//            NVMatrix& soft_T = grads.getTranspose();\n//            soft_T.transpose(grads_T);\n//            delete &soft_T;\n//        } else {\n//            grads.transpose(grads_T);\n//        }\n//        grads_T.copyToHost(miniDataGrads);\n//        delete &miniDataGrads;\n//        \n//        _convNet->reset();\n//    }\n//    cudaThreadSynchronize();\n//    _convNet->getResultQueue().enqueue(new WorkResult(WorkResult::BATCH_DONE, batchCost));\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/initw.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom python_util.gpumodel import *\nimport numpy as n\nimport numpy.random as nr\n\ndef get_src(filename):\n    src = IGPUModel.load_checkpoint(filename)\n    return src['model_state']['layers']\n    \n# Initialize weight matrix by copying weight matrix of given layer\ndef makew(name, idx, shape, params):\n    src = get_src(params[0])\n    return src[name]['weights'][idx]\n    \n# Initialize bias vector by copying bias vector of given layer\ndef makeb(name, shape, params):\n    src = get_src(params[0])\n    return src[name]['biases']\n    \ndef concat(shape, src, src_layers, src_func):\n    mat = n.empty(shape, dtype=n.single, order='F')\n    start = 0\n    for s in src_layers:\n        m = src_func(src[s])\n        mat[:,start:start+m.shape[1]] = m\n        start += m.shape[1]\n    return mat\n\n# Initialize weight matrix by concatenating weight matrices of given layers\ndef makewcat(name, idx, shape, params):\n    src, src_layers = get_src(params[0]), params[1:]\n    return concat(shape, src, src_layers, lambda x: x['weights'][idx])\n    \n# Initialize bias vector by concatenating bias vectors of given layers\ndef makebcat(name, shape, params):\n    src, src_layers = get_src(params[0]), params[1:]\n    return concat(shape, src, src_layers, lambda x: x['biases'])\n\n# Initialize bias vector from tuple input\ndef makeb_vec(name, shape, params):\n    return n.array([n.single(x) for x in params], dtype=n.single).reshape((1, len(params)))\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/layer.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom math import exp\nimport sys\nimport ConfigParser as cfg\nimport os\nimport numpy as n\nimport numpy.random as nr\nfrom math import ceil, floor\nfrom collections import OrderedDict\nfrom os import linesep as NL\nfrom python_util.options import OptionsParser\nimport re\n\nclass LayerParsingError(Exception):\n    pass\n\n# A neuron that doesn't take parameters\nclass NeuronParser:\n    def __init__(self, type, func_str, uses_acts=True, uses_inputs=True):\n        self.type = type\n        self.func_str = func_str\n        self.uses_acts = uses_acts  \n        self.uses_inputs = uses_inputs\n        \n    def parse(self, type):\n        if type == self.type:\n            return {'type': self.type,\n                    'params': {},\n                    'usesActs': self.uses_acts,\n                    'usesInputs': self.uses_inputs}\n        return None\n    \n# A neuron that takes parameters\nclass ParamNeuronParser(NeuronParser):\n    neuron_regex = re.compile(r'^\\s*(\\w+)\\s*\\[\\s*(\\w+(\\s*,\\w+)*)\\s*\\]\\s*$')\n    def __init__(self, type, func_str, uses_acts=True, uses_inputs=True):\n        NeuronParser.__init__(self, type, func_str, uses_acts, uses_inputs)\n        m = self.neuron_regex.match(type)\n        self.base_type = m.group(1)\n        self.param_names = m.group(2).split(',')\n        assert len(set(self.param_names)) == len(self.param_names)\n        \n    def parse(self, type):\n        m = re.match(r'^%s\\s*\\[([\\d,\\.\\s\\-]*)\\]\\s*$' % self.base_type, type)\n        if m:\n            try:\n                param_vals = [float(v.strip()) for v in m.group(1).split(',')]\n                if len(param_vals) == len(self.param_names):\n                    return {'type': self.base_type,\n                            'params': dict(zip(self.param_names, param_vals)),\n                            'usesActs': self.uses_acts,\n                            'usesInputs': self.uses_inputs}\n            except TypeError:\n                pass\n        return None\n\nclass AbsTanhNeuronParser(ParamNeuronParser):\n    def __init__(self):\n        ParamNeuronParser.__init__(self, 'abstanh[a,b]', 'f(x) = a * |tanh(b * x)|')\n        \n    def parse(self, type):\n        dic = ParamNeuronParser.parse(self, type)\n        # Make b positive, since abs(tanh(bx)) = abs(tanh(-bx)) and the C++ code\n        # assumes b is positive.\n        if dic:\n            dic['params']['b'] = abs(dic['params']['b'])\n        return dic\n\nclass ParamParser:\n    lrs_regex = re.compile(r'^\\s*(\\w+)\\s*(?:\\[\\s*(\\w+(\\s*;\\w+)*)\\s*\\])?\\s*$')\n    param_converters = {'i': int,\n                        'f': float}\n    def __init__(self, type):\n        m = self.lrs_regex.match(type)\n        self.base_type = m.group(1)\n        param_names_with_type = m.group(2).split(';') if m.group(2) is not None else []\n        self.param_names = [p[1:] for p in param_names_with_type]\n        self.param_types = [self.param_converters[p[0]] for p in param_names_with_type]\n        self.param_regex_inner = \";\".join([('\\s*%s\\s*=\\s*[^;,\\s=]+\\s*' % p) for p in self.param_names])\n        self.regex_str = ('^%s\\s*(?:\\[(%s)\\])?\\s*$') % (self.base_type, self.param_regex_inner)\n        assert len(set(self.param_names)) == len(self.param_names)\n    \n    def parse(self, type):\n        m = re.match(self.regex_str, type, flags=re.IGNORECASE)\n        if m:\n            try:\n                param_vals = [ptype(v.split('=')[1].strip()) for ptype,v in zip(self.param_types, m.group(1).split(';'))] if m.group(1) is not None else []\n                if len(param_vals) == len(self.param_names):\n                    return {'type': self.base_type,\n                            'params': dict(zip(self.param_names, param_vals))}\n            except TypeError:\n                pass\n        return None\n\n# Subclass that throws more convnet-specific exceptions than the default\nclass MyConfigParser(cfg.SafeConfigParser):\n    def safe_get(self, section, option, f=cfg.SafeConfigParser.get, typestr=None, default=None):\n        try:\n            return f(self, section, option)\n        except cfg.NoOptionError, e:\n            if default is not None:\n                return default\n            raise LayerParsingError(\"Layer '%s': required parameter '%s' missing\" % (section, option))\n        except ValueError, e:\n            if typestr is None:\n                raise e\n            raise LayerParsingError(\"Layer '%s': parameter '%s' must be %s\" % (section, option, typestr))\n        \n    def safe_get_list(self, section, option, f=str, typestr='strings', default=None):\n        v = self.safe_get(section, option, default=default)\n        if type(v) == list:\n            return v\n        try:\n            return [f(x.strip()) for x in v.split(',')]\n        except:\n            raise LayerParsingError(\"Layer '%s': parameter '%s' must be ','-delimited list of %s\" % (section, option, typestr))\n        \n    def safe_get_int(self, section, option, default=None):\n        return self.safe_get(section, option, f=cfg.SafeConfigParser.getint, typestr='int', default=default)\n        \n    def safe_get_float(self, section, option, default=None):\n        return self.safe_get(section, option, f=cfg.SafeConfigParser.getfloat, typestr='float', default=default)\n    \n    def safe_get_bool(self, section, option, default=None):\n        return self.safe_get(section, option, f=cfg.SafeConfigParser.getboolean, typestr='bool', default=default)\n    \n    def safe_get_float_list(self, section, option, default=None):\n        return self.safe_get_list(section, option, float, typestr='floats', default=default)\n    \n    def safe_get_int_list(self, section, option, default=None):\n        return self.safe_get_list(section, option, int, typestr='ints', default=default)\n    \n    def safe_get_bool_list(self, section, option, default=None):\n        return self.safe_get_list(section, option, lambda x: x.lower() in ('true', '1'), typestr='bools', default=default)\n\n# A class that implements part of the interface of MyConfigParser\nclass FakeConfigParser(object):\n    def __init__(self, dic):\n        self.dic = dic\n\n    def safe_get(self, section, option, default=None):\n        if option in self.dic:\n            return self.dic[option]\n        return default\n    \n    def safe_get_int(self, section, option, default=None):\n        return int(self.safe_get(section, option, default))\n    \n    def safe_get_int_list(self, section, option, default=None):\n        return list(self.safe_get(section, option, default))\n\nclass LayerParser:\n    def __init__(self):\n        self.dic = {}\n        self.set_defaults()\n        \n    # Post-processing step -- this is called after all layers have been initialized\n    def optimize(self, layers):\n        self.dic['actsTarget'] = -1\n        self.dic['actsGradTarget'] = -1\n        if len(set(len(l['gpu']) for l in layers.values() if 'inputs' in l and self.dic['name'] in l['inputs'])) > 1:\n#            print set(len(l['gpu']) for l in layers.values())\n            raise LayerParsingError(\"Layer '%s': all next layers must have equal number of replicas.\" % (self.dic['name']))\n    \n    def parse_params(self, vals, parsers, param_name, human_name, num_params=1):\n        dic, name = self.dic, self.dic['name']\n        \n#        print vals\n        if len(vals) != num_params and len(vals) != 1:\n            raise LayerParsingError(\"Layer '%s': expected list of length %d for %s but got list of length %d.\"% (name, num_params, param_name, len(vals)))\n        parsed = []\n#        print vals\n        for v in vals:\n            for p in parsers:\n                parsedv = p.parse(v)\n                if parsedv: \n                    parsed += [parsedv]\n                    break\n        if len(parsed) == 1 and num_params > 1:\n            parsed = parsed * num_params\n        if len(parsed) == num_params:\n            return parsed\n#        print parsed, vals\n        raise LayerParsingError(\"Layer '%s': unable to parse %s %s=%s.\" % (name, human_name, param_name, \",\".join(vals)))\n    \n    # Add parameters from layer parameter file\n    def add_params(self, mcp):\n        pass\n#        self.dic['conserveMem'] = mcp.convnet.op.get_value('conserve_mem') if mcp.convnet is not None else 0\n    \n    def init(self, dic):\n        self.dic = dic\n        return self\n    \n    def set_defaults(self):\n        self.dic['outputs'] = 0\n        self.dic['parser'] = self\n        self.dic['requiresParams'] = False\n        # Does this layer use its own activity matrix\n        # for some purpose other than computing its output?\n        # Usually, this will only be true for layers that require their\n        # own activity matrix for gradient computations. For example, layers\n        # with logistic units must compute the gradient y * (1 - y), where y is \n        # the activity matrix.\n        # \n        # Layers that do not not use their own activity matrix should advertise\n        # this, since this will enable memory-saving matrix re-use optimizations.\n        #\n        # The default value of this property is True, for safety purposes.\n        # If a layer advertises that it does not use its own activity matrix when\n        # in fact it does, bad things will happen.\n        self.dic['usesActs'] = True\n        \n        # Does this layer use the activity matrices of its input layers\n        # for some purpose other than computing its output?\n        #\n        # Again true by default for safety\n        self.dic['usesInputs'] = True\n        \n        # Force this layer to use its own activity gradient matrix,\n        # instead of borrowing one from one of its inputs.\n        # \n        # This should be true for layers where the mapping from output\n        # gradient to input gradient is non-elementwise.\n        self.dic['forceOwnActs'] = True\n        \n        # Does this layer need the gradient at all?\n        # Should only be true for layers with parameters (weights).\n        self.dic['gradConsumer'] = False\n        \n        # The gpu indices on which this layer runs\n        self.dic['gpu'] = [-1]\n        \n    def parse(self, name, mcp, prev_layers, model=None):\n        self.prev_layers = prev_layers\n        self.dic['name'] = name\n        self.dic['type'] = mcp.safe_get(name, 'type')\n        self.dic['id'] = len(prev_layers)\n\n        return self.dic  \n\n    def verify_float_range(self, v, param_name, _min, _max):\n        self.verify_num_range(v, param_name, _min, _max, strconv=lambda x: '%.3f' % x)\n\n    def verify_num_range(self, v, param_name, _min, _max, strconv=lambda x:'%d' % x):\n        if type(v) == list:\n            for i,vv in enumerate(v):\n                self._verify_num_range(vv, param_name, _min, _max, i, strconv=strconv)\n        else:\n            self._verify_num_range(v, param_name, _min, _max, strconv=strconv)\n    \n    def _verify_num_range(self, v, param_name, _min, _max, input=-1, strconv=lambda x:'%d' % x):\n        layer_name = self.dic['name'] if input < 0 else '%s[%d]' % (self.dic['name'], input)\n        if _min is not None and _max is not None and (v < _min or v > _max):\n            raise LayerParsingError(\"Layer '%s': parameter '%s' must be in the range %s-%s\" % (layer_name, param_name, strconv(_min), strconv(_max)))\n        elif _min is not None and v < _min:\n            raise LayerParsingError(\"Layer '%s': parameter '%s' must be greater than or equal to %s\" % (layer_name, param_name,  strconv(_min)))\n        elif _max is not None and v > _max:\n            raise LayerParsingError(\"Layer '%s': parameter '%s' must be smaller than or equal to %s\" % (layer_name, param_name,  strconv(_max)))\n    \n    def verify_divisible(self, value, div, value_name, div_name=None, input_idx=0):\n        layer_name = self.dic['name'] if len(self.dic['inputs']) == 0 else '%s[%d]' % (self.dic['name'], input_idx)\n        if value % div != 0:\n            raise LayerParsingError(\"Layer '%s': parameter '%s' must be divisible by %s\" % (layer_name, value_name, str(div) if div_name is None else \"'%s'\" % div_name))\n        \n    def verify_str_in(self, value, param_name, lst, input_idx=-1):\n        lname = self.dic['name'] if input_idx == -1 else ('%s[%d]' % (self.dic['name'], input_idx))\n        if value not in lst:\n            raise LayerParsingError(\"Layer '%s': parameter '%s' must be one of %s\" % (lname, param_name, \", \".join(\"'%s'\" % s for s in lst)))\n        \n    def verify_int_in(self, value, param_name, lst):\n        if value not in lst:\n            raise LayerParsingError(\"Layer '%s': parameter '%s' must be one of %s\" % (self.dic['name'], param_name, \", \".join(\"'%d'\" % s for s in lst)))\n        \n    def verify_all_ints_in(self, values, param_name, lst):\n        if len([v for v in values if v not in lst]) > 0:\n            raise LayerParsingError(\"Layer '%s': all parameters to '%s' must be among %s\" % (self.dic['name'], param_name, \", \".join(\"'%d'\" % s for s in lst)))\n    \n    def verify_input_dims(self, dims):\n        for i,d in enumerate(dims):\n            if d is not None and self.dic['numInputs'][i] != d: # first input must be labels\n                raise LayerParsingError(\"Layer '%s': dimensionality of input %d must be %d\" % (self.dic['name'], i, d))\n\n    # This looks for neuron=x arguments in various layers, and creates\n    # separate layer definitions for them.\n    @staticmethod\n    def detach_neuron_layers(layers):\n        for name,l in layers.items():\n            if l['type'] != 'neuron' and 'neuron' in l and l['neuron']:\n                NeuronLayerParser().detach_neuron_layer(name, layers)\n                \n    @staticmethod\n    def parse_layers(layer_cfg_path, param_cfg_path, model, layers={}):\n        try:\n            if not os.path.exists(layer_cfg_path):\n                raise LayerParsingError(\"Layer definition file '%s' does not exist\" % layer_cfg_path)\n            if not os.path.exists(param_cfg_path):\n                raise LayerParsingError(\"Layer parameter file '%s' does not exist\" % param_cfg_path)\n            if len(layers) == 0:\n                mcp = MyConfigParser(dict_type=OrderedDict)\n                mcp.readfp(open(layer_cfg_path))\n                for name in mcp.sections():\n                    if not mcp.has_option(name, 'type'):\n                        raise LayerParsingError(\"Layer '%s': no type given\" % name)\n                    ltype = mcp.safe_get(name, 'type')\n                    if ltype not in layer_parsers:\n                        raise LayerParsingError(\"Layer '%s': Unknown layer type: '%s'\" % (name, ltype))\n                    layers[name] = layer_parsers[ltype]().parse(name, mcp, layers, model)\n                \n                LayerParser.detach_neuron_layers(layers)\n                for l in layers.values():\n                    l['parser'].optimize(layers)\n                    del l['parser']\n                    \n                for name,l in layers.items():\n                    if not l['type'].startswith('cost.'):\n                        found = max(name in l2['inputs'] for l2 in layers.values() if 'inputs' in l2)\n                        if not found:\n                            raise LayerParsingError(\"Layer '%s' of type '%s' is unused\" % (name, l['type']))\n            \n            mcp = MyConfigParser(dict_type=OrderedDict)\n            mcp.readfp(open(param_cfg_path))\n#            mcp.convnet = model\n            for name,l in layers.items():\n                if not mcp.has_section(name) and l['requiresParams']:\n                    raise LayerParsingError(\"Layer '%s' of type '%s' requires extra parameters, but none given in file '%s'.\" % (name, l['type'], param_cfg_path))\n                lp = layer_parsers[l['type']]().init(l)\n                lp.add_params(mcp)\n        except LayerParsingError, e:\n            print e\n            sys.exit(1)\n        return layers\n        \n    @staticmethod\n    def register_layer_parser(ltype, cls):\n        if ltype in layer_parsers:\n            raise LayerParsingError(\"Layer type '%s' already registered\" % ltype)\n        layer_parsers[ltype] = cls\n\n# Any layer that takes an input (i.e. non-data layer)\nclass LayerWithInputParser(LayerParser):\n    def __init__(self, num_inputs=-1):\n        LayerParser.__init__(self)\n        self.num_inputs = num_inputs\n        \n    def verify_num_params(self, params, auto_expand=True):\n        for param in params:\n            if len(self.dic[param]) != len(self.dic['inputs']):\n                if auto_expand and len(self.dic[param]) == 1:\n                    self.dic[param] *= len(self.dic['inputs'])\n                else:\n                    raise LayerParsingError(\"Layer '%s': %s list length does not match number of inputs\" % (self.dic['name'], param))        \n    \n    # layers: dictionary: name -> layer\n    def optimize(self, layers):\n        LayerParser.optimize(self, layers)\n        dic = self.dic\n        \n        # Check if I have an input that no one else uses.\n        #print \"Layer %s optimizing\" % dic['name']\n        if not dic['forceOwnActs']:\n            for i, inp in enumerate(dic['inputLayers']):\n                if inp['outputs'] == dic['outputs'] and sum(('inputs' in ll) and (inp['name'] in ll['inputs']) for ll in layers.itervalues()) == 1:\n                    # I can share my activity matrix with this layer\n                    # if it does not use its activity matrix, and I \n                    # do not need to remember my inputs.\n                    # TODO: a dropout layer should always be able to overwrite\n                    # its input. Make it so.\n#                    print \"Layer %s(uses inputs=%d), input %s(uses acts = %d)\" % (dic['name'], dic['usesInputs'], inp['name'], inp['usesActs'])\n                    if not inp['usesActs'] and not dic['usesInputs']:\n                        dic['actsTarget'] = i\n                        print \"Layer %s using acts from layer %s\" % (dic['name'], inp['name'])\n#                        print \"Layer '%s' sharing activity matrix with layer '%s'\" % (dic['name'], l['name'])\n                    # I can share my gradient matrix with this layer if we're on the same GPU.\n                    # This is different from the logic for actsTarget because this guy doesn't\n                    # have an actsGrad matrix on my GPU if our GPUs are different, so there's\n                    # nothing to share.\n                    if dic['gpu'] == inp['gpu']:\n                        dic['actsGradTarget'] = i\n#                    print \"Layer '%s' sharing activity gradient matrix with layer '%s'\" % (dic['name'], l['name'])\n            \n    def parse(self, name, mcp, prev_layers, model=None):\n        dic = LayerParser.parse(self, name, mcp, prev_layers, model)\n        \n        dic['inputs'] = [inp.strip() for inp in mcp.safe_get(name, 'inputs').split(',')]\n        \n        for inp in dic['inputs']:\n            if inp not in prev_layers:\n                raise LayerParsingError(\"Layer '%s': input layer '%s' not defined\" % (name, inp))\n            \n        dic['inputLayers'] = [prev_layers[inp] for inp in dic['inputs']]\n        dic['gpu'] = mcp.safe_get_int_list(name, 'gpu', default=dic['inputLayers'][0]['gpu'])\n        dic['gpus'] = \", \".join('%s' % d for d in dic['gpu'])\n        dic['numReplicas'] = len(dic['gpu'])\n        \n        if len(set(dic['gpu'])) != len(dic['gpu']):\n            raise LayerParsingError(\"Layer '%s': all replicas must run on different GPUs.\" % (name))\n        \n        for inp in dic['inputs']:\n            # Data layers do not explicitly define how many replicas they have.\n            # The number of replicas for a data layer is given by the number of replicas\n            # in the next layer(s). So we set that here.\n            inpl = prev_layers[inp]\n            if inpl['type'] == 'data':\n                inpl['numReplicas'] = dic['numReplicas']\n            if inpl['numReplicas'] % dic['numReplicas'] != 0:\n                raise LayerParsingError(\"Layer '%s': number of replicas (%d) must divide number of replicas in all input layers (input %s has %d replicas).\" % (name, dic['numReplicas'], inpl['name'], inpl['numReplicas']))\n        if len(set(inp['numReplicas'] for inp in dic['inputLayers'])) != 1:\n            raise LayerParsingError(\"Layer '%s': all input layers must have equal numbers of replicas.\" % (name))\n\n        # Need to also assert that all *next* layers have equal number of replicas but this is hard so it's done in Layer.optimize\n        for inp in dic['inputLayers']:\n            if inp['outputs'] == 0:\n                raise LayerParsingError(\"Layer '%s': input layer '%s' does not produce any output\" % (name, inp['name']))\n        dic['numInputs'] = [inp['outputs'] for inp in dic['inputLayers']]\n        \n        # Layers can declare a neuron activation function to apply to their output, as a shortcut\n        # to avoid declaring a separate neuron layer above themselves.\n        dic['neuron'] = mcp.safe_get(name, 'neuron', default=\"\")\n        if self.num_inputs > 0 and len(dic['numInputs']) != self.num_inputs:\n            raise LayerParsingError(\"Layer '%s': number of inputs must be %d\" % (name, self.num_inputs))\n        \n        if model:\n            self.verify_all_ints_in(dic['gpu'], 'gpu', range(len(model.op.get_value('gpu'))))\n        return dic\n    \n    def verify_img_size(self):\n        dic = self.dic\n        if dic['numInputs'][0] % dic['imgPixels'] != 0 or dic['imgSize'] * dic['imgSize'] != dic['imgPixels']:\n            raise LayerParsingError(\"Layer '%s': has %-d dimensional input, not interpretable as %d-channel images\" % (dic['name'], dic['numInputs'][0], dic['channels']))\n    \n    @staticmethod\n    def grad_consumers_below(dic):\n        if dic['gradConsumer']:\n            return True\n        if 'inputLayers' in dic:\n            return any(LayerWithInputParser.grad_consumers_below(l) for l in dic['inputLayers'])\n        \n    def verify_no_grads(self):\n        if LayerWithInputParser.grad_consumers_below(self.dic):\n            raise LayerParsingError(\"Layer '%s': layers of type '%s' cannot propagate gradient and must not be placed over layers with parameters.\" % (self.dic['name'], self.dic['type']))\n\nclass NailbedLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n        \n    def parse(self, name, mcp, prev_layers, model=None):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['forceOwnActs'] = False\n        dic['usesActs'] = False\n        dic['usesInputs'] = False\n        \n        dic['channels'] = mcp.safe_get_int(name, 'channels')\n        dic['stride'] = mcp.safe_get_int(name, 'stride')\n\n        self.verify_num_range(dic['channels'], 'channels', 1, None)\n        \n        # Computed values\n        dic['imgPixels'] = dic['numInputs'][0] / dic['channels']\n        dic['imgSize'] = int(n.sqrt(dic['imgPixels']))\n        dic['outputsX'] = (dic['imgSize'] + dic['stride'] - 1) / dic['stride']\n        dic['start'] = (dic['imgSize'] - dic['stride'] * (dic['outputsX'] - 1)) / 2\n        dic['outputs'] = dic['channels'] * dic['outputsX']**2\n        \n        self.verify_num_range(dic['outputsX'], 'outputsX', 0, None)\n        \n        self.verify_img_size()\n        \n        print \"Initialized bed-of-nails layer '%s' on GPUs %s, producing %dx%d %d-channel output\" % (name, dic['gpus'], dic['outputsX'], dic['outputsX'], dic['channels'])\n        return dic\n    \nclass GaussianBlurLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n        \n    def parse(self, name, mcp, prev_layers, model=None):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['forceOwnActs'] = False\n        dic['usesActs'] = False\n        dic['usesInputs'] = False\n        dic['outputs'] = dic['numInputs'][0]\n        \n        dic['channels'] = mcp.safe_get_int(name, 'channels')\n        dic['filterSize'] = mcp.safe_get_int(name, 'filterSize')\n        dic['stdev'] = mcp.safe_get_float(name, 'stdev')\n\n        self.verify_num_range(dic['channels'], 'channels', 1, None)\n        self.verify_int_in(dic['filterSize'], 'filterSize', [3, 5, 7, 9])\n        \n        # Computed values\n        dic['imgPixels'] = dic['numInputs'][0] / dic['channels']\n        dic['imgSize'] = int(n.sqrt(dic['imgPixels']))\n        dic['filter'] = n.array([exp(-(dic['filterSize']/2 - i)**2 / float(2 * dic['stdev']**2)) \n                                 for i in xrange(dic['filterSize'])], dtype=n.float32).reshape(1, dic['filterSize'])\n        dic['filter'] /= dic['filter'].sum()\n        self.verify_img_size()\n        \n        if dic['filterSize'] > dic['imgSize']:\n            raise LayerParsingError(\"Later '%s': filter size (%d) must be smaller than image size (%d).\" % (dic['name'], dic['filterSize'], dic['imgSize']))\n        \n        print \"Initialized Gaussian blur layer '%s', producing %dx%d %d-channel output\" % (name, dic['imgSize'], dic['imgSize'], dic['channels'])\n        \n        return dic\n    \nclass HorizontalReflectionLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n        \n    def parse(self, name, mcp, prev_layers, model=None):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['outputs'] = dic['numInputs'][0]\n        dic['channels'] = mcp.safe_get_int(name, 'channels')\n  \n        self.verify_num_range(dic['channels'], 'channels', 1, 3)\n\n        # Computed values\n        dic['imgPixels'] = dic['numInputs'][0] / dic['channels']\n        dic['imgSize'] = int(n.sqrt(dic['imgPixels']))\n        self.verify_img_size()\n        \n        print \"Initialized horizontal reflection layer '%s', producing %dx%d %d-channel output\" % (name, dic['imgSize'], dic['imgSize'], dic['channels'])\n        \n        return dic\n    \nclass ResizeLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n        \n    def parse(self, name, mcp, prev_layers, model=None):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['forceOwnActs'] = False\n        dic['usesActs'] = False\n        dic['usesInputs'] = False\n        \n        dic['channels'] = mcp.safe_get_int(name, 'channels')\n        dic['imgPixels'] = dic['numInputs'][0] / dic['channels']\n        dic['imgSize'] = int(n.sqrt(dic['imgPixels']))\n        \n        dic['scale'] = mcp.safe_get_float(name, 'scale')\n        dic['tgtSize'] = int(floor(dic['imgSize'] / dic['scale']))\n        dic['tgtPixels'] = dic['tgtSize']**2\n        self.verify_num_range(dic['channels'], 'channels', 1, None)\n        # Really not recommended to use this for such severe scalings\n        self.verify_float_range(dic['scale'], 'scale', 0.5, 2) \n\n        dic['outputs'] = dic['channels'] * dic['tgtPixels']\n        \n        self.verify_img_size()\n        self.verify_no_grads()\n        \n        print \"Initialized resize layer '%s', producing %dx%d %d-channel output\" % (name, dic['tgtSize'], dic['tgtSize'], dic['channels'])\n        \n        return dic\n    \nclass RandomScaleLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n        \n    def parse(self, name, mcp, prev_layers, model=None):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['forceOwnActs'] = False\n        dic['usesActs'] = False\n        dic['usesInputs'] = False\n        \n        dic['channels'] = mcp.safe_get_int(name, 'channels')\n        self.verify_num_range(dic['channels'], 'channels', 1, None)\n        \n        # Computed values\n        dic['imgPixels'] = dic['numInputs'][0] / dic['channels']\n        dic['imgSize'] = int(n.sqrt(dic['imgPixels']))\n        \n        dic['maxScale'] = mcp.safe_get_float(name, 'maxScale')\n        dic['tgtSize'] = mcp.safe_get_int(name, 'tgtSize')\n        min_size = int(floor(dic['imgSize'] / dic['maxScale']))\n        max_size = dic['imgSize'] #int(floor(dic['imgSize'] * dic['maxScale']))\n        if dic['tgtSize'] < min_size:\n            raise LayerParsingError(\"Layer '%s': target size must be greater than minimum image size after rescaling (%d)\" % (name, min_size))\n        if dic['tgtSize'] > max_size:\n            raise LayerParsingError(\"Layer '%s': target size must be smaller than maximum image size after rescaling (%d)\" % (name, max_size))\n        dic['tgtPixels'] = dic['tgtSize']**2\n        \n        self.verify_float_range(dic['maxScale'], 'maxScale', 1, 2) \n\n        dic['outputs'] = dic['channels'] * dic['tgtPixels']\n        \n        self.verify_img_size()\n        self.verify_no_grads()\n        \n        print \"Initialized random scale layer '%s', producing %dx%d %d-channel output\" % (name, dic['tgtSize'], dic['tgtSize'], dic['channels'])\n        \n        return dic\n    \nclass CropLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n        \n    def parse(self, name, mcp, prev_layers, model=None):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['forceOwnActs'] = False\n        dic['usesActs'] = False\n        dic['usesInputs'] = False\n        \n        dic['channels'] = mcp.safe_get_int(name, 'channels')\n        self.verify_num_range(dic['channels'], 'channels', 1, None)\n        dic['startX'] = mcp.safe_get_int(name, 'startX')\n        dic['startY'] = mcp.safe_get_int(name, 'startY', default=dic['startX'])\n        dic['sizeX'] = mcp.safe_get_int(name, 'sizeX')\n        \n        # Computed values\n        dic['imgPixels'] = dic['numInputs'][0] / dic['channels']\n        dic['imgSize'] = int(n.sqrt(dic['imgPixels']))\n     \n        dic['outputs'] = dic['channels'] * (dic['sizeX']**2)\n        \n        self.verify_num_range(dic['startX'], 'startX', 0, dic['imgSize']-1)\n        self.verify_num_range(dic['sizeX'], 'sizeX', 1, dic['imgSize'])\n        self.verify_num_range(dic['startY'], 'startY', 0, dic['imgSize']-1)\n        self.verify_img_size()\n        self.verify_no_grads()\n        \n        if dic['startX'] + dic['sizeX'] > dic['imgSize']:\n            raise LayerParsingError(\"Layer '%s': startX (%d) + sizeX (%d) > imgSize (%d)\" % (name, dic['startX'], dic['sizeX'], dic['imgSize']))\n        \n        print \"Initialized cropping layer '%s', producing %dx%d %d-channel output\" % (name, dic['sizeX'], dic['sizeX'], dic['channels'])\n        \n        return dic\n    \nclass ColorTransformLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n    \n    def parse(self, name, mcp, prev_layers, model=None):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['forceOwnActs'] = False\n        dic['usesActs'] = False\n        dic['usesInputs'] = False\n\n        # Computed values\n        dic['imgPixels'] = dic['numInputs'][0] / 3\n        dic['imgSize'] = int(n.sqrt(dic['imgPixels']))\n        dic['channels'] = 3\n        dic['outputs'] = dic['numInputs'][0]\n        \n        self.verify_img_size()\n        self.verify_no_grads()\n        \n        return dic\n    \nclass RGBToYUVLayerParser(ColorTransformLayerParser):\n    def __init__(self):\n        ColorTransformLayerParser.__init__(self)\n        \n    def parse(self, name, mcp, prev_layers, model=None):\n        dic = ColorTransformLayerParser.parse(self, name, mcp, prev_layers, model)\n        print \"Initialized RGB --> YUV layer '%s', producing %dx%d %d-channel output\" % (name, dic['imgSize'], dic['imgSize'], dic['channels'])\n        return dic\n    \nclass RGBToLABLayerParser(ColorTransformLayerParser):\n    def __init__(self):\n        ColorTransformLayerParser.__init__(self)\n        \n    def parse(self, name, mcp, prev_layers, model=None):\n        dic = ColorTransformLayerParser.parse(self, name, mcp, prev_layers, model)\n        dic['center'] = mcp.safe_get_bool(name, 'center', default=False)\n        print \"Initialized RGB --> LAB layer '%s', producing %dx%d %d-channel output\" % (name, dic['imgSize'], dic['imgSize'], dic['channels'])\n        return dic\n\nclass NeuronLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n    \n    @staticmethod\n    def get_unused_layer_name(layers, wish):\n        if wish not in layers:\n            return wish\n        for i in xrange(1, 100):\n            name = '%s.%d' % (wish, i)\n            if name not in layers:\n                return name\n        raise LayerParsingError(\"This is insane.\")\n    \n    def parse_neuron(self, neuron_str):\n        for n in neuron_parsers:\n            p = n.parse(neuron_str)\n            if p: # Successfully parsed neuron, return it\n                self.dic['neuron'] = p\n                self.dic['usesActs'] = self.dic['neuron']['usesActs']\n                self.dic['usesInputs'] = self.dic['neuron']['usesInputs']\n                \n                return\n        # Could not parse neuron\n        # Print available neuron types\n        colnames = ['Neuron type', 'Function']\n        m = max(len(colnames[0]), OptionsParser._longest_value(neuron_parsers, key=lambda x:x.type)) + 2\n        ntypes = [OptionsParser._bold(colnames[0].ljust(m))] + [n.type.ljust(m) for n in neuron_parsers]\n        fnames = [OptionsParser._bold(colnames[1])] + [n.func_str for n in neuron_parsers]\n        usage_lines = NL.join(ntype + fname for ntype,fname in zip(ntypes, fnames))\n        \n        raise LayerParsingError(\"Layer '%s': unable to parse neuron type '%s'. Valid neuron types: %sWhere neurons have parameters, they must be floats.\" % (self.dic['name'], neuron_str, NL + usage_lines + NL))\n    \n    def detach_neuron_layer(self, src_name, layers):\n        dic = self.dic\n#        self.set_defaults()\n        dic['name'] = NeuronLayerParser.get_unused_layer_name(layers, '%s_neuron' % src_name)\n        dic['type'] = 'neuron'\n        dic['inputs'] = src_name\n        dic['neuron'] = layers[src_name]['neuron']\n        dic['gpu'] = layers[src_name]['gpu']\n        \n        # Yes it's not entirely correct to pass all of layers as prev_layers, but it's harmless\n        dic = self.parse(dic['name'], FakeConfigParser(dic), layers)\n        dic['src_layer'] = src_name\n        \n        # Link upper layers to this new one\n        for l in layers.values():\n            if 'inputs' in l:\n                l['inputs'] = [inp if inp != src_name else dic['name'] for inp in l['inputs']]\n                l['inputLayers'] = [inp if inp['name'] != src_name else dic for inp in l['inputLayers']]\n        layers[dic['name']] = dic\n    \n    def parse(self, name, mcp, prev_layers, model=None):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['outputs'] = dic['numInputs'][0]\n        self.parse_neuron(dic['neuron'])\n        dic['forceOwnActs'] = False\n        print \"Initialized neuron layer '%s' on GPUs %s, producing %d outputs\" % (name, dic['gpus'], dic['outputs'])\n        return dic\n\nclass EltwiseSumLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self)\n    \n    def add_params(self, mcp):\n        LayerWithInputParser.add_params(self, mcp)\n        dic, name = self.dic, self.dic['name']\n        dic['coeffs'] = mcp.safe_get_float_list(name, 'coeffs', default=[1.0] * len(dic['inputs']))\n    \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        \n        if len(set(dic['numInputs'])) != 1:\n            raise LayerParsingError(\"Layer '%s': all inputs must have the same dimensionality. Got dimensionalities: %s\" % (name, \", \".join(str(s) for s in dic['numInputs'])))\n        dic['outputs'] = dic['numInputs'][0]\n        dic['usesInputs'] = False\n        dic['usesActs'] = False\n        dic['forceOwnActs'] = False\n        dic['requiresParams'] = True        \n        \n        print \"Initialized elementwise sum layer '%s' on GPUs %s, producing %d outputs\" % (name, dic['gpus'], dic['outputs'])\n        return dic\n    \nclass EltwiseMaxLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        if len(dic['inputs']) < 2:\n            raise LayerParsingError(\"Layer '%s': elementwise max layer must have at least 2 inputs, got %d.\" % (name, len(dic['inputs'])))\n        if len(set(dic['numInputs'])) != 1:\n            raise LayerParsingError(\"Layer '%s': all inputs must have the same dimensionality. Got dimensionalities: %s\" % (name, \", \".join(str(s) for s in dic['numInputs'])))\n        dic['outputs'] = dic['numInputs'][0]\n\n        print \"Initialized elementwise max layer '%s' on GPUs %s, producing %d outputs\" % (name, dic['gpus'], dic['outputs'])\n        return dic\n    \nclass SumLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        \n        dic['stride'] = mcp.safe_get_int(name, 'stride', default=1)\n        self.verify_divisible(dic['numInputs'][0], dic['stride'], 'input dimensionality', 'stride')\n        dic['outputs'] = dic['numInputs'][0] / dic['stride']\n    \n        print \"Initialized sum layer '%s' on GPUs %s, producing %d outputs\" % (name, dic['gpus'], dic['outputs'])\n        return dic\n    \nclass DropoutLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n        \n    def add_params(self, mcp):\n        LayerWithInputParser.add_params(self, mcp)\n        dic, name = self.dic, self.dic['name']\n        dic['enable'] = mcp.safe_get_bool(name, 'enable', default=True)\n        dic['keep'] = mcp.safe_get_float(name, 'keep', default=0.5)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['requiresParams'] = True\n        dic['usesInputs'] = False\n        dic['usesActs'] = False\n        dic['forceOwnActs'] = False\n        dic['outputs'] = dic['numInputs'][0]\n\n        print \"Initialized %s layer '%s' on GPUs %s, producing %d outputs\" % (dic['type'], name, dic['gpus'], dic['outputs'])\n        return dic\n    \nclass Dropout2LayerParser(DropoutLayerParser):\n    def __init__(self):\n        DropoutLayerParser.__init__(self)\n    \nclass WeightLayerParser(LayerWithInputParser):\n    LAYER_PAT = re.compile(r'^\\s*([^\\s\\[]+)(?:\\[(\\d+)\\])?\\s*$') # matches things like layername[5], etc\n    \n    def __init__(self, num_inputs=-1):\n        LayerWithInputParser.__init__(self, num_inputs=num_inputs)\n    \n    @staticmethod\n    def get_layer_name(name_str):\n        m = WeightLayerParser.LAYER_PAT.match(name_str)\n        if not m:\n            return None\n        return m.group(1), m.group(2)\n        \n    def add_params(self, mcp):\n        LayerWithInputParser.add_params(self, mcp)\n        dic, name = self.dic, self.dic['name']\n        dic['momW'] = mcp.safe_get_float_list(name, 'momW')\n        dic['momB'] = mcp.safe_get_float(name, 'momB')\n        dic['superEps'] = mcp.safe_get_float(name, 'superEps', default=0.0)\n        dic['superMom'] = mcp.safe_get_float(name, 'superMom', default=0.0)\n        dic['wc'] = mcp.safe_get_float_list(name, 'wc', default=[0.0] * len(dic['inputs']))\n        dic['wball'] = mcp.safe_get_float_list(name, 'wball', default=[0.0] * len(dic['inputs']))\n        self.verify_num_params(['momW', 'wc', 'wball'])\n#        dic['wballNormed'] = [wball * nweights for wball,nweights in zip(dic['wball'], dic['weightsPerFilter'])]\n        dic['wballNormed'] = dic['wball']\n        \n        # Convert from old-style 0.001,0.02 hyperparam specification to new-stye\n        # const[base=0.001],const[base=0.02] and so forth\n        def convert_scalars_to_schedules(scalars):\n            parts = scalars.split(',')\n            for i,p in enumerate(parts):\n                p = p.strip()\n                if re.match('(?:\\d*\\.)?\\d+$', p):\n                    parts[i] = 'const[base=%s]' % p\n            return parts\n            \n        dic['epsW'] = self.parse_params(convert_scalars_to_schedules(mcp.safe_get(name, 'epsW')), lrs_parsers, 'epsW', 'learning rate schedule', num_params=len(dic['inputs']))\n        dic['epsB'] = self.parse_params(convert_scalars_to_schedules(mcp.safe_get(name, 'epsB')), lrs_parsers, 'epsB', 'learning rate schedule', num_params=1)[0]\n        \n        dic['updatePeriod'] = mcp.safe_get_int(name, 'updatePeriod', default=0) # 0 means update as often as possible\n        # TODO: assert that updatePeriod is a multiple of active pass period, which is unknown here.\n        # the assert has to go in some post-processing step..\n        dic['gradConsumer'] = dic['epsB']['params']['base'] > 0 or any(w['params']['base'] > 0 for w in dic['epsW'])\n\n    @staticmethod\n    def unshare_weights(layer, layers, matrix_idx=None):\n        def unshare(layer, layers, indices):\n            for i in indices:\n                if layer['weightSourceLayers'][i] >= 0:\n                    src_matrix_idx = layer['weightSourceMatrixIndices'][i]\n                    layer['weightSourceLayers'][i] = \"\"\n                    layer['weightSourceMatrixIndices'][i] = -1\n                    layer['weights'][i] = layer['weights'][i].copy()\n                    layer['weightsInc'][i] = n.zeros_like(layer['weights'][i])\n                    print \"Unshared weight matrix %s[%d] from %s[%d].\" % (layer['name'], i, layer['weightSourceLayers'][i], src_matrix_idx)\n                else:\n                    print \"Weight matrix %s[%d] already unshared.\" % (layer['name'], i)\n        if 'weightSourceLayers' in layer:\n            unshare(layer, layers, range(len(layer['inputs'])) if matrix_idx is None else [matrix_idx])\n\n    # Load weight/biases initialization module\n    def call_init_func(self, param_name, shapes, input_idx=-1):\n        dic = self.dic\n        func_pat = re.compile('^([^\\.]+)\\.([^\\(\\)]+)\\s*(?:\\(([^,]+(?:,[^,]+)*)\\))?$')\n        m = func_pat.match(dic[param_name])\n        if not m:\n            raise LayerParsingError(\"Layer '%s': '%s' parameter must have format 'moduleName.functionName(param1,param2,...)'; got: %s.\" % (dic['name'], param_name, dic['initWFunc']))\n        module, func = m.group(1), m.group(2)\n        params = m.group(3).split(',') if m.group(3) is not None else []\n        try:\n            mod = __import__(module)\n            return getattr(mod, func)(dic['name'], input_idx, shapes, params=params) if input_idx >= 0 else getattr(mod, func)(dic['name'], shapes, params=params)\n        except (ImportError, AttributeError, TypeError), e:\n            raise LayerParsingError(\"Layer '%s': %s.\" % (dic['name'], e))\n        \n    def make_weights(self, initW, rows, cols, order='C'):\n        dic = self.dic\n        dic['weights'], dic['weightsInc'] = [], []\n        if dic['initWFunc']: # Initialize weights from user-supplied python function\n            # Initialization function is supplied in the format\n            # module.func\n            for i in xrange(len(dic['inputs'])):\n                dic['weights'] += [self.call_init_func('initWFunc', (rows[i], cols[i]), input_idx=i)]\n\n                if type(dic['weights'][i]) != n.ndarray:\n                    raise LayerParsingError(\"Layer '%s[%d]': weight initialization function %s must return numpy.ndarray object. Got: %s.\" % (dic['name'], i, dic['initWFunc'], type(dic['weights'][i])))\n                if dic['weights'][i].dtype != n.float32:\n                    raise LayerParsingError(\"Layer '%s[%d]': weight initialization function %s must weight matrices consisting of single-precision floats. Got: %s.\" % (dic['name'], i, dic['initWFunc'], dic['weights'][i].dtype))\n                if dic['weights'][i].shape != (rows[i], cols[i]):\n                    raise LayerParsingError(\"Layer '%s[%d]': weight matrix returned by weight initialization function %s has wrong shape. Should be: %s; got: %s.\" % (dic['name'], i, dic['initWFunc'], (rows[i], cols[i]), dic['weights'][i].shape))\n                # Convert to desired order\n                dic['weights'][i] = n.require(dic['weights'][i], requirements=order)\n                dic['weightsInc'] += [n.zeros_like(dic['weights'][i])]\n                print \"Layer '%s[%d]' initialized weight matrices from function %s\" % (dic['name'], i, dic['initWFunc'])\n        else:\n            for i in xrange(len(dic['inputs'])):\n                if dic['weightSourceLayers'][i] != '': # Shared weight matrix\n                    src_layer = self.prev_layers[dic['weightSourceLayers'][i]] if dic['weightSourceLayers'][i] != dic['name'] else dic\n                    dic['weights'] += [src_layer['weights'][dic['weightSourceMatrixIndices'][i]]]\n                    dic['weightsInc'] += [src_layer['weightsInc'][dic['weightSourceMatrixIndices'][i]]]\n                    if dic['weights'][i].shape != (rows[i], cols[i]):\n                        raise LayerParsingError(\"Layer '%s': weight sharing source matrix '%s' has shape %dx%d; should be %dx%d.\" \n                                                % (dic['name'], dic['weightSource'][i], dic['weights'][i].shape[0], dic['weights'][i].shape[1], rows[i], cols[i]))\n                    print \"Layer '%s' initialized weight matrix %d from %s\" % (dic['name'], i, dic['weightSource'][i])\n                else:\n                    dic['weights'] += [n.array(initW[i] * nr.randn(rows[i], cols[i]), dtype=n.single, order=order)]\n                    dic['weightsInc'] += [n.zeros_like(dic['weights'][i])]\n        \n    def make_biases(self, rows, cols, order='C'):\n        dic = self.dic\n        if dic['initBFunc']:\n            dic['biases'] = self.call_init_func('initBFunc', (rows, cols))\n            if type(dic['biases']) != n.ndarray:\n                raise LayerParsingError(\"Layer '%s': bias initialization function %s must return numpy.ndarray object. Got: %s.\" % (dic['name'], dic['initBFunc'], type(dic['biases'])))\n            if dic['biases'].dtype != n.float32:\n                raise LayerParsingError(\"Layer '%s': bias initialization function %s must return numpy.ndarray object consisting of single-precision floats. Got: %s.\" % (dic['name'], dic['initBFunc'], dic['biases'].dtype))\n            if dic['biases'].shape != (rows, cols):\n                raise LayerParsingError(\"Layer '%s': bias vector returned by bias initialization function %s has wrong shape. Should be: %s; got: %s.\" % (dic['name'], dic['initBFunc'], (rows, cols), dic['biases'].shape))\n\n            dic['biases'] = n.require(dic['biases'], requirements=order)\n            print \"Layer '%s' initialized bias vector from function %s\" % (dic['name'], dic['initBFunc'])\n        else:\n            dic['biases'] = dic['initB'] * n.ones((rows, cols), order=order, dtype=n.single)\n        dic['biasesInc'] = n.zeros_like(dic['biases'])\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['requiresParams'] = True\n        dic['gradConsumer'] = True\n        dic['usesActs'] = False\n        dic['initW'] = mcp.safe_get_float_list(name, 'initW', default=0.01)\n        dic['initB'] = mcp.safe_get_float(name, 'initB', default=0)\n        dic['initWFunc'] = mcp.safe_get(name, 'initWFunc', default=\"\")\n        dic['initBFunc'] = mcp.safe_get(name, 'initBFunc', default=\"\")\n        # Find shared weight matrices\n        \n        dic['weightSource'] = mcp.safe_get_list(name, 'weightSource', default=[''] * len(dic['inputs']))\n        self.verify_num_params(['initW'])\n        self.verify_num_params(['weightSource'], auto_expand=False)\n        \n        dic['weightSourceLayers'] = []\n        dic['weightSourceMatrixIndices'] = []\n\n        for i, src_name in enumerate(dic['weightSource']):\n            src_layer_matrix_idx = -1\n            src_layer_name = ''\n            if src_name != '':\n                src_layer_match = WeightLayerParser.get_layer_name(src_name)\n                if src_layer_match is None:\n                    raise LayerParsingError(\"Layer '%s': unable to parse weight sharing source '%s'. Format is layer[idx] or just layer, in which case idx=0 is used.\" % (name, src_name))\n                src_layer_name = src_layer_match[0]\n                src_layer_matrix_idx = int(src_layer_match[1]) if src_layer_match[1] is not None else 0\n\n                if src_layer_name not in prev_layers and src_layer_name != name:\n                    raise LayerParsingError(\"Layer '%s': weight sharing source layer '%s' does not exist.\" % (name, src_layer_name))\n                \n#                src_layer_idx = prev_names.index(src_layer_name) if src_layer_name != name else len(prev_names)\n                src_layer = prev_layers[src_layer_name] if src_layer_name != name else dic\n                if src_layer['gpu'] != dic['gpu']:\n                    raise LayerParsingError(\"Layer '%s': weight sharing source layer '%s' runs on GPUs %s, while '%s' runs on GPUs %s.\" % (name, src_layer_name, src_layer['gpu'], name, dic['gpu']))\n                if src_layer['type'] != dic['type']:\n                    raise LayerParsingError(\"Layer '%s': weight sharing source layer '%s' is of type '%s'; should be '%s'.\" % (name, src_layer_name, src_layer['type'], dic['type']))\n                if src_layer_name != name and len(src_layer['weights']) <= src_layer_matrix_idx:\n                    raise LayerParsingError(\"Layer '%s': weight sharing source layer '%s' has %d weight matrices, but '%s[%d]' requested.\" % (name, src_layer_name, len(src_layer['weights']), src_name, src_layer_matrix_idx))\n                if src_layer_name == name and src_layer_matrix_idx >= i:\n                    raise LayerParsingError(\"Layer '%s': weight sharing source '%s[%d]' not defined yet.\" % (name, name, src_layer_matrix_idx))\n\n            dic['weightSourceLayers'] += [src_layer_name]\n            dic['weightSourceMatrixIndices'] += [src_layer_matrix_idx]\n                \n        return dic\n        \nclass FCLayerParser(WeightLayerParser):\n    def __init__(self):\n        WeightLayerParser.__init__(self)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = WeightLayerParser.parse(self, name, mcp, prev_layers, model)\n        \n        dic['outputs'] = mcp.safe_get_int(name, 'outputs')\n        dic['weightsPerFilter'] = dic['numInputs']\n        self.verify_num_range(dic['outputs'], 'outputs', 1, None)\n        self.make_weights(dic['initW'], dic['numInputs'], [dic['outputs']] * len(dic['numInputs']), order='F')\n        self.make_biases(1, dic['outputs'], order='F')\n\n        print \"Initialized fully-connected layer '%s' on GPUs %s, producing %d outputs\" % (name, dic['gpus'], dic['outputs'])\n        return dic\n    \nclass SplitFCLayerParser(WeightLayerParser):\n    def __init__(self):\n        WeightLayerParser.__init__(self)\n    \n    def parse(self, name, mcp, prev_layers, model):\n        dic = WeightLayerParser.parse(self, name, mcp, prev_layers, model)\n        dic['parts'] = mcp.safe_get_int(name, 'parts')\n        dic['outputs'] = mcp.safe_get_int(name, 'outputs') * dic['parts']\n        dic['weightsPerFilter'] = dic['numInputs']\n        self.verify_num_range(dic['parts'], 'parts', 1, None)\n        \n        self.make_weights(dic['initW'], dic['numInputs'], [dic['outputs']/dic['parts']] * len(dic['numInputs']), order='F')\n        self.make_biases(1, dic['outputs'], order='F')\n        \n        for i in xrange(len(dic['numInputs'])):\n            self.verify_divisible(dic['numInputs'][i], dic['parts'], 'numInputs', 'parts', input_idx=i)\n            \n        print \"Initialized split fully-connected layer '%s' on GPUs %s, producing %d outputs in %d parts\" % (name, dic['gpus'], dic['outputs'], dic['parts'])\n        return dic\n    \nclass LocalLayerParser(WeightLayerParser):\n    def __init__(self):\n        WeightLayerParser.__init__(self)\n        \n    # Convert convolutional layer to unshared, locally-connected layer\n    @staticmethod\n    def conv_to_local(layers, lname):\n        layer = layers[lname]\n        if layer['type'] == 'conv':\n            layer['type'] = 'local'\n            for inp,inpname in enumerate(layer['inputs']):\n                src_layer_name = layer['weightSourceLayers'][inp]\n                if src_layer_name != '':\n                    src_layer = layers[src_layer_name]\n                    src_matrix_idx = layer['weightSourceMatrixIndices'][inp]\n                    LocalLayerParser.conv_to_local(layers, src_layer_name)\n                    for w in ('weights', 'weightsInc'):\n                        layer[w][inp] = src_layer[w][src_matrix_idx]\n                else:\n                    layer['weights'][inp] = n.require(n.reshape(n.tile(n.reshape(layer['weights'][inp], (1, n.prod(layer['weights'][inp].shape))), (layer['modules'], 1)),\n                                                        (layer['modules'] * layer['filterChannels'][inp] * layer['filterPixels'][inp], layer['filters'])),\n                                                      requirements='C')\n                    layer['weightsInc'][inp] = n.zeros_like(layer['weights'][inp])\n            if layer['sharedBiases']:\n                layer['biases'] = n.require(n.repeat(layer['biases'], layer['modules'], axis=0), requirements='C')\n                layer['biasesInc'] = n.zeros_like(layer['biases'])\n            \n            print \"Converted layer '%s' from convolutional to unshared, locally-connected\" % layer['name']\n            \n            # Also call this function on any layers sharing my weights\n            for l in layers:\n                if 'weightSourceLayers' in l and lname in l['weightSourceLayers']:\n                    LocalLayerParser.conv_to_local(layers, l)\n        return layer\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = WeightLayerParser.parse(self, name, mcp, prev_layers, model)\n        dic['requiresParams'] = True\n        dic['usesActs'] = False\n        # Supplied values\n        dic['channels'] = mcp.safe_get_int_list(name, 'channels')\n        dic['padding'] = mcp.safe_get_int_list(name, 'padding', default=[0]*len(dic['inputs']))\n        dic['stride'] = mcp.safe_get_int_list(name, 'stride', default=[1]*len(dic['inputs']))\n        dic['filterSize'] = mcp.safe_get_int_list(name, 'filterSize')\n        dic['filters'] = mcp.safe_get_int_list(name, 'filters')\n        dic['groups'] = mcp.safe_get_int_list(name, 'groups', default=[1]*len(dic['inputs']))\n        dic['initW'] = mcp.safe_get_float_list(name, 'initW')\n        dic['initCFunc'] = mcp.safe_get(name, 'initCFunc', default='')\n        dic['modulesX'] = mcp.safe_get_int(name, 'modulesX', default=0)\n\n        \n        self.verify_num_params(['channels', 'padding', 'stride', 'filterSize', \\\n                                'filters', 'groups', 'initW'])\n        \n        self.verify_num_range(dic['stride'], 'stride', 1, None)\n        self.verify_num_range(dic['filterSize'],'filterSize', 1, None)  \n        self.verify_num_range(dic['padding'], 'padding', 0, None)\n        self.verify_num_range(dic['channels'], 'channels', 1, None)\n        self.verify_num_range(dic['groups'], 'groups', 1, None)\n        self.verify_num_range(dic['modulesX'], 'modulesX', 0, None)\n        for i in xrange(len(dic['filters'])):\n            self.verify_divisible(dic['filters'][i], 16, 'filters', input_idx=i)\n        \n        # Computed values\n        dic['imgPixels'] = [numInputs/channels for numInputs,channels in zip(dic['numInputs'], dic['channels'])]\n        dic['imgSize'] = [int(n.sqrt(imgPixels)) for imgPixels in dic['imgPixels']]\n        self.verify_num_range(dic['imgSize'], 'imgSize', 1, None)\n        dic['filters'] = [filters*groups for filters,groups in zip(dic['filters'], dic['groups'])]\n        dic['filterPixels'] = [filterSize**2 for filterSize in dic['filterSize']]\n        if dic['modulesX'] <= 0:\n            dic['modulesX'] = [1 + int(ceil((2*padding + imgSize - filterSize) / float(stride))) for padding,imgSize,filterSize,stride in zip(dic['padding'], dic['imgSize'], dic['filterSize'], dic['stride'])]\n        else:\n            dic['modulesX'] = [dic['modulesX']] * len(dic['inputs'])\n\n        dic['filterChannels'] = [channels/groups for channels,groups in zip(dic['channels'], dic['groups'])]\n        \n        if len(set(dic['modulesX'])) != 1 or len(set(dic['filters'])) != 1:\n            raise LayerParsingError(\"Layer '%s': all inputs must produce equally-dimensioned output. Dimensions are: %s.\" % (name, \", \".join(\"%dx%dx%d\" % (filters, modulesX, modulesX) for filters,modulesX in zip(dic['filters'], dic['modulesX']))))\n\n        dic['modulesX'] = dic['modulesX'][0]\n        dic['modules'] = dic['modulesX']**2\n        dic['filters'] = dic['filters'][0]\n        dic['outputs'] = dic['modules'] * dic['filters']\n#        dic['filterConns'] = [[]] * len(dic['inputs'])\n        for i in xrange(len(dic['inputs'])):\n            if dic['numInputs'][i] % dic['imgPixels'][i] != 0 or dic['imgSize'][i] * dic['imgSize'][i] != dic['imgPixels'][i]:\n                raise LayerParsingError(\"Layer '%s[%d]': has %-d dimensional input, not interpretable as square %d-channel images\" % (name, i, dic['numInputs'][i], dic['channels'][i]))\n            if dic['channels'][i] > 3 and dic['channels'][i] % 4 != 0:\n                raise LayerParsingError(\"Layer '%s[%d]': number of channels must be smaller than 4 or divisible by 4\" % (name, i))\n#            if dic['filterSize'][i] > totalPadding[i] + dic['imgSize'][i]:\n#                raise LayerParsingError(\"Layer '%s[%d]': filter size (%d) greater than image size + padding (%d)\" % (name, i, dic['filterSize'][i], dic['padding'][i] + dic['imgSize'][i]))\n            if -dic['padding'][i] + dic['stride'][i] * (dic['modulesX'] - 1) + dic['filterSize'][i] < dic['imgSize'][i]:\n                raise LayerParsingError(\"Layer '%s[%d]': %dx%d output map with padding=%d, stride=%d does not cover entire input image.\" % (name, i, dic['modulesX'], dic['outputsX'], dic['padding'][i], dic['stride'][i]))\n\n            if dic['groups'][i] > 1:\n                self.verify_divisible(dic['channels'][i], 4*dic['groups'][i], 'channels', '4 * groups', input_idx=i)\n            self.verify_divisible(dic['channels'][i], dic['groups'][i], 'channels', 'groups', input_idx=i)\n\n            self.verify_divisible(dic['filters'], 16*dic['groups'][i], 'filters * groups', input_idx=i)\n            \n        \n            dic['padding'][i] = -dic['padding'][i]\n#        dic['overSample'] = [groups*filterChannels/channels for groups,filterChannels,channels in zip(dic['groups'], dic['filterChannels'], dic['channels'])]\n        dic['weightsPerFilter'] = [fc * (fz**2) for fc, fz in zip(dic['filterChannels'], dic['filterSize'])]\n        \n        return dic    \n\nclass ConvLayerParser(LocalLayerParser):\n    def __init__(self):\n        LocalLayerParser.__init__(self)\n        \n    def add_params(self, mcp):\n        LocalLayerParser.add_params(self, mcp)\n        self.dic['wcNormMax'] = mcp.safe_get_float_list(self.dic['name'], 'wcNormMax', default=[0.0] * len(self.dic['inputs']))\n        self.dic['wcNormMin'] = mcp.safe_get_float_list(self.dic['name'], 'wcNormMin', default=[0.0] * len(self.dic['inputs']))\n        self.verify_num_params(['wcNormMax', 'wcNormMin'])\n        for min,max in zip(self.dic['wcNormMin'], self.dic['wcNormMax']):\n            if min > max:\n                raise LayerParsingError(\"Layer '%s': wcNormMin must be <= wcNormMax.\" % (self.dic['name']))\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LocalLayerParser.parse(self, name, mcp, prev_layers, model)\n        \n        dic['sumWidth'] = mcp.safe_get_int(name, 'sumWidth')\n        dic['sharedBiases'] = mcp.safe_get_bool(name, 'sharedBiases', default=True)\n        \n        num_biases = dic['filters'] if dic['sharedBiases'] else dic['modules']*dic['filters']\n\n        eltmult = lambda list1, list2: [l1 * l2 for l1,l2 in zip(list1, list2)]\n        self.make_weights(dic['initW'], eltmult(dic['filterPixels'], dic['filterChannels']), [dic['filters']] * len(dic['inputs']), order='C')\n        self.make_biases(num_biases, 1, order='C')\n\n        print \"Initialized convolutional layer '%s' on GPUs %s, producing %dx%d %d-channel output\" % (name, dic['gpus'], dic['modulesX'], dic['modulesX'], dic['filters'])\n        return dic    \n    \nclass LocalUnsharedLayerParser(LocalLayerParser):\n    def __init__(self):\n        LocalLayerParser.__init__(self)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LocalLayerParser.parse(self, name, mcp, prev_layers, model)\n        eltmult = lambda list1, list2: [l1 * l2 for l1,l2 in zip(list1, list2)]\n        scmult = lambda x, lst: [x * l for l in lst]\n        self.make_weights(dic['initW'], scmult(dic['modules'], eltmult(dic['filterPixels'], dic['filterChannels'])), [dic['filters']] * len(dic['inputs']), order='C')\n        self.make_biases(dic['modules'] * dic['filters'], 1, order='C')\n        \n        print \"Initialized locally-connected layer '%s' on GPUs %s, producing %dx%d %d-channel output\" % (name, dic['gpus'], dic['modulesX'], dic['modulesX'], dic['filters'])\n        return dic  \n    \nclass DataLayerParser(LayerParser):\n    def __init__(self):\n        LayerParser.__init__(self)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LayerParser.parse(self, name, mcp, prev_layers, model)\n        dic['dataIdx'] = mcp.safe_get_int(name, 'dataIdx')\n        dic['start'] = mcp.safe_get_int(name, 'start', default=0)\n        dic['end'] = mcp.safe_get_int(name, 'end', default=model.train_data_provider.get_data_dims(idx=dic['dataIdx']))\n        dic['outputs'] = dic['end'] - dic['start']\n#        dic['usesActs'] = False\n        print \"Initialized data layer '%s', producing %d outputs\" % (name, dic['outputs'])\n        return dic\n\nclass SoftmaxLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['outputs'] = dic['inputLayers'][0]['outputs']\n        print \"Initialized softmax layer '%s' on GPUs %s, producing %d outputs\" % (name, dic['gpus'], dic['outputs'])\n        return dic\n    \nclass ConcatentionLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['outputs'] = sum(l['outputs'] for l in dic['inputLayers'])\n        dic['copyOffsets'] = [sum(dic['inputLayers'][j]['outputs'] for j in xrange(i)) for i in xrange(len(dic['inputLayers']))]\n        print \"Initialized concatenation layer '%s' on GPUs %s, producing %d outputs\" % (name, dic['gpus'], dic['outputs'])\n        return dic\n    \nclass PassThroughLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self)\n        \n    # Note: this doesn't verify all the necessary constraints. Layer construction may still fail in C++ code.\n    # For example, it does not verify that every layer only has one pass-through parent. Obviously having \n    # two such parents is incoherent.\n    def parse(self, name, mcp, prev_layers, model):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n#        if len(dic['inputLayers']) == 1:\n#            raise LayerParsingError(\"Layer %s: pass-through layer must have more than one input.\" % dic['name'])\n        if len(dic['gpu']) != len(dic['inputLayers'][0]['gpu']):\n            raise LayerParsingError(\"Layer '%s': number of replicas in pass-through layer must be equivalent to number of replicas in input layers.\" % dic['name'])\n        for inp in dic['inputLayers']:\n            conflicting_layers = [l for l in prev_layers.values() if l['type'] == 'pass' and inp['name'] in l['inputs'] and len(set(dic['gpu']).intersection(set(l['gpu']))) > 0]\n            if len(conflicting_layers) > 0:\n                raise LayerParsingError(\"Layer '%s' conflicts with layer '%s'. Both pass-through layers take layer '%s' as input and operate on an overlapping set of GPUs.\" % (dic['name'], conflicting_layers[0]['name'], inp['name']))\n        dic['outputs'] = sum(l['outputs'] for l in dic['inputLayers'])\n#        dic['copyOffsets'] = [sum(dic['inputLayers'][j]['outputs'] for j in xrange(i)) for i in xrange(len(dic['inputLayers']))]\n        print \"Initialized pass-through layer '%s' on GPUs %s, producing %d outputs\" % (name, dic['gpus'], dic['outputs'])\n        return dic\n\nclass PoolLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n    \n    def add_params(self, mcp):\n        LayerWithInputParser.add_params(self, mcp)\n        dic, name = self.dic, self.dic['name']\n    \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['channels'] = mcp.safe_get_int(name, 'channels')\n        dic['sizeX'] = mcp.safe_get_int(name, 'sizeX')\n        dic['start'] = mcp.safe_get_int(name, 'start', default=0)\n        dic['stride'] = mcp.safe_get_int(name, 'stride')\n        dic['outputsX'] = mcp.safe_get_int(name, 'outputsX', default=0)\n        dic['pool'] = mcp.safe_get(name, 'pool')\n        \n        # Avg pooler does not use its acts or inputs\n        dic['usesActs'] = dic['pool'] != 'avg'\n        dic['usesInputs'] = dic['pool'] != 'avg'\n        \n        dic['imgPixels'] = dic['numInputs'][0] / dic['channels']\n        dic['imgSize'] = int(n.sqrt(dic['imgPixels']))\n        \n        if dic['pool'] == 'avg':\n            dic['sum'] = mcp.safe_get_bool(name, 'sum', default=False)\n        \n        self.verify_num_range(dic['sizeX'], 'sizeX', 1, dic['imgSize'])\n        self.verify_num_range(dic['stride'], 'stride', 1, dic['sizeX'])\n        self.verify_num_range(dic['outputsX'], 'outputsX', 0, None)\n        self.verify_num_range(dic['channels'], 'channels', 1, None)\n        \n        if LayerWithInputParser.grad_consumers_below(dic):\n            self.verify_divisible(dic['channels'], 16, 'channels')\n        self.verify_str_in(dic['pool'], 'pool', ['max', 'maxabs', 'avg'])\n        \n        self.verify_img_size()\n\n        if dic['outputsX'] <= 0:\n            dic['outputsX'] = int(ceil((dic['imgSize'] - dic['start'] - dic['sizeX']) / float(dic['stride']))) + 1;\n        dic['outputs'] = dic['outputsX']**2 * dic['channels']\n        \n        print \"Initialized %s-pooling layer '%s' on GPUs %s, producing %dx%d %d-channel output\" % (dic['pool'], name, dic['gpus'], dic['outputsX'], dic['outputsX'], dic['channels'])\n        return dic\n    \n\nclass CrossMapPoolLayerParser(LayerWithInputParser):\n    def __init__(self):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n    \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['channels'] = mcp.safe_get_int(name, 'channels')\n        dic['size'] = mcp.safe_get_int(name, 'size')\n        dic['start'] = mcp.safe_get_int(name, 'start', default=0)\n        dic['stride'] = mcp.safe_get_int(name, 'stride')\n        dic['outputChannels'] = mcp.safe_get_int(name, 'outputs', default=0)\n        dic['pool'] = mcp.safe_get(name, 'pool')\n        dic['requiresParams'] = False\n        \n        # Avg pooler does not use its acts or inputs\n        dic['usesActs'] = 'pool' != 'avg'\n        dic['usesInputs'] = 'pool' != 'avg'\n        \n        dic['imgPixels'] = dic['numInputs'][0] / dic['channels']\n        dic['imgSize'] = int(n.sqrt(dic['imgPixels']))\n        dic['outputs'] = dic['outputChannels'] * dic['imgPixels']\n        \n        self.verify_num_range(dic['size'], 'size', 1, dic['channels'])\n        self.verify_num_range(dic['stride'], 'stride', 1, dic['size'])\n        self.verify_num_range(dic['outputChannels'], 'outputChannels', 0, None)\n        self.verify_num_range(dic['channels'], 'channels', 1, None)\n        self.verify_num_range(dic['start'], 'start', None, 0)\n        \n        self.verify_str_in(dic['pool'], 'pool', ['max'])\n        self.verify_img_size()\n        \n        covered_chans = dic['start'] + (dic['outputChannels'] - 1) * dic['stride'] + dic['size']\n        if covered_chans < dic['channels']:\n            raise LayerParsingError(\"Layer '%s': cross-map pooling with start=%d, stride=%d, size=%d, outputs=%d covers only %d of %d input channels.\" % \\\n                                    (name, dic['start'], dic['stride'], dic['size'], dic['outputChannels'], covered_chans, dic['channels']))\n        \n        print \"Initialized cross-map %s-pooling layer '%s' on GPUs %s, producing %dx%d %d-channel output\" % (dic['pool'], name, dic['gpus'], dic['imgSize'], dic['imgSize'], dic['outputChannels'])\n        return dic\n    \nclass NormLayerParser(LayerWithInputParser):\n    RESPONSE_NORM = 'response'\n    CONTRAST_NORM = 'contrast'\n    CROSSMAP_RESPONSE_NORM = 'cross-map response'\n    \n    def __init__(self, norm_type):\n        LayerWithInputParser.__init__(self, num_inputs=1)\n        self.norm_type = norm_type\n        \n    def add_params(self, mcp):\n        LayerWithInputParser.add_params(self, mcp)\n        dic, name = self.dic, self.dic['name']\n        dic['scale'] = mcp.safe_get_float(name, 'scale')\n        dic['scale'] /= dic['size'] if self.norm_type == self.CROSSMAP_RESPONSE_NORM else dic['size']**2\n        dic['pow'] = mcp.safe_get_float(name, 'pow')\n        dic['minDiv'] = mcp.safe_get_float(name, 'minDiv', default=1.0)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['requiresParams'] = True\n        dic['channels'] = mcp.safe_get_int(name, 'channels')\n        dic['size'] = mcp.safe_get_int(name, 'size')\n        dic['blocked'] = mcp.safe_get_bool(name, 'blocked', default=False)\n        \n        dic['imgPixels'] = dic['numInputs'][0] / dic['channels']\n        dic['imgSize'] = int(n.sqrt(dic['imgPixels']))\n        \n        # Contrast normalization layer does not use its inputs\n        dic['usesInputs'] = self.norm_type != self.CONTRAST_NORM\n        \n        self.verify_num_range(dic['channels'], 'channels', 1, None)\n        if self.norm_type == self.CROSSMAP_RESPONSE_NORM: \n            self.verify_num_range(dic['size'], 'size', 2, dic['channels'])\n            if dic['channels'] % 16 != 0:\n                raise LayerParsingError(\"Layer '%s': number of channels must be divisible by 16 when using crossMap\" % name)\n        else:\n            self.verify_num_range(dic['size'], 'size', 1, dic['imgSize'])\n        \n        if self.norm_type != self.CROSSMAP_RESPONSE_NORM and dic['channels'] > 3 and dic['channels'] % 4 != 0:\n            raise LayerParsingError(\"Layer '%s': number of channels must be smaller than 4 or divisible by 4\" % name)\n\n        self.verify_img_size()\n\n        dic['outputs'] = dic['imgPixels'] * dic['channels']\n        print \"Initialized %s-normalization layer '%s' on GPUs %s, producing %dx%d %d-channel output\" % (self.norm_type, name, dic['gpus'], dic['imgSize'], dic['imgSize'], dic['channels'])\n        return dic\n\nclass CostParser(LayerWithInputParser):\n    def __init__(self, num_inputs=-1):\n        LayerWithInputParser.__init__(self, num_inputs=num_inputs)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = LayerWithInputParser.parse(self, name, mcp, prev_layers, model)\n        dic['requiresParams'] = True\n        # Stored as string because python can't pickle lambda functions\n        dic['outputFilter'] = 'lambda costs,num_cases: [c/num_cases for c in costs]'\n        dic['children'] = mcp.safe_get_list(name, 'children', default=[])\n        # Aggregated costs only produce outputs which are additive.\n        for c in dic['children']:\n            if c not in prev_layers:\n                raise LayerParsingError(\"Layer '%s': child cost layer '%s' not defined\" % (name, c))\n            if prev_layers[c]['type'] != dic['type']:\n                raise LayerParsingError(\"Layer '%s': child cost layer '%s' must have same type as parent\" % (name, c))\n            prev_layers[c]['aggregated'] = 1\n        dic['aggregated'] = dic['children'] != []\n        del dic['neuron']\n        return dic\n\n    def add_params(self, mcp):\n        LayerWithInputParser.add_params(self, mcp)\n        dic, name = self.dic, self.dic['name']\n        dic['coeff'] = mcp.safe_get_float(name, 'coeff')\n        dic['gradConsumer'] = dic['coeff'] > 0\n            \nclass CrossEntCostParser(CostParser):\n    def __init__(self):\n        CostParser.__init__(self, num_inputs=2)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = CostParser.parse(self, name, mcp, prev_layers, model)\n        if dic['numInputs'][0] != model.train_data_provider.get_num_classes(): # first input must be labels\n            raise LayerParsingError(\"Layer '%s': Dimensionality of first input must be equal to number of labels\" % name)\n        if dic['inputLayers'][1]['type'] != 'softmax':\n            raise LayerParsingError(\"Layer '%s': Second input must be softmax layer\" % name)\n        if dic['numInputs'][1] != model.train_data_provider.get_num_classes():\n            raise LayerParsingError(\"Layer '%s': Softmax input '%s' must produce %d outputs, because that is the number of classes in the dataset\" \\\n                                    % (name, dic['inputs'][1], model.train_data_provider.get_num_classes()))\n        \n        print \"Initialized cross-entropy cost '%s' on GPUs %s\" % (name, dic['gpus'])\n        return dic\n    \nclass LogregCostParser(CostParser):\n    def __init__(self):\n        CostParser.__init__(self, num_inputs=2)\n        \n    def add_params(self, mcp):\n        CostParser.add_params(self, mcp)\n        dic, name = self.dic, self.dic['name']\n        dic['topk'] = mcp.safe_get_int(name, 'topk', default=1)\n        if dic['topk'] > dic['numInputs'][1]:\n            raise LayerParsingError(\"Layer '%s': parameter 'topk'must not have value greater than the number of classess.\"  % (name))\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = CostParser.parse(self, name, mcp, prev_layers, model)\n        dic['requiresParams'] = True\n        if dic['numInputs'][0] != 1: # first input must be labels\n            raise LayerParsingError(\"Layer '%s': dimensionality of first input must be 1\" % name)\n        if dic['inputLayers'][1]['type'] != 'softmax':\n            raise LayerParsingError(\"Layer '%s': second input must be softmax layer\" % name)\n        if dic['numInputs'][1] != model.train_data_provider.get_num_classes():\n            raise LayerParsingError(\"Layer '%s': softmax input '%s' must produce %d outputs, because that is the number of classes in the dataset\" \\\n                                    % (name, dic['inputs'][1], model.train_data_provider.get_num_classes()))\n            \n        print \"Initialized logistic regression cost '%s' on GPUs %s\" % (name, dic['gpus'])\n        return dic\n    \nclass BinomialCrossEntCostParser(CostParser):\n    def __init__(self):\n        CostParser.__init__(self, num_inputs=2)\n        \n    def add_params(self, mcp):\n        CostParser.add_params(self, mcp)\n        self.dic['posWeight'] = mcp.safe_get_float(self.dic['name'], 'posWeight', default=1.0)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = CostParser.parse(self, name, mcp, prev_layers, model)\n\n        if dic['numInputs'][0] != dic['numInputs'][1]:\n            raise LayerParsingError(\"Layer '%s': both inputs must produce the same number of outputs\" % (name))\n\n        if 'neuron' not in dic['inputLayers'][1] or dic['inputLayers'][1]['neuron'] != 'logistic':\n            print \"WARNING: Layer '%s': input '%s' is not logistic, results may not be what you intend.\" % (dic['name'], dic['inputs'][1])\n        \n        if dic['type'] == 'cost.bce':\n            print \"Initialized binomial cross-entropy cost '%s' on GPUs %s\" % (name, dic['gpus'])\n        \n        \n        dic['computeSoftmaxErrorRate'] = True\n        return dic\n    \nclass DetectionCrossEntCostParser(BinomialCrossEntCostParser):\n    def __init__(self):\n        BinomialCrossEntCostParser.__init__(self)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = BinomialCrossEntCostParser.parse(self, name, mcp, prev_layers, model)\n        if dic['numInputs'][0] != model.train_data_provider.get_num_classes(): # first input must be labels\n            raise LayerParsingError(\"Layer '%s': Dimensionality of first input must be equal to number of labels\" % name)\n        dic['computeSoftmaxErrorRate'] = False\n        dic['outputFilter'] = 'lambda costs,num_cases: [c/num_cases for c in costs[:2]] + [(class_cost[2] / class_cost[j] if class_cost[j] > 0 else n.inf) for class_cost in [costs[2:][i*3:(i+1)*3] for i in range(len(costs[2:])/3)] for j in range(2)]'\n        dic['outputFilterFormatter'] = 'lambda self,costs: \"(crossent) %.6f, (err) %.6f, \" % (costs[0], costs[1]) + \", \".join(\"(%s) %.6f, %.6f\" % (self.train_data_provider.batch_meta[\"label_names\"][i/2-1],costs[i],costs[i+1]) for i in xrange(2, len(costs), 2))'\n        print \"Initialized detection cross-entropy cost '%s' on GPUs %s\" % (name, dic['gpus'])\n        return dic\n    \nclass SumOfSquaresCostParser(CostParser):\n    def __init__(self):\n        CostParser.__init__(self, num_inputs=1)\n        \n    def parse(self, name, mcp, prev_layers, model):\n        dic = CostParser.parse(self, name, mcp, prev_layers, model)\n        print \"Initialized sum-of-squares cost '%s' on GPUs %s\" % (name, dic['gpus'])\n        return dic\n    \n# All the layer parsers\nlayer_parsers = {'data' :           lambda : DataLayerParser(),\n                 'fc':              lambda : FCLayerParser(),\n                 'sfc':             lambda : SplitFCLayerParser(),\n                 'conv':            lambda : ConvLayerParser(),\n                 'local':           lambda : LocalUnsharedLayerParser(),\n                 'softmax':         lambda : SoftmaxLayerParser(),\n                 'eltsum':          lambda : EltwiseSumLayerParser(),\n                 'eltmax':          lambda : EltwiseMaxLayerParser(),\n                 'sum':             lambda : SumLayerParser(),\n                 'neuron':          lambda : NeuronLayerParser(),\n                 'pool':            lambda : PoolLayerParser(),\n                 'cmpool':          lambda : CrossMapPoolLayerParser(),\n                 'rnorm':           lambda : NormLayerParser(NormLayerParser.RESPONSE_NORM),\n                 'cnorm':           lambda : NormLayerParser(NormLayerParser.CONTRAST_NORM),\n                 'cmrnorm':         lambda : NormLayerParser(NormLayerParser.CROSSMAP_RESPONSE_NORM),\n                 'nailbed':         lambda : NailbedLayerParser(),\n                 'blur':            lambda : GaussianBlurLayerParser(),\n                 'href':            lambda : HorizontalReflectionLayerParser(),\n                 'resize':          lambda : ResizeLayerParser(),\n                 'rgb2yuv':         lambda : RGBToYUVLayerParser(),\n                 'rgb2lab':         lambda : RGBToLABLayerParser(),\n                 'rscale':          lambda : RandomScaleLayerParser(),\n                 'crop':            lambda : CropLayerParser(),\n                 'concat':          lambda : ConcatentionLayerParser(),\n                 'pass':            lambda : PassThroughLayerParser(),\n                 'dropout':         lambda : DropoutLayerParser(),\n                 'dropout2':        lambda : Dropout2LayerParser(),\n                 'cost.logreg':     lambda : LogregCostParser(),\n                 'cost.crossent':   lambda : CrossEntCostParser(),\n                 'cost.bce':        lambda : BinomialCrossEntCostParser(),\n                 'cost.dce':        lambda : DetectionCrossEntCostParser(),\n                 'cost.sum2':       lambda : SumOfSquaresCostParser()}\n \n# All the neuron parsers\n# This isn't a name --> parser mapping as the layer parsers above because neurons don't have fixed names.\n# A user may write tanh[0.5,0.25], etc.\nneuron_parsers = sorted([NeuronParser('ident', 'f(x) = x', uses_acts=False, uses_inputs=False),\n                         NeuronParser('logistic', 'f(x) = 1 / (1 + e^-x)', uses_acts=True, uses_inputs=False),\n                         NeuronParser('abs', 'f(x) = |x|', uses_acts=False, uses_inputs=True),\n                         NeuronParser('relu', 'f(x) = max(0, x)', uses_acts=True, uses_inputs=False),\n                         NeuronParser('nrelu', 'f(x) = max(0, x) + noise', uses_acts=True, uses_inputs=False),\n                         NeuronParser('softrelu', 'f(x) = log(1 + e^x)', uses_acts=True, uses_inputs=False),\n                         NeuronParser('square', 'f(x) = x^2', uses_acts=False, uses_inputs=True),\n                         NeuronParser('sqrt', 'f(x) = sqrt(x)', uses_acts=True, uses_inputs=False),\n                         ParamNeuronParser('log[a]', 'f(x) = log(a + x)', uses_acts=False, uses_inputs=True),\n                         ParamNeuronParser('tanh[a,b]', 'f(x) = a * tanh(b * x)', uses_acts=True, uses_inputs=False),\n                         ParamNeuronParser('brelu[a]', 'f(x) = min(a, max(0, x))', uses_acts=True, uses_inputs=False),\n                         ParamNeuronParser('linear[a,b]', 'f(x) = a * x + b', uses_acts=True, uses_inputs=False),\n                         ParamNeuronParser('drelu[a]', 'f(x) = x - a * tanh(x / a)', uses_acts=False, uses_inputs=True)],\n                        key=lambda x:x.type)\n\n# Learning rate schedules\nlrs_parsers = sorted([ParamParser('const[fbase]'),\n                      ParamParser('linear[fbase;ftgtFactor]'),\n                      ParamParser('exp[fbase;ftgtFactor]'),\n                      ParamParser('dexp[fbase;ftgtFactor;inumSteps]')])\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/layers/layer-params-cifar10-11pct.cfg",
    "content": "# 11% error on CIFAR-10 - layer parameter file\n# Methodology:\n# 1. Train on batches 1-4, use batch 5 for validation.\n# 2. After about 350 epochs, validation error no longer making improvements.\n# 3. Fold in batch 5.\n# 4. Train on batches 1-5 for about 150 more epochs, until the batch 5 error is near the errors for batches 1-4. It takes forever to actually get there but after 150 epochs it's close enough.\n# 5. Lower learning rates (epsW) by a factor of 10 to 0.0001, train for 10 more epochs.\n# 6. Lower learning rates (epsW) by another factor of 10 to 0.00001, train for 10 more epochs.\n# 7. Stop. Test on batch 6 with --test-range=6 --multiview-test=1 --logreg-name=logprob (read more about what this does here: http://code.google.com/p/cuda-convnet/wiki/TrainingNet#Training_on_image_translations )\n\n# More details about methodology: http://code.google.com/p/cuda-convnet/wiki/Methodology\n\n[conv1]\nepsW=0.001\nepsB=0.002\nmomW=0.9\nmomB=0.9\nwc=0.000\n\n[conv2]\nepsW=0.001\nepsB=0.002\nmomW=0.9\nmomB=0.9\nwc=0.000\n\n[local3]\nepsW=0.001\nepsB=0.002\nmomW=0.9\nmomB=0.9\nwc=0.004\n\n[local4]\nepsW=0.001\nepsB=0.002\nmomW=0.9\nmomB=0.9\nwc=0.004\n\n[fc10]\nepsW=0.001\nepsB=0.002\nmomW=0.9\nmomB=0.9\nwc=0.01\n\n[logprob]\ncoeff=1\n\n[rnorm1]\nscale=0.001\npow=0.75\n\n[rnorm2]\nscale=0.001\npow=0.75\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/layers/layer-params-imagenet-1gpu.cfg",
    "content": "[conv1]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0.00\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[conv2]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0.00\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[conv3]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[conv4]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[conv5]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc4096a]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc4096b]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc1000]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[logprob]\ncoeff=1\ntopk=5\n\n[dropout1]\nenable=true\n\n[dropout2]\nenable=true\n\n[rnorm1]\nscale=0.0001\npow=0.75\nminDiv=2\n\n[rnorm2]\nscale=0.0001\npow=0.75\nminDiv=2\n\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/layers/layer-params-imagenet-2gpu-data.cfg",
    "content": "[conv1]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0.00\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[conv2]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0.00\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[conv3]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[conv4]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[conv5]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc4096a]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc4096b]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc1000]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[logprob]\ncoeff=1\ntopk=5\n\n[dropout1]\nenable=true\n\n[dropout2]\nenable=true\n\n[rnorm1]\nscale=0.0001\npow=0.75\nminDiv=2\n\n[rnorm2]\nscale=0.0001\npow=0.75\nminDiv=2\n\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/layers/layer-params-imagenet-2gpu-model.cfg",
    "content": "[conv1a]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0.00\n\n\n[conv1b]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0.00\n\n\n[conv2a]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0.00\n\n\n[conv2b]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0.00\n\n\n[conv3a]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9,0.9\nmomB=0.9\nwc=0.0005,0.0005\nwball=0,0\n\n\n\n[conv3b]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9,0.9\nmomB=0.9\nwc=0.0005,0.0005\nwball=0,0\n\n\n[conv4a]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\n\n\n[conv4b]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\n\n\n[conv5a]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\n\n\n[conv5b]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\n\n\n[fc2048a]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9,0.9\nmomB=0.9\nwc=0.0005,0.0005\nwball=0,0\n\n\n[fc2048b]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9,0.9\nmomB=0.9\nwc=0.0005,0.0005\nwball=0,0\n\n\n[fc2048ba]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9,0.9\nmomB=0.9\nwc=0.0005,0.0005\nwball=0,0\n\n\n[fc2048bb]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9,0.9\nmomB=0.9\nwc=0.0005,0.0005\nwball=0,0\n\n[fc1000]\nepsW=dexp[base=0.02;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.04;tgtFactor=25;numSteps=2]\nmomW=0.9,0.9\nmomB=0.9\nwc=0.0005,0.0005\nwball=0,0\n\n\n[logprob]\ncoeff=1\ntopk=5\n\n[dropout1a]\nenable=true\nkeep=0.5\n\n[dropout2a]\nenable=true\nkeep=0.5\n\n[dropout1b]\nenable=true\nkeep=0.5\n\n[dropout2b]\nenable=true\nkeep=0.5\n\n[rnorm1a]\nscale=0.0001\npow=0.75\nminDiv=2\n\n[rnorm1b]\nscale=0.0001\npow=0.75\nminDiv=2\n\n[rnorm2a]\nscale=0.0001\npow=0.75\nminDiv=2\n\n[rnorm2b]\nscale=0.0001\npow=0.75\nminDiv=2\n\n[cnorm2a]\nscale=0.001\npow=0.75\n\n[cnorm2b]\nscale=0.001\npow=0.75\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/layers/layer-params-imagenet-4gpu-data-model.cfg",
    "content": "[conv1]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0.00\nepsW=dexp[base=0.04;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.08;tgtFactor=10;numSteps=2]\n\n[conv2]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0.00\nepsW=dexp[base=0.04;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.08;tgtFactor=10;numSteps=2]\n\n[conv3]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.04;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.08;tgtFactor=10;numSteps=2]\n\n[conv4]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.04;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.08;tgtFactor=10;numSteps=2]\n\n[conv5]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.04;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.08;tgtFactor=10;numSteps=2]\n\n[fc1024a]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc1024b]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc1024c]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc1024d]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc1024ba]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc1024bb]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc1024bc]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc1024bd]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n\n[fc1000]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.01;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.02;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[logprob]\ncoeff=1\ntopk=5\n\n[dropout1a]\nenable=true\nkeep=0.5\n\n[dropout1b]\nenable=true\nkeep=0.5\n\n[dropout1c]\nenable=true\nkeep=0.5\n\n[dropout1d]\nenable=true\nkeep=0.5\n\n[dropout2a]\nenable=true\nkeep=0.5\n\n[dropout2b]\nenable=true\nkeep=0.5\n\n[dropout2c]\nenable=true\nkeep=0.5\n\n[dropout2d]\nenable=true\nkeep=0.5\n\n[rnorm1]\nscale=0.0001\npow=0.75\nminDiv=2\n\n[rnorm2]\nscale=0.0001\npow=0.75\nminDiv=2\n\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/layers/layer-params-imagenet-4gpu-data.cfg",
    "content": "[conv1]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0.00\nepsW=dexp[base=0.04;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.08;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[conv2]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0.00\nepsW=dexp[base=0.04;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.08;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[conv3]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.04;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.08;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[conv4]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.04;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.08;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[conv5]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.04;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.08;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc4096a]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.04;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.08;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc4096b]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.04;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.08;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[fc1000]\nmomW=0.9\nmomB=0.9\nwc=0.0005\nwball=0\nepsW=dexp[base=0.04;tgtFactor=250;numSteps=4]\nepsB=dexp[base=0.08;tgtFactor=10;numSteps=2]\nupdatePeriod=1\n\n[logprob]\ncoeff=1\ntopk=5\n\n[dropout1]\nenable=true\n\n[dropout2]\nenable=true\n\n[rnorm1]\nscale=0.0001\npow=0.75\nminDiv=2\n\n[rnorm2]\nscale=0.0001\npow=0.75\nminDiv=2\n\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/layers/layers-cifar10-11pct.cfg",
    "content": "[data]\ntype=data\ndataIdx=0\n\n[labels]\ntype=data\ndataIdx=1\n\n[conv1]\ntype=conv\ninputs=data\nchannels=3\nfilters=64\npadding=2\nstride=1\nfilterSize=5\nneuron=relu\ninitW=0.0001\nsumWidth=4\nsharedBiases=1\ngpu=0\n\n[pool1]\ntype=pool\npool=max\ninputs=conv1\nstart=0\nsizeX=3\nstride=2\noutputsX=0\nchannels=64\n\n[rnorm1]\ntype=cmrnorm\ninputs=pool1\nchannels=64\nsize=9\n\n[conv2]\ntype=conv\ninputs=rnorm1\nfilters=64\npadding=2\nstride=1\nfilterSize=5\nchannels=64\nneuron=relu\ninitW=0.01\nsumWidth=2\nsharedBiases=1\n\n[rnorm2]\ntype=cmrnorm\ninputs=conv2\nchannels=64\nsize=9\n\n[pool2]\ntype=pool\npool=max\ninputs=rnorm2\nstart=0\nsizeX=3\nstride=2\noutputsX=0\nchannels=64\n\n[local3]\ntype=local\ninputs=pool2\nfilters=64\npadding=1\nstride=1\nfilterSize=3\nchannels=64\nneuron=relu\ninitW=0.04\n\n[local4]\ntype=local\ninputs=local3\nfilters=32\npadding=1\nstride=1\nfilterSize=3\nchannels=64\nneuron=relu\ninitW=0.04\n\n[fc10]\ntype=fc\noutputs=10\ninputs=local4\ninitW=0.01\n\n[probs]\ntype=softmax\ninputs=fc10\n\n[logprob]\ntype=cost.logreg\ninputs=labels,probs\ngpu=0\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/layers/layers-imagenet-1gpu.cfg",
    "content": "[data]\ntype=data\ndataIdx=0\n\n[labvec]\ntype=data\ndataIdx=1\n\n[conv1]\ntype=conv\ninputs=data\nchannels=3\nfilters=64\npadding=0\nstride=4\nfilterSize=11\ninitW=0.01\nsumWidth=4\nsharedBiases=1\ngpu=0\n\n[rnorm1]\ntype=cmrnorm\ninputs=conv1\nchannels=64\nsize=5\n\n[pool1]\ntype=pool\npool=max\ninputs=rnorm1\nsizeX=3\nstride=2\nchannels=64\nneuron=relu\n\n[conv2]\ntype=conv\ninputs=pool1\nfilters=192\npadding=2\nstride=1\nfilterSize=5\nchannels=64\ninitW=0.01\ninitB=1\nsumWidth=3\nsharedBiases=1\nneuron=relu\n\n[rnorm2]\ntype=cmrnorm\ninputs=conv2\nchannels=192\nsize=5\n\n[pool2]\ntype=pool\npool=max\ninputs=rnorm2\nsizeX=3\nstride=2\nchannels=192\n\n[conv3]\ntype=conv\ninputs=pool2\nfilters=384\npadding=1\nstride=1\nfilterSize=3\nchannels=192\ninitW=0.03\nsumWidth=3\nsharedBiases=1\nneuron=relu\n\n[conv4]\ntype=conv\ninputs=conv3\nfilters=256\npadding=1\nstride=1\nfilterSize=3\nchannels=384\nneuron=relu\ninitW=0.03\ninitB=1\nsumWidth=3\nsharedBiases=1\n\n[conv5]\ntype=conv\ninputs=conv4\nfilters=256\npadding=1\nstride=1\nfilterSize=3\nchannels=256\ninitW=0.03\ninitB=1\nsumWidth=3\n\n[pool3]\ntype=pool\npool=max\ninputs=conv5\nsizeX=3\nstride=2\nchannels=256\nneuron=relu\n\n[fc4096a]\ntype=fc\ninputs=pool3\noutputs=4096\ninitW=0.01\ninitB=1\nneuron=relu\ngpu=0\n\n[dropout1]\ntype=dropout2\ninputs=fc4096a\n\n[fc4096b]\ntype=fc\ninputs=dropout1\noutputs=4096\ninitW=0.01\ninitB=1\nneuron=relu\ngpu=0\n\n[dropout2]\ntype=dropout2\ninputs=fc4096b\n\n[fc1000]\ntype=fc\noutputs=1000\ninputs=dropout2\ninitW=0.01\ninitB=-7\ngpu=0\n\n[probs]\ntype=softmax\ninputs=fc1000\n\n[logprob]\ntype=cost.logreg\ninputs=labvec,probs\ngpu=0\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/layers/layers-imagenet-2gpu-data.cfg",
    "content": "[data]\ntype=data\ndataIdx=0\n\n[labvec]\ntype=data\ndataIdx=1\n\n[conv1]\ntype=conv\ninputs=data\nchannels=3\nfilters=64\npadding=0\nstride=4\nfilterSize=11\ninitW=0.01\nsumWidth=4\nsharedBiases=1\ngpu=0,1\n\n[rnorm1]\ntype=cmrnorm\ninputs=conv1\nchannels=64\nsize=5\n\n[pool1]\ntype=pool\npool=max\ninputs=rnorm1\nsizeX=3\nstride=2\nchannels=64\nneuron=relu\n\n[conv2]\ntype=conv\ninputs=pool1\nfilters=192\npadding=2\nstride=1\nfilterSize=5\nchannels=64\ninitW=0.01\ninitB=1\nsumWidth=3\nsharedBiases=1\nneuron=relu\n\n[rnorm2]\ntype=cmrnorm\ninputs=conv2\nchannels=192\nsize=5\n\n[pool2]\ntype=pool\npool=max\ninputs=rnorm2\nsizeX=3\nstride=2\nchannels=192\n\n[conv3]\ntype=conv\ninputs=pool2\nfilters=384\npadding=1\nstride=1\nfilterSize=3\nchannels=192\ninitW=0.03\nsumWidth=3\nsharedBiases=1\nneuron=relu\n\n[conv4]\ntype=conv\ninputs=conv3\nfilters=256\npadding=1\nstride=1\nfilterSize=3\nchannels=384\nneuron=relu\ninitW=0.03\ninitB=1\nsumWidth=3\nsharedBiases=1\n\n[conv5]\ntype=conv\ninputs=conv4\nfilters=256\npadding=1\nstride=1\nfilterSize=3\nchannels=256\ninitW=0.03\ninitB=1\nsumWidth=3\n\n[pool3]\ntype=pool\npool=max\ninputs=conv5\nsizeX=3\nstride=2\nchannels=256\nneuron=relu\n\n[fc4096a]\ntype=fc\ninputs=pool3\noutputs=4096\ninitW=0.01\ninitB=1\nneuron=relu\n\n[dropout1]\ntype=dropout2\ninputs=fc4096a\n\n[fc4096b]\ntype=fc\ninputs=dropout1\noutputs=4096\ninitW=0.01\ninitB=1\nneuron=relu\n\n[dropout2]\ntype=dropout2\ninputs=fc4096b\n\n[fc1000]\ntype=fc\noutputs=1000\ninputs=dropout2\ninitW=0.01\ninitB=-7\n\n[probs]\ntype=softmax\ninputs=fc1000\n\n[logprob]\ntype=cost.logreg\ninputs=labvec,probs\ngpu=0,1\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/layers/layers-imagenet-2gpu-model.cfg",
    "content": "[data]\ntype=data\ndataIdx=0\n\n[labels]\ntype=data\ndataIdx=1\n\n[conv1a]\ntype=conv\ninputs=data\nchannels=3\nfilters=48\npadding=0\nstride=4\nfilterSize=11\ninitW=0.01\nsumWidth=3\nsharedBiases=1\ngpu=0\n\n[conv1b]\ntype=conv\ninputs=data\nchannels=3\nfilters=48\npadding=0\nstride=4\nfilterSize=11\ninitW=0.01\nsumWidth=3\nsharedBiases=1\ngpu=1\n\n[rnorm1a]\ntype=cmrnorm\ninputs=conv1a\nchannels=48\nsize=5\n\n[rnorm1b]\ntype=cmrnorm\ninputs=conv1b\nchannels=48\nsize=5\n\n[pool1a]\ntype=pool\npool=max\ninputs=rnorm1a\nsizeX=3\nstride=2\nchannels=48\nneuron=relu\n\n[pool1b]\ntype=pool\npool=max\ninputs=rnorm1b\nsizeX=3\nstride=2\nchannels=48\nneuron=relu\n\n[conv2a]\ntype=conv\ninputs=pool1a\nfilters=128\npadding=2\nstride=1\nfilterSize=5\nchannels=48\ninitW=0.01\ninitB=1\nsumWidth=3\nsharedBiases=1\nneuron=relu\ngpu=0\n\n[conv2b]\ntype=conv\ninputs=pool1b\nfilters=128\npadding=2\nstride=1\nfilterSize=5\nchannels=48\ninitW=0.01\ninitB=1\nsumWidth=3\nsharedBiases=1\nneuron=relu\ngpu=1\n\n[rnorm2a]\ntype=cmrnorm\ninputs=conv2a\nchannels=128\nsize=5\n\n[rnorm2b]\ntype=cmrnorm\ninputs=conv2b\nchannels=128\nsize=5\n\n[cnorm2a]\ntype=rnorm\ninputs=rnorm2a\nchannels=128\nsize=5\n\n[cnorm2b]\ntype=rnorm\ninputs=rnorm2b\nchannels=128\nsize=5\n\n[pool2a]\ntype=pool\npool=max\ninputs=cnorm2a\nsizeX=3\nstride=2\nchannels=128\n\n[pool2b]\ntype=pool\npool=max\ninputs=cnorm2b\nsizeX=3\nstride=2\nchannels=128\n\n[conv3a]\ntype=conv\ninputs=pool2a,pool2b\nfilters=192,192\npadding=1,1\nstride=1,1\nfilterSize=3,3\nchannels=128,128\ninitW=0.03,0.03\nsumWidth=2\nsharedBiases=1\nneuron=relu\ngpu=0\n\n[conv3b]\ntype=conv\ninputs=pool2a,pool2b\nfilters=192,192\npadding=1,1\nstride=1,1\nfilterSize=3,3\nchannels=128,128\ninitW=0.03,0.03\nsumWidth=2\nsharedBiases=1\nneuron=relu\ngpu=1\n\n[conv4a]\ntype=conv\ninputs=conv3a\nfilters=192\npadding=1\nstride=1\nfilterSize=3\nchannels=192\nneuron=relu\ninitW=0.03\ninitB=1\nsumWidth=2\nsharedBiases=1\n\n[conv4b]\ntype=conv\ninputs=conv3b\nfilters=192\npadding=1\nstride=1\nfilterSize=3\nchannels=192\nneuron=relu\ninitW=0.03\ninitB=1\nsumWidth=2\nsharedBiases=1\n\n\n[conv5a]\ntype=conv\ninputs=conv4a\nfilters=128\npadding=1\nstride=1\nfilterSize=3\nchannels=192\ninitW=0.03\ninitB=1\nsumWidth=2\ngroups=1\nrandSparse=0\n\n[conv5b]\ntype=conv\ninputs=conv4b\nfilters=128\npadding=1\nstride=1\nfilterSize=3\nchannels=192\ninitW=0.03\ninitB=1\nsumWidth=2\ngroups=1\nrandSparse=0\n\n[pool3a]\ntype=pool\npool=max\ninputs=conv5a\nsizeX=3\nstride=2\nchannels=128\nneuron=relu\n\n[pool3b]\ntype=pool\npool=max\ninputs=conv5b\nsizeX=3\nstride=2\nchannels=128\nneuron=relu\n\n[fc2048a]\ntype=fc\ninputs=pool3a,pool3b\noutputs=2048\ninitW=0.01,0.01\ninitB=1\nneuron=relu\ngpu=0\n\n[fc2048b]\ntype=fc\ninputs=pool3a,pool3b\noutputs=2048\ninitW=0.01,0.01\ninitB=1\nneuron=relu\ngpu=1\n\n[dropout1a]\ntype=dropout\ninputs=fc2048a\n\n[dropout1b]\ntype=dropout\ninputs=fc2048b\n\n[fc2048ba]\ntype=fc\ninputs=dropout1a,dropout1b\noutputs=2048\ninitW=0.01,0.01\ninitB=1\nneuron=relu\ngpu=0\n\n[fc2048bb]\ntype=fc\ninputs=dropout1b,dropout1a\noutputs=2048\ninitW=0.01,0.01\ninitB=1\nneuron=relu\ngpu=1\n\n[dropout2a]\ntype=dropout\ninputs=fc2048ba\n\n[dropout2b]\ntype=dropout\ninputs=fc2048bb\n\n[fc1000]\ntype=fc\noutputs=1000\ninputs=dropout2a,dropout2b\ninitW=0.01,0.01\ngpu=0\n\n[probs]\ntype=softmax\ninputs=fc1000\n\n[logprob]\ntype=cost.logreg\ninputs=labels,probs\ngpu=0\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/layers/layers-imagenet-4gpu-data-model.cfg",
    "content": "[data]\ntype=data\ndataIdx=0\n\n[labvec]\ntype=data\ndataIdx=1\n\n[conv1]\ntype=conv\ninputs=data\nchannels=3\nfilters=64\npadding=0\nstride=4\nfilterSize=11\ninitW=0.01\nsumWidth=4\nsharedBiases=1\ngpu=0,1,2,3\n\n[rnorm1]\ntype=cmrnorm\ninputs=conv1\nchannels=64\nsize=5\n\n[pool1]\ntype=pool\npool=max\ninputs=rnorm1\nsizeX=3\nstride=2\nchannels=64\nneuron=relu\n\n[conv2]\ntype=conv\ninputs=pool1\nfilters=192\npadding=2\nstride=1\nfilterSize=5\nchannels=64\ninitW=0.01\ninitB=1\nsumWidth=3\nsharedBiases=1\nneuron=relu\n\n[rnorm2]\ntype=cmrnorm\ninputs=conv2\nchannels=192\nsize=5\n\n[pool2]\ntype=pool\npool=max\ninputs=rnorm2\nsizeX=3\nstride=2\nchannels=192\n\n[conv3]\ntype=conv\ninputs=pool2\nfilters=384\npadding=1\nstride=1\nfilterSize=3\nchannels=192\ninitW=0.03\nsumWidth=3\nsharedBiases=1\nneuron=relu\n\n[conv4]\ntype=conv\ninputs=conv3\nfilters=256\npadding=1\nstride=1\nfilterSize=3\nchannels=384\nneuron=relu\ninitW=0.03\ninitB=1\nsumWidth=3\nsharedBiases=1\n\n[conv5]\ntype=conv\ninputs=conv4\nfilters=256\npadding=1\nstride=1\nfilterSize=3\nchannels=256\ninitW=0.03\ninitB=1\nsumWidth=3\n\n[pool3]\ntype=pool\npool=max\ninputs=conv5\nsizeX=3\nstride=2\nchannels=256\nneuron=relu\n\n[fc1024a]\ntype=fc\ninputs=pool3\noutputs=1024\ninitW=0.01\ninitB=1\nneuron=relu\ngpu=0\n\n[fc1024b]\ntype=fc\ninputs=pool3\noutputs=1024\ninitW=0.01\ninitB=1\nneuron=relu\ngpu=1\n\n[fc1024c]\ntype=fc\ninputs=pool3\noutputs=1024\ninitW=0.01\ninitB=1\nneuron=relu\ngpu=2\n\n[fc1024d]\ntype=fc\ninputs=pool3\noutputs=1024\ninitW=0.01\ninitB=1\nneuron=relu\ngpu=3\n\n[dropout1a]\ntype=dropout2\ninputs=fc1024a\n\n[dropout1b]\ntype=dropout2\ninputs=fc1024b\n\n[dropout1c]\ntype=dropout2\ninputs=fc1024c\n\n[dropout1d]\ntype=dropout2\ninputs=fc1024d\n\n# This is like a concatenation layer\n[pass1a]\ntype=pass\ninputs=dropout1a,dropout1b,dropout1c,dropout1d\ngpu=0\n\n# This is like a concatenation layer\n[pass1b]\ntype=pass\ninputs=dropout1a,dropout1b,dropout1c,dropout1d\ngpu=1\n\n# This is like a concatenation layer\n[pass1c]\ntype=pass\ninputs=dropout1a,dropout1b,dropout1c,dropout1d\ngpu=2\n\n# This is like a concatenation layer\n[pass1d]\ntype=pass\ninputs=dropout1a,dropout1b,dropout1c,dropout1d\ngpu=3\n\n\n[fc1024ba]\ntype=fc\ninputs=pass1a\noutputs=1024\ninitW=0.01\ninitB=1\nneuron=relu\n\n[fc1024bb]\ntype=fc\ninputs=pass1b\noutputs=1024\ninitW=0.01\ninitB=1\nneuron=relu\n\n[fc1024bc]\ntype=fc\ninputs=pass1c\noutputs=1024\ninitW=0.01\ninitB=1\nneuron=relu\n\n[fc1024bd]\ntype=fc\ninputs=pass1d\noutputs=1024\ninitW=0.01\ninitB=1\nneuron=relu\n\n[dropout2a]\ntype=dropout2\ninputs=fc1024ba\n\n[dropout2b]\ntype=dropout2\ninputs=fc1024bb\n\n[dropout2c]\ntype=dropout2\ninputs=fc1024bc\n\n[dropout2d]\ntype=dropout2\ninputs=fc1024bd\n\n[pass2a]\ninputs=dropout2a,dropout2b,dropout2c,dropout2d\ntype=pass\ngpu=0\n\n[fc1000]\ntype=fc\noutputs=1000\ninputs=pass2a\ninitW=0.01\n\n[probs]\ntype=softmax\ninputs=fc1000\n\n[logprob]\ntype=cost.logreg\ninputs=labvec,probs\ngpu=0\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/layers/layers-imagenet-4gpu-data.cfg",
    "content": "[data]\ntype=data\ndataIdx=0\n\n[labvec]\ntype=data\ndataIdx=1\n\n[conv1]\ntype=conv\ninputs=data\nchannels=3\nfilters=64\npadding=0\nstride=4\nfilterSize=11\ninitW=0.01\nsumWidth=4\nsharedBiases=1\ngpu=0,1,2,3\n\n[rnorm1]\ntype=cmrnorm\ninputs=conv1\nchannels=64\nsize=5\n\n[pool1]\ntype=pool\npool=max\ninputs=rnorm1\nsizeX=3\nstride=2\nchannels=64\nneuron=relu\n\n[conv2]\ntype=conv\ninputs=pool1\nfilters=192\npadding=2\nstride=1\nfilterSize=5\nchannels=64\ninitW=0.01\ninitB=1\nsumWidth=3\nsharedBiases=1\nneuron=relu\n\n[rnorm2]\ntype=cmrnorm\ninputs=conv2\nchannels=192\nsize=5\n\n[pool2]\ntype=pool\npool=max\ninputs=rnorm2\nsizeX=3\nstride=2\nchannels=192\n\n[conv3]\ntype=conv\ninputs=pool2\nfilters=384\npadding=1\nstride=1\nfilterSize=3\nchannels=192\ninitW=0.03\nsumWidth=3\nsharedBiases=1\nneuron=relu\n\n[conv4]\ntype=conv\ninputs=conv3\nfilters=256\npadding=1\nstride=1\nfilterSize=3\nchannels=384\nneuron=relu\ninitW=0.03\ninitB=1\nsumWidth=3\nsharedBiases=1\n\n[conv5]\ntype=conv\ninputs=conv4\nfilters=256\npadding=1\nstride=1\nfilterSize=3\nchannels=256\ninitW=0.03\ninitB=1\nsumWidth=3\n\n[pool3]\ntype=pool\npool=max\ninputs=conv5\nsizeX=3\nstride=2\nchannels=256\nneuron=relu\n\n[fc4096a]\ntype=fc\ninputs=pool3\noutputs=4096\ninitW=0.01\ninitB=1\nneuron=relu\n\n[dropout1]\ntype=dropout2\ninputs=fc4096a\n\n[fc4096b]\ntype=fc\ninputs=dropout1\noutputs=4096\ninitW=0.01\ninitB=1\nneuron=relu\n\n[dropout2]\ntype=dropout2\ninputs=fc4096b\n\n[fc1000]\ntype=fc\noutputs=1000\ninputs=dropout2\ninitW=0.01\ninitB=-7\n\n[probs]\ntype=softmax\ninputs=fc1000\n\n[logprob]\ntype=cost.logreg\ninputs=labvec,probs\ngpu=0,1,2,3\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/make-data/make-data.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#################################################################################\n\n\n# This script makes batches suitable for training from raw ILSVRC 2012 tar files.\n\nimport tarfile\nfrom StringIO import StringIO\nfrom random import shuffle\nimport sys\nfrom time import time\nfrom pyext._MakeDataPyExt import resizeJPEG\nimport itertools\nimport os\nimport cPickle\nimport scipy.io\nimport math\nimport argparse as argp\n\n# Set this to True to crop images to square. In this case each image will be\n# resized such that its shortest edge is OUTPUT_IMAGE_SIZE pixels, and then the\n# center OUTPUT_IMAGE_SIZE x OUTPUT_IMAGE_SIZE patch will be extracted.\n#\n# Set this to False to preserve image borders. In this case each image will be\n# resized such that its shortest edge is OUTPUT_IMAGE_SIZE pixels. This was\n# demonstrated to be superior by Andrew Howard in his very nice paper:\n# http://arxiv.org/abs/1312.5402\nCROP_TO_SQUARE          = True\nOUTPUT_IMAGE_SIZE       = 256\n\n# Number of threads to use for JPEG decompression and image resizing.\nNUM_WORKER_THREADS      = 8\n\n# Don't worry about these.\nOUTPUT_BATCH_SIZE = 3072\nOUTPUT_SUB_BATCH_SIZE = 1024\n\ndef pickle(filename, data):\n    with open(filename, \"w\") as fo:\n        cPickle.dump(data, fo, protocol=cPickle.HIGHEST_PROTOCOL)\n\ndef unpickle(filename):\n    fo = open(filename, 'r')\n    contents = cPickle.load(fo)\n    fo.close()\n    return contents\n\ndef partition_list(l, partition_size):\n    divup = lambda a,b: (a + b - 1) / b\n    return [l[i*partition_size:(i+1)*partition_size] for i in xrange(divup(len(l),partition_size))]\n\ndef open_tar(path, name):\n    if not os.path.exists(path):\n        print \"ILSVRC 2012 %s not found at %s. Make sure to set ILSVRC_SRC_DIR correctly at the top of this file (%s).\" % (name, path, sys.argv[0])\n        sys.exit(1)\n    return tarfile.open(path)\n\ndef makedir(path):\n    if not os.path.exists(path):\n        os.makedirs(path)\n\ndef parse_devkit_meta(ILSVRC_DEVKIT_TAR):\n    tf = open_tar(ILSVRC_DEVKIT_TAR, 'devkit tar')\n    fmeta = tf.extractfile(tf.getmember('ILSVRC2012_devkit_t12/data/meta.mat'))\n    meta_mat = scipy.io.loadmat(StringIO(fmeta.read()))\n    labels_dic = dict((m[0][1][0], m[0][0][0][0]-1) for m in meta_mat['synsets'] if m[0][0][0][0] >= 1 and m[0][0][0][0] <= 1000)\n    label_names_dic = dict((m[0][1][0], m[0][2][0]) for m in meta_mat['synsets'] if m[0][0][0][0] >= 1 and m[0][0][0][0] <= 1000)\n    label_names = [tup[1] for tup in sorted([(v,label_names_dic[k]) for k,v in labels_dic.items()], key=lambda x:x[0])]\n\n    fval_ground_truth = tf.extractfile(tf.getmember('ILSVRC2012_devkit_t12/data/ILSVRC2012_validation_ground_truth.txt'))\n    validation_ground_truth = [[int(line.strip()) - 1] for line in fval_ground_truth.readlines()]\n    tf.close()\n    return labels_dic, label_names, validation_ground_truth\n\ndef write_batches(target_dir, name, start_batch_num, labels, jpeg_files):\n    jpeg_files = partition_list(jpeg_files, OUTPUT_BATCH_SIZE)\n    labels = partition_list(labels, OUTPUT_BATCH_SIZE)\n    makedir(target_dir)\n    print \"Writing %s batches...\" % name\n    for i,(labels_batch, jpeg_file_batch) in enumerate(zip(labels, jpeg_files)):\n        t = time()\n        jpeg_strings = list(itertools.chain.from_iterable(resizeJPEG([jpeg.read() for jpeg in jpeg_file_batch], OUTPUT_IMAGE_SIZE, NUM_WORKER_THREADS, CROP_TO_SQUARE)))\n        batch_path = os.path.join(target_dir, 'data_batch_%d' % (start_batch_num + i))\n        makedir(batch_path)\n        for j in xrange(0, len(labels_batch), OUTPUT_SUB_BATCH_SIZE):\n            pickle(os.path.join(batch_path, 'data_batch_%d.%d' % (start_batch_num + i, j/OUTPUT_SUB_BATCH_SIZE)), \n                   {'data': jpeg_strings[j:j+OUTPUT_SUB_BATCH_SIZE],\n                    'labels': labels_batch[j:j+OUTPUT_SUB_BATCH_SIZE]})\n        print \"Wrote %s (%s batch %d of %d) (%.2f sec)\" % (batch_path, name, i+1, len(jpeg_files), time() - t)\n    return i + 1\n\nif __name__ == \"__main__\":\n    parser = argp.ArgumentParser()\n    parser.add_argument('--src-dir', help='Directory containing ILSVRC2012_img_train.tar, ILSVRC2012_img_val.tar, and ILSVRC2012_devkit_t12.tar.gz', required=True)\n    parser.add_argument('--tgt-dir', help='Directory to output ILSVRC 2012 batches suitable for cuda-convnet to train on.', required=True)\n    args = parser.parse_args()\n    \n    print \"CROP_TO_SQUARE: %s\" % CROP_TO_SQUARE\n    print \"OUTPUT_IMAGE_SIZE: %s\" % OUTPUT_IMAGE_SIZE\n    print \"NUM_WORKER_THREADS: %s\" % NUM_WORKER_THREADS\n\n    ILSVRC_TRAIN_TAR = os.path.join(args.src_dir, 'ILSVRC2012_img_train.tar')\n    ILSVRC_VALIDATION_TAR = os.path.join(args.src_dir, 'ILSVRC2012_img_val.tar')\n    ILSVRC_DEVKIT_TAR = os.path.join(args.src_dir, 'ILSVRC2012_devkit_t12.tar.gz')\n\n    assert OUTPUT_BATCH_SIZE % OUTPUT_SUB_BATCH_SIZE == 0\n    labels_dic, label_names, validation_labels = parse_devkit_meta(ILSVRC_DEVKIT_TAR)\n\n    with open_tar(ILSVRC_TRAIN_TAR, 'training tar') as tf:\n        synsets = tf.getmembers()\n        synset_tars = [tarfile.open(fileobj=tf.extractfile(s)) for s in synsets]\n        print \"Loaded synset tars.\"\n        print \"Building training set image list (this can take 10-20 minutes)...\"\n        sys.stdout.flush()\n    \n        train_jpeg_files = []\n        for i,st in enumerate(synset_tars):\n            if i % 100 == 0:\n                print \"%d%% ...\" % int(round(100.0 * float(i) / len(synset_tars))),\n                sys.stdout.flush()\n            train_jpeg_files += [st.extractfile(m) for m in st.getmembers()]\n            st.close()\n            \n        shuffle(train_jpeg_files)\n        train_labels = [[labels_dic[jpeg.name[:9]]] for jpeg in train_jpeg_files]\n        print \"done\"\n    \n        # Write training batches\n        i = write_batches(args.tgt_dir, 'training', 0, train_labels, train_jpeg_files)\n    \n    # Write validation batches\n    val_batch_start = int(math.ceil((i / 1000.0))) * 1000\n    with open_tar(ILSVRC_VALIDATION_TAR, 'validation tar') as tf:\n        validation_jpeg_files = sorted([tf.extractfile(m) for m in tf.getmembers()], key=lambda x:x.name)\n        write_batches(args.tgt_dir, 'validation', val_batch_start, validation_labels, validation_jpeg_files)\n    \n    # Write meta file\n    meta = unpickle('input_meta')\n    meta_file = os.path.join(args.tgt_dir, 'batches.meta')\n    meta.update({'batch_size': OUTPUT_BATCH_SIZE,\n                 'num_vis': OUTPUT_IMAGE_SIZE**2 * 3,\n                 'label_names': label_names})\n    pickle(meta_file, meta)\n    print \"Wrote %s\" % meta_file\n    print \"All done! ILSVRC 2012 batches are in %s\" % args.tgt_dir\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/make-data/pyext/Makefile",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nINCLUDES := -I./include\nCOMMONFLAGS :=\nCC_ARGS := \n\nifndef debug\n\tCC_ARGS += -O3\nendif\nCC=g++\n\nOUT_DIR=./bin/$(OUT_SUFFIX)\n\nPYTHON_VERSION=$(shell python -V 2>&1 | cut -d ' ' -f 2 | cut -d '.' -f 1,2)\nLINK_LIBS := -L$(CUDA_INSTALL_PATH)/lib64 `pkg-config --libs python` `pkg-config --libs opencv` -lpthread\n\nINCLUDES += -I$(PYTHON_INCLUDE_PATH) \nOUT_FILE=_MakeDataPyExt.so\n\nall: dir classes $(OUT_FILE)\n\ndir:\n\tmkdir -p $(OUT_DIR)/src\n\nSOURCES = $(shell echo src/*.cpp)\nCLASSES = $(SOURCES:.cpp=.o)\n\nclasses: $(CLASSES)\n\n%.o: %.cpp\n\t$(CC) $(CC_ARGS) -c -fPIC $(BUILD_ARGS) $(COMMONFLAGS) $(INCLUDES) $< -o $(OUT_DIR)/$*.o\n\n$(OUT_FILE): classes\n\tcd $(OUT_DIR) && $(CC) $(CC_ARGS) $(BUILD_ARGS) $(COMMONFLAGS) -shared -Wl,-no-undefined -o $(OUT_FILE) $(CLASSES) $(LINK_LIBS)\n\tln -sf $(OUT_DIR)/$(OUT_FILE) .\n\nclean:\n\trm -rf $(OUT_DIR)/*\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/make-data/pyext/__init__.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License."
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/make-data/pyext/include/pyext.h",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef INCLUDE_PYEXT_H_\n#define INCLUDE_PYEXT_H_\n\n#include <stdio.h>\n//#include <jpeglib.h>\n#include <opencv2/opencv.hpp>\n#include <Python.h>\n#include \"../../../util/include/thread.h\"\n\n#define JPEG_QUALITY      95\n\n#ifndef DIVUP\n#define DIVUP(a,b) (((a) + (b) - 1) / (b))\n#endif\n\nextern \"C\" {\n    void init_MakeDataPyExt();\n}\nPyObject* resizeJPEG(PyObject *self, PyObject *args);\n\nclass DecoderThread : public Thread {\n protected:\n    PyObject* _py_list_src;\n    PyObject* _py_list_tgt;\n    int _start_img, _end_img;\n    int _target_size;\n    bool _crop_to_square;\n\n    cv::Mat _resized_mat_buffer;\n    std::vector<uchar> _output_jpeg_buffer;\n    std::vector<int> _encode_params;\n\n    void* run();\n    void makeJPEG(int idx);\n\n public:\n    DecoderThread(PyObject* py_list_src, int start_img, int end_img, int target_size, bool crop_to_square);\n    virtual ~DecoderThread();\n    PyObject* getTargetList();\n};\n\n\n#endif  // INCLUDE_PYEXT_H_\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/make-data/pyext/src/pyext.cpp",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"../include/pyext.h\"\n\nusing namespace std;\n\nstatic PyMethodDef _MakeDataPyExtMethods[] = {{ \"resizeJPEG\", resizeJPEG, METH_VARARGS },\n                                              { NULL, NULL }\n};\n\nvoid init_MakeDataPyExt() {\n    (void) Py_InitModule(\"_MakeDataPyExt\", _MakeDataPyExtMethods);\n}\n\nPyObject* resizeJPEG(PyObject *self, PyObject *args) {\n\n    PyListObject* pyListSrc;\n    int tgtImgSize, numThreads;\n    int cropToSquare;\n\n    if (!PyArg_ParseTuple(args, \"O!iii\",\n                          &PyList_Type, &pyListSrc,\n                          &tgtImgSize,\n                          &numThreads,\n                          &cropToSquare)) {\n        return NULL;\n    }\n\n    DecoderThread* threads[numThreads];\n    int num_imgs = PyList_GET_SIZE(pyListSrc);\n    int num_imgs_per_thread = DIVUP(num_imgs, numThreads);\n    for (int t = 0; t < numThreads; ++t) {\n        int start_img = t * num_imgs_per_thread;\n        int end_img = min(num_imgs, (t+1) * num_imgs_per_thread);\n\n        threads[t] = new DecoderThread((PyObject*)pyListSrc, start_img, end_img, tgtImgSize, cropToSquare);\n        threads[t]->start();\n    }\n\n    PyObject* pyListTgt = PyList_New(0);\n    for (int t = 0; t < numThreads; ++t) {\n        threads[t]->join();\n        PyList_Append(pyListTgt, threads[t]->getTargetList());\n        delete threads[t]; // the thread's list too\n    }\n\n    return pyListTgt;\n}\n\nDecoderThread::DecoderThread(PyObject* py_list_src, int start_img, int end_img, int target_size, bool crop_to_square)\n: Thread(true), _py_list_src(py_list_src), _start_img(start_img), _end_img(end_img), _target_size(target_size), _crop_to_square(crop_to_square) {\n\n    _encode_params.push_back(CV_IMWRITE_JPEG_QUALITY);\n    _encode_params.push_back(JPEG_QUALITY);\n    _py_list_tgt = PyList_New(0);\n}\n\nDecoderThread::~DecoderThread(){\n    Py_DECREF(_py_list_tgt);\n}\n\nvoid* DecoderThread::run() {\n    for (int i = _start_img; i < _end_img; ++i) {\n        makeJPEG(i);\n    }\n    return NULL;\n}\n\nPyObject* DecoderThread::getTargetList() {\n    return _py_list_tgt;\n}\n\nvoid DecoderThread::makeJPEG(int idx) {\n    /*\n     * Decompress JPEG\n     */\n    PyObject* pySrc = PyList_GET_ITEM(_py_list_src, idx);\n    uchar* src = (unsigned char*)PyString_AsString(pySrc);\n    size_t src_len = PyString_GET_SIZE(pySrc);\n    vector<uchar> src_vec(src, src + src_len);\n\n    cv::Mat decoded_mat = cv::imdecode(cv::Mat(src_vec), CV_LOAD_IMAGE_COLOR);\n    assert(decoded_mat.channels() == 3);\n\n    /*\n     * Resize\n     */\n    double min_dim = std::min(decoded_mat.size().height, decoded_mat.size().width);\n    double scale_factor = _target_size / min_dim;\n\n    int new_height = round(scale_factor * decoded_mat.size().height);\n    int new_width = round(scale_factor * decoded_mat.size().width);\n    assert((new_height == _target_size && new_width >= _target_size)\n           || (new_width == _target_size && new_height >= _target_size));\n    int interpolation = scale_factor == 1 ? cv::INTER_LINEAR\n                      : scale_factor > 1 ? cv::INTER_CUBIC : cv::INTER_AREA;\n\n    cv::resize(decoded_mat, _resized_mat_buffer, cv::Size(new_width, new_height), 0, 0, interpolation);\n\n    /*\n     * Conditionally crop and compress JPEG\n     */\n    if (_crop_to_square) {\n        int crop_start_x = (new_width - _target_size) / 2;\n        int crop_start_y = (new_height - _target_size) / 2;\n        cv::Rect cropRect(crop_start_x, crop_start_y, _target_size, _target_size);\n        cv::Mat cropped_mat_buffer = _resized_mat_buffer(cropRect);\n        cv::imencode(\".jpg\", cropped_mat_buffer, _output_jpeg_buffer, _encode_params);\n    } else {\n        cv::imencode(\".jpg\", _resized_mat_buffer, _output_jpeg_buffer, _encode_params);\n    }\n\n    char* output_jpeg_buffer_ptr = reinterpret_cast<char*>(&_output_jpeg_buffer[0]);\n    PyObject* pyStr = PyString_FromStringAndSize(output_jpeg_buffer_ptr, _output_jpeg_buffer.size());\n    PyList_Append(_py_list_tgt, pyStr);\n    Py_DECREF(pyStr);\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/nvmatrix/Makefile",
    "content": "################################################################################\n#\n# Copyright 1993-2012 NVIDIA Corporation.  All rights reserved.\n#\n# NOTICE TO USER:   \n#\n# This source code is subject to NVIDIA ownership rights under U.S. and \n# international Copyright laws.  \n#\n# NVIDIA MAKES NO REPRESENTATION ABOUT THE SUITABILITY OF THIS SOURCE \n# CODE FOR ANY PURPOSE.  IT IS PROVIDED \"AS IS\" WITHOUT EXPRESS OR \n# IMPLIED WARRANTY OF ANY KIND.  NVIDIA DISCLAIMS ALL WARRANTIES WITH \n# REGARD TO THIS SOURCE CODE, INCLUDING ALL IMPLIED WARRANTIES OF \n# MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE.   \n# IN NO EVENT SHALL NVIDIA BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL, \n# OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS \n# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE \n# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE \n# OR PERFORMANCE OF THIS SOURCE CODE.  \n#\n# U.S. Government End Users.  This source code is a \"commercial item\" as \n# that term is defined at 48 C.F.R. 2.101 (OCT 1995), consisting  of \n# \"commercial computer software\" and \"commercial computer software \n# documentation\" as such terms are used in 48 C.F.R. 12.212 (SEPT 1995) \n# and is provided to the U.S. Government only as a commercial end item.  \n# Consistent with 48 C.F.R.12.212 and 48 C.F.R. 227.7202-1 through \n# 227.7202-4 (JUNE 1995), all U.S. Government End Users acquire the \n# source code with only those rights set forth herein.\n#\n################################################################################\n\n# Location of the CUDA Toolkit binaries and libraries\nCUDA_INC_PATH  = $(CUDA_INSTALL_PATH)/include\nCUDA_BIN_PATH  = $(CUDA_INSTALL_PATH)/bin\nCUDA_LIB_PATH  = $(CUDA_INSTALL_PATH)/lib64\n\n# Common binaries\nNVCC            = $(CUDA_BIN_PATH)/nvcc\nGCC             = g++\nAR\t\t\t\t= ar\n\n# CUDA code generation flags\nGENCODE_SM35    := -gencode arch=compute_35,code=sm_35\nGENCODE_FLAGS   := $(GENCODE_SM35)\n\nLDFLAGS   := -L$(CUDA_LIB_PATH) -lcudart\nCCFLAGS   := -m64\nNVCCFLAGS := -m64\n\n# Debug build flags\nifeq ($(dbg),1)\n      CCFLAGS   += -g\n      NVCCFLAGS += -g -G\n      DBG := debug\nelse\n      DBG := release\n      NVCCFLAGS += -O3\n      CCFLAGS += -O3\nendif\n\n# Add profiler output\nifeq ($(prof),1)\n\tNVCCFLAGS += --ptxas-options=-v\nendif\n\nTARGETDIR := ./bin/$(DBG)\nOBJDIR := ./obj/$(DBG)\n\n########## USER STUFF ###########\nLDFLAGS   \t\t+= -L../util -lutilpy -lcublas\nINCLUDES      \t:= -I$(CUDA_INC_PATH) -I $(CUDA_SDK_PATH)/common/inc -I./include\n\nCUFILES\t:= $(shell find . -name \"*.cu\")\nCU_DEPS\t:= $(shell find . -name \"*.cuh\")\nCCFILES\t:= $(shell find . -name \"*.cpp\")\nC_DEPS\t:= $(shell find . -name \"*.h\")\n\nNVCCFLAGS += --compiler-options '-fPIC'\nLDFLAGS += -shared\nCCFLAGS += -fPIC\nTARGET := $(TARGETDIR)/libnvmatrix.so\n\n################################################################################\n# Set up target and object files\n################################################################################\nOBJS +=  $(patsubst %.cpp,$(OBJDIR)/%.cpp.o,$(CCFILES))\nOBJS +=  $(patsubst %.c,$(OBJDIR)/%.c.o,$(CFILES))\nOBJS +=  $(patsubst %.cu,$(OBJDIR)/%.cu.o,$(CUFILES))\n\n# Target rules\nall: makedirs $(TARGET)\n\n$(OBJDIR)/%.cu.o : %.cu $(CU_DEPS)\n\t$(NVCC) $(NVCCFLAGS) $(GENCODE_FLAGS) $(INCLUDES) -o $@ -c $<\n\n$(OBJDIR)/%.cpp.o : %.cpp $(C_DEPS)\n\t$(GCC) $(CCFLAGS) $(INCLUDES) -o $@ -c $<\n\n$(TARGET): $(OBJS)\n\t$(GCC) $(CCFLAGS) -o $@ $+ $(LDFLAGS)\n\tln -sf $(TARGET) .\n\nmakedirs:\n\tmkdir -p $(TARGETDIR)\n\tmkdir -p $(OBJDIR)/src\n\nclean:\n\trm -rf ./obj\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/nvmatrix/include/memory.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef MEMORY_CUH_H_\n#define MEMORY_CUH_H_\n#include <map>\n#include <cuda.h>\n#include <string.h>\n#include <vector>\n#include <assert.h>\n\n#include <helper_cuda.h>\n#include \"../../util/include/sync.h\"\n#include \"nvmatrix_kernels.cuh\"\n\n#define GPU_ALLOC_FRACTION                  0.95 // Take 95% of available GPU memory\n#define HOST_ALLOC_CHUNK                    (1UL << 32)\n#define SYNC_ON_FREE                        true\n#define BUCKET_TYPE                         unsigned int\n\n// Allocte memory from up to this many buckets higher than desired without subdividing\n#define BUCKET_DIVISION_THRESHOLD           1\n#define NUM_BUCKETS                         static_cast<int>(sizeof(BUCKET_TYPE) * 8)\n#define CLZ(x)                              ((x) == 0 ? (NUM_BUCKETS) : __builtin_clz(x))\n#define CEIL_LOG2(x)                        (NUM_BUCKETS - CLZ(x))                      // Ceiling of log base 2 of (x + 1)\n#define LOG_FIRST_BUCKET_SIZE               12\n#define FIRST_BUCKET_SIZE                   (1 << LOG_FIRST_BUCKET_SIZE)                // First bucket is for 4K bytes\n#define GET_ALLOC_BUCKET(size)              (CEIL_LOG2(((size) - 1) >> LOG_FIRST_BUCKET_SIZE))\n#define GET_DEALLOC_BUCKET(size)            (CEIL_LOG2((size) >> (1 + LOG_FIRST_BUCKET_SIZE)))\n#define GET_BUCKET_SIZE(b)                  (1UL << (LOG_FIRST_BUCKET_SIZE + b))\n\n#define BUCKET_MASK(b)                      (1UL << (b))\n#define PREV_BUCKETS_MASK(b)                (BUCKET_MASK(b) - 1)\n#define AVAILABLE_NEXT_MASK(b, buckets)     ((buckets) & ~PREV_BUCKETS_MASK(b))\n\n/*\n * Returns the \"best-matching\" available bucket as defined by policy.\n * The two policies are:\n *\n *      TAKE_FROM_BIGGEST = true: If a bucket in the range\n *      b...{b + BUCKET_DIVISION_THRESHOLD} is available, return the smallest\n *      available bucket in that range. Otherwise return the *biggest* available\n *      bucket greater than or equal to b.\n *\n *      TAKE_FROM_BIGGEST = false: Return the *smallest* available bucket greater\n *      than or equal to b.\n *\n * Returns -1 when no satisfactory bucket is available.\n */\n#define TAKE_FROM_BIGGEST                   true\n#if TAKE_FROM_BIGGEST\n#define GET_AVAILABLE_BUCKET(b, buckets)                                                                 \\\n                                    (-1 + (((AVAILABLE_NEXT_MASK(b, buckets))                            \\\n                                             & (PREV_BUCKETS_MASK((b) + 1 + BUCKET_DIVISION_THRESHOLD))) \\\n        /* Smallest bucket >= b */         ? __builtin_ffs(AVAILABLE_NEXT_MASK(b, buckets))              \\\n        /* Biggest bucket >= b */          : CEIL_LOG2(AVAILABLE_NEXT_MASK(b, buckets))))\n#else\n#define GET_AVAILABLE_BUCKET(b, buckets)    __builtin_ffs(AVAILABLE_NEXT_MASK(b, buckets))\n#endif\n\n/*\n * Bit get/set/clear.\n */\n#define GET_BIT(x, bit)             ((x) & (1 << (bit)))\n#define SET_BIT(x, bit)             ((x) |= (1 << (bit)))\n#define CLEAR_BIT(x, bit)           ((x) &= ~(1 << (bit)))\n\ntypedef struct __align__(512) {\n    char data;\n} DataType;\n\n#define SIZE_ROUNDUP(size) (sizeof(DataType) * DIVUP((size), sizeof(DataType)))\n\nclass MemorySegment {\n    friend class FastMemoryManager;\nprotected:\n    DataType* _data;\n    size_t _size;\n    int _deviceID;\n    // Resizes itself to _size - size and\n    // returns pointer to new memory segment\n    MemorySegment* subdivide(size_t size) {\n        assert(size < _size);\n//        assert(size % sizeof(DataType) == 0);\n        _size -= size;\n        return new MemorySegment(_data + _size / sizeof(DataType), size, _deviceID);\n    }\n\n    inline size_t getSize() const {\n        return _size;\n    }\npublic:\n    MemorySegment(DataType* data, size_t size, int deviceID) : _data(data), _size(size), _deviceID(deviceID) {\n        assert(size % sizeof(DataType) == 0);\n    }\n    // In some cases size is irrelevant\n    template<typename T> MemorySegment(T* data) : _data(reinterpret_cast<DataType*>(data)), _size(0), _deviceID(-1) {\n\n    }\n\n    template <class T /*= DataType*/>\n    inline T* getData() const {\n        return reinterpret_cast<T*>(_data);\n    }\n\n    template <class T /*= DataType*/>\n    inline T** getDataPtr() {\n        return reinterpret_cast<T**>(&_data);\n    }\n\n    inline int getDeviceID() const {\n        return _deviceID;\n    }\n};\n\nclass MemoryManager {\nprotected:\n    static Lock _globalLock;\npublic:\n    virtual MemoryManager* init() = 0;\n    virtual MemorySegment* malloc(size_t size) = 0;\n    virtual void free(MemorySegment* mem) = 0;\n    virtual ~MemoryManager() {\n\n    }\n};\n\nclass FastMemoryManager : public MemoryManager {\nprotected:\n    int _deviceID;\n    Lock _lock;\n    DataType* _data;\n    size_t _size;\n    BUCKET_TYPE _buckets; // Bucket availability bit vector\n    std::vector<std::vector<MemorySegment*> > _freeSegments; // bucket idx -> vector of segments\n\n    static std::map<int, MemoryManager*> _memoryManagers;\n\n    virtual void allocateInitialSegment() {\n        assert(_deviceID >= 0);\n        assert(FIRST_BUCKET_SIZE % sizeof(DataType) == 0);\n        checkCudaErrors(cudaSetDevice(_deviceID));\n        size_t memFree, memTotal;\n        checkCudaErrors(cudaMemGetInfo(&memFree, &memTotal));\n        _size = sizeof(DataType) * (size_t(round(double(memFree) * GPU_ALLOC_FRACTION)) / sizeof(DataType));\n        printf(\"FastMemoryManager[%d] allocating %lu-byte initial segment\\n\", _deviceID, _size);\n        checkCudaErrors(cudaMalloc(&_data, _size));\n    }\n\n    virtual void freeInitialSegment() {\n        checkCudaErrors(cudaFree(_data));\n    }\n\npublic:\n    static MemoryManager& getInstance(int deviceID);\n    static void destroyInstance(int deviceID);\n\n    FastMemoryManager(int deviceID) : _deviceID(deviceID), _data(NULL), _size(0), _buckets(0) {\n    }\n\n    ~FastMemoryManager() {\n        freeInitialSegment();\n        for (int i = 0; i < _freeSegments.size(); ++i) {\n            for (int j = 0; j < _freeSegments[i].size(); ++j) {\n                delete _freeSegments[i][j];\n            }\n        }\n    }\n\n    virtual MemoryManager* init() {\n        allocateInitialSegment();\n\n        for (int i = 0; i < NUM_BUCKETS; ++i) {\n            _freeSegments.push_back(std::vector<MemorySegment*>());\n        }\n        int bucket = GET_DEALLOC_BUCKET(_size);\n        SET_BIT(_buckets, bucket);\n        _freeSegments[bucket].push_back(new MemorySegment(_data, _size, _deviceID));\n        return this;\n    }\n\n    MemorySegment* malloc(size_t size) {\n        assert(size > 0);\n        int requestedBucket = GET_ALLOC_BUCKET(size);\n        _lock.acquire();\n\n        int bucket = GET_AVAILABLE_BUCKET(requestedBucket, _buckets);\n//        if (bucket - requestedBucket > BUCKET_DIVISION_THRESHOLD) {\n//            printf(\"MemoryManager[%d] requested size: %lu, requested bucket: %d, available bucket: %d\\n\", _deviceID, size, requestedBucket, bucket);\n//        }\n\n        assert(bucket >= requestedBucket); // Out of memory\n\n        MemorySegment* sourceSegment = _freeSegments[bucket].back();\n        MemorySegment* ret = sourceSegment;\n        if (bucket - requestedBucket > BUCKET_DIVISION_THRESHOLD) { // We got a much bigger chunk than we wanted\n            ret = sourceSegment->subdivide(GET_BUCKET_SIZE(requestedBucket));\n            int newSrcBucket = GET_DEALLOC_BUCKET(sourceSegment->getSize());\n            if (newSrcBucket != bucket) {\n                _freeSegments[bucket].pop_back();\n                _freeSegments[newSrcBucket].push_back(sourceSegment);\n                SET_BIT(_buckets, newSrcBucket);\n            }\n        } else {\n            _freeSegments[bucket].pop_back();\n        }\n        if (_freeSegments[bucket].size() == 0) {\n            CLEAR_BIT(_buckets, bucket);\n        }\n        _lock.release();\n        return ret;\n    }\n\n    void free(MemorySegment* mem) {\n        assert(mem != NULL);\n        assert(mem->getSize() >= FIRST_BUCKET_SIZE);\n        int bucket = GET_DEALLOC_BUCKET(mem->getSize());\n        // Synchronize for safety, so that we don't free memory that's being used. Not synchronizing\n        // could potentially cause a problem if we re-allocate the just-freed chunk and attempt to\n        // use it in a different stream.\n        if (SYNC_ON_FREE) {\n            int d;\n            checkCudaErrors(cudaGetDevice(&d));\n            checkCudaErrors(cudaSetDevice(mem->getDeviceID()));\n            checkCudaErrors(cudaDeviceSynchronize());\n            checkCudaErrors(cudaSetDevice(d));\n        }\n        _lock.acquire();\n        _freeSegments[bucket].push_back(mem);\n        SET_BIT(_buckets, bucket);\n//        printf(\"MemoryManager[%d] Freed segment of size %lu into bucket %lu\\n\", _deviceID, mem->getSize(), bucket);\n        _lock.release();\n    }\n};\n\nclass FastHostMemoryManager : public FastMemoryManager {\nprotected:\n    static MemoryManager* _memoryManager;\n    void allocateInitialSegment() {\n        _size = HOST_ALLOC_CHUNK;\n        checkCudaErrors(cudaHostAlloc(&_data, _size, cudaHostAllocPortable));\n    }\n    void freeInitialSegment () {\n        checkCudaErrors(cudaFreeHost(_data));\n    }\npublic:\n    FastHostMemoryManager() : FastMemoryManager(DEVICE_HOST) {\n    }\n\n    static MemoryManager& getInstance();\n    static void destroyInstance();\n};\n\nclass CUDAMemoryManager : public MemoryManager {\nprotected:\n    static MemoryManager* _memoryManager;\n\n    virtual void _malloc(DataType** data, size_t size) {\n        checkCudaErrors(cudaMalloc(data, size));\n    }\n    virtual void _free(MemorySegment* mem) {\n        checkCudaErrors(cudaFree(mem->getData<DataType>()));\n    }\npublic:\n    static MemoryManager& getInstance(int deviceID);\n    static void destroyInstance(int deviceID);\n    CUDAMemoryManager() {\n    }\n\n    MemoryManager* init() {\n        return this;\n    }\n\n    MemorySegment* malloc(size_t size) {\n        MemorySegment* seg = new MemorySegment(reinterpret_cast<DataType*>(NULL));\n        DataType** data = seg->getDataPtr<DataType>();\n        _malloc(data, size);\n        return seg;\n    }\n\n    void free(MemorySegment* mem) {\n        assert(mem != NULL);\n        _free(mem);\n        delete mem;\n    }\n};\n\nclass CUDAHostMemoryManager : public CUDAMemoryManager {\nprotected:\n    static MemoryManager* _memoryManager;\n    void _free(MemorySegment* mem) {\n        checkCudaErrors(cudaFreeHost(mem->getData<DataType>()));\n    }\n    void _malloc(DataType** data, size_t size) {\n        checkCudaErrors(cudaHostAlloc(data, size, cudaHostAllocPortable));\n    }\npublic:\n    static MemoryManager& getInstance();\n    static void destroyInstance();\n    CUDAHostMemoryManager() : CUDAMemoryManager() {\n\n    }\n};\n#endif /* MEMORY_CUH_H_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/nvmatrix/include/nvmatrix.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef NVMATRIX_H_\n#define NVMATRIX_H_\n\n#include <map>\n#include <vector>\n#include <cublas_v2.h>\n#include <cuda.h>\n#include <curand.h>\n#include <time.h>\n#include <curand_kernel.h>\n\n#include <helper_cuda.h>\n#include \"../../util/include/matrix.h\"\n#include \"nvmatrix_kernels.cuh\"\n#include \"nvmatrix_operators.cuh\"\n#include \"memory.cuh\"\n\n#ifdef WARNINGS\n#define WARN(msg) printf(\"WARN: File %s, line %d: %s\\n\", __FILE__, __LINE__, msg);\n#else\n#define WARN(msg) ;\n#endif\n\n#define CURAND_CALL(x) do { if((x) != CURAND_STATUS_SUCCESS) { \\\n                            printf(\"CURAND Error at %s:%d\\n\",__FILE__,__LINE__);\\\n                            exit(EXIT_FAILURE);}} while(0)\n\n#define CUBLAS_CALL(x) do { if((x) != CUBLAS_STATUS_SUCCESS) { \\\n                            printf(\"CUBLAS Error at %s:%d\\n\",__FILE__,__LINE__);\\\n                            exit(EXIT_FAILURE);}} while(0)\n\n/*\n * Memory manager to use for GPU memory allocations.\n *\n * CUDAMemoryManager: Default Nvidia memory manager; just calls cudaMalloc / cudaFree.\n *                    Allocating and freeing memory is slow.\n * FastMemoryManager: A GPU memory manager with very fast (constant time)\n *                    alloc / free, but possibly more wasteful of memory.\n */\n#define DEVICE_MEMORY_MANAGER       CUDAMemoryManager\n\n/*\n * Memory manager to use for host memory allocations.\n *\n * CUDAHostMemoryManager: Default Nvidia memory manager; just calls cudaHostAlloc / cudaFreeHost.\n *                        Allocating and freeing memory is slow.\n * FastHostMemoryManager: A host memory manager with very fast (constant time)\n *                        alloc / free, but possibly more wasteful of memory.\n */\n#define HOST_MEMORY_MANAGER         CUDAHostMemoryManager\n\nclass NVMatrix;\ntypedef std::vector<NVMatrix*> NVMatrixV;\n\nclass NVMatrix {\nprotected:\n    int _numCols, _numRows;\n    int _numElements;\n    int _stride;\n//    float* getDevData();\n    MemorySegment* _memSegment;\n    bool _isTrans;\n    bool _ownsData;\n    // This flag makes sure that the NVMatrix destructor does nothing\n    // when called on HostNVMatrix instance.\n    bool _deleted;\n    cudaTextureObject_t _texObj;\n\n//    static std::map<int,curandGenerator_t> rndGen;\n    static std::map<int,MemorySegment*> _rndDevStates;\n    static std::map<int,cublasHandle_t> _cublasHandles;\n    // Map from device id --> # of random streams initialized on that device\n    static std::map<int,int> _rndDevThreads;\n    static pthread_mutex_t *_rndMutex, *_cublasMutex, *_streamMutex;\n    // Map from device id --> default stream\n    static std::map<int,cudaStream_t> _defaultStreams;\n\n    cublasOperation_t getTransChar() const {\n        /*\n         * not a typo! return opposite character because a\n         * non-transposed nvmatrix is in row-major order while a non-transposed\n         * cublas matrix is in column-major order.\n         */\n        return _isTrans ? CUBLAS_OP_N : CUBLAS_OP_T;\n    }\n\n    void _init(bool isTrans);\n    void _sum_setParams(int n, dim3* blocks, dim3* threads);\n    template<class Agg> float cpuAgg(Agg agg, cudaStream_t stream);\n    template<class Agg> float _totalAgg(Agg agg);\n    template<class Agg> float _totalAgg(Agg agg, cudaStream_t stream);\n    template<class Agg> float _totalAgg(Agg agg, NVMatrix& tmpbuf, cudaStream_t stream);\n    template<class Agg, class UnaryOp, class BinaryOp> void _aggregate(int axis, NVMatrix& target, Agg agg, UnaryOp uop, BinaryOp bop, cudaStream_t stream, NVMatrix* tmp);\n    template<class Agg, class UnaryOp, class BinaryOp> void _aggregate(int axis, NVMatrix& target, Agg agg, UnaryOp uop, BinaryOp bop, cudaStream_t stream);\n    template<class Agg, class UnaryOp, class BinaryOp> void _aggregate(int axis, NVMatrix& target, Agg agg, UnaryOp uop, BinaryOp bop);\n    template<class Agg, class BinaryOp> void _aggregate(int axis, NVMatrix& target, Agg agg, BinaryOp bop, cudaStream_t stream);\n    template<class Agg, class BinaryOp> void _aggregate(int axis, NVMatrix& target, Agg agg, BinaryOp bop);\n    template<class Agg, class BinaryOp> NVMatrix& _aggregate(int axis, Agg agg, BinaryOp bop, cudaStream_t stream);\n    template<class Agg, class BinaryOp> NVMatrix& _aggregate(int axis, Agg agg, BinaryOp bop);\n    template<class Agg, class UnaryOp, class BinaryOp> NVMatrix& _aggregate(int axis, Agg agg, UnaryOp, BinaryOp bop, cudaStream_t stream);\n    template<class Agg, class UnaryOp, class BinaryOp> NVMatrix& _aggregate(int axis, Agg agg, UnaryOp, BinaryOp bop);\n\n    template<class Agg, class UnaryOp, class BinaryOp> void _aggregate(int axis, NVMatrix& target, Agg agg, UnaryOp uop, BinaryOp bop, NVMatrix& tmp);\n    template<class Agg, class BinaryOp> void _aggregate(int axis, NVMatrix& target, Agg agg, BinaryOp bop, cudaStream_t stream, NVMatrix& tmp);\n    template<class Agg, class BinaryOp> void _aggregate(int axis, NVMatrix& target, Agg agg, BinaryOp bop, NVMatrix& tmp);\n    template<class Agg, class BinaryOp> NVMatrix& _aggregate(int axis, Agg agg, BinaryOp bop, cudaStream_t stream, NVMatrix& tmp);\n    template<class Agg, class BinaryOp> NVMatrix& _aggregate(int axis, Agg agg, BinaryOp bop, NVMatrix& tmp);\n    template<class Agg, class UnaryOp, class BinaryOp> NVMatrix& _aggregate(int axis, Agg agg, UnaryOp, BinaryOp bop, cudaStream_t stream, NVMatrix& tmp);\n    template<class Agg, class UnaryOp, class BinaryOp> NVMatrix& _aggregate(int axis, Agg agg, UnaryOp, BinaryOp bop, NVMatrix& tmp);\n\n    template <class Randomizer> void _unaryRandomize(NVMatrix& target, Randomizer rnd, cudaStream_t stream);\n    template <class Randomizer> void _unaryRandomize(NVMatrix& target, Randomizer rnd);\n    template <class Randomizer> void _binaryRandomize(NVMatrix& data2, NVMatrix& target, Randomizer rnd);\n    template <class Randomizer> void _binaryRandomize(NVMatrix& data2, NVMatrix& target, Randomizer rnd, cudaStream_t stream);\n\n    virtual void alloc(int numElements);\n    virtual void dealloc();\n    void deallocTexture();\n    virtual NVMatrix& construct() const;\n    virtual NVMatrix& construct(bool isTrans) const;\n    virtual NVMatrix& construct(int numRows, int numCols, bool isTrans=false) const;\n    virtual NVMatrix& construct(const Matrix& like, bool copy) const;\n    virtual NVMatrix& construct(const NVMatrix& like, bool copy) const;\n    virtual NVMatrix& construct(const NVMatrix& like) const;\n    virtual NVMatrix& construct(const Matrix& like) const;\n    virtual NVMatrix& construct(MemorySegment* mem, int numRows, int numCols, int stride, bool isTrans) const;\n    static cublasHandle_t getCublasHandle();\n    static cublasHandle_t getCublasHandle(int deviceID);\npublic:\n    NVMatrix();\n    NVMatrix(bool isTrans);\n    NVMatrix(int numRows, int numCols, bool isTrans=false);\n    NVMatrix(const Matrix& like, bool copy);\n    NVMatrix(const NVMatrix& like, bool copy);\n    NVMatrix(const NVMatrix& like);\n    NVMatrix(const Matrix& like);\n    NVMatrix(MemorySegment* mem, int numRows, int numCols, int stride, bool isTrans);\n    virtual ~NVMatrix();\n\n    // Returns the device ID on which the data pointer is allocated\n    int getDataDeviceID() const;\n    static void initRandom(unsigned long long seed, int numStreams, cudaStream_t stream);\n    static void initRandom(unsigned long long seed, int numStreams);\n    static void initRandom(unsigned long long seed);\n    static void initRandom();\n    static void initCublas();\n    static void destroyCublas();\n    static std::pair<size_t, size_t> getCudaMemorySize();\n\n    // Returns the currently-active device ID for calling thread\n    static int getDeviceID();\n    static void setDeviceID(int d);\n    static bool canAccessPeer(int srcDevice, int tgtDevice);\n    static bool isRndInitialized();\n    static bool isRndInitialized(bool haveLock);\n    static curandState* getCurandState();\n    static curandState* getCurandState(int numStreams);\n    static void destroyRandom();\n    static pthread_mutex_t* makeMutex();\n    static cudaStream_t getDefaultStream(int deviceID);\n    static cudaStream_t getDefaultStream();\n    static void syncDevice();\n    static void syncStream();\n    static void syncStream(cudaStream_t stream);\n\n    /*\n     * DO NOT DEREFERENCE IN HOST CODE! This is a device memory pointer.\n     */\n    float* getCellPtr(int i, int j) const {\n        if (_isTrans) {\n            return &getDevData()[j * _numRows + i];\n        }\n        return &getDevData()[i * _numCols + j];\n    }\n\n    bool isSameDims(const Matrix& m) const {\n        return m.getNumRows() == _numRows && m.getNumCols() == _numCols;\n    }\n\n    bool isSameDims(const NVMatrix& m) const {\n        return m.getNumRows() == _numRows && m.getNumCols() == _numCols;\n    }\n\n    int getNumRows() const {\n        return _numRows;\n    }\n\n    int getNumCols() const {\n        return _numCols;\n    }\n\n    int getStride() const {\n        return _stride;\n    }\n\n    int getLeadingDim() const {\n        return _isTrans ? _numRows : _numCols;\n    }\n\n    int getFollowingDim() const {\n        return !_isTrans ? _numRows : _numCols;\n    }\n\n    /*\n     * FALSE:    Row-major order.\n     * TRUE:     Column-major order.\n     */\n    bool isTrans() const {\n        return _isTrans;\n    }\n\n    bool isView() const {\n        return !_ownsData;\n    }\n\n    float* getDevData() const {\n        return _memSegment == NULL ? NULL : _memSegment->getData<float>();\n    }\n\n    MemorySegment& getMemorySegment() const {\n        return *_memSegment;\n    }\n\n    int getNumElements() const {\n        return _numElements;\n    }\n\n    size_t getNumDataBytes() const {\n        return size_t(_numElements) * 4;\n    }\n\n    /*\n     * Only use if you know what you're doing!\n     * Does not actually transpose matrix.\n     */\n    void setTrans(bool trans) {\n        if (trans != _isTrans) {\n            assert(isContiguous());\n            _isTrans = trans;\n            _stride = getLeadingDim();\n        }\n    }\n\n    /*\n     * Only use if you know what you're doing!\n     * This toggles whether this object will free its GPU memory when it's destroyed.\n     */\n    void setIsView(bool isView) {\n        _ownsData = !isView;\n    }\n\n    bool isContiguous() const {\n        return _stride == getLeadingDim() || getFollowingDim() == 1;\n    }\n\n    void truncate() {\n        resize(0,0);\n    }\n\n    virtual cudaTextureObject_t getTextureObject();\n\n    virtual void copyFromHost(const Matrix& hostMatrix);\n    virtual void copyFromHost(const Matrix& hostMatrix, bool resizeTarget);\n    virtual void copyFromHost(const Matrix& hostMatrix, bool resizeTarget, cudaStream_t stream);\n    virtual void copyToHost(Matrix& hostMatrix) const;\n    virtual void copyToHost(Matrix& hostMatrix, bool resizeTarget) const;\n    virtual void copyToHost(Matrix& hostMatrix, bool resizeTarget, cudaStream_t stream) const;\n    void copy(NVMatrix& dest) const;\n    void copy(NVMatrix& dest, cudaStream_t stream) const;\n    NVMatrix& copy() const;\n    void addProduct(NVMatrix& a, NVMatrix &b, float scaleThis, float scaleAB, cudaStream_t stream);\n    void addProduct(NVMatrix& a, NVMatrix &b, float scaleThis, float scaleAB);\n    void addProduct(NVMatrix& a, NVMatrix &b);\n    void rightMult(NVMatrix &b, float scaleAB, NVMatrix &target, cudaStream_t stream);\n    void rightMult(NVMatrix &b, float scaleAB, NVMatrix &target);\n    void rightMult(NVMatrix &b, NVMatrix &target);\n    void rightMult(NVMatrix &b, float scaleAB);\n    void randomizeUniform();\n    void addGaussianNoise(NVMatrix& stdevs, bool var, NVMatrix& target);\n    void addGaussianNoise(float stdev, NVMatrix& target);\n    void addGaussianNoise(NVMatrix& stdevs, bool var);\n    void addGaussianNoise(NVMatrix& stdevs);\n    void addGaussianNoise(float stdev);\n    void addGaussianNoise();\n    void randomizeGaussian();\n    void randomizeGaussian(float stdev);\n    void randomizeGaussian(float mean, float stdev);\n    void randomizeGaussian(float mean, NVMatrix& stdevs);\n    void randomizeGaussian(float mean, float stdevMult, NVMatrix& stdevs);\n    void randomizeGaussian(NVMatrix& stdevs);\n    void randomizeGaussian(NVMatrix& stdevs, NVMatrix& target);\n    void binarizeProbs();\n    void binarizeProbs(NVMatrix& target);\n\n    void biggerThan(NVMatrix& m, NVMatrix& target);\n    void biggerThan(NVMatrix& m);\n    void biggerThanVector(NVMatrix& vec, NVMatrix& target);\n    void biggerThanVector(NVMatrix& vec);\n    void equals(NVMatrix& m, NVMatrix& target);\n    void equals(NVMatrix& m);\n\n    void _checkBounds(int startRow, int endRow, int startCol, int endCol) const;\n    NVMatrix& slice(int startRow, int endRow, int startCol, int endCol) const;\n    void slice(int startRow, int endRow, int startCol, int endCol, NVMatrix& target) const;\n    NVMatrix& sliceRows(int startRow, int endRow) const;\n    void sliceRows(int startRow, int endRow, NVMatrix& target) const;\n    NVMatrix& sliceCols(int startCol, int endCol) const;\n    void sliceCols(int startCol, int endCol, NVMatrix& target) const;\n\n    NVMatrixV& splitRows(int numParts);\n    NVMatrixV& splitCols(int numParts);\n\n    template <class Op> void apply(Op op, NVMatrix& target, cudaStream_t stream) {\n        if (!target.isSameDims(*this)) {\n            target.resize(*this);\n        }\n        if (getNumElements() > 0) {\n            int height = target.getFollowingDim(), width = target.getLeadingDim();\n\n            if (target.isTrans() == isTrans()) {\n                if (!isContiguous() || !target.isContiguous()) {\n                    dim3 blocks(std::min(NUM_BLOCKS_MAX, DIVUP(width, ELTWISE_THREADS_X)),\n                            std::min(NUM_BLOCKS_MAX, DIVUP(height, ELTWISE_THREADS_Y)));\n                    dim3 threads(ELTWISE_THREADS_X, ELTWISE_THREADS_Y);\n                    kEltwiseUnaryOp<Op><<<blocks, threads, 0, stream>>>(getDevData(), target.getDevData(), height, width, getStride(), target.getStride(), op);\n                    getLastCudaError(\"kEltwiseUnaryOp: Kernel execution failed\");\n                } else {\n                    dim3 threads = dim3(ELTWISE_FLAT_THREADS_X);\n                    dim3 blocks = dim3(std::min(128, DIVUP(_numElements, ELTWISE_FLAT_THREADS_X)));\n                    kEltwiseUnaryOpFlat<Op><<<blocks, threads, 0, stream>>>(getDevData(), target.getDevData(), _numElements, op);\n                    getLastCudaError(\"kEltwiseUnaryOpFlat: Kernel execution failed\");\n                }\n            } else {\n                dim3 blocks(std::min(NUM_BLOCKS_MAX, DIVUP(width, ELTWISE_THREADS_X)),\n                        std::min(NUM_BLOCKS_MAX, DIVUP(height, ELTWISE_THREADS_Y)));\n                dim3 threads(ELTWISE_THREADS_X, ELTWISE_THREADS_Y);\n                bool checkBounds = !(width % ELTWISE_THREADS_X == 0 && height % ELTWISE_THREADS_X == 0);\n    //            printf(\"height: %d, width: %d, stride: %d, target stride: %d, check bounds: %d, threads.x: %d, threads.y: %d, blocks.x: %d, blocks.y: %d\\n\",\n    //                    height, width, getStride(), target.getStride(), checkBounds, threads.x, threads.y, blocks.x, blocks.y);\n                if (checkBounds) {\n                    kEltwiseUnaryOpTrans<Op, true><<<blocks, threads, 0, stream>>>(getDevData(), target.getDevData(), height, width, getStride(), target.getStride(), op);\n                } else {\n                    kEltwiseUnaryOpTrans<Op, false><<<blocks, threads, 0, stream>>>(getDevData(), target.getDevData(), height, width, getStride(), target.getStride(), op);\n                }\n                getLastCudaError(\"kEltwiseUnaryOpTrans: Kernel execution failed\");\n            }\n        }\n    }\n\n    template <class Op> void apply(Op op, cudaStream_t stream) {\n        apply(op, *this, stream);\n    }\n\n    template <class Op> void apply(Op op, NVMatrix& target) {\n        apply(op, target, getDefaultStream());\n    }\n\n    template <class Op> void apply(Op op) {\n        apply(op, *this);\n    }\n\n    template <class Op> void applyBinary(Op op, NVMatrix& b) {\n        applyBinary(op, b, *this);\n    }\n\n    template <class Op> void applyBinary(Op op, NVMatrix& b, NVMatrix& target) {\n        applyBinary(op, b, target, getDefaultStream());\n    }\n\n    template <class Op> void applyBinary(Op op, NVMatrix& b, NVMatrix& target, cudaStream_t stream) {\n        assert(this->isSameDims(b));\n\n        if (!target.isSameDims(*this)) {\n            target.resize(*this);\n        }\n\n        if (getNumElements() > 0) {\n            int height = target.getFollowingDim(), width = target.getLeadingDim();\n            if (target.isTrans() == isTrans() && target.isTrans() == b.isTrans()) {\n                if (!isContiguous() || !b.isContiguous() || !target.isContiguous()) {\n                    dim3 blocks(std::min(128, DIVUP(width, ELTWISE_THREADS_X)),\n                                std::min(128, DIVUP(height, ELTWISE_THREADS_Y)));\n                    dim3 threads(ELTWISE_THREADS_X, ELTWISE_THREADS_Y);\n                    kEltwiseBinaryOp<Op><<<blocks, threads, 0, stream>>>(getDevData(), b.getDevData(), target.getDevData(), height, width, getStride(),\n                                                              b.getStride(), target.getStride(), op);\n                } else {\n                    dim3 threads = dim3(ELTWISE_FLAT_THREADS_X);\n                    dim3 blocks = dim3(std::min(128, DIVUP(_numElements, ELTWISE_FLAT_THREADS_X)));\n                    kEltwiseBinaryOpFlat<Op><<<blocks, threads, 0, stream>>>(getDevData(), b.getDevData(), target.getDevData(), _numElements, op);\n                }\n                getLastCudaError(\"kEltwiseBinaryOp: Kernel execution failed\");\n            } else {\n\n                dim3 blocks(std::min(128, DIVUP(width, ELTWISE_THREADS_X)),\n                            std::min(128, DIVUP(height, ELTWISE_THREADS_Y)));\n                dim3 threads(ELTWISE_THREADS_X, ELTWISE_THREADS_Y);\n                //  both x here since y divides x\n                bool checkBounds = !(width % ELTWISE_THREADS_X == 0 && height % ELTWISE_THREADS_X == 0);\n                if (target.isTrans() == isTrans() && target.isTrans() != b.isTrans()) {\n                    if (checkBounds) {\n                        kEltwiseBinaryOpTrans<Op,true,false,false><<<blocks, threads, 0, stream>>>(getDevData(), b.getDevData(), target.getDevData(), height, width,getStride(),\n                                                                   b.getStride(), target.getStride(), op);\n                    } else {\n                        kEltwiseBinaryOpTrans<Op,false,false,false><<<blocks, threads, 0, stream>>>(getDevData(), b.getDevData(), target.getDevData(), height, width,getStride(),\n                                                                   b.getStride(), target.getStride(), op);\n                    }\n                } else if (target.isTrans() != isTrans() && target.isTrans() != b.isTrans()) {\n                    if (checkBounds) {\n                        kEltwiseBinaryOpTrans<Op,true,true,false><<<blocks, threads, 0, stream>>>(getDevData(), b.getDevData(), target.getDevData(), height, width,getStride(),\n                                                                   b.getStride(), target.getStride(), op);\n                    } else {\n                        kEltwiseBinaryOpTrans<Op,false,true,false><<<blocks, threads, 0, stream>>>(getDevData(), b.getDevData(), target.getDevData(), height, width,getStride(),\n                                                                   b.getStride(), target.getStride(), op);\n                    }\n                } else if (target.isTrans() != isTrans() && target.isTrans() == b.isTrans()) {\n                    if (checkBounds) {\n                        kEltwiseBinaryOpTrans<Op,true,false,true><<<blocks, threads, 0, stream>>>(b.getDevData(), getDevData(), target.getDevData(), height, width,b.getStride(),\n                                                                   getStride(), target.getStride(), op);\n                    } else {\n                        kEltwiseBinaryOpTrans<Op,false,false,true><<<blocks, threads, 0, stream>>>(b.getDevData(), getDevData(), target.getDevData(), height, width, b.getStride(),\n                                                                   getStride(), target.getStride(), op);\n                    }\n                }\n                getLastCudaError(\"kEltwiseBinaryOpTrans: Kernel execution failed\");\n            }\n        }\n    }\n\n    template <class Op> void applyTernary(Op op, NVMatrix& b, NVMatrix& c, NVMatrix& target) {\n        applyTernary(op, b, c, target, getDefaultStream());\n    }\n\n    template <class Op> void applyTernary(Op op, NVMatrix& b, NVMatrix& c, NVMatrix& target, cudaStream_t stream) {\n        assert(isSameDims(b));\n        assert(isSameDims(c));\n        // For now ternary ops are only supported for matrices of same transposedness\n        assert(isTrans() == b.isTrans());\n        assert(isTrans() == c.isTrans());\n        if (!target.isSameDims(*this) || target.isTrans() != isTrans()) {\n            target.resize(*this);\n        }\n        if (getNumElements() > 0) {\n            int height = target.getFollowingDim(), width = target.getLeadingDim();\n            if (!isContiguous() || !b.isContiguous() || !c.isContiguous() || !target.isContiguous()) {\n                dim3 blocks(std::min(512, DIVUP(width, ELTWISE_THREADS_X)),\n                            std::min(512, DIVUP(height, ELTWISE_THREADS_Y)));\n                dim3 threads(ELTWISE_THREADS_X, ELTWISE_THREADS_Y);\n                kEltwiseTernaryOp<Op><<<blocks, threads, 0, stream>>>(getDevData(), b.getDevData(), c.getDevData(), target.getDevData(), height, width,\n                                                                       getStride(), b.getStride(), c.getStride(), target.getStride(), op);\n                getLastCudaError(\"kEltwiseTernaryOp: Kernel execution failed\");\n            } else {\n                dim3 threads = dim3(ELTWISE_FLAT_THREADS_X);\n                dim3 blocks = dim3(std::min(128, DIVUP(_numElements, ELTWISE_FLAT_THREADS_X)));\n                kEltwiseTernaryOpFlat<Op><<<blocks, threads, 0, stream>>>(getDevData(), b.getDevData(), c.getDevData(), target.getDevData(), _numElements, op);\n                getLastCudaError(\"kEltwiseTernaryOpFlat: Kernel execution failed\");\n            }\n        }\n    }\n\n    bool resize(int numRows, int numCols, bool trans);\n    bool resize(int numRows, int numCols);\n    bool resize(const NVMatrix &like);\n    bool resize(const Matrix &like);\n    void reshape(int numRows, int numCols);\n    NVMatrix& reshaped(int numRows, int numCols) const;\n    void copy(NVMatrix &dest, int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol, int destStartRow, int destStartCol) const;\n    void copy(NVMatrix &dest, int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol, int destStartRow, int destStartCol, cudaStream_t stream) const;\n    void add(NVMatrix& b, float scaleA, float scaleB, NVMatrix& target, cudaStream_t stream);\n    void add(NVMatrix& b, float scaleA, float scaleB, NVMatrix& target);\n    void add(NVMatrix& b, float scaleB, NVMatrix& target);\n    void add(NVMatrix& b, NVMatrix& target);\n    void add(NVMatrix& b, float scaleB);\n    void add(NVMatrix& b, float scaleA, float scaleB);\n    void add(NVMatrix& b);\n    void eltwiseMult(NVMatrix& b);\n    void eltwiseMult(NVMatrix& b, NVMatrix& target);\n    void eltwiseDivide(NVMatrix& b);\n    void eltwiseDivide(NVMatrix& b, NVMatrix& target);\n    void squaredDiff(NVMatrix& b);\n    void squaredDiff(NVMatrix& b, NVMatrix& target);\n    void subtract(NVMatrix& b, NVMatrix& target);\n    void subtract(NVMatrix& b);\n    void addVector(NVMatrix& vec, float scaleVec, NVMatrix& target, cudaStream_t stream);\n    void addVector(NVMatrix& vec, float scaleVec, NVMatrix& target);\n    void addVector(NVMatrix& vec);\n    void addVector(NVMatrix& vec, float scaleVec);\n    void addVector(NVMatrix& vec, NVMatrix& target);\n    void equalsVector(NVMatrix& vec, NVMatrix& target);\n    void equalsVector(NVMatrix& vec);\n    void eltwiseMultByVector(NVMatrix& vec, NVMatrix& target, cudaStream_t stream);\n    void eltwiseMultByVector(NVMatrix& vec, NVMatrix& target);\n    void eltwiseMultByVector(NVMatrix& vec);\n    void eltwiseMultByVector(NVMatrix& vec, cudaStream_t stream);\n    void eltwiseDivideByVector(NVMatrix& vec, NVMatrix& target);\n    void eltwiseDivideByVector(NVMatrix& vec);\n    void tile(int timesY, int timesX, NVMatrix& target);\n    void tile(int timesY, int timesX, NVMatrix& target, cudaStream_t stream);\n\n    void addSum(NVMatrix& a, int axis, float scaleThis, float scaleSum);\n    void addSum(NVMatrix& a, int axis, float scaleThis, float scaleSum, cudaStream_t stream);\n    void addMax(NVMatrix& a, int axis, float scaleThis, float scaleMax);\n    void addMax(NVMatrix& a, int axis, float scaleThis, float scaleMax, cudaStream_t stream);\n    void sum(int axis, NVMatrix& target, cudaStream_t stream);\n    void sum(int axis, NVMatrix& target);\n    void sum(int axis, NVMatrix& target, cudaStream_t stream, NVMatrix& tmp);\n    void sum(int axis, NVMatrix& target, NVMatrix& tmp);\n    NVMatrix& sum(int axis);\n    void max(int axis, NVMatrix& target);\n    void max(int axis, NVMatrix& target, NVMatrix& tmp);\n    NVMatrix& max(int axis);\n    void min(int axis, NVMatrix& target);\n    NVMatrix& min(int axis);\n    void sumOfSquares(int axis, NVMatrix& target, cudaStream_t stream);\n    void sumOfSquares(int axis, NVMatrix& target);\n    NVMatrix& sumOfSquares(int axis);\n    float mean();\n    float sum();\n    float sum(NVMatrix& tmpbuf);\n    float max();\n    float min();\n    float countInf();\n    float countNan();\n    float norm2();\n    float norm();\n\n    void inRangeInc(float lower, float upper);\n    void inRangeInc(float lower, float upper, NVMatrix& target);\n    void inRangeExc(float lower, float upper);\n    void inRangeExc(float lower, float upper, NVMatrix& target);\n    void biggerThanScalar(float scalar);\n    void biggerThanScalar(float scalar, NVMatrix& target);\n    void smallerThanScalar(float scalar);\n    void smallerThanScalar(float scalar, NVMatrix& target);\n    void addScalar(float scaleThis, float scalar, NVMatrix& target);\n    void addScalar(float scalar, NVMatrix& target);\n    void addScalar(float scalar);\n    void minWithScalar(float scalar, NVMatrix& target);\n    void minWithScalar(float scalar);\n    void maxWithScalar(float scalar, NVMatrix& target);\n    void maxWithScalar(float scalar);\n    void pow(float p, NVMatrix& target);\n    void pow(float p);\n    void scale(float _scale);\n    void scale(float _scale, NVMatrix& target);\n    void scale(float _scale, NVMatrix& target, cudaStream_t stream);\n    void scale(float _scale, cudaStream_t stream);\n    void zero();\n    void zero(NVMatrix& like);\n\n    float dotProduct(NVMatrix& b, NVMatrix& tmp, cudaStream_t stream);\n    float dotProduct(NVMatrix& b, cudaStream_t stream);\n    float dotProduct(NVMatrix& b);\n\n    /*\n     * Does SOFT transpose and returns result, leaving this matrix unchanged\n     */\n    NVMatrix& getTranspose();\n    NVMatrix& getClone();\n\n    /*\n     * Does HARD transpose and puts result in target\n     */\n    void transpose(NVMatrix& target);\n\n    /*\n     * Does SOFT transpose\n     */\n    void transpose();\n    bool transpose(bool trans);\n\n    void flipTrans(NVMatrix& target, cudaStream_t stream);\n    void flipTrans(NVMatrix& target);\n    NVMatrix& flipTrans();\n\n    void print(int startRow, int rows, int startCol, int cols) const;\n    void print(int rows, int cols) const;\n    void printShape(const char* name) const;\n\n    template <class Op> void applyBinaryV(Op op, NVMatrix& vec, NVMatrix& target) {\n        applyBinaryV(op, vec, target, getDefaultStream());\n    }\n\n    template <class Op> void applyBinaryV(Op op, NVMatrix& vec, NVMatrix& target, cudaStream_t stream) {\n        assert(&target != &vec); // for now\n        if (isSameDims(vec)) {\n            applyBinary(op, vec, target, stream);\n            return;\n        }\n        assert(vec.getNumRows() == 1 || vec.getNumCols() == 1);\n        assert(vec.getNumRows() == _numRows || vec.getNumCols() == _numCols);\n        assert(vec.isContiguous());\n\n        target.resize(*this); // target must be same orientation as me for now\n        int width = getLeadingDim(); //_isTrans ? _numRows : _numCols;\n        int height = getFollowingDim(); //_isTrans ? _numCols : _numRows;\n        dim3 threads(ADD_VEC_THREADS_X, ADD_VEC_THREADS_Y);\n\n        if ((vec.getNumRows() == _numRows && !isTrans()) || (vec.getNumCols() == _numCols && isTrans())) {\n            dim3 blocks(std::min(512, DIVUP(width, ADD_VEC_THREADS_X)), std::min(NUM_BLOCKS_MAX, DIVUP(height, ADD_VEC_THREADS_Y)));\n            kColVectorOp<Op><<<blocks, threads, 0, stream>>>(getDevData(), vec.getDevData(), target.getDevData(), width, height, getStride(), target.getStride(), op);\n        } else {\n            dim3 blocks(std::min(NUM_BLOCKS_MAX, DIVUP(width, ADD_VEC_THREADS_X)), std::min(NUM_BLOCKS_MAX, DIVUP(height, ADD_VEC_THREADS_Y)));\n            kRowVectorOp<Op><<<blocks, threads, 0, stream>>>(getDevData(), vec.getDevData(), target.getDevData(), width, height, getStride(), target.getStride(), op);\n        }\n        getLastCudaError(\"Kernel execution failed\");\n    //    cudaThreadSynchronize();\n    }\n\n    template<class UnaryOperator> float argMax(UnaryOperator u) {\n       return _totalAgg(NVMatrixAggs::ArgMax<UnaryOperator>(u));\n    }\n    static void batchedMatrixMultiply(NVMatrixV& a, NVMatrixV& b, NVMatrixV& target, float scaleTarget, float scaleAB, cudaStream_t stream, const float** aPtrsDev, const float** bPtrsDev, float** tgtPtrsDev);\n    static void batchedMatrixMultiply(NVMatrixV& a, NVMatrixV& b, NVMatrixV& target, float scaleTarget, float scaleAB, cudaStream_t stream);\n    static void batchedMatrixMultiply(NVMatrixV& a, NVMatrixV& b, NVMatrixV& target, float scaleTarget, float scaleAB, const float** aPtrsDev, const float** bPtrsDev, float** tgtPtrsDev);\n    static void batchedMatrixMultiply(NVMatrixV& a, NVMatrixV& b, NVMatrixV& target, float scaleTarget, float scaleAB);\n\n    static void assertSame(NVMatrixV& a);\n};\n\nclass HostNVMatrix : public NVMatrix {\nprotected:\n    void alloc(int numElements);\n    void dealloc();\n    NVMatrix& construct() const;\n    NVMatrix& construct(bool isTrans) const;\n    NVMatrix& construct(int numRows, int numCols, bool isTrans=false) const;\n    NVMatrix& construct(const Matrix& like, bool copy) const;\n    NVMatrix& construct(const NVMatrix& like, bool copy) const;\n    NVMatrix& construct(const NVMatrix& like) const;\n    NVMatrix& construct(const Matrix& like) const;\n    NVMatrix& construct(MemorySegment* mem, int numRows, int numCols, int stride, bool isTrans) const;\npublic:\n    ~HostNVMatrix();\n    HostNVMatrix();\n    HostNVMatrix(bool isTrans);\n    HostNVMatrix(int numRows, int numCols, bool isTrans=false);\n    HostNVMatrix(const Matrix& like, bool copy);\n    HostNVMatrix(const NVMatrix& like, bool copy);\n    HostNVMatrix(const NVMatrix& like);\n    HostNVMatrix(const Matrix& like);\n    HostNVMatrix(MemorySegment* mem, int numRows, int numCols, int stride, bool isTrans);\n    void copyFromHost(const Matrix& hostMatrix);\n    void copyFromHost(const Matrix& hostMatrix, bool resizeTarget);\n    void copyFromHost(const Matrix& hostMatrix, bool resizeTarget, cudaStream_t stream);\n    void copyToHost(Matrix& hostMatrix) const;\n    void copyToHost(Matrix& hostMatrix, bool resizeTarget) const;\n    void copyToHost(Matrix& hostMatrix, bool resizeTarget, cudaStream_t stream) const;\n    cudaTextureObject_t getTextureObject();\n};\n\n#endif /* NVMATRIX_H_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/nvmatrix/include/nvmatrix_kernels.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef NVMATRIX_KERNEL_H_\n#define NVMATRIX_KERNEL_H_\n\n#include <curand_kernel.h>\n\n#if defined(_WIN64) || defined(_WIN32)\n#define uint unsigned int\n#endif\n\n#define NUM_BLOCKS_MAX                      65535\n#define TEXTURE_SIZE_MAX                    (1<<29)\n\n#define NUM_RND_BLOCKS                      96\n#define NUM_RND_THREADS_PER_BLOCK           128\n#define NUM_RND_STREAMS                     (NUM_RND_BLOCKS * NUM_RND_THREADS_PER_BLOCK)\n\n/*\n * Default grid/block sizes for the various functions.\n */\n#define ADD_BLOCK_SIZE                      16\n\n#define NUM_TILE_BLOCKS                     4096\n#define NUM_TILE_THREADS_PER_BLOCK          512\n\n#define ELTWISE_THREADS_X                   32\n#define ELTWISE_THREADS_Y                   8\n\n#define ELTWISE_FLAT_THREADS_X              128\n\n#define NUM_SUM_COLS_THREADS_PER_BLOCK      128\n\n#define AGG_SHORT_ROWS_THREADS_X            32\n#define AGG_SHORT_ROWS_THREADS_Y            8\n#define AGG_SHORT_ROWS_LOOPS_Y              32\n\n#define DP_BLOCKSIZE                        512\n#define CPUSUM_MAX                          4096\n\n#define ADD_VEC_THREADS_X                   64\n#define ADD_VEC_THREADS_Y                   4\n\n#ifndef DIVUP\n#define DIVUP(x, y) (((x) + (y) - 1) / (y))\n#endif\n\n#define MYMAX(a, b) ((a) > (b) ? (a) : (b))\n\n#ifndef MUL24 // legacy\n#define MUL24(x,y) ((x) * (y))\n#endif\n\n#define AWR_NUM_THREADS           256\n#define WARP_SIZE                 32\n#define AWR_NUM_WARPS             AWR_NUM_THREADS / WARP_SIZE \n#define AWR_LOG_NUM_THREADS       8\n#define LOG_WARP_SIZE             5\n#define AWR_LOG_NUM_WARPS         3\n\n#define DEVICE_HOST               -1\n#define DEVICE_NULL               -2\n\n__global__ void kTile(const float* src, float* tgt, const uint srcWidth, const uint srcHeight, const uint tgtWidth, const uint tgtHeight);\n__global__ void kDotProduct_r(float* a, float* b, float* target, const uint numElements);\n__global__ void kSetupCurand(curandState *state, unsigned long long seed);\n\ntemplate<typename T> \n__device__ T shfl_down(T a, int b, int c=WARP_SIZE) {\n#if __CUDA_ARCH__ >= 300\n    return __shfl_down(a, b, c);\n#else\n    return 0;\n#endif\n}\n\n/*\n * For now this is supported only for arrays with the same transposedness.\n */\ntemplate<class Op>\n__global__ void kEltwiseTernaryOp(const float* a, const float* b, const float* c, float* const dest,\n                                  const uint height, const uint width, uint strideA, const uint strideB, const uint strideC,\n                                  const uint strideDest, Op op) {\n    const uint idxX = blockIdx.x * ELTWISE_THREADS_X + threadIdx.x;\n    const uint idxY = blockIdx.y * ELTWISE_THREADS_Y + threadIdx.y;\n\n    for (uint y = idxY; y < height; y += gridDim.y * ELTWISE_THREADS_Y) {\n        for (uint x = idxX; x < width; x += gridDim.x * ELTWISE_THREADS_X) {\n            dest[y * strideDest + x] = op(a[y * strideA + x], b[y * strideB + x], c[y * strideC + x]);\n        }\n    }\n}\n\ntemplate<class Op>\n__global__ void kEltwiseTernaryOpFlat(const float* a, const float* b, const float* c, float* const dest, const uint numElements, Op op) {\n    const uint idxX = blockIdx.x * ELTWISE_FLAT_THREADS_X + threadIdx.x;\n\n    for (uint x = idxX; x < numElements; x += gridDim.x * ELTWISE_FLAT_THREADS_X) {\n        dest[x] = op(a[x], b[x], c[x]);\n    }\n}\n\n\n/*\n * dest here is assumed to be \"not transposed\" -- height and width correspond to it.\n * b is assumed to be transposed.\n * a can be either transposed or not -- depending on parameter.\n * \n * Performs dest := op(a, b)\n */\ntemplate<class Op, bool checkBounds, bool aTrans, bool reverse>\n__global__ void kEltwiseBinaryOpTrans(const float* a, const float* b, float* const dest,\n                             const uint height, const uint width,\n                             const uint strideA, const uint strideB, const uint strideDest, Op op) {\n\n    __shared__ float shmem[ELTWISE_THREADS_X][ELTWISE_THREADS_X + 1];\n\n    // x here because that's how much work we do\n    for (uint by = ELTWISE_THREADS_X * blockIdx.y; by < height; by += ELTWISE_THREADS_X * gridDim.y) {\n        for (uint bx = ELTWISE_THREADS_X * blockIdx.x; bx < width; bx += ELTWISE_THREADS_X * gridDim.x) {\n            const uint readX = by + threadIdx.x;\n            const uint readY = bx + threadIdx.y;\n\n            for (uint y = 0; y < ELTWISE_THREADS_X; y+= ELTWISE_THREADS_Y) {\n                if (!checkBounds || (readX < height && readY + y < width)) {\n                    if (aTrans) {\n                        shmem[threadIdx.x][threadIdx.y + y] = reverse ? op(b[(readY+y) * strideB + readX], a[(readY+y) * strideA + readX])\n                                                                      : op(a[(readY+y) * strideA + readX], b[(readY+y) * strideB + readX]);\n                    } else {\n                        shmem[threadIdx.x][threadIdx.y + y] = b[(readY+y) * strideB + readX];\n                    }\n                }\n            }\n            __syncthreads();\n\n            const uint writeX = bx + threadIdx.x;\n            const uint writeY = by + threadIdx.y;\n\n            for (uint y = 0; y < ELTWISE_THREADS_X; y+= ELTWISE_THREADS_Y) {\n                if(!checkBounds || (writeX < width && writeY + y < height)) {\n                    if (aTrans) {\n                        dest[(writeY + y) * strideDest + writeX] = shmem[threadIdx.y + y][threadIdx.x];\n                    } else {\n                        dest[(writeY + y) * strideDest + writeX] = reverse ? op(shmem[threadIdx.y + y][threadIdx.x], a[(writeY + y) * strideA + writeX])\n                                                                           : op(a[(writeY + y) * strideA + writeX], shmem[threadIdx.y + y][threadIdx.x]);\n                    }\n                }\n            }\n            __syncthreads();\n        }\n    }\n}\ntemplate<class Op>\n__global__ void kEltwiseBinaryOp(const float* a, const float* b, float* const dest, const uint height, const uint width,\n                             const uint strideA, const uint strideB, const uint strideDest, Op op) {\n    const uint idxX = blockIdx.x * ELTWISE_THREADS_X + threadIdx.x;\n    const uint idxY = blockIdx.y * ELTWISE_THREADS_Y + threadIdx.y;\n\n    for (uint y = idxY; y < height; y += gridDim.y * ELTWISE_THREADS_Y) {\n        for (uint x = idxX; x < width; x += gridDim.x * ELTWISE_THREADS_X) {\n            dest[y * strideDest + x] = op(a[y * strideA + x], b[y * strideB + x]);\n        }\n    }\n}\n\ntemplate<class Op>\n__global__ void kEltwiseBinaryOpFlat(const float* a, const float* b, float* const dest, const uint numElements, Op op) {\n    const uint idxX = blockIdx.x * ELTWISE_FLAT_THREADS_X + threadIdx.x;\n\n    for (uint x = idxX; x < numElements; x += gridDim.x * ELTWISE_FLAT_THREADS_X) {\n        dest[x] = op(a[x], b[x]);\n    }\n}\n\n/*\n * dest here is assumed to be \"not transposed\" -- height and width correspond to it.\n */\ntemplate<class Op, bool checkBounds>\n__global__ void kEltwiseUnaryOpTrans(const float* a, float* const dest,\n                                     const uint height, const uint width,\n                                     const uint strideA, const uint strideDest, Op op) {\n\n    __shared__ float shmem[ELTWISE_THREADS_X][ELTWISE_THREADS_X + 1];\n\n    for (uint by = ELTWISE_THREADS_X * blockIdx.y; by < height; by += ELTWISE_THREADS_X * gridDim.y) {\n        for (uint bx = ELTWISE_THREADS_X * blockIdx.x; bx < width; bx += ELTWISE_THREADS_X * gridDim.x) {\n            const uint readX = by + threadIdx.x;\n            const uint readY = bx + threadIdx.y;\n            for (uint y = 0; y < ELTWISE_THREADS_X; y+= ELTWISE_THREADS_Y) {\n                if (!checkBounds || (readX < height && readY + y < width)) {\n                    shmem[threadIdx.x][threadIdx.y + y] = op(a[(readY + y) * strideA + readX]);\n                }\n            }\n            __syncthreads();\n\n            const uint writeX = bx + threadIdx.x;\n            const uint writeY = by + threadIdx.y;\n            for (uint y = 0; y < ELTWISE_THREADS_X; y+= ELTWISE_THREADS_Y) {\n                if(!checkBounds || (writeX < width && writeY + y < height)) {\n                    dest[(writeY + y) * strideDest + writeX] = shmem[threadIdx.y + y][threadIdx.x];\n\n                }\n            }\n            __syncthreads();\n        }\n    }\n}\n\ntemplate<class Op>\n__global__ void kEltwiseUnaryOpFlat(const float* a, float* const dest, const uint numElements, Op op) {\n    const uint idxX = blockIdx.x * ELTWISE_FLAT_THREADS_X + threadIdx.x;\n\n    for (uint x = idxX; x < numElements; x += gridDim.x * ELTWISE_FLAT_THREADS_X) {\n        dest[x] = op(a[x]);\n    }\n}\n\ntemplate<class Op>\n__global__ void kEltwiseUnaryOp(const float* a, float* const dest, const uint height, const uint width,\n                                const uint strideA, const uint strideDest, Op op) {\n    const uint idxX = blockIdx.x * ELTWISE_THREADS_X + threadIdx.x;\n    const uint idxY = blockIdx.y * ELTWISE_THREADS_Y + threadIdx.y;\n\n    for (uint y = idxY; y < height; y += gridDim.y * ELTWISE_THREADS_Y) {\n        for (uint x = idxX; x < width; x += gridDim.x * ELTWISE_THREADS_X) {\n            dest[y * strideDest + x] = op(a[y * strideA + x]);\n        }\n    }\n}\n\n/*\n * Matrix in ROW-MAJOR order!\n */\ntemplate <class Op>\n__global__ void kRowVectorOp(const float* mat, const float* vec, float* const tgtMat, const uint width, const uint height,\n                             const uint matStride, const uint tgtStride, Op op) {\n    __shared__ float shVec[ADD_VEC_THREADS_X];\n    const uint bx = ADD_VEC_THREADS_X * blockIdx.x;\n    const uint by = ADD_VEC_THREADS_Y * blockIdx.y;\n\n    for (uint x = bx; x < width; x += gridDim.x * ADD_VEC_THREADS_X) {\n        __syncthreads();\n        if (x + threadIdx.x < width && threadIdx.y == 0) {\n            shVec[threadIdx.x] = vec[x + threadIdx.x];\n        }\n        __syncthreads();\n\n        if (x + threadIdx.x < width) {\n            for (uint y = by + threadIdx.y; y < height; y += gridDim.y * ADD_VEC_THREADS_Y) {\n                tgtMat[y * tgtStride + x + threadIdx.x] = op(mat[y * matStride + x + threadIdx.x], shVec[threadIdx.x]);\n            }\n        }\n    }\n}\n\n/*\n * Matrix in ROW-MAJOR order!\n */\ntemplate <class Op>\n__global__ void kColVectorOp(float* mat, float* vec, float* tgtMat,\n                             const uint width, const uint height,\n                             const uint matStride, const uint tgtStride, Op op) {\n    __shared__ float shVec[ADD_VEC_THREADS_Y];\n    const uint by = ADD_VEC_THREADS_Y * blockIdx.y;\n    const uint bx = ADD_VEC_THREADS_X * blockIdx.x;\n    const uint tidx = ADD_VEC_THREADS_X * threadIdx.y + threadIdx.x;\n    \n    mat += threadIdx.y * matStride;\n    vec += tidx;\n    tgtMat += threadIdx.y * tgtStride;\n\n    for (uint y = by; y < height; y += gridDim.y * ADD_VEC_THREADS_Y) {\n        __syncthreads();\n        if (y + tidx < height && tidx < ADD_VEC_THREADS_Y) {\n            shVec[tidx] = vec[y];\n        }\n        __syncthreads();\n\n        if (y + threadIdx.y < height) {\n            for (uint x = bx + threadIdx.x; x < width; x += gridDim.x * ADD_VEC_THREADS_X) {\n                tgtMat[(y) * tgtStride + x] = op(mat[(y) * matStride + x], shVec[threadIdx.y]);\n            }\n        }\n    }\n}\n\n/*\n * This one gets coalesced reads but computes only a partial sum which\n * must either be summed again (recursively) or summed on the host.\n */\ntemplate<class Agg, class UnaryOp, class BinaryOp, int blockSize>\n__global__ void kAggRows(const float* mat, float* matSum, const uint width, const uint height, const uint sumWidth, Agg agg, UnaryOp uop, BinaryOp bop) {\n    const int idxX = blockIdx.x * blockSize*2 + threadIdx.x;\n\n    __shared__ float accum[blockSize*2];\n\n    matSum += blockIdx.y * sumWidth + blockIdx.x;\n    /*\n     * Here it's important to make sure that all threads in a block call __syncthreads,\n     * so I have even the redundant threads (for which idxX >= width) enter this loop\n     * just so that they may call __syncthreads at the appropriate times.\n     */\n    mat += width * blockIdx.y + idxX;\n\n    accum[threadIdx.x] = agg.getBaseValue();\n    accum[threadIdx.x + blockSize] = agg.getBaseValue();\n    for (uint idxY = blockIdx.y; idxY < height; idxY += gridDim.y) {\n        if (idxX < width) {\n            accum[threadIdx.x] = uop(mat[0]);\n            if(idxX + blockSize < width)\n                accum[threadIdx.x + blockSize] = uop(mat[blockSize]);\n        }\n        if (blockSize >= 512) {\n            __syncthreads();\n            if (threadIdx.x < 512)\n                accum[threadIdx.x] = agg(accum[threadIdx.x], accum[threadIdx.x + 512]);\n        }\n        if (blockSize >= 256) {\n            __syncthreads();\n            if (threadIdx.x < 256)\n                accum[threadIdx.x] = agg(accum[threadIdx.x],accum[threadIdx.x + 256]);\n        }\n        if (blockSize >= 128) {\n            __syncthreads();\n            if (threadIdx.x < 128)\n                accum[threadIdx.x] = agg(accum[threadIdx.x],accum[threadIdx.x + 128]);\n        }\n        if (blockSize >= 64) {\n            __syncthreads();\n            if (threadIdx.x < 64)\n                accum[threadIdx.x] = agg(accum[threadIdx.x],accum[threadIdx.x + 64]);\n        }\n\n        __syncthreads();\n        volatile float* myAccum = &accum[threadIdx.x];\n        if (threadIdx.x < 32) { // executed only by first warp\n            myAccum[0] = agg(myAccum[0], myAccum[32]);\n            myAccum[0] = agg(myAccum[0], myAccum[16]);\n            myAccum[0] = agg(myAccum[0], myAccum[8]);\n            myAccum[0] = agg(myAccum[0], myAccum[4]);\n            myAccum[0] = agg(myAccum[0], myAccum[2]);\n            myAccum[0] = agg(myAccum[0], myAccum[1]);\n        }\n\n        if (threadIdx.x == 0) {\n            matSum[0] = bop(matSum[0], myAccum[0]);\n            matSum += gridDim.y * sumWidth;\n        }\n        __syncthreads();\n        mat += width * gridDim.y;\n    }\n}\n\ntemplate<class Agg, class BinaryOp>\n__global__ void kAggRows_wholerow(const float* mat, float* matSum, const uint width, const uint height, Agg agg, BinaryOp op) {\n    const int tidx = threadIdx.x;\n\n    __shared__ float accum[AWR_NUM_THREADS];\n    volatile float* vMyAccum = &accum[tidx];\n    float* myAccum = &accum[tidx];\n    \n    matSum += blockIdx.y;\n    mat += width * blockIdx.y;\n\n    for (uint idxY = blockIdx.y; idxY < height; idxY += gridDim.y) {\n        myAccum[0] = agg.getBaseValue();\n        for (uint x = tidx; x < width; x += AWR_NUM_THREADS) {\n            myAccum[0] = agg(myAccum[0], mat[x]);\n        }\n        #pragma unroll\n        for (uint i = AWR_LOG_NUM_THREADS - 1; i > LOG_WARP_SIZE; i--) {\n            const uint d = 1 << i;\n            __syncthreads();\n            if (tidx < d) {\n                myAccum[0] = agg(myAccum[0], myAccum[d]);\n            }\n        }\n        __syncthreads();\n        if (tidx < WARP_SIZE) {\n            #pragma unroll\n            for (int i = LOG_WARP_SIZE; i >= 0; i--) {\n                const uint d = 1 << i;\n                vMyAccum[0] = agg(vMyAccum[0], vMyAccum[d]);\n            }\n\n            if (tidx == 0) {\n                matSum[0] = op(matSum[0], vMyAccum[0]);\n                matSum += gridDim.y;\n            }\n        }\n        __syncthreads();\n        mat += width * gridDim.y;\n    }\n}\n\n/*\n * Implements multiscan idea from http://www.moderngpu.com\n * Not really useful for pure reductions but neat nonetheless.\n */\ntemplate<class Agg, class UnaryOp, class BinaryOp>\n__global__ void kAggRows_wholerow_nosync(const float* mat, float* matSum, const uint width, const uint height,\n                                         Agg agg, UnaryOp uop, BinaryOp bop) {\n    const uint tidx = threadIdx.x;\n    const uint warpIdx = tidx / WARP_SIZE;\n    const uint lane = tidx % WARP_SIZE;\n    \n    __shared__ float accum[(WARP_SIZE + 1) * AWR_NUM_WARPS];\n    __shared__ float finalAccum[AWR_NUM_WARPS];\n\n    float* myAccum = &accum[warpIdx * (WARP_SIZE + 1) + lane];\n    float* myFinalAccum = &finalAccum[tidx];\n    //volatile float* vMyAccum = &accum[warpIdx * (WARP_SIZE + 1) + lane];\n    matSum += blockIdx.y;\n    mat += width * blockIdx.y;\n\n    float rAccum = agg.getBaseValue(); // cache in register, a bit faster than shmem\n    #pragma unroll 32\n    for (uint x = tidx; x < width; x += AWR_NUM_THREADS) {\n        rAccum = agg(rAccum, uop(mat[x]));\n    }\n    myAccum[0] = rAccum;\n    \n    // Each warp does a reduction that doesn't require synchronizatoin\n    #pragma unroll\n    for (uint i = 0; i < LOG_WARP_SIZE; i++) {\n        const uint d = 1 << i;\n        myAccum[0] = agg(myAccum[0], shfl_down(myAccum[0], d));\n    }\n    __syncthreads();\n    // The warps write their results\n    if (tidx < AWR_NUM_WARPS) {\n        //volatile float* vMyFinalAccum = &finalAccum[tidx];\n        myFinalAccum[0] = accum[tidx * (WARP_SIZE + 1)];\n        #pragma unroll\n        for (uint i = 0; i < AWR_LOG_NUM_WARPS; i++) {\n            const uint d = 1 << i;\n            myFinalAccum[0] = agg(myFinalAccum[0], shfl_down(myFinalAccum[0], d));\n        }\n        if (tidx == 0) {\n            matSum[0] = bop(matSum[0], myFinalAccum[0]);\n            matSum += gridDim.y;\n        }\n    }\n}\n\n/*\n * To be used when the rows are <= 64.\n *\n * TODO: try to reduce reg usage. i think this can be made faster too.\n */\n//#define AGG_SHORT_ROWS_LOOPS_X  4\ntemplate <class Agg, class UnaryOp, class BinaryOp, int LOOPS_X, int THREADS_X>\n__global__ void kAggShortRows(const float* mat, float* matSum, const uint width, const uint height, Agg agg, UnaryOp uop, BinaryOp bop) {\n    const uint shmemX = THREADS_X + 1;\n    __shared__ float shmem[AGG_SHORT_ROWS_THREADS_Y*shmemX];\n\n    const uint tidx = threadIdx.y * THREADS_X + threadIdx.x;\n    const uint ty = LOOPS_X == 1 ? tidx / width : threadIdx.y; // when loops==1, width is gonna be smaller than block x dim\n    const uint tx = LOOPS_X == 1 ? tidx % width : threadIdx.x;\n    const uint bidx = blockIdx.y * gridDim.x + blockIdx.x;\n    const uint blockRowIdx = bidx * AGG_SHORT_ROWS_LOOPS_Y * AGG_SHORT_ROWS_THREADS_Y;\n    float* shmemWrite = shmem + MUL24(ty, shmemX) + tx;\n    matSum += blockRowIdx + tidx;\n//    shmem[MUL24(threadIdx.y, shmemX) + threadIdx.x] = 0;\n    mat += width * blockRowIdx + MUL24(ty, width) + tx;\n    float* shmemWriteZeros = &shmem[MUL24(threadIdx.y,shmemX) + threadIdx.x];\n\n    bool doAgg = tidx < AGG_SHORT_ROWS_THREADS_Y ;\n\n    if (blockRowIdx < height) {\n#pragma unroll\n        for (uint y = 0; y < AGG_SHORT_ROWS_LOOPS_Y*AGG_SHORT_ROWS_THREADS_Y; y += AGG_SHORT_ROWS_THREADS_Y) {\n            doAgg &= tidx + y + blockRowIdx < height;\n            const bool heightIdxOK = ty < AGG_SHORT_ROWS_THREADS_Y && ty + y + blockRowIdx < height;\n\n            shmemWriteZeros[0] = agg.getBaseValue();\n            __syncthreads();\n#pragma unroll\n            for(uint x = 0; x < LOOPS_X * THREADS_X; x+= THREADS_X) {\n//                __syncthreads();\n                if (heightIdxOK && x + tx < width) {\n                    shmemWrite[0] = agg(uop(mat[x]), shmemWrite[0]);\n                }\n            }\n            __syncthreads();\n            if (doAgg) {\n                /*\n                 * I tried doing this final sum as a 4-step reduction, with 8 threads\n                 * per warp participating. It was slightly slower.\n                 */\n                float accum = agg.getBaseValue();\n                float* shmemRead = shmem + MUL24(tidx, shmemX);\n                // this loops too much if the rows are really short :(\n#pragma unroll\n                for (uint i = 0; i < THREADS_X; i++) {\n                    accum = agg(accum, shmemRead[0]);\n                    shmemRead++;\n                }\n                matSum[0] = bop(matSum[0], accum);\n                matSum += AGG_SHORT_ROWS_THREADS_Y;\n            }\n            __syncthreads();\n            mat += width * AGG_SHORT_ROWS_THREADS_Y;\n        }\n    }\n}\n\ntemplate <class Agg, class UnaryOp, class BinaryOp>\n__global__ void kAggShortRows2(const float* mat, float* matSum, const uint width, const uint height, Agg agg, UnaryOp uop, BinaryOp bop) {\n    const uint shmemX = AGG_SHORT_ROWS_THREADS_X + 1;\n    __shared__ float shmem[AGG_SHORT_ROWS_THREADS_Y*shmemX];\n    const uint LOOPS_X = DIVUP(width, AGG_SHORT_ROWS_THREADS_X);\n    const uint tidx = threadIdx.y * AGG_SHORT_ROWS_THREADS_X + threadIdx.x;\n\n    const uint bidx = blockIdx.y * gridDim.x + blockIdx.x;\n    const uint blockRowIdx = bidx * AGG_SHORT_ROWS_LOOPS_Y * AGG_SHORT_ROWS_THREADS_Y;\n\n    float* shmemWrite = shmem + MUL24(threadIdx.y, shmemX) + threadIdx.x;\n    matSum += blockRowIdx + tidx;\n//    shmem[MUL24(threadIdx.y, shmemX) + threadIdx.x] = 0;\n    mat += width * blockRowIdx + MUL24(threadIdx.y, width) + threadIdx.x;\n\n    bool doAgg = tidx < AGG_SHORT_ROWS_THREADS_Y;\n    if(blockRowIdx < height) {\n        for (uint y = 0; y < AGG_SHORT_ROWS_LOOPS_Y*AGG_SHORT_ROWS_THREADS_Y; y += AGG_SHORT_ROWS_THREADS_Y) {\n            doAgg &= tidx + y + blockRowIdx < height;\n            const bool heightIdxOK = threadIdx.y + y + blockRowIdx < height;\n            float accum = agg.getBaseValue();\n            shmemWrite[0] = agg.getBaseValue();\n\n            for(uint x = 0; x < LOOPS_X * AGG_SHORT_ROWS_THREADS_X; x+= AGG_SHORT_ROWS_THREADS_X) {\n//                __syncthreads();\n                if (heightIdxOK && x + threadIdx.x < width) {\n                    shmemWrite[0] = agg(uop(mat[x]), shmemWrite[0]);\n                }\n            }\n\n            __syncthreads();\n            if (doAgg) {\n                float* shmemRead = shmem + MUL24(tidx, shmemX);\n\n#pragma unroll\n                for (uint i = 0; i < AGG_SHORT_ROWS_THREADS_X; i++) {\n                    accum = agg(accum, shmemRead[0]);\n                    shmemRead++;\n                }\n\n                matSum[0] = bop(matSum[0], accum);\n                matSum += AGG_SHORT_ROWS_THREADS_Y;\n            }\n            __syncthreads();\n            mat += width * AGG_SHORT_ROWS_THREADS_Y;\n        }\n    }\n}\n\n/*\n * Bad when there are few columns.\n */\ntemplate <class Agg, class UnaryOp, class BinaryOp>\n__global__ void kDumbAggCols(cudaTextureObject_t mat, float* const vec, const uint width, const uint height, Agg agg, UnaryOp uop, BinaryOp bop) {\n    const uint idx = blockIdx.x * blockDim.x + threadIdx.x;\n    if (idx < width) {\n        float mx = agg.getBaseValue();\n        for (uint j = 0; j < height; j++) {\n            mx = agg(uop(tex1Dfetch<float>(mat, width * j + idx)), mx);\n        }\n        vec[idx] = bop(vec[idx], mx);\n    }\n}\n\n/*\n * Better with few columns because it only computes a partial sum.\n */\ntemplate <class Agg, class UnaryOp>\n__global__ void kAggCols(cudaTextureObject_t mat, float* const vec, const uint width, const uint height, const uint sumLength, Agg agg, UnaryOp op) {\n    const uint idxX = blockIdx.x * blockDim.x + threadIdx.x;\n    const uint idxY = blockIdx.y * sumLength;\n    if (idxX < width) {\n        float mx = agg.getBaseValue();\n        for (uint j = idxY; j < min(height,idxY + sumLength); j++) {\n            mx = agg(op(tex1Dfetch<float>(mat, j * width + idxX)), mx);\n        }\n        vec[blockIdx.y * width + idxX] = mx;\n    }\n}\n\ntemplate <class Agg>\n__global__ void kTotalAgg(const float* a, float* const target, const uint numElements, Agg agg) {\n    __shared__ float shmem[DP_BLOCKSIZE];\n    uint eidx = DP_BLOCKSIZE * blockIdx.x + threadIdx.x;\n    shmem[threadIdx.x] = agg.getBaseValue();\n    if (eidx < gridDim.x * DP_BLOCKSIZE) {\n        for (; eidx < numElements; eidx += gridDim.x * DP_BLOCKSIZE) {\n            shmem[threadIdx.x] = agg(shmem[threadIdx.x], a[eidx]);\n        }\n    }\n    __syncthreads();\n    if (threadIdx.x < 256) {\n        shmem[threadIdx.x] = agg(shmem[threadIdx.x], shmem[threadIdx.x + 256]);\n    }\n    __syncthreads();\n    if (threadIdx.x < 128) {\n        shmem[threadIdx.x] = agg(shmem[threadIdx.x], shmem[threadIdx.x + 128]);\n    }\n    __syncthreads();\n    if (threadIdx.x < 64) {\n        shmem[threadIdx.x] = agg(shmem[threadIdx.x], shmem[threadIdx.x + 64]);\n    }\n    __syncthreads();\n    if (threadIdx.x < 32) {\n        volatile float* mysh = &shmem[threadIdx.x];\n        *mysh = agg(*mysh, mysh[32]);\n        *mysh = agg(*mysh, mysh[16]);\n        *mysh = agg(*mysh, mysh[8]);\n        *mysh = agg(*mysh, mysh[4]);\n        *mysh = agg(*mysh, mysh[2]);\n        *mysh = agg(*mysh, mysh[1]);\n        if (threadIdx.x == 0) {\n            target[blockIdx.x] = *mysh;\n        }\n    }\n}\n\nclass AddGaussianUnaryRandomizer {\nprivate:\n    const float stdev;\npublic:\n    AddGaussianUnaryRandomizer(float _stdev) : stdev(_stdev) {\n    }\n    __device__ inline float operator ()(float data, curandState* state) {\n        return data + stdev * curand_normal(state);\n    }\n};\n\nclass BinarizeUnaryRandomizer {\npublic:\n    __device__ inline float operator ()(float data, curandState* state) {\n        return data > curand_uniform(state);\n    }\n};\n\nclass UniformUnaryRandomizer {\npublic:\n    __device__ inline float operator ()(float data, curandState* state) {\n        return curand_uniform(state);\n    }\n};\n\nclass GaussianUnaryRandomizer {\nprivate:\n    const float mean, stdev;\npublic:\n    GaussianUnaryRandomizer(float _mean, float _stdev) : mean(_mean), stdev(_stdev) {\n    }\n    __device__ inline float operator ()(float data, curandState* state) {\n        return mean + stdev * curand_normal(state);\n    }\n};\n\ntemplate <bool var>\nclass AddGaussianBinaryRandomizer {\npublic:\n    __device__ inline float operator ()(float data, float stdev, curandState* state) {\n        return data + (var ? stdev : 1) * stdev * curand_normal(state);\n    }\n};\n\nclass GaussianBinaryRandomizer {\nprivate:\n    const float mean;\npublic:\n    GaussianBinaryRandomizer(float _mean) : mean(_mean) {\n    }\n    __device__ inline float operator ()(float data, float stdev, curandState* state) {\n        return mean + stdev * curand_normal(state);\n    }\n};\n\nclass ScaledGaussianBinaryRandomizer {\nprivate:\n    const float mean, stdevScale;\npublic:\n    ScaledGaussianBinaryRandomizer(float _mean, float _stdevScale) : mean(_mean), stdevScale(_stdevScale) {\n    }\n    __device__ inline float operator ()(float data, float stdev, curandState* state) {\n        return mean + stdevScale * stdev * curand_normal(state);\n    }\n};\n\ntemplate<class Randomizer>\n__global__ void kUnaryRandomize(float* data, float* targets, curandState* state, const uint numElements, Randomizer rnd) {\n    const uint tidx = NUM_RND_THREADS_PER_BLOCK * blockIdx.x + threadIdx.x;\n    curandState localState = state[tidx];\n\n    for (uint i = tidx; i < numElements; i += NUM_RND_STREAMS) {\n        targets[i] = rnd(data[i], &localState);\n    }\n    state[tidx] = localState;\n}\n\ntemplate<class Randomizer>\n__global__ void kBinaryRandomize(float* data, float* data2, float* targets, curandState* state, const uint numElements, Randomizer rnd) {\n    const uint tidx = NUM_RND_THREADS_PER_BLOCK * blockIdx.x + threadIdx.x;\n    curandState localState = state[tidx];\n\n    for (uint i = tidx; i < numElements; i += NUM_RND_STREAMS) {\n        targets[i] = rnd(data[i], data2[i], &localState);\n    }\n    state[tidx] = localState;\n}\n\n#endif /* NVMATRIX_KERNEL_H_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/nvmatrix/include/nvmatrix_operators.cuh",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef NVMATRIX_OPERATORS_CUH\n#define\tNVMATRIX_OPERATORS_CUH\n\nclass NVMatrixOps {\npublic:\n    class Exp {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return __expf(a);\n        }\n    };\n\n    class Logistic {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return __fdividef(1.0f, 1.0f + __expf(-a));\n        }\n    };\n\n    class Log {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return __logf(a);\n        }\n    };\n\n    class Square {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return a * a;\n        }\n    };\n\n    class Sqrt {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return sqrtf(a);\n        }\n    };\n\n    class SqrtAbs {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return sqrtf(fabsf(a));\n        }\n    };\n\n    class Reciprocal {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return 1.0f / a;\n        }\n    };\n\n    class Abs {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return a > 0 ? a : -a;\n        }\n    };\n\n    class Sign {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return (a > 0) - (a < 0);\n        }\n    };\n\n    class Identity {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return a;\n        }\n    };\n\n    class Zero {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return 0;\n        }\n    };\n\n    class One {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return 1;\n        }\n    };\n\n    class Const {\n    private:\n        const float scalar;\n    public:\n        Const(const float _scalar) : scalar(_scalar) {\n        }\n        __device__ inline float operator()(const float a) const {\n            return scalar;\n        }\n    };\n\n    class OneMinus {\n    public:\n        __device__ inline float operator()(const float x) const {\n            return 1.0f - x;\n        }\n    };\n\n    class Linear {\n    protected:\n        float _a, _b;\n    public:\n        __device__ inline float operator()(float x) const {\n            return _a * x + _b;\n        }\n        Linear(float a, float b) : _a(a), _b(b) {\n        }\n    };\n\n    class IsNan {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return isnan(a);\n        }\n    };\n\n    class IsInf {\n    public:\n        __device__ inline float operator()(const float a) const {\n            return isinf(a);\n        }\n    };\n\n    class SmallerThanScalar {\n    private:\n        const float scalar;\n    public:\n        SmallerThanScalar(const float _scalar) : scalar(_scalar) {\n        }\n        __device__ inline float operator()(const float a) const {\n            return a < scalar;\n        }\n    };\n\n    class BiggerThanScalar {\n    private:\n        const float scalar;\n    public:\n        BiggerThanScalar(const float _scalar) : scalar(_scalar) {\n        }\n        __device__ inline float operator()(const float a) const {\n            return a > scalar;\n        }\n    };\n\n    class AddScalar {\n    private:\n        const float scalar;\n    public:\n        AddScalar(const float _scalar) : scalar(_scalar) {\n        }\n        __device__ inline float operator()(const float a) const {\n            return a + scalar;\n        }\n    };\n\n    class WeightedAddScalar {\n    private:\n        const float weight, scalar;\n    public:\n        WeightedAddScalar(const float _weight, const float _scalar) : weight(_weight), scalar(_scalar) {\n        }\n        __device__ inline float operator()(const float a) const {\n            return weight * a + scalar;\n        }\n    };\n\n    class MultByScalar {\n    private:\n        const float scalar;\n    public:\n        MultByScalar(const float _scalar) : scalar(_scalar) {\n        }\n        __device__ inline float operator()(const float a) const {\n            return a * scalar;\n        }\n    };\n\n    class Pow {\n    private:\n        const float p;\n    public:\n        Pow(const float _p) : p(_p) {\n        }\n        __device__ inline float operator()(const float a) const {\n            return __powf(a, p);\n        }\n    };\n\n    template <bool exclusive>\n    class InRange {\n    private:\n        const float lower, upper;\n    public:\n        InRange(const float _lower, const float _upper) : lower(_lower), upper(_upper) {\n        }\n        __device__ inline float operator()(const float a) const {\n            return exclusive ? a > lower && a < upper : a >= lower && a <= upper;\n        }\n    };\n\n    class MinWithScalar {\n    private:\n        const float scalar;\n    public:\n        MinWithScalar(const float _scalar) : scalar(_scalar) {\n        }\n        __device__ inline float operator()(const float a) const {\n            return a > scalar ? scalar : a;\n        }\n    };\n\n    class MaxWithScalar {\n    private:\n        const float scalar;\n    public:\n        MaxWithScalar(const float _scalar) : scalar(_scalar) {\n        }\n        __device__ inline float operator()(const float a) const {\n            return a > scalar ? a : scalar;\n        }\n    };\n};\n\nclass NVMatrixBinaryOps {\npublic:\n    class BinaryOp {\n    public:\n    };\n    class Equals : public BinaryOp {\n    public:\n        __device__ inline float operator()(const float a, const float b) const {\n            return a == b;\n        }\n    };\n\n    class BiggerThan : public BinaryOp {\n    public:\n        __device__ inline float operator()(const float a, const float b) const {\n            return a > b;\n        }\n    };\n\n    class Divide : public BinaryOp {\n    public:\n        __device__ inline float operator()(const float a, const float b) const  {\n            return __fdividef(a, b);\n        }\n    };\n\n    class DivideAccurate : public BinaryOp {\n    public:\n        __device__ inline float operator()(const float a, const float b) const  {\n            return a / b;\n        }\n    };\n\n    class DivideSafe : public BinaryOp {\n    public:\n        __device__ inline float operator()(const float a, const float b) const  {\n            return b == 0 ? 0 : __fdividef(a, b);\n        }\n    };\n\n    class DivideSafeAccurate : public BinaryOp {\n    public:\n        __device__ inline float operator()(const float a, const float b) const  {\n            return b == 0 ? 0 : (a / b);\n        }\n    };\n\n    class Multiply : public BinaryOp {\n    public:\n        __device__ inline float operator()(const float a, const float b) const {\n            return a * b;\n        }\n    };\n\n    class SquaredDiff : public BinaryOp {\n    public:\n        __device__ inline float operator()(const float a, const float b) const {\n            return (a - b) * (a - b);\n        }\n    };\n\n    class WeightedAdd : public BinaryOp {\n    private:\n        const float scaleA, scaleB;\n    public:\n        WeightedAdd(const float _scaleA, const float _scaleB) : scaleA(_scaleA), scaleB(_scaleB) {\n        }\n        WeightedAdd() : scaleA(0), scaleB(0) { // Compiler complains about no default constructor?\n        }\n        __device__ inline float operator()(const float a, const float b) const {\n            return a * scaleA + b * scaleB;\n        }\n    };\n\n    class WeightedAdd1 : public BinaryOp {\n    private:\n        const float scaleB;\n    public:\n        WeightedAdd1(const float _scaleB) : scaleB(_scaleB) {\n        }\n        __device__ inline float operator()(const float a, const float b) const {\n            return a + b * scaleB;\n        }\n    };\n\n    class ScaledAdd : public BinaryOp {\n    private:\n        const float scaleB;\n    public:\n        ScaledAdd(const float _scaleB) : scaleB(_scaleB) {\n        }\n        __device__ inline float operator()(const float a, const float b) const {\n            return a + b * scaleB;\n        }\n    };\n\n    class Add : public BinaryOp {\n    public:\n        __device__ inline float operator()(const float a, const float b) const {\n            return a + b;\n        }\n    };\n\n    class First : public BinaryOp {\n    public:\n        __device__ inline float operator()(const float a, const float b) const {\n            return a;\n        }\n    };\n\n    class Second : public BinaryOp {\n    public:\n        __device__ inline float operator()(const float a, const float b) const {\n            return b;\n        }\n    };\n\n    class SecondScaled : public BinaryOp {\n    private:\n        const float scale;\n    public:\n        SecondScaled(const float _scale) : scale(_scale) {\n        }\n\n        SecondScaled() : scale(0) { // Compiler complains about no default constructor?\n        }\n        __device__ inline float operator()(const float a, const float b) const {\n            return scale * b;\n        }\n    };\n\n    template<class UnaryOp, class BinaryOp>\n    class CompositeSecond : public BinaryOp {\n    private:\n        UnaryOp _uop;\n        BinaryOp _bop;\n    public:\n        CompositeSecond(UnaryOp uop, BinaryOp bop) : _uop(uop), _bop(bop) {\n\n        }\n        __device__ inline float operator()(const float a, const float b) const {\n            return _bop(a, _uop(b));\n        }\n    };\n};\n\nclass NVMatrixAggs {\npublic:\n    class Sum {\n    public:\n        __device__ inline float operator()(const float a, const float b) const {\n            return a + b;\n        }\n        __device__ inline float getBaseValue() {\n            return 0;\n        }\n    };\n\n    class Max {\n    public:\n        __device__ inline float operator()(const float a, const float b) const {\n            return a > b ? a : b;\n        }\n        __device__ inline float getBaseValue() {\n            return -2e38;\n        }\n    };\n\n    class Min {\n    public:\n        __device__ inline float operator()(const float a, const float b) const {\n            return a > b ? b : a;\n        }\n        __device__ inline float getBaseValue() {\n            return 2e38;\n        }\n    };\n\n    class CountNan {\n    public:\n        __device__ inline float operator()(const float a, const float b) const {\n            return a + isnan(b);\n        }\n        __device__ inline float getBaseValue() {\n            return 0;\n        }\n    };\n\n    class CountInf {\n    public:\n        __device__ inline float operator()(const float a, const float b) const {\n            return a + isinf(b);\n        }\n        __device__ inline float getBaseValue() {\n            return 0;\n        }\n    };\n\n    template<class UnaryOperator>\n    class ArgMax {\n    private:\n       UnaryOperator u;\n    public:\n       ArgMax(UnaryOperator _u) : u(_u) {\n       }\n       __device__ inline float operator()(const float a, const float b) const {\n           return u(a) > u(b) ? a : b;\n       }\n       __device__ inline float getBaseValue() {\n           return u.getArgMin();\n       }\n    };\n};\n\nclass NVMatrixTernaryOps {\npublic:\n    class Add {\n    public:\n        __device__ inline float operator()(const float a, const float b, const float c) const {\n            return a + b + c;\n        }\n    };\n    class WeightedAdd {\n    private:\n        const float scaleA, scaleB, scaleC;\n    public:\n        WeightedAdd(const float _scaleA, const float _scaleB, const float _scaleC) : scaleA(_scaleA), scaleB(_scaleB), scaleC(_scaleC) {\n        }\n        __device__ inline float operator()(const float a, const float b, const float c) const {\n            return a * scaleA + b * scaleB + c * scaleC;\n        }\n    };\n};\n\n#endif\t/* NVMATRIX_OPERATORS_CUH */\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/nvmatrix/src/memory.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"../include/memory.cuh\"\n\nLock MemoryManager::_globalLock;\nstd::map<int, MemoryManager*> FastMemoryManager::_memoryManagers;\n\nMemoryManager& FastMemoryManager::getInstance(int deviceID) {\n    _globalLock.acquire();\n    if (_memoryManagers.count(deviceID) == 0) {\n        _memoryManagers[deviceID] = (new FastMemoryManager(deviceID))->init();\n    }\n    MemoryManager& ret = *_memoryManagers[deviceID];\n    _globalLock.release();\n    return ret;\n}\n\nMemoryManager* CUDAMemoryManager::_memoryManager = NULL;\nMemoryManager& CUDAMemoryManager::getInstance(int deviceID) {\n    _globalLock.acquire();\n    if (_memoryManager == NULL) {\n        _memoryManager = new CUDAMemoryManager();\n    }\n    _globalLock.release();\n    return *_memoryManager;\n}\n\nMemoryManager* CUDAHostMemoryManager::_memoryManager = NULL;\nMemoryManager& CUDAHostMemoryManager::getInstance() {\n    _globalLock.acquire();\n    if (_memoryManager == NULL) {\n        _memoryManager = new CUDAHostMemoryManager();\n    }\n    _globalLock.release();\n    return *_memoryManager;\n}\n\nMemoryManager* FastHostMemoryManager::_memoryManager = NULL;\nMemoryManager& FastHostMemoryManager::getInstance() {\n    _globalLock.acquire();\n    if (_memoryManager == NULL) {\n        _memoryManager = (new FastHostMemoryManager())->init();\n    }\n    _globalLock.release();\n    return *_memoryManager;\n}\n\n\nvoid FastMemoryManager::destroyInstance(int deviceID) {\n    _globalLock.acquire();\n    if (_memoryManagers.count(deviceID) != 0) {\n        delete _memoryManagers[deviceID];\n        _memoryManagers.erase(deviceID);\n    }\n    _globalLock.release();\n}\n\nvoid FastHostMemoryManager::destroyInstance() {\n    _globalLock.acquire();\n    if (_memoryManager != NULL) {\n        delete _memoryManager;\n        _memoryManager = NULL;\n    }\n    _globalLock.release();\n}\n\nvoid CUDAMemoryManager::destroyInstance(int deviceID) {\n    _globalLock.acquire();\n    if (_memoryManager != NULL) {\n        delete _memoryManager;\n        _memoryManager = NULL;\n    }\n    _globalLock.release();\n}\n\nvoid CUDAHostMemoryManager::destroyInstance() {\n    _globalLock.acquire();\n    if (_memoryManager != NULL) {\n        delete _memoryManager;\n        _memoryManager = NULL;\n    }\n    _globalLock.release();\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/nvmatrix/src/nvmatrix.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <set>\n#include <vector>\n#include <assert.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <fstream>\n#include <iostream>\n#include <algorithm>\n#include <typeinfo>\n#include <map>\n#include <cuda.h>\n#include <signal.h>\n#include \"../include/nvmatrix.cuh\"\n#include \"../include/nvmatrix_operators.cuh\"\n\nusing namespace std;\n\n/*\n * Device random number generator pointers.\n */\n//map<int,curandGenerator_t> NVMatrix::rndGen;\nmap<int,MemorySegment*> NVMatrix::_rndDevStates;\nmap<int,int> NVMatrix::_rndDevThreads;\npthread_mutex_t* NVMatrix::_rndMutex = makeMutex();\npthread_mutex_t* NVMatrix::_cublasMutex = makeMutex();\npthread_mutex_t* NVMatrix::_streamMutex = makeMutex();\nstd::map<int,cublasHandle_t> NVMatrix::_cublasHandles;\nstd::map<int,cudaStream_t> NVMatrix::_defaultStreams;\n\npthread_mutex_t* NVMatrix::makeMutex() {\n    pthread_mutex_t* m = (pthread_mutex_t*) malloc(sizeof(pthread_mutex_t));\n    pthread_mutex_init(m, NULL);\n    return m;\n}\n/*\n   Do not call resize in _init because resize is a virtual function\n   which is overridden in base class. Since C++ is retarded and unable\n   to call overridden functions from constructors, we shall call resize\n   separately from every constructor after calling _init.\n*/\nvoid NVMatrix::_init(bool isTrans) {\n    _numRows = 0;\n    _numCols = 0;\n    _numElements = 0;\n    _ownsData = true;\n\n    _isTrans = isTrans;\n    _memSegment = NULL;\n\n    _stride = 0;\n    _texObj = 0;\n}\n\nNVMatrix::NVMatrix() : _deleted(false) {\n    _init(false);\n}\n\nNVMatrix::NVMatrix(bool isTrans) : _deleted(false) {\n    _init(isTrans);\n}\n\nNVMatrix::NVMatrix(int numRows, int numCols, bool isTrans) : _deleted(false) {\n    _init(isTrans);\n    resize(numRows, numCols);\n}\n\nNVMatrix::NVMatrix(const Matrix& like, bool copy) : _deleted(false) {\n    _init(like.isTrans());\n    resize(like.getNumRows(), like.getNumCols());\n    if (copy) {\n        copyFromHost(like);\n    }\n}\n\nNVMatrix::NVMatrix(const NVMatrix& like, bool copy) : _deleted(false) {\n    _init(like.isTrans());\n    resize(like.getNumRows(), like.getNumCols());\n    if (copy) {\n        like.copy(*this);\n    }\n}\n\n/*\n * Initializes NVMatrix with same dimensions as given matrix but\n * does not copy any data.\n */\nNVMatrix::NVMatrix(const NVMatrix& like) : _deleted(false) {\n    _init(like.isTrans());\n    resize(like.getNumRows(), like.getNumCols());\n}\n\n/*\n * Initializes NVMatrix with same dimensions as given matrix but\n * does not copy any data.\n */\nNVMatrix::NVMatrix(const Matrix& like) : _deleted(false) {\n    _init(false);\n    resize(like.getNumRows(), like.getNumCols());\n}\n\nNVMatrix::NVMatrix(MemorySegment* mem, int numRows, int numCols, int stride, bool isTrans) :\n    _numRows(numRows),\n    _numCols(numCols),\n    _numElements(numRows*numCols),\n    _ownsData(false),\n    _memSegment(mem),\n    _isTrans(isTrans),\n    _deleted(false),\n    _texObj(0) {\n    _stride = stride < 0 ? getLeadingDim() : stride;\n}\n\nNVMatrix::~NVMatrix() {\n    if (!_deleted) {\n        deallocTexture();\n        if(_ownsData && _numElements > 0) {\n            dealloc();\n        } else {\n            // dealloc deletes the mem segment. But if this is a view,\n            // then we still need to delete the mem segment object.\n//            assert(_memSegment == NULL || _memSegment->getSize() == 0);\n            delete _memSegment;\n        }\n    }\n}\n\nvoid NVMatrix::copyFromHost(const Matrix& hostMatrix) {\n    copyFromHost(hostMatrix, false, getDefaultStream());\n}\n\nvoid NVMatrix::copyFromHost(const Matrix& hostMatrix, bool resizeTarget) {\n    copyFromHost(hostMatrix, resizeTarget, getDefaultStream());\n}\n\nvoid NVMatrix::copyFromHost(const Matrix& hostMatrix, bool resizeTarget, cudaStream_t stream) {\n    if (resizeTarget) {\n        resize(hostMatrix);\n    } else {\n        assert(isSameDims(hostMatrix));\n    }\n    setTrans(hostMatrix.isTrans());\n\n    if (getNumElements() > 0) {\n        CUBLAS_CALL(cublasSetMatrixAsync(hostMatrix.getLeadingDim(), hostMatrix.getFollowingDim(), sizeof(float),\n                                    hostMatrix.getData(), hostMatrix.getLeadingDim(), getDevData(), _stride, stream));\n        syncStream(stream);\n    }\n}\n\nvoid NVMatrix::copyToHost(Matrix& hostMatrix) const {\n    copyToHost(hostMatrix, false, getDefaultStream());\n}\n\nvoid NVMatrix::copyToHost(Matrix& hostMatrix, bool resizeTarget) const {\n    copyToHost(hostMatrix, resizeTarget, getDefaultStream());\n}\n\nvoid NVMatrix::copyToHost(Matrix& hostMatrix, bool resizeTarget, cudaStream_t stream) const {\n    if (resizeTarget) {\n        hostMatrix.resize(_numRows, _numCols);\n    } else {\n        assert(isSameDims(hostMatrix));\n    }\n    hostMatrix.setTrans(_isTrans);\n\n    if (getNumElements() > 0) {\n        CUBLAS_CALL(cublasGetMatrixAsync(getLeadingDim(),getFollowingDim(), sizeof(float),\n                                         getDevData(), getStride(), hostMatrix.getData(), hostMatrix.getLeadingDim(), stream));\n        syncStream(stream);\n    }\n}\n\nvoid NVMatrix::copy(NVMatrix& dest) const {\n    copy(dest, getDefaultStream());\n}\n\nvoid NVMatrix::copy(NVMatrix& dest, cudaStream_t stream) const {\n    if (&dest != this) {\n        if (!isSameDims(dest)) {\n            dest.resize(*this);\n        }\n        copy(dest, 0, -1, 0, -1, 0, 0, stream);\n    }\n}\n\nNVMatrix& NVMatrix::copy() const {\n    NVMatrix& c = construct();\n    copy(c);\n    return c;\n}\n\nvoid NVMatrix::rightMult(NVMatrix &b, float scaleAB, NVMatrix &target) {\n    rightMult(b, scaleAB, target, getDefaultStream());\n}\n\nvoid NVMatrix::rightMult(NVMatrix &b, float scaleAB, NVMatrix &target, cudaStream_t stream) {\n//    if(&target != this && &target != &b) {\n//        target.resize(_numRows, b.getNumCols());\n//        target.setTrans(true);\n//    }\n    target.addProduct(*this, b, 0, scaleAB, stream);\n}\n\nvoid NVMatrix::rightMult(NVMatrix &b, float scaleAB) {\n    rightMult(b, scaleAB, *this);\n}\n\nvoid NVMatrix::rightMult(NVMatrix &b, NVMatrix& target) {\n    rightMult(b, 1, target);\n}\n\nvoid NVMatrix::addProduct(NVMatrix& a, NVMatrix &b, float scaleThis, float scaleAB) {\n    addProduct(a, b, scaleThis, scaleAB, getDefaultStream());\n}\n\n/*\n * This will only work if this matrix is in column-major order! In other words,\n * if isTrans() returns true.\n */\nvoid NVMatrix::addProduct(NVMatrix& a, NVMatrix &b, float scaleThis, float scaleAB, cudaStream_t stream) {\n    assert(a.getNumCols() == b.getNumRows());\n\n    if (scaleThis == 0) {\n        resize(a.getNumRows(), b.getNumCols());\n        setTrans(true);\n    }\n\n    assert(this->getNumRows() == a.getNumRows());\n    assert(this->getNumCols() == b.getNumCols());\n    assert(_isTrans);\n    CUBLAS_CALL(cublasSetStream_v2(getCublasHandle(), stream));\n    CUBLAS_CALL(cublasSgemm_v2(getCublasHandle(), a.getTransChar(), b.getTransChar(), a.getNumRows(), b.getNumCols(), a.getNumCols(),\n                               &scaleAB, a.getDevData(), a.getStride(), b.getDevData(), b.getStride(),\n                               &scaleThis, getDevData(), getStride()));\n}\n\nvoid NVMatrix::addProduct(NVMatrix& a, NVMatrix &b) {\n    addProduct(a, b, 1, 1);\n}\n\nvoid NVMatrix::assertSame(NVMatrixV& a) {\n    for (int i = 1; i < a.size(); ++i) {\n        assert(a[i]->isSameDims(*a[0]));\n        assert(a[i]->isTrans() == a[0]->isTrans());\n        assert(a[i]->getStride() == a[0]->getStride());\n        assert(a[i]->getDataDeviceID() == a[0]->getDataDeviceID());\n    }\n}\n\nvoid NVMatrix::batchedMatrixMultiply(NVMatrixV& a, NVMatrixV& b, NVMatrixV& target, float scaleTarget, float scaleAB,\n                                     const float** aPtrsDev, const float** bPtrsDev, float** tgtPtrsDev) {\n    batchedMatrixMultiply(a, b, target, scaleTarget, scaleAB, getDefaultStream(), aPtrsDev, bPtrsDev, tgtPtrsDev);\n}\n\nvoid NVMatrix::batchedMatrixMultiply(NVMatrixV& a, NVMatrixV& b, NVMatrixV& target, float scaleTarget, float scaleAB) {\n    batchedMatrixMultiply(a, b, target, scaleTarget, scaleAB, getDefaultStream());\n}\n\nvoid NVMatrix::batchedMatrixMultiply(NVMatrixV& a, NVMatrixV& b, NVMatrixV& target, float scaleTarget, float scaleAB, cudaStream_t stream,\n                                     const float** aPtrsDev, const float** bPtrsDev, float** tgtPtrsDev) {\n    assert(a.size() == b.size());\n    assert(a.size() == target.size());\n    assertSame(a);\n    assertSame(b);\n    assertSame(target);\n\n    const int batch = a.size();\n    if (batch > 0) {\n        const int rows = a[0]->getNumRows(), inner = a[0]->getNumCols(), cols = b[0]->getNumCols();\n\n        assert(inner == b[0]->getNumRows());\n        assert(target[0]->getNumRows() == rows);\n        assert(target[0]->getNumCols() == cols);\n\n        const int lda = a[0]->getStride(), ldb = b[0]->getStride(), ldc = target[0]->getStride();\n        cublasOperation_t atrans = a[0]->getTransChar(), btrans = b[0]->getTransChar();\n\n        CUBLAS_CALL(cublasSetStream_v2(getCublasHandle(), stream));\n        CUBLAS_CALL(cublasSgemmBatched(getCublasHandle(), atrans, btrans, rows, cols, inner, &scaleAB, aPtrsDev, lda, bPtrsDev, ldb, &scaleTarget, tgtPtrsDev, ldc, batch));\n    }\n}\n\nvoid NVMatrix::batchedMatrixMultiply(NVMatrixV& a, NVMatrixV& b, NVMatrixV& target, float scaleTarget, float scaleAB, cudaStream_t stream) {\n    assert(a.size() == b.size());\n    assert(a.size() == target.size() || target.size() == 0);\n\n    const int batch = a.size();\n    if (batch > 0) {\n        const int rows = a[0]->getNumRows(), cols = b[0]->getNumCols();\n\n        const float* aPtrs[batch], *bPtrs[batch], *tgtPtrs[batch];\n        for (int i = 0; i < batch; ++i) {\n            if (target.size() <= i) {\n                target.push_back(new NVMatrix(rows, cols, true));\n            }\n            aPtrs[i] = a[i]->getDevData();\n            bPtrs[i] = b[i]->getDevData();\n            tgtPtrs[i] = target[i]->getDevData();\n        }\n\n//        const float** aPtrsDev, **bPtrsDev;\n//        float **tgtPtrsDev;\n//        checkCudaErrors(cudaMalloc(&aPtrsDev, batch * sizeof(float*)));\n//        checkCudaErrors(cudaMalloc(&bPtrsDev, batch * sizeof(float*)));\n//        checkCudaErrors(cudaMalloc(&tgtPtrsDev, batch * sizeof(float*)));\n        MemorySegment* aPtrsDev = DEVICE_MEMORY_MANAGER::getInstance(getDeviceID()).malloc(batch * sizeof(float*));\n        MemorySegment* bPtrsDev = DEVICE_MEMORY_MANAGER::getInstance(getDeviceID()).malloc(batch * sizeof(float*));\n        MemorySegment* tgtPtrsDev = DEVICE_MEMORY_MANAGER::getInstance(getDeviceID()).malloc(batch * sizeof(float*));\n\n        checkCudaErrors(cudaMemcpyAsync(aPtrsDev, aPtrs, batch * sizeof(float*), cudaMemcpyHostToDevice, stream));\n        checkCudaErrors(cudaMemcpyAsync(bPtrsDev, bPtrs, batch * sizeof(float*), cudaMemcpyHostToDevice, stream));\n        checkCudaErrors(cudaMemcpyAsync(tgtPtrsDev, tgtPtrs, batch * sizeof(float*), cudaMemcpyHostToDevice, stream));\n\n        batchedMatrixMultiply(a, b, target, scaleTarget, scaleAB, stream, const_cast<const float**>(aPtrsDev->getData<float*>()),\n                                                                          const_cast<const float**>(bPtrsDev->getData<float*>()),\n                                                                          tgtPtrsDev->getData<float*>());\n\n//        checkCudaErrors(cudaFree(aPtrsDev));\n//        checkCudaErrors(cudaFree(bPtrsDev));\n//        checkCudaErrors(cudaFree(tgtPtrsDev));\n        DEVICE_MEMORY_MANAGER::getInstance(getDeviceID()).free(aPtrsDev);\n        DEVICE_MEMORY_MANAGER::getInstance(getDeviceID()).free(bPtrsDev);\n        DEVICE_MEMORY_MANAGER::getInstance(getDeviceID()).free(tgtPtrsDev);\n    }\n}\n\ntemplate <class Randomizer>\nvoid NVMatrix::_unaryRandomize(NVMatrix& target, Randomizer rnd) {\n    _unaryRandomize(target, rnd, getDefaultStream());\n}\n\ntemplate <class Randomizer>\nvoid NVMatrix::_unaryRandomize(NVMatrix& target, Randomizer rnd, cudaStream_t stream) {\n    assert(isRndInitialized());\n    assert(isContiguous() && target.isContiguous());\n    if (!isSameDims(target)) {\n        target.resize(*this);\n    }\n    assert(isTrans() == target.isTrans());\n    kUnaryRandomize<<<NUM_RND_BLOCKS,NUM_RND_THREADS_PER_BLOCK, 0, stream>>>(getDevData(), target.getDevData(), getCurandState(), getNumElements(), rnd);\n    getLastCudaError(\"kUnaryRandomize: Kernel execution failed\");\n}\n\ntemplate <class Randomizer>\nvoid NVMatrix::_binaryRandomize(NVMatrix& data2, NVMatrix& target, Randomizer rnd) {\n    _binaryRandomize(data2, target, rnd, getDefaultStream());\n}\n\ntemplate <class Randomizer>\nvoid NVMatrix::_binaryRandomize(NVMatrix& data2, NVMatrix& target, Randomizer rnd, cudaStream_t stream) {\n    assert(isRndInitialized());\n    assert(isContiguous() && data2.isContiguous() && target.isContiguous());\n    assert(isSameDims(data2));\n    assert(isTrans() == data2.isTrans());\n    if (!isSameDims(target)) {\n        target.resize(*this);\n    }\n    assert(isTrans() == target.isTrans());\n    kBinaryRandomize<<<NUM_RND_BLOCKS,NUM_RND_THREADS_PER_BLOCK, 0, stream>>>(getDevData(), data2.getDevData(), target.getDevData(), getCurandState(), getNumElements(), rnd);\n    getLastCudaError(\"kBinaryRandomize: Kernel execution failed\");\n}\n\nvoid NVMatrix::initRandom(unsigned long long seed, int numStreams) {\n    NVMatrix::initRandom(seed, numStreams, NVMatrix::getDefaultStream());\n}\n\nvoid NVMatrix::initRandom(unsigned long long seed, int numStreams, cudaStream_t stream) {\n//    printf(\"init random on device %d\\n\", getDeviceID());\n    pthread_mutex_lock(_rndMutex);\n    assert(!isRndInitialized(true));\n    int d = getDeviceID();\n//    _rndDevStates[d] = NULL;\n    _rndDevThreads[d] = numStreams;\n    _rndDevStates[d] = DEVICE_MEMORY_MANAGER::getInstance(d).malloc(numStreams * sizeof(curandState));\n//    checkCudaErrors(cudaMalloc((void **)&_rndDevStates[d], numStreams * sizeof(curandState)));\n    pthread_mutex_unlock(_rndMutex);\n    kSetupCurand<<<NUM_RND_BLOCKS, NUM_RND_THREADS_PER_BLOCK, 0, stream>>>(getCurandState(), 1 + seed*2); // so there's no chance it'll be correlated with the other one\n    getLastCudaError(\"kSetupCurand: Kernel execution failed\");\n}\n\nvoid NVMatrix::initRandom(unsigned long long seed) {\n    initRandom(seed, NUM_RND_STREAMS);\n}\n\nvoid NVMatrix::initRandom() {\n    NVMatrix::initRandom(time(0));\n}\n\nvoid NVMatrix::initCublas() {\n    int d = getDeviceID();\n    pthread_mutex_lock(_cublasMutex);\n    assert(_cublasHandles.count(d) == 0);\n    CUBLAS_CALL(cublasCreate(&_cublasHandles[d]));\n    // It appears that cublasCreate causes a host -> device copy on stream 0,\n    // so we synchronize with it because we run everything else on other\n    // streams.\n    syncDevice();\n    pthread_mutex_unlock(_cublasMutex);\n}\n\nvoid NVMatrix::destroyCublas() {\n    int d = getDeviceID();\n    pthread_mutex_lock(_cublasMutex);\n    assert(_cublasHandles.count(d) > 0);\n    CUBLAS_CALL(cublasDestroy(_cublasHandles[d]));\n    _cublasHandles.erase(d);\n    pthread_mutex_unlock(_cublasMutex);\n}\n\ncublasHandle_t NVMatrix::getCublasHandle() {\n    return getCublasHandle(getDeviceID());\n}\n\ncublasHandle_t NVMatrix::getCublasHandle(int deviceID) {\n    pthread_mutex_lock(_cublasMutex);\n    assert(_cublasHandles.count(deviceID) > 0);\n    cublasHandle_t h = _cublasHandles[deviceID];\n    pthread_mutex_unlock(_cublasMutex);\n    return h;\n}\n\ncudaStream_t NVMatrix::getDefaultStream() {\n    return getDefaultStream(NVMatrix::getDeviceID());\n}\n\ncudaStream_t NVMatrix::getDefaultStream(int deviceID) {\n    if (deviceID >= 0) {\n        pthread_mutex_lock(_streamMutex);\n        if (_defaultStreams.count(deviceID) == 0) {\n            int oldDeviceID = getDeviceID();\n            NVMatrix::setDeviceID(deviceID);\n            checkCudaErrors(cudaStreamCreateWithFlags(&_defaultStreams[deviceID], cudaStreamNonBlocking));\n            NVMatrix::setDeviceID(oldDeviceID);\n        }\n        cudaStream_t s = _defaultStreams[deviceID];\n        pthread_mutex_unlock(_streamMutex);\n        return s;\n    }\n    return 0;\n}\n\nvoid NVMatrix::syncDevice() {\n    checkCudaErrors(cudaDeviceSynchronize());\n}\n\nvoid NVMatrix::syncStream(cudaStream_t stream) {\n    checkCudaErrors(cudaStreamSynchronize(stream));\n}\n\nvoid NVMatrix::syncStream() {\n    syncStream(getDefaultStream());\n}\n\ncurandState* NVMatrix::getCurandState() {\n    /*\n     * Even though we're only reading from the map here, it's important to grab\n     * the mutex because another thread may be writing to it.\n     */\n    pthread_mutex_lock(_rndMutex);\n    int d = getDeviceID();\n    assert(isRndInitialized(true));\n    curandState* r = _rndDevStates[d]->getData<curandState>();\n    pthread_mutex_unlock(_rndMutex);\n    return r;\n}\n\ncurandState* NVMatrix::getCurandState(int numStreams) {\n    int d = getDeviceID();\n    pthread_mutex_lock(_rndMutex);\n    assert(isRndInitialized(true));\n    bool realloc = numStreams >  _rndDevThreads[d];\n    pthread_mutex_unlock(_rndMutex);\n\n    if (realloc) {\n        destroyRandom();\n        initRandom(time(0), numStreams);\n    }\n    return getCurandState();\n}\n\nint NVMatrix::getDataDeviceID() const {\n    if (getDevData() == NULL) {\n        return DEVICE_NULL;\n    }\n    struct cudaPointerAttributes atts;\n    checkCudaErrors(cudaPointerGetAttributes(&atts, getDevData()));\n    return atts.memoryType == cudaMemoryTypeDevice ? atts.device : DEVICE_HOST;\n}\n\n\nint NVMatrix::getDeviceID() {\n    int d;\n    checkCudaErrors(cudaGetDevice(&d));\n//    if (d == 0) {\n//        raise(SIGABRT);\n//    }\n    return d;\n}\n\nvoid NVMatrix::setDeviceID(int d) {\n    assert(d >= 0);\n//    printf(\"Setting device to %d\\n\", d);\n//    if (d == 0) {\n//        raise(SIGABRT);\n//    }\n    checkCudaErrors(cudaSetDevice(d));\n}\n\nbool NVMatrix::canAccessPeer(int srcDevice, int tgtDevice) {\n    if (srcDevice == tgtDevice) {\n        return true;\n    }\n    int canAccess;\n    checkCudaErrors(cudaDeviceCanAccessPeer(&canAccess, srcDevice, tgtDevice));\n    return canAccess;\n}\n\nbool NVMatrix::isRndInitialized(bool haveLock) {\n    if (!haveLock) {\n        pthread_mutex_lock(_rndMutex);\n    }\n    bool b = _rndDevStates.count(getDeviceID()) != 0;\n    if (!haveLock) {\n        pthread_mutex_unlock(_rndMutex);\n    }\n    return b;\n}\n\nbool NVMatrix::isRndInitialized() {\n    return isRndInitialized(false);\n}\n\nvoid NVMatrix::destroyRandom() {\n    int d = getDeviceID();\n    pthread_mutex_lock(_rndMutex);\n    assert(isRndInitialized(true));\n//    checkCudaErrors(cudaFree(_rndDevStates[d]));\n    DEVICE_MEMORY_MANAGER::getInstance(d).free(_rndDevStates[d]);\n    _rndDevStates.erase(d);\n    _rndDevThreads.erase(d);\n    pthread_mutex_unlock(_rndMutex);\n}\n\nvoid NVMatrix::binarizeProbs() {\n    binarizeProbs(*this);\n}\n\nvoid NVMatrix::binarizeProbs(NVMatrix& target) {\n    _unaryRandomize(target, BinarizeUnaryRandomizer());\n}\n\nvoid NVMatrix::randomizeUniform() {\n    assert(isContiguous());\n    assert(isRndInitialized());\n//    CURAND_CALL(curandGenerateUniform(rndGen, _devData, getNumElements()));\n    _unaryRandomize(*this, UniformUnaryRandomizer());\n}\n\nvoid NVMatrix::randomizeGaussian() {\n    randomizeGaussian(1);\n}\n\nvoid NVMatrix::randomizeGaussian(float stdev) {\n    randomizeGaussian(0, stdev);\n}\n\nvoid NVMatrix::randomizeGaussian(float mean, float stdev) {\n    assert(isContiguous());\n    assert(isRndInitialized());\n//    CURAND_CALL(curandGenerateNormal(rndGen, _devData, getNumElements(), mean, stdev));\n    _unaryRandomize(*this, GaussianUnaryRandomizer(mean, stdev));\n}\n\n/*\n * Kind of a hack since we don't actually need the contents of this matrix for it,\n * so we don't really need a binary randomizer.\n */\nvoid NVMatrix::randomizeGaussian(NVMatrix& stdevs) {\n    randomizeGaussian(0, stdevs);\n}\n\nvoid NVMatrix::randomizeGaussian(float mean, NVMatrix& stdevs) {\n    _binaryRandomize(stdevs, *this, GaussianBinaryRandomizer(mean));\n}\n\nvoid NVMatrix::randomizeGaussian(float mean, float stdevMult, NVMatrix& stdevs) {\n    _binaryRandomize(stdevs, *this, ScaledGaussianBinaryRandomizer(mean, stdevMult));\n}\n\nvoid NVMatrix::addGaussianNoise() {\n    addGaussianNoise(1);\n}\n\nvoid NVMatrix::addGaussianNoise(float stdev) {\n    addGaussianNoise(stdev, *this);\n}\n\nvoid NVMatrix::addGaussianNoise(float stdev, NVMatrix& target) {\n    _unaryRandomize(target, AddGaussianUnaryRandomizer(stdev));\n}\n\nvoid NVMatrix::addGaussianNoise(NVMatrix& stdevs, bool var) {\n    addGaussianNoise(stdevs, var, *this);\n}\n\nvoid NVMatrix::addGaussianNoise(NVMatrix& stdevs) {\n    addGaussianNoise(stdevs, false, *this);\n}\n\nvoid NVMatrix::addGaussianNoise(NVMatrix& stdevs, bool var, NVMatrix& target) {\n    if (var) {\n        _binaryRandomize(stdevs, target, AddGaussianBinaryRandomizer<true>());\n    } else {\n        _binaryRandomize(stdevs, target, AddGaussianBinaryRandomizer<false>());\n    }\n}\n\nvoid NVMatrix::biggerThan(NVMatrix& b, NVMatrix& target) {\n    applyBinary(NVMatrixBinaryOps::BiggerThan(), b, target);\n}\n\nvoid NVMatrix::biggerThan(NVMatrix& b) {\n    biggerThan(b, *this);\n}\n\nvoid NVMatrix::equals(NVMatrix& b, NVMatrix& target) {\n    applyBinary(NVMatrixBinaryOps::Equals(), b, target);\n}\n\nvoid NVMatrix::equals(NVMatrix& m) {\n    equals(m, *this);\n}\n\nvoid NVMatrix::biggerThanVector(NVMatrix& vec, NVMatrix& target) {\n    applyBinaryV(NVMatrixBinaryOps::BiggerThan(), vec, target);\n}\n\nvoid NVMatrix::biggerThanVector(NVMatrix& vec) {\n    biggerThanVector(vec, *this);\n}\n\nvoid NVMatrix::_checkBounds(int startRow, int endRow, int startCol, int endCol) const {\n    assert(startRow >= 0 && startRow <= _numRows);\n    assert(endRow >= startRow && endRow <= _numRows);\n\n    assert(startCol >= 0 && startCol <= _numCols);\n    assert(endCol >= startCol && endCol <= _numCols);\n}\n\n/*\n * The only place where stride is supported for now!\n * Will ALWAYS return a view of the original data, sometimes non-contiguous.\n */\nNVMatrix& NVMatrix::slice(int startRow, int endRow, int startCol, int endCol) const {\n    endRow = endRow < 0 ? this->_numRows : endRow;\n    endCol = endCol < 0 ? this->_numCols : endCol;\n    _checkBounds(startRow, endRow, startCol, endCol);\n\n    if (!isTrans()) {\n        return construct(new MemorySegment(this->getDevData() + startRow * _stride + startCol), endRow - startRow, endCol - startCol, _stride, false);\n    }\n    return construct(new MemorySegment(this->getDevData() + startCol * _stride + startRow), endRow - startRow, endCol - startCol, _stride, true);\n}\n\n/* this will NEVER return a view */\nvoid NVMatrix::slice(int startRow, int endRow, int startCol, int endCol, NVMatrix& target) const {\n    endRow = endRow < 0 ? this->_numRows : endRow;\n    endCol = endCol < 0 ? this->_numCols : endCol;\n    _checkBounds(startRow, endRow, startCol, endCol);\n\n    int sliceRows = endRow - startRow, sliceCols = endCol - startCol;\n    if (target.getNumRows() != sliceRows || target.getNumCols() != sliceCols) {\n        target.resize(sliceRows, sliceCols);\n    }\n    this->copy(target, startRow, endRow, startCol, endCol, 0, 0);\n}\n\nNVMatrix& NVMatrix::sliceRows(int startRow, int endRow) const {\n    return slice(startRow, endRow, 0, -1);\n}\n\nvoid NVMatrix::sliceRows(int startRow, int endRow, NVMatrix& target) const {\n    slice(startRow, endRow, 0, -1, target);\n}\n\nNVMatrix& NVMatrix::sliceCols(int startCol, int endCol) const {\n    return slice(0, -1, startCol, endCol);\n}\n\nvoid NVMatrix::sliceCols(int startCol, int endCol, NVMatrix& target) const {\n    slice(0, -1, startCol, endCol, target);\n}\n\nNVMatrixV& NVMatrix::splitRows(int numParts) {\n    assert(getNumRows() % numParts == 0);\n    NVMatrixV& v = *new NVMatrixV();\n    int partSize = getNumRows() / numParts;\n    for (int p = 0; p < numParts; ++p) {\n        v.push_back(&sliceRows(p * partSize, (p+1) * partSize));\n    }\n    return v;\n}\n\nNVMatrixV& NVMatrix::splitCols(int numParts) {\n    assert(getNumCols() % numParts == 0);\n    NVMatrixV& v = *new NVMatrixV();\n    int partSize = getNumCols() / numParts;\n    for (int p = 0; p < numParts; ++p) {\n        v.push_back(&sliceCols(p * partSize, (p+1) * partSize));\n    }\n    return v;\n}\n\n/*\n * Guaranteed to not change the data if the number of elements doesn't change.\n * So you can use this to \"reshape\" a matrix.\n */\nbool NVMatrix::resize(int numRows, int numCols, bool trans) {\n    setTrans(trans);\n    bool reallocated = false;\n    if (numRows != _numRows || numCols != _numCols) {\n        assert(_ownsData || (_numElements == numRows * numCols && isContiguous()));\n        if (_numElements != numRows * numCols) {\n            if (_numElements > 0) { // free old memory\n                dealloc();\n            }\n            if (numRows * numCols > 0) { // allocate new memory\n                alloc(numCols * numRows);\n            } else {\n                _memSegment = NULL;\n            }\n            reallocated = true;\n        }\n        _numRows = numRows;\n        _numCols = numCols;\n        _numElements = numRows * numCols;\n        _stride = getLeadingDim();\n    }\n    return reallocated;\n}\n\nbool NVMatrix::resize(int numRows, int numCols) {\n    return resize(numRows, numCols, isTrans());\n}\n\nbool NVMatrix::resize(const NVMatrix& like) {\n    setTrans(like.isTrans());\n    return resize(like.getNumRows(), like.getNumCols());\n}\n\nbool NVMatrix::resize(const Matrix& like) {\n    setTrans(like.isTrans());\n    return resize(like.getNumRows(), like.getNumCols());\n}\n\nvoid NVMatrix::reshape(int numRows, int numCols) {\n    assert(isContiguous());\n    assert(_numElements == numRows*numCols);\n    _numRows = numRows;\n    _numCols = numCols;\n    _stride = getLeadingDim();\n}\n\nNVMatrix& NVMatrix::reshaped(int numRows, int numCols) const {\n    assert(isContiguous());\n    assert(_numElements == numRows*numCols);\n    return construct(new MemorySegment(*_memSegment), numRows, numCols, -1, _isTrans);\n}\n\nvoid NVMatrix::copy(NVMatrix &dest, int srcStartRow, int srcEndRow,\n                    int srcStartCol, int srcEndCol,\n                    int destStartRow, int destStartCol) const {\n    copy(dest, srcStartRow, srcEndRow, srcStartCol, srcEndCol, destStartRow, destStartCol, getDefaultStream());\n}\n\nvoid NVMatrix::copy(NVMatrix &dest, int srcStartRow, int srcEndRow,\n                    int srcStartCol, int srcEndCol,\n                    int destStartRow, int destStartCol, cudaStream_t stream) const {\n    srcEndRow = srcEndRow < 0 ? _numRows : srcEndRow;\n    srcEndCol = srcEndCol < 0 ? _numCols : srcEndCol;\n    NVMatrix* srcSlice = &slice(srcStartRow, srcEndRow, srcStartCol, srcEndCol);\n    NVMatrix* destSlice = &dest.slice(destStartRow, destStartRow + srcEndRow - srcStartRow, destStartCol, destStartCol + srcEndCol - srcStartCol);\n    if (srcSlice->isContiguous() && destSlice->isContiguous() && srcSlice->isSameDims(*destSlice) && srcSlice->isTrans() == destSlice->isTrans()) {\n        // The commonest case.\n        checkCudaErrors(cudaMemcpyAsync(destSlice->getDevData(), srcSlice->getDevData(), srcSlice->getNumDataBytes(), cudaMemcpyDefault, stream));\n    } else {\n        srcSlice->apply(NVMatrixOps::Identity(), *destSlice, stream);\n    }\n    delete srcSlice;\n    delete destSlice;\n}\n\n\nNVMatrix& NVMatrix::getTranspose() {\n    return construct(new MemorySegment(*_memSegment), _numCols, _numRows, _stride, !_isTrans);\n}\n\nNVMatrix& NVMatrix::getClone() {\n    return construct(new MemorySegment(*_memSegment), _numRows, _numCols, _stride, _isTrans);\n}\n\nvoid NVMatrix::transpose(NVMatrix& target) {\n    flipTrans(target);\n    target.setTrans(!target.isTrans());\n    target.reshape(target.getNumCols(), target.getNumRows());\n}\n\nvoid NVMatrix::transpose() {\n    int tmp = _numCols;\n    _numCols = _numRows;\n    _numRows = tmp;\n    _isTrans = !_isTrans;\n}\n\nbool NVMatrix::transpose(bool trans) {\n    bool oldTrans = _isTrans;\n    if (oldTrans != trans) {\n        transpose();\n    }\n    return oldTrans;\n}\n\n/*\n * Flips the ordering of the matrix from row-major to column-major and vice versa.\n * This creates temporary storage -- not a cheap operation.\n *\n * This is not equivalent to a \"hard transpose\". The resultant matrix still has\n * the same dimensions, its layout in memory just changes.\n */\nNVMatrix& NVMatrix::flipTrans() {\n    NVMatrix& meTrans = construct(*this);\n    flipTrans(meTrans);\n    return meTrans;\n}\n\nvoid NVMatrix::flipTrans(NVMatrix& target) {\n    flipTrans(target, getDefaultStream());\n}\n\nvoid NVMatrix::flipTrans(NVMatrix& target, cudaStream_t stream) {\n    assert(&target != this);\n    target.resize(_numRows, _numCols);\n    target.setTrans(!isTrans());\n//    target.printShape(\"target\");\n//    this->printShape(\"this\");\n    apply(NVMatrixOps::Identity(), target, stream);\n}\n\nvoid NVMatrix::squaredDiff(NVMatrix& b) {\n    squaredDiff(b, *this);\n}\n\nvoid NVMatrix::squaredDiff(NVMatrix& b, NVMatrix& target) {\n    applyBinary(NVMatrixBinaryOps::SquaredDiff(), b, target);\n}\n\nvoid NVMatrix::add(NVMatrix& b, float scaleA, float scaleB, NVMatrix& target) {\n    add(b, scaleA, scaleB, target, NVMatrix::getDefaultStream());\n}\n\nvoid NVMatrix::add(NVMatrix& b, float scaleA, float scaleB, NVMatrix& target, cudaStream_t stream) {\n    if (scaleA == 0) {\n        b.scale(scaleB, target, stream);\n    } else if (scaleB == 0) {\n        scale(scaleA, target, stream);\n    } else if (scaleA == 1 && scaleB == 1) { // slight optimization\n        applyBinary(NVMatrixBinaryOps::Add(), b, target, stream);\n    } else if (scaleA == 1) {\n        applyBinary(NVMatrixBinaryOps::WeightedAdd1(scaleB), b, target, stream);\n    } else {\n        applyBinary(NVMatrixBinaryOps::WeightedAdd(scaleA, scaleB), b, target, stream);\n    }\n}\n\nvoid NVMatrix::add(NVMatrix& b, float scaleB, NVMatrix& target) {\n    add(b, 1, scaleB, target);\n}\n\nvoid NVMatrix::add(NVMatrix& b, NVMatrix& target) {\n    add(b, 1, target);\n}\n\nvoid NVMatrix::add(NVMatrix& b, float scaleB) {\n    add(b, scaleB, *this);\n}\n\nvoid NVMatrix::add(NVMatrix& b, float scaleA, float scaleB) {\n    add(b, scaleA, scaleB, *this);\n}\n\nvoid NVMatrix::add(NVMatrix& b) {\n    add(b, 1, *this);\n}\n\nvoid NVMatrix::subtract(NVMatrix& b, NVMatrix& target) {\n    add(b, -1, target);\n}\n\nvoid NVMatrix::subtract(NVMatrix& b) {\n    add(b, -1);\n}\n\nvoid NVMatrix::eltwiseMult(NVMatrix& b, NVMatrix& target) {\n    applyBinary(NVMatrixBinaryOps::Multiply(), b, target);\n}\n\nvoid NVMatrix::eltwiseMult(NVMatrix& b) {\n    eltwiseMult(b, *this);\n}\n\nvoid NVMatrix::eltwiseDivide(NVMatrix& b, NVMatrix& target) {\n    applyBinary(NVMatrixBinaryOps::Divide(), b, target);\n}\n\nvoid NVMatrix::eltwiseDivide(NVMatrix& b) {\n    eltwiseDivide(b, *this);\n}\n\nvoid NVMatrix::tile(int timesY, int timesX, NVMatrix& target) {\n    tile(timesY, timesX, target, getDefaultStream());\n}\n\nvoid NVMatrix::tile(int timesY, int timesX, NVMatrix& target, cudaStream_t stream) {\n    assert(isContiguous() && target.isContiguous());\n    assert(timesX > 0 && timesY > 0);\n    target.resize(_numRows*timesY, _numCols*timesX);\n    target.setTrans(_isTrans);\n    if(!isTrans()) {\n        kTile<<<NUM_TILE_BLOCKS,NUM_TILE_THREADS_PER_BLOCK, 0, stream>>>(getDevData(), target.getDevData(), _numCols, _numRows, target._numCols, target._numRows);\n    } else {\n        kTile<<<NUM_TILE_BLOCKS,NUM_TILE_THREADS_PER_BLOCK, 0, stream>>>(getDevData(), target.getDevData(), _numRows, _numCols, target._numRows, target._numCols);\n    }\n    getLastCudaError(\"Kernel execution failed\");\n}\n\nvoid NVMatrix::addVector(NVMatrix& vec, float scaleVec, NVMatrix& target) {\n    addVector(vec, scaleVec, target, getDefaultStream());\n}\n\nvoid NVMatrix::addVector(NVMatrix& vec, float scaleVec, NVMatrix& target, cudaStream_t stream) {\n    applyBinaryV(NVMatrixBinaryOps::ScaledAdd(scaleVec), vec, target, stream);\n}\n\nvoid NVMatrix::addVector(NVMatrix& vec) {\n    addVector(vec, 1);\n}\n\nvoid NVMatrix::addVector(NVMatrix& vec, float scaleVec) {\n    addVector(vec, scaleVec, *this);\n}\n\nvoid NVMatrix::addVector(NVMatrix& vec, NVMatrix& target) {\n    addVector(vec, 1, target);\n}\n\nvoid NVMatrix::equalsVector(NVMatrix& vec, NVMatrix& target) {\n    applyBinaryV(NVMatrixBinaryOps::Equals(), vec, target);\n}\n\nvoid NVMatrix::equalsVector(NVMatrix& vec) {\n    equalsVector(vec, *this);\n}\n\nvoid NVMatrix::eltwiseMultByVector(NVMatrix& vec, NVMatrix& target) {\n    eltwiseMultByVector(vec, target, getDefaultStream());\n}\n\nvoid NVMatrix::eltwiseMultByVector(NVMatrix& vec, NVMatrix& target, cudaStream_t stream) {\n    applyBinaryV(NVMatrixBinaryOps::Multiply(), vec, target, stream);\n}\n\nvoid NVMatrix::eltwiseMultByVector(NVMatrix& vec, cudaStream_t stream) {\n    eltwiseMultByVector(vec, *this, stream);\n}\n\nvoid NVMatrix::eltwiseMultByVector(NVMatrix& vec) {\n    eltwiseMultByVector(vec, *this);\n}\n\nvoid NVMatrix::eltwiseDivideByVector(NVMatrix& vec) {\n    eltwiseDivideByVector(vec,  *this);\n}\n\nvoid NVMatrix::eltwiseDivideByVector(NVMatrix& vec, NVMatrix& target) {\n    applyBinaryV(NVMatrixBinaryOps::Divide(), vec, target);\n}\n\ntemplate<class Agg, class UnaryOp, class BinaryOp>\nvoid NVMatrix::_aggregate(int axis, NVMatrix& target, Agg agg, UnaryOp uop, BinaryOp bop, cudaStream_t stream) {\n    _aggregate(axis, target, agg, uop, bop, stream, NULL);\n}\n\n/*\n * TODO: this is a mess, fix it. it works pretty fast but it's too ugly.\n * TODO: this function is _really_ bad for very long aggregations of few columns.\n */\ntemplate<class Agg, class UnaryOp, class BinaryOp>\nvoid NVMatrix::_aggregate(int axis, NVMatrix& target, Agg agg, UnaryOp uop, BinaryOp bop, cudaStream_t stream, NVMatrix* tmp) {\n    assert(axis == 0 || axis == 1);\n    assert(isContiguous()  && target.isContiguous());\n    assert(&target != this);\n    int width = _isTrans ? _numRows : _numCols;\n    int height = _isTrans ? _numCols : _numRows;\n\n    target.setTrans(_isTrans);\n    assert(width > 0);\n    assert(height > 0);\n    if((axis == 0 && !_isTrans) || (axis == 1 && _isTrans)) { //col sum\n        target.resize(!_isTrans ? 1 : _numRows, !_isTrans ? _numCols : 1);\n//        int height = getFollowingDim();\n        if ((height <= 2048 || width >= 4096)) {\n            int numBlocks = DIVUP(width, NUM_SUM_COLS_THREADS_PER_BLOCK);\n            assert(numBlocks * NUM_SUM_COLS_THREADS_PER_BLOCK >= width);\n            assert(numBlocks < NUM_BLOCKS_MAX);\n            kDumbAggCols<Agg, UnaryOp, BinaryOp><<<numBlocks,NUM_SUM_COLS_THREADS_PER_BLOCK, 0, stream>>>(getTextureObject(), target.getDevData(), width, height, agg, uop, bop);\n            getLastCudaError(\"kDumbAggCols: Kernel execution failed\");\n        } else { // Specialize the case when we have very long columns and few of them\n            const int sumLength = 128;\n            bool deltmp = tmp == NULL;\n            if (tmp == NULL) {\n                tmp = new NVMatrix(false);\n            }\n\n            int numBlocksX = DIVUP(width, NUM_SUM_COLS_THREADS_PER_BLOCK);\n            int numBlocksY = DIVUP(height, sumLength);\n            tmp->resize(numBlocksY, width);\n\n            dim3 blocks(numBlocksX, numBlocksY);\n            dim3 threads(NUM_SUM_COLS_THREADS_PER_BLOCK);\n            kAggCols<Agg, UnaryOp><<<blocks,threads, 0, stream>>>(getTextureObject(), tmp->getDevData(), width, height, sumLength, agg, uop);\n            getLastCudaError(\"kAggCols: Kernel execution failed\");\n\n            int numBlocks = DIVUP(width, NUM_SUM_COLS_THREADS_PER_BLOCK);\n            kDumbAggCols<Agg, NVMatrixOps::Identity, BinaryOp><<<numBlocks,NUM_SUM_COLS_THREADS_PER_BLOCK, 0, stream>>>(tmp->getTextureObject(), target.getDevData(), width, numBlocksY, agg, NVMatrixOps::Identity(), bop);\n            getLastCudaError(\"kDumbAggCols: Kernel execution failed\");\n            if (deltmp) {\n                delete tmp;\n            }\n        }\n    } else { // row sum\n        target.resize(_isTrans ? 1 : _numRows, _isTrans ? _numCols : 1);\n        if (width > 1) {\n            if (height >= 16384) { // linear aggregation\n                int numBlocksX = 1;\n                int numBlocksY = DIVUP(height, AGG_SHORT_ROWS_THREADS_Y*AGG_SHORT_ROWS_LOOPS_Y);\n                int numThreadsX = width <= 4 ? 4 : width <= 8 ? 8 : width <= 12 ? 12 : width <= 16 ? 16 : AGG_SHORT_ROWS_THREADS_X;\n                int numThreadsY = AGG_SHORT_ROWS_THREADS_Y;\n                while (numBlocksY > NUM_BLOCKS_MAX) {\n                    numBlocksY = DIVUP(numBlocksY,2);\n                    numBlocksX *= 2;\n                }\n                dim3 grid(numBlocksX, numBlocksY), threads(numThreadsX, numThreadsY);\n                if(width <= 16) {\n                    if(width <= 4) {\n                        kAggShortRows<Agg, UnaryOp, BinaryOp, 1, 4><<<grid, threads, 0, stream>>>(getDevData(), target.getDevData(),width, height, agg, uop, bop);\n                    } else if(width <= 8) {\n                        kAggShortRows<Agg, UnaryOp, BinaryOp, 1, 8><<<grid, threads, 0, stream>>>(getDevData(), target.getDevData(),width, height, agg, uop, bop);\n                    } else if(width <= 12) {\n                        kAggShortRows<Agg, UnaryOp, BinaryOp, 1, 12><<<grid, threads, 0, stream>>>(getDevData(), target.getDevData(),width, height, agg, uop, bop);\n                    } else {\n                        kAggShortRows<Agg, UnaryOp, BinaryOp, 1, 16><<<grid, threads, 0, stream>>>(getDevData(), target.getDevData(),width, height, agg, uop, bop);\n                    }\n                } else if(width <= 32) {\n                    kAggShortRows<Agg, UnaryOp, BinaryOp, 2, AGG_SHORT_ROWS_THREADS_X><<<grid, threads, 0, stream>>>(getDevData(), target.getDevData(),width, height, agg, uop, bop);\n                } else if(width <= 48){\n                    kAggShortRows<Agg, UnaryOp, BinaryOp, 3, AGG_SHORT_ROWS_THREADS_X><<<grid, threads, 0, stream>>>(getDevData(), target.getDevData(),width, height, agg, uop, bop);\n                } else if(width <= 64){\n                    kAggShortRows<Agg, UnaryOp, BinaryOp, 4, AGG_SHORT_ROWS_THREADS_X><<<grid, threads, 0, stream>>>(getDevData(), target.getDevData(),width, height, agg, uop, bop);\n                } else {\n                    kAggShortRows2<Agg, UnaryOp, BinaryOp><<<grid, threads, 0, stream>>>(getDevData(), target.getDevData(),width, height, agg, uop, bop);\n                }\n            } else {\n                if (width >= 512) {\n                    // NOTE: this is the only case which I bothered to try to optimize for Kepler\n                    dim3 threads(AWR_NUM_THREADS);\n                    dim3 blocks(1, height);\n                    kAggRows_wholerow_nosync<<<blocks, threads, 0, stream>>>(getDevData(), target.getDevData(), width, height, agg, uop, bop);\n                } else {\n\n                    int numThreadsX = width <= 64 ? 32 : (width <= 128 ? 64 : (width <= 256 ? 128 : (width <= 512 ? 256 : 512)));\n                    int numThreadsY = 1;\n                    int numBlocksX = DIVUP(width, 2*numThreadsX);\n                    int numBlocksY = std::min(height, NUM_BLOCKS_MAX);\n\n                    dim3 grid(numBlocksX, numBlocksY), threads(numThreadsX, numThreadsY);\n                    assert(numBlocksX <= NUM_BLOCKS_MAX);\n                    assert(numBlocksY <= NUM_BLOCKS_MAX);\n\n                    if(width <= 64) {\n                        kAggRows<Agg, UnaryOp, BinaryOp, 32><<<grid, threads, 0, stream>>>(getDevData(), target.getDevData(),\n                                                   width, height, target.getLeadingDim(), agg, uop, bop);\n                    } else if(width <= 128) {\n                        kAggRows<Agg, UnaryOp, BinaryOp, 64><<<grid, threads, 0, stream>>>(getDevData(), target.getDevData(),\n                                                   width, height, target.getLeadingDim(), agg, uop, bop);\n                    } else if(width <= 256) {\n                        kAggRows<Agg, UnaryOp, BinaryOp, 128><<<grid, threads, 0, stream>>>(getDevData(), target.getDevData(),\n                                                   width, height, target.getLeadingDim(), agg, uop, bop);\n                    } else if(width <= 512) {\n                        kAggRows<Agg, UnaryOp, BinaryOp, 256><<<grid, threads, 0, stream>>>(getDevData(), target.getDevData(),\n                                                   width, height, target.getLeadingDim(), agg, uop, bop);\n                    } else {\n                        kAggRows<Agg, UnaryOp, BinaryOp, 512><<<grid, threads, 0, stream>>>(getDevData(), target.getDevData(),\n                                                   width, height, target.getLeadingDim(), agg, uop, bop);\n                    }\n\n                    getLastCudaError(\"agg rows: Kernel execution failed\");\n                }\n            }\n        } else {\n            target.applyBinary(NVMatrixBinaryOps::CompositeSecond<UnaryOp, BinaryOp>(uop, bop), *this, target, stream);\n//            copy(target, stream);\n        }\n    }\n}\n\ntemplate<class Agg, class UnaryOp, class BinaryOp>\nvoid NVMatrix::_aggregate(int axis, NVMatrix& target, Agg agg, UnaryOp uop, BinaryOp bop) {\n    _aggregate(axis, target, agg, uop, bop, getDefaultStream());\n}\n\ntemplate<class Agg, class BinaryOp>\nvoid NVMatrix::_aggregate(int axis, NVMatrix& target, Agg agg, BinaryOp bop) {\n    _aggregate(axis, target, agg, NVMatrixOps::Identity(), bop, getDefaultStream());\n}\n\ntemplate<class Agg, class BinaryOp>\nvoid NVMatrix::_aggregate(int axis, NVMatrix& target, Agg agg, BinaryOp bop, cudaStream_t stream) {\n    _aggregate(axis, target, agg, NVMatrixOps::Identity(), bop, stream);\n}\n\ntemplate<class Agg, class UnaryOp, class BinaryOp>\nNVMatrix& NVMatrix::_aggregate(int axis, Agg agg, UnaryOp uop, BinaryOp bop) {\n    NVMatrix &sumVec = construct();\n    _aggregate(axis, sumVec, agg, uop, bop);\n    return sumVec;\n}\n\ntemplate<class Agg, class UnaryOp, class BinaryOp>\nNVMatrix& NVMatrix::_aggregate(int axis, Agg agg, UnaryOp uop, BinaryOp bop, cudaStream_t stream) {\n    NVMatrix &sumVec = construct();\n    _aggregate(axis, sumVec, agg, uop, bop, stream);\n    return sumVec;\n}\n\ntemplate<class Agg, class BinaryOp>\nNVMatrix& NVMatrix::_aggregate(int axis, Agg agg, BinaryOp bop) {\n    return _aggregate(axis, agg, NVMatrixOps::Identity(), bop);\n}\n\ntemplate<class Agg, class BinaryOp>\nNVMatrix& NVMatrix::_aggregate(int axis, Agg agg, BinaryOp bop, cudaStream_t stream) {\n    return _aggregate(axis, agg, NVMatrixOps::Identity(), bop, stream);\n}\n\n\n\ntemplate<class Agg, class UnaryOp, class BinaryOp>\nvoid NVMatrix::_aggregate(int axis, NVMatrix& target, Agg agg, UnaryOp uop, BinaryOp bop, NVMatrix& tmp) {\n    _aggregate(axis, target, agg, uop, bop, getDefaultStream(), tmp);\n}\n\ntemplate<class Agg, class BinaryOp>\nvoid NVMatrix::_aggregate(int axis, NVMatrix& target, Agg agg, BinaryOp bop, NVMatrix& tmp) {\n    _aggregate(axis, target, agg, NVMatrixOps::Identity(), bop, getDefaultStream(), &tmp);\n}\n\ntemplate<class Agg, class BinaryOp>\nvoid NVMatrix::_aggregate(int axis, NVMatrix& target, Agg agg, BinaryOp bop, cudaStream_t stream, NVMatrix& tmp) {\n    _aggregate(axis, target, agg, NVMatrixOps::Identity(), bop, stream, &tmp);\n}\n\ntemplate<class Agg, class UnaryOp, class BinaryOp>\nNVMatrix& NVMatrix::_aggregate(int axis, Agg agg, UnaryOp uop, BinaryOp bop, NVMatrix& tmp) {\n    NVMatrix &sumVec = construct();\n    _aggregate(axis, sumVec, agg, uop, bop, tmp);\n    return sumVec;\n}\n\ntemplate<class Agg, class UnaryOp, class BinaryOp>\nNVMatrix& NVMatrix::_aggregate(int axis, Agg agg, UnaryOp uop, BinaryOp bop, cudaStream_t stream, NVMatrix& tmp) {\n    NVMatrix &sumVec = construct();\n    _aggregate(axis, sumVec, agg, uop, bop, stream, tmp);\n    return sumVec;\n}\n\ntemplate<class Agg, class BinaryOp>\nNVMatrix& NVMatrix::_aggregate(int axis, Agg agg, BinaryOp bop, NVMatrix& tmp) {\n    return _aggregate(axis, agg, NVMatrixOps::Identity(), bop, tmp);\n}\n\ntemplate<class Agg, class BinaryOp>\nNVMatrix& NVMatrix::_aggregate(int axis, Agg agg, BinaryOp bop, cudaStream_t stream, NVMatrix& tmp) {\n    return _aggregate(axis, agg, NVMatrixOps::Identity(), bop, stream, tmp);\n}\n\nvoid NVMatrix::inRangeInc(float lower, float upper) {\n    inRangeInc(lower, upper, *this);\n}\nvoid NVMatrix::inRangeInc(float lower, float upper, NVMatrix& target) {\n    apply(NVMatrixOps::InRange<false>(lower, upper), target);\n}\n\nvoid NVMatrix::inRangeExc(float lower, float upper) {\n    inRangeExc(lower, upper, *this);\n}\n\nvoid NVMatrix::inRangeExc(float lower, float upper, NVMatrix& target) {\n    apply(NVMatrixOps::InRange<true>(lower, upper), target);\n}\n\nvoid NVMatrix::biggerThanScalar(float scalar) {\n    biggerThanScalar(scalar, *this);\n}\n\nvoid NVMatrix::biggerThanScalar(float scalar, NVMatrix& target) {\n    apply(NVMatrixOps::BiggerThanScalar(scalar), target);\n}\n\nvoid NVMatrix::smallerThanScalar(float scalar) {\n    smallerThanScalar(scalar, *this);\n}\n\nvoid NVMatrix::smallerThanScalar(float scalar, NVMatrix& target) {\n    apply(NVMatrixOps::SmallerThanScalar(scalar), target);\n}\n\nvoid NVMatrix::addScalar(float scaleThis, float scalar, NVMatrix& target) {\n    apply(NVMatrixOps::WeightedAddScalar(scaleThis, scalar), target);\n}\n\nvoid NVMatrix::addScalar(float scalar, NVMatrix& target) {\n    apply(NVMatrixOps::AddScalar(scalar), target);\n}\n\nvoid NVMatrix::addScalar(float scalar) {\n    addScalar(scalar, *this);\n}\n\nvoid NVMatrix::minWithScalar(float scalar, NVMatrix& target) {\n    apply(NVMatrixOps::MinWithScalar(scalar), target);\n}\n\nvoid NVMatrix::minWithScalar(float scalar) {\n    minWithScalar(scalar, *this);\n}\n\nvoid NVMatrix::maxWithScalar(float scalar, NVMatrix& target) {\n    apply(NVMatrixOps::MaxWithScalar(scalar), target);\n}\n\nvoid NVMatrix::maxWithScalar(float scalar) {\n    maxWithScalar(scalar, *this);\n}\n\nvoid NVMatrix::pow(float p, NVMatrix& target) {\n    apply(NVMatrixOps::Pow(p), target);\n}\n\nvoid NVMatrix::pow(float p) {\n    pow(p, *this);\n}\n\nvoid NVMatrix::scale(float _scale) {\n    scale(_scale, *this);\n}\n\nvoid NVMatrix::scale(float _scale, cudaStream_t stream) {\n    scale(_scale, *this, stream);\n}\n\nvoid NVMatrix::scale(float _scale, NVMatrix& target) {\n    scale(_scale, target, NVMatrix::getDefaultStream());\n}\n\nvoid NVMatrix::scale(float _scale, NVMatrix& target, cudaStream_t stream) {\n    if (_scale != 1 || &target != this) { // optimize away scale by 1\n        if (_scale == 1) {\n            copy(target, stream);\n        } else {\n            apply(NVMatrixOps::MultByScalar(_scale), target, stream);\n        }\n    }\n}\n\nvoid NVMatrix::zero() {\n    apply(NVMatrixOps::Zero());\n}\n\nvoid NVMatrix::zero(NVMatrix& like) {\n    resize(like);\n    zero();\n}\n\nvoid NVMatrix::max(int axis, NVMatrix& target) {\n    _aggregate(axis, target, NVMatrixAggs::Max(), NVMatrixBinaryOps::Second());\n}\n\nvoid NVMatrix::max(int axis, NVMatrix& target, NVMatrix& tmp) {\n    _aggregate(axis, target, NVMatrixAggs::Max(), NVMatrixBinaryOps::Second(), tmp);\n}\n\nvoid NVMatrix::addSum(NVMatrix& a, int axis, float scaleThis, float scaleSum) {\n    addSum(a, axis, scaleThis, scaleSum, getDefaultStream());\n}\n\nvoid NVMatrix::addSum(NVMatrix& a, int axis, float scaleThis, float scaleSum, cudaStream_t stream) {\n    if (scaleThis != 0) {\n        a._aggregate(axis, *this, NVMatrixAggs::Sum(), NVMatrixBinaryOps::WeightedAdd(scaleThis, scaleSum), stream);\n    } else {\n        a._aggregate(axis, *this, NVMatrixAggs::Sum(), NVMatrixBinaryOps::SecondScaled(scaleSum), stream);\n    }\n}\n\nvoid NVMatrix::addMax(NVMatrix& a, int axis, float scaleThis, float scaleMax) {\n    addMax(a, axis, scaleThis, scaleMax, getDefaultStream());\n}\n\nvoid NVMatrix::addMax(NVMatrix& a, int axis, float scaleThis, float scaleMax, cudaStream_t stream) {\n    if (scaleThis != 0) {\n        a._aggregate(axis, *this, NVMatrixAggs::Max(), NVMatrixBinaryOps::WeightedAdd(scaleThis, scaleMax), stream);\n    } else {\n        a._aggregate(axis, *this, NVMatrixAggs::Max(), NVMatrixBinaryOps::SecondScaled(scaleMax), stream);\n    }\n}\n\nvoid NVMatrix::sum(int axis, NVMatrix& target) {\n    sum(axis, target, getDefaultStream());\n}\n\nvoid NVMatrix::sum(int axis, NVMatrix& target, cudaStream_t stream) {\n    _aggregate(axis, target, NVMatrixAggs::Sum(), NVMatrixBinaryOps::Second(), stream);\n}\n\nvoid NVMatrix::sum(int axis, NVMatrix& target, NVMatrix& tmp) {\n    sum(axis, target, getDefaultStream(), tmp);\n}\n\nvoid NVMatrix::sum(int axis, NVMatrix& target, cudaStream_t stream, NVMatrix& tmp) {\n    _aggregate(axis, target, NVMatrixAggs::Sum(), NVMatrixBinaryOps::Second(), stream, tmp);\n}\n\nvoid NVMatrix::sumOfSquares(int axis, NVMatrix& target) {\n    sumOfSquares(axis, target, getDefaultStream());\n}\n\nvoid NVMatrix::sumOfSquares(int axis, NVMatrix& target, cudaStream_t stream) {\n    _aggregate(axis, target, NVMatrixAggs::Sum(), NVMatrixOps::Square(), NVMatrixBinaryOps::Second(), stream);\n}\n\nvoid NVMatrix::min(int axis, NVMatrix& target) {\n    _aggregate(axis, target, NVMatrixAggs::Min(), NVMatrixBinaryOps::Second());\n}\n\nNVMatrix& NVMatrix::max(int axis) {\n    return _aggregate(axis, NVMatrixAggs::Max(), NVMatrixBinaryOps::Second());\n}\n\nNVMatrix& NVMatrix::sum(int axis) {\n    return _aggregate(axis, NVMatrixAggs::Sum(), NVMatrixBinaryOps::Second());\n}\n\nNVMatrix& NVMatrix::min(int axis) {\n    return _aggregate(axis, NVMatrixAggs::Min(), NVMatrixBinaryOps::Second());\n}\n\nNVMatrix& NVMatrix::sumOfSquares(int axis) {\n    return _aggregate(axis, NVMatrixAggs::Sum(), NVMatrixOps::Square(), NVMatrixBinaryOps::Second());\n}\n\nvoid NVMatrix::_sum_setParams(int n, dim3* blocks, dim3* threads) {\n    *threads = dim3(DP_BLOCKSIZE);\n    *blocks = dim3(std::min(CPUSUM_MAX, DIVUP(n, DP_BLOCKSIZE)));\n}\n\nfloat NVMatrix::mean() {\n    return sum() / getNumElements();\n}\n\nfloat NVMatrix::sum() {\n    return _totalAgg(NVMatrixAggs::Sum());\n}\n\nfloat NVMatrix::sum(NVMatrix& tmpbuf) {\n    return _totalAgg(NVMatrixAggs::Sum(), tmpbuf, getDefaultStream());\n}\n\nfloat NVMatrix::max() {\n    return _totalAgg(NVMatrixAggs::Max());\n}\n\nfloat NVMatrix::min() {\n    return _totalAgg(NVMatrixAggs::Min());\n}\n\nfloat NVMatrix::countNan() {\n    return _totalAgg(NVMatrixAggs::CountNan());\n}\n\nfloat NVMatrix::countInf() {\n    return _totalAgg(NVMatrixAggs::CountInf());\n}\n\ntemplate<class Agg>\nfloat NVMatrix::_totalAgg(Agg agg) {\n    return _totalAgg(agg, getDefaultStream());\n}\n\ntemplate<class Agg>\nfloat NVMatrix::_totalAgg(Agg agg, cudaStream_t stream) {\n    NVMatrix tmp;\n    return _totalAgg(agg, tmp, stream);\n}\n\ntemplate<class Agg>\nfloat NVMatrix::_totalAgg(Agg agg, NVMatrix& tmpbuf, cudaStream_t stream) {\n    assert(isContiguous());\n    dim3 blocks, threads;\n    // Sum most of it on GPU\n\n    _sum_setParams(getNumElements(), &blocks, &threads);\n    tmpbuf.resize(1, blocks.x);\n    kTotalAgg<<<blocks, threads, 0, stream>>>(getDevData(), tmpbuf.getDevData(), getNumElements(), agg);\n    getLastCudaError(\"kTotalAgg: Kernel execution failed\");\n    // Don't need to sync because we copyToHost in the same stream, so it's serialized\n//    NVMatrix::syncStream(stream);\n    return tmpbuf.cpuAgg(agg, stream);\n}\ntemplate<class Agg>\nfloat NVMatrix::cpuAgg(Agg agg, cudaStream_t stream) {\n    Matrix bufCPU(getNumRows(), getNumCols());\n    copyToHost(bufCPU, false, stream);\n    if (getNumElements() > 1) { // Sum remainder on CPU\n        if (typeid(Agg) == typeid(NVMatrixAggs::Sum)) {\n            return bufCPU.sum();\n        } else if (typeid(Agg) == typeid(NVMatrixAggs::Max)) {\n            return bufCPU.max();\n        } else if (typeid(Agg) == typeid(NVMatrixAggs::Min)) {\n            return bufCPU.min();\n        } else if (typeid(Agg) == typeid(NVMatrixAggs::CountNan)) {\n            return bufCPU.hasNan(); //yea, it's not the same, who cares\n        } else if (typeid(Agg) == typeid(NVMatrixAggs::CountInf)) {\n            return bufCPU.hasInf();\n        } else {\n            assert(false);\n        }\n    }\n    return bufCPU(0,0);\n}\n\nfloat NVMatrix::dotProduct(NVMatrix& b) {\n    return dotProduct(b, getDefaultStream());\n}\n\nfloat NVMatrix::dotProduct(NVMatrix& b, cudaStream_t stream) {\n    NVMatrix tmp;\n    return dotProduct(b, tmp, stream);\n}\n\n/*\n * Fast dot product only for matrices with same transposedness.\n */\nfloat NVMatrix::dotProduct(NVMatrix& b, NVMatrix& tmp, cudaStream_t stream) {\n    assert(isContiguous() && b.isContiguous());\n    assert(isSameDims(b));\n    assert(isTrans() == b.isTrans()); // see?\n    dim3 blocks, threads;\n    _sum_setParams(getNumElements(), &blocks, &threads);\n//    NVMatrix target(1, blocks.x);\n    tmp.resize(1, blocks.x);\n    kDotProduct_r<<<blocks, threads, 0, stream>>>(getDevData(), b.getDevData(), tmp.getDevData(), getNumElements());\n    getLastCudaError(\"kDotProduct_r: Kernel execution failed\");\n//    cudaThreadSynchronize();\n//    syncStream(stream);\n//    return tmp._totalAgg(NVMatrixAggs::Sum(), stream);\n    return tmp.cpuAgg(NVMatrixAggs::Sum(), stream);\n}\n\nfloat NVMatrix::norm2() {\n    return dotProduct(*this);\n}\n\nfloat NVMatrix::norm() {\n    return sqrt(norm2());\n}\n\nvoid NVMatrix::print(int startRow, int rows, int startCol, int cols) const {\n//    cudaThreadSynchronize();\n    syncDevice();\n    Matrix hm = Matrix(_numRows, _numCols);\n    copyToHost(hm);\n    hm.print(startRow, rows, startCol, cols);\n}\n\nvoid NVMatrix::print(int rows, int cols) const {\n    print(0, rows, 0, cols);\n}\n\nvoid NVMatrix::printShape(const char* name) const {\n    printf(\"%s: %dx%d\\n\", name, _numRows, _numCols);\n}\n\nvoid NVMatrix::alloc(int numElements) {\n    _memSegment = DEVICE_MEMORY_MANAGER::getInstance(getDeviceID()).malloc(numElements * sizeof(float));\n}\n\nvoid NVMatrix::dealloc() {\n    DEVICE_MEMORY_MANAGER::getInstance(_memSegment->getDeviceID()).free(_memSegment);\n    _memSegment = NULL;\n    deallocTexture();\n}\n\nvoid NVMatrix::deallocTexture() {\n    if (_texObj != 0) {\n        checkCudaErrors(cudaDestroyTextureObject(_texObj));\n        _texObj = 0;\n    }\n}\n\ncudaTextureObject_t NVMatrix::getTextureObject() {\n   if (_texObj == 0) {\n       assert(isContiguous());\n       //size_t memFree, memTotal;\n\n       struct cudaResourceDesc resDesc;\n       memset(&resDesc, 0, sizeof(resDesc));\n       resDesc.resType = cudaResourceTypeLinear;\n       resDesc.res.linear.devPtr = getDevData();\n       resDesc.res.linear.sizeInBytes = getNumDataBytes();\n       resDesc.res.linear.desc = cudaCreateChannelDesc(32, 0, 0, 0, cudaChannelFormatKindFloat);\n       struct cudaTextureDesc texDesc;\n       memset(&texDesc, 0, sizeof(texDesc));\n       checkCudaErrors(cudaCreateTextureObject(&_texObj, &resDesc, &texDesc, NULL));\n   }\n   assert(_texObj != 0);\n   return _texObj;\n}\n\nNVMatrix& NVMatrix::construct() const {\n    return *new NVMatrix();\n}\nNVMatrix& NVMatrix::construct(bool isTrans) const {\n    return *new NVMatrix(isTrans);\n}\nNVMatrix& NVMatrix::construct(int numRows, int numCols, bool isTrans) const {\n    return *new NVMatrix(numRows, numCols, isTrans);\n}\nNVMatrix& NVMatrix::construct(const Matrix& like, bool copy) const {\n    return *new NVMatrix(like, copy);\n}\nNVMatrix& NVMatrix::construct(const NVMatrix& like, bool copy) const {\n    return *new NVMatrix(like, copy);\n}\nNVMatrix& NVMatrix::construct(const NVMatrix& like) const {\n    return *new NVMatrix(like);\n}\nNVMatrix& NVMatrix::construct(const Matrix& like) const {\n    return *new NVMatrix(like);\n}\nNVMatrix& NVMatrix::construct(MemorySegment* mem, int numRows, int numCols, int stride, bool isTrans) const {\n    return *new NVMatrix(mem, numRows, numCols, stride, isTrans);\n}\n\nstd::pair<size_t, size_t> NVMatrix::getCudaMemorySize() {\n    size_t memFree, memTotal;\n    checkCudaErrors(cudaMemGetInfo(&memFree, &memTotal));\n    return std::pair<size_t,size_t>(memFree, memTotal);\n}\n\n\n/* ================\n * HostNVMatrix\n * ================\n */\nHostNVMatrix::~HostNVMatrix() {\n    if (_ownsData && _numElements > 0) {\n        dealloc();\n    } else {\n        // dealloc frees the mem segment. But if this is a view,\n        // then we need to delete the mem segment object.\n//        assert(_memSegment == NULL || _memSegment->getSize() == 0);\n        delete _memSegment;\n    }\n    _deleted = true;\n}\nHostNVMatrix::HostNVMatrix() : NVMatrix() {\n    _init(false);\n}\nHostNVMatrix::HostNVMatrix(bool isTrans) {\n    _init(isTrans);\n}\nHostNVMatrix::HostNVMatrix(int numRows, int numCols, bool isTrans)  {\n    _init(isTrans);\n    resize(numRows, numCols);\n}\nHostNVMatrix::HostNVMatrix(const Matrix& like, bool copy)  {\n    _init(like.isTrans());\n    resize(like.getNumRows(), like.getNumCols());\n    if (copy) {\n        copyFromHost(like);\n    }\n}\nHostNVMatrix::HostNVMatrix(const NVMatrix& like, bool copy)  {\n    _init(like.isTrans());\n    resize(like.getNumRows(), like.getNumCols());\n    if (copy) {\n        like.copy(*this);\n    }\n}\nHostNVMatrix::HostNVMatrix(const NVMatrix& like)  {\n    _init(like.isTrans());\n    resize(like.getNumRows(), like.getNumCols());\n}\nHostNVMatrix::HostNVMatrix(const Matrix& like) {\n    _init(false);\n    resize(like.getNumRows(), like.getNumCols());\n}\nHostNVMatrix::HostNVMatrix(MemorySegment* mem, int numRows, int numCols, int stride, bool isTrans)\n    : NVMatrix(mem, numRows, numCols, stride, isTrans) {\n}\n\nNVMatrix& HostNVMatrix::construct() const {\n    return *new HostNVMatrix();\n}\nNVMatrix& HostNVMatrix::construct(bool isTrans) const {\n    return *new HostNVMatrix(isTrans);\n}\nNVMatrix& HostNVMatrix::construct(int numRows, int numCols, bool isTrans) const {\n    return *new HostNVMatrix(numRows, numCols, isTrans);\n}\nNVMatrix& HostNVMatrix::construct(const Matrix& like, bool copy) const {\n    return *new HostNVMatrix(like, copy);\n}\nNVMatrix& HostNVMatrix::construct(const NVMatrix& like, bool copy) const {\n    return *new HostNVMatrix(like, copy);\n}\nNVMatrix& HostNVMatrix::construct(const NVMatrix& like) const {\n    return *new HostNVMatrix(like);\n}\nNVMatrix& HostNVMatrix::construct(const Matrix& like) const {\n    return *new HostNVMatrix(like);\n}\nNVMatrix& HostNVMatrix::construct(MemorySegment* mem, int numRows, int numCols, int stride, bool isTrans) const {\n    return *new HostNVMatrix(mem, numRows, numCols, stride, isTrans);\n}\n\nvoid HostNVMatrix::copyFromHost(const Matrix& hostMatrix, bool resizeTarget, cudaStream_t stream) {\n    if (resizeTarget) {\n        resize(hostMatrix);\n    } else {\n        assert(isSameDims(hostMatrix));\n    }\n    setTrans(hostMatrix.isTrans());\n    if (getNumElements() > 0) {\n        checkCudaErrors(cudaMemcpy2D(getDevData(), _stride * sizeof(float), hostMatrix.getData(),\n                                     hostMatrix.getLeadingDim() * sizeof(float), getLeadingDim() * sizeof(float),\n                                     getFollowingDim(), cudaMemcpyHostToHost));\n//        syncStream(stream);\n    }\n}\n\nvoid HostNVMatrix::copyFromHost(const Matrix& hostMatrix, bool resizeTarget) {\n    copyFromHost(hostMatrix, resizeTarget, 0);\n}\n\nvoid HostNVMatrix::copyFromHost(const Matrix& hostMatrix) {\n    copyFromHost(hostMatrix, false, 0);\n}\n\nvoid HostNVMatrix::copyToHost(Matrix& hostMatrix, bool resizeTarget, cudaStream_t stream) const {\n    if (resizeTarget) {\n        hostMatrix.resize(getNumRows(), getNumCols());\n    } else {\n        assert(isSameDims(hostMatrix));\n    }\n    hostMatrix.setTrans(_isTrans);\n    if (getNumElements() > 0) {\n        checkCudaErrors(cudaMemcpy2D(hostMatrix.getData(), hostMatrix.getLeadingDim() * sizeof(float),\n                                     getDevData(), _stride * sizeof(float), getLeadingDim() * sizeof(float),\n                                     getFollowingDim(), cudaMemcpyHostToHost));\n//        syncStream(stream);\n    }\n}\n\nvoid HostNVMatrix::copyToHost(Matrix& hostMatrix, bool resizeTarget) const {\n    copyToHost(hostMatrix, resizeTarget, 0);\n}\n\nvoid HostNVMatrix::copyToHost(Matrix& hostMatrix) const {\n    copyToHost(hostMatrix, false, 0);\n}\n\nvoid HostNVMatrix::alloc(int numElements) {\n//    checkCudaErrors(cudaHostAlloc(&_devData, numElements * sizeof(float), cudaHostAllocPortable));\n    _memSegment = HOST_MEMORY_MANAGER::getInstance().malloc(numElements * sizeof(float));\n//    _memSegment = FastHostMemoryManager::getInstance().malloc(numElements * sizeof(float));\n}\n\nvoid HostNVMatrix::dealloc() {\n//    FastHostMemoryManager::getInstance().free(_memSegment);\n    HOST_MEMORY_MANAGER::getInstance().free(_memSegment);\n    _memSegment = NULL;\n//    checkCudaErrors(cudaFreeHost(_devData));\n}\n\ncudaTextureObject_t HostNVMatrix::getTextureObject() {\n    assert(false);\n    return 0;\n}\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/nvmatrix/src/nvmatrix_kernels.cu",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <cuda_runtime.h>\n#include \"../include/nvmatrix_kernels.cuh\"\n\n__global__ void kTile(const float* src, float* tgt, const uint srcWidth, const uint srcHeight, const uint tgtWidth, const uint tgtHeight) {\n    const int idx = blockIdx.x * blockDim.x + threadIdx.x;\n    const int numThreads = blockDim.x * gridDim.x;\n    //    const unsigned int numEls = tgtWidth * tgtHeight;\n    for (uint i = idx; i < tgtWidth * tgtHeight; i += numThreads) {\n        const uint y = i / tgtWidth;\n        const uint x = i % tgtWidth;\n        const uint srcY = y % srcHeight;\n        const uint srcX = x % srcWidth;\n        tgt[i] = src[srcY * srcWidth + srcX];\n    }\n}\n\n__global__ void kDotProduct_r(float* a, float* b, float* target,  const uint numElements) {\n    __shared__ float shmem[DP_BLOCKSIZE];\n\n    uint eidx = DP_BLOCKSIZE * blockIdx.x + threadIdx.x;\n    shmem[threadIdx.x] = 0;\n    if (eidx < gridDim.x * DP_BLOCKSIZE) {\n        for (; eidx < numElements; eidx += gridDim.x * DP_BLOCKSIZE) {\n            shmem[threadIdx.x] += a[eidx] * b[eidx];\n        }\n    }\n    __syncthreads();\n    if (threadIdx.x < 256) {\n        shmem[threadIdx.x] += shmem[threadIdx.x + 256];\n    }\n    __syncthreads();\n    if (threadIdx.x < 128) {\n        shmem[threadIdx.x] += shmem[threadIdx.x + 128];\n    }\n    __syncthreads();\n    if (threadIdx.x < 64) {\n        shmem[threadIdx.x] += shmem[threadIdx.x + 64];\n    }\n    __syncthreads();\n    if (threadIdx.x < 32) {\n        volatile float* mysh = &shmem[threadIdx.x];\n        *mysh += mysh[32];\n        *mysh += mysh[16];\n        *mysh += mysh[8];\n        *mysh += mysh[4];\n        *mysh += mysh[2];\n        *mysh += mysh[1];\n        if (threadIdx.x == 0) {\n            target[blockIdx.x] = *mysh;\n        }\n    }\n}\n\n__global__ void kSetupCurand(curandState *state, unsigned long long seed) {\n    const uint tidx = NUM_RND_THREADS_PER_BLOCK * blockIdx.x + threadIdx.x;\n    /* Each thread gets same seed, a different sequence number,\n     no offset */\n    curand_init(seed, tidx, 0, &state[tidx]);\n}\n\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/python_util/__init__.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License."
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/python_util/data.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport numpy as n\nfrom numpy.random import randn, rand, random_integers\nimport os\nfrom threading import Thread\nfrom util import *\n\nBATCH_META_FILE = \"batches.meta\"\n\nclass DataLoaderThread(Thread):\n    def __init__(self, path, tgt):\n        Thread.__init__(self)\n        self.path = path\n        self.tgt = tgt\n    def run(self):\n        self.tgt += [unpickle(self.path)]\n        \nclass DataProvider:\n    BATCH_REGEX = re.compile('^data_batch_(\\d+)(\\.\\d+)?$')\n    def __init__(self, data_dir, batch_range=None, init_epoch=1, init_batchnum=None, dp_params={}, test=False):\n        if batch_range == None:\n            batch_range = DataProvider.get_batch_nums(data_dir)\n        if init_batchnum is None or init_batchnum not in batch_range:\n            init_batchnum = batch_range[0]\n\n        self.data_dir = data_dir\n        self.batch_range = batch_range\n        self.curr_epoch = init_epoch\n        self.curr_batchnum = init_batchnum\n        self.dp_params = dp_params\n        self.batch_meta = self.get_batch_meta(data_dir)\n        self.data_dic = None\n        self.test = test\n        self.batch_idx = batch_range.index(init_batchnum)\n\n    def get_next_batch(self):\n        if self.data_dic is None or len(self.batch_range) > 1:\n            self.data_dic = self.get_batch(self.curr_batchnum)\n        epoch, batchnum = self.curr_epoch, self.curr_batchnum\n        self.advance_batch()\n\n        return epoch, batchnum, self.data_dic\n            \n    def get_batch(self, batch_num):\n        fname = self.get_data_file_name(batch_num)\n        if os.path.isdir(fname): # batch in sub-batches\n            sub_batches = sorted(os.listdir(fname), key=alphanum_key)\n            #print sub_batches\n            num_sub_batches = len(sub_batches)\n            tgts = [[] for i in xrange(num_sub_batches)]\n            threads = [DataLoaderThread(os.path.join(fname, s), tgt) for (s, tgt) in zip(sub_batches, tgts)]\n            for thread in threads:\n                thread.start()\n            for thread in threads:\n                thread.join()\n            \n            return [t[0] for t in tgts]\n        return unpickle(self.get_data_file_name(batch_num))\n    \n    def get_data_dims(self,idx=0):\n        return self.batch_meta['num_vis'] if idx == 0 else 1\n    \n    def advance_batch(self):\n        self.batch_idx = self.get_next_batch_idx()\n        self.curr_batchnum = self.batch_range[self.batch_idx]\n        if self.batch_idx == 0: # we wrapped\n            self.curr_epoch += 1\n            \n    def get_next_batch_idx(self):\n        return (self.batch_idx + 1) % len(self.batch_range)\n    \n    def get_next_batch_num(self):\n        return self.batch_range[self.get_next_batch_idx()]\n    \n    # get filename of current batch\n    def get_data_file_name(self, batchnum=None):\n        if batchnum is None:\n            batchnum = self.curr_batchnum\n        return os.path.join(self.data_dir, 'data_batch_%d' % batchnum)\n    \n    @classmethod\n    def get_instance(cls, data_dir, batch_range=None, init_epoch=1, init_batchnum=None, type=\"default\", dp_params={}, test=False):\n        # why the fuck can't i reference DataProvider in the original definition?\n        #cls.dp_classes['default'] = DataProvider\n        type = type or DataProvider.get_batch_meta(data_dir)['dp_type'] # allow data to decide data provider\n        if type.startswith(\"dummy-\"):\n            name = \"-\".join(type.split('-')[:-1]) + \"-n\"\n            if name not in dp_types:\n                raise DataProviderException(\"No such data provider: %s\" % type)\n            _class = dp_classes[name]\n            dims = int(type.split('-')[-1])\n            return _class(dims)\n        elif type in dp_types:\n            _class = dp_classes[type]\n            return _class(data_dir, batch_range, init_epoch, init_batchnum, dp_params, test)\n        \n        raise DataProviderException(\"No such data provider: %s\" % type)\n    \n    @classmethod\n    def register_data_provider(cls, name, desc, _class):\n        if name in dp_types:\n            raise DataProviderException(\"Data provider %s already registered\" % name)\n        dp_types[name] = desc\n        dp_classes[name] = _class\n        \n    @staticmethod\n    def get_batch_meta(data_dir):\n        return unpickle(os.path.join(data_dir, BATCH_META_FILE))\n    \n    @staticmethod\n    def get_batch_filenames(srcdir):\n        return sorted([f for f in os.listdir(srcdir) if DataProvider.BATCH_REGEX.match(f)], key=alphanum_key)\n    \n    @staticmethod\n    def get_batch_nums(srcdir):\n        names = DataProvider.get_batch_filenames(srcdir)\n        return sorted(list(set(int(DataProvider.BATCH_REGEX.match(n).group(1)) for n in names)))\n        \n    @staticmethod\n    def get_num_batches(srcdir):\n        return len(DataProvider.get_batch_nums(srcdir))\n    \nclass DummyDataProvider(DataProvider):\n    def __init__(self, data_dim):\n        #self.data_dim = data_dim\n        self.batch_range = [1]\n        self.batch_meta = {'num_vis': data_dim, 'data_in_rows':True}\n        self.curr_epoch = 1\n        self.curr_batchnum = 1\n        self.batch_idx = 0\n        \n    def get_next_batch(self):\n        epoch,  batchnum = self.curr_epoch, self.curr_batchnum\n        self.advance_batch()\n        data = rand(512, self.get_data_dims()).astype(n.single)\n        return self.curr_epoch, self.curr_batchnum, {'data':data}\n\nclass LabeledDataProvider(DataProvider):   \n    def __init__(self, data_dir, batch_range=None, init_epoch=1, init_batchnum=None, dp_params={}, test=False):\n        DataProvider.__init__(self, data_dir, batch_range, init_epoch, init_batchnum, dp_params, test)\n        \n    def get_num_classes(self):\n        return len(self.batch_meta['label_names'])\n        \nclass LabeledDummyDataProvider(DummyDataProvider):\n    def __init__(self, data_dim, num_classes=10, num_cases=7):\n        #self.data_dim = data_dim\n        self.batch_range = [1]\n        self.batch_meta = {'num_vis': data_dim,\n                           'label_names': [str(x) for x in range(num_classes)],\n                           'data_in_rows':True}\n        self.num_cases = num_cases\n        self.num_classes = num_classes\n        self.curr_epoch = 1\n        self.curr_batchnum = 1\n        self.batch_idx=0\n        self.data = None\n        \n    def get_num_classes(self):\n        return self.num_classes\n    \n    def get_next_batch(self):\n        epoch,  batchnum = self.curr_epoch, self.curr_batchnum\n        self.advance_batch()\n        if self.data is None:\n            data = rand(self.num_cases, self.get_data_dims()).astype(n.single) # <--changed to rand\n            labels = n.require(n.c_[random_integers(0,self.num_classes-1,self.num_cases)], requirements='C', dtype=n.single)\n            self.data, self.labels = data, labels\n        else:\n            data, labels = self.data, self.labels\n#        print data.shape, labels.shape\n        return self.curr_epoch, self.curr_batchnum, [data.T, labels.T ]\n\n    \ndp_types = {\"dummy-n\": \"Dummy data provider for n-dimensional data\",\n            \"dummy-labeled-n\": \"Labeled dummy data provider for n-dimensional data\"}\ndp_classes = {\"dummy-n\": DummyDataProvider,\n              \"dummy-labeled-n\": LabeledDummyDataProvider}\n    \nclass DataProviderException(Exception):\n    pass\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/python_util/gpumodel.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport numpy as n\nimport os\nfrom time import time, asctime, localtime, strftime\nfrom util import *\nfrom data import *\nfrom options import *\nfrom math import ceil, floor, sqrt\nfrom data import DataProvider, dp_types\nimport sys\nimport shutil\nimport platform\nfrom os import linesep as NL\nfrom threading import Thread\nimport tempfile as tf\n\nclass ModelStateException(Exception):\n    pass\n\nclass CheckpointWriter(Thread):\n    def __init__(self, path, dic):\n        Thread.__init__(self)\n        self.path = path\n        self.dic = dic\n        \n    def run(self):\n        save_dir = os.path.dirname(self.path)\n        save_file = os.path.basename(self.path)\n        # Write checkpoint to temporary filename\n        tmpfile = tf.NamedTemporaryFile(dir=os.path.dirname(save_dir), delete=False)\n        pickle(tmpfile, self.dic) # Also closes tf\n        # Move it to final filename\n        os.rename(tmpfile.name, self.path)\n        # Delete old checkpoints\n        for f in os.listdir(save_dir):\n            if f != save_file:\n                os.remove(os.path.join(save_dir, f))\n\n# GPU Model interface\nclass IGPUModel:\n    def __init__(self, model_name, op, load_dic, filename_options=[], dp_params={}):\n        # these are input parameters\n        self.model_name = model_name\n        self.op = op\n        self.options = op.options\n        self.load_dic = load_dic\n        self.filename_options = filename_options\n        self.dp_params = dp_params\n        self.device_ids = self.op.get_value('gpu')\n        self.fill_excused_options()\n        self.checkpoint_writer = None\n        #assert self.op.all_values_given()\n        \n        for o in op.get_options_list():\n            setattr(self, o.name, o.value)\n        self.loaded_from_checkpoint = load_dic is not None\n        # these are things that the model must remember but they're not input parameters\n        if self.loaded_from_checkpoint:\n            self.model_state = load_dic[\"model_state\"]\n            self.save_file = self.options[\"save_file_override\"].value if self.options[\"save_file_override\"].value_given else self.options['load_file'].value\n            if not os.path.isdir(self.save_file) and os.path.exists(self.save_file):\n                self.save_file = os.path.dirname(self.save_file)\n#            print self.options[\"save_file_override\"].value, self.save_file\n        else:\n            self.model_state = {}\n            self.save_file = self.options[\"save_file_override\"].value if self.options[\"save_file_override\"].value_given else os.path.join(self.options['save_path'].value, model_name + \"_\" + '_'.join(['%s_%s' % (char, self.options[opt].get_str_value()) for opt, char in filename_options]) + '_' + strftime('%Y-%m-%d_%H.%M.%S'))\n            self.model_state[\"train_outputs\"] = []\n            self.model_state[\"test_outputs\"] = []\n            self.model_state[\"epoch\"] = 1\n            self.model_state[\"batchnum\"] = self.train_batch_range[0]\n#            print self.save_file\n\n        self.init_data_providers()\n        if load_dic: \n            self.train_data_provider.advance_batch()\n            \n        # model state often requries knowledge of data provider, so it's initialized after\n        try:\n            self.init_model_state()\n        except ModelStateException, e:\n            print e\n            sys.exit(1)\n        for var, val in self.model_state.iteritems():\n            setattr(self, var, val)\n            \n        self.import_model()\n        self.init_model_lib()\n\n    def import_model(self):\n        print \"=========================\"\n        print \"Importing %s C++ module\" % ('_' + self.model_name)\n        self.libmodel = __import__('_' + self.model_name) \n                   \n    def fill_excused_options(self):\n        pass\n    \n    def init_data_providers(self):\n        self.dp_params['convnet'] = self\n        try:\n            self.test_data_provider = DataProvider.get_instance(self.data_path, self.test_batch_range,\n                                                                type=self.dp_type, dp_params=self.dp_params, test=True)\n            self.train_data_provider = DataProvider.get_instance(self.data_path, self.train_batch_range,\n                                                                     self.model_state[\"epoch\"], self.model_state[\"batchnum\"],\n                                                                     type=self.dp_type, dp_params=self.dp_params, test=False)\n        except DataProviderException, e:\n            print \"Unable to create data provider: %s\" % e\n            self.print_data_providers()\n            sys.exit()\n        \n    def init_model_state(self):\n        pass\n       \n    def init_model_lib(self):\n        pass\n    \n    def start(self):\n        if self.test_only:\n            self.test_outputs += [self.get_test_error()]\n            self.print_test_results()\n        else:\n            self.train()\n        self.cleanup()\n        if self.force_save:\n            self.save_state().join()\n        sys.exit(0)\n    \n    def train(self):\n        print \"=========================\"\n        print \"Training %s\" % self.model_name\n        self.op.print_values()\n        print \"=========================\"\n        self.print_model_state()\n        print \"Running on CUDA device(s) %s\" % \", \".join(\"%d\" % d for d in self.device_ids)\n        print \"Current time: %s\" % asctime(localtime())\n        print \"Saving checkpoints to %s\" % self.save_file\n        print \"=========================\"\n        next_data = self.get_next_batch()\n        while self.epoch <= self.num_epochs:\n            data = next_data\n            self.epoch, self.batchnum = data[0], data[1]\n            self.print_iteration()\n            sys.stdout.flush()\n            \n            compute_time_py = time()\n            self.start_batch(data)\n            \n            # load the next batch while the current one is computing\n            next_data = self.get_next_batch()\n            \n            batch_output = self.finish_batch()\n            self.train_outputs += [batch_output]\n            self.print_train_results()\n\n            if self.get_num_batches_done() % self.testing_freq == 0:\n                self.sync_with_host()\n                self.test_outputs += [self.get_test_error()]\n                self.print_test_results()\n                self.print_test_status()\n                self.conditional_save()\n            \n            self.print_elapsed_time(time() - compute_time_py)\n    \n    def cleanup(self):\n        if self.checkpoint_writer is not None:\n            self.checkpoint_writer.join()\n            self.checkpoint_writer = None\n        \n    def print_model_state(self):\n        pass\n    \n    def get_num_batches_done(self):\n        return len(self.train_batch_range) * (self.epoch - 1) + self.batchnum - self.train_batch_range[0] + 1\n    \n    def get_next_batch(self, train=True):\n        dp = self.train_data_provider\n        if not train:\n            dp = self.test_data_provider\n        return self.parse_batch_data(dp.get_next_batch(), train=train)\n    \n    def parse_batch_data(self, batch_data, train=True):\n        return batch_data[0], batch_data[1], batch_data[2]['data']\n    \n    def start_batch(self, batch_data, train=True):\n        self.libmodel.startBatch(batch_data[2], not train)\n    \n    def finish_batch(self):\n        return self.libmodel.finishBatch()\n    \n    def print_iteration(self):\n        print \"\\t%d.%d...\" % (self.epoch, self.batchnum),\n    \n    def print_elapsed_time(self, compute_time_py):\n        print \"(%.3f sec)\" % (compute_time_py)\n    \n    def print_train_results(self):\n        batch_error = self.train_outputs[-1][0]\n        if not (batch_error > 0 and batch_error < 2e20):\n            print \"Crazy train error: %.6f\" % batch_error\n            self.cleanup()\n\n        print \"Train error: %.6f \" % (batch_error),\n\n    def print_test_results(self):\n        batch_error = self.test_outputs[-1][0]\n        print \"%s\\t\\tTest error: %.6f\" % (NL, batch_error),\n\n    def print_test_status(self):\n        status = (len(self.test_outputs) == 1 or self.test_outputs[-1][0] < self.test_outputs[-2][0]) and \"ok\" or \"WORSE\"\n        print status,\n        \n    def sync_with_host(self):\n        if self.checkpoint_writer is not None:\n            self.checkpoint_writer.join()\n            self.checkpoint_writer = None\n        self.libmodel.syncWithHost()\n        \n    def conditional_save(self):\n        batch_error = self.test_outputs[-1][0]\n        if batch_error > 0 and batch_error < self.max_test_err:\n            self.save_state()\n        else:\n            print \"\\tTest error > %g, not saving.\" % self.max_test_err,\n    \n    def aggregate_test_outputs(self, test_outputs):\n        test_error = tuple([sum(t[r] for t in test_outputs) / (1 if self.test_one else len(self.test_batch_range)) for r in range(len(test_outputs[-1]))])\n        return test_error\n    \n    def get_test_error(self):\n        next_data = self.get_next_batch(train=False)\n        test_outputs = []\n        while True:\n            data = next_data\n            start_time_test = time()\n            self.start_batch(data, train=False)\n            load_next = (not self.test_one or self.test_only) and data[1] < self.test_batch_range[-1]\n            if load_next: # load next batch\n                next_data = self.get_next_batch(train=False)\n            test_outputs += [self.finish_batch()]\n            if self.test_only: # Print the individual batch results for safety\n                print \"batch %d: %s\" % (data[1], str(test_outputs[-1])),\n                self.print_elapsed_time(time() - start_time_test)\n            if not load_next:\n                break\n            sys.stdout.flush()\n            \n        return self.aggregate_test_outputs(test_outputs)\n    \n    def set_var(self, var_name, var_val):\n        setattr(self, var_name, var_val)\n        self.model_state[var_name] = var_val\n        return var_val\n        \n    def get_var(self, var_name):\n        return self.model_state[var_name]\n        \n    def has_var(self, var_name):\n        return var_name in self.model_state\n        \n    def save_state(self):\n        for att in self.model_state:\n            if hasattr(self, att):\n                self.model_state[att] = getattr(self, att)\n        \n        dic = {\"model_state\": self.model_state,\n               \"op\": self.op}\n            \n        checkpoint_file = \"%d.%d\" % (self.epoch, self.batchnum)\n        checkpoint_file_full_path = os.path.join(self.save_file, checkpoint_file)\n        if not os.path.exists(self.save_file):\n            os.makedirs(self.save_file)\n    \n        assert self.checkpoint_writer is None\n        self.checkpoint_writer = CheckpointWriter(checkpoint_file_full_path, dic)\n        self.checkpoint_writer.start()\n        print \"-------------------------------------------------------\"\n        print \"Saved checkpoint to %s\" % self.save_file\n        print \"=======================================================\",\n        return self.checkpoint_writer\n        \n    def get_progress(self):\n        num_batches_total = self.num_epochs * len(self.train_batch_range)\n        return min(1.0, max(0.0, float(self.get_num_batches_done()-1) / num_batches_total))\n    \n    @staticmethod\n    def load_checkpoint(load_dir):\n        if os.path.isdir(load_dir):\n            return unpickle(os.path.join(load_dir, sorted(os.listdir(load_dir), key=alphanum_key)[-1]))\n        return unpickle(load_dir)\n\n    @staticmethod\n    def get_options_parser():\n        op = OptionsParser()\n        op.add_option(\"load-file\", \"load_file\", StringOptionParser, \"Load file\", default=\"\", excuses=OptionsParser.EXCUSE_ALL)\n        op.add_option(\"save-path\", \"save_path\", StringOptionParser, \"Save path\", excuses=['save_file_override'])\n        op.add_option(\"save-file\", \"save_file_override\", StringOptionParser, \"Save file override\", excuses=['save_path'])\n        op.add_option(\"train-range\", \"train_batch_range\", RangeOptionParser, \"Data batch range: training\")\n        op.add_option(\"test-range\", \"test_batch_range\", RangeOptionParser, \"Data batch range: testing\")\n        op.add_option(\"data-provider\", \"dp_type\", StringOptionParser, \"Data provider\", default=\"default\")\n        op.add_option(\"test-freq\", \"testing_freq\", IntegerOptionParser, \"Testing frequency\", default=25)\n        op.add_option(\"epochs\", \"num_epochs\", IntegerOptionParser, \"Number of epochs\", default=500)\n        op.add_option(\"data-path\", \"data_path\", StringOptionParser, \"Data path\")\n        \n        op.add_option(\"max-test-err\", \"max_test_err\", FloatOptionParser, \"Maximum test error for saving\")\n        op.add_option(\"test-only\", \"test_only\", BooleanOptionParser, \"Test and quit?\", default=0)\n        op.add_option(\"test-one\", \"test_one\", BooleanOptionParser, \"Test on one batch at a time?\", default=1)\n        op.add_option(\"force-save\", \"force_save\", BooleanOptionParser, \"Force save before quitting\", default=0)\n        op.add_option(\"gpu\", \"gpu\", ListOptionParser(IntegerOptionParser), \"GPU override\")\n        return op\n\n    @staticmethod\n    def print_data_providers():\n        print \"Available data providers:\"\n        for dp, desc in dp_types.iteritems():\n            print \"    %s: %s\" % (dp, desc)\n            \n\n    @staticmethod\n    def parse_options(op):\n        try:\n            load_dic = None\n            options = op.parse()\n            load_location = None\n#            print options['load_file'].value_given, options['save_file_override'].value_given\n#            print options['save_file_override'].value\n            if options['load_file'].value_given:\n                load_location = options['load_file'].value\n            elif options['save_file_override'].value_given and os.path.exists(options['save_file_override'].value):\n                load_location = options['save_file_override'].value\n            \n            if load_location is not None:\n                load_dic = IGPUModel.load_checkpoint(load_location)\n                old_op = load_dic[\"op\"]\n                old_op.merge_from(op)\n                op = old_op\n            op.eval_expr_defaults()\n            return op, load_dic\n        except OptionMissingException, e:\n            print e\n            op.print_usage()\n        except OptionException, e:\n            print e\n        except UnpickleError, e:\n            print \"Error loading checkpoint:\"\n            print e\n        sys.exit()\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/python_util/options.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport sys\nfrom getopt import getopt\nimport os\nimport re\n#import types\n\nTERM_BOLD_START = \"\\033[1m\"\nTERM_BOLD_END = \"\\033[0m\"\n\nclass Option:\n    def __init__(self, letter, name, desc, parser, set_once, default, excuses, requires, save):\n        assert not name is None\n        self.letter = letter\n        self.name = name\n        self.desc = desc\n        self.parser = parser\n        self.set_once = set_once\n        self.default = default\n        self.excuses = excuses\n        self.requires = requires\n        self.save = save\n        \n        self.value = None\n        self.value_given = False\n        self.prefixed_letter = min(2, len(letter)) * '-' + letter\n        \n    def set_value(self, value, parse=True):\n        try:\n            self.value = self.parser.parse(value) if parse else value\n            self.value_given = True\n#            print self.name, self.value\n        except OptionException, e:\n            raise OptionException(\"Unable to parse option %s (%s): %s\" % (self.prefixed_letter, self.desc, e))\n        \n    def set_default(self):\n        if not self.default is None:\n            self.value = self.default\n    \n    def eval_expr_default(self, env):\n        try:\n            if isinstance(self.default, OptionExpression) and not self.value_given:\n                self.value = self.default.evaluate(env)\n                if not self.parser.is_type(self.value):\n                    raise OptionException(\"expression result %s is not of right type (%s)\" % (self.value, self.parser.get_type_str()))\n        except Exception, e:\n            raise OptionException(\"Unable to set default value for option %s (%s): %s\" % (self.prefixed_letter, self.desc, e))\n            \n    def get_str_value(self, get_default_str=False):\n        val = self.value\n        if get_default_str: val = self.default\n        if val is None: return \"\"\n        if isinstance(val, OptionExpression):\n            return val.expr\n        return self.parser.to_string(val)\n\nclass OptionsParser:\n    \"\"\"An option parsing class. All options without default values are mandatory, unless a excuses\n    option (usually a load file) is given.\n    Does not support options without arguments.\"\"\"\n    SORT_LETTER = 1\n    SORT_DESC = 2\n    SORT_EXPR_LAST = 3\n    EXCUSE_ALL = \"all\"\n    def __init__(self):\n        self.options = {}\n        \n    def add_option(self, letter, name, parser, desc, set_once=False, default=None, excuses=[], requires=[], save=True):\n        \"\"\"\n        The letter parameter is the actual parameter that the user will have to supply on the command line.\n        The name parameter is some name to be given to this option and must be a valid python variable name.\n        \n        An explanation of the \"default\" parameter:\n        The default value, if specified, should have the same type as the option.\n        You can also specify an expression as the default value. In this case, the default value of the parameter\n        will be the output of the expression. The expression may assume all other option names\n        as local variables. For example, you can define the hidden bias\n        learning rate to be 10 times the weight learning rate by setting this default:\n        \n        default=OptionExpression(\"eps_w * 10\") (assuming an option named eps_w exists).\n        \n        However, it is up to you to make sure you do not make any circular expression definitions.\n        \n        Note that the order in which the options are parsed is arbitrary.\n        In particular, expression default values that depend on other expression default values\n        will often raise errors (depending on the order in which they happen to be parsed).\n        Therefore it is best not to make the default value of one variable depend on the value\n        of another if the other variable's default value is itself an expression.\n        \n        An explanation of the \"excuses\" parameter:\n        All options are mandatory, but certain options can exclude other options from being mandatory.\n        For example, if the excuses parameter for option \"load_file\" is [\"num_hid\", \"num_vis\"],\n        then the options num_hid and num_vis are not mandatory as long as load_file is specified.\n        Use the special flag EXCUSE_ALL to allow an option to make all other options optional.\n        \"\"\"\n        \n        assert name not in self.options\n        self.options[name] = Option(letter, name, desc, parser, set_once, default, excuses, requires, save)\n    \n    def set_value(self, name, value, parse=True):\n        self.options[name].set_value(value, parse=parse)\n    \n    def get_value(self, name):\n        return self.options[name].value\n        \n    def delete_option(self, name):\n        if name in self.options:\n            del self.options[name]\n            \n    def parse(self, eval_expr_defaults=False):\n        \"\"\"Parses the options in sys.argv based on the options added to this parser. The\n        default behavior is to leave any expression default options as OptionExpression objects.\n        Set eval_expr_defaults=True to circumvent this.\"\"\"\n        short_opt_str = ''.join([\"%s:\" % self.options[name].letter for name in self.options if len(self.options[name].letter) == 1])\n        long_opts = [\"%s=\" % self.options[name].letter for name in self.options if len(self.options[name].letter) > 1]\n        (go, ga) = getopt(sys.argv[1:], short_opt_str, longopts=long_opts)\n        dic = dict(go)\n        \n        for o in self.get_options_list(sort_order=self.SORT_EXPR_LAST):\n            if o.prefixed_letter in dic:  \n                o.set_value(dic[o.prefixed_letter])\n            else:\n                # check if excused or has default\n                excused = max([o2.prefixed_letter in dic for o2 in self.options.values() if o2.excuses == self.EXCUSE_ALL or o.name in o2.excuses])\n                if not excused and o.default is None:\n                    raise OptionMissingException(\"Option %s (%s) not supplied\" % (o.prefixed_letter, o.desc))\n                o.set_default()\n            # check requirements\n            if o.prefixed_letter in dic:\n                for o2 in self.get_options_list(sort_order=self.SORT_LETTER):\n                    if o2.name in o.requires and o2.prefixed_letter not in dic:\n                        raise OptionMissingException(\"Option %s (%s) requires option %s (%s)\" % (o.prefixed_letter, o.desc,\n                                                                                                 o2.prefixed_letter, o2.desc))\n        if eval_expr_defaults:\n            self.eval_expr_defaults()\n        return self.options\n    \n    def merge_from(self, op2):\n        \"\"\"Merges the options in op2 into this instance, but does not overwrite\n        this instances's SET options with op2's default values.\"\"\"\n        for name, o in self.options.iteritems():\n            if name in op2.options and ((op2.options[name].value_given and op2.options[name].value != self.options[name].value) or not op2.options[name].save):\n                if op2.options[name].set_once:\n                    raise OptionException(\"Option %s (%s) cannot be changed\" % (op2.options[name].prefixed_letter, op2.options[name].desc))\n                self.options[name] = op2.options[name]\n        for name in op2.options:\n            if name not in self.options:\n                self.options[name] = op2.options[name]\n    \n    def eval_expr_defaults(self):\n        env = dict([(name, o.value) for name, o in self.options.iteritems()])\n        for o in self.options.values():\n            o.eval_expr_default(env)\n            \n    def all_values_given(self):\n        return max([o.value_given for o in self.options.values() if o.default is not None])\n    \n    def get_options_list(self, sort_order=SORT_LETTER):\n        \"\"\" Returns the list of Option objects in this OptionParser,\n        sorted as specified\"\"\"\n        \n        cmp = lambda x, y: (x.desc < y.desc and -1 or 1)\n        if sort_order == self.SORT_LETTER:\n            cmp = lambda x, y: (x.letter < y.letter and -1 or 1)\n        elif sort_order == self.SORT_EXPR_LAST:\n            cmp = lambda x, y: (type(x.default) == OptionExpression and 1 or -1)\n        return sorted(self.options.values(), cmp=cmp)\n    \n    def print_usage(self, print_constraints=False):\n        print \"%s usage:\" % os.path.basename(sys.argv[0])\n        opslist = self.get_options_list()\n\n        usage_strings = []\n        num_def = 0\n        for o in opslist:\n            excs = ' '\n            if o.default is None:\n                excs = ', '.join(sorted([o2.prefixed_letter for o2 in self.options.values() if o2.excuses == self.EXCUSE_ALL or o.name in o2.excuses]))\n            reqs = ', '.join(sorted([o2.prefixed_letter for o2 in self.options.values() if o2.name in o.requires]))\n            usg = (OptionsParser._bold(o.prefixed_letter) + \" <%s>\" % o.parser.get_type_str(), o.desc, (\"[%s]\" % o.get_str_value(get_default_str=True)) if not o.default is None else None, excs, reqs)\n            if o.default is None:\n                usage_strings += [usg]\n            else:\n                usage_strings.insert(num_def, usg)\n                num_def += 1\n                \n        col_widths = [self._longest_value(usage_strings, key=lambda x:x[i]) for i in range(len(usage_strings[0]) - 1)]\n\n        col_names = [\"    Option\", \"Description\", \"Default\"]\n        if print_constraints:\n            col_names += [\"Excused by\", \"Requires\"]\n        for i, s in enumerate(col_names):\n            print self._bold(s.ljust(col_widths[i])),\n\n        print \"\"\n        for l, d, de, ex, req in usage_strings:\n            if de is None:\n                de = ' '\n                print (\"     %s  -\" % l.ljust(col_widths[0])), d.ljust(col_widths[1]), de.ljust(col_widths[2]),\n            else:\n                print (\"    [%s] -\" % l.ljust(col_widths[0])), d.ljust(col_widths[1]), de.ljust(col_widths[2]),\n            if print_constraints:\n                print ex.ljust(col_widths[3]), req\n            else:\n                print \"\"\n                \n    def print_values(self):\n        longest_desc = self._longest_value(self.options.values(), key=lambda x:x.desc)\n        longest_def_value = self._longest_value([v for v in self.options.values() if not v.value_given and not v.default is None],\n                                                 key=lambda x:x.get_str_value())\n        for o in self.get_options_list(sort_order=self.SORT_DESC):\n            print \"%s: %s %s\" % (o.desc.ljust(longest_desc), o.get_str_value().ljust(longest_def_value), (not o.value_given and not o.default is None) and \"[DEFAULT]\" or \"\")\n    \n    @staticmethod\n    def _longest_value(values, key=lambda x:x):\n        mylen = lambda x: 0 if x is None else len(x)\n        return mylen(key(max(values, key=lambda x:mylen(key(x)))))\n\n    @staticmethod\n    def _bold(str):\n        return TERM_BOLD_START + str + TERM_BOLD_END\n\nclass OptionException(Exception):\n    pass\n                \nclass OptionMissingException(OptionException):\n    pass\n\nclass OptionParser:\n    @staticmethod\n    def parse(value):\n        return str(value)\n       \n    @staticmethod\n    def to_string(value):\n        return str(value)\n    \n    @staticmethod\n    def get_type_str():\n        pass\n    \nclass IntegerOptionParser(OptionParser):\n    @staticmethod\n    def parse(value):\n        try:\n            return int(value)\n        except:\n            raise OptionException(\"argument is not an integer\")\n    \n    @staticmethod\n    def get_type_str():\n        return \"int\"\n    \n    @staticmethod\n    def is_type(value):\n        return type(value) == int\n    \nclass BooleanOptionParser(OptionParser):\n    @staticmethod\n    def parse(value):\n        try:\n            v = int(value)\n            if not v in (0,1):\n                raise OptionException\n            return v\n        except:\n            raise OptionException(\"argument is not a boolean\")\n    \n    @staticmethod\n    def get_type_str():\n        return \"0/1\"\n    \n    @staticmethod\n    def is_type(value):\n        return type(value) == int and value in (0, 1)\n        \nclass StringOptionParser(OptionParser):       \n    @staticmethod\n    def get_type_str():\n        return \"string\"\n    \n    @staticmethod\n    def is_type(value):\n        return type(value) == str\n    \nclass FloatOptionParser(OptionParser):\n    @staticmethod\n    def parse(value):\n        try:\n            return float(value)\n        except:\n            raise OptionException(\"argument is not a float\")\n    \n    @staticmethod\n    def to_string(value):\n        return \"%.6g\" % value\n    \n    @staticmethod\n    def get_type_str():\n        return \"float\"\n    \n    @staticmethod\n    def is_type(value):\n        return type(value) == float\n    \nclass RangeOptionParser(OptionParser):\n    @staticmethod\n    def parse(value):\n        m = re.match(\"^(\\d+)\\-(\\d+)$\", value)\n        try:\n            if m: return range(int(m.group(1)), int(m.group(2)) + 1)\n            return [int(value)]\n        except:\n            raise OptionException(\"argument is neither an integer nor a range\")\n    \n    @staticmethod\n    def to_string(value):\n        return \"%d-%d\" % (value[0], value[-1])\n    \n    @staticmethod\n    def get_type_str():\n        return \"int[-int]\"\n    \n    @staticmethod\n    def is_type(value):\n        return type(value) == list\n    \nclass ListOptionParser(OptionParser):\n    \"\"\"\n    A parser that parses a delimited list of items. If the \"parsers\"\n    argument is a list of parsers, then the list of items must have the form and length\n    specified by that list. \n    \n    Example:\n    ListOptionParser([FloatOptionParser, IntegerOptionParser])\n    \n    would parse \"0.5,3\" but not \"0.5,3,0.6\" or \"0.5\" or \"3,0.5\".\n    \n    If the \"parsers\" argument is another parser, then the list of items may be of\n    arbitrary length, but each item must be parseable by the given parser.\n    \n    Example:\n    ListOptionParser(FloatOptionParser)\n    \n    would parse \"0.5\" and \"0.5,0.3\" and \"0.5,0.3,0.6\", etc.\n    \"\"\"\n    def __init__(self, parsers, sepchar=','):\n        self.parsers = parsers\n        self.sepchar = sepchar\n        \n    def parse(self, value):\n        values = value.split(self.sepchar)\n        if type(self.parsers) == list and len(values) != len(self.parsers):\n            raise OptionException(\"requires %d arguments, given %d\" % (len(self.parsers), len(values)))\n        \n        try:\n            if type(self.parsers) == list:\n                return [p.parse(v) for p, v in zip(self.parsers, values)]\n            return [self.parsers.parse(v) for v in values]\n        except:\n            raise OptionException(\"argument is not of the form %s\" % self.get_type_str())\n    \n    def to_string(self, value):\n        if type(self.parsers) == list:\n            return self.sepchar.join([p.to_string(v) for p, v in zip(self.parsers, value)])\n        return self.sepchar.join([self.parsers.to_string(v) for v in value])\n    \n    def get_type_str(self):\n        if type(self.parsers) == list:\n            return self.sepchar.join([p.get_type_str() for p in self.parsers])\n        return \"%s%s...\" % (self.parsers.get_type_str(), self.sepchar)\n    \n    @staticmethod\n    def is_type(value):\n        return type(value) == list\n    \nclass OptionExpression:\n    \"\"\"\n    This allows you to specify option values in terms of other option values.\n    Example:\n    op.add_option(\"eps-w\", \"eps_w\", ListOptionParser(FloatOptionParser), \"Weight learning rates for each layer\")\n    op.add_option(\"eps-b\", \"eps_b\", ListOptionParser(FloatOptionParser), \"Bias learning rates for each layer\", default=OptionExpression(\"[o * 10 for o in eps_w]\"))\n    \n    This says: the default bias learning rate for each layer is 10\n    times the weight learning rate for that layer.\n    \"\"\"\n    def __init__(self, expr):\n        self.expr = expr\n    \n    def evaluate(self, options):\n        locals().update(options)\n        try:\n            return eval(self.expr)\n        except Exception, e:\n            raise OptionException(\"expression '%s': unable to parse: %s\" % (self.expr, e))\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/python_util/util.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport re\nimport cPickle\nimport os\nfrom cStringIO import StringIO\n\nclass UnpickleError(Exception):\n    pass\n\nGPU_LOCK_NO_SCRIPT = -2\nGPU_LOCK_NO_LOCK = -1\n\ndef pickle(filename, data):\n    fo = filename\n    if type(filename) == str:\n        fo = open(filename, \"w\")\n    \n    cPickle.dump(data, fo, protocol=cPickle.HIGHEST_PROTOCOL)\n    fo.close()\n    \ndef unpickle(filename):\n    if not os.path.exists(filename):\n        raise UnpickleError(\"Path '%s' does not exist.\" % filename)\n\n    fo = open(filename, 'r')\n    z = StringIO()\n    file_size = os.fstat(fo.fileno()).st_size\n    # Read 1GB at a time to avoid overflow\n    while fo.tell() < file_size:\n        z.write(fo.read(1 << 30))\n    fo.close()\n    dict = cPickle.loads(z.getvalue())\n    z.close()\n    \n    return dict\n\ndef is_intel_machine():\n    VENDOR_ID_REGEX = re.compile('^vendor_id\\s+: (\\S+)')\n    f = open('/proc/cpuinfo')\n    for line in f:\n        m = VENDOR_ID_REGEX.match(line)\n        if m:\n            f.close()\n            return m.group(1) == 'GenuineIntel'\n    f.close()\n    return False\n\n# Returns the CPUs associated with a given GPU\ndef get_cpus_for_gpu(gpu):\n    #proc = subprocess.Popen(['nvidia-smi', '-q', '-i', str(gpu)], stdout=subprocess.PIPE)\n    #lines = proc.communicate()[0]\n    #lines = subprocess.check_output(['nvidia-smi', '-q', '-i', str(gpu)]).split(os.linesep)\n\n    with open('/proc/driver/nvidia/gpus/%d/information' % gpu) as f:\n        for line in f:\n            if line.startswith('Bus Location'):\n                bus_id = line.split(':', 1)[1].strip()\n                bus_id = bus_id[:7] + ':' + bus_id[8:]\n                ff = open('/sys/module/nvidia/drivers/pci:nvidia/%s/local_cpulist' % bus_id)\n                cpus_str = ff.readline()\n                ff.close()\n                cpus = [cpu for s in cpus_str.split(',') for cpu in range(int(s.split('-')[0]),int(s.split('-')[1])+1)]\n                return cpus\n    return [-1]\n\ndef get_cpu():\n    if is_intel_machine():\n        return 'intel'\n    return 'amd'\n\ndef is_windows_machine():\n    return os.name == 'nt'\n    \ndef tryint(s):\n    try:\n        return int(s)\n    except:\n        return s\n\ndef alphanum_key(s):\n    return [tryint(c) for c in re.split('([0-9]+)', s)]\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/shownet.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport sys\nfrom tarfile import TarFile, TarInfo\nfrom matplotlib import pylab as pl\nimport numpy as n\nimport getopt as opt\nfrom python_util.util import *\nfrom math import sqrt, ceil, floor\nfrom python_util.gpumodel import IGPUModel\nimport random as r\nimport numpy.random as nr\nfrom convnet import ConvNet\nfrom python_util.options import *\nfrom PIL import Image\nfrom time import sleep\n\nclass ShowNetError(Exception):\n    pass\n\nclass ShowConvNet(ConvNet):\n    def __init__(self, op, load_dic):\n        ConvNet.__init__(self, op, load_dic)\n\n    def init_data_providers(self):\n        self.need_gpu = self.op.get_value('show_preds') \n        class Dummy:\n            def advance_batch(self):\n                pass\n        if self.need_gpu:\n            ConvNet.init_data_providers(self)\n        else:\n            self.train_data_provider = self.test_data_provider = Dummy()\n    \n    def import_model(self):\n        if self.need_gpu:\n            ConvNet.import_model(self)\n            \n    def init_model_state(self):\n        if self.op.get_value('show_preds'):\n            self.softmax_name = self.op.get_value('show_preds')\n            \n    def init_model_lib(self):\n        if self.need_gpu:\n            ConvNet.init_model_lib(self)\n\n    def plot_cost(self):\n        if self.show_cost not in self.train_outputs[0][0]:\n            raise ShowNetError(\"Cost function with name '%s' not defined by given convnet.\" % self.show_cost)\n#        print self.test_outputs\n        train_errors = [eval(self.layers[self.show_cost]['outputFilter'])(o[0][self.show_cost], o[1])[self.cost_idx] for o in self.train_outputs]\n        test_errors = [eval(self.layers[self.show_cost]['outputFilter'])(o[0][self.show_cost], o[1])[self.cost_idx] for o in self.test_outputs]\n        if self.smooth_test_errors:\n            test_errors = [sum(test_errors[max(0,i-len(self.test_batch_range)):i])/(i-max(0,i-len(self.test_batch_range))) for i in xrange(1,len(test_errors)+1)]\n        numbatches = len(self.train_batch_range)\n        test_errors = n.row_stack(test_errors)\n        test_errors = n.tile(test_errors, (1, self.testing_freq))\n        test_errors = list(test_errors.flatten())\n        test_errors += [test_errors[-1]] * max(0,len(train_errors) - len(test_errors))\n        test_errors = test_errors[:len(train_errors)]\n\n        numepochs = len(train_errors) / float(numbatches)\n        pl.figure(1)\n        x = range(0, len(train_errors))\n        pl.plot(x, train_errors, 'k-', label='Training set')\n        pl.plot(x, test_errors, 'r-', label='Test set')\n        pl.legend()\n        ticklocs = range(numbatches, len(train_errors) - len(train_errors) % numbatches + 1, numbatches)\n        epoch_label_gran = int(ceil(numepochs / 20.)) \n        epoch_label_gran = int(ceil(float(epoch_label_gran) / 10) * 10) if numepochs >= 10 else epoch_label_gran \n        ticklabels = map(lambda x: str((x[1] / numbatches)) if x[0] % epoch_label_gran == epoch_label_gran-1 else '', enumerate(ticklocs))\n\n        pl.xticks(ticklocs, ticklabels)\n        pl.xlabel('Epoch')\n#        pl.ylabel(self.show_cost)\n        pl.title('%s[%d]' % (self.show_cost, self.cost_idx))\n#        print \"plotted cost\"\n        \n    def make_filter_fig(self, filters, filter_start, fignum, _title, num_filters, combine_chans, FILTERS_PER_ROW=16):\n        MAX_ROWS = 24\n        MAX_FILTERS = FILTERS_PER_ROW * MAX_ROWS\n        num_colors = filters.shape[0]\n        f_per_row = int(ceil(FILTERS_PER_ROW / float(1 if combine_chans else num_colors)))\n        filter_end = min(filter_start+MAX_FILTERS, num_filters)\n        filter_rows = int(ceil(float(filter_end - filter_start) / f_per_row))\n    \n        filter_pixels = filters.shape[1]\n        filter_size = int(sqrt(filters.shape[1]))\n        fig = pl.figure(fignum)\n        fig.text(.5, .95, '%s %dx%d filters %d-%d' % (_title, filter_size, filter_size, filter_start, filter_end-1), horizontalalignment='center') \n        num_filters = filter_end - filter_start\n        if not combine_chans:\n            bigpic = n.zeros((filter_size * filter_rows + filter_rows + 1, filter_size*num_colors * f_per_row + f_per_row + 1), dtype=n.single)\n        else:\n            bigpic = n.zeros((3, filter_size * filter_rows + filter_rows + 1, filter_size * f_per_row + f_per_row + 1), dtype=n.single)\n    \n        for m in xrange(filter_start,filter_end ):\n            filter = filters[:,:,m]\n            y, x = (m - filter_start) / f_per_row, (m - filter_start) % f_per_row\n            if not combine_chans:\n                for c in xrange(num_colors):\n                    filter_pic = filter[c,:].reshape((filter_size,filter_size))\n                    bigpic[1 + (1 + filter_size) * y:1 + (1 + filter_size) * y + filter_size,\n                           1 + (1 + filter_size*num_colors) * x + filter_size*c:1 + (1 + filter_size*num_colors) * x + filter_size*(c+1)] = filter_pic\n            else:\n                filter_pic = filter.reshape((3, filter_size,filter_size))\n                bigpic[:,\n                       1 + (1 + filter_size) * y:1 + (1 + filter_size) * y + filter_size,\n                       1 + (1 + filter_size) * x:1 + (1 + filter_size) * x + filter_size] = filter_pic\n                \n        pl.xticks([])\n        pl.yticks([])\n        if not combine_chans:\n            pl.imshow(bigpic, cmap=pl.cm.gray, interpolation='nearest')\n        else:\n            bigpic = bigpic.swapaxes(0,2).swapaxes(0,1)\n            pl.imshow(bigpic, interpolation='nearest')        \n        \n    def plot_filters(self):\n        FILTERS_PER_ROW = 16\n        filter_start = 0 # First filter to show\n        if self.show_filters not in self.layers:\n            raise ShowNetError(\"Layer with name '%s' not defined by given convnet.\" % self.show_filters)\n        layer = self.layers[self.show_filters]\n        filters = layer['weights'][self.input_idx]\n#        filters = filters - filters.min()\n#        filters = filters / filters.max()\n        if layer['type'] == 'fc': # Fully-connected layer\n            num_filters = layer['outputs']\n            channels = self.channels\n            filters = filters.reshape(channels, filters.shape[0]/channels, filters.shape[1])\n        elif layer['type'] in ('conv', 'local'): # Conv layer\n            num_filters = layer['filters']\n            channels = layer['filterChannels'][self.input_idx]\n            if layer['type'] == 'local':\n                filters = filters.reshape((layer['modules'], channels, layer['filterPixels'][self.input_idx], num_filters))\n                filters = filters[:, :, :, self.local_plane] # first map for now (modules, channels, pixels)\n                filters = filters.swapaxes(0,2).swapaxes(0,1)\n                num_filters = layer['modules']\n#                filters = filters.swapaxes(0,1).reshape(channels * layer['filterPixels'][self.input_idx], num_filters * layer['modules'])\n#                num_filters *= layer['modules']\n                FILTERS_PER_ROW = layer['modulesX']\n            else:\n                filters = filters.reshape(channels, filters.shape[0]/channels, filters.shape[1])\n        \n        \n        # Convert YUV filters to RGB\n        if self.yuv_to_rgb and channels == 3:\n            R = filters[0,:,:] + 1.28033 * filters[2,:,:]\n            G = filters[0,:,:] + -0.21482 * filters[1,:,:] + -0.38059 * filters[2,:,:]\n            B = filters[0,:,:] + 2.12798 * filters[1,:,:]\n            filters[0,:,:], filters[1,:,:], filters[2,:,:] = R, G, B\n        combine_chans = not self.no_rgb and channels == 3\n        \n        # Make sure you don't modify the backing array itself here -- so no -= or /=\n        if self.norm_filters:\n            #print filters.shape\n            filters = filters - n.tile(filters.reshape((filters.shape[0] * filters.shape[1], filters.shape[2])).mean(axis=0).reshape(1, 1, filters.shape[2]), (filters.shape[0], filters.shape[1], 1))\n            filters = filters / n.sqrt(n.tile(filters.reshape((filters.shape[0] * filters.shape[1], filters.shape[2])).var(axis=0).reshape(1, 1, filters.shape[2]), (filters.shape[0], filters.shape[1], 1)))\n            #filters = filters - n.tile(filters.min(axis=0).min(axis=0), (3, filters.shape[1], 1))\n            #filters = filters / n.tile(filters.max(axis=0).max(axis=0), (3, filters.shape[1], 1))\n        #else:\n        filters = filters - filters.min()\n        filters = filters / filters.max()\n\n        self.make_filter_fig(filters, filter_start, 2, 'Layer %s' % self.show_filters, num_filters, combine_chans, FILTERS_PER_ROW=FILTERS_PER_ROW)\n    \n    def plot_predictions(self):\n        epoch, batch, data = self.get_next_batch(train=False) # get a test batch\n        num_classes = self.test_data_provider.get_num_classes()\n        NUM_ROWS = 2\n        NUM_COLS = 4\n        NUM_IMGS = NUM_ROWS * NUM_COLS if not self.save_preds else data[0].shape[1]\n        NUM_TOP_CLASSES = min(num_classes, 5) # show this many top labels\n        NUM_OUTPUTS = self.model_state['layers'][self.softmax_name]['outputs']\n        PRED_IDX = 1\n        \n        label_names = [lab.split(',')[0] for lab in self.test_data_provider.batch_meta['label_names']]\n        if self.only_errors:\n            preds = n.zeros((data[0].shape[1], NUM_OUTPUTS), dtype=n.single)\n        else:\n            preds = n.zeros((NUM_IMGS, NUM_OUTPUTS), dtype=n.single)\n            #rand_idx = nr.permutation(n.r_[n.arange(1), n.where(data[1] == 552)[1], n.where(data[1] == 795)[1], n.where(data[1] == 449)[1], n.where(data[1] == 274)[1]])[:NUM_IMGS]\n            rand_idx = nr.randint(0, data[0].shape[1], NUM_IMGS)\n            if NUM_IMGS < data[0].shape[1]:\n                data = [n.require(d[:,rand_idx], requirements='C') for d in data]\n#        data += [preds]\n        # Run the model\n        print  [d.shape for d in data], preds.shape\n        self.libmodel.startFeatureWriter(data, [preds], [self.softmax_name])\n        IGPUModel.finish_batch(self)\n        print preds\n        data[0] = self.test_data_provider.get_plottable_data(data[0])\n\n        if self.save_preds:\n            if not gfile.Exists(self.save_preds):\n                gfile.MakeDirs(self.save_preds)\n            preds_thresh = preds > 0.5 # Binarize predictions\n            data[0] = data[0] * 255.0\n            data[0][data[0]<0] = 0\n            data[0][data[0]>255] = 255\n            data[0] = n.require(data[0], dtype=n.uint8)\n            dir_name = '%s_predictions_batch_%d' % (os.path.basename(self.save_file), batch)\n            tar_name = os.path.join(self.save_preds, '%s.tar' % dir_name)\n            tfo = gfile.GFile(tar_name, \"w\")\n            tf = TarFile(fileobj=tfo, mode='w')\n            for img_idx in xrange(NUM_IMGS):\n                img = data[0][img_idx,:,:,:]\n                imsave = Image.fromarray(img)\n                prefix = \"CORRECT\" if data[1][0,img_idx] == preds_thresh[img_idx,PRED_IDX] else \"FALSE_POS\" if preds_thresh[img_idx,PRED_IDX] == 1 else \"FALSE_NEG\"\n                file_name = \"%s_%.2f_%d_%05d_%d.png\" % (prefix, preds[img_idx,PRED_IDX], batch, img_idx, data[1][0,img_idx])\n#                gf = gfile.GFile(file_name, \"w\")\n                file_string = StringIO()\n                imsave.save(file_string, \"PNG\")\n                tarinf = TarInfo(os.path.join(dir_name, file_name))\n                tarinf.size = file_string.tell()\n                file_string.seek(0)\n                tf.addfile(tarinf, file_string)\n            tf.close()\n            tfo.close()\n#                gf.close()\n            print \"Wrote %d prediction PNGs to %s\" % (preds.shape[0], tar_name)\n        else:\n            fig = pl.figure(3, figsize=(12,9))\n            fig.text(.4, .95, '%s test samples' % ('Mistaken' if self.only_errors else 'Random'))\n            if self.only_errors:\n                # what the net got wrong\n                if NUM_OUTPUTS > 1:\n                    err_idx = [i for i,p in enumerate(preds.argmax(axis=1)) if p not in n.where(data[2][:,i] > 0)[0]]\n                else:\n                    err_idx = n.where(data[1][0,:] != preds[:,0].T)[0]\n                    print err_idx\n                err_idx = r.sample(err_idx, min(len(err_idx), NUM_IMGS))\n                data[0], data[1], preds = data[0][:,err_idx], data[1][:,err_idx], preds[err_idx,:]\n                \n            \n            import matplotlib.gridspec as gridspec\n            import matplotlib.colors as colors\n            cconv = colors.ColorConverter()\n            gs = gridspec.GridSpec(NUM_ROWS*2, NUM_COLS,\n                                   width_ratios=[1]*NUM_COLS, height_ratios=[2,1]*NUM_ROWS )\n            #print data[1]\n            for row in xrange(NUM_ROWS):\n                for col in xrange(NUM_COLS):\n                    img_idx = row * NUM_COLS + col\n                    if data[0].shape[0] <= img_idx:\n                        break\n                    pl.subplot(gs[(row * 2) * NUM_COLS + col])\n                    #pl.subplot(NUM_ROWS*2, NUM_COLS, row * 2 * NUM_COLS + col + 1)\n                    pl.xticks([])\n                    pl.yticks([])\n                    img = data[0][img_idx,:,:,:]\n                    pl.imshow(img, interpolation='lanczos')\n                    show_title = data[1].shape[0] == 1\n                    true_label = [int(data[1][0,img_idx])] if show_title else n.where(data[1][:,img_idx]==1)[0]\n                    #print true_label\n                    #print preds[img_idx,:].shape\n                    #print preds[img_idx,:].max()\n                    true_label_names = [label_names[i] for i in true_label]\n                    img_labels = sorted(zip(preds[img_idx,:], label_names), key=lambda x: x[0])[-NUM_TOP_CLASSES:]\n                    #print img_labels\n                    axes = pl.subplot(gs[(row * 2 + 1) * NUM_COLS + col])\n                    height = 0.5\n                    ylocs = n.array(range(NUM_TOP_CLASSES))*height\n                    pl.barh(ylocs, [l[0] for l in img_labels], height=height, \\\n                            color=['#ffaaaa' if l[1] in true_label_names else '#aaaaff' for l in img_labels])\n                    #pl.title(\", \".join(true_labels))\n                    if show_title:\n                        pl.title(\", \".join(true_label_names), fontsize=15, fontweight='bold')\n                    else:\n                        print true_label_names\n                    pl.yticks(ylocs + height/2, [l[1] for l in img_labels], x=1, backgroundcolor=cconv.to_rgba('0.65', alpha=0.5), weight='bold')\n                    for line in enumerate(axes.get_yticklines()): \n                        line[1].set_visible(False) \n                    #pl.xticks([width], [''])\n                    #pl.yticks([])\n                    pl.xticks([])\n                    pl.ylim(0, ylocs[-1] + height)\n                    pl.xlim(0, 1)\n\n    def start(self):\n        self.op.print_values()\n#        print self.show_cost\n        if self.show_cost:\n            self.plot_cost()\n        if self.show_filters:\n            self.plot_filters()\n        if self.show_preds:\n            self.plot_predictions()\n\n        if pl:\n            pl.show()\n        sys.exit(0)\n            \n    @classmethod\n    def get_options_parser(cls):\n        op = ConvNet.get_options_parser()\n        for option in list(op.options):\n            if option not in ('gpu', 'load_file', 'inner_size', 'train_batch_range', 'test_batch_range', 'multiview_test', 'data_path', 'pca_noise', 'scalar_mean'):\n                op.delete_option(option)\n        op.add_option(\"show-cost\", \"show_cost\", StringOptionParser, \"Show specified objective function\", default=\"\")\n        op.add_option(\"show-filters\", \"show_filters\", StringOptionParser, \"Show learned filters in specified layer\", default=\"\")\n        op.add_option(\"norm-filters\", \"norm_filters\", BooleanOptionParser, \"Individually normalize filters shown with --show-filters\", default=0)\n        op.add_option(\"input-idx\", \"input_idx\", IntegerOptionParser, \"Input index for layer given to --show-filters\", default=0)\n        op.add_option(\"cost-idx\", \"cost_idx\", IntegerOptionParser, \"Cost function return value index for --show-cost\", default=0)\n        op.add_option(\"no-rgb\", \"no_rgb\", BooleanOptionParser, \"Don't combine filter channels into RGB in layer given to --show-filters\", default=False)\n        op.add_option(\"yuv-to-rgb\", \"yuv_to_rgb\", BooleanOptionParser, \"Convert RGB filters to YUV in layer given to --show-filters\", default=False)\n        op.add_option(\"channels\", \"channels\", IntegerOptionParser, \"Number of channels in layer given to --show-filters (fully-connected layers only)\", default=0)\n        op.add_option(\"show-preds\", \"show_preds\", StringOptionParser, \"Show predictions made by given softmax on test set\", default=\"\")\n        op.add_option(\"save-preds\", \"save_preds\", StringOptionParser, \"Save predictions to given path instead of showing them\", default=\"\")\n        op.add_option(\"only-errors\", \"only_errors\", BooleanOptionParser, \"Show only mistaken predictions (to be used with --show-preds)\", default=False, requires=['show_preds'])\n        op.add_option(\"local-plane\", \"local_plane\", IntegerOptionParser, \"Local plane to show\", default=0)\n        op.add_option(\"smooth-test-errors\", \"smooth_test_errors\", BooleanOptionParser, \"Use running average for test error plot?\", default=1)\n\n        op.options['load_file'].default = None\n        return op\n    \nif __name__ == \"__main__\":\n    #nr.seed(6)\n    try:\n        op = ShowConvNet.get_options_parser()\n        op, load_dic = IGPUModel.parse_options(op)\n        model = ShowConvNet(op, load_dic)\n        model.start()\n    except (UnpickleError, ShowNetError, opt.GetoptError), e:\n        print \"----------------\"\n        print \"Error:\"\n        print e \n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/util/Makefile",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#    http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nLINK_LIBS := -L$(ATLAS_LIB_PATH) -latlas -lcblas\nINCLUDES := -I./include\nCOMMONFLAGS :=\nCC_ARGS := \nCC=g++\n\nifndef debug\n\tCC_ARGS += -O3\nendif\n\nOUT_DIR=./bin/$(OUT_SUFFIX)\nOUT_FILE=libutil.so\n\nifeq ($(numpy), 1)\n\tPYTHON_VERSION=$(shell python -V 2>&1 | cut -d ' ' -f 2 | cut -d '.' -f 1,2)\n\tLINK_LIBS += -lpython$(PYTHON_VERSION)\n\n\tINCLUDES += -I$(PYTHON_INCLUDE_PATH) -I$(NUMPY_INCLUDE_PATH)\n\tCOMMONFLAGS += -DNUMPY_INTERFACE\n\tOUT_FILE=libutilpy.so\nendif\n\nOBJECTS = matrix.cpp \n\nall: dir classes $(OUT_FILE)\n\ndir:\n\tmkdir -p $(OUT_DIR)/src\n\nSOURCES = $(shell echo src/*.cpp)\nCLASSES = $(SOURCES:.cpp=.o)\n\nclasses: $(CLASSES)\n\n%.o: %.cpp\n\t$(CC) $(CC_ARGS) -c -fPIC $(BUILD_ARGS) $(COMMONFLAGS) $(INCLUDES) $< -o $(OUT_DIR)/$*.o\n\n$(OUT_FILE): classes\n\tcd $(OUT_DIR) && $(CC) $(CC_ARGS) $(BUILD_ARGS) $(COMMONFLAGS) -shared -Wl,-no-undefined -o $(OUT_FILE) $(CLASSES) $(LINK_LIBS)\n\tln -sf $(OUT_DIR)/$(OUT_FILE) .\n\nclean:\n\trm -rf $(OUT_DIR)/*\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/util/include/matrix.h",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef MATRIX_H_\n#define MATRIX_H_\n\n#include \"matrix_funcs.h\"\n#ifdef NUMPY_INTERFACE\n#include <Python.h>\n#include <arrayobject.h>\n#endif\n#include <limits>\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n#include <math.h>\n#include <vector>\n\nextern \"C\" {\n// #include <cblas.h>\n#include \"caffe2/utils/cblas.h\"\n}\n\n#ifdef DOUBLE_PRECISION\n#define CBLAS_GEMM cblas_dgemm\n#define CBLAS_SCAL cblas_dscal\n#define CBLAS_AXPY cblas_daxpy\n#else\n#define CBLAS_GEMM cblas_sgemm\n#define CBLAS_SCAL cblas_sscal\n#define CBLAS_AXPY cblas_saxpy\n#endif /* DOUBLE_PRECISION */\n\n#define MTYPE_MAX numeric_limits<MTYPE>::max()\n\ntypedef long long int int64;\n\nclass Matrix {\nprivate:\n    MTYPE* _data;\n    bool _ownsData;\n    int64 _numRows, _numCols;\n    int64 _numElements;\n    CBLAS_TRANSPOSE _trans;\n\n    void _init(MTYPE* data, int64 numRows, int64 numCols, bool transpose, bool ownsData);\n    void _tileTo2(Matrix& target) const;\n    void _copyAllTo(Matrix& target) const;\n    MTYPE _sum_column(int64 col) const;\n    MTYPE _sum_row(int64 row) const;\n    MTYPE _aggregate(MTYPE(*agg_func)(MTYPE, MTYPE), MTYPE initialValue) const;\n    void _aggregate(int64 axis, Matrix& target, MTYPE(*agg_func)(MTYPE, MTYPE), MTYPE initialValue) const;\n    MTYPE _aggregateRow(int64 row, MTYPE(*agg_func)(MTYPE, MTYPE), MTYPE initialValue) const;\n    MTYPE _aggregateCol(int64 row, MTYPE(*agg_func)(MTYPE, MTYPE), MTYPE initialValue) const;\n    void _updateDims(int64 numRows, int64 numCols);\n    void _applyLoop(MTYPE(*func)(MTYPE));\n    void _applyLoop(MTYPE (*func)(MTYPE), Matrix& target);\n    void _applyLoop2(const Matrix& a, MTYPE(*func)(MTYPE, MTYPE), Matrix& target) const;\n    void _applyLoop2(const Matrix& a, MTYPE (*func)(MTYPE,MTYPE, MTYPE), MTYPE scalar, Matrix& target) const;\n    void _applyLoop2(const Matrix& a, MTYPE (*func)(MTYPE,MTYPE, MTYPE, MTYPE), MTYPE scalar1, MTYPE scalar2, Matrix& target) const;\n    void _applyLoopScalar(const MTYPE scalar, MTYPE(*func)(MTYPE, MTYPE), Matrix& target) const;\n    void _checkBounds(int64 startRow, int64 endRow, int64 startCol, int64 endCol) const;\n    void _divideByVector(const Matrix& vec, Matrix& target);\n    inline int64 _getNumColsBackEnd() const {\n        return _trans == CblasNoTrans ? _numCols : _numRows;\n    }\npublic:\n    enum FUNCTION {\n        TANH, RECIPROCAL, SQUARE, ABS, EXP, LOG, ZERO, ONE, LOGISTIC1, LOGISTIC2, SIGN\n    };\n    Matrix();\n    Matrix(int64 numRows, int64 numCols);\n    Matrix(int64 numRows, int64 numCols, bool transpose);\n#ifdef NUMPY_INTERFACE\n    Matrix(const PyArrayObject *src);\n#endif\n    Matrix(const Matrix &like);\n    Matrix(MTYPE* data, int64 numRows, int64 numCols);\n    Matrix(MTYPE* data, int64 numRows, int64 numCols, bool transpose);\n    ~Matrix();\n\n    inline MTYPE& getCell(int64 i, int64 j) const {\n        assert(i >= 0 && i < _numRows);\n        assert(j >= 0 && j < _numCols);\n        if (_trans == CblasTrans) {\n            return _data[j * _numRows + i];\n        }\n        return _data[i * _numCols + j];\n    }\n\n    MTYPE& operator()(int64 i, int64 j) const {\n        return getCell(i, j);\n    }\n\n    inline MTYPE* getData() const {\n        return _data;\n    }\n\n    inline bool isView() const {\n        return !_ownsData;\n    }\n\n    inline int64 getNumRows() const {\n        return _numRows;\n    }\n\n    inline int64 getNumCols() const {\n        return _numCols;\n    }\n\n    inline int64 getNumDataBytes() const {\n        return _numElements * sizeof(MTYPE);\n    }\n\n    inline int64 getNumElements() const {\n        return _numElements;\n    }\n\n    inline int64 getLeadingDim() const {\n        return _trans == CblasTrans ? _numRows : _numCols;\n    }\n\n    inline int64 getFollowingDim() const {\n        return _trans == CblasTrans ? _numCols : _numRows;\n    }\n\n    inline CBLAS_TRANSPOSE getBLASTrans() const {\n        return _trans;\n    }\n\n    inline bool isSameDims(const Matrix& a) const {\n        return a.getNumRows() == getNumRows() && a.getNumCols() == getNumCols();\n    }\n\n    inline bool isTrans() const {\n        return _trans == CblasTrans;\n    }\n\n    /*\n     * Only use if you know what you're doing!\n     * Does not update any dimensions. Just flips the _trans flag.\n     *\n     * Use transpose() if you want to get the transpose of this matrix.\n     */\n    inline void setTrans(bool trans) {\n        assert(isTrans() == trans || !isView());\n        _trans = trans ? CblasTrans : CblasNoTrans;\n    }\n\n    void apply(FUNCTION f);\n    void apply(Matrix::FUNCTION f, Matrix& target);\n    void subtractFromScalar(MTYPE scalar);\n    void subtractFromScalar(MTYPE scalar, Matrix &target) const;\n    void biggerThanScalar(MTYPE scalar);\n    void smallerThanScalar(MTYPE scalar);\n    void equalsScalar(MTYPE scalar);\n    void biggerThanScalar(MTYPE scalar, Matrix& target) const;\n    void smallerThanScalar(MTYPE scalar, Matrix& target) const;\n    void equalsScalar(MTYPE scalar, Matrix& target) const;\n    void biggerThan(Matrix& a);\n    void biggerThan(Matrix& a, Matrix& target) const;\n    void smallerThan(Matrix& a);\n    void smallerThan(Matrix& a, Matrix& target) const;\n    void minWith(Matrix &a);\n    void minWith(Matrix &a, Matrix &target) const;\n    void maxWith(Matrix &a);\n    void maxWith(Matrix &a, Matrix &target) const;\n    void equals(Matrix& a);\n    void equals(Matrix& a, Matrix& target) const;\n    void notEquals(Matrix& a) ;\n    void notEquals(Matrix& a, Matrix& target) const;\n    void add(const Matrix &m);\n    void add(const Matrix &m, MTYPE scale);\n    void add(const Matrix &m, MTYPE scaleThis, MTYPE scaleM);\n    void add(const Matrix &m, Matrix& target);\n    void add(const Matrix &m, MTYPE scaleM, Matrix &target);\n    void add(const Matrix &m, MTYPE scaleThis, MTYPE scaleM, Matrix &target);\n    void subtract(const Matrix &m);\n    void subtract(const Matrix &m, Matrix& target);\n    void subtract(const Matrix &m, MTYPE scale);\n    void subtract(const Matrix &m, MTYPE scale, Matrix& target);\n    void addVector(const Matrix& vec, MTYPE scale);\n    void addVector(const Matrix& vec, MTYPE scale, Matrix& target);\n    void addVector(const Matrix& vec);\n    void addVector(const Matrix& vec, Matrix& target);\n    void addScalar(MTYPE scalar);\n    void addScalar(MTYPE scalar, Matrix& target) const;\n    void maxWithScalar(MTYPE scalar);\n    void maxWithScalar(MTYPE scalar, Matrix &target) const;\n    void minWithScalar(MTYPE scalar);\n    void minWithScalar(MTYPE scalar, Matrix &target) const;\n    void eltWiseMultByVector(const Matrix& vec);\n    void eltWiseMultByVector(const Matrix& vec, Matrix& target);\n    void eltWiseDivideByVector(const Matrix& vec);\n    void eltWiseDivideByVector(const Matrix& vec, Matrix& target);\n    void resize(int64 newNumRows, int64 newNumCols);\n    void resize(const Matrix& like);\n    Matrix& slice(int64 startRow, int64 endRow, int64 startCol, int64 endCol) const;\n    void slice(int64 startRow, int64 endRow, int64 startCol, int64 endCol, Matrix &target) const;\n    Matrix& sliceRows(int64 startRow, int64 endRow) const;\n    void sliceRows(int64 startRow, int64 endRow, Matrix& target) const;\n    Matrix& sliceCols(int64 startCol, int64 endCol) const;\n    void sliceCols(int64 startCol, int64 endCol, Matrix& target) const;\n    void rightMult(const Matrix &b, MTYPE scale);\n    void rightMult(const Matrix &b, Matrix &target) const;\n    void rightMult(const Matrix &b);\n    void rightMult(const Matrix &b, MTYPE scaleAB, Matrix &target) const;\n    void addProduct(const Matrix &a, const Matrix &b, MTYPE scaleAB, MTYPE scaleThis);\n    void addProduct(const Matrix& a, const Matrix& b);\n    void eltWiseMult(const Matrix& a);\n    void eltWiseMult(const Matrix& a, Matrix& target) const;\n    void eltWiseDivide(const Matrix& a);\n    void eltWiseDivide(const Matrix& a, Matrix &target) const;\n    Matrix& transpose() const;\n    Matrix& transpose(bool hard) const;\n    Matrix& tile(int64 timesY, int64 timesX) const;\n    void tile(int64 timesY, int64 timesX, Matrix& target) const;\n    void copy(Matrix &dest, int64 srcStartRow, int64 srcEndRow, int64 srcStartCol, int64 srcEndCol, int64 destStartRow, int64 destStartCol) const;\n    Matrix& copy() const;\n    void copy(Matrix& target) const;\n    Matrix& sum(int64 axis) const;\n    void sum(int64 axis, Matrix &target) const;\n    MTYPE sum() const;\n    MTYPE max() const;\n    Matrix& max(int64 axis) const;\n    void max(int64 axis, Matrix& target) const;\n    MTYPE min() const;\n    Matrix& min(int64 axis) const;\n    void min(int64 axis, Matrix& target) const;\n    MTYPE norm() const;\n    MTYPE norm2() const;\n    void scale(MTYPE scale);\n    void scale(MTYPE alpha, Matrix& target);\n    void reshape(int64 numRows, int64 numCols);\n    Matrix& reshaped(int64 numRows, int64 numCols);\n    void printShape(const char* name) const;\n    bool hasNan() const;\n    bool hasInf() const;\n\n    void randomizeNormal(MTYPE mean, MTYPE stdev);\n    void randomizeUniform();\n    void randomizeNormal();\n    void print() const;\n    void print(int64 startRow,int64 rows, int64 startCol,int64 cols) const;\n    void print(int64 rows, int64 cols) const;\n};\n\ntypedef std::vector<Matrix*> MatrixV;\n\n#endif /* MATRIX_H_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/util/include/matrix_funcs.h",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef MATRIX_FUNCS_H_\n#define MATRIX_FUNCS_H_\n\n#include <stdlib.h>\n#include <math.h>\n#include <algorithm>\n\n#ifdef DOUBLE_PRECISION\n#define MTYPE double\n#else\n#define MTYPE float\n#endif\n\n#define MYRAND ((double)rand() / ((double)RAND_MAX + 1))\n\ninline MTYPE _zero(MTYPE x) {\n    return 0;\n}\n\ninline MTYPE _one(MTYPE x) {\n    return 1;\n}\n\ninline MTYPE _abs(MTYPE x) {\n    return x > 0 ? x : -x;\n}\n\ninline MTYPE _square(MTYPE x) {\n    return x * x;\n}\n\ninline MTYPE _sigma1(MTYPE x) {\n    return (tanh(x / 2) + 1) / 2;\n}\n\ninline MTYPE _sigma2(MTYPE x) {\n    return 1 / (1 + exp(-x));\n}\n\ninline MTYPE _recip(MTYPE x) {\n    return 1 / x;\n}\n\ninline MTYPE _exp(MTYPE x) {\n    return exp(x);\n}\n\ninline MTYPE _log(MTYPE x) {\n    return log(x);\n}\n\ninline MTYPE _tanh(MTYPE x) {\n    return tanh(x);\n}\n\ninline MTYPE _sign(MTYPE x) {\n    return x > 0 ? 1 : -1;\n}\n\ninline MTYPE _rand(MTYPE x) {\n    return MYRAND;\n}\n\ninline MTYPE _divide(MTYPE x, MTYPE y) {\n    return x / y;\n}\n\ninline MTYPE _mult(MTYPE x, MTYPE y) {\n    return x * y;\n}\n\ninline MTYPE _add(MTYPE x, MTYPE y) {\n    return x + y;\n}\n\ninline MTYPE _addSquare(MTYPE x, MTYPE y) {\n    return x*x + y;\n}\n\ninline MTYPE _addWithScale(MTYPE x, MTYPE y, MTYPE scale) {\n    return x + scale*y;\n}\n\ninline MTYPE _addWithScale2(MTYPE x, MTYPE y, MTYPE scaleThis, MTYPE scaleM) {\n    return scaleThis * x + scaleM * y;\n}\n\ninline MTYPE _max(MTYPE x, MTYPE y) {\n    return std::max(x, y);\n}\n\ninline MTYPE _min(MTYPE x, MTYPE y) {\n    return std::min(x, y);\n}\n\ninline MTYPE _bigger(MTYPE x, MTYPE y) {\n    return x > y;\n}\n\ninline MTYPE _smaller(MTYPE x, MTYPE y) {\n    return x < y;\n}\n\ninline MTYPE _equal(MTYPE x, MTYPE y) {\n    return x == y;\n}\n\ninline MTYPE _notEqual(MTYPE x, MTYPE y) {\n    return x != y;\n}\n\n#endif /* MATRIX_FUNCS_H_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/util/include/queue.h",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef QUEUE_H_\n#define QUEUE_H_\n#include <pthread.h>\n#include <stdlib.h>\n\n/*\n * A thread-safe circular queue that automatically grows but never shrinks.\n */\ntemplate <class T>\nclass Queue {\nprivate:\n    T *_elements;\n    int _numElements;\n    int _head, _tail;\n    int _maxSize;\n    pthread_mutex_t *_queueMutex;\n    pthread_cond_t *_queueCV;\n\n    void _init(int initialSize) {\n        _numElements = 0;\n        _head = 0;\n        _tail = 0;\n        _maxSize = initialSize;\n        _elements = new T[initialSize];\n        _queueCV = (pthread_cond_t*)(malloc(sizeof (pthread_cond_t)));\n        _queueMutex = (pthread_mutex_t*)(malloc(sizeof (pthread_mutex_t)));\n        pthread_mutex_init(_queueMutex, NULL);\n        pthread_cond_init(_queueCV, NULL);\n    }\n\n    void expand() {\n        T *newStorage = new T[_maxSize * 2];\n        memcpy(newStorage, _elements + _head, (_maxSize - _head) * sizeof(T));\n        memcpy(newStorage + _maxSize - _head, _elements, _tail * sizeof(T));\n        delete[] _elements;\n        _elements = newStorage;\n        _head = 0;\n        _tail = _numElements;\n        _maxSize *= 2;\n    }\npublic:\n    Queue(int initialSize) {\n        _init(initialSize);\n    }\n\n    Queue()  {\n        _init(1);\n    }\n\n    ~Queue() {\n        pthread_mutex_destroy(_queueMutex);\n        pthread_cond_destroy(_queueCV);\n        delete[] _elements;\n        free(_queueMutex);\n        free(_queueCV);\n    }\n\n    void enqueue(T el) {\n        pthread_mutex_lock(_queueMutex);\n        if (_numElements == _maxSize) {\n            expand();\n        }\n        _elements[_tail] = el;\n        _tail = (_tail + 1) % _maxSize;\n        _numElements++;\n\n        pthread_cond_signal(_queueCV);\n        pthread_mutex_unlock(_queueMutex);\n    }\n\n    /*\n     * Blocks until not empty.\n     */\n    T dequeue() {\n        pthread_mutex_lock(_queueMutex);\n        // Apparently, pthread_cond_signal may actually unblock\n        // multiple threads, so a while loop is needed here.\n        while (_numElements == 0) {\n            pthread_cond_wait(_queueCV, _queueMutex);\n        }\n        T el = _elements[_head];\n        _head = (_head + 1) % _maxSize;\n        _numElements--;\n        pthread_mutex_unlock(_queueMutex);\n        return el;\n    }\n\n    /*\n     * Obviously this number can change by the time you actually look at it.\n     */\n    inline int getNumElements() const {\n        return _numElements;\n    }\n};\n\n#endif /* QUEUE_H_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/util/include/sync.h",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef SYNC_H_\n#define SYNC_H_\n\n#include <pthread.h>\n\nclass Lock {\nprivate: \n    pthread_mutex_t _mutex;\npublic:\n    Lock() {\n        pthread_mutex_init(&_mutex, NULL);\n    }\n    ~Lock() {\n        pthread_mutex_destroy(&_mutex);\n    }\n    \n    void acquire() {\n        pthread_mutex_lock(&_mutex);\n    }\n    \n    void release() {\n        pthread_mutex_unlock(&_mutex);\n    }\n};\n\nclass ThreadSynchronizer {\nprivate:\n    int _numThreads;\n    int _numSynced;\n    pthread_mutex_t *_syncMutex;\n    pthread_cond_t *_syncThresholdCV;\npublic:\n    ThreadSynchronizer(int numThreads) {\n        _numThreads = numThreads;\n        _numSynced = 0;\n        _syncMutex = (pthread_mutex_t*) malloc(sizeof(pthread_mutex_t));\n        _syncThresholdCV = (pthread_cond_t*) malloc(sizeof(pthread_cond_t));\n        pthread_mutex_init(_syncMutex, NULL);\n        pthread_cond_init(_syncThresholdCV, NULL);\n    }\n\n    ~ThreadSynchronizer() {\n        pthread_mutex_destroy(_syncMutex);\n        pthread_cond_destroy(_syncThresholdCV);\n        free(_syncMutex);\n        free(_syncThresholdCV);\n    }\n\n    void sync() {\n        pthread_mutex_lock(_syncMutex);\n        _numSynced++;\n\n        if (_numSynced == _numThreads) {\n            _numSynced = 0;\n            pthread_cond_broadcast(_syncThresholdCV);\n        } else {\n            pthread_cond_wait(_syncThresholdCV, _syncMutex);\n        }\n        pthread_mutex_unlock(_syncMutex);\n    }\n};\n\n#endif /* SYNC_H_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/util/include/thread.h",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef THREAD_H_\n#define THREAD_H_\n#include <pthread.h>\n#include <stdio.h>\n#include <errno.h>\n#include <assert.h>\n#include <vector>\n\n#define NUM_CPUS_MAX    48\n\n/*\n * Abstract joinable thread class.\n * The only thing the implementer has to fill in is the run method.\n */\nclass Thread {\nprivate:\n    cpu_set_t *_cpu_set;\n    pthread_attr_t _pthread_attr;\n    pthread_t _threadID;\n    bool _joinable, _startable;\n\n    static void* start_pthread_func(void *obj) {\n        void* retval = reinterpret_cast<Thread*>(obj)->run();\n        pthread_exit(retval);\n        return retval;\n    }\nprotected:\n    virtual void* run() = 0;\npublic:\n    Thread(bool joinable) : _cpu_set(NULL), _joinable(joinable), _startable(true) {\n        pthread_attr_init(&_pthread_attr);\n    }\n\n    Thread(bool joinable, std::vector<int>& cpus) : _cpu_set(NULL), _joinable(joinable), _startable(true) {\n        pthread_attr_init(&_pthread_attr);\n        setAffinity(cpus);\n    }\n\n    virtual ~Thread() {\n        if (_cpu_set != NULL) {\n            CPU_FREE(_cpu_set);\n        }\n        pthread_attr_destroy(&_pthread_attr);\n    }\n\n    void setAffinity(std::vector<int>& cpus) {\n        assert(_startable);\n        _cpu_set = CPU_ALLOC(NUM_CPUS_MAX);\n        size_t size = CPU_ALLOC_SIZE(NUM_CPUS_MAX);\n        if (cpus.size() > 0 && cpus[0] >= 0) {\n            CPU_ZERO_S(size, _cpu_set);\n            for (int i = 0; i < cpus.size(); i++) {\n                assert(cpus[i] < NUM_CPUS_MAX);\n                CPU_SET_S(cpus[i], size, _cpu_set);\n//                printf(\"set cpu %d\\n\", cpus[i]);\n            }\n            pthread_attr_setaffinity_np(&_pthread_attr, size, _cpu_set);\n        }\n    }\n\n    pthread_t start() {\n        assert(_startable);\n        _startable = false;\n        pthread_attr_setdetachstate(&_pthread_attr, _joinable ? PTHREAD_CREATE_JOINABLE : PTHREAD_CREATE_DETACHED);\n        int n;\n        if ((n = pthread_create(&_threadID, &_pthread_attr, &Thread::start_pthread_func, (void*)this))) {\n            errno = n;\n            perror(\"pthread_create error\");\n        }\n        return _threadID;\n    }\n\n    void join(void **status) {\n        assert(_joinable);\n        int n;\n        if((n = pthread_join(_threadID, status))) {\n            errno = n;\n            perror(\"pthread_join error\");\n        }\n    }\n\n    void join() {\n        join(NULL);\n    }\n\n    pthread_t getThreadID() const {\n        return _threadID;\n    }\n\n    bool isStartable() const {\n        return _startable;\n    }\n};\n\n#endif /* THREAD_H_ */\n"
  },
  {
    "path": "caffe2/contrib/cuda-convnet2/util/src/matrix.cpp",
    "content": "/*\n * Copyright 2014 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"../include/matrix.h\"\n#include \"../include/matrix_funcs.h\"\n\n#if defined(_WIN64) || defined(_WIN32)\ndouble sqrt(int _X) {return sqrt((double) _X);}\ndouble log(int _X) {return log((double) _X);}\n#endif\n\nusing namespace std;\n\nvoid Matrix::_init(MTYPE* data, int64 numRows, int64 numCols, bool transpose, bool ownsData) {\n    _updateDims(numRows, numCols);\n    _ownsData = ownsData;\n    _trans = transpose ? CblasTrans : CblasNoTrans;\n    _data = data;\n}\n\nMatrix::Matrix() {\n    _init(NULL, 0, 0, false, true);\n}\n\nMatrix::Matrix(int64 numRows, int64 numCols) {\n    _init(NULL, numRows, numCols, false, true);\n    this->_data = numRows * numCols > 0 ? new MTYPE[this->_numElements] : NULL;\n}\n\nMatrix::Matrix(int64 numRows, int64 numCols, bool transpose) {\n    _init(NULL, numRows, numCols, transpose, true);\n    this->_data = numRows * numCols > 0 ? new MTYPE[this->_numElements] : NULL;\n}\n\nMatrix::Matrix(const Matrix &like) {\n    _init(NULL, like.getNumRows(), like.getNumCols(), false, true);\n    this->_data = new MTYPE[this->_numElements];\n}\n\n/* construct a matrix with another matrix's data. the resultant\n * matrix does NOT own its data */\nMatrix::Matrix(MTYPE* data, int64 numRows, int64 numCols) {\n    _init(data, numRows, numCols, false, false);\n}\n\n/* construct a matrix with another matrix's data (and optionally transpose it). the resultant\n * matrix does NOT own its data -- it is a VIEW */\nMatrix::Matrix(MTYPE* data, int64 numRows, int64 numCols, bool transpose) {\n    _init(data, numRows, numCols, transpose, false);\n}\n\n#ifdef NUMPY_INTERFACE\nMatrix::Matrix(const PyArrayObject *src) {\n    this->_data = NULL;\n    this->_trans = CblasNoTrans;\n    if (src != NULL) {\n        this->_updateDims(PyArray_DIM(src,0), PyArray_DIM(src,1));\n        if (src->flags & NPY_CONTIGUOUS || src->flags & NPY_FORTRAN) {\n            this->_data = (MTYPE*) src->data;\n            this->_ownsData = false;\n            this->_trans = src->flags & NPY_CONTIGUOUS ? CblasNoTrans : CblasTrans;\n        } else {\n            this->_data = new MTYPE[PyArray_DIM(src,0) * PyArray_DIM(src,1)];\n            for (int64 i = 0; i < PyArray_DIM(src,0); i++) {\n                for (int64 j = 0; j < PyArray_DIM(src,1); j++) {\n                    (*this)(i,j) = *reinterpret_cast<MTYPE*>(PyArray_GETPTR2(src,i,j));\n                }\n            }\n            this->_ownsData = true;\n        }\n    }\n}\n#endif\nMatrix::~Matrix() {\n    if(this->_data != NULL && this->_ownsData) {\n        delete[] this->_data;\n    }\n}\n\nvoid Matrix::_updateDims(int64 numRows, int64 numCols) {\n    this->_numRows = numRows;\n    this->_numCols = numCols;\n    this->_numElements = numRows * numCols;\n}\n\nvoid Matrix::_checkBounds(int64 startRow, int64 endRow, int64 startCol, int64 endCol) const {\n    assert(startRow >= 0 && startRow <= _numRows);\n    assert(endRow >= 0 && endRow <= _numRows);\n    assert(startCol >= 0 && startCol <= _numCols);\n    assert(endCol >= 0 && endCol <= _numCols);\n}\n\n/* will return a view if possible */\nMatrix& Matrix::slice(int64 startRow, int64 endRow, int64 startCol, int64 endCol) const {\n    endRow = endRow < 0 ? this->_numRows : endRow;\n    endCol = endCol < 0 ? this->_numCols : endCol;\n    _checkBounds(startRow, endRow, startCol, endCol);\n    if (!isTrans() && ((startCol == 0 && endCol == this->_numCols) || (startRow == endRow - 1))) {\n        return *new Matrix(this->_data + startRow * this->_numCols + startCol, endRow - startRow, endCol - startCol);\n    } else if (isTrans() && ((startRow == 0 && endRow == this->_numRows) || (startCol == endCol - 1))) {\n        return *new Matrix(this->_data + startCol * this->_numRows + startRow, endRow - startRow, endCol - startCol, true);\n    }\n    Matrix& newSlice = *new Matrix(endRow - startRow, endCol - startCol);\n    this->copy(newSlice, startRow, endRow, startCol, endCol, 0, 0);\n    return newSlice;\n}\n\n/* this will NEVER return a view, unlike Matrix_slice */\nvoid Matrix::slice(int64 startRow, int64 endRow, int64 startCol, int64 endCol, Matrix& target) const {\n    endRow = endRow < 0 ? this->_numRows : endRow;\n    endCol = endCol < 0 ? this->_numCols : endCol;\n    _checkBounds(startRow, endRow, startCol, endCol);\n    target.resize(endRow - startRow, endCol - startCol);\n    this->copy(target, startRow, endRow, startCol, endCol, 0, 0);\n}\n\nMatrix& Matrix::sliceRows(int64 startRow, int64 endRow) const {\n    return slice(startRow, endRow, 0, -1);\n}\n\nvoid Matrix::sliceRows(int64 startRow, int64 endRow, Matrix& target) const {\n    slice(startRow, endRow, 0, -1, target);\n}\n\nMatrix& Matrix::sliceCols(int64 startCol, int64 endCol) const {\n    return slice(0, -1, startCol, endCol);\n}\n\nvoid Matrix::sliceCols(int64 startCol, int64 endCol, Matrix& target) const {\n    slice(0, -1, startCol, endCol, target);\n}\n\nvoid Matrix::subtractFromScalar(MTYPE scalar) {\n    subtractFromScalar(scalar, *this);\n}\n\nvoid Matrix::subtractFromScalar(MTYPE scalar, Matrix& target) const {\n    if(&target != this) {\n        copy(target);\n    }\n    target.scale(-1);\n    target.addScalar(scalar);\n}\n\nvoid Matrix::biggerThanScalar(MTYPE scalar) {\n    biggerThanScalar(scalar, *this);\n}\n\nvoid Matrix::smallerThanScalar(MTYPE scalar) {\n    smallerThanScalar(scalar, *this);\n}\n\nvoid Matrix::equalsScalar(MTYPE scalar) {\n    equalsScalar(scalar, *this);\n}\n\nvoid Matrix::biggerThanScalar(MTYPE scalar, Matrix& target) const {\n    target.resize(*this);\n    _applyLoopScalar(scalar, &_bigger, target);\n}\n\nvoid Matrix::smallerThanScalar(MTYPE scalar, Matrix& target) const {\n    target.resize(*this);\n    _applyLoopScalar(scalar, &_smaller, target);\n}\n\nvoid Matrix::equalsScalar(MTYPE scalar, Matrix& target) const {\n    target.resize(*this);\n    _applyLoopScalar(scalar, &_equal, target);\n}\n\nvoid Matrix::add(const Matrix &m) {\n    add(m, 1, *this);\n}\n\nvoid Matrix::add(const Matrix &m, Matrix& target) {\n    add(m, 1, target);\n}\n\nvoid Matrix::add(const Matrix &m, MTYPE scale) {\n    add(m, scale, *this);\n}\n\nvoid Matrix::subtract(const Matrix &m) {\n    add(m, -1, *this);\n}\n\nvoid Matrix::subtract(const Matrix &m, Matrix& target) {\n    add(m, -1, target);\n}\n\nvoid Matrix::subtract(const Matrix &m, MTYPE scale) {\n    add(m, -scale, *this);\n}\n\nvoid Matrix::subtract(const Matrix &m, MTYPE scale, Matrix& target) {\n    add(m, -scale, target);\n}\n\nvoid Matrix::add(const Matrix &m, MTYPE scaleM, Matrix &target) {\n    add(m, 1, scaleM, target);\n}\n\nvoid Matrix::add(const Matrix &m, MTYPE scaleThis, MTYPE scaleM) {\n    add(m, scaleThis, scaleM, *this);\n}\n\nvoid Matrix::add(const Matrix &m, MTYPE scaleThis, MTYPE scaleM, Matrix &target) {\n    assert(this->isSameDims(m));\n    if (isTrans() != m.isTrans() || isTrans() != target.isTrans() || scaleThis != 1) {\n        if (&target != this) {\n            target.resize(*this);\n        }\n        if(scaleThis == 1 && scaleM == 1) {\n            this->_applyLoop2(m, &_add, target);\n        } else if (scaleThis == 1) {\n            this->_applyLoop2(m, &_addWithScale, scaleM, target);\n        } else {\n            this->_applyLoop2(m, &_addWithScale2, scaleThis, scaleM, target);\n        }\n    } else {\n        if (&target != this) {\n            copy(target);\n        }\n        CBLAS_AXPY(getNumElements(), scaleM, m._data, 1, target._data, 1);\n    }\n}\n\nvoid Matrix::addScalar(MTYPE scalar) {\n    addScalar(scalar, *this);\n}\n\nvoid Matrix::addScalar(MTYPE scalar, Matrix& target) const {\n    target.resize(*this);\n    _applyLoopScalar(scalar, &_add, target);\n}\n\nvoid Matrix::maxWithScalar(MTYPE scalar) {\n    maxWithScalar(scalar, *this);\n}\n\nvoid Matrix::maxWithScalar(MTYPE scalar, Matrix& target) const {\n    target.resize(*this);\n    _applyLoopScalar(scalar, &_max, target);\n}\n\nvoid Matrix::minWithScalar(MTYPE scalar) {\n    minWithScalar(scalar, *this);\n}\n\nvoid Matrix::minWithScalar(MTYPE scalar, Matrix& target) const {\n    target.resize(*this);\n    _applyLoopScalar(scalar, &_min, target);\n}\n\nvoid Matrix::biggerThan(Matrix& a) {\n    biggerThan(a, *this);\n}\n\nvoid Matrix::biggerThan(Matrix& a, Matrix& target) const {\n    assert(isSameDims(a));\n    target.resize(*this);\n    _applyLoop2(a, &_bigger, target);\n}\n\nvoid Matrix::smallerThan(Matrix& a) {\n    smallerThan(a, *this);\n}\n\nvoid Matrix::smallerThan(Matrix& a, Matrix& target) const {\n    assert(isSameDims(a));\n    target.resize(*this);\n    _applyLoop2(a, &_smaller, target);\n}\n\nvoid Matrix::equals(Matrix& a) {\n    equals(a, *this);\n}\n\nvoid Matrix::equals(Matrix& a, Matrix& target) const {\n    assert(isSameDims(a));\n    target.resize(*this);\n    _applyLoop2(a, &_equal, target);\n}\n\nvoid Matrix::notEquals(Matrix& a) {\n    notEquals(a, *this);\n}\n\nvoid Matrix::notEquals(Matrix& a, Matrix& target) const {\n    assert(isSameDims(a));\n    target.resize(*this);\n    _applyLoop2(a, &_notEqual, target);\n}\n\nvoid Matrix::minWith(Matrix &a) {\n    minWith(a, *this);\n}\n\nvoid Matrix::minWith(Matrix &a, Matrix& target) const {\n    assert(isSameDims(a));\n    target.resize(*this);\n    _applyLoop2(a, &_min, target);\n}\n\nvoid Matrix::maxWith(Matrix &a) {\n    maxWith(a, *this);\n}\n\nvoid Matrix::maxWith(Matrix &a, Matrix& target) const {\n    assert(isSameDims(a));\n    target.resize(*this);\n    _applyLoop2(a, &_max, target);\n}\n\n/* this := this + scale*tile(vec) */\nvoid Matrix::addVector(const Matrix& vec, MTYPE scale, Matrix& target) {\n    if(&target != this) {\n        copy(target);\n    }\n    assert(std::min(vec.getNumCols(), vec.getNumRows()) == 1);\n    const bool rowVector = vec.getNumRows() == 1;\n    const bool colVector = vec.getNumCols() == 1;\n    assert((rowVector && vec.getNumCols() == target.getNumCols()) || (colVector && vec.getNumRows() == target.getNumRows()));\n    if (rowVector && colVector) {\n        addScalar(vec(0,0) * scale, target);\n        return;\n    }\n    const int64 loopTil = rowVector ? target.getNumRows() : target.getNumCols();\n    const int64 dataInc = ((rowVector && target.isTrans()) || (!rowVector && !target.isTrans())) ? 1 : (rowVector ? target.getNumCols() : target.getNumRows());\n    const int64 myStride = ((target.isTrans() && rowVector) || (!target.isTrans() && !rowVector)) ? loopTil : 1;\n    for (int64 i = 0; i < loopTil; i++) {\n        CBLAS_AXPY(vec.getNumElements(), scale, vec._data, 1, target._data + dataInc * i, myStride);\n    }\n}\n\n/* this := this + scale*tile(vec) */\nvoid Matrix::addVector(const Matrix& vec, MTYPE scale) {\n    addVector(vec, scale, *this);\n}\n\nvoid Matrix::addVector(const Matrix& vec) {\n    addVector(vec, 1, *this);\n}\n\nvoid Matrix::addVector(const Matrix& vec, Matrix& target) {\n    addVector(vec, 1, target);\n}\n\nvoid Matrix::eltWiseMultByVector(const Matrix& vec) {\n    eltWiseMultByVector(vec, *this);\n}\n\n/* omg test these */\nvoid Matrix::eltWiseMultByVector(const Matrix& vec, Matrix& target) {\n    if(&target != this) {\n        copy(target);\n    }\n    assert(std::min(vec.getNumCols(), vec.getNumRows()) == 1);\n    const bool rowVector = vec.getNumRows() == 1;\n    assert((rowVector && vec.getNumCols() == target.getNumCols()) || (!rowVector && vec.getNumRows() == target.getNumRows()));\n    const int64 dataInc = ((rowVector && !target.isTrans()) || (!rowVector && target.isTrans())) ? 1 : (rowVector ? target.getNumRows() : target.getNumCols());\n    const int64 myStride = ((!target.isTrans() && !rowVector) || (target.isTrans() && rowVector)) ? 1 : vec.getNumElements();\n    const int64 numScaling = rowVector ? target.getNumRows() : target.getNumCols();\n    for (int64 i = 0; i < vec.getNumElements(); i++) {\n        CBLAS_SCAL(numScaling, vec._data[i], target._data + dataInc * i, myStride);\n    }\n}\n\n/* return := scale * this * b */\nvoid Matrix::rightMult(const Matrix& b, MTYPE scale) {\n    rightMult(b, scale, *this);\n}\n\n/* return := this * b */\nvoid Matrix::rightMult(const Matrix& b) {\n    rightMult(b, 1);\n}\n\n/* target := this * b\n * also resizes target if necessary.*/\nvoid Matrix::rightMult(const Matrix &b, Matrix &target) const {\n    rightMult(b, 1, target);\n}\n\n/* target := scaleAB * this * b\n * also resizes target if necessary.*/\nvoid Matrix::rightMult(const Matrix &b, MTYPE scaleAB, Matrix &target) const {\n    if(&target != this) {\n        target.resize(this->_numRows, b._numCols);\n    }\n    target.addProduct(*this, b, scaleAB, 0);\n}\n\n/* this := scaleAB * a*b + scaleC * this\n * ALL SIZES MUST BE CORRECT. */\nvoid Matrix::addProduct(const Matrix& a, const Matrix& b, MTYPE scaleAB, MTYPE scaleThis) {\n    assert(a.getNumCols() == b.getNumRows());\n    assert(this->getNumRows() == a.getNumRows() && this->getNumCols() == b.getNumCols());\n    assert(!isTrans());\n    CBLAS_GEMM(CblasRowMajor, a._trans, b._trans, a._numRows, b._numCols, a._numCols, scaleAB, a._data,\n            a._getNumColsBackEnd(), b._data, b._getNumColsBackEnd(), scaleThis, this->_data, this->_numCols);\n}\n\nvoid Matrix::addProduct(const Matrix& a, const Matrix& b) {\n    addProduct(a, b, 1, 1);\n}\n\nMatrix& Matrix::transpose() const {\n    return *new Matrix(this->_data, this->_numCols, this->_numRows, !isTrans());\n}\n\nMatrix& Matrix::transpose(bool hard) const {\n    if (!hard || isTrans()) {\n        return transpose();\n    }\n    Matrix &meTrans = *new Matrix(_numCols, _numRows);\n    for (int64 i = 0; i < _numRows; i++) {\n        for (int64 j = 0; j < _numCols; j++) {\n            meTrans(j, i) = (*this)(i, j);\n        }\n    }\n    return meTrans;\n}\n\nMatrix& Matrix::tile(int64 timesY, int64 timesX) const {\n    Matrix& tiled = *new Matrix(this->_numRows * timesY, this->_numCols * timesX);\n    _tileTo2(tiled);\n    return tiled;\n}\n\n/* resizes target if necessary */\nvoid Matrix::tile(int64 timesY, int64 timesX, Matrix& target) const {\n    target.resize(this->_numRows * timesY, this->_numCols * timesX);\n    _tileTo2(target);\n}\n\n/* a variant ... seems to be no faster than original. */\nvoid Matrix::_tileTo2(Matrix& target) const {\n    for(int64 y = 0; y < target._numRows; y += this->_numRows) {\n        for(int64 x = 0; x < target._numCols; x += this->_numCols) {\n            this->copy(target, 0, -1, 0, -1, y, x);\n        }\n    }\n}\n\n/* guarantees that result will be non-transposed */\nvoid Matrix::resize(int64 newNumRows, int64 newNumCols) {\n    if(this->_numRows != newNumRows || this->_numCols != newNumCols) {\n        assert(!isView());\n        if (this->getNumElements() != newNumRows * newNumCols) {\n            delete[] this->_data; //deleting NULL is ok, sez c++\n            this->_data = new MTYPE[newNumRows * newNumCols];\n        }\n        this->_updateDims(newNumRows, newNumCols);\n        this->_trans = CblasNoTrans;\n    }\n}\n\nvoid Matrix::resize(const Matrix& like) {\n    resize(like.getNumRows(), like.getNumCols());\n}\n\nvoid Matrix::scale(MTYPE alpha) {\n    scale(alpha, *this);\n}\n\nvoid Matrix::scale(MTYPE alpha, Matrix& target) {\n    if (&target != this) {\n        target.resize(*this);\n        copy(target);\n    }\n    CBLAS_SCAL(getNumElements(), alpha, target._data, 1);\n}\n\n/* performs no resizing.\n * Warnings:\n * 1. ALL DIMENSIONS MUST BE CORRECT\n * 2. The source and destination memories better not overlap! */\nvoid Matrix::copy(Matrix& dest, int64 srcStartRow, int64 srcEndRow, int64 srcStartCol, int64 srcEndCol, int64 destStartRow, int64 destStartCol) const {\n    srcEndRow = srcEndRow < 0 ? this->_numRows : srcEndRow;\n    srcEndCol = srcEndCol < 0 ? this->_numCols : srcEndCol;\n    assert(destStartRow >= 0 && destStartCol >= 0); //some range-checking\n    assert(srcEndRow <= _numRows && srcEndCol <= _numCols);\n    assert(destStartRow + srcEndRow - srcStartRow <= dest.getNumRows());\n    assert(destStartCol + srcEndCol - srcStartCol <= dest.getNumCols());\n    // I found no evidence that memcpy is actually faster than just\n    // copying element-by-element.\n    if (!isTrans() && !dest.isTrans()) {\n        int64 src_start_idx = this->_numCols * srcStartRow + srcStartCol;\n        int64 dest_start_idx = dest._numCols * destStartRow + destStartCol;\n        int64 copy_row_width = srcEndCol - srcStartCol;\n\n        for (int64 i = srcStartRow; i < srcEndRow; i++) {\n            memcpy(dest._data + dest_start_idx + dest._numCols * (i - srcStartRow),\n                    this->_data + src_start_idx + this->_numCols * (i - srcStartRow), sizeof(MTYPE) * copy_row_width);\n        }\n    } else {\n        for (int64 i = srcStartRow; i < srcEndRow; i++) {\n            for (int64 j = srcStartCol; j < srcEndCol; j++) {\n                dest(i - srcStartRow + destStartRow, j - srcStartCol + destStartCol) = (*this)(i, j);\n            }\n        }\n    }\n}\n\n/* preserves everything excluding transposedness.\n * new matrix owns its data */\nMatrix& Matrix::copy() const {\n    Matrix& copy = *new Matrix(*this);\n    this->copy(copy);\n    return copy;\n}\n\n/* resizes target if necessary */\nvoid Matrix::copy(Matrix& target) const {\n    target.resize(this->_numRows, this->_numCols); //target is now non-transposed\n    if(this->isTrans() == target.isTrans()) {\n        this->_copyAllTo(target);\n    } else { //if I'm transposed, make sure that target is non-transposed copy\n        this->copy(target, 0, -1, 0, -1, 0, 0);\n    }\n}\n\nvoid Matrix::_copyAllTo(Matrix& target) const {\n    assert(target.isTrans() == isTrans());\n    memcpy((void*) target._data, (void*) this->_data, this->getNumDataBytes());\n    target._trans = this->_trans;\n}\n\nMTYPE Matrix::min() const {\n    return _aggregate(&_min, MTYPE_MAX);\n}\n\nMatrix& Matrix::min(int64 axis) const {\n    Matrix& target = axis == 0 ? *new Matrix(1, this->_numCols) : *new Matrix(this->_numRows, 1);\n    this->min(axis, target);\n    return target;\n}\n\nvoid Matrix::min(int64 axis, Matrix& target) const {\n    _aggregate(axis, target, &_min, MTYPE_MAX);\n}\n\nMTYPE Matrix::max() const {\n    return _aggregate(&_max, -MTYPE_MAX);\n}\n\nMatrix& Matrix::max(int64 axis) const {\n    Matrix& target = axis == 0 ? *new Matrix(1, this->_numCols) : *new Matrix(this->_numRows, 1);\n    this->max(axis, target);\n    return target;\n}\n\nvoid Matrix::max(int64 axis, Matrix& target) const {\n    _aggregate(axis, target, &_max, -MTYPE_MAX);\n}\n\nMTYPE Matrix::sum() const {\n    return _aggregate(&_add, 0);\n}\n\nMTYPE Matrix::norm() const {\n    return sqrt(norm2());\n}\n\nMTYPE Matrix::norm2() const {\n    return _aggregate(&_addSquare, 0);\n}\n\nMatrix& Matrix::sum(int64 axis) const {\n    Matrix& target = axis == 0 ? *new Matrix(1, this->_numCols) : *new Matrix(this->_numRows, 1);\n    this->sum(axis, target);\n    return target;\n}\n\nvoid Matrix::sum(int64 axis, Matrix& target) const {\n    _aggregate(axis, target, &_add, 0);\n}\n\nvoid Matrix::_aggregate(int64 axis, Matrix& target, MTYPE (*agg_func)(MTYPE, MTYPE), MTYPE initialValue) const {\n    if (axis == 0) {\n        target.resize(1, this->_numCols);\n        for (int64 j = 0; j < this->_numCols; j++) {\n            target(0, j) = _aggregateCol(j, agg_func, initialValue);\n        }\n    } else {\n        target.resize(this->_numRows, 1);\n        for (int64 i = 0; i < this->_numRows; i++) {\n            target(i, 0) = _aggregateRow(i, agg_func, initialValue);\n        }\n    }\n}\n\nMTYPE Matrix::_aggregateRow(int64 row, MTYPE (*agg_func)(MTYPE, MTYPE), MTYPE initialValue) const {\n    MTYPE v = initialValue;\n    for (int64 j = 0; j < this->_numCols; j++) {\n        v = agg_func((*this)(row, j), v);\n    }\n    return v;\n}\n\nMTYPE Matrix::_aggregateCol(int64 col, MTYPE (*agg_func)(MTYPE, MTYPE), MTYPE initialValue) const {\n    MTYPE v = initialValue;\n    for (int64 i = 0; i < this->_numRows; i++) {\n        v = agg_func((*this)(i, col), v);\n    }\n    return v;\n}\n\nMTYPE Matrix::_aggregate(MTYPE (*agg_func)(MTYPE, MTYPE), MTYPE initialValue) const {\n    MTYPE v = initialValue;\n    MTYPE* ptr = _data;\n    for (int64 i = 0; i < getNumElements(); i++, ptr++) {\n        v = agg_func(*ptr, v);\n    }\n    return v;\n}\n\nvoid Matrix::printShape(const char* name) const {\n    printf(\"%s: %lldx%lld\\n\", name, getNumRows(), getNumCols());\n}\n\nvoid Matrix::print() const {\n    print(0,getNumRows(),0, getNumCols());\n}\n\nvoid Matrix::print(int64 rows, int64 cols) const {\n    print(0,rows,0, cols);\n}\n\nvoid Matrix::print(int64 startRow, int64 rows, int64 startCol, int64 cols) const {\n    for (int64 i = startRow; i < std::min(startRow+rows, this->_numRows); i++) {\n        for (int64 j = startCol; j < std::min(startCol+cols, this->_numCols); j++) {\n            printf(\"%.15f \", (*this)(i, j));\n        }\n        printf(\"\\n\");\n    }\n}\n\nvoid Matrix::apply(Matrix::FUNCTION f) {\n    apply(f, *this);\n}\n\n\nvoid Matrix::apply(Matrix::FUNCTION f, Matrix& target) {\n    MTYPE (*func)(MTYPE);\n    if(f == EXP) {\n        func = &_exp;\n    } else if(f == TANH) {\n        func = &_tanh;\n    } else if(f == RECIPROCAL) {\n        func = &_recip;\n    } else if (f == SQUARE) {\n        func = &_square;\n    } else if(f == LOG) {\n        func = &_log;\n    } else if(f == ZERO) {\n        func = &_zero;\n    } else if (f == ONE) {\n        func = &_one;\n    } else if(f == LOGISTIC1) {\n        func = &_sigma1;\n    } else if(f == LOGISTIC2) {\n        func = &_sigma2;\n    } else if (f == ABS) {\n        func = &_abs;\n    } else if (f == SIGN) {\n        func = &_sign;\n    } else {\n        return;\n        //LOG(FATAL) << \"Matrix::apply: Unknown function type\";\n    }\n    this->_applyLoop(func, target);\n}\n\nvoid Matrix::eltWiseMult(const Matrix& a, Matrix& target) const {\n    assert(isSameDims(a));\n    target.resize(*this);\n    this->_applyLoop2(a, &_mult, target);\n}\n\nvoid Matrix::eltWiseDivide(const Matrix& a, Matrix& target) const {\n    assert(isSameDims(a));\n    target.resize(*this);\n    this->_applyLoop2(a, &_divide, target);\n}\n\nvoid Matrix::eltWiseMult(const Matrix& a) {\n    eltWiseMult(a, *this);\n}\n\nvoid Matrix::eltWiseDivide(const Matrix& a) {\n    eltWiseDivide(a, *this);\n}\n\nvoid Matrix::randomizeUniform() {\n    this->_applyLoop(&_rand);\n}\n\nvoid Matrix::randomizeNormal() {\n    //LOG(FATAL) << \"randomizeNormal only implemented on MKL!\";\n}\n\nvoid Matrix::randomizeNormal(MTYPE mean, MTYPE stdev) {\n    //LOG(FATAL) << \"randomizeNormal only implemented on MKL!\";\n}\n\nvoid Matrix::eltWiseDivideByVector(const Matrix& vec) {\n    eltWiseDivideByVector(vec, *this);\n}\n\n/* This function allocates a chunk of memory at most as big as the input vector */\nvoid Matrix::eltWiseDivideByVector(const Matrix& vec, Matrix& target) {\n    assert(std::min(vec.getNumCols(), vec.getNumRows()) == 1);\n    const bool rowVector = vec.getNumRows() == 1;\n    assert((rowVector && vec.getNumCols() == getNumCols()) || (!rowVector && vec.getNumRows() == getNumRows()));\n    if(&target != this) {\n        target.resize(*this);\n    }\n    _divideByVector(vec, target);\n}\n\nvoid Matrix::_divideByVector(const Matrix& vec, Matrix& target) {\n    Matrix& vecInverse = vec.copy();\n    vecInverse.apply(RECIPROCAL);\n    eltWiseMultByVector(vecInverse,target);\n    delete &vecInverse;\n}\n\nvoid Matrix::reshape(int64 numRows, int64 numCols) {\n    assert(_numElements == numRows*numCols);\n    _numRows = numRows;\n    _numCols = numCols;\n}\n\nMatrix& Matrix::reshaped(int64 numRows, int64 numCols) {\n    assert(_numElements == numRows*numCols);\n    return *new Matrix(_data, numRows, numCols, isTrans());\n}\n\nvoid Matrix::_applyLoop(MTYPE (*func)(MTYPE), Matrix& target) {\n    MTYPE *ptr = this->_data, *tgtPtr = target._data;\n    for (int64 i = 0; i < getNumElements(); i++, ptr++, tgtPtr++) {\n        *tgtPtr = (*func)(*ptr);\n    }\n}\n\nvoid Matrix::_applyLoop(MTYPE (*func)(MTYPE)) {\n    _applyLoop(func, *this);\n}\n\nvoid Matrix::_applyLoop2(const Matrix& a, MTYPE (*func)(MTYPE,MTYPE), Matrix& target) const {\n    for (int64 i = 0; i < getNumRows(); i++) {\n        for (int64 j = 0; j < getNumCols(); j++) {\n            target(i, j) = (*func)((*this)(i, j), a(i, j));\n        }\n    }\n}\n\nvoid Matrix::_applyLoop2(const Matrix& a, MTYPE (*func)(MTYPE,MTYPE, MTYPE), MTYPE scalar, Matrix& target) const {\n    for (int64 i = 0; i < getNumRows(); i++) {\n        for (int64 j = 0; j < getNumCols(); j++) {\n            target(i, j) = (*func)((*this)(i, j), a(i, j), scalar);\n        }\n    }\n}\n\nvoid Matrix::_applyLoop2(const Matrix& a, MTYPE (*func)(MTYPE,MTYPE, MTYPE, MTYPE), MTYPE scalar1, MTYPE scalar2, Matrix& target) const {\n    for (int64 i = 0; i < getNumRows(); i++) {\n        for (int64 j = 0; j < getNumCols(); j++) {\n            target(i, j) = (*func)((*this)(i, j), a(i, j), scalar1, scalar2);\n        }\n    }\n}\n\nvoid Matrix::_applyLoopScalar(const MTYPE scalar, MTYPE(*func)(MTYPE, MTYPE), Matrix& target) const {\n    MTYPE *myPtr = _data;\n    MTYPE *targetPtr = target._data;\n    for (int64 i = 0; i < getNumElements(); i++, myPtr++, targetPtr++) {\n        *targetPtr = (*func)(*myPtr, scalar);\n    }\n}\n\nbool Matrix::hasNan() const {\n    for (int64 r = 0; r < _numRows; r++) {\n        for (int64 c = 0; c < _numCols; c++) {\n            if (isnan((*this)(r,c))) {\n                return true;\n            }\n        }\n    }\n    return false;\n}\n\nbool Matrix::hasInf() const {\n    for (int64 r = 0; r < _numRows; r++) {\n        for (int64 c = 0; c < _numCols; c++) {\n            if (isinf((*this)(r,c))) {\n                return true;\n            }\n        }\n    }\n    return false;\n}\n\n\n"
  },
  {
    "path": "caffe2/contrib/docker-ubuntu-14.04/Dockerfile",
    "content": "FROM ubuntu:14.04\nMAINTAINER caffe-dev <caffe-dev@googlegroups.com>\n\n# A docker container with CUDA and caffe2 installed.\n# Note: this should install everything but cudnn, which requires you to have a\n# manual registration and download from the NVidia website. After creating this\n# docker image, the Caffe2 repository is located at /opt/caffe2. You can install\n# cudnn manually and re-compile caffe2.\n\n################################################################################\n# Step 1: set up cuda on the ubuntu box.\n################################################################################\n\nRUN apt-get update && apt-get install -q -y \\\n  build-essential \\\n  wget\n\nRUN cd /tmp && \\\n  wget http://developer.download.nvidia.com/compute/cuda/7_0/Prod/local_installers/cuda_7.0.28_linux.run && \\\n  chmod +x cuda_*_linux.run && ./cuda_*_linux.run -extract=`pwd` && \\\n  ./NVIDIA-Linux-x86_64-*.run -s --no-kernel-module && \\\n  ./cuda-linux64-rel-*.run -noprompt && \\\n  rm -rf *\n\n# Ensure the CUDA libs and binaries are in the correct environment variables\nENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64\nENV PATH=$PATH:/usr/local/cuda/bin\n\n# Run nvcc to make sure things are set correctly.\nRUN nvcc --version\n\n################################################################################\n# Step 2: set up caffe2 pre-requisites\n################################################################################\n\nRUN apt-get update && apt-get install -q -y \\\n  git \\\n  libeigen3-dev \\\n  libgoogle-glog-dev \\\n  libleveldb-dev \\\n  liblmdb-dev \\\n  libopencv-dev \\\n  libprotobuf-dev \\\n  libsnappy-dev \\\n  zlib1g-dev \\\n  libbz2-dev \\\n  protobuf-compiler \\\n  python-dev \\\n  python-pip\n\nRUN cd /tmp && \\\n  git clone https://github.com/facebook/rocksdb.git && \\\n  cd /tmp/rocksdb && \\\n  make && make install && \\\n  cd / && \\\n  rm -rf /tmp/rocksdb\n\n# Caffe2 works best with openmpi 1.8.5 or above (which has cuda support).\n# If you do not need openmpi, skip this step.\nRUN cd /tmp && \\\n  wget http://www.open-mpi.org/software/ompi/v1.10/downloads/openmpi-1.10.0.tar.gz && \\\n  tar xzvf openmpi-1.10.0.tar.gz && \\\n  cd /tmp/openmpi-1.10.0 && \\\n  ./configure --with-cuda --with-threads && \\\n  make && make install && \\\n  cd / && \\\n  rm -rf /tmp/openmpi-1.10.0 && \\\n  rm /tmp/openmpi-1.10.0.tar.gz\n\n# Caffe2 requires zeromq 4.0 or above, manually install.\n# If you do not need zeromq, skip this step.\nRUN apt-get install -q -y autoconf libtool\nRUN mkdir /tmp/zeromq-build && \\\n  cd /tmp/zeromq-build && \\\n  wget https://github.com/zeromq/zeromq4-1/archive/v4.1.3.tar.gz && \\\n  tar xzvf v4.1.3.tar.gz --strip 1 && \\\n  ./autogen.sh && \\\n  ./configure --without-libsodium && \\\n  make && make install && \\\n  cd / && \\\n  rm -rf /tmp/zeromq-build\n\n# pip self upgrade\nRUN pip install --upgrade pip\n\n# Python dependencies\nRUN pip install \\\n  matplotlib \\\n  numpy \\\n  protobuf\n\n################################################################################\n# Step 3: install optional dependencies (\"good to have\" features)\n################################################################################\n\nRUN apt-get install -q -y \\\n  gfortran \\\n  graphviz \\\n  libatlas-base-dev \\\n  vim\n\nRUN pip install \\\n  flask \\\n  ipython \\\n  notebook \\\n  pydot \\\n  python-nvd3 \\\n  scipy \\\n  tornado\n\n# This is intentional. scikit-image has to be after scipy.\nRUN pip install \\\n  scikit-image\n\n################################################################################\n# Step 4: set up caffe2\n################################################################################\n\n# Get the repository, and build.\nRUN cd /opt && \\\n  git clone https://github.com/Yangqing/caffe2.git && \\\n  cd /opt/caffe2 && \\\n  make\n\n# Now, we know that some of the caffe tests will fail. How do we deal with\n# those?\n"
  },
  {
    "path": "caffe2/contrib/gloo/CMakeLists.txt",
    "content": "if(USE_GLOO)\n  set(Caffe2_CONTRIB_GLOO_CPU_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/allgather_ops.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/allreduce_ops.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/barrier_ops.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/broadcast_ops.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/common.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/common_world_ops.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/context.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/reduce_scatter_ops.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/store_handler.cc\"\n    )\n\n  set(Caffe2_CONTRIB_GLOO_GPU_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/allreduce_ops_gpu.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/broadcast_ops_gpu.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/common_world_ops_gpu.cc\"\n    )\n\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${Caffe2_CONTRIB_GLOO_CPU_SRC} PARENT_SCOPE)\n  set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${Caffe2_CONTRIB_GLOO_GPU_SRC} PARENT_SCOPE)\nendif()\n"
  },
  {
    "path": "caffe2/contrib/gloo/allgather_ops.cc",
    "content": "/**\n * Copyright (c) 2017-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"allgather_ops.h\"\n\n#include <gloo/allgather_ring.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\ntemplate <class Context>\nvoid AllgatherOp<Context>::initializeAlgorithm() {\n  if (init_.template IsType<float>()) {\n    algorithm_.reset(new ::gloo::AllgatherRing<float>(\n        init_.context,\n        init_.template getInputs<float>(),\n        init_.template getOutput<float>(),\n        init_.size));\n  } else if (init_.template IsType<long>()) {\n    algorithm_.reset(new ::gloo::AllgatherRing<long>(\n        init_.context,\n        init_.template getInputs<long>(),\n        init_.template getOutput<long>(),\n        init_.size));\n  } else if (init_.template IsType<int>()) {\n    algorithm_.reset(new ::gloo::AllgatherRing<int>(\n        init_.context,\n        init_.template getInputs<int>(),\n        init_.template getOutput<int>(),\n        init_.size));\n  } else if (init_.template IsType<float16>()) {\n    algorithm_.reset(new ::gloo::AllgatherRing<::gloo::float16>(\n        init_.context,\n        init_.template getInputs<::gloo::float16>(),\n        init_.template getOutput<::gloo::float16>(),\n        init_.size));\n  } else {\n    CAFFE_ENFORCE(false, \"Unhandled type: \", init_.meta.name());\n  }\n}\n\nnamespace {\n\nREGISTER_CPU_OPERATOR_WITH_ENGINE(Allgather, GLOO, AllgatherOp<CPUContext>);\n\n} // namespace\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/allgather_ops.h",
    "content": "/**\n * Copyright (c) 2017-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <algorithm>\n\n#include \"caffe2/contrib/gloo/common.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/types.h\"\n\n#include <gloo/algorithm.h>\n#include <gloo/common/error.h>\n#include <gloo/context.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\ntemplate <class Context>\nclass AllgatherOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  AllgatherOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        ws_(ws),\n        status_blob_(\n            OperatorBase::GetSingleArgument<std::string>(\"status_blob\", \"\")) {\n    if (status_blob_ != \"\") {\n      ws_->CreateBlob(status_blob_);\n    }\n  }\n\n  virtual ~AllgatherOp() {}\n\n  bool RunOnDevice() override {\n    std::call_once(once_, [&] { initialize(); });\n\n    // If any parameter has changed in between runs, the initialized\n    // algorithm is invalid and cannot be used.\n    update(current_);\n    CAFFE_ENFORCE(current_ == init_, \"Inputs/outputs have changed\");\n\n    try {\n      algorithm_->run();\n    } catch (::gloo::IoException& ioe) {\n      LOG(ERROR) << \"Caught gloo IO exception: \" << ioe.what();\n      if (status_blob_ != \"\") {\n        signalFailure(ws_->GetBlob(status_blob_), ioe);\n        return false;\n      } else {\n        throw ioe;\n      }\n    }\n    return true;\n  }\n\n protected:\n  void initialize() {\n    // Allocate output tensor\n    CAFFE_ENFORCE_EQ(OutputSize(), 1);\n    auto comm_size =\n        OperatorBase::Input<std::shared_ptr<::gloo::Context>>(0)->size;\n    const auto dims =\n        std::vector<TIndex>(1, (InputSize() - 1) * Input(1).size() * comm_size);\n    Output(0)->Resize(dims);\n\n    // Store which inputs/outputs this instance initialized with\n    update(init_);\n\n    CAFFE_ENFORCE_EQ(init_.outputs.size(), 1);\n\n    // Verify tensors all have same size\n    size_t size = Input(1).size();\n    for (auto i = 2; i < InputSize(); i++) {\n      CAFFE_ENFORCE_EQ(Input(i).size(), size);\n    }\n\n    // Verify tensors all have same type\n    TypeMeta meta = Input(1).meta();\n    for (auto i = 2; i < InputSize(); i++) {\n      CAFFE_ENFORCE(Input(i).meta() == meta);\n    }\n\n    // Finally initialize the algorithm\n    initializeAlgorithm();\n  }\n\n  void initializeAlgorithm();\n\n  std::once_flag once_;\n  std::unique_ptr<::gloo::Algorithm> algorithm_;\n\n  // Captures the parameters passed to Gloo when first initialized.\n  // An instance is updated every time this op runs and is compared\n  // to the reference instance for equality. If any parameter has\n  // changed from run to run, the initialized algorithm is invalid.\n  void update(GlooParameters& params) {\n    params.context = OperatorBase::Input<std::shared_ptr<::gloo::Context>>(0);\n    params.inputs.resize(InputSize() - 1);\n    params.size = Input(1).size();\n    params.meta = Input(1).meta();\n    for (auto i = 0; i < params.inputs.size(); i++) {\n      params.inputs[i] = Input(i + 1).template raw_data();\n    }\n    params.outputs.resize(OutputSize());\n    params.outputs[0] = Output(0)->raw_mutable_data(params.meta);\n  }\n\n  GlooParameters init_;\n  GlooParameters current_;\n  Workspace* ws_;\n  std::string status_blob_;\n};\n\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/allreduce_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"allreduce_ops.h\"\n\n#include <gloo/allreduce_halving_doubling.h>\n#include <gloo/allreduce_ring.h>\n#include <gloo/allreduce_ring_chunked.h>\n#include <gloo/types.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\ntemplate <class Context>\nvoid AllreduceOp<Context>::initializeHalvingDoubling() {\n  if (init_.template IsType<float>()) {\n    algorithm_.reset(new ::gloo::AllreduceHalvingDoubling<float>(\n        init_.context, init_.template getOutputs<float>(), init_.size));\n  } else if (init_.template IsType<::caffe2::float16>()) {\n    algorithm_.reset(new ::gloo::AllreduceHalvingDoubling<::gloo::float16>(\n        init_.context,\n        init_.template getOutputs<::gloo::float16>(),\n        init_.size));\n  } else {\n    CAFFE_ENFORCE(false, \"Unhandled type: \", init_.meta.name());\n  }\n}\n\ntemplate <class Context>\nvoid AllreduceOp<Context>::initializeRingFull() {\n  if (init_.template IsType<float>()) {\n    algorithm_.reset(new ::gloo::AllreduceRing<float>(\n        init_.context, init_.template getOutputs<float>(), init_.size));\n  } else if (init_.template IsType<::caffe2::float16>()) {\n    algorithm_.reset(new ::gloo::AllreduceRing<::gloo::float16>(\n        init_.context,\n        init_.template getOutputs<::gloo::float16>(),\n        init_.size));\n  } else {\n    CAFFE_ENFORCE(false, \"Unhandled type: \", init_.meta.name());\n  }\n}\n\ntemplate <class Context>\nvoid AllreduceOp<Context>::initializeRingChunked() {\n  if (init_.template IsType<float>()) {\n    algorithm_.reset(new ::gloo::AllreduceRingChunked<float>(\n        init_.context, init_.template getOutputs<float>(), init_.size));\n  } else if (init_.template IsType<::caffe2::float16>()) {\n    algorithm_.reset(new ::gloo::AllreduceRingChunked<::gloo::float16>(\n        init_.context,\n        init_.template getOutputs<::gloo::float16>(),\n        init_.size));\n  } else {\n    CAFFE_ENFORCE(false, \"Unhandled type: \", init_.meta.name());\n  }\n}\n\nnamespace {\n\nREGISTER_CPU_OPERATOR_WITH_ENGINE(Allreduce, GLOO, AllreduceOp<CPUContext>);\n\n} // namespace\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/allreduce_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <algorithm>\n\n#include \"caffe2/contrib/gloo/common.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\n#include <gloo/algorithm.h>\n#include <gloo/common/error.h>\n#include <gloo/context.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\ntemplate <class Context>\nclass AllreduceOp final : public Operator<Context> {\n  enum Mode { RING_FULL, RING_CHUNKED, HALVING_DOUBLING };\n\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  AllreduceOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        ws_(ws),\n        status_blob_(\n            OperatorBase::GetSingleArgument<std::string>(\"status_blob\", \"\")),\n        gpu_direct_(\n            OperatorBase::GetSingleArgument<bool>(\"gpu_direct\", false)) {\n    if (status_blob_ != \"\") {\n      ws_->CreateBlob(status_blob_);\n    }\n  }\n\n  virtual ~AllreduceOp() {}\n\n  bool RunOnDevice() override {\n    std::call_once(once_, [&] { initialize(); });\n\n    // If any parameter has changed in between runs, the initialized\n    // algorithm is invalid and cannot be used.\n    update(current_);\n    CAFFE_ENFORCE(current_ == init_, \"Inputs/outputs have changed\");\n\n    try {\n      algorithm_->run();\n    } catch (::gloo::IoException& ioe) {\n      LOG(ERROR) << \"Caught gloo IO exception: \" << ioe.what();\n      if (status_blob_ != \"\") {\n        signalFailure(ws_->GetBlob(status_blob_), ioe);\n        return false;\n      } else {\n        throw ioe;\n      }\n    }\n    return true;\n  }\n\n protected:\n  void initialize() {\n    Mode mode = HALVING_DOUBLING;\n    auto bytes = Input(1).nbytes();\n\n    // Store which inputs/outputs this instance initialized with\n    update(init_);\n\n    // Verify inputs == ouputs\n    CAFFE_ENFORCE_EQ(init_.inputs.size(), init_.outputs.size());\n    for (auto i = 0; i < init_.inputs.size(); i++) {\n      CAFFE_ENFORCE_EQ(init_.inputs[i], init_.outputs[i]);\n    }\n\n    // Verify tensors all have same size\n    size_t size = Input(1).size();\n    for (auto i = 2; i < InputSize(); i++) {\n      CAFFE_ENFORCE_EQ(Input(i).size(), size);\n    }\n\n    // Verify tensors all have same type\n    TypeMeta meta = Input(1).meta();\n    for (auto i = 2; i < InputSize(); i++) {\n      CAFFE_ENFORCE(Input(i).meta() == meta);\n    }\n\n    switch (mode) {\n      case RING_FULL:\n        initializeRingFull();\n        return;\n      case RING_CHUNKED:\n        initializeRingChunked();\n        return;\n      case HALVING_DOUBLING:\n        initializeHalvingDoubling();\n        return;\n    }\n\n    CAFFE_ENFORCE(false, \"Unreachable code\");\n  }\n\n  void initializeHalvingDoubling();\n  void initializeRingFull();\n  void initializeRingChunked();\n\n  std::once_flag once_;\n  std::unique_ptr<::gloo::Algorithm> algorithm_;\n\n  // Captures the parameters passed to Gloo when first initialized.\n  // An instance is updated every time this op runs and is compared\n  // to the reference instance for equality. If any parameter has\n  // changed from run to run, the initialized algorithm is invalid.\n  void update(GlooParameters& params) {\n    params.context = OperatorBase::Input<std::shared_ptr<::gloo::Context>>(0);\n    params.inputs.resize(InputSize() - 1);\n    params.outputs.resize(OutputSize());\n    for (auto i = 0; i < params.inputs.size(); i++) {\n      params.inputs[i] = Input(i + 1).template raw_data();\n      params.outputs[i] = Output(i)->template raw_mutable_data();\n    }\n    params.size = Output(0)->size();\n    params.meta = Output(0)->meta();\n  }\n\n  GlooParameters init_;\n  GlooParameters current_;\n  Workspace* ws_;\n  std::string status_blob_;\n  const bool gpu_direct_;\n};\n\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/allreduce_ops_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"allreduce_ops.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/logging.h\"\n\n#include <gloo/cuda_allreduce_halving_doubling.h>\n#include <gloo/cuda_allreduce_ring.h>\n#include <gloo/cuda_allreduce_ring_chunked.h>\n#include <gloo/types.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\nnamespace {\n\n// Decides on using GPUDirect based on device support.\ntemplate <template <typename T, typename W> class A, typename T>\nstd::unique_ptr<::gloo::Algorithm> initializeAlgorithm(\n    bool gpu_direct_,\n    std::shared_ptr<::gloo::Context> context,\n    std::vector<T*> ptrs,\n    size_t size) {\n  if (gpu_direct_) {\n    if (context->getDevice()->hasGPUDirect()) {\n      return std::unique_ptr<::gloo::Algorithm>(\n        new A<T, ::gloo::CudaDeviceWorkspace<T>>(context, ptrs, size));\n    } else {\n      LOG(WARNING)\n        << \"GPUDirect not available; \"\n        << \"Gloo communication will go through system memory instead.\";\n    }\n  }\n\n  return std::unique_ptr<::gloo::Algorithm>(\n    new A<T, ::gloo::CudaHostWorkspace<T>>(context, ptrs, size));\n}\n\n} // namespace\n\ntemplate <class Context>\nvoid AllreduceOp<Context>::initializeHalvingDoubling() {\n  if (init_.template IsType<float>()) {\n    algorithm_ =\n      initializeAlgorithm<::gloo::CudaAllreduceHalvingDoubling, float>(\n        gpu_direct_,\n        init_.context,\n        init_.template getOutputs<float>(),\n        init_.size);\n  } else if (init_.template IsType<float16>()) {\n    algorithm_ =\n      initializeAlgorithm<::gloo::CudaAllreduceHalvingDoubling, ::gloo::float16>(\n        gpu_direct_,\n        init_.context,\n        init_.template getOutputs<::gloo::float16>(),\n        init_.size);\n  } else {\n    CAFFE_ENFORCE(false, \"Unhandled type: \", init_.meta.name());\n  }\n}\n\ntemplate <class Context>\nvoid AllreduceOp<Context>::initializeRingFull() {\n  if (init_.template IsType<float>()) {\n    algorithm_ =\n      initializeAlgorithm<::gloo::CudaAllreduceRing, float>(\n        gpu_direct_,\n        init_.context,\n        init_.template getOutputs<float>(),\n        init_.size);\n  } else if (init_.template IsType<float16>()) {\n    algorithm_ =\n      initializeAlgorithm<::gloo::CudaAllreduceRing, ::gloo::float16>(\n        gpu_direct_,\n        init_.context,\n        init_.template getOutputs<::gloo::float16>(),\n        init_.size);\n  } else {\n    CAFFE_ENFORCE(false, \"Unhandled type: \", init_.meta.name());\n  }\n}\n\ntemplate <class Context>\nvoid AllreduceOp<Context>::initializeRingChunked() {\n  if (init_.template IsType<float>()) {\n    algorithm_ =\n      initializeAlgorithm<::gloo::CudaAllreduceRingChunked, float>(\n        gpu_direct_,\n        init_.context,\n        init_.template getOutputs<float>(),\n        init_.size);\n  } else if (init_.template IsType<float16>()) {\n    algorithm_ =\n      initializeAlgorithm<::gloo::CudaAllreduceRingChunked, ::gloo::float16>(\n        gpu_direct_,\n        init_.context,\n        init_.template getOutputs<::gloo::float16>(),\n        init_.size);\n  } else {\n    CAFFE_ENFORCE(false, \"Unhandled type: \", init_.meta.name());\n  }\n}\n\nnamespace {\n\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(Allreduce, GLOO, AllreduceOp<CUDAContext>);\n\n} // namespace\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/barrier_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"barrier_ops.h\"\n\nnamespace caffe2 {\nnamespace gloo {\nnamespace {\n\nREGISTER_CPU_OPERATOR_WITH_ENGINE(Barrier, GLOO, BarrierOp<CPUContext>);\n\n} // namespace\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/barrier_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/contrib/gloo/common.h\"\n#include \"caffe2/core/operator.h\"\n\n#include <gloo/algorithm.h>\n#include <gloo/barrier_all_to_one.h>\n#include <gloo/common/error.h>\n#include <gloo/context.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\ntemplate <class Context>\nclass BarrierOp final : public Operator<Context> {\n public:\n  BarrierOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        ws_(ws),\n        status_blob_(\n            OperatorBase::GetSingleArgument<std::string>(\"status_blob\", \"\")) {\n    if (status_blob_ != \"\") {\n      ws_->CreateBlob(status_blob_);\n    }\n  }\n\n  virtual ~BarrierOp() {}\n\n  bool RunOnDevice() override {\n    auto context = OperatorBase::Input<std::shared_ptr<::gloo::Context>>(0);\n    std::call_once(once_, [&] {\n      initContext_ = context;\n      // Use an all-to-one barrier synchronizing against rank 0\n      algorithm_.reset(new ::gloo::BarrierAllToOne(initContext_, 0));\n    });\n\n    // If any parameter has changed in between runs, the initialized\n    // algorithm is invalid and cannot be used.\n    CAFFE_ENFORCE(context == initContext_, \"Context has changed\");\n\n    try {\n      algorithm_->run();\n    } catch (::gloo::IoException& ioe) {\n      LOG(ERROR) << \"Caught gloo IO exception: \" << ioe.what();\n      if (status_blob_ != \"\") {\n        signalFailure(ws_->GetBlob(status_blob_), ioe);\n        return false;\n      } else {\n        throw ioe;\n      }\n    }\n    return true;\n  }\n\n protected:\n  std::once_flag once_;\n  std::shared_ptr<::gloo::Context> initContext_;\n  std::unique_ptr<::gloo::Algorithm> algorithm_;\n  Workspace* ws_;\n  std::string status_blob_;\n};\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/broadcast_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"broadcast_ops.h\"\n\n#include <gloo/broadcast_one_to_all.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\ntemplate <class Context>\nvoid BroadcastOp<Context>::initializeAlgorithm() {\n  if (init_.template IsType<float>()) {\n    algorithm_.reset(new ::gloo::BroadcastOneToAll<float>(\n        init_.context, init_.template getOutputs<float>(), init_.size, root_));\n  } else if (init_.template IsType<long>()) {\n    algorithm_.reset(new ::gloo::BroadcastOneToAll<long>(\n        init_.context, init_.template getOutputs<long>(), init_.size, root_));\n  } else if (init_.template IsType<int>()) {\n    algorithm_.reset(new ::gloo::BroadcastOneToAll<int>(\n        init_.context, init_.template getOutputs<int>(), init_.size, root_));\n  } else if (init_.template IsType<float16>()) {\n    algorithm_.reset(new ::gloo::BroadcastOneToAll<::gloo::float16>(\n        init_.context,\n        init_.template getOutputs<::gloo::float16>(),\n        init_.size,\n        root_));\n  } else {\n    CAFFE_ENFORCE(false, \"Unhandled type: \", init_.meta.name());\n  }\n}\n\nnamespace {\n\nREGISTER_CPU_OPERATOR_WITH_ENGINE(Broadcast, GLOO, BroadcastOp<CPUContext>);\n\n} // namespace\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/broadcast_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <algorithm>\n\n#include \"caffe2/contrib/gloo/common.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/types.h\"\n\n#include <gloo/algorithm.h>\n#include <gloo/common/error.h>\n#include <gloo/context.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\ntemplate <class Context>\nclass BroadcastOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  BroadcastOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        root_(OperatorBase::template GetSingleArgument<int>(\"root\", 0)),\n        ws_(ws),\n        status_blob_(\n            OperatorBase::GetSingleArgument<std::string>(\"status_blob\", \"\")) {\n    if (status_blob_ != \"\") {\n      ws_->CreateBlob(status_blob_);\n    }\n  }\n\n  virtual ~BroadcastOp() {}\n\n  bool RunOnDevice() override {\n    std::call_once(once_, [&] { initialize(); });\n\n    // If any parameter has changed in between runs, the initialized\n    // algorithm is invalid and cannot be used.\n    update(current_);\n    CAFFE_ENFORCE(current_ == init_, \"Inputs/outputs have changed\");\n\n    try {\n      algorithm_->run();\n    } catch (::gloo::IoException& ioe) {\n      LOG(ERROR) << \"Caught gloo IO exception: \" << ioe.what();\n      if (status_blob_ != \"\") {\n        signalFailure(ws_->GetBlob(status_blob_), ioe);\n        return false;\n      } else {\n        throw ioe;\n      }\n    }\n    return true;\n  }\n\n protected:\n  void initialize() {\n    // Store which inputs/outputs this instance initialized with\n    update(init_);\n\n    // Verify inputs == ouputs\n    CAFFE_ENFORCE_EQ(init_.inputs.size(), init_.outputs.size());\n    for (auto i = 0; i < init_.inputs.size(); i++) {\n      CAFFE_ENFORCE_EQ(init_.inputs[i], init_.outputs[i]);\n    }\n\n    // Verify tensors all have same size\n    size_t size = Input(1).size();\n    for (auto i = 2; i < InputSize(); i++) {\n      CAFFE_ENFORCE_EQ(Input(i).size(), size);\n    }\n\n    // Verify tensors all have same size\n    TypeMeta meta = Input(1).meta();\n    for (auto i = 2; i < InputSize(); i++) {\n      CAFFE_ENFORCE(Input(i).meta() == meta);\n    }\n\n    // Finally initialize the algorithm\n    initializeAlgorithm();\n  }\n\n  void initializeAlgorithm();\n\n  const int root_;\n  std::once_flag once_;\n  std::unique_ptr<::gloo::Algorithm> algorithm_;\n\n  // Captures the parameters passed to Gloo when first initialized.\n  // An instance is updated every time this op runs and is compared\n  // to the reference instance for equality. If any parameter has\n  // changed from run to run, the initialized algorithm is invalid.\n  void update(GlooParameters& params) {\n    params.context = OperatorBase::Input<std::shared_ptr<::gloo::Context>>(0);\n    params.inputs.resize(InputSize() - 1);\n    params.outputs.resize(OutputSize());\n    for (auto i = 0; i < params.inputs.size(); i++) {\n      params.inputs[i] = Input(i + 1).template raw_data();\n      params.outputs[i] = Output(i)->template raw_mutable_data();\n    }\n    params.size = Output(0)->size();\n    params.meta = Output(0)->meta();\n  }\n\n  GlooParameters init_;\n  GlooParameters current_;\n  Workspace* ws_;\n  std::string status_blob_;\n};\n\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/broadcast_ops_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"broadcast_ops.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\n#include <gloo/cuda_broadcast_one_to_all.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\ntemplate <class Context>\nvoid BroadcastOp<Context>::initializeAlgorithm() {\n  if (init_.template IsType<float>()) {\n    algorithm_.reset(new ::gloo::CudaBroadcastOneToAll<float>(\n        init_.context, init_.template getOutputs<float>(), init_.size, root_));\n  } else if (init_.template IsType<long>()) {\n    algorithm_.reset(new ::gloo::CudaBroadcastOneToAll<long>(\n        init_.context, init_.template getOutputs<long>(), init_.size, root_));\n  } else if (init_.template IsType<int>()) {\n    algorithm_.reset(new ::gloo::CudaBroadcastOneToAll<int>(\n        init_.context, init_.template getOutputs<int>(), init_.size, root_));\n  } else if (init_.template IsType<float16>()) {\n    algorithm_.reset(new ::gloo::CudaBroadcastOneToAll<::gloo::float16>(\n        init_.context,\n        init_.template getOutputs<::gloo::float16>(),\n        init_.size,\n        root_));\n  } else {\n    CAFFE_ENFORCE(false, \"Unhandled type: \", init_.meta.name());\n  }\n}\n\nnamespace {\n\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(Broadcast, GLOO, BroadcastOp<CUDAContext>);\n\n} // namespace\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/common.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/contrib/gloo/common.h\"\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/tensor.h\"\n\n#include <gloo/transport/tcp/device.h>\n#if defined(GLOO_USE_IBVERBS) && GLOO_USE_IBVERBS\n#include <gloo/transport/ibverbs/device.h>\n#endif\n\nnamespace caffe2 {\nnamespace gloo {\n\nvoid signalFailure(Blob* status_blob, std::exception& /* unused */) {\n  auto* res = status_blob->GetMutable<TensorCPU>();\n  res->Resize(1);\n  res->template mutable_data<int32_t>()[0] = 1;\n}\n\nstd::shared_ptr<::gloo::transport::Device> createDevice(\n    const createDeviceAttr attr) {\n  if (attr.transport == \"tcp\") {\n    ::gloo::transport::tcp::attr tcpAttr;\n    if (attr.interface.size() > 0) {\n      tcpAttr.iface = attr.interface;\n    }\n    return ::gloo::transport::tcp::CreateDevice(tcpAttr);\n  } else if (attr.transport == \"ibverbs\") {\n#if defined(GLOO_USE_IBVERBS) && GLOO_USE_IBVERBS\n    ::gloo::transport::ibverbs::attr ibverbsAttr;\n    ibverbsAttr.port = 1;\n    ibverbsAttr.index = 0;\n    if (attr.interface.size() > 0) {\n      ibverbsAttr.name = attr.interface;\n    }\n    return ::gloo::transport::ibverbs::CreateDevice(ibverbsAttr);\n#else\n    CAFFE_THROW(\n      \"Gloo was not compiled with ibverbs support. \",\n      \"Please recompile with -DUSE_IBVERBS=1.\");\n#endif\n  }\n\n  CAFFE_THROW(\"Invalid transport: \", attr.transport);\n}\n\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/common.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <exception>\n\n#include \"caffe2/core/blob.h\"\n\n#include <gloo/config.h>\n#include <gloo/context.h>\n#include <gloo/transport/device.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\nvoid signalFailure(Blob* status_blob, std::exception& exception);\n\nstruct createDeviceAttr {\n    // \"tcp\" or \"ibverbs\"\n    std::string transport;\n\n    // E.g. \"eth0\" (tcp), or \"mlx5_0\" (ibverbs).\n    // This may be empty to make Gloo figure it out.\n    std::string interface;\n};\n\nstd::shared_ptr<::gloo::transport::Device> createDevice(\n    const createDeviceAttr attr);\n\n// Captures the parameters passed to Gloo.\nstruct GlooParameters {\n  std::shared_ptr<::gloo::Context> context;\n  std::vector<const void*> inputs;\n  std::vector<void*> outputs;\n  size_t size;\n  TypeMeta meta;\n\n  template <typename T>\n  std::vector<const T*> getInputs() {\n    std::vector<const T*> result;\n    result.reserve(inputs.size());\n    for (auto& input : inputs) {\n      result.push_back(reinterpret_cast<const T*>(input));\n    }\n    return result;\n  }\n\n  template <typename T>\n  std::vector<T*> getOutputs() {\n    std::vector<T*> result;\n    result.reserve(outputs.size());\n    for (auto& output : outputs) {\n      result.push_back(reinterpret_cast<T*>(output));\n    }\n    return result;\n  }\n\n  template <typename T>\n  T* getOutput() {\n    return reinterpret_cast<T*>(outputs[0]);\n  }\n\n  template <typename T>\n  bool IsType() const {\n    return meta.Match<T>();\n  }\n\n  bool operator==(GlooParameters const& other) const {\n    return context == other.context && inputs == other.inputs &&\n        outputs == other.outputs && size == other.size;\n  }\n};\n\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/common_world_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/contrib/gloo/common_world_ops.h\"\n\n#include <gloo/transport/tcp/device.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\ntemplate <>\nvoid CreateCommonWorld<CPUContext>::initializeForContext() {\n  // Nothing to initialize for CPUContext.\n}\n\nnamespace {\n\nREGISTER_CPU_OPERATOR_WITH_ENGINE(\n    CreateCommonWorld,\n    GLOO,\n    CreateCommonWorld<CPUContext>);\n\nREGISTER_CPU_OPERATOR_WITH_ENGINE(\n    CloneCommonWorld,\n    GLOO,\n    CloneCommonWorld<CPUContext>);\n\nREGISTER_CPU_OPERATOR_WITH_ENGINE(DestroyCommonWorld, GLOO, DestroyCommonWorld);\n\n} // namespace\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/common_world_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/contrib/gloo/common.h\"\n#include \"caffe2/contrib/gloo/store_handler.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/distributed/store_handler.h\"\n\n#include <gloo/common/error.h>\n#include <gloo/config.h>\n#include <gloo/rendezvous/context.h>\n#include <gloo/rendezvous/prefix_store.h>\n\n#if defined(GLOO_USE_MPI) && GLOO_USE_MPI\n#include <gloo/mpi/context.h>\n#endif\n\nnamespace caffe2 {\nnamespace gloo {\n\ntemplate <class Context>\nclass CreateCommonWorld final : public Operator<Context> {\n public:\n  using CommonWorld = std::shared_ptr<::gloo::Context>;\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  CreateCommonWorld(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        size_(OperatorBase::template GetSingleArgument<int>(\"size\", 0)),\n        rank_(OperatorBase::template GetSingleArgument<int>(\"rank\", 0)),\n        sync_(OperatorBase::template GetSingleArgument<bool>(\"sync\", false)),\n        transport_(OperatorBase::template GetSingleArgument<std::string>(\n                       \"transport\", \"tcp\")),\n        interface_(OperatorBase::template GetSingleArgument<std::string>(\n                       \"interface\", \"\")),\n        mpi_rendezvous_(OperatorBase::template GetSingleArgument<bool>(\n                       \"mpi_rendezvous\", false)),\n        status_blob_(\n            OperatorBase::GetSingleArgument<std::string>(\"status_blob\", \"\")),\n        timeout_ms_(OperatorBase::GetSingleArgument<int>(\"timeout_ms\", -1)),\n        ws_(ws) {\n    CAFFE_ENFORCE(\n        operator_def.has_name(), \"CreateCommonWorld operator requires name\");\n    CAFFE_ENFORCE(rank_ >= 0 && rank_ < size_);\n    name_ = operator_def.name();\n    if (status_blob_ != \"\") {\n      ws_->CreateBlob(status_blob_);\n    }\n    initialize();\n  }\n\n  virtual ~CreateCommonWorld() {\n  }\n\n  CommonWorld rendezvousWithMPI() {\n#if defined(GLOO_USE_MPI) && GLOO_USE_MPI\n    auto context = ::gloo::mpi::Context::createManaged();\n    if (timeout_ms_ != -1) {\n      context->setTimeout(std::chrono::milliseconds(timeout_ms_));\n    }\n    context->connectFullMesh(device_);\n    return context;\n#else\n    CAFFE_THROW(\n      \"Gloo was not compiled with MPI support. \",\n      \"Please recompile with -DUSE_MPI=1.\");\n#endif\n  }\n\n  CommonWorld rendezvousWithStore(\n      const std::unique_ptr<StoreHandler>& handler) {\n    // Use PrefixStore to isolate different CreateCommonWorld instances\n    StoreHandlerWrapper wrapper(*handler);\n    ::gloo::rendezvous::PrefixStore store(name_, wrapper);\n    auto context = std::make_shared<::gloo::rendezvous::Context>(rank_, size_);\n    if (timeout_ms_ != -1) {\n      context->setTimeout(std::chrono::milliseconds(timeout_ms_));\n    }\n    context->connectFullMesh(store, device_);\n    return context;\n  }\n\n  bool RunOnDevice() override {\n    try {\n      CommonWorld context;\n      if (mpi_rendezvous_) {\n        context = rendezvousWithMPI();\n      } else {\n        CAFFE_ENFORCE_EQ(InputSize(), 1, \"Expected store handler input\");\n        const auto& handler =\n            OperatorBase::Input<std::unique_ptr<StoreHandler>>(STORE_HANDLER);\n        context = rendezvousWithStore(handler);\n      }\n\n      // Switch pairs to synchronous mode if configured to do so\n      if (sync_) {\n        for (int i = 0; i < context->size; i++) {\n          auto& pair = context->getPair(i);\n          if (pair) {\n            pair->setSync(true, false);\n          }\n        }\n      }\n\n      *OperatorBase::Output<CommonWorld>(COMM) = std::move(context);\n    } catch (::gloo::IoException& ioe) {\n      LOG(ERROR) << \"Caught gloo IO exception: \" << ioe.what();\n      return handleException(ioe);\n    } catch (::caffe2::StoreHandlerTimeoutException& te) {\n      LOG(ERROR) << \"Caught store handler timeout exception: \" << te.what();\n      return handleException(te);\n    }\n    return true;\n  }\n\n private:\n  bool handleException(std::exception& ex) {\n    if (status_blob_ != \"\") {\n      signalFailure(ws_->GetBlob(status_blob_), ex);\n      return false;\n    } else {\n      throw ex;\n    }\n  }\n\n  void initialize() {\n    // Share single device between all common worlds.\n    static std::once_flag once;\n    static std::shared_ptr<::gloo::transport::Device> device;\n    std::call_once(once, [&]() {\n        createDeviceAttr attr;\n        attr.transport = transport_;\n        attr.interface = interface_;\n        device = createDevice(attr);\n      });\n    device_ = device;\n\n    // Context specific initialization.\n    initializeForContext();\n  }\n\n  void initializeForContext();\n\n  const int size_;\n  const int rank_;\n  const bool sync_;\n  const std::string transport_;\n  const std::string interface_;\n  const bool mpi_rendezvous_;\n  const std::string status_blob_;\n  const int timeout_ms_;\n  Workspace* ws_;\n\n  std::string name_;\n  std::shared_ptr<::gloo::transport::Device> device_;\n\n  INPUT_TAGS(STORE_HANDLER);\n  OUTPUT_TAGS(COMM);\n};\n\ntemplate <class Context>\nclass CloneCommonWorld final : public Operator<Context> {\n public:\n  using CommonWorld = std::shared_ptr<::gloo::Context>;\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  CloneCommonWorld(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        sync_(OperatorBase::template GetSingleArgument<bool>(\"sync\", false)),\n        ws_(ws),\n        status_blob_(\n            OperatorBase::GetSingleArgument<std::string>(\"status_blob\", \"\")) {\n    if (status_blob_ != \"\") {\n      ws_->CreateBlob(status_blob_);\n    }\n  }\n\n  virtual ~CloneCommonWorld() {}\n\n  bool RunOnDevice() override {\n    try {\n      auto existing = OperatorBase::Input<CommonWorld>(EXISTING_COMM);\n      ::gloo::rendezvous::ContextFactory factory(existing);\n      auto clone = factory.makeContext(existing->getDevice());\n\n      // Switch pairs to synchronous mode if configured to do so\n      if (sync_) {\n        for (int i = 0; i < clone->size; i++) {\n          auto& pair = clone->getPair(i);\n          if (pair) {\n            pair->setSync(true, false);\n          }\n        }\n      }\n\n      *OperatorBase::Output<CommonWorld>(CLONED_COMM) = std::move(clone);\n    } catch (::gloo::IoException& ioe) {\n      LOG(ERROR) << \"Caught gloo IO exception: \" << ioe.what();\n      return handleException(ioe);\n    }\n    return true;\n  }\n\n private:\n  bool handleException(std::exception& ex) {\n    if (status_blob_ != \"\") {\n      signalFailure(ws_->GetBlob(status_blob_), ex);\n      return false;\n    } else {\n      throw ex;\n    }\n  }\n\n  const bool sync_;\n  Workspace* ws_;\n  std::string status_blob_;\n\n  INPUT_TAGS(EXISTING_COMM);\n  OUTPUT_TAGS(CLONED_COMM);\n};\n\nclass DestroyCommonWorld final : public Operator<CPUContext> {\n public:\n  DestroyCommonWorld(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {\n    cw_name_ = operator_def.input(0);\n  }\n\n  bool RunOnDevice() override {\n    if (OperatorBase::InputBlob(0).GetRaw() == nullptr) {\n      return true;\n    }\n    const auto& context =\n        OperatorBase::Input<std::shared_ptr<::gloo::Context>>(0);\n\n    if (context) {\n      LOG(INFO) << \"Closing connections: \" << cw_name_;\n      context->closeConnections();\n    }\n    return true;\n  }\n\n private:\n  std::string cw_name_;\n};\n\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/common_world_ops_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/contrib/gloo/common_world_ops.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\n#include <gloo/cuda.h>\n#include <gloo/transport/tcp/device.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\ntemplate <>\nvoid CreateCommonWorld<CUDAContext>::initializeForContext() {\n  static std::once_flag once;\n  std::call_once(once, [&]() {\n      // This is the first time we call Gloo code for a CUDAContext.\n      // Share Caffe2 CUDA mutex with Gloo.\n      ::gloo::CudaShared::setMutex(&CUDAContext::mutex());\n    });\n}\n\nnamespace {\n\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(\n    CreateCommonWorld,\n    GLOO,\n    CreateCommonWorld<CUDAContext>);\n\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(\n    CloneCommonWorld,\n    GLOO,\n    CloneCommonWorld<CUDAContext>);\n\n} // namespace\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/context.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"context.h\"\n\n#include \"caffe2/core/typeid.h\"\n\n#include <gloo/types.h>\n\nnamespace caffe2 {\n\nCAFFE_KNOWN_TYPE(::gloo::float16);\nCAFFE_KNOWN_TYPE(std::shared_ptr<::gloo::Context>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/context.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <gloo/context.h>\n"
  },
  {
    "path": "caffe2/contrib/gloo/gloo_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n#!/usr/bin/env python\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\nfrom multiprocessing import Process, Queue\n\nimport numpy as np\nimport os\nimport pickle\nimport tempfile\nimport shutil\n\nfrom caffe2.python import core, workspace, dyndep\nimport caffe2.python.hypothesis_test_util as hu\nfrom gloo.python import IoError\n\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/distributed:file_store_handler_ops\")\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/distributed:redis_store_handler_ops\")\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/distributed:store_ops\")\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/contrib/gloo:gloo_ops\")\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/contrib/gloo:gloo_ops_gpu\")\n\nop_engine = 'GLOO'\n\n\nclass TemporaryDirectory:\n    def __enter__(self):\n        self.tmpdir = tempfile.mkdtemp()\n        return self.tmpdir\n\n    def __exit__(self, type, value, traceback):\n        shutil.rmtree(self.tmpdir)\n\n\nclass TestCase(hu.HypothesisTestCase):\n    test_counter = 0\n    sync_counter = 0\n\n    def run_test_locally(self, fn, device_option=None, **kwargs):\n        # Queue for assertion errors on subprocesses\n        queue = Queue()\n\n        # Capture any exception thrown by the subprocess\n        def run_fn(*args, **kwargs):\n            try:\n                with core.DeviceScope(device_option):\n                    fn(*args, **kwargs)\n                    workspace.ResetWorkspace()\n                    queue.put(True)\n            except Exception as ex:\n                queue.put(ex)\n\n        # Start N processes in the background\n        procs = []\n        for i in range(kwargs['comm_size']):\n            kwargs['comm_rank'] = i\n            proc = Process(\n                target=run_fn,\n                kwargs=kwargs)\n            proc.start()\n            procs.append(proc)\n\n        # Test complete, join background processes\n        while len(procs) > 0:\n            proc = procs.pop(0)\n            while proc.is_alive():\n                proc.join(10)\n\n            # Raise exception if we find any. Otherwise each worker\n            # should put a True into the queue\n            # Note that the following is executed ALSO after\n            # the last process was joined, so if ANY exception\n            # was raised, it will be re-raised here.\n            self.assertFalse(queue.empty(), \"Job failed without a result\")\n            o = queue.get()\n            if isinstance(o, Exception):\n                raise o\n            else:\n                self.assertTrue(o)\n\n    def run_test_distributed(self, fn, device_option=None, **kwargs):\n        comm_rank = os.getenv('COMM_RANK')\n        self.assertIsNotNone(comm_rank)\n        comm_size = os.getenv('COMM_SIZE')\n        self.assertIsNotNone(comm_size)\n        kwargs['comm_rank'] = int(comm_rank)\n        kwargs['comm_size'] = int(comm_size)\n        with core.DeviceScope(device_option):\n            fn(**kwargs)\n            workspace.ResetWorkspace()\n\n    def create_common_world(self, comm_rank, comm_size, tmpdir=None, existing_cw=None):\n        store_handler = \"store_handler\"\n\n        # If REDIS_HOST is set, use RedisStoreHandler for rendezvous.\n        if existing_cw is None:\n            redis_host = os.getenv(\"REDIS_HOST\")\n            redis_port = int(os.getenv(\"REDIS_PORT\", 6379))\n            if redis_host is not None:\n                workspace.RunOperatorOnce(\n                    core.CreateOperator(\n                        \"RedisStoreHandlerCreate\",\n                        [],\n                        [store_handler],\n                        prefix=str(TestCase.test_counter) + \"/\",\n                        host=redis_host,\n                        port=redis_port))\n            else:\n                workspace.RunOperatorOnce(\n                    core.CreateOperator(\n                        \"FileStoreHandlerCreate\",\n                        [],\n                        [store_handler],\n                        path=tmpdir))\n            common_world = \"common_world\"\n        else:\n            common_world = str(existing_cw) + \".forked\"\n\n        inputs = [store_handler]\n        if existing_cw is not None:\n            inputs.append(existing_cw)\n        workspace.RunOperatorOnce(\n            core.CreateOperator(\n                \"CreateCommonWorld\",\n                inputs,\n                [common_world],\n                size=comm_size,\n                rank=comm_rank,\n                sync=True,\n                engine=op_engine))\n        return (store_handler, common_world)\n\n    def synchronize(self, store_handler, value, comm_rank=None):\n        TestCase.sync_counter += 1\n        blob = \"sync_{}\".format(TestCase.sync_counter)\n        if comm_rank == 0:\n            workspace.FeedBlob(blob, pickle.dumps(value))\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"StoreSet\",\n                    [store_handler, blob],\n                    []))\n        else:\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"StoreGet\",\n                    [store_handler],\n                    [blob]))\n        return pickle.loads(workspace.FetchBlob(blob))\n\n    def _test_broadcast(self,\n                        comm_rank=None,\n                        comm_size=None,\n                        blob_size=None,\n                        num_blobs=None,\n                        tmpdir=None,\n                        use_float16=False,\n                        ):\n        store_handler, common_world = self.create_common_world(\n            comm_rank=comm_rank,\n            comm_size=comm_size,\n            tmpdir=tmpdir)\n\n        blob_size = self.synchronize(\n            store_handler,\n            blob_size,\n            comm_rank=comm_rank)\n\n        num_blobs = self.synchronize(\n            store_handler,\n            num_blobs,\n            comm_rank=comm_rank)\n\n        for i in range(comm_size):\n            blobs = []\n            for j in range(num_blobs):\n                blob = \"blob_{}\".format(j)\n                offset = (comm_rank * num_blobs) + j\n                value = np.full(blob_size, offset,\n                                np.float16 if use_float16 else np.float32)\n                workspace.FeedBlob(blob, value)\n                blobs.append(blob)\n\n            net = core.Net(\"broadcast\")\n            net.Broadcast(\n                [common_world] + blobs,\n                blobs,\n                root=i,\n                engine=op_engine)\n\n            workspace.CreateNet(net)\n            workspace.RunNet(net.Name())\n\n            for j in range(num_blobs):\n                np.testing.assert_array_equal(\n                    workspace.FetchBlob(blobs[j]),\n                    i * num_blobs)\n\n            # Run the net a few more times to check the operator\n            # works not just the first time it's called\n            for _tmp in range(4):\n                workspace.RunNet(net.Name())\n\n    @given(comm_size=st.integers(min_value=2, max_value=8),\n           blob_size=st.integers(min_value=1e3, max_value=1e6),\n           num_blobs=st.integers(min_value=1, max_value=4),\n           device_option=st.sampled_from([hu.cpu_do]),\n           use_float16=st.booleans())\n    def test_broadcast(self, comm_size, blob_size, num_blobs, device_option,\n                       use_float16):\n        TestCase.test_counter += 1\n        if os.getenv('COMM_RANK') is not None:\n            self.run_test_distributed(\n                self._test_broadcast,\n                blob_size=blob_size,\n                num_blobs=num_blobs,\n                use_float16=use_float16,\n                device_option=device_option)\n        else:\n            with TemporaryDirectory() as tmpdir:\n                self.run_test_locally(\n                    self._test_broadcast,\n                    comm_size=comm_size,\n                    blob_size=blob_size,\n                    num_blobs=num_blobs,\n                    device_option=device_option,\n                    tmpdir=tmpdir,\n                    use_float16=use_float16)\n\n    def _test_allreduce(self,\n                        comm_rank=None,\n                        comm_size=None,\n                        blob_size=None,\n                        num_blobs=None,\n                        tmpdir=None,\n                        use_float16=False\n                        ):\n        store_handler, common_world = self.create_common_world(\n            comm_rank=comm_rank,\n            comm_size=comm_size,\n            tmpdir=tmpdir)\n\n        blob_size = self.synchronize(\n            store_handler,\n            blob_size,\n            comm_rank=comm_rank)\n\n        num_blobs = self.synchronize(\n            store_handler,\n            num_blobs,\n            comm_rank=comm_rank)\n\n        blobs = []\n        for i in range(num_blobs):\n            blob = \"blob_{}\".format(i)\n            value = np.full(blob_size, (comm_rank * num_blobs) + i,\n                            np.float16 if use_float16 else np.float32)\n            workspace.FeedBlob(blob, value)\n            blobs.append(blob)\n\n        net = core.Net(\"allreduce\")\n        net.Allreduce(\n            [common_world] + blobs,\n            blobs,\n            engine=op_engine)\n\n        workspace.CreateNet(net)\n        workspace.RunNet(net.Name())\n\n        for i in range(num_blobs):\n            np.testing.assert_array_equal(\n                workspace.FetchBlob(blobs[i]),\n                (num_blobs * comm_size) * (num_blobs * comm_size - 1) / 2)\n\n        # Run the net a few more times to check the operator\n        # works not just the first time it's called\n        for _tmp in range(4):\n            workspace.RunNet(net.Name())\n\n    def _test_allreduce_multicw(self,\n                                comm_rank=None,\n                                comm_size=None,\n                                tmpdir=None\n                                ):\n        _store_handler, common_world = self.create_common_world(\n            comm_rank=comm_rank,\n            comm_size=comm_size,\n            tmpdir=tmpdir)\n\n        _, common_world2 = self.create_common_world(\n            comm_rank=comm_rank,\n            comm_size=comm_size,\n            tmpdir=tmpdir,\n            existing_cw=common_world)\n\n        blob_size = 1e4\n        num_blobs = 4\n\n        for cw in [common_world, common_world2]:\n            blobs = []\n            for i in range(num_blobs):\n                blob = \"blob_{}\".format(i)\n                value = np.full(blob_size, (comm_rank * num_blobs) + i, np.float32)\n                workspace.FeedBlob(blob, value)\n                blobs.append(blob)\n\n            net = core.Net(\"allreduce_multicw\")\n            net.Allreduce(\n                [cw] + blobs,\n                blobs,\n                engine=op_engine)\n\n            workspace.RunNetOnce(net)\n            for i in range(num_blobs):\n                np.testing.assert_array_equal(\n                    workspace.FetchBlob(blobs[i]),\n                    (num_blobs * comm_size) * (num_blobs * comm_size - 1) / 2)\n\n    @given(comm_size=st.integers(min_value=2, max_value=8),\n           blob_size=st.integers(min_value=1e3, max_value=1e6),\n           num_blobs=st.integers(min_value=1, max_value=4),\n           device_option=st.sampled_from([hu.cpu_do]),\n           use_float16=st.booleans())\n    def test_allreduce(self, comm_size, blob_size, num_blobs, device_option,\n                       use_float16):\n        TestCase.test_counter += 1\n        if os.getenv('COMM_RANK') is not None:\n            self.run_test_distributed(\n                self._test_allreduce,\n                blob_size=blob_size,\n                num_blobs=num_blobs,\n                use_float16=use_float16,\n                device_option=device_option)\n        else:\n            with TemporaryDirectory() as tmpdir:\n                self.run_test_locally(\n                    self._test_allreduce,\n                    comm_size=comm_size,\n                    blob_size=blob_size,\n                    num_blobs=num_blobs,\n                    device_option=device_option,\n                    tmpdir=tmpdir,\n                    use_float16=use_float16)\n\n    def _test_reduce_scatter(self,\n                             comm_rank=None,\n                             comm_size=None,\n                             blob_size=None,\n                             num_blobs=None,\n                             tmpdir=None,\n                             use_float16=False\n                             ):\n        store_handler, common_world = self.create_common_world(\n            comm_rank=comm_rank,\n            comm_size=comm_size,\n            tmpdir=tmpdir)\n\n        blob_size = self.synchronize(\n            store_handler,\n            blob_size,\n            comm_rank=comm_rank)\n\n        num_blobs = self.synchronize(\n            store_handler,\n            num_blobs,\n            comm_rank=comm_rank)\n\n        blobs = []\n        for i in range(num_blobs):\n            blob = \"blob_{}\".format(i)\n            value = np.full(blob_size, (comm_rank * num_blobs) + i,\n                            np.float16 if use_float16 else np.float32)\n            workspace.FeedBlob(blob, value)\n            blobs.append(blob)\n\n        # Specify distribution among ranks i.e. number of elements\n        # scattered/distributed to each process.\n        recv_counts = np.zeros(comm_size, dtype=np.int32)\n        remaining = blob_size\n        chunk_size = (blob_size + comm_size - 1) / comm_size\n        for i in range(comm_size):\n            recv_counts[i] = min(chunk_size, remaining)\n            remaining = remaining - chunk_size if remaining > chunk_size else 0\n        recv_counts_blob = \"recvCounts\"\n        workspace.FeedBlob(recv_counts_blob, recv_counts)\n        blobs.append(recv_counts_blob)\n\n        net = core.Net(\"reduce_scatter\")\n        net.ReduceScatter(\n            [common_world] + blobs,\n            blobs,\n            engine=op_engine)\n\n        workspace.CreateNet(net)\n        workspace.RunNet(net.Name())\n\n        for i in range(num_blobs):\n            np.testing.assert_array_equal(\n                np.resize(workspace.FetchBlob(blobs[i]), recv_counts[comm_rank]),\n                (num_blobs * comm_size) * (num_blobs * comm_size - 1) / 2)\n\n        # Run the net a few more times to check the operator\n        # works not just the first time it's called\n        for _tmp in range(4):\n            workspace.RunNet(net.Name())\n\n    @given(comm_size=st.integers(min_value=2, max_value=8),\n           blob_size=st.integers(min_value=1e3, max_value=1e6),\n           num_blobs=st.integers(min_value=1, max_value=4),\n           device_option=st.sampled_from([hu.cpu_do]),\n           use_float16=st.booleans())\n    def test_reduce_scatter(self, comm_size, blob_size, num_blobs,\n                            device_option, use_float16):\n        TestCase.test_counter += 1\n        if os.getenv('COMM_RANK') is not None:\n            self.run_test_distributed(\n                self._test_reduce_scatter,\n                blob_size=blob_size,\n                num_blobs=num_blobs,\n                use_float16=use_float16,\n                device_option=device_option)\n        else:\n            with TemporaryDirectory() as tmpdir:\n                self.run_test_locally(\n                    self._test_reduce_scatter,\n                    comm_size=comm_size,\n                    blob_size=blob_size,\n                    num_blobs=num_blobs,\n                    device_option=device_option,\n                    tmpdir=tmpdir,\n                    use_float16=use_float16)\n\n    def _test_allgather(self,\n                        comm_rank=None,\n                        comm_size=None,\n                        blob_size=None,\n                        num_blobs=None,\n                        tmpdir=None,\n                        use_float16=False\n                        ):\n        store_handler, common_world = self.create_common_world(\n            comm_rank=comm_rank,\n            comm_size=comm_size,\n            tmpdir=tmpdir)\n\n        blob_size = self.synchronize(\n            store_handler,\n            blob_size,\n            comm_rank=comm_rank)\n\n        num_blobs = self.synchronize(\n            store_handler,\n            num_blobs,\n            comm_rank=comm_rank)\n\n        blobs = []\n        for i in range(num_blobs):\n            blob = \"blob_{}\".format(i)\n            value = np.full(blob_size, (comm_rank * num_blobs) + i,\n                            np.float16 if use_float16 else np.float32)\n            workspace.FeedBlob(blob, value)\n            blobs.append(blob)\n\n        net = core.Net(\"allgather\")\n        net.Allgather(\n            [common_world] + blobs,\n            [\"Gathered\"],\n            engine=op_engine)\n\n        workspace.CreateNet(net)\n        workspace.RunNet(net.Name())\n        # create expected output\n        expected_output = np.array([])\n        for i in range(comm_size):\n            for j in range(num_blobs):\n                value = np.full(blob_size, (i * num_blobs) + j,\n                                np.float16 if use_float16 else np.float32)\n                expected_output = np.concatenate((expected_output, value))\n        np.testing.assert_array_equal(\n            workspace.FetchBlob(\"Gathered\"), expected_output)\n\n        # Run the net a few more times to check the operator\n        # works not just the first time it's called\n        for _tmp in range(4):\n            workspace.RunNet(net.Name())\n\n    @given(comm_size=st.integers(min_value=2, max_value=8),\n           blob_size=st.integers(min_value=1e3, max_value=1e6),\n           num_blobs=st.integers(min_value=1, max_value=4),\n           device_option=st.sampled_from([hu.cpu_do]),\n           use_float16=st.booleans())\n    def test_allgather(self, comm_size, blob_size, num_blobs, device_option,\n                       use_float16):\n        TestCase.test_counter += 1\n        if os.getenv('COMM_RANK') is not None:\n            self.run_test_distributed(\n                self._test_allgather,\n                blob_size=blob_size,\n                num_blobs=num_blobs,\n                use_float16=use_float16,\n                device_option=device_option)\n        else:\n            with TemporaryDirectory() as tmpdir:\n                self.run_test_locally(\n                    self._test_allgather,\n                    comm_size=comm_size,\n                    blob_size=blob_size,\n                    num_blobs=num_blobs,\n                    device_option=device_option,\n                    tmpdir=tmpdir,\n                    use_float16=use_float16)\n\n    @given(device_option=st.sampled_from([hu.cpu_do]))\n    def test_forked_cw(self, device_option):\n        TestCase.test_counter += 1\n        if os.getenv('COMM_RANK') is not None:\n            self.run_test_distributed(\n                self._test_allreduce_multicw,\n                device_option=device_option)\n        else:\n            with TemporaryDirectory() as tmpdir:\n                self.run_test_locally(\n                    self._test_allreduce_multicw,\n                    comm_size=8,\n                    device_option=device_option,\n                    tmpdir=tmpdir)\n\n    def _test_barrier(\n        self,\n        comm_rank=None,\n        comm_size=None,\n        tmpdir=None,\n    ):\n        store_handler, common_world = self.create_common_world(\n            comm_rank=comm_rank, comm_size=comm_size, tmpdir=tmpdir\n        )\n\n        net = core.Net(\"barrier\")\n        net.Barrier(\n            [common_world],\n            [],\n            engine=op_engine)\n\n        workspace.CreateNet(net)\n        workspace.RunNet(net.Name())\n\n        # Run the net a few more times to check the operator\n        # works not just the first time it's called\n        for _tmp in range(4):\n            workspace.RunNet(net.Name())\n\n    @given(comm_size=st.integers(min_value=2, max_value=8),\n           device_option=st.sampled_from([hu.cpu_do]))\n    def test_barrier(self, comm_size, device_option):\n        TestCase.test_counter += 1\n        if os.getenv('COMM_RANK') is not None:\n            self.run_test_distributed(\n                self._test_barrier,\n                device_option=device_option)\n        else:\n            with TemporaryDirectory() as tmpdir:\n                self.run_test_locally(\n                    self._test_barrier,\n                    comm_size=comm_size,\n                    device_option=device_option,\n                    tmpdir=tmpdir)\n\n    def _test_close_connection(\n        self,\n        comm_rank=None,\n        comm_size=None,\n        tmpdir=None,\n    ):\n        '''\n        One node calls close connection, others wait it on barrier.\n        Test will check that all will exit eventually.\n        '''\n        # Caffe's for closers only:\n        # https://www.youtube.com/watch?v=QMFwFgG9NE8\n        closer = comm_rank == comm_size // 2,\n\n        store_handler, common_world = self.create_common_world(\n            comm_rank=comm_rank, comm_size=comm_size, tmpdir=tmpdir\n        )\n\n        net = core.Net(\"barrier_or_close\")\n        if not closer:\n            net.Barrier(\n                [common_world],\n                [],\n                engine=op_engine)\n        else:\n            net.DestroyCommonWorld(\n                [common_world], [common_world], engine=op_engine)\n            # Sleep a bit to ensure others start the barrier\n            import time\n            time.sleep(0.1)\n\n        workspace.CreateNet(net)\n        workspace.RunNet(net.Name())\n\n    @given(comm_size=st.integers(min_value=2, max_value=8),\n           device_option=st.sampled_from([hu.cpu_do]))\n    def test_close_connection(self, comm_size, device_option):\n        import time\n        start_time = time.time()\n        TestCase.test_counter += 1\n        if os.getenv('COMM_RANK') is not None:\n            self.run_test_distributed(\n                self._test_close_connection,\n                device_option=device_option)\n        else:\n            with TemporaryDirectory() as tmpdir:\n                self.run_test_locally(\n                    self._test_close_connection,\n                    comm_size=comm_size,\n                    device_option=device_option,\n                    tmpdir=tmpdir)\n        # Check that test finishes quickly because connections get closed\n        self.assertLess(time.time() - start_time, 2.0)\n\n    def _test_io_error(\n        self,\n        comm_rank=None,\n        comm_size=None,\n        tmpdir=None,\n    ):\n        '''\n        Only one node will participate in allreduce, resulting in an IoError\n        '''\n        store_handler, common_world = self.create_common_world(\n            comm_rank=comm_rank,\n            comm_size=comm_size,\n            tmpdir=tmpdir)\n\n        if comm_rank == 0:\n            blob_size = 1000\n            num_blobs = 1\n\n            blobs = []\n            for i in range(num_blobs):\n                blob = \"blob_{}\".format(i)\n                value = np.full(\n                    blob_size, (comm_rank * num_blobs) + i, np.float32\n                )\n                workspace.FeedBlob(blob, value)\n                blobs.append(blob)\n\n            net = core.Net(\"allreduce\")\n            net.Allreduce(\n                [common_world] + blobs,\n                blobs,\n                engine=op_engine)\n\n            workspace.CreateNet(net)\n            workspace.RunNet(net.Name())\n\n    @given(comm_size=st.integers(min_value=2, max_value=8),\n           device_option=st.sampled_from([hu.cpu_do]))\n    def test_io_error(self, comm_size, device_option):\n        TestCase.test_counter += 1\n        with self.assertRaises(IoError):\n            if os.getenv('COMM_RANK') is not None:\n                self.run_test_distributed(\n                    self._test_io_error,\n                    device_option=device_option)\n            else:\n                with TemporaryDirectory() as tmpdir:\n                    self.run_test_locally(\n                        self._test_io_error,\n                        comm_size=comm_size,\n                        device_option=device_option,\n                        tmpdir=tmpdir)\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/contrib/gloo/py_export.cc",
    "content": "#include <gloo/common/error.h>\n#include <pybind11/pybind11.h>\n\nnamespace gloo {\nnamespace python {\n\nnamespace py = pybind11;\n\nPYBIND11_MODULE(python, m) {\n  m.doc() = \"Python interface for Gloo\";\n  py::register_exception<IoException>(m, \"IoError\");\n}\n\n} // namespace python\n} // namespace gloo\n"
  },
  {
    "path": "caffe2/contrib/gloo/reduce_scatter_ops.cc",
    "content": "/**\n * Copyright (c) 2018-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"reduce_scatter_ops.h\"\n\n#include <gloo/reduce_scatter.h>\n#include <gloo/types.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\ntemplate <class Context>\nvoid ReduceScatterOp<Context>::initializeHalvingDoubling() {\n  if (init_.template IsType<float>()) {\n    algorithm_.reset(new ::gloo::ReduceScatterHalvingDoubling<float>(\n        init_.context,\n        init_.template getOutputs<float>(),\n        init_.size,\n        recvCounts_));\n  } else if (init_.template IsType<::caffe2::float16>()) {\n    algorithm_.reset(new ::gloo::ReduceScatterHalvingDoubling<::gloo::float16>(\n        init_.context,\n        init_.template getOutputs<::gloo::float16>(),\n        init_.size,\n        recvCounts_));\n  } else {\n    CAFFE_ENFORCE(false, \"Unhandled type: \", init_.meta.name());\n  }\n}\n\nnamespace {\n\nREGISTER_CPU_OPERATOR_WITH_ENGINE(\n    ReduceScatter,\n    GLOO,\n    ReduceScatterOp<CPUContext>);\n\n} // namespace\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/reduce_scatter_ops.h",
    "content": "/**\n * Copyright (c) 2018-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <algorithm>\n\n#include \"caffe2/contrib/gloo/common.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\n#include <gloo/algorithm.h>\n#include <gloo/common/error.h>\n#include <gloo/context.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\ntemplate <class Context>\nclass ReduceScatterOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  ReduceScatterOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        ws_(ws),\n        status_blob_(\n            OperatorBase::GetSingleArgument<std::string>(\"status_blob\", \"\")) {\n    if (status_blob_ != \"\") {\n      ws_->CreateBlob(status_blob_);\n    }\n  }\n\n  virtual ~ReduceScatterOp() {}\n\n  bool RunOnDevice() override {\n    std::call_once(once_, [&] { initialize(); });\n\n    // If any parameter has changed in between runs, the initialized\n    // algorithm is invalid and cannot be used.\n    update(current_);\n    CAFFE_ENFORCE(current_ == init_, \"Inputs/outputs have changed\");\n\n    try {\n      algorithm_->run();\n    } catch (::gloo::IoException& ioe) {\n      LOG(ERROR) << \"Caught gloo IO exception: \" << ioe.what();\n      if (status_blob_ != \"\") {\n        signalFailure(ws_->GetBlob(status_blob_), ioe);\n        return false;\n      } else {\n        throw ioe;\n      }\n    }\n    return true;\n  }\n\n protected:\n  void initialize() {\n    // Store which inputs/outputs this instance initialized with\n    update(init_);\n\n    // Verify inputs == ouputs\n    CAFFE_ENFORCE_EQ(init_.inputs.size(), init_.outputs.size());\n    for (auto i = 0; i < init_.inputs.size(); i++) {\n      CAFFE_ENFORCE_EQ(init_.inputs[i], init_.outputs[i]);\n    }\n\n    // Verify tensors all have same size\n    size_t size = Input(1).size();\n    for (auto i = 2; i < InputSize() - 1; i++) {\n      CAFFE_ENFORCE_EQ(Input(i).size(), size);\n    }\n\n    // Verify tensors all have same type\n    TypeMeta meta = Input(1).meta();\n    for (auto i = 2; i < InputSize() - 1; i++) {\n      CAFFE_ENFORCE(Input(i).meta() == meta);\n    }\n\n    initializeHalvingDoubling();\n  }\n\n  void initializeHalvingDoubling();\n\n  std::once_flag once_;\n  std::unique_ptr<::gloo::Algorithm> algorithm_;\n\n  // Captures the parameters passed to Gloo when first initialized.\n  // An instance is updated every time this op runs and is compared\n  // to the reference instance for equality. If any parameter has\n  // changed from run to run, the initialized algorithm is invalid.\n  void update(GlooParameters& params) {\n    params.context = OperatorBase::Input<std::shared_ptr<::gloo::Context>>(0);\n    params.inputs.resize(InputSize() - 2);\n    params.outputs.resize(OutputSize() - 1);\n    for (auto i = 0; i < params.inputs.size(); i++) {\n      params.inputs[i] = Input(i + 1).template raw_data();\n      params.outputs[i] = Output(i)->template raw_mutable_data();\n    }\n    params.size = Output(0)->size();\n    params.meta = Output(0)->meta();\n\n    // Verify recvCountsSize == comm_size\n    CAFFE_ENFORCE_EQ(Input(InputSize() - 1).size(), params.context->size);\n    int* recvCounts = (int*)Input(InputSize() - 1).template raw_data();\n    recvCounts_.assign(recvCounts, recvCounts + Input(InputSize() - 1).size());\n  }\n\n  GlooParameters init_;\n  GlooParameters current_;\n  Workspace* ws_;\n  std::string status_blob_;\n  std::vector<int> recvCounts_;\n};\n\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/store_handler.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"store_handler.h\"\n\nnamespace caffe2 {\nnamespace gloo {\n\nvoid StoreHandlerWrapper::set(\n    const std::string& key,\n    const std::vector<char>& data) {\n  std::string stringValue(data.data(), data.size());\n  handler_.set(key, stringValue);\n}\n\nstd::vector<char> StoreHandlerWrapper::get(const std::string& key) {\n  std::string str = handler_.get(key);\n  return std::vector<char>(str.begin(), str.end());\n}\n\nvoid StoreHandlerWrapper::wait(\n    const std::vector<std::string>& keys,\n    const std::chrono::milliseconds& timeout) {\n  handler_.wait(keys, timeout);\n}\n\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/gloo/store_handler.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/distributed/store_handler.h\"\n\n#include <gloo/rendezvous/store.h>\n\nnamespace caffe2 {\nnamespace gloo {\n\nclass StoreHandlerWrapper : public ::gloo::rendezvous::Store {\n public:\n  explicit StoreHandlerWrapper(StoreHandler& handler) : handler_(handler) {}\n\n  virtual ~StoreHandlerWrapper() {}\n\n  virtual void set(const std::string& key, const std::vector<char>& data)\n      override;\n\n  virtual std::vector<char> get(const std::string& key) override;\n\n  virtual void wait(const std::vector<std::string>& keys) override {\n    wait(keys, ::gloo::rendezvous::Store::kDefaultTimeout);\n  }\n\n  virtual void wait(\n      const std::vector<std::string>& keys,\n      const std::chrono::milliseconds& timeout) override;\n\n protected:\n  StoreHandler& handler_;\n};\n\n} // namespace gloo\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/nccl/CMakeLists.txt",
    "content": "if(USE_NCCL)\n    message(STATUS \"Include NCCL operators\")\n    set(Caffe2_CONTRIB_NCCL_GPU_SRC\n        \"${CMAKE_CURRENT_SOURCE_DIR}/cuda_nccl_gpu.cc\"\n        \"${CMAKE_CURRENT_SOURCE_DIR}/cuda_nccl_op_gpu.cc\"\n    )\n\n    set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${Caffe2_CONTRIB_NCCL_GPU_SRC})\n    set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nelse()\n  message(STATUS \"NCCL operators skipped due to no CUDA support\")\nendif()\n"
  },
  {
    "path": "caffe2/contrib/nccl/cuda_nccl_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"cuda_nccl_gpu.h\"\n\nnamespace caffe2 {\n\nnamespace nccl {\n\nnamespace {\n\nstd::vector<int> getDevices(const NCCLExecution& ex) {\n  std::vector<int> result;\n  result.reserve(ex.elements.size());\n  for (const auto& el: ex.elements) {\n    result.push_back(el.device);\n  }\n  return result;\n}\n\nclass NCCLContext {\n public:\n  explicit NCCLContext(const NCCLExecution& ex)\n      : devices_(getDevices(ex)), master_gpu_id_(ex.stream_gpu_id) {\n    comms_.resize(devices_.size());\n    CAFFE_NCCL_CHECK(\n        ncclCommInitAll(comms_.data(), devices_.size(), devices_.data()));\n\n    streams_.resize(devices_.size());\n    events_.resize(devices_.size());\n    for (auto i = 0; i < devices_.size(); ++i) {\n      DeviceGuard g(devices_[i]);\n      // get stream priorities\n      int lo_pri, hi_pri;\n      CUDA_ENFORCE(cudaDeviceGetStreamPriorityRange(&lo_pri, &hi_pri));\n      CUDA_ENFORCE(cudaStreamCreateWithPriority(\n          &streams_[i], cudaStreamNonBlocking, hi_pri));\n      CUDA_ENFORCE(cudaEventCreateWithFlags(\n          &events_[i], cudaEventDefault | cudaEventDisableTiming));\n    }\n    DeviceGuard g(master_gpu_id_);\n    CUDA_ENFORCE(cudaEventCreateWithFlags(\n        &master_event_, cudaEventDefault | cudaEventDisableTiming));\n  }\n\n  ~NCCLContext() {\n    for (auto i = 0; i < devices_.size(); ++i) {\n      DeviceGuard g(devices_[i]);\n      CUDA_ENFORCE(cudaStreamDestroy(streams_[i]));\n      CUDA_ENFORCE(cudaEventDestroy(events_[i]));\n    }\n    DeviceGuard g(master_gpu_id_);\n    CUDA_ENFORCE(cudaEventDestroy(master_event_));\n    for (auto& comm : comms_) {\n      ncclCommDestroy(comm);\n    }\n  }\n\n  std::vector<int> devices_;\n  std::vector<ncclComm_t> comms_;\n  std::vector<cudaStream_t> streams_;\n  int master_gpu_id_;\n  cudaEvent_t master_event_;\n  std::vector<cudaEvent_t> events_;\n\n  DISABLE_COPY_AND_ASSIGN(NCCLContext);\n};\n\n// We share the contexts across multiple operators, hence the\n// thread-local cache\nstatic std::mutex& gContextsMutex() {\n  static std::mutex m;\n  return m;\n}\n\nstd::unordered_map<std::string, std::unique_ptr<NCCLContext>>& gContexts() {\n  // Initiazed after CUDA, so guaranteed to be destructed before CUDA.\n  static std::unordered_map<std::string, std::unique_ptr<NCCLContext>> m;\n  return m;\n}\n\nstd::string ncclKey(const NCCLExecution& ex) {\n  std::string result;\n  int curr_device;\n  CUDA_CHECK(cudaGetDevice(&curr_device));\n  result += to_string(curr_device) + \":\";\n  for (const auto& el : ex.elements) {\n    result += to_string(el.device) + \",\";\n  }\n  return result;\n}\n\nNCCLContext* getNCCLContext(const NCCLExecution& ex) {\n  auto& contexts = gContexts();\n  const auto key = ncclKey(ex);\n  if (!contexts[key]) {\n    LOG(INFO) << \"Creating NCCLContext for key: \" << key;\n    contexts[key].reset(new NCCLContext(ex));\n  }\n  return CHECK_NOTNULL(contexts[key].get());\n}\n\ntemplate <typename T>\nclass ncclTypeWrapper;\n\ntemplate <>\nclass ncclTypeWrapper<float> {\n public:\n  static const ncclDataType_t type = ncclFloat;\n};\n\ntemplate <>\nclass ncclTypeWrapper<int> {\n public:\n  static const ncclDataType_t type = ncclInt;\n};\n\n#ifdef CAFFE_HAS_CUDA_FP16\ntemplate <>\nclass ncclTypeWrapper<float16> {\n public:\n  static const ncclDataType_t type = ncclHalf;\n};\n#endif\n\ntemplate <typename T, typename InitF, typename F>\nvoid runNCCL(const NCCLExecution& ex, InitF&& init_f, F&& f) {\n  // do initialization\n  for (auto i = 0; i < ex.elements.size(); ++i) {\n    auto& ctx = ex.elements[i];\n    DeviceGuard g(ctx.device);\n    init_f(ex.elements[i]);\n  }\n\n  std::lock_guard<std::mutex> g(gContextsMutex());\n  auto* context = getNCCLContext(ex);\n  auto& comms = context->comms_;\n  auto& streams = context->streams_;\n  auto& events = context->events_;\n  // Record an event on the master context, wait on it in each of the\n  // children streams, so the children streams are synchronized WRT\n  // the original stream.\n  {\n    DeviceGuard g(ex.stream_gpu_id);\n    CUDA_ENFORCE(cudaEventRecord(context->master_event_, ex.stream));\n  }\n\n  {\n    // lock out alloc / free while NCCL launches\n    std::lock_guard<std::mutex> lock(CUDAContext::mutex());\n\n#if NCCL_VERSION_MIN(2, 0, 0)\n    CAFFE_NCCL_CHECK(ncclGroupStart());\n#endif\n\n    for (auto i = 0; i < ex.elements.size(); ++i) {\n      auto& ctx = ex.elements[i];\n      DeviceGuard g(ctx.device);\n      auto& comm = comms[i];\n      auto& stream = streams[i];\n      auto& event = events[i];\n\n      DCHECK_EQ(ctx.device, GetGPUIDForPointer(ctx.src->raw_data()));\n      CUDA_ENFORCE(cudaStreamWaitEvent(stream, context->master_event_, 0));\n      f(ctx, comm, stream);\n    }\n\n#if NCCL_VERSION_MIN(2, 0, 0)\n    CAFFE_NCCL_CHECK(ncclGroupEnd());\n#endif\n\n    for (auto i = 0; i < ex.elements.size(); ++i) {\n      auto& ctx = ex.elements[i];\n      DeviceGuard g(ctx.device);\n      auto& comm = comms[i];\n      auto& stream = streams[i];\n      auto& event = events[i];\n\n      // Record an event on each children stream that we have finished\n      // our computation\n      CUDA_ENFORCE(cudaEventRecord(event, stream));\n    }\n  }\n\n  // Now, wait on all the events in the original stream.\n  DeviceGuard dg(ex.stream_gpu_id);\n  for (auto& event : events) {\n    CUDA_ENFORCE(cudaStreamWaitEvent(CHECK_NOTNULL(ex.stream), event, 0));\n  }\n}\n\n}\n\ntemplate <typename T>\nvoid NCCL<T>::AllReduce(const NCCLExecution& ex) {\n  return runNCCL<T>(\n      ex,\n      [](const NCCLElement& ctx) {\n        ctx.dst->Resize(ctx.src->dims());\n        ctx.dst->template mutable_data<T>();\n      },\n      [](const NCCLElement& ctx, ncclComm_t comm, cudaStream_t stream) {\n        CAFFE_NCCL_CHECK(ncclAllReduce(\n            ctx.src->raw_data(),\n            ctx.dst->raw_mutable_data(),\n            ctx.dst->size(),\n            ncclTypeWrapper<T>::type,\n            ncclSum,\n            comm,\n            stream));\n      });\n}\n\ntemplate <typename T>\nvoid NCCL<T>::Broadcast(const NCCLExecution& ex) {\n  return runNCCL<T>(\n      ex,\n      [](const NCCLElement& ctx) {\n        ctx.dst->Resize(ctx.src->dims());\n        ctx.dst->template mutable_data<T>();\n      },\n      [&ex](const NCCLElement& ctx, ncclComm_t comm, cudaStream_t stream) {\n        CAFFE_NCCL_CHECK(ncclBcast(\n            ctx.dst->raw_mutable_data(),\n            ctx.dst->size(),\n            ncclTypeWrapper<T>::type,\n            ex.root,\n            comm,\n            stream));\n      });\n}\n\ntemplate <typename T>\nvoid NCCL<T>::Reduce(const NCCLExecution& ex) {\n  return runNCCL<T>(\n      ex,\n      [](const NCCLElement& ctx) {\n        if (ctx.dst) {\n          ctx.dst->Resize(ctx.src->dims());\n          ctx.dst->template mutable_data<T>();\n        }\n      },\n      [&ex](const NCCLElement& ctx, ncclComm_t comm, cudaStream_t stream) {\n        CAFFE_NCCL_CHECK(ncclReduce(\n            ctx.src->raw_data(),\n            ctx.dst ? ctx.dst->raw_mutable_data() : nullptr,\n            ctx.src->size(),\n            ncclTypeWrapper<T>::type,\n            ncclSum,\n            ex.root,\n            comm,\n            stream));\n      });\n}\n\ntemplate <typename T>\nvoid NCCL<T>::AllGather(const NCCLExecution& ex) {\n  const auto n = ex.elements.size();\n  return runNCCL<T>(\n      ex,\n      [n](const NCCLElement& ctx) {\n        CAFFE_ENFORCE_NE(ctx.src, ctx.dst);\n        std::vector<TIndex> dims;\n        dims.reserve(ctx.src->ndim() + 1);\n        dims.push_back(n);\n        for (auto d : ctx.src->dims()) {\n          dims.push_back(d);\n        }\n        ctx.dst->Resize(dims);\n        ctx.dst->template mutable_data<T>();\n      },\n      [n](const NCCLElement& ctx, ncclComm_t comm, cudaStream_t stream) {\n#if NCCL_VERSION_MIN(2, 0, 0)\n        CAFFE_NCCL_CHECK(ncclAllGather(\n            ctx.src->raw_data(),\n            ctx.dst->raw_mutable_data(),\n            ctx.src->size(),\n            ncclTypeWrapper<T>::type,\n            comm,\n            stream));\n#else\n        CAFFE_NCCL_CHECK(ncclAllGather(\n            ctx.src->raw_data(),\n            ctx.src->size(),\n            ncclTypeWrapper<T>::type,\n            ctx.dst->raw_mutable_data(),\n            comm,\n            stream));\n#endif\n      });\n}\n\ntemplate <typename T>\nvoid NCCL<T>::ReduceScatter(const NCCLExecution& ex) {\n  const auto n = ex.elements.size();\n  return runNCCL<T>(\n      ex,\n      [](const NCCLElement& ctx) {\n        CAFFE_ENFORCE_NE(ctx.src, ctx.dst);\n        const auto& srcDims = ctx.src->dims();\n        std::vector<TIndex> dstDims(srcDims.begin() + 1, srcDims.end());\n        ctx.dst->Resize(dstDims);\n        ctx.dst->template mutable_data<T>();\n      },\n      [n](const NCCLElement& ctx, ncclComm_t comm, cudaStream_t stream) {\n        CAFFE_NCCL_CHECK(ncclReduceScatter(\n            ctx.src->raw_data(),\n            ctx.dst->raw_mutable_data(),\n            ctx.dst->size(),\n            ncclTypeWrapper<T>::type,\n            ncclSum,\n            comm,\n            stream));\n      });\n}\n\n// Explicit instantiation\ntemplate class NCCL<float>;\ntemplate class NCCL<int>;\n#ifdef CAFFE_HAS_CUDA_FP16\ntemplate class NCCL<float16>;\n#endif\n}\n}\n"
  },
  {
    "path": "caffe2/contrib/nccl/cuda_nccl_gpu.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <cstddef>\n\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/logging.h\"\n\n#include <nccl.h>\n#include <unordered_map>\n\n#define NCCL_VERSION_MIN(major, minor, patch) \\\n  ((NCCL_MAJOR > major) || \\\n    ((NCCL_MAJOR == major) && ((NCCL_MINOR > minor) || \\\n      ((NCCL_MINOR == minor) && (NCCL_PATCH >= patch)) )))\n\nnamespace caffe2 {\n\nnamespace nccl {\n\n#define CAFFE_NCCL_CHECK(condition)    \\\n  do {                                 \\\n    ncclResult_t status = (condition); \\\n    CAFFE_ENFORCE_EQ(                  \\\n        status,                        \\\n        ncclSuccess,                   \\\n        \" \",                           \\\n        \"Error at: \",                  \\\n        __FILE__,                      \\\n        __LINE__,                      \\\n        \": \",                          \\\n        ncclGetErrorString(status));   \\\n  } while (0)\n\nstruct NCCLElement {\n  const TensorCUDA* src{nullptr};\n  TensorCUDA* dst{nullptr};\n  int device{0};\n};\n\nstruct NCCLExecution {\n  int stream_gpu_id{0};\n  cudaStream_t stream{nullptr};\n  std::vector<NCCLElement> elements;\n  size_t root{0};\n};\n\ntemplate <typename T>\nclass NCCL {\n public:\n  static void AllReduce(const NCCLExecution& ex);\n  static void Broadcast(const NCCLExecution& ex);\n  static void Reduce(const NCCLExecution& ex);\n  static void AllGather(const NCCLExecution& ex);\n  static void ReduceScatter(const NCCLExecution& ex);\n};\n}\n}\n"
  },
  {
    "path": "caffe2/contrib/nccl/cuda_nccl_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/operator.h\"\n\n#include \"cuda_nccl_gpu.h\"\n\nnamespace caffe2 {\n\nnccl::NCCLExecution getNCCLElements(\n    OperatorBase* op,\n    const CUDAContext& context) {\n  // We either do an N-N op, or an N-1 op.\n  CAFFE_ENFORCE(op->InputSize() == op->OutputSize() || op->OutputSize() == 1);\n  nccl::NCCLExecution ex;\n  ex.stream_gpu_id = context.cuda_gpu_id();\n  ex.stream = context.cuda_stream();\n  ex.root = op->template GetSingleArgument<int>(\"root\", 0);\n  ex.elements.resize(op->InputSize());\n  for (auto i = 0; i < op->InputSize(); ++i) {\n    auto& el = ex.elements[i];\n    el.src = &(op->Input<TensorCUDA>(i));\n    if (op->OutputSize() == 1) {\n      // Reduce op\n      if (i == ex.root) {\n        el.dst = op->Output<TensorCUDA>(0);\n      }\n    } else if (i < op->OutputSize()) {\n      el.dst = op->Output<TensorCUDA>(i);\n    }\n    // TODO - expensive (>1ms) - cache these.\n    el.device = GetGPUIDForPointer(op->Input<TensorCUDA>(i).raw_data());\n  }\n\n  return ex;\n}\n\nnamespace {\n// Check if all inputs are float\ntemplate <typename T>\nbool AllInputsAre(OperatorBase* op) {\n  for (auto i = 0; i < op->InputSize(); ++i) {\n    if (op->Input<TensorCUDA>(i).IsType<T>()) {\n      continue;\n    } else {\n      return false;\n    }\n  }\n  return true;\n}\n}; // namespace\n\nclass NCCLAllreduceOp final : public Operator<CUDAContext> {\n public:\n  using Operator::Operator;\n  NCCLAllreduceOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws) {}\n  bool RunOnDevice() override {\n    if (InputSize() == 1)\n      return true;\n\n    if (AllInputsAre<float>(this)) {\n      nccl::NCCL<float>::AllReduce(getNCCLElements(this, context_));\n      return true;\n    } else if (AllInputsAre<float16>(this)) {\n      nccl::NCCL<float16>::AllReduce(getNCCLElements(this, context_));\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n protected:\n};\n\nclass NCCLBroadcastOp final : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  using Operator::Operator;\n  bool RunOnDevice() override {\n    if (InputSize() == 1)\n      return true;\n    if (AllInputsAre<float>(this)) {\n      nccl::NCCL<float>::Broadcast(getNCCLElements(this, context_));\n      return true;\n    } else if (AllInputsAre<float16>(this)) {\n      nccl::NCCL<float16>::Broadcast(getNCCLElements(this, context_));\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n protected:\n};\n\nclass NCCLReduceOp final : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  using Operator::Operator;\n  bool RunOnDevice() override {\n    if (InputSize() == 1)\n      return true;\n    const auto& ex = getNCCLElements(this, context_);\n\n    if (AllInputsAre<float>(this)) {\n      nccl::NCCL<float>::Reduce(ex);\n      return true;\n    } else if (AllInputsAre<float16>(this)) {\n      nccl::NCCL<float16>::Reduce(ex);\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n protected:\n};\n\nclass NCCLAllGatherOp final : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  using Operator::Operator;\n  bool RunOnDevice() override {\n    if (InputSize() == 1)\n      return true;\n    if (AllInputsAre<float>(this)) {\n      nccl::NCCL<float>::AllGather(getNCCLElements(this, context_));\n      return true;\n    } else if (AllInputsAre<float16>(this)) {\n      nccl::NCCL<float16>::AllGather(getNCCLElements(this, context_));\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n protected:\n};\n\nclass NCCLReduceScatterOp final : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  using Operator::Operator;\n  bool RunOnDevice() override {\n    if (AllInputsAre<float>(this)) {\n      nccl::NCCL<float>::ReduceScatter(getNCCLElements(this, context_));\n      return true;\n    } else if (AllInputsAre<float16>(this)) {\n      nccl::NCCL<float16>::ReduceScatter(getNCCLElements(this, context_));\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n protected:\n};\n\nnamespace {\n\nstd::pair<std::vector<DeviceOption>, std::vector<DeviceOption>> ncclOpDevInfer(\n    const OperatorDef& def) {\n  std::vector<DeviceOption> opt;\n  for (int i = 0; i < def.input().size(); ++i) {\n    DeviceOption dev;\n    dev.set_device_type(1);\n    dev.set_cuda_gpu_id(i);\n    opt.push_back(dev);\n  }\n  return std::make_pair(opt, opt);\n}\n\nREGISTER_CUDA_OPERATOR(NCCLAllreduce, NCCLAllreduceOp);\nOPERATOR_SCHEMA(NCCLAllreduce)\n    .NumInputs(1, CAFFE2_COMPILE_TIME_MAX_GPUS)\n    .NumOutputs(1, CAFFE2_COMPILE_TIME_MAX_GPUS)\n    .IdenticalTypeAndShape()\n    .InputsCanCrossDevices()\n    .AllowOneToOneInplace()\n    .DeviceInferenceFunction(ncclOpDevInfer);\nSHOULD_NOT_DO_GRADIENT(NCCLAllreduce);\n\nREGISTER_CUDA_OPERATOR(NCCLBroadcast, NCCLBroadcastOp);\nOPERATOR_SCHEMA(NCCLBroadcast)\n    .NumInputs(1, CAFFE2_COMPILE_TIME_MAX_GPUS)\n    .NumOutputs(1, CAFFE2_COMPILE_TIME_MAX_GPUS)\n    .IdenticalTypeAndShape()\n    .InputsCanCrossDevices()\n    .EnforceOneToOneInplace()\n    .DeviceInferenceFunction(ncclOpDevInfer);\n\nSHOULD_NOT_DO_GRADIENT(NCCLBroadcast);\n\nREGISTER_CUDA_OPERATOR(NCCLReduce, NCCLReduceOp);\nOPERATOR_SCHEMA(NCCLReduce)\n    .NumInputs(1, CAFFE2_COMPILE_TIME_MAX_GPUS)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInput(0)\n    .InputsCanCrossDevices()\n    .AllowInplace([](int in, int out) -> bool { return (out == 0); })\n    .DeviceInferenceFunction(ncclOpDevInfer);\nSHOULD_NOT_DO_GRADIENT(NCCLReduce);\n\nREGISTER_CUDA_OPERATOR(NCCLAllGather, NCCLAllGatherOp);\nOPERATOR_SCHEMA(NCCLAllGather)\n    .NumInputs(1, CAFFE2_COMPILE_TIME_MAX_GPUS)\n    .NumOutputs(1, CAFFE2_COMPILE_TIME_MAX_GPUS)\n    .InputsCanCrossDevices()\n    .DeviceInferenceFunction(ncclOpDevInfer);\nSHOULD_NOT_DO_GRADIENT(NCCLAllGather);\n\nREGISTER_CUDA_OPERATOR(NCCLReduceScatter, NCCLReduceScatterOp);\nOPERATOR_SCHEMA(NCCLReduceScatter)\n    .NumInputs(1, CAFFE2_COMPILE_TIME_MAX_GPUS)\n    .NumOutputs(1, CAFFE2_COMPILE_TIME_MAX_GPUS)\n    .InputsCanCrossDevices()\n    .DeviceInferenceFunction(ncclOpDevInfer);\nSHOULD_NOT_DO_GRADIENT(NCCLReduceScatter);\n} // namespace\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/nccl/nccl_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given, assume\nimport numpy as np\nimport time\nimport os\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core, workspace, muji, dyndep\nimport caffe2.python.hypothesis_test_util as hu\n\nnp.random.seed(1)\n\ndyndep.InitOpsLibrary('@/caffe2/caffe2/contrib/nccl:nccl_ops')\n\n\ndef gpu_device(i):\n    device_option = caffe2_pb2.DeviceOption()\n    device_option.device_type = caffe2_pb2.CUDA\n    device_option.cuda_gpu_id = i\n    return device_option\n\n\ndef benchmark(ws, net, warmups=5, iters=100):\n    for _ in range(warmups):\n        ws.run(net)\n    plan = core.Plan(\"plan\")\n    plan.AddStep(core.ExecutionStep(\"test-step\", net, iters))\n    before = time.time()\n    ws.run(plan)\n    after = time.time()\n    print(\"Timing network, time taken per-iteration: {:.6f}ms\".format((\n        after - before) / float(iters) * 1000.0))\n    return after - before\n\n\n@unittest.skipIf(not workspace.has_gpu_support, \"NCCL only on GPU\")\nclass NCCLOpsTest(hu.HypothesisTestCase):\n    @given(n=st.integers(min_value=2, max_value=workspace.NumCudaDevices()),\n           m=st.integers(min_value=1, max_value=1000),\n           in_place=st.booleans())\n    def test_nccl_allreduce(self, n, m, in_place):\n        xs = [np.random.randn(m).astype(np.float32) for i in range(n)]\n        inputs = [str(\"x_{}\".format(i)) for i in range(n)]\n        prefix = \"\" if in_place else \"o\"\n        outputs = [str(\"{}x_{}\".format(prefix, i)) for i in range(n)]\n        op = core.CreateOperator(\"NCCLAllreduce\", inputs, outputs)\n        input_device_options = {n: gpu_device(i) for i, n in enumerate(inputs)}\n\n        def allreduce(*args):\n            assert len(args) == n\n            output = np.sum(args, axis=0)\n            return [output for _ in range(n)]\n\n        outputs = self.assertReferenceChecks(\n            hu.gpu_do, op, [xs[i] for i, _ in enumerate(inputs)],\n            allreduce, input_device_options)\n        for output in outputs:\n            np.testing.assert_array_equal(outputs[0], output)\n            self.assertEqual(outputs[0].tobytes(), output.tobytes())\n\n    @given(n=st.integers(min_value=2, max_value=workspace.NumCudaDevices()),\n           m=st.integers(min_value=1, max_value=1000),\n           root=st.integers(min_value=0,\n                            max_value=workspace.NumCudaDevices() - 1))\n    def test_nccl_broadcast(self, n, m, root):\n        assume(root < n)\n        xs = [np.random.randn(m).astype(np.float32) for i in range(n)]\n        inputs = [str(\"x_{}\".format(i)) for i in range(n)]\n        op = core.CreateOperator(\"NCCLBroadcast\", inputs, inputs, root=root)\n        input_device_options = {n: gpu_device(i) for i, n in enumerate(inputs)}\n\n        def broadcast(*args):\n            assert len(args) == n\n            return [args[root] for _ in range(n)]\n\n        self.assertReferenceChecks(\n            hu.gpu_do, op, [xs[i] for i, _ in enumerate(inputs)],\n            broadcast, input_device_options)\n\n    @given(n=st.integers(min_value=2, max_value=workspace.NumCudaDevices()),\n           m=st.integers(min_value=1, max_value=1000),\n           # NCCL Reduce seems to deadlock for non-zero roots.\n           root=st.integers(min_value=0, max_value=0),\n           in_place=st.booleans())\n    def test_nccl_reduce(self, n, m, root, in_place):\n        assume(in_place is False or root == 0)\n        xs = [np.random.randn(m).astype(np.float32) for i in range(n)]\n        inputs = [str(\"x_{}\".format(i)) for i in range(n)]\n        op = core.CreateOperator(\n            \"NCCLReduce\", inputs,\n            inputs[root] if in_place else b\"o\", root=root)\n        input_device_options = {n: gpu_device(i) for i, n in enumerate(inputs)}\n\n        def reduce(*args):\n            assert len(args) == n\n            return [np.sum(args, axis=0)]\n\n        self.assertReferenceChecks(\n            hu.gpu_do, op, [xs[i] for i, _ in enumerate(inputs)],\n            reduce, input_device_options)\n\n    @given(n=st.integers(min_value=2, max_value=workspace.NumCudaDevices()),\n           m=st.integers(min_value=1, max_value=1000))\n    def test_nccl_allgather(self, n, m):\n        xs = [np.random.randn(m).astype(np.float32) for i in range(n)]\n        inputs = [str(\"x_{}\".format(i)) for i in range(n)]\n        outputs = [str(\"o_{}\".format(i)) for i in range(n)]\n        op = core.CreateOperator(\"NCCLAllGather\", inputs, outputs)\n        input_device_options = {n: gpu_device(i) for i, n in enumerate(inputs)}\n\n        def allgather(*args):\n            assert len(args) == n\n            return [np.stack(args, axis=0) for _ in range(n)]\n\n        outputs = self.assertReferenceChecks(\n            hu.gpu_do, op, [xs[i] for i, _ in enumerate(inputs)],\n            allgather, input_device_options)\n        for output in outputs:\n            np.testing.assert_array_equal(outputs[0], output)\n            self.assertEqual(outputs[0].tobytes(), output.tobytes())\n\n    @given(n=st.integers(min_value=2, max_value=workspace.NumCudaDevices()),\n           m=st.integers(min_value=1, max_value=1000))\n    def test_nccl_reduce_scatter(self, n, m):\n        xs = [np.random.randn(n, m).astype(np.float32) for i in range(n)]\n        inputs = [str(\"x_{}\".format(i)) for i in range(n)]\n        outputs = [str(\"o_{}\".format(i)) for i in range(n)]\n        op = core.CreateOperator(\"NCCLReduceScatter\", inputs, outputs)\n        input_device_options = {n: gpu_device(i) for i, n in enumerate(inputs)}\n\n        def reduce_scatter(*args):\n            assert len(args) == n\n            reduced = sum(args)\n            assert len(reduced.shape) > 1\n            ref = [reduced[i, :] for i in range(n)]\n            return ref\n\n        self.assertReferenceChecks(\n            hu.gpu_do, op, [xs[i] for i, _ in enumerate(inputs)],\n            reduce_scatter, input_device_options)\n\n    @given(n=st.integers(min_value=2, max_value=workspace.NumCudaDevices()),\n           m=st.integers(min_value=100000, max_value=100000),\n           iters=st.integers(min_value=1, max_value=100),\n           net_type=st.sampled_from([\"dag\", \"async_dag\", \"simple\"]))\n    def _test_nccl_sync(self, n, m, iters, net_type):\n        inputs = [str(\"x_{}\".format(i)) for i in range(n)]\n        extra_inputs = [str(\"xe_{}\".format(i)) for i in range(n)]\n        net = core.Net(\"asdf\")\n        net.Proto().type = net_type\n        net.Proto().num_workers = n\n        for i in range(n):\n            net.ConstantFill([], inputs[i], shape=[m], value=0.0,\n                             device_option=gpu_device(i))\n            net.ConstantFill([], extra_inputs[i], shape=[m], value=1.0,\n                             device_option=gpu_device(i))\n            for _ in range(iters):\n                net.Sum([inputs[i], extra_inputs[i]], [inputs[i]],\n                        device_option=gpu_device(i))\n        net.NCCLReduce(inputs, [inputs[0]], device_option=gpu_device(0))\n        self.ws.run(net)\n        np.testing.assert_array_equal(\n            self.ws.blobs[inputs[0]].fetch(),\n            np.full(shape=(m,), fill_value=iters * n, dtype=np.float32))\n\n    @unittest.skipIf(not os.environ.get(\"CAFFE2_BENCHMARK\"), \"Benchmark\")\n    def test_timings(self):\n        for n in range(2, workspace.NumCudaDevices()):\n            for in_place in [False, True]:\n                xs = [np.random.randn(1e7).astype(np.float32)\n                      for i in range(n)]\n                inputs = [str(\"x_{}\".format(i)) for i in range(n)]\n                prefix = \"\" if in_place else \"o\"\n                outputs = [str(\"{}x_{}\".format(prefix, i)) for i in range(n)]\n\n                net = core.Net(\"test\")\n                net.NCCLAllreduce(inputs, outputs)\n                net.RunAllOnGPU()\n                for i in range(n):\n                    self.ws.create_blob(inputs[i]).feed(xs[i], gpu_device(i))\n                self.ws.run(net)\n                net_time = benchmark(self.ws, net)\n                vanilla = core.Net(\"vanilla\")\n                muji.Allreduce(vanilla, inputs)\n                vanilla_time = benchmark(self.ws, vanilla)\n                print(\"Speedup for NCCL: {:.2f}\".format(\n                    vanilla_time / net_time))\n"
  },
  {
    "path": "caffe2/contrib/nervana/CMakeLists.txt",
    "content": "if(USE_NERVANA_GPU)\n  message(STATUS \"Include Nervana operators\")\n  set(Caffe2_CONTRIB_NCCL_GPU_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/nervana_c_api.cu\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/nervana_fc_op_gpu.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/nervana_init_gpu.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/nervana_math_gpu.cc\"\n  )\n\n  set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${Caffe2_CONTRIB_NCCL_GPU_SRC})\n  set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nendif()\n"
  },
  {
    "path": "caffe2/contrib/nervana/nervana.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_FB_NERVANA_INIT_H_\n#define CAFFE2_FB_NERVANA_INIT_H_\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/flags.h\"\n\n#include \"nervana_c_api.h\"\n\n/**\n * A flag that specifies the nervana cubin path.\n */\nCAFFE2_DECLARE_string(nervana_cubin_path);\n\nnamespace caffe2 {\n\n/**\n * An empty class to be used in identifying the engine in the math functions.\n */\nclass NervanaEngine {};\n\n/**\n * Returns whether the nervana kernels are loaded or not.\n */\nbool NervanaKernelLoaded();\n\n/**\n * An initialization function that is run once by caffe2::GlobalInit()\n * that initializes the nervana kernels.\n */\nbool Caffe2InitializeNervanaKernels();\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_FB_NERVANA_INIT_H_\n"
  },
  {
    "path": "caffe2/contrib/nervana/nervana_c_api.cu",
    "content": "/*\n * Copyright 2015 Baidu USA, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <vector>\n#include <string>\n#include <map>\n#include <cuda.h>\n#include <iostream>\n#include <sstream>\n#include <mutex>\n#include <tuple>\n#include \"nervana_c_api.h\"\n\nstd::map<CUdevice, int> nervana_sm_counts_;\nstd::map<std::string, CUfunction> nervana_kernels_;\nstd::vector<CUmodule> nervana_modules_;\n\n//for when we need to modify the above data structures\nstd::mutex nervana_load_kernels_mutex_;\nstd::mutex nervana_sm_count_mutex_;\n\nextern \"C\" bool nervana_loadKernels(const char* const base_path_cstr) {\n    std::lock_guard<std::mutex> lock(nervana_load_kernels_mutex_);\n\n    //better would be a vector<string>, but there is a bug in nvcc that prevents this\n    // (bug report filed) (fixed in 7.5)\n    std::string names[36] = {\n        \"hgemm_nn_vec_128x128\",\n        \"hgemm_nn_128x128\",\n        \"hgemm_nt_vec_128x128\",\n        \"hgemm_nt_128x128\",\n        \"hgemm_tn_vec_128x128\",\n        \"hgemm_tn_128x128\",\n        \"hgemm_nn_vec_128x64\",\n        \"hgemm_nn_128x64\",\n        \"hgemm_tn_vec_128x64\",\n        \"hgemm_tn_128x64\",\n        \"hgemm_nn_vec_128x32\",\n        \"hgemm_nn_128x32\",\n        \"hgemm_tn_vec_128x32\",\n        \"hgemm_tn_128x32\",\n        \"hgemm_nn_32x128\",\n        \"hgemm_nn_vec_32x128\",\n        \"hgemm_nt_32x128\",\n        \"hgemm_nt_vec_32x128\",\n        \"sgemm_nn_vec_128x128\",\n        \"sgemm_nn_128x128\",\n        \"sgemm_nt_vec_128x128\",\n        \"sgemm_nt_128x128\",\n        \"sgemm_tn_vec_128x128\",\n        \"sgemm_tn_128x128\",\n        \"sgemm_nn_vec_128x64\",\n        \"sgemm_nn_128x64\",\n        \"sgemm_tn_vec_128x64\",\n        \"sgemm_tn_128x64\",\n        \"sgemm_nn_vec_128x32\",\n        \"sgemm_nn_128x32\",\n        \"sgemm_tn_vec_128x32\",\n        \"sgemm_tn_128x32\",\n        \"sgemm_nn_32x128\",\n        \"sgemm_nn_vec_32x128\",\n        \"sgemm_nt_32x128\",\n        \"sgemm_nt_vec_32x128\"\n    };\n\n    std::string base_path(base_path_cstr);\n\n    for (auto kernel : names) {\n        if (nervana_kernels_.count(kernel) > 0)\n            continue;\n\n        CUmodule module;\n\n        std::string path = base_path + kernel + std::string(\".cubin\");\n        CUresult res = cuModuleLoad(&module, path.c_str());\n\n        if (res != CUDA_SUCCESS) {\n            // std::cerr << \"Failed to load: \" << kernel << \" \" << res << std::endl;\n            return false;\n        }\n\n        nervana_modules_.push_back(module);\n\n        CUfunction function;\n        res = cuModuleGetFunction(&function, module, kernel.c_str());\n        if (res != CUDA_SUCCESS) {\n            // std::cerr << \"Failed to extract: \" << kernel << \" \" << res << std::endl;\n            return false;\n        }\n\n        nervana_kernels_.insert(std::make_pair(kernel, function));\n    }\n\n    return true;\n}\n\nextern \"C\" bool nervana_unloadKernels() {\n    std::lock_guard<std::mutex> lock(nervana_load_kernels_mutex_);\n    while(nervana_modules_.size() > 0) {\n        auto module = nervana_modules_.back();\n        CUresult res = cuModuleUnload(module);\n\n        nervana_modules_.pop_back();\n\n        if (res != CUDA_SUCCESS)\n            return false;\n    }\n\n    nervana_kernels_.clear();\n\n    return true;\n}\n\nextern \"C\" size_t nervana_randStateSizeBytes() {\n    return 2048 * 32 * sizeof(int);\n}\n\nstd::tuple<int, int, int> get_grid_dimensions(int grid, int m, int n, int sm_count, const std::string& trans)\n{\n    int sizeA, sizeB, threads;\n    if (grid >= 0) {\n        if (grid == 0) {\n            sizeA = 32;\n            sizeB = 128;\n            threads = 128;\n        } else if (grid == 1) {\n            sizeA = 128;\n            sizeB = 32;\n            threads = 128;\n        } else if (grid == 2) {\n            sizeA = 128;\n            sizeB = 64;\n            threads = 128;\n        } else if (grid == 3) {\n            sizeA = 128;\n            sizeB = 128;\n            threads = 256;\n        }\n    } else {\n        int sh = min(m, n);\n\n        int size;\n        if (sh < 384 - 16) {\n            int sh128 = sh % 128;\n            if (sh128 > 0 && sh128 < 112) {\n                if (sh128 > 48 && sh128 <= 64) {\n                    int sh64 = sh / 64;\n                    int wide = max(m, n);\n                    sh64 *= (wide / 128 + (wide % 128 != 0)) / sm_count;\n                    if (sh64 > 1) {\n                        size = 64;\n                    }\n                    else {\n                        size = 32;\n                    }\n                }\n                else {\n                    size = 32;\n                }\n            }\n            else {\n                size = 128;\n            }\n        } else {\n            size = 128;\n        }\n\n        if (m >= n) {\n            if (trans == \"nt\") {\n                size = 128;\n            }\n            sizeA = 128;\n            sizeB = size;\n        } else {\n            if (trans == \"tn\") {\n                size = 128;\n            } else if (size == 64) {\n                //temporary until kernels exist\n                size = 32;\n            }\n            sizeA = size;\n            sizeB = 128;\n        }\n        threads = (sizeA == 128 && sizeB == 128) ? 256 : 128;\n    }\n\n    return std::make_tuple(sizeA, sizeB, threads);\n}\n\nextern \"C\" bool nervana_sgemm(float *A, float *B, float *C,\n                              bool a_t, bool b_t,\n                              int m, int n, int k,\n                              int lda, int ldb, int ldc,\n                              float alpha, float beta,\n                              unsigned int *rand_state,\n                              bool stochastic_round, bool apply_relu,\n                              CUstream stream, int grid\n                             )\n{\n    int sm_count;\n    {\n        std::lock_guard<std::mutex> lock(nervana_sm_count_mutex_);\n\n        CUdevice device;\n        CUresult res = cuCtxGetDevice(&device);\n        if (res != CUDA_SUCCESS) {\n            return false;\n        }\n        auto count = nervana_sm_counts_.find(device);\n        if (count != nervana_sm_counts_.end()) {\n            sm_count = count->second;\n        }\n        else {\n            int pi;\n            res = cuDeviceGetAttribute(&pi, CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT, device);\n            if (res != CUDA_SUCCESS) {\n                return false;\n            }\n            sm_count = pi;\n            nervana_sm_counts_[device] = pi;\n        }\n    }\n\n    std::string name = \"sgemm_\";\n\n    std::string trans;\n    trans += a_t ? 't' : 'n';\n    trans += b_t ? 't' : 'n';\n\n    name += trans;\n\n    int sizeA, sizeB, threads;\n\n    std::tie(sizeA, sizeB, threads) = get_grid_dimensions(grid, m, n, sm_count, trans);\n\n    int k_vec = (sizeA == 32 || sizeB == 32) ? 4 : 16;\n\n    if ( (trans == \"tn\" && m % 4 == 0  && n % 4 == 0) ||\n         (trans == \"nn\" && k % k_vec == 0 && n % 4 == 0) ||\n         (trans == \"nt\" && k % k_vec == 0)) {\n         name += \"_vec\";\n    }\n\n    int gridA = m / sizeA + (m % sizeA != 0);\n    int gridB = n / sizeB + (n % sizeB != 0);\n    std::stringstream ss;\n    ss << \"_\" << sizeA << \"x\" << sizeB;\n    name += ss.str();\n\n    int flags = 0;\n    flags |= (stochastic_round << 0);\n    flags |= (apply_relu << 1);\n\n    CUresult res;\n\n    if (a_t)\n        lda *= (8 * sizeof(float));\n\n    if (!b_t)\n        ldb *= (8 * sizeof(float));\n\n    int zero = 0;\n    void *args[17] = {&rand_state, &A, &B, &C, &lda, &ldb, &ldc, &m, &n, &k, &alpha, &beta, &flags,\n                      &zero, &zero, &zero, &zero};\n\n    res = cuLaunchKernel(nervana_kernels_[name],\n                         1, gridA, gridB,\n                         threads, 1, 1,\n                         0,\n                         stream, args, NULL);\n\n    if (res != CUDA_SUCCESS) {\n        std::cerr << \"Error launching kernel \" << name << \" \" << res << std::endl;\n        return false;\n    }\n\n    return true;\n}\n\nextern \"C\" bool nervana_hgemm(short *A, short *B, short *C,\n                              bool a_t, bool b_t,\n                              int m, int n, int k,\n                              int lda, int ldb, int ldc,\n                              float alpha, float beta,\n                              unsigned int *rand_state,\n                              bool stochastic_round, bool apply_relu,\n                              CUstream stream, int grid\n                             )\n{\n    int sm_count;\n    {\n        std::lock_guard<std::mutex> lock(nervana_sm_count_mutex_);\n\n        CUdevice device;\n        CUresult res = cuCtxGetDevice(&device);\n        if (res != CUDA_SUCCESS) {\n            return false;\n        }\n        auto count = nervana_sm_counts_.find(device);\n        if (count != nervana_sm_counts_.end()) {\n            sm_count = count->second;\n        }\n        else {\n            int pi;\n            res = cuDeviceGetAttribute(&pi, CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT, device);\n            if (res != CUDA_SUCCESS) {\n                return false;\n            }\n            sm_count = pi;\n            nervana_sm_counts_[device] = pi;\n        }\n    }\n\n    std::string name = \"hgemm_\";\n\n    std::string trans;\n    trans += a_t ? 't' : 'n';\n    trans += b_t ? 't' : 'n';\n\n    name += trans;\n\n    int sizeA, sizeB, threads;\n\n    std::tie(sizeA, sizeB, threads) = get_grid_dimensions(grid, m, n, sm_count, trans);\n\n    int k_vec = (sizeA == 32 || sizeB == 32) ? 4 : 16;\n\n    if ( (trans == \"tn\" && m % 4 == 0 && n % 4 == 0) ||\n         (trans == \"nn\" && k % k_vec == 0 && n % 4 == 0) ||\n         (trans == \"nt\" && k % k_vec == 0)) {\n         name += \"_vec\";\n    }\n\n    int gridA = m / sizeA + (m % sizeA != 0);\n    int gridB = n / sizeB + (n % sizeB != 0);\n    std::stringstream ss;\n    ss << \"_\" << sizeA << \"x\" << sizeB;\n    name += ss.str();\n\n    int flags = 0;\n    flags |= (stochastic_round << 0);\n    flags |= (apply_relu << 1);\n\n    CUresult res;\n\n    if (a_t)\n        lda *= (8 * sizeof(short));\n\n    if (!b_t)\n        ldb *= (8 * sizeof(short));\n\n    int zero = 0;\n    void *args[17] = {&rand_state, &A, &B, &C, &lda, &ldb, &ldc, &m, &n, &k, &alpha, &beta, &flags,\n                      &zero, &zero, &zero, &zero};\n\n    res = cuLaunchKernel(nervana_kernels_[name],\n                         1, gridA, gridB,\n                         threads, 1, 1,\n                         0,\n                         stream, args, NULL);\n\n    if (res != CUDA_SUCCESS) {\n        std::cerr << \"Error launching kernel \" << name << \" \" << res << std::endl;\n        return false;\n    }\n\n    return true;\n}\n\nextern \"C\" bool nervana_sgemm_colmajor(float *A, float *B, float *C,\n                                       bool a_t, bool b_t,\n                                       int m, int n, int k,\n                                       int lda, int ldb, int ldc,\n                                       float alpha, float beta,\n                                       unsigned int *rand_state,\n                                       bool stochastic_round, bool apply_relu,\n                                       CUstream stream, int grid\n                                      )\n{\n    return nervana_sgemm(B, A, C,\n                         b_t, a_t,\n                         n, m, k,\n                         ldb, lda, ldc,\n                         alpha, beta,\n                         rand_state, stochastic_round, apply_relu,\n                         stream, grid);\n}\n\nextern \"C\" bool nervana_hgemm_colmajor(short *A, short *B, short *C,\n                                       bool a_t, bool b_t,\n                                       int m, int n, int k,\n                                       int lda, int ldb, int ldc,\n                                       float alpha, float beta,\n                                       unsigned int *rand_state,\n                                       bool stochastic_round, bool apply_relu,\n                                       CUstream stream, int grid\n                                      )\n{\n    return nervana_hgemm(B, A, C,\n                         b_t, a_t,\n                         n, m, k,\n                         ldb, lda, ldc,\n                         alpha, beta,\n                         rand_state, stochastic_round, apply_relu,\n                         stream, grid);\n}\n"
  },
  {
    "path": "caffe2/contrib/nervana/nervana_c_api.h",
    "content": "/*\n * Copyright 2015 Baidu USA, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <cuda.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#else\n#include <stdbool.h>\n#endif\n\n/** Load all the sgemm and hgemm cubins from the given path\n * \\param [in] base_path path to the kernel cubins\n * \\return true on success and false if an error was encountered\n */\nbool nervana_loadKernels(const char* const base_path);\n\n/** Unload all currently loaded cubins\n * \\return true on success and false if an error was encountered\n */\nbool nervana_unloadKernels();\n\n/** Return the number of bytes required for the random state\n *  used in stochastic rounding.\n *  \\return bytes required for random state\n */\n size_t nervana_randStateSizeBytes();\n\n/** Perform BLAS sgemm on alpha * A * B + beta * C, with the\n *  additional options of stochastic rounding and applying a\n *  rectified linear unit (relu) to the result.  This routine expects\n *  all matrices to be in row-major order.\n *  \\param [in] A Pointer to the data for matrix A\n *  \\param [in] B Pointer to the data for matrix B\n *  \\param [in, out] C Pointer to the data for matrix C\n *  \\param [in] m number of rows of C\n *  \\param [in] n number of columns of C\n *  \\param [in] k inner dimension of multiplication\n *  \\param [in] lda leading dimension of two-dimensional array A\n *  \\param [in] ldb leading dimension of two-dimensional array B\n *  \\param [in] ldc leading dimension of two-dimensional array C\n *  \\param [in] alpha scalar used for multiplication\n *  \\param [in] beta scalar used for multiplication\n *  \\param [in, out] rand_state pointer to memory used for random state\n *              use nervana_randStateSizeBytes to allocate the correct size\n *              if stochastic_round is false, this can be NULL\n *  \\param [in] stochastic_round true if stochastic rounding should be used\n *  \\param [in] apply_relu true if a relu should be applied to the result\n *  \\param [in] stream The cudaStream on which the kernel should be launched\n *  \\param [in] grid Choose a specific grid configuration: 0=32x128, 1=128x32, 2=128x64, 3=128x128\n */\n bool nervana_sgemm(float *A, float *B, float *C,\n                    bool a_t, bool b_t,\n                    int m, int n, int k,\n                    int lda, int ldb, int ldc,\n                    float alpha, float beta,\n                    unsigned int *rand_state,\n                    bool stochastic_round, bool apply_relu,\n                    CUstream stream, int grid=-1\n                    );\n\n/** Perform BLAS hgemm on alpha * A * B + beta * C, with the\n *  additional options of stochastic rounding and applying a\n *  rectified linear unit (relu) to the result.  This routine expects\n *  all matrices to be in row-major order.\n *  \\param [in] A Pointer to the data for matrix A\n *  \\param [in] B Pointer to the data for matrix B\n *  \\param [in, out] C Pointer to the data for matrix C\n *  \\param [in] m number of rows of C\n *  \\param [in] n number of columns of C\n *  \\param [in] k inner dimension of multiplication\n *  \\param [in] lda leading dimension of two-dimensional array A\n *  \\param [in] ldb leading dimension of two-dimensional array B\n *  \\param [in] ldc leading dimension of two-dimensional array C\n *  \\param [in] alpha scalar used for multiplication\n *  \\param [in] beta scalar used for multiplication\n *  \\param [in, out] rand_state pointer to memory used for random state\n *              use nervana_randStateSizeBytes to allocate the correct size\n *              if stochastic_round is false, this can be NULL\n *  \\param [in] stochastic_round true if stochastic rounding should be used\n *  \\param [in] apply_relu true if a relu should be applied to the result\n *  \\param [in] stream The cudaStream on which the kernel should be launched\n *  \\param [in] grid Choose a specific grid configuration: 0=32x128, 1=128x32, 2=128x64, 3=128x128\n */\n bool nervana_hgemm(short *A, short *B, short *C,\n                    bool a_t, bool b_t,\n                    int m, int n, int k,\n                    int lda, int ldb, int ldc,\n                    float alpha, float beta,\n                    unsigned int *rand_state,\n                    bool stochastic_round, bool apply_relu,\n                    CUstream stream, int grid=-1\n                    );\n\n bool nervana_sgemm_colmajor(float *A, float *B, float *C,\n                             bool a_t, bool b_t,\n                             int m, int n, int k,\n                             int lda, int ldb, int ldc,\n                             float alpha, float beta,\n                             unsigned int *rand_state,\n                             bool stochastic_round, bool apply_relu,\n                             CUstream stream, int grid=-1\n                             );\n\n bool nervana_hgemm_colmajor(short *A, short *B, short *C,\n                             bool a_t, bool b_t,\n                             int m, int n, int k,\n                             int lda, int ldb, int ldc,\n                             float alpha, float beta,\n                             unsigned int *rand_state,\n                             bool stochastic_round, bool apply_relu,\n                             CUstream stream, int grid=-1\n                             );\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "caffe2/contrib/nervana/nervana_fc_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"nervana.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/fully_connected_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(\n    FC,\n    NERVANA,\n    FullyConnectedOp<CUDAContext, NervanaEngine>);\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(\n    FCGradient,\n    NERVANA,\n    FullyConnectedGradientOp<CUDAContext, NervanaEngine>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/nervana/nervana_fc_op_gpu_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"nervana.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/operators/fully_connected_op.h\"\n#include \"caffe2/utils/math.h\"\n#include \"common/gtest/gtest_extensions.h\"\n\n#include <gtest/gtest.h>\n\nCAFFE2_DECLARE_string(caffe_test_root);\n\nnamespace caffe2 {\n\nnamespace {\nstatic void AddConstInput(const std::vector<int>& shape, const float value,\n                          const string& name, Workspace* ws) {\n  DeviceOption option;\n  option.set_device_type(CUDA);\n  CUDAContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<Tensor<CUDAContext>>();\n  tensor->Resize(shape);\n  math::Set<float, CUDAContext>(tensor->size(), value,\n                                tensor->mutable_data<float>(),\n                                &context);\n  return;\n}\n}  // namespace\n\nTEST(NervanaFullyConnectedTest, Test) {\n  if (!NervanaKernelLoaded()) {\n    SKIP() << \"Nervana kernels are not loaded. Skipping test.\";\n  }\n  Workspace ws;\n  OperatorDef def;\n  def.set_name(\"test\");\n  def.set_type(\"FC\");\n  def.add_input(\"X\");\n  def.add_input(\"W\");\n  def.add_input(\"B\");\n  def.add_output(\"Y\");\n  def.mutable_device_option()->set_device_type(CUDA);\n  def.set_engine(\"NERVANA\");\n  AddConstInput(std::vector<int>{5, 10}, 1., \"X\", &ws);\n  AddConstInput(std::vector<int>{6, 10}, 1., \"W\", &ws);\n  AddConstInput(std::vector<int>{6}, 0.1, \"B\", &ws);\n  unique_ptr<OperatorBase> op(\n      new FullyConnectedOp<CUDAContext, NervanaEngine>(def, &ws));\n  EXPECT_NE(nullptr, op.get());\n  EXPECT_TRUE(op->Run());\n  Blob* Yblob = ws.GetBlob(\"Y\");\n  EXPECT_NE(nullptr, Yblob);\n  auto& Y = Yblob->Get<Tensor<CUDAContext>>();\n  TensorCPU Y_cpu(Y);\n  EXPECT_EQ(Y.size(), 5 * 6);\n  for (int i = 0; i < Y.size(); ++i) {\n    CHECK_LT(Y_cpu.data<float>()[i], 10.11);\n    CHECK_GT(Y_cpu.data<float>()[i], 10.09);\n  }\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/nervana/nervana_init_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/flags.h\"\n\n#include \"nervana_c_api.h\"\n\n\nCAFFE2_DEFINE_string(nervana_cubin_path,\n                     \"/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib/cubin/\",\n                     \"The cubin path for nervana kernels. Currently defaulted \"\n                     \"to the internal fb deployment path.\");\n\nnamespace caffe2 {\n\nnamespace {\nstatic bool g_nervana_kernel_loaded = false;\n}  // namespace\n\nbool NervanaKernelLoaded() { return g_nervana_kernel_loaded; }\n\nbool Caffe2InitializeNervanaKernels(int*, char***) {\n  // If we do not specify the nervana cubin path, we will simply return.\n  if (FLAGS_nervana_cubin_path.size() == 0) {\n    VLOG(1) << \"Nervana cubin loading skipped.\";\n    return true;\n  }\n  g_nervana_kernel_loaded =\n      nervana_loadKernels(FLAGS_nervana_cubin_path.c_str());\n  if (g_nervana_kernel_loaded) {\n    VLOG(1) << \"Loaded nervana kernels from path \"\n            << FLAGS_nervana_cubin_path;\n  } else {\n    // Since this is not a critical error we will just vlog it.\n    VLOG(1) << \"Cannot load nervana gpu kernels from path \"\n            << FLAGS_nervana_cubin_path\n            << \", will disable Caffe2 nervana engines.\";\n  }\n  // We will always return true for this initialization, because the loading\n  // result is kept and accessible via NervanaKernelLoaded(). This allows us\n  // to register an init function but not forcing the user to have to install\n  // nervana kernels, delaying the failure to the first time a nervana kernel\n  // is actually called.\n  return true;\n}\n\nREGISTER_CAFFE2_INIT_FUNCTION(Caffe2InitializeNervanaKernels,\n                              &Caffe2InitializeNervanaKernels,\n                              \"Initialize nervana kernels for caffe2.\");\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/nervana/nervana_math_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"nervana.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nnamespace math {\n\n// Caffe2 gemm provides a simpler interface to the gemm functions, with the\n// limitation that the data has to be contiguous in memory.\ntemplate <>\nvoid Gemm<float, CUDAContext, NervanaEngine>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float* A,\n    const float* B,\n    const float beta,\n    float* C,\n    CUDAContext* context,\n    TensorProto::DataType /*math_type*/) {\n  // Note that cublas follows fortran order, so the order is different from\n  // the cblas convention.\n  int lda = (TransA == CblasNoTrans) ? K : M;\n  int ldb = (TransB == CblasNoTrans) ? N : K;\n  bool a_t = (TransA == CblasTrans);\n  bool b_t = (TransB == CblasTrans);\n  CAFFE_ENFORCE(nervana_sgemm(\n      const_cast<float*>(A),\n      const_cast<float*>(B),\n      C,\n      a_t,\n      b_t,\n      M,\n      N,\n      K,\n      lda,\n      ldb,\n      N,\n      alpha,\n      beta,\n      nullptr,\n      false,\n      false,\n      context->cuda_stream()));\n}\n\n}  // namespace math\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/nnpack/nnpack_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common.h\"\n\n#ifdef CAFFE2_USE_MKL\n#include <mkl.h>\n#endif\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/operators/leaky_relu_op.h\"\n#include \"caffe2/utils/cpuid.h\"\n#include \"caffe2/utils/math.h\"\n#include \"nnpack.h\"\n\nCAFFE2_DEFINE_int(\n    caffe2_nnpack_num_threads, 1,\n    \"The number of nnpack pthreadpool threads.\");\nCAFFE2_DEFINE_bool(\n    caffe2_nnpack_use_mkl_num_threads, true,\n    \"If MKL is built, this sets nnpack to use the same number of threads as \"\n    \"MKL does. This overrides caffe2_nnpack_num_threads if set.\");\n\nnamespace caffe2 {\n////////////////////////////////////////////////////////////////////////////////\n// Helper Functions\n////////////////////////////////////////////////////////////////////////////////\n\nnamespace {\n\nbool has_nnpack() {\n  // nnp_initialize is a noop after the first call so it's safe to invoke it\n  // repeatedly\n  auto nnpack_status = nnp_initialize();\n  return nnpack_status == nnp_status_success;\n}\n\nnnp_convolution_algorithm get_nnp_convolution_algorithm(\n    const std::string& algo) {\n  if (algo == \"AUTO\") {\n    return nnp_convolution_algorithm_auto;\n  }\n  if (algo == \"WINOGRAD\") {\n    return nnp_convolution_algorithm_wt8x8;\n  }\n  if (algo == \"FT16\") {\n    return nnp_convolution_algorithm_ft16x16;\n  }\n  if (algo == \"FT8\") {\n    return nnp_convolution_algorithm_ft8x8;\n  }\n  return nnp_convolution_algorithm_auto;\n}\n\nnnp_convolution_transform_strategy get_nnp_convolution_transform_strategy(\n    const std::string& kts) {\n  if (kts == \"BLOCK\") {\n    return nnp_convolution_transform_strategy_block_based;\n  }\n  if (kts == \"TUPLE\") {\n    return nnp_convolution_transform_strategy_tuple_based;\n  }\n  return nnp_convolution_transform_strategy_block_based;\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Thread Pool\n////////////////////////////////////////////////////////////////////////////////\n\nstatic pthreadpool_t nnpack_threadpool_ = nullptr;\n\npthreadpool_t nnpack_threadpool() {\n  if (nnpack_threadpool_ == nullptr) {\n    enum nnp_status nnpack_status = nnp_initialize();\n    CAFFE_ENFORCE(\n        nnpack_status == nnp_status_success, \"NNPack is not supported here!\");\n    int num_threads = FLAGS_caffe2_nnpack_num_threads;\n    if (FLAGS_caffe2_nnpack_use_mkl_num_threads) {\n#ifdef CAFFE2_USE_MKL\n      num_threads = mkl_get_max_threads();\n#else\n      VLOG(1) << \"I am asked to use MKL num of threads for NNPACK but this \"\n                 \"Caffe2 is not built with MKL. Skipping.\";\n#endif\n    }\n    nnpack_threadpool_ = pthreadpool_create(num_threads);\n  }\n  return nnpack_threadpool_;\n}\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// NNPACK Ops\n////////////////////////////////////////////////////////////////////////////////\n\nclass NNPACKConvOp final : public ConvPoolOpBase<CPUContext> {\n public:\n  NNPACKConvOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CPUContext>(operator_def, ws),\n        algo_(get_nnp_convolution_algorithm(\n            OperatorBase::GetSingleArgument<std::string>(\"algo\", \"AUTO\"))),\n        kts_(get_nnp_convolution_transform_strategy(\n            OperatorBase::GetSingleArgument<std::string>(\"kts\", \"TUPLE\"))) {\n    OPERATOR_NEEDS_FEATURE(\n        this->order_ == StorageOrder::NCHW,\n        \"NNPack only supports NCHW order. Please consider adding \"\n        \"TransposeOp with axes=[0, 3, 1, 2] before NNPack Conv.\");\n    OPERATOR_NEEDS_FEATURE(\n        dilation_h() == 1 && dilation_w() == 1,\n        \"The NNPack convolution does not support dilation yet.\");\n    // NNPACK can be built with avx2 support only and might not be able to run\n    // on a given machine.\n    OPERATOR_NEEDS_FEATURE(has_nnpack(), \"NNPack can't run here. No AVX2?\");\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    auto& X = Input(0);\n    auto& filter = Input(1);\n    auto& bias = Input(2);\n    auto* Y = Output(0);\n\n    const int N = X.dim32(0), C = X.dim32(1), H = X.dim32(2), W = X.dim32(3);\n    const int M = filter.dim32(0);\n\n    CAFFE_ENFORCE(X.ndim() == 4, \"Input dim should be 4\");\n    CAFFE_ENFORCE(filter.ndim(), 4);\n    CAFFE_ENFORCE(C % this->group_ == 0, \"\");\n    CAFFE_ENFORCE(M % this->group_ == 0, \"\");\n    CAFFE_ENFORCE(filter.dim32(1) == C / this->group_, \"\");\n    CAFFE_ENFORCE(filter.dim32(2) == this->kernel_h(), \"\");\n    CAFFE_ENFORCE(filter.dim32(3) == this->kernel_w(), \"\");\n    CAFFE_ENFORCE(bias.size() == M, \"\");\n\n    ConvPoolOpBase<CPUContext>::SetOutputSize(X, Y, filter.dim32(0));\n    const int oH = Y->dim32(2), oW = Y->dim32(3);\n\n    if (N > 1) {\n      CAFFE_ENFORCE_EQ(\n          this->stride_h(),\n          1,\n          \"NNPack only supports stride = 1 when doing batch feedforward\");\n      CAFFE_ENFORCE_EQ(\n          this->stride_w(),\n          1,\n          \"NNPack only supports stride = 1 when doing batch feedforward\");\n    }\n    std::vector<int> pads(\n        {this->pad_t(), this->pad_b(), this->pad_l(), this->pad_r()});\n    std::vector<int> stride({this->stride_h(), this->stride_w()});\n\n    const size_t input_channels = X.dim32(1);\n    const size_t output_channels = Y->dim32(1);\n    const nnp_size input_size = {.width = static_cast<size_t>(X.dim32(3)),\n                                 .height = static_cast<size_t>(X.dim32(2))};\n    // filter is MCHW\n    const nnp_size kernel_size = {\n        .width = static_cast<size_t>(filter.dim32(3)),\n        .height = static_cast<size_t>(filter.dim32(2))};\n    // pad is tblr\n    const nnp_padding padding = {.top = static_cast<size_t>(pads[0]),\n                                 .right = static_cast<size_t>(pads[3]),\n                                 .bottom = static_cast<size_t>(pads[1]),\n                                 .left = static_cast<size_t>(pads[2])};\n\n    const nnp_size output_subsample = {\n        .width = static_cast<size_t>(stride[1]),\n        .height = static_cast<size_t>(stride[0])};\n    if (N == 1) {\n      VLOG(1) << \"Running inference mode\";\n      for (auto g = 0; g < group_; ++g) {\n        const auto status = nnp_convolution_inference(\n            algo_,\n            kts_,\n            C / group_,\n            M / group_,\n            input_size,\n            padding,\n            kernel_size,\n            output_subsample,\n            X.template data<float>() + g * H * W * (C / group_),\n            filter.template data<float>() + filter.size() / group_ * g,\n            bias.template data<float>() + bias.size() / group_ * g,\n            Y->template mutable_data<float>() + g * oH * oW * (M / group_),\n            nnpack_threadpool(),\n            nullptr);\n        CAFFE_ENFORCE(nnp_status_success == status, \"\");\n      }\n    } else {\n      VLOG(1) << \"Running batched mode\";\n      for (auto g = 0; g < group_; ++g) {\n        const auto status = nnp_convolution_output(\n            algo_,\n            N,\n            C / group_,\n            M / group_,\n            input_size,\n            padding,\n            kernel_size,\n            X.template data<float>() + g * H * W * (C / group_),\n            filter.template data<float>() + filter.size() / group_ * g,\n            bias.template data<float>() + bias.size() / group_ * g,\n            Y->template mutable_data<float>() + g * oH * oW * (M / group_),\n            nnpack_threadpool(),\n            nullptr);\n        CAFFE_ENFORCE(nnp_status_success == status, \"\");\n      }\n    }\n    return true;\n  }\n\n private:\n  const nnp_convolution_algorithm algo_;\n  const nnp_convolution_transform_strategy kts_;\n};\n\nclass NNPACKMaxPoolOp final : public ConvPoolOpBase<CPUContext> {\n public:\n  NNPACKMaxPoolOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(\n        this->order_ == StorageOrder::NCHW,\n        \"NNPack only supports NCHW order. Please consider add \"\n        \"TransposeOp with axes=[0, 3, 1, 2] before NNPack Conv.\");\n    OPERATOR_NEEDS_FEATURE(\n        this->kernel_h() == 2, \"NNPack only supports MaxPool kernel size 2*2!\");\n    OPERATOR_NEEDS_FEATURE(\n        this->kernel_w() == 2, \"NNPack only supports MaxPool kernel size 2*2!\");\n    OPERATOR_NEEDS_FEATURE(\n        this->stride_h() == 2, \"NNPack only supports MaxPool stride size 2*2!\");\n    OPERATOR_NEEDS_FEATURE(\n        this->stride_w() == 2, \"NNPack only supports MaxPool stride size 2*2!\");\n    OPERATOR_NEEDS_FEATURE(\n        this->pad_t() == 0,\n        \"NNPack Pooling differs from Caffe2 Pooling when pad > 0!\");\n    OPERATOR_NEEDS_FEATURE(\n        this->pad_l() == 0,\n        \"NNPack Pooling differs from Caffe2 Pooling when pad > 0!\");\n    OPERATOR_NEEDS_FEATURE(\n        this->pad_r() == 0,\n        \"NNPack Pooling differs from Caffe2 Pooling when pad > 0!\");\n    OPERATOR_NEEDS_FEATURE(\n        this->pad_b() == 0,\n        \"NNPack Pooling differs from Caffe2 Pooling when pad > 0!\");\n    // NNPACK can be built with avx2 support only and might not be able to run\n    // on a given machine.\n    OPERATOR_NEEDS_FEATURE(has_nnpack(), \"NNPack can't run here. No AVX2?\");\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n    CAFFE_ENFORCE(X.ndim() == 4, \"\");\n    const int H = X.dim32(2), W = X.dim32(3);\n    ConvPoolOpBase<CPUContext>::SetOutputSize(X, Y, X.dim32(1));\n    std::vector<int> pads(\n        {this->pad_t(), this->pad_b(), this->pad_l(), this->pad_r()});\n    std::vector<int> stride({this->stride_h(), this->stride_w()});\n    std::vector<int> pooling({this->kernel_h(), this->kernel_w()});\n\n    // Input X is in NCHW order\n    const size_t batch_size = X.dim32(0);\n    const size_t input_channels = X.dim32(1);\n    const nnp_size input_size = {.width = static_cast<size_t>(X.dim32(3)),\n                                 .height = static_cast<size_t>(X.dim32(2))};\n    // pooling kernel\n    const nnp_size pooling_size = {.width = static_cast<size_t>(pooling[1]),\n                                   .height = static_cast<size_t>(pooling[0])};\n    // pad is tblr\n    const nnp_padding padding = {.top = static_cast<size_t>(pads[0]),\n                                 .right = static_cast<size_t>(pads[3]),\n                                 .bottom = static_cast<size_t>(pads[1]),\n                                 .left = static_cast<size_t>(pads[2])};\n\n    const nnp_size pooling_stride = {.width = static_cast<size_t>(stride[1]),\n                                     .height = static_cast<size_t>(stride[0])};\n    const auto status = nnp_max_pooling_output(\n        batch_size,\n        input_channels,\n        input_size,\n        padding,\n        pooling_size,\n        pooling_stride,\n        X.template data<float>(),\n        Y->template mutable_data<float>(),\n        nnpack_threadpool());\n    CAFFE_ENFORCE(nnp_status_success == status, \"\");\n    return true;\n  }\n\n private:\n};\n\nclass NNPACKReluOp final : public Operator<CPUContext> {\n public:\n  NNPACKReluOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {\n    // NNPACK can be built with avx2 support only and might not be able to run\n    // on a given machine.\n    OPERATOR_NEEDS_FEATURE(has_nnpack(), \"NNPack can't run here. No AVX2?\");\n  }\n\n  bool RunOnDevice() override {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n    const auto status = nnp_relu_output(\n        1,\n        X.size(),\n        X.template data<float>(),\n        Y->template mutable_data<float>(),\n        0.0,\n        nnpack_threadpool());\n    CAFFE_ENFORCE(nnp_status_success == status, \"\");\n    return true;\n  }\n\n private:\n};\n\nclass NNPACKLeakyReluOp final : public LeakyReluOp<float, CPUContext> {\n public:\n  NNPACKLeakyReluOp(const OperatorDef& operator_def, Workspace* ws)\n      : LeakyReluOp<float, CPUContext>(operator_def, ws) {\n    // NNPACK can be built with avx2 support only and might not be able to run\n    // on a given machine.\n    OPERATOR_NEEDS_FEATURE(has_nnpack(), \"NNPack can't run here. No AVX2?\");\n  }\n\n  bool RunOnDevice() override {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n    const auto status = nnp_relu_output(\n        1,\n        X.size(),\n        X.template data<float>(),\n        Y->template mutable_data<float>(),\n        alpha_,\n        nnpack_threadpool());\n    CAFFE_ENFORCE(nnp_status_success == status, \"\");\n    return true;\n  }\n\n private:\n};\n\nREGISTER_CPU_OPERATOR_WITH_ENGINE(Conv, NNPACK, NNPACKConvOp);\nREGISTER_CPU_OPERATOR_WITH_ENGINE(MaxPool, NNPACK, NNPACKMaxPoolOp);\nREGISTER_CPU_OPERATOR_WITH_ENGINE(Relu, NNPACK, NNPACKReluOp);\nREGISTER_CPU_OPERATOR_WITH_ENGINE(LeakyRelu, NNPACK, NNPACKLeakyReluOp);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/nnpack/nnpack_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given, assume, settings\nimport numpy as np\nimport time\nimport os\nfrom caffe2.python import core, dyndep\nimport caffe2.python.hypothesis_test_util as hu\n\n\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/contrib/nnpack:nnpack_ops\")\n\nnp.random.seed(1)\n\n\ndef benchmark(ws, net, warmups=5, iters=100):\n    for _ in range(warmups):\n        ws.run(net)\n    plan = core.Plan(\"plan\")\n    plan.AddStep(core.ExecutionStep(\"test-step\", net, iters))\n    before = time.time()\n    ws.run(plan)\n    after = time.time()\n    print(\"Timing network, time taken per-iteration: {:.6f}ms\".format((\n        after - before) / float(iters) * 1000.0))\n    return after - before\n\n\ndef has_avx2():\n    import subprocess\n    try:\n        subprocess.check_output([\"grep\", \"avx2\", \"/proc/cpuinfo\"])\n        return True\n    except subprocess.CalledProcessError:\n        # grep exits with rc 1 on no matches\n        return False\n\n\n@unittest.skipIf(not has_avx2(), \"NNPACK requires AVX2\")\nclass NNPackOpsTest(hu.HypothesisTestCase):\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 2),\n           kernel=st.integers(3, 5),\n           size=st.integers(5, 10),\n           input_channels=st.integers(1, 8),\n           output_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 5),\n           groups=st.integers(1, 2))\n    def test_convolution_correctness(self, stride, pad, kernel, size,\n                                     input_channels, output_channels,\n                                     batch_size, groups):\n        assume(input_channels % groups == 0)\n        assume(output_channels % groups == 0)\n        assume(output_channels == input_channels / groups)\n        assume(stride <= kernel)\n        if stride != 1:\n            assume(batch_size == 1)\n\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n        w = np.random.rand(\n            output_channels, input_channels, kernel, kernel).astype(np.float32)\\\n            - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        order = \"NCHW\"\n        outputs = {}\n        for engine in [\"\", \"NNPACK\"]:\n            op = core.CreateOperator(\n                \"Conv\",\n                [\"X\", \"w\", \"b\"],\n                [\"Y\"],\n                stride=stride,\n                kernel=kernel,\n                pad=pad,\n                order=order,\n                kts=\"TUPLE\",\n                engine=engine,\n                group=groups,\n            )\n            self.ws.create_blob(\"X\").feed(X)\n            self.ws.create_blob(\"w\").feed(w)\n            self.ws.create_blob(\"b\").feed(b)\n            self.ws.run(op)\n            outputs[engine] = self.ws.blobs[\"Y\"].fetch()\n        np.testing.assert_allclose(\n            outputs[\"\"],\n            outputs[\"NNPACK\"],\n            atol=1e-4,\n            rtol=1e-4)\n\n    @given(size=st.sampled_from([6, 8]),\n           input_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 5))\n    def test_max_pool_correctness(self, size, input_channels, batch_size):\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n        order = \"NCHW\"\n        outputs = {}\n        # only 2 * 2 stride and 2 * 2 pool is supported in NNPack now\n        stride = 2\n        kernel = 2\n        # The pooling strategy of NNPack is different from caffe2 pooling\n        pad = 0\n        for engine in [\"\", \"NNPACK\"]:\n            op = core.CreateOperator(\n                \"MaxPool\",\n                [\"X\"],\n                [\"Y\"],\n                stride=stride,\n                kernel=kernel,\n                pad=pad,\n                order=order,\n                engine=engine,\n            )\n            self.ws.create_blob(\"X\").feed(X)\n            self.ws.run(op)\n            outputs[engine] = self.ws.blobs[\"Y\"].fetch()\n        np.testing.assert_allclose(\n            outputs[\"\"],\n            outputs[\"NNPACK\"],\n            atol=1e-4,\n            rtol=1e-4)\n\n    @given(size=st.sampled_from([6, 8]),\n           input_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 5))\n    def test_relu_correctness(self, size, input_channels, batch_size):\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n        outputs = {}\n        for engine in [\"\", \"NNPACK\"]:\n            op = core.CreateOperator(\n                \"Relu\",\n                [\"X\"],\n                [\"Y\"],\n                engine=engine,\n            )\n            self.ws.create_blob(\"X\").feed(X)\n            self.ws.run(op)\n            outputs[engine] = self.ws.blobs[\"Y\"].fetch()\n        np.testing.assert_allclose(\n            outputs[\"\"],\n            outputs[\"NNPACK\"],\n            atol=1e-4,\n            rtol=1e-4)\n\n    @given(size=st.sampled_from([6, 8]),\n           input_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 5),\n           alpha=st.floats(0, 1))\n    def test_leaky_relu_correctness(self, size, input_channels, batch_size,\n                                    alpha):\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n        outputs = {}\n        for engine in [\"\", \"NNPACK\"]:\n            op = core.CreateOperator(\n                \"LeakyRelu\",\n                [\"X\"],\n                [\"Y\"],\n                alpha=alpha,\n                engine=engine,\n            )\n            self.ws.create_blob(\"X\").feed(X)\n            self.ws.run(op)\n            outputs[engine] = self.ws.blobs[\"Y\"].fetch()\n        np.testing.assert_allclose(\n            outputs[\"\"],\n            outputs[\"NNPACK\"],\n            atol=1e-4,\n            rtol=1e-4)\n\n    @settings(timeout=3600)\n    @unittest.skipIf(not os.environ.get(\"CAFFE2_BENCHMARK\"), \"Benchmark\")\n    @given(stride=st.integers(1, 1),\n           pad=st.integers(0, 2),\n           kernel=st.sampled_from([3, 5, 7]),\n           size=st.integers(30, 90),\n           input_channels=st.sampled_from([3, 64, 256]),\n           output_channels=st.sampled_from([32, 96, 256]),\n           batch_size=st.sampled_from([32, 64, 96, 128]))\n    def test_timings(self, stride, pad, kernel, size,\n                     input_channels, output_channels, batch_size):\n        assume(stride <= kernel)\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n        w = np.random.rand(output_channels, input_channels,\n                           kernel, kernel).astype(np.float32) - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        order = \"NCHW\"\n        times = {}\n        for engine in [\"\", \"NNPACK\"]:\n            net = core.Net(engine + \"_test\")\n            net.Conv(\n                [\"X\", \"W\", \"b\"], \"Y\",\n                order=order,\n                kernel=kernel,\n                stride=stride,\n                pad=pad,\n                kts=\"TUPLE\",\n                engine=engine,\n            )\n            self.ws.create_blob(\"X\").feed(X)\n            self.ws.create_blob(\"W\").feed(w)\n            self.ws.create_blob(\"b\").feed(b)\n            self.ws.run(net)\n            times[engine] = benchmark(self.ws, net)\n        print(\"Speedup for NNPACK: {:.2f}\".format(\n            times[\"\"] / times[\"NNPACK\"]))\n\n    @settings(timeout=3600)\n    @unittest.skipIf(not os.environ.get(\"CAFFE2_BENCHMARK\"), \"Benchmark\")\n    @given(size=st.integers(30, 90),\n           input_channels=st.sampled_from([3, 64, 256]),\n           batch_size=st.sampled_from([32, 64, 96, 128]))\n    def test_relu_timings(self, size, input_channels, batch_size):\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n        times = {}\n        for engine in [\"\", \"NNPACK\"]:\n            net = core.Net(engine + \"_test\")\n            net.Relu(\n                [\"X\"],\n                [\"Y\"],\n                engine=engine,\n            )\n            self.ws.create_blob(\"X\").feed(X)\n            self.ws.run(net)\n            times[engine] = benchmark(self.ws, net)\n        print(\"Speedup for NNPACK: {:.2f}\".format(\n            times[\"\"] / times[\"NNPACK\"]))\n"
  },
  {
    "path": "caffe2/contrib/playground/AnyExp.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom abc import abstractmethod\n\nfrom caffe2.python import workspace\nfrom caffe2.python import timeout_guard\nfrom caffe2.python import data_parallel_model\nfrom . import checkpoint as checkpoint\n\nfrom . import ModuleRegister as ModuleRegister\nfrom . import module_map as module_map\n\n# instantiate logger outside of distributed operators may trigger error\n# logger need to be created in each idividual operator instead.\nimport os\nimport inspect\nimport time\nimport logging\nlogging.basicConfig()\nlog = logging.getLogger(\"AnyExp\")\nlog.setLevel(logging.DEBUG)\n\n\ndef initOpts(opts):\n\n    workspace.GlobalInit(\n        ['caffe2', '--caffe2_log_level=2', '--caffe2_gpu_memory_tracking=0'])\n\n    assert (opts['distributed']['num_gpus'] > 0 or\n            opts['distributed']['num_cpus'] > 0),\\\n        \"Need to specify num_gpus or num_cpus to decide which device to use.\"\n\n    trainWithCPU = (opts['distributed']['num_gpus'] == 0)\n    num_xpus = opts['distributed']['num_cpus'] if \\\n        trainWithCPU else opts['distributed']['num_gpus']\n    first_xpu = opts['distributed']['first_cpu_id'] if \\\n        trainWithCPU else opts['distributed']['first_gpu_id']\n    opts['distributed']['device'] = 'cpu' if trainWithCPU else 'gpu'\n\n    opts['model_param']['combine_spatial_bn'] =\\\n        trainWithCPU and opts['model_param']['combine_spatial_bn']\n\n    opts['distributed']['num_xpus'] = num_xpus\n    opts['distributed']['first_xpu_id'] = first_xpu\n    opts['temp_var'] = {}\n    opts['temp_var']['metrics_output'] = {}\n\n    return opts\n\n\ndef initDefaultModuleMap():\n    registerModuleMap(module_map)\n\n\ndef registerModuleMap(module_map):\n    ModuleRegister.registerModuleMap(module_map)\n\n\ndef aquireDatasets(opts):\n    myAquireDataModule = ModuleRegister.getModule(opts['input']['input_name_py'])\n    return myAquireDataModule.get_input_dataset(opts)\n\n\ndef createTrainerClass(opts):\n    return ModuleRegister.constructTrainerClass(AnyExpTrainer, opts)\n\n\ndef runShardedTrainLoop(opts, myTrainFun):\n    start_epoch = 0\n    pretrained_model = opts['model_param']['pretrained_model']\n    if pretrained_model != '' and os.path.exists(pretrained_model):\n        # Only want to get start_epoch.\n        start_epoch, prev_checkpointed_lr, best_metric = \\\n            checkpoint.initialize_params_from_file(\n                model=None,\n                weights_file=pretrained_model,\n                num_xpus=1,\n                opts=opts,\n                broadcast_computed_param=True,\n                reset_epoch=opts['model_param']['reset_epoch'],\n            )\n    log.info('start epoch: {}'.format(start_epoch))\n    pretrained_model = None if pretrained_model == '' else pretrained_model\n    ret = None\n\n    pretrained_model = \"\"\n    shard_results = []\n\n    for epoch in range(start_epoch,\n                       opts['epoch_iter']['num_epochs'],\n                       opts['epoch_iter']['num_epochs_per_flow_schedule']):\n        # must support checkpoint or the multiple schedule will always\n        # start from initial state\n        checkpoint_model = None if epoch == start_epoch else ret['model']\n        pretrained_model = None if epoch > start_epoch else pretrained_model\n        shard_results = []\n        # with LexicalContext('epoch{}_gang'.format(epoch),gang_schedule=False):\n        for shard_id in range(opts['distributed']['num_shards']):\n            opts['temp_var']['shard_id'] = shard_id\n            opts['temp_var']['pretrained_model'] = pretrained_model\n            opts['temp_var']['checkpoint_model'] = checkpoint_model\n            opts['temp_var']['epoch'] = epoch\n            opts['temp_var']['start_epoch'] = start_epoch\n            shard_ret = myTrainFun(opts)\n            shard_results.append(shard_ret)\n\n        ret = None\n        # always only take shard_0 return\n        for shard_ret in shard_results:\n            if shard_ret is not None:\n                ret = shard_ret\n                opts['temp_var']['metrics_output'] = ret['metrics']\n                break\n        log.info('ret is: {}'.format(str(ret)))\n\n    return ret\n\n\ndef trainFun():\n    def simpleTrainFun(opts):\n        trainerClass = createTrainerClass(opts)\n        trainer = trainerClass(opts)\n        return trainer.buildModelAndTrain(opts)\n    return simpleTrainFun\n\n\ndef initialize_params_from_file(*args, **kwargs):\n    return checkpoint.initialize_params_from_file(*args, **kwargs)\n\nclass AnyExpTrainer(object):\n\n    def __init__(self, opts):\n        import logging\n        logging.basicConfig()\n        log = logging.getLogger(\"AnyExp\")\n        log.setLevel(logging.DEBUG)\n        self.log = log\n\n        self.opts = opts\n        self.train_dataset = None\n        self.test_dataset = None\n        self.train_df = None\n        self.test_df = None\n\n        self.metrics = {}\n        self.plotsIngredients = []\n\n        self.record_epochs = []\n        self.samples_per_sec = []\n        self.secs_per_train = []\n\n        self.metrics_output = opts['temp_var']['metrics_output']\n\n        first_xpu = opts['distributed']['first_xpu_id']\n        num_xpus = opts['distributed']['num_xpus']\n\n        self.xpus = range(first_xpu, first_xpu + num_xpus)\n\n        self.total_batch_size = \\\n            self.opts['epoch_iter']['batch_per_device'] * \\\n            self.opts['distributed']['num_xpus'] * \\\n            self.opts['distributed']['num_shards']\n        self.epoch_iterations = \\\n            self.opts['epoch_iter']['num_train_sample_per_epoch'] // \\\n            self.total_batch_size\n\n        if len(opts['input']['datasets']) > 0:\n            self.train_df = opts['input']['datasets'][0]\n            if len(opts['input']['datasets']) == 2:\n                self.test_df = opts['input']['datasets'][1]\n        # at this point, the intance of this class becomes many instances\n        # running on different machines.  Most of their attributes are same,\n        # but the shard_ids are different.\n        self.shard_id = opts['temp_var']['shard_id']\n        self.start_epoch = opts['temp_var']['start_epoch']\n        self.epoch = opts['temp_var']['epoch']\n        self.epochs_to_run = opts['epoch_iter']['num_epochs_per_flow_schedule']\n\n        log.info('opts: {}'.format(str(opts)))\n\n    @abstractmethod\n    def get_input_dataset(self, opts):\n        pass\n\n    @abstractmethod\n    def get_model_input_fun(self):\n        pass\n\n    @abstractmethod\n    def init_model(self):\n        pass\n\n    def init_metrics(self):\n        metrics = self.opts['output']['metrics']\n        for metric in metrics:\n            meterClass = self.getMeterClass(metric['meter_py'])\n            # log.info('metric.meter_kargs {}'.format(metric.meter_kargs))\n            # log.info('type meter_kargs {}'.format(type(metric.meter_kargs)))\n            meterInstance = meterClass(opts=self.opts, **metric['meter_kargs'])\n            self.add_metric(metric['name'], meterInstance, metric['is_train'])\n\n    def getMeterClass(self, meterName):\n        return ModuleRegister.getClassFromModule(meterName, meterName)\n\n    def add_metric(self, name, calculator, is_train):\n        metrics = self.metrics\n        metrics[name] = {}\n        metrics[name]['calculator'] = calculator\n        metrics[name]['is_train'] = is_train\n        metrics[name]['output'] = []\n\n    def extendMetricsOutput(self):\n        metrics_output = self.metrics_output\n        if not metrics_output:\n            metrics_output['epochs'] = self.record_epochs\n            metrics_output['samples_per_sec'] = self.samples_per_sec\n            metrics_output['secs_per_train'] = self.secs_per_train\n            for metric, value in self.metrics.items():\n                metrics_output[metric] = value['output']\n        else:\n            metrics_output['epochs'].extend(self.record_epochs)\n            metrics_output['samples_per_sec'].extend(self.samples_per_sec)\n            metrics_output['secs_per_train'].extend(self.secs_per_train)\n            for metric, value in self.metrics.items():\n                metrics_output[metric].extend(value['output'])\n\n    @abstractmethod\n    def init_plots(self):\n        pass\n\n    def add_plot(self, x, x_title, ys, y_title):\n        plotsIngredients = self.plotsIngredients\n        aPlotIngredients = {}\n        aPlotIngredients['x'] = x\n        aPlotIngredients['x_title'] = x_title\n        aPlotIngredients['ys'] = ys\n        aPlotIngredients['y_title'] = y_title\n        plotsIngredients.append(aPlotIngredients)\n\n    @abstractmethod\n    def init_logs(self):\n        pass\n\n    def list_of_epochs(self):\n        iter_end_point = min(self.opts['epoch_iter']['num_epochs'],\n                             self.epoch +\n                             self.opts['epoch_iter']['num_epochs_per_flow_schedule'])\n        return range(self.epoch, iter_end_point)\n\n    def list_of_epoch_iters(self):\n        return range(0, self.epoch_iterations)\n\n    @abstractmethod\n    def fun_per_epoch_b4RunNet(self, epoch):\n        pass\n\n    @abstractmethod\n    def fun_per_epoch_aftRunNet(self, epoch):\n        pass\n\n    def checkpoint(self, epoch):\n        self.model_path = checkpoint.save_model_params(\n            True, self.train_model, self.gen_checkpoint_path(True, epoch + 1),\n            epoch + 1, self.opts, float('-inf'))\n\n    def gen_checkpoint_path(self, is_checkpoint, epoch):\n        if (is_checkpoint):\n            filename = \"model_checkpoint_epoch{}.pkl\".format(epoch)\n        else:\n            filename = \"model_final.pkl\"\n        return self.opts['output']['checkpoint_folder'] + filename\n\n    # @abstractmethod\n    # def gen_checkpoint_path(self, is_checkpoint, epoch):\n    #     pass\n\n    @abstractmethod\n    def fun_per_iter_b4RunNet(self, epoch, epoch_iter):\n        pass\n\n    @abstractmethod\n    def fun_per_iter_aftRunNetB4Test(self, epoch, epoch_iter):\n        pass\n\n    @abstractmethod\n    def fun_per_iter_aftRunNetAftTest(self, epoch, epoch_iter):\n        pass\n\n    @abstractmethod\n    def fun_conclude_operator(self, opts):\n        pass\n\n    def createMetricsPlotsModelsOutputs(self):\n        self.extendMetricsOutput()\n        self.model_output = self.model_path\n\n    @abstractmethod\n    def assembleAllOutputs(self):\n        pass\n\n    @abstractmethod\n    def gen_input_builder_fun(self, model, dataset, is_train):\n        pass\n\n    @abstractmethod\n    def gen_forward_pass_builder_fun(self, model, dataset, is_train):\n        pass\n\n    @abstractmethod\n    def gen_param_update_builder_fun(self, model, dataset, is_train):\n        pass\n\n    @abstractmethod\n    def gen_optimizer_fun(self, model, dataset, is_train):\n        pass\n\n    @abstractmethod\n    def gen_rendezvous_ctx(self, model, dataset, is_train):\n        pass\n\n    # @abstractmethod\n    def planning_output(self):\n        self.init_metrics()\n        self.init_plots()\n        self.init_logs()\n\n    def prep_data_parallel_models(self):\n        self.prep_a_data_parallel_model(self.train_model,\n                                        self.train_dataset, True)\n        self.prep_a_data_parallel_model(self.test_model,\n                                        self.test_dataset, False)\n\n    def prep_a_data_parallel_model(self, model, dataset, is_train):\n        if model is None:\n            pass\n\n        log.info('in prep_a_data_parallel_model')\n\n        param_update = \\\n            self.gen_param_update_builder_fun(model, dataset, is_train) \\\n            if self.gen_param_update_builder_fun is not None else None\n        log.info('in prep_a_data_parallel_model param_update done ')\n\n        optimizer = \\\n            self.gen_optimizer_fun(model, dataset, is_train) \\\n            if self.gen_optimizer_fun is not None else None\n        log.info('in prep_a_data_parallel_model optimizer done ')\n\n        max_ops = self.opts['model_param']['max_concurrent_distributed_ops']\n        data_parallel_model.Parallelize(\n            model,\n            input_builder_fun=self.gen_input_builder_fun(model, dataset, is_train),\n            forward_pass_builder_fun=self.gen_forward_pass_builder_fun(\n                model, dataset, is_train),\n            param_update_builder_fun=param_update,\n            optimizer_builder_fun=optimizer,\n            devices=self.xpus,\n            rendezvous=self.gen_rendezvous_ctx(model, dataset, is_train),\n            broadcast_computed_params=False,\n            optimize_gradient_memory=self.opts['model_param']['memonger'],\n            use_nccl=self.opts['model_param']['cuda_nccl'],\n            max_concurrent_distributed_ops=max_ops,\n            cpu_device=(self.opts['distributed']['device'] == 'cpu'),\n            # \"shared model\" will only keep model parameters for cpu_0 or gpu_0\n            # will cause issue when initialize each gpu_0, gpu_1, gpu_2 ...\n            # shared_model=(self.opts['distributed']['device'] == 'cpu'),\n            combine_spatial_bn=self.opts['model_param']['combine_spatial_bn'],\n        )\n        log.info('in prep_a_data_parallel_model Parallelize done ')\n\n        # log.info(\"Current blobs in workspace: {}\".format(workspace.Blobs()))\n\n        workspace.RunNetOnce(model.param_init_net)\n        log.info('in prep_a_data_parallel_model RunNetOnce done ')\n\n        workspace.CreateNet(model.net)\n        log.info('in prep_a_data_parallel_model CreateNet done ')\n\n    def loadCheckpoint(self):\n        opts = self.opts\n        previous_checkpoint = opts['temp_var']['checkpoint_model']\n        pretrained_model = opts['temp_var']['pretrained_model']\n        num_xpus = opts['distributed']['num_xpus']\n        if (previous_checkpoint is not None):\n            if os.path.exists(previous_checkpoint):\n                log.info('Load previous checkpoint:{}'.format(\n                    previous_checkpoint\n                ))\n                start_epoch, prev_checkpointed_lr, _best_metric = \\\n                    checkpoint.initialize_params_from_file(\n                        model=self.train_model,\n                        weights_file=previous_checkpoint,\n                        num_xpus=num_xpus,\n                        opts=opts,\n                        broadcast_computed_param=True,\n                        reset_epoch=False,\n                    )\n        elif pretrained_model is not None and os.path.exists(pretrained_model):\n            log.info(\"Load pretrained model: {}\".format(pretrained_model))\n            start_epoch, prev_checkpointed_lr, best_metric = \\\n                checkpoint.initialize_params_from_file(\n                    model=self.train_model,\n                    weights_file=pretrained_model,\n                    num_xpus=num_xpus,\n                    opts=opts,\n                    broadcast_computed_param=True,\n                    reset_epoch=opts['model_param']['reset_epoch'],\n                )\n\n        data_parallel_model.FinalizeAfterCheckpoint(self.train_model)\n\n    def buildModelAndTrain(self, opts):\n        log.info('in buildModelAndTrain, trainer_input: {}'.format(str(opts)))\n        log.info(\"check type self: {}\".format(type(self)))\n        log.info(\"check self dir: {}\".format(dir(self)))\n        log.info(\"check self get_input_dataset methods: {}\".\n                 format(inspect.getsource(self.get_input_dataset)))\n        log.info(\"check self gen_input_builder_fun method: {}\".\n                 format(inspect.getsource(self.gen_input_builder_fun)))\n        log.info(\"check self gen_forward_pass_builder_fun method: {}\".\n                 format(inspect.getsource(self.gen_forward_pass_builder_fun)))\n        if self.gen_param_update_builder_fun is not None:\n            log.info(\"check self gen_param_update_builder_fun method: {}\".\n                     format(inspect.getsource(self.gen_param_update_builder_fun)))\n        else:\n            log.info(\"check self gen_optimizer_fun method: {}\".\n                     format(inspect.getsource(self.gen_optimizer_fun)))\n        log.info(\"check self assembleAllOutputs method: {}\".\n                 format(inspect.getsource(self.assembleAllOutputs)))\n\n        self.get_model_input_fun()\n\n        self.init_model()\n\n        self.planning_output()\n\n        self.prep_data_parallel_models()\n\n        self.loadCheckpoint()\n\n        for epoch in self.list_of_epochs():\n\n            log.info(\"start training epoch {}\".format(epoch))\n\n            self.fun_per_epoch_b4RunNet(epoch)\n\n            for epoch_iter in self.list_of_epoch_iters():\n\n                self.iter_start_time = time.time()\n\n                self.fun_per_iter_b4RunNet(epoch, epoch_iter)\n                self.run_training_net()\n                self.fun_per_iter_aftRunNetB4Test(epoch, epoch_iter)\n\n                self.iter_end_time = time.time()\n\n                if (epoch_iter %\n                opts['epoch_iter']['num_train_iteration_per_test'] == 0):\n                    secs_per_train = (self.iter_end_time - self.iter_start_time)\n                    self.secs_per_train.append(secs_per_train)\n\n                    sample_trained = self.total_batch_size\n                    samples_per_sec = sample_trained / secs_per_train\n                    self.samples_per_sec.append(samples_per_sec)\n\n                    self.fract_epoch = (epoch +\n                    float(epoch_iter) / self.epoch_iterations)\n                    self.record_epochs.append(self.fract_epoch)\n\n                    for key in self.metrics:\n                        metric = self.metrics[key]\n                        if not metric['is_train']:\n                            continue\n                        metric['calculator'].Add()\n                        metric['output'].append(metric['calculator'].Compute())\n\n                    self.test_loop_start_time = time.time()\n                    for _test_iter in range(0, opts['epoch_iter']['num_test_iter']):\n                        timeout = 2000.0\n                        with timeout_guard.CompleteInTimeOrDie(timeout):\n                            workspace.RunNet(self.test_model.net.Proto().name)\n                        for key in self.metrics:\n                            metric = self.metrics[key]\n                            if metric['is_train']:\n                                continue\n                            metric['calculator'].Add()\n                    self.test_loop_end_time = time.time()\n                    self.sec_per_test_loop = \\\n                        self.test_loop_end_time - self.test_loop_start_time\n\n                    for metric in self.metrics.values():\n                        if metric['is_train']:\n                            continue\n                        metric['output'].append(metric['calculator'].Compute())\n\n                    logStr = 'epoch:{}/{} iter:{}/{} secs_per_train:{} '.format(\n                        self.fract_epoch, self.opts['epoch_iter']['num_epochs'],\n                        epoch_iter, self.epoch_iterations, secs_per_train)\n                    logStr += 'samples_per_sec:{} loop {} tests takes {} sec'.format(\n                        samples_per_sec, opts['epoch_iter']['num_test_iter'],\n                        self.sec_per_test_loop)\n                    for metric, value in self.metrics.items():\n                        logStr += ' {}:{} '.format(metric, value['output'][-1])\n                    log.info('Iter Stats: {}'.format(logStr))\n\n                self.fun_per_iter_aftRunNetAftTest(epoch, epoch_iter)\n\n            self.checkpoint(epoch)\n\n            self.fun_per_epoch_aftRunNet(epoch)\n\n        self.fun_conclude_operator()\n\n        self.createMetricsPlotsModelsOutputs()\n\n        return self.assembleAllOutputs()\n"
  },
  {
    "path": "caffe2/contrib/playground/AnyExpOnTerm.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport argparse\nimport json\nimport caffe2.contrib.playground.AnyExp as AnyExp\n\nimport logging\nlogging.basicConfig()\nlog = logging.getLogger(\"AnyExpOnTerm\")\nlog.setLevel(logging.DEBUG)\n\nif __name__ == '__main__':\n\n    parser = argparse.ArgumentParser(description='Any Experiment training.')\n    parser.add_argument(\"--parameters-json\", type=json.loads,\n                        help='model options in json format', dest=\"params\")\n\n    args = parser.parse_args()\n    opts = args.params['opts']\n    opts = AnyExp.initOpts(opts)\n    log.info('opts is: {}'.format(str(opts)))\n\n    AnyExp.initDefaultModuleMap()\n\n    opts['input']['datasets'] = AnyExp.aquireDatasets(opts)\n\n    # defined this way so that AnyExp.trainFun(opts) can be replaced with\n    # some other custermized training function.\n    ret = AnyExp.runShardedTrainLoop(opts, AnyExp.trainFun())\n\n    log.info('ret is: {}'.format(str(ret)))\n"
  },
  {
    "path": "caffe2/contrib/playground/ModuleRegister.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport inspect\nimport logging\nlogging.basicConfig()\nlog = logging.getLogger(\"ModuleRegister\")\nlog.setLevel(logging.DEBUG)\n\nMODULE_MAPS = []\n\n\ndef registerModuleMap(module_map):\n    MODULE_MAPS.append(module_map)\n    log.info(\"ModuleRegister get modules from  ModuleMap content: {}\".\n             format(inspect.getsource(module_map)))\n\n\ndef constructTrainerClass(myTrainerClass, opts):\n\n    log.info(\"ModuleRegister, myTrainerClass name is {}\".\n             format(myTrainerClass.__name__))\n    log.info(\"ModuleRegister, myTrainerClass type is {}\".\n             format(type(myTrainerClass)))\n    log.info(\"ModuleRegister, myTrainerClass dir is {}\".\n             format(dir(myTrainerClass)))\n\n    myInitializeModelModule = getModule(opts['model']['model_name_py'])\n    log.info(\"ModuleRegister, myInitializeModelModule dir is {}\".\n             format(dir(myInitializeModelModule)))\n\n    myTrainerClass.init_model = myInitializeModelModule.init_model\n    myTrainerClass.run_training_net = myInitializeModelModule.run_training_net\n    myTrainerClass.fun_per_iter_b4RunNet = \\\n        myInitializeModelModule.fun_per_iter_b4RunNet\n    myTrainerClass.fun_per_epoch_b4RunNet = \\\n        myInitializeModelModule.fun_per_epoch_b4RunNet\n\n    myInputModule = getModule(opts['input']['input_name_py'])\n    log.info(\"ModuleRegister, myInputModule {} dir is {}\".\n             format(opts['input']['input_name_py'], myInputModule.__name__))\n\n    # Override input methods of the myTrainerClass class\n    myTrainerClass.get_input_dataset = myInputModule.get_input_dataset\n    myTrainerClass.get_model_input_fun = myInputModule.get_model_input_fun\n    myTrainerClass.gen_input_builder_fun = myInputModule.gen_input_builder_fun\n\n    # myForwardPassModule = GetForwardPassModule(opts)\n    myForwardPassModule = getModule(opts['model']['forward_pass_py'])\n    myTrainerClass.gen_forward_pass_builder_fun = \\\n        myForwardPassModule.gen_forward_pass_builder_fun\n\n    myParamUpdateModule = getModule(opts['model']['parameter_update_py'])\n    myTrainerClass.gen_param_update_builder_fun =\\\n        myParamUpdateModule.gen_param_update_builder_fun \\\n        if myParamUpdateModule is not None else None\n\n    myOptimizerModule = getModule(opts['model']['optimizer_py'])\n    myTrainerClass.gen_optimizer_fun = \\\n        myOptimizerModule.gen_optimizer_fun \\\n        if myOptimizerModule is not None else None\n\n    myRendezvousModule = getModule(opts['model']['rendezvous_py'])\n    myTrainerClass.gen_rendezvous_ctx = \\\n        myRendezvousModule.gen_rendezvous_ctx \\\n        if myRendezvousModule is not None else None\n\n    # override output module\n    myOutputModule = getModule(opts['output']['gen_output_py'])\n\n    log.info(\"ModuleRegister, myOutputModule is {}\".\n             format(myOutputModule.__name__))\n    myTrainerClass.fun_conclude_operator = myOutputModule.fun_conclude_operator\n    myTrainerClass.assembleAllOutputs = myOutputModule.assembleAllOutputs\n\n    return myTrainerClass\n\n\ndef getModule(moduleName):\n    log.info(\"MODULE_MAPS content {}\".format(str(MODULE_MAPS)))\n    myModule = None\n    for ModuleMap in MODULE_MAPS:\n        log.info(\"iterate through MODULE_MAPS content {}\".\n                 format(str(ModuleMap)))\n        for name, obj in inspect.getmembers(ModuleMap):\n            log.info(\"iterate through MODULE_MAPS a name {}\".format(str(name)))\n            if name == moduleName:\n                log.info(\"AnyExp get module {} with source:{}\".\n                         format(moduleName, inspect.getsource(obj)))\n                myModule = obj\n                return myModule\n    return None\n\n\ndef getClassFromModule(moduleName, className):\n    myClass = None\n    for ModuleMap in MODULE_MAPS:\n        for name, obj in inspect.getmembers(ModuleMap):\n            if name == moduleName:\n                log.info(\"ModuleRegistry from module {} get class {} of source:{}\".\n                         format(moduleName, className, inspect.getsource(obj)))\n                myClass = getattr(obj, className)\n                return myClass\n    return None\n"
  },
  {
    "path": "caffe2/contrib/playground/README.md",
    "content": "# Playground for Caffe2 Models\n\nPlayground is created to allow modelers to reuse the components of their models.  It is based on data parallel model of Caffe2.  Playground provide a framework that takes care of regular trainer iteration procedures and abstracting out APIs that allows user to apply customized model components of their own.  User can swap / exchange /reuse these components without rewriting the whole training script.  Once the components are in place, user can use parameterized launch command to drive their experiments.  This will be convenient for creating large amount of experiments with different components defined for each of them.  It may be used as a tool to explore different model architect / algorithms like optimizer / momentum / learning rate / batch normalization parameters.\n\nPlayground project highlight:\n1. parameter driven: no need to create py script for each experiment, just swap components using parameters.  Very customizable, add your own component and add your opts in the command as you want.\n2. All models follows a typical way of train/testing epoch iteration.  Many aspects can be customized, example: run epoch by loss instead of predetermined iteration\n3. customizable components, trained metrics, also specified with parameters\n4. gpu or cpu training supported\n5. parallel training on multiple host supported\n6. checkpoint, pre-trained model helps with recover interrupted / failed experiment\n\n\n### Example Usage\nPlayground comes with a resnet50 example, located in resnet50demo folder.  To see how playground works, do the following:\n\n1. make sure your caffe2 build successful with openCV and lmdb dependencies supported.\n\n2. make sure you have training/testing datasets ready in folders that can be accessible to trainer / distributed trainers\n\n3. specify a folder that you would like to store your checkpoint model files.\n\n4. use this command to launch a training, verify epochs are running with metrics reported in log and model file store in your checkpoint folder\n\n$ python caffe2/contrib/playground/AnyExpOnTerm.py --parameters-json '{\n\"opts\":{\n\n    \"input\":{\n        \"input_name_py\":\"gfs_IN1k\",\n        \"train_input_path\":\"/mnt/vol/gfsai-oregon/ai-group/datasets/imagenet_lmdb/ilsvrc12_train_lmdb/\",\n        \"test_input_path\":\"/mnt/vol/gfsai-oregon/ai-group/datasets/imagenet_lmdb/ilsvrc12_val_lmdb\",\n        \"scale_jitter_type\": 1, \"color_jitter\": true,      \"color_lighting\": true,\n        \"namespace\": \"aml\",  \"table\": \"imagenet_data\",  \"column_handle\": \"everstore_handle\",\n        \"column_label\": \"label\", \"column_id\": \"image_id\",  \"label_type\": 0,\n        \"train_partition\": {\"ds\": \"2017-07-31\", \"config\": \"imagenet1k\", \"is_train\": \"1\"},\n        \"test_partition\": {\"ds\": \"2017-07-31\", \"config\": \"imagenet1k\", \"is_train\": \"0\"},\n        \"num_classes\":1000, \"loadsize\" : 256, \"imsize\": 224, \"decode_threads\": 8, \"datasets\":[]},\n\n    \"model\":{\n        \"model_name_py\":\"IN1k_resnet50\",\n        \"forward_pass_py\":\"caffe2_resnet50_default_forward\",\n        \"parameter_update_py\":\"explicit_resnet_param_update\",\n        \"optimizer_py\":\"\",\n        \"rendezvous_py\":\"rendezvous_filestore\"},\n\n    \"model_param\":{\n        \"pretrained_model\":\"\", \"reset_epoch\":true, \"memonger\" : true, \"cuda_nccl\": true,\n        \"combine_spatial_bn\":true, \"max_concurrent_distributed_ops\" : 16,\n        \"base_learning_rate\":0.05, \"bn_epsilon\":0.00001, \"bn_momentum\":0.9, \"custom_bn_init\": true,\n        \"bn_init_gamma\":1e-323, \"weight_decay\":1e-4, \"weight_decay_bn\":1e-323},\n\n    \"epoch_iter\":{\n        \"num_train_sample_per_epoch\":512,\n        \"num_test_sample\": 250,\n        \"num_epochs\":10,\n        \"num_epochs_per_flow_schedule\":5,\n        \"num_train_iteration_per_test\": 10,\n        \"batch_per_device\":32,\n        \"num_test_iter\":2},\n\n    \"distributed\":{\n        \"num_shards\":1,\n        \"num_gpus\":2,\n        \"first_gpu_id\":0,\n        \"num_cpus\":4,\n        \"first_cpu_id\":0},\n\n    \"output\":{\n        \"gen_output_py\":\"output_generator\",\n        \"gen_checkpoint_path_py\":\"gen_checkpoint_path\",\n        \"checkpoint_folder\":\"/home/diyu/model_checkpoint/\",\n        \"metrics\":[\n            {\"name\":\"train_loss\",\n             \"meter_py\":\"ComputeLoss\",\n             \"meter_kargs\":{\"blob_name\":\"loss\"},\n             \"is_train\":true},\n            {\"name\":\"test_loss\",\n             \"meter_py\":\"ComputeLoss\",\n             \"meter_kargs\":{\"blob_name\":\"loss\"},\n             \"is_train\":false},\n            {\"name\":\"train_accuracy_top1\",\n             \"meter_py\":\"ComputeTopKAccuracy\",\n             \"meter_kargs\":{\"blob_name\":[\"softmax\", \"label\"], \"topk\":1},\n             \"is_train\":true},\n            {\"name\":\"train_accuracy_top5\",\n             \"meter_py\":\"ComputeTopKAccuracy\",\n             \"meter_kargs\":{\"blob_name\":[\"softmax\", \"label\"], \"topk\":5},\n             \"is_train\":true},\n            {\"name\":\"test_accuracy_top1\",\n             \"meter_py\":\"ComputeTopKAccuracy\",\n             \"meter_kargs\":{\"blob_name\":[\"softmax\", \"label\"], \"topk\":1},\n             \"is_train\":false},\n            {\"name\":\"test_accuracy_top5\",\n             \"meter_py\":\"ComputeTopKAccuracy\",\n             \"meter_kargs\":{\"blob_name\":[\"softmax\", \"label\"], \"topk\":5},\n             \"is_train\":false}],\n        \"plots\":[\n            {\"x\":\"\", \"x_title\":\"\", \"ys\":[\"train_loss\", \"test_loss\"],\n             \"y_title\":\"train and test loss\"},\n            {\"x\":\"epochs\", \"x_title\":\"epochs\",\n             \"ys\":[\"train_accuracy_top1\",\"test_accuracy_top1\",\n                   \"train_accuracy_top5\",\"test_accuracy_top5\"],\n             \"y_title\":\"\"}]}}\n\n}'\n\n5. now you can switch to different components that supplied in resnet50demo folder like so:\n\n   \"forward_pass_py\":\"caffe2_resnet50_default_forward\", --> \"explicit_resnet_forward\"  (which is a resnet100 model)\n\n   and/or\n\n   \"parameter_update_py\":\"caffe2_resnet50_default_param_update\", --> \"explicit_resnet_param_update\"\n\n   playground should be able to launch training epochs and give you results\n\n\n### General Usage Guideline\n\n1. mandatory non empty opts: input_name_py, datasets, model_name_py, forward_pass_py, (parameter_update_py or optimizer_py), rendezvous_py, memonger, all epoch_iter opts, all distributed opts, gen_output_py\n\n2. mandatory nullable opts: pretrained_model, max_concurrent_distributed_ops, combine_spatial_bn\n\n3. other module dependent opts can be changed or removed: the rest of the opts.\n\n4. specify any additional opts depends on your modules' need, directly add them into the command line opts dictionary and no need to change any py code.  You should create your module to make sure they knows how to handle these new opts.  You access your own opts in such a manner:  self.opt['your_own_arg']['your_own_sub_arg']\n\n5. checkpoint is performed at the end of each epoch by default and generated model file can be find in log.  Each checkpoint can be used as pre-trained model to start new experiment.  Make sure new experiment is compatible with pre-trained model if you specified it.  For example, gpu experiment and cpu experiment can not share checkpoint, because the blob names are different.  Any experiments with different blob names can not share checkpoint.\n\n6. The metric and plots are reported when experiment finish running.  Intermediate results are reported in the log of the CreateTrainerAndRunManyEpochs operator as iteration goes on.\n\n7. if num_gpus is specified, the trainer will try to use gpu.  if num_gpus = 0, the trainer will use cpu to train.  For gpu training, batch_per_device are typically 32, for cpu training, batch_per_device is normally set to 2 with num_cpus higher like 8 or 16 depends on your machines' configuration.\n\n8. if train on single host, let \"num_shards\" = 1,  if multiple hosts, specify your \"num_shards\" and start parallelized training from each shards similarly to the resnet50_trainer.py example in caffe2/python/examples/ folder.\n\n\n### Develop Your Own Components\n\n1. Create a folder for your own experiment under caffe2/contrib/playground/ and go to this folder.\n\n2. Create a base model file, for example IN1kResnet50.py.  In this script you need to implement init function and in it, instantiate your train/test model and give them to self.train_model and self.test_model.  In this base model class, you can also chose to override other functions you'd like to customize, for example if you want to iterate according to accuracy instead of fixed number of loops, override list_of_epochs(), and list_of_epoch_iters()\n\n3. Create component py scripts implementing the generators arguments of data_parallel_model.Parallelize().  Total four of them: input_builder_fun, forward_pass_builder_fun,  one of param_update_builder_fun or optimizer_builder_fun, and rendezvous.  This is where you can switch between different components. Examples: for the demo IN1k_resnet50 experiments, I created two different forward function: explicit_resnet50_forward.py and caffe2_resnet50_default_forward.py.  Both implemented the API \"gen_param_update_builder_fun\", which is abstract method in the framework class AnyExp.py\n\n4. Next import the module components you created into module_map.py.  This import is needed to include these packages during building.  Give imported module a module name, normally if module is just a simple file contains some functions, just use the py script file name.  If the module contains class and the class is needed for module input, name it with the class name, examples are the meter classes like compute_loss.  When launching your experiment, in opts for the term “xxx_py” fill in the name you chose in module_map.py.  Playground will find your module and load it.\n\n5. Create as many modules as you need.  Then when you perform your experiment, specify the module you want in opts correspondingly and you can run your experiment with ease.\n\n6. In the demo, the opts item “gen_output_py” uses output_generator.py , which provides a minimum way to generating final experimental result, stored in the form of a dict.  It will allow user to do whatever visualization with these data after the training is finished.\n\n7. Customize your experimental result.  A meter interface is provided to implement your own metrics calculators.  Example compute_loss.py and compute_topk_accuracy.py.  For training metrics, results are calculated right away in each iteration.  For testing metrics, results are accumulated for the whole loop and finally calculated after test iteration finishes.  Once your have your meter class defined, you can start defining what metrics to report in your opts['output']['metrics'] list.  The name you give to your metrics can later be used when you define your plots.  The Playground will always record throughput metrics secs_per_train and samples_per_sec.\n"
  },
  {
    "path": "caffe2/contrib/playground/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/contrib/playground/checkpoint.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nimport cPickle as pickle\nfrom collections import OrderedDict\n\nfrom caffe2.proto import caffe2_pb2\n\nfrom caffe2.python import workspace, core, scope\n\nimport logging\nlogging.basicConfig()\nlog = logging.getLogger(\"AnyExpOnTerm\")\nlog.setLevel(logging.DEBUG)\n\n\ndef initialize_params_from_file(\n        model, weights_file, num_xpus, opts,\n        broadcast_computed_param=False, reset_epoch=False):\n    start_epoch, lr, best_metric = initialize_master_xpu_model_params(\n        model, weights_file, opts, reset_epoch)\n    broadcast_parameters(opts, model, num_xpus, broadcast_computed_param)\n    return start_epoch, lr, best_metric\n\n\ndef initialize_master_xpu_model_params(model, weights_file, opts, reset_epoch):\n    log.info(\"Initializing model params from file: {}\".format(weights_file))\n    with open(weights_file, 'r') as fopen:\n        blobs = pickle.load(fopen)\n    if 'blobs' in blobs:\n        blobs = blobs['blobs']\n\n    start_epoch = 0\n    best_metric = float('-inf')\n    if 'epoch' in blobs:\n        log.info('epoch {} is found in model file'.format(blobs['epoch']))\n        if not reset_epoch:\n            start_epoch = blobs['epoch']\n        else:\n            log.info('Reset epoch')\n    else:\n        log.info('no epoch is found in model file')\n    lr = opts['model_param']['base_learning_rate']\n    if 'lr' in blobs:\n        lr = blobs['lr']\n    if 'best_metric' in blobs and not reset_epoch:\n        best_metric = blobs['best_metric']\n\n    if model is not None:\n        log.info('initialize model parameters using weights file: {}'.format(\n            weights_file\n        ))\n        ws_blobs = workspace.Blobs()\n        unscoped_blob_names = OrderedDict()\n        for blob in model.GetAllParams():\n            unscoped_blob_names[unscope_name(str(blob))] = True\n        root_xpu_id = opts['distributed']['first_xpu_id']\n        device = opts['distributed']['device']\n        caffe2_pb2_DEVICE =\\\n            caffe2_pb2.CUDA if opts['distributed']['device'] == 'gpu'\\\n            else caffe2_pb2.CPU\n        with core.NameScope('{}_{}'.format(device, root_xpu_id)):\n            with core.DeviceScope(core.DeviceOption(caffe2_pb2_DEVICE, 0)):\n                for unscoped_blob_name in unscoped_blob_names.keys():\n                    scoped_blob_name = scoped_name(unscoped_blob_name)\n                    if unscoped_blob_name not in blobs:\n                        log.info('{:s} not found'.format(unscoped_blob_name))\n                        continue\n                    log.info(\n                        '{:s} loaded from weights file into: {:s}'.format(\n                            unscoped_blob_name, scoped_blob_name\n                        )\n                    )\n                    if scoped_blob_name in ws_blobs:\n                        ws_blob = workspace.FetchBlob(scoped_blob_name)\n                        if not ws_blob.shape == blobs[unscoped_blob_name].shape:\n                            log.info(\n                                ('Workspace blob {} with shape {} does '\n                                    'not match weights file shape {}').format(\n                                            unscoped_blob_name, ws_blob.shape,\n                                            blobs[unscoped_blob_name].shape)\n                            )\n                        else:\n                            workspace.FeedBlob(\n                                scoped_blob_name,\n                                blobs[unscoped_blob_name].astype(\n                                    np.float32, copy=False))\n    else:\n        log.info('Skip initializing model parameters from file: {}'.format(\n            weights_file\n        ))\n    log.info('Complete initialize_master_xpu_model_params')\n    return start_epoch, lr, best_metric\n\n\ndef broadcast_parameters(opts, model, num_xpus, broadcast_computed_param=False):\n    if num_xpus == 1:\n        log.info(\"only 1 device. Skip parameter broadcast\")\n        return\n    all_params = [model.GetParams()]\n    if broadcast_computed_param:\n        all_params.append(model.GetComputedParams())\n    caffe2_pb2_DEVICE =\\\n        caffe2_pb2.CUDA if opts['distributed']['device'] == 'gpu'\\\n        else caffe2_pb2.CPU\n    for params in all_params:\n        assert len(params) % num_xpus == 0, \\\n            \"Current model dosen't match device number when loading checkpoint\"\n        params_per_xpu = int(len(params) / num_xpus)\n        for idx in range(params_per_xpu):\n            blobs = [param for param in params[idx::params_per_xpu]]\n            data = workspace.FetchBlob(blobs[0])\n            log.info('Broadcasting {} to'.format(str(blobs[0])))\n            for i, p in enumerate(blobs[1:]):\n                log.info(' |-> {}'.format(str(p)))\n                with core.DeviceScope(core.DeviceOption(caffe2_pb2_DEVICE, i+1)):\n                    workspace.FeedBlob(p, data)\n    log.info(\"Complete parameter broadcast\")\n\n\ndef save_model_params(is_checkpoint, model, checkpoint_path, epoch, opts, best_metric):\n    # best_metric=float('-inf')\n    try:\n        save_model_params_blob(\n            model, checkpoint_path, epoch, opts, best_metric\n        )\n    except Exception as e:\n        log.warning('Exception from save_model_params {}'.format(str(e)))\n    return checkpoint_path\n\n\ndef save_model_params_blob(model, params_file, epoch, opts, best_metric):\n    # best_metric=float('-inf')\n    log.info(\"Saving model params...\")\n    root_xpu_id = opts['distributed']['first_xpu_id']\n    device = opts['distributed']['device']\n    save_params = [str(param) for param in\n                   model.GetParams('{}_{}'.format(device, root_xpu_id))]\n    save_computed_params = [str(param) for param in\n                            model.GetComputedParams('{}_{}'\n                            .format(device, root_xpu_id))]\n    save_blobs = {}\n    save_blobs['epoch'] = epoch\n    save_blobs['best_metric'] = best_metric\n    save_blobs['lr'] = \\\n        workspace.FetchBlob('{}_{}/lr'.format(device, root_xpu_id))\n    for param in save_params + save_computed_params:\n        scoped_blob_name = str(param)\n        unscoped_blob_name = unscope_name(scoped_blob_name)\n        if unscoped_blob_name not in save_blobs:\n            save_blobs[unscoped_blob_name] = workspace.FetchBlob(\n                scoped_blob_name)\n            log.debug(\n                '{:s} -> {:s}'.format(scoped_blob_name, unscoped_blob_name))\n    log.info('to weights file {}'.format(params_file))\n    try:\n        with open(params_file, 'w') as fwrite:\n            pickle.dump(dict(blobs=save_blobs), fwrite, pickle.HIGHEST_PROTOCOL)\n    except IOError as e:\n        log.error('I/O error({0}): {1}'.format(e.errno, e.strerror))\n\n\ndef unscope_name(blob_name):\n    return blob_name[blob_name.rfind(scope._NAMESCOPE_SEPARATOR) + 1:]\n\n\ndef scoped_name(blob_name):\n    return scope.CurrentNameScope() + blob_name\n"
  },
  {
    "path": "caffe2/contrib/playground/compute_loss.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport caffe2.contrib.playground.meter as Meter\nfrom caffe2.python import workspace\n\n\nclass ComputeLoss(Meter.Meter):\n    def __init__(self, opts=None, blob_name=''):\n        self.blob_name = blob_name\n        self.opts = opts\n        self.iter = 0\n        self.value = 0\n\n    def Reset(self):\n        self.iter = 0\n        self.value = 0\n\n    def Add(self):\n        \"\"\"Average values of a blob on each gpu\"\"\"\n        value = 0\n        for idx in range(self.opts['distributed']['first_xpu_id'],\n                         self.opts['distributed']['first_xpu_id'] +\n                         self.opts['distributed']['num_xpus']):\n            value += workspace.FetchBlob('{}_{}/{}'.\n                format(self.opts['distributed']['device'], idx, self.blob_name))\n        self.value += value\n        self.iter += 1\n\n    def Compute(self):\n        result = self.opts['distributed']['num_shards'] * self.value / self.iter\n        self.Reset()\n        return result\n"
  },
  {
    "path": "caffe2/contrib/playground/compute_topk_accuracy.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport caffe2.contrib.playground.meter as Meter\nfrom caffe2.python import workspace\nimport numpy as np\n\n\nclass ComputeTopKAccuracy(Meter.Meter):\n    # Python default arguments are evaluated once when the function is\n    # defined, not each time the function is called\n    # This means that if you use a mutable default argument and mutate it,\n    # you will and have mutated that object for\n    # all future calls to the function as well.\n    # def __init__(self, blob_name=['softmax', 'label'], opts=None, topk=1):\n    def __init__(self, blob_name=None, opts=None, topk=1):\n        if blob_name is None:\n            blob_name = ['softmax', 'label']\n        self.blob_name = blob_name\n        self.opts = opts\n        self.topk = topk\n        self.iter = 0\n        self.value = 0\n\n    def Reset(self):\n        self.iter = 0\n        self.value = 0\n\n    def Add(self):\n        for idx in range(self.opts['distributed']['first_xpu_id'],\n                         self.opts['distributed']['first_xpu_id'] +\n                         self.opts['distributed']['num_xpus']):\n            prefix = '{}_{}/'.format(self.opts['distributed']['device'], idx)\n            softmax = workspace.FetchBlob(prefix + self.blob_name[0])\n            labels = workspace.FetchBlob(prefix + self.blob_name[1])\n            output = np.squeeze(softmax)\n            target = np.squeeze(labels)\n            if len(output.shape) == 1:\n                output = output.reshape((1, output.shape[0]))\n            else:\n                assert len(output.shape) == 2, \\\n                    'wrong output size (1D or 2D expected)'\n            assert len(target.shape) == 1, 'wrong target size (1D expected)'\n            assert output.shape[0] == target.shape[0], \\\n                'target and output do not match'\n\n            N = output.shape[0]\n            pred = np.argsort(-output, axis=1)[:, :self.topk]\n            correct = pred.astype(target.dtype) == np.repeat(\n                target.reshape((N, 1)), [self.topk], axis=1)\n            self.value += np.sum(correct[:, :self.topk])\n            self.iter += N\n\n    def Compute(self):\n        result = self.value / self.iter\n        self.Reset()\n        return result\n"
  },
  {
    "path": "caffe2/contrib/playground/meter.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom abc import abstractmethod\n\n\nclass Meter(object):\n\n    @abstractmethod\n    def __init__(self, **kwargs):\n        pass\n\n    @abstractmethod\n    def Reset(self):\n        pass\n\n    @abstractmethod\n    def Add(self):\n        pass\n\n    @abstractmethod\n    def Compute(self):\n        pass\n"
  },
  {
    "path": "caffe2/contrib/playground/module_map.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n# Input\nimport caffe2.contrib.playground.resnet50demo.\\\n    gfs_IN1k as gfs_IN1k  # noqa\n\n# model\nimport caffe2.contrib.playground.resnet50demo.\\\n    IN1k_resnet50 as IN1k_resnet50 # noqa\n\n# FORWARD_PASS\nimport caffe2.contrib.playground.resnet50demo.\\\n    caffe2_resnet50_default_forward as caffe2_resnet50_default_forward # noqa\n\nimport caffe2.contrib.playground.resnet50demo.\\\n    explicit_resnet_forward as explicit_resnet_forward # noqa\n\n# PARAMETER_UPDATE\nimport caffe2.contrib.playground.resnet50demo.\\\n    caffe2_resnet50_default_param_update as caffe2_resnet50_default_param_update # noqa\n\nimport caffe2.contrib.playground.resnet50demo.\\\n    explicit_resnet_param_update as explicit_resnet_param_update # noqa\n\n# RENDEZVOUS\nimport caffe2.contrib.playground.resnet50demo.\\\n    rendezvous_filestore as rendezvous_filestore # noqa\n\n# OUTPUT\nimport caffe2.contrib.playground.\\\n    output_generator as output_generator # noqa\n\n# METERS\n# for meters, use the class name as your module name in this map\nimport caffe2.contrib.playground.\\\n    compute_loss as ComputeLoss # noqa\n\nimport caffe2.contrib.playground.\\\n    compute_topk_accuracy as ComputeTopKAccuracy # noqa\n"
  },
  {
    "path": "caffe2/contrib/playground/output_generator.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import timeout_guard\n\ndef fun_conclude_operator(self):\n    # Ensure the program exists. This is to \"fix\" some unknown problems\n    # causing the job sometimes get stuck.\n    timeout_guard.EuthanizeIfNecessary(600.0)\n\n\ndef assembleAllOutputs(self):\n    output = {}\n    output['train_model'] = self.train_model\n    output['test_model'] = self.test_model\n    output['model'] = self.model_output\n    output['metrics'] = self.metrics_output\n    return output\n"
  },
  {
    "path": "caffe2/contrib/playground/resnet50demo/IN1k_resnet50.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nfrom caffe2.python import workspace, cnn, core\nfrom caffe2.python import timeout_guard\nfrom caffe2.proto import caffe2_pb2\n\n\ndef init_model(self):\n    train_model = cnn.CNNModelHelper(\n        order=\"NCHW\",\n        name=\"resnet50\",\n        use_cudnn=True,\n        cudnn_exhaustive_search=False\n    )\n    self.train_model = train_model\n\n    test_model = cnn.CNNModelHelper(\n        order=\"NCHW\",\n        name=\"resnet50_test\",\n        use_cudnn=False,\n        cudnn_exhaustive_search=False,\n        init_params=False,\n    )\n    self.test_model = test_model\n\n    self.log.info(\"Model creation completed\")\n\n\ndef fun_per_epoch_b4RunNet(self, epoch):\n    pass\n\n\ndef fun_per_iter_b4RunNet(self, epoch, epoch_iter):\n\n    learning_rate = 0.05\n    for idx in range(self.opts['distributed']['first_xpu_id'],\n                     self.opts['distributed']['first_xpu_id'] +\n                     self.opts['distributed']['num_xpus']):\n        caffe2_pb2_device = caffe2_pb2.CUDA if \\\n            self.opts['distributed']['device'] == 'gpu' else \\\n            caffe2_pb2.CPU\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2_device, idx)):\n            workspace.FeedBlob(\n                '{}_{}/lr'.format(self.opts['distributed']['device'], idx),\n                np.array(learning_rate, dtype=np.float32)\n            )\n\n\ndef run_training_net(self):\n    timeout = 2000.0\n    with timeout_guard.CompleteInTimeOrDie(timeout):\n        workspace.RunNet(self.train_model.net.Proto().name)\n"
  },
  {
    "path": "caffe2/contrib/playground/resnet50demo/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/contrib/playground/resnet50demo/caffe2_resnet50_default_forward.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport caffe2.python.models.resnet as resnet\n\n\ndef gen_forward_pass_builder_fun(self, model, dataset, is_train):\n    def create_resnet50_model_ops(model, loss_scale):\n        [softmax, loss] = resnet.create_resnet50(\n            model,\n            \"data\",\n            num_input_channels=3,\n            num_labels=1000,\n            label=\"label\",\n        )\n        model.Accuracy([softmax, \"label\"], \"accuracy\")\n\n        my_loss_scale = 1. / self.opts['distributed']['num_xpus'] / \\\n            self.opts['distributed']['num_shards']\n\n        loss = model.Scale(loss, scale=my_loss_scale)\n\n        return [loss]\n    return create_resnet50_model_ops\n"
  },
  {
    "path": "caffe2/contrib/playground/resnet50demo/caffe2_resnet50_default_param_update.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n\ndef gen_param_update_builder_fun(self, model, dataset, is_train):\n    if not is_train:\n        return None\n    else:\n        def add_parameter_update_ops(model):\n            model.AddWeightDecay(1e-4)\n            ITER = model.Iter(\"ITER\")\n            stepsz = int(30 *\n                         self.opts['epoch_iter']['num_train_sample_per_epoch'] /\n                         self.total_batch_size)\n            LR = model.net.LearningRate(\n                [ITER],\n                \"lr\",\n                base_lr=self.opts['model_param']['base_learning_rate'],\n                policy=\"step\",\n                stepsize=stepsz,\n                gamma=0.1,\n            )\n\n            params = model.GetParams()\n            assert(len(params) > 0)\n            for param in params:\n                param_grad = model.param_to_grad[param]\n                param_momentum = model.param_init_net.ConstantFill(\n                    [param], param + '_momentum', value=0.0\n                )\n\n                # Update param_grad and param_momentum in place\n                model.net.MomentumSGDUpdate(\n                    [param_grad, param_momentum, LR, param],\n                    [param_grad, param_momentum, param],\n                    momentum=0.9,\n                    nesterov=1\n                )\n\n        return add_parameter_update_ops\n"
  },
  {
    "path": "caffe2/contrib/playground/resnet50demo/explicit_resnet_forward.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport logging\nlogging.basicConfig()\nlog = logging.getLogger(\"AnyExp\")\nlog.setLevel(logging.DEBUG)\n\ndef gen_forward_pass_builder_fun(self, model, dataset, is_train):\n    split = 'train' if is_train else 'test'\n    opts = self.opts\n\n    def model_creator(model, loss_scale):\n        model, softmax, loss = resnet_imagenet_create_model(\n            model=model,\n            data='data',\n            labels='label',\n            split=split,\n            opts=opts,\n            dataset=dataset,\n        )\n        return [loss]\n    return model_creator\n\n\ndef resnet_imagenet_create_model(model, data, labels, split, opts, dataset):\n    model_helper = ResNetModelHelper(model, split, opts)\n    opts_depth = 101\n    log.info(' | ResNet-{} Imagenet'.format(opts_depth))\n    assert opts_depth in BLOCK_CONFIG.keys(), \\\n        'Block config is not defined for specified model depth. Please check.'\n    (n1, n2, n3, n4) = BLOCK_CONFIG[opts_depth]\n\n    num_features = 2048\n    residual_block = model_helper.bottleneck_block\n    if opts_depth in [18, 34]:\n        num_features = 512\n        residual_block = model_helper.basic_block\n\n    num_classes = 1000\n    conv_blob = model.Conv(\n        data, 'conv1', 3, 64, 7, stride=2, pad=3, weight_init=('MSRAFill', {}),\n        bias_init=('ConstantFill', {'value': 0.}), no_bias=0\n    )\n    test_mode = False\n    if split in ['test', 'val']:\n        test_mode = True\n    bn_blob = model.SpatialBN(\n        conv_blob, 'res_conv1_bn', 64,\n        # does not appear to affect test_loss performance\n        # epsilon=1e-3,\n        epsilon=opts['model_param']['bn_epsilon'],\n        # momentum=0.1,\n        momentum=opts['model_param']['bn_momentum'],\n        is_test=test_mode,\n    )\n    relu_blob = model.Relu(bn_blob, bn_blob)\n    max_pool = model.MaxPool(relu_blob, 'pool1', kernel=3, stride=2, pad=1)\n\n    # TODO: This can be further optimized by passing dim_in, dim_out = features,\n    # dim_out = features * 4\n    if opts_depth in [50, 101, 152, 200, 264, 284]:\n        blob_in, dim_in = model_helper.residual_layer(\n            residual_block, max_pool, 64, 256, stride=1, num_blocks=n1,\n            prefix='res2', dim_inner=64\n        )\n        blob_in, dim_in = model_helper.residual_layer(\n            residual_block, blob_in, dim_in, 512, stride=2, num_blocks=n2,\n            prefix='res3', dim_inner=128\n        )\n        blob_in, dim_in = model_helper.residual_layer(\n            residual_block, blob_in, dim_in, 1024, stride=2, num_blocks=n3,\n            prefix='res4', dim_inner=256\n        )\n        blob_in, dim_in = model_helper.residual_layer(\n            residual_block, blob_in, dim_in, 2048, stride=2, num_blocks=n4,\n            prefix='res5', dim_inner=512\n        )\n\n    pool_blob = model.AveragePool(blob_in, 'pool5', kernel=7, stride=1)\n\n    loss_scale = 1. / opts['distributed']['num_xpus'] / \\\n        opts['distributed']['num_shards']\n\n    loss = None\n\n    fc_blob = model.FC(\n        pool_blob, 'pred', num_features, num_classes,\n        # does not appear to affect test_loss performance\n        # weight_init=('GaussianFill', {'std': opts.fc_init_std}),\n        # bias_init=('ConstantFill', {'value': 0.})\n        weight_init=None,\n        bias_init=None)\n    softmax, loss = model.SoftmaxWithLoss(\n        [fc_blob, labels],\n        ['softmax', 'loss'],\n        scale=loss_scale)\n    model.Accuracy(['softmax', labels], 'accuracy')\n    return model, softmax, loss\n\n\nBLOCK_CONFIG = {\n    18: (2, 2, 2, 2),\n    34: (3, 4, 6, 3),\n    50: (3, 4, 6, 3),\n    101: (3, 4, 23, 3),\n    152: (3, 8, 36, 3),\n    284: (3, 32, 64, 3),\n}\n\n\nclass ResNetModelHelper():\n\n    def __init__(self, model, split, opts):\n        self.model = model\n        self.split = split\n        self.opts = opts\n\n    # shortcut type B\n    def add_shortcut(self, blob_in, dim_in, dim_out, stride, prefix):\n        if dim_in == dim_out:\n            return blob_in\n        conv_blob = self.model.Conv(\n            blob_in, prefix, dim_in, dim_out, kernel=1,\n            stride=stride,\n            weight_init=(\"MSRAFill\", {}),\n            bias_init=('ConstantFill', {'value': 0.}), no_bias=1\n        )\n        test_mode = False\n        if self.split in ['test', 'val']:\n            test_mode = True\n        bn_blob = self.model.SpatialBN(\n            conv_blob, prefix + \"_bn\", dim_out,\n            # epsilon=1e-3,\n            # momentum=0.1,\n            epsilon=self.opts['model_param']['bn_epsilon'],\n            momentum=self.opts['model_param']['bn_momentum'],\n            is_test=test_mode,\n        )\n        return bn_blob\n\n    def conv_bn(\n        self, blob_in, dim_in, dim_out, kernel, stride, prefix, group=1, pad=1,\n    ):\n        conv_blob = self.model.Conv(\n            blob_in, prefix, dim_in, dim_out, kernel, stride=stride,\n            pad=pad, group=group,\n            weight_init=(\"MSRAFill\", {}),\n            bias_init=('ConstantFill', {'value': 0.}), no_bias=1\n        )\n        test_mode = False\n        if self.split in ['test', 'val']:\n            test_mode = True\n        bn_blob = self.model.SpatialBN(\n            conv_blob, prefix + \"_bn\", dim_out,\n            epsilon=self.opts['model_param']['bn_epsilon'],\n            momentum=self.opts['model_param']['bn_momentum'],\n            is_test=test_mode,\n        )\n        return bn_blob\n\n    def conv_bn_relu(\n        self, blob_in, dim_in, dim_out, kernel, stride, prefix, pad=1, group=1,\n    ):\n        bn_blob = self.conv_bn(\n            blob_in, dim_in, dim_out, kernel, stride, prefix, group=group,\n            pad=pad\n        )\n        return self.model.Relu(bn_blob, bn_blob)\n\n    # 3(a)this block uses multi-way group conv implementation that splits blobs\n    def multiway_bottleneck_block(\n        self, blob_in, dim_in, dim_out, stride, prefix, dim_inner, group\n    ):\n        blob_out = self.conv_bn_relu(\n            blob_in, dim_in, dim_inner, 1, 1, prefix + \"_branch2a\", pad=0,\n        )\n\n        conv_blob = self.model.GroupConv_Deprecated(\n            blob_out, prefix + \"_branch2b\", dim_inner, dim_inner, kernel=3,\n            stride=stride, pad=1, group=group, weight_init=(\"MSRAFill\", {}),\n            bias_init=('ConstantFill', {'value': 0.}), no_bias=1\n        )\n        test_mode = False\n        if self.split in ['test', 'val']:\n            test_mode = True\n        bn_blob = self.model.SpatialBN(\n            conv_blob, prefix + \"_branch2b_bn\", dim_out,\n            epsilon=self.opts['model_param']['bn_epsilon'],\n            momentum=self.opts['model_param']['bn_momentum'], is_test=test_mode,\n        )\n        relu_blob = self.model.Relu(bn_blob, bn_blob)\n\n        bn_blob = self.conv_bn(\n            relu_blob, dim_inner, dim_out, 1, 1, prefix + \"_branch2c\", pad=0\n        )\n        if self.opts['model_param']['custom_bn_init']:\n            self.model.param_init_net.ConstantFill(\n                [bn_blob + '_s'], bn_blob + '_s',\n                value=self.opts['model_param']['bn_init_gamma'])\n\n        sc_blob = self.add_shortcut(\n            blob_in, dim_in, dim_out, stride, prefix=prefix + \"_branch1\"\n        )\n        sum_blob = self.model.net.Sum([bn_blob, sc_blob], prefix + \"_sum\")\n        return self.model.Relu(sum_blob, sum_blob)\n\n    # 3(c) this block uses cudnn group conv op\n    def group_bottleneck_block(\n        self, blob_in, dim_in, dim_out, stride, prefix, dim_inner, group\n    ):\n        blob_out = self.conv_bn_relu(\n            blob_in, dim_in, dim_inner, 1, 1, prefix + \"_branch2a\", pad=0,\n        )\n        blob_out = self.conv_bn_relu(\n            blob_out, dim_inner, dim_inner, 3, stride, prefix + \"_branch2b\",\n            group=group\n        )\n        bn_blob = self.conv_bn(\n            blob_out, dim_inner, dim_out, 1, 1, prefix + \"_branch2c\", pad=0\n        )\n        if self.opts['model_param']['custom_bn_init']:\n            self.model.param_init_net.ConstantFill(\n                [bn_blob + '_s'], bn_blob + '_s',\n                value=self.opts['model_param']['bn_init_gamma'])\n\n        sc_blob = self.add_shortcut(\n            blob_in, dim_in, dim_out, stride, prefix=prefix + \"_branch1\"\n        )\n        sum_blob = self.model.net.Sum([bn_blob, sc_blob], prefix + \"_sum\")\n        return self.model.Relu(sum_blob, sum_blob)\n\n    # bottleneck residual layer for 50, 101, 152 layer networks\n    def bottleneck_block(\n        self, blob_in, dim_in, dim_out, stride, prefix, dim_inner, group=None\n    ):\n        blob_out = self.conv_bn_relu(\n            blob_in, dim_in, dim_inner, 1, 1, prefix + \"_branch2a\", pad=0,\n        )\n        blob_out = self.conv_bn_relu(\n            blob_out, dim_inner, dim_inner, 3, stride, prefix + \"_branch2b\",\n        )\n        bn_blob = self.conv_bn(\n            blob_out, dim_inner, dim_out, 1, 1, prefix + \"_branch2c\", pad=0\n        )\n        if self.opts['model_param']['custom_bn_init']:\n            self.model.param_init_net.ConstantFill(\n                [bn_blob + '_s'], bn_blob + '_s',\n                value=self.opts['model_param']['bn_init_gamma'])\n\n        sc_blob = self.add_shortcut(\n            blob_in, dim_in, dim_out, stride, prefix=prefix + \"_branch1\"\n        )\n        sum_blob = self.model.net.Sum([bn_blob, sc_blob], prefix + \"_sum\")\n        return self.model.Relu(sum_blob, sum_blob)\n\n    # basic layer for the 18 and 34 layer networks and the CIFAR data netwrorks\n    def basic_block(\n        self, blob_in, dim_in, dim_out, stride, prefix, dim_inner=None,\n        group=None,\n    ):\n        blob_out = self.conv_bn_relu(\n            blob_in, dim_in, dim_out, 3, stride, prefix + \"_branch2a\"\n        )\n        bn_blob = self.conv_bn(\n            blob_out, dim_out, dim_out, 3, 1, prefix + \"_branch2b\", pad=1\n        )\n        sc_blob = self.add_shortcut(\n            blob_in, dim_in, dim_out, stride, prefix=prefix + \"_branch1\"\n        )\n        sum_blob = self.model.net.Sum([bn_blob, sc_blob], prefix + \"_sum\")\n        return self.model.Relu(sum_blob, sum_blob)\n\n    def residual_layer(\n        self, block_fn, blob_in, dim_in, dim_out, stride, num_blocks, prefix,\n        dim_inner=None, group=None\n    ):\n        # prefix is something like: res2, res3, etc.\n        # each res layer has num_blocks stacked\n        for idx in range(num_blocks):\n            block_prefix = \"{}_{}\".format(prefix, idx)\n            block_stride = 2 if (idx == 0 and stride == 2) else 1\n            blob_in = block_fn(\n                blob_in, dim_in, dim_out, block_stride, block_prefix, dim_inner,\n                group\n            )\n            dim_in = dim_out\n        return blob_in, dim_in\n"
  },
  {
    "path": "caffe2/contrib/playground/resnet50demo/explicit_resnet_param_update.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import workspace, core\nfrom caffe2.proto import caffe2_pb2\n\n\ndef gen_param_update_builder_fun(self, model, dataset, is_train):\n    if not is_train:\n        return None\n    else:\n        # from sherlok\n        for idx in range(self.opts['distributed']['first_xpu_id'],\n                         self.opts['distributed']['first_xpu_id'] +\n                         self.opts['distributed']['num_xpus']):\n            with core.DeviceScope(core.DeviceOption(caffe2_pb2.CUDA, idx)):\n                workspace.CreateBlob('{}_{}/lr'.\n                    format(self.opts['distributed']['device'], idx))\n\n        def add_parameter_update_ops(model):\n            model.Iter(\"ITER\")\n            weight_decay = model.param_init_net.ConstantFill(\n                [], 'weight_decay', shape=[1],\n                value=self.opts['model_param']['weight_decay']\n            )\n            weight_decay_bn = model.param_init_net.ConstantFill(\n                [], 'weight_decay_bn', shape=[1],\n                value=self.opts['model_param']['weight_decay_bn']\n            )\n            one = model.param_init_net.ConstantFill(\n                [], \"ONE\", shape=[1], value=1.0\n            )\n\n            '''\n            Add the momentum-SGD update.\n            '''\n            params = model.GetParams()\n            assert(len(params) > 0)\n\n            for param in params:\n                param_grad = model.param_to_grad[param]\n                param_momentum = model.param_init_net.ConstantFill(\n                    [param], param + '_momentum', value=0.0\n                )\n\n                if '_bn' in str(param):\n                    model.WeightedSum(\n                        [param_grad, one, param, weight_decay_bn], param_grad\n                    )\n                else:\n                    model.WeightedSum(\n                        [param_grad, one, param, weight_decay], param_grad\n                    )\n\n                # Update param_grad and param_momentum in place\n                model.net.MomentumSGDUpdate(\n                    [param_grad, param_momentum, 'lr', param],\n                    [param_grad, param_momentum, param],\n                    momentum=0.9,\n                    nesterov=1\n                )\n\n        return add_parameter_update_ops\n"
  },
  {
    "path": "caffe2/contrib/playground/resnet50demo/gfs_IN1k.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n# # example1 using gfs as input source.\n\ndef gen_input_builder_fun(self, model, dataset, is_train):\n    if is_train:\n        input_path = self.opts['input']['train_input_path']\n    else:\n        input_path = self.opts['input']['test_input_path']\n\n    reader = model.CreateDB(\"reader\",\n                            db=input_path,\n                            db_type='lmdb',\n                            shard_id=self.shard_id,\n                            num_shards=self.opts['distributed']['num_shards'],)\n\n    def AddImageInput(model, reader, batch_size, img_size):\n        '''\n        Image input operator that loads data from reader and\n        applies certain transformations to the images.\n        '''\n        data, label = model.ImageInput(\n            reader,\n            [\"data\", \"label\"],\n            batch_size=batch_size,\n            use_caffe_datum=True,\n            mean=128.,\n            std=128.,\n            scale=256,\n            crop=img_size,\n            mirror=1,\n            is_test=True\n        )\n        data = model.StopGradient(data, data)\n\n    def add_image_input(model):\n        AddImageInput(\n            model,\n            reader,\n            batch_size=self.opts['epoch_iter']['batch_per_device'],\n            img_size=self.opts['input']['imsize'],\n        )\n    return add_image_input\n\n\ndef get_input_dataset(opts):\n    return []\n\n\ndef get_model_input_fun(self):\n    pass\n"
  },
  {
    "path": "caffe2/contrib/playground/resnet50demo/rendezvous_filestore.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nfrom caffe2.python import dyndep\ndyndep.InitOpsLibrary('@/caffe2/caffe2/distributed:file_store_handler_ops')\n\n\n# rendezvous should NOT be unique for each operator.  It should have\n# the same run_id on different operators.  say we have two shards,\n# both shards created rendezvous of run_id \"aaa_bbb_epoch_09\", and this\n# rendezvous will wait for two shards to join because max_shards is specified\n# to be 2.  If each shard created an rendezvous with different run_id,\n# each of them are waiting for different rendezvous to join, they will\n# never wait for each other and therefore timeout eventually.\n\ndef gen_rendezvous_ctx(self, model, dataset, is_train):\n    if self.opts['distributed']['num_shards'] < 2:\n        return None\n    # have issue when try to set this up on more shards\n    workspace.RunOperatorOnce(\n        core.CreateOperator(\n            \"FileStoreHandlerCreate\", [], [\"store_handler\"],\n            path=\"/tmp\",\n            prefix=\"epoch.{}\".format(self.epoch),\n        )\n    )\n\n    rendezvous = dict(\n        kv_handler=\"store_handler\",\n        shard_id=self.shard_id,\n        num_shards=self.opts['distributed']['num_shards'],\n        engine=\"GLOO\",\n        # transport=args.distributed_transport,\n        transport=\"tcp\",\n        # interface=interfaces[0],\n        interface=[],\n        exit_nets=None) if is_train else None\n    return rendezvous\n"
  },
  {
    "path": "caffe2/contrib/prof/CMakeLists.txt",
    "content": "if (USE_PROF)\n  set(Caffe2_CONTRIB_PROF_CPU_SRCS\n      \"${CMAKE_CURRENT_SOURCE_DIR}/prof_dag_net.cc\"\n      \"${CMAKE_CURRENT_SOURCE_DIR}/prof_dag_stats_op.cc\"\n  )\n  set(Caffe2_CONTRIB_PROF_GPU_SRCS\n      \"${CMAKE_CURRENT_SOURCE_DIR}/cuda_profile_ops.cc\"\n  )\n\n  if (USE_PROF_HTRACE)\n    set(Caffe2_CONTRIB_PROF_CPU_SRCS ${Caffe2_CONTRIB_PROF_CPU_SRCS}\n        \"${CMAKE_CURRENT_SOURCE_DIR}/htrace_conf.cc\"\n        \"${CMAKE_CURRENT_SOURCE_DIR}/htrace_dag_net.cc\"\n    )\n    set(Caffe2_CONTRIB_PROF_GPU_SRCS ${Caffe2_CONTRIB_PROF_GPU_SRCS}\n        \"${CMAKE_CURRENT_SOURCE_DIR}/htrace_async_dag_net_gpu.cc\"\n    )\n  endif()\n\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${Caffe2_CONTRIB_PROF_CPU_SRCS} PARENT_SCOPE)\n  set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${Caffe2_CONTRIB_PROF_GPU_SRCS} PARENT_SCOPE)\nendif()\n"
  },
  {
    "path": "caffe2/contrib/prof/cuda_profile_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/operator.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\n#include <cuda_profiler_api.h>\n\nnamespace caffe2 {\n\nstatic std::vector<std::string> kCudaProfileConfiguration = {\n    \"gpustarttimestamp\",\n    \"gpuendtimestamp\",\n    \"gridsize3d\",\n    \"threadblocksize\",\n    \"dynsmemperblock\",\n    \"stasmemperblock\",\n    \"regperthread\",\n    \"memtransfersize\",\n    \"memtransferdir\",\n    \"memtransferhostmemtype\",\n    \"streamid\",\n    \"cacheconfigrequested\",\n    \"cacheconfigexecuted\",\n    \"countermodeaggregate\",\n    \"enableonstart 0\",\n    \"active_warps\",\n    \"active_cycles\",\n};\n\nclass CudaProfileInitializeOp : public OperatorBase {\n public:\n  CudaProfileInitializeOp(const OperatorDef& operator_def, Workspace* ws)\n      : OperatorBase(operator_def, ws),\n        output_(GetSingleArgument<std::string>(\"output\", \"/tmp/output\")) {\n    std::array<char, 128> buf;\n    std::string tmpl = \"/tmp/cuda_profile_config.XXXXXX\";\n    CAFFE_ENFORCE_LT(tmpl.size(), buf.size());\n    memcpy(buf.data(), tmpl.data(), tmpl.size());\n    auto result = mktemp(buf.data());\n    CAFFE_ENFORCE_NE(strlen(result), 0, \"mktemp: \", strerror(errno));\n    config_ = result;\n\n    // Write configuration to temporary file\n    {\n      std::ofstream ofs(config_, std::ios::out | std::ios::trunc);\n      CAFFE_ENFORCE(ofs.is_open(), \"ofstream: \", ofs.rdstate());\n      for (const auto& line : kCudaProfileConfiguration) {\n        ofs << line << std::endl;\n      }\n    }\n  }\n\n  ~CudaProfileInitializeOp() {\n    unlink(config_.c_str());\n  }\n\n  virtual bool Run(int /* unused */ /*stream_id*/ = 0) {\n    // If this fails, check the contents of \"output\" for hints.\n    CUDA_CHECK(\n        cudaProfilerInitialize(config_.c_str(), output_.c_str(), cudaCSV));\n    return true;\n  }\n\n protected:\n  std::string config_;\n  std::string output_;\n};\n\nclass CudaProfileStartOp : public OperatorBase {\n public:\n  CudaProfileStartOp(const OperatorDef& operator_def, Workspace* ws)\n      : OperatorBase(operator_def, ws) {}\n\n  virtual bool Run(int /* unused */ /*stream_id*/ = 0) {\n    CUDA_ENFORCE(cudaProfilerStart());\n    return true;\n  }\n};\n\nclass CudaProfileStopOp : public OperatorBase {\n public:\n  CudaProfileStopOp(const OperatorDef& operator_def, Workspace* ws)\n      : OperatorBase(operator_def, ws) {}\n\n  virtual bool Run(int /* unused */ /*stream_id*/ = 0) {\n    CUDA_ENFORCE(cudaProfilerStop());\n    return true;\n  }\n};\n\nOPERATOR_SCHEMA(CudaProfileInitialize);\nOPERATOR_SCHEMA(CudaProfileStart);\nOPERATOR_SCHEMA(CudaProfileStop);\n\nREGISTER_CPU_OPERATOR(CudaProfileInitialize, CudaProfileInitializeOp);\nREGISTER_CPU_OPERATOR(CudaProfileStart, CudaProfileStartOp);\nREGISTER_CPU_OPERATOR(CudaProfileStop, CudaProfileStopOp);\n\nREGISTER_CUDA_OPERATOR(CudaProfileInitialize, CudaProfileInitializeOp);\nREGISTER_CUDA_OPERATOR(CudaProfileStart, CudaProfileStartOp);\nREGISTER_CUDA_OPERATOR(CudaProfileStop, CudaProfileStopOp);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/prof/cuda_profile_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core, dyndep, workspace\n\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/contrib/prof:cuda_profile_ops\")\n\n\nclass CudaProfileOpsTest(unittest.TestCase):\n    @unittest.skipIf(workspace.NumCudaDevices() < 1, \"Need at least 1 GPU\")\n    def test_run(self):\n        net = core.Net(\"net\")\n        net.CudaProfileInitialize([], [], output=\"/tmp/cuda_profile_test\")\n        net.CudaProfileStart([], [])\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CUDA, 0)):\n            net.ConstantFill([], [\"out\"], shape=[1, 3, 244, 244])\n        net.CudaProfileStop([], [])\n\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n"
  },
  {
    "path": "caffe2/contrib/prof/htrace_async_dag_net_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <htrace.hpp>\n\n#include \"caffe2/contrib/prof/htrace_conf.h\"\n#include \"caffe2/core/net_async_dag_gpu.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\nnamespace {\n\nclass HTraceAsyncDAGNet : public AsyncDAGNet {\n public:\n  HTraceAsyncDAGNet(const std::shared_ptr<const NetDef>& net_def, Workspace* ws)\n      : AsyncDAGNet(net_def, ws) {\n    VLOG(1) << \"Constructing HTraceAsyncDAGNet \" << net_def->name();\n\n    for (auto& worker : workers_) {\n      std::thread::id worker_id = worker.get_id();\n      std::stringstream stream;\n      stream << \"worker-scope-\" << worker_id;\n      htrace_worker_scope_map_[worker_id] = std::make_shared<htrace::Scope>(\n          htrace_tracer_, htrace_root_scope_.GetSpanId(), stream.str());\n    }\n  }\n\n  ~HTraceAsyncDAGNet() {\n    VLOG(1) << \"Closing all htrace scopes for workers\";\n\n    // Due to the implementation of htrace,\n    // we need to make sure we delete the scopes in order.\n    // Simply calling map.clear() may not preserve the order.\n    auto iter = htrace_worker_scope_map_.begin();\n    while (iter != htrace_worker_scope_map_.end()) {\n      iter = htrace_worker_scope_map_.erase(iter);\n    }\n  }\n\n protected:\n  bool DoRunAsync() override {\n    htrace::Scope run_scope(\n        htrace_tracer_,\n        htrace_root_scope_.GetSpanId(),\n        \"run-scope-\" + caffe2::to_string(run_count_++));\n    return AsyncDAGNet::DoRunAsync();\n  }\n\n  htrace::Conf htrace_conf_{defaultHTraceConf(name_)};\n  htrace::Tracer htrace_tracer_{\"htrace-tracer\", htrace_conf_};\n  htrace::Sampler htrace_sampler_{&htrace_tracer_, htrace_conf_};\n  htrace::Scope htrace_root_scope_{htrace_tracer_,\n                                   htrace_sampler_,\n                                   \"root-scope\"};\n  std::map<std::thread::id, std::shared_ptr<htrace::Scope>>\n      htrace_worker_scope_map_;\n  int run_count_ = 0;\n};\n\nREGISTER_NET(htrace_async_dag, HTraceAsyncDAGNet);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/prof/htrace_conf.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"htrace_conf.h\"\n\n#include <htrace.hpp>\n#include <algorithm>\n#include <ctime>\n\nCAFFE2_DEFINE_string(\n    caffe2_htrace_span_log_path,\n    \"\",\n    \"Span log path for htrace\");\n\nnamespace caffe2 {\n\nconst string defaultHTraceConf(const string& net_name) {\n  // create a duplicate because we may need to modify the name\n  string net_name_copy(net_name);\n\n  // make sure the net name is a valid file name\n  std::replace(net_name_copy.begin(), net_name_copy.end(), '/', '_');\n  std::replace(net_name_copy.begin(), net_name_copy.end(), '\\\\', '_');\n\n  // take current local time\n  time_t rawtime;\n  std::time(&rawtime);\n  struct tm timeinfo;\n  localtime_r(&rawtime, &timeinfo);\n\n  // and append it to the log file name, in a human-readable format\n  std::string buf;\n  buf.resize(30); // 15 should be enough, but apparently is too short.\n  strftime(&buf[0], buf.size(), \"%Y%m%d_%H%M%S\", &timeinfo);\n  auto datetime = buf.data();\n\n  std::stringstream stream;\n  stream << HTRACE_SPAN_RECEIVER_KEY << \"=local.file;\";\n  stream << HTRACE_SAMPLER_KEY << \"=always;\";\n\n  if (FLAGS_caffe2_htrace_span_log_path.empty()) {\n    stream << HTRACE_LOCAL_FILE_RCV_PATH_KEY << \"=/tmp/htrace_\" << net_name_copy\n           << \"_span_log_\" << datetime << \";\";\n  } else {\n    stream << HTRACE_LOCAL_FILE_RCV_PATH_KEY << \"=\"\n           << FLAGS_caffe2_htrace_span_log_path << \";\";\n  }\n\n  return stream.str();\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/prof/htrace_conf.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/flags.h\"\n\nCAFFE2_DECLARE_string(caffe2_htrace_span_log_path);\n\nnamespace caffe2 {\n\nconst string defaultHTraceConf(const string& net_name);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/prof/htrace_dag_net.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <htrace.hpp>\n\n#include \"caffe2/contrib/prof/htrace_conf.h\"\n#include \"caffe2/core/net_dag.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\nnamespace {\n\nclass HTraceDAGNet : public DAGNetBase {\n public:\n  HTraceDAGNet(const std::shared_ptr<const NetDef>& net_def, Workspace* ws)\n      : DAGNetBase(net_def, ws) {\n    VLOG(1) << \"Constructing HTrace DAG Net \" << net_def->name();\n\n    for (auto& worker : workers_) {\n      std::thread::id worker_id = worker.get_id();\n      std::stringstream stream;\n      stream << \"worker-scope-\" << worker_id;\n      htrace_worker_scope_map_[worker_id] = std::make_shared<htrace::Scope>(\n          htrace_tracer_, htrace_root_scope_.GetSpanId(), stream.str());\n    }\n  }\n\n  bool SupportsAsync() override {\n    return false;\n  }\n\n  ~HTraceDAGNet() {\n    VLOG(1) << \"Closing all htrace scopes for workers\";\n\n    // Due to the implementation of htrace,\n    // we need to make sure we delete the scopes in order.\n    // Simply calling map.clear() may not preserve the order.\n    auto iter = htrace_worker_scope_map_.begin();\n    while (iter != htrace_worker_scope_map_.end()) {\n      iter = htrace_worker_scope_map_.erase(iter);\n    }\n  }\n\n protected:\n  bool DoRunAsync() override {\n    htrace::Scope run_scope(\n        htrace_tracer_,\n        htrace_root_scope_.GetSpanId(),\n        \"run-scope-\" + caffe2::to_string(run_count_++));\n    return DAGNetBase::DoRunAsync();\n  }\n\n  bool RunAt(int /* unused */, const std::vector<int>& chain) override {\n    std::thread::id thread_id = std::this_thread::get_id();\n    auto worker_scope = htrace_worker_scope_map_[thread_id];\n\n    bool success = true;\n    for (const auto idx : chain) {\n      const auto& op = operator_nodes_[idx].operator_;\n      const auto& def = op->debug_def();\n      const string& print_name =\n          (def.name().size()\n               ? def.name()\n               : (op->OutputSize() ? def.output(0) : \"NO_OUTPUT\"));\n      const string& op_type = def.type();\n\n      htrace::Scope operator_scope(\n          htrace_tracer_,\n          worker_scope->GetSpanId(),\n          \"#\" + caffe2::to_string(idx) + \" (\" + print_name + \", \" + op_type +\n              \")\");\n      success &= operator_nodes_[idx].operator_->Run();\n    }\n    return success;\n  }\n\n  htrace::Conf htrace_conf_{defaultHTraceConf(name_)};\n  htrace::Tracer htrace_tracer_{\"htrace-tracer\", htrace_conf_};\n  htrace::Sampler htrace_sampler_{&htrace_tracer_, htrace_conf_};\n  htrace::Scope htrace_root_scope_{htrace_tracer_,\n                                   htrace_sampler_,\n                                   \"root-scope\"};\n  std::map<std::thread::id, std::shared_ptr<htrace::Scope>>\n      htrace_worker_scope_map_;\n  int run_count_ = 0;\n};\n\nREGISTER_NET(htrace_dag, HTraceDAGNet);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/prof/htrace_to_chrome.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package htrace_to_chrome\n# Module caffe2.contrib.prof.htrace_to_chrome\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport argparse\nimport json\nimport re\nimport sys\n\ndisplay_levels = [\"network\", \"worker\", \"operator\", \"kernel\"]\n\n\ndef stop_display(limit, curr):\n    return display_levels.index(limit) <= display_levels.index(curr)\n\n\ndef build_trace_dict(f, start_time, end_time):\n    \"\"\"Creates a python dictionary that has trace ids as keys and the\n    corresponding trace objects as values.\n\n    Input: python file object that points to a file with traces, written by\n    htrace-c's local file span receiver.\n    The exact format shouldn't concern you if you're using htrace-c correctly.\n    https://github.com/apache/incubator-htrace/blob/master/htrace-c.\n\n    Returns: a tuple (trace_dic, root_list), where trace_dic is a dictionary\n    containing all traces parsed from the input file object, and root_list is a\n    list of traces from trace_dic which have no parents.\n    Each value in trace_dic is in the form of another dictionary with the\n    folowing keys:\n        \"begin\"   : timestamp of trace start time, microseconds\n        \"end\"     : timestamp of trace end time, microseconds\n        \"desc\"    : description of trace\n        \"parent\"  : trace id of parent trace\n        \"children\": dictionary of child traces, in the same format as trace_dic\n    \"\"\"\n    trace_dic = {}\n    root_list = []\n    for line in f:\n        h = json.loads(line)\n        if h[\"e\"] < start_time or h[\"b\"] > end_time:\n            continue\n\n        entry = {\"begin\": h[\"b\"], \"end\": h[\"e\"], \"desc\": h[\"d\"]}\n        if \"p\" not in h or len(h[\"p\"]) == 0:\n            root_list.append(entry)\n        else:\n            entry[\"parent\"] = h[\"p\"][0]\n        trace_dic[h[\"a\"]] = entry\n\n    for k, v in trace_dic.items():\n        if \"parent\" not in v:\n            continue\n        parent = trace_dic[v[\"parent\"]]\n        if \"children\" not in parent:\n            parent[\"children\"] = {}\n        parent[\"children\"][k] = v\n\n    return trace_dic, root_list\n\n\ndef generate_chrome_trace(root_list, display):\n    \"\"\"Takes trace objects created by build_trace_dict() and generates a list of\n    python dictionaries that can be written to a file in json format, which in\n    turn can be given to Chrome tracing (chrome://tracing).\n\n    Input: refer to root_list in build_trace_dict()'s return value.\n\n    Output: list of dictionaries that can be directly written to a json file by\n    json.dumps().\n    The dictionary format follows the JSON array format of Chrome tracing.\n    Complete events (\"ph\": \"X\") are used to express most traces; such events\n    will appear as horizontal blocks with lengths equal to the trace duration.\n    Instant events (\"ph\": \"i\") are used for traces with many occurrencs which\n    may make the trace graph unreadable; such events are shown as thin lines.\n    \"\"\"\n    ct = []\n    for root_idx, root in enumerate(root_list):\n        # network-level spans\n        ct.append({\n            \"name\": root[\"desc\"],\n            \"ph\": \"X\",\n            \"ts\": root[\"begin\"],\n            \"dur\": root[\"end\"] - root[\"begin\"],\n            \"pid\": root_idx,\n            \"tid\": root_idx,\n            \"args\": {\n                \"Start timestamp\": root[\"begin\"],\n                \"End timestamp\": root[\"end\"]\n            }\n        })\n\n        for _, v in root[\"children\"].items():\n            # run-scopes and worker-scopes\n            c = {\n                \"name\": v[\"desc\"],\n                \"ph\": \"X\",\n                \"ts\": v[\"begin\"],\n                \"dur\": v[\"end\"] - v[\"begin\"],\n                \"pid\": root_idx,\n                \"args\": {\n                    \"Start timestamp\": v[\"begin\"],\n                    \"End timestamp\": v[\"end\"]\n                }\n            }\n\n            if \"run-scope\" in v[\"desc\"]:\n                c[\"tid\"] = root_idx\n                ct.append(c)\n            else:\n                if stop_display(display, \"network\"):\n                    continue\n\n                m = re.search(\"(?<=worker-scope-)\\d+\", v[\"desc\"])\n                wid = m.group(0)\n                c[\"tid\"] = wid\n                ct.append(c)\n\n                if stop_display(display, \"worker\") or \"children\" not in v:\n                    continue\n                for k_op, v_op in v[\"children\"].items():\n                    # operator scopes\n                    ct.append({\n                        \"name\": v_op[\"desc\"],\n                        \"ph\": \"X\",\n                        \"ts\": v_op[\"begin\"],\n                        \"dur\": v_op[\"end\"] - v_op[\"begin\"],\n                        \"pid\": root_idx,\n                        \"tid\": wid,\n                        \"args\": {\n                            \"Start timestamp\": v_op[\"begin\"],\n                            \"End timestamp\": v_op[\"end\"]\n                        }\n                    })\n\n                    if stop_display(display, \"operator\") or \"children\" not in v_op:\n                        continue\n                    for idx, (k_gpu_op, v_gpu_op) in \\\n                            enumerate(sorted(v_op[\"children\"].items(),\n                                             key=lambda e: e[1][\"begin\"])):\n                        # kernel scopes\n                        if idx == 0:\n                            ct.append({\n                                \"name\": v_op[\"desc\"] + \"-GPU\",\n                                \"ph\": \"X\",\n                                \"ts\": v_gpu_op[\"begin\"],\n                                \"dur\": v_gpu_op[\"end\"] - v_gpu_op[\"begin\"],\n                                \"pid\": root_idx,\n                                \"tid\": wid,\n                                \"args\": {\n                                    \"desc\": \"NEW OPERATOR\",\n                                    \"Start timestamp\": v_gpu_op[\"begin\"],\n                                    \"End timestamp\": v_gpu_op[\"end\"]\n                                }\n                            })\n\n                        ct.append({\n                            \"name\": v_op[\"desc\"] + \"-GPU\",\n                            \"ph\": \"i\",\n                            \"ts\": v_gpu_op[\"begin\"],\n                            \"pid\": root_idx,\n                            \"tid\": wid,\n                            \"args\": {\n                                \"desc\": v_gpu_op[\"desc\"]\n                            }\n                        })\n\n    return ct\n\n\ndef get_argument_parser():\n    parser = argparse.ArgumentParser(\n        description=\"Format conversion from HTrace to Chrome tracing.\")\n    parser.add_argument(\"htrace_log\", type=str, help=\"input htrace span log file\")\n    parser.add_argument(\"--display\",\n                        type=str, choices=display_levels, default=\"operator\",\n                        help=\"deepest level of spans to display (default: operator)\")\n    parser.add_argument(\"--start_time\", type=int, default=-1,\n                        help=\"do not display spans occuring before this timestamp\")\n    parser.add_argument(\"--end_time\", type=int, default=sys.maxsize,\n                        help=\"do not display spans occuring after this timestamp\")\n    return parser\n\n\ndef main():\n    args = get_argument_parser().parse_args()\n    with open(args.htrace_log, \"r\") as f:\n        trace_dic, root_list = build_trace_dict(f, args.start_time, args.end_time)\n\n    ct = generate_chrome_trace(root_list, args.display)\n    print(\"Writing chrome json file to %s.json\" % args.htrace_log)\n    print(\"Now import %s.json in chrome://tracing\" % args.htrace_log)\n    with open(args.htrace_log + \".json\", \"w\") as f:\n        f.write(json.dumps(ct))\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "caffe2/contrib/prof/prof_dag_net.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"prof_dag_net.h\"\n\n#include <cmath>\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n\nnamespace caffe2 {\n\nProfDAGNet::ProfDAGNet(\n    const std::shared_ptr<const NetDef>& net_def,\n    Workspace* ws)\n    : DAGNetBase(net_def, ws), time_per_op_total_(operator_nodes_.size()) {\n  VLOG(1) << \"Constructing ProfDAGNet \" << name_;\n}\n\nProfDAGNet::~ProfDAGNet() {\n  VLOG(1) << \"Closing ProfDAGNet \" << name_;\n  if (runs_ <= 1) {\n    LOG(INFO) << \"Insufficient runs to produce meaningful data.\";\n    return;\n  }\n  PrintStats();\n}\n\nvoid ProfDAGNet::ValidateOpTensorDevices() {\n  bool had_mismatches = false;\n  for (int idx = 0; idx < operator_nodes_.size(); idx++) {\n    const auto& node = operator_nodes_[idx];\n    auto mismatches =\n        ValidateTensorDevices(*node.operator_, node.operator_->debug_def());\n    for (auto& mismatch : mismatches) {\n      had_mismatches = true;\n      LOG(INFO) << \"== PERFORMANCE WARNING == \\n\"\n                << \" Operator \" << node.operator_->debug_def().type()\n                << \" expects GPU \" << mismatch.second.first.cuda_gpu_id()\n                << \" but tensor [\" << mismatch.first << \"] is on GPU \"\n                << mismatch.second.second.cuda_gpu_id();\n    }\n  }\n  if (!had_mismatches) {\n    LOG(INFO) << \"Analyzed operator & blob GPU assignments -- no mismatches\";\n  }\n}\n\nbool ProfDAGNet::DoRunAsync() {\n  runs_++;\n\n  // don't collect statistics from first run\n  if (runs_ <= 1) {\n    bool success = DAGNetBase::DoRunAsync();\n    ValidateOpTensorDevices();\n    return success;\n  }\n\n  CAFFE_ENFORCE(\n      time_per_op_total_.size() == operator_nodes_.size(),\n      \"Data collected for \",\n      time_per_op_total_.size(),\n      \" ops, expected \",\n      operator_nodes_.size(),\n      \" ops.\");\n\n  // Create a copy of cumulative stats before the run so we can\n  // later collect the difference\n  vector<Stats> time_per_op_pre_run(time_per_op_total_);\n  bool success = DAGNetBase::DoRunAsync();\n\n  // Aggregate this run's stats per operator type\n  CaffeMap<string, float> time_per_op_type_run;\n  for (int idx = 0; idx < operator_nodes_.size(); idx++) {\n    const auto& node = operator_nodes_[idx];\n    const string& op_type = node.operator_->debug_def().type();\n    time_per_op_type_run[op_type] +=\n        time_per_op_total_[idx].sum - time_per_op_pre_run[idx].sum;\n    time_per_op_type_total_[op_type].cnt += 1;\n  }\n\n  for (const auto& item : time_per_op_type_run) {\n    time_per_op_type_total_[item.first].sum += item.second;\n    time_per_op_type_total_[item.first].sqrsum += item.second * item.second;\n  }\n\n  return success;\n}\n\nProfDAGProto ProfDAGNet::ProtoMsg(std::pair<std::string, Stats> op_stat) const {\n  ProfDAGProto message;\n  float mean = op_stat.second.sum / (runs_ - 1);\n  float stddev = std::sqrt(op_stat.second.sqrsum / (runs_ - 1) - mean * mean);\n  message.set_mean(mean);\n  message.set_stddev(stddev);\n  message.set_name(op_stat.first);\n  return message;\n}\n\nProfDAGProtos ProfDAGNet::GetOperatorStats() {\n  ProfDAGProtos prof_dag_protos;\n  for (auto& item : time_per_op_type_total_) {\n    auto buf = prof_dag_protos.add_stats();\n    buf->CopyFrom(ProtoMsg(item));\n  }\n  return prof_dag_protos;\n}\n\n// GetPerOperatorCost collects the execution time of each operator, the output\n// is formatted as a map: (netName__opIndex__opType, cost)\nProfDAGProtos ProfDAGNet::GetPerOperatorCost() {\n  CAFFE_ENFORCE(\n      time_per_op_total_.size() == operator_nodes_.size(),\n      \"Data collected for \",\n      time_per_op_total_.size(),\n      \" ops, expected \",\n      operator_nodes_.size(),\n      \" ops.\");\n\n  ProfDAGProtos prof_dag_protos;\n  for (int idx = 0; idx < operator_nodes_.size(); idx++) {\n    const auto& op = operator_nodes_[idx].operator_;\n    const auto& def = op->debug_def();\n    const string& op_type = def.type();\n\n    auto buf = prof_dag_protos.add_stats();\n    std::string op_output_name =\n        name_ + \"___\" + to_string(idx) + \"___\" + op_type;\n    std::pair<std::string, Stats> op_stat =\n        std::pair<std::string, Stats>(op_output_name, time_per_op_total_[idx]);\n    buf->CopyFrom(ProtoMsg(op_stat));\n  }\n  return prof_dag_protos;\n}\n\nbool ProfDAGNet::RunAt(int /* unused */, const std::vector<int>& chain) {\n  bool success = true;\n  Timer timer;\n  for (const auto idx : chain) {\n    // don't collect metrics from first run\n    if (runs_ <= 1) {\n      success &= operator_nodes_[idx].operator_->Run();\n\n    } else {\n      timer.Start();\n      success &= operator_nodes_[idx].operator_->Run();\n      float spent = timer.MilliSeconds();\n\n      CAFFE_ENFORCE(\n          time_per_op_total_.size() > idx,\n          \"Expecting \",\n          time_per_op_total_.size(),\n          \" ops, but op #\",\n          idx,\n          \" was given.\");\n      time_per_op_total_[idx].sum += spent;\n      time_per_op_total_[idx].sqrsum += spent * spent;\n    }\n  }\n  return success;\n}\n\nvoid ProfDAGNet::PrintStats() {\n  CAFFE_ENFORCE(\n      time_per_op_total_.size() == operator_nodes_.size(),\n      \"Data collected for \",\n      time_per_op_total_.size(),\n      \" ops, expected \",\n      operator_nodes_.size(),\n      \" ops.\");\n\n  CAFFE_ENFORCE(runs_ > 1, \"# of runs: \", runs_, \", expected > 1.\");\n  int measured_runs = runs_ - 1;\n\n  LOG(INFO) << \"Measured operators over \" << measured_runs << \" net runs.\";\n\n  for (int idx = 0; idx < operator_nodes_.size(); idx++) {\n    const auto& op = operator_nodes_[idx].operator_;\n    const auto& def = op->debug_def();\n    const string& op_type = def.type();\n    const string& print_name = def.name().size()\n        ? def.name()\n        : (op->OutputSize() ? def.output(0) : \"NO_OUTPUT\");\n\n    float mean = time_per_op_total_[idx].sum / measured_runs;\n    float stddev =\n        std::sqrt(time_per_op_total_[idx].sqrsum / measured_runs - mean * mean);\n    VLOG(1) << \"Op #\" << idx << \" (\" << print_name << \", \" << op_type << \") \"\n            << mean << \" ms/run (\" << stddev << \" ms/run)\";\n  }\n\n  LOG(INFO) << \"Mean time in operator per run (stddev):\";\n  for (const auto& item : time_per_op_type_total_) {\n    float mean = item.second.sum / measured_runs;\n    float stddev = std::sqrt(item.second.sqrsum / measured_runs - mean * mean);\n    LOG(INFO) << std::setw(10) << std::setfill(' ') << mean << \" ms/run (\"\n              << std::setw(10) << std::setfill(' ') << stddev << \" ms/run) \"\n              << \" Op count per run: \" << (item.second.cnt / measured_runs)\n              << \"  \" << item.first;\n  }\n}\n\nnamespace {\n\nREGISTER_NET(prof_dag, ProfDAGNet);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/prof/prof_dag_net.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/net_dag.h\"\n#include \"caffe2/proto/prof_dag.pb.h\"\n\nnamespace caffe2 {\n\nstruct Stats {\n  float sum;\n  float sqrsum;\n  size_t cnt;\n};\n\n/**\n * This net type is identical to DAGNet, except that it\n * measures the time taken for each and every operator.\n *\n * To collect statistics from stable runs, this net ignores the first run.\n * Thus, at least two runs are required for this net to print operator metrics.\n */\nclass ProfDAGNet : public DAGNetBase {\n public:\n  ProfDAGNet(const std::shared_ptr<const NetDef>& net_def, Workspace* ws);\n  ~ProfDAGNet();\n  bool SupportsAsync() override {\n    return false;\n  }\n  ProfDAGProtos GetOperatorStats();\n\n  // GetPerOperatorCost collects the execution time of each operator, the\n  // output is formatted as a map: (netName__opIndex__opType, cost)\n  ProfDAGProtos GetPerOperatorCost();\n\n protected:\n  bool DoRunAsync() override;\n  bool RunAt(int chain_id, const std::vector<int>& chain) override;\n  void PrintStats();\n  void ValidateOpTensorDevices();\n  ProfDAGProto ProtoMsg(std::pair<std::string, Stats> op_stat) const;\n  // Cumulative sum and sum squared time spent per operator instance in net.\n  std::vector<Stats> time_per_op_total_;\n  // Cumulative sum and sum squared time spent per unique operator type.\n  CaffeMap<std::string, Stats> time_per_op_type_total_;\n  int runs_ = 0;\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/prof/prof_dag_stats_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"prof_dag_stats_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(GetProfDagStats, GetProfDagStatsOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(GetProfDagStats)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGets the profiling statistics.\n)DOC\")\n    .Arg(\n        \"per_op\",\n        \"(bool) default to false; False: calculate per-op-type cost.\"\n        \"True: calculate per-op cost, the cost of multiple instances of the same \"\n        \"op will be calculated separately\")\n    .Arg(\n        \"partial_net_name\",\n        \"(string) default to empty; describes the partial name of the ProfDAGNet\")\n    .Arg(\n        \"net_name\",\n        \"(string) default to empty; describes the name of the ProfDAGNet\");\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/prof/prof_dag_stats_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FULLY_CONNECTED_OP_H_\n#define CAFFE2_OPERATORS_FULLY_CONNECTED_OP_H_\n\n#include \"caffe2/contrib/prof/prof_dag_net.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n// This operator outputs the ProfDAGNet stats\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nclass GetProfDagStatsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  GetProfDagStatsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        net_name_(OperatorBase::GetSingleArgument<std::string>(\"net_name\", \"\")),\n        partial_net_name_(OperatorBase::GetSingleArgument<std::string>(\n            \"partial_net_name\",\n            \"\")),\n        per_op_(OperatorBase::GetSingleArgument<bool>(\"per_op\", false)) {\n    ws_ = ws;\n    CAFFE_ENFORCE(\n        !(net_name_.empty() && partial_net_name_.empty()),\n        \"You need to provide net_name or partial_net_name\");\n    CAFFE_ENFORCE(\n        net_name_.empty() || partial_net_name_.empty(),\n        \"You can not provide both net_name and partial_net_name\");\n  }\n  ~GetProfDagStatsOp() {}\n\n  bool RunOnDevice() override {\n    // find the net by net_name_ or partial_net_name\n    NetBase* net = nullptr;\n    if (!net_name_.empty()) {\n      net = ws_->GetNet(net_name_);\n    } else if (!partial_net_name_.empty()) {\n      for (auto& current_net : ws_->Nets()) {\n        if (current_net.find(partial_net_name_) != std::string::npos) {\n          CAFFE_ENFORCE(\n              net == nullptr,\n              \"There are multiple nets with \",\n              partial_net_name_,\n              \" as part of their name\");\n          net = ws_->GetNet(current_net);\n        }\n      }\n      CAFFE_ENFORCE(\n          net,\n          \"Can not find a net with \",\n          partial_net_name_,\n          \" as part of its name\");\n    }\n\n    auto prof_dag_net = dynamic_cast_if_rtti<ProfDAGNet*>(net);\n    CAFFE_ENFORCE(prof_dag_net);\n\n    ProfDAGProtos stats;\n    if (per_op_) {\n      stats = prof_dag_net->GetPerOperatorCost();\n    } else {\n      stats = prof_dag_net->GetOperatorStats();\n    }\n\n    // Write protobuf message to the output blob\n    std::string serialized_data;\n    CAFFE_ENFORCE(stats.SerializeToString(&serialized_data));\n    Output(0)->Resize(1);\n    Output(0)->template mutable_data<std::string>()[0] = serialized_data;\n\n    return true;\n  }\n\n protected:\n  std::string net_name_;\n  std::string partial_net_name_;\n  bool per_op_;\n  Workspace* ws_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_FULLY_CONNECTED_OP_H_\n"
  },
  {
    "path": "caffe2/contrib/script/CMakeLists.txt",
    "content": "# ---[ GPU files\n# ------[ cuDNN\nfile(GLOB tmp *_cudnn.cc)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# ------[ general GPU\nfile(GLOB tmp *_gpu.cc)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# ------[ CUDA sources\nfile(GLOB tmp *.cu)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# exclude test files\nfile(GLOB tmp *_test.cc)\nexclude(Caffe2_GPU_SRCS \"${Caffe2_GPU_SRCS}\" ${tmp})\n\n# ---[ CPU files.\nfile(GLOB tmp *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp})\n# exclude test files and gpu files\nfile(GLOB tmp *_test.cc)\nexclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${tmp})\nexclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${Caffe2_GPU_SRCS})\n\n# ---[ GPU test files\nfile(GLOB tmp *_gpu_test.cc)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} ${tmp})\n\n# ---[ CPU test files\nfile(GLOB tmp *_test.cc)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${tmp})\nexclude(Caffe2_CPU_TEST_SRCS \"${Caffe2_CPU_TEST_SRCS}\" ${Caffe2_GPU_TEST_SRCS})\n\n# ---[ Send the lists to the parent scope.\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/contrib/script/caffe2_script_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom hypothesis import given\n\nfrom caffe2.python import core, workspace\nfrom caffe2.proto import caffe2_pb2\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\n\nimport numpy as np\n\n\ndef feed_inputs(inputs):\n    for name, value in inputs.items():\n        workspace.FeedBlob(name, value)\n\n\ndef assert_proto_equals(proto, expected):\n    proto_lines = proto.strip().split('\\n')\n    expected_lines = expected.strip().split('\\n')\n    assert len(proto_lines) == len(expected_lines), \\\n        '{} != {}'.format(proto, expected)\n    for left, right in zip(proto_lines, expected_lines):\n        assert left.strip() == right.strip(), \\\n            '{} != {}'.format(proto, expected)\n\n\nclass TestCaffe2Script(hu.HypothesisTestCase):\n    test_program = \"\"\"\n          def foo(a,b,X,W) -> (c):\n              t = a + b*b\n              c = FC(X,W,t)\n          def testIf(c0,c1,t,f) -> (r):\n              if c0 < c1:\n                  r = t\n              else:\n                  r = f\n              r = Add(r,3f,broadcast=1)\n          def testWhile(r) -> (r):\n              m = 0\n              while m < 4:\n                  # Plus operator automatically broadcasts, and we cannot\n                  # do in-place B and C arguments when we broadcast, so use\n                  # an explicit Add op.\n                  r = Add(r, r)\n                  m = m + 1\n      \"\"\"\n\n    @given(firstdim=st.integers(min_value=1, max_value=4096),\n           seconddim=st.integers(min_value=1, max_value=4096),\n           seed=st.integers(min_value=0, max_value=65536),\n           **hu.gcs)\n    def test_foo(self, firstdim, seconddim, seed, gc, dc):\n        np.random.seed(int(seed))\n        inputs = {}\n        a = inputs['a'] = np.random.rand(seconddim).astype(np.float32)\n        b = inputs['b'] = np.random.rand(seconddim).astype(np.float32)\n        X = inputs['X'] = np.random.rand(firstdim, firstdim).astype(np.float32)\n        W = inputs['W'] = np.random.rand(seconddim, firstdim).astype(np.float32)\n\n        feed_inputs(inputs)\n\n        CU = core.C.CompilationUnit()\n        CU.define(self.test_program)\n        CU.create_net('foo').run()\n\n        ref_t = a + b * b\n        ref_c = np.matmul(X, W.transpose()) + ref_t\n        actual_c = workspace.FetchBlob('c')\n\n        np.testing.assert_allclose(actual_c, ref_c, rtol=1e-05)\n\n    def test_trinary(self):\n        CU = core.C.CompilationUnit()\n        CU.define(\"\"\"\n            def foo(c) -> (d):\n                d = 1 + (2 if c else 4)\n        \"\"\")\n        workspace.FeedBlob('c', np.ones((1), dtype=bool))\n        net = CU.create_net('foo')\n        net.run()\n        assert(3 == workspace.FetchBlob('d'))\n        workspace.FeedBlob('c', np.zeros((1), dtype=bool))\n        net.run()\n        assert(5 == workspace.FetchBlob('d'))\n\n    def test_bool_literal(self):\n        CU = core.C.CompilationUnit()\n        CU.define(\"\"\"\n            def foo() -> (a,b):\n                a = True\n                b = False\n        \"\"\")\n        net = CU.create_net('foo')\n        net.run()\n        assert(workspace.FetchBlob('a'))\n        assert(not workspace.FetchBlob('b'))\n\n    def test_bool_operators(self):\n        CU = core.C.CompilationUnit()\n        CU.define(\"\"\"\n            def foo() -> (a, b, c, d, e):\n                a = True and False\n                b = True or False\n                c = not b\n                d = not False or True\n                e = not (1 if a else 0) == (1 if b else 0)\n        \"\"\")\n        net = CU.create_net('foo')\n        net.run()\n        assert(not workspace.FetchBlob('a'))\n        assert(workspace.FetchBlob('b'))\n        assert(not workspace.FetchBlob('c'))\n        assert(workspace.FetchBlob('d'))\n        assert(workspace.FetchBlob('e'))\n\n    def expect_fail(self, fn, msg):\n        try:\n            fn()\n        except RuntimeError as r:\n            if msg not in str(r):\n                raise RuntimeError(\n                    \"Failed wrong: expected string '{}' \".format(msg) +\n                    \"in error message but found\\n{}\".format(str(r)))\n\n    def test_fails(self):\n        def fail_inputs():\n            CU = core.C.CompilationUnit()\n            CU.define(\"\"\"\n                def foo() -> ():\n                    Print(1,4)\n            \"\"\")\n        self.expect_fail(fail_inputs, \"expects 1 inputs but found 2\")\n\n        def fail_undef():\n            CU = core.C.CompilationUnit()\n            CU.define(\"\"\"\n                def foo(a) -> (b):\n                    a = what()\n            \"\"\")\n        self.expect_fail(fail_undef, \"attempting to call unknown operation\")\n\n        def fail_schema():\n            CU = core.C.CompilationUnit()\n            CU.define(\"\"\"\n                def foo(a) -> (b):\n                    a = FC(a,a,a)\n            \"\"\")\n        self.expect_fail(fail_schema, \"failed schema checking\")\n\n    def test_print(self):\n        CU = core.C.CompilationUnit()\n        CU.define(\"\"\"\n            def foo() -> ():\n                a = 1\n                Print(a)\n                Print(a+1)\n                _ = 4\n                Print(_) # verify in print this isn't _ but some temorary\n                Print(1)\n                Print(1.f)\n                Print(3.0)\n        \"\"\")\n        net = CU.create_net('foo')\n        net.run()\n\n    def test_method(self):\n        CU = core.C.CompilationUnit()\n        CU.define(\"\"\"\n            def foo() -> (a):\n                a = (3+1).Add(4).Add(1)\n        \"\"\")\n        net = CU.create_net('foo')\n        net.run()\n        assert(9 == workspace.FetchBlob('a'))\n\n    def test_plus_eq(self):\n        CU = core.C.CompilationUnit()\n        CU.define(\"\"\"\n            def foo() -> (a):\n                a = 4\n                a += 1\n        \"\"\")\n        net = CU.create_net('foo')\n        net.run()\n        assert(5 == workspace.FetchBlob('a'))\n\n    def test_cast(self):\n        CU = core.C.CompilationUnit()\n        CU.define(\"\"\"\n            def foo() -> (a):\n                a = int(4.5f)\n        \"\"\")\n        net = CU.create_net('foo')\n        net.run()\n        assert(4 == workspace.FetchBlob('a'))\n\n    def test_global(self):\n        CU = core.C.CompilationUnit()\n        CU.define(\"\"\"\n            def foo() -> (a):\n                global m\n                m.a = 4\n                m.b = 5\n                a = m.a + m.b\n        \"\"\")\n        net = CU.create_net('foo')\n        net.run()\n        assert(9 == workspace.FetchBlob('a'))\n\n    def test_module_as_arg_ret(self):\n        CU = core.C.CompilationUnit()\n        CU.define(\"\"\"\n            def bar(a,c) -> (b):\n                b = Module()\n                temp = a.second\n                b.first = temp\n                b.second = a.first + c\n            def foo() -> (a,b):\n                x = Module()\n                x.first = 1\n                x.second = 2\n                x.y = bar(x,4)\n                a = x.y.first\n                b = x.y.second\n        \"\"\")\n        net = CU.create_net('foo')\n        net.run()\n        assert(2 == workspace.FetchBlob('a'))\n        assert(5 == workspace.FetchBlob('b'))\n\n    def test_call_extern(self):\n        CU = core.C.CompilationUnit()\n        net = caffe2_pb2.NetDef()\n        net.op.extend([\n            core.CreateOperator(\n                'Mul',\n                ['i', 'i'],\n                ['o'],\n            )\n        ])\n        net.external_input.append('i')\n        net.external_output.append('o')\n\n        CU.extern(\"myActualExtern\", net)\n        CU.define(\"\"\"\n            def myExtern(x) -> (y):\n                t = x\n                if t > 1:\n                    y = t * t\n                else:\n                    y = 5\n            def foo() -> (b):\n                a = 4\n                a += 1\n                b = 2 + myExtern(a) + myExtern(a, rename=False) + myActualExtern(a)\n        \"\"\")\n        net = CU.create_net('foo')\n        net.run()\n        assert(77 == workspace.FetchBlob('b'))\n\n    @given(seed=st.integers(min_value=0, max_value=65536), **hu.gcs)\n    def test_if(self, seed, gc, dc):\n        np.random.seed(int(seed))\n        inputs = {}\n        c0 = inputs['c0'] = np.random.rand(1).astype(np.float32)\n        c1 = inputs['c1'] = np.random.rand(1).astype(np.float32)\n        t = inputs['t'] = np.random.rand(3, 3).astype(np.float32)\n        f = inputs['f'] = np.random.rand(3, 3).astype(np.float32)\n\n        feed_inputs(inputs)\n\n        CU = core.C.CompilationUnit()\n        CU.define(self.test_program)\n        CU.create_net('testIf').run()\n\n        if c0 < c1:\n            ref_r = t + 3\n        else:\n            ref_r = f + 3\n        actual_r = workspace.FetchBlob('r')\n\n        np.testing.assert_allclose(actual_r, ref_r)\n\n    @given(seed=st.integers(min_value=0, max_value=65536), **hu.gcs)\n    def test_while(self, seed, gc, dc):\n        np.random.seed(int(seed))\n        inputs = {}\n        r = inputs['r'] = np.ones([3, 3]).astype(np.float32)\n\n        feed_inputs(inputs)\n\n        CU = core.C.CompilationUnit()\n        CU.define(self.test_program)\n        CU.create_net('testWhile').run()\n\n        m = 0\n        while m < 4:\n            r = r + r\n            m = m + 1\n\n        actual_r = workspace.FetchBlob('r')\n\n        np.testing.assert_allclose(actual_r, r)\n\n    @given(seed=st.integers(min_value=0, max_value=65536), **hu.gcs)\n    def test_gather(self, seed, gc, dc):\n        CU = core.C.CompilationUnit()\n        CU.define(\"\"\"\n        def easy(tensor, indices) -> (output):\n            output = tensor[indices]\n        def hard(tensor, i, j, k) -> (output):\n            output = tensor[i][j][k]\n        \"\"\")\n\n        # First check that the generated proto is as expected. This tests that\n        # we desugar the gather syntax correctly and emit the right code.\n        proto = CU.get_proto('easy')\n        assert_proto_equals(proto, \"\"\"\n            name: \"easy\"\n            op {\n              input: \"tensor\"\n              input: \"indices\"\n              output: \"output\"\n              type: \"Gather\"\n            }\"\"\")\n\n        proto = CU.get_proto('hard')\n        assert_proto_equals(proto, \"\"\"\n            name: \"hard\"\n            op {\n              input: \"tensor\"\n              input: \"i\"\n              output: \"$t1\"\n              type: \"Gather\"\n            }\n            op {\n              input: \"$t1\"\n              input: \"j\"\n              output: \"$t0\"\n              type: \"Gather\"\n            }\n            op {\n              input: \"$t0\"\n              input: \"k\"\n              output: \"output\"\n              type: \"Gather\"\n            }\"\"\")\n\n        # Now just test that the effect of the generated code is as expected.\n        np.random.seed(int(seed))\n        tensor = np.random.rand(5, 4, 3).astype(np.float32)\n        indices = np.random.randint(len(tensor), size=(5, 5))\n\n        feed_inputs(dict(tensor=tensor, indices=indices))\n\n        net = CU.create_net('easy')\n        net.run()\n\n        output = workspace.FetchBlob('output')\n        expected_output = [tensor[sample] for sample in indices]\n        np.testing.assert_allclose(output, expected_output)\n\n    @given(seed=st.integers(min_value=0, max_value=65536), **hu.gcs)\n    def test_slice(self, seed, gc, dc):\n        CU = core.C.CompilationUnit()\n        CU.define(\"\"\"\n        def slice_from_tensor(tensor, start, end) -> (output):\n            output = tensor[start:end]\n        def slice_from_vector(vector, start, end) -> (a, b, c, d):\n            a = vector[start:end]\n            b = vector[start:]\n            c = vector[:end]\n            d = vector[:]\n        \"\"\")\n\n        # slice_from_tensor\n        proto = CU.get_proto('slice_from_tensor')\n        assert_proto_equals(proto, \"\"\"\n            name: \"slice_from_tensor\"\n            op {\n              input: \"tensor\"\n              input: \"start\"\n              input: \"end\"\n              output: \"output\"\n              type: \"Slice\"\n            }\"\"\")\n\n        np.random.seed(int(seed))\n        tensor = np.random.rand(5, 4, 3).astype(np.float32)\n        start = np.array([0, 1, 0], dtype=np.int32)\n        end = np.array([-1, 2, -1], dtype=np.int32)\n\n        feed_inputs(dict(tensor=tensor, start=start, end=end))\n\n        net = CU.create_net('slice_from_tensor')\n        net.run()\n\n        output = workspace.FetchBlob('output')\n        np.testing.assert_allclose(output, tensor[:, 1:2])\n\n        # slice_from_vector\n        proto = CU.get_proto('slice_from_vector')\n        assert_proto_equals(proto, \"\"\"\n            name: \"slice_from_vector\"\n            op {\n              input: \"vector\"\n              input: \"start\"\n              input: \"end\"\n              output: \"a\"\n              type: \"Slice\"\n            }\n            op {\n              output: \"$t0\"\n              type: \"ConstantFill\"\n              arg {\n                name: \"dtype\"\n                i: 2\n              }\n              arg {\n                name: \"value\"\n                i: -1\n              }\n              arg {\n                name: \"shape\"\n                ints: 1\n              }\n            }\n            op {\n              input: \"vector\"\n              input: \"start\"\n              input: \"$t0\"\n              output: \"b\"\n              type: \"Slice\"\n            }\n            op {\n              output: \"$t1\"\n              type: \"ConstantFill\"\n              arg {\n                name: \"dtype\"\n                i: 2\n              }\n             arg {\n                name: \"value\"\n                i: 0\n              }\n              arg {\n                name: \"shape\"\n                ints: 1\n              }\n            }\n            op {\n              input: \"vector\"\n              input: \"$t1\"\n              input: \"end\"\n              output: \"c\"\n              type: \"Slice\"\n            }\n            op {\n              output: \"$t2\"\n              type: \"ConstantFill\"\n              arg {\n                name: \"dtype\"\n                i: 2\n              }\n             arg {\n                name: \"value\"\n                i: 0\n              }\n              arg {\n                name: \"shape\"\n                ints: 1\n              }\n            }\n            op {\n              output: \"$t3\"\n              type: \"ConstantFill\"\n              arg {\n                name: \"dtype\"\n                i: 2\n              }\n             arg {\n                name: \"value\"\n                i: -1\n              }\n              arg {\n                name: \"shape\"\n                ints: 1\n              }\n            }\n            op {\n              input: \"vector\"\n              input: \"$t2\"\n              input: \"$t3\"\n              output: \"d\"\n              type: \"Slice\"\n            }\"\"\")\n\n        vector = np.random.rand(10).astype(np.float32)\n        start = np.array([2], dtype=np.int32)\n        end = np.array([6], dtype=np.int32)\n        feed_inputs(dict(vector=vector, start=start, end=end))\n\n        net = CU.create_net('slice_from_vector')\n        net.run()\n\n        output = workspace.FetchBlob('a')\n        np.testing.assert_allclose(output, vector[2:6])\n\n        output = workspace.FetchBlob('b')\n        np.testing.assert_allclose(output, vector[2:])\n\n        output = workspace.FetchBlob('c')\n        np.testing.assert_allclose(output, vector[:6])\n\n        output = workspace.FetchBlob('d')\n        np.testing.assert_allclose(output, vector)\n"
  },
  {
    "path": "caffe2/contrib/script/compiler.cc",
    "content": "#include \"caffe2/core/net.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\n#include \"compiler.h\"\n#include \"parser.h\"\n\nnamespace caffe2 {\nnamespace script {\n\nnamespace {\n\nstatic std::unordered_set<std::string> ops_containing_nets = {\n    \"If\",\n    \"While\",\n    \"RecurrentNetwork\",\n};\n// record of defined function\n// NetDef + metadata\nstruct FunctionDefinition {\n  explicit FunctionDefinition(Def tree)\n      : tree(new Def(tree)), net_def(new NetDef()) {}\n\n  explicit FunctionDefinition(std::unique_ptr<NetDef> def)\n      : tree(nullptr), net_def(std::move(def)) {\n    // we coop extern_inputs/extern_outputs to be the inputs/outputs to\n    // this net as a function\n    // but we _dont_ set these when creating the net in the workspace\n    // because they require the net to have valid inputs/outputs\n    inputs.insert(\n        inputs.begin(),\n        net_def->external_input().begin(),\n        net_def->external_input().end());\n    outputs.insert(\n        outputs.begin(),\n        net_def->external_output().begin(),\n        net_def->external_output().end());\n    net_def->clear_external_output();\n    net_def->clear_external_input();\n  }\n\n  bool isExtern() const {\n    return tree == nullptr;\n  }\n  std::unique_ptr<Def> tree;\n  std::unique_ptr<NetDef> net_def;\n  std::vector<std::string> inputs;\n  std::vector<std::string> outputs;\n};\n\n} // namespace\n\nusing SymbolTable = std::unordered_map<std::string, FunctionDefinition>;\n\nstruct DefCompiler {\n  DefCompiler(FunctionDefinition& def, SymbolTable& symbol_table)\n      : def(def),\n        net_def_stack({def.net_def.get()}),\n        symbol_table(symbol_table) {}\n  void run() {\n    auto& tree = *def.tree;\n    cur().set_name(tree.name().name());\n    for (auto input : tree.params()) {\n      auto& name = input.ident().name();\n      map(name, name);\n      def.inputs.push_back(name);\n    }\n    for (auto output : tree.returns()) {\n      auto& name = output.ident().name();\n      map(name, name);\n      def.outputs.push_back(name);\n    }\n    emitStatements(tree.statements());\n  }\n  void emitExpressionStatement(TreeRef stmt) {\n    // expression with no used outputs\n    emit(stmt, {});\n  }\n  void emitStatements(const ListView<TreeRef>& statements) {\n    for (auto stmt : statements) {\n      switch (stmt->kind()) {\n        case TK_IF:\n          emitIf(If(stmt));\n          break;\n        case TK_WHILE:\n          emitWhile(While(stmt));\n          break;\n        case TK_ASSIGN:\n          emitAssignment(Assign(stmt));\n          break;\n        case TK_GLOBAL:\n          for (auto ident : stmt->trees()) {\n            auto name = Ident(ident).name();\n            map(name, name);\n          }\n          break;\n        default:\n          emitExpressionStatement(stmt);\n          break;\n      }\n    }\n  }\n  void map(const std::string& name, const std::string& value) {\n    env[name] = value;\n  }\n  const std::string& lookup(const Ident& ident) {\n    if (env.count(ident.name()) == 0)\n      throw ErrorReport(ident) << \"undefined value \" << ident.name();\n    return env[ident.name()];\n  }\n  void emitAssignment(const Assign& stmt) {\n    std::vector<std::string> outputs;\n    for (auto lhs : stmt.lhs()) {\n      std::string name = getLHS(lhs);\n      // use of \"_\" gets renamed in Caffe2 graphs so that two uses\n      // don't unintentionally interfere with each other\n      if (name == \"_\") {\n        name = fresh();\n      }\n      outputs.push_back(name);\n    }\n    if (stmt.reduction() != '=') {\n      if (stmt.lhs().size() != 1) {\n        throw ErrorReport(stmt)\n            << \"reductions are only allow when there is a single variable \"\n            << \"on the left-hand side.\";\n      }\n      auto lhs = stmt.lhs()[0];\n      auto expr =\n          Compound::create(stmt.reduction(), stmt.range(), {lhs, stmt.rhs()});\n      emit(expr, outputs);\n    } else {\n      emit(stmt.rhs(), outputs);\n    }\n    int i = 0;\n    for (auto ident : stmt.lhs()) {\n      if (ident->kind() == TK_IDENT)\n        map(Ident(ident).name(), outputs.at(i));\n      i++;\n    }\n  }\n  void emitIf(const If& stmt) {\n    auto cond = getValue(stmt.cond());\n    auto op = cur().add_op();\n    op->set_type(\"If\");\n    op->add_input(cond);\n    auto true_branch = op->add_arg();\n    true_branch->set_name(\"then_net\");\n    auto nd = true_branch->mutable_n();\n    net_def_stack.push_back(nd);\n    emitStatements(stmt.trueBranch());\n    net_def_stack.pop_back();\n    if (stmt.falseBranch().size() > 0) {\n      auto false_branch = op->add_arg();\n      false_branch->set_name(\"else_net\");\n      auto nd = false_branch->mutable_n();\n      net_def_stack.push_back(nd);\n      emitStatements(stmt.falseBranch());\n      net_def_stack.pop_back();\n    }\n  }\n  void emitWhile(const While& stmt) {\n    std::string loop_var = fresh();\n    emitConst(0, loop_var, \"i\"); // it needs a definition before loop\n    auto op = cur().add_op();\n    op->set_type(\"While\");\n    auto cond = op->add_arg();\n    cond->set_name(\"cond_net\");\n    auto cond_net = cond->mutable_n();\n\n    net_def_stack.push_back(cond_net);\n    emit(stmt.cond(), {loop_var});\n    net_def_stack.pop_back();\n\n    op->add_input(loop_var);\n    auto body = op->add_arg();\n    body->set_name(\"loop_net\");\n    auto body_net = body->mutable_n();\n\n    net_def_stack.push_back(body_net);\n    emitStatements(stmt.body());\n    net_def_stack.pop_back();\n  }\n  std::string getLHS(const TreeRef& tree) {\n    switch (tree->kind()) {\n      case TK_IDENT: {\n        return Ident(tree).name();\n      } break;\n      case '.': {\n        auto sel = Select(tree);\n        std::string lhs = getValue(sel.value());\n        // TODO: check whether this subname exists in object lhs\n        return lhs + \"/\" + sel.selector().name();\n      } break;\n      default: {\n        throw ErrorReport(tree)\n            << \"This expression cannot appear on the left-hand size of an assignment\";\n      } break;\n    }\n  }\n  std::string getValue(const TreeRef& tree) {\n    switch (tree->kind()) {\n      case TK_IDENT: {\n        return lookup(Ident(tree));\n      } break;\n      case '.': {\n        auto sel = Select(tree);\n        std::string lhs = getValue(sel.value());\n        // TODO: check whether this subname exists in object lhs\n        return lhs + \"/\" + sel.selector().name();\n      } break;\n      default: {\n        std::string name = fresh();\n        emit(tree, {name});\n        return name;\n      } break;\n    }\n  }\n  std::string fresh(std::string prefix = \"$t\") {\n    return std::string(prefix) + caffe2::to_string(next_fresh++);\n  }\n  const char* operatorName(int kind, int ninputs) {\n    switch (kind) {\n      case '+':\n        return \"Add\";\n      case '-':\n        if (ninputs == 1)\n          return \"Negative\";\n        else\n          return \"Sub\";\n      case '*':\n        return \"Mul\";\n      case '/':\n        return \"Div\";\n      case TK_NE:\n        return \"NE\";\n      case TK_EQ:\n        return \"EQ\";\n      case '<':\n        return \"LT\";\n      case '>':\n        return \"GT\";\n      case TK_LE:\n        return \"LE\";\n      case TK_GE:\n        return \"GE\";\n      case TK_IF_EXPR:\n        return \"Conditional\";\n      case TK_AND:\n        return \"And\";\n      case TK_OR:\n        return \"Or\";\n      case TK_NOT:\n        return \"Not\";\n      default:\n        throw std::runtime_error(\"unknown kind \" + caffe2::to_string(kind));\n    }\n  }\n  void fillArg(Argument* arg, const Attribute& attr) {\n    std::string name = attr.name().name();\n    arg->set_name(name);\n    auto value = attr.value();\n    // TODO: handle non-float attributes\n    switch (value->kind()) {\n      case TK_CONST: {\n        auto v = value->tree(0)->doubleValue();\n        auto f = value->tree(1)->stringValue();\n        if (f == \"f\")\n          arg->set_f(v);\n        else\n          arg->set_i(v);\n      } break;\n      case TK_LIST:\n        for (auto t : value->trees()) {\n          auto v = t->tree(0)->doubleValue();\n          auto f = t->tree(1)->stringValue();\n          if (f == \"f\")\n            arg->add_floats(v);\n          else\n            arg->add_ints(v);\n        }\n        break;\n    }\n  }\n  template <typename Trees>\n  std::vector<std::string> getValues(const Trees& trees) {\n    std::vector<std::string> result;\n    for (const auto& tree : trees) {\n      result.push_back(getValue(tree));\n    }\n    return result;\n  }\n\n  bool renameLookup(\n      std::unordered_map<std::string, std::string>& rename_map,\n      const std::string& name,\n      std::string& rename) {\n    // first look for name in the map directly\n    auto it = rename_map.find(name);\n    if (it != rename_map.end()) {\n      rename = it->second;\n      return true;\n    }\n    // otherwise if we have a rename entry like a => b and a name \"a/foo/bar\"\n    // then replace it with \"b/foo/bar\"\n    auto p = name.find(\"/\");\n    if (p == std::string::npos)\n      return false;\n    it = rename_map.find(name.substr(0, p));\n    if (it != rename_map.end()) {\n      rename = it->second + name.substr(p);\n      return true;\n    }\n    return false;\n  }\n  void renameOp(\n      std::unordered_map<std::string, std::string>& rename_map,\n      const Apply& apply,\n      const std::string& prefix,\n      bool isExtern,\n      OperatorDef* new_op) {\n    for (size_t i = 0; i < new_op->input().size(); i++) {\n      auto& name = new_op->input(i);\n      std::string renamed;\n      bool defined = renameLookup(rename_map, name, renamed);\n      if (!isExtern && !defined) {\n        throw ErrorReport(apply)\n            << \" unexpected undefined name '\" << name\n            << \"' while attempting to inline '\" << apply.name().name() << \"'\";\n      } else if (!defined) {\n        // extern function using a global name, assign it an identity mapping\n        rename_map[name] = name;\n      }\n      new_op->set_input(i, renamed);\n    }\n    for (size_t i = 0; i < new_op->output().size(); i++) {\n      auto& name = new_op->output(i);\n      std::string renamed;\n      if (!renameLookup(rename_map, name, renamed)) {\n        renamed = prefix + name;\n        rename_map[name] = renamed;\n      }\n      new_op->set_output(i, renamed);\n    }\n    // handle control flow inside the op as well\n    if (ops_containing_nets.count(new_op->type()) > 0) {\n      for (size_t i = 0; i < new_op->arg_size(); i++) {\n        auto* arg = new_op->mutable_arg(i);\n        if (arg->has_n()) {\n          auto* n = arg->mutable_n();\n          for (size_t j = 0; j < n->op_size(); j++) {\n            renameOp(rename_map, apply, prefix, isExtern, n->mutable_op(j));\n          }\n        }\n      }\n    }\n  }\n\n  bool hasBypassRename(const Apply& apply) {\n    for (auto attr : apply.attributes()) {\n      if (attr.name().name() == \"rename\") {\n        if (attr.value()->kind() != TK_CONST) {\n          throw ErrorReport(attr.value()) << \"expected a single constant\";\n        }\n        return attr.value()->tree(0)->doubleValue() == 0;\n      }\n    }\n    return false;\n  }\n\n  // emit a function call by inlining the function's NetDef into our\n  // net def, renaming temporaries func_name<unique_id>/orig_name\n  // renaming only happens for values defined by the function\n  // that are not marked outputs\n\n  // inputs/outputs are passed by reference\n  void emitFunctionCall(Apply& apply, const std::vector<std::string>& outputs) {\n    std::string fname = apply.name().name();\n    std::string prefix = fresh(fname) + \"/\";\n    auto& fn = symbol_table.at(apply.name().name());\n    bool isExtern = fn.isExtern();\n    auto inputs = getValues(apply.inputs());\n    std::unordered_map<std::string, std::string> rename_map;\n    if (inputs.size() != fn.inputs.size()) {\n      throw ErrorReport(apply) << fname << \" expected \" << fn.inputs.size()\n                               << \" values but received \" << inputs.size();\n    }\n    for (size_t i = 0; i < inputs.size(); i++) {\n      rename_map[fn.inputs[i]] = inputs[i];\n    }\n    if (outputs.size() != fn.outputs.size()) {\n      throw ErrorReport(apply) << fname << \" expected \" << fn.outputs.size()\n                               << \" values but received \" << outputs.size();\n    }\n    for (size_t i = 0; i < outputs.size(); i++) {\n      rename_map[fn.outputs[i]] = outputs[i];\n    }\n    for (auto& op : fn.net_def->op()) {\n      auto new_op = cur().add_op();\n      new_op->CopyFrom(op);\n      if (hasBypassRename(apply)) {\n        prefix = \"\";\n      }\n      renameOp(rename_map, apply, prefix, isExtern, new_op);\n    }\n  }\n  void expectOutputs(\n      const TreeRef& tree,\n      const std::vector<std::string>& outputs,\n      size_t size) {\n    if (outputs.size() != size) {\n      throw ErrorReport(tree)\n          << \"expected operator to produce \" << outputs.size()\n          << \" outputs but it produced \" << size;\n    }\n  }\n  void appendOutputs(\n      const TreeRef& tree,\n      OperatorDef* op,\n      const std::vector<std::string>& outputs,\n      size_t size) {\n    expectOutputs(tree, outputs, size);\n    for (size_t i = 0; i < size; i++) {\n      op->add_output(outputs[i]);\n    }\n  }\n  void emitOperator(\n      const Apply& apply,\n      const OpSchema* schema,\n      const std::vector<std::string>& outputs) {\n    // must be before add_op\n    auto values = getValues(apply.inputs());\n    if (values.size() < schema->min_input() ||\n        values.size() > schema->max_input()) {\n      if (schema->min_input() == schema->max_input()) {\n        throw ErrorReport(apply) << \"operator expects \" << schema->min_input()\n                                 << \" inputs but found \" << values.size();\n      } else {\n        throw ErrorReport(apply)\n            << \"operator takes between \" << schema->min_input() << \" and \"\n            << schema->max_input() << \" inputs but found \" << values.size()\n            << \".\";\n      }\n    }\n    auto numActualOutputs = schema->CalculateOutput(values.size());\n    if (numActualOutputs != kCannotComputeNumOutputs &&\n        outputs.size() != numActualOutputs) {\n      throw ErrorReport(apply)\n          << \"operator produces \" << numActualOutputs\n          << \" outputs but matched to \" << outputs.size() << \" outputs\";\n    }\n    auto op = cur().add_op();\n    op->set_type(apply.name().name());\n    for (auto& v : values) {\n      op->add_input(v);\n    }\n    // assume 1 output unless matched to more\n    appendOutputs(apply, op, outputs, outputs.size());\n    for (auto attribute : apply.attributes()) {\n      fillArg(op->add_arg(), attribute);\n    }\n    // Ok, we checked the stuff where we can easily give a friendly error\n    // message, now verify against the schema and report the error at the line\n    if (!schema->Verify(*op)) {\n      throw ErrorReport(apply) << \"failed schema checking\";\n    }\n  }\n\n  // Emit an operation, writing results into 'outputs'.\n  // This will _always_ compute something, unlike 'getValue' which simply\n  // returns an already computed reference if possible.\n  // So if 'tree' is an identifier or nested identifier (foo.bar)\n  // this will cause it to be _copied_ into outputs.\n  void emit(const TreeRef& tree, const std::vector<std::string>& outputs) {\n    switch (tree->kind()) {\n      case TK_IDENT:\n      case '.': {\n        auto op = cur().add_op();\n        op->set_type(\"Copy\");\n        op->add_input(getValue(tree));\n        appendOutputs(tree, op, outputs, 1);\n      } break;\n      case TK_NE:\n      case TK_EQ:\n      case '<':\n      case '>':\n      case TK_LE:\n      case TK_GE:\n      case '-':\n      case '*':\n      case '/':\n      case '+':\n      case TK_AND:\n      case TK_OR:\n      case TK_NOT:\n      case TK_IF_EXPR: {\n        // must be before add_op\n        auto values = getValues(tree->trees());\n        auto op = cur().add_op();\n        op->set_type(operatorName(tree->kind(), tree->trees().size()));\n        for (auto& v : values) {\n          op->add_input(v);\n        }\n        appendOutputs(tree, op, outputs, 1);\n        auto broadcast = op->add_arg();\n        broadcast->set_name(\"broadcast\");\n        broadcast->set_i(1);\n      } break;\n      case TK_APPLY: {\n        auto apply = Apply(tree);\n        // Handle built-ins like zeros, ones, etc\n        if (builtins.count(apply.name().name()) > 0) {\n          builtins[apply.name().name()](this, apply, outputs);\n          break;\n        }\n        if (symbol_table.count(apply.name().name()) > 0) {\n          emitFunctionCall(apply, outputs);\n          break;\n        }\n        auto schema = OpSchemaRegistry::Schema(apply.name().name());\n        if (schema) {\n          emitOperator(apply, schema, outputs);\n          break;\n        }\n        throw ErrorReport(apply)\n            << \"attempting to call unknown operation or function '\"\n            << apply.name().name() << \"'\";\n      } break;\n      case TK_CAST: {\n        auto cast = Cast(tree);\n        auto c2type = getType(cast.type());\n        auto input = getValue(cast.input());\n        auto op = cur().add_op();\n        op->set_type(\"Cast\");\n        op->add_input(input);\n        appendOutputs(tree, op, outputs, 1);\n        auto arg = op->add_arg();\n        arg->set_name(\"to\");\n        arg->set_i(c2type);\n      } break;\n      case TK_CONST: {\n        expectOutputs(tree, outputs, 1);\n        emitConst(\n            tree->tree(0)->doubleValue(),\n            outputs[0],\n            tree->tree(1)->stringValue());\n      } break;\n      case TK_GATHER: {\n        const auto gather = Gather(tree);\n        desugarAndEmitOperator(\n            \"Gather\",\n            gather.range(),\n            {gather.value(), gather.indices()},\n            outputs);\n        break;\n      }\n      case TK_SLICE: {\n        const auto slice = Slice(tree);\n        desugarAndEmitOperator(\n            \"Slice\",\n            slice.range(),\n            {slice.value(), slice.startOr(0), slice.endOr(-1)},\n            outputs);\n        break;\n      }\n      default:\n        throw ErrorReport(tree) << \"NYI: \" << tree;\n        break;\n    }\n  }\n\n  // Desugars constructs that are syntactic sugar and emits the corresponding\n  // operator invocation, e.g. tensor[indices] -> tensor.Gather(indices).\n  void desugarAndEmitOperator(\n      const std::string& operatorName,\n      const SourceRange& range,\n      TreeList&& inputs,\n      const std::vector<std::string>& outputs) {\n    const auto applyName = Ident::create(range, operatorName);\n    const auto applyInputs =\n        Compound::create(TK_LIST, range, std::move(inputs));\n    const auto applyAttributes = Compound::create(TK_LIST, range, {});\n    const auto apply =\n        Apply::create(range, applyName, applyInputs, applyAttributes);\n    const auto schema = OpSchemaRegistry::Schema(operatorName);\n    assert(schema != nullptr);\n    emitOperator(Apply(apply), schema, outputs);\n  }\n\n  TensorProto_DataType getType(int type) {\n    switch (type) {\n      case TK_INT:\n        return TensorProto_DataType_INT32;\n      case TK_FLOAT:\n        return TensorProto_DataType_FLOAT;\n      case TK_LONG:\n        return TensorProto_DataType_INT64;\n      case TK_BOOL:\n        return TensorProto_DataType_BOOL;\n      default:\n        throw std::runtime_error(\n            \"expected type token: \" + caffe2::to_string(type));\n    }\n  }\n\n  OperatorDef* emitConst(\n      double v,\n      const std::string& output,\n      const std::string& type_ident) {\n    auto op = cur().add_op();\n    op->set_type(\"ConstantFill\");\n    auto dtype = op->add_arg();\n    dtype->set_name(\"dtype\");\n    auto value = op->add_arg();\n    value->set_name(\"value\");\n    if (type_ident == \"f\") {\n      dtype->set_i(TensorProto_DataType_FLOAT);\n      value->set_f(v);\n    } else if (type_ident == \"LL\") {\n      dtype->set_i(TensorProto_DataType_INT64);\n      value->set_i(v);\n    } else if (type_ident == \"b\") {\n      dtype->set_i(TensorProto_DataType_BOOL);\n      value->set_i(v != 0);\n    } else if (type_ident == \"i\") {\n      dtype->set_i(TensorProto_DataType_INT32);\n      value->set_i(v);\n    } else {\n      throw std::runtime_error(\"unknown type_ident \" + type_ident);\n    }\n    auto shape = op->add_arg();\n    shape->set_name(\"shape\");\n    shape->add_ints(1);\n    op->add_output(output);\n    return op;\n  }\n  NetDef& cur() {\n    return *net_def_stack.back();\n  }\n  FunctionDefinition& def; // the def being constructed\n  std::unordered_map<std::string, std::string>\n      env; // map from name in Def to name in NetDef\n  std::vector<NetDef*> net_def_stack;\n  SymbolTable& symbol_table;\n  int next_fresh = 0;\n\n private:\n  void emitFillOp(const Apply& apply, const std::vector<std::string>& outputs) {\n    auto builtin_type = apply.name().name();\n    auto values = getValues(apply.inputs());\n    if (values.size() > 1) {\n      throw ErrorReport(apply)\n          << \"Built-in \" << builtin_type << \" accepts 0 or 1 inputs.\";\n    }\n    bool has_shape = false;\n    for (const auto& attribute : apply.attributes()) {\n      if (attribute.name().name() == \"shape\") {\n        has_shape = true;\n      } else {\n        throw ErrorReport(apply)\n            << \"Unrecognized attribute \" << attribute.name().name()\n            << \" for built-in \" << builtin_type;\n      }\n    }\n    if (builtin_type == \"zeros\" || builtin_type == \"ones\") {\n      if ((values.size() != 1) && !has_shape) {\n        throw ErrorReport(apply)\n            << \"Built-in \" << builtin_type\n            << \" requires either 1 input or 1 shape attribute\";\n      }\n    } else {\n      // zeros_like or ones_like\n      if (values.size() != 1) {\n        throw ErrorReport(apply)\n            << \"Built-in \" << builtin_type << \" requires 1 input\";\n      }\n    }\n\n    auto op = cur().add_op();\n    op->set_type(\"ConstantFill\");\n    if (values.size()) {\n      op->add_input(values[0]);\n      auto* input_as_shape = op->add_arg();\n      input_as_shape->set_name(\"input_as_shape\");\n      if (builtin_type.find(\"_like\") != std::string::npos) {\n        // zeros_like, ones_like take the shape of the input as constant\n        // tensor shape\n        input_as_shape->set_i(0);\n      } else {\n        // zeros, ones take the values in the tensor as constant tensor\n        // shape\n        input_as_shape->set_i(1);\n      }\n    } else {\n      fillArg(op->add_arg(), apply.attributes()[0]);\n    }\n\n    auto value = op->add_arg();\n    value->set_name(\"value\");\n    if (builtin_type.find(\"ones\") != std::string::npos) {\n      value->set_f(1.0f);\n    } else {\n      value->set_f(0.0f);\n    }\n    appendOutputs(apply, op, outputs, 1);\n  }\n  // emitModule doesn't actually do anything except for allow\n  // statements like a = Module() to register 'a' as a valid identifier\n  // so that a.b = ... will work\n  void emitModule(const Apply& apply, const std::vector<std::string>& outputs) {\n    expectOutputs(apply, outputs, 1);\n  }\n  std::unordered_map<\n      std::string,\n      std::function<void(\n          DefCompiler*,\n          const Apply&,\n          const std::vector<std::string>& outputs)>>\n      builtins{{\"zeros\", &DefCompiler::emitFillOp},\n               {\"zeros_like\", &DefCompiler::emitFillOp},\n               {\"ones\", &DefCompiler::emitFillOp},\n               {\"ones_like\", &DefCompiler::emitFillOp},\n               {\"Module\", &DefCompiler::emitModule}};\n};\n\nstruct CompilationUnitImpl {\n  void defineFunction(const Def& def) {\n    if (functions.count(def.name().name()) > 0) {\n      throw ErrorReport(def) << def.name().name() << \" already defined.\";\n    }\n    DefCompiler c(\n        functions.emplace(def.name().name(), FunctionDefinition(def))\n            .first->second,\n        functions);\n    c.run();\n  }\n\n  void define(const std::string& str) {\n    Parser p(str);\n    while (p.lexer().cur().kind != TK_EOF) {\n      defineFunction(Def(p.parseFunction()));\n    }\n  }\n\n  std::unique_ptr<NetBase> createNet(Workspace* ws, const std::string& str) {\n    if (functions.count(str) == 0)\n      throw ErrorReport() << \"undefined function: \" << str << \"\\n\";\n    auto& def = functions.at(str);\n    return caffe2::CreateNet(*def.net_def, ws);\n  }\n\n  void defineExtern(const std::string& name, std::unique_ptr<NetDef> net_def) {\n    // TODO: unify extern and function namespaces\n    if (functions.count(name) > 0) {\n      throw ErrorReport() << \"function '\" << name << \"' already defined.\";\n    }\n    functions.emplace(name, FunctionDefinition(std::move(net_def)));\n  }\n\n  std::string getProto(const std::string& functionName) {\n    return functions.at(functionName).net_def->DebugString();\n  }\n\n private:\n  friend struct DefCompiler;\n  SymbolTable functions;\n};\n\nCompilationUnit::CompilationUnit() : pImpl(new CompilationUnitImpl()) {}\n\nvoid CompilationUnit::define(const std::string& str) {\n  return pImpl->define(str);\n}\n\nvoid CompilationUnit::defineExtern(\n    const std::string& name,\n    std::unique_ptr<NetDef> nd) {\n  pImpl->defineExtern(name, std::move(nd));\n}\n\nstd::unique_ptr<NetBase> CompilationUnit::createNet(\n    Workspace* ws,\n    const std::string& str) {\n  return pImpl->createNet(ws, str);\n}\n\nstd::string CompilationUnit::getProto(const std::string& functionName) const {\n  return pImpl->getProto(functionName);\n}\n\nCompilationUnit::~CompilationUnit() {}\n\n} // namespace script\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/script/compiler.h",
    "content": "#pragma once\n#include <memory>\n#include <string>\n#include \"caffe2/core/net.h\"\n\nnamespace caffe2 {\nnamespace script {\n\nstruct CompilationUnitImpl;\nstruct CompilationUnit {\n  CompilationUnit();\n  void define(const std::string& str);\n  void defineExtern(const std::string& str, std::unique_ptr<NetDef> netdef);\n  std::unique_ptr<NetBase> createNet(Workspace* ws, const std::string& name);\n  std::string getProto(const std::string& functionName) const;\n  ~CompilationUnit();\n\n private:\n  std::unique_ptr<CompilationUnitImpl> pImpl;\n};\n\n} // namespace script\n}; // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/script/error_report.h",
    "content": "#pragma once\n\n#include \"caffe2/contrib/script/tree.h\"\n\nnamespace caffe2 {\nnamespace script {\n\nstruct ErrorReport : public std::exception {\n  ErrorReport(const ErrorReport& e)\n      : ss(e.ss.str()), context(e.context), the_message(e.the_message) {}\n\n  ErrorReport() : context(nullptr) {}\n  explicit ErrorReport(const SourceRange& r)\n      : context(std::make_shared<SourceRange>(r)) {}\n  explicit ErrorReport(const TreeRef& tree) : ErrorReport(tree->range()) {}\n  explicit ErrorReport(const Token& tok) : ErrorReport(tok.range) {}\n  virtual const char* what() const noexcept override {\n    std::stringstream msg;\n    msg << \"\\n\" << ss.str();\n    if (context != nullptr) {\n      msg << \":\\n\";\n      context->highlight(msg);\n    } else {\n      msg << \".\\n\";\n    }\n    the_message = msg.str();\n    return the_message.c_str();\n  }\n\n private:\n  template <typename T>\n  friend const ErrorReport& operator<<(const ErrorReport& e, const T& t);\n\n  mutable std::stringstream ss;\n  std::shared_ptr<SourceRange> context;\n  mutable std::string the_message;\n};\n\ntemplate <typename T>\nconst ErrorReport& operator<<(const ErrorReport& e, const T& t) {\n  e.ss << t;\n  return e;\n}\n\n#define C2S_ASSERT(ctx, cond)                                              \\\n  if (!(cond)) {                                                           \\\n    throw ::caffe2::script::ErrorReport(ctx)                               \\\n        << __FILE__ << \":\" << __LINE__ << \": assertion failed: \" << #cond; \\\n  }\n} // namespace script\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/script/examples/example_beam_search.c2s",
    "content": "[[\"log_probs\", [6, 1, 44463], \"float32\"], [\"attentions\", [6, 1, 21], \"float32\"], [\"inputs\", [21], \"float32\"]]\nbeam_search\n[\"scores_t\"]\n\ndef beam_search(inputs, log_probs, attentions) -> ():\n    beam_size = 6LL\n    length = 20LL\n    beam_output_shape, _ = Concat(length + 1LL, beam_size, axis=0)\n    output_token_beam_list = int(zeros(beam_output_shape))\n    output_prev_index_beam_list = int(zeros(beam_output_shape))\n    output_score_beam_list = zeros(beam_output_shape)\n\n    input_length = inputs.Size().ExpandDims(dims=[0])\n\n    attention_beam_output_shape, _ = Concat(\n        input_length, beam_output_shape, axis=0)\n    output_attention_weights_beam_list = zeros(attention_beam_output_shape)\n\n    attention_step_output_shape, _ = Concat(beam_size, input_length, axis=0)\n    attention_t = zeros(attention_step_output_shape)\n\n    scores_t = zeros(shape=[1, 6])\n    hypo_t = int(zeros(shape=[6]))\n    tokens_t = int(ones(shape=[6])) * 99\n\n    output_token_beam_list = output_token_beam_list.ScatterAssign(0, tokens_t)\n    output_token_beam_list = output_token_beam_list.ExpandDims(dims=[2])\n    output_prev_index_beam_list = output_prev_index_beam_list.ScatterAssign(\n        0, hypo_t)\n    output_prev_index_beam_list = output_prev_index_beam_list.ExpandDims(dims=[2])\n    output_score_beam_list = output_score_beam_list.ScatterAssign(0, scores_t)\n    output_score_beam_list = output_score_beam_list.ExpandDims(dims=[2])\n    output_attention_weights_beam_list = output_attention_weights_beam_list\\\n        .ScatterAssign(0, attention_t)\n\n    length_32 = int(length)\n\n    timestep = 0\n    not_finished = True\n    while not_finished:\n        # TODO: once we have a metaprogramming facility we need to insert the\n        # body of the post_eos_penalty here programmatically\n\n        best_scores_per_hypo, best_tokens_per_hypo = log_probs.TopK(k=6)\n\n        # Add the best score in each hypothesis to the cumulative score so far\n        output_scores = best_scores_per_hypo + scores_t.Squeeze(dims=[0])\n\n        # Flatten scores so we can find the best overall out of all hypotheses\n        output_scores_flattened_slice, _ = output_scores.FlattenToVec()\\\n            .Slice(0, 6 if timestep == 0 else -1).Reshape(shape=[1, -1])\n\n        # Find top K out of all\n        scores_t, best_indices = output_scores_flattened_slice.TopK(k=6)\n\n        # Integer floor divide on indices finds the association back to original\n        #  hypotheses. Use this to reorder states\n        hypo_t_int64 = best_indices / 6LL\n\n        # Reorder attentions\n        attention_t, _ = attentions.Gather(hypo_t_int64)\\\n            .Reshape(shape=[1, 6, -1])\n        tokens_t_int64 = best_tokens_per_hypo.FlattenToVec()\\\n            .Gather(best_indices).Cast(to=2)\n\n        timestep += 1\n        not_finished = timestep < length_32\n\n        output_token_beam_list = output_token_beam_list\\\n            .ScatterAssign(timestep, tokens_t)\n        output_prev_index_beam_list = output_prev_index_beam_list\\\n            .ScatterAssign(timestep, hypo_t)\n        output_score_beam_list = output_score_beam_list\\\n            .ScatterAssign(timestep, scores_t)\n        output_attention_weights_beam_list = output_attention_weights_beam_list\\\n            .ScatterAssign(timestep, attention_t)\n"
  },
  {
    "path": "caffe2/contrib/script/examples/example_post_eos_penalty.c2s",
    "content": "[[\"tokens_t\", [1, 6], \"int32\"], [\"hypo_t\", [1, 6], \"int32\"], [\"log_probs\", [6, 1, 44463], \"float32\"], [\"on_initial_step\", [1], \"bool_\"]]\npost_eos_penalty\n[\"log_probs\"]\n\ndef post_eos_penalty(tokens_t, hypo_t, log_probs, on_initial_step) \\\n  -> (log_probs):\n  eos_token = 1\n  finished_penalty = 0f if on_initial_step else 0.5f\n  predecessor_tokens = tokens_t.FlattenToVec().Gather(hypo_t.FlattenToVec())\n  predecessor_is_eos = float(predecessor_tokens == eos_token)\n  log_probs = log_probs.Add(\n      predecessor_is_eos * finished_penalty, broadcast=1, axis=0\n  )\n"
  },
  {
    "path": "caffe2/contrib/script/examples/run_examples.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nimport glob\nimport json\nimport numpy as np\n\nexample_files = glob.glob('example_*.c2s')\n\nfor ex in example_files:\n    print('Running example file', ex)\n    with open(ex, 'r') as f:\n        inits = json.loads(f.readline())\n        net_name = f.readline().strip()\n        outputs = json.loads(f.readline())\n\n        CU = core.C.CompilationUnit()\n        CU.define(f.read())\n\n    # Initialize workspace with required inputs\n    for name, shape, dt in inits:\n        workspace.FeedBlob(name, np.random.rand(*shape).astype(np.dtype(dt)))\n\n    net = CU.create_net(net_name)\n    net.run()\n\n    print('Success! Interesting outputs:')\n    for output in outputs:\n        print(output, workspace.FetchBlob(output))\n"
  },
  {
    "path": "caffe2/contrib/script/lexer.cc",
    "content": "#include \"caffe2/contrib/script/lexer.h\"\n#include \"caffe2/core/common.h\"\n\nnamespace caffe2 {\nnamespace script {\n\nstd::string kindToString(int kind) {\n  if (kind < 256)\n    return std::string(1, kind);\n  switch (kind) {\n#define DEFINE_CASE(tok, str, _) \\\n  case tok:                      \\\n    return str;\n    TC_FORALL_TOKEN_KINDS(DEFINE_CASE)\n#undef DEFINE_CASE\n    default:\n      throw std::runtime_error(\"unknown kind: \" + caffe2::to_string(kind));\n  }\n}\n\nSharedParserData& sharedParserData() {\n  static SharedParserData data; // safely handles multi-threaded init\n  return data;\n}\n} // namespace script\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/script/lexer.h",
    "content": "#pragma once\n#include <assert.h>\n#include <algorithm>\n#include <iostream>\n#include <memory>\n#include <sstream>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"caffe2/core/common.h\"\n\nnamespace caffe2 {\nnamespace script {\n\n// single character tokens are just the character itself '+'\n// multi-character tokens need an entry here\n// if the third entry is not the empty string, it is used\n// in the lexer to match this token.\n\n// These kinds are also used in Tree.h as the kind of the AST node.\n// Some kinds TK_APPLY, TK_LIST are only used in the AST and are not seen in the\n// lexer.\n\n#define TC_FORALL_TOKEN_KINDS(_)                 \\\n  _(TK_EOF, \"eof\", \"\")                           \\\n  _(TK_WHITESPACE, \"whitespace\", \"\")             \\\n  _(TK_NUMBER, \"number\", \"\")                     \\\n  _(TK_NEWLINE, \"newline\", \"\")                   \\\n  _(TK_INDENT, \"indent\", \"\")                     \\\n  _(TK_DEDENT, \"dedent\", \"\")                     \\\n  _(TK_WHERE, \"where\", \"where\")                  \\\n  _(TK_FLOAT, \"float\", \"float\")                  \\\n  _(TK_DOUBLE, \"double\", \"double\")               \\\n  _(TK_LONG, \"long\", \"long\")                     \\\n  _(TK_INT, \"int\", \"int\")                        \\\n  _(TK_DEF, \"def\", \"def\")                        \\\n  _(TK_ARROW, \"arrow\", \"->\")                     \\\n  _(TK_EQUIVALENT, \"equivalent\", \"<=>\")          \\\n  _(TK_IDENT, \"ident\", \"\")                       \\\n  _(TK_STRING, \"string\", \"\")                     \\\n  _(TK_CONST, \"const\", \"\")                       \\\n  _(TK_LIST, \"list\", \"\")                         \\\n  _(TK_OPTION, \"option\", \"\")                     \\\n  _(TK_APPLY, \"apply\", \"\")                       \\\n  _(TK_COMPREHENSION, \"comprehension\", \"\")       \\\n  _(TK_TENSOR_TYPE, \"tensor_type\", \"\")           \\\n  _(TK_RANGE_CONSTRAINT, \"range_constraint\", \"\") \\\n  _(TK_PARAM, \"param\", \"\")                       \\\n  _(TK_INFERRED, \"inferred\", \"\")                 \\\n  _(TK_BOOL, \"bool\", \"\")                         \\\n  _(TK_ACCESS, \"access\", \"\")                     \\\n  _(TK_ASSIGN, \"assign\", \"\")                     \\\n  _(TK_ATTRIBUTE, \"attribute\", \"\")               \\\n  _(TK_IF, \"if\", \"if\")                           \\\n  _(TK_ELSE, \"else\", \"else\")                     \\\n  _(TK_ELIF, \"elif\", \"elif\")                     \\\n  _(TK_WHILE, \"while\", \"while\")                  \\\n  _(TK_NE, \"ne\", \"!=\")                           \\\n  _(TK_EQ, \"eq\", \"==\")                           \\\n  _(TK_LE, \"le\", \"<=\")                           \\\n  _(TK_GE, \"ge\", \">=\")                           \\\n  _(TK_IF_EXPR, \"if\", \"\")                        \\\n  _(TK_TRUE, \"True\", \"True\")                     \\\n  _(TK_FALSE, \"False\", \"False\")                  \\\n  _(TK_AND, \"and\", \"and\")                        \\\n  _(TK_OR, \"or\", \"or\")                           \\\n  _(TK_NOT, \"not\", \"not\")                        \\\n  _(TK_CAST, \"cast\", \"\")                         \\\n  _(TK_PLUS_EQ, \"+=\", \"+=\")                      \\\n  _(TK_MINUS_EQ, \"-=\", \"-=\")                     \\\n  _(TK_TIMES_EQ, \"*=\", \"*=\")                     \\\n  _(TK_DIV_EQ, \"/=\", \"/=\")                       \\\n  _(TK_GLOBAL, \"global\", \"global\")               \\\n  _(TK_BUILT_IN, \"built-in\", \"\")                 \\\n  _(TK_SLICE, \"slice\", \"\")                       \\\n  _(TK_GATHER, \"gather\", \"\")\nstatic const char* valid_single_char_tokens = \"+-*/()[]:,={}><.\";\n\nenum TokenKind {\n  // we use characters to represent themselves so skip all valid characters\n  // before\n  // assigning enum values to multi-char tokens.\n  TK_DUMMY_START = 256,\n#define DEFINE_TOKEN(tok, _, _2) tok,\n  TC_FORALL_TOKEN_KINDS(DEFINE_TOKEN)\n#undef DEFINE_TOKEN\n};\n\nstd::string kindToString(int kind);\n\n// nested hash tables that indicate char-by-char what is a valid token.\nstruct TokenTrie;\nusing TokenTrieRef = std::unique_ptr<TokenTrie>;\nstruct TokenTrie {\n  TokenTrie() : kind(0) {}\n  void insert(const char* str, int tok) {\n    if (*str == '\\0') {\n      assert(kind == 0);\n      kind = tok;\n      return;\n    }\n    auto& entry = children[*str];\n    if (entry == nullptr) {\n      entry.reset(new TokenTrie());\n    }\n    entry->insert(str + 1, tok);\n  }\n  int kind; // 0 == invalid token\n  std::unordered_map<char, TokenTrieRef> children;\n};\n\n// stuff that is shared against all TC lexers/parsers and is initialized only\n// once.\nstruct SharedParserData {\n  SharedParserData() : head(new TokenTrie()) {\n    // listed in increasing order of precedence\n    std::vector<std::vector<int>> binary_ops = {\n        {TK_IF},\n        {TK_AND, TK_OR},\n        {}, // reserve a level for unary not\n        {'<', '>', TK_EQ, TK_LE, TK_GE, TK_NE},\n        {'+', '-'},\n        {'*', '/'},\n    };\n    std::vector<std::vector<int>> unary_ops = {\n        {'-'},\n    };\n\n    std::stringstream ss;\n    for (const char* c = valid_single_char_tokens; *c; c++) {\n      const char str[] = {*c, '\\0'};\n      head->insert(str, *c);\n    }\n\n#define ADD_CASE(tok, _, tokstring) \\\n  if (*tokstring != '\\0') {         \\\n    head->insert(tokstring, tok);   \\\n  }\n    TC_FORALL_TOKEN_KINDS(ADD_CASE)\n#undef ADD_CASE\n\n    // precedence starts at 1 so that there is always a 0 precedence\n    // less than any other precedence\n    int prec = 1;\n    for (auto& group : binary_ops) {\n      for (auto& element : group) {\n        binary_prec[element] = prec;\n      }\n      prec++;\n    }\n    // unary ops\n    for (auto& group : unary_ops) {\n      for (auto& element : group) {\n        unary_prec[element] = prec;\n      }\n      prec++;\n    }\n    // add unary not separately because it slots into the precedence of\n    // binary operators\n    unary_prec[TK_NOT] = binary_prec[TK_AND] + 1;\n  }\n  // 1. skip whitespace\n  // 2. handle comment or newline\n  //\n  bool isNumber(const std::string& str, size_t start, size_t* len) {\n    char first = str[start];\n    // strtod allows numbers to start with + or - or nan or inf\n    // http://en.cppreference.com/w/cpp/string/byte/strtof\n    // but we want only the number part, otherwise 1+3 will turn into two\n    // adjacent numbers in the lexer\n    if (first == '-' || first == '+' || isalpha(first))\n      return false;\n    const char* startptr = str.c_str() + start;\n    char* endptr;\n    std::strtod(startptr, &endptr);\n    *len = endptr - startptr;\n    return *len > 0;\n  }\n  bool isblank(int n) {\n    return isspace(n) && n != '\\n';\n  }\n  // find the longest match of str.substring(pos) against a token, return true\n  // if successful\n  // filling in kind, start,and len\n  bool match(\n      const std::string& str,\n      size_t pos,\n      bool continuation, // are we inside a scope where newlines don't count\n                         // (e.g. inside parens)\n      bool whitespace_token, // should we treat whitespace as a token\n      int* kind,\n      size_t* start,\n      size_t* len) {\n    *start = pos;\n    // skip whitespace\n    while (pos < str.size() && isblank(str[pos]))\n      pos++;\n\n    // special handling\n    if (pos < str.size()) {\n      if (str[pos] == '#') {\n        // skip comments\n        while (pos < str.size() && str[pos] != '\\n')\n          pos++;\n        // tail call, handle whitespace and more comments\n        return match(\n            str, pos, continuation, whitespace_token, kind, start, len);\n      }\n      if (str[pos] == '\\\\' && pos + 1 < str.size() && str[pos + 1] == '\\n' &&\n          !whitespace_token) {\n        return match(str, pos + 2, continuation, false, kind, start, len);\n      }\n      if (str[pos] == '\\n') {\n        return match(\n            str, pos + 1, continuation, !continuation, kind, start, len);\n      }\n    }\n    if (pos == str.size()) {\n      *kind = TK_EOF;\n      *start = pos;\n      *len = 0;\n      return true;\n    }\n    // invariant: the next token is not whitespace or newline\n    if (whitespace_token) {\n      *kind = TK_WHITESPACE;\n      *len = pos - *start;\n      return true;\n    }\n    *start = pos;\n    // check for a valid number\n    if (isNumber(str, pos, len)) {\n      *kind = TK_NUMBER;\n      return true;\n    }\n    // check for either an ident or a token\n    // ident tracks whether what we have scanned so far could be an identifier\n    // matched indicates if we have found any match.\n    bool matched = false;\n    bool ident = true;\n    TokenTrie* cur = head.get();\n    for (size_t i = 0; pos + i < str.size() && (ident || cur != nullptr); i++) {\n      ident = ident && validIdent(i, str[pos + i]);\n      if (ident) {\n        matched = true;\n        *len = i + 1;\n        *kind = TK_IDENT;\n      }\n      // check for token second, so that e.g. 'max' matches the token TK_MAX\n      // rather the\n      // identifier 'max'\n      if (cur) {\n        auto it = cur->children.find(str[pos + i]);\n        cur = (it == cur->children.end()) ? nullptr : it->second.get();\n        if (cur && cur->kind != 0) {\n          matched = true;\n          *len = i + 1;\n          *kind = cur->kind;\n        }\n      }\n    }\n    return matched;\n  }\n  bool isUnary(int kind, int* prec) {\n    auto it = unary_prec.find(kind);\n    if (it != unary_prec.end()) {\n      *prec = it->second;\n      return true;\n    }\n    return false;\n  }\n  bool isBinary(int kind, int* prec) {\n    auto it = binary_prec.find(kind);\n    if (it != binary_prec.end()) {\n      *prec = it->second;\n      return true;\n    }\n    return false;\n  }\n  bool isRightAssociative(int kind) {\n    switch (kind) {\n      case '?':\n        return true;\n      default:\n        return false;\n    }\n  }\n\n private:\n  bool validIdent(size_t i, char n) {\n    return isalpha(n) || n == '_' || (i > 0 && isdigit(n));\n  }\n  TokenTrieRef head;\n  std::unordered_map<int, int>\n      unary_prec; // map from token to its unary precedence\n  std::unordered_map<int, int>\n      binary_prec; // map from token to its binary precedence\n};\n\nSharedParserData& sharedParserData();\n\n// a range of a shared string 'file_' with functions to help debug by highlight\n// that\n// range.\nstruct SourceRange {\n  SourceRange(\n      const std::shared_ptr<std::string>& file_,\n      size_t start_,\n      size_t end_)\n      : file_(file_), start_(start_), end_(end_) {}\n  const std::string text() const {\n    return file().substr(start(), end() - start());\n  }\n  size_t size() const {\n    return end() - start();\n  }\n  void highlight(std::ostream& out) const {\n    const std::string& str = file();\n    size_t begin = start();\n    size_t end = start();\n    while (begin > 0 && str[begin - 1] != '\\n')\n      --begin;\n    while (end < str.size() && str[end] != '\\n')\n      ++end;\n    out << str.substr(0, end) << \"\\n\";\n    out << std::string(start() - begin, ' ');\n    size_t len = std::min(size(), end - start());\n    out << std::string(len, '~')\n        << (len < size() ? \"...  <--- HERE\" : \" <--- HERE\");\n    out << str.substr(end);\n    if (str.size() > 0 && str.back() != '\\n')\n      out << \"\\n\";\n  }\n  const std::string& file() const {\n    return *file_;\n  }\n  const std::shared_ptr<std::string>& file_ptr() const {\n    return file_;\n  }\n  size_t start() const {\n    return start_;\n  }\n  size_t end() const {\n    return end_;\n  }\n\n private:\n  std::shared_ptr<std::string> file_;\n  size_t start_;\n  size_t end_;\n};\n\nstruct Token {\n  int kind;\n  SourceRange range;\n  Token(int kind, const SourceRange& range) : kind(kind), range(range) {}\n  double doubleValue() {\n    assert(TK_NUMBER == kind);\n    size_t idx;\n    double r = stod(text(), &idx);\n    assert(idx == range.size());\n    return r;\n  }\n  std::string text() {\n    return range.text();\n  }\n  std::string kindString() const {\n    return kindToString(kind);\n  }\n};\n\nstruct Lookahead {\n  Lookahead(const Token& t) : t(t) {}\n  Token t;\n  bool valid = false;\n  size_t repeat = 0;\n};\n\nstruct Lexer {\n  std::shared_ptr<std::string> file;\n  explicit Lexer(const std::string& str)\n      : file(std::make_shared<std::string>(str)),\n        pos(0),\n        cur_(TK_EOF, SourceRange(file, 0, 0)),\n        lookahead_(cur_),\n        repeat(0),\n        nesting(0),\n        shared(sharedParserData()) {\n    auto first_indent = lexRaw(true);\n    indent_stack.push_back(first_indent.range.size());\n    next();\n  }\n  Token next() {\n    Token r = cur_;\n    if (repeat > 0) {\n      repeat--;\n    } else if (lookahead_.valid) {\n      lookahead_.valid = false;\n      repeat = lookahead_.repeat;\n      cur_ = lookahead_.t;\n    } else {\n      std::tie(cur_, repeat) = lex();\n    }\n    return r;\n  }\n  bool nextIf(int kind) {\n    if (cur_.kind != kind)\n      return false;\n    next();\n    return true;\n  }\n\n  [[noreturn]] void reportError(const std::string& what) {\n    reportError(what, cur_);\n  }\n  [[noreturn]] void reportError(const std::string& what, const Token& t) {\n    std::stringstream ss;\n    ss << what << \":\\n\";\n    t.range.highlight(ss);\n    throw std::runtime_error(ss.str());\n  }\n  [[noreturn]] void expected(const std::string& what, const Token& t) {\n    std::stringstream ss;\n    ss << \"expected \" << what << \" but found '\" << t.kindString()\n       << \"' here:\\n\";\n    t.range.highlight(ss);\n    throw std::runtime_error(ss.str());\n  }\n  [[noreturn]] void expected(const std::string& what) {\n    expected(what, cur_);\n  }\n  Token expect(int kind) {\n    if (cur_.kind != kind) {\n      expected(kindToString(kind));\n    }\n    return next();\n  }\n  Token& lookahead() {\n    if (!lookahead_.valid) {\n      lookahead_.valid = true;\n      std::tie(lookahead_.t, lookahead_.repeat) = lex();\n    }\n    return lookahead_.t;\n  }\n  Token& cur() {\n    return cur_;\n  }\n\n private:\n  // token, number of times to repeat it\n  std::pair<Token, int> lex() {\n    auto r = lexRaw();\n    int repeat = 0;\n    switch (r.kind) {\n      case '(':\n      case '[':\n      case '{':\n        nesting++;\n        break;\n      case ')':\n      case ']':\n      case '}':\n        nesting--;\n        break;\n      case TK_WHITESPACE: {\n        size_t depth = r.range.size();\n        if (depth > indent_stack.back()) {\n          indent_stack.push_back(depth);\n          r.kind = TK_INDENT;\n        } else if (depth == indent_stack.back()) {\n          r.kind = TK_NEWLINE;\n        } else {\n          while (indent_stack.back() != depth) {\n            indent_stack.pop_back();\n            repeat++;\n            if (indent_stack.size() == 0) {\n              reportError(\"invalid ident level\", r);\n            }\n          }\n          repeat--; // first repeat is this return\n          r.kind = TK_DEDENT;\n        }\n      } break;\n      case TK_EOF:\n        if (indent_stack.size() > 1) {\n          r.kind = TK_DEDENT;\n          indent_stack.pop_back();\n        }\n        break;\n      default:\n        break;\n    }\n    return std::make_pair(r, repeat);\n  }\n  Token lexRaw(bool whitespace_token = false) {\n    int kind;\n    size_t start;\n    size_t length;\n    assert(file);\n    if (!shared.match(\n            *file,\n            pos,\n            nesting > 0,\n            whitespace_token,\n            &kind,\n            &start,\n            &length)) {\n      expected(\n          \"a valid token\",\n          Token((*file)[start], SourceRange(file, start, start + 1)));\n    }\n    auto t = Token(kind, SourceRange(file, start, start + length));\n    pos = start + length;\n    return t;\n  }\n  size_t pos;\n  Token cur_;\n  Lookahead lookahead_;\n  size_t repeat; // how many times to repeat the current token until we continue\n\n  size_t nesting; // depth of ( [ { nesting...\n  std::vector<int> indent_stack; // stack of identation level of blocks\n  SharedParserData& shared;\n};\n} // namespace script\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/script/parser.h",
    "content": "#pragma once\n#include \"lexer.h\"\n#include \"tree.h\"\n#include \"tree_views.h\"\n\nnamespace caffe2 {\nnamespace script {\n\nstruct Parser {\n  explicit Parser(const std::string& str)\n      : L(str), shared(sharedParserData()) {}\n\n  TreeRef parseIdent() {\n    auto t = L.expect(TK_IDENT);\n    // whenever we parse something that has a TreeView type we always\n    // use its create method so that the accessors and the constructor\n    // of the Compound tree are in the same place.\n    return Ident::create(t.range, t.text());\n  }\n  TreeRef createApply(TreeRef ident, TreeList& inputs) {\n    TreeList attributes;\n    auto range = L.cur().range;\n    parseOperatorArguments(inputs, attributes);\n    return Apply::create(\n        range,\n        ident,\n        List(range, std::move(inputs)),\n        List(range, std::move(attributes)));\n  }\n  // things like a 1.0 or a(4) that are not unary/binary expressions\n  // and have higher precedence than all of them\n  TreeRef parseBaseExp() {\n    TreeRef prefix;\n    switch (L.cur().kind) {\n      case TK_NUMBER:\n      case TK_TRUE:\n      case TK_FALSE: {\n        prefix = parseConst();\n      } break;\n      case '(': {\n        L.next();\n        prefix = parseExp();\n        L.expect(')');\n      } break;\n      case TK_FLOAT:\n      case TK_INT:\n      case TK_LONG: {\n        auto r = L.cur().range;\n        auto type = c(L.next().kind, r, {});\n        L.expect('(');\n        auto exp = parseExp();\n        L.expect(')');\n        prefix = Cast::create(r, type, exp);\n      } break;\n      default: {\n        prefix = parseIdent();\n        if (L.cur().kind == '(') {\n          TreeList inputs;\n          prefix = createApply(prefix, inputs);\n        }\n      } break;\n    }\n    while (true) {\n      if (L.nextIf('.')) {\n        const auto name = parseIdent();\n        if (L.cur().kind == '(') {\n          TreeList inputs = {prefix};\n          prefix = createApply(name, inputs);\n        } else {\n          prefix = Select::create(name->range(), prefix, name);\n        }\n      } else if (L.cur().kind == '[') {\n        prefix = parseSliceOrGather(prefix);\n      } else {\n        break;\n      }\n    }\n    return prefix;\n  }\n  TreeRef parseOptionalReduction() {\n    auto r = L.cur().range;\n    switch (L.cur().kind) {\n      case TK_PLUS_EQ:\n      case TK_MINUS_EQ:\n      case TK_TIMES_EQ:\n      case TK_DIV_EQ: {\n        int modifier = L.next().text()[0];\n        return c(modifier, r, {});\n      } break;\n      default: {\n        L.expect('=');\n        return c('=', r, {}); // no reduction\n      } break;\n    }\n  }\n  TreeRef\n  parseTrinary(TreeRef true_branch, const SourceRange& range, int binary_prec) {\n    auto cond = parseExp();\n    L.expect(TK_ELSE);\n    auto false_branch = parseExp(binary_prec);\n    return c(TK_IF_EXPR, range, {cond, true_branch, false_branch});\n  }\n  // parse the longest expression whose binary operators have\n  // precedence strictly greater than 'precedence'\n  // precedence == 0 will parse _all_ expressions\n  // this is the core loop of 'top-down precedence parsing'\n  TreeRef parseExp(int precedence = 0) {\n    TreeRef prefix = nullptr;\n    int unary_prec;\n    if (shared.isUnary(L.cur().kind, &unary_prec)) {\n      auto kind = L.cur().kind;\n      auto pos = L.cur().range;\n      L.next();\n      prefix = c(kind, pos, {parseExp(unary_prec)});\n    } else {\n      prefix = parseBaseExp();\n    }\n    int binary_prec;\n    while (shared.isBinary(L.cur().kind, &binary_prec)) {\n      if (binary_prec <= precedence) // not allowed to parse something which is\n        // not greater than 'precedenc'\n        break;\n\n      int kind = L.cur().kind;\n      auto pos = L.cur().range;\n      L.next();\n      if (shared.isRightAssociative(kind))\n        binary_prec--;\n\n      // special case for trinary operator\n      if (kind == TK_IF) {\n        prefix = parseTrinary(prefix, pos, binary_prec);\n        continue;\n      }\n\n      prefix = c(kind, pos, {prefix, parseExp(binary_prec)});\n    }\n    return prefix;\n  }\n  TreeRef\n  parseList(int begin, int sep, int end, std::function<TreeRef(int)> parse) {\n    auto r = L.cur().range;\n    L.expect(begin);\n    TreeList elements;\n    if (L.cur().kind != end) {\n      int i = 0;\n      do {\n        elements.push_back(parse(i++));\n      } while (L.nextIf(sep));\n    }\n    L.expect(end);\n    return c(TK_LIST, r, std::move(elements));\n  }\n  TreeRef parseNonEmptyList(int sep, std::function<TreeRef(int)> parse) {\n    TreeList elements;\n    int i = 0;\n    do {\n      elements.push_back(parse(i++));\n    } while (L.nextIf(sep));\n    return c(TK_LIST, elements[0]->range(), std::move(elements));\n  }\n  TreeRef parseExpList() {\n    return parseList('(', ',', ')', [&](int i) { return parseExp(); });\n  }\n  TreeRef parseConst() {\n    // 'b' - boolean\n    // 'LL' 64-bit integer\n    // 'f' single-precision float\n    // 'i' 32-bit integer\n    // 'f' is default if '.' appears in the number\n    auto range = L.cur().range;\n    if (L.nextIf(TK_TRUE)) {\n      return c(TK_CONST, range, {d(1), s(\"b\")});\n    } else if (L.nextIf(TK_FALSE)) {\n      return c(TK_CONST, range, {d(0), s(\"b\")});\n    }\n    float mult = 1.0f;\n    while (L.nextIf('-')) {\n      mult *= -1.0f;\n    }\n    auto t = L.expect(TK_NUMBER);\n    std::string type_ident =\n        (t.text().find('.') == std::string::npos) ? \"i\" : \"f\";\n    if (L.cur().kind == TK_IDENT) {\n      Token type_ident_tok = L.expect(TK_IDENT);\n      type_ident = type_ident_tok.text();\n      if (type_ident != \"LL\" && type_ident != \"f\") {\n        throw ErrorReport(type_ident_tok)\n            << \"expected 'f' or 'LL' \"\n            << \"as numeric type identifier but found '\" << type_ident << \"'\";\n      }\n    }\n    return c(TK_CONST, t.range, {d(mult * t.doubleValue()), s(type_ident)});\n  }\n  TreeRef parseAttributeValue() {\n    int kind = L.cur().kind;\n    switch (kind) {\n      case '[':\n        return parseList('[', ',', ']', [&](int i) { return parseConst(); });\n      default:\n        return parseConst();\n    }\n  }\n  void parseOperatorArguments(TreeList& inputs, TreeList& attributes) {\n    L.expect('(');\n    if (L.cur().kind != ')') {\n      do {\n        if (L.cur().kind == TK_IDENT && L.lookahead().kind == '=') {\n          auto ident = parseIdent();\n          L.expect('=');\n          auto v = parseAttributeValue();\n          attributes.push_back(Attribute::create(ident->range(), ident, v));\n        } else {\n          inputs.push_back(parseExp());\n        }\n      } while (L.nextIf(','));\n    }\n    L.expect(')');\n  }\n\n  // OK: [a] (gather), [a:], [:a], [a:b], [:] (slice)\n  // Not OK: []\n  TreeRef parseSliceOrGather(TreeRef value) {\n    const auto range = L.cur().range;\n    L.expect('[');\n\n    // `first` will either be the gather indices, or the start of the slice.\n    TreeRef first, second;\n\n    // Here we can either have a colon (which starts a slice), or an expression.\n    // If an expression, we don't know yet if it will be a slice or a gather.\n    if (L.cur().kind != ':') {\n      first = parseExp();\n      if (L.nextIf(']')) {\n        return Gather::create(range, value, first);\n      } else {\n        first = c(TK_OPTION, range, {first});\n      }\n    } else {\n      first = c(TK_OPTION, range, {});\n    }\n    L.expect(':');\n    // Now we *may* have an expression.\n    if (L.cur().kind != ']') {\n      second = c(TK_OPTION, range, {parseExp()});\n    } else {\n      second = c(TK_OPTION, range, {});\n    }\n    L.expect(']');\n\n    return Slice::create(range, value, first, second);\n  }\n  TreeRef parseIdentList() {\n    return parseList('(', ',', ')', [&](int i) { return parseIdent(); });\n  }\n  TreeRef parseParam() {\n    auto typ = parseType();\n    if (L.cur().kind != TK_IDENT && typ->trees()[0]->kind() == TK_IDENT) {\n      // oops, it wasn't a type but just a param without any type specified\n      return Param::create(\n          typ->range(), typ->trees()[0], c(TK_INFERRED, typ->range(), {}));\n    }\n    auto ident = parseIdent();\n    return Param::create(typ->range(), ident, typ);\n  }\n  // TODO: these functions should be unnecessary, but we currently do not\n  // emit a TK_NEWLINE before a series of TK_DEDENT tokens\n  // so if we see a TK_DEDENT then we know a newline must have happened and\n  // ignore it. The real fix is to patch the lexer so TK_NEWLINE does get\n  // emited before a TK_INDENT\n  void expectEndOfLine() {\n    if (L.cur().kind != TK_DEDENT)\n      L.expect(TK_NEWLINE);\n  }\n  bool isEndOfLine() {\n    return L.cur().kind == TK_NEWLINE || L.cur().kind == TK_DEDENT;\n  }\n\n  // 'first' has already been parsed since expressions can exist\n  // alone on a line:\n  // first[,other,lhs] = rhs\n  TreeRef parseAssign(TreeRef first) {\n    TreeRef list = parseOneOrMoreExp(first);\n    auto red = parseOptionalReduction();\n    auto rhs = parseExp();\n    expectEndOfLine();\n    return Assign::create(list->range(), list, red, rhs);\n  }\n  TreeRef parseStmt() {\n    switch (L.cur().kind) {\n      case TK_IF:\n        return parseIf();\n      case TK_WHILE:\n        return parseWhile();\n      case TK_GLOBAL: {\n        auto range = L.next().range;\n        std::vector<TreeRef> idents;\n        do {\n          idents.push_back(parseIdent());\n        } while (L.nextIf(','));\n        expectEndOfLine();\n        return c(TK_GLOBAL, range, std::move(idents));\n      }\n      default: {\n        auto r = parseExp();\n        if (!isEndOfLine()) {\n          return parseAssign(r);\n        } else {\n          expectEndOfLine();\n          return r;\n        }\n      }\n    }\n  }\n  TreeRef parseScalarType() {\n    switch (L.cur().kind) {\n      case TK_INT:\n      case TK_FLOAT:\n      case TK_LONG:\n      case TK_DOUBLE: {\n        auto t = L.next();\n        return c(t.kind, t.range, {});\n      }\n      default:\n        return parseIdent();\n    }\n  }\n  TreeRef parseOptionalIdentList() {\n    TreeRef list = nullptr;\n    if (L.cur().kind == '(') {\n      list = parseIdentList();\n    } else {\n      list = c(TK_LIST, L.cur().range, {});\n    }\n    return list;\n  }\n  TreeRef parseType() {\n    auto st = parseScalarType();\n    auto list = parseOptionalIdentList();\n    return TensorType::create(st->range(), st, list);\n  }\n  // 'first' has already been parsed, add the rest\n  // if they exist\n  // first[, the, rest]\n  TreeRef parseOneOrMoreExp(TreeRef first) {\n    TreeList list{first};\n    while (L.nextIf(',')) {\n      list.push_back(parseExp());\n    }\n    return List(list.back()->range(), std::move(list));\n  }\n  TreeRef parseIf() {\n    auto r = L.cur().range;\n    L.expect(TK_IF);\n    auto cond = parseExp();\n    L.expect(':');\n    auto true_branch = parseStatements();\n    auto false_branch = List(L.cur().range, {});\n    if (L.nextIf(TK_ELSE)) {\n      L.expect(':');\n      false_branch = parseStatements();\n    }\n    return If::create(r, cond, true_branch, false_branch);\n  }\n  TreeRef parseWhile() {\n    auto r = L.cur().range;\n    L.expect(TK_WHILE);\n    auto cond = parseExp();\n    L.expect(':');\n    auto body = parseStatements();\n    return While::create(r, cond, body);\n  }\n  TreeRef parseStatements() {\n    auto r = L.cur().range;\n    L.expect(TK_INDENT);\n    TreeList stmts;\n    while (true) {\n      stmts.push_back(parseStmt());\n      if (L.nextIf(TK_DEDENT))\n        break;\n    }\n    return c(TK_LIST, r, std::move(stmts));\n  }\n  TreeRef parseFunction() {\n    L.expect(TK_DEF);\n    auto name = parseIdent();\n    auto paramlist =\n        parseList('(', ',', ')', [&](int i) { return parseParam(); });\n    L.expect(TK_ARROW);\n    auto retlist =\n        parseList('(', ',', ')', [&](int i) { return parseParam(); });\n    L.expect(':');\n    auto stmts_list = parseStatements();\n    return Def::create(name->range(), name, paramlist, retlist, stmts_list);\n  }\n  Lexer& lexer() {\n    return L;\n  }\n\n private:\n  // short helpers to create nodes\n  TreeRef d(double v) {\n    return Number::create(v);\n  }\n  TreeRef s(const std::string& s) {\n    return String::create(s);\n  }\n  TreeRef c(int kind, const SourceRange& range, TreeList&& trees) {\n    return Compound::create(kind, range, std::move(trees));\n  }\n  TreeRef List(const SourceRange& range, TreeList&& trees) {\n    return c(TK_LIST, range, std::move(trees));\n  }\n  Lexer L;\n  SharedParserData& shared;\n};\n} // namespace script\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/script/tree.h",
    "content": "#pragma once\n\n#include <memory>\n#include <vector>\n\n#include \"caffe2/contrib/script/lexer.h\"\n\nnamespace caffe2 {\nnamespace script {\n\n// Tree's are used to represent all forms of TC IR, pre- and post- typechecking.\n// Rather than have a full class hierarchy for all TC statements,\n// Trees are a slight variation of Lisp S-expressions.\n// for instance the expression a*b+1 is represented as:\n// (+ (* (ident a) (ident b)) (const 1))\n// Atoms like 'a', 'b', and '1' are represented by subclasses of Tree which\n// define stringValue() and doubleValue().\n// Everything else is a Compound object, which has a 'kind' that is a token from\n// Lexer.h's TokenKind enum, and contains a list of subtrees.\n// Like TokenKind single-character operators like '+' are representing using the\n// character itself, so add.kind() == '+'.\n// Compound objects are also always associated with a SourceRange for\n// reporting error message.\n\n// Memory management of trees is done using shared_ptr.\n\nstruct Tree;\nusing TreeRef = std::shared_ptr<Tree>;\nusing TreeList = std::vector<TreeRef>;\n\nstatic const TreeList empty_trees = {};\n\nstruct Tree : std::enable_shared_from_this<Tree> {\n  Tree(int kind_) : kind_(kind_) {}\n  int kind() const {\n    return kind_;\n  }\n  virtual bool isAtom() const {\n    return true;\n  }\n  virtual const SourceRange& range() const {\n    throw std::runtime_error(\"is an Atom\");\n  }\n  virtual double doubleValue() const {\n    throw std::runtime_error(\"not a TK_NUMBER\");\n  }\n  virtual const std::string& stringValue() const {\n    throw std::runtime_error(\"not a TK_STRING\");\n  }\n  virtual bool boolValue() const {\n    throw std::runtime_error(\"not a TK_BOOL\");\n  }\n  virtual const TreeList& trees() const {\n    return empty_trees;\n  }\n  const TreeRef& tree(size_t i) const {\n    return trees().at(i);\n  }\n  virtual TreeRef map(std::function<TreeRef(TreeRef)> fn) {\n    return shared_from_this();\n  }\n  template <typename... Args>\n  void match(int k, Args&... args) {\n    matchD(k, \"unknown\", 0, args...);\n  }\n  template <typename... Args>\n  void matchD(int k, const char* filename, int lineno, Args&... args) {\n    if (kind() != k) {\n      std::stringstream ss;\n      ss << filename << \":\" << lineno << \": expecting kind '\" << kindToString(k)\n         << \"' but found '\" << kind() << \"'\\n\";\n      range().highlight(ss);\n      throw std::runtime_error(ss.str());\n    }\n    std::initializer_list<TreeRef*> vars = {&args...};\n    if (vars.size() > trees().size()) {\n      std::stringstream ss;\n      ss << filename << \":\" << lineno << \": trying to match \" << vars.size()\n         << \" variables against \" << trees().size() << \" values in list.\\n\";\n      range().highlight(ss);\n      throw std::runtime_error(ss.str());\n    }\n    size_t i = 0;\n    for (TreeRef* v : vars) {\n      *v = trees()[i++];\n    }\n  }\n  virtual ~Tree() {}\n\n private:\n  int kind_;\n};\n\nstruct String : public Tree {\n  String(const std::string& value_) : Tree(TK_STRING), value_(value_) {}\n  virtual const std::string& stringValue() const override {\n    return value_;\n  }\n  template <typename... Args>\n  static TreeRef create(Args&&... args) {\n    return std::make_shared<String>(std::forward<Args>(args)...);\n  }\n\n private:\n  std::string value_;\n};\nstruct Number : public Tree {\n  Number(double value_) : Tree(TK_NUMBER), value_(value_) {}\n  virtual double doubleValue() const override {\n    return value_;\n  }\n  template <typename... Args>\n  static TreeRef create(Args&&... args) {\n    return std::make_shared<Number>(std::forward<Args>(args)...);\n  }\n\n private:\n  double value_;\n};\nstruct Bool : public Tree {\n  Bool(bool value_) : Tree(TK_BOOL), value_(value_) {}\n  virtual double doubleValue() const override {\n    return value_;\n  }\n  template <typename... Args>\n  static TreeRef create(Args&&... args) {\n    return std::make_shared<Bool>(std::forward<Args>(args)...);\n  }\n\n private:\n  bool value_;\n};\n\nstatic SourceRange mergeRanges(SourceRange c, const TreeList& others) {\n  for (auto t : others) {\n    if (t->isAtom())\n      continue;\n    size_t s = std::min(c.start(), t->range().start());\n    size_t e = std::max(c.end(), t->range().end());\n    c = SourceRange(c.file_ptr(), s, e);\n  }\n  return c;\n}\n\nstruct Compound : public Tree {\n  Compound(int kind, const SourceRange& range_) : Tree(kind), range_(range_) {}\n  Compound(int kind, const SourceRange& range_, TreeList&& trees_)\n      : Tree(kind),\n        range_(mergeRanges(range_, trees_)),\n        trees_(std::move(trees_)) {}\n  virtual const TreeList& trees() const override {\n    return trees_;\n  }\n  static TreeRef\n  create(int kind, const SourceRange& range_, TreeList&& trees_) {\n    return std::make_shared<Compound>(kind, range_, std::move(trees_));\n  }\n  virtual bool isAtom() const override {\n    return false;\n  }\n  virtual TreeRef map(std::function<TreeRef(TreeRef)> fn) override {\n    TreeList trees_;\n    for (auto& t : trees()) {\n      trees_.push_back(fn(t));\n    }\n    return Compound::create(kind(), range(), std::move(trees_));\n  }\n  const SourceRange& range() const override {\n    return range_;\n  }\n\n private:\n  SourceRange range_;\n  TreeList trees_;\n};\n\n// tree pretty printer\nstruct pretty_tree {\n  pretty_tree(const TreeRef& tree, size_t col = 40) : tree(tree), col(col) {}\n  const TreeRef& tree;\n  size_t col;\n  std::unordered_map<TreeRef, std::string> flat_strings;\n  const std::string& get_flat(const TreeRef& t) {\n    auto it = flat_strings.find(t);\n    if (it != flat_strings.end())\n      return it->second;\n\n    std::stringstream out;\n    switch (t->kind()) {\n      case TK_NUMBER:\n        out << t->doubleValue();\n        break;\n      case TK_STRING:\n        out << t->stringValue();\n        break;\n      default:\n        out << \"(\" << kindToString(t->kind());\n        for (auto e : t->trees()) {\n          out << \" \" << get_flat(e);\n        }\n        out << \")\";\n        break;\n    }\n    auto it_ = flat_strings.emplace(t, out.str());\n    return it_.first->second;\n  }\n  void print(std::ostream& out, const TreeRef& t, int indent) {\n    const std::string& s = get_flat(t);\n    if (indent + s.size() < col || t->isAtom()) {\n      out << s;\n      return;\n    }\n    std::string k = kindToString(t->kind());\n    out << \"(\" << k;\n    for (auto e : t->trees()) {\n      out << \"\\n\" << std::string(indent + 2, ' ');\n      print(out, e, indent + 2);\n    }\n    out << \")\";\n  }\n};\n\nstatic inline std::ostream& operator<<(std::ostream& out, pretty_tree t_) {\n  t_.print(out, t_.tree, 0);\n  return out << std::endl;\n}\n\nstatic inline std::ostream& operator<<(std::ostream& out, TreeRef t) {\n  return out << pretty_tree(t);\n}\n\n} // namespace script\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/script/tree_views.h",
    "content": "#pragma once\n#include \"error_report.h\"\n#include \"tree.h\"\n\nnamespace caffe2 {\nnamespace script {\n\n// TreeView provides a statically-typed way to access the members of a TreeRef\n// instead of using TK_MATCH\n\nstruct TreeView {\n  explicit TreeView(const TreeRef& tree_) : tree_(tree_) {}\n  TreeRef tree() const {\n    return tree_;\n  }\n  const SourceRange& range() const {\n    return tree_->range();\n  }\n  operator TreeRef() const {\n    return tree_;\n  }\n\n protected:\n  TreeRef tree_;\n};\n\ntemplate <typename T>\nstruct ListViewIterator {\n  ListViewIterator(TreeList::const_iterator it) : it(it) {}\n  bool operator!=(const ListViewIterator& rhs) const {\n    return it != rhs.it;\n  }\n  T operator*() const {\n    return T(*it);\n  }\n  void operator++() {\n    ++it;\n  }\n  void operator--() {\n    --it;\n  }\n\n private:\n  TreeList::const_iterator it;\n};\n\ntemplate <typename T>\nstruct ListView : public TreeView {\n  ListView(const TreeRef& tree) : TreeView(tree) {\n    tree->match(TK_LIST);\n  }\n  typedef ListViewIterator<T> iterator;\n  typedef ListViewIterator<T> const_iterator;\n  iterator begin() const {\n    return iterator(tree_->trees().begin());\n  }\n  iterator end() const {\n    return iterator(tree_->trees().end());\n  }\n  T operator[](size_t i) const {\n    return T(tree_->trees().at(i));\n  }\n  TreeRef map(std::function<TreeRef(const T&)> fn) {\n    return tree_->map([&](TreeRef v) { return fn(T(v)); });\n  }\n  size_t size() const {\n    return tree_->trees().size();\n  }\n};\n\ntemplate <typename T>\nstruct OptionView : public TreeView {\n  explicit OptionView(const TreeRef& tree) : TreeView(tree) {\n    C2S_ASSERT(tree, tree->kind() == TK_OPTION);\n  }\n  bool present() const {\n    return tree_->trees().size() > 0;\n  }\n  T get() const {\n    C2S_ASSERT(tree_, present());\n    return T(tree_->trees()[0]);\n  }\n  TreeRef map(std::function<TreeRef(const T&)> fn) {\n    return tree_->map([&](TreeRef v) { return fn(T(v)); });\n  }\n};\n\nstruct Ident : public TreeView {\n  // each subclass of TreeView provides:\n  // 1. a constructor that takes a TreeRef, and matches it to the right type.\n  explicit Ident(const TreeRef& tree) : TreeView(tree) {\n    tree_->match(TK_IDENT, name_);\n  }\n  // 2. accessors that get underlying information out of the object\n  // in this case, we return the name of the identifier, and handle the\n  // converstion to a string in the method\n  const std::string& name() const {\n    return name_->stringValue();\n  }\n\n  // 3. a static method 'create' that creates the underlying TreeRef object\n  // for every TreeRef kind that has a TreeView, the parser always uses\n  // (e.g.) Ident::create rather than Compound::Create, this means that\n  // changes to the structure of Ident are always made right here rather\n  // than both in the parser and in this code\n  static TreeRef create(const SourceRange& range, const std::string& name) {\n    return Compound::create(TK_IDENT, range, {String::create(name)});\n  }\n\n private:\n  TreeRef name_;\n};\n\nstruct Attribute : public TreeView {\n  explicit Attribute(const TreeRef& tree) : TreeView(tree) {\n    tree_->match(TK_ATTRIBUTE, name_, value_);\n  }\n  Ident name() const {\n    return Ident(name_);\n  }\n  TreeRef value() const {\n    return value_;\n  }\n  static TreeRef create(const SourceRange& range, TreeRef name, TreeRef value) {\n    return Compound::create(TK_ATTRIBUTE, range, {name, value});\n  }\n\n private:\n  TreeRef name_;\n  TreeRef value_;\n};\n\nstruct Apply : public TreeView {\n  explicit Apply(const TreeRef& tree) : TreeView(tree) {\n    tree_->match(TK_APPLY, name_, inputs_, attributes_);\n  }\n\n  Ident name() const {\n    return Ident(name_);\n  }\n  ListView<TreeRef> inputs() const {\n    return ListView<TreeRef>(inputs_);\n  }\n  ListView<Attribute> attributes() const {\n    return ListView<Attribute>(attributes_);\n  }\n\n  static TreeRef create(\n      const SourceRange& range,\n      TreeRef name,\n      TreeRef inputs,\n      TreeRef attributes) {\n    return Compound::create(TK_APPLY, range, {name, inputs, attributes});\n  }\n\n private:\n  TreeRef name_;\n  TreeRef inputs_;\n  TreeRef attributes_;\n};\n\nstruct Slice : public TreeView {\n  explicit Slice(const TreeRef& tree) : TreeView(tree) {\n    tree_->match(TK_SLICE, value_, start_, end_);\n  }\n\n  TreeRef value() const {\n    return value_;\n  }\n\n  OptionView<TreeRef> start() const {\n    return OptionView<TreeRef>(start_);\n  }\n\n  OptionView<TreeRef> end() const {\n    return OptionView<TreeRef>(end_);\n  }\n\n  TreeRef startOr(int alternative) const {\n    const auto startOption = start();\n    return startOption.present() ? startOption.get() : createInt(alternative);\n  }\n\n  TreeRef endOr(int alternative) const {\n    const auto endOption = end();\n    return endOption.present() ? endOption.get() : createInt(alternative);\n  }\n\n  static TreeRef\n  create(const SourceRange& range, TreeRef value, TreeRef start, TreeRef end) {\n    return Compound::create(TK_SLICE, range, {value, start, end});\n  }\n\n private:\n  TreeRef createInt(int value) const {\n    return Compound::create(\n        TK_CONST, range(), {Number::create(value), String::create(\"i\")});\n  }\n\n  TreeRef value_;\n  TreeRef start_;\n  TreeRef end_;\n};\n\nstruct Gather : public TreeView {\n  explicit Gather(const TreeRef& tree) : TreeView(tree) {\n    tree_->match(TK_GATHER, value_, indices_);\n  }\n\n  TreeRef value() const {\n    return value_;\n  }\n\n  TreeRef indices() const {\n    return indices_;\n  }\n\n  static TreeRef\n  create(const SourceRange& range, TreeRef value, TreeRef indices) {\n    return Compound::create(TK_GATHER, range, {value, indices});\n  }\n\n private:\n  TreeRef value_;\n  TreeRef indices_;\n};\n\nstruct Cast : public TreeView {\n  explicit Cast(const TreeRef& tree) : TreeView(tree) {\n    tree_->match(TK_CAST, type_, input_);\n  }\n\n  int type() const {\n    return type_->kind();\n  }\n  TreeRef input() const {\n    return input_;\n  }\n\n  static TreeRef create(const SourceRange& range, TreeRef type, TreeRef input) {\n    return Compound::create(TK_CAST, range, {type, input});\n  }\n\n private:\n  TreeRef type_;\n  TreeRef input_;\n};\n\nstruct TensorType : public TreeView {\n  explicit TensorType(const TreeRef& tree) : TreeView(tree) {\n    tree_->match(TK_TENSOR_TYPE, scalar_type_, dims_);\n  }\n  static TreeRef\n  create(const SourceRange& range, TreeRef scalar_type_, TreeRef dims_) {\n    return Compound::create(TK_TENSOR_TYPE, range, {scalar_type_, dims_});\n  }\n  int scalarType() const {\n    if (scalar_type_->kind() == TK_IDENT)\n      throw ErrorReport(tree_)\n          << \" TensorType has a symbolic ident \" << Ident(scalar_type_).name()\n          << \" rather than a concrete type\";\n    return scalar_type_->kind();\n  }\n  ListView<Ident> dims() const {\n    return ListView<Ident>(dims_);\n  }\n\n private:\n  TreeRef scalar_type_;\n  TreeRef dims_;\n};\n\nstruct Param : public TreeView {\n  explicit Param(const TreeRef& tree) : TreeView(tree) {\n    tree_->match(TK_PARAM, ident_, type_);\n  }\n  static TreeRef create(const SourceRange& range, TreeRef ident, TreeRef type) {\n    return Compound::create(TK_PARAM, range, {ident, type});\n  }\n  // when the type of a field is statically know the accessors return\n  // the wrapped type. for instance here we know ident_ is an identifier\n  // so the accessor returns an Ident\n  // this means that clients can do p.ident().name() to get the name of the\n  // parameter.\n  Ident ident() const {\n    return Ident(ident_);\n  }\n  // may be TensorType or TK_INFERRED\n  TreeRef type() const {\n    return type_;\n  }\n  bool typeIsInferred() const {\n    return type_->kind() == TK_INFERRED;\n  }\n  // helper for when you know the type is not inferred.\n  TensorType tensorType() const {\n    return TensorType(type_);\n  }\n\n private:\n  TreeRef ident_;\n  TreeRef type_;\n};\n\nstruct Assign : public TreeView {\n  explicit Assign(const TreeRef& tree) : TreeView(tree) {\n    tree_->match(TK_ASSIGN, lhs_, reduction_, rhs_);\n  }\n  static TreeRef create(\n      const SourceRange& range,\n      TreeRef lhs,\n      TreeRef reduction,\n      TreeRef rhs) {\n    return Compound::create(TK_ASSIGN, range, {lhs, reduction, rhs});\n  }\n  // when the type of a field is statically know the accessors return\n  // the wrapped type. for instance here we know ident_ is an identifier\n  // so the accessor returns an Ident\n  // this means that clients can do p.ident().name() to get the name of the\n  // parameter.\n  ListView<TreeRef> lhs() const {\n    return ListView<TreeRef>(lhs_);\n  }\n  int reduction() const {\n    return reduction_->kind();\n  }\n  TreeRef rhs() const {\n    return rhs_;\n  }\n\n private:\n  TreeRef lhs_;\n  TreeRef reduction_;\n  TreeRef rhs_;\n};\n\nstruct Def : public TreeView {\n  explicit Def(const TreeRef& tree) : TreeView(tree) {\n    tree->match(TK_DEF, name_, paramlist, retlist, stmts_list);\n  }\n  Ident name() const {\n    return Ident(name_);\n  }\n  // ListView helps turn TK_LISTs into vectors of TreeViews\n  // so that we can, e.g., return lists of parameters\n  ListView<Param> params() const {\n    return ListView<Param>(paramlist);\n  }\n  ListView<Param> returns() const {\n    return ListView<Param>(retlist);\n  }\n  ListView<TreeRef> statements() const {\n    return ListView<TreeRef>(stmts_list);\n  }\n  static TreeRef create(\n      const SourceRange& range,\n      TreeRef name,\n      TreeRef paramlist,\n      TreeRef retlist,\n      TreeRef stmts_list) {\n    return Compound::create(\n        TK_DEF, range, {name, paramlist, retlist, stmts_list});\n  }\n\n private:\n  TreeRef name_;\n  TreeRef paramlist;\n  TreeRef retlist;\n  TreeRef stmts_list;\n};\n\nstruct Select : public TreeView {\n  explicit Select(const TreeRef& tree) : TreeView(tree) {\n    tree_->match('.', value_, selector_);\n  }\n  TreeRef value() const {\n    return value_;\n  }\n  Ident selector() const {\n    return Ident(selector_);\n  }\n  static TreeRef\n  create(const SourceRange& range, TreeRef value, TreeRef selector) {\n    return Compound::create('.', range, {value, selector});\n  }\n\n private:\n  TreeRef value_;\n  TreeRef selector_;\n};\n\nstruct If : public TreeView {\n  explicit If(const TreeRef& tree) : TreeView(tree) {\n    tree_->match(TK_IF, cond_, true_branch_, false_branch_);\n  }\n  const TreeRef& cond() const {\n    return cond_;\n  }\n  ListView<TreeRef> trueBranch() const {\n    return ListView<TreeRef>(true_branch_);\n  }\n  ListView<TreeRef> falseBranch() const {\n    return ListView<TreeRef>(false_branch_);\n  }\n\n  static TreeRef create(\n      const SourceRange& range,\n      TreeRef cond_,\n      TreeRef true_branch_,\n      TreeRef false_branch_) {\n    return Compound::create(TK_IF, range, {cond_, true_branch_, false_branch_});\n  }\n\n private:\n  TreeRef cond_;\n  TreeRef true_branch_;\n  TreeRef false_branch_;\n};\n\nstruct While : public TreeView {\n  explicit While(const TreeRef& tree) : TreeView(tree) {\n    tree_->match(TK_WHILE, cond_, body_);\n  }\n  const TreeRef& cond() const {\n    return cond_;\n  }\n  ListView<TreeRef> body() const {\n    return ListView<TreeRef>(body_);\n  }\n\n  static TreeRef\n  create(const SourceRange& range, TreeRef cond_, TreeRef body_) {\n    return Compound::create(TK_WHILE, range, {cond_, body_});\n  }\n\n private:\n  TreeRef cond_;\n  TreeRef body_;\n};\n\n} // namespace script\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/contrib/shm_mutex/CMakeLists.txt",
    "content": "if(USE_SHM_MUTEX)\n  set(Caffe2_CONTRIB_SHMMUTEX_CPU_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/shm_mutex.cc\"\n    )\n\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${Caffe2_CONTRIB_SHMMUTEX_CPU_SRC} PARENT_SCOPE)\nendif()\n"
  },
  {
    "path": "caffe2/contrib/shm_mutex/shm_mutex.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"shm_mutex.h\"\n\nShmProcessMutexCheck& ShmProcessMutexCheck::getInstance() {\n  static ShmProcessMutexCheck singleton;\n  return singleton;\n}\n\nbool ShmProcessMutexCheck::addLock(const std::string& name) {\n  std::lock_guard<std::mutex> l(m_);\n  auto p = shmLocks_.emplace(name);\n  return p.second;\n}\n\nbool ShmProcessMutexCheck::removeLock(const std::string& name) {\n  std::lock_guard<std::mutex> l(m_);\n  return shmLocks_.erase(name) == 1;\n}\n"
  },
  {
    "path": "caffe2/contrib/shm_mutex/shm_mutex.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * This implements a machine-wide mutex to be used\n * to synchronize CUDA calls (memory allocation and frees) and\n * NCCL calls. This prevents a potential deadlock that\n * can occur.\n *\n * The implementation has a few caveats:\n *   - it assumes that PID are not reused\n *   - there is a possible race between the creation (shm_open followed\n *     by ftruncate) and the spin on 'isInitialized' (if the memory region is\n *     not all zeroes).\n *\n * There are two implementations of the mutex and they vary mostly by how\n * they wait:\n *   - The ShmTicketMutex_t is a simple ticket based lock and processes will\n *     queue up and only attempt to grab the lock when it is their turn\n *   - The ShmTTSetMutex_t is a simple test-test-and-set mutex. It is possibly\n *     faster for low contention.\n *\n * Use both as you would use any std::mutex. Both mutexes support try_lock as\n * well.\n */\n#pragma once\n\n#include <fcntl.h>\n#include <signal.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n#include <climits>\n\n#include <atomic>\n#include <mutex>\n#include <string>\n#include <unordered_set>\n\n#include \"caffe2/core/logging.h\"\n\nconst int kTicketDelay = 1000;\nconst int kTimeout = 1000;\n\nclass ShmProcessMutexCheck {\n public:\n  static ShmProcessMutexCheck& getInstance();\n  ShmProcessMutexCheck(const ShmProcessMutexCheck&) = delete;\n  ShmProcessMutexCheck& operator=(const ShmProcessMutexCheck&) = delete;\n\n  bool addLock(const std::string& name);\n  bool removeLock(const std::string& name);\n\n protected:\n  ShmProcessMutexCheck() = default;\n  std::mutex m_;\n  std::unordered_set<std::string> shmLocks_;\n};\n\ntemplate <class Derived>\nstruct shm_traits;\n\nusing ShmBaseHeader = struct {\n  std::atomic<bool> isInitialized;\n  std::atomic<int> countMapped;\n  std::atomic<pid_t> owner;\n};\n\ntemplate <class Impl>\nclass ShmProcessMutex {\n public:\n  using header_t = typename shm_traits<Impl>::header_t;\n\n  explicit ShmProcessMutex(const char* name)\n      : name_(name), check_(ShmProcessMutexCheck::getInstance()) {\n    CAFFE_ENFORCE(check_.addLock(name_), \"Creating duplicate lock: \", name_);\n    myPid_ = getpid();\n    // Try to open and map the shared memory location\n    int fd = -1;\n    while (true) {\n      fd = shm_open(name, O_RDWR, 0);\n      if (fd == -1) {\n        CAFFE_ENFORCE(\n            errno == ENOENT,\n            \"shm_open failed with not ENOENT: \",\n            strerror(errno));\n\n        // Create new object\n        fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0700);\n        if (fd == -1 && errno == EEXIST) {\n          // Some other process created first; loop around to re-open\n          continue;\n        }\n        CAFFE_ENFORCE(\n            fd != -1, \"shm_open failed with create: \", strerror(errno));\n        // At this point, we are the creator of the shared object.\n        // Initialize the header_ (it's all 0 right now)\n        auto rv = ftruncate(fd, sizeof(header_t));\n        CAFFE_ENFORCE(rv != -1, \"ftruncate: \", strerror(errno));\n\n        // Map memory and initialize\n        header_ = (header_t*)mmap(\n            nullptr,\n            sizeof(header_t),\n            PROT_READ | PROT_WRITE,\n            MAP_SHARED,\n            fd,\n            0);\n        CAFFE_ENFORCE(header_ != MAP_FAILED, \"mmap: \", strerror(errno));\n\n        header_->countMapped = 1;\n        header_->owner = 0;\n        header_->isInitialized.store(true, std::memory_order_release);\n        close(fd);\n        break;\n      } else {\n        // Object exists, we just map it\n        header_ = (header_t*)mmap(\n            nullptr,\n            sizeof(header_t),\n            PROT_READ | PROT_WRITE,\n            MAP_SHARED,\n            fd,\n            0);\n        CAFFE_ENFORCE(header_ != MAP_FAILED, \"mmap: \", strerror(errno));\n\n        // Wait for memory to be initialized\n        while (header_->isInitialized.load(std::memory_order_acquire) == 0) {\n          // Spin; should be done soon\n        }\n        // Now check if we can register ourself by incrementing countMapped.\n        // If we are \"locked-out\" (shared object being destroyed), retry\n        if (header_->countMapped.fetch_add(1, std::memory_order_relaxed) < 0) {\n          header_->countMapped.fetch_sub(1, std::memory_order_relaxed);\n          int rv = munmap(header_, sizeof(header_t));\n          CAFFE_ENFORCE(rv == 0, \"munmap (to retry) failed: \", strerror(errno));\n          close(fd);\n          continue;\n        }\n        close(fd);\n        break;\n      }\n    }\n  }\n\n  ~ShmProcessMutex() {\n    if (header_ != nullptr) {\n      // We are participating in a lock. Destroy\n      internalDestroy();\n    }\n  }\n\n  // Copy and assignment operator are implicitly deleted\n\n  ShmProcessMutex(ShmProcessMutex&& toMove) noexcept\n      : header_(toMove.header_),\n        myPid_(toMove.myPid_),\n        name_(toMove.name_),\n        check_(toMove.check_) {\n    toMove.header_ = nullptr;\n    toMove.myPid_ = -1;\n  }\n\n  ShmProcessMutex& operator=(ShmProcessMutex&& toMove) {\n    CAFFE_ENFORCE(toMove.myPid_ == this->myPid_);\n    if (&toMove != this) {\n      internalDestroy();\n      header_ = toMove.header_;\n      name_ = toMove.name_;\n      toMove.header_ = nullptr;\n      toMove.myPid_ = -1;\n    }\n    return *this;\n  }\n\n  void lock() {\n    pid_t expectedPid = 0;\n    while (not header_->owner.compare_exchange_weak(\n        expectedPid,\n        myPid_,\n        std::memory_order_relaxed,\n        std::memory_order_relaxed)) {\n      if (expectedPid == 0) {\n        continue;\n      }\n      // Someone else has the lock. We check if that process is\n      // still alive\n      if (kill(expectedPid, 0) < 0 && errno == ESRCH) {\n        // The process no longer exists. Try to \"steal\" the lock\n        continue;\n      }\n      while (true) {\n        if (static_cast<Impl*>(this)->waitForLock()) {\n          return;\n        }\n        expectedPid = header_->owner.load(std::memory_order_relaxed);\n        if (expectedPid == 0 || (kill(expectedPid, 0) < 0 && errno == ESRCH)) {\n          break;\n        }\n      }\n    }\n  }\n\n  bool try_lock() {\n    pid_t expectedPid = 0;\n    bool firstTry = true;\n    while (not header_->owner.compare_exchange_weak(\n        expectedPid,\n        myPid_,\n        std::memory_order_relaxed,\n        std::memory_order_relaxed)) {\n      if (expectedPid == 0) {\n        continue;\n      }\n      // Someone else has the lock. We check if that process is\n      // still alive\n      if (firstTry && kill(expectedPid, 0) < 0 && errno == ESRCH) {\n        firstTry = false;\n        // The process no longer exists. Try to \"steal\" the lock once\n        continue;\n      }\n      return false;\n    }\n    return true;\n  }\n\n  void unlock() noexcept {\n    header_->owner.store(0, std::memory_order_relaxed);\n    static_cast<Impl*>(this)->subUnlock();\n  }\n\n protected:\n  header_t* header_;\n  pid_t myPid_;\n  std::string name_;\n\n  ShmProcessMutexCheck& check_;\n\n private:\n  void internalDestroy() {\n    CAFFE_ENFORCE(header_ != nullptr, \"Internal error\");\n    CAFFE_ENFORCE(check_.removeLock(name_), \"Double free of lock: \", name_);\n    // Unmap the memory. If we are the last one, \"lock\" the\n    // shared memory and free it if successful\n    int oldCount = header_->countMapped.fetch_sub(1, std::memory_order_relaxed);\n    bool doUnlink = false;\n    if (oldCount == 1) {\n      // We were the last one. We attempt to lock out\n      // future processes by exchanging with something very negative\n      // This simplifies the checks when checking for lock out\n      oldCount = 0;\n      if (header_->countMapped.compare_exchange_strong(\n              oldCount,\n              INT_MIN,\n              std::memory_order_relaxed,\n              std::memory_order_relaxed)) {\n        doUnlink = true;\n      }\n    }\n    int rv = munmap(header_, sizeof(header_t));\n    CAFFE_ENFORCE(rv == 0, \"munmap failed: \", strerror(errno));\n    if (doUnlink) {\n      rv = shm_unlink(name_.c_str());\n      CAFFE_ENFORCE(rv == 0, \"shm_unlink failed: \", strerror(errno));\n    }\n  }\n};\n\ntemplate <class T>\nclass ShmTTSetMutex : public ShmProcessMutex<ShmTTSetMutex<T>> {\n public:\n  friend class ShmProcessMutex<ShmTTSetMutex<T>>;\n  explicit ShmTTSetMutex(const char* name, int timeout = kTimeout)\n      : ShmProcessMutex<ShmTTSetMutex>(name), timeout_(timeout) {}\n\n protected:\n  bool waitForLock() {\n    int delay = timeout_;\n    pid_t expectedPid = 0;\n    while (--delay > 0 &&\n           this->header_->owner.load(std::memory_order_relaxed)) {\n      // Empty loop\n      __asm__ __volatile__(\"\");\n    }\n    return this->header_->owner.compare_exchange_strong(\n        expectedPid, this->myPid_, std::memory_order_relaxed);\n  }\n\n  void subUnlock() noexcept {}\n  int timeout_;\n};\n\ntemplate <class T>\nclass ShmTicketMutex : public ShmProcessMutex<ShmTicketMutex<T>> {\n public:\n  friend class ShmProcessMutex<ShmTicketMutex<T>>;\n  explicit ShmTicketMutex(const char* name, int delay = kTicketDelay)\n      : ShmProcessMutex<ShmTicketMutex>(name), delay_(delay) {}\n\n protected:\n  bool waitForLock() {\n    pid_t expectedPid = 0;\n    int slot = this->header_->ticket.fetch_add(1, std::memory_order_relaxed);\n    for (;;) {\n      int spintime =\n          (slot - this->header_->now.load(std::memory_order_relaxed)) * delay_;\n      for (int i = 0; i < spintime; i++) {\n        // Empty loop\n        __asm__ __volatile__(\"\");\n      }\n      if (this->header_->now.load(std::memory_order_relaxed) == slot) {\n        break;\n      }\n    }\n    return this->header_->owner.compare_exchange_strong(\n        expectedPid, this->myPid_, std::memory_order_relaxed);\n  }\n\n  void subUnlock() noexcept {\n    this->header_->now.fetch_add(1, std::memory_order_relaxed);\n  }\n\n  int delay_;\n};\n\ntemplate <class T>\nstruct shm_traits<ShmTTSetMutex<T>> {\n  using header_t = T;\n};\n\ntemplate <class T>\nstruct shm_traits<ShmTicketMutex<T>> {\n  using header_t = T;\n};\n\nusing TicketStruct = struct : ShmBaseHeader {\n  std::atomic<unsigned> ticket;\n  std::atomic<unsigned> now;\n};\n\ntemplate class ShmTicketMutex<TicketStruct>;\ntemplate class ShmTTSetMutex<ShmBaseHeader>;\n\nusing ShmTicketMutex_t = ShmTicketMutex<TicketStruct>;\nusing ShmTTSetMutex_t = ShmTTSetMutex<ShmBaseHeader>;\n"
  },
  {
    "path": "caffe2/contrib/tensorboard/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/contrib/tensorboard/tensorboard.md",
    "content": "# Using TensorBoard in ifbpy #\n\n## Simple Example ##\n\n```lang=py\n\nimport caffe2.contrib.tensorboard.tensorboard as tb\nimport caffe2.contrib.tensorboard.tensorboard_exporter as tb_exporter\nfrom caffe2.python import brew, core, model_helper\n\nmodel = model_helper.ModelHelper(name=\"overfeat\")\ndata, label = brew.image_input(\n    model, [\"db\"], [\"data\", \"label\"], is_test=0\n)\nwith core.NameScope(\"conv1\"):\n    conv1 = brew.conv(model, data, \"conv1\", 3, 96, 11, stride=4)\n    relu1 = brew.relu(model, conv1, conv1)\n    pool1 = brew.max_pool(model, relu1, \"pool1\", kernel=2, stride=2)\nwith core.NameScope(\"conv2\"):\n    conv2 = brew.conv(model, pool1, \"conv2\", 96, 256, 5)\n    relu2 = brew.relu(model, conv2, conv2)\n    pool2 = brew.max_pool(model, relu2, \"pool2\", kernel=2, stride=2)\nwith core.NameScope(\"conv3\"):\n    conv3 = brew.conv(model, pool2, \"conv3\", 256, 512, 3, pad=1)\n    relu3 = brew.relu(model, conv3, conv3)\nwith core.NameScope(\"conv4\"):\n    conv4 = brew.conv(model, relu3, \"conv4\", 512, 1024, 3, pad=1)\n    relu4 = brew.relu(model, conv4, conv4)\nwith core.NameScope(\"conv5\"):\n    conv5 = brew.conv(model, relu4, \"conv5\", 1024, 1024, 3, pad=1)\n    relu5 = brew.relu(model, conv5, conv5)\n    pool5 = brew.max_pool(model, relu5, \"pool5\", kernel=2, stride=2)\nwith core.NameScope(\"fc6\"):\n    fc6 = brew.fc(model, pool5, \"fc6\", 1024*6*6, 3072)\n    relu6 = brew.relu(model, fc6, \"fc6\")\nwith core.NameScope(\"fc7\"):\n    fc7 = brew.fc(model, relu6, \"fc7\", 3072, 4096)\n    relu7 = brew.relu(model, fc7, \"fc7\")\nwith core.NameScope(\"classifier\"):\n    fc8 = brew.fc(model, relu7, \"fc8\", 4096, 1000)\n    pred = brew.softmax(model, fc8, \"pred\")\n    xent = model.LabelCrossEntropy([pred, label], \"xent\")\n    loss = model.AveragedLoss(xent, \"loss\")\nmodel.net.RunAllOnGPU()\nmodel.param_init_net.RunAllOnGPU()\nmodel.AddGradientOperators([loss], skip=1)\n\ntb.Config.HEIGHT = 700\ntb.visualize_cnn(model)\n```\n"
  },
  {
    "path": "caffe2/contrib/tensorboard/tensorboard.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport click\nimport collections\nimport logging\nimport numpy as np\nimport os\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core\nimport caffe2.contrib.tensorboard.tensorboard_exporter as tb_exporter\nimport tensorflow as tf\n\n\nclass Config(object):\n    HEIGHT = 600\n    ASPECT_RATIO = 1.6\n\n\nCODE_TEMPLATE = \"\"\"\n<script>\n  function load() {{\n    document.getElementById(\"{id}\").pbtxt = {data};\n  }}\n</script>\n<link rel=\"import\"\n  href=\"https://tensorboard.appspot.com/tf-graph-basic.build.html\"\n  onload=load()\n>\n<div style=\"height:{height}px\">\n  <tf-graph-basic id=\"{id}\"></tf-graph-basic>\n</div>\n\"\"\"\n\nIFRAME_TEMPLATE = \"\"\"\n<iframe\n  seamless\n  style=\"width:{width}px;height:{height}px;border:0\"\n  srcdoc=\"{code}\">\n</iframe>\n\"\"\"\n\n\ndef _show_graph(graph_def):\n    import IPython.display\n\n    code = CODE_TEMPLATE.format(\n        data=repr(str(graph_def)),\n        id='graph' + str(np.random.rand()),\n        height=Config.HEIGHT)\n\n    iframe = IFRAME_TEMPLATE.format(\n        code=code.replace('\"', '&quot;'),\n        width=Config.HEIGHT * Config.ASPECT_RATIO,\n        height=Config.HEIGHT + 20)\n\n    IPython.display.display(IPython.display.HTML(iframe))\n\n\ndef visualize_cnn(cnn, **kwargs):\n    g = tb_exporter.cnn_to_graph_def(cnn, **kwargs)\n    _show_graph(g)\n\n\ndef visualize_net(nets, **kwargs):\n    g = tb_exporter.nets_to_graph_def(nets, **kwargs)\n    _show_graph(g)\n\n\ndef visualize_ops(ops, **kwargs):\n    g = tb_exporter.ops_to_graph_def(ops, **kwargs)\n    _show_graph(g)\n\n\n@click.group()\ndef cli():\n    pass\n\n\ndef write_events(tf_dir, events):\n    # tf.summary.FileWriter exists in the current Tensorflow release\n    # tf.train.SummaryWriter is the way in older versions\n    if hasattr(tf.summary, 'FileWriter'):\n        writer = tf.summary.FileWriter(logdir=tf_dir, max_queue=len(events))\n    else:\n        writer = tf.train.SummaryWriter(logdir=tf_dir, max_queue=len(events))\n\n    for event in events:\n        writer.add_event(event)\n    writer.flush()\n    writer.close()\n\n\ndef graph_def_to_event(step, graph_def):\n    return tf.Event(\n        wall_time=step, step=step, graph_def=graph_def.SerializeToString())\n\n\n@cli.command(\"tensorboard-graphs\")\n@click.option(\"--c2-netdef\", type=click.Path(exists=True, dir_okay=False),\n              multiple=True)\n@click.option(\"--tf-dir\", type=click.Path(exists=True))\ndef tensorboard_graphs(c2_netdef, tf_dir):\n    log = logging.getLogger(__name__)\n    log.setLevel(logging.INFO)\n\n    def parse_net_def(path):\n        import google.protobuf.text_format\n        net_def = caffe2_pb2.NetDef()\n        with open(path) as f:\n            google.protobuf.text_format.Merge(f.read(), net_def)\n        return core.Net(net_def)\n\n    graph_defs = [tb_exporter.nets_to_graph_def([parse_net_def(path)])\n                  for path in c2_netdef]\n    events = [graph_def_to_event(i, graph_def)\n              for (i, graph_def) in enumerate(graph_defs, start=1)]\n    write_events(tf_dir, events)\n    log.info(\"Wrote %s graphs to logdir %s\", len(events), tf_dir)\n\n\n@cli.command(\"tensorboard-events\")\n@click.option(\"--c2-dir\", type=click.Path(exists=True, file_okay=False),\n              help=\"Root directory of the Caffe2 run\")\n@click.option(\"--tf-dir\", type=click.Path(writable=True),\n              help=\"Output path to the logdir used by TensorBoard\")\ndef tensorboard_events(c2_dir, tf_dir):\n    np.random.seed(1701)\n    log = logging.getLogger(__name__)\n    log.setLevel(logging.INFO)\n    S = collections.namedtuple('S', ['min', 'max', 'mean', 'std'])\n\n    def parse_summary(filename):\n        try:\n            with open(filename) as f:\n                rows = [(float(el) for el in line.split()) for line in f]\n                return [S(*r) for r in rows]\n        except Exception as e:\n            log.exception(e)\n            return None\n\n    def get_named_summaries(root):\n        summaries = [\n            (fname, parse_summary(os.path.join(dirname, fname)))\n            for dirname, _, fnames in os.walk(root)\n            for fname in fnames\n        ]\n        return [(n, s) for (n, s) in summaries if s]\n\n    def inferred_histo(summary, samples=1000):\n        np.random.seed(hash(\n            summary.std + summary.mean + summary.min + summary.max))\n        samples = np.random.randn(samples) * summary.std + summary.mean\n        samples = np.clip(samples, a_min=summary.min, a_max=summary.max)\n        (hist, edges) = np.histogram(samples)\n        upper_edges = edges[1:]\n        r = tf.HistogramProto(\n            min=summary.min,\n            max=summary.max,\n            num=len(samples),\n            sum=samples.sum(),\n            sum_squares=(samples * samples).sum())\n        r.bucket_limit.extend(upper_edges)\n        r.bucket.extend(hist)\n        return r\n\n    def named_summaries_to_events(named_summaries):\n        names = [n for (n, _) in named_summaries]\n        summaries = [s for (_, s) in named_summaries]\n        summaries = list(zip(*summaries))\n\n        def event(step, values):\n            s = tf.Summary()\n            scalar = [\n                tf.Summary.Value(\n                    tag=\"{}/{}\".format(name, field),\n                    simple_value=v)\n                for name, value in zip(names, values)\n                for field, v in value._asdict().items()]\n            hist = [\n                tf.Summary.Value(\n                    tag=\"{}/inferred_normal_hist\".format(name),\n                    histo=inferred_histo(value))\n                for name, value in zip(names, values)\n            ]\n            s.value.extend(scalar + hist)\n            return tf.Event(wall_time=int(step), step=step, summary=s)\n\n        return [event(step, values)\n                for step, values in enumerate(summaries, start=1)]\n\n    named_summaries = get_named_summaries(c2_dir)\n    events = named_summaries_to_events(named_summaries)\n    write_events(tf_dir, events)\n    log.info(\"Wrote %s events to logdir %s\", len(events), tf_dir)\n\n\nif __name__ == \"__main__\":\n    cli()\n"
  },
  {
    "path": "caffe2/contrib/tensorboard/tensorboard_exporter.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom builtins import bytes\nimport copy\nimport logging\nimport os\nimport six\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core, workspace\nimport tensorflow as tf\nfrom tensorflow.core.framework import tensor_shape_pb2\nfrom tensorflow.core.framework import graph_pb2\n\n\ndef _make_unique_name(seen, name, min_version=0):\n    assert name is not None\n    i = min_version\n    x = '%s_%d' % (name, i) if i else name\n    while x in seen:\n        i += 1\n        x = '%s_%d' % (name, i)\n    seen.add(x)\n    return x\n\n\ndef _convert_to_ssa(shapes, track_blob_names, ops):\n    \"\"\"\n    Convert an operator graph to SSA (i.e. out-of-place).\n\n    I.e. blobs will be renamed so that each blob is produced only once.\n    \"\"\"\n    ir = core.IR(ops)\n    seen = set()\n    versioned = {}\n    shapes2 = {}\n    track_blob_names2 = {}\n\n    def ssa_name(name, versions):\n        assert name in versions\n        version = versions[name]\n        if (name, version) in versioned:\n            return versioned[(name, version)]\n        # Always setting name2 = `{name}_{version}` would work, but we also try\n        # to avoid a trailing `_0`, so we have to be careful not to introduce\n        # name collisions, such as (foo_1, 0) = foo_1 = (foo, 1).\n        # Note: operator names (if any) will be handled later.\n        name2 = _make_unique_name(seen, name, min_version=version)\n        versioned[(name, version)] = name2\n        # Transfer shape.\n        if name in shapes:\n            shapes2[name2] = shapes[name]\n        if track_blob_names and name in track_blob_names:\n            track_blob_names2[name2] = track_blob_names[name]\n        return name2\n\n    for (op, ssa) in zip(ops, ir.ssa):\n        assert op is ssa.op\n        inputs = list(op.input)\n        outputs = list(op.output)\n        del op.input[:]\n        del op.output[:]\n        op.input.extend(ssa_name(name, ssa.in_versions) for name in inputs)\n        op.output.extend(ssa_name(name, ssa.out_versions) for name in outputs)\n\n    shapes.clear()\n    shapes.update(shapes2)\n    if track_blob_names:\n        track_blob_names.clear()\n        track_blob_names.update(track_blob_names2)\n\n\ndef _get_blob_names(ops):\n    names = set()\n    for op in ops:\n        names.update(op.input)\n        names.update(op.output)\n    return {name: name for name in names}\n\n\ndef _remap_keys(m, f):\n    m2 = {f(key): value for key, value in six.iteritems(m)}\n    m.clear()\n    m.update(m2)\n\n\ndef _rename_all(shapes, track_blob_names, ops, f):\n    seen = set()\n    renamed = {}\n\n    def g(name):\n        \"\"\" Collision-free version of f.\n        \"\"\"\n        if name is None:\n            return None\n        if name in renamed:\n            return renamed[name]\n        name2 = _make_unique_name(seen, f(name))\n        renamed[name] = name2\n        return name2\n\n    for op in ops:\n        inputs = list(op.input)\n        outputs = list(op.output)\n        del op.input[:]\n        del op.output[:]\n        op.input.extend(g(name) for name in inputs)\n        op.output.extend(g(name) for name in outputs)\n\n    _remap_keys(shapes, g)\n    if track_blob_names:\n        _remap_keys(track_blob_names, g)\n    # Rename all operator names (if any) independently so that the\n    # unique-fication happens only once in _fill_missing_operator_names().\n    seen.clear()\n    renamed.clear()\n    for op in ops:\n        op.name = g(op.name)\n\n\ndef _add_gradient_scope(shapes, track_blob_names, ops):\n    \"\"\"\n    For all operators or blobs with name containing \"_grad\", add a\n    \"GRADIENTS/\" scope.\n\n    Note: breaks graph execution since the blob -> gradient mapping is\n    hardcoded.\n    \"\"\"\n    def f(name):\n        if '_grad' in name:\n            return 'GRADIENTS/{}'.format(name)\n        else:\n            return name\n    _rename_all(shapes, track_blob_names, ops, f)\n\n\ndef _replace_colons(shapes, track_blob_names, ops, repl):\n    \"\"\"\n    `:i` has a special meaning in Tensorflow.\n    \"\"\"\n    def f(name):\n        return name.replace(':', repl)\n    _rename_all(shapes, track_blob_names, ops, f)\n\n\ndef _fill_missing_operator_names(ops):\n    ''' Give missing operators a name.\n\n    We expect C2 operators to be generally unnamed. This gives them a scope\n    (inferred from their outputs) and a name after their type. Duplicates will\n    be postfixed by an index.\n    '''\n    seen = set()\n    for op in ops:\n        # Make sure operator names don't collide with blobs.\n        seen.update(op.input)\n        seen.update(op.output)\n    for op in ops:\n        if op.name:\n            name = op.name\n        elif op.output or op.input:\n            l = [os.path.dirname(name) for name in op.output or op.input]\n            scope = os.path.commonprefix(l)\n            name = os.path.join(scope, op.type)\n        else:\n            name = op.type\n        assert(name)\n        op.name = _make_unique_name(seen, name)\n\n\ndef _tf_device(device_option):\n    if not device_option.HasField(\"device_type\"):\n        return \"\"\n    if device_option.device_type == caffe2_pb2.CPU:\n        return \"/cpu:*\"\n    if device_option.device_type == caffe2_pb2.CUDA:\n        return \"/gpu:{}\".format(device_option.cuda_gpu_id)\n    raise Exception(\"Unhandled device\", device_option)\n\n\ndef _add_tf_shape(m, ints):\n    sh = tensor_shape_pb2.TensorShapeProto()\n    for i in ints:\n        dim = tensor_shape_pb2.TensorShapeProto.Dim()\n        dim.size = i\n        sh.dim.extend([dim])\n    m['_output_shapes'].list.shape.extend([sh])\n\n\ndef _set_tf_attr(m, arg):\n    k = arg.name\n    if k == 'shape' and arg.ints:\n        _add_tf_shape(m, arg.ints)\n        return\n    if arg.HasField(\"f\"):\n        m[k].f = arg.f\n        return\n    if arg.HasField(\"i\"):\n        m[k].i = arg.i\n        return\n    if arg.HasField(\"s\"):\n        m[k].s = (\n            arg.s if isinstance(arg.s, bytes) else str(arg.s).encode('utf-8')\n        )\n        return\n    if arg.floats:\n        m[k].list.f.extend(arg.floats)\n        return\n    if arg.ints:\n        m[k].list.i.extend(arg.ints)\n        return\n    if arg.strings:\n        m[k].list.s.extend(\n            s if isinstance(s, bytes) else str(s).encode('utf-8')\n            for s in arg.strings\n        )\n        return\n    # The value is an empty list.\n    m[k].list.s.extend([])\n\n\ndef _operator_to_node(shapes, op):\n    assert op.name, op\n    # Check for existance of __version__ for backwards compatibility\n    n = tf.NodeDef() if hasattr(tf, '__version__') else graph_pb2.NodeDef()\n    n.name = op.name\n    n.input.extend(op.input)\n    n.op = op.type\n    n.device = _tf_device(op.device_option)\n    if shapes:\n        # Add shapes in order.\n        for output in op.output:\n            if output not in shapes:\n                break\n            _add_tf_shape(n.attr, shapes[output])\n    for arg in op.arg:\n        _set_tf_attr(n.attr, arg)\n    return n\n\n\ndef _blob_to_node(producing_ops, shapes, name):\n    assert name\n    # Check for existance of __version__ for backwards compatibility\n    n = tf.NodeDef() if hasattr(tf, '__version__') else graph_pb2.NodeDef()\n    n.name = name\n    inputs = producing_ops.get(name, [])\n    if inputs:\n        n.op = 'Blob'\n    else:\n        n.op = 'Placeholder'\n    n.input.extend('%s:%d' % (op.name, i) for op, i in inputs)\n    if inputs:\n        device = inputs[0][0].device_option\n        if (all(input[0].device_option == device for input in inputs)):\n            n.device = _tf_device(device)\n    if shapes and name in shapes:\n        _add_tf_shape(n.attr, shapes[name])\n    return n\n\n\ndef _operators_to_graph_def(\n    shapes,\n    ops,\n    replace_colons='$',\n    with_ssa=True,\n    with_gradient_scope=True,\n    track_blob_names=None,  # pass an empty array to track blob names\n):\n    if track_blob_names is not None:\n        track_blob_names.clear()\n        track_blob_names.update(_get_blob_names(ops))\n    if replace_colons:\n        _replace_colons(shapes, track_blob_names, ops, replace_colons)\n    if with_ssa:\n        _convert_to_ssa(shapes, track_blob_names, ops)\n    if with_gradient_scope:\n        _add_gradient_scope(shapes, track_blob_names, ops)\n    _fill_missing_operator_names(ops)\n    # Check for existance of __version__ for backwards compatibility\n    g = tf.GraphDef() if hasattr(tf, '__version__') else graph_pb2.GraphDef()\n    producing_ops = {}\n    blobs = set()\n    for op in ops:\n        g.node.extend([_operator_to_node(shapes, op)])\n        for input_blob in op.input:\n            blobs.add(input_blob)\n        for i, output_blob in enumerate(op.output):\n            blobs.add(output_blob)\n            producing_ops.setdefault(output_blob, []).append((op, i))\n    for blob in blobs:\n        g.node.extend([_blob_to_node(producing_ops, shapes, blob)])\n    return g\n\n\ndef _propagate_device_option(net):\n    if not net.HasField(\"device_option\"):\n        return\n    for op in net.op:\n        if not op.HasField(\"device_option\"):\n            op.device_option.CopyFrom(net.device_option)\n\n\ndef _try_get_shapes(nets):\n    try:\n        # Note: this will inspect the workspace for better or worse.\n        shapes, _ = workspace.InferShapesAndTypes(nets)\n        return shapes\n    except Exception as e:\n        logging.warning('Failed to compute shapes: %s', e)\n        return {}\n\n\ndef nets_to_graph_def(nets, shapes=None, **kwargs):\n    if shapes is None:\n        shapes = _try_get_shapes(nets)\n    nets = [copy.deepcopy(net.Proto()) for net in nets]\n    shapes = copy.deepcopy(shapes)\n    for net in nets:\n        _propagate_device_option(net)\n    return _operators_to_graph_def(\n        shapes,\n        [op for net in nets for op in net.op],\n        **kwargs\n    )\n\n\ndef cnn_to_graph_def(cnn, **kwargs):\n    return nets_to_graph_def([cnn.param_init_net, cnn.net], **kwargs)\n\n\ndef ops_to_graph_def(ops, shapes=None, **kwargs):\n    ops = copy.deepcopy(ops)\n    shapes = copy.deepcopy(shapes or {})\n    return _operators_to_graph_def(shapes, ops, **kwargs)\n"
  },
  {
    "path": "caffe2/contrib/tensorboard/tensorboard_exporter_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\n\nfrom caffe2.proto import caffe2_pb2\nimport caffe2.python.cnn as cnn\nimport caffe2.python.core as core\nimport caffe2.contrib.tensorboard.tensorboard_exporter as tb\n\nEXPECTED = \"\"\"\nnode {\n  name: \"conv1/XavierFill\"\n  op: \"XavierFill\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"_output_shapes\"\n    value {\n      list {\n        shape {\n          dim {\n            size: 96\n          }\n          dim {\n            size: 3\n          }\n          dim {\n            size: 11\n          }\n          dim {\n            size: 11\n          }\n        }\n      }\n    }\n  }\n}\nnode {\n  name: \"conv1/ConstantFill\"\n  op: \"ConstantFill\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"_output_shapes\"\n    value {\n      list {\n        shape {\n          dim {\n            size: 96\n          }\n        }\n      }\n    }\n  }\n}\nnode {\n  name: \"classifier/XavierFill\"\n  op: \"XavierFill\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"_output_shapes\"\n    value {\n      list {\n        shape {\n          dim {\n            size: 1000\n          }\n          dim {\n            size: 4096\n          }\n        }\n      }\n    }\n  }\n}\nnode {\n  name: \"classifier/ConstantFill\"\n  op: \"ConstantFill\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"_output_shapes\"\n    value {\n      list {\n        shape {\n          dim {\n            size: 1000\n          }\n        }\n      }\n    }\n  }\n}\nnode {\n  name: \"ImageInput\"\n  op: \"ImageInput\"\n  input: \"db\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"cudnn_exhaustive_search\"\n    value {\n      i: 0\n    }\n  }\n  attr {\n    key: \"is_test\"\n    value {\n      i: 0\n    }\n  }\n  attr {\n    key: \"use_cudnn\"\n    value {\n      i: 1\n    }\n  }\n}\nnode {\n  name: \"NHWC2NCHW\"\n  op: \"NHWC2NCHW\"\n  input: \"data_nhwc\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"conv1/Conv\"\n  op: \"Conv\"\n  input: \"data\"\n  input: \"conv1/conv1_w\"\n  input: \"conv1/conv1_b\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"exhaustive_search\"\n    value {\n      i: 0\n    }\n  }\n  attr {\n    key: \"kernel\"\n    value {\n      i: 11\n    }\n  }\n  attr {\n    key: \"order\"\n    value {\n      s: \"NCHW\"\n    }\n  }\n  attr {\n    key: \"stride\"\n    value {\n      i: 4\n    }\n  }\n}\nnode {\n  name: \"conv1/Relu\"\n  op: \"Relu\"\n  input: \"conv1/conv1\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"cudnn_exhaustive_search\"\n    value {\n      i: 0\n    }\n  }\n  attr {\n    key: \"order\"\n    value {\n      s: \"NCHW\"\n    }\n  }\n}\nnode {\n  name: \"conv1/MaxPool\"\n  op: \"MaxPool\"\n  input: \"conv1/conv1_1\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"cudnn_exhaustive_search\"\n    value {\n      i: 0\n    }\n  }\n  attr {\n    key: \"kernel\"\n    value {\n      i: 2\n    }\n  }\n  attr {\n    key: \"order\"\n    value {\n      s: \"NCHW\"\n    }\n  }\n  attr {\n    key: \"stride\"\n    value {\n      i: 2\n    }\n  }\n}\nnode {\n  name: \"classifier/FC\"\n  op: \"FC\"\n  input: \"conv1/pool1\"\n  input: \"classifier/fc_w\"\n  input: \"classifier/fc_b\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"cudnn_exhaustive_search\"\n    value {\n      i: 0\n    }\n  }\n  attr {\n    key: \"order\"\n    value {\n      s: \"NCHW\"\n    }\n  }\n  attr {\n    key: \"use_cudnn\"\n    value {\n      i: 1\n    }\n  }\n}\nnode {\n  name: \"classifier/Softmax\"\n  op: \"Softmax\"\n  input: \"classifier/fc\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"cudnn_exhaustive_search\"\n    value {\n      i: 0\n    }\n  }\n  attr {\n    key: \"order\"\n    value {\n      s: \"NCHW\"\n    }\n  }\n}\nnode {\n  name: \"classifier/LabelCrossEntropy\"\n  op: \"LabelCrossEntropy\"\n  input: \"classifier/pred\"\n  input: \"label\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"classifier/AveragedLoss\"\n  op: \"AveragedLoss\"\n  input: \"classifier/xent\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/classifier/ConstantFill\"\n  op: \"ConstantFill\"\n  input: \"classifier/loss\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"value\"\n    value {\n      f: 1.0\n    }\n  }\n}\nnode {\n  name: \"GRADIENTS/classifier/AveragedLossGradient\"\n  op: \"AveragedLossGradient\"\n  input: \"classifier/xent\"\n  input: \"GRADIENTS/classifier/loss_autogen_grad\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/classifier/LabelCrossEntropyGradient\"\n  op: \"LabelCrossEntropyGradient\"\n  input: \"classifier/pred\"\n  input: \"label\"\n  input: \"GRADIENTS/classifier/xent_grad\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/classifier/SoftmaxGradient\"\n  op: \"SoftmaxGradient\"\n  input: \"classifier/pred\"\n  input: \"GRADIENTS/classifier/pred_grad\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"cudnn_exhaustive_search\"\n    value {\n      i: 0\n    }\n  }\n  attr {\n    key: \"order\"\n    value {\n      s: \"NCHW\"\n    }\n  }\n}\nnode {\n  name: \"GRADIENTS/c/FCGradient\"\n  op: \"FCGradient\"\n  input: \"conv1/pool1\"\n  input: \"classifier/fc_w\"\n  input: \"GRADIENTS/classifier/fc_grad\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"cudnn_exhaustive_search\"\n    value {\n      i: 0\n    }\n  }\n  attr {\n    key: \"order\"\n    value {\n      s: \"NCHW\"\n    }\n  }\n  attr {\n    key: \"use_cudnn\"\n    value {\n      i: 1\n    }\n  }\n}\nnode {\n  name: \"GRADIENTS/conv1/MaxPoolGradient\"\n  op: \"MaxPoolGradient\"\n  input: \"conv1/conv1_1\"\n  input: \"conv1/pool1\"\n  input: \"GRADIENTS/conv1/pool1_grad\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"cudnn_exhaustive_search\"\n    value {\n      i: 0\n    }\n  }\n  attr {\n    key: \"kernel\"\n    value {\n      i: 2\n    }\n  }\n  attr {\n    key: \"order\"\n    value {\n      s: \"NCHW\"\n    }\n  }\n  attr {\n    key: \"stride\"\n    value {\n      i: 2\n    }\n  }\n}\nnode {\n  name: \"GRADIENTS/conv1/ReluGradient\"\n  op: \"ReluGradient\"\n  input: \"conv1/conv1_1\"\n  input: \"GRADIENTS/conv1/conv1_grad\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"cudnn_exhaustive_search\"\n    value {\n      i: 0\n    }\n  }\n  attr {\n    key: \"order\"\n    value {\n      s: \"NCHW\"\n    }\n  }\n}\nnode {\n  name: \"GRADIENTS/ConvGradient\"\n  op: \"ConvGradient\"\n  input: \"data\"\n  input: \"conv1/conv1_w\"\n  input: \"GRADIENTS/conv1/conv1_grad_1\"\n  device: \"/gpu:0\"\n  attr {\n    key: \"exhaustive_search\"\n    value {\n      i: 0\n    }\n  }\n  attr {\n    key: \"kernel\"\n    value {\n      i: 11\n    }\n  }\n  attr {\n    key: \"order\"\n    value {\n      s: \"NCHW\"\n    }\n  }\n  attr {\n    key: \"stride\"\n    value {\n      i: 4\n    }\n  }\n}\nnode {\n  name: \"GRADIENTS/NCHW2NHWC\"\n  op: \"NCHW2NHWC\"\n  input: \"GRADIENTS/data_grad\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"conv1/conv1_w\"\n  op: \"Blob\"\n  input: \"conv1/XavierFill:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"classifier/fc\"\n  op: \"Blob\"\n  input: \"classifier/FC:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"data_nhwc\"\n  op: \"Blob\"\n  input: \"ImageInput:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/conv1/conv1_b_grad\"\n  op: \"Blob\"\n  input: \"GRADIENTS/ConvGradient:1\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/classifier/pred_grad\"\n  op: \"Blob\"\n  input: \"GRADIENTS/classifier/LabelCrossEntropyGradient:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/classifier/fc_grad\"\n  op: \"Blob\"\n  input: \"GRADIENTS/classifier/SoftmaxGradient:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"conv1/conv1_b\"\n  op: \"Blob\"\n  input: \"conv1/ConstantFill:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/classifier/fc_b_grad\"\n  op: \"Blob\"\n  input: \"GRADIENTS/c/FCGradient:1\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/classifier/fc_w_grad\"\n  op: \"Blob\"\n  input: \"GRADIENTS/c/FCGradient:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"label\"\n  op: \"Blob\"\n  input: \"ImageInput:1\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/data_grad\"\n  op: \"Blob\"\n  input: \"GRADIENTS/ConvGradient:2\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"classifier/loss\"\n  op: \"Blob\"\n  input: \"classifier/AveragedLoss:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"conv1/conv1\"\n  op: \"Blob\"\n  input: \"conv1/Conv:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/conv1/conv1_grad\"\n  op: \"Blob\"\n  input: \"GRADIENTS/conv1/MaxPoolGradient:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"classifier/xent\"\n  op: \"Blob\"\n  input: \"classifier/LabelCrossEntropy:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/classifier/loss_autogen_grad\"\n  op: \"Blob\"\n  input: \"GRADIENTS/classifier/ConstantFill:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"classifier/fc_w\"\n  op: \"Blob\"\n  input: \"classifier/XavierFill:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"conv1/conv1_1\"\n  op: \"Blob\"\n  input: \"conv1/Relu:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"db\"\n  op: \"Placeholder\"\n}\nnode {\n  name: \"classifier/pred\"\n  op: \"Blob\"\n  input: \"classifier/Softmax:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"classifier/fc_b\"\n  op: \"Blob\"\n  input: \"classifier/ConstantFill:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/classifier/xent_grad\"\n  op: \"Blob\"\n  input: \"GRADIENTS/classifier/AveragedLossGradient:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"data\"\n  op: \"Blob\"\n  input: \"NHWC2NCHW:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/conv1/conv1_w_grad\"\n  op: \"Blob\"\n  input: \"GRADIENTS/ConvGradient:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/conv1/conv1_grad_1\"\n  op: \"Blob\"\n  input: \"GRADIENTS/conv1/ReluGradient:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/data_nhwc_grad\"\n  op: \"Blob\"\n  input: \"GRADIENTS/NCHW2NHWC:0\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"GRADIENTS/conv1/pool1_grad\"\n  op: \"Blob\"\n  input: \"GRADIENTS/c/FCGradient:2\"\n  device: \"/gpu:0\"\n}\nnode {\n  name: \"conv1/pool1\"\n  op: \"Blob\"\n  input: \"conv1/MaxPool:0\"\n  device: \"/gpu:0\"\n}\n\"\"\"\n\n\nclass TensorboardExporterTest(unittest.TestCase):\n    def test_that_operators_gets_non_colliding_names(self):\n        op = caffe2_pb2.OperatorDef()\n        op.type = 'foo'\n        op.input.extend(['foo'])\n        tb._fill_missing_operator_names([op])\n        self.assertEqual(op.input[0], 'foo')\n        self.assertEqual(op.name, 'foo_1')\n\n    def test_that_replacing_colons_gives_non_colliding_names(self):\n        # .. and update shapes\n        op = caffe2_pb2.OperatorDef()\n        op.name = 'foo:0'\n        op.input.extend(['foo:0', 'foo$0'])\n        shapes = {'foo:0': [1]}\n        track_blob_names = tb._get_blob_names([op])\n        tb._replace_colons(shapes, track_blob_names, [op], '$')\n        self.assertEqual(op.input[0], 'foo$0')\n        self.assertEqual(op.input[1], 'foo$0_1')\n        # Collision but blobs and op names are handled later by\n        # _fill_missing_operator_names.\n        self.assertEqual(op.name, 'foo$0')\n        self.assertEqual(len(shapes), 1)\n        self.assertEqual(shapes['foo$0'], [1])\n        self.assertEqual(len(track_blob_names), 2)\n        self.assertEqual(track_blob_names['foo$0'], 'foo:0')\n        self.assertEqual(track_blob_names['foo$0_1'], 'foo$0')\n\n    def test_that_adding_gradient_scope_does_no_fancy_renaming(self):\n        # because it cannot create collisions\n        op = caffe2_pb2.OperatorDef()\n        op.name = 'foo_grad'\n        op.input.extend(['foo_grad', 'foo_grad_1'])\n        shapes = {'foo_grad': [1]}\n        track_blob_names = tb._get_blob_names([op])\n        tb._add_gradient_scope(shapes, track_blob_names, [op])\n        self.assertEqual(op.input[0], 'GRADIENTS/foo_grad')\n        self.assertEqual(op.input[1], 'GRADIENTS/foo_grad_1')\n        self.assertEqual(op.name, 'GRADIENTS/foo_grad')\n        self.assertEqual(len(shapes), 1)\n        self.assertEqual(shapes['GRADIENTS/foo_grad'], [1])\n        self.assertEqual(len(track_blob_names), 2)\n        self.assertEqual(\n            track_blob_names['GRADIENTS/foo_grad'], 'foo_grad')\n        self.assertEqual(\n            track_blob_names['GRADIENTS/foo_grad_1'], 'foo_grad_1')\n\n    def test_that_auto_ssa_gives_non_colliding_names(self):\n        op1 = caffe2_pb2.OperatorDef()\n        op1.output.extend(['foo'])\n        op2 = caffe2_pb2.OperatorDef()\n        op2.input.extend(['foo'])\n        op2.output.extend(['foo'])\n        op2.output.extend(['foo_1'])\n        shapes = {'foo': [1], 'foo_1': [2]}\n        track_blob_names = tb._get_blob_names([op1, op2])\n        tb._convert_to_ssa(shapes, track_blob_names, [op1, op2])\n        self.assertEqual(op1.output[0], 'foo')\n        self.assertEqual(op2.input[0], 'foo')\n        self.assertEqual(op2.output[0], 'foo_1')\n        # Unfortunate name but we do not parse original `_` for now.\n        self.assertEqual(op2.output[1], 'foo_1_1')\n        self.assertEqual(len(shapes), 3)\n        self.assertEqual(shapes['foo'], [1])\n        self.assertEqual(shapes['foo_1'], [1])\n        self.assertEqual(shapes['foo_1_1'], [2])\n        self.assertEqual(len(track_blob_names), 3)\n        self.assertEqual(track_blob_names['foo'], 'foo')\n        self.assertEqual(track_blob_names['foo_1'], 'foo')\n        self.assertEqual(track_blob_names['foo_1_1'], 'foo_1')\n\n    def test_simple_cnnmodel(self):\n        model = cnn.CNNModelHelper(\"NCHW\", name=\"overfeat\")\n        data, label = model.ImageInput([\"db\"], [\"data\", \"label\"], is_test=0)\n        with core.NameScope(\"conv1\"):\n            conv1 = model.Conv(data, \"conv1\", 3, 96, 11, stride=4)\n            relu1 = model.Relu(conv1, conv1)\n            pool1 = model.MaxPool(relu1, \"pool1\", kernel=2, stride=2)\n        with core.NameScope(\"classifier\"):\n            fc = model.FC(pool1, \"fc\", 4096, 1000)\n            pred = model.Softmax(fc, \"pred\")\n            xent = model.LabelCrossEntropy([pred, label], \"xent\")\n            loss = model.AveragedLoss(xent, \"loss\")\n        model.net.RunAllOnGPU()\n        model.param_init_net.RunAllOnGPU()\n        model.AddGradientOperators([loss], skip=1)\n        track_blob_names = {}\n        graph = tb.cnn_to_graph_def(\n            model,\n            track_blob_names=track_blob_names,\n            shapes={},\n        )\n        self.assertEqual(\n            track_blob_names['GRADIENTS/conv1/conv1_b_grad'],\n            'conv1/conv1_b_grad',\n        )\n        self.maxDiff = None\n        # We can't guarantee the order in which they appear, so we sort\n        # both before we compare them\n        sep = \"node {\"\n        expected = \"\\n\".join(sorted(\n            sep + \"\\n  \" + part.strip()\n            for part in EXPECTED.strip().split(sep)\n            if part.strip()\n        ))\n        actual = \"\\n\".join(sorted(\n            sep + \"\\n  \" + part.strip()\n            for part in str(graph).strip().split(sep)\n            if part.strip()\n        ))\n        self.assertMultiLineEqual(actual, expected)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/contrib/tensorboard/tensorboard_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport click.testing\nimport numpy as np\nimport os\nimport tempfile\nimport unittest\n\nfrom caffe2.python import brew, core, model_helper\nimport caffe2.contrib.tensorboard.tensorboard as tb\nimport caffe2.contrib.tensorboard.tensorboard_exporter as tb_exporter\nimport tensorflow as tf\n\n\nclass TensorboardTest(unittest.TestCase):\n\n    def test_events(self):\n        runner = click.testing.CliRunner()\n        c2_dir = tempfile.mkdtemp()\n        np.random.seed(1701)\n        n_iters = 2\n        blobs = [\"w\", \"b\"]\n        data = np.random.randn(len(blobs), n_iters, 10)\n        for i, blob in enumerate(blobs):\n            with open(os.path.join(c2_dir, blob), \"w\") as f:\n                for row in data[i]:\n                    stats = [row.min(), row.max(), row.mean(), row.std()]\n                    f.write(\" \".join(str(s) for s in stats) + \"\\n\")\n\n        # Test error handling path\n        with open(os.path.join(c2_dir, \"not-a-summary\"), \"w\") as f:\n            f.write(\"not-a-summary\")\n\n        tf_dir = tempfile.mkdtemp()\n        result = runner.invoke(\n            tb.cli,\n            [\"tensorboard-events\", \"--c2-dir\", c2_dir, \"--tf-dir\", tf_dir])\n        self.assertEqual(result.exit_code, 0)\n        entries = list(os.walk(tf_dir))\n        self.assertEqual(len(entries), 1)\n        ((d, _, (fname,)),) = entries\n        self.assertEqual(tf_dir, d)\n        events = list(tf.train.summary_iterator(os.path.join(tf_dir, fname)))\n        self.assertEqual(len(events), n_iters + 1)\n        events = events[1:]\n        self.maxDiff = None\n        self.assertEqual(len(events), 2)\n\n    def test_tensorboard_graphs(self):\n        model = model_helper.ModelHelper(name=\"overfeat\")\n        data, label = brew.image_input(\n            model, [\"db\"], [\"data\", \"label\"], is_test=0\n        )\n        with core.NameScope(\"conv1\"):\n            conv1 = brew.conv(model, data, \"conv1\", 3, 96, 11, stride=4)\n            relu1 = brew.relu(model, conv1, conv1)\n            pool1 = brew.max_pool(model, relu1, \"pool1\", kernel=2, stride=2)\n        with core.NameScope(\"classifier\"):\n            fc = brew.fc(model, pool1, \"fc\", 4096, 1000)\n            pred = brew.softmax(model, fc, \"pred\")\n            xent = model.LabelCrossEntropy([pred, label], \"xent\")\n            loss = model.AveragedLoss(xent, \"loss\")\n        model.AddGradientOperators([loss], skip=1)\n\n        c2_dir = tempfile.mkdtemp()\n        tf_dir = tempfile.mkdtemp()\n\n        with open(os.path.join(c2_dir, \"init\"), \"w\") as f:\n            f.write(str(model.param_init_net.Proto()))\n        with open(os.path.join(c2_dir, \"net\"), \"w\") as f:\n            f.write(str(model.net.Proto()))\n        runner = click.testing.CliRunner()\n        result = runner.invoke(\n            tb.cli,\n            [\"tensorboard-graphs\",\n             \"--c2-netdef\", os.path.join(c2_dir, \"init\"),\n             \"--c2-netdef\", os.path.join(c2_dir, \"net\"),\n             \"--tf-dir\", tf_dir])\n        self.assertEqual(result.exit_code, 0)\n        entries = list(os.walk(tf_dir))\n        self.assertEqual(len(entries), 1)\n        ((d, _, (fname,)),) = entries\n        self.assertEqual(tf_dir, d)\n        events = list(tf.train.summary_iterator(os.path.join(tf_dir, fname)))\n        self.assertEqual(len(events), 3)\n        events = events[1:]\n        nets = [model.param_init_net, model.net]\n        for i, (event, net) in enumerate(zip(events, nets), start=1):\n            self.assertEqual(event.step, i)\n            self.assertEqual(event.wall_time, i)\n            self.assertEqual(\n                event.graph_def,\n                tb_exporter.nets_to_graph_def([net]).SerializeToString())\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/contrib/torch/th_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nextern \"C\" {\n#include <THNN.h>\n}\n\nnamespace caffe2 {\n\nnamespace {\n\nusing UniqueTHFloatTensor =\n    std::unique_ptr<THFloatTensor, decltype(&THFloatTensor_free)>;\n\nUniqueTHFloatTensor aliasFromTensorCPU(TensorCPU* tensor) {\n  if (!tensor->ndim()) {\n    return UniqueTHFloatTensor(THFloatTensor_new(), THFloatTensor_free);\n  }\n\n  THLongStorage* thshape = THLongStorage_newWithSize(tensor->ndim());\n  for (int i = 0; i < tensor->ndim(); ++i) {\n    THLongStorage_set(thshape, i, tensor->dim(i));\n  }\n  THFloatStorage* storage = THFloatStorage_newWithData(\n      tensor->template mutable_data<float>(), tensor->size());\n  THFloatStorage_clearFlag(storage, TH_STORAGE_FREEMEM);\n  auto* th = THFloatTensor_newWithStorage(storage, 0, thshape, nullptr);\n  THFloatStorage_free(storage);\n  THLongStorage_free(thshape);\n  CAFFE_ENFORCE_EQ(\n      THFloatTensor_storage(th)->data, tensor->template mutable_data<float>());\n  return UniqueTHFloatTensor(th, THFloatTensor_free);\n}\n\nvoid copyToTensorCPU(UniqueTHFloatTensor th, TensorCPU* tensor) {\n  // TODO - if th and tensor point to the same data and have the same\n  // size, elide the copy!\n  th = UniqueTHFloatTensor(\n      THFloatTensor_newContiguous(th.get()), THFloatTensor_free);\n  const auto dims = std::vector<TIndex>(\n      th->size, th->size + THFloatTensor_nDimension(th.get()));\n  // Short-circuit if we never reallocated in TH\n  auto* storage = THFloatTensor_storage(th.get());\n  // Short-circuit if we never reallocated in TH\n  if (dims == tensor->dims() &&\n      storage->data == tensor->template data<float>()) {\n    THFloatStorage_clearFlag(storage, TH_STORAGE_FREEMEM);\n    return;\n  }\n  tensor->Resize(dims);\n  CPUContext ctx;\n  ctx.Copy<float, CPUContext, CPUContext>(\n      tensor->size(), storage->data, tensor->mutable_data<float>());\n}\n\n// _Everything_ below here can be autogenerated with the TBD\n// THNN/THCUNN schema. This is just a proof of concept.\n\nclass THNNELUCPUOp final : public Operator<CPUContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CPUContext);\n  using Operator<CPUContext>::Operator;\n  bool RunOnDevice() override {\n    // TODO - we can autogenerate this from a schema.\n    auto X = aliasFromTensorCPU(const_cast<TensorCPU*>(&Input(0)));\n    auto Y = aliasFromTensorCPU(Output(0));\n    THNN_FloatELU_updateOutput(\n        nullptr,\n        X.get(),\n        Y.get(),\n        GetSingleArgument<float>(\"alpha\", 1.0),\n        &Input(0) == Output(0));\n    copyToTensorCPU(std::move(Y), Output(0));\n    return true;\n  }\n};\n\nclass THNNELUCPUGradientOp final : public Operator<CPUContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CPUContext);\n  using Operator<CPUContext>::Operator;\n\n  bool RunOnDevice() override {\n    // TODO - we can autogenerate this from a schema.\n    auto X = aliasFromTensorCPU(const_cast<TensorCPU*>(&Input(0)));\n    auto Y = aliasFromTensorCPU(const_cast<TensorCPU*>(&Input(1)));\n    auto dY = aliasFromTensorCPU(const_cast<TensorCPU*>(&Input(2)));\n    auto dX = aliasFromTensorCPU(Output(0));\n    THNN_FloatELU_updateGradInput(\n        nullptr,\n        X.get(),\n        dY.get(),\n        dX.get(),\n        Y.get(),\n        GetSingleArgument<float>(\"alpha\", 1.0),\n        &Input(2) == Output(0) /* inplace */);\n    copyToTensorCPU(std::move(dX), Output(0));\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR_WITH_ENGINE(ELU, THNN, THNNELUCPUOp);\nREGISTER_CPU_OPERATOR_WITH_ENGINE(ELUGradient, THNN, THNNELUCPUGradientOp);\n\nclass GetELUGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"ELUGradient\",\n        \"\",\n        vector<string>{I(0), O(0), GO(0)},\n        vector<string>{GI(0)},\n        Def().arg());\n  }\n};\nREGISTER_GRADIENT(ELU, GetELUGradient);\n}\n}\n"
  },
  {
    "path": "caffe2/contrib/torch/th_ops_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/operator.h\"\n\n#include <THCAllocator.h>\n#include <THCStorage.h>\n#include <THCTensor.h>\n\n#include <THCUNN.h>\n\nnamespace caffe2 {\n\nnamespace {\n\nTHCState* getTHCState() {\n  // TODO don't leak the THCState. We only have as many threads as\n  // e.g. the number of AsyncDAGNet worker threads, which is small\n  // (O(numGPUs)).\n  static thread_local THCState* state = nullptr;\n  if (!state) {\n    state = new THCState();\n    THCudaInit(state);\n    CHECK_NOTNULL(state);\n  }\n  return state;\n}\n\nstruct THCudaTensorDeleter {\n  explicit THCudaTensorDeleter(THCState* state)\n      : state_(CHECK_NOTNULL(state)) {}\n  void operator()(THCudaTensor* th) {\n    THCudaTensor_free(state_, th);\n  }\n\n  THCState* state_{nullptr};\n};\n\nusing UniqueTHCudaTensor = std::unique_ptr<THCudaTensor, THCudaTensorDeleter>;\n\nTHCState* thnnState(CUDAContext* context) {\n  THCState* state = getTHCState();\n  THCStream* stream = THCState_getStream(state);\n  // TODO - swap these back after we're done before we handle\n  // deletion.\n  // TODO - handle proper destroy of existing handle\n  // (if not already caffe2 set handle)\n  stream->stream = context->cuda_stream();\n\n  // TODO - destroy the current handle\n  int device;\n  THCudaCheck(cudaGetDevice(&device));\n  int blasHandleIndex = THCState_getCurrentBlasHandleIndex(state);\n  THCState_getDeviceBlasHandle(state, device, blasHandleIndex); // to reserve\n  THCCudaResourcesPerDevice* res = &(state->resourcesPerDevice[device]);\n  res->blasHandles[blasHandleIndex - 1] = context->cublas_handle();\n  return state;\n}\n\nUniqueTHCudaTensor aliasFromTensorCUDA(\n    CUDAContext* context,\n    TensorCUDA* tensor) {\n  auto* state = thnnState(context);\n  if (!tensor->ndim()) {\n    return UniqueTHCudaTensor(\n        THCudaTensor_new(state), THCudaTensorDeleter(state));\n  }\n  THLongStorage* thshape = THLongStorage_newWithSize(tensor->ndim());\n  for (int i = 0; i < tensor->ndim(); ++i) {\n    THLongStorage_set(thshape, i, tensor->dim(i));\n  }\n  THCudaStorage* storage = THCudaStorage_newWithData(\n      state, tensor->mutable_data<float>(), tensor->size());\n  THCudaStorage_clearFlag(state, storage, TH_STORAGE_FREEMEM);\n  auto* th =\n      THCudaTensor_newWithStorage(state, storage, 0, thshape, nullptr);\n  THCudaStorage_free(state, storage);\n  THLongStorage_free(thshape);\n  CAFFE_ENFORCE_EQ(\n      THCudaTensor_storage(state, th)->data,\n      tensor->mutable_data<float>());\n  return UniqueTHCudaTensor(th, THCudaTensorDeleter(state));\n}\n\nvoid copyToTensorCUDA(\n    CUDAContext* context,\n    UniqueTHCudaTensor th,\n    TensorCUDA* tensor) {\n  auto* state = thnnState(context);\n  // As contiguous\n  th = UniqueTHCudaTensor(\n      THCudaTensor_newContiguous(state, th.get()),\n      THCudaTensorDeleter(state));\n  const auto dims = std::vector<TIndex>(\n      th->size, th->size + THCudaTensor_nDimension(state, th.get()));\n  auto* storage = THCudaTensor_storage(state, th.get());\n  // Short-circuit if we never reallocated in TH\n  if (dims == tensor->dims() && storage->data == tensor->data<float>()) {\n    THCudaStorage_clearFlag(state, storage, TH_STORAGE_FREEMEM);\n    return;\n  }\n\n  tensor->Resize(dims);\n  context->Copy<float, CUDAContext, CUDAContext>(\n      tensor->size(), storage->data, tensor->mutable_data<float>());\n}\n\n// _Everything_ below here can be autogenerated with the TBD\n// THNN/THCUNN schema. This is just a proof of concept.\n\nclass THNNELUCUDAOp final : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  using Operator<CUDAContext>::Operator;\n\n  bool RunOnDevice() override {\n    // TODO - we can autogenerate this from a schema.\n    auto* state = thnnState(&context_);\n    auto X = aliasFromTensorCUDA(&context_, const_cast<TensorCUDA*>(&Input(0)));\n    auto Y = aliasFromTensorCUDA(&context_, Output(0));\n    THNN_CudaELU_updateOutput(\n        state,\n        X.get(),\n        Y.get(),\n        GetSingleArgument<float>(\"alpha\", 1.0),\n        &Input(0) == Output(0));\n    copyToTensorCUDA(&context_, std::move(Y), Output(0));\n    return true;\n  }\n};\n\nclass THNNELUCUDAGradientOp final : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  using Operator<CUDAContext>::Operator;\n\n  bool RunOnDevice() override {\n    // TODO - we can autogenerate this from a schema.\n    auto* state = thnnState(&context_);\n    auto X = aliasFromTensorCUDA(&context_, const_cast<TensorCUDA*>(&Input(0)));\n    auto Y = aliasFromTensorCUDA(&context_, const_cast<TensorCUDA*>(&Input(1)));\n    auto dY =\n        aliasFromTensorCUDA(&context_, const_cast<TensorCUDA*>(&Input(2)));\n    auto dX = aliasFromTensorCUDA(&context_, Output(0));\n    THNN_CudaELU_updateGradInput(\n        state,\n        X.get(),\n        dY.get(),\n        dX.get(),\n        Y.get(),\n        GetSingleArgument<float>(\"alpha\", 1.0),\n        &Input(2) == Output(0) /* inplace */);\n    copyToTensorCUDA(&context_, std::move(dX), Output(0));\n    return true;\n  }\n};\n\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(ELU, THNN, THNNELUCUDAOp);\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(ELUGradient, THNN, THNNELUCUDAGradientOp);\n}\n}\n"
  },
  {
    "path": "caffe2/contrib/torch/th_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, dyndep\nimport caffe2.python.hypothesis_test_util as hu\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport numpy as np\n\ndyndep.InitOpsLibrary('@/caffe2/caffe2/contrib/torch:th_ops')\n\n\ntry:\n    dyndep.InitOpsLibrary('@/caffe2/caffe2/contrib/torch:th_ops_gpu')\n    HAS_GPU = True\nexcept Exception as e:\n    print(\"Exception loading Torch GPU library: \", e)\n    # GPU import can fail, as Torch is not using cuda-lazy\n    HAS_GPU = False\n    pass\n\n\nclass THOpsTest(hu.HypothesisTestCase):\n    @given(X=hu.tensor(),\n           alpha=st.floats(min_value=0.1, max_value=2.0),\n           in_place=st.booleans(),\n           **(hu.gcs if HAS_GPU else hu.gcs_cpu_only))\n    def test_elu(self, X, alpha, in_place, gc, dc):\n        op = core.CreateOperator(\n            \"ELU\",\n            [\"X\"],\n            [\"X\" if in_place else \"Y\"],\n            engine=\"THNN\",\n            alpha=alpha)\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n        def elu(X):\n            Y = np.copy(X)\n            Y[Y <= 0] = (np.exp(Y[Y <= 0]) - 1) * alpha\n            return (Y,)\n\n        self.assertReferenceChecks(gc, op, [X], elu)\n        # Avoid the nonlinearity at 0 for gradient checker.\n        X[X == 0] += 0.2\n        X[np.abs(X) < 0.2] += np.sign(X[np.abs(X) < 0.2])\n        assert len(X[np.abs(X) < 0.2]) == 0\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n"
  },
  {
    "path": "caffe2/contrib/torch/torch_op.cpp",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"torch_op.h\"\n\nnamespace caffe2 {\n\nnamespace torch {\n\nconst char* TyTraits<CPUContext>::moduleTy = \"float\";\nconst char* TyTraits<CPUContext>::tensorTy = \"torch.FloatTensor\";\nconst char* TyTraits<CPUContext>::prelude = R\"(\n        require 'torch'\n        require 'nn'\n)\";\n}\n\nstruct GetTorchGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  std::vector<OperatorDef> GetGradientDefs() override {\n    std::vector<std::string> gradientInputs;\n    for (int i = 0; i < def_.input_size(); ++i) {\n      gradientInputs.push_back(I(i));\n    }\n    for (int i = 0; i < def_.output_size(); ++i) {\n      gradientInputs.push_back(O(i));\n    }\n    for (int i = 0; i < def_.output_size(); ++i) {\n      gradientInputs.push_back(GO(i));\n    }\n    std::vector<std::string> gradientOutputs;\n    for (int i = 0; i < def_.input_size(); ++i) {\n      gradientOutputs.push_back(GI(i));\n    }\n\n    return SingleGradientDef(\n        \"TorchGradient\", \"\", gradientInputs, gradientOutputs);\n  }\n};\n\n\nREGISTER_CPU_OPERATOR(Torch, TorchOp<CPUContext>);\nREGISTER_CPU_OPERATOR(TorchInit, TorchInitOp<CPUContext>);\nREGISTER_CPU_OPERATOR(TorchGradient, TorchGradientOp<CPUContext>);\nREGISTER_GRADIENT(Torch, GetTorchGradient);\nOPERATOR_SCHEMA(Torch).AllowInplace([](int, int) { return true; });\nOPERATOR_SCHEMA(TorchInit);\nOPERATOR_SCHEMA(TorchGradient).AllowInplace([](int, int) { return true; });\n}\n"
  },
  {
    "path": "caffe2/contrib/torch/torch_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n#include <unordered_map>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nextern \"C\" {\n#include <TH/THStorage.h>\n#include <TH/THTensor.h>\n#include <lua.h>\n#include <luaT.h>\n#include <lualib.h>\n}\n\nnamespace caffe2 {\n\nnamespace torch {\n\ntemplate <typename Context>\nstruct TyTraits {};\n\ntemplate <>\nstruct TyTraits<CPUContext> {\n  static const char* moduleTy;\n  static const char* prelude;\n  static const char* tensorTy;\n  using Tensor = THFloatTensor;\n};\n\ntemplate <typename Context>\nclass Torch final {\n public:\n  using Traits = TyTraits<Context>;\n  Torch() {\n    L_ = luaL_newstate();\n    luaL_openlibs(L_);\n    luaL_loadstring(L_, Traits::prelude);\n    int err = lua_pcall(L_, 0, 0, 0);\n    CAFFE_ENFORCE_EQ(err, 0, lua_tostring(L_, -1));\n  };\n\n  ~Torch() {\n    lua_close(L_);\n  }\n\n  lua_State* L() {\n    return L_;\n  }\n\n  static const char* tensorTy(const Blob& blob) {\n    CAFFE_ENFORCE(blob.template IsType<Tensor<Context>>());\n    const auto& tc = blob.template Get<Tensor<Context>>();\n    CAFFE_ENFORCE(\n        tc.template IsType<float>() + tc.meta().name(), \", \", tc.size());\n    return Traits::tensorTy;\n  }\n\n  void setContext(Context* /*context*/) {}\n\n  void setTensor(typename Traits::Tensor* t, Blob* blob) {\n    CAFFE_ENFORCE_EQ(tensorTy(*blob), Traits::tensorTy);\n    auto* tc = blob->template GetMutable<Tensor<Context>>();\n    CAFFE_ENFORCE_EQ(THFloatTensor_nElement(t), tc->size());\n    THFloatStorage* storage = THFloatStorage_newWithData(\n        tc->template mutable_data<float>(), tc->size());\n    THFloatStorage_clearFlag(storage, TH_STORAGE_FREEMEM);\n    THFloatStorage* original = t->storage;\n    t->storage = storage;\n    THFloatStorage_free(original);\n  }\n\n  std::vector<TIndex> tensorShape(typename Traits::Tensor* t) {\n    auto* size = t->size;\n    return std::vector<TIndex>(size, size + THFloatTensor_nDimension(t));\n  }\n\n  typename Traits::Tensor* newTensorAs(const Tensor<Context>& tc) {\n    THLongStorage* thshape = THLongStorage_newWithSize(tc.ndim());\n    for (uint32_t i = 0; i < tc.ndim(); ++i) {\n      THLongStorage_set(thshape, i, tc.dim(i));\n    }\n    THFloatTensor* d = THFloatTensor_newWithSize(thshape, nullptr);\n    THLongStorage_free(thshape);\n    return d;\n  }\n\n  typename Traits::Tensor* blobToTensor(Blob* blob) {\n    CAFFE_ENFORCE_EQ(tensorTy(*blob), Traits::tensorTy);\n    auto* tc = blob->template GetMutable<Tensor<Context>>();\n\n    size_t size = tc->size();\n    THLongStorage* thshape = THLongStorage_newWithSize(tc->ndim());\n    for (int i = 0; i < tc->ndim(); ++i) {\n      THLongStorage_set(thshape, i, tc->dim(i));\n    }\n    THFloatStorage* storage =\n        THFloatStorage_newWithData(tc->template mutable_data<float>(), size);\n    THFloatStorage_clearFlag(storage, TH_STORAGE_FREEMEM);\n    auto* th = THFloatTensor_newWithStorage(storage, 0, thshape, nullptr);\n    THFloatStorage_free(storage);\n    THLongStorage_free(thshape);\n    CAFFE_ENFORCE_EQ(\n        THFloatTensor_storage(th)->data, tc->template mutable_data<float>());\n    return th;\n  }\n\n  std::vector<typename Traits::Tensor*> pushTable(\n      const std::vector<Blob*>& blobs) {\n    if (blobs.empty()) {\n      lua_pushnil(L());\n      return {};\n    }\n\n    if (blobs.size() == 1) {\n      auto* th = blobToTensor(blobs[0]);\n      luaT_pushudata(L(), th, tensorTy(*blobs[0]));\n      return {th};\n    }\n\n    std::vector<typename Traits::Tensor*> res;\n    lua_createtable(L(), blobs.size(), 0);\n    int index = 1;\n    for (auto* blob : blobs) {\n      auto* th = blobToTensor(blob);\n      res.push_back(th);\n      luaT_pushudata(L(), th, tensorTy(*blob));\n      lua_rawseti(L(), -2, index++);\n    }\n    return res;\n  }\n\n  void verifyOutput(Blob* dst, typename Traits::Tensor* torchDst) {\n    if (!luaT_isudata(L(), -1, Traits::tensorTy)) {\n      LOG(FATAL) << \"Unsupported Torch tensor type \" << luaT_typename(L(), -1);\n    }\n\n    // Invariant: dst has the same size as src, and has the same data\n    // values as src.\n    auto* src = static_cast<typename Traits::Tensor*>(\n        luaT_toudata(L(), -1, Traits::tensorTy));\n    auto* thDst = static_cast<typename Traits::Tensor*>(torchDst);\n    auto* tcDst = dst->template GetMutable<Tensor<Context>>();\n    CAFFE_ENFORCE(src->storage->data);\n    CAFFE_ENFORCE(src->storage->size);\n    CAFFE_ENFORCE_EQ(src->storage->data, thDst->storage->data);\n    CAFFE_ENFORCE_EQ(src->storage->data, tcDst->template data<float>());\n    CAFFE_ENFORCE_EQ(src->storage->size, thDst->storage->size);\n    CAFFE_ENFORCE_EQ(src->storage->size, tcDst->size());\n  }\n\n  void verifyOutputs(\n      const std::vector<Blob*>& blobs,\n      const std::vector<typename Traits::Tensor*>& tensors) {\n    CAFFE_ENFORCE_EQ(tensors.size(), blobs.size());\n\n    if (blobs.empty()) {\n      return;\n    }\n\n    if (blobs.size() == 1) {\n      verifyOutput(blobs[0], tensors[0]);\n      return;\n    }\n\n    CAFFE_ENFORCE(lua_istable(L(), -1));\n    lua_pushnil(L());\n    for (auto i = 0; i < blobs.size(); ++i) {\n      CAFFE_ENFORCE(lua_next(L(), -2));\n      verifyOutput(blobs[i], tensors[i]);\n      lua_pop(L(), 1);\n    }\n    lua_pop(L(), 1);\n  }\n\nprivate:\n  lua_State* L_;\n};\n}\n\ntemplate <typename Context>\nclass TorchOpBase : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using OperatorBase::Outputs;\n  using OperatorBase::Inputs;\n  TorchOpBase(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    lua_State* L = state_.L();\n    CAFFE_ENFORCE_EQ(lua_gettop(L), 0);\n    const auto initString = \"return \" +\n        OperatorBase::GetSingleArgument<std::string>(\"init\", \"\") + \":\" +\n        torch::Torch<Context>::Traits::moduleTy + \"()\";\n    CAFFE_ENFORCE_EQ(luaL_loadstring(L, initString.c_str()), 0);\n    int err = lua_pcall(L, 0, 1, 0);\n    CAFFE_ENFORCE_EQ(err, 0, lua_tostring(L, -1));\n    // Get number of parameters\n    uint32_t numParams = 0;\n    lua_getfield(L, -1, \"parameters\");\n    lua_pushvalue(L, -2);\n    CAFFE_ENFORCE_EQ(lua_pcall(L, 1, LUA_MULTRET, 0), 0);\n    if (lua_gettop(L) == 1) {\n      numParams = 0;\n    } else {\n      CAFFE_ENFORCE_EQ(lua_gettop(L), 3);\n      numParams = lua_objlen(L, -2);\n      lua_pop(L, 2);\n    }\n    CAFFE_ENFORCE_EQ(\n        numParams, OperatorBase::GetSingleArgument<int>(\"num_params\", 0));\n    // TODO: free parameters?\n    self_ = luaL_ref(L, LUA_REGISTRYINDEX);\n  }\n\n  void reshapeBlobs(\n      const std::vector<Blob*>& inputBlobs,\n      const std::vector<Blob*>& paramBlobs,\n      const std::vector<Blob*>& outputBlobs) {\n    auto cacheEqual = [=]() {\n      if (cachedInputSizes_.size() != inputBlobs.size()) {\n        return false;\n      }\n\n      for (auto i = 0; i < inputBlobs.size(); ++i) {\n        const auto& current =\n            inputBlobs[i]->template Get<Tensor<Context>>().dims();\n        const auto& cached = cachedInputSizes_[i];\n        if (current != cached) {\n          return false;\n        }\n      }\n      return true;\n    };\n\n    if (cacheEqual()) {\n      return;\n    }\n    LOG(INFO) << \"Cached blobs not equal, running :updateOutput to reshape\";\n    lua_State* L = state_.L();\n    CAFFE_ENFORCE_EQ(lua_gettop(L), 0);\n    lua_rawgeti(L, LUA_REGISTRYINDEX, self_);\n    lua_getfield(L, -1, \"updateOutput\");\n    lua_pushvalue(L, -2); // self\n    if (inputBlobs.size() == 1) {\n      const auto& tc = inputBlobs[0]->template Get<Tensor<Context>>();\n      auto* inputData = state_.newTensorAs(tc);\n      luaT_pushudata(L, inputData, torch::Torch<Context>::Traits::tensorTy);\n    } else if (inputBlobs.size() > 1) {\n      lua_createtable(L, inputBlobs.size(), 0);\n      for (auto i = 0; i < inputBlobs.size(); ++i) {\n        const auto* blob = inputBlobs[i];\n        const auto& tc = blob->template Get<Tensor<Context>>();\n        auto* inputData = state_.newTensorAs(tc);\n        luaT_pushudata(L, inputData, torch::Torch<Context>::Traits::tensorTy);\n        lua_rawseti(L, -2, i + 1);\n      }\n    }\n    int err = lua_pcall(L, 2, 0, 0);\n    CAFFE_ENFORCE_EQ(err, 0, lua_tostring(L, -1));\n    if (paramBlobs.size() != 0) {\n      lua_getfield(L, -1, \"parameters\");\n      lua_pushvalue(L, -2);\n      int err2 = lua_pcall(L, 1, LUA_MULTRET, 0);\n      CAFFE_ENFORCE_EQ(err2, 0);\n      CAFFE_ENFORCE_EQ(lua_gettop(L), 3);\n      lua_pushnil(L);\n      int i = 0;\n      while (lua_next(L, -3) && i < paramBlobs.size()) {\n        CAFFE_ENFORCE(\n            luaT_isudata(L, -1, torch::Torch<Context>::Traits::tensorTy));\n        auto* param =\n            static_cast<typename torch::Torch<Context>::Traits::Tensor*>(\n                luaT_toudata(L, -1, torch::Torch<Context>::Traits::tensorTy));\n        auto paramShape = state_.tensorShape(param);\n        auto* blob = paramBlobs[i];\n        auto* tc = blob->template GetMutable<Tensor<Context>>();\n        if (tc->size() == 0) {\n          tc->Resize(paramShape);\n          tc->template mutable_data<float>();\n        } else {\n          CAFFE_ENFORCE(tc->dims() == paramShape);\n        }\n        lua_pop(L, 1);\n        i++;\n      }\n      CAFFE_ENFORCE_EQ(i, paramBlobs.size());\n      lua_pop(L, 2);\n    }\n    lua_getfield(L, -1, \"output\");\n    if (outputBlobs.size() == 0) {\n    } else if (outputBlobs.size() == 1) {\n      CAFFE_ENFORCE(\n          luaT_isudata(L, -1, torch::Torch<Context>::Traits::tensorTy));\n      auto* output =\n          static_cast<typename torch::Torch<Context>::Traits::Tensor*>(\n              luaT_toudata(L, -1, torch::Torch<Context>::Traits::tensorTy));\n      auto outputShape = state_.tensorShape(output);\n      auto* blob = outputBlobs[0];\n      auto* tc = blob->template GetMutable<Tensor<Context>>();\n      tc->Resize(outputShape);\n      tc->template mutable_data<float>();\n    } else {\n      lua_pushnil(L);\n      auto i = 0;\n      while (lua_next(L, -2) && i < outputBlobs.size()) {\n        CAFFE_ENFORCE(\n            luaT_isudata(L, -1, torch::Torch<Context>::Traits::tensorTy));\n        auto* output =\n            static_cast<typename torch::Torch<Context>::Traits::Tensor*>(\n                luaT_toudata(L, -1, torch::Torch<Context>::Traits::tensorTy));\n        auto outputShape = state_.tensorShape(output);\n        auto* blob = outputBlobs[i];\n        auto* tc = blob->template GetMutable<Tensor<Context>>();\n        if (tc->size() == 0) {\n          tc->Resize(outputShape);\n          tc->template mutable_data<float>();\n        } else {\n          CAFFE_ENFORCE(tc->dims() == outputShape);\n        }\n        ++i;\n      }\n      CAFFE_ENFORCE_EQ(i, outputBlobs.size());\n    }\n    lua_pop(L, 2);\n    CAFFE_ENFORCE_EQ(lua_gettop(L), 0);\n\n    cachedInputSizes_.clear();\n    for (const auto* blob : inputBlobs) {\n      const auto& dims = blob->template Get<Tensor<Context>>().dims();\n      cachedInputSizes_.push_back(dims);\n    }\n  }\n\n protected:\n  torch::Torch<Context> state_;\n  int self_{0};\n  std::vector<std::vector<TIndex>> cachedInputSizes_;\n};\n\ntemplate <typename Context>\nclass TorchOp : public TorchOpBase<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using OperatorBase::Outputs;\n  using OperatorBase::Inputs;\n  using TorchOpBase<Context>::state_;\n  using TorchOpBase<Context>::self_;\n\n  using TorchOpBase<Context>::TorchOpBase;\n\n  bool RunOnDevice() final {\n    const auto numInputs =\n        OperatorBase::GetSingleArgument<int>(\"num_inputs\", 1);\n    const auto numParams =\n        OperatorBase::GetSingleArgument<int>(\"num_params\", 0);\n    const auto numOutputs =\n        OperatorBase::GetSingleArgument<int>(\"num_outputs\", 1);\n    CAFFE_ENFORCE_EQ(InputSize(), numInputs + numParams);\n    CAFFE_ENFORCE_EQ(OutputSize(), numOutputs);\n\n    std::vector<Blob*> inputBlobs;\n    for (auto i = 0; i < numInputs; ++i) {\n      inputBlobs.push_back(const_cast<Blob*>(Inputs()[i]));\n    }\n    std::vector<Blob*> paramBlobs;\n    for (auto i = numInputs; i < numInputs + numParams; ++i) {\n      paramBlobs.push_back(const_cast<Blob*>(Inputs()[i]));\n    }\n    // Outputs must already be pre-sized\n    this->reshapeBlobs(inputBlobs, paramBlobs, Outputs());\n\n    lua_State* L = state_.L();\n    CAFFE_ENFORCE_EQ(lua_gettop(L), 0);\n    state_.setContext(&context_);\n\n    // Deserialize self table\n    lua_rawgeti(L, LUA_REGISTRYINDEX, self_);\n\n    auto torchOutputs = state_.pushTable(Outputs());\n    // set the output field\n    lua_setfield(L, -2, \"output\");\n    // set the parameters\n    if (numParams != 0) {\n      // get the parameters into the stack\n      lua_getfield(L, -1, \"parameters\");\n      lua_pushvalue(L, -2);\n      int err = lua_pcall(L, 1, 1, 0);\n      CAFFE_ENFORCE_EQ(err, 0);\n      // iterate the parameters table to put tblobs inside\n      lua_pushnil(L);\n      auto i = 0;\n      while (lua_next(L, -2) && i < numParams) {\n        CAFFE_ENFORCE(\n            luaT_isudata(L, -1, state_.tensorTy(*paramBlobs[i])),\n            luaT_typename(L, -1));\n        auto* udata = luaT_toudata(L, -1, state_.tensorTy(*paramBlobs[i]));\n        state_.setTensor(\n            static_cast<typename torch::Torch<Context>::Traits::Tensor*>(udata),\n            const_cast<Blob*>(paramBlobs[i]));\n        i++;\n        lua_pop(L, 1);\n      }\n      CAFFE_ENFORCE_EQ(i, numParams);\n      lua_pop(L, 1); // pop the parameter table\n    }\n    // call updateOutput\n    // | self\n    lua_getfield(L, -1, \"updateOutput\");\n    // | self | updateOutput\n    lua_pushvalue(L, -2);\n    // | self | updateOutput | self\n    auto torchInputs = state_.pushTable(inputBlobs);\n    // | self | updateOutput | self | inputs\n    int err = lua_pcall(L, 2, 1, 0); // doesn't need the output\n    CAFFE_ENFORCE_EQ(err, 0, lua_tostring(L, -1));\n    state_.verifyOutputs(Outputs(), torchOutputs);\n    lua_pop(L, 2);\n    CAFFE_ENFORCE_EQ(lua_gettop(L), 0);\n    return true;\n  }\n};\n\ntemplate <typename Context>\nclass TorchInitOp : public TorchOpBase<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using OperatorBase::Outputs;\n  using OperatorBase::Inputs;\n  using TorchOpBase<Context>::TorchOpBase;\n\n  bool RunOnDevice() final {\n    const auto numInputs =\n        OperatorBase::GetSingleArgument<int>(\"num_inputs\", 1);\n    const auto numParams =\n        OperatorBase::GetSingleArgument<int>(\"num_params\", 0);\n    const auto numOutputs =\n        OperatorBase::GetSingleArgument<int>(\"num_outputs\", 1);\n    std::vector<Blob*> inputBlobs;\n    for (auto i = 0; i < numInputs; ++i) {\n      inputBlobs.push_back(const_cast<Blob*>(Inputs()[i]));\n    }\n    std::vector<Blob*> paramBlobs;\n    for (auto i = numInputs; i < numInputs + numParams; ++i) {\n      paramBlobs.push_back(const_cast<Blob*>(Inputs()[i]));\n    }\n    this->reshapeBlobs(inputBlobs, paramBlobs, Outputs());\n    return true;\n  }\n};\n\ntemplate <typename Context>\nclass TorchGradientOp : public TorchOpBase<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using OperatorBase::Outputs;\n  using OperatorBase::Inputs;\n  using TorchOpBase<Context>::state_;\n  using TorchOpBase<Context>::self_;\n  using TorchOpBase<Context>::TorchOpBase;\n\n  bool RunOnDevice() final {\n    const auto numInputs =\n        OperatorBase::GetSingleArgument<int>(\"num_inputs\", 1);\n    const auto numParams =\n        OperatorBase::GetSingleArgument<int>(\"num_params\", 0);\n    const auto numOutputs =\n        OperatorBase::GetSingleArgument<int>(\"num_outputs\", 1);\n    lua_State* L = state_.L();\n    CAFFE_ENFORCE_EQ(lua_gettop(L), 0);\n    // inputs, params, outputs, grad outputs\n    CAFFE_ENFORCE_EQ(InputSize(), numInputs + numParams + 2 * numOutputs);\n    // grad inputs, grad params\n    CAFFE_ENFORCE_EQ(OutputSize(), numInputs + numParams);\n    state_.setContext(&context_);\n\n    std::vector<Blob*> outputBlobs;\n    for (auto i = numInputs + numParams; i < numInputs + numParams + numOutputs;\n         ++i) {\n      outputBlobs.push_back(const_cast<Blob*>(Inputs()[i]));\n    }\n    std::vector<Blob*> inputBlobs;\n    for (auto i = 0; i < numInputs; ++i) {\n      inputBlobs.push_back(const_cast<Blob*>(Inputs()[i]));\n    }\n    std::vector<Blob*> gradOutputBlobs;\n    for (auto i = numInputs + numParams + numOutputs;\n         i < numInputs + numParams + numOutputs + numOutputs;\n         ++i) {\n      gradOutputBlobs.push_back(const_cast<Blob*>(Inputs()[i]));\n    }\n    std::vector<Blob*> gradInputBlobs;\n    for (auto i = 0; i < numInputs; ++i) {\n      gradInputBlobs.push_back(Outputs()[i]);\n    }\n    std::vector<Blob*> paramBlobs;\n    for (auto i = numInputs; i < numInputs + numParams; ++i) {\n      paramBlobs.push_back(const_cast<Blob*>(Inputs()[i]));\n    }\n    std::vector<Blob*> gradParamBlobs;\n    for (auto i = numInputs; i < numInputs + numParams; ++i) {\n      gradParamBlobs.push_back(Outputs()[i]);\n    }\n\n    // Ensure shapes are correct.\n    for (auto i = 0; i < OutputSize(); ++i) {\n      Output(i)->ResizeLike(Input(i));\n      Output(i)->template mutable_data<float>();\n    }\n\n    lua_rawgeti(L, LUA_REGISTRYINDEX, self_);\n    state_.pushTable(outputBlobs);\n    lua_setfield(L, -2, \"output\");\n\n    const auto& torchGradInputs = state_.pushTable(gradInputBlobs);\n    lua_setfield(L, -2, \"gradInput\");\n    if (numParams != 0) {\n      // get the parameters into the stack\n      lua_getfield(L, -1, \"parameters\");\n      lua_pushvalue(L, -2);\n      int err = lua_pcall(L, 1, LUA_MULTRET, 0);\n      CAFFE_ENFORCE_EQ(err, 0, lua_tostring(L, -1));\n      // iterate the parameters table to put tblobs inside\n      lua_pushnil(L);\n      auto i = 0;\n      while (lua_next(L, -3) && i < numParams) {\n        CAFFE_ENFORCE(luaT_isudata(L, -1, state_.tensorTy(*paramBlobs[i])));\n        auto* udata = luaT_toudata(L, -1, state_.tensorTy(*paramBlobs[i]));\n        state_.setTensor(\n            static_cast<typename torch::Torch<Context>::Traits::Tensor*>(udata),\n            const_cast<Blob*>(paramBlobs[i]));\n        i++;\n        lua_pop(L, 1);\n      }\n      CAFFE_ENFORCE_EQ(i, numParams);\n      // iterate the grad of params\n      lua_pushnil(L);\n      i = 0;\n      while (lua_next(L, -2) && i < numParams) {\n        CAFFE_ENFORCE(luaT_isudata(L, -1, state_.tensorTy(*gradParamBlobs[i])));\n        auto* udata = luaT_toudata(L, -1, state_.tensorTy(*gradParamBlobs[i]));\n        state_.setTensor(\n            static_cast<typename torch::Torch<Context>::Traits::Tensor*>(udata),\n            const_cast<Blob*>(gradParamBlobs[i]));\n        i++;\n        lua_pop(L, 1);\n      }\n      CAFFE_ENFORCE_EQ(i, numParams);\n      lua_pop(L, 2); // pop the parameters\n    }\n    lua_getfield(L, -1, \"zeroGradParameters\");\n    lua_pushvalue(L, -2);\n    CAFFE_ENFORCE_EQ(lua_pcall(L, 1, 0, 0), 0);\n    state_.pushTable(inputBlobs);\n    state_.pushTable(gradOutputBlobs);\n    // call\n    lua_getfield(L, -3, \"accGradParameters\");\n    lua_pushvalue(L, -4);\n    lua_pushvalue(L, -4);\n    lua_pushvalue(L, -4);\n    lua_pushnumber(L, 1);\n    int err = lua_pcall(L, 4, 0, 0); // doesn't need the output\n    CAFFE_ENFORCE_EQ(err, 0, lua_tostring(L, -1));\n    lua_getfield(L, -3, \"updateGradInput\");\n    lua_pushvalue(L, -4);\n    lua_pushvalue(L, -4);\n    lua_pushvalue(L, -4);\n    err = lua_pcall(L, 3, 1, 0); // doesn't need the output\n    CAFFE_ENFORCE_EQ(err, 0, lua_tostring(L, -1));\n    state_.verifyOutputs(gradInputBlobs, torchGradInputs);\n    lua_pop(L, 4);\n    CAFFE_ENFORCE_EQ(lua_gettop(L), 0);\n    return true;\n  }\n};\n}\n"
  },
  {
    "path": "caffe2/contrib/torch/torch_op_gpu.cpp",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"torch_op.h\"\n\nextern \"C\" {\n#include <THCStorage.h>\n#include <THCTensor.h>\n#include <THCStream.h>\n}\n\nnamespace caffe2 {\n\nnamespace torch {\n\ntemplate <>\nstruct TyTraits<CUDAContext> {\n  static const char* moduleTy;\n  static const char* prelude;\n  static const char* tensorTy;\n  using Tensor = THCudaTensor;\n};\n\nconst char* TyTraits<CUDAContext>::tensorTy = \"torch.CudaTensor\";\nconst char* TyTraits<CUDAContext>::moduleTy = \"cuda\";\nconst char* TyTraits<CUDAContext>::prelude = R\"(\n        require 'torch'\n        require 'nn'\n        require 'cunn'\n)\";\n\nTHCState* cudaState(Torch<CUDAContext>* t) {\n  auto* L = t->L();\n  lua_getglobal(L, \"cutorch\");\n  CAFFE_ENFORCE(!lua_isnil(L, -1));\n  lua_getfield(L, -1, \"_state\");\n  CAFFE_ENFORCE(!lua_isnil(L, -1));\n  THCState* state = reinterpret_cast<THCState*>(lua_touserdata(L, -1));\n  lua_pop(L, 2);\n  return state;\n}\n\ntemplate <>\nvoid Torch<CUDAContext>::setContext(CUDAContext* context) {\n  THCState *state = cudaState(this);\n  THCStream* stream = THCState_getStream(state);\n  THCudaCheck(cudaStreamDestroy(stream->stream));\n  stream->stream = context->cuda_stream();\n}\n\ntemplate <>\nvoid Torch<CUDAContext>::setTensor(typename Traits::Tensor* t, Blob* blob) {\n  CAFFE_ENFORCE_EQ(tensorTy(*blob), Traits::tensorTy);\n  auto* cs = cudaState(this);\n  auto* tc = blob->template GetMutable<Tensor<CUDAContext>>();\n  CAFFE_ENFORCE_EQ(THCudaTensor_nElement(cs, t), tc->size());\n  THCudaStorage* storage = THCudaStorage_newWithData(\n      cs, tc->template mutable_data<float>(), tc->size());\n  THCudaStorage_clearFlag(cs, storage, TH_STORAGE_FREEMEM);\n  THCudaStorage* original = t->storage;\n  t->storage = storage;\n  THCudaStorage_free(cs, original);\n}\n\ntemplate <>\ntypename Torch<CUDAContext>::Traits::Tensor* Torch<CUDAContext>::blobToTensor(\n    Blob* blob) {\n  CAFFE_ENFORCE_EQ(tensorTy(*blob), Traits::tensorTy);\n  auto* cs = cudaState(this);\n  auto* tc = blob->template GetMutable<Tensor<CUDAContext>>();\n\n  size_t size = tc->size();\n  THLongStorage* thshape = THLongStorage_newWithSize(tc->ndim());\n  for (int i = 0; i < tc->ndim(); ++i) {\n    THLongStorage_set(thshape, i, tc->dim(i));\n  }\n  THCudaStorage* storage =\n      THCudaStorage_newWithData(cs, tc->template mutable_data<float>(), size);\n  THCudaStorage_clearFlag(cs, storage, TH_STORAGE_FREEMEM);\n  auto* th = THCudaTensor_newWithStorage(cs, storage, 0, thshape, nullptr);\n  THCudaStorage_free(cs, storage);\n  THLongStorage_free(thshape);\n  CAFFE_ENFORCE_EQ(\n      THCudaTensor_storage(cs, th)->data, tc->template mutable_data<float>());\n  return th;\n}\n\ntemplate <>\nstd::vector<TIndex> Torch<CUDAContext>::tensorShape(\n    typename Traits::Tensor* t) {\n  auto* cs = cudaState(this);\n  auto* size = t->size;\n  return std::vector<TIndex>(size, size + THCudaTensor_nDimension(cs, t));\n}\n\ntemplate <>\ntypename Torch<CUDAContext>::Traits::Tensor* Torch<CUDAContext>::newTensorAs(\n    const Tensor<CUDAContext>& tc) {\n  auto* cs = cudaState(this);\n  THLongStorage* thshape = THLongStorage_newWithSize(tc.ndim());\n  for (uint32_t i = 0; i < tc.ndim(); ++i) {\n    THLongStorage_set(thshape, i, tc.dim(i));\n  }\n  THCudaTensor* d = THCudaTensor_newWithSize(cs, thshape, nullptr);\n  THLongStorage_free(thshape);\n  return d;\n}\n}\n\nREGISTER_CUDA_OPERATOR(Torch, TorchOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(TorchGradient, TorchGradientOp<CUDAContext>);\n}\n"
  },
  {
    "path": "caffe2/contrib/torch/torch_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, dyndep\nimport caffe2.python.hypothesis_test_util as hu\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport numpy as np\nimport os\nimport unittest\n\ntry:\n    from libfb import parutil\nexcept ImportError as e:\n    # If libfb not found, skip all tests in this file\n    raise unittest.SkipTest(str(e))\n\ncore.GlobalInit([\"python\", \"--caffe2_log_level=0\"])\n\ndyndep.InitOpsLibrary('@/caffe2/caffe2/contrib/torch:torch_ops')\n\nRUNTIME = parutil.get_runtime_path()\nif 'LUA_PATH' not in os.environ:\n    os.environ['LUA_PATH'] = \";\".join([\n        os.path.join(RUNTIME, '_lua', '?.lua'),\n        os.path.join(RUNTIME, '_lua', '?', 'init.lua'),\n    ])\n    os.environ['LUA_CPATH'] = os.path.join(RUNTIME, '_lua', '?.so')\n\n\nclass TorchOpTest(hu.HypothesisTestCase):\n    @given(n=st.integers(min_value=1, max_value=10),\n           i=st.integers(min_value=1, max_value=10),\n           h=st.integers(min_value=2, max_value=10))\n    def test_feed(self, n, i, h):\n        op = core.CreateOperator(\n            \"Torch\", [\"x\", \"W\", \"b\"], [\"y\"],\n            init=b\"nn.Linear({i}, {h})\".format(h=h, i=i),\n            num_inputs=1,\n            num_params=2,\n            num_outputs=1\n        )\n        x = np.random.randn(n, i).astype(np.float32)\n        W = np.random.randn(h, i).astype(np.float32)\n        b = np.random.randn(h).astype(np.float32)\n        self.ws.create_blob(\"x\").feed(x)\n        self.ws.create_blob(\"W\").feed(W)\n        self.ws.create_blob(\"b\").feed(b)\n        self.ws.run(op)\n        y = self.ws.blobs[\"y\"].fetch()\n        print(\"y\", y)\n        y = y.reshape((n, h))\n        np.testing.assert_allclose(y, np.dot(x, W.T) + b, atol=1e-4, rtol=1e-4)\n\n    @given(n=st.integers(min_value=1, max_value=10),\n           i=st.integers(min_value=1, max_value=10),\n           h=st.integers(min_value=2, max_value=10),\n           **hu.gcs)\n    def test_gradient(self, n, i, h, gc, dc):\n        op = core.CreateOperator(\n            \"Torch\", [\"x\", \"W\", \"b\"], [\"y\"],\n            init=b\"nn.Linear({i}, {h})\".format(h=h, i=i),\n            num_inputs=1,\n            num_params=2,\n            num_outputs=1\n        )\n        x = np.random.randn(n, i).astype(np.float32)\n        W = np.random.randn(h, i).astype(np.float32)\n        b = np.random.randn(h).astype(np.float32)\n        inputs = [x, W, b]\n        self.assertDeviceChecks(dc, op, inputs, [0])\n        for i, _ in enumerate(inputs):\n            self.assertGradientChecks(gc, op, inputs, i, [0])\n\n    @given(n=st.integers(min_value=1, max_value=10),\n           i=st.integers(min_value=1, max_value=10),\n           h=st.integers(min_value=2, max_value=10),\n           iters=st.integers(min_value=1, max_value=100))\n    def test_iterated(self, n, i, h, iters):\n        x = np.random.randn(n, i).astype(np.float32)\n        W = np.random.randn(h, i).astype(np.float32)\n        b = np.random.randn(h).astype(np.float32)\n        self.ws.create_blob(\"x\").feed(x)\n        self.ws.create_blob(\"W\").feed(W)\n        self.ws.create_blob(\"b\").feed(b)\n        net = core.Net(\"op\")\n        net.Torch(\n            [\"x\", \"W\", \"b\"], [\"y\"],\n            init=b\"nn.Linear({i}, {h})\".format(h=h, i=i),\n            num_inputs=1,\n            num_params=2,\n            num_outputs=1\n        )\n        print(net.Proto())\n        net_ = self.ws.create_net(net)\n        for i in range(iters):\n            if i % 1000 == 0:\n                print(i)\n            net_.run()\n\n        y = self.ws.blobs[\"y\"].fetch()\n        y = y.reshape((n, h))\n        np.testing.assert_allclose(y, np.dot(x, W.T) + b, atol=1e-4, rtol=1e-4)\n\n    def test_leakage_torch(self):\n        n = 1\n        i = 100\n        h = 1000\n        iters = 2000\n        x = np.random.randn(n, i).astype(np.float32)\n        W = np.random.randn(h, i).astype(np.float32)\n        b = np.random.randn(h).astype(np.float32)\n        self.ws.create_blob(\"x\").feed(x)\n        self.ws.create_blob(\"W\").feed(W)\n        self.ws.create_blob(\"b\").feed(b)\n        net = core.Net(\"op\")\n        net.Torch(\n            [\"x\", \"W\", \"b\"], [\"y\"],\n            init=b\"nn.Linear({i}, {h})\".format(h=h, i=i),\n            num_inputs=1,\n            num_params=2,\n            num_outputs=1\n        )\n        net_ = self.ws.create_net(net)\n        for i in range(iters):\n            if i % 1000 == 0:\n                print(i)\n            net_.run()\n\n        y = self.ws.blobs[\"y\"].fetch()\n        y = y.reshape((n, h))\n        np.testing.assert_allclose(y, np.dot(x, W.T) + b, atol=1e-4, rtol=1e-4)\n"
  },
  {
    "path": "caffe2/contrib/warpctc/ctc_op.cpp",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"ctc_op.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\nnamespace detail {\ntemplate <>\nctcComputeInfo workspaceInfo<CPUContext>(const CPUContext& /*context*/) {\n  ctcComputeInfo result;\n  result.loc = CTC_CPU;\n  // CpuCTC overrides OMP threads set by --caffe2_omp_num_threads on init.\n  // Default to 0 to use the configured omp_get_max_threads().\n  result.num_threads = 0;\n  return result;\n}\n}\n\nREGISTER_CPU_OPERATOR(CTC, CTCOp<float, CPUContext>);\nOPERATOR_SCHEMA(CTC).NumInputs(4).NumOutputs(2, 3);\n//    .EnforceInputOutputGradient({{0, 0}});\n\nnamespace {\nclass GetCTCGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"Copy\", \"\", vector<string>{O(0)}, vector<string>{GI(0)});\n  }\n};\n}\nREGISTER_GRADIENT(CTC, GetCTCGradient);\n}\n"
  },
  {
    "path": "caffe2/contrib/warpctc/ctc_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <ctc.h>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/core/common_cudnn.h\"\n\n#define CTC_CHECK(condition)           \\\n  do {                                 \\\n    ctcStatus_t status = condition;    \\\n    CAFFE_ENFORCE_EQ(                  \\\n        status,                        \\\n        CTC_STATUS_SUCCESS,            \\\n        \" Error at: \",                 \\\n        __FILE__,                      \\\n        \":\",                           \\\n        __LINE__,                      \\\n        \": \",                          \\\n        ::ctcGetStatusString(status)); \\\n  } while (0)\n\nnamespace caffe2 {\n\nnamespace detail {\n\ntemplate <typename Context>\nctcComputeInfo workspaceInfo(const Context& context);\n\n}\n\ntemplate <typename T, typename Context>\nclass CTCOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  CTCOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        is_test_(\n            OperatorBase::GetSingleArgument<int>(OpSchema::Arg_IsTest, 0)) {\n    CAFFE_ENFORCE(\n        (is_test_ && OutputSize() == 2) || (!is_test_ && OutputSize() == 3));\n  }\n\n  bool RunOnDevice() override {\n    // inputs\n    const auto& inputs = Input(INPUTS);\n    const auto minibatchSize = inputs.dim(1);\n    const auto alphabetSize = inputs.dim(2);\n    const auto& labels = OperatorBase::template Input<TensorCPU>(LABELS);\n    const auto& labelLengths =\n        OperatorBase::template Input<TensorCPU>(LABEL_LENGTHS);\n    const auto& inputLengths =\n        OperatorBase::template Input<TensorCPU>(INPUT_LENGTHS);\n\n    // outputs\n    Tensor<Context>* gradients = nullptr;\n    TensorCPU* costs;\n    Tensor<Context>* workspace;\n    if (!is_test_) {\n      // [grads, costs, workspace] to maintain backward compatibility\n      gradients = Output(0);\n      gradients->ResizeLike(inputs);\n      costs = OperatorBase::template Output<TensorCPU>(1);\n      costs->ResizeLike(labelLengths);\n      workspace = Output(2);\n    } else {\n      // [costs, workspace]\n      costs = OperatorBase::template Output<TensorCPU>(0);\n      costs->ResizeLike(labelLengths);\n      workspace = Output(1);\n    }\n\n    size_t workspaceSizeBytes;\n    CTC_CHECK(get_workspace_size(\n        labelLengths.template data<int>(),\n        inputLengths.template data<int>(),\n        alphabetSize,\n        minibatchSize,\n        detail::workspaceInfo(context_),\n        &workspaceSizeBytes));\n    workspace->Resize(workspaceSizeBytes);\n    CTC_CHECK(compute_ctc_loss(\n        inputs.template data<T>(),\n        gradients ? gradients->template mutable_data<T>() : nullptr,\n        labels.template data<int>(),\n        labelLengths.template data<int>(),\n        inputLengths.template data<int>(),\n        alphabetSize,\n        minibatchSize,\n        costs->template mutable_data<T>(),\n        workspace->template mutable_data<uint8_t>(),\n        detail::workspaceInfo(context_)));\n    return true;\n  }\n\nprivate:\n bool is_test_;\n\n INPUT_TAGS(INPUTS, LABELS, LABEL_LENGTHS, INPUT_LENGTHS);\n};\n}\n\n#undef CTC_CHECK\n"
  },
  {
    "path": "caffe2/contrib/warpctc/ctc_op_gpu.cpp",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/operator.h\"\n#include \"ctc_op.h\"\n\nnamespace caffe2 {\n\nnamespace detail {\ntemplate <>\nctcComputeInfo workspaceInfo<CUDAContext>(const CUDAContext& context) {\n  ctcComputeInfo result;\n  result.loc = CTC_GPU;\n  result.stream = context.cuda_stream();\n  return result;\n}\n}\n\nREGISTER_CUDA_OPERATOR(CTC, CTCOp<float, CUDAContext>);\n}\n"
  },
  {
    "path": "caffe2/contrib/warpctc/ctc_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nfrom caffe2.proto import caffe2_pb2\n\nfrom caffe2.python import core, workspace, dyndep, test_util\n\ndyndep.InitOpsLibrary('@/caffe2/caffe2/contrib/warpctc:ctc_ops')\nworkspace.GlobalInit([\"python\"])\n\n\ndef softmax(w):\n    maxes = np.amax(w, axis=-1, keepdims=True)\n    e = np.exp(w - maxes)\n    dist = e / np.sum(e, axis=-1, keepdims=True)\n    return dist\n\n\nclass CTCOpsTest(test_util.TestCase):\n    def verify_cost(self, device_option, is_test):\n        alphabet_size = 5\n        N = 1\n        T = 2\n\n        inputs = np.asarray(\n            [\n                [[0.1, 0.6, 0.1, 0.1, 0.1]],\n                [[0.1, 0.1, 0.6, 0.1, 0.1]],\n            ]\n        ).reshape(T, N, alphabet_size).astype(np.float32)\n\n        labels = np.asarray([1, 2]).astype(np.int32).reshape(T)\n        label_lengths = np.asarray([2]).astype(np.int32).reshape(N)\n        input_lengths = np.asarray([T]).astype(np.int32)\n\n        net = core.Net(\"test-net\")\n        output_blobs = [\"costs\", \"workspace\"] if is_test \\\n                else [\"inputs_grad_to_be_copied\", \"costs\", \"workspace\"]\n        net.CTC([\"inputs\", \"labels\", \"label_lengths\", \"input_lengths\"],\n                output_blobs,\n                is_test=is_test,\n                device_option=device_option)\n        if not is_test:\n            net.AddGradientOperators([\"costs\"])\n        self.ws.create_blob(\"inputs\").feed(inputs, device_option=device_option)\n        self.ws.create_blob(\"labels\").feed(labels)\n        self.ws.create_blob(\"label_lengths\").feed(label_lengths)\n        self.ws.create_blob(\"input_lengths\").feed(input_lengths)\n        self.ws.run(net)\n        probs = softmax(inputs)\n        expected = probs[0, 0, 1] * probs[1, 0, 2]\n        self.assertEqual(self.ws.blobs[\"costs\"].fetch().shape, (N,))\n        self.assertEqual(self.ws.blobs[\"costs\"].fetch().dtype, np.float32)\n        cost = self.ws.blobs[\"costs\"].fetch()[0]\n        print(cost)\n        self.assertAlmostEqual(np.exp(-cost), expected)\n        if not is_test:\n            # Make sure inputs_grad was added by AddGradientOperators and\n            # it is equal to the inputs_grad_to_be_copied blob returned by CTCop\n            assert np.array_equal(\n                self.ws.blobs[\"inputs_grad\"].fetch(),\n                self.ws.blobs[\"inputs_grad_to_be_copied\"].fetch()\n            )\n\n    def test_ctc_cost_cpu(self):\n        self.verify_cost(\n            caffe2_pb2.DeviceOption(device_type=caffe2_pb2.CPU),\n            is_test=False)\n\n    def test_ctc_cost_gpu(self):\n        self.verify_cost(\n            caffe2_pb2.DeviceOption(device_type=caffe2_pb2.CUDA,\n                                    cuda_gpu_id=0),\n            is_test=False)\n\n    def test_ctc_forward_only_cpu(self):\n        self.verify_cost(\n            caffe2_pb2.DeviceOption(device_type=caffe2_pb2.CPU),\n            is_test=True)\n\n    def test_ctc_forward_only_gpu(self):\n        self.verify_cost(\n            caffe2_pb2.DeviceOption(device_type=caffe2_pb2.CUDA,\n                                    cuda_gpu_id=0),\n            is_test=True)\n"
  },
  {
    "path": "caffe2/core/CMakeLists.txt",
    "content": "# ---[ GPU files\n# ------[ cuDNN\nfile(GLOB tmp *_cudnn.cc)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# ------[ general GPU\nfile(GLOB tmp *_gpu.cc)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# ------[ CUDA sources\nfile(GLOB tmp *.cu)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# exclude test files\nfile(GLOB tmp *_test.cc)\nexclude(Caffe2_GPU_SRCS \"${Caffe2_GPU_SRCS}\" ${tmp})\n\n# ---[ CPU files.\nfile(GLOB tmp *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp})\n# exclude test files and gpu files\nfile(GLOB tmp *_test.cc)\nexclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${tmp})\nexclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${Caffe2_GPU_SRCS})\n\n# ---[ GPU test files\nfile(GLOB tmp *_gpu_test.cc)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} ${tmp})\n\n# ---[ CPU test files\nfile(GLOB tmp *_test.cc)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${tmp})\nexclude(Caffe2_CPU_TEST_SRCS \"${Caffe2_CPU_TEST_SRCS}\" ${Caffe2_GPU_TEST_SRCS})\n\n# ---[ Send the lists to the parent scope.\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/core/allocator.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/typeid.h\"\n\nCAFFE2_DEFINE_bool(\n    caffe2_report_cpu_memory_usage,\n    false,\n    \"If set, print out detailed memory usage\");\n\nCAFFE2_DEFINE_bool(\n    caffe2_cpu_allocator_do_zero_fill,\n    true,\n    \"If set, do memory zerofilling when allocating on CPU\");\n\nnamespace caffe2 {\n\nvoid NoDelete(void*) {}\n\nstatic std::unique_ptr<CPUAllocator> g_cpu_allocator(new DefaultCPUAllocator());\nCPUAllocator* GetCPUAllocator() {\n  return g_cpu_allocator.get();\n}\n\nvoid SetCPUAllocator(CPUAllocator* alloc) {\n  g_cpu_allocator.reset(alloc);\n}\n\nMemoryAllocationReporter CPUContext::reporter_;\n\nvoid MemoryAllocationReporter::New(void* ptr, size_t nbytes) {\n  std::lock_guard<std::mutex> guard(mutex_);\n  size_table_[ptr] = nbytes;\n  allocated_ += nbytes;\n  LOG(INFO) << \"Caffe2 alloc \" << nbytes << \" bytes, total alloc \" << allocated_\n            << \" bytes.\";\n}\n\nvoid MemoryAllocationReporter::Delete(void* ptr) {\n  std::lock_guard<std::mutex> guard(mutex_);\n  auto it = size_table_.find(ptr);\n  CHECK(it != size_table_.end());\n  allocated_ -= it->second;\n  LOG(INFO) << \"Caffe2 deleted \" << it->second << \" bytes, total alloc \"\n            << allocated_ << \" bytes.\";\n  size_table_.erase(it);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/allocator.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_ALLOCATOR_H_\n#define CAFFE2_CORE_ALLOCATOR_H_\n\n#include <unordered_map>\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/numa.h\"\n\nCAFFE2_DECLARE_bool(caffe2_report_cpu_memory_usage);\nCAFFE2_DECLARE_bool(caffe2_cpu_allocator_do_zero_fill);\n\nnamespace caffe2 {\n\n// Use 32-byte alignment should be enough for computation up to AVX512.\nconstexpr size_t gCaffe2Alignment = 32;\n\nusing MemoryDeleter = void (*)(void*);\n\n// A helper function that is basically doing nothing.\nvoid NoDelete(void*);\n\n// A virtual allocator class to do memory allocation and deallocation.\nstruct CPUAllocator {\n  CPUAllocator() {}\n  virtual ~CPUAllocator() noexcept {}\n  virtual std::pair<void*, MemoryDeleter> New(size_t nbytes) = 0;\n  virtual MemoryDeleter GetDeleter() = 0;\n};\n\n// A virtual struct that is used to report Caffe2's memory allocation and\n// deallocation status\nclass MemoryAllocationReporter {\n public:\n  MemoryAllocationReporter() : allocated_(0) {}\n  void New(void* ptr, size_t nbytes);\n  void Delete(void* ptr);\n\n private:\n  std::mutex mutex_;\n  std::unordered_map<void*, size_t> size_table_;\n  size_t allocated_;\n};\n\nstruct DefaultCPUAllocator final : CPUAllocator {\n  DefaultCPUAllocator() {}\n  ~DefaultCPUAllocator() override {}\n  std::pair<void*, MemoryDeleter> New(size_t nbytes) override {\n    void* data = nullptr;\n#ifdef __ANDROID__\n    data = memalign(gCaffe2Alignment, nbytes);\n#elif defined(_MSC_VER)\n    data = _aligned_malloc(nbytes, gCaffe2Alignment);\n#else\n    CAFFE_ENFORCE_EQ(posix_memalign(&data, gCaffe2Alignment, nbytes), 0);\n#endif\n    CAFFE_ENFORCE(data);\n    // move data to a thread's NUMA node\n    NUMAMove(data, nbytes, GetCurrentNUMANode());\n    if (FLAGS_caffe2_cpu_allocator_do_zero_fill) {\n      memset(data, 0, nbytes);\n    }\n    return {data, Delete};\n  }\n\n#ifdef _MSC_VER\n  static void Delete(void* data) {\n    _aligned_free(data);\n  }\n#else\n  static void Delete(void* data) {\n    free(data);\n  }\n#endif\n\n  MemoryDeleter GetDeleter() override {\n    return Delete;\n  }\n};\n\n// Get the CPU Alloctor.\nCPUAllocator* GetCPUAllocator();\n// Sets the CPU allocator to the given allocator: the caller gives away the\n// ownership of the pointer.\nvoid SetCPUAllocator(CPUAllocator* alloc);\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_ALLOCATOR_H_\n"
  },
  {
    "path": "caffe2/core/asan.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n// Detect address sanitizer as some stuff doesn't work with it\n\n#undef CAFFE2_ASAN_ENABLED\n\n// for clang\n#if defined(__has_feature)\n#if ((__has_feature(address_sanitizer)))\n#define CAFFE2_ASAN_ENABLED 1\n#endif\n#endif\n\n// for gcc\n#if defined(__SANITIZE_ADDRESS__)\n#if __SANITIZE_ADDRESS__\n#if !defined(CAFFE2_ASAN_ENABLED)\n#define CAFFE2_ASAN_ENABLED 1\n#endif\n#endif\n#endif\n\n#if !defined(CAFFE2_ASAN_ENABLED)\n#define CAFFE2_ASAN_ENABLED 0\n#endif\n\n// Define sanitization macro\n#if !CAFFE2_ASAN_ENABLED\n#define CAFFE2_NO_SANITIZE(...)\n#else\n#define CAFFE2_NO_SANITIZE(...) __attribute__((no_sanitize(__VA_ARGS__)))\n#endif\n"
  },
  {
    "path": "caffe2/core/blob.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_BLOB_H_\n#define CAFFE2_CORE_BLOB_H_\n\n#include <cstddef>\n#include <sstream>\n#include <typeinfo>\n#include <type_traits>\n#include <vector>\n\n#include \"caffe2/core/blob_serializer_base.h\"\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/typeid.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\n/**\n * @brief Blob is a general container that hosts a typed pointer.\n *\n * A Blob hosts a pointer as well as its type, and takes charge of deleting it\n * properly when the blob is deallocated or re-allocated with a new type. A blob\n * could contain anything, although the most common case is to contain a Tensor.\n */\nclass Blob {\n public:\n  typedef void (*DestroyCall)(void*);\n\n  /**\n   * Initializes an empty Blob.\n   */\n  Blob() : meta_(), pointer_(nullptr) {}\n  ~Blob() { Reset(); }\n\n  Blob(Blob&& other) noexcept\n      : meta_(std::move(other.meta_)),\n        pointer_(std::move(other.pointer_)),\n        destroy_(std::move(other.destroy_)) {\n    other.meta_ = {};\n    other.pointer_ = nullptr;\n    other.destroy_ = nullptr;\n  }\n\n  Blob& operator=(Blob&& other) noexcept {\n    meta_ = std::move(other.meta_);\n    pointer_ = std::move(other.pointer_);\n    destroy_ = std::move(other.destroy_);\n    other.meta_ = {};\n    other.pointer_ = nullptr;\n    other.destroy_ = nullptr;\n    return *this;\n  }\n\n  /**\n   * Checks if the content stored in the blob is of type T.\n   */\n  template <class T>\n  bool IsType() const { return meta_.Match<T>(); }\n\n  /**\n   * Returns the meta info of the blob.\n   */\n  inline const TypeMeta& meta() const { return meta_; }\n\n  /**\n   * Returns a printable typename of the blob.\n   */\n  inline const char* TypeName() const { return meta_.name(); }\n\n  /**\n   * @brief Gets the const reference of the stored object. The code checks if\n   * the stored object is of the desired type.\n   */\n  template <class T>\n  const T& Get() const {\n    CAFFE_ENFORCE(\n        IsType<T>(),\n        \"wrong type for the Blob instance. Blob contains \",\n        meta_.name(),\n        \" while caller expects \",\n        TypeMeta::TypeName<T>());\n    return *static_cast<const T*>(pointer_);\n  }\n\n  const void* GetRaw() const {\n    return pointer_;\n  }\n  void* GetRaw() {\n    return pointer_;\n  }\n\n  /**\n   * @brief Gets a mutable pointer to the stored object.\n   *\n   * If the current object is not of the right type, a new object is created\n   * and the old object is freed. Note that type T should have a default\n   * constructor. Otherwise, create the object yourself first, and use\n   * Reset().\n   */\n  template <class T>\n  T* GetMutable(bool* is_new_object=nullptr) {\n    if (IsType<T>()) {\n      if (is_new_object) *is_new_object = false;\n      return static_cast<T*>(pointer_);\n    } else {\n      if (is_new_object) *is_new_object = true;\n      VLOG(1) << \"Create new mutable object \" << TypeMeta::TypeName<T>();\n      return Reset<T>(new T());\n    }\n  }\n\n  /**\n   * Sets the underlying object to the allocated one. The Blob then takes over\n   * the ownership of the passed in pointer. If there is already an object in\n   * the Blob, the old object is freed.\n   *\n   * This is used when the underlying class T does not have a default ctor, or\n   * complex initializations needs to be done outside the blob.\n   */\n  template <class T>\n  T* Reset(T* allocated) {\n    if (pointer_ && destroy_) {\n      destroy_(pointer_);\n    }\n    meta_ = TypeMeta::Make<T>();\n    pointer_ = static_cast<void*>(allocated);\n    destroy_ = &Destroy<T>;\n    return allocated;\n  }\n\n  inline void*\n  Reset(void* allocated, const TypeMeta& meta, const DestroyCall& destroy) {\n    if (pointer_ && destroy_) {\n      destroy_(pointer_);\n    }\n    meta_ = meta;\n    pointer_ = static_cast<void*>(allocated);\n    destroy_ = destroy;\n    return allocated;\n  }\n\n  /**\n   * Releases the ownership, if any, this Blob has on the underlying pointer.\n   * The user is then responsible for freeing the data if needed\n   */\n  inline DestroyCall Release() {\n    DestroyCall d = destroy_;\n    destroy_ = nullptr;\n    return d;\n  }\n\n  /**\n   * Sets the underlying object to the allocated one, but does not take over\n   * the ownership of the passed in pointer. If there is already an object in\n   * the Blob, the old object is freed.\n   *\n   * Unlike Reset, this does not take over the ownership of the pointer and the\n   * caller is responsible for making sure that the lifetime of the allocated\n   * blob outlasts the lifetime of any access to this blob, until another Reset\n   * call is made or the blob is destructed.\n   */\n  template <class T>\n  typename std::remove_const<T>::type* ShareExternal(\n      typename std::remove_const<T>::type* allocated) {\n    return static_cast<T*>(ShareExternal(\n        static_cast<void*>(allocated),\n        TypeMeta::Make<typename std::remove_const<T>::type>()));\n  }\n\n  void* ShareExternal(void* allocated, const TypeMeta& meta) {\n    if (pointer_ && destroy_) {\n      destroy_(pointer_);\n    }\n    meta_ = meta;\n    pointer_ = static_cast<void*>(allocated);\n    destroy_ = nullptr;\n    return allocated;\n  }\n\n  /**\n   * Resets the Blob to an empty one.\n   */\n  inline void Reset() {\n    if (pointer_ && destroy_) {\n      destroy_(pointer_);\n    }\n    pointer_ = nullptr;\n    meta_ = TypeMeta();\n    destroy_ = nullptr;\n  }\n\n  /**\n   * Serializes the current blob, if possible. Note that this serialization uses\n   * the registration mechanism and one has to implement specific serialization\n   * approaches for specific classes. Acceptor should take care of writing data\n   * to the actual storage.\n   */\n  void Serialize(\n      const string& name,\n      BlobSerializerBase::SerializationAcceptor acceptor,\n      int chunk_size = kDefaultChunkSize) const;\n\n  /**\n   * @brief Convenience function to serialize a blob to a string.\n   *\n   * This is a conveinence function to serialize small Blobs that produce\n   * manageable serialized strings. To serialize big blobs such as\n   * large sparse tensors, use the fully-functional interface in\n   * blob_serializer_base.h.\n   *\n   * NOTE: this function doesn't do chunking and might break with big tensors.\n   */\n  string Serialize(const string& name) const;\n\n  /**\n   * @brief Swaps the underlying storage of two blobs.\n   */\n  void swap(Blob& rhs) {\n    using std::swap;\n    swap(meta_, rhs.meta_);\n    swap(pointer_, rhs.pointer_);\n    swap(destroy_, rhs.destroy_);\n  }\n\n  /**\n   * Deserializes from a string containing either BlobProto or TensorProto. If\n   * the deserialization fails, the content in the blob should no longer be\n   * trusted.\n   */\n  void Deserialize(const string& content);\n  void Deserialize(const BlobProto& proto);\n\n private:\n  /**\n   * @brief A destroy call that is used to properly deconstruct objects.\n   */\n  template <class T>\n  static void Destroy(void* pointer) {\n    delete static_cast<T*>(pointer);\n  }\n  TypeMeta meta_;\n  void* pointer_ = nullptr;\n  DestroyCall destroy_ = nullptr;\n\n  DISABLE_COPY_AND_ASSIGN(Blob);\n};\n\ninline void swap(Blob& lhs, Blob& rhs) {\n  lhs.swap(rhs);\n}\n\n}  // namespace caffe2\n#endif  // CAFFE2_CORE_BLOB_H_\n"
  },
  {
    "path": "caffe2/core/blob_gpu_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>  // NOLINT\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\nnamespace {\n\ntemplate <typename T> class TensorGPUTest : public ::testing::Test {};\ntemplate <typename T> class TensorGPUDeathTest : public ::testing::Test {};\ntypedef ::testing::Types<char, int, float> TensorTypes;\nTYPED_TEST_CASE(TensorGPUTest, TensorTypes);\nTYPED_TEST_CASE(TensorGPUDeathTest, TensorTypes);\n\nTYPED_TEST(TensorGPUTest, TensorInitializedEmpty) {\n  if (!caffe2::HasCudaGPU()) return;\n  TensorCUDA tensor;\n  EXPECT_EQ(tensor.ndim(), 0);\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  tensor.Resize(dims);\n  EXPECT_EQ(tensor.ndim(), 3);\n  EXPECT_EQ(tensor.dim32(0), 2);\n  EXPECT_EQ(tensor.dim32(1), 3);\n  EXPECT_EQ(tensor.dim32(2), 5);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  EXPECT_TRUE(tensor.data<TypeParam>() != nullptr);\n}\n\nTYPED_TEST(TensorGPUTest, TensorInitializedNonEmpty) {\n  if (!HasCudaGPU()) return;\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  TensorCUDA tensor(dims);\n  EXPECT_EQ(tensor.ndim(), 3);\n  EXPECT_EQ(tensor.dim32(0), 2);\n  EXPECT_EQ(tensor.dim32(1), 3);\n  EXPECT_EQ(tensor.dim32(2), 5);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  EXPECT_TRUE(tensor.data<TypeParam>() != nullptr);\n  dims[0] = 7;\n  dims[1] = 11;\n  dims[2] = 13;\n  dims.push_back(17);\n  tensor.Resize(dims);\n  EXPECT_EQ(tensor.ndim(), 4);\n  EXPECT_EQ(tensor.dim32(0), 7);\n  EXPECT_EQ(tensor.dim32(1), 11);\n  EXPECT_EQ(tensor.dim32(2), 13);\n  EXPECT_EQ(tensor.dim32(3), 17);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  EXPECT_TRUE(tensor.data<TypeParam>() != nullptr);\n}\n\nTYPED_TEST(TensorGPUTest, TensorShareData) {\n  if (!HasCudaGPU()) return;\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  TensorCUDA tensor(dims);\n  TensorCUDA other_tensor(dims);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  other_tensor.ShareData(tensor);\n  EXPECT_TRUE(tensor.data<TypeParam>() != nullptr);\n  EXPECT_TRUE(other_tensor.data<TypeParam>() != nullptr);\n  EXPECT_EQ(tensor.data<TypeParam>(), other_tensor.data<TypeParam>());\n}\n\nTYPED_TEST(TensorGPUTest, TensorShareDataCanUseDifferentShapes) {\n  if (!HasCudaGPU()) return;\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  vector<int> alternate_dims(1);\n  alternate_dims[0] = 2 * 3 * 5;\n  TensorCUDA tensor(dims);\n  TensorCUDA other_tensor(alternate_dims);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  other_tensor.ShareData(tensor);\n  EXPECT_EQ(other_tensor.ndim(), 1);\n  EXPECT_EQ(other_tensor.dim32(0), alternate_dims[0]);\n  EXPECT_TRUE(tensor.data<TypeParam>() != nullptr);\n  EXPECT_TRUE(other_tensor.data<TypeParam>() != nullptr);\n  EXPECT_EQ(tensor.data<TypeParam>(), other_tensor.data<TypeParam>());\n}\n\nTYPED_TEST(TensorGPUTest, NoLongerSharesAfterResize) {\n  if (!HasCudaGPU()) return;\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  TensorCUDA tensor(dims);\n  TensorCUDA other_tensor(dims);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  other_tensor.ShareData(tensor);\n  EXPECT_EQ(tensor.data<TypeParam>(), other_tensor.data<TypeParam>());\n  auto* old_pointer = other_tensor.data<TypeParam>();\n\n  dims[0] = 7;\n  tensor.Resize(dims);\n  EXPECT_EQ(old_pointer, other_tensor.data<TypeParam>());\n  EXPECT_NE(old_pointer, tensor.mutable_data<TypeParam>());\n}\n\nTYPED_TEST(TensorGPUDeathTest, CannotAccessDataWhenEmpty) {\n  if (!HasCudaGPU()) return;\n  ::testing::FLAGS_gtest_death_test_style = \"threadsafe\";\n  TensorCUDA tensor;\n  EXPECT_EQ(tensor.ndim(), 0);\n  EXPECT_THROW(tensor.data<TypeParam>(), EnforceNotMet);\n}\n\n#define TEST_SERIALIZATION_GPU_WITH_TYPE(TypeParam, field_name)            \\\n  TEST(TensorGPUTest, TensorSerialization_##TypeParam) {                   \\\n    if (!HasCudaGPU()) {                                                   \\\n      return;                                                              \\\n    }                                                                      \\\n    Blob blob;                                                             \\\n    TensorCPU cpu_tensor;                                                  \\\n    cpu_tensor.Resize(2, 3);                                               \\\n    for (int i = 0; i < 6; ++i) {                                          \\\n      cpu_tensor.mutable_data<TypeParam>()[i] = static_cast<TypeParam>(i); \\\n    }                                                                      \\\n    blob.GetMutable<TensorCUDA>()->CopyFrom(cpu_tensor);                   \\\n    string serialized = blob.Serialize(\"test\");                            \\\n    BlobProto proto;                                                       \\\n    CAFFE_ENFORCE(proto.ParseFromString(serialized));                      \\\n    EXPECT_EQ(proto.name(), \"test\");                                       \\\n    EXPECT_EQ(proto.type(), \"Tensor\");                                     \\\n    EXPECT_TRUE(proto.has_tensor());                                       \\\n    const TensorProto& tensor_proto = proto.tensor();                      \\\n    EXPECT_EQ(                                                             \\\n        tensor_proto.data_type(),                                          \\\n        TypeMetaToDataType(TypeMeta::Make<TypeParam>()));                  \\\n    EXPECT_EQ(tensor_proto.field_name##_size(), 6);                        \\\n    for (int i = 0; i < 6; ++i) {                                          \\\n      EXPECT_EQ(tensor_proto.field_name(i), static_cast<TypeParam>(i));    \\\n    }                                                                      \\\n    Blob new_blob;                                                         \\\n    EXPECT_NO_THROW(new_blob.Deserialize(serialized));                     \\\n    EXPECT_TRUE(new_blob.IsType<TensorCUDA>());                            \\\n    TensorCPU new_cpu_tensor(blob.Get<TensorCUDA>());                      \\\n    EXPECT_EQ(new_cpu_tensor.ndim(), 2);                                   \\\n    EXPECT_EQ(new_cpu_tensor.dim(0), 2);                                   \\\n    EXPECT_EQ(new_cpu_tensor.dim(1), 3);                                   \\\n    for (int i = 0; i < 6; ++i) {                                          \\\n      EXPECT_EQ(                                                           \\\n          cpu_tensor.data<TypeParam>()[i],                                 \\\n          new_cpu_tensor.data<TypeParam>()[i]);                            \\\n    }                                                                      \\\n  }\n\nTEST_SERIALIZATION_GPU_WITH_TYPE(bool, int32_data)\nTEST_SERIALIZATION_GPU_WITH_TYPE(double, double_data)\nTEST_SERIALIZATION_GPU_WITH_TYPE(float, float_data)\nTEST_SERIALIZATION_GPU_WITH_TYPE(int, int32_data)\nTEST_SERIALIZATION_GPU_WITH_TYPE(int8_t, int32_data)\nTEST_SERIALIZATION_GPU_WITH_TYPE(int16_t, int32_data)\nTEST_SERIALIZATION_GPU_WITH_TYPE(uint8_t, int32_data)\nTEST_SERIALIZATION_GPU_WITH_TYPE(uint16_t, int32_data)\nTEST_SERIALIZATION_GPU_WITH_TYPE(int64_t, int64_data)\n\nTEST(TensorTest, TensorSerializationMultiDevices) {\n  Blob blob;\n  TensorCPU tensor;\n  tensor.Resize(2, 3);\n  for (int i = 0; i < 6; ++i) {\n    tensor.mutable_data<float>()[i] = i;\n  }\n  for (int gpu_id = 0; gpu_id < NumCudaDevices(); ++gpu_id) {\n    DeviceGuard guard(gpu_id);\n    CUDAContext context(gpu_id);\n    blob.Reset(new TensorCUDA(tensor, &context));\n    string serialized = blob.Serialize(\"test\");\n    BlobProto proto;\n    CAFFE_ENFORCE(proto.ParseFromString(serialized));\n    EXPECT_EQ(proto.name(), \"test\");\n    EXPECT_TRUE(proto.has_tensor());\n    const TensorProto& tensor_proto = proto.tensor();\n    EXPECT_EQ(tensor_proto.data_type(), TensorProto::FLOAT);\n    EXPECT_EQ(tensor_proto.float_data_size(), 6);\n    for (int i = 0; i < 6; ++i) {\n      EXPECT_EQ(tensor_proto.float_data(i), i);\n    }\n    EXPECT_TRUE(tensor_proto.has_device_detail());\n    EXPECT_EQ(tensor_proto.device_detail().device_type(), CUDA);\n    EXPECT_EQ(tensor_proto.device_detail().cuda_gpu_id(), gpu_id);\n    // Test if the restored blob is still of the same device.\n    blob.Reset();\n    EXPECT_NO_THROW(blob.Deserialize(serialized));\n    EXPECT_TRUE(blob.IsType<TensorCUDA>());\n    EXPECT_EQ(GetGPUIDForPointer(blob.Get<TensorCUDA>().data<float>()),\n              gpu_id);\n    // Test if we force the restored blob on a different device, we\n    // can still get so.\n    blob.Reset();\n    proto.mutable_tensor()->mutable_device_detail()->set_cuda_gpu_id(0);\n    EXPECT_NO_THROW(blob.Deserialize(proto.SerializeAsString()));\n    EXPECT_TRUE(blob.IsType<TensorCUDA>());\n    EXPECT_EQ(GetGPUIDForPointer(blob.Get<TensorCUDA>().data<float>()), 0);\n  }\n}\n\n}  // namespace\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/blob_serialization.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/blob_serialization.h\"\n\n#include <sstream>\n#include <mutex>\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nCAFFE2_DEFINE_int(\n    caffe2_tensor_chunk_size,\n    1000000,\n    \"Chunk size to split tensor data into\");\n\nCAFFE2_DEFINE_int(\n    caffe2_max_tensor_serializer_threads,\n    16,\n    \"Maximal number of threads that can be used for tensor serialization\");\n\nCAFFE2_DEFINE_bool(\n    caffe2_serialize_fp16_as_bytes,\n    false,\n    \"Serialize FLOAT16 tensors using byte_data field\");\n\nnamespace caffe2 {\n/**\n * @brief StringSerializer is the serializer for String.\n *\n * StringSerializer takes in a blob that contains a String, and serializes it\n * into a BlobProto protocol buffer.\n */\nclass StringSerializer : public BlobSerializerBase {\n public:\n  StringSerializer() {}\n  ~StringSerializer() {}\n  /**\n   * Serializes a Blob. Note that this blob has to contain Tensor<Context>,\n   * otherwise this function produces a fatal error.\n   */\n  void Serialize(\n      const Blob& blob,\n      const string& name,\n      SerializationAcceptor acceptor) override {\n    CAFFE_ENFORCE(blob.IsType<std::string>());\n\n    BlobProto blob_proto;\n    blob_proto.set_name(name);\n    blob_proto.set_type(\"std::string\");\n    blob_proto.set_content(blob.template Get<std::string>());\n    acceptor(name, blob_proto.SerializeAsString());\n  }\n};\n\n/**\n * @brief StringDeserializer is the deserializer for Strings.\n *\n */\nclass StringDeserializer : public BlobDeserializerBase {\n public:\n  void Deserialize(const BlobProto& proto, Blob* blob) override {\n    *blob->GetMutable<std::string>() = proto.content();\n  }\n};\n\n// The blob serialization member function implementation.\nvoid Blob::Serialize(\n    const string& name,\n    BlobSerializerBase::SerializationAcceptor acceptor,\n    int chunk_size) const {\n  std::unique_ptr<BlobSerializerBase> serializer(CreateSerializer(meta_.id()));\n  CAFFE_ENFORCE(serializer, \"No known serializer for \", meta_.name());\n  serializer->SerializeWithChunkSize(*this, name, acceptor, chunk_size);\n}\n\n// The blob serialization member function implementation.\nstd::string Blob::Serialize(const string& name) const {\n  std::string data;\n  BlobSerializerBase::SerializationAcceptor acceptor = [&data](\n      const std::string&, const std::string& blob) {\n    DCHECK(data.empty()); // should be called once with kNoChunking\n    data = blob;\n  };\n  this->Serialize(name, acceptor, kNoChunking);\n  return data;\n}\n\n// Specialization for StoreDeviceDetail for CPU - nothing needs to be done.\ntemplate <>\nvoid TensorSerializer<CPUContext>::StoreDeviceDetail(\n    const Tensor<CPUContext>& /*input*/,\n    TensorProto* /*proto*/) {}\n\n// The actual serialization registry objects.\nCAFFE_DEFINE_TYPED_REGISTRY(\n    BlobSerializerRegistry,\n    CaffeTypeId,\n    BlobSerializerBase,\n    std::unique_ptr);\n\nCAFFE_DEFINE_REGISTRY(BlobDeserializerRegistry, BlobDeserializerBase);\n\nvoid Blob::Deserialize(const string& content) {\n  BlobProto blob_proto;\n  CAFFE_ENFORCE(\n      blob_proto.ParseFromString(content),\n      \"Cannot parse content into a BlobProto.\");\n  Deserialize(blob_proto);\n}\n\nvoid Blob::Deserialize(const BlobProto& blob_proto) {\n  if (blob_proto.type() == kTensorBlobType) {\n    // This is a tensor object. Depending on the device type, we will\n    // use the corresponding TensorDeserializer.\n    auto deserializer = CreateDeserializer(\n        \"Tensor\" +\n        DeviceTypeName(blob_proto.tensor().device_detail().device_type()));\n    // Tensor's deserializer should always be registered, but we will double\n    // check if it is not null anyway.\n    CAFFE_ENFORCE(deserializer.get());\n    deserializer->Deserialize(blob_proto, this);\n  } else {\n    auto deserializer = CreateDeserializer(blob_proto.type());\n    CAFFE_ENFORCE(\n        deserializer.get(),\n        \"No registered deserializer for type \",\n        blob_proto.type());\n    deserializer->Deserialize(blob_proto, this);\n  }\n}\n\nnamespace {\n// Serialize TensorCPU.\nREGISTER_BLOB_SERIALIZER(\n    (TypeMeta::Id<TensorCPU>()),\n    TensorSerializer<CPUContext>);\nREGISTER_BLOB_DESERIALIZER(TensorCPU, TensorDeserializer<CPUContext>);\n// Serialize std::string\nREGISTER_BLOB_SERIALIZER((TypeMeta::Id<std::string>()), StringSerializer);\nREGISTER_BLOB_DESERIALIZER(std::string, StringDeserializer);\n}  // namespace\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/blob_serialization.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_BLOB_SERIALIZATION_H_\n#define CAFFE2_CORE_BLOB_SERIALIZATION_H_\n\n#include <limits>\n#include <future>\n\n#include <google/protobuf/repeated_field.h>\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/blob_serializer_base.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/typeid.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/utils/simple_queue.h\"\n\nCAFFE2_DECLARE_int(caffe2_tensor_chunk_size);\nCAFFE2_DECLARE_int(caffe2_max_tensor_serializer_threads);\nCAFFE2_DECLARE_bool(caffe2_serialize_fp16_as_bytes);\n\nnamespace caffe2 {\n\nconstexpr auto kTensorBlobType = \"Tensor\";\n// String used to separate chunk id from the blob name when storing in DB\nconstexpr auto kChunkIdSeparator = \"#%\";\n\n// The Blob serialization registry and serializer creator functions.\nCAFFE_DECLARE_TYPED_REGISTRY(\n    BlobSerializerRegistry,\n    CaffeTypeId,\n    BlobSerializerBase,\n    std::unique_ptr);\n#define REGISTER_BLOB_SERIALIZER(id, ...) \\\n  CAFFE_REGISTER_TYPED_CLASS(BlobSerializerRegistry, id, __VA_ARGS__)\n// Creates an operator with the given operator definition.\ninline unique_ptr<BlobSerializerBase> CreateSerializer(CaffeTypeId id) {\n  return BlobSerializerRegistry()->Create(id);\n}\n\n/**\n * @brief TensorSerializer is the serializer for Tensors.\n *\n * TensorSerializer takes in a blob that contains a Tensor, and serializes it\n * into a TensorProto protocol buffer.\n */\ntemplate <class Context>\nclass TensorSerializer : public BlobSerializerBase {\n public:\n  TensorSerializer() : context_() {}\n  ~TensorSerializer() override {}\n  /**\n   * Serializes a Blob. Note that this blob has to contain Tensor<Context>,\n   * otherwise this function produces a fatal error.\n   */\n  void Serialize(\n      const Blob& blob,\n      const string& name,\n      SerializationAcceptor acceptor) override;\n  void SerializeWithChunkSize(\n      const Blob& blob,\n      const string& name,\n      SerializationAcceptor acceptor,\n      int chunk_size) override;\n\n  void Serialize(const Tensor<Context>& tensor, const string& name,\n                 TensorProto* proto, size_t chunkBegin, int32_t chunkSize);\n\n private:\n  // A utility function to store the device context detauls.\n  void StoreDeviceDetail(const Tensor<Context>& input, TensorProto* proto);\n  Context context_;\n};\n\n/**\n * @brief BlobDeserializerBase is an abstract class that deserializes a blob\n * from a BlobProto or a TensorProto.\n */\nclass BlobDeserializerBase {\n public:\n  virtual ~BlobDeserializerBase() {}\n\n  // Deserializes from a BlobProto object.\n  virtual void Deserialize(const BlobProto& proto, Blob* blob) = 0;\n};\n\nCAFFE_DECLARE_REGISTRY(BlobDeserializerRegistry, BlobDeserializerBase);\n#define REGISTER_BLOB_DESERIALIZER(name, ...) \\\n  CAFFE_REGISTER_CLASS(BlobDeserializerRegistry, name, __VA_ARGS__)\n// Creates an operator with the given operator definition.\ninline unique_ptr<BlobDeserializerBase> CreateDeserializer(const string& type) {\n  return BlobDeserializerRegistry()->Create(type);\n}\n\n/**\n * @brief TensorDeserializer is the deserializer for Tensors.\n *\n * The device that the deserialized Tensor will live under is determined by the\n * device_detail field. If you want to specify the device of the deserialized\n * tensor, change the TensorProto's corresponding fields before calling\n * Deserialize.\n */\ntemplate <class Context>\nclass TensorDeserializer : public BlobDeserializerBase {\n public:\n  void Deserialize(const BlobProto& proto, Blob* blob) override;\n  void Deserialize(const TensorProto& proto, Tensor<Context>* tensor);\n};\n\n////////////////////////////////////////////////////////////////////////////////\n// Implementations\n////////////////////////////////////////////////////////////////////////////////\n\nnamespace detail {\ntemplate <typename SrcType, typename DstType, class Context>\ninline void CopyToProtoAsIs(\n    const size_t size,\n    const SrcType* src,\n    google::protobuf::RepeatedField<DstType>* field,\n    Context* context) {\n  static_assert(\n      sizeof(SrcType) == sizeof(DstType),\n      \"The source type and dest type cannot be copied as-is. Did \"\n      \"you mean CopyToProtoWithCast?\");\n  field->Reserve(size);\n  for (int i = 0; i < size; ++i) {\n    field->Add(0);\n  }\n  context->template Copy<SrcType, Context, CPUContext>(\n      size, src, reinterpret_cast<SrcType*>(field->mutable_data()));\n  // Make sure that we finish the copy into the protobuf.\n  context->FinishDeviceComputation();\n}\n\ntemplate <typename SrcType, typename DstType, class Context>\ninline void CopyToProtoWithCast(\n    const size_t size,\n    const SrcType* src,\n    google::protobuf::RepeatedField<DstType>* field,\n    Context* context) {\n  // TODO: we are having one unnecessary copy here if the context is already\n  // CPUContext. Remove it if it is performance critical.\n  unique_ptr<SrcType[]> buffer(new SrcType[size]);\n  context->template Copy<SrcType, Context, CPUContext>(\n      size, src, buffer.get());\n  context->FinishDeviceComputation();\n  field->Reserve(size);\n  for (int i = 0; i < size; ++i) {\n    field->Add(static_cast<DstType>(buffer[i]));\n  }\n}\n\ntemplate <typename SrcType, typename DstType, class Context>\ninline void CopyFromProtoAsIs(\n    const size_t size,\n    const google::protobuf::RepeatedField<SrcType>& field,\n    DstType* dst,\n    Context* context) {\n  static_assert(\n      sizeof(SrcType) == sizeof(DstType),\n      \"The source type and dest type cannot be copied as-is. Did \"\n      \"you mean CopyFromProtoWithCast?\");\n  CAFFE_ENFORCE_EQ(size, field.size(), \"Incorrect proto field size.\");\n  context->template Copy<DstType, CPUContext, Context>(\n      size, reinterpret_cast<const DstType*>(field.data()), dst);\n}\n\ntemplate <typename SrcType, typename DstType, class Context>\ninline void CopyFromProtoWithCast(\n    const size_t size,\n    const google::protobuf::RepeatedField<SrcType>& field,\n    DstType* dst,\n    Context* context) {\n  CAFFE_ENFORCE_EQ(size, field.size(), \"Incorrect proto field size.\");\n  // TODO: we are having one unnecessary copy here if the context is already\n  // CPUContext. Remove it if it is performance critical.\n  unique_ptr<DstType[]> buffer(new DstType[size]);\n  const SrcType* src = field.data();\n  for (int i = 0; i < size; ++i) {\n    buffer[i] = static_cast<DstType>(src[i]);\n  }\n  context->template Copy<DstType, CPUContext, Context>(size, buffer.get(), dst);\n}\n\n}  // namespace detail\n\ntemplate <class Context>\nvoid TensorSerializer<Context>::Serialize(\n    const Blob& blob,\n    const string& name,\n    BlobSerializerBase::SerializationAcceptor acceptor) {\n  this->SerializeWithChunkSize(blob, name, acceptor, kDefaultChunkSize);\n}\n\ntemplate <class Context>\nvoid TensorSerializer<Context>::SerializeWithChunkSize(\n    const Blob& blob,\n    const string& name,\n    BlobSerializerBase::SerializationAcceptor acceptor,\n    int chunk_size) {\n  CAFFE_ENFORCE(blob.IsType<Tensor<Context>>());\n  const auto& tensor = blob.template Get<Tensor<Context>>();\n  if (chunk_size == kNoChunking) {\n    chunk_size = tensor.size() + 1; // to account for empty tensors\n  } else if (chunk_size == kDefaultChunkSize) {\n    chunk_size = FLAGS_caffe2_tensor_chunk_size;\n  }\n\n  auto processChunk = [&](int64_t chunkStart) {\n    BlobProto blob_proto;\n    blob_proto.set_name(name);\n    blob_proto.set_type(kTensorBlobType);\n    TensorProto& proto = *blob_proto.mutable_tensor();\n    proto.set_name(name);\n    this->Serialize(\n        tensor, name, blob_proto.mutable_tensor(), chunkStart, chunk_size);\n    acceptor(\n        MakeString(name, kChunkIdSeparator, chunkStart / chunk_size),\n        blob_proto.SerializeAsString());\n  };\n\n#ifndef __ANDROID__\n  std::vector<std::future<void>> futures;\n  // Poorman's IOBound ThreadPool\n  SimpleQueue<size_t> chunkQueue;\n  auto task = [&]() {\n    size_t chunkStart;\n    while (chunkQueue.Pop(&chunkStart)) {\n      processChunk(chunkStart);\n    }\n  };\n  if (tensor.size() > chunk_size) {\n    for (int i = 0; i < FLAGS_caffe2_max_tensor_serializer_threads; ++i) {\n      futures.emplace_back(std::async(std::launch::async, task));\n    }\n  }\n#endif\n\n  VLOG(1) << \"Serializing blob \" << name;\n  // Serialize whole vector. If vector is empty, it's shape still needs to be\n  // serialized in empty proto\n  for (size_t chunkBegin = 0;\n       chunkBegin < std::max(tensor.size(), static_cast<TIndex>(1));\n       chunkBegin += chunk_size) {\n    VLOG(2) << \"Starting a chunk at \" << chunkBegin;\n#ifndef __ANDROID__\n    if (tensor.size() > chunk_size) {\n      chunkQueue.Push(chunkBegin);\n    } else {\n      // Sync mode for small tensors\n      processChunk(chunkBegin);\n    }\n#else\n    // Since Android does not have std::future, we will always do sync mode\n    processChunk(chunkBegin);\n#endif\n  }\n\n#ifndef __ANDROID__\n  chunkQueue.NoMoreJobs();\n  for (auto& fut : futures) {\n    fut.get();\n  }\n#endif\n}\n\ntemplate <class Context>\nvoid TensorSerializer<Context>::Serialize(\n    const Tensor<Context>& input,\n    const string& /*name*/,\n    TensorProto* proto_ptr,\n    size_t chunkBegin,\n    int32_t chunkSize) {\n  CAFFE_ENFORCE(\n      chunkBegin <= input.size(),\n      \"Chunk begin is out of tensor: \",\n      chunkBegin,\n      ' ',\n      input.size());\n  if (chunkBegin + chunkSize > input.size()) {\n    chunkSize = input.size() - chunkBegin;\n  }\n\n  CAFFE_ENFORCE(\n      input.raw_data() || chunkSize == 0,\n      \"The input does not have data input yet. This is probably because you \"\n      \"created a tensor of non-zero shape but never filled its data via \"\n      \"mutable_data() calls. This means that it makes no sense to serialize \"\n      \"the tensor content.\");\n\n  TensorProto& proto = *proto_ptr;\n  proto.mutable_segment()->set_begin(chunkBegin);\n  proto.mutable_segment()->set_end(chunkBegin + chunkSize);\n\n  for (int i = 0; i < input.ndim(); ++i) {\n    proto.add_dims(input.dim(i));\n  }\n  const TensorProto::DataType data_type = TypeMetaToDataType(input.meta());\n  proto.set_data_type(data_type);\n  StoreDeviceDetail(input, &proto);\n\n  // A lot of copypaste is error prone. Should we create a macro for this?\n  switch (data_type) {\n  case TensorProto_DataType_FLOAT:\n    detail::CopyToProtoAsIs(\n        chunkSize,\n        input.template data<float>() + chunkBegin,\n        proto.mutable_float_data(),\n        &this->context_);\n    break;\n  case TensorProto_DataType_INT32:\n    detail::CopyToProtoAsIs(\n        chunkSize,\n        input.template data<int>() + chunkBegin,\n        proto.mutable_int32_data(),\n        &this->context_);\n    break;\n  case TensorProto_DataType_BYTE:\n    LOG(FATAL) << \"This should not happen. When serializing, \"\n                  \"BYTE is deprecated and moved to UINT8.\";\n    break;\n  case TensorProto_DataType_STRING:\n    {\n      proto.mutable_string_data()->Reserve(chunkSize);\n      const string* content = input.template data<string>();\n      for (int i = chunkBegin; i < chunkBegin + chunkSize; ++i) {\n        proto.add_string_data(content[i]);\n      }\n      break;\n    }\n  case TensorProto_DataType_BOOL:\n    detail::CopyToProtoWithCast(\n        chunkSize,\n        input.template data<bool>() + chunkBegin,\n        proto.mutable_int32_data(),\n        &this->context_);\n    break;\n  case TensorProto_DataType_UINT8:\n    detail::CopyToProtoWithCast(\n        chunkSize,\n        input.template data<uint8_t>() + chunkBegin,\n        proto.mutable_int32_data(),\n        &this->context_);\n    break;\n  case TensorProto_DataType_INT8:\n    detail::CopyToProtoWithCast(\n        chunkSize,\n        input.template data<int8_t>() + chunkBegin,\n        proto.mutable_int32_data(),\n        &this->context_);\n    break;\n  case TensorProto_DataType_UINT16:\n    detail::CopyToProtoWithCast(\n        chunkSize,\n        input.template data<uint16_t>() + chunkBegin,\n        proto.mutable_int32_data(),\n        &this->context_);\n    break;\n  case TensorProto_DataType_INT16:\n    detail::CopyToProtoWithCast(\n        chunkSize,\n        input.template data<int16_t>() + chunkBegin,\n        proto.mutable_int32_data(),\n        &this->context_);\n    break;\n  case TensorProto_DataType_INT64:\n    detail::CopyToProtoAsIs(\n        chunkSize,\n        input.template data<int64_t>() + chunkBegin,\n        proto.mutable_int64_data(),\n        &this->context_);\n    break;\n  case TensorProto_DataType_FLOAT16: {\n    if (FLAGS_caffe2_serialize_fp16_as_bytes) {\n      const int kValue = 1;\n      CAFFE_ENFORCE_EQ(\n          reinterpret_cast<const char*>(&kValue)[0],\n          1,\n          \"Serialization of FLOAT16 on big endian platform \"\n          \"is not written yet.\");\n      unique_ptr<char[]> buffer(new char[2 * chunkSize]);\n      this->context_.template Copy<char, Context, CPUContext>(\n          2 * chunkSize,\n          reinterpret_cast<const char*>(\n              input.template data<float16>() + chunkBegin),\n          buffer.get());\n      this->context_.FinishDeviceComputation();\n      proto.set_byte_data(buffer.release(), 2 * chunkSize);\n    } else {\n      detail::CopyToProtoWithCast(\n          chunkSize,\n          reinterpret_cast<const uint16_t*>(input.template data<float16>()) +\n              chunkBegin,\n          proto.mutable_int32_data(),\n          &this->context_);\n    }\n  } break;\n  case TensorProto_DataType_DOUBLE:\n    detail::CopyToProtoAsIs(\n        chunkSize,\n        input.template data<double>() + chunkBegin,\n        proto.mutable_double_data(),\n        &this->context_);\n    break;\n  case TensorProto_DataType_UNDEFINED: {\n    proto.mutable_string_data()->Reserve(chunkSize);\n    Blob temp_blob;\n    const char* raw_data = static_cast<const char*>(input.raw_data());\n    for (int i = chunkBegin; i < chunkBegin + chunkSize; ++i) {\n      temp_blob.ShareExternal(\n          const_cast<char*>(raw_data + i * input.itemsize()), input.meta());\n      proto.add_string_data(temp_blob.Serialize(\"\"));\n    }\n  } break;\n    // Note: we intentially do not provide \"default:\" so if any new data types\n    // are added, the compiler should warn the user to add the case here.\n  }\n}\n\ntemplate <class Context>\nvoid TensorDeserializer<Context>::Deserialize(\n    const BlobProto& blob_proto,\n    Blob* blob) {\n  Deserialize(blob_proto.tensor(), blob->GetMutable<Tensor<Context>>());\n}\n\ntemplate <class Context>\nvoid TensorDeserializer<Context>::Deserialize(\n    const TensorProto& proto,\n    Tensor<Context>* tensor) {\n  // We create a local context for deserializing. Since Caffe2 contexts are\n  // usually lightweighted, this should not involve too much overhead.\n  Context context(proto.device_detail());\n  context.SwitchToDevice(0);\n  vector<TIndex> dims;\n  for (const TIndex d : proto.dims()) {\n    dims.push_back(d);\n  }\n  tensor->Resize(dims);\n\n  int64_t chunkBegin = 0;\n  auto chunkEnd = tensor->size();\n  if (proto.has_segment()) {\n    chunkBegin = proto.segment().begin();\n    chunkEnd = proto.segment().end();\n  }\n  CAFFE_ENFORCE(\n      0 <= chunkBegin && chunkBegin <= chunkEnd && chunkEnd <= tensor->size(),\n      \"Invalid chunk \",\n      chunkBegin,\n      ' ',\n      chunkEnd,\n      \" with total tensor size \",\n      tensor->size());\n  auto chunkSize = chunkEnd - chunkBegin;\n\n  switch (proto.data_type()) {\n    case TensorProto_DataType_FLOAT:\n      detail::CopyFromProtoAsIs(\n          chunkSize,\n          proto.float_data(),\n          tensor->template mutable_data<float>() + chunkBegin,\n          &context);\n      break;\n    case TensorProto_DataType_INT32:\n      detail::CopyFromProtoAsIs(\n          chunkSize,\n          proto.int32_data(),\n          tensor->template mutable_data<int>() + chunkBegin,\n          &context);\n      break;\n    case TensorProto_DataType_BYTE:\n      // Since BYTE stores the data in a string field instead of a repreated\n      // field we will have it special cased.\n      CAFFE_ENFORCE_EQ(\n          chunkSize, proto.byte_data().size(), \"Incorrect proto field size.\");\n      context.template Copy<uint8_t, Context, CPUContext>(\n          chunkSize,\n          reinterpret_cast<const uint8_t*>(proto.byte_data().data()),\n          tensor->template mutable_data<uint8_t>() + chunkBegin);\n      break;\n    case TensorProto_DataType_STRING:\n      // Special handing of string because it is a non-fundamental type.\n      {\n        string* content = tensor->template mutable_data<string>();\n        for (int i = 0; i < chunkSize; ++i) {\n          content[i + chunkBegin] = proto.string_data(i);\n        }\n      }\n      break;\n    case TensorProto_DataType_BOOL:\n      detail::CopyFromProtoWithCast(\n          chunkSize,\n          proto.int32_data(),\n          tensor->template mutable_data<bool>() + chunkBegin,\n          &context);\n      break;\n    case TensorProto_DataType_UINT8:\n      detail::CopyFromProtoWithCast(\n          chunkSize,\n          proto.int32_data(),\n          tensor->template mutable_data<uint8_t>() + chunkBegin,\n          &context);\n      break;\n    case TensorProto_DataType_INT8:\n      detail::CopyFromProtoWithCast(\n          chunkSize,\n          proto.int32_data(),\n          tensor->template mutable_data<int8_t>() + chunkBegin,\n          &context);\n      break;\n    case TensorProto_DataType_UINT16:\n      detail::CopyFromProtoWithCast(\n          chunkSize,\n          proto.int32_data(),\n          tensor->template mutable_data<uint16_t>() + chunkBegin,\n          &context);\n      break;\n    case TensorProto_DataType_INT16:\n      detail::CopyFromProtoWithCast(\n          chunkSize,\n          proto.int32_data(),\n          tensor->template mutable_data<int16_t>() + chunkBegin,\n          &context);\n      break;\n    case TensorProto_DataType_INT64:\n      detail::CopyFromProtoAsIs(\n          chunkSize,\n          proto.int64_data(),\n          tensor->template mutable_data<int64_t>() + chunkBegin,\n          &context);\n      break;\n    case TensorProto_DataType_FLOAT16:\n      if (proto.has_byte_data()) {\n        const int kValue = 1;\n        CAFFE_ENFORCE_EQ(\n            reinterpret_cast<const char*>(&kValue)[0],\n            1,\n            \"Serialization of FLOAT16 on big endian platform \"\n            \"is not written yet.\");\n        CAFFE_ENFORCE_EQ(\n            2 * chunkSize,\n            proto.byte_data().size(),\n            \"Incorrect proto field size.\");\n        context.template Copy<float16, Context, CPUContext>(\n            chunkSize,\n            reinterpret_cast<const float16*>(proto.byte_data().data()),\n            tensor->template mutable_data<float16>() + chunkBegin);\n      } else {\n        // Backward compatibility with models which used int32_data field\n        detail::CopyFromProtoWithCast(\n            chunkSize,\n            proto.int32_data(),\n            reinterpret_cast<uint16_t*>(\n                tensor->template mutable_data<float16>()) +\n                chunkBegin,\n            &context);\n      }\n      break;\n    case TensorProto_DataType_DOUBLE:\n      detail::CopyFromProtoAsIs(\n          chunkSize,\n          proto.double_data(),\n          tensor->template mutable_data<double>() + chunkBegin,\n          &context);\n      break;\n    case TensorProto_DataType_UNDEFINED: {\n      Blob temp_blob;\n      void* raw_ptr = nullptr;\n      for (int i = 0; i < chunkSize; ++i) {\n        temp_blob.Deserialize(proto.string_data(i));\n        if (i == 0) {\n          raw_ptr = tensor->raw_mutable_data(temp_blob.meta());\n        }\n        temp_blob.meta().copy()(\n            temp_blob.GetRaw(),\n            static_cast<char*>(raw_ptr) +\n                (i + chunkBegin) * temp_blob.meta().itemsize(),\n            1);\n      }\n    }\n  }\n  context.FinishDeviceComputation();\n}\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_CORE_BLOB_SERIALIZATION_H_\n"
  },
  {
    "path": "caffe2/core/blob_serialization_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nvoid TensorSerializer<CUDAContext>::StoreDeviceDetail(\n    const Tensor<CUDAContext>& input, TensorProto* proto) {\n  auto* device_detail = proto->mutable_device_detail();\n  device_detail->set_device_type(CUDA);\n  device_detail->set_cuda_gpu_id(\n      GetGPUIDForPointer(input.raw_data()));\n}\n\nnamespace {\nREGISTER_BLOB_SERIALIZER(\n    (TypeMeta::Id<TensorCUDA>()),\n    TensorSerializer<CUDAContext>);\nREGISTER_BLOB_DESERIALIZER(TensorCUDA, TensorDeserializer<CUDAContext>);\n}\n}  // namespace caffe2\n\n"
  },
  {
    "path": "caffe2/core/blob_serializer_base.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <string>\n#include <functional>\n\nnamespace caffe2 {\n\nclass Blob;\n\nconstexpr int kDefaultChunkSize = -1;\nconstexpr int kNoChunking = 0;\n\n/**\n * @brief BlobSerializerBase is an abstract class that serializes a blob to a\n * string.\n *\n * This class exists purely for the purpose of registering type-specific\n * serialization code. If you need to serialize a specific type, you should\n * write your own Serializer class, and then register it using\n * REGISTER_BLOB_SERIALIZER. For a detailed example, see TensorSerializer for\n * details.\n */\nclass BlobSerializerBase {\n public:\n  virtual ~BlobSerializerBase() {}\n  using SerializationAcceptor =\n     std::function<void(const std::string& blobName, const std::string& data)>;\n  /**\n   * @brief The virtual function that returns a serialized string for the input\n   * blob.\n   * @param blob\n   *     the input blob to be serialized.\n   * @param name\n   *     the blob name to be used in the serialization implementation. It is up\n   *     to the implementation whether this name field is going to be used or\n   *     not.\n   * @param acceptor\n   *     a lambda which accepts key value pairs to save them to storage.\n   *     serailizer can use it to save blob in several chunks\n   *     acceptor should be thread-safe\n   */\n  virtual void Serialize(const Blob& blob, const std::string& name,\n                        SerializationAcceptor acceptor) = 0;\n\n  virtual void SerializeWithChunkSize(\n      const Blob& blob,\n      const std::string& name,\n      SerializationAcceptor acceptor,\n      int /*chunk_size*/) {\n    // Base implementation.\n    Serialize(blob, name, acceptor);\n  }\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/blob_stats.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/blob_stats.h\"\n\nnamespace caffe2 {\n\nconst BlobStatGetter* BlobStatRegistry::get(CaffeTypeId id) {\n  auto it = map_.find(id);\n  if (it == map_.end()) {\n    return nullptr;\n  }\n  return it->second.get();\n}\n\nBlobStatRegistry& BlobStatRegistry::instance() {\n  static BlobStatRegistry registry;\n  return registry;\n}\n\nvoid BlobStatRegistry::doRegister(\n    CaffeTypeId id,\n    std::unique_ptr<BlobStatGetter>&& v) {\n  // don't use CAFFE_ENFORCE_EQ to avoid static initialization order fiasco.\n  if (map_.count(id) > 0) {\n    throw std::runtime_error(\"BlobStatRegistry: Type already registered.\");\n  }\n  map_[id] = std::move(v);\n}\n\nnamespace BlobStat {\n\nsize_t sizeBytes(const Blob& blob) {\n  auto* p = BlobStatRegistry::instance().get(blob.meta().id());\n  return p ? p->sizeBytes(blob) : 0;\n}\n\n} // namespace BlobStats\n}\n"
  },
  {
    "path": "caffe2/core/blob_stats.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/core/typeid.h\"\n\n#include <unordered_map>\n\nnamespace caffe2 {\n\nstruct BlobStatGetter {\n  virtual size_t sizeBytes(const Blob& blob) const = 0;\n  virtual ~BlobStatGetter() {}\n};\n\nstruct BlobStatRegistry {\n private:\n  std::unordered_map<CaffeTypeId, std::unique_ptr<BlobStatGetter>> map_;\n  void doRegister(CaffeTypeId id, std::unique_ptr<BlobStatGetter>&& v);\n\n public:\n  template <typename T, typename Getter>\n  struct Registrar {\n    Registrar() {\n      BlobStatRegistry::instance().doRegister(\n          TypeMeta::Id<T>(), std::unique_ptr<Getter>(new Getter));\n    }\n  };\n\n  const BlobStatGetter* get(CaffeTypeId id);\n  static BlobStatRegistry& instance();\n};\n\n#define REGISTER_BLOB_STAT_GETTER(Type, BlobStatGetterClass)    \\\n  static BlobStatRegistry::Registrar<Type, BlobStatGetterClass> \\\n      CAFFE_ANONYMOUS_VARIABLE(BlobStatRegistry)\n\nnamespace BlobStat {\n\n/**\n * Return size in bytes of the blob, if available for a blob of given type.\n * If not available, return 0.\n */\nsize_t sizeBytes(const Blob& blob);\n}\n}\n"
  },
  {
    "path": "caffe2/core/blob_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n#include <memory>\n#include <mutex>\n\n#include <gtest/gtest.h>\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/qtensor.h\"\n#include \"caffe2/core/qtensor_serialization.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nCAFFE2_DEFINE_int64(caffe2_test_big_tensor_size, 100000000, \"\");\nCAFFE2_DECLARE_int(caffe2_tensor_chunk_size);\nCAFFE2_DECLARE_bool(caffe2_serialize_fp16_as_bytes);\n\nnamespace caffe2 {\nusing namespace ::caffe2::db;\nnamespace {\nclass BlobTestFoo {\n public:\n  int32_t val;\n};\nclass BlobTestBar {};\n}\n\nCAFFE_KNOWN_TYPE(BlobTestFoo);\nCAFFE_KNOWN_TYPE(BlobTestBar);\n\nclass BlobTestFooSerializer : public BlobSerializerBase {\n public:\n  BlobTestFooSerializer() {}\n  ~BlobTestFooSerializer() {}\n  /**\n   * Serializes a Blob. Note that this blob has to contain Tensor<Context>,\n   * otherwise this function produces a fatal error.\n   */\n  void Serialize(\n      const Blob& blob,\n      const string& name,\n      SerializationAcceptor acceptor) override {\n    CAFFE_ENFORCE(blob.IsType<BlobTestFoo>());\n\n    BlobProto blob_proto;\n    blob_proto.set_name(name);\n    blob_proto.set_type(\"BlobTestFoo\");\n    // For simplicity we will just serialize the 4-byte content as a string.\n    blob_proto.set_content(std::string(\n        reinterpret_cast<const char*>(&(blob.Get<BlobTestFoo>().val)),\n        sizeof(int32_t)));\n    acceptor(name, blob_proto.SerializeAsString());\n  }\n};\n\nclass BlobTestFooDeserializer : public BlobDeserializerBase {\n public:\n  void Deserialize(const BlobProto& proto, Blob* blob) override {\n    blob->GetMutable<BlobTestFoo>()->val =\n        reinterpret_cast<const int32_t*>(proto.content().c_str())[0];\n  }\n};\n\nREGISTER_BLOB_SERIALIZER((TypeMeta::Id<BlobTestFoo>()), BlobTestFooSerializer);\nREGISTER_BLOB_DESERIALIZER(BlobTestFoo, BlobTestFooDeserializer);\n\nnamespace {\n\nTEST(BlobTest, Blob) {\n  Blob blob;\n\n  int* int_unused CAFFE2_UNUSED = blob.GetMutable<int>();\n  EXPECT_TRUE(blob.IsType<int>());\n  EXPECT_FALSE(blob.IsType<BlobTestFoo>());\n\n  BlobTestFoo* foo_unused CAFFE2_UNUSED = blob.GetMutable<BlobTestFoo>();\n  EXPECT_TRUE(blob.IsType<BlobTestFoo>());\n  EXPECT_FALSE(blob.IsType<int>());\n}\n\nTEST(BlobTest, BlobNewObjectFlag) {\n  Blob blob;\n\n  bool is_new_object = true;\n\n  blob.GetMutable<int>(&is_new_object);\n  EXPECT_TRUE(is_new_object);\n  blob.GetMutable<int>(&is_new_object);\n  EXPECT_FALSE(is_new_object);\n\n  blob.GetMutable<BlobTestFoo>(&is_new_object);\n  EXPECT_TRUE(is_new_object);\n  blob.GetMutable<BlobTestFoo>(&is_new_object);\n  EXPECT_FALSE(is_new_object);\n}\n\nTEST(BlobTest, BlobUninitialized) {\n  Blob blob;\n  ASSERT_THROW(blob.Get<int>(), EnforceNotMet);\n}\n\nTEST(BlobTest, BlobWrongType) {\n  Blob blob;\n  BlobTestFoo* foo_unused CAFFE2_UNUSED = blob.GetMutable<BlobTestFoo>();\n  EXPECT_TRUE(blob.IsType<BlobTestFoo>());\n  EXPECT_FALSE(blob.IsType<int>());\n  // When not null, we should only call with the right type.\n  EXPECT_NE(&blob.Get<BlobTestFoo>(), nullptr);\n  ASSERT_THROW(blob.Get<int>(), EnforceNotMet);\n}\n\nTEST(BlobTest, BlobReset) {\n  Blob blob;\n  std::unique_ptr<BlobTestFoo> foo(new BlobTestFoo());\n  EXPECT_TRUE(blob.Reset(foo.release()) != nullptr);\n  // Also test that Reset works.\n  blob.Reset();\n}\n\nTEST(BlobTest, BlobMove) {\n  Blob blob1;\n  std::unique_ptr<BlobTestFoo> foo(new BlobTestFoo());\n  auto* fooPtr = foo.get();\n  EXPECT_TRUE(blob1.Reset(foo.release()) != nullptr);\n  Blob blob2;\n  blob2 = std::move(blob1);\n  ASSERT_THROW(blob1.Get<BlobTestFoo>(), EnforceNotMet);\n  EXPECT_EQ(&blob2.Get<BlobTestFoo>(), fooPtr);\n  Blob blob3{std::move(blob2)};\n  EXPECT_EQ(&blob3.Get<BlobTestFoo>(), fooPtr);\n}\n\nTEST(BlobTest, BlobShareExternalPointer) {\n  Blob blob;\n  std::unique_ptr<BlobTestFoo> foo(new BlobTestFoo());\n  EXPECT_EQ(blob.ShareExternal<BlobTestFoo>(foo.get()), foo.get());\n  EXPECT_TRUE(blob.IsType<BlobTestFoo>());\n  // Also test that Reset works.\n  blob.Reset();\n}\n\nTEST(BlobTest, BlobShareExternalObject) {\n  Blob blob;\n  BlobTestFoo foo;\n  EXPECT_EQ(blob.ShareExternal<BlobTestFoo>(&foo), &foo);\n  EXPECT_TRUE(blob.IsType<BlobTestFoo>());\n  // Also test that Reset works.\n  blob.Reset();\n}\n\nTEST(BlobTest, StringSerialization) {\n  const std::string kTestString = \"Hello world?\";\n  Blob blob;\n  *blob.GetMutable<std::string>() = kTestString;\n\n  string serialized = blob.Serialize(\"test\");\n  BlobProto proto;\n  CHECK(proto.ParseFromString(serialized));\n  EXPECT_EQ(proto.name(), \"test\");\n  EXPECT_EQ(proto.type(), \"std::string\");\n  EXPECT_FALSE(proto.has_tensor());\n  EXPECT_EQ(proto.content(), kTestString);\n}\n\nTEST(TensorNonTypedTest, TensorChangeType) {\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  TensorCPU tensor(dims);\n\n  auto* ptr = tensor.mutable_data<int>();\n  EXPECT_TRUE(ptr != nullptr);\n  EXPECT_TRUE(tensor.data<int>() != nullptr);\n  EXPECT_TRUE(tensor.meta().Match<int>());\n\n  // int and float are same size, so should retain the pointer\n  EXPECT_TRUE(tensor.mutable_data<float>() == (float*)ptr);\n  EXPECT_TRUE(tensor.data<float>() == (const float*)ptr);\n  EXPECT_TRUE(tensor.meta().Match<float>());\n\n  // float16 is smaller, so still should share buffer\n  EXPECT_TRUE(tensor.mutable_data<float16>() == (float16*)ptr);\n  EXPECT_TRUE(tensor.data<float16>() == (const float16*)ptr);\n  EXPECT_TRUE(tensor.meta().Match<float16>());\n\n  // share the data with other tensor so that the pointer won't be reused\n  // when we reallocate\n  TensorCPU other_tensor(dims);\n  other_tensor.ShareData(tensor);\n  // but double is bigger, so it should allocate a new one\n  auto* doubleptr = tensor.mutable_data<double>();\n  EXPECT_TRUE(doubleptr != (double*)ptr);\n  EXPECT_TRUE(doubleptr != nullptr);\n  EXPECT_TRUE(tensor.data<double>() != nullptr);\n  EXPECT_TRUE(tensor.meta().Match<double>());\n}\n\ntemplate <typename T> class TensorCPUTest : public ::testing::Test {};\ntemplate <typename T> class TensorCPUDeathTest : public ::testing::Test {};\ntypedef ::testing::Types<char, int, float> TensorTypes;\nTYPED_TEST_CASE(TensorCPUTest, TensorTypes);\nTYPED_TEST_CASE(TensorCPUDeathTest, TensorTypes);\n\nTYPED_TEST(TensorCPUTest, TensorInitializedEmpty) {\n  TensorCPU tensor;\n  EXPECT_EQ(tensor.ndim(), 0);\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  tensor.Resize(dims);\n  EXPECT_EQ(tensor.ndim(), 3);\n  EXPECT_EQ(tensor.dim32(0), 2);\n  EXPECT_EQ(tensor.dim32(1), 3);\n  EXPECT_EQ(tensor.dim32(2), 5);\n  EXPECT_EQ(tensor.size(), 2 * 3 * 5);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  EXPECT_TRUE(tensor.data<TypeParam>() != nullptr);\n}\n\nTYPED_TEST(TensorCPUTest, TensorInitializedNonEmpty) {\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  TensorCPU tensor(dims);\n  EXPECT_EQ(tensor.ndim(), 3);\n  EXPECT_EQ(tensor.dim32(0), 2);\n  EXPECT_EQ(tensor.dim32(1), 3);\n  EXPECT_EQ(tensor.dim32(2), 5);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  EXPECT_TRUE(tensor.data<TypeParam>() != nullptr);\n  dims[0] = 7;\n  dims[1] = 11;\n  dims[2] = 13;\n  dims.push_back(17);\n  tensor.Resize(dims);\n  EXPECT_EQ(tensor.ndim(), 4);\n  EXPECT_EQ(tensor.dim32(0), 7);\n  EXPECT_EQ(tensor.dim32(1), 11);\n  EXPECT_EQ(tensor.dim32(2), 13);\n  EXPECT_EQ(tensor.dim32(3), 17);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  EXPECT_TRUE(tensor.data<TypeParam>() != nullptr);\n}\n\nTYPED_TEST(TensorCPUTest, TensorInitializedZeroDim) {\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 0;\n  dims[2] = 5;\n  TensorCPU tensor(dims);\n  EXPECT_EQ(tensor.ndim(), 3);\n  EXPECT_EQ(tensor.dim32(0), 2);\n  EXPECT_EQ(tensor.dim32(1), 0);\n  EXPECT_EQ(tensor.dim32(2), 5);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() == nullptr);\n  EXPECT_TRUE(tensor.data<TypeParam>() == nullptr);\n}\n\nTYPED_TEST(TensorCPUTest, TensorResizeZeroDim) {\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  TensorCPU tensor(dims);\n  EXPECT_EQ(tensor.ndim(), 3);\n  EXPECT_EQ(tensor.dim32(0), 2);\n  EXPECT_EQ(tensor.dim32(1), 3);\n  EXPECT_EQ(tensor.dim32(2), 5);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  EXPECT_TRUE(tensor.data<TypeParam>() != nullptr);\n\n  dims[0] = 7;\n  dims[1] = 0;\n  dims[2] = 13;\n  tensor.Resize(dims);\n  EXPECT_EQ(tensor.size(), 0);\n  EXPECT_EQ(tensor.ndim(), 3);\n  EXPECT_EQ(tensor.dim32(0), 7);\n  EXPECT_EQ(tensor.dim32(1), 0);\n  EXPECT_EQ(tensor.dim32(2), 13);\n  // output value can be arbitrary, but the call to data() shouldn't crash\n  tensor.mutable_data<TypeParam>();\n  tensor.data<TypeParam>();\n}\n\nTYPED_TEST(TensorCPUTest, TensorInitializedScalar) {\n  vector<int> dims;\n  TensorCPU tensor(dims);\n  EXPECT_EQ(tensor.ndim(), 0);\n  EXPECT_EQ(tensor.size(), 1);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  EXPECT_TRUE(tensor.data<TypeParam>() != nullptr);\n}\n\nTYPED_TEST(TensorCPUTest, TensorShareData) {\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  TensorCPU tensor(dims);\n  TensorCPU other_tensor(dims);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  other_tensor.ShareData(tensor);\n  EXPECT_TRUE(tensor.data<TypeParam>() != nullptr);\n  EXPECT_TRUE(other_tensor.data<TypeParam>() != nullptr);\n  EXPECT_EQ(tensor.data<TypeParam>(), other_tensor.data<TypeParam>());\n  // Set one value, check the other\n  for (int i = 0; i < tensor.size(); ++i) {\n    tensor.mutable_data<TypeParam>()[i] = i;\n    EXPECT_EQ(other_tensor.data<TypeParam>()[i], i);\n  }\n}\n\nTYPED_TEST(TensorCPUTest, TensorShareDataRawPointer) {\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  std::unique_ptr<TypeParam[]> raw_buffer(new TypeParam[2*3*5]);\n  TensorCPU tensor(dims);\n  tensor.ShareExternalPointer(raw_buffer.get());\n  EXPECT_EQ(tensor.mutable_data<TypeParam>(), raw_buffer.get());\n  EXPECT_EQ(tensor.data<TypeParam>(), raw_buffer.get());\n  // Set one value, check the other\n  for (int i = 0; i < tensor.size(); ++i) {\n    raw_buffer.get()[i] = i;\n    EXPECT_EQ(tensor.data<TypeParam>()[i], i);\n  }\n}\n\nTYPED_TEST(TensorCPUTest, TensorShareDataRawPointerWithMeta) {\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  std::unique_ptr<TypeParam[]> raw_buffer(new TypeParam[2 * 3 * 5]);\n  TensorCPU tensor(dims);\n  TypeMeta meta = TypeMeta::Make<TypeParam>();\n  tensor.ShareExternalPointer(raw_buffer.get(), meta);\n  EXPECT_EQ(tensor.mutable_data<TypeParam>(), raw_buffer.get());\n  EXPECT_EQ(tensor.data<TypeParam>(), raw_buffer.get());\n  // Set one value, check the other\n  for (int i = 0; i < tensor.size(); ++i) {\n    raw_buffer.get()[i] = i;\n    EXPECT_EQ(tensor.data<TypeParam>()[i], i);\n  }\n}\n\nTYPED_TEST(TensorCPUTest, CannotShareDataWhenShapeNotSet) {\n  std::unique_ptr<TypeParam[]> raw_buffer(new TypeParam[10]);\n  TensorCPU tensor;\n  ASSERT_THROW(tensor.ShareExternalPointer(raw_buffer.get()), EnforceNotMet);\n}\n\nTYPED_TEST(TensorCPUTest, TensorShareDataCanUseDifferentShapes) {\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  vector<int> alternate_dims(1);\n  alternate_dims[0] = 2 * 3 * 5;\n  TensorCPU tensor(dims);\n  TensorCPU other_tensor(alternate_dims);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  other_tensor.ShareData(tensor);\n  EXPECT_EQ(other_tensor.ndim(), 1);\n  EXPECT_EQ(other_tensor.dim32(0), alternate_dims[0]);\n  EXPECT_TRUE(tensor.data<TypeParam>() != nullptr);\n  EXPECT_TRUE(other_tensor.data<TypeParam>() != nullptr);\n  EXPECT_EQ(tensor.data<TypeParam>(), other_tensor.data<TypeParam>());\n  // Set one value, check the other\n  for (int i = 0; i < tensor.size(); ++i) {\n    tensor.mutable_data<TypeParam>()[i] = i;\n    EXPECT_EQ(other_tensor.data<TypeParam>()[i], i);\n  }\n}\n\n\nTYPED_TEST(TensorCPUTest, NoLongerSharesAfterResize) {\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  TensorCPU tensor(dims);\n  TensorCPU other_tensor(dims);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  other_tensor.ShareData(tensor);\n  EXPECT_EQ(tensor.data<TypeParam>(), other_tensor.data<TypeParam>());\n  auto* old_pointer = other_tensor.data<TypeParam>();\n\n  dims[0] = 7;\n  tensor.Resize(dims);\n  EXPECT_EQ(old_pointer, other_tensor.data<TypeParam>());\n  EXPECT_NE(old_pointer, tensor.mutable_data<TypeParam>());\n}\n\nTYPED_TEST(TensorCPUTest, NoLongerSharesAfterFreeMemory) {\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  TensorCPU tensor(dims);\n  TensorCPU other_tensor(dims);\n  EXPECT_TRUE(tensor.mutable_data<TypeParam>() != nullptr);\n  other_tensor.ShareData(tensor);\n  EXPECT_EQ(tensor.data<TypeParam>(), other_tensor.data<TypeParam>());\n  auto* old_pointer = other_tensor.data<TypeParam>();\n\n  tensor.FreeMemory();\n  EXPECT_EQ(old_pointer, other_tensor.data<TypeParam>());\n  EXPECT_NE(old_pointer, tensor.mutable_data<TypeParam>());\n}\n\nTYPED_TEST(TensorCPUTest, KeepOnShrink) {\n  // Set flags (defaults)\n  FLAGS_caffe2_keep_on_shrink = true;\n  FLAGS_caffe2_max_keep_on_shrink_memory = LLONG_MAX;\n\n  vector<int> dims{2, 3, 5};\n  TensorCPU tensor(dims);\n  TypeParam* ptr = tensor.mutable_data<TypeParam>();\n  EXPECT_TRUE(ptr != nullptr);\n  // Expanding - will reallocate\n  tensor.Resize(3, 4, 6);\n  TypeParam* larger_ptr = tensor.mutable_data<TypeParam>();\n  EXPECT_TRUE(larger_ptr != nullptr);\n\n  // This check can fail when malloc() returns the same recently freed address\n  //EXPECT_NE(ptr, larger_ptr);\n\n  // Shrinking - will not reallocate\n  tensor.Resize(1, 2, 4);\n  TypeParam* smaller_ptr = tensor.mutable_data<TypeParam>();\n  EXPECT_TRUE(smaller_ptr != nullptr);\n  EXPECT_EQ(larger_ptr, smaller_ptr);\n  // resize to 0 in the meantime;\n  tensor.Resize(3, 0, 6);\n  // Expanding but still under capacity - will not reallocate\n  tensor.Resize(2, 3, 5);\n  TypeParam* new_ptr = tensor.mutable_data<TypeParam>();\n  EXPECT_TRUE(new_ptr != nullptr);\n  EXPECT_EQ(larger_ptr, new_ptr);\n}\n\nTYPED_TEST(TensorCPUTest, MaxKeepOnShrink) {\n  // Set flags\n  FLAGS_caffe2_keep_on_shrink = true;\n  FLAGS_caffe2_max_keep_on_shrink_memory = 8 * 4 * sizeof(TypeParam);\n\n  vector<int> dims{1, 8, 8};\n  TensorCPU tensor(dims);\n  TypeParam* ptr = tensor.mutable_data<TypeParam>();\n  EXPECT_TRUE(ptr != nullptr);\n  // Shrinking - will not reallocate\n  tensor.Resize(1, 7, 8);\n  TypeParam* smaller_ptr = tensor.mutable_data<TypeParam>();\n  EXPECT_TRUE(smaller_ptr != nullptr);\n  EXPECT_EQ(ptr, smaller_ptr);\n  // Resize to more than maximum shrink, should reallocate\n  tensor.Resize(1, 1, 8);\n  TypeParam* new_ptr = tensor.mutable_data<TypeParam>();\n  EXPECT_TRUE(new_ptr != nullptr);\n\n  // This check can fail when malloc() returns the same recently freed address\n  //EXPECT_NE(ptr, new_ptr);\n\n  // Restore default flags\n  FLAGS_caffe2_max_keep_on_shrink_memory = LLONG_MAX;\n}\n\nTYPED_TEST(TensorCPUDeathTest, CannotAccessRawDataWhenEmpty) {\n  TensorCPU tensor;\n  EXPECT_EQ(tensor.ndim(), 0);\n  ASSERT_ANY_THROW(tensor.raw_data());\n}\n\nTYPED_TEST(TensorCPUDeathTest, CannotAccessDataWhenEmpty) {\n  TensorCPU tensor;\n  EXPECT_EQ(tensor.ndim(), 0);\n  ASSERT_ANY_THROW(tensor.data<TypeParam>());\n}\n\nTEST(TensorTest, TensorNonFundamentalType) {\n  TensorCPU tensor(vector<int>{2, 3, 4});\n  EXPECT_TRUE(tensor.mutable_data<std::string>() != nullptr);\n  const std::string* ptr = tensor.data<std::string>();\n  for (int i = 0; i < tensor.size(); ++i) {\n    EXPECT_TRUE(ptr[i] == \"\");\n  }\n}\n\nTEST(TensorTest, TensorNonFundamentalTypeCopy) {\n  TensorCPU tensor(vector<int>{2, 3, 4});\n  std::string* ptr = tensor.mutable_data<std::string>();\n  EXPECT_TRUE(ptr != nullptr);\n  for (int i = 0; i < tensor.size(); ++i) {\n    EXPECT_TRUE(ptr[i] == \"\");\n    ptr[i] = \"filled\";\n  }\n  TensorCPU dst_tensor(tensor);\n  const std::string* dst_ptr = dst_tensor.data<std::string>();\n  for (int i = 0; i < dst_tensor.size(); ++i) {\n    EXPECT_TRUE(dst_ptr[i] == \"filled\");\n  }\n}\n\nTEST(TensorTest, Tensor64BitDimension) {\n  // Initialize a large tensor.\n  TIndex large_number =\n      static_cast<int64_t>(std::numeric_limits<int>::max()) + 1;\n  TensorCPU tensor(vector<TIndex>{large_number});\n  EXPECT_EQ(tensor.ndim(), 1);\n  EXPECT_EQ(tensor.dim(0), large_number);\n  EXPECT_EQ(tensor.size(), large_number);\n  try {\n    EXPECT_TRUE(tensor.mutable_data<char>() != nullptr);\n  } catch (const EnforceNotMet& e) {\n    string msg = e.what();\n    size_t found = msg.find(\"posix_memalign\");\n    if (found != string::npos) {\n      msg = msg.substr(0, msg.find('\\n'));\n      LOG(WARNING) << msg;\n      LOG(WARNING) << \"Out of memory issue with posix_memalign;\\n\";\n      return;\n    } else {\n      throw e;\n    }\n  }\n  EXPECT_EQ(tensor.nbytes(), large_number * sizeof(char));\n  EXPECT_EQ(tensor.itemsize(), sizeof(char));\n  // Try to go even larger, but this time we will not do mutable_data because we\n  // do not have a large enough memory.\n  tensor.Resize(large_number, 100);\n  EXPECT_EQ(tensor.ndim(), 2);\n  EXPECT_EQ(tensor.dim(0), large_number);\n  EXPECT_EQ(tensor.dim(1), 100);\n  EXPECT_EQ(tensor.size(), large_number * 100);\n}\n\nTEST(TensorDeathTest, CannotCastDownLargeDims) {\n  TIndex large_number =\n      static_cast<int64_t>(std::numeric_limits<int>::max()) + 1;\n  TensorCPU tensor(vector<TIndex>{large_number});\n  EXPECT_EQ(tensor.ndim(), 1);\n  EXPECT_EQ(tensor.dim(0), large_number);\n  ASSERT_THROW(tensor.dim32(0), EnforceNotMet);\n}\n\n#define TEST_SERIALIZATION_WITH_TYPE(TypeParam, field_name)               \\\n  TEST(TensorTest, TensorSerialization_##TypeParam) {                     \\\n    Blob blob;                                                            \\\n    TensorCPU* tensor = blob.GetMutable<TensorCPU>();                     \\\n    tensor->Resize(2, 3);                                                 \\\n    for (int i = 0; i < 6; ++i) {                                         \\\n      tensor->mutable_data<TypeParam>()[i] = static_cast<TypeParam>(i);   \\\n    }                                                                     \\\n    string serialized = blob.Serialize(\"test\");                           \\\n    BlobProto proto;                                                      \\\n    CHECK(proto.ParseFromString(serialized));                             \\\n    EXPECT_EQ(proto.name(), \"test\");                                      \\\n    EXPECT_EQ(proto.type(), \"Tensor\");                                    \\\n    EXPECT_TRUE(proto.has_tensor());                                      \\\n    const TensorProto& tensor_proto = proto.tensor();                     \\\n    EXPECT_EQ(                                                            \\\n        tensor_proto.data_type(),                                         \\\n        TypeMetaToDataType(TypeMeta::Make<TypeParam>()));                 \\\n    EXPECT_EQ(tensor_proto.field_name##_size(), 6);                       \\\n    for (int i = 0; i < 6; ++i) {                                         \\\n      EXPECT_EQ(tensor_proto.field_name(i), static_cast<TypeParam>(i));   \\\n    }                                                                     \\\n    Blob new_blob;                                                        \\\n    EXPECT_NO_THROW(new_blob.Deserialize(serialized));                    \\\n    EXPECT_TRUE(new_blob.IsType<TensorCPU>());                            \\\n    const TensorCPU& new_tensor = blob.Get<TensorCPU>();                  \\\n    EXPECT_EQ(new_tensor.ndim(), 2);                                      \\\n    EXPECT_EQ(new_tensor.dim(0), 2);                                      \\\n    EXPECT_EQ(new_tensor.dim(1), 3);                                      \\\n    for (int i = 0; i < 6; ++i) {                                         \\\n      EXPECT_EQ(                                                          \\\n          tensor->data<TypeParam>()[i], new_tensor.data<TypeParam>()[i]); \\\n    }                                                                     \\\n  }                                                                       \\\n                                                                          \\\n  TEST(EmptyTensorTest, TensorSerialization_##TypeParam) {                \\\n    Blob blob;                                                            \\\n    TensorCPU* tensor = blob.GetMutable<TensorCPU>();                     \\\n    tensor->Resize(0, 3);                                                 \\\n    tensor->mutable_data<TypeParam>();                                    \\\n    string serialized = blob.Serialize(\"test\");                           \\\n    BlobProto proto;                                                      \\\n    CHECK(proto.ParseFromString(serialized));                             \\\n    EXPECT_EQ(proto.name(), \"test\");                                      \\\n    EXPECT_EQ(proto.type(), \"Tensor\");                                    \\\n    EXPECT_TRUE(proto.has_tensor());                                      \\\n    const TensorProto& tensor_proto = proto.tensor();                     \\\n    EXPECT_EQ(                                                            \\\n        tensor_proto.data_type(),                                         \\\n        TypeMetaToDataType(TypeMeta::Make<TypeParam>()));                 \\\n    EXPECT_EQ(tensor_proto.field_name##_size(), 0);                       \\\n    Blob new_blob;                                                        \\\n    EXPECT_NO_THROW(new_blob.Deserialize(serialized));                    \\\n    EXPECT_TRUE(new_blob.IsType<TensorCPU>());                            \\\n    const TensorCPU& new_tensor = blob.Get<TensorCPU>();                  \\\n    EXPECT_EQ(new_tensor.ndim(), 2);                                      \\\n    EXPECT_EQ(new_tensor.dim(0), 0);                                      \\\n    EXPECT_EQ(new_tensor.dim(1), 3);                                      \\\n  }\n\nTEST_SERIALIZATION_WITH_TYPE(bool, int32_data)\nTEST_SERIALIZATION_WITH_TYPE(double, double_data)\nTEST_SERIALIZATION_WITH_TYPE(float, float_data)\nTEST_SERIALIZATION_WITH_TYPE(int, int32_data)\nTEST_SERIALIZATION_WITH_TYPE(int8_t, int32_data)\nTEST_SERIALIZATION_WITH_TYPE(int16_t, int32_data)\nTEST_SERIALIZATION_WITH_TYPE(uint8_t, int32_data)\nTEST_SERIALIZATION_WITH_TYPE(uint16_t, int32_data)\nTEST_SERIALIZATION_WITH_TYPE(int64_t, int64_data)\n\nTEST(TensorTest, TensorSerialization_CustomType) {\n  Blob blob;\n  TensorCPU* tensor = blob.GetMutable<TensorCPU>();\n  tensor->Resize(2, 3);\n  for (int i = 0; i < 6; ++i) {\n    tensor->mutable_data<BlobTestFoo>()[i].val = i;\n  }\n  string serialized = blob.Serialize(\"test\");\n  BlobProto proto;\n  CHECK(proto.ParseFromString(serialized));\n  EXPECT_EQ(proto.name(), \"test\");\n  EXPECT_EQ(proto.type(), \"Tensor\");\n  Blob new_blob;\n  EXPECT_NO_THROW(new_blob.Deserialize(serialized));\n  EXPECT_TRUE(new_blob.IsType<TensorCPU>());\n  const TensorCPU& new_tensor = blob.Get<TensorCPU>();\n  EXPECT_EQ(new_tensor.ndim(), 2);\n  EXPECT_EQ(new_tensor.dim(0), 2);\n  EXPECT_EQ(new_tensor.dim(1), 3);\n  for (int i = 0; i < 6; ++i) {\n    EXPECT_EQ(\n        new_tensor.data<BlobTestFoo>()[i].val,\n        tensor->data<BlobTestFoo>()[i].val);\n  }\n}\n\nTEST(TensorTest, float16) {\n  const TIndex kSize = 3000000;\n  Blob blob;\n  TensorCPU* tensor = blob.GetMutable<TensorCPU>();\n  tensor->Resize(kSize);\n  for (int i = 0; i < tensor->size(); ++i) {\n    tensor->mutable_data<float16>()[i].x = i % 10000;\n  }\n  string serialized = blob.Serialize(\"test\");\n  BlobProto proto;\n  CHECK(proto.ParseFromString(serialized));\n  EXPECT_EQ(proto.name(), \"test\");\n  EXPECT_EQ(proto.type(), \"Tensor\");\n  EXPECT_TRUE(proto.has_tensor());\n  const TensorProto& tensor_proto = proto.tensor();\n  EXPECT_EQ(\n      tensor_proto.data_type(), TypeMetaToDataType(TypeMeta::Make<float16>()));\n  if (FLAGS_caffe2_serialize_fp16_as_bytes) {\n    EXPECT_EQ(tensor_proto.byte_data().size(), 2 * kSize);\n    for (int i = 0; i < kSize; ++i) {\n      auto value = tensor->mutable_data<float16>()[i].x;\n      auto low_bits = static_cast<char>(value & 0xff);\n      auto high_bits = static_cast<char>(value >> 8);\n      EXPECT_EQ(tensor_proto.byte_data()[2 * i], low_bits);\n      EXPECT_EQ(tensor_proto.byte_data()[2 * i + 1], high_bits);\n    }\n  } else {\n    EXPECT_EQ(tensor_proto.int32_data().size(), kSize);\n  }\n  Blob new_blob;\n  EXPECT_NO_THROW(new_blob.Deserialize(serialized));\n  EXPECT_TRUE(new_blob.IsType<TensorCPU>());\n  const TensorCPU& new_tensor = blob.Get<TensorCPU>();\n  EXPECT_EQ(new_tensor.ndim(), 1);\n  EXPECT_EQ(new_tensor.dim(0), kSize);\n  for (int i = 0; i < kSize; ++i) {\n    EXPECT_EQ(new_tensor.data<float16>()[i].x, i % 10000);\n  }\n}\n\nTEST(QTensorTest, QTensorSerialization) {\n  Blob blob;\n  QTensor<CPUContext>* qtensor = blob.GetMutable<QTensor<CPUContext>>();\n  qtensor->SetPrecision(5);\n  qtensor->SetSigned(false);\n  qtensor->SetScale(1.337);\n  qtensor->SetBias(-1.337);\n  qtensor->Resize(std::vector<int>{2, 3});\n  // \"Randomly\" set bits.\n  srand(0);\n  for (int i = 0; i < 6; ++i) {\n    for (int j = 0; j < 5; ++j) {\n      qtensor->SetBitAtIndex(j, i, rand() % 2);\n    }\n  }\n\n  string serialized = blob.Serialize(\"test\");\n  BlobProto proto;\n  CHECK(proto.ParseFromString(serialized));\n  EXPECT_EQ(proto.name(), \"test\");\n  EXPECT_EQ(proto.type(), \"QTensor\");\n  EXPECT_TRUE(proto.has_qtensor());\n  const QTensorProto& qtensor_proto = proto.qtensor();\n\n  EXPECT_EQ(qtensor_proto.precision(), qtensor->precision());\n  EXPECT_EQ(qtensor_proto.scale(), qtensor->scale());\n  EXPECT_EQ(qtensor_proto.bias(), qtensor->bias());\n  EXPECT_EQ(qtensor_proto.is_signed(), qtensor->is_signed());\n\n  Blob new_blob;\n  new_blob.Deserialize(serialized);\n  EXPECT_TRUE(new_blob.IsType<QTensor<CPUContext>>());\n  const QTensor<CPUContext>& new_qtensor = blob.Get<QTensor<CPUContext>>();\n  EXPECT_EQ(new_qtensor.ndim(), 2);\n  EXPECT_EQ(new_qtensor.dim32(0), 2);\n  EXPECT_EQ(new_qtensor.dim32(1), 3);\n  for (int i = 0; i < 6; ++i) {\n    for (int j = 0; j < 5; ++j) {\n      EXPECT_EQ(qtensor->GetBitAtIndex(j, i), new_qtensor.GetBitAtIndex(j, i));\n    }\n  }\n}\n\nusing StringMap = std::vector<std::pair<string, string>>;\n\nclass VectorCursor : public db::Cursor {\n public:\n  explicit VectorCursor(StringMap* data) : data_(data) {\n    pos_ = 0;\n  }\n  ~VectorCursor() {}\n  void Seek(const string& /* unused */) override {}\n  void SeekToFirst() override {}\n  void Next() override {\n    ++pos_;\n  }\n  string key() override {\n    return (*data_)[pos_].first;\n  }\n  string value() override {\n    return (*data_)[pos_].second;\n  }\n  bool Valid() override {\n    return pos_ < data_->size();\n  }\n\n private:\n  StringMap* data_ = nullptr;\n  size_t pos_ = 0;\n};\n\nclass VectorDB : public db::DB {\n public:\n  VectorDB(const string& source, db::Mode mode)\n      : DB(source, mode), name_(source) {}\n  ~VectorDB() {\n    data_.erase(name_);\n  }\n  void Close() override {}\n  std::unique_ptr<db::Cursor> NewCursor() override {\n    return make_unique<VectorCursor>(getData());\n  }\n  std::unique_ptr<db::Transaction> NewTransaction() override {\n    CAFFE_THROW(\"Not implemented\");\n  }\n  static void registerData(const string& name, StringMap&& data) {\n    std::lock_guard<std::mutex> guard(dataRegistryMutex_);\n    data_[name] = std::move(data);\n  }\n\n private:\n  StringMap* getData() {\n    auto it = data_.find(name_);\n    CAFFE_ENFORCE(it != data_.end(), \"Can't find \", name_);\n    return &(it->second);\n  }\n\n private:\n  string name_;\n  static std::mutex dataRegistryMutex_;\n  static std::map<string, StringMap> data_;\n};\n\nstd::mutex VectorDB::dataRegistryMutex_;\nstd::map<string, StringMap> VectorDB::data_;\n\nREGISTER_CAFFE2_DB(vector_db, VectorDB);\n\ntemplate <typename TypeParam>\nclass TypedTensorTest : public ::testing::Test {};\ntypedef ::testing::\n    Types<float, bool, double, int, int8_t, int16_t, uint8_t, uint16_t, int64_t>\n        TensorDataTypes;\nTYPED_TEST_CASE(TypedTensorTest, TensorDataTypes);\n\nTYPED_TEST(TypedTensorTest, BigTensorSerialization) {\n  int64_t d1 = 2;\n  int64_t d2 = FLAGS_caffe2_test_big_tensor_size\n      ? FLAGS_caffe2_test_big_tensor_size / d1\n      : static_cast<int64_t>(std::numeric_limits<int>::max()) + 1;\n  int64_t size = d1 * d2;\n  string db_source = (string)std::tmpnam(nullptr);\n  VLOG(1) << \"db_source: \" << db_source;\n\n  {\n    VLOG(1) << \"Test begin\";\n    Blob blob;\n    TensorCPU* tensor = blob.GetMutable<TensorCPU>();\n    VLOG(1) << \"Allocating blob\";\n    tensor->Resize(d1, d2);\n    auto mutableData = tensor->mutable_data<TypeParam>();\n    VLOG(1) << \"Filling out the blob\";\n    for (int64_t i = 0; i < size; ++i) {\n      mutableData[i] = static_cast<TypeParam>(i);\n    }\n    StringMap data;\n    std::mutex mutex;\n    /*auto db = CreateDB(\"minidb\", db_source, WRITE);*/\n    auto acceptor = [&](const std::string& key, const std::string& value) {\n      std::lock_guard<std::mutex> guard(mutex);\n      /*db->NewTransaction()->Put(key, value);*/\n      data.emplace_back(key, value);\n    };\n    blob.Serialize(\"test\", acceptor);\n    VectorDB::registerData(db_source, std::move(data));\n    VLOG(1) << \"finished writing to DB\";\n  }\n\n  {\n    DeviceOption option;\n    option.set_device_type(CPU);\n    Argument db_type_arg = MakeArgument<string>(\"db_type\", \"vector_db\");\n    Argument absolute_path_arg = MakeArgument<bool>(\"absolute_path\", true);\n    Argument db_source_arg = MakeArgument<string>(\"db\", db_source);\n    auto op_def = CreateOperatorDef(\n        \"Load\",\n        \"\",\n        std::vector<string>{},\n        std::vector<string>({\"test\"}),\n        std::vector<Argument>{db_type_arg, db_source_arg, absolute_path_arg},\n        option,\n        \"DUMMY_ENGINE\");\n    Workspace ws;\n    auto load_op = CreateOperator(op_def, &ws);\n    EXPECT_TRUE(load_op != nullptr);\n    VLOG(1) << \"Running operator\";\n\n    load_op->Run();\n    VLOG(1) << \"Reading blob from workspace\";\n    auto new_blob = ws.GetBlob(\"test\");\n    EXPECT_TRUE(new_blob->IsType<TensorCPU>());\n    const auto& new_tensor = new_blob->Get<TensorCPU>();\n\n    EXPECT_EQ(new_tensor.ndim(), d1);\n    EXPECT_EQ(new_tensor.dim(0), d1);\n    EXPECT_EQ(new_tensor.dim(1), d2);\n    for (int64_t i = 0; i < size; ++i) {\n      EXPECT_EQ(static_cast<TypeParam>(i), new_tensor.data<TypeParam>()[i]);\n    }\n  }\n}\n\nstruct DummyType {\n  /* This struct is used to test serialization and deserialization of huge\n   * blobs, that are not tensors.\n   */\n\n  /* implicit */ DummyType(int n_chunks_init = 0) : n_chunks(n_chunks_init) {}\n  std::string serialize(const std::string& name, const int32_t chunk_id) const {\n    BlobProto blobProto;\n    blobProto.set_name(name);\n    blobProto.set_type(\"DummyType\");\n    std::string content(\"\");\n    blobProto.set_content(content);\n    blobProto.set_content_num_chunks(n_chunks);\n    blobProto.set_content_chunk_id(chunk_id);\n    return blobProto.SerializeAsString();\n  }\n  void deserialize(const BlobProto& /* unused */) {\n    ++n_chunks;\n  }\n  int n_chunks;\n};\n\nclass DummyTypeSerializer : public BlobSerializerBase {\n public:\n  DummyTypeSerializer() {}\n  ~DummyTypeSerializer() {}\n  void Serialize(\n      const Blob& blob,\n      const string& name,\n      SerializationAcceptor acceptor) override {\n    CAFFE_ENFORCE(blob.IsType<DummyType>());\n    const auto& container = blob.template Get<DummyType>();\n    for (int k = 0; k < container.n_chunks; ++k) {\n      std::string serialized_chunk = container.serialize(name, k);\n      acceptor(MakeString(name, kChunkIdSeparator, k), serialized_chunk);\n    }\n  }\n};\n\nclass DummyTypeDeserializer : public BlobDeserializerBase {\n public:\n  void Deserialize(const BlobProto& proto, Blob* blob) override {\n    auto* container = blob->GetMutable<DummyType>();\n    container->deserialize(proto);\n  }\n};\n}\n\nCAFFE_KNOWN_TYPE(DummyType);\n\nnamespace {\nREGISTER_BLOB_SERIALIZER((TypeMeta::Id<DummyType>()), DummyTypeSerializer);\nCAFFE_REGISTER_TYPED_CLASS(\n    BlobDeserializerRegistry,\n    \"DummyType\",\n    DummyTypeDeserializer);\n\nTEST(ContentChunks, Serialization) {\n  string db_source = (string)std::tmpnam(nullptr);\n  VLOG(1) << \"db_source: \" << db_source;\n\n  {\n    VLOG(1) << \"Test begin\";\n    Blob blob;\n    DummyType* container = blob.GetMutable<DummyType>();\n    VLOG(1) << \"Allocating blob\";\n    container->n_chunks = 10;\n    VLOG(1) << \"Filling out the blob\";\n    StringMap data;\n    std::mutex mutex;\n    auto acceptor = [&](const std::string& key, const std::string& value) {\n      std::lock_guard<std::mutex> guard(mutex);\n      data.emplace_back(key, value);\n    };\n    blob.Serialize(\"test\", acceptor);\n    VectorDB::registerData(db_source, std::move(data));\n    VLOG(1) << \"finished writing to DB\";\n  }\n\n  {\n    DeviceOption option;\n    option.set_device_type(CPU);\n    Argument db_type_arg = MakeArgument<string>(\"db_type\", \"vector_db\");\n    Argument absolute_path_arg = MakeArgument<bool>(\"absolute_path\", true);\n    Argument db_source_arg = MakeArgument<string>(\"db\", db_source);\n    auto op_def = CreateOperatorDef(\n        \"Load\",\n        \"\",\n        std::vector<string>{},\n        std::vector<string>({\"test\"}),\n        std::vector<Argument>{db_type_arg, db_source_arg, absolute_path_arg},\n        option,\n        \"DUMMY_ENGINE\");\n    Workspace ws;\n    auto load_op = CreateOperator(op_def, &ws);\n    EXPECT_TRUE(load_op != nullptr);\n    VLOG(1) << \"Running operator\";\n\n    load_op->Run();\n    VLOG(1) << \"Reading blob from workspace\";\n    auto new_blob = ws.GetBlob(\"test\");\n    EXPECT_TRUE(new_blob->IsType<DummyType>());\n    const auto& container = new_blob->Get<DummyType>();\n    EXPECT_EQ(container.n_chunks, 10);\n  }\n}\n\nTEST(CustomChunkSize, BigTensorSerialization) {\n  int64_t d1 = 2;\n  int64_t d2 = FLAGS_caffe2_test_big_tensor_size\n      ? FLAGS_caffe2_test_big_tensor_size / d1\n      : static_cast<int64_t>(std::numeric_limits<int>::max()) + 1;\n  int64_t size = d1 * d2;\n\n  Blob blob;\n  TensorCPU* tensor = blob.GetMutable<TensorCPU>();\n  tensor->Resize(d1, d2);\n  tensor->mutable_data<float>();\n  std::mutex mutex;\n  int counter = 0;\n  auto acceptor = [&](const std::string& /*key*/,\n                      const std::string& /*value*/) {\n    std::lock_guard<std::mutex> guard(mutex);\n    counter++;\n  };\n  blob.Serialize(\"test\", acceptor, size);\n  EXPECT_EQ(counter, 1);\n\n  counter = 0;\n  blob.Serialize(\"test\", acceptor, (size / 2) + 1);\n  EXPECT_EQ(counter, 2);\n\n  counter = 0;\n  blob.Serialize(\"test\", acceptor, kNoChunking);\n  EXPECT_EQ(counter, 1);\n}\n\nTEST(QTensor, QTensorSizingTest) {\n  vector<int> dims(3);\n  dims[0] = 2;\n  dims[1] = 3;\n  dims[2] = 5;\n  QTensor<CPUContext> qtensor(dims, 3);\n  EXPECT_TRUE(qtensor.mutable_data() != nullptr);\n  EXPECT_EQ(qtensor.nbytes(), 12);\n  EXPECT_EQ(qtensor.size(), 30);\n}\n\nTEST(BlobTest, CastingMessage) {\n  Blob b;\n  b.GetMutable<BlobTestFoo>();\n  b.Get<BlobTestFoo>();\n  try {\n    b.Get<BlobTestBar>();\n    FAIL() << \"Should have thrown\";\n  } catch (const EnforceNotMet& e) {\n    string msg = e.what();\n    msg = msg.substr(0, msg.find('\\n'));\n    LOG(INFO) << msg;\n    EXPECT_NE(msg.find(\"BlobTestFoo\"), std::string::npos) << msg;\n    EXPECT_NE(msg.find(\"BlobTestBar\"), std::string::npos) << msg;\n  }\n}\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/common.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <atomic>\n\n#include \"caffe2/core/common.h\"\n\nnamespace caffe2 {\n\n// A global variable to mark if Caffe2 has cuda linked to the current runtime.\n// Do not directly use this variable, but instead use the HasCudaRuntime()\n// function below.\nstd::atomic<bool> g_caffe2_has_cuda_linked{false};\n\nbool HasCudaRuntime() {\n  return g_caffe2_has_cuda_linked.load();\n}\n\nnamespace internal {\nvoid SetCudaRuntimeFlag() {\n  g_caffe2_has_cuda_linked.store(true);\n}\n} // namespace internal\n\nconst std::map<string, string>& GetBuildOptions() {\n#ifndef CAFFE2_BUILD_STRINGS\n#define CAFFE2_BUILD_STRINGS {}\n#endif\n  static const std::map<string, string> kMap = CAFFE2_BUILD_STRINGS;\n  return kMap;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/common.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_COMMON_H_\n#define CAFFE2_CORE_COMMON_H_\n\n#include <algorithm>\n#include <map>\n#include <memory>\n#include <numeric>\n#include <set>\n#include <sstream>\n#include <string>\n#include <type_traits>\n#include <vector>\n\n#ifdef __APPLE__\n#include <TargetConditionals.h>\n#endif\n\n#if defined(_MSC_VER)\n#include <io.h>\n#else\n#include <unistd.h>\n#endif\n\n// Macros used during the build of this caffe2 instance. This header file\n// is automatically generated by the cmake script during build.\n#include \"caffe2/core/macros.h\"\n\nnamespace caffe2 {\n\n// Data type for caffe2 Index/Size. We use size_t to be safe here as well as for\n// large matrices that are common in sparse math.\ntypedef int64_t TIndex;\n\n// Note(Yangqing): NVCC does not play well with unordered_map on some platforms,\n// forcing us to use std::map instead of unordered_map. This may affect speed\n// in some cases, but in most of the computation code we do not access map very\n// often, so it should be fine for us. I am putting a CaffeMap alias so we can\n// change it more easily if things work out for unordered_map down the road.\ntemplate <typename Key, typename Value>\nusing CaffeMap = std::map<Key, Value>;\n// using CaffeMap = std::unordered_map;\n\n// Using statements for common classes that we refer to in caffe2 very often.\n// Note that we only place it inside caffe2 so the global namespace is not\n// polluted.\n/* using override */\nusing std::set;\nusing std::string;\nusing std::unique_ptr;\nusing std::vector;\n\n// Just in order to mark things as not implemented. Do not use in final code.\n#define CAFFE_NOT_IMPLEMENTED CAFFE_THROW(\"Not Implemented.\")\n\n// suppress an unused variable.\n#ifdef _MSC_VER\n#define CAFFE2_UNUSED\n#define CAFFE2_USED\n#else\n#define CAFFE2_UNUSED __attribute__((__unused__))\n#define CAFFE2_USED __attribute__((__used__))\n#endif //_MSC_VER\n\n// Disable the copy and assignment operator for a class. Note that this will\n// disable the usage of the class in std containers.\n#ifndef DISABLE_COPY_AND_ASSIGN\n#define DISABLE_COPY_AND_ASSIGN(classname)                              \\\nprivate:                                                                       \\\n  classname(const classname&) = delete;                                        \\\n  classname& operator=(const classname&) = delete\n#endif\n\n// Define enabled when building for iOS or Android devices\n#if !defined(CAFFE2_MOBILE)\n#if defined(__ANDROID__)\n#define CAFFE2_ANDROID 1\n#define CAFFE2_MOBILE 1\n#elif (defined(__APPLE__) &&                                            \\\n       (TARGET_IPHONE_SIMULATOR || TARGET_OS_SIMULATOR || TARGET_OS_IPHONE))\n#define CAFFE2_IOS 1\n#define CAFFE2_MOBILE 1\n#elif (defined(__APPLE__) && TARGET_OS_MAC)\n#define CAFFE2_IOS 1\n#define CAFFE2_MOBILE 0\n#else\n#define CAFFE2_MOBILE 0\n#endif // ANDROID / IOS / MACOS\n#endif // CAFFE2_MOBILE\n\n// Define alignment macro that is cross platform\n#if defined(_MSC_VER)\n#define CAFFE2_ALIGNED(x) __declspec(align(x))\n#else\n#define CAFFE2_ALIGNED(x) __attribute__((aligned(x)))\n#endif\n\n/**\n * Macro for marking functions as having public visibility.\n * Ported from folly/CPortability.h\n */\n#ifndef __GNUC_PREREQ\n#if defined __GNUC__ && defined __GNUC_MINOR__\n#define __GNUC_PREREQ(maj, min) \\\n  ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))\n#else\n#define __GNUC_PREREQ(maj, min) 0\n#endif\n#endif\n\n// Defines CAFFE2_EXPORT and CAFFE2_IMPORT. On Windows, this corresponds to\n// different declarations (dllexport and dllimport). On Linux/Mac, it just\n// resolves to the same \"default visibility\" setting.\n#if defined(_MSC_VER)\n#if defined(CAFFE2_BUILD_SHARED_LIBS)\n#define CAFFE2_EXPORT __declspec(dllexport)\n#define CAFFE2_IMPORT __declspec(dllimport)\n#else\n#define CAFFE2_EXPORT\n#define CAFFE2_IMPORT\n#endif\n#else\n#if defined(__GNUC__)\n#if __GNUC_PREREQ(4, 9)\n#define CAFFE2_EXPORT [[gnu::visibility(\"default\")]]\n#else\n#define CAFFE2_EXPORT __attribute__((__visibility__(\"default\")))\n#endif\n#else\n#define CAFFE2_EXPORT\n#endif\n#define CAFFE2_IMPORT CAFFE2_EXPORT\n#endif\n\n// CAFFE2_API is a macro that, depends on whether you are building the\n// main caffe2 library or not, resolves to either CAFFE2_EXPORT or\n// CAFFE2_IMPORT.\n//\n// This is used in e.g. Caffe2's protobuf files: when building the main library,\n// it is defined as CAFFE2_EXPORT to fix a Windows global-variable-in-dll\n// issue, and for anyone dependent on Caffe2 it will be defined as\n// CAFFE2_IMPORT.\n\n#ifdef CAFFE2_BUILD_MAIN_LIB\n#define CAFFE2_API CAFFE2_EXPORT\n#else\n#define CAFFE2_API CAFFE2_IMPORT\n#endif\n\n// make_unique is a C++14 feature. If we don't have 14, we will emulate\n// its behavior. This is copied from folly/Memory.h\n#if __cplusplus >= 201402L ||                                              \\\n    (defined __cpp_lib_make_unique && __cpp_lib_make_unique >= 201304L) || \\\n    (defined(_MSC_VER) && _MSC_VER >= 1900)\n/* using override */\nusing std::make_unique;\n#else\n\ntemplate<typename T, typename... Args>\ntypename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type\nmake_unique(Args&&... args) {\n  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));\n}\n\n// Allows 'make_unique<T[]>(10)'. (N3690 s20.9.1.4 p3-4)\ntemplate<typename T>\ntypename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type\nmake_unique(const size_t n) {\n  return std::unique_ptr<T>(new typename std::remove_extent<T>::type[n]());\n}\n\n// Disallows 'make_unique<T[10]>()'. (N3690 s20.9.1.4 p5)\ntemplate<typename T, typename... Args>\ntypename std::enable_if<\n  std::extent<T>::value != 0, std::unique_ptr<T>>::type\nmake_unique(Args&&...) = delete;\n\n#endif\n\n// to_string, stoi and stod implementation for Android related stuff.\n// Note(jiayq): Do not use the CAFFE2_TESTONLY_FORCE_STD_STRING_TEST macro\n// outside testing code that lives under common_test.cc\n#if defined(__ANDROID__) || defined(CAFFE2_TESTONLY_FORCE_STD_STRING_TEST)\n#define CAFFE2_TESTONLY_WE_ARE_USING_CUSTOM_STRING_FUNCTIONS 1\ntemplate <typename T>\nstd::string to_string(T value)\n{\n  std::ostringstream os;\n  os << value;\n  return os.str();\n}\n\ninline int stoi(const string& str) {\n  std::stringstream ss;\n  int n = 0;\n  ss << str;\n  ss >> n;\n  return n;\n}\n\ninline double stod(const string& str, std::size_t* pos = 0) {\n  std::stringstream ss;\n  ss << str;\n  double val = 0;\n  ss >> val;\n  if (pos) {\n    if (ss.tellg() == std::streampos(-1)) {\n      *pos = str.size();\n    } else {\n      *pos = ss.tellg();\n    }\n  }\n  return val;\n}\n#else\n#define CAFFE2_TESTONLY_WE_ARE_USING_CUSTOM_STRING_FUNCTIONS 0\nusing std::to_string;\nusing std::stoi;\nusing std::stod;\n#endif // defined(__ANDROID__) || defined(CAFFE2_FORCE_STD_STRING_FALLBACK_TEST)\n\n// dynamic cast reroute: if RTTI is disabled, go to reinterpret_cast\ntemplate <typename Dst, typename Src>\ninline Dst dynamic_cast_if_rtti(Src ptr) {\n#ifdef __GXX_RTTI\n  return dynamic_cast<Dst>(ptr);\n#else\n  return reinterpret_cast<Dst>(ptr);\n#endif\n}\n\n// SkipIndices are used in operator_fallback_gpu.h and operator_fallback_mkl.h\n// as utilty functions that marks input / output indices to skip when we use a\n// CPU operator as the fallback of GPU/MKL operator option.\ntemplate <int... values>\nclass SkipIndices {\n private:\n  template <int V>\n  static inline bool ContainsInternal(const int i) {\n    return (i == V);\n  }\n  template <int First, int Second, int... Rest>\n  static inline bool ContainsInternal(const int i) {\n    return (i == First) && ContainsInternal<Second, Rest...>(i);\n  }\n\n public:\n  static inline bool Contains(const int i) {\n    return ContainsInternal<values...>(i);\n  }\n};\n\ntemplate <>\nclass SkipIndices<> {\n public:\n  static inline bool Contains(const int /*i*/) {\n    return false;\n  }\n};\n\n// HasCudaRuntime() tells the program whether the binary has Cuda runtime\n// linked. This function should not be used in static initialization functions\n// as the underlying boolean variable is going to be switched on when one\n// loads libcaffe2_gpu.so.\nbool HasCudaRuntime();\nnamespace internal {\n// Sets the Cuda Runtime flag that is used by HasCudaRuntime(). You should\n// never use this function - it is only used by the Caffe2 gpu code to notify\n// Caffe2 core that cuda runtime has been loaded.\nvoid SetCudaRuntimeFlag();\n}\n// Returns which setting Caffe2 was configured and built with (exported from\n// CMake)\nconst std::map<string, string>& GetBuildOptions();\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_CORE_COMMON_H_\n"
  },
  {
    "path": "caffe2/core/common_cudnn.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common_cudnn.h\"\n#include \"caffe2/core/cudnn_wrappers.h\"\n\n#include \"caffe2/core/init.h\"\n\nnamespace caffe2 {\n\nCuDNNWrapper::PerGPUCuDNNStates& CuDNNWrapper::cudnn_states() {\n  // New it (never delete) to avoid calling the destructors on process\n  // exit and racing against the CUDA shutdown sequence.\n  static auto* p = new CuDNNWrapper::PerGPUCuDNNStates();\n  CHECK_NOTNULL(p);\n  return *p;\n}\n\nnamespace {\nbool PrintCuDNNInfo(int*, char***) {\n  VLOG(1) << \"Caffe2 is built with CuDNN version \" << CUDNN_VERSION;\n  return true;\n}\n\nREGISTER_CAFFE2_INIT_FUNCTION(PrintCuDNNInfo, &PrintCuDNNInfo,\n                              \"Print CuDNN Info.\");\n\n}  // namespace\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/common_cudnn.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_COMMON_CUDNN_H_\n#define CAFFE2_CORE_COMMON_CUDNN_H_\n\n#include <array>\n#include <mutex>\n\n#include <cudnn.h>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nstatic_assert(\n    CUDNN_VERSION >= 5000,\n    \"Caffe2 requires cudnn version 5.0 or above.\");\n\n#if CUDNN_VERSION < 6000\n#pragma message \"CUDNN version under 6.0 is supported at best effort.\"\n#pragma message \"We strongly encourage you to move to 6.0 and above.\"\n#pragma message \"This message is intended to annoy you enough to update.\"\n#endif // CUDNN_VERSION < 6000\n\n#define CUDNN_VERSION_MIN(major, minor, patch) \\\n  (CUDNN_VERSION >= ((major) * 1000 + (minor) * 100 + (patch)))\n\nnamespace caffe2 {\n\nnamespace internal {\n/**\n * A helper function to obtain cudnn error strings.\n */\ninline const char* cudnnGetErrorString(cudnnStatus_t status) {\n  switch (status) {\n    case CUDNN_STATUS_SUCCESS:\n      return \"CUDNN_STATUS_SUCCESS\";\n    case CUDNN_STATUS_NOT_INITIALIZED:\n      return \"CUDNN_STATUS_NOT_INITIALIZED\";\n    case CUDNN_STATUS_ALLOC_FAILED:\n      return \"CUDNN_STATUS_ALLOC_FAILED\";\n    case CUDNN_STATUS_BAD_PARAM:\n      return \"CUDNN_STATUS_BAD_PARAM\";\n    case CUDNN_STATUS_INTERNAL_ERROR:\n      return \"CUDNN_STATUS_INTERNAL_ERROR\";\n    case CUDNN_STATUS_INVALID_VALUE:\n      return \"CUDNN_STATUS_INVALID_VALUE\";\n    case CUDNN_STATUS_ARCH_MISMATCH:\n      return \"CUDNN_STATUS_ARCH_MISMATCH\";\n    case CUDNN_STATUS_MAPPING_ERROR:\n      return \"CUDNN_STATUS_MAPPING_ERROR\";\n    case CUDNN_STATUS_EXECUTION_FAILED:\n      return \"CUDNN_STATUS_EXECUTION_FAILED\";\n    case CUDNN_STATUS_NOT_SUPPORTED:\n      return \"CUDNN_STATUS_NOT_SUPPORTED\";\n    case CUDNN_STATUS_LICENSE_ERROR:\n      return \"CUDNN_STATUS_LICENSE_ERROR\";\n    default:\n      return \"Unknown cudnn error number\";\n  }\n}\n} // namespace internal\n\n// A macro that wraps around a cudnn statement so we can check if the cudnn\n// execution finishes or not.\n#define CUDNN_ENFORCE(condition)                          \\\n  do {                                                    \\\n    cudnnStatus_t status = condition;                     \\\n    CAFFE_ENFORCE_EQ(                                     \\\n        status,                                           \\\n        CUDNN_STATUS_SUCCESS,                             \\\n        \", Error at: \",                                   \\\n        __FILE__,                                         \\\n        \":\",                                              \\\n        __LINE__,                                         \\\n        \": \",                                             \\\n        ::caffe2::internal::cudnnGetErrorString(status)); \\\n  } while (0)\n#define CUDNN_CHECK(condition)                              \\\n  do {                                                      \\\n    cudnnStatus_t status = condition;                       \\\n    CHECK(status == CUDNN_STATUS_SUCCESS)                   \\\n        << ::caffe2::internal::cudnnGetErrorString(status); \\\n  } while (0)\n\n// report the version of cuDNN Caffe2 was compiled with\ninline size_t cudnnCompiledVersion() {\n  return CUDNN_VERSION;\n}\n// report the runtime version of cuDNN\ninline size_t cudnnRuntimeVersion() {\n  return cudnnGetVersion();\n}\n\n// Check compatibility of compiled and runtime cuDNN versions\ninline void CheckCuDNNVersions() {\n  // Version format is major*1000 + minor*100 + patch\n  // Major, minor and patch versions must all match\n  bool version_match = cudnnCompiledVersion() == cudnnRuntimeVersion();\n  CAFFE_ENFORCE(version_match,\n                \"cuDNN compiled (\", cudnnCompiledVersion(), \") and \"\n                \"runtime (\", cudnnRuntimeVersion(), \") versions mismatch\");\n}\n\n/**\n * cudnnTypeWrapper is a wrapper class that allows us to refer to the cudnn type\n * in a template function. The class is specialized explicitly for different\n * data types below.\n */\ntemplate <typename T>\nclass cudnnTypeWrapper;\n\ntemplate <>\nclass cudnnTypeWrapper<float> {\n public:\n  static const cudnnDataType_t type = CUDNN_DATA_FLOAT;\n  typedef const float ScalingParamType;\n  typedef float BNParamType;\n  static ScalingParamType* kOne() {\n    static ScalingParamType v = 1.0;\n    return &v;\n  }\n  static const ScalingParamType* kZero() {\n    static ScalingParamType v = 0.0;\n    return &v;\n  }\n};\n\n#if CUDNN_VERSION_MIN(6, 0, 0)\ntemplate <>\nclass cudnnTypeWrapper<int> {\n public:\n  static const cudnnDataType_t type = CUDNN_DATA_INT32;\n  typedef const int ScalingParamType;\n  typedef int BNParamType;\n  static ScalingParamType* kOne() {\n    static ScalingParamType v = 1;\n    return &v;\n  }\n  static const ScalingParamType* kZero() {\n    static ScalingParamType v = 0;\n    return &v;\n  }\n};\n#endif // CUDNN_VERSION_MIN(6, 0, 0)\n\ntemplate <>\nclass cudnnTypeWrapper<double> {\n public:\n  static const cudnnDataType_t type = CUDNN_DATA_DOUBLE;\n  typedef const double ScalingParamType;\n  typedef double BNParamType;\n  static ScalingParamType* kOne() {\n    static ScalingParamType v = 1.0;\n    return &v;\n  }\n  static ScalingParamType* kZero() {\n    static ScalingParamType v = 0.0;\n    return &v;\n  }\n};\n\ntemplate <>\nclass cudnnTypeWrapper<float16> {\n public:\n  static const cudnnDataType_t type = CUDNN_DATA_HALF;\n  typedef const float ScalingParamType;\n  typedef float BNParamType;\n  static ScalingParamType* kOne() {\n    static ScalingParamType v = 1.0;\n    return &v;\n  }\n  static ScalingParamType* kZero() {\n    static ScalingParamType v = 0.0;\n    return &v;\n  }\n};\n\n/**\n * A wrapper function to convert the Caffe storage order to cudnn storage order\n * enum values.\n */\ninline cudnnTensorFormat_t GetCudnnTensorFormat(const StorageOrder& order) {\n  switch (order) {\n    case StorageOrder::NHWC:\n      return CUDNN_TENSOR_NHWC;\n    case StorageOrder::NCHW:\n      return CUDNN_TENSOR_NCHW;\n    default:\n      LOG(FATAL) << \"Unknown cudnn equivalent for order: \" << order;\n  }\n  // Just to suppress compiler warnings\n  return CUDNN_TENSOR_NCHW;\n}\n\n/**\n * cudnnTensorDescWrapper is the placeholder that wraps around a\n * cudnnTensorDescriptor_t, allowing us to do descriptor change as-needed during\n * runtime.\n */\nclass cudnnTensorDescWrapper {\n public:\n  cudnnTensorDescWrapper() {\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&desc_));\n  }\n  ~cudnnTensorDescWrapper() noexcept {\n    CUDNN_CHECK(cudnnDestroyTensorDescriptor(desc_));\n  }\n\n  inline cudnnTensorDescriptor_t Descriptor(\n      const cudnnTensorFormat_t format,\n      const cudnnDataType_t type,\n      const vector<int>& dims,\n      bool* changed) {\n    if (type_ == type && format_ == format && dims_ == dims) {\n      // if not changed, simply return the current descriptor.\n      if (changed)\n        *changed = false;\n      return desc_;\n    }\n    CAFFE_ENFORCE_EQ(\n        dims.size(), 4, \"Currently only 4-dimensional descriptor supported.\");\n    format_ = format;\n    type_ = type;\n    dims_ = dims;\n    CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n        desc_,\n        format,\n        type,\n        dims_[0],\n        (format == CUDNN_TENSOR_NCHW ? dims_[1] : dims_[3]),\n        (format == CUDNN_TENSOR_NCHW ? dims_[2] : dims_[1]),\n        (format == CUDNN_TENSOR_NCHW ? dims_[3] : dims_[2])));\n    if (changed)\n      *changed = true;\n    return desc_;\n  }\n\n  template <typename T>\n  inline cudnnTensorDescriptor_t Descriptor(\n      const StorageOrder& order,\n      const vector<int>& dims) {\n    return Descriptor(\n        GetCudnnTensorFormat(order), cudnnTypeWrapper<T>::type, dims, nullptr);\n  }\n\n private:\n  cudnnTensorDescriptor_t desc_;\n  cudnnTensorFormat_t format_;\n  cudnnDataType_t type_;\n  vector<int> dims_;\n  DISABLE_COPY_AND_ASSIGN(cudnnTensorDescWrapper);\n};\n\nclass cudnnFilterDescWrapper {\n public:\n  cudnnFilterDescWrapper() {\n    CUDNN_ENFORCE(cudnnCreateFilterDescriptor(&desc_));\n  }\n  ~cudnnFilterDescWrapper() noexcept {\n    CUDNN_CHECK(cudnnDestroyFilterDescriptor(desc_));\n  }\n\n  inline cudnnFilterDescriptor_t Descriptor(\n      const StorageOrder& order,\n      const cudnnDataType_t type,\n      const vector<int>& dims,\n      bool* changed) {\n    if (type_ == type && order_ == order && dims_ == dims) {\n      // if not changed, simply return the current descriptor.\n      if (changed)\n        *changed = false;\n      return desc_;\n    }\n    CAFFE_ENFORCE_EQ(\n        dims.size(), 4, \"Currently only 4-dimensional descriptor supported.\");\n    order_ = order;\n    type_ = type;\n    dims_ = dims;\n    CUDNN_ENFORCE(cudnnSetFilter4dDescriptor(\n        desc_,\n        type,\n        GetCudnnTensorFormat(order),\n        dims_[0],\n        // TODO - confirm that this is correct for NHWC\n        (order == StorageOrder::NCHW ? dims_[1] : dims_[3]),\n        (order == StorageOrder::NCHW ? dims_[2] : dims_[1]),\n        (order == StorageOrder::NCHW ? dims_[3] : dims_[2])));\n    if (changed)\n      *changed = true;\n    return desc_;\n  }\n\n  template <typename T>\n  inline cudnnFilterDescriptor_t Descriptor(\n      const StorageOrder& order,\n      const vector<int>& dims) {\n    return Descriptor(order, cudnnTypeWrapper<T>::type, dims, nullptr);\n  }\n\n private:\n  cudnnFilterDescriptor_t desc_;\n  StorageOrder order_;\n  cudnnDataType_t type_;\n  vector<int> dims_;\n  DISABLE_COPY_AND_ASSIGN(cudnnFilterDescWrapper);\n};\n\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_COMMON_CUDNN_H_\n"
  },
  {
    "path": "caffe2/core/common_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common_gpu.h\"\n\n#include <atomic>\n#include <cstdlib>\n#include <sstream>\n\n#include \"caffe2/core/asan.h\"\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/logging.h\"\n\nCAFFE2_DEFINE_bool(\n    caffe2_cuda_full_device_control,\n    false,\n    \"If true, assume all the cudaSetDevice and cudaGetDevice calls will be \"\n    \"controlled by Caffe2, and non-Caffe2 code will ensure that the entry and \"\n    \"exit point has the same cuda device. Under the hood, Caffe2 will use \"\n    \"thread local variables to cache the device, in order to speed up set and \"\n    \"get device calls. This is an experimental feature that may have non \"\n    \"trivial side effects, so use it with care and only enable it if you are \"\n    \"absolutely sure. Also, this flag should not be changed after the program \"\n    \"initializes.\");\n\nnamespace caffe2 {\n\nint NumCudaDevices() {\n  if (getenv(\"CAFFE2_DEBUG_CUDA_INIT_ORDER\")) {\n    static bool first = true;\n    if (first) {\n      first = false;\n      std::cerr << \"DEBUG: caffe2::NumCudaDevices() invoked for the first time\"\n                << std::endl;\n    }\n  }\n  static int count = -1;\n  if (count < 0) {\n    auto err = cudaGetDeviceCount(&count);\n    switch (err) {\n      case cudaSuccess:\n        // Everything is good.\n        break;\n      case cudaErrorNoDevice:\n        count = 0;\n        break;\n      case cudaErrorInsufficientDriver:\n        LOG(WARNING) << \"Insufficient cuda driver. Cannot use cuda.\";\n        count = 0;\n        break;\n      case cudaErrorInitializationError:\n        LOG(WARNING) << \"Cuda driver initialization failed, you might not \"\n                        \"have a cuda gpu.\";\n        count = 0;\n        break;\n      case cudaErrorUnknown:\n        LOG(ERROR) << \"Found an unknown error - this may be due to an \"\n                      \"incorrectly set up environment, e.g. changing env \"\n                      \"variable CUDA_VISIBLE_DEVICES after program start. \"\n                      \"I will set the available devices to be zero.\";\n        count = 0;\n        break;\n      case cudaErrorMemoryAllocation:\n#if CAFFE2_ASAN_ENABLED\n        // In ASAN mode, we know that a cudaErrorMemoryAllocation error will\n        // pop up.\n        LOG(ERROR) << \"It is known that CUDA does not work well with ASAN. As \"\n                      \"a result we will simply shut down CUDA support. If you \"\n                      \"would like to use GPUs, turn off ASAN.\";\n        count = 0;\n        break;\n#else // CAFFE2_ASAN_ENABLED\n        // If we are not in ASAN mode and we get cudaErrorMemoryAllocation,\n        // this means that something is wrong before NumCudaDevices() call.\n        LOG(FATAL) << \"Unexpected error from cudaGetDeviceCount(). Did you run \"\n                      \"some cuda functions before calling NumCudaDevices() \"\n                      \"that might have already set an error? Error: \"\n                   << err;\n        break;\n#endif // CAFFE2_ASAN_ENABLED\n      default:\n        LOG(FATAL) << \"Unexpected error from cudaGetDeviceCount(). Did you run \"\n                      \"some cuda functions before calling NumCudaDevices() \"\n                      \"that might have already set an error? Error: \"\n                   << err;\n    }\n  }\n  return count;\n}\n\nnamespace {\nint gDefaultGPUID = 0;\n// Only used when FLAGS_caffe2_cuda_full_device_control is set true.\nthread_local int gCurrentDevice = -1;\n}  // namespace\n\nvoid SetDefaultGPUID(const int deviceid) {\n  CAFFE_ENFORCE_LT(\n      deviceid,\n      NumCudaDevices(),\n      \"The default gpu id should be smaller than the number of gpus \"\n      \"on this machine: \",\n      deviceid,\n      \" vs \",\n      NumCudaDevices());\n  gDefaultGPUID = deviceid;\n}\n\nint GetDefaultGPUID() { return gDefaultGPUID; }\n\nint CaffeCudaGetDevice() {\n  if (FLAGS_caffe2_cuda_full_device_control) {\n    if (gCurrentDevice < 0) {\n      CUDA_ENFORCE(cudaGetDevice(&gCurrentDevice));\n    }\n    return gCurrentDevice;\n  } else {\n    int gpu_id = 0;\n    CUDA_ENFORCE(cudaGetDevice(&gpu_id));\n    return gpu_id;\n  }\n}\n\nvoid CaffeCudaSetDevice(const int id) {\n  if (FLAGS_caffe2_cuda_full_device_control) {\n    if (gCurrentDevice != id) {\n      CUDA_ENFORCE(cudaSetDevice(id));\n    }\n    gCurrentDevice = id;\n  } else {\n    CUDA_ENFORCE(cudaSetDevice(id));\n  }\n}\n\nint GetGPUIDForPointer(const void* ptr) {\n  cudaPointerAttributes attr;\n  cudaError_t err = cudaPointerGetAttributes(&attr, ptr);\n\n  if (err == cudaErrorInvalidValue) {\n    // Occurs when the pointer is in the CPU address space that is\n    // unmanaged by CUDA; make sure the last error state is cleared,\n    // since it is persistent\n    err = cudaGetLastError();\n    CHECK(err == cudaErrorInvalidValue);\n    return -1;\n  }\n\n  // Otherwise, there must be no error\n  CUDA_ENFORCE(err);\n\n  if (attr.memoryType == cudaMemoryTypeHost) {\n    return -1;\n  }\n\n  return attr.device;\n}\n\nstruct CudaDevicePropWrapper {\n  CudaDevicePropWrapper() : props(NumCudaDevices()) {\n    for (int i = 0; i < NumCudaDevices(); ++i) {\n      CUDA_ENFORCE(cudaGetDeviceProperties(&props[i], i));\n    }\n  }\n\n  vector<cudaDeviceProp> props;\n};\n\nconst cudaDeviceProp& GetDeviceProperty(const int deviceid) {\n  // According to C++11 standard section 6.7, static local variable init is\n  // thread safe. See\n  //   https://stackoverflow.com/questions/8102125/is-local-static-variable-initialization-thread-safe-in-c11\n  // for details.\n  static CudaDevicePropWrapper props;\n  CAFFE_ENFORCE_LT(\n      deviceid,\n      NumCudaDevices(),\n      \"The gpu id should be smaller than the number of gpus \",\n      \"on this machine: \",\n      deviceid,\n      \" vs \",\n      NumCudaDevices());\n  return props.props[deviceid];\n}\n\nvoid DeviceQuery(const int device) {\n  const cudaDeviceProp& prop = GetDeviceProperty(device);\n  std::stringstream ss;\n  ss << std::endl;\n  ss << \"Device id:                     \" << device << std::endl;\n  ss << \"Major revision number:         \" << prop.major << std::endl;\n  ss << \"Minor revision number:         \" << prop.minor << std::endl;\n  ss << \"Name:                          \" << prop.name << std::endl;\n  ss << \"Total global memory:           \" << prop.totalGlobalMem << std::endl;\n  ss << \"Total shared memory per block: \" << prop.sharedMemPerBlock\n     << std::endl;\n  ss << \"Total registers per block:     \" << prop.regsPerBlock << std::endl;\n  ss << \"Warp size:                     \" << prop.warpSize << std::endl;\n  ss << \"Maximum memory pitch:          \" << prop.memPitch << std::endl;\n  ss << \"Maximum threads per block:     \" << prop.maxThreadsPerBlock\n     << std::endl;\n  ss << \"Maximum dimension of block:    \"\n     << prop.maxThreadsDim[0] << \", \" << prop.maxThreadsDim[1] << \", \"\n     << prop.maxThreadsDim[2] << std::endl;\n  ss << \"Maximum dimension of grid:     \"\n     << prop.maxGridSize[0] << \", \" << prop.maxGridSize[1] << \", \"\n     << prop.maxGridSize[2] << std::endl;\n  ss << \"Clock rate:                    \" << prop.clockRate << std::endl;\n  ss << \"Total constant memory:         \" << prop.totalConstMem << std::endl;\n  ss << \"Texture alignment:             \" << prop.textureAlignment << std::endl;\n  ss << \"Concurrent copy and execution: \"\n     << (prop.deviceOverlap ? \"Yes\" : \"No\") << std::endl;\n  ss << \"Number of multiprocessors:     \" << prop.multiProcessorCount\n     << std::endl;\n  ss << \"Kernel execution timeout:      \"\n     << (prop.kernelExecTimeoutEnabled ? \"Yes\" : \"No\") << std::endl;\n  LOG(INFO) << ss.str();\n  return;\n}\n\nbool GetCudaPeerAccessPattern(vector<vector<bool> >* pattern) {\n  int gpu_count;\n  if (cudaGetDeviceCount(&gpu_count) != cudaSuccess) return false;\n  pattern->clear();\n  pattern->resize(gpu_count, vector<bool>(gpu_count, false));\n  for (int i = 0; i < gpu_count; ++i) {\n    for (int j = 0; j < gpu_count; ++j) {\n      int can_access = true;\n      if (i != j) {\n        if (cudaDeviceCanAccessPeer(&can_access, i, j)\n                 != cudaSuccess) {\n          return false;\n        }\n      }\n      (*pattern)[i][j] = static_cast<bool>(can_access);\n    }\n  }\n  return true;\n}\n\nbool TensorCoreAvailable() {\n  // requires CUDA 9.0 and above\n#if CUDA_VERSION < 9000\n  return false;\n#else\n  int device = CaffeCudaGetDevice();\n  auto& prop = GetDeviceProperty(device);\n\n  return prop.major >= 7;\n#endif\n}\n\nconst char* cublasGetErrorString(cublasStatus_t error) {\n  switch (error) {\n  case CUBLAS_STATUS_SUCCESS:\n    return \"CUBLAS_STATUS_SUCCESS\";\n  case CUBLAS_STATUS_NOT_INITIALIZED:\n    return \"CUBLAS_STATUS_NOT_INITIALIZED\";\n  case CUBLAS_STATUS_ALLOC_FAILED:\n    return \"CUBLAS_STATUS_ALLOC_FAILED\";\n  case CUBLAS_STATUS_INVALID_VALUE:\n    return \"CUBLAS_STATUS_INVALID_VALUE\";\n  case CUBLAS_STATUS_ARCH_MISMATCH:\n    return \"CUBLAS_STATUS_ARCH_MISMATCH\";\n  case CUBLAS_STATUS_MAPPING_ERROR:\n    return \"CUBLAS_STATUS_MAPPING_ERROR\";\n  case CUBLAS_STATUS_EXECUTION_FAILED:\n    return \"CUBLAS_STATUS_EXECUTION_FAILED\";\n  case CUBLAS_STATUS_INTERNAL_ERROR:\n    return \"CUBLAS_STATUS_INTERNAL_ERROR\";\n#if CUDA_VERSION >= 6000\n  case CUBLAS_STATUS_NOT_SUPPORTED:\n    return \"CUBLAS_STATUS_NOT_SUPPORTED\";\n#if CUDA_VERSION >= 6050\n  case CUBLAS_STATUS_LICENSE_ERROR:\n    return \"CUBLAS_STATUS_LICENSE_ERROR\";\n#endif  // CUDA_VERSION >= 6050\n#endif  // CUDA_VERSION >= 6000\n  }\n  // To suppress compiler warning.\n  return \"Unrecognized cublas error string\";\n}\n\nconst char* curandGetErrorString(curandStatus_t error) {\n  switch (error) {\n  case CURAND_STATUS_SUCCESS:\n    return \"CURAND_STATUS_SUCCESS\";\n  case CURAND_STATUS_VERSION_MISMATCH:\n    return \"CURAND_STATUS_VERSION_MISMATCH\";\n  case CURAND_STATUS_NOT_INITIALIZED:\n    return \"CURAND_STATUS_NOT_INITIALIZED\";\n  case CURAND_STATUS_ALLOCATION_FAILED:\n    return \"CURAND_STATUS_ALLOCATION_FAILED\";\n  case CURAND_STATUS_TYPE_ERROR:\n    return \"CURAND_STATUS_TYPE_ERROR\";\n  case CURAND_STATUS_OUT_OF_RANGE:\n    return \"CURAND_STATUS_OUT_OF_RANGE\";\n  case CURAND_STATUS_LENGTH_NOT_MULTIPLE:\n    return \"CURAND_STATUS_LENGTH_NOT_MULTIPLE\";\n  case CURAND_STATUS_DOUBLE_PRECISION_REQUIRED:\n    return \"CURAND_STATUS_DOUBLE_PRECISION_REQUIRED\";\n  case CURAND_STATUS_LAUNCH_FAILURE:\n    return \"CURAND_STATUS_LAUNCH_FAILURE\";\n  case CURAND_STATUS_PREEXISTING_FAILURE:\n    return \"CURAND_STATUS_PREEXISTING_FAILURE\";\n  case CURAND_STATUS_INITIALIZATION_FAILED:\n    return \"CURAND_STATUS_INITIALIZATION_FAILED\";\n  case CURAND_STATUS_ARCH_MISMATCH:\n    return \"CURAND_STATUS_ARCH_MISMATCH\";\n  case CURAND_STATUS_INTERNAL_ERROR:\n    return \"CURAND_STATUS_INTERNAL_ERROR\";\n  }\n  // To suppress compiler warning.\n  return \"Unrecognized curand error string\";\n}\n\n// Turn on the flag g_caffe2_has_cuda_linked to true for HasCudaRuntime()\n// function.\nnamespace {\nclass CudaRuntimeFlagFlipper {\n public:\n  CudaRuntimeFlagFlipper() {\n    internal::SetCudaRuntimeFlag();\n  }\n};\nstatic CudaRuntimeFlagFlipper g_flipper;\n} // namespace\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/common_gpu.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_COMMON_GPU_H_\n#define CAFFE2_CORE_COMMON_GPU_H_\n\n#include <assert.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n// Disable strict aliasing errors for CUDA 9.\n// The cuda_fp16.h header in CUDA 9 RC triggers this diagnostic.\n// It is included by cusparse.h as well, so guarding the\n// inclusion of that header here is not enough.\n#if CUDA_VERSION >= 9000\n#ifdef __GNUC__\n#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)\n#pragma GCC diagnostic push\n#endif\n#pragma GCC diagnostic ignored \"-Wstrict-aliasing\"\n#endif // __GNUC__\n#endif // CUDA_VERSION >= 9000\n\n#include <cublas_v2.h>\n#include <curand.h>\n#include <driver_types.h>\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/common.h\"\n\n// This is a macro defined for cuda fp16 support. In default, cuda fp16 is\n// supported by NVCC 7.5, but it is also included in the Tegra X1 platform with\n// a (custom?) NVCC 7.0. As a result, we would normally just check the cuda\n// version here, but would also allow a use to pass in the flag\n// CAFFE_HAS_CUDA_FP16 manually.\n\n#ifndef CAFFE_HAS_CUDA_FP16\n#if CUDA_VERSION >= 7050\n#define CAFFE_HAS_CUDA_FP16\n#endif  // CUDA_VERSION >= 7050\n#endif  // CAFFE_HAS_CUDA_FP16\n\n#ifdef CAFFE_HAS_CUDA_FP16\n#include <cuda_fp16.h>\n#endif\n\n// Re-enable strict aliasing diagnostic if it was disabled.\n#if CUDA_VERSION >= 9000\n#ifdef __GNUC__\n#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)\n#pragma GCC diagnostic pop\n#endif\n#endif // __GNUC__\n#endif // CUDA_VERSION >= 9000\n\n/**\n * The maximum number of GPUs that caffe2 recognizes.\n */\n#define CAFFE2_COMPILE_TIME_MAX_GPUS 16\n/**\n * The maximum number of peers that each gpu can have when doing p2p setup.\n * Currently, according to NVidia documentation, each device can support a\n * system-wide maximum of eight peer connections.\n * When Caffe2 sets up peer access resources, if we have more than 8 gpus,\n * we will enable peer access in groups of 8.\n */\n#define CAFFE2_CUDA_MAX_PEER_SIZE 8\n\nnamespace caffe2 {\n\n#if CUDA_VERSION >= 9000\n/**\n * Empty class to identify TensorCore-based math\n */\nclass TensorCoreEngine {};\n#endif\n\n/**\n * A runtime function to report the cuda version that Caffe2 is built with.\n */\ninline int CudaVersion() { return CUDA_VERSION; }\n\n/**\n * Returns the number of devices.\n */\nint NumCudaDevices();\n\n/**\n * Check if the current running session has a cuda gpu present.\n *\n * Note that this is different from having caffe2 built with cuda. Building\n * Caffe2 with cuda only guarantees that this function exists. If there are no\n * cuda gpus present in the machine, or there are hardware configuration\n * problems like an insufficient driver, this function will still return false,\n * meaning that there is no usable GPU present.\n *\n * In the open source build, it is possible that Caffe2's GPU code is\n * dynamically loaded, and as a result a library could be only linked to the\n * CPU code, but want to test if cuda is later available or not. In this case,\n * one should use HasCudaRuntime() from common.h.\n */\ninline bool HasCudaGPU() { return NumCudaDevices() > 0; }\n\n/**\n * Gets the current GPU id. This is a simple wrapper around cudaGetDevice().\n */\nint CaffeCudaGetDevice();\n\n/**\n * Gets the current GPU id. This is a simple wrapper around cudaGetDevice().\n */\nvoid CaffeCudaSetDevice(const int id);\n\n/**\n * Gets the GPU id that the current pointer is located at.\n */\nint GetGPUIDForPointer(const void* ptr);\n\n/**\n * Gets the device property for the given device. This function is thread safe.\n */\nconst cudaDeviceProp& GetDeviceProperty(const int device);\n\n/**\n * Runs a device query function and prints out the results to LOG(INFO).\n */\nvoid DeviceQuery(const int deviceid);\n\n/**\n * Return a peer access pattern by returning a matrix (in the format of a\n * nested vector) of boolean values specifying whether peer access is possible.\n *\n * This function returns false if anything wrong happens during the query of\n * the GPU access pattern.\n */\nbool GetCudaPeerAccessPattern(vector<vector<bool> >* pattern);\n\n/**\n * Return the availability of TensorCores for math\n */\nbool TensorCoreAvailable();\n\n/**\n * Return a human readable cublas error string.\n */\nconst char* cublasGetErrorString(cublasStatus_t error);\n\n/**\n * Return a human readable curand error string.\n */\nconst char* curandGetErrorString(curandStatus_t error);\n\n// CUDA: various checks for different function calls.\n#define CUDA_ENFORCE(condition, ...)     \\\n  do {                              \\\n    cudaError_t error = condition;  \\\n    CAFFE_ENFORCE_EQ(               \\\n        error,                      \\\n        cudaSuccess,                \\\n        \"Error at: \",               \\\n        __FILE__,                   \\\n        \":\",                        \\\n        __LINE__,                   \\\n        \": \",                       \\\n        cudaGetErrorString(error), ##__VA_ARGS__); \\\n  } while (0)\n#define CUDA_CHECK(condition)                                 \\\n  do {                                                        \\\n    cudaError_t error = condition;                            \\\n    CHECK(error == cudaSuccess) << cudaGetErrorString(error); \\\n  } while (0)\n\n#define CUDA_DRIVERAPI_ENFORCE(condition)                            \\\n  do {                                                               \\\n    CUresult result = condition;                                     \\\n    if (result != CUDA_SUCCESS) {                                    \\\n      const char* msg;                                               \\\n      cuGetErrorName(result, &msg);                                  \\\n      CAFFE_THROW(\"Error at: \", __FILE__, \":\", __LINE__, \": \", msg); \\\n    }                                                                \\\n  } while (0)\n#define CUDA_DRIVERAPI_CHECK(condition)                                 \\\n  do {                                                                  \\\n    CUresult result = condition;                                        \\\n    if (result != CUDA_SUCCESS) {                                       \\\n      const char* msg;                                                  \\\n      cuGetErrorName(result, &msg);                                     \\\n      LOG(FATAL) << \"Error at: \" << __FILE__ << \":\" << __LINE__ << \": \" \\\n                 << msg;                                                \\\n    }                                                                   \\\n  } while (0)\n\n#define CUBLAS_ENFORCE(condition)                \\\n  do {                                           \\\n    cublasStatus_t status = condition;           \\\n    CAFFE_ENFORCE_EQ(                            \\\n        status,                                  \\\n        CUBLAS_STATUS_SUCCESS,                   \\\n        \"Error at: \",                            \\\n        __FILE__,                                \\\n        \":\",                                     \\\n        __LINE__,                                \\\n        \": \",                                    \\\n        ::caffe2::cublasGetErrorString(status)); \\\n  } while (0)\n#define CUBLAS_CHECK(condition)                    \\\n  do {                                             \\\n    cublasStatus_t status = condition;             \\\n    CHECK(status == CUBLAS_STATUS_SUCCESS)         \\\n        << ::caffe2::cublasGetErrorString(status); \\\n  } while (0)\n\n#define CURAND_ENFORCE(condition)                \\\n  do {                                           \\\n    curandStatus_t status = condition;           \\\n    CAFFE_ENFORCE_EQ(                            \\\n        status,                                  \\\n        CURAND_STATUS_SUCCESS,                   \\\n        \"Error at: \",                            \\\n        __FILE__,                                \\\n        \":\",                                     \\\n        __LINE__,                                \\\n        \": \",                                    \\\n        ::caffe2::curandGetErrorString(status)); \\\n  } while (0)\n#define CURAND_CHECK(condition)                    \\\n  do {                                             \\\n    curandStatus_t status = condition;             \\\n    CHECK(status == CURAND_STATUS_SUCCESS)         \\\n        << ::caffe2::curandGetErrorString(status); \\\n  } while (0)\n\n#define CUDA_1D_KERNEL_LOOP(i, n)                                 \\\n  for (size_t i = blockIdx.x * blockDim.x + threadIdx.x; i < (n); \\\n       i += blockDim.x * gridDim.x)\n\n// CUDA_KERNEL_ASSERT is a macro that wraps an assert() call inside cuda\n// kernels. This is not supported by Apple platforms so we special case it.\n// See http://docs.nvidia.com/cuda/cuda-c-programming-guide/#assertion\n#ifdef __APPLE__\n#define CUDA_KERNEL_ASSERT(...)\n#else  // __APPLE__\n#define CUDA_KERNEL_ASSERT(...) assert(__VA_ARGS__)\n#endif  // __APPLE__\n\n// The following helper functions are here so that you can write a kernel call\n// when you are not particularly interested in maxing out the kernels'\n// performance. Usually, this will give you a reasonable speed, but if you\n// really want to find the best performance, it is advised that you tune the\n// size of the blocks and grids more reasonably.\n// A legacy note: this is derived from the old good Caffe days, when I simply\n// hard-coded the number of threads and wanted to keep backward compatibility\n// for different computation capabilities.\n// For more info on CUDA compute capabilities, visit the NVidia website at:\n//    http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#compute-capabilities\n\n// The number of cuda threads to use. 512 is used for backward compatibility,\n// and it is observed that setting it to 1024 usually does not bring much\n// performance gain (which makes sense, because warp size being 32 means that\n// blindly setting a huge block for a random kernel isn't optimal).\nconstexpr int CAFFE_CUDA_NUM_THREADS = 512;\n// The maximum number of blocks to use in the default kernel call. We set it to\n// 4096 which would work for compute capability 2.x (where 65536 is the limit).\n// This number is very carelessly chosen. Ideally, one would like to look at\n// the hardware at runtime, and pick the number of blocks that makes most\n// sense for the specific runtime environment. This is a todo item.\nconstexpr int CAFFE_MAXIMUM_NUM_BLOCKS = 4096;\n\n/**\n * @brief Compute the number of blocks needed to run N threads.\n */\ninline int CAFFE_GET_BLOCKS(const int N) {\n  return std::min((N + CAFFE_CUDA_NUM_THREADS - 1) / CAFFE_CUDA_NUM_THREADS,\n                  CAFFE_MAXIMUM_NUM_BLOCKS);\n}\n\nclass DeviceGuard {\n public:\n  explicit DeviceGuard(int newDevice) : previous_(CaffeCudaGetDevice()) {\n    if (previous_ != newDevice) {\n      CaffeCudaSetDevice(newDevice);\n    }\n  }\n\n  ~DeviceGuard() noexcept {\n    CaffeCudaSetDevice(previous_);\n  }\n\n private:\n  int previous_;\n};\n\n}  // namespace caffe2\n#endif  // CAFFE2_CORE_COMMON_GPU_H_\n"
  },
  {
    "path": "caffe2/core/common_omp.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_COMMON_OMP_H_\n#define CAFFE2_CORE_COMMON_OMP_H_\n\n#ifdef _OPENMP\n#include <omp.h>\n#endif // _OPENMP\n\n#endif // CAFFE2_CORE_COMMON_OMP_H_\n"
  },
  {
    "path": "caffe2/core/common_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n#include <memory>\n\n#define CAFFE2_TESTONLY_FORCE_STD_STRING_TEST\n\n#include \"caffe2/core/common.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\n\n#ifndef __ANDROID__\n\n// Simple tests to make sure that our stoi and stod implementations are \n// matching the std implementations, but not testing it very extensively\n// as one should be using the std version most of the time.\nTEST(CommonTest, TestStoi) {\n  EXPECT_TRUE(CAFFE2_TESTONLY_WE_ARE_USING_CUSTOM_STRING_FUNCTIONS);\n  string s = \"1234\";\n  int i_std = std::stoi(s);\n  int i_caffe2 = ::caffe2::stoi(s);\n  EXPECT_EQ(i_std, i_caffe2);\n}\n\nTEST(CommonTest, TestStod) {\n  // Full string is parsed.\n  string s = \"1.234\";\n  std::size_t p_std = 0, p_caffe2 = 0;\n  double d_std = std::stod(s, &p_std);\n  double d_caffe2 = ::caffe2::stod(s, &p_caffe2);\n  EXPECT_EQ(d_std, d_caffe2);\n  EXPECT_EQ(p_std, p_caffe2);\n\n  // Only part of the string is parsed.\n  s = \"1.234 5.678\";\n  d_std = std::stod(s, &p_std);\n  d_caffe2 = ::caffe2::stod(s, &p_caffe2);\n  EXPECT_EQ(d_std, d_caffe2);\n  EXPECT_EQ(p_std, p_caffe2);\n}\n\n#endif // __ANDROID__\n\n}  // namespace caffe2\n\n\n"
  },
  {
    "path": "caffe2/core/context.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n\n#include <atomic>\n#if defined(_MSC_VER)\n#include <process.h>\n#endif\n\nnamespace caffe2 {\n\nuint32_t RandomNumberSeed() {\n  // Originally copied from folly::randomNumberSeed (at 418ad4)\n  // modified to use chrono instead of sys/time.h\n  static std::atomic<uint32_t> seedInput(0);\n  auto tv = std::chrono::system_clock::now().time_since_epoch();\n  uint64_t usec = static_cast<uint64_t>(\n      std::chrono::duration_cast<std::chrono::microseconds>(tv).count());\n  uint32_t tv_sec = usec / 1000000;\n  uint32_t tv_usec = usec % 1000000;\n  const uint32_t kPrime0 = 51551;\n  const uint32_t kPrime1 = 61631;\n  const uint32_t kPrime2 = 64997;\n  const uint32_t kPrime3 = 111857;\n  return kPrime0 * (seedInput++) + kPrime1 * static_cast<uint32_t>(getpid()) +\n      kPrime2 * tv_sec + kPrime3 * tv_usec;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/context.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_CONTEXT_H_\n#define CAFFE2_CORE_CONTEXT_H_\n\n#include <cstdlib>\n#include <ctime>\n#include <random>\n#include <unordered_map>\n\n#include \"caffe2/core/allocator.h\"\n#include \"caffe2/core/event.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/typeid.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nCAFFE2_DECLARE_bool(caffe2_report_cpu_memory_usage);\n\nnamespace caffe2 {\n\n/**\n * A function to generate a random number seed that is unique in a best-effort\n * basis, using an ever-incrementing seed and the current time.\n */\nuint32_t RandomNumberSeed();\n\n/**\n * The CPU Context, representing the bare minimum of what a Context class in\n * Caffe2 should implement.\n *\n * See operator.h, especially Operator<Context>, for how Context are used in\n * actual operator implementations that are associated with specific devices.\n * In general, the Context class is passed in as a template argument, and\n * the operator can use the functions defined in the context to execute whatever\n * computation it has.\n *\n * A Context defines all the necessities to run an operator on a specific\n * device. Specific Context classes have the freedom to choose what functions it\n * implements, but there are a few functions that you should consider\n * implementing if you want to write your own context class:\n * - void SwitchToDevice(): any necessary code to switch to the device before\n *     running anything.\n * - void WaitEvent(const Event& ev): make the current context to wait on\n *     an event. For example, for cuda, this is the equivalent of\n *     cudaStreamWaitEvent. For CPU context, it essentially synchronizes the\n *     event.\n * - void Record(Event* ev): record the async activities on the current context\n *     to the event. For example, for cuda, this is the equivalent of\n *     cudaEventRecord on the current stream. For CPU context, it is always\n *     synchronous.\n * - void FinishDeviceComputation(): any wrapping-up work after all the\n *     computation of the operator is done. If there are errors during the\n *     execution, throw exception. For example, in a CUDAContext, this function\n *     carries out a stream synchronization and spots potential errors for\n *     the cuda kernel calls.\n * - static std::pair<void*, MemoryDeleter> New(size_t nbytes): allocates\n       memory and returns a deleter.\n * - template <class SrcContext, class DstContext> void CopyBytes(...): does\n *     cross context memory copy.\n * - template <typename T, class SrcContext, class DstContext> void Copy(...):\n *     usually a simple wrapper around the above CopyBytes function.\n *\n * We intentionally did not create a base class for the various possible Context\n * classes there might be, since they are intended to be specified during\n * compile time using templates rather than via polymorphism. You should also\n * not have classes derived from existing context classes.\n */\nclass CPUContext final {\n public:\n  typedef std::mt19937 rand_gen_type;\n  CPUContext() : random_seed_(RandomNumberSeed()) {}\n  explicit CPUContext(const DeviceOption& option)\n      : random_seed_(\n            option.has_random_seed() ? option.random_seed()\n                                     : RandomNumberSeed()) {\n    CAFFE_ENFORCE_EQ(option.device_type(), CPU);\n  }\n\n  ~CPUContext() noexcept {}\n\n  inline void SwitchToDevice(int /*stream_id*/) {}\n  inline void SwitchToDevice() {\n    SwitchToDevice(0);\n  }\n\n  inline void WaitEvent(const Event& ev) {\n    ev.Wait(CPU, this);\n  }\n\n  inline void Record(Event* ev, const char* err_msg = nullptr) const {\n    CAFFE_ENFORCE(ev, \"Event must not be null.\");\n    ev->Record(CPU, this, err_msg);\n  }\n\n  inline void FinishDeviceComputation() {}\n\n  inline rand_gen_type& RandGenerator() {\n    if (!random_generator_.get()) {\n      random_generator_.reset(new rand_gen_type(random_seed_));\n    }\n    return *random_generator_.get();\n  }\n\n  static std::pair<void*, MemoryDeleter> New(size_t nbytes) {\n    auto data_and_deleter = GetCPUAllocator()->New(nbytes);\n    if (FLAGS_caffe2_report_cpu_memory_usage) {\n      reporter_.New(data_and_deleter.first, nbytes);\n      data_and_deleter.second = ReportAndDelete;\n    }\n    return data_and_deleter;\n  }\n\n  // Two copy functions that deals with cross-device copies.\n  template <class SrcContext, class DstContext>\n  inline void CopyBytes(size_t nbytes, const void* src, void* dst);\n\n  template <typename T, class SrcContext, class DstContext>\n  inline void Copy(size_t n, const T* src, T* dst) {\n    if (std::is_fundamental<T>::value) {\n      CopyBytes<SrcContext, DstContext>(\n          n * sizeof(T),\n          static_cast<const void*>(src),\n          static_cast<void*>(dst));\n    } else {\n      for (int i = 0; i < n; ++i) {\n        dst[i] = src[i];\n      }\n    }\n  }\n\n  template <class SrcContext, class DstContext>\n  inline void\n  CopyItems(const TypeMeta& meta, size_t n, const void* src, void* dst) {\n    if (meta.copy()) {\n      meta.copy()(src, dst, n);\n    } else {\n      CopyBytes<SrcContext, DstContext>(n * meta.itemsize(), src, dst);\n    }\n  }\n\n  // By default CPU operators don't have async device parts\n  static bool HasAsyncPartDefault() {\n    return false;\n  }\n\n  static bool SupportsAsyncScheduling() {\n    return false;\n  }\n\n  // CPU streams are not implemented and are silently ignored by CPU ops,\n  // return true to signal executor to schedule a CPU op\n  static bool IsStreamFree(const DeviceOption& /* unused */, int /* unused */) {\n    return true;\n  }\n\n protected:\n  // TODO(jiayq): instead of hard-coding a generator, make it more flexible.\n  int random_seed_{1701};\n  std::unique_ptr<rand_gen_type> random_generator_;\n  CAFFE2_API static MemoryAllocationReporter reporter_;\n\n private:\n  static void ReportAndDelete(void* ptr) {\n    reporter_.Delete(ptr);\n    GetCPUAllocator()->GetDeleter()(ptr);\n  }\n};\n\ntemplate<>\ninline void CPUContext::CopyBytes<CPUContext, CPUContext>(\n    size_t nbytes, const void* src, void* dst) {\n  if (nbytes == 0) {\n    return;\n  }\n  CAFFE_ENFORCE(src);\n  CAFFE_ENFORCE(dst);\n  memcpy(dst, src, nbytes);\n}\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_CORE_CONTEXT_H_\n"
  },
  {
    "path": "caffe2/core/context_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <algorithm>\n#include <atomic>\n#include <cstdlib>\n#include <string>\n#include <unordered_map>\n\n#include \"cub/util_allocator.cuh\"\n\n#include \"caffe2/core/asan.h\"\n#include \"caffe2/core/common_cudnn.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/string_utils.h\"\n\nCAFFE2_DEFINE_string(caffe2_cuda_memory_pool, \"\",\n              \"Sets the memory pool used by caffe2. Possible values are \"\n              \"none, cnmen and cub.\");\n\n// For description of CUB caching allocator configuration, see\n// https://nvlabs.github.io/cub/structcub_1_1_caching_device_allocator.html\nCAFFE2_DEFINE_int(caffe2_cub_bin_growth, 8,\n             \"If using cub as the memory allocator, sets the growth of bins \"\n             \"used by the cub pool.\");\nCAFFE2_DEFINE_int(caffe2_cub_min_bin, 3,\n             \"If using cub as the memory allocator, sets the min number of \"\n             \"bins.\");\nCAFFE2_DEFINE_int(caffe2_cub_max_bin, 10,\n             \"If using cub as the memory allocator, sets the max number of \"\n             \"bins.\");\nCAFFE2_DEFINE_int(caffe2_cub_max_managed_mb, 10 * 1024,\n             \"If using cub as the memory allocators, sets the maximum amount \"\n             \"of memory managed in gigabytes\");\nCAFFE2_DEFINE_bool(\n    caffe2_cub_print_allocation_events,\n    false,\n    \"If true CachingDeviceAllocator will print allocation and deallocation \"\n    \"events to stdout.\");\n\nCAFFE2_DEFINE_bool(\n    caffe2_gpu_memory_tracking,\n    false,\n    \"If set, logs changes in GPU memory allocations\");\nCAFFE2_DEFINE_int(\n    caffe2_gpu_memory_report_interval_mb,\n    128,\n    \"The threshold in MB on how frequently to report memory changes\");\n\nnamespace caffe2 {\n\nCAFFE_KNOWN_TYPE(Tensor<CUDAContext>);\n\nthread_local ThreadLocalCUDAObjects CUDAContext::cuda_objects_;\n\n// TODO(jiayq): these variables shouldn't be currently accessed during static\n// initialization. We should consider moving them to a Mayer's singleton to\n// be totally safe against SIOF.\n\n// Static global variables for setting up the memory pool.\nCudaMemoryPoolType g_cuda_memory_pool_type;\n\n// For cub allocator\nunique_ptr<cub::CachingDeviceAllocator> g_cub_allocator;\n// an unordered map that holds the map from the cuda memory pointer to the\n// device id that it is allocated from. This is used in the cuda memory pool\n// cases, where we need the device id to carry out the deletion.\n// Note(jiayq): an alternate approach is to use cudaGetPointerAttributes, but\n// that is usually quite slow. We might want to benchmark the speed difference\n// though.\n// Note(jiayq): another alternate approach is to augment the Tensor class that\n// would allow one to record the device id. However, this does not address any\n// non-tensor allocation and deallocation.\n// Ideally, a memory pool should already have the device id information, as\n// long as we are using UVA (as of CUDA 5 and later) so the addresses are\n// unique.\nstatic std::unordered_map<void*, uint8_t> g_cuda_device_affiliation;\n\n// Data structures for optional memory tracking. Access to these structures\n// is garded by the CUDAContext::mutex.\nstatic std::unordered_map<void*, long> g_size_map;\nstatic std::vector<long> g_total_by_gpu_map(CAFFE2_COMPILE_TIME_MAX_GPUS, 0);\nstatic std::vector<long> g_max_by_gpu_map(CAFFE2_COMPILE_TIME_MAX_GPUS, 0);\n\nstatic long g_total_mem = 0;\nstatic long g_last_rep = 0;\n\nCudaMemoryPoolType GetCudaMemoryPoolType() {\n  return g_cuda_memory_pool_type;\n}\n\nvector<TIndex> GetCUDATensorInfo(\n    const void* c,\n    bool* shares_data,\n    size_t* capacity,\n    DeviceOption* device) {\n  vector<TIndex> dims =\n      GetTensorInfo<CUDAContext>(c, shares_data, capacity, device);\n  const Tensor<CUDAContext>* tc = static_cast<const Tensor<CUDAContext>*>(c);\n  device->set_device_type(CUDA);\n  device->set_cuda_gpu_id(GetGPUIDForPointer(tc->raw_data()));\n  return dims;\n}\n\n///////////////////////////////////////////////////////////////////////////////\n// A wrapper to allow us to lazily initialize all cuda environments that Caffe\n// uses. This gets done the first time a caffe2::CUDAContext::New() gets called\n// which is probably the decisive indication that this caffe2 run is going to\n// use GPUs. We avoid cuda initialization with core/init.h functionalities so\n// that we have minimal resource impact in case we will need to run multiple\n// caffe2 instances on a GPU machine.\n///////////////////////////////////////////////////////////////////////////////\n\nstatic void Caffe2InitializeCuda() {\n  // If the current run does not have any cuda devices, do nothing.\n  if (!HasCudaGPU()) {\n    VLOG(1) << \"No cuda gpu present. Skipping.\";\n    return;\n  }\n  // Check if the number of GPUs matches the expected compile-time max number\n  // of GPUs.\n  CAFFE_ENFORCE_LE(\n      NumCudaDevices(),\n      CAFFE2_COMPILE_TIME_MAX_GPUS,\n      \"Number of CUDA devices on the machine is larger than the compiled \"\n      \"max number of gpus expected (\",\n      CAFFE2_COMPILE_TIME_MAX_GPUS,\n      \"). Increase that and recompile the caffe binary.\");\n\n  for (int i = 0; i < NumCudaDevices(); ++i) {\n    DeviceGuard g(i);\n    // Enable peer access.\n    const int peer_group = i / CAFFE2_CUDA_MAX_PEER_SIZE;\n    const int peer_start = peer_group * CAFFE2_CUDA_MAX_PEER_SIZE;\n    const int peer_end = std::min(\n        NumCudaDevices(), (peer_group + 1) * CAFFE2_CUDA_MAX_PEER_SIZE);\n    VLOG(1) << \"Enabling peer access within group #\" << peer_group\n            << \", from gpuid \" << peer_start << \" to \" << peer_end - 1\n            << \", for gpuid \" << i << \".\";\n\n    for (int j = peer_start; j < peer_end; ++j) {\n      if (i == j) continue;\n      int can_access;\n      CUDA_ENFORCE(cudaDeviceCanAccessPeer(&can_access, i, j));\n      if (can_access) {\n        VLOG(1) << \"Enabling peer access from \" << i << \" to \" << j;\n        // Note: just for future reference, the 0 here is not a gpu id, it is\n        // a reserved flag for cudaDeviceEnablePeerAccess that should always be\n        // zero currently.\n        CUDA_ENFORCE(cudaDeviceEnablePeerAccess(j, 0));\n      }\n    }\n  }\n\n  RegisterTypeCallFunction(\n    TypeMeta::Id<Tensor<CUDAContext>>(),\n    GetTensorType<CUDAContext>\n  );\n\n  RegisterTensorInfoFunction(\n      TypeMeta::Id<Tensor<CUDAContext>>(), GetCUDATensorInfo);\n\n  // Check the versions of cuDNN that were compiled and linked with are compatible\n  CheckCuDNNVersions();\n}\n\nstatic void SetUpCub() {\n  VLOG(1) << \"Setting up cub memory pool.\";\n  // Sets up the cub memory pool\n  try {\n    g_cub_allocator.reset(new cub::CachingDeviceAllocator(\n        FLAGS_caffe2_cub_bin_growth,\n        FLAGS_caffe2_cub_min_bin,\n        FLAGS_caffe2_cub_max_bin,\n        size_t(FLAGS_caffe2_cub_max_managed_mb) * 1024L * 1024L,\n        false,\n        FLAGS_caffe2_cub_print_allocation_events));\n  } catch (...) {\n    CAFFE_THROW(\"Some error happened at cub initialization.\");\n  }\n  VLOG(1) << \"Done setting up cub memory pool.\";\n}\n\nstatic void Caffe2SetCUDAMemoryPool() {\n  if (FLAGS_caffe2_cuda_memory_pool == \"\" ||\n      FLAGS_caffe2_cuda_memory_pool == \"none\") {\n    g_cuda_memory_pool_type = CudaMemoryPoolType::NONE;\n  } else if (FLAGS_caffe2_cuda_memory_pool == \"cnmem\") {\n    CAFFE_THROW(\"CNMEM is no longer used by Caffe2. Use cub instead. \"\n                \"This error message may go away in the future.\");\n  } else if (FLAGS_caffe2_cuda_memory_pool == \"cub\") {\n    // Sets up cub.\n    g_cuda_memory_pool_type = CudaMemoryPoolType::CUB;\n    SetUpCub();\n  } else {\n    CAFFE_THROW(\"Unrecognized cuda memory pool type: \",\n                FLAGS_caffe2_cuda_memory_pool);\n  }\n}\n\n// An initialization function that sets the CPU side to use pinned cpu\n// allocator.\nvoid Caffe2UsePinnedCPUAllocator() {\n#if CAFFE2_ASAN_ENABLED\n  // Note(jiayq): for more details, see\n  //     https://github.com/google/sanitizers/issues/629\n  LOG(WARNING) << \"There are known issues between address sanitizer and \"\n                  \"cudaMallocHost. As a result, caffe2 will not enable pinned \"\n                  \"memory allocation in asan mode. If you are expecting any \"\n                  \"behavior that depends on asan, be advised that it is not \"\n                  \"turned on.\";\n#else\n  if (!HasCudaGPU()) {\n    VLOG(1) << \"No GPU present. I won't use pinned allocator then.\";\n    return;\n  }\n  VLOG(1) << \"Caffe2 gpu: setting CPUAllocator to PinnedCPUAllocator.\";\n  SetCPUAllocator(new PinnedCPUAllocator());\n#endif\n}\n\n// Caffe2CudaInitializerHelper is a minimal struct whose sole purpose is to\n// detect the first hint that this Caffe2 run is going to use GPU: either\n// CUDAContext is initialized or CUDAContext::New is called. It then runs\n// all the related cuda initialization functions.\nnamespace {\nstruct Caffe2CudaInitializerHelper {\n  Caffe2CudaInitializerHelper() {\n    // We cannot use bool because nvcc changes bool to __nv_bool which does\n    // not have a std::atomic instantiation.\n    static std::atomic<char> first_call(1);\n    if (first_call.fetch_and((char)0)) {\n      Caffe2InitializeCuda();\n      Caffe2SetCUDAMemoryPool();\n      Caffe2UsePinnedCPUAllocator();\n    }\n  }\n};\n}  // namespace\n\n/**\n * A utility function to rectify the gpu id. If the context specifies the\n * gpu id to be -1, it means that we will just use the current gpu id when\n * the function is being called.\n */\nstatic inline int RectifyGPUID(const int gpu_id) {\n  return gpu_id == -1 ? CaffeCudaGetDevice() : gpu_id;\n}\n\nCUDAContext::CUDAContext(const int gpu_id)\n    : gpu_id_(RectifyGPUID(gpu_id)), random_seed_(RandomNumberSeed()) {\n  static Caffe2CudaInitializerHelper g_cuda_initializer_;\n}\n\nCUDAContext::CUDAContext(const DeviceOption& option)\n    : gpu_id_(\n          option.has_cuda_gpu_id() ? RectifyGPUID(option.cuda_gpu_id())\n                                   : CaffeCudaGetDevice()),\n      random_seed_(\n          option.has_random_seed() ? option.random_seed()\n                                   : RandomNumberSeed()) {\n  static Caffe2CudaInitializerHelper g_cuda_initializer_;\n  DCHECK_EQ(option.device_type(), CUDA);\n}\n\n// shared mutex to lock out alloc / free during NCCL launches\nstd::mutex& CUDAContext::mutex() {\n  static std::mutex m;\n  return m;\n}\n\nstd::vector<long> CUDAContext::TotalMemoryByGpu() {\n  std::lock_guard<std::mutex> lock(CUDAContext::mutex());\n  CAFFE_ENFORCE(\n      FLAGS_caffe2_gpu_memory_tracking,\n      \"Pass --caffe2_gpu_memory_tracking to enable memory stats\");\n  return g_total_by_gpu_map;\n}\n\nstd::vector<long> CUDAContext::MaxMemoryByGpu() {\n  std::lock_guard<std::mutex> lock(CUDAContext::mutex());\n  CAFFE_ENFORCE(\n      FLAGS_caffe2_gpu_memory_tracking,\n      \"Pass --caffe2_gpu_memory_tracking to enable memory stats\");\n  return g_max_by_gpu_map;\n}\n\nnamespace {\nvoid TrackMemoryAlloc(size_t nbytes) {\n  int this_gpu = CaffeCudaGetDevice();\n  g_total_by_gpu_map[this_gpu] += nbytes;\n  g_max_by_gpu_map[this_gpu] =\n      max(g_max_by_gpu_map[this_gpu], g_total_by_gpu_map[this_gpu]);\n  g_total_mem += nbytes;\n  if (g_total_mem - g_last_rep >\n      FLAGS_caffe2_gpu_memory_report_interval_mb * 1024 * 1024) {\n    for (int gpu = 0; gpu < g_total_by_gpu_map.size(); gpu++) {\n      long t = g_total_by_gpu_map[gpu];\n      long max_t = g_max_by_gpu_map[gpu];\n      if (max_t > 0) {\n        if (max_t != t) {\n          LOG(INFO) << \"GPU \" << gpu << \": \" << t / 1024 / 1024 << \" MB\"\n                    << \" (max: \" << max_t / 1024 / 1024 << \" MB)\";\n        } else {\n          LOG(INFO) << \"GPU \" << gpu << \": \" << t / 1024 / 1024 << \" MB\";\n        }\n      }\n    }\n    LOG(INFO) << \"Total: \" << g_total_mem / 1024 / 1024 << \" MB\";\n    g_last_rep = g_total_mem;\n  }\n}\n}\n\nstd::pair<void*, MemoryDeleter> CUDAContext::New(size_t nbytes) {\n  // Lock the mutex\n  std::lock_guard<std::mutex> lock(CUDAContext::mutex());\n  // A one-time caffe2 cuda initializer.\n  static Caffe2CudaInitializerHelper g_cuda_initializer_;\n  void* ptr = nullptr;\n\n  if (FLAGS_caffe2_gpu_memory_tracking) {\n    TrackMemoryAlloc(nbytes);\n  }\n  switch (g_cuda_memory_pool_type) {\n  case CudaMemoryPoolType::NONE:\n    CUDA_ENFORCE(cudaMalloc(&ptr, nbytes));\n    if (FLAGS_caffe2_gpu_memory_tracking) {\n      g_size_map[ptr] = nbytes;\n      g_cuda_device_affiliation[ptr] = CaffeCudaGetDevice();\n    }\n    return {ptr, Delete};\n  case CudaMemoryPoolType::CUB:\n    CUDA_ENFORCE(g_cub_allocator->DeviceAllocate(&ptr, nbytes));\n    g_cuda_device_affiliation[ptr] = CaffeCudaGetDevice();\n    VLOG(2) << \"CUB allocating pointer \" << ptr << \" on device \"\n            << CaffeCudaGetDevice();\n    if (FLAGS_caffe2_gpu_memory_tracking) {\n      g_size_map[ptr] = nbytes;\n    }\n    return {ptr, Delete};\n  }\n  return {nullptr, Delete};\n}\n\nvoid CUDAContext::Delete(void* ptr) {\n  // lock the mutex\n  std::lock_guard<std::mutex> lock(CUDAContext::mutex());\n\n  if (FLAGS_caffe2_gpu_memory_tracking) {\n    auto sz_it = g_size_map.find(ptr);\n    DCHECK(sz_it != g_size_map.end());\n    auto aff_it = g_cuda_device_affiliation.find(ptr);\n    DCHECK(aff_it != g_cuda_device_affiliation.end());\n    g_total_mem -= sz_it->second;\n    g_total_by_gpu_map[aff_it->second] -= sz_it->second;\n    g_size_map.erase(sz_it);\n  }\n\n  switch (g_cuda_memory_pool_type) {\n  case CudaMemoryPoolType::NONE: {\n    // If memory pool is not set up, use simple cudaFree.\n    cudaError_t error = cudaFree(ptr);\n    // For some reason, in Python runtime we sometimes delete a data pointer\n    // after the cuda runtime exits - this is odd but is probably caused by\n    // a static workspace that pycaffe2 uses, and the destruction got\n    // entangled in some race condition. Anyway, since cuda runtime is exiting\n    // anyway, we will not need to worry about memory leak, so we basically\n    // ignore it. This is definitely not ideal but works for now.\n    if (error != cudaSuccess && error != cudaErrorCudartUnloading) {\n      LOG(FATAL) << \"Error at: \" << __FILE__ << \":\" << __LINE__ << \": \"\n                 << cudaGetErrorString(error);\n    }\n\n    if (FLAGS_caffe2_gpu_memory_tracking) {\n      g_cuda_device_affiliation.erase(g_cuda_device_affiliation.find(ptr));\n    }\n\n    break; }\n  case CudaMemoryPoolType::CUB: {\n    auto it = g_cuda_device_affiliation.find(ptr);\n    DCHECK(it != g_cuda_device_affiliation.end());\n    VLOG(2) << \"CUB freeing pointer \" << ptr << \" on device \" << it->second;\n    CUDA_ENFORCE(g_cub_allocator->DeviceFree(it->second, ptr));\n    g_cuda_device_affiliation.erase(it);\n    break;\n  }\n  }\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/context_gpu.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_CONTEXT_GPU_H_\n#define CAFFE2_CORE_CONTEXT_GPU_H_\n\n#include <ctime>\n#include <mutex>\n\n#include \"caffe2/core/common_cudnn.h\"\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/numa.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\nenum class CudaMemoryPoolType {\n  NONE = 0,\n  CUB = 1,\n};\n\n/**\n * Gets the current memory pool type used by Caffe2.\n *\n * The memory pool is set up during caffe2's global initialization time.\n */\nCudaMemoryPoolType GetCudaMemoryPoolType();\n\n/**\n * A struct to host thread-local cuda objects.\n *\n * In Caffe2, each thread has its own non-default cuda stream as well as\n * related objects such as cublas and curand handles. This is achieved by\n * having the ThreadLocalCUDAObjects wrapper that takes care of allocating\n * and deallocating these objects at the thread scope. This class is solely\n * used inside CUDAContext and should not be used externally.\n */\nclass ThreadLocalCUDAObjects {\n  friend class CUDAContext;\n\n private:\n  ThreadLocalCUDAObjects() {\n    for (int i = 0; i < CAFFE2_COMPILE_TIME_MAX_GPUS; ++i) {\n      cuda_streams_[i] = vector<cudaStream_t>();\n      cublas_handles_[i] = vector<cublasHandle_t>();\n      cudnn_handles_[i] = vector<cudnnHandle_t>();\n    }\n  }\n\n  cudaStream_t GetStream(int gpu, int stream_id) {\n    vector<cudaStream_t>& gpu_streams = cuda_streams_[gpu];\n    if (gpu_streams.size() <= stream_id) {\n      gpu_streams.resize(stream_id + 1, nullptr);\n    }\n    if (!gpu_streams[stream_id]) {\n      DeviceGuard guard(gpu);\n      CUDA_ENFORCE(cudaStreamCreateWithFlags(\n          &gpu_streams[stream_id], cudaStreamNonBlocking));\n    }\n    return gpu_streams[stream_id];\n  }\n\n  cublasHandle_t GetHandle(int gpu, int stream_id) {\n    DeviceGuard guard(gpu);\n    vector<cublasHandle_t>& gpu_handles = cublas_handles_[gpu];\n    if (gpu_handles.size() <= stream_id) {\n      gpu_handles.resize(stream_id + 1, nullptr);\n    }\n    if (!gpu_handles[stream_id]) {\n      CUBLAS_ENFORCE(cublasCreate(&gpu_handles[stream_id]));\n      // The default is CUBLAS_POINTER_MODE_HOST. You can override\n      // it after obtaining the cublas handle, but do that with\n      // caution.\n      CUBLAS_ENFORCE(cublasSetPointerMode(\n          gpu_handles[stream_id], CUBLAS_POINTER_MODE_HOST));\n      CUBLAS_ENFORCE(\n          cublasSetStream(gpu_handles[stream_id], GetStream(gpu, stream_id)));\n    }\n    return gpu_handles[stream_id];\n  }\n\n  cudnnHandle_t GetCudnnHandle(int gpu, int stream_id) {\n    DeviceGuard guard(gpu);\n    vector<cudnnHandle_t>& gpu_handles = cudnn_handles_[gpu];\n    if (gpu_handles.size() <= stream_id) {\n      gpu_handles.resize(stream_id + 1, nullptr);\n    }\n    if (!gpu_handles[stream_id]) {\n      CUDNN_ENFORCE(cudnnCreate(&gpu_handles[stream_id]));\n      CUDNN_ENFORCE(\n          cudnnSetStream(gpu_handles[stream_id], GetStream(gpu, stream_id)));\n    }\n    return gpu_handles[stream_id];\n  }\n\n  ~ThreadLocalCUDAObjects() noexcept {\n    for (int i = 0; i < CAFFE2_COMPILE_TIME_MAX_GPUS; ++i) {\n      for (auto& handle : cublas_handles_[i]) {\n        if (handle) {\n          CUBLAS_CHECK(cublasDestroy(handle));\n        }\n      }\n      for (auto& stream : cuda_streams_[i]) {\n        if (stream) {\n          CUDA_CHECK(cudaStreamDestroy(stream));\n        }\n      }\n      for (auto& handle : cudnn_handles_[i]) {\n        if (handle) {\n          CUDNN_CHECK(cudnnDestroy(handle));\n        }\n      }\n    }\n  }\n  vector<cudaStream_t> cuda_streams_[CAFFE2_COMPILE_TIME_MAX_GPUS];\n  vector<cublasHandle_t> cublas_handles_[CAFFE2_COMPILE_TIME_MAX_GPUS];\n  vector<cudnnHandle_t> cudnn_handles_[CAFFE2_COMPILE_TIME_MAX_GPUS];\n};\n\nclass CUDAContext final {\n public:\n  // The default cuda context constructor.\n  explicit CUDAContext(const int gpu_id = -1);\n  explicit CUDAContext(const DeviceOption& option);\n\n  ~CUDAContext() {\n    if (curand_generator_) {\n      CURAND_CHECK(curandDestroyGenerator(curand_generator_));\n    }\n    FinishDeviceComputation();\n  }\n\n  inline void SwitchToDevice(int stream_id) {\n    set_stream_id(stream_id);\n    CaffeCudaSetDevice(gpu_id_);\n  }\n  inline void SwitchToDevice() {\n    SwitchToDevice(0);\n  }\n\n  inline void WaitEvent(const Event& ev) {\n    ev.Wait(CUDA, this);\n  }\n\n  inline void Record(Event* ev, const char* err_msg = nullptr) const {\n    CAFFE_ENFORCE(ev, \"Event must not be null.\");\n    ev->Record(CUDA, this, err_msg);\n  }\n\n  void FinishDeviceComputation() {\n    cudaStreamSynchronize(cuda_objects_.GetStream(gpu_id_, stream_id_));\n    cudaError_t error = cudaGetLastError();\n    if (error != cudaSuccess) {\n      CAFFE_THROW(\"Encountered CUDA error: \", cudaGetErrorString(error));\n    }\n  }\n\n  inline int cuda_gpu_id() const {\n    return gpu_id_;\n  }\n\n  inline cudaStream_t cuda_stream() {\n    return cuda_stream(gpu_id_, stream_id_);\n  }\n\n  inline cudaStream_t cuda_stream() const {\n    return cuda_stream(gpu_id_, stream_id_);\n  }\n\n  static cudaStream_t cuda_stream(int gpu_id, int stream_id) {\n    return cuda_objects_.GetStream(gpu_id, stream_id);\n  }\n\n  cublasHandle_t cublas_handle() {\n    return cuda_objects_.GetHandle(gpu_id_, stream_id_);\n  }\n\n  cudnnHandle_t cudnn_handle() {\n    return cuda_objects_.GetCudnnHandle(gpu_id_, stream_id_);\n  }\n\n  curandGenerator_t& curand_generator() {\n    if (!curand_generator_) {\n      DeviceGuard guard(gpu_id_);\n      CURAND_ENFORCE(\n          curandCreateGenerator(&curand_generator_, CURAND_RNG_PSEUDO_DEFAULT));\n      CURAND_ENFORCE(\n          curandSetPseudoRandomGeneratorSeed(curand_generator_, random_seed_));\n      CHECK_NOTNULL(curand_generator_);\n    }\n    CURAND_ENFORCE(curandSetStream(curand_generator_, cuda_stream()));\n    return curand_generator_;\n  }\n\n  static std::pair<void*, MemoryDeleter> New(size_t nbytes);\n\n  // Get a mutex to lock out cudaMalloc / cudaFree calls when\n  // NCCL kernels are being launched. Should remove threat of\n  // deadlocks\n  static std::mutex& mutex();\n\n  // Functions to query memory stats. Only available if flag\n  // --caffe2_gpu_memory_tracking is enabled.\n  static std::vector<long> TotalMemoryByGpu();\n  static std::vector<long> MaxMemoryByGpu();\n\n  template <class SrcContext, class DstContext>\n  inline void CopyBytes(size_t nbytes, const void* src, void* dst) {\n    CUDA_ENFORCE(cudaMemcpyAsync(\n        dst,\n        src,\n        nbytes,\n        cudaMemcpyDefault,\n        cuda_objects_.GetStream(gpu_id_, stream_id_)));\n  }\n\n  template <typename T, class SrcContext, class DstContext>\n  inline void Copy(int n, const T* src, T* dst) {\n    CopyBytes<SrcContext, DstContext>(n * sizeof(T),\n                                 static_cast<const void*>(src),\n                                 static_cast<void*>(dst));\n  }\n\n  template <class SrcContext, class DstContext>\n  inline void\n  CopyItems(const TypeMeta& meta, size_t n, const void* src, void* dst) {\n    CAFFE_ENFORCE(!meta.copy(), \"CUDAContext requires fundamental types.\");\n    CopyBytes<SrcContext, DstContext>(n * meta.itemsize(), src, dst);\n  }\n\n  // By default CUDA operators have async device parts\n  static bool HasAsyncPartDefault() {\n    return true;\n  }\n\n  static bool SupportsAsyncScheduling() {\n    return true;\n  }\n\n  static bool IsStreamFree(const DeviceOption& option, int stream_id) {\n    auto stream = CUDAContext::cuda_stream(option.cuda_gpu_id(), stream_id);\n    return cudaStreamQuery(stream) == cudaSuccess;\n  }\n\n protected:\n  static void Delete(void* data);\n  void set_stream_id(int stream_id) {\n    stream_id_ = stream_id;\n  }\n\n  int gpu_id_;\n  int stream_id_ = 0;\n  int random_seed_;\n  curandGenerator_t curand_generator_{nullptr};\n  static thread_local ThreadLocalCUDAObjects cuda_objects_;\n};\n\n// For the CPU context, we also allow a (probably expensive) function\n// to copy the data from a cuda context. Inside the function, we create\n// a temporary CUDAContext object to carry out the copy. From the caller's\n// side, these functions are synchronous with respect to the host, similar\n// to a normal CPUContext::CopyBytes<CPUContext, CPUContext> call.\ntemplate<>\ninline void CPUContext::CopyBytes<CUDAContext, CPUContext>(\n    size_t nbytes, const void* src, void* dst) {\n  CUDAContext context(GetGPUIDForPointer(src));\n  context.CopyBytes<CUDAContext, CPUContext>(nbytes, src, dst);\n}\ntemplate<>\ninline void CPUContext::CopyBytes<CPUContext, CUDAContext>(\n    size_t nbytes, const void* src, void* dst) {\n  CUDAContext context(GetGPUIDForPointer(dst));\n  context.CopyBytes<CPUContext, CUDAContext>(nbytes, src, dst);\n}\n\n/**\n * An allocator that does the CPU memory allocation with pinned memory.\n *\n * This is needed because if we want to do any asynchronous cuda memcpy,\n * the underlying CPU memory also needs to be allocated into pinned memory\n * space. As a result, whenever Caffe2 is built with GPU and there is\n * GPU present during runtime, at global initialization time we will set\n * the CPU memory allocator to allocate pinned memory.\n */\nstruct PinnedCPUAllocator final : CPUAllocator {\n  PinnedCPUAllocator() {}\n  ~PinnedCPUAllocator() override {}\n  std::pair<void*, MemoryDeleter> New(size_t nbytes) override {\n    void* data;\n    std::lock_guard<std::mutex> lock(CUDAContext::mutex());\n    if (IsNUMAEnabled()) {\n      auto ptr_and_deleter = baseAllocator_.New(nbytes);\n      data = ptr_and_deleter.first;\n      CAFFE_ENFORCE(data);\n      CUDA_ENFORCE(cudaHostRegister(data, nbytes, cudaHostRegisterDefault));\n    } else {\n      CUDA_ENFORCE(cudaMallocHost(&data, nbytes));\n    }\n    memset(data, 0, nbytes);\n    return {data, Delete};\n  }\n\n  MemoryDeleter GetDeleter() override {\n    return Delete;\n  }\n\n private:\n  static void Delete(void* data) {\n    // Caffe2 uses a lazy way to figure out if one is actually going to use GPUs\n    // or not. If a CUDAContext::New() call is made, inside the CUDAContext\n    // function we will switch the cpu side allocator to a PinnedCPUAllocator.\n    // But, if one calls CPUContext::New() before any cuda allocations,\n    // PinnedCPUAllocator can still delete the corresponding memory.\n    std::lock_guard<std::mutex> lock(CUDAContext::mutex());\n    if (IsNUMAEnabled()) {\n      CUDA_ENFORCE(cudaHostUnregister(data));\n      DefaultCPUAllocator::Delete(data);\n    } else {\n      cudaError_t err = cudaFreeHost(data);\n      if (err == cudaErrorInvalidValue) {\n        free(data);\n        // Calling cudaGetLastError will reset the cuda error.\n        cudaGetLastError();\n      } else {\n        // For all other errors, still do a cuda check.\n        CUDA_ENFORCE(err);\n      }\n    }\n  }\n\n  DefaultCPUAllocator baseAllocator_;\n};\n\n// For simplicity, we will typedef Tensor<CPUContext> to TensorCPU.\ntypedef Tensor<CUDAContext> TensorCUDA;\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_CORE_CONTEXT_GPU_H_\n"
  },
  {
    "path": "caffe2/core/context_gpu_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <chrono>\n#include <future>\n#include <random>\n#include <thread>\n#include <array>\n\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include <gtest/gtest.h>\n\nCAFFE2_DECLARE_bool(caffe2_cuda_full_device_control);\n\nnamespace caffe2 {\n\nnamespace {\nstd::shared_ptr<void> shared_from_new(std::pair<void*, MemoryDeleter>&& p) {\n  return std::shared_ptr<void>(p.first, std::move(p.second));\n}\n}\n\nTEST(CUDATest, HasCudaRuntime) {\n  EXPECT_TRUE(HasCudaRuntime());\n}\n\nTEST(CUDAContextTest, TestAllocDealloc) {\n  if (!HasCudaGPU()) return;\n  CUDAContext context(0);\n  context.SwitchToDevice();\n  auto data = shared_from_new(CUDAContext::New(10 * sizeof(float)));\n  EXPECT_NE(data.get(), nullptr);\n}\n\nTEST(CUDAContextTest, TestSetGetDeviceWithoutCaffeMode) {\n  // For a while, set full device control to be true.\n  for (int i = 0; i < NumCudaDevices(); ++i) {\n    CaffeCudaSetDevice(i);\n    EXPECT_EQ(CaffeCudaGetDevice(), i);\n  }\n  for (int i = NumCudaDevices() - 1; i >= 0; --i) {\n    CaffeCudaSetDevice(i);\n    EXPECT_EQ(CaffeCudaGetDevice(), i);\n  }\n}\n\nTEST(CUDAContextTest, TestSetGetDeviceWithCaffeMode) {\n  // For a while, set full device control to be true.\n  FLAGS_caffe2_cuda_full_device_control = true;\n  for (int i = 0; i < NumCudaDevices(); ++i) {\n    CaffeCudaSetDevice(i);\n    EXPECT_EQ(CaffeCudaGetDevice(), i);\n  }\n  for (int i = NumCudaDevices() - 1; i >= 0; --i) {\n    CaffeCudaSetDevice(i);\n    EXPECT_EQ(CaffeCudaGetDevice(), i);\n  }\n  FLAGS_caffe2_cuda_full_device_control = false;\n}\n\nTEST(CUDAContextTest, MemoryPoolAllocateDealloc) {\n  if (!HasCudaGPU())\n    return;\n  if (GetCudaMemoryPoolType() == CudaMemoryPoolType::NONE) {\n    LOG(ERROR) << \"Choose a memory type that is not none to test memory pool.\";\n    return;\n  }\n  const int nbytes = 1048576;\n  for (int i = 0; i < NumCudaDevices(); ++i) {\n    LOG(INFO) << \"Device \" << i << \" of \" << NumCudaDevices();\n    DeviceGuard guard(i);\n    auto allocated = shared_from_new(CUDAContext::New(nbytes));\n    EXPECT_NE(allocated, nullptr);\n    cudaPointerAttributes attr;\n    CUDA_ENFORCE(cudaPointerGetAttributes(&attr, allocated.get()));\n    EXPECT_EQ(attr.memoryType, cudaMemoryTypeDevice);\n    EXPECT_EQ(attr.device, i);\n    void* prev_allocated = allocated.get();\n    allocated.reset();\n    auto new_allocated = shared_from_new(CUDAContext::New(nbytes));\n    // With a pool, the above allocation should yield the same address.\n    EXPECT_EQ(new_allocated.get(), prev_allocated);\n    // But, if we are allocating something larger, we will have a different\n    // chunk of memory.\n    auto larger_allocated = shared_from_new(CUDAContext::New(nbytes * 2));\n    EXPECT_NE(larger_allocated.get(), prev_allocated);\n  }\n}\n\ncudaStream_t getStreamForHandle(cublasHandle_t handle) {\n  cudaStream_t stream = nullptr;\n  CUBLAS_ENFORCE(cublasGetStream(handle, &stream));\n  CHECK_NOTNULL(stream);\n  return stream;\n}\n\nTEST(CUDAContextTest, TestSameThreadSameObject) {\n  if (!HasCudaGPU()) return;\n  CUDAContext context_a(0);\n  CUDAContext context_b(0);\n  EXPECT_EQ(context_a.cuda_stream(), context_b.cuda_stream());\n  EXPECT_EQ(context_a.cublas_handle(), context_b.cublas_handle());\n  EXPECT_EQ(\n      context_a.cuda_stream(), getStreamForHandle(context_b.cublas_handle()));\n  // CuRAND generators are context-local.\n  EXPECT_NE(context_a.curand_generator(), context_b.curand_generator());\n}\n\nTEST(CUDAContextTest, TestSameThreadDifferntObjectIfDifferentDevices) {\n  if (NumCudaDevices() > 1) {\n    CUDAContext context_a(0);\n    CUDAContext context_b(1);\n    EXPECT_NE(context_a.cuda_stream(), context_b.cuda_stream());\n    EXPECT_NE(context_a.cublas_handle(), context_b.cublas_handle());\n    EXPECT_NE(\n        context_a.cuda_stream(), getStreamForHandle(context_b.cublas_handle()));\n    EXPECT_NE(context_a.curand_generator(), context_b.curand_generator());\n  }\n}\n\nnamespace {\n// A test function to return a stream address from a temp CUDA context. You\n// should not use that stream though, because the actual stream is destroyed\n// after thread exit.\nvoid TEST_GetStreamAddress(cudaStream_t* ptr) {\n  CUDAContext context(0);\n  *ptr = context.cuda_stream();\n  // Sleep for a while so we have concurrent thread executions\n  std::this_thread::sleep_for(std::chrono::seconds(1));\n}\n}  // namespace\n\nTEST(CUDAContextTest, TestDifferntThreadDifferentobject) {\n  if (!HasCudaGPU()) return;\n  std::array<cudaStream_t, 2> temp = {0};\n  // Same thread\n  TEST_GetStreamAddress(&temp[0]);\n  TEST_GetStreamAddress(&temp[1]);\n  EXPECT_TRUE(temp[0] != nullptr);\n  EXPECT_TRUE(temp[1] != nullptr);\n  EXPECT_EQ(temp[0], temp[1]);\n  // Different threads\n  std::thread thread_a(TEST_GetStreamAddress, &temp[0]);\n  std::thread thread_b(TEST_GetStreamAddress, &temp[1]);\n  thread_a.join();\n  thread_b.join();\n  EXPECT_TRUE(temp[0] != nullptr);\n  EXPECT_TRUE(temp[1] != nullptr);\n  EXPECT_NE(temp[0], temp[1]);\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/context_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <random>\n\n#include <gtest/gtest.h>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\nTEST(CPUContextTest, TestAllocAlignment) {\n  for (int i = 1; i < 10; ++i) {\n    auto data = CPUContext::New(i);\n    EXPECT_EQ((reinterpret_cast<size_t>(data.first) % gCaffe2Alignment), 0);\n    data.second(data.first);\n  }\n}\n\nTEST(CPUContextTest, TestAllocDealloc) {\n  auto data_and_deleter = CPUContext::New(10 * sizeof(float));\n  float* data = static_cast<float*>(data_and_deleter.first);\n  EXPECT_NE(data, nullptr);\n  auto dst_data_and_deleter = CPUContext::New(10 * sizeof(float));\n  float* dst_data = static_cast<float*>(dst_data_and_deleter.first);\n  EXPECT_NE(dst_data, nullptr);\n  for (int i = 0; i < 10; ++i) {\n    data[i] = i;\n  }\n  DeviceOption option;\n  CPUContext context(option);\n  context.Copy<float, CPUContext, CPUContext>(10, data, dst_data);\n  for (int i = 0; i < 10; ++i) {\n    EXPECT_FLOAT_EQ(dst_data[i], i);\n  }\n  data_and_deleter.second(data);\n  dst_data_and_deleter.second(dst_data);\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/cudnn_wrappers.h",
    "content": "// Copyright 2004-present Facebook. All Rights Reserved.\n\n#ifndef CAFFE2_CORE_CUDNN_WRAPPERS_H_\n#define CAFFE2_CORE_CUDNN_WRAPPERS_H_\n\n#include \"caffe2/core/common_cudnn.h\"\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\nclass CuDNNWrapper;\n\n/**\n * CuDNNWorkspace is a wrapper around a raw cuda pointer that holds the cudnn\n * scratch space. This struct is meant to be only used in CuDNNWrapper to\n * provide a program-wide scratch space for CuDNN. The reason behind it is that\n * cudnn function calls are usually very efficient, hence one probably does not\n * want to run multiple cudnn calls at the same time. As a result, one should\n * not need more than one cudnn workspace per device.\n */\nstruct CuDNNWorkspace {\n  ~CuDNNWorkspace() noexcept {}\n\n  void* get(size_t nbytes) {\n    if (nbytes_ < nbytes) {\n      reset();\n      auto data_and_deleter = CUDAContext::New(nbytes);\n      data_ = {data_and_deleter.first, data_and_deleter.second};\n      nbytes_ = nbytes;\n    }\n    CAFFE_ENFORCE_GE(nbytes_, nbytes);\n    return data_.get();\n  }\n\n  void reset() {\n    data_ = nullptr;\n    nbytes_ = 0;\n  }\n\n private:\n  std::unique_ptr<void, MemoryDeleter> data_{nullptr, NoDelete};\n  size_t nbytes_{0};\n};\n\n// CuDNNState is the owner of the CuDNNWorkspace, and serializes all\n// executions of operations that use the state onto it's own stream\n// (so multiple Net workers can reuse the same workspace from\n// different threads and CUDA streams).\nclass CuDNNState {\n public:\n  explicit CuDNNState(size_t gpu_id) : gpu_id_(gpu_id) {\n    DeviceGuard g(gpu_id_);\n    CUDNN_ENFORCE(cudnnCreate(&cudnn_handle_));\n    CUDA_ENFORCE(cudaEventCreate(&before_));\n    CUDA_ENFORCE(cudaEventCreate(&after_));\n    CUDA_ENFORCE(cudaStreamCreate(&stream_));\n    CUDNN_ENFORCE(cudnnSetStream(cudnn_handle_, stream_));\n  }\n\n  ~CuDNNState() noexcept {\n    DeviceGuard g(gpu_id_);\n    CUDNN_CHECK(cudnnDestroy(cudnn_handle_));\n    CUDA_CHECK(cudaStreamDestroy(stream_));\n    CUDA_CHECK(cudaEventDestroy(after_));\n    CUDA_CHECK(cudaEventDestroy(before_));\n  }\n\n  cudnnHandle_t& cudnn_handle() {\n    return cudnn_handle_;\n  }\n\n  CuDNNWorkspace& workspace() {\n    return workspace_;\n  }\n\n  template <typename F>\n  void execute(cudaStream_t stream, F&& f) {\n    CUDA_ENFORCE(cudaEventRecord(before_, stream));\n    CUDA_ENFORCE(cudaStreamWaitEvent(stream_, before_, 0));\n    f(this);\n    CUDA_ENFORCE(cudaEventRecord(after_, stream_));\n    CUDA_ENFORCE(cudaStreamWaitEvent(stream, after_, 0));\n  }\n\n private:\n  cudnnHandle_t cudnn_handle_{nullptr};\n  cudaEvent_t before_{nullptr};\n  cudaEvent_t after_{nullptr};\n  cudaStream_t stream_{nullptr};\n  CuDNNWorkspace workspace_;\n  size_t gpu_id_{0};\n  DISABLE_COPY_AND_ASSIGN(CuDNNState);\n};\n\n/**\n * CuDNNWrapper is a class that wraps the cudnn handles and cudnn workspaces.\n *\n * The wrapper ensures that for each thread and each gpu, there is one\n * identical cudnn handle, which is also associated with the thread-local\n * per-device cuda stream. The wrapper also hosts the device-specific cudnn\n * workspace (scratch space for some cudnn functions).\n *\n */\nclass CuDNNWrapper {\n public:\n  /**\n   * Creates a cudnn wrapper associated with a CUDAContext object. Note that\n   * the CUDAContext object should outlive the CuDNNWrapper.\n   */\n  explicit CuDNNWrapper(CUDAContext* context) : context_(context) {}\n\n  /**\n   * Returns the inline cudnn handle that executes on the current\n   * thread's cuda_stream.\n   */\n  cudnnHandle_t inline_cudnn_handle() {\n    return context_->cudnn_handle();\n  }\n\n  // Executes the closure F on the CuDNNState associated with state_idx\n  template <typename F>\n  void with_cudnn_state(size_t state_idx, F&& f) {\n    CAFFE_ENFORCE(\n        state_idx < CAFFE2_COMPILE_TIME_MAX_CUDNN_STATES, \"Invalid state_idx\");\n    auto& sync_state = cudnn_states()[context_->cuda_gpu_id()][state_idx];\n\n    DeviceGuard dg(context_->cuda_gpu_id());\n\n    // We need to serialize execution on the CuDNNState as we can't\n    // allow multiple threads to race through the cudaEventRecord\n    // calls (so a worker thread might wait on another worker thread's\n    // execution)\n    std::lock_guard<std::mutex> g(sync_state.mutex);\n    if (!sync_state.state.get()) {\n      sync_state.state.reset(new CuDNNState(context_->cuda_gpu_id()));\n    }\n    CHECK_NOTNULL(sync_state.state.get())->execute(context_->cuda_stream(), f);\n  }\n\n protected:\n  // Pointer to an external cuda context that the cudnn wrapper will use.\n  CUDAContext* context_;\n\n  static constexpr size_t CAFFE2_COMPILE_TIME_MAX_CUDNN_STATES = 4;\n\n  struct SyncedCuDNNState {\n    std::mutex mutex;\n    std::unique_ptr<CuDNNState> state;\n  };\n\n  using PerGPUCuDNNStates = std::array<\n      std::array<SyncedCuDNNState, CAFFE2_COMPILE_TIME_MAX_CUDNN_STATES>,\n      CAFFE2_COMPILE_TIME_MAX_GPUS>;\n  static PerGPUCuDNNStates& cudnn_states();\n\n  DISABLE_COPY_AND_ASSIGN(CuDNNWrapper);\n};\n\n}; // namespace caffe2\n\n#endif\n"
  },
  {
    "path": "caffe2/core/db.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/db.h\"\n\n#include <mutex>\n\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\n\nCAFFE_KNOWN_TYPE(db::DBReader);\nCAFFE_KNOWN_TYPE(db::Cursor);\n\nnamespace db {\n\nCAFFE_DEFINE_REGISTRY(Caffe2DBRegistry, DB, const string&, Mode);\n\n// Below, we provide a bare minimum database \"minidb\" as a reference\n// implementation as well as a portable choice to store data.\n// Note that the MiniDB classes are not exposed via a header file - they should\n// be created directly via the db interface. See MiniDB for details.\n\nclass MiniDBCursor : public Cursor {\n public:\n  explicit MiniDBCursor(FILE* f, std::mutex* mutex)\n    : file_(f), lock_(*mutex), valid_(true) {\n    // We call Next() to read in the first entry.\n    Next();\n  }\n  ~MiniDBCursor() {}\n\n  void Seek(const string& /*key*/) override {\n    LOG(FATAL) << \"MiniDB does not support seeking to a specific key.\";\n  }\n\n  void SeekToFirst() override {\n    fseek(file_, 0, SEEK_SET);\n    CAFFE_ENFORCE(!feof(file_), \"Hmm, empty file?\");\n    // Read the first item.\n    valid_ = true;\n    Next();\n  }\n\n  void Next() override {\n    // First, read in the key and value length.\n    if (fread(&key_len_, sizeof(int), 1, file_) == 0) {\n      // Reaching EOF.\n      VLOG(1) << \"EOF reached, setting valid to false\";\n      valid_ = false;\n      return;\n    }\n    CAFFE_ENFORCE_EQ(fread(&value_len_, sizeof(int), 1, file_), 1);\n    CAFFE_ENFORCE_GT(key_len_, 0);\n    CAFFE_ENFORCE_GT(value_len_, 0);\n    // Resize if the key and value len is larger than the current one.\n    if (key_len_ > key_.size()) {\n      key_.resize(key_len_);\n    }\n    if (value_len_ > value_.size()) {\n      value_.resize(value_len_);\n    }\n    // Actually read in the contents.\n    CAFFE_ENFORCE_EQ(\n        fread(key_.data(), sizeof(char), key_len_, file_), key_len_);\n    CAFFE_ENFORCE_EQ(\n        fread(value_.data(), sizeof(char), value_len_, file_), value_len_);\n    // Note(Yangqing): as we read the file, the cursor naturally moves to the\n    // beginning of the next entry.\n  }\n\n  string key() override {\n    CAFFE_ENFORCE(valid_, \"Cursor is at invalid location!\");\n    return string(key_.data(), key_len_);\n  }\n\n  string value() override {\n    CAFFE_ENFORCE(valid_, \"Cursor is at invalid location!\");\n    return string(value_.data(), value_len_);\n  }\n\n  bool Valid() override { return valid_; }\n\n private:\n  FILE* file_;\n  std::lock_guard<std::mutex> lock_;\n  bool valid_;\n  int key_len_;\n  vector<char> key_;\n  int value_len_;\n  vector<char> value_;\n};\n\nclass MiniDBTransaction : public Transaction {\n public:\n  explicit MiniDBTransaction(FILE* f, std::mutex* mutex)\n    : file_(f), lock_(*mutex) {}\n  ~MiniDBTransaction() {\n    Commit();\n  }\n\n  void Put(const string& key, const string& value) override {\n    int key_len = key.size();\n    int value_len = value.size();\n    CAFFE_ENFORCE_EQ(fwrite(&key_len, sizeof(int), 1, file_), 1);\n    CAFFE_ENFORCE_EQ(fwrite(&value_len, sizeof(int), 1, file_), 1);\n    CAFFE_ENFORCE_EQ(\n        fwrite(key.c_str(), sizeof(char), key_len, file_), key_len);\n    CAFFE_ENFORCE_EQ(\n        fwrite(value.c_str(), sizeof(char), value_len, file_), value_len);\n  }\n\n  void Commit() override {\n    if (file_ != nullptr) {\n      CAFFE_ENFORCE_EQ(fflush(file_), 0);\n      file_ = nullptr;\n    }\n  }\n\n private:\n  FILE* file_;\n  std::lock_guard<std::mutex> lock_;\n\n  DISABLE_COPY_AND_ASSIGN(MiniDBTransaction);\n};\n\nclass MiniDB : public DB {\n public:\n  MiniDB(const string& source, Mode mode) : DB(source, mode), file_(nullptr) {\n    switch (mode) {\n      case NEW:\n        file_ = fopen(source.c_str(), \"wb\");\n        break;\n      case WRITE:\n        file_ = fopen(source.c_str(), \"ab\");\n        fseek(file_, 0, SEEK_END);\n        break;\n      case READ:\n        file_ = fopen(source.c_str(), \"rb\");\n        break;\n    }\n    CAFFE_ENFORCE(file_, \"Cannot open file: \" + source);\n    VLOG(1) << \"Opened MiniDB \" << source;\n  }\n  ~MiniDB() { Close(); }\n\n  void Close() override {\n    if (file_) {\n      fclose(file_);\n    }\n    file_ = nullptr;\n  }\n\n  unique_ptr<Cursor> NewCursor() override {\n    CAFFE_ENFORCE_EQ(this->mode_, READ);\n    return make_unique<MiniDBCursor>(file_, &file_access_mutex_);\n  }\n\n  unique_ptr<Transaction> NewTransaction() override {\n    CAFFE_ENFORCE(this->mode_ == NEW || this->mode_ == WRITE);\n    return make_unique<MiniDBTransaction>(file_, &file_access_mutex_);\n  }\n\n private:\n  FILE* file_;\n  // access mutex makes sure we don't have multiple cursors/transactions\n  // reading the same file.\n  std::mutex file_access_mutex_;\n};\n\nREGISTER_CAFFE2_DB(MiniDB, MiniDB);\nREGISTER_CAFFE2_DB(minidb, MiniDB);\n\nvoid DBReaderSerializer::Serialize(\n    const Blob& blob,\n    const string& name,\n    BlobSerializerBase::SerializationAcceptor acceptor) {\n  CAFFE_ENFORCE(blob.IsType<DBReader>());\n  auto& reader = blob.Get<DBReader>();\n  DBReaderProto proto;\n  proto.set_name(name);\n  proto.set_source(reader.source_);\n  proto.set_db_type(reader.db_type_);\n  if (reader.cursor() && reader.cursor()->SupportsSeek()) {\n    proto.set_key(reader.cursor()->key());\n  }\n  BlobProto blob_proto;\n  blob_proto.set_name(name);\n  blob_proto.set_type(\"DBReader\");\n  blob_proto.set_content(proto.SerializeAsString());\n  acceptor(name, blob_proto.SerializeAsString());\n}\n\nvoid DBReaderDeserializer::Deserialize(const BlobProto& proto, Blob* blob) {\n  DBReaderProto reader_proto;\n  CAFFE_ENFORCE(\n      reader_proto.ParseFromString(proto.content()),\n      \"Cannot parse content into a DBReaderProto.\");\n  blob->Reset(new DBReader(reader_proto));\n}\n\nnamespace {\n// Serialize TensorCPU.\nREGISTER_BLOB_SERIALIZER((TypeMeta::Id<DBReader>()),\n                         DBReaderSerializer);\nREGISTER_BLOB_DESERIALIZER(DBReader, DBReaderDeserializer);\n}  // namespace\n\n}  // namespace db\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/db.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_DB_H_\n#define CAFFE2_CORE_DB_H_\n\n#include <mutex>\n\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\nnamespace db {\n\n/**\n * The mode of the database, whether we are doing a read, write, or creating\n * a new database.\n */\nenum Mode { READ, WRITE, NEW };\n\n/**\n * An abstract class for the cursor of the database while reading.\n */\nclass Cursor {\n public:\n  Cursor() { }\n  virtual ~Cursor() { }\n  /**\n   * Seek to a specific key (or if the key does not exist, seek to the\n   * immediate next). This is optional for dbs, and in default, SupportsSeek()\n   * returns false meaning that the db cursor does not support it.\n   */\n  virtual void Seek(const string& key) = 0;\n  virtual bool SupportsSeek() { return false; }\n  /**\n   * Seek to the first key in the database.\n   */\n  virtual void SeekToFirst() = 0;\n  /**\n   * Go to the next location in the database.\n   */\n  virtual void Next() = 0;\n  /**\n   * Returns the current key.\n   */\n  virtual string key() = 0;\n  /**\n   * Returns the current value.\n   */\n  virtual string value() = 0;\n  /**\n   * Returns whether the current location is valid - for example, if we have\n   * reached the end of the database, return false.\n   */\n  virtual bool Valid() = 0;\n\n  DISABLE_COPY_AND_ASSIGN(Cursor);\n};\n\n/**\n * An abstract class for the current database transaction while writing.\n */\nclass Transaction {\n public:\n  Transaction() { }\n  virtual ~Transaction() { }\n  /**\n   * Puts the key value pair to the database.\n   */\n  virtual void Put(const string& key, const string& value) = 0;\n  /**\n   * Commits the current writes.\n   */\n  virtual void Commit() = 0;\n\n  DISABLE_COPY_AND_ASSIGN(Transaction);\n};\n\n/**\n * An abstract class for accessing a database of key-value pairs.\n */\nclass DB {\n public:\n  DB(const string& /*source*/, Mode mode) : mode_(mode) {}\n  virtual ~DB() { }\n  /**\n   * Closes the database.\n   */\n  virtual void Close() = 0;\n  /**\n   * Returns a cursor to read the database. The caller takes the ownership of\n   * the pointer.\n   */\n  virtual std::unique_ptr<Cursor> NewCursor() = 0;\n  /**\n   * Returns a transaction to write data to the database. The caller takes the\n   * ownership of the pointer.\n   */\n  virtual std::unique_ptr<Transaction> NewTransaction() = 0;\n\n protected:\n  Mode mode_;\n\n  DISABLE_COPY_AND_ASSIGN(DB);\n};\n\n// Database classes are registered by their names so we can do optional\n// dependencies.\nCAFFE_DECLARE_REGISTRY(Caffe2DBRegistry, DB, const string&, Mode);\n#define REGISTER_CAFFE2_DB(name, ...) \\\n  CAFFE_REGISTER_CLASS(Caffe2DBRegistry, name, __VA_ARGS__)\n\n/**\n * Returns a database object of the given database type, source and mode. The\n * caller takes the ownership of the pointer. If the database type is not\n * supported, a nullptr is returned. The caller is responsible for examining the\n * validity of the pointer.\n */\ninline unique_ptr<DB> CreateDB(\n    const string& db_type, const string& source, Mode mode) {\n  auto result = Caffe2DBRegistry()->Create(db_type, source, mode);\n  VLOG(1) << ((!result) ? \"not found db \" : \"found db \") << db_type;\n  return result;\n}\n\n/**\n * Returns whether or not a database exists given the database type and path.\n */\ninline bool DBExists(const string& db_type, const string& full_db_name) {\n  // Warning! We assume that creating a DB throws an exception if the DB\n  // does not exist. If the DB constructor does not follow this design\n  // pattern,\n  // the returned output (the existence tensor) can be wrong.\n  try {\n    std::unique_ptr<DB> db(\n        caffe2::db::CreateDB(db_type, full_db_name, caffe2::db::READ));\n    return true;\n  } catch (...) {\n    return false;\n  }\n}\n\n/**\n * A reader wrapper for DB that also allows us to serialize it.\n */\nclass DBReader {\n public:\n\n  friend class DBReaderSerializer;\n  DBReader() {}\n\n  DBReader(\n      const string& db_type,\n      const string& source,\n      const int32_t num_shards = 1,\n      const int32_t shard_id = 0) {\n    Open(db_type, source, num_shards, shard_id);\n  }\n\n  explicit DBReader(const DBReaderProto& proto) {\n    Open(proto.db_type(), proto.source());\n    if (proto.has_key()) {\n      CAFFE_ENFORCE(cursor_->SupportsSeek(),\n          \"Encountering a proto that needs seeking but the db type \"\n          \"does not support it.\");\n      cursor_->Seek(proto.key());\n    }\n    num_shards_ = 1;\n    shard_id_ = 0;\n  }\n\n  explicit DBReader(std::unique_ptr<DB> db)\n      : db_type_(\"<memory-type>\"),\n        source_(\"<memory-source>\"),\n        db_(std::move(db)) {\n    CAFFE_ENFORCE(db_.get(), \"Passed null db\");\n    cursor_ = db_->NewCursor();\n  }\n\n  void Open(\n      const string& db_type,\n      const string& source,\n      const int32_t num_shards = 1,\n      const int32_t shard_id = 0) {\n    // Note(jiayq): resetting is needed when we re-open e.g. leveldb where no\n    // concurrent access is allowed.\n    cursor_.reset();\n    db_.reset();\n    db_type_ = db_type;\n    source_ = source;\n    db_ = CreateDB(db_type_, source_, READ);\n    CAFFE_ENFORCE(db_, \"Cannot open db: \", source_, \" of type \", db_type_);\n    InitializeCursor(num_shards, shard_id);\n  }\n\n  void Open(\n      unique_ptr<DB>&& db,\n      const int32_t num_shards = 1,\n      const int32_t shard_id = 0) {\n    cursor_.reset();\n    db_.reset();\n    db_ = std::move(db);\n    CAFFE_ENFORCE(db_.get(), \"Passed null db\");\n    InitializeCursor(num_shards, shard_id);\n  }\n\n public:\n  /**\n   * Read a set of key and value from the db and move to next. Thread safe.\n   *\n   * The string objects key and value must be created by the caller and\n   * explicitly passed in to this function. This saves one additional object\n   * copy.\n   *\n   * If the cursor reaches its end, the reader will go back to the head of\n   * the db. This function can be used to enable multiple input ops to read\n   * the same db.\n   *\n   * Note(jiayq): we loosen the definition of a const function here a little\n   * bit: the state of the cursor is actually changed. However, this allows\n   * us to pass in a DBReader to an Operator without the need of a duplicated\n   * output blob.\n   */\n  void Read(string* key, string* value) const {\n    CAFFE_ENFORCE(cursor_ != nullptr, \"Reader not initialized.\");\n    std::unique_lock<std::mutex> mutex_lock(reader_mutex_);\n    *key = cursor_->key();\n    *value = cursor_->value();\n\n    // In sharded mode, each read skips num_shards_ records\n    for (int s = 0; s < num_shards_; s++) {\n      cursor_->Next();\n      if (!cursor_->Valid()) {\n        MoveToBeginning();\n        break;\n      }\n    }\n  }\n\n  /**\n   * @brief Seeks to the first key. Thread safe.\n   */\n  void SeekToFirst() const {\n    CAFFE_ENFORCE(cursor_ != nullptr, \"Reader not initialized.\");\n    std::unique_lock<std::mutex> mutex_lock(reader_mutex_);\n    MoveToBeginning();\n  }\n\n  /**\n   * Returns the underlying cursor of the db reader.\n   *\n   * Note that if you directly use the cursor, the read will not be thread\n   * safe, because there is no mechanism to stop multiple threads from\n   * accessing the same cursor. You should consider using Read() explicitly.\n   */\n  inline Cursor* cursor() const {\n    LOG(ERROR) << \"Usually for a DBReader you should use Read() to be \"\n                  \"thread safe. Consider refactoring your code.\";\n    return cursor_.get();\n  }\n\n private:\n  void InitializeCursor(const int32_t num_shards, const int32_t shard_id) {\n    CAFFE_ENFORCE(num_shards >= 1);\n    CAFFE_ENFORCE(shard_id >= 0);\n    CAFFE_ENFORCE(shard_id < num_shards);\n    num_shards_ = num_shards;\n    shard_id_ = shard_id;\n    cursor_ = db_->NewCursor();\n    SeekToFirst();\n  }\n\n  void MoveToBeginning() const {\n    cursor_->SeekToFirst();\n    for (auto s = 0; s < shard_id_; s++) {\n      cursor_->Next();\n      CAFFE_ENFORCE(\n          cursor_->Valid(), \"Db has less rows than shard id: \", s, shard_id_);\n    }\n  }\n\n  string db_type_;\n  string source_;\n  unique_ptr<DB> db_;\n  unique_ptr<Cursor> cursor_;\n  mutable std::mutex reader_mutex_;\n  uint32_t num_shards_;\n  uint32_t shard_id_;\n\n  DISABLE_COPY_AND_ASSIGN(DBReader);\n};\n\nclass DBReaderSerializer : public BlobSerializerBase {\n public:\n  /**\n   * Serializes a DBReader. Note that this blob has to contain DBReader,\n   * otherwise this function produces a fatal error.\n   */\n  void Serialize(\n      const Blob& blob,\n      const string& name,\n      BlobSerializerBase::SerializationAcceptor acceptor) override;\n};\n\nclass DBReaderDeserializer : public BlobDeserializerBase {\n public:\n  void Deserialize(const BlobProto& proto, Blob* blob) override;\n};\n\n}  // namespace db\n}  // namespace caffe2\n\n#endif  // CAFFE2_CORE_DB_H_\n"
  },
  {
    "path": "caffe2/core/event.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/event_cpu.h\"\n\nnamespace caffe2 {\n\nCAFFE2_API EventCreateFunction Event::event_creator_[MaxDeviceTypes];\nCAFFE2_API EventRecordFunction Event::event_recorder_[MaxDeviceTypes];\nCAFFE2_API EventWaitFunction\n    Event::event_waiter_[MaxDeviceTypes][MaxDeviceTypes];\nCAFFE2_API EventFinishFunction Event::event_finisher_[MaxDeviceTypes];\n\nCAFFE2_API EventQueryFunction Event::event_querier_[MaxDeviceTypes];\nCAFFE2_API EventErrorMessageFunction\n    Event::event_err_msg_getter_[MaxDeviceTypes];\nCAFFE2_API EventSetFinishedFunction\n    Event::event_finished_setter_[MaxDeviceTypes];\nCAFFE2_API EventResetFunction Event::event_resetter_[MaxDeviceTypes];\n\nnamespace {\nconst std::string kNoError = \"No error\";\n}\n\nvoid EventCreateCPU(const DeviceOption& option, Event* event) {\n  event->event_ = std::make_shared<CPUEventWrapper>(option);\n}\n\nvoid EventRecordCPU(\n    Event* event,\n    const void* /* unused */,\n    const char* err_msg) {\n  auto* wrapper = static_cast<CPUEventWrapper*>(event->event_.get());\n  std::unique_lock<std::mutex> lock(wrapper->mutex_);\n\n  // Possible state changes:\n  //  INITIALIZED -> SCHEDULED or SUCCESS/FAILED\n  //  SCHEDULED -> SUCCESS/FAILED\n  //  SUCCESS/FAILED - terminal, no further changes to status_/err_msg_\n\n  CAFFE_ENFORCE(\n      wrapper->status_ != EventStatus::EVENT_SCHEDULED,\n      \"Calling Record multiple times\");\n\n  // Event might be in SUCCESS/FAILED state in case an op has\n  // finished async execution part first\n  if (wrapper->status_ == EventStatus::EVENT_INITIALIZED) {\n    if (!err_msg) {\n      wrapper->status_ = EventStatus::EVENT_SCHEDULED;\n    } else {\n      wrapper->err_msg_ = err_msg;\n      wrapper->status_ = EventStatus::EVENT_FAILED;\n      wrapper->cv_completed_.notify_all();\n    }\n  }\n}\n\nvoid EventFinishCPU(const Event* event) {\n  auto* wrapper = static_cast<CPUEventWrapper*>(event->event_.get());\n  std::unique_lock<std::mutex> lock(wrapper->mutex_);\n  while (wrapper->status_ != EventStatus::EVENT_SUCCESS &&\n         wrapper->status_ != EventStatus::EVENT_FAILED) {\n    wrapper->cv_completed_.wait(lock);\n  }\n}\n\nvoid EventWaitCPUCPU(const Event* event, void* /* context */) {\n  EventFinishCPU(event);\n}\n\nEventStatus EventQueryCPU(const Event* event) {\n  auto* wrapper = static_cast<CPUEventWrapper*>(event->event_.get());\n  return static_cast<EventStatus>(wrapper->status_.load());\n}\n\nconst std::string& EventErrorMessageCPU(const Event* event) {\n  auto* wrapper = static_cast<CPUEventWrapper*>(event->event_.get());\n  if (wrapper->status_ == EventStatus::EVENT_FAILED) {\n    // Failed is a terminal state, not synchronizing,\n    // err_msg_ should not be changed anymore\n    return wrapper->err_msg_;\n  } else {\n    return kNoError;\n  }\n}\n\nvoid EventSetFinishedCPU(const Event* event, const char* err_msg) {\n  auto* wrapper = static_cast<CPUEventWrapper*>(event->event_.get());\n  std::unique_lock<std::mutex> lock(wrapper->mutex_);\n\n  CAFFE_ENFORCE(\n      wrapper->status_ == EventStatus::EVENT_INITIALIZED ||\n          wrapper->status_ == EventStatus::EVENT_SCHEDULED,\n      \"Calling SetFinished on finished event\");\n\n  if (!err_msg) {\n    wrapper->status_ = EventStatus::EVENT_SUCCESS;\n  } else {\n    wrapper->err_msg_ = err_msg;\n    wrapper->status_ = EventStatus::EVENT_FAILED;\n  }\n  wrapper->cv_completed_.notify_all();\n}\n\nvoid EventResetCPU(Event* event) {\n  auto* wrapper = static_cast<CPUEventWrapper*>(event->event_.get());\n  std::unique_lock<std::mutex> lock(wrapper->mutex_);\n  wrapper->status_ = EventStatus::EVENT_INITIALIZED;\n  wrapper->err_msg_ = \"\";\n}\n\nREGISTER_EVENT_CREATE_FUNCTION(CPU, EventCreateCPU);\nREGISTER_EVENT_RECORD_FUNCTION(CPU, EventRecordCPU);\nREGISTER_EVENT_WAIT_FUNCTION(CPU, CPU, EventWaitCPUCPU);\nREGISTER_EVENT_FINISH_FUNCTION(CPU, EventFinishCPU);\n\nREGISTER_EVENT_QUERY_FUNCTION(CPU, EventQueryCPU);\nREGISTER_EVENT_ERROR_MESSAGE_FUNCTION(CPU, EventErrorMessageCPU);\nREGISTER_EVENT_SET_FINISHED_FUNCTION(CPU, EventSetFinishedCPU);\nREGISTER_EVENT_RESET_FUNCTION(CPU, EventResetCPU);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/event.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_EVENT_H_\n#define CAFFE2_CORE_EVENT_H_\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\nconstexpr int MaxDeviceTypes = DeviceType::COMPILE_TIME_MAX_DEVICE_TYPES;\nclass Event;\n\nenum EventStatus {\n  EVENT_INITIALIZED = 0,\n  EVENT_SCHEDULED = 1,\n  EVENT_SUCCESS = 2,\n  EVENT_FAILED = 3,\n};\n\n// For the following functions, void* shall be interpreted as the corresponding\n// context object corresponding to the device type associated with the\n// functions.\n\n// Initializes event\ntypedef void (*EventCreateFunction)(const DeviceOption& option, Event*);\n\n// Called on event to signal that CPU part of operation is finished,\n// Optionally accepts error message from CPU part.\n// Should be called no more than once per event\ntypedef void (*EventRecordFunction)(Event*, const void*, const char*);\n\n// Waits and returns as soon as possible in order schedule next operation,\n// e.g. for CUDA->CUDA waits only for CPU part of CUDA op,\n// for CUDA->CPU waits till the CUDA op is fully completed.\n// Prepares context to synchronize device part of operation.\n// Can be called concurrently from multiple threads\ntypedef void (*EventWaitFunction)(const Event*, void*);\n\n// Waits till operation is fully finished,\n// can be called concurrently from multiple threads\ntypedef void (*EventFinishFunction)(const Event*);\n\n// Queries current status of operation,\n// can be called concurrently from multiple threads\ntypedef EventStatus (*EventQueryFunction)(const Event*);\ntypedef const std::string& (*EventErrorMessageFunction)(const Event*);\ntypedef void (*EventSetFinishedFunction)(const Event*, const char*);\ntypedef void (*EventResetFunction)(Event*);\n\nclass Event {\n public:\n  explicit Event(const DeviceOption& option)\n      : event_(), type_(option.device_type()), option_(option) {\n    CAFFE_ENFORCE_LT(type_, MaxDeviceTypes);\n    CAFFE_ENFORCE(event_creator_[type_]);\n    event_creator_[type_](option, this);\n  }\n\n  // Nothing needs to be done in the destructor, as the event creator should\n  // set the proper destruction process for the unique_ptr.\n  ~Event() {}\n\n  void Record(\n      int recorder_type,\n      const void* context,\n      const char* err_msg = nullptr) {\n    CAFFE_ENFORCE_EQ(\n        recorder_type,\n        type_,\n        \"You are trying to record with a wrong device type.\");\n    CAFFE_ENFORCE(event_recorder_[recorder_type]);\n    event_recorder_[recorder_type](this, context, err_msg);\n  }\n\n  void Wait(int waiter_type, void* context) const {\n    CAFFE_ENFORCE(event_waiter_[waiter_type][type_]);\n    event_waiter_[waiter_type][type_](this, context);\n  }\n\n  void Finish() const {\n    CAFFE_ENFORCE(event_finisher_[type_]);\n    event_finisher_[type_](this);\n  }\n\n  EventStatus Query() const {\n    CAFFE_ENFORCE(event_querier_[type_]);\n    return event_querier_[type_](this);\n  }\n\n  const std::string& ErrorMessage() const {\n    CAFFE_ENFORCE(event_err_msg_getter_[type_]);\n    return event_err_msg_getter_[type_](this);\n  }\n\n  void Reset() {\n    CAFFE_ENFORCE(event_resetter_[type_]);\n    event_resetter_[type_](this);\n  }\n\n  const DeviceOption& GetDeviceOption() const {\n    return option_;\n  }\n\n  bool IsScheduled() const {\n    return Query() == EventStatus::EVENT_SCHEDULED;\n  }\n\n  bool IsFinished() const {\n    auto status = Query();\n    return status == EventStatus::EVENT_SUCCESS ||\n        status == EventStatus::EVENT_FAILED;\n  }\n\n  void SetFinished(const char* err_msg = nullptr) {\n    CAFFE_ENFORCE(event_finished_setter_[type_]);\n    return event_finished_setter_[type_](this, err_msg);\n  }\n\n  // If parent op has succeeded, then we can run any child op;\n  // If parent op is in scheduled state, we need to check that:\n  //  - child op supports async scheduling\n  //  - there's a way to setup synchronization between async parent and\n  //    child - both child and parent should use the same type of device,\n  //    non-blocking synchronization between different device types is not\n  //    supported\n  // If parent op is in another state (initialized or failed) then scheduling\n  // is not possible\n  bool CanSchedule(const Event& child_event, bool supports_async) const {\n    return CanSchedule(type_, Query(), child_event.GetType(), supports_async);\n  }\n\n  static bool CanSchedule(\n      int parent_type,\n      EventStatus parent_status,\n      int child_type,\n      bool child_supports_async) {\n    if (parent_status == EventStatus::EVENT_SUCCESS) {\n      return true;\n    }\n    if (parent_status == EventStatus::EVENT_SCHEDULED) {\n      return (parent_type == child_type) && child_supports_async;\n    }\n    return false;\n  }\n\n  int GetType() const {\n    return type_;\n  }\n\n  // event_ is going to be accessed by the EventCreate/Record/Wait/Finish\n  // functions, but one should not use it outside the own Event functionalities.\n  // In the future we may move it to a private member.\n  std::shared_ptr<void> event_;\n\n private:\n  int type_;\n  DeviceOption option_;\n\n  CAFFE2_API static EventCreateFunction event_creator_[MaxDeviceTypes];\n  CAFFE2_API static EventRecordFunction event_recorder_[MaxDeviceTypes];\n  CAFFE2_API static EventWaitFunction event_waiter_[MaxDeviceTypes]\n                                                   [MaxDeviceTypes];\n  CAFFE2_API static EventFinishFunction event_finisher_[MaxDeviceTypes];\n\n  CAFFE2_API static EventQueryFunction event_querier_[MaxDeviceTypes];\n  CAFFE2_API static EventErrorMessageFunction\n      event_err_msg_getter_[MaxDeviceTypes];\n  CAFFE2_API static EventSetFinishedFunction\n      event_finished_setter_[MaxDeviceTypes];\n  CAFFE2_API static EventResetFunction event_resetter_[MaxDeviceTypes];\n\n  template <int d>\n  friend struct EventCreateFunctionRegisterer;\n  template <int d>\n  friend struct EventRecordFunctionRegisterer;\n  template <int w, int d>\n  friend struct EventWaitFunctionRegisterer;\n  template <int d>\n  friend struct EventFinishFunctionRegisterer;\n\n  template <int d>\n  friend struct EventQueryFunctionRegisterer;\n  template <int d>\n  friend struct EventErrorMessageFunctionRegisterer;\n  template <int d>\n  friend struct EventSetFinishedFunctionRegisterer;\n  template <int d>\n  friend struct EventResetFunctionRegisterer;\n};\n\ntemplate <int d>\nstruct EventCreateFunctionRegisterer {\n  explicit EventCreateFunctionRegisterer(EventCreateFunction f) {\n    static_assert(d < MaxDeviceTypes, \"\");\n    Event::event_creator_[d] = f;\n  }\n};\n#define REGISTER_EVENT_CREATE_FUNCTION(d, f)                     \\\n  namespace {                                                    \\\n  static EventCreateFunctionRegisterer<d> g_event_create_##d(f); \\\n  }\n\ntemplate <int d>\nstruct EventRecordFunctionRegisterer {\n  explicit EventRecordFunctionRegisterer(EventRecordFunction f) {\n    static_assert(d < MaxDeviceTypes, \"\");\n    Event::event_recorder_[d] = f;\n  }\n};\n#define REGISTER_EVENT_RECORD_FUNCTION(d, f)                     \\\n  namespace {                                                    \\\n  static EventRecordFunctionRegisterer<d> g_event_record_##d(f); \\\n  }\n\ntemplate <int waiter_type, int event_type>\nstruct EventWaitFunctionRegisterer {\n  explicit EventWaitFunctionRegisterer(EventWaitFunction f) {\n    static_assert(waiter_type < MaxDeviceTypes, \"\");\n    static_assert(event_type < MaxDeviceTypes, \"\");\n    Event::event_waiter_[waiter_type][event_type] = f;\n  }\n};\n#define REGISTER_EVENT_WAIT_FUNCTION(w, d, f)                         \\\n  namespace {                                                         \\\n  static EventWaitFunctionRegisterer<w, d> g_event_wait_##w##_##d(f); \\\n  }\n\ntemplate <int d>\nstruct EventQueryFunctionRegisterer {\n  explicit EventQueryFunctionRegisterer(EventQueryFunction f) {\n    static_assert(d < MaxDeviceTypes, \"\");\n    Event::event_querier_[d] = f;\n  }\n};\n#define REGISTER_EVENT_QUERY_FUNCTION(d, f)                    \\\n  namespace {                                                  \\\n  static EventQueryFunctionRegisterer<d> g_event_query_##d(f); \\\n  }\n\ntemplate <int d>\nstruct EventErrorMessageFunctionRegisterer {\n  explicit EventErrorMessageFunctionRegisterer(EventErrorMessageFunction f) {\n    static_assert(d < MaxDeviceTypes, \"\");\n    Event::event_err_msg_getter_[d] = f;\n  }\n};\n#define REGISTER_EVENT_ERROR_MESSAGE_FUNCTION(d, f)                     \\\n  namespace {                                                           \\\n  static EventErrorMessageFunctionRegisterer<d> g_event_err_msg_##d(f); \\\n  }\n\ntemplate <int d>\nstruct EventSetFinishedFunctionRegisterer {\n  explicit EventSetFinishedFunctionRegisterer(EventSetFinishedFunction f) {\n    static_assert(d < MaxDeviceTypes, \"\");\n    Event::event_finished_setter_[d] = f;\n  }\n};\n#define REGISTER_EVENT_SET_FINISHED_FUNCTION(d, f)                          \\\n  namespace {                                                               \\\n  static EventSetFinishedFunctionRegisterer<d> g_event_set_finished_##d(f); \\\n  }\n\ntemplate <int d>\nstruct EventFinishFunctionRegisterer {\n  explicit EventFinishFunctionRegisterer(EventFinishFunction f) {\n    static_assert(d < MaxDeviceTypes, \"\");\n    Event::event_finisher_[d] = f;\n  }\n};\n#define REGISTER_EVENT_FINISH_FUNCTION(d, f)                     \\\n  namespace {                                                    \\\n  static EventFinishFunctionRegisterer<d> g_event_finish_##d(f); \\\n  }\n\ntemplate <int d>\nstruct EventResetFunctionRegisterer {\n  explicit EventResetFunctionRegisterer(EventResetFunction f) {\n    static_assert(d < MaxDeviceTypes, \"\");\n    Event::event_resetter_[d] = f;\n  }\n};\n#define REGISTER_EVENT_RESET_FUNCTION(d, f)                    \\\n  namespace {                                                  \\\n  static EventResetFunctionRegisterer<d> g_event_reset_##d(f); \\\n  }\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_EVENT_H_\n"
  },
  {
    "path": "caffe2/core/event_cpu.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/event.h\"\n#include \"caffe2/core/operator.h\"\n\n#include <atomic>\n\nnamespace caffe2 {\n\nstruct CPUEventWrapper {\n  explicit CPUEventWrapper(const DeviceOption& option)\n      : status_(EventStatus::EVENT_INITIALIZED) {\n    CAFFE_ENFORCE(\n        option.device_type() == CPU || option.device_type() == MKLDNN,\n        \"Expected CPU/MKLDNN device type\");\n  }\n  ~CPUEventWrapper() {}\n\n  std::mutex mutex_;\n  std::condition_variable cv_completed_;\n  std::atomic<int> status_;\n  std::string err_msg_;\n};\n\nvoid EventCreateCPU(const DeviceOption& option, Event* event);\n\nvoid EventRecordCPU(\n    Event* event,\n    const void* /* unused */,\n    const char* err_msg);\n\nvoid EventFinishCPU(const Event* event);\n\nvoid EventWaitCPUCPU(const Event* event, void* /* context */);\n\nEventStatus EventQueryCPU(const Event* event);\n\nconst std::string& EventErrorMessageCPU(const Event* event);\n\nvoid EventSetFinishedCPU(const Event* event, const char* err_msg);\n\nbool EventCanScheduleCPU(const Event*, const Event*);\n\nvoid EventResetCPU(Event*);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/event_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/event_cpu.h\"\n#include \"caffe2/core/operator.h\"\n\n#include <atomic>\n\nnamespace caffe2 {\n\nstruct CudaEventWrapper {\n  explicit CudaEventWrapper(const DeviceOption& option)\n      : cuda_stream_(nullptr),\n        cuda_gpu_id_(option.cuda_gpu_id()),\n        status_(EventStatus::EVENT_INITIALIZED) {\n    CAFFE_ENFORCE(option.device_type(), CUDA);\n    DeviceGuard g(cuda_gpu_id_);\n    CUDA_ENFORCE(cudaEventCreate(\n        &cuda_event_, cudaEventDefault | cudaEventDisableTiming));\n  }\n  ~CudaEventWrapper() {\n    DeviceGuard g(cuda_gpu_id_);\n    CUDA_CHECK(cudaEventDestroy(cuda_event_));\n  }\n\n  cudaEvent_t cuda_event_;\n  cudaStream_t cuda_stream_;\n  int cuda_gpu_id_;\n\n  std::atomic<int> status_;\n  std::mutex mutex_recorded_;\n  std::condition_variable cv_recorded_;\n  std::string err_msg_;\n};\n\nnamespace {\nconst std::string kNoError = \"No error\";\n}\n\nvoid EventCreateCUDA(const DeviceOption& option, Event* event) {\n  event->event_ = std::make_shared<CudaEventWrapper>(option);\n}\n\nvoid EventRecordCUDA(Event* event, const void* context, const char* err_msg) {\n  auto* wrapper = static_cast<CudaEventWrapper*>(event->event_.get());\n  {\n    std::unique_lock<std::mutex> lock(wrapper->mutex_recorded_);\n\n    // Possible state changes:\n    //  INITIALIZED -> SCHEDULED/FAILED\n    //  SCHEDULED -> SUCCESS/FAILED\n    //  SUCCESS/FAILED - terminal\n    //\n    // No further changes to cuda_event_ and cuda_stream_ after transitioning\n    // from INITIALIZED\n    // No further changes to err_msg_ after transitioning into FAILED\n\n    CAFFE_ENFORCE_EQ(\n        wrapper->status_,\n        EventStatus::EVENT_INITIALIZED,\n        \"Calling Record multiple times\");\n\n    if (!err_msg) {\n      // When recording, one needs to make sure that the current gpu id is\n      // correct.\n      // TODO(jiayq): move the enforce logic to the caller?\n      const auto& current_device = CaffeCudaGetDevice();\n      CAFFE_ENFORCE_EQ(\n          current_device,\n          wrapper->cuda_gpu_id_,\n          \"When you call EventRecordCUDA, your current device should be the same \"\n          \"as the device specified by the event.\");\n      CAFFE_ENFORCE_EQ(\n          current_device,\n          static_cast<const CUDAContext*>(context)->cuda_gpu_id());\n      CUDA_ENFORCE(cudaEventRecord(\n          wrapper->cuda_event_,\n          static_cast<const CUDAContext*>(context)->cuda_stream()));\n      wrapper->cuda_stream_ =\n          static_cast<const CUDAContext*>(context)->cuda_stream();\n      wrapper->status_ = EventStatus::EVENT_SCHEDULED;\n    } else {\n      wrapper->err_msg_ = err_msg;\n      wrapper->status_ = EventStatus::EVENT_FAILED;\n    }\n  }\n  wrapper->cv_recorded_.notify_all();\n}\n\nvoid EventFinishCUDA(const Event* event) {\n  auto* wrapper = static_cast<CudaEventWrapper*>(event->event_.get());\n  {\n    std::unique_lock<std::mutex> lock(wrapper->mutex_recorded_);\n    while (wrapper->status_ == EventStatus::EVENT_INITIALIZED) {\n      wrapper->cv_recorded_.wait(lock);\n    }\n  }\n\n  if (wrapper->status_ == EventStatus::EVENT_SCHEDULED) {\n    // ok, even if event is already completed and status was not yet updated\n    DeviceGuard g(wrapper->cuda_gpu_id_);\n    auto cudaResult = cudaEventSynchronize(wrapper->cuda_event_);\n    if (cudaResult == cudaSuccess) {\n      wrapper->status_ = EventStatus::EVENT_SUCCESS;\n    } else {\n      const auto& err_msg = cudaGetErrorString(cudaResult);\n\n      std::unique_lock<std::mutex> lock(wrapper->mutex_recorded_);\n      wrapper->err_msg_ = err_msg;\n      wrapper->status_ = EventStatus::EVENT_FAILED;\n    }\n  }\n}\n\n// Both waiter and event are CUDA. Non-blocking\nvoid EventWaitCUDACUDA(const Event* event, void* context) {\n  auto* wrapper = static_cast<CudaEventWrapper*>(event->event_.get());\n  {\n    std::unique_lock<std::mutex> lock(wrapper->mutex_recorded_);\n    while (wrapper->status_ == EventStatus::EVENT_INITIALIZED) {\n      wrapper->cv_recorded_.wait(lock);\n    }\n  }\n\n  if (wrapper->status_ == EventStatus::EVENT_SCHEDULED) {\n    // ok, even if event is already completed and status was not yet updated\n    auto context_stream = static_cast<CUDAContext*>(context)->cuda_stream();\n    auto event_stream = wrapper->cuda_stream_;\n    if (context_stream != event_stream) {\n      // CAFFE_ENFORCE_EQ(\n      //    CaffeCudaGetDevice(),\n      //    static_cast<const CUDAContext*>(context)->cuda_gpu_id());\n      CUDA_CHECK(cudaStreamWaitEvent(context_stream, wrapper->cuda_event_, 0));\n    }\n  }\n}\n\n// Waiter is CPU, event is CUDA\nvoid EventWaitCPUCUDA(const Event* event, void* context) {\n  EventFinishCUDA(event);\n}\n\n// Waiter is CUDA, event is CPU\nvoid EventWaitCUDACPU(const Event* event, void* context) {\n  event->Finish(); // calls EventFinishCPU\n}\n\nEventStatus EventQueryCUDA(const Event* event) {\n  auto* wrapper = static_cast<CudaEventWrapper*>(event->event_.get());\n  if (wrapper->status_ == EventStatus::EVENT_SCHEDULED) {\n    auto cudaResult = cudaEventQuery(wrapper->cuda_event_);\n    if (cudaResult == cudaSuccess) {\n      wrapper->status_ = EventStatus::EVENT_SUCCESS;\n    } else if (cudaResult != cudaErrorNotReady) {\n      const auto& err_msg = cudaGetErrorString(cudaResult);\n\n      std::unique_lock<std::mutex> lock(wrapper->mutex_recorded_);\n      wrapper->err_msg_ = err_msg;\n      wrapper->status_ = EventStatus::EVENT_FAILED;\n    }\n  }\n  return static_cast<EventStatus>(wrapper->status_.load());\n}\n\nconst std::string& EventErrorMessageCUDA(const Event* event) {\n  auto* wrapper = static_cast<CudaEventWrapper*>(event->event_.get());\n  // supposed to be called after EventQueryCUDA to update status first\n  if (wrapper->status_ == EventStatus::EVENT_FAILED) {\n    return wrapper->err_msg_;\n  } else {\n    return kNoError;\n  }\n}\n\nvoid EventSetFinishedCUDA(const Event* event, const char* err_msg) {\n  auto* wrapper = static_cast<CudaEventWrapper*>(event->event_.get());\n  {\n    std::unique_lock<std::mutex> lock(wrapper->mutex_recorded_);\n\n    CAFFE_ENFORCE_EQ(\n        wrapper->status_,\n        EventStatus::EVENT_INITIALIZED,\n        \"Calling SetFinished on recorded CUDA event\");\n\n    if (!err_msg) {\n      wrapper->status_ = EventStatus::EVENT_SUCCESS;\n    } else {\n      wrapper->err_msg_ = err_msg;\n      wrapper->status_ = EventStatus::EVENT_FAILED;\n    }\n  }\n  wrapper->cv_recorded_.notify_all();\n}\n\nvoid EventResetCUDA(Event* event) {\n  auto* wrapper = static_cast<CudaEventWrapper*>(event->event_.get());\n  std::unique_lock<std::mutex> lock(wrapper->mutex_recorded_);\n  wrapper->status_ = EventStatus::EVENT_INITIALIZED;\n  wrapper->err_msg_ = \"\";\n  wrapper->cuda_stream_ = nullptr;\n}\n\nREGISTER_EVENT_CREATE_FUNCTION(CUDA, EventCreateCUDA);\nREGISTER_EVENT_RECORD_FUNCTION(CUDA, EventRecordCUDA);\nREGISTER_EVENT_WAIT_FUNCTION(CUDA, CUDA, EventWaitCUDACUDA);\nREGISTER_EVENT_WAIT_FUNCTION(CPU, CUDA, EventWaitCPUCUDA);\nREGISTER_EVENT_WAIT_FUNCTION(CUDA, CPU, EventWaitCUDACPU);\nREGISTER_EVENT_FINISH_FUNCTION(CUDA, EventFinishCUDA);\n\nREGISTER_EVENT_QUERY_FUNCTION(CUDA, EventQueryCUDA);\nREGISTER_EVENT_ERROR_MESSAGE_FUNCTION(CUDA, EventErrorMessageCUDA);\nREGISTER_EVENT_SET_FINISHED_FUNCTION(CUDA, EventSetFinishedCUDA);\nREGISTER_EVENT_RESET_FUNCTION(CUDA, EventResetCUDA);\n\nREGISTER_EVENT_WAIT_FUNCTION(MKLDNN, CUDA, EventWaitCPUCUDA);\nREGISTER_EVENT_WAIT_FUNCTION(CUDA, MKLDNN, EventWaitCUDACPU);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/event_gpu_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/event.h\"\n\nnamespace caffe2 {\n\nTEST(EventCUDATest, EventBasics) {\n  if (!HasCudaGPU())\n    return;\n  DeviceOption device_cpu;\n  device_cpu.set_device_type(CPU);\n  DeviceOption device_cuda;\n  device_cuda.set_device_type(CUDA);\n\n  CPUContext context_cpu(device_cpu);\n  CUDAContext context_cuda(device_cuda);\n\n  Event event_cpu(device_cpu);\n  Event event_cuda(device_cuda);\n\n  // CPU context and event interactions\n  context_cpu.Record(&event_cpu);\n  event_cpu.SetFinished();\n  event_cpu.Finish();\n  context_cpu.WaitEvent(event_cpu);\n\n  event_cpu.Reset();\n  event_cpu.Record(CPU, &context_cpu);\n  event_cpu.SetFinished();\n  event_cpu.Wait(CPU, &context_cpu);\n\n  // CUDA context and event interactions\n  context_cuda.SwitchToDevice();\n  context_cuda.Record(&event_cuda);\n  context_cuda.WaitEvent(event_cuda);\n  event_cuda.Finish();\n\n  event_cuda.Reset();\n  event_cuda.Record(CUDA, &context_cuda);\n  event_cuda.Wait(CUDA, &context_cuda);\n\n  // CPU context waiting for CUDA event\n  context_cpu.WaitEvent(event_cuda);\n\n  // CUDA context waiting for CPU event\n  context_cuda.WaitEvent(event_cpu);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/event_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/event.h\"\n\nnamespace caffe2 {\n\nTEST(EventCPUTest, EventBasics) {\n  DeviceOption device_option;\n  device_option.set_device_type(CPU);\n  Event event(device_option);\n  CPUContext context;\n\n  context.Record(&event);\n  event.SetFinished();\n\n  context.WaitEvent(event);\n  event.Finish();\n\n  event.Reset();\n  event.Record(CPU, &context);\n  event.SetFinished();\n  event.Wait(CPU, &context);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/flags.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/flags.h\"\n\n#include <cstdlib>\n#include <sstream>\n\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\n\n#ifdef CAFFE2_USE_GFLAGS\n\nvoid SetUsageMessage(const string& str) {\n  if (UsageMessage() != nullptr) {\n    // Usage message has already been set, so we will simply return.\n    return;\n  }\n  gflags::SetUsageMessage(str);\n}\n\nconst char* UsageMessage() {\n  return gflags::ProgramUsage();\n}\n\nbool ParseCaffeCommandLineFlags(int* pargc, char*** pargv) {\n  if (*pargc == 0) return true;\n  return gflags::ParseCommandLineFlags(pargc, pargv, true);\n}\n\nbool CommandLineFlagsHasBeenParsed() {\n  // There is no way we query gflags right now, so we will simply return true.\n  return true;\n}\n\n#else  // CAFFE2_USE_GFLAGS\n\n\nCAFFE_DEFINE_REGISTRY(Caffe2FlagsRegistry, Caffe2FlagParser, const string&);\n\nnamespace {\nstatic bool gCommandLineFlagsParsed = false;\n// Since caffe flags is going to be loaded before caffe logging, we would\n// need to have a stringstream to hold the messages instead of directly\n// using caffe logging.\nstd::stringstream& GlobalInitStream() {\n  static std::stringstream ss;\n  return ss;\n}\nstatic string gUsageMessage = \"(Usage message not set.)\";\n}\n\n\nvoid SetUsageMessage(const string& str) { gUsageMessage = str; }\nconst char* UsageMessage() { return gUsageMessage.c_str(); }\n\nbool ParseCaffeCommandLineFlags(int* pargc, char*** pargv) {\n  if (*pargc == 0) return true;\n  char** argv = *pargv;\n  bool success = true;\n  GlobalInitStream() << \"Parsing commandline arguments for caffe2.\"\n                     << std::endl;\n  // write_head is the location we write the unused arguments to.\n  int write_head = 1;\n  for (int i = 1; i < *pargc; ++i) {\n    string arg(argv[i]);\n\n    if (arg.find(\"--help\") != string::npos) {\n      // Print the help message, and quit.\n      std::cout << UsageMessage() << std::endl;\n      std::cout << \"Arguments: \" << std::endl;\n      for (const auto& help_msg : Caffe2FlagsRegistry()->HelpMessage()) {\n        std::cout << \"    \" << help_msg.first << \": \" << help_msg.second\n                  << std::endl;\n      }\n      exit(0);\n    }\n    // If the arg does not start with \"--\", we will ignore it.\n    if (arg[0] != '-' || arg[1] != '-') {\n      GlobalInitStream()\n          << \"Caffe2 flag: commandline argument does not match --name=var \"\n             \"or --name format: \"\n          << arg << \". Ignoring this argument.\" << std::endl;\n      argv[write_head++] = argv[i];\n      continue;\n    }\n\n    string key;\n    string value;\n    int prefix_idx = arg.find('=');\n    if (prefix_idx == string::npos) {\n      // If there is no equality char in the arg, it means that the\n      // arg is specified in the next argument.\n      key = arg.substr(2, arg.size() - 2);\n      ++i;\n      if (i == *pargc) {\n        GlobalInitStream()\n            << \"Caffe2 flag: reached the last commandline argument, but \"\n               \"I am expecting a value for \" << arg;\n        success = false;\n        break;\n      }\n      value = string(argv[i]);\n    } else {\n      // If there is an equality character, we will basically use the value\n      // after the \"=\".\n      key = arg.substr(2, prefix_idx - 2);\n      value = arg.substr(prefix_idx + 1, string::npos);\n    }\n    // If the flag is not registered, we will ignore it.\n    if (!Caffe2FlagsRegistry()->Has(key)) {\n      GlobalInitStream() << \"Caffe2 flag: unrecognized commandline argument: \"\n                         << arg << std::endl;\n      success = false;\n      break;\n    }\n    std::unique_ptr<Caffe2FlagParser> parser(\n        Caffe2FlagsRegistry()->Create(key, value));\n    if (!parser->success()) {\n      GlobalInitStream() << \"Caffe2 flag: illegal argument: \"\n                         << arg << std::endl;\n      success = false;\n      break;\n    }\n  }\n  *pargc = write_head;\n  gCommandLineFlagsParsed = true;\n  // TODO: when we fail commandline flag parsing, shall we continue, or\n  // shall we just quit loudly? Right now we carry on the computation, but\n  // since there are failures in parsing, it is very likely that some\n  // downstream things will break, in which case it makes sense to quit loud\n  // and early.\n  if (!success) {\n    std::cerr << GlobalInitStream().str();\n  }\n  // Clear the global init stream.\n  GlobalInitStream().str(std::string());\n  return success;\n}\n\nbool CommandLineFlagsHasBeenParsed() {\n  return gCommandLineFlagsParsed;\n}\n\ntemplate <>\nbool Caffe2FlagParser::Parse<string>(const string& content, string* value) {\n  *value = content;\n  return true;\n}\n\ntemplate <>\nbool Caffe2FlagParser::Parse<int>(const string& content, int* value) {\n  try {\n    *value = std::atoi(content.c_str());\n    return true;\n  } catch(...) {\n    GlobalInitStream() << \"Caffe2 flag error: Cannot convert argument to int: \"\n                       << content << std::endl;\n    return false;\n  }\n}\n\ntemplate <>\nbool Caffe2FlagParser::Parse<int64_t>(const string& content, int64_t* value) {\n  try {\n    static_assert(sizeof(long long) == sizeof(int64_t), \"\");\n#ifdef __ANDROID__\n    // Android does not have std::atoll.\n    *value = atoll(content.c_str());\n#else\n    *value = std::atoll(content.c_str());\n#endif\n    return true;\n  } catch (...) {\n    GlobalInitStream() << \"Caffe2 flag error: Cannot convert argument to int: \"\n                       << content << std::endl;\n    return false;\n  }\n}\n\ntemplate <>\nbool Caffe2FlagParser::Parse<double>(const string& content, double* value) {\n  try {\n    *value = std::atof(content.c_str());\n    return true;\n  } catch(...) {\n    GlobalInitStream()\n        << \"Caffe2 flag error: Cannot convert argument to double: \"\n        << content << std::endl;\n    return false;\n  }\n}\n\ntemplate <>\nbool Caffe2FlagParser::Parse<bool>(const string& content, bool* value) {\n  if (content == \"false\" || content == \"False\" || content == \"FALSE\" ||\n      content == \"0\") {\n    *value = false;\n    return true;\n  } else if (content == \"true\" || content == \"True\" || content == \"TRUE\" ||\n      content == \"1\") {\n    *value = true;\n    return true;\n  } else {\n    GlobalInitStream()\n        << \"Caffe2 flag error: Cannot convert argument to bool: \"\n        << content << std::endl\n        << \"Note that if you are passing in a bool flag, you need to \"\n           \"explicitly specify it, like --arg=True or --arg True. Otherwise, \"\n           \"the next argument may be inadvertently used as the argument, \"\n           \"causing the above error.\"\n        << std::endl;\n    return false;\n  }\n}\n\n#endif  // CAFFE2_USE_GFLAGS\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/flags.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @file flags.h\n * @brief Commandline flags support for Caffe2.\n *\n * This is a portable commandline flags tool for caffe2, so we can optionally\n * choose to use gflags or a lightweighted custom implementation if gflags is\n * not possible on a certain platform. If you have gflags installed, set the\n * macro CAFFE2_USE_GFLAGS will seamlessly route everything to gflags.\n *\n * To define a flag foo of type bool default to true, do the following in the\n * *global* namespace:\n *     CAFFE2_DEFINE_bool(foo, true, \"An example.\");\n *\n * To use it in another .cc file, you can use CAFFE2_DECLARE_* as follows:\n *     CAFFE2_DECLARE_bool(foo);\n *\n * In both cases, you can then access the flag via caffe2::FLAGS_foo.\n */\n\n#ifndef CAFFE2_CORE_FLAGS_H_\n#define CAFFE2_CORE_FLAGS_H_\n\n#include \"caffe2/core/registry.h\"\n\nnamespace caffe2 {\n/**\n * Sets the usage message when a commandline tool is called with \"--help\".\n */\nvoid SetUsageMessage(const string& str);\n\n/**\n * Returns the usage message for the commandline tool set by SetUsageMessage.\n */\nconst char* UsageMessage();\n\n/**\n * Parses the commandline flags.\n *\n * This command parses all the commandline arguments passed in via pargc\n * and argv. Once it is finished, partc and argv will contain the remaining\n * commandline args that caffe2 does not deal with. Note that following\n * convention, argv[0] contains the binary name and is not parsed.\n */\nbool ParseCaffeCommandLineFlags(int* pargc, char*** pargv);\n/**\n * Checks if the commandline flags has already been passed.\n */\nbool CommandLineFlagsHasBeenParsed();\n\n}  // namespace caffe2\n\n\n////////////////////////////////////////////////////////////////////////////////\n// Below are gflags and non-gflags specific implementations.\n////////////////////////////////////////////////////////////////////////////////\n\n#ifdef CAFFE2_USE_GFLAGS\n\n#include <gflags/gflags.h>\n\n// gflags before 2.0 uses namespace google and after 2.1 uses namespace gflags.\n// Using GFLAGS_GFLAGS_H_ to capture this change.\n#ifndef GFLAGS_GFLAGS_H_\nnamespace gflags = google;\n#endif  // GFLAGS_GFLAGS_H_\n\n#define CAFFE2_GFLAGS_DEF_WRAPPER(type, name, default_value, help_str)         \\\n  DEFINE_##type(name, default_value, help_str);                                \\\n  namespace caffe2 {                                                           \\\n    using ::FLAGS_##name;                                                      \\\n  }\n\n#define CAFFE2_DEFINE_int(name, default_value, help_str)                       \\\n  CAFFE2_GFLAGS_DEF_WRAPPER(int32, name, default_value, help_str)\n#define CAFFE2_DEFINE_int64(name, default_value, help_str)                     \\\n  CAFFE2_GFLAGS_DEF_WRAPPER(int64, name, default_value, help_str)              \n#define CAFFE2_DEFINE_double(name, default_value, help_str)                    \\\n  CAFFE2_GFLAGS_DEF_WRAPPER(double, name, default_value, help_str)\n#define CAFFE2_DEFINE_bool(name, default_value, help_str)                      \\\n  CAFFE2_GFLAGS_DEF_WRAPPER(bool, name, default_value, help_str)\n#define CAFFE2_DEFINE_string(name, default_value, help_str) \\\n  CAFFE2_GFLAGS_DEF_WRAPPER(string, name, default_value, help_str)\n\n// DECLARE_typed_var should be used in header files and in the global namespace.\n#define CAFFE2_GFLAGS_DECLARE_WRAPPER(type, name)                             \\\n  DECLARE_##type(name);                                                       \\\n  namespace caffe2 {                                                          \\\n    using ::FLAGS_##name;                                                     \\\n  }  // namespace caffe2\n\n#define CAFFE2_DECLARE_int(name) CAFFE2_GFLAGS_DECLARE_WRAPPER(int32, name)\n#define CAFFE2_DECLARE_int64(name) CAFFE2_GFLAGS_DECLARE_WRAPPER(int64, name)\n#define CAFFE2_DECLARE_double(name) CAFFE2_GFLAGS_DECLARE_WRAPPER(double, name)\n#define CAFFE2_DECLARE_bool(name) CAFFE2_GFLAGS_DECLARE_WRAPPER(bool, name)\n#define CAFFE2_DECLARE_string(name) CAFFE2_GFLAGS_DECLARE_WRAPPER(string, name)\n\n#else   // CAFFE2_USE_GFLAGS\n\nnamespace caffe2 {\n\nclass Caffe2FlagParser {\n public:\n  Caffe2FlagParser() {}\n  bool success() { return success_; }\n\n protected:\n  template <typename T>\n  bool Parse(const string& content, T* value);\n  bool success_;\n};\n\nCAFFE_DECLARE_REGISTRY(Caffe2FlagsRegistry, Caffe2FlagParser, const string&);\n\n}  // namespace caffe2\n\n// The macros are defined outside the caffe2 namespace. In your code, you should\n// write the CAFFE2_DEFINE_* and CAFFE2_DECLARE_* macros outside any namespace\n// as well.\n\n#define CAFFE2_DEFINE_typed_var(type, name, default_value, help_str)          \\\n  namespace caffe2 {                                                          \\\n  CAFFE2_EXPORT type FLAGS_##name = default_value;                            \\\n  namespace {                                                                 \\\n  class Caffe2FlagParser_##name : public Caffe2FlagParser {                   \\\n   public:                                                                    \\\n    explicit Caffe2FlagParser_##name(const string& content) {                 \\\n      success_ = Caffe2FlagParser::Parse<type>(content, &FLAGS_##name);       \\\n    }                                                                         \\\n  };                                                                          \\\n  }                                                                           \\\n  RegistererCaffe2FlagsRegistry g_Caffe2FlagsRegistry_##name(                 \\\n      #name,                                                                  \\\n      Caffe2FlagsRegistry(),                                                  \\\n      RegistererCaffe2FlagsRegistry::DefaultCreator<Caffe2FlagParser_##name>, \\\n      \"(\" #type \", default \" #default_value \") \" help_str);                   \\\n  }\n\n#define CAFFE2_DEFINE_int(name, default_value, help_str)                       \\\n  CAFFE2_DEFINE_typed_var(int, name, default_value, help_str)\n#define CAFFE2_DEFINE_int64(name, default_value, help_str) \\\n  CAFFE2_DEFINE_typed_var(int64_t, name, default_value, help_str)\n#define CAFFE2_DEFINE_double(name, default_value, help_str) \\\n  CAFFE2_DEFINE_typed_var(double, name, default_value, help_str)\n#define CAFFE2_DEFINE_bool(name, default_value, help_str)                      \\\n  CAFFE2_DEFINE_typed_var(bool, name, default_value, help_str)\n#define CAFFE2_DEFINE_string(name, default_value, help_str)                    \\\n  CAFFE2_DEFINE_typed_var(string, name, default_value, help_str)\n\n// DECLARE_typed_var should be used in header files and in the global namespace.\n#define CAFFE2_DECLARE_typed_var(type, name) \\\n  namespace caffe2 {                         \\\n  CAFFE2_IMPORT extern type FLAGS_##name;    \\\n  } // namespace caffe2\n\n#define CAFFE2_DECLARE_int(name) CAFFE2_DECLARE_typed_var(int, name)\n#define CAFFE2_DECLARE_int64(name) CAFFE2_DECLARE_typed_var(int64_t, name)\n#define CAFFE2_DECLARE_double(name) CAFFE2_DECLARE_typed_var(double, name)\n#define CAFFE2_DECLARE_bool(name) CAFFE2_DECLARE_typed_var(bool, name)\n#define CAFFE2_DECLARE_string(name) CAFFE2_DECLARE_typed_var(string, name)\n\n#endif  // CAFFE2_USE_GFLAGS\n\n#endif  // CAFFE2_CORE_FLAGS_H_\n"
  },
  {
    "path": "caffe2/core/graph.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/graph.h\"\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\nnamespace transform {\n\nGraph::Graph(const NetDef& net) : netdef_(net) {\n  nodes_.clear();\n  nodes_.resize(net.op_size());\n\n  // Copy over operators\n  for (int x = 0; x < net.op_size(); x++) {\n    node(x).op = net.op(x);\n  }\n\n  // For any blob, which operator was the last to write to it?\n  // In python, this is known as \"versions\".\n  std::unordered_map<string, int> edge_parent;\n\n  for (int i = 0; i < nodes_.size(); i++) {\n    for (const string& blob : node(i).op.input()) {\n      auto it = edge_parent.find(blob);\n      if (it != edge_parent.end()) {\n        int j = it->second;\n        node(i).parents[j].push_back(blob);\n        node(j).children[i].push_back(blob);\n      } else {\n        external_input_.insert(blob);\n      }\n    }\n    for (const string& blob : node(i).op.output()) {\n      edge_parent[blob] = i;\n    }\n  }\n\n  // Traverse opposite direction to find external outputs\n\n  // For any blob, which operator was the last to read to from it?\n  std::unordered_map<string, int> edge_child;\n\n  for (int i = nodes_.size() - 1; i >= 0; i--) {\n    for (const string& blob : node(i).op.output()) {\n      auto it = edge_child.find(blob);\n      if (it == edge_child.end()) {\n        external_output_.insert(blob);\n      }\n    }\n    for (const string& blob : node(i).op.input()) {\n      edge_child[blob] = i;\n    }\n  }\n}\n\nconst std::vector<std::pair<string, int>> Graph::GetSubgraphInput(\n    const std::vector<int>& match) {\n  return GetSubgraphPerimeterHelper(true, match);\n}\n\nconst std::vector<std::pair<string, int>> Graph::GetSubgraphOutput(\n    const std::vector<int>& match) {\n  return GetSubgraphPerimeterHelper(false, match);\n}\n\n// This helper function will either get:\n//    1) a list for the blobs that write INTO a subgraph\n//    2) a list of for the blobs that are written FROM a subgraph.\n//\n// The \"from_children\" flag determines if it is case 1 (true) or case 2 (false).\nconst std::vector<std::pair<string, int>> Graph::GetSubgraphPerimeterHelper(\n    bool from_children,\n    const std::vector<int>& match) {\n  std::vector<std::pair<string, int>> edge_list;\n  std::unordered_set<int> match_set(match.begin(), match.end());\n  for (int x = 0; x < nodes_.size(); x++) {\n    if (!is_node_active(x)) {\n      continue;\n    }\n    if (!match_set.count(x)) { // x is not in subgraph\n      const auto& list = from_children ? node(x).children : node(x).parents;\n      for (const auto& edge : list) {\n        int parent = edge.first;\n        const auto& blobs = edge.second;\n        if (match_set.count(parent)) { // but has a parent that is in subgraph\n          for (const string& blob : blobs) {\n            edge_list.push_back({blob, x});\n          }\n        }\n      }\n    }\n  }\n  // return the list in sorted order, to allow binary searching\n  std::sort(edge_list.begin(), edge_list.end());\n  return edge_list;\n}\n\nNetDef Graph::GetNetDef() {\n  std::vector<bool> visited(nodes_.size(), false);\n\n  // Copy over all the properties of the netdef we're based on\n  NetDef netdef = netdef_;\n\n  // But we're going to put in our own operators.\n  netdef.clear_op();\n\n  // Keeps track of the number of parents yet to be processed.\n  std::vector<int> unchecked_parent_count;\n\n  // We will perform a topological traversal on the nodes, but we will prefer\n  // nodes that come earlier in the execution order.\n\n  // This is a min-heap, which stores its elements in ascending order.\n  // This stores the nodes in the order we process them to be in.\n  // This guarantees the lowest lexicographical topological ordering.\n\n  // This also means the original nodes will be kept in their execution order.\n  std::priority_queue<int, std::vector<int>, std::greater<int>> q;\n\n  // In our graph, G, the nodes don't have a strict ordering. But in the netdef,\n  // they must (since nets are operators executed in some order).\n  // How do we make sure that the order of operators in our generated netdef\n  // is valid?\n  // 1) The ordering of the netdef must be topologically sorted, respect to G.\n  //    If A -> B is an edge in the graph G, then A must come before B in the\n  //    netdef's ordering.\n  // 2) No blob conflicts: If A -> B is an edge in the graph G, and A writes to\n  //    blob X and B reads from blob X, then there cannot be an op that writes\n  //    to blob X between A and B in the ordering.\n  //\n  // Perform a Topological Sort, to find an order for the Operators to be in.\n  // We will keep track of the number of parents each node has.\n  // We begin with an empty queue, and push in all nodes that do not have any\n  // parents. Then, we keep track of all unprocessed parents for each node.\n  // When a node has no more unprocessed parents, we can push it into the queue\n  // to be processed. This guarantees condition 1 is satisfied.\n\n  // TODO(benz): Currently, condition 2 is not guaranteed to be satisified.\n  // However, giving each blob unique names via SSA will satisfy this condition.\n  // Then, the resulting graph can be optimized with memonger.\n\n  for (int i = 0; i < nodes_.size(); i++) {\n    unchecked_parent_count.push_back(node(i).parents.size());\n    if (node(i).parents.size() == 0 && is_node_active(i)) {\n      q.push(i);\n      visited[i] = true;\n    }\n  }\n\n  while (!q.empty()) {\n    int idx = q.top();\n    q.pop();\n    if (!is_node_active(idx)) {\n      continue;\n    }\n    // Creates a new OperatorDef in NetDef\n    auto& op = *(netdef.add_op());\n    // Sets it equal to the OperatorDef at node(idx)\n    op = node(idx).op;\n    for (const auto& edge : node(idx).children) {\n      int child = edge.first;\n      if (!visited[child] && is_node_active(child)) {\n        unchecked_parent_count[child]--;\n        if (unchecked_parent_count[child] == 0) {\n          q.push(child);\n          visited[child] = true;\n        }\n      }\n    }\n  }\n  return netdef;\n}\n\nvoid Graph::DeactivateSubgraph(std::vector<int> subgraph) {\n  for (int idx : subgraph) {\n    // remove all edges connected to inactive node\n    for (const auto& edge : node(idx).parents) {\n      int parent = edge.first;\n      node(parent).children.erase(idx);\n    }\n    for (const auto& edge : node(idx).children) {\n      int child = edge.first;\n      node(child).parents.erase(idx);\n    }\n    // actually mark flags as false\n    node(idx).active = false;\n  }\n}\n\n} // namespace transform\n\nOperatorDef* AddOp(\n    NetDef* netdef_ptr,\n    string op_type,\n    std::vector<string> inputs,\n    std::vector<string> outputs) {\n  CHECK(netdef_ptr);\n  auto& netdef = *netdef_ptr;\n  auto op_ptr = netdef.add_op();\n  auto& op = *op_ptr;\n  op.set_type(op_type);\n  for (const string& inp : inputs) {\n    op.add_input(inp);\n  }\n  for (const string& outp : outputs) {\n    op.add_output(outp);\n  }\n  return op_ptr;\n}\n\nbool MatchStrings(string p, string s) {\n  if (p == \"*\") { // star accepts anything\n    return true;\n  }\n  // TODO(benz): memoize this. (high constant factor boost in performance)\n  vector<string> choices = split('|', p);\n  for (const string& candidate : choices) {\n    if (candidate == s) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool MatchArguments(const OperatorDef& p_op, const OperatorDef& g_op) {\n  for (const auto& p_arg : p_op.arg()) {\n    if (!p_arg.has_name()) {\n      continue;\n    }\n    bool found = false;\n    for (const auto& g_arg : g_op.arg()) {\n      if (p_arg.name() == g_arg.name()) {\n        found = true;\n        if (p_arg.has_f()) {\n          if (!g_arg.has_f() || p_arg.f() != g_arg.f()) {\n            return false;\n          }\n        }\n        if (p_arg.has_i()) {\n          if (!g_arg.has_i() || p_arg.i() != g_arg.i()) {\n            return false;\n          }\n        }\n        if (p_arg.has_s()) {\n          if (!g_arg.has_s() || !MatchStrings(p_arg.s(), g_arg.s())) {\n            return false;\n          }\n        }\n        if (p_arg.floats_size() != g_arg.floats_size()) {\n          return false;\n        }\n        for (int i = 0; i < p_arg.floats_size(); i++) {\n          if (p_arg.floats(i) != g_arg.floats(i)) {\n            return false;\n          }\n        }\n        if (p_arg.ints_size() != g_arg.ints_size()) {\n          return false;\n        }\n        for (int i = 0; i < p_arg.ints_size(); i++) {\n          if (p_arg.ints(i) != g_arg.ints(i)) {\n            return false;\n          }\n        }\n        if (p_arg.strings_size() != g_arg.strings_size()) {\n          return false;\n        }\n        for (int i = 0; i < p_arg.strings_size(); i++) {\n          if (!MatchStrings(p_arg.strings(i), g_arg.strings(i))) {\n            return false;\n          }\n        }\n      }\n    }\n    if (!found) {\n      return false;\n    }\n  }\n  return true;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/graph.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include \"caffe2/utils/string_utils.h\"\n\n#include <algorithm>\n#include <unordered_map>\n#include <unordered_set>\n\nnamespace caffe2 {\n\nnamespace transform {\n\n/**\n *  Graph representation of an operator.\n */\nstruct Node {\n public:\n  // Empty constructor for resize\n  Node() {}\n\n  // Alternate constructor\n  Node(\n      const OperatorDef& op,\n      bool active,\n      std::map<int, std::vector<string>> parents,\n      std::map<int, std::vector<string>> children)\n      : op(op), active(active), parents(parents), children(children) {}\n\n  // The OperatorDef which this node represents.\n  OperatorDef op;\n\n  // Keeps track of if an operator has been deleted through a transformation.\n  bool active = true;\n\n  // Stores a pair (idx, blob_list),\n  //  idx = index of the child\n  //  blob_list = a list of strings, containing the blobs that connect the nodes\n  std::map<int, std::vector<string>> parents;\n  std::map<int, std::vector<string>> children;\n};\n\n/**\n *  Graph representation of a Netdef.\n */\nstruct Graph {\n public:\n  /**\n   * Given a subgraph, gets all of the parents of the subgraph, as well as\n   * their associated blob names. Sorted by blob names.\n   *\n   * <string, int> := (name of blob writing into subgraph,\n   *                  index of node that writes into subgraph using that blob)\n   */\n  const std::vector<std::pair<string, int>> GetSubgraphInput(\n      const std::vector<int>& subgraph);\n\n  /**\n   * Given a subgraph, gets all of the children of the subgraph, as well as\n   * their associated blob names. Sorted by blob names.\n   *\n   * <string, int> := (name of blob reading from subgraph,\n   *                  index of node that reads from subgraph using that blob)\n   */\n  const std::vector<std::pair<string, int>> GetSubgraphOutput(\n      const std::vector<int>& subgraph);\n\n  /**\n   * Graph generation.\n   * Given a netdef, returns a Graph.\n   *\n   * Each node represents an operator.\n   * An edge exists between two nodes if the parent op writes to a blob, which\n   * is the input of the child blob, with no other op writing to the blob in\n   * between the execution order.\n   *\n   * Time Complexity: O(E), where E is the number of blobs\n   */\n  explicit Graph(const NetDef& net_def);\n\n  /**\n   * Generates a NetDef Representation for the current graph.\n   * Nodes are visited in topological order, which is proper Opdef ordering.\n   * TODO(benz):\n   * There exists conflicts with repeated blob names, where topological sorting\n   * is not sufficient for correct netdef representation, unless blobs are\n   * renamed.\n   * For example, if after a transformation, We have operator ancestry:\n   * A --> B --> C, and also A --> D --> E, where B -> C and D -> E uses the\n   * same blob name, then A, B, D, E, C is a correct topological ordering,\n   * but D will write to the blob that C reads from, instead of B.\n   * Currently believe that there will always be ambiguity unless blobs are\n   * renamed.\n   * This is solved by performing SSA on all transformed blob names.\n   */\n  NetDef GetNetDef();\n\n  /**\n   * Deactivate a subgraph, and get rid of all edges into this subgraph.\n   */\n  void DeactivateSubgraph(std::vector<int> subgraph);\n\n  const size_t size() const {\n    return nodes_.size();\n  }\n\n  void push_node(const Node& new_node) {\n    return nodes_.push_back(new_node);\n  }\n\n  void resize_nodes(size_t new_size) {\n    nodes_.resize(new_size);\n  }\n\n  // Index safe, less verbose way to access nodes\n  inline const Node& node(size_t idx) const {\n    return nodes_.at(idx);\n  }\n\n  inline Node& node(size_t idx) {\n    return nodes_.at(idx);\n  }\n\n  inline bool is_node_active(size_t idx) {\n    return node(idx).active;\n  }\n\n  inline const std::set<string>& external_input() const {\n    return external_input_;\n  }\n\n  inline const std::set<string>& external_output() const {\n    return external_output_;\n  }\n\n private:\n  const std::vector<std::pair<string, int>> GetSubgraphPerimeterHelper(\n      bool from_children,\n      const std::vector<int>& match);\n\n  // Stores the netdef representation. Is updated upon calls to GetNetDef.\n  NetDef netdef_;\n\n  // Stores which blobs the graph reads from, and writes to.\n  std::set<string> external_input_;\n  std::set<string> external_output_;\n\n  // Keeps track of all the Operators currently within graph, even if inactive.\n  std::vector<Node> nodes_;\n};\n\n} // namespace transform\n\n// Adds an operator def to a netdef.\n// Returns the ptr, if you want to add anything extra (such as device_option)\nOperatorDef* AddOp(\n    NetDef* netdef_ptr,\n    string op_type,\n    std::vector<string> inputs,\n    std::vector<string> outputs);\n\n/**\n * This allows for the use of * and | to match operator types,\n * engines, or any other property that is represented by strings.\n *\n * For example, if we wanted to match an operator to Conv or FC, we can give:\n * \"Conv|FC\" as the type() of that op.\n */\nbool MatchStrings(string p, string s);\n\n/**\n * This ensures that each named arg that exists in the pattern exists in g_op,\n * is equal in value.\n */\nbool MatchArguments(const OperatorDef& p_op, const OperatorDef& g_op);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/graph_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <google/protobuf/text_format.h>\n#include <gtest/gtest.h>\n#include \"caffe2/core/graph.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\nusing transform::Graph;\n\nstatic std::atomic<int> counter;\n\nclass GraphDummyOp final : public OperatorBase {\n public:\n  using OperatorBase::OperatorBase;\n  bool Run(int /* unused */) override {\n    counter.fetch_add(1);\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(GraphDummyOp1, GraphDummyOp);\n\nOPERATOR_SCHEMA(GraphDummyOp1)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\n\nREGISTER_CPU_OPERATOR(GraphDummyOp2, GraphDummyOp);\n\nOPERATOR_SCHEMA(GraphDummyOp2)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\n\nREGISTER_CPU_OPERATOR(GraphDummyOp3, GraphDummyOp);\n\nOPERATOR_SCHEMA(GraphDummyOp3)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\n\n// Checks if two netdefs are  in terms of type, input, and output.\nvoid compare_netdefs(const NetDef& net_a, const NetDef& net_b) {\n  EXPECT_EQ(net_a.op_size(), net_b.op_size());\n  for (int i = 0; i < net_a.op_size(); i++) {\n    EXPECT_EQ(net_a.op(i).type(), net_b.op(i).type());\n    EXPECT_EQ(net_a.op(i).input_size(), net_b.op(i).input_size());\n    for (int j = 0; j < net_a.op(i).input_size(); j++) {\n      EXPECT_EQ(net_a.op(i).input(j), net_b.op(i).input(j));\n    }\n    EXPECT_EQ(net_a.op(i).output_size(), net_b.op(i).output_size());\n    for (int j = 0; j < net_a.op(i).output_size(); j++) {\n      EXPECT_EQ(net_a.op(i).output(j), net_b.op(i).output(j));\n    }\n  }\n}\n\nTEST(GraphTest, TestGenerateGraphChain) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef netdef;\n  AddOp(&netdef, \"GraphDummyOp1\", {\"in\"}, {\"mid1\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"mid1\"}, {\"mid2\"});\n  AddOp(&netdef, \"GraphDummyOp1\", {\"mid2\"}, {\"mid3\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"mid3\"}, {\"out\"});\n  Graph g(netdef);\n  EXPECT_EQ(g.size(), 4);\n  for (int i = 0; i < 4; i++) {\n    if (i < 3) {\n      EXPECT_EQ(g.node(i).children.size(), 1);\n      EXPECT_TRUE(g.node(i).children.count(i + 1));\n    }\n    if (i > 0) {\n      EXPECT_EQ(g.node(i).parents.size(), 1);\n      EXPECT_TRUE(g.node(i).parents.count(i - 1));\n    }\n  }\n  NetDef retrieved_net = g.GetNetDef();\n  compare_netdefs(retrieved_net, netdef);\n}\n\nTEST(GraphTest, TestGenerateGraphChainInPlace) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef netdef;\n  AddOp(&netdef, \"GraphDummyOp1\", {\"in\"}, {\"out\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"out\"}, {\"out\"});\n  AddOp(&netdef, \"GraphDummyOp1\", {\"out\"}, {\"out\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"out\"}, {\"out\"});\n  Graph g(netdef);\n  EXPECT_EQ(g.size(), 4);\n  for (int i = 0; i < 4; i++) {\n    if (i < 3) {\n      EXPECT_EQ(g.node(i).children.size(), 1);\n      EXPECT_TRUE(g.node(i).children.count(i + 1));\n    }\n    if (i > 0) {\n      EXPECT_EQ(g.node(i).parents.size(), 1);\n      EXPECT_TRUE(g.node(i).parents.count(i - 1));\n    }\n  }\n  NetDef retrieved_net = g.GetNetDef();\n  compare_netdefs(retrieved_net, netdef);\n}\n\n// Diamond Graph\nTEST(GraphTest, TestGenerateGraphBranch) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef netdef;\n\n  AddOp(&netdef, \"GraphDummyOp1\", {\"in\"}, {\"mid1\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"mid1\"}, {\"mid2\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"mid1\"}, {\"mid3\"});\n  AddOp(&netdef, \"GraphDummyOp3\", {\"mid2\", \"mid3\"}, {\"out\"});\n\n  Graph g(netdef);\n\n  EXPECT_EQ(g.size(), 4);\n  EXPECT_EQ(g.node(0).parents.size(), 0);\n  EXPECT_EQ(g.node(0).children.size(), 2);\n  EXPECT_EQ(g.node(1).parents.size(), 1);\n  EXPECT_EQ(g.node(1).children.size(), 1);\n  EXPECT_EQ(g.node(2).parents.size(), 1);\n  EXPECT_EQ(g.node(2).children.size(), 1);\n  EXPECT_EQ(g.node(3).parents.size(), 2);\n  EXPECT_EQ(g.node(3).children.size(), 0);\n\n  NetDef retrieved_net = g.GetNetDef();\n  compare_netdefs(retrieved_net, netdef);\n}\n\n// Double Diamond Graph, reused names\nTEST(GraphTest, TestReusedInputs) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef netdef;\n\n  AddOp(&netdef, \"GraphDummyOp1\", {\"in\"}, {\"in\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"in\"}, {\"mid1\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"in\"}, {\"mid2\"});\n  AddOp(&netdef, \"GraphDummyOp3\", {\"mid1\", \"mid2\"}, {\"in\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"in\"}, {\"mid1\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"in\"}, {\"mid2\"});\n  AddOp(&netdef, \"GraphDummyOp3\", {\"mid1\", \"mid2\"}, {\"in\"});\n\n  Graph g(netdef);\n\n  EXPECT_EQ(g.size(), 7);\n  EXPECT_EQ(g.node(0).parents.size(), 0);\n  EXPECT_EQ(g.node(0).children.size(), 2);\n  EXPECT_EQ(g.node(1).parents.size(), 1);\n  EXPECT_EQ(g.node(1).children.size(), 1);\n  EXPECT_EQ(g.node(2).parents.size(), 1);\n  EXPECT_EQ(g.node(2).children.size(), 1);\n  EXPECT_EQ(g.node(3).parents.size(), 2);\n  EXPECT_EQ(g.node(3).children.size(), 2);\n  EXPECT_EQ(g.node(4).parents.size(), 1);\n  EXPECT_EQ(g.node(4).children.size(), 1);\n  EXPECT_EQ(g.node(5).parents.size(), 1);\n  EXPECT_EQ(g.node(5).children.size(), 1);\n  EXPECT_EQ(g.node(6).parents.size(), 2);\n  EXPECT_EQ(g.node(6).children.size(), 0);\n\n  NetDef retrieved_net = g.GetNetDef();\n  compare_netdefs(retrieved_net, netdef);\n}\n\nTEST(GraphTest, TestGetPerimeter) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef netdef;\n\n  AddOp(&netdef, \"GraphDummyOp1\", {\"in\"}, {\"in\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"in\"}, {\"mid1\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"in\"}, {\"mid2\"});\n  AddOp(&netdef, \"GraphDummyOp3\", {\"mid1\", \"mid2\"}, {\"in\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"in\"}, {\"mid1\"});\n  AddOp(&netdef, \"GraphDummyOp2\", {\"in\"}, {\"mid2\"});\n  AddOp(&netdef, \"GraphDummyOp1\", {\"mid1\", \"mid2\"}, {\"in\"});\n\n  Graph g(netdef);\n  std::vector<int> subgraph = {3};\n\n  auto subgraph_input = g.GetSubgraphInput(subgraph);\n  EXPECT_EQ(subgraph_input.size(), 2);\n  EXPECT_EQ(subgraph_input[0], std::make_pair(string(\"mid1\"), 1));\n  EXPECT_EQ(subgraph_input[1], std::make_pair(string(\"mid2\"), 2));\n\n  auto subgraph_output = g.GetSubgraphOutput(subgraph);\n  EXPECT_EQ(subgraph_output.size(), 2);\n  EXPECT_EQ(subgraph_output[0], std::make_pair(string(\"in\"), 4));\n  EXPECT_EQ(subgraph_output[1], std::make_pair(string(\"in\"), 5));\n}\n\n} // namespace\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/init.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/operator.h\" // for StaticLinkingProtector\n\n#include <iomanip>\n\nCAFFE2_DEFINE_bool(caffe2_version, false,\n                   \"Print Caffe2 version and build options on startup\");\n\nnamespace caffe2 {\n\nnamespace internal {\nCaffe2InitializeRegistry* Caffe2InitializeRegistry::Registry() {\n  static Caffe2InitializeRegistry gRegistry;\n  return &gRegistry;\n}\n}\n\nbool GlobalInit(int* pargc, char*** pargv) {\n  static bool global_init_was_already_run = false;\n  static StaticLinkingProtector g_protector;\n  if (global_init_was_already_run) {\n    VLOG(1) << \"GlobalInit has already been called: did you double-call?\";\n    return true;\n  }\n  global_init_was_already_run = true;\n  bool success = true;\n  success &= internal::Caffe2InitializeRegistry::Registry()\n      ->RunRegisteredEarlyInitFunctions(pargc, pargv);\n  CAFFE_ENFORCE(success,\n                \"Failed to run some early init functions for caffe2.\");\n  success &= ParseCaffeCommandLineFlags(pargc, pargv);\n  success &= InitCaffeLogging(pargc, *pargv);\n  // Print out the current build version. Using cerr as LOG(INFO) might be off\n  if (VLOG_IS_ON(1) || FLAGS_caffe2_version) {\n    std::cerr << \"Caffe2 build configuration: \" << std::endl;\n    for (const auto& it : GetBuildOptions()) {\n      std::cerr << \"  \" << std::setw(25) << std::left << it.first << \" : \"\n                << it.second << std::endl;\n    }\n  }\n  // All other initialization functions.\n  success &= internal::Caffe2InitializeRegistry::Registry()\n      ->RunRegisteredInitFunctions(pargc, pargv);\n  if (!success) {\n    global_init_was_already_run = false;\n  }\n  CAFFE_ENFORCE(success,\n                \"Failed to run some init functions for caffe2.\");\n  // TODO: if we fail GlobalInit(), should we continue?\n  return success;\n}\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/init.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_INIT_H_\n#define CAFFE2_CORE_INIT_H_\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\n\nnamespace internal {\nclass Caffe2InitializeRegistry {\n public:\n  typedef bool (*InitFunction)(int*, char***);\n  // Registry() is defined in .cpp file to make registration work across\n  // multiple shared libraries loaded with RTLD_LOCAL\n  static Caffe2InitializeRegistry* Registry();\n\n  void Register(InitFunction function, bool run_early,\n                const char* description) {\n    if (run_early) {\n      early_init_functions_.emplace_back(function, description);\n    } else {\n      init_functions_.emplace_back(function, description);\n    }\n  }\n\n  bool RunRegisteredEarlyInitFunctions(int* pargc, char*** pargv) {\n    return RunRegisteredInitFunctionsInternal(\n        early_init_functions_, pargc, pargv);\n  }\n\n  bool RunRegisteredInitFunctions(int* pargc, char*** pargv) {\n    return RunRegisteredInitFunctionsInternal(init_functions_, pargc, pargv);\n  }\n\n private:\n  // Run all registered initialization functions. This has to be called AFTER\n  // all static initialization are finished and main() has started, since we are\n  // using logging.\n  bool RunRegisteredInitFunctionsInternal(\n      vector<std::pair<InitFunction, const char*>>& functions,\n      int* pargc, char*** pargv) {\n    for (const auto& init_pair : functions) {\n      VLOG(1) << \"Running init function: \" << init_pair.second;\n      if (!(*init_pair.first)(pargc, pargv)) {\n        LOG(ERROR) << \"Initialization function failed.\";\n        return false;\n      }\n    }\n    return true;\n  }\n\n  Caffe2InitializeRegistry() {}\n  vector<std::pair<InitFunction, const char*> > early_init_functions_;\n  vector<std::pair<InitFunction, const char*> > init_functions_;\n};\n}  // namespace internal\n\nclass InitRegisterer {\n public:\n  InitRegisterer(internal::Caffe2InitializeRegistry::InitFunction function,\n                 bool run_early, const char* description) {\n    internal::Caffe2InitializeRegistry::Registry()\n        ->Register(function, run_early, description);\n  }\n};\n\n#define REGISTER_CAFFE2_INIT_FUNCTION(name, function, description)             \\\n  namespace {                                                                  \\\n  ::caffe2::InitRegisterer g_caffe2_initregisterer_##name(                     \\\n      function, false, description);                                           \\\n  }  // namespace\n\n#define REGISTER_CAFFE2_EARLY_INIT_FUNCTION(name, function, description)       \\\n  namespace {                                                                  \\\n  ::caffe2::InitRegisterer g_caffe2_initregisterer_##name(                     \\\n      function, true, description);                                            \\\n  }  // namespace\n\n/**\n * @brief Initialize the global environment of caffe2.\n *\n * Caffe2 uses a registration pattern for initialization functions. Custom\n * initialization functions should take the signature\n *     bool (*func)(int*, char***)\n * where the pointers to argc and argv are passed in. Caffe2 then runs the\n * initialization in three phases:\n * (1) Functions registered with REGISTER_CAFFE2_EARLY_INIT_FUNCTION. Note that\n *     since it is possible the logger is not initialized yet, any logging in\n *     such early init functions may not be printed correctly.\n * (2) Parses Caffe-specific commandline flags, and initializes caffe logging.\n * (3) Functions registered with REGISTER_CAFFE2_INIT_FUNCTION.\n * If there is something wrong at each stage, the function returns false. If\n * the global initialization has already been run, the function returns false\n * as well.\n */\nbool GlobalInit(int* pargc, char*** argv);\n\n}  // namespace caffe2\n#endif  // CAFFE2_CORE_INIT_H_\n"
  },
  {
    "path": "caffe2/core/init_intrinsics_check.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/utils/cpuid.h\"\n\nCAFFE2_DEFINE_bool(\n    caffe2_quit_on_unsupported_cpu_feature,\n    false,\n    \"If set, when Caffe2 is built with a CPU feature (like avx2) but the \"\n    \"current CPU does not support it, quit early. If not set (by default), \"\n    \"log this as an error message and continue execution.\");\n\nnamespace caffe2 {\n\nstatic void QuitIfFeatureUnsupported(\n    const bool cpu_has_feature, const string& feature) {\n  VLOG(1) << \"Caffe2 built with \" << feature << \".\";\n  if (!cpu_has_feature) {\n    string err_string =\n        \"The Caffe2 binary is compiled with CPU feature \" + feature +\n        \", but your CPU does not support it. This will lead to segfaults \"\n        \"on your machine, such as SIGILL 'illegal instructions' on Linux. \"\n        \"As a result Caffe2 will preemptively quit. Please install or \"\n        \"build a Caffe2 binary with the feature turned off.\";\n    if (FLAGS_caffe2_quit_on_unsupported_cpu_feature) {\n      LOG(FATAL) << err_string;\n    } else {\n      LOG(ERROR) << err_string;\n    }\n  }\n}\n\nstatic void WarnIfFeatureUnused(\n    const bool cpu_has_feature, const string& feature) {\n  VLOG(1) << \"Caffe2 not built with \" << feature << \".\";\n  if (cpu_has_feature) {\n#ifdef CAFFE2_NO_CROSS_ARCH_WARNING\n    // When cross-compiling single binary for multiple archs - turns off the\n    // annoying warning\n    VLOG(1)\n#else\n    LOG(ERROR)\n#endif\n        << \"CPU feature \" << feature\n        << \" is present on your machine, \"\n           \"but the Caffe2 binary is not compiled with it. It means you \"\n           \"may not get the full speed of your CPU.\";\n  }\n}\n\nbool Caffe2CheckIntrinsicsFeatures(int*, char***) {\n\n#ifdef __AVX__\n  QuitIfFeatureUnsupported(GetCpuId().avx(), \"avx\");\n#else\n  WarnIfFeatureUnused(GetCpuId().avx(), \"avx\");\n#endif\n\n#ifdef __AVX2__\n  QuitIfFeatureUnsupported(GetCpuId().avx2(), \"avx2\");\n#else\n  WarnIfFeatureUnused(GetCpuId().avx2(), \"avx2\");\n#endif\n\n#ifdef __FMA__\n  QuitIfFeatureUnsupported(GetCpuId().fma(), \"fma\");\n#else\n  WarnIfFeatureUnused(GetCpuId().fma(), \"fma\");\n#endif\n\n  return true;\n}\n\nREGISTER_CAFFE2_INIT_FUNCTION(\n    Caffe2CheckIntrinsicsFeatures,\n    &Caffe2CheckIntrinsicsFeatures,\n    \"Check intrinsics compatibility between the CPU feature and the binary.\");\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/init_omp.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdlib.h>\n\n#include \"caffe2/core/common.h\"\n\n#ifdef _OPENMP\n#include \"caffe2/core/common_omp.h\"\n#endif  // _OPENMP\n\n#ifdef CAFFE2_USE_MKL\n#include <mkl.h>\n#endif // CAFFE2_USE_MKL\n\n#include \"caffe2/core/init.h\"\n\nCAFFE2_DEFINE_int(\n    caffe2_omp_num_threads, 0,\n    \"The number of openmp threads. 0 to use default value. \"\n    \"Does not have effect if OpenMP is disabled.\");\nCAFFE2_DEFINE_int(\n    caffe2_mkl_num_threads,\n    0,\n    \"The number of mkl threads. 0 to use default value. If set, \"\n    \"this overrides the caffe2_omp_num_threads flag if both are set. \"\n    \"Does not have effect if MKL is not used.\");\n\nnamespace caffe2 {\n\n#ifdef _OPENMP\nbool Caffe2SetOpenMPThreads(int*, char***) {\n  if (!getenv(\"OMP_NUM_THREADS\")) {\n    // OMP_NUM_THREADS not passed explicitly, so *disable* OMP by\n    // default. The user can use the CLI flag to override.\n    VLOG(1) << \"OMP_NUM_THREADS not passed, defaulting to 1 thread\";\n    omp_set_num_threads(1);\n  }\n\n  if (FLAGS_caffe2_omp_num_threads > 0) {\n    VLOG(1) << \"Setting omp_num_threads to \" << FLAGS_caffe2_omp_num_threads;\n    omp_set_num_threads(FLAGS_caffe2_omp_num_threads);\n  }\n  VLOG(1) << \"Caffe2 running with \" << omp_get_max_threads() << \" OMP threads\";\n  return true;\n}\nREGISTER_CAFFE2_INIT_FUNCTION(Caffe2SetOpenMPThreads,\n                              &Caffe2SetOpenMPThreads,\n                              \"Set OpenMP threads.\");\n#endif // _OPENMP\n\n#ifdef CAFFE2_USE_MKL\nbool Caffe2SetMKLThreads(int*, char***) {\n  if (!getenv(\"MKL_NUM_THREADS\")) {\n    VLOG(1) << \"MKL_NUM_THREADS not passed, defaulting to 1 thread\";\n    mkl_set_num_threads(1);\n  }\n\n  // If caffe2_omp_num_threads is set, we use that for MKL as well.\n  if (FLAGS_caffe2_omp_num_threads > 0) {\n    VLOG(1) << \"Setting mkl_num_threads to \" << FLAGS_caffe2_omp_num_threads\n            << \" as inherited from omp_num_threads.\";\n    mkl_set_num_threads(FLAGS_caffe2_omp_num_threads);\n  }\n\n  // Override omp_num_threads if mkl_num_threads is set.\n  if (FLAGS_caffe2_mkl_num_threads > 0) {\n    VLOG(1) << \"Setting mkl_num_threads to \" << FLAGS_caffe2_mkl_num_threads;\n    mkl_set_num_threads(FLAGS_caffe2_mkl_num_threads);\n  }\n  VLOG(1) << \"Caffe2 running with \" << mkl_get_max_threads() << \" MKL threads\";\n  return true;\n}\nREGISTER_CAFFE2_INIT_FUNCTION(\n    Caffe2SetMKLThreads,\n    &Caffe2SetMKLThreads,\n    \"Set MKL threads.\");\n#endif // CAFFE2_USE_MKL\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/init_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n#include <memory>\n\n#include \"caffe2/core/init.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\nnamespace {\nbool gTestInitFunctionHasBeenRun = false;\n\nbool TestInitFunction(int*, char***) {\n  gTestInitFunctionHasBeenRun = true;\n  return true;\n}\nREGISTER_CAFFE2_INIT_FUNCTION(TestInitFunction,\n                              &TestInitFunction,\n                              \"Just a test to see if GlobalInit invokes \"\n                              \"registered functions correctly.\");\n\nint dummy_argc = 1;\nconst char* dummy_name = \"foo\";\nchar** dummy_argv = const_cast<char**>(&dummy_name);\n}  // namespace\n\nTEST(InitTest, TestInitFunctionHasRun) {\n  caffe2::GlobalInit(&dummy_argc, &dummy_argv);\n  EXPECT_TRUE(gTestInitFunctionHasBeenRun);\n}\n\nTEST(InitTest, CanRerunGlobalInit) {\n  caffe2::GlobalInit(&dummy_argc, &dummy_argv);\n  EXPECT_TRUE(caffe2::GlobalInit(&dummy_argc, &dummy_argv));\n}\n\n}  // namespace caffe2\n\n\n"
  },
  {
    "path": "caffe2/core/logging.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/flags.h\"\n\n#include <algorithm>\n#include <cstring>\n#include <numeric>\n\n// Common code that we use regardless of whether we use glog or not.\n\nCAFFE2_DEFINE_bool(caffe2_use_fatal_for_enforce, false,\n                   \"If set true, when CAFFE_ENFORCE is not met, abort instead \"\n                   \"of throwing an exception.\");\n\nnamespace caffe2 {\nstd::string StripBasename(const std::string &full_path) {\n  const char kSeparator = '/';\n  size_t pos = full_path.rfind(kSeparator);\n  if (pos != std::string::npos) {\n    return full_path.substr(pos + 1, std::string::npos);\n  } else {\n    return full_path;\n  }\n}\n\nsize_t ReplaceAll(string& s, const char* from, const char* to) {\n  CAFFE_ENFORCE(from && *from);\n  CAFFE_ENFORCE(to);\n\n  size_t numReplaced = 0;\n  string::size_type lenFrom = std::strlen(from);\n  string::size_type lenTo = std::strlen(to);\n  for (string::size_type pos = s.find(from); pos != string::npos;\n       pos = s.find(from, pos + lenTo)) {\n    s.replace(pos, lenFrom, to);\n    numReplaced++;\n  }\n  return numReplaced;\n}\n\nstatic std::function<string(void)> FetchStackTrace = []() { return \"\"; };\n\nvoid SetStackTraceFetcher(std::function<string(void)> fetcher) {\n  FetchStackTrace = fetcher;\n}\n\nstatic std::function<void(const OperatorDef&)> OperatorLogger =\n    [](const OperatorDef&) { return; };\n\nvoid SetOperatorLogger(std::function<void(const OperatorDef&)> tracer) {\n  OperatorLogger = tracer;\n}\n\nstd::function<void(const OperatorDef&)> GetOperatorLogger() {\n  return OperatorLogger;\n}\n\nEnforceNotMet::EnforceNotMet(\n    const char* file,\n    const int line,\n    const char* condition,\n    const string& msg,\n    const void* caller)\n    : msg_stack_{MakeString(\n          \"[enforce fail at \",\n          StripBasename(std::string(file)),\n          \":\",\n          line,\n          \"] \",\n          condition,\n          \". \",\n          msg,\n          \" \")},\n      stack_trace_(FetchStackTrace()) {\n  if (FLAGS_caffe2_use_fatal_for_enforce) {\n    LOG(FATAL) << msg_stack_[0];\n  }\n  caller_ = caller;\n  full_msg_ = this->msg();\n}\n\nvoid EnforceNotMet::AppendMessage(const string& msg) {\n  msg_stack_.push_back(msg);\n  full_msg_ = this->msg();\n}\n\nstring EnforceNotMet::msg() const {\n  return std::accumulate(msg_stack_.begin(), msg_stack_.end(), string(\"\")) +\n      stack_trace_;\n}\n\nconst char* EnforceNotMet::what() const noexcept {\n  return full_msg_.c_str();\n}\n\nconst void* EnforceNotMet::caller() const noexcept {\n  return caller_;\n}\n\n}  // namespace caffe2\n\n\n#ifdef CAFFE2_USE_GOOGLE_GLOG\n\n#ifdef CAFFE2_USE_GFLAGS\n// GLOG's minloglevel\nCAFFE2_DECLARE_int(minloglevel);\n// GLOG's verbose log value.\nCAFFE2_DECLARE_int(v);\n// GLOG's logtostderr value\nCAFFE2_DECLARE_bool(logtostderr);\n\n#else\n\nusing fLI::FLAGS_minloglevel;\nusing fLI::FLAGS_v;\nusing fLB::FLAGS_logtostderr;\n\n#endif // CAFFE2_USE_GFLAGS\n\nCAFFE2_DEFINE_int(caffe2_log_level, google::GLOG_ERROR,\n                  \"The minimum log level that caffe2 will output.\");\n\n// Google glog's api does not have an external function that allows one to check\n// if glog is initialized or not. It does have an internal function - so we are\n// declaring it here. This is a hack but has been used by a bunch of others too\n// (e.g. Torch).\nnamespace google {\nnamespace glog_internal_namespace_ {\nbool IsGoogleLoggingInitialized();\n}  // namespace glog_internal_namespace_\n}  // namespace google\n\n\nnamespace caffe2 {\nbool InitCaffeLogging(int* argc, char** argv) {\n  if (*argc == 0) return true;\n#if !defined(_MSC_VER)\n  // This trick can only be used on UNIX platforms\n  if (!::google::glog_internal_namespace_::IsGoogleLoggingInitialized())\n#endif\n  {\n    ::google::InitGoogleLogging(argv[0]);\n#if !defined(_MSC_VER)\n  // This is never defined on Windows\n    ::google::InstallFailureSignalHandler();\n#endif\n  }\n  // If caffe2_log_level is set and is lower than the min log level by glog,\n  // we will transfer the caffe2_log_level setting to glog to override that.\n  FLAGS_minloglevel = std::min(FLAGS_caffe2_log_level, FLAGS_minloglevel);\n  // If caffe2_log_level is explicitly set, let's also turn on logtostderr.\n  if (FLAGS_caffe2_log_level < google::GLOG_ERROR) {\n    FLAGS_logtostderr = 1;\n  }\n  // Also, transfer the caffe2_log_level verbose setting to glog.\n  if (FLAGS_caffe2_log_level < 0) {\n    FLAGS_v = std::min(FLAGS_v, -FLAGS_caffe2_log_level);\n  }\n  return true;\n}\n\nvoid ShowLogInfoToStderr() {\n  FLAGS_logtostderr = 1;\n  FLAGS_minloglevel = std::min(FLAGS_minloglevel, google::GLOG_INFO);\n}\n}  // namespace caffe2\n\n#else  // !CAFFE2_USE_GOOGLE_GLOG\n\n#ifdef ANDROID\n#include <android/log.h>\n#endif // ANDROID\n\nCAFFE2_DEFINE_int(caffe2_log_level, ERROR,\n                  \"The minimum log level that caffe2 will output.\");\n\nnamespace caffe2 {\nbool InitCaffeLogging(int* argc, char** argv) {\n  // When doing InitCaffeLogging, we will assume that caffe's flag paser has\n  // already finished.\n  if (*argc == 0) return true;\n  if (!CommandLineFlagsHasBeenParsed()) {\n    std::cerr << \"InitCaffeLogging() has to be called after \"\n                 \"ParseCaffeCommandLineFlags. Modify your program to make sure \"\n                 \"of this.\" << std::endl;\n    return false;\n  }\n  if (FLAGS_caffe2_log_level > FATAL) {\n    std::cerr << \"The log level of Caffe2 has to be no larger than FATAL(\"\n              << FATAL << \"). Capping it to FATAL.\" << std::endl;\n    FLAGS_caffe2_log_level = FATAL;\n  }\n  return true;\n}\n\nvoid ShowLogInfoToStderr() {\n  FLAGS_caffe2_log_level = INFO;\n}\n\nMessageLogger::MessageLogger(const char *file, int line, int severity)\n  : severity_(severity) {\n  if (severity_ < FLAGS_caffe2_log_level) {\n    // Nothing needs to be logged.\n    return;\n  }\n#ifdef ANDROID\n  tag_ = \"native\";\n#else  // !ANDROID\n  tag_ = \"\";\n#endif  // ANDROID\n  /*\n  time_t rawtime;\n  struct tm * timeinfo;\n  time(&rawtime);\n  timeinfo = localtime(&rawtime);\n  std::chrono::nanoseconds ns =\n      std::chrono::duration_cast<std::chrono::nanoseconds>(\n          std::chrono::high_resolution_clock::now().time_since_epoch());\n  */\n  stream_ << \"[\" << CAFFE2_SEVERITY_PREFIX[std::min(4, FATAL - severity_)]\n          //<< (timeinfo->tm_mon + 1) * 100 + timeinfo->tm_mday\n          //<< std::setfill('0')\n          //<< \" \" << std::setw(2) << timeinfo->tm_hour\n          //<< \":\" << std::setw(2) << timeinfo->tm_min\n          //<< \":\" << std::setw(2) << timeinfo->tm_sec\n          //<< \".\" << std::setw(9) << ns.count() % 1000000000\n          << \" \" << StripBasename(std::string(file)) << \":\" << line << \"] \";\n}\n\n// Output the contents of the stream to the proper channel on destruction.\nMessageLogger::~MessageLogger() {\n  if (severity_ < FLAGS_caffe2_log_level) {\n    // Nothing needs to be logged.\n    return;\n  }\n  stream_ << \"\\n\";\n#ifdef ANDROID\n  static const int android_log_levels[] = {\n      ANDROID_LOG_FATAL,    // LOG_FATAL\n      ANDROID_LOG_ERROR,    // LOG_ERROR\n      ANDROID_LOG_WARN,     // LOG_WARNING\n      ANDROID_LOG_INFO,     // LOG_INFO\n      ANDROID_LOG_DEBUG,    // VLOG(1)\n      ANDROID_LOG_VERBOSE,  // VLOG(2) .. VLOG(N)\n  };\n  int android_level_index = FATAL - std::min(FATAL, severity_);\n  int level = android_log_levels[std::min(android_level_index, 5)];\n  // Output the log string the Android log at the appropriate level.\n  __android_log_print(level, tag_, \"%s\", stream_.str().c_str());\n  // Indicate termination if needed.\n  if (severity_ == FATAL) {\n    __android_log_print(ANDROID_LOG_FATAL, tag_, \"terminating.\\n\");\n  }\n#else  // !ANDROID\n  if (severity_ >= FLAGS_caffe2_log_level) {\n    // If not building on Android, log all output to std::cerr.\n    std::cerr << stream_.str();\n  }\n#endif  // ANDROID\n  if (severity_ == FATAL) {\n    DealWithFatal();\n  }\n}\n\n}  // namespace caffe2\n\n#endif  // !CAFFE2_USE_GOOGLE_GLOG\n"
  },
  {
    "path": "caffe2/core/logging.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_LOGGING_H_\n#define CAFFE2_CORE_LOGGING_H_\n\n#include <climits>\n#include <exception>\n#include <functional>\n#include <limits>\n#include <sstream>\n\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\n// CAFFE2_LOG_THRESHOLD is a compile time flag that would allow us to turn off\n// logging at compile time so no logging message below that level is produced\n// at all. The value should be between INT_MIN and CAFFE_FATAL.\n#ifndef CAFFE2_LOG_THRESHOLD\n// If we have not defined the compile time log threshold, we keep all the\n// log cases.\n#define CAFFE2_LOG_THRESHOLD INT_MIN\n#endif // CAFFE2_LOG_THRESHOLD\n\n// Below are different implementations for glog and non-glog cases.\n#ifdef CAFFE2_USE_GOOGLE_GLOG\n#include \"caffe2/core/logging_is_google_glog.h\"\n#else // !CAFFE2_USE_GOOGLE_GLOG\n#include \"caffe2/core/logging_is_not_google_glog.h\"\n#endif // CAFFE2_USE_GOOGLE_GLOG\n\nCAFFE2_DECLARE_int(caffe2_log_level);\nCAFFE2_DECLARE_bool(caffe2_use_fatal_for_enforce);\n\nnamespace caffe2 {\n// Functions that we use for initialization.\nbool InitCaffeLogging(int* argc, char** argv);\n\nconstexpr bool IsUsingGoogleLogging() {\n#ifdef CAFFE2_USE_GOOGLE_GLOG\n  return true;\n#else\n  return false;\n#endif\n}\n\n/**\n * A utility to allow one to show log info to stderr after the program starts.\n *\n * This is similar to calling GLOG's --logtostderr, or setting caffe2_log_level\n * to smaller than INFO. You are recommended to only use this in a few sparse\n * cases, such as when you want to write a tutorial or something. Normally, use\n * the commandline flags to set the log level.\n */\nvoid ShowLogInfoToStderr();\n\ninline void MakeStringInternal(std::stringstream& /*ss*/) {}\n\ntemplate <typename T>\ninline void MakeStringInternal(std::stringstream& ss, const T& t) {\n  ss << t;\n}\n\ntemplate <typename T, typename... Args>\ninline void\nMakeStringInternal(std::stringstream& ss, const T& t, const Args&... args) {\n  MakeStringInternal(ss, t);\n  MakeStringInternal(ss, args...);\n}\n\ntemplate <typename... Args>\nstring MakeString(const Args&... args) {\n  std::stringstream ss;\n  MakeStringInternal(ss, args...);\n  return string(ss.str());\n}\n\n// Specializations for already-a-string types.\ntemplate <>\ninline string MakeString(const string& str) {\n  return str;\n}\ninline string MakeString(const char* c_str) {\n  return string(c_str);\n}\n\ntemplate <class Container>\ninline string Join(const string& delimiter, const Container& v) {\n  std::stringstream s;\n  int cnt = static_cast<int64_t>(v.size()) - 1;\n  for (auto i = v.begin(); i != v.end(); ++i, --cnt) {\n    s << (*i) << (cnt ? delimiter : \"\");\n  }\n  return s.str();\n}\n\n// Obtains the base name from a full path.\nstring StripBasename(const std::string& full_path);\n\n// Replace all occurrences of \"from\" substring to \"to\" string.\n// Returns number of replacements\nsize_t ReplaceAll(string& s, const char* from, const char* to);\n\nvoid SetStackTraceFetcher(std::function<string(void)> fetcher);\n\nvoid SetOperatorLogger(std::function<void(const OperatorDef&)> tracer);\nstd::function<void(const OperatorDef&)> GetOperatorLogger();\n\nclass EnforceNotMet : public std::exception {\n public:\n  EnforceNotMet(\n      const char* file,\n      const int line,\n      const char* condition,\n      const string& msg,\n      const void* caller=nullptr);\n  void AppendMessage(const string& msg);\n  string msg() const;\n  inline const vector<string>& msg_stack() const {\n    return msg_stack_;\n  }\n\n  const char* what() const noexcept override;\n\n  const void* caller() const noexcept;\n\n private:\n  vector<string> msg_stack_;\n  string full_msg_;\n  string stack_trace_;\n  const void* caller_;\n};\n\n#define CAFFE_ENFORCE(condition, ...)                                         \\\n  do {                                                                        \\\n    if (!(condition)) {                                                       \\\n      throw ::caffe2::EnforceNotMet(                                          \\\n          __FILE__, __LINE__, #condition, ::caffe2::MakeString(__VA_ARGS__)); \\\n    }                                                                         \\\n  } while (false)\n\n#define CAFFE_ENFORCE_WITH_CALLER(condition, ...)                             \\\n  do {                                                                        \\\n    if (!(condition)) {                                                       \\\n      throw ::caffe2::EnforceNotMet(                                          \\\n          __FILE__, __LINE__, #condition, ::caffe2::MakeString(__VA_ARGS__), this); \\\n    }                                                                         \\\n  } while (false)\n\n#define CAFFE_THROW(...)         \\\n  throw ::caffe2::EnforceNotMet( \\\n      __FILE__, __LINE__, \"\", ::caffe2::MakeString(__VA_ARGS__))\n\n/**\n * Rich logging messages\n *\n * CAFFE_ENFORCE_THAT can be used with one of the \"checker functions\" that\n * capture input argument values and add it to the exception message. E.g.\n * `CAFFE_ENFORCE_THAT(Equals(foo(x), bar(y)), \"Optional additional message\")`\n * would evaluate both foo and bar only once and if the results are not equal -\n * include them in the exception message.\n *\n * Some of the basic checker functions like Equals or Greater are already\n * defined below. Other header might define customized checkers by adding\n * functions to caffe2::enforce_detail namespace. For example:\n *\n *   namespace caffe2 { namespace enforce_detail {\n *   inline EnforceFailMessage IsVector(const vector<TIndex>& shape) {\n *     if (shape.size() == 1) { return EnforceOK(); }\n *     return MakeString(\"Shape \", shape, \" is not a vector\");\n *   }\n *   }}\n *\n * With further usages like `CAFFE_ENFORCE_THAT(IsVector(Input(0).dims()))`\n *\n * Convenient wrappers for binary operations like CAFFE_ENFORCE_EQ are provided\n * too. Please use them instead of CHECK_EQ and friends for failures in\n * user-provided input.\n */\n\nnamespace enforce_detail {\n\nstruct EnforceOK {};\n\nclass EnforceFailMessage {\n public:\n#ifdef _MSC_VER\n  // MSVC + NVCC ignores constexpr and will issue a warning if included.\n  /* implicit */ EnforceFailMessage(EnforceOK) : msg_(nullptr) {}\n#else\n  constexpr /* implicit */ EnforceFailMessage(EnforceOK) : msg_(nullptr) {}\n#endif\n  EnforceFailMessage(EnforceFailMessage&&) = default;\n  EnforceFailMessage(const EnforceFailMessage&) = delete;\n  EnforceFailMessage& operator=(EnforceFailMessage&&) = delete;\n  EnforceFailMessage& operator=(const EnforceFailMessage&) = delete;\n\n  // Catch all wrong usages like CAFFE_ENFORCE_THAT(x < y)\n  template <class... Args>\n  /* implicit */ EnforceFailMessage(Args...) {\n    static_assert(\n        // This stands for an \"impossible\" condition. Plain `false` doesn't\n        // trick compiler enough.\n        sizeof...(Args) == std::numeric_limits<std::size_t>::max(),\n        \"CAFFE_ENFORCE_THAT has to be used with one of special check functions \"\n        \"like `Equals`. Use CAFFE_ENFORCE for simple boolean checks.\");\n  }\n\n  /* implicit */ EnforceFailMessage(std::string&& msg) {\n    msg_ = new std::string(std::move(msg));\n  }\n  inline bool bad() const {\n    return msg_ != nullptr;\n  }\n  std::string get_message_and_free(std::string&& extra) const {\n    std::string r;\n    if (extra.empty()) {\n      r = std::move(*msg_);\n    } else {\n      r = ::caffe2::MakeString(std::move(*msg_), \". \", std::move(extra));\n    }\n    delete msg_;\n    return r;\n  }\n\n private:\n  std::string* msg_;\n};\n\n#define BINARY_COMP_HELPER(name, op)                         \\\n  template <typename T1, typename T2>                        \\\n  inline EnforceFailMessage name(const T1& x, const T2& y) { \\\n    if (x op y) {                                            \\\n      return EnforceOK();                                    \\\n    }                                                        \\\n    return MakeString(x, \" vs \", y);                         \\\n  }\nBINARY_COMP_HELPER(Equals, ==)\nBINARY_COMP_HELPER(NotEquals, !=)\nBINARY_COMP_HELPER(Greater, >)\nBINARY_COMP_HELPER(GreaterEquals, >=)\nBINARY_COMP_HELPER(Less, <)\nBINARY_COMP_HELPER(LessEquals, <=)\n#undef BINARY_COMP_HELPER\n\n#define CAFFE_ENFORCE_THAT_IMPL(condition, expr, ...)                   \\\n  do {                                                                  \\\n    using namespace ::caffe2::enforce_detail;                           \\\n    const EnforceFailMessage& CAFFE_ENFORCE_THAT_IMPL_r_ = (condition); \\\n    if (CAFFE_ENFORCE_THAT_IMPL_r_.bad()) {                             \\\n      throw ::caffe2::EnforceNotMet(                                    \\\n          __FILE__,                                                     \\\n          __LINE__,                                                     \\\n          expr,                                                         \\\n          CAFFE_ENFORCE_THAT_IMPL_r_.get_message_and_free(              \\\n              ::caffe2::MakeString(__VA_ARGS__)));                      \\\n    }                                                                   \\\n  } while (false)\n\n#define CAFFE_ENFORCE_THAT_IMPL_WITH_CALLER(condition, expr, ...)      \\\n  do {                                                                 \\\n    using namespace ::caffe2::enforce_detail;                          \\\n    const EnforceFailMessage& CAFFE_ENFORCE_THAT_IMPL_WITH_CALLER_r_ = \\\n        (condition);                                                   \\\n    if (CAFFE_ENFORCE_THAT_IMPL_WITH_CALLER_r_.bad()) {                \\\n      throw ::caffe2::EnforceNotMet(                                   \\\n          __FILE__,                                                    \\\n          __LINE__,                                                    \\\n          expr,                                                        \\\n          CAFFE_ENFORCE_THAT_IMPL_WITH_CALLER_r_.get_message_and_free( \\\n              ::caffe2::MakeString(__VA_ARGS__)),                      \\\n          this);                                                       \\\n    }                                                                  \\\n  } while (false)\n}\n\n#define CAFFE_ENFORCE_THAT(condition, ...) \\\n  CAFFE_ENFORCE_THAT_IMPL((condition), #condition, __VA_ARGS__)\n\n#define CAFFE_ENFORCE_EQ(x, y, ...) \\\n  CAFFE_ENFORCE_THAT_IMPL(Equals((x), (y)), #x \" == \" #y, __VA_ARGS__)\n#define CAFFE_ENFORCE_NE(x, y, ...) \\\n  CAFFE_ENFORCE_THAT_IMPL(NotEquals((x), (y)), #x \" != \" #y, __VA_ARGS__)\n#define CAFFE_ENFORCE_LE(x, y, ...) \\\n  CAFFE_ENFORCE_THAT_IMPL(LessEquals((x), (y)), #x \" <= \" #y, __VA_ARGS__)\n#define CAFFE_ENFORCE_LT(x, y, ...) \\\n  CAFFE_ENFORCE_THAT_IMPL(Less((x), (y)), #x \" < \" #y, __VA_ARGS__)\n#define CAFFE_ENFORCE_GE(x, y, ...) \\\n  CAFFE_ENFORCE_THAT_IMPL(GreaterEquals((x), (y)), #x \" >= \" #y, __VA_ARGS__)\n#define CAFFE_ENFORCE_GT(x, y, ...) \\\n  CAFFE_ENFORCE_THAT_IMPL(Greater((x), (y)), #x \" > \" #y, __VA_ARGS__)\n#define CAFFE_ENFORCE_EQ_WITH_CALLER(x, y, ...) \\\n  CAFFE_ENFORCE_THAT_IMPL_WITH_CALLER(Equals((x), (y)), #x \" == \" #y, __VA_ARGS__)\n#define CAFFE_ENFORCE_NE_WITH_CALLER(x, y, ...) \\\n  CAFFE_ENFORCE_THAT_IMPL_WITH_CALLER(NotEquals((x), (y)), #x \" != \" #y, __VA_ARGS__)\n#define CAFFE_ENFORCE_LE_WITH_CALLER(x, y, ...) \\\n  CAFFE_ENFORCE_THAT_IMPL_WITH_CALLER(LessEquals((x), (y)), #x \" <= \" #y, __VA_ARGS__)\n#define CAFFE_ENFORCE_LT_WITH_CALLER(x, y, ...) \\\n  CAFFE_ENFORCE_THAT_IMPL_WITH_CALLER(Less((x), (y)), #x \" < \" #y, __VA_ARGS__)\n#define CAFFE_ENFORCE_GE_WITH_CALLER(x, y, ...) \\\n  CAFFE_ENFORCE_THAT_IMPL_WITH_CALLER(GreaterEquals((x), (y)), #x \" >= \" #y, __VA_ARGS__)\n#define CAFFE_ENFORCE_GT_WITH_CALLER(x, y, ...) \\\n  CAFFE_ENFORCE_THAT_IMPL_WITH_CALLER(Greater((x), (y)), #x \" > \" #y, __VA_ARGS__)\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_LOGGING_H_\n"
  },
  {
    "path": "caffe2/core/logging_is_google_glog.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_LOGGING_IS_GOOGLE_GLOG_H_\n#define CAFFE2_CORE_LOGGING_IS_GOOGLE_GLOG_H_\n\n#include <iomanip>  // because some of the caffe2 code uses e.g. std::setw\n// Using google glog. For glog 0.3.2 versions, stl_logging.h needs to be before\n// logging.h to actually use stl_logging. Because template magic.\n// In addition, we do not do stl logging in .cu files because nvcc does not like\n// it. Some mobile platforms do not like stl_logging, so we add an\n// overload in that case as well.\n\n#if !defined(__CUDACC__) && !defined(CAFFE2_USE_MINIMAL_GOOGLE_GLOG)\n#include <glog/stl_logging.h>\n#else // !defined(__CUDACC__) && !!defined(CAFFE2_USE_MINIMAL_GOOGLE_GLOG)\n\n// here, we need to register a fake overload for vector/string - here,\n// we just ignore the entries in the logs.\n\n#define INSTANTIATE_FOR_CONTAINER(container)                                \\\n  template <class... Types>                                                 \\\n  std::ostream& operator<<(std::ostream& out, const container<Types...>&) { \\\n    return out;                                                             \\\n  }\n\nINSTANTIATE_FOR_CONTAINER(std::vector)\nINSTANTIATE_FOR_CONTAINER(std::map)\nINSTANTIATE_FOR_CONTAINER(std::set)\n#undef INSTANTIATE_FOR_CONTAINER\n\n#endif\n\n#include <glog/logging.h>\n\n\n#endif  // CAFFE2_CORE_LOGGING_IS_GOOGLE_GLOG_H_\n"
  },
  {
    "path": "caffe2/core/logging_is_not_google_glog.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_LOGGING_IS_NOT_GOOGLE_GLOG_H_\n#define CAFFE2_CORE_LOGGING_IS_NOT_GOOGLE_GLOG_H_\n\n#include <chrono>\n#include <climits>\n#include <ctime>\n#include <iomanip>\n#include <string>\n#include <fstream>\n#include <set>\n#include <sstream>\n#include <vector>\n\n#include \"caffe2/core/flags.h\"\n\n// Log severity level constants.\nconst int FATAL   = 3;\n#if !defined(_MSC_VER) || !defined(ERROR)\n// Windows defines the ERROR macro already, and as a result we will\n// simply use that one. The downside is that one will now mix LOG(INFO)\n// and LOG(ERROR) because ERROR is defined to be zero. Anyway, the\n// recommended way is to use glog so fixing this is a low-pri item.\nconst int ERROR   = 2;\n#endif\nconst int WARNING = 1;\nconst int INFO    = 0;\nconst char CAFFE2_SEVERITY_PREFIX[] = \"FEWIV\";\n\nnamespace caffe2 {\nclass MessageLogger {\n public:\n  MessageLogger(const char *file, int line, int severity);\n  ~MessageLogger();\n  // Return the stream associated with the logger object.\n  std::stringstream &stream() { return stream_; }\n\n private:\n  // When there is a fatal log, we simply abort.\n  void DealWithFatal() { abort(); }\n\n  const char* tag_;\n  std::stringstream stream_;\n  int severity_;\n};\n\n// This class is used to explicitly ignore values in the conditional\n// logging macros.  This avoids compiler warnings like \"value computed\n// is not used\" and \"statement has no effect\".\nclass LoggerVoidify {\n public:\n  LoggerVoidify() { }\n  // This has to be an operator with a precedence lower than << but\n  // higher than ?:\n  void operator&(const std::ostream &s) { }\n};\n\n// Log a message and terminate.\ntemplate<class T>\nvoid LogMessageFatal(const char *file, int line, const T &message) {\n  MessageLogger(file, line, FATAL).stream() << message;\n}\n\n// Helpers for CHECK_NOTNULL(). Two are necessary to support both raw pointers\n// and smart pointers.\ntemplate <typename T>\nT& CheckNotNullCommon(const char *file, int line, const char *names, T& t) {\n  if (t == nullptr) {\n    LogMessageFatal(file, line, std::string(names));\n  }\n  return t;\n}\n\ntemplate <typename T>\nT* CheckNotNull(const char *file, int line, const char *names, T* t) {\n  return CheckNotNullCommon(file, line, names, t);\n}\n\ntemplate <typename T>\nT& CheckNotNull(const char *file, int line, const char *names, T& t) {\n  return CheckNotNullCommon(file, line, names, t);\n}\n}  // namespace caffe2\n\n// ---------------------- Logging Macro definitions --------------------------\n\n\nstatic_assert(CAFFE2_LOG_THRESHOLD <= FATAL,\n              \"CAFFE2_LOG_THRESHOLD should at most be FATAL.\");\n// If n is under the compile time caffe log threshold, The _CAFFE_LOG(n)\n// should not generate anything in optimized code.\n#define LOG(n) \\\n  if (n >= CAFFE2_LOG_THRESHOLD) \\\n    ::caffe2::MessageLogger((char*)__FILE__, __LINE__, n).stream()\n#define VLOG(n) LOG((-n))\n\n#define LOG_IF(n, condition)                    \\\n  if (n >= CAFFE2_LOG_THRESHOLD && (condition)) \\\n  ::caffe2::MessageLogger((char*)__FILE__, __LINE__, n).stream()\n#define VLOG_IF(n, condition) LOG_IF((-n), (condition))\n\n#define VLOG_IS_ON(verboselevel) (CAFFE2_LOG_THRESHOLD <= -(verboselevel))\n\n// Log only if condition is met.  Otherwise evaluates to void.\n#define FATAL_IF(condition) \\\n  condition ? (void) 0 : ::caffe2::LoggerVoidify() & \\\n      ::caffe2::MessageLogger((char*)__FILE__, __LINE__, FATAL).stream()\n\n// Check for a given boolean condition.\n#define CHECK(condition) FATAL_IF(condition) \\\n        << \"Check failed: \" #condition \" \"\n\n#ifndef NDEBUG\n// Debug only version of CHECK\n#define DCHECK(condition) FATAL_IF(condition) \\\n        << \"Check failed: \" #condition \" \"\n#else\n// Optimized version - generates no code.\n#define DCHECK(condition) if(false) CHECK(condition)\n#endif  // NDEBUG\n\n#define CHECK_OP(val1, val2, op) FATAL_IF((val1 op val2)) \\\n  << \"Check failed: \" #val1 \" \" #op \" \" #val2 \" \"\n\n// Check_op macro definitions\n#define CHECK_EQ(val1, val2) CHECK_OP(val1, val2, ==)\n#define CHECK_NE(val1, val2) CHECK_OP(val1, val2, !=)\n#define CHECK_LE(val1, val2) CHECK_OP(val1, val2, <=)\n#define CHECK_LT(val1, val2) CHECK_OP(val1, val2, <)\n#define CHECK_GE(val1, val2) CHECK_OP(val1, val2, >=)\n#define CHECK_GT(val1, val2) CHECK_OP(val1, val2, >)\n\n#ifndef NDEBUG\n// Debug only versions of CHECK_OP macros.\n#define DCHECK_EQ(val1, val2) CHECK_OP(val1, val2, ==)\n#define DCHECK_NE(val1, val2) CHECK_OP(val1, val2, !=)\n#define DCHECK_LE(val1, val2) CHECK_OP(val1, val2, <=)\n#define DCHECK_LT(val1, val2) CHECK_OP(val1, val2, <)\n#define DCHECK_GE(val1, val2) CHECK_OP(val1, val2, >=)\n#define DCHECK_GT(val1, val2) CHECK_OP(val1, val2, >)\n#else  // !NDEBUG\n// These versions generate no code in optimized mode.\n#define DCHECK_EQ(val1, val2) if(false) CHECK_OP(val1, val2, ==)\n#define DCHECK_NE(val1, val2) if(false) CHECK_OP(val1, val2, !=)\n#define DCHECK_LE(val1, val2) if(false) CHECK_OP(val1, val2, <=)\n#define DCHECK_LT(val1, val2) if(false) CHECK_OP(val1, val2, <)\n#define DCHECK_GE(val1, val2) if(false) CHECK_OP(val1, val2, >=)\n#define DCHECK_GT(val1, val2) if(false) CHECK_OP(val1, val2, >)\n#endif  // NDEBUG\n\n// Check that a pointer is not null.\n#define CHECK_NOTNULL(val) \\\n  ::caffe2::CheckNotNull( \\\n      __FILE__, __LINE__, \"Check failed: '\" #val \"' Must be non NULL\", (val))\n\n#ifndef NDEBUG\n// Debug only version of CHECK_NOTNULL\n#define DCHECK_NOTNULL(val) \\\n  ::caffe2::CheckNotNull( \\\n      __FILE__, __LINE__, \"Check failed: '\" #val \"' Must be non NULL\", (val))\n#else  // !NDEBUG\n// Optimized version - generates no code.\n#define DCHECK_NOTNULL(val) if (false) CHECK_NOTNULL(val)\n#endif  // NDEBUG\n\n// ---------------------- Support for std objects --------------------------\n// These are adapted from glog to support a limited set of logging capability\n// for STL objects.\n\nnamespace caffe2 {\n// Forward declare these two, and define them after all the container streams\n// operators so that we can recurse from pair -> container -> container -> pair\n// properly.\ntemplate<class First, class Second>\nstd::ostream& operator<<(\n    std::ostream& out, const std::pair<First, Second>& p);\ntemplate <class Iter>\nvoid PrintSequence(std::ostream& ss, Iter begin, Iter end);\n\n#define INSTANTIATE_FOR_CONTAINER(container) \\\ntemplate <class... Types> \\\nstd::ostream& operator<<( \\\n    std::ostream& out, const container<Types...>& seq) { \\\n  PrintSequence(out, seq.begin(), seq.end()); \\\n  return out; \\\n}\n\nINSTANTIATE_FOR_CONTAINER(std::vector)\nINSTANTIATE_FOR_CONTAINER(std::map)\nINSTANTIATE_FOR_CONTAINER(std::set)\n#undef INSTANTIATE_FOR_CONTAINER\n\ntemplate<class First, class Second>\ninline std::ostream& operator<<(\n    std::ostream& out, const std::pair<First, Second>& p) {\n  out << '(' << p.first << \", \" << p.second << ')';\n  return out;\n}\n\ntemplate <class Iter>\ninline void PrintSequence(std::ostream& out, Iter begin, Iter end) {\n  // Output at most 100 elements -- appropriate if used for logging.\n  for (int i = 0; begin != end && i < 100; ++i, ++begin) {\n    if (i > 0) out << ' ';\n    out << *begin;\n  }\n  if (begin != end) {\n    out << \" ...\";\n  }\n}\n}  // namespace caffe2\n\n#endif  // CAFFE2_CORE_LOGGING_IS_NOT_GOOGLE_GLOG_H_\n"
  },
  {
    "path": "caffe2/core/logging_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <algorithm>\n\n#include \"caffe2/core/logging.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\n\nTEST(LoggingTest, TestEnforceTrue) {\n  // This should just work.\n  CAFFE_ENFORCE(true, \"Isn't it?\");\n}\n\nTEST(LoggingTest, TestEnforceFalse) {\n  bool kFalse = false;\n  std::swap(FLAGS_caffe2_use_fatal_for_enforce, kFalse);\n  try {\n    CAFFE_ENFORCE(false, \"This throws.\");\n    // This should never be triggered.\n    ADD_FAILURE();\n  } catch (const EnforceNotMet&) {\n  }\n  std::swap(FLAGS_caffe2_use_fatal_for_enforce, kFalse);\n}\n\nTEST(LoggingTest, TestEnforceEquals) {\n  int x = 4;\n  int y = 5;\n  try {\n    CAFFE_ENFORCE_THAT(Equals(++x, ++y));\n    // This should never be triggered.\n    ADD_FAILURE();\n  } catch (const EnforceNotMet& err) {\n    EXPECT_NE(err.msg().find(\"5 vs 6\"), string::npos);\n  }\n\n  // arguments are expanded only once\n  CAFFE_ENFORCE_THAT(Equals(++x, y));\n  EXPECT_EQ(x, 6);\n  EXPECT_EQ(y, 6);\n}\n\nTEST(LoggingTest, EnforceShowcase) {\n  // It's not really a test but rather a convenient thing that you can run and\n  // see all messages\n  int one = 1;\n  int two = 2;\n  int three = 3;\n#define WRAP_AND_PRINT(exp)                     \\\n  try {                                         \\\n    exp;                                        \\\n  } catch (const EnforceNotMet&) {              \\\n    /* EnforceNotMet already does LOG(ERROR) */ \\\n  }\n  WRAP_AND_PRINT(CAFFE_ENFORCE_EQ(one, two));\n  WRAP_AND_PRINT(CAFFE_ENFORCE_NE(one * 2, two));\n  WRAP_AND_PRINT(CAFFE_ENFORCE_GT(one, two));\n  WRAP_AND_PRINT(CAFFE_ENFORCE_GE(one, two));\n  WRAP_AND_PRINT(CAFFE_ENFORCE_LT(three, two));\n  WRAP_AND_PRINT(CAFFE_ENFORCE_LE(three, two));\n\n  WRAP_AND_PRINT(CAFFE_ENFORCE_EQ(\n      one * two + three, three * two, \"It's a pretty complicated expression\"));\n\n  WRAP_AND_PRINT(CAFFE_ENFORCE_THAT(Equals(one * two + three, three * two)));\n}\n\nTEST(LoggingTest, Join) {\n  auto s = Join(\", \", vector<int>({1, 2, 3}));\n  EXPECT_EQ(s, \"1, 2, 3\");\n  s = Join(\":\", vector<string>());\n  EXPECT_EQ(s, \"\");\n  s = Join(\", \", set<int>({3, 1, 2}));\n  EXPECT_EQ(s, \"1, 2, 3\");\n}\n\n#if GTEST_HAS_DEATH_TEST\nTEST(LoggingDeathTest, TestEnforceUsingFatal) {\n  bool kTrue = true;\n  std::swap(FLAGS_caffe2_use_fatal_for_enforce, kTrue);\n  EXPECT_DEATH(CAFFE_ENFORCE(false, \"This goes fatal.\"), \"\");\n  std::swap(FLAGS_caffe2_use_fatal_for_enforce, kTrue);\n}\n#endif\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/macros.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n// This is a placeholder. The real content will be generated by the cmake\n// script.\n"
  },
  {
    "path": "caffe2/core/macros.h.in",
    "content": "// Automatically generated header file for caffe2 macros. These\n// macros are used to build the Caffe2 binary, and if you are\n// building a dependent library, they will need to be set as well\n// for your program to link correctly.\n\n#pragma once\n\n// Caffe2 version. The plan is to increment the minor version every other week\n// as a track point for bugs, until we find a proper versioning cycle.\n\n#define CAFFE2_VERSION_MAJOR @CAFFE2_VERSION_MAJOR@\n#define CAFFE2_VERSION_MINOR @CAFFE2_VERSION_MINOR@\n#define CAFFE2_VERSION_PATCH @CAFFE2_VERSION_PATCH@\n#define CAFFE2_GIT_VERSION \"@CAFFE2_GIT_VERSION@\"\n\nstatic_assert(\n    CAFFE2_VERSION_MINOR < 100,\n    \"Programming error: you set a minor version that is too big.\");\nstatic_assert(\n    CAFFE2_VERSION_PATCH < 100,\n    \"Programming error: you set a patch version that is too big.\");\n\n#define CAFFE2_VERSION                                         \\\n  (CAFFE2_VERSION_MAJOR * 10000 + CAFFE2_VERSION_MINOR * 100 + \\\n   CAFFE2_VERSION_PATCH)\n\n#cmakedefine CAFFE2_ANDROID\n#cmakedefine CAFFE2_BUILD_SHARED_LIBS\n#cmakedefine CAFFE2_FORCE_FALLBACK_CUDA_MPI\n#cmakedefine CAFFE2_HAS_MKL_DNN\n#cmakedefine CAFFE2_HAS_MKL_SGEMM_PACK\n#cmakedefine CAFFE2_PERF_WITH_AVX\n#cmakedefine CAFFE2_PERF_WITH_AVX2\n#cmakedefine CAFFE2_THREADPOOL_MAIN_IMBALANCE\n#cmakedefine CAFFE2_THREADPOOL_STATS\n#cmakedefine CAFFE2_UNIQUE_LONG_TYPEMETA\n#cmakedefine CAFFE2_USE_EXCEPTION_PTR\n#cmakedefine CAFFE2_USE_ACCELERATE\n#cmakedefine CAFFE2_USE_EIGEN_FOR_BLAS\n#cmakedefine CAFFE2_USE_FBCODE\n#cmakedefine CAFFE2_USE_GFLAGS\n#cmakedefine CAFFE2_USE_GOOGLE_GLOG\n#cmakedefine CAFFE2_USE_LITE_PROTO\n#cmakedefine CAFFE2_USE_MKL\n#cmakedefine CAFFE2_USE_NVTX\n#cmakedefine CAFFE2_DISABLE_NUMA\n\n#ifndef EIGEN_MPL2_ONLY\n#cmakedefine EIGEN_MPL2_ONLY\n#endif\n\n// Useful build settings that are recorded in the compiled binary\n#define CAFFE2_BUILD_STRINGS { \\\n  {\"GIT_VERSION\", \"${CAFFE2_GIT_VERSION}\"}, \\\n  {\"CXX_FLAGS\", \"${CMAKE_CXX_FLAGS}\"}, \\\n  {\"BUILD_TYPE\", \"${CMAKE_BUILD_TYPE}\"}, \\\n  {\"BLAS\", \"${BLAS}\"}, \\\n  {\"USE_ATEN\", \"${USE_ATEN}\"}, \\\n  {\"USE_CUDA\", \"${USE_CUDA}\"}, \\\n  {\"USE_NCCL\", \"${USE_NCCL}\"}, \\\n  {\"USE_MPI\", \"${USE_MPI}\"}, \\\n  {\"USE_GFLAGS\", \"${USE_GFLAGS}\"}, \\\n  {\"USE_GLOG\", \"${USE_GLOG}\"}, \\\n  {\"USE_GLOO\", \"${USE_GLOI}\"}, \\\n  {\"USE_NNPACK\", \"${USE_NNPACK}\"}, \\\n  {\"USE_OPENMP\", \"${USE_OPENMP}\"}, \\\n  {\"FORCE_FALLBACK_CUDA_MPI\", \"${CAFFE2_FORCE_FALLBACK_CUDA_MPI}\"}, \\\n  {\"HAS_MKL_DNN\", \"${CAFFE2_HAS_MKL_DNN}\"}, \\\n  {\"HAS_MKL_SGEMM_PACK\", \"${CAFFE2_HAS_MKL_SGEMM_PACK}\"}, \\\n  {\"PERF_WITH_AVX\", \"${CAFFE2_PERF_WITH_AVX}\"}, \\\n  {\"PERF_WITH_AVX2\", \"${CAFFE2_PERF_WITH_AVX2}\"}, \\\n  {\"UNIQUE_LONG_TYPEMETA\", \"${CAFFE2_UNIQUE_LONG_TYPEMETA}\"}, \\\n  {\"USE_EXCEPTION_PTR\", \"${CAFFE2_USE_EXCEPTION_PTR}\"}, \\\n  {\"USE_ACCELERATE\", \"${CAFFE2_USE_ACCELERATE}\"}, \\\n  {\"USE_EIGEN_FOR_BLAS\", \"${CAFFE2_USE_EIGEN_FOR_BLAS}\"}, \\\n  {\"USE_LITE_PROTO\", \"${CAFFE2_USE_LITE_PROTO}\"}, \\\n  {\"USE_MKL\", \"${CAFFE2_USE_MKL}\"}, \\\n  {\"USE_NVTX\", \"${CAFFE2_USE_NVTX}\"}, \\\n  {\"DISABLE_NUMA\", \"${CAFFE2_DISABLE_NUMA}\"}, \\\n}\n"
  },
  {
    "path": "caffe2/core/memonger.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/memonger.h\"\n\n#include <set>\n#include <unordered_set>\n\n#include \"caffe2/utils/proto_utils.h\"\n#include \"google/protobuf/text_format.h\"\n\nnamespace caffe2 {\nnamespace memonger {\n\nNetDef optimize_inference_net(\n    const NetDef& net,\n    const std::set<string>& static_blobs) {\n  if (net.type() != \"\" && net.type() != \"simple\") {\n    LOG(INFO) << \"Cannot optimize memory for nets of type: \" << net.type();\n    return net;\n  }\n\n  std::vector<OperatorDef> ops;\n  for (auto& op : net.op()) {\n    if (op.type() == \"RecurrentNetwork\") {\n      // NOTE: for subtleties of RNN op memonger, see memonger.py on how\n      // to deal with the forward/backward links etc.\n      LOG(INFO) << \"Memonger does not support RecurrentNetwork yet\";\n      return net;\n    }\n    ops.push_back(op);\n  }\n\n  // Step 1: count first and last operator for each blob\n  std::unordered_set<std::string> all_blobs;\n  std::unordered_map<std::string, std::pair<int, int>> ranges;\n  for (int i = 0; i < ops.size(); i++) {\n    for (auto& inp : ops[i].input()) {\n      if (ranges.find(inp) != ranges.end()) {\n        ranges[inp].second = i;\n      }\n      all_blobs.insert(inp);\n    }\n    for (auto& outp : ops[i].output()) {\n      all_blobs.insert(outp);\n      if (static_blobs.find(outp) != static_blobs.end()) {\n        continue;\n      }\n      if (ranges.find(outp) == ranges.end()) {\n        ranges[outp] = std::make_pair(i, i);\n      }\n    }\n  }\n\n  // Step 2: pass over ops and recycle\n  std::vector<std::string> free_blobs;\n  std::unordered_map<std::string, std::string> renaming;\n  std::unordered_map<std::string, std::string> mapping;\n\n  for (int i = 0; i < ops.size(); i++) {\n    auto& op = ops[i];\n    std::unordered_set<std::string> new_free_blobs;\n\n    // Check if some input is used the last time, and release it\n    for (auto& inp : op.input()) {\n      auto rit = ranges.find(inp);\n      if (rit != ranges.end() && rit->second.second == i) {\n        if (mapping.find(inp) == mapping.end()) {\n          new_free_blobs.insert(inp);\n          mapping[inp] = inp;\n\n          // Safety check to prevent double-memongering nets.\n          string shared_blob =\n              \"__m\" + caffe2::to_string(renaming.size()) + \"_shared\";\n          if (all_blobs.find(shared_blob) != all_blobs.end()) {\n            LOG(INFO) << \"Net was already memongered!\";\n            return net;\n          }\n          renaming[inp] = shared_blob;\n        } else {\n          new_free_blobs.insert(mapping[inp]);\n        }\n      }\n    }\n\n    // Check if some output appears the first time, and see if we can replace it\n    // with a recycled blob.\n    for (auto& outp : op.output()) {\n      if (!free_blobs.empty()) {\n        // first use?\n        auto rit = ranges.find(outp);\n        if (rit != ranges.end() && rit->second.first == i) {\n          std::string recycled = free_blobs.back();\n          free_blobs.pop_back();\n          mapping[outp] = recycled;\n        }\n      }\n    }\n\n    // Add blobs released from this op to the pool.\n    for (auto& b : new_free_blobs) {\n      free_blobs.push_back(b);\n    }\n  }\n\n  // Step 3: rename inputs and outputs and create new net\n  NetDef optim_net = net;\n  optim_net.mutable_op()->Clear();\n  for (auto op : ops) {\n    for (int i = 0; i < op.input_size(); i++) {\n      auto& inp = op.input(i);\n      if (mapping.find(inp) != mapping.end()) {\n        op.set_input(i, renaming[mapping[inp]]);\n      }\n    }\n    for (int i = 0; i < op.output_size(); i++) {\n      auto& outp = op.output(i);\n      if (mapping.find(outp) != mapping.end()) {\n        op.set_output(i, renaming[mapping[outp]]);\n      }\n    }\n    auto* ao = optim_net.add_op();\n    ao->CopyFrom(op);\n  }\n\n  LOG(INFO) << \"optimized net using \" << renaming.size() << \" shared blobs\";\n  return optim_net;\n}\n\nclass ComputeBlobRecyclingForDag {\n public:\n  explicit ComputeBlobRecyclingForDag(const int size)\n      : op_inputs_(size),\n        op_visited_count_(size),\n        op_token_deposit_(size),\n        op_visited_(size, false) {}\n  NetDef OptimizeNet(\n      const NetDef& net,\n      const std::vector<string>& heads,\n      const std::vector<int>& op_indices,\n      const std::unordered_set<string>& shareable_blob_names,\n      const string& namescope,\n      const std::unordered_set<string>& dont_share_blob_names,\n      const std::unordered_map<string, vector<int>>& blob_shapes) {\n    // Construct the set of input blobs.\n    std::unordered_set<string> heads_blobs_set(heads.begin(), heads.end());\n\n    // Construct the set of output blobs we want to optimize.\n    for (const int op_index : op_indices) {\n      for (const auto& output : net.op(op_index).output()) {\n        optim_op_outputs_.insert(output);\n      }\n    }\n\n    // Compute operators in degree (op_inputs_) and initialize how many ops are\n    // sharing input blobs (share_counts_).\n    // Note: We have to handle the cases where output blobs are shared.\n    std::unordered_map<string, int> blob_seen;\n    for (const int op_index : op_indices) {\n      for (const auto& input : net.op(op_index).input()) {\n        if (has_key(shareable_blob_names, input) ||\n            has_key(heads_blobs_set, input)) {\n          if (has_key(optim_op_outputs_, input)) {\n            CAFFE_ENFORCE(\n                blob_seen.find(input) != blob_seen.end(),\n                \"Input \",\n                input,\n                \" was not output by an op before\");\n            op_inputs_[op_index] += blob_seen[input];\n          } else {\n            share_counts_[input] = 1;\n          }\n          blob_to_ops_[input].push_back(op_index);\n        }\n      }\n      for (const auto& output : net.op(op_index).output()) {\n        blob_seen[output] += 1;\n        blob_device_[output] = net.op(op_index).device_option();\n        // Exception for CopyGPUToCPU that has\n        // cuda device option but whose inputs/outputs are on CPU\n        if (net.op(op_index).type() == \"CopyGPUToCPU\") {\n          blob_device_[output].set_device_type(0);\n          blob_device_[output].set_cuda_gpu_id(0);\n        }\n      }\n    }\n\n    // The main recursive call. Here we do start DFS in the operator graph\n    // from the input blobs.\n    for (const auto& input_blob : heads) {\n      for (const int op_index : blob_to_ops_[input_blob]) {\n        if (!op_visited_[op_index]) {\n          vector<std::pair<int, string>> free_blobs;\n          std::unordered_set<int> tokens{tokens_counter_++};\n          process_op(\n              net,\n              shareable_blob_names,\n              namescope,\n              dont_share_blob_names,\n              blob_shapes,\n              op_index,\n              &free_blobs,\n              &tokens);\n        }\n      }\n    }\n\n    // Rename mapped blobs.\n    std::unordered_map<string, string> renamed;\n    int name_idx = 0;\n    std::unordered_set<string> mapped_blobs_set;\n    for (const auto& mapped_blob : mapping_) {\n      mapped_blobs_set.insert(mapped_blob.second);\n      if (has_key(optim_op_outputs_, mapped_blob.second)) {\n        if (renamed.find(mapped_blob.second) == renamed.end()) {\n          renamed.insert(\n              {mapped_blob.second,\n               namescope + \"__m\" + caffe2::to_string(name_idx++) + \"_shared\"});\n        }\n      } else {\n        renamed.insert({mapped_blob.second, mapped_blob.second});\n      }\n    }\n\n    // Recursively rename mapped_blobs.\n    mapping_.insert(renamed.begin(), renamed.end());\n    bool had_changes = true;\n    while (had_changes) {\n      had_changes = false;\n      for (const auto mapped_blob : mapping_) {\n        if (has_key(renamed, mapped_blob.second) &&\n            renamed[mapped_blob.second] != mapped_blob.second) {\n          renamed[mapped_blob.first] = renamed[mapped_blob.second];\n          mapping_[mapped_blob.first] = renamed[mapped_blob.first];\n        }\n      }\n    }\n\n    NetDef optimized_net = apply_assignments(net);\n    LOG(INFO) << \"Remapping \" << mapping_.size() << \" using \"\n              << mapped_blobs_set.size() << \" shared blobs.\";\n    if (floats_saved_ > 0) {\n      LOG(INFO) << \"Memonger saved approximately : \"\n                << (floats_saved_ * 4.0 / 1024.0 / 1024.0) << \" MB.\";\n    }\n\n    return optimized_net;\n  }\n\n private:\n  NetDef apply_assignments(const NetDef& net) {\n    NetDef optimized_net = net;\n    // Rename optimized_net blobs.\n    for (int i = 0; i < optimized_net.op_size(); ++i) {\n      // Special handling for RNNs, which have internal nets that\n      // can refer to memongered blobs\n      if (optimized_net.op(i).type().find(\"RecurrentNetwork\") == 0) {\n        apply_recurrent_blob_assignments(optimized_net.mutable_op(i));\n      }\n\n      for (int j = 0; j < optimized_net.op(i).input_size(); ++j) {\n        const string& input_name =\n            get_blob_or_mapped_blob(optimized_net.op(i).input(j));\n        optimized_net.mutable_op(i)->set_input(j, input_name);\n      }\n\n      for (int j = 0; j < optimized_net.op(i).output_size(); ++j) {\n        auto output_name =\n            get_blob_or_mapped_blob(optimized_net.op(i).output(j));\n        optimized_net.mutable_op(i)->set_output(j, output_name);\n      }\n    }\n    return optimized_net;\n  }\n\n  void apply_recurrent_blob_assignments(OperatorDef* op) {\n    // Recursively map stepnets in RecurrentNetworks, and\n    // attach a mapping table\n    for (int i = 0; i < op->arg_size(); i++) {\n      Argument* arg = op->mutable_arg(i);\n      const string& name = arg->name();\n      if (name == \"step_net\" || name == \"backward_step_net\") {\n        if (arg->has_n()) {\n          NetDef* step_net_ref = arg->mutable_n();\n          CAFFE_ENFORCE(\n              !arg->has_s(),\n              \"Invalid definition for \",\n              name,\n              \". Only one of NetDef and string should be present\");\n          NetDef optimized_net = apply_assignments(*step_net_ref);\n          step_net_ref->CopyFrom(optimized_net);\n        } else {\n          NetDef step_net;\n          CAFFE_ENFORCE(\n              google::protobuf::TextFormat::ParseFromString(\n                  arg->s(), &step_net),\n              \"Could not parse step net:\",\n              name);\n          step_net = apply_assignments(step_net);\n          arg->set_s(ProtoDebugString(step_net));\n        }\n      }\n    }\n\n    // Store renamings\n    vector<string> inputs_outputs(op->input().begin(), op->input().end());\n    inputs_outputs.insert(\n        inputs_outputs.end(), op->output().begin(), op->output().end());\n\n    for (auto& b : inputs_outputs) {\n      string mapped = get_blob_or_mapped_blob(b);\n      if (b != mapped) {\n        Argument* map_arg = op->add_arg();\n        map_arg->set_name(b + \".rename\");\n        map_arg->set_s(mapped);\n      }\n    }\n  }\n\n  template <typename K, typename V>\n  inline bool has_key(const std::unordered_map<K, V>& in_map, const K& key) {\n    return in_map.find(key) != in_map.end();\n  }\n\n  template <typename K>\n  inline bool has_key(const std::unordered_set<K>& in_set, const K& key) {\n    return in_set.find(key) != in_set.end();\n  }\n\n  void process_op(\n      const NetDef& net,\n      const std::unordered_set<string>& shareable_blob_names,\n      const string& namescope,\n      const std::unordered_set<string>& dont_share_blob_names,\n      const std::unordered_map<string, vector<int>>& blob_shapes,\n      int op_index,\n      std::vector<std::pair<int, string>>* free_blobs,\n      std::unordered_set<int>* tokens) {\n    // The tokens we have now is the union of current tokens operator is holding\n    // and tokens pushed from parents.\n    tokens->insert(\n        op_token_deposit_[op_index].begin(), op_token_deposit_[op_index].end());\n    op_token_deposit_[op_index].clear();\n    CAFFE_ENFORCE(!op_visited_[op_index]);\n    op_visited_[op_index] = true;\n\n    const OperatorDef& current_op = net.op(op_index);\n\n    // The set of freed input blobs by processing current op.\n    std::vector<std::pair<int, string>> new_free_blobs;\n    std::unordered_set<string> new_free_blobs_set;\n\n    // Now update blob tokens.\n    for (const auto& input : current_op.input()) {\n      const auto& actual_blob = get_blob_or_mapped_blob(input);\n      req_tokens_[actual_blob].insert(tokens->begin(), tokens->end());\n      if (actual_blob != input) {\n        req_tokens_[input].insert(tokens->begin(), tokens->end());\n      }\n    }\n    for (const auto& output : current_op.output()) {\n      const auto& actual_blob = get_blob_or_mapped_blob(output);\n      req_tokens_[actual_blob].insert(tokens->begin(), tokens->end());\n      if (actual_blob != output) {\n        req_tokens_[output].insert(tokens->begin(), tokens->end());\n      }\n    }\n\n    // Increment blob count and check if we can free input blobs.\n    for (const auto& input : current_op.input()) {\n      if (has_key(shareable_blob_names, input)) {\n        blob_input_count_[input]++;\n        if (blob_input_count_[input] == blob_to_ops_[input].size()) {\n          const string& actual_blob = get_blob_or_mapped_blob(input);\n          if (!has_key(dont_share_blob_names, actual_blob)) {\n            new_free_blobs.emplace_back(\n                -share_counts_[actual_blob], actual_blob);\n            new_free_blobs_set.insert(actual_blob);\n          }\n        }\n      }\n    }\n\n    // Check if we can recycle free blobs and use it as output blob.\n    for (const auto& output : current_op.output()) {\n      if (has_key(shareable_blob_names, output) &&\n          !has_key(processed_output_blobs_, output) &&\n          !has_key(new_free_blobs_set, output)) {\n        const string freed_blob = get_free_blob(\n            output, blob_shapes, tokens, free_blobs, blob_device_[output]);\n        if (freed_blob != \"\") {\n          req_tokens_[freed_blob].insert(tokens->begin(), tokens->end());\n          share_counts_[freed_blob]++;\n          mapping_[output] = freed_blob;\n        }\n        processed_output_blobs_.insert(output);\n      }\n    }\n\n    // Insert new freed blobs.\n    std::unordered_set<string> free_blob_set;\n    for (const auto& free_blob : *free_blobs) {\n      free_blob_set.insert(free_blob.second);\n    }\n    for (const auto& new_free_blob : new_free_blobs) {\n      if (!has_key(free_blob_set, new_free_blob.second)) {\n        free_blobs->push_back(new_free_blob);\n        if (blob_shapes.size() > 0) {\n          if (!has_key(blob_sizes_, new_free_blob.second)) {\n            blob_sizes_.insert(\n                {new_free_blob.second,\n                 infer_blob_size(new_free_blob.second, blob_shapes)});\n          }\n        }\n        std::push_heap(\n            free_blobs->begin(),\n            free_blobs->end(),\n            std::greater<std::pair<int, string>>());\n      }\n    }\n\n    int num_branches = 0;\n    for (const auto& output : current_op.output()) {\n      num_branches += blob_to_ops_[output].size();\n    }\n\n    for (const auto& output : current_op.output()) {\n      for (const auto& input_op_index : blob_to_ops_[output]) {\n        op_visited_count_[input_op_index]++;\n        if (op_visited_count_[input_op_index] == op_inputs_[input_op_index]) {\n          std::unordered_set<int> new_tokens;\n          new_tokens.insert(tokens->begin(), tokens->end());\n          if (num_branches > 1) {\n            new_tokens.insert(tokens_counter_++);\n          }\n          process_op(\n              net,\n              shareable_blob_names,\n              namescope,\n              dont_share_blob_names,\n              blob_shapes,\n              input_op_index,\n              free_blobs,\n              &new_tokens);\n        } else {\n          if (!op_visited_[input_op_index]) {\n            op_token_deposit_[input_op_index].insert(\n                tokens->begin(), tokens->end());\n          }\n        }\n      }\n    }\n  }\n\n  inline int infer_blob_size(\n      const string& blob_name,\n      const std::unordered_map<string, vector<int>>& blob_shapes) {\n    const auto& blob_shapes_iter = blob_shapes.find(blob_name);\n    if (blob_shapes_iter == blob_shapes.end()) {\n      return 0;\n    }\n    int size = 1;\n    for (int i = 0; i < blob_shapes_iter->second.size(); ++i) {\n      size *= blob_shapes_iter->second[i];\n    }\n    return size;\n  }\n\n  inline string get_blob_or_mapped_blob(const string& blob_name) {\n    auto mapped_blob = mapping_.find(blob_name);\n    if (mapped_blob == mapping_.end()) {\n      return blob_name;\n    } else {\n      return mapped_blob->second;\n    }\n  }\n\n  // Rturns true if the op that generates that blob acquires all tokens.\n  inline bool can_use_blob(\n      const string& blob_name,\n      std::unordered_set<int>* tokens,\n      const DeviceOption& device_option) {\n    const DeviceOption& blob_device = blob_device_[blob_name];\n    if (device_option.device_type() != blob_device.device_type() ||\n        device_option.cuda_gpu_id() != blob_device.cuda_gpu_id()) {\n      return false;\n    }\n    for (const int token : req_tokens_[blob_name]) {\n      if (tokens->find(token) == tokens->end()) {\n        return false;\n      }\n    }\n    return true;\n  };\n\n  // Returns the name of the blob that we are going to map blob_name into.\n  inline string get_free_blob(\n      const string& blob_name,\n      const std::unordered_map<string, vector<int>>& blob_shapes,\n      std::unordered_set<int>* tokens,\n      std::vector<std::pair<int, string>>* free_blobs,\n      const DeviceOption& device) {\n    string freed_blob = \"\";\n    if (blob_shapes.size() == 0) {\n      std::vector<std::pair<int, string>> cant_use_blobs;\n      while (free_blobs->size() > 0) {\n        std::pop_heap(\n            free_blobs->begin(),\n            free_blobs->end(),\n            std::greater<std::pair<int, string>>());\n        const auto cand_free_blob = free_blobs->back();\n        free_blobs->pop_back();\n        if (can_use_blob(cand_free_blob.second, tokens, device)) {\n          freed_blob = cand_free_blob.second;\n          break;\n        } else {\n          cant_use_blobs.push_back(cand_free_blob);\n        }\n      }\n      for (const auto& cant_use_blob : cant_use_blobs) {\n        free_blobs->push_back(cant_use_blob);\n        std::push_heap(\n            free_blobs->begin(),\n            free_blobs->end(),\n            std::greater<std::pair<int, string>>());\n      }\n    } else {\n      // Heuristic to choose the largest blob to fit output thats\n      // slightly less than blob_size.\n      const int blob_size = infer_blob_size(blob_name, blob_shapes);\n      int best_size = -1;\n      int free_blob_index = -1;\n      for (int i = 0; i < free_blobs->size(); ++i) {\n        const string& cb_name = (*free_blobs)[i].second;\n        if (can_use_blob(cb_name, tokens, device)) {\n          const int cand_bz = blob_sizes_[cb_name];\n          CAFFE_ENFORCE(blob_sizes_.find(cb_name) != blob_sizes_.end());\n          if (cand_bz >= best_size) {\n            if (best_size < blob_size || best_size >= cand_bz) {\n              best_size = cand_bz;\n              free_blob_index = i;\n            }\n          }\n        }\n      }\n      if (free_blob_index != -1) {\n        floats_saved_ += best_size;\n        freed_blob = (*free_blobs)[free_blob_index].second;\n        free_blobs->erase(free_blobs->begin() + free_blob_index);\n      }\n    }\n    return freed_blob;\n  };\n\n  int tokens_counter_ = 1;\n  int floats_saved_ = 0;\n  // blob_name -> Op edges.\n  std::unordered_map<string, std::vector<int>> blob_to_ops_;\n  // Current Op in degree.\n  std::unordered_map<string, int> blob_input_count_;\n  // Op in degree.\n  std::vector<int> op_inputs_;\n  // Current Op visit counts.\n  std::vector<int> op_visited_count_;\n  std::unordered_map<string, int> share_counts_;\n  std::unordered_map<string, int> blob_sizes_;\n  std::unordered_map<string, std::unordered_set<int>> req_tokens_;\n  std::vector<std::unordered_set<int>> op_token_deposit_;\n  std::unordered_set<string> optim_op_outputs_;\n  std::unordered_map<string, string> mapping_;\n  std::unordered_map<string, DeviceOption> blob_device_;\n  // The set of output blobs we already processed.\n  std::unordered_set<string> processed_output_blobs_;\n  std::vector<bool> op_visited_;\n};\n\nNetDef compute_blob_recycling_for_dag(\n    const NetDef& net,\n    const std::vector<string>& heads,\n    const std::vector<int>& op_indices,\n    const std::unordered_set<string>& shareable_blob_names,\n    const string& namescope,\n    const std::unordered_set<string>& dont_share_blob_names,\n    const std::unordered_map<string, vector<int>>& blob_shapes) {\n  ComputeBlobRecyclingForDag memonger(net.op_size());\n  return memonger.OptimizeNet(\n      net,\n      heads,\n      op_indices,\n      shareable_blob_names,\n      namescope,\n      dont_share_blob_names,\n      blob_shapes);\n}\n\n} // memonger\n} // caffe2\n"
  },
  {
    "path": "caffe2/core/memonger.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_MEMONGER_H_\n#define CAFFE2_CORE_MEMONGER_H_\n\n#include <unordered_set>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\nnamespace memonger {\n\nNetDef optimize_inference_net(\n    const NetDef& net,\n    const std::set<string>& static_blobs);\n\nNetDef compute_blob_recycling_for_dag(\n    const NetDef& net,\n    const std::vector<string>& heads,\n    const std::vector<int>& op_indices,\n    const std::unordered_set<string>& shareable_blob_names,\n    const string& namescope,\n    const std::unordered_set<string>& dont_share_blob_names,\n    const std::unordered_map<string, vector<int>>& blob_shapes);\n\n} // memonger\n} // caffe2\n\n#endif\n"
  },
  {
    "path": "caffe2/core/module.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/module.h\"\n\n#ifndef _MSC_VER\n#include <dlfcn.h>\n#endif\n\nnamespace caffe2 {\n\nstatic std::mutex& gModuleChangeMutex() {\n  static std::mutex m_;\n  return m_;\n}\n\nstatic CaffeMap<string, const ModuleSchema*>& MutableCurrentModules() {\n  static CaffeMap<string, const ModuleSchema*> module_schema_map_;\n  return module_schema_map_;\n}\n\n// Note(jiayq): I am not sure whether the module handles are going to be used\n// as C2 uses modules via registration, but let's keep the handles at least.\nstatic CaffeMap<string, void*> CurrentModuleHandles() {\n  static CaffeMap<string, void*> module_handle_map_;\n  return module_handle_map_;\n}\n\nconst CaffeMap<string, const ModuleSchema*>& CurrentModules() {\n  return MutableCurrentModules();\n}\n\nModuleSchema::ModuleSchema(const char* name, const char* description)\n    : name_(name), description_(description) {\n  std::lock_guard<std::mutex> guard(gModuleChangeMutex());\n  MutableCurrentModules().emplace(name, this);\n}\n\nbool HasModule(const string& name) {\n auto& modules = CurrentModules();\n return (modules.find(name) != modules.end());\n}\n\n#ifdef _MSC_VER\n\nvoid LoadModule(const string& name, const string& filename) {\n  CAFFE_ENFORCE(!HasModule(name),\n    \"On Windows, LoadModule is currently not supported yet and you should \"\n    \"use static linking for any module that you intend to use.\");\n}\n\n#else\n\nvoid LoadModule(const string& name, const string& filename) {\n  CAFFE_ENFORCE(\n      name.size() > 0 || filename.size() > 0,\n      \"You must provide at least one of name and filename.\");\n  if (name.size() && HasModule(name)) {\n    VLOG(1) << \"Module \" << name << \" already present. Skip loading.\"; \n    return;\n  }\n  void* handle = nullptr;\n  if (filename.size()) {\n    handle = dlopen(\n        filename.c_str(), RTLD_NOW | RTLD_GLOBAL);\n    CAFFE_ENFORCE(handle != nullptr,\n      \"Cannot load module \",\n      name,\n      \" (with given filename \",\n      filename,\n      \"), are you sure it is correct?\");\n  } else {\n    string inferred_name = string(\"lib\") + name + \".so\";\n    handle = dlopen(\n        inferred_name.c_str(), RTLD_NOW | RTLD_GLOBAL);\n#ifdef __APPLE__\n    // For apple, we will also try the dylib extension.\n    if (!handle) {\n      string inferred_name = string(\"lib\") + name + \".dylib\";\n      handle = dlopen(\n          inferred_name.c_str(), RTLD_NOW | RTLD_GLOBAL | RTLD_NODELETE);\n    }\n#endif\n    CAFFE_ENFORCE(handle != nullptr,\n        \"Cannot load module \",\n        name,\n        \" (with inferred filename \",\n        inferred_name,\n        \"), are you sure it is in the dynamic linker search path?\");\n  }\n  // After the module is loaded, we should check if it actually has the\n  // intended module name. If not, it might be that the module file name\n  // and the module name are inconsistent.\n  if (name.size()) {\n    string module_name_check = \"gCaffe2ModuleSanityCheck\" + name;\n    CAFFE_ENFORCE(dlsym(handle, module_name_check.c_str()),\n        \"The loaded module \",\n        name,\n        \" did not pass the module name sanity check. Is it built with the \"\n        \"right configs? Make sure the file name and the CAFFE2_MODULE name \"\n        \"are consistent.\");\n    // After it passes the dlopen and dlsym check, we should add it to the\n    // current handles.\n    std::lock_guard<std::mutex> guard(gModuleChangeMutex());\n    CurrentModuleHandles()[name] = handle;\n  } else {\n    // If not, we issue a warning that one is recommended to use explicit\n    // module name.\n    LOG(WARNING)\n        << \"Module file \" << filename\n        << \" was loaded without a proper module name. It is recommended \"\n           \"that one load a model with an explicit module name in addition \"\n           \"to the filename.\";\n    // As a contingency, we will store the current module handle with the\n    // filename.\n    std::lock_guard<std::mutex> guard(gModuleChangeMutex());\n    CurrentModuleHandles()[filename] = handle;\n  }\n}\n\n#endif // _MSC_VER\n\n}  // namespace caffe2\n\n"
  },
  {
    "path": "caffe2/core/module.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A global dictionary that holds information about what Caffe2 modules have\n * been loaded in the current runtime, and also utility functions to load\n * modules.\n */\n#ifndef CAFFE2_CORE_MODULE_H_\n#define CAFFE2_CORE_MODULE_H_\n\n#include <algorithm>\n#include <cstdio>\n#include <cstdlib>\n#include <functional>\n#include <memory>\n#include <mutex>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/typeid.h\"\n\nnamespace caffe2 {\n\n/**\n * A module schema that can be used to store specific information about\n * different modules. Currently, we only store the name and a simple\n * description of what this module does.\n */\nclass ModuleSchema {\n public:\n  ModuleSchema(const char* name, const char* description);\n\n private:\n  const char* name_;\n  const char* description_;\n};\n\n\n/**\n * @brief Current Modules present in the Caffe2 runtime.\n * Returns:\n *   map: a map of modules and (optionally) their description. The key is the\n *       module name, and the value is the description for that module. The\n *       module name is recommended to be the part that constitutes the trunk\n *       of the dynamic library: for example, a module called\n *       libcaffe2_db_rocksdb.so should have the name \"caffe2_db_rocksdb\". The\n *       reason we do not use \"lib\" is because it's somewhat redundant, and\n *       the reason we do not include \".so\" is for cross-platform compatibility\n *       on platforms like mac os.\n */\nconst CaffeMap<string, const ModuleSchema*>& CurrentModules();\n\n/**\n * @brief Checks whether a module is already present in the current binary.\n */\nbool HasModule(const string& name);\n\n/**\n * @brief Load a module.\n * Inputs:\n *   name: a module name or a path name.\n *       It is recommended that you use the name of the module, and leave the\n *       full path option to only experimental modules.\n *   filename: (optional) a filename that serves as a hint to load the module.\n */\nvoid LoadModule(const string& name, const string& filename=\"\");\n\n\n#define CAFFE2_MODULE(name, description)                                    \\\n  extern \"C\" {                                                              \\\n    const bool gCaffe2ModuleSanityCheck##name() { return true; }            \\\n  }                                                                         \\\n  namespace {                                                               \\\n    static ::caffe2::ModuleSchema module_schema_##name(#name, description); \\\n  }\n\n}  // namespace caffe2\n#endif  // CAFFE2_CORE_MODULE_H_\n"
  },
  {
    "path": "caffe2/core/module_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n#include <memory>\n\n#include \"caffe2/core/module.h\"\n#include \"caffe2/core/operator.h\"\n#include <gtest/gtest.h>\n#include \"caffe2/core/logging.h\"\n\n// An explicitly defined module, testing correctness when we statically link a\n// module\nCAFFE2_MODULE(caffe2_module_test_static, \"Static module for testing.\");\n\nnamespace caffe2 {\n\nclass Caffe2ModuleTestStaticDummyOp : public OperatorBase {\n public:\n  using OperatorBase::OperatorBase;\n  bool Run(int /* unused */ /*stream_id*/) override {\n    return true;\n  }\n  virtual string type() {\n    return \"base\";\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n  Caffe2ModuleTestStaticDummy, Caffe2ModuleTestStaticDummyOp);\nOPERATOR_SCHEMA(Caffe2ModuleTestStaticDummy);\n\nTEST(ModuleTest, StaticModule) {\n  const string name = \"caffe2_module_test_static\";\n  const auto& modules = CurrentModules();\n  EXPECT_EQ(modules.count(name), 1);\n  EXPECT_TRUE(HasModule(name));\n\n  // LoadModule should not raise an error, since the module is already present.\n  LoadModule(name);\n  // Even a non-existing path should not cause error.\n  LoadModule(name, \"/does/not/exist.so\");\n  EXPECT_EQ(modules.count(name), 1);\n  EXPECT_TRUE(HasModule(name));\n\n  // The module will then introduce the Caffe2ModuleTestStaticDummyOp.\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"Caffe2ModuleTestStaticDummy\");\n  unique_ptr<OperatorBase> op = CreateOperator(op_def, &ws);\n  EXPECT_NE(nullptr, op.get());\n}\n\n#ifdef CAFFE2_BUILD_SHARED_LIBS\nTEST(ModuleTest, DynamicModule) {\n  const string name = \"caffe2_module_test_dynamic\";\n  const auto& modules = CurrentModules();\n  EXPECT_EQ(modules.count(name), 0);\n  EXPECT_FALSE(HasModule(name));\n\n  // Before loading, we should not be able to create the op.\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"Caffe2ModuleTestDynamicDummy\");\n  EXPECT_THROW(\n      CreateOperator(op_def, &ws),\n      EnforceNotMet);\n\n  // LoadModule should load the proper module.\n  LoadModule(name);\n  EXPECT_EQ(modules.count(name), 1);\n  EXPECT_TRUE(HasModule(name));\n\n  // The module will then introduce the Caffe2ModuleTestDynamicDummyOp.\n  unique_ptr<OperatorBase> op_after_load = CreateOperator(op_def, &ws);\n  EXPECT_NE(nullptr, op_after_load.get());\n}\n#endif\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/net.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/net_simple.h\"\n\n#include <set>\n#include <unordered_map>\n#include <unordered_set>\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\nCAFFE_DEFINE_REGISTRY(\n    NetRegistry,\n    NetBase,\n    const std::shared_ptr<const NetDef>&,\n    Workspace*);\n\nNetBase::NetBase(\n    const std::shared_ptr<const NetDef>& def,\n    Workspace* /* unused */)\n    : external_input_(\n          def->external_input().begin(),\n          def->external_input().end()),\n      external_output_(\n          def->external_output().begin(),\n          def->external_output().end()),\n      name_(def->name()),\n      net_def_(def) {\n  // Check that node_name is empty for all ops\n  for (const OperatorDef& op : def->op()) {\n    if (op.has_device_option()) {\n      CAFFE_ENFORCE(\n          !op.device_option().has_node_name(),\n          \"node_name must be empty for all operators at execution time.\");\n    }\n  }\n\n  // Go through the operators and make sure that blobs are correctly made.\n  std::set<string> known_blobs(\n      external_input_.begin(), external_input_.end());\n  std::set<string> remaining_output(\n      external_output_.begin(), external_output_.end());\n  for (const auto& blob : known_blobs) {\n    remaining_output.erase(blob);\n  }\n  for (const OperatorDef& op : def->op()) {\n    for (const string& in : op.input()) {\n      if (!known_blobs.count(in)) {\n        if (external_input_.size()) {\n          CAFFE_THROW(\n              \"op \",\n              op.type(),\n              \": Source for input \",\n              in,\n              \" is unknown for net \",\n              def->name(),\n              \", operator \",\n              ProtoDebugString(op));\n        } else {\n          // If we are not declaring input and output, we will simply VLOG it\n          // for debugging purposes.\n          VLOG(1) << \"op \" << op.type() << \": input \" << in << \" is unknown.\";\n        }\n      }\n    }\n    for (const string& out : op.output()) {\n      known_blobs.insert(out);\n      remaining_output.erase(out);\n    }\n  }\n  // Finally, check if all declared outputs are being created.\n  CAFFE_ENFORCE(\n      remaining_output.size() == 0,\n      \"Some of the blobs are declared as output but never produced by the \"\n      \"net \",\n      def->name(),\n      \", the first one is \",\n      *remaining_output.begin());\n}\n\nbool NetBase::RunAsync() {\n  for (auto& op : GetOperators()) {\n    op->ResetEvent();\n  }\n  return DoRunAsync();\n}\n\nstatic NetObserverCreator GlobalNetObserverCreator = [](NetBase* net) {\n  // A no-op ObserverBase<NetBase> observer\n  return std::unique_ptr<NetObserver>(new NetObserver(net));\n};\n\nvoid SetGlobalNetObserverCreator(NetObserverCreator creator) {\n  GlobalNetObserverCreator = creator;\n  VLOG(1) << \"Have set custom GlobalNetObserverCreator\";\n}\n\nunique_ptr<NetBase> CreateNet(const NetDef& net_def, Workspace* ws) {\n  std::shared_ptr<NetDef> tmp_net_def(new NetDef(net_def));\n  return CreateNet(tmp_net_def, ws);\n}\n\nunique_ptr<NetBase> CreateNet(\n    const std::shared_ptr<const NetDef>& net_def,\n    Workspace* ws) {\n  // In default, we will return a simple network that just runs all operators\n  // sequentially.\n  unique_ptr<NetBase> net;\n  if (!net_def->has_type()) {\n    net = std::unique_ptr<NetBase>(new SimpleNet(net_def, ws));\n  } else {\n    net = NetRegistry()->Create(net_def->type(), net_def, ws);\n  }\n  VLOG(1) << \"Adding a global observer to a net\";\n  if (net) {\n    net->AttachObserver(GlobalNetObserverCreator(net.get()));\n  }\n  return net;\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/net.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_NET_H_\n#define CAFFE2_CORE_NET_H_\n\n#include <atomic>\n#include <climits>\n#include <cstddef>\n#include <thread> // NOLINT\n#include <typeinfo>\n#include <unordered_map>\n#include <vector>\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/observer.h\"\n#include \"caffe2/core/operator_schema.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/simple_queue.h\"\n\nnamespace caffe2 {\n\nclass NetBase;\ntypedef ObserverBase<NetBase> NetObserver;\ntypedef std::function<std::unique_ptr<NetObserver>(NetBase*)>\n    NetObserverCreator;\n\nclass OperatorBase;\nclass Workspace;\n\n// Net is a thin struct that owns all the operators together with the operator\n// contexts.\nclass NetBase : public Observable<NetBase> {\n public:\n  NetBase(const std::shared_ptr<const NetDef>& net_def, Workspace* ws);\n  virtual ~NetBase() noexcept {}\n\n  virtual bool SupportsAsync() = 0;\n  inline const vector<const Event*>& events() const {\n    return events_;\n  }\n\n  virtual void Wait() {\n    // by default just wait till all events are finished\n    for (const auto& event : events_) {\n      event->Finish();\n    }\n  }\n\n  virtual bool Run() {\n    if (!RunAsync()) {\n      LOG(ERROR) << \"Failed to execute async run\";\n      return false;\n    }\n    Wait();\n    for (const Event* event : events_) {\n      if (event->Query() != EventStatus::EVENT_SUCCESS) {\n        CAFFE_THROW(event->ErrorMessage());\n      }\n    }\n    return true;\n  }\n\n  virtual bool RunAsync();\n\n  /**\n   * Benchmarks a network.\n   *\n   * This function returns a vector of float recording the number of milli-\n   * seconds spent during the benchmark. The 0-th item is the time spent per\n   * each network run, and if a net instantiation supports run_individual,\n   * the remainder of the vector returns the number of milliseconds spent per\n   * opeartor.\n   */\n  virtual vector<float> TEST_Benchmark(\n      const int /*warmup_runs*/,\n      const int /*main_runs*/,\n      const bool /*run_individual*/) {\n    LOG(ERROR) << \"Benchmark not implemented for this net type.\";\n    return vector<float>();\n  }\n\n  inline const vector<string>& external_output() const {\n    return external_output_;\n  }\n\n  inline const vector<string>& external_input() const {\n    return external_input_;\n  }\n\n  /* Used to attach Observers to operators of a Net\n   *\n   * Returns pointers to objects owned with unique_ptrs.\n   * Use with caution.\n   */\n  virtual vector<OperatorBase*> GetOperators() const = 0;\n\n  const string& Name() const {\n    return name_;\n  }\n\n  inline const NetDef& debug_def() const {\n    CAFFE_ENFORCE(has_debug_def(), \"net_def was null!\");\n    return *net_def_;\n  }\n\n  inline bool has_debug_def() const {\n    return net_def_ != nullptr;\n  }\n\n protected:\n  virtual bool DoRunAsync() {\n    CAFFE_THROW(\"Not implemented\");\n  };\n\n  vector<string> external_input_;\n  vector<string> external_output_;\n  string name_;\n  vector<const Event*> events_;\n  std::shared_ptr<const NetDef> net_def_;\n  DISABLE_COPY_AND_ASSIGN(NetBase);\n};\n\nCAFFE_DECLARE_REGISTRY(\n    NetRegistry,\n    NetBase,\n    const std::shared_ptr<const NetDef>&,\n    Workspace*);\n#define REGISTER_NET_CREATOR(key, ...) \\\n  CAFFE_REGISTER_CREATOR(NetRegistry, key, __VA_ARGS__)\n#define REGISTER_NET(name, ...) \\\n  CAFFE_REGISTER_CLASS(NetRegistry, name, __VA_ARGS__)\n\n/**\n * @brief Creates a network, accessing / creating blobs in the given workspace.\n *\n * Note that this is different from Workspace::CreateNet. The latter adds the\n * created net object to the workspace's net map, while this function returns\n * a standalone net object.\n */\nunique_ptr<NetBase> CreateNet(const NetDef& net_def, Workspace* ws);\nunique_ptr<NetBase> CreateNet(\n    const std::shared_ptr<const NetDef>& net_def,\n    Workspace* ws);\n\nvoid SetGlobalNetObserverCreator(NetObserverCreator creator);\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_NET_H_\n"
  },
  {
    "path": "caffe2/core/net_async_base.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/net_async_polling.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n\nCAFFE2_DEFINE_int(\n    caffe2_streams_per_gpu,\n    32,\n    \"Number of streams per GPU to use in GPU thread pool\");\n\nCAFFE2_DECLARE_bool(caffe2_dag_net_collect_stats);\n\nCAFFE2_DEFINE_bool(\n    caffe2_net_async_finish_chain,\n    false,\n    \"Wait for chain to finish\");\n\nCAFFE2_DEFINE_int(\n    caffe2_net_async_max_gpus,\n    16,\n    \"Max number of GPUs allowed in net async executor\");\n\nCAFFE2_DEFINE_int(\n    caffe2_net_async_max_numa_nodes,\n    8,\n    \"Max number of NUMA nodes allowed in net async executor\");\n\nCAFFE2_DEFINE_int(\n    caffe2_net_async_cpu_pool_size,\n    0,\n    \"Number of threads in CPU pool (default - number of cores)\");\n\nCAFFE2_DEFINE_bool(\n    caffe2_net_async_check_stream_status,\n    true,\n    \"Select next non-busy stream\");\n\nnamespace caffe2 {\n\nthread_local std::vector<int> AsyncNetBase::stream_counters_;\n\nAsyncNetBase::AsyncNetBase(\n    const std::shared_ptr<const NetDef>& net_def,\n    Workspace* ws)\n    : NetBase(net_def, ws) {\n  operator_nodes_ = dag_utils::prepareOperatorNodes(net_def, ws);\n  operators_.reserve(operator_nodes_.size());\n  for (const auto& node : operator_nodes_) {\n    operators_.push_back(node.operator_.get());\n  }\n\n  const auto& execution_chains = dag_utils::computeChains(operator_nodes_);\n  chains_.reserve(execution_chains.size());\n  for (const auto& kv : execution_chains) {\n    chains_.push_back(kv.second);\n  }\n  chain_nodes_ = dag_utils::prepareChainGraphNodes(operator_nodes_, chains_);\n\n  events_.reserve(chains_.size());\n  for (const auto& chain : chains_) {\n    const auto& op = operators_[chain.back()];\n    events_.push_back(&op->event());\n  }\n\n  gpu_pools_.resize(FLAGS_caffe2_net_async_max_gpus);\n  cpu_pools_.resize(FLAGS_caffe2_net_async_max_numa_nodes);\n  DeviceOption cpu_option;\n  cpu_option.set_device_type(CPU);\n  cpu_pool_ = ThreadPoolRegistry()->Create(\n      DeviceTypeName(cpu_option.device_type()), cpu_option);\n}\n\nstd::shared_ptr<TaskThreadPool> AsyncNetBase::pool_getter(\n    std::vector<std::shared_ptr<TaskThreadPool>>& pools,\n    int pool_idx,\n    const DeviceOption& device_option) {\n  std::unique_lock<std::mutex> pools_lock(pools_mutex_);\n  auto pool = pools[pool_idx];\n  if (!pool) {\n    pool = ThreadPoolRegistry()->Create(\n        DeviceTypeName(device_option.device_type()), device_option);\n    pools[pool_idx] = pool;\n  }\n  return pool;\n}\n\nstd::shared_ptr<TaskThreadPool> AsyncNetBase::pool(\n    const DeviceOption& device_option) {\n  if (device_option.device_type() == CPU) {\n    auto numa_node_id = device_option.numa_node_id();\n    if (numa_node_id == -1) {\n      return cpu_pool_;\n    } else {\n      CAFFE_ENFORCE(\n          numa_node_id >= 0 &&\n              numa_node_id < FLAGS_caffe2_net_async_max_numa_nodes,\n          \"Invalid NUMA node id: \" + caffe2::to_string(numa_node_id));\n      return pool_getter(cpu_pools_, numa_node_id, device_option);\n    }\n  } else if (device_option.device_type() == CUDA) {\n    auto gpu_id = device_option.cuda_gpu_id();\n    CAFFE_ENFORCE(\n        gpu_id >= 0 && gpu_id < FLAGS_caffe2_net_async_max_gpus,\n        \"Invalid GPU id: \" + caffe2::to_string(gpu_id));\n    return pool_getter(gpu_pools_, gpu_id, device_option);\n  } else {\n    CAFFE_THROW(\n        \"Unsupported device type \" +\n        caffe2::to_string(device_option.device_type()));\n  }\n}\n\nint AsyncNetBase::stream(int task_id) {\n  const auto& device_option = event(task_id).GetDeviceOption();\n  int stream_id = 0;\n  if (device_option.device_type() == CUDA) {\n    int gpu_id = device_option.cuda_gpu_id();\n    CAFFE_ENFORCE_GE(gpu_id, 0, \"Invalid gpu id: \" + caffe2::to_string(gpu_id));\n    if (gpu_id >= stream_counters_.size()) {\n      stream_counters_.resize(gpu_id + 1, 0);\n    }\n    do {\n      stream_id = stream_counters_[gpu_id]++;\n      stream_counters_[gpu_id] %= FLAGS_caffe2_streams_per_gpu;\n    } while (!isStreamFree(task_id, stream_id) &&\n             FLAGS_caffe2_net_async_check_stream_status);\n  }\n  return stream_id;\n}\n\nbool AsyncNetBase::isStreamFree(int task_id, int stream_id) const {\n  auto& task = chains_[task_id];\n  auto& last_task_op = operators_[task.back()];\n  return last_task_op->IsStreamFree(stream_id);\n}\n\nbool AsyncNetBase::canSchedule(\n    int task_id,\n    const std::vector<EventStatus>* status) {\n  auto first_child_op_id = chains_[task_id].front();\n  for (auto parent_id : parents(task_id)) {\n    auto last_parent_op_id = chains_[parent_id].back();\n    EventStatus parent_status;\n    if (status) {\n      parent_status = status->at(parent_id);\n    } else {\n      parent_status = operators_[last_parent_op_id]->event().Query();\n    }\n    bool can_schedule = Event::CanSchedule(\n        operators_[last_parent_op_id]->event().GetType(),\n        parent_status,\n        operators_[first_child_op_id]->event().GetType(),\n        operators_[first_child_op_id]->SupportsAsyncScheduling());\n    if (!can_schedule) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nint AsyncNetBase::tasksNum() const {\n  return chains_.size();\n}\n\nEvent& AsyncNetBase::event(int task_id) const {\n  auto& task = chains_[task_id];\n  auto& last_task_op = operators_[task.back()];\n  return last_task_op->event();\n}\n\nEventStatus AsyncNetBase::query(int task_id) const {\n  return event(task_id).Query();\n}\n\nconst std::vector<int>& AsyncNetBase::children(int task_id) const {\n  const auto& task_node = chain_nodes_[task_id];\n  return task_node.children_;\n}\n\nconst std::vector<int>& AsyncNetBase::parents(int task_id) const {\n  const auto& task_node = chain_nodes_[task_id];\n  return task_node.parents_;\n}\n\nvoid AsyncNetBase::asyncWait(\n    int task_id,\n    int stream_id,\n    const std::vector<int>& wait_task_ids) const {\n  auto first_op_id = chains_[task_id].front();\n  auto& first_op = operators_[first_op_id];\n  std::vector<const Event*> events;\n  events.reserve(wait_task_ids.size());\n  for (auto wait_task_id : wait_task_ids) {\n    events.push_back(&event(wait_task_id));\n  }\n  first_op->WaitEvents(events, stream_id);\n}\n\nvoid AsyncNetBase::run(int task_id, int stream_id) {\n  std::string err_msg;\n  for (auto& op_id : chains_[task_id]) {\n    auto& op = operators_[op_id];\n    try {\n      CAFFE_ENFORCE(op->RunAsync(stream_id), \"Failed to execute an op\");\n    } catch (const std::exception& e) {\n      CAFFE_THROW(\n          std::string(e.what()) + \",  op \" +\n          (op->has_debug_def() ? op->type() : \" unknown\"));\n    } catch (...) {\n      CAFFE_THROW(\n          \"Failed to execute task: unknown error,  op \" +\n          (op->has_debug_def() ? op->type() : \" unknown\"));\n    }\n  }\n\n  if (FLAGS_caffe2_net_async_finish_chain) {\n    operators_[chains_[task_id].back()]->event().Finish();\n  }\n}\n\nvoid AsyncNetBase::finishTasks(const std::unordered_set<int>& task_ids) {\n  for (const auto& task_id : task_ids) {\n    event(task_id).Finish();\n  }\n}\n\nvoid AsyncNetBase::finalizeEvents() {\n  for (auto task_id = 0; task_id < tasksNum(); ++task_id) {\n    auto status = query(task_id);\n    if (status == EventStatus::EVENT_SCHEDULED) {\n      event(task_id).Finish();\n    } else if (status == EventStatus::EVENT_INITIALIZED) {\n      event(task_id).SetFinished();\n    }\n  }\n}\n\nAsyncNetBase::~AsyncNetBase() {}\n\nCAFFE_DEFINE_SHARED_REGISTRY(\n    ThreadPoolRegistry,\n    TaskThreadPool,\n    const DeviceOption&);\n\nnamespace {\nstd::shared_ptr<TaskThreadPool> AsyncNetCPUThreadPoolCreator(\n    const DeviceOption& device_option) {\n  CAFFE_ENFORCE_EQ(\n      device_option.device_type(),\n      CPU,\n      \"Unexpected device type for CPU thread pool\");\n  return GetAsyncNetCPUThreadPool(device_option.numa_node_id());\n}\n} // namespace\n\nCAFFE_REGISTER_CREATOR(ThreadPoolRegistry, CPU, AsyncNetCPUThreadPoolCreator);\n\n/* static */\nstd::shared_ptr<TaskThreadPool> GetAsyncNetCPUThreadPool(int numa_node_id) {\n  // Note: numa_node_id = -1 (DeviceOption's default value) corresponds to\n  // no NUMA used\n  static std::unordered_map<int, std::weak_ptr<TaskThreadPool>> pools;\n  static std::mutex pool_mutex;\n  std::lock_guard<std::mutex> lock(pool_mutex);\n\n  std::shared_ptr<TaskThreadPool> shared_pool = nullptr;\n  if (pools.count(numa_node_id)) {\n    shared_pool = pools.at(numa_node_id).lock();\n  }\n  if (!shared_pool) {\n    auto pool_size = FLAGS_caffe2_net_async_cpu_pool_size;\n    if (pool_size <= 0) {\n      auto num_cores = std::thread::hardware_concurrency();\n      CAFFE_ENFORCE(num_cores > 0, \"Failed to get number of CPU cores\");\n      pool_size = num_cores;\n    }\n    LOG(INFO) << \"Using cpu pool size: \" << pool_size;\n    shared_pool = std::make_shared<TaskThreadPool>(pool_size, numa_node_id);\n    pools[numa_node_id] = shared_pool;\n  }\n  return shared_pool;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/net_async_base.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_NET_ASYNC_BASE_H_\n#define CAFFE2_CORE_NET_ASYNC_BASE_H_\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/net_dag_utils.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/core/stats.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include \"caffe2/utils/thread_pool.h\"\n\nnamespace caffe2 {\n\nclass AsyncNetBase : public NetBase {\n public:\n  AsyncNetBase(const std::shared_ptr<const NetDef>& net_def, Workspace* ws);\n  ~AsyncNetBase() override;\n\n  bool SupportsAsync() override {\n    return true;\n  }\n\n  vector<OperatorBase*> GetOperators() const override {\n    return operators_;\n  }\n\n protected:\n  bool canSchedule(\n      int chain_id,\n      const std::vector<EventStatus>* status = nullptr);\n\n  int tasksNum() const;\n  Event& event(int task_id) const;\n  EventStatus query(int task_id) const;\n  const std::vector<int>& children(int task_id) const;\n  const std::vector<int>& parents(int task_id) const;\n  void asyncWait(\n      int task_id,\n      int stream_id,\n      const std::vector<int>& wait_task_ids) const;\n  void run(int task_id, int stream_id);\n  int stream(int task_id);\n  std::shared_ptr<TaskThreadPool> pool(const DeviceOption& device_option);\n\n  void finishTasks(const std::unordered_set<int>& task_ids);\n  void finalizeEvents();\n\n  bool isStreamFree(int task_id, int stream_id) const;\n\n  // Operator/task graph\n  std::vector<OperatorBase*> operators_;\n  std::vector<dag_utils::OperatorNode> operator_nodes_;\n  std::vector<std::vector<int>> chains_;\n  std::vector<dag_utils::OpGraphNode> chain_nodes_; // chains' parents/children\n\n  // Pools and streams\n  std::mutex pools_mutex_;\n  std::shared_ptr<TaskThreadPool> cpu_pool_;\n  std::vector<std::shared_ptr<TaskThreadPool>> cpu_pools_;\n  std::vector<std::shared_ptr<TaskThreadPool>> gpu_pools_;\n  static thread_local std::vector<int> stream_counters_;\n\n  DISABLE_COPY_AND_ASSIGN(AsyncNetBase);\n\n private:\n  std::shared_ptr<TaskThreadPool> pool_getter(\n      std::vector<std::shared_ptr<TaskThreadPool>>& pools,\n      int pool_idx,\n      const DeviceOption& device_option);\n};\n\nCAFFE_DECLARE_SHARED_REGISTRY(\n    ThreadPoolRegistry,\n    TaskThreadPool,\n    const DeviceOption&);\n\nstd::shared_ptr<TaskThreadPool> GetAsyncNetCPUThreadPool(int numa_node_id);\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_NET_ASYNC_POLLING_H_\n"
  },
  {
    "path": "caffe2/core/net_async_dag_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/net_async_dag_gpu.h\"\n\n#include <set>\n#include <stack>\n#include <unordered_map>\n#include <unordered_set>\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/static_tracepoint.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\n#ifdef CAFFE2_USE_NVTX\n#include <nvToolsExt.h>\n#endif\n\nCAFFE2_DEFINE_bool(caffe2_use_nvtx, false, \"Use NVTX ranges for profiling\");\n\nCAFFE2_DEFINE_bool(\n    caffe2_async_dag_use_multiple_streams,\n    false,\n    \"Use multiple streams per thread\");\n\nCAFFE2_DECLARE_bool(caffe2_dag_net_collect_stats);\n\nCAFFE2_DECLARE_bool(caffe2_net_async_finish_chain);\n\nCAFFE2_DECLARE_int(caffe2_streams_per_gpu);\n\nCAFFE2_DECLARE_bool(caffe2_net_async_check_stream_status);\n\nnamespace caffe2 {\n\nthread_local std::vector<int> AsyncDAGNet::stream_counters_;\n\nnamespace {\n\nusing Color = int32_t;\nconstexpr Color kRunColor = 0x0000CCFF; // blue\nconstexpr Color kRecordColor = 0x00FF3300; // red\nconstexpr Color kWaitColor = 0x0066FF33; // green\n\n#ifdef CAFFE2_USE_NVTX\n\nclass ProfiledRange {\n public:\n  ProfiledRange(const OperatorDef& def, Color color) {\n    if (!FLAGS_caffe2_use_nvtx) {\n      return;\n    }\n    nvtxEventAttributes_t eventAttrib = {0};\n    eventAttrib.version = NVTX_VERSION;\n    eventAttrib.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE;\n    eventAttrib.colorType = NVTX_COLOR_ARGB;\n    eventAttrib.color = color;\n    eventAttrib.messageType = NVTX_MESSAGE_TYPE_ASCII;\n    eventAttrib.message.ascii = def.type().c_str();\n    range_ = nvtxRangeStartEx(&eventAttrib);\n    CAFFE_ENFORCE(range_, \"Start range is invalid.\");\n  }\n\n  ~ProfiledRange() {\n    if (!FLAGS_caffe2_use_nvtx) {\n      return;\n    }\n    nvtxRangeEnd(range_);\n  }\n\n private:\n  nvtxRangeId_t range_ = 0;\n  DISABLE_COPY_AND_ASSIGN(ProfiledRange);\n};\n\n#else\n\nclass ProfiledRange {\n public:\n  ProfiledRange(const OperatorDef& def, Color color) {}\n\n private:\n  DISABLE_COPY_AND_ASSIGN(ProfiledRange);\n};\n\n#endif // ifdef CAFFE2_USE_NVTX\n\n} // namespace\n\nAsyncDAGNet::AsyncDAGNet(\n    const std::shared_ptr<const NetDef>& net_def,\n    Workspace* ws)\n    : DAGNetBase(net_def, ws) {\n  VLOG(1) << \"Constructing Async DAG Net \" << net_def->name();\n  eventRecorded_.resize(net_def->op_size());\n\n  // For all chains, their tail should consist the list of events that we are\n  // needing for synchronization in the Run() inteface, unless there are other\n  // chains depending on it.\n  events_.reserve(execution_chains_.size());\n  for (const auto& chain : execution_chains_) {\n    const int tail_op_idx = chain.second.back();\n    if (operator_nodes_[tail_op_idx].children_.empty()) {\n      events_.push_back(&operator_nodes_[tail_op_idx].operator_->event());\n    }\n  }\n  VLOG(1) << \"Total \" << execution_chains_.size()\n          << \" chains, final waiting on \" << events_.size() << \" events\";\n}\n\nint AsyncDAGNet::stream(const DeviceOption& device_option) {\n  int stream_id = 0;\n  if (device_option.device_type() == CUDA) {\n    int gpu_id = device_option.cuda_gpu_id();\n    CAFFE_ENFORCE_GE(gpu_id, 0, \"Invalid gpu id: \" + caffe2::to_string(gpu_id));\n    if (gpu_id >= stream_counters_.size()) {\n      stream_counters_.resize(gpu_id + 1, 0);\n    }\n    do {\n      stream_id = stream_counters_[gpu_id]++;\n      stream_counters_[gpu_id] %= FLAGS_caffe2_streams_per_gpu;\n    } while (FLAGS_caffe2_net_async_check_stream_status &&\n             !CUDAContext::IsStreamFree(device_option, stream_id));\n  }\n  return stream_id;\n}\n\nbool AsyncDAGNet::RunAt(int chain_id, const std::vector<int>& chain) {\n  CAFFE_ENFORCE(!chain.empty(), \"Chain should not be empty.\");\n  const auto source_idx = chain.front();\n  const auto& parents = operator_nodes_[source_idx].parents_;\n  // Help ensure that our chaining is correct by verifying at least\n  // one parent recorded an event.\n  CAFFE_ENFORCE(\n      parents.empty() ||\n          std::any_of(\n              parents.begin(),\n              parents.end(),\n              [this](int p) { return eventRecorded_[p]; }),\n      \"None of the parent is recorded for an event.\");\n\n  int stream_id = 0;\n  if (FLAGS_caffe2_async_dag_use_multiple_streams) {\n    stream_id = stream(\n        operator_nodes_[source_idx].operator_->event().GetDeviceOption());\n  }\n\n  std::vector<const Event*> parent_events;\n  parent_events.reserve(operator_nodes_[source_idx].parents_.size());\n  for (auto source_parent_idx : operator_nodes_[source_idx].parents_) {\n    parent_events.push_back(\n        &operator_nodes_[source_parent_idx].operator_->event());\n  }\n  {\n    ProfiledRange r(\n        operator_nodes_[source_idx].operator_->debug_def(), kWaitColor);\n    operator_nodes_[source_idx].operator_->WaitEvents(parent_events, stream_id);\n  }\n\n  if (FLAGS_caffe2_dag_net_collect_stats) {\n    const auto& device_option =\n        operator_nodes_[source_idx].operator_->event().GetDeviceOption();\n    CAFFE_EVENT(\n        stats_[device_option.device_type()],\n        task_wait_time_us,\n        task_timers_[chain_id]->MicroSeconds());\n  }\n\n  // We've waited on all our parent indices.\n  bool success = true;\n  for (auto idx : chain) {\n    ProfiledRange r(operator_nodes_[idx].operator_->debug_def(), kRunColor);\n    success &= operator_nodes_[idx].operator_->RunAsync(stream_id);\n  }\n\n  const auto& sink_idx = chain.back();\n  if (success && FLAGS_caffe2_net_async_finish_chain) {\n    operator_nodes_[sink_idx].operator_->event().Finish();\n  }\n  CAFFE_ENFORCE(\n      !eventRecorded_[sink_idx],\n      \"An event for \",\n      sink_idx,\n      \" should not be recorded.\");\n  eventRecorded_[sink_idx] = 1;\n\n  if (FLAGS_caffe2_dag_net_collect_stats) {\n    const auto& device_option =\n        operator_nodes_[source_idx].operator_->event().GetDeviceOption();\n    CAFFE_EVENT(\n        stats_[device_option.device_type()],\n        task_time_to_scheduled_us,\n        task_timers_[chain_id]->MicroSeconds());\n  }\n  return success;\n}\n\nbool AsyncDAGNet::DoRunAsync() {\n  // Reset the event tracking at each iteration\n  eventRecorded_.assign(eventRecorded_.size(), 0);\n\n  const auto result = DAGNetBase::DoRunAsync();\n  return result;\n}\n\nREGISTER_NET(async_dag, AsyncDAGNet);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/net_async_dag_gpu.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_NET_ASYNC_DAG_GPU_H_\n#define CAFFE2_CORE_NET_ASYNC_DAG_GPU_H_\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/net_dag.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\n// Run an event-driven graph - before each operator chain, wait on each parent\n// operator for the chain source, then execute each operator. Due to the chain\n// construction mechanism, operators in the same chain implicitly runs on the\n// same stream.\n// AsyncDAGNet is only registered in gpu mode, because CPU code is always sync\n// and a CPU only AsyncDAG net is essentially a DAG net.\nclass AsyncDAGNet : public DAGNetBase {\n public:\n  AsyncDAGNet(const std::shared_ptr<const NetDef>& net_def, Workspace* ws);\n  bool SupportsAsync() override {\n    return true;\n  }\n  bool RunAt(int chain_id, const std::vector<int>& chain) override;\n\n protected:\n  bool DoRunAsync() override;\n\n  // Tracks whether a given op has had an event recorded in each\n  // RunAt() iteration.\n  std::vector<int32_t> eventRecorded_;\n\n  int stream(const DeviceOption& device_option);\n  static thread_local std::vector<int> stream_counters_;\n\n  DISABLE_COPY_AND_ASSIGN(AsyncDAGNet);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_NET_ASYNC_DAG_GPU_H_\n"
  },
  {
    "path": "caffe2/core/net_async_gpu_thread_pool.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_NET_ASYNC_GPU_THREAD_POOL_H_\n#define CAFFE2_CORE_NET_ASYNC_GPU_THREAD_POOL_H_\n\n#include \"caffe2/core/net_async_polling.h\"\n\nnamespace caffe2 {\n\nstd::shared_ptr<TaskThreadPool> GetAsyncNetGPUThreadPool(int gpu_id);\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_NET_ASYNC_GPU_THREAD_POOL_H_\n"
  },
  {
    "path": "caffe2/core/net_async_gpu_thread_pool_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/net_async_gpu_thread_pool.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\nCAFFE2_DEFINE_int(caffe2_threads_per_gpu, 1, \"Number of CPU threads per GPU\");\n\nnamespace caffe2 {\n\nnamespace {\nstd::shared_ptr<TaskThreadPool> AsyncNetGPUThreadPoolCreator(\n    const DeviceOption& device_option) {\n  CAFFE_ENFORCE_EQ(\n      device_option.device_type(),\n      CUDA,\n      \"Unexpected device type for CUDA thread pool\");\n  return GetAsyncNetGPUThreadPool(device_option.cuda_gpu_id());\n}\n} // namespace\n\nCAFFE_REGISTER_CREATOR(ThreadPoolRegistry, CUDA, AsyncNetGPUThreadPoolCreator);\n\nstd::shared_ptr<TaskThreadPool> GetAsyncNetGPUThreadPool(int gpu_id) {\n  static std::unordered_map<int, std::weak_ptr<TaskThreadPool>> pools;\n  static std::mutex pool_mutex;\n  std::lock_guard<std::mutex> lock(pool_mutex);\n\n  std::shared_ptr<TaskThreadPool> shared_pool = nullptr;\n  if (pools.count(gpu_id)) {\n    shared_pool = pools.at(gpu_id).lock();\n  }\n  if (!shared_pool) {\n    shared_pool =\n        std::make_shared<TaskThreadPool>(FLAGS_caffe2_threads_per_gpu);\n    pools[gpu_id] = shared_pool;\n  }\n  return shared_pool;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/net_async_polling.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/net_async_polling.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n\nCAFFE2_DECLARE_bool(caffe2_dag_net_collect_stats);\n\nnamespace caffe2 {\n\nAsyncPollingNet::AsyncPollingNet(\n    const std::shared_ptr<const NetDef>& net_def,\n    Workspace* ws)\n    : AsyncNetBase(net_def, ws), running_(false) {\n  task_timers_.resize(tasksNum());\n  for (auto task_id = 0; task_id < tasksNum(); ++task_id) {\n    task_timers_[task_id] = caffe2::make_unique<Timer>();\n  }\n\n  stats_.reserve(DeviceType::COMPILE_TIME_MAX_DEVICE_TYPES);\n  for (auto device_idx = 0;\n       device_idx < DeviceType::COMPILE_TIME_MAX_DEVICE_TYPES;\n       ++device_idx) {\n    stats_.emplace_back(\n        \"async_net/stats/\" + net_def->name() + \"/\" +\n        caffe2::DeviceTypeName(device_idx));\n  }\n\n  reset();\n}\n\nbool AsyncPollingNet::DoRunAsync() {\n  CAFFE_ENFORCE(!running_, \"Concurrent RunAsync calls\");\n  running_ = true;\n  reset();\n\n  StartAllObservers();\n\n  Timer timer;\n  bool success = pollAndSchedule();\n  if (FLAGS_caffe2_dag_net_collect_stats) {\n    CAFFE_EVENT(stats_[CPU], poll_time_ms, timer.MilliSeconds());\n  }\n  if (!success) {\n    finalizeEvents();\n  }\n\n  StopAllObservers();\n  running_ = false;\n  return success;\n}\n\nvoid AsyncPollingNet::schedule(int task_id) {\n  if (FLAGS_caffe2_dag_net_collect_stats) {\n    task_timers_[task_id]->Start();\n  }\n  const auto& device_option = event(task_id).GetDeviceOption();\n  pool(device_option)->run([this, task_id, device_option]() {\n    int stream_id = stream(task_id);\n\n    if (FLAGS_caffe2_dag_net_collect_stats) {\n      CAFFE_EVENT(\n          stats_[device_option.device_type()],\n          task_pool_wait_time_us,\n          task_timers_[task_id]->MicroSeconds());\n    }\n\n    // Non-blocking wait, setups scheduling of dependent async computations;\n    // canSchedule ensures that there's no busy wait,\n    // for CUDA events we need to insert CUDA event synchronization to ensure\n    // that async CUDA computations are executed in correct order\n    asyncWait(task_id, stream_id, parents(task_id));\n    try {\n      if (FLAGS_caffe2_dag_net_collect_stats) {\n        Timer run_time;\n        run(task_id, stream_id);\n        CAFFE_EVENT(\n            stats_[device_option.device_type()],\n            task_run_time_us,\n            run_time.MicroSeconds());\n      } else {\n        run(task_id, stream_id);\n      }\n    } catch (const std::exception&) {\n      has_chain_failed_ = true;\n    }\n  });\n}\n\nvoid AsyncPollingNet::reset() {\n  status_.clear();\n  status_.resize(tasksNum(), EventStatus::EVENT_INITIALIZED);\n  has_chain_failed_ = false;\n}\n\nbool AsyncPollingNet::pollAndSchedule() {\n  std::unordered_set<int> scheduled_tasks;\n  std::unordered_set<int> current_tasks;\n\n  for (auto task_id = 0; task_id < tasksNum(); ++task_id) {\n    if (parents(task_id).empty()) {\n      current_tasks.insert(task_id);\n      scheduled_tasks.insert(task_id);\n      schedule(task_id);\n    }\n  }\n\n  Timer timer;\n  while (!current_tasks.empty()) {\n    std::unordered_set<int> updated_tasks;\n    std::unordered_set<int> next_tasks;\n    updated_tasks.reserve(current_tasks.size());\n\n    if (FLAGS_caffe2_dag_net_collect_stats) {\n      timer.Start();\n    }\n    if (has_chain_failed_) {\n      finishTasks(current_tasks);\n      return false;\n    }\n    for (auto& task_id : current_tasks) {\n      auto prev_status = status_[task_id];\n      status_[task_id] = query(task_id);\n      if (status_[task_id] == EventStatus::EVENT_FAILED) {\n        finishTasks(current_tasks);\n        return false;\n      }\n\n      if (prev_status != status_[task_id]) {\n        updated_tasks.insert(task_id);\n        if (FLAGS_caffe2_dag_net_collect_stats) {\n          updateTaskStats(task_id);\n        }\n      }\n\n      if (status_[task_id] != EventStatus::EVENT_SUCCESS) {\n        next_tasks.insert(task_id);\n      }\n    }\n    if (FLAGS_caffe2_dag_net_collect_stats) {\n      CAFFE_EVENT(\n          stats_[CPU], poll_status_update_time_us, timer.MicroSeconds());\n    }\n\n    std::unordered_set<int> visited_children;\n    for (auto& task_id : updated_tasks) {\n      CAFFE_ENFORCE(\n          status_[task_id] == EventStatus::EVENT_SCHEDULED ||\n          status_[task_id] == EventStatus::EVENT_SUCCESS);\n\n      for (auto& child_id : children(task_id)) {\n        if (!visited_children.count(child_id)) {\n          visited_children.insert(child_id);\n          // Important - check whether we have already scheduled the task,\n          // e.g. a child CUDA task can be scheduled after parent CUDA\n          // task becomes EventStatus::EVENT_SCHEDULED and also later when\n          // parent CUDA task becomes EventStatus::EVENT_SUCCESS\n          if (!scheduled_tasks.count(child_id) &&\n              canSchedule(child_id, &status_)) {\n            next_tasks.insert(child_id);\n            scheduled_tasks.insert(child_id);\n            schedule(child_id);\n          }\n        }\n      }\n    }\n\n    current_tasks.swap(next_tasks);\n  }\n  return true;\n}\n\nvoid AsyncPollingNet::updateTaskStats(int task_id) {\n  const auto& device_option = event(task_id).GetDeviceOption();\n  if (status_[task_id] == EventStatus::EVENT_SCHEDULED) {\n    CAFFE_EVENT(\n        stats_[device_option.device_type()],\n        task_time_to_scheduled_us,\n        task_timers_[task_id]->MicroSeconds());\n  }\n  if (status_[task_id] == EventStatus::EVENT_SUCCESS) {\n    CAFFE_EVENT(\n        stats_[device_option.device_type()],\n        task_time_to_succeeded_ms,\n        task_timers_[task_id]->MilliSeconds());\n  }\n}\n\nAsyncPollingNet::~AsyncPollingNet() {}\n\nREGISTER_NET(async_polling, AsyncPollingNet);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/net_async_polling.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_NET_ASYNC_POLLING_H_\n#define CAFFE2_CORE_NET_ASYNC_POLLING_H_\n\n#include \"caffe2/core/net_async_base.h\"\n\nnamespace caffe2 {\n\nclass AsyncPollingNet : public AsyncNetBase {\n public:\n  AsyncPollingNet(const std::shared_ptr<const NetDef>& net_def, Workspace* ws);\n  ~AsyncPollingNet() override;\n\n protected:\n  bool DoRunAsync() override;\n\n  bool pollAndSchedule();\n  void schedule(int task_id);\n\n  // Synchronization\n  std::mutex running_mutex_;\n  std::condition_variable running_cv_;\n  std::atomic<bool> running_;\n\n  // Stats\n  struct AsyncPollingNetStats {\n    CAFFE_STAT_CTOR(AsyncPollingNetStats);\n    CAFFE_AVG_EXPORTED_STAT(poll_time_ms);\n    CAFFE_AVG_EXPORTED_STAT(task_pool_wait_time_us);\n    CAFFE_AVG_EXPORTED_STAT(task_run_time_us);\n    CAFFE_AVG_EXPORTED_STAT(poll_status_update_time_us);\n    CAFFE_AVG_EXPORTED_STAT(task_time_to_scheduled_us);\n    CAFFE_AVG_EXPORTED_STAT(task_time_to_succeeded_ms);\n  };\n  mutable std::vector<AsyncPollingNetStats> stats_;\n  std::vector<std::unique_ptr<Timer>> task_timers_;\n  void updateTaskStats(int task_id);\n\n  // Polling\n  std::vector<EventStatus> status_;\n  void reset();\n  std::atomic<bool> has_chain_failed_;\n\n  DISABLE_COPY_AND_ASSIGN(AsyncPollingNet);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_NET_ASYNC_POLLING_H_\n"
  },
  {
    "path": "caffe2/core/net_async_scheduling.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/net_async_scheduling.h\"\n\nCAFFE2_DEFINE_bool(\n    caffe2_net_async_always_schedule_child,\n    false,\n    \"Always schedule child chains from parent chain\");\n\nnamespace caffe2 {\n\nAsyncSchedulingNet::AsyncSchedulingNet(\n    const std::shared_ptr<const NetDef>& net_def,\n    Workspace* ws)\n    : AsyncNetBase(net_def, ws), running_(false) {\n  reset();\n}\n\nvoid AsyncSchedulingNet::reset() {\n  processed_tasks_num_ = 0;\n  cleanup_ = false;\n  success_ = true;\n\n  for (auto task_id = 0; task_id < tasksNum(); ++task_id) {\n    auto& task_ops = chains_[task_id];\n    auto& task_op_node = operator_nodes_[task_ops.front()];\n    task_op_node.runtime_parent_count_ = parents(task_id).size();\n  }\n  exception_messages_.clear();\n}\n\nvoid AsyncSchedulingNet::Wait() {\n  std::unique_lock<std::mutex> lock(running_mutex_);\n  while (running_) {\n    running_cv_.wait(lock);\n  }\n}\n\nvoid AsyncSchedulingNet::schedule(int task_id) {\n  const auto& device_option = event(task_id).GetDeviceOption();\n  pool(device_option)->run([this, task_id]() {\n    if (success_) {\n      int stream_id = stream(task_id);\n      asyncWait(task_id, stream_id, parents(task_id));\n      try {\n        run(task_id, stream_id);\n      } catch (const std::exception& e) {\n        std::unique_lock<std::mutex> lock(exception_mutex_);\n        exception_messages_.push_back(e.what());\n        success_ = false;\n      }\n    }\n\n    auto task_count = ++processed_tasks_num_;\n\n    for (auto child_id : children(task_id)) {\n      int parent_count = updateParentCount(child_id);\n      if (parent_count == 0) {\n        if (cleanup_ || FLAGS_caffe2_net_async_always_schedule_child ||\n            canSchedule(child_id)) {\n          schedule(child_id);\n        } else {\n          const auto& device_option = event(child_id).GetDeviceOption();\n          pool(device_option)\n              ->run(std::bind(\n                  &AsyncSchedulingNet::pollAndSchedule, this, child_id));\n        }\n      }\n    }\n\n    if (success_) {\n      if (task_count == tasksNum()) {\n        // All tasks are finished, polling thread is sleeping;\n        // only one thread enters here\n        finalizeEvents();\n        finishRun();\n        return;\n      }\n    } else {\n      // Before setting running_ to false and notifying waiters we need to\n      // 1. Ensure that only one thread does the cleanup\n      // 2. Ensure that all other pending tasks in workers and polling threads\n      //    are finished and\n      // 3. Ensure that all tasks that were not scheduled have their events set\n      {\n        std::unique_lock<std::mutex> cleanup_lock(cleanup_mutex_);\n        if (cleanup_) {\n          return;\n        }\n        cleanup_ = true;\n      }\n\n      // Errors are not recoverable and happen in exceptional cases,\n      // ok to busy wait\n      while (processed_tasks_num_ != tasksNum()) {\n      }\n\n      // Make sure all events are set, wait for scheduled events\n      finalizeEvents();\n\n      // Notify observers and waiters\n      finishRun();\n    }\n  });\n}\n\nvoid AsyncSchedulingNet::pollAndSchedule(int task_id) {\n  if (canSchedule(task_id) || cleanup_) {\n    // force schedule the rest of the tasks if cleanup is started\n    schedule(task_id);\n  } else {\n    const auto& device_option = event(task_id).GetDeviceOption();\n    pool(device_option)\n        ->run(std::bind(&AsyncSchedulingNet::pollAndSchedule, this, task_id));\n  }\n}\n\nint AsyncSchedulingNet::updateParentCount(int child_id) {\n  auto& child_ops = chains_[child_id];\n  auto& child_node = operator_nodes_[child_ops.front()];\n  int parent_count = --child_node.runtime_parent_count_;\n  CAFFE_ENFORCE_GE(parent_count, 0);\n  return parent_count;\n}\n\nvoid AsyncSchedulingNet::finishRun() {\n  // notify observers and waiters\n  StopAllObservers();\n  running_ = false;\n  running_cv_.notify_all();\n}\n\nbool AsyncSchedulingNet::DoRunAsync() {\n  std::unique_lock<std::mutex> lock(running_mutex_);\n  CAFFE_ENFORCE(!running_, \"Concurrent RunAsync calls\");\n  running_ = true;\n  reset();\n\n  StartAllObservers();\n\n  for (auto task_id = 0; task_id < tasksNum(); ++task_id) {\n    if (parents(task_id).empty()) {\n      schedule(task_id);\n    }\n  }\n\n  return true;\n}\n\nAsyncSchedulingNet::~AsyncSchedulingNet() {}\n\nREGISTER_NET(async_scheduling, AsyncSchedulingNet);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/net_async_scheduling.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_NET_ASYNC_SCHEDULING_H_\n#define CAFFE2_CORE_NET_ASYNC_SCHEDULING_H_\n\n#include \"caffe2/core/net_async_base.h\"\n\nnamespace caffe2 {\n\nclass AsyncSchedulingNet : public AsyncNetBase {\n public:\n  AsyncSchedulingNet(\n      const std::shared_ptr<const NetDef>& net_def,\n      Workspace* ws);\n  ~AsyncSchedulingNet() override;\n\n  void Wait() override;\n\n protected:\n  bool DoRunAsync() override;\n\n  void pollAndSchedule(int task_id);\n  void schedule(int task_id);\n  void reset();\n  virtual void finishRun();\n  int updateParentCount(int child_id);\n\n  std::mutex running_mutex_;\n  std::condition_variable running_cv_;\n  std::atomic<bool> running_;\n  std::atomic<bool> success_;\n\n  std::mutex cleanup_mutex_;\n  std::atomic<bool> cleanup_;\n\n  std::atomic<int> processed_tasks_num_;\n  std::mutex exception_mutex_;\n  std::vector<std::string> exception_messages_;\n\n  DISABLE_COPY_AND_ASSIGN(AsyncSchedulingNet);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_NET_ASYNC_SCHEDULING_H_\n"
  },
  {
    "path": "caffe2/core/net_dag.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/net_dag.h\"\n\n#include <set>\n#include <stack>\n#include <unordered_map>\n#include <unordered_set>\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/static_tracepoint.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nCAFFE2_DEFINE_bool(\n    caffe2_disable_chaining,\n    false,\n    \"Disable chaining logic (some latent multi-device issues).\");\n\nCAFFE2_DEFINE_bool(\n    caffe2_dag_net_collect_stats,\n    false,\n    \"Collect time stats in DAG net\");\n\nnamespace caffe2 {\n\nDAGNetBase::DAGNetBase(\n    const std::shared_ptr<const NetDef>& net_def,\n    Workspace* ws)\n    : NetBase(net_def, ws), caught_exception_yet_(false), iter_(0) {\n  // Blob creator allows us to track which operator created which blob.\n  VLOG(1) << \"Constructing DAGNet \" << net_def->name();\n\n  operator_nodes_ = dag_utils::prepareOperatorNodes(net_def, ws);\n\n  execution_chains_ =\n      (FLAGS_caffe2_disable_chaining\n           ? dag_utils::singleChains(operator_nodes_)\n           : dag_utils::computeChains(operator_nodes_));\n\n  operators_.reserve(operator_nodes_.size());\n  for (const auto& node : operator_nodes_) {\n    operators_.push_back(node.operator_.get());\n  }\n\n  LOG(INFO) << \"Number of parallel execution chains \"\n            << execution_chains_.size()\n            << \" Number of operators = \" << net_def->op_size();\n  // TODO: do we want to make sure that there are no loops in the\n  // dependency graph?\n\n  // Figure out the initial frontier - this is the one we will feed into the job\n  // queue to start a run.\n  for (int idx = 0; idx < operator_nodes_.size(); ++idx) {\n    if (operator_nodes_[idx].parents_.size() == 0) {\n      initial_frontier_.push_back(idx);\n    }\n  }\n  // Finally, start the workers.\n  int num_workers = net_def->has_num_workers() ? net_def->num_workers() : 1;\n  CAFFE_ENFORCE(num_workers > 0, \"Must have a positive number of workers.\");\n  if (num_workers == 1) {\n    LOG(WARNING) << \"Number of workers is 1: this means that all operators \"\n                 << \"will be executed sequentially. Did you forget to set \"\n                 << \"num_workers in the NetDef?\";\n  }\n  num_workers_ = num_workers;\n\n  for (int idx = 0; idx < operator_nodes_.size(); ++idx) {\n    if (operator_nodes_[idx].is_chain_start_) {\n      task_timers_[idx] = caffe2::make_unique<Timer>();\n    }\n  }\n  stats_.reserve(DeviceType::COMPILE_TIME_MAX_DEVICE_TYPES);\n  for (auto device_idx = 0;\n       device_idx < DeviceType::COMPILE_TIME_MAX_DEVICE_TYPES;\n       ++device_idx) {\n    stats_.emplace_back(\n        \"dag_net/stats/\" + net_def->name() + \"/\" +\n        caffe2::DeviceTypeName(device_idx));\n  }\n}\n\nDAGNetBase::~DAGNetBase() {\n  if (job_queue_) {\n    job_queue_->NoMoreJobs();\n    VLOG(1) << \"Joining workers.\";\n    for (auto& worker : workers_) {\n      worker.join();\n    }\n  }\n}\n\nbool DAGNetBase::DoRunAsync() {\n  StartAllObservers();\n\n  // Lock run_in_progress_ to prevent concurrent Run()s.\n  std::unique_lock<std::mutex> run_lock(run_in_progress_);\n  VLOG(1) << \"Running parallel net.\";\n  // First, set up job queue.\n  remaining_ops_ = operator_nodes_.size();\n  success_ = true;\n  iter_++;\n  if (!job_queue_) {\n    job_queue_ = caffe2::make_unique<SimpleQueue<int>>();\n  }\n  // Figure out number of workers to start.\n  auto num_workers_to_start = num_workers_ - workers_.size();\n\n  // Ensure the number of workers matches the defined in case\n  // any of the previously started threads terminated.\n  for (auto i = 0; i < num_workers_to_start; i++) {\n    VLOG(1) << \"Start worker #\" << workers_.size();\n    workers_.push_back(std::thread(&DAGNetBase::WorkerFunction, this));\n  }\n  // Initialize the runtime parent count.\n  for (auto& node : operator_nodes_) {\n    node.runtime_parent_count_ = node.parents_.size();\n  }\n  // Kickstart the job queue.\n  for (auto& value : initial_frontier_) {\n    if (FLAGS_caffe2_dag_net_collect_stats) {\n      task_timers_[value]->Start();\n    }\n    job_queue_->Push(value);\n  }\n  // Wait for failure or completed execution.\n  {\n    std::unique_lock<std::mutex> mutex_lock(remaining_ops_mutex_);\n    for (;;) {\n      if (remaining_ops_ == 0 || !success_) {\n        break;\n      }\n      cv_.wait(mutex_lock);\n    }\n  }\n  // Wait for all workers to terminate after failure.\n  // If there is a failure, it is unlikely that the net is executed\n  // again without modifications. Therefore it's easier to let the\n  // workers terminate here, versus adding a drain state to make the\n  // sure the job queue is cleared.\n  if (!success_) {\n    for (auto& worker : workers_) {\n      worker.join();\n    }\n    workers_.clear();\n    job_queue_.reset(nullptr);\n#ifdef CAFFE2_USE_EXCEPTION_PTR\n    if (caught_exception_) {\n      // Reset flag here in case Net gets run again\n      caught_exception_yet_ = false;\n      std::rethrow_exception(caught_exception_);\n    }\n#endif // CAFFE2_USE_EXCEPTION_PTR\n    return success_;\n  }\n  VLOG(2) << \"All ops finished running.\";\n  for (const auto& op : operator_nodes_) {\n    CAFFE_ENFORCE(\n        op.runtime_parent_count_ == 0,\n        \"Operator \",\n        op.operator_->debug_def().name(),\n        \"(\",\n        op.operator_->debug_def().type(),\n        \") has some runtime parents left.\");\n  }\n\n  StopAllObservers();\n  // If the above while loop finished, we know that the current run finished.\n  return success_;\n}\n\nvoid DAGNetBase::HandleException(\n    int operator_idx,\n    const std::string& exception_str) {\n  const std::string& operator_name =\n      operator_nodes_[operator_idx].operator_->debug_def().name();\n  const std::string& operator_type =\n      operator_nodes_[operator_idx].operator_->debug_def().type();\n  const char* prefix = \"Exception from operator '\";\n#ifdef CAFFE2_USE_EXCEPTION_PTR\n  if (!caught_exception_yet_.exchange(true)) {\n    caught_exception_ = std::current_exception();\n  } else {\n    prefix = \"Secondary exception from operator '\";\n  }\n#endif // CAFFE2_USE_EXCEPTION_PTR\n  LOG(ERROR) << prefix << operator_name << \"' (type '\" << operator_type\n             << \"'): \" << exception_str << \"\\n\";\n#ifndef CAFFE2_USE_EXCEPTION_PTR\n  throw; // Can't capture for dispatch to other thread, re-throw here\n#endif // CAFFE2_USE_EXCEPTION_PTR\n}\n\nvoid DAGNetBase::WorkerFunction() {\n  // WorkerFunctions() is an infinite loop until there are no more jobs to run.\n  while (true) {\n    int idx = 0;\n\n    // Return if there are no more operators to run (e.g. the\n    // DAGNetBase is destructing, or there was an error on another\n    // worker and we're cleaning up).\n    if (!job_queue_->Pop(&idx)) {\n      return;\n    }\n    if (FLAGS_caffe2_dag_net_collect_stats) {\n      auto device_option =\n          operator_nodes_[idx].operator_->event().GetDeviceOption();\n      CAFFE_EVENT(\n          stats_[device_option.device_type()],\n          task_pool_wait_time_us,\n          task_timers_[idx]->MicroSeconds());\n    }\n\n    VLOG(1) << \"Running operator #\" << idx << \" \"\n            << operator_nodes_[idx].operator_->debug_def().name() << \"(\"\n            << operator_nodes_[idx].operator_->debug_def().type() << \").\";\n    CAFFE_ENFORCE(\n        execution_chains_.find(idx) != execution_chains_.end(),\n        \"Can't find chain \",\n        idx,\n        \".\");\n    const auto& chain = execution_chains_[idx];\n    bool this_success = false;\n    try {\n      this_success = RunAt(idx, execution_chains_[idx]);\n\n      if (!this_success) {\n        // If an exception was thrown, the operator def will get printed\n        // by Operator::Run[Async], but if no exception occurs we print it here.\n        LOG(ERROR) << \"Operator chain failed: \"\n                   << ProtoDebugString(\n                          operator_nodes_[idx].operator_->debug_def());\n      }\n    } catch (std::exception& e) {\n      std::string exception_str = GetExceptionString(e);\n      HandleException(idx, exception_str);\n    } catch (...) {\n      std::string exception_str = \"Unknown exception\";\n      HandleException(idx, exception_str);\n    }\n\n    // Do book-keeping\n    std::vector<int> chains_to_queue;\n    for (const auto idx : chain) {\n      for (const auto child : operator_nodes_[idx].children_) {\n        const int count = --operator_nodes_[child].runtime_parent_count_;\n        CAFFE_ENFORCE(\n            count >= 0,\n            \"Found runtime parent count smaller than zero for \",\n            \"operator node \",\n            operator_nodes_[child].operator_->debug_def().name(),\n            \"(\",\n            operator_nodes_[child].operator_->debug_def().type(),\n            \").\");\n\n        if (count != 0) {\n          continue;\n        }\n\n        if (operator_nodes_[child].is_chain_start_) {\n          VLOG(2) << \"Pushing chain #\" << child << \" to queue.\";\n          chains_to_queue.push_back(child);\n        }\n      }\n    }\n\n    // Notify the caller of Run\n    {\n      std::unique_lock<std::mutex> mutex_lock(remaining_ops_mutex_);\n      remaining_ops_ -= chain.size();\n      CAFFE_ENFORCE(remaining_ops_ >= 0);\n      success_ &= this_success;\n      if (remaining_ops_ == 0 || !success_) {\n        cv_.notify_one();\n      }\n\n      // Terminate thread if this or any other operator chain failed.\n      if (!success_) {\n        job_queue_->NoMoreJobs();\n        return;\n      }\n\n      // Queue follow up operator chains.\n      // Can't do this inline because it can race with another thread\n      // calling NoMoreJobs(). So the lock needs to be held on push.\n      for (const auto idx : chains_to_queue) {\n        if (FLAGS_caffe2_dag_net_collect_stats) {\n          task_timers_[idx]->Start();\n        }\n        job_queue_->Push(idx);\n      }\n    }\n\n    VLOG(2) << \"Finished executing operator #\" << idx;\n  }\n}\n\nvector<float> DAGNetBase::TEST_Benchmark(\n    const int warmup_runs,\n    const int main_runs,\n    const bool run_individual) {\n  LOG(INFO) << \"Starting benchmark.\";\n  LOG(INFO) << \"Running warmup runs.\";\n  CAFFE_ENFORCE(\n      warmup_runs >= 0,\n      \"Number of warm up runs should be non negative, provided \",\n      warmup_runs,\n      \".\");\n  for (int i = 0; i < warmup_runs; ++i) {\n    CAFFE_ENFORCE(Run(), \"Warmup run \", i, \" has failed.\");\n  }\n\n  LOG(INFO) << \"Main runs.\";\n  CAFFE_ENFORCE(\n      main_runs >= 0,\n      \"Number of main runs should be non negative, provided \",\n      main_runs,\n      \".\");\n  Timer timer;\n  for (int i = 0; i < main_runs; ++i) {\n    CAFFE_ENFORCE(Run(), \"Main run \", i, \" has failed.\");\n  }\n  auto millis = timer.MilliSeconds();\n  LOG(INFO) << \"Main run finished. Milliseconds per iter: \"\n            << millis / main_runs\n            << \". Iters per second: \" << 1000.0 * main_runs / millis;\n\n  if (run_individual) {\n    LOG(INFO) << \"DAGNet does not do per-op benchmark. To do so, \"\n                 \"switch to a simple net type.\";\n  }\n  return vector<float>{millis / main_runs};\n}\n\nbool DAGNet::RunAt(int chain_id, const std::vector<int>& chain) {\n  for (const auto i : chain) {\n#ifdef CAFFE2_ENABLE_SDT\n    const auto& op_name =\n        operator_nodes_[i].operator_->debug_def().name().c_str();\n    const auto& op_type =\n        operator_nodes_[i].operator_->debug_def().type().c_str();\n    auto* op_ptr = operator_nodes_[i].operator_.get();\n    const auto& net_name = name_.c_str();\n    CAFFE_SDT(operator_start, net_name, op_name, op_type, op_ptr);\n#endif\n    const auto success = operator_nodes_[i].operator_->Run();\n#ifdef CAFFE2_ENABLE_SDT\n    CAFFE_SDT(operator_done, net_name, op_name, op_type, op_ptr);\n#endif\n    if (!success) {\n      return false;\n    }\n  }\n  if (FLAGS_caffe2_dag_net_collect_stats) {\n    auto device_option =\n        operator_nodes_[chain_id].operator_->event().GetDeviceOption();\n    CAFFE_EVENT(\n        stats_[device_option.device_type()],\n        task_time_to_succeeded_ms,\n        task_timers_[chain_id]->MilliSeconds());\n  }\n  return true;\n}\n\nREGISTER_NET(dag, DAGNet);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/net_dag.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_NET_DAG_H_\n#define CAFFE2_CORE_NET_DAG_H_\n\n#include <atomic>\n#include <climits>\n#include <cstddef>\n#include <thread> // NOLINT\n#include <typeinfo>\n#include <unordered_map>\n#include <vector>\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/net_dag_utils.h\"\n#include \"caffe2/core/observer.h\"\n#include \"caffe2/core/operator_schema.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/core/stats.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/simple_queue.h\"\n\nnamespace caffe2 {\n\nclass DAGNetBase : public NetBase {\n public:\n  DAGNetBase(const std::shared_ptr<const NetDef>& net_def, Workspace* ws);\n  ~DAGNetBase() override;\n\n  // WorkerFunction() is a function wrapper to allow us to run worker threads.\n  // It checks out one ready-to-run operator from the job queue, runs it,\n  // notifies all its children, and for any children that is ready, enqueues\n  // it to the job queue.\n  void WorkerFunction();\n  vector<float> TEST_Benchmark(\n      const int warmup_runs,\n      const int main_runs,\n      const bool run_individual) override;\n\n  const dag_utils::ExecutionChains& TEST_execution_chains() const {\n    return execution_chains_;\n  }\n\n  vector<OperatorBase*> GetOperators() const override {\n    return operators_;\n  }\n\n protected:\n  bool DoRunAsync() override;\n\n  virtual bool RunAt(int chain_id, const std::vector<int>& chain) = 0;\n  void HandleException(int operator_idx, const std::string& exception_str);\n\n  vector<dag_utils::OperatorNode> operator_nodes_;\n  vector<OperatorBase*> operators_;\n  dag_utils::ExecutionChains execution_chains_;\n  vector<int> initial_frontier_;\n  std::unique_ptr<SimpleQueue<int>> job_queue_;\n  std::vector<std::thread> workers_;\n  int num_workers_;\n  int remaining_ops_;\n\n  bool success_;\n  // Use an atomic to guard caught_exception_ so it is written to only once\n  std::atomic<bool> caught_exception_yet_;\n#ifdef CAFFE2_USE_EXCEPTION_PTR\n  std::exception_ptr caught_exception_;\n#endif // CAFFE2_USE_EXCEPTION_PTR\n  int iter_;\n  std::mutex remaining_ops_mutex_;\n  std::condition_variable cv_;\n  std::mutex run_in_progress_;\n\n  struct DAGNetStats {\n    CAFFE_STAT_CTOR(DAGNetStats);\n    CAFFE_AVG_EXPORTED_STAT(task_pool_wait_time_us);\n    CAFFE_AVG_EXPORTED_STAT(task_time_to_scheduled_us);\n    CAFFE_AVG_EXPORTED_STAT(task_time_to_succeeded_ms);\n    CAFFE_AVG_EXPORTED_STAT(task_wait_time_us);\n  };\n  mutable std::vector<DAGNetStats> stats_;\n  std::unordered_map<int, std::unique_ptr<Timer>> task_timers_;\n\n  DISABLE_COPY_AND_ASSIGN(DAGNetBase);\n};\n\nclass DAGNet : public DAGNetBase {\n public:\n  using DAGNetBase::DAGNetBase;\n\n protected:\n  bool RunAt(int chain_id, const std::vector<int>& chain) override;\n  bool SupportsAsync() override {\n    return false;\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_NET_DAG_H_\n"
  },
  {
    "path": "caffe2/core/net_dag_utils.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/net_dag_utils.h\"\n\n#include <set>\n#include <stack>\n#include <unordered_map>\n#include <unordered_set>\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/static_tracepoint.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\nnamespace dag_utils {\n\nnamespace {\nvoid prune(int node_idx, std::vector<OpGraphNode>& nodes) {\n  // Ancestor table for tracking the visited nodes\n  std::vector<bool> ancestors(nodes.size(), false);\n  // stack element is pair of <curr_node, previous_node>\n  std::stack<std::pair<int, int>> nodes_stack;\n  // initialize the prev_node to be -1\n  nodes_stack.push(std::make_pair(node_idx, -1));\n\n  while (!nodes_stack.empty()) {\n    const auto& node_pair = nodes_stack.top();\n    int curr = node_pair.first;\n    int prev = node_pair.second;\n\n    // If the node has already been visited, pop curr out of\n    // stack and clean up the ancestor table\n    CAFFE_ENFORCE(curr < ancestors.size(), \"Out of bound access\");\n    if (ancestors[curr]) {\n      ancestors[curr] = false;\n      nodes_stack.pop();\n      continue;\n    }\n\n    // Check if this has a parent that can be pruned:\n    //  if parent is not the previous node visited and is\n    //  an ancestor of the current traversar, it can be\n    //  pruned.\n    if (prev >= 0) {\n      std::vector<int> new_parents;\n      for (auto parent : nodes[curr].parents_) {\n        if (parent != prev && ancestors[parent]) {\n          // We can prune this one\n          nodes[parent].children_.erase(\n              std::remove(\n                  nodes[parent].children_.begin(),\n                  nodes[parent].children_.end(),\n                  curr),\n              nodes[parent].children_.end());\n        } else {\n          new_parents.push_back(parent);\n        }\n      }\n      nodes[curr].parents_ = new_parents;\n    }\n\n    ancestors[curr] = true;\n\n    // Descend -- but only once from each node\n    if (nodes[curr].visited_inputs == nodes[curr].num_orig_parents) {\n      const auto& children = nodes[curr].children_;\n      for (auto child : children) {\n        nodes[child].visited_inputs++;\n        nodes_stack.push(std::make_pair(child, curr));\n      }\n    }\n  }\n}\n\n/**\n * Prune redundant dependencies to improve chaining.\n * TODO: t15868555 This algorithm is fast but can miss dependencies.\n */\nstd::vector<OpGraphNode> pruneOpNodeGraph(\n    const std::vector<OperatorNode>& nodes) {\n  Timer t;\n  std::vector<OpGraphNode> pruned;\n\n  // Create a separate list of pruned operatornodes used\n  // for the chaining computation. Because of the unique_ptr\n  // in the OperatorNode, we cannot do a copy but have to\n  // copy just the fields we need.\n  for (auto& node : nodes) {\n    OpGraphNode nd;\n    nd.children_ = node.children_;\n    nd.parents_ = node.parents_;\n    nd.num_orig_parents = nd.parents_.size();\n    pruned.push_back(nd);\n  }\n\n  for (int i = 0; i < pruned.size(); ++i) {\n    if (pruned[i].parents_.size() == 0) {\n      prune(i, pruned);\n    }\n  }\n\n  LOG(INFO) << \"Operator graph pruning prior to chain compute took: \"\n            << t.Seconds() << \" secs\";\n  return pruned;\n}\n\nvoid updateOperatorNodes(\n    std::vector<OperatorNode>& nodes,\n    const ExecutionChains& chains) {\n  for (int i = 0; i < nodes.size(); ++i) {\n    auto& node = nodes[i];\n    if (chains.find(i) != chains.end()) {\n      node.is_chain_start_ = true;\n    } else {\n      node.is_chain_start_ = false;\n    }\n    node.runtime_parent_count_ = 0;\n  }\n}\n} // namespace\n\nExecutionChains computeChains(std::vector<OperatorNode>& orig_nodes) {\n  const std::vector<OpGraphNode> nodes = pruneOpNodeGraph(orig_nodes);\n  vector<int> initial_frontier;\n  for (int idx = 0; idx < nodes.size(); ++idx) {\n    if (nodes[idx].parents_.size() == 0) {\n      initial_frontier.push_back(idx);\n    }\n  }\n\n  // We need to construct the node_seen_count to know how many inner edges each\n  // node has.\n  std::unordered_map<int, int> node_seen_count;\n\n  for (int root_index : initial_frontier) {\n    const auto& root = nodes[root_index];\n    std::stack<std::pair<int, std::vector<int>::const_iterator>> depth_stack;\n    depth_stack.push(make_pair(root_index, root.children_.begin()));\n    node_seen_count[root_index]++;\n    CAFFE_ENFORCE(\n        node_seen_count[root_index] == 1,\n        \"root node \",\n        root_index,\n        \" visit count must be == 1\");\n\n    while (depth_stack.size() > 0) {\n      auto cur = depth_stack.top();\n      depth_stack.pop();\n      if (cur.second != nodes[cur.first].children_.end()) {\n        int node_index = *cur.second;\n        node_seen_count[node_index]++;\n        cur.second++;\n        depth_stack.push(cur);\n        if (node_seen_count[node_index] == 1) {\n          // Visit each child only once.\n          depth_stack.push(\n              make_pair(node_index, nodes[node_index].children_.begin()));\n        }\n      }\n    }\n  }\n  // Now, we compute the set of execution chains An execution chain is\n  // a linear set of nodes that can be executed on a single stream\n  // (e.g. a chain of single input, single output operators)\n  ExecutionChains chains;\n  std::unordered_set<int> seen_nodes;\n  std::vector<int> chain;\n  std::pair<int, std::vector<int>::const_iterator> cur;\n  std::stack<std::pair<int, std::vector<int>::const_iterator>> depth_stack;\n  auto check_current_for_chaining = [&]() -> bool {\n    return (\n        node_seen_count[cur.first] == 1 &&\n        (chain.size() == 0 ||\n         (\n             // A chain of operators is executed without additional\n             // synchronization by calling RunAsync sequentially on each\n             // operator and passing the same stream id on each call.\n             // RunAsync may schedule an async computation on device.\n             // In order to be scheduled on the same chain two operators\n             // (parent and dependent) need to satisfy:\n             //  1. Both ops are on the same device _and_\n             //  2. Parent op does not have an async part or\n             //     dependent op can be executed as an async dependency\n\n             IsSameDevice(\n                 orig_nodes[cur.first].operator_->device_option(),\n                 orig_nodes[chain.back()].operator_->device_option()) &&\n             (!orig_nodes[chain.back()].operator_->HasAsyncPart() ||\n              orig_nodes[cur.first].operator_->SupportsAsyncScheduling()))));\n  };\n  auto commit_chain = [&]() {\n    if (chain.size() > 0) {\n      CAFFE_ENFORCE(\n          chains.insert({chain.front(), chain}).second,\n          \"Chain \",\n          chain.front(),\n          \" was already added.\");\n      VLOG(2) << \"Added chain: \" << chain.front() << \"with elements\";\n      for (auto ch : chain) {\n        VLOG(2) << ch << \", \";\n      }\n      chain.clear();\n    }\n  };\n  auto depth_traverse = [&]() {\n    while (cur.second != nodes[cur.first].children_.end() &&\n           seen_nodes.find(*cur.second) != seen_nodes.end()) {\n      cur.second++;\n    }\n\n    if (cur.second != nodes[cur.first].children_.end()) {\n      auto next = make_pair(*cur.second, nodes[*cur.second].children_.begin());\n      depth_stack.push(cur);\n      depth_stack.push(next);\n    }\n  };\n  for (int root_index : initial_frontier) {\n    depth_stack.push(\n        make_pair(root_index, nodes[root_index].children_.begin()));\n    while (depth_stack.size() > 0) {\n      cur = depth_stack.top();\n      depth_stack.pop();\n      if (seen_nodes.find(cur.first) == seen_nodes.end()) {\n        seen_nodes.insert(cur.first);\n        // Has one child, can be candidate for chain or can be added to the\n        // previous chain.\n        if (nodes[cur.first].children_.size() == 1) {\n          if (check_current_for_chaining()) {\n            // Add oneself to the current chain.\n            VLOG(1) << \"Adding to existing chain\" << cur.first;\n            chain.push_back(cur.first);\n            int index = *nodes[cur.first].children_.begin();\n            depth_stack.push(make_pair(index, nodes[index].children_.begin()));\n          } else {\n            // Can't belong to the previous chain, commit previous chain and\n            // start a new one.\n            commit_chain();\n            chain.push_back(cur.first);\n            int index = *nodes[cur.first].children_.begin();\n            depth_stack.push(make_pair(index, nodes[index].children_.begin()));\n          }\n        } else if (\n            nodes[cur.first].children_.size() == 0 &&\n            check_current_for_chaining()) {\n          // Add current node to the current chain and commit.\n          chain.push_back(cur.first);\n          commit_chain();\n        } else {\n          // Node has more than one child.\n          commit_chain();\n          // Add current node as an independent chain since it won't be a part\n          // of a bigger chain.\n          chain.push_back(cur.first);\n          commit_chain();\n          depth_traverse();\n        }\n      } else {\n        // This node has been seen before, we will only traverse its children.\n        // Commit any pending chains and continue traversing.\n        commit_chain();\n        depth_traverse();\n      }\n    } // End while\n\n    // Check if this if is even needed.\n    commit_chain();\n  }\n  CAFFE_ENFORCE(\n      seen_nodes.size() == nodes.size(),\n      \"Haven't seen all the nodes, expected number of nodes \",\n      nodes.size(),\n      \", but seen only \",\n      seen_nodes.size(),\n      \".\");\n\n  updateOperatorNodes(orig_nodes, chains);\n  return chains;\n}\n\nExecutionChains singleChains(std::vector<OperatorNode>& nodes) {\n  ExecutionChains chains;\n  for (auto i = 0; i < nodes.size(); ++i) {\n    chains[i] = {i};\n  }\n  updateOperatorNodes(nodes, chains);\n  return chains;\n}\n\nstd::vector<OperatorNode> prepareOperatorNodes(\n    const std::shared_ptr<const NetDef>& net_def,\n    Workspace* ws) {\n  std::vector<OperatorNode> operator_nodes(net_def->op_size());\n  std::map<string, int> blob_creator;\n  std::map<string, std::set<int>> blob_readers;\n  bool net_def_has_device_option = net_def->has_device_option();\n  // Initialize the operators\n  for (int idx = 0; idx < net_def->op_size(); ++idx) {\n    const OperatorDef& op_def = net_def->op(idx);\n    VLOG(1) << \"Creating operator #\" << idx << \": \" << op_def.name() << \": \"\n            << op_def.type();\n    if (!op_def.has_device_option() && net_def_has_device_option) {\n      OperatorDef temp_def(op_def);\n      temp_def.mutable_device_option()->CopyFrom(net_def->device_option());\n      operator_nodes[idx].operator_ = CreateOperator(temp_def, ws, idx);\n    } else {\n      auto op = CreateOperator(op_def, ws, idx);\n      op->set_debug_def(\n          std::shared_ptr<const OperatorDef>{net_def, &(net_def->op(idx))});\n      operator_nodes[idx].operator_ = std::move(op);\n    }\n    // Check the inputs, and set up parents if necessary. This addressese the\n    // read after write case.\n    auto checkInputs =\n        [&](const google::protobuf::RepeatedPtrField<std::string>& inputs) {\n          for (const string& input : inputs) {\n            if (blob_creator.count(input) == 0) {\n              VLOG(1) << \"Input \" << input << \" not produced by this net. \"\n                      << \"Assuming it is pre-existing.\";\n            } else {\n              int parent = blob_creator[input];\n              VLOG(1) << \"op dependency (RaW \" << input << \"): \" << parent\n                      << \"->\" << idx;\n              operator_nodes[idx].parents_.push_back(parent);\n              operator_nodes[parent].children_.push_back(idx);\n            }\n            // Add the current idx to the readers of this input.\n            blob_readers[input].insert(idx);\n          }\n        };\n    checkInputs(op_def.input());\n    checkInputs(op_def.control_input());\n\n    // Check the outputs.\n    for (const string& output : op_def.output()) {\n      if (blob_creator.count(output) != 0) {\n        // This addresses the write after write case - we will assume that all\n        // writes are inherently sequential.\n        int waw_parent = blob_creator[output];\n        VLOG(1) << \"op dependency (WaW \" << output << \"): \" << waw_parent\n                << \"->\" << idx;\n        operator_nodes[idx].parents_.push_back(waw_parent);\n        operator_nodes[waw_parent].children_.push_back(idx);\n      }\n      // This addresses the write after read case - we will assume that writes\n      // should only occur after all previous reads are finished.\n      for (const int war_parent : blob_readers[output]) {\n        VLOG(1) << \"op dependency (WaR \" << output << \"): \" << war_parent\n                << \"->\" << idx;\n        operator_nodes[idx].parents_.push_back(war_parent);\n        operator_nodes[war_parent].children_.push_back(idx);\n      }\n      // Renew the creator of the output name.\n      blob_creator[output] = idx;\n      // The write would create an implicit barrier that all earlier readers of\n      // this output is now parents of the current op, and future writes would\n      // not need to depend on these earlier readers. Thus, we can clear up the\n      // blob readers.\n      blob_readers[output].clear();\n    }\n  }\n\n  // Now, make sure that the parent list and the children list do not contain\n  // duplicated items.\n  for (int i = 0; i < operator_nodes.size(); ++i) {\n    auto& node = operator_nodes[i];\n    // Sort, remove duplicates, and delete self dependency.\n    auto& p = node.parents_;\n    std::sort(p.begin(), p.end());\n    p.erase(std::unique(p.begin(), p.end()), p.end());\n    p.erase(std::remove(p.begin(), p.end(), i), p.end());\n    // Do the same for the children vector.\n    auto& c = node.children_;\n    std::sort(c.begin(), c.end());\n    c.erase(std::unique(c.begin(), c.end()), c.end());\n    c.erase(std::remove(c.begin(), c.end(), i), c.end());\n  }\n\n  return operator_nodes;\n}\n\nstd::vector<OpGraphNode> prepareChainGraphNodes(\n    const std::vector<dag_utils::OperatorNode>& operator_nodes,\n    const std::vector<std::vector<int>>& execution_chains) {\n  std::unordered_map<int, int> op_to_chain_idx;\n  for (int chain_idx = 0; chain_idx < execution_chains.size(); ++chain_idx) {\n    const auto& chain_indices = execution_chains[chain_idx];\n    for (const auto& chain_op_idx : chain_indices) {\n      CAFFE_ENFORCE(!op_to_chain_idx.count(chain_op_idx));\n      op_to_chain_idx[chain_op_idx] = chain_idx;\n    }\n  }\n\n  std::vector<OpGraphNode> chain_nodes(execution_chains.size());\n  for (int op_idx = 0; op_idx < operator_nodes.size(); ++op_idx) {\n    CAFFE_ENFORCE(op_to_chain_idx.count(op_idx));\n    auto chain_idx = op_to_chain_idx[op_idx];\n    auto& chain = chain_nodes[chain_idx];\n    auto& op_node = operator_nodes[op_idx];\n\n    for (const auto& child_idx : op_node.children_) {\n      CAFFE_ENFORCE(op_to_chain_idx.count(child_idx));\n      auto child_chain_idx = op_to_chain_idx[child_idx];\n      if (child_chain_idx != chain_idx) {\n        auto it = std::find(\n            chain.children_.begin(), chain.children_.end(), child_chain_idx);\n        if (it == chain.children_.end()) {\n          chain.children_.push_back(child_chain_idx);\n        }\n      }\n    }\n\n    for (const auto& parent_idx : op_node.parents_) {\n      CAFFE_ENFORCE(op_to_chain_idx.count(parent_idx));\n      auto parent_chain_idx = op_to_chain_idx[parent_idx];\n      if (parent_chain_idx != chain_idx) {\n        auto it = std::find(\n            chain.parents_.begin(), chain.parents_.end(), parent_chain_idx);\n        if (it == chain.parents_.end()) {\n          chain.parents_.push_back(parent_chain_idx);\n        }\n      }\n    }\n  }\n\n  return chain_nodes;\n}\n\n} // namespace dag_utils\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/net_dag_utils.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_NET_DAG_UTILS_H_\n#define CAFFE2_CORE_NET_DAG_UTILS_H_\n\n#include <atomic>\n#include <climits>\n#include <cstddef>\n#include <thread> // NOLINT\n#include <typeinfo>\n#include <unordered_map>\n#include <unordered_set>\n#include <vector>\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/observer.h\"\n#include \"caffe2/core/operator_schema.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/simple_queue.h\"\n\nnamespace caffe2 {\nnamespace dag_utils {\n\nstruct OperatorNode {\n  unique_ptr<OperatorBase> operator_;\n  vector<int> children_;\n  vector<int> parents_;\n  std::atomic<int> runtime_parent_count_;\n  bool is_chain_start_ = false;\n};\n\nstruct OpGraphNode {\n  vector<int> children_;\n  vector<int> parents_;\n  int visited_inputs = 0;\n  int num_orig_parents;\n};\n\nusing ExecutionChains = std::unordered_map<int, std::vector<int>>;\n\nExecutionChains computeChains(std::vector<OperatorNode>& orig_nodes);\n\nExecutionChains singleChains(std::vector<OperatorNode>& nodes);\n\nstd::vector<OperatorNode> prepareOperatorNodes(\n    const std::shared_ptr<const NetDef>& net_def,\n    Workspace* ws);\n\nstd::vector<OpGraphNode> prepareChainGraphNodes(\n    const std::vector<dag_utils::OperatorNode>& operator_nodes,\n    const std::vector<std::vector<int>>& execution_chains);\n\n} // namespace dag_utils\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_NET_DAG_UTILS_H_\n"
  },
  {
    "path": "caffe2/core/net_gpu_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <google/protobuf/text_format.h>\n#include <gtest/gtest.h>\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/net_dag.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/scope_guard.h\"\n\nCAFFE2_DECLARE_bool(caffe2_disable_chaining);\n\nnamespace caffe2 {\n\nnamespace {\n\nstatic std::atomic<int> counter;\n\n// A net test dummy op that does nothing but scaffolding. Here, we\n// inherit from OperatorBase because we instantiate on both CPU and\n// GPU. In general, you want to only inherit from Operator<Context>.\nclass NetTestDummyOp final : public OperatorBase {\n public:\n  using OperatorBase::OperatorBase;\n\n  NetTestDummyOp(const OperatorDef& operator_def, Workspace* ws)\n      : OperatorBase(operator_def, ws),\n        fail_(OperatorBase::GetSingleArgument<bool>(\"fail\", false)) {}\n\n  bool Run(int /* unused */ /*stream_id*/) override {\n    if (fail_) {\n      return false;\n    }\n    counter.fetch_add(1);\n    return true;\n  }\n\n  // Simulate CUDA operator behavior\n  bool HasAsyncPart() const override {\n    return debug_def().device_option().device_type() == CUDA;\n  }\n\n  bool SupportsAsyncScheduling() const override {\n    return debug_def().device_option().device_type() == CUDA;\n  }\n\n protected:\n  const bool fail_;\n};\n\nREGISTER_CPU_OPERATOR(NetTestDummy, NetTestDummyOp);\nREGISTER_CUDA_OPERATOR(NetTestDummy, NetTestDummyOp);\nREGISTER_CPU_OPERATOR(NetTestDummy2, NetTestDummyOp);\nREGISTER_CUDA_OPERATOR(NetTestDummy2, NetTestDummyOp);\n\nOPERATOR_SCHEMA(NetTestDummy)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\nOPERATOR_SCHEMA(NetTestDummy2)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{1, 0}});\n\n}  // namespace\n\nvoid testExecution(std::unique_ptr<NetBase>& net, int num_ops) {\n  // Run 100 times\n  for (int i = 0; i < 100; i++) {\n    counter.exchange(0);\n    net.get()->Run();\n    ASSERT_EQ(num_ops, counter.load());\n  }\n}\n\nvoid checkChainingAndRun(\n    const char* spec,\n    const dag_utils::ExecutionChains& expected) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef net_def;\n  CAFFE_ENFORCE(google::protobuf::TextFormat::ParseFromString(spec, &net_def));\n  {\n    net_def.set_num_workers(4);\n    auto old = FLAGS_caffe2_disable_chaining;\n    auto g = MakeGuard([&]() { FLAGS_caffe2_disable_chaining = old; });\n    FLAGS_caffe2_disable_chaining = false;\n\n    std::unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n    auto* dag = dynamic_cast_if_rtti<DAGNetBase*>(net.get());\n    CHECK_NOTNULL(dag);\n    const auto& chains = dag->TEST_execution_chains();\n    EXPECT_EQ(chains, expected);\n    testExecution(net, net_def.op().size());\n  }\n}\n\nTEST(NetTest, DISABLED_ChainingForDifferentDevices) {\n  const auto spec = R\"DOC(\n        name: \"example\"\n        type: \"dag\"\n        external_input: \"in\"\n        op {\n          input: \"in\"\n          output: \"hidden\"\n          type: \"NetTestDummy\"\n        }\n        op {\n          input: \"hidden\"\n          output: \"out\"\n          type: \"NetTestDummy\"\n          device_option {\n            device_type: 1\n          }\n        }\n        op {\n          input: \"out\"\n          output: \"out2\"\n          type: \"NetTestDummy\"\n          device_option {\n            device_type: 1\n          }\n        }\n        op {\n          input: \"out2\"\n          output: \"out3\"\n          type: \"NetTestDummy\"\n          device_option {\n            device_type: 1\n            cuda_gpu_id: 1\n          }\n        }\n)DOC\";\n  if (HasCudaGPU() && NumCudaDevices() >= 2) {\n    checkChainingAndRun(spec, {{0, {0, 1, 2}}, {3, {3}}});\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/net_simple.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/net_simple.h\"\n#include \"caffe2/core/net.h\"\n\n#include <set>\n#include <unordered_map>\n#include <unordered_set>\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/static_tracepoint.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\nSimpleNet::SimpleNet(\n    const std::shared_ptr<const NetDef>& net_def,\n    Workspace* ws)\n    : NetBase(net_def, ws) {\n  VLOG(1) << \"Constructing SimpleNet \" << net_def->name();\n  const bool net_def_has_device_option = net_def->has_device_option();\n  // Initialize the operators\n  for (int idx = 0; idx < net_def->op_size(); ++idx) {\n    const auto& operator_def = net_def->op(idx);\n    VLOG(1) << \"Creating operator \" << operator_def.name() << \": \"\n            << operator_def.type();\n    std::unique_ptr<OperatorBase> op{nullptr};\n    if (!operator_def.has_device_option() && net_def_has_device_option) {\n      // In the case that the operator def does not specify a device option but\n      // the net def has a default option, we copy the device option over to the\n      // operator def.\n      OperatorDef temp_def(operator_def);\n      temp_def.mutable_device_option()->CopyFrom(net_def->device_option());\n      op = CreateOperator(temp_def, ws, idx);\n    } else {\n      op = CreateOperator(operator_def, ws, idx);\n      op->set_debug_def(\n          std::shared_ptr<const OperatorDef>{net_def, &(net_def->op(idx))});\n    }\n    operators_.emplace_back(std::move(op));\n  }\n}\n\nbool SimpleNet::Run() {\n  StartAllObservers();\n  VLOG(1) << \"Running net \" << name_;\n  for (auto& op : operators_) {\n    VLOG(1) << \"Running operator \" << op->debug_def().name() << \"(\"\n            << op->debug_def().type() << \").\";\n#ifdef CAFFE2_ENABLE_SDT\n    const auto& op_name = op->debug_def().name().c_str();\n    const auto& op_type = op->debug_def().type().c_str();\n    auto* op_ptr = op.get();\n    const auto& net_name = name_.c_str();\n    CAFFE_SDT(operator_start, net_name, op_name, op_type, op_ptr);\n#endif\n    bool res = op->Run();\n#ifdef CAFFE2_ENABLE_SDT\n    CAFFE_SDT(operator_done, net_name, op_name, op_type, op_ptr);\n#endif\n    if (!res) {\n      LOG(ERROR) << \"Operator failed: \" << ProtoDebugString(op->debug_def());\n      return false;\n    }\n  }\n  StopAllObservers();\n  return true;\n}\n\nbool SimpleNet::RunAsync() {\n  return Run();\n}\n\nnamespace {\ntemplate <typename A, typename B>\nbool PairLargerThan(const std::pair<A, B>& x, const std::pair<A, B>& y) {\n  return x.second > y.second;\n}\n}\n\nvector<float> SimpleNet::TEST_Benchmark(\n    const int warmup_runs,\n    const int main_runs,\n    const bool run_individual) {\n  LOG(INFO) << \"Starting benchmark.\";\n  LOG(INFO) << \"Running warmup runs.\";\n  CAFFE_ENFORCE(\n      warmup_runs >= 0,\n      \"Number of warm up runs should be non negative, provided \",\n      warmup_runs,\n      \".\");\n  for (int i = 0; i < warmup_runs; ++i) {\n    CAFFE_ENFORCE(Run(), \"Warmup run \", i, \" has failed.\");\n  }\n\n  LOG(INFO) << \"Main runs.\";\n  CAFFE_ENFORCE(\n      main_runs >= 0,\n      \"Number of main runs should be non negative, provided \",\n      main_runs,\n      \".\");\n  Timer timer;\n  for (int i = 0; i < main_runs; ++i) {\n    CAFFE_ENFORCE(Run(), \"Main run \", i, \" has failed.\");\n  }\n  auto millis = timer.MilliSeconds();\n  LOG(INFO) << \"Main run finished. Milliseconds per iter: \"\n            << millis / main_runs\n            << \". Iters per second: \" << 1000.0 * main_runs / millis;\n\n  vector<float> time_per_op(operators_.size(), 0);\n  vector<uint64_t> flops_per_op;\n  vector<uint64_t> memory_bytes_per_op;\n  vector<uint64_t> param_bytes_per_op;\n  CaffeMap<string, float> time_per_op_type;\n  CaffeMap<string, float> flops_per_op_type;\n  CaffeMap<string, float> memory_bytes_per_op_type;\n  CaffeMap<string, float> param_bytes_per_op_type;\n  if (run_individual) {\n    for (int i = 0; i < main_runs; ++i) {\n      for (auto& op : operators_) {\n        op->ResetEvent();\n      }\n      int idx = 0;\n      for (auto& op : operators_) {\n        const string& op_type = op->debug_def().type();\n        if (i == 0) { // Gather flops on the first run.\n          auto* schema = OpSchemaRegistry::Schema(op_type);\n          if (schema && schema->HasCostInferenceFunction()) {\n            vector<TensorShape> shapes = op->InputTensorShapes();\n\n            OpSchema::Cost cost = schema->InferCost(op->debug_def(), shapes);\n\n            flops_per_op.emplace_back(cost.flops);\n            memory_bytes_per_op.emplace_back(cost.bytes_moved);\n            param_bytes_per_op.emplace_back(cost.params_bytes);\n\n            flops_per_op_type[op_type] += cost.flops;\n            memory_bytes_per_op_type[op_type] += cost.bytes_moved;\n            param_bytes_per_op_type[op_type] += cost.params_bytes;\n          }\n        }\n        timer.Start();\n        CAFFE_ENFORCE(\n            op->Run(),\n            \"operator \",\n            op->debug_def().name(),\n            \"(\",\n            op_type,\n            \") has failed.\");\n        float spent = timer.MilliSeconds();\n        time_per_op[idx] += spent;\n        time_per_op_type[op_type] += spent;\n        ++idx;\n      }\n    }\n    int idx = 0;\n    for (auto& op : operators_) {\n      const string& op_type = op->debug_def().type();\n      const string& print_name =\n          (op->debug_def().name().size()\n               ? op->debug_def().name()\n               : (op->debug_def().output_size() ? op->debug_def().output(0)\n                                                : \"NO_OUTPUT\"));\n      std::stringstream flops_str;\n      if (idx < flops_per_op.size() && flops_per_op[idx]) {\n        flops_str << \" (\" << to_string(1.0e-9 * flops_per_op[idx]) << \" GFLOP, \"\n                  << to_string(1.0e-6 * flops_per_op[idx] / time_per_op[idx])\n                  << \" GFLOPS)\";\n      }\n      std::stringstream memory_bytes_str;\n      if (idx < memory_bytes_per_op.size() && memory_bytes_per_op[idx]) {\n        memory_bytes_str << \" (\" << to_string(1.0e-6 * memory_bytes_per_op[idx])\n                         << \" MB)\";\n      }\n      std::stringstream param_bytes_str;\n      if (idx < param_bytes_per_op.size() && param_bytes_per_op[idx]) {\n        memory_bytes_str << \" (\" << to_string(1.0e-6 * param_bytes_per_op[idx])\n                         << \" MB)\";\n      }\n      LOG(INFO) << \"Operator #\" << idx << \" (\" << print_name << \", \" << op_type\n                << \") \" << time_per_op[idx] / main_runs << \" ms/iter\"\n                << flops_str.str() << memory_bytes_str.str()\n                << param_bytes_str.str();\n      ++idx;\n    }\n    const std::vector<string> metric(\n        {\"Time\", \"FLOP\", \"Feature Memory\", \"Parameter Memory\"});\n    const std::vector<double> normalizer(\n        {1.0 / main_runs, 1.0e-9, 1.0e-6, 1.0e-6});\n    const std::vector<string> unit({\"ms\", \"GFLOP\", \"MB\", \"MB\"});\n\n    std::vector<CaffeMap<string, float>*> metric_per_op_type_vec_vec;\n    metric_per_op_type_vec_vec.emplace_back(&time_per_op_type);\n    metric_per_op_type_vec_vec.emplace_back(&flops_per_op_type);\n    metric_per_op_type_vec_vec.emplace_back(&memory_bytes_per_op_type);\n    metric_per_op_type_vec_vec.emplace_back(&param_bytes_per_op_type);\n    for (int i = 0; i < metric_per_op_type_vec_vec.size(); ++i) {\n      LOG(INFO) << metric[i] << \" per operator type:\";\n      auto* item = metric_per_op_type_vec_vec[i];\n      std::vector<std::pair<string, float>> metric_per_op_type_vec(\n          (*item).begin(), (*item).end());\n      std::sort(\n          metric_per_op_type_vec.begin(),\n          metric_per_op_type_vec.end(),\n          PairLargerThan<string, float>);\n      float total_metric = 0.;\n      for (const auto& op_item : metric_per_op_type_vec) {\n        total_metric += op_item.second * normalizer[i];\n      }\n      for (const auto& op_item : metric_per_op_type_vec) {\n        float percent = 0.;\n        if (total_metric > 0.) {\n          percent = (100.0 * op_item.second * normalizer[i] / total_metric);\n        }\n        LOG(INFO) << std::setw(15) << std::setfill(' ')\n                  << op_item.second * normalizer[i] << \" \" << unit[i] << \". \"\n                  << std::setw(10) << std::setfill(' ') << percent << \"%. \"\n                  << op_item.first;\n      }\n      LOG(INFO) << std::setw(15) << std::setfill(' ') << total_metric << \" \"\n                << unit[i] << \" in Total\";\n    }\n  }\n  // We will reuse time_per_op to return the result of BenchmarkNet.\n  for (int i = 0; i < time_per_op.size(); ++i) {\n    time_per_op[i] /= main_runs;\n  }\n  time_per_op.insert(time_per_op.begin(), millis / main_runs);\n  return time_per_op;\n}\n\nREGISTER_NET(simple, SimpleNet);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/net_simple.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_NET_SIMPLE_H_\n#define CAFFE2_CORE_NET_SIMPLE_H_\n\n#include <vector>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\n// This is the very basic structure you need to run a network - all it\n// does is simply to run everything in sequence. If you want more fancy control\n// such as a DAG-like execution, check out other better net implementations.\nclass SimpleNet : public NetBase {\n public:\n  SimpleNet(const std::shared_ptr<const NetDef>& net_def, Workspace* ws);\n  bool SupportsAsync() override {\n    return false;\n  }\n\n  vector<float> TEST_Benchmark(\n      const int warmup_runs,\n      const int main_runs,\n      const bool run_individual) override;\n\n  /*\n   * This returns a list of pointers to objects stored in unique_ptrs.\n   * Used by Observers.\n   *\n   * Think carefully before using.\n   */\n  vector<OperatorBase*> GetOperators() const override {\n    vector<OperatorBase*> op_list;\n    for (auto& op : operators_) {\n      op_list.push_back(op.get());\n    }\n    return op_list;\n  }\n\n protected:\n  bool Run() override;\n  bool RunAsync() override;\n\n  vector<unique_ptr<OperatorBase>> operators_;\n\n  DISABLE_COPY_AND_ASSIGN(SimpleNet);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_NET_SIMPLE_H_\n"
  },
  {
    "path": "caffe2/core/net_simple_async.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/net_simple_async.h\"\n#include \"caffe2/core/net.h\"\n\n#include <set>\n#include <unordered_map>\n#include <unordered_set>\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/static_tracepoint.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\nAsyncSimpleNet::AsyncSimpleNet(\n    const std::shared_ptr<const NetDef>& net_def,\n    Workspace* ws)\n    : NetBase(net_def, ws) {\n  VLOG(1) << \"Constructing AsyncSimpleNet \" << net_def->name();\n  const bool net_def_has_device_option = net_def->has_device_option();\n  // Initialize the operators\n  const DeviceOption* first_device_option = nullptr;\n  const DeviceOption* current_device_option;\n  for (int idx = 0; idx < net_def->op_size(); ++idx) {\n    const auto& operator_def = net_def->op(idx);\n    VLOG(1) << \"Creating operator \" << operator_def.name() << \": \"\n            << operator_def.type();\n    std::unique_ptr<OperatorBase> op{nullptr};\n    if (!operator_def.has_device_option() && net_def_has_device_option) {\n      // In the case that the operator def does not specify a device option but\n      // the net def has a default option, we copy the device option over to the\n      // operator def.\n      OperatorDef temp_def(operator_def);\n      temp_def.mutable_device_option()->CopyFrom(net_def->device_option());\n      op = CreateOperator(temp_def, ws, idx);\n      current_device_option = &net_def->device_option();\n    } else {\n      op = CreateOperator(operator_def, ws, idx);\n      op->set_debug_def(\n          std::shared_ptr<const OperatorDef>{net_def, &(net_def->op(idx))});\n      current_device_option = &operator_def.device_option();\n    }\n    if (!first_device_option) {\n      first_device_option = current_device_option;\n    } else {\n      CAFFE_ENFORCE(\n          IsSameDevice(*first_device_option, *current_device_option),\n          \"AsyncSimpleNet supports only single device networks\");\n    }\n    operators_.emplace_back(std::move(op));\n  }\n  events_ = {&operators_.back()->event()};\n}\n\nbool AsyncSimpleNet::DoRunAsync() {\n  StartAllObservers();\n\n  VLOG(1) << \"Running net \" << name_;\n  for (auto& op : operators_) {\n    VLOG(1) << \"Running operator \" << op->debug_def().name() << \"(\"\n            << op->debug_def().type() << \").\";\n#ifdef CAFFE2_ENABLE_SDT\n    const auto& op_name = op->debug_def().name().c_str();\n    const auto& op_type = op->debug_def().type().c_str();\n    auto* op_ptr = op.get();\n    const auto& net_name = name_.c_str();\n    CAFFE_SDT(operator_start, net_name, op_name, op_type, op_ptr);\n#endif\n    bool res = op->RunAsync();\n#ifdef CAFFE2_ENABLE_SDT\n    CAFFE_SDT(operator_done, net_name, op_name, op_type, op_ptr);\n#endif\n    if (!res) {\n      LOG(ERROR) << \"Operator failed: \" << ProtoDebugString(op->debug_def());\n      return false;\n    }\n  }\n  StopAllObservers();\n  return true;\n}\n\nvector<float> AsyncSimpleNet::TEST_Benchmark(\n    const int warmup_runs,\n    const int main_runs,\n    const bool run_individual) {\n  LOG(INFO) << \"Starting benchmark.\";\n  LOG(INFO) << \"Running warmup runs.\";\n  CAFFE_ENFORCE(\n      warmup_runs >= 0,\n      \"Number of warm up runs should be non negative, provided \",\n      warmup_runs,\n      \".\");\n  for (int i = 0; i < warmup_runs; ++i) {\n    CAFFE_ENFORCE(Run(), \"Warmup run \", i, \" has failed.\");\n  }\n\n  LOG(INFO) << \"Main runs.\";\n  CAFFE_ENFORCE(\n      main_runs >= 0,\n      \"Number of main runs should be non negative, provided \",\n      main_runs,\n      \".\");\n  Timer timer;\n  for (int i = 0; i < main_runs; ++i) {\n    CAFFE_ENFORCE(Run(), \"Main run \", i, \" has failed.\");\n  }\n  auto millis = timer.MilliSeconds();\n  LOG(INFO) << \"Main run finished. Milliseconds per iter: \"\n            << millis / main_runs\n            << \". Iters per second: \" << 1000.0 * main_runs / millis;\n\n  if (run_individual) {\n    LOG(INFO) << \"AsyncSimpleNet does not do per-op benchmark. To do so, \"\n                 \"switch to a simple net type.\";\n  }\n  return vector<float>{millis / main_runs};\n}\n\nREGISTER_NET(async_simple, AsyncSimpleNet);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/net_simple_async.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_NET_SIMPLE_ASYNC_H_\n#define CAFFE2_CORE_NET_SIMPLE_ASYNC_H_\n\n#include <vector>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\n// This is the very basic structure you need to run a network - all it\n// does is simply to run everything in sequence. If you want more fancy control\n// such as a DAG-like execution, check out other better net implementations.\nclass AsyncSimpleNet : public NetBase {\n public:\n  AsyncSimpleNet(const std::shared_ptr<const NetDef>& net_def, Workspace* ws);\n\n  bool SupportsAsync() override {\n    return true;\n  }\n\n  vector<float> TEST_Benchmark(\n      const int warmup_runs,\n      const int main_runs,\n      const bool run_individual) override;\n\n  /*\n   * This returns a list of pointers to objects stored in unique_ptrs.\n   * Used by Observers.\n   *\n   * Think carefully before using.\n   */\n  vector<OperatorBase*> GetOperators() const override {\n    vector<OperatorBase*> op_list;\n    for (auto& op : operators_) {\n      op_list.push_back(op.get());\n    }\n    return op_list;\n  }\n\n protected:\n  bool DoRunAsync() override;\n\n  vector<unique_ptr<OperatorBase>> operators_;\n\n  DISABLE_COPY_AND_ASSIGN(AsyncSimpleNet);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_NET_SIMPLE_ASYNC_H_\n"
  },
  {
    "path": "caffe2/core/net_singlethread_async_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <condition_variable>\n#include <mutex>\n#include <stack>\n\n#if !defined(_MSC_VER) && !defined(__APPLE__)\n#include <sched.h>\n#endif\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/net_simple.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\nnamespace gpu_single_thread {\n\nstruct Task {\n  std::vector<std::unique_ptr<OperatorBase>>* ops_;\n  std::condition_variable* cv_;\n  std::mutex* mtx_;\n  int stream_id_;\n  bool done_ = false;\n};\n\nclass GPUExecutor {\n public:\n  explicit GPUExecutor(int gpu_id) : gpu_id_(gpu_id) {}\n\n  ~GPUExecutor() {\n    queue_.NoMoreJobs();\n    thread_.join();\n  }\n\n  void RunJob(Task* task) {\n    queue_.Push(task);\n  }\n\n  void start() {\n    thread_ = std::thread(&GPUExecutor::WorkerFunction, this);\n  }\n\n  static std::shared_ptr<GPUExecutor> Get(int gpu);\n  static void Release(int gpu);\n\n private:\n  void set_affinity();\n  void WorkerFunction();\n\n  std::thread thread_;\n  int gpu_id_;\n  SimpleQueue<Task*> queue_;\n  static std::shared_ptr<GPUExecutor> executors_[CAFFE2_COMPILE_TIME_MAX_GPUS];\n  static std::mutex gpu_mtx_[CAFFE2_COMPILE_TIME_MAX_GPUS];\n};\n\nstd::shared_ptr<GPUExecutor>\n    GPUExecutor::executors_[CAFFE2_COMPILE_TIME_MAX_GPUS];\nstd::mutex GPUExecutor::gpu_mtx_[CAFFE2_COMPILE_TIME_MAX_GPUS];\n\nstd::shared_ptr<GPUExecutor> GPUExecutor::Get(int gpu) {\n  std::lock_guard<std::mutex> grd(gpu_mtx_[gpu]);\n  if (!executors_[gpu].get()) {\n    executors_[gpu].reset(new GPUExecutor(gpu));\n    executors_[gpu].get()->start();\n  }\n  return executors_[gpu];\n}\n\nvoid GPUExecutor::Release(int gpu) {\n  std::lock_guard<std::mutex> grd(gpu_mtx_[gpu]);\n  if (executors_[gpu].use_count() == 1) {\n    executors_[gpu].reset();\n  }\n}\n\nvoid GPUExecutor::set_affinity() {\n// TODO: find a Windows-compatible affinity setting approach.\n// Currently, set_affinity has no effect in Windows. The code is still\n// correct with possible slowdowns.\n#if !defined(_MSC_VER) && !defined(__APPLE__)\n  /* Set CPU affinity */\n  int num_cores = std::thread::hardware_concurrency();\n  if (num_cores > 0) {\n    cpu_set_t mask;\n    CPU_ZERO(&mask);\n\n    CPU_SET(gpu_id_ % num_cores, &mask);\n    if (sched_setaffinity(0, sizeof(cpu_set_t), &mask)) {\n      LOG(WARNING) << \"Could not set CPU affinity\";\n    }\n  }\n#endif\n}\n\n// Worker that takes list of operators from the queue\n// and executes them.\nvoid GPUExecutor::WorkerFunction() {\n  int stream_id_seq = 0;\n  std::stack<int> streams;\n  set_affinity();\n\n  while (true) {\n    Task* task = nullptr;\n    vector<Task*> task_batch;\n\n    if (!queue_.Pop(&task)) {\n      return;\n    }\n    int num_tasks = 1 + queue_.size();\n\n    // Grab all tasks currently in queue so we can run them in parallel\n    // Since we have only one producer, we know this does not block\n\n    // TODO: launch ops in \"zig-zag\" manner so that we can start multiple\n    // streams as simultaneously as possible\n    for (int i = num_tasks - 1; i >= 0; i--) {\n      assert(task != nullptr);\n      if (streams.empty()) {\n        task->stream_id_ = stream_id_seq++;\n      } else {\n        task->stream_id_ = streams.top();\n        streams.pop();\n      }\n\n      for (auto& op : *task->ops_) {\n        op->RunAsync(task->stream_id_);\n      }\n      task_batch.push_back(task);\n\n      // Get the next one\n      if (i > 0) {\n        if (!queue_.Pop(&task)) {\n          return;\n        }\n      }\n    }\n\n    // Wait for the currently executing streams\n    for (auto& pendtask : task_batch) {\n      cudaStream_t stream =\n          CUDAContext::cuda_stream(gpu_id_, pendtask->stream_id_);\n      CUDA_ENFORCE(cudaStreamSynchronize(stream));\n      streams.push(pendtask->stream_id_);\n      std::unique_lock<std::mutex> lk(*pendtask->mtx_);\n      pendtask->done_ = true;\n      pendtask->cv_->notify_one();\n    }\n  }\n}\n\nclass SingleThreadAsyncNet : public SimpleNet {\n public:\n  using SimpleNet::SimpleNet;\n\n  ~SingleThreadAsyncNet() {\n    if (executor_.get()) {\n      // Explicitly reset my holding of the exeuctor so it can be\n      // killed.\n      executor_.reset();\n      GPUExecutor::Release(gpu_id_);\n    }\n  }\n\n  bool Run() override {\n    if (!executor_.get()) {\n      initialize();\n    }\n\n    // Dispatch jobs to the gpu-specific executor thread\n    std::unique_lock<std::mutex> lk(mutex_);\n    Task t;\n    t.ops_ = &operators_;\n    t.cv_ = &cv_;\n    t.mtx_ = &mutex_;\n    t.done_ = false;\n    executor_.get()->RunJob(&t);\n\n    while (!t.done_) {\n      cv_.wait(lk);\n    }\n\n    return true;\n  }\n\n private:\n  std::condition_variable cv_;\n  std::mutex mutex_;\n\n  void initialize() {\n    std::lock_guard<std::mutex> grd(mutex_);\n\n    /* Check the gpu id of this net and check that only one\n       GPU has operators on this net */\n    gpu_id_ = (-1);\n    for (auto& op : operators_) {\n      if (op->device_option().device_type() == CUDA) {\n        if (gpu_id_ < 0) {\n          gpu_id_ = op->device_option().cuda_gpu_id();\n        } else {\n          CAFFE_ENFORCE_EQ(\n              gpu_id_,\n              op->device_option().cuda_gpu_id(),\n              \"One net can only have operators for one GPU\");\n        }\n      }\n    }\n    executor_ = GPUExecutor::Get(gpu_id_);\n  }\n\n  int gpu_id_;\n  std::shared_ptr<GPUExecutor> executor_;\n};\n\nREGISTER_NET(singlethread_async, SingleThreadAsyncNet)\n\n} // namespace gpu_single_thread\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/net_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <google/protobuf/text_format.h>\n#include <gtest/gtest.h>\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/net_dag.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/scope_guard.h\"\n\nCAFFE2_DECLARE_bool(caffe2_disable_chaining);\n\nnamespace caffe2 {\n\nnamespace {\n\nstatic std::atomic<int> counter;\n\n// A net test dummy op that does nothing but scaffolding. Here, we\n// inherit from OperatorBase because we instantiate on both CPU and\n// GPU. In general, you want to only inherit from Operator<Context>.\nclass NetTestDummyOp final : public OperatorBase {\n public:\n  using OperatorBase::OperatorBase;\n\n  NetTestDummyOp(const OperatorDef& operator_def, Workspace* ws)\n      : OperatorBase(operator_def, ws),\n        fail_(OperatorBase::GetSingleArgument<bool>(\"fail\", false)) {}\n\n  bool Run(int /* unused */ /*stream_id*/) override {\n    if (fail_) {\n      return false;\n    }\n    counter.fetch_add(1);\n    return true;\n  }\n\n  // Simulate CUDA operator behavior\n  bool HasAsyncPart() const override {\n    return debug_def().device_option().device_type() == CUDA;\n  }\n\n  bool SupportsAsyncScheduling() const override {\n    return debug_def().device_option().device_type() == CUDA;\n  }\n\n protected:\n  const bool fail_;\n};\n\nREGISTER_CPU_OPERATOR(NetTestDummy, NetTestDummyOp);\nREGISTER_CUDA_OPERATOR(NetTestDummy, NetTestDummyOp);\nREGISTER_CPU_OPERATOR(NetTestDummy2, NetTestDummyOp);\nREGISTER_CUDA_OPERATOR(NetTestDummy2, NetTestDummyOp);\n\nOPERATOR_SCHEMA(NetTestDummy)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\nOPERATOR_SCHEMA(NetTestDummy2)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{1, 0}});\n\nunique_ptr<NetBase> CreateNetTestHelper(\n    Workspace* ws,\n    const vector<string>& input,\n    const vector<string>& output) {\n  NetDef net_def;\n  {\n    auto& op = *(net_def.add_op());\n    op.set_type(\"NetTestDummy\");\n    op.add_input(\"in\");\n    op.add_output(\"hidden\");\n  }\n  {\n    auto& op = *(net_def.add_op());\n    op.set_type(\"NetTestDummy\");\n    op.add_input(\"hidden\");\n    op.add_output(\"out\");\n  }\n\n  for (const auto& name : input) {\n    net_def.add_external_input(name);\n  }\n  for (const auto& name : output) {\n    net_def.add_external_output(name);\n  }\n  return CreateNet(net_def, ws);\n}\n\n}  // namespace\n\nTEST(NetTest, ConstructionNoDeclaredInputOutput) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  unique_ptr<NetBase> net(\n      CreateNetTestHelper(&ws, vector<string>(), vector<string>()));\n  EXPECT_TRUE(net.get() != nullptr);\n}\n\nTEST(NetTest, ConstructionDeclaredInput) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  unique_ptr<NetBase> net(\n      CreateNetTestHelper(&ws, vector<string>{\"in\"}, vector<string>()));\n  EXPECT_TRUE(net.get() != nullptr);\n}\n\nTEST(NetTest, ConstructionDeclaredOutput) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  unique_ptr<NetBase> net(\n      CreateNetTestHelper(&ws, vector<string>(), vector<string>{\"out\"}));\n  EXPECT_TRUE(net.get() != nullptr);\n}\n\nTEST(NetTest, DeclaredInputInsufficient) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  ASSERT_THROW(\n      CreateNetTestHelper(&ws, vector<string>{\"unuseful_in\"},\n                          vector<string>()),\n      EnforceNotMet);\n}\n\nTEST(NetDeathTest, DeclaredOutputNotMet) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  ASSERT_THROW(\n      CreateNetTestHelper(&ws, vector<string>(),\n                          vector<string>{\"unproduced_out\"}),\n      EnforceNotMet);\n}\n\nvoid testExecution(std::unique_ptr<NetBase>& net, int num_ops) {\n  // Run 100 times\n  for (int i = 0; i < 100; i++) {\n    counter.exchange(0);\n    net.get()->Run();\n    ASSERT_EQ(num_ops, counter.load());\n  }\n}\n\nvoid checkChainingAndRun(\n    const char* spec,\n    const dag_utils::ExecutionChains& expected) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef net_def;\n  CAFFE_ENFORCE(google::protobuf::TextFormat::ParseFromString(spec, &net_def));\n  {\n    net_def.set_num_workers(4);\n    auto old = FLAGS_caffe2_disable_chaining;\n    auto g = MakeGuard([&]() { FLAGS_caffe2_disable_chaining = old; });\n    FLAGS_caffe2_disable_chaining = false;\n\n    std::unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n    auto* dag = dynamic_cast_if_rtti<DAGNetBase*>(net.get());\n    CHECK_NOTNULL(dag);\n    const auto& chains = dag->TEST_execution_chains();\n    EXPECT_TRUE(chains == expected);\n    testExecution(net, net_def.op().size());\n  }\n}\n\nvoid checkNumChainsAndRun(const char* spec, const int expected_num_chains) {\n  Workspace ws;\n\n  NetDef net_def;\n  CAFFE_ENFORCE(google::protobuf::TextFormat::ParseFromString(spec, &net_def));\n  net_def.set_num_workers(4);\n\n  // Create all external inputs\n  for (auto inp : net_def.external_input()) {\n    ws.CreateBlob(inp);\n  }\n\n  {\n    auto old = FLAGS_caffe2_disable_chaining;\n    auto g = MakeGuard([&]() { FLAGS_caffe2_disable_chaining = old; });\n    FLAGS_caffe2_disable_chaining = false;\n\n    std::unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n    auto* dag = dynamic_cast_if_rtti<DAGNetBase*>(net.get());\n    CHECK_NOTNULL(dag);\n    const auto& chains = dag->TEST_execution_chains();\n    EXPECT_EQ(expected_num_chains, chains.size());\n    testExecution(net, net_def.op().size());\n  }\n}\n\nTEST(NetTest, ChainingForLinearModel) {\n  const auto spec = R\"DOC(\n        name: \"example\"\n        type: \"dag\"\n        external_input: \"in\"\n        op {\n          input: \"in\"\n          output: \"hidden\"\n          type: \"NetTestDummy\"\n        }\n        op {\n          input: \"hidden\"\n          output: \"out\"\n          type: \"NetTestDummy\"\n        }\n)DOC\";\n  checkChainingAndRun(spec, {{0, {0, 1}}});\n}\n\nTEST(NetTest, ChainingForFork) {\n  const auto spec = R\"DOC(\n        name: \"example\"\n        type: \"dag\"\n        external_input: \"in\"\n        op {\n          input: \"in\"\n          output: \"hidden\"\n          type: \"NetTestDummy\"\n        }\n        op {\n          input: \"hidden\"\n          output: \"out1\"\n          type: \"NetTestDummy\"\n        }\n        op {\n          input: \"hidden\"\n          output: \"out2\"\n          type: \"NetTestDummy\"\n        }\n)DOC\";\n  checkChainingAndRun(spec, {{0, {0}}, {1, {1}}, {2, {2}}});\n}\n\n// TEST(NetTest, ChainingForJoinWithAncestor) {\n//   const auto spec = R\"DOC(\n//         name: \"example\"\n//         type: \"dag\"\n//         external_input: \"in\"\n//         op {\n//           input: \"in\"\n//           output: \"hidden\"\n//           type: \"NetTestDummy\"\n//         }\n//         op {\n//           input: \"hidden\"\n//           output: \"out1\"\n//           type: \"NetTestDummy\"\n//         }\n//         op {\n//           input: \"hidden\"\n//           output: \"out2\"\n//           type: \"NetTestDummy\"\n//         }\n//         op {\n//           input: \"hidden\"\n//           input: \"out2\"\n//           type: \"NetTestDummy\"\n//         }\n// )DOC\";\n//   checkChainingAndRun(spec, {{0, {0}}, {1, {1}}, {2, {2, 3}}});\n// }\n\nTEST(NetTest, ChainingForForkJoin) {\n  const auto spec = R\"DOC(\n        name: \"example\"\n        type: \"dag\"\n        external_input: \"in\"\n        op {\n          input: \"in\"\n          output: \"hidden1\"\n          type: \"NetTestDummy\"\n        }\n        op {\n          input: \"in\"\n          output: \"hidden2\"\n          type: \"NetTestDummy\"\n        }\n        op {\n          input: \"hidden1\"\n          input: \"hidden2\"\n          output: \"out\"\n          type: \"NetTestDummy\"\n        }\n        op {\n          input: \"out\"\n          output: \"out2\"\n          type: \"NetTestDummy\"\n        }\n)DOC\";\n  checkChainingAndRun(spec, {{0, {0}}, {1, {1}}, {2, {2, 3}}});\n}\n\nTEST(NetTest, ChainingForwardBackward) {\n  const auto spec = R\"DOC(\n  name: \"gpu_0\"\n  type: \"dag\"\n  op {\n    input: \"in\"\n    input: \"fc_0_w\"\n    input: \"fc_0_b\"\n    output: \"fc_0\"\n    name: \"0\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_0\"\n    output: \"fc_0\"\n    name: \"1\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_0\"\n    input: \"fc_1_w\"\n    input: \"fc_1_b\"\n    output: \"fc_1\"\n    name: \"2\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_1\"\n    output: \"fc_1\"\n    name: \"3\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_1\"\n    input: \"fc_2_w\"\n    input: \"fc_2_b\"\n    output: \"fc_2\"\n    name: \"4\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_2\"\n    output: \"fc_2\"\n    name: \"5\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_2\"\n    input: \"fc_3_w\"\n    input: \"fc_3_b\"\n    output: \"fc_3\"\n    name: \"6\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_3\"\n    output: \"fc_3\"\n    name: \"7\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_3\"\n    input: \"fc_4_w\"\n    input: \"fc_4_b\"\n    output: \"fc_4\"\n    name: \"8\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_4\"\n    output: \"fc_4\"\n    name: \"9\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_4\"\n    input: \"in2\"\n    output: \"LabelCrossEntropy\"\n    name: \"10\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"LabelCrossEntropy\"\n    output: \"AveragedLoss\"\n    name: \"11\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"AveragedLoss\"\n    output: \"AveragedLoss_autogen_grad\"\n    name: \"12\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"LabelCrossEntropy\"\n    input: \"AveragedLoss_autogen_grad\"\n    output: \"LabelCrossEntropy_grad\"\n    name: \"13\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_4\"\n    input: \"label\"\n    input: \"LabelCrossEntropy_grad\"\n    output: \"fc_4_grad\"\n    name: \"14\"\n    type: \"NetTestDummy2\"\n  }\n  op {\n    input: \"fc_4\"\n    input: \"fc_4_grad\"\n    output: \"fc_4_grad\"\n    name: \"15\"\n    type: \"NetTestDummy2\"\n  }\n  op {\n    input: \"fc_3\"\n    input: \"fc_4_w\"\n    input: \"fc_4_grad\"\n    output: \"fc_4_w_grad\"\n    output: \"fc_4_b_grad\"\n    output: \"fc_3_grad\"\n    name: \"16\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_3\"\n    input: \"fc_3_grad\"\n    output: \"fc_3_grad\"\n    name: \"17\"\n    type: \"NetTestDummy2\"\n  }\n  op {\n    input: \"fc_2\"\n    input: \"fc_3_w\"\n    input: \"fc_3_grad\"\n    output: \"fc_3_w_grad\"\n    output: \"fc_3_b_grad\"\n    output: \"fc_2_grad\"\n    name: \"18\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_2\"\n    input: \"fc_2_grad\"\n    output: \"fc_2_grad\"\n    name: \"19\"\n    type: \"NetTestDummy2\"\n  }\n  op {\n    input: \"fc_1\"\n    input: \"fc_2_w\"\n    input: \"fc_2_grad\"\n    output: \"fc_2_w_grad\"\n    output: \"fc_2_b_grad\"\n    output: \"fc_1_grad\"\n    name: \"20\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_1\"\n    input: \"fc_1_grad\"\n    output: \"fc_1_grad\"\n    name: \"21\"\n    type: \"NetTestDummy2\"\n  }\n  op {\n    input: \"fc_0\"\n    input: \"fc_1_w\"\n    input: \"fc_1_grad\"\n    output: \"fc_1_w_grad\"\n    output: \"fc_1_b_grad\"\n    output: \"fc_0_grad\"\n    name: \"22\"\n    type: \"NetTestDummy\"\n  }\n  op {\n    input: \"fc_0\"\n    input: \"fc_0_grad\"\n    output: \"fc_0_grad\"\n    name: \"23\"\n    type: \"NetTestDummy2\"\n  }\n  op {\n    input: \"in\"\n    input: \"fc_0_w\"\n    input: \"fc_0_grad\"\n    output: \"fc_0_w_grad\"\n    output: \"fc_0_b_grad\"\n    output: \"data_grad\"\n    name: \"24\"\n    type: \"NetTestDummy\"\n  }\n  external_input: \"in\"\n  external_input: \"in2\"\n  external_input: \"LR\"\n  external_input: \"fc_0_w\"\n  external_input: \"fc_0_b\"\n  external_input: \"fc_1_w\"\n  external_input: \"fc_1_b\"\n  external_input: \"fc_2_w\"\n  external_input: \"fc_2_b\"\n  external_input: \"fc_3_w\"\n  external_input: \"fc_3_b\"\n  external_input: \"fc_4_w\"\n  external_input: \"fc_4_b\"\n  external_input: \"label\"\n  )DOC\";\n  checkNumChainsAndRun(spec, 1);\n}\n\nTEST(NetTest, ChainingForHogwildModel) {\n  const auto spec = R\"DOC(\n        name: \"example\"\n        type: \"dag\"\n        external_input: \"in\"\n        op {\n          input: \"in\"\n          output: \"hidden1\"\n          type: \"NetTestDummy\"\n        }\n        op {\n          input: \"hidden1\"\n          output: \"mid1\"\n          type: \"NetTestDummy\"\n        }\n        op {\n          input: \"mid1\"\n          output: \"out1\"\n          type: \"NetTestDummy\"\n        }\n        op {\n          input: \"in\"\n          output: \"hidden2\"\n          type: \"NetTestDummy\"\n        }\n        op {\n          input: \"hidden2\"\n          output: \"mid2\"\n          type: \"NetTestDummy\"\n        }\n        op {\n          input: \"mid2\"\n          output: \"out2\"\n          type: \"NetTestDummy\"\n        }\n)DOC\";\n  checkNumChainsAndRun(spec, 2);\n}\n\nTEST(NetTest, FailingOperator) {\n  const auto spec = R\"DOC(\n        name: \"example\"\n        type: \"dag\"\n        external_input: \"in\"\n        op {\n          input: \"in\"\n          output: \"hidden\"\n          type: \"NetTestDummy\"\n        }\n        op {\n          input: \"hidden\"\n          output: \"out\"\n          type: \"NetTestDummy\"\n          arg {\n            name: \"fail\"\n            i: 1\n          }\n        }\n)DOC\";\n\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n\n  NetDef net_def;\n  CAFFE_ENFORCE(google::protobuf::TextFormat::ParseFromString(spec, &net_def));\n\n  {\n    net_def.set_num_workers(4);\n    auto old = FLAGS_caffe2_disable_chaining;\n    auto g = MakeGuard([&]() { FLAGS_caffe2_disable_chaining = old; });\n    FLAGS_caffe2_disable_chaining = false;\n\n    std::unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n    for (int i = 0; i < 10; i++) {\n      counter.exchange(0);\n      ASSERT_FALSE(net.get()->Run());\n      ASSERT_EQ(1, counter.load());\n    }\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/numa.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/numa.h\"\n\nCAFFE2_DEFINE_bool(\n    caffe2_cpu_numa_enabled,\n    false,\n    \"Use NUMA whenever possible.\");\n\n#if defined(__linux__) && !defined(CAFFE2_DISABLE_NUMA) && CAFFE2_MOBILE == 0\n#include <numa.h>\n#include <numaif.h>\n#define CAFFE2_NUMA_ENABLED\n#endif\n\nnamespace caffe2 {\n\n#ifdef CAFFE2_NUMA_ENABLED\nbool IsNUMAEnabled() {\n  return FLAGS_caffe2_cpu_numa_enabled && numa_available() >= 0;\n}\n\nvoid NUMABind(int numa_node_id) {\n  if (numa_node_id < 0) {\n    return;\n  }\n  if (!IsNUMAEnabled()) {\n    VLOG(1) << \"NUMA is not enabled\";\n    return;\n  }\n\n  CAFFE_ENFORCE(\n      numa_node_id <= numa_max_node(),\n      \"NUMA node id \" + caffe2::to_string(numa_node_id) + \" is unavailable\");\n\n  auto bm = numa_allocate_nodemask();\n  numa_bitmask_clearall(bm);\n  numa_bitmask_setbit(bm, numa_node_id);\n  numa_bind(bm);\n  numa_bitmask_free(bm);\n}\n\nint GetNUMANode(const void* ptr) {\n  if (!IsNUMAEnabled()) {\n    VLOG(1) << \"NUMA is not enabled\";\n    return -1;\n  }\n  CAFFE_ENFORCE(ptr);\n\n  int numa_node = -1;\n  CAFFE_ENFORCE(\n      get_mempolicy(\n          &numa_node, NULL, 0, (void*)ptr, MPOL_F_NODE | MPOL_F_ADDR) == 0,\n      \"Unable to get memory policy\");\n  return numa_node;\n}\n\nint GetNumNUMANodes() {\n  if (!IsNUMAEnabled()) {\n    VLOG(1) << \"NUMA is not enabled\";\n    return -1;\n  }\n\n  return numa_num_configured_nodes();\n}\n\nvoid NUMAMove(void* ptr, size_t size, int numa_node_id) {\n  if (numa_node_id < 0) {\n    return;\n  }\n  if (!IsNUMAEnabled()) {\n    VLOG(1) << \"NUMA is not enabled\";\n    return;\n  }\n  CAFFE_ENFORCE(ptr);\n\n  size_t page_start_ptr = (((size_t)ptr) & ~(getpagesize() - 1));\n  size_t offset = ((size_t)ptr) - page_start_ptr;\n  // Avoid extra dynamic allocation and NUMA api calls\n  CAFFE_ENFORCE(numa_node_id >= 0 && numa_node_id < sizeof(unsigned long) * 8);\n  unsigned long mask = 1UL << numa_node_id;\n  CAFFE_ENFORCE(\n      mbind(\n          (void*)page_start_ptr,\n          size + offset,\n          MPOL_BIND,\n          &mask,\n          sizeof(mask) * 8,\n          MPOL_MF_MOVE | MPOL_MF_STRICT) == 0,\n      \"Could not move memory to a NUMA node\");\n}\n\nint GetCurrentNUMANode() {\n  if (!IsNUMAEnabled()) {\n    VLOG(1) << \"NUMA is not enabled\";\n    return -1;\n  }\n\n  return numa_node_of_cpu(sched_getcpu());\n}\n\n#else // CAFFE2_NUMA_ENABLED\n\nbool IsNUMAEnabled() {\n  return false;\n}\n\nvoid NUMABind(int numa_node_id) {\n  if (numa_node_id >= 0) {\n    VLOG(1) << \"NUMA is not enabled\";\n  }\n}\n\nint GetNUMANode(const void* ptr) {\n  VLOG(1) << \"NUMA is not enabled\";\n  return -1;\n}\n\nint GetNumNUMANodes() {\n  VLOG(1) << \"NUMA is not enabled\";\n  return -1;\n}\n\nvoid NUMAMove(void* ptr, size_t size, int numa_node_id) {\n  if (numa_node_id >= 0) {\n    VLOG(1) << \"NUMA is not enabled\";\n  }\n}\n\nint GetCurrentNUMANode() {\n  VLOG(1) << \"NUMA is not enabled\";\n  return -1;\n}\n\n#endif // CAFFE2_NUMA_ENABLED\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/numa.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_NUMA_H_\n#define CAFFE2_CORE_NUMA_H_\n\n#include \"caffe2/core/logging.h\"\n\nCAFFE2_DECLARE_bool(caffe2_cpu_numa_enabled);\n\nnamespace caffe2 {\n\nbool IsNUMAEnabled();\n\nvoid NUMABind(int numa_node_id);\n\nint GetNUMANode(const void* ptr);\n\nint GetNumNUMANodes();\n\nvoid NUMAMove(void* ptr, size_t size, int numa_node_id);\n\nint GetCurrentNUMANode();\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_NUMA_H_\n"
  },
  {
    "path": "caffe2/core/observer.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <memory>\n#include <unordered_set>\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\n\n/**\n *  Use this to implement a Observer using the Observer Pattern template.\n */\n\ntemplate <class T>\nclass ObserverBase {\n public:\n  explicit ObserverBase(T* subject) : subject_(subject) {}\n\n  virtual void Start() {}\n  virtual void Stop() {}\n\n  virtual std::unique_ptr<ObserverBase<T>> copy(T* subject) {\n    LOG(WARNING) << \"copy() is not implemented and nullptr will be returned.\";\n    return nullptr;\n  }\n\n  virtual std::string debugInfo() {\n    return \"Not implemented.\";\n  }\n\n  virtual ~ObserverBase() noexcept {};\n\n  T* subject() const {\n    return subject_;\n  }\n\n protected:\n  T* subject_;\n};\n\n/**\n *  Inherit to make your class observable.\n */\ntemplate <class T>\nclass Observable {\n public:\n  virtual ~Observable(){};\n  using Observer = ObserverBase<T>;\n\n  /* Returns a reference to the observer after addition. */\n  const Observer* AttachObserver(std::unique_ptr<Observer> observer) {\n    CAFFE_ENFORCE(observer, \"Couldn't attach a null observer.\");\n    std::unordered_set<const Observer*> observers;\n    for (auto& ob : observers_list_) {\n      observers.insert(ob.get());\n    }\n\n    const auto* observer_ptr = observer.get();\n    if (observers.count(observer_ptr)) {\n      return observer_ptr;\n    }\n    observers_list_.push_back(std::move(observer));\n\n    return observer_ptr;\n  }\n\n  /**\n   * Returns a unique_ptr to the removed observer. If not found, return a\n   * nullptr\n   */\n  std::unique_ptr<Observer> DetachObserver(const Observer* observer_ptr) {\n    for (auto it = observers_list_.begin(); it != observers_list_.end(); ++it) {\n      if (it->get() == observer_ptr) {\n        auto res = std::move(*it);\n        observers_list_.erase(it);\n        return res;\n      }\n    }\n    return nullptr;\n  }\n\n  virtual size_t NumObservers() {\n    return observers_list_.size();\n  }\n\n  void StartAllObservers() {\n    for (auto& observer : observers_list_) {\n      observer->Start();\n    }\n  }\n\n  void StopAllObservers() {\n    for (auto& observer : observers_list_) {\n      observer->Stop();\n    }\n  }\n\n protected:\n  std::vector<std::unique_ptr<Observer>> observers_list_;\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/observer_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <google/protobuf/text_format.h>\n#include <gtest/gtest.h>\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/net_dag.h\"\n#include \"caffe2/core/net_simple.h\"\n#include \"caffe2/core/observer.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/core/scope_guard.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\nstatic std::atomic<int> counter;\n\ntemplate <class T>\nclass DummyObserver final : public ObserverBase<T> {\n public:\n  explicit DummyObserver<T>(T* subject_) : ObserverBase<T>(subject_) {}\n  void Start() override;\n  void Stop() override;\n\n  ~DummyObserver() {}\n};\n\ntemplate <>\nvoid DummyObserver<NetBase>::Start() {\n  vector<OperatorBase*> operators = subject_->GetOperators();\n  for (auto& op : operators) {\n    op->AttachObserver(caffe2::make_unique<DummyObserver<OperatorBase>>(op));\n  }\n  counter.fetch_add(1000);\n}\n\ntemplate <>\nvoid DummyObserver<OperatorBase>::Start() {\n  counter.fetch_add(100);\n}\n\ntemplate <>\nvoid DummyObserver<NetBase>::Stop() {\n  counter.fetch_add(10);\n}\n\ntemplate <>\nvoid DummyObserver<OperatorBase>::Stop() {\n  counter.fetch_add(1);\n}\n\nclass ObsTestDummyOp final : public OperatorBase {\n public:\n  using OperatorBase::OperatorBase;\n  bool Run(int /* unused */) override {\n    StartAllObservers();\n    StopAllObservers();\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(ObsTestDummy, ObsTestDummyOp);\nREGISTER_CUDA_OPERATOR(ObsTestDummy, ObsTestDummyOp);\n\nOPERATOR_SCHEMA(ObsTestDummy)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\n\nunique_ptr<NetBase> CreateNetTestHelper(Workspace* ws, bool isDAG = false) {\n  NetDef net_def;\n  if (isDAG) {\n    net_def.set_type(\"dag\");\n  }\n  {\n    auto& op = *(net_def.add_op());\n    op.set_type(\"ObsTestDummy\");\n    op.add_input(\"in\");\n    op.add_output(\"hidden\");\n  }\n  {\n    auto& op = *(net_def.add_op());\n    op.set_type(\"ObsTestDummy\");\n    op.add_input(\"hidden\");\n    op.add_output(\"out\");\n  }\n  net_def.add_external_input(\"in\");\n  net_def.add_external_output(\"out\");\n\n  return CreateNet(net_def, ws);\n}\n}\n\nTEST(ObserverTest, TestNotify) {\n  auto count_before = counter.load();\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef net_def;\n  unique_ptr<NetBase> net(CreateNetTestHelper(&ws));\n  EXPECT_EQ(caffe2::dynamic_cast_if_rtti<SimpleNet*>(net.get()), net.get());\n  unique_ptr<DummyObserver<NetBase>> net_ob =\n      make_unique<DummyObserver<NetBase>>(net.get());\n  net.get()->AttachObserver(std::move(net_ob));\n  net.get()->Run();\n  auto count_after = counter.load();\n  EXPECT_EQ(1212, count_after - count_before);\n}\n\nTEST(ObserverTest, TestUniqueMap) {\n  auto count_before = counter.load();\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef net_def;\n  unique_ptr<NetBase> net(CreateNetTestHelper(&ws));\n  EXPECT_EQ(caffe2::dynamic_cast_if_rtti<SimpleNet*>(net.get()), net.get());\n  unique_ptr<DummyObserver<NetBase>> net_ob =\n      make_unique<DummyObserver<NetBase>>(net.get());\n  auto* ref = net.get()->AttachObserver(std::move(net_ob));\n  net.get()->Run();\n  unique_ptr<Observable<NetBase>::Observer> test =\n      net.get()->DetachObserver(ref);\n  auto count_after = counter.load();\n  EXPECT_EQ(1212, count_after - count_before);\n}\n\nTEST(ObserverTest, TestNotifyAfterDetach) {\n  auto count_before = counter.load();\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef net_def;\n  unique_ptr<NetBase> net(CreateNetTestHelper(&ws));\n  unique_ptr<DummyObserver<NetBase>> net_ob =\n      make_unique<DummyObserver<NetBase>>(net.get());\n  auto* ob = net.get()->AttachObserver(std::move(net_ob));\n  net.get()->DetachObserver(ob);\n  net.get()->Run();\n  auto count_after = counter.load();\n  EXPECT_EQ(0, count_after - count_before);\n}\n\nTEST(ObserverTest, TestDAGNetBase) {\n  auto count_before = counter.load();\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef net_def;\n  unique_ptr<NetBase> net(CreateNetTestHelper(&ws, true));\n  EXPECT_EQ(caffe2::dynamic_cast_if_rtti<DAGNetBase*>(net.get()), net.get());\n  unique_ptr<DummyObserver<NetBase>> net_ob =\n      make_unique<DummyObserver<NetBase>>(net.get());\n  net.get()->AttachObserver(std::move(net_ob));\n  net.get()->Run();\n  auto count_after = counter.load();\n  EXPECT_EQ(1212, count_after - count_before);\n}\n\nTEST(ObserverTest, TestMultipleNetBase) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef net_def;\n  unique_ptr<NetBase> net(CreateNetTestHelper(&ws, true));\n  EXPECT_EQ(caffe2::dynamic_cast_if_rtti<NetBase*>(net.get()), net.get());\n\n  // There may be some default observers\n  const size_t prev_num = net.get()->NumObservers();\n  const int num_tests = 100;\n  vector<const Observable<NetBase>::Observer*> observers;\n  for (int i = 0; i < num_tests; ++i) {\n    unique_ptr<DummyObserver<NetBase>> net_ob =\n        make_unique<DummyObserver<NetBase>>(net.get());\n    observers.emplace_back(net.get()->AttachObserver(std::move(net_ob)));\n  }\n\n  net.get()->Run();\n\n  for (const auto& observer : observers) {\n    net.get()->DetachObserver(observer);\n  }\n\n  EXPECT_EQ(net.get()->NumObservers(), prev_num);\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/operator.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/operator.h\"\n\n#include <algorithm>\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/operator_gradient.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/core/workspace.h\"\n\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include \"caffe2/utils/string_utils.h\"\n\nCAFFE2_DEFINE_int(\n    caffe2_operator_max_engine_name_length,\n    10,\n    \"Maximum engine name length to be stored\");\nCAFFE2_DEFINE_bool(\n    caffe2_disable_implicit_engine_preference,\n    false,\n    \"If set, disable implicit engine preferences. This is useful for unit \"\n    \"testing and debugging cases.\");\n\nnamespace caffe2 {\n\nOperatorBase::OperatorBase(const OperatorDef& operator_def, Workspace* ws)\n    : operator_ws_(ws),\n      operator_def_(std::make_shared<OperatorDef>(operator_def)),\n      device_option_(\n          operator_def.has_device_option() ? operator_def.device_option()\n                                           : DeviceOption()),\n      event_(caffe2::make_unique<Event>(device_option_)) {\n  for (const string& input_str : operator_def.input()) {\n    auto* blob = ws->GetBlob(input_str);\n    CAFFE_ENFORCE(\n        blob != nullptr,\n        \"op \",\n        operator_def.type(),\n        \": Encountered a non-existing input blob: \",\n        input_str);\n    inputs_.push_back(blob);\n  }\n\n  GetOperatorLogger()(operator_def);\n\n  for (const string& output_str : operator_def.output()) {\n    outputs_.push_back(CHECK_NOTNULL(ws->CreateBlob(output_str)));\n  }\n}\n\nvector<TensorShape> OperatorBase::InputTensorShapes() {\n  vector<TensorShape> tps;\n  for (const auto& blob : inputs_) {\n    tps.push_back(GetTensorShapeOfBlob(blob));\n  }\n  return tps;\n}\n\nnamespace {\n\nPerOpEnginePrefType& g_per_op_engine_pref() {\n  static auto* g_per_op_engine_pref_ = new PerOpEnginePrefType();\n  return *g_per_op_engine_pref_;\n}\n\nGlobalEnginePrefType& g_global_engine_pref() {\n  static auto* g_global_engine_pref_ =\n      new GlobalEnginePrefType{{DeviceType::CUDA, {\"CUDNN\"}}};\n  return *g_global_engine_pref_;\n}\n\nunique_ptr<OperatorBase> TryCreateOperator(\n    const string& key, const OperatorDef& operator_def, Workspace* ws) {\n  auto type = operator_def.device_option().device_type();\n  CAFFE_ENFORCE(\n      gDeviceTypeRegistry()->count(type),\n      \"Device type \",\n      type,\n      \" not registered.\");\n  OperatorRegistry* registry = gDeviceTypeRegistry()->at(type);\n  VLOG(1) << \"Creating operator with device type \" << type;\n  try {\n    return registry->Create(key, operator_def, ws);\n  } catch (const UnsupportedOperatorFeature& err) {\n    LOG(WARNING) << \"Operator \" << operator_def.type()\n                 << \" does not support the requested feature. Msg: \"\n                 << err.what()\n                 << \". Proto is: \" << ProtoDebugString(operator_def);\n    return nullptr;\n  }\n}\n\nunique_ptr<OperatorBase> _CreateOperator(\n    const OperatorDef& operator_def,\n    Workspace* ws) {\n  static StaticLinkingProtector g_protector;\n  const auto op_type = operator_def.type();\n  const auto device_type = operator_def.device_option().device_type();\n\n#ifndef CAFFE2_NO_OPERATOR_SCHEMA\n  // first, check with OpSchema if the operator is legal.\n  auto* schema = OpSchemaRegistry::Schema(op_type);\n  if (schema) {\n    CAFFE_ENFORCE(\n        schema->Verify(operator_def),\n        \"Operator def did not pass schema checking: \",\n        ProtoDebugString(operator_def));\n  } else {\n    // We would like to recommend every op to register its schema, so if there\n    // is not one, we print a LOG_ERROR. But we will still allow the operator\n    // to be constructed.\n    LOG(ERROR) << \"Cannot find operator schema for \" << op_type\n               << \". Will skip schema checking.\";\n  }\n#endif\n\n  // second try engines specified in the operator_def and preferred engines\n  std::vector<std::string> engines{};\n  if (operator_def.engine().size()) {\n    const auto op_def_engines = split(',', operator_def.engine());\n    engines.insert(engines.end(), op_def_engines.begin(), op_def_engines.end());\n  }\n  if (!FLAGS_caffe2_disable_implicit_engine_preference &&\n      g_per_op_engine_pref().count(device_type) &&\n      g_per_op_engine_pref()[device_type].count(op_type)) {\n    const auto& preferred_engines =\n        g_per_op_engine_pref()[device_type][op_type];\n    VLOG(2) << \"Inserting per-op engine preference: \" << preferred_engines;\n    engines.insert(\n        engines.end(), preferred_engines.begin(), preferred_engines.end());\n  }\n  if (!FLAGS_caffe2_disable_implicit_engine_preference &&\n      g_global_engine_pref().count(device_type)) {\n    const auto& preferred_engines = g_global_engine_pref()[device_type];\n    VLOG(2) << \"Inserting global engine preference: \" << preferred_engines;\n    engines.insert(\n        engines.end(), preferred_engines.begin(), preferred_engines.end());\n  }\n  for (const auto& engine : engines) {\n    const std::string key = OpRegistryKey(op_type, engine);\n    VLOG(1) << \"Trying to create operator \" << op_type << \" with engine \"\n            << engine;\n    auto op = TryCreateOperator(key, operator_def, ws);\n    if (op) {\n      if (engine.size() <= FLAGS_caffe2_operator_max_engine_name_length) {\n        op->annotate_engine(engine);\n      } else {\n        op->annotate_engine(\n            engine.substr(0, FLAGS_caffe2_operator_max_engine_name_length));\n      }\n      return op;\n    } else {\n      // If the above fails, we will just return the normal case with the\n      // default implementation.\n      VLOG(1) << \"Engine \" << engine\n              << \" is not available for operator \" << op_type << \".\";\n    }\n  }\n  if (operator_def.engine().size() && !VLOG_IS_ON(1)) {\n    LOG(INFO) << \"Engine \" << operator_def.engine()\n              << \" is not available for operator \" << op_type << \".\";\n  }\n  VLOG(1) << \"Using default implementation.\";\n\n  // Lastly, if the engine does not work here, try using the default engine.\n  auto op = TryCreateOperator(op_type, operator_def, ws);\n  CAFFE_ENFORCE(\n      op,\n      \"Cannot create operator of type '\",\n      op_type,\n      \"' on the device '\",\n      DeviceTypeName(device_type),\n      \"'. Verify that implementation for the corresponding device exist. It \"\n      \"might also happen if the binary is not linked with the operator \"\n      \"implementation code. If Python frontend is used it might happen if \"\n      \"dyndep.InitOpsLibrary call is missing. Operator def: \",\n      ProtoDebugString(operator_def));\n  return op;\n}\n\n} // namespace\n\nconst std::string OpRegistryKey(\n    const std::string& op_type,\n    const std::string& engine) {\n  if (engine == \"\" || engine == \"DEFAULT\") {\n    return op_type;\n  } else {\n    return op_type + \"_ENGINE_\" + engine;\n  }\n}\n\nvoid SetPerOpEnginePref(const PerOpEnginePrefType& per_op_engine_pref) {\n  for (const auto& device_pref_pair : per_op_engine_pref) {\n    const auto& device_type = device_pref_pair.first;\n    CAFFE_ENFORCE(\n        gDeviceTypeRegistry()->count(device_type),\n        \"Device type \",\n        device_type,\n        \" not registered.\");\n    auto* registry = gDeviceTypeRegistry()->at(device_type);\n\n    for (const auto& op_pref_pair : device_pref_pair.second) {\n      const auto& op_type = op_pref_pair.first;\n      CAFFE_ENFORCE(\n          registry->Has(op_type),\n          \"Operator type \",\n          op_type,\n          \" not registered in \",\n          device_type,\n          \" registry.\");\n    }\n  }\n  g_per_op_engine_pref() = per_op_engine_pref;\n}\n\nvoid SetGlobalEnginePref(const GlobalEnginePrefType& global_engine_pref) {\n  for (const auto& device_pref_pair : global_engine_pref) {\n    const auto& device_type = device_pref_pair.first;\n    CAFFE_ENFORCE(\n        gDeviceTypeRegistry()->count(device_type),\n        \"Device type \",\n        device_type,\n        \" not registered.\");\n  }\n  g_global_engine_pref() = global_engine_pref;\n}\n\nvoid SetEnginePref(\n    const PerOpEnginePrefType& per_op_engine_pref,\n    const GlobalEnginePrefType& global_engine_pref) {\n  SetPerOpEnginePref(per_op_engine_pref);\n  SetGlobalEnginePref(global_engine_pref);\n}\n\nvoid SetOpEnginePref(\n    const std::string& op_type,\n    const CaffeMap<int, EnginePrefType>& op_pref) {\n  for (const auto& device_pref_pair : op_pref) {\n    const auto& device_type = device_pref_pair.first;\n    CAFFE_ENFORCE(\n        gDeviceTypeRegistry()->count(device_type),\n        \"Device type \",\n        device_type,\n        \" not registered.\");\n    CAFFE_ENFORCE(\n        gDeviceTypeRegistry()->at(device_type)->Has(op_type),\n        \"Operator type \",\n        op_type,\n        \" not registered in \",\n        device_type,\n        \" registry.\");\n    g_per_op_engine_pref()[device_type][op_type] = device_pref_pair.second;\n  }\n}\n\nunique_ptr<OperatorBase> CreateOperator(\n    const OperatorDef& operator_def,\n    Workspace* ws,\n    int net_position) {\n  try {\n    auto op = _CreateOperator(operator_def, ws);\n    op->set_net_position(net_position);\n    return op;\n  } catch (...) {\n    if (net_position != 0) {\n      VLOG(1) << \"Operator constructor with net position \" << net_position\n              << \" failed\";\n      ws->last_failed_op_net_position = net_position;\n    } else {\n      VLOG(1) << \"Failed operator constructor doesn't have an id set\";\n    }\n    throw;\n  }\n}\n\nstd::map<int32_t, OperatorRegistry*>* gDeviceTypeRegistry() {\n  static std::map<int32_t, OperatorRegistry*> g_device_type_registry;\n  return &g_device_type_registry;\n}\n\nCAFFE_DEFINE_REGISTRY(\n    CPUOperatorRegistry,\n    OperatorBase,\n    const OperatorDef&,\n    Workspace*);\nCAFFE_REGISTER_DEVICE_TYPE(DeviceType::CPU, CPUOperatorRegistry);\n\nCAFFE_DEFINE_REGISTRY(\n    CUDAOperatorRegistry,\n    OperatorBase,\n    const OperatorDef&,\n    Workspace*);\nCAFFE_REGISTER_DEVICE_TYPE(DeviceType::CUDA, CUDAOperatorRegistry);\n\nCAFFE_DEFINE_REGISTRY(\n    GradientRegistry,\n    GradientMakerBase,\n    const OperatorDef&, const vector<GradientWrapper>&);\n\nGradientOpsMeta GetGradientForOp(\n    const OperatorDef& def, const vector<GradientWrapper>& g_output) {\n  std::unique_ptr<GradientMakerBase> maker(\n      GradientRegistry()->Create(def.type(), def, g_output));\n  CAFFE_ENFORCE(maker,\n      \"Gradient maker for operator \", def.type(), \" not implemented.\");\n  GradientOpsMeta meta = maker->Get();\n  // Copy device option, engine, and arguments if needed.\n  if (maker->CopyDeviceOption() && def.has_device_option()) {\n    for (OperatorDef& grad_def : meta.ops_) {\n      grad_def.mutable_device_option()->CopyFrom(def.device_option());\n    }\n  }\n  // Copy engine if needed.\n  if (maker->CopyEngine() && def.has_engine()) {\n    for (OperatorDef& grad_def : meta.ops_) {\n      grad_def.set_engine(def.engine());\n    }\n  }\n  // Copy arguments if needed.\n  if (maker->CopyArguments() && def.arg_size()) {\n    for (OperatorDef& grad_def : meta.ops_) {\n      for (auto& arg : def.arg()) {\n        grad_def.add_arg()->CopyFrom(arg);\n      }\n    }\n  }\n  // VLOG for debugging purposes.\n  for (const OperatorDef& grad_def : meta.ops_) {\n    VLOG(1) << \"Gradient ops: \" << ProtoDebugString(grad_def);\n  }\n  // Check if the gradient computation has returned the right size for the\n  // gradient vector.\n  CAFFE_ENFORCE_EQ(meta.g_input_.size(), def.input_size());\n  VLOG(1) << \"Gradients:\";\n  for (const GradientWrapper& grad : meta.g_input_) {\n    // The gradient should either be (1) not set, or (2) dense, or (3) sparse,\n    // but cannot be both dense and sparse.\n    if (!grad.IsDense() && !grad.IsSparse()) {\n      VLOG(1) << \"\\t [no gradient]\";\n    } else if (grad.IsDense()) {\n      VLOG(1) << \"\\t [dense]\" << grad.dense_;\n    } else {\n      CAFFE_ENFORCE(\n          grad.indices_.size() && grad.values_.size(),\n          \"For sparse gradient, one should set both indices and values. \"\n          \"Currently we have: (\" +\n              grad.indices_ + \", \" + grad.values_ + \").\");\n      VLOG(1) << \"\\t [sparse] \" << grad.indices_ << \", \" << grad.values_;\n    }\n  }\n  return meta;\n}\n\nstatic TensorShapes InferBlobShapesAndTypes(\n    CaffeMap<string, TensorShape>& blob_desc,\n    const vector<std::unique_ptr<NetDef>>& nets) {\n  for (auto& defptr : nets) {\n    // Hack to work with auto split gradients\n    CaffeMap<string, string> unmatched_sum_blobs;\n    CaffeMap<string, TensorShape> reshape_cache;\n\n    for (const OperatorDef& op : defptr.get()->op()) {\n      // Hack to ignore queues\n      if (op.type().find(\"Dequeue\") != std::string::npos ||\n          op.type().find(\"Enqueue\") != std::string::npos) {\n        continue;\n      }\n\n      vector<TensorShape> input_desc;\n      bool found_all = true;\n      for (const string& in : op.input()) {\n        auto inp_desc = blob_desc.find(in);\n        if (inp_desc == blob_desc.end()) {\n          LOG(WARNING) << \"Shape and type inference failed for input: \" << in\n                       << \" for op \" << op.type() << \", skipping.\";\n          found_all = false;\n          break;\n        }\n        input_desc.push_back(inp_desc->second);\n      }\n      if (!found_all) {\n        continue;\n      }\n      auto op_schema = OpSchemaRegistry::Schema(op.type());\n      if (op_schema == nullptr) {\n        LOG(WARNING) << \"Shape inference failed, no schema for: \" << op.type();\n        continue;\n      }\n\n      // Special handling for Sum as it used with the autosplits, which have\n      // different naming convention. Assuming that all sum inputs must be of\n      // same size, we can infer their shapes.\n      if (op.type() == \"Sum\") {\n        TensorShape sum_shape;\n        for (auto inp : op.input()) {\n          auto it = blob_desc.find(inp);\n          if (it != blob_desc.end() && !it->second.unknown_shape()) {\n            if (it->second.dims_size() > 0) {\n              sum_shape = blob_desc[inp];\n              break;\n            }\n          }\n        }\n        for (auto inp : op.input()) {\n          auto it = blob_desc.find(inp);\n          if (it == blob_desc.end() || it->second.unknown_shape()) {\n            blob_desc[inp] = sum_shape;\n            if (sum_shape.dims_size() == 0) {\n              // Match later with the output\n              unmatched_sum_blobs[inp] = op.output(0);\n            }\n          }\n        }\n      }\n\n      if (op.type() == \"Reshape\" && op.is_gradient_op()) {\n        CAFFE_ENFORCE(reshape_cache.find(op.input(1)) != reshape_cache.end());\n        TensorShape cached = reshape_cache[op.input(1)];\n        blob_desc[op.output(0)] = cached;\n        continue;\n      }\n\n      std::vector<TensorShape> out;\n      try {\n        out = op_schema->InferTensor(op, input_desc);\n        if (op.is_gradient_op() && out.size()) {\n          // Special handling for gradient ops. We can assume gradients\n          // are of same size as the corresponding variables. This is bit\n          // ugly to base on string matching, but we don't have the connection\n          // between variable and its gradient specified\n\n          CaffeMap<string, string> grads_to_params =\n              GradientMakerBase::MatchGradsToParams(op);\n\n          for (int i = 0; i < out.size(); i++) {\n            if (out[i].unknown_shape()) {\n              std::string gradout = op.output(i);\n\n              if (grads_to_params.find(gradout) != grads_to_params.end()) {\n                std::string var = grads_to_params[gradout];\n                if (blob_desc.find(var) != blob_desc.end()) {\n                  out[i] = blob_desc[var];\n                }\n              }\n            }\n          }\n        }\n\n        if (op.type() == \"Reshape\") {\n          // Reshape stores the original input shape to its second output\n          // blob. We need this for gradient reshape.\n          reshape_cache[op.output(1)] = input_desc[0];\n        }\n\n      } catch (::caffe2::EnforceNotMet& enf) {\n        LOG(ERROR) << \"Shape inference error: \" << enf.msg();\n        LOG(ERROR) << \"Operator: \" << ProtoDebugString(op) << std::endl;\n        LOG(ERROR) << \"Returning empty results.\";\n\n        TensorShapes tps;\n        return tps;\n      }\n\n      if (out.size() != op.output_size()) {\n        if (op.type() == \"Slice\") {\n          CAFFE_ENFORCE(\n              out.size() == 0,\n              \"For Slice operator, either shape of all output blobs are \"\n              \"inferred or shape of none can be inferred.\");\n        } else {\n          CAFFE_THROW(\n              \"Invalid shape inference for operator \",\n              op.type(),\n              \" Expected \",\n              op.output_size(),\n              \" outputs, but got \",\n              out.size());\n        }\n      } else {\n        for (int i = 0; i < out.size(); i++) {\n          blob_desc[op.output(i)] = out[i];\n        }\n      }\n    } // net.ops\n\n    for (auto& unmatched : unmatched_sum_blobs) {\n      if (blob_desc.find(unmatched.second) != blob_desc.end()) {\n        blob_desc[unmatched.first] = blob_desc[unmatched.second];\n      }\n    }\n\n  } // nets\n  TensorShapes tps;\n  for (auto kv : blob_desc) {\n    TensorShape& tp = kv.second;\n    TensorShape* tpnew = tps.add_shapes();\n    tpnew->CopyFrom(tp);\n    tpnew->set_name(kv.first);\n  }\n  return tps;\n}\n\nTensorShape GetTensorShapeOfBlob(const Blob* b) {\n  TypeCall type_fun = GetTypeCallFunction(b->meta().id());\n  TensorInfoCall tensor_info_fun = GetTensorInfoFunction(b->meta().id());\n  TensorShape tp;\n\n  if (type_fun) {\n    tp.set_data_type(TypeMetaToDataType(type_fun(b->GetRaw())));\n  }\n  if (tensor_info_fun) {\n    bool _shares_data;\n    size_t _capacity;\n    DeviceOption _device;\n    auto shape =\n        tensor_info_fun(b->GetRaw(), &_shares_data, &_capacity, &_device);\n    for (auto d : shape) {\n      tp.add_dims(d);\n    }\n  } else {\n    tp.set_unknown_shape(true);\n  }\n  return tp;\n}\n\nTensorShapes InferBlobShapesAndTypesFromWorkspace(\n    Workspace* ws,\n    const vector<std::unique_ptr<NetDef>>& nets) {\n  CaffeMap<string, TensorShape> blob_desc;\n  // Populate shapes from workplace\n  const std::vector<string>& ws_blobs = ws->Blobs();\n  for (const auto& s : ws_blobs) {\n    Blob* b = ws->GetBlob(s);\n    TensorShape tp = GetTensorShapeOfBlob(b);\n    blob_desc[s] = tp;\n  }\n  return InferBlobShapesAndTypes(blob_desc, nets);\n}\n\nTensorShapes InferBlobShapesAndTypesFromMap(\n    const CaffeMap<std::string, std::vector<TIndex>>& blob_dimensions,\n    const vector<std::unique_ptr<NetDef>>& nets) {\n  CaffeMap<string, TensorShape> blob_desc;\n  // Populate shapes from known blobs\n  for (const auto& blob : blob_dimensions) {\n    TensorShape tp;\n    for (auto d : blob.second) {\n      CAFFE_ENFORCE_GT(d, 0);\n      tp.add_dims(d);\n    }\n    blob_desc[blob.first] = tp;\n  }\n  return InferBlobShapesAndTypes(blob_desc, nets);\n}\n\nstd::map<string, std::pair<DeviceOption, DeviceOption>> ValidateTensorDevices(\n    OperatorBase& op,\n    const OperatorDef& op_def) {\n  std::map<string, std::pair<DeviceOption, DeviceOption>> mismatches;\n  DeviceOption op_device = op_def.device_option();\n\n#ifndef CAFFE2_NO_OPERATOR_SCHEMA\n  // Check from op schema if this op is used for crossing devices\n  auto op_schema = OpSchemaRegistry::Schema(op_def.type());\n  if (op_schema != nullptr) {\n    if (op_schema->inputs_can_cross_devices()) {\n      return mismatches;\n    }\n  }\n#endif // CAFFE2_NO_OPERATOR_SCHEMA\n\n  auto Check = [&](const Blob& blob, std::string blob_name) {\n    TensorInfoCall tensor_info_fun = GetTensorInfoFunction(blob.meta().id());\n    if (tensor_info_fun) {\n      bool _shares_data;\n      size_t _capacity;\n      DeviceOption blob_device;\n      tensor_info_fun(\n          const_cast<Blob&>(blob).GetRaw(),\n          &_shares_data,\n          &_capacity,\n          &blob_device);\n\n      if (blob_device.device_type() == CUDA &&\n          blob_device.cuda_gpu_id() != op_device.cuda_gpu_id()) {\n        mismatches[blob_name] = std::make_pair(op_device, blob_device);\n      }\n    }\n  };\n\n  // Check that inputs have same device type as the op\n  for (int i = 0; i < op.InputSize(); i++) {\n    Check(op.InputBlob(i), op_def.input(i));\n  }\n  for (int i = 0; i < op.OutputSize(); i++) {\n    Check(*op.OutputBlob(i), op_def.output(i));\n  }\n  return mismatches;\n}\n\nstd::set<std::string> GetRegisteredOperators() {\n  std::set<std::string> all_keys;\n\n  // CPU operators\n  for (const auto& name : CPUOperatorRegistry()->Keys()) {\n    all_keys.emplace(name);\n  }\n  // CUDA operators\n  for (const auto& name : CUDAOperatorRegistry()->Keys()) {\n    all_keys.emplace(name);\n  }\n\n  return all_keys;\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/operator.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_OPERATOR_H_\n#define CAFFE2_CORE_OPERATOR_H_\n\n#include <array>\n#include <climits>\n#include <cstddef>\n#include <exception>\n#include <set>\n#include <typeinfo>\n#include <vector>\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/observer.h\"\n#include \"caffe2/core/operator_gradient.h\"\n#include \"caffe2/core/operator_schema.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\nclass OperatorBase;\ntypedef ObserverBase<OperatorBase> OperatorObserver;\n\nclass OperatorBase : public Observable<OperatorBase> {\n public:\n  explicit OperatorBase(const OperatorDef& operator_def, Workspace* ws);\n  virtual ~OperatorBase() noexcept {}\n\n  /** @brief Checks if the operator has an argument of the given name.\n   */\n  inline bool HasArgument(const string& name) const {\n    CAFFE_ENFORCE(operator_def_, \"operator_def was null!\");\n    return ArgumentHelper::HasArgument(*operator_def_, name);\n  }\n\n  // Functions that deal with arguments. Basically, this allows us to map an\n  // argument name to a specific type of argument that we are trying to access.\n  template <typename T>\n  inline T GetSingleArgument(const string& name, const T& default_value) const {\n    CAFFE_ENFORCE(operator_def_, \"operator_def was null!\");\n    return ArgumentHelper::GetSingleArgument<OperatorDef, T>(\n        *operator_def_, name, default_value);\n  }\n  template <typename T>\n  inline bool HasSingleArgumentOfType(const string& name) const {\n    CAFFE_ENFORCE(operator_def_, \"operator_def was null!\");\n    return ArgumentHelper::HasSingleArgumentOfType<OperatorDef, T>(\n        *operator_def_, name);\n  }\n  template <typename T>\n  inline vector<T> GetRepeatedArgument(\n      const string& name,\n      const vector<T>& default_value = {}) const {\n    CAFFE_ENFORCE(operator_def_, \"operator_def was null!\");\n    return ArgumentHelper::GetRepeatedArgument<OperatorDef, T>(\n        *operator_def_, name, default_value);\n  }\n\n  // Get the inputs and outputs as specific types.\n  template <typename T>\n  inline const T& Input(int idx) {\n    DCHECK_LT(idx, inputs_.size());\n    try {\n      return inputs_.at(idx)->template Get<T>();\n    } catch (::caffe2::EnforceNotMet& enf) {\n      if (has_debug_def()) {\n        enf.AppendMessage(\".\\nOffending Blob name: \");\n        enf.AppendMessage(debug_def().input(idx));\n        enf.AppendMessage(\".\\n\");\n      }\n      throw enf;\n    }\n  }\n\n  template <typename T>\n  inline T* Output(int idx) {\n    return outputs_.at(idx)->template GetMutable<T>();\n  }\n\n  inline const Blob& InputBlob(int idx) {\n    return *inputs_.at(idx);\n  }\n\n  inline Blob* OutputBlob(int idx) {\n    return outputs_.at(idx);\n  }\n\n  template <typename T>\n  inline bool InputIsType(int idx) {\n    return inputs_.at(idx)->template IsType<T>();\n  }\n\n  template <typename T>\n  inline bool OutputIsType(int idx) {\n    return outputs_.at(idx)->template IsType<T>();\n  }\n\n  inline int InputSize() { return inputs_.size(); }\n  inline int OutputSize() { return outputs_.size(); }\n  inline const vector<const Blob*>& Inputs() const { return inputs_; }\n  inline const vector<Blob*>& Outputs() { return outputs_; }\n  vector<TensorShape> InputTensorShapes();\n\n  virtual void WaitEvent(const Event& ev, int stream_id = -1) {\n    ev.Finish();\n  }\n\n  inline void Wait(const OperatorBase& other, int stream_id = -1) {\n    WaitEvent(other.event(), stream_id);\n  }\n\n  virtual void WaitEvents(\n      const std::vector<const Event*>& events,\n      int stream_id = -1) {\n    for (const auto& ev : events) {\n      ev->Finish();\n    }\n  }\n\n  virtual void Finish() {\n    if (event_) {\n      event_->Finish();\n    }\n  }\n\n  virtual bool Run(int /* unused */ /*stream_id*/ = 0) {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n  virtual bool HasAsyncPart() const {\n    return false;\n  }\n\n  virtual bool SupportsAsyncScheduling() const {\n    return false;\n  }\n\n  // RunAsync, if implemenented by the specific operators, will schedule the\n  // computation on the corresponding context and record the event in its\n  // event_ member object. If the specific operator does not support RunAsync,\n  // it will simply be synchronous as a fallback.\n  virtual bool RunAsync(int stream_id = 0) {\n    return Run(stream_id);\n  }\n\n  virtual void AddRelatedBlobInfo(EnforceNotMet* err) {\n    if (!has_debug_def()) {\n      return;\n    }\n\n    bool found_input;\n    if (err->caller() != nullptr) {\n      for (int i = 0; i < inputs_.size(); i++) {\n        if (inputs_[i]->GetRaw() == err->caller()) {\n          found_input = true;\n          err->AppendMessage(\n              \"\\n** while accessing input: \" + debug_def().input(i));\n          break;\n        }\n      }\n      for (int i = 0; i < outputs_.size(); i++) {\n        if (outputs_[i]->GetRaw() == err->caller()) {\n          if (found_input) {\n            err->AppendMessage(\"\\n OR \");\n          }\n          err->AppendMessage(\n              \"\\n** while accessing output: \" + debug_def().output(i));\n          break;\n        }\n      }\n    }\n  }\n\n  inline const OperatorDef& debug_def() const {\n    CAFFE_ENFORCE(has_debug_def(), \"operator_def was null!\");\n    return *operator_def_;\n  }\n\n  inline void set_debug_def(\n      const std::shared_ptr<const OperatorDef>& operator_def) {\n    operator_def_ = operator_def;\n  }\n\n  inline bool has_debug_def() const {\n    return operator_def_ != nullptr;\n  }\n\n public:\n  void RecordLastFailedOpNetPosition() {\n    if (net_position_ != kNoNetPositionSet) {\n      VLOG(1) << \"Operator with id \" << net_position_ << \" failed\";\n      operator_ws_->last_failed_op_net_position = net_position_;\n    } else {\n      VLOG(1) << \"Failed operator doesn't have id set\";\n    }\n  }\n\n  int net_position() const {\n    return net_position_;\n  }\n\n  void set_net_position(int idx) {\n    net_position_ = idx;\n  }\n\n  const DeviceOption& device_option() const {\n    return device_option_;\n  }\n\n  const Event& event() const {\n    CAFFE_ENFORCE(event_, \"Event is disabled\");\n    return *event_;\n  }\n\n  Event& event() {\n    CAFFE_ENFORCE(event_, \"Event is disabled\");\n    return *event_;\n  }\n\n  void ResetEvent() {\n    if (event_) {\n      event_->Reset();\n    }\n  }\n\n  void DisableEvent() {\n    event_ = nullptr;\n  }\n\n  bool IsEventDisabled() const {\n    return !event_;\n  }\n\n  // Checks whether stream is ready to execute new computation,\n  // used in stream allocation optimization to skip stream that is currently\n  // busy. Depends on context and operator's device, returns true by default\n  virtual bool IsStreamFree(int /* unused */) const {\n    return true;\n  }\n\n  const std::string& type() {\n    CAFFE_ENFORCE(operator_def_.get() != nullptr);\n    return operator_def_->type();\n  }\n\n  void annotate_engine(const std::string& engine) {\n    engine_ = engine;\n  }\n\n  const std::string& engine() const {\n    return engine_;\n  }\n\n public:\n  static constexpr int kNoNetPositionSet = -1;\n\n private:\n  Workspace* operator_ws_;\n  std::shared_ptr<const OperatorDef> operator_def_;\n  DeviceOption device_option_;\n  std::string engine_;\n  vector<const Blob*> inputs_;\n  vector<Blob*> outputs_;\n\n  int net_position_{kNoNetPositionSet};\n\n protected:\n  virtual void RecordEvent(const char* err_msg = nullptr) {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n  // An event used by asynchronous execution.\n  std::unique_ptr<Event> event_;\n\n  DISABLE_COPY_AND_ASSIGN(OperatorBase);\n};\n\n// If your operator does not need any specialized contructor or destructor,\n// you can simply use this to save two lines of code.\n#define USE_SIMPLE_BASE_CTOR_DTOR(name)                                        \\\n  name(const OperatorDef& operator_def, Workspace* ws)                         \\\n      : OperatorBase(operator_def, ws) {}                                      \\\n  virtual ~name() noexcept {}\n\n// OP_SINGLE_ARG provides a shorter initialization choice for initialization of\n// member variables for the class constructors.\n#define OP_SINGLE_ARG(type, name, variable, default)                           \\\n  variable(OperatorBase::GetSingleArgument<type>(name, (default)))\n\n// INPUT_TAGS and OUTPUT_TAGS are optional features to name the indices of the\n// operator's inputs and outputs, in order to avoid confusion. For example, for\n// a fully convolution layer that has input, weight and bias, you can define its\n// input tags as:\n//     INPUT_TAGS(INPUT, WEIGHT, BIAS);\n// And in the code, instead of doing\n//     auto& weight = Input(1);\n// you can now do\n//     auto& weight = Input(WEIGHT);\n// to make it more clear.\n#define INPUT_TAGS(first_input, ...)                                           \\\n  enum _InputTags { first_input = 0, __VA_ARGS__ }\n#define OUTPUT_TAGS(first_input, ...)                                          \\\n  enum _OutputTags { first_input = 0, __VA_ARGS__ }\n\n// Operator is the class that you usually want to derive, if your operator will\n// run on different devices. You should then implement the RunOnDevice()\n// function.\ntemplate <class Context>\nclass Operator : public OperatorBase {\n public:\n  explicit Operator(const OperatorDef& operator_def, Workspace* ws)\n      : OperatorBase(operator_def, ws), context_(operator_def.device_option()) {\n    // In the constructor, we switch to the device so that the child class\n    // constructors will run on that device.\n    context_.SwitchToDevice(0);\n  }\n  ~Operator() noexcept override {}\n\n  inline const Tensor<Context>& Input(int idx) {\n    return OperatorBase::template Input<Tensor<Context>>(idx);\n  }\n  inline Tensor<Context>* Output(int idx) {\n    return OperatorBase::template Output<Tensor<Context>>(idx);\n  }\n\n  void WaitEvent(const Event& ev, int stream_id = -1) final {\n    if (stream_id >= 0) {\n      context_.SwitchToDevice(stream_id);\n    }\n    context_.WaitEvent(ev);\n  }\n\n  void WaitEvents(const std::vector<const Event*>& events, int stream_id = -1)\n      final {\n    if (stream_id >= 0) {\n      context_.SwitchToDevice(stream_id);\n    }\n    for (const auto& ev : events) {\n      context_.WaitEvent(*ev);\n    }\n  }\n\n  // The run function of Operator switches to the device, and then carries out\n  // the actual computation with RunOnDevice(). You should implement RunOnDevice\n  // instead of Run().\n  // Note: Run does not update operator's event and can be used only with\n  // non-async executors that do not rely on events\n  bool Run(int stream_id = 0) final {\n    try {\n      StartAllObservers();\n\n      context_.SwitchToDevice(stream_id);\n      bool result = RunOnDevice();\n      if (!result) {\n        this->RecordLastFailedOpNetPosition();\n      }\n      context_.FinishDeviceComputation(); // throws on error\n\n      StopAllObservers();\n\n      return result;\n    } catch (EnforceNotMet& err) {\n      if (has_debug_def()) {\n        err.AppendMessage(\n            \"Error from operator: \\n\" + ProtoDebugString(debug_def()));\n        AddRelatedBlobInfo(&err);\n      }\n      this->RecordLastFailedOpNetPosition();\n      throw;\n    } catch (...) {\n      this->RecordLastFailedOpNetPosition();\n      throw;\n    }\n  }\n\n  bool RunAsync(int stream_id = 0) final {\n    try {\n      context_.SwitchToDevice(stream_id);\n      auto result = RunOnDevice();\n      if (result) {\n        if (HasAsyncPart()) {\n          RecordEvent();\n        } else {\n          // Manually set CPU operator's event status to finished,\n          // unless this is an async CPU operator\n          event().SetFinished();\n        }\n      } else {\n        event().SetFinished(getErrorMsg().c_str());\n        this->RecordLastFailedOpNetPosition();\n      }\n      return result;\n    } catch (EnforceNotMet& err) {\n      if (has_debug_def()) {\n        err.AppendMessage(\n            \"Error from operator: \\n\" + ProtoDebugString(debug_def()));\n        AddRelatedBlobInfo(&err);\n      }\n      event().SetFinished(err.what());\n      this->RecordLastFailedOpNetPosition();\n      throw;\n    } catch (const std::exception& err) {\n      event().SetFinished(err.what());\n      this->RecordLastFailedOpNetPosition();\n      throw;\n    } catch (...) {\n      event().SetFinished(getErrorMsg().c_str());\n      this->RecordLastFailedOpNetPosition();\n      throw;\n    }\n  }\n\n  bool IsStreamFree(int stream_id) const override {\n    return context_.IsStreamFree(device_option(), stream_id);\n  }\n\n  virtual bool RunOnDevice() = 0;\n\n  // Returns whether operator has async on device part.\n  // CUDA operators by default have async parts, CPU operators by default\n  // don't have async parts and are finished after RunOnDevice call.\n  // Events of operators that don't have async parts are automatically set\n  // to finished state by RunAsync.\n  // Defaulting to the value from context (true for CUDA, false for CPU).\n  // Override in case of async CPU operators\n  bool HasAsyncPart() const override {\n    return context_.HasAsyncPartDefault();\n  }\n\n  // Returns whether operator's RunOnDevice schedules async on device part and\n  // can be run without waiting for parent operator's async part to be finished\n  // on the same device.\n  // Note: when true, RunOnDevice must not access the content of the input blobs\n  // as they might not be computed yet\n  // Note: when true, operator's device needs to support async scheduling:\n  //  - supports concept of streams: async ops scheduled on the same stream are\n  //    guaranteed to be executed in the same order they were scheduled\n  //  - provides non-blocking cross device/cross stream synchronization\n  //    primitives\n  //\n  // By default, assuming an op with an async part can be scheduled\n  // asynchronously if device supports async scheduling\n  bool SupportsAsyncScheduling() const override {\n    return HasAsyncPart() && context_.SupportsAsyncScheduling();\n  }\n\n protected:\n  void RecordEvent(const char* err_msg = nullptr) final {\n    if (event_) {\n      context_.Record(event_.get(), err_msg);\n    }\n  }\n\n  std::string getErrorMsg() {\n    if (has_debug_def()) {\n      return \"Error from operator: \" + ProtoDebugString(debug_def());\n    } else {\n      return \"Error from operator: no op def\";\n    }\n  }\n\n  Context context_;\n};\n\n#define USE_OPERATOR_BASE_FUNCTIONS                                 \\\n  /* using override */ using OperatorBase::HasArgument;             \\\n  /* using override */ using OperatorBase::GetSingleArgument;       \\\n  /* using override */ using OperatorBase::HasSingleArgumentOfType; \\\n  /* using override */ using OperatorBase::GetRepeatedArgument;     \\\n  /* using override */ using OperatorBase::InputIsType;             \\\n  /* using override */ using OperatorBase::InputSize;               \\\n  /* using override */ using OperatorBase::OutputSize\n\n#define USE_OPERATOR_FUNCTIONS(context)                    \\\n  USE_OPERATOR_BASE_FUNCTIONS;                             \\\n  /* using override */ using Operator<context>::context_;  \\\n  /* using override */ using Operator<context>::Input;     \\\n  /* using override */ using Operator<context>::InputBlob; \\\n  /* using override */ using Operator<context>::Output;    \\\n  /* using override */ using Operator<context>::OutputBlob\n\n#define USE_OPERATOR_CONTEXT_FUNCTIONS USE_OPERATOR_FUNCTIONS(Context)\n\n#define USE_SIMPLE_CTOR_DTOR(name)                                             \\\n  name(const OperatorDef& operator_def, Workspace* ws)                         \\\n      : Operator<Context>(operator_def, ws) {}                                 \\\n  virtual ~name() noexcept {}\n\n// Helpers to implement runtime op polymorphism. Often it's convenient to make\n// an op work on different input types (e.g. i32 vs i64 indices) or special-case\n// it for particular input size (e.g. ScatterWeightedSum for block size of 1\n// doesn't need to call Eigen).\n//\n// DispatchHelper provides compile-time generation of nested \"if\" statements,\n// e.g. `DispatchHelper<FixedValues<1, 4>>::call(this, block_size);`\n// unrolls into:\n//   if (block_size == 1) {\n//     return DoRunWithValue<1>();\n//   } else if (block_size = 4) {\n//     return DoRunWithValue<4>();\n//   } else {\n//     return DoRunWithValue<-1>();\n//   }`\n//\n// DoRunWithValue implementation can use template arguments to do \"if\"\n// statements\n// or proxy to functions in math.h which often provide fixed size\n// implementation.\n//\n// Similarly `TensorTypes<int32_t, int64_t>(this, Input(0))` provides branching\n// based on type of the first input and calls DoRunWithType.\n//\n// Note, that the same instance of Op class is used as the method, not class is\n// templated. We might consider adding static class-level polymorphism later.\n//\n// Convenient macro USE_DISPATCH_HELPER is provided for declaring friendship in\n// case DoRunWithValue or DoRunWithType are declared non-public.\n\n#define USE_DISPATCH_HELPER                           \\\n  template <typename FirstArg, typename... ExtraArgs> \\\n  friend struct DispatchHelper\n\ntemplate <int... Values>\nstruct FixedValues {};\n\ntemplate <typename... Types>\nstruct TensorTypes {};\n\n// Special tag that can be listed in TensorTypes to denote that a special\n// implementation in 'RunWithOtherType' needs to be called instead of failing\n// Obviously this needs to be the last item in lists, e.g.\n// TensorTypes<float, double, GenericTensorImplementation>\nstruct GenericTensorImplementation {};\n\n// Same as TensorTypes but call DoRunWithType2\ntemplate <typename... Types>\nstruct TensorTypes2 {};\n\ntemplate <typename Sizes, typename... ExtraArgs>\nstruct DispatchHelper;\n\ntemplate <int FirstVal, int... Values, typename... ExtraArgs>\nstruct DispatchHelper<FixedValues<FirstVal, Values...>, ExtraArgs...> {\n  template <typename Op>\n  static bool call(Op* op, int value) {\n    if (FirstVal == value) {\n      return op->template DoRunWithValue<ExtraArgs..., FirstVal>();\n    }\n    return DispatchHelper<FixedValues<Values...>, ExtraArgs...>::template call<\n        Op>(op, value);\n  }\n};\n\ntemplate <typename... ExtraArgs>\nstruct DispatchHelper<FixedValues<>, ExtraArgs...> {\n  template <typename Op>\n  static bool call(Op* op, TIndex /*size*/) {\n    return op->template DoRunWithValue<ExtraArgs..., -1>();\n  }\n};\n\n#define CAFFE2_DEFINE_TENSOR_TYPES_DISPATCHER(                                 \\\n    TensorTypes, DoRunWithType, DoRunWithOtherType)                            \\\n  template <typename FirstType, typename... Types, typename... ExtraArgs>      \\\n  struct DispatchHelper<TensorTypes<FirstType, Types...>, ExtraArgs...> {      \\\n    template <typename Op>                                                     \\\n    static bool call(Op* op, const TypeMeta& meta) {                           \\\n      static_assert(                                                           \\\n          !std::is_same<GenericTensorImplementation, FirstType>::value,        \\\n          \"GenericTensorImplementation must be the last in TensorTypes list\"); \\\n      if (meta.Match<FirstType>()) {                                           \\\n        return op->template DoRunWithType<ExtraArgs..., FirstType>();          \\\n      }                                                                        \\\n      return DispatchHelper<TensorTypes<Types...>, ExtraArgs...>::             \\\n          template call<Op>(op, meta);                                         \\\n    }                                                                          \\\n    template <typename Op, typename Context>                                   \\\n    static bool call(Op* op, const Tensor<Context>& tensor) {                  \\\n      return call<Op>(op, tensor.meta());                                      \\\n    }                                                                          \\\n    template <typename Op>                                                     \\\n    static bool call(Op* op, const Blob& blob) {                               \\\n      return call<Op>(op, blob.meta());                                        \\\n    }                                                                          \\\n  };                                                                           \\\n                                                                               \\\n  template <typename... ExtraArgs>                                             \\\n  struct DispatchHelper<TensorTypes<>, ExtraArgs...> {                         \\\n    template <typename Op>                                                     \\\n    static bool call(Op* /* unused */, const TypeMeta& meta) {                 \\\n      CAFFE_THROW(\"Unsupported type of tensor: \", meta.name());                \\\n    }                                                                          \\\n    template <typename Op, typename Context>                                   \\\n    static bool call(Op* op, const Tensor<Context>& tensor) {                  \\\n      return call<Op>(op, tensor.meta());                                      \\\n    }                                                                          \\\n    template <typename Op>                                                     \\\n    static bool call(Op* op, const Blob& blob) {                               \\\n      return call<Op>(op, blob.meta());                                        \\\n    }                                                                          \\\n  };                                                                           \\\n                                                                               \\\n  template <typename... ExtraArgs>                                             \\\n  struct DispatchHelper<                                                       \\\n      TensorTypes<GenericTensorImplementation>,                                \\\n      ExtraArgs...> {                                                          \\\n    template <typename Op>                                                     \\\n    static bool call(Op* op, const TypeMeta&) {                                \\\n      return op->template DoRunWithOtherType<ExtraArgs...>();                  \\\n    }                                                                          \\\n    template <typename Op, typename Context>                                   \\\n    static bool call(Op* op, const Tensor<Context>& tensor) {                  \\\n      return call<Op>(op, tensor.meta());                                      \\\n    }                                                                          \\\n    template <typename Op>                                                     \\\n    static bool call(Op* op, const Blob& blob) {                               \\\n      return call<Op>(op, blob.meta());                                        \\\n    }                                                                          \\\n  };\nCAFFE2_DEFINE_TENSOR_TYPES_DISPATCHER(\n    TensorTypes,\n    DoRunWithType,\n    DoRunWithOtherType)\nCAFFE2_DEFINE_TENSOR_TYPES_DISPATCHER(\n    TensorTypes2,\n    DoRunWithType2,\n    DoRunWithOtherType2)\n#undef CAFFE2_DEFINE_TENSOR_TYPES_DISPATCHER\n\n// The device type registry. This works in two phases:\n// (1) gDeviceTypeRegistry() maps the device types values to the actual operator\n//     registry function.\n// (2) Then, one can call the operator registry function to further create the\n//     operators.\ntypedef Registry<\n    std::string,\n    std::unique_ptr<OperatorBase>,\n    const OperatorDef&,\n    Workspace*>\n    OperatorRegistry;\ntypedef Registry<\n    std::string,\n    std::unique_ptr<OperatorBase>,\n    const OperatorDef&,\n    Workspace*>* (*RegistryFunction)();\nstd::map<int32_t, OperatorRegistry*>* gDeviceTypeRegistry();\n\nstruct DeviceTypeRegisterer {\n  explicit DeviceTypeRegisterer(int32_t type, RegistryFunction func) {\n    if (gDeviceTypeRegistry()->count(type)) {\n      std::cerr << \"Device type \" << type\n                << \"registered twice. This should not happen. Did you have \"\n                   \"duplicated numbers assigned to different devices?\";\n      std::exit(1);\n    }\n    // Calling the registry function to get the actual registry pointer.\n    gDeviceTypeRegistry()->emplace(type, func());\n  }\n};\n\n#define CAFFE_REGISTER_DEVICE_TYPE(type, registry_function) \\\n  namespace {                                               \\\n  static DeviceTypeRegisterer CAFFE_ANONYMOUS_VARIABLE(     \\\n      DeviceType)(type, &registry_function);                \\\n  }\n\n// The operator registry. Since we are not expecting a great number of devices,\n// we will simply have an if-then type command and allocate the actual\n// generation to device-specific registerers.\n// Note that although we have CUDA and CUDNN here, the registerers themselves do\n// not depend on specific cuda or cudnn libraries. This means that we will be\n// able to compile it even when there is no cuda available - we simply do not\n// link any cuda or cudnn operators.\nCAFFE_DECLARE_REGISTRY(\n    CPUOperatorRegistry,\n    OperatorBase,\n    const OperatorDef&,\n    Workspace*);\n#define REGISTER_CPU_OPERATOR_CREATOR(key, ...) \\\n  CAFFE_REGISTER_CREATOR(CPUOperatorRegistry, key, __VA_ARGS__)\n#define REGISTER_CPU_OPERATOR(name, ...)                           \\\n  extern void CAFFE2_PLEASE_ADD_OPERATOR_SCHEMA_FOR_##name();      \\\n  static void CAFFE2_UNUSED CAFFE_ANONYMOUS_VARIABLE_CPU##name() { \\\n    CAFFE2_PLEASE_ADD_OPERATOR_SCHEMA_FOR_##name();                \\\n  }                                                                \\\n  CAFFE_REGISTER_CLASS(CPUOperatorRegistry, name, __VA_ARGS__)\n#define REGISTER_CPU_OPERATOR_STR(str_name, ...) \\\n  CAFFE_REGISTER_TYPED_CLASS(CPUOperatorRegistry, str_name, __VA_ARGS__)\n\n#define REGISTER_CPU_OPERATOR_WITH_ENGINE(name, engine, ...) \\\n  CAFFE_REGISTER_CLASS(CPUOperatorRegistry, name##_ENGINE_##engine, __VA_ARGS__)\n\nCAFFE_DECLARE_REGISTRY(\n    CUDAOperatorRegistry,\n    OperatorBase,\n    const OperatorDef&,\n    Workspace*);\n#define REGISTER_CUDA_OPERATOR_CREATOR(key, ...) \\\n  CAFFE_REGISTER_CREATOR(CUDAOperatorRegistry, key, __VA_ARGS__)\n#define REGISTER_CUDA_OPERATOR(name, ...)                           \\\n  extern void CAFFE2_PLEASE_ADD_OPERATOR_SCHEMA_FOR_##name();       \\\n  static void CAFFE2_UNUSED CAFFE_ANONYMOUS_VARIABLE_CUDA##name() { \\\n    CAFFE2_PLEASE_ADD_OPERATOR_SCHEMA_FOR_##name();                 \\\n  }                                                                 \\\n  CAFFE_REGISTER_CLASS(CUDAOperatorRegistry, name, __VA_ARGS__)\n#define REGISTER_CUDA_OPERATOR_STR(str_name, ...) \\\n  CAFFE_REGISTER_TYPED_CLASS(CUDAOperatorRegistry, str_name, __VA_ARGS__)\n\n#define REGISTER_CUDA_OPERATOR_WITH_ENGINE(name, engine, ...) \\\n  CAFFE_REGISTER_CLASS(                                       \\\n      CUDAOperatorRegistry, name##_ENGINE_##engine, __VA_ARGS__)\n\n// Macros for cudnn since we use it often\n#define REGISTER_CUDNN_OPERATOR(name, ...) \\\n  REGISTER_CUDA_OPERATOR_WITH_ENGINE(name, CUDNN, __VA_ARGS__)\n\n// StaticLinkingProtector is a helper class that ensures that the Caffe2\n// library is linked correctly with whole archives (in the case of static\n// linking). What happens is that when CreateOperator is called for the first\n// time, it instantiates an OperatorLinkingProtector object to check if the\n// operator registry is empty. If it is empty, this means that we are not\n// properly linking the library.\n//\n// You should not need to use this class.\nstruct StaticLinkingProtector {\n  StaticLinkingProtector() {\n    const int registered_ops = CPUOperatorRegistry()->Keys().size();\n    // Note: this is a check failure instead of an exception, because if\n    // the linking is wrong, Caffe2 won't be able to run properly anyway,\n    // so it's better to fail loud.\n    // If Caffe2 is properly linked with whole archive, there should be more\n    // than zero registered ops.\n    if (registered_ops == 0) {\n      LOG(FATAL) <<\n        \"You might have made a build error: the Caffe2 library does not seem \"\n        \"to be linked with whole-static library option. To do so, use \"\n        \"-Wl,-force_load (clang) or -Wl,--whole-archive (gcc) to link the \"\n        \"Caffe2 library.\";\n    }\n  }\n};\n\n// An exception that can be thrown by an operator constructor that notifies\n// that it does not support the given setting. This can be usually used for\n// specific engines that only implement a subset of the features required by\n// the original operator schema.\n// TODO(jiayq): make more feature-complete exception message.\nclass UnsupportedOperatorFeature : public std::exception {\n public:\n  UnsupportedOperatorFeature(const string& msg) : msg_(msg) {}\n  const char* what() const noexcept override {\n    return msg_.c_str();\n  }\n\n private:\n  string msg_;\n};\n\n// A helper macro that should ONLY be used in the operator constructor to check\n// if needed features are met. If not, throws the UnsupportedOperatorFeature\n// exception with the given message.\n#define OPERATOR_NEEDS_FEATURE(condition, ...)                           \\\n  if (!(condition)) {                                                    \\\n    throw UnsupportedOperatorFeature(::caffe2::MakeString(__VA_ARGS__)); \\\n  }\n\n// Creates an operator with the given operator definition.\n// Throws on error and never returns nullptr\nunique_ptr<OperatorBase> CreateOperator(\n    const OperatorDef& operator_def,\n    Workspace* ws,\n    int net_position = OperatorBase::kNoNetPositionSet);\n\nconst std::string OpRegistryKey(\n    const std::string& op_type,\n    const std::string& engine = \"\");\n\n// User can set the preferred engines as a list of engine names, in\n// descending order of preference.\nusing EnginePrefType = std::vector<std::string>;\n// {device_type -> {operator_name -> EnginePrefType}}\nusing PerOpEnginePrefType =\n    CaffeMap<int, CaffeMap<std::string, EnginePrefType>>;\n// {device_type -> EnginePrefType}\nusing GlobalEnginePrefType = CaffeMap<int, EnginePrefType>;\nvoid SetPerOpEnginePref(const PerOpEnginePrefType& per_op_engine_pref);\nvoid SetGlobalEnginePref(const GlobalEnginePrefType& global_engine_pref);\nvoid SetEnginePref(\n    const PerOpEnginePrefType& per_op_engine_pref,\n    const GlobalEnginePrefType& global_engine_pref);\nvoid SetOpEnginePref(\n    const std::string& op_type,\n    const CaffeMap<int, EnginePrefType>& op_pref);\n\nTensorShape GetTensorShapeOfBlob(const Blob* b);\n\nTensorShapes InferBlobShapesAndTypesFromWorkspace(\n    Workspace* ws,\n    const vector<std::unique_ptr<NetDef>>& nets);\n\nTensorShapes InferBlobShapesAndTypesFromMap(\n    const CaffeMap<std::string, std::vector<TIndex>>& blob_dimensions,\n    const vector<std::unique_ptr<NetDef>>& nets);\n\nstd::map<string, std::pair<DeviceOption, DeviceOption>> ValidateTensorDevices(\n    OperatorBase& op,\n    const OperatorDef& op_def);\n\n// Get a set of registered operator names\nstd::set<std::string> GetRegisteredOperators();\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_CORE_OPERATOR_H_\n"
  },
  {
    "path": "caffe2/core/operator_gpu_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <string>\n\n#include <gtest/gtest.h>\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\nclass JustTest : public OperatorBase {\n public:\n  using OperatorBase::OperatorBase;\n  bool Run(int /* unused */ /*stream_id*/) override {\n    return true;\n  }\n  virtual std::string type() {\n    return \"BASE\";\n  }\n};\n\nclass JustTestCUDA : public JustTest {\n public:\n  using JustTest::JustTest;\n  bool Run(int /* unused */ /*stream_id*/) override {\n    return true;\n  }\n  std::string type() override {\n    return \"CUDA\";\n  }\n};\n\nclass JustTestCUDNN : public JustTest {\n public:\n  using JustTest::JustTest;\n  bool Run(int /* unused */ /*stream_id*/) override {\n    return true;\n  }\n  std::string type() override {\n    return \"CUDNN\";\n  }\n};\n\nOPERATOR_SCHEMA(JustTest).NumInputs(0, 1).NumOutputs(0, 1);\nREGISTER_CUDA_OPERATOR(JustTest, JustTestCUDA);\nREGISTER_CUDNN_OPERATOR(JustTest, JustTestCUDNN);\n\nTEST(EnginePrefTest, GPUDeviceDefaultPreferredEngines) {\n  if (!HasCudaGPU())\n    return;\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.mutable_device_option()->set_device_type(CUDA);\n  op_def.set_type(\"JustTest\");\n\n  {\n    const auto op = CreateOperator(op_def, &ws);\n    EXPECT_NE(nullptr, op.get());\n    // CUDNN should be taken as it's in the default global preferred engines\n    // list\n    EXPECT_EQ(static_cast<JustTest*>(op.get())->type(), \"CUDNN\");\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/operator_gradient.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_OPERATOR_GRADIENT_H_\n#define CAFFE2_CORE_OPERATOR_GRADIENT_H_\n\n#include \"caffe2/core/operator_schema.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\n/* @brief A struct that abstracts on top of dense and sparse blobs.\n *\n * For a dense blob, its gradient name should be written into dense_, and for\n * a sparse blob, its gradient name should be written into indice_ for\n * the sparse indices and value_ for the values.\n */\nstruct GradientWrapper {\n  string dense_;\n  string indices_;\n  string values_;\n\n  inline bool IsDense() const {\n    return (dense_.size() != 0);\n  }\n  inline bool IsSparse() const {\n    return (indices_.size() != 0 || values_.size() != 0);\n  }\n  inline bool IsEmpty() const {\n    return (!IsDense() && !IsSparse());\n  }\n};\n\n/**\n * A struct that holds the gradient operators and related gradient maps.\n */\nstruct GradientOpsMeta {\n  vector<OperatorDef> ops_;\n  vector<GradientWrapper> g_input_;\n\n  GradientOpsMeta() {}\n  GradientOpsMeta(\n      const vector<OperatorDef>& ops,\n      const vector<GradientWrapper>& v)\n      : ops_(ops), g_input_(v) {}\n};\n\nclass GradientMakerBase {\n public:\n  GradientMakerBase(\n      const OperatorDef& def,\n      const vector<GradientWrapper>& g_output)\n      : def_(def), g_output_(g_output), g_input_(def.input_size()){};\n  virtual ~GradientMakerBase() {}\n  virtual bool CopyDeviceOption() const {\n    return true;\n  }\n  virtual bool CopyEngine() const {\n    return true;\n  }\n  virtual bool CopyArguments() const {\n    return true;\n  }\n\n  virtual void VerifyOp() const {\n    auto* schema = OpSchemaRegistry::Schema(def_.type());\n    if (schema) {\n      CAFFE_ENFORCE(\n          schema->Verify(def_),\n          \"(GradientMaker) Operator def did not pass schema checking: \",\n          ProtoDebugString(def_));\n    }\n  }\n\n  /**\n   * @brief Returns the gradient ops meta.\n   *\n   * If your gradient op generator only use standard input and output\n   * manipulations, you can simply implement GetGradientDefs() that\n   * returns vector<OperatorDef>. In that, you can call GI, GI_V and GI_I\n   * that will automatically create the gradient registration for you.\n   *\n   * If you need to do custom gradient name registration, overload this\n   * function directly.\n   */\n  virtual GradientOpsMeta Get() {\n    VerifyOp();\n    vector<OperatorDef> new_defs = GetGradientDefs();\n    for (auto& opdef : new_defs) {\n      opdef.set_is_gradient_op(true);\n    }\n    return GradientOpsMeta(new_defs, g_input_);\n  };\n\n  const OperatorDef& Def() const {\n    return def_;\n  }\n\n protected:\n  virtual vector<OperatorDef> GetGradientDefs() {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n  // Helper functions to return names for the gradient computation.\n  // I(idx), O(idx): return the input and output names.\n  // GO(idx): return the name of the gradient for output idx.\n  // GI(idx), GI_I(idx), GI_V(idx): return the name of the gradient for\n  //     input idx, and also registers that name into the gradient\n  //     registry to be returned.\n  string I(const int i) {\n    CAFFE_ENFORCE((i >= 0) && (i < def_.input().size()));\n    return def_.input(i);\n  }\n  string O(const int i) {\n    CAFFE_ENFORCE((i >= 0) && (i < def_.output().size()));\n    return def_.output(i);\n  }\n  string GI(const int i) {\n    CAFFE_ENFORCE(\n        !g_input_.at(i).IsSparse(),\n        \"Input \",\n        def_.input(i),\n        \" already set to sparse.\");\n    g_input_.at(i).dense_ = GradientName(def_.input(i));\n    return GradientName(def_.input(i));\n  }\n  string GI_I(const int i) {\n    CAFFE_ENFORCE(\n        !g_input_.at(i).IsDense(),\n        \"Input \",\n        def_.input(i),\n        \" already set to dense.\");\n    g_input_.at(i).indices_ = GradientSliceIndices(def_.input(i));\n    return GradientSliceIndices(def_.input(i));\n  }\n  string GI_V(const int i) {\n    CAFFE_ENFORCE(\n        !g_input_.at(i).IsDense(),\n        \"Input \",\n        def_.input(i),\n        \" already set to dense.\");\n    g_input_.at(i).values_ = GradientSliceValues(def_.input(i));\n    return GradientSliceValues(def_.input(i));\n  }\n  string GO(const int i) {\n    CAFFE_ENFORCE(\n        g_output_.at(i).IsDense(),\n        \"Gradient of output \",\n        def_.output(i),\n        (g_output_.at(i).IsSparse() ? \" is sparse (expected dense).\"\n                                    : \" is not provided!\"));\n    return g_output_.at(i).dense_;\n  }\n  string GO_I(const int i) {\n    CAFFE_ENFORCE(\n        g_output_.at(i).IsSparse(),\n        \"Gradient of output \",\n        def_.output(i),\n        (g_output_.at(i).IsDense() ? \" is dense (expected sparse).\"\n                                   : \" is not provided!\"));\n    return g_output_.at(i).indices_;\n  }\n  string GO_V(const int i) {\n    CAFFE_ENFORCE(\n        g_output_.at(i).IsSparse(),\n        \"Gradient of output \",\n        def_.output(i),\n        (g_output_.at(i).IsDense() ? \" is dense (expected sparse).\"\n                                   : \" is not provided!\"));\n    return g_output_.at(i).values_;\n  }\n  const GradientWrapper& GradOut(int i) {\n    return g_output_.at(i);\n  }\n\n  // Function to add a gradient pair to map.\n  void SetDense(const int i, const string& name) {\n    CAFFE_ENFORCE(\n        !g_input_.at(i).IsSparse(),\n        \"Input \",\n        def_.input(i),\n        \" already set to sparse.\");\n    g_input_.at(i).dense_ = name;\n  }\n  void SetSparse(const int i, const string& indices, const string& values) {\n    CAFFE_ENFORCE(\n        !g_input_.at(i).IsDense(),\n        \"Input \",\n        def_.input(i),\n        \" already set to dense.\");\n    g_input_.at(i).indices_ = indices;\n    g_input_.at(i).values_ = values;\n  }\n\n  /**\n   * @brief a helper function to allow one to create one single operator\n   * def, which is usually the case for many simple operators.\n   */\n  template <class... Args>\n  inline static vector<OperatorDef> SingleGradientDef(const Args&... args) {\n    return vector<OperatorDef>{CreateOperatorDef(args...)};\n  }\n\n public:\n  /**\n    * Returns map that returns the parameters that the gradients are for.\n    */\n  static CaffeMap<string, string> MatchGradsToParams(const OperatorDef& op) {\n    // NOTE: how to go beyond string-matching?\n    CaffeMap<string, string> m;\n    for (auto& out : op.output()) {\n      if (IsGradientBlob(out)) {\n        m[out] = out.substr(0, out.length() - 5);\n      }\n    }\n    return m;\n  }\n\n private:\n  // Utility functions for gradient name computation. We don't expose them\n  // in order to discourage the use of such names explicitly.\n  static string GradientName(const string& name) {\n    return name + \"_grad\";\n  }\n\n  static bool IsGradientBlob(const string& name) {\n    return name.length() > 5 && name.find(\"_grad\") == name.length() - 5;\n  }\n\n  static string GradientNameToParam(const string& name) {\n    CHECK(IsGradientBlob(name));\n    return name.substr(0, name.length() - 5);\n  }\n\n  static string GradientSliceIndices(const string& name) {\n    return name + \"_grad_indices\";\n  }\n\n  static string GradientSliceValues(const string& name) {\n    return name + \"_grad_values\";\n  }\n\n protected:\n  // We make the member variables protected in case someone wants to write\n  // a fully custom Get() function.\n  const OperatorDef& def_;\n  const vector<GradientWrapper>& g_output_;\n  vector<GradientWrapper> g_input_;\n};\n\n/**\n * @brief A helper class to indicate that the operator does not need gradient\n * computation.\n *\n * Use the macro NO_GRADIENT to register operators that do not have gradients.\n * Note that this is different fron SHOULD_NOT_DO_GRADIENT: the latter means\n * that the gradient computation should not flow through it at all, and throws\n * an error if it is called.\n */\nclass NoGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return vector<OperatorDef>();\n  }\n};\n\n/**\n * @brief A helper class to indicate that the operator should have no gradient.\n *\n * This is used when the operator definition is designed to not have a gradient.\n * Calling a gradient on this operator def will cause Caffe2 to quit.\n */\nstruct ThrowInTheTowelIfGradientIsCalled : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  GradientOpsMeta Get() override {\n    CAFFE_ENFORCE(\n        false, \"One should not call gradient for operator \", def_.type(), \".\");\n  }\n};\n\n/**\n * @brief A helper class to indicate that the gradient mechanism is not ready.\n *\n * This should only be used sparsely when the gradient does exist, but we have\n * not implemented it yet and are using this as a lazy excuse. Eventually, a\n * gradient operator should be implemented.\n */\nstruct GradientNotImplementedYet : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  GradientOpsMeta Get() override {\n    CAFFE_ENFORCE(\n        false,\n        \"Operator \",\n        def_.type(),\n        \" should have a gradient but is not implemented yet.\");\n  }\n};\n\nCAFFE_DECLARE_REGISTRY(\n    GradientRegistry,\n    GradientMakerBase,\n    const OperatorDef&,\n    const vector<GradientWrapper>&);\n\n#define REGISTER_GRADIENT(name, ...) \\\n  CAFFE_REGISTER_CLASS(GradientRegistry, name, __VA_ARGS__)\n#define REGISTER_GRADIENT_STR(str_name, ...) \\\n  CAFFE_REGISTER_TYPED_CLASS(GradientRegistry, str_name, __VA_ARGS__)\n\n// NO_GRADIENT means that the operator does not need any gradient computation.\n#define NO_GRADIENT(name) REGISTER_GRADIENT(name, NoGradient)\n\n// SHOULD_NOT_DO_GRADIENT means that the operator is not designed to have\n// gradient operators. If you attempt to call the gradient, a log fatal will\n// occur.\n#define SHOULD_NOT_DO_GRADIENT(name) \\\n  REGISTER_GRADIENT(name, ThrowInTheTowelIfGradientIsCalled)\n\n#define GRADIENT_NOT_IMPLEMENTED_YET(name) \\\n  REGISTER_GRADIENT(name, GradientNotImplementedYet)\n\n/**\n * @brief Gets the GradientOpsMeta for the given operator def.\n */\nGradientOpsMeta GetGradientForOp(\n    const OperatorDef& def,\n    const vector<GradientWrapper>& g_output);\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_OPERATOR_GRADIENT_H_\n"
  },
  {
    "path": "caffe2/core/operator_schema.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/operator_schema.h\"\n\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\n\nbool OpSchema::Verify(const OperatorDef& def) const {\n  // Check the number of inputs.\n  if (def.input_size() < min_input_ || def.input_size() > max_input_) {\n    LOG(ERROR) << \"Input size \" << def.input_size()\n                    << \" not in range [min=\" << min_input_ << \", max=\"\n                    << max_input_ << \"].\";\n    return false;\n  }\n  if (!num_inputs_allowed_(def.input_size())) {\n    LOG(ERROR) << \"Input size \" << def.input_size()\n                    << \" not in allowed input sizes.\";\n    return false;\n  }\n  // Check the number of outputs.\n  if (def.output_size() < min_output_ || def.output_size() > max_output_) {\n    LOG(ERROR) << \"Output size \" << def.output_size()\n                    << \" not in range [min=\" << min_output_ << \", max=\"\n                    << max_output_ << \"].\";\n    return false;\n  }\n  if (!num_outputs_allowed_(def.output_size())) {\n    LOG(ERROR) << \"Output size \" << def.output_size()\n                    << \" not in allowed output sizes.\";\n    return false;\n  }\n  if (!num_inputs_outputs_allowed_(def.input_size(), def.output_size())) {\n    LOG(ERROR) << \"Combination of input size \" << def.input_size()\n               << \"and output size \" << def.output_size() << \" not in allowed.\";\n    return false;\n  }\n  // If the number of outputs can be calculated, check if the number matches.\n  if (calculate_output_) {\n    int expected_nout = calculate_output_(def.input_size());\n    if (expected_nout != kCannotComputeNumOutputs &&\n        def.output_size() != expected_nout) {\n      LOG(ERROR) << \"Output size \" << def.output_size()\n                      << \" not matching expected output size, which is \"\n                      << expected_nout;\n      return false;\n    }\n  }\n\n  // Check in-place settings.\n  for (int in_idx = 0; in_idx < def.input_size(); ++in_idx) {\n    for (int out_idx = 0; out_idx < def.output_size(); ++out_idx) {\n      // If an input is the same as an output but in-place is not opt-in\n      // either as allowed or enforced, we will fail the verification.\n      if (def.input(in_idx) == def.output(out_idx) &&\n          (!inplace_allowed_(in_idx, out_idx)\n          && !inplace_enforced_(in_idx, out_idx))) {\n        LOG(ERROR) << \"Input index \" << in_idx << \" and output idx \" << out_idx\n                   << \" (\" << def.input(in_idx) << \")\"\n                   << \" are set to be in-place but this is actually not \"\n                   << \"supported by op \" << def.type();\n        return false;\n      }\n      if (def.input(in_idx) != def.output(out_idx) &&\n          inplace_enforced_(in_idx, out_idx)) {\n        LOG(ERROR) << \"Input index \" << in_idx << \" (\" << def.input(in_idx) << \")\"\n                   << \" and output idx \" << out_idx\n                   << \" (\" << def.output(in_idx) << \")\"\n                   << \" are not in-place but should be as required by op \"\n                   << def.type();\n        return false;\n      }\n    }\n  }\n\n  std::set<std::string> present_args{};\n  for (const auto& arg : def.arg()) {\n    present_args.insert(arg.name());\n  }\n\n  for (const auto& arg : args()) {\n    if (arg.is_required() &&\n        present_args.find(arg.name()) == present_args.end()) {\n      LOG(ERROR) << \"Argument '\" << arg.name() << \"' is required for Operator '\"\n                 << def.type() << \"'.\";\n      return false;\n    }\n  }\n\n  // Phew. All verifications passed.\n  return true;\n}\n\nOpSchema& OpSchema::NumInputs(int min, int max) {\n  min_input_ = min;\n  max_input_ = max;\n  return *this;\n}\n\nOpSchema& OpSchema::NumInputs(int n) {\n  return NumInputs(n, n);\n}\n\nOpSchema& OpSchema::NumInputs(std::function<bool(int)> func) {\n  num_inputs_allowed_ = func;\n  return *this;\n}\n\nOpSchema& OpSchema::NumInputs(set<int> allowed_input_nums) {\n  return NumInputs(\n      [allowed_input_nums](int n)->bool {\n        return allowed_input_nums.count(n);\n      });\n}\n\nOpSchema& OpSchema::NumOutputs(int min, int max) {\n  min_output_ = min;\n  max_output_ = max;\n  return *this;\n}\n\nOpSchema& OpSchema::NumOutputs(int n) {\n  return NumOutputs(n, n);\n}\n\nOpSchema& OpSchema::NumOutputs(std::function<bool(int)> func) {\n  num_outputs_allowed_ = func;\n  return *this;\n}\n\nOpSchema& OpSchema::NumOutputs(set<int> allowed_output_nums) {\n  return NumOutputs(\n      [allowed_output_nums](int n)->bool {\n        return allowed_output_nums.count(n);\n      });\n}\n\nOpSchema& OpSchema::NumInputsOutputs(std::function<bool(int, int)> func) {\n  num_inputs_outputs_allowed_ = func;\n  return *this;\n}\n\nOpSchema& OpSchema::OutputCalculator(std::function<int(int)> calc) {\n  calculate_output_ = calc;\n  return *this;\n}\n\nOpSchema& OpSchema::SameNumberOfOutput() {\n  return OutputCalculator([](int n)->int { return n; } );\n}\n\nOpSchema& OpSchema::AllowInplace(std::function<bool(int, int)> inplace) {\n  inplace_allowed_ = inplace;\n  return *this;\n}\n\nOpSchema& OpSchema::AllowInplace(set<std::pair<int, int>> inplace) {\n  return AllowInplace(\n      [inplace](int in, int out)->bool {\n        return inplace.count(std::make_pair(in, out));\n      });\n}\n\nOpSchema& OpSchema::AllowOneToOneInplace() {\n  return AllowInplace([](int in, int out) { return in == out; });\n}\n\nOpSchema& OpSchema::EnforceInplace(std::function<bool(int, int)> inplace) {\n  inplace_enforced_ = inplace;\n  return *this;\n}\n\nOpSchema& OpSchema::EnforceInplace(set<std::pair<int, int>> inplace) {\n  return EnforceInplace(\n      [inplace](int in, int out)->bool {\n        return inplace.count(std::make_pair(in, out));\n      });\n}\n\nOpSchema& OpSchema::EnforceOneToOneInplace() {\n  return EnforceInplace([](int in, int out) { return in == out; });\n}\n\nOpSchema& OpSchema::Private() {\n  private_ = true;\n  return *this;\n}\n\nOpSchema& OpSchema::InputsCanCrossDevices() {\n  inputs_can_cross_devices_ = true;\n  return *this;\n}\n\nOpSchema& OpSchema::TensorInferenceFunction(\n    TensorInferenceFunctionType function) {\n  tensor_inference_function_ = function;\n  return *this;\n}\n\nOpSchema& OpSchema::IdenticalTypeAndShape() {\n  return TensorInferenceFunction(\n      [](const OperatorDef&, const vector<TensorShape>& input_types) {\n        return vector<TensorShape>(input_types);\n      });\n}\n\nOpSchema& OpSchema::IdenticalTypeAndShapeOfInput(int idx) {\n  return TensorInferenceFunction(\n      [idx](const OperatorDef&, const vector<TensorShape>& input_types) {\n        vector<TensorShape> out(1);\n        out[0] = input_types[idx];\n        return out;\n      });\n}\n\nOpSchema& OpSchema::IdenticalTypeAndShapeOfInputDim(int idx, int dim) {\n  return TensorInferenceFunction(\n      [idx, dim](const OperatorDef&, const vector<TensorShape>& input_types) {\n        vector<TensorShape> out(1);\n        out[0].add_dims(input_types[idx].dims(dim));\n        out[0].set_data_type(input_types[idx].data_type());\n        return out;\n      });\n}\n\nOpSchema& OpSchema::ScalarType(::caffe2::TensorProto_DataType dt) {\n  return TensorInferenceFunction(\n      [dt](const OperatorDef&, const vector<TensorShape>& /*input_types*/) {\n        vector<TensorShape> out(1);\n        out[0].set_data_type(dt);\n        return out;\n      });\n}\n\nOpSchema& OpSchema::CostInferenceFunction(CostInferenceFunctionType function) {\n  cost_inference_function_ =\n      caffe2::make_unique<CostInferenceFunctionType>(function);\n  return *this;\n}\n\nOpSchema& OpSchema::DeviceInferenceFunction(\n    DeviceInferenceFunctionType function) {\n  device_inference_function_ = function;\n  return *this;\n}\n\nOpSchema& OpSchema::SetDoc(const string& doc) {\n  doc_ = doc;\n  return *this;\n}\n\nOpSchema&\nOpSchema::Arg(const char* name, const char* description, bool required) {\n  args_.push_back(Argument(name, description, required));\n  return *this;\n}\n\n#define DEFINE_STANDARG_ARG(name, str)                                \\\n  CAFFE2_API const char* OpSchema::Arg_##name = #str;                 \\\n  CAFFE2_API OpSchema& OpSchema::Arg##name(const char* description) { \\\n    return Arg(#str, description, true);                              \\\n  }\n\nDEFINE_STANDARG_ARG(IsTest, is_test)\n\n#undef DEFINE_STANDARG_ARG\n\nOpSchema& OpSchema::Input(const int n, const char* name, const char* description) {\n  if (input_desc_.size() <= n) {\n    input_desc_.resize(n + 1);\n  }\n  input_desc_[n] = std::make_pair(name, description);\n  return *this;\n}\n\nOpSchema& OpSchema::Output(const int n, const char* name, const char* description) {\n  if (output_desc_.size() <= n) {\n    output_desc_.resize(n + 1);\n  }\n  output_desc_[n] = std::make_pair(name, description);\n  return *this;\n}\n\nOpSchema& OpSchema::FillUsing(std::function<void(OpSchema&)> populator) {\n  if (populator) {\n    populator(*this);\n  }\n  return *this;\n}\n\nint OpSchema::CalculateOutput(int num_input) const {\n  if (min_output_ == max_output_) {\n    return min_output_;\n  } else if (calculate_output_) {\n    return calculate_output_(num_input);\n  } else {\n    return kCannotComputeNumOutputs;\n  }\n}\n\nstd::ostream& operator<<(std::ostream& out, const OpSchema& schema) {\n  if (!schema.args().empty()) {\n    out << \"Arguments:\" << std::endl;\n    for (const auto& arg : schema.args()) {\n      out << \"  \" << arg.name() << \" : \" << arg.description() << std::endl;\n    }\n  }\n  if (schema.max_input_ > 0) {\n    out << \"Inputs:\" << std::endl;\n    if (!schema.input_desc_.empty()) {\n      for (int i = 0; i < schema.input_desc_.size(); ++i) {\n        const auto& p = schema.input_desc_[i];\n        out << \"  \" << i << \", \" << (p.first ? p.first : \"(unnamed)\") << \" : \"\n            << (p.second ? p.second : \"(no doc)\") << std::endl;\n      }\n    } else {\n      out << \"  (no explicit description available)\" << std::endl;\n    }\n  }\n  if (schema.max_output_ > 0) {\n    out << \"Outputs:\" << std::endl;\n    if (!schema.output_desc_.empty()) {\n      for (int i = 0; i < schema.output_desc_.size(); ++i) {\n        const auto& p = schema.output_desc_[i];\n        out << \"  \" << i << \", \" << (p.first ? p.first : \"(unnamed)\") << \" : \"\n            << (p.second ? p.second : \"(no doc)\") << std::endl;\n      }\n    } else {\n      out << \"  (no explicit description available)\" << std::endl;\n    }\n  }\n  out << std::endl;\n  if (schema.doc()) {\n    out << schema.doc();\n  } else {\n    out << \"(no documentation yet)\" << std::endl;\n  }\n  out << std::endl;\n  if (schema.line_) {\n    out << \"Defined at \" << schema.file_ << \":\" << schema.line_ << std::endl;\n  }\n  return out;\n}\n\nCaffeMap<string, OpSchema>& OpSchemaRegistry::map() {\n  static CaffeMap<string, OpSchema> map;\n  return map;\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/operator_schema.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_OPERATOR_SCHEMA_H_\n#define CAFFE2_CORE_OPERATOR_SCHEMA_H_\n\n#include <climits>\n#include <functional>\n#include <initializer_list>\n#include <ostream>\n#include <set>\n#include <vector>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\n// A const value returned by OpSchema::CalculateOutput() if the number of\n// output cannot be determined.\nconstexpr int kCannotComputeNumOutputs = -1;\n\n/**\n * @brief A class to record the schema of an op.\n *\n * OpSchema records the common interface of an op specified by its name. This\n * is optional for each operator implemented in Caffe2 but is strongly\n * recommended.\n *\n * To register an OpSchema, one can use the macro OPERATOR_SCHEMA(name) and\n * then append the various functions in the class. For example, for an op\n * that takes in two inputs, one output, and the first input and output\n * could be in-place, can be written as\n *\n *     OPERATOR_SCHEMA(name)\n *         .NumInputs(2).NumOutputs(1).AllowInplace({{0, 0}});\n */\nclass OpSchema {\n public:\n  OpSchema() : file_(\"unknown\"), line_(0) {}\n  OpSchema(const string& file, const int line) : file_(file), line_(line) {}\n\n  /**\n   * @brief Returns the file that the op schema is registered from.\n   */\n  inline const string& file() const {\n    return file_;\n  }\n\n  /**\n   * @brief Returns the line in file that the op schema is registered from.\n   */\n  inline int line() const {\n    return line_;\n  }\n\n  /**\n   * @brief Returns the docstring of the op schema.\n   */\n  inline const char* doc() const {\n    return doc_.empty() ? nullptr : doc_.c_str();\n  }\n\n  /**\n   * @brief Verifies if an operator definition protobuf matches the pattern\n   * specified in the schema.\n   */\n  bool Verify(const OperatorDef& def) const;\n\n  // Functions to set the property of the operator schemas.\n  // Sets the number of inputs, either a fixed number or a min and a max.\n\n  /**\n   * @brief A single input.\n   */\n  OpSchema& NumInputs(int n);\n  /**\n   * @brief Input could be in range [min, max], inclusive.\n   */\n  OpSchema& NumInputs(int min, int max);\n  /**\n   * @brief Input could be one of the values specified in allowed_input_nums.\n   */\n  OpSchema& NumInputs(set<int> allowed_input_nums);\n  /**\n   * @brief Input is checked with a specified function.\n   */\n  OpSchema& NumInputs(std::function<bool(int)> func);\n\n  // Sets the number of outputs, either a fixed number, a min and a max,\n  // or a function that takes in the input number and produces an output\n  // number. Use only one function in the set below.\n  /**\n   * @brief A single output.\n   */\n  OpSchema& NumOutputs(int n);\n  /**\n   * @brief Output could be in range [min, max], inclusive.\n   */\n  OpSchema& NumOutputs(int min, int max);\n  /**\n   * @brief Output could be one of the values specified in allowed_output_nums.\n   */\n  OpSchema& NumOutputs(set<int> allowed_output_nums);\n  /**\n   * @brief Output is checked with a specified function.\n   */\n  OpSchema& NumOutputs(std::function<bool(int)> func);\n\n  /**\n   * @brief Relationship between inputs and outputs is checked with a specified\n   * function.\n   */\n  OpSchema& NumInputsOutputs(std::function<bool(int, int)> func);\n\n  // Set the function that can calculate the number of output based on the\n  // number of input. Use only one function in the set below.\n  /**\n   * @brief Set the output calculator to a user-defined function.\n   */\n  OpSchema& OutputCalculator(std::function<int(int)> calc);\n  /**\n   * @brief Set the number of outputs to be the same as the number of inputs.\n   */\n  OpSchema& SameNumberOfOutput();\n\n  // Sets the rule to allow optional in-place operation.\n  OpSchema& AllowInplace(std::function<bool(int, int)> inplace);\n  OpSchema& AllowInplace(set<std::pair<int, int>> inplace);\n  OpSchema& AllowOneToOneInplace();\n  // Sets the rule to enforce in-place opeartion.\n  OpSchema& EnforceInplace(std::function<bool(int, int)> inplace);\n  OpSchema& EnforceInplace(set<std::pair<int, int>> inplace);\n  OpSchema& EnforceOneToOneInplace();\n\n  // Functions to deal with type and shape inference. Basically, this registers\n  // a function that takes in an OperatorDef and a series of input type and\n  // shape specified by TensorProto objects (whose data fields are empty), and\n  // produces a series of output type and shape.\n  typedef std::function<\n      vector<TensorShape>(const OperatorDef&, const vector<TensorShape>&)>\n      TensorInferenceFunctionType;\n  /**\n   * @brief Sets the tensor inference function, which is a std::function object\n   * defined in operator_schema.h.\n   */\n  OpSchema& TensorInferenceFunction(TensorInferenceFunctionType function);\n  /**\n   * @brief Sets the tensor inference function to produce the same output as\n   * the input.\n   */\n  OpSchema& IdenticalTypeAndShape();\n  OpSchema& IdenticalTypeAndShapeOfInput(int idx);\n  OpSchema& IdenticalTypeAndShapeOfInputDim(int idx, int dim);\n  OpSchema& ScalarType(::caffe2::TensorProto_DataType dt);\n\n  /**\n   * @brief A function to allow one to infer the type and shape from the op\n   * schema.\n   */\n  inline vector<TensorShape> InferTensor(\n      const OperatorDef& def,\n      const vector<TensorShape> input_type_shape) const {\n    return tensor_inference_function_(def, input_type_shape);\n  }\n\n  /*\n   * @brief A struct to store various cost information about\n   * an operator such as FLOPs, total memory use and parameters.\n   */\n  struct Cost {\n    uint64_t flops; // Floating point operations.\n    uint64_t bytes_moved; // Total memory used.\n    uint64_t params_bytes; // Memory footprint of parameters\n  };\n  /**\n   * @brief Registers a function that takes in an OperatorDef\n   * and a series of input shapes and returns the total \"cost\"\n   * required to run the operator via struct by value.\n   */\n  typedef std::function<\n      struct Cost(const OperatorDef&, const vector<TensorShape>&)>\n      CostInferenceFunctionType;\n\n  /**\n   * @brief Register the Cost inference function.\n   */\n  OpSchema& CostInferenceFunction(CostInferenceFunctionType function);\n\n#if 0 // def _MSC_VER\n  /**\n   * @brief Register the Cost inference function via a pointer.\n   */\n  template <typename T,\n            typename = std::enable_if<\n                std::is_same<CostInferenceFunctionType&&, T>:value\n                >:type>\n  inline OpSchema& CostInferenceFunction(T func) {\n    // Note: This is here in order to resolve an MSVC compiler issue: it\n    // does not automatically convert a function pointer to a std::function,\n    // and needs an explicit conversion.\n    return CostInferenceFunction(CostInferenceFunctionType(func));\n  }\n#endif // _MSC_VER\n\n  bool HasCostInferenceFunction() const {\n    return !!cost_inference_function_;\n  }\n\n  inline struct Cost InferCost(\n      const OperatorDef& def,\n      const vector<TensorShape>& input_tensor_shape) const {\n    CAFFE_ENFORCE(\n        cost_inference_function_, \"Cost inference function not defined.\");\n    return (*cost_inference_function_)(def, input_tensor_shape);\n  }\n\n  // Functions to do documentation for the operator schema.\n  OpSchema& SetDoc(const string& doc);\n\n  struct Argument {\n    Argument(const char* name, const char* description, bool required)\n        : name_{name}, description_{description}, required_{required} {}\n\n    const char* name() const {\n      return name_;\n    }\n\n    const char* description() const {\n      return description_;\n    }\n\n    bool is_required() const {\n      return required_;\n    }\n\n   private:\n    const char* name_;\n    const char* description_;\n    const bool required_;\n  };\n\n  OpSchema&\n  Arg(const char* name, const char* description, bool required = false);\n\n#define DECLARE_STANDARD_ARG(name, str)     \\\n  CAFFE2_API static const char* Arg_##name; \\\n  CAFFE2_API OpSchema& Arg##name(const char* description);\n\n  DECLARE_STANDARD_ARG(IsTest, is_test)\n\n#undef DECLARE_STANDARD_ARG\n\n  OpSchema& Input(const int n, const char* name, const char* description);\n  OpSchema& Output(const int n, const char* name, const char* description);\n  // Calls the passed function with `this` as an argument. Useful for\n  // adding docs for temlated/macro ops.\n  OpSchema& FillUsing(std::function<void(OpSchema&)> populator);\n\n  // Remove from documentation\n  OpSchema& Private();\n\n  // This op can pass data across devices\n  OpSchema& InputsCanCrossDevices();\n\n  /**\n   * @brief A function to allow one to get the number of outputs based on the\n   * number of inputs, if this schema supports it.\n   */\n  int CalculateOutput(int num_input) const;\n\n  int min_input() const {\n    return min_input_;\n  }\n\n  int max_input() const {\n    return max_input_;\n  }\n\n  int min_output() const {\n    return min_output_;\n  }\n\n  int max_output() const {\n    return max_output_;\n  }\n\n  bool num_inputs_allowed(int x) const {\n    return num_inputs_allowed_(x);\n  }\n\n  bool num_outputs_allowed(int x) const {\n    return num_outputs_allowed_(x);\n  }\n\n  bool num_inputs_outputs_allowed(int x, int y) const {\n    return num_inputs_outputs_allowed_(x, y);\n  }\n\n  int inf() const {\n    return std::numeric_limits<int>::max();\n  }\n\n  friend std::ostream& operator<<(std::ostream& out, const OpSchema& schema);\n\n  const std::vector<Argument>& args() const {\n    return args_;\n  }\n\n  const std::vector<std::pair<const char*, const char*>>& input_desc() const {\n    return input_desc_;\n  }\n  const std::vector<std::pair<const char*, const char*>>& output_desc() const {\n    return output_desc_;\n  }\n  bool private_op() {\n    return private_;\n  }\n  bool inputs_can_cross_devices() const {\n    return inputs_can_cross_devices_;\n  }\n\n  /**\n   * @brief Returns the required device location of inputs and outputs.\n   */\n  using DeviceInferenceFunctionType = std::function<\n      std::pair<std::vector<DeviceOption>, std::vector<DeviceOption>>(\n          const OperatorDef& def)>;\n\n  OpSchema& DeviceInferenceFunction(DeviceInferenceFunctionType function);\n\n  /**\n   * @brief Infer required device location of an op's inputs and outputs\n   */\n  inline std::pair<std::vector<DeviceOption>, std::vector<DeviceOption>>\n  InferDevice(const OperatorDef& def) const {\n    return device_inference_function_(def);\n  }\n\n private:\n  string file_;\n  string doc_;\n  std::vector<Argument> args_{};\n  std::vector<std::pair<const char*, const char*>> input_desc_{};\n  std::vector<std::pair<const char*, const char*>> output_desc_{};\n  int line_ = 0;\n  int min_input_ = 0;\n  int max_input_ = std::numeric_limits<int>::max();\n  int min_output_ = 0;\n  int max_output_ = std::numeric_limits<int>::max();\n  bool private_ = false;\n  bool inputs_can_cross_devices_ = false;\n  std::function<bool(int)> num_inputs_allowed_ = [](int) { return true; };\n  std::function<bool(int)> num_outputs_allowed_ = [](int) { return true; };\n  std::function<bool(int, int)> num_inputs_outputs_allowed_ = [](int, int) {\n    return true;\n  };\n  std::function<int(int)> calculate_output_;\n  // In default, any in-place operation is neither allowed nor enforced.\n  std::function<bool(int, int)> inplace_allowed_ = [](int, int) {\n    return false;\n  };\n  std::function<bool(int, int)> inplace_enforced_ = [](int, int) {\n    return false;\n  };\n  TensorInferenceFunctionType tensor_inference_function_ =\n      [](const OperatorDef& def, const vector<TensorShape>&) {\n        vector<TensorShape> out;\n        for (int i = 0; i < def.output_size(); i++) {\n          TensorShape ts;\n          ts.set_unknown_shape(true);\n          out.push_back(ts);\n        }\n        return out;\n      };\n  std::unique_ptr<CostInferenceFunctionType> cost_inference_function_ = nullptr;\n  DeviceInferenceFunctionType device_inference_function_ =\n      [](const OperatorDef& def) {\n        auto op_device =\n            def.has_device_option() ? def.device_option() : DeviceOption();\n        vector<DeviceOption> in_dev(def.input_size(), op_device);\n        vector<DeviceOption> out_dev(def.output_size(), op_device);\n        return std::make_pair(in_dev, out_dev);\n      };\n};\n\n/**\n * @brief A registry to hold all the operator schemas.\n */\nclass OpSchemaRegistry {\n public:\n  static OpSchema&\n  NewSchema(const string& key, const string& file, const int line) {\n    auto& m = map();\n    if (m.count(key)) {\n      const auto& schema = m[key];\n      std::ios_base::Init init;\n      std::cerr << \"Trying to register schema with name \" << key\n                << \" from file \" << file << \" line \" << line\n                << \", but it is already registered from file \" << schema.file()\n                << \" line \" << schema.line();\n      abort();\n    }\n    m.emplace(std::make_pair(key, OpSchema(file, line)));\n    return m[key];\n  }\n\n  static const OpSchema* Schema(const string& key) {\n    auto& m = map();\n    if (m.count(key)) {\n      return &m[key];\n    } else {\n      return nullptr;\n    }\n  }\n\n private:\n  // OpSchemaRegistry should not need to be instantiated.\n  OpSchemaRegistry() = delete;\n\n  /**\n   * @brief Returns the underlying string to OpSchema map.\n   *\n   * You should not manually manipulate the map object returned. Instead, use\n   * the macros defined such as OPERATOR_SCHEMA to register your operator\n   * schema.\n   *\n   * We wrap it inside a function to avoid the static initialization order\n   * fiasco.\n   */\n  static CaffeMap<string, OpSchema>& map();\n};\n\n// Helper function for creating simple tensorproto with dimension and type\ntemplate <typename T_I = int>\ninline TensorShape CreateTensorShape(\n    vector<T_I> dims,\n    ::caffe2::TensorProto_DataType dt) {\n  TensorShape ts;\n  for (int d : dims) {\n    ts.add_dims(d);\n  }\n  ts.set_data_type(dt);\n  return ts;\n}\n\n// Helper function\ninline vector<TIndex> GetDimsVector(const TensorShape& shape) {\n  vector<TIndex> dims;\n  for (auto d : shape.dims()) {\n    dims.push_back(d);\n  }\n  return dims;\n}\n\n// Helper function for infer op inputs and outputs device information.\ninline std::pair<std::vector<DeviceOption>, std::vector<DeviceOption>>\nInferOpInputOutputDevice(const OperatorDef& op) {\n  auto op_schema = OpSchemaRegistry::Schema(op.type());\n  CAFFE_ENFORCE(\n      op_schema, \"Device inference failed. No schema for: \", op.type());\n  // TODO(wyiming) : add try catch here.\n  return op_schema->InferDevice(op);\n}\n\ntemplate <uint64_t OpsPerPoint>\nOpSchema::Cost PointwiseCostInference(\n    const OperatorDef& /* unused */,\n    const vector<TensorShape>& inputs) {\n  struct OpSchema::Cost c;\n  const TensorShape X = inputs[0];\n  uint64_t size = 1;\n\n  for (auto i = 0; i < X.dims().size(); ++i) {\n    size *= X.dims(i);\n  }\n\n  c.flops = size * OpsPerPoint;\n  c.bytes_moved = size;\n  return c;\n}\n\n} // namespace caffe2\n\n#ifndef CAFFE2_NO_OPERATOR_SCHEMA\n\n#define OPERATOR_SCHEMA(name)                            \\\n  void CAFFE2_PLEASE_ADD_OPERATOR_SCHEMA_FOR_##name(){}; \\\n  static OpSchema* CAFFE_ANONYMOUS_VARIABLE(name) =      \\\n      &OpSchemaRegistry::NewSchema(#name, __FILE__, __LINE__)\n#define OPERATOR_SCHEMA_STR(name)                                  \\\n  static OpSchema* CAFFE_ANONYMOUS_VARIABLE(schema_registration) = \\\n      &OpSchemaRegistry::NewSchema(name, __FILE__, __LINE__)\n\n#else // CAFFE2_NO_OPERATOR_SCHEMA\n\n#define OPERATOR_SCHEMA(name)                            \\\n  void CAFFE2_PLEASE_ADD_OPERATOR_SCHEMA_FOR_##name(){}; \\\n  static OpSchema* CAFFE_ANONYMOUS_VARIABLE(name) =      \\\n      1 ? nullptr : &OpSchemaRegistry::NewSchema(#name, __FILE__, __LINE__)\n#define OPERATOR_SCHEMA_STR(name)                                  \\\n  static OpSchema* CAFFE_ANONYMOUS_VARIABLE(schema_registration) = \\\n      1 ? nullptr : &OpSchemaRegistry::NewSchema(name, __FILE__, __LINE__)\n\n#endif // CAFFE2_NO_OPERATOR_SCHEMA\n\n#endif // CAFFE2_CORE_OPERATOR_SCHEMA_H_\n"
  },
  {
    "path": "caffe2/core/operator_schema_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/operator_schema.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\n\nOPERATOR_SCHEMA(OpSchemaTestOp)\n  .NumInputs(1).NumOutputs(1)\n  .SetDoc(R\"DOC(Test Documentation)DOC\")\n  .Input(0, \"in0\", \"dummy input.\")\n  .Output(0, \"out0\", \"dummy output.\");\n\nTEST(OperatorSchemaTest, BasicSchema) {\n  const OpSchema* schema = OpSchemaRegistry::Schema(\"OpSchemaTestOp\");\n#ifdef CAFFE2_NO_OPERATOR_SCHEMA\n  EXPECT_TRUE(schema == nullptr);\n  return;\n#endif\n  EXPECT_TRUE(schema != nullptr);\n  EXPECT_TRUE(schema->doc() != nullptr);\n  OperatorDef def1 = CreateOperatorDef(\n      \"OpSchemaTestOp\", \"\",\n      vector<string>{\"in\"}, vector<string>{\"out\"});\n  EXPECT_TRUE(schema->Verify(def1));\n  OperatorDef def2 = CreateOperatorDef(\n      \"OpSchemaTestOp\", \"\",\n      vector<string>{\"in1\", \"in2\"}, vector<string>{\"out\"});\n  EXPECT_FALSE(schema->Verify(def2));\n  OperatorDef def3 = CreateOperatorDef(\n      \"OpSchemaTestOp\", \"\",\n      vector<string>{\"in\"}, vector<string>{\"out1\", \"out2\"});\n  EXPECT_FALSE(schema->Verify(def3));\n}\n\nOPERATOR_SCHEMA(OpSchemaSpecifiedInputOutputOp)\n  .NumInputs({2, 4}).NumOutputs({1, 3});\n\nTEST(OperatorSchemaTest, SpecifiedInputOutput) {\n  const OpSchema* schema\n      = OpSchemaRegistry::Schema(\"OpSchemaSpecifiedInputOutputOp\");\n#ifdef CAFFE2_NO_OPERATOR_SCHEMA\n  EXPECT_TRUE(schema == nullptr);\n  return;\n#endif\n  EXPECT_TRUE(schema != nullptr);\n  OperatorDef def1 = CreateOperatorDef(\n      \"OpSchemaSpecifiedInputOutputOp\", \"\",\n      vector<string>{\"in\"}, vector<string>{\"out\"});\n  EXPECT_FALSE(schema->Verify(def1));\n  OperatorDef def2 = CreateOperatorDef(\n      \"OpSchemaSpecifiedInputOutputOp\", \"\",\n      vector<string>{\"in1\", \"in2\"}, vector<string>{\"out\"});\n  EXPECT_TRUE(schema->Verify(def2));\n  OperatorDef def3 = CreateOperatorDef(\n      \"OpSchemaSpecifiedInputOutputOp\", \"\",\n      vector<string>{\"in1\", \"in2\"}, vector<string>{\"out1\", \"out2\"});\n  EXPECT_FALSE(schema->Verify(def3));\n}\n\nOPERATOR_SCHEMA(OpSchemaInputOutputRelationOp)\n    .NumInputsOutputs([](int in, int out) {\n      return out == in || out == in * 2;\n    });\n\nTEST(OperatorSchemaTest, InputOutputRelation) {\n  const OpSchema* schema\n      = OpSchemaRegistry::Schema(\"OpSchemaInputOutputRelationOp\");\n#ifdef CAFFE2_NO_OPERATOR_SCHEMA\n  EXPECT_TRUE(schema == nullptr);\n  return;\n#endif\n  EXPECT_TRUE(schema != nullptr);\n  OperatorDef def1 = CreateOperatorDef(\n      \"OpSchemaInputOutputRelationOp\", \"\",\n      vector<string>{\"in\"}, vector<string>{\"out\"});\n  EXPECT_TRUE(schema->Verify(def1));\n  OperatorDef def2 = CreateOperatorDef(\n      \"OpSchemaInputOutputRelationOp\", \"\",\n      vector<string>{\"in\"}, vector<string>{\"out1\", \"out2\"});\n  EXPECT_TRUE(schema->Verify(def2));\n  OperatorDef def3 = CreateOperatorDef(\n      \"OpSchemaInputOutputRelationOp\", \"\",\n      vector<string>{\"in1\", \"in2\", \"in3\"}, vector<string>{\"out1\", \"out2\"});\n  EXPECT_FALSE(schema->Verify(def3));\n}\n\nOPERATOR_SCHEMA(OpSchemaSameInputOutputOp)\n    .SameNumberOfOutput();\n\nTEST(OperatorSchemaTest, SameInputOutput) {\n  const OpSchema* schema =\n      OpSchemaRegistry::Schema(\"OpSchemaSameInputOutputOp\");\n#ifdef CAFFE2_NO_OPERATOR_SCHEMA\n  EXPECT_TRUE(schema == nullptr);\n  return;\n#endif\n  OperatorDef def1 = CreateOperatorDef(\n      \"OpSchemaSameInputOutputOp\", \"\",\n      vector<string>{\"in\"}, vector<string>{\"out\"});\n  EXPECT_TRUE(schema->Verify(def1));\n  OperatorDef def2 = CreateOperatorDef(\n      \"OpSchemaSameInputOutputOp\", \"\",\n      vector<string>{\"in1\", \"in2\"}, vector<string>{\"out1\", \"out2\"});\n  EXPECT_TRUE(schema->Verify(def2));\n  OperatorDef def3 = CreateOperatorDef(\n      \"OpSchemaSameInputOutputOp\", \"\",\n      vector<string>{\"in1\", \"in2\"}, vector<string>{\"out1\", \"out2\", \"out3\"});\n  EXPECT_FALSE(schema->Verify(def3));\n}\n\nOPERATOR_SCHEMA(OpSchemaCalculateOutputOp)\n    .NumInputs(1, 5).NumOutputs(2, 6)\n    .OutputCalculator([](int n) { return n + 1; });\n\nTEST(OperatorSchemaTest, CalculateOutput) {\n  const OpSchema* schema =\n      OpSchemaRegistry::Schema(\"OpSchemaCalculateOutputOp\");\n#ifdef CAFFE2_NO_OPERATOR_SCHEMA\n  EXPECT_TRUE(schema == nullptr);\n  return;\n#endif\n  OperatorDef def1 = CreateOperatorDef(\n      \"OpSchemaCalculateOutputOp\", \"\",\n      vector<string>{\"in\"}, vector<string>{\"out\"});\n  EXPECT_FALSE(schema->Verify(def1));\n  OperatorDef def2 = CreateOperatorDef(\n      \"OpSchemaCalculateOutputOp\", \"\",\n      vector<string>{\"in1\", \"in2\"}, vector<string>{\"out1\", \"out2\"});\n  EXPECT_FALSE(schema->Verify(def2));\n  OperatorDef def3 = CreateOperatorDef(\n      \"OpSchemaCalculateOutputOp\", \"\",\n      vector<string>{\"in1\", \"in2\"}, vector<string>{\"out1\", \"out2\", \"out3\"});\n  EXPECT_TRUE(schema->Verify(def3));\n}\n\nOPERATOR_SCHEMA(OpSchemaInplace)\n    .NumInputs(2).NumOutputs(2)\n    .AllowInplace({{0, 0}})\n    .EnforceInplace({{1, 1}});\n\nTEST(OperatorSchemaTest, Inplace) {\n  const OpSchema* schema =\n      OpSchemaRegistry::Schema(\"OpSchemaInplace\");\n#ifdef CAFFE2_NO_OPERATOR_SCHEMA\n  EXPECT_TRUE(schema == nullptr);\n  return;\n#endif\n  OperatorDef def1 = CreateOperatorDef(\n      \"OpSchemaInplace\", \"\",\n      vector<string>{\"in1\", \"in2\"}, vector<string>{\"out1\", \"in2\"});\n  EXPECT_TRUE(schema->Verify(def1));\n  OperatorDef def2 = CreateOperatorDef(\n      \"OpSchemaInplace\", \"\",\n      vector<string>{\"in1\", \"in2\"}, vector<string>{\"in1\", \"in2\"});\n  EXPECT_TRUE(schema->Verify(def2));\n  OperatorDef def3 = CreateOperatorDef(\n      \"OpSchemaInplace\", \"\",\n      vector<string>{\"in1\", \"in2\"}, vector<string>{\"in1\", \"out2\"});\n  EXPECT_FALSE(schema->Verify(def3));\n  OperatorDef def4 = CreateOperatorDef(\n      \"OpSchemaInplace\", \"\",\n      vector<string>{\"in1\", \"in2\"}, vector<string>{\"out1\", \"out2\"});\n  EXPECT_FALSE(schema->Verify(def4));\n}\n\nOPERATOR_SCHEMA(OpSchemaSameInputOutputTensorInference).IdenticalTypeAndShape();\n\nTEST(OperatorSchemaTest, TensorInferenceIdentical) {\n  const OpSchema* schema =\n      OpSchemaRegistry::Schema(\"OpSchemaSameInputOutputTensorInference\");\n#ifdef CAFFE2_NO_OPERATOR_SCHEMA\n  EXPECT_TRUE(schema == nullptr);\n  return;\n#endif\n  OperatorDef def = CreateOperatorDef(\n      \"OpSchemaSameInputOutputTensorInference\",\n      \"\",\n      vector<string>{\"in\"},\n      vector<string>{\"out\"});\n  vector<TensorShape> shapes(1);\n  shapes[0].set_data_type(TensorProto::FLOAT);\n  shapes[0].add_dims(1);\n  shapes[0].add_dims(2);\n  shapes[0].add_dims(3);\n  vector<TensorShape> out = schema->InferTensor(def, shapes);\n  EXPECT_EQ(out.size(), 1);\n  EXPECT_EQ(out[0].SerializeAsString(), shapes[0].SerializeAsString());\n}\n\nOPERATOR_SCHEMA(OpSchemaArbitraryTensorInference)\n    .TensorInferenceFunction(\n        [](const OperatorDef&, const vector<TensorShape>&) {\n          vector<TensorShape> shapes(1);\n          shapes[0].set_data_type(TensorProto::FLOAT);\n          shapes[0].add_dims(1701);\n          return shapes;\n        });\n\nTEST(OperatorSchemaTest, TensorInferenceArbitrary) {\n  const OpSchema* schema =\n      OpSchemaRegistry::Schema(\"OpSchemaArbitraryTensorInference\");\n#ifdef CAFFE2_NO_OPERATOR_SCHEMA\n  EXPECT_TRUE(schema == nullptr);\n  return;\n#endif\n  OperatorDef def = CreateOperatorDef(\n      \"OpSchemaArbitraryTensorInference\",\n      \"\",\n      vector<string>{\"in\"},\n      vector<string>{\"out\"});\n  vector<TensorShape> out = schema->InferTensor(def, vector<TensorShape>());\n  EXPECT_EQ(out.size(), 1);\n  EXPECT_EQ(out[0].data_type(), TensorProto::FLOAT);\n  EXPECT_EQ(out[0].dims_size(), 1);\n  EXPECT_EQ(out[0].dims(0), 1701);\n}\n\nTEST(OperatorSchemaTest, TestCastSchema) {\n  // This tests a use case of the schema: the Cast op takes in the def and\n  // deduces the\n  // schema from the \"to\" argument.\n  const OpSchema* schema = OpSchemaRegistry::Schema(\"Cast\");\n#ifdef CAFFE2_NO_OPERATOR_SCHEMA\n  EXPECT_TRUE(schema == nullptr);\n  return;\n#endif\n  if (!schema) {\n    // Compiled without the Cast op.\n    return;\n  }\n  OperatorDef def = CreateOperatorDef(\n      \"Cast\",\n      \"\",\n      vector<string>{\"in\"},\n      vector<string>{\"out\"},\n      vector<Argument>{MakeArgument<int64_t>(\"to\", TensorProto::UINT8)});\n  vector<TensorShape> out = schema->InferTensor(def, vector<TensorShape>(1));\n  EXPECT_EQ(out.size(), 1);\n  // Data type should be inferred.\n  EXPECT_EQ(out[0].data_type(), TensorProto::UINT8);\n  // Dim should not be set (same as input);\n  EXPECT_EQ(out[0].dims_size(), 0);\n}\n\nOPERATOR_SCHEMA(OpSchemaCostInference)\n    .NumInputs(2)\n    .NumOutputs(2)\n    .CostInferenceFunction([](const OperatorDef& /*def*/,\n                              const vector<TensorShape>& inputs) {\n      struct OpSchema::Cost c;\n      c.flops = 2 * inputs[0].dims(0) * inputs[0].dims(1) * inputs[1].dims(1);\n      return c;\n    });\n\nTEST(OperatorSchemaTest, TestCostInference) {\n  const OpSchema* schema = OpSchemaRegistry::Schema(\"OpSchemaCostInference\");\n#ifdef CAFFE2_NO_OPERATOR_SCHEMA\n  EXPECT_TRUE(schema == nullptr);\n  return;\n#endif\n  if (!schema) {\n    return;\n  }\n  OperatorDef def = CreateOperatorDef(\n      \"OpSchemaCostInference\", \"\", vector<string>{\"in\"}, vector<string>{\"out\"});\n  vector<TensorShape> shapes(2);\n  shapes[0].set_data_type(TensorProto::FLOAT);\n  shapes[0].add_dims(10);\n  shapes[0].add_dims(10);\n  shapes[1].set_data_type(TensorProto::FLOAT);\n  shapes[1].add_dims(10);\n  shapes[1].add_dims(10);\n  EXPECT_EQ(2000, schema->InferCost(def, shapes).flops);\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/operator_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/operator.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\n\n// Since we instantiate this on CPU and GPU (but don't want a\n// CUDAContext dependency, we use OperatorBase. In general, you only\n// want to inherit from Operator<Context> in your code.\nclass JustTest : public OperatorBase {\n public:\n  using OperatorBase::OperatorBase;\n  bool Run(int /* unused */ /*stream_id*/) override {\n    return true;\n  }\n  virtual string type() {\n    return \"base\";\n  }\n};\n\nclass JustTestAndNeverConstructs : public JustTest {\n public:\n  JustTestAndNeverConstructs(const OperatorDef& def, Workspace* ws)\n      : JustTest(def, ws) {\n    throw UnsupportedOperatorFeature(\"I just don't construct.\");\n  }\n  bool Run(int /* unused */ /*stream_id*/) override {\n    return true;\n  }\n  string type() override {\n    return \"FOO\";\n  }\n};\n\nclass JustTestAndDoesConstruct : public JustTest {\n public:\n  using JustTest::JustTest;\n  bool Run(int /* unused */ /*stream_id*/) override {\n    return true;\n  }\n  string type() override {\n    return \"BAR\";\n  }\n};\n\nclass JustTestWithSomeOutput : public JustTest {\n public:\n  using JustTest::JustTest;\n  bool Run(int /* unused */ /*stream_id*/) override {\n    *OperatorBase::Output<int>(0) = 5;\n    return true;\n  }\n  string type() override {\n    return \"SETTING_SOME_OUTPUT\";\n  }\n};\n\nclass ThrowException : public Operator<CPUContext> {\n public:\n  explicit ThrowException(const OperatorDef& op_def, Workspace* ws)\n      : Operator<CPUContext>(op_def, ws) {}\n  bool RunOnDevice() override {\n    CAFFE_THROW(\"Throwing an exception.\");\n  }\n};\n\nOPERATOR_SCHEMA(JustTest).NumInputs(0, 1).NumOutputs(0, 1);\nOPERATOR_SCHEMA(JustTestCPUOnly).NumInputs(0, 1).NumOutputs(0, 1);\nOPERATOR_SCHEMA(ThrowException).NumInputs(0).NumOutputs(0);\nOPERATOR_SCHEMA(JustTestWithSomeOutput);\n\nREGISTER_CPU_OPERATOR(JustTest, JustTest);\nREGISTER_CPU_OPERATOR(JustTestCPUOnly, JustTest);\nREGISTER_CPU_OPERATOR_WITH_ENGINE(JustTest, FOO, JustTestAndNeverConstructs);\nREGISTER_CPU_OPERATOR_WITH_ENGINE(JustTest, BAR, JustTestAndDoesConstruct);\nREGISTER_CPU_OPERATOR_WITH_ENGINE(JustTest, BAZ, JustTestAndDoesConstruct);\nREGISTER_CUDA_OPERATOR(JustTest, JustTest);\nREGISTER_CPU_OPERATOR(ThrowException, ThrowException);\nREGISTER_CPU_OPERATOR(JustTestWithSomeOutput, JustTestWithSomeOutput);\n\nTEST(OperatorTest, DeviceTypeRegistryWorks) {\n  EXPECT_EQ(gDeviceTypeRegistry()->count(DeviceType::CPU), 1);\n}\n\nTEST(OperatorTest, RegistryWorks) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"JustTest\");\n  unique_ptr<OperatorBase> op = CreateOperator(op_def, &ws);\n  EXPECT_NE(nullptr, op.get());\n  // After introducing events, CUDA operator creation has to have CUDA compiled\n  // as it needs to instantiate an Event object with CUDAContext. Thus we will\n  // guard this test below.\n  if (HasCudaRuntime()) {\n    op_def.mutable_device_option()->set_device_type(CUDA);\n    op = CreateOperator(op_def, &ws);\n    EXPECT_NE(nullptr, op.get());\n  }\n}\n\nTEST(OperatorTest, RegistryWrongDevice) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"JustTypeCPUOnly\");\n  op_def.mutable_device_option()->set_device_type(CUDA);\n  try {\n    CreateOperator(op_def, &ws);\n    LOG(FATAL) << \"No exception was thrown\";\n  } catch (const std::exception& e) {\n    LOG(INFO) << \"Exception \" << e.what();\n  }\n}\n\nTEST(OperatorTest, ExceptionWorks) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"ThrowException\");\n  unique_ptr<OperatorBase> op = CreateOperator(op_def, &ws);\n  // Note: we do not do ASSERT_THROW in order to print out\n  // the error message for inspection.\n  try {\n    op->Run();\n    // This should not happen - exception should throw above.\n    LOG(FATAL) << \"This should not happen.\";\n  } catch (const EnforceNotMet& err) {\n    LOG(INFO) << err.msg();\n  }\n  try {\n    op->RunAsync();\n    // This should not happen - exception should throw above.\n    LOG(FATAL) << \"This should not happen.\";\n  } catch (const EnforceNotMet& err) {\n    LOG(INFO) << err.msg();\n  }\n}\n\nTEST(OperatorTest, FallbackIfEngineDoesNotBuild) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"JustTest\");\n  op_def.set_engine(\"FOO\");\n  unique_ptr<OperatorBase> op = CreateOperator(op_def, &ws);\n  EXPECT_NE(nullptr, op.get());\n  EXPECT_EQ(static_cast<JustTest*>(op.get())->type(), \"base\");\n}\n\nTEST(OperatorTest, MultipleEngineChoices) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"JustTest\");\n  op_def.set_engine(\"FOO,BAR\");\n  unique_ptr<OperatorBase> op = CreateOperator(op_def, &ws);\n  EXPECT_NE(nullptr, op.get());\n  EXPECT_EQ(static_cast<JustTest*>(op.get())->type(), \"BAR\");\n}\n\nTEST(OperatorTest, CannotUseUninitializedBlob) {\n  Workspace ws;\n  OperatorDef op_def;\n  op_def.set_name(\"JustTest0\");\n  op_def.set_type(\"JustTest\");\n  op_def.add_input(\"input\");\n  op_def.add_output(\"output\");\n  ASSERT_THROW(CreateOperator(op_def, &ws), EnforceNotMet);\n}\n\nTEST(OperatorTest, TestParameterAccess) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_name(\"JustTest0\");\n  op_def.set_type(\"JustTest\");\n  op_def.add_input(\"input\");\n  op_def.add_output(\"output\");\n  AddArgument<float>(\"arg0\", 0.1, &op_def);\n  AddArgument<vector<int>>(\"arg1\", vector<int>{1, 2}, &op_def);\n  AddArgument<string>(\"arg2\", \"argstring\", &op_def);\n  EXPECT_NE(ws.CreateBlob(\"input\"), nullptr);\n  OperatorBase op(op_def, &ws);\n  EXPECT_FLOAT_EQ(op.GetSingleArgument<float>(\"arg0\", 0.0), 0.1);\n  vector<int> i = op.GetRepeatedArgument<int>(\"arg1\");\n  EXPECT_EQ(i.size(), 2);\n  EXPECT_EQ(i[0], 1);\n  EXPECT_EQ(i[1], 2);\n  EXPECT_EQ(op.GetSingleArgument<string>(\"arg2\", \"default\"), \"argstring\");\n  auto default1 = op.GetRepeatedArgument<int>(\"arg3\", {2, 3});\n  EXPECT_EQ(default1.size(), 2);\n  EXPECT_EQ(default1[0], 2);\n  EXPECT_EQ(default1[1], 3);\n  auto default2 = op.GetRepeatedArgument<int>(\"arg4\");\n  EXPECT_EQ(default2.size(), 0);\n}\n\nTEST(OperatorTest, CannotAccessParameterWithWrongType) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_name(\"JustTest0\");\n  op_def.set_type(\"JustTest\");\n  op_def.add_input(\"input\");\n  op_def.add_output(\"output\");\n  AddArgument<float>(\"arg0\", 0.1f, &op_def);\n  EXPECT_NE(ws.CreateBlob(\"input\"), nullptr);\n  OperatorBase op(op_def, &ws);\n  EXPECT_FLOAT_EQ(op.GetSingleArgument<float>(\"arg0\", 0.0), 0.1);\n  ASSERT_THROW(op.GetSingleArgument<int>(\"arg0\", 0), EnforceNotMet);\n}\n\n#if GTEST_HAS_DEATH_TEST\nTEST(OperatorDeathTest, DISABLED_CannotAccessRepeatedParameterWithWrongType) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_name(\"JustTest0\");\n  op_def.set_type(\"JustTest\");\n  op_def.add_input(\"input\");\n  op_def.add_output(\"output\");\n  AddArgument<vector<float>>(\"arg0\", vector<float>{0.1f}, &op_def);\n  EXPECT_NE(ws.CreateBlob(\"input\"), nullptr);\n  OperatorBase op(op_def, &ws);\n  auto args = op.GetRepeatedArgument<float>(\"arg0\");\n  EXPECT_EQ(args.size(), 1);\n  EXPECT_FLOAT_EQ(args[0], 0.1f);\n  EXPECT_DEATH(op.GetRepeatedArgument<int>(\"arg0\"),\n               \"Argument does not have the right field: expected ints\");\n}\n#endif\n\nTEST(OperatorTest, TestDefaultValue) {\n  OperatorDef op_def;\n  Workspace ws;\n  OperatorBase op(op_def, &ws);\n  EXPECT_FLOAT_EQ(op.GetSingleArgument<float>(\"arg-nonexisting\", 0.5f), 0.5f);\n}\n\nTEST(OperatorTest, TestSetUp) {\n  Workspace ws;\n  OperatorDef op_def;\n  op_def.set_name(\"JustTest0\");\n  op_def.set_type(\"JustTest\");\n  op_def.add_input(\"input\");\n  op_def.add_output(\"output\");\n  EXPECT_NE(nullptr, ws.CreateBlob(\"input\"));\n  unique_ptr<OperatorBase> op(CreateOperator(op_def, &ws));\n  EXPECT_NE(nullptr, op.get());\n  EXPECT_TRUE(ws.HasBlob(\"output\"));\n}\n\nTEST(OperatorTest, TestSetUpInputOutputCount) {\n  Workspace ws;\n  OperatorDef op_def;\n  op_def.set_name(\"JustTest0\");\n  op_def.set_type(\"JustTest\");\n  op_def.add_input(\"input\");\n  op_def.add_input(\"input2\");\n  op_def.add_output(\"output\");\n  EXPECT_NE(nullptr, ws.CreateBlob(\"input\"));\n  EXPECT_NE(nullptr, ws.CreateBlob(\"input2\"));\n#ifndef CAFFE2_NO_OPERATOR_SCHEMA\n  // JustTest will only accept one single input.\n  ASSERT_ANY_THROW(CreateOperator(op_def, &ws));\n#endif\n\n  op_def.clear_input();\n  op_def.add_input(\"input\");\n  op_def.add_output(\"output2\");\n#ifndef CAFFE2_NO_OPERATOR_SCHEMA\n  // JustTest will only produce one single output.\n  ASSERT_ANY_THROW(CreateOperator(op_def, &ws));\n#endif\n}\n\nTEST(OperatorTest, TestOutputValues) {\n  NetDef net_def;\n  net_def.set_name(\"NetForTest\");\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_name(\"JustTest1\");\n  op_def.set_type(\"JustTestWithSomeOutput\");\n  op_def.add_output(\"output\");\n  // JustTest will only produce one single output.\n  net_def.add_op()->CopyFrom(op_def);\n  unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n  EXPECT_TRUE(net->Run());\n  EXPECT_TRUE(ws.HasBlob(\"output\"));\n  EXPECT_EQ(ws.GetBlob(\"output\")->Get<int>(), 5);\n}\n\nNetDef GetNetDefForTest() {\n  NetDef net_def;\n  OperatorDef op_def;\n  net_def.set_name(\"NetForTest\");\n  op_def.set_name(\"JustTest0\");\n  op_def.set_type(\"JustTest\");\n  op_def.add_input(\"input\");\n  op_def.add_output(\"hidden\");\n  net_def.add_op()->CopyFrom(op_def);\n  op_def.set_name(\"JustTest1\");\n  op_def.set_input(0, \"hidden\");\n  op_def.set_output(0, \"output\");\n  net_def.add_op()->CopyFrom(op_def);\n  return net_def;\n}\n\nTEST(NetTest, TestScaffoldingSimpleNet) {\n  NetDef net_def = GetNetDefForTest();\n  net_def.set_type(\"simple\");\n  Workspace ws;\n  EXPECT_NE(nullptr, ws.CreateBlob(\"input\"));\n  unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n  EXPECT_NE(nullptr, net.get());\n  EXPECT_TRUE(ws.HasBlob(\"input\"));\n  EXPECT_TRUE(ws.HasBlob(\"hidden\"));\n  EXPECT_TRUE(ws.HasBlob(\"output\"));\n  EXPECT_TRUE(net->Run());\n}\n\nTEST(NetTest, TestScaffoldingDAGNet) {\n  NetDef net_def = GetNetDefForTest();\n  net_def.set_type(\"dag\");\n  net_def.set_num_workers(1);\n  Workspace ws;\n  EXPECT_NE(nullptr, ws.CreateBlob(\"input\"));\n  unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n  EXPECT_NE(nullptr, net.get());\n  EXPECT_TRUE(ws.HasBlob(\"input\"));\n  EXPECT_TRUE(ws.HasBlob(\"hidden\"));\n  EXPECT_TRUE(ws.HasBlob(\"output\"));\n  EXPECT_TRUE(net->Run());\n}\n\nclass GetFooGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return vector<OperatorDef>{\n        CreateOperatorDef(\n            \"FooGradient\", \"\",\n            std::vector<string>{GO(0)},\n            std::vector<string>{GI(0)})};\n  }\n};\n\nREGISTER_GRADIENT(Foo, GetFooGradient);\n\nTEST(OperatorGradientRegistryTest, GradientSimple) {\n  Argument arg = MakeArgument<int>(\"arg\", 1);\n  DeviceOption option;\n  option.set_device_type(CPU);\n  OperatorDef def = CreateOperatorDef(\n      \"Foo\", \"\", std::vector<string>{\"in\"}, std::vector<string>{\"out\"},\n      std::vector<Argument>{arg}, option, \"DUMMY_ENGINE\");\n  vector<GradientWrapper> g_output(1);\n  g_output[0].dense_ = \"out_grad\";\n  GradientOpsMeta meta = GetGradientForOp(def, g_output);\n  // Check the names, input and output.\n  EXPECT_EQ(meta.ops_.size(), 1);\n  const OperatorDef& grad_op = meta.ops_[0];\n  EXPECT_EQ(grad_op.type(), \"FooGradient\");\n  EXPECT_EQ(grad_op.name(), \"\");\n  EXPECT_EQ(grad_op.input_size(), 1);\n  EXPECT_EQ(grad_op.output_size(), 1);\n  EXPECT_EQ(grad_op.input(0), \"out_grad\");\n  EXPECT_EQ(grad_op.output(0), \"in_grad\");\n  // Checks the engine, device option and arguments.\n  EXPECT_EQ(grad_op.engine(), \"DUMMY_ENGINE\");\n  EXPECT_EQ(grad_op.device_option().device_type(), CPU);\n  EXPECT_EQ(grad_op.arg_size(), 1);\n  EXPECT_EQ(grad_op.arg(0).SerializeAsString(),\n            MakeArgument<int>(\"arg\", 1).SerializeAsString());\n  // Checks the gradient name for input.\n  EXPECT_EQ(meta.g_input_.size(), 1);\n  EXPECT_TRUE(meta.g_input_[0].IsDense());\n  EXPECT_EQ(meta.g_input_[0].dense_, \"in_grad\");\n}\n\nTEST(EnginePrefTest, PerOpEnginePref) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"JustTest\");\n\n  SetPerOpEnginePref({{DeviceType::CPU, {{\"JustTest\", {\"BAR\"}}}}});\n  {\n    const auto op = CreateOperator(op_def, &ws);\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_EQ(static_cast<JustTest*>(op.get())->type(), \"BAR\");\n  }\n  // clear\n  SetPerOpEnginePref({});\n\n  // Invalid operator type\n  ASSERT_THROW(\n      SetPerOpEnginePref({{DeviceType::CPU, {{\"NO_EXIST\", {\"BAR\"}}}}}),\n      EnforceNotMet);\n}\n\nTEST(EnginePrefTest, GlobalEnginePref) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"JustTest\");\n\n  SetGlobalEnginePref({{DeviceType::CPU, {\"FOO\", \"BAR\"}}});\n  {\n    const auto op = CreateOperator(op_def, &ws);\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_EQ(static_cast<JustTest*>(op.get())->type(), \"BAR\");\n  }\n  // clear\n  SetGlobalEnginePref({});\n\n  SetGlobalEnginePref({{DeviceType::CPU, {\"FOO\"}}});\n  {\n    const auto op = CreateOperator(op_def, &ws);\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_EQ(static_cast<JustTest*>(op.get())->type(), \"base\");\n  }\n  // clear\n  SetGlobalEnginePref({});\n\n  // Invalid device type\n  ASSERT_THROW(SetGlobalEnginePref({{8888, {\"FOO\"}}}), EnforceNotMet);\n}\n\nTEST(EnginePrefTest, GlobalEnginePrefAndPerOpEnginePref) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"JustTest\");\n\n  SetPerOpEnginePref({{DeviceType::CPU, {{\"JustTest\", {\"BAR\"}}}}});\n  SetGlobalEnginePref({{DeviceType::CPU, {\"BAZ\"}}});\n  {\n    const auto op = CreateOperator(op_def, &ws);\n    EXPECT_NE(nullptr, op.get());\n    // per op pref takes precedence\n    EXPECT_EQ(static_cast<JustTest*>(op.get())->type(), \"BAR\");\n  }\n  // clear\n  SetPerOpEnginePref({});\n  SetGlobalEnginePref({});\n}\n\nTEST(EnginePrefTest, GlobalEnginePrefAndPerOpEnginePrefAndOpDef) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"JustTest\");\n  op_def.set_engine(\"BAR\");\n\n  SetPerOpEnginePref({{DeviceType::CPU, {{\"JustTest\", {\"BAZ\"}}}}});\n  SetGlobalEnginePref({{DeviceType::CPU, {\"BAZ\"}}});\n  {\n    const auto op = CreateOperator(op_def, &ws);\n    EXPECT_NE(nullptr, op.get());\n    // operator_def takes precedence\n    EXPECT_EQ(static_cast<JustTest*>(op.get())->type(), \"BAR\");\n  }\n  // clear\n  SetPerOpEnginePref({});\n  SetGlobalEnginePref({});\n}\n\nTEST(EnginePrefTest, SetOpEnginePref) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"JustTest\");\n\n  SetPerOpEnginePref({{DeviceType::CPU, {{\"JustTest\", {\"BAZ\"}}}}});\n  SetOpEnginePref(\"JustTest\", {{DeviceType::CPU, {\"BAR\"}}});\n  {\n    const auto op = CreateOperator(op_def, &ws);\n    EXPECT_NE(nullptr, op.get());\n    // operator_def takes precedence\n    EXPECT_EQ(static_cast<JustTest*>(op.get())->type(), \"BAR\");\n  }\n  // clear\n  SetPerOpEnginePref({});\n  SetGlobalEnginePref({});\n}\n\nTEST(EnginePrefTest, SetDefaultEngine) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"JustTest\");\n\n  SetPerOpEnginePref({{DeviceType::CPU, {{\"JustTest\", {\"DEFAULT\"}}}}});\n  SetGlobalEnginePref({{DeviceType::CPU, {\"BAR\"}}});\n  {\n    const auto op = CreateOperator(op_def, &ws);\n    EXPECT_NE(nullptr, op.get());\n    // operator_def takes precedence\n    EXPECT_EQ(static_cast<JustTest*>(op.get())->type(), \"base\");\n  }\n  // clear\n  SetPerOpEnginePref({});\n  SetGlobalEnginePref({});\n}\n\nclass JustTestWithRequiredArg : public JustTest {\n public:\n  using JustTest::JustTest;\n  bool Run(int /* unused */ /*stream_id*/) override {\n    return true;\n  }\n  string type() override {\n    return \"JustTestWithRequiredArg\";\n  }\n};\n\nREGISTER_CPU_OPERATOR(JustTestWithRequiredArg, JustTestWithRequiredArg);\nOPERATOR_SCHEMA(JustTestWithRequiredArg)\n    .NumInputs(0, 1)\n    .NumOutputs(0, 1)\n    .Arg(\"test_arg\", \"this arg is required\", true);\n\nTEST(RequiredArg, Basic) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"JustTestWithRequiredArg\");\n\n  {\n    try {\n      CreateOperator(op_def, &ws);\n      LOG(FATAL) << \"No exception was thrown\";\n    } catch (const std::exception& e) {\n      LOG(INFO) << \"Exception thrown (expected): \" << e.what();\n    }\n  }\n\n  {\n    op_def.add_arg()->CopyFrom(MakeArgument(\"test_arg\", 1));\n    const auto op = CreateOperator(op_def, &ws);\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_EQ(\n        static_cast<JustTest*>(op.get())->type(), \"JustTestWithRequiredArg\");\n  }\n}\n\nclass JustTestWithStandardIsTestArg : public JustTest {\n public:\n  using JustTest::JustTest;\n  bool Run(int /* unused */ /*stream_id*/) override {\n    return true;\n  }\n  string type() override {\n    return \"JustTestWithStandardIsTestArg\";\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    JustTestWithStandardIsTestArg,\n    JustTestWithStandardIsTestArg);\nOPERATOR_SCHEMA(JustTestWithStandardIsTestArg)\n    .NumInputs(0, 1)\n    .NumOutputs(0, 1)\n    .ArgIsTest(\"this is_test arg is required\");\n\nTEST(IsTestArg, standard) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"JustTestWithStandardIsTestArg\");\n\n  {\n    try {\n      CreateOperator(op_def, &ws);\n      LOG(FATAL) << \"No exception was thrown\";\n    } catch (const std::exception& e) {\n      LOG(INFO) << \"Exception thrown (expected): \" << e.what();\n    }\n  }\n\n  {\n    op_def.add_arg()->CopyFrom(MakeArgument(OpSchema::Arg_IsTest, 1));\n    const auto op = CreateOperator(op_def, &ws);\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_EQ(\n        static_cast<JustTest*>(op.get())->type(),\n        \"JustTestWithStandardIsTestArg\");\n  }\n}\n\nclass JustTestWithNonStandardIsTestArg : public JustTest {\n public:\n  using JustTest::JustTest;\n  bool Run(int /* unused */ /*stream_id*/) override {\n    return true;\n  }\n  string type() override {\n    return \"JustTestWithNonStandardIsTestArg\";\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    JustTestWithNonStandardIsTestArg,\n    JustTestWithNonStandardIsTestArg);\nOPERATOR_SCHEMA(JustTestWithNonStandardIsTestArg)\n    .NumInputs(0, 1)\n    .NumOutputs(0, 1)\n    .Arg(OpSchema::Arg_IsTest, \"this is_test arg is not required\");\n\nTEST(IsTestArg, non_standard) {\n  OperatorDef op_def;\n  Workspace ws;\n  op_def.set_type(\"JustTestWithNonStandardIsTestArg\");\n\n  const auto op = CreateOperator(op_def, &ws);\n  EXPECT_NE(nullptr, op.get());\n  EXPECT_EQ(\n      static_cast<JustTest*>(op.get())->type(),\n      \"JustTestWithNonStandardIsTestArg\");\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/parallel_net_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <chrono>  // NOLINT\n#include <ctime>\n#include <thread>  // NOLINT\n\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/operator.h\"\n#include \"google/protobuf/text_format.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\n\nusing std::clock_t;\nusing std::clock;\n\n// When measuring time, we relax the measured time by +- 20ms.\nconst int kTimeThreshold = 20;\n\n// SleepOp basically sleeps for a given number of seconds.\n// We allow arbitrary inputs and at most one output so that we can\n// test scaffolding of networks. If the output is 1, it will be filled with\n// vector<clock_t> with two elements: start time and end time.\nclass SleepOp final : public Operator<CPUContext> {\n public:\n  SleepOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        ms_(OperatorBase::GetSingleArgument<int>(\"ms\", 1000)) {\n    DCHECK_GT(ms_, 0);\n    DCHECK_LT(ms_, 3600 * 1000) << \"Really? This long?\";\n  }\n\n  bool RunOnDevice() override {\n    clock_t start = clock();\n    std::this_thread::sleep_for(std::chrono::milliseconds(ms_));\n    clock_t end = clock();\n    if (OperatorBase::OutputSize()) {\n      vector<clock_t>* output = OperatorBase::Output<vector<clock_t> >(0);\n      output->resize(2);\n      (*output)[0] = start;\n      (*output)[1] = end;\n    }\n    return true;\n  }\n\n private:\n  int ms_;\n};\n\nOPERATOR_SCHEMA(Sleep).NumInputs(0, INT_MAX).NumOutputs(0, 1);\n\nREGISTER_CPU_OPERATOR(Sleep, SleepOp);\nREGISTER_CUDA_OPERATOR(Sleep, SleepOp);\n\nconst char kSleepNetDefString[] =\n\"  name: \\\"sleepnet\\\"\"\n\"  type: \\\"dag\\\"\"\n\"  num_workers: 2\"\n\"  op {\"\n\"    output: \\\"sleep1\\\"\"\n\"    name: \\\"sleep1\\\"\"\n\"    type: \\\"Sleep\\\"\"\n\"    arg {\"\n\"      name: \\\"ms\\\"\"\n\"      i: 100\"\n\"    }\"\n\"  }\"\n\"  op {\"\n\"    input: \\\"sleep1\\\"\"\n\"    output: \\\"sleep2\\\"\"\n\"    name: \\\"sleep2\\\"\"\n\"    type: \\\"Sleep\\\"\"\n\"    arg {\"\n\"      name: \\\"ms\\\"\"\n\"      i: 100\"\n\"    }\"\n\"  }\"\n\"  op {\"\n\"    output: \\\"sleep3\\\"\"\n\"    name: \\\"sleep3\\\"\"\n\"    type: \\\"Sleep\\\"\"\n\"    arg {\"\n\"      name: \\\"ms\\\"\"\n\"      i: 150\"\n\"    }\"\n\"  }\";\n\nnamespace {\n// Run a network and get its duration in milliseconds.\nint RunNetAndGetDuration(const string& net_def_str, const string& type) {\n  NetDef net_def;\n  CAFFE_ENFORCE(\n      google::protobuf::TextFormat::ParseFromString(net_def_str, &net_def));\n  net_def.set_type(type);\n  Workspace ws;\n  unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n  CAFFE_ENFORCE(net.get() != nullptr);\n  auto start_time = std::chrono::system_clock::now();\n  CAFFE_ENFORCE(net->Run());\n  // Inspect the time - it should be around 200 milliseconds, since sleep3 can\n  // run in parallel with sleep1 and sleep2.\n  auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(\n      std::chrono::system_clock::now() - start_time);\n  int milliseconds = duration.count();\n  return milliseconds;\n}\n}  // namespace\n\nTEST(DAGNetTest, TestDAGNetTiming) {\n  int ms = RunNetAndGetDuration(string(kSleepNetDefString), \"dag\");\n  EXPECT_NEAR(ms, 200, kTimeThreshold);\n}\n\n// For sanity check, we also test the sequential time - it should take 0.35\n// seconds instead since everything has to be sequential.\nTEST(SimpleNetTest, TestSimpleNetTiming) {\n  int ms = RunNetAndGetDuration(string(kSleepNetDefString), \"simple\");\n  EXPECT_NEAR(ms, 350, kTimeThreshold);\n}\n\n// This network has two operators reading the same blob at the same time. This\n// should not change anything and the DAG should still make sleep2 and sleep3\n// run in parallel.\nconst char kSleepNetDefStringReadAfterRead[] =\n\"  name: \\\"sleepnet\\\"\"\n\"  type: \\\"dag\\\"\"\n\"  num_workers: 2\"\n\"  op {\"\n\"    output: \\\"sleep1\\\"\"\n\"    name: \\\"sleep1\\\"\"\n\"    type: \\\"Sleep\\\"\"\n\"    arg {\"\n\"      name: \\\"ms\\\"\"\n\"      i: 100\"\n\"    }\"\n\"  }\"\n\"  op {\"\n\"    input: \\\"sleep1\\\"\"\n\"    output: \\\"sleep2\\\"\"\n\"    name: \\\"sleep2\\\"\"\n\"    type: \\\"Sleep\\\"\"\n\"    arg {\"\n\"      name: \\\"ms\\\"\"\n\"      i: 100\"\n\"    }\"\n\"  }\"\n\"  op {\"\n\"    input: \\\"sleep1\\\"\"\n\"    output: \\\"sleep3\\\"\"\n\"    name: \\\"sleep3\\\"\"\n\"    type: \\\"Sleep\\\"\"\n\"    arg {\"\n\"      name: \\\"ms\\\"\"\n\"      i: 150\"\n\"    }\"\n\"  }\";\n\nTEST(DAGNetTest, TestDAGNetTimingReadAfterRead) {\n  int ms = RunNetAndGetDuration(string(kSleepNetDefStringReadAfterRead), \"dag\");\n  EXPECT_NEAR(ms, 250, kTimeThreshold);\n}\n\n// For sanity check, we also test the sequential time - it should take 0.35\n// seconds instead since everything has to be sequential.\nTEST(SimpleNetTest, TestSimpleNetTimingReadAfterRead) {\n  int ms = RunNetAndGetDuration(string(kSleepNetDefStringReadAfterRead), \"simple\");\n  EXPECT_NEAR(ms, 350, kTimeThreshold);\n}\n\n// This network has two operators writing out the sleep2 blob. As a result, the\n// operator sleep2-again creates a write after write dependency and the whole\n// process should be sequential.\nconst char kSleepNetDefStringWriteAfterWrite[] =\n\"  name: \\\"sleepnet\\\"\"\n\"  type: \\\"dag\\\"\"\n\"  num_workers: 2\"\n\"  op {\"\n\"    output: \\\"sleep1\\\"\"\n\"    name: \\\"sleep1\\\"\"\n\"    type: \\\"Sleep\\\"\"\n\"    arg {\"\n\"      name: \\\"ms\\\"\"\n\"      i: 100\"\n\"    }\"\n\"  }\"\n\"  op {\"\n\"    input: \\\"sleep1\\\"\"\n\"    output: \\\"sleep2\\\"\"\n\"    name: \\\"sleep2\\\"\"\n\"    type: \\\"Sleep\\\"\"\n\"    arg {\"\n\"      name: \\\"ms\\\"\"\n\"      i: 100\"\n\"    }\"\n\"  }\"\n\"  op {\"\n\"    output: \\\"sleep2\\\"\"\n\"    name: \\\"sleep2-again\\\"\"\n\"    type: \\\"Sleep\\\"\"\n\"    arg {\"\n\"      name: \\\"ms\\\"\"\n\"      i: 150\"\n\"    }\"\n\"  }\";\n\nTEST(DAGNetTest, TestDAGNetTimingWriteAfterWrite) {\n  int ms = RunNetAndGetDuration(\n      string(kSleepNetDefStringWriteAfterWrite), \"dag\");\n  EXPECT_NEAR(ms, 350, kTimeThreshold);\n}\n\nTEST(SimpleNetTest, TestSimpleNetTimingWriteAfterWrite) {\n  int ms = RunNetAndGetDuration(\n      string(kSleepNetDefStringWriteAfterWrite), \"simple\");\n  EXPECT_NEAR(ms, 350, kTimeThreshold);\n}\n\n// This network has an operator writing to sleep1 while another operator is\n// accessing it. As a result, the operator sleep1-again creates a write after\n// read dependency and the whole process should be sequential.\nconst char kSleepNetDefStringWriteAfterRead[] =\n\"  name: \\\"sleepnet\\\"\"\n\"  type: \\\"dag\\\"\"\n\"  num_workers: 2\"\n\"  op {\"\n\"    output: \\\"sleep1\\\"\"\n\"    name: \\\"sleep1\\\"\"\n\"    type: \\\"Sleep\\\"\"\n\"    arg {\"\n\"      name: \\\"ms\\\"\"\n\"      i: 100\"\n\"    }\"\n\"  }\"\n\"  op {\"\n\"    input: \\\"sleep1\\\"\"\n\"    output: \\\"sleep2\\\"\"\n\"    name: \\\"sleep2\\\"\"\n\"    type: \\\"Sleep\\\"\"\n\"    arg {\"\n\"      name: \\\"ms\\\"\"\n\"      i: 100\"\n\"    }\"\n\"  }\"\n\"  op {\"\n\"    output: \\\"sleep1\\\"\"\n\"    name: \\\"sleep1-again\\\"\"\n\"    type: \\\"Sleep\\\"\"\n\"    arg {\"\n\"      name: \\\"ms\\\"\"\n\"      i: 150\"\n\"    }\"\n\"  }\";\n\nTEST(DAGNetTest, TestDAGNetTimingWriteAfterRead) {\n  int ms = RunNetAndGetDuration(\n      string(kSleepNetDefStringWriteAfterRead), \"dag\");\n  EXPECT_NEAR(ms, 350, kTimeThreshold);\n}\n\nTEST(SimpleNetTest, TestSimpleNetTimingWriteAfterRead) {\n  int ms = RunNetAndGetDuration(\n      string(kSleepNetDefStringWriteAfterRead), \"simple\");\n  EXPECT_NEAR(ms, 350, kTimeThreshold);\n}\n\n// This network has an operator writing to sleep1 while another\n// operator has a control dependency on it. As a result, the operator\n// sleep1-again creates a write after read dependency and the whole\n// process should be sequential.\nconst char kSleepNetDefStringControlDependency[] = R\"DOC(\n  name: \"sleepnet\"\n  type: \"dag\"\n  num_workers: 2\n  op {\n    output: \"sleep1\"\n    name: \"sleep1\"\n    type: \"Sleep\"\n    arg {\n      name: \"ms\"\n      i: 100\n    }\n  }\n  op {\n    control_input: \"sleep1\"\n    output: \"sleep2\"\n    name: \"sleep2\"\n    type: \"Sleep\"\n    arg {\n      name: \"ms\"\n      i: 100\n    }\n  }\n  op {\n    output: \"sleep1\"\n    name: \"sleep1-again\"\n    type: \"Sleep\"\n    arg {\n      name: \"ms\"\n      i: 150\n    }\n  }\n)DOC\";\n\nTEST(DAGNetTest, TestDAGNetTimingControlDependency) {\n  int ms = RunNetAndGetDuration(\n      string(kSleepNetDefStringControlDependency), \"dag\");\n  EXPECT_NEAR(ms, 350, kTimeThreshold);\n}\n\nTEST(SimpleNetTest, TestSimpleNetTimingControlDependency) {\n  int ms = RunNetAndGetDuration(\n      string(kSleepNetDefStringControlDependency), \"simple\");\n  EXPECT_NEAR(ms, 350, kTimeThreshold);\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/plan_executor.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/plan_executor.h\"\n\n#include <condition_variable>\n#include <memory>\n#include <mutex>\n#include <thread>\n#include <unordered_map>\n#include <vector>\n\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nCAFFE2_DEFINE_bool(\n    caffe2_handle_executor_threads_exceptions,\n    false,\n    \"If used we will handle exceptions in executor threads. \"\n    \"This avoids SIGABRT but may cause process to deadlock\");\n\nnamespace caffe2 {\n\nnamespace {\n\nstruct NetDefInfo {\n  const NetDef* netDef;\n  // in order to keep the \"override existing nets\" on the top-level workflow,\n  // we need to makr the nets that already exist so that we can override them\n  // exactly once.\n  bool needsOverride;\n};\n\nusing NetDefMap = std::unordered_map<std::string, NetDefInfo>;\n\nstruct Reporter {\n  struct ReporterInstance {\n    std::mutex report_mutex;\n    std::condition_variable report_cv;\n    std::thread report_thread;\n    ReporterInstance(int intervalMillis, bool* done, std::function<void()> f) {\n      auto interval = std::chrono::milliseconds(intervalMillis);\n      auto reportWorker = [=]() {\n        std::unique_lock<std::mutex> lk(report_mutex);\n        do {\n          report_cv.wait_for(lk, interval, [&]() { return *done; });\n          f();\n        } while (!*done);\n      };\n      report_thread = std::thread(reportWorker);\n    }\n  };\n\n  void start(int64_t intervalMillis, std::function<void()> f) {\n    instances_.emplace_back(new ReporterInstance(intervalMillis, &done, f));\n  }\n\n  ~Reporter() {\n    done = true;\n    for (auto& instance : instances_) {\n      if (!instance->report_thread.joinable()) {\n        continue;\n      }\n      instance->report_cv.notify_all();\n      instance->report_thread.join();\n    }\n  }\n\n private:\n  std::vector<std::unique_ptr<ReporterInstance>> instances_;\n  bool done{false};\n};\n\n// Returns a function that returns `true` if we should continue\n// iterating, given the current iteration count.\nstd::function<bool(int64_t)> getContinuationTest(\n    Workspace* /*ws*/,\n    const ExecutionStep& step) {\n  if (step.has_should_stop_blob()) {\n    CAFFE_ENFORCE(\n        !step.has_num_iter(),\n        \"Must not specify num_iter if should_stop_blob is set\");\n  }\n\n  if (!step.has_should_stop_blob()) { // control by iteration\n    CAFFE_ENFORCE(!step.has_only_once(), \"not supported\");\n    int64_t iterations = step.has_num_iter() ? step.num_iter() : 1;\n    VLOG(1) << \"Will execute step \" << step.name() << \" for \" << iterations\n            << \" iterations.\";\n    return [=](int64_t i) { return i < iterations; };\n  } else { // control by signal blob\n    bool onlyOnce = step.has_only_once() && step.only_once();\n    VLOG(1) << \"Will execute step\" << step.name() << (onlyOnce ? \" once \" : \"\")\n            << \" until stopped by blob \" << step.should_stop_blob();\n    if (onlyOnce) {\n      return [](int64_t i) { return i == 0; };\n    } else {\n      return [](int64_t /*i*/) { return true; };\n    }\n  }\n};\n\n// if the blob doesn't exist or is not initiaized, return false\ninline bool getShouldStop(const Blob* b) {\n  if (!b || !b->meta().id()) { // not exist or uninitialized\n    return false;\n  }\n\n  const auto& t = b->Get<TensorCPU>();\n  CAFFE_ENFORCE(t.IsType<bool>() && t.size() == 1, \"expects a scalar boolean\");\n  return *(t.template data<bool>());\n}\n\n/**\n * Injects a blob named 'GLOBAL_WORKSPACE_ID' for each workspace, only if\n * another blob named 'NODE_ID' is present. 'NODE_ID' blob can be used in a\n * distribued run and in this case 'GLOBAL_WORKSPACE_ID' can be used across\n * machines for other purposes (e.g. to support model parallelism). Essentially,\n * 'GLOBAL_WORKSPACE_ID' is an identifier for a workspace that is unique across\n * all 'NODE_ID's.\n */\nstruct WorkspaceIdInjector {\n  static const string NODE_ID;\n  static const string GLOBAL_WORKSPACE_ID;\n\n  void InjectWorkspaceId(Workspace* workspace) {\n    if (workspace->HasBlob(NODE_ID)) {\n      Blob* node_id_blob = workspace->GetBlob(NODE_ID);\n      TensorCPU node_id_tensor = node_id_blob->template Get<TensorCPU>();\n      int node_id = node_id_tensor.template data<int32_t>()[0];\n      CAFFE_ENFORCE(\n          seq_ < (1 << 16),\n          \"Integer overflow while calculating GLOBAL_WORKSPACE_ID blob\");\n      int32_t global_ws_id = (seq_++) + (static_cast<int32_t>(node_id) << 16);\n      Blob* global_ws_id_blob = workspace->CreateLocalBlob(GLOBAL_WORKSPACE_ID);\n      TensorCPU* global_ws_id_tensor =\n          global_ws_id_blob->template GetMutable<TensorCPU>();\n      global_ws_id_tensor->Resize();\n      global_ws_id_tensor->template mutable_data<int32_t>()[0] = global_ws_id;\n      VLOG(1) << \"Adding \" << GLOBAL_WORKSPACE_ID << \" = \" << global_ws_id;\n    }\n  }\n\n private:\n  std::atomic<int> seq_{0};\n};\n\nconst string WorkspaceIdInjector::NODE_ID = \"NODE_ID\";\nconst string WorkspaceIdInjector::GLOBAL_WORKSPACE_ID = \"GLOBAL_WORKSPACE_ID\";\n\nstruct CompiledExecutionStep;\n\n/**\n * Controls compilation and runtime cloning of execution steps.\n *\n * If step.create_workspace=False, this wrapper will compile the execution step\n * and its children once, and calls to ExecutionStepWrapper::compiled() will\n * always return the same compiled step.\n * If step.create_workspace=True, no compilation is done at creation time.\n * Instead, a new CompiledExecutionStep is created for every compiled() call.\n *\n * CompiledExecutionStep owns its Workspace, and the lifetime of the\n * compiled step along with its workspace will be tied to the lifetime of\n * the `CompileGuard` object returned by compiled().\n *\n * ExecuteStepRecursive will call call compiled() once before the given\n * execution step is run and keep it alive for the length of its execution.\n * This means that, for steps with create_workspace=true, a child workspace\n * will be created everytime the step is executed, and destroyed right\n * afterwards.\n */\nstruct ExecutionStepWrapper {\n  ExecutionStepWrapper(\n      const ExecutionStep* step,\n      Workspace* externalWorkspace,\n      ShouldContinue externalShouldContinue,\n      NetDefMap* netDefs,\n      WorkspaceIdInjector* ws_id_injector)\n      : step_(step),\n        externalWorkspace_(externalWorkspace),\n        externalShouldContinue_(externalShouldContinue),\n        netDefs_(netDefs),\n        ws_id_injector_(ws_id_injector) {\n    // If this execution step does not create a child workspace,\n    // then just eagerly-compile it. This will trigger CreateNet on the\n    // nets used by this execution step.\n    if (!step_->create_workspace()) {\n      compiledStep_ = doCompile();\n    }\n  }\n\n  class CompiledGuard {\n    void reset(std::unique_ptr<CompiledExecutionStep>&& compiled) {\n      compiled_ = std::move(compiled);\n      compiledRef_ = compiled_.get();\n    }\n    void reset(CompiledExecutionStep* compiledRef) {\n      compiled_.reset();\n      compiledRef_ = compiledRef;\n    }\n\n   public:\n    CompiledExecutionStep* operator->() {\n      return compiledRef_;\n    }\n\n   private:\n    CompiledGuard() {}\n    std::unique_ptr<CompiledExecutionStep> compiled_;\n    CompiledExecutionStep* compiledRef_;\n    friend struct ExecutionStepWrapper;\n  };\n\n  const ExecutionStep& step() {\n    return *step_;\n  }\n\n  CompiledGuard compiled() {\n    CompiledGuard guard;\n    if (compiledStep_) {\n      guard.reset(compiledStep_.get());\n    } else {\n      guard.reset(doCompile());\n    }\n    return guard;\n  }\n\n private:\n  std::unique_ptr<CompiledExecutionStep> doCompile();\n\n  const ExecutionStep* step_;\n  Workspace* externalWorkspace_;\n  ShouldContinue externalShouldContinue_;\n  NetDefMap* netDefs_;\n  std::unique_ptr<CompiledExecutionStep> compiledStep_;\n  WorkspaceIdInjector* ws_id_injector_;\n};\n\nstruct CompiledExecutionStep {\n  typedef std::function<bool(int)> ShouldContinue;\n\n  CompiledExecutionStep(\n      const ExecutionStep* mainStep,\n      Workspace* externalWorkspace,\n      ShouldContinue externalShouldContinue,\n      NetDefMap* netDefs,\n      WorkspaceIdInjector* ws_id_injector)\n      : step(mainStep) {\n    if (mainStep->create_workspace()) {\n      localWorkspace_.reset(new Workspace(externalWorkspace));\n      workspace = localWorkspace_.get();\n      ws_id_injector->InjectWorkspaceId(workspace);\n    } else {\n      workspace = externalWorkspace;\n    }\n\n    CAFFE_ENFORCE(\n        (step->substep_size() == 0 || step->network_size() == 0),\n        \"An ExecutionStep should either have substep or networks\"\n        \"but not both.\");\n\n    auto createAndGetNet = [&](const std::string& network_name) {\n      auto it = netDefs->find(network_name);\n      CAFFE_ENFORCE(\n          it != netDefs->end(),\n          \"ExecutionStep \" + mainStep->name() + \" uses undefined net \" +\n              network_name);\n      // needsOverride does not need synchronization because it is only\n      // relevant for non-dynamic executions steps. This is due to the fact\n      // that concurrent nets run on child workspaces, that do not needOverride.\n      if (it->second.needsOverride || !workspace->GetNet(network_name)) {\n        workspace->CreateNet(*it->second.netDef, true);\n        it->second.needsOverride = false;\n      }\n      auto* net = workspace->GetNet(network_name);\n      CAFFE_ENFORCE(net != nullptr, \"Network \", network_name, \" not found.\");\n      return net;\n    };\n\n    if (step->substep_size()) {\n      ShouldContinue substepShouldContinue;\n      if (!step->concurrent_substeps() || step->substep().size() <= 1) {\n        substepShouldContinue = externalShouldContinue;\n      } else {\n        substepShouldContinue = [this, externalShouldContinue](int64_t it) {\n          return !gotFailure && externalShouldContinue(it);\n        };\n      }\n\n      for (const auto& ss : step->substep()) {\n        auto compiledSubstep = std::make_shared<ExecutionStepWrapper>(\n            &ss, workspace, substepShouldContinue, netDefs, ws_id_injector);\n        if (ss.has_run_every_ms()) {\n          reportSubsteps.push_back(compiledSubstep);\n        } else {\n          recurringSubsteps.push_back(compiledSubstep);\n        }\n      }\n    } else {\n      for (const string& network_name : step->network()) {\n        networks.push_back(createAndGetNet(network_name));\n      }\n    }\n\n    if (step->has_should_stop_blob()) {\n      shouldStop = workspace->GetBlob(step->should_stop_blob());\n      CAFFE_ENFORCE(\n          shouldStop, \"blob \", step->should_stop_blob(), \" does not exist\");\n    }\n\n    if (step->has_report_net()) {\n      CAFFE_ENFORCE(\n          step->has_report_interval(),\n          \"A report_interval must be provided if report_net is set.\");\n      reportNet = createAndGetNet(step->report_net());\n    } else {\n      reportNet = nullptr;\n    }\n\n    netShouldContinue = getContinuationTest(workspace, *step);\n    shouldContinue = [this, externalShouldContinue](int64_t iter) {\n      return externalShouldContinue(iter) && this->netShouldContinue(iter);\n    };\n  }\n\n  const ExecutionStep* step;\n  Workspace* workspace;\n  vector<std::shared_ptr<ExecutionStepWrapper>> reportSubsteps;\n  vector<std::shared_ptr<ExecutionStepWrapper>> recurringSubsteps;\n\n  vector<NetBase*> networks;\n  NetBase* reportNet;\n  Blob* shouldStop{nullptr};\n  ShouldContinue netShouldContinue;\n  ShouldContinue shouldContinue;\n  std::atomic<bool> gotFailure{false};\n\n private:\n  std::unique_ptr<Workspace> localWorkspace_;\n};\n\nstd::unique_ptr<CompiledExecutionStep> ExecutionStepWrapper::doCompile() {\n  return std::unique_ptr<CompiledExecutionStep>(new CompiledExecutionStep(\n      step_,\n      externalWorkspace_,\n      externalShouldContinue_,\n      netDefs_,\n      ws_id_injector_));\n}\n\n#define CHECK_SHOULD_STOP(step, shouldStop)                       \\\n  if (getShouldStop(shouldStop)) {                                \\\n    VLOG(1) << \"Execution step \" << step.name() << \" stopped by \" \\\n            << step.should_stop_blob();                           \\\n    return true;                                                  \\\n  }\n\nbool ExecuteStepRecursive(ExecutionStepWrapper& stepWrapper) {\n  const auto& step = stepWrapper.step();\n  auto compiledStep = stepWrapper.compiled();\n\n  VLOG(1) << \"Running execution step \" << step.name();\n\n  std::unique_ptr<Reporter> reporter;\n  if (step.has_report_net() || compiledStep->reportSubsteps.size() > 0) {\n    reporter = caffe2::make_unique<Reporter>();\n    auto* reportNet = compiledStep->reportNet;\n    if (reportNet) {\n      VLOG(1) << \"Starting reporter net\";\n      reporter->start(step.report_interval() * 1000, [reportNet]() {\n        if (!reportNet->Run()) {\n          LOG(WARNING) << \"Error running report_net.\";\n        }\n      });\n    }\n    for (auto& substepWrapper : compiledStep->reportSubsteps) {\n      reporter->start(\n          substepWrapper->step().run_every_ms(), [substepWrapper]() {\n            if (!ExecuteStepRecursive(*substepWrapper)) {\n              LOG(WARNING) << \"Error running report step.\";\n            }\n          });\n    }\n  }\n\n  const Blob* shouldStop = compiledStep->shouldStop;\n\n  if (step.substep_size()) {\n    bool sequential =\n        (!step.concurrent_substeps() || step.substep().size() <= 1) &&\n        (!step.has_num_concurrent_instances() ||\n         step.num_concurrent_instances() <= 1);\n    for (int64_t iter = 0; compiledStep->shouldContinue(iter); ++iter) {\n      if (sequential) {\n        VLOG(1) << \"Executing step \" << step.name() << \" iteration \" << iter;\n        for (auto& substepWrapper : compiledStep->recurringSubsteps) {\n          if (!ExecuteStepRecursive(*substepWrapper)) {\n            return false;\n          }\n          CHECK_SHOULD_STOP(step, shouldStop);\n        }\n      } else {\n        VLOG(1) << \"Executing step \" << step.name() << \" iteration \" << iter\n                << \" with \" << step.substep().size() << \" concurrent substeps\";\n\n        std::atomic<int> next_substep{0};\n        std::mutex exception_mutex;\n        string first_exception;\n        auto worker = [&]() {\n          auto num_substeps = compiledStep->recurringSubsteps.size();\n          int substep_id = next_substep++ % num_substeps;\n          if (compiledStep->gotFailure) {\n            return;\n          }\n          try {\n            if (!ExecuteStepRecursive(\n                    *compiledStep->recurringSubsteps.at(substep_id))) {\n              compiledStep->gotFailure = true;\n            }\n          } catch (const std::exception& ex) {\n            std::lock_guard<std::mutex> guard(exception_mutex);\n            if (!first_exception.size()) {\n              first_exception = GetExceptionString(ex);\n              LOG(ERROR) << \"Parallel worker exception:\\n\" << first_exception;\n            }\n            compiledStep->gotFailure = true;\n            if (!FLAGS_caffe2_handle_executor_threads_exceptions) {\n              // In complex plans other threads might get stuck if another\n              // one fails. So we let exception to go out of thread which\n              // causes SIGABRT. In local setup one might use this flag\n              // in order to use Python debugger after a failure\n              throw;\n            }\n          }\n        };\n\n        std::vector<std::thread> threads;\n        auto numThreads = compiledStep->recurringSubsteps.size();\n        if (step.has_num_concurrent_instances()) {\n          numThreads *= step.num_concurrent_instances();\n        }\n        for (int64_t i = 0; i < numThreads; ++i) {\n          threads.emplace_back(worker);\n        }\n        for (auto& thread : threads) {\n          thread.join();\n        }\n        if (compiledStep->gotFailure) {\n          LOG(ERROR) << \"One of the workers failed.\";\n          if (first_exception.size()) {\n            CAFFE_THROW(\n                \"One of the workers died with an unhandled exception \",\n                first_exception);\n          }\n          return false;\n        }\n        // concurrent substeps should be careful about setting should_stop_blob\n        CHECK_SHOULD_STOP(step, shouldStop);\n      }\n    }\n    return true;\n  } else {\n    // If this ExecutionStep just contains nets, we can directly run it.\n    for (int64_t iter = 0; compiledStep->shouldContinue(iter); ++iter) {\n      VLOG(1) << \"Executing networks \" << step.name() << \" iteration \" << iter;\n      for (NetBase* network : compiledStep->networks) {\n        if (!network->Run()) {\n          return false;\n        }\n        CHECK_SHOULD_STOP(step, shouldStop);\n      }\n    }\n  }\n  return true;\n}\n\n#undef CHECK_SHOULD_STOP\n}\n\nbool RunPlanOnWorkspace(\n    Workspace* ws,\n    const PlanDef& plan,\n    ShouldContinue shouldContinue) {\n  LOG(INFO) << \"Started executing plan.\";\n  if (plan.execution_step_size() == 0) {\n    LOG(WARNING) << \"Nothing to run - did you define a correct plan?\";\n    // We will do nothing, but the plan is still legal so we will return true.\n    return true;\n  }\n  LOG(INFO) << \"Initializing networks.\";\n\n  NetDefMap net_defs;\n  for (const NetDef& net_def : plan.network()) {\n    CAFFE_ENFORCE(\n        net_defs.count(net_def.name()) == 0,\n        \"Your plan contains networks of the same name \\\"\",\n        net_def.name(),\n        \"\\\", which should not happen. Check your plan to see \"\n        \"if you made a programming error in creating the plan.\");\n    auto netAlreadyExists = ws->GetNet(net_def.name()) != nullptr;\n    net_defs[net_def.name()] = NetDefInfo{&net_def, netAlreadyExists};\n  }\n  WorkspaceIdInjector ws_id_injector;\n  Timer plan_timer;\n  for (const ExecutionStep& step : plan.execution_step()) {\n    Timer step_timer;\n    ExecutionStepWrapper stepWrapper(\n        &step, ws, shouldContinue, &net_defs, &ws_id_injector);\n    if (!ExecuteStepRecursive(stepWrapper)) {\n      LOG(ERROR) << \"Failed initializing step \" << step.name();\n      return false;\n    }\n    LOG(INFO) << \"Step \" << step.name() << \" took \" << step_timer.Seconds()\n              << \" seconds.\";\n  }\n  float exec_time = plan_timer.Seconds();\n\n#ifndef CAFFE2_MOBILE\n  PlanExecutionTime plan_stat(plan.name());\n  CAFFE_EVENT(\n      plan_stat, plan_execution_time_ns, (long)(exec_time * 1000000000));\n#endif // CAFFE2_MOBILE\n\n  LOG(INFO) << \"Total plan took \" << exec_time << \" seconds.\";\n  LOG(INFO) << \"Plan executed successfully.\";\n  return true;\n}\n}\n"
  },
  {
    "path": "caffe2/core/plan_executor.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <functional>\n#ifndef CAFFE2_MOBILE\n#include \"caffe2/core/stats.h\"\n#endif // CAFFE2_MOBILE\n\nnamespace caffe2 {\n\nclass Workspace;\nclass PlanDef;\n\ntypedef std::function<bool(int)> ShouldContinue;\n\nbool RunPlanOnWorkspace(Workspace* ws, const PlanDef& plan, ShouldContinue);\n\n#ifndef CAFFE2_MOBILE\nstruct PlanExecutionTime {\n  CAFFE_STAT_CTOR(PlanExecutionTime);\n  CAFFE_EXPORTED_STAT(plan_execution_time_ns);\n};\n#endif // CAFFE2_MOBILE\n}\n"
  },
  {
    "path": "caffe2/core/predictor.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/predictor.h\"\n\n#include <unordered_set>\n\nnamespace caffe2 {\n\nnamespace {\n\nvoid enforceIsTensor(Workspace* ws, const std::string& name) {\n  auto blob = ws->GetBlob(name);\n  CAFFE_ENFORCE(blob, \"Blob does not exist: \", name);\n  CAFFE_ENFORCE(\n      blob->template IsType<TensorCPU>(), \"Blob is not a CPU Tensor: \", name);\n}\n\nvoid shareInputTensor(\n    Workspace* ws,\n    const std::string& name,\n    TensorCPU* input) {\n  enforceIsTensor(ws, name);\n  auto* blob = ws->GetBlob(name);\n  CAFFE_ENFORCE(blob, \"Blob: \", name, \" does not exist\");\n  auto* tensor = blob->template GetMutable<TensorCPU>();\n  tensor->ResizeLike(*input);\n  tensor->ShareData(*input);\n}\n\nTensorCPU* extractOutputTensor(Workspace* ws, const std::string& name) {\n  enforceIsTensor(ws, name);\n  auto* blob = ws->GetBlob(name);\n  CAFFE_ENFORCE(blob, \"Blob: \", name, \" does not exist\");\n  return blob->template GetMutable<TensorCPU>();\n}\n\nconst NetDef& getNet(const MetaNetDef& def, const std::string& name) {\n  for (const auto& n : def.nets()) {\n    if (n.key() == name) {\n      return n.value();\n    }\n  }\n  CAFFE_THROW(\"Net not found: \", name);\n}\n\nconst ::google::protobuf::RepeatedPtrField<::std::string>& getBlobs(\n    const MetaNetDef& def,\n    const std::string& name) {\n  for (const auto& b : def.blobs()) {\n    if (b.key() == name) {\n      return b.value();\n    }\n  }\n  CAFFE_THROW(\"Blob not found: \", name);\n}\n} // namespace\n\nPredictor::Predictor(const MetaNetDef& def, Workspace* parent)\n    : Predictor(\n          getNet(\n              def,\n              PredictorConsts::default_instance().global_init_net_type()),\n          getNet(def, PredictorConsts::default_instance().predict_net_type()),\n          parent) {\n  const auto& inputs =\n      getBlobs(def, PredictorConsts::default_instance().inputs_blob_type());\n  for (const auto& input : inputs) {\n    inputNames_.insert(input);\n  }\n}\n\nPredictor::Predictor(\n    const NetDef& init_net,\n    const NetDef& run_net,\n    Workspace* parent)\n    : run_net_(run_net), ws_(parent) {\n  CAFFE_ENFORCE(ws_.RunNetOnce(init_net));\n\n  // real model inputs can be fed later in run* functions\n  const auto& initialized_vec = ws_.Blobs();\n  const std::unordered_set<std::string> initialized{initialized_vec.begin(),\n                                                    initialized_vec.end()};\n  for (const auto& name : run_net.external_input()) {\n    if (!initialized.count(name)) {\n      auto* blob = ws_.CreateBlob(name);\n      blob->template GetMutable<TensorCPU>();\n    }\n  }\n  CAFFE_ENFORCE(ws_.CreateNet(run_net));\n}\n\nPredictor::~Predictor() {}\n\nbool Predictor::run(const TensorVector& inputs, TensorVector* outputs) {\n  CAFFE_ENFORCE(inputs.size() <= run_net_.external_input_size());\n  for (auto i = 0; i < inputs.size(); ++i) {\n    shareInputTensor(&ws_, run_net_.external_input(i), inputs[i]);\n  }\n\n  if (!ws_.RunNet(run_net_.name())) {\n    return false;\n  }\n\n  outputs->resize(run_net_.external_output_size());\n  for (auto i = 0; i < outputs->size(); ++i) {\n    (*outputs)[i] = extractOutputTensor(&ws_, run_net_.external_output(i));\n  }\n  return true;\n}\n\nbool Predictor::run_map(const TensorMap& inputs, TensorVector* outputs) {\n  if (!inputNames_.empty()) {\n    CAFFE_ENFORCE_EQ(inputs.size(), inputNames_.size());\n  }\n  for (auto input : inputs) {\n    if (!inputNames_.empty()) {\n      CAFFE_ENFORCE_GT(inputNames_.count(input.first), 0);\n    }\n    shareInputTensor(&ws_, input.first, input.second);\n  }\n\n  if (!ws_.RunNet(run_net_.name())) {\n    return false;\n  }\n\n  outputs->resize(run_net_.external_output_size());\n  for (auto i = 0; i < outputs->size(); ++i) {\n    (*outputs)[i] = extractOutputTensor(&ws_, run_net_.external_output(i));\n  }\n  return true;\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/predictor.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <unordered_set>\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/proto/metanet.pb.h\"\n#include \"caffe2/proto/predictor_consts.pb.h\"\n\nnamespace caffe2 {\n\nclass Predictor {\n public:\n  using TensorVector = std::vector<TensorCPU*>;\n  using TensorMap = std::unordered_map<std::string, TensorCPU*>;\n\n  // MetaNetDef contains 'init_net', 'run_net', and meta-info\n  // The meta-info is used to verify inputs are correctly passed\n  Predictor(const MetaNetDef& net, Workspace* parent = nullptr);\n\n  // Runs the `init_net` once, then saves the `run_net` to be executed\n  // in `::run`\n  Predictor(\n      const NetDef& init_net,\n      const NetDef& run_net,\n      Workspace* parent = nullptr);\n  ~Predictor();\n\n  // Executes `run_net` on the inputs.\n  // The first `inputs.size()` inputs from run_net::external_inputs\n  // are shared with the data in `inputs`.\n\n  // Precondition:\n  //   inputs.size() <= run_net_.external_inputs.size()\n\n  // Postcondition:\n  //   outputs->size() == run_net.external_inputs.size()\n\n  // Returns true on success\n  bool run(const TensorVector& inputs, TensorVector* outputs);\n\n  // Similar to run, but consumes a map of name to tensor as input\n  bool run_map(const TensorMap& inputs, TensorVector* outputs);\n\n  const NetDef& def() const {\n    return run_net_;\n  };\n\n  Workspace* ws() {\n    return &ws_;\n  };\n\n private:\n  NetDef run_net_;\n  Workspace ws_;\n  std::unordered_set<std::string> inputNames_;\n};\n}\n"
  },
  {
    "path": "caffe2/core/predictor_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <google/protobuf/text_format.h>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/predictor.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\n\nnamespace {\n\nconst char* predictSpec = R\"DOC(\n        name: \"predict\"\n        type: \"dag\"\n        external_input: \"data\"\n        external_input: \"W\"\n        external_input: \"b\"\n        external_output: \"y\"\n        op {\n          input: \"data\"\n          input: \"W\"\n          input: \"b\"\n          output: \"y\"\n          type: \"FC\"\n        }\n)DOC\";\n\nconst char* initSpec = R\"DOC(\n        name: \"init\"\n        type: \"dag\"\n        op {\n          type: \"ConstantFill\"\n          output: \"W\"\n          arg {\n            name: \"shape\"\n            ints: 10\n            ints: 4\n          }\n          arg {\n            name: \"value\"\n            f: 2.0\n          }\n        }\n        op {\n          type: \"ConstantFill\"\n          output: \"b\"\n          arg {\n            name: \"shape\"\n            ints: 10\n          }\n          arg {\n            name: \"value\"\n            f: 2.0\n          }\n        }\n\n)DOC\";\n\nconst char* metaSpec = R\"DOC(\n  blobs {\n    key: \"INPUTS_BLOB_TYPE\"\n    value: \"data\"\n  }\n  nets {\n    key: \"GLOBAL_INIT_NET_TYPE\"\n    value: {\n      name: \"init\"\n      type: \"dag\"\n      op {\n        type: \"ConstantFill\"\n        output: \"data\"\n        arg {\n          name: \"shape\"\n          ints: 1\n          ints: 4\n        }\n        arg {\n          name: \"value\"\n          f: 2.0\n        }\n      }\n      op {\n        type: \"ConstantFill\"\n        output: \"W\"\n        arg {\n          name: \"shape\"\n          ints: 10\n          ints: 4\n        }\n        arg {\n          name: \"value\"\n          f: 2.0\n        }\n      }\n      op {\n        type: \"ConstantFill\"\n        output: \"b\"\n        arg {\n          name: \"shape\"\n          ints: 10\n        }\n        arg {\n          name: \"value\"\n          f: 2.0\n        }\n      }\n    }\n  }\n  nets {\n    key: \"PREDICT_NET_TYPE\"\n    value: {\n      name: \"predict\"\n      type: \"dag\"\n      external_input: \"data\"\n      external_input: \"W\"\n      external_input: \"b\"\n      external_output: \"y\"\n      op {\n        input: \"data\"\n        input: \"W\"\n        input: \"b\"\n        output: \"y\"\n        type: \"FC\"\n      }\n    }\n  }\n)DOC\";\n\nstd::unique_ptr<Blob> randomTensor(\n    const std::vector<TIndex>& dims,\n    CPUContext* ctx) {\n  auto blob = make_unique<Blob>();\n  auto* t = blob->GetMutable<TensorCPU>();\n  t->Resize(dims);\n  math::RandUniform<float, CPUContext>(\n      t->size(), -1.0, 1.0, t->template mutable_data<float>(), ctx);\n  return blob;\n}\n\nNetDef parseNetDef(const std::string& value) {\n  NetDef def;\n  CAFFE_ENFORCE(\n      google::protobuf::TextFormat::ParseFromString(value, &def),\n      \"Failed to parse NetDef with value: \",\n      value);\n  return def;\n};\n\nMetaNetDef parseMetaNetDef(const std::string& value) {\n  MetaNetDef def;\n  CAFFE_ENFORCE(\n      google::protobuf::TextFormat::ParseFromString(value, &def),\n      \"Failed to parse NetDef with value: \",\n      value);\n  return def;\n}\n}\n\nclass PredictorTest : public testing::Test {\n public:\n  void SetUp() override {\n    DeviceOption op;\n    op.set_random_seed(1701);\n    ctx_ = caffe2::make_unique<CPUContext>(op);\n    NetDef init, run;\n    p_ = caffe2::make_unique<Predictor>(\n        parseNetDef(initSpec), parseNetDef(predictSpec));\n  }\n\n  std::unique_ptr<CPUContext> ctx_;\n  std::unique_ptr<Predictor> p_;\n};\n\nTEST_F(PredictorTest, SimpleBatchSized) {\n  auto inputData = randomTensor({1, 4}, ctx_.get());\n  Predictor::TensorVector input{inputData->template GetMutable<TensorCPU>()};\n  Predictor::TensorVector output;\n  p_->run(input, &output);\n  EXPECT_EQ(output.size(), 1);\n  EXPECT_TRUE(output.front()->dims().size() == 2);\n  EXPECT_TRUE(output.front()->dim(0) == 1);\n  EXPECT_TRUE(output.front()->dim(1) == 10);\n  EXPECT_NEAR(output.front()->data<float>()[4], 0.1209, 1E-4);\n}\n\nTEST_F(PredictorTest, SimpleBatchSizedMapInput) {\n  auto inputData = randomTensor({1, 4}, ctx_.get());\n  Predictor::TensorMap input{\n      {\"data\", inputData->template GetMutable<TensorCPU>()}};\n  Predictor::TensorVector output;\n  p_->run_map(input, &output);\n  EXPECT_EQ(output.size(), 1);\n  EXPECT_TRUE(output.front()->dims().size() == 2);\n  EXPECT_TRUE(output.front()->dim(0) == 1);\n  EXPECT_TRUE(output.front()->dim(1) == 10);\n  EXPECT_NEAR(output.front()->data<float>()[4], 0.1209, 1E-4);\n}\n\nclass PredictorMetaNetDefTest : public testing::Test {\n public:\n  void SetUp() override {\n    DeviceOption op;\n    op.set_random_seed(1701);\n    ctx_ = caffe2::make_unique<CPUContext>(op);\n    p_ = caffe2::make_unique<Predictor>(parseMetaNetDef(metaSpec));\n  }\n\n  std::unique_ptr<CPUContext> ctx_;\n  std::unique_ptr<Predictor> p_;\n};\n\nTEST_F(PredictorMetaNetDefTest, SimpleMetaNetDefInitializer) {\n  auto inputData = randomTensor({1, 4}, ctx_.get());\n  Predictor::TensorMap input{\n      {\"data\", inputData->template GetMutable<TensorCPU>()}};\n  Predictor::TensorVector output;\n  p_->run_map(input, &output);\n  EXPECT_EQ(output.size(), 1);\n  EXPECT_TRUE(output.front()->dims().size() == 2);\n  EXPECT_TRUE(output.front()->dim(0) == 1);\n  EXPECT_TRUE(output.front()->dim(1) == 10);\n  EXPECT_NEAR(output.front()->data<float>()[4], 0.1209, 1E-4);\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/qtensor.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/qtensor.h\"\n\nnamespace caffe2 {\nCAFFE_KNOWN_TYPE(QTensor<CPUContext>);\n}\n"
  },
  {
    "path": "caffe2/core/qtensor.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_QTENSOR_H_\n#define CAFFE2_CORE_QTENSOR_H_\n\n#include <algorithm>\n#include <climits>\n#include <cstddef>\n#include <vector>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/typeid.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass QTensor {\n public:\n  QTensor() {}\n  virtual ~QTensor() {}\n  /**\n   * @brief Creates a quantized tensor of the given dimension.\n   *\n   * Note that the actual data allocation is not going to be carried out until\n   * the first time mutable_data() is called.\n   *\n   * The underlying storage of the quantized tensor interleaves elements\n   * by bit depth.\n   *\n   * Labeled memory for tensor of size 6, precision 3\n   *   [ E1[0] E2[0] E3[0] E4[0] E5[0] E6[0] ] // Least significant Bits\n   *   [ E1[1] E2[1] E3[1] E4[1] E5[1] E6[1] ]\n   *   [ E1[2] E2[2] E3[2] E4[2] E5[2] E6[2] ]\n   *\n   * In the case of sign bits (see enable_sign argument), an extra bit\n   * per element is added:\n   *\n   * Labeled memory for tensor of size 6, precision 3, sign bit enabled\n   *   [ E1[0] E2[0] E3[0] E4[0] E5[0] E6[0] ]\n   *   [ E1[1] E2[1] E3[1] E4[1] E5[1] E6[1] ]\n   *   [ E1[2] E2[2] E3[2] E4[2] E5[2] E6[2] ]\n   *   [ E1[s] E2[s] E3[s] E4[s] E5[s] E6[s] ]\n   *   Where 's' is 1 if E is negative\n   *\n   * The reason for this layout is the ability to efficiently multiply\n   * many low precision integers as a sum of popcnt(A & B) * 1 << bit.\n   * Explained here: https://arxiv.org/abs/1606.06160\n   */\n  explicit QTensor(\n      const std::vector<int>& dims,\n      const unsigned char precision,\n      const bool signbit = false)\n      : precision_(precision), signed_(signbit) {\n    Resize(dims);\n  }\n\n  void Resize(std::vector<int> dim_source) {\n    if (dims_ != dim_source) {\n      size_t source_size = std::accumulate(\n          dim_source.begin(), dim_source.end(), 1, std::multiplies<int>());\n      if ((source_size * (precision_ + signed_)) > capacity_) {\n        data_.reset();\n        capacity_ = 0;\n      }\n      dims_ = dim_source;\n      size_ = source_size;\n    }\n  }\n\n  void\n  SetBitAtIndex(const unsigned char bit, const size_t index, const bool value) {\n    // Get the mutable data at bit depth `bit`.\n    unsigned char* d = mutable_data();\n\n    CAFFE_ENFORCE(\n        bit < precision_ + signed_,\n        \"Attempted to a set a bit that is not allocated.\");\n    CAFFE_ENFORCE(bit * aligned_size() < capacity_);\n\n    auto idx = (aligned_size() * bit) / CHAR_BIT;\n    d = &d[idx];\n\n    idx = index / CHAR_BIT;\n    auto shift = CHAR_BIT - (index % CHAR_BIT) - 1;\n\n    if (value) {\n      d[idx] |= 1 << shift;\n    } else {\n      d[idx] &= ~(1 << shift);\n    }\n  }\n\n  bool GetBitAtIndex(const unsigned char bit, const size_t index) const {\n    // Get the data at bit depth `bit`\n    const unsigned char* d = data();\n    auto idx = (aligned_size() * bit) / CHAR_BIT;\n    d = &d[idx];\n\n    idx = index / CHAR_BIT;\n    auto shift = CHAR_BIT - (index % CHAR_BIT) - 1;\n\n    return d[idx] & (1 << shift);\n  }\n\n  void SetPrecision(const unsigned char precision) {\n    precision_ = precision;\n    data_.reset();\n  }\n\n  void SetSigned(const bool make_signed = true) {\n    signed_ = make_signed;\n    data_.reset();\n  }\n\n  void SetScale(const double scale) {\n    scale_ = scale;\n  }\n\n  void SetBias(const double bias) {\n    bias_ = bias;\n  }\n\n  unsigned char* mutable_data() {\n    if (!data_) {\n      auto ptr_and_deleter = Context::New(nbytes());\n      data_.reset(\n          static_cast<unsigned char*>(ptr_and_deleter.first),\n          ptr_and_deleter.second);\n      capacity_ = nbytes() * CHAR_BIT;\n    }\n    CAFFE_ENFORCE(capacity_ == nbytes() * CHAR_BIT);\n    return data_.get();\n  }\n\n  inline const unsigned char* data() const {\n    return data_.get();\n  }\n\n  inline size_t size() const {\n    return size_;\n  }\n\n  inline unsigned char alignment() const {\n    return alignment_;\n  }\n\n  inline unsigned char precision() const {\n    return precision_;\n  }\n\n  inline const vector<int>& dims() const {\n    return dims_;\n  }\n\n  inline bool is_signed() const {\n    return signed_;\n  }\n\n  /**\n   * Returns the number of dimensions of the data.\n   */\n  inline int ndim() const {\n    return dims_.size();\n  }\n\n  inline size_t aligned_size() const {\n    return alignment_ * ((size_ + alignment_ - 1) / alignment_);\n  }\n\n  inline size_t nbytes() const {\n    return (aligned_size() * (precision_ + signed_)) / CHAR_BIT;\n  }\n\n  inline double scale() const {\n    return scale_;\n  }\n\n  inline double bias() const {\n    return bias_;\n  }\n\n  /**\n   * Returns the i-th dimension of the qtensor in int.\n   */\n  inline int dim32(const int i) const {\n    DCHECK_LT(i, dims_.size()) << \"Exceeding ndim limit \" << dims_.size();\n    DCHECK_GE(i, 0) << \"Cannot have negative index\";\n    CAFFE_ENFORCE_LT(dims_[i], std::numeric_limits<int>::max());\n    return static_cast<int>(dims_[i]);\n  }\n\n  /**\n   * Returns the 'canonical' version of a (usually)  user-specified axis,\n   * allowing for negative indexing (e.g., -1 for the last axis).\n   *\n   * @param axis_index the axis index.\n   *        If 0 <= index < ndim(), return index.\n   *        If -ndim <= index <= -1, return (ndim() - (-index)),\n   *        e.g., the last axis index (ndim() - 1) if index == -1,\n   *        the second to last if index == -2, etc.\n   *        Dies on out of range index.\n   */\n  inline int canonical_axis_index(int axis_index) const {\n    CAFFE_ENFORCE_GE(axis_index, -ndim());\n    CAFFE_ENFORCE_LT(axis_index, ndim());\n    if (axis_index < 0) {\n      return axis_index + ndim();\n    }\n    return axis_index;\n  }\n\n  /**\n   * Return product of all dimensions starting from K.\n   */\n  inline TIndex size_from_dim(int k) const {\n    TIndex r = 1;\n    for (int i = k; i < dims_.size(); ++i) {\n      r *= dims_[i];\n    }\n    return r;\n  }\n\n  /**\n   * Product of all dims up to.\n   */\n  inline TIndex size_to_dim(int k) const {\n    CAFFE_ENFORCE(k < dims_.size());\n    TIndex r = 1;\n    for (int i = 0; i < k; ++i) {\n      r *= dims_[i];\n    }\n    return r;\n  }\n\n protected:\n  std::vector<int> dims_;\n  size_t size_ = 0;\n\n  // Precision in bits.\n  unsigned char precision_ = CHAR_BIT;\n  // Bit alignment.\n  unsigned char alignment_ = CHAR_BIT;\n\n  // Allocated data.\n  std::shared_ptr<unsigned char> data_;\n\n  // value = scale_ * (x + bias_)\n  double scale_;\n  double bias_;\n  bool signed_ = false;\n\n  // Capacity in bits.\n  size_t capacity_ = 0;\n};\n\n} // namespace caffe2\n#endif // CAFFE2_CORE_QTENSOR_H_\n"
  },
  {
    "path": "caffe2/core/qtensor_serialization.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/qtensor_serialization.h\"\n\nnamespace caffe2 {\nnamespace {\nREGISTER_BLOB_SERIALIZER(\n    (TypeMeta::Id<QTensor<CPUContext>>()),\n    QTensorSerializer<CPUContext>);\nREGISTER_BLOB_DESERIALIZER(QTensor, QTensorDeserializer<CPUContext>);\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/qtensor_serialization.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_QTENSOR_SERIALIZATION_H_\n#define CAFFE2_CORE_QTENSOR_SERIALIZATION_H_\n\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/qtensor.h\"\n\nnamespace caffe2 {\n\nconstexpr auto kQTensorBlobQType = \"QTensor\";\n\ntemplate <class Context>\nclass QTensorSerializer : public BlobSerializerBase {\n public:\n  QTensorSerializer() : context_() {}\n  ~QTensorSerializer() {}\n  /**\n   * Serializes a Blob. Note that this blob has to contain QTensor<Context>.\n   */\n  void Serialize(\n      const Blob& blob,\n      const string& name,\n      SerializationAcceptor acceptor) override;\n\n private:\n  Context context_;\n};\n\ntemplate <class Context>\nclass QTensorDeserializer : public BlobDeserializerBase {\n public:\n  void Deserialize(const BlobProto& proto, Blob* blob) override;\n  void Deserialize(const QTensorProto& proto, QTensor<Context>* tensor);\n};\n\ntemplate <class Context>\nvoid QTensorSerializer<Context>::Serialize(\n    const Blob& blob,\n    const string& name,\n    BlobSerializerBase::SerializationAcceptor acceptor) {\n  const auto& qtensor = blob.template Get<QTensor<Context>>();\n  BlobProto blob_proto;\n  blob_proto.set_name(name);\n  blob_proto.set_type(kQTensorBlobQType);\n  QTensorProto& proto = *blob_proto.mutable_qtensor();\n  proto.set_name(name);\n  for (int i = 0; i < qtensor.ndim(); ++i) {\n    proto.add_dims(qtensor.dim32(i));\n  }\n  proto.set_precision(qtensor.precision());\n  proto.set_scale(qtensor.scale());\n  proto.set_bias(qtensor.bias());\n  proto.set_is_signed(qtensor.is_signed());\n  detail::CopyToProtoWithCast(\n      qtensor.nbytes(), qtensor.data(), proto.mutable_data(), &this->context_);\n  acceptor(name, blob_proto.SerializeAsString());\n}\n\ntemplate <class Context>\nvoid QTensorDeserializer<Context>::Deserialize(\n    const BlobProto& blob_proto,\n    Blob* blob) {\n  Deserialize(blob_proto.qtensor(), blob->GetMutable<QTensor<Context>>());\n}\n\ntemplate <class Context>\nvoid QTensorDeserializer<Context>::Deserialize(\n    const QTensorProto& proto,\n    QTensor<Context>* qtensor) {\n  Context context{};\n  vector<int> dims;\n  for (const int d : proto.dims()) {\n    dims.push_back(d);\n  }\n  qtensor->Resize(dims);\n  qtensor->SetPrecision(proto.precision());\n  qtensor->SetScale(proto.scale());\n  qtensor->SetBias(proto.bias());\n  qtensor->SetSigned(proto.is_signed());\n\n  detail::CopyFromProtoWithCast(\n      qtensor->nbytes(), proto.data(), qtensor->mutable_data(), &context);\n}\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_QTENSOR_SERIALIZATION_H_\n"
  },
  {
    "path": "caffe2/core/registry.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Simple registry implementation in Caffe2 that uses static variables to\n * register object creators during program initialization time.\n */\n#ifndef CAFFE2_CORE_REGISTRY_H_\n#define CAFFE2_CORE_REGISTRY_H_\n\n#include <algorithm>\n#include <cstdio>\n#include <cstdlib>\n#include <functional>\n#include <memory>\n#include <mutex>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/typeid.h\"\n\nnamespace caffe2 {\n\ntemplate <typename KeyType>\ninline void PrintOffendingKey(const KeyType& key) {\n  printf(\"[key type printing not supported]\\n\");\n}\n\ntemplate <>\ninline void PrintOffendingKey(const string& key) {\n  printf(\"Offending key: %s.\\n\", key.c_str());\n}\n\n/**\n * @brief A template class that allows one to register classes by keys.\n *\n * The keys are usually a string specifying the name, but can be anything that\n * can be used in a std::map.\n *\n * You should most likely not use the Registry class explicitly, but use the\n * helper macros below to declare specific registries as well as registering\n * objects.\n */\ntemplate <class SrcType, class ObjectPtrType, class... Args>\nclass Registry {\n public:\n  typedef std::function<ObjectPtrType(Args...)> Creator;\n\n  Registry() : registry_() {}\n\n  void Register(const SrcType& key, Creator creator) {\n    // The if statement below is essentially the same as the following line:\n    // CHECK_EQ(registry_.count(key), 0) << \"Key \" << key\n    //                                   << \" registered twice.\";\n    // However, CHECK_EQ depends on google logging, and since registration is\n    // carried out at static initialization time, we do not want to have an\n    // explicit dependency on glog's initialization function.\n    std::lock_guard<std::mutex> lock(register_mutex_);\n    if (registry_.count(key) != 0) {\n      printf(\"Key already registered.\\n\");\n      PrintOffendingKey(key);\n      std::exit(1);\n    }\n    registry_[key] = creator;\n  }\n\n  void Register(const SrcType& key, Creator creator, const string& help_msg) {\n    Register(key, creator);\n    help_message_[key] = help_msg;\n  }\n\n  inline bool Has(const SrcType& key) { return (registry_.count(key) != 0); }\n\n  ObjectPtrType Create(const SrcType& key, Args... args) {\n    if (registry_.count(key) == 0) {\n      // Returns nullptr if the key is not registered.\n      return nullptr;\n    }\n    return registry_[key](args...);\n  }\n\n  /**\n   * Returns the keys currently registered as a vector.\n   */\n  vector<SrcType> Keys() {\n    vector<SrcType> keys;\n    for (const auto& it : registry_) {\n      keys.push_back(it.first);\n    }\n    return keys;\n  }\n\n  const CaffeMap<SrcType, string>& HelpMessage() const {\n    return help_message_;\n  }\n\n  const char* HelpMessage(const SrcType& key) const {\n    auto it = help_message_.find(key);\n    if (it == help_message_.end()) {\n      return nullptr;\n    }\n    return it->second.c_str();\n  }\n\n private:\n  CaffeMap<SrcType, Creator> registry_;\n  CaffeMap<SrcType, string> help_message_;\n  std::mutex register_mutex_;\n\n  DISABLE_COPY_AND_ASSIGN(Registry);\n};\n\ntemplate <class SrcType, class ObjectPtrType, class... Args>\nclass Registerer {\n public:\n  Registerer(\n      const SrcType& key,\n      Registry<SrcType, ObjectPtrType, Args...>* registry,\n      typename Registry<SrcType, ObjectPtrType, Args...>::Creator creator,\n      const string& help_msg = \"\") {\n    registry->Register(key, creator, help_msg);\n  }\n\n  template <class DerivedType>\n  static ObjectPtrType DefaultCreator(Args... args) {\n    // TODO(jiayq): old versions of NVCC does not handle make_unique well\n    // so we are forced to use a unique_ptr constructor here. Check if it is\n    // fine to use make_unique in the future.\n    // return make_unique<DerivedType>(args...);\n    return ObjectPtrType(new DerivedType(args...));\n  }\n};\n\n/**\n * CAFFE_ANONYMOUS_VARIABLE(str) introduces an identifier starting with\n * str and ending with a number that varies with the line.\n * Pretty much a copy from 'folly/Preprocessor.h'\n */\n#define CAFFE_CONCATENATE_IMPL(s1, s2) s1##s2\n#define CAFFE_CONCATENATE(s1, s2) CAFFE_CONCATENATE_IMPL(s1, s2)\n#ifdef __COUNTER__\n#define CAFFE_ANONYMOUS_VARIABLE(str) CAFFE_CONCATENATE(str, __COUNTER__)\n#else\n#define CAFFE_ANONYMOUS_VARIABLE(str) CAFFE_CONCATENATE(str, __LINE__)\n#endif\n\n/**\n * CAFFE_DECLARE_TYPED_REGISTRY is a macro that expands to a function\n * declaration, as well as creating a convenient typename for its corresponding\n * registerer.\n */\n#define CAFFE_DECLARE_TYPED_REGISTRY(                                    \\\n    RegistryName, SrcType, ObjectType, PtrType, ...)                     \\\n  Registry<SrcType, PtrType<ObjectType>, ##__VA_ARGS__>* RegistryName(); \\\n  typedef Registerer<SrcType, PtrType<ObjectType>, ##__VA_ARGS__>        \\\n      Registerer##RegistryName;\n\n#define CAFFE_DEFINE_TYPED_REGISTRY(                                         \\\n    RegistryName, SrcType, ObjectType, PtrType, ...)                         \\\n  Registry<SrcType, PtrType<ObjectType>, ##__VA_ARGS__>* RegistryName() {    \\\n    static Registry<SrcType, PtrType<ObjectType>, ##__VA_ARGS__>* registry = \\\n        new Registry<SrcType, PtrType<ObjectType>, ##__VA_ARGS__>();         \\\n    return registry;                                                         \\\n  }\n\n// Note(Yangqing): The __VA_ARGS__ below allows one to specify a templated\n// creator with comma in its templated arguments.\n#define CAFFE_REGISTER_TYPED_CREATOR(RegistryName, key, ...)                  \\\n  namespace {                                                                 \\\n  static Registerer##RegistryName CAFFE_ANONYMOUS_VARIABLE(g_##RegistryName)( \\\n      key, RegistryName(), __VA_ARGS__);                                      \\\n  }\n\n#define CAFFE_REGISTER_TYPED_CLASS(RegistryName, key, ...)                    \\\n  namespace {                                                                 \\\n  static Registerer##RegistryName CAFFE_ANONYMOUS_VARIABLE(g_##RegistryName)( \\\n      key,                                                                    \\\n      RegistryName(),                                                         \\\n      Registerer##RegistryName::DefaultCreator<__VA_ARGS__>,                  \\\n      DemangleType<__VA_ARGS__>());                                           \\\n  }\n\n// CAFFE_DECLARE_REGISTRY and CAFFE_DEFINE_REGISTRY are hard-wired to use string\n// as the key\n// type, because that is the most commonly used cases.\n#define CAFFE_DECLARE_REGISTRY(RegistryName, ObjectType, ...) \\\n  CAFFE_DECLARE_TYPED_REGISTRY(                               \\\n      RegistryName, std::string, ObjectType, std::unique_ptr, ##__VA_ARGS__)\n\n#define CAFFE_DEFINE_REGISTRY(RegistryName, ObjectType, ...) \\\n  CAFFE_DEFINE_TYPED_REGISTRY(                               \\\n      RegistryName, std::string, ObjectType, std::unique_ptr, ##__VA_ARGS__)\n\n#define CAFFE_DECLARE_SHARED_REGISTRY(RegistryName, ObjectType, ...) \\\n  CAFFE_DECLARE_TYPED_REGISTRY(                                      \\\n      RegistryName, std::string, ObjectType, std::shared_ptr, ##__VA_ARGS__)\n\n#define CAFFE_DEFINE_SHARED_REGISTRY(RegistryName, ObjectType, ...) \\\n  CAFFE_DEFINE_TYPED_REGISTRY(                                      \\\n      RegistryName, std::string, ObjectType, std::shared_ptr, ##__VA_ARGS__)\n\n// CAFFE_REGISTER_CREATOR and CAFFE_REGISTER_CLASS are hard-wired to use string\n// as the key\n// type, because that is the most commonly used cases.\n#define CAFFE_REGISTER_CREATOR(RegistryName, key, ...) \\\n  CAFFE_REGISTER_TYPED_CREATOR(RegistryName, #key, __VA_ARGS__)\n\n#define CAFFE_REGISTER_CLASS(RegistryName, key, ...) \\\n  CAFFE_REGISTER_TYPED_CLASS(RegistryName, #key, __VA_ARGS__)\n\n}  // namespace caffe2\n#endif  // CAFFE2_CORE_REGISTRY_H_\n"
  },
  {
    "path": "caffe2/core/registry_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n#include <memory>\n\n#include \"caffe2/core/registry.h\"\n#include <gtest/gtest.h>\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\nnamespace {\n\nclass Foo {\n public:\n  explicit Foo(int x) { LOG(INFO) << \"Foo \" << x; }\n};\n\nCAFFE_DECLARE_REGISTRY(FooRegistry, Foo, int);\nCAFFE_DEFINE_REGISTRY(FooRegistry, Foo, int);\n#define REGISTER_FOO(clsname) \\\n  CAFFE_REGISTER_CLASS(FooRegistry, clsname, clsname)\n\nclass Bar : public Foo {\n public:\n  explicit Bar(int x) : Foo(x) { LOG(INFO) << \"Bar \" << x; }\n};\nREGISTER_FOO(Bar);\n\nclass AnotherBar : public Foo {\n public:\n  explicit AnotherBar(int x) : Foo(x) {\n    LOG(INFO) << \"AnotherBar \" << x;\n  }\n};\nREGISTER_FOO(AnotherBar);\n\nTEST(RegistryTest, CanRunCreator) {\n  unique_ptr<Foo> bar(FooRegistry()->Create(\"Bar\", 1));\n  EXPECT_TRUE(bar != nullptr) << \"Cannot create bar.\";\n  unique_ptr<Foo> another_bar(FooRegistry()->Create(\"AnotherBar\", 1));\n  EXPECT_TRUE(another_bar != nullptr);\n}\n\nTEST(RegistryTest, ReturnNullOnNonExistingCreator) {\n  EXPECT_EQ(FooRegistry()->Create(\"Non-existing bar\", 1), nullptr);\n}\n}\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/scope_guard.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Copyright 2016 Facebook\n * @author Tudor Bosman (tudorb@fb.com)\n */\n\n#pragma once\n\n#include <cstddef>\n#include <functional>\n#include <new>\n#include <type_traits>\n#include <utility>\n\nnamespace caffe2 {\n\n// Copied from folly/ScopeGuard.h\n\nnamespace detail {\n\nclass ScopeGuardImplBase {\n public:\n  void dismiss() noexcept {\n    dismissed_ = true;\n  }\n\n protected:\n  ScopeGuardImplBase() noexcept : dismissed_(false) {}\n\n  static ScopeGuardImplBase makeEmptyScopeGuard() noexcept {\n    return ScopeGuardImplBase{};\n  }\n\n  template <typename T>\n  static const T& asConst(const T& t) noexcept {\n    return t;\n  }\n\n  bool dismissed_;\n};\n\ntemplate <typename FunctionType>\nclass ScopeGuardImpl : public ScopeGuardImplBase {\n public:\n  explicit ScopeGuardImpl(FunctionType& fn) noexcept(\n      std::is_nothrow_copy_constructible<FunctionType>::value)\n      : ScopeGuardImpl(\n            asConst(fn),\n            makeFailsafe(std::is_nothrow_copy_constructible<FunctionType>{},\n                         &fn)) {}\n\n  explicit ScopeGuardImpl(const FunctionType& fn) noexcept(\n      std::is_nothrow_copy_constructible<FunctionType>::value)\n      : ScopeGuardImpl(\n            fn,\n            makeFailsafe(std::is_nothrow_copy_constructible<FunctionType>{},\n                         &fn)) {}\n\n  explicit ScopeGuardImpl(FunctionType&& fn) noexcept(\n      std::is_nothrow_move_constructible<FunctionType>::value)\n      : ScopeGuardImpl(\n            std::move_if_noexcept(fn),\n            makeFailsafe(std::is_nothrow_move_constructible<FunctionType>{},\n                         &fn)) {}\n\n  ScopeGuardImpl(ScopeGuardImpl&& other) noexcept(\n      std::is_nothrow_move_constructible<FunctionType>::value)\n      : function_(std::move_if_noexcept(other.function_)) {\n    // If the above line attempts a copy and the copy throws, other is\n    // left owning the cleanup action and will execute it (or not) depending\n    // on the value of other.dismissed_. The following lines only execute\n    // if the move/copy succeeded, in which case *this assumes ownership of\n    // the cleanup action and dismisses other.\n    dismissed_ = other.dismissed_;\n    other.dismissed_ = true;\n  }\n\n  ~ScopeGuardImpl() noexcept {\n    if (!dismissed_) {\n      execute();\n    }\n  }\n\n private:\n  static ScopeGuardImplBase makeFailsafe(std::true_type, const void*) noexcept {\n    return makeEmptyScopeGuard();\n  }\n\n  template <typename Fn>\n  static auto makeFailsafe(std::false_type, Fn* fn) noexcept\n      -> ScopeGuardImpl<decltype(std::ref(*fn))> {\n    return ScopeGuardImpl<decltype(std::ref(*fn))>{std::ref(*fn)};\n  }\n\n  template <typename Fn>\n  explicit ScopeGuardImpl(Fn&& fn, ScopeGuardImplBase&& failsafe)\n      : ScopeGuardImplBase{}, function_(std::forward<Fn>(fn)) {\n    failsafe.dismiss();\n  }\n\n  void* operator new(std::size_t) = delete;\n\n  void execute() noexcept { function_(); }\n\n  FunctionType function_;\n};\n\ntemplate <typename F>\nusing ScopeGuardImplDecay = ScopeGuardImpl<typename std::decay<F>::type>;\n\n} // namespace detail\n\n/**\n * ScopeGuard is a general implementation of the \"Initialization is\n * Resource Acquisition\" idiom.  Basically, it guarantees that a function\n * is executed upon leaving the currrent scope unless otherwise told.\n *\n * The MakeGuard() function is used to create a new ScopeGuard object.\n * It can be instantiated with a lambda function, a std::function<void()>,\n * a functor, or a void(*)() function pointer.\n *\n *\n * Usage example: Add a friend to memory iff it is also added to the db.\n *\n * void User::addFriend(User& newFriend) {\n *   // add the friend to memory\n *   friends_.push_back(&newFriend);\n *\n *   // If the db insertion that follows fails, we should\n *   // remove it from memory.\n *   auto guard = MakeGuard([&] { friends_.pop_back(); });\n *\n *   // this will throw an exception upon error, which\n *   // makes the ScopeGuard execute UserCont::pop_back()\n *   // once the Guard's destructor is called.\n *   db_->addFriend(GetName(), newFriend.GetName());\n *\n *   // an exception was not thrown, so don't execute\n *   // the Guard.\n *   guard.dismiss();\n * }\n *\n * Examine ScopeGuardTest.cpp for some more sample usage.\n *\n * Stolen from:\n *   Andrei's and Petru Marginean's CUJ article:\n *     http://drdobbs.com/184403758\n *   and the loki library:\n *     http://loki-lib.sourceforge.net/index.php?n=Idioms.ScopeGuardPointer\n *   and triendl.kj article:\n *     http://www.codeproject.com/KB/cpp/scope_guard.aspx\n */\ntemplate <typename F>\ndetail::ScopeGuardImplDecay<F> MakeGuard(F&& f) noexcept(\n    noexcept(detail::ScopeGuardImplDecay<F>(static_cast<F&&>(f)))) {\n  return detail::ScopeGuardImplDecay<F>(static_cast<F&&>(f));\n}\n\n}  // namespaces\n"
  },
  {
    "path": "caffe2/core/static_tracepoint.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * Copyright 2017 Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#if defined(__ELF__) && (defined(__x86_64__) || defined(__i386__))\n#include <caffe2/core/static_tracepoint_elfx86.h>\n\n#define CAFFE_SDT(name, ...)                                         \\\n  CAFFE_SDT_PROBE_N(                                                 \\\n    caffe2, name, CAFFE_SDT_NARG(0, ##__VA_ARGS__), ##__VA_ARGS__)\n#else\n#define CAFFE_SDT(name, ...) do {} while(0)\n#endif\n"
  },
  {
    "path": "caffe2/core/static_tracepoint_elfx86.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * Copyright 2017 Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n// Default constraint for the probe arguments as operands.\n#ifndef CAFFE_SDT_ARG_CONSTRAINT\n#define CAFFE_SDT_ARG_CONSTRAINT      \"nor\"\n#endif\n\n// Instruction to emit for the probe.\n#define CAFFE_SDT_NOP                 nop\n\n// Note section properties.\n#define CAFFE_SDT_NOTE_NAME           \"stapsdt\"\n#define CAFFE_SDT_NOTE_TYPE           3\n\n// Size of address depending on platform.\n#ifdef __LP64__\n#define CAFFE_SDT_ASM_ADDR            .8byte\n#else\n#define CAFFE_SDT_ASM_ADDR            .4byte\n#endif\n\n// Assembler helper Macros.\n#define CAFFE_SDT_S(x)                #x\n#define CAFFE_SDT_ASM_1(x)            CAFFE_SDT_S(x) \"\\n\"\n#define CAFFE_SDT_ASM_2(a, b)         CAFFE_SDT_S(a) \",\" CAFFE_SDT_S(b) \"\\n\"\n#define CAFFE_SDT_ASM_3(a, b, c)      CAFFE_SDT_S(a) \",\" CAFFE_SDT_S(b) \",\"    \\\n                                      CAFFE_SDT_S(c) \"\\n\"\n#define CAFFE_SDT_ASM_STRING(x)       CAFFE_SDT_ASM_1(.asciz CAFFE_SDT_S(x))\n\n// Helper to determine the size of an argument.\n#define CAFFE_SDT_ISARRAY(x)  (__builtin_classify_type(x) == 14)\n#define CAFFE_SDT_ARGSIZE(x)  (CAFFE_SDT_ISARRAY(x) ? sizeof(void*) : sizeof(x))\n\n// Format of each probe arguments as operand.\n// Size of the arugment tagged with CAFFE_SDT_Sn, with \"n\" constraint.\n// Value of the argument tagged with CAFFE_SDT_An, with configured constraint.\n#define CAFFE_SDT_ARG(n, x)                                                    \\\n  [CAFFE_SDT_S##n] \"n\"                ((size_t)CAFFE_SDT_ARGSIZE(x)),          \\\n  [CAFFE_SDT_A##n] CAFFE_SDT_ARG_CONSTRAINT (x)\n\n// Templates to append arguments as operands.\n#define CAFFE_SDT_OPERANDS_0()        [__sdt_dummy] \"g\" (0)\n#define CAFFE_SDT_OPERANDS_1(_1)      CAFFE_SDT_ARG(1, _1)\n#define CAFFE_SDT_OPERANDS_2(_1, _2)                                           \\\n  CAFFE_SDT_OPERANDS_1(_1), CAFFE_SDT_ARG(2, _2)\n#define CAFFE_SDT_OPERANDS_3(_1, _2, _3)                                       \\\n  CAFFE_SDT_OPERANDS_2(_1, _2), CAFFE_SDT_ARG(3, _3)\n#define CAFFE_SDT_OPERANDS_4(_1, _2, _3, _4)                                   \\\n  CAFFE_SDT_OPERANDS_3(_1, _2, _3), CAFFE_SDT_ARG(4, _4)\n#define CAFFE_SDT_OPERANDS_5(_1, _2, _3, _4, _5)                               \\\n  CAFFE_SDT_OPERANDS_4(_1, _2, _3, _4), CAFFE_SDT_ARG(5, _5)\n#define CAFFE_SDT_OPERANDS_6(_1, _2, _3, _4, _5, _6)                           \\\n  CAFFE_SDT_OPERANDS_5(_1, _2, _3, _4, _5), CAFFE_SDT_ARG(6, _6)\n#define CAFFE_SDT_OPERANDS_7(_1, _2, _3, _4, _5, _6, _7)                       \\\n  CAFFE_SDT_OPERANDS_6(_1, _2, _3, _4, _5, _6), CAFFE_SDT_ARG(7, _7)\n#define CAFFE_SDT_OPERANDS_8(_1, _2, _3, _4, _5, _6, _7, _8)                   \\\n  CAFFE_SDT_OPERANDS_7(_1, _2, _3, _4, _5, _6, _7), CAFFE_SDT_ARG(8, _8)\n\n// Templates to reference the arguments from operands in note section.\n#define CAFFE_SDT_ARGFMT(no)        %n[CAFFE_SDT_S##no]@%[CAFFE_SDT_A##no]\n#define CAFFE_SDT_ARG_TEMPLATE_0    /*No arguments*/\n#define CAFFE_SDT_ARG_TEMPLATE_1    CAFFE_SDT_ARGFMT(1)\n#define CAFFE_SDT_ARG_TEMPLATE_2    CAFFE_SDT_ARG_TEMPLATE_1 CAFFE_SDT_ARGFMT(2)\n#define CAFFE_SDT_ARG_TEMPLATE_3    CAFFE_SDT_ARG_TEMPLATE_2 CAFFE_SDT_ARGFMT(3)\n#define CAFFE_SDT_ARG_TEMPLATE_4    CAFFE_SDT_ARG_TEMPLATE_3 CAFFE_SDT_ARGFMT(4)\n#define CAFFE_SDT_ARG_TEMPLATE_5    CAFFE_SDT_ARG_TEMPLATE_4 CAFFE_SDT_ARGFMT(5)\n#define CAFFE_SDT_ARG_TEMPLATE_6    CAFFE_SDT_ARG_TEMPLATE_5 CAFFE_SDT_ARGFMT(6)\n#define CAFFE_SDT_ARG_TEMPLATE_7    CAFFE_SDT_ARG_TEMPLATE_6 CAFFE_SDT_ARGFMT(7)\n#define CAFFE_SDT_ARG_TEMPLATE_8    CAFFE_SDT_ARG_TEMPLATE_7 CAFFE_SDT_ARGFMT(8)\n\n// Structure of note section for the probe.\n#define CAFFE_SDT_NOTE_CONTENT(provider, name, arg_template)                   \\\n  CAFFE_SDT_ASM_1(990: CAFFE_SDT_NOP)                                          \\\n  CAFFE_SDT_ASM_3(     .pushsection .note.stapsdt,\"\",\"note\")                   \\\n  CAFFE_SDT_ASM_1(     .balign 4)                                              \\\n  CAFFE_SDT_ASM_3(     .4byte 992f-991f, 994f-993f, CAFFE_SDT_NOTE_TYPE)       \\\n  CAFFE_SDT_ASM_1(991: .asciz CAFFE_SDT_NOTE_NAME)                             \\\n  CAFFE_SDT_ASM_1(992: .balign 4)                                              \\\n  CAFFE_SDT_ASM_1(993: CAFFE_SDT_ASM_ADDR 990b)                                \\\n  CAFFE_SDT_ASM_1(     CAFFE_SDT_ASM_ADDR 0) /*Reserved for Semaphore address*/\\\n  CAFFE_SDT_ASM_1(     CAFFE_SDT_ASM_ADDR 0) /*Reserved for Semaphore name*/   \\\n  CAFFE_SDT_ASM_STRING(provider)                                               \\\n  CAFFE_SDT_ASM_STRING(name)                                                   \\\n  CAFFE_SDT_ASM_STRING(arg_template)                                           \\\n  CAFFE_SDT_ASM_1(994: .balign 4)                                              \\\n  CAFFE_SDT_ASM_1(     .popsection)\n\n// Main probe Macro.\n#define CAFFE_SDT_PROBE(provider, name, n, arglist)                            \\\n    __asm__ __volatile__ (                                                     \\\n      CAFFE_SDT_NOTE_CONTENT(provider, name, CAFFE_SDT_ARG_TEMPLATE_##n)       \\\n      :: CAFFE_SDT_OPERANDS_##n arglist                                        \\\n    )                                                                          \\\n\n// Helper Macros to handle variadic arguments.\n#define CAFFE_SDT_NARG_(_0, _1, _2, _3, _4, _5, _6, _7, _8, N, ...) N\n#define CAFFE_SDT_NARG(...)                                                    \\\n  CAFFE_SDT_NARG_(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)\n#define CAFFE_SDT_PROBE_N(provider, name, N, ...)                              \\\n  CAFFE_SDT_PROBE(provider, name, N, (__VA_ARGS__))\n"
  },
  {
    "path": "caffe2/core/stats.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/stats.h\"\n\n#include <condition_variable>\n#include <thread>\n\nnamespace caffe2 {\n\nExportedStatMap toMap(const ExportedStatList& stats) {\n  ExportedStatMap statMap;\n  for (const auto& stat : stats) {\n    // allow for multiple instances of a key\n    statMap[stat.key] += stat.value;\n  }\n  return statMap;\n}\n\nStatValue* StatRegistry::add(const std::string& name) {\n  std::lock_guard<std::mutex> lg(mutex_);\n  auto it = stats_.find(name);\n  if (it != stats_.end()) {\n    return it->second.get();\n  }\n  auto v = std::unique_ptr<StatValue>(new StatValue);\n  auto value = v.get();\n  stats_.insert(std::make_pair(name, std::move(v)));\n  return value;\n}\n\nvoid StatRegistry::publish(ExportedStatList& exported, bool reset) {\n  std::lock_guard<std::mutex> lg(mutex_);\n  exported.resize(stats_.size());\n  int i = 0;\n  for (const auto& kv : stats_) {\n    auto& out = exported.at(i++);\n    out.key = kv.first;\n    out.value = reset ? kv.second->reset() : kv.second->get();\n    out.ts = std::chrono::high_resolution_clock::now();\n  }\n}\n\nvoid StatRegistry::update(const ExportedStatList& data) {\n  for (const auto& stat : data) {\n    add(stat.key)->increment(stat.value);\n  }\n}\n\nStatRegistry::~StatRegistry() {}\n\nStatRegistry& StatRegistry::get() {\n  static StatRegistry r;\n  return r;\n}\n}\n"
  },
  {
    "path": "caffe2/core/stats.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <atomic>\n#include <memory>\n#include <mutex>\n#include <string>\n#include <unordered_map>\n#include <vector>\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/static_tracepoint.h\"\n\nnamespace caffe2 {\n\nclass StatValue {\n  std::atomic<int64_t> v_{0};\n\n public:\n  int64_t increment(int64_t inc) {\n    return v_ += inc;\n  }\n\n  int64_t reset(int64_t value = 0) {\n    return v_.exchange(value);\n  }\n\n  int64_t get() const {\n    return v_.load();\n  }\n};\n\nstruct ExportedStatValue {\n  std::string key;\n  int64_t value;\n  std::chrono::time_point<std::chrono::high_resolution_clock> ts;\n};\n\n/**\n * @brief Holds names and values of counters exported from a StatRegistry.\n */\nusing ExportedStatList = std::vector<ExportedStatValue>;\nusing ExportedStatMap = std::unordered_map<std::string, int64_t>;\n\nExportedStatMap toMap(const ExportedStatList& stats);\n\n/**\n * @brief Holds a map of atomic counters keyed by name.\n *\n * The StatRegistry singleton, accessed through StatRegistry::get(), holds\n * counters registered through the macro CAFFE_EXPORTED_STAT. Example of usage:\n *\n * struct MyCaffeClass {\n *   MyCaffeClass(const std::string& instanceName): stats_(instanceName) {}\n *   void run(int numRuns) {\n *     try {\n *       CAFFE_EVENT(stats_, num_runs, numRuns);\n *       tryRun(numRuns);\n *       CAFFE_EVENT(stats_, num_successes);\n *     } catch (std::exception& e) {\n *       CAFFE_EVENT(stats_, num_failures, 1, \"arg_to_usdt\", e.what());\n *     }\n *     CAFFE_EVENT(stats_, usdt_only, 1, \"arg_to_usdt\");\n *   }\n *  private:\n *   struct MyStats {\n *     CAFFE_STAT_CTOR(MyStats);\n *     CAFFE_EXPORTED_STAT(num_runs);\n *     CAFFE_EXPORTED_STAT(num_successes);\n *     CAFFE_EXPORTED_STAT(num_failures);\n *     CAFFE_STAT(usdt_only);\n *   } stats_;\n * };\n *\n * int main() {\n *   MyCaffeClass a(\"first\");\n *   MyCaffeClass b(\"second\");\n *   for (int i = 0; i < 10; ++i) {\n *     a.run(10);\n *     b.run(5);\n *   }\n *   ExportedStatList finalStats;\n *   StatRegistry::get().publish(finalStats);\n * }\n *\n * For every new instance of MyCaffeClass, a new counter is created with\n * the instance name as prefix. Everytime run() is called, the corresponding\n * counter will be incremented by the given value, or 1 if value not provided.\n *\n * Counter values can then be exported into an ExportedStatList. In the\n * example above, considering \"tryRun\" never throws, `finalStats` will be\n * populated as follows:\n *\n *   first/num_runs       100\n *   first/num_successes   10\n *   first/num_failures     0\n *   second/num_runs       50\n *   second/num_successes  10\n *   second/num_failures    0\n *\n * The event usdt_only is not present in ExportedStatList because it is declared\n * as CAFFE_STAT, which does not create a counter.\n *\n * Additionally, for each call to CAFFE_EVENT, a USDT probe is generated.\n * The probe will be set up with the following arguments:\n *   - Probe name: field name (e.g. \"num_runs\")\n *   - Arg #0: instance name (e.g. \"first\", \"second\")\n *   - Arg #1: For CAFFE_EXPORTED_STAT, value of the updated counter\n *             For CAFFE_STAT, -1 since no counter is available\n *   - Args ...: Arguments passed to CAFFE_EVENT, including update value\n *             when provided.\n *\n * It is also possible to create additional StatRegistry instances beyond\n * the singleton. These instances are not automatically populated with\n * CAFFE_EVENT. Instead, they can be populated from an ExportedStatList\n * structure by calling StatRegistry::update().\n *\n */\nclass StatRegistry {\n  std::mutex mutex_;\n  std::unordered_map<std::string, std::unique_ptr<StatValue>> stats_;\n\n public:\n  /**\n   * Retrieve the singleton StatRegistry, which gets populated\n   * through the CAFFE_EVENT macro.\n   */\n  static StatRegistry& get();\n\n  /**\n   * Add a new counter with given name. If a counter for this name already\n   * exists, returns a pointer to it.\n   */\n  StatValue* add(const std::string& name);\n\n  /**\n   * Populate an ExportedStatList with current counter values.\n   * If `reset` is true, resets all counters to zero. It is guaranteed that no\n   * count is lost.\n   */\n  void publish(ExportedStatList& exported, bool reset = false);\n\n  ExportedStatList publish(bool reset = false) {\n    ExportedStatList stats;\n    publish(stats, reset);\n    return stats;\n  }\n\n  /**\n   * Update values of counters contained in the given ExportedStatList to\n   * the values provided, creating counters that don't exist.\n   */\n  void update(const ExportedStatList& data);\n\n  ~StatRegistry();\n};\n\nstruct Stat {\n  std::string groupName;\n  std::string name;\n  Stat(const std::string& gn, const std::string& n) : groupName(gn), name(n) {}\n\n  template <typename... Unused>\n  int64_t increment(Unused...) {\n    return -1;\n  }\n};\n\nclass ExportedStat : public Stat {\n  StatValue* value_;\n\n public:\n  ExportedStat(const std::string& gn, const std::string& n)\n      : Stat(gn, n), value_(StatRegistry::get().add(gn + \"/\" + n)) {}\n\n  int64_t increment(int64_t value = 1) {\n    return value_->increment(value);\n  }\n\n  template <typename T, typename Unused1, typename... Unused>\n  int64_t increment(T value, Unused1, Unused...) {\n    return increment(value);\n  }\n};\n\nclass AvgExportedStat : public ExportedStat {\n private:\n  ExportedStat count_;\n\n public:\n  AvgExportedStat(const std::string& gn, const std::string& n)\n      : ExportedStat(gn, n + \"/sum\"), count_(gn, n + \"/count\") {}\n\n  int64_t increment(int64_t value = 1) {\n    count_.increment();\n    return ExportedStat::increment(value);\n  }\n\n  template <typename T, typename Unused1, typename... Unused>\n  int64_t increment(T value, Unused1, Unused...) {\n    return increment(value);\n  }\n};\n\nclass StdDevExportedStat : public ExportedStat {\n  // Uses an offset (first_) to remove issue of cancellation\n  // Variance is then (sumsqoffset_ - (sumoffset_^2) / count_) / (count_ - 1)\n private:\n  ExportedStat count_;\n  ExportedStat sumsqoffset_;\n  ExportedStat sumoffset_;\n  std::atomic<int64_t> first_{std::numeric_limits<int64_t>::min()};\n  int64_t const_min_{std::numeric_limits<int64_t>::min()};\n\n public:\n  StdDevExportedStat(const std::string& gn, const std::string& n)\n      : ExportedStat(gn, n + \"/sum\"),\n        count_(gn, n + \"/count\"),\n        sumsqoffset_(gn, n + \"/sumsqoffset\"),\n        sumoffset_(gn, n + \"/sumoffset\") {}\n\n  int64_t increment(int64_t value = 1) {\n    first_.compare_exchange_strong(const_min_, value);\n    int64_t offset_value = first_.load();\n    int64_t orig_value = value;\n    value -= offset_value;\n    count_.increment();\n    sumsqoffset_.increment(value * value);\n    sumoffset_.increment(value);\n    return ExportedStat::increment(orig_value);\n  }\n\n  template <typename T, typename Unused1, typename... Unused>\n  int64_t increment(T value, Unused1, Unused...) {\n    return increment(value);\n  }\n};\n\nclass DetailedExportedStat : public ExportedStat {\n private:\n  std::vector<ExportedStat> details_;\n\n public:\n  DetailedExportedStat(const std::string& gn, const std::string& n)\n      : ExportedStat(gn, n) {}\n\n  void setDetails(const std::vector<std::string>& detailNames) {\n    details_.clear();\n    for (const auto& detailName : detailNames) {\n      details_.emplace_back(groupName, name + \"/\" + detailName);\n    }\n  }\n\n  template <typename T, typename... Unused>\n  int64_t increment(T value, size_t detailIndex, Unused...) {\n    if (detailIndex < details_.size()) {\n      details_[detailIndex].increment(value);\n    }\n    return ExportedStat::increment(value);\n  }\n};\n\nnamespace detail {\n\ntemplate <class T>\nstruct _ScopeGuard {\n  T f_;\n  std::chrono::high_resolution_clock::time_point start_;\n\n  explicit _ScopeGuard(T f)\n      : f_(f), start_(std::chrono::high_resolution_clock::now()) {}\n  ~_ScopeGuard() {\n    using namespace std::chrono;\n    auto duration = high_resolution_clock::now() - start_;\n    int64_t nanos = duration_cast<nanoseconds>(duration).count();\n    f_(nanos);\n  }\n\n  // Using implicit cast to bool so that it can be used in an 'if' condition\n  // within CAFFE_DURATION macro below.\n  /* implicit */ operator bool() {\n    return true;\n  }\n};\n\ntemplate <class T>\n_ScopeGuard<T> ScopeGuard(T f) {\n  return _ScopeGuard<T>(f);\n}\n}\n\n#define CAFFE_STAT_CTOR(ClassName)                 \\\n  ClassName(std::string name) : groupName(name) {} \\\n  std::string groupName\n\n#define CAFFE_EXPORTED_STAT(name) \\\n  ExportedStat name {             \\\n    groupName, #name              \\\n  }\n\n#define CAFFE_AVG_EXPORTED_STAT(name) \\\n  AvgExportedStat name {              \\\n    groupName, #name                  \\\n  }\n\n#define CAFFE_STDDEV_EXPORTED_STAT(name) \\\n  StdDevExportedStat name {              \\\n    groupName, #name                     \\\n  }\n\n#define CAFFE_DETAILED_EXPORTED_STAT(name) \\\n  DetailedExportedStat name {              \\\n    groupName, #name                       \\\n  }\n\n#define CAFFE_STAT(name) \\\n  Stat name {            \\\n    groupName, #name     \\\n  }\n\n#define CAFFE_EVENT(stats, field, ...)                              \\\n  {                                                                 \\\n    auto __caffe_event_value_ = stats.field.increment(__VA_ARGS__); \\\n    CAFFE_SDT(                                                      \\\n        field,                                                      \\\n        stats.field.groupName.c_str(),                              \\\n        __caffe_event_value_,                                       \\\n        ##__VA_ARGS__);                                             \\\n  }\n\n#define CAFFE_DURATION(stats, field, ...)                \\\n  if (auto g = detail::ScopeGuard([&](int64_t nanos) {   \\\n        CAFFE_EVENT(stats, field, nanos, ##__VA_ARGS__); \\\n      }))\n}\n"
  },
  {
    "path": "caffe2/core/stats_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <chrono>\n#include <iostream>\n#include <thread>\n\n#include \"caffe2/core/stats.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\nnamespace {\n\nstruct MyCaffeClass {\n  explicit MyCaffeClass(const std::string& name) : stats_(name) {}\n\n  void tryRun(int) {}\n\n  void run(int numRuns) {\n    try {\n      CAFFE_EVENT(stats_, num_runs, numRuns);\n      tryRun(numRuns);\n      CAFFE_EVENT(stats_, num_successes);\n    } catch (std::exception& e) {\n      CAFFE_EVENT(stats_, num_failures, 1, \"arg_to_usdt\", e.what());\n    }\n    CAFFE_EVENT(stats_, usdt_only, 1, \"arg_to_usdt\");\n  }\n\n private:\n  struct MyStats {\n    CAFFE_STAT_CTOR(MyStats);\n    CAFFE_EXPORTED_STAT(num_runs);\n    CAFFE_EXPORTED_STAT(num_successes);\n    CAFFE_EXPORTED_STAT(num_failures);\n    CAFFE_STAT(usdt_only);\n  } stats_;\n};\n\nExportedStatMap filterMap(\n    const ExportedStatMap& map,\n    const ExportedStatMap& keys) {\n  ExportedStatMap filtered;\n  for (const auto& kv : map) {\n    if (keys.count(kv.first) > 0) {\n      filtered.insert(kv);\n    }\n  }\n  return filtered;\n}\n\n#define EXPECT_SUBSET(map, sub) EXPECT_EQ(filterMap((map), (sub)), (sub))\n\nTEST(StatsTest, StatsTestClass) {\n  MyCaffeClass a(\"first\");\n  MyCaffeClass b(\"second\");\n  for (int i = 0; i < 10; ++i) {\n    a.run(10);\n    b.run(5);\n  }\n  EXPECT_SUBSET(\n      ExportedStatMap({\n          {\"first/num_runs\", 100},\n          {\"first/num_successes\", 10},\n          {\"first/num_failures\", 0},\n          {\"second/num_runs\", 50},\n          {\"second/num_successes\", 10},\n          {\"second/num_failures\", 0},\n      }),\n      toMap(StatRegistry::get().publish()));\n}\n\nTEST(StatsTest, StatsTestDuration) {\n  struct TestStats {\n    CAFFE_STAT_CTOR(TestStats);\n    CAFFE_STAT(count);\n    CAFFE_AVG_EXPORTED_STAT(time_ns);\n  };\n  TestStats stats(\"stats\");\n  CAFFE_DURATION(stats, time_ns) {\n    std::this_thread::sleep_for(std::chrono::microseconds(1));\n  }\n\n  ExportedStatList data;\n  StatRegistry::get().publish(data);\n  auto map = toMap(data);\n  auto countIt = map.find(\"stats/time_ns/count\");\n  auto sumIt = map.find(\"stats/time_ns/sum\");\n  EXPECT_TRUE(countIt != map.end() && sumIt != map.end());\n  EXPECT_EQ(countIt->second, 1);\n  EXPECT_GT(sumIt->second, 0);\n}\n\nTEST(StatsTest, StatsTestSimple) {\n  struct TestStats {\n    CAFFE_STAT_CTOR(TestStats);\n    CAFFE_STAT(s1);\n    CAFFE_STAT(s2);\n    CAFFE_EXPORTED_STAT(s3);\n  };\n  TestStats i1(\"i1\");\n  TestStats i2(\"i2\");\n  CAFFE_EVENT(i1, s1);\n  CAFFE_EVENT(i1, s2);\n  CAFFE_EVENT(i1, s3, 1);\n  CAFFE_EVENT(i1, s3, -1);\n  CAFFE_EVENT(i2, s3, 2);\n\n  ExportedStatList data;\n  StatRegistry::get().publish(data);\n  EXPECT_SUBSET(toMap(data), ExportedStatMap({{\"i1/s3\", 0}, {\"i2/s3\", 2}}));\n\n  StatRegistry reg2;\n  reg2.update(data);\n  reg2.update(data);\n\n  EXPECT_SUBSET(\n      toMap(reg2.publish(true)), ExportedStatMap({{\"i1/s3\", 0}, {\"i2/s3\", 4}}));\n  EXPECT_SUBSET(\n      toMap(reg2.publish()), ExportedStatMap({{\"i1/s3\", 0}, {\"i2/s3\", 0}}));\n}\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/tensor.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/tensor.h\"\n\n#include \"caffe2/core/blob_stats.h\"\n#include \"caffe2/core/flags.h\"\n\nCAFFE2_DEFINE_bool(\n    caffe2_keep_on_shrink,\n    true,\n    \"If set, keeps memory when a tensor is shrinking its size.\");\n\nCAFFE2_DEFINE_int64(\n    caffe2_max_keep_on_shrink_memory,\n    LLONG_MAX,\n    \"The maximum memory in bytes to keep on shrink, if the difference between \"\n    \"tensor sizes is bigger than this then tensor will be reset.\");\n\nnamespace caffe2 {\n// declaring it here instead of context.cc because tensor.h includes context.h\nCAFFE_KNOWN_TYPE(Tensor<CPUContext>);\n\nTensorPrinter::TensorPrinter(\n    const std::string& tensor_name,\n    const std::string& file_name,\n    int limit)\n    : to_file_(!file_name.empty()),\n      limit_(limit ? limit : k_limit_default_),\n      tensor_name_(tensor_name) {\n  if (to_file_) {\n    // We will output to file instead of printing on screen.\n    // We will write each individual tensor to its individual file.\n    log_file_.reset(new std::ofstream(\n        file_name, std::ofstream::out | std::ofstream::trunc));\n    CAFFE_ENFORCE(\n        log_file_->good(),\n        \"Failed to open TensorPrinter file \",\n        file_name,\n        \". rdstate() = \",\n        log_file_->rdstate());\n  }\n}\n\nTensorPrinter::~TensorPrinter() {\n  if (log_file_.get()) {\n    log_file_->close();\n  }\n}\n\nstatic CaffeMap<CaffeTypeId, TypeCall> type_call_registry_ {\n  {TypeMeta::Id<Tensor<CPUContext>>(), GetTensorType<CPUContext>}\n};\n\nTypeCall GetTypeCallFunction(CaffeTypeId id) {\n  auto f = type_call_registry_.find(id);\n  if (f == type_call_registry_.end()) {\n    return nullptr;\n  }\n  return f->second;\n}\n\nvoid RegisterTypeCallFunction(CaffeTypeId id, TypeCall c) {\n  type_call_registry_[id] = c;\n}\n\nstatic CaffeMap<CaffeTypeId, TensorInfoCall> tensor_info_call_registry_{\n    {TypeMeta::Id<Tensor<CPUContext>>(), GetTensorInfo<CPUContext>}};\n\nTensorInfoCall GetTensorInfoFunction(CaffeTypeId id) {\n  auto f = tensor_info_call_registry_.find(id);\n  if (f == tensor_info_call_registry_.end()) {\n    return nullptr;\n  }\n  return f->second;\n}\n\nvoid RegisterTensorInfoFunction(CaffeTypeId id, TensorInfoCall c) {\n  tensor_info_call_registry_[id] = c;\n}\n\nnamespace {\n\nstruct TensorCPUStatGetter : BlobStatGetter {\n  size_t sizeBytes(const Blob& blob) const override {\n    const auto& tensor = blob.Get<TensorCPU>();\n    auto nbytes = tensor.nbytes();\n    if (nbytes > 0 && tensor.IsType<std::string>()) {\n      const auto* data = tensor.data<std::string>();\n      for (size_t i = 0; i < tensor.size(); ++i) {\n        nbytes += data[i].size();\n      }\n    }\n    return nbytes;\n  }\n};\nREGISTER_BLOB_STAT_GETTER(TensorCPU, TensorCPUStatGetter);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/tensor.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_TENSOR_H_\n#define CAFFE2_CORE_TENSOR_H_\n\n#include <cstddef>\n#include <cstdint>\n#include <fstream>\n#include <sstream>\n#include <type_traits>\n#include <typeinfo>\n#include <vector>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/typeid.h\"\n#include \"caffe2/core/logging.h\"\n\n// A global boolean variable to control whether we free memory when a Tensor\n// is shrinked to a smaller size. As a result, a Tensor is always going to\n// keep the memory allocated for its maximum capacity reshaped to so far.\nCAFFE2_DECLARE_bool(caffe2_keep_on_shrink);\n\n// Since we can have high variance in blob memory allocated across different\n// inputs in the same run, we will shrink the blob only if the memory gain\n// is larger than this flag in bytes.\nCAFFE2_DECLARE_int64(caffe2_max_keep_on_shrink_memory);\n\nnamespace caffe2 {\n\n/**\n * A utility function to convert vector<int> to vector<TIndex>.\n */\ninline vector<TIndex> ToVectorTIndex(const std::vector<int>& src) {\n  return vector<TIndex>(src.begin(), src.end());\n}\n\n/**\n * Return product of all dimensions starting from K\n */\ninline TIndex size_from_dim_(int k, const vector<TIndex>& dims) {\n  TIndex r = 1;\n  for (int i = k; i < dims.size(); ++i) {\n    r *= dims[i];\n  }\n  return r;\n}\n\n// Product of all dims up to\ninline TIndex size_to_dim_(int k, const vector<TIndex>& dims) {\n  CAFFE_ENFORCE(k <= dims.size());\n  TIndex r = 1;\n  for (int i = 0; i < k; ++i) {\n    r *= dims[i];\n  }\n  return r;\n}\n\n// Product of all dims between k and l (not including dims[k] and dims[l])\ninline TIndex size_between_dim_(int k, int l, const vector<TIndex>& dims) {\n  CAFFE_ENFORCE(l < dims.size());\n  TIndex r = 1;\n  if (k < l) {\n    for (int i = k + 1; i < l; ++i) {\n      r *= dims[i];\n    }\n  } else {\n    for (int i = l + 1; i < k; ++i) {\n      r *= dims[i];\n    }\n  }\n  return r;\n}\n\ninline int canonical_axis_index_(int axis_index, int ndims) {\n  CAFFE_ENFORCE_GE(axis_index, -ndims);\n  CAFFE_ENFORCE_LT(axis_index, ndims);\n  if (axis_index < 0) {\n    return axis_index + ndims;\n  }\n  return axis_index;\n}\n\n/**\n * @brief Tensor is the basic class in Caffe2 that stores a contiguous memory\n * with its shape information.\n *\n * The Tensor class is essentially a wrapper around a device-specific memory\n * (the device is specified by the Context template argument), and deals with\n * the allocation and de-allocation of such memory. We make a simplified\n * assumption that the memory is always contiguous.\n */\ntemplate <class Context>\nclass Tensor {\n public:\n  /**\n   * Initializes an empty tensor.\n   */\n  Tensor() {}\n\n  /**\n   * @brief Creates a tensor of the given dimension.\n   *\n   * Note that the actual data allocation is not going to be carried out until\n   * the first time mutable_data() is called.\n   */\n  explicit Tensor(const vector<TIndex>& dims) { Resize(dims); }\n  explicit Tensor(const vector<int>& dims) { Resize(dims); }\n\n  /**\n   * @brief Creates a tensor from a source tensor, copying over the content.\n   *\n   * Note that the source tensor can be from a different device context. The\n   * second argument provides a device context object (either Context or\n   * SrcContext) that will be responsible for copying the underlying data.\n   * If you do not wish to pass in a Context object, an equivalent constructor\n   * function exists that will create an implicit context object for copy, but\n   * be noted that this will cause a potential performance hit.\n   */\n  template <class SrcContext, class ContextForCopy>\n  Tensor(const Tensor<SrcContext>& src, ContextForCopy* context) {\n    CopyFrom(src, context);\n  }\n\n  /**\n   * @brief Creates a tensor from a source tensor, copying over the content.\n   *\n   * Note that this may have a potential performance hit, since a temporary\n   * context object will be created for the memory copy. Prefer explicitly\n   * providing a context for copy if you can.\n   *\n   * Since it's a potentially expensive operation - making copy constructor\n   * explicit here. If SrcContext != Context it's actually a typecast\n   * constructor and it should be definitely explicit.\n   */\n  template <class SrcContext>\n  explicit Tensor(const Tensor<SrcContext>& src) {\n    CopyFrom(src);\n  }\n\n  /**\n   * @brief Creates a tensor, and fills its contents with the given values.\n   */\n  template <typename T>\n  Tensor(const vector<TIndex>& dims, const vector<T>& values, Context* context)\n      : meta_(TypeMeta::Make<T>()) {\n    Resize(dims);\n    CAFFE_ENFORCE_EQ_WITH_CALLER(values.size(), size_);\n    context->template Copy<T, CPUContext, Context>(size_, values.data(), mutable_data<T>());\n  }\n\n  /**\n   * @brief Creates a scalar tensor, and fills its content with the given value.\n   */\n  template <typename T,\n            typename = typename std::enable_if<std::is_scalar<T>::value>::type>\n  Tensor(const T& value, Context* context) {\n    Resize(vector<TIndex>{});\n    context->template Copy<T, CPUContext, Context>(size_, &value, mutable_data<T>());\n  }\n\n  /**\n   * @brief Copies the data from a source tensor, with a contex provided to\n   * carry out the underlying memcpy operation.\n   */\n  template <class SrcContext, class ContextForCopy>\n  void CopyFrom(const Tensor<SrcContext>& src, ContextForCopy* context) {\n    if ((void*)&src == (void*)this) {\n      return;\n    }\n    meta_ = src.meta();\n    Resize(src.dims());\n    if (size() > 0) {\n      if (meta_.copy()) {\n        meta_.copy()(src.raw_data(), raw_mutable_data(), size());\n      } else {\n        context->template CopyBytes<SrcContext, Context>(\n            nbytes(), src.raw_data(), raw_mutable_data());\n      }\n    }\n  }\n\n  /**\n   * @brief Copies the data from a source tensor.\n   *\n   * Note that this may have a potential performance hit, since a temporary\n   * context object will be created for the memory copy. Prefer explicitly\n   * providing a context for copy if you can.\n   */\n  template <class SrcContext>\n  inline void CopyFrom(const Tensor<SrcContext>& src) {\n    SrcContext tmp_context;\n    CopyFrom(src, &tmp_context);\n  }\n\n  virtual ~Tensor() noexcept {}\n\n  /**\n   * @brief Extends the outer-most dimension of this tensor by num elements,\n   * preserving the existing data.\n   *\n   * The underlying data may be reallocated in order to accommodate the new\n   * elements, in which case this tensors' capacity is grown at a factor of\n   * growthPct. This ensures that Extend runs on an amortized O(1) time\n   * complexity.\n   */\n  template <class ContextForCopy>\n  void Extend(TIndex num, float growthPct, ContextForCopy* context) {\n    CAFFE_ENFORCE_GE_WITH_CALLER(dims_.size(), 1);\n    auto newDims = dims_;\n    newDims[0] += num;\n    if (!data_) {\n      Resize(newDims);\n      return;\n    }\n    auto newSize = std::accumulate(\n        newDims.begin(),\n        newDims.end(),\n        static_cast<TIndex>(1),\n        std::multiplies<TIndex>());\n    if (newSize * meta_.itemsize() <= capacity_) {\n      dims_ = newDims;\n      size_ = newSize;\n      return;\n    }\n    auto newCapacity = dims_;\n    newCapacity[0] = std::max<size_t>(\n        newDims[0], std::ceil(dims_[0] * (growthPct + 100) / 100));\n    Reserve(newCapacity, context);\n    dims_ = newDims;\n    size_ = newSize;\n  }\n\n  template <class T, class ContextForCopy>\n  void Reserve(const std::vector<T>& newCapacity, ContextForCopy* context) {\n    auto newSize = std::accumulate(\n        newCapacity.begin(),\n        newCapacity.end(),\n        static_cast<TIndex>(1),\n        std::multiplies<TIndex>());\n    if (newSize * meta_.itemsize() <= capacity_) {\n      return;\n    }\n    auto oldData = std::move(data_);\n    auto oldSize = size_;\n    auto oldDims = dims_;\n    Resize(newCapacity);\n    auto* newData = raw_mutable_data(meta_);\n    context->template CopyItems<ContextForCopy, ContextForCopy>(\n        meta_, oldSize, oldData.get(), newData);\n    dims_ = oldDims;\n    size_ = oldSize;\n    reserved_ = true;\n  }\n\n  /**\n   * @brief Shrinks the outer-most dimension to given size, keeping the data.\n   *\n   * This method guarantees that no re-allocations are carried out, which means\n   * that the extra capacity after the end of the shurnk tensor is maintained.\n   */\n  void Shrink(TIndex outer_dim) {\n    CAFFE_ENFORCE_WITH_CALLER(dims_.size() >= 1, \"Tensor must be at least 1D\");\n    CAFFE_ENFORCE_WITH_CALLER(\n        outer_dim <= dims_[0],\n        \"New outer dimension must be smaller than current.\");\n    dims_[0] = outer_dim;\n    size_ = std::accumulate(\n        dims_.begin(),\n        dims_.end(),\n        static_cast<TIndex>(1),\n        std::multiplies<TIndex>());\n  }\n\n  /**\n   * @brief Resizes a tensor.\n   *\n   * Resize takes in a vector of ints specifying the dimensions of the tensor.\n   * You can pass in an empty vector to specify that it is a scalar (i.e.\n   * containing one single item).\n   *\n   * The underlying storage may be deleted after calling Resize: if the new\n   * shape leads to a different number of items in the tensor, the old memory\n   * is deleted and new memory will be allocated next time you call\n   * mutable_data(). However, if the shape is different but the total number of\n   * items is the same, the underlying storage is kept.\n   */\n  template <typename... Ts>\n  void Resize(Ts... dim_source) {\n    bool size_changed = SetDims(dim_source...);\n    if (size_changed) {\n      // If needed, we will free the data. the next mutable_data() call\n      // will create the data storage.\n      int64_t new_size = size_ * meta_.itemsize();\n      bool reset_tensor = false;\n      if (reserved_) {\n        // If tensor is reserved then don't claim its memeory unless capacity_\n        // is smaller than new size\n        reset_tensor = capacity_ < new_size;\n      } else {\n        reset_tensor = capacity_ < new_size || !FLAGS_caffe2_keep_on_shrink ||\n            capacity_ - new_size > FLAGS_caffe2_max_keep_on_shrink_memory;\n      }\n\n      if (reset_tensor) {\n        FreeMemory();\n      }\n    }\n  }\n\n  /**\n   * Resize the tensor like the source tensor. Note that this is just a\n   * sugar wrapper that essentially calls Resize(src_tensor.dims()).\n   */\n  template <class OtherContext>\n  inline void ResizeLike(const Tensor<OtherContext>& src_tensor) {\n    // Note: need casting for different context types.\n    if (static_cast<void*>(this) != static_cast<const void*>(&src_tensor)) {\n      Resize(src_tensor.dims());\n    }\n  }\n\n  /**\n   * Resizes the tensor without touching underlying storage.\n   * This requires the total size of the tensor to remains constant.\n   */\n  inline void Reshape(const vector<TIndex>& dims) {\n    TIndex new_size = 1;\n    for (auto d : dims) {\n      CAFFE_ENFORCE_GE_WITH_CALLER(d, 0);\n      new_size *= d;\n    }\n    CAFFE_ENFORCE_WITH_CALLER(\n        new_size == size_,\n        \"New size and old size are not equal. You cannot use Reshape, \"\n        \"but should use Resize.\"\n        // TODO(jiayq): remove the following warning after pending diffs\n        // stabilize.\n        \" The old caffe2 mixes Reshape and Resize but this behavior has \"\n        \"been changed. If you find this error, most likely you will need \"\n        \"to change corresponding code from Reshape to Resize.\");\n    dims_ = dims;\n  }\n\n  inline void Reshape(const vector<int>& dims) {\n    Reshape(ToVectorTIndex(dims));\n  }\n\n  /**\n   * Release whatever memory the tensor was holding but keep size and type\n   * information. Subsequent call to mutable_data will trigger new memory\n   * allocation.\n   */\n  inline void FreeMemory() {\n    data_.reset();\n    capacity_ = 0;\n    // If reserved is true and we changed tensor memory then it is fine\n    // to switch it to false, if Resize is called from Reserve and it triggers\n    // FreeMemory() then reserved_ will be set to true at end of Reserve()\n    reserved_ = false;\n  }\n\n  /**\n   * A utility function to print the debug string for the tensor. Note that this\n   * is very slow since it involves quite some string operations, so do not use\n   * it in your performance-critical code.\n   */\n  string DebugString() const {\n    std::stringstream ss;\n    ss << \"A Tensor of item size \" << itemsize() << \" and type \"\n       << meta_.name() << \" and dimension (\";\n    for (int d : dims_) {\n      ss << d << \",\";\n    }\n    ss << \").\";\n    return ss.str();\n  }\n\n  void swap(Tensor<Context>& other) {\n    std::swap(dims_, other.dims_);\n    std::swap(size_, other.size_);\n    std::swap(meta_, other.meta_);\n    std::swap(data_, other.data_);\n    std::swap(shares_data_, other.shares_data_);\n    std::swap(capacity_, other.capacity_);\n    std::swap(reserved_, other.reserved_);\n  }\n\n  /**\n   * @brief Shares the data with another tensor.\n   *\n   * To share data between two tensors, the sizes of the two tensors must be\n   * equal already. The reason we do not implicitly do a Resize to make the two\n   * tensors have the same shape is that we want to allow tensors of different\n   * shapes but the same number of items to still be able to share data. This\n   * allows one to e.g. have a n-dimensional Tensor and a flattened version\n   * sharing the same underlying storage.\n   *\n   * The source tensor should already have its data allocated.\n   */\n  void ShareData(const Tensor& src) {\n    meta_ = src.meta();\n    CAFFE_ENFORCE_EQ_WITH_CALLER(\n        src.size_,\n        size_,\n        \"Size mismatch - did you call reshape before sharing the data?\");\n    // It is possible that the source tensor hasn't called mutable_data() yet,\n    // in which case ShareData() doesn't make much sense since we don't really\n    // know what to share yet.\n    CAFFE_ENFORCE_WITH_CALLER(\n        src.data_.get() || src.size_ == 0,\n        \"Source tensor has no content and has size > 0\");\n    // Finally, do sharing.\n    data_ = src.data_;\n    capacity_ = src.capacity_;\n    shares_data_ = true;\n  }\n\n  /**\n   * @brief Shares the data with an externally managed pointer.\n   *\n   * This is similar to ShareData() but the source is a pointer with an advanced\n   * deleter option. In default, no deletion takes place, and one needs to make\n   * sure that the external memory is deallocated only after the tensor finishes\n   * using it. If a Deleter object is passed in, when this tensor is reallocated\n   * or freed, the deleter function is going to be called.\n   */\n  template <typename T, typename Deleter = MemoryDeleter>\n  void ShareExternalPointer(T* src, size_t capacity = 0, Deleter d = nullptr) {\n    ShareExternalPointer(src, TypeMeta::Make<T>(), capacity, d);\n  }\n\n  template <typename Deleter = MemoryDeleter>\n  void ShareExternalPointer(\n      void* src,\n      const TypeMeta& meta,\n      size_t capacity = 0,\n      Deleter d = nullptr) {\n    meta_ = meta;\n    CAFFE_ENFORCE_WITH_CALLER(\n        meta_.id(),\n        \"To share with a raw external pointer you need to have meta \"\n        \"already set.\");\n    CAFFE_ENFORCE_WITH_CALLER(\n        size_ >= 0,\n        \"To share data with a raw pointer, you need to set shape first.\");\n    // Check if the deleter is a MemoryDeleter and is a simple nullptr.\n    if (std::is_same<MemoryDeleter, Deleter>::value &&\n        reinterpret_cast<MemoryDeleter*>(&d)[0] == nullptr) {\n      // Use aliasing constructor trick to avoid calling the destructor.\n      data_ = std::shared_ptr<void>(std::shared_ptr<void>(), src);\n    } else {\n      data_.reset(src, d);\n    }\n    // Sets capacity. If not specified, we will implicitly assume that\n    // the capacity is the current size.\n    if (capacity) {\n      capacity_ = capacity;\n    } else {\n      capacity_ = nbytes();\n    }\n    shares_data_ = true;\n  }\n\n  bool shares_data() const {\n    return shares_data_;\n  }\n\n  /**\n   * Returns a const raw void* pointer of the underlying storage. mutable_data()\n   * or raw_mutable_data() must have been called prior to this function call.\n   */\n  inline const void* raw_data() const {\n    CAFFE_ENFORCE_WITH_CALLER(data_.get() || size_ == 0);\n    return data_.get();\n  }\n\n  /**\n   * Returns a typed pointer of the underlying storage. mutable_data() or\n   * raw_mutable_data() must have been called prior to this function call, and\n   * the data type must be of the correct type. If you want to get a void*\n   * pointer instead, use raw_data().\n   */\n  template <typename T>\n  inline const T* data() const {\n    CAFFE_ENFORCE_WITH_CALLER(\n        data_.get() || size_ == 0,\n        \"The tensor is of non-zero shape, but its data is not allocated yet. \"\n        \"Caffe2 uses a lazy allocation, so you will need to call \"\n        \"mutable_data() or raw_mutable_data() to actually allocate memory.\");\n    CAFFE_ENFORCE_WITH_CALLER(\n        IsType<T>(),\n        \"Tensor type mismatch, caller expects elements to be \",\n        TypeMeta::TypeName<T>(),\n        \" while tensor contains \",\n        meta_.name());\n    return static_cast<T*>(data_.get());\n  }\n\n  /**\n   * Returns a mutable raw pointer of the underlying storage. Since we will need\n   * to know the type of the data for allocation, a TypeMeta object is passed in\n   * to specify the necessary information. This is conceptually equivalent of\n   * calling mutable_data<T>() where the TypeMeta parameter meta is derived from\n   * the type T. This function differs from mutable_data<T>() in the sense that\n   * the type T can be specified during runtime via the TypeMeta object.\n   *\n   * If the existing data does not match the desired type, it will be deleted\n   * and a new storage will be created.\n   */\n  inline void* raw_mutable_data(const TypeMeta& meta) {\n    // For 0-size tensors it's fine to return any pointer (including nullptr)\n    if (meta_ == meta && (data_.get() || size_ == 0)) {\n      return data_.get();\n    } else {\n      bool had_special_dtor = meta_.dtor() != nullptr;\n      meta_ = meta;\n      CAFFE_ENFORCE_WITH_CALLER(\n          size_ >= 0,\n          \"Tensor is not initialized. You probably need to call Resize() \"\n          \"before calling mutable_data()\");\n\n      // We can reuse the existing buffer if the current data does not have\n      // a special destructor and the new data doesn't have a special\n      // constructor.\n      if (size_ == 0 ||\n          (meta.ctor() == nullptr && !had_special_dtor &&\n           capacity_ >= size_ * meta_.itemsize())) {\n        return data_.get();\n      }\n      if (meta.ctor()) {\n        // For types that need placement new, we will call it, as well as\n        // making sure that when the data is freed, it calls the right\n        // destruction procedure.\n        auto size = size_;\n        auto dtor = meta_.dtor();\n        auto ptr_and_deleter = Context::New(size_ * meta_.itemsize());\n        auto deleter = ptr_and_deleter.second;\n        data_.reset(\n            ptr_and_deleter.first, [size, dtor, deleter](void* ptr) -> void {\n              dtor(ptr, size);\n              deleter(ptr);\n            });\n        meta_.ctor()(data_.get(), size_);\n      } else {\n        // For fundamental type, new and delete is easier.\n        auto ptr_and_deleter = Context::New(size_ * meta_.itemsize());\n        data_.reset(ptr_and_deleter.first, ptr_and_deleter.second);\n      }\n      capacity_ = size_ * meta_.itemsize();\n      return data_.get();\n    }\n  }\n\n  /**\n   * Returns a mutable raw pointer of the underlying storage. This can only be\n   * used when you know for sure that the underlying storage of the tensor is\n   * already created via an earlier raw_mutable_data(meta) call or a\n   * mutable_data<T>() call.\n   *\n   * If the existing data does not match the desired type, it will be deleted\n   * and a new storage will be created.\n   */\n  inline void* raw_mutable_data() {\n    CAFFE_ENFORCE_WITH_CALLER(\n        meta_.id() != 0,\n        \"Calling raw_mutable_data() without meta, but the current meta is \"\n        \"of unknown type.\");\n    return raw_mutable_data(meta_);\n  }\n\n  /**\n   * Returns a typed pointer of the underlying storage.\n   *\n   * For fundamental types, we reuse possible existing storage if there\n   * is sufficient capacity.\n   */\n   template <typename T>\n    inline T* mutable_data() {\n      if ((size_ == 0 || data_.get()) && IsType<T>()) {\n        return static_cast<T*>(data_.get());\n      }\n      return static_cast<T*>(raw_mutable_data(TypeMeta::Make<T>()));\n    }\n\n\n  /**\n   * Returns the number of dimensions of the data.\n   */\n  inline int ndim() const { return dims_.size(); }\n  /**\n   * Returns the size (i.e. the number of items) of the tensor.\n   */\n  inline TIndex size() const { return size_; }\n  /**\n   * Return the number of bytes each item takes in the tensor.\n   */\n  inline size_t itemsize() const { return meta_.itemsize(); }\n  /**\n   * Returns the total number of bytes of the storage.\n   *\n   * This is equivalent to calling size() * itemsize().\n   */\n  inline size_t nbytes() const { return size_ * meta_.itemsize(); }\n\n  inline size_t capacity_nbytes() const {\n    return capacity_;\n  }\n  /**\n   * Returns the dimensions of the tensor as a vector.\n   */\n  inline const vector<TIndex>& dims() const { return dims_; }\n\n  inline TIndex size_from_dim(int k) const {\n    return size_from_dim_(k, dims_);\n  }\n\n  inline TIndex size_to_dim(int k) const {\n    return size_to_dim_(k, dims_);\n  }\n\n  inline TIndex size_between_dim(int k, int l) const {\n    return size_between_dim_(k, l, dims_);\n  }\n\n  /**\n  * Returns the 'canonical' version of a (usually)  user-specified axis,\n  * allowing for negative indexing (e.g., -1 for the last axis).\n  *\n  * @param axis_index the axis index.\n  *        If 0 <= index < ndim(), return index.\n  *        If -ndim <= index <= -1, return (ndim() - (-index)),\n  *        e.g., the last axis index (ndim() - 1) if index == -1,\n  *        the second to last if index == -2, etc.\n  *        Dies on out of range index.\n  */\n  inline int canonical_axis_index(int axis_index) const {\n    return canonical_axis_index_(axis_index, ndim());\n  }\n\n  /**\n   * Checks if the tensor content is of the given data type.\n   */\n  template <typename T>\n  inline bool IsType() const { return meta_.Match<T>(); }\n  /**\n   * Returns the TypeMeta object associated with the current data type.\n   */\n  inline const TypeMeta& meta() const { return meta_; }\n\n  /**\n   * Returns the i-th dimension of the tensor in int.\n   *\n   * This function returns an int value instead of TIndex, which depending on\n   * the typedef could be int64. If you want int64 dim values, make sure you\n   * call dim() instead.\n   */\n  inline int dim32(const int i) const {\n    #ifndef NDEBUG\n    CAFFE_ENFORCE_LT_WITH_CALLER(i, dims_.size(), \"Exceeding ndim limit\");\n    CAFFE_ENFORCE_GE_WITH_CALLER(i, 0, \"Cannot have negative dimension index\");\n    #endif\n    CAFFE_ENFORCE_LT_WITH_CALLER(dims_[i], std::numeric_limits<int>::max());\n    return static_cast<int>(dims_[i]);\n  }\n\n  /**\n   * Returns the i-th dimension of the tensor. Note that the passed in index\n   * must be between 0 (inclusive) and the number of dimensions, otherwise\n   * this function will produce a fatal message.\n   */\n  inline TIndex dim(const int i) const {\n    #ifndef NDEBUG\n    CAFFE_ENFORCE_LT_WITH_CALLER(i, dims_.size(), \"Exceeding ndim limit\");\n    CAFFE_ENFORCE_GE_WITH_CALLER(i, 0, \"Cannot have negative dimension index\");\n    #endif\n    return dims_[i];\n  }\n\n protected:\n  vector<TIndex> dims_;\n  TIndex size_ = -1;\n  TypeMeta meta_;\n  std::shared_ptr<void> data_;\n  bool shares_data_ = false;\n  size_t capacity_ = 0;\n  bool reserved_ = false;\n  // In case of chunk load we store how much data was already loaded\n\n private:\n  template <\n      typename T,\n      typename = typename std::enable_if<std::is_integral<T>::value>::type>\n  bool SetDims(const vector<T>& src) {\n    auto old_size = size_;\n    dims_.resize(src.size());\n    TIndex new_size = 1;\n    for (unsigned int i = 0; i < src.size(); ++i) {\n      new_size *= src[i];\n      dims_[i] = src[i];\n    }\n    size_ = new_size;\n    return size_ != old_size;\n  }\n\n  bool SetDims() {\n    auto old_size = size_;\n    dims_.resize(0);\n    size_ = 1;\n    return size_ != old_size;\n  }\n\n  // TODO(jiayq): maybe rewrite the following functions with initializer list.\n  // NVCC does not play well with initializer lists last time, but worth\n  // another shot.\n  bool SetDims(const TIndex d0) {\n    auto old_size = size_;\n    dims_.resize(1);\n    dims_[0] = d0;\n    size_ = d0;\n    return size_ != old_size;\n  }\n\n  bool SetDims(const TIndex d0, const TIndex d1) {\n    auto old_size = size_;\n    dims_.resize(2);\n    dims_[0] = d0;\n    dims_[1] = d1;\n    size_ = d0 * d1;\n    return size_ != old_size;\n  }\n\n  bool SetDims(const TIndex d0, const TIndex d1, const TIndex d2) {\n    auto old_size = size_;\n    dims_.resize(3);\n    dims_[0] = d0;\n    dims_[1] = d1;\n    dims_[2] = d2;\n    size_ = d0 * d1 * d2;\n    return size_ != old_size;\n  }\n\n  bool\n  SetDims(const TIndex d0, const TIndex d1, const TIndex d2, const TIndex d3) {\n    auto old_size = size_;\n    dims_.resize(4);\n    dims_[0] = d0;\n    dims_[1] = d1;\n    dims_[2] = d2;\n    dims_[3] = d3;\n    size_ = d0 * d1 * d2 * d3;\n    return size_ != old_size;\n  }\n\n  // Note(jiayq): possibly a rule-of-three violation, but we explicitly\n  // discourage the use of = for Tensors.\n  Tensor& operator=(const Tensor& src) = delete;\n};\n\n// For simplicity, we will typedef Tensor<CPUContext> to TensorCPU.\ntypedef Tensor<CPUContext> TensorCPU;\n\nconstexpr int k_limit_default_ = 1000;\n\n// Type call registry\ntypedef TypeMeta (*TypeCall)(const void*);\nTypeCall GetTypeCallFunction(CaffeTypeId id);\nvoid RegisterTypeCallFunction(CaffeTypeId id, TypeCall c);\n\ntemplate <class Context>\nTypeMeta GetTensorType(const void* c) {\n  const Tensor<Context>* tc = static_cast<const Tensor<Context>*>(c);\n  return tc->meta();\n}\n\n// Shape call registry\ntypedef vector<TIndex> (*TensorInfoCall)(\n    const void*,\n    bool* shares_data,\n    size_t* capacity,\n    DeviceOption* device);\nTensorInfoCall GetTensorInfoFunction(CaffeTypeId id);\nvoid RegisterTensorInfoFunction(CaffeTypeId id, TensorInfoCall c);\n\ntemplate <class Context>\nvector<TIndex> GetTensorInfo(\n    const void* c,\n    bool* shares_data,\n    size_t* capacity,\n    DeviceOption* device) {\n  const Tensor<Context>* tc = static_cast<const Tensor<Context>*>(c);\n  *shares_data = tc->shares_data();\n  *capacity = tc->capacity_nbytes();\n  device->set_device_type(CPU);\n  device->set_cuda_gpu_id(0);\n  return tc->dims();\n}\n\nclass TensorPrinter {\n public:\n  explicit TensorPrinter(\n      const std::string& tensor_name = \"\",\n      const std::string& file_name = \"\",\n      int limit = k_limit_default_);\n  ~TensorPrinter();\n\n  template <class T>\n  void Print(const Tensor<CPUContext>& tensor);\n\n  template <class Context>\n  void PrintMeta(const Tensor<Context>& tensor);\n\n  template <class Context>\n  string MetaStr(const Tensor<Context>& tensor);\n\n private:\n  bool to_file_;\n  int limit_;\n  std::unique_ptr<std::ofstream> log_file_;\n  std::string tensor_name_;\n};\n\ntemplate <class T>\nvoid TensorPrinter::Print(const Tensor<CPUContext>& tensor) {\n  std::stringstream values_stream;\n  // One most likely doesn't want to print int64-number of items for visual\n  // inspection, so we cast down to int here.\n  int total_count = static_cast<int>(\n      std::min(tensor.size(), TIndex(limit_)));\n  const T* tensor_data = tensor.template data<T>();\n  for (int i = 0; i < total_count - 1; ++i) {\n    values_stream << tensor_data[i] << \",\";\n  }\n  // We do not add a comma after the last item.\n  values_stream << tensor_data[total_count - 1];\n  if (to_file_) {\n    (*log_file_) << MetaStr(tensor) << values_stream.str() << std::endl;\n  } else {\n    // Log to console.\n    LOG(INFO) << MetaStr(tensor) << values_stream.str();\n  }\n}\n\ntemplate <class Context>\nvoid TensorPrinter::PrintMeta(const Tensor<Context>& tensor) {\n  if (to_file_) {\n    (*log_file_) << MetaStr(tensor) << std::endl;\n  } else {\n    LOG(INFO) << MetaStr(tensor);\n  }\n}\n\ntemplate <class Context>\nstd::string TensorPrinter::MetaStr(const Tensor<Context>& tensor) {\n  std::stringstream meta_stream;\n  meta_stream << \"Tensor \" << tensor_name_ << \" of type \"\n              << tensor.meta().name() << \". Dims: (\";\n  for (const auto dim : tensor.dims()) {\n    meta_stream << dim << \",\";\n  }\n  meta_stream << \"): \";\n  return meta_stream.str();\n}\n\n}  // namespace caffe2\n#endif  // CAFFE2_CORE_TENSOR_H_\n"
  },
  {
    "path": "caffe2/core/timer.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_TIMER_H_\n#define CAFFE2_CORE_TIMER_H_\n\n#include <chrono>\n\n#include \"caffe2/core/common.h\"\n\nnamespace caffe2 {\n\n/**\n * @brief A simple timer object for measuring time.\n *\n * This is a minimal class around a std::chrono::high_resolution_clock that\n * serves as a utility class for testing code.\n */\nclass Timer {\n public:\n  typedef std::chrono::high_resolution_clock clock;\n  typedef std::chrono::nanoseconds ns;\n  Timer() { Start(); }\n  /**\n   * @brief Starts a timer.\n   */\n  inline void Start() { start_time_ = clock::now(); }\n  inline float NanoSeconds() {\n    return static_cast<float>(\n        std::chrono::duration_cast<ns>(clock::now() - start_time_).count());\n  }\n  /**\n   * @brief Returns the elapsed time in milliseconds.\n   */\n  inline float MilliSeconds() { return NanoSeconds() / 1000000.f; }\n  /**\n   * @brief Returns the elapsed time in microseconds.\n   */\n  inline float MicroSeconds() { return NanoSeconds() / 1000.f; }\n  /**\n   * @brief Returns the elapsed time in seconds.\n   */\n  inline float Seconds() { return NanoSeconds() / 1000000000.f; }\n\n protected:\n  std::chrono::time_point<clock> start_time_;\n  DISABLE_COPY_AND_ASSIGN(Timer);\n};\n}\n\n#endif  // CAFFE2_CORE_TIMER_H_\n"
  },
  {
    "path": "caffe2/core/timer_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <chrono>\n#include <iostream>\n#include <thread>\n\n#include \"caffe2/core/timer.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\nnamespace {\n\nTEST(TimerTest, Test) {\n  Timer timer;\n\n  // A timer auto-starts when it is constructed.\n  std::this_thread::sleep_for(std::chrono::microseconds(1));\n  EXPECT_GT(timer.NanoSeconds(), 0);\n\n  // Sleep for a while, and get the time.\n  timer.Start();\n  std::this_thread::sleep_for(std::chrono::milliseconds(100));\n  float ns = timer.NanoSeconds();\n  float us = timer.MicroSeconds();\n  float ms = timer.MilliSeconds();\n\n  // Time should be at least accurate +- 10%.\n  EXPECT_NEAR(ns, 100000000, 10000000);\n  EXPECT_NEAR(us, 100000, 10000);\n  EXPECT_NEAR(ms, 100, 10);\n\n  // Test restarting the clock.\n  timer.Start();\n  EXPECT_LT(timer.MicroSeconds(), 1000);\n}\n\nTEST(TimerTest, TestLatency) {\n  constexpr int iter = 1000;\n  float latency = 0;\n  Timer timer;\n  for (int i = 0; i < iter; ++i) {\n    timer.Start();\n    latency += timer.NanoSeconds();\n  }\n  std::cout << \"Average nanosecond latency is: \" << latency / iter << std::endl;\n  latency = 0;\n  for (int i = 0; i < iter; ++i) {\n    timer.Start();\n    latency += timer.MicroSeconds();\n  }\n  std::cout << \"Average microsecond latency is: \" << latency / iter << std::endl;\n  latency = 0;\n  for (int i = 0; i < iter; ++i) {\n    timer.Start();\n    latency += timer.MilliSeconds();\n  }\n  std::cout << \"Average millisecond latency is: \" << latency / iter << std::endl;\n}\n\n}  // namespace\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/transform.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/transform.h\"\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\nusing transform::Graph;\n\nCAFFE_DEFINE_REGISTRY(TransformRegistry, Transform);\n\nstd::vector<std::vector<int>> Transform::PatternMatch(const Graph& graph) {\n  // checks if the node at index i is matched already or not\n  std::vector<bool> matched(graph.size(), false);\n\n  // stores matches, which are ordered subgraphs of G\n  std::vector<std::vector<int>> matches;\n\n  // Consider every possible node as the starting point.\n  for (int idx = 0; idx < graph.size(); ++idx) {\n    // The current working subgraph. We will try to add new nodes to this,\n    // when invoking the PatternRule.\n    std::vector<int> subgraph;\n\n    // The largest \"validated\" subgraph found so far.\n    // This will be mutated by PatternMatchHelper.\n    std::vector<int> best_subgraph;\n\n    // Only begin to match if the start node is accepted.\n    if (!matched.at(idx) && PatternRule(graph, subgraph, idx)) {\n      subgraph.push_back(idx);\n      PatternMatchHelper(graph, matched, &subgraph, &best_subgraph);\n      subgraph.pop_back();\n    }\n    if (best_subgraph.size() > 0) { // match found\n      matches.push_back(best_subgraph);\n      for (const auto& x : best_subgraph) {\n        matched[x] = true;\n      }\n    }\n  }\n  return matches;\n}\n\nvoid Transform::TryNeighbors(\n    const Graph& graph,\n    const std::map<int, std::vector<string>>& neighbors,\n    const std::vector<bool>& matched,\n    std::vector<int>* subgraph_ptr,\n    std::vector<int>* best_subgraph_ptr) {\n  auto& subgraph = *subgraph_ptr;\n  for (const auto& edge : neighbors) {\n    int j = edge.first;\n    if (std::find(subgraph.begin(), subgraph.end(), j) == subgraph.end()) {\n      if (!matched.at(j) && PatternRule(graph, subgraph, j)) {\n        subgraph.push_back(j);\n        PatternMatchHelper(graph, matched, subgraph_ptr, best_subgraph_ptr);\n        subgraph.pop_back();\n      }\n    }\n  }\n}\n\nvoid Transform::PatternMatchHelper(\n    const Graph& graph,\n    const std::vector<bool>& matched,\n    std::vector<int>* subgraph_ptr,\n    std::vector<int>* best_subgraph_ptr) {\n  CHECK(subgraph_ptr);\n  auto& subgraph = *subgraph_ptr;\n  CHECK(best_subgraph_ptr);\n  auto& best_subgraph = *best_subgraph_ptr;\n\n  // If the current subgraph is valid, and the largest we've seen so far,\n  // make it the best_subgraph.\n  if (ValidatorRule(graph, subgraph) &&\n      subgraph.size() > best_subgraph.size()) {\n    best_subgraph = subgraph;\n  }\n\n  int size_before = subgraph.size();\n\n  if (pattern_match_type_ == CONNECTED_SUBGRAPH) {\n    // Connected Component Order Pattern Matching\n    // We want to match subgraphs which are connected ConnectedComponents\n\n    // Try adding each parent and child of every node in the subgraph,\n    // and see if we can accept it.\n    for (int i = 0; i < subgraph.size(); i++) {\n      int x = subgraph[i];\n      TryNeighbors(\n          graph,\n          graph.node(x).children,\n          matched,\n          subgraph_ptr,\n          best_subgraph_ptr);\n      CAFFE_ENFORCE(\n          size_before == subgraph.size(),\n          \"Subgraph size should not change after returning from recursive call.\");\n      TryNeighbors(\n          graph,\n          graph.node(x).parents,\n          matched,\n          subgraph_ptr,\n          best_subgraph_ptr);\n      CAFFE_ENFORCE(\n          size_before == subgraph.size(),\n          \"Subgraph size should not change after returning from recursive call.\");\n    }\n  } else if (pattern_match_type_ == SORTED_WRT_EXECUTION_ORDER) {\n    // Sorted Execution Order Pattern matching\n    // We want to be able to match subgraphs in sorted execution order\n\n    // We can safely assume our subgraph is already sorted.\n    // This means, we only need to consider nodes that come after the LAST\n    // node in our current subgraph.\n    // Thus, we simply iterate over the nodes that come AFTER the last node of\n    // our current subgraph.\n    int start_idx = 0;\n    if (subgraph.size() > 0) {\n      start_idx = subgraph.back() + 1;\n    }\n    for (int i = start_idx; i < graph.size(); i++) {\n      if (!matched.at(i) && PatternRule(graph, subgraph, i)) {\n        subgraph.push_back(i);\n        PatternMatchHelper(graph, matched, subgraph_ptr, best_subgraph_ptr);\n        subgraph.pop_back();\n      }\n    }\n  } else if (pattern_match_type_ == GENERAL) {\n    // General Pattern matching\n    // We want to be able to match any ordered subgraph\n\n    // For every current subgraph, we consider all nodes to be\n    // the next candidate node, as long as it isn't already matched.\n    for (int i = 0; i < graph.size(); i++) {\n      if (std::find(subgraph.begin(), subgraph.end(), i) == subgraph.end()) {\n        // Then we try appending it to the subgraph.\n        if (!matched.at(i) && PatternRule(graph, subgraph, i)) {\n          subgraph.push_back(i);\n          PatternMatchHelper(graph, matched, subgraph_ptr, best_subgraph_ptr);\n          subgraph.pop_back();\n        }\n      }\n    }\n  } else {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n}\n\nvoid Transform::ReplacePattern(\n    const std::vector<vector<int>>& matches,\n    Graph* graph) {\n  for (const auto& match : matches) {\n    // Make sure each matched node is still active (not overwritten)\n    bool is_match_active = true;\n    for (int idx : match) {\n      if (!graph->is_node_active(idx)) {\n        is_match_active = false;\n      }\n    }\n\n    // Simply try to apply the replace rule upon every match.\n    if (is_match_active && !ReplaceRule(match, graph)) {\n      CAFFE_THROW(\"Replace failed!\");\n    }\n  }\n}\n\n// The simple interface - performs the transformation upon a NetDef, and returns\n// the result.\nNetDef Transform::ApplyTo(const NetDef& orig_net) {\n  Graph g(orig_net);\n  const auto matches = PatternMatch(g);\n  ReplacePattern(matches, &g);\n  return g.GetNetDef();\n}\n\n// Create a Transform object\nunique_ptr<Transform> CreateTransform(string key) {\n  auto t = TransformRegistry()->Create(key);\n  CAFFE_ENFORCE(t != nullptr, \"Transform not found in registry: \", key);\n  return t;\n}\n\n// Create a Transform object from registry,\n// and immediately apply it to a Netdef.\nNetDef ApplyTransform(const string& key, const NetDef& netdef) {\n  auto t = CreateTransform(key);\n  return t->ApplyTo(netdef);\n}\n\ndouble average_net_run_duration(\n    const NetDef& netdef,\n    const NetDef& init_netdef,\n    const int warmup_runs,\n    const int main_runs) {\n  Workspace ws;\n  if (init_netdef.op_size() > 0) {\n    std::unique_ptr<NetBase> init_net(CreateNet(init_netdef, &ws));\n    CHECK(init_net);\n    CAFFE_ENFORCE(init_net->Run(), \"Init run has failed!\");\n  } else {\n    // If a proper init_net is not provided, then this is the best we can do.\n    for (auto inp : netdef.external_input()) {\n      ws.CreateBlob(inp);\n    }\n  }\n  std::unique_ptr<NetBase> net(CreateNet(netdef, &ws));\n  CHECK(net);\n  CAFFE_ENFORCE(\n      warmup_runs >= 0,\n      \"Number of warm up runs should be non negative, provided \",\n      warmup_runs,\n      \".\");\n\n  for (int i = 0; i < warmup_runs; i++) {\n    CAFFE_ENFORCE(net->Run(), \"Warmup run \", i, \" has failed.\");\n  }\n\n  CAFFE_ENFORCE(\n      main_runs > 0,\n      \"Number of main runs should be positive, provided \",\n      main_runs,\n      \".\");\n  Timer timer;\n  for (int i = 0; i < main_runs; i++) {\n    CAFFE_ENFORCE(net->Run(), \"Main run \", i, \" has failed.\");\n  }\n  return timer.MilliSeconds();\n}\n\n// Create a Transform object from registry, apply it to a NetDef.\n// Will only return the transformed net if it is faster than the old net.\n// This will run the init net first, will run the two nets warmup_runs times.\n// Then, we will take the average time of main_runs runs, and only keep the\n// transformed net if it is faster by a factor of improvement_threshold.\nNetDef ApplyTransformIfFaster(\n    const string& key,\n    const NetDef& netdef,\n    const NetDef& init_netdef,\n    const int warmup_runs,\n    const int main_runs,\n    const double improvement_threshold) {\n  NetDef transformed_netdef = ApplyTransform(key, netdef);\n  double original_net_time =\n      average_net_run_duration(netdef, init_netdef, warmup_runs, main_runs);\n  double new_net_time = average_net_run_duration(\n      transformed_netdef, init_netdef, warmup_runs, main_runs);\n  if (original_net_time > improvement_threshold * new_net_time) {\n    return transformed_netdef;\n  }\n  return netdef;\n}\n\n} // namespace Caffe2\n"
  },
  {
    "path": "caffe2/core/transform.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/graph.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\n/**\n * The Transform Base Object\n *\n * A Transform is an operation which manipulates a Caffe2 NetDef.\n * You can consider it as a function: Transform.ApplyTo(NetDef) -> NetDef\n *\n * A Transform Operation does 4 things:\n *    1) Creates a Graph object from a NetDef, which stores connections.\n *    2) Pattern Matches on the Graph, to find subgraphs it wants to change.\n *    3) Replaces the subgraphs that it's matched with new operators.\n *    4) Creates a NetDef from the changed Graph, and returns it.\n *\n * The effect of a Transform is defined by its 3 protected virtual functions.\n *    1) PatternRule determines for an ordered subgraph and a node, whether to\n *        consider adding the node to the subgraph.\n *    2) ValidatorRule determines, for an ordered subgraph, whether it is a\n *        match.\n *    3) ReplaceRule mutates the graph, based on a matched subgraph.\n *\n * This is the base class for all derived classes to base off. To create your\n * own transform, write your implementations for PatternRule, ValidatorRule, and\n * ReplaceRule.\n */\nclass Transform {\n public:\n  Transform() {}\n\n  /**\n   * Apply a Transform onto a NetDef.\n   * Returns the transformed NetDef.\n   */\n  NetDef ApplyTo(const NetDef& orig_net_def);\n\n  virtual ~Transform() {}\n\n  /**\n   * Determines the type of subgraphs that PatternMatch will find.\n   *\n   * CONNECTED_SUBGRAPH will only match subgraphs that are connected.\n   * These subgraphs satisfy that every node of the match is connected to the\n   * subgraph of the nodes that come before it.\n   * For example, in the graph (1) --> (2) --> (3) --> (4),\n   *    This is capable of matching the subgraph [2, 3] and [4, 3]\n   *    This is not capable of matching the subgraph [2, 4].\n   *\n   *\n   * SORTED_WRT_EXECUTION_ORDER will match subgraphs that guarantee\n   * sorted execution order.\n   * The nodes don't have to be connected. It is faster than General.\n   * For example, in the graph (1) --> (2) --> (3) --> (4),\n   *    This is capable of matching the subgraph [2, 4], [3, 4].\n   *    This is not capable of matching the subgraph [3, 1], [4, 3].\n   *\n   *\n   * GENERAL can match any subgraph.\n   * For example, in the graph (1) --> (2) --> (3) --> (4),\n   *    This is capable of matching subgraphs [2, 4], [3, 4], [4, 2, 1].\n   *    There is no ordered subgraph of G that cannot be matched by this.\n   */\n  enum PatternMatchType {\n    CONNECTED_SUBGRAPH,\n    SORTED_WRT_EXECUTION_ORDER,\n    GENERAL\n  };\n\n  /**\n   * Generates all matches (stored as ordered subgraphs) and returns them.\n   *\n   * A match is stored as vector<int>, which is a mapping to OperatorDefs\n   * in Graph. The order matters.\n   */\n  std::vector<std::vector<int>> PatternMatch(const transform::Graph& graph);\n\n  /**\n   * Applies the replace rule onto each of the matches found.\n   */\n  void ReplacePattern(\n      const std::vector<std::vector<int>>& matches,\n      transform::Graph* graph);\n\n protected:\n  /**\n   * The PatternRule essentially answers:\n   * Given the current subgraph (ordered), should we append the new node at idx?\n   */\n  virtual bool PatternRule(\n      const transform::Graph& g,\n      const std::vector<int>& subgraph,\n      int /*idx*/) {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n  /**\n   * The ValidatorRule essentially answers:\n   * Given a subgraph, can we accept it?\n   */\n  virtual bool ValidatorRule(\n      const transform::Graph& g,\n      const std::vector<int>& subgraph) {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n  /**\n   * The ReplaceRule actually mutates the graph, and applies the transformation\n   * upon the subgraph.\n   */\n  virtual bool ReplaceRule(\n      const std::vector<int>& subgraph,\n      transform::Graph* g_ptr) {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n  void SetPatternMatchType(PatternMatchType type) {\n    pattern_match_type_ = type;\n  }\n\n private:\n  /**\n   * A helper function for PatternMatch, which keeps track of the best subgraph\n   * so far.\n   */\n  void PatternMatchHelper(\n      const transform::Graph& graph,\n      const std::vector<bool>& matched,\n      std::vector<int>* subgraph_ptr,\n      std::vector<int>* best_subgraph_ptr);\n  /**\n   * Attempts to append each neighbor to the end of the subgraph.\n   */\n  void TryNeighbors(\n      const transform::Graph& graph,\n      const std::map<int, std::vector<string>>& neighbors,\n      const std::vector<bool>& matched,\n      std::vector<int>* subgraph_ptr,\n      std::vector<int>* best_subgraph_ptr);\n\n  PatternMatchType pattern_match_type_ = CONNECTED_SUBGRAPH;\n};\n\n// Creates a Transform based on a key, which should be defined in registry.\nunique_ptr<Transform> CreateTransform(string key);\n\nCAFFE_DECLARE_REGISTRY(TransformRegistry, Transform);\n#define REGISTER_TRANSFORM(name, ...) \\\n  CAFFE_REGISTER_CLASS(TransformRegistry, name, __VA_ARGS__)\n\n// Create a Transform object from registry,\n// and immediately apply it to a Netdef.\nNetDef ApplyTransform(const string& key, const NetDef& netdef);\n\n// Create a Transform object from registry, apply it to a NetDef.\n// Will only return the transformed net if it is faster than the old net.\n// This will run the init net first, will run the two nets warmup_runs times.\n// Then, we will take the average time of main_runs runs, and only keep the\n// transformed net if it is faster by a factor of improvement_threshold.\nNetDef ApplyTransformIfFaster(\n    const string& key,\n    const NetDef& netdef,\n    const NetDef& init_netdef,\n    const int warmup_runs,\n    const int main_runs,\n    const double improvement_threshold);\n\n} // namespace\n"
  },
  {
    "path": "caffe2/core/transform_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <google/protobuf/text_format.h>\n#include <gtest/gtest.h>\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/transform.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\nusing transform::Graph;\n\nstatic std::atomic<int> counter;\n\nclass TransformDummyOp final : public OperatorBase {\n public:\n  using OperatorBase::OperatorBase;\n  bool Run(int /* unused */) override {\n    counter.fetch_add(1);\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(TransformDummyOp1, TransformDummyOp);\n\nOPERATOR_SCHEMA(TransformDummyOp1)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\n\nREGISTER_CPU_OPERATOR(TransformDummyOp2, TransformDummyOp);\n\nOPERATOR_SCHEMA(TransformDummyOp2)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\n\nREGISTER_CPU_OPERATOR(TransformDummyOp3, TransformDummyOp);\n\nOPERATOR_SCHEMA(TransformDummyOp3)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\n\n/**\n * This TransformDummy transform will find all subgraphs of shape\n * (TransformDummyOp1 -> TransformDummyOp2) and replaces them with\n * (TransformDummyOp3). Simple unit test.\n */\nclass DummyTransform : public Transform {\n public:\n  // Finds all patterns of the form (TransformDummyOp1 -> TransformDummyOp2)\n  bool PatternRule(const Graph& g, const std::vector<int>& subgraph, int idx)\n      override {\n    if (subgraph.size() >= pattern_chain.size()) {\n      return false;\n    }\n    // which index are we trying to append the new node to?\n    int pattern_idx = subgraph.size();\n    // type doesn't match\n    if (g.node(idx).op.type() != pattern_chain[pattern_idx]) {\n      return false;\n    }\n    // not that head, and doesn't have exactly 1 parent\n    if (pattern_idx > 0 && g.node(idx).parents.size() != 1) {\n      return false;\n    }\n    // not that tail, and doesn't have exactly 1 child\n    if (pattern_idx < pattern_chain.size() - 1 &&\n        g.node(idx).children.size() != 1) {\n      return false;\n    }\n\n    return true;\n  }\n\n  // Checks if the subgraph matched is (TransformDummyOp1 -> TransformDummyOp2)\n  bool ValidatorRule(const Graph& g, const std::vector<int>& subgraph)\n      override {\n    if (subgraph.size() == 2) {\n      if (g.node(subgraph[0]).op.type() == \"TransformDummyOp1\" &&\n          g.node(subgraph[1]).op.type() == \"TransformDummyOp2\") {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  // Replaces a match of (TransformDummyOp1 -> TransformDummyOp2) with\n  // (TransformDummyOp3)\n  bool ReplaceRule(const std::vector<int>& match, Graph* g_ptr) override {\n    CHECK(g_ptr);\n    auto& g = *g_ptr;\n    OperatorDef new_op;\n    new_op.set_type(\"TransformDummyOp3\");\n    int new_idx = g.size();\n\n    std::map<int, std::vector<string>> new_op_children;\n    std::map<int, std::vector<string>> new_op_parents;\n\n    // for each node parent in the head of the match, connect it to our new node\n    for (const auto& edge : g.node(match[0]).parents) {\n      int parent = edge.first;\n      for (const auto& blob : edge.second) {\n        g.node(parent).children[new_idx].push_back(blob);\n        new_op_parents[parent].push_back(blob);\n      }\n    }\n    for (const string& blob : g.node(match[0]).op.input()) {\n      new_op.add_input(blob);\n    }\n\n    // for each child in the tail of the match, connect it to our new node\n    for (const auto& edge : g.node(match[1]).children) {\n      int child = edge.first;\n      for (const auto& blob : edge.second) {\n        g.node(child).parents[new_idx].push_back(blob);\n        new_op_children[child].push_back(blob);\n      }\n    }\n    for (const string& blob : g.node(match[1]).op.output()) {\n      new_op.add_output(blob);\n    }\n\n    g.DeactivateSubgraph(match);\n\n    g.push_node(transform::Node(new_op, true, new_op_parents, new_op_children));\n    return true;\n  }\n\n private:\n  const std::vector<string> pattern_chain = {\"TransformDummyOp1\",\n                                             \"TransformDummyOp2\"};\n};\n\nREGISTER_TRANSFORM(TransformDummySwap, DummyTransform)\n\nTEST(TransformTest, TestPatternMatch) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef netdef;\n\n  AddOp(&netdef, \"TransformDummyOp1\", {\"in\"}, {\"mid1\"});\n  AddOp(&netdef, \"TransformDummyOp2\", {\"mid1\"}, {\"mid2\"});\n  AddOp(&netdef, \"TransformDummyOp1\", {\"mid2\"}, {\"mid3\"});\n  AddOp(&netdef, \"TransformDummyOp2\", {\"mid3\"}, {\"out\"});\n\n  auto t = CreateTransform(\"TransformDummySwap\");\n  Graph g(netdef);\n  auto matches = t->PatternMatch(g);\n\n  EXPECT_EQ(matches.size(), 2);\n  EXPECT_EQ(matches[0][0], 0);\n  EXPECT_EQ(matches[0][1], 1);\n  EXPECT_EQ(matches[1][0], 2);\n  EXPECT_EQ(matches[1][1], 3);\n}\n\nTEST(TransformTest, TestReplacePattern) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef netdef;\n\n  AddOp(&netdef, \"TransformDummyOp1\", {\"in\"}, {\"mid1\"});\n  AddOp(&netdef, \"TransformDummyOp2\", {\"mid1\"}, {\"mid2\"});\n  AddOp(&netdef, \"TransformDummyOp1\", {\"mid2\"}, {\"mid3\"});\n  AddOp(&netdef, \"TransformDummyOp2\", {\"mid3\"}, {\"out\"});\n\n  auto t = CreateTransform(\"TransformDummySwap\");\n  Graph g(netdef);\n  std::vector<std::vector<int>> matches = {{0, 1}, {2, 3}};\n  t->ReplacePattern(matches, &g);\n\n  EXPECT_EQ(g.size(), 6);\n  EXPECT_FALSE(g.is_node_active(0));\n  EXPECT_FALSE(g.is_node_active(1));\n  EXPECT_FALSE(g.is_node_active(2));\n  EXPECT_FALSE(g.is_node_active(3));\n  EXPECT_TRUE(g.is_node_active(4));\n  EXPECT_TRUE(g.is_node_active(5));\n\n  EXPECT_EQ(g.node(4).children.size(), 1);\n  EXPECT_EQ(g.node(4).parents.size(), 0);\n  EXPECT_TRUE(g.node(4).children.count(5));\n\n  NetDef replaced_netdef = g.GetNetDef();\n\n  EXPECT_EQ(replaced_netdef.op().size(), 2);\n  EXPECT_EQ(replaced_netdef.op(0).type(), \"TransformDummyOp3\");\n  EXPECT_EQ(replaced_netdef.op(0).input(0), \"in\");\n  EXPECT_EQ(replaced_netdef.op(1).type(), \"TransformDummyOp3\");\n  EXPECT_EQ(replaced_netdef.op(1).output(0), \"out\");\n}\n\nTEST(TransformTest, TestTransformApply) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef netdef;\n  AddOp(&netdef, \"TransformDummyOp1\", {\"in\"}, {\"mid1\"});\n  AddOp(&netdef, \"TransformDummyOp2\", {\"mid1\"}, {\"mid2\"});\n  AddOp(&netdef, \"TransformDummyOp1\", {\"mid2\"}, {\"mid3\"});\n  AddOp(&netdef, \"TransformDummyOp2\", {\"mid3\"}, {\"out\"});\n\n  NetDef replaced_netdef = ApplyTransform(\"TransformDummySwap\", netdef);\n\n  EXPECT_EQ(replaced_netdef.op().size(), 2);\n  EXPECT_EQ(replaced_netdef.op(0).type(), \"TransformDummyOp3\");\n  EXPECT_EQ(replaced_netdef.op(0).input(0), \"in\");\n  EXPECT_EQ(replaced_netdef.op(1).type(), \"TransformDummyOp3\");\n  EXPECT_EQ(replaced_netdef.op(1).output(0), \"out\");\n}\n\n/**\n * Transform with Sorted Order matching.\n * Matches two operators of type TransformDummyOp1, even if disconnected.\n * These operators will be given in execution order,\n * but doesn't need connectivity.\n * Changes them to TransformDummyOp2.\n */\nclass SortedDummyTransform : public Transform {\n public:\n  SortedDummyTransform() {\n    SetPatternMatchType(SORTED_WRT_EXECUTION_ORDER);\n  }\n  bool PatternRule(const Graph& g, const std::vector<int>& subgraph, int idx)\n      override {\n    if (g.node(idx).op.type() != \"TransformDummyOp1\") {\n      return false;\n    }\n    return true;\n  }\n  bool ValidatorRule(const Graph& g, const std::vector<int>& subgraph)\n      override {\n    if (subgraph.size() == 2) {\n      if (g.node(subgraph[0]).op.type() == \"TransformDummyOp1\" &&\n          g.node(subgraph[1]).op.type() == \"TransformDummyOp1\") {\n        return true;\n      }\n    }\n    return false;\n  }\n  bool ReplaceRule(const std::vector<int>& match, Graph* g_ptr) override {\n    CHECK(g_ptr);\n    for (const auto& x : match) {\n      g_ptr->node(x).op.set_type(\"TransformDummyOp2\");\n    }\n    return true;\n  }\n};\n\nREGISTER_TRANSFORM(SortedTransformDummySwap, SortedDummyTransform)\n\nTEST(TransformTest, TestPatternMatchTypeSortedOrder) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef netdef;\n\n  AddOp(&netdef, \"TransformDummyOp1\", {\"in\"}, {\"mid1\"});\n  AddOp(&netdef, \"TransformDummyOp3\", {\"mid1\"}, {\"mid2\"});\n  AddOp(&netdef, \"TransformDummyOp1\", {\"mid2\"}, {\"mid3\"});\n  AddOp(&netdef, \"TransformDummyOp3\", {\"mid3\"}, {\"out\"});\n\n  auto t = CreateTransform(\"SortedTransformDummySwap\");\n  NetDef replaced_netdef = t->ApplyTo(netdef);\n\n  EXPECT_EQ(replaced_netdef.op().size(), 4);\n  EXPECT_EQ(replaced_netdef.op(0).type(), \"TransformDummyOp2\");\n  EXPECT_EQ(replaced_netdef.op(2).type(), \"TransformDummyOp2\");\n}\n\n/**\n * General subgraph transform.\n * Matches a TransformDummyOp1, and a TransformDummyOp2.\n * Order doesn't matter. Connectedness doesn't matter.\n * Turns them into TransformDummyOp3.\n */\nclass GeneralDummyTransform : public Transform {\n public:\n  GeneralDummyTransform() {\n    SetPatternMatchType(GENERAL);\n  }\n  bool PatternRule(const Graph& g, const std::vector<int>& subgraph, int idx)\n      override {\n    if (subgraph.size() == 0 && g.node(idx).op.type() == \"TransformDummyOp1\") {\n      return true;\n    }\n    if (subgraph.size() == 1 && g.node(idx).op.type() == \"TransformDummyOp2\") {\n      return true;\n    }\n    return false;\n  }\n  bool ValidatorRule(const Graph& g, const std::vector<int>& subgraph)\n      override {\n    if (subgraph.size() == 2) {\n      if (g.node(subgraph[0]).op.type() == \"TransformDummyOp1\" &&\n          g.node(subgraph[1]).op.type() == \"TransformDummyOp2\") {\n        return true;\n      }\n    }\n    return false;\n  }\n  bool ReplaceRule(const std::vector<int>& match, Graph* g_ptr) override {\n    CHECK(g_ptr);\n    for (const auto& x : match) {\n      g_ptr->node(x).op.set_type(\"TransformDummyOp3\");\n    }\n    return true;\n  }\n};\n\nREGISTER_TRANSFORM(GeneralTransformDummySwap, GeneralDummyTransform)\n\nTEST(TransformTest, TestPatternMatchTypeGeneral) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef netdef;\n\n  AddOp(&netdef, \"TransformDummyOp2\", {\"in\"}, {\"mid1\"});\n  AddOp(&netdef, \"TransformDummyOp3\", {\"mid1\"}, {\"mid2\"});\n  AddOp(&netdef, \"TransformDummyOp1\", {\"mid2\"}, {\"mid3\"});\n  AddOp(&netdef, \"TransformDummyOp3\", {\"mid3\"}, {\"out\"});\n\n  auto t = CreateTransform(\"GeneralTransformDummySwap\");\n  NetDef replaced_netdef = t->ApplyTo(netdef);\n\n  EXPECT_EQ(replaced_netdef.op().size(), 4);\n  EXPECT_EQ(replaced_netdef.op(0).type(), \"TransformDummyOp3\");\n  EXPECT_EQ(replaced_netdef.op(2).type(), \"TransformDummyOp3\");\n}\n\nclass TransformSleepFastOp final : public OperatorBase {\n public:\n  using OperatorBase::OperatorBase;\n  bool Run(int /* unused */) override {\n    std::this_thread::sleep_for(std::chrono::milliseconds(30));\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(TransformSleepFastOp, TransformSleepFastOp);\n\nOPERATOR_SCHEMA(TransformSleepFastOp)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\n\nclass TransformSleepSlowOp final : public OperatorBase {\n public:\n  using OperatorBase::OperatorBase;\n  bool Run(int /* unused */) override {\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(TransformSleepSlowOp, TransformSleepSlowOp);\n\nOPERATOR_SCHEMA(TransformSleepSlowOp)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\n\n/**\n * This TransformDummy transform will find all operators of type old_type,\n * and replace them with type new_type.\n */\nclass TypeSwapTransform : public Transform {\n public:\n  // Determine the actual strings through inheriting from derived type.\n  explicit TypeSwapTransform(string old_type, string new_type)\n      : old_type(old_type), new_type(new_type) {}\n\n  // Really simple, only accept if it's a FastSleepOp, and no match so far.\n  bool PatternRule(const Graph& g, const std::vector<int>& subgraph, int idx)\n      override {\n    if (subgraph.size() == 0 && g.node(idx).op.type() == old_type) {\n      return true;\n    }\n    return false;\n  }\n  // Checks if the subgraph matched is a FastSleepOp\n  bool ValidatorRule(const Graph& g, const std::vector<int>& subgraph)\n      override {\n    if (subgraph.size() == 1) {\n      if (g.node(subgraph[0]).op.type() == old_type) {\n        return true;\n      }\n    }\n    return false;\n  }\n  // Replaces op of original type to new type.\n  bool ReplaceRule(const std::vector<int>& match, Graph* g_ptr) override {\n    CHECK(g_ptr);\n    auto& g = *g_ptr;\n    g.node(match[0]).op.set_type(new_type);\n    return true;\n  }\n\n private:\n  string old_type;\n  string new_type;\n};\n\nclass FastToSlowTransform : public TypeSwapTransform {\n public:\n  explicit FastToSlowTransform()\n      : TypeSwapTransform(\"TransformSleepFastOp\", \"TransformSleepSlowOp\") {}\n};\n\nREGISTER_TRANSFORM(FastToSlow, FastToSlowTransform);\n\nclass SlowToFastTransform : public TypeSwapTransform {\n public:\n  explicit SlowToFastTransform()\n      : TypeSwapTransform(\"TransformSleepSlowOp\", \"TransformSleepFastOp\") {}\n};\n\nREGISTER_TRANSFORM(SlowToFast, SlowToFastTransform);\n\nTEST(TransformTest, TestApplyTransformIfFasterIsFaster) {\n  NetDef init_netdef;\n  auto* op = AddOp(&init_netdef, \"ConstantFill\", {}, {\"in\"});\n\n  NetDef netdef;\n  AddOp(&netdef, \"TransformDummyOp1\", {\"in\"}, {\"mid\"});\n  AddOp(&netdef, \"TransformSleepSlowOp\", {\"mid\"}, {\"out\"});\n  netdef.add_external_input(\"in\"); // This is important for this function.\n\n  // Make sure the transform would work normally.\n  auto transformed_net = ApplyTransform(\"SlowToFast\", netdef);\n  EXPECT_EQ(transformed_net.op(1).type(), \"TransformSleepFastOp\");\n\n  // Should be still transform normally.\n  auto mystery_net =\n      ApplyTransformIfFaster(\"SlowToFast\", netdef, init_netdef, 5, 10, 1.01);\n  EXPECT_EQ(mystery_net.op(1).type(), \"TransformSleepFastOp\");\n}\n\nTEST(TransformTest, TestApplyTransformIfFasterButSlower) {\n  NetDef init_netdef;\n  auto* op = AddOp(&init_netdef, \"ConstantFill\", {}, {\"in\"});\n\n  NetDef netdef;\n  AddOp(&netdef, \"TransformDummyOp1\", {\"in\"}, {\"mid\"});\n  AddOp(&netdef, \"TransformSleepFastOp\", {\"mid\"}, {\"out\"});\n  netdef.add_external_input(\"in\"); // This is important for this function.\n\n  // Make sure the transform would work normally.\n  auto transformed_net = ApplyTransform(\"FastToSlow\", netdef);\n  EXPECT_EQ(transformed_net.op(1).type(), \"TransformSleepSlowOp\");\n\n  // Should not actually change!\n  auto mystery_net =\n      ApplyTransformIfFaster(\"FastToSlow\", netdef, init_netdef, 5, 10, 1.01);\n  EXPECT_EQ(mystery_net.op(1).type(), \"TransformSleepFastOp\");\n}\n\n} // namespace\n\n} // namespace Caffe2\n"
  },
  {
    "path": "caffe2/core/typeid.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/typeid.h\"\n#include \"caffe2/core/scope_guard.h\"\n\n#if !defined(_MSC_VER)\n#include <cxxabi.h>\n#endif\n\nnamespace caffe2 {\nstd::map<CaffeTypeId, string>& gTypeNames() {\n  static std::map<CaffeTypeId, string> g_type_names;\n  return g_type_names;\n}\n\nstd::set<string>& gRegisteredTypeNames() {\n  static std::set<string> g_registered_type_names;\n  return g_registered_type_names;\n}\n\nstd::mutex& gCaffe2TypeRegistrationMutex() {\n  static std::mutex g_caffe2_type_registration_mutex;\n  return g_caffe2_type_registration_mutex;\n}\n\n#if defined(_MSC_VER)\n// Windows does not have cxxabi.h, so we will simply return the original.\nstring Demangle(const char* name) {\n  return string(name);\n}\n#else\nstring Demangle(const char* name) {\n  int status = 0;\n  auto demangled = ::abi::__cxa_demangle(name, nullptr, nullptr, &status);\n  if (demangled) {\n    auto guard = MakeGuard([demangled]() { free(demangled); });\n    return string(demangled);\n  }\n  return name;\n}\n#endif\n\nstring GetExceptionString(const std::exception& e) {\n#ifdef __GXX_RTTI\n  return Demangle(typeid(e).name()) + \": \" + e.what();\n#else\n  return string(\"Exception (no RTTI available): \") + e.what();\n#endif // __GXX_RTTI\n}\n\nnamespace {\n// This single registerer exists solely for us to be able to name a TypeMeta\n// for unintializied blob. You should not use this struct yourself - it is\n// intended to be only instantiated once here.\nstruct UninitializedTypeNameRegisterer {\n  UninitializedTypeNameRegisterer() {\n    gTypeNames()[0] = \"nullptr (uninitialized)\";\n  }\n};\nstatic UninitializedTypeNameRegisterer g_uninitialized_type_name_registerer;\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/typeid.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_TYPEID_H_\n#define CAFFE2_CORE_TYPEID_H_\n\n#include <cassert>\n#include <cstdlib>\n#include <iostream>\n#include <map>\n#include <mutex>\n#include <type_traits>\n#ifdef __GXX_RTTI\n#include <set>\n#include <typeinfo>\n#endif\n\n#include <exception>\n\n#include \"caffe2/core/common.h\"\n\nnamespace caffe2 {\n\ntypedef intptr_t CaffeTypeId;\nstd::map<CaffeTypeId, string>& gTypeNames();\nstd::set<string>& gRegisteredTypeNames();\n\n// A utility function to demangle a function name.\nstring Demangle(const char* name);\n\n/**\n * Returns the printable name of the type.\n *\n * Works for all types, not only the ones registered with CAFFE_KNOWN_TYPE\n */\ntemplate <typename T>\nstatic const char* DemangleType() {\n#ifdef __GXX_RTTI\n  static const string name = Demangle(typeid(T).name());\n  return name.c_str();\n#else // __GXX_RTTI\n  return \"(RTTI disabled, cannot show name)\";\n#endif // __GXX_RTTI\n}\n\n// A utility function to return an exception string by prepending its exception\n// type before its what() content.\nstring GetExceptionString(const std::exception& e);\n\nstd::mutex& gCaffe2TypeRegistrationMutex();\n\ntemplate <typename T>\nstruct TypeNameRegisterer {\n  TypeNameRegisterer(CaffeTypeId id, const string& literal_name) {\n    std::lock_guard<std::mutex> guard(gCaffe2TypeRegistrationMutex());\n#ifdef __GXX_RTTI\n    (void)literal_name;\n\n    string name = Demangle(typeid(T).name());\n    // If we are in RTTI mode, we will also use this opportunity to do sanity\n    // check if there are duplicated ids registered for the same type. This\n    // usually happens when one does not do RTLD_GLOBAL, which is often the\n    // case in Python. The way we do the check is to make sure that there are\n    // no duplicated names registered - this could be done by checking the\n    // uniqueness of names.\n    if (gRegisteredTypeNames().count(name)) {\n      std::cerr << \"Type name \" << name\n                << \" registered twice. This should \"\n                   \"not happen. Do you have duplicated CAFFE_KNOWN_TYPE?\"\n                << std::endl;\n      throw std::runtime_error(\"TypeNameRegisterer error with type \" + name);\n    }\n    gRegisteredTypeNames().insert(name);\n    gTypeNames()[id] = name;\n#else // __GXX_RTTI\n    if (literal_name.empty()) {\n      gTypeNames()[id] = \"(RTTI disabled, cannot show name)\";\n    } else {\n      gTypeNames()[id] = literal_name;\n    }\n#endif // __GXX_RTTI\n  }\n};\n\n/**\n * TypeMeta is a thin class that allows us to store the type of a container such\n * as a blob, or the data type of a tensor, with a unique run-time id. It also\n * stores some additional data such as the item size and the name of the type\n * for run-time inspection.\n */\nclass TypeMeta {\n public:\n  typedef void (*PlacementNew)(void*, size_t);\n  typedef void (*TypedCopy)(const void*, void*, size_t);\n  typedef void (*TypedDestructor)(void*, size_t);\n  /** Create a dummy TypeMeta object. To create a TypeMeta object for a specific\n   * type, use TypeMeta::Make<T>().\n   */\n  TypeMeta()\n      : id_(0), itemsize_(0), ctor_(nullptr), copy_(nullptr), dtor_(nullptr) {}\n\n  /**\n   * Copy constructor.\n   */\n  TypeMeta(const TypeMeta& src)\n      : id_(src.id_),\n        itemsize_(src.itemsize_),\n        ctor_(src.ctor_),\n        copy_(src.copy_),\n        dtor_(src.dtor_) {}\n  /**\n   * Assignment operator.\n   */\n  TypeMeta& operator=(const TypeMeta& src) {\n    if (this == &src)\n      return *this;\n    id_ = src.id_;\n    itemsize_ = src.itemsize_;\n    ctor_ = src.ctor_;\n    copy_ = src.copy_;\n    dtor_ = src.dtor_;\n    return *this;\n  }\n\n private:\n  // TypeMeta can only be created by Make, making sure that we do not\n  // create incorrectly mixed up TypeMeta objects.\n  TypeMeta(\n      CaffeTypeId i,\n      size_t s,\n      PlacementNew ctor,\n      TypedCopy copy,\n      TypedDestructor dtor)\n      : id_(i), itemsize_(s), ctor_(ctor), copy_(copy), dtor_(dtor) {}\n\n public:\n  /**\n   * Returns the type id.\n   */\n  inline const CaffeTypeId& id() const {\n    return id_;\n  }\n  /**\n   * Returns the size of the item.\n   */\n  inline const size_t& itemsize() const {\n    return itemsize_;\n  }\n  /**\n   * Returns the placement new function pointer for individual items.\n   */\n  inline PlacementNew ctor() const {\n    return ctor_;\n  }\n  /**\n   * Returns the typed copy function pointer for individual iterms.\n   */\n  inline TypedCopy copy() const {\n    return copy_;\n  }\n  /**\n   * Returns the destructor function pointer for individual items.\n   */\n  inline TypedDestructor dtor() const {\n    return dtor_;\n  }\n  /**\n   * Returns a printable name for the type.\n   */\n  inline const char* name() const {\n    auto it = gTypeNames().find(id_);\n    assert(it != gTypeNames().end());\n    return it->second.c_str();\n  }\n  inline bool operator==(const TypeMeta& m) const {\n    return (id_ == m.id_);\n  }\n  inline bool operator!=(const TypeMeta& m) const {\n    return (id_ != m.id_);\n  }\n\n  template <typename T>\n  inline bool Match() const {\n    return (id_ == Id<T>());\n  }\n\n  // Below are static functions that can be called by passing a specific type.\n\n  /**\n   * Returns the unique id for the given type T. The id is unique for the type T\n   * in the sense that for any two different types, their id are different; for\n   * the same type T, the id remains the same over different calls of the\n   * function. However, this is not guaranteed over different runs, as the id\n   * is generated during run-time. Do NOT serialize the id for storage.\n   */\n  template <typename T>\n  CAFFE2_API static CaffeTypeId Id();\n\n  /**\n   * Returns the item size of the type. This is equivalent to sizeof(T).\n   */\n  template <typename T>\n  static size_t ItemSize() {\n    return sizeof(T);\n  }\n\n  /**\n   * Returns the registered printable name of the type.\n   *\n   * Works for only the ones registered with CAFFE_KNOWN_TYPE\n   */\n  template <typename T>\n  static const char* TypeName() {\n    auto it = gTypeNames().find(Id<T>());\n    assert(it != gTypeNames().end());\n    return it->second.c_str();\n  }\n\n  /**\n   * Placement new function for the type.\n   */\n  template <typename T>\n  static void _Ctor(void* ptr, size_t n) {\n    T* typed_ptr = static_cast<T*>(ptr);\n    for (int i = 0; i < n; ++i) {\n      new (typed_ptr + i) T;\n    }\n  }\n\n  /**\n   * Typed copy function for classes.\n   */\n  template <typename T>\n  static void _Copy(const void* src, void* dst, size_t n) {\n    const T* typed_src = static_cast<const T*>(src);\n    T* typed_dst = static_cast<T*>(dst);\n    for (int i = 0; i < n; ++i) {\n      typed_dst[i] = typed_src[i];\n    }\n  }\n\n  /**\n   * A placeholder function for types that do not allow assignment.\n   */\n  template <typename T>\n  static void\n  _CopyNotAllowed(const void* /*src*/, void* /*dst*/, size_t /*n*/) {\n    std::cerr << \"Type \" << DemangleType<T>() << \" does not allow assignment.\";\n    // This is an error by design, so we will quit loud.\n    abort();\n  }\n\n  /**\n   * Destructor for non-fundamental types.\n   */\n  template <typename T>\n  static void _Dtor(void* ptr, size_t n) {\n    T* typed_ptr = static_cast<T*>(ptr);\n    for (int i = 0; i < n; ++i) {\n      typed_ptr[i].~T();\n    }\n  }\n\n  /**\n   * Returns a TypeMeta object that corresponds to the typename T.\n   */\n  template <typename T>\n  static typename std::enable_if<\n      std::is_fundamental<T>::value || std::is_pointer<T>::value,\n      TypeMeta>::type\n  Make() {\n    return TypeMeta(Id<T>(), ItemSize<T>(), nullptr, nullptr, nullptr);\n  }\n\n  template <\n      typename T,\n      typename std::enable_if<\n          !(std::is_fundamental<T>::value || std::is_pointer<T>::value) &&\n          std::is_copy_assignable<T>::value>::type* = nullptr>\n  static TypeMeta Make() {\n    return TypeMeta(Id<T>(), ItemSize<T>(), _Ctor<T>, _Copy<T>, _Dtor<T>);\n  }\n\n  template <typename T>\n  static TypeMeta Make(\n      typename std::enable_if<\n          !(std::is_fundamental<T>::value || std::is_pointer<T>::value) &&\n          !std::is_copy_assignable<T>::value>::type* = 0) {\n    return TypeMeta(\n        Id<T>(), ItemSize<T>(), _Ctor<T>, _CopyNotAllowed<T>, _Dtor<T>);\n  }\n\n private:\n  CaffeTypeId id_;\n  size_t itemsize_;\n  PlacementNew ctor_;\n  TypedCopy copy_;\n  TypedDestructor dtor_;\n};\n\n/**\n * Register unique id for a type so it can be used in TypeMeta context, e.g. be\n * used as a type for Blob or for Tensor elements.\n *\n * CAFFE_KNOWN_TYPE does explicit instantiation of TypeMeta::Id<T> template\n * function and thus needs to be put in a single translation unit (.cpp file)\n * for a given type T. Other translation units that use type T as a type of the\n * caffe2::Blob or element type of caffe2::Tensor need to depend on the\n * translation unit that contains CAFFE_KNOWN_TYPE declaration via regular\n * linkage dependencies.\n *\n * NOTE: the macro needs to be invoked in ::caffe2 namespace\n */\n// Implementation note: in MSVC, we will need to prepend the CAFFE2_EXPORT\n// keyword in order to get things compiled properly. in Linux, gcc seems to\n// create attribute ignored error for explicit template instantiations, see\n//   http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0537r0.html\n//   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51930\n// and as a result, we define these two macros slightly differently.\n\n#ifdef _MSC_VER\n#define CAFFE_KNOWN_TYPE(T)                              \\\n  template <>                                            \\\n  CAFFE2_EXPORT CaffeTypeId TypeMeta::Id<T>() {          \\\n    static bool type_id_bit[1];                          \\\n    static TypeNameRegisterer<T> registerer(             \\\n        reinterpret_cast<CaffeTypeId>(type_id_bit), #T); \\\n    return reinterpret_cast<CaffeTypeId>(type_id_bit);   \\\n  }\n#else // _MSC_VER\n#define CAFFE_KNOWN_TYPE(T)                              \\\n  template <>                                            \\\n  CaffeTypeId TypeMeta::Id<T>() {                        \\\n    static bool type_id_bit[1];                          \\\n    static TypeNameRegisterer<T> registerer(             \\\n        reinterpret_cast<CaffeTypeId>(type_id_bit), #T); \\\n    return reinterpret_cast<CaffeTypeId>(type_id_bit);   \\\n  }\n#endif\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_TYPEID_H_\n"
  },
  {
    "path": "caffe2/core/typeid_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/typeid.h\"\n#include \"caffe2/core/types.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\nnamespace {\n\nclass TypeMetaTestFoo {};\nclass TypeMetaTestBar {};\n}\n\nCAFFE_KNOWN_TYPE(TypeMetaTestFoo);\nCAFFE_KNOWN_TYPE(TypeMetaTestBar);\n\nnamespace {\n\nTEST(TypeMetaTest, TypeMetaStatic) {\n  EXPECT_EQ(TypeMeta::ItemSize<int>(), sizeof(int));\n  EXPECT_EQ(TypeMeta::ItemSize<float>(), sizeof(float));\n  EXPECT_EQ(TypeMeta::ItemSize<TypeMetaTestFoo>(), sizeof(TypeMetaTestFoo));\n  EXPECT_EQ(TypeMeta::ItemSize<TypeMetaTestBar>(), sizeof(TypeMetaTestBar));\n  EXPECT_NE(TypeMeta::Id<int>(), TypeMeta::Id<float>());\n  EXPECT_NE(TypeMeta::Id<int>(), TypeMeta::Id<TypeMetaTestFoo>());\n  EXPECT_NE(TypeMeta::Id<TypeMetaTestFoo>(), TypeMeta::Id<TypeMetaTestBar>());\n  EXPECT_EQ(TypeMeta::Id<int>(), TypeMeta::Id<int>());\n  EXPECT_EQ(TypeMeta::Id<TypeMetaTestFoo>(), TypeMeta::Id<TypeMetaTestFoo>());\n}\n\nTEST(TypeMetaTest, Names) {\n  TypeMeta null_meta;\n  EXPECT_TRUE(string(null_meta.name()) == \"nullptr (uninitialized)\");\n  TypeMeta int_meta = TypeMeta::Make<int>();\n  EXPECT_TRUE(string(int_meta.name()) == \"int\");\n#ifdef __GXX_RTTI\n  TypeMeta string_meta = TypeMeta::Make<string>();\n  // For string, we should have a demangled name.\n  EXPECT_TRUE(\n      string(string_meta.name()) != typeid(string).name());\n  EXPECT_TRUE(\n      string(string_meta.name()) == Demangle(typeid(string).name()));\n#endif  // __GXX_RTTI\n}\n\nTEST(TypeMetaTest, TypeMeta) {\n  TypeMeta int_meta = TypeMeta::Make<int>();\n  TypeMeta float_meta = TypeMeta::Make<float>();\n  TypeMeta foo_meta = TypeMeta::Make<TypeMetaTestFoo>();\n  TypeMeta bar_meta = TypeMeta::Make<TypeMetaTestBar>();\n\n  TypeMeta another_int_meta = TypeMeta::Make<int>();\n  TypeMeta another_foo_meta = TypeMeta::Make<TypeMetaTestFoo>();\n\n  EXPECT_EQ(int_meta, another_int_meta);\n  EXPECT_EQ(foo_meta, another_foo_meta);\n  EXPECT_NE(int_meta, float_meta);\n  EXPECT_NE(int_meta, foo_meta);\n  EXPECT_NE(foo_meta, bar_meta);\n  EXPECT_TRUE(int_meta.Match<int>());\n  EXPECT_TRUE(foo_meta.Match<TypeMetaTestFoo>());\n  EXPECT_FALSE(int_meta.Match<float>());\n  EXPECT_FALSE(int_meta.Match<TypeMetaTestFoo>());\n  EXPECT_FALSE(foo_meta.Match<int>());\n  EXPECT_FALSE(foo_meta.Match<TypeMetaTestBar>());\n  EXPECT_EQ(int_meta.id(), TypeMeta::Id<int>());\n  EXPECT_EQ(float_meta.id(), TypeMeta::Id<float>());\n  EXPECT_EQ(foo_meta.id(), TypeMeta::Id<TypeMetaTestFoo>());\n  EXPECT_EQ(bar_meta.id(), TypeMeta::Id<TypeMetaTestBar>());\n  EXPECT_EQ(int_meta.itemsize(), TypeMeta::ItemSize<int>());\n  EXPECT_EQ(float_meta.itemsize(), TypeMeta::ItemSize<float>());\n  EXPECT_EQ(foo_meta.itemsize(), TypeMeta::ItemSize<TypeMetaTestFoo>());\n  EXPECT_EQ(bar_meta.itemsize(), TypeMeta::ItemSize<TypeMetaTestBar>());\n  EXPECT_STREQ(int_meta.name(), \"int\");\n  EXPECT_STREQ(float_meta.name(), \"float\");\n#ifdef __GXX_RTTI\n  EXPECT_NE(\n      std::string(foo_meta.name()).find(\"TypeMetaTestFoo\"), std::string::npos);\n  EXPECT_NE(\n      std::string(bar_meta.name()).find(\"TypeMetaTestBar\"), std::string::npos);\n#endif\n}\n\n\nclass ClassAllowAssignment {\n public:\n  ClassAllowAssignment() : x(42) {}\n  ClassAllowAssignment(const ClassAllowAssignment& src) : x(src.x) {}\n  ClassAllowAssignment& operator=(const ClassAllowAssignment& src) = default;\n  int x;\n};\n\nclass ClassNoAssignment {\n public:\n  ClassNoAssignment() : x(42) {}\n  ClassNoAssignment(const ClassNoAssignment& src) = delete;\n  ClassNoAssignment& operator=(const ClassNoAssignment& src) = delete;\n  int x;\n};\n}\n\nCAFFE_KNOWN_TYPE(ClassAllowAssignment);\nCAFFE_KNOWN_TYPE(ClassNoAssignment);\n\nnamespace {\n\nTEST(TypeMetaTest, CtorDtorAndCopy) {\n  TypeMeta fundamental_meta = TypeMeta::Make<int>();\n  EXPECT_EQ(fundamental_meta.ctor(), nullptr);\n  EXPECT_EQ(fundamental_meta.dtor(), nullptr);\n  EXPECT_EQ(fundamental_meta.copy(), nullptr);\n\n  TypeMeta meta_a = TypeMeta::Make<ClassAllowAssignment>();\n  EXPECT_TRUE(meta_a.ctor() != nullptr);\n  EXPECT_TRUE(meta_a.dtor() != nullptr);\n  EXPECT_TRUE(meta_a.copy() != nullptr);\n  ClassAllowAssignment src;\n  src.x = 10;\n  ClassAllowAssignment dst;\n  EXPECT_EQ(dst.x, 42);\n  meta_a.copy()(&src, &dst, 1);\n  EXPECT_EQ(dst.x, 10);\n\n  TypeMeta meta_b = TypeMeta::Make<ClassNoAssignment>();\n\n  EXPECT_TRUE(meta_b.ctor() != nullptr);\n  EXPECT_TRUE(meta_b.dtor() != nullptr);\n#ifndef __clang__\n  // gtest seems to have some problem with function pointers and\n  // clang right now... Disabling it.\n  // TODO: figure out the real cause.\n  EXPECT_EQ(meta_b.copy(),\n            &(TypeMeta::_CopyNotAllowed<ClassNoAssignment>));\n#endif\n}\n\nTEST(TypeMetaTest, Float16IsNotUint16) {\n  EXPECT_NE(TypeMeta::Id<uint16_t>(), TypeMeta::Id<float16>());\n}\n\n}  // namespace\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/types.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/types.h\"\n#include \"caffe2/core/typeid.h\"\n\n#include <atomic>\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace caffe2 {\n\nCAFFE_KNOWN_TYPE(float);\nCAFFE_KNOWN_TYPE(int);\nCAFFE_KNOWN_TYPE(std::string);\nCAFFE_KNOWN_TYPE(bool);\nCAFFE_KNOWN_TYPE(uint8_t);\nCAFFE_KNOWN_TYPE(int8_t);\nCAFFE_KNOWN_TYPE(uint16_t);\nCAFFE_KNOWN_TYPE(int16_t);\nCAFFE_KNOWN_TYPE(int64_t);\nCAFFE_KNOWN_TYPE(float16);\nCAFFE_KNOWN_TYPE(double);\nCAFFE_KNOWN_TYPE(char);\nCAFFE_KNOWN_TYPE(std::unique_ptr<std::mutex>);\nCAFFE_KNOWN_TYPE(std::unique_ptr<std::atomic<bool>>);\nCAFFE_KNOWN_TYPE(std::vector<int64_t>);\nCAFFE_KNOWN_TYPE(std::vector<unsigned long>);\nCAFFE_KNOWN_TYPE(bool*);\nCAFFE_KNOWN_TYPE(char*);\nCAFFE_KNOWN_TYPE(int*);\n\n#ifdef CAFFE2_UNIQUE_LONG_TYPEMETA\nCAFFE_KNOWN_TYPE(long);\nCAFFE_KNOWN_TYPE(std::vector<long>);\n#endif // CAFFE2_UNIQUE_LONG_TYPEMETA\n\nTensorProto::DataType TypeMetaToDataType(const TypeMeta& meta) {\n  static_assert(sizeof(int) == 4,\n                \"int in this compiler does not equal to 4 bytes.\");\n  static std::map<CaffeTypeId, TensorProto::DataType> data_type_map {\n    {TypeMeta::Id<float>(), TensorProto_DataType_FLOAT},\n    {TypeMeta::Id<int>(), TensorProto_DataType_INT32},\n    // BYTE does not have a type meta to proto mapping: we should\n    // always use uint8_t when serializing. BYTE is kept for backward\n    // compatibility.\n    // {TypeMeta::Id<>(), TensorProto_DataType_BYTE},\n    {TypeMeta::Id<string>(), TensorProto_DataType_STRING},\n    {TypeMeta::Id<bool>(), TensorProto_DataType_BOOL},\n    {TypeMeta::Id<uint8_t>(), TensorProto_DataType_UINT8},\n    {TypeMeta::Id<int8_t>(), TensorProto_DataType_INT8},\n    {TypeMeta::Id<uint16_t>(), TensorProto_DataType_UINT16},\n    {TypeMeta::Id<int16_t>(), TensorProto_DataType_INT16},\n    {TypeMeta::Id<int64_t>(), TensorProto_DataType_INT64},\n    {TypeMeta::Id<float16>(), TensorProto_DataType_FLOAT16},\n    {TypeMeta::Id<double>(), TensorProto_DataType_DOUBLE},\n  };\n  const auto it = data_type_map.find(meta.id());\n  return (it == data_type_map.end()\n          ? TensorProto_DataType_UNDEFINED : it->second);\n}\n\nconst TypeMeta& DataTypeToTypeMeta(const TensorProto::DataType& dt) {\n  static std::map<TensorProto::DataType, TypeMeta> type_meta_map{\n      {TensorProto_DataType_FLOAT, TypeMeta::Make<float>()},\n      {TensorProto_DataType_INT32, TypeMeta::Make<int>()},\n      {TensorProto_DataType_STRING, TypeMeta::Make<std::string>()},\n      {TensorProto_DataType_BOOL, TypeMeta::Make<bool>()},\n      {TensorProto_DataType_UINT8, TypeMeta::Make<uint8_t>()},\n      {TensorProto_DataType_INT8, TypeMeta::Make<int8_t>()},\n      {TensorProto_DataType_UINT16, TypeMeta::Make<uint16_t>()},\n      {TensorProto_DataType_INT16, TypeMeta::Make<int16_t>()},\n      {TensorProto_DataType_INT64, TypeMeta::Make<int64_t>()},\n      {TensorProto_DataType_FLOAT16, TypeMeta::Make<float16>()},\n      {TensorProto_DataType_DOUBLE, TypeMeta::Make<double>()},\n  };\n  const auto it = type_meta_map.find(dt);\n  if (it == type_meta_map.end()) {\n    throw std::runtime_error(\"Unknown data type.\");\n  }\n  return it->second;\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/types.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_TYPES_H_\n#define CAFFE2_CORE_TYPES_H_\n\n#include <cstdint>\n#include <string>\n#include <type_traits>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/typeid.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\n// Storage orders that are often used in the image applications.\nenum StorageOrder {\n  UNKNOWN = 0,\n  NHWC = 1,\n  NCHW = 2,\n};\n\ninline StorageOrder StringToStorageOrder(const string& str) {\n  if (str == \"NHWC\" || str == \"nhwc\") {\n    return StorageOrder::NHWC;\n  } else if (str == \"NCHW\" || str == \"nchw\") {\n    return StorageOrder::NCHW;\n  } else {\n    LOG(ERROR) << \"Unknown storage order string: \" << str;\n    return StorageOrder::UNKNOWN;\n  }\n}\n\ninline constexpr char NameScopeSeparator() { return '/'; }\n\n// From TypeMeta to caffe2::DataType protobuffer enum.\nTensorProto::DataType TypeMetaToDataType(const TypeMeta& meta);\n\n// From caffe2::DataType protobuffer enum to TypeMeta\nconst TypeMeta& DataTypeToTypeMeta(const TensorProto::DataType& dt);\n\n}  // namespace caffe2\n\n///////////////////////////////////////////////////////////////////////////////\n// Half float definition. Currently half float operators are mainly on CUDA\n// gpus.\n// The reason we do not directly use the cuda __half data type is because that\n// requires compilation with nvcc. The float16 data type should be compatible\n// with the cuda __half data type, but will allow us to refer to the data type\n// without the need of cuda.\nstatic_assert(sizeof(unsigned short) == 2,\n              \"Short on this platform is not 16 bit.\");\nnamespace caffe2 {\ntypedef struct CAFFE2_ALIGNED(2) __f16 { uint16_t x; } float16;\n\n// Helpers to avoid using typeinfo with -rtti\ntemplate <typename T>\ninline bool fp16_type();\n// explicit instantation for float16 defined in types.cc.\ntemplate <>\ninline bool fp16_type<float16>() {\n  return true;\n}\n// The rest.\ntemplate <typename T>\ninline bool fp16_type() {\n  return false;\n}\n\n}  // namespace caffe2\n\n// Make __f16 a fundamental type.\nnamespace std {\ntemplate<>\nstruct is_fundamental<caffe2::__f16> : std::integral_constant<bool, true> {\n};\n}  // namespace std\n\n#endif  // CAFFE2_CORE_TYPES_H_\n"
  },
  {
    "path": "caffe2/core/workspace.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/workspace.h\"\n\n#include <algorithm>\n#include <ctime>\n#include <mutex>\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/plan_executor.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nCAFFE2_DEFINE_bool(\n    caffe2_print_blob_sizes_at_exit,\n    false,\n    \"If true, workspace destructor will print all blob shapes\");\n\nnamespace caffe2 {\n\nvoid Workspace::PrintBlobSizes() {\n  vector<string> blobs = LocalBlobs();\n  size_t cumtotal = 0;\n\n  // First get total sizes and sort\n  vector<std::pair<size_t, std::string>> blob_sizes;\n  for (const auto& s : blobs) {\n    Blob* b = this->GetBlob(s);\n    TensorInfoCall shape_fun = GetTensorInfoFunction(b->meta().id());\n    if (shape_fun) {\n      bool shares_data = false;\n      size_t capacity;\n      DeviceOption _device;\n      auto shape = shape_fun(b->GetRaw(), &shares_data, &capacity, &_device);\n      if (shares_data) {\n        // Blobs sharing data do not actually take any memory\n        capacity = 0;\n      }\n      cumtotal += capacity;\n      blob_sizes.push_back(make_pair(capacity, s));\n    }\n  }\n  std::sort(\n      blob_sizes.begin(),\n      blob_sizes.end(),\n      [](const std::pair<size_t, std::string>& a,\n         const std::pair<size_t, std::string>& b) {\n        return b.first < a.first;\n      });\n\n  // Then print in descending order\n  LOG(INFO) << \"---- Workspace blobs: ---- \";\n  LOG(INFO) << \"name;current shape;capacity bytes;percentage\";\n  for (const auto& sb : blob_sizes) {\n    Blob* b = this->GetBlob(sb.second);\n    TensorInfoCall shape_fun = GetTensorInfoFunction(b->meta().id());\n    CHECK(shape_fun != nullptr);\n    bool _shares_data = false;\n    size_t capacity;\n    DeviceOption _device;\n\n    auto shape = shape_fun(b->GetRaw(), &_shares_data, &capacity, &_device);\n    std::stringstream ss;\n    ss << sb.second << \";\";\n    for (const auto d : shape) {\n      ss << d << \",\";\n    }\n    LOG(INFO) << ss.str() << \";\" << sb.first << \";\" << std::setprecision(3)\n              << (cumtotal > 0 ? 100.0 * double(sb.first) / cumtotal : 0.0)\n              << \"%\";\n  }\n  LOG(INFO) << \"Total;;\" << cumtotal << \";100%\";\n}\n\nvector<string> Workspace::LocalBlobs() const {\n  vector<string> names;\n  names.reserve(blob_map_.size());\n  for (auto& entry : blob_map_) {\n    names.push_back(entry.first);\n  }\n  return names;\n}\n\nvector<string> Workspace::Blobs() const {\n  vector<string> names;\n  names.reserve(blob_map_.size());\n  for (auto& entry : blob_map_) {\n    names.push_back(entry.first);\n  }\n  for (const auto& forwarded : forwarded_blobs_) {\n    const auto parent_ws = forwarded.second.first;\n    const auto& parent_name = forwarded.second.second;\n    if (parent_ws->HasBlob(parent_name)) {\n      names.push_back(forwarded.first);\n    }\n  }\n  if (shared_) {\n    const auto& shared_blobs = shared_->Blobs();\n    names.insert(names.end(), shared_blobs.begin(), shared_blobs.end());\n  }\n  return names;\n}\n\nBlob* Workspace::CreateBlob(const string& name) {\n  if (HasBlob(name)) {\n    VLOG(1) << \"Blob \" << name << \" already exists. Skipping.\";\n  } else if (forwarded_blobs_.count(name)) {\n    // possible if parent workspace deletes forwarded blob\n    VLOG(1) << \"Blob \" << name << \" is already forwarded from parent workspace \"\n            << \"(blob \" << forwarded_blobs_[name].second << \"). Skipping.\";\n  } else {\n    VLOG(1) << \"Creating blob \" << name;\n    blob_map_[name] = unique_ptr<Blob>(new Blob());\n  }\n  return GetBlob(name);\n}\n\nBlob* Workspace::CreateLocalBlob(const string& name) {\n  if (blob_map_.count(name)) {\n    VLOG(1) << \"Blob \" << name << \" already exists. Skipping.\";\n  } else {\n    VLOG(1) << \"Creating blob \" << name;\n    blob_map_[name] = unique_ptr<Blob>(new Blob());\n  }\n  return GetBlob(name);\n}\n\nBlob* Workspace::RenameBlob(const string& old_name, const string& new_name) {\n  // We allow renaming only local blobs for API clarity purpose\n  auto it = blob_map_.find(old_name);\n  CAFFE_ENFORCE(\n      it != blob_map_.end(),\n      \"Blob \",\n      old_name,\n      \" is not in the local blob list\");\n\n  // New blob can't be in any parent either, otherwise it will hide a parent\n  // blob\n  CAFFE_ENFORCE(\n      !HasBlob(new_name), \"Blob \", new_name, \"is already in the workspace\");\n\n  // First delete the old record\n  auto value = std::move(it->second);\n  blob_map_.erase(it);\n\n  auto* raw_ptr = value.get();\n  blob_map_[new_name] = std::move(value);\n  return raw_ptr;\n}\n\nbool Workspace::RemoveBlob(const string& name) {\n  auto it = blob_map_.find(name);\n  if (it != blob_map_.end()) {\n    VLOG(1) << \"Removing blob \" << name << \" from this workspace.\";\n    blob_map_.erase(it);\n    return true;\n  }\n\n  // won't go into shared_ here\n  VLOG(1) << \"Blob \" << name << \" not exists. Skipping.\";\n  return false;\n}\n\nconst Blob* Workspace::GetBlob(const string& name) const {\n  if (blob_map_.count(name)) {\n    return blob_map_.at(name).get();\n  } else if (forwarded_blobs_.count(name)) {\n    const auto parent_ws = forwarded_blobs_.at(name).first;\n    const auto& parent_name = forwarded_blobs_.at(name).second;\n    return parent_ws->GetBlob(parent_name);\n  } else if (shared_ && shared_->HasBlob(name)) {\n    return shared_->GetBlob(name);\n  }\n  LOG(WARNING) << \"Blob \" << name << \" not in the workspace.\";\n  // TODO(Yangqing): do we want to always print out the list of blobs here?\n  // LOG(WARNING) << \"Current blobs:\";\n  // for (const auto& entry : blob_map_) {\n  //   LOG(WARNING) << entry.first;\n  // }\n  return nullptr;\n}\n\nvoid Workspace::AddBlobMapping(\n    const Workspace* parent,\n    const std::unordered_map<string, string>& forwarded_blobs,\n    bool skip_defined_blobs) {\n  CAFFE_ENFORCE(parent, \"Parent workspace must be specified\");\n  for (const auto& forwarded : forwarded_blobs) {\n    CAFFE_ENFORCE(\n        parent->HasBlob(forwarded.second),\n        \"Invalid parent workspace blob \" + forwarded.second);\n    if (forwarded_blobs_.count(forwarded.first)) {\n      const auto& ws_blob = forwarded_blobs_[forwarded.first];\n      CAFFE_ENFORCE_EQ(\n          ws_blob.first, parent, \"Redefinition of blob \" + forwarded.first);\n      CAFFE_ENFORCE_EQ(\n          ws_blob.second,\n          forwarded.second,\n          \"Redefinition of blob \" + forwarded.first);\n    } else {\n      if (skip_defined_blobs && HasBlob(forwarded.first)) {\n        continue;\n      }\n      CAFFE_ENFORCE(\n          !HasBlob(forwarded.first), \"Redefinition of blob \" + forwarded.first);\n      // Lazy blob resolution - store the parent workspace and\n      // blob name, blob value might change in the parent workspace\n      forwarded_blobs_[forwarded.first] =\n          std::make_pair(parent, forwarded.second);\n    }\n  }\n}\n\nBlob* Workspace::GetBlob(const string& name) {\n  return const_cast<Blob*>(static_cast<const Workspace*>(this)->GetBlob(name));\n}\n\nNetBase* Workspace::CreateNet(const NetDef& net_def, bool overwrite) {\n  std::shared_ptr<NetDef> tmp_net_def(new NetDef(net_def));\n  return CreateNet(tmp_net_def, overwrite);\n}\n\nNetBase* Workspace::CreateNet(\n    const std::shared_ptr<const NetDef>& net_def,\n    bool overwrite) {\n  CAFFE_ENFORCE(net_def->has_name(), \"Net definition should have a name.\");\n  if (net_map_.count(net_def->name()) > 0) {\n    if (!overwrite) {\n      CAFFE_THROW(\n          \"I respectfully refuse to overwrite an existing net of the same \"\n          \"name \\\"\",\n          net_def->name(),\n          \"\\\", unless you explicitly specify overwrite=true.\");\n    }\n    VLOG(1) << \"Deleting existing network of the same name.\";\n    // Note(Yangqing): Why do we explicitly erase it here? Some components of\n    // the old network, such as an opened LevelDB, may prevent us from creating\n    // a new network before the old one is deleted. Thus we will need to first\n    // erase the old one before the new one can be constructed.\n    net_map_.erase(net_def->name());\n  }\n  // Create a new net with its name.\n  VLOG(1) << \"Initializing network \" << net_def->name();\n  net_map_[net_def->name()] =\n      unique_ptr<NetBase>(caffe2::CreateNet(net_def, this));\n  if (net_map_[net_def->name()].get() == nullptr) {\n    LOG(ERROR) << \"Error when creating the network.\"\n               << \"Maybe net type: [\" << net_def->type() << \"] does not exist\";\n    net_map_.erase(net_def->name());\n    return nullptr;\n  }\n  return net_map_[net_def->name()].get();\n}\n\nNetBase* Workspace::GetNet(const string& name) {\n  if (!net_map_.count(name)) {\n    return nullptr;\n  } else {\n    return net_map_[name].get();\n  }\n}\n\nvoid Workspace::DeleteNet(const string& name) {\n  if (net_map_.count(name)) {\n    net_map_.erase(name);\n  }\n}\n\nbool Workspace::RunNet(const string& name) {\n  if (!net_map_.count(name)) {\n    LOG(ERROR) << \"Network \" << name << \" does not exist yet.\";\n    return false;\n  }\n  return net_map_[name]->Run();\n}\n\nbool Workspace::RunOperatorOnce(const OperatorDef& op_def) {\n  std::unique_ptr<OperatorBase> op(CreateOperator(op_def, this));\n  if (op.get() == nullptr) {\n    LOG(ERROR) << \"Cannot create operator of type \" << op_def.type();\n    return false;\n  }\n  if (!op->Run()) {\n    LOG(ERROR) << \"Error when running operator \" << op_def.type();\n    return false;\n  }\n  return true;\n}\nbool Workspace::RunNetOnce(const NetDef& net_def) {\n  std::unique_ptr<NetBase> net(caffe2::CreateNet(net_def, this));\n  if (net == nullptr) {\n    CAFFE_THROW(\n        \"Could not create net: \" + net_def.name() + \" of type \" +\n        net_def.type());\n  }\n  if (!net->Run()) {\n    LOG(ERROR) << \"Error when running network \" << net_def.name();\n    return false;\n  }\n  return true;\n}\n\nbool Workspace::RunPlan(const PlanDef& plan, ShouldContinue shouldContinue) {\n  return RunPlanOnWorkspace(this, plan, shouldContinue);\n}\n\nThreadPool* Workspace::GetThreadPool() {\n  std::lock_guard<std::mutex> guard(thread_pool_creation_mutex_);\n  if (!thread_pool_) {\n    thread_pool_ = ThreadPool::defaultThreadPool();\n  }\n  return thread_pool_.get();\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/core/workspace.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_WORKSPACE_H_\n#define CAFFE2_CORE_WORKSPACE_H_\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/observer.h\"\n\n#include <climits>\n#include <cstddef>\n#include <mutex>\n#include <typeinfo>\n#include <unordered_set>\n#include <vector>\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/signal_handler.h\"\n#include \"caffe2/utils/threadpool/ThreadPool.h\"\n\nCAFFE2_DECLARE_bool(caffe2_print_blob_sizes_at_exit);\n\nnamespace caffe2 {\n\nclass NetBase;\n\nstruct StopOnSignal {\n  StopOnSignal()\n      : handler_(std::make_shared<SignalHandler>(\n            SignalHandler::Action::STOP,\n            SignalHandler::Action::STOP)) {}\n\n  StopOnSignal(const StopOnSignal& other) : handler_(other.handler_) {}\n\n  bool operator()(int /*iter*/) {\n    return handler_->CheckForSignals() != SignalHandler::Action::STOP;\n  }\n\n  std::shared_ptr<SignalHandler> handler_;\n};\n\n/**\n * Workspace is a class that holds all the related objects created during\n * runtime: (1) all blobs, and (2) all instantiated networks. It is the owner of\n * all these objects and deals with the scaffolding logistics.\n */\nclass Workspace {\n public:\n  typedef std::function<bool(int)> ShouldContinue;\n  typedef CaffeMap<string, unique_ptr<Blob> > BlobMap;\n  typedef CaffeMap<string, unique_ptr<NetBase> > NetMap;\n  /**\n   * Initializes an empty workspace.\n   */\n  Workspace() : root_folder_(\".\"), shared_(nullptr) {}\n\n  /**\n   * Initializes an empty workspace with the given root folder.\n   *\n   * For any operators that are going to interface with the file system, such\n   * as load operators, they will write things under this root folder given\n   * by the workspace.\n   */\n  explicit Workspace(const string& root_folder)\n      : root_folder_(root_folder), shared_(nullptr) {}\n\n  /**\n   * Initializes a workspace with a shared workspace.\n   *\n   * When we access a Blob, we will first try to access the blob that exists\n   * in the local workspace, and if not, access the blob that exists in the\n   * shared workspace. The caller keeps the ownership of the shared workspace\n   * and is responsible for making sure that its lifetime is longer than the\n   * created workspace.\n   */\n  explicit Workspace(const Workspace* shared)\n      : root_folder_(\".\"), shared_(shared) {}\n\n  /**\n   * Initializes workspace with parent workspace, blob name remapping\n   * (new name -> parent blob name), no other blobs are inherited from\n   * parent workspace\n   */\n  Workspace(\n      const Workspace* shared,\n      const std::unordered_map<string, string>& forwarded_blobs)\n      : root_folder_(\".\"), shared_(nullptr) {\n    CAFFE_ENFORCE(shared, \"Parent workspace must be specified\");\n    for (const auto& forwarded : forwarded_blobs) {\n      CAFFE_ENFORCE(\n          shared->HasBlob(forwarded.second), \"Invalid parent workspace blob\");\n      forwarded_blobs_[forwarded.first] =\n          std::make_pair(shared, forwarded.second);\n    }\n  }\n\n  /**\n   * Initializes a workspace with a root folder and a shared workspace.\n   */\n  Workspace(const string& root_folder, Workspace* shared)\n      : root_folder_(root_folder), shared_(shared) {}\n\n  ~Workspace() {\n    if (FLAGS_caffe2_print_blob_sizes_at_exit) {\n      PrintBlobSizes();\n    }\n  }\n\n  /**\n   * Adds blob mappings from workspace to the blobs from parent workspace.\n   * Creates blobs under possibly new names that redirect read/write operations\n   * to the blobs in the parent workspace.\n   * Arguments:\n   *  parent - pointer to parent workspace\n   *  forwarded_blobs - map from new blob name to blob name in parent's\n   * workspace skip_defined_blob - if set skips blobs with names that already\n   * exist in the workspace, otherwise throws exception\n   */\n  void AddBlobMapping(\n      const Workspace* parent,\n      const std::unordered_map<string, string>& forwarded_blobs,\n      bool skip_defined_blobs = false);\n\n  /**\n   * Converts prevously mapped tensor blobs to local blobs, copies values from\n   * parent workspace blobs into new local blobs. Ignores undefined blobs.\n   */\n  template <class Context>\n  void CopyForwardedTensors(const std::unordered_set<std::string>& blobs) {\n    for (const auto& blob : blobs) {\n      if (!forwarded_blobs_.count(blob)) {\n        continue;\n      }\n      const auto& ws_blob = forwarded_blobs_[blob];\n      const auto* parent_ws = ws_blob.first;\n      auto* from_blob = parent_ws->GetBlob(ws_blob.second);\n      CAFFE_ENFORCE(from_blob);\n      CAFFE_ENFORCE(\n          from_blob->template IsType<Tensor<Context>>(),\n          \"Expected blob with tensor value\",\n          ws_blob.second);\n      forwarded_blobs_.erase(blob);\n      auto* to_blob = CreateBlob(blob);\n      CAFFE_ENFORCE(to_blob);\n      const auto& from_tensor = from_blob->template Get<Tensor<Context>>();\n      auto* to_tensor = to_blob->template GetMutable<Tensor<Context>>();\n      to_tensor->CopyFrom(from_tensor);\n    }\n  }\n\n  /**\n   * Return list of blobs owned by this Workspace, not including blobs\n   * shared from parent workspace.\n   */\n  vector<string> LocalBlobs() const;\n\n  /**\n   * Return a list of blob names. This may be a bit slow since it will involve\n   * creation of multiple temp variables. For best performance, simply use\n   * HasBlob() and GetBlob().\n   */\n  vector<string> Blobs() const;\n\n  /**\n   * Return the root folder of the workspace.\n   */\n  const string& RootFolder() { return root_folder_; }\n  /**\n   * Checks if a blob with the given name is present in the current workspace.\n   */\n  inline bool HasBlob(const string& name) const {\n    // First, check the local workspace,\n    // Then, check the forwarding map, then the parent workspace\n    if (blob_map_.count(name)) {\n      return true;\n    } else if (forwarded_blobs_.count(name)) {\n      const auto parent_ws = forwarded_blobs_.at(name).first;\n      const auto& parent_name = forwarded_blobs_.at(name).second;\n      return parent_ws->HasBlob(parent_name);\n    } else if (shared_) {\n      return shared_->HasBlob(name);\n    }\n    return false;\n  }\n\n  void PrintBlobSizes();\n\n  /**\n   * Creates a blob of the given name. The pointer to the blob is returned, but\n   * the workspace keeps ownership of the pointer. If a blob of the given name\n   * already exists, the creation is skipped and the existing blob is returned.\n   */\n  Blob* CreateBlob(const string& name);\n  /**\n   * Similar to CreateBlob(), but it creates a blob in the local workspace even\n   * if another blob with the same name already exists in the parent workspace\n   * -- in such case the new blob hides the blob in parent workspace. If a blob\n   * of the given name already exists in the local workspace, the creation is\n   * skipped and the existing blob is returned.\n   */\n  Blob* CreateLocalBlob(const string& name);\n  /**\n   * Remove the blob of the given name. Return true if removed and false if\n   * not exist.\n   * Will NOT remove from the shared workspace.\n   */\n  bool RemoveBlob(const string& name);\n  /**\n   * Gets the blob with the given name as a const pointer. If the blob does not\n   * exist, a nullptr is returned.\n   */\n  const Blob* GetBlob(const string& name) const;\n  /**\n   * Gets the blob with the given name as a mutable pointer. If the blob does\n   * not exist, a nullptr is returned.\n   */\n  Blob* GetBlob(const string& name);\n\n  /**\n   * Renames a local workspace blob. If blob is not found in the local blob list\n   * or if the target name is already present in local or any parent blob list\n   * the function will through.\n   */\n  Blob* RenameBlob(const string& old_name, const string& new_name);\n\n  /**\n   * Creates a network with the given NetDef, and returns the pointer to the\n   * network. If there is anything wrong during the creation of the network, a\n   * nullptr is returned. The Workspace keeps ownership of the pointer.\n   *\n   * If there is already a net created in the workspace with the given name,\n   * CreateNet will overwrite it if overwrite=true is specified. Otherwise, an\n   * exception is thrown.\n   */\n  NetBase* CreateNet(const NetDef& net_def, bool overwrite = false);\n  NetBase* CreateNet(\n      const std::shared_ptr<const NetDef>& net_def,\n      bool overwrite = false);\n  /**\n   * Gets the pointer to a created net. The workspace keeps ownership of the\n   * network.\n   */\n  NetBase* GetNet(const string& net_name);\n  /**\n   * Deletes the instantiated network with the given name.\n   */\n  void DeleteNet(const string& net_name);\n  /**\n   * Finds and runs the instantiated network with the given name. If the network\n   * does not exist or there are errors running the network, the function\n   * returns false.\n   */\n  bool RunNet(const string& net_name);\n\n  /**\n   * Returns a list of names of the currently instantiated networks.\n   */\n  vector<string> Nets() const {\n    vector<string> names;\n    for (auto& entry : net_map_) {\n      names.push_back(entry.first);\n    }\n    return names;\n  }\n\n  /**\n   * Runs a plan that has multiple nets and execution steps.\n   */\n  bool RunPlan(const PlanDef& plan_def,\n               ShouldContinue should_continue = StopOnSignal{});\n\n  /*\n   * Returns a CPU threadpool instace for parallel execution of\n   * work. The threadpool is created lazily; if no operators use it,\n   * then no threadpool will be created.\n   */\n  ThreadPool* GetThreadPool();\n\n  // RunOperatorOnce and RunNetOnce runs an operator or net once. The difference\n  // between RunNet and RunNetOnce lies in the fact that RunNet allows you to\n  // have a persistent net object, while RunNetOnce creates a net and discards\n  // it on the fly - this may make things like database read and random number\n  // generators repeat the same thing over multiple calls.\n  bool RunOperatorOnce(const OperatorDef& op_def);\n  bool RunNetOnce(const NetDef& net_def);\n\n public:\n  std::atomic<int> last_failed_op_net_position;\n\n private:\n  BlobMap blob_map_;\n  NetMap net_map_;\n  const string root_folder_;\n  const Workspace* shared_;\n  std::unordered_map<string, std::pair<const Workspace*, string>>\n      forwarded_blobs_;\n  std::unique_ptr<ThreadPool> thread_pool_;\n  std::mutex thread_pool_creation_mutex_;\n\n  DISABLE_COPY_AND_ASSIGN(Workspace);\n};\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_CORE_WORKSPACE_H_\n"
  },
  {
    "path": "caffe2/core/workspace_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n\n#include \"caffe2/core/operator.h\"\n#include <gtest/gtest.h>\n\n\nnamespace caffe2 {\n\nclass WorkspaceTestFoo {};\n\nCAFFE_KNOWN_TYPE(WorkspaceTestFoo);\n\nTEST(WorkspaceTest, BlobAccess) {\n  Workspace ws;\n\n  EXPECT_FALSE(ws.HasBlob(\"nonexisting\"));\n  EXPECT_EQ(ws.GetBlob(\"nonexisting\"), nullptr);\n\n  EXPECT_EQ(ws.GetBlob(\"newblob\"), nullptr);\n  EXPECT_NE(nullptr, ws.CreateBlob(\"newblob\"));\n  EXPECT_NE(nullptr, ws.GetBlob(\"newblob\"));\n  EXPECT_TRUE(ws.HasBlob(\"newblob\"));\n\n  // Different names should still be not created.\n  EXPECT_FALSE(ws.HasBlob(\"nonexisting\"));\n  EXPECT_EQ(ws.GetBlob(\"nonexisting\"), nullptr);\n\n  // Check if the returned Blob is OK for all operations\n  Blob* blob = ws.GetBlob(\"newblob\");\n  int* int_unused CAFFE2_UNUSED = blob->GetMutable<int>();\n  EXPECT_TRUE(blob->IsType<int>());\n  EXPECT_FALSE(blob->IsType<WorkspaceTestFoo>());\n  EXPECT_NE(&blob->Get<int>(), nullptr);\n\n  // Re-creating the blob does not change the content as long as it already\n  // exists.\n  EXPECT_NE(nullptr, ws.CreateBlob(\"newblob\"));\n  EXPECT_TRUE(blob->IsType<int>());\n  EXPECT_FALSE(blob->IsType<WorkspaceTestFoo>());\n  // When not null, we should only call with the right type.\n  EXPECT_NE(&blob->Get<int>(), nullptr);\n\n  // Re-creating the blob through CreateLocalBlob does not change the content\n  // either.\n  EXPECT_NE(nullptr, ws.CreateLocalBlob(\"newblob\"));\n  EXPECT_TRUE(blob->IsType<int>());\n  EXPECT_NE(&blob->Get<int>(), nullptr);\n\n  // test removing blob\n  EXPECT_FALSE(ws.HasBlob(\"nonexisting\"));\n  EXPECT_FALSE(ws.RemoveBlob(\"nonexisting\"));\n  EXPECT_TRUE(ws.HasBlob(\"newblob\"));\n  EXPECT_TRUE(ws.RemoveBlob(\"newblob\"));\n  EXPECT_FALSE(ws.HasBlob(\"newblob\"));\n}\n\nTEST(WorkspaceTest, RunEmptyPlan) {\n  PlanDef plan_def;\n  Workspace ws;\n  EXPECT_TRUE(ws.RunPlan(plan_def));\n}\n\nTEST(WorkspaceTest, Sharing) {\n  Workspace parent;\n  EXPECT_FALSE(parent.HasBlob(\"a\"));\n  EXPECT_TRUE(parent.CreateBlob(\"a\"));\n  EXPECT_TRUE(parent.GetBlob(\"a\"));\n  {\n    Workspace child(&parent);\n    // Child can access parent blobs\n    EXPECT_TRUE(child.HasBlob(\"a\"));\n    EXPECT_TRUE(child.GetBlob(\"a\"));\n    // Child can create local blobs\n    EXPECT_FALSE(child.HasBlob(\"b\"));\n    EXPECT_FALSE(child.GetBlob(\"b\"));\n    EXPECT_TRUE(child.CreateBlob(\"b\"));\n    EXPECT_TRUE(child.GetBlob(\"b\"));\n    // Parent cannot access child blobs\n    EXPECT_FALSE(parent.GetBlob(\"b\"));\n    EXPECT_FALSE(parent.HasBlob(\"b\"));\n    // Parent can create duplicate names\n    EXPECT_TRUE(parent.CreateBlob(\"b\"));\n    // But child has local overrides\n    EXPECT_NE(child.GetBlob(\"b\"), parent.GetBlob(\"b\"));\n    // Child can create a blob that already exists in the parent\n    EXPECT_TRUE(child.CreateBlob(\"a\"));\n    EXPECT_EQ(child.GetBlob(\"a\"), parent.GetBlob(\"a\"));\n    // Child can create a local blob for the blob already exists in the parent\n    EXPECT_TRUE(child.CreateLocalBlob(\"a\"));\n    // But the local blob will be different from the one in parent workspace\n    EXPECT_NE(child.GetBlob(\"a\"), parent.GetBlob(\"a\"));\n  }\n}\n\nTEST(WorkspaceTest, BlobMapping) {\n  Workspace parent;\n  EXPECT_FALSE(parent.HasBlob(\"a\"));\n  EXPECT_TRUE(parent.CreateBlob(\"a\"));\n  EXPECT_TRUE(parent.GetBlob(\"a\"));\n  {\n    std::unordered_map<string, string> forwarded_blobs;\n    forwarded_blobs[\"inner_a\"] = \"a\";\n    Workspace child(&parent, forwarded_blobs);\n    EXPECT_FALSE(child.HasBlob(\"a\"));\n    EXPECT_TRUE(child.HasBlob(\"inner_a\"));\n    EXPECT_TRUE(child.GetBlob(\"inner_a\"));\n    Workspace ws;\n    EXPECT_TRUE(ws.CreateBlob(\"b\"));\n    forwarded_blobs.clear();\n    forwarded_blobs[\"inner_b\"] = \"b\";\n    child.AddBlobMapping(&ws, forwarded_blobs);\n    EXPECT_FALSE(child.HasBlob(\"b\"));\n    EXPECT_TRUE(child.HasBlob(\"inner_b\"));\n    EXPECT_TRUE(child.GetBlob(\"inner_b\"));\n  }\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/cuda_rtc/CMakeLists.txt",
    "content": "if(USE_CUDA)\n    set(Caffe2_CUDA_RTC_GPU_SRC\n        \"${CMAKE_CURRENT_SOURCE_DIR}/elemenntwise_rtc_gpu.cc\"\n        \"${CMAKE_CURRENT_SOURCE_DIR}/pool_op_rtc_gpu.cc\"\n    )\n\n    set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${Caffe2_CUDA_RTC_GPU_SRC})\n    set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nelse()\n    message(STATUS \"CUDA RTC operators skipped due to no CUDA support\")\nendif()\n"
  },
  {
    "path": "caffe2/cuda_rtc/common_rtc.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CUDA_RTC_COMMON_RTC_H_\n#define CAFFE2_CUDA_RTC_COMMON_RTC_H_\n\n#include <sstream>\n#include <string>\n\n#include <cuda.h>\n#include <nvrtc.h>\n\n#define NVRTC_CHECK(condition)                                                 \\\n  do {                                                                         \\\n    nvrtcResult result = condition;                                            \\\n    if (result != NVRTC_SUCCESS) {                                             \\\n      LOG(FATAL) << \"Error at: \" << __FILE__ << \":\" << __LINE__ << \": \"   \\\n                      << nvrtcGetErrorString(result);                          \\\n    }                                                                          \\\n  } while(0)\n\nnamespace caffe2 {\n\ntemplate <typename Derived>\nclass CudaRTCFunction {\n public:\n  CudaRTCFunction() : module_loaded_(false) {}\n  ~CudaRTCFunction() {\n    if (module_loaded_) {\n      CUDA_DRIVERAPI_ENFORCE(cuModuleUnload(module_));\n    }\n  }\n\n  // TODO: this function is nontrivial and since CudaRTCFunction uses CRTP, it\n  // may potentially increase the binary size. In that case, move common parts\n  // into a separate function.\n  template <typename... Args>\n  void Compile(Args... args) {\n    string src = static_cast<Derived*>(this)->GetSource(args...);\n    string name = static_cast<Derived*>(this)->KernelName(args...);\n    VLOG(1) << \"function name: \" << name;\n    VLOG(1) << \"function src:\\n\" << src;\n    // Actually do the compiling.\n    nvrtcProgram prog;\n    NVRTC_CHECK(nvrtcCreateProgram(\n        &prog, src.c_str(), nullptr, 0, nullptr, nullptr));\n    // Compile the program.\n    // TODO(Yangqing): how to find the current gpu architecture instead of hard\n    // coding it?\n    const char *nvrtc_opts[] = {\"--gpu-architecture=compute_35\",\n                                \"--use_fast_math\"};\n    nvrtcResult compile_result = nvrtcCompileProgram(\n        prog, 2, nvrtc_opts);\n    if (compile_result != NVRTC_SUCCESS) {\n      size_t log_size;\n      NVRTC_CHECK(nvrtcGetProgramLogSize(prog, &log_size));\n      vector<char> nvrtc_log(log_size);\n      NVRTC_CHECK(nvrtcGetProgramLog(prog, nvrtc_log.data()));\n      LOG(FATAL) << \"Compilation failure for nvrtc(\"\n                 << nvrtcGetErrorString(compile_result) << \"): \\n\"\n                 << nvrtc_log.data();\n    }\n    size_t ptx_size;\n    NVRTC_CHECK(nvrtcGetPTXSize(prog, &ptx_size));\n    vector<char> nvrtc_ptx(ptx_size);\n    NVRTC_CHECK(nvrtcGetPTX(prog, nvrtc_ptx.data()));\n    NVRTC_CHECK(nvrtcDestroyProgram(&prog));\n    // After compilation, load the module.\n    if (module_loaded_) {\n      CUDA_DRIVERAPI_ENFORCE(cuModuleUnload(module_));\n    }\n    CUDA_DRIVERAPI_ENFORCE(\n        cuModuleLoadDataEx(&module_, nvrtc_ptx.data(), 0, 0, 0));\n    module_loaded_ = true;\n    CUDA_DRIVERAPI_ENFORCE(\n        cuModuleGetFunction(&kernel_, module_, name.c_str()));\n  }\n\n  template <typename... Args>\n  void Launch(unsigned int gx, unsigned int gy, unsigned int gz,\n              unsigned int bx, unsigned int by, unsigned int bz,\n              unsigned int shared_mem, cudaStream_t stream,\n              Args... args) {\n    CAFFE_ENFORCE(\n        module_loaded_, \"Cannot call Launch before a module is loaded.\");\n    void * args_voidp[] = {&args...};\n    CUDA_DRIVERAPI_ENFORCE(cuLaunchKernel(\n        kernel_, gx, gy, gz, bx, by, bz, shared_mem, stream, args_voidp, 0));\n  }\n\n  void LaunchEx(unsigned int gx, unsigned int gy, unsigned int gz,\n                unsigned int bx, unsigned int by, unsigned int bz,\n                unsigned int shared_mem, cudaStream_t stream,\n                void** extra) {\n    CAFFE_ENFORCE(\n        module_loaded_, \"Cannot call Launch before a module is loaded.\");\n    CUDA_DRIVERAPI_ENFORCE(cuLaunchKernel(\n        kernel_, gx, gy, gz, bx, by, bz, shared_mem, stream, nullptr, extra));\n  }\n\n private:\n  bool module_loaded_;\n  CUmodule module_;\n  CUfunction kernel_;\n};\n\n// TODO: this is in no way unique and is just a hack right now.\ninline std::string GetUniqueName() {\n  static constexpr int len = 20;\n  static const char alpha[] =\n      \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\n  std::stringstream ss;\n  ss << \"_cuda_kernel_\";\n  for (int i = 0; i < len; ++i) {\n    ss << alpha[rand() % (sizeof(alpha) - 1)];\n  }\n  return ss.str();\n}\n\n}  // namepsace caffe2\n\n#endif  // CAFFE2_CUDA_RTC_COMMON_RTC_H_\n"
  },
  {
    "path": "caffe2/cuda_rtc/elemenntwise_rtc_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/cuda_rtc/common_rtc.h\"\n\nnamespace caffe2 {\nnamespace {\nclass ElementwiseRTCFunction\n    : public CudaRTCFunction<ElementwiseRTCFunction> {\n public:\n  ElementwiseRTCFunction() : CudaRTCFunction(), name_(GetUniqueName()) {}\n\n  template <typename... Args>\n  string KernelName(Args... /*args*/) {\n    return name_;\n  }\n\n  template <typename... Args>\n  string GetSource(Args... args);\n\n private:\n  string name_;\n};\n\ntemplate<>\nstring ElementwiseRTCFunction::GetSource(\n    int input_size, int output_size,\n    const string command_string) {\n  std::stringstream ss;\n  ss << \"extern \\\"C\\\" __global__ void \" << name_ <<\n        \"(const size_t nthreads, \\n\";\n  // Insert the parameter list.\n  int remain_params = input_size + output_size;\n  for (int i = 0; i < input_size; ++i) {\n    ss << \"const float* in\" << i\n       << ((remain_params--) ? \", \\n\" : \"\");\n  }\n  for (int i = 0; i < output_size; ++i) {\n    ss << \"float* out\" << i\n       << ((remain_params--) ? \", \\n\" : \"\");\n  }\n  ss << \") {\\n\"\n        \"for (int index = blockIdx.x * blockDim.x + threadIdx.x;\\n\"\n        \"index < nthreads; index += blockDim.x * gridDim.x) {\\n\"\n     << command_string << \"\\n\"\n     << \"}\\n}\";\n  return ss.str();\n}\n}  // namespace\n\n/**\n * A GPU operator that can generate limited elementwise operations.\n *\n * ElementwiseRTCOp allows one to do a simple and limited thing: it takes in\n * multiple inputs and multiple outputs, as well as a raw string argument\n * rtc_src. The runtime then generates the following kernel code:\n *\n *   __global__ void kernel_name(const size_t nthreads, ...) {\n *     for(int index = blockIdx.x * blockDim.x + threadIdx.x;\n *         index < nthreads; index += blockDim.x * gridDim.x) {\n *       rtc_src\n *     }\n *   }\n * where the \"...\" part is auto generated, so one can refer to the input and\n * output as in0, in1, ..., out0, out1... in the rtc_src string.\n *\n * For example, if one wants to do a vector multiplication, one can take two\n * inputs and one outputs, and write rtc_src as\n *     out0[index] = in0[index] * in1[index];\n *\n * This op is currently highly experimental. We do not have a gradient\n * registered for it either.\n */\nclass ElementwiseRTCOp final : public Operator<CUDAContext> {\n public:\n  ElementwiseRTCOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws) {\n    const string src = OperatorBase::GetSingleArgument<string>(\n        \"rtc_src\", \"\");\n    CAFFE_ENFORCE(src.size(), \"Op should have a non-zero source code size.\");\n    func_.Compile(InputSize(), OutputSize(), src);\n  }\n  ~ElementwiseRTCOp() {}\n\n  bool RunOnDevice() override {\n    static_assert(sizeof(void*) == sizeof(size_t),\n                  \"The argbuffer relies on the assumption that void* and \"\n                  \"size_t have the same size.\");\n    vector<size_t> argBuffer_vec(InputSize() + OutputSize() + 1);\n    size_t* argBuffer = argBuffer_vec.data();\n    CAFFE_ENFORCE(\n        Input(0).size() < std::numeric_limits<int>::max(),\n        \"The kernel function currently only supports int index.\");\n    argBuffer[0] = Input(0).size();\n    void** ptr_buffer = reinterpret_cast<void**>(argBuffer + 1);\n    for (int i = 0; i < InputSize(); ++i) {\n      ptr_buffer[i] = const_cast<float*>(Input(i).data<float>());\n    }\n    for (int i = 0; i < OutputSize(); ++i) {\n      Output(i)->ResizeLike(Input(0));\n      ptr_buffer[i + InputSize()] = Output(i)->mutable_data<float>();\n    }\n    size_t argBufferSize = sizeof(argBuffer);\n    void* config[] = {\n      CU_LAUNCH_PARAM_BUFFER_POINTER, argBuffer,\n      CU_LAUNCH_PARAM_BUFFER_SIZE, &argBufferSize,\n      CU_LAUNCH_PARAM_END\n    };\n    func_.LaunchEx(CAFFE_GET_BLOCKS(Input(0).size()), 1, 1,\n                   CAFFE_CUDA_NUM_THREADS, 1, 1,\n                   0, context_.cuda_stream(), config);\n    return true;\n  }\n\n private:\n  ElementwiseRTCFunction func_;\n};\n\nnamespace {\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(ElementwiseRTC, NVRTC, ElementwiseRTCOp);\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/cuda_rtc/pool_op_rtc_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdio>\n\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/pool_op.h\"\n#include \"caffe2/cuda_rtc/common_rtc.h\"\n\nnamespace caffe2 {\nnamespace {\nclass AveragePool {};\nclass MaxPool {};\n}  // namespace\n\nnamespace {\n\n// The max pool forward function, with parameters written in const int.\nconst char kMaxPoolForwardNCHWSource[] = R\"(\nextern \"C\"\n__global__ void %s(const float* bottom_data, float* top_data) {\n  const int nthreads = %d;\n  const int channels = %d;\n  const int height = %d;\n  const int width = %d;\n  const int pooled_height = %d;\n  const int pooled_width = %d;\n  const int kernel_h = %d;\n  const int kernel_w = %d;\n  const int stride_h = %d;\n  const int stride_w = %d;\n  const int pad_t = %d;\n  const int pad_l = %d;\n  for (int index = blockIdx.x * blockDim.x + threadIdx.x;\n       index < nthreads; index += blockDim.x * gridDim.x) {\n    int pw = index %% pooled_width;\n    int ph = (index / pooled_width) %% pooled_height;\n    int c = (index / (pooled_width * pooled_height)) %% channels;\n    int n = index / (pooled_width * pooled_height * channels);\n    int hstart = ph * stride_h - pad_t;\n    int wstart = pw * stride_w - pad_l;\n    int hend = min(hstart + kernel_h, height);\n    int wend = min(wstart + kernel_w, width);\n    hstart = max(hstart, 0);\n    wstart = max(wstart, 0);\n    float maxval = -1.0e37f;\n    const float* bdata_offset = bottom_data + n * channels * height * width;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        maxval = fmaxf(\n            bdata_offset[c * height * width + h * width + w], maxval);\n      }\n    }\n    top_data[index] = maxval;\n  }\n}\n)\";\n\n// The max pool forward function, with parameters written in const int.\nconst char kMaxPoolBackwardNCHWSource[] = R\"(\nextern \"C\"\n__global__ void %s(\n    const float* const bottom_data, const float* const top_data,\n    const float* const top_diff, float* const bottom_diff) {\n  const int nthreads = %d;\n  const int num = %d;\n  const int channels = %d;\n  const int height = %d;\n  const int width = %d;\n  const int pooled_height = %d;\n  const int pooled_width = %d;\n  const int kernel_h = %d;\n  const int kernel_w = %d;\n  const int stride_h = %d;\n  const int stride_w = %d;\n  const int pad_t = %d;\n  const int pad_l = %d;\n  for (int index = blockIdx.x * blockDim.x + threadIdx.x;\n       index < nthreads; index += blockDim.x * gridDim.x) {\n    const int w = index %% width + pad_l;\n    const int h = (index / width) %% height + pad_t;\n    const int c = (index / width / height) %% channels;\n    const int n = index / width / height / channels;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    const int pwstart = (w < kernel_w) ? 0 : (w - kernel_w) / stride_w + 1;\n    const int pwend = min(w / stride_w + 1, pooled_width);\n    const int top_offset =\n        (n * channels + c) * pooled_height * pooled_width;\n    bottom_diff[index] = 0;\n    for (int ph = phstart; ph < phend; ++ph) {\n      for (int pw = pwstart; pw < pwend; ++pw) {\n        int top_local_offset = top_offset + ph * pooled_width + pw;\n        if (bottom_data[index] == top_data[top_local_offset]) {\n          bottom_diff[index] += top_diff[top_local_offset];\n        }\n      }\n    }\n  }\n}\n)\";\n\n\nclass MaxPoolRTCFunction : public CudaRTCFunction<MaxPoolRTCFunction> {\n public:\n  MaxPoolRTCFunction() : CudaRTCFunction(), name_(GetUniqueName()) {}\n\n  template <typename... Args>\n  string KernelName(Args... /*args*/) {\n    return name_;\n  }\n\n  template <typename... Args>\n  string GetSource(Args... args);\n\n private:\n  string name_;\n};\n\nclass MaxPoolGradientRTCFunction\n    : public CudaRTCFunction<MaxPoolGradientRTCFunction> {\n public:\n  MaxPoolGradientRTCFunction() : CudaRTCFunction(), name_(GetUniqueName()) {}\n\n  template <typename... Args>\n  string KernelName(Args... /*args*/) {\n    return name_;\n  }\n\n  template <typename... Args>\n  string GetSource(Args... args);\n\n private:\n  string name_;\n};\n\n\ntemplate <>\nstring MaxPoolRTCFunction::GetSource(\n    const int output_size,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l) {\n  char buffer[65536];\n  int nbytes = snprintf(\n      buffer, 65536, kMaxPoolForwardNCHWSource, name_.c_str(), output_size,\n      channels, height, width, pooled_height, pooled_width, kernel_h, kernel_w,\n      stride_h, stride_w, pad_t, pad_l);\n  DCHECK_GE(nbytes, 0);\n  DCHECK_LT(nbytes, 65536);\n  return string(buffer);\n}\n\ntemplate <>\nstring MaxPoolGradientRTCFunction::GetSource(\n    const int output_size,\n    const int num,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l) {\n  char buffer[65536];\n  int nbytes = snprintf(\n      buffer, 65536, kMaxPoolBackwardNCHWSource, name_.c_str(), output_size,\n      num, channels, height, width, pooled_height, pooled_width, kernel_h,\n      kernel_w, stride_h, stride_w, pad_t, pad_l);\n  DCHECK_GE(nbytes, 0);\n  DCHECK_LT(nbytes, 65536);\n  return string(buffer);\n}\n\n}  // namespace\n\n\nclass MaxPoolRTCOp final : public ConvPoolOpBase<CUDAContext> {\n public:\n  MaxPoolRTCOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CUDAContext>(operator_def, ws) {\n    CAFFE_ENFORCE_EQ(\n        order_, StorageOrder::NCHW, \"Currently only NCHW is supported.\");\n  }\n  ~MaxPoolRTCOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n    ConvPoolOpBase::SetOutputSize(X, Y, X.dim32(1));\n\n    if (input_dims_ != X.dims()) {\n      // recompile\n      VLOG(1) << \"MaxPool RTC recompiling\";\n      CAFFE_ENFORCE_LT(Y->size(), std::numeric_limits<int>::max());\n      func_.Compile(\n          static_cast<int>(Y->size()),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          Y->dim32(2),\n          Y->dim32(3),\n          kernel_h(),\n          kernel_w(),\n          stride_h(),\n          stride_w(),\n          pad_t(),\n          pad_l());\n      input_dims_ = X.dims();\n    }\n    // Carry out the pooling computation.\n    func_.Launch(CAFFE_GET_BLOCKS(Y->size()), 1, 1, CAFFE_CUDA_NUM_THREADS,\n                 1, 1, 0, context_.cuda_stream(),\n                 X.data<float>(), Y->mutable_data<float>());\n    return true;\n  }\n\n  bool RunOnDeviceWithOrderNHWC() override {\n    LOG(FATAL) << \"Not implemented.\";\n    return false;\n  }\n\n private:\n  MaxPoolRTCFunction func_;\n  vector<TIndex> input_dims_;\n};\n\nclass MaxPoolGradientRTCOp final : public ConvPoolOpBase<CUDAContext> {\n public:\n  MaxPoolGradientRTCOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CUDAContext>(operator_def, ws) {\n    CAFFE_ENFORCE_EQ(\n        order_, StorageOrder::NCHW, \"Currently only NCHW is supported.\");\n  }\n  ~MaxPoolGradientRTCOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    auto& X = Input(0);\n    auto& Y = Input(1);\n    auto& dY = Input(2);\n    CAFFE_ENFORCE_EQ(dY.ndim(), 4);\n    auto* dX = Output(0);\n    dX->ResizeLike(X);\n    ConvPoolOpBase<CUDAContext>::ComputePads({X.dim32(2), X.dim32(3)});\n    if (input_dims_ != X.dims()) {\n      VLOG(1) << \"MaxPoolGradient RTC recompiling\";\n      CAFFE_ENFORCE_LT(X.size(), std::numeric_limits<int>::max());\n      func_.Compile(\n          static_cast<int>(X.size()),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          dY.dim32(2),\n          dY.dim32(3),\n          kernel_h(),\n          kernel_w(),\n          stride_h(),\n          stride_w(),\n          pad_t(),\n          pad_l());\n      input_dims_ = X.dims();\n    }\n    func_.Launch(CAFFE_GET_BLOCKS(X.size()), 1, 1, CAFFE_CUDA_NUM_THREADS, 1, 1,\n                 0, context_.cuda_stream(),\n                 X.data<float>(), Y.data<float>(), dY.data<float>(),\n                 dX->mutable_data<float>());\n    return true;\n  }\n\n  bool RunOnDeviceWithOrderNHWC() override {\n    LOG(FATAL) << \"Not implemented.\";\n    return false;\n  }\n\n private:\n  MaxPoolGradientRTCFunction func_;\n  vector<TIndex> input_dims_;\n};\n\nnamespace {\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(MaxPool, NVRTC, MaxPoolRTCOp);\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(MaxPoolGradient, NVRTC,\n                                   MaxPoolGradientRTCOp);\n}  // namespace\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/db/CMakeLists.txt",
    "content": "set(Caffe2_DB_COMMON_CPU_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/create_db_op.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/protodb.cc\"\n)\nset(Caffe2_DB_COMMON_GPU_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/create_db_op_gpu.cc\"\n)\n\n# Common files that are always going to be included.\nlist(APPEND Caffe2_CPU_SRCS ${Caffe2_DB_COMMON_CPU_SRC})\nlist(APPEND Caffe2_GPU_SRCS ${Caffe2_DB_COMMON_GPU_SRC})\n\n# DB specific files\nif (USE_LMDB)\n  list(APPEND Caffe2_CPU_SRCS \"${CMAKE_CURRENT_SOURCE_DIR}/lmdb.cc\")\nendif()\n\nif (USE_LEVELDB)\n  list(APPEND Caffe2_CPU_SRCS \"${CMAKE_CURRENT_SOURCE_DIR}/leveldb.cc\")\nendif()\n\nif (USE_ZMQ)\n  list(APPEND Caffe2_CPU_SRCS \"${CMAKE_CURRENT_SOURCE_DIR}/zmqdb.cc\")\nendif()\n\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/db/create_db_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/db/create_db_op.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(CreateDB, CreateDBOp<CPUContext>);\n\nOPERATOR_SCHEMA(CreateDB).NumInputs(0).NumOutputs(1);\n\nNO_GRADIENT(CreateDB);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/db/create_db_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_DB_CREATE_DB_OP_H_\n#define CAFFE2_DB_CREATE_DB_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass CreateDBOp final : public Operator<Context> {\n public:\n  CreateDBOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        db_type_(OperatorBase::template GetSingleArgument<string>(\n            \"db_type\",\n            \"leveldb\")),\n        db_name_(OperatorBase::template GetSingleArgument<string>(\"db\", \"\")),\n        num_shards_(\n            OperatorBase::template GetSingleArgument<int>(\"num_shards\", 1)),\n        shard_id_(\n            OperatorBase::template GetSingleArgument<int>(\"shard_id\", 0)) {\n    CAFFE_ENFORCE_GT(db_name_.size(), 0, \"Must specify a db name.\");\n  }\n\n  bool RunOnDevice() final {\n    OperatorBase::Output<db::DBReader>(0)->Open(\n        db_type_, db_name_, num_shards_, shard_id_);\n    return true;\n  }\n\n private:\n  string db_type_;\n  string db_name_;\n  uint32_t num_shards_;\n  uint32_t shard_id_;\n  DISABLE_COPY_AND_ASSIGN(CreateDBOp);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_DB_CREATE_DB_OP_H_\n"
  },
  {
    "path": "caffe2/db/create_db_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/db/create_db_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(CreateDB, CreateDBOp<CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/db/db_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdio>\n#include <iomanip>\n#include <sstream>\n#include <thread>\n\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\nnamespace db {\n\nconstexpr int kMaxItems = 10;\n\nstatic bool CreateAndFill(const string& db_type, const string& name) {\n  VLOG(1) << \"Creating db: \" << name;\n  std::unique_ptr<DB> db(CreateDB(db_type, name, NEW));\n  if (!db.get()) {\n    LOG(ERROR) << \"Cannot create db of type \" << db_type;\n    return false;\n  }\n  std::unique_ptr<Transaction> trans(db->NewTransaction());\n  for (int i = 0; i < kMaxItems; ++i) {\n    std::stringstream ss;\n    ss << std::setw(2) << std::setfill('0') << i;\n    trans->Put(ss.str(), ss.str());\n  }\n  trans->Commit();\n  trans.reset();\n  db.reset();\n  return true;\n}\n\nstatic void TestCursor(Cursor* cursor) {\n  // Test the first key.\n  cursor->SeekToFirst();\n  EXPECT_EQ(cursor->key(), \"00\");\n  EXPECT_EQ(cursor->value(), \"00\");\n  // Test if Next() works.\n  cursor->Next();\n  EXPECT_EQ(cursor->key(), \"01\");\n  cursor->Next();\n  EXPECT_EQ(cursor->key(), \"02\");\n  // Test if we can return to the first key.\n  cursor->SeekToFirst();\n  EXPECT_EQ(cursor->key(), \"00\");\n  // Test seeking to a key that exists.\n  cursor->Seek(\"05\");\n  EXPECT_EQ(cursor->key(), \"05\");\n  // Test seeking to a key that does not exist - that should give us the\n  // immediate next key.\n  cursor->Seek(\"07.5\");\n  EXPECT_EQ(cursor->key(), \"08\");\n  // Test seeking over the end of the db - that should make the current\n  // cursor invalid.\n  cursor->Seek(\"11\");\n  EXPECT_FALSE(cursor->Valid());\n  // Test seeking to empty string, aka the beginning\n  cursor->Seek(\"\");\n  EXPECT_EQ(cursor->key(), \"00\");\n}\n\nstatic void DBSeekTestWrapper(const string& db_type) {\n  std::string name = std::tmpnam(nullptr);\n  if (!CreateAndFill(db_type, name)) {\n    // Manually fail the test, and not do anything onwards.\n    EXPECT_TRUE(0);\n  } else {\n    std::unique_ptr<DB> db(CreateDB(db_type, name, READ));\n    std::unique_ptr<Cursor> cursor(db->NewCursor());\n    TestCursor(cursor.get());\n  }\n}\n\nTEST(DBSeekTest, RocksDB) {\n  DBSeekTestWrapper(\"rocksdb\");\n}\n\nTEST(DBSeekTest, LevelDB) {\n  DBSeekTestWrapper(\"leveldb\");\n}\n\nTEST(DBSeekTest, LMDB) {\n  DBSeekTestWrapper(\"lmdb\");\n}\n\nTEST(DBReaderTest, Reader) {\n  std::string name = std::tmpnam(nullptr);\n  CreateAndFill(\"leveldb\", name);\n  std::unique_ptr<DBReader> reader(new DBReader(\"leveldb\", name));\n  EXPECT_TRUE(reader->cursor() != nullptr);\n  // DBReader should have a full-fledged cursor.\n  TestCursor(reader->cursor());\n  // Test the Read() functionality.\n  reader->cursor()->Seek(\"05\");\n  EXPECT_EQ(reader->cursor()->key(), \"05\");\n  string key;\n  string value;\n  reader->Read(&key, &value);\n  EXPECT_EQ(key, \"05\");\n  EXPECT_EQ(value, \"05\");\n  reader->Read(&key, &value);\n  EXPECT_EQ(key, \"06\");\n  EXPECT_EQ(value, \"06\");\n\n  // Test if we are able to serialize it using the blob serialization\n  // interface.\n  reader->cursor()->Seek(\"05\");\n  EXPECT_EQ(reader->cursor()->key(), \"05\");\n  Blob reader_blob;\n  reader_blob.Reset(reader.release());\n  std::string str = reader_blob.Serialize(\"saved_reader\");\n  // Release to close the old reader.\n  reader_blob.Reset();\n  BlobProto blob_proto;\n  CHECK(blob_proto.ParseFromString(str));\n  EXPECT_EQ(blob_proto.name(), \"saved_reader\");\n  EXPECT_EQ(blob_proto.type(), \"DBReader\");\n  DBReaderProto proto;\n  CHECK(proto.ParseFromString(blob_proto.content()));\n  EXPECT_EQ(proto.source(), name);\n  EXPECT_EQ(proto.db_type(), \"leveldb\");\n  EXPECT_EQ(proto.key(), \"05\");\n  // Test restoring the reader from the serialized proto.\n  EXPECT_NO_THROW(reader_blob.Deserialize(str));\n  EXPECT_TRUE(reader_blob.IsType<DBReader>());\n  const DBReader& new_reader = reader_blob.Get<DBReader>();\n  EXPECT_TRUE(new_reader.cursor() != nullptr);\n  EXPECT_EQ(new_reader.cursor()->key(), \"05\");\n\n  // Test Reader's multi-threading capability.\n  vector<unique_ptr<std::thread>> threads(kMaxItems);\n  vector<string> keys(kMaxItems);\n  vector<string> values(kMaxItems);\n  for (int i = 0; i < kMaxItems; ++i) {\n    threads[i].reset(new std::thread(\n        [&new_reader](string* key, string* value) {\n          new_reader.Read(key, value);\n        },\n        &keys[i], &values[i]));\n  }\n  for (int i = 0; i < kMaxItems; ++i) {\n    threads[i]->join();\n    EXPECT_TRUE(keys[i].size() > 0);\n  }\n  // Check if the names are all unique by putting them into a set and\n  // checking the size.\n  std::set<string> keys_set(keys.begin(), keys.end());\n  EXPECT_EQ(keys_set.size(), kMaxItems);\n}\n\nTEST(DBReaderShardedTest, Reader) {\n  std::string name = std::tmpnam(nullptr);\n  CreateAndFill(\"leveldb\", name);\n\n  std::unique_ptr<DBReader> reader0(new DBReader(\"leveldb\", name, 3, 0));\n  string key;\n  string value;\n  reader0->Read(&key, &value);\n  EXPECT_EQ(key, \"00\");\n  EXPECT_EQ(value, \"00\");\n  reader0->Read(&key, &value);\n  EXPECT_EQ(key, \"03\");\n  EXPECT_EQ(value, \"03\");\n  reader0->Read(&key, &value);\n  EXPECT_EQ(key, \"06\");\n  EXPECT_EQ(value, \"06\");\n  reader0->Read(&key, &value);\n  EXPECT_EQ(key, \"09\");\n  EXPECT_EQ(value, \"09\");\n  reader0->Read(&key, &value);\n  EXPECT_EQ(key, \"00\");\n  EXPECT_EQ(value, \"00\");\n  reader0->Read(&key, &value);\n  EXPECT_EQ(key, \"03\");\n  EXPECT_EQ(value, \"03\");\n\n  CreateAndFill(\"leveldb\", name + \"1\");\n  std::unique_ptr<DBReader> reader1(new DBReader(\"leveldb\", name + \"1\", 3, 1));\n  reader1->Read(&key, &value);\n  EXPECT_EQ(key, \"01\");\n  EXPECT_EQ(value, \"01\");\n  reader1->Read(&key, &value);\n  EXPECT_EQ(key, \"04\");\n  EXPECT_EQ(value, \"04\");\n\n  CreateAndFill(\"leveldb\", name + \"2\");\n  std::unique_ptr<DBReader> reader2(new DBReader(\"leveldb\", name + \"2\", 3, 2));\n  reader2->Read(&key, &value);\n  EXPECT_EQ(key, \"02\");\n  EXPECT_EQ(value, \"02\");\n  reader2->Read(&key, &value);\n  EXPECT_EQ(key, \"05\");\n  EXPECT_EQ(value, \"05\");\n}\n\n}  // namespace db\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/db/leveldb.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/flags.h\"\n#include \"leveldb/db.h\"\n#include \"leveldb/write_batch.h\"\n\nCAFFE2_DEFINE_int(caffe2_leveldb_block_size, 65536,\n                  \"The caffe2 leveldb block size when writing a leveldb.\");\n\nnamespace caffe2 {\nnamespace db {\n\nclass LevelDBCursor : public Cursor {\n public:\n  explicit LevelDBCursor(leveldb::DB* db)\n      : iter_(db->NewIterator(leveldb::ReadOptions())) {\n    SeekToFirst();\n  }\n  ~LevelDBCursor() {}\n  void Seek(const string& key) override { iter_->Seek(key); }\n  bool SupportsSeek() override { return true; }\n  void SeekToFirst() override { iter_->SeekToFirst(); }\n  void Next() override { iter_->Next(); }\n  string key() override { return iter_->key().ToString(); }\n  string value() override { return iter_->value().ToString(); }\n  bool Valid() override { return iter_->Valid(); }\n\n private:\n  std::unique_ptr<leveldb::Iterator> iter_;\n};\n\nclass LevelDBTransaction : public Transaction {\n public:\n  explicit LevelDBTransaction(leveldb::DB* db) : db_(db) {\n    CAFFE_ENFORCE(db_);\n    batch_.reset(new leveldb::WriteBatch());\n  }\n  ~LevelDBTransaction() { Commit(); }\n  void Put(const string& key, const string& value) override {\n    batch_->Put(key, value);\n  }\n  void Commit() override {\n    leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch_.get());\n    batch_.reset(new leveldb::WriteBatch());\n    CAFFE_ENFORCE(\n        status.ok(),\n        \"Failed to write batch to leveldb. \", status.ToString());\n  }\n\n private:\n  leveldb::DB* db_;\n  std::unique_ptr<leveldb::WriteBatch> batch_;\n\n  DISABLE_COPY_AND_ASSIGN(LevelDBTransaction);\n};\n\nclass LevelDB : public DB {\n public:\n  LevelDB(const string& source, Mode mode) : DB(source, mode) {\n    leveldb::Options options;\n    options.block_size = FLAGS_caffe2_leveldb_block_size;\n    options.write_buffer_size = 268435456;\n    options.max_open_files = 100;\n    options.error_if_exists = mode == NEW;\n    options.create_if_missing = mode != READ;\n    leveldb::DB* db_temp;\n    leveldb::Status status = leveldb::DB::Open(options, source, &db_temp);\n    CAFFE_ENFORCE(\n        status.ok(),\n        \"Failed to open leveldb \", source, \". \", status.ToString());\n    db_.reset(db_temp);\n    VLOG(1) << \"Opened leveldb \" << source;\n  }\n\n  void Close() override { db_.reset(); }\n  unique_ptr<Cursor> NewCursor() override {\n    return make_unique<LevelDBCursor>(db_.get());\n  }\n  unique_ptr<Transaction> NewTransaction() override {\n    return make_unique<LevelDBTransaction>(db_.get());\n  }\n\n private:\n  std::unique_ptr<leveldb::DB> db_;\n};\n\nREGISTER_CAFFE2_DB(LevelDB, LevelDB);\n// For lazy-minded, one can also call with lower-case name.\nREGISTER_CAFFE2_DB(leveldb, LevelDB);\n\n}  // namespace db\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/db/lmdb.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"lmdb.h\"  // NOLINT\n\n#if defined(_MSC_VER)\n#include <direct.h>\n#endif\n\n#include <sys/stat.h>\n\n#include <string>\n\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\nnamespace db {\n\nconstexpr size_t LMDB_MAP_SIZE = 1099511627776;  // 1 TB\n\ninline void MDB_CHECK(int mdb_status) {\n  CAFFE_ENFORCE_EQ(mdb_status, MDB_SUCCESS, mdb_strerror(mdb_status));\n}\n\nclass LMDBCursor : public Cursor {\n public:\n  explicit LMDBCursor(MDB_env* mdb_env)\n      : mdb_env_(mdb_env), valid_(false) {\n    MDB_CHECK(mdb_txn_begin(mdb_env_, NULL, MDB_RDONLY, &mdb_txn_));\n    MDB_CHECK(mdb_dbi_open(mdb_txn_, NULL, 0, &mdb_dbi_));\n    MDB_CHECK(mdb_cursor_open(mdb_txn_, mdb_dbi_, &mdb_cursor_));\n    SeekToFirst();\n  }\n  virtual ~LMDBCursor() {\n    mdb_cursor_close(mdb_cursor_);\n    mdb_dbi_close(mdb_env_, mdb_dbi_);\n    mdb_txn_abort(mdb_txn_);\n  }\n\n  void Seek(const string& key) override {\n    if (key.size() == 0) {\n      SeekToFirst();\n      return;\n    }\n    // a key of 16k size should be enough? I am not sure though.\n    mdb_key_.mv_size = key.size();\n    mdb_key_.mv_data = const_cast<char*>(key.c_str());\n    int mdb_status = mdb_cursor_get(\n        mdb_cursor_, &mdb_key_, &mdb_value_, MDB_SET_RANGE);\n    if (mdb_status == MDB_NOTFOUND) {\n      valid_ = false;\n    } else {\n      MDB_CHECK(mdb_status);\n      valid_ = true;\n    }\n  }\n\n  bool SupportsSeek() override { return true; }\n\n  void SeekToFirst() override { SeekLMDB(MDB_FIRST); }\n\n  void Next() override { SeekLMDB(MDB_NEXT); }\n\n  string key() override {\n    return string(static_cast<const char*>(mdb_key_.mv_data), mdb_key_.mv_size);\n  }\n\n  string value() override {\n    return string(static_cast<const char*>(mdb_value_.mv_data),\n        mdb_value_.mv_size);\n  }\n\n  bool Valid() override { return valid_; }\n\n private:\n  void SeekLMDB(MDB_cursor_op op) {\n    int mdb_status = mdb_cursor_get(mdb_cursor_, &mdb_key_, &mdb_value_, op);\n    if (mdb_status == MDB_NOTFOUND) {\n      valid_ = false;\n    } else {\n      MDB_CHECK(mdb_status);\n      valid_ = true;\n    }\n  }\n\n  MDB_env* mdb_env_;\n  MDB_txn* mdb_txn_;\n  MDB_dbi mdb_dbi_;\n  MDB_cursor* mdb_cursor_;\n  MDB_val mdb_key_, mdb_value_;\n  bool valid_;\n};\n\nclass LMDBTransaction final : public Transaction {\n public:\n  explicit LMDBTransaction(MDB_env* mdb_env)\n      : mdb_env_(mdb_env) {\n    MDB_CHECK(mdb_txn_begin(mdb_env_, NULL, 0, &mdb_txn_));\n    MDB_CHECK(mdb_dbi_open(mdb_txn_, NULL, 0, &mdb_dbi_));\n  }\n  ~LMDBTransaction() {\n    MDB_CHECK(mdb_txn_commit(mdb_txn_));\n    mdb_dbi_close(mdb_env_, mdb_dbi_);\n  }\n  void Put(const string& key, const string& value) override;\n  void Commit() override {\n    MDB_CHECK(mdb_txn_commit(mdb_txn_));\n    mdb_dbi_close(mdb_env_, mdb_dbi_);\n    // Begin a new transaction.\n    MDB_CHECK(mdb_txn_begin(mdb_env_, NULL, 0, &mdb_txn_));\n    MDB_CHECK(mdb_dbi_open(mdb_txn_, NULL, 0, &mdb_dbi_));\n  }\n\n private:\n  MDB_env* mdb_env_;\n  MDB_dbi mdb_dbi_;\n  MDB_txn* mdb_txn_;\n\n  DISABLE_COPY_AND_ASSIGN(LMDBTransaction);\n};\n\nclass LMDB : public DB {\n public:\n  LMDB(const string& source, Mode mode);\n  virtual ~LMDB() { Close(); }\n  void Close() override {\n    if (mdb_env_ != NULL) {\n      mdb_env_close(mdb_env_);\n      mdb_env_ = NULL;\n    }\n  }\n  unique_ptr<Cursor> NewCursor() override {\n    return make_unique<LMDBCursor>(mdb_env_);\n  }\n  unique_ptr<Transaction> NewTransaction() override {\n    return make_unique<LMDBTransaction>(mdb_env_);\n  }\n\n private:\n  MDB_env* mdb_env_;\n};\n\nLMDB::LMDB(const string& source, Mode mode) : DB(source, mode) {\n  MDB_CHECK(mdb_env_create(&mdb_env_));\n  MDB_CHECK(mdb_env_set_mapsize(mdb_env_, LMDB_MAP_SIZE));\n  if (mode == NEW) {\n#if defined(_MSC_VER)\n    CAFFE_ENFORCE_EQ(_mkdir(source.c_str()), 0, \"mkdir \", source, \" failed\");\n#else\n    CAFFE_ENFORCE_EQ(\n        mkdir(source.c_str(), 0744), 0, \"mkdir \", source, \" failed\");\n#endif\n  }\n  int flags = 0;\n  if (mode == READ) {\n    flags = MDB_RDONLY | MDB_NOTLS | MDB_NOLOCK;\n  }\n  MDB_CHECK(mdb_env_open(mdb_env_, source.c_str(), flags, 0664));\n  VLOG(1) << \"Opened lmdb \" << source;\n}\n\nvoid LMDBTransaction::Put(const string& key, const string& value) {\n  MDB_val mdb_key, mdb_value;\n  mdb_key.mv_data = const_cast<char*>(key.data());\n  mdb_key.mv_size = key.size();\n  mdb_value.mv_data = const_cast<char*>(value.data());\n  mdb_value.mv_size = value.size();\n  MDB_CHECK(mdb_put(mdb_txn_, mdb_dbi_, &mdb_key, &mdb_value, 0));\n}\n\nREGISTER_CAFFE2_DB(LMDB, LMDB);\nREGISTER_CAFFE2_DB(lmdb, LMDB);\n\n}  // namespace db\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/db/protodb.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <unordered_set>\n\n#include \"caffe2/core/db.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\nnamespace db {\n\nclass ProtoDBCursor : public Cursor {\n public:\n  explicit ProtoDBCursor(const TensorProtos* proto)\n    : proto_(proto), iter_(0) {}\n  ~ProtoDBCursor() {}\n\n  void Seek(const string& /*str*/) override {\n    CAFFE_THROW(\"ProtoDB is not designed to support seeking.\");\n  }\n\n  void SeekToFirst() override { iter_ = 0; }\n  void Next() override { ++iter_; }\n  string key() override { return proto_->protos(iter_).name(); }\n  string value() override { return proto_->protos(iter_).SerializeAsString(); }\n  bool Valid() override { return iter_ < proto_->protos_size(); }\n\n private:\n  const TensorProtos* proto_;\n  int iter_;\n};\n\nclass ProtoDBTransaction : public Transaction {\n public:\n  explicit ProtoDBTransaction(TensorProtos* proto)\n      : proto_(proto), existing_names_() {\n    for (const auto& tensor : proto_->protos()) {\n      existing_names_.insert(tensor.name());\n    }\n  }\n  ~ProtoDBTransaction() { Commit(); }\n  void Put(const string& key, const string& value) override {\n    if (existing_names_.count(key)) {\n      CAFFE_THROW(\"An item with key \", key, \" already exists.\");\n    }\n    auto* tensor = proto_->add_protos();\n    CAFFE_ENFORCE(\n        tensor->ParseFromString(value),\n        \"Cannot parse content from the value string.\");\n    CAFFE_ENFORCE(\n        tensor->name() == key,\n        \"Passed in key \",\n        key,\n        \" does not equal to the tensor name \",\n        tensor->name());\n  }\n  // Commit does nothing. The protocol buffer will be written at destruction\n  // of ProtoDB.\n  void Commit() override {}\n\n private:\n  TensorProtos* proto_;\n  std::unordered_set<string> existing_names_;\n\n  DISABLE_COPY_AND_ASSIGN(ProtoDBTransaction);\n};\n\nclass ProtoDB : public DB {\n public:\n  ProtoDB(const string& source, Mode mode)\n      : DB(source, mode), proto_(), source_(source) {\n    if (mode == READ || mode == WRITE) {\n      // Read the current protobuffer.\n      CAFFE_ENFORCE(\n          ReadProtoFromFile(source, &proto_), \"Cannot read protobuffer.\");\n    }\n    LOG(INFO) << \"Opened protodb \" << source;\n  }\n  ~ProtoDB() { Close(); }\n\n  void Close() override {\n    if (mode_ == NEW || mode_ == WRITE) {\n      WriteProtoToBinaryFile(proto_, source_);\n    }\n  }\n\n  unique_ptr<Cursor> NewCursor() override {\n    return make_unique<ProtoDBCursor>(&proto_);\n  }\n  unique_ptr<Transaction> NewTransaction() override {\n    return make_unique<ProtoDBTransaction>(&proto_);\n  }\n\n private:\n  TensorProtos proto_;\n  string source_;\n};\n\nREGISTER_CAFFE2_DB(ProtoDB, ProtoDB);\n// For lazy-minded, one can also call with lower-case name.\nREGISTER_CAFFE2_DB(protodb, ProtoDB);\n\n}  // namespace db\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/db/zmqdb.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <atomic>\n#include <condition_variable>\n#include <mutex>\n#include <thread>  // NOLINT\n\n#include \"caffe2/core/db.h\"\n#include \"caffe2/utils/zmq_helper.h\"\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\nnamespace db {\n\nclass ZmqDBCursor : public Cursor {\n public:\n  explicit ZmqDBCursor(const string& source)\n      : source_(source), socket_(ZMQ_PULL),\n        prefetched_(false), finalize_(false) {\n    socket_.Connect(source_);\n    // Start prefetching thread.\n    prefetch_thread_.reset(\n        new std::thread([this] { this->Prefetch(); }));\n    // obtain the first value.\n    Next();\n  }\n\n  ~ZmqDBCursor() {\n    finalize_ = true;\n    prefetched_ = false;\n    producer_.notify_one();\n    // Wait for the prefetch thread to finish elegantly.\n    prefetch_thread_->join();\n    socket_.Disconnect(source_);\n  }\n\n  void Seek(const string& /*key*/) override { /* do nothing */\n  }\n\n  void SeekToFirst() override { /* do nothing */ }\n\n  void Next() override {\n    std::unique_lock<std::mutex> lock(prefetch_access_mutex_);\n    while (!prefetched_) consumer_.wait(lock);\n    key_ = prefetch_key_;\n    value_ = prefetch_value_;\n    prefetched_ = false;\n    producer_.notify_one();\n  }\n\n  string key() override { return key_; }\n  string value() override { return value_; }\n  bool Valid() override { return true; }\n\n private:\n\n  void Prefetch() {\n    while (!finalize_) {\n      std::unique_lock<std::mutex> lock(prefetch_access_mutex_);\n      while (prefetched_) producer_.wait(lock);\n      if (finalize_) {\n        return;\n      }\n      ZmqMessage msg;\n      socket_.RecvTillSuccess(&msg);\n      prefetch_key_.assign(static_cast<char*>(msg.data()), msg.size());\n      socket_.RecvTillSuccess(&msg);\n      prefetch_value_.assign(static_cast<char*>(msg.data()), msg.size());\n      prefetched_ = true;\n      consumer_.notify_one();\n    }\n  }\n\n  string source_;\n  ZmqSocket socket_;\n  string key_;\n  string value_;\n  string prefetch_key_;\n  string prefetch_value_;\n\n  unique_ptr<std::thread> prefetch_thread_;\n  std::mutex prefetch_access_mutex_;\n  std::condition_variable producer_, consumer_;\n  std::atomic<bool> prefetched_;\n  // finalize_ is used to tell the prefetcher to quit.\n  std::atomic<bool> finalize_;\n};\n\nclass ZmqDB : public DB {\n public:\n  ZmqDB(const string& source, Mode mode)\n      : DB(source, mode), source_(source) {\n    CAFFE_ENFORCE(mode == READ, \"ZeroMQ DB only supports read mode.\");\n  }\n\n  ~ZmqDB() {}\n\n  void Close() override {}\n\n  unique_ptr<Cursor> NewCursor() override {\n    return make_unique<ZmqDBCursor>(source_);\n  }\n\n  unique_ptr<Transaction> NewTransaction() override {\n    CAFFE_THROW(\"ZeroMQ DB does not support writing with a transaction.\");\n    return nullptr;  // dummy placeholder to suppress old compiler warnings.\n  }\n\n private:\n  string source_;\n};\n\nREGISTER_CAFFE2_DB(ZmqDB, ZmqDB);\n// For lazy-minded, one can also call with lower-case name.\nREGISTER_CAFFE2_DB(zmqdb, ZmqDB);\n\n}  // namespace db\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/distributed/CMakeLists.txt",
    "content": "set(Caffe2_STORE_COMMON_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/file_store_handler.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/file_store_handler_op.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/store_handler.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/store_ops.cc\"\n)\n\nset(Caffe2_STORE_COMMON_GPU_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/file_store_handler_op_gpu.cc\"\n)\n\nset(Caffe2_STORE_REDIS_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/redis_store_handler.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/redis_store_handler_op.cc\"\n)\n\nset(Caffe2_STORE_REDIS_GPU_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/redis_store_handler_op_gpu.cc\"\n)\n\n# Common files that are always going to be included.\nlist(APPEND Caffe2_CPU_SRCS ${Caffe2_STORE_COMMON_SRC})\nlist(APPEND Caffe2_GPU_SRCS ${Caffe2_STORE_COMMON_GPU_SRC})\n\nif (USE_REDIS)\n  list(APPEND Caffe2_CPU_SRCS ${Caffe2_STORE_REDIS_SRC})\n  list(APPEND Caffe2_GPU_SRCS ${Caffe2_STORE_REDIS_GPU_SRC})\nendif()\n\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/distributed/file_store_handler.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"file_store_handler_op.h\"\n\n#include <errno.h>\n#include <fcntl.h>\n#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/stat.h>\n\n#include <array>\n#include <chrono>\n#include <iostream>\n#include <thread>\n\n#if defined(_MSC_VER)\n#include <direct.h> // for _mkdir\n#endif\n\n#include \"caffe2/utils/murmur_hash3.h\"\n\nnamespace caffe2 {\n\nstatic std::string encodeName(const std::string& name) {\n  std::array<uint64_t, 2> out;\n  MurmurHash3_x64_128(name.data(), name.size(), 0xcafef00d, out.data());\n\n  // Size is 33 to have space for final NUL\n  std::array<char, 33> buf;\n  for (int i = 0; i < 16; i++) {\n    snprintf(&buf[i * 2], buf.size() - (i * 2), \"%02x\", ((char*)out.data())[i]);\n  }\n\n  // Return everything but the final NUL\n  return std::string(buf.data(), buf.size() - 1);\n}\n\nFileStoreHandler::FileStoreHandler(\n    const std::string& path,\n    const std::string& prefix) {\n  basePath_ = realPath(path);\n  if (!prefix.empty()) {\n    basePath_ = basePath_ + \"/\" + encodeName(prefix);\n  }\n#if defined(_MSC_VER)\n  auto ret = _mkdir(basePath_.c_str());\n#else\n  auto ret = mkdir(basePath_.c_str(), 0777);\n#endif // defined(_MSC_VER)\n  if (ret == -1) {\n    CHECK_EQ(errno, EEXIST) << \"mkdir: \" << strerror(errno);\n  }\n}\n\nFileStoreHandler::~FileStoreHandler() {}\n\nstd::string FileStoreHandler::realPath(const std::string& path) {\n#if defined(_MSC_VER)\n  std::array<char, _MAX_PATH> buf;\n  auto ret = _fullpath(buf.data(), path.c_str(), buf.size());\n#else\n  std::array<char, PATH_MAX> buf;\n  auto ret = realpath(path.c_str(), buf.data());\n#endif\n  CHECK_EQ(buf.data(), ret) << \"realpath: \" << strerror(errno);\n  return std::string(buf.data());\n}\n\nstd::string FileStoreHandler::tmpPath(const std::string& name) {\n  return basePath_ + \"/.\" + encodeName(name);\n}\n\nstd::string FileStoreHandler::objectPath(const std::string& name) {\n  return basePath_ + \"/\" + encodeName(name);\n}\n\nvoid FileStoreHandler::set(const std::string& name, const std::string& data) {\n  auto tmp = tmpPath(name);\n  auto path = objectPath(name);\n\n  {\n    std::ofstream ofs(tmp.c_str(), std::ios::out | std::ios::trunc);\n    if (!ofs.is_open()) {\n      CAFFE_ENFORCE(\n          false, \"File cannot be created: \", tmp, \" (\", ofs.rdstate(), \")\");\n    }\n    ofs << data;\n  }\n\n  // Atomically movve result to final location\n  auto rv = rename(tmp.c_str(), path.c_str());\n  CAFFE_ENFORCE_EQ(rv, 0, \"rename: \", strerror(errno));\n}\n\nstd::string FileStoreHandler::get(const std::string& name) {\n  auto path = objectPath(name);\n  std::string result;\n\n  // Block until key is set\n  wait({name});\n\n  std::ifstream ifs(path.c_str(), std::ios::in);\n  if (!ifs) {\n    CAFFE_ENFORCE(\n        false, \"File cannot be opened: \", path, \" (\", ifs.rdstate(), \")\");\n  }\n  ifs.seekg(0, std::ios::end);\n  size_t n = ifs.tellg();\n  result.resize(n);\n  ifs.seekg(0);\n  ifs.read(&result[0], n);\n  return result;\n}\n\nint64_t FileStoreHandler::add(\n    const std::string& /* unused */,\n    int64_t /* unused */) {\n  CHECK(false) << \"add not implemented for FileStoreHandler\";\n  return 0;\n}\n\nbool FileStoreHandler::check(const std::vector<std::string>& names) {\n  std::vector<std::string> paths;\n  for (const auto& name : names) {\n    paths.push_back(objectPath(name));\n  }\n\n  for (const auto& path : paths) {\n    int fd = open(path.c_str(), O_RDONLY);\n    if (fd == -1) {\n      // Only deal with files that don't exist.\n      // Anything else is a problem.\n      CHECK_EQ(errno, ENOENT);\n\n      // One of the paths doesn't exist; return early\n      return false;\n    }\n\n    close(fd);\n  }\n\n  return true;\n}\n\nvoid FileStoreHandler::wait(\n    const std::vector<std::string>& names,\n    const std::chrono::milliseconds& timeout) {\n  // Not using inotify because it doesn't work on many\n  // shared filesystems (such as NFS).\n  const auto start = std::chrono::steady_clock::now();\n  while (!check(names)) {\n    const auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(\n        std::chrono::steady_clock::now() - start);\n    if (timeout != kNoTimeout && elapsed > timeout) {\n      STORE_HANDLER_TIMEOUT(\"Wait timeout for name(s): \", Join(\" \", names));\n    }\n    /* sleep override */\n    std::this_thread::sleep_for(std::chrono::milliseconds(10));\n  }\n}\n}\n"
  },
  {
    "path": "caffe2/distributed/file_store_handler.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <caffe2/distributed/store_handler.h>\n\nnamespace caffe2 {\n\nclass FileStoreHandler : public StoreHandler {\n public:\n  explicit FileStoreHandler(const std::string& path, const std::string& prefix);\n  virtual ~FileStoreHandler();\n\n  virtual void set(const std::string& name, const std::string& data) override;\n\n  virtual std::string get(const std::string& name) override;\n\n  virtual int64_t add(const std::string& name, int64_t value) override;\n\n  virtual bool check(const std::vector<std::string>& names) override;\n\n  virtual void wait(\n      const std::vector<std::string>& names,\n      const std::chrono::milliseconds& timeout = kDefaultTimeout) override;\n\n protected:\n  std::string basePath_;\n\n  std::string realPath(const std::string& path);\n\n  std::string tmpPath(const std::string& name);\n\n  std::string objectPath(const std::string& name);\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/distributed/file_store_handler_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"file_store_handler_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(\n    FileStoreHandlerCreate,\n    FileStoreHandlerCreateOp<CPUContext>);\n\nOPERATOR_SCHEMA(FileStoreHandlerCreate)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nCreates a unique_ptr<StoreHandler> that uses the filesystem as backing\nstore (typically a filesystem shared between many nodes, such as NFS).\nThis store handler is not built to be fast. Its recommended use is for\nintegration tests and prototypes where extra dependencies are\ncumbersome. Use an ephemeral path to ensure multiple processes or runs\ndon't interfere.\n)DOC\")\n    .Arg(\"path\", \"base path used by the FileStoreHandler\")\n    .Arg(\"prefix\", \"prefix for all keys used by this store\")\n    .Output(0, \"handler\", \"unique_ptr<StoreHandler>\");\n\nNO_GRADIENT(FileStoreHandlerCreateOp);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/distributed/file_store_handler_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"file_store_handler.h\"\n\n#include <caffe2/core/operator.h>\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass FileStoreHandlerCreateOp final : public Operator<Context> {\n public:\n  explicit FileStoreHandlerCreateOp(\n      const OperatorDef& operator_def,\n      Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        basePath_(\n            OperatorBase::template GetSingleArgument<std::string>(\"path\", \"\")),\n        prefix_(OperatorBase::template GetSingleArgument<std::string>(\n            \"prefix\",\n            \"\")) {\n    CAFFE_ENFORCE_NE(basePath_, \"\", \"path is a required argument\");\n  }\n\n  bool RunOnDevice() override {\n    auto ptr =\n        std::unique_ptr<StoreHandler>(new FileStoreHandler(basePath_, prefix_));\n    *OperatorBase::Output<std::unique_ptr<StoreHandler>>(HANDLER) =\n        std::move(ptr);\n    return true;\n  }\n\n private:\n  std::string basePath_;\n  std::string prefix_;\n\n  OUTPUT_TAGS(HANDLER);\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/distributed/file_store_handler_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"file_store_handler_op.h\"\n\n#include <caffe2/core/context_gpu.h>\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(\n    FileStoreHandlerCreate,\n    FileStoreHandlerCreateOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/distributed/file_store_handler_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport errno\nimport os\nimport tempfile\nimport shutil\n\nfrom caffe2.distributed.python import StoreHandlerTimeoutError\nfrom caffe2.distributed.store_ops_test_util import StoreOpsTests\nfrom caffe2.python import core, workspace, dyndep\nfrom caffe2.python.test_util import TestCase\n\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/distributed:file_store_handler_ops\")\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/distributed:store_ops\")\n\n\nclass TestFileStoreHandlerOp(TestCase):\n    testCounter = 0\n\n    def setUp(self):\n        super(TestFileStoreHandlerOp, self).setUp()\n        self.tmpdir = tempfile.mkdtemp()\n\n        # Use counter to tell test cases apart\n        TestFileStoreHandlerOp.testCounter += 1\n\n    def tearDown(self):\n        shutil.rmtree(self.tmpdir)\n        super(TestFileStoreHandlerOp, self).tearDown()\n\n    def create_store_handler(self):\n        # Use new path for every test so they are isolated\n        path = self.tmpdir + \"/\" + str(TestFileStoreHandlerOp.testCounter)\n\n        # Ensure path exists (including counter)\n        try:\n            os.makedirs(path)\n        except OSError as exc:\n            if exc.errno == errno.EEXIST and os.path.isdir(path):\n                pass\n            else:\n                raise\n\n        store_handler = \"store_handler\"\n        workspace.RunOperatorOnce(\n            core.CreateOperator(\n                \"FileStoreHandlerCreate\",\n                [],\n                [store_handler],\n                path=path))\n\n        return store_handler\n\n    def test_set_get(self):\n        StoreOpsTests.test_set_get(self.create_store_handler)\n\n    def test_get_timeout(self):\n        with self.assertRaises(StoreHandlerTimeoutError):\n            StoreOpsTests.test_get_timeout(self.create_store_handler)\n"
  },
  {
    "path": "caffe2/distributed/py_export.cc",
    "content": "#include <pybind11/pybind11.h>\n\n#include \"caffe2/distributed/store_handler.h\"\n\nnamespace caffe2 {\nnamespace python {\n\nnamespace py = pybind11;\n\nPYBIND11_MODULE(python, m) {\n  m.doc() = \"Python interface for distributed Caffe2\";\n\n  py::register_exception<StoreHandlerTimeoutException>(\n      m, \"StoreHandlerTimeoutError\");\n}\n\n} // namespace python\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/distributed/redis_store_handler.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"redis_store_handler.h\"\n\n#include <caffe2/core/logging.h>\n\n#include <chrono>\n#include <thread>\n#include <vector>\n\nnamespace caffe2 {\n\nRedisStoreHandler::RedisStoreHandler(\n    std::string& host,\n    int port,\n    std::string& prefix)\n    : host_(host), port_(port), prefix_(prefix) {\n  struct timeval tv = {\n      .tv_sec = 5, .tv_usec = 0,\n  };\n\n  redis_ = redisConnectWithTimeout(host.c_str(), port, tv);\n  CAFFE_ENFORCE_NE(redis_, (redisContext*)nullptr);\n  CAFFE_ENFORCE_EQ(redis_->err, 0, redis_->errstr);\n}\n\nRedisStoreHandler::~RedisStoreHandler() {\n  redisFree(redis_);\n}\n\nstd::string RedisStoreHandler::compoundKey(const std::string& name) {\n  return prefix_ + name;\n}\n\nvoid RedisStoreHandler::set(const std::string& name, const std::string& data) {\n  auto key = compoundKey(name);\n  void* ptr = redisCommand(\n      redis_,\n      \"SETNX %b %b\",\n      key.c_str(),\n      (size_t)key.size(),\n      data.c_str(),\n      (size_t)data.size());\n  CAFFE_ENFORCE_NE(ptr, (void*)nullptr, redis_->errstr);\n  redisReply* reply = static_cast<redisReply*>(ptr);\n  CAFFE_ENFORCE_EQ(reply->type, REDIS_REPLY_INTEGER);\n  CAFFE_ENFORCE_EQ(\n      reply->integer,\n      1,\n      \"Value at \",\n      name,\n      \" was already set\",\n      \" (perhaps you reused a run ID you have used before?)\");\n}\n\nstd::string RedisStoreHandler::get(const std::string& name) {\n  // Block until key is set\n  wait({name});\n\n  auto key = compoundKey(name);\n  void* ptr = redisCommand(redis_, \"GET %b\", key.c_str(), (size_t)key.size());\n  CAFFE_ENFORCE_NE(ptr, (void*)nullptr, redis_->errstr);\n  redisReply* reply = static_cast<redisReply*>(ptr);\n  CAFFE_ENFORCE_EQ(reply->type, REDIS_REPLY_STRING);\n  return std::string(reply->str, reply->len);\n}\n\nint64_t RedisStoreHandler::add(const std::string& name, int64_t value) {\n  auto key = compoundKey(name);\n  void* ptr = redisCommand(\n      redis_, \"INCRBY %b %ld\", key.c_str(), (size_t)key.size(), value);\n  CAFFE_ENFORCE_NE(ptr, (void*)nullptr, redis_->errstr);\n  redisReply* reply = static_cast<redisReply*>(ptr);\n  CAFFE_ENFORCE_EQ(reply->type, REDIS_REPLY_INTEGER);\n  return reply->integer;\n}\n\nbool RedisStoreHandler::check(const std::vector<std::string>& names) {\n  std::vector<std::string> args;\n  args.push_back(\"EXISTS\");\n  for (const auto& name : names) {\n    args.push_back(compoundKey(name));\n  }\n\n  std::vector<const char*> argv;\n  std::vector<size_t> argvlen;\n  for (const auto& arg : args) {\n    argv.push_back(arg.c_str());\n    argvlen.push_back(arg.length());\n  }\n\n  auto argc = argv.size();\n  void* ptr = redisCommandArgv(redis_, argc, argv.data(), argvlen.data());\n  CAFFE_ENFORCE_NE(ptr, (void*)nullptr, redis_->errstr);\n  redisReply* reply = static_cast<redisReply*>(ptr);\n  CAFFE_ENFORCE_EQ(reply->type, REDIS_REPLY_INTEGER);\n  return reply->integer == names.size();\n}\n\nvoid RedisStoreHandler::wait(\n    const std::vector<std::string>& names,\n    const std::chrono::milliseconds& timeout) {\n  // Simple approach: poll...\n  // Complex approach: use pub/sub.\n  // Polling is fine for the typical rendezvous use case, as it is\n  // only done at initialization time and  not at run time.\n  const auto start = std::chrono::steady_clock::now();\n  while (!check(names)) {\n    const auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(\n        std::chrono::steady_clock::now() - start);\n    if (timeout != kNoTimeout && elapsed > timeout) {\n      STORE_HANDLER_TIMEOUT(\"Wait timeout for name(s): \", Join(\" \", names));\n    }\n    /* sleep override */\n    std::this_thread::sleep_for(std::chrono::milliseconds(10));\n  }\n}\n}\n"
  },
  {
    "path": "caffe2/distributed/redis_store_handler.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <caffe2/distributed/store_handler.h>\n\nextern \"C\" {\n#include <hiredis/hiredis.h>\n}\n\n#include <string>\n\nnamespace caffe2 {\n\nclass RedisStoreHandler : public StoreHandler {\n public:\n  explicit RedisStoreHandler(std::string& host, int port, std::string& prefix);\n  virtual ~RedisStoreHandler();\n\n  virtual void set(const std::string& name, const std::string& data) override;\n\n  virtual std::string get(const std::string& name) override;\n\n  virtual int64_t add(const std::string& name, int64_t value) override;\n\n  virtual bool check(const std::vector<std::string>& names) override;\n\n  virtual void wait(\n      const std::vector<std::string>& names,\n      const std::chrono::milliseconds& timeout = kDefaultTimeout) override;\n\n private:\n  std::string host_;\n  int port_;\n  std::string prefix_;\n\n  redisContext* redis_;\n\n  std::string compoundKey(const std::string& name);\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/distributed/redis_store_handler_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"redis_store_handler_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(\n    RedisStoreHandlerCreate,\n    RedisStoreHandlerCreateOp<CPUContext>);\n\nOPERATOR_SCHEMA(RedisStoreHandlerCreate)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nCreates a unique_ptr<StoreHandler> that uses a Redis server as backing store.\n)DOC\")\n    .Arg(\"host\", \"host name of Redis server\")\n    .Arg(\"port\", \"port number of Redis server\")\n    .Arg(\"prefix\", \"keys used by this instance are prefixed with this string\")\n    .Output(0, \"handler\", \"unique_ptr<StoreHandler>\");\n\nNO_GRADIENT(RedisStoreHandlerCreateOp);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/distributed/redis_store_handler_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"redis_store_handler.h\"\n\n#include <caffe2/core/operator.h>\n\n#include <string>\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass RedisStoreHandlerCreateOp final : public Operator<Context> {\n public:\n  explicit RedisStoreHandlerCreateOp(\n      const OperatorDef& operator_def,\n      Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        host_(\n            OperatorBase::template GetSingleArgument<std::string>(\"host\", \"\")),\n        port_(OperatorBase::template GetSingleArgument<int>(\"port\", 0)),\n        prefix_(OperatorBase::template GetSingleArgument<std::string>(\n            \"prefix\",\n            \"\")) {\n    CAFFE_ENFORCE_NE(host_, \"\", \"host is a required argument\");\n    CAFFE_ENFORCE_NE(port_, 0, \"port is a required argument\");\n  }\n\n  bool RunOnDevice() override {\n    auto ptr = std::unique_ptr<StoreHandler>(\n        new RedisStoreHandler(host_, port_, prefix_));\n    *OperatorBase::Output<std::unique_ptr<StoreHandler>>(HANDLER) =\n        std::move(ptr);\n    return true;\n  }\n\n private:\n  std::string host_;\n  int port_;\n  std::string prefix_;\n\n  OUTPUT_TAGS(HANDLER);\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/distributed/redis_store_handler_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"redis_store_handler_op.h\"\n\n#include <caffe2/core/context_gpu.h>\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(\n    RedisStoreHandlerCreate,\n    RedisStoreHandlerCreateOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/distributed/redis_store_handler_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport os\nimport uuid\n\nfrom caffe2.distributed.python import StoreHandlerTimeoutError\nfrom caffe2.distributed.store_ops_test_util import StoreOpsTests\nfrom caffe2.python import core, workspace, dyndep\nfrom caffe2.python.test_util import TestCase\n\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/distributed:redis_store_handler_ops\")\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/distributed:store_ops\")\n\n\nclass TestRedisStoreHandlerOp(TestCase):\n    def setUp(self):\n        super(TestRedisStoreHandlerOp, self).setUp()\n        self.uuid = str(uuid.uuid4()) + \"/\"\n\n    def tearDown(self):\n        super(TestRedisStoreHandlerOp, self).tearDown()\n\n    def create_store_handler(self):\n        store_handler = \"store_handler\"\n        workspace.RunOperatorOnce(\n            core.CreateOperator(\n                \"RedisStoreHandlerCreate\",\n                [],\n                [store_handler],\n                prefix=self.uuid,\n                host=os.getenv(\"REDIS_HOST\", \"localhost\"),\n                port=int(os.getenv(\"REDIS_PORT\", 6379))))\n        return store_handler\n\n    def test_set_get(self):\n        StoreOpsTests.test_set_get(self.create_store_handler)\n\n    def test_get_timeout(self):\n        with self.assertRaises(StoreHandlerTimeoutError):\n            StoreOpsTests.test_get_timeout(self.create_store_handler)\n"
  },
  {
    "path": "caffe2/distributed/store_handler.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"store_handler.h\"\n\n#include <memory>\n\n#include \"caffe2/core/typeid.h\"\n\nnamespace caffe2 {\n\nconstexpr std::chrono::milliseconds StoreHandler::kDefaultTimeout;\nconstexpr std::chrono::milliseconds StoreHandler::kNoTimeout;\n\nStoreHandler::~StoreHandler() {\n  // NOP; definition is here to make sure library contains\n  // symbols for this abstract class.\n}\n\nCAFFE_KNOWN_TYPE(std::unique_ptr<StoreHandler>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/distributed/store_handler.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <chrono>\n#include <cstdint>\n#include <stdexcept>\n#include <string>\n#include <vector>\n\nnamespace caffe2 {\n\nclass StoreHandler {\n public:\n  static constexpr std::chrono::milliseconds kDefaultTimeout =\n      std::chrono::seconds(30);\n  static constexpr std::chrono::milliseconds kNoTimeout =\n      std::chrono::milliseconds::zero();\n\n  virtual ~StoreHandler();\n\n  /*\n   * Set data for the key if it doesn't exist.\n   * If the key exists the data should be the same as the existing key.\n   */\n  virtual void set(const std::string& name, const std::string& data) = 0;\n\n  /*\n   * Get the data for the key.\n   * The call should wait until the key is stored with default timeout\n   * and return data if set else fail.\n   */\n  virtual std::string get(const std::string& name) = 0;\n\n  /*\n   * Does an atomic add operation on the key and returns the latest updated\n   * value.\n   * Note: To access the current value for this counter call with value = 0\n   */\n  virtual int64_t add(const std::string& name, int64_t value) = 0;\n\n  /*\n   * Check if a keys exist in the store.\n   */\n  virtual bool check(const std::vector<std::string>& names) = 0;\n\n  /*\n   * Wait for Keys to be stored.\n   */\n  virtual void wait(\n      const std::vector<std::string>& names,\n      const std::chrono::milliseconds& timeout = kDefaultTimeout) = 0;\n};\n\nstruct StoreHandlerTimeoutException : public std::runtime_error {\n  StoreHandlerTimeoutException() = default;\n  explicit StoreHandlerTimeoutException(const std::string& msg)\n      : std::runtime_error(msg) {}\n};\n\n#define STORE_HANDLER_TIMEOUT(...)              \\\n  throw ::caffe2::StoreHandlerTimeoutException( \\\n      ::caffe2::MakeString(\"[\", __FILE__, \":\", __LINE__, \"] \", __VA_ARGS__));\n}\n"
  },
  {
    "path": "caffe2/distributed/store_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"store_ops.h\"\n\nnamespace caffe2 {\n\nconstexpr auto kBlobName = \"blob_name\";\nconstexpr auto kAddValue = \"add_value\";\n\nStoreSetOp::StoreSetOp(const OperatorDef& operator_def, Workspace* ws)\n    : Operator<CPUContext>(operator_def, ws),\n      blobName_(\n          GetSingleArgument<std::string>(kBlobName, operator_def.input(DATA))) {\n}\n\nbool StoreSetOp::RunOnDevice() {\n  // Serialize and pass to store\n  auto* handler =\n      OperatorBase::Input<std::unique_ptr<StoreHandler>>(HANDLER).get();\n  handler->set(blobName_, InputBlob(DATA).Serialize(blobName_));\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(StoreSet, StoreSetOp);\nOPERATOR_SCHEMA(StoreSet)\n    .NumInputs(2)\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(\nSet a blob in a store. The key is the input blob's name and the value\nis the data in that blob. The key can be overridden by specifying the\n'blob_name' argument.\n)DOC\")\n    .Arg(\"blob_name\", \"alternative key for the blob (optional)\")\n    .Input(0, \"handler\", \"unique_ptr<StoreHandler>\")\n    .Input(1, \"data\", \"data blob\");\n\nStoreGetOp::StoreGetOp(const OperatorDef& operator_def, Workspace* ws)\n    : Operator<CPUContext>(operator_def, ws),\n      blobName_(GetSingleArgument<std::string>(\n          kBlobName,\n          operator_def.output(DATA))) {}\n\nbool StoreGetOp::RunOnDevice() {\n  // Get from store and deserialize\n  auto* handler =\n      OperatorBase::Input<std::unique_ptr<StoreHandler>>(HANDLER).get();\n  OperatorBase::Outputs()[DATA]->Deserialize(handler->get(blobName_));\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(StoreGet, StoreGetOp);\nOPERATOR_SCHEMA(StoreGet)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGet a blob from a store. The key is the output blob's name. The key\ncan be overridden by specifying the 'blob_name' argument.\n)DOC\")\n    .Arg(\"blob_name\", \"alternative key for the blob (optional)\")\n    .Input(0, \"handler\", \"unique_ptr<StoreHandler>\")\n    .Output(0, \"data\", \"data blob\");\n\nStoreAddOp::StoreAddOp(const OperatorDef& operator_def, Workspace* ws)\n    : Operator<CPUContext>(operator_def, ws),\n      blobName_(GetSingleArgument<std::string>(kBlobName, \"\")),\n      addValue_(GetSingleArgument<int64_t>(kAddValue, 1)) {\n  CAFFE_ENFORCE(HasArgument(kBlobName));\n}\n\nbool StoreAddOp::RunOnDevice() {\n  auto* handler =\n      OperatorBase::Input<std::unique_ptr<StoreHandler>>(HANDLER).get();\n  Output(VALUE)->Resize(1);\n  Output(VALUE)->mutable_data<int64_t>()[0] =\n      handler->add(blobName_, addValue_);\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(StoreAdd, StoreAddOp);\nOPERATOR_SCHEMA(StoreAdd)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nAdd a value to a remote counter. If the key is not set, the store\ninitializes it to 0 and then performs the add operation. The operation\nreturns the resulting counter value.\n)DOC\")\n    .Arg(\"blob_name\", \"key of the counter (required)\")\n    .Arg(\"add_value\", \"value that is added (optional, default: 1)\")\n    .Input(0, \"handler\", \"unique_ptr<StoreHandler>\")\n    .Output(0, \"value\", \"the current value of the counter\");\n\nStoreWaitOp::StoreWaitOp(const OperatorDef& operator_def, Workspace* ws)\n    : Operator<CPUContext>(operator_def, ws),\n      blobNames_(GetRepeatedArgument<std::string>(kBlobName)) {}\n\nbool StoreWaitOp::RunOnDevice() {\n  auto* handler =\n      OperatorBase::Input<std::unique_ptr<StoreHandler>>(HANDLER).get();\n  if (InputSize() == 2 && Input(1).IsType<std::string>()) {\n    CAFFE_ENFORCE(\n        blobNames_.empty(), \"cannot specify both argument and input blob\");\n    std::vector<std::string> blobNames;\n    auto* namesPtr = Input(1).data<std::string>();\n    for (int i = 0; i < Input(1).size(); ++i) {\n      blobNames.push_back(namesPtr[i]);\n    }\n    handler->wait(blobNames);\n  } else {\n    handler->wait(blobNames_);\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(StoreWait, StoreWaitOp);\nOPERATOR_SCHEMA(StoreWait)\n    .NumInputs(1, 2)\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(\nWait for the specified blob names to be set. The blob names can be passed\neither as an input blob with blob names or as an argument.\n)DOC\")\n    .Arg(\"blob_names\", \"names of the blobs to wait for (optional)\")\n    .Input(0, \"handler\", \"unique_ptr<StoreHandler>\")\n    .Input(1, \"names\", \"names of the blobs to wait for (optional)\");\n}\n"
  },
  {
    "path": "caffe2/distributed/store_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"store_handler.h\"\n\n#include <caffe2/core/operator.h>\n\nnamespace caffe2 {\n\nclass StoreSetOp final : public Operator<CPUContext> {\n public:\n  StoreSetOp(const OperatorDef& operator_def, Workspace* ws);\n  bool RunOnDevice() override;\n\n private:\n  std::string blobName_;\n\n  INPUT_TAGS(HANDLER, DATA);\n};\n\nclass StoreGetOp final : public Operator<CPUContext> {\n public:\n  StoreGetOp(const OperatorDef& operator_def, Workspace* ws);\n  bool RunOnDevice() override;\n\n private:\n  std::string blobName_;\n\n  INPUT_TAGS(HANDLER);\n  OUTPUT_TAGS(DATA);\n};\n\nclass StoreAddOp final : public Operator<CPUContext> {\n public:\n  StoreAddOp(const OperatorDef& operator_def, Workspace* ws);\n  bool RunOnDevice() override;\n\n private:\n  std::string blobName_;\n  int addValue_;\n\n  INPUT_TAGS(HANDLER);\n  OUTPUT_TAGS(VALUE);\n};\n\nclass StoreWaitOp final : public Operator<CPUContext> {\n public:\n  StoreWaitOp(const OperatorDef& operator_def, Workspace* ws);\n  bool RunOnDevice() override;\n\n private:\n  std::vector<std::string> blobNames_;\n\n  INPUT_TAGS(HANDLER);\n};\n}\n"
  },
  {
    "path": "caffe2/distributed/store_ops_test_util.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package store_ops_test_util\n# Module caffe2.distributed.store_ops_test_util\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom multiprocessing import Process, Queue\n\nimport numpy as np\n\nfrom caffe2.python import core, workspace\n\n\nclass StoreOpsTests(object):\n    @classmethod\n    def _test_set_get(cls, queue, create_store_handler_fn, index, num_procs):\n        store_handler = create_store_handler_fn()\n        blob = \"blob\"\n        value = np.full(1, 1, np.float32)\n\n        # Use last process to set blob to make sure other processes\n        # are waiting for the blob before it is set.\n        if index == (num_procs - 1):\n            workspace.FeedBlob(blob, value)\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"StoreSet\",\n                    [store_handler, blob],\n                    [],\n                    blob_name=blob))\n\n        output_blob = \"output_blob\"\n        workspace.RunOperatorOnce(\n            core.CreateOperator(\n                \"StoreGet\",\n                [store_handler],\n                [output_blob],\n                blob_name=blob))\n\n        try:\n            np.testing.assert_array_equal(workspace.FetchBlob(output_blob), 1)\n        except AssertionError as err:\n            queue.put(err)\n\n        workspace.ResetWorkspace()\n\n    @classmethod\n    def test_set_get(cls, create_store_handler_fn):\n        # Queue for assertion errors on subprocesses\n        queue = Queue()\n\n        # Start N processes in the background\n        num_procs = 4\n        procs = []\n        for index in range(num_procs):\n            proc = Process(\n                target=cls._test_set_get,\n                args=(queue, create_store_handler_fn, index, num_procs, ))\n            proc.start()\n            procs.append(proc)\n\n        # Test complete, join background processes\n        for proc in procs:\n            proc.join()\n\n        # Raise first error we find, if any\n        if not queue.empty():\n            raise queue.get()\n\n    @classmethod\n    def test_get_timeout(cls, create_store_handler_fn):\n        store_handler = create_store_handler_fn()\n        net = core.Net('get_missing_blob')\n        net.StoreGet([store_handler], 1, blob_name='blob')\n        workspace.RunNetOnce(net)\n"
  },
  {
    "path": "caffe2/experiments/operators/fully_connected_op_decomposition.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/experiments/operators/fully_connected_op_decomposition.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(FC_Decomp, FullyConnectedOpDecomp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(FCGradient_Decomp,\n                      FullyConnectedDecompGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(FC_Decomp).NumInputs(4).NumOutputs(1);\nOPERATOR_SCHEMA(FCGradient_Decomp).NumInputs(4).NumOutputs(3, 4);\n\nclass GetFCDecompGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE_EQ(def_.input_size(), 4);\n    // TODO(wyiming): Check whether it is right? Let's move fast first.\n    return SingleGradientDef(\n        \"FCGradient_Decomp\", \"\",\n        vector<string>{I(0), I(1), I(2), GO(0)},\n        vector<string>{GI(1), GI(2), GI(3), GI(0)});\n  }\n};\nREGISTER_GRADIENT(FC_Decomp, GetFCDecompGradient);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/experiments/operators/fully_connected_op_decomposition.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FULLY_CONNECTED_OP_DECOMPOSITION_H_\n#define CAFFE2_OPERATORS_FULLY_CONNECTED_OP_DECOMPOSITION_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n/*\n * Although a FC_decomp is just like 2 small FC,\n * it is better to have it as one op for future analysis.\n * And if we have 2 FC with bias, it is not right.\n * TODO(wyiming): decompose the layer into 2 matrices\n * W(N * K) = U(N * middle) * trans(V(K * middle))\n * */\n// This is Caffe's InnerProductOp, with a name that fits its purpose better.\ntemplate <typename T, class Context, class Engine=DefaultEngine>\nclass FullyConnectedOpDecomp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  FullyConnectedOpDecomp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  ~FullyConnectedOpDecomp() {}\n\n  bool RunOnDevice() override {\n    const auto& X = Input(0);\n    const auto& U = Input(1);\n    const auto& V = Input(2);\n    const auto& b = Input(3);\n    auto* Y = Output(0);\n    //auto* buffer_ptr = Output(1);\n    // Size M * middle;\n    //auto& multi_buffer_ = *buffer_ptr;\n    CAFFE_ENFORCE_GE(X.ndim(), 1);\n    CAFFE_ENFORCE_GE(U.ndim(), 2);\n    CAFFE_ENFORCE_GE(V.ndim(), 2);\n    if (X.ndim() > 2 || U.ndim() > 2 || V.ndim() > 2) {\n      VLOG(1) << \"Using legacy support for arbitrary input and weight \"\n                       \"dimensions.\";\n    }\n    CAFFE_ENFORCE_EQ(b.ndim(), 1);\n    // batch size\n    int M = X.ndim() > 1 ? X.dim32(0) : 1;\n    // Feature dimension\n    int K = X.size() / M;\n    // number of outputs.\n    int N = U.dim32(0);\n    int middle = U.dim32(0);\n    CAFFE_ENFORCE_EQ(K, V.dim32(0));\n    CAFFE_ENFORCE_EQ(N, b.dim32(0));\n    if (X.ndim() > 1) {\n      Y->Resize(M, N);\n      multi_buffer_.Resize(M, middle);\n    } else {\n      Y->Resize(N);\n      multi_buffer_.Resize(middle);\n    }\n  // The col buffer is stored in CHW order as well - kernel_dim, and the height\n  // and width.\n    //  multi_buffer_.Resize(M, middle);\n    T* multi_buffer_data = multi_buffer_.template mutable_data<T>();\n    //  X * V * tans(U)\n    math::Gemm<T, Context, Engine>(\n        CblasNoTrans, CblasNoTrans, M, middle, K, 1, X.template data<T>(),\n        V.template data<T>(), 0, multi_buffer_data,\n        &context_);\n    math::Gemm<T, Context, Engine>(\n        CblasNoTrans, CblasTrans, M, N, middle, 1, multi_buffer_data,\n        U.template data<T>(), 0, Y->template mutable_data<T>(),\n        &context_);\n    // Add bias term\n    if (bias_multiplier_.size() != M) {\n      // If the helper bias multiplier is not M, reshape and fill it with one.\n      bias_multiplier_.Resize(M);\n      math::Set<T, Context>(\n          M, static_cast<T>(1), bias_multiplier_.template mutable_data<T>(),\n          &context_);\n    }\n    math::Gemm<T, Context, Engine>(\n        CblasNoTrans, CblasNoTrans, M, N, 1, 1,\n        bias_multiplier_.template data<T>(), b.template data<T>(), 1,\n        Y->template mutable_data<T>(), &context_);\n    return true;\n  }\n\n protected:\n  Tensor<Context> bias_multiplier_;\n  Tensor<Context> multi_buffer_;\n};\n\ntemplate <typename T, class Context, class Engine=DefaultEngine>\nclass FullyConnectedDecompGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  FullyConnectedDecompGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  ~FullyConnectedDecompGradientOp() {}\n\n  bool RunOnDevice() override {\n    const auto& X = Input(0);\n    const auto& U = Input(1);\n    const auto& V = Input(2);\n    const auto& dY = Input(3);\n    DCHECK_GE(X.ndim(), 1);\n    DCHECK_GE(U.ndim(), 2);\n    DCHECK_GE(V.ndim(), 2);\n    DCHECK_LE(dY.ndim(), 2);\n    // batch size\n    int M = X.ndim() > 1 ? X.dim32(0) : 1;\n    // Feature dimension\n    int K = X.size() / M;\n    // number of outputs.\n    int N = U.dim32(0);\n    int middle = U.dim32(1);\n    DCHECK_EQ(K, V.dim32(0));\n    if (dY.ndim() > 1) {\n      DCHECK_EQ(M, dY.dim32(0));\n      DCHECK_EQ(N, dY.dim32(1));\n    } else {\n      DCHECK_EQ(X.ndim(), 1);\n      DCHECK_EQ(N, dY.size());\n    }\n    auto* dU = Output(0);\n    auto* dV = Output(1);\n    auto* db = Output(2);\n    dU->ResizeLike(U);\n    dV->ResizeLike(V);\n    db->Resize(N);\n\n    // Compute dU\n    // first compute X * V\n    du_buffer_.Resize(N, middle);\n    T* du_buffer_data = du_buffer_.template mutable_data<T>();\n    math::Gemm<T, Context, Engine>(\n        CblasNoTrans, CblasNoTrans, M, middle, K, 1,\n        X.template data<T>(), V.template data<T>(),\n        0, du_buffer_data,\n        &context_);\n    math::Gemm<T, Context, Engine>(\n        CblasTrans, CblasNoTrans, N, middle, M, 1,\n        dY.template data<T>(), du_buffer_data,\n        0, dU->template mutable_data<T>(),\n        &context_);\n    // Compute dV\n    // first compute dY * U\n    dv_buffer_.Resize(M, middle);\n    T* dv_buffer_data = dv_buffer_.template mutable_data<T>();\n    math::Gemm<T, Context, Engine>(\n        CblasNoTrans, CblasNoTrans, M, middle, N, 1,\n        dY.template data<T>(), U.template data<T>(),\n        0, dv_buffer_data,\n        &context_);\n    math::Gemm<T, Context, Engine>(\n        CblasTrans, CblasNoTrans, K, middle, M, 1,\n        dY.template data<T>(), du_buffer_data,\n        0, dV->template mutable_data<T>(),\n        &context_);\n    if (bias_multiplier_.size() != M) {\n      // If the helper bias multiplier is not M, reshape and fill it with one.\n      bias_multiplier_.Resize(M);\n      math::Set<T, Context>(\n          M, static_cast<T>(1),\n          bias_multiplier_.template mutable_data<T>(),\n          &context_);\n    }\n    // Compute dB\n    math::Gemv<T, Context>(\n        CblasTrans, M, N, 1, dY.template data<T>(),\n        bias_multiplier_.template data<T>(), 0,\n        db->template mutable_data<T>(),\n        &context_);\n    // Compute dX if necessary.\n    if (OutputSize() == 4) {\n      auto* dX = Output(3);\n      dX->ResizeLike(X);\n      dx_buffer_.Resize(M, middle);\n      T* dx_buffer_data = dx_buffer_.template mutable_data<T>();\n      math::Gemm<T, Context, Engine>(\n          CblasNoTrans, CblasNoTrans, M, middle, N, 1,\n          dY.template data<T>(), U.template data<T>(),\n          0, dx_buffer_data,\n          &context_);\n      math::Gemm<T, Context, Engine>(\n          CblasNoTrans, CblasTrans, M, K, middle, 1,\n          dx_buffer_data, V.template data<T>(),\n          0, dX->template mutable_data<T>(),\n          &context_);\n    }\n\n    return true;\n  }\n\n protected:\n  Tensor<Context> bias_multiplier_;\n  Tensor<Context> du_buffer_;\n  Tensor<Context> dv_buffer_;\n  Tensor<Context> dx_buffer_;\n};\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_OPERATORS_FULLY_CONNECTED_OP_H_\n"
  },
  {
    "path": "caffe2/experiments/operators/fully_connected_op_decomposition_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/experiments/operators/fully_connected_op_decomposition.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(FC_Decomp, FullyConnectedOpDecomp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(FCGradient_Decomp,\n                       FullyConnectedDecompGradientOp<float, CUDAContext>);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/experiments/operators/fully_connected_op_prune.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/experiments/operators/fully_connected_op_prune.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(FC_Prune, FullyConnectedOpPrune<float, CPUContext>);\nREGISTER_CPU_OPERATOR(FCGradient_Prune,\n                      FullyConnectedPruneGradientOp<float, CPUContext>);\n/* 8 Inputs:\n * X    W   Mask  bias  Ag_dw   Mask_seq  thres   comp_lb\n * */\nOPERATOR_SCHEMA(FC_Prune).NumInputs(8).NumOutputs(1, 2);\nOPERATOR_SCHEMA(FCGradient_Prune).NumInputs(8).NumOutputs(6, 7)\n      .AllowInplace({{1, 2}, {2, 3}, {4, 4}, {5, 5}});\n\nclass GetFCPruneGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE_EQ(def_.input_size(), 8);\n    return SingleGradientDef(\n        \"FCGradient_Prune\", \"\",\n        vector<string>{I(0), I(1), I(2), GO(0), I(4), I(5), I(6), I(7)},\n        vector<string>{GI(1), GI(3), I(1), I(2), I(4), I(5), GI(0)});\n  }\n};\nREGISTER_GRADIENT(FC_Prune, GetFCPruneGradient);\n}  // namespace\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/experiments/operators/fully_connected_op_prune.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FULLY_CONNECTED_OP_PRUNE_H_\n#define CAFFE2_OPERATORS_FULLY_CONNECTED_OP_PRUNE_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n  namespace {\n\n    template<int N>\n      using Shape = std::array<int, N>;\n\n    template<int N>\n      const std::vector<TIndex>& shape(Shape<N> vs) {\n        static thread_local std::vector<TIndex> cache;\n        cache.resize(vs.size());\n        for (auto i = 0; i < vs.size(); ++i) {\n          cache[i] = vs[i];\n        }\n        return cache;\n      }\n\n    inline const std::vector<TIndex>& shape(int i) {\n      return shape<1>(Shape<1>({i}));\n    }\n\n    inline const std::vector<TIndex>& shape(int i, int j) {\n      return shape<2>(Shape<2>({i, j}));\n    }\n\n    template <typename T, class Context>\n      void MaskMatrix(const T* mask, T* mat,\n          int M, int N);\n\n    template <typename T, class Context>\n      void MaskMatrix_Inc(T* mask_seq, T* mat,\n          int M, int N, int seq_len, T target);\n\n    template <typename T, class Context>\n      void AggrDW(T* ag_dw, const T* dw, int N, int K, Context* context);\n\n    template <typename T>\n      int MatrixCompare_LT(const T* mat, float thres,\n                           T* mask_seq, int M, int N);\n\n    // TODO(wyiming): write an incremental Mask\n    // Incremental Mask: only give the new mask positions;\n    // Assuming that weights masked will not be mask again;\n    // The incremental mask can also be used to update mask matrix;\n    // But this will include template for bool and float;\n    template <>\n      void MaskMatrix<float, CPUContext>(\n          const float* mask, float* mat, int M, int N) {\n        int offset = 0;\n        for (int i = 0; i < M; ++i) {\n          for (int j = 0; j < N; ++j) {\n            mat[offset] = mask[offset]? mat[offset] : 0;\n            offset++;\n          }\n        }\n      }\n\n      template <>\n      void MaskMatrix_Inc<float, CPUContext>(\n          float* mask_seq,\n          float* mat,\n          int /*M*/,\n          int /*N*/,\n          int seq_len,\n          float target) {\n        for (int i = 0; i < seq_len; ++i) {\n          // assume that the mask_seq is smaller than size\n          // Although it seems that random access gets bad performance,\n          // we make sure that seq is in order;\n          mat[static_cast<int>(mask_seq[i])] = target;\n        }\n      }\n\n    template <>\n      void AggrDW<float, CPUContext>(\n          float* ag_dw, const float* dw,\n          int N, int K, CPUContext* context) {\n        math::Add<float, CPUContext>(N*K, dw, ag_dw, ag_dw, context);\n      }\n\n    template <>\n      int MatrixCompare_LT<float>(\n          const float* mat, float thres,\n          float* mask_seq, int M, int N) {\n        int seq_len = 0;\n        int offset = 0;\n        for (int i = 0 ; i < M; ++i) {\n          for (int j = 0; j < N; ++j) {\n            if (mat[offset] != 0 &&\n                (mat[offset] < thres && mat[offset] > -thres)) {\n              mask_seq[seq_len++] = static_cast<float>(offset);\n            }\n            offset++;\n          }\n        }\n        return seq_len;\n      }\n\n  }\n\n  // This is Caffe's InnerProductOp, with a name that fits its purpose better.\n  template <typename T, class Context, class Engine=DefaultEngine>\n    class FullyConnectedOpPrune final : public Operator<Context> {\n      public:\n        USE_OPERATOR_CONTEXT_FUNCTIONS;\n        FullyConnectedOpPrune(const OperatorDef& operator_def, Workspace* ws)\n          : Operator<Context>(operator_def, ws) {}\n        ~FullyConnectedOpPrune() {}\n\n        bool RunOnDevice() override {\n          const auto& X = Input(0);\n          const auto& W = Input(1);\n          const auto& Mask = Input(2);\n          const auto& b = Input(3);\n          auto* Y = Output(0);\n          CAFFE_ENFORCE_GE(X.ndim(), 1);\n          CAFFE_ENFORCE_GE(W.ndim(), 2);\n          if (X.ndim() > 2 || W.ndim() > 2) {\n            VLOG(1) << \"Using legacy support for arbitrary input and weight \"\n              \"dimensions.\";\n          }\n          CAFFE_ENFORCE_EQ(b.ndim(), 1);\n          // batch size\n          int M = X.ndim() > 1 ? X.dim32(0) : 1;\n          // Feature dimension\n          int K = X.size() / M;\n          // number of outputs.\n          int N = W.dim32(0);\n          CAFFE_ENFORCE_EQ(K, W.size() / W.dim32(0));\n          CAFFE_ENFORCE_EQ(N, b.dim32(0));\n          if (X.ndim() > 1) {\n            Y->Resize(M, N);\n          } else {\n            Y->Resize(N);\n          }\n          // W * x\n          math::Gemm<T, Context, Engine>(\n              CblasNoTrans, CblasTrans, M, N, K, 1, X.template data<T>(),\n              W.template data<T>(), 0, Y->template mutable_data<T>(),\n              &context_);\n          // Add bias term\n          if (bias_multiplier_.size() != M) {\n            // If the helper bias multiplier is not M,\n            // reshape and fill it with one.\n            bias_multiplier_.Resize(M);\n            math::Set<T, Context>(\n                M, static_cast<T>(1),\n                bias_multiplier_.template mutable_data<T>(),\n                &context_);\n          }\n          math::Gemm<T, Context, Engine>(\n              CblasNoTrans, CblasNoTrans, M, N, 1, 1,\n              bias_multiplier_.template data<T>(), b.template data<T>(), 1,\n              Y->template mutable_data<T>(), &context_);\n          if (OutputSize() == 2){\n            auto* Comp_rate = Output(1);\n            Comp_rate->Resize(vector<TIndex>());\n            T* comp_data = Comp_rate->template mutable_data<T>();\n            math::Sum<T, Context>(\n                Mask.size(), Mask.template data<T>(), comp_data, &context_);\n            math::Scale<T, Context>(\n                1, static_cast<T>(1.) / Mask.size(), comp_data, comp_data,\n                &context_);\n          }\n          return true;\n        }\n\n      protected:\n        Tensor<Context> bias_multiplier_;\n    };\n\n  template <typename T, class Context, class Engine=DefaultEngine>\n    class FullyConnectedPruneGradientOp : public Operator<Context> {\n      public:\n        int iter_offset;\n      public:\n        USE_OPERATOR_CONTEXT_FUNCTIONS;\n        FullyConnectedPruneGradientOp\n          (const OperatorDef& operator_def, Workspace* ws)\n          : Operator<Context>(operator_def, ws) { iter_offset = 0; }\n        ~FullyConnectedPruneGradientOp() {}\n\n        bool RunOnDevice() override {\n          const auto& X = Input(0);\n          //const auto& W = Input(1);\n          auto* W_ptr = Output(2);\n          auto& W = *W_ptr;\n          //const auto& Mask = Input(2);\n          auto* Mask_ptr = Output(3);\n          auto& Mask = *Mask_ptr;\n          const auto& dY = Input(3);\n          //const auto& Ag_dW = Input(4);\n          auto* Ag_dW_ptr = Output(4);\n          auto& Ag_dW = *Ag_dW_ptr;\n          // it is also the Input(5)\n          auto* mask_seq_auto = Output(5);\n          // how about get threshold\n          auto& thres = Input(6);\n          //TODO(wyiming): check comp_lb is a float\n          auto& comp_lb = Input(7);\n          DCHECK_GE(X.ndim(), 1);\n          DCHECK_GE(W.ndim(), 2);\n          DCHECK_LE(dY.ndim(), 2);\n          // batch size\n          int M = X.ndim() > 1 ? X.dim32(0) : 1;\n          // Feature dimension\n          int K = X.size() / M;\n          // number of outputs.\n          int N = W.dim32(0);\n          // TODO(wyiming): add this window_size to workspace?\n          int window_size = 100;\n          // TODO(wyiming): this threshold should be\n          // based on distribution of the layer weight\n          float thr = 0.01;\n          DCHECK_EQ(Mask.dim32(0), W.dim32(0));\n          DCHECK_EQ(Mask.dim32(1), W.dim32(1));\n          DCHECK_EQ(Ag_dW.dim32(0), W.dim32(0));\n          DCHECK_EQ(Ag_dW.dim32(1), W.dim32(1));\n          DCHECK_EQ(K, W.size() / W.dim32(0));\n          if (dY.ndim() > 1) {\n            DCHECK_EQ(M, dY.dim32(0));\n            DCHECK_EQ(N, dY.dim32(1));\n          } else {\n            DCHECK_EQ(X.ndim(), 1);\n            DCHECK_EQ(N, dY.size());\n          }\n          auto* dW = Output(0);\n          auto* db = Output(1);\n          dW->ResizeLike(W);\n          db->Resize(N);\n\n          // Compute dW\n          math::Gemm<T, Context, Engine>(\n              CblasTrans, CblasNoTrans, N, K, M, 1,\n              dY.template data<T>(), X.template data<T>(),\n              0, dW->template mutable_data<T>(),\n              &context_);\n\n          comp_r_buf_.Resize(vector<TIndex>());\n          T* comp_data = comp_r_buf_.template mutable_data<T>();\n          math::Sum<T, Context>(\n              Mask.size(), Mask.template data<T>(), comp_data, &context_);\n          math::Scale<T, Context>(\n              1, static_cast<T>(1.) / Mask.size(), comp_data, comp_data,\n              &context_);\n          // update W size window\n          // Notice here we need to maintain state in OP.\n          // This is new in Caffe2.\n          // And this is something we might need to discuss in the future.\n          // at most mask half of the matrix at time\n          // 1. mask dw with previous mask\n          MaskMatrix<T, Context>(Mask.template mutable_data<T>(),\n              dW->template mutable_data<T>(), N, K);\n          if(*comp_data > *(comp_lb.template data<T>())){\n            iter_offset++;\n            if (iter_offset % window_size == 0) {\n              // TODO(wyiming):do the prune here;\n              sum_buffer_.ResizeLike(W);\n              math::Add<T, Context>(W.size(),\n                  W.template mutable_data<T>(),\n                  Ag_dW.template mutable_data<T>(),\n                  sum_buffer_.template mutable_data<T>(),\n                  &context_);\n              mask_seq_auto->ResizeLike(W);\n              T* mask_seq = mask_seq_auto->template mutable_data<T>();\n              math::Set<T, Context>(N*K, static_cast<T>(0),\n                  mask_seq_auto->template mutable_data<T>(), &context_);\n              // 2. find dw below thres but not eq 0\n              int seq_len = MatrixCompare_LT<T>(\n                  Ag_dW_ptr->template mutable_data<T>(),\n                  *thres.template data<T>(), mask_seq, N, K);\n              // 3. use the mask_seq to update W and dw\n              MaskMatrix_Inc<T, Context>(mask_seq,\n                                         dW->template mutable_data<T>(),\n                                         N, K, seq_len, 0);\n              MaskMatrix_Inc<T, Context>(mask_seq,\n                                         W.template mutable_data<T>(),\n                                         N, K, seq_len, 0);\n              MaskMatrix_Inc<T, Context>(mask_seq,\n                                         Mask.template mutable_data<T>(),\n                                         N, K, seq_len, 0);\n              math::Set<T, Context>(N*K, static_cast<T>(0),\n                  Ag_dW.template mutable_data<T>(),\n                  &context_);\n            } else {\n              // add dW to Aggregate dW.\n              AggrDW<T, Context>(\n                  Ag_dW.template mutable_data<T>(),\n                  dW->template mutable_data<T>(),\n                  N, K, &context_);\n            }\n          }\n          if (bias_multiplier_.size() != M) {\n            // If the helper bias multiplier is not M,\n            // reshape and fill it with one.\n            bias_multiplier_.Resize(M);\n            math::Set<T, Context>(\n                M, static_cast<T>(1),\n                bias_multiplier_.template mutable_data<T>(),\n                &context_);\n          }\n          // Compute dB\n          math::Gemv<T, Context>(\n              CblasTrans, M, N, 1, dY.template data<T>(),\n              bias_multiplier_.template data<T>(), 0,\n              db->template mutable_data<T>(),\n              &context_);\n          // Compute dX if necessary.\n          if (OutputSize() == 7) {\n            auto* dX = Output(6);\n            dX->ResizeLike(X);\n            math::Gemm<T, Context, Engine>(\n                CblasNoTrans, CblasNoTrans, M, K, N, 1,\n                dY.template data<T>(), W.template data<T>(),\n                0, dX->template mutable_data<T>(),\n                &context_);\n          }\n\n          return true;\n        }\n\n      protected:\n        Tensor<Context> bias_multiplier_;\n        Tensor<Context> sum_buffer_;\n        Tensor<Context> comp_r_buf_;\n    };\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_OPERATORS_FULLY_CONNECTED_OP_H_\n"
  },
  {
    "path": "caffe2/experiments/operators/fully_connected_op_sparse.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/experiments/operators/fully_connected_op_sparse.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(FC_Sparse, FullyConnectedOp_SPARSE<float, CPUContext>);\n\nOPERATOR_SCHEMA(FC_Sparse).NumInputs(5).NumOutputs(1);\n}  // namespace\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/experiments/operators/fully_connected_op_sparse.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FULLY_CONNECTED_OP_SPARSE_H_\n#define CAFFE2_OPERATORS_FULLY_CONNECTED_OP_SPARSE_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n#ifdef CAFFE2_USE_MKL\n#include <mkl.h>\n#endif  // CAFFE2_USE_MKL\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate<int N>\nusing Shape = std::array<int, N>;\n\ntemplate<int N>\nconst std::vector<TIndex>& shape(Shape<N> vs) {\n  static thread_local std::vector<TIndex> cache;\n  cache.resize(vs.size());\n  for (auto i = 0; i < vs.size(); ++i) {\n    cache[i] = vs[i];\n  }\n  return cache;\n}\n\ninline const std::vector<TIndex>& shape(int i) {\n  return shape<1>(Shape<1>({i}));\n}\n\ninline const std::vector<TIndex>& shape(int i, int j) {\n  return shape<2>(Shape<2>({i, j}));\n}\n\ntemplate <typename T, class Context>\nvoid Sparse_mm(const T* acsr, const int* ia, const int* ja,\n              int m, int k, int n, const T* b, T* c, Context* context);\n\ntemplate<typename T, class Context>\nvoid trans_mat(const T* o, T* t, int m, int n, Context* context);\n\ntemplate <>\nvoid trans_mat<float, CPUContext>(\n    const float* o,\n    float* t,\n    int m,\n    int n,\n    CPUContext* /*context*/) {\n  for(int i = 0; i < m; ++i){\n    for(int j = 0; j < n; ++j){\n      t[j*m+i]=o[i*n+j];\n    }\n  }\n}\n\n// C = A(sparse) * B\n// No transpose;\ntemplate <>\nvoid Sparse_mm<float, CPUContext>(\n    const float* acsr,\n    const int* ia,\n    const int* ja,\n    int m,\n    int k,\n    int n,\n    const float* b,\n    float* c,\n    CPUContext* /*context*/) {\n  float alpha = 1.0, beta = 0.;\n  mkl_scsrmm(\"N\", &m, &n, &k, &alpha, \"GLNC\",\n             acsr, ja, ia, ia+1, b, &n, &beta, c, &n);\n}\n\n}\n\n// This is Caffe's InnerProductOp, with a name that fits its purpose better.\ntemplate <typename T, class Context, class Engine=DefaultEngine>\nclass FullyConnectedOp_SPARSE final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  FullyConnectedOp_SPARSE(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  ~FullyConnectedOp_SPARSE() {}\n\n  bool RunOnDevice() override {\n    const auto& Xt = Input(0); // transposed X\n    const auto& Wcsr = Input(1);\n    const auto& iw = Input(2);\n    const auto& jw = Input(3);\n    // Notice that we do not need to transpose b\n    const auto& b = Input(4);\n    auto* Yt = Output(0); //transposed Y\n    // here we assume X is k-by-m\n    CAFFE_ENFORCE_EQ(Xt.ndim(), 2);\n    CAFFE_ENFORCE_EQ(b.ndim(), 1);\n    // batch size\n    int K = Xt.ndim() > 1 ? Xt.dim32(0) : 1;\n    // Feature dimension\n    int M = Xt.size() / K;\n    // number of outputs.\n    int N = iw.dim32(0)-1;\n    CAFFE_ENFORCE_EQ(N, b.dim32(0));\n    Yt->Resize(shape(N, M));\n\n    // Y' = W * X';\n    Sparse_mm<T, Context>(\n      Wcsr.template data<T>(), iw.template data<int>(),\n      jw.template data<int>(), N, K, M, Xt.template data<T>(),\n      Yt->template mutable_data<T>(), &context_);\n    // Add bias term\n    if (bias_multiplier_.size() != M) {\n      // If the helper bias multiplier is not M, reshape and fill it with one.\n      bias_multiplier_.Resize(shape(M));\n      math::Set<T, Context>(\n          M, static_cast<T>(1), bias_multiplier_.template mutable_data<T>(),\n          &context_);\n    }\n    math::Gemm<T, Context, Engine>(\n        CblasNoTrans, CblasNoTrans, N, M, 1, 1,\n        b.template data<T>(), bias_multiplier_.template data<T>(), 1,\n        Yt->template mutable_data<T>(), &context_);\n    return true;\n  }\n\n protected:\n  Tensor<Context> bias_multiplier_;\n};\n\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_OPERATORS_FULLY_CONNECTED_OP_H_\n"
  },
  {
    "path": "caffe2/experiments/operators/funhash_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/experiments/operators/funhash_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(FunHash, FunHashOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(FunHashGradient, FunHashGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(FunHash)\n    .NumInputs(4, 5)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nThis layer compresses a fully-connected layer for sparse inputs\nvia hashing.\nIt takes four required inputs and an optional fifth input.\nThe first three inputs `scalars`, `indices`, and `segment_ids` are\nthe sparse segmented representation of sparse data, which are the\nsame as the last three inputs of the `SparseSortedSegmentWeightedSum`\noperator. If the argument `num_segments` is specified, it would be used\nas the first dimension for the output; otherwise it would be derived\nfrom the maximum segment ID.\n\nThe fourth input is a 1D weight vector. Each entry of the fully-connected\nlayer would be randomly mapped from one of the entries in this vector.\n\nWhen the optional fifth input vector is present, each weight of the\nfully-connected layer would be the linear combination of K entries\nrandomly mapped from the weight vector, provided the input\n(length-K vector) serves as the coefficients.\n)DOC\")\n    .Input(0, \"scalars\", \"Values of the non-zero entries of the sparse data.\")\n    .Input(1, \"indices\", \"Indices to the non-zero valued features.\")\n    .Input(2, \"segment_ids\",\n        \"Segment IDs corresponding to the non-zero entries.\")\n    .Input(3, \"weight\", \"Weight vector\")\n    .Input(4, \"alpha\",\n        \"Optional coefficients for linear combination of hashed weights.\")\n    .Output(0, \"output\",\n        \"Output tensor with the first dimension equal to the number \"\n        \"of segments.\")\n    .Arg(\"num_outputs\", \"Number of outputs\")\n    .Arg(\"num_segments\", \"Number of segments\");\n\nOPERATOR_SCHEMA(FunHashGradient)\n    .NumInputs(5, 6)\n    .NumOutputs(1, 2);\n\nclass GetFunHashGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    if (def_.input_size() == 4) {\n      return SingleGradientDef(\n          \"FunHashGradient\", \"\",\n          vector<string>{GO(0), I(0), I(1), I(2), I(3)},\n          vector<string>{GI(3)});\n    }\n    // def_.input_size() == 5\n    return SingleGradientDef(\n        \"FunHashGradient\", \"\",\n        vector<string>{GO(0), I(0), I(1), I(2), I(3), I(4)},\n        vector<string>{GI(3), GI(4)});\n  }\n};\n\nREGISTER_GRADIENT(FunHash, GetFunHashGradient);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/experiments/operators/funhash_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FUNHASH_OP_H_\n#define CAFFE2_OPERATORS_FUNHASH_OP_H_\n\n#include <xxhash.h>\n#include <array>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\n#define SIGN_MAGIC 0x9e3779b97f4a7c15\n#define INDEX_MAGIC 0xf39cc0605cedc834\n\n#define USE_SIGN\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass FunHashOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  FunHashOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        num_outputs_(\n            OperatorBase::GetSingleArgument<TIndex>(\"num_outputs\", -1)),\n        num_segments_(\n            OperatorBase::GetSingleArgument<TIndex>(\"num_segments\", -1)),\n        seed_(OperatorBase::GetSingleArgument<uint64_t>(\"seed\", 0)) {\n    CAFFE_ENFORCE(\n        OperatorBase::HasArgument(\"num_outputs\"),\n        \"Argument `num_outputs` is missing.\");\n    // If alpha is provided, use adaptive hashing parameterized by alpha.\n    adaptive_ = (InputSize() == 5);\n  }\n\n  bool RunOnDevice() override {\n    const auto& val = Input(0);\n    const auto& key = Input(1);\n    const auto& seg = Input(2);\n    const auto& weight = Input(3);\n\n    TIndex num_alpha = 1;\n    if (adaptive_) {\n      const auto& alpha = Input(4);\n      num_alpha = alpha.dim(0);\n    }\n\n    const auto* seg_data = seg.template data<int>();\n\n    TIndex num_weight = weight.dim(0);\n    TIndex num_nz_ent = seg.dim(0);\n\n    TIndex n_segments = num_segments_;\n    if (num_segments_ == -1) {\n      for (TIndex i = 0; i < num_nz_ent; ++i) {\n        if (seg_data[i] > n_segments) {\n          n_segments = seg_data[i];\n        }\n      }\n      ++n_segments;\n    }\n\n    auto* output = Output(0);\n    output->Resize(n_segments, num_outputs_);\n\n    T* output_data = output->template mutable_data<T>();\n\n    memset(output_data, 0, sizeof(T) * n_segments * num_outputs_);\n\n    const auto* weight_data = weight.template data<T>();\n    const auto* alpha_data = adaptive_ ? Input(4).template data<T>() : 0;\n    const auto* val_data = val.template data<T>();\n    const auto* key_data = key.template data<TIndex>();\n\n    for (TIndex j = 0; j < num_nz_ent; ++j) {\n      TIndex cur_seg = seg_data[j];\n      TIndex cur_key = key_data[j];\n      T cur_val = val_data[j];\n      TIndex output_stride = cur_seg * num_outputs_;\n      for (TIndex i = 0; i < num_outputs_; ++i) {\n        T sum = 0;\n        for (TIndex k = 0; k < num_alpha; ++k) {\n          uint64_t hash;\n          // The hash function takes as input four integers:\n          // 1. feature index\n          // 2. output index\n          // 3. alpha index\n          // 4. magic number: SIGN_MAGIC for sign (-1/+1)\n          //                  INDEX_MAGIC for weight index\n          hash_data[0] = cur_key;\n          hash_data[1] = i;\n          hash_data[2] = k;\n\n          hash_data[3] = INDEX_MAGIC;\n          hash = XXH64(hash_data.data(), hash_data.size(), seed_);\n          TIndex index = hash % num_weight;\n\n          T cur_weight = weight_data[index];\n#ifdef USE_SIGN\n          hash_data[3] = SIGN_MAGIC;\n          hash = XXH64(hash_data.data(), hash_data.size(), seed_);\n          if (hash % 2) {\n            cur_weight = -cur_weight;\n          }\n#endif // USE_SIGN\n\n          if (adaptive_) {\n            sum += cur_weight * alpha_data[k];\n          } else {\n            sum += cur_weight;\n          }\n        }\n        output_data[output_stride + i] += sum * cur_val;\n      }\n    }\n\n    return true;\n  }\n\n protected:\n  TIndex num_outputs_;\n  TIndex num_segments_;\n  uint64_t seed_;\n  std::array<uint64_t, 4> hash_data;\n  bool adaptive_;\n};\n\ntemplate <typename T, class Context>\nclass FunHashGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  FunHashGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        num_outputs_(\n            OperatorBase::GetSingleArgument<TIndex>(\"num_outputs\", -1)),\n        seed_(OperatorBase::GetSingleArgument<uint64_t>(\"seed\", 0)) {\n    adaptive_ = (InputSize() == 6);\n  }\n\n  bool RunOnDevice() override {\n    const auto& grad_out = Input(0);\n    const auto& val = Input(1);\n    const auto& key = Input(2);\n    const auto& seg = Input(3);\n    const auto& weight = Input(4);\n\n    TIndex num_alpha = 1;\n    T* grad_alpha_data = 0;\n\n    if (adaptive_) {\n      const auto& alpha = Input(5);\n      num_alpha = alpha.dim(0);\n      auto* grad_alpha = Output(1);\n      grad_alpha->ResizeLike(alpha);\n      grad_alpha_data = grad_alpha->template mutable_data<T>();\n      memset(grad_alpha_data, 0, sizeof(T) * num_alpha);\n    }\n\n    const auto* seg_data = seg.template data<int>();\n\n    TIndex num_weight = weight.dim(0);\n    TIndex num_nz_ent = seg.dim(0);\n\n    auto* grad_weight = Output(0);\n    grad_weight->ResizeLike(weight);\n    T* grad_weight_data = grad_weight->template mutable_data<T>();\n\n    const auto* grad_out_data = grad_out.template data<T>();\n    const auto* weight_data = weight.template data<T>();\n    const auto* alpha_data = adaptive_ ? Input(5).template data<T>() : 0;\n    const auto* val_data = val.template data<T>();\n    const auto* key_data = key.template data<TIndex>();\n\n    memset(grad_weight_data, 0, sizeof(T) * num_weight);\n\n    for (TIndex j = 0; j < num_nz_ent; ++j) {\n      TIndex cur_seg = seg_data[j];\n      TIndex cur_key = key_data[j];\n      T cur_val = val_data[j];\n      TIndex grad_out_stride = cur_seg * num_outputs_;\n      for (TIndex i = 0; i < num_outputs_; ++i) {\n        T grad_out_scale = grad_out_data[grad_out_stride + i] * cur_val;\n        for (TIndex k = 0; k < num_alpha; ++k) {\n          uint64_t hash;\n          hash_data[0] = cur_key;\n          hash_data[1] = i;\n          hash_data[2] = k;\n\n          hash_data[3] = INDEX_MAGIC;\n          hash = XXH64(hash_data.data(), hash_data.size(), seed_);\n          TIndex index = hash % num_weight;\n\n          T cur_grad_out_scale = grad_out_scale;\n#ifdef USE_SIGN\n          hash_data[3] = SIGN_MAGIC;\n          hash = XXH64(hash_data.data(), hash_data.size(), seed_);\n          if (hash % 2) {\n            cur_grad_out_scale = -cur_grad_out_scale;\n          }\n#endif // USE_SIGN\n\n          if (adaptive_) {\n            grad_alpha_data[k] += cur_grad_out_scale * weight_data[index];\n            grad_weight_data[index] += alpha_data[k] * cur_grad_out_scale;\n          } else {\n            grad_weight_data[index] += cur_grad_out_scale;\n          }\n        }\n      }\n    }\n    return true;\n  }\n\n protected:\n  TIndex num_outputs_;\n  uint64_t seed_;\n  std::array<uint64_t, 4> hash_data;\n  bool adaptive_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_FUNHASH_OP_H_\n"
  },
  {
    "path": "caffe2/experiments/operators/sparse_funhash_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/experiments/operators/sparse_funhash_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(SparseFunHash, SparseFunHashOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    SparseFunHashGradient,\n    SparseFunHashGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(SparseFunHash)\n    .NumInputs(4, 5)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nThis layer compresses a fully-connected layer for sparse inputs\nvia hashing.\nIt takes four required inputs and an option fifth input.\nThe first three inputs `scalars`, `indices`, and `segment_ids` are\nthe sparse segmented representation of sparse data, which are the\nsame as the last three inputs of the `SparseSortedSegmentWeightedSum`\noperator. If the argument `num_segments` is specified, it would be used\nas the first dimension for the output; otherwise it would be derived\nfrom the maximum segment ID.\n\nThe fourth input is a 1D weight vector. Each entry of the fully-connected\nlayer would be randomly mapped from one of the entries in this vector.\n\nWhen the optional fifth input vector is present, each weight of the\nfully-connected layer would be the linear combination of K entries\nrandomly mapped from the weight vector, provided the input\n(length-K vector) serves as the coefficients.\n)DOC\")\n    .Input(0, \"scalars\", \"Values of the non-zero entries of the sparse data.\")\n    .Input(1, \"indices\", \"Indices to the non-zero valued features.\")\n    .Input(\n        2,\n        \"segment_ids\",\n        \"Segment IDs corresponding to the non-zero entries.\")\n    .Input(3, \"weight\", \"Weight vector\")\n    .Input(\n        4,\n        \"alpha\",\n        \"Optional coefficients for linear combination of hashed weights.\")\n    .Output(\n        0,\n        \"output\",\n        \"Output tensor with the first dimension equal to the number \"\n        \"of segments.\")\n    .Arg(\"num_outputs\", \"Number of outputs\")\n    .Arg(\"num_segments\", \"Number of segments\");\n\nOPERATOR_SCHEMA(SparseFunHashGradient).NumInputs(5, 6).NumOutputs(2, 3);\n\nclass GetSparseFunHashGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    if (def_.input_size() == 4) {\n      return SingleGradientDef(\n          \"SparseFunHashGradient\",\n          \"\",\n          vector<string>{GO(0), I(0), I(1), I(2), I(3)},\n          vector<string>{GI_V(3), GI_I(3)});\n    }\n    // def_.input_size() == 5\n    return SingleGradientDef(\n        \"SparseFunHashGradient\",\n        \"\",\n        vector<string>{GO(0), I(0), I(1), I(2), I(3), I(4)},\n        vector<string>{GI_V(3), GI_I(3), GI(4)});\n  }\n};\n\nREGISTER_GRADIENT(SparseFunHash, GetSparseFunHashGradient);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/experiments/operators/sparse_funhash_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SPARSE_FUNHASH_OP_H_\n#define CAFFE2_OPERATORS_SPARSE_FUNHASH_OP_H_\n\n#include <xxhash.h>\n#include <array>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\n#define HASH_MAGIC 0x9e3779b97f4a7c15\n\n#define USE_SIGN\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SparseFunHashOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SparseFunHashOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        num_outputs_(\n            OperatorBase::GetSingleArgument<TIndex>(\"num_outputs\", -1)),\n        num_segments_(\n            OperatorBase::GetSingleArgument<TIndex>(\"num_segments\", -1)),\n        seed_(OperatorBase::GetSingleArgument<uint64_t>(\"seed\", 0)) {\n    CAFFE_ENFORCE(\n        OperatorBase::HasArgument(\"num_outputs\"),\n        \"Argument `num_outputs` is missing.\");\n    // If alpha is provided, use adaptive hashing parameterized by alpha.\n    adaptive_ = (InputSize() == 5);\n  }\n\n  bool RunOnDevice() override {\n    const auto& val = Input(0);\n    const auto& key = Input(1);\n    const auto& seg = Input(2);\n    const auto& weight = Input(3);\n\n    TIndex num_alpha = 1;\n    if (adaptive_) {\n      const auto& alpha = Input(4);\n      num_alpha = alpha.dim(0);\n    }\n\n    const auto* seg_data = seg.template data<int>();\n\n    TIndex num_weight = weight.dim(0);\n    TIndex num_nz_ent = seg.dim(0);\n\n    TIndex n_segments = num_segments_;\n    if (num_segments_ == -1) {\n      for (TIndex i = 0; i < num_nz_ent; ++i) {\n        if (seg_data[i] > n_segments) {\n          n_segments = seg_data[i];\n        }\n      }\n      ++n_segments;\n    }\n\n    auto* output = Output(0);\n    output->Resize(n_segments, num_outputs_);\n\n    T* output_data = output->template mutable_data<T>();\n\n    memset(output_data, 0, sizeof(T) * n_segments * num_outputs_);\n\n    const auto* weight_data = weight.template data<T>();\n    const auto* alpha_data = adaptive_ ? Input(4).template data<T>() : 0;\n    const auto* val_data = val.template data<T>();\n    const auto* key_data = key.template data<TIndex>();\n\n    for (TIndex j = 0; j < num_nz_ent; ++j) {\n      TIndex cur_seg = seg_data[j];\n      TIndex cur_key = key_data[j];\n      T cur_val = val_data[j];\n      TIndex output_stride = cur_seg * num_outputs_;\n      for (TIndex i = 0; i < num_outputs_; ++i) {\n        T sum = 0;\n        for (TIndex k = 0; k < num_alpha; ++k) {\n          // The hash function takes as input three integers:\n          // 1. feature index\n          // 2. output index\n          // 3. alpha index\n          // 4. magic number to improve hashing\n          hash_data[0] = cur_key;\n          hash_data[1] = i;\n          hash_data[2] = k;\n          hash_data[3] = HASH_MAGIC;\n\n          uint64_t hash = XXH64(hash_data.data(), hash_data.size(), seed_);\n\n#ifdef USE_SIGN\n          // Use the least significant bit for sign, the rest for weights.\n          TIndex index = (hash >> 1) % num_weight;\n          T cur_weight = weight_data[index];\n          if (hash & 1) {\n            cur_weight = -cur_weight;\n          }\n#else\n          TIndex index = hash % num_weight;\n          T cur_weight = weight_data[index];\n#endif\n\n          if (adaptive_) {\n            sum += cur_weight * alpha_data[k];\n          } else {\n            sum += cur_weight;\n          }\n        }\n        output_data[output_stride + i] += sum * cur_val;\n      }\n    }\n\n    return true;\n  }\n\n protected:\n  TIndex num_outputs_;\n  TIndex num_segments_;\n  uint64_t seed_;\n  std::array<uint64_t, 4> hash_data;\n  bool adaptive_;\n};\n\ntemplate <typename T, class Context>\nclass SparseFunHashGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SparseFunHashGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        num_outputs_(\n            OperatorBase::GetSingleArgument<TIndex>(\"num_outputs\", -1)),\n        seed_(OperatorBase::GetSingleArgument<uint64_t>(\"seed\", 0)) {\n    adaptive_ = (InputSize() == 6);\n  }\n\n  bool RunOnDevice() override {\n    const auto& grad_out = Input(0);\n    const auto& val = Input(1);\n    const auto& key = Input(2);\n    const auto& seg = Input(3);\n    const auto& weight = Input(4);\n\n    TIndex num_alpha = 1;\n    T* grad_alpha_data = 0;\n\n    if (adaptive_) {\n      const auto& alpha = Input(5);\n      num_alpha = alpha.dim(0);\n      auto* grad_alpha = Output(2);\n      grad_alpha->ResizeLike(alpha);\n      grad_alpha_data = grad_alpha->template mutable_data<T>();\n      memset(grad_alpha_data, 0, sizeof(T) * num_alpha);\n    }\n\n    const auto* seg_data = seg.template data<int>();\n\n    TIndex num_weight = weight.dim(0);\n    TIndex num_nz_ent = seg.dim(0);\n\n    TIndex grad_weight_size = num_nz_ent * num_outputs_ * num_alpha;\n    auto* grad_weight_val = Output(0);\n    grad_weight_val->Resize(grad_weight_size);\n    T* grad_weight_val_data = grad_weight_val->template mutable_data<T>();\n\n    auto* grad_weight_ind = Output(1);\n    grad_weight_ind->Resize(grad_weight_size);\n    auto* grad_weight_ind_data =\n        grad_weight_ind->template mutable_data<TIndex>();\n\n    const auto* grad_out_data = grad_out.template data<T>();\n    const auto* weight_data = weight.template data<T>();\n    const auto* alpha_data = adaptive_ ? Input(5).template data<T>() : 0;\n    const auto* val_data = val.template data<T>();\n    const auto* key_data = key.template data<TIndex>();\n\n    TIndex w_ind = 0;\n    for (TIndex j = 0; j < num_nz_ent; ++j) {\n      TIndex cur_seg = seg_data[j];\n      TIndex cur_key = key_data[j];\n      T cur_val = val_data[j];\n      TIndex grad_out_stride = cur_seg * num_outputs_;\n      for (TIndex i = 0; i < num_outputs_; ++i) {\n        T grad_out_scale = grad_out_data[grad_out_stride + i] * cur_val;\n        for (TIndex k = 0; k < num_alpha; ++k) {\n          hash_data[0] = cur_key;\n          hash_data[1] = i;\n          hash_data[2] = k;\n          hash_data[3] = HASH_MAGIC;\n\n          uint64_t hash = XXH64(hash_data.data(), hash_data.size(), seed_);\n\n          T cur_grad_out_scale = grad_out_scale;\n#ifdef USE_SIGN\n          TIndex index = (hash >> 1) % num_weight;\n          if (hash & 1) {\n            cur_grad_out_scale = -cur_grad_out_scale;\n          }\n#else\n          TIndex index = hash % num_weight;\n#endif\n\n          if (adaptive_) {\n            grad_alpha_data[k] += cur_grad_out_scale * weight_data[index];\n            grad_weight_val_data[w_ind] = alpha_data[k] * cur_grad_out_scale;\n          } else {\n            grad_weight_val_data[w_ind] = cur_grad_out_scale;\n          }\n          grad_weight_ind_data[w_ind] = index;\n          ++w_ind;\n        }\n      }\n    }\n    return true;\n  }\n\n protected:\n  TIndex num_outputs_;\n  uint64_t seed_;\n  std::array<uint64_t, 4> hash_data;\n  bool adaptive_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SPARSE_FUNHASH_OP_H_\n"
  },
  {
    "path": "caffe2/experiments/operators/sparse_matrix_reshape_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/experiments/operators/sparse_matrix_reshape_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(SparseMatrixReshape, SparseMatrixReshapeOp<CPUContext>);\n\nOPERATOR_SCHEMA(SparseMatrixReshape)\n    .NumInputs(2)\n    .NumOutputs(2)\n    .AllowInplace({{0, 0}, {1, 1}})\n    .SetDoc(R\"DOC(\nCompute the indices of the reshaped sparse matrix.\n\nIt takes two 1D tensors as input: the column indices (in int64) and\nthe row indices (in int), which correspond to `INDICES` and `SEGMENT_IDS`\nin `SparseSortedSegment` family.\nIt outputs the corresponding reshaped column and row indices.\n\nTwo arguments are required:\nan argument `old_shape` specifies the original shape of the matrix,\nand `new_shape` specifies the new shape.\nOne of the dimension in `old_shape` and `new_shape` can be -1.\nThe valid combinations are listed below, where p, q, r, s are\nstrictly positive integers.\n\nold_shape=(p, q)\nnew_shape=(r, s)\n\nold_shape=(p, q)\nnew_shape=(-1, s)\n\nold_shape=(p, q)\nnew_shape=(r, -1)\n\nold_shape=(-1, q)\nnew_shape=(-1, s)\n\nNote that only the first dimension in `old_shape` can be -1. In that case\nthe second dimension in `new_shape` must NOT be -1.\n)DOC\")\n    .Arg(\"old_shape\", \"Old shape.\")\n    .Arg(\"new_shape\", \"New shape.\")\n    .Input(0, \"old_col\", \"Original column indices.\")\n    .Input(1, \"old_row\", \"Original row indices.\")\n    .Output(0, \"new_col\", \"New column indices.\")\n    .Output(1, \"new_row\", \"New row indices.\");\n\nSHOULD_NOT_DO_GRADIENT(SparseMatrixReshape);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/experiments/operators/sparse_matrix_reshape_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SPARSE_MATRIX_RESHAPE_H_\n#define CAFFE2_OPERATORS_SPARSE_MATRIX_RESHAPE_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass SparseMatrixReshapeOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SparseMatrixReshapeOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    CAFFE_ENFORCE(\n        OperatorBase::HasArgument(\"old_shape\"),\n        \"Argument `old_shape` is missing.\");\n    CAFFE_ENFORCE(\n        OperatorBase::HasArgument(\"new_shape\"),\n        \"Argument `new_shape` is missing.\");\n\n    vector<TIndex> old_shape =\n        OperatorBase::GetRepeatedArgument<TIndex>(\"old_shape\");\n    vector<TIndex> new_shape =\n        OperatorBase::GetRepeatedArgument<TIndex>(\"new_shape\");\n\n    CAFFE_ENFORCE(\n        old_shape.size() == 2,\n        \"Argument `old_shape` must contain exactly two integers.\");\n    CAFFE_ENFORCE(\n        new_shape.size() == 2,\n        \"Argument `new_shape` must contain exactly two integers.\");\n\n    CAFFE_ENFORCE(\n        old_shape[1] > 0,\n        \"The second dimension in argument `old_shape` must be positive.\");\n\n    old_stride_ = old_shape[1];\n\n    if (old_shape[0] == -1) {\n      CAFFE_ENFORCE(\n          new_shape[1] > 0,\n          \"The second dimension in `new_shape` must be positive.\");\n    } else {\n      CAFFE_ENFORCE(\n          old_shape[0] > 0,\n          \"The first dimension in `old_shape` must be positive.\");\n\n      TIndex matrix_size = old_shape[0] * old_shape[1];\n\n      if (new_shape[0] == -1) {\n        CAFFE_ENFORCE(\n            new_shape[1] > 0,\n            \"Only one dimension in argument `new_shape` can be -1.\");\n        CAFFE_ENFORCE(\n            matrix_size % new_shape[1] == 0,\n            \"Argument `new_shape` does not agree with `old_shape`.\");\n      } else {\n        CAFFE_ENFORCE(\n            new_shape[0] > 0 && (new_shape[1] == -1 || new_shape[1] > 0),\n            \"Dimensions in argument `new_shape` must be positive or -1.\");\n        if (new_shape[1] == -1) {\n          CAFFE_ENFORCE(\n              matrix_size % new_shape[0] == 0,\n              \"Argument `new_shape` does not agree with `old_shape`.\");\n          new_shape[1] = matrix_size / new_shape[0];\n        } else {\n          CAFFE_ENFORCE(\n              new_shape[0] * new_shape[1] == matrix_size,\n              \"Argument `new_shape` does not agree with `old_shape`.\");\n        }\n      }\n    }\n    new_stride_ = new_shape[1];\n  }\n\n  bool RunOnDevice() override {\n    auto& old_col = Input(0);\n    CAFFE_ENFORCE(old_col.ndim() == 1, \"Row index tensor must be 1-D.\");\n    auto& old_row = Input(1);\n    CAFFE_ENFORCE(old_row.ndim() == 1, \"Column index tensor must be 1-D.\");\n\n    const auto nnz = old_col.size();\n    CAFFE_ENFORCE(\n        old_row.size() == nnz,\n        \"Column and row tensors must have the same size.\");\n\n    auto* new_col = Output(0);\n    auto* new_row = Output(1);\n    new_col->Resize(nnz);\n    new_row->Resize(nnz);\n\n    const auto* old_col_data = old_col.template data<TIndex>();\n    const auto* old_row_data = old_row.template data<int>();\n\n    auto* new_col_data = new_col->template mutable_data<TIndex>();\n    auto* new_row_data = new_row->template mutable_data<int>();\n\n    for (int i = 0; i < nnz; ++i) {\n      TIndex offset = old_row_data[i] * old_stride_ + old_col_data[i];\n      new_row_data[i] = offset / new_stride_;\n      new_col_data[i] = offset % new_stride_;\n    }\n\n    return true;\n  }\n\n private:\n  TIndex old_stride_;\n  TIndex new_stride_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SPARSE_MATRIX_RESHAPE_H_\n"
  },
  {
    "path": "caffe2/experiments/operators/tt_contraction_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/experiments/operators/tt_contraction_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(TTContraction, TTContractionOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(TTContraction)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nTensor contraction C = A * B\n)DOC\")\n    .Arg(\"K\", \"i_{k-1} * r_k\")\n    .Arg(\"M\", \"r_{k-1} * o_{k-1}\")\n    .Arg(\"N\", \"o_k\")\n    .Input(0, \"A\", \"2D matrix of size (K x M)\")\n    .Input(1, \"B\", \"tensor\")\n    .Output(0, \"C\", \"contracted tensor\");\n\nREGISTER_CPU_OPERATOR(\n    TTContractionGradient,\n    TTContractionGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(TTContractionGradient).NumInputs(3).NumOutputs(2);\n\nclass GetTTContractionGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"TTContractionGradient\",\n        \"\",\n        vector<string>{GO(0), I(0), I(1)},\n        vector<string>{GI(0), GI(1)},\n        Def().arg());\n  }\n};\n\nREGISTER_GRADIENT(TTContraction, GetTTContractionGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/experiments/operators/tt_contraction_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_TT_CONTRACTION_OP_H_\n#define CAFFE2_OPERATORS_TT_CONTRACTION_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nclass TTContractionOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  TTContractionOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        K_(OperatorBase::GetSingleArgument<TIndex>(\"K\", 0)),\n        M_(OperatorBase::GetSingleArgument<TIndex>(\"M\", 0)),\n        N_(OperatorBase::GetSingleArgument<TIndex>(\"N\", 0)) {\n    CAFFE_ENFORCE(OperatorBase::HasArgument(\"K\"), \"Argument `K` is missing.\");\n    CAFFE_ENFORCE(OperatorBase::HasArgument(\"M\"), \"Argument `M` is missing.\");\n    CAFFE_ENFORCE(OperatorBase::HasArgument(\"N\"), \"Argument `N` is missing.\");\n  }\n\n  bool RunOnDevice() override {\n    const auto& A = Input(0);\n    const auto& B = Input(1);\n    auto* C = Output(0);\n\n    CAFFE_ENFORCE(A.ndim() == 2, A.ndim());\n\n    TIndex A_size = A.size_from_dim(0);\n    TIndex B_size = B.size_from_dim(0);\n\n    CAFFE_ENFORCE(\n        K_ * M_ == A_size,\n        \"Argument `K` and `M` do not agree with the size of A.\");\n\n    CAFFE_ENFORCE(\n        B_size % (K_ * N_) == 0,\n        \"Argument `K` and `N` do not agree with the size of B.\");\n\n    TIndex D_ = B_size / (K_ * N_);\n\n    TIndex C_size = D_ * M_ * N_;\n    C->Resize(vector<TIndex>{C_size});\n\n    TIndex B_stride = K_ * N_;\n    TIndex C_stride = M_ * N_;\n\n    const T* A_data = A.template data<T>();\n    const T* B_data = B.template data<T>();\n    T* C_data = C->template mutable_data<T>();\n\n    for (TIndex B_index = 0; B_index < B_size; B_index += B_stride) {\n      math::Gemm<T, Context, Engine>(\n          CblasTrans,\n          CblasNoTrans,\n          M_, N_, K_, 1,\n          A_data,\n          B_data + B_index,\n          0,\n          C_data,\n          &context_);\n      C_data += C_stride;\n    }\n\n    return true;\n  }\n\n protected:\n  TIndex K_;\n  TIndex M_;\n  TIndex N_;\n};\n\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nclass TTContractionGradientOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  TTContractionGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        K_(OperatorBase::GetSingleArgument<TIndex>(\"K\", 0)),\n        M_(OperatorBase::GetSingleArgument<TIndex>(\"M\", 0)),\n        N_(OperatorBase::GetSingleArgument<TIndex>(\"N\", 0)) {}\n\n  bool RunOnDevice() override {\n    const auto& G = Input(0);\n    const auto& A = Input(1);\n    const auto& B = Input(2);\n    auto* dA = Output(0);\n    auto* dB = Output(1);\n\n    TIndex G_size = G.size_from_dim(0);\n    TIndex D_ = G_size / (M_ * N_);\n\n    TIndex dB_size = D_ * K_ * N_;\n\n    dA->Resize(A.dims());\n    dB->Resize(B.dims());\n\n    TIndex B_stride = K_ * N_;\n    TIndex G_stride = M_ * N_;\n\n    const T* G_data = G.template data<T>();\n    const T* A_data = A.template data<T>();\n    const T* B_data = B.template data<T>();\n\n    T* dA_data = dA->template mutable_data<T>();\n    T* dB_data = dB->template mutable_data<T>();\n\n    const T* G_ptr = G_data;\n    for (TIndex B_index = 0; B_index < dB_size; B_index += B_stride) {\n      math::Gemm<T, Context, Engine>(\n          CblasNoTrans,\n          CblasTrans,\n          K_, M_, N_, 1,\n          B_data + B_index,\n          G_ptr,\n          B_index == 0 ? 0 : 1,\n          dA_data,\n          &context_);\n      G_ptr += G_stride;\n    }\n\n    G_ptr = G_data;\n    for (TIndex B_index = 0; B_index < dB_size; B_index += B_stride) {\n      math::Gemm<T, Context, Engine>(\n          CblasNoTrans,\n          CblasNoTrans,\n          K_, N_, M_, 1,\n          A_data,\n          G_ptr,\n          0,\n          dB_data + B_index,\n          &context_);\n      G_ptr += G_stride;\n    }\n\n    return true;\n  }\n\n protected:\n  TIndex K_;\n  TIndex M_;\n  TIndex N_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_TT_CONTRACTION_OP_H_\n"
  },
  {
    "path": "caffe2/experiments/operators/tt_contraction_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/experiments/operators/tt_contraction_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(TTContraction, TTContractionOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    TTContractionGradient,\n    TTContractionGradientOp<float, CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/experiments/operators/tt_pad_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/experiments/operators/tt_pad_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(TTPad, TTPadOp<float, CPUContext>);\nOPERATOR_SCHEMA(TTPad).NumInputs(1).NumOutputs(2).EnforceInplace({{0, 0}});\n\nREGISTER_CPU_OPERATOR(TTPadGradient, TTPadGradientOp<float, CPUContext>);\nOPERATOR_SCHEMA(TTPadGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .EnforceInplace({{0, 0}});\n\nclass GetTTPadGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"TTPadGradient\",\n        \"\",\n        vector<string>{GO(0), O(1)},\n        vector<string>{GI(0)},\n        Def().arg());\n  }\n};\n\nREGISTER_GRADIENT(TTPad, GetTTPadGradient);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/experiments/operators/tt_pad_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_TT_PAD_OP_H_\n#define CAFFE2_OPERATORS_TT_PAD_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nclass TTPadOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  TTPadOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        scale_(OperatorBase::GetSingleArgument<TIndex>(\"scale\", 0)) {\n    CAFFE_ENFORCE(\n        OperatorBase::HasArgument(\"scale\"), \"Argument `scale` is missing.\");\n  }\n\n  bool RunOnDevice() override {\n    const auto& X = Input(0);\n    auto* X_pad = Output(0);\n    CAFFE_ENFORCE(&X == X_pad);\n\n    CAFFE_ENFORCE(X.ndim() == 2, X.ndim());\n\n    auto X_dim0 = X.dim(0);\n    auto X_dim1 = X.dim(1);\n\n    auto* X_orig_dim0 = Output(1);\n    X_orig_dim0->Resize(1);\n    *X_orig_dim0->template mutable_data<TIndex>() = X_dim0;\n\n    if (X_dim0 % scale_ != 0) {\n      TIndex padded_dim0 = (X_dim0 / scale_ + 1) * scale_;\n      auto dim0_diff = padded_dim0 - X_dim0;\n      // set growthPct to the upper bound percentage: (100 * scale_ / X_dim0)\n      X_pad->template Extend(dim0_diff, 100 * scale_ / X_dim0, &context_);\n\n      auto* X_pad_data = X_pad->template mutable_data<T>();\n      TIndex X_size = X_dim0 * X_dim1;\n      memset(X_pad_data + X_size, 0, dim0_diff * X_dim1 * sizeof(T));\n    }\n\n    return true;\n  }\n\n protected:\n  TIndex scale_;\n};\n\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nclass TTPadGradientOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  TTPadGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    const auto& G = Input(0);\n    auto* output = Output(0);\n    CAFFE_ENFORCE(&G == output);\n\n    auto old_dim0 = *Input(1).template data<TIndex>();\n    auto new_dim0 = G.dim(0);\n    auto dim1 = G.dim(1);\n\n    if (old_dim0 < new_dim0) {\n      output->Shrink(old_dim0);\n    }\n\n    return true;\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_TT_PAD_OP_H_\n"
  },
  {
    "path": "caffe2/experiments/python/SparseTransformer.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package SparseTransformer\n# Module caffe2.experiments.python.SparseTransformer\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import workspace\nimport scipy.sparse\n\n\nclass NetDefNode():\n\n    def __init__(self, name, optype, p=None, op=None):\n        self.name = name\n        self.optype = optype\n        self.ops = {}\n        self.prev = {}\n        self.insertInput(p)\n        self.visited = False\n        self.op = op\n\n    def insertInput(self, p):\n        \"\"\"\n        Insert input of this op\n        also maintain the output of previous op\n        p: a node or a list of node\n        \"\"\"\n        if isinstance(p, list):\n            for i in p:\n                self.prev[i.name] = i\n                i.ops[self.name] = self\n        elif isinstance(p, NetDefNode):\n            self.prev[p.name] = p\n            p.ops[self.name] = self\n\n    def deleteInput(self, p):\n        if isinstance(p, NetDefNode):\n            del self.prev[p.name]\n            del p.ops[self.name]\n\n\ndef maskNallocate(weight_name):\n    \"\"\"\n    Combine mask and weights\n    create wcsr, iw, jw, return their names\n    \"\"\"\n    w = workspace.FetchBlob(weight_name)\n    w_csr = scipy.sparse.csr_matrix(w)\n    wcsr = w_csr.data\n    iw = w_csr.indptr\n    jw = w_csr.indices\n    workspace.FeedBlob(weight_name + \"wcsr\", wcsr)\n    workspace.FeedBlob(weight_name + \"iw\", iw)\n    workspace.FeedBlob(weight_name + \"jw\", jw)\n    return weight_name + \"wcsr\", weight_name + \"iw\", weight_name + \"jw\"\n\n\ndef transFCRelu(cur, id2node, name2id, ops, model):\n    \"\"\"\n    Add trans before and after this FC_Prune->(Relu)->FC_Prune chain.\n    \"\"\"\n    # 1. add trans before the start of this chain\n    # assuming that cur is a FC_Prune, and it has only one input\n    pre = cur.prev.itervalues().next()\n    # Create a node /op and insert it.\n    # TODO(wyiming): check whether it is correct here\n    current_blob = model.Transpose(cur.op.input[0], cur.op.input[0] + \"_trans\")\n#     print model.net.Proto()\n    trans_op = model.net.Proto().op[-1]\n    trans_node = NetDefNode(trans_op.output[0], \"Transpose\", pre, trans_op)\n    trans_node.visited = True\n    pre_new = trans_node\n\n    # 2. use while loop to visit the chain\n    while True:\n        # breakup with the parent\n        cur.deleteInput(pre)\n        if not (cur.optype == \"FC_Prune\" or cur.optype == \"Relu\"):\n            print(\"Reaching the end of the chain\")\n            break\n        if len(cur.ops) > 1:\n            print(\"A FC/Relu giving more than 1 useful outputs\")\n        if cur.optype == \"FC_Prune\":\n            op = cur.op\n            wcsr, iw, jw = maskNallocate(op.input[1])\n            bias_name = op.input[3]\n            # TODO(wyiming): create a new Op here\n            current_blob = model.FC_Sparse(current_blob,\n                                           cur.op.output[0] + \"_Sparse\",\n                                           wcsr, iw, jw, bias_name)\n            sps_op = model.net.Proto().op[-1]\n            sps_node = NetDefNode(cur.op.output[0] + \"_Sparse\",\n                                  \"FC_Sparse\",\n                                  pre_new, sps_op)\n            sps_node.visited = True\n            pre_new = sps_node\n        if cur.optype == \"Relu\":\n            op = cur.op\n            current_blob = model.Relu(current_blob, current_blob)\n            rel_op = model.net.Proto().op[-1]\n            rel_node = NetDefNode(str(current_blob), \"Relu\",\n                                  pre_new, rel_op)\n            rel_node.visited = True\n            pre_new = rel_node\n\n        cur.visited = True\n        pre = cur\n        flag = False\n        for _, temp in cur.ops.iteritems():\n            if temp.optype == \"Relu\" or temp.optype == \"FC_Prune\":\n                flag = True\n                cur = temp\n        if not flag:\n            # assume that there is only 1 output that is not PrintOP\n            cur = cur.ops.itervalues().next()\n            cur.deleteInput(pre)\n            print(\"No FC/RElu children\")\n            print(cur.op.type)\n            break\n    # 3. add trans after this chain like 1.\n    current_blob = model.Transpose(current_blob, pre.op.output[0])\n    trans_op = model.net.Proto().op[-1]\n    trans_node = NetDefNode(str(current_blob), \"Transpose\", pre_new, trans_op)\n    trans_node.visited = True\n    cur.insertInput(trans_node)\n    print(cur.prev)\n    print(trans_node.ops)\n\n\ndef Prune2Sparse(cur, id2node, name2id, ops, model):\n    # Assume that FC and Relu takes in only 1 input;\n    # If not raise warning\n    if not cur.visited and cur.optype == \"FC_Prune\":\n        transFCRelu(cur, id2node, name2id, ops, model)\n\n    cur.visited = True\n    for name, n in cur.ops.iteritems():\n        Prune2Sparse(n, id2node, name2id, ops, model)\n\n\ndef net2list(net_root):\n    \"\"\"\n    Use topological order(BFS) to print the op of a net in a list\n    \"\"\"\n    bfs_queue = []\n    op_list = []\n    cur = net_root\n    for _, n in cur.ops.iteritems():\n        bfs_queue.append(n)\n    while bfs_queue:\n        node = bfs_queue[0]\n        bfs_queue = bfs_queue[1:]\n        op_list.append(node.op)\n        for _, n in node.ops.iteritems():\n            bfs_queue.append(n)\n\n    return op_list\n\n\ndef netbuilder(model):\n    print(\"Welcome to model checker\")\n    proto = model.net.Proto()\n    net_name2id = {}\n    net_id2node = {}\n    net_root = NetDefNode(\"net_root\", \"root\", None)\n\n    for op_id, op in enumerate(proto.op):\n        if op.type == \"Print\":\n            continue\n        op_name = '%s/%s (op#%d)' % (op.name, op.type, op_id) \\\n                  if op.name else '%s (op#%d)' % (op.type, op_id)\n        # print(op_name)\n        op_node = NetDefNode(op_name, op.type, op=op)\n        net_id2node[op_id] = op_node\n\n        if_has_layer_input = False\n        for input_name in op.input:\n            if input_name not in net_name2id:\n                # assume that un_occured name are non_layers\n                # TODO: write a non-layer checker and log it\n                continue\n            op_node.insertInput(net_id2node[net_name2id[input_name]])\n            if_has_layer_input = True\n\n        if not if_has_layer_input:\n            op_node.insertInput(net_root)\n\n        for output_name in op.output:\n            net_name2id[output_name] = op_id\n\n    return net_root, net_name2id, net_id2node\n"
  },
  {
    "path": "caffe2/experiments/python/convnet_benchmarks.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package convnet_benchmarks\n# Module caffe2.experiments.python.convnet_benchmarks\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\"\"\"\nBenchmark for common convnets.\n\n(NOTE: Numbers below prior with missing parameter=update step, TODO to update)\n\nSpeed on Titan X, with 10 warmup steps and 10 main steps and with different\nversions of cudnn, are as follows (time reported below is per-batch time,\nforward / forward+backward):\n\n                    CuDNN V3        CuDNN v4\n                    AlexNet         32.5 / 108.0    27.4 /  90.1\n                    OverFeat       113.0 / 342.3    91.7 / 276.5\n                    Inception      134.5 / 485.8   125.7 / 450.6\n                    VGG (batch 64) 200.8 / 650.0   164.1 / 551.7\n\nSpeed on Inception with varied batch sizes and CuDNN v4 is as follows:\n\nBatch Size   Speed per batch     Speed per image\n16             22.8 /  72.7         1.43 / 4.54\n32             38.0 / 127.5         1.19 / 3.98\n64             67.2 / 233.6         1.05 / 3.65\n128            125.7 / 450.6         0.98 / 3.52\n\nSpeed on Tesla M40, which 10 warmup steps and 10 main steps and with cudnn\nv4, is as follows:\n\nAlexNet         68.4 / 218.1\nOverFeat       210.5 / 630.3\nInception      300.2 / 1122.2\nVGG (batch 64) 405.8 / 1327.7\n\n(Note that these numbers involve a \"full\" backprop, i.e. the gradient\nwith respect to the input image is also computed.)\n\nTo get the numbers, simply run:\n\nfor MODEL in AlexNet OverFeat Inception; do\nPYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size 128 --model $MODEL --forward_only True\ndone\nfor MODEL in AlexNet OverFeat Inception; do\nPYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size 128 --model $MODEL\ndone\nPYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size 64 --model VGGA --forward_only True\nPYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size 64 --model VGGA\n\nfor BS in 16 32 64 128; do\nPYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size $BS --model Inception --forward_only True\nPYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size $BS --model Inception\ndone\n\nNote that VGG needs to be run at batch 64 due to memory limit on the backward\npass.\n\"\"\"\n\nimport argparse\nimport time\n\nfrom caffe2.python import cnn, workspace, core\n\nimport caffe2.python.SparseTransformer as SparseTransformer\n\n\ndef MLP(order):\n    model = cnn.CNNModelHelper()\n    d = 256\n    depth = 20\n    width = 3\n    for i in range(depth):\n        for j in range(width):\n            current = \"fc_{}_{}\".format(i, j) if i > 0 else \"data\"\n            next_ = \"fc_{}_{}\".format(i + 1, j)\n            model.FC(\n                current, next_,\n                dim_in=d, dim_out=d,\n                weight_init=model.XavierInit,\n                bias_init=model.XavierInit)\n            model.Sum([\"fc_{}_{}\".format(depth, j)\n                       for j in range(width)], [\"sum\"])\n            model.FC(\"sum\", \"last\",\n                     dim_in=d, dim_out=1000,\n                     weight_init=model.XavierInit,\n                     bias_init=model.XavierInit)\n            xent = model.LabelCrossEntropy([\"last\", \"label\"], \"xent\")\n            model.AveragedLoss(xent, \"loss\")\n            return model, d\n\n\ndef AlexNet(order):\n    model = cnn.CNNModelHelper(order, name=\"alexnet\",\n                               use_cudnn=True, cudnn_exhaustive_search=True)\n    conv1 = model.Conv(\n        \"data\",\n        \"conv1\",\n        3,\n        64,\n        11,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        stride=4,\n        pad=2\n    )\n\n    relu1 = model.Relu(conv1, \"conv1\")\n    pool1 = model.MaxPool(relu1, \"pool1\", kernel=3, stride=2)\n    conv2 = model.Conv(\n        pool1,\n        \"conv2\",\n        64,\n        192,\n        5,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=2\n    )\n    relu2 = model.Relu(conv2, \"conv2\")\n    pool2 = model.MaxPool(relu2, \"pool2\", kernel=3, stride=2)\n    conv3 = model.Conv(\n        pool2,\n        \"conv3\",\n        192,\n        384,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu3 = model.Relu(conv3, \"conv3\")\n    conv4 = model.Conv(\n        relu3,\n        \"conv4\",\n        384,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu4 = model.Relu(conv4, \"conv4\")\n    conv5 = model.Conv(\n        relu4,\n        \"conv5\",\n        256,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu5 = model.Relu(conv5, \"conv5\")\n    pool5 = model.MaxPool(relu5, \"pool5\", kernel=3, stride=2)\n    fc6 = model.FC(\n        pool5, \"fc6\", 256 * 6 * 6, 4096, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    relu6 = model.Relu(fc6, \"fc6\")\n    fc7 = model.FC(\n        relu6, \"fc7\", 4096, 4096, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    relu7 = model.Relu(fc7, \"fc7\")\n    fc8 = model.FC(\n        relu7, \"fc8\", 4096, 1000, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    pred = model.Softmax(fc8, \"pred\")\n    xent = model.LabelCrossEntropy([pred, \"label\"], \"xent\")\n    model.AveragedLoss(xent, \"loss\")\n    return model, 224\n\n\ndef OverFeat(order):\n    model = cnn.CNNModelHelper(order, name=\"overfeat\",\n                               use_cudnn=True, cudnn_exhaustive_search=True)\n    conv1 = model.Conv(\n        \"data\",\n        \"conv1\",\n        3,\n        96,\n        11,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        stride=4\n    )\n    relu1 = model.Relu(conv1, \"conv1\")\n    pool1 = model.MaxPool(relu1, \"pool1\", kernel=2, stride=2)\n    conv2 = model.Conv(\n        pool1, \"conv2\", 96, 256, 5, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    relu2 = model.Relu(conv2, \"conv2\")\n    pool2 = model.MaxPool(relu2, \"pool2\", kernel=2, stride=2)\n    conv3 = model.Conv(\n        pool2,\n        \"conv3\",\n        256,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu3 = model.Relu(conv3, \"conv3\")\n    conv4 = model.Conv(\n        relu3,\n        \"conv4\",\n        512,\n        1024,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu4 = model.Relu(conv4, \"conv4\")\n    conv5 = model.Conv(\n        relu4,\n        \"conv5\",\n        1024,\n        1024,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu5 = model.Relu(conv5, \"conv5\")\n    pool5 = model.MaxPool(relu5, \"pool5\", kernel=2, stride=2)\n    fc6 = model.FC(\n        pool5, \"fc6\", 1024 * 6 * 6, 3072, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    relu6 = model.Relu(fc6, \"fc6\")\n    fc7 = model.FC(\n        relu6, \"fc7\", 3072, 4096, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    relu7 = model.Relu(fc7, \"fc7\")\n    fc8 = model.FC(\n        relu7, \"fc8\", 4096, 1000, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    pred = model.Softmax(fc8, \"pred\")\n    xent = model.LabelCrossEntropy([pred, \"label\"], \"xent\")\n    model.AveragedLoss(xent, \"loss\")\n    return model, 231\n\n\ndef VGGA(order):\n    model = cnn.CNNModelHelper(order, name='vgg-a',\n                               use_cudnn=True, cudnn_exhaustive_search=True)\n    conv1 = model.Conv(\n        \"data\",\n        \"conv1\",\n        3,\n        64,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu1 = model.Relu(conv1, \"conv1\")\n    pool1 = model.MaxPool(relu1, \"pool1\", kernel=2, stride=2)\n    conv2 = model.Conv(\n        pool1,\n        \"conv2\",\n        64,\n        128,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu2 = model.Relu(conv2, \"conv2\")\n    pool2 = model.MaxPool(relu2, \"pool2\", kernel=2, stride=2)\n    conv3 = model.Conv(\n        pool2,\n        \"conv3\",\n        128,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu3 = model.Relu(conv3, \"conv3\")\n    conv4 = model.Conv(\n        relu3,\n        \"conv4\",\n        256,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu4 = model.Relu(conv4, \"conv4\")\n    pool4 = model.MaxPool(relu4, \"pool4\", kernel=2, stride=2)\n    conv5 = model.Conv(\n        pool4,\n        \"conv5\",\n        256,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu5 = model.Relu(conv5, \"conv5\")\n    conv6 = model.Conv(\n        relu5,\n        \"conv6\",\n        512,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu6 = model.Relu(conv6, \"conv6\")\n    pool6 = model.MaxPool(relu6, \"pool6\", kernel=2, stride=2)\n    conv7 = model.Conv(\n        pool6,\n        \"conv7\",\n        512,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu7 = model.Relu(conv7, \"conv7\")\n    conv8 = model.Conv(\n        relu7,\n        \"conv8\",\n        512,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu8 = model.Relu(conv8, \"conv8\")\n    pool8 = model.MaxPool(relu8, \"pool8\", kernel=2, stride=2)\n\n    fcix = model.FC(\n        pool8, \"fcix\", 512 * 7 * 7, 4096, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    reluix = model.Relu(fcix, \"fcix\")\n    fcx = model.FC(\n        reluix, \"fcx\", 4096, 4096, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    relux = model.Relu(fcx, \"fcx\")\n    fcxi = model.FC(\n        relux, \"fcxi\", 4096, 1000, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    pred = model.Softmax(fcxi, \"pred\")\n    xent = model.LabelCrossEntropy([pred, \"label\"], \"xent\")\n    model.AveragedLoss(xent, \"loss\")\n    return model, 231\n\n\ndef net_DAG_Builder(model):\n    print(\"====================================================\")\n    print(\"                 Start Building DAG                 \")\n    print(\"====================================================\")\n    net_root = SparseTransformer.netbuilder(model)\n    return net_root\n\n\ndef _InceptionModule(\n    model, input_blob, input_depth, output_name, conv1_depth, conv3_depths,\n    conv5_depths, pool_depth\n):\n    # path 1: 1x1 conv\n    conv1 = model.Conv(\n        input_blob, output_name + \":conv1\", input_depth, conv1_depth, 1,\n        ('XavierFill', {}), ('ConstantFill', {})\n    )\n    conv1 = model.Relu(conv1, conv1)\n    # path 2: 1x1 conv + 3x3 conv\n    conv3_reduce = model.Conv(\n        input_blob, output_name +\n        \":conv3_reduce\", input_depth, conv3_depths[0],\n        1, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    conv3_reduce = model.Relu(conv3_reduce, conv3_reduce)\n    conv3 = model.Conv(\n        conv3_reduce,\n        output_name + \":conv3\",\n        conv3_depths[0],\n        conv3_depths[1],\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    conv3 = model.Relu(conv3, conv3)\n    # path 3: 1x1 conv + 5x5 conv\n    conv5_reduce = model.Conv(\n        input_blob, output_name +\n        \":conv5_reduce\", input_depth, conv5_depths[0],\n        1, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    conv5_reduce = model.Relu(conv5_reduce, conv5_reduce)\n    conv5 = model.Conv(\n        conv5_reduce,\n        output_name + \":conv5\",\n        conv5_depths[0],\n        conv5_depths[1],\n        5,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=2\n    )\n    conv5 = model.Relu(conv5, conv5)\n    # path 4: pool + 1x1 conv\n    pool = model.MaxPool(\n        input_blob,\n        output_name + \":pool\",\n        kernel=3,\n        stride=1,\n        pad=1\n    )\n    pool_proj = model.Conv(\n        pool, output_name + \":pool_proj\", input_depth, pool_depth, 1,\n        ('XavierFill', {}), ('ConstantFill', {})\n    )\n    pool_proj = model.Relu(pool_proj, pool_proj)\n    output = model.Concat([conv1, conv3, conv5, pool_proj], output_name)\n    return output\n\n\ndef Inception(order):\n    model = cnn.CNNModelHelper(order, name=\"inception\",\n                               use_cudnn=True, cudnn_exhaustive_search=True)\n    conv1 = model.Conv(\n        \"data\",\n        \"conv1\",\n        3,\n        64,\n        7,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        stride=2,\n        pad=3\n    )\n    relu1 = model.Relu(conv1, \"conv1\")\n    pool1 = model.MaxPool(relu1, \"pool1\", kernel=3, stride=2, pad=1)\n    conv2a = model.Conv(\n        pool1, \"conv2a\", 64, 64, 1, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    conv2a = model.Relu(conv2a, conv2a)\n    conv2 = model.Conv(\n        conv2a,\n        \"conv2\",\n        64,\n        192,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu2 = model.Relu(conv2, \"conv2\")\n    pool2 = model.MaxPool(relu2, \"pool2\", kernel=3, stride=2, pad=1)\n    # Inception modules\n    inc3 = _InceptionModule(\n        model, pool2, 192, \"inc3\", 64, [96, 128], [16, 32], 32\n    )\n    inc4 = _InceptionModule(\n        model, inc3, 256, \"inc4\", 128, [128, 192], [32, 96], 64\n    )\n    pool5 = model.MaxPool(inc4, \"pool5\", kernel=3, stride=2, pad=1)\n    inc5 = _InceptionModule(\n        model, pool5, 480, \"inc5\", 192, [96, 208], [16, 48], 64\n    )\n    inc6 = _InceptionModule(\n        model, inc5, 512, \"inc6\", 160, [112, 224], [24, 64], 64\n    )\n    inc7 = _InceptionModule(\n        model, inc6, 512, \"inc7\", 128, [128, 256], [24, 64], 64\n    )\n    inc8 = _InceptionModule(\n        model, inc7, 512, \"inc8\", 112, [144, 288], [32, 64], 64\n    )\n    inc9 = _InceptionModule(\n        model, inc8, 528, \"inc9\", 256, [160, 320], [32, 128], 128\n    )\n    pool9 = model.MaxPool(inc9, \"pool9\", kernel=3, stride=2, pad=1)\n    inc10 = _InceptionModule(\n        model, pool9, 832, \"inc10\", 256, [160, 320], [32, 128], 128\n    )\n    inc11 = _InceptionModule(\n        model, inc10, 832, \"inc11\", 384, [192, 384], [48, 128], 128\n    )\n    pool11 = model.AveragePool(inc11, \"pool11\", kernel=7, stride=1)\n    fc = model.FC(\n        pool11, \"fc\", 1024, 1000, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    # It seems that Soumith's benchmark does not have softmax on top\n    # for Inception. We will add it anyway so we can have a proper\n    # backward pass.\n    pred = model.Softmax(fc, \"pred\")\n    xent = model.LabelCrossEntropy([pred, \"label\"], \"xent\")\n    model.AveragedLoss(xent, \"loss\")\n    return model, 224\n\n\ndef AddInput(model, batch_size, db, db_type):\n    \"\"\"Adds the data input part.\"\"\"\n    data_uint8, label = model.TensorProtosDBInput(\n        [], [\"data_uint8\", \"label\"], batch_size=batch_size,\n        db=db, db_type=db_type\n    )\n    data = model.Cast(data_uint8, \"data_nhwc\", to=core.DataType.FLOAT)\n    data = model.NHWC2NCHW(data, \"data\")\n    data = model.Scale(data, data, scale=float(1. / 256))\n    data = model.StopGradient(data, data)\n    return data, label\n\n\ndef AddParameterUpdate(model):\n    \"\"\" Simple plain SGD update -- not tuned to actually train the models \"\"\"\n    ITER = model.Iter(\"iter\")\n    LR = model.LearningRate(\n        ITER, \"LR\", base_lr=-1e-8, policy=\"step\", stepsize=10000, gamma=0.999)\n    ONE = model.param_init_net.ConstantFill([], \"ONE\", shape=[1], value=1.0)\n    for param in model.params:\n        param_grad = model.param_to_grad[param]\n        model.WeightedSum([param, ONE, param_grad, LR], param)\n\n\ndef Benchmark(model_gen, arg):\n    model, input_size = model_gen(arg.order)\n    model.Proto().type = arg.net_type\n    model.Proto().num_workers = arg.num_workers\n\n    # In order to be able to run everything without feeding more stuff, let's\n    # add the data and label blobs to the parameter initialization net as well.\n\n    if arg.order == \"NCHW\":\n        input_shape = [arg.batch_size, 3, input_size, input_size]\n    else:\n        input_shape = [arg.batch_size, input_size, input_size, 3]\n        if arg.model == \"MLP\":\n            input_shape = [arg.batch_size, input_size]\n\n    model.param_init_net.GaussianFill(\n        [],\n        \"data\",\n        shape=input_shape,\n        mean=0.0,\n        std=1.0\n    )\n    model.param_init_net.UniformIntFill(\n        [],\n        \"label\",\n        shape=[arg.batch_size, ],\n        min=0,\n        max=999\n    )\n\n    if arg.forward_only:\n        print('{}: running forward only.'.format(arg.model))\n    else:\n        print('{}: running forward-backward.'.format(arg.model))\n        model.AddGradientOperators([\"loss\"])\n        AddParameterUpdate(model)\n\n        if arg.order == 'NHWC':\n            print(\n                '==WARNING==\\n'\n                'NHWC order with CuDNN may not be supported yet, so I might\\n'\n                'exit suddenly.'\n            )\n\n    if not arg.cpu:\n        model.param_init_net.RunAllOnGPU()\n        model.net.RunAllOnGPU()\n\n    if arg.dump_model:\n        # Writes out the pbtxt for benchmarks on e.g. Android\n        with open(\n            \"{0}_init_batch_{1}.pbtxt\".format(arg.model, arg.batch_size), \"w\"\n        ) as fid:\n            fid.write(str(model.param_init_net.Proto()))\n            with open(\"{0}.pbtxt\".format(arg.model,\n                                         arg.batch_size), \"w\") as fid:\n                fid.write(str(model.net.Proto()))\n\n    workspace.RunNetOnce(model.param_init_net)\n    workspace.CreateNet(model.net)\n    for i in range(arg.warmup_iterations):\n        workspace.RunNet(model.net.Proto().name)\n\n    plan = core.Plan(\"plan\")\n    plan.AddStep(core.ExecutionStep(\"run\", model.net, arg.iterations))\n    start = time.time()\n    workspace.RunPlan(plan)\n    print('Spent: {}'.format((time.time() - start) / arg.iterations))\n    if arg.layer_wise_benchmark:\n        print('Layer-wise benchmark.')\n        workspace.BenchmarkNet(model.net.Proto().name, 1, arg.iterations, True)\n\n\ndef GetArgumentParser():\n    parser = argparse.ArgumentParser(description=\"Caffe2 benchmark.\")\n    parser.add_argument(\n        \"--batch_size\",\n        type=int,\n        default=128,\n        help=\"The batch size.\"\n    )\n    parser.add_argument(\"--model\", type=str, help=\"The model to benchmark.\")\n    parser.add_argument(\n        \"--order\",\n        type=str,\n        default=\"NCHW\",\n        help=\"The order to evaluate.\"\n    )\n    parser.add_argument(\n        \"--cudnn_ws\",\n        type=int,\n        default=-1,\n        help=\"The cudnn workspace size.\"\n    )\n    parser.add_argument(\n        \"--iterations\",\n        type=int,\n        default=10,\n        help=\"Number of iterations to run the network.\"\n    )\n    parser.add_argument(\n        \"--warmup_iterations\",\n        type=int,\n        default=10,\n        help=\"Number of warm-up iterations before benchmarking.\"\n    )\n    parser.add_argument(\n        \"--forward_only\",\n        action='store_true',\n        help=\"If set, only run the forward pass.\"\n    )\n    parser.add_argument(\n        \"--layer_wise_benchmark\",\n        action='store_true',\n        help=\"If True, run the layer-wise benchmark as well.\"\n    )\n    parser.add_argument(\n        \"--cpu\",\n        action='store_true',\n        help=\"If True, run testing on CPU instead of GPU.\"\n    )\n    parser.add_argument(\n        \"--dump_model\",\n        action='store_true',\n        help=\"If True, dump the model prototxts to disk.\"\n    )\n    parser.add_argument(\"--net_type\", type=str, default=\"dag\")\n    parser.add_argument(\"--num_workers\", type=int, default=2)\n    return parser\n\n\nif __name__ == '__main__':\n    args = GetArgumentParser().parse_args()\n    if (\n        not args.batch_size or not args.model or not args.order or\n        not args.cudnn_ws\n    ):\n        GetArgumentParser().print_help()\n\n    workspace.GlobalInit(['caffe2', '--caffe2_log_level=0'])\n    model_map = {\n        'AlexNet': AlexNet,\n        'OverFeat': OverFeat,\n        'VGGA': VGGA,\n        'Inception': Inception,\n        'MLP': MLP,\n    }\n    Benchmark(model_map[args.model], args)\n"
  },
  {
    "path": "caffe2/experiments/python/device_reduce_sum_bench.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package device_reduce_sum_bench\n# Module caffe2.experiments.python.device_reduce_sum_bench\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport argparse\nimport itertools\nimport logging\nimport os\n\nfrom six import add_metaclass\nimport numpy as np\n\nfrom caffe2.python import workspace, core\nfrom caffe2.python.hypothesis_test_util import runOpBenchmark, gpu_do\n\nlogging.basicConfig()\nlogger = logging.getLogger(os.path.basename(__file__))\nlogger.setLevel(logging.INFO)\n\nALL_BENCHMARKS = {}\n\n\nclass BenchmarkMeta(type):\n    def __new__(metacls, name, bases, class_dict):\n        cls = type.__new__(metacls, name, bases, class_dict)\n        if name != 'Benchmark':\n            ALL_BENCHMARKS[name] = cls\n        return cls\n\n\n@add_metaclass(BenchmarkMeta)\nclass Benchmark(object):\n\n    def __init__(self):\n        self.results = []\n\n    def display(self):\n        print('Results ({}):'.format(type(self).__name__))\n        print('input size                      ms/iter')\n        print('------------------------------  -----------')\n        for size, ms in self.results:\n            print('{!s:<30}  {:.4f}'.format(size, ms))\n\n\nclass SumElements(Benchmark):\n    def run(self):\n        op = core.CreateOperator(\n            \"SumElements\",\n            [\"X\"],\n            [\"y\"]\n        )\n\n        for n in itertools.imap(pow, itertools.cycle([10]), range(10)):\n            X = np.random.rand(n).astype(np.float32)\n            logger.info('Running benchmark for n = {}'.format(n))\n            ret = runOpBenchmark(gpu_do, op, inputs=[X])\n            self.results.append((n, ret[1]))\n\n\nclass SumSqrElements(Benchmark):\n    def run(self):\n        op = core.CreateOperator(\n            \"SumSqrElements\",\n            [\"X\"],\n            [\"y\"]\n        )\n\n        for n in itertools.imap(pow, itertools.cycle([10]), range(10)):\n            X = np.random.rand(n).astype(np.float32)\n            logger.info('Running benchmark for n = {}'.format(n))\n            ret = runOpBenchmark(gpu_do, op, inputs=[X])\n            self.results.append((n, ret[1]))\n\n\nclass SoftMaxWithLoss(Benchmark):\n    def run(self):\n        op = core.CreateOperator(\n            \"SoftmaxWithLoss\",\n            [\"X\", \"label\"],\n            [\"probs\", \"avgloss\"],\n        )\n\n        for n in itertools.imap(pow, itertools.cycle([10]), range(8)):\n            for D in itertools.imap(pow, itertools.cycle([10]), range(3)):\n                X = np.random.rand(n, D).astype(np.float32)\n                label = (np.random.rand(n) * D).astype(np.int32)\n                logger.info('Running benchmark for n = {}, D= {}'.format(n, D))\n                ret = runOpBenchmark(gpu_do, op, inputs=[X, label])\n                self.results.append(((n, D), ret[1]))\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(os.path.basename(__file__))\n    parser.add_argument('-b', '--benchmarks', nargs='+',\n                        default=ALL_BENCHMARKS.keys(),\n                        help='benchmarks to run (default: %(default)s))')\n    return parser.parse_args()\n\n\ndef main():\n    args = parse_args()\n\n    benchmarks = [ALL_BENCHMARKS[name]() for name in args.benchmarks]\n    for bench in benchmarks:\n        bench.run()\n    for bench in benchmarks:\n        bench.display()\n\n\nif __name__ == '__main__':\n    workspace.GlobalInit(['caffe2', '--caffe2_log_level=2'])\n    main()\n"
  },
  {
    "path": "caffe2/experiments/python/funhash_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nfrom scipy.sparse import coo_matrix\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestFunHash(hu.HypothesisTestCase):\n    @given(n_out=st.integers(min_value=5, max_value=20),\n           n_in=st.integers(min_value=10, max_value=20),\n           n_data=st.integers(min_value=2, max_value=8),\n           n_weight=st.integers(min_value=8, max_value=15),\n           n_alpha=st.integers(min_value=3, max_value=8),\n           sparsity=st.floats(min_value=0.1, max_value=1.0),\n           **hu.gcs)\n    def test_funhash(self, n_out, n_in, n_data, n_weight, n_alpha, sparsity,\n                     gc, dc):\n        A = np.random.rand(n_data, n_in)\n        A[A > sparsity] = 0\n        A_coo = coo_matrix(A)\n        val, key, seg = A_coo.data, A_coo.col, A_coo.row\n\n        weight = np.random.rand(n_weight).astype(np.float32)\n        alpha = np.random.rand(n_alpha).astype(np.float32)\n        val = val.astype(np.float32)\n        key = key.astype(np.int64)\n        seg = seg.astype(np.int32)\n\n        op = core.CreateOperator(\n            'FunHash',\n            ['val', 'key', 'seg', 'weight', 'alpha'],\n            ['out'],\n            num_outputs=n_out)\n\n        # Check over multiple devices\n        self.assertDeviceChecks(\n            dc, op, [val, key, seg, weight, alpha], [0])\n        # Gradient check wrt weight\n        self.assertGradientChecks(\n            gc, op, [val, key, seg, weight, alpha], 3, [0])\n        # Gradient check wrt alpha\n        self.assertGradientChecks(\n            gc, op, [val, key, seg, weight, alpha], 4, [0])\n\n        op2 = core.CreateOperator(\n            'FunHash',\n            ['val', 'key', 'seg', 'weight'],\n            ['out'],\n            num_outputs=n_out)\n\n        # Check over multiple devices\n        self.assertDeviceChecks(\n            dc, op2, [val, key, seg, weight], [0])\n        # Gradient check wrt weight\n        self.assertGradientChecks(\n            gc, op2, [val, key, seg, weight], 3, [0])\n"
  },
  {
    "path": "caffe2/experiments/python/net_construct_bench.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package net_construct_bench\n# Module caffe2.experiments.python.net_construct_bench\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport argparse\nimport logging\nimport time\n\nfrom caffe2.python import workspace, data_parallel_model\nfrom caffe2.python import cnn\n\nimport caffe2.python.models.resnet as resnet\n\n'''\nSimple benchmark that creates a data-parallel resnet-50 model\nand measurs the time.\n'''\n\n\nlogging.basicConfig()\nlog = logging.getLogger(\"net_construct_bench\")\nlog.setLevel(logging.DEBUG)\n\n\ndef AddMomentumParameterUpdate(train_model, LR):\n    '''\n    Add the momentum-SGD update.\n    '''\n    params = train_model.GetParams()\n    assert(len(params) > 0)\n    ONE = train_model.param_init_net.ConstantFill(\n        [], \"ONE\", shape=[1], value=1.0,\n    )\n    NEGONE = train_model.param_init_net.ConstantFill(\n        [], 'NEGONE', shape=[1], value=-1.0,\n    )\n\n    for param in params:\n        param_grad = train_model.param_to_grad[param]\n        param_momentum = train_model.param_init_net.ConstantFill(\n            [param], param + '_momentum', value=0.0\n        )\n\n        # Update param_grad and param_momentum in place\n        train_model.net.MomentumSGD(\n            [param_grad, param_momentum, LR],\n            [param_grad, param_momentum],\n            momentum=0.9,\n            nesterov=1\n        )\n\n        # Update parameters by applying the moment-adjusted gradient\n        train_model.WeightedSum(\n            [param, ONE, param_grad, NEGONE],\n            param\n        )\n\n\ndef Create(args):\n    gpus = list(range(args.num_gpus))\n    log.info(\"Running on gpus: {}\".format(gpus))\n\n    # Create CNNModeLhelper object\n    train_model = cnn.CNNModelHelper(\n        order=\"NCHW\",\n        name=\"resnet50\",\n        use_cudnn=True,\n        cudnn_exhaustive_search=False\n    )\n\n    # Model building functions\n    def create_resnet50_model_ops(model, loss_scale):\n        [softmax, loss] = resnet.create_resnet50(\n            model,\n            \"data\",\n            num_input_channels=3,\n            num_labels=1000,\n            label=\"label\",\n        )\n        model.Accuracy([softmax, \"label\"], \"accuracy\")\n        return [loss]\n\n    # SGD\n    def add_parameter_update_ops(model):\n        model.AddWeightDecay(1e-4)\n        ITER = model.Iter(\"ITER\")\n        stepsz = int(30)\n        LR = model.net.LearningRate(\n            [ITER],\n            \"LR\",\n            base_lr=0.1,\n            policy=\"step\",\n            stepsize=stepsz,\n            gamma=0.1,\n        )\n        AddMomentumParameterUpdate(model, LR)\n\n    def add_image_input(model):\n        pass\n\n    start_time = time.time()\n\n    # Create parallelized model\n    data_parallel_model.Parallelize_GPU(\n        train_model,\n        input_builder_fun=add_image_input,\n        forward_pass_builder_fun=create_resnet50_model_ops,\n        param_update_builder_fun=add_parameter_update_ops,\n        devices=gpus,\n    )\n\n    ct = time.time() - start_time\n    train_model.net._CheckLookupTables()\n\n    log.info(\"Model create for {} gpus took: {} secs\".format(len(gpus), ct))\n\n\ndef main():\n    # TODO: use argv\n    parser = argparse.ArgumentParser(\n        description=\"Caffe2: Benchmark for net construction\"\n    )\n    parser.add_argument(\"--num_gpus\", type=int, default=1,\n                        help=\"Number of GPUs.\")\n    args = parser.parse_args()\n\n    Create(args)\n\n\nif __name__ == '__main__':\n    workspace.GlobalInit(['caffe2', '--caffe2_log_level=2'])\n    import cProfile\n\n    cProfile.run('main()', sort=\"cumulative\")\n"
  },
  {
    "path": "caffe2/experiments/python/sparse_funhash_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nfrom scipy.sparse import coo_matrix\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestFunHash(hu.HypothesisTestCase):\n    @given(n_out=st.integers(min_value=5, max_value=20),\n           n_in=st.integers(min_value=10, max_value=20),\n           n_data=st.integers(min_value=2, max_value=8),\n           n_weight=st.integers(min_value=8, max_value=15),\n           n_alpha=st.integers(min_value=3, max_value=8),\n           sparsity=st.floats(min_value=0.1, max_value=1.0),\n           **hu.gcs)\n    def test_funhash(self, n_out, n_in, n_data, n_weight, n_alpha, sparsity,\n                     gc, dc):\n        A = np.random.rand(n_data, n_in)\n        A[A > sparsity] = 0\n        A_coo = coo_matrix(A)\n        val, key, seg = A_coo.data, A_coo.col, A_coo.row\n\n        weight = np.random.rand(n_weight).astype(np.float32)\n        alpha = np.random.rand(n_alpha).astype(np.float32)\n        val = val.astype(np.float32)\n        key = key.astype(np.int64)\n        seg = seg.astype(np.int32)\n\n        op = core.CreateOperator(\n            'SparseFunHash',\n            ['val', 'key', 'seg', 'weight', 'alpha'],\n            ['out'],\n            num_outputs=n_out)\n\n        # Gradient check wrt weight\n        self.assertGradientChecks(\n            gc, op, [val, key, seg, weight, alpha], 3, [0])\n        # Gradient check wrt alpha\n        self.assertGradientChecks(\n            gc, op, [val, key, seg, weight, alpha], 4, [0])\n\n        op2 = core.CreateOperator(\n            'SparseFunHash',\n            ['val', 'key', 'seg', 'weight'],\n            ['out'],\n            num_outputs=n_out)\n\n        # Gradient check wrt weight\n        self.assertGradientChecks(\n            gc, op2, [val, key, seg, weight], 3, [0])\n"
  },
  {
    "path": "caffe2/experiments/python/sparse_reshape_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nfrom scipy.sparse import coo_matrix\n\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\n\n\ndef test_reshape(old_shape, new_shape, stride_only=False):\n    blob_in0 = 'col'\n    blob_out0 = 'col_out'\n\n    blob_in1 = 'row'\n    blob_out1 = 'row_out'\n\n    old_shape_for_op = (-1, old_shape[1]) if stride_only else old_shape\n\n    op = core.CreateOperator('SparseMatrixReshape',\n                             [blob_in0, blob_in1],\n                             [blob_out0, blob_out1],\n                             old_shape=old_shape_for_op,\n                             new_shape=new_shape)\n\n    A = np.random.random_sample(old_shape)\n    A[np.random.random_sample(old_shape) > .5] = 0\n    A_coo = coo_matrix(A)\n    old_row, old_col = A_coo.row, A_coo.col\n\n    workspace.FeedBlob(blob_in0, old_col.astype(np.int64))\n    workspace.FeedBlob(blob_in1, old_row.astype(np.int32))\n\n    workspace.RunOperatorOnce(op)\n\n    A_new_coo = coo_matrix(A.reshape(new_shape))\n    new_row, new_col = A_new_coo.row, A_new_coo.col\n\n    col_out = workspace.FetchBlob(blob_out0)\n    row_out = workspace.FetchBlob(blob_out1)\n\n    np.testing.assert_array_equal(col_out, new_col)\n    np.testing.assert_array_equal(row_out, new_row)\n\n\nclass TestSparseMatrixReshapeOp(TestCase):\n    def test_basic_reshape(self):\n        test_reshape(old_shape=(3, 4), new_shape=(4, 3))\n\n    def test_missing_dim(self):\n        test_reshape(old_shape=(2, 8), new_shape=(-1, 4))\n\n    def test_stride_only(self):\n        test_reshape(old_shape=(2, 8), new_shape=(-1, 4), stride_only=True)\n\n    def test_sparse_reshape_mm(self):\n        M, N, K = 300, 400, 500\n        A = np.random.rand(M, K).astype(np.float32)\n        A_sparse = A * (np.random.rand(*A.shape) > .5)\n        A_sparse = A_sparse.reshape((K, M))\n        A_coo = coo_matrix(A_sparse)\n        idx0, idx1, a = A_coo.row, A_coo.col, A_coo.data\n        B = np.random.rand(K, N).astype(np.float32)\n\n        workspace.FeedBlob('col', idx1.astype(np.int64))\n        workspace.FeedBlob('row', idx0.astype(np.int32))\n        workspace.FeedBlob('B', B)\n        workspace.FeedBlob('a', a)\n\n        reshape_op = core.CreateOperator(\n            'SparseMatrixReshape',\n            ['col', 'row'],\n            ['new_col', 'new_row'],\n            old_shape=(K, M),\n            new_shape=(M, K))\n\n        mm_op = core.CreateOperator(\n            'SparseUnsortedSegmentWeightedSum',\n            ['B', 'a', 'new_col', 'new_row'],\n            ['Y'])\n\n        workspace.RunOperatorOnce(reshape_op)\n        workspace.RunOperatorOnce(mm_op)\n\n        Y = workspace.FetchBlob('Y')\n        np.testing.assert_allclose(A_sparse.reshape(M, K).dot(B), Y,\n                                   rtol=1e-4)\n"
  },
  {
    "path": "caffe2/experiments/python/tt_contraction_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestTTContraction(hu.HypothesisTestCase):\n    @given(D=st.integers(min_value=5, max_value=20),\n           K=st.integers(min_value=5, max_value=20),\n           M=st.integers(min_value=5, max_value=20),\n           N=st.integers(min_value=5, max_value=20),\n           **hu.gcs)\n    def test_tt_contraction(self, D, K, M, N, gc, dc):\n        A = np.random.rand(K, M).astype(np.float32)\n        B = np.random.rand(D, K, N).astype(np.float32)\n\n        workspace.FeedBlob('A', A)\n        workspace.FeedBlob('B', B)\n\n        op = core.CreateOperator(\n            'TTContraction',\n            ['A', 'B'],\n            ['C'],\n            K=K,\n            M=M,\n            N=N)\n        workspace.RunOperatorOnce(op)\n\n        def tt_contraction_ref(A_, B_):\n            return ((A_[:, :, np.newaxis] * B_[:, :, np.newaxis, :])\n                    .sum(axis=1).flatten()),\n\n        # Check against numpy reference\n        self.assertReferenceChecks(gc, op, [A, B], tt_contraction_ref)\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [A, B], [0])\n        # Gradient check wrt A\n        self.assertGradientChecks(gc, op, [A, B], 0, [0])\n        # Gradient check wrt B\n        self.assertGradientChecks(gc, op, [A, B], 1, [0])\n"
  },
  {
    "path": "caffe2/experiments/python/tt_pad_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestTTPad(hu.HypothesisTestCase):\n    @given(K=st.integers(min_value=2, max_value=10),\n           M=st.integers(min_value=10, max_value=20),\n           N=st.integers(min_value=10, max_value=20),\n           **hu.gcs)\n    def test_tt_pad(self, K, M, N, gc, dc):\n        op = core.CreateOperator(\n            'TTPad',\n            ['A'],\n            ['A', 'dim0'],\n            scale=(K))\n\n        A = np.random.rand(M, N).astype(np.float32)\n        workspace.FeedBlob('A', A)\n        workspace.RunOperatorOnce(op)\n\n        def tt_pad_ref(A_):\n            M_ = A_.shape[0]\n            if M_ % K == 0:\n                new_dim0 = M_\n            else:\n                new_dim0 = (M_ // K + 1) * K\n            return (np.vstack((A_, np.zeros((new_dim0 - M_, A_.shape[1])))),\n                    np.array([A.shape[0]]))\n\n        # Check against numpy reference\n        self.assertReferenceChecks(gc, op, [A], tt_pad_ref)\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [A], [0])\n        # Gradient check wrt A\n        self.assertGradientChecks(gc, op, [A], 0, [0])\n"
  },
  {
    "path": "caffe2/image/CMakeLists.txt",
    "content": "if(USE_OPENCV AND OpenCV_FOUND)\n        message(STATUS \"Including image processing operators\")\n  # ---[ GPU files\n  # ------[ cuDNN\n  file(GLOB tmp *_cudnn.cc)\n  set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n  # ------[ general GPU\n  file(GLOB tmp *_gpu.cc)\n  set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n  # ------[ CUDA sources\n  file(GLOB tmp *.cu)\n  set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n  # exclude test files\n  file(GLOB tmp *_test.cc)\n  exclude(Caffe2_GPU_SRCS \"${Caffe2_GPU_SRCS}\" ${tmp})\n\n  # ---[ CPU files.\n  file(GLOB tmp *.cc)\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp})\n  # exclude test files and gpu files\n  file(GLOB tmp *_test.cc)\n  exclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${tmp})\n  exclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${Caffe2_GPU_SRCS})\n\n  # ---[ GPU test files\n  file(GLOB tmp *_gpu_test.cc)\n  set(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} ${tmp})\n\n  # ---[ CPU test files\n  file(GLOB tmp *_test.cc)\n  set(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${tmp})\n  exclude(Caffe2_CPU_TEST_SRCS \"${Caffe2_CPU_TEST_SRCS}\" ${Caffe2_GPU_TEST_SRCS})\n\n  # ---[ Send the lists to the parent scope.\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\n  set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\n  set(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\n  set(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\nelse()\n        message(STATUS \"Excluding image processing operators due to no opencv\")\nendif()\n"
  },
  {
    "path": "caffe2/image/image_input_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/image/image_input_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(ImageInput, ImageInputOp<CPUContext>);\n\nOPERATOR_SCHEMA(ImageInput)\n    .NumInputs(0, 1)\n    .NumOutputs(2, INT_MAX)\n    .TensorInferenceFunction(\n        [](const OperatorDef& def, const vector<TensorShape>& /* unused */ ) {\n          vector<TensorShape> out(2);\n          ArgumentHelper helper(def);\n          int batch_size = helper.GetSingleArgument<int>(\"batch_size\", 0);\n          int crop = helper.GetSingleArgument<int>(\"crop\", -1);\n          int color = helper.GetSingleArgument<int>(\"color\", 1);\n          CHECK_GT(crop, 0);\n          out[0] = CreateTensorShape(\n              vector<int>{batch_size, crop, crop, color ? 3 : 1},\n              TensorProto::FLOAT);\n          out[1] =\n              CreateTensorShape(vector<int>{1, batch_size}, TensorProto::INT32);\n          return out;\n        })\n    .SetDoc(R\"DOC(\nImports and processes images from a database. For each run of the operator,\nbatch_size images will be processed. GPUs can optionally be used for\npart of the processing.\n\nThe following transformations are applied to the image\n  - A bounding box is applied to the initial image (optional)\n  - The image is rescaled either up or down (with the scale argument) or\n    just up (with the minsize argument)\n  - The image is randomly cropped (crop size is passed as an argument but\n    the location of the crop is random except if is_test is passed in which case\n    the image in cropped at the center)\n  - The image is normalized. Each of its color channels can have separate\n    normalization values\n\nThe dimension of the output image will always be cropxcrop\n)DOC\")\n    .Arg(\"batch_size\", \"Number of images to output for each run of the operator\"\n         \". Must be 1 or greater\")\n    .Arg(\"color\", \"Number of color channels (1 or 3). Defaults to 1\")\n    .Arg(\"color_jitter\", \"Whether or not to do color jitter. Defaults to 0\")\n    .Arg(\"img_saturation\", \"Image saturation scale used in color jittering. \"\n         \"Defaults to 0.4\")\n    .Arg(\"img_brightness\", \"Image brightness scale used in color jittering. \"\n         \"Defaults to 0.4\")\n    .Arg(\"img_contrast\", \"Image contrast scale used in color jittering. \"\n         \"Defaults to 0.4\")\n    .Arg(\"color_lighting\", \"Whether or not to do color lighting.\"\n         \" Defaults to 0\")\n    .Arg(\"color_lighting_std\", \"Std of normal distribution where color lighting\"\n        \" scaling factor is sampled. Defaults to 0.1\")\n    .Arg(\"scale_jitter_type\", \"Type 0: No scale jittering \"\n          \"Type 1: Inception-style scale jittering\")\n    .Arg(\"label_type\", \"Type 0: single integer label for multi-class \"\n        \"classification. Type 1: sparse active label indices for multi-label \"\n        \"classification. Type 2: dense label embedding vector for label \"\n        \"embedding regression\")\n    .Arg(\"scale\", \"Scale the size of the smallest dimension of the image to\"\n         \" this. Scale and minsize are mutually exclusive.\"\n         \" Must be larger than crop\")\n    .Arg(\"minsize\", \"Scale the size of the smallest dimension of the image to\"\n         \" this only if the size is initially smaller. Scale and minsize are\"\n         \" mutually exclusive. Must be larger than crop.\")\n    .Arg(\"warp\", \"If 1, both dimensions of the image will be set to minsize or\"\n         \" scale; otherwise, the other dimension is proportionally scaled.\"\n         \" Defaults to 0\")\n    .Arg(\"crop\", \"Size to crop the image to. Must be provided\")\n    .Arg(\"mirror\", \"Whether or not to mirror the image. Defaults to 0\")\n    .Arg(\"mean\", \"Mean by which to normalize color channels.\"\n         \" Defaults to 0.\")\n    .Arg(\"mean_per_channel\", \"Vector of means per color channel \"\n         \" (1 or 3 elements). Defaults to mean argument. Channel order BGR\")\n    .Arg(\"std\", \"Standard deviation by which to normalize color channels.\"\n         \" Defaults to 1.\")\n    .Arg(\"std_per_channel\", \"Vector of standard dev. per color channel \"\n     \" (1 or 3 elements). Defaults to std argument. Channel order is BGR\")\n    .Arg(\"bounding_ymin\", \"Bounding box coordinate. Defaults to -1 (none)\")\n    .Arg(\"bounding_xmin\", \"Bounding box coordinate. Defaults to -1 (none)\")\n    .Arg(\"bounding_height\", \"Bounding box coordinate. Defaults to -1 (none)\")\n    .Arg(\"bounding_width\", \"Bounding box coordinate. Defaults to -1 (none)\")\n    .ArgIsTest(\"Set to 1 to do deterministic cropping. Defaults to 0\")\n    .Arg(\"use_caffe_datum\", \"1 if the input is in Caffe format. Defaults to 0\")\n    .Arg(\"use_gpu_transform\", \"1 if GPU acceleration should be used.\"\n         \" Defaults to 0. Can only be 1 in a CUDAContext\")\n    .Arg(\"decode_threads\", \"Number of CPU decode/transform threads.\"\n         \" Defaults to 4\")\n    .Arg(\"output_type\", \"If gpu_transform, can set to FLOAT or FLOAT16.\")\n    .Arg(\"db\", \"Name of the database (if not passed as input)\")\n    .Arg(\"db_type\", \"Type of database (if not passed as input).\"\n         \" Defaults to leveldb\")\n    .Arg(\"output_sizes\", \"The sizes of any outputs besides the data and label \"\n         \"(should have a number of elements equal to the number of additional \"\n         \"outputs)\")\n    .Arg(\"random_scale\", \"[min, max] shortest-side desired for image resize. \"\n         \"Defaults to [-1, -1] or no random resize desired.\")\n    .Input(0, \"reader\", \"The input reader (a db::DBReader)\")\n    .Output(0, \"data\", \"Tensor containing the images\")\n    .Output(1, \"label\", \"Tensor containing the labels\")\n    .Output(2, \"additional outputs\", \"Any outputs after the first 2 will be \"\n            \"Tensors read from the input TensorProtos\");\n\nNO_GRADIENT(ImageInput);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/image/image_input_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#ifndef CAFFE2_IMAGE_IMAGE_INPUT_OP_H_\n#define CAFFE2_IMAGE_IMAGE_INPUT_OP_H_\n\n#include <opencv2/opencv.hpp>\n\n#include <iostream>\n#include <algorithm>\n\n#include \"caffe/proto/caffe.pb.h\"\n#include \"caffe2/core/db.h\"\n#include \"caffe2/utils/cast.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/thread_pool.h\"\n#include \"caffe2/operators/prefetch_op.h\"\n#include \"caffe2/image/transform_gpu.h\"\n\nnamespace caffe2 {\n\nclass CUDAContext;\n\ntemplate <class Context>\nclass ImageInputOp final\n    : public PrefetchOperator<Context> {\n  // SINGLE_LABEL: single integer label for multi-class classification\n  // MULTI_LABEL_SPARSE: sparse active label indices for multi-label classification\n  // MULTI_LABEL_DENSE: dense label embedding vector for label embedding regression\n  // MULTI_LABEL_WEIGHTED_SPARSE: sparse active label indices with per-label weights\n  // for multi-label classification\n  // SINGLE_LABEL_WEIGHTED: single integer label for multi-class classification with weighted sampling\n  enum LABEL_TYPE {\n    SINGLE_LABEL = 0,\n    MULTI_LABEL_SPARSE = 1,\n    MULTI_LABEL_DENSE = 2,\n    MULTI_LABEL_WEIGHTED_SPARSE = 3,\n    SINGLE_LABEL_WEIGHTED = 4\n  };\n\n  // INCEPTION_STYLE: Random crop with size 8% - 100% image area and aspect\n  // ratio in [3/4, 4/3]. Reference: GoogleNet paper\n  enum SCALE_JITTER_TYPE {\n    NO_SCALE_JITTER = 0,\n    INCEPTION_STYLE = 1\n    // TODO(zyan3): ResNet-style random scale jitter\n  };\n\n public:\n  using OperatorBase::OutputSize;\n  using PrefetchOperator<Context>::context_;\n  using PrefetchOperator<Context>::prefetch_thread_;\n  explicit ImageInputOp(const OperatorDef& operator_def,\n                                    Workspace* ws);\n  ~ImageInputOp() {\n    PrefetchOperator<Context>::Finalize();\n  }\n\n  bool Prefetch() override;\n  bool CopyPrefetched() override;\n\n private:\n  using BoundingBox = struct {\n    bool valid;\n    int ymin;\n    int xmin;\n    int height;\n    int width;\n  };\n\n  // Structure to store per-image information\n  // This can be modified by the DecodeAnd* so needs\n  // to be privatized per launch.\n  using PerImageArg = struct {\n    BoundingBox bounding_params;\n  };\n\n  bool GetImageAndLabelAndInfoFromDBValue(\n      const string& value, cv::Mat* img, PerImageArg& info, int item_id,\n      std::mt19937* randgen);\n  void DecodeAndTransform(\n      const std::string& value, float *image_data, int item_id,\n      const int channels, std::size_t thread_index);\n  void DecodeAndTransposeOnly(\n      const std::string& value, uint8_t *image_data, int item_id,\n      const int channels, std::size_t thread_index);\n\n  unique_ptr<db::DBReader> owned_reader_;\n  const db::DBReader* reader_;\n  CPUContext cpu_context_;\n  TensorCPU prefetched_image_;\n  TensorCPU prefetched_label_;\n  vector<TensorCPU> prefetched_additional_outputs_;\n  Tensor<Context> prefetched_image_on_device_;\n  Tensor<Context> prefetched_label_on_device_;\n  vector<Tensor<Context>> prefetched_additional_outputs_on_device_;\n  // Default parameters for images\n  PerImageArg default_arg_;\n  int batch_size_;\n  LABEL_TYPE label_type_;\n  int num_labels_;\n\n  bool color_;\n  bool color_jitter_;\n  float img_saturation_;\n  float img_brightness_;\n  float img_contrast_;\n  bool color_lighting_;\n  float color_lighting_std_;\n  std::vector<std::vector<float>> color_lighting_eigvecs_;\n  std::vector<float> color_lighting_eigvals_;\n  SCALE_JITTER_TYPE scale_jitter_type_;\n  int scale_;\n  // Minsize is similar to scale except that it will only\n  // force the image to scale up if it is too small. In other words,\n  // it ensures that both dimensions of the image are at least minsize_\n  int minsize_;\n  bool warp_;\n  int crop_;\n  std::vector<float> mean_;\n  std::vector<float> std_;\n  Tensor<Context> mean_gpu_;\n  Tensor<Context> std_gpu_;\n  bool mirror_;\n  bool is_test_;\n  bool use_caffe_datum_;\n  bool gpu_transform_;\n  bool mean_std_copied_ = false;\n\n  // thread pool for parse + decode\n  int num_decode_threads_;\n  int additional_inputs_offset_;\n  int additional_inputs_count_;\n  std::shared_ptr<TaskThreadPool> thread_pool_;\n\n  // Output type for GPU transform path\n  TensorProto_DataType output_type_;\n\n  // random minsize\n  vector<int> random_scale_;\n  bool random_scaling_;\n\n\n  // Working variables\n  std::vector<std::mt19937> randgen_per_thread_;\n};\n\ntemplate <class Context>\nImageInputOp<Context>::ImageInputOp(\n    const OperatorDef& operator_def,\n    Workspace* ws)\n    : PrefetchOperator<Context>(operator_def, ws),\n      reader_(nullptr),\n      prefetched_additional_outputs_(OutputSize() - 2),\n      prefetched_additional_outputs_on_device_(OutputSize() - 2),\n      batch_size_(\n          OperatorBase::template GetSingleArgument<int>(\"batch_size\", 0)),\n      label_type_(static_cast<LABEL_TYPE>(\n          OperatorBase::template GetSingleArgument<int>(\"label_type\", 0))),\n      num_labels_(\n          OperatorBase::template GetSingleArgument<int>(\"num_labels\", 0)),\n      color_(OperatorBase::template GetSingleArgument<int>(\"color\", 1)),\n      color_jitter_(\n          OperatorBase::template GetSingleArgument<int>(\"color_jitter\", 0)),\n      img_saturation_(OperatorBase::template GetSingleArgument<float>(\n          \"img_saturation\",\n          0.4)),\n      img_brightness_(OperatorBase::template GetSingleArgument<float>(\n          \"img_brightness\",\n          0.4)),\n      img_contrast_(\n          OperatorBase::template GetSingleArgument<float>(\"img_contrast\", 0.4)),\n      color_lighting_(\n          OperatorBase::template GetSingleArgument<int>(\"color_lighting\", 0)),\n      color_lighting_std_(OperatorBase::template GetSingleArgument<float>(\n          \"color_lighting_std\",\n          0.1)),\n      scale_jitter_type_(static_cast<SCALE_JITTER_TYPE>(\n          OperatorBase::template GetSingleArgument<int>(\n              \"scale_jitter_type\",\n              0))),\n      scale_(OperatorBase::template GetSingleArgument<int>(\"scale\", -1)),\n      minsize_(OperatorBase::template GetSingleArgument<int>(\"minsize\", -1)),\n      warp_(OperatorBase::template GetSingleArgument<int>(\"warp\", 0)),\n      crop_(OperatorBase::template GetSingleArgument<int>(\"crop\", -1)),\n      mirror_(OperatorBase::template GetSingleArgument<int>(\"mirror\", 0)),\n      is_test_(OperatorBase::template GetSingleArgument<int>(\n          OpSchema::Arg_IsTest,\n          0)),\n      use_caffe_datum_(\n          OperatorBase::template GetSingleArgument<int>(\"use_caffe_datum\", 0)),\n      gpu_transform_(OperatorBase::template GetSingleArgument<int>(\n          \"use_gpu_transform\",\n          0)),\n      num_decode_threads_(\n          OperatorBase::template GetSingleArgument<int>(\"decode_threads\", 4)),\n      thread_pool_(std::make_shared<TaskThreadPool>(num_decode_threads_)),\n      // output type only supported with CUDA and use_gpu_transform for now\n      output_type_(\n          cast::GetCastDataType(ArgumentHelper(operator_def), \"output_type\")),\n      random_scale_(\n          OperatorBase::template GetRepeatedArgument<int>(\"random_scale\", {-1,-1})) {\n  if ((random_scale_[0] == -1) || (random_scale_[1] == -1)) {\n    random_scaling_ = false;\n  } else {\n    random_scaling_ = true;\n    minsize_ = random_scale_[0];\n  }\n\n  mean_ = OperatorBase::template GetRepeatedArgument<float>(\n    \"mean_per_channel\",\n    {OperatorBase::template GetSingleArgument<float>(\"mean\", 0.)});\n\n  std_ = OperatorBase::template GetRepeatedArgument<float>(\n    \"std_per_channel\",\n    {OperatorBase::template GetSingleArgument<float>(\"std\", 1.)});\n\n  vector<int> additional_output_sizes =\n      OperatorBase::template GetRepeatedArgument<int>(\n          \"output_sizes\", vector<int>(OutputSize() - 2, 1));\n  additional_inputs_count_ = OutputSize() - 2;\n\n  default_arg_.bounding_params = {\n    false,\n    OperatorBase::template GetSingleArgument<int>(\"bounding_ymin\", -1),\n    OperatorBase::template GetSingleArgument<int>(\"bounding_xmin\", -1),\n    OperatorBase::template GetSingleArgument<int>(\"bounding_height\", -1),\n    OperatorBase::template GetSingleArgument<int>(\"bounding_width\", -1),\n  };\n\n  if (operator_def.input_size() == 0) {\n    LOG(ERROR) << \"You are using an old ImageInputOp format that creates \"\n                       \"a local db reader. Consider moving to the new style \"\n                       \"that takes in a DBReader blob instead.\";\n    string db_name =\n        OperatorBase::template GetSingleArgument<string>(\"db\", \"\");\n    CAFFE_ENFORCE_GT(db_name.size(), 0, \"Must specify a db name.\");\n    owned_reader_.reset(new db::DBReader(\n        OperatorBase::template GetSingleArgument<string>(\n            \"db_type\", \"leveldb\"),\n        db_name));\n    reader_ = owned_reader_.get();\n  }\n\n  // hard-coded PCA eigenvectors and eigenvalues, based on RBG channel order\n  color_lighting_eigvecs_.push_back(\n    std::vector<float>{-144.7125, 183.396, 102.2295});\n  color_lighting_eigvecs_.push_back(\n    std::vector<float>{-148.104, -1.1475, -207.57});\n  color_lighting_eigvecs_.push_back(\n    std::vector<float>{-148.818, -177.174, 107.1765});\n\n  color_lighting_eigvals_ = std::vector<float>{0.2175, 0.0188, 0.0045};\n\n  CAFFE_ENFORCE_GT(batch_size_, 0, \"Batch size should be nonnegative.\");\n  if (use_caffe_datum_) {\n    CAFFE_ENFORCE(label_type_ == SINGLE_LABEL || label_type_ == SINGLE_LABEL_WEIGHTED,\n      \"Caffe datum only supports single integer label\");\n  }\n  if (label_type_ !=  SINGLE_LABEL && label_type_ != SINGLE_LABEL_WEIGHTED) {\n    CAFFE_ENFORCE_GT(num_labels_, 0,\n      \"Number of labels must be set for using either sparse label indices or dense label embedding.\");\n  }\n  if (label_type_ == MULTI_LABEL_WEIGHTED_SPARSE ||\n    label_type_ == SINGLE_LABEL_WEIGHTED) {\n    additional_inputs_offset_ = 3;\n  } else {\n    additional_inputs_offset_ = 2;\n  }\n  CAFFE_ENFORCE((scale_ > 0) != (minsize_ > 0),\n                \"Must provide one and only one of scaling or minsize\");\n  CAFFE_ENFORCE_GT(crop_, 0, \"Must provide the cropping value.\");\n  CAFFE_ENFORCE_GE(\n    scale_ > 0 ? scale_ : minsize_,\n    crop_, \"The scale/minsize value must be no smaller than the crop value.\");\n\n  CAFFE_ENFORCE_EQ(\n      mean_.size(),\n      std_.size(),\n      \"The mean and std. dev vectors must be of the same size.\");\n  CAFFE_ENFORCE(mean_.size() == 1 || mean_.size() == 3,\n                \"The mean and std. dev vectors must be of size 1 or 3\");\n  CAFFE_ENFORCE(\n      !use_caffe_datum_ || OutputSize() == 2,\n      \"There can only be 2 outputs if the Caffe datum format is used\");\n  CAFFE_ENFORCE(\n      additional_output_sizes.size() == OutputSize() - 2,\n      \"If the output sizes are specified, they must be specified for all \"\n      \"additional outputs\");\n\n  CAFFE_ENFORCE(random_scale_.size() == 2,\n      \"Must provide [scale_min, scale_max]\");\n  CAFFE_ENFORCE_GE(random_scale_[1], random_scale_[0],\n      \"random scale must provide a range [min, max]\");\n\n  if (default_arg_.bounding_params.ymin < 0\n      || default_arg_.bounding_params.xmin < 0\n      || default_arg_.bounding_params.height < 0\n      || default_arg_.bounding_params.width < 0) {\n    default_arg_.bounding_params.valid = false;\n  } else {\n    default_arg_.bounding_params.valid = true;\n  }\n\n  if (mean_.size() == 1) {\n    // We are going to extend to 3 using the first value\n    mean_.resize(3, mean_[0]);\n    std_.resize(3, std_[0]);\n  }\n\n  LOG(INFO) << \"Creating an image input op with the following setting: \";\n  LOG(INFO) << \"    Using \" << num_decode_threads_ << \" CPU threads;\";\n  if (gpu_transform_) {\n    LOG(INFO) << \"    Performing transformation on GPU\";\n  }\n  LOG(INFO) << \"    Outputting in batches of \" << batch_size_ << \" images;\";\n  LOG(INFO) << \"    Treating input image as \"\n            << (color_ ? \"color \" : \"grayscale \") << \"image;\";\n  if (default_arg_.bounding_params.valid) {\n    LOG(INFO) << \"    Applying a default bounding box of Y [\"\n              << default_arg_.bounding_params.ymin << \"; \"\n              << default_arg_.bounding_params.ymin +\n      default_arg_.bounding_params.height\n              << \") x X [\"\n              << default_arg_.bounding_params.xmin << \"; \"\n              << default_arg_.bounding_params.xmin +\n      default_arg_.bounding_params.width\n              << \")\";\n  }\n  if (scale_ > 0 && !random_scaling_) {\n    LOG(INFO) << \"    Scaling image to \" << scale_\n              << (warp_ ? \" with \" : \" without \") << \"warping;\";\n  } else {\n    if (random_scaling_) {\n      // randomly set min_size_ for each image\n      LOG(INFO) << \"    Randomly scaling shortest side between \"\n                << random_scale_[0] << \" and \"\n                << random_scale_[1];\n    } else {\n      // Here, minsize_ > 0\n      LOG(INFO) << \"    Ensuring minimum image size of \" << minsize_\n                << (warp_ ? \" with \" : \" without \") << \"warping;\";\n    }\n  }\n  LOG(INFO) << \"    \" << (is_test_ ? \"Central\" : \"Random\")\n            << \" cropping image to \" << crop_\n            << (mirror_ ? \" with \" : \" without \") << \"random mirroring;\";\n  LOG(INFO) << \"Label Type: \" << label_type_;\n  LOG(INFO) << \"Num Labels: \" << num_labels_;\n\n  auto mit = mean_.begin();\n  auto sit = std_.begin();\n\n  for (int i = 0;\n       mit != mean_.end() && sit != std_.end();\n       ++mit, ++sit, ++i) {\n    LOG(INFO) << \"    Default [Channel \" << i << \"] Subtract mean \" << *mit\n              << \" and divide by std \" << *sit << \".\";\n    // We actually will use the inverse of std, so inverse it here\n    *sit = 1.f / *sit;\n  }\n  LOG(INFO) << \"    Outputting images as \"\n            << OperatorBase::template GetSingleArgument<string>(\"output_type\", \"unknown\") << \".\";\n\n  std::mt19937 meta_randgen(time(nullptr));\n  for (int i = 0; i < num_decode_threads_; ++i) {\n    randgen_per_thread_.emplace_back(meta_randgen());\n  }\n  prefetched_image_.Resize(\n      TIndex(batch_size_),\n      TIndex(crop_),\n      TIndex(crop_),\n      TIndex(color_ ? 3 : 1));\n  if (label_type_ != SINGLE_LABEL && label_type_ != SINGLE_LABEL_WEIGHTED) {\n    prefetched_label_.Resize(TIndex(batch_size_), TIndex(num_labels_));\n  } else {\n    prefetched_label_.Resize(vector<TIndex>(1, batch_size_));\n  }\n\n  for (int i = 0; i < additional_output_sizes.size(); ++i) {\n    prefetched_additional_outputs_[i].Resize(\n        TIndex(batch_size_), TIndex(additional_output_sizes[i]));\n  }\n}\n\n// Inception-stype scale jittering\ntemplate <class Context>\nbool RandomSizedCropping(\n  cv::Mat* img,\n  const int crop,\n  std::mt19937* randgen\n) {\n  cv::Mat scaled_img;\n  bool inception_scale_jitter = false;\n  int im_height = img->rows, im_width = img->cols;\n  int area = im_height * im_width;\n  std::uniform_real_distribution<> area_dis(0.08, 1.0);\n  std::uniform_real_distribution<> aspect_ratio_dis(3.0 / 4.0, 4.0 / 3.0);\n\n  cv::Mat cropping;\n  for (int i = 0; i < 10; ++i) {\n    int target_area = int(ceil(area_dis(*randgen) * area));\n    float aspect_ratio = aspect_ratio_dis(*randgen);\n    int nh = floor(std::sqrt(((float)target_area / aspect_ratio)));\n    int nw = floor(std::sqrt(((float)target_area * aspect_ratio)));\n    if (nh >= 1 && nh <= im_height && nw >=1 && nw <= im_width) {\n      int height_offset = std::uniform_int_distribution<>(\n        0, im_height - nh)(*randgen);\n      int width_offset = std::uniform_int_distribution<>(\n        0,im_width - nw)(*randgen);\n      cv::Rect ROI(width_offset, height_offset, nw, nh);\n      cropping = (*img)(ROI);\n      cv::resize(\n          cropping,\n          scaled_img,\n          cv::Size(crop, crop),\n          0,\n          0,\n          cv::INTER_AREA);\n      *img = scaled_img;\n      inception_scale_jitter = true;\n      break;\n    }\n  }\n  return inception_scale_jitter;\n}\n\ntemplate <class Context>\nbool ImageInputOp<Context>::GetImageAndLabelAndInfoFromDBValue(\n    const string& value,\n    cv::Mat* img,\n    PerImageArg& info,\n    int item_id,\n    std::mt19937* randgen) {\n  //\n  // recommend using --caffe2_use_fatal_for_enforce=1 when using ImageInputOp\n  // as this function runs on a worker thread and the exceptions from\n  // CAFFE_ENFORCE are silently dropped by the thread worker functions\n  //\n  cv::Mat src;\n\n  // Use the default information for images\n  info = default_arg_;\n  if (use_caffe_datum_) {\n    // The input is a caffe datum format.\n    caffe::Datum datum;\n    CAFFE_ENFORCE(datum.ParseFromString(value));\n\n    prefetched_label_.mutable_data<int>()[item_id] = datum.label();\n    if (datum.encoded()) {\n      // encoded image in datum.\n      src = cv::imdecode(\n          cv::Mat(\n              1,\n              datum.data().size(),\n              CV_8UC1,\n              const_cast<char*>(datum.data().data())),\n          color_ ? CV_LOAD_IMAGE_COLOR : CV_LOAD_IMAGE_GRAYSCALE);\n    } else {\n      // Raw image in datum.\n      CAFFE_ENFORCE(datum.channels() == 3 || datum.channels() == 1);\n\n      int src_c = datum.channels();\n      src.create(\n          datum.height(), datum.width(), (src_c == 3) ? CV_8UC3 : CV_8UC1);\n\n      if (src_c == 1) {\n        memcpy(src.ptr<uchar>(0), datum.data().data(), datum.data().size());\n      } else {\n        // Datum stores things in CHW order, let's do HWC for images to make\n        // things more consistent with conventional image storage.\n        for (int c = 0; c < 3; ++c) {\n          const char* datum_buffer =\n              datum.data().data() + datum.height() * datum.width() * c;\n          uchar* ptr = src.ptr<uchar>(0) + c;\n          for (int h = 0; h < datum.height(); ++h) {\n            for (int w = 0; w < datum.width(); ++w) {\n              *ptr = *(datum_buffer++);\n              ptr += 3;\n            }\n          }\n        }\n      }\n    }\n  } else {\n    // The input is a caffe2 format.\n    TensorProtos protos;\n    CAFFE_ENFORCE(protos.ParseFromString(value));\n    const TensorProto& image_proto = protos.protos(0);\n    const TensorProto& label_proto = protos.protos(1);\n    vector<TensorProto> additional_output_protos;\n    int start = additional_inputs_offset_;\n    int end = start + additional_inputs_count_;\n    for (int i = start; i < end; ++i) {\n      additional_output_protos.push_back(protos.protos(i));\n    }\n\n    if (protos.protos_size() == end + 1) {\n      // We have bounding box information\n      const TensorProto& bounding_proto = protos.protos(end);\n      DCHECK_EQ(bounding_proto.data_type(), TensorProto::INT32);\n      DCHECK_EQ(bounding_proto.int32_data_size(), 4);\n      info.bounding_params.valid = true;\n      info.bounding_params.ymin = bounding_proto.int32_data(0);\n      info.bounding_params.xmin = bounding_proto.int32_data(1);\n      info.bounding_params.height = bounding_proto.int32_data(2);\n      info.bounding_params.width = bounding_proto.int32_data(3);\n    }\n\n    if (image_proto.data_type() == TensorProto::STRING) {\n      // encoded image string.\n      DCHECK_EQ(image_proto.string_data_size(), 1);\n      const string& encoded_image_str = image_proto.string_data(0);\n      int encoded_size = encoded_image_str.size();\n      // We use a cv::Mat to wrap the encoded str so we do not need a copy.\n      src = cv::imdecode(\n          cv::Mat(\n              1,\n              &encoded_size,\n              CV_8UC1,\n              const_cast<char*>(encoded_image_str.data())),\n          color_ ? CV_LOAD_IMAGE_COLOR : CV_LOAD_IMAGE_GRAYSCALE);\n    } else if (image_proto.data_type() == TensorProto::BYTE) {\n      // raw image content.\n      int src_c = (image_proto.dims_size() == 3) ? image_proto.dims(2) : 1;\n      CAFFE_ENFORCE(src_c == 3 || src_c == 1);\n\n      src.create(\n          image_proto.dims(0),\n          image_proto.dims(1),\n          (src_c == 3) ? CV_8UC3 : CV_8UC1);\n      memcpy(\n          src.ptr<uchar>(0),\n          image_proto.byte_data().data(),\n          image_proto.byte_data().size());\n    } else {\n      LOG(FATAL) << \"Unknown image data type.\";\n    }\n\n    if (label_proto.data_type() == TensorProto::FLOAT) {\n      if (label_type_ == SINGLE_LABEL || label_type_ == SINGLE_LABEL_WEIGHTED) {\n        DCHECK_EQ(label_proto.float_data_size(), 1);\n        prefetched_label_.mutable_data<float>()[item_id] =\n            label_proto.float_data(0);\n      } else if (label_type_ == MULTI_LABEL_SPARSE) {\n        float* label_data = prefetched_label_.mutable_data<float>() +\n          item_id * num_labels_;\n        memset(label_data, 0, sizeof(float) * num_labels_);\n        for (int i = 0; i < label_proto.float_data_size(); ++i) {\n          label_data[(int)label_proto.float_data(i)] = 1.0;\n        }\n      } else if (label_type_ == MULTI_LABEL_WEIGHTED_SPARSE) {\n        const TensorProto& weight_proto = protos.protos(2);\n        float* label_data =\n            prefetched_label_.mutable_data<float>() + item_id * num_labels_;\n        memset(label_data, 0, sizeof(float) * num_labels_);\n        for (int i = 0; i < label_proto.float_data_size(); ++i) {\n          label_data[(int)label_proto.float_data(i)] =\n              weight_proto.float_data(i);\n        }\n      } else if (label_type_ == MULTI_LABEL_DENSE) {\n        CAFFE_ENFORCE(label_proto.float_data_size() == num_labels_);\n        float* label_data = prefetched_label_.mutable_data<float>() +\n          item_id * num_labels_;\n        for (int i = 0; i < label_proto.float_data_size(); ++i) {\n          label_data[i] = label_proto.float_data(i);\n        }\n      } else {\n        LOG(ERROR) << \"Unknown label type:\" << label_type_;\n      }\n    } else if (label_proto.data_type() == TensorProto::INT32) {\n      if (label_type_ == SINGLE_LABEL || label_type_ == SINGLE_LABEL_WEIGHTED) {\n        DCHECK_EQ(label_proto.int32_data_size(), 1);\n        prefetched_label_.mutable_data<int>()[item_id] =\n            label_proto.int32_data(0);\n      } else if (label_type_ == MULTI_LABEL_SPARSE) {\n        int* label_data = prefetched_label_.mutable_data<int>() +\n          item_id * num_labels_;\n        memset(label_data, 0, sizeof(int) * num_labels_);\n        for (int i = 0; i < label_proto.int32_data_size(); ++i) {\n          label_data[label_proto.int32_data(i)] = 1;\n        }\n      } else if (label_type_ == MULTI_LABEL_WEIGHTED_SPARSE) {\n        const TensorProto& weight_proto = protos.protos(2);\n        float* label_data =\n            prefetched_label_.mutable_data<float>() + item_id * num_labels_;\n        memset(label_data, 0, sizeof(float) * num_labels_);\n        for (int i = 0; i < label_proto.int32_data_size(); ++i) {\n          label_data[label_proto.int32_data(i)] = weight_proto.float_data(i);\n        }\n      } else if (label_type_ == MULTI_LABEL_DENSE) {\n        CAFFE_ENFORCE(label_proto.int32_data_size() == num_labels_);\n        int* label_data = prefetched_label_.mutable_data<int>() +\n          item_id * num_labels_;\n        for (int i = 0; i < label_proto.int32_data_size(); ++i) {\n          label_data[i] = label_proto.int32_data(i);\n        }\n      } else {\n        LOG(ERROR) << \"Unknown label type:\" << label_type_;\n      }\n    } else {\n      LOG(FATAL) << \"Unsupported label data type.\";\n    }\n\n    for (int i = 0; i < additional_output_protos.size(); ++i) {\n      auto additional_output_proto = additional_output_protos[i];\n\n      if (additional_output_proto.data_type() == TensorProto::FLOAT) {\n        float* additional_output =\n            prefetched_additional_outputs_[i].template mutable_data<float>() +\n            item_id * additional_output_proto.float_data_size();\n\n        for (int j = 0; j < additional_output_proto.float_data_size(); ++j) {\n          additional_output[j] = additional_output_proto.float_data(j);\n        }\n      } else if (additional_output_proto.data_type() == TensorProto::INT32) {\n        int* additional_output =\n            prefetched_additional_outputs_[i].template mutable_data<int>() +\n            item_id * additional_output_proto.int32_data_size();\n\n        for (int j = 0; j < additional_output_proto.int32_data_size(); ++j) {\n          additional_output[j] = additional_output_proto.int32_data(j);\n        }\n      } else if (additional_output_proto.data_type() == TensorProto::INT64) {\n        int64_t* additional_output =\n            prefetched_additional_outputs_[i].template mutable_data<int64_t>() +\n            item_id * additional_output_proto.int64_data_size();\n\n        for (int j = 0; j < additional_output_proto.int64_data_size(); ++j) {\n          additional_output[j] = additional_output_proto.int64_data(j);\n        }\n      }\n      else {\n        LOG(FATAL) << \"Unsupported output type.\";\n      }\n    }\n  }\n\n  //\n  // convert source to the color format requested from Op\n  //\n  int out_c = color_ ? 3 : 1;\n  if (out_c == src.channels()) {\n    *img = src;\n  } else {\n    cv::cvtColor(src, *img, (out_c == 1) ? CV_BGR2GRAY : CV_GRAY2BGR);\n  }\n\n  // Note(Yangqing): I believe that the mat should be created continuous.\n  CAFFE_ENFORCE(img->isContinuous());\n\n  // Sanity check now that we decoded everything\n\n  // Ensure that the bounding box is legit\n  if (info.bounding_params.valid\n      && (src.rows < info.bounding_params.ymin + info.bounding_params.height\n        || src.cols < info.bounding_params.xmin + info.bounding_params.width\n     )) {\n    info.bounding_params.valid = false;\n  }\n\n  // Apply the bounding box if requested\n  if (info.bounding_params.valid) {\n    // If we reach here, we know the parameters are sane\n    cv::Rect bounding_box(info.bounding_params.xmin, info.bounding_params.ymin,\n                          info.bounding_params.width, info.bounding_params.height);\n    *img = (*img)(bounding_box);\n\n    /*\n    LOG(INFO) << \"Did bounding with ymin:\"\n              << info.bounding_params.ymin << \" xmin:\" << info.bounding_params.xmin\n              << \" height:\" << info.bounding_params.height\n              << \" width:\" << info.bounding_params.width << \"\\n\";\n    LOG(INFO) << \"Bounded matrix: \" << img;\n    */\n  } else {\n    // LOG(INFO) << \"No bounding\\n\";\n  }\n\n  cv::Mat scaled_img;\n  bool inception_scale_jitter = false;\n  if (scale_jitter_type_ == INCEPTION_STYLE) {\n    if (!is_test_) {\n      // Inception-stype scale jittering is only used for training\n      inception_scale_jitter = RandomSizedCropping<Context>(img, crop_, randgen);\n      // if a random crop is still not found, do simple random cropping later\n    }\n  }\n\n  if ((scale_jitter_type_ == NO_SCALE_JITTER) ||\n    (scale_jitter_type_ == INCEPTION_STYLE && !inception_scale_jitter)) {\n      int scaled_width, scaled_height;\n      int scale_to_use = scale_ > 0 ? scale_ : minsize_;\n\n      // set the random minsize\n      if (random_scaling_) {\n        scale_to_use = std::uniform_int_distribution<>(random_scale_[0],\n                                                       random_scale_[1])(*randgen);\n      }\n\n      if (warp_) {\n        scaled_width = scale_to_use;\n        scaled_height = scale_to_use;\n      } else if (img->rows > img->cols) {\n        scaled_width = scale_to_use;\n        scaled_height =\n            static_cast<float>(img->rows) * scale_to_use / img->cols;\n      } else {\n        scaled_height = scale_to_use;\n        scaled_width =\n            static_cast<float>(img->cols) * scale_to_use / img->rows;\n      }\n      if ((scale_ > 0 &&\n           (scaled_height != img->rows || scaled_width != img->cols))\n          || (scaled_height > img->rows || scaled_width > img->cols)) {\n        // We rescale in all cases if we are using scale_\n        // but only to make the image bigger if using minsize_\n        /*\n        LOG(INFO) << \"Scaling to \" << scaled_width << \" x \" << scaled_height\n                  << \" From \" << img->cols << \" x \" << img->rows;\n        */\n        cv::resize(\n            *img,\n            scaled_img,\n            cv::Size(scaled_width, scaled_height),\n            0,\n            0,\n            cv::INTER_AREA);\n        *img = scaled_img;\n      }\n  }\n  // TODO(Yangqing): return false if any error happens.\n  return true;\n}\n\n// assume HWC order and color channels BGR\ntemplate <class Context>\nvoid Saturation(\n  float* img,\n  const int img_size,\n  const float alpha_rand,\n  std::mt19937* randgen\n) {\n  float alpha = 1.0f +\n    std::uniform_real_distribution<float>(-alpha_rand, alpha_rand)(*randgen);\n  // BGR to Gray scale image: R -> 0.299, G -> 0.587, B -> 0.114\n  int p = 0;\n  for (int h = 0; h < img_size; ++h) {\n    for (int w = 0; w < img_size; ++w) {\n      float gray_color = img[3 * p] * 0.114f + img[3 * p + 1] * 0.587f +\n        img[3 * p + 2] * 0.299f;\n      for (int c = 0; c < 3; ++c) {\n        img[3 * p + c] = img[3 * p + c] * alpha + gray_color * (1.0f - alpha);\n      }\n      p++;\n    }\n  }\n}\n\n// assume HWC order and color channels BGR\ntemplate <class Context>\nvoid Brightness(\n  float* img,\n  const int img_size,\n  const float alpha_rand,\n  std::mt19937* randgen\n) {\n  float alpha = 1.0f +\n    std::uniform_real_distribution<float>(-alpha_rand, alpha_rand)(*randgen);\n  int p = 0;\n  for (int h = 0; h < img_size; ++h) {\n    for (int w = 0; w < img_size; ++w) {\n      for (int c = 0; c < 3; ++c) {\n        img[p++] *= alpha;\n      }\n    }\n  }\n}\n\n// assume HWC order and color channels BGR\ntemplate <class Context>\nvoid Contrast(\n  float* img,\n  const int img_size,\n  const float alpha_rand,\n  std::mt19937* randgen\n){\n  float gray_mean = 0;\n  int p = 0;\n  for (int h = 0; h < img_size; ++h) {\n    for (int w = 0; w < img_size; ++w) {\n      // BGR to Gray scale image: R -> 0.299, G -> 0.587, B -> 0.114\n      gray_mean += img[3 * p] * 0.114f + img[3 * p + 1] * 0.587f +\n        img[3 * p + 2] * 0.299f;\n      p++;\n    }\n  }\n  gray_mean /= (img_size * img_size);\n\n  float alpha = 1.0f +\n    std::uniform_real_distribution<float>(-alpha_rand, alpha_rand)(*randgen);\n  p = 0;\n  for (int h = 0; h < img_size; ++h) {\n    for (int w = 0; w < img_size; ++w) {\n      for (int c = 0; c < 3; ++c) {\n        img[p] = img[p] * alpha + gray_mean * (1.0f - alpha);\n        p++;\n      }\n    }\n  }\n}\n\n// assume HWC order and color channels BGR\ntemplate <class Context>\nvoid ColorJitter(\n  float* img,\n  const int img_size,\n  const float saturation,\n  const float brightness,\n  const float contrast,\n  std::mt19937* randgen\n) {\n  std::srand (unsigned(std::time(0)));\n  std::vector<int> jitter_order{0, 1, 2};\n  // obtain a time-based seed:\n  unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();\n  std::shuffle(jitter_order.begin(), jitter_order.end(),\n    std::default_random_engine(seed));\n\n  for (int i = 0; i < 3; ++i) {\n    if (jitter_order[i] == 0) {\n      Saturation<Context>(img, img_size, saturation, randgen);\n    } else if (jitter_order[i] == 1) {\n      Brightness<Context>(img, img_size, brightness, randgen);\n    } else {\n      Contrast<Context>(img, img_size, contrast, randgen);\n    }\n  }\n}\n\n// assume HWC order and color channels BGR\ntemplate <class Context>\nvoid ColorLighting(\n  float* img,\n  const int img_size,\n  const float alpha_std,\n  const std::vector<std::vector<float>>& eigvecs,\n  const std::vector<float>& eigvals,\n  std::mt19937* randgen\n) {\n  std::normal_distribution<float> d(0, alpha_std);\n  std::vector<float> alphas(3);\n  for (int i = 0; i < 3; ++i) {\n    alphas[i] = d(*randgen);\n  }\n\n  std::vector<float> delta_rgb(3, 0.0);\n  for (int i = 0; i < 3; ++i) {\n    for (int j = 0; j < 3; ++j) {\n      delta_rgb[i] += eigvecs[i][j] * eigvals[j] * alphas[j];\n    }\n  }\n\n  int p = 0;\n  for (int h = 0; h < img_size; ++h) {\n    for (int w = 0; w < img_size; ++w) {\n      for (int c = 0; c < 3; ++c) {\n        img[p++] += delta_rgb[2 - c];\n      }\n    }\n  }\n\n}\n\n// assume HWC order and color channels BGR\n// mean subtraction and scaling.\ntemplate <class Context>\nvoid ColorNormalization(\n  float* img,\n  const int img_size,\n  const int channels,\n  const std::vector<float>& mean,\n  const std::vector<float>& std\n) {\n  int p = 0;\n  for (int h = 0; h < img_size; ++h) {\n    for (int w = 0; w < img_size; ++w) {\n      for (int c = 0; c < channels; ++c) {\n        img[p] = (img[p] - mean[c]) * std[c];\n        p++;\n      }\n    }\n  }\n}\n\n// Factored out image transformation\ntemplate <class Context>\nvoid TransformImage(\n    const cv::Mat& scaled_img,\n    const int channels,\n    float* image_data,\n    const bool color_jitter,\n    const float saturation,\n    const float brightness,\n    const float contrast,\n    const bool color_lighting,\n    const float color_lighting_std,\n    const std::vector<std::vector<float>>& color_lighting_eigvecs,\n    const std::vector<float>& color_lighting_eigvals,\n    const int crop,\n    const bool mirror,\n    const std::vector<float>& mean,\n    const std::vector<float>& std,\n    std::mt19937* randgen,\n    std::bernoulli_distribution* mirror_this_image,\n    bool is_test = false) {\n  CAFFE_ENFORCE_GE(\n      scaled_img.rows, crop, \"Image height must be bigger than crop.\");\n  CAFFE_ENFORCE_GE(\n      scaled_img.cols, crop, \"Image width must be bigger than crop.\");\n\n  // find the cropped region, and copy it to the destination matrix\n  int width_offset, height_offset;\n  if (is_test) {\n    width_offset = (scaled_img.cols - crop) / 2;\n    height_offset = (scaled_img.rows - crop) / 2;\n  } else {\n    width_offset =\n      std::uniform_int_distribution<>(0, scaled_img.cols - crop)(*randgen);\n    height_offset =\n      std::uniform_int_distribution<>(0, scaled_img.rows - crop)(*randgen);\n  }\n\n  float* image_data_ptr = image_data;\n  if (!is_test && mirror && (*mirror_this_image)(*randgen)) {\n    // Copy mirrored image.\n    for (int h = height_offset; h < height_offset + crop; ++h) {\n      for (int w = width_offset + crop - 1; w >= width_offset; --w) {\n        const uint8_t* cv_data = scaled_img.ptr(h) + w * channels;\n        for (int c = 0; c < channels; ++c) {\n          *(image_data_ptr++) = static_cast<float>(cv_data[c]);\n        }\n      }\n    }\n  } else {\n    // Copy normally.\n    for (int h = height_offset; h < height_offset + crop; ++h) {\n      for (int w = width_offset; w < width_offset + crop; ++w) {\n        const uint8_t* cv_data = scaled_img.ptr(h) + w * channels;\n        for (int c = 0; c < channels; ++c) {\n          *(image_data_ptr++) = static_cast<float>(cv_data[c]);\n        }\n      }\n    }\n  }\n\n  if (color_jitter && channels == 3 && !is_test) {\n    ColorJitter<Context>(image_data, crop, saturation, brightness, contrast,\n      randgen);\n  }\n  if (color_lighting && channels == 3 && !is_test) {\n    ColorLighting<Context>(image_data, crop, color_lighting_std,\n      color_lighting_eigvecs, color_lighting_eigvals, randgen);\n  }\n\n  // Color normalization\n  // Mean subtraction and scaling.\n  ColorNormalization<Context>(image_data, crop, channels, mean, std);\n}\n\n// Only crop / transose the image\n// leave in uint8_t dataType\ntemplate <class Context>\nvoid CropTransposeImage(const cv::Mat& scaled_img, const int channels,\n                        uint8_t *cropped_data, const int crop,\n                        const bool mirror, std::mt19937 *randgen,\n                        std::bernoulli_distribution *mirror_this_image,\n                        bool is_test = false) {\n  CAFFE_ENFORCE_GE(\n      scaled_img.rows, crop, \"Image height must be bigger than crop.\");\n  CAFFE_ENFORCE_GE(\n      scaled_img.cols, crop, \"Image width must be bigger than crop.\");\n\n  // find the cropped region, and copy it to the destination matrix\n  int width_offset, height_offset;\n  if (is_test) {\n    width_offset = (scaled_img.cols - crop) / 2;\n    height_offset = (scaled_img.rows - crop) / 2;\n  } else {\n    width_offset =\n      std::uniform_int_distribution<>(0, scaled_img.cols - crop)(*randgen);\n    height_offset =\n      std::uniform_int_distribution<>(0, scaled_img.rows - crop)(*randgen);\n  }\n\n  if (mirror && (*mirror_this_image)(*randgen)) {\n    // Copy mirrored image.\n    for (int h = height_offset; h < height_offset + crop; ++h) {\n      for (int w = width_offset + crop - 1; w >= width_offset; --w) {\n        const uint8_t* cv_data = scaled_img.ptr(h) + w*channels;\n        for (int c = 0; c < channels; ++c) {\n          *(cropped_data++) = cv_data[c];\n        }\n      }\n    }\n  } else {\n    // Copy normally.\n    for (int h = height_offset; h < height_offset + crop; ++h) {\n      for (int w = width_offset; w < width_offset + crop; ++w) {\n        const uint8_t* cv_data = scaled_img.ptr(h) + w*channels;\n        for (int c = 0; c < channels; ++c) {\n          *(cropped_data++) = cv_data[c];\n        }\n      }\n    }\n  }\n}\n\n// Parse datum, decode image, perform transform\n// Intended as entry point for binding to thread pool\ntemplate <class Context>\nvoid ImageInputOp<Context>::DecodeAndTransform(\n      const std::string& value, float *image_data, int item_id,\n      const int channels, std::size_t thread_index) {\n\n  CAFFE_ENFORCE((int)thread_index < num_decode_threads_);\n\n  std::bernoulli_distribution mirror_this_image(0.5f);\n  std::mt19937* randgen = &(randgen_per_thread_[thread_index]);\n\n  cv::Mat img;\n  // Decode the image\n  PerImageArg info;\n  CHECK(GetImageAndLabelAndInfoFromDBValue(value, &img, info, item_id,\n    randgen));\n\n  // Factor out the image transformation\n  TransformImage<Context>(img, channels, image_data,\n    color_jitter_, img_saturation_, img_brightness_, img_contrast_,\n    color_lighting_, color_lighting_std_, color_lighting_eigvecs_,\n    color_lighting_eigvals_, crop_, mirror_, mean_, std_,\n    randgen, &mirror_this_image, is_test_);\n}\n\ntemplate <class Context>\nvoid ImageInputOp<Context>::DecodeAndTransposeOnly(\n    const std::string& value, uint8_t *image_data, int item_id,\n    const int channels, std::size_t thread_index) {\n\n  CAFFE_ENFORCE((int)thread_index < num_decode_threads_);\n\n  std::bernoulli_distribution mirror_this_image(0.5f);\n  std::mt19937* randgen = &(randgen_per_thread_[thread_index]);\n\n  cv::Mat img;\n  // Decode the image\n  PerImageArg info;\n  CHECK(GetImageAndLabelAndInfoFromDBValue(value, &img, info, item_id,\n    randgen));\n\n  // Factor out the image transformation\n  CropTransposeImage<Context>(img, channels, image_data, crop_, mirror_,\n                              randgen, &mirror_this_image, is_test_);\n}\n\n\ntemplate <class Context>\nbool ImageInputOp<Context>::Prefetch() {\n  if (!owned_reader_.get()) {\n    // if we are not owning the reader, we will get the reader pointer from\n    // input. Otherwise the constructor should have already set the reader\n    // pointer.\n    reader_ = &OperatorBase::Input<db::DBReader>(0);\n  }\n  const int channels = color_ ? 3 : 1;\n  // Call mutable_data() once to allocate the underlying memory.\n  if (gpu_transform_) {\n    // we'll transfer up in int8, then convert later\n    prefetched_image_.mutable_data<uint8_t>();\n  } else {\n    prefetched_image_.mutable_data<float>();\n  }\n\n  prefetched_label_.mutable_data<int>();\n  // Prefetching handled with a thread pool of \"decode_threads\" threads.\n\n  for (int item_id = 0; item_id < batch_size_; ++item_id) {\n    std::string key, value;\n    cv::Mat img;\n\n    // read data\n    reader_->Read(&key, &value);\n\n    // determine label type based on first item\n    if( item_id == 0 ) {\n      if( use_caffe_datum_ ) {\n        prefetched_label_.mutable_data<int>();\n      } else {\n        TensorProtos protos;\n        CAFFE_ENFORCE(protos.ParseFromString(value));\n        TensorProto_DataType labeldt = protos.protos(1).data_type();\n        if( labeldt == TensorProto::INT32 ) {\n          prefetched_label_.mutable_data<int>();\n        } else if ( labeldt == TensorProto::FLOAT) {\n          prefetched_label_.mutable_data<float>();\n        } else {\n          LOG(FATAL) << \"Unsupported label type.\";\n        }\n\n        for (int i = 0; i < additional_inputs_count_; ++i) {\n          int index = additional_inputs_offset_ + i;\n          TensorProto additional_output_proto = protos.protos(index);\n\n          if (additional_output_proto.data_type() == TensorProto::FLOAT) {\n            prefetched_additional_outputs_[i].template mutable_data<float>();\n          } else if (\n              additional_output_proto.data_type() == TensorProto::INT32) {\n            prefetched_additional_outputs_[i].template mutable_data<int>();\n          } else if (\n              additional_output_proto.data_type() == TensorProto::INT64) {\n            prefetched_additional_outputs_[i].template mutable_data<int64_t>();\n          } else {\n            LOG(FATAL) << \"Unsupported output type.\";\n          }\n        }\n      }\n    }\n\n    // launch into thread pool for processing\n    // TODO: support color jitter and color lighting in gpu_transform\n    if (gpu_transform_) {\n      // output of decode will still be int8\n      uint8_t* image_data = prefetched_image_.mutable_data<uint8_t>() +\n          crop_ * crop_ * channels * item_id;\n      thread_pool_->runTaskWithID(std::bind(\n          &ImageInputOp<Context>::DecodeAndTransposeOnly,\n          this,\n          std::string(value),\n          image_data,\n          item_id,\n          channels,\n          std::placeholders::_1));\n    } else {\n      float* image_data = prefetched_image_.mutable_data<float>() +\n          crop_ * crop_ * channels * item_id;\n      thread_pool_->runTaskWithID(std::bind(\n          &ImageInputOp<Context>::DecodeAndTransform,\n          this,\n          std::string(value),\n          image_data,\n          item_id,\n          channels,\n          std::placeholders::_1));\n    }\n  }\n  thread_pool_->waitWorkComplete();\n\n  // If the context is not CPUContext, we will need to do a copy in the\n  // prefetch function as well.\n  if (!std::is_same<Context, CPUContext>::value) {\n    prefetched_image_on_device_.CopyFrom(prefetched_image_, &context_);\n    prefetched_label_on_device_.CopyFrom(prefetched_label_, &context_);\n\n    for (int i = 0; i < prefetched_additional_outputs_on_device_.size(); ++i) {\n      prefetched_additional_outputs_on_device_[i].CopyFrom(\n          prefetched_additional_outputs_[i], &context_);\n    }\n  }\n  return true;\n}\n\ntemplate <class Context>\nbool ImageInputOp<Context>::CopyPrefetched() {\n  auto* image_output = OperatorBase::Output<Tensor<Context> >(0);\n  auto* label_output = OperatorBase::Output<Tensor<Context> >(1);\n  vector<Tensor<Context>*> additional_outputs_output;\n\n  for (int i = 2; i < OutputSize(); ++i) {\n    additional_outputs_output.push_back(\n        OperatorBase::Output<Tensor<Context>>(i));\n  }\n\n  // Note(jiayq): The if statement below should be optimized away by the\n  // compiler since std::is_same is a constexpr.\n  if (std::is_same<Context, CPUContext>::value) {\n    image_output->CopyFrom(prefetched_image_, &context_);\n    label_output->CopyFrom(prefetched_label_, &context_);\n\n    for (int i = 0; i < additional_outputs_output.size(); ++i) {\n      additional_outputs_output[i]->CopyFrom(\n          prefetched_additional_outputs_[i], &context_);\n    }\n  } else {\n    // TODO: support color jitter and color lighting in gpu_transform\n    if (gpu_transform_) {\n      if (!mean_std_copied_) {\n        mean_gpu_.Resize(mean_.size());\n        std_gpu_.Resize(std_.size());\n\n        context_.template Copy<float, CPUContext, Context>(\n          mean_.size(), mean_.data(), mean_gpu_.template mutable_data<float>());\n        context_.template Copy<float, CPUContext, Context>(\n          std_.size(), std_.data(), std_gpu_.template mutable_data<float>());\n        mean_std_copied_ = true;\n      }\n      // GPU transform kernel allows explicitly setting output type\n      if (output_type_ == TensorProto_DataType_FLOAT) {\n        TransformOnGPU<uint8_t,float,Context>(prefetched_image_on_device_,\n                                              image_output, mean_gpu_,\n                                              std_gpu_, &context_);\n      } else if (output_type_ == TensorProto_DataType_FLOAT16) {\n        TransformOnGPU<uint8_t,float16,Context>(prefetched_image_on_device_,\n                                                image_output, mean_gpu_,\n                                                std_gpu_, &context_);\n      }  else {\n        return false;\n      }\n    } else {\n      image_output->CopyFrom(prefetched_image_on_device_, &context_);\n    }\n    label_output->CopyFrom(prefetched_label_on_device_, &context_);\n\n    for (int i = 0; i < additional_outputs_output.size(); ++i) {\n      additional_outputs_output[i]->CopyFrom(\n          prefetched_additional_outputs_on_device_[i], &context_);\n    }\n  }\n  return true;\n}\n}  // namespace caffe2\n\n#endif  // CAFFE2_IMAGE_IMAGE_INPUT_OP_H_\n"
  },
  {
    "path": "caffe2/image/image_input_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/image/image_input_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(ImageInput, ImageInputOp<CUDAContext>);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/image/transform_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/image/transform_gpu.h\"\n#include \"caffe2/utils/conversions.h\"\n\n/**\n *\n * Copyright (c) 2016, NVIDIA CORPORATION, All rights reserved\n * Distributed under 2-clause BSD license; see accompanying LICENSE file\n *\n **/\n\nnamespace caffe2 {\n\nnamespace {\n\n// input in (int8, NHWC), output in (fp32, NCHW)\ntemplate <typename In, typename Out>\n__global__ void transform_kernel(\n    const int N,\n    const int C,\n    const int H,\n    const int W,\n    const float* mean,\n    const float* std,\n    const In* in,\n    Out* out) {\n  const int n = blockIdx.x;\n\n  const int nStride = C*H*W;\n\n  // pointers to data for this image\n  const In* input_ptr = &in[n*nStride];\n  Out* output_ptr = &out[n*nStride];\n\n  // either read or write uncoalesced - try reading\n  for (int c=0; c < C; ++c) {\n    for (int h=threadIdx.y; h < H; h += blockDim.y) {\n      for (int w=threadIdx.x; w < W; w += blockDim.x) {\n        int in_idx = c + C*w + C*W*h;  // HWC\n        int out_idx = c*H*W + h*W + w;  // CHW\n\n        output_ptr[out_idx] = convert::To<float,Out>(\n          (convert::To<In,float>(input_ptr[in_idx])-mean[c]) * std[c]);\n      }\n    }\n  }\n}\n\n}\n\ntemplate <typename T_IN, typename T_OUT, class Context>\n\nbool TransformOnGPU(Tensor<Context>& X, Tensor<Context> *Y,\n                    Tensor<Context>& mean, Tensor<Context>& std,\n                    Context *context) {\n  // data comes in as NHWC\n  const int N = X.dim32(0), C = X.dim32(3), H = X.dim32(1), W = X.dim32(2);\n  // data goes out as NCHW\n  Y->Resize(std::vector<int>{N,C,H,W});\n\n  auto* input_data = X.template data<T_IN>();\n  auto* output_data = Y->template mutable_data<T_OUT>();\n\n  transform_kernel<\n    T_IN, T_OUT><<<N, dim3(16, 16), 0, context->cuda_stream()>>>(\n      N, C, H, W, mean.template data<float>(), std.template data<float>(),\n      input_data, output_data);\n  return true;\n};\n\ntemplate bool TransformOnGPU<uint8_t, float, CUDAContext>(Tensor<CUDAContext>& X,\n                                                          Tensor<CUDAContext> *Y,\n                                                          Tensor<CUDAContext>& mean,\n                                                          Tensor<CUDAContext>& std,\n                                                          CUDAContext *context);\n\ntemplate bool TransformOnGPU<uint8_t, float16, CUDAContext>(Tensor<CUDAContext>& X,\n                                                            Tensor<CUDAContext> *Y,\n                                                            Tensor<CUDAContext>& mean,\n                                                            Tensor<CUDAContext>& std,\n                                                            CUDAContext *context);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/image/transform_gpu.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_IMAGE_TRANSFORM_GPU_H_\n#define CAFFE2_IMAGE_TRANSFORM_GPU_H_\n\n/**\n *\n * Copyright (c) 2016, NVIDIA CORPORATION, All rights reserved\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\n * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n **/\n\n#include \"caffe2/core/context.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T_IN, typename T_OUT, class Context>\nbool TransformOnGPU(Tensor<Context>& X, Tensor<Context>* Y,\n                    Tensor<Context>& mean, Tensor<Context>& std,\n                    Context* context);\n\n}  // namespace caffe2\n\n#endif\n"
  },
  {
    "path": "caffe2/mkl/CMakeLists.txt",
    "content": "if(BLAS STREQUAL \"MKL\")\n  message(STATUS \"Including MKL operators\")\n\n  # ---[ CPU files.\n  file(GLOB_RECURSE tmp *.cc)\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp})\n  # exclude test files and gpu files\n  file(GLOB_RECURSE tmp *_test.cc)\n  exclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${tmp})\n\n  # ---[ CPU test files - currently none but just to be safe\n  file(GLOB_RECURSE tmp *_test.cc)\n  set(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${tmp})\n\n  # ---[ Send the lists to the parent scope.\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\n  set(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nelse()\n  message(STATUS \"Excluding mkl operators as we are not using mkl\")\nendif()\n"
  },
  {
    "path": "caffe2/mkl/mkl_operator.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nCAFFE2_DEFINE_bool(\n    caffe2_mkl_memonger_in_use,\n    false,\n    \"Turn on if memonger is used to force reallocate intermediate \"\n    \"and output buffers within each op\");\n\nnamespace caffe2 {\n\nCAFFE_DEFINE_REGISTRY(\n    MKLOperatorRegistry,\n    OperatorBase,\n    const OperatorDef&,\n    Workspace*);\nCAFFE_REGISTER_DEVICE_TYPE(DeviceType::MKLDNN, MKLOperatorRegistry);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mkl/mkl_utils.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_MKL_UTILS_H_\n#define CAFFE2_UTILS_MKL_UTILS_H_\n\n#include \"caffe2/core/macros.h\"  // For caffe2 macros.\n\n#ifdef CAFFE2_USE_MKL\n\n#include \"caffe2/mkl/utils/mkl_version_check.h\"\n\n// MKLDNN_CHECK should be used in places where exceptions should not be thrown,\n// such as in destructors.\n#define MKLDNN_CHECK(condition)   \\\n  do {                            \\\n    dnnError_t error = condition; \\\n    CAFFE_ENFORCE_EQ(             \\\n        error,                    \\\n        E_SUCCESS,                \\\n        \"Error at : \",            \\\n        __FILE__,                 \\\n        \":\",                      \\\n        __LINE__,                 \\\n        \", error number: \",       \\\n        error);                   \\\n  } while (0)\n\n#define MKLDNN_SAFE_CALL(condition) \\\n  do {                              \\\n    dnnError_t error = condition;   \\\n    CAFFE_ENFORCE_EQ(               \\\n        error,                      \\\n        E_SUCCESS,                  \\\n        \"Error at : \",              \\\n        __FILE__,                   \\\n        \":\",                        \\\n        __LINE__,                   \\\n        \", error number: \",         \\\n        error);                     \\\n  } while (0)\n\n#define CHECK_INPUT_FILTER_DIMS(X, filter, condition) \\\n  do {                                                \\\n    if (cached_input_dims_ != X.dims() ||             \\\n        cached_filter_dims_ != filter.dims()) {       \\\n      cached_input_dims_ = X.dims();                  \\\n      cached_filter_dims_ = filter.dims();            \\\n      condition = true;                               \\\n    } else {                                          \\\n      condition = false;                              \\\n    }                                                 \\\n  } while (0)\n\n#define CHECK_INPUT_DIMS(X, condition)    \\\n  do {                                    \\\n    if (cached_input_dims_ != X.dims()) { \\\n      cached_input_dims_ = X.dims();      \\\n      condition = true;                   \\\n    } else {                              \\\n      condition = false;                  \\\n    }                                     \\\n  } while (0)\n\n// All caffe2 mkl related headers\n\n#ifdef CAFFE2_HAS_MKL_DNN\n#include \"caffe2/mkl/utils/mkl_context.h\"\n#include \"caffe2/mkl/utils/mkl_dnn_cppwrapper.h\"\n#include \"caffe2/mkl/utils/mkl_memory.h\"\n#include \"caffe2/mkl/utils/mkl_operator.h\"\n#endif // CAFFE2_HAS_MKL_DNN\n\n#ifdef CAFFE2_HAS_MKL_SGEMM_PACK\n#include \"caffe2/mkl/utils/sgemm_pack.h\"\n#endif // CAFFE2_HAS_MKL_SGEMM_PACK\n\n#endif // CAFFE2_USE_MKL\n#endif // CAFFE2_UTILS_MKL_UTILS_H_\n"
  },
  {
    "path": "caffe2/mkl/mkl_utils_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/mkl/mkl_utils.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/math.h\"\n\n#include <gtest/gtest.h>\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\nnamespace mkl {\n\nTEST(MKLDNNTest, SimpleConvolutionTest) {\n  // In order to create an internal layout, let's use convolution as primitive.\n  size_t dimension = 4;\n  size_t bdata_sizes[4] = {32, 32, 8, 16};\n  size_t bdata_offsets[4] = {1, 32, 32 * 32, 32 * 32 * 8};\n  size_t tdata_sizes[4] = {30, 30, 64, 16};\n  size_t fdata_sizes[4] = {3, 3, 8, 64};\n  size_t strides[2] = {1, 1};\n  int pads[2] = {0, 0};\n\n  // Creating Input and output tensors\n  TensorCPU X(vector<TIndex>{16, 8, 32, 32});\n  TensorCPU W(vector<TIndex>{64, 8, 3, 3});\n  TensorCPU b(vector<TIndex>{64});\n  TensorCPU Y(vector<TIndex>{16, 64, 30, 30});\n\n  float* data = X.mutable_data<float>();\n  for (int i = 0; i < X.size(); ++i) {\n    data[i] = 1;\n  }\n  data = W.mutable_data<float>();\n  for (int i = 0; i < W.size(); ++i) {\n    data[i] = 1;\n  }\n  data = b.mutable_data<float>();\n  for (int i = 0; i < b.size(); ++i) {\n    data[i] = 0.1;\n  }\n\n  PrimitiveWrapper<float> primitive(\n      dnnConvolutionCreateForwardBias<float>,\n      nullptr,\n      dnnAlgorithmConvolutionDirect,\n      dimension,\n      bdata_sizes,\n      tdata_sizes,\n      fdata_sizes,\n      strides,\n      pads,\n      dnnBorderZeros);\n\n  // Test if the resource wrapper works.\n  MKLMemory<float> X_wrapper(X.dims(), primitive, dnnResourceSrc);\n  X_wrapper.CopyFrom(X);\n  TensorCPU X_recover(X.dims());\n  X_wrapper.CopyTo(&X_recover);\n  const float* recover_data = X_recover.data<float>();\n  for (int i = 0; i < X_recover.size(); ++i) {\n    EXPECT_EQ(recover_data[i], 1);\n  }\n\n  // Create W, b and Y wrappers, and run the convolution algorithm.\n  MKLMemory<float> W_wrapper(W.dims(), primitive, dnnResourceFilter);\n  W_wrapper.CopyFrom(W);\n  MKLMemory<float> b_wrapper(b.dims(), primitive, dnnResourceBias);\n  b_wrapper.CopyFrom(b);\n  MKLMemory<float> Y_wrapper(Y.dims(), primitive, dnnResourceDst);\n\n  void* resources[dnnResourceNumber] = {\n      X_wrapper.buffer(),\n      Y_wrapper.buffer(),\n      W_wrapper.buffer(),\n      b_wrapper.buffer(),\n  };\n\n  MKLDNN_SAFE_CALL(dnnExecute<float>(primitive, resources));\n  Y_wrapper.CopyTo(&Y);\n  const float* out_data = Y.data<float>();\n  for (int i = 0; i < Y.size(); ++i) {\n    EXPECT_NEAR(out_data[i], 72.1, 1e-5);\n  }\n}\n\nTEST(MKLDNNTest, MKLMemoryCopyTest) {\n  // Test copy with valid and empty shapes.\n  // MKL calls fail at different points for dims {0} and dims {0,N} despite\n  // the buffer size being empty for both - former in dnnAllocateBuffer and\n  // the latter in dnnConversionExecute (likely due to some difference in\n  // layout?). Test both cases.\n  vector<vector<TIndex>> dims_list{{10, 3, 20, 20}, {0}, {0, 10}};\n  for (const auto& dims : dims_list) {\n    auto X_cpu_in = caffe2::make_unique<TensorCPU>(dims);\n    CPUContext ctx;\n    math::RandUniform<float, CPUContext>(\n        X_cpu_in->size(),\n        -1.0,\n        1.0,\n        X_cpu_in->template mutable_data<float>(),\n        &ctx);\n\n    // CPU -> MKL1\n    auto X_mkl1 = caffe2::make_unique<MKLMemory<float>>(dims);\n    X_mkl1->CopyFrom(*X_cpu_in);\n\n    // MK1 -> MKL2\n    auto X_mkl2 = caffe2::make_unique<MKLMemory<float>>(dims);\n    X_mkl2->CopyFrom(*X_mkl1);\n\n    // MKL1 <- MKL2\n    X_mkl1 = caffe2::make_unique<MKLMemory<float>>();\n    X_mkl2->CopyTo(X_mkl1.get());\n    EXPECT_EQ(X_mkl1->dims(), dims);\n    EXPECT_EQ(X_mkl1->size(), X_cpu_in->size());\n\n    // CPU <- MKL1\n    auto X_cpu_out = caffe2::make_unique<TensorCPU>();\n    X_mkl1->CopyTo(X_cpu_out.get());\n    EXPECT_EQ(X_cpu_out->dims(), dims);\n    EXPECT_EQ(X_cpu_out->size(), X_cpu_in->size());\n    for (int i = 0; i < X_cpu_in->size(); ++i) {\n      EXPECT_NEAR(\n          X_cpu_in->data<float>()[i], X_cpu_out->data<float>()[i], 1e-5);\n    }\n  }\n}\n\n} // namespace mkl\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/mklmemory_serialization.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/mkl/mkl_utils.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\nnamespace mkl {\n/**\n * @brief MKLMemorySerializer is the serializer for MKLMemory.\n *\n * MKLMemorySerializer takes in a blob that contains an MKLMemory, and\n * serializes it into a TensorProto protocol buffer.\n */\nclass MKLMemorySerializer : public BlobSerializerBase {\n public:\n  MKLMemorySerializer() {}\n  ~MKLMemorySerializer() {}\n\n  void Serialize(\n      const Blob& blob,\n      const string& name,\n      SerializationAcceptor acceptor) override {\n    BlobProto blob_proto;\n    blob_proto.set_name(name);\n    blob_proto.set_type(kTensorBlobType);\n    TensorProto* proto = blob_proto.mutable_tensor();\n    auto* device_detail = proto->mutable_device_detail();\n    device_detail->set_device_type(MKLDNN);\n    proto->set_name(name);\n    if (blob.IsType<MKLMemory<float>>()) {\n      const MKLMemory<float>& src = blob.Get<MKLMemory<float>>();\n      CAFFE_ENFORCE(\n          src.buffer(), \"Cannot serialize an empty MKLMemory object.\");\n      size_t total = 1;\n      for (int i = 0; i < src.dims().size(); ++i) {\n        proto->add_dims(src.dims()[i]);\n        total *= src.dims()[i];\n      }\n      proto->mutable_float_data()->Reserve(total);\n      while (total--) {\n        proto->add_float_data(0);\n      }\n      src.CopyTo(proto->mutable_float_data()->mutable_data());\n    } else if (blob.IsType<MKLMemory<double>>()) {\n      const MKLMemory<double>& src = blob.Get<MKLMemory<double>>();\n      CAFFE_ENFORCE(\n          src.buffer(), \"Cannot serialize an empty MKLMemory object.\");\n      size_t total = 1;\n      for (int i = 0; i < src.dims().size(); ++i) {\n        proto->add_dims(src.dims()[i]);\n        total *= src.dims()[i];\n      }\n      proto->mutable_double_data()->Reserve(total);\n      while (total--) {\n        proto->add_double_data(0);\n      }\n      src.CopyTo(proto->mutable_double_data()->mutable_data());\n    } else {\n      CAFFE_THROW(\n          \"MKLMemory could only be either float or double. \"\n          \"Encountered unsupported type.\");\n    }\n    acceptor(name, blob_proto.SerializeAsString());\n  }\n};\n\n/**\n * @brief MKLMemoryDeserializer is the deserializer for TensorProto that has\n * MKLDNN as its device.\n *\n * The device that the deserialized Tensor will live under is determined by the\n * device_detail field. If you want to specify the device of the deserialized\n * tensor, change the TensorProto's corresponding fields before calling\n * Deserialize.\n */\nclass MKLMemoryDeserializer : public BlobDeserializerBase {\n public:\n  void Deserialize(const BlobProto& blob_proto, Blob* blob) override {\n    const TensorProto& proto = blob_proto.tensor();\n    CAFFE_ENFORCE(\n        proto.data_type() == TensorProto_DataType_FLOAT ||\n            proto.data_type() == TensorProto_DataType_DOUBLE,\n        \"MKLMemory only supports either float or double formats.\");\n    CAFFE_ENFORCE(\n        !proto.has_segment(), \"MKLMemory does not support segment right now.\");\n    vector<TIndex> dims;\n    for (const TIndex d : proto.dims()) {\n      dims.push_back(d);\n    }\n    // TODO: right now, every time we do a deserializer we create a new MKL\n    // Memory object. Optionally, we can change that.\n    switch (proto.data_type()) {\n      case TensorProto_DataType_FLOAT: {\n        auto dst = make_unique<MKLMemory<float>>(dims);\n        dst->CopyFrom(proto.float_data().data());\n        blob->Reset(dst.release());\n        break;\n      }\n      case TensorProto_DataType_DOUBLE: {\n        auto dst = make_unique<MKLMemory<double>>(dims);\n        dst->CopyFrom(proto.double_data().data());\n        blob->Reset(dst.release());\n        break;\n      }\n      default:\n        CAFFE_THROW(\"This should not happen, we guarded things above already.\");\n    }\n  }\n};\n\n} // namespace mkl\n\nREGISTER_BLOB_SERIALIZER(\n    (TypeMeta::Id<mkl::MKLMemory<float>>()),\n    mkl::MKLMemorySerializer);\nREGISTER_BLOB_SERIALIZER(\n    (TypeMeta::Id<mkl::MKLMemory<double>>()),\n    mkl::MKLMemorySerializer);\nREGISTER_BLOB_DESERIALIZER(TensorMKLDNN, mkl::MKLMemoryDeserializer);\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/mklmemory_serialization_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/common.h\"\n#include \"caffe2/mkl/mkl_utils.h\"\n\n#include <gtest/gtest.h>\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\n\nusing mkl::MKLMemory;\n\nTEST(MKLTest, MKLMemorySerialization) {\n  Blob blob;\n  vector<int> shape{2, 3, 4};\n  float data[2 * 3 * 4];\n  for (int i = 0; i < 2 * 3 * 4; ++i) {\n    data[i] = i;\n  }\n  blob.Reset<MKLMemory<float>>(new MKLMemory<float>(shape));\n  MKLMemory<float>* mkl_memory = blob.GetMutable<MKLMemory<float>>();\n  mkl_memory->CopyFrom(data);\n  string serialized = blob.Serialize(\"test\");\n  BlobProto proto;\n  CHECK(proto.ParseFromString(serialized));\n  EXPECT_EQ(proto.name(), \"test\");\n  EXPECT_EQ(proto.type(), \"Tensor\");\n  EXPECT_TRUE(proto.has_tensor());\n  const TensorProto& tensor_proto = proto.tensor();\n  EXPECT_EQ(\n      tensor_proto.data_type(), TypeMetaToDataType(TypeMeta::Make<float>()));\n  EXPECT_EQ(tensor_proto.float_data_size(), 2 * 3 * 4);\n  for (int i = 0; i < 2 * 3 * 4; ++i) {\n    EXPECT_EQ(tensor_proto.float_data(i), static_cast<float>(i));\n  }\n  Blob new_blob;\n  EXPECT_NO_THROW(new_blob.Deserialize(serialized));\n  EXPECT_TRUE(new_blob.IsType<MKLMemory<float>>());\n  const auto& new_mkl_memory = blob.Get<MKLMemory<float>>();\n  EXPECT_EQ(new_mkl_memory.dims().size(), 3);\n  EXPECT_EQ(new_mkl_memory.dims()[0], 2);\n  EXPECT_EQ(new_mkl_memory.dims()[1], 3);\n  EXPECT_EQ(new_mkl_memory.dims()[2], 4);\n  float recovered_data[2 * 3 * 4];\n  new_mkl_memory.CopyTo(recovered_data);\n  for (int i = 0; i < 2 * 3 * 4; ++i) {\n    EXPECT_EQ(recovered_data[i], i);\n  }\n}\n\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/operators/concat_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/mkl/mkl_utils.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\nnamespace mkl {\n\ntemplate <typename T>\nclass MKLConcatOp final : public MKLOperator<T> {\n public:\n  USE_MKLOPERATOR_FUNCTIONS(T);\n\n  MKLConcatOp(const OperatorDef& operator_def, Workspace* ws)\n      : MKLOperator<T>(operator_def, ws) {\n    CAFFE_ENFORCE(\n        !(OperatorBase::HasArgument(\"axis\") &&\n          OperatorBase::HasArgument(\"order\")),\n        \"You shouldn't specify both the dim to concat, and the order \"\n        \"in the case of 4-D images.\");\n\n    int add_axis;\n    if (OperatorBase::HasArgument(\"axis\")) {\n      axis_ = OperatorBase::GetSingleArgument<int>(\"axis\", -1);\n      add_axis = OperatorBase::GetSingleArgument<int>(\"add_axis\", 0);\n    } else {\n      const auto& order = StringToStorageOrder(\n          OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"));\n      OPERATOR_NEEDS_FEATURE(\n          order == StorageOrder::NCHW, \"Only NCHW order supported.\");\n      axis_ = 1;\n      add_axis = 0;\n    }\n\n    OPERATOR_NEEDS_FEATURE(\n        axis_ == 1 || axis_ == -3, \"Only channel concatenation is supported.\");\n    OPERATOR_NEEDS_FEATURE(add_axis == 0, \"Adding axis is not supported.\");\n  }\n\n  bool RunOnDevice() override {\n    const auto& X0 = Input(0);\n    auto* Y = Output(0);\n    int nInputs = InputSize();\n    int nDims = X0.ndim();\n    CAFFE_ENFORCE_EQ(nDims, 4, \"Only NCHW inputs are supported\");\n\n    bool dims_changed = (input_size_cache_.size() != nInputs);\n    for (int i = 0; i < nInputs && !dims_changed; ++i) {\n      dims_changed = (input_size_cache_[i] != Input(i).dims());\n    }\n\n    if (dims_changed || FLAGS_caffe2_mkl_memonger_in_use) {\n      input_size_cache_.resize(nInputs);\n      int output_channels = 0;\n      int canonical_axis = canonical_axis_index_(axis_, nDims);\n      vector<dnnLayout_t> input_layouts(nInputs);\n      for (int i = 0; i < nInputs; ++i) {\n        const auto& Xi = Input(i);\n        CAFFE_ENFORCE_EQ(Xi.ndim(), nDims, \"Input \", i, \" has wrong ndim.\");\n        for (int j = 0; j < nDims; ++j) {\n          if (j == canonical_axis) {\n            continue;\n          }\n          CAFFE_ENFORCE_EQ(\n              Xi.dim32(j),\n              X0.dim32(j),\n              \"Input \",\n              i,\n              \" has dimension mismatch at axis \",\n              j);\n        }\n        input_size_cache_[i] = Xi.dims();\n        output_channels += Xi.dim32(canonical_axis);\n        input_layouts[i] = Xi.layout();\n      }\n      cached_output_dims_ = X0.dims();\n      cached_output_dims_[canonical_axis] = output_channels;\n\n      primitive_.Reset(\n          dnnConcatCreate<T>, nullptr, nInputs, input_layouts.data());\n      Y->Reset(cached_output_dims_, primitive_, dnnResourceDst);\n      buffer_.Reset(cached_output_dims_, primitive_, dnnResourceDst, true);\n    }\n    bool shared = buffer_.ShareFrom(*Y);\n\n    for (int i = 0; i < nInputs; ++i) {\n      resources_[dnnResourceMultipleSrc + i] = Input(i).buffer();\n    }\n    resources_[dnnResourceDst] = buffer_.buffer();\n    ExecutePrimitive();\n    buffer_.CopyTo(Y, primitive_, dnnResourceDst);\n    if (FLAGS_caffe2_mkl_memonger_in_use && !shared) {\n      buffer_.Reset();\n    }\n    return true;\n  }\n\n private:\n  int axis_;\n  vector<TIndex> cached_output_dims_;\n};\n\n} // namespace mkl\n\nREGISTER_MKL_OPERATOR(Concat, mkl::MKLConcatOp<float>);\n\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/operators/conv_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/mkl/mkl_utils.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\nnamespace mkl {\n\ntemplate <typename T>\nclass MKLConvOp final : public ConvPoolOpBase<MKLContext> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(MKLContext);\n  MKLConvOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<MKLContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(\n        dilation_h() == 1 && dilation_w() == 1, \"Dilation not supported.\");\n    OPERATOR_NEEDS_FEATURE(\n        pad_l() == pad_r() && pad_t() == pad_b(),\n        \"Uneven padding not supported.\");\n    OPERATOR_NEEDS_FEATURE(\n        order_ == StorageOrder::NCHW, \"Only NCHW order supported.\");\n  }\n  ~MKLConvOp() {}\n\n  // TODO(jiayq): support double if needed.\n  bool RunOnDeviceWithOrderNCHW() override {\n    const auto& X = OperatorBase::Input<MKLMemory<float>>(INPUT);\n    const auto& filter = OperatorBase::Input<MKLMemory<float>>(FILTER);\n\n    const int M = filter.dim32(0);\n    if (InputSize() == 2 && !zero_bias_) {\n      TensorCPU cpu_zero_bias;\n      cpu_zero_bias.Resize(M);\n      CPUContext ctx;\n      math::Set<T, CPUContext>(\n          M, 0.0, cpu_zero_bias.template mutable_data<float>(), &ctx);\n\n      zero_bias_.reset(new MKLMemory<T>(std::vector<TIndex>{M}));\n      zero_bias_->CopyFrom(cpu_zero_bias);\n    }\n    const auto& bias = InputSize() == 2\n        ? *zero_bias_\n        : OperatorBase::Input<MKLMemory<float>>(BIAS);\n\n    MKLMemory<float>* Y = OperatorBase::Output<MKLMemory<float>>(0);\n    CAFFE_ENFORCE(4 == X.ndim());\n    const int N = X.dim32(0), C = X.dim32(1), H = X.dim32(2), W = X.dim32(3);\n    CAFFE_ENFORCE(4 == filter.ndim());\n\n    bool dims_changed;\n    CHECK_INPUT_FILTER_DIMS(X, filter, dims_changed);\n    if (dims_changed || FLAGS_caffe2_mkl_memonger_in_use) {\n      CAFFE_ENFORCE(\n          C == filter.dim32(1) * group_,\n          \"Convolution op: input channels does not match: # of input channels \",\n          C,\n          \" is not equal to kernel channels * group:\",\n          filter.dim32(1),\n          \"*\",\n          group_);\n      CAFFE_ENFORCE(\n          M % group_ == 0,\n          \"The number of output channels is not divisible by group.\");\n      CAFFE_ENFORCE(filter.dim32(2) == kernel_h());\n      CAFFE_ENFORCE(filter.dim32(3) == kernel_w());\n      CAFFE_ENFORCE(bias.ndim() == 1);\n      CAFFE_ENFORCE(bias.dim32(0) == M);\n\n      size_t dimension = 4;\n      size_t bdata_sizes[4] = {W, H, C, N};\n      // We will utilize the SetOutputSize() function int he base class\n      // with dummy TensorCPU input and output to calculate the sizes.\n      TensorCPU dummy_input(X.dims());\n      TensorCPU dummy_output;\n      ConvPoolOpBase<MKLContext>::SetOutputSize(\n          dummy_input, &dummy_output, M);\n      size_t tdata_sizes[4] = {\n          dummy_output.dim(3), dummy_output.dim(2),\n          dummy_output.dim(1), dummy_output.dim(0)};\n      size_t fdata_sizes[5] = {\n          kernel_w(), kernel_h(), C / group_, M / group_, group_};\n      size_t strides[2] = {stride_w(), stride_h()};\n      int pads[2] = {-pad_l(), -pad_t()};\n\n      if (group_ > 1) {\n        primitive_.Reset(\n            dnnGroupsConvolutionCreateForwardBias<float>,\n            nullptr,\n            dnnAlgorithmConvolutionDirect,\n            group_,\n            dimension,\n            bdata_sizes,\n            tdata_sizes,\n            fdata_sizes,\n            strides,\n            pads,\n            dnnBorderZeros);\n      } else {\n        primitive_.Reset(\n            dnnConvolutionCreateForwardBias<float>,\n            nullptr,\n            dnnAlgorithmConvolutionDirect,\n            dimension,\n            bdata_sizes,\n            tdata_sizes,\n            fdata_sizes,\n            strides,\n            pads,\n            dnnBorderZeros);\n      }\n      Y->Reset(dummy_output.dims(), primitive_, dnnResourceDst);\n      buffer_.Reset(dummy_output.dims(), primitive_, dnnResourceDst, true);\n      input_layout_.Reset(primitive_, dnnResourceSrc);\n      filter_layout_.Reset(primitive_, dnnResourceFilter);\n      bias_layout_.Reset(primitive_, dnnResourceBias);\n    }\n\n    // Try to share from the output: this allows us to avoid unnecessary copy\n    // operations, if the output is already allocated and is having the same\n    // layout as the buffer has.\n    bool shared = buffer_.ShareFrom(*Y);\n\n    std::shared_ptr<void> X_view = X.View(\n        input_layout_, primitive_, dnnResourceSrc);\n    std::shared_ptr<void> bias_view =\n        bias.View(bias_layout_, primitive_, dnnResourceBias);\n    std::shared_ptr<void> filter_view;\n    if (group_ > 1) {\n      // Explicitly reformat the buffer.\n      MKLMemory<float> group_filter(\n          std::vector<TIndex>{TIndex(group_),\n                              TIndex(filter.dim32(0) / group_),\n                              TIndex(filter.dim32(1)),\n                              TIndex(filter.dim32(2)),\n                              TIndex(filter.dim32(3))},\n          nullptr,\n          dnnResourceFilter,\n          /*share_memory_if_possible=*/true);\n      group_filter.CopyFrom(filter.buffer());\n      filter_view =\n          group_filter.View(filter_layout_, primitive_, dnnResourceFilter);\n    } else {\n      filter_view = filter.View(filter_layout_, primitive_, dnnResourceFilter);\n    }\n\n    resources_[dnnResourceSrc] = X_view.get(); // X.buffer();\n    resources_[dnnResourceFilter] = filter_view.get();\n    resources_[dnnResourceBias] = bias_view.get();\n    resources_[dnnResourceDst] = buffer_.buffer();\n\n    MKLDNN_SAFE_CALL(mkl::dnnExecute<T>(primitive_, resources_));\n    buffer_.CopyTo(Y, primitive_, dnnResourceDst);\n    if (FLAGS_caffe2_mkl_memonger_in_use && !shared) {\n      // buffer_ is not shared with Y. Free memory since it'll\n      // be re-allocated in the next run anyway due to memonger in use.\n      buffer_.Reset();\n    }\n    return true;\n  }\n\n  bool RunOnDeviceWithOrderNHWC() override {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n private:\n  // Input: X, W, b\n  // Output: Y\n  std::unique_ptr<MKLMemory<T>> zero_bias_;\n  vector<TIndex> cached_input_dims_;\n  vector<TIndex> cached_filter_dims_;\n  PrimitiveWrapper<T> primitive_;\n  LayoutWrapper<T> input_layout_;\n  LayoutWrapper<T> filter_layout_;\n  LayoutWrapper<T> bias_layout_;\n  MKLMemory<T> buffer_;\n  void* resources_[dnnResourceNumber] = {0};\n  INPUT_TAGS(INPUT, FILTER, BIAS);\n};\n\n} // namespace mkl\n\n\nREGISTER_MKL_OPERATOR(Conv, mkl::MKLConvOp<float>);\n\n}  // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/operators/conv_op_mkldnn.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/mkl/mkl_utils.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\nnamespace mkl {\n\ntemplate <typename T>\nclass ConvMKLDNNOp final : public ConvPoolOpBase<CPUContext> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(CPUContext);\n  ConvMKLDNNOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(\n        dilation_h() == 1 && dilation_w() == 1, \"Dilation not supported.\");\n    OPERATOR_NEEDS_FEATURE(\n        pad_l() == pad_r() && pad_t() == pad_b(),\n        \"Uneven padding not supported.\");\n    OPERATOR_NEEDS_FEATURE(\n        order_ == StorageOrder::NCHW, \"Only NCHW order supported.\");\n  }\n  ~ConvMKLDNNOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    auto& X = Input(INPUT);\n    auto& filter = Input(FILTER);\n    auto& bias = Input(BIAS);\n    TensorCPU* Y = Output(0);\n    CAFFE_ENFORCE(4 == X.ndim());\n    const int N = X.dim32(0), C = X.dim32(1), H = X.dim32(2), W = X.dim32(3);\n    CAFFE_ENFORCE(4 == filter.ndim());\n    const int M = filter.dim32(0);\n    CAFFE_ENFORCE(\n        C == filter.dim32(1),\n        \"Convolution op: # of input channels \",\n        C,\n        \" is not equal to kernel channels:\",\n        filter.dim32(1));\n    CAFFE_ENFORCE(filter.dim32(2) == kernel_h());\n    CAFFE_ENFORCE(filter.dim32(3) == kernel_w());\n    CAFFE_ENFORCE(bias.ndim() == 1);\n    CAFFE_ENFORCE(bias.dim32(0) == M);\n    ConvPoolOpBase<CPUContext>::SetOutputSize(X, Y, filter.dim32(0));\n    // Pre-allocate Y so we can potentially share memory if applicable.\n    Y->mutable_data<T>();\n\n    if (cached_input_dims_ != X.dims() ||\n        cached_filter_dims_ != filter.dims()) {\n      cached_input_dims_ = X.dims();\n      cached_filter_dims_ = filter.dims();\n      // In order to create an internal layout, let's use convolution as\n      // primitive.\n      size_t dimension = 4;\n      size_t bdata_sizes[4] = {W, H, C, N};\n      size_t bdata_offsets[4] = {1, W, W * H, W * H * C};\n      size_t tdata_sizes[4] = {Y->dim(3), Y->dim(2), Y->dim(1), Y->dim(0)};\n      size_t fdata_sizes[4] = {kernel_w(), kernel_h(), C, M};\n      size_t strides[2] = {stride_w(), stride_h()};\n      int pads[2] = {-pad_l(), -pad_t()};\n\n      primitive_.Reset(\n          dnnConvolutionCreateForwardBias<float>,\n          nullptr,\n          dnnAlgorithmConvolutionDirect,\n          dimension,\n          bdata_sizes,\n          tdata_sizes,\n          fdata_sizes,\n          strides,\n          pads,\n          dnnBorderZeros);\n      X_wrapper_.reset(\n          new MKLMemory<T>(X.dims(), primitive_, dnnResourceSrc, true));\n      filter_wrapper_.reset(\n          new MKLMemory<T>(filter.dims(), primitive_, dnnResourceFilter, true));\n      bias_wrapper_.reset(\n          new MKLMemory<T>(bias.dims(), primitive_, dnnResourceBias, true));\n      Y_wrapper_.reset(\n          new MKLMemory<T>(Y->dims(), primitive_, dnnResourceDst, true));\n      X_wrapper_->CopyFrom(X);\n      filter_wrapper_->CopyFrom(filter);\n      bias_wrapper_->CopyFrom(bias);\n      Y_wrapper_->ShareFromTensor(*Y);\n      resources_[dnnResourceSrc] = X_wrapper_->buffer();\n      resources_[dnnResourceFilter] = filter_wrapper_->buffer();\n      resources_[dnnResourceBias] = bias_wrapper_->buffer();\n      resources_[dnnResourceDst] = Y_wrapper_->buffer();\n    } else {\n      X_wrapper_->CopyFrom(X);\n      filter_wrapper_->CopyFrom(filter);\n      bias_wrapper_->CopyFrom(bias);\n      Y_wrapper_->ShareFromTensor(*Y);\n    }\n    MKLDNN_SAFE_CALL(dnnExecute<float>(primitive_, resources_));\n    Y_wrapper_->CopyTo(Y);\n    return true;\n  }\n\n  bool RunOnDeviceWithOrderNHWC() override {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n private:\n  // Input: X, W, b\n  // Output: Y\n  vector<TIndex> cached_input_dims_;\n  vector<TIndex> cached_filter_dims_;\n  PrimitiveWrapper<T> primitive_;\n  unique_ptr<MKLMemory<T>> X_wrapper_ = nullptr;\n  unique_ptr<MKLMemory<T>> filter_wrapper_ = nullptr;\n  unique_ptr<MKLMemory<T>> bias_wrapper_ = nullptr;\n  unique_ptr<MKLMemory<T>> Y_wrapper_ = nullptr;\n  void* resources_[dnnResourceNumber] = {0};\n  INPUT_TAGS(INPUT, FILTER, BIAS);\n};\n\n} // namespace mkl\n\nREGISTER_CPU_OPERATOR_WITH_ENGINE(Conv, MKLDNN, mkl::ConvMKLDNNOp<float>);\n\n}  // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/operators/elementwise_sum_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/mkl/mkl_utils.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\nnamespace mkl {\n\ntemplate <typename T>\nclass MKLSumOp final : public MKLOperator<T> {\n public:\n  USE_MKLOPERATOR_FUNCTIONS(T);\n\n  MKLSumOp(const OperatorDef& operator_def, Workspace* ws)\n      : MKLOperator<T>(operator_def, ws) {\n    coefficients_.resize(this->InputSize(), 1);\n    // caffe2::AddOp support broadcast but dnnSumCreate() doesn't.\n    bool broadcast = OperatorBase::GetSingleArgument<bool>(\"broadcast\", false);\n    OPERATOR_NEEDS_FEATURE(\n        !broadcast, \"Broadcast is not yet supported with MKLDNN.\");\n  }\n\n  bool RunOnDevice() override {\n    const MKLMemory<T>& X0 = Input(0);\n    MKLMemory<T>* Y = Output(0);\n    bool dims_changed;\n    CHECK_INPUT_DIMS(X0, dims_changed);\n    if (dims_changed || FLAGS_caffe2_mkl_memonger_in_use) {\n      primitive_.Reset(\n          dnnSumCreate<T>,\n          nullptr,\n          this->InputSize(),\n          X0.layout(),\n          coefficients_.data());\n      if (Y != &X0) {\n        Y->Reset(X0.dims(), primitive_, dnnResourceDst);\n      }\n      buffer_.Reset(X0.dims(), primitive_, dnnResourceDst, true);\n    }\n    input_views_.resize(this->InputSize());\n    for (auto i = 0; i < this->InputSize(); ++i) {\n      const MKLMemory<T>& Xi = Input(i);\n      CAFFE_ENFORCE_EQ(X0.dims(), Xi.dims());\n      // Input layouts might be different depending on preceding primitives.\n      // Create a consistent view as dnnSumCreate expects it that way.\n      input_views_[i] = Xi.View(X0.layout());\n      resources_[dnnResourceMultipleSrc + i] = input_views_[i].get();\n    }\n    bool shared = false;\n    if (Y != &X0) {\n      // TODO: MKLDNN seems broken in the in-place case, so when we specify\n      // in-place we will need to use buffer differnt from X0/Y.\n      shared = buffer_.ShareFrom(*Y);\n    }\n    resources_[dnnResourceDst] = buffer_.buffer();\n    MKLDNN_SAFE_CALL(mkl::dnnExecute<T>(primitive_, resources_));\n    buffer_.CopyTo(Y, primitive_, dnnResourceDst);\n    if (FLAGS_caffe2_mkl_memonger_in_use && !shared) {\n      buffer_.Reset();\n    }\n    return true;\n  }\n\n private:\n  std::vector<float> coefficients_;\n  vector<TIndex> cached_input_dims_;\n  vector<std::shared_ptr<void>> input_views_;\n};\n\n} // namespace mkl\n\nREGISTER_MKL_OPERATOR(Sum, mkl::MKLSumOp<float>);\nREGISTER_MKL_OPERATOR(Add, mkl::MKLSumOp<float>);\n\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/operators/fully_connected_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/mkl/mkl_utils.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\nnamespace mkl {\n\ntemplate <typename T>\nclass MKLFullyConnectedOp final : public MKLOperator<T> {\n public:\n  MKLFullyConnectedOp(const OperatorDef& operator_def, Workspace* ws)\n      : MKLOperator<T>(operator_def, ws),\n        axis_(OperatorBase::GetSingleArgument<int32_t>(\"axis\", 1)) {}\n  ~MKLFullyConnectedOp() {}\n\n  bool RunOnDevice() override {\n    auto& X = OperatorBase::Input<MKLMemory<float>>(INPUT);\n    auto& filter = OperatorBase::Input<MKLMemory<float>>(FILTER);\n    auto& bias = OperatorBase::Input<MKLMemory<float>>(BIAS);\n    MKLMemory<float>* Y = OperatorBase::Output<MKLMemory<float>>(0);\n\n    CAFFE_ENFORCE(filter.ndim() == 2, filter.ndim());\n    CAFFE_ENFORCE(bias.ndim() == 1, bias.ndim());\n\n    bool dims_changed;\n    CHECK_INPUT_FILTER_DIMS(X, filter, dims_changed);\n    if (dims_changed || FLAGS_caffe2_mkl_memonger_in_use) {\n      const int N = filter.dim32(0);\n      CAFFE_ENFORCE(N == bias.dim32(0));\n\n      auto Y_shape = X.dims();\n      Y_shape[1] = N;\n      Y_shape.resize(2);\n\n      size_t inputSizes[4];\n      if (X.ndim() == 2) {\n        inputSizes[0] = X.dim32(1);\n        inputSizes[1] = X.dim32(0);\n      } else {\n        CAFFE_ENFORCE(X.ndim(), 4);\n        inputSizes[0] = X.dim32(3);\n        inputSizes[1] = X.dim32(2);\n        inputSizes[2] = X.dim32(1);\n        inputSizes[3] = X.dim32(0);\n      }\n\n      size_t outputSizes[2] = {Y_shape[1], Y_shape[0]};\n\n      primitive_.Reset(\n          dnnInnerProductCreateForwardBias<float>,\n          nullptr,\n          X.ndim(),\n          inputSizes,\n          outputSizes[0]);\n\n      Y->Reset(Y_shape, primitive_, dnnResourceDst);\n      buffer_.Reset(Y_shape, primitive_, dnnResourceDst, true);\n\n      input_layout_.Reset(primitive_, dnnResourceSrc);\n      filter_layout_.Reset(primitive_, dnnResourceFilter);\n    }\n\n    // Try to share from the output: this allows us to avoid unnecessary copy\n    // operations, if the output is already allocated and is having the same\n    // layout as the buffer has.\n    bool shared = buffer_.ShareFrom(*Y);\n\n    std::shared_ptr<void> X_view =\n        X.View(input_layout_, primitive_, dnnResourceSrc);\n    std::shared_ptr<void> filter_view =\n        filter.View(filter_layout_, primitive_, dnnResourceFilter);\n\n    resources_[dnnResourceSrc] = X_view.get();\n    resources_[dnnResourceFilter] = filter_view.get();\n\n    resources_[dnnResourceBias] = bias.buffer();\n    resources_[dnnResourceDst] = buffer_.buffer();\n\n    MKLDNN_SAFE_CALL(mkl::dnnExecute<T>(primitive_, resources_));\n    buffer_.CopyTo(Y, primitive_, dnnResourceDst);\n    if (FLAGS_caffe2_mkl_memonger_in_use && !shared) {\n      buffer_.Reset();\n    }\n    return true;\n  }\n\n private:\n  // Input: X, W, b\n  // Output: Y\n  size_t axis_{1};\n  vector<TIndex> cached_input_dims_;\n  vector<TIndex> cached_filter_dims_;\n  PrimitiveWrapper<T> primitive_;\n  LayoutWrapper<T> input_layout_;\n  LayoutWrapper<T> filter_layout_;\n  LayoutWrapper<T> bias_layout_;\n  MKLMemory<T> buffer_;\n  void* resources_[dnnResourceNumber] = {0};\n  INPUT_TAGS(INPUT, FILTER, BIAS);\n};\n\n} // namespace mkl\n\nREGISTER_MKL_OPERATOR(FC, mkl::MKLFullyConnectedOp<float>);\n\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/operators/local_response_normalization_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/local_response_normalization_op.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\n#include \"caffe2/mkl/mkl_utils.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\nnamespace mkl {\n\ntemplate <typename T>\nclass MKLLRNOp final : public LRNOpBase<T, MKLContext> {\n public:\n  MKLLRNOp(const OperatorDef& operator_def, Workspace* ws)\n      : LRNOpBase<T, MKLContext>(operator_def, ws) {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n private:\n  vector<TIndex> cached_input_dims_;\n  LayoutWrapper<T> workspace_layout_;\n  std::unique_ptr<MKLWorkspace<T>> workspace_buffer_;\n  PrimitiveWrapper<T> primitive_;\n  MKLMemory<T> buffer_;\n  void* resources_[dnnResourceNumber] = {0};\n};\n\ntemplate <>\nbool MKLLRNOp<float>::RunOnDeviceWithOrderNCHW() {\n  auto& X = OperatorBase::Input<MKLMemory<float>>(0);\n  MKLMemory<float>* Y = OperatorBase::Output<MKLMemory<float>>(0);\n\n  bool dims_changed;\n  CHECK_INPUT_DIMS(X, dims_changed);\n  if (dims_changed || FLAGS_caffe2_mkl_memonger_in_use) {\n    size_t dim = X.ndim();\n    CAFFE_ENFORCE(4 == dim);\n\n    // Create main primitive.\n    primitive_.Reset(\n        dnnLRNCreateForward_F32,\n        nullptr,\n        X.layout(),\n        size_,\n        alpha_,\n        beta_,\n        bias_);\n\n    Y->Reset(X.dims(), primitive_, dnnResourceDst);\n    buffer_.Reset(X.dims(), primitive_, dnnResourceDst, true);\n\n    workspace_layout_.Reset(primitive_, dnnResourceWorkspace);\n    workspace_buffer_ =\n        caffe2::make_unique<MKLWorkspace<float>>(workspace_layout_);\n  }\n\n  // Try to share from the output: this allows us to avoid unnecessary copy\n  // operations, if the output is already allocated and is having the same\n  // layout as the buffer has.\n  bool shared = buffer_.ShareFrom(*Y);\n  resources_[dnnResourceSrc] = X.buffer();\n  resources_[dnnResourceDst] = buffer_.buffer();\n  resources_[dnnResourceWorkspace] = workspace_buffer_->buffer();\n  MKLDNN_SAFE_CALL(mkl::dnnExecute<float>(primitive_, resources_));\n  buffer_.CopyTo(Y, primitive_, dnnResourceDst);\n  if (FLAGS_caffe2_mkl_memonger_in_use && !shared) {\n    buffer_.Reset();\n  }\n  return true;\n}\n\ntemplate <>\nbool MKLLRNOp<float>::RunOnDeviceWithOrderNHWC() {\n  CAFFE_NOT_IMPLEMENTED;\n}\n\n} // namespace mkl\n\nREGISTER_MKL_OPERATOR(LRN, mkl::MKLLRNOp<float>);\n\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/operators/operator_fallback_mkl.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/mkl/operators/operator_fallback_mkl.h\"\n\n#include \"caffe2/mkl/utils/mkl_operator.h\"\n#include \"caffe2/operators/channel_shuffle_op.h\"\n#include \"caffe2/operators/cross_entropy_op.h\"\n#include \"caffe2/operators/dropout_op.h\"\n#include \"caffe2/operators/elementwise_linear_op.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/operators/filler_op.h\"\n#include \"caffe2/operators/load_save_op.h\"\n#include \"caffe2/operators/loss_op.h\"\n#include \"caffe2/operators/reshape_op.h\"\n#include \"caffe2/operators/softmax_op.h\"\n#include \"caffe2/operators/utility_ops.h\"\n\nnamespace caffe2 {\nnamespace {\nstruct SigmoidCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* /*device_context*/) {\n    ConstEigenVectorArrayMap<T> xM(x, n);\n    EigenVectorArrayMap<T>(y, n) = 1. / (1. + (-xM).exp());\n  }\n};\n} // namespace\n} // namespace caffe2\n\n// can add more non-MKL operators if needed\nnamespace caffe2 {\nREGISTER_MKL_OPERATOR(\n    Softmax,\n    mkl::MKLFallbackOp<SoftmaxOp<float, CPUContext>>);\nREGISTER_MKL_OPERATOR(\n    Reshape,\n    mkl::MKLFallbackOp<ReshapeOp<float, CPUContext>, SkipIndices<1>>);\nREGISTER_MKL_OPERATOR(\n    LabelCrossEntropy,\n    mkl::MKLFallbackOp<LabelCrossEntropyOp<float, CPUContext>>);\nREGISTER_MKL_OPERATOR(\n    AveragedLoss,\n    mkl::MKLFallbackOp<AveragedLoss<float, CPUContext>>);\n\n// filter operators\nREGISTER_MKL_OPERATOR(\n    XavierFill,\n    mkl::MKLFallbackOp<XavierFillOp<float, CPUContext>>);\nREGISTER_MKL_OPERATOR(\n    ConstantFill,\n    mkl::MKLFallbackOp<ConstantFillOp<CPUContext>>);\nREGISTER_MKL_OPERATOR(\n    GaussianFill,\n    mkl::MKLFallbackOp<GaussianFillOp<float, CPUContext>>);\nREGISTER_MKL_OPERATOR(\n    MSRAFill,\n    mkl::MKLFallbackOp<MSRAFillOp<float, CPUContext>>);\nREGISTER_MKL_OPERATOR(Load, mkl::MKLFallbackOp<LoadOp<CPUContext>>);\nREGISTER_MKL_OPERATOR(Save, mkl::MKLFallbackOp<SaveOp<CPUContext>>);\n\nREGISTER_MKL_OPERATOR(\n    Sigmoid,\n    mkl::MKLFallbackOp<\n        UnaryElementwiseOp<TensorTypes<float>, CPUContext, SigmoidCPUFunctor>>);\nREGISTER_MKL_OPERATOR(\n    Dropout,\n    mkl::MKLFallbackOp<DropoutOp<float, CPUContext>, SkipIndices<1>>);\nREGISTER_MKL_OPERATOR(\n    ElementwiseLinear,\n    mkl::MKLFallbackOp<ElementwiseLinearOp<float, CPUContext>>);\nREGISTER_MKL_OPERATOR(\n    ChannelShuffle,\n    mkl::MKLFallbackOp<ChannelShuffleOp<CPUContext>>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mkl/operators/operator_fallback_mkl.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_OPERATOR_FALLBACK_H_\n#define CAFFE2_OPERATORS_OPERATOR_FALLBACK_H_\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/mkl/mkl_utils.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\nnamespace caffe2 {\nnamespace mkl {\n\n/**\n * @brief A templated class to allow one to wrap a CPU operator as an MKL\n * operator.\n *\n * This class can be used when one does not have the MKL implementation ready\n * yet for an operator. Essentially, what this op does is to automatically\n * deal with data copy for you. Plausibly, this causes a lot of overhead and\n * is not optimal, so you should use this operator mostly for quick prototyping\n * purpose.\n *\n * All the input and output of the original operator should be TensorCPU.\n *\n * Example usage: if you have a class MyMagicOp that is CPU based, and you use\n * the registration code\n *     REGISTER_CPU_OPERATOR(MyMagic, MyMagicOp);\n * to register the CPU side, you can create its corresponding MKL operator\n * (with performance hits of course) via\n *     REGISTER_MKL_OPERATOR(MyMagic,\n *                            MKLFallbackOp<MyMagicOp>);\n *\n * Advanced usage: if you want to have some specific outputs never copied, you\n * can use the SkipOutputCopy template argument to do that. For example, if\n * MyMagic produces two outputs and the first output is always going to live on\n * the CPU, you can do\n *     REGISTER_MKL_OPERATOR(MyMagic,\n *                            MKLFallbackOp<MyMagicOp, SkipIndices<0>>);\n */\ntemplate <class CPUOp, typename SkipOutputCopy = SkipIndices<>>\nclass MKLFallbackOp final : public Operator<MKLContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(MKLContext);\n  MKLFallbackOp(const OperatorDef& def, Workspace* ws)\n      : Operator<MKLContext>(def, ws) {\n    CAFFE_ENFORCE_EQ(def.device_option().device_type(), MKLDNN);\n    OperatorDef base_def_(def);\n    // base_def_ runs on CPU, so we will set its device option to CPU.\n    // Copy to allow random_seed to be correctly propagated.\n    base_def_.mutable_device_option()->CopyFrom(def.device_option());\n    base_def_.mutable_device_option()->set_device_type(CPU);\n    // Set up the symbols for the local workspace.\n    for (const string& name : def.input()) {\n      local_input_blobs_.push_back(local_ws_.CreateBlob(name));\n      CHECK_NOTNULL(local_input_blobs_.back());\n    }\n    base_op_.reset(new CPUOp(base_def_, &local_ws_));\n    for (const string& name : def.output()) {\n      local_output_blobs_.push_back(local_ws_.GetBlob(name));\n      CHECK_NOTNULL(local_output_blobs_.back());\n    }\n  }\n\n  bool RunOnDevice() override {\n    for (int i = 0; i < InputSize(); ++i) {\n      if (OperatorBase::InputIsType<MKLMemory<float>>(i)) {\n        OperatorBase::Input<MKLMemory<float>>(i).CopyTo(\n            local_input_blobs_[i]->template GetMutable<TensorCPU>());\n      } else if (OperatorBase::InputIsType<MKLMemory<double>>(i)) {\n        OperatorBase::Input<MKLMemory<double>>(i).CopyTo(\n            local_input_blobs_[i]->template GetMutable<TensorCPU>());\n      } else {\n        VLOG(1) << \"Input \" << i << \" is not MKLMemory. Skipping copy.\";\n        // Note(jiayq): This removes a const but conceptually\n        // local_input_blobs will only be used as const blob input for the\n        // base op so we are still fine.\n        local_input_blobs_[i]->ShareExternal(\n            const_cast<void*>(OperatorBase::Inputs()[i]->GetRaw()),\n            OperatorBase::Inputs()[i]->meta());\n      }\n    }\n\n    if (!base_op_->Run()) {\n      LOG(ERROR) << \"Base op run failed in MKLFallbackOp. Def: \"\n                 << ProtoDebugString(this->debug_def());\n      return false;\n    }\n\n    for (int i = 0; i < OutputSize(); ++i) {\n      if (SkipOutputCopy::Contains(i)) {\n        VLOG(1) << \"Copy output: index \" << i << \" skipped.\";\n        continue;\n      }\n      CAFFE_ENFORCE(\n          local_output_blobs_[i]->template IsType<TensorCPU>(),\n          \"MKL fallback op currently does not support non-TensorCPU \"\n          \"output type who needs copying.\");\n      const auto& src = local_output_blobs_[i]->template Get<TensorCPU>();\n      if (src.template IsType<float>()) {\n        Blob* dst = OperatorBase::OutputBlob(i);\n        if (!dst->template IsType<MKLMemory<float>>() ||\n            dst->Get<MKLMemory<float>>().dims() != src.dims()) {\n          dst->Reset(new MKLMemory<float>(src.dims()));\n        }\n        dst->GetMutable<MKLMemory<float>>()->CopyFrom(src);\n      } else if (src.template IsType<double>()) {\n        Blob* dst = OperatorBase::OutputBlob(i);\n        if (!dst->template IsType<MKLMemory<double>>() ||\n            dst->Get<MKLMemory<double>>().dims() != src.dims()) {\n          dst->Reset(new MKLMemory<double>(src.dims()));\n        }\n        dst->GetMutable<MKLMemory<double>>()->CopyFrom(src);\n      } else {\n        CAFFE_THROW(\"MKLMemory only supports float and double.\");\n      }\n    }\n    return true;\n  }\n\n protected:\n  Workspace local_ws_;\n  vector<Blob*> local_input_blobs_;\n  vector<Blob*> local_output_blobs_;\n  std::unique_ptr<CPUOp> base_op_;\n};\n}\n\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n#endif // CAFFE2_OPERATORS_OPERATOR_FALLBACK_H_\n"
  },
  {
    "path": "caffe2/mkl/operators/packed_fc_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdint>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/mkl/mkl_utils.h\"\n#include \"caffe2/utils/cpuid.h\"\n#include \"caffe2/utils/math.h\"\n\n#ifdef CAFFE2_HAS_MKL_SGEMM_PACK\n\nnamespace caffe2 {\n\nCAFFE_KNOWN_TYPE(mkl::MKLPackedMatrix);\n\nnamespace mkl {\n\nclass PackedFCOp final : public Operator<CPUContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CPUContext);\n  PackedFCOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        axis_(OperatorBase::GetSingleArgument<int32_t>(\"axis\", 1)) {\n    OPERATOR_NEEDS_FEATURE(\n        GetCpuId().avx2() || operator_def.type() == \"PackedFC\",\n        \"If you are trying to use PackedFCOp as a FC with PACKED engine on \"\n        \"a machine that does not have avx2, be noted that the functionality \"\n        \"is not tuned and you are better off directly using FC.\");\n    // TODO(jiayq): after MKL update, remove this constraint. This is different\n    // from the check above, as the above is a performance hint and the below\n    // is about correctness.\n    CAFFE_ENFORCE(\n        GetCpuId().avx2(),\n        \"Do not run PackedFC on a machine that does not have avx2 \"\n        \"right now, as there is a known issue with MKL 2017.0.098 \"\n        \"that produces wrong results on non-avx2 machines.\");\n  }\n  ~PackedFCOp() {}\n\n  bool RunOnDevice() override {\n    const auto& X = Input(0);\n    const auto& b = Input(2);\n    auto* Y = Output(0);\n    CAFFE_ENFORCE(b.ndim() == 1, b.ndim());\n    // batch size\n    const auto canonical_axis = X.canonical_axis_index(axis_);\n    const int M = X.size_to_dim(canonical_axis);\n    const int K = X.size_from_dim(canonical_axis);\n    const int N = b.size();\n\n    // Check out what is the passed in format.\n    const MKLPackedMatrix* packed_matrix = nullptr;\n    if (OperatorBase::InputIsType<TensorCPU>(1)) {\n      const auto& W = Input(1);\n      CAFFE_ENFORCE_EQ(W.ndim(), 2);\n      CAFFE_ENFORCE_EQ(W.dim32(0), N);\n      CAFFE_ENFORCE_EQ(W.dim32(1), K);\n      // Note(jiayq): This will strictly check that we have a proper usage of\n      // the PackedFC operator. The motivation is that, this operator is\n      // stateful unlike most ops in Caffe2, but checking whether the weight\n      // has changed matters quite a lot in the critical path. We only enable\n      // this test during DEBUG mode for performance considerations.\n      DCHECK(hash_ == 0 || hash_ == Hash(W.template data<float>(), W.size()))\n          << \"PackedFCOp is currently stateful: you should not change the \"\n             \"weight during runtime. This is only sanity-checked in debug \"\n             \"mode for speed considerations.\";\n      if (!local_packed_matrix_.get() || local_packed_matrix_->n_ != M) {\n        // If there is no pre packed matrix, or the batch size changed, we\n        // do a re-pack.\n        local_packed_matrix_.reset(new MKLPackedMatrix(\n            CblasBMatrix,\n            CblasTrans,\n            M,\n            N,\n            K,\n            1.f,\n            W.template data<float>(),\n            K));\n      }\n      packed_matrix = local_packed_matrix_.get();\n    } else if (OperatorBase::InputIsType<MKLPackedMatrix>(1)) {\n      packed_matrix = &OperatorBase::Input<MKLPackedMatrix>(1);\n    }\n    CAFFE_ENFORCE_EQ(packed_matrix->m_, M);\n    CAFFE_ENFORCE_EQ(packed_matrix->k_, K);\n    CAFFE_ENFORCE_EQ(packed_matrix->n_, N);\n    // Do we want to check the other flags as well?\n\n    Y_shape_cache_ = X.dims();\n    // This is an invariant of canonical_axis, so we can DCHECK.\n    DCHECK_LE(canonical_axis + 1, Y_shape_cache_.size());\n    Y_shape_cache_.resize(canonical_axis + 1);\n    Y_shape_cache_[canonical_axis] = N;\n    Y->Resize(Y_shape_cache_);\n    CAFFE_ENFORCE(M * N == Y->size());\n\n    cblas_sgemm_compute(\n        CblasRowMajor,\n        CblasNoTrans,\n        CblasPacked,\n        M,\n        N,\n        K,\n        X.template data<float>(),\n        K,\n        packed_matrix->data_,\n        K,\n        0,\n        Y->template mutable_data<float>(),\n        N);\n\n    // Add bias term\n    if (bias_multiplier_.size() != M) {\n      // If the helper bias multiplier is not M, reshape and fill it with one.\n      bias_multiplier_.Resize(M);\n      math::Set<float, CPUContext>(\n          M, 1.f, bias_multiplier_.template mutable_data<float>(), &context_);\n    }\n    math::Gemm<float, CPUContext>(\n        CblasNoTrans,\n        CblasNoTrans,\n        M,\n        N,\n        1,\n        1,\n        bias_multiplier_.template data<float>(),\n        b.template data<float>(),\n        1,\n        Y->template mutable_data<float>(),\n        &context_);\n    return true;\n  }\n\n protected:\n  uint32_t Hash(const float* ptr, size_t n) {\n    uint32_t hash = 0;\n    const uint32_t* ptr_i = reinterpret_cast<const uint32_t*>(ptr);\n    for (int i = 0; i < n; ++i) {\n      hash ^= ptr_i[i];\n    }\n    return hash;\n  }\n  size_t axis_{1};\n  uint32_t hash_{0};\n  vector<TIndex> Y_shape_cache_;\n  Tensor<CPUContext> bias_multiplier_;\n  std::unique_ptr<MKLPackedMatrix> local_packed_matrix_;\n};\n\n} // namespace mkl\n\nREGISTER_CPU_OPERATOR(PackedFC, mkl::PackedFCOp);\nREGISTER_CPU_OPERATOR_WITH_ENGINE(FC, PACKED, mkl::PackedFCOp);\n\nOPERATOR_SCHEMA(PackedFC).NumInputs(3).NumOutputs(1).SetDoc(R\"DOC(\nComputes the result of passing an input vector X into a fully connected\nlayer with 2D weight matrix W and 1D bias vector b. This is essentially the\nsame as the FC operator but allows one to pack the weight matrix for more\nefficient inference. See the schema for the FC op for details.\n\nUnlike many other operators in Caffe2, this operator is stateful: it assumes\nthat the input weight matrix W never changes, so it is only suitable for\ninference time when the weight matrix never gets updated by any other ops.\nDue to performance considerations, this is not checked in non-debug builds.\n)DOC\");\n\nSHOULD_NOT_DO_GRADIENT(PackedFC);\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_SGEMM_PACK\n"
  },
  {
    "path": "caffe2/mkl/operators/pool_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n\n#include \"caffe2/mkl/mkl_utils.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\n\nnamespace mkl {\n\ntemplate <typename T>\nclass MKLPoolOp final : public ConvPoolOpBase<MKLContext> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(MKLContext);\n  MKLPoolOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<MKLContext>(operator_def, ws) {\n    CAFFE_ENFORCE(\n        (dilation_h() == 1) && (dilation_w() == 1),\n        \"Pooling op does not support dilation right now.\");\n    if (!global_pooling_) {\n      CAFFE_ENFORCE(\n          pad_t() < kernel_h() && pad_b() < kernel_h() &&\n              pad_l() < kernel_w() && pad_r() < kernel_w(),\n          \"Pad should be smaller than kernel.\");\n    }\n    // Figure out the pooling descriptor.\n    if (operator_def.type().substr(0, 7) == \"MaxPool\") {\n      algo = dnnAlgorithmPoolingMax;\n    } else if (operator_def.type().substr(0, 11) == \"AveragePool\") {\n      algo = dnnAlgorithmPoolingAvg;\n    } else {\n      LOG(FATAL) << \"Unsupported pooling method: \" << operator_def.type();\n    }\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n  // Input: X\n  // Output: Y\n private:\n  vector<TIndex> cached_input_dims_;\n  // vector<TIndex> cached_avgpool_input_dims_;\n  LayoutWrapper<T> workspace_layout_;\n  std::unique_ptr<MKLWorkspace<T>> workspace_buffer_;\n  PrimitiveWrapper<T> primitive_;\n  MKLMemory<T> buffer_;\n  void* resources_[dnnResourceNumber] = {0};\n  dnnAlgorithm_t algo;\n};\n\ntemplate <>\nbool MKLPoolOp<float>::RunOnDeviceWithOrderNCHW() {\n  auto& X = OperatorBase::Input<MKLMemory<float>>(0);\n  MKLMemory<float>* Y = OperatorBase::Output<MKLMemory<float>>(0);\n\n  bool dims_changed;\n  CHECK_INPUT_DIMS(X, dims_changed);\n  if (dims_changed || FLAGS_caffe2_mkl_memonger_in_use) {\n    // We will utilize the SetOutputSize() function in the base class\n    // with dummy TensorCPU input and output to calculate the sizes.\n    TensorCPU dummy_input(X.dims());\n    TensorCPU dummy_output;\n\n    ConvPoolOpBase<MKLContext>::SetOutputSize(\n        dummy_input, &dummy_output, X.dim32(1));\n    size_t dim = X.ndim();\n    CAFFE_ENFORCE(4 == dim);\n\n    int paddings[4] = {-pad_l(), -pad_t(), -pad_r(), -pad_b()};\n    size_t strides[2] = {stride_w(), stride_h()};\n    size_t kernel_size[2] = {kernel_w(), kernel_h()};\n\n    // Create main primitive.\n    primitive_.Reset(\n        dnnPoolingCreateForward_F32,\n        nullptr,\n        algo,\n        X.layout(),\n        kernel_size,\n        strides,\n        paddings,\n        dnnBorderZerosAsymm);\n\n    Y->Reset(dummy_output.dims(), primitive_, dnnResourceDst);\n    buffer_.Reset(dummy_output.dims(), primitive_, dnnResourceDst, true);\n\n    workspace_layout_.Reset(primitive_, dnnResourceWorkspace);\n    workspace_buffer_ =\n        caffe2::make_unique<MKLWorkspace<float>>(workspace_layout_);\n  }\n\n  // Try to share from the output: this allows us to avoid unnecessary copy\n  // operations, if the output is already allocated and is having the same\n  // layout as the buffer has.\n  bool shared = buffer_.ShareFrom(*Y);\n  resources_[dnnResourceSrc] = X.buffer();\n  resources_[dnnResourceDst] = buffer_.buffer();\n  resources_[dnnResourceWorkspace] = workspace_buffer_->buffer();\n  MKLDNN_SAFE_CALL(mkl::dnnExecute<float>(primitive_, resources_));\n  buffer_.CopyTo(Y, primitive_, dnnResourceDst);\n  if (FLAGS_caffe2_mkl_memonger_in_use && !shared) {\n    buffer_.Reset();\n  }\n  return true;\n}\n\ntemplate <>\nbool MKLPoolOp<float>::RunOnDeviceWithOrderNHWC() {\n  CAFFE_NOT_IMPLEMENTED;\n}\n\n} // namespace mkl\n\nREGISTER_MKL_OPERATOR(AveragePool, mkl::MKLPoolOp<float>);\nREGISTER_MKL_OPERATOR(MaxPool, mkl::MKLPoolOp<float>);\n\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/operators/relu_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/relu_op.h\"\n#include \"caffe2/mkl/mkl_utils.h\"\n\n#include \"caffe2/utils/math.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\nnamespace mkl {\n\ntemplate <typename T>\nclass MKLReluOp : public MKLOperator<T> {\n public:\n  USE_MKLOPERATOR_FUNCTIONS(T);\n  USE_SIMPLE_MKL_CTOR_DTOR(MKLReluOp, T);\n  bool RunOnDevice() override {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n\n    bool dims_changed;\n    CHECK_INPUT_DIMS(X, dims_changed);\n    if (dims_changed || FLAGS_caffe2_mkl_memonger_in_use) {\n      // First run or changed input size, will need to recreate environment\n      primitive_.Reset(dnnReLUCreateForward<T>, nullptr, X.layout(), 0.f);\n      if (&X != Y) {\n        Y->Reset(X.dims(), primitive_, dnnResourceDst);\n      }\n      buffer_.Reset(X.dims(), primitive_, dnnResourceDst, true);\n    }\n    // Try to share from the output: this allows us to avoid unnecessary copy\n    // operations, if the output is already allocated and is having the same\n    // layout as the buffer has.\n    bool shared = buffer_.ShareFrom(*Y);\n    CAFFE_ENFORCE(dnnLayoutCompare_F32(X.layout(), buffer_.layout()));\n    resources_[dnnResourceSrc] = X.buffer();\n    resources_[dnnResourceDst] = buffer_.buffer();\n    ExecutePrimitive();\n    buffer_.CopyTo(Y, primitive_, dnnResourceDst);\n    if (FLAGS_caffe2_mkl_memonger_in_use && !shared) {\n      buffer_.Reset();\n    }\n    return true;\n  }\n\n private:\n  vector<TIndex> cached_input_dims_;\n};\n\ntemplate <typename T>\nclass MKLReluGradientOp : public MKLOperator<T> {\n public:\n  USE_MKLOPERATOR_FUNCTIONS(T);\n  USE_SIMPLE_MKL_CTOR_DTOR(MKLReluGradientOp, T);\n  bool RunOnDevice() override {\n    auto& Y = Input(0);\n    auto& dY = Input(1);\n    auto* dX = Output(0);\n    if (input_size_cache_.size() != 1 || input_size_cache_[0] != Y.dims()) {\n      // First run or changed input size, will need to recreate environment\n      primitive_.Reset(\n          dnnReLUCreateBackward<T>, nullptr, dY.layout(), Y.layout(), 0.f);\n      dX->Reset(Y.dims(), primitive_, dnnResourceDiffSrc);\n      buffer_.Reset(Y.dims(), primitive_, dnnResourceDiffSrc, true);\n    }\n    // Try to share from the output: this will save a copy if the output is\n    // already allocated and is having the same layout as the buffer has.\n    buffer_.ShareFrom(*dX);\n    // MKLDNN seems to use X instead of Y for src, let's see if this works.\n    resources_[dnnResourceSrc] = Y.buffer();\n    resources_[dnnResourceDiffDst] = dY.buffer();\n    resources_[dnnResourceDiffSrc] = buffer_.buffer();\n    ExecutePrimitive();\n    buffer_.CopyTo(dX);\n    return true;\n  }\n};\n} // namespace mkl\n\nREGISTER_MKL_OPERATOR(Relu, mkl::MKLReluOp<float>);\nREGISTER_MKL_OPERATOR(ReluGradient, mkl::MKLReluGradientOp<float>);\n\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/operators/spatial_batch_norm_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/spatial_batch_norm_op.h\"\n#include <math.h>\n\n#include \"caffe2/mkl/mkl_utils.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\nnamespace mkl {\n\ntemplate <typename T>\nclass MKLBNOp final : public SpatialBNOp<MKLContext> {\n public:\n  MKLBNOp(const OperatorDef& operator_def, Workspace* ws)\n      : SpatialBNOp<MKLContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(\n        order_ == StorageOrder::NCHW, \"Only NCHW order supported.\");\n    OPERATOR_NEEDS_FEATURE(\n        operator_def.input(0) != operator_def.output(0),\n        \"Inplace BN not supported\");\n  }\n\n  bool RunOnDevice() {\n    auto& X = OperatorBase::Input<MKLMemory<float>>(INPUT);\n    auto& scale = OperatorBase::Input<MKLMemory<float>>(SCALE);\n    auto& bias = OperatorBase::Input<MKLMemory<float>>(BIAS);\n\n    MKLMemory<float>* Y = OperatorBase::Output<MKLMemory<float>>(OUTPUT);\n    // anded with is_test_-1 to avoid uninitialized access in case of testing\n    MKLMemory<float>* running_mean =\n        OperatorBase::Output<MKLMemory<float>>(RUNNING_MEAN & (is_test_ - 1));\n    MKLMemory<float>* running_var =\n        OperatorBase::Output<MKLMemory<float>>(RUNNING_VAR & (is_test_ - 1));\n    MKLMemory<float>* saved_mean =\n        OperatorBase::Output<MKLMemory<float>>(SAVED_MEAN & (is_test_ - 1));\n    MKLMemory<float>* saved_var =\n        OperatorBase::Output<MKLMemory<float>>(SAVED_INV_VAR & (is_test_ - 1));\n\n    // current code supports only NCHW -\n    // have to look for MKL related changes for NHWC later\n    const int N = X.dim32(0);\n    const int C = (order_ == StorageOrder::NCHW ? X.dim32(1) : X.dim32(3));\n    const int H = (order_ == StorageOrder::NCHW ? X.dim32(2) : X.dim32(1));\n    const int W = (order_ == StorageOrder::NCHW ? X.dim32(3) : X.dim32(2));\n\n    DCHECK_EQ(scale.ndim(), 1);\n    DCHECK_EQ(bias.ndim(), 1);\n    DCHECK_EQ(scale.dim32(0), C);\n    DCHECK_EQ(bias.dim32(0), C);\n\n    bool dims_changed;\n    CHECK_INPUT_DIMS(X, dims_changed);\n    if (dims_changed || FLAGS_caffe2_mkl_memonger_in_use) {\n      // Create main primitive.\n      if (is_test_) {\n        primitive_.Reset(\n            dnnBatchNormalizationCreateForward_v2<T>,\n            nullptr,\n            X.layout(),\n            epsilon_,\n            dnnUseInputMeanVariance | dnnUseScaleShift);\n      } else {\n        primitive_.Reset(\n            dnnBatchNormalizationCreateForward_v2<T>,\n            nullptr,\n            X.layout(),\n            epsilon_,\n            dnnUseScaleShift);\n\n        // using scale dims as it is also of size C\n        saved_mean->Reset(scale.dims(), primitive_, dnnResourceMean);\n        saved_var->Reset(scale.dims(), primitive_, dnnResourceVariance);\n        running_mean->Reset(scale.dims(), primitive_, dnnResourceMean);\n        running_var->Reset(scale.dims(), primitive_, dnnResourceVariance);\n\n        running_mean_buf = (T*)running_mean->buffer();\n        running_var_buf = (T*)running_var->buffer();\n      }\n      Y->Reset(X.dims(), primitive_, dnnResourceDst);\n      buffer_.Reset(X.dims(), primitive_, dnnResourceDst, true);\n\n      scale_bias_layout_.Reset(primitive_, dnnResourceScaleShift);\n      scale_bias_buffer_ =\n          caffe2::make_unique<MKLWorkspace<float>>(scale_bias_layout_);\n\n      // fill scale and bias into a single buffer\n      scale_buf = (T*)scale.buffer();\n      bias_buf = (T*)bias.buffer();\n      for (int i = 0; i < C; i++) {\n        scale_bias_buffer_->buffer()[i] = scale_buf[i];\n        scale_bias_buffer_->buffer()[C + i] = bias_buf[i];\n      }\n    }\n\n    // Try to share from the output: this allows us to avoid unnecessary copy\n    // operations, if the output is already allocated and is having the same\n    // layout as the buffer has.\n    bool shared = buffer_.ShareFrom(*Y);\n    resources_[dnnResourceSrc] = X.buffer();\n    resources_[dnnResourceDst] = buffer_.buffer();\n    resources_[dnnResourceScaleShift] = scale_bias_buffer_->buffer();\n\n    if (is_test_) {\n      auto& est_mean = OperatorBase::Input<MKLMemory<float>>(EST_MEAN);\n      auto& est_var = OperatorBase::Input<MKLMemory<float>>(EST_VAR);\n\n      resources_[dnnResourceMean] = est_mean.buffer();\n      resources_[dnnResourceVariance] = est_var.buffer();\n    } else {\n      resources_[dnnResourceMean] = saved_mean->buffer();\n      resources_[dnnResourceVariance] = saved_var->buffer();\n    }\n\n    MKLDNN_SAFE_CALL(mkl::dnnExecute<float>(primitive_, resources_));\n\n    if (!is_test_) {\n      // compute running mean and variance\n      saved_mean_buf = (T*)saved_mean->buffer();\n      saved_var_buf = (T*)saved_var->buffer();\n\n      for (int i = 0; i < C; i++) {\n        running_mean_buf[i] = running_mean_buf[i] * momentum_ +\n            saved_mean_buf[i] * (1. - momentum_);\n        running_var_buf[i] = running_var_buf[i] * momentum_ +\n            saved_var_buf[i] * (1. - momentum_);\n        saved_var_buf[i] = (1 / sqrt(saved_var_buf[i] + epsilon_));\n      }\n    }\n    buffer_.CopyTo(Y, primitive_, dnnResourceDst);\n    if (FLAGS_caffe2_mkl_memonger_in_use && !shared) {\n      buffer_.Reset();\n    }\n    return true;\n  }\n\n private:\n  vector<TIndex> cached_input_dims_;\n  LayoutWrapper<T> scale_bias_layout_;\n  LayoutWrapper<T> saved_mean_layout_;\n  LayoutWrapper<T> saved_var_layout_;\n  LayoutWrapper<T> running_mean_layout_;\n  LayoutWrapper<T> running_var_layout_;\n  std::unique_ptr<MKLWorkspace<T>> scale_bias_buffer_;\n  T* scale_buf = nullptr;\n  T* bias_buf = nullptr;\n  T* saved_mean_buf = nullptr;\n  T* saved_var_buf = nullptr;\n  T* running_mean_buf = nullptr;\n  T* running_var_buf = nullptr;\n  PrimitiveWrapper<T> primitive_;\n  MKLMemory<T> buffer_;\n  void* resources_[dnnResourceNumber] = {0};\n};\n} // namespace mkl\n\nREGISTER_MKL_OPERATOR(SpatialBN, mkl::MKLBNOp<float>);\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/operators/squeeze_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/mkl/mkl_utils.h\"\n#include \"caffe2/operators/expand_squeeze_dims_op.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\nnamespace mkl {\n\ntemplate <typename T>\nclass MKLSqueezeOp final : public MKLOperator<T> {\n public:\n  USE_MKLOPERATOR_FUNCTIONS(T);\n\n  MKLSqueezeOp(const OperatorDef& operator_def, Workspace* ws)\n      : MKLOperator<T>(operator_def, ws),\n        dims_(OperatorBase::GetRepeatedArgument<int>(\"dims\")) {\n    auto originalSize = dims_.size();\n    CAFFE_ENFORCE(originalSize > 0, \"Parameter `dims` must be provided.\");\n\n    std::sort(dims_.begin(), dims_.end());\n    dims_.erase(std::unique(dims_.begin(), dims_.end()), dims_.end());\n    if (dims_.size() < originalSize) {\n      LOG(WARNING) << \"Parameter `dims` has repeated dimensions.\";\n    }\n    CAFFE_ENFORCE(dims_.front() >= 0, \"Dimension ids must be non-negative.\");\n  }\n\n  bool RunOnDevice() override {\n    const auto& X = Input(0);\n    auto* Y = Output(0);\n\n    CAFFE_ENFORCE_GT(\n        X.ndim(),\n        dims_.back(),\n        \"Input needs at least \",\n        (dims_.back() + 1),\n        \" dimensions.\");\n    const auto& new_dims = SqueezeOp<MKLContext>::ComputeDims(X.dims(), dims_);\n\n    bool dims_changed;\n    CHECK_INPUT_DIMS(X, dims_changed);\n    if (dims_changed || FLAGS_caffe2_mkl_memonger_in_use) {\n      // Temp buffer mainly to convert the input to plain layout before\n      // Reshape() if the input has a custom layout.\n      buffer_.Reset(X.dims());\n    }\n\n    // Always copy to temp buffer to avoid subsequent runs throwing layout\n    // mismatch errors for X.\n    buffer_.CopyFrom(X);\n    Y->Reset(X.dims(), nullptr, dnnResourceNumber, true);\n    CAFFE_ENFORCE(dnnLayoutCompare<T>(buffer_.layout(), Y->layout()));\n    CAFFE_ENFORCE(Y->ShareFrom(buffer_));\n    Y->Reshape(new_dims);\n    return true;\n  }\n\n private:\n  vector<int> dims_;\n  vector<TIndex> cached_input_dims_;\n};\n\n} // namespace mkl\n\nREGISTER_MKL_OPERATOR(Squeeze, mkl::MKLSqueezeOp<float>);\n\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/operators/utility_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/utility_ops.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/mkl/mkl_utils.h\"\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\nnamespace mkl {\n\nclass CopyCPUToMKLOp final : public MKLOperator<float> {\n public:\n  using MKLOperator<float>::MKLOperator;\n  bool RunOnDevice() override {\n    const auto& X = OperatorBase::Input<TensorCPU>(0);\n    auto* Y = OperatorBase::OutputBlob(0);\n    if (!Y->template IsType<MKLMemory<float>>() ||\n        Y->Get<MKLMemory<float>>().dims() != X.dims()) {\n      Y->Reset(new MKLMemory<float>(X.dims()));\n    }\n    Y->GetMutable<MKLMemory<float>>()->CopyFrom(X);\n    return true;\n  }\n};\n\nclass CopyMKLToCPUOp final : public MKLOperator<float> {\n public:\n  using MKLOperator<float>::MKLOperator;\n\n  bool RunOnDevice() override {\n    const auto& X = OperatorBase::Input<MKLMemory<float>>(0);\n    auto* Y = OperatorBase::Output<TensorCPU>(0);\n    X.CopyTo(Y);\n    return true;\n  }\n};\n\n} // namespace mkl\n\nREGISTER_MKL_OPERATOR(CopyCPUToMKL, mkl::CopyCPUToMKLOp);\nREGISTER_MKL_OPERATOR(CopyMKLToCPU, mkl::CopyMKLToCPUOp);\n\nOPERATOR_SCHEMA(CopyCPUToMKL)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .Input(0, \"cpu_blob\", \"The input TensorCPU to copy\")\n    .Output(0, \"mkl_blob\", \"The output MKLMemory to copy to\");\nOPERATOR_SCHEMA(CopyMKLToCPU)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .Input(0, \"mkl_blob\", \"The input MKLMemory to copy\")\n    .Output(0, \"cpu_blob\", \"The output TensorCPU to copy to\");\n\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/utils/mkl_context.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// #include \"caffe2/mkl/utils/mkl_context.h\"\n\n#include \"caffe2/core/event_cpu.h\"\n\nnamespace caffe2 {\n\n// MKL events are the same as CPU events\n\nREGISTER_EVENT_CREATE_FUNCTION(MKLDNN, EventCreateCPU);\nREGISTER_EVENT_RECORD_FUNCTION(MKLDNN, EventRecordCPU);\nREGISTER_EVENT_WAIT_FUNCTION(MKLDNN, MKLDNN, EventWaitCPUCPU);\nREGISTER_EVENT_WAIT_FUNCTION(MKLDNN, CPU, EventWaitCPUCPU);\nREGISTER_EVENT_WAIT_FUNCTION(CPU, MKLDNN, EventWaitCPUCPU);\nREGISTER_EVENT_FINISH_FUNCTION(MKLDNN, EventFinishCPU);\n\nREGISTER_EVENT_QUERY_FUNCTION(MKLDNN, EventQueryCPU);\nREGISTER_EVENT_ERROR_MESSAGE_FUNCTION(MKLDNN, EventErrorMessageCPU);\nREGISTER_EVENT_SET_FINISHED_FUNCTION(MKLDNN, EventSetFinishedCPU);\nREGISTER_EVENT_RESET_FUNCTION(MKLDNN, EventResetCPU);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mkl/utils/mkl_context.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_MKL_CONTEXT_H_\n#define CAFFE2_UTILS_MKL_CONTEXT_H_\n\n#include <cstdlib>\n#include <ctime>\n#include <random>\n\n#include \"caffe2/core/context.h\"\n\nnamespace caffe2 {\n\n/**\n * The MKL Context, which is largely the same as the CPUContext. We instantiate\n * this mainly in order to have a first-class MKL device.\n *\n * Note that although New() and Delete() are implemented, we expect MKLContext\n * operators to mainly perform input and output via MKLMemory. As a result,\n * most likely MKLContext::New and ::Delete won't be used as often.\n */\nclass MKLContext final {\n public:\n  MKLContext() : random_seed_(RandomNumberSeed()) {}\n  explicit MKLContext(const DeviceOption& option)\n      : random_seed_(\n            option.has_random_seed() ? option.random_seed()\n                                     : RandomNumberSeed()) {\n    CAFFE_ENFORCE_EQ(option.device_type(), MKLDNN);\n  }\n\n  ~MKLContext() {}\n\n  inline void SwitchToDevice(int /*stream_id*/ = 0) {}\n\n  inline void WaitEvent(const Event& ev) {\n    ev.Wait(MKLDNN, this);\n  }\n\n  inline void Record(Event* ev, const char* err_msg = nullptr) const {\n    CAFFE_ENFORCE(ev, \"Event must not be null.\");\n    ev->Record(MKLDNN, this, err_msg);\n  }\n\n  inline void FinishDeviceComputation() {}\n\n  inline std::mt19937& RandGenerator() {\n    if (!random_generator_.get()) {\n      random_generator_.reset(new std::mt19937(random_seed_));\n    }\n    return *random_generator_.get();\n  }\n\n  inline static std::pair<void*, MemoryDeleter> New(size_t nbytes) {\n    return GetCPUAllocator()->New(nbytes);\n  }\n\n  // Two copy functions that deals with cross-device copies.\n  template <class SrcContext, class DstContext>\n  inline void CopyBytes(size_t nbytes, const void* src, void* dst);\n\n  template <typename T, class SrcContext, class DstContext>\n  inline void Copy(size_t n, const T* src, T* dst) {\n    if (std::is_fundamental<T>::value) {\n      CopyBytes<SrcContext, DstContext>(\n          n * sizeof(T),\n          static_cast<const void*>(src),\n          static_cast<void*>(dst));\n    } else {\n      for (int i = 0; i < n; ++i) {\n        dst[i] = src[i];\n      }\n    }\n  }\n\n  template <class SrcContext, class DstContext>\n  inline void\n  CopyItems(const TypeMeta& meta, size_t n, const void* src, void* dst) {\n    if (meta.copy()) {\n      meta.copy()(src, dst, n);\n    } else {\n      CopyBytes<SrcContext, DstContext>(n * meta.itemsize(), src, dst);\n    }\n  }\n\n  // By default MKL operators don't have async device parts\n  static bool HasAsyncPartDefault() {\n    return false;\n  }\n\n  static bool SupportsAsyncScheduling() {\n    return false;\n  }\n\n  static bool IsStreamFree(const DeviceOption& /* unused */, int /* unused */) {\n    return true;\n  }\n\n protected:\n  // TODO(jiayq): instead of hard-coding a generator, make it more flexible.\n  int random_seed_{1701};\n  std::unique_ptr<std::mt19937> random_generator_;\n};\n\ntemplate <>\ninline void MKLContext::CopyBytes<MKLContext, MKLContext>(\n    size_t nbytes,\n    const void* src,\n    void* dst) {\n  memcpy(dst, src, nbytes);\n}\n\ntemplate <>\ninline void MKLContext::CopyBytes<CPUContext, MKLContext>(\n    size_t nbytes,\n    const void* src,\n    void* dst) {\n  memcpy(dst, src, nbytes);\n}\n\ntemplate <>\ninline void MKLContext::CopyBytes<MKLContext, CPUContext>(\n    size_t nbytes,\n    const void* src,\n    void* dst) {\n  memcpy(dst, src, nbytes);\n}\n} // namespace caffe2\n\n#endif // CAFFE2_UTILS_MKL_CONTEXT_H_\n"
  },
  {
    "path": "caffe2/mkl/utils/mkl_dnn_cppwrapper.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Do not directl include this file. Include caffe2/mkl/mkl_utils.h instead.\n#ifndef CAFFE2_UTILS_MKL_MKL_DNN_CPPWRAPPER_H\n#define CAFFE2_UTILS_MKL_MKL_DNN_CPPWRAPPER_H\n\n#include <stdarg.h>\n#include <stddef.h>\n\n#include <mkl.h>\n\n#define C2_MKL_TEMPLATE_PREFIX \\\n  template <typename T>        \\\n  inline\n#define C2_MKL_SPEC_PREFIX \\\n  template <>              \\\n  inline\n\nnamespace caffe2 {\nnamespace mkl {\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnLayoutCreate(\n    dnnLayout_t* pLayout,\n    size_t dimension,\n    const size_t size[],\n    const size_t strides[]);\nC2_MKL_SPEC_PREFIX dnnError_t dnnLayoutCreate<float>(\n    dnnLayout_t* pLayout,\n    size_t dimension,\n    const size_t size[],\n    const size_t strides[]) {\n  return dnnLayoutCreate_F32(pLayout, dimension, size, strides);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnLayoutCreate<double>(\n    dnnLayout_t* pLayout,\n    size_t dimension,\n    const size_t size[],\n    const size_t strides[]) {\n  return dnnLayoutCreate_F64(pLayout, dimension, size, strides);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnLayoutCreateFromPrimitive(\n    dnnLayout_t* pLayout,\n    const dnnPrimitive_t primitive,\n    dnnResourceType_t type);\nC2_MKL_SPEC_PREFIX dnnError_t dnnLayoutCreateFromPrimitive<float>(\n    dnnLayout_t* pLayout,\n    const dnnPrimitive_t primitive,\n    dnnResourceType_t type) {\n  return dnnLayoutCreateFromPrimitive_F32(pLayout, primitive, type);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnLayoutCreateFromPrimitive<double>(\n    dnnLayout_t* pLayout,\n    const dnnPrimitive_t primitive,\n    dnnResourceType_t type) {\n  return dnnLayoutCreateFromPrimitive_F64(pLayout, primitive, type);\n}\n\nC2_MKL_TEMPLATE_PREFIX size_t dnnLayoutGetMemorySize(const dnnLayout_t layout);\nC2_MKL_SPEC_PREFIX size_t\ndnnLayoutGetMemorySize<float>(const dnnLayout_t layout) {\n  return dnnLayoutGetMemorySize_F32(layout);\n}\nC2_MKL_SPEC_PREFIX size_t\ndnnLayoutGetMemorySize<double>(const dnnLayout_t layout) {\n  return dnnLayoutGetMemorySize_F64(layout);\n}\n\nC2_MKL_TEMPLATE_PREFIX int dnnLayoutCompare(\n    const dnnLayout_t l1,\n    const dnnLayout_t l2);\nC2_MKL_SPEC_PREFIX int dnnLayoutCompare<float>(\n    const dnnLayout_t l1,\n    const dnnLayout_t l2) {\n  return dnnLayoutCompare_F32(l1, l2);\n}\nC2_MKL_SPEC_PREFIX int dnnLayoutCompare<double>(\n    const dnnLayout_t l1,\n    const dnnLayout_t l2) {\n  return dnnLayoutCompare_F64(l1, l2);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t\ndnnAllocateBuffer(void** pPtr, dnnLayout_t layout);\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnAllocateBuffer<float>(void** pPtr, dnnLayout_t layout) {\n  return dnnAllocateBuffer_F32(pPtr, layout);\n}\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnAllocateBuffer<double>(void** pPtr, dnnLayout_t layout) {\n  return dnnAllocateBuffer_F64(pPtr, layout);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnReleaseBuffer(void* ptr);\nC2_MKL_SPEC_PREFIX dnnError_t dnnReleaseBuffer<float>(void* ptr) {\n  return dnnReleaseBuffer_F32(ptr);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnReleaseBuffer<double>(void* ptr) {\n  return dnnReleaseBuffer_F64(ptr);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnLayoutDelete(dnnLayout_t layout);\nC2_MKL_SPEC_PREFIX dnnError_t dnnLayoutDelete<float>(dnnLayout_t layout) {\n  return dnnLayoutDelete_F32(layout);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnLayoutDelete<double>(dnnLayout_t layout) {\n  return dnnLayoutDelete_F64(layout);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t\ndnnPrimitiveAttributesCreate(dnnPrimitiveAttributes_t* attributes);\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnPrimitiveAttributesCreate<float>(dnnPrimitiveAttributes_t* attributes) {\n  return dnnPrimitiveAttributesCreate_F32(attributes);\n}\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnPrimitiveAttributesCreate<double>(dnnPrimitiveAttributes_t* attributes) {\n  return dnnPrimitiveAttributesCreate_F64(attributes);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t\ndnnPrimitiveAttributesDestroy(dnnPrimitiveAttributes_t attributes);\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnPrimitiveAttributesDestroy<float>(dnnPrimitiveAttributes_t attributes) {\n  return dnnPrimitiveAttributesDestroy_F32(attributes);\n}\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnPrimitiveAttributesDestroy<double>(dnnPrimitiveAttributes_t attributes) {\n  return dnnPrimitiveAttributesDestroy_F64(attributes);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnPrimitiveGetAttributes(\n    dnnPrimitive_t primitive,\n    dnnPrimitiveAttributes_t* attributes);\nC2_MKL_SPEC_PREFIX dnnError_t dnnPrimitiveGetAttributes<float>(\n    dnnPrimitive_t primitive,\n    dnnPrimitiveAttributes_t* attributes) {\n  return dnnPrimitiveGetAttributes_F32(primitive, attributes);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnPrimitiveGetAttributes<double>(\n    dnnPrimitive_t primitive,\n    dnnPrimitiveAttributes_t* attributes) {\n  return dnnPrimitiveGetAttributes_F64(primitive, attributes);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t\ndnnExecute(dnnPrimitive_t primitive, void* resources[]);\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnExecute<float>(dnnPrimitive_t primitive, void* resources[]) {\n  return dnnExecute_F32(primitive, resources);\n}\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnExecute<double>(dnnPrimitive_t primitive, void* resources[]) {\n  return dnnExecute_F64(primitive, resources);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t\ndnnExecuteAsync(dnnPrimitive_t primitive, void* resources[]);\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnExecuteAsync<float>(dnnPrimitive_t primitive, void* resources[]) {\n  return dnnExecuteAsync_F32(primitive, resources);\n}\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnExecuteAsync<double>(dnnPrimitive_t primitive, void* resources[]) {\n  return dnnExecuteAsync_F64(primitive, resources);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnWaitFor(dnnPrimitive_t primitive);\nC2_MKL_SPEC_PREFIX dnnError_t dnnWaitFor<float>(dnnPrimitive_t primitive) {\n  return dnnWaitFor_F32(primitive);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnWaitFor<double>(dnnPrimitive_t primitive) {\n  return dnnWaitFor_F64(primitive);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnDelete(dnnPrimitive_t primitive);\nC2_MKL_SPEC_PREFIX dnnError_t dnnDelete<float>(dnnPrimitive_t primitive) {\n  return dnnDelete_F32(primitive);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnDelete<double>(dnnPrimitive_t primitive) {\n  return dnnDelete_F64(primitive);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnConversionCreate(\n    dnnPrimitive_t* pConversion,\n    const dnnLayout_t from,\n    const dnnLayout_t to);\nC2_MKL_SPEC_PREFIX dnnError_t dnnConversionCreate<float>(\n    dnnPrimitive_t* pConversion,\n    const dnnLayout_t from,\n    const dnnLayout_t to) {\n  return dnnConversionCreate_F32(pConversion, from, to);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnConversionCreate<double>(\n    dnnPrimitive_t* pConversion,\n    const dnnLayout_t from,\n    const dnnLayout_t to) {\n  return dnnConversionCreate_F64(pConversion, from, to);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t\ndnnConversionExecute(dnnPrimitive_t conversion, void* from, void* to);\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnConversionExecute<float>(dnnPrimitive_t conversion, void* from, void* to) {\n  return dnnConversionExecute_F32(conversion, from, to);\n}\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnConversionExecute<double>(dnnPrimitive_t conversion, void* from, void* to) {\n  return dnnConversionExecute_F64(conversion, from, to);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnConvolutionCreateForward(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type);\nC2_MKL_SPEC_PREFIX dnnError_t dnnConvolutionCreateForward<float>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnConvolutionCreateForward_F32(\n      pConvolution,\n      attributes,\n      algorithm,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\n\nC2_MKL_SPEC_PREFIX dnnError_t dnnConvolutionCreateForward<double>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnConvolutionCreateForward_F64(\n      pConvolution,\n      attributes,\n      algorithm,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnConvolutionCreateForwardBias(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type);\nC2_MKL_SPEC_PREFIX dnnError_t dnnConvolutionCreateForwardBias<float>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnConvolutionCreateForwardBias_F32(\n      pConvolution,\n      attributes,\n      algorithm,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnConvolutionCreateForwardBias<double>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnConvolutionCreateForwardBias_F64(\n      pConvolution,\n      attributes,\n      algorithm,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnConvolutionCreateBackwardData(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type);\nC2_MKL_SPEC_PREFIX dnnError_t dnnConvolutionCreateBackwardData<float>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnConvolutionCreateBackwardData_F32(\n      pConvolution,\n      attributes,\n      algorithm,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnConvolutionCreateBackwardData<double>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnConvolutionCreateBackwardData_F64(\n      pConvolution,\n      attributes,\n      algorithm,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnConvolutionCreateBackwardFilter(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type);\nC2_MKL_SPEC_PREFIX dnnError_t dnnConvolutionCreateBackwardFilter<float>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnConvolutionCreateBackwardFilter_F32(\n      pConvolution,\n      attributes,\n      algorithm,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnConvolutionCreateBackwardFilter<double>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnConvolutionCreateBackwardFilter_F64(\n      pConvolution,\n      attributes,\n      algorithm,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnConvolutionCreateBackwardBias(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t dstSize[]);\nC2_MKL_SPEC_PREFIX dnnError_t dnnConvolutionCreateBackwardBias<float>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t dstSize[]) {\n  return dnnConvolutionCreateBackwardBias_F32(\n      pConvolution, attributes, algorithm, dimension, dstSize);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnConvolutionCreateBackwardBias<double>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t dimension,\n    const size_t dstSize[]) {\n  return dnnConvolutionCreateBackwardBias_F64(\n      pConvolution, attributes, algorithm, dimension, dstSize);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnGroupsConvolutionCreateForward(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type);\nC2_MKL_SPEC_PREFIX dnnError_t dnnGroupsConvolutionCreateForward<float>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnGroupsConvolutionCreateForward_F32(\n      pConvolution,\n      attributes,\n      algorithm,\n      groups,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnGroupsConvolutionCreateForward<double>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnGroupsConvolutionCreateForward_F64(\n      pConvolution,\n      attributes,\n      algorithm,\n      groups,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnGroupsConvolutionCreateForwardBias(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type);\nC2_MKL_SPEC_PREFIX dnnError_t dnnGroupsConvolutionCreateForwardBias<float>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnGroupsConvolutionCreateForwardBias_F32(\n      pConvolution,\n      attributes,\n      algorithm,\n      groups,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnGroupsConvolutionCreateForwardBias<double>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnGroupsConvolutionCreateForwardBias_F64(\n      pConvolution,\n      attributes,\n      algorithm,\n      groups,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnGroupsConvolutionCreateBackwardData(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type);\nC2_MKL_SPEC_PREFIX dnnError_t dnnGroupsConvolutionCreateBackwardData<float>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnGroupsConvolutionCreateBackwardData_F32(\n      pConvolution,\n      attributes,\n      algorithm,\n      groups,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnGroupsConvolutionCreateBackwardData<double>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnGroupsConvolutionCreateBackwardData_F64(\n      pConvolution,\n      attributes,\n      algorithm,\n      groups,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnGroupsConvolutionCreateBackwardFilter(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type);\nC2_MKL_SPEC_PREFIX dnnError_t dnnGroupsConvolutionCreateBackwardFilter<float>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnGroupsConvolutionCreateBackwardFilter_F32(\n      pConvolution,\n      attributes,\n      algorithm,\n      groups,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnGroupsConvolutionCreateBackwardFilter<double>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t srcSize[],\n    const size_t dstSize[],\n    const size_t filterSize[],\n    const size_t convolutionStrides[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnGroupsConvolutionCreateBackwardFilter_F64(\n      pConvolution,\n      attributes,\n      algorithm,\n      groups,\n      dimension,\n      srcSize,\n      dstSize,\n      filterSize,\n      convolutionStrides,\n      inputOffset,\n      border_type);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnGroupsConvolutionCreateBackwardBias(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t dstSize[]);\nC2_MKL_SPEC_PREFIX dnnError_t dnnGroupsConvolutionCreateBackwardBias<float>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t dstSize[]) {\n  return dnnGroupsConvolutionCreateBackwardBias_F32(\n      pConvolution, attributes, algorithm, groups, dimension, dstSize);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnGroupsConvolutionCreateBackwardBias<double>(\n    dnnPrimitive_t* pConvolution,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t algorithm,\n    size_t groups,\n    size_t dimension,\n    const size_t dstSize[]) {\n  return dnnGroupsConvolutionCreateBackwardBias_F64(\n      pConvolution, attributes, algorithm, groups, dimension, dstSize);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnReLUCreateForward(\n    dnnPrimitive_t* pRelu,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float negativeSlope);\nC2_MKL_SPEC_PREFIX dnnError_t dnnReLUCreateForward<float>(\n    dnnPrimitive_t* pRelu,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float negativeSlope) {\n  return dnnReLUCreateForward_F32(pRelu, attributes, dataLayout, negativeSlope);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnReLUCreateForward<double>(\n    dnnPrimitive_t* pRelu,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float negativeSlope) {\n  return dnnReLUCreateForward_F64(pRelu, attributes, dataLayout, negativeSlope);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnReLUCreateBackward(\n    dnnPrimitive_t* pRelu,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t diffLayout,\n    const dnnLayout_t dataLayout,\n    float negativeSlope);\nC2_MKL_SPEC_PREFIX dnnError_t dnnReLUCreateBackward<float>(\n    dnnPrimitive_t* pRelu,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t diffLayout,\n    const dnnLayout_t dataLayout,\n    float negativeSlope) {\n  return dnnReLUCreateBackward_F32(\n      pRelu, attributes, diffLayout, dataLayout, negativeSlope);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnReLUCreateBackward<double>(\n    dnnPrimitive_t* pRelu,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t diffLayout,\n    const dnnLayout_t dataLayout,\n    float negativeSlope) {\n  return dnnReLUCreateBackward_F64(\n      pRelu, attributes, diffLayout, dataLayout, negativeSlope);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnLRNCreateForward(\n    dnnPrimitive_t* pLrn,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    size_t kernel_size,\n    float alpha,\n    float beta,\n    float k);\nC2_MKL_SPEC_PREFIX dnnError_t dnnLRNCreateForward<float>(\n    dnnPrimitive_t* pLrn,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    size_t kernel_size,\n    float alpha,\n    float beta,\n    float k) {\n  return dnnLRNCreateForward_F32(\n      pLrn, attributes, dataLayout, kernel_size, alpha, beta, k);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnLRNCreateForward<double>(\n    dnnPrimitive_t* pLrn,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    size_t kernel_size,\n    float alpha,\n    float beta,\n    float k) {\n  return dnnLRNCreateForward_F64(\n      pLrn, attributes, dataLayout, kernel_size, alpha, beta, k);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnLRNCreateBackward(\n    dnnPrimitive_t* pLrn,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t diffLayout,\n    const dnnLayout_t dataLayout,\n    size_t kernel_size,\n    float alpha,\n    float beta,\n    float k);\nC2_MKL_SPEC_PREFIX dnnError_t dnnLRNCreateBackward<float>(\n    dnnPrimitive_t* pLrn,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t diffLayout,\n    const dnnLayout_t dataLayout,\n    size_t kernel_size,\n    float alpha,\n    float beta,\n    float k) {\n  return dnnLRNCreateBackward_F32(\n      pLrn, attributes, diffLayout, dataLayout, kernel_size, alpha, beta, k);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnLRNCreateBackward<double>(\n    dnnPrimitive_t* pLrn,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t diffLayout,\n    const dnnLayout_t dataLayout,\n    size_t kernel_size,\n    float alpha,\n    float beta,\n    float k) {\n  return dnnLRNCreateBackward_F64(\n      pLrn, attributes, diffLayout, dataLayout, kernel_size, alpha, beta, k);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnPoolingCreateForward(\n    dnnPrimitive_t* pPooling,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t op,\n    const dnnLayout_t srcLayout,\n    const size_t kernelSize[],\n    const size_t kernelStride[],\n    const int inputOffset[],\n    const dnnBorder_t border_type);\nC2_MKL_SPEC_PREFIX dnnError_t dnnPoolingCreateForward<float>(\n    dnnPrimitive_t* pPooling,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t op,\n    const dnnLayout_t srcLayout,\n    const size_t kernelSize[],\n    const size_t kernelStride[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnPoolingCreateForward_F32(\n      pPooling,\n      attributes,\n      op,\n      srcLayout,\n      kernelSize,\n      kernelStride,\n      inputOffset,\n      border_type);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnPoolingCreateForward<double>(\n    dnnPrimitive_t* pPooling,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t op,\n    const dnnLayout_t srcLayout,\n    const size_t kernelSize[],\n    const size_t kernelStride[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnPoolingCreateForward_F64(\n      pPooling,\n      attributes,\n      op,\n      srcLayout,\n      kernelSize,\n      kernelStride,\n      inputOffset,\n      border_type);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnPoolingCreateBackward(\n    dnnPrimitive_t* pPooling,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t op,\n    const dnnLayout_t srcLayout,\n    const size_t kernelSize[],\n    const size_t kernelStride[],\n    const int inputOffset[],\n    const dnnBorder_t border_type);\nC2_MKL_SPEC_PREFIX dnnError_t dnnPoolingCreateBackward<float>(\n    dnnPrimitive_t* pPooling,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t op,\n    const dnnLayout_t srcLayout,\n    const size_t kernelSize[],\n    const size_t kernelStride[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnPoolingCreateBackward_F32(\n      pPooling,\n      attributes,\n      op,\n      srcLayout,\n      kernelSize,\n      kernelStride,\n      inputOffset,\n      border_type);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnPoolingCreateBackward<double>(\n    dnnPrimitive_t* pPooling,\n    dnnPrimitiveAttributes_t attributes,\n    dnnAlgorithm_t op,\n    const dnnLayout_t srcLayout,\n    const size_t kernelSize[],\n    const size_t kernelStride[],\n    const int inputOffset[],\n    const dnnBorder_t border_type) {\n  return dnnPoolingCreateBackward_F64(\n      pPooling,\n      attributes,\n      op,\n      srcLayout,\n      kernelSize,\n      kernelStride,\n      inputOffset,\n      border_type);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnConcatCreate(\n    dnnPrimitive_t* pConcat,\n    dnnPrimitiveAttributes_t attributes,\n    const size_t N,\n    dnnLayout_t src[]);\nC2_MKL_SPEC_PREFIX dnnError_t dnnConcatCreate<float>(\n    dnnPrimitive_t* pConcat,\n    dnnPrimitiveAttributes_t attributes,\n    const size_t N,\n    dnnLayout_t src[]) {\n  return dnnConcatCreate_F32(pConcat, attributes, N, src);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnConcatCreate<double>(\n    dnnPrimitive_t* pConcat,\n    dnnPrimitiveAttributes_t attributes,\n    const size_t N,\n    dnnLayout_t src[]) {\n  return dnnConcatCreate_F64(pConcat, attributes, N, src);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnSplitCreate(\n    dnnPrimitive_t* pSplit,\n    dnnPrimitiveAttributes_t attributes,\n    const size_t N,\n    dnnLayout_t src,\n    size_t dst[]);\nC2_MKL_SPEC_PREFIX dnnError_t dnnSplitCreate<float>(\n    dnnPrimitive_t* pSplit,\n    dnnPrimitiveAttributes_t attributes,\n    const size_t N,\n    dnnLayout_t src,\n    size_t dst[]) {\n  return dnnSplitCreate_F32(pSplit, attributes, N, src, dst);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnSplitCreate<double>(\n    dnnPrimitive_t* pSplit,\n    dnnPrimitiveAttributes_t attributes,\n    const size_t N,\n    dnnLayout_t src,\n    size_t dst[]) {\n  return dnnSplitCreate_F64(pSplit, attributes, N, src, dst);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnSumCreate(\n    dnnPrimitive_t* pSum,\n    dnnPrimitiveAttributes_t attributes,\n    const size_t nSummands,\n    dnnLayout_t layout,\n    T* coefficients);\nC2_MKL_SPEC_PREFIX dnnError_t dnnSumCreate<float>(\n    dnnPrimitive_t* pSum,\n    dnnPrimitiveAttributes_t attributes,\n    const size_t nSummands,\n    dnnLayout_t layout,\n    float* coefficients) {\n  return dnnSumCreate_F32(pSum, attributes, nSummands, layout, coefficients);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnSumCreate<double>(\n    dnnPrimitive_t* pSum,\n    dnnPrimitiveAttributes_t attributes,\n    const size_t nSummands,\n    dnnLayout_t layout,\n    double* coefficients) {\n  return dnnSumCreate_F64(pSum, attributes, nSummands, layout, coefficients);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnBatchNormalizationCreateForward(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps);\nC2_MKL_SPEC_PREFIX dnnError_t dnnBatchNormalizationCreateForward<float>(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps) {\n  return dnnBatchNormalizationCreateForward_F32(\n      pBatchNormalization, attributes, dataLayout, eps);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnBatchNormalizationCreateForward<double>(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps) {\n  return dnnBatchNormalizationCreateForward_F64(\n      pBatchNormalization, attributes, dataLayout, eps);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnBatchNormalizationCreateBackwardData(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps);\nC2_MKL_SPEC_PREFIX dnnError_t dnnBatchNormalizationCreateBackwardData<float>(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps) {\n  return dnnBatchNormalizationCreateBackwardData_F32(\n      pBatchNormalization, attributes, dataLayout, eps);\n}\n\nC2_MKL_SPEC_PREFIX dnnError_t dnnBatchNormalizationCreateBackwardData<double>(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps) {\n  return dnnBatchNormalizationCreateBackwardData_F64(\n      pBatchNormalization, attributes, dataLayout, eps);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnBatchNormalizationCreateBackwardScaleShift(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps);\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnBatchNormalizationCreateBackwardScaleShift<float>(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps) {\n  return dnnBatchNormalizationCreateBackwardScaleShift_F32(\n      pBatchNormalization, attributes, dataLayout, eps);\n}\nC2_MKL_SPEC_PREFIX dnnError_t\ndnnBatchNormalizationCreateBackwardScaleShift<double>(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps) {\n  return dnnBatchNormalizationCreateBackwardScaleShift_F64(\n      pBatchNormalization, attributes, dataLayout, eps);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnBatchNormalizationCreateForward_v2(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps,\n    unsigned int flags);\nC2_MKL_SPEC_PREFIX dnnError_t dnnBatchNormalizationCreateForward_v2<float>(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps,\n    unsigned int flags) {\n  return dnnBatchNormalizationCreateForward_v2_F32(\n      pBatchNormalization, attributes, dataLayout, eps, flags);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnBatchNormalizationCreateForward_v2<double>(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps,\n    unsigned int flags) {\n  return dnnBatchNormalizationCreateForward_v2_F64(\n      pBatchNormalization, attributes, dataLayout, eps, flags);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnBatchNormalizationCreateBackward_v2(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps,\n    unsigned int flags);\nC2_MKL_SPEC_PREFIX dnnError_t dnnBatchNormalizationCreateBackward_v2<float>(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps,\n    unsigned int flags) {\n  return dnnBatchNormalizationCreateBackward_v2_F32(\n      pBatchNormalization, attributes, dataLayout, eps, flags);\n}\n\nC2_MKL_SPEC_PREFIX dnnError_t dnnBatchNormalizationCreateBackward_v2<double>(\n    dnnPrimitive_t* pBatchNormalization,\n    dnnPrimitiveAttributes_t attributes,\n    const dnnLayout_t dataLayout,\n    float eps,\n    unsigned int flags) {\n  return dnnBatchNormalizationCreateBackward_v2_F64(\n      pBatchNormalization, attributes, dataLayout, eps, flags);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnInnerProductCreateForward(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[],\n    size_t outputChannels);\nC2_MKL_SPEC_PREFIX dnnError_t dnnInnerProductCreateForward<float>(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[],\n    size_t outputChannels) {\n  return dnnInnerProductCreateForward_F32(\n      pInnerProduct, attributes, dimensions, srcSize, outputChannels);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnInnerProductCreateForward<double>(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[],\n    size_t outputChannels) {\n  return dnnInnerProductCreateForward_F64(\n      pInnerProduct, attributes, dimensions, srcSize, outputChannels);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnInnerProductCreateForwardBias(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[],\n    size_t outputChannels);\nC2_MKL_SPEC_PREFIX dnnError_t dnnInnerProductCreateForwardBias<float>(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[],\n    size_t outputChannels) {\n  return dnnInnerProductCreateForwardBias_F32(\n      pInnerProduct, attributes, dimensions, srcSize, outputChannels);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnInnerProductCreateForwardBias<double>(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[],\n    size_t outputChannels) {\n  return dnnInnerProductCreateForwardBias_F64(\n      pInnerProduct, attributes, dimensions, srcSize, outputChannels);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnInnerProductCreateBackwardData(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[],\n    size_t outputChannels);\nC2_MKL_SPEC_PREFIX dnnError_t dnnInnerProductCreateBackwardData<float>(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[],\n    size_t outputChannels) {\n  return dnnInnerProductCreateBackwardData_F32(\n      pInnerProduct, attributes, dimensions, srcSize, outputChannels);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnInnerProductCreateBackwardData<double>(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[],\n    size_t outputChannels) {\n  return dnnInnerProductCreateBackwardData_F64(\n      pInnerProduct, attributes, dimensions, srcSize, outputChannels);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnInnerProductCreateBackwardFilter(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[],\n    size_t outputChannels);\nC2_MKL_SPEC_PREFIX dnnError_t dnnInnerProductCreateBackwardFilter<float>(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[],\n    size_t outputChannels) {\n  return dnnInnerProductCreateBackwardFilter_F32(\n      pInnerProduct, attributes, dimensions, srcSize, outputChannels);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnInnerProductCreateBackwardFilter<double>(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[],\n    size_t outputChannels) {\n  return dnnInnerProductCreateBackwardFilter_F64(\n      pInnerProduct, attributes, dimensions, srcSize, outputChannels);\n}\n\nC2_MKL_TEMPLATE_PREFIX dnnError_t dnnInnerProductCreateBackwardBias(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[]);\nC2_MKL_SPEC_PREFIX dnnError_t dnnInnerProductCreateBackwardBias<float>(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[]) {\n  return dnnInnerProductCreateBackwardBias_F32(\n      pInnerProduct, attributes, dimensions, srcSize);\n}\nC2_MKL_SPEC_PREFIX dnnError_t dnnInnerProductCreateBackwardBias<double>(\n    dnnPrimitive_t* pInnerProduct,\n    dnnPrimitiveAttributes_t attributes,\n    size_t dimensions,\n    const size_t srcSize[]) {\n  return dnnInnerProductCreateBackwardBias_F64(\n      pInnerProduct, attributes, dimensions, srcSize);\n}\n\n} // namespace mkl\n} // namespace caffe2\n\n// Undef macros to make sure that things are clean.\n#undef C2_MKL_TEMPLATE_PREFIX\n#undef C2_MKL_SPEC_PREFIX\n\n#endif // CAFFE2_UTILS_MKL_MKL_DNN_CPPWRAPPER_H\n"
  },
  {
    "path": "caffe2/mkl/utils/mkl_memory.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/mkl/mkl_utils.h\"\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/tensor.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nCAFFE2_DEFINE_bool(\n    caffe2_mkl_implicit_layout_change, false,\n    \"Controls the behavior when we call View() on an MKLMemory: if it is set \"\n    \"true, then the View() function will actually change the underlying \"\n    \"storage. If it is set false, an implicit copy is triggered but the \"\n    \"original storage is not affected.\"\n    );\n\nnamespace caffe2 {\n\nCAFFE_KNOWN_TYPE(mkl::MKLMemory<float>);\nCAFFE_KNOWN_TYPE(mkl::MKLMemory<double>);\n\ntemplate <typename T>\nstatic vector<TIndex> GetMKLTensorInfo(\n    const void* c,\n    bool* shares_data,\n    size_t* capacity,\n    DeviceOption* device) {\n  const mkl::MKLMemory<T>* tc = static_cast<const mkl::MKLMemory<T>*>(c);\n  // it's too hard to get sharing info from mkl::MKLMemory\n  *shares_data = false;\n  *capacity = tc->size() * sizeof(T);\n  device->set_device_type(MKLDNN);\n  device->set_cuda_gpu_id(0);\n  return tc->dims();\n}\n\ntemplate <typename T>\nstatic TypeMeta GetMKLTensorType(const void*) {\n  return TypeMeta::Make<T>();\n}\n\ntemplate <typename T>\nstatic void RegisterForType() {\n  RegisterTypeCallFunction(\n      TypeMeta::Id<mkl::MKLMemory<T>>(), GetMKLTensorType<T>);\n  RegisterTensorInfoFunction(\n      TypeMeta::Id<mkl::MKLMemory<T>>(), GetMKLTensorInfo<T>);\n}\n\nstatic bool Caffe2InitializeMKL(int*, char***) {\n  RegisterForType<float>();\n  RegisterForType<double>();\n  return true;\n}\n\nREGISTER_CAFFE2_INIT_FUNCTION(\n    InitMKLDNNContext,\n    &Caffe2InitializeMKL,\n    \"Register wrappers for MKLContext\");\n\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/mkl/utils/mkl_memory.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_MKL_MKL_MEMORY_H_\n#define CAFFE2_UTILS_MKL_MKL_MEMORY_H_\n\n#include <string>\n#include <vector>\n#include <mutex>\n\n#include \"caffe2/core/flags.h\" // for TIndex\n#include \"caffe2/core/tensor.h\" // for TIndex\n#include \"caffe2/mkl/utils/mkl_dnn_cppwrapper.h\"\n\n// A global boolean variable that controls the behavior when we call View() on\n// an MKLMemory: if it is set true, then the View() function will actually\n// change the underlying storage. If it is set false, an implicit copy is\n// triggered but the original storage is not affected.\nCAFFE2_DECLARE_bool(caffe2_mkl_implicit_layout_change);\n\nnamespace caffe2 {\nnamespace mkl {\n\ntemplate <typename T>\nclass PrimitiveWrapper {\n public:\n  PrimitiveWrapper() {}\n  // Creates a primitive wrapper from an existing primitive. The wrapper\n  // takes over ownership.\n  explicit PrimitiveWrapper(dnnPrimitive_t primitive) : primitive_(primitive) {}\n\n  template <typename Creator, typename FirstArg, typename... Args>\n  PrimitiveWrapper(Creator creator, FirstArg&& arg, Args&&... args) {\n    creator(&primitive_, arg, args...);\n  }\n\n  ~PrimitiveWrapper() {\n    if (primitive_) {\n      MKLDNN_CHECK(dnnDelete<T>(primitive_));\n    }\n  }\n\n  template <typename Creator, typename... Args>\n  void Reset(Creator creator, Args&&... args) {\n    if (primitive_) {\n      MKLDNN_SAFE_CALL(dnnDelete<T>(primitive_));\n    }\n    creator(&primitive_, args...);\n  }\n\n  void Reset() {\n    if (primitive_) {\n      MKLDNN_SAFE_CALL(dnnDelete<T>(primitive_));\n      primitive_ = nullptr;\n    }\n  }\n\n  operator dnnPrimitive_t() const {\n    return primitive_;\n  }\n\n private:\n  dnnPrimitive_t primitive_ = 0;\n  DISABLE_COPY_AND_ASSIGN(PrimitiveWrapper);\n};\n\ntemplate <typename T>\nclass LayoutWrapper {\n public:\n  LayoutWrapper() {}\n  // Create a user layout from a TensorCPU with the given shapes.\n  explicit LayoutWrapper(const TensorCPU& tensor) {\n    Reset(tensor);\n  }\n\n  // Create an internal layout from the primitive and type.\n  LayoutWrapper(const dnnPrimitive_t primitive, const dnnResourceType_t type) {\n    Reset(primitive, type);\n  }\n\n  // Create a user layout from the given dimension, size and strides.\n  LayoutWrapper(\n      const size_t dimension,\n      const size_t size[],\n      const size_t strides[]) {\n    Reset(dimension, size, strides);\n  }\n\n  // Destructs the layout wrapper.\n  ~LayoutWrapper() {\n    if (layout_)\n      MKLDNN_CHECK(dnnLayoutDelete<T>(layout_));\n  }\n\n  // Create a user layout from a TensorCPU with the given shapes.\n  void Reset(const TensorCPU& tensor) {\n    if (layout_)\n      MKLDNN_CHECK(dnnLayoutDelete<T>(layout_));\n    CAFFE_ENFORCE(tensor.size(), \"Cannot reset with an empty tensor.\");\n    size_t dimension = tensor.ndim();\n    size_t size[dimension];\n    size_t strides[dimension];\n    for (int i = 0; i < dimension; ++i) {\n      size[i] = tensor.dim(dimension - i - 1);\n      strides[i] = (i == 0) ? 1 : strides[i - 1] * size[i - 1];\n    }\n    MKLDNN_SAFE_CALL(dnnLayoutCreate<T>(&layout_, dimension, size, strides));\n  }\n\n  // Create an internal layout from the primitive and type.\n  void Reset(const dnnPrimitive_t primitive, const dnnResourceType_t type) {\n    CAFFE_ENFORCE(primitive, \"Cannot reset with an unknwon primitive.\");\n    CAFFE_ENFORCE(\n        type != dnnResourceNumber,\n        \"Cannot reset with an unknown resource number.\");\n    if (layout_) {\n      MKLDNN_CHECK(dnnLayoutDelete<T>(layout_));\n    }\n    MKLDNN_SAFE_CALL(\n        dnnLayoutCreateFromPrimitive<T>(&layout_, primitive, type));\n  }\n\n  // Create a user layout from the given dimension, size and strides.\n  void\n  Reset(const size_t dimension, const size_t size[], const size_t strides[]) {\n    if (layout_)\n      MKLDNN_CHECK(dnnLayoutDelete<T>(layout_));\n    MKLDNN_SAFE_CALL(dnnLayoutCreate<T>(&layout_, dimension, size, strides));\n  }\n\n  void Reset() {\n    if (layout_) {\n      MKLDNN_CHECK(dnnLayoutDelete<T>(layout_));\n      layout_ = nullptr;\n    }\n  }\n\n  operator dnnLayout_t() const {\n    return layout_;\n  }\n\n private:\n  dnnLayout_t layout_ = 0;\n  DISABLE_COPY_AND_ASSIGN(LayoutWrapper);\n};\n\n/**\n * @brief A wrapper around an opaque MKL internal resource that has certain\n * layouts and convertion primitives set up.\n *\n * Most of the MKLMemory functions are not thread safe.\n */\ntemplate <typename T>\nclass MKLMemory {\n public:\n  // Initializes an empty MKLMemory.\n  MKLMemory() {}\n  // Initialize an MKLMemory with the given size, strides, dnn\n  // primitive and type.\n  MKLMemory(\n      const size_t dimension,\n      const size_t size[],\n      const size_t strides[],\n      const dnnPrimitive_t primitive = nullptr,\n      const dnnResourceType_t type = dnnResourceNumber,\n      bool share_mem_if_possible = false) {\n    Reset(dimension, size, strides, primitive, type, share_mem_if_possible);\n  }\n\n  // Initialize an MKLMemory, with the given dimension assuming a C-contiguous\n  // storage.\n  template <typename IndexType>\n  explicit MKLMemory(\n      const vector<IndexType>& dims,\n      const dnnPrimitive_t primitive = nullptr,\n      const dnnResourceType_t type = dnnResourceNumber,\n      bool share_mem_if_possible = false) {\n    Reset(dims, primitive, type, share_mem_if_possible);\n  }\n\n  // Initialize an MKLMemory with the given size, strides, dnn\n  // primitive and type.\n  void Reset(\n      const size_t dimension,\n      const size_t size[],\n      const size_t strides[],\n      const dnnPrimitive_t primitive = nullptr,\n      const dnnResourceType_t type = dnnResourceNumber,\n      bool share_mem_if_possible = false) {\n    buffer_.reset();\n    dims_.resize(dimension);\n    size_ = 1;\n    for (int i = 0; i < dimension; ++i) {\n      dims_[i] = size[dimension - 1 - i];\n      size_ *= dims_[i];\n    }\n    user_layout_.Reset(dimension, size, strides);\n    if (primitive) {\n      layout_.Reset(primitive, type);\n    } else {\n      layout_.Reset(dimension, size, strides);\n    }\n    convert_in_.Reset(dnnConversionCreate<T>, user_layout_, layout_);\n    convert_out_.Reset(dnnConversionCreate<T>, layout_, user_layout_);\n    share_mem_if_possible_ = share_mem_if_possible;\n    layout_is_user_layout_ = dnnLayoutCompare<T>(layout_, user_layout_);\n    VLOG(2) << \"layout is user layout? \" << layout_is_user_layout_;\n    if (!share_mem_if_possible_) {\n      // If we are not going to share memory, we will simply allocate\n      // memory upfront.\n      buffer();\n    }\n  }\n\n  // Initialize an MKLMemory, with the given dimension assuming a C-contiguous\n  // storage.\n  template <typename IndexType>\n  void Reset(\n      const vector<IndexType>& dims,\n      const dnnPrimitive_t primitive = nullptr,\n      const dnnResourceType_t type = dnnResourceNumber,\n      bool share_mem_if_possible = false) {\n    buffer_.reset();\n    dims_.resize(dims.size());\n    size_ = 1;\n    for (int i = 0; i < dims.size(); ++i) {\n      dims_[i] = dims[i];\n      size_ *= dims_[i];\n    }\n    size_t dimension = dims.size();\n    vector<size_t> size(dimension);\n    vector<size_t> strides(dimension);\n    for (int i = 0; i < dimension; ++i) {\n      size[i] = dims[dimension - i - 1];\n      strides[i] = (i == 0) ? 1 : strides[i - 1] * size[i - 1];\n    }\n    user_layout_.Reset(dims.size(), size.data(), strides.data());\n    if (primitive) {\n      layout_.Reset(primitive, type);\n    } else {\n      layout_.Reset(dimension, size.data(), strides.data());\n    }\n    convert_in_.Reset(dnnConversionCreate<T>, user_layout_, layout_);\n    convert_out_.Reset(dnnConversionCreate<T>, layout_, user_layout_);\n    share_mem_if_possible_ = share_mem_if_possible;\n    layout_is_user_layout_ = dnnLayoutCompare<T>(layout_, user_layout_);\n    VLOG(2) << \"layout is user layout? \" << layout_is_user_layout_;\n    if (!share_mem_if_possible_) {\n      // If we are not going to share memory, we will simply allocate\n      // memory upfront.\n      buffer();\n    }\n  }\n\n  void Reset() {\n    buffer_.reset();\n    dims_.clear();\n    size_ = 0;\n    user_layout_.Reset();\n    layout_.Reset();\n    convert_in_.Reset();\n    convert_out_.Reset();\n  }\n\n  /**\n   * Resizes the tensor without touching underlying storage.\n   * This requires the total size of the tensor to remains constant.\n   */\n  template <typename IndexType>\n  void Reshape(const vector<IndexType>& dims) {\n    CAFFE_ENFORCE(\n        layout_is_user_layout_,\n        \"Reshape is not allowed for custom layouts. \"\n        \"Convert to plain layout before invoking Reshape().\");\n\n    TIndex new_size = 1;\n    for (auto i = 0; i < dims.size(); ++i) {\n      CAFFE_ENFORCE_GE_WITH_CALLER(dims[i], 0);\n      new_size *= dims[i];\n    }\n    CAFFE_ENFORCE_WITH_CALLER(\n        new_size == size_,\n        \"New size and old size are not equal. Reshape is not possible.\");\n\n    vector<TIndex> new_dims(dims.size());\n    vector<size_t> size(dims.size());\n    vector<size_t> strides(dims.size());\n    for (int i = 0; i < dims.size(); ++i) {\n      new_dims[i] = dims[i];\n      size[i] = dims[dims.size() - i - 1];\n      strides[i] = (i == 0) ? 1 : strides[i - 1] * size[i - 1];\n    }\n    dims_ = new_dims;\n    user_layout_.Reset(dims.size(), size.data(), strides.data());\n    layout_.Reset(dims.size(), size.data(), strides.data());\n    convert_in_.Reset(dnnConversionCreate<T>, user_layout_, layout_);\n    convert_out_.Reset(dnnConversionCreate<T>, layout_, user_layout_);\n  }\n\n  // Destructs the MKLMemory.\n  ~MKLMemory() {}\n\n  void CopyFrom(const void* ptr) {\n    if (share_mem_if_possible_ && layout_is_user_layout_) {\n      VLOG(2) << \"Sharing underlying memory and skip copy.\";\n      buffer_.reset(const_cast<void*>(ptr), [](void*) -> void {});\n    } else if (size_ == 0) {\n      VLOG(2) << \"Cannot copy into empty MKL buffer.\";\n    } else {\n      VLOG(2) << \"Copying external content.\";\n      MKLDNN_SAFE_CALL(dnnConversionExecute<T>(\n          convert_in_, const_cast<void*>(ptr), buffer()));\n    }\n  }\n\n  void CopyFrom(const TensorCPU& tensor) {\n    CAFFE_ENFORCE_EQ(\n        tensor.dims(),\n        dims_,\n        \"Dims does not match the expected dims of the resource.\");\n    CopyFrom(tensor.template data<T>());\n  }\n\n  void CopyFrom(const MKLMemory<T>& other) {\n    CAFFE_ENFORCE_EQ(\n        other.dims(),\n        dims_,\n        \"Dims does not match the expected dims of the resource.\");\n\n    if (share_mem_if_possible_ && dnnLayoutCompare<T>(other.layout_, layout_)) {\n      buffer_ = other.buffer_;\n    } else if (size_ == 0) {\n      VLOG(2) << \"Cannot copy between empty MKL buffers\";\n    } else {\n      PrimitiveWrapper<T> convert(\n          dnnConversionCreate<T>, other.layout_, layout_);\n      MKLDNN_SAFE_CALL(\n          dnnConversionExecute<T>(convert, other.buffer(), buffer()));\n    }\n  }\n\n  bool ShareFromRaw(const void* ptr) {\n    if (share_mem_if_possible_ && layout_is_user_layout_) {\n      buffer_.reset(const_cast<void*>(ptr), [](void*) -> void {});\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  bool ShareFromTensor(const TensorCPU& tensor) {\n    CAFFE_ENFORCE_EQ(\n        tensor.dims(),\n        dims_,\n        \"Dims does not match the expected dims of the resource.\");\n    return ShareFromRaw(tensor.template data<T>());\n  }\n\n  bool ShareFrom(const MKLMemory<T>& other) {\n    if (share_mem_if_possible_ && dnnLayoutCompare<T>(other.layout_, layout_)) {\n      VLOG(2) << \"Sharing underlying memory.\";\n      buffer_ = other.buffer_;\n      if (!buffer_.get()) {\n        VLOG(2) << \"Warning: the source MKLMemory has no content yet, so the \"\n                   \"sharing actually has no effect.\";\n      }\n      return true;\n    } else {\n      VLOG(2) << \"Not sharing underlying memory.\";\n      return false;\n    }\n  }\n\n  void CopyTo(void* ptr) const {\n    if (buffer_.get() == ptr) {\n      // This is already mapping to the same memory region. Skip copy.\n      VLOG(2) << \"CopyTo does not need actual copying, as we are sharing \"\n                 \"memory with the output.\";\n      return;\n    }\n    CAFFE_ENFORCE(\n        buffer_.get(), \"Canot copy out from an uninitialized MKLMemory.\");\n    VLOG(2) << \"Copy to external memory.\";\n    MKLDNN_SAFE_CALL(dnnConversionExecute<T>(convert_out_, buffer_.get(), ptr));\n  }\n\n  void CopyTo(TensorCPU* tensor) const {\n    if (tensor->size() > 0 && buffer_.get() == tensor->mutable_data<T>()) {\n      // This is already mapping to the same memory region. Skip copy.\n      VLOG(2) << \"CopyTo does not need actual copying, as we are sharing \"\n                 \"memory with the output.\";\n      return;\n    }\n    tensor->Resize(dims_);\n    CopyTo(tensor->mutable_data<T>());\n  }\n\n  // Copies to another MKL memory.\n  //\n  // This function\n  void CopyTo(\n      MKLMemory<T>* other,\n      const dnnPrimitive_t primitive = nullptr,\n      const dnnResourceType_t type = dnnResourceNumber) {\n    if (buffer_ && buffer_.get() == other->buffer_.get()) {\n      CAFFE_ENFORCE(\n          dnnLayoutCompare<T>(other->layout_, layout_),\n          \"MKLMemory layout does not match, despite in-place buffers\");\n      CAFFE_ENFORCE(\n          other->dims() == dims(),\n          \"MKLMemory dimensions do not match, despite in-place buffers\");\n      VLOG(2) << \"CopyTo does not need actual copying, as we are sharing \"\n                 \"memory with the output.\";\n      // This is already mapping to the same memory region. Skip copy.\n      return;\n    }\n    // TODO(jiayq): if primitive creation is a big overhead and we will be\n    // consistently copying stuff with fixed src and dst layouts, consider\n    // making a cache for the primitive below.\n    VLOG(2) << \"CopyTo requires copying. Performing direct copy.\";\n    if (dims() != other->dims()) {\n      other->Reset(dims(), primitive, type);\n    }\n    if (size_ == 0) {\n      VLOG(2) << \"Cannot copy between empty MKL buffers.\";\n      return;\n    }\n    CAFFE_ENFORCE(\n        buffer_.get(), \"Cannot copy out from an uninitialized MKLMemory.\");\n    PrimitiveWrapper<T> convert(\n        dnnConversionCreate<T>, layout_, other->layout_);\n    MKLDNN_SAFE_CALL(\n        dnnConversionExecute<T>(convert, buffer_.get(), other->buffer()));\n  }\n\n  inline void* buffer() {\n    if (buffer_ == nullptr) {\n      CAFFE_ENFORCE(\n          layout_ != nullptr, \"Trying to allocate buffer but layout is empty.\");\n      if (size_ == 0) {\n        VLOG(2) << \"Cannot allocate empty MKL buffer.\";\n        return buffer_.get();\n      }\n      void* allocated = nullptr;\n      MKLDNN_SAFE_CALL(dnnAllocateBuffer<T>(&allocated, layout_));\n      buffer_.reset(allocated, [](void* ptr) -> void {\n        MKLDNN_CHECK(dnnReleaseBuffer<T>(ptr));\n      });\n    }\n    return buffer_.get();\n  }\n\n  // MKLDNN does not use const void* even for the inputs, so we will\n  // have to use void* and rely on the underlying implementation to make\n  // sure that the buffer is actually not changed.\n  inline void* buffer() const {\n    CAFFE_ENFORCE(\n        buffer_ != nullptr, \"Trying to refer to an unallocated buffer.\");\n    return buffer_.get();\n  }\n\n  inline const vector<TIndex>& dims() const {\n    return dims_;\n  }\n\n  inline const int ndim() const { return dims_.size(); }\n\n  inline int dim32(const int i) const {\n    CAFFE_ENFORCE_LT(dims_.at(i), std::numeric_limits<int>::max());\n    return static_cast<int>(dims_[i]);\n  }\n\n  /**\n   * Returns the size (i.e., the number of items) in the buffer.\n   */\n  inline TIndex size() const {\n    return size_;\n  }\n\n  /**\n   * Returns the i-th dimension of the tensor. Note that the passed in index\n   * must be between 0 (inclusive) and the number of dimensions, otherwise\n   * this function will produce a fatal message.\n   */\n  inline TIndex dim(const int i) const {\n    return dims_.at(i);\n  }\n\n  inline const LayoutWrapper<T>& layout() const {\n    return layout_;\n  }\n\n  inline bool is_user_layout() const {\n    return layout_is_user_layout_;\n  }\n\n  // Returns a view of the content. We mark this function const, but be noted\n  // that the returned std::shared_ptr is not const protected - user discretion\n  // is recommended for correctness.\n  std::shared_ptr<void> View(\n      dnnLayout_t layout_wanted,\n      dnnPrimitive_t primitive = nullptr,\n      dnnResourceType_t type = dnnResourceNumber) const {\n    std::lock_guard<std::mutex> lock(buffer_lock_);\n    if (dnnLayoutCompare<T>(layout_wanted, layout_)) {\n      // If they are the same, return the original content.\n      VLOG(2) << \"Creating a view without the need of copying.\";\n      return std::shared_ptr<void>(buffer_);\n    } else {\n      void* temp_buffer;\n      VLOG(2) << \"Creating a view with copying.\";\n      MKLDNN_SAFE_CALL(dnnAllocateBuffer<T>(&temp_buffer, layout_wanted));\n      PrimitiveWrapper<T> convert(\n          dnnConversionCreate<T>, layout_, layout_wanted);\n      MKLDNN_SAFE_CALL(dnnConversionExecute<T>(\n          convert, buffer_.get(), temp_buffer));\n      if (primitive && FLAGS_caffe2_mkl_implicit_layout_change) {\n        VLOG(2) << \"Implicit layout change set. \"\n                   \"Changing the underlying storage.\";\n        // We will need to call Reset to set up all the member variables.\n        // This is not thread safe, so we might want to double check if this\n        // makes sense in actual use cases.\n        const_cast<MKLMemory<T>*>(this)->Reset(\n            dims_, primitive, type, share_mem_if_possible_);\n        CAFFE_ENFORCE(dnnLayoutCompare<T>(layout_wanted, layout_),\n                      \"You passed in a target layout that is not \"\n                      \"generated by the given primitive and type.\");\n        buffer_.reset(temp_buffer, [](void* ptr) -> void {\n                MKLDNN_CHECK(dnnReleaseBuffer<T>(ptr));\n            });\n        return std::shared_ptr<void>(buffer_);\n      } else {\n        return std::shared_ptr<void>(temp_buffer, [](void* ptr) -> void {\n                MKLDNN_CHECK(dnnReleaseBuffer<T>(ptr));\n            });\n      }\n    }\n  }\n\n private:\n  bool share_mem_if_possible_;\n  bool layout_is_user_layout_;\n  // The internal buffer in the specific dnn layout.\n  // It is marked mutable but any modification in a const function should\n  // be accompanied by the buffer lock, see the View() function.\n  mutable std::shared_ptr<void> buffer_;\n  // A mutex to control the access of buffer in the View() function.\n  mutable std::mutex buffer_lock_;\n  // The dimensions in the same order as Caffe2 does. This is used to\n  // interface with C2.\n  vector<TIndex> dims_;\n  // Number of items in the buffer.\n  TIndex size_ = -1;\n  // The user dnn layout.\n  LayoutWrapper<T> user_layout_;\n  // The internal dnn layout.\n  LayoutWrapper<T> layout_;\n  // The primitive to use to convert from user layout to internal layout\n  PrimitiveWrapper<T> convert_in_;\n  // The primitive to use to convert from internal layout to user layout\n  PrimitiveWrapper<T> convert_out_;\n\n  DISABLE_COPY_AND_ASSIGN(MKLMemory);\n};\n\ntemplate <typename T>\nclass MKLWorkspace {\n public:\n  MKLWorkspace(const LayoutWrapper<T>& layout) {\n    MKLDNN_SAFE_CALL(mkl::dnnAllocateBuffer<T>(&buffer_, layout));\n  }\n  ~MKLWorkspace() {\n    dnnReleaseBuffer<T>(buffer_);\n  }\n  T* buffer() {\n    return reinterpret_cast<T*>(buffer_);\n  }\n\n private:\n  void* buffer_;\n  DISABLE_COPY_AND_ASSIGN(MKLWorkspace);\n};\n\n} // namespace mkl\n} // namespace caffe2\n\n#endif // CAFFE2_UTILS_MKL_MKL_MEMORY_H_\n"
  },
  {
    "path": "caffe2/mkl/utils/mkl_operator.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_MKL_OPERATOR_H_\n#define CAFFE2_UTILS_MKL_OPERATOR_H_\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/mkl/utils/mkl_dnn_cppwrapper.h\"\n#include \"caffe2/mkl/utils/mkl_memory.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nCAFFE2_DECLARE_bool(caffe2_mkl_memonger_in_use);\n\nnamespace caffe2 {\n\nCAFFE_DECLARE_REGISTRY(\n    MKLOperatorRegistry,\n    OperatorBase,\n    const OperatorDef&,\n    Workspace*);\n#define REGISTER_MKL_OPERATOR_CREATOR(key, ...) \\\n  CAFFE_REGISTER_CREATOR(MKLOperatorRegistry, key, __VA_ARGS__)\n#define REGISTER_MKL_OPERATOR(name, ...) \\\n  CAFFE_REGISTER_CLASS(MKLOperatorRegistry, name, __VA_ARGS__)\n#define REGISTER_MKL_OPERATOR_STR(str_name, ...) \\\n  CAFFE_REGISTER_TYPED_CLASS(MKLOperatorRegistry, str_name, __VA_ARGS__)\n\n#define REGISTER_MKL_OPERATOR_WITH_ENGINE(name, engine, ...) \\\n  CAFFE_REGISTER_CLASS(MKLOperatorRegistry, name##_ENGINE_##engine, __VA_ARGS__)\n\nnamespace mkl {\n// MKLOperator is the base scaffolding of the operators that uses MKLDNN. It\n// provides a few operators that are useful to MKLDNN specific implementations.\ntemplate <typename T>\nclass MKLOperator : public OperatorBase {\n public:\n  explicit MKLOperator(const OperatorDef& operator_def, Workspace* ws)\n      : OperatorBase(operator_def, ws),\n        context_(operator_def.device_option()) {}\n  virtual ~MKLOperator() {}\n\n  inline const MKLMemory<T>& Input(int idx) {\n    return OperatorBase::template Input<MKLMemory<T>>(idx);\n  }\n  inline MKLMemory<T>* Output(int idx) {\n    return OperatorBase::template Output<MKLMemory<T>>(idx);\n  }\n\n  // The run function of Operator switches to the device, and then carries out\n  // the actual computation with RunOnDevice(). You should implement RunOnDevice\n  // instead of Run().\n  bool Run(int /* unused */ /*stream_id*/) final {\n    // Since MKLDNN does not need to do SwithToDevice and\n    // FinishDeviceComputation,\n    // it is always just a re-route to RunOnDevice().\n    try {\n      return RunOnDevice();\n    } catch (EnforceNotMet& err) {\n      err.AppendMessage(getErrorMsg());\n      throw;\n    }\n  }\n\n  // Waits for a previous event. Note that to properly wait and run\n  // asynchronously, WaitEvent, RunAsync and Record should all be executed\n  // on the same CPU thread.\n  void WaitEvent(const Event& ev, int /* unused */) final {\n    context_.WaitEvent(ev);\n  }\n\n  void WaitEvents(const std::vector<const Event*>& events, int /* unused */)\n      final {\n    for (const auto& ev : events) {\n      context_.WaitEvent(*ev);\n    }\n  }\n\n  void RecordEvent(const char* err_msg = nullptr) final {\n    if (event_) {\n      context_.Record(event_.get(), err_msg);\n    }\n  }\n\n  virtual bool RunOnDevice() = 0;\n\n  inline void ExecutePrimitive() {\n    MKLDNN_SAFE_CALL(mkl::dnnExecute<T>(primitive_, resources_));\n  }\n\n protected:\n  std::string getErrorMsg() {\n    if (has_debug_def()) {\n      return \"Error from operator: \" + ProtoDebugString(debug_def());\n    } else {\n      return \"Error from operator: no op def\";\n    }\n  }\n\n  MKLContext context_;\n  // The primitive used in the operator.\n  PrimitiveWrapper<T> primitive_;\n  // Size cache for all the input sizes.\n  vector<vector<TIndex>> input_size_cache_;\n  // An internal MKLMemory buffer. This is usually handy when we have a\n  // single output from the operator. If your operator has multiple outputs\n  // then you should allocate your own buffer.\n  MKLMemory<T> buffer_;\n  // The resources vector that we will need to use;\n  void* resources_[dnnResourceNumber];\n};\n} // namespace mkl\n\n#define USE_MKLOPERATOR_FUNCTIONS(T)                            \\\n  USE_OPERATOR_BASE_FUNCTIONS;                                  \\\n  /* using override */ using MKLOperator<T>::Input;             \\\n  /* using override */ using MKLOperator<T>::Output;            \\\n  /* using override */ using MKLOperator<T>::ExecutePrimitive;  \\\n  /* using override */ using MKLOperator<T>::primitive_;        \\\n  /* using override */ using MKLOperator<T>::input_size_cache_; \\\n  /* using override */ using MKLOperator<T>::buffer_;           \\\n  /* using override */ using MKLOperator<T>::resources_\n\n#define USE_SIMPLE_MKL_CTOR_DTOR(name, T)              \\\n  name(const OperatorDef& operator_def, Workspace* ws) \\\n      : MKLOperator<T>(operator_def, ws) {}            \\\n  virtual ~name() {}\n\n} // namespace caffe2\n\n#endif // CAFFE2_UTILS_MKL_OPERATOR_H_\n"
  },
  {
    "path": "caffe2/mkl/utils/mkl_version_check.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_MKL_MKL_VERSION_CHECK_H_\n#define CAFFE2_UTILS_MKL_MKL_VERSION_CHECK_H_\n#ifdef CAFFE2_USE_MKL\n\n#include <mkl.h>\n\n#if INTEL_MKL_VERSION >= 20170000\n#define CAFFE2_HAS_MKL_SGEMM_PACK\n#define CAFFE2_HAS_MKL_DNN\n#endif // INTEL_MKL_VERSION >= 20170000\n\n#endif // CAFFE2_USE_MKL\n#endif // CAFFE2_UTILS_MKL_MKL_VERSION_CHECK_H_\n"
  },
  {
    "path": "caffe2/mkl/utils/sgemm_pack.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_MKL_SGEMM_PACK_H_\n#define CAFFE2_UTILS_MKL_SGEMM_PACK_H_\n\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\nnamespace mkl {\nstruct MKLPackedMatrix {\n  CBLAS_IDENTIFIER identifier_;\n  CBLAS_TRANSPOSE trans_;\n  int m_;\n  int n_;\n  int k_;\n  float alpha_;\n  int ld_;\n  float* data_ = nullptr;\n\n  MKLPackedMatrix(\n      const CBLAS_IDENTIFIER identifier,\n      const CBLAS_TRANSPOSE trans,\n      const int m,\n      const int n,\n      const int k,\n      const float alpha,\n      const float* src,\n      const int ld)\n      : identifier_(identifier),\n        trans_(trans),\n        m_(m),\n        n_(n),\n        k_(k),\n        alpha_(alpha),\n        ld_(ld) {\n    data_ = cblas_sgemm_alloc(identifier, m, n, k);\n    CAFFE_ENFORCE(data_, \"MKL runtime error: cannot allocate sgemm memory.\");\n    cblas_sgemm_pack(\n        CblasRowMajor, identifier, trans, m, n, k, alpha, src, ld, data_);\n  }\n\n  ~MKLPackedMatrix() {\n    if (data_) {\n      cblas_sgemm_free(data_);\n    }\n  }\n};\n\n} // namespace mkl\n} // namespace caffe2\n\n#endif // CAFFE2_UTILS_MKL_SGEMM_PACK_H_\n"
  },
  {
    "path": "caffe2/mobile/CMakeLists.txt",
    "content": "add_subdirectory(contrib)\n\n# CPU source, test sources, binary sources\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_BINARY_SRCS ${Caffe2_CPU_BINARY_SRCS} PARENT_SCOPE)\n\n# GPU source, test sources, binary sources\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_BINARY_SRCS ${Caffe2_GPU_BINARY_SRCS} PARENT_SCOPE)"
  },
  {
    "path": "caffe2/mobile/contrib/CMakeLists.txt",
    "content": "add_subdirectory(ios)\nadd_subdirectory(opengl)\nif (USE_ACL)\n  add_subdirectory(arm-compute)\nendif()\n# Finally pass the src lists back to the parent\n\nif (USE_NNAPI)\n  add_subdirectory(nnapi)\nendif()\n\n# CPU source, test sources, binary sources\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_BINARY_SRCS ${Caffe2_CPU_BINARY_SRCS} PARENT_SCOPE)\n\n# GPU source, test sources, binary sources\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_BINARY_SRCS ${Caffe2_GPU_BINARY_SRCS} PARENT_SCOPE)"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/CMakeLists.txt",
    "content": "add_subdirectory(core)\nadd_subdirectory(operators)\nadd_subdirectory(test)\n\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/README.md",
    "content": "# Caffe2 - ARM Compute Backend\n\n## Build\n\nTo build, clone and install scons\n\n```\nbrew install scons\n```\n\nset ANDROID_NDK to /opt/android_ndk/xxx(e.g. /opt/android_ndk/android-ndk-r15c/)\n\nsetup toolchain:\nLet's say $PATH_TO_TOOLCHAIN is the directory you want to store the toolchain files.\n\narm\n```\nrm -rf $PATH_TO_TOOLCHAIN\n$ANDROID_NDK/build/tools/make_standalone_toolchain.py --arch arm --api 21 --install-dir $PATH_TO_TOOLCHAIN\n```\n\narm64\n```\nrm -rf PATH_TO_TOOLCHAIN\n$ANDROID_NDK/build/tools/make_standalone_toolchain.py --arch arm64 --api 21 --install-dir $PATH_TO_TOOLCHAIN\n```\n\nadd the toolchain path to .bashrc/.zshrc etc.\ne.g.\n```\nexport PATH=$PATH:$PATH_TO_TOOLCHAIN/bin\n```\n\nuse the build\\_android.sh:\n\nfor 32-bit ARM\n```\n./scripts/build_android.sh -DUSE_ACL=ON -DBUILD_TEST=ON\n```\n\nfor 64-bit ARM\n```\n./scripts/build_android.sh -DUSE_ACL=ON -DBUILD_TEST=ON -DANDROID_ABI=arm64-v8a -DANDROID_TOOLCHAIN=clang\n```\n\nBefore switch between 32 bit and 64 bit, please make sure to delete build\\_android folder:\n```\nrm -rf build_android\n```\n## Test\nPlug in an android device, and run a test\n\n```\ncd build_android\nadb push bin/gl_conv_op_test /data/local/tmp && adb shell '/data/local/tmp/gl_conv_op_test'\n```\nor use a script to run them all\n\nIn caffe2 top level directory\n```\n./caffe2/mobile/contrib/arm-compute/run_tests.sh build_android\n```\n\nNote that some tests(fully_connected and alignment) have been disabled until the next release of ACL.\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/core/CMakeLists.txt",
    "content": "file(GLOB_RECURSE tmp *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/core/context.cc",
    "content": "#include \"context.h\"\n\n#include \"caffe2/core/allocator.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\nCAFFE_KNOWN_TYPE(GLTensor<GLfloat>);\nCAFFE_KNOWN_TYPE(GLTensor<GLhalf>);\nCAFFE_KNOWN_TYPE(GLTensor<half>);\nCAFFE_KNOWN_TYPE(Tensor<GLContext>);\n\nbool GLContext::initialized = false;\n\nGLContext::GLContext() {\n  CAFFE_ENFORCE(arm_compute::opengles31_is_available());\n  if(!initialized) {\n    arm_compute::GCScheduler::get().default_init();\n    initialized = true;\n  }\n}\n\nvoid EventCreateOPENGL(const DeviceOption & /* unused */,\n                       Event * /* unused */) {}\nvoid EventRecordOPENGL(Event * /* unused */, const void * /* unused */,\n                       const char * /* unused */) {}\nvoid EventWaitOPENGLOPENGL(const Event * /* unused */, void * /* unused */) {}\nvoid EventFinishOPENGL(const Event * /* unused */) {}\nvoid EventResetOPENGL(Event * /* unused */) {}\n\nREGISTER_EVENT_CREATE_FUNCTION(OPENGL, EventCreateOPENGL);\nREGISTER_EVENT_RECORD_FUNCTION(OPENGL, EventRecordOPENGL);\nREGISTER_EVENT_WAIT_FUNCTION(OPENGL, OPENGL, EventWaitOPENGLOPENGL);\nREGISTER_EVENT_FINISH_FUNCTION(OPENGL, EventFinishOPENGL);\nREGISTER_EVENT_RESET_FUNCTION(OPENGL, EventResetOPENGL);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/core/context.h",
    "content": "#ifndef CAFFE2_OPENGL_CONTEXT_H_\n#define CAFFE2_OPENGL_CONTEXT_H_\n\n#ifdef CAFFE2_OPENGL_BACKEND\n#error Can only build one OpenGL backend at a time.\n#else\n#define CAFFE2_OPENGL_BACKEND\n#endif\n\n#include \"caffe2/core/allocator.h\"\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/tensor.h\"\n\n#include \"arm_compute/core/GLES_COMPUTE/OpenGLES.h\"\n#include \"arm_compute/runtime/GLES_COMPUTE/GCFunctions.h\"\n#include \"arm_compute/runtime/GLES_COMPUTE/GCScheduler.h\"\n#include \"arm_compute/runtime/GLES_COMPUTE/GCTensor.h\"\n\n#include \"arm_compute/core/Types.h\"\n#include \"arm_compute/runtime/Allocator.h\"\n#include \"arm_compute/runtime/BlobLifetimeManager.h\"\n#include \"arm_compute/runtime/MemoryManagerOnDemand.h\"\n#include \"arm_compute/runtime/PoolManager.h\"\n#include \"utils/Utils.h\"\n#include \"include/half/half.hpp\"\n\nnamespace caffe2 {\n\ntypedef half_float::half half;\ntypedef half DataType;\n\ntemplate <typename T> class GLTensor;\n\nclass GLContext final {\npublic:\n  static bool initialized;\n  explicit GLContext();\n  explicit GLContext(const DeviceOption &option) {\n    DCHECK_EQ(option.device_type(), OPENGL);\n    GLContext();\n  }\n  ~GLContext() {}\n\n  static void sync() { arm_compute::GCScheduler::get().memory_barrier(); }\n\n  template <typename T>\n  using deleted_unique_ptr = std::unique_ptr<T, std::function<void(T *)>>;\n\n  template <typename T>\n  static deleted_unique_ptr<const GLTensor<T>> getGLTensor(const Blob *b) {\n    if (b->IsType<TensorCPU>()) {\n      auto &Xcpu = b->Get<TensorCPU>();\n      GLTensor<T> *X_raw_ptr;\n      X_raw_ptr = new GLTensor<T>();\n      X_raw_ptr->ResizeLike(Xcpu);\n      deleted_unique_ptr<const GLTensor<T>> X_unique_ptr(\n          X_raw_ptr, [](const GLTensor<T> *X) { delete X; });\n      return X_unique_ptr;\n    }\n    const GLTensor<T> *X_raw_ptr;\n    X_raw_ptr = &b->Get<GLTensor<T>>();\n    deleted_unique_ptr<const GLTensor<T>> X_unique_ptr(\n        X_raw_ptr, [](const GLTensor<T> *X) { return; });\n    return X_unique_ptr;\n  }\n\n  /*\n   * Everything below is basically boiler plate for Context classes\n   */\n  static std::pair<void *, MemoryDeleter> New(size_t nbytes) {\n    return std::pair<void *, MemoryDeleter>(malloc(nbytes), GLContext::Delete);\n  }\n\n  static void Delete(void *data) {\n    if (data != nullptr) {\n      free(data);\n    }\n  }\n\n  template <class SrcContext, class DstContext>\n  inline void CopyBytes(size_t nbytes, const void *src, void *dst) {}\n\n  template <typename T, class SrcContext, class DstContext>\n  inline void Copy(int n, const T *src, T *dst) {\n    CopyBytes<SrcContext, DstContext>(n * sizeof(T),\n                                      static_cast<const void *>(src),\n                                      static_cast<void *>(dst));\n  }\n\n  template <class SrcContext, class DstContext>\n  inline void CopyItems(const TypeMeta &meta, size_t n, const void *src,\n                        void *dst) {\n    CAFFE_ENFORCE(!meta.copy(), \"GLContext requires fundamental types.\");\n    CopyBytes<SrcContext, DstContext>(n * meta.itemsize(), src, dst);\n  }\n\n  void SwitchToDevice(int a, ...) { /* TODO */\n  }\n  void SwitchToDevice() { SwitchToDevice(0); }\n\n  inline void WaitEvent(const Event &ev) { /* TODO */\n  }\n  void FinishDeviceComputation() { /* TODO */\n  }\n  inline void Record(Event *ev, const char *&) const { /* TODO */\n  }\n  static bool IsStreamFree(const DeviceOption& /* unused */, int /* unused */) {\n    return true;\n  }\n  bool HasAsyncPartDefault() const { return false; }\n  bool SupportsAsyncScheduling() const { return false; }\n\n};\n\ntemplate <typename T> class GLTensor {\nprivate:\n  bool allocated_ = false;\npublic:\n  GLTensor() { tensor_ = make_unique<arm_compute::GCTensor>(); }\n  ~GLTensor() { tensor_->allocator()->free(); }\n\n  template <typename TensorType> void ResizeLike(TensorType &X) {\n    tensor_->allocator()->free();\n    SetDims(X.dims());\n    shape_ = arm_compute::TensorShape();\n    for (int i = 0; i < dims_.size(); i++) {\n      shape_.set(dims_.size() - i - 1, dims_[i]);\n    }\n\n    tensor_->allocator()->init(\n        arm_compute::TensorInfo(shape_, 1, arm_compute::DataType::F16));\n  }\n\n  template <typename... Ts> void Resize(Ts... dim_source) {\n    bool size_changed = SetDims(dim_source...);\n    if (size_changed) {\n      // TODO: Make it type generic\n      int64_t new_size = size_ * sizeof(T);\n      tensor_->allocator()->free();\n      for (int i = 0; i < dims_.size(); i++) {\n        shape_.set(dims_.size() - i - 1, dims_[i]);\n      }\n      tensor_->allocator()->init(\n          arm_compute::TensorInfo(shape_, 1, arm_compute::DataType::F16));\n    }\n  }\n\n  // Allocates and copies data if needed\n  void lazy_allocate(const Blob *b, bool allocate_tensor, bool try_to_copy_from_cpu) const {\n    if (try_to_copy_from_cpu) {\n      // we skip GLTensors, nothing to copy\n      if (!b->IsType<GLTensor>()) {\n        // typically only called on the second run\n        if (allocate_tensor) {\n          allocate();\n        }\n        fillGLTensor(b);\n      }\n    }\n  }\n\n  void allocate() const {\n    tensor_->allocator()->allocate();\n  }\n\n  void fillGLTensor(const Blob *b) const {\n    if (b->IsType<TensorCPU>()) {\n      auto &Xcpu = b->Get<TensorCPU>();\n\n      T *buffer = map();\n      char *byte_buffer = (char *)buffer;\n      auto info = tensor_->info();\n      if (Xcpu.ndim() == 4) {\n        auto M = Xcpu.dim32(0);\n        auto C = Xcpu.dim32(1);\n        auto H = Xcpu.dim32(2);\n        auto W = Xcpu.dim32(3);\n        for (auto m = 0; m < M; ++m) {\n          for (auto c = 0; c < C; ++c) {\n            for (auto h = 0; h < H; ++h) {\n              for (auto w = 0; w < W; ++w) {\n                T *b = (T *)(&byte_buffer[info->offset_element_in_bytes(\n                    arm_compute::Coordinates(w, h, c, m))]);\n                // require cpu input blob to be float\n                *b = T(Xcpu.data<float>()[((m * C + c) * H + h) * W + w]);\n              }\n            }\n          }\n        }\n      } else if (Xcpu.ndim() == 3) {\n        auto C = Xcpu.dim32(0);\n        auto H = Xcpu.dim32(1);\n        auto W = Xcpu.dim32(2);\n        for (auto c = 0; c < C; ++c) {\n          for (auto h = 0; h < H; ++h) {\n            for (auto w = 0; w < W; ++w) {\n              T *b = (T *)(&byte_buffer[info->offset_element_in_bytes(\n                  arm_compute::Coordinates(w, h, c))]);\n              // require cpu input blob to be float\n              *b = T(Xcpu.data<float>()[(c * H + h) * W + w]);\n            }\n          }\n        }\n      } else if (Xcpu.ndim() == 2) {\n        auto H = Xcpu.dim32(0);\n        auto W = Xcpu.dim32(1);\n        for (auto h = 0; h < H; ++h) {\n          for (auto w = 0; w < W; ++w) {\n            T *b = (T *)(&byte_buffer[info->offset_element_in_bytes(\n                arm_compute::Coordinates(w, h))]);\n            // require cpu input blob to be float\n            *b = T(Xcpu.data<float>()[h * W + w]);\n          }\n        }\n      } else {\n        auto size = Xcpu.dim32(0);\n        for (auto i = 0; i < size; ++i) {\n          T *b = (T *)(&byte_buffer[info->offset_element_in_bytes(arm_compute::Coordinates(i))]);\n          // require cpu input blob to be float\n          *b = T(Xcpu.data<float>()[i]);\n        }\n      }\n      unmap();\n    }\n  }\n\n\n  const int32_t ndim() const { return dims_.size(); }\n\n  vector<TIndex> dims() const { return dims_; }\n\n  const int32_t dim32(const int index) const { return dims_.at(index); }\n\n  const int32_t size() const {\n    int32_t s = 1;\n    for (int i = 0; i < dims_.size(); i++) {\n      s *= dims_[i];\n    }\n    return s;\n  }\n\n  arm_compute::GCTensor *get_underlying() const { return tensor_.get(); }\n\n  T *map() const {\n    GLContext::sync();\n    tensor_->map(true);\n    return reinterpret_cast<T *>(tensor_->buffer());\n  }\n\n  void unmap() const { return tensor_->unmap(); }\n\n  void sync() const {\n    GLContext::sync();\n    tensor_->map();\n    tensor_->unmap();\n  }\n\nprivate:\n  template <typename TI, typename = typename std::enable_if<\n                             std::is_integral<TI>::value>::type>\n  bool SetDims(const vector<TI> &src) {\n    auto old_size = size_;\n    dims_.resize(src.size());\n    TIndex new_size = 1;\n    for (unsigned int i = 0; i < src.size(); ++i) {\n      new_size *= src[i];\n      dims_[i] = src[i];\n    }\n    size_ = new_size;\n    return size_ != old_size;\n  }\n\n  bool SetDims() {\n    auto old_size = size_;\n    dims_.resize(0);\n    size_ = 1;\n    return size_ != old_size;\n  }\n\n  bool SetDims(const TIndex d0) {\n    auto old_size = size_;\n    dims_.resize(1);\n    dims_[0] = d0;\n    size_ = d0;\n    return size_ != old_size;\n  }\n\n  bool SetDims(const TIndex d0, const TIndex d1) {\n    auto old_size = size_;\n    dims_.resize(2);\n    dims_[0] = d0;\n    dims_[1] = d1;\n    size_ = d0 * d1;\n    return size_ != old_size;\n  }\n\n  bool SetDims(const TIndex d0, const TIndex d1, const TIndex d2) {\n    auto old_size = size_;\n    dims_.resize(3);\n    dims_[0] = d0;\n    dims_[1] = d1;\n    dims_[2] = d2;\n    size_ = d0 * d1 * d2;\n    return size_ != old_size;\n  }\n\n  bool SetDims(const TIndex d0, const TIndex d1, const TIndex d2,\n               const TIndex d3) {\n    auto old_size = size_;\n    dims_.resize(4);\n    dims_[0] = d0;\n    dims_[1] = d1;\n    dims_[2] = d2;\n    dims_[3] = d3;\n    size_ = d0 * d1 * d2 * d3;\n    return size_ != old_size;\n  }\n\n  vector<TIndex> dims_;\n  TIndex size_ = -1;\n  arm_compute::TensorShape shape_;\n  unique_ptr<arm_compute::GCTensor> tensor_;\n};\n\ntemplate<typename T = half>\nvoid getTensorCPU(const GLTensor<T>& g_, TensorCPU& g) {\n  g.Resize(g_.dims());\n  T *buffer = g_.map();\n\n  for (auto i = 0; i < g.size(); ++i) {\n    auto tmp = buffer[i];\n    g.mutable_data<float>()[i] = tmp;\n  }\n  g_.unmap();\n}\n\n\n} // namespace caffe2\n\n#endif /* CAFFE2_OPENGL_CONTEXT_H_ */\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/core/net_gl.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/mobile/contrib/arm-compute/core/net_gl.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/core/net.h\"\n\n#include <set>\n#include <unordered_map>\n#include <unordered_set>\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/static_tracepoint.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\nGLNet::GLNet(\n    const std::shared_ptr<const NetDef>& net_def,\n    Workspace* ws)\n    : NetBase(net_def, ws) {\n  ws_ = ws;\n  VLOG(1) << \"Constructing GLNet \" << net_def->name();\n  const bool net_def_has_device_option = net_def->has_device_option();\n  // Initialize the operators\n  for (int idx = 0; idx < net_def->op_size(); ++idx) {\n    const auto& operator_def = net_def->op(idx);\n    VLOG(1) << \"Creating operator \" << operator_def.name() << \": \"\n            << operator_def.type();\n    output_blobs_.push_back(operator_def.output(0));\n    if (operator_def.has_device_option() && operator_def.device_option().device_type() == OPENGL) {\n      opengl_device_.push_back(true);\n    } else {\n      opengl_device_.push_back(false);\n    }\n\n    std::unique_ptr<OperatorBase> op{nullptr};\n    if (!operator_def.has_device_option() && net_def_has_device_option) {\n      // In the case that the operator def does not specify a device option but\n      // the net def has a default option, we copy the device option over to the\n      // operator def.\n      OperatorDef temp_def(operator_def);\n      temp_def.mutable_device_option()->CopyFrom(net_def->device_option());\n      op = CreateOperator(temp_def, ws, idx);\n    } else {\n      op = CreateOperator(operator_def, ws, idx);\n      op->set_debug_def(\n          std::shared_ptr<const OperatorDef>{net_def, &(net_def->op(idx))});\n    }\n    operators_.emplace_back(std::move(op));\n  }\n}\n\nbool GLNet::Run() {\n  StartAllObservers();\n  if (first_run_) {\n    first_run_ = false;\n    for (auto& op: operators_) {\n      if (op->device_option().device_type() == OPENGL) {\n        op->Run();\n      }\n    }\n  }\n  VLOG(1) << \"Running net \" << name_;\n  for (auto& op : operators_) {\n    bool res = op->Run();\n    if (!res) {\n      LOG(ERROR) << \"Operator failed: \" << ProtoDebugString(op->debug_def());\n      return false;\n    }\n  }\n  StopAllObservers();\n  return true;\n}\n\nbool GLNet::RunAsync() {\n  return Run();\n}\n\nnamespace {\ntemplate <typename A, typename B>\nbool PairLargerThan(const std::pair<A, B>& x, const std::pair<A, B>& y) {\n  return x.second > y.second;\n}\n}\n\nvector<float> GLNet::TEST_Benchmark(\n    const int warmup_runs,\n    const int main_runs,\n    const bool run_individual) {\n  LOG(INFO) << \"Starting benchmark.\";\n  LOG(INFO) << \"Running warmup runs.\";\n  CAFFE_ENFORCE(\n      warmup_runs >= 0,\n      \"Number of warm up runs should be non negative, provided \",\n      warmup_runs,\n      \".\");\n  for (int i = 0; i < warmup_runs; ++i) {\n    CAFFE_ENFORCE(Run(), \"Warmup run \", i, \" has failed.\");\n  }\n\n  auto last_blob = output_blobs_[output_blobs_.size() - 1];\n  Blob *gpu_out_blob = ws_->GetBlob(last_blob);\n  auto &g_ = gpu_out_blob->Get<GLTensor<half>>();\n  // Enforce gpu execution\n  g_.sync();\n\n  LOG(INFO) << \"Main runs.\";\n  CAFFE_ENFORCE(\n      main_runs >= 0,\n      \"Number of main runs should be non negative, provided \",\n      main_runs,\n      \".\");\n  Timer timer;\n  for (int i = 0; i < main_runs; ++i) {\n    CAFFE_ENFORCE(Run(), \"Main run \", i, \" has failed.\");\n  }\n  g_.sync();\n\n  auto millis = timer.MilliSeconds();\n  LOG(INFO) << \"[C2DEBUG] Main run finished. Milliseconds per iter: \"\n            << millis / main_runs\n            << \". Iters per second: \" << 1000.0 * main_runs / millis;\n\n  vector<float> time_per_op(operators_.size(), 0);\n  vector<uint64_t> flops_per_op(operators_.size(), 0);\n  CaffeMap<string, float> time_per_op_type;\n  if (run_individual) {\n    for (int i = 0; i < main_runs; ++i) {\n      for (auto& op : operators_) {\n        op->ResetEvent();\n      }\n      int idx = 0;\n      for (auto& op : operators_) {\n        const string& op_type = op->debug_def().type();\n        if (i == 0) { // Gather flops on the first run.\n          auto* schema = OpSchemaRegistry::Schema(op_type);\n          if (schema && schema->HasCostInferenceFunction()) {\n            vector<TensorShape> shapes = op->InputTensorShapes();\n            flops_per_op[idx] =\n                schema->InferCost(op->debug_def(), shapes).flops;\n          }\n        }\n        timer.Start();\n        CAFFE_ENFORCE(\n            op->Run(),\n            \"operator \",\n            op->debug_def().name(),\n            \"(\",\n            op_type,\n            \") has failed.\");\n        if (opengl_device_[idx]) {\n          Blob *gpu_out_blob = ws_->GetBlob(output_blobs_[idx]);\n          auto &g_ = gpu_out_blob->Get<GLTensor<half>>();\n          g_.sync();\n        }\n        float spent = timer.MilliSeconds();\n        time_per_op[idx] += spent;\n        time_per_op_type[op_type] += spent;\n        ++idx;\n      }\n    }\n\n    int idx = 0;\n    for (auto& op : operators_) {\n      const string& op_type = op->debug_def().type();\n      const string& print_name =\n          (op->debug_def().name().size()\n               ? op->debug_def().name()\n               : (op->debug_def().output_size() ? op->debug_def().output(0)\n                                                : \"NO_OUTPUT\"));\n      std::stringstream flops_str;\n      if (flops_per_op[idx]) {\n        flops_str << \" (\"\n                  << to_string(1.0e-6 * flops_per_op[idx] / time_per_op[idx])\n                  << \" GFLOPS)\";\n      }\n      LOG(INFO) << \"[C2DEBUG] Operator #\" << idx << \" (\" << print_name << \", \" << op_type\n                << \") \" << time_per_op[idx] / main_runs << \" ms/iter\"\n                << flops_str.str();\n      ++idx;\n    }\n    LOG(INFO) << \"[C2DEBUG] Time per operator type:\";\n    // sort by decreasing time spending.\n    std::vector<std::pair<string, float>> time_per_op_type_vec(\n        time_per_op_type.begin(), time_per_op_type.end());\n    std::sort(\n        time_per_op_type_vec.begin(),\n        time_per_op_type_vec.end(),\n        PairLargerThan<string, float>);\n    for (const auto& item : time_per_op_type_vec) {\n      LOG(INFO) << \"[C2DEBUG] \" << std::setw(15) << std::setfill(' ') << item.second / main_runs\n                << \" \" << item.first;\n    }\n  }\n  // We will reuse time_per_op to return the result of BenchmarkNet.\n  for (int i = 0; i < time_per_op.size(); ++i) {\n    time_per_op[i] /= main_runs;\n  }\n  time_per_op.insert(time_per_op.begin(), millis / main_runs);\n  return time_per_op;\n}\n\nREGISTER_NET(opengl, GLNet);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/core/net_gl.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CORE_NET_GL_H_\n#define CAFFE2_CORE_NET_GL_H_\n\n#include <vector>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/registry.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\n// This is the very basic structure you need to run a network with\n// ARM's compute library\nclass GLNet : public NetBase {\n private:\n  bool first_run_ = true;\n  Workspace* ws_;\n  // record output blob for sync step in operator level benchmarking\n  std::vector<string> output_blobs_;\n  // record operator type and only sync after gpu op\n  std::vector<bool> opengl_device_;\n public:\n  GLNet(const std::shared_ptr<const NetDef>& net_def, Workspace* ws);\n  bool SupportsAsync() override {\n    return false;\n  }\n\n  vector<float> TEST_Benchmark(\n      const int warmup_runs,\n      const int main_runs,\n      const bool run_individual) override;\n\n  /*\n   * This returns a list of pointers to objects stored in unique_ptrs.\n   * Used by Observers.\n   *\n   * Think carefully before using.\n   */\n  vector<OperatorBase*> GetOperators() const override {\n    vector<OperatorBase*> op_list;\n    for (auto& op : operators_) {\n      op_list.push_back(op.get());\n    }\n    return op_list;\n  }\n\n protected:\n  bool Run();\n  bool RunAsync();\n  bool DoRunAsync() override {\n    return Run();\n  }\n\n  vector<unique_ptr<OperatorBase>> operators_;\n\n  DISABLE_COPY_AND_ASSIGN(GLNet);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_CORE_NET_SIMPLE_H_\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/core/operator.cc",
    "content": "#include \"operator.h\"\n\nnamespace caffe2 {\n\nCAFFE_DEFINE_REGISTRY(GLOperatorRegistry, OperatorBase, const OperatorDef &,\n                      Workspace *);\nCAFFE_REGISTER_DEVICE_TYPE(DeviceType::OPENGL, GLOperatorRegistry);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/core/operator.h",
    "content": "#ifndef CAFFE2_OPENGL_OPERATOR_H_\n#define CAFFE2_OPENGL_OPERATOR_H_\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/registry.h\"\n\nnamespace caffe2 {\n\nCAFFE_DECLARE_REGISTRY(GLOperatorRegistry, OperatorBase, const OperatorDef &,\n                       Workspace *);\n#define REGISTER_GL_OPERATOR_CREATOR(key, ...)                                 \\\n  CAFFE_REGISTER_CREATOR(GLOperatorRegistry, key, __VA_ARGS__)\n#define REGISTER_GL_OPERATOR(name, ...)                                        \\\n  extern void CAFFE2_PLEASE_ADD_OPERATOR_SCHEMA_FOR_##name();                  \\\n  static void CAFFE2_UNUSED CAFFE_ANONYMOUS_VARIABLE_GL##name() {              \\\n    CAFFE2_PLEASE_ADD_OPERATOR_SCHEMA_FOR_##name();                            \\\n  }                                                                            \\\n  CAFFE_REGISTER_CLASS(GLOperatorRegistry, name, __VA_ARGS__)\n#define REGISTER_GL_OPERATOR_STR(str_name, ...)                                \\\n  CAFFE_REGISTER_TYPED_CLASS(GLOperatorRegistry, str_name, __VA_ARGS__)\n\n#define REGISTER_GL_OPERATOR_WITH_ENGINE(name, engine, ...)                    \\\n  CAFFE_REGISTER_CLASS(GLOperatorRegistry, name##_ENGINE_##engine, __VA_ARGS__)\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPENGL_OPERATOR_H_\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/core/rewrite_net.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"rewrite_net.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include <unordered_map>\n\nnamespace caffe2 {\n\nstruct Analysis {\n  struct SSA {\n    using BlobVersions = std::unordered_map<std::string, size_t>;\n    BlobVersions inVersions;\n    BlobVersions outVersions;\n  };\n  std::vector<SSA> ssa;\n  std::unordered_map<std::string, std::unordered_map<size_t, std::vector<size_t>>> inUsages;\n};\n\nstatic Analysis analyzeNet(const NetDef& net) {\n  Analysis::SSA::BlobVersions frontier;\n  Analysis analysis;\n\n  auto play = [&](size_t i, const OperatorDef& op) {\n    Analysis::SSA::BlobVersions inVersions;\n    for (const auto& s : op.input()) {\n      inVersions[s] = frontier[s];\n      analysis.inUsages[s][frontier[s]].push_back(i);\n    }\n    Analysis::SSA::BlobVersions outVersions;\n    for (const auto& s : op.output()) {\n      if (frontier.find(s) != frontier.end()) {\n        frontier[s] += 1;\n      }\n      outVersions[s] = frontier[s];\n    }\n    analysis.ssa.push_back(Analysis::SSA{inVersions, outVersions});\n  };\n\n  for (auto i = 0; i < net.op_size(); ++i) {\n    play(i, net.op(i));\n  }\n  return analysis;\n}\n\nstatic void insertCopyFromGLOp(NetDef& predictNet, const std::string& cpu_blob) {\n  auto* op = predictNet.add_op();\n  op->set_name(\"CopyFromGL\");\n  op->set_type(\"CopyFromGL\");\n  op->add_input(cpu_blob + \"_M\");\n  op->add_output(cpu_blob);\n}\n\nstatic NetDef insertInputOutputCopyOps(const NetDef& def, std::unordered_set<std::string>& cpuOp) {\n  // Do some validation of the outputs. For this version, we require:\n  // - a single input (first element of external_input()) is consumed by the NetDef\n  // - a single output (first element of external_output()) is produced by the NetDef.\n  // - the input is consumed by def.op(0), and this is the only consumer.\n  // - the output is produced by def.op(-1).\n  CAFFE_ENFORCE_GE(def.external_input_size(), 1);\n  CAFFE_ENFORCE_GE(def.external_output_size(), 1);\n  auto analysis = analyzeNet(def);\n  // enforce a single use of the input blob.\n  CAFFE_ENFORCE_GE(def.op_size(), 1);\n\n  const auto& inputBlob = def.external_input(0);\n  // Enforce that the input blob has a single usage - in the first operator.\n  CAFFE_ENFORCE(analysis.inUsages[inputBlob][0] == (std::vector<size_t>{0}));\n  // Enforce that the external_output(0) blob is produced by the last operator in this sequence.\n  const auto& outputBlob = def.external_output(0);\n  CAFFE_ENFORCE(analysis.ssa.back().outVersions.find(outputBlob) !=\n                analysis.ssa.back().outVersions.end());\n  const auto& outputBlobVersion = analysis.ssa.back().outVersions[outputBlob];\n  // This should hold true by definition of the SSA analysis.\n  CAFFE_ENFORCE(analysis.inUsages[outputBlob].find(outputBlobVersion) ==\n                analysis.inUsages[outputBlob].end());\n\n  NetDef mdef;\n  mdef.CopyFrom(def);\n  mdef.clear_op();\n\n  std::unordered_map<std::string, std::set<size_t>> cpu_blobs, gpu_blobs;\n  cpu_blobs[def.external_input(0)].insert(0);\n\n  for (auto i = 0; i < def.op_size(); i++) {\n    const auto& currentOp = def.op(i);\n    if (cpuOp.count(currentOp.type()) > 0) {\n      // CPU Op\n      // insert copyFromOpenGLOp\n      for (auto j = 0; j < currentOp.input_size(); j++) {\n        auto& input = currentOp.input(j);\n        auto version = analysis.ssa[i].inVersions[input];\n        if (gpu_blobs[input].count(version) > 0) {\n          insertCopyFromGLOp(mdef, input);\n        }\n      }\n      auto* op = mdef.add_op();\n      op->CopyFrom(currentOp);\n      for (auto j = 0; j < currentOp.output_size(); j++) {\n        auto& output = currentOp.output(j);\n        auto version = analysis.ssa[i].outVersions[output];\n        cpu_blobs[output].insert(version);\n      }\n    } else {\n      // OpenGL Op\n      auto* op = mdef.add_op();\n      op->CopyFrom(currentOp);\n\n     for (auto j = 0; j < op->input_size(); j++) {\n        auto* input = op->mutable_input(j);\n        auto version = analysis.ssa[i].inVersions[*input];\n        if (gpu_blobs[*input].count(version) > 0) {\n          *input = *input + \"_M\";\n        }\n      }\n\n      for (auto j = 0; j < currentOp.output_size(); j++) {\n        auto& output = currentOp.output(j);\n        auto version = analysis.ssa[i].outVersions[output];\n        gpu_blobs[output].insert(version);\n        // add _M to intermediate OpenGL op outputs\n        auto* output_ = op->mutable_output(j);\n        bool inter = true;\n        for(auto k = 0; k < def.external_output_size(); k++) {\n          if (*output_ == def.external_output(k)) {\n            inter = false;\n          }\n        }\n        if (inter) {\n          *output_ = *output_ + \"_M\";\n        }\n      }\n    }\n  }\n  return mdef;\n}\n\nstatic bool tryFuseAdjacentOps(const OperatorDef& currentOp,\n                               const OperatorDef& nextOp,\n                               OperatorDef* fusedOp,\n                               std::unordered_set<std::string>& glOps) {\n  // Check for possible invalid opportunities.\n  if (currentOp.output_size() != 1 || nextOp.output_size() != 1) {\n    return false;\n  }\n  // The fused op cannot be inplace\n  if (currentOp.output(0) != nextOp.input(0) || currentOp.input(0) == nextOp.output(0)) {\n    return false;\n  }\n\n  static const std::map<std::pair<std::string, std::string>, std::string> fusionOpportunities = {\n      {{\"OpenGLInstanceNorm\", \"OpenGLPRelu\"}, \"OpenGLInstanceNormPRelu\"},\n      {{\"OpenGLConv\", \"OpenGLPRelu\"}, \"OpenGLConvPRelu\"},\n      {{\"OpenGLConv\", \"OpenGLRelu\"}, \"OpenGLConvRelu\"},\n      {{\"OpenGLConvTranspose\", \"OpenGLPRelu\"}, \"OpenGLConvTransposePRelu\"}};\n  auto it = fusionOpportunities.find({currentOp.type(), nextOp.type()});\n  if (it == fusionOpportunities.end()) {\n    return false;\n  }\n\n  glOps.insert(it->second);\n  fusedOp->CopyFrom(currentOp);\n  fusedOp->set_output(0, nextOp.output(0));\n  fusedOp->set_type(it->second);\n  for (auto i = 1; i < nextOp.input_size(); i++) {\n    fusedOp->add_input(nextOp.input(i));\n  }\n  return true;\n}\n\nstatic NetDef runOpenGLFusion(const NetDef& def, std::unordered_set<std::string>& glOps) {\n  CHECK_GE(def.op_size(), 1);\n  NetDef mdef;\n  mdef.CopyFrom(def);\n  mdef.clear_op();\n  auto i = 0;\n\n  while (i < def.op_size()) {\n    if (i == def.op_size() - 1) {\n      VLOG(2) << \"Last operator, skipping\";\n      auto* op = mdef.add_op();\n      op->CopyFrom(def.op(i));\n      i += 1;\n      continue;\n    }\n\n    const auto& currentOp = def.op(i);\n    const auto& nextOp = def.op(i + 1);\n    OperatorDef fusedOp;\n    if (tryFuseAdjacentOps(currentOp, nextOp, &fusedOp, glOps)) {\n      VLOG(2) << \"Found an adjacent fusion for: \" << currentOp.type() << \", \" << nextOp.type();\n      // We can fuse.\n      auto* op = mdef.add_op();\n      op->CopyFrom(fusedOp);\n      i += 2;\n      continue;\n    }\n    VLOG(2) << \"No fusion available for: \" << currentOp.type() << \", \" << nextOp.type();\n    // Just emit the current type.\n    auto* op = mdef.add_op();\n    op->CopyFrom(currentOp);\n    i += 1;\n  }\n  return mdef;\n}\n\nvoid dumpDefForOpenGL(const NetDef& d) {\n  for (const auto& op : d.op()) {\n    LOG(INFO) << op.input(0) << \" -> \" << op.type() << \" -> \" << op.output(0);\n  }\n}\n\n// // For debugging\n// void dumpDefForOpenGL(const NetDef &net) {\n//  for (const auto &op : net.op()) {\n//    printf(\"***Operator: %s\\n\", op.type().c_str());\n//    for (auto input : op.input()) {\n//      printf(\"\\tInput: %s\\n\", input.c_str());\n//    }\n//\n//    for (auto output : op.output()) {\n//      printf(\"\\tOutput: %s\\n\", output.c_str());\n//    }\n//  }\n//}\n\nNetDef rewritePredictNetForOpenGL(const NetDef& predictNet, bool runFusion, std::unordered_set<std::string> cpuOps) {\n  CAFFE_ENFORCE_GE(predictNet.op_size(), 1);\n  NetDef net;\n  net.CopyFrom(predictNet);\n\n  // if (runFusion) {\n  //   net = runOpenGLFusion(net, openGLOps);\n  // }\n\n  net = insertInputOutputCopyOps(net, cpuOps);\n  net.set_type(\"opengl\");\n\n  for (auto i = 0; i < net.op().size(); ++i) {\n    auto op = net.mutable_op(i);\n    if (std::find(cpuOps.begin(), cpuOps.end(), op->type()) == cpuOps.end()) {\n      op->mutable_device_option()->set_device_type(OPENGL);\n    }\n  }\n\n  return net;\n}\n\nbool tryConvertToOpenGL(const NetDef& predictNet,\n                        NetDef* glPredictNet,\n                        bool runFusion,\n                        std::unordered_set<std::string> cpuOps) {\n  try {\n    // Throws if unsupported operators are found.\n    *glPredictNet = rewritePredictNetForOpenGL(predictNet, runFusion, cpuOps);\n    dumpDefForOpenGL(*glPredictNet);\n    // Throws if unsupported parameters are found.\n    LOG(INFO) << \"OpenGL is successfully enabled\";\n    return true;\n  } catch (const std::exception& e) {\n    LOG(ERROR) << \"Caught exception trying to convert NetDef to OpenGL: \" << e.what();\n    return false;\n  }\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/core/rewrite_net.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n#include \"caffe2/mobile/contrib/arm-compute/core/net_gl.h\"\n#include <unordered_set>\n\nnamespace caffe2 {\nbool tryConvertToOpenGL(const NetDef& predictNet,\n                        NetDef* glPredictNet,\n                        bool runFusion,\n                        std::unordered_set<std::string> cpuOps);\n\n// Exposed for testing\nNetDef rewritePredictNetForOpenGL(const NetDef& predictNet,\n                                  bool runFusion,\n                                  std::unordered_set<std::string> cpuOps);\nvoid dumpDefForOpenGL(const NetDef& net);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/CMakeLists.txt",
    "content": "file(GLOB_RECURSE tmp *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/activation_ops.cc",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/operator.h\"\n\n#include \"caffe2/mobile/contrib/arm-compute/operators/activation_ops.h\"\n#include \"caffe2/operators/relu_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\nbool GLReluOp<T>::RunOnDevice() {\n\n  auto *Xblob = OperatorBase::Inputs()[0];\n  if (first_run_) {\n    X_ = GLContext::getGLTensor<T>(Xblob);\n  }\n\n  GLTensor<T> *Y =\n      OperatorBase::Outputs()[0]->template GetMutable<GLTensor<T>>();\n\n  if (first_run_) {\n    first_run_ = false;\n    if (Y->get_underlying() != X_->get_underlying())\n    {\n        Y->ResizeLike(*X_);\n    }\n    relu_layer_.configure(\n        X_->get_underlying(), Y->get_underlying(),\n        arm_compute::ActivationLayerInfo(\n          arm_compute::ActivationLayerInfo::ActivationFunction::RELU));\n\n  } else {\n    X_->lazy_allocate(Xblob, second_run_, true);\n    if (second_run_) {\n      second_run_ = false;\n      // in place activation, do not need to allocate new memory\n      if (Y->get_underlying() != X_->get_underlying())\n      {\n          Y->allocate();\n      }\n    }\n    relu_layer_.run();\n  }\n\n  return true;\n}\n\nREGISTER_GL_OPERATOR(Relu, GLReluOp<half>);\n\ntemplate <typename T>\nbool GLSigmoidOp<T>::RunOnDevice() {\n\n  auto *Xblob = OperatorBase::Inputs()[0];\n  if (first_run_) {\n    X_ = GLContext::getGLTensor<T>(Xblob);\n  }\n\n  GLTensor<T> *Y =\n      OperatorBase::Outputs()[0]->template GetMutable<GLTensor<T>>();\n  if (first_run_) {\n    first_run_ = false;\n\n    if (Y->get_underlying() != X_->get_underlying())\n    {\n        Y->ResizeLike(*X_);\n    }\n\n    sigmoid_layer_.configure(\n      X_->get_underlying(), Y->get_underlying(),\n      arm_compute::ActivationLayerInfo(\n          arm_compute::ActivationLayerInfo::ActivationFunction::LOGISTIC));\n  } else {\n    X_->lazy_allocate(Xblob, second_run_, true);\n    if (second_run_) {\n      second_run_ = false;\n      // in place activation, do not need to allocate new memory\n      if (Y->get_underlying() != X_->get_underlying())\n      {\n          Y->allocate();\n      }\n    }\n    sigmoid_layer_.run();\n  }\n\n  return true;\n}\n\nREGISTER_GL_OPERATOR(Sigmoid, GLSigmoidOp<DataType>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/activation_ops.h",
    "content": "#ifndef CAFFE2_OPENGL_OPERATORS_ACTIVATION_OPS_H_\n#define CAFFE2_OPENGL_OPERATORS_ACTIVATION_OPS_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\nclass GLSigmoidOp final : public Operator<GLContext> {\npublic:\n  GLSigmoidOp(const OperatorDef &operator_def, Workspace *ws)\n      : Operator<GLContext>(operator_def, ws) {}\n  USE_OPERATOR_FUNCTIONS(GLContext);\n  bool RunOnDevice() override;\nprivate:\n  arm_compute::GCActivationLayer sigmoid_layer_;\n  bool first_run_ = true, second_run_ = true;\n  GLContext::deleted_unique_ptr<const GLTensor<T>> X_;\n};\n\ntemplate <typename T> class GLReluOp final : public Operator<GLContext> {\npublic:\n  GLReluOp(const OperatorDef &operator_def, Workspace *ws)\n      : Operator<GLContext>(operator_def, ws) {}\n  virtual ~GLReluOp() noexcept {}\n  USE_OPERATOR_FUNCTIONS(GLContext);\n  bool RunOnDevice() override;\nprivate:\n  arm_compute::GCActivationLayer relu_layer_;\n  bool first_run_ = true, second_run_ = true;\n  GLContext::deleted_unique_ptr<const GLTensor<T>> X_;\n\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPENGL_OPERATORS_ACTIVATION_OPS_H_\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/concat_op.cc",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/operator.h\"\n#include \"caffe2/operators/concat_split_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T> class GLConcatOp final : public Operator<GLContext> {\npublic:\n  GLConcatOp(const OperatorDef &operator_def, Workspace *ws)\n      : Operator<GLContext>(operator_def, ws) {}\n  virtual ~GLConcatOp() noexcept {}\n  USE_OPERATOR_FUNCTIONS(GLContext);\n  bool RunOnDevice() override;\nprivate:\n  arm_compute::GCDepthConcatenateLayer concat_layer_;\n  bool first_run_ = true, second_run_ = true;\n  std::vector<GLContext::deleted_unique_ptr<const GLTensor<T>>> inputs_;\n  int channelCount_ = 0;\n};\n\n\ntemplate <typename T>\nbool GLConcatOp<T>::RunOnDevice() {\n\n  CAFFE_ENFORCE(InputSize() <= 4 && InputSize() >= 2, \"Number \\\n  of input must be between 2 and 4.\");\n\n  auto *X0blob = OperatorBase::Inputs()[0];\n  auto X0 = GLContext::getGLTensor<T>(X0blob);\n  if (first_run_) {\n    inputs_.push_back(std::move(X0));\n  }\n\n  int N = inputs_[0]->dim32(0);\n  int channels = inputs_[0]->dim32(1);\n  int height = inputs_[0]->dim32(2);\n  int width = inputs_[0]->dim32(3);\n  std::vector<const Blob*> inputsBlob;\n  inputsBlob.push_back(X0blob);\n\n  if (first_run_) {\n    channelCount_ = channels;\n    for (int i = 1; i < Inputs().size(); ++i) {\n      auto *Xblob = OperatorBase::Inputs()[i];\n      auto X = GLContext::getGLTensor<T>(Xblob);\n      CAFFE_ENFORCE_EQ(N, X->dim32(0), X->dim32(0));\n      CAFFE_ENFORCE_EQ(height, X->dim32(2), X->dim32(2));\n      CAFFE_ENFORCE_EQ(width, X->dim32(3), X->dim32(3));\n      channelCount_ += X->dim32(1);\n      inputs_.push_back(std::move(X));\n    }\n  }\n\n  for (int i = 1; i < Inputs().size(); ++i) {\n    auto *Xblob = OperatorBase::Inputs()[i];\n    inputsBlob.push_back(Xblob);\n  }\n  std::vector<int> output_dims = {N, channelCount_, height, width};\n  GLTensor<T> *Y =\n      OperatorBase::Outputs()[0]->template GetMutable<GLTensor<T>>();\n  if (first_run_) {\n    first_run_ = false;\n    Y->Resize(output_dims);\n\n    std::vector<arm_compute::IGCTensor*> inputsGC;\n    for (int i = 0; i < inputs_.size(); ++i) {\n      inputsGC.push_back(inputs_[i]->get_underlying());\n    }\n    concat_layer_.configure(inputsGC, Y->get_underlying());\n  } else {\n    for (int i = 0; i < inputs_.size(); ++i) {\n      auto* X = inputs_[i].get();\n      auto* Xblob = inputsBlob[i];\n      X->lazy_allocate(Xblob, second_run_, true);\n    }\n    if (second_run_) {\n      second_run_ = false;\n      Y->allocate();\n    }\n    concat_layer_.run();\n  }\n\n  return true;\n}\n\nREGISTER_GL_OPERATOR(Concat, GLConcatOp<DataType>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/conv_op.cc",
    "content": "#include \"arm_compute/graph/Graph.h\"\n#include \"arm_compute/graph/Nodes.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/operator.h\"\n\n#include \"caffe2/operators/conv_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\nclass GLConvOp final : public ConvPoolOpBase<GLContext> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(GLContext);\n  GLConvOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<GLContext>(operator_def, ws) {\n    // Since this is the default convolution implementation, we will\n    // use CAFFE_ENFORCE instead of OPERATOR_NEEDS_FEATURE.\n    CAFFE_ENFORCE(\n        group_ == 1 || order_ == StorageOrder::NCHW,\n        \"Group convolution only supports NCHW order right now.\");\n  }\n  ~GLConvOp() {}\n\n  bool RunOnDevice() override;\nprivate:\n  arm_compute::GCDirectConvolutionLayer conv_;\n  bool first_run_ = true, second_run_ = true;\n  GLContext::deleted_unique_ptr<const GLTensor<T>> X_, filter_, bias_;\n};\n\ntemplate <typename T>\nbool GLConvOp<T>::RunOnDevice() {\n  auto *Xblob = OperatorBase::Inputs()[0];\n  auto *filterblob = OperatorBase::Inputs()[1];\n  auto *biasblob = OperatorBase::Inputs()[2];\n\n  if (first_run_) {\n    X_ = GLContext::getGLTensor<T>(Xblob);\n    filter_ = GLContext::getGLTensor<T>(filterblob);\n    bias_ = GLContext::getGLTensor<T>(biasblob);\n  }\n\n  GLTensor<T> *Y =\n    OperatorBase::Outputs()[0]->template GetMutable<GLTensor<T>>();\n\n  const int N = X_->dim32(0), H = X_->dim32(2), W = X_->dim32(3), C = X_->dim32(1);\n\n  CAFFE_ENFORCE_EQ(kernel_.size(), 2,\n                   \"Only 2d convolution is supported with ARM compute backend\");\n\n  CAFFE_ENFORCE(X_->ndim(), filter_->ndim());\n  const int M = filter_->dim32(0);\n  CAFFE_ENFORCE(filter_->dim32(2) == kernel_h());\n  CAFFE_ENFORCE(filter_->dim32(3) == kernel_w());\n  CAFFE_ENFORCE(filter_->dim32(1) == C);\n\n  if (first_run_) {\n    first_run_ = false;\n\n    // resize output accordingly\n    TensorCPU fakeX;\n    fakeX.Resize(X_->dims());\n    TensorCPU fakeY;\n    ConvPoolOpBase<GLContext>::SetOutputSize(fakeX, &fakeY, filter_->dim32(0));\n    Y->ResizeLike(fakeY);\n    LOG(INFO) << \"[C2DEBUG] dims of X \" << X_->dims();\n    LOG(INFO) << \"[C2DEBUG] dims of X(gctensor) \"\n      << X_->get_underlying()->info()->dimension(3) << \" \"\n      << X_->get_underlying()->info()->dimension(2) << \" \"\n      << X_->get_underlying()->info()->dimension(1) << \" \"\n      << X_->get_underlying()->info()->dimension(0) << \" \"\n    ;\n    LOG(INFO) << \"[C2DEBUG] dims of Y \" << Y->dims();\n    LOG(INFO) << \"[C2DEBUG] dims of Y(gctensor) \"\n      << Y->get_underlying()->info()->dimension(3) << \" \"\n      << Y->get_underlying()->info()->dimension(2) << \" \"\n      << Y->get_underlying()->info()->dimension(1) << \" \"\n      << Y->get_underlying()->info()->dimension(0) << \" \"\n    ;\n\n    conv_.configure(\n        X_->get_underlying(), filter_->get_underlying(), bias_->get_underlying(),\n        Y->get_underlying(),\n        arm_compute::PadStrideInfo(stride_[0], stride_[1], pads_[0], pads_[1]));\n\n  } else {\n    // Always attempt to copy the CPU to GPU on input\n    X_->lazy_allocate(Xblob, second_run_, true);\n    filter_->lazy_allocate(filterblob, second_run_, second_run_);\n    bias_->lazy_allocate(biasblob, second_run_, second_run_);\n    if (second_run_) {\n      second_run_ = false;\n      if (Y->get_underlying() != X_->get_underlying()) {\n        Y->allocate();\n      }\n    }\n    conv_.run();\n  }\n\n  return true;\n}\n\nREGISTER_GL_OPERATOR(Conv, GLConvOp<DataType>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/copy_op.cc",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T> class CopyFromGLOp final : public Operator<GLContext> {\npublic:\n  CopyFromGLOp(const OperatorDef &operator_def, Workspace *ws)\n      : Operator<GLContext>(operator_def, ws) {}\n  virtual ~CopyFromGLOp() noexcept {}\n  USE_OPERATOR_FUNCTIONS(GLContext);\n  bool RunOnDevice() override;\nprivate:\n  bool first_run_ = true, second_run_ = true;\n  std::vector<GLContext::deleted_unique_ptr<const GLTensor<T>>> inputs_;\n};\n\ntemplate <typename T>\nbool CopyFromGLOp<T>::RunOnDevice() {\n\n  auto *X0blob = OperatorBase::Inputs()[0];\n  auto X0 = GLContext::getGLTensor<T>(X0blob);\n\n  if (first_run_) {\n    inputs_.push_back(std::move(X0));\n  }\n  std::vector<const Blob*> inputsBlob;\n  inputsBlob.push_back(X0blob);\n\n  if (first_run_) {\n    for (int i = 1; i < Inputs().size(); ++i) {\n      auto *Xblob = OperatorBase::Inputs()[i];\n      auto X = GLContext::getGLTensor<T>(Xblob);\n      inputs_.push_back(std::move(X));\n    }\n  }\n\n  for (int i = 1; i < Inputs().size(); ++i) {\n    auto *Xblob = OperatorBase::Inputs()[i];\n    inputsBlob.push_back(Xblob);\n  }\n\n  if (first_run_) {\n    first_run_ = false;\n  } else {\n    for (int i = 0; i < inputs_.size(); ++i) {\n      auto* X = inputs_[i].get();\n      auto* Xblob = inputsBlob[i];\n      X->lazy_allocate(Xblob, second_run_, second_run_);\n    }\n    if (second_run_) {\n      // Don't need to allocate output\n      second_run_ = false;\n    }\n    for (auto i = 0; i < Inputs().size(); ++i) {\n      // Blob\n      auto* Xblob = inputsBlob[i];\n      // GLTensor\n      auto* X = inputs_[i].get();\n      Output(i)->Resize(X->dims());\n      Output(i)->template mutable_data<float>();\n      // hardcoding CPU Tensors to be float\n      CAFFE_ENFORCE_EQ(Output(i)->nbytes(), X->size() * sizeof(float));\n      getTensorCPU(*X, *(OperatorBase::Outputs()[i]->template GetMutable<TensorCPU>()));\n    }\n  }\n  return true;\n}\n\nREGISTER_GL_OPERATOR(CopyFromGL, CopyFromGLOp<DataType>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/elementwise_sum_op.cc",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/operator.h\"\n#include \"caffe2/operators/utility_ops.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T> class GLSumOp final : public Operator<GLContext> {\npublic:\n  GLSumOp(const OperatorDef &operator_def, Workspace *ws)\n      : Operator<GLContext>(operator_def, ws) {}\n  virtual ~GLSumOp() noexcept {}\n  USE_OPERATOR_FUNCTIONS(GLContext);\n  bool RunOnDevice() override;\nprivate:\n  arm_compute::GCArithmeticAddition add_layer_;\n  bool first_run_ = true, second_run_ = true;\n  GLContext::deleted_unique_ptr<const GLTensor<T>> A_, B_;\n};\n\n\ntemplate <typename T>\nbool GLSumOp<T>::RunOnDevice() {\n\n  auto *Ablob = OperatorBase::Inputs()[0];\n  auto *Bblob = OperatorBase::Inputs()[1];\n\n  if (first_run_) {\n    A_ = GLContext::getGLTensor<T>(Ablob);\n    B_ = GLContext::getGLTensor<T>(Bblob);\n  }\n\n  GLTensor<T> *Y =\n      OperatorBase::Outputs()[0]->template GetMutable<GLTensor<T>>();\n  if (first_run_) {\n    first_run_ = false;\n    Y->ResizeLike(*A_);\n    add_layer_.configure(A_->get_underlying(), B_->get_underlying(), Y->get_underlying(), arm_compute::ConvertPolicy::SATURATE);\n  } else {\n    A_->lazy_allocate(Ablob, second_run_, true);\n    B_->lazy_allocate(Bblob, second_run_, true);\n    if (second_run_) {\n      Y->allocate();\n      second_run_ = false;\n    }\n    add_layer_.run();\n  }\n\n  return true;\n}\n\nREGISTER_GL_OPERATOR(Sum, GLSumOp<DataType>);\nREGISTER_GL_OPERATOR(Add, GLSumOp<DataType>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/fully_connected_op.cc",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/operator.h\"\n\n#include \"caffe2/operators/fully_connected_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T> class GLFullyConnectedOp final : public Operator<GLContext> {\npublic:\n  GLFullyConnectedOp(const OperatorDef &operator_def, Workspace *ws)\n      : Operator<GLContext>(operator_def, ws) {}\n  virtual ~GLFullyConnectedOp() noexcept {}\n  USE_OPERATOR_FUNCTIONS(GLContext);\n  bool RunOnDevice() override;\nprivate:\n  arm_compute::GCFullyConnectedLayer fc_layer_;\n  bool first_run_ = true, second_run_ = true;\n  GLContext::deleted_unique_ptr<const GLTensor<T>> X_, W_, B_;\n};\n\ntemplate <typename T>\nbool GLFullyConnectedOp<T>::RunOnDevice() {\n\n  auto Xblob = OperatorBase::Inputs()[0];\n  auto *Wblob = OperatorBase::Inputs()[1];\n  auto *Bblob = OperatorBase::Inputs()[2];\n\n  if (first_run_) {\n    X_ = GLContext::getGLTensor<T>(Xblob);\n    W_ = GLContext::getGLTensor<T>(Wblob);\n    B_ = GLContext::getGLTensor<T>(Bblob);\n  }\n\n  auto M = X_->dim32(0);\n  auto CIn = X_->dim32(1);\n  auto Height = X_->dim32(2);\n  auto Width = X_->dim32(3);\n  auto N = W_->dim32(0);\n\n  CAFFE_ENFORCE_EQ(1, B_->ndim());\n  CAFFE_ENFORCE_EQ(N, B_->dim32(0));\n\n  vector<TIndex> output_dims = {M, N};\n  GLTensor<T> *Y =\n      OperatorBase::Outputs()[0]->template GetMutable<GLTensor<T>>();\n  if (first_run_) {\n    first_run_ = false;\n    Y->Resize(output_dims);\n\n    fc_layer_.configure(X_->get_underlying(), W_->get_underlying(),\n                     B_->get_underlying(), Y->get_underlying(), true, false);\n  } else {\n    X_->lazy_allocate(Xblob, second_run_, true);\n    W_->lazy_allocate(Wblob, second_run_, second_run_);\n    B_->lazy_allocate(Bblob, second_run_, second_run_);\n    if (second_run_) {\n      second_run_ = false;\n      Y->allocate();\n    }\n    fc_layer_.run();\n  }\n\n  return true;\n}\n\nREGISTER_GL_OPERATOR(FC, GLFullyConnectedOp<DataType>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/norm_planar_yuv_op.cc",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\nclass GLNormalizePlanarYUVOp final : public Operator<GLContext> {\npublic:\n  GLNormalizePlanarYUVOp(const OperatorDef &operator_def, Workspace *ws)\n      : Operator<GLContext>(operator_def, ws) {}\n  virtual ~GLNormalizePlanarYUVOp() noexcept {}\n  USE_OPERATOR_FUNCTIONS(GLContext);\n  bool RunOnDevice() override;\nprivate:\n  arm_compute::GCNormalizePlanarYUVLayer norm_layer_;\n  bool first_run_ = true, second_run_ = true;\n  GLContext::deleted_unique_ptr<const GLTensor<T>> X_, mean_, sd_;\n};\n\ntemplate <typename T> bool GLNormalizePlanarYUVOp<T>::RunOnDevice() {\n\n  auto Xblob = OperatorBase::Inputs()[0];\n  auto *meanblob = OperatorBase::Inputs()[1];\n  auto *sdblob = OperatorBase::Inputs()[2];\n\n  if (first_run_) {\n    X_ = GLContext::getGLTensor<T>(Xblob);\n    mean_ = GLContext::getGLTensor<T>(meanblob);\n    sd_ = GLContext::getGLTensor<T>(sdblob);\n  }\n\n  CAFFE_ENFORCE_EQ(X_->ndim(), 4);\n  auto N = X_->dim32(0);\n  auto C = X_->dim32(1);\n  auto H = X_->dim32(2);\n  auto W = X_->dim32(3);\n\n  CAFFE_ENFORCE_EQ(C, mean_->dim32(1));\n  CAFFE_ENFORCE_EQ(C, sd_->dim32(1));\n\n  GLTensor<T> *Y =\n      OperatorBase::Outputs()[0]->template GetMutable<GLTensor<T>>();\n  if (first_run_) {\n    first_run_ = false;\n    Y->ResizeLike(*X_);\n    norm_layer_.configure(X_->get_underlying(), Y->get_underlying(), mean_->get_underlying(), sd_->get_underlying());\n  } else {\n    X_->lazy_allocate(Xblob, second_run_, true);\n    mean_->lazy_allocate(meanblob, second_run_, second_run_);\n    sd_->lazy_allocate(sdblob, second_run_, second_run_);\n    if (second_run_) {\n      second_run_ = false;\n      Y->allocate();\n    }\n    norm_layer_.run();\n  }\n\n  return true;\n}\n\nREGISTER_GL_OPERATOR(NormalizePlanarYUV, GLNormalizePlanarYUVOp<DataType>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/pool_op.cc",
    "content": "#include \"caffe2/operators/pool_op.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\nclass GLAveragePoolOp final : public ConvPoolOpBase<GLContext> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(GLContext);\n  GLAveragePoolOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<GLContext>(operator_def, ws) {\n  }\n  ~GLAveragePoolOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\nprivate:\n  arm_compute::GCPoolingLayer pooling_layer_;\n  bool first_run_ = true, second_run_ = true;\n  GLContext::deleted_unique_ptr<const GLTensor<T>> X_;\n};\n\ntemplate<typename T>\nclass GLMaxPoolOp final : public ConvPoolOpBase<GLContext> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(GLContext);\n  GLMaxPoolOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<GLContext>(operator_def, ws) {\n  }\n  ~GLMaxPoolOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\nprivate:\n  arm_compute::GCPoolingLayer pooling_layer_;\n  bool first_run_ = true, second_run_ = true;\n  GLContext::deleted_unique_ptr<const GLTensor<T>> X_;\n};\n\ntemplate <>\nbool GLAveragePoolOp<half>::RunOnDeviceWithOrderNCHW() {\n\n  auto *Xblob = OperatorBase::Inputs()[0];\n  if (first_run_) {\n    X_ = GLContext::getGLTensor<half>(Xblob);\n  }\n\n  int N = X_->dim32(0);\n  int channels = X_->dim32(1);\n  int height = X_->dim32(2);\n  int width = X_->dim32(3);\n\n  GLTensor<half> *Y =\n      OperatorBase::Outputs()[0]->template GetMutable<GLTensor<half>>();\n  if (first_run_) {\n    first_run_ = false;\n    CAFFE_ENFORCE_EQ(kernel_.size(), 2, \"ARM OpenGL only supports 2D pooling\");\n    CAFFE_ENFORCE_EQ(kernel_h(), kernel_w(),\n                     \"ARM OpenGL only supports equal kernel size\");\n    if (global_pooling_) {\n      vector<TIndex> output_dims = {N, channels, 1, 1};\n      Y->Resize(output_dims);\n    } else {\n      vector<TIndex> output_dims = {N, channels, 0, 0};\n      output_dims[2] = (height + pad_t() + pad_b() - kernel_h()) / stride_h() + 1;\n      output_dims[3] = (width + pad_l() + pad_r() - kernel_w()) / stride_w() + 1;\n      Y->Resize(output_dims);\n    }\n    if (global_pooling_) {\n      arm_compute::PoolingLayerInfo info(arm_compute::PoolingType::AVG);\n      pooling_layer_.configure(X_->get_underlying(), Y->get_underlying(), info);\n    } else {\n      arm_compute::PadStrideInfo ps_info(stride_w(), stride_h(), pad_l(), pad_r(),\n                                         pad_t(), pad_b(),\n                                         arm_compute::DimensionRoundingType::FLOOR);\n      arm_compute::PoolingLayerInfo info(arm_compute::PoolingType::AVG, kernel_h(),\n                                         ps_info);\n      pooling_layer_.configure(X_->get_underlying(), Y->get_underlying(), info);\n    }\n  } else {\n    X_->lazy_allocate(Xblob, second_run_, true);\n    if (second_run_) {\n      second_run_ = false;\n      Y->allocate();\n    }\n    pooling_layer_.run();\n  }\n\n  return true;\n}\n\ntemplate <> bool GLMaxPoolOp<half>::RunOnDeviceWithOrderNCHW() {\n\n  auto *Xblob = OperatorBase::Inputs()[0];\n  if (first_run_) {\n    X_ = GLContext::getGLTensor<half>(Xblob);\n  }\n\n  int N = X_->dim32(0);\n  int channels = X_->dim32(1);\n  int height = X_->dim32(2);\n  int width = X_->dim32(3);\n\n  GLTensor<half> *Y =\n      OperatorBase::Outputs()[0]->template GetMutable<GLTensor<half>>();\n\n  if (first_run_) {\n    first_run_ = false;\n    CAFFE_ENFORCE_EQ(kernel_.size(), 2, \"ARM OpenGL only supports 2D pooling\");\n    CAFFE_ENFORCE_EQ(kernel_h(), kernel_w(),\n                     \"ARM OpenGL only supports equal kernel size\");\n    if (global_pooling_) {\n      vector<TIndex> output_dims = {N, channels, 1, 1};\n      Y->Resize(output_dims);\n    } else {\n      vector<int> output_dims = {1, 0, 0, 0};\n      output_dims[1] = channels;\n      output_dims[2] = (height + pad_t() + pad_b() - kernel_h()) / stride_h() + 1;\n      output_dims[3] = (width + pad_l() + pad_r() - kernel_w()) / stride_w() + 1;\n      Y->Resize(output_dims);\n    }\n    if (global_pooling_) {\n      arm_compute::PoolingLayerInfo info(arm_compute::PoolingType::MAX);\n      pooling_layer_.configure(X_->get_underlying(), Y->get_underlying(), info);\n    } else {\n      arm_compute::PadStrideInfo ps_info(stride_w(), stride_h(), pad_l(), pad_r(),\n                                         pad_t(), pad_b(),\n                                         arm_compute::DimensionRoundingType::FLOOR);\n      arm_compute::PoolingLayerInfo info(arm_compute::PoolingType::MAX, kernel_h(),\n                                         ps_info);\n      pooling_layer_.configure(X_->get_underlying(), Y->get_underlying(), info);\n    }\n  } else {\n    X_->lazy_allocate(Xblob, second_run_, true);\n    if (second_run_) {\n      second_run_ = false;\n      Y->allocate();\n    }\n    pooling_layer_.run();\n  }\n\n  return true;\n}\n\ntemplate <>\nbool GLAveragePoolOp<half>::RunOnDeviceWithOrderNHWC() {\n  return false;\n}\n\ntemplate <>\nbool GLMaxPoolOp<half>::RunOnDeviceWithOrderNHWC() {\n  return false;\n}\n\nREGISTER_GL_OPERATOR(AveragePool, GLAveragePoolOp<DataType>);\nREGISTER_GL_OPERATOR(MaxPool, GLMaxPoolOp<DataType>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/reshape_op.cc",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/operator.h\"\n#include \"caffe2/operators/reshape_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T> class GLReshapeOp final : public Operator<GLContext> {\npublic:\n  GLReshapeOp(const OperatorDef &operator_def, Workspace *ws)\n      : Operator<GLContext>(operator_def, ws) {}\n  virtual ~GLReshapeOp() noexcept {}\n  USE_OPERATOR_FUNCTIONS(GLContext);\n  bool RunOnDevice() override;\n};\n\ntemplate <typename T>\nbool GLReshapeOp<T>::RunOnDevice() {\n  auto *Xblob = OperatorBase::Inputs()[0];\n  auto X = GLContext::getGLTensor<T>(Xblob);\n  LOG(INFO) << \"[C2DEBUG] X: \" << X->dim32(0) << \" \" << X->dim32(1) << \" \" << X->dim32(2) << \" \" << X->dim32(3);\n  auto arg = OperatorBase::GetRepeatedArgument<int>(\"shape\");\n  for (int i = 0; i < arg.size(); ++i) {\n    LOG(INFO) << \"[C2DEBUG] shape: \" << arg[i];\n  }\n  return true;\n}\n\nREGISTER_GL_OPERATOR(Reshape, GLReshapeOp<DataType>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/resize_op.cc",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/operator.h\"\n#include \"caffe2/operators/resize_op.h\"\n\nnamespace caffe2 {\n\ntemplate<typename T>\nclass GLResizeNearestOp final : public Operator<GLContext> {\npublic:\n  GLResizeNearestOp(const OperatorDef &operator_def, Workspace *ws)\n    : Operator<GLContext>(operator_def, ws), width_scale_(1), height_scale_(1) {\n    if (HasArgument(\"width_scale\")) {\n      width_scale_ = static_cast<float>(\n          OperatorBase::GetSingleArgument<float>(\"width_scale\", 1));\n    }\n    if (HasArgument(\"height_scale\")) {\n      height_scale_ = static_cast<float>(\n          OperatorBase::GetSingleArgument<float>(\"height_scale\", 1));\n    }\n    CAFFE_ENFORCE_GT(width_scale_, 0);\n    CAFFE_ENFORCE_GT(height_scale_, 0);\n  }\n  virtual ~GLResizeNearestOp() noexcept {}\n  USE_OPERATOR_FUNCTIONS(GLContext);\n  bool RunOnDevice() override;\nprivate:\n  float width_scale_;\n  float height_scale_;\n  arm_compute::GCScale resize_layer_;\n  bool first_run_ = true, second_run_ = true;\n  GLContext::deleted_unique_ptr<const GLTensor<T>> X_;\n};\n\ntemplate <typename T>\nbool GLResizeNearestOp<T>::RunOnDevice() {\n\n  auto* Xblob = OperatorBase::Inputs()[0];\n\n  if (first_run_) {\n    X_ = GLContext::getGLTensor<T>(Xblob);\n  }\n\n  auto N = X_->dim32(0);\n  auto C = X_->dim32(1);\n  auto H = X_->dim32(2);\n  auto W = X_->dim32(3);\n\n  GLTensor<T> *Y =\n      OperatorBase::Outputs()[0]->template GetMutable<GLTensor<T>>();\n  if (first_run_) {\n    vector<TIndex> output_dims = {N, C, H * height_scale_, W * width_scale_};\n    Y->Resize(output_dims);\n    first_run_ = false;\n    resize_layer_.configure(X_->get_underlying(), Y->get_underlying(), arm_compute::InterpolationPolicy::NEAREST_NEIGHBOR, arm_compute::BorderMode::UNDEFINED);\n  } else {\n    X_->lazy_allocate(Xblob, second_run_, true);\n    if (second_run_) {\n      second_run_ = false;\n      Y->allocate();\n    }\n    resize_layer_.run();\n  }\n\n  return true;\n}\n\nREGISTER_GL_OPERATOR(ResizeNearest, GLResizeNearestOp<DataType>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/softmax_op.cc",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/operator.h\"\n\n#include \"caffe2/operators/softmax_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T> class GLSoftmaxOp final : public Operator<GLContext> {\npublic:\n  GLSoftmaxOp(const OperatorDef &operator_def, Workspace *ws)\n      : Operator<GLContext>(operator_def, ws) {}\n  virtual ~GLSoftmaxOp() noexcept {}\n  USE_OPERATOR_FUNCTIONS(GLContext);\n  bool RunOnDevice() override;\nprivate:\n  arm_compute::GCSoftmaxLayer softmax_layer_;\n  bool first_run_ = true, second_run_ = true;\n  GLContext::deleted_unique_ptr<const GLTensor<T>> X_;\n};\n\ntemplate <typename T>\nbool GLSoftmaxOp<T>::RunOnDevice() {\n\n  auto *Xblob = OperatorBase::Inputs()[0];\n  if (first_run_) {\n    X_ = GLContext::getGLTensor<T>(Xblob);\n  }\n\n  GLTensor<T> *Y =\n      OperatorBase::Outputs()[0]->template GetMutable<GLTensor<T>>();\n  if (first_run_) {\n    first_run_ = false;\n    Y->ResizeLike(*X_);\n    softmax_layer_.configure(X_->get_underlying(), Y->get_underlying());\n  } else {\n    X_->lazy_allocate(Xblob, second_run_, true);\n    if (second_run_) {\n      second_run_ = false;\n      Y->allocate();\n    }\n    softmax_layer_.run();\n  }\n\n  return true;\n}\n\nREGISTER_GL_OPERATOR(Softmax, GLSoftmaxOp<DataType>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/operators/spatial_batch_norm_op.cc",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/operator.h\"\n\n#include \"caffe2/operators/spatial_batch_norm_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T> class GLSpatialBNOp final : public Operator<GLContext> {\npublic:\n  GLSpatialBNOp(const OperatorDef &operator_def, Workspace *ws)\n      : Operator<GLContext>(operator_def, ws),\n        is_test_(OperatorBase::GetSingleArgument<int>(OpSchema::Arg_IsTest, 0)),\n        epsilon_(OperatorBase::GetSingleArgument<float>(\"epsilon\", 1e-5)),\n        momentum_(OperatorBase::GetSingleArgument<float>(\"momentum\", 0.9)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) { }\n  virtual ~GLSpatialBNOp() noexcept {}\n  USE_OPERATOR_FUNCTIONS(GLContext);\n  bool RunOnDevice() override;\n protected:\n  bool is_test_;\n  double epsilon_;\n  double momentum_;\n  StorageOrder order_;\n  INPUT_TAGS(INPUT, SCALE, BIAS, EST_MEAN, EST_VAR);\n  OUTPUT_TAGS(OUTPUT, RUNNING_MEAN, RUNNING_VAR, SAVED_MEAN, SAVED_INV_VAR);\nprivate:\n  arm_compute::GCBatchNormalizationLayer bn_layer_;\n  bool first_run_ = true, second_run_ = true;\n  GLContext::deleted_unique_ptr<const GLTensor<T>> X_, mean_, var_, bias_, scale_;\n};\n\ntemplate <typename T>\nbool GLSpatialBNOp<T>::RunOnDevice() {\n  auto *XBlob = OperatorBase::Inputs()[0];\n  auto *scaleBlob = OperatorBase::Inputs()[SCALE];\n  auto *biasBlob = OperatorBase::Inputs()[BIAS];\n  auto *meanBlob = OperatorBase::Inputs()[EST_MEAN];\n  auto *varBlob = OperatorBase::Inputs()[EST_VAR];\n\n  if (first_run_) {\n    X_ = GLContext::getGLTensor<T>(XBlob);\n    scale_ = GLContext::getGLTensor<T>(scaleBlob);\n    bias_ = GLContext::getGLTensor<T>(biasBlob);\n    mean_ = GLContext::getGLTensor<T>(meanBlob);\n    var_ = GLContext::getGLTensor<T>(varBlob);\n  }\n\n  auto C = X_->dim32(1);\n  CAFFE_ENFORCE_EQ(scale_->ndim(), 1);\n  CAFFE_ENFORCE_EQ(bias_->ndim(), 1);\n  CAFFE_ENFORCE_EQ(mean_->ndim(), 1);\n  CAFFE_ENFORCE_EQ(var_->ndim(), 1);\n\n  CAFFE_ENFORCE_EQ(scale_->dim32(0), C);\n  CAFFE_ENFORCE_EQ(bias_->dim32(0), C);\n  CAFFE_ENFORCE_EQ(mean_->dim32(0), C);\n  CAFFE_ENFORCE_EQ(var_->dim32(0), C);\n\n  GLTensor<T> *Y =\n      OperatorBase::Outputs()[0]->template GetMutable<GLTensor<T>>();\n  if (first_run_) {\n    first_run_ = false;\n    Y->ResizeLike(*X_);\n    bn_layer_.configure(X_->get_underlying(), Y->get_underlying(),\n                     mean_->get_underlying(), var_->get_underlying(),\n                     bias_->get_underlying(), scale_->get_underlying(), epsilon_);\n  } else {\n    X_->lazy_allocate(XBlob, second_run_, true);\n    scale_->lazy_allocate(scaleBlob, second_run_, second_run_);\n    bias_->lazy_allocate(biasBlob, second_run_, second_run_);\n    mean_->lazy_allocate(meanBlob, second_run_, second_run_);\n    var_->lazy_allocate(varBlob, second_run_, second_run_);\n    if (second_run_) {\n      second_run_ = false;\n      Y->allocate();\n    }\n    bn_layer_.run();\n  }\n  return true;\n}\n\nREGISTER_GL_OPERATOR(SpatialBN, GLSpatialBNOp<DataType>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/run_tests.sh",
    "content": "set -vex\n\nif [ -z \"$CAFFE2_BINARY_DIR\" ] ; then\n  if [ -z \"$1\" ] ; then\n    CAFFE2_BINARY_DIR=.\n  else\n    CAFFE2_BINARY_DIR=$1\n  fi\nfi\n\nfiles=($(find \"$CAFFE2_BINARY_DIR\" -type f -name \"*_test\"))\nfor test_binary in \"${files[@]}\";\ndo\n  test_binary_base=$(basename $test_binary)\n  if [[ $test_binary_base == gl* ]];then\n    echo Running $test_binary_base\n    adb push $test_binary \"/data/local/tmp/$test_binary_base\"\n    adb shell \"GLOG_logtostderr=1 /data/local/tmp/$test_binary_base\" \n  fi\ndone\n\necho All tests passed.\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/CMakeLists.txt",
    "content": "file(GLOB tmp *_test.cc)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${tmp} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_activation_ops_test.cc",
    "content": "#include \"gl_operator_test.h\"\n\nnamespace caffe2 {\n\nTEST(OPENGLOperatorTest, Sigmoid) {\n  Workspace ws;\n\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {1, 4, 4, 4});\n\n  NetDef cpu_net;\n  {\n    AddOp(&cpu_net, \"Sigmoid\", {\"cpu_X\"}, {\"ref_Y\"});\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Sigmoid\", {\"cpu_X\"}, {\"gpu_Y\"});\n    MAKE_OPENGL_OPERATOR(def);\n  }\n  compareNetResult(ws, cpu_net, gpu_net);\n\n}\n\nTEST(OPENGLOperatorTest, ReLU) {\n  Workspace ws;\n\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {1, 4, 4, 4});\n\n  NetDef cpu_net;\n  {\n    AddOp(&cpu_net, \"Relu\", {\"cpu_X\"}, {\"ref_Y\"});\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Relu\", {\"cpu_X\"}, {\"gpu_Y\"});\n    MAKE_OPENGL_OPERATOR(def);\n  }\n\n  compareNetResult(ws, cpu_net, gpu_net);\n}\n\nTEST(OPENGLOperatorTest, SigmoidTwice) {\n  Workspace ws;\n\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {1, 4, 4, 4});\n\n  NetDef cpu_net;\n  {\n    AddOp(&cpu_net, \"Sigmoid\", {\"cpu_X\"}, {\"ref_Y1\"});\n    AddOp(&cpu_net, \"Sigmoid\", {\"ref_Y1\"}, {\"ref_Y2\"});\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Sigmoid\", {\"cpu_X\"}, {\"gpu_Y1\"});\n    MAKE_OPENGL_OPERATOR(def);\n  }\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Sigmoid\", {\"gpu_Y1\"}, {\"gpu_Y2\"});\n    MAKE_OPENGL_OPERATOR(def);\n  }\n\n  compareNetResult(ws, cpu_net, gpu_net, \"ref_Y2\", \"gpu_Y2\");\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_alignment_test.cc",
    "content": "#include \"gl_operator_test.h\"\n#include \"caffe2/core/timer.h\"\n\nnamespace caffe2 {\n\nconstexpr float tol = 5.0e-2;\n\n// {MaxPool, Relu, Add} followed by pad 1 conv\nTEST(OPENGLOperatorTest, ConvMaxPoolConv) {\n\n  Workspace ws;\n  auto channel_in = 16;\n  auto channel_out = 16;\n  auto spatial = 32;\n  auto kern = 3;\n\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {1, channel_in, spatial, spatial}, 1337);\n  PopulateCPUBlob(&ws, true, \"W\", {channel_out, channel_in, kern, kern}, 1337);\n  PopulateCPUBlob(&ws, false, \"b\", {channel_out}, 0);\n  PopulateCPUBlob(&ws, true, \"W2\", {channel_out, channel_in, kern, kern});\n  PopulateCPUBlob(&ws, true, \"b2\", {channel_out});\n\n#define ADD_CONV_ARGS                                                          \\\n  {                                                                            \\\n    ADD_ARG((*def), \"kernel\", i, kern);                                           \\\n    ADD_ARG((*def), \"stride\", i, 1);                                              \\\n    ADD_ARG((*def), \"pad\", i, 1);                                                 \\\n    ADD_ARG((*def), \"order\", s, \"NCHW\");                                          \\\n  }\n\n  NetDef cpu_net;\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"Conv\", {\"cpu_X\", \"W\", \"b\"}, {\"ref_Y\"});\n    def->set_name(\"cpu_conv\");\n    ADD_CONV_ARGS;\n  }\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"MaxPool\", {\"ref_Y\"}, {\"ref_maxpool\"});\n    ADD_ARG((*def), \"kernel\", i, 2);\n    ADD_ARG((*def), \"pad\", i, 0);\n    ADD_ARG((*def), \"stride_w\", i, 2);\n    ADD_ARG((*def), \"stride_h\", i, 2);\n    ADD_ARG((*def), \"order\", s, \"NCHW\");\n  }\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"Conv\", {\"ref_maxpool\", \"W2\", \"b2\"}, {\"ref_Y2\"});\n    ADD_CONV_ARGS;\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Conv\", {\"cpu_X\", \"W\", \"b\"}, {\"gpu_Y\"});\n    MAKE_OPENGL_OPERATOR(def);\n    ADD_CONV_ARGS;\n  }\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"MaxPool\", {\"gpu_Y\"}, {\"gpu_maxpool\"});\n    ADD_ARG((*def), \"kernel\", i, 2);\n    ADD_ARG((*def), \"pad\", i, 0);\n    ADD_ARG((*def), \"stride_w\", i, 2);\n    ADD_ARG((*def), \"stride_h\", i, 2);\n    ADD_ARG((*def), \"order\", s, \"NCHW\");\n    MAKE_OPENGL_OPERATOR(def);\n  }\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Conv\", {\"gpu_maxpool\", \"W2\", \"b2\"}, {\"gpu_Y2\"});\n    MAKE_OPENGL_OPERATOR(def);\n    ADD_CONV_ARGS;\n  }\n\n#undef ADD_CONV_ARGS\n\n  // will work after next release of ACL\n  // compareNetResult4D(ws, cpu_net, gpu_net, \"ref_Y2\", \"gpu_Y2\", tol);\n}\n\nTEST(OPENGLOperatorTest, ConvReluConv) {\n\n  Workspace ws;\n  auto channel_in = 16;\n  auto channel_out = 16;\n  auto spatial = 32;\n  auto kern = 3;\n\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {1, channel_in, spatial, spatial}, 1337);\n  PopulateCPUBlob(&ws, true, \"W\", {channel_out, channel_in, kern, kern}, 1337);\n  PopulateCPUBlob(&ws, false, \"b\", {channel_out}, 0);\n  PopulateCPUBlob(&ws, true, \"W2\", {channel_out, channel_in, kern, kern});\n  PopulateCPUBlob(&ws, true, \"b2\", {channel_out});\n\n#define ADD_CONV_ARGS                                                          \\\n  {                                                                            \\\n    ADD_ARG((*def), \"kernel\", i, kern);                                           \\\n    ADD_ARG((*def), \"stride\", i, 1);                                              \\\n    ADD_ARG((*def), \"pad\", i, 1);                                                 \\\n    ADD_ARG((*def), \"order\", s, \"NCHW\");                                          \\\n  }\n\n  NetDef cpu_net;\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"Conv\", {\"cpu_X\", \"W\", \"b\"}, {\"ref_Y\"});\n    def->set_name(\"cpu_conv\");\n    ADD_CONV_ARGS;\n  }\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"Relu\", {\"ref_Y\"}, {\"ref_relu\"});\n  }\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"Conv\", {\"ref_relu\", \"W2\", \"b2\"}, {\"ref_Y2\"});\n    ADD_CONV_ARGS;\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Conv\", {\"cpu_X\", \"W\", \"b\"}, {\"gpu_Y\"});\n    MAKE_OPENGL_OPERATOR(def);\n    ADD_CONV_ARGS;\n  }\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Relu\", {\"gpu_Y\"}, {\"gpu_relu\"});\n    MAKE_OPENGL_OPERATOR(def);\n  }\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Conv\", {\"gpu_relu\", \"W2\", \"b2\"}, {\"gpu_Y2\"});\n    MAKE_OPENGL_OPERATOR(def);\n    ADD_CONV_ARGS;\n  }\n\n#undef ADD_CONV_ARGS\n\n  // will work after next release of ACL\n  // compareNetResult4D(ws, cpu_net, gpu_net, \"ref_Y2\", \"gpu_Y2\", tol);\n\n}\n\nTEST(OPENGLOperatorTest, ConvAddConv) {\n\n  Workspace ws;\n  auto channel_in = 16;\n  auto channel_out = 16;\n  auto spatial = 32; // --> 2x2 w no padding, all values 9\n  auto kern = 3;\n\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {1, channel_in, spatial, spatial}, 1337);\n  PopulateCPUBlob(&ws, true, \"W\", {channel_out, channel_in, kern, kern}, 1337);\n  PopulateCPUBlob(&ws, false, \"b\", {channel_out}, 0);\n  PopulateCPUBlob(&ws, true, \"W2\", {channel_out, channel_in, kern, kern});\n  PopulateCPUBlob(&ws, true, \"b2\", {channel_out});\n  PopulateCPUBlob(&ws, true, \"cpu_Y\", {1, channel_in, spatial, spatial}, 1337);\n\n#define ADD_CONV_ARGS                                                          \\\n  {                                                                            \\\n    ADD_ARG((*def), \"kernel\", i, kern);                                           \\\n    ADD_ARG((*def), \"stride\", i, 1);                                              \\\n    ADD_ARG((*def), \"pad\", i, 1);                                                 \\\n    ADD_ARG((*def), \"order\", s, \"NCHW\");                                          \\\n  }\n\n  NetDef cpu_net;\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"Conv\", {\"cpu_X\", \"W\", \"b\"}, {\"ref_Y\"});\n    def->set_name(\"cpu_conv\");\n    ADD_CONV_ARGS;\n  }\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"Add\", {\"ref_Y\", \"cpu_Y\"}, {\"ref_add\"});\n  }\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"Conv\", {\"ref_add\", \"W2\", \"b2\"}, {\"ref_Y2\"});\n    ADD_CONV_ARGS;\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Conv\", {\"cpu_X\", \"W\", \"b\"}, {\"gpu_Y\"});\n    MAKE_OPENGL_OPERATOR(def);\n    ADD_CONV_ARGS;\n  }\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Add\", {\"gpu_Y\", \"cpu_Y\"}, {\"gpu_add\"});\n    MAKE_OPENGL_OPERATOR(def);\n  }\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Conv\", {\"gpu_add\", \"W2\", \"b2\"}, {\"gpu_Y2\"});\n    MAKE_OPENGL_OPERATOR(def);\n    ADD_CONV_ARGS;\n  }\n#undef ADD_CONV_ARGS\n\n  // will work after next release of ACL\n  // compareNetResult4D(ws, cpu_net, gpu_net, \"ref_Y2\", \"gpu_Y2\", tol);\n\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_concat_op_test.cc",
    "content": "#include \"gl_operator_test.h\"\n\nnamespace caffe2 {\n\nTEST(OPENGLOperatorTest, Concat) {\n\n  for (auto Cs: std::vector<std::vector<int>>{\n      {4, 4},\n      {4, 4, 4},\n      {6, 6, 6},\n      {16, 8, 4},\n      {32, 8, 16, 4},\n    }) {\n    Workspace ws;\n    int batchSize = 1;\n    int H = 8;\n    int W = 8;\n    for (int i = 0; i < Cs.size(); ++i) {\n      PopulateCPUBlob(&ws, true, std::string(\"cpu_X\") + caffe2::to_string(i), {batchSize, Cs[i], H, W});\n    }\n\n  NetDef cpu_net;\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"Concat\", {}, {\"ref_Y\", \"cpu_dummy\"});\n      for (int i = 0; i < Cs.size(); ++i ) {\n        def->add_input(std::string(\"cpu_X\") + caffe2::to_string(i));\n      }\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Concat\", {}, {\"gpu_Y\", \"gpu_dummy\"});\n    MAKE_OPENGL_OPERATOR(def);\n    for (int i = 0; i < Cs.size(); ++i ) {\n      def->add_input(std::string(\"cpu_X\") + caffe2::to_string(i));\n    }\n  }\n\n  compareNetResult(ws, cpu_net, gpu_net);\n\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_context_test.cc",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\n\nTEST(OPENGLContextTest, Initialization) {\n  auto gc = new GLContext();\n  delete gc;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_conv_op_test.cc",
    "content": "#include \"gl_operator_test.h\"\n#include \"caffe2/core/timer.h\"\n\nnamespace caffe2 {\n\nconstexpr float tol = 3.0e-2;\n\nTEST(OPENGLOperatorTest, Conv) {\n\n Workspace ws;\n auto channel_in = 16;\n auto channel_out = 16;\n auto spatial = 16; // --> 2x2 w no padding, all values 9\n auto kern = 3;\n\n PopulateCPUBlob(&ws, true, \"cpu_X\", {1, channel_in, spatial, spatial}, 1337);\n PopulateCPUBlob(&ws, true, \"W\", {channel_out, channel_in, kern, kern}, 1337);\n PopulateCPUBlob(&ws, false, \"b\", {channel_out}, 0);\n\n#define ADD_CONV_ARGS                                                          \\\n  {                                                                            \\\n    ADD_ARG((*def), \"kernel\", i, kern);                                           \\\n    ADD_ARG((*def), \"stride\", i, 1);                                              \\\n    ADD_ARG((*def), \"pad\", i, 0);                                                 \\\n    ADD_ARG((*def), \"order\", s, \"NCHW\");                                          \\\n  }\n\n  NetDef cpu_net;\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"Conv\", {\"cpu_X\", \"W\", \"b\"}, {\"ref_Y\"});\n    def->set_name(\"cpu_conv\");\n    ADD_CONV_ARGS;\n  }\n  ws.RunNetOnce(cpu_net);\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n\n    OperatorDef* def = AddOp(&gpu_net, \"Conv\", {\"cpu_X\", \"W\", \"b\"}, {\"gpu_Y\"});\n    MAKE_OPENGL_OPERATOR(def);\n    ADD_CONV_ARGS;\n  }\n\n#undef ADD_CONV_ARGS\n\n  compareNetResult4D(ws, cpu_net, gpu_net, \"ref_Y\", \"gpu_Y\", tol);\n\n}\n\nTEST(OPENGLOperatorTest, ConvReluConv) {\n\n  Workspace ws;\n  auto channel_in = 16;\n  auto channel_out = 16;\n  auto spatial = 32; // --> 2x2 w no padding, all values 9\n  auto kern = 3;\n\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {1, channel_in, spatial, spatial}, 1337);\n  PopulateCPUBlob(&ws, true, \"W\", {channel_out, channel_in, kern, kern}, 1337);\n  PopulateCPUBlob(&ws, false, \"b\", {channel_out}, 0);\n  PopulateCPUBlob(&ws, true, \"W2\", {channel_out, channel_in, kern, kern});\n  PopulateCPUBlob(&ws, true, \"b2\", {channel_out});\n\n#define ADD_CONV_ARGS                                                          \\\n  {                                                                            \\\n    ADD_ARG((*def), \"kernel\", i, kern);                                           \\\n    ADD_ARG((*def), \"stride\", i, 1);                                              \\\n    ADD_ARG((*def), \"pad\", i, 0);                                                 \\\n    ADD_ARG((*def), \"order\", s, \"NCHW\");                                          \\\n  }\n\n  NetDef cpu_net;\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"Conv\", {\"cpu_X\", \"W\", \"b\"}, {\"ref_Y\"});\n    def->set_name(\"cpu_conv\");\n    ADD_CONV_ARGS;\n  }\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"Relu\", {\"ref_Y\"}, {\"ref_relu\"});\n  }\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"Conv\", {\"ref_relu\", \"W2\", \"b2\"}, {\"ref_Y2\"});\n    ADD_CONV_ARGS;\n  }\n\n  ws.RunNetOnce(cpu_net);\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Conv\", {\"cpu_X\", \"W\", \"b\"}, {\"gpu_Y\"});\n    MAKE_OPENGL_OPERATOR(def);\n    ADD_CONV_ARGS;\n  }\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Relu\", {\"gpu_Y\"}, {\"gpu_relu\"});\n    MAKE_OPENGL_OPERATOR(def);\n  }\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Conv\", {\"gpu_relu\", \"W2\", \"b2\"}, {\"gpu_Y2\"});\n    MAKE_OPENGL_OPERATOR(def);\n    ADD_CONV_ARGS;\n  }\n\n#undef ADD_CONV_ARGS\n\n  compareNetResult4D(ws, cpu_net, gpu_net, \"ref_Y2\", \"gpu_Y2\", tol);\n\n}\n\nTEST(OPENGLOperatorTest, ConvBenchmark) {\n\n Workspace ws;\n auto channel_in = 4;\n auto channel_out = 4;\n auto spatial = 10;\n auto kern = 3;\n long long iters = 2;\n\n PopulateCPUBlob(&ws, false, \"cpu_X\", {1, channel_in, spatial, spatial}, 1, 0, 0.1);\n\n#define ADD_CONV_ARGS(_def)                                                        \\\n {                                                                                 \\\n    ADD_ARG((*_def), \"kernel\", i, kern);                                           \\\n    ADD_ARG((*_def), \"stride\", i, 1);                                              \\\n    ADD_ARG((*_def), \"pad\", i, 0);                                                 \\\n    ADD_ARG((*_def), \"order\", s, \"NCHW\");                                          \\\n  }\n\n  NetDef gpu_net;\n  NetDef cpu_net;\n  gpu_net.set_type(\"opengl\");\n\n  std::string prev_out = \"cpu_X\";\n  for (auto i = 0; i < iters; ++i) {\n    std::string weightName = \"W\" + to_string(i);\n    std::string biasName = \"b\" + to_string(i);\n    std::string output = \"conv\" + to_string(i);\n    PopulateCPUBlob(&ws, false, weightName, {channel_out, channel_in, kern, kern}, 1);\n    PopulateCPUBlob(&ws, false, biasName, {channel_out}, 0);\n    OperatorDef* def = AddOp(&gpu_net, \"Conv\", {prev_out, weightName, biasName}, {output});\n    if (i == 0) {\n      OperatorDef* def2 = AddOp(&cpu_net, \"Conv\", {prev_out, weightName, biasName}, {\"cpu\" + output});\n    ADD_CONV_ARGS(def2);\n    } else {\n      OperatorDef* def2 = AddOp(&cpu_net, \"Conv\", {\"cpu\" + prev_out, weightName, biasName}, {\"cpu\" + output});\n    ADD_CONV_ARGS(def2);\n    }\n    prev_out = output;\n    MAKE_OPENGL_OPERATOR(def);\n    ADD_CONV_ARGS(def);\n  }\n\n#undef ADD_CONV_ARGS\n\n  compareNetResult4D(ws, cpu_net, gpu_net, \"cpu\" + prev_out, prev_out, tol);\n\n}\n\n} // namespace caffe2\n\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_copy_op_test.cc",
    "content": "#include \"gl_operator_test.h\"\n\nnamespace caffe2 {\n\nTEST(OPENGLOperatorTest, CopyFromGL) {\n\n  for (auto dims: std::vector<std::vector<int>>{\n      {1},\n      {1, 2},\n      {1, 2, 3},\n      {1, 2, 3, 4},\n      {4, 3, 2, 1},\n      {4, 9, 8, 13},\n    }) {\n    Workspace ws;\n    PopulateCPUBlob(&ws, true, std::string(\"cpu_X\"), dims);\n\n    NetDef gpu_net;\n    gpu_net.set_type(\"opengl\");\n    {\n      OperatorDef* def = AddOp(&gpu_net, \"CopyFromGL\", {\"cpu_X\"}, {\"cpu_X2\"});\n      MAKE_OPENGL_OPERATOR(def);\n    }\n    ws.RunNetOnce(gpu_net);\n    Blob *cpu_out = ws.GetBlob(\"cpu_X\");\n    Blob *gpu_out = ws.GetBlob(\"cpu_X2\");\n    EXPECT_NE(nullptr, cpu_out);\n    EXPECT_NE(nullptr, gpu_out);\n\n    auto &t1 = cpu_out->Get<TensorCPU>();\n    auto &t2 = gpu_out->Get<TensorCPU>();\n    double tol=0.01;\n    for (auto i = 0; i < t1.size(); ++i) {\n      EXPECT_NEAR(t1.data<float>()[i], t2.data<float>()[i], tol)\n        << \"at index \" << i;\n    }\n  }\n}\n\n} // namespace caffe2"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_elementwise_sum_op_test.cc",
    "content": "#include \"gl_operator_test.h\"\n\nnamespace caffe2 {\n\nTEST(OPENGLOperatorTest, Sum) {\n  Workspace ws;\n  int N = 28;\n  int D = 128;\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {N, D}, 1);\n  PopulateCPUBlob(&ws, true, \"cpu_Y\", {N, D}, 1);\n\n  NetDef cpu_net;\n  {\n    AddOp(&cpu_net, \"Sum\", {\"cpu_X\", \"cpu_Y\"}, {\"ref_Y\"});\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Sum\", {\"cpu_X\", \"cpu_Y\"}, {\"gpu_Y\"});\n    MAKE_OPENGL_OPERATOR(def);\n  }\n\n  compareNetResult(ws, cpu_net, gpu_net);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_fully_connected_op_test.cc",
    "content": "#include \"gl_operator_test.h\"\n\nnamespace caffe2 {\n\nTEST(OPENGLOperatorTest, FC) {\n\n  Workspace ws;\n  int batchSize = 1;\n  int CIn = 4;\n  int H = 8;\n  int W = 8;\n  int COut = 16;\n\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {batchSize, CIn, H, W});\n  PopulateCPUBlob(&ws, true, \"cpu_W\", {COut, CIn * H * W});\n  PopulateCPUBlob(&ws, true, \"cpu_B\", {COut});\n\n  constexpr float tol = 0.2;\n\n  NetDef cpu_net;\n  {\n    AddOp(&cpu_net, \"FC\", {\"cpu_X\", \"cpu_W\", \"cpu_B\"}, {\"ref_Y\"});\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"FC\", {\"cpu_X\", \"cpu_W\", \"cpu_B\"}, {\"gpu_Y\"});\n    MAKE_OPENGL_OPERATOR(def);\n  }\n\n  // will work after the next release of ACL\n  // compareNetResult(ws, cpu_net, gpu_net, \"ref_Y\", \"gpu_Y\", tol, true);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_model_test.cc",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/test/gl_model_test.h\"\n\nnamespace caffe2 {\n\n// The last softmax op didn't pass because of the dimension mismatch, and we are not likely to hit it in other models, but the implementation should be correct\n// TEST(OPENGLModelTest, SqueezenetV11) {\n//   std::string parent_path = \"/data/local/tmp/\";\n//   benchmarkModel(parent_path + \"squeezenet_init.pb\", parent_path + \"squeezenet_predict.pb\", \"data\", {1, 3, 224, 224}, \"squeezenet_v11\");\n// }\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_model_test.h",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include \"caffe2/mobile/contrib/arm-compute/test/gl_operator_test.h\"\n#include \"caffe2/mobile/contrib/arm-compute/core/rewrite_net.h\"\n#include <gtest/gtest.h>\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/workspace.h\"\n#include <unordered_set>\n\nCAFFE2_DEFINE_int(warmup, 10, \"The number of iterations to warm up.\");\nCAFFE2_DEFINE_int(iter, 100, \"The number of iterations to run.\");\nCAFFE2_DEFINE_bool(\n    run_individual,\n    false,\n    \"Whether to benchmark individual operators.\");\n\n\nconstexpr float tol = 0.03;\nnamespace caffe2 {\n  void benchmarkModel(std::string init_net_pb, std::string predict_net_pb, std::string input_name, std::vector<int> input_dims, std::string net_name=\"benchmark_net\", std::unordered_set<std::string> cpu_ops = std::unordered_set<std::string>({})) {\n    unique_ptr<caffe2::Workspace> ws(new caffe2::Workspace());\n    NetDef init_net_def;\n    CAFFE_ENFORCE(ReadProtoFromFile(init_net_pb, &init_net_def));\n    CAFFE_ENFORCE(ws->RunNetOnce(init_net_def));\n    NetDef predict_net_def, predict_net_def_gpu;\n    CAFFE_ENFORCE(ReadProtoFromFile(predict_net_pb, &predict_net_def));\n    PopulateCPUBlob(ws.get(), true, input_name, input_dims);\n\n    tryConvertToOpenGL(predict_net_def, &predict_net_def_gpu, false, cpu_ops);\n    // change the name of last op\n    auto index = predict_net_def_gpu.op().size() - 1;\n    auto last_blob = predict_net_def_gpu.op()[index].output()[0];\n    auto op = predict_net_def_gpu.mutable_op(index);\n    auto output = op->mutable_output(0);\n    *output = last_blob + \"_gpu\";\n\n    for (auto i = 0; i < predict_net_def_gpu.external_output_size(); ++i) {\n      auto out = predict_net_def_gpu.mutable_external_output(i);\n      if (*out == last_blob) {\n        *out = last_blob + \"_gpu\";\n      }\n    }\n\n    compareNetResult4D(*ws, predict_net_def, predict_net_def_gpu, last_blob, last_blob + \"_gpu\");\n\n  NetBase* net = ws->CreateNet(predict_net_def);\n  LOG(INFO) << \"[C2DEBUG] Benchmarking OpenGL Net\";\n  net->TEST_Benchmark(caffe2::FLAGS_warmup, caffe2::FLAGS_iter, caffe2::FLAGS_run_individual);\n  // Test CPU\n  for (auto i = 0; i < predict_net_def.op().size(); ++i) {\n    auto op = predict_net_def.mutable_op(i);\n    if (std::find(cpu_ops.begin(), cpu_ops.end(), op->type()) == cpu_ops.end()) {\n      op->mutable_device_option()->set_device_type(CPU);\n    }\n  }\n  predict_net_def.set_type(\"simple\");\n  predict_net_def.set_name(\"cpu_net\");\n  net = ws->CreateNet(predict_net_def);\n  LOG(INFO) << \"[C2DEBUG] Benchmarking CPU Net\";\n  net->TEST_Benchmark(caffe2::FLAGS_warmup, caffe2::FLAGS_iter, caffe2::FLAGS_run_individual);\n\n  }\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_norm_planar_yuv_op_test.cc",
    "content": "#include \"gl_operator_test.h\"\n\nnamespace caffe2 {\n\nconstexpr float tol = 5.0e-2;\n\nTEST(OPENGLOperatorTest, NormPlanarYUV) {\n\n  Workspace ws;\n  int batchSize = 1;\n  int channels = 8;\n\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {batchSize, channels, 8, 13});\n\n  PopulateCPUBlob(&ws, true, \"cpu_mean\", {1, channels});\n  PopulateCPUBlob(&ws, true, \"cpu_stddev\", {1, channels}, 1, 0.5);\n\n  NetDef cpu_net;\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"NormalizePlanarYUV\", {\"cpu_X\", \"cpu_mean\", \"cpu_stddev\"}, {\"ref_Y\"});\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"NormalizePlanarYUV\", {\"cpu_X\", \"cpu_mean\", \"cpu_stddev\"}, {\"gpu_Y\"});\n    MAKE_OPENGL_OPERATOR(def);\n  }\n\n  compareNetResult4D(ws, cpu_net, gpu_net);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_operator_test.h",
    "content": "#include \"caffe2/mobile/contrib/arm-compute/core/context.h\"\n#include <gtest/gtest.h>\n\n#include \"caffe2/core/graph.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/workspace.h\"\n\nnamespace caffe2 {\n\n#define DECLARE_OPENGL_OPERATOR(_name)                                         \\\n  OperatorDef _name;                                                           \\\n  _name.mutable_device_option()->set_device_type(OPENGL);\n\n#define MAKE_OPENGL_OPERATOR(_op)                                              \\\n  _op->mutable_device_option()->set_device_type(OPENGL);\n\n#define ADD_ARG(_op, _name, _type, _val)                                       \\\n  {                                                                            \\\n    Argument *arg = _op.add_arg();                                             \\\n    arg->set_name(_name);                                                      \\\n    arg->set_##_type(_val);                                                    \\\n  }\n\n// Use value 1337 to generate a blob that is deterministic\n// and unique at each value (for debugging purposes)\ntemplate<typename T = float>\nvoid PopulateCPUBlob(Workspace *ws, bool random, std::string name,\n                     std::vector<int> dims, int val = 1, int dist_shift = 0, float variance = 1) {\n  Blob *blob = ws->CreateBlob(name);\n  auto *tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(dims);\n  T *t_data = tensor->mutable_data<T>();\n  std::random_device rd;\n  std::mt19937 e2(rd());\n  std::normal_distribution<> dist(0 + dist_shift, variance + dist_shift);\n  for (int i = 0; i < tensor->size(); ++i) {\n    t_data[i] = T(random ? dist(e2) : (val == 1337 ? i : val));\n  }\n}\n\ntemplate<typename T = half>\nvoid compareNetResult(Workspace& ws,\n                      NetDef& cpu_net, NetDef& gpu_net,\n                      string cpu_blob=\"ref_Y\",\n                      string gpu_blob=\"gpu_Y\",\n                      double tol=0.01,\n                      bool relative=false) {\n  ws.RunNetOnce(cpu_net);\n  ws.RunNetOnce(gpu_net);\n\n  Blob *cpu_out = ws.GetBlob(cpu_blob);\n  Blob *gpu_out = ws.GetBlob(gpu_blob);\n  EXPECT_NE(nullptr, cpu_out);\n  EXPECT_NE(nullptr, gpu_out);\n\n  TensorCPU g;\n  auto& g_ = gpu_out->Get<GLTensor<T>>();\n  getTensorCPU(g_, g);\n\n  auto &t = cpu_out->Get<TensorCPU>();\n  EXPECT_EQ(g.size(), t.size());\n\n  for (auto i = 0; i < g.size(); ++i) {\n    if (relative) {\n      EXPECT_NEAR(g.data<float>()[i], t.data<float>()[i], tol + tol * std::abs(t.data<float>()[i])) << \"at index \" << i;\n    } else{\n      EXPECT_NEAR(g.data<float>()[i], t.data<float>()[i], tol)\n        << \"at index \" << i;\n    }\n  }\n}\n\ntemplate<typename T = half>\nvoid compareNetResult4D(Workspace& ws,\n                        NetDef& cpu_net, NetDef& gpu_net,\n                        string cpu_blob=\"ref_Y\",\n                        string gpu_blob=\"gpu_Y\",\n                        double tol=0.05) {\n  ws.RunNetOnce(cpu_net);\n  ws.RunNetOnce(gpu_net);\n\n  Blob *cpu_out = ws.GetBlob(cpu_blob);\n  Blob *gpu_out = ws.GetBlob(gpu_blob);\n  auto &g_ = gpu_out->Get<GLTensor<T>>();\n\n  EXPECT_NE(nullptr, cpu_out);\n  EXPECT_NE(nullptr, gpu_out);\n\n  TensorCPU g;\n  auto &t = cpu_out->Get<TensorCPU>();\n  g.Resize(g_.dims());\n  T *buffer = g_.map();\n  char *byte_buffer = (char *)buffer;\n  auto info = g_.get_underlying()->info();\n\n  CAFFE_ENFORCE(byte_buffer != NULL);\n  auto C = t.dim32(1);\n  auto H = t.dim32(2);\n  auto W = t.dim32(3);\n  int diff_num = 0;\n#define get_elem(_a, _b, _c)                                            \\\n  (half *)&byte_buffer[info->offset_element_in_bytes(                   \\\n      arm_compute::Coordinates(_a, _b, _c))]\n  for (auto c = 0; c < C; ++c) {\n    for (auto h = 0; h < H; ++h) {\n      for (auto w = 0; w < W; ++w) {\n        auto t_elem = t.data<float>()[(c * H + h) * W + w];\n        auto g_elem = get_elem(w, h, c);\n\n        if (!isnan(t_elem) && (std::abs(t_elem - float(*g_elem)) > tol + tol * std::abs(t_elem))) {\n            diff_num++;\n        }\n        CHECK(diff_num <= 0.03 * C*H*W);\n      }\n    }\n  }\n#undef get_elem\n  g_.unmap();\n}\n\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_pool_op_test.cc",
    "content": "#include \"gl_operator_test.h\"\n\nnamespace caffe2 {\n\nTEST(OPENGLOperatorTest, AveragePool) {\n  Workspace ws;\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {1, 1, 8, 8});\n\n  NetDef cpu_net;\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"AveragePool\", {\"cpu_X\"}, {\"ref_Y\"});\n    ADD_ARG((*def), \"kernel\", i, 2);\n    ADD_ARG((*def), \"pad\", i, 0);\n    ADD_ARG((*def), \"stride\", i, 2);\n    ADD_ARG((*def), \"order\", s, \"NCHW\");\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"AveragePool\", {\"cpu_X\"}, {\"gpu_Y\"});\n    ADD_ARG((*def), \"kernel\", i, 2);\n    ADD_ARG((*def), \"pad\", i, 0);\n    ADD_ARG((*def), \"stride\", i, 2);\n    ADD_ARG((*def), \"order\", s, \"NCHW\");\n    MAKE_OPENGL_OPERATOR(def);\n  }\n\n  compareNetResult(ws, cpu_net, gpu_net);\n\n}\n\nTEST(OPENGLOperatorTest, MaxPool) {\n  Workspace ws;\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {1, 1, 8, 8});\n\n  NetDef cpu_net;\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"MaxPool\", {\"cpu_X\"}, {\"ref_Y\"});\n    ADD_ARG((*def), \"kernel\", i, 2);\n    ADD_ARG((*def), \"pad\", i, 0);\n    ADD_ARG((*def), \"stride\", i, 2);\n    ADD_ARG((*def), \"order\", s, \"NCHW\");\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"MaxPool\", {\"cpu_X\"}, {\"gpu_Y\"});\n    ADD_ARG((*def), \"kernel\", i, 2);\n    ADD_ARG((*def), \"pad\", i, 0);\n    ADD_ARG((*def), \"stride\", i, 2);\n    ADD_ARG((*def), \"order\", s, \"NCHW\");\n    MAKE_OPENGL_OPERATOR(def);\n  }\n\n  compareNetResult(ws, cpu_net, gpu_net);\n\n}\n\nTEST(OPENGLOperatorTest, AverageGlobalPool) {\n  Workspace ws;\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {1, 1, 8, 8});\n\n  NetDef cpu_net;\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"AveragePool\", {\"cpu_X\"}, {\"ref_Y\"});\n    ADD_ARG((*def), \"global_pooling\", i, 1);\n    ADD_ARG((*def), \"pad\", i, 0);\n    ADD_ARG((*def), \"stride\", i, 1);\n    ADD_ARG((*def), \"order\", s, \"NCHW\");\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"AveragePool\", {\"cpu_X\"}, {\"gpu_Y\"});\n    ADD_ARG((*def), \"global_pooling\", i, 1);\n    ADD_ARG((*def), \"pad\", i, 0);\n    ADD_ARG((*def), \"stride\", i, 1);\n    ADD_ARG((*def), \"order\", s, \"NCHW\");\n    MAKE_OPENGL_OPERATOR(def);\n  }\n\n  compareNetResult(ws, cpu_net, gpu_net);\n\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_resize_op_test.cc",
    "content": "#include \"gl_operator_test.h\"\n\nnamespace caffe2 {\n\nTEST(OPENGLOperatorTest, ResizeNearest) {\n\n  Workspace ws;\n  float height_scale = 2;\n  float width_scale = 2;\n  int N = 1;\n  int CIn = 7;\n\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {N, CIn, 37, 89});\n\n  NetDef cpu_net;\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"ResizeNearest\", {\"cpu_X\"}, {\"ref_Y\"});\n    ADD_ARG((*def), \"height_scale\", f, height_scale);\n    ADD_ARG((*def), \"width_scale\", f, width_scale);\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"ResizeNearest\", {\"cpu_X\"}, {\"gpu_Y\"});\n    MAKE_OPENGL_OPERATOR(def);\n    ADD_ARG((*def), \"height_scale\", f, height_scale);\n    ADD_ARG((*def), \"width_scale\", f, width_scale);\n  }\n\n  compareNetResult4D(ws, cpu_net, gpu_net);\n\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_softmax_op_test.cc",
    "content": "#include \"gl_operator_test.h\"\n\nnamespace caffe2 {\n\nTEST(OPENGLOperatorTest, Softmax) {\n\n  Workspace ws;\n  int N = 1;\n  int D = 128;\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {N, D}, 1);\n\n  NetDef cpu_net;\n  {\n    AddOp(&cpu_net, \"Softmax\", {\"cpu_X\"}, {\"ref_Y\"});\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"Softmax\", {\"cpu_X\"}, {\"gpu_Y\"});\n    MAKE_OPENGL_OPERATOR(def);\n  }\n\n  compareNetResult(ws, cpu_net, gpu_net);\n\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/arm-compute/test/gl_spatial_batch_norm_op_test.cc",
    "content": "#include \"gl_operator_test.h\"\n\nnamespace caffe2 {\n\nTEST(OPENGLOperatorTest, SpatialBN) {\n\n  Workspace ws;\n  int batchSize = 1;\n  int channels = 8;\n\n  PopulateCPUBlob(&ws, true, \"cpu_X\", {3, channels, 8, 13});\n  PopulateCPUBlob(&ws, true, \"cpu_scale\", {channels});\n  PopulateCPUBlob(&ws, true, \"cpu_bias\", {channels});\n  PopulateCPUBlob(&ws, true, \"cpu_mean\", {channels});\n  PopulateCPUBlob(&ws, true, \"cpu_var\", {channels}, 1, 0.5);\n\n  NetDef cpu_net;\n  {\n    OperatorDef* def = AddOp(&cpu_net, \"SpatialBN\", {\"cpu_X\", \"cpu_scale\", \"cpu_bias\", \"cpu_mean\", \"cpu_var\"}, {\"ref_Y\"});\n    ADD_ARG((*def), OpSchema::Arg_IsTest, i, 1);\n  }\n\n  NetDef gpu_net;\n  gpu_net.set_type(\"opengl\");\n  {\n    OperatorDef* def = AddOp(&gpu_net, \"SpatialBN\", {\"cpu_X\", \"cpu_scale\", \"cpu_bias\", \"cpu_mean\", \"cpu_var\"}, {\"gpu_Y\"});\n    MAKE_OPENGL_OPERATOR(def);\n    ADD_ARG((*def), OpSchema::Arg_IsTest, i, 1);\n  }\n\n  compareNetResult4D(ws, cpu_net, gpu_net, \"ref_Y\", \"gpu_Y\", 0.01);\n\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/CMakeLists.txt",
    "content": "# TODO: figure out conflict between contrib/nnpack/nnpack_ops.cc and mobile_nnpack.cc\nif(IOS)\n  # Basic ios srcs.\n  set(Caffe2_CONTRIB_IOS_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/ios_caffe.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/ios_caffe_predictor.cc\"\n    # \"${CMAKE_CURRENT_SOURCE_DIR}/mobile_nnpack.cc\"\n    )\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${Caffe2_CONTRIB_IOS_SRC})\n\n  if (USE_METAL)\n    # metal/mpscnn files\n    add_subdirectory(mpscnn)\n  endif()\nendif()\n\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/ios_caffe.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"ios_caffe.h\"\n#include \"caffe2/core/predictor.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/mobile/contrib/ios/ios_caffe_predictor.h\"\n\nCaffe2IOSPredictor* MakeCaffe2Predictor(const std::string& init_net_str,\n                                        const std::string& predict_net_str,\n                                        bool disableMultithreadProcessing,\n                                        bool allowMetalOperators,\n                                        std::string& errorMessage) {\n  caffe2::NetDef init_net, predict_net;\n  init_net.ParseFromString(init_net_str);\n  predict_net.ParseFromString(predict_net_str);\n\n  Caffe2IOSPredictor* predictor = NULL;\n  try {\n    predictor = Caffe2IOSPredictor::NewCaffe2IOSPredictor(\n        init_net, predict_net, disableMultithreadProcessing, allowMetalOperators);\n  } catch (const caffe2::EnforceNotMet& e) {\n    std::string error = e.msg();\n    errorMessage.swap(error);\n    return NULL;\n  } catch (const std::exception& e) {\n    std::string error = e.what();\n    errorMessage.swap(error);\n    return NULL;\n  }\n  return predictor;\n}\n\nvoid GenerateStylizedImage(std::vector<float>& originalImage,\n                           const std::string& init_net_str,\n                           const std::string& predict_net_str,\n                           int height,\n                           int width,\n                           std::vector<float>& dataOut) {\n  caffe2::NetDef init_net, predict_net;\n  init_net.ParseFromString(init_net_str);\n  predict_net.ParseFromString(predict_net_str);\n  caffe2::Predictor p(init_net, predict_net);\n\n  std::vector<int> dims({1, 3, height, width});\n  caffe2::TensorCPU input;\n  input.Resize(dims);\n  input.ShareExternalPointer(originalImage.data());\n  caffe2::Predictor::TensorVector input_vec{&input};\n  caffe2::Predictor::TensorVector output_vec;\n  p.run(input_vec, &output_vec);\n  assert(output_vec.size() == 1);\n  caffe2::TensorCPU* output = output_vec.front();\n  // output is our styled image\n  float* outputArray = output->mutable_data<float>();\n  dataOut.assign(outputArray, outputArray + output->size());\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/ios_caffe.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#ifdef __cplusplus\n\n#include <string>\n#include <vector>\n#include \"caffe2/core/predictor.h\"\n#include \"caffe2/mobile/contrib/ios/ios_caffe_defines.h\"\n#include \"caffe2/mobile/contrib/ios/ios_caffe_predictor.h\"\n\nextern \"C\" {\n\nIOS_CAFFE_EXPORT Caffe2IOSPredictor* MakeCaffe2Predictor(const std::string& init_net_str,\n                                                         const std::string& predict_net_str,\n                                                         bool disableMultithreadProcessing,\n                                                         bool allowMetalOperators,\n                                                         std::string& errorMessage);\nIOS_CAFFE_EXPORT void GenerateStylizedImage(std::vector<float>& originalImage,\n                                            const std::string& init_net_str,\n                                            const std::string& predict_net_str,\n                                            int height,\n                                            int width,\n                                            std::vector<float>& dataOut);\n}\n\n#endif\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/ios_caffe_defines.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#define IOS_CAFFE_EXPORT __attribute__((visibility(\"default\")))\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/ios_caffe_predictor.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/mobile/contrib/ios/ios_caffe_predictor.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/core/tensor.h\"\n\n#if defined(CAFFE2_USE_MPSCNN) && CAFFE2_MOBILE\n#include \"caffe2/mobile/contrib/ios/mpscnn/mpscnn.h\"\n#endif\n\nCAFFE2_DECLARE_bool(caffe2_force_shared_col_buffer);\n\nCaffe2IOSPredictor* Caffe2IOSPredictor::NewCaffe2IOSPredictor(const caffe2::NetDef& init_net,\n                                                              const caffe2::NetDef& predict_net,\n                                                              bool disableMultithreadProcessing,\n                                                              bool allowMetalOperators) {\n  caffe2::NetDef metal_predict_net;\n  bool usingMetalOperators = false;\n#if defined(CAFFE2_USE_MPSCNN) && CAFFE2_MOBILE\n  if (allowMetalOperators) {\n    caffe2::dumpDef(predict_net);\n    if (caffe2::tryConvertToMPSCNN(init_net, predict_net, &metal_predict_net)) {\n      LOG(INFO) << \"Successfully converted to MPSCNN\";\n      caffe2::dumpDef(metal_predict_net);\n      usingMetalOperators = true;\n    } else {\n      LOG(ERROR) << \"Failed converting model to MPSCNN\";\n    }\n  }\n#endif\n\n  return new Caffe2IOSPredictor(init_net,\n                                usingMetalOperators ? metal_predict_net : predict_net,\n                                disableMultithreadProcessing,\n                                usingMetalOperators);\n}\n\nCaffe2IOSPredictor::Caffe2IOSPredictor(const caffe2::NetDef& init_net,\n                                       const caffe2::NetDef& predict_net,\n                                       bool disableMultithreadProcessing,\n                                       bool usingMetalOperators)\n    : usingMetalOperators(usingMetalOperators), predictor_(init_net, predict_net) {\n#if CAFFE2_MOBILE\n  if (disableMultithreadProcessing) {\n    caffe2::ThreadPool* threadpool = predictor_.ws()->GetThreadPool();\n    if (threadpool != nullptr) {\n      threadpool->setMinWorkSize(std::numeric_limits<size_t>::max());\n    }\n  }\n#endif\n}\n\nvoid Caffe2IOSPredictor::run(const Tensor& inData, Tensor& outData, std::string& errorMessage) {\n  caffe2::FLAGS_caffe2_force_shared_col_buffer = true;\n  caffe2::TensorCPU input;\n  input.Resize(inData.dims);\n  input.ShareExternalPointer(inData.data);\n  caffe2::Predictor::TensorVector input_vec{&input};\n  caffe2::Predictor::TensorVector output_vec;\n  try {\n    predictor_.run(input_vec, &output_vec);\n  } catch (const caffe2::EnforceNotMet& e) {\n    std::string error = e.msg();\n    errorMessage.swap(error);\n    return;\n  } catch (const std::exception& e) {\n    std::string error = e.what();\n    errorMessage.swap(error);\n    return;\n  }\n  caffe2::TensorCPU* output = output_vec.front();\n  outData.data = output->mutable_data<uint8_t>();\n  outData.dims = output->dims();\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/ios_caffe_predictor.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include <string>\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/predictor.h\"\n#include \"caffe2/mobile/contrib/ios/ios_caffe_defines.h\"\n\nstruct Tensor {\n  std::vector<int64_t> dims;\n  uint8_t* data;\n};\n\nclass IOS_CAFFE_EXPORT Caffe2IOSPredictor final {\n public:\n  /**\n   @allowMetalOperators Allow converting eligible operators to Metal GPU framework accelerated\n   operators. Setting this flag to true doesn't gaurantee predictor will be using Metal operators;\n   Client code must check usingMetalOperators flag to determine predictor is using them.\n   */\n  static Caffe2IOSPredictor* NewCaffe2IOSPredictor(const caffe2::NetDef& init_net,\n                                                   const caffe2::NetDef& predict_net,\n                                                   bool disableMultithreadProcessing,\n                                                   bool allowMetalOperators);\n  void run(const Tensor& inData, Tensor& outData, std::string& errorMessage);\n  ~Caffe2IOSPredictor(){};\n\n  const bool usingMetalOperators;\n\n private:\n  Caffe2IOSPredictor(const caffe2::NetDef& init_net,\n                     const caffe2::NetDef& predict_net,\n                     bool disableMultithreadProcessing,\n                     bool usingMetalOperators);\n  caffe2::Predictor predictor_;\n};\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/mpscnn/CMakeLists.txt",
    "content": "if(USE_METAL)\n  file(GLOB_RECURSE tmp *.mm *.cc)\n  # exclude test files\n  file(GLOB_RECURSE test_files *_test.cc)\n  exclude(tmp \"${tmp}\" ${test_files})\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp} PARENT_SCOPE)\nendif()\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/mpscnn/MPSCNN.metal",
    "content": "\n#include <metal_stdlib>\n\nusing namespace metal;\n\nconstant ushort ushort_arg_0[[function_constant(0)]];\nconstant ushort ushort_arg_1[[function_constant(1)]];\nconstant ushort ushort_arg_2[[function_constant(2)]];\nconstant ushort ushort_arg_3[[function_constant(3)]];\nconstant ushort ushort_arg_4[[function_constant(4)]];\nconstant ushort ushort_arg_5[[function_constant(5)]];\nconstant ushort ushort_arg_6[[function_constant(6)]];\nconstant ushort ushort_arg_7[[function_constant(7)]];\nconstant ushort ushort_arg_8[[function_constant(8)]];\nconstant ushort ushort_arg_9[[function_constant(9)]];\n\ninline constexpr ushort divRoundUp(ushort x, ushort y) { return (x + (y - 1)) / y; }\n\nkernel void affine(constant half4* scale[[buffer(0)]],\n                   constant half4* shift[[buffer(1)]],\n                   texture2d_array<half, access::read> in[[texture(0)]],\n                   texture2d_array<half, access::write> out[[texture(1)]],\n                   ushort3 gid[[thread_position_in_grid]]) {\n  const ushort C = ushort_arg_0;\n  if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n    return;\n  }\n  const half4 scale_c = scale[gid.z % divRoundUp(C, 4)];\n  const half4 shift_c = shift[gid.z % divRoundUp(C, 4)];\n  ushort2 gid_(gid.x, gid.y);\n  const half4 x = in.read(gid_, gid.z);\n  const half4 y = scale_c * x + shift_c;\n  out.write(y, gid_, gid.z);\n}\n\nkernel void affine_nonarray(constant half4* scale[[buffer(0)]],\n                            constant half4* shift[[buffer(1)]],\n                            texture2d<half, access::read> in[[texture(0)]],\n                            texture2d<half, access::write> out[[texture(1)]],\n                            ushort2 gid[[thread_position_in_grid]]) {\n  if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n    return;\n  }\n  const half4 scale_c = scale[0];\n  const half4 shift_c = shift[0];\n  half4 x = in.read(gid);\n  const half4 y = scale_c * x + shift_c;\n  out.write(y, gid);\n}\n\nkernel void prelu_nonshared(constant half4* weights[[buffer(0)]],\n                            texture2d_array<half, access::read> in[[texture(0)]],\n                            texture2d_array<half, access::write> out[[texture(1)]],\n                            ushort3 gid[[thread_position_in_grid]]) {\n  const ushort C = ushort_arg_0;\n  const ushort S = ushort_arg_1;\n  const bool channel_shared = S == 1;\n  if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n    return;\n  }\n  half4 w = channel_shared ? half4(weights[0][0], weights[0][0], weights[0][0], weights[0][0])\n                           : weights[gid.z % divRoundUp(C, 4)];\n  ushort2 gid_(gid.x, gid.y);\n  half4 x = in.read(gid_, gid.z);\n  half4 y = select(x * w, x, x > 0.0h);\n  out.write(y, gid_, gid.z);\n}\n\nkernel void prelu_nonshared_nonarray(constant half4* weights[[buffer(0)]],\n                                     texture2d<half, access::read> in[[texture(0)]],\n                                     texture2d<half, access::write> out[[texture(1)]],\n                                     ushort2 gid[[thread_position_in_grid]]) {\n  // const ushort C = ushort_arg_0;\n  const ushort S = ushort_arg_1;\n  const bool channel_shared = S == 1;\n  if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n    return;\n  }\n  half4 w = channel_shared ? half4(weights[0][0], weights[0][0], weights[0][0], weights[0][0])\n                           : weights[0];\n  half4 x = in.read(gid);\n  half4 y = select(x * w, x, x > 0.0h);\n  out.write(y, gid);\n}\n\n// One block per texture.\n// 256 threads per block.\nusing AccT = float4;\n\nconstant const bool instance_norm_has_prelu = ushort_arg_1 > 0;\n\nkernel void instance_norm(\n    constant half4* weights[[buffer(0)]],\n    constant half4* bias[[buffer(1)]],\n    constant half4* preluWeights[[ buffer(2), function_constant(instance_norm_has_prelu) ]],\n    texture2d_array<half, access::read> in[[texture(0)]],\n    texture2d_array<half, access::write> out[[texture(1)]],\n    ushort3 gid[[thread_position_in_grid]],\n    ushort tid[[thread_index_in_threadgroup]],\n    ushort3 tcount[[threads_per_threadgroup]]) {\n  if (gid.z >= out.get_array_size()) {\n    return;\n  }\n  const ushort C = ushort_arg_0;\n  const ushort S = ushort_arg_1;\n  const bool channel_shared = S == 1;\n  const ushort c = gid.z % divRoundUp(C, 4);\n  constexpr ushort THREADGROUP_SIZE = 256;\n\n  threadgroup AccT per_thread_state[THREADGROUP_SIZE];\n  // Each block handles a single texture.\n  per_thread_state[tid] = 0;\n  for (ushort y = gid.y; y < in.get_height(); y += tcount.y) {\n    for (ushort x = gid.x; x < in.get_width(); x += tcount.x) {\n      per_thread_state[tid] += static_cast<AccT>(in.read(ushort2(x, y), gid.z));\n    }\n  }\n\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n\n  // 256 -> 32 reduction\n  if (tid < 32) {\n    per_thread_state[tid] += per_thread_state[tid + 32] + per_thread_state[tid + 64] +\n                             per_thread_state[tid + 96] + per_thread_state[tid + 128] +\n                             per_thread_state[tid + 160] + per_thread_state[tid + 192] +\n                             per_thread_state[tid + 224];\n  }\n\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n\n  if (tid == 0) {\n    AccT sum = 0.0;\n    for (ushort i = 0; i < 32; ++i) {\n      sum += per_thread_state[i];\n    }\n    sum /= (in.get_width() * in.get_height());\n    per_thread_state[0] = sum;\n  }\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n  // Broadcast to all threads.\n  const AccT mean = per_thread_state[0];\n\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n\n  per_thread_state[tid] = 0;\n  for (ushort y = gid.y; y < in.get_height(); y += tcount.y) {\n    for (ushort x = gid.x; x < in.get_width(); x += tcount.x) {\n      AccT delta = static_cast<AccT>(in.read(ushort2(x, y), gid.z)) - mean;\n      per_thread_state[tid] += delta * delta;\n    }\n  }\n\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n\n  // 256 -> 32 reduction\n  if (tid < 32) {\n    per_thread_state[tid] += per_thread_state[tid + 32] + per_thread_state[tid + 64] +\n                             per_thread_state[tid + 96] + per_thread_state[tid + 128] +\n                             per_thread_state[tid + 160] + per_thread_state[tid + 192] +\n                             per_thread_state[tid + 224];\n  }\n\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n\n  if (tid == 0) {\n    AccT sum = 0.0;\n    for (ushort i = 0; i < 32; ++i) {\n      sum += per_thread_state[i];\n    }\n    sum /= (in.get_width() * in.get_height());\n    per_thread_state[0] = 1.0 / sqrt(max(sum, AccT(1e-5, 1e-5, 1e-5, 1e-5)) + 1.0e-5);\n  }\n\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n  // Broadcast to all threads.\n  const AccT inv_var = per_thread_state[0];\n\n  const AccT c_weights = static_cast<AccT>(weights[c]);\n  const AccT c_bias = static_cast<AccT>(bias[c]);\n\n  const AccT scale = inv_var * c_weights;\n  const AccT shift = c_bias - mean * scale;\n\n  half4 w;\n  if (instance_norm_has_prelu) {\n    w = channel_shared ? half4(preluWeights[0][0]) : preluWeights[c];\n  }\n  for (ushort y = gid.y; y < in.get_height(); y += tcount.y) {\n    for (ushort x = gid.x; x < in.get_width(); x += tcount.x) {\n      half4 scaled =\n          static_cast<half4>(static_cast<AccT>(in.read(ushort2(x, y), gid.z)) * scale + shift);\n      if (instance_norm_has_prelu) {\n        scaled = select(scaled * w, scaled, scaled > 0.0h);\n      }\n      out.write(scaled, ushort2(x, y), gid.z);\n    }\n  }\n}\n\n// One block per texture.\n// 256 threads per block.\nkernel void instance_norm_nonarray(\n    constant half4* weights[[buffer(0)]],\n    constant half4* bias[[buffer(1)]],\n    constant half4* preluWeights[[ buffer(2), function_constant(instance_norm_has_prelu) ]],\n    texture2d<half, access::read> in[[texture(0)]],\n    texture2d<half, access::write> out[[texture(1)]],\n    ushort3 gid[[thread_position_in_grid]],\n    ushort tid[[thread_index_in_threadgroup]],\n    ushort3 tcount[[threads_per_threadgroup]]) {\n  // const ushort C = ushort_arg_0;\n  const ushort S = ushort_arg_1;\n  const bool channel_shared = S == 1;\n  constexpr ushort THREADGROUP_SIZE = 256;\n\n  threadgroup AccT per_thread_state[THREADGROUP_SIZE];\n  // Each block handles a single texture.\n  per_thread_state[tid] = 0;\n  for (ushort y = gid.y; y < in.get_height(); y += tcount.y) {\n    for (ushort x = gid.x; x < in.get_width(); x += tcount.x) {\n      per_thread_state[tid] += static_cast<AccT>(in.read(ushort2(x, y)));\n    }\n  }\n\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n\n  // 256 -> 32 reduction\n  if (tid < 32) {\n    per_thread_state[tid] += per_thread_state[tid + 32] + per_thread_state[tid + 64] +\n                             per_thread_state[tid + 96] + per_thread_state[tid + 128] +\n                             per_thread_state[tid + 160] + per_thread_state[tid + 192] +\n                             per_thread_state[tid + 224];\n  }\n\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n\n  if (tid == 0) {\n    AccT sum = 0.0;\n    for (ushort i = 0; i < 32; ++i) {\n      sum += per_thread_state[i];\n    }\n    sum /= (in.get_width() * in.get_height());\n    per_thread_state[0] = sum;\n  }\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n  // Broadcast to all threads.\n  const AccT mean = per_thread_state[0];\n\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n\n  per_thread_state[tid] = 0;\n  for (ushort y = gid.y; y < in.get_height(); y += tcount.y) {\n    for (ushort x = gid.x; x < in.get_width(); x += tcount.x) {\n      AccT delta = static_cast<AccT>(in.read(ushort2(x, y))) - mean;\n      per_thread_state[tid] += delta * delta;\n    }\n  }\n\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n\n  // 256 -> 32 reduction\n  if (tid < 32) {\n    per_thread_state[tid] += per_thread_state[tid + 32] + per_thread_state[tid + 64] +\n                             per_thread_state[tid + 96] + per_thread_state[tid + 128] +\n                             per_thread_state[tid + 160] + per_thread_state[tid + 192] +\n                             per_thread_state[tid + 224];\n  }\n\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n\n  if (tid == 0) {\n    AccT sum = 0.0;\n    for (ushort i = 0; i < 32; ++i) {\n      sum += per_thread_state[i];\n    }\n    sum /= (in.get_width() * in.get_height());\n    per_thread_state[0] = 1.0 / sqrt(max(sum, AccT(1e-5, 1e-5, 1e-5, 1e-5)) + 1.0e-5);\n  }\n\n  threadgroup_barrier(mem_flags::mem_threadgroup);\n  // Broadcast to all threads.\n  const AccT inv_var = per_thread_state[0];\n\n  const AccT c_weights = static_cast<AccT>(weights[0]);\n  const AccT c_bias = static_cast<AccT>(bias[0]);\n\n  const AccT scale = inv_var * c_weights;\n  const AccT shift = c_bias - mean * scale;\n\n  half4 w;\n  if (instance_norm_has_prelu) {\n    w = channel_shared ? half4(preluWeights[0][0]) : preluWeights[0];\n  }\n  for (ushort y = gid.y; y < in.get_height(); y += tcount.y) {\n    for (ushort x = gid.x; x < in.get_width(); x += tcount.x) {\n      half4 scaled = static_cast<half4>(static_cast<AccT>(in.read(ushort2(x, y))) * scale + shift);\n      if (instance_norm_has_prelu) {\n        scaled = select(scaled * w, scaled, scaled > 0.0h);\n      }\n      out.write(scaled, ushort2(x, y));\n    }\n  }\n}\n\nkernel void copy_nchw_to_metal(constant float* in[[buffer(0)]],\n                               texture2d_array<half, access::write> out[[texture(0)]],\n                               ushort3 gid[[thread_position_in_grid]]) {\n  const ushort C = ushort_arg_0;\n  const ushort H = ushort_arg_1;\n  const ushort W = ushort_arg_2;\n  if (gid.x >= W || gid.y >= H) {\n    return;\n  }\n\n  const ushort n = gid.z / divRoundUp(C, 4);\n  const ushort c = gid.z - n * divRoundUp(C, 4);\n\n// TODO: are the `else` branches needed?\n// TODO: trick the optimizer for case where C == 4?\n#define CHW_TO_CHWP4(idx, n, c_, h, w)                                     \\\n  if ((c_) < C) {                                                          \\\n    trns[idx] = in[n * H * W * C + int(c_) * H * W + int(h) * W + int(w)]; \\\n  } else {                                                                 \\\n    trns[idx] = 0.0h;                                                      \\\n  }\n\n  half4 trns;\n  CHW_TO_CHWP4(0, n, c * 4 + 0, gid.y, gid.x);\n  CHW_TO_CHWP4(1, n, c * 4 + 1, gid.y, gid.x);\n  CHW_TO_CHWP4(2, n, c * 4 + 2, gid.y, gid.x);\n  CHW_TO_CHWP4(3, n, c * 4 + 3, gid.y, gid.x);\n#undef CHW_TO_CHWP4\n\n  out.write(trns, gid.xy, gid.z);\n}\n\nkernel void copy_nchw_to_metal_nonarray(constant float* in[[buffer(0)]],\n                                        texture2d<half, access::write> out[[texture(0)]],\n                                        ushort2 gid[[thread_position_in_grid]]) {\n  const ushort C = ushort_arg_0;\n  const ushort H = ushort_arg_1;\n  const ushort W = ushort_arg_2;\n\n  if (gid.x >= W || gid.y >= H) {\n    return;\n  }\n\n  half4 trns;\n// TODO: are the `else` branches needed?\n// TODO: trick the optimizer for case where C % 4 == 0?\n\n#define CHW_TO_CHWP4(idx, c, h, w)                        \\\n  if ((c) < C) {                                          \\\n    trns[idx] = in[int(c) * H * W + int(h) * W + int(w)]; \\\n  } else {                                                \\\n    trns[idx] = 0.0h;                                     \\\n  }\n\n  CHW_TO_CHWP4(0, 0, gid.y, gid.x);\n  CHW_TO_CHWP4(1, 1, gid.y, gid.x);\n  CHW_TO_CHWP4(2, 2, gid.y, gid.x);\n  CHW_TO_CHWP4(3, 3, gid.y, gid.x);\n#undef CHW_TO_CHWP4\n\n  out.write(trns, gid.xy);\n}\n\nkernel void copy_metal_to_nchw(texture2d_array<half, access::read> in[[texture(0)]],\n                               device float* out[[buffer(0)]],\n                               ushort3 gid[[thread_position_in_grid]]) {\n  const ushort C = ushort_arg_0;\n  const ushort H = ushort_arg_1;\n  const ushort W = ushort_arg_2;\n\n  if (gid.x >= W || gid.y >= H) {\n    return;\n  }\n  const ushort n = gid.z / divRoundUp(C, 4);\n  const ushort c = gid.z - n * divRoundUp(C, 4);\n\n  half4 cs = in.read(gid.xy, gid.z);\n\n#define CHWP4_TO_CHW(idx, n, c_, h, w)                                    \\\n  if ((c_) < C) {                                                         \\\n    out[n * H * W * C + int(c_) * H * W + int(h) * W + int(w)] = cs[idx]; \\\n  }\n\n  CHWP4_TO_CHW(0, n, c * 4 + 0, gid.y, gid.x);\n  CHWP4_TO_CHW(1, n, c * 4 + 1, gid.y, gid.x);\n  CHWP4_TO_CHW(2, n, c * 4 + 2, gid.y, gid.x);\n  CHWP4_TO_CHW(3, n, c * 4 + 3, gid.y, gid.x);\n#undef CHWP4_TO_CHW\n}\n\nkernel void copy_metal_to_nchw_nonarray(texture2d<half, access::read> in[[texture(0)]],\n                                        device float* out[[buffer(0)]],\n                                        ushort2 gid[[thread_position_in_grid]]) {\n  const ushort C = ushort_arg_0;\n  const ushort H = ushort_arg_1;\n  const ushort W = ushort_arg_2;\n\n  if (gid.x >= W || gid.y >= H) {\n    return;\n  }\n\n  half4 cs = in.read(gid.xy);\n\n#define CHWP4_TO_CHW(idx, c, h, w)                       \\\n  if ((c) < C) {                                         \\\n    out[int(c) * H * W + int(h) * W + int(w)] = cs[idx]; \\\n  }\n\n  CHWP4_TO_CHW(0, 0, gid.y, gid.x);\n  CHWP4_TO_CHW(1, 1, gid.y, gid.x);\n  CHWP4_TO_CHW(2, 2, gid.y, gid.x);\n  CHWP4_TO_CHW(3, 3, gid.y, gid.x);\n#undef CHWP4_TO_CHW\n}\n\nkernel void convtranspose_upscale(texture2d_array<half, access::read> in[[texture(0)]],\n                                  texture2d_array<half, access::write> out[[texture(1)]],\n                                  ushort3 gid[[thread_position_in_grid]]) {\n  // All resolved at compile time.\n  // Assume symmetric kernel/stride/pad for now.\n  const ushort kernel_ = ushort_arg_0;\n  const ushort stride = ushort_arg_1;\n  const ushort pad = ushort_arg_2;\n\n  half4 zero(0.0h, 0.0h, 0.0h, 0.0h);\n\n  if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n    return;\n  }\n  const ushort2 gid_ = gid.xy;\n  if (gid.x < kernel_ - 1 - pad || gid.y < kernel_ - 1 - pad) {\n    out.write(zero, gid_, gid.z);\n    return;\n  }\n\n  if (((gid.x - (kernel_ - 1 - pad)) % stride == 0) &&\n      ((gid.y - (kernel_ - 1 - pad)) % stride == 0)) {\n    ushort2 in_pos((gid.x - (kernel_ - 1 - pad)) / stride, (gid.y - (kernel_ - 1 - pad)) / stride);\n\n    if (in_pos.x < in.get_width() && in_pos.y < in.get_height()) {\n      half4 input = in.read(in_pos, gid.z);\n      out.write(input, gid_, gid.z);\n    } else {\n      out.write(zero, gid_, gid.z);\n    }\n  } else {\n    out.write(zero, gid_, gid.z);\n  }\n}\n\nconstant bool has_in_arr = (ushort_arg_7 > 1 || ushort_arg_0 * ushort_arg_1 * ushort_arg_6 > 4);\nconstant bool has_out_arr = (ushort_arg_7 > 1 || ushort_arg_6 > 4);\nconstant bool has_in_tex = (!has_in_arr);\nconstant bool has_out_tex = (!has_out_arr);\n\nkernel void col2im(\n    texture2d_array<half, access::read> ina[[ texture(0), function_constant(has_in_arr) ]],\n    texture2d<half, access::read> in[[ texture(0), function_constant(has_in_tex) ]],\n    texture2d_array<half, access::write> outa[[ texture(1), function_constant(has_out_arr) ]],\n    texture2d<half, access::write> out[[ texture(1), function_constant(has_out_tex) ]],\n    constant half4* bias[[buffer(0)]],\n    ushort3 gid[[thread_position_in_grid]]) {\n  if (has_out_tex) {\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n      return;\n    }\n  } else {\n    if (gid.x >= outa.get_width() || gid.y >= outa.get_height()) {\n      return;\n    }\n  }\n  const ushort kernel_h = ushort_arg_0;\n  const ushort kernel_w = ushort_arg_1;\n  const ushort stride_h = ushort_arg_2;\n  const ushort stride_w = ushort_arg_3;\n  const ushort pad_l = ushort_arg_4;\n  const ushort pad_t = ushort_arg_5;\n  const ushort C = ushort_arg_6;\n  //  const int N = ushort_arg_7;\n  const ushort height_col = ushort_arg_8; //(outa.get_height() + pad + pad - kernel_) / stride + 1;\n  const ushort width_col = ushort_arg_9; // (outa.get_width() + pad + pad - kernel_) / stride + 1;\n\n  const ushort n = gid.z / divRoundUp(C, 4);\n  const ushort c = gid.z - n * divRoundUp(C, 4);\n\n  const ushort w = gid.x + pad_l;\n  const ushort h = gid.y + pad_t;\n\n  // compute the start and end of the output\n  const ushort w_col_start = (w < kernel_w) ? 0 : (w - kernel_w) / stride_w + 1;\n  const ushort w_col_end = min(ushort(w / stride_w + 1), ushort(width_col));\n  const ushort h_col_start = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n  const ushort h_col_end = min(ushort(h / stride_h + 1), ushort(height_col));\n\n  float4 val = static_cast<float4>(bias[c]);\n  for (ushort h_col = h_col_start; h_col < h_col_end; ++h_col) {\n    for (ushort w_col = w_col_start; w_col < w_col_end; ++w_col) {\n      const ushort w_k = w - w_col * stride_w;\n      const ushort h_k = h - h_col * stride_h;\n\n      // layout is essentially: [N][K][K][C][H][W]\n      // - where the divRoundUp(K * K * C, 4) channels are interleaved as usual.\n      // Thus, it's actually [N][divRoundUp(K * K * C, 4)][H][W].\u0013\n\n      // If C % 4 is not zero, then we have to play some games via partial indexing.\n      // TODO: is it worth optimizing this loop via padding in C?\n      if (C % 4 == 0) {\n        ushort c_col = n * kernel_h * kernel_w * divRoundUp(C, 4) +\n                       h_k * kernel_w * divRoundUp(C, 4) + w_k * divRoundUp(C, 4) + c;\n        if (has_in_arr) {\n          val += static_cast<float4>(ina.read(ushort2(w_col, h_col), c_col));\n        }\n        if (has_in_tex) {\n          val += static_cast<float4>(in.read(ushort2(w_col, h_col), c_col));\n        }\n      } else {\n        half4 components(0, 0, 0, 0);\n        for (auto i = 0; i < 4; ++i) {\n          ushort c_col_i = n * divRoundUp(kernel_h * kernel_w * C, 4) * 4 + h_k * kernel_w * C +\n                           w_k * C + c * 4 + i;\n          ushort c_col_i_z = c_col_i / 4;\n          ushort c_col_i_off = c_col_i - c_col_i_z * 4;\n          if (has_in_arr) {\n            components[i] = ina.read(ushort2(w_col, h_col), c_col_i_z)[c_col_i_off];\n          }\n          if (has_in_tex) {\n            components[i] = in.read(ushort2(w_col, h_col))[c_col_i_off];\n          }\n        }\n        val += static_cast<float4>(components);\n      }\n    }\n  }\n  if (has_out_arr) {\n    outa.write(static_cast<half4>(val), gid.xy, gid.z);\n  }\n  if (has_out_tex) {\n    out.write(static_cast<half4>(val), gid.xy);\n  }\n}\n\nkernel void preprocess_stylizer(device uchar4* in[[buffer(0)]],\n                                constant half* mean[[buffer(1)]],\n                                constant half4* noise[[buffer(2)]],\n                                texture2d<half, access::write> out[[texture(0)]],\n                                ushort2 gid[[thread_position_in_grid]]) {\n\n  if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n    return;\n  }\n  const ushort noise_size = ushort_arg_0;\n\n  half4 mean_half(mean[0], mean[1], mean[2], 0.0h);\n  uint input_noise_idx = ((uint)out.get_width() * (uint)gid.y + (uint)gid.x) % (noise_size / 4);\n  const half4 input_noise = noise[input_noise_idx];\n  const uint W = out.get_width();\n#define in_at(h, w) in[(uint)(h)*W + (uint)(w)]\n  uchar4 input = in_at(gid.y, gid.x);\n#undef in_at\n  half4 input_half = static_cast<half4>(input);\n  out.write(input_half - mean_half + input_noise, gid);\n}\n\nkernel void deprocess_stylizer(texture2d<half, access::read> in[[texture(0)]],\n                               device uchar4* out[[buffer(0)]],\n                               constant half* mean[[buffer(1)]],\n                               ushort2 gid[[thread_position_in_grid]]) {\n  if (gid.x >= in.get_width() || gid.y >= in.get_height()) {\n    return;\n  }\n\n  half4 value = in.read(gid);\n\n  half4 mean_h(mean[0], mean[1], mean[2], 0.0h);\n  half4 min_h(0.0h, 0.0h, 0.0h, 255.0h);\n  half4 max_h(255.0h, 255.0h, 255.0h, 255.0h);\n  half4 clamped = clamp(value + mean_h, min_h, max_h);\n  const uint W = in.get_width();\n#define out_at(h, w, v) out[(uint)(h)*W + (uint)(w)] = (v)\n  out_at(gid.y, gid.x, static_cast<uchar4>(clamped));\n#undef out_at\n}\n\nkernel void reflection_padding_nonarray(texture2d<half, access::read> in[[texture(0)]],\n                                        texture2d<half, access::write> out[[texture(1)]],\n                                        ushort2 gid[[thread_position_in_grid]]) {\n  if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n    return;\n  }\n  ushort H = in.get_height();\n  ushort PH = out.get_height();\n\n  // Note: we assume symmetric padding on H/W here, which is verified\n  // in the calling code.\n  ushort pad_h = (PH - H) / 2;\n  ushort W = in.get_width();\n  ushort PW = out.get_width();\n  ushort pad_w = (PW - W) / 2;\n\n  short h = short(gid.y) - short(pad_h);\n  h = max(h, short(-h));\n  h = min(h, short(2 * H - h - 2));\n\n  short w = short(gid.x) - short(pad_w);\n  w = max(w, short(-w));\n  w = min(w, short(2 * W - w - 2));\n\n  ushort2 inid(w, h);\n  out.write(in.read(inid), gid);\n}\n\nkernel void reflection_padding(texture2d_array<half, access::read> in[[texture(0)]],\n                               texture2d_array<half, access::write> out[[texture(1)]],\n                               ushort3 gid[[thread_position_in_grid]]) {\n  if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n    return;\n  }\n  ushort H = in.get_height();\n  ushort PH = out.get_height();\n\n  // Note: we assume symmetric padding on H/W here, which is verified\n  // in the calling code.\n  ushort pad_h = (PH - H) / 2;\n  ushort W = in.get_width();\n  ushort PW = out.get_width();\n  ushort pad_w = (PW - W) / 2;\n\n  short h = short(gid.y) - short(pad_h);\n  h = max(h, short(-h));\n  h = min(h, short(2 * H - h - 2));\n\n  short w = short(gid.x) - short(pad_w);\n  w = max(w, short(-w));\n  w = min(w, short(2 * W - w - 2));\n\n  ushort2 inid(w, h);\n\n  out.write(in.read(inid, gid.z), gid.xy, gid.z);\n}\n\nkernel void bilinear_upsample(texture2d<half, access::sample> in[[texture(0)]],\n                              texture2d<half, access::write> out[[texture(1)]],\n                              ushort2 gid[[thread_position_in_grid]]) {\n  if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n    return;\n  }\n  ushort2 src = gid / 2;\n  constexpr sampler sampler(address::clamp_to_edge, filter::linear, coord::pixel);\n  half4 value = in.sample(sampler, static_cast<float2>(src));\n  out.write(value, gid);\n}\n\nconstant bool in0_is_tex = ushort_arg_0 <= 1 && ushort_arg_1 <= 4;\nconstant bool in0_is_arr = !in0_is_tex;\n\nkernel void elementwise_mul(texture2d<half, access::read> in0[[texture(0), function_constant(in0_is_tex)]],\n                            texture2d_array<half, access::read> ina0[[texture(0), function_constant(in0_is_arr)]],\n                            texture2d<half, access::write> out[[texture(2), function_constant(in0_is_tex)]],\n                            texture2d_array<half, access::write> outa[[texture(2), function_constant(in0_is_arr)]],\n                            constant float* in1[[buffer(1)]],\n                            ushort3 gid[[thread_position_in_grid]]) {\n  ushort last_dim = ushort_arg_2;\n  ushort idx;\n  if (in0_is_tex) {\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n      return;\n    }\n    idx = gid.y * out.get_width() + gid.x;\n  } else {\n    if (gid.x >= outa.get_width() || gid.y >= outa.get_height()) {\n      return;\n    }\n    idx = gid.y * outa.get_width() + gid.x;\n  }\n  ushort2 gid_ = gid.xy;\n  if (in0_is_tex) {\n    out.write(in0.read(gid_) * in1[idx % last_dim], gid_);\n  } else {\n    outa.write(ina0.read(gid_, gid.z) * in1[idx % last_dim], gid_, gid.z);\n  }\n}\n\nkernel void elementwise_sub(texture2d<half, access::read> in0[[texture(0), function_constant(in0_is_tex)]],\n                            texture2d_array<half, access::read> ina0[[texture(0), function_constant(in0_is_arr)]],\n                            texture2d<half, access::write> out[[texture(2), function_constant(in0_is_tex)]],\n                            texture2d_array<half, access::write> outa[[texture(2), function_constant(in0_is_arr)]],\n                            constant float* in1[[buffer(1)]],\n                            ushort3 gid[[thread_position_in_grid]]) {\n  ushort last_dim = ushort_arg_2;\n  ushort idx;\n  if (in0_is_tex) {\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n      return;\n    }\n    idx = gid.y * out.get_width() + gid.x;\n  } else {\n    if (gid.x >= outa.get_width() || gid.y >= outa.get_height()) {\n      return;\n    }\n    idx = gid.y * outa.get_width() + gid.x;\n  }\n  ushort2 gid_ = gid.xy;\n  if (in0_is_tex) {\n    out.write(in0.read(gid_) - in1[idx % last_dim], gid_);\n  } else {\n    outa.write(ina0.read(gid_, gid.z) - in1[idx % last_dim], gid_, gid.z);\n  }\n}\n\n\nkernel void elementwise_add_nonarray(texture2d<half, access::read> in0[[texture(0)]],\n                                     texture2d<half, access::read> in1[[texture(1)]],\n                                     texture2d<half, access::write> out[[texture(2)]],\n                                     ushort2 gid[[thread_position_in_grid]]) {\n  if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n    return;\n  }\n  out.write(in0.read(gid) + in1.read(gid), gid);\n}\n\nkernel void elementwise_add(texture2d_array<half, access::read> in0[[texture(0)]],\n                            texture2d_array<half, access::read> in1[[texture(1)]],\n                            texture2d_array<half, access::write> out[[texture(2)]],\n                            ushort3 gid[[thread_position_in_grid]]) {\n  if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n    return;\n  }\n  ushort2 gid_ = gid.xy;\n  out.write(in0.read(gid_, gid.z) + in1.read(gid_, gid.z), gid_, gid.z);\n}\n\nconstant bool has_in0_arg = (ushort_arg_0 > 0);\nconstant bool has_in1_arg = (ushort_arg_1 > 0);\nconstant bool has_in2_arg = (ushort_arg_2 > 0);\nconstant bool has_in3_arg = (ushort_arg_3 > 0);\n\nconstant bool has_in0_tex = (has_in0_arg && ushort_arg_0 <= 4 && ushort_arg_4 <= 1);\nconstant bool has_in1_tex = (has_in1_arg && ushort_arg_1 <= 4 && ushort_arg_4 <= 1);\nconstant bool has_in2_tex = (has_in2_arg && ushort_arg_2 <= 4 && ushort_arg_4 <= 1);\nconstant bool has_in3_tex = (has_in3_arg && ushort_arg_3 <= 4 && ushort_arg_4 <= 1);\n\nconstant bool has_in0_array = (has_in0_arg && !has_in0_tex);\nconstant bool has_in1_array = (has_in1_arg && !has_in1_tex);\nconstant bool has_in2_array = (has_in2_arg && !has_in2_tex);\nconstant bool has_in3_array = (has_in3_arg && !has_in3_tex);\n\nconstant bool concat_has_out_tex = (ushort_arg_4 <= 4 && ushort_arg_5 <= 1);\nconstant bool concat_has_out_array = !concat_has_out_tex;\n\ninline ushort idx_3(ushort z, ushort C0, ushort C1, ushort C2, ushort C3) {\n  if (z < C0) {\n    return 0;\n  }\n  if (z < (C0 + C1)) {\n    return 1;\n  }\n  if (z < (C0 + C1 + C2)) {\n    return 2;\n  }\n  return 3;\n}\n\ninline ushort idx_2(ushort z, ushort C0, ushort C1, ushort C2) {\n  if (z < C0) {\n    return 0;\n  }\n  if (z < (C0 + C1)) {\n    return 1;\n  }\n  return 2;\n}\n\ninline ushort idx_1(ushort z, ushort C0, ushort C1) {\n  if (z < C0) {\n    return 0;\n  } else {\n    return 1;\n  }\n}\n\ninline ushort idx_0(ushort z, ushort C0) { return 0; }\n\n// in a texture_array with size C, find the offset for image N at plane c.\ninline constexpr ushort z_off(ushort n, ushort c, ushort C) { return n * divRoundUp(C, 4) + c / 4; }\n\nkernel void concat(\n                   texture2d<half, access::read> in0[[ texture(0), function_constant(has_in0_tex) ]],\n                   texture2d<half, access::read> in1[[ texture(1), function_constant(has_in1_tex) ]],\n                   texture2d<half, access::read> in2[[ texture(2), function_constant(has_in2_tex) ]],\n                   texture2d<half, access::read> in3[[ texture(3), function_constant(has_in3_tex) ]],\n                   texture2d_array<half, access::read> ina0[[ texture(0), function_constant(has_in0_array) ]],\n                   texture2d_array<half, access::read> ina1[[ texture(1), function_constant(has_in1_array) ]],\n                   texture2d_array<half, access::read> ina2[[ texture(2), function_constant(has_in2_array) ]],\n                   texture2d_array<half, access::read> ina3[[ texture(3), function_constant(has_in3_array) ]],\n                   texture2d<half, access::write> out[[texture(5),\n                                                       function_constant(concat_has_out_tex) ]],\n                   texture2d_array<half, access::write> outa[[texture(5),\n                                                              function_constant(concat_has_out_array) ]],\n                   ushort3 gid[[thread_position_in_grid]]) {\n  if (concat_has_out_tex) {\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n      return;\n    }\n  } else {\n    if (gid.x >= outa.get_width() || gid.y >= outa.get_height()) {\n      return;\n    }\n  }\n  \n  const ushort C0 = ushort_arg_0;\n  const ushort C1 = ushort_arg_1;\n  const ushort C2 = ushort_arg_2;\n  const ushort C3 = ushort_arg_3;\n  const ushort C = C0 + C1 + C2 + C3;\n  const ushort n = gid.z / divRoundUp(C, 4);\n  const ushort c = gid.z - n * divRoundUp(C, 4);\n  // Fill channel 4*c to 4*(c+1) of nth image of output\n  \n  ushort2 gid_ = gid.xy;\n  half4 value;\n  \n  for (int off = 0; off < 4; ++off) {\n    ushort cur_channel = c * 4 + off;\n    ushort cur_idx = 0;\n    if (cur_channel >= C) {\n      break;\n    }\n    if (has_in3_arg) {\n      cur_idx = idx_3(cur_channel, C0, C1, C2, C3);\n    } else if (has_in2_arg) {\n      cur_idx = idx_2(cur_channel, C0, C1, C2);\n    } else if (has_in1_arg) {\n      cur_idx = idx_1(cur_channel, C0, C1);\n    } else if (has_in0_arg) {\n      cur_idx = idx_0(cur_channel, C0);\n    } else {\n      // never reached.\n      cur_idx = 0;\n    }\n    ushort src_off = 0;\n    switch (cur_idx) {\n      case 0:\n        src_off = cur_channel % 4;\n        break;\n      case 1:\n        src_off = (cur_channel - C0) % 4;\n        break;\n      case 2:\n        src_off = (cur_channel - (C0 + C1)) % 4;\n        break;\n      case 3:\n        src_off = (cur_channel - (C0 + C1 + C2)) % 4;\n        break;\n    }\n    // try to see if we can only issue one read op for the 4 values\n    bool fast_path = false;\n    if (off == 0 && src_off == 0 && (cur_channel + 3) < C) {\n      ushort last_idx = 0;\n      if (has_in3_arg) {\n        last_idx = idx_3(cur_channel + 3, C0, C1, C2, C3);\n      } else if (has_in2_arg) {\n        last_idx = idx_2(cur_channel + 3, C0, C1, C2);\n      } else if (has_in1_arg) {\n        last_idx = idx_1(cur_channel + 3, C0, C1);\n      } else if (has_in0_arg) {\n        last_idx = idx_0(cur_channel + 3, C0);\n      } else {\n        // never reached.\n        last_idx = 0;\n      }\n      if (cur_idx == last_idx) {\n        fast_path = true;\n      }\n    }\n    switch (cur_idx) {\n      case 0: {\n        if (has_in0_tex) {\n          if (fast_path) {\n            value = in0.read(gid_);\n          } else {\n            value[off] = in0.read(gid_)[src_off];\n          }\n        }\n        if (has_in0_array) {\n          if (fast_path) {\n            value = ina0.read(gid_, z_off(n, cur_channel, C0));\n          } else {\n            value[off] = ina0.read(gid_, z_off(n, cur_channel, C0))[src_off];\n          }\n        }\n        break;\n      }\n      case 1: {\n        if (has_in1_tex) {\n          if (fast_path) {\n            value = in1.read(gid_);\n          } else {\n            value[off] = in1.read(gid_)[src_off];\n          }\n        }\n        if (has_in1_array) {\n          if (fast_path) {\n            value = ina1.read(gid_, z_off(n, cur_channel - C0, C1));\n          } else {\n            value[off] = ina1.read(gid_, z_off(n, cur_channel - C0, C1))[src_off];\n          }\n        }\n        break;\n      }\n      case 2: {\n        if (has_in2_tex) {\n          if (fast_path) {\n            value = in2.read(gid_);\n          } else {\n            value[off] = in2.read(gid_)[src_off];\n          }\n        }\n        if (has_in2_array) {\n          if (fast_path) {\n            value = ina2.read(gid_, z_off(n, cur_channel - (C0 + C1), C2));\n          } else {\n            value[off] = ina2.read(gid_, z_off(n, cur_channel - (C0 + C1), C2))[src_off];\n          }\n        }\n        break;\n      }\n      case 3: {\n        if (has_in3_tex) {\n          if (fast_path) {\n            value = in3.read(gid_);\n          } else {\n            value[off] = in3.read(gid_)[src_off];\n          }\n        }\n        if (has_in3_array) {\n          if (fast_path) {\n            value = ina3.read(gid_, z_off(n, cur_channel - (C0 + C1 + C2), C3));\n          } else {\n            value[off] = ina3.read(gid_, z_off(n, cur_channel - (C0 + C1 + C2), C3))[src_off];\n          }\n        }\n        break;\n      }\n    }\n    if (fast_path) {\n      break;\n    }\n  }\n  if (concat_has_out_tex) {\n    out.write(value, gid_, gid.z);\n  } else {\n    outa.write(value, gid_, gid.z);\n  }\n}\n\nusing RoIT = half;\nusing RoIT4 = half4;\nconstant bool rw_has_in_arr = (ushort_arg_3 > 1 ||  ushort_arg_2 > 4);\nconstant bool rw_has_out_arr = (ushort_arg_4 > 1 || ushort_arg_2 > 4);\nconstant bool rw_has_in_tex = (!rw_has_in_arr);\nconstant bool rw_has_out_tex = (!rw_has_out_arr);\nkernel void roi_warp(texture2d_array<half, access::sample> ina[[texture(0), function_constant(rw_has_in_arr)]],\n                     texture2d<half, access::sample> in[[texture(0), function_constant(rw_has_in_tex)]],\n                     texture2d_array<half, access::write> outa[[texture(1), function_constant(rw_has_out_arr)]],\n                     texture2d<half, access::write> out[[texture(1), function_constant(rw_has_out_tex)]],\n                     constant half4* rois[[buffer(0)]],\n                     ushort3 gid[[thread_position_in_grid]]) {\n  ushort out_width, out_height;\n  if (rw_has_out_arr) {\n    out_width = outa.get_width();\n    out_height = outa.get_height();\n  } else {\n    out_width = out.get_width();\n    out_height = out.get_height();\n  }\n  if (gid.x >= out_width || gid.y >= out_height) {\n    return;\n  }\n  constexpr sampler s2(coord::pixel, address::clamp_to_edge, filter::linear);\n  \n  const half spatial_scale = half(ushort_arg_0) / 10000;\n  const ushort sampling_ratio = ushort_arg_1;\n  const ushort C = ushort_arg_2;\n  const ushort pw = gid.x;\n  const ushort ph = gid.y;\n  const ushort n = gid.z / divRoundUp(C, 4);\n  const ushort c = gid.z % divRoundUp(C, 4);\n  \n  const RoIT4 roi_scaled = rois[n] * spatial_scale;\n  const RoIT roi_start_w = roi_scaled[0];\n  const RoIT roi_start_h = roi_scaled[1];\n  const RoIT roi_end_w = roi_scaled[2];\n  const RoIT roi_end_h = roi_scaled[3];\n  \n  // Force malformed ROIs to be 1x1\n  const RoIT roi_width = max(roi_end_w - roi_start_w, (RoIT)1.);\n  const RoIT roi_height = max(roi_end_h - roi_start_h, (RoIT)1.);\n  \n  const RoIT bin_size_h = static_cast<RoIT>(roi_height) / static_cast<RoIT>(out_height);\n  const RoIT bin_size_w = static_cast<RoIT>(roi_width) / static_cast<RoIT>(out_width);\n  const ushort roi_bin_grid_h = sampling_ratio > 0 ? sampling_ratio : ceil(roi_height / static_cast<RoIT>(out_height));\n  const ushort roi_bin_grid_w = sampling_ratio > 0 ? sampling_ratio : ceil(roi_width / static_cast<RoIT>(out_width));\n  const ushort iy_upper = (sampling_ratio > 0) ? roi_bin_grid_h : (roi_bin_grid_h + 1);\n  const ushort ix_upper = (sampling_ratio > 0) ? roi_bin_grid_w : (roi_bin_grid_w + 1);\n  \n  const RoIT count = iy_upper * ix_upper;\n  \n  RoIT4 output_val = 0.0;\n  for (int iy = 0; iy < iy_upper; iy++) {\n    for (int ix = 0; ix < ix_upper; ix++) {\n      const RoIT y =\n      roi_start_h + ph * bin_size_h + iy * bin_size_h / static_cast<RoIT>(roi_bin_grid_h);\n      const RoIT x =\n      roi_start_w + pw * bin_size_w + ix * bin_size_w / static_cast<RoIT>(roi_bin_grid_w);\n      if (rw_has_in_arr) {\n        output_val += ina.sample(s2, float2(x + 0.5, y + 0.5), c);\n      } else {\n        output_val += in.sample(s2, float2(x + 0.5, y + 0.5));\n      }\n    }\n  }\n  output_val /= count;\n  if (rw_has_out_arr) {\n    outa.write(static_cast<half4>(output_val), gid.xy, gid.z);\n  } else {\n    out.write(static_cast<half4>(output_val), gid.xy);\n  }\n}\n\nkernel void nms(device uint* mask[[buffer(0)]],\n                constant float* proposals[[buffer(1)]],\n                constant int* indices[[buffer(2)]],\n                ushort2 tgid[[threadgroup_position_in_grid]],\n                ushort2 tid[[thread_position_in_threadgroup]]) {\n  const ushort num_proposals = ushort_arg_0;\n  const ushort threads_per_group = ushort_arg_1;\n  float nms_thresh = float(ushort_arg_2) / 10000.0;\n  const ushort global_offset = ushort_arg_3;\n  const ushort row_start = tgid.y;\n  const ushort col_start = tgid.x;\n  const ushort trd_id = tid.x;\n\n  const short row_size = min(short(32), short(num_proposals - row_start * threads_per_group));\n  const short col_size = min(short(32), short(num_proposals - col_start * threads_per_group));\n\n  // mask the bit if the IoU between two proposals exceeds the threshold\n  if (trd_id < row_size) {\n    const ushort cur_idx = global_offset + row_start * threads_per_group + trd_id;\n    const ushort offset = indices[cur_idx] * 4;\n    const float4 cur_proposal = float4(\n        proposals[offset], proposals[offset + 1], proposals[offset + 2], proposals[offset + 3]);\n    uint cur_mask = 0;\n    ushort group_start = 0; // start index within group\n    if (row_start == col_start) {\n      // if in the same group, start from the next\n      group_start = trd_id + 1;\n    }\n    for (ushort i = group_start; i < col_size; i++) {\n      float4 a = cur_proposal;\n      ushort idx = indices[global_offset + col_start * threads_per_group + i] * 4;\n      float4 b = float4(proposals[idx], proposals[idx + 1], proposals[idx + 2], proposals[idx + 3]);\n      float left = max(a[0], b[0]);\n      float right = min(a[2], b[2]);\n      float top = max(a[1], b[1]);\n      float bottom = min(a[3], b[3]);\n      float width = max(right - left + 1.0, 0.0);\n      float height = max(bottom - top + 1.0, 0.0);\n      float interS = width * height;\n      float Sa = (a[2] - a[0] + 1.0) * (a[3] - a[1] + 1.0);\n      float Sb = (b[2] - b[0] + 1.0) * (b[3] - b[1] + 1.0);\n      float iou = interS / (Sa + Sb - interS);\n      if (iou - nms_thresh > 0) {\n        cur_mask |= 1U << i;\n      }\n    }\n    ushort col_blocks = (num_proposals + threads_per_group - 1) / threads_per_group;\n    mask[cur_idx * col_blocks + col_start] = cur_mask;\n  }\n}\n\nkernel void resize_nearest(texture2d_array<half, access::sample> in[[texture(0)]],\n                           texture2d_array<half, access::write> out[[texture(1)]],\n                           ushort3 gid[[thread_position_in_grid]]) {\n  const ushort oH = ushort_arg_0;\n  const ushort oW = ushort_arg_1;\n  if (gid.x >= oW || gid.y >= oH) {\n    return;\n  }\n  const float height_scale = float(ushort_arg_2) / 10000;\n  const float width_scale = float(ushort_arg_3) / 10000;\n  constexpr sampler s(coord::pixel, address::clamp_to_edge, filter::nearest);\n  const int in_y = (int)(gid.y / height_scale);\n  const int in_x = (int)(gid.x / width_scale);\n  out.write(in.sample(s, float2(in_x, in_y), gid.z), gid.xy, gid.z);\n}\n\nkernel void resize_nearest_nonarray(texture2d<half, access::sample> in[[texture(0)]],\n                                    texture2d<half, access::write> out[[texture(1)]],\n                                    ushort2 gid[[thread_position_in_grid]]) {\n  const ushort oH = ushort_arg_0;\n  const ushort oW = ushort_arg_1;\n  if (gid.x >= oW || gid.y >= oH) {\n    return;\n  }\n  const float height_scale = float(ushort_arg_2) / 10000;\n  const float width_scale = float(ushort_arg_3) / 10000;\n  constexpr sampler s(coord::pixel, address::clamp_to_edge, filter::nearest);\n  const int in_y = (int)(gid.y / height_scale);\n  const int in_x = (int)(gid.x / width_scale);\n  out.write(in.sample(s, float2(in_x, in_y)), gid.xy);\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/mpscnn/mpscnn.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n#include \"caffe2/core/net.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\nstatic constexpr const char* kMPSCNNReadCountArg = \"__mpscnn_read_count__\";\nstatic constexpr const char* kMPSCNNOutputIsTempImageArg = \"__mpscnn_output_is_temp_img__\";\nstatic constexpr const int kMetalMaxTextureArrLength = 2048;\n// We currently only try to convert a fixed set of operators that handle a subset of a full\n// CNN. We also only run when MPSCNN is available, provides a speedup.\n// On failure, returns false. On success, returns true, and sets the MPSCNN net in the output\n// parameter.\n\nbool tryConvertToMPSCNN(const NetDef& initNet, const NetDef& predictNet, NetDef* mpscnnPredictNet);\n\n// Exposed for testing.\nNetDef annotateDefWithReadCounts(const NetDef& net);\nNetDef rewriteForMetal(const NetDef& net);\nNetDef runMPSCNNFusion(const NetDef& net);\nvoid dumpDef(const NetDef& d);\nvoid mpscnnRecordExecutionFinish();\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/mpscnn/mpscnn.mm",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/context.h\"\n\n#if defined(CAFFE2_USE_MPSCNN) && CAFFE2_MOBILE\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/operators/conv_transpose_unpool_op_base.h\"\n#include \"caffe2/operators/generate_proposals_op.h\"\n#include \"caffe2/operators/generate_proposals_op_util_boxes.h\"\n#include \"caffe2/operators/spatial_batch_norm_op.h\"\n\n#include \"mpscnn.h\"\n#include \"mpscnn_context.h\"\n\n#import <Metal/Metal.h>\n#import <MetalPerformanceShaders/MetalPerformanceShaders.h>\n#import <UIKit/UIDevice.h>\n\n#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) \\\n  ([[[UIDevice currentDevice] systemVersion]       \\\n       compare:v                                   \\\n       options:NSNumericSearch] != NSOrderedAscending)\n\n// Only compiles against Base SDK iOS 11.0 or greater\n@interface ConvDataSource : NSObject<MPSCNNConvolutionDataSource>\n@property float* weights_;\n@property float* bias_;\n@property MPSCNNConvolutionDescriptor* desc_;\n@end\n\n@implementation ConvDataSource\n- (id)initWithWeight:(float*)weights\n                bias:(float*)bias\n                desc:(MPSCNNConvolutionDescriptor*)desc {\n  self = [super init];\n  self.weights_ = weights;\n  self.bias_ = bias;\n  self.desc_ = desc;\n  return self;\n}\n- (float*)biasTerms {\n  return self.bias_;\n}\n\n- (MPSDataType)dataType {\n  return MPSDataTypeFloat32;\n}\n- (MPSCNNConvolutionDescriptor*)descriptor {\n  return self.desc_;\n}\n- (NSString*)label {\n  return nullptr;\n}\n- (BOOL)load {\n  return true;\n}\n- (float*)lookupTableForUInt8Kernel {\n  return nullptr;\n}\n- (void)purge {\n  return;\n}\n- (vector_float2*)rangesForUInt8Kernel {\n  return nullptr;\n}\n- (void*)weights {\n  return self.weights_;\n}\n@end\n\nnamespace caffe2 {\n\nnamespace {\nauto divRoundUp(uint x, uint y) -> uint {\n  return (x + y - 1) / y;\n}\n\nMPSTemporaryImage* createTemporaryImage(\n    const OperatorBase* op,\n    id<MTLCommandBuffer> commandBuffer,\n    int n,\n    int height,\n    int width,\n    int channels,\n    size_t output_idx = 0) {\n  auto* image = [MPSTemporaryImage\n      temporaryImageWithCommandBuffer:commandBuffer\n                      imageDescriptor:\n                          [MPSImageDescriptor\n                              imageDescriptorWithChannelFormat:\n                                  MPSImageFeatureChannelFormatFloat16\n                                                         width:width\n                                                        height:height\n                                               featureChannels:channels\n                                                numberOfImages:n\n                                                         usage:\n                                                             MTLTextureUsageShaderRead |\n                                                         MTLTextureUsageShaderWrite]];\n  // We'll try to look at the per-output_idx read-count argument, otherwise,\n  // we'll use the operator-global default.\n  const auto& readCounts = op->GetRepeatedArgument<int>(kMPSCNNReadCountArg);\n  const auto readCount = readCounts.size()\n      ? readCounts.at(output_idx)\n      : op->GetSingleArgument<int>(kMPSCNNReadCountArg, 1);\n  CAFFE_ENFORCE_GE(readCount, 1);\n  image.readCount = readCount;\n  return image;\n}\n\nMPSImage* createStaticImage(int n, int height, int width, int channels) {\n  return [[MPSImage alloc]\n       initWithDevice:getMPSCNNContext().device\n      imageDescriptor:\n          [MPSImageDescriptor\n              imageDescriptorWithChannelFormat:\n                  MPSImageFeatureChannelFormatFloat16\n                                         width:width\n                                        height:height\n                               featureChannels:channels\n                                numberOfImages:n\n                                         usage:MTLTextureUsageShaderRead |\n                                         MTLTextureUsageShaderWrite]];\n}\n\nclass MPSImageWrapper {\n public:\n  MPSImageWrapper() {}\n  MPSImageWrapper(\n      const OperatorBase* op,\n      MPSImageWrapper* parent,\n      int n,\n      int height,\n      int width,\n      int channels,\n      size_t output_idx = 0) {\n    /* If the parent wrapper contains a temporary image, we need to pass on the\n     * command buffer because the temporary images are attached to the command\n     * buffer, we will need to use the same command buffer in order to use the\n     * temporary image. We don't want to synchronize the parent wrapper because\n     * it is still in use. If the parent wrapper contains a static image, we\n     * should create a new command buffer because we use static image so it can\n     * survive synchronization(commit of the command buffer), which means if we\n     * pass on the command buffer the command buffer will be commited in\n     * multiple places in the graph. Also since we don't pass on parent's\n     * command buffer,we need to synchronize(commit) it since it won't be used\n     * in the future.\n     */\n    bool passOnCb = parent != nullptr && parent->isTemporaryImage_;\n    commandBuffer_ = passOnCb ? parent->commandBuffer_\n                              : [getMPSCNNContext().commandQueue commandBuffer];\n\n    bool commitInputCb = parent != nullptr && !parent->isTemporaryImage_;\n    if (commitInputCb) {\n      parent->synchronize();\n    }\n\n    const auto& isTemporaryImages =\n        op->GetRepeatedArgument<int>(kMPSCNNOutputIsTempImageArg);\n    isTemporaryImage_ = isTemporaryImages.size()\n        ? isTemporaryImages.at(output_idx)\n        : op->GetSingleArgument<int>(kMPSCNNOutputIsTempImageArg, 1);\n    if (isTemporaryImage_) {\n      image_ = createTemporaryImage(\n          op, commandBuffer_, n, height, width, channels, output_idx);\n    } else {\n      image_ = createStaticImage(n, height, width, channels);\n    }\n  }\n\n  void markRead() {\n    if (isTemporaryImage_) {\n      MPSTemporaryImage* tempImg = (MPSTemporaryImage*)image_;\n      tempImg.readCount -= 1;\n    }\n  }\n\n  MPSImage* getImage() const {\n    return image_;\n  }\n\n  id<MTLCommandBuffer> getCommandBuffer() const {\n    return commandBuffer_;\n  }\n\n  void synchronize() {\n    // commit the command buffer if it is notEnqueued\n    if (commandBuffer_ != nullptr && commandBuffer_.status == 0) {\n      [commandBuffer_ commit];\n    }\n  }\n\n  void cleanup() {\n    markRead();\n    synchronize();\n  }\n\n  void copyToOutputBlob(Blob* output) {\n    output->GetMutable<MPSImageWrapper>()->image_ = image_;\n    output->GetMutable<MPSImageWrapper>()->commandBuffer_ = commandBuffer_;\n    output->GetMutable<MPSImageWrapper>()->isTemporaryImage_ =\n        isTemporaryImage_;\n  }\n\n private:\n  MPSImage* image_{nullptr};\n  id<MTLCommandBuffer> commandBuffer_{nullptr};\n  bool isTemporaryImage_ = true;\n};\n\nNSString*\nkernelFor(const MPSImage* X, NSString* arrayKernel, NSString* nonArrayKernel) {\n  if (X.featureChannels > 4) {\n    return arrayKernel;\n  }\n  if (X.numberOfImages > 1) {\n    return arrayKernel;\n  }\n  return nonArrayKernel;\n}\n\nstruct LaunchParams {\n  MTLSize threadsPerThreadgroup;\n  MTLSize threadgroupsPerGrid;\n};\n\nLaunchParams spatialPointwiseKernelLaunchParams(\n    id<MTLComputePipelineState> pipeline,\n    const MPSImage* im) {\n  const auto maxThreadsPerThreadgroup =\n      [pipeline maxTotalThreadsPerThreadgroup];\n  const auto threadExecutionWidth = [pipeline threadExecutionWidth];\n  const auto threadsPerThreadgroup = MTLSizeMake(\n      8 /* threadExecutionWidth */,\n      4 /* maxThreadsPerThreadgroup / threadExecutionWidth */,\n      1);\n  const auto threadgroupsPerGrid = MTLSizeMake(\n      divRoundUp(im.width, threadsPerThreadgroup.width),\n      divRoundUp(im.height, threadsPerThreadgroup.height),\n      im.numberOfImages * divRoundUp(im.featureChannels, 4));\n  return {threadsPerThreadgroup, threadgroupsPerGrid};\n};\n\nvoid computeOutputHW(\n    ConvPoolOpBase<CPUContext>* op,\n    int H,\n    int W,\n    int* OH,\n    int* OW) {\n  Tensor<CPUContext> input, output;\n  input.Resize(1, 1, H, W);\n  op->SetOutputSize<CPUContext>(input, &output, 1);\n  CAFFE_ENFORCE_EQ(output.ndim(), 4);\n  *OH = output.dim(2);\n  *OW = output.dim(3);\n}\n\nconstexpr int computeMPSAlignOffset(int kernel, int pad) {\n  // To set the offset, we can just match the top-left pixel (in the input\n  // image, with negative values for padding) that we look at. For 3x3s1p1, we\n  // look at the (-1, -1) pixel in the original impl. For 3x3s1p0, we look at\n  // (0, 0) pixel. For 3x3s1p2, look at (-2, -2) MPSCNN always looks at\n  // (-floor(kernel_size - 1 / 2), -floor(kernel_size - 1 / 2)) Thus, we just\n  // need to match this up.\n\n  // For 3x3s1p1, offset should be (0, 0)\n  // For 3x3s1p0, offset should be (1, 1)\n  // For 3x3s1p2, offset should be (-1, -1)\n  const int mps_offset = kernel / 2;\n  const int c2_offset = pad;\n  return mps_offset - c2_offset;\n};\n\n// Compute the 1-d index of a n-dimensional contiguous row-major tensor for\n//     a given n-dimensional index 'index'\nsize_t ComputeStartIndex(\n    const TensorCPU& tensor,\n    const std::vector<int>& index) {\n  DCHECK_EQ(index.size(), tensor.ndim());\n\n  size_t ret = 0;\n  for (int i = 0; i < index.size(); i++) {\n    ret += index[i] * tensor.size_from_dim(i + 1);\n  }\n\n  return ret;\n}\n\n// Get a sub tensor view from 'tensor' using data pointer from 'tensor'\ntemplate <class T>\nutils::ConstTensorView<T> GetSubTensorView(\n    const TensorCPU& tensor,\n    int dim0_start_index) {\n  DCHECK_EQ(tensor.meta().itemsize(), sizeof(T));\n\n  if (tensor.size() == 0) {\n    return utils::ConstTensorView<T>(nullptr, {});\n  }\n\n  std::vector<int> start_dims(tensor.ndim(), 0);\n  start_dims.at(0) = dim0_start_index;\n  auto st_idx = ComputeStartIndex(tensor, start_dims);\n  auto ptr = tensor.data<T>() + st_idx;\n\n  auto& input_dims = tensor.dims();\n  std::vector<int> ret_dims(input_dims.begin() + 1, input_dims.end());\n\n  utils::ConstTensorView<T> ret(ptr, ret_dims);\n  return ret;\n}\n\nclass CopyToMPSCNNOp final : public Operator<CPUContext> {\n public:\n  CopyToMPSCNNOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    inputBuffers_.resize(Inputs().size());\n    std::vector<MPSImageWrapper> wrappers(Inputs().size());\n    for (auto i = 0; i < Inputs().size(); ++i) {\n      const auto& X = Input(i);\n      CAFFE_ENFORCE(X.ndim() > 0 && X.ndim() <= 4);\n      std::vector<TIndex> XDims = {1, 1, 1, 1};\n      XDims.assign(X.dims().begin(), X.dims().end());\n\n      caffe2::Timer t;\n      const auto n = XDims[0];\n      const auto width = XDims[3];\n      const auto height = XDims[2];\n      const auto channels = XDims[1];\n      caffe2::Timer copyT;\n      if (!inputBuffers_[i] || inputBuffers_[i].length != X.nbytes()) {\n        inputBuffers_[i] = [getMPSCNNContext().device\n            newBufferWithLength:X.nbytes()\n                        options:MTLResourceOptionCPUCacheModeWriteCombined];\n      }\n      memcpy([inputBuffers_[i] contents], X.raw_data(), X.nbytes());\n      VLOG(2) << \"CopyToMPSCNNOp input copy took: \" << copyT.MilliSeconds();\n      if (i == 0) {\n        wrappers[i] =\n            MPSImageWrapper(this, nullptr, n, height, width, channels, i);\n      } else {\n        wrappers[i] =\n            MPSImageWrapper(this, &wrappers[0], n, height, width, channels, i);\n      }\n      auto commandBuffer = wrappers[i].getCommandBuffer();\n      MPSImage* output = wrappers[i].getImage();\n      id<MTLComputeCommandEncoder> encoder =\n          [commandBuffer computeCommandEncoder];\n      id<MTLComputePipelineState> state =\n          getMPSCNNContext().getSpecializedPipelineState(\n              kernelFor(\n                  output,\n                  @\"copy_nchw_to_metal\",\n                  @\"copy_nchw_to_metal_nonarray\"),\n              {{ushort(channels), ushort(height), ushort(width)}});\n      [encoder setComputePipelineState:state];\n      [encoder setBuffer:inputBuffers_[i] offset:0 atIndex:0];\n      [encoder setTexture:[output texture] atIndex:0];\n      const auto& launchParams =\n          spatialPointwiseKernelLaunchParams(state, output);\n      [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n              threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n      [encoder endEncoding];\n      VLOG(2) << \"CopyToMPSCNNOp took: \" << t.MilliSeconds();\n      wrappers[i].copyToOutputBlob(Outputs()[i]);\n    }\n    return true;\n  }\n\n private:\n  std::vector<id<MTLBuffer>> inputBuffers_;\n};\n\nREGISTER_CPU_OPERATOR(CopyToMPSCNN, CopyToMPSCNNOp);\nOPERATOR_SCHEMA(CopyToMPSCNN)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1, INT_MAX)\n    .SameNumberOfOutput();\n\nauto mpsImageSize = [](MPSImage* X) {\n  return X.featureChannels * X.height * X.width * X.numberOfImages;\n};\n\nclass CopyFromMPSCNNOp final : public Operator<CPUContext> {\n public:\n  CopyFromMPSCNNOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    caffe2::Timer t;\n    auto Wrapper = [&](size_t i) {\n      return Inputs()[i]->template Get<MPSImageWrapper>();\n    };\n    auto cb = [&](size_t i) { return Wrapper(i).getCommandBuffer(); };\n    auto X = [&](size_t i) { return Wrapper(i).getImage(); };\n\n    auto cb0 = cb(0);\n    outputBuffers_.resize(Inputs().size());\n    for (auto i = 0; i < Inputs().size(); ++i) {\n      CAFFE_ENFORCE_EQ(cb0, cb(i));\n      MPSImage* Xi = X(i);\n      if (!outputBuffers_[i] ||\n          outputBuffers_[i].length != mpsImageSize(Xi) * sizeof(float)) {\n        outputBuffers_[i] = [getMPSCNNContext().device\n            newBufferWithLength:mpsImageSize(Xi) * sizeof(float)\n                        options:MTLResourceOptionCPUCacheModeDefault];\n      }\n      id<MTLComputeCommandEncoder> encoder = [cb0 computeCommandEncoder];\n      id<MTLComputePipelineState> state =\n          getMPSCNNContext().getSpecializedPipelineState(\n              kernelFor(\n                  Xi, @\"copy_metal_to_nchw\", @\"copy_metal_to_nchw_nonarray\"),\n              {{ushort(Xi.featureChannels),\n                ushort(Xi.height),\n                ushort(Xi.width)}});\n\n      [encoder setComputePipelineState:state];\n      [encoder setBuffer:outputBuffers_[i] offset:0 atIndex:0];\n      [encoder setTexture:[Xi texture] atIndex:0];\n\n      const auto& launchParams = spatialPointwiseKernelLaunchParams(state, Xi);\n      [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n              threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n      [encoder endEncoding];\n      Wrapper(i).markRead();\n    }\n    [cb0 commit];\n    [cb0 waitUntilCompleted];\n\n    for (auto i = 0; i < Inputs().size(); ++i) {\n      caffe2::Timer copyOutT;\n      MPSImage* Xi = X(i);\n      Output(i)->Resize(\n          Xi.numberOfImages, Xi.featureChannels, Xi.height, Xi.width);\n      Output(i)->mutable_data<float>();\n      CAFFE_ENFORCE_EQ(outputBuffers_[i].length, Output(i)->nbytes());\n      memcpy(\n          Output(i)->mutable_data<float>(),\n          [outputBuffers_[i] contents],\n          outputBuffers_[i].length);\n      VLOG(2) << \"CopyFromMPSCNNOp memcpy took: \" << copyOutT.MilliSeconds();\n    }\n    VLOG(2) << \"CopyFromMPSCNNOp took: \" << t.MilliSeconds();\n    return true;\n  }\n\n private:\n  std::vector<id<MTLBuffer>> outputBuffers_;\n};\n\nREGISTER_CPU_OPERATOR(CopyFromMPSCNN, CopyFromMPSCNNOp);\nOPERATOR_SCHEMA(CopyFromMPSCNN)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1, INT_MAX)\n    .SameNumberOfOutput();\n\nclass MPSCNNPackedInt8BGRANHWCToNCHWCStylizerPreprocessOp final\n    : public Operator<CPUContext> {\n public:\n  MPSCNNPackedInt8BGRANHWCToNCHWCStylizerPreprocessOp(\n      const OperatorDef& operator_def,\n      Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws), ws_(ws) {}\n\n  bool RunOnDevice() override {\n    const auto& X = Input(0);\n    const auto& mean = Input(1);\n    CAFFE_ENFORCE_EQ(mean.size(), 3);\n    CAFFE_ENFORCE_EQ(X.ndim(), 4);\n    CAFFE_ENFORCE_EQ(X.dim(0), 1);\n    CAFFE_ENFORCE_EQ(X.dim(3), 4);\n    const auto H = X.dim(1);\n    const auto W = X.dim(2);\n\n    caffe2::Timer t;\n\n    auto* noiseBlob = ws_->CreateBlob(\"__CAFFE2_STYLIZER_NOISE__\");\n    ushort noiseSize = OperatorBase::GetSingleArgument<int>(\n        \"noise_size\", 491 /* prime to avoid artifacts */);\n    // Treaded as half4 in the kernel, so need half4 here.\n    noiseSize = divRoundUp(noiseSize, 4) * 4;\n    if (!noiseBlob->IsType<TensorCPU>() ||\n        noiseBlob->Get<TensorCPU>().size() != noiseSize) {\n      VLOG(2) << \"Initializing stylizer with noise: \" << noiseSize;\n      caffe2::Timer rt;\n      // Initialize random noise on first use.\n      // Cache it to maintain temporal consistency.\n      auto* t = noiseBlob->template GetMutable<TensorCPU>();\n      t->Resize(noiseSize);\n      math::RandGaussian<float, CPUContext>(\n          t->size(),\n          0.0,\n          OperatorBase::GetSingleArgument<float>(\"noise_std\", 10.0),\n          t->template mutable_data<float>(),\n          &context_);\n      VLOG(2) << \"Preprocess initializing noise: \" << rt.MilliSeconds();\n    }\n    const auto& noise = noiseBlob->Get<TensorCPU>();\n\n    if (!inputBuffer_ || inputBuffer_.length != X.nbytes()) {\n      caffe2::Timer pt;\n\n      inputBuffer_ = [getMPSCNNContext().device\n          newBufferWithLength:X.nbytes()\n                      options:MTLResourceOptionCPUCacheModeWriteCombined];\n      meanBuffer_ = [getMPSCNNContext().device\n          newBufferWithLength:4 * 2 // (3/4 half-floats).\n                      options:MTLResourceOptionCPUCacheModeWriteCombined];\n      noiseBuffer_ = [getMPSCNNContext().device\n          newBufferWithLength:noiseSize * sizeof(float16_t)\n                      options:MTLResourceOptionCPUCacheModeWriteCombined];\n\n      float16_t* meanBufferPtr = (float16_t*)[meanBuffer_ contents];\n      CAFFE_ENFORCE(meanBufferPtr);\n      for (auto i = 0; i < mean.size(); ++i) {\n        meanBufferPtr[i] = mean.data<float>()[i];\n      }\n      float16_t* noiseBufferPtr = (float16_t*)[noiseBuffer_ contents];\n      CAFFE_ENFORCE(noiseBufferPtr);\n      for (auto i = 0; i < noise.size(); ++i) {\n        noiseBufferPtr[i] = noise.data<float>()[i];\n      }\n\n      VLOG(2) << \"Preprocess construct took: \" << pt.MilliSeconds();\n    }\n\n    {\n      caffe2::Timer ct;\n      memcpy([inputBuffer_ contents], X.raw_data(), X.nbytes());\n      VLOG(2) << \"Preprocess memcpy took: \" << ct.MilliSeconds();\n    }\n    auto outputWrapper = MPSImageWrapper(this, nullptr, 1, H, W, 3);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n\n    id<MTLComputeCommandEncoder> encoder =\n        [commandBuffer computeCommandEncoder];\n    id<MTLComputePipelineState> state =\n        getMPSCNNContext().getSpecializedPipelineState(\n            @\"preprocess_stylizer\", {noiseSize});\n\n    [encoder setComputePipelineState:state];\n    [encoder setBuffer:inputBuffer_ offset:0 atIndex:0];\n    [encoder setBuffer:meanBuffer_ offset:0 atIndex:1];\n    [encoder setBuffer:noiseBuffer_ offset:0 atIndex:2];\n\n    [encoder setTexture:[output texture] atIndex:0];\n    const auto& launchParams =\n        spatialPointwiseKernelLaunchParams(state, output);\n    [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n            threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n    [encoder endEncoding];\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n\n    VLOG(2) << \"Preprocess took: \" << t.MilliSeconds();\n    return true;\n  }\n\n private:\n  Workspace* ws_{nullptr};\n  id<MTLBuffer> inputBuffer_{nullptr};\n  id<MTLBuffer> noiseBuffer_{nullptr};\n  id<MTLBuffer> meanBuffer_{nullptr};\n};\n\nREGISTER_CPU_OPERATOR(\n    MPSCNNPackedInt8BGRANHWCToNCHWCStylizerPreprocess,\n    MPSCNNPackedInt8BGRANHWCToNCHWCStylizerPreprocessOp);\nOPERATOR_SCHEMA(MPSCNNPackedInt8BGRANHWCToNCHWCStylizerPreprocess)\n    .NumInputs(2)\n    .NumOutputs(1);\n\nclass MPSCNNBRGNCHWCToPackedInt8BGRAStylizerDeprocessOp final\n    : public Operator<CPUContext> {\n public:\n  MPSCNNBRGNCHWCToPackedInt8BGRAStylizerDeprocessOp(\n      const OperatorDef& operator_def,\n      Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto inputWrapper = Inputs()[0]->Get<MPSImageWrapper>();\n    MPSImage* X = inputWrapper.getImage();\n    id<MTLCommandBuffer> commandBuffer = inputWrapper.getCommandBuffer();\n\n    const auto& mean = Input(1);\n    caffe2::Timer t;\n    const auto W = X.width;\n    const auto H = X.height;\n    CAFFE_ENFORCE_EQ(X.featureChannels, 3);\n    CAFFE_ENFORCE_EQ(X.numberOfImages, 1);\n\n    if (!outputBuffer_ || outputBuffer_.length != X.height * X.width * 4) {\n      caffe2::Timer pt;\n\n      outputBuffer_ = [getMPSCNNContext().device\n          newBufferWithLength:X.height * X.width * 4\n                      options:MTLResourceOptionCPUCacheModeDefault];\n      meanBuffer_ = [getMPSCNNContext().device\n          newBufferWithLength:4 * 2 // (3/4 half-floats).\n                      options:MTLResourceOptionCPUCacheModeWriteCombined];\n      float16_t* meanBufferPtr = (float16_t*)[meanBuffer_ contents];\n      for (auto i = 0; i < mean.size(); ++i) {\n        meanBufferPtr[i] = mean.data<float>()[i];\n      }\n      VLOG(2) << \"Deprocess copy took: \" << pt.MilliSeconds();\n    }\n    id<MTLComputeCommandEncoder> encoder =\n        [commandBuffer computeCommandEncoder];\n    id<MTLComputePipelineState> state =\n        getMPSCNNContext().getPipelineState(@\"deprocess_stylizer\");\n\n    CAFFE_ENFORCE_EQ(outputBuffer_.length, X.height * X.width * 4);\n    [encoder setComputePipelineState:state];\n    [encoder setBuffer:outputBuffer_ offset:0 atIndex:0];\n    [encoder setBuffer:meanBuffer_ offset:0 atIndex:1];\n    [encoder setTexture:[X texture] atIndex:0];\n    const auto& launchParams = spatialPointwiseKernelLaunchParams(state, X);\n    [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n            threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n    [encoder endEncoding];\n    inputWrapper.markRead();\n\n    [commandBuffer commit];\n    [commandBuffer waitUntilCompleted];\n\n    Output(0)->Resize(1, X.height, X.width, 4);\n    {\n      caffe2::Timer ct;\n      memcpy(\n          Output(0)->mutable_data<uint8_t>(),\n          [outputBuffer_ contents],\n          [outputBuffer_ length]);\n      VLOG(2) << \"Deprocess copy: \" << t.MilliSeconds();\n    }\n    CAFFE_ENFORCE_EQ(Output(0)->nbytes(), [outputBuffer_ length]);\n    VLOG(2) << \"Deprocess took: \" << t.MilliSeconds();\n\n    return true;\n  }\n\n private:\n  id<MTLBuffer> outputBuffer_{nullptr};\n  id<MTLBuffer> meanBuffer_{nullptr};\n};\n\nREGISTER_CPU_OPERATOR(\n    MPSCNNBRGNCHWCToPackedInt8BGRAStylizerDeprocess,\n    MPSCNNBRGNCHWCToPackedInt8BGRAStylizerDeprocessOp);\nOPERATOR_SCHEMA(MPSCNNBRGNCHWCToPackedInt8BGRAStylizerDeprocess)\n    .NumInputs(2)\n    .NumOutputs(1);\n\ntemplate <typename Neuron>\nclass MPSCNNNeuronOp final : public Operator<CPUContext> {\n public:\n  MPSCNNNeuronOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    caffe2::Timer t;\n    auto inputWrapper = Inputs()[0]->template Get<MPSImageWrapper>();\n    MPSImage* X = inputWrapper.getImage();\n\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &inputWrapper,\n        X.numberOfImages,\n        X.height,\n        X.width,\n        X.featureChannels);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n    CAFFE_ENFORCE_EQ(output.width, X.width);\n    CAFFE_ENFORCE_EQ(output.height, X.height);\n    CAFFE_ENFORCE_EQ(output.featureChannels, X.featureChannels);\n\n    if (!neuron_) {\n      neuron_ = Neuron::t();\n    }\n    [neuron_ encodeToCommandBuffer:commandBuffer\n                       sourceImage:X\n                  destinationImage:output];\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n\n    VLOG(2) << \"ElementwiseAdd took: \" << t.MilliSeconds();\n    return true;\n  }\n  MPSCNNNeuron* neuron_{nullptr};\n};\n\n#define INIT_NEURON_OP(n)                                          \\\n  REGISTER_CPU_OPERATOR(MPSCNN##n, MPSCNNNeuronOp<n##NeuronInit>); \\\n  OPERATOR_SCHEMA(MPSCNN##n).NumInputs(1).NumOutputs(1).AllowInplace({{0, 0}});\n\nstruct SigmoidNeuronInit {\n  static MPSCNNNeuron* t() {\n    return\n        [[MPSCNNNeuronSigmoid alloc] initWithDevice:getMPSCNNContext().device];\n  }\n};\nINIT_NEURON_OP(Sigmoid);\n\nstruct ReluNeuronInit {\n  static MPSCNNNeuron* t() {\n    return\n        [[MPSCNNNeuronReLU alloc] initWithDevice:getMPSCNNContext().device a:0];\n  }\n};\nINIT_NEURON_OP(Relu);\n\nstruct TanhNeuronInit {\n  static MPSCNNNeuron* t() {\n    return [[MPSCNNNeuronTanH alloc] initWithDevice:getMPSCNNContext().device\n                                                  a:1\n                                                  b:1];\n  }\n};\nINIT_NEURON_OP(Tanh);\n\n#undef INIT_NEURON_OP\n\ntemplate <typename Neuron>\nclass MPSCNNConvOp final : public ConvPoolOpBase<CPUContext> {\n public:\n  MPSCNNConvOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(\n        this->order_ == StorageOrder::NCHW, \"Metal only supports NCHW order.\");\n    OPERATOR_NEEDS_FEATURE(\n        kernel_h() == kernel_w(),\n        \"Metal only supports equal kernel dimension.\");\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    caffe2::Timer t;\n    auto inputWrapper = Inputs()[0]->template Get<MPSImageWrapper>();\n    MPSImage* X = inputWrapper.getImage();\n\n    auto& filter = Input(FILTER);\n    auto& bias = Input(BIAS);\n    CAFFE_ENFORCE_EQ(filter.ndim(), 4);\n    // For NCHW, X.dim32(1), inputChannels\n    const int C = X.featureChannels;\n    const int M = filter.dim32(0);\n    const int Cf = filter.dim32(1);\n\n    CAFFE_ENFORCE(filter.dim32(2) == kernel_h(), \"\");\n    CAFFE_ENFORCE(filter.dim32(3) == kernel_w(), \"\");\n    CAFFE_ENFORCE(bias.ndim() == 1, \"\");\n    CAFFE_ENFORCE(bias.dim32(0) == M, \"\");\n\n    const auto kH = kernel_h();\n    const auto kW = kernel_w();\n\n    // ConvPoolOpBase<CPUContext>::SetOutputSize(X, Y, filter.dim32(0));\n    // Reformat weights from [M][C][kH][kW] to [M][kH][kW][C].\n    if (!conv_) {\n      caffe2::Timer consT;\n      std::vector<float> refilter(M * kH * kW * Cf);\n      auto* filter_ = filter.template data<float>();\n      for (auto m = 0; m < M; ++m) {\n        for (auto c = 0; c < Cf; ++c) {\n          for (auto kh = 0; kh < kH; ++kh) {\n            for (auto kw = 0; kw < kW; ++kw) {\n              // refilter[m][kh][kw][c]\n              refilter[m * kH * kW * Cf + kh * kW * Cf + kw * Cf + c] =\n                  // filter[m][c][kh][kw]\n                  filter_[m * Cf * kH * kW + c * kH * kW + kh * kW + kw];\n            }\n          }\n        }\n      }\n      // DepthwiseConv path\n      bool runtimeAtLeastIOS11 =\n          SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@\"11.0\");\n      // Only inputFeatureChannels == outputFeatureChannels is supported right\n      // now\n      if (runtimeAtLeastIOS11 && this->group_ > 1 && Cf == 1 &&\n          M == this->group_) {\n        MPSCNNDepthWiseConvolutionDescriptor* desc =\n            [MPSCNNDepthWiseConvolutionDescriptor\n                cnnConvolutionDescriptorWithKernelWidth:kW\n                                           kernelHeight:kH\n                                   inputFeatureChannels:C\n                                  outputFeatureChannels:M\n                                           neuronFilter:Neuron::t()];\n        desc.strideInPixelsX = stride_w();\n        desc.strideInPixelsY = stride_h();\n        desc.groups = 1;\n        auto data_source = [[ConvDataSource alloc]\n            initWithWeight:refilter.data()\n                      bias:const_cast<float*>(bias.template data<float>())\n                      desc:desc];\n        conv_ =\n            [[MPSCNNConvolution alloc] initWithDevice:getMPSCNNContext().device\n                                              weights:data_source];\n      } else {\n        if (this->group_ > 1) {\n          CAFFE_ENFORCE_EQ(\n              Cf % 4,\n              0,\n              \"MPSCNNConvolution requires number of input \\\n                           channels in each group to be multiple of 4 for \\\n                           group > 1.\");\n        }\n        MPSCNNConvolutionDescriptor* desc = [MPSCNNConvolutionDescriptor\n            cnnConvolutionDescriptorWithKernelWidth:kW\n                                       kernelHeight:kH\n                               inputFeatureChannels:C\n                              outputFeatureChannels:M\n                                       neuronFilter:Neuron::t()];\n        desc.strideInPixelsX = stride_w();\n        desc.strideInPixelsY = stride_h();\n        desc.groups = this->group_;\n        auto data_source = [[ConvDataSource alloc]\n            initWithWeight:refilter.data()\n                      bias:const_cast<float*>(bias.template data<float>())\n                      desc:desc];\n        conv_ =\n            [[MPSCNNConvolution alloc] initWithDevice:getMPSCNNContext().device\n                                              weights:data_source];\n      }\n\n      [conv_ setEdgeMode:MPSImageEdgeModeZero];\n\n      MPSOffset offset;\n      offset.x = computeMPSAlignOffset(kW, pad_l());\n      offset.y = computeMPSAlignOffset(kH, pad_t());\n      offset.z = 0;\n      [conv_ setOffset:offset];\n      VLOG(2) << \"MPSCNNConv ConvDesc took: \" << consT.MilliSeconds();\n    }\n\n    CAFFE_ENFORCE_EQ(conv_.strideInPixelsY, stride_h());\n    CAFFE_ENFORCE_EQ(conv_.strideInPixelsX, stride_w());\n    CAFFE_ENFORCE_EQ(conv_.inputFeatureChannels, Cf * this->group_);\n    CAFFE_ENFORCE_EQ(M % conv_.groups, 0);\n    CAFFE_ENFORCE_EQ(conv_.outputFeatureChannels, M);\n    CAFFE_ENFORCE_EQ(conv_.kernelWidth, kW);\n    CAFFE_ENFORCE_EQ(conv_.kernelHeight, kH);\n\n    int output_height;\n    int output_width;\n    computeOutputHW(this, X.height, X.width, &output_height, &output_width);\n    int output_channels = M;\n\n    VLOG(2) << \"Output height: \" << output_height;\n    VLOG(2) << \"Output width:\" << output_width;\n    VLOG(2) << \"Output channels:\" << output_channels;\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &inputWrapper,\n        X.numberOfImages,\n        output_height,\n        output_width,\n        output_channels);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n    CAFFE_ENFORCE_EQ(output.height, output_height);\n    CAFFE_ENFORCE_EQ(output.width, output_width);\n    [conv_ encodeToCommandBuffer:commandBuffer\n                     sourceImage:X\n                destinationImage:output];\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n\n    VLOG(2) << \"MPSCNNConv took: \" << t.MilliSeconds();\n    return true;\n  }\n\n  // Input: X, W, b\n  // Output: Y\n  INPUT_TAGS(INPUT, FILTER, BIAS);\n\n  MPSCNNConvolution* conv_{nullptr};\n};\n\n// No-op init\nstruct EmptyNeuronInit {\n  static MPSCNNNeuron* t() {\n    return nil;\n  }\n};\n\n// We can allow the input weights/bias and output to alias each other,\n// for example when doing a Conv + out-of-place ReLU, then fusing.\n#define INIT_CONV_NEURON_OP(name, neuron)                        \\\n  REGISTER_CPU_OPERATOR(name, MPSCNNConvOp<neuron>);             \\\n  OPERATOR_SCHEMA(name).NumInputs(3).NumOutputs(1).AllowInplace( \\\n      {{1, 0}, {2, 0}});\n\nINIT_CONV_NEURON_OP(MPSCNNConv, EmptyNeuronInit);\nINIT_CONV_NEURON_OP(MPSCNNConvRelu, ReluNeuronInit);\nINIT_CONV_NEURON_OP(MPSCNNConvSigmoid, SigmoidNeuronInit);\n\n#undef INIT_CONV_NEURON_OP\n\nclass MPSCNNPadImageOp final : public ConvPoolOpBase<CPUContext> {\n public:\n  MPSCNNPadImageOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(\n        this->order_ == StorageOrder::NCHW, \"Metal only supports NCHW order.\");\n\n    OPERATOR_NEEDS_FEATURE(\n        OperatorBase::GetSingleArgument<string>(\"mode\", \"\") == \"reflect\",\n        \"Metal only supports reflection\");\n    kernel_[0] = kernel_[1] = 1;\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    caffe2::Timer t;\n    auto inputWrapper = Inputs()[0]->Get<MPSImageWrapper>();\n    MPSImage* X = inputWrapper.getImage();\n\n    const auto pH = pad_t();\n    const auto pW = pad_l();\n    const auto output_height = X.height + 2 * pH;\n    const auto output_width = X.width + 2 * pW;\n    VLOG(1) << \"Output height: \" << output_height;\n    VLOG(1) << \"Output width:\" << output_width;\n    VLOG(2) << \"Output channels:\" << X.featureChannels;\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &inputWrapper,\n        X.numberOfImages,\n        output_height,\n        output_width,\n        X.featureChannels);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n    CAFFE_ENFORCE_EQ(output.height, output_height);\n    CAFFE_ENFORCE_EQ(output.width, output_width);\n    id<MTLComputeCommandEncoder> encoder =\n        [commandBuffer computeCommandEncoder];\n    id<MTLComputePipelineState> state =\n        getMPSCNNContext().getPipelineState(kernelFor(\n            output, @\"reflection_padding\", @\"reflection_padding_nonarray\"));\n    [encoder setComputePipelineState:state];\n    [encoder setTexture:[X texture] atIndex:0];\n    [encoder setTexture:[output texture] atIndex:1];\n    const auto& launchParams =\n        spatialPointwiseKernelLaunchParams(state, output);\n    [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n            threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n    [encoder endEncoding];\n    inputWrapper.markRead();\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n\n    VLOG(2) << \"PadImage took: \" << t.MilliSeconds();\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNPadImage, MPSCNNPadImageOp);\nOPERATOR_SCHEMA(MPSCNNPadImage).NumInputs(1).NumOutputs(1);\n\nclass MPSCNNMulOp final : public Operator<CPUContext> {\n public:\n  MPSCNNMulOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(\n        OperatorBase::GetSingleArgument<int>(\"broadcast\", 0) == 1,\n        \"MPSCNNMul only supports broadcast\");\n\n    OPERATOR_NEEDS_FEATURE(\n        OperatorBase::HasArgument(\"axis\") == false,\n        \"MPSCNNMul does not support axis\");\n  }\n\n  bool RunOnDevice() override {\n    caffe2::Timer t;\n\n    auto wrapper0 = Inputs()[0]->Get<MPSImageWrapper>();\n    MPSImage* X0 = wrapper0.getImage();\n\n    const auto& X1 = Input(1);\n    CAFFE_ENFORCE_EQ(\n        X1.ndim(),\n        1,\n        \"MPSCNNMulOp: Only ndim == 1 for Input(1) is supported for now\");\n\n    auto X1_ = [getMPSCNNContext().device\n        newBufferWithBytes:X1.template data<float>()\n                    length:sizeof(float) * X1.size()\n                   options:MTLResourceOptionCPUCacheModeDefault];\n\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &wrapper0,\n        X0.numberOfImages,\n        X0.height,\n        X0.width,\n        X0.featureChannels);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n\n    id<MTLComputeCommandEncoder> encoder =\n        [commandBuffer computeCommandEncoder];\n    id<MTLComputePipelineState> state =\n        getMPSCNNContext().getSpecializedPipelineState(\n            @\"elementwise_mul\",\n            {{ushort(X0.numberOfImages),\n              ushort(X0.featureChannels),\n              ushort(X1.dim32(0))}});\n\n    [encoder setComputePipelineState:state];\n    [encoder setTexture:[X0 texture] atIndex:0];\n    [encoder setBuffer:X1_ offset:0 atIndex:1];\n    [encoder setTexture:[output texture] atIndex:2];\n    const auto& launchParams =\n        spatialPointwiseKernelLaunchParams(state, output);\n    [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n            threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n    [encoder endEncoding];\n    wrapper0.markRead();\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n    VLOG(2) << \"ElementwiseMul took: \" << t.MilliSeconds();\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNMul, MPSCNNMulOp);\nOPERATOR_SCHEMA(MPSCNNMul).NumInputs(2).NumOutputs(1).AllowInplace({{0, 0}});\n\nclass MPSCNNSubOp final : public Operator<CPUContext> {\n public:\n  MPSCNNSubOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(\n        OperatorBase::GetSingleArgument<int>(\"broadcast\", 0) == 1,\n        \"MPSCNNSub only supports broadcast\");\n\n    OPERATOR_NEEDS_FEATURE(\n        OperatorBase::HasArgument(\"axis\") == false,\n        \"MPSCNNSub does not support axis\");\n  }\n\n  bool RunOnDevice() override {\n    caffe2::Timer t;\n\n    auto wrapper0 = Inputs()[0]->Get<MPSImageWrapper>();\n    MPSImage* X0 = wrapper0.getImage();\n\n    const auto& X1 = Input(1);\n    CAFFE_ENFORCE_EQ(\n        X1.ndim(),\n        1,\n        \"MPSCNNSubOp: Only ndim == 1 for Input(1) is supported for now\");\n\n    auto X1_ = [getMPSCNNContext().device\n        newBufferWithBytes:X1.template data<float>()\n                    length:sizeof(float) * X1.size()\n                   options:MTLResourceOptionCPUCacheModeDefault];\n\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &wrapper0,\n        X0.numberOfImages,\n        X0.height,\n        X0.width,\n        X0.featureChannels);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n\n    id<MTLComputeCommandEncoder> encoder =\n        [commandBuffer computeCommandEncoder];\n    id<MTLComputePipelineState> state =\n        getMPSCNNContext().getSpecializedPipelineState(\n            @\"elementwise_sub\",\n            {{ushort(X0.numberOfImages),\n              ushort(X0.featureChannels),\n              ushort(X1.dim32(0))}});\n\n    [encoder setComputePipelineState:state];\n    [encoder setTexture:[X0 texture] atIndex:0];\n    [encoder setBuffer:X1_ offset:0 atIndex:1];\n    [encoder setTexture:[output texture] atIndex:2];\n    const auto& launchParams =\n        spatialPointwiseKernelLaunchParams(state, output);\n    [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n            threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n    [encoder endEncoding];\n    wrapper0.markRead();\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n    VLOG(2) << \"ElementwiseSub took: \" << t.MilliSeconds();\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNSub, MPSCNNSubOp);\nOPERATOR_SCHEMA(MPSCNNSub).NumInputs(2).NumOutputs(1).AllowInplace({{0, 0}});\n\nclass MPSCNNAddOp final : public Operator<CPUContext> {\n public:\n  MPSCNNAddOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    caffe2::Timer t;\n\n    auto wrapper0 = Inputs()[0]->Get<MPSImageWrapper>();\n    auto wrapper1 = Inputs()[1]->Get<MPSImageWrapper>();\n    MPSImage* X0 = wrapper0.getImage();\n    MPSImage* X1 = wrapper1.getImage();\n    CAFFE_ENFORCE_EQ(wrapper0.getCommandBuffer(), wrapper1.getCommandBuffer());\n\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &wrapper0,\n        X0.numberOfImages,\n        X0.height,\n        X0.width,\n        X0.featureChannels);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n    CAFFE_ENFORCE_EQ(X1.width, X0.width);\n    CAFFE_ENFORCE_EQ(X1.height, X0.height);\n    CAFFE_ENFORCE_EQ(X1.featureChannels, X0.featureChannels);\n    id<MTLComputeCommandEncoder> encoder =\n        [commandBuffer computeCommandEncoder];\n    id<MTLComputePipelineState> state = getMPSCNNContext().getPipelineState(\n        kernelFor(X0, @\"elementwise_add\", @\"elementwise_add_nonarray\"));\n\n    [encoder setComputePipelineState:state];\n    [encoder setTexture:[X0 texture] atIndex:0];\n    [encoder setTexture:[X1 texture] atIndex:1];\n    [encoder setTexture:[output texture] atIndex:2];\n    const auto& launchParams =\n        spatialPointwiseKernelLaunchParams(state, output);\n    [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n            threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n    [encoder endEncoding];\n    wrapper0.markRead();\n    wrapper1.markRead();\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n\n    VLOG(2) << \"ElementwiseAdd took: \" << t.MilliSeconds();\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNAdd, MPSCNNAddOp);\n// Not really in-place per-se, but semantically is valid and preserves\n// compatibility.\nOPERATOR_SCHEMA(MPSCNNAdd).NumInputs(2).NumOutputs(1).AllowInplace({{0, 0}});\n\nclass MPSCNNAveragePoolOp final : public ConvPoolOpBase<CPUContext> {\n public:\n  MPSCNNAveragePoolOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(\n        this->order_ == StorageOrder::NCHW, \"Metal only supports NCHW order.\");\n    OPERATOR_NEEDS_FEATURE(\n        kernel_h() == kernel_w(),\n        \"Metal only supports equal kernel dimension.\");\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    caffe2::Timer t;\n    auto inputWrapper = Inputs()[0]->Get<MPSImageWrapper>();\n    MPSImage* X = inputWrapper.getImage();\n\n    if (!pool_ || this->global_pooling_) {\n      caffe2::Timer consT;\n      this->ComputePads({(int)X.height, (int)X.width});\n      pool_ =\n          [[MPSCNNPoolingAverage alloc] initWithDevice:getMPSCNNContext().device\n                                           kernelWidth:kernel_w()\n                                          kernelHeight:kernel_h()\n                                       strideInPixelsX:stride_w()\n                                       strideInPixelsY:stride_h()];\n\n      [pool_ setEdgeMode:MPSImageEdgeModeClamp];\n      MPSOffset offset;\n      offset.x = computeMPSAlignOffset(kernel_w(), pad_l());\n      offset.y = computeMPSAlignOffset(kernel_h(), pad_t());\n      offset.z = 0;\n      [pool_ setOffset:offset];\n      VLOG(2) << \"MPSCNNAveragePool PoolDesc took: \" << consT.MilliSeconds();\n    }\n\n    CAFFE_ENFORCE_EQ(pool_.strideInPixelsY, stride_h());\n    CAFFE_ENFORCE_EQ(pool_.strideInPixelsX, stride_w());\n    int output_height;\n    int output_width;\n    computeOutputHW(this, X.height, X.width, &output_height, &output_width);\n\n    VLOG(2) << \"Output height: \" << output_height;\n    VLOG(2) << \"Output width:\" << output_width;\n    VLOG(2) << \"Output channels:\" << X.featureChannels;\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &inputWrapper,\n        X.numberOfImages,\n        output_height,\n        output_width,\n        X.featureChannels);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n    CAFFE_ENFORCE_EQ(output.height, output_height);\n    CAFFE_ENFORCE_EQ(output.width, output_width);\n    [pool_ encodeToCommandBuffer:commandBuffer\n                     sourceImage:X\n                destinationImage:output];\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n\n    VLOG(2) << \"MPSCNNAveragePool took: \" << t.MilliSeconds();\n    return true;\n  }\n\n  MPSCNNPoolingAverage* pool_{nullptr};\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNAveragePool, MPSCNNAveragePoolOp);\nOPERATOR_SCHEMA(MPSCNNAveragePool).NumInputs(1).NumOutputs(1);\n\nclass MPSCNNMaxPoolOp final : public ConvPoolOpBase<CPUContext> {\n public:\n  MPSCNNMaxPoolOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(\n        this->order_ == StorageOrder::NCHW, \"Metal only supports NCHW order.\");\n    OPERATOR_NEEDS_FEATURE(\n        kernel_h() == kernel_w(),\n        \"Metal only supports equal kernel dimension.\");\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    caffe2::Timer t;\n    auto inputWrapper = Inputs()[0]->Get<MPSImageWrapper>();\n    MPSImage* X = inputWrapper.getImage();\n\n    if (!pool_ || this->global_pooling_) {\n      caffe2::Timer consT;\n      this->ComputePads({(int)X.height, (int)X.width});\n      pool_ = [[MPSCNNPoolingMax alloc] initWithDevice:getMPSCNNContext().device\n                                           kernelWidth:kernel_w()\n                                          kernelHeight:kernel_h()\n                                       strideInPixelsX:stride_w()\n                                       strideInPixelsY:stride_h()];\n\n      [pool_ setEdgeMode:MPSImageEdgeModeClamp];\n      MPSOffset offset;\n      offset.x = computeMPSAlignOffset(kernel_w(), pad_l());\n      offset.y = computeMPSAlignOffset(kernel_h(), pad_t());\n      offset.z = 0;\n      [pool_ setOffset:offset];\n      VLOG(2) << \"MPSCNNMaxPool PoolDesc took: \" << consT.MilliSeconds();\n    }\n\n    CAFFE_ENFORCE_EQ(pool_.strideInPixelsY, stride_h());\n    CAFFE_ENFORCE_EQ(pool_.strideInPixelsX, stride_w());\n\n    int output_height;\n    int output_width;\n    computeOutputHW(this, X.height, X.width, &output_height, &output_width);\n\n    VLOG(2) << \"Output height: \" << output_height;\n    VLOG(2) << \"Output width:\" << output_width;\n    VLOG(2) << \"Output channels:\" << X.featureChannels;\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &inputWrapper,\n        X.numberOfImages,\n        output_height,\n        output_width,\n        X.featureChannels);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n    CAFFE_ENFORCE_EQ(output.height, output_height);\n    CAFFE_ENFORCE_EQ(output.width, output_width);\n    [pool_ encodeToCommandBuffer:commandBuffer\n                     sourceImage:X\n                destinationImage:output];\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n\n    VLOG(2) << \"MPSCNNMaxPool took: \" << t.MilliSeconds();\n    return true;\n  }\n\n  MPSCNNPoolingMax* pool_{nullptr};\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNMaxPool, MPSCNNMaxPoolOp);\nOPERATOR_SCHEMA(MPSCNNMaxPool).NumInputs(1).NumOutputs(1);\n\nclass MPSCNNSoftmaxOp final : public Operator<CPUContext> {\n public:\n  MPSCNNSoftmaxOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    caffe2::Timer t;\n    auto inputWrapper = Inputs()[0]->Get<MPSImageWrapper>();\n    MPSImage* X = inputWrapper.getImage();\n    CAFFE_ENFORCE_EQ(X.height, 1);\n    CAFFE_ENFORCE_EQ(X.width, 1);\n    if (!softmax_) {\n      softmax_ =\n          [[MPSCNNSoftMax alloc] initWithDevice:getMPSCNNContext().device];\n    }\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &inputWrapper,\n        X.numberOfImages,\n        X.height,\n        X.width,\n        X.featureChannels);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n    [softmax_ encodeToCommandBuffer:commandBuffer\n                        sourceImage:X\n                   destinationImage:output];\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n    VLOG(2) << \"MPSCNNSoftmax took: \" << t.MilliSeconds();\n    return true;\n  }\n\n  MPSCNNSoftMax* softmax_{nullptr};\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNSoftmax, MPSCNNSoftmaxOp);\nOPERATOR_SCHEMA(MPSCNNSoftmax).NumInputs(1).NumOutputs(1);\n\ntemplate <typename Neuron>\nclass MPSCNNFullyConnectedOp final : public Operator<CPUContext> {\n public:\n  MPSCNNFullyConnectedOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    caffe2::Timer t;\n    auto inputWrapper = Inputs()[0]->template Get<MPSImageWrapper>();\n    MPSImage* X = inputWrapper.getImage();\n    const auto& W = Input(1);\n    const auto& b = Input(2);\n\n    const auto input_channels = W.dim32(1) / X.width / X.height;\n    CAFFE_ENFORCE_EQ(input_channels, X.featureChannels);\n    const auto output_channels = W.dim32(0);\n    if (!fc_) {\n      const auto M = output_channels;\n      const auto kH = X.height;\n      const auto kW = X.width;\n      const auto C = input_channels;\n      std::vector<float> refilter(M * kH * kW * C);\n      auto* filter_ = W.template data<float>();\n      for (auto m = 0; m < M; ++m) {\n        for (auto c = 0; c < C; ++c) {\n          for (auto kh = 0; kh < kH; ++kh) {\n            for (auto kw = 0; kw < kW; ++kw) {\n              // refilter[m][kh][kw][c]\n              refilter[m * kH * kW * C + kh * kW * C + kw * C + c] =\n                  // filter[m][c][kh][kw]\n                  filter_[m * C * kH * kW + c * kH * kW + kh * kW + kw];\n            }\n          }\n        }\n      }\n\n      MPSCNNConvolutionDescriptor* desc = [MPSCNNConvolutionDescriptor\n          cnnConvolutionDescriptorWithKernelWidth:X.width\n                                     kernelHeight:X.height\n                             inputFeatureChannels:input_channels\n                            outputFeatureChannels:output_channels\n                                     neuronFilter:Neuron::t()];\n      auto data_source = [[ConvDataSource alloc]\n          initWithWeight:refilter.data()\n                    bias:const_cast<float*>(b.template data<float>())\n                    desc:desc];\n      fc_ = [[MPSCNNConvolution alloc] initWithDevice:getMPSCNNContext().device\n                                              weights:data_source];\n    }\n    // Note that X.numberOfImages can change between calls, but X.height and\n    // X.width are static by definition.\n    VLOG(2) << \"MPSCNNFC: \" << X.numberOfImages << \", \" << X.width << \", \"\n            << X.height << \", \" << X.featureChannels << \", \" << output_channels;\n\n    [fc_ setClipRect:MTLRegionMake3D(0, 0, 0, 1, 1, X.numberOfImages)];\n    MPSOffset off;\n    off.x = X.width / 2;\n    off.y = X.height / 2;\n    off.z = 0;\n    [fc_ setOffset:off];\n    auto outputWrapper = MPSImageWrapper(\n        this, &inputWrapper, X.numberOfImages, 1, 1, output_channels);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n    [fc_ encodeToCommandBuffer:commandBuffer\n                   sourceImage:X\n              destinationImage:output];\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n    VLOG(2) << \"MPSCNNFC took: \" << t.MilliSeconds();\n    return true;\n  }\n\n  MPSCNNConvolution* fc_{nullptr};\n};\n\n#define INIT_FC_NEURON_OP(name, neuron)                        \\\n  REGISTER_CPU_OPERATOR(name, MPSCNNFullyConnectedOp<neuron>); \\\n  OPERATOR_SCHEMA(name).NumInputs(3).NumOutputs(1);\n\nINIT_FC_NEURON_OP(MPSCNNFC, EmptyNeuronInit);\nINIT_FC_NEURON_OP(MPSCNNFCRelu, ReluNeuronInit);\n#undef INIT_FC_NEURON_OP\n\nclass MPSCNNDropoutOp final : public Operator<CPUContext> {\n public:\n  MPSCNNDropoutOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  // Just pass inputs through, since we assume inference-time only.\n  bool RunOnDevice() override {\n    auto inputWrapper = Inputs()[0]->Get<MPSImageWrapper>();\n    inputWrapper.copyToOutputBlob(Outputs()[0]);\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNDropout, MPSCNNDropoutOp);\n// Never use the second output (the mask).\nOPERATOR_SCHEMA(MPSCNNDropout)\n    .NumInputs(1)\n    .NumOutputs(1, 2)\n    .AllowInplace({{0, 0}});\n\nclass MPSCNNConvTransposeOp final : public ConvTransposeUnpoolBase<CPUContext> {\n public:\n  MPSCNNConvTransposeOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvTransposeUnpoolBase<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(\n        this->order_ == StorageOrder::NCHW, \"Metal only supports NCHW order.\");\n    CAFFE_ENFORCE_EQ(\n        kernel_w(), kernel_h(), \"Metal only supports equal kernel dimensions\");\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    caffe2::Timer t;\n    auto inputWrapper = Inputs()[0]->Get<MPSImageWrapper>();\n\n    MPSImage* X = inputWrapper.getImage();\n\n    auto& filter = Input(FILTER);\n    auto& bias = Input(BIAS);\n    CAFFE_ENFORCE(filter.ndim(), 4);\n    const int output_channels = filter.dim32(1);\n    const int input_channels = filter.dim32(0);\n\n    CAFFE_ENFORCE(X.featureChannels == input_channels, \"\");\n    CAFFE_ENFORCE(filter.dim32(2) == kernel_h(), \"\");\n    CAFFE_ENFORCE(filter.dim32(3) == kernel_w(), \"\");\n    CAFFE_ENFORCE(bias.ndim() == 1, \"\");\n    CAFFE_ENFORCE(bias.dim32(0) == output_channels, \"\");\n\n    const auto kH = kernel_h();\n    const auto kW = kernel_w();\n\n    int output_height =\n        (X.height - 1) * stride_h() + kH - pad_b() - pad_t() + adj_h();\n    int output_width =\n        (X.width - 1) * stride_w() + kW - pad_l() - pad_r() + adj_w();\n\n    VLOG(2) << \"Output height: \" << output_height;\n    VLOG(2) << \"Output width:\" << output_width;\n    VLOG(2) << \"Output channels:\" << output_channels;\n\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &inputWrapper,\n        X.numberOfImages,\n        output_height,\n        output_width,\n        output_channels);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n\n    bool runtimeAtLeastIOS11 = SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@\"11.0\");\n    // initialization\n    if (!conv_trans_ && !conv_) {\n      caffe2::Timer consT;\n      std::vector<float> refilter(kH * kW * output_channels * input_channels);\n      refilter.assign(kH * kW * output_channels * input_channels, 0.0f);\n      DCHECK_EQ(refilter.size(), filter.size());\n      auto* filter_ = filter.template data<float>();\n      // For iOS11+ Reformat weights from WT[IC][OC][kH][kW] to\n      // W[OC][kH][kW][IC]; For previous versions, reformat weights\n      // to W[kH][kW][OC][IC]\n      // Also rotate the weight matrix spatially by 180 degrees\n      for (auto oc = 0; oc < output_channels; ++oc) {\n        for (auto ic = 0; ic < input_channels; ++ic) {\n          for (auto kh = 0; kh < kH; ++kh) {\n            for (auto kw = 0; kw < kW; ++kw) {\n              const auto inputIdx =\n                  ic * output_channels * kH * kW + oc * kH * kW + kh * kW + kw;\n              int outputIdx;\n              if (runtimeAtLeastIOS11) {\n                outputIdx = oc * kH * kW * input_channels +\n                    (kH - 1 - kh) * kW * input_channels +\n                    (kW - 1 - kw) * input_channels + ic;\n              } else {\n                outputIdx = kh * kW * output_channels * input_channels +\n                    kw * output_channels * input_channels +\n                    oc * input_channels + ic;\n              }\n              DCHECK_LT(inputIdx, filter.size());\n              DCHECK_LT(outputIdx, filter.size());\n              refilter[outputIdx] = filter_[inputIdx];\n            }\n          }\n        }\n      }\n      DCHECK_EQ(filter.size(), input_channels * output_channels * kH * kW);\n      // initialize data structures\n      if (runtimeAtLeastIOS11) {\n        MPSCNNConvolutionDescriptor* desc = [MPSCNNConvolutionDescriptor\n            cnnConvolutionDescriptorWithKernelWidth:kW\n                                       kernelHeight:kH\n                               inputFeatureChannels:input_channels\n                              outputFeatureChannels:output_channels];\n        desc.strideInPixelsX = this->stride_w();\n        desc.strideInPixelsY = this->stride_h();\n        desc.groups = 1;\n        auto data_source = [[ConvDataSource alloc]\n            initWithWeight:refilter.data()\n                      bias:const_cast<float*>(bias.data<float>())\n                      desc:desc];\n\n        conv_trans_ = [[MPSCNNConvolutionTranspose alloc]\n            initWithDevice:getMPSCNNContext().device\n                   weights:data_source];\n        MPSOffset offset;\n        offset.x = 0;\n        offset.y = 0;\n        offset.z = 0;\n        [conv_trans_ setOffset:offset];\n        // kernel offset + padding offset\n        conv_trans_.kernelOffsetX = kW / 2 - kW + 1 + this->pad_l();\n        conv_trans_.kernelOffsetY = kH / 2 - kH + 1 + this->pad_t();\n        VLOG(2) << \"MPSCNNConvTranspose ConvDesc took: \"\n                << consT.MilliSeconds();\n      } else {\n        MPSCNNConvolutionDescriptor* desc = [MPSCNNConvolutionDescriptor\n            cnnConvolutionDescriptorWithKernelWidth:1\n                                       kernelHeight:1\n                               inputFeatureChannels:input_channels\n                              outputFeatureChannels:output_channels * kH * kW\n                                       neuronFilter:nil];\n        // We need to zero-fill the bias here.\n        std::vector<float> fakeBias;\n        fakeBias.assign(output_channels * kH * kW, 0);\n\n        desc.strideInPixelsX = 1;\n        desc.strideInPixelsY = 1;\n        auto data_source =\n            [[ConvDataSource alloc] initWithWeight:refilter.data()\n                                              bias:fakeBias.data()\n                                              desc:desc];\n        conv_ =\n            [[MPSCNNConvolution alloc] initWithDevice:getMPSCNNContext().device\n                                              weights:data_source];\n        [conv_ setEdgeMode:MPSImageEdgeModeZero];\n        MPSOffset offset;\n        offset.x = 0;\n        offset.y = 0;\n        offset.z = 0;\n        [conv_ setOffset:offset];\n\n        const auto biasBytes = divRoundUp(bias.size(), 4) * 4 * 2;\n        biasBuffer_ = [getMPSCNNContext().device\n            newBufferWithLength:biasBytes\n                        options:MTLResourceOptionCPUCacheModeDefault];\n        for (auto i = 0; i < bias.size(); ++i) {\n          ((float16_t*)[biasBuffer_ contents])[i] = bias.data<float>()[i];\n        }\n\n        VLOG(2) << \"MPSCNNConvTranspose ConvDesc took: \"\n                << consT.MilliSeconds();\n      } // data structure initialization\n    } // initialization\n    CAFFE_ENFORCE((conv_trans_ && !conv_) || (!conv_trans_ && conv_));\n\n    // run the computation\n    if (conv_trans_) {\n      MPSImage* output = outputWrapper.getImage();\n      X = inputWrapper.getImage();\n      CAFFE_ENFORCE_EQ(conv_trans_.groups, 1);\n      [conv_trans_ encodeToCommandBuffer:commandBuffer\n                             sourceImage:X\n                        destinationImage:output];\n    } else {\n      CAFFE_ENFORCE_EQ(conv_.strideInPixelsY, 1);\n      CAFFE_ENFORCE_EQ(conv_.strideInPixelsX, 1);\n      CAFFE_ENFORCE_EQ(conv_.groups, 1);\n      CAFFE_ENFORCE_EQ(conv_.inputFeatureChannels, input_channels);\n      CAFFE_ENFORCE_EQ(conv_.outputFeatureChannels, output_channels * kH * kW);\n      CAFFE_ENFORCE_EQ(conv_.kernelWidth, 1);\n      CAFFE_ENFORCE_EQ(conv_.kernelHeight, 1);\n      if (divRoundUp(X.numberOfImages * output_channels * kH * kW, 4) >\n          kMetalMaxTextureArrLength) {\n        LOG(INFO) << \"ConvTranspose \" << X.numberOfImages << \" \"\n                  << output_channels << \" \" << kH << \" \" << kW;\n        LOG(ERROR)\n            << \"arrayLength exceeds the maximum allowed length in texture\";\n        inputWrapper.cleanup();\n        outputWrapper.cleanup();\n        return false;\n      }\n      VLOG(2) << \"ConvTranspose:\" << output_channels << \" \" << kH << \" \" << kW\n              << \" \" << X.numberOfImages;\n\n      auto gemmed = createTemporaryImage(\n          this,\n          commandBuffer,\n          X.numberOfImages,\n          X.height,\n          X.width,\n          output_channels * kH * kW);\n      {\n        caffe2::Timer gt;\n        [conv_ encodeToCommandBuffer:commandBuffer\n                         sourceImage:X\n                    destinationImage:gemmed];\n        VLOG(2) << \"MPSCNNConvTranspose GEMM took: \" << gt.MilliSeconds();\n      }\n      MPSImage* output = outputWrapper.getImage();\n\n      {\n        caffe2::Timer cit;\n        id<MTLComputePipelineState> state =\n            getMPSCNNContext().getSpecializedPipelineState(\n                @\"col2im\",\n                {{ushort(kernel_h()),\n                  ushort(kernel_w()),\n                  ushort(stride_h()),\n                  ushort(stride_w()),\n                  ushort(pad_l()),\n                  ushort(pad_t()),\n                  ushort(output.featureChannels),\n                  ushort(output.numberOfImages),\n                  ushort(gemmed.height),\n                  ushort(gemmed.width)}});\n        id<MTLComputeCommandEncoder> encoder =\n            [commandBuffer computeCommandEncoder];\n        [encoder setComputePipelineState:state];\n        [encoder setTexture:[gemmed texture] atIndex:0];\n        [encoder setTexture:[output texture] atIndex:1];\n        [encoder setBuffer:biasBuffer_ offset:0 atIndex:0];\n        const auto& launchParams =\n            spatialPointwiseKernelLaunchParams(state, output);\n        [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n                threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n        [encoder endEncoding];\n        gemmed.readCount -= 1;\n        VLOG(2) << \"MPSCNNConvTranspose upscaling took: \" << cit.MilliSeconds();\n      }\n    }\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n    VLOG(2) << \"MPSCNNConvTranspose took: \" << t.MilliSeconds();\n    return true;\n  }\n\n  // Input: X, W, b\n  // Output: Y\n  INPUT_TAGS(INPUT, FILTER, BIAS);\n  MPSCNNConvolutionTranspose* conv_trans_{nullptr};\n  id<MTLBuffer> biasBuffer_;\n  MPSCNNConvolution* conv_{nullptr};\n};\n\n// No-op init\n#define INIT_CONV_TRANSPOSE_NEURON_OP(name)           \\\n  REGISTER_CPU_OPERATOR(name, MPSCNNConvTransposeOp); \\\n  OPERATOR_SCHEMA(name).NumInputs(3).NumOutputs(1);\n\nINIT_CONV_TRANSPOSE_NEURON_OP(MPSCNNConvTranspose);\n#undef INIT_CONV_TRANSPOSE_NEURON_OP\n\nenum class InstanceNormFusionTy {\n  NONE,\n  PRELU,\n};\n\ntemplate <InstanceNormFusionTy fusionTy>\nclass MPSCNNInstanceNormOp final : public Operator<CPUContext> {\n public:\n  MPSCNNInstanceNormOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto inputWrapper = Inputs()[0]->template Get<MPSImageWrapper>();\n    MPSImage* X = inputWrapper.getImage();\n\n    const auto& scale = Input(1);\n    const auto& bias = Input(2);\n    CAFFE_ENFORCE_EQ(scale.size(), X.featureChannels);\n    CAFFE_ENFORCE_EQ(bias.size(), X.featureChannels);\n    const auto scaleBytes = divRoundUp(scale.size(), 4) * 4 * 2;\n    if (!scaleBuffer_ || !biasBuffer_ || scaleBuffer_.length != scaleBytes ||\n        biasBuffer_.length != scaleBytes) {\n      caffe2::Timer cvt;\n      // Round-up to nearest multiple of 4,\n      // so accesses to X[i * 4 + 3]  in kernel is valid.\n      scaleBuffer_ = [getMPSCNNContext().device\n          newBufferWithLength:scaleBytes\n                      options:MTLResourceOptionCPUCacheModeDefault];\n      biasBuffer_ = [getMPSCNNContext().device\n          newBufferWithLength:scaleBytes\n                      options:MTLResourceOptionCPUCacheModeDefault];\n      for (auto i = 0; i < scale.size(); ++i) {\n        ((float16_t*)[scaleBuffer_ contents])[i] =\n            scale.template data<float>()[i];\n      }\n      for (auto i = 0; i < bias.size(); ++i) {\n        ((float16_t*)[biasBuffer_ contents])[i] =\n            bias.template data<float>()[i];\n      }\n      if (fusionTy == InstanceNormFusionTy::PRELU) {\n        const auto& preluWeight = Input(3);\n        preluWeightBuffer_ = [getMPSCNNContext().device\n            newBufferWithLength:divRoundUp(preluWeight.size(), 4) * 4 * 2\n                        options:MTLResourceOptionCPUCacheModeDefault];\n        for (auto i = 0; i < preluWeight.size(); ++i) {\n          ((float16_t*)[preluWeightBuffer_ contents])[i] =\n              preluWeight.template data<float>()[i];\n        }\n      }\n      VLOG(2) << \"Buffer setup took: \" << cvt.MilliSeconds();\n    }\n\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &inputWrapper,\n        X.numberOfImages,\n        X.height,\n        X.width,\n        X.featureChannels);\n    auto commandBuffer = inputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n\n    caffe2::Timer t;\n    id<MTLComputeCommandEncoder> encoder =\n        [commandBuffer computeCommandEncoder];\n    id<MTLComputePipelineState> state =\n        getMPSCNNContext().getSpecializedPipelineState(\n            kernelFor(X, @\"instance_norm\", @\"instance_norm_nonarray\"),\n            {{ushort(X.featureChannels),\n              fusionTy == InstanceNormFusionTy::PRELU ? ushort(Input(3).size())\n                                                      : ushort(0)}});\n\n    [encoder setComputePipelineState:state];\n    [encoder setBuffer:scaleBuffer_ offset:0 atIndex:0];\n    [encoder setBuffer:biasBuffer_ offset:0 atIndex:1];\n    [encoder setTexture:[X texture] atIndex:0];\n    [encoder setTexture:[output texture] atIndex:1];\n    if (fusionTy == InstanceNormFusionTy::PRELU) {\n      [encoder setBuffer:preluWeightBuffer_ offset:0 atIndex:2];\n    }\n    [encoder dispatchThreadgroups:MTLSizeMake(\n                                      1,\n                                      1,\n                                      X.numberOfImages *\n                                          divRoundUp(X.featureChannels, 4))\n            threadsPerThreadgroup:MTLSizeMake(16, 16, 1)];\n    [encoder endEncoding];\n    inputWrapper.markRead();\n    VLOG(2) << \"InstanceNorm took: \" << t.MilliSeconds();\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n\n    return true;\n  }\n\n private:\n  id<MTLBuffer> scaleBuffer_;\n  id<MTLBuffer> biasBuffer_;\n  id<MTLBuffer> preluWeightBuffer_;\n};\n\nREGISTER_CPU_OPERATOR(\n    MPSCNNInstanceNorm,\n    MPSCNNInstanceNormOp<InstanceNormFusionTy::NONE>);\nOPERATOR_SCHEMA(MPSCNNInstanceNorm).NumInputs(3).NumOutputs(1);\nREGISTER_CPU_OPERATOR(\n    MPSCNNInstanceNormPRelu,\n    MPSCNNInstanceNormOp<InstanceNormFusionTy::PRELU>);\nOPERATOR_SCHEMA(MPSCNNInstanceNormPRelu).NumInputs(4).NumOutputs(1);\n\nclass MPSCNNNormalizePlanarYUVOp final : public Operator<CPUContext> {\n public:\n  MPSCNNNormalizePlanarYUVOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto inputWrapper = Inputs()[0]->template Get<MPSImageWrapper>();\n    MPSImage* X = inputWrapper.getImage();\n\n    const auto& mean = Input(1);\n    const auto& std = Input(2);\n    CAFFE_ENFORCE_EQ(mean.size(), X.featureChannels);\n    CAFFE_ENFORCE_EQ(std.size(), X.featureChannels);\n    const auto scaleBytes = divRoundUp(mean.size(), 4) * 4 * 2;\n    if (!scaleBuffer_ || !shiftBuffer_ || scaleBuffer_.length != scaleBytes ||\n        shiftBuffer_.length != scaleBytes) {\n      caffe2::Timer cvt;\n      scaleBuffer_ = [getMPSCNNContext().device\n          newBufferWithLength:scaleBytes\n                      options:MTLResourceOptionCPUCacheModeDefault];\n      shiftBuffer_ = [getMPSCNNContext().device\n          newBufferWithLength:scaleBytes\n                      options:MTLResourceOptionCPUCacheModeDefault];\n      // op computes (X - mean) / std = X * 1/std + (-mean/std)\n      // Thus set scale = 1.0/std, shift = (-mean/std)\n      for (auto i = 0; i < mean.size(); ++i) {\n        ((float16_t*)[scaleBuffer_ contents])[i] =\n            1.0 / double(std.template data<float>()[i]);\n        ((float16_t*)[shiftBuffer_ contents])[i] =\n            double(-mean.template data<float>()[i]) /\n            double(std.template data<float>()[i]);\n      }\n      VLOG(2) << \"Buffer setup took: \" << cvt.MilliSeconds();\n    }\n\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &inputWrapper,\n        X.numberOfImages,\n        X.height,\n        X.width,\n        X.featureChannels);\n    auto commandBuffer = inputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n\n    caffe2::Timer t;\n    id<MTLComputeCommandEncoder> encoder =\n        [commandBuffer computeCommandEncoder];\n    id<MTLComputePipelineState> state =\n        getMPSCNNContext().getSpecializedPipelineState(\n            kernelFor(X, @\"affine\", @\"affine_nonarray\"),\n            {ushort(X.featureChannels)});\n\n    [encoder setComputePipelineState:state];\n    [encoder setBuffer:scaleBuffer_ offset:0 atIndex:0];\n    [encoder setBuffer:shiftBuffer_ offset:0 atIndex:1];\n    [encoder setTexture:[X texture] atIndex:0];\n    [encoder setTexture:[output texture] atIndex:1];\n    const auto& launchParams =\n        spatialPointwiseKernelLaunchParams(state, output);\n    [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n            threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n    [encoder endEncoding];\n    inputWrapper.markRead();\n    VLOG(2) << \"InstanceNorm took: \" << t.MilliSeconds();\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n\n    return true;\n  }\n\n private:\n  id<MTLBuffer> scaleBuffer_;\n  id<MTLBuffer> shiftBuffer_;\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNNormalizePlanarYUV, MPSCNNNormalizePlanarYUVOp);\nOPERATOR_SCHEMA(MPSCNNNormalizePlanarYUV).NumInputs(3).NumOutputs(1);\n\nclass MPSCNNPReluOp final : public Operator<CPUContext> {\n public:\n  MPSCNNPReluOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto inputWrapper = Inputs()[0]->Get<MPSImageWrapper>();\n    const MPSImage* X = inputWrapper.getImage();\n\n    const auto& scale = Input(1);\n    const auto scaleBytes = divRoundUp(scale.size(), 4) * 4 * 2;\n    if (!scaleBuffer_ || scaleBuffer_.length != scaleBytes) {\n      caffe2::Timer cvt;\n      scaleBuffer_ = [getMPSCNNContext().device\n          newBufferWithLength:scaleBytes\n                      options:MTLResourceOptionCPUCacheModeDefault];\n      for (auto i = 0; i < scale.size(); ++i) {\n        ((float16_t*)[scaleBuffer_ contents])[i] = scale.data<float>()[i];\n      }\n      VLOG(2) << \"Buffer setup took: \" << cvt.MilliSeconds();\n    }\n\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &inputWrapper,\n        X.numberOfImages,\n        X.height,\n        X.width,\n        X.featureChannels);\n    auto commandBuffer = inputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n    caffe2::Timer t;\n    id<MTLComputeCommandEncoder> encoder =\n        [commandBuffer computeCommandEncoder];\n    id<MTLComputePipelineState> state =\n        getMPSCNNContext().getSpecializedPipelineState(\n            kernelFor(X, @\"prelu_nonshared\", @\"prelu_nonshared_nonarray\"),\n            {{ushort(X.featureChannels), ushort(scale.size())}});\n\n    [encoder setComputePipelineState:state];\n    [encoder setBuffer:scaleBuffer_ offset:0 atIndex:0];\n    [encoder setTexture:[X texture] atIndex:0];\n    [encoder setTexture:[output texture] atIndex:1];\n\n    const auto& launchParams =\n        spatialPointwiseKernelLaunchParams(state, output);\n    [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n            threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n    [encoder endEncoding];\n    inputWrapper.markRead();\n    VLOG(2) << \"PRelu took: \" << t.MilliSeconds();\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n\n    return true;\n  }\n\n private:\n  id<MTLBuffer> scaleBuffer_;\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNPRelu, MPSCNNPReluOp);\n// Allow in-place isn't *really* valid here, since nothing is in-place for Metal\n// texture arrays, but requires re-export.\nOPERATOR_SCHEMA(MPSCNNPRelu).NumInputs(2).NumOutputs(1).AllowInplace({{0, 0}});\n\nclass MPSCNNRoIWarpOp final : public Operator<CPUContext> {\n public:\n  MPSCNNRoIWarpOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        spatial_scale_(\n            OperatorBase::GetSingleArgument<float>(\"spatial_scale\", 1.)),\n        pooled_height_(OperatorBase::GetSingleArgument<int>(\"pooled_h\", 1)),\n        pooled_width_(OperatorBase::GetSingleArgument<int>(\"pooled_w\", 1)),\n        sampling_ratio_(\n            OperatorBase::GetSingleArgument<int>(\"sampling_ratio\", -1)) {\n    CAFFE_ENFORCE_GT(spatial_scale_, 0);\n    CAFFE_ENFORCE_GT(pooled_height_, 0);\n    CAFFE_ENFORCE_GT(pooled_width_, 0);\n    CAFFE_ENFORCE_GE(sampling_ratio_, 0);\n    VLOG(1) << \"spatial_scale: \" << spatial_scale_;\n    VLOG(1) << \"pooled_h: \" << pooled_height_;\n    VLOG(1) << \"pooled_w: \" << pooled_width_;\n    VLOG(1) << \"sampling_ratio: \" << sampling_ratio_;\n  }\n\n  bool RunOnDevice() override {\n    caffe2::Timer t;\n    auto inputWrapper = Inputs()[0]->Get<MPSImageWrapper>();\n    auto X = inputWrapper.getImage();\n    CAFFE_ENFORCE_EQ(X.numberOfImages, 1);\n    const auto& R = Input(1);\n    CAFFE_ENFORCE_EQ(R.ndim(), 2);\n    CAFFE_ENFORCE(R.dim32(1) == 4 || R.dim32(1) == 5);\n    const auto roiBytes = R.dim32(0) * 4 * sizeof(float16_t);\n    if (!roiBuffer_ || roiBuffer_.length != roiBytes) {\n      caffe2::Timer cvt;\n      roiBuffer_ = [getMPSCNNContext().device\n          newBufferWithLength:roiBytes\n                      options:MTLResourceOptionCPUCacheModeDefault];\n    }\n    float16_t* roiBuffer = (float16_t*)[roiBuffer_ contents];\n    // Help compiler generate vcvt?\n    const auto Rdim = R.dim32(1);\n    CAFFE_ENFORCE(Rdim == 4 || Rdim == 5);\n    auto off = Rdim == 5 ? 1 : 0;\n    for (auto i = 0; i < R.dim32(0); ++i) {\n      if (Rdim == 5) {\n        // only handle batch-size of one, so the batch index must be one.\n        CAFFE_ENFORCE_EQ(R.data<float>()[i * Rdim], 0.0);\n      }\n      roiBuffer[i * 4 + 0] = R.data<float>()[i * Rdim + off + 0];\n      roiBuffer[i * 4 + 1] = R.data<float>()[i * Rdim + off + 1];\n      roiBuffer[i * 4 + 2] = R.data<float>()[i * Rdim + off + 2];\n      roiBuffer[i * 4 + 3] = R.data<float>()[i * Rdim + off + 3];\n    }\n    auto featureChannels = X.featureChannels;\n    VLOG(1) << \"RoIWarp input size:\" << X.numberOfImages << \" \"\n            << featureChannels << \" \" << X.height << \" \" << X.width;\n    VLOG(1) << \"RoIWarp output size:\" << R.dim32(0) << \" \" << featureChannels\n            << \" \" << pooled_width_ << \" \" << pooled_height_;\n    if (R.dim32(0) <= 0) {\n      LOG(ERROR) << \"number of RoIs <= 0 in RoIWarp \" << R.dim32(0);\n      inputWrapper.cleanup();\n      return false;\n    }\n    if (divRoundUp(R.dim32(0) * featureChannels, 4) >\n        kMetalMaxTextureArrLength) {\n      LOG(INFO) << \"MPSCNNRoIWarp \" << R.dim32(0) << \" \" << featureChannels;\n      LOG(ERROR) << \"arrayLength exceeds the maximum allowed length in texture\";\n      inputWrapper.cleanup();\n      return false;\n    }\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &inputWrapper,\n        R.dim32(0),\n        pooled_height_,\n        pooled_width_,\n        featureChannels);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n    VLOG(1) << \"output: \" << output.numberOfImages << \", \"\n            << output.featureChannels << \", \" << output.height << \", \"\n            << output.width;\n    id<MTLComputeCommandEncoder> encoder =\n        [commandBuffer computeCommandEncoder];\n    id<MTLComputePipelineState> state =\n        getMPSCNNContext().getSpecializedPipelineState(\n            @\"roi_warp\",\n            {{ushort(spatial_scale_ * 10000),\n              ushort(sampling_ratio_),\n              ushort(featureChannels),\n              ushort(X.numberOfImages),\n              ushort(output.numberOfImages)}});\n\n    [encoder setComputePipelineState:state];\n    [encoder setBuffer:roiBuffer_ offset:0 atIndex:0];\n    [encoder setTexture:[X texture] atIndex:0];\n    [encoder setTexture:[output texture] atIndex:1];\n\n    const auto& launchParams =\n        spatialPointwiseKernelLaunchParams(state, output);\n    [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n            threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n    [encoder endEncoding];\n    inputWrapper.markRead();\n    VLOG(2) << \"RoIWarp took: \" << t.MilliSeconds();\n    VLOG(1) << \"ROIWarp size: \" << output.numberOfImages << \", \"\n            << output.featureChannels << \", \" << output.height << \", \"\n            << output.width;\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n\n    return true;\n  }\n\n private:\n  float spatial_scale_;\n  int pooled_height_;\n  int pooled_width_;\n  int sampling_ratio_;\n\n  id<MTLBuffer> roiBuffer_;\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNRoIWarp, MPSCNNRoIWarpOp);\nOPERATOR_SCHEMA(MPSCNNRoIWarp).NumInputs(2).NumOutputs(1);\n\nclass MPSCNNGenerateProposalsCPPOp final : public Operator<CPUContext> {\n public:\n  MPSCNNGenerateProposalsCPPOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        spatial_scale_(\n            OperatorBase::GetSingleArgument<float>(\"spatial_scale\", 1.0 / 16)),\n        feat_stride_(1.0 / spatial_scale_),\n        rpn_pre_nms_topN_(\n            OperatorBase::GetSingleArgument<int>(\"pre_nms_topN\", 6000)),\n        rpn_post_nms_topN_(\n            OperatorBase::GetSingleArgument<int>(\"post_nms_topN\", 300)),\n        rpn_nms_thresh_(\n            OperatorBase::GetSingleArgument<float>(\"nms_thresh\", 0.7f)),\n        rpn_min_size_(OperatorBase::GetSingleArgument<float>(\"min_size\", 16)) {}\n\n  template <class Derived1, class Derived2>\n  std::vector<int> nms_metal(\n      const Eigen::ArrayBase<Derived1>& proposals, // EArrXXf\n      const Eigen::ArrayBase<Derived2>& scores, // EArrXf\n      const std::vector<int>& sorted_indices,\n      float thresh) const {\n    CAFFE_ENFORCE_EQ(proposals.rows(), scores.rows());\n    CAFFE_ENFORCE_EQ(proposals.cols(), 4);\n    CAFFE_ENFORCE_EQ(scores.cols(), 1);\n    CAFFE_ENFORCE_LE(sorted_indices.size(), proposals.rows());\n\n    std::vector<float> proposals_cpu(proposals.size());\n    Eigen::Map<ERArrXXf>(\n        &proposals_cpu[0], proposals.rows(), proposals.cols()) = proposals;\n\n    int box_num = sorted_indices.size();\n    int col_blocks = divRoundUp(box_num, maxThreadsPerThreadgroup);\n    auto pre_nms_size = box_num;\n    auto preNmsProposalsBuffer_ = [getMPSCNNContext().device\n        newBufferWithBytes:proposals_cpu.data()\n                    length:proposals.size() * sizeof(float)\n                   options:MTLResourceOptionCPUCacheModeDefault];\n    auto sortedIndicesBuffer_ = [getMPSCNNContext().device\n        newBufferWithBytes:sorted_indices.data()\n                    length:pre_nms_size * sizeof(int)\n                   options:MTLResourceOptionCPUCacheModeDefault];\n\n    int pose_nms_size = fmin(rpn_post_nms_topN_, pre_nms_size);\n    // round pose_nms_size up to the next power of 2\n    int batch_size = pow(2, ceil(log(pose_nms_size) / log(2)));\n\n    auto maskBuffer_ = [getMPSCNNContext().device\n        newBufferWithLength:batch_size * col_blocks * sizeof(uint32_t)\n                    options:MTLResourceOptionCPUCacheModeDefault];\n    std::vector<uint32_t> masks(batch_size * col_blocks);\n\n    std::vector<int> keep(pose_nms_size);\n    int num_to_keep = 0;\n    bool terminate = false;\n    std::vector<uint32_t> remv(col_blocks);\n\n    for (int offset = 0; !terminate && offset < box_num; offset += batch_size) {\n      auto commandBuffer = [getMPSCNNContext().commandQueue commandBuffer];\n      auto encoder = [commandBuffer computeCommandEncoder];\n      auto state = getMPSCNNContext().getSpecializedPipelineState(\n          @\"nms\",\n          {{ushort(batch_size),\n            maxThreadsPerThreadgroup,\n            ushort(rpn_nms_thresh_ * 10000),\n            ushort(offset)}});\n      [encoder setComputePipelineState:state];\n      [encoder setBuffer:maskBuffer_ offset:0 atIndex:0];\n      [encoder setBuffer:preNmsProposalsBuffer_ offset:0 atIndex:1];\n      [encoder setBuffer:sortedIndicesBuffer_ offset:0 atIndex:2];\n      const auto threadsPerThreadgroup =\n          MTLSizeMake(maxThreadsPerThreadgroup, 1, 1);\n      const auto threadgroupsPerGrid = MTLSizeMake(\n          divRoundUp(batch_size, maxThreadsPerThreadgroup),\n          divRoundUp(box_num, maxThreadsPerThreadgroup),\n          1);\n      [encoder dispatchThreadgroups:threadgroupsPerGrid\n              threadsPerThreadgroup:threadsPerThreadgroup];\n      [encoder endEncoding];\n      [commandBuffer commit];\n      [commandBuffer waitUntilCompleted];\n      uint32_t* maskBufferPointer = (uint32_t*)[maskBuffer_ contents];\n      std::copy(\n          maskBufferPointer,\n          maskBufferPointer + (maskBuffer_.length / sizeof(uint32_t)),\n          masks.begin());\n\n      for (int i = offset; i < fmin(offset + batch_size, box_num); ++i) {\n        int nblock = i / maxThreadsPerThreadgroup;\n        int inblock = i % maxThreadsPerThreadgroup;\n        if (!(remv[nblock] & (1U << inblock))) {\n          keep[num_to_keep++] = sorted_indices[i];\n          if (num_to_keep >= pose_nms_size) {\n            terminate = true;\n            break;\n          }\n          uint* p = &masks[0] + (i - offset) * col_blocks;\n          for (int j = nblock; j < col_blocks; j++) {\n            remv[j] |= p[j];\n          }\n        }\n      }\n    }\n    keep.resize(num_to_keep);\n    return keep;\n  }\n  void ProposalsForOneImage(\n      const Eigen::Array3f& im_info,\n      const Eigen::Map<const ERMatXf>& all_anchors,\n      const utils::ConstTensorView<float>& bbox_deltas_tensor,\n      const utils::ConstTensorView<float>& scores_tensor,\n      ERArrXXf* out_boxes,\n      EArrXf* out_probs) const {\n    const auto& pre_nms_topN = rpn_pre_nms_topN_;\n    const auto& post_nms_topN = rpn_post_nms_topN_;\n    const auto& nms_thresh = rpn_nms_thresh_;\n    const auto& min_size = rpn_min_size_;\n\n    // Transpose and reshape predicted bbox transformations to get them\n    // into the same order as the anchors:\n    //   - bbox deltas will be (4 * A, H, W) format from conv output\n    //   - transpose to (H, W, 4 * A)\n    //   - reshape to (H * W * A, 4) where rows are ordered by (H, W, A)\n    //     in slowest to fastest order to match the enumerated anchors\n    CAFFE_ENFORCE_EQ(bbox_deltas_tensor.ndim(), 3);\n    CAFFE_ENFORCE_EQ(bbox_deltas_tensor.dim(0) % 4, 0);\n    auto A = bbox_deltas_tensor.dim(0) / 4;\n    auto H = bbox_deltas_tensor.dim(1);\n    auto W = bbox_deltas_tensor.dim(2);\n    // equivalent to python code\n    //  bbox_deltas = bbox_deltas.transpose((1, 2, 0)).reshape((-1, 4))\n    ERArrXXf bbox_deltas(H * W * A, 4);\n    Eigen::Map<ERMatXf>(bbox_deltas.data(), H * W, 4 * A) =\n        Eigen::Map<const ERMatXf>(bbox_deltas_tensor.data(), A * 4, H * W)\n            .transpose();\n    CAFFE_ENFORCE_EQ(bbox_deltas.rows(), all_anchors.rows());\n\n    // - scores are (A, H, W) format from conv output\n    // - transpose to (H, W, A)\n    // - reshape to (H * W * A, 1) where rows are ordered by (H, W, A)\n    //   to match the order of anchors and bbox_deltas\n    CAFFE_ENFORCE_EQ(scores_tensor.ndim(), 3);\n    CAFFE_ENFORCE_EQ(scores_tensor.dims(), (vector<int>{A, H, W}));\n    // equivalent to python code\n    // scores = scores.transpose((1, 2, 0)).reshape((-1, 1))\n    EArrXf scores(scores_tensor.size());\n    Eigen::Map<ERMatXf>(scores.data(), H * W, A) =\n        Eigen::Map<const ERMatXf>(scores_tensor.data(), A, H * W).transpose();\n    // Transform anchors into proposals via bbox transformations\n    auto proposals = utils::bbox_transform(all_anchors.array(), bbox_deltas);\n\n    // 2. clip proposals to image (may result in proposals with zero area\n    // that will be removed in the next step)\n    proposals = utils::clip_boxes(proposals, im_info[0], im_info[1]);\n\n    // 3. remove predicted boxes with either height or width < min_size\n    auto keep = utils::filter_boxes(proposals, min_size, im_info);\n\n    DCHECK_LE(keep.size(), scores.size());\n\n    // 4. sort all (proposal, score) pairs by score from highest to lowest\n    // 5. take top pre_nms_topN (e.g. 6000)\n    std::sort(keep.begin(), keep.end(), [&scores](int lhs, int rhs) {\n      return scores[lhs] > scores[rhs];\n    });\n\n    if (pre_nms_topN > 0 && pre_nms_topN < keep.size()) {\n      keep.resize(pre_nms_topN);\n    }\n\n    // 6. apply loose nms (e.g. threshold = 0.7)\n    // 7. take after_nms_topN (e.g. 300)\n    // 8. return the top proposals (-> RoIs top)\n    keep = nms_metal(proposals, scores, keep, nms_thresh);\n    if (post_nms_topN > 0 && post_nms_topN < keep.size()) {\n      keep.resize(post_nms_topN);\n    }\n    // Generate outputs\n    utils::GetSubArrayRows(proposals, utils::AsEArrXt(keep), out_boxes);\n    utils::GetSubArray(scores, utils::AsEArrXt(keep), out_probs);\n  }\n\n  bool RunOnDevice() override {\n    const auto& scores = Input(0);\n    const auto& bbox_deltas = Input(1);\n    const auto& im_info_tensor = Input(2);\n    const auto& anchors = Input(3);\n    auto* out_rois = Output(0);\n    auto* out_rois_probs = Output(1);\n\n    CAFFE_ENFORCE_EQ(scores.ndim(), 4, scores.ndim());\n    CAFFE_ENFORCE(scores.template IsType<float>(), scores.meta().name());\n    const auto num_images = scores.dim(0);\n    const auto A = scores.dim(1);\n    const auto height = scores.dim(2);\n    const auto width = scores.dim(3);\n    const auto K = height * width;\n\n    // bbox_deltas: (num_images, A * 4, H, W)\n    CAFFE_ENFORCE_EQ(\n        bbox_deltas.dims(), (vector<TIndex>{num_images, 4 * A, height, width}));\n\n    // im_info_tensor: (num_images, 3), format [height, width, scale; ...]\n    CAFFE_ENFORCE_EQ(im_info_tensor.dims(), (vector<TIndex>{num_images, 3}));\n    CAFFE_ENFORCE(\n        im_info_tensor.template IsType<float>(), im_info_tensor.meta().name());\n\n    // anchors: (A, 4)\n    CAFFE_ENFORCE_EQ(anchors.dims(), (vector<TIndex>{A, 4}));\n    CAFFE_ENFORCE(anchors.template IsType<float>(), anchors.meta().name());\n    // Broadcast the anchors to all pixels\n    auto all_anchors_vec =\n        utils::ComputeAllAnchors(anchors, height, width, feat_stride_);\n    Eigen::Map<const ERMatXf> all_anchors(all_anchors_vec.data(), K * A, 4);\n\n    Eigen::Map<const ERArrXXf> im_info(\n        im_info_tensor.data<float>(),\n        im_info_tensor.dim(0),\n        im_info_tensor.dim(1));\n\n    const int roi_col_count = 5;\n    out_rois->Resize(0, roi_col_count);\n    out_rois_probs->Resize(0);\n    Timer t1;\n    // Use openmp for acceleration?\n    for (int i = 0; i < num_images; i++) {\n      auto cur_im_info = im_info.row(i);\n      auto cur_bbox_deltas = GetSubTensorView<float>(bbox_deltas, i);\n      auto cur_scores = GetSubTensorView<float>(scores, i);\n\n      ERArrXXf im_i_boxes;\n      EArrXf im_i_probs;\n      ProposalsForOneImage(\n          cur_im_info,\n          all_anchors,\n          cur_bbox_deltas,\n          cur_scores,\n          &im_i_boxes,\n          &im_i_probs);\n\n      int csz = im_i_boxes.rows();\n      int cur_start_idx = out_rois->dim(0);\n\n      out_rois->Extend(csz, 50, &context_);\n      out_rois_probs->Extend(csz, 50, &context_);\n\n      // write rois\n      Eigen::Map<ERArrXXf> cur_rois(\n          out_rois->mutable_data<float>() + cur_start_idx * roi_col_count,\n          csz,\n          5);\n      cur_rois.col(0).setConstant(i);\n      cur_rois.block(0, 1, csz, 4) = im_i_boxes;\n\n      // write rois_probs\n      Eigen::Map<EArrXf>(\n          out_rois_probs->mutable_data<float>() + cur_start_idx, csz) =\n          im_i_probs;\n    }\n\n    return true;\n  }\n\n protected:\n  // spatial_scale_ must be declared before feat_stride_\n  float spatial_scale_{1.0};\n  float feat_stride_{1.0};\n\n  // RPN_PRE_NMS_TOP_N\n  ushort rpn_pre_nms_topN_{6000};\n  // RPN_POST_NMS_TOP_N\n  ushort rpn_post_nms_topN_{300};\n  // RPN_NMS_THRESH\n  float rpn_nms_thresh_{0.7};\n  // RPN_MIN_SIZE\n  float rpn_min_size_{16};\n  // threads per thread group, used in nms\n  ushort maxThreadsPerThreadgroup{32};\n\n private:\n  id<MTLBuffer> out_rois_{nullptr};\n  id<MTLBuffer> out_rois_probs_{nullptr};\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNGenerateProposalsCPP, MPSCNNGenerateProposalsCPPOp);\nOPERATOR_SCHEMA(MPSCNNGenerateProposalsCPP).NumInputs(4).NumOutputs(2);\n\nclass MPSCNNSpatialBNOp final : public SpatialBNOp<CPUContext> {\n public:\n  MPSCNNSpatialBNOp(const OperatorDef& operator_def, Workspace* ws)\n      : SpatialBNOp<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto inputWrapper = Inputs()[0]->Get<MPSImageWrapper>();\n    const MPSImage* X = inputWrapper.getImage();\n    const auto& scale = Input(SCALE);\n    const auto& bias = Input(BIAS);\n    const auto& var = Input(EST_VAR);\n    const auto& mean = Input(EST_MEAN);\n    CAFFE_ENFORCE_EQ(scale.size(), X.featureChannels);\n    CAFFE_ENFORCE_EQ(bias.size(), X.featureChannels);\n    CAFFE_ENFORCE_EQ(var.size(), X.featureChannels);\n    CAFFE_ENFORCE_EQ(mean.size(), X.featureChannels);\n\n    const auto scaleBytes = divRoundUp(scale.size(), 4) * 4 * 2;\n    if (!scaleBuffer_ || scaleBuffer_.length != scaleBytes) {\n      caffe2::Timer cvt;\n      scaleBuffer_ = [getMPSCNNContext().device\n          newBufferWithLength:scaleBytes\n                      options:MTLResourceOptionCPUCacheModeDefault];\n      shiftBuffer_ = [getMPSCNNContext().device\n          newBufferWithLength:scaleBytes\n                      options:MTLResourceOptionCPUCacheModeDefault];\n      for (auto i = 0; i < scale.size(); ++i) {\n        // We can fuse the output computation as follows:\n        //   ((x - est_mean) * (inv_var) * scale + bias\n        // to\n        //   (x * inv_var * scale) + (bias - est_mean * inv_var * scale)\n\n        const auto inv_std = 1.0 / std::sqrt(var.data<float>()[i] + epsilon_);\n        ((float16_t*)[scaleBuffer_ contents])[i] =\n            scale.data<float>()[i] * inv_std;\n        ((float16_t*)[shiftBuffer_ contents])[i] = bias.data<float>()[i] -\n            mean.data<float>()[i] * inv_std * scale.data<float>()[i];\n      }\n      VLOG(2) << \"Buffer setup took: \" << cvt.MilliSeconds();\n    }\n\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &inputWrapper,\n        X.numberOfImages,\n        X.height,\n        X.width,\n        X.featureChannels);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n    caffe2::Timer t;\n    id<MTLComputeCommandEncoder> encoder =\n        [commandBuffer computeCommandEncoder];\n    id<MTLComputePipelineState> state =\n        getMPSCNNContext().getSpecializedPipelineState(\n            kernelFor(output, @\"affine\", @\"affine_nonarray\"),\n            {ushort(X.featureChannels)});\n\n    [encoder setComputePipelineState:state];\n    [encoder setBuffer:scaleBuffer_ offset:0 atIndex:0];\n    [encoder setBuffer:shiftBuffer_ offset:0 atIndex:1];\n    [encoder setTexture:[X texture] atIndex:0];\n    [encoder setTexture:[output texture] atIndex:1];\n\n    const auto& launchParams =\n        spatialPointwiseKernelLaunchParams(state, output);\n    [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n            threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n    [encoder endEncoding];\n    inputWrapper.markRead();\n    VLOG(2) << \"SpatialBN took: \" << t.MilliSeconds();\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n\n    return true;\n  }\n\n private:\n  id<MTLBuffer> scaleBuffer_;\n  id<MTLBuffer> shiftBuffer_;\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNSpatialBN, MPSCNNSpatialBNOp);\nOPERATOR_SCHEMA(MPSCNNSpatialBN).NumInputs(5).NumOutputs(1);\n\nclass MPSCNNConcatOp final : public Operator<CPUContext> {\n public:\n  MPSCNNConcatOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {\n    // Only handle three inputs for now.\n    OPERATOR_NEEDS_FEATURE(\n        Inputs().size() <= 4, \"MPSCNNConcat only handles up to four inputs\");\n  }\n\n  bool RunOnDevice() override {\n    auto Wrapper = [&](size_t i) {\n      return Inputs()[i]->template Get<MPSImageWrapper>();\n    };\n    auto cb = [&](size_t i) { return Wrapper(i).getCommandBuffer(); };\n    auto X = [&](size_t i) { return Wrapper(i).getImage(); };\n\n    // C0, C1, C2, C3, C, N\n    std::vector<ushort> channels = {\n        {0, 0, 0, 0, 0, ushort(X(0).numberOfImages)}};\n    size_t channelCount = 0;\n    for (auto i = 0; i < Inputs().size(); ++i) {\n      // this does not hold for non-temp images inputs\n      CAFFE_ENFORCE_EQ(cb(0), cb(i));\n      CAFFE_ENFORCE_EQ(X(0).height, X(i).height);\n      CAFFE_ENFORCE_EQ(X(0).width, X(i).width);\n      channels[i] = X(i).featureChannels;\n      channelCount += X(i).featureChannels;\n    }\n    channels[4] = channelCount;\n\n    auto wrapper0 = Inputs()[0]->template Get<MPSImageWrapper>();\n    auto outputWrapper = MPSImageWrapper(\n        this,\n        &wrapper0,\n        X(0).numberOfImages,\n        X(0).height,\n        X(0).width,\n        channelCount);\n    auto commandBuffer = outputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n    caffe2::Timer t;\n    id<MTLComputeCommandEncoder> encoder =\n        [commandBuffer computeCommandEncoder];\n    id<MTLComputePipelineState> state =\n        getMPSCNNContext().getSpecializedPipelineState(@\"concat\", channels);\n\n    [encoder setComputePipelineState:state];\n    for (auto i = 0; i < Inputs().size(); ++i) {\n      [encoder setTexture:[X(i) texture] atIndex:i];\n    }\n    [encoder setTexture:[output texture] atIndex:5];\n    const auto& launchParams =\n        spatialPointwiseKernelLaunchParams(state, output);\n    [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n            threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n    [encoder endEncoding];\n    for (auto i = 0; i < Inputs().size(); ++i) {\n      Wrapper(i).markRead();\n    }\n\n    VLOG(2) << \"Concat took: \" << t.MilliSeconds();\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNConcat, MPSCNNConcatOp);\n// Only store one output in practice (ignore the shape argument).\nOPERATOR_SCHEMA(MPSCNNConcat).NumInputs(2, 4).NumOutputs(1, 2);\n\nclass MPSCNNResizeNearestOp final : public Operator<CPUContext> {\n public:\n  MPSCNNResizeNearestOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {\n    width_scale_ = OperatorBase::GetSingleArgument<float>(\"width_scale\", 1);\n    height_scale_ = OperatorBase::GetSingleArgument<float>(\"height_scale\", 1);\n    CAFFE_ENFORCE_GT(width_scale_, 0);\n    CAFFE_ENFORCE_GT(height_scale_, 0);\n\n    // due to the way we pass these parameters, we don't support the scale to be\n    // larger than 6.5\n    CAFFE_ENFORCE_LE(width_scale_, 6.5);\n    CAFFE_ENFORCE_LE(height_scale_, 6.5);\n  }\n\n  bool RunOnDevice() override {\n    auto inputWrapper = Inputs()[0]->Get<MPSImageWrapper>();\n    const MPSImage* X = inputWrapper.getImage();\n\n    const int N = X.numberOfImages, C = X.featureChannels, H = X.height,\n              W = X.width;\n    int output_width = W * width_scale_;\n    int output_height = H * height_scale_;\n    auto outputWrapper =\n        MPSImageWrapper(this, &inputWrapper, N, output_height, output_width, C);\n    auto commandBuffer = inputWrapper.getCommandBuffer();\n    MPSImage* output = outputWrapper.getImage();\n\n    auto encoder = [commandBuffer computeCommandEncoder];\n    auto state = getMPSCNNContext().getSpecializedPipelineState(\n        kernelFor(output, @\"resize_nearest\", @\"resize_nearest_nonarray\"),\n        {{ushort(output_height),\n          ushort(output_width),\n          ushort(height_scale_ * 10000),\n          ushort(width_scale_ * 10000)}});\n    [encoder setComputePipelineState:state];\n    [encoder setTexture:[X texture] atIndex:0];\n    [encoder setTexture:[output texture] atIndex:1];\n    auto launchParams = spatialPointwiseKernelLaunchParams(state, output);\n    [encoder dispatchThreadgroups:launchParams.threadgroupsPerGrid\n            threadsPerThreadgroup:launchParams.threadsPerThreadgroup];\n    [encoder endEncoding];\n    inputWrapper.markRead();\n    outputWrapper.copyToOutputBlob(Outputs()[0]);\n\n    return true;\n  }\n\n protected:\n  float width_scale_;\n  float height_scale_;\n};\n\nREGISTER_CPU_OPERATOR(MPSCNNResizeNearest, MPSCNNResizeNearestOp);\nOPERATOR_SCHEMA(MPSCNNResizeNearest).NumInputs(1).NumOutputs(1);\n}\n\nCAFFE_KNOWN_TYPE(MPSImageWrapper);\n} // namespace caffe2\n\n#endif\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/mpscnn/mpscnn_context.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#import <Metal/MTLBuffer.h>\n#import <Metal/MTLDevice.h>\n#import <Metal/MTLLibrary.h>\n\n#include <array>\n#include <mutex>\n#include <string>\n#include <thread>\n#include <unordered_map>\n\nnamespace caffe2 {\n\nstruct MPSCNNContext {\n public:\n  id<MTLDevice> device;\n  id<MTLCommandQueue> commandQueue;\n  id<MTLLibrary> library;\n\n  id<MTLComputePipelineState> getPipelineState(NSString* kernel);\n  id<MTLComputePipelineState> getSpecializedPipelineState(NSString* kernel,\n                                                          const std::vector<ushort>& constants);\n\n private:\n  std::mutex pipelineCacheMutex_;\n  std::unordered_map<std::string, id<MTLComputePipelineState>> pipelineCache_;\n};\n\n// get the singleton instance.\nMPSCNNContext& getMPSCNNContext();\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/mpscnn/mpscnn_context.mm",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"caffe2/core/common.h\"\n\n#if CAFFE2_MOBILE\n\n#include \"mpscnn_context.h\"\n#include \"mpscnn_kernels.h\"\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/timer.h\"\n\n#include <array>\n#include <mutex>\n#include <thread>\n\n#import <Metal/MTLFunctionConstantValues.h>\n\nnamespace caffe2 {\n\nMPSCNNContext& getMPSCNNContext() {\n  static std::once_flag once;\n  static MPSCNNContext ctx;\n  std::call_once(once, []() {\n    NSError* compileError = nil;\n    ctx.device = MTLCreateSystemDefaultDevice();\n    ctx.library = [ctx.device newLibraryWithSource:[NSString stringWithUTF8String:MPSCNN_KERNELS]\n                                           options:nil\n                                             error:&compileError];\n    if (compileError != nil || ctx.library == nil) {\n      CAFFE_THROW(\"Failed to load kernels: \", [[compileError localizedDescription] UTF8String]);\n    }\n    ctx.commandQueue = [ctx.device newCommandQueue];\n  });\n  return ctx;\n}\n\nid<MTLComputePipelineState> MPSCNNContext::getPipelineState(NSString* kernel) {\n  std::string kernelStr = std::string([kernel UTF8String]);\n  std::lock_guard<std::mutex> g(pipelineCacheMutex_);\n  if (pipelineCache_.find(kernelStr) != pipelineCache_.end()) {\n    VLOG(1) << \"Hit in pipeline cache for: \" << kernelStr;\n    return pipelineCache_[kernelStr];\n  }\n  LOG(INFO) << \"Miss in pipeline cache for: \" << kernelStr;\n  id<MTLFunction> func = [library newFunctionWithName:kernel];\n  if (!func) {\n    CAFFE_THROW(\"Couldn't get function: \", kernelStr);\n    return nullptr;\n  }\n  NSError* errors;\n  id<MTLComputePipelineState> state =\n      [device newComputePipelineStateWithFunction:func error:&errors];\n  if (!state) {\n    CAFFE_THROW(\"Couldn't get state: \", kernelStr);\n    return nullptr;\n  }\n  pipelineCache_[kernelStr] = state;\n  return state;\n}\n\nid<MTLComputePipelineState> MPSCNNContext::getSpecializedPipelineState(\n    NSString* kernel, const std::vector<ushort>& constants) {\n  std::string kernelStr = std::string([kernel UTF8String]);\n  for (auto i = 0; i < constants.size(); ++i) {\n    kernelStr += \"_\" + std::to_string(constants[i]);\n  }\n  std::lock_guard<std::mutex> g(pipelineCacheMutex_);\n  if (pipelineCache_.find(kernelStr) != pipelineCache_.end()) {\n    VLOG(1) << \"Hit in pipeline cache for: \" << kernelStr;\n    return pipelineCache_[kernelStr];\n  }\n  MTLFunctionConstantValues* constantValues = [MTLFunctionConstantValues new];\n  for (auto i = 0; i < constants.size(); ++i) {\n    [constantValues setConstantValue:&constants[i] type:MTLDataTypeUShort atIndex:i];\n  }\n  NSError* errors;\n\n  LOG(INFO) << \"Miss in pipeline cache for: \" << kernelStr;\n  id<MTLFunction> func =\n      [library newFunctionWithName:kernel constantValues:constantValues error:&errors];\n  if (!func) {\n    CAFFE_THROW(\"Couldn't get function: \",\n                kernelStr,\n                \" error: \",\n                [[errors localizedDescription] UTF8String]);\n    return nullptr;\n  }\n  id<MTLComputePipelineState> state =\n      [device newComputePipelineStateWithFunction:func error:&errors];\n  if (!state) {\n    CAFFE_THROW(\"Couldn't get function: \",\n                kernelStr,\n                \" error: \",\n                [[errors localizedDescription] UTF8String]);\n    return nullptr;\n  }\n  pipelineCache_[kernelStr] = state;\n  return state;\n}\n}\n\n#endif\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/mpscnn/mpscnn_graph.mm",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/operator.h\"\n#include \"mpscnn.h\"\n#include \"mpscnn_context.h\"\n\n#import <Metal/Metal.h>\n#import <MetalPerformanceShaders/MetalPerformanceShaders.h>\n#import <UIKit/UIDevice.h>\n\nnamespace caffe2 {\nstruct Analysis {\n  struct SSA {\n    using BlobVersions = std::unordered_map<std::string, size_t>;\n    BlobVersions inVersions;\n    BlobVersions outVersions;\n  };\n  std::vector<SSA> ssa;\n  std::unordered_map<\n      std::string,\n      std::unordered_map<size_t, std::vector<size_t>>>\n      inUsages;\n};\n\nAnalysis analyzeNet(const NetDef& net) {\n  Analysis::SSA::BlobVersions frontier;\n  Analysis analysis;\n\n  auto play = [&](size_t i, const OperatorDef& op) {\n    Analysis::SSA::BlobVersions inVersions;\n    for (const auto& s : op.input()) {\n      inVersions[s] = frontier[s];\n      analysis.inUsages[s][frontier[s]].push_back(i);\n    }\n    Analysis::SSA::BlobVersions outVersions;\n    for (const auto& s : op.output()) {\n      if (frontier.find(s) != frontier.end()) {\n        frontier[s] += 1;\n      }\n      outVersions[s] = frontier[s];\n    }\n    analysis.ssa.push_back(Analysis::SSA{inVersions, outVersions});\n  };\n\n  for (auto i = 0; i < net.op_size(); ++i) {\n    play(i, net.op(i));\n  }\n  return analysis;\n}\n\nNetDef insertInputOutputCopyOps(const NetDef& def) {\n  // Do some validation of the outputs. For this version, we require:\n  // - a single input (first element of external_input()) is consumed by the\n  // NetDef - a single output (first element of external_output()) is produced\n  // by the NetDef. - the input is consumed by def.op(0), and this is the only\n  // consumer. - the output is produced by def.op(-1).\n  CAFFE_ENFORCE_GE(def.external_input_size(), 1);\n  CAFFE_ENFORCE_GE(def.external_output_size(), 1);\n  auto analysis = analyzeNet(def);\n  // enforce a single use of the input blob.\n  CAFFE_ENFORCE_GE(def.op_size(), 1);\n  const auto& inputBlob = def.external_input(0);\n  // Enforce that the input blob has a single usage - in the first operator.\n  CAFFE_ENFORCE(analysis.inUsages[inputBlob][0] == (std::vector<size_t>{0}));\n  // Enforce that the external_output(0) blob is produced by the last operator\n  // in this sequence.\n  const auto& outputBlob = def.external_output(0);\n  CAFFE_ENFORCE(\n      analysis.ssa.back().outVersions.find(outputBlob) !=\n      analysis.ssa.back().outVersions.end());\n  const auto& outputBlobVersion = analysis.ssa.back().outVersions[outputBlob];\n  // This should hold true by definition of the SSA analysis.\n  CAFFE_ENFORCE(\n      analysis.inUsages[outputBlob].find(outputBlobVersion) ==\n      analysis.inUsages[outputBlob].end());\n  NetDef mdef;\n  mdef.CopyFrom(def);\n  mdef.clear_op();\n\n  {\n    auto& op = *(mdef.add_op());\n    op.set_type(\"CopyToMPSCNN\");\n    op.add_input(def.external_input(0));\n    op.add_output(\"__METAL_INPUT_COPY__\");\n  }\n\n  for (auto i = 0; i < def.op_size(); ++i) {\n    const auto& ogOp = def.op(i);\n    auto op = mdef.add_op();\n    op->CopyFrom(ogOp);\n    if (i == 0) {\n      CAFFE_ENFORCE_EQ(op->input(0), def.external_input(0));\n      op->set_input(0, \"__METAL_INPUT_COPY__\");\n    }\n    if (i == def.op_size() - 1) {\n      CAFFE_ENFORCE_EQ(op->output(0), def.external_output(0));\n      op->set_output(0, \"__METAL_OUTPUT_COPY__\");\n    }\n  }\n  {\n    auto& op = *(mdef.add_op());\n    op.set_type(\"CopyFromMPSCNN\");\n    op.add_input(\"__METAL_OUTPUT_COPY__\");\n    op.add_output(def.external_output(0));\n  }\n  return mdef;\n}\n\nbool nextIsOnlyUserOfCurrent(\n    const Analysis& analysis,\n    size_t currentIdx,\n    const OperatorDef& currentOp,\n    const OperatorDef& nextOp) {\n  CAFFE_ENFORCE_EQ(currentOp.output_size(), 1);\n  CAFFE_ENFORCE_GE(nextOp.input_size(), 1);\n  CAFFE_ENFORCE_EQ(currentOp.output(0), nextOp.input(0));\n  const auto outputName = currentOp.output(0);\n  // Find the version of the output name we are currently looking at.\n  // This is guaranteed to exist by SSA analysis.\n  const auto currentOutputVersion =\n      analysis.ssa.at(currentIdx).outVersions.at(outputName);\n  VLOG(2) << \"Blob: \" << outputName << \", idx: \" << currentOutputVersion;\n  // Find the usages of this in the SSA analysis.\n\n  // Has this blob every been used?\n  if (analysis.inUsages.find(outputName) == analysis.inUsages.end()) {\n    return false;\n  }\n\n  // Has this version of the blob ever been used?\n  if (analysis.inUsages.at(outputName).find(currentOutputVersion) ==\n      analysis.inUsages.at(outputName).end()) {\n    return false;\n  }\n  const auto currentOutputUsages =\n      analysis.inUsages.at(outputName).at(currentOutputVersion);\n  VLOG(2) << \"Blob: \" << outputName << \", idx: \" << currentOutputVersion\n          << \", usages[0]: \" << currentOutputUsages[0];\n\n  return currentOutputUsages == std::vector<size_t>{currentIdx + 1};\n}\nbool tryFuseAdjacentOps(\n    const Analysis& analysis,\n    size_t currentIdx,\n    const OperatorDef& currentOp,\n    const OperatorDef& nextOp,\n    OperatorDef* fusedOp) {\n  // Check for possible invalid opportunities.\n  // Must be identical outputs, with either in-place usage for nextOp, *or* the\n  // only use of the output of currentOp is the consumption by nextOp.\n  if (currentOp.output_size() != 1 || !nextOp.input_size() ||\n      nextOp.output_size() != 1) {\n    return false;\n  }\n\n  if (currentOp.output(0) != nextOp.input(0)) {\n    return false;\n  }\n\n  if (!nextIsOnlyUserOfCurrent(analysis, currentIdx, currentOp, nextOp)) {\n    return false;\n  }\n\n  // Can we autogenerate this at registration time instead?\n  static const std::map<std::pair<std::string, std::string>, std::string>\n      fusionOpportunities = {{\n          {{\"MPSCNNConv\", \"MPSCNNRelu\"}, \"MPSCNNConvRelu\"},\n          {{\"MPSCNNConv\", \"MPSCNNSigmoid\"}, \"MPSCNNConvSigmoid\"},\n          {{\"MPSCNNFC\", \"MPSCNNRelu\"}, \"MPSCNNFCRelu\"},\n          {{\"MPSCNNInstanceNorm\", \"MPSCNNPRelu\"}, \"MPSCNNInstanceNormPRelu\"},\n      }};\n  auto it = fusionOpportunities.find({currentOp.type(), nextOp.type()});\n  if (it == fusionOpportunities.end()) {\n    return false;\n  }\n  // MPSCNNConvRelu and MPSCNNConvSigmoid cannot be in-place\n  if (currentOp.type() == \"MPSCNNConv\" &&\n      currentOp.input(0) == nextOp.output(0)) {\n    return false;\n  }\n  LOG(INFO) << \"Found a fusion between adjacent ops: (\" << currentOp.type()\n            << \", \" << nextOp.type() << \") -> \" << it->second;\n  fusedOp->CopyFrom(currentOp);\n  fusedOp->set_type(it->second);\n  for (auto i = 1; i < nextOp.input_size(); ++i) {\n    fusedOp->add_input(nextOp.input(i));\n  }\n  fusedOp->set_output(0, nextOp.output(0));\n  return true;\n}\n\nNetDef runMPSCNNFusion(const NetDef& def) {\n  CAFFE_ENFORCE_GE(def.op_size(), 1);\n  NetDef mdef;\n  mdef.CopyFrom(def);\n  mdef.clear_op();\n  auto i = 0;\n  auto analysis = analyzeNet(def);\n\n  while (i < def.op_size()) {\n    if (i == def.op_size() - 1) {\n      VLOG(2) << \"Last operator, skipping\";\n      auto* op = mdef.add_op();\n      op->CopyFrom(def.op(i));\n      i += 1;\n      continue;\n    }\n\n    const auto& currentOp = def.op(i);\n    const auto& nextOp = def.op(i + 1);\n    OperatorDef fusedOp;\n    if (tryFuseAdjacentOps(analysis, i, currentOp, nextOp, &fusedOp)) {\n      VLOG(2) << \"Found an adjacent fusion at: \" << i;\n      // We can fuse.\n      auto* op = mdef.add_op();\n      op->CopyFrom(fusedOp);\n      i += 2;\n      continue;\n    }\n    VLOG(2) << \"No fusion available\";\n    // Just emit the current type.\n    auto* op = mdef.add_op();\n    op->CopyFrom(currentOp);\n    i += 1;\n  }\n  return mdef;\n}\n\nNetDef rewriteForMetal(const NetDef& def) {\n  NetDef mdef;\n  mdef.CopyFrom(def);\n\n  const auto& opKeyList = CPUOperatorRegistry()->Keys();\n  const auto& opKeySet =\n      std::set<std::string>(opKeyList.begin(), opKeyList.end());\n  for (auto i = 0; i < mdef.op_size(); ++i) {\n    auto* op = mdef.mutable_op(i);\n    const auto mpscnnOp = std::string(\"MPSCNN\") + op->type();\n    CAFFE_ENFORCE(opKeySet.find(mpscnnOp) != opKeySet.end());\n    op->set_type(mpscnnOp);\n  }\n\n  mdef = runMPSCNNFusion(mdef);\n  static std::set<std::string> mpscnnInputOps = {\n      \"CopyToMPSCNN\", \"MPSCNNPackedInt8BGRANHWCToNCHWCStylizerPreprocess\"};\n  static std::set<std::string> mpscnnOutputOps = {\n      \"CopyFromMPSCNN\", \"MPSCNNBRGNCHWCToPackedInt8BGRAStylizerDeprocess\"};\n\n  if (mpscnnInputOps.find(mdef.op(0).type()) == mpscnnInputOps.end() &&\n      mpscnnOutputOps.find(mdef.op(mdef.op_size() - 1).type()) ==\n          mpscnnOutputOps.end()) {\n    mdef = insertInputOutputCopyOps(mdef);\n  }\n  CAFFE_ENFORCE_GE(mdef.op_size(), 2);\n  CAFFE_ENFORCE(mpscnnInputOps.find(mdef.op(0).type()) != mpscnnInputOps.end());\n  CAFFE_ENFORCE(\n      mpscnnOutputOps.find(mdef.op(mdef.op_size() - 1).type()) !=\n      mpscnnOutputOps.end());\n  return mdef;\n}\n\nvoid dumpDef(const NetDef& d) {\n  for (const auto& op : d.op()) {\n    LOG(INFO) << op.input(0) << \" -> \" << op.type() << \" -> \" << op.output(0);\n  }\n}\n\nNetDef annotateDefWithReadCounts(const NetDef& net) {\n  // Now we have usage versions, we want to compute, for each blob version, the\n  // number of usages of each blob version. ReadCount\n  auto analysis = analyzeNet(net);\n  using ReadCount = std::unordered_map<std::string, size_t>;\n  std::vector<ReadCount> readCounts;\n\n  auto computeReadCount = [&](size_t i, const OperatorDef& op) {\n    ReadCount rcs;\n    for (const auto bv : analysis.ssa[i].outVersions) {\n      const auto versionUsages = analysis.inUsages[bv.first][bv.second];\n      rcs[bv.first] = versionUsages.size();\n    }\n    readCounts.push_back(rcs);\n  };\n  for (auto i = 0; i < net.op_size(); ++i) {\n    computeReadCount(i, net.op(i));\n  }\n\n  NetDef annotatedNet;\n  annotatedNet.CopyFrom(net);\n  for (auto i = 0; i < annotatedNet.op_size(); ++i) {\n    auto* op = annotatedNet.mutable_op(i);\n    // TODO - relax this? CAFFE_ENFORCE_EQ(op->output_size(), 1);\n    const auto& blob = op->output(0);\n    const size_t readCount = readCounts[i][blob];\n    if (readCount > 1) {\n      auto* arg = op->add_arg();\n      arg->set_name(kMPSCNNReadCountArg);\n      arg->set_i(readCount);\n      LOG(INFO) << \"Op: \" << i << \", ty: \" << op->type() << \", blob: \" << blob\n                << \", read count: \" << readCount;\n    }\n  }\n  return annotatedNet;\n}\n\nbool tryConvertToMPSCNN(\n    const NetDef& initNet,\n    const NetDef& predictNet,\n    NetDef* metalPredictNet) {\n  // iOS 10.0 and above.\n\n#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) \\\n  ([[[UIDevice currentDevice] systemVersion]       \\\n       compare:v                                   \\\n       options:NSNumericSearch] != NSOrderedAscending)\n  if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@\"11.0\")) {\n    LOG(ERROR) << \"MPSCNN is only supported for ios version above 11.0.\";\n    return false;\n  }\n#undef SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO\n  // The iOS GPU Family 3 v2 feature set. Introduced with the Apple A9 GPU and\n  // iOS 10.0. Don't instantiate the MPSCNNContext, as that compiles the kernel\n  // source.\n  if (![MTLCreateSystemDefaultDevice()\n          supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v2]) {\n    LOG(ERROR) << \"The iOS GPU is less than an A9, so MPSCNN is not available\";\n    return false;\n  }\n\n  try {\n    // Instantiating the net and catching failures allows us to\n    Workspace ws;\n    ws.RunNetOnce(initNet);\n    // Throws if unsupported operators are found.\n    *metalPredictNet = rewriteForMetal(predictNet);\n    *metalPredictNet = annotateDefWithReadCounts(*metalPredictNet);\n    // Throws if unsupported parameters are found.\n    ws.CreateNet(*metalPredictNet);\n    LOG(INFO) << \"MPSCNN is successfully enabled\";\n    return true;\n  } catch (const std::exception& e) {\n    LOG(ERROR) << \"Caught exception trying to convert NetDef to MPSCNN: \"\n               << e.what();\n    return false;\n  }\n}\n\nvoid mpscnnRecordExecutionFinish() {\n  [getMPSCNNContext().commandQueue insertDebugCaptureBoundary];\n}\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/mpscnn/mpscnn_graph_mask.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n#include \"caffe2/core/net.h\"\n#include \"mpscnn.h\"\n\nnamespace caffe2 {\n// We currently only try to convert a fixed set of operators that handle a subset of a full\n// CNN. We also only run when MPSCNN is available, provides a speedup.\n// On failure, returns false. On success, returns true, and sets the MPSCNN net in the output\n// parameter.\n// The rewrite function now supports insertion of copies in intermediate ops.\nbool tryConvertToMPSCNNIntermediateCopies(const NetDef& initNet,\n                                          const NetDef& predictNet,\n                                          NetDef* mpscnnPredictNet);\nNetDef setSpecialArgs(const NetDef& def);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/mpscnn/mpscnn_graph_mask.mm",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"mpscnn_graph_mask.h\"\n#include \"caffe2/core/operator.h\"\n#include \"mpscnn_context.h\"\n\n#import <Metal/Metal.h>\n#import <MetalPerformanceShaders/MetalPerformanceShaders.h>\n#import <UIKit/UIDevice.h>\n\nnamespace caffe2 {\n\nnamespace {\nenum class StorageType {\n  MPSTEMPORARYIMAGE, /* Default for MPSCNN */\n  MPSIMAGE,\n  CPU,\n  INVALID\n};\n\nstring as_string(StorageType st) {\n  switch (st) {\n  case StorageType::MPSTEMPORARYIMAGE:\n    return \"MPSTEMPORARYIMAGE\";\n  case StorageType::MPSIMAGE:\n    return \"MPSIMAGE\";\n  case StorageType::CPU:\n    return \"CPU\";\n  case StorageType::INVALID:\n    return \"INVALID\";\n  }\n}\n\nstd::unordered_map<string, std::vector<StorageType>> inputStorageTypeMap = {\n    {\"MPSCNNGenerateProposalsCPP\",\n     std::vector<StorageType>{StorageType::CPU,\n                              StorageType::CPU,\n                              StorageType::CPU,\n                              StorageType::CPU}},\n    {\"MPSCNNRoIWarp\",\n     std::vector<StorageType>{StorageType::MPSTEMPORARYIMAGE,\n                              StorageType::CPU}},\n    {\"MPSCNNConvRelu\",\n     std::vector<StorageType>{StorageType::MPSTEMPORARYIMAGE,\n                              StorageType::CPU,\n                              StorageType::CPU}},\n    {\"MPSCNNFC\",\n     std::vector<StorageType>{StorageType::MPSTEMPORARYIMAGE,\n                              StorageType::CPU,\n                              StorageType::CPU}},\n    {\"MPSCNNConv\",\n     std::vector<StorageType>{StorageType::MPSTEMPORARYIMAGE,\n                              StorageType::CPU,\n                              StorageType::CPU}},\n    {\"MPSCNNConvTranspose\",\n     std::vector<StorageType>{StorageType::MPSTEMPORARYIMAGE,\n                              StorageType::CPU,\n                              StorageType::CPU}},\n    {\"MPSCNNMul\",\n     std::vector<StorageType>{StorageType::MPSTEMPORARYIMAGE,\n                              StorageType::CPU}},\n    {\"MPSCNNSub\",\n     std::vector<StorageType>{StorageType::MPSTEMPORARYIMAGE,\n                              StorageType::CPU}}};\nstd::unordered_map<string, std::vector<StorageType>> outputStorageTypeMap = {\n    {\"MPSCNNGenerateProposalsCPP\", std::vector<StorageType>{StorageType::CPU, StorageType::CPU}}};\nstd::vector<string> opsNeedsSync = {\"MPSCNNGenerateProposalsCPP\", \"CopyFromMPSCNN\", \"CopyToMPSCNN\"};\n\nstruct Analysis {\n  struct SSA {\n    using BlobVersions = std::unordered_map<std::string, size_t>;\n    BlobVersions inVersions;\n    BlobVersions outVersions;\n  };\n  struct BlobInfo {\n    std::vector<size_t> inUsages; // ids for operator that used the blob\n    StorageType storageType = StorageType::INVALID; // storage type of the blob\n    int commandBufferId; // the id for command buffer used by the blob\n  };\n  std::vector<SSA> ssa;\n  // blob name -> blob version -> blob information\n  std::unordered_map<std::string, std::unordered_map<size_t, BlobInfo>> blobInfoMap;\n  int currentCommandBufferId = 0;\n};\n\nvoid ssaAnalysis(Analysis& analysis, const NetDef& net) {\n  Analysis::SSA::BlobVersions frontier;\n\n  auto play = [&](size_t i, const OperatorDef& op) {\n    Analysis::SSA::BlobVersions inVersions;\n    for (const auto& s : op.input()) {\n      inVersions[s] = frontier[s];\n      analysis.blobInfoMap[s][frontier[s]].inUsages.push_back(i);\n    }\n    Analysis::SSA::BlobVersions outVersions;\n    auto isTemporaryImages = std::vector<int>();\n    for (auto j = 0; j < op.arg_size(); ++j) {\n      if (op.arg(j).name() == kMPSCNNOutputIsTempImageArg) {\n        for (auto k = 0; k < op.arg(j).ints_size(); ++k) {\n          isTemporaryImages.push_back(op.arg(j).ints(k));\n        }\n      }\n    }\n\n    for (auto j = 0; j < op.output_size(); j++) {\n      auto s = op.output(j);\n      if (frontier.find(s) != frontier.end()) {\n        frontier[s] += 1;\n      }\n      outVersions[s] = frontier[s];\n      if (outputStorageTypeMap.find(op.type()) != outputStorageTypeMap.end()) {\n        analysis.blobInfoMap[s][frontier[s]].storageType = outputStorageTypeMap[op.type()][j];\n      } else if (op.type() == \"CopyFromMPSCNN\") {\n        analysis.blobInfoMap[s][frontier[s]].storageType = StorageType::CPU;\n      } else if (isTemporaryImages.size() > 0) {\n        if (isTemporaryImages.at(j)) {\n          analysis.blobInfoMap[s][frontier[s]].storageType = StorageType::MPSTEMPORARYIMAGE;\n        } else {\n          analysis.blobInfoMap[s][frontier[s]].storageType = StorageType::MPSIMAGE;\n        }\n      } else if (op.type().find(\"MPSCNN\") != std::string::npos) {\n        analysis.blobInfoMap[s][frontier[s]].storageType = StorageType::MPSTEMPORARYIMAGE;\n      } else {\n        analysis.blobInfoMap[s][frontier[s]].storageType = StorageType::CPU;\n      }\n      VLOG(2) << op.type() << \" outputBlobTypes:\" << s << \" \" << frontier[s] << \" \"\n              << as_string(analysis.blobInfoMap[s][frontier[s]].storageType);\n    }\n    analysis.ssa.push_back(Analysis::SSA{inVersions, outVersions});\n  };\n\n  for (auto i = 0; i < net.op_size(); ++i) {\n    play(i, net.op(i));\n  }\n}\n\nstatic void rewriteOutput(OperatorDef* op, int i) {\n  auto output = op->output(i);\n  op->set_output(i, output + \"_M\");\n}\n\nstatic void rewriteInput(OperatorDef* op, int i) {\n  auto input = op->input(i);\n  op->set_input(i, input + \"_I\");\n}\n\nstatic void insertOutputCopyFromMPSCNNOp(NetDef& predictNet, const std::string& cpu_blob) {\n  auto* op = predictNet.add_op();\n  op->set_type(\"CopyFromMPSCNN\");\n  op->add_input(cpu_blob + \"_M\");\n  op->add_output(cpu_blob);\n}\n\nstatic void insertInputCopyFromMPSCNNOp(NetDef& predictNet, const std::string& cpu_blob) {\n  auto* op = predictNet.add_op();\n  op->set_type(\"CopyFromMPSCNN\");\n  op->add_input(cpu_blob);\n  op->add_output(cpu_blob + \"_I\");\n}\n\nstatic void insertInputCopyToMPSCNNOp(NetDef& predictNet, const std::string& gpu_blob) {\n  auto* op = predictNet.add_op();\n  op->set_type(\"CopyToMPSCNN\");\n  op->add_input(gpu_blob);\n  op->add_output(gpu_blob + \"_I\");\n}\n\nvoid commandBufferAnalysis(Analysis& analysis, NetDef& def) {\n  analysis.currentCommandBufferId = 0;\n  analysis.blobInfoMap[def.op(0).input(0)][0].commandBufferId = analysis.currentCommandBufferId;\n  for (auto i = 0; i < def.op_size(); ++i) {\n    auto op = def.op(i);\n    if (std::find(opsNeedsSync.begin(), opsNeedsSync.end(), op.type()) != opsNeedsSync.end()) {\n      analysis.currentCommandBufferId += 1;\n      for (auto j = 0; j < op.output_size(); ++j) {\n        auto outputBlob = op.output(j);\n        auto version = analysis.ssa[i].outVersions[outputBlob];\n        analysis.blobInfoMap[outputBlob][version].commandBufferId = analysis.currentCommandBufferId;\n      }\n    } else {\n      int inputCommandBufferId = 0;\n      for (auto j = 0; j < op.input_size(); ++j) {\n        auto inputBlob = op.input(j);\n        auto version = analysis.ssa[i].inVersions[inputBlob];\n        if (analysis.blobInfoMap.find(inputBlob) != analysis.blobInfoMap.end() &&\n            analysis.blobInfoMap[inputBlob][version].storageType == StorageType::MPSIMAGE) {\n          analysis.currentCommandBufferId += 1;\n          inputCommandBufferId = analysis.currentCommandBufferId;\n        } else {\n          inputCommandBufferId =\n              fmax(inputCommandBufferId, analysis.blobInfoMap[inputBlob][version].commandBufferId);\n        }\n      }\n      // command buffer same as input\n      for (auto j = 0; j < op.output_size(); ++j) {\n        auto outputBlob = op.output(j);\n        auto version = analysis.ssa[i].outVersions[outputBlob];\n        analysis.blobInfoMap[outputBlob][version].commandBufferId = inputCommandBufferId;\n      }\n    }\n    for (auto j = 0; j < op.output_size(); ++j) {\n      auto outputBlob = op.output(j);\n      auto version = analysis.ssa[i].outVersions[outputBlob];\n      VLOG(2) << \"command buffer analysis: \" << outputBlob << \" \" << version << \" \"\n              << analysis.blobInfoMap[outputBlob][version].commandBufferId;\n    }\n  }\n}\n\nvoid analyzeNet(Analysis& analysis, NetDef& net) {\n  analysis.ssa.clear();\n  analysis.blobInfoMap.clear();\n  ssaAnalysis(analysis, net);\n  commandBufferAnalysis(analysis, net);\n}\n\nNetDef mergeCopyFromMPSCNN(Analysis& analysis, NetDef& def) {\n  analyzeNet(analysis, def);\n  // command buffer id -> op id\n  std::unordered_map<int, std::vector<size_t>> commandBufferToOps;\n  // For CopyFromMPSCNN, find the command buffer id each input blob uses. and\n  // aggreagate the ops with the same command buffer\n  for (auto i = 0; i < def.op_size(); ++i) {\n    auto op = def.op(i);\n    if (op.type() == \"CopyFromMPSCNN\") {\n      auto blobName = op.input(0);\n      auto version = analysis.ssa[i].inVersions[blobName];\n      auto commandId = analysis.blobInfoMap[blobName][version].commandBufferId;\n      VLOG(2) << \"Command buffer to ops:\" << blobName << \" \" << version << \" \" << commandId;\n      if (commandBufferToOps.find(commandId) == commandBufferToOps.end()) {\n        commandBufferToOps[commandId] = std::vector<size_t>();\n      }\n      commandBufferToOps[commandId].push_back(i);\n    }\n  }\n\n  std::vector<size_t> opsToRemove;\n  for (auto item : commandBufferToOps) {\n    auto commandBufferId = item.first;\n    auto ops = item.second;\n    if (ops.size() > 1) {\n      VLOG(2) << \"Merging for command buffer:\" << commandBufferId;\n      // Let's use the first input as an indicator whether the data is for\n      // external output or internal use, if the data used by intermediate node,\n      // we want to keep the first operator, otherwise, we want to keep\n      // the last operator.\n      // [LATER]There might be cases when some of the data is for external output and\n      // others used by intermediate node, we'll need to have better heuristics\n      // for these cases.\n      auto externalUse = false;\n      auto firstCopy = def.op(ops[0]);\n      auto firstOutput = firstCopy.output(0);\n      for (auto i = 0; i < def.external_output_size(); ++i) {\n        if (def.external_output(i) == firstOutput) {\n          externalUse = true;\n        }\n      }\n      int removeStart, removeEnd, keepIndex;\n      if (externalUse) {\n        // change the last op into the new op and remove the other ops;\n        removeStart = 0;\n        removeEnd = ops.size() - 1;\n        keepIndex = ops[removeEnd];\n      } else {\n        removeStart = 1;\n        removeEnd = ops.size();\n        keepIndex = ops[removeStart - 1];\n      }\n      auto* op = def.mutable_op(keepIndex);\n      auto inputOutputs = std::set<std::pair<string, string>>();\n      for (auto i = removeStart; i < removeEnd; ++i) {\n        auto op0 = def.op(ops[i]);\n        if (op0.input(0) != op->input(0)) {\n          inputOutputs.insert(make_pair(op0.input(0), op0.output(0)));\n        }\n      }\n      for (auto inputOutput : inputOutputs) {\n        op->add_input(inputOutput.first);\n        op->add_output(inputOutput.second);\n      }\n      for (auto i = removeStart; i < removeEnd; ++i) {\n        opsToRemove.push_back(ops[i]);\n      }\n    }\n  }\n\n  NetDef mdef;\n  mdef.CopyFrom(def);\n  mdef.clear_op();\n  for (auto i = 0; i < def.op_size(); ++i) {\n    if (std::find(opsToRemove.begin(), opsToRemove.end(), i) == opsToRemove.end()) {\n      const auto& ogOp = def.op(i);\n      auto op = mdef.add_op();\n      op->CopyFrom(ogOp);\n    }\n  }\n  return mdef;\n}\n\n/* Remove the CopyToMPSCNN ops that has the same input/output version\n */\nNetDef mergeCopyToMPSCNN(Analysis& analysis, NetDef& def) {\n  std::vector<size_t> opsToRemove;\n  std::set<std::pair<string, size_t>> copiedBlobs;\n  for (auto i = 0; i < def.op_size(); ++i) {\n    auto op = def.op(i);\n    if (def.op(i).type() == \"CopyToMPSCNN\") {\n      auto blobName = op.input(0);\n      auto version = analysis.ssa[i].inVersions[blobName];\n      auto pair = make_pair(blobName, version);\n      if (std::find(copiedBlobs.begin(), copiedBlobs.end(), pair) == copiedBlobs.end()) {\n        copiedBlobs.insert(pair);\n      } else {\n        opsToRemove.push_back(i);\n      }\n    }\n  }\n  NetDef mdef;\n  mdef.CopyFrom(def);\n  mdef.clear_op();\n  for (auto i = 0; i < def.op_size(); ++i) {\n    if (std::find(opsToRemove.begin(), opsToRemove.end(), i) == opsToRemove.end()) {\n      const auto& ogOp = def.op(i);\n      auto op = mdef.add_op();\n      op->CopyFrom(ogOp);\n    }\n  }\n  return mdef;\n}\n\nbool addTempImageArgs(Analysis& analysis, NetDef& def) {\n  analyzeNet(analysis, def);\n\n  std::vector<int> synced; // synced command buffer ids;\n  std::set<std::pair<string, size_t>> mpsImageBlobs; // blobname, version\n\n  // We want to add temp arg one by one since it changes the command buffer id\n  // for later operators.\n  bool found = false;\n  // identify the images that the command buffer is synced before\n  for (auto i = 0; i < def.op_size(); ++i) {\n    auto op = def.op(i);\n    if (op.type().find(\"MPSCNN\") == string::npos) {\n      continue;\n    }\n    for (auto j = 0; j < op.input_size(); ++j) {\n      auto inputBlob = op.input(j);\n      auto version = analysis.ssa[i].inVersions[inputBlob];\n      auto commandId = analysis.blobInfoMap[inputBlob][version].commandBufferId;\n      if (std::find(opsNeedsSync.begin(), opsNeedsSync.end(), op.type()) != opsNeedsSync.end()) {\n        synced.push_back(commandId);\n        break;\n      }\n      if (std::find(synced.begin(), synced.end(), commandId) != synced.end() &&\n          analysis.blobInfoMap.find(inputBlob) != analysis.blobInfoMap.end() &&\n          analysis.blobInfoMap[inputBlob][version].storageType == StorageType::MPSTEMPORARYIMAGE) {\n        VLOG(2) << \"mpsimage blob:\" << inputBlob << \" \" << version << \" \"\n                << \"input \" << j << \" command: \" << commandId;\n        mpsImageBlobs.insert(make_pair(inputBlob, version));\n        found = true;\n      }\n    }\n    if (found) {\n      break;\n    }\n  }\n  // find the blob and add argument\n  if (found) {\n    for (auto i = 0; i < def.op_size(); ++i) {\n      auto op = def.mutable_op(i);\n      std::vector<int> isTempImages;\n      bool setArg = false;\n      for (auto j = 0; j < op->output_size(); ++j) {\n        auto outputBlob = op->output(j);\n        auto version = analysis.ssa[i].outVersions[outputBlob];\n        if (mpsImageBlobs.find(make_pair(outputBlob, version)) != mpsImageBlobs.end()) {\n          setArg = true;\n          isTempImages.push_back(0);\n        } else {\n          isTempImages.push_back(1);\n        }\n      }\n      if (setArg) {\n        auto& arg = *(op->add_arg());\n        arg.set_name(kMPSCNNOutputIsTempImageArg);\n        for (auto j = 0; j < isTempImages.size(); ++j) {\n          arg.add_ints(isTempImages[j]);\n        }\n      }\n    }\n  }\n  return found;\n}\n\nNetDef insertCopies(const NetDef& def) {\n  // For this version, we insert CopyFromMPSCNN both for\n  // intermediate nodes and the output node when necessary\n  CAFFE_ENFORCE_GE(def.external_input_size(), 1);\n  CAFFE_ENFORCE_GE(def.external_output_size(), 1);\n\n  Analysis analysis;\n  ssaAnalysis(analysis, def);\n\n  CAFFE_ENFORCE_GE(def.op_size(), 1);\n\n  const auto& outputBlob = def.external_output(0);\n  const auto& outputBlobVersion = analysis.ssa.back().outVersions[outputBlob];\n\n  // This should hold true by definition of the SSA analysis.\n  CAFFE_ENFORCE(analysis.blobInfoMap[outputBlob].find(outputBlobVersion) ==\n                    analysis.blobInfoMap[outputBlob].end() ||\n                analysis.blobInfoMap[outputBlob][outputBlobVersion].inUsages.size() == 0);\n  NetDef mdef;\n  mdef.CopyFrom(def);\n  mdef.clear_op();\n\n  const auto& opKeyList = CPUOperatorRegistry()->Keys();\n  const auto& opKeySet = std::set<std::string>(opKeyList.begin(), opKeyList.end());\n\n  for (auto i = 0; i < def.op_size(); ++i) {\n    const auto& ogOp = def.op(i);\n    auto inputsToRewrite = std::vector<int>();\n\n    for (auto j = 0; j < ogOp.input_size(); j++) {\n      // The blob storage type accepted by the operator\n      auto expectedBlobType = StorageType::MPSTEMPORARYIMAGE;\n      // The storage type for blob produced by previous operators\n      // if it's not produced by previous operators, then it should be network\n      // parameters which are stored in CPU\n      auto actualBlobType = StorageType::CPU;\n      // For non-mpscnn operators, we assume the expected storage type to be CPU\n      if (ogOp.type().find(\"MPSCNN\") == std::string::npos) {\n        expectedBlobType = StorageType::CPU;\n      }\n      auto inputBlob = ogOp.input(j);\n      auto version = analysis.ssa[i].inVersions[inputBlob];\n      // Check whether the blob is produced by previous operators\n      if (analysis.blobInfoMap.find(inputBlob) != analysis.blobInfoMap.end() &&\n          analysis.blobInfoMap[inputBlob][version].storageType != StorageType::INVALID) {\n        actualBlobType = analysis.blobInfoMap[inputBlob][version].storageType;\n        VLOG(2) << \"Found \" << inputBlob << \" \" << j << \" with type\" << as_string(actualBlobType);\n      }\n      if (inputStorageTypeMap.find(ogOp.type()) != inputStorageTypeMap.end()) {\n        expectedBlobType = inputStorageTypeMap[ogOp.type()][j];\n      }\n      if (expectedBlobType != actualBlobType) {\n        if (expectedBlobType == StorageType::CPU &&\n            (actualBlobType == StorageType::MPSTEMPORARYIMAGE ||\n             actualBlobType == StorageType::MPSIMAGE)) {\n          // copy input(MPSCNN) to input_I(CPU)\n          insertInputCopyFromMPSCNNOp(mdef, ogOp.input(j));\n          // rewrite input to input_I for the operator\n          inputsToRewrite.push_back(j);\n        } else if ((expectedBlobType == StorageType::MPSTEMPORARYIMAGE ||\n                    expectedBlobType == StorageType::MPSIMAGE) &&\n                   actualBlobType == StorageType::CPU) {\n          insertInputCopyToMPSCNNOp(mdef, ogOp.input(j));\n          inputsToRewrite.push_back(j);\n        } // We don't need to insert copies in other cases\n      }\n    }\n\n    auto op = mdef.add_op();\n    op->CopyFrom(ogOp);\n\n    for (auto j = 0; j < inputsToRewrite.size(); ++j) {\n      rewriteInput(op, inputsToRewrite[j]);\n    }\n\n    // rewrite name for (single) external input\n    if (op->type().find(\"MPSCNN\") != std::string::npos &&\n        opKeySet.find(op->type()) != opKeySet.end()) {\n      // input used by multiple ops\n      const auto& inputBlob = def.external_input(0);\n      if (std::find(analysis.blobInfoMap[inputBlob][0].inUsages.begin(),\n                    analysis.blobInfoMap[inputBlob][0].inUsages.end(),\n                    i) != analysis.blobInfoMap[inputBlob][0].inUsages.end()) {\n        for (auto j = 0; j < op->input_size(); ++j) {\n          if (op->input(j) == def.external_input(0)) {\n            op->set_input(j, \"__METAL_INPUT_COPY__\");\n          }\n        }\n      }\n    }\n\n    // if the output is in external output, copy from metal when necessary\n    for (auto j = 0; j < op->output_size(); ++j) {\n      for (auto k = 0; k < def.external_output_size(); ++k) {\n        // Assuming external output blob has unique name, e.g. only version 0\n        // of the blob is used as the output\n        if (op->output(j) == def.external_output(k) &&\n            analysis.blobInfoMap[op->output(j)][0].storageType != StorageType::CPU) {\n          // copy output_M(MPSCNN) to output(CPU)\n          insertOutputCopyFromMPSCNNOp(mdef, op->output(j));\n          // rewrite output to output_M for the operator\n          rewriteOutput(op, j);\n        }\n      }\n    }\n  }\n\n  // Since adding temp image arg changes the result for command buffer analysis,\n  // which is the analysis the function is based on, we'll add one temp image\n  // arg at a time and re-run ssa analysis after each and repeat the process\n  // until convergence\n  int i = 0;\n  while (addTempImageArgs(analysis, mdef) && i < 3 * mdef.op_size()) {\n    i++;\n  };\n\n  mdef = mergeCopyFromMPSCNN(analysis, mdef);\n  mdef = mergeCopyToMPSCNN(analysis, mdef);\n\n  return mdef;\n}\n\nNetDef rewriteForMetalI(const NetDef& def) {\n  NetDef mdef;\n  mdef.CopyFrom(def);\n\n  const auto& opKeyList = CPUOperatorRegistry()->Keys();\n  const auto& opKeySet = std::set<std::string>(opKeyList.begin(), opKeyList.end());\n  for (auto i = 0; i < mdef.op_size(); ++i) {\n    auto* op = mdef.mutable_op(i);\n    const auto mpscnnOp = std::string(\"MPSCNN\") + op->type();\n    if (opKeySet.find(mpscnnOp) != opKeySet.end()) {\n      op->set_type(mpscnnOp);\n    }\n  }\n\n  static std::set<std::string> mpscnnInputOps = {\n      \"CopyToMPSCNN\", \"MPSCNNPackedInt8BGRANHWCToNCHWCStylizerPreprocess\"};\n\n  mdef = insertCopies(mdef);\n\n  mdef = runMPSCNNFusion(mdef);\n\n  mdef = setSpecialArgs(mdef);\n\n  CAFFE_ENFORCE_GE(mdef.op_size(), 2);\n  CAFFE_ENFORCE(mpscnnInputOps.find(mdef.op(0).type()) != mpscnnInputOps.end());\n  return mdef;\n}\n} // namespace\n\nNetDef setSpecialArgs(const NetDef& def) {\n  NetDef mdef;\n  mdef.CopyFrom(def);\n  for (auto i = 0; i < mdef.op_size(); ++i) {\n    auto* op = mdef.mutable_op(i);\n    // setting post_nms_top_N for MPSCNNGenerateProposalsCPP to 36 due to the\n    // texture array length constraint in RoIWarp\n    if (op->type() == \"MPSCNNGenerateProposalsCPP\" || op->type() == \"GenerateProposalsCPP\") {\n      auto* arg = op->mutable_arg(0);\n      arg->set_i(36);\n    }\n  }\n  return mdef;\n}\n\nbool tryConvertToMPSCNNIntermediateCopies(const NetDef& initNet,\n                                          const NetDef& predictNet,\n                                          NetDef* metalPredictNet) {\n// iOS 10.0 and above.\n#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)                                 \\\n  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != \\\n   NSOrderedAscending)\n#define SYSTEM_VERSION_EQUAL_TO(v) \\\n  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)\n\n  if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@\"11.0\")) {\n    LOG(ERROR) << \"MPSCNN is only supported for ios version above 11.0.\";\n    return false;\n  }\n#undef SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO\n#undef SYSTEM_VERSION_EQUAL_TO\n\n  // The iOS GPU Family 3 v2 feature set. Introduced with the Apple A9 GPU and iOS 10.0.\n  // Don't instantiate the MPSCNNContext, as that compiles the kernel source.\n  if (![MTLCreateSystemDefaultDevice() supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v2]) {\n    LOG(ERROR) << \"The iOS GPU is less than an A9, so MPSCNN is not available\";\n    return false;\n  }\n\n  try {\n    // Instantiating the net and catching failures allows us to\n    Workspace ws;\n    ws.RunNetOnce(initNet);\n    // Throws if unsupported operators are found.\n    *metalPredictNet = rewriteForMetalI(predictNet);\n    *metalPredictNet = annotateDefWithReadCounts(*metalPredictNet);\n    // Throws if unsupported parameters are found.\n    ws.CreateNet(*metalPredictNet);\n    LOG(INFO) << \"MPSCNN is successfully enabled\";\n    return true;\n  } catch (const std::exception& e) {\n    LOG(ERROR) << \"Caught exception trying to convert NetDef to MPSCNN: \" << e.what();\n    return false;\n  }\n}\n} // caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/mpscnn/mpscnn_kernels.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// @generated\n\nstatic const char* MPSCNN_KERNELS = R\"V0G0N(\n\n\n#include <metal_stdlib>\n\nusing namespace metal;\n\nconstant ushort ushort_arg_0[[function_constant(0)]];\nconstant ushort ushort_arg_1[[function_constant(1)]];\nconstant ushort ushort_arg_2[[function_constant(2)]];\nconstant ushort ushort_arg_3[[function_constant(3)]];\nconstant ushort ushort_arg_4[[function_constant(4)]];\nconstant ushort ushort_arg_5[[function_constant(5)]];\nconstant ushort ushort_arg_6[[function_constant(6)]];\nconstant ushort ushort_arg_7[[function_constant(7)]];\nconstant ushort ushort_arg_8[[function_constant(8)]];\nconstant ushort ushort_arg_9[[function_constant(9)]];\n\ninline constexpr ushort divRoundUp(ushort x, ushort y) { return (x + (y - 1)) / y; }\n\nkernel void affine(constant half4* scale[[buffer(0)]],\n                   constant half4* shift[[buffer(1)]],\n                   texture2d_array<half, access::read> in[[texture(0)]],\n                   texture2d_array<half, access::write> out[[texture(1)]],\n                   ushort3 gid[[thread_position_in_grid]]) {\n    const ushort C = ushort_arg_0;\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n        return;\n    }\n    const half4 scale_c = scale[gid.z % divRoundUp(C, 4)];\n    const half4 shift_c = shift[gid.z % divRoundUp(C, 4)];\n    ushort2 gid_(gid.x, gid.y);\n    const half4 x = in.read(gid_, gid.z);\n    const half4 y = scale_c * x + shift_c;\n    out.write(y, gid_, gid.z);\n}\n\nkernel void affine_nonarray(constant half4* scale[[buffer(0)]],\n                            constant half4* shift[[buffer(1)]],\n                            texture2d<half, access::read> in[[texture(0)]],\n                            texture2d<half, access::write> out[[texture(1)]],\n                            ushort2 gid[[thread_position_in_grid]]) {\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n        return;\n    }\n    const half4 scale_c = scale[0];\n    const half4 shift_c = shift[0];\n    half4 x = in.read(gid);\n    const half4 y = scale_c * x + shift_c;\n    out.write(y, gid);\n}\n\nkernel void prelu_nonshared(constant half4* weights[[buffer(0)]],\n                            texture2d_array<half, access::read> in[[texture(0)]],\n                            texture2d_array<half, access::write> out[[texture(1)]],\n                            ushort3 gid[[thread_position_in_grid]]) {\n    const ushort C = ushort_arg_0;\n    const ushort S = ushort_arg_1;\n    const bool channel_shared = S == 1;\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n        return;\n    }\n    half4 w = channel_shared ? half4(weights[0][0], weights[0][0], weights[0][0], weights[0][0])\n    : weights[gid.z % divRoundUp(C, 4)];\n    ushort2 gid_(gid.x, gid.y);\n    half4 x = in.read(gid_, gid.z);\n    half4 y = select(x * w, x, x > 0.0h);\n    out.write(y, gid_, gid.z);\n}\n\nkernel void prelu_nonshared_nonarray(constant half4* weights[[buffer(0)]],\n                                     texture2d<half, access::read> in[[texture(0)]],\n                                     texture2d<half, access::write> out[[texture(1)]],\n                                     ushort2 gid[[thread_position_in_grid]]) {\n    // const ushort C = ushort_arg_0;\n    const ushort S = ushort_arg_1;\n    const bool channel_shared = S == 1;\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n        return;\n    }\n    half4 w = channel_shared ? half4(weights[0][0], weights[0][0], weights[0][0], weights[0][0])\n    : weights[0];\n    half4 x = in.read(gid);\n    half4 y = select(x * w, x, x > 0.0h);\n    out.write(y, gid);\n}\n\n// One block per texture.\n// 256 threads per block.\nusing AccT = float4;\n\nconstant const bool instance_norm_has_prelu = ushort_arg_1 > 0;\n\nkernel void instance_norm(\n                          constant half4* weights[[buffer(0)]],\n                          constant half4* bias[[buffer(1)]],\n                          constant half4* preluWeights[[ buffer(2), function_constant(instance_norm_has_prelu) ]],\n                          texture2d_array<half, access::read> in[[texture(0)]],\n                          texture2d_array<half, access::write> out[[texture(1)]],\n                          ushort3 gid[[thread_position_in_grid]],\n                          ushort tid[[thread_index_in_threadgroup]],\n                          ushort3 tcount[[threads_per_threadgroup]]) {\n    if (gid.z >= out.get_array_size()) {\n        return;\n    }\n    const ushort C = ushort_arg_0;\n    const ushort S = ushort_arg_1;\n    const bool channel_shared = S == 1;\n    const ushort c = gid.z % divRoundUp(C, 4);\n    constexpr ushort THREADGROUP_SIZE = 256;\n    \n    threadgroup AccT per_thread_state[THREADGROUP_SIZE];\n    // Each block handles a single texture.\n    per_thread_state[tid] = 0;\n    for (ushort y = gid.y; y < in.get_height(); y += tcount.y) {\n        for (ushort x = gid.x; x < in.get_width(); x += tcount.x) {\n            per_thread_state[tid] += static_cast<AccT>(in.read(ushort2(x, y), gid.z));\n        }\n    }\n    \n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    \n    // 256 -> 32 reduction\n    if (tid < 32) {\n        per_thread_state[tid] += per_thread_state[tid + 32] + per_thread_state[tid + 64] +\n        per_thread_state[tid + 96] + per_thread_state[tid + 128] +\n        per_thread_state[tid + 160] + per_thread_state[tid + 192] +\n        per_thread_state[tid + 224];\n    }\n    \n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    \n    if (tid == 0) {\n        AccT sum = 0.0;\n        for (ushort i = 0; i < 32; ++i) {\n            sum += per_thread_state[i];\n        }\n        sum /= (in.get_width() * in.get_height());\n        per_thread_state[0] = sum;\n    }\n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    // Broadcast to all threads.\n    const AccT mean = per_thread_state[0];\n    \n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    \n    per_thread_state[tid] = 0;\n    for (ushort y = gid.y; y < in.get_height(); y += tcount.y) {\n        for (ushort x = gid.x; x < in.get_width(); x += tcount.x) {\n            AccT delta = static_cast<AccT>(in.read(ushort2(x, y), gid.z)) - mean;\n            per_thread_state[tid] += delta * delta;\n        }\n    }\n    \n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    \n    // 256 -> 32 reduction\n    if (tid < 32) {\n        per_thread_state[tid] += per_thread_state[tid + 32] + per_thread_state[tid + 64] +\n        per_thread_state[tid + 96] + per_thread_state[tid + 128] +\n        per_thread_state[tid + 160] + per_thread_state[tid + 192] +\n        per_thread_state[tid + 224];\n    }\n    \n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    \n    if (tid == 0) {\n        AccT sum = 0.0;\n        for (ushort i = 0; i < 32; ++i) {\n            sum += per_thread_state[i];\n        }\n        sum /= (in.get_width() * in.get_height());\n        per_thread_state[0] = 1.0 / sqrt(max(sum, AccT(1e-5, 1e-5, 1e-5, 1e-5)) + 1.0e-5);\n    }\n    \n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    // Broadcast to all threads.\n    const AccT inv_var = per_thread_state[0];\n    \n    const AccT c_weights = static_cast<AccT>(weights[c]);\n    const AccT c_bias = static_cast<AccT>(bias[c]);\n    \n    const AccT scale = inv_var * c_weights;\n    const AccT shift = c_bias - mean * scale;\n    \n    half4 w;\n    if (instance_norm_has_prelu) {\n        w = channel_shared ? half4(preluWeights[0][0]) : preluWeights[c];\n    }\n    for (ushort y = gid.y; y < in.get_height(); y += tcount.y) {\n        for (ushort x = gid.x; x < in.get_width(); x += tcount.x) {\n            half4 scaled =\n            static_cast<half4>(static_cast<AccT>(in.read(ushort2(x, y), gid.z)) * scale + shift);\n            if (instance_norm_has_prelu) {\n                scaled = select(scaled * w, scaled, scaled > 0.0h);\n            }\n            out.write(scaled, ushort2(x, y), gid.z);\n        }\n    }\n}\n\n// One block per texture.\n// 256 threads per block.\nkernel void instance_norm_nonarray(\n                                   constant half4* weights[[buffer(0)]],\n                                   constant half4* bias[[buffer(1)]],\n                                   constant half4* preluWeights[[ buffer(2), function_constant(instance_norm_has_prelu) ]],\n                                   texture2d<half, access::read> in[[texture(0)]],\n                                   texture2d<half, access::write> out[[texture(1)]],\n                                   ushort3 gid[[thread_position_in_grid]],\n                                   ushort tid[[thread_index_in_threadgroup]],\n                                   ushort3 tcount[[threads_per_threadgroup]]) {\n    // const ushort C = ushort_arg_0;\n    const ushort S = ushort_arg_1;\n    const bool channel_shared = S == 1;\n    constexpr ushort THREADGROUP_SIZE = 256;\n    \n    threadgroup AccT per_thread_state[THREADGROUP_SIZE];\n    // Each block handles a single texture.\n    per_thread_state[tid] = 0;\n    for (ushort y = gid.y; y < in.get_height(); y += tcount.y) {\n        for (ushort x = gid.x; x < in.get_width(); x += tcount.x) {\n            per_thread_state[tid] += static_cast<AccT>(in.read(ushort2(x, y)));\n        }\n    }\n    \n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    \n    // 256 -> 32 reduction\n    if (tid < 32) {\n        per_thread_state[tid] += per_thread_state[tid + 32] + per_thread_state[tid + 64] +\n        per_thread_state[tid + 96] + per_thread_state[tid + 128] +\n        per_thread_state[tid + 160] + per_thread_state[tid + 192] +\n        per_thread_state[tid + 224];\n    }\n    \n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    \n    if (tid == 0) {\n        AccT sum = 0.0;\n        for (ushort i = 0; i < 32; ++i) {\n            sum += per_thread_state[i];\n        }\n        sum /= (in.get_width() * in.get_height());\n        per_thread_state[0] = sum;\n    }\n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    // Broadcast to all threads.\n    const AccT mean = per_thread_state[0];\n    \n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    \n    per_thread_state[tid] = 0;\n    for (ushort y = gid.y; y < in.get_height(); y += tcount.y) {\n        for (ushort x = gid.x; x < in.get_width(); x += tcount.x) {\n            AccT delta = static_cast<AccT>(in.read(ushort2(x, y))) - mean;\n            per_thread_state[tid] += delta * delta;\n        }\n    }\n    \n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    \n    // 256 -> 32 reduction\n    if (tid < 32) {\n        per_thread_state[tid] += per_thread_state[tid + 32] + per_thread_state[tid + 64] +\n        per_thread_state[tid + 96] + per_thread_state[tid + 128] +\n        per_thread_state[tid + 160] + per_thread_state[tid + 192] +\n        per_thread_state[tid + 224];\n    }\n    \n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    \n    if (tid == 0) {\n        AccT sum = 0.0;\n        for (ushort i = 0; i < 32; ++i) {\n            sum += per_thread_state[i];\n        }\n        sum /= (in.get_width() * in.get_height());\n        per_thread_state[0] = 1.0 / sqrt(max(sum, AccT(1e-5, 1e-5, 1e-5, 1e-5)) + 1.0e-5);\n    }\n    \n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    // Broadcast to all threads.\n    const AccT inv_var = per_thread_state[0];\n    \n    const AccT c_weights = static_cast<AccT>(weights[0]);\n    const AccT c_bias = static_cast<AccT>(bias[0]);\n    \n    const AccT scale = inv_var * c_weights;\n    const AccT shift = c_bias - mean * scale;\n    \n    half4 w;\n    if (instance_norm_has_prelu) {\n        w = channel_shared ? half4(preluWeights[0][0]) : preluWeights[0];\n    }\n    for (ushort y = gid.y; y < in.get_height(); y += tcount.y) {\n        for (ushort x = gid.x; x < in.get_width(); x += tcount.x) {\n            half4 scaled = static_cast<half4>(static_cast<AccT>(in.read(ushort2(x, y))) * scale + shift);\n            if (instance_norm_has_prelu) {\n                scaled = select(scaled * w, scaled, scaled > 0.0h);\n            }\n            out.write(scaled, ushort2(x, y));\n        }\n    }\n}\n\nkernel void copy_nchw_to_metal(constant float* in[[buffer(0)]],\n                               texture2d_array<half, access::write> out[[texture(0)]],\n                               ushort3 gid[[thread_position_in_grid]]) {\n    const ushort C = ushort_arg_0;\n    const ushort H = ushort_arg_1;\n    const ushort W = ushort_arg_2;\n    if (gid.x >= W || gid.y >= H) {\n        return;\n    }\n    \n    const ushort n = gid.z / divRoundUp(C, 4);\n    const ushort c = gid.z - n * divRoundUp(C, 4);\n    \n    // TODO: are the `else` branches needed?\n    // TODO: trick the optimizer for case where C == 4?\n#define CHW_TO_CHWP4(idx, n, c_, h, w)                                     \\\nif ((c_) < C) {                                                          \\\ntrns[idx] = in[n * H * W * C + int(c_) * H * W + int(h) * W + int(w)]; \\\n} else {                                                                 \\\ntrns[idx] = 0.0h;                                                      \\\n}\n    \n    half4 trns;\n    CHW_TO_CHWP4(0, n, c * 4 + 0, gid.y, gid.x);\n    CHW_TO_CHWP4(1, n, c * 4 + 1, gid.y, gid.x);\n    CHW_TO_CHWP4(2, n, c * 4 + 2, gid.y, gid.x);\n    CHW_TO_CHWP4(3, n, c * 4 + 3, gid.y, gid.x);\n#undef CHW_TO_CHWP4\n    \n    out.write(trns, gid.xy, gid.z);\n}\n\nkernel void copy_nchw_to_metal_nonarray(constant float* in[[buffer(0)]],\n                                        texture2d<half, access::write> out[[texture(0)]],\n                                        ushort2 gid[[thread_position_in_grid]]) {\n    const ushort C = ushort_arg_0;\n    const ushort H = ushort_arg_1;\n    const ushort W = ushort_arg_2;\n    \n    if (gid.x >= W || gid.y >= H) {\n        return;\n    }\n    \n    half4 trns;\n    // TODO: are the `else` branches needed?\n    // TODO: trick the optimizer for case where C % 4 == 0?\n    \n#define CHW_TO_CHWP4(idx, c, h, w)                        \\\nif ((c) < C) {                                          \\\ntrns[idx] = in[int(c) * H * W + int(h) * W + int(w)]; \\\n} else {                                                \\\ntrns[idx] = 0.0h;                                     \\\n}\n    \n    CHW_TO_CHWP4(0, 0, gid.y, gid.x);\n    CHW_TO_CHWP4(1, 1, gid.y, gid.x);\n    CHW_TO_CHWP4(2, 2, gid.y, gid.x);\n    CHW_TO_CHWP4(3, 3, gid.y, gid.x);\n#undef CHW_TO_CHWP4\n    \n    out.write(trns, gid.xy);\n}\n\nkernel void copy_metal_to_nchw(texture2d_array<half, access::read> in[[texture(0)]],\n                               device float* out[[buffer(0)]],\n                               ushort3 gid[[thread_position_in_grid]]) {\n    const ushort C = ushort_arg_0;\n    const ushort H = ushort_arg_1;\n    const ushort W = ushort_arg_2;\n    \n    if (gid.x >= W || gid.y >= H) {\n        return;\n    }\n    const ushort n = gid.z / divRoundUp(C, 4);\n    const ushort c = gid.z - n * divRoundUp(C, 4);\n    \n    half4 cs = in.read(gid.xy, gid.z);\n    \n#define CHWP4_TO_CHW(idx, n, c_, h, w)                                    \\\nif ((c_) < C) {                                                         \\\nout[n * H * W * C + int(c_) * H * W + int(h) * W + int(w)] = cs[idx]; \\\n}\n    \n    CHWP4_TO_CHW(0, n, c * 4 + 0, gid.y, gid.x);\n    CHWP4_TO_CHW(1, n, c * 4 + 1, gid.y, gid.x);\n    CHWP4_TO_CHW(2, n, c * 4 + 2, gid.y, gid.x);\n    CHWP4_TO_CHW(3, n, c * 4 + 3, gid.y, gid.x);\n#undef CHWP4_TO_CHW\n}\n\nkernel void copy_metal_to_nchw_nonarray(texture2d<half, access::read> in[[texture(0)]],\n                                        device float* out[[buffer(0)]],\n                                        ushort2 gid[[thread_position_in_grid]]) {\n    const ushort C = ushort_arg_0;\n    const ushort H = ushort_arg_1;\n    const ushort W = ushort_arg_2;\n    \n    if (gid.x >= W || gid.y >= H) {\n        return;\n    }\n    \n    half4 cs = in.read(gid.xy);\n    \n#define CHWP4_TO_CHW(idx, c, h, w)                       \\\nif ((c) < C) {                                         \\\nout[int(c) * H * W + int(h) * W + int(w)] = cs[idx]; \\\n}\n    \n    CHWP4_TO_CHW(0, 0, gid.y, gid.x);\n    CHWP4_TO_CHW(1, 1, gid.y, gid.x);\n    CHWP4_TO_CHW(2, 2, gid.y, gid.x);\n    CHWP4_TO_CHW(3, 3, gid.y, gid.x);\n#undef CHWP4_TO_CHW\n}\n\nkernel void convtranspose_upscale(texture2d_array<half, access::read> in[[texture(0)]],\n                                  texture2d_array<half, access::write> out[[texture(1)]],\n                                  ushort3 gid[[thread_position_in_grid]]) {\n    // All resolved at compile time.\n    // Assume symmetric kernel/stride/pad for now.\n    const ushort kernel_ = ushort_arg_0;\n    const ushort stride = ushort_arg_1;\n    const ushort pad = ushort_arg_2;\n    \n    half4 zero(0.0h, 0.0h, 0.0h, 0.0h);\n    \n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n        return;\n    }\n    const ushort2 gid_ = gid.xy;\n    if (gid.x < kernel_ - 1 - pad || gid.y < kernel_ - 1 - pad) {\n        out.write(zero, gid_, gid.z);\n        return;\n    }\n    \n    if (((gid.x - (kernel_ - 1 - pad)) % stride == 0) &&\n        ((gid.y - (kernel_ - 1 - pad)) % stride == 0)) {\n        ushort2 in_pos((gid.x - (kernel_ - 1 - pad)) / stride, (gid.y - (kernel_ - 1 - pad)) / stride);\n        \n        if (in_pos.x < in.get_width() && in_pos.y < in.get_height()) {\n            half4 input = in.read(in_pos, gid.z);\n            out.write(input, gid_, gid.z);\n        } else {\n            out.write(zero, gid_, gid.z);\n        }\n    } else {\n        out.write(zero, gid_, gid.z);\n    }\n}\n\nconstant bool has_in_arr = (ushort_arg_7 > 1 || ushort_arg_0 * ushort_arg_1 * ushort_arg_6 > 4);\nconstant bool has_out_arr = (ushort_arg_7 > 1 || ushort_arg_6 > 4);\nconstant bool has_in_tex = (!has_in_arr);\nconstant bool has_out_tex = (!has_out_arr);\n\nkernel void col2im(\n                   texture2d_array<half, access::read> ina[[ texture(0), function_constant(has_in_arr) ]],\n                   texture2d<half, access::read> in[[ texture(0), function_constant(has_in_tex) ]],\n                   texture2d_array<half, access::write> outa[[ texture(1), function_constant(has_out_arr) ]],\n                   texture2d<half, access::write> out[[ texture(1), function_constant(has_out_tex) ]],\n                   constant half4* bias[[buffer(0)]],\n                   ushort3 gid[[thread_position_in_grid]]) {\n    if (has_out_tex) {\n      if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n        return;\n      }\n    } else {\n      if (gid.x >= outa.get_width() || gid.y >= outa.get_height()) {\n        return;\n      }\n    }\n    const ushort kernel_h = ushort_arg_0;\n    const ushort kernel_w = ushort_arg_1;\n    const ushort stride_h = ushort_arg_2;\n    const ushort stride_w = ushort_arg_3;\n    const ushort pad_l = ushort_arg_4;\n    const ushort pad_t = ushort_arg_5;\n    const ushort C = ushort_arg_6;\n    //  const int N = ushort_arg_7;\n    const ushort height_col = ushort_arg_8; \n    const ushort width_col = ushort_arg_9;\n    \n    const ushort n = gid.z / divRoundUp(C, 4);\n    const ushort c = gid.z - n * divRoundUp(C, 4);\n    \n    const ushort w = gid.x + pad_l;\n    const ushort h = gid.y + pad_t;\n    \n    // compute the start and end of the output\n    const ushort w_col_start = (w < kernel_w) ? 0 : (w - kernel_w) / stride_w + 1;\n    const ushort w_col_end = min(ushort(w / stride_w + 1), ushort(width_col));\n    const ushort h_col_start = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const ushort h_col_end = min(ushort(h / stride_h + 1), ushort(height_col));\n    \n    float4 val = static_cast<float4>(bias[c]);\n    for (ushort h_col = h_col_start; h_col < h_col_end; ++h_col) {\n        for (ushort w_col = w_col_start; w_col < w_col_end; ++w_col) {\n            const ushort w_k = w - w_col * stride_w;\n            const ushort h_k = h - h_col * stride_h;\n            \n            // layout is essentially: [N][K][K][C][H][W]\n            // - where the divRoundUp(K * K * C, 4) channels are interleaved as usual.\n            // Thus, it's actually [N][divRoundUp(K * K * C, 4)][H][W].\u0013\n            \n            // If C % 4 is not zero, then we have to play some games via partial indexing.\n            // TODO: is it worth optimizing this loop via padding in C?\n            if (C % 4 == 0) {\n                ushort c_col = n * kernel_h * kernel_w * divRoundUp(C, 4) +\n                h_k * kernel_w * divRoundUp(C, 4) + w_k * divRoundUp(C, 4) + c;\n                if (has_in_arr) {\n                    val += static_cast<float4>(ina.read(ushort2(w_col, h_col), c_col));\n                }\n                if (has_in_tex) {\n                    val += static_cast<float4>(in.read(ushort2(w_col, h_col), c_col));\n                }\n            } else {\n                half4 components(0, 0, 0, 0);\n                for (auto i = 0; i < 4; ++i) {\n                    ushort c_col_i = n * divRoundUp(kernel_h * kernel_w * C, 4) * 4 + h_k * kernel_w * C +\n                    w_k * C + c * 4 + i;\n                    ushort c_col_i_z = c_col_i / 4;\n                    ushort c_col_i_off = c_col_i - c_col_i_z * 4;\n                    if (has_in_arr) {\n                        components[i] = ina.read(ushort2(w_col, h_col), c_col_i_z)[c_col_i_off];\n                    }\n                    if (has_in_tex) {\n                        components[i] = in.read(ushort2(w_col, h_col))[c_col_i_off];\n                    }\n                }\n                val += static_cast<float4>(components);\n            }\n        }\n    }\n    if (has_out_arr) {\n        outa.write(static_cast<half4>(val), gid.xy, gid.z);\n    }\n    if (has_out_tex) {\n        out.write(static_cast<half4>(val), gid.xy);\n    }\n}\n\nkernel void preprocess_stylizer(device uchar4* in[[buffer(0)]],\n                                constant half* mean[[buffer(1)]],\n                                constant half4* noise[[buffer(2)]],\n                                texture2d<half, access::write> out[[texture(0)]],\n                                ushort2 gid[[thread_position_in_grid]]) {\n    \n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n        return;\n    }\n    const ushort noise_size = ushort_arg_0;\n    \n    half4 mean_half(mean[0], mean[1], mean[2], 0.0h);\n    uint input_noise_idx = ((uint)out.get_width() * (uint)gid.y + (uint)gid.x) % (noise_size / 4);\n    const half4 input_noise = noise[input_noise_idx];\n    const uint W = out.get_width();\n#define in_at(h, w) in[(uint)(h)*W + (uint)(w)]\n    uchar4 input = in_at(gid.y, gid.x);\n#undef in_at\n    half4 input_half = static_cast<half4>(input);\n    out.write(input_half - mean_half + input_noise, gid);\n}\n\nkernel void deprocess_stylizer(texture2d<half, access::read> in[[texture(0)]],\n                               device uchar4* out[[buffer(0)]],\n                               constant half* mean[[buffer(1)]],\n                               ushort2 gid[[thread_position_in_grid]]) {\n    if (gid.x >= in.get_width() || gid.y >= in.get_height()) {\n        return;\n    }\n    \n    half4 value = in.read(gid);\n    \n    half4 mean_h(mean[0], mean[1], mean[2], 0.0h);\n    half4 min_h(0.0h, 0.0h, 0.0h, 255.0h);\n    half4 max_h(255.0h, 255.0h, 255.0h, 255.0h);\n    half4 clamped = clamp(value + mean_h, min_h, max_h);\n    const uint W = in.get_width();\n#define out_at(h, w, v) out[(uint)(h)*W + (uint)(w)] = (v)\n    out_at(gid.y, gid.x, static_cast<uchar4>(clamped));\n#undef out_at\n}\n\nkernel void reflection_padding_nonarray(texture2d<half, access::read> in[[texture(0)]],\n                                        texture2d<half, access::write> out[[texture(1)]],\n                                        ushort2 gid[[thread_position_in_grid]]) {\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n        return;\n    }\n    ushort H = in.get_height();\n    ushort PH = out.get_height();\n    \n    // Note: we assume symmetric padding on H/W here, which is verified\n    // in the calling code.\n    ushort pad_h = (PH - H) / 2;\n    ushort W = in.get_width();\n    ushort PW = out.get_width();\n    ushort pad_w = (PW - W) / 2;\n    \n    short h = short(gid.y) - short(pad_h);\n    h = max(h, short(-h));\n    h = min(h, short(2 * H - h - 2));\n    \n    short w = short(gid.x) - short(pad_w);\n    w = max(w, short(-w));\n    w = min(w, short(2 * W - w - 2));\n    \n    ushort2 inid(w, h);\n    out.write(in.read(inid), gid);\n}\n\nkernel void reflection_padding(texture2d_array<half, access::read> in[[texture(0)]],\n                               texture2d_array<half, access::write> out[[texture(1)]],\n                               ushort3 gid[[thread_position_in_grid]]) {\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n        return;\n    }\n    ushort H = in.get_height();\n    ushort PH = out.get_height();\n    \n    // Note: we assume symmetric padding on H/W here, which is verified\n    // in the calling code.\n    ushort pad_h = (PH - H) / 2;\n    ushort W = in.get_width();\n    ushort PW = out.get_width();\n    ushort pad_w = (PW - W) / 2;\n    \n    short h = short(gid.y) - short(pad_h);\n    h = max(h, short(-h));\n    h = min(h, short(2 * H - h - 2));\n    \n    short w = short(gid.x) - short(pad_w);\n    w = max(w, short(-w));\n    w = min(w, short(2 * W - w - 2));\n    \n    ushort2 inid(w, h);\n    \n    out.write(in.read(inid, gid.z), gid.xy, gid.z);\n}\n\nkernel void bilinear_upsample(texture2d<half, access::sample> in[[texture(0)]],\n                              texture2d<half, access::write> out[[texture(1)]],\n                              ushort2 gid[[thread_position_in_grid]]) {\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n        return;\n    }\n    ushort2 src = gid / 2;\n    constexpr sampler sampler(address::clamp_to_edge, filter::linear, coord::pixel);\n    half4 value = in.sample(sampler, static_cast<float2>(src));\n    out.write(value, gid);\n}\n\nconstant bool in0_is_tex = ushort_arg_0 <= 1 && ushort_arg_1 <= 4;\nconstant bool in0_is_arr = !in0_is_tex;\n\nkernel void elementwise_mul(texture2d<half, access::read> in0[[texture(0), function_constant(in0_is_tex)]],\n                            texture2d_array<half, access::read> ina0[[texture(0), function_constant(in0_is_arr)]],\n                            texture2d<half, access::write> out[[texture(2), function_constant(in0_is_tex)]],\n                            texture2d_array<half, access::write> outa[[texture(2), function_constant(in0_is_arr)]],\n                            constant float* in1[[buffer(1)]],\n                            ushort3 gid[[thread_position_in_grid]]) {\n  ushort last_dim = ushort_arg_2;\n  ushort idx;\n  if (in0_is_tex) {\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n      return;\n    }\n    idx = gid.y * out.get_width() + gid.x;\n  } else {\n    if (gid.x >= outa.get_width() || gid.y >= outa.get_height()) {\n      return;\n    }\n    idx = gid.y * outa.get_width() + gid.x;\n  }\n  ushort2 gid_ = gid.xy;\n  if (in0_is_tex) {\n    out.write(in0.read(gid_) * in1[idx % last_dim], gid_);\n  } else {\n    outa.write(ina0.read(gid_, gid.z) * in1[idx % last_dim], gid_, gid.z);\n  }\n}\n\nkernel void elementwise_sub(texture2d<half, access::read> in0[[texture(0), function_constant(in0_is_tex)]],\n                            texture2d_array<half, access::read> ina0[[texture(0), function_constant(in0_is_arr)]],\n                            texture2d<half, access::write> out[[texture(2), function_constant(in0_is_tex)]],\n                            texture2d_array<half, access::write> outa[[texture(2), function_constant(in0_is_arr)]],\n                            constant float* in1[[buffer(1)]],\n                            ushort3 gid[[thread_position_in_grid]]) {\n  ushort last_dim = ushort_arg_2;\n  ushort idx;\n  if (in0_is_tex) {\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n      return;\n    }\n    idx = gid.y * out.get_width() + gid.x;\n  } else {\n    if (gid.x >= outa.get_width() || gid.y >= outa.get_height()) {\n      return;\n    }\n    idx = gid.y * outa.get_width() + gid.x;\n  }\n  ushort2 gid_ = gid.xy;\n  if (in0_is_tex) {\n    out.write(in0.read(gid_) - in1[idx % last_dim], gid_);\n  } else {\n    outa.write(ina0.read(gid_, gid.z) - in1[idx % last_dim], gid_, gid.z);\n  }\n}\n\nkernel void elementwise_add_nonarray(texture2d<half, access::read> in0[[texture(0)]],\n                                     texture2d<half, access::read> in1[[texture(1)]],\n                                     texture2d<half, access::write> out[[texture(2)]],\n                                     ushort2 gid[[thread_position_in_grid]]) {\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n        return;\n    }\n    out.write(in0.read(gid) + in1.read(gid), gid);\n}\n\nkernel void elementwise_add(texture2d_array<half, access::read> in0[[texture(0)]],\n                            texture2d_array<half, access::read> in1[[texture(1)]],\n                            texture2d_array<half, access::write> out[[texture(2)]],\n                            ushort3 gid[[thread_position_in_grid]]) {\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n        return;\n    }\n    ushort2 gid_ = gid.xy;\n    out.write(in0.read(gid_, gid.z) + in1.read(gid_, gid.z), gid_, gid.z);\n}\n\nconstant bool has_in0_arg = (ushort_arg_0 > 0);\nconstant bool has_in1_arg = (ushort_arg_1 > 0);\nconstant bool has_in2_arg = (ushort_arg_2 > 0);\nconstant bool has_in3_arg = (ushort_arg_3 > 0);\n\nconstant bool has_in0_tex = (has_in0_arg && ushort_arg_0 <= 4 && ushort_arg_5 <= 1);\nconstant bool has_in1_tex = (has_in1_arg && ushort_arg_1 <= 4 && ushort_arg_5 <= 1);\nconstant bool has_in2_tex = (has_in2_arg && ushort_arg_2 <= 4 && ushort_arg_5 <= 1);\nconstant bool has_in3_tex = (has_in3_arg && ushort_arg_3 <= 4 && ushort_arg_5 <= 1);\n\nconstant bool has_in0_array = (has_in0_arg && !has_in0_tex);\nconstant bool has_in1_array = (has_in1_arg && !has_in1_tex);\nconstant bool has_in2_array = (has_in2_arg && !has_in2_tex);\nconstant bool has_in3_array = (has_in3_arg && !has_in3_tex);\n\nconstant bool concat_has_out_tex = (ushort_arg_4 <= 4 && ushort_arg_5 <= 1);\nconstant bool concat_has_out_array = !concat_has_out_tex;\n\ninline ushort idx_3(ushort z, ushort C0, ushort C1, ushort C2, ushort C3) {\n  if (z < C0) {\n    return 0;\n  }\n  if (z < (C0 + C1)) {\n    return 1;\n  }\n  if (z < (C0 + C1 + C2)) {\n    return 2;\n  }\n  return 3;\n}\n\ninline ushort idx_2(ushort z, ushort C0, ushort C1, ushort C2) {\n  if (z < C0) {\n    return 0;\n  }\n  if (z < (C0 + C1)) {\n    return 1;\n  }\n  return 2;\n}\n\ninline ushort idx_1(ushort z, ushort C0, ushort C1) {\n  if (z < C0) {\n    return 0;\n  } else {\n    return 1;\n  }\n}\n\ninline ushort idx_0(ushort z, ushort C0) { return 0; }\n\n// in a texture_array with size C, find the offset for image N at plane c.\ninline constexpr ushort z_off(ushort n, ushort c, ushort C) { return n * divRoundUp(C, 4) + c / 4; }\n\nkernel void concat(\n                   texture2d<half, access::read> in0[[ texture(0), function_constant(has_in0_tex) ]],\n                   texture2d<half, access::read> in1[[ texture(1), function_constant(has_in1_tex) ]],\n                   texture2d<half, access::read> in2[[ texture(2), function_constant(has_in2_tex) ]],\n                   texture2d<half, access::read> in3[[ texture(3), function_constant(has_in3_tex) ]],\n                   texture2d_array<half, access::read> ina0[[ texture(0), function_constant(has_in0_array) ]],\n                   texture2d_array<half, access::read> ina1[[ texture(1), function_constant(has_in1_array) ]],\n                   texture2d_array<half, access::read> ina2[[ texture(2), function_constant(has_in2_array) ]],\n                   texture2d_array<half, access::read> ina3[[ texture(3), function_constant(has_in3_array) ]],\n                   texture2d<half, access::write> out[[texture(5),\n                                                       function_constant(concat_has_out_tex) ]],\n                   texture2d_array<half, access::write> outa[[texture(5),\n                                                              function_constant(concat_has_out_array) ]],\n                   ushort3 gid[[thread_position_in_grid]]) {\n  if (concat_has_out_tex) {\n    if (gid.x >= out.get_width() || gid.y >= out.get_height()) {\n      return;\n    }\n  } else {\n    if (gid.x >= outa.get_width() || gid.y >= outa.get_height()) {\n      return;\n    }\n  }\n  \n  const ushort C0 = ushort_arg_0;\n  const ushort C1 = ushort_arg_1;\n  const ushort C2 = ushort_arg_2;\n  const ushort C3 = ushort_arg_3;\n  const ushort C = C0 + C1 + C2 + C3;\n  const ushort n = gid.z / divRoundUp(C, 4);\n  const ushort c = gid.z - n * divRoundUp(C, 4);\n  // Fill channel 4*c to 4*(c+1) of nth image of output\n  \n  ushort2 gid_ = ushort2(gid.x, gid.y);\n  half4 value;\n  \n  for (int off = 0; off < 4; ++off) {\n    ushort cur_channel = c * 4 + off;\n    ushort cur_idx = 0;\n    if (cur_channel >= C) {\n      break;\n    }\n    if (has_in3_arg) {\n      cur_idx = idx_3(cur_channel, C0, C1, C2, C3);\n    } else if (has_in2_arg) {\n      cur_idx = idx_2(cur_channel, C0, C1, C2);\n    } else if (has_in1_arg) {\n      cur_idx = idx_1(cur_channel, C0, C1);\n    } else if (has_in0_arg) {\n      cur_idx = idx_0(cur_channel, C0);\n    } else {\n      // never reached.\n      cur_idx = 0;\n    }\n    ushort src_off = 0;\n    switch (cur_idx) {\n      case 0:\n        src_off = cur_channel % 4;\n        break;\n      case 1:\n        src_off = (cur_channel - C0) % 4;\n        break;\n      case 2:\n        src_off = (cur_channel - (C0 + C1)) % 4;\n        break;\n      case 3:\n        src_off = (cur_channel - (C0 + C1 + C2)) % 4;\n        break;\n    }\n    // try to see if we can only issue one read op for the 4 values\n    bool fast_path = false;\n    if (off == 0 && src_off == 0 && (cur_channel + 3) < C) {\n      ushort last_idx = 0;\n      if (has_in3_arg) {\n        last_idx = idx_3(cur_channel + 3, C0, C1, C2, C3);\n      } else if (has_in2_arg) {\n        last_idx = idx_2(cur_channel + 3, C0, C1, C2);\n      } else if (has_in1_arg) {\n        last_idx = idx_1(cur_channel + 3, C0, C1);\n      } else if (has_in0_arg) {\n        last_idx = idx_0(cur_channel + 3, C0);\n      } else {\n        // never reached.\n        last_idx = 0;\n      }\n      if (cur_idx == last_idx) {\n        fast_path = true;\n      }\n    }\n    switch (cur_idx) {\n      case 0: {\n        if (has_in0_tex) {\n          if (fast_path) {\n            value = in0.read(gid_);\n          } else {\n            value[off] = in0.read(gid_)[src_off];\n          }\n        }\n        if (has_in0_array) {\n          if (fast_path) {\n            value = ina0.read(gid_, z_off(n, cur_channel, C0));\n          } else {\n            value[off] = ina0.read(gid_, z_off(n, cur_channel, C0))[src_off];\n          }\n        }\n        break;\n      }\n      case 1: {\n        if (has_in1_tex) {\n          if (fast_path) {\n            value = in1.read(gid_);\n          } else {\n            value[off] = in1.read(gid_)[src_off];\n          }\n        }\n        if (has_in1_array) {\n          if (fast_path) {\n            value = ina1.read(gid_, z_off(n, cur_channel - C0, C1));\n          } else {\n            value[off] = ina1.read(gid_, z_off(n, cur_channel - C0, C1))[src_off];\n          }\n        }\n        break;\n      }\n      case 2: {\n        if (has_in2_tex) {\n          if (fast_path) {\n            value = in2.read(gid_);\n          } else {\n            value[off] = in2.read(gid_)[src_off];\n          }\n        }\n        if (has_in2_array) {\n          if (fast_path) {\n            value = ina2.read(gid_, z_off(n, cur_channel - (C0 + C1), C2));\n          } else {\n            value[off] = ina2.read(gid_, z_off(n, cur_channel - (C0 + C1), C2))[src_off];\n          }\n        }\n        break;\n      }\n      case 3: {\n        if (has_in3_tex) {\n          if (fast_path) {\n            value = in3.read(gid_);\n          } else {\n            value[off] = in3.read(gid_)[src_off];\n          }\n        }\n        if (has_in3_array) {\n          if (fast_path) {\n            value = ina3.read(gid_, z_off(n, cur_channel - (C0 + C1 + C2), C3));\n          } else {\n            value[off] = ina3.read(gid_, z_off(n, cur_channel - (C0 + C1 + C2), C3))[src_off];\n          }\n        }\n        break;\n      }\n    }\n    if (fast_path) {\n      break;\n    }\n  }\n  if (concat_has_out_tex) {\n    out.write(value, gid_, gid.z);\n  } else {\n    outa.write(value, gid_, gid.z);\n  }\n}\n\nusing RoIT = half;\nusing RoIT4 = half4;\nconstant bool rw_has_in_arr = (ushort_arg_3 > 1 ||  ushort_arg_2 > 4);\nconstant bool rw_has_out_arr = (ushort_arg_4 > 1 || ushort_arg_2 > 4);\nconstant bool rw_has_in_tex = (!rw_has_in_arr);\nconstant bool rw_has_out_tex = (!rw_has_out_arr);\nkernel void roi_warp(texture2d_array<half, access::sample> ina[[texture(0), function_constant(rw_has_in_arr)]],\n                     texture2d<half, access::sample> in[[texture(0), function_constant(rw_has_in_tex)]],\n                     texture2d_array<half, access::write> outa[[texture(1), function_constant(rw_has_out_arr)]],\n                     texture2d<half, access::write> out[[texture(1), function_constant(rw_has_out_tex)]],\n                     constant half4* rois[[buffer(0)]],\n                     ushort3 gid[[thread_position_in_grid]]) {\n  ushort out_width, out_height;\n  if (rw_has_out_arr) {\n    out_width = outa.get_width();\n    out_height = outa.get_height();\n  } else {\n    out_width = out.get_width();\n    out_height = out.get_height();\n  }\n  if (gid.x >= out_width || gid.y >= out_height) {\n    return;\n  }\n  constexpr sampler s2(coord::pixel, address::clamp_to_edge, filter::linear);\n\n  const half spatial_scale = half(ushort_arg_0) / 10000;\n  const ushort sampling_ratio = ushort_arg_1;\n  const ushort C = ushort_arg_2;\n  const ushort pw = gid.x;\n  const ushort ph = gid.y;\n  const ushort n = gid.z / divRoundUp(C, 4);\n  const ushort c = gid.z % divRoundUp(C, 4);\n\n  const RoIT4 roi_scaled = rois[n] * spatial_scale;\n  const RoIT roi_start_w = roi_scaled[0];\n  const RoIT roi_start_h = roi_scaled[1];\n  const RoIT roi_end_w = roi_scaled[2];\n  const RoIT roi_end_h = roi_scaled[3];\n\n  // Force malformed ROIs to be 1x1\n  const RoIT roi_width = max(roi_end_w - roi_start_w, (RoIT)1.);\n  const RoIT roi_height = max(roi_end_h - roi_start_h, (RoIT)1.);\n\n  const RoIT bin_size_h = static_cast<RoIT>(roi_height) / static_cast<RoIT>(out_height);\n  const RoIT bin_size_w = static_cast<RoIT>(roi_width) / static_cast<RoIT>(out_width);\n  const ushort roi_bin_grid_h = sampling_ratio > 0 ? sampling_ratio : ceil(roi_height / static_cast<RoIT>(out_height));\n  const ushort roi_bin_grid_w = sampling_ratio > 0 ? sampling_ratio : ceil(roi_width / static_cast<RoIT>(out_width));\n  const ushort iy_upper = (sampling_ratio > 0) ? roi_bin_grid_h : (roi_bin_grid_h + 1);\n  const ushort ix_upper = (sampling_ratio > 0) ? roi_bin_grid_w : (roi_bin_grid_w + 1);\n\n  const RoIT count = iy_upper * ix_upper;\n\n  RoIT4 output_val = 0.0;\n  for (int iy = 0; iy < iy_upper; iy++) {\n    for (int ix = 0; ix < ix_upper; ix++) {\n      const RoIT y =\n          roi_start_h + ph * bin_size_h + iy * bin_size_h / static_cast<RoIT>(roi_bin_grid_h);\n      const RoIT x =\n          roi_start_w + pw * bin_size_w + ix * bin_size_w / static_cast<RoIT>(roi_bin_grid_w);\n      if (rw_has_in_arr) {\n        output_val += ina.sample(s2, float2(x + 0.5, y + 0.5), c);\n      } else {\n        output_val += in.sample(s2, float2(x + 0.5, y + 0.5));\n      }\n    }\n  }\n  output_val /= count;\n  if (rw_has_out_arr) {\n    outa.write(static_cast<half4>(output_val), gid.xy, gid.z);\n  } else {\n    out.write(static_cast<half4>(output_val), gid.xy);\n  }\n}\n\nkernel void resize_nearest(texture2d_array<half, access::sample> in[[texture(0)]],\n                           texture2d_array<half, access::write> out[[texture(1)]],\n                           ushort3 gid[[thread_position_in_grid]]) {\n    const ushort oH = ushort_arg_0;\n    const ushort oW = ushort_arg_1;\n    if (gid.x >= oW || gid.y >= oH) {\n        return;\n    }\n    const float height_scale = float(ushort_arg_2) / 10000;\n    const float width_scale = float(ushort_arg_3) / 10000;\n    constexpr sampler s(coord::pixel, address::clamp_to_edge, filter::nearest);\n    const int in_y = (int)(gid.y / height_scale);\n    const int in_x = (int)(gid.x / width_scale);\n    out.write(in.sample(s, float2(in_x, in_y), gid.z), gid.xy, gid.z);\n}\n\nkernel void resize_nearest_nonarray(texture2d<half, access::sample> in[[texture(0)]],\n                                    texture2d<half, access::write> out[[texture(1)]],\n                                    ushort2 gid[[thread_position_in_grid]]) {\n    const ushort oH = ushort_arg_0;\n    const ushort oW = ushort_arg_1;\n    if (gid.x >= oW || gid.y >= oH) {\n        return;\n    }\n    const float height_scale = float(ushort_arg_2) / 10000;\n    const float width_scale = float(ushort_arg_3) / 10000;\n    constexpr sampler s(coord::pixel, address::clamp_to_edge, filter::nearest);\n    const int in_y = (int)(gid.y / height_scale);\n    const int in_x = (int)(gid.x / width_scale);\n    out.write(in.sample(s, float2(in_x, in_y)), gid.xy);\n}\n\nkernel void nms(device uint* mask[[buffer(0)]],\n                constant float* proposals[[buffer(1)]],\n                constant int* indices[[buffer(2)]],\n                ushort2 tgid[[threadgroup_position_in_grid]],\n                ushort2 tid[[thread_position_in_threadgroup]]) {\n    const ushort num_proposals = ushort_arg_0;\n    const ushort threads_per_group = ushort_arg_1;\n    float nms_thresh = float(ushort_arg_2) / 10000.0;\n    const ushort global_offset = ushort_arg_3;\n    const ushort row_start = tgid.y;\n    const ushort col_start = tgid.x;\n    const ushort trd_id = tid.x;\n    \n    const short row_size = min(short(32), short(num_proposals - row_start * threads_per_group));\n    const short col_size = min(short(32), short(num_proposals - col_start * threads_per_group));\n    \n    // mask the bit if the IoU between two proposals exceeds the threshold\n    if (trd_id < row_size) {\n        const ushort cur_idx = global_offset + row_start * threads_per_group + trd_id;\n        const ushort offset = indices[cur_idx] * 4;\n        const float4 cur_proposal = float4(\n                                           proposals[offset], proposals[offset + 1], proposals[offset + 2], proposals[offset + 3]);\n        uint cur_mask = 0;\n        ushort group_start = 0; // start index within group\n        if (row_start == col_start) {\n            // if in the same group, start from the next\n            group_start = trd_id + 1;\n        }\n        for (ushort i = group_start; i < col_size; i++) {\n            float4 a = cur_proposal;\n            ushort idx = indices[global_offset + col_start * threads_per_group + i] * 4;\n            float4 b = float4(proposals[idx], proposals[idx + 1], proposals[idx + 2], proposals[idx + 3]);\n            float left = max(a[0], b[0]);\n            float right = min(a[2], b[2]);\n            float top = max(a[1], b[1]);\n            float bottom = min(a[3], b[3]);\n            float width = max(right - left + 1.0, 0.0);\n            float height = max(bottom - top + 1.0, 0.0);\n            float interS = width * height;\n            float Sa = (a[2] - a[0] + 1.0) * (a[3] - a[1] + 1.0);\n            float Sb = (b[2] - b[0] + 1.0) * (b[3] - b[1] + 1.0);\n            float iou = interS / (Sa + Sb - interS);\n            if (iou - nms_thresh > 0) {\n                cur_mask |= 1U << i;\n            }\n        }\n        ushort col_blocks = (num_proposals + threads_per_group - 1) / threads_per_group;\n        mask[cur_idx * col_blocks + col_start] = cur_mask;\n    }\n}\n\n\n)V0G0N\";\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/mpscnn/mpscnn_test.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"caffe2/core/net.h\"\n#pragma once\n\nnamespace caffe2 {\n\nvoid testMPSCNN();\nvoid compareModels(const NetDef& initNet, NetDef predictNet);\nvoid verifyRewrite(const NetDef& initNet, const NetDef& net, std::vector<int> inputDims);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/mpscnn/mpscnn_test.mm",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common.h\"\n\n#if CAFFE2_MOBILE && defined(CAFFE2_USE_MPSCNN_TEST)\n\n#include \"mpscnn_context.h\"\n#include \"mpscnn_graph_mask.h\"\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator_schema.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\n#import <UIKit/UIDevice.h>\n\n#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) \\\n  ([[[UIDevice currentDevice] systemVersion]       \\\n       compare:v                                   \\\n       options:NSNumericSearch] != NSOrderedAscending)\n\nnamespace caffe2 {\n\n/* Utility functions for operator definition */\nvoid add_arg_int(OperatorDef& op, string name, int value) {\n  auto& arg = *(op.add_arg());\n  arg.set_name(name);\n  arg.set_i(value);\n}\n\nvoid add_arg_str(OperatorDef& op, string name, string value) {\n  auto& arg = *(op.add_arg());\n  arg.set_name(name);\n  arg.set_s(value);\n}\n\nvoid add_arg_float(OperatorDef& op, string name, float value) {\n  auto& arg = *(op.add_arg());\n  arg.set_name(name);\n  arg.set_f(value);\n}\n\nvoid add_arg_int_list(\n    OperatorDef& op,\n    std::vector<string> names,\n    std::vector<int> values) {\n  CAFFE_ENFORCE_EQ(names.size(), values.size());\n  for (auto i = 0; i < names.size(); i++) {\n    add_arg_int(op, names[i], values[i]);\n  }\n}\n\nvoid add_arg_str_list(\n    OperatorDef& op,\n    std::vector<string> names,\n    std::vector<string> values) {\n  CAFFE_ENFORCE_EQ(names.size(), values.size());\n  for (auto i = 0; i < names.size(); i++) {\n    add_arg_str(op, names[i], values[i]);\n  }\n}\n\nvoid add_inputs(OperatorDef& op, std::vector<string> inputs) {\n  for (auto i = 0; i < inputs.size(); i++) {\n    op.add_input(inputs[i]);\n  }\n}\n\nvoid add_outputs(OperatorDef& op, std::vector<string> outputs) {\n  for (auto i = 0; i < outputs.size(); i++) {\n    op.add_output(outputs[i]);\n  }\n}\n\nvoid testMPSCNN() {\n  // initialize.\n  getMPSCNNContext();\n\n  {\n    for (const auto C : std::vector<size_t>{1, 2, 3, 4, 8, 11, 12}) {\n      for (const auto H : std::vector<size_t>{1, 7, 15, 39}) {\n        for (const auto W : std::vector<size_t>{1, 7, 15, 39}) {\n          for (const auto N : std::vector<size_t>{1, 2}) {\n            for (const auto BS : std::vector<size_t>{1, 2}) {\n              LOG(INFO) << \"MPSCNNCopyFrom/To Test\";\n              auto mtl = [&](size_t i) {\n                return std::string(\"X_mtl_\") + std::to_string(i);\n              };\n              auto cpu = [&](size_t i) {\n                return std::string(\"X_cpu_\") + std::to_string(i);\n              };\n              auto y_cpu = [&](size_t i) {\n                return std::string(\"Y_cpu_\") + std::to_string(i);\n              };\n\n              Workspace ws;\n              for (auto i = 0; i < N; ++i) {\n                auto* t = ws.CreateBlob(cpu(i))->GetMutable<TensorCPU>();\n                t->Resize(BS, C, H, W);\n                CPUContext ctx;\n                math::RandGaussian<float, CPUContext>(\n                    t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n              }\n\n              NetDef netdef;\n              {\n                auto& op = *(netdef.add_op());\n                op.set_type(\"CopyToMPSCNN\");\n                for (auto i = 0; i < N; ++i) {\n                  op.add_input(cpu(i));\n                  op.add_output(mtl(i));\n                }\n              }\n              {\n                auto& op = *(netdef.add_op());\n                op.set_type(\"CopyFromMPSCNN\");\n                for (auto i = 0; i < N; ++i) {\n                  op.add_input(mtl(i));\n                  op.add_output(y_cpu(i));\n                }\n              }\n\n              ws.RunNetOnce(netdef);\n              for (auto i = 0; i < N; ++i) {\n                const auto& t1 = ws.GetBlob(cpu(i))->Get<TensorCPU>();\n                const auto& t2 = ws.GetBlob(y_cpu(i))->Get<TensorCPU>();\n                CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n                for (auto i = 0; i < t1.size(); ++i) {\n                  // FP16 <-> FP32 round trip.\n                  CHECK_NEAR(t1.data<float>()[i], t2.data<float>()[i], 1e-2);\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  {\n    for (const auto ndim : std::vector<size_t>{1, 2, 3, 4}) {\n      for (const auto N : std::vector<size_t>{1, 2}) {\n        LOG(INFO) << \"MPSCNNCopyFrom/To ndim Test\";\n        auto mtl = [&](size_t i) {\n          return std::string(\"X_mtl_\") + std::to_string(i);\n        };\n        auto cpu = [&](size_t i) {\n          return std::string(\"X_cpu_\") + std::to_string(i);\n        };\n        auto y_cpu = [&](size_t i) {\n          return std::string(\"Y_cpu_\") + std::to_string(i);\n        };\n\n        Workspace ws;\n        for (auto i = 0; i < N; ++i) {\n          auto* t = ws.CreateBlob(cpu(i))->GetMutable<TensorCPU>();\n          switch (ndim) {\n            case 1:\n              t->Resize(5);\n              break;\n            case 2:\n              t->Resize(5, 3);\n              break;\n            case 3:\n              t->Resize(5, 3, 4);\n              break;\n            case 4:\n              t->Resize(5, 3, 4, 2);\n              break;\n          }\n          CPUContext ctx;\n          math::RandGaussian<float, CPUContext>(\n              t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n        }\n\n        NetDef netdef;\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"CopyToMPSCNN\");\n          for (auto i = 0; i < N; ++i) {\n            op.add_input(cpu(i));\n            op.add_output(mtl(i));\n          }\n        }\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"CopyFromMPSCNN\");\n          for (auto i = 0; i < N; ++i) {\n            op.add_input(mtl(i));\n            op.add_output(y_cpu(i));\n          }\n        }\n\n        ws.RunNetOnce(netdef);\n        for (auto i = 0; i < N; ++i) {\n          const auto& t1 = ws.GetBlob(cpu(i))->Get<TensorCPU>();\n          const auto& t2 = ws.GetBlob(y_cpu(i))->Get<TensorCPU>();\n          CAFFE_ENFORCE_EQ(t1.size(), t2.size());\n          for (auto i = 0; i < t1.size(); ++i) {\n            // FP16 <-> FP32 round trip.\n            CHECK_NEAR(t1.data<float>()[i], t2.data<float>()[i], 1e-2);\n          }\n        }\n      }\n    }\n  }\n\n  {\n    for (const auto& batch_size : std::vector<int>{{1, 2}}) {\n      for (const auto& channels : std::vector<int>{{3, 8}}) {\n        LOG(INFO) << \"MPSCNNNormalizePlanarYUV Test: \";\n        Workspace ws;\n        {\n          auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n          t->Resize(batch_size, channels, 8, 13);\n          CPUContext ctx;\n          math::RandGaussian<float, CPUContext>(\n              t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n        }\n\n        {\n          auto* t = ws.CreateBlob(\"mean\")->GetMutable<TensorCPU>();\n          t->Resize(1, channels);\n          CPUContext ctx;\n          math::RandGaussian<float, CPUContext>(\n              t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n        }\n        {\n          auto* t = ws.CreateBlob(\"stddev\")->GetMutable<TensorCPU>();\n          t->Resize(1, channels);\n          CPUContext ctx;\n          math::RandUniform<float, CPUContext>(\n              t->size(), 0.5, 1.5, t->mutable_data<float>(), &ctx);\n        }\n\n        NetDef netdef;\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"CopyToMPSCNN\");\n          op.add_input(\"X_cpu\");\n          op.add_output(\"X_mtl\");\n        }\n\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"MPSCNNNormalizePlanarYUV\");\n          op.add_input(\"X_mtl\");\n          op.add_input(\"mean\");\n          op.add_input(\"stddev\");\n          op.add_output(\"Y_mtl\");\n        }\n\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"CopyFromMPSCNN\");\n          op.add_input(\"Y_mtl\");\n          op.add_output(\"Y_cpu\");\n        }\n\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"NormalizePlanarYUV\");\n          op.add_input(\"X_cpu\");\n          op.add_input(\"mean\");\n          op.add_input(\"stddev\");\n          op.add_output(\"Y_ref\");\n        }\n\n        ws.RunNetOnce(netdef);\n        const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n        const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n        CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n        for (auto i = 0; i < t1.size(); ++i) {\n          // FP16 <-> FP32 round trip, accumulation, etc.\n          const float t1_i = t1.data<float>()[i];\n          const float t2_i = t2.data<float>()[i];\n          CHECK_NEAR(t1_i, t2_i, 0.1);\n        }\n      }\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNInstanceNorm Test\";\n    enum class PreluTy { NONE, CHANNEL, SHARED };\n    for (const auto batchSize : {1, 2}) {\n      for (const auto channels : {3, 8}) {\n        for (const auto prelu :\n             {PreluTy::NONE, PreluTy::CHANNEL, PreluTy::SHARED}) {\n          for (const auto dim : {10, 40}) {\n            Workspace ws;\n            {\n              auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n              t->Resize(batchSize, channels, dim, dim);\n              CPUContext ctx;\n              // Too noisy.\n              math::RandGaussian<float, CPUContext>(\n                  t->size(), 0, 3, t->mutable_data<float>(), &ctx);\n            }\n\n            {\n              auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n              t->Resize(channels);\n              CPUContext ctx;\n              for (auto i = 0; i < t->size(); ++i) {\n                t->mutable_data<float>()[i] = i;\n              }\n              // Too noisy.\n              // math::RandGaussian<float, CPUContext>(t->size(), 0, 1,\n              // t->mutable_data<float>(), &ctx);\n            }\n            {\n              auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n              t->Resize(channels);\n              CPUContext ctx;\n              for (auto i = 0; i < t->size(); ++i) {\n                t->mutable_data<float>()[i] = 8 - 2 * i;\n              }\n              // Too noisy.\n              // math::RandGaussian<float, CPUContext>(t->size(), 0, 1,\n              // t->mutable_data<float>(), &ctx);\n            }\n            {\n              auto* t = ws.CreateBlob(\"pw\")->GetMutable<TensorCPU>();\n              t->Resize(prelu == PreluTy::SHARED ? 1 : channels);\n              CPUContext ctx;\n              // Too noisy.\n              math::RandGaussian<float, CPUContext>(\n                  t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n            }\n\n            NetDef netdef;\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"CopyToMPSCNN\");\n              op.add_input(\"X_cpu\");\n              op.add_output(\"X_mtl\");\n            }\n\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\n                  prelu == PreluTy::NONE ? \"MPSCNNInstanceNorm\"\n                                         : \"MPSCNNInstanceNormPRelu\");\n              op.add_input(\"X_mtl\");\n              op.add_input(\"W\");\n              op.add_input(\"b\");\n              if (prelu != PreluTy::NONE) {\n                op.add_input(\"pw\");\n              }\n              op.add_output(\"Y_mtl\");\n            }\n\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"CopyFromMPSCNN\");\n              op.add_input(\"Y_mtl\");\n              op.add_output(\"Y_cpu\");\n            }\n\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"InstanceNorm\");\n              op.add_input(\"X_cpu\");\n              op.add_input(\"W\");\n              op.add_input(\"b\");\n              auto& arg = *(op.add_arg());\n              arg.set_name(\"order\");\n              arg.set_s(\"NCHW\");\n              op.add_output(\"Y_ref\");\n            }\n\n            if (prelu != PreluTy::NONE) {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"PRelu\");\n              op.add_input(\"Y_ref\");\n              op.add_input(\"pw\");\n              auto& arg = *(op.add_arg());\n              arg.set_name(\"order\");\n              arg.set_s(\"NCHW\");\n              op.add_output(\"Y_ref\");\n            }\n\n            ws.RunNetOnce(netdef);\n            const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n            const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n            CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n            for (auto i = 0; i < t1.size(); ++i) {\n              // FP16 <-> FP32 round trip, accumulation, etc.\n              const float t1_i = t1.data<float>()[i];\n              const float t2_i = t2.data<float>()[i];\n              // Can be larger due to FP errors.\n              constexpr float tol = 5.0e-2;\n              CHECK(std::abs(t1_i - t2_i) <= (tol + tol * std::abs(t1_i)))\n                  << t1_i << \", \" << t2_i;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  {\n    for (const auto& shared : std::vector<bool>{{true, false}}) {\n      for (const auto& array : std::vector<bool>{{true, false}}) {\n        for (const auto& batch_size : std::vector<int>{{1, 2}}) {\n          LOG(INFO) << \"MPSCNNPRelu Test: \" << shared << array << batch_size;\n          Workspace ws;\n          const auto channels = array ? 12 : 3;\n          {\n            auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n            t->Resize(batch_size, channels, 8, 13);\n            CPUContext ctx;\n            math::RandGaussian<float, CPUContext>(\n                t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n          }\n\n          {\n            auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n            t->Resize(shared ? channels : 1);\n            CPUContext ctx;\n            math::RandGaussian<float, CPUContext>(\n                t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n          }\n\n          NetDef netdef;\n          {\n            auto& op = *(netdef.add_op());\n            op.set_type(\"CopyToMPSCNN\");\n            op.add_input(\"X_cpu\");\n            op.add_output(\"X_mtl\");\n          }\n\n          {\n            auto& op = *(netdef.add_op());\n            op.set_type(\"MPSCNNPRelu\");\n            op.add_input(\"X_mtl\");\n            op.add_input(\"b\");\n            op.add_output(\"Y_mtl\");\n          }\n\n          {\n            auto& op = *(netdef.add_op());\n            op.set_type(\"CopyFromMPSCNN\");\n            op.add_input(\"Y_mtl\");\n            op.add_output(\"Y_cpu\");\n          }\n\n          {\n            auto& op = *(netdef.add_op());\n            op.set_type(\"PRelu\");\n            op.add_input(\"X_cpu\");\n            op.add_input(\"b\");\n            auto& arg = *(op.add_arg());\n            arg.set_name(\"order\");\n            arg.set_s(\"NCHW\");\n            op.add_output(\"Y_ref\");\n          }\n\n          ws.RunNetOnce(netdef);\n          const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n          const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n          CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n          for (auto i = 0; i < t1.size(); ++i) {\n            // FP16 <-> FP32 round trip, accumulation, etc.\n            const float t1_i = t1.data<float>()[i];\n            const float t2_i = t2.data<float>()[i];\n            CHECK_NEAR(t1_i, t2_i, 0.1);\n          }\n        }\n      }\n    }\n  }\n\n  {\n    for (const auto& channels : std::vector<size_t>{3, 12, 15}) {\n      for (const auto& batch_size : std::vector<size_t>{1, 2}) {\n        LOG(INFO) << \"MPSCNNSpatialBN Test: \" << channels;\n        Workspace ws;\n        {\n          auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n          t->Resize(batch_size, channels, 8, 13);\n          CPUContext ctx;\n          math::RandGaussian<float, CPUContext>(\n              t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n        }\n\n        for (const std::string name : {\"scale\", \"bias\", \"mean\", \"var\"}) {\n          auto* t = ws.CreateBlob(name)->GetMutable<TensorCPU>();\n          t->Resize(channels);\n          CPUContext ctx;\n          // High mean to avoid var division by zero.\n          math::RandGaussian<float, CPUContext>(\n              t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n          if (name == \"var\") {\n            for (auto i = 0; i < t->size(); ++i) {\n              t->mutable_data<float>()[i] =\n                  std::abs(t->mutable_data<float>()[i]) + 0.5;\n            }\n          }\n        }\n\n        NetDef netdef;\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"CopyToMPSCNN\");\n          op.add_input(\"X_cpu\");\n          op.add_output(\"X_mtl\");\n        }\n\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"MPSCNNSpatialBN\");\n          op.add_input(\"X_mtl\");\n          op.add_input(\"scale\");\n          op.add_input(\"bias\");\n          op.add_input(\"mean\");\n          op.add_input(\"var\");\n          {\n            auto& arg = *(op.add_arg());\n            arg.set_name(OpSchema::Arg_IsTest);\n            arg.set_i(1);\n          }\n\n          op.add_output(\"Y_mtl\");\n        }\n\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"CopyFromMPSCNN\");\n          op.add_input(\"Y_mtl\");\n          op.add_output(\"Y_cpu\");\n        }\n\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"SpatialBN\");\n          op.add_input(\"X_cpu\");\n          op.add_input(\"scale\");\n          op.add_input(\"bias\");\n          op.add_input(\"mean\");\n          op.add_input(\"var\");\n          {\n            auto& arg = *(op.add_arg());\n            arg.set_name(OpSchema::Arg_IsTest);\n            arg.set_i(1);\n          }\n\n          op.add_output(\"Y_ref\");\n        }\n\n        ws.RunNetOnce(netdef);\n        const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n        const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n        CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n        for (auto i = 0; i < t1.size(); ++i) {\n          // FP16 <-> FP32 round trip, accumulation, etc.\n          const float t1_i = t1.data<float>()[i];\n          const float t2_i = t2.data<float>()[i];\n          CHECK_NEAR(t1_i, t2_i, 0.1);\n        }\n      }\n    }\n  }\n\n  {\n    for (const auto& batchSize : std::vector<size_t>{2, 1}) {\n      for (const auto& H : std::vector<size_t>{1, 8}) {\n        for (const auto& W : std::vector<size_t>{1, 8}) {\n          for (const auto& CIn : std::vector<size_t>{1, 12, 224}) {\n            for (const auto& COut : std::vector<size_t>{1, 12, 224}) {\n              LOG(INFO) << \"MPSCNNFC Test\";\n              Workspace ws;\n              {\n                auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n                t->Resize(batchSize, CIn, H, W);\n                CPUContext ctx;\n                math::RandGaussian<float, CPUContext>(\n                    t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n              }\n\n              {\n                auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n                t->Resize(COut, CIn * H * W);\n                CPUContext ctx;\n                math::RandGaussian<float, CPUContext>(\n                    t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n              }\n\n              {\n                auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n                t->Resize(COut);\n                CPUContext ctx;\n                math::RandGaussian<float, CPUContext>(\n                    t->size(), 0, 0.0001, t->mutable_data<float>(), &ctx);\n              }\n\n              NetDef netdef;\n              {\n                auto& op = *(netdef.add_op());\n                op.set_type(\"CopyToMPSCNN\");\n                op.add_input(\"X_cpu\");\n                op.add_output(\"X_mtl\");\n              }\n\n              {\n                auto& op = *(netdef.add_op());\n                op.set_type(\"MPSCNNFC\");\n                op.add_input(\"X_mtl\");\n                op.add_input(\"W\");\n                op.add_input(\"b\");\n                op.add_output(\"Y_mtl\");\n              }\n\n              {\n                auto& op = *(netdef.add_op());\n                op.set_type(\"CopyFromMPSCNN\");\n                op.add_input(\"Y_mtl\");\n                op.add_output(\"Y_cpu\");\n              }\n              {\n                auto& op = *(netdef.add_op());\n                op.set_type(\"FC\");\n                op.add_input(\"X_cpu\");\n                op.add_input(\"W\");\n                op.add_input(\"b\");\n                auto& arg = *(op.add_arg());\n                arg.set_name(\"order\");\n                arg.set_s(\"NCHW\");\n                op.add_output(\"Y_ref\");\n              }\n\n              ws.RunNetOnce(netdef);\n              const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n              const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n              CAFFE_ENFORCE_EQ(t2.ndim(), 4);\n              CAFFE_ENFORCE_EQ(t1.ndim(), 2);\n              CAFFE_ENFORCE(t2.dim32(2) == 1 && t2.dim32(3) == 1);\n              const_cast<TensorCPU&>(t2).Reshape(\n                  std::vector<TIndex>{TIndex(batchSize), TIndex(COut)});\n              // Note dims do not match, as Metal leaves a 1x1 spatial\n              // dimension.\n              CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n\n              for (auto i = 0; i < t1.size(); ++i) {\n                // FP16 <-> FP32 round trip, accumulation, etc.\n                const float t1_i = t1.data<float>()[i];\n                const float t2_i = t2.data<float>()[i];\n                // LOG(INFO) << \"i: \" << i << \", cpu: \" << t1_i << \", mtl: \" <<\n                // t2_i;\n                CHECK_NEAR(t1_i, t2_i, 0.7);\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  {\n    for (const auto& pool : {\"MaxPool\", \"AveragePool\"}) {\n      for (const auto& global_pooling : {true, false}) {\n        for (const auto& batchSize : std::vector<size_t>{1, 2}) {\n          for (const auto& stride_h : std::vector<int>{1, 2, 3}) {\n            for (const auto& stride_w : std::vector<int>{1, 2, 3}) {\n              for (const auto& kernel_h : std::vector<int>{1, 3, 5}) {\n                for (const auto& kernel_w : std::vector<int>{1, 3, 5}) {\n                  for (const auto& pad_l : std::vector<int>{0, kernel_w / 2}) {\n                    for (const auto& pad_r :\n                         std::vector<int>{0, kernel_w / 2}) {\n                      for (const auto& pad_t :\n                           std::vector<int>{0, kernel_h / 2}) {\n                        for (const auto& pad_b :\n                             std::vector<int>{0, kernel_h / 2}) {\n                          // Waiting response from Apple\n                          if (kernel_h != kernel_w) {\n                            continue;\n                          }\n                          LOG(INFO) << \"MPSCNNPool Test: \" << pool;\n                          Workspace ws;\n                          {\n                            auto* t =\n                                ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n                            t->Resize(batchSize, 8, 8, 13);\n                            CPUContext ctx;\n                            math::RandGaussian<float, CPUContext>(\n                                t->size(),\n                                0,\n                                1,\n                                t->mutable_data<float>(),\n                                &ctx);\n                          }\n\n                          NetDef netdef;\n#define ADD_ARGS(op)                                   \\\n  do {                                                 \\\n    if (global_pooling) {                              \\\n      add_arg_int(op, \"stride\", 1);                    \\\n    } else {                                           \\\n      add_arg_int_list(                                \\\n          op,                                          \\\n          std::vector<string>{\"pad_l\",                 \\\n                              \"pad_r\",                 \\\n                              \"pad_t\",                 \\\n                              \"pad_b\",                 \\\n                              \"kernel_w\",              \\\n                              \"kernel_h\",              \\\n                              \"stride_w\",              \\\n                              \"stride_h\"},             \\\n          std::vector<int>{pad_l,                      \\\n                           pad_r,                      \\\n                           pad_t,                      \\\n                           pad_b,                      \\\n                           kernel_w,                   \\\n                           kernel_h,                   \\\n                           stride_w,                   \\\n                           stride_h});                 \\\n    }                                                  \\\n    add_arg_int(op, \"global_pooling\", global_pooling); \\\n  } while (false)\n                          {\n                            auto& op = *(netdef.add_op());\n                            op.set_type(\"CopyToMPSCNN\");\n                            op.add_input(\"X_cpu\");\n                            op.add_output(\"X_mtl\");\n                          }\n\n                          {\n                            auto& op = *(netdef.add_op());\n                            op.set_type(std::string(\"MPSCNN\") + pool);\n                            op.add_input(\"X_mtl\");\n                            ADD_ARGS(op);\n                            op.add_output(\"Y_mtl\");\n                          }\n\n                          {\n                            auto& op = *(netdef.add_op());\n                            op.set_type(\"CopyFromMPSCNN\");\n                            op.add_input(\"Y_mtl\");\n                            op.add_output(\"Y_cpu\");\n                          }\n\n                          {\n                            auto& op = *(netdef.add_op());\n                            op.set_type(pool);\n                            op.add_input(\"X_cpu\");\n                            ADD_ARGS(op);\n                            op.add_output(\"Y_ref\");\n                          }\n#undef ADD_ARGS\n\n                          ws.RunNetOnce(netdef);\n                          const auto& t2 =\n                              ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n                          const auto& t1 =\n                              ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n                          CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n                          for (auto i = 0; i < t1.size(); ++i) {\n                            // FP16 <-> FP32 round trip, accumulation, etc.\n                            const float t1_i = t1.data<float>()[i];\n                            const float t2_i = t2.data<float>()[i];\n                            CHECK_NEAR(t1_i, t2_i, 0.1);\n                          }\n                        }\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNPadImage Test\";\n    for (const auto dims :\n         std::vector<std::vector<size_t>>{{1, 3, 50, 80}, {1, 12, 50, 80}}) {\n      Workspace ws;\n      {\n        auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n        t->Resize(dims);\n        CPUContext ctx;\n        math::RandGaussian<float, CPUContext>(\n            t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n      }\n\n      NetDef netdef;\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(\"CopyToMPSCNN\");\n        op.add_input(\"X_cpu\");\n        op.add_output(\"X_mtl\");\n      }\n\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(\"MPSCNNPadImage\");\n        op.add_input(\"X_mtl\");\n        {\n          auto& arg = *(op.add_arg());\n          arg.set_name(\"pad\");\n          arg.set_i(10);\n        }\n        {\n          auto& arg = *(op.add_arg());\n          arg.set_name(\"mode\");\n          arg.set_s(\"reflect\");\n        }\n        op.add_output(\"Y_mtl\");\n      }\n\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(\"CopyFromMPSCNN\");\n        op.add_input(\"Y_mtl\");\n        op.add_output(\"Y_cpu\");\n      }\n\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(\"PadImage\");\n        op.add_input(\"X_cpu\");\n        {\n          auto& arg = *(op.add_arg());\n          arg.set_name(\"pad\");\n          arg.set_i(10);\n        }\n        {\n          auto& arg = *(op.add_arg());\n          arg.set_name(\"mode\");\n          arg.set_s(\"reflect\");\n        }\n        op.add_output(\"Y_ref\");\n      }\n\n      ws.RunNetOnce(netdef);\n      const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n      const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n      CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n      for (auto i = 0; i < t1.size(); ++i) {\n        // FP16 <-> FP32 round trip, accumulation, etc.\n        const float t1_i = t1.data<float>()[i];\n        const float t2_i = t2.data<float>()[i];\n        // LOG(INFO) << \"i: \" << i << \", \" << \"CPU: \" << t1_i << \", MTL: \" <<\n        // t2_i;\n        CHECK_NEAR(t1_i, t2_i, 0.01);\n      }\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNPreprocess Test\";\n    Workspace ws;\n    {\n      auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(1, 8, 13, 4);\n      CPUContext ctx;\n      for (auto i = 0; i < t->size(); ++i) {\n        t->mutable_data<uint8_t>()[i] = rand() % 255;\n      }\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"mean\")->GetMutable<TensorCPU>();\n      t->Resize(3);\n      CPUContext ctx;\n      t->mutable_data<float>()[0] = 100;\n      t->mutable_data<float>()[1] = 50;\n      t->mutable_data<float>()[2] = 150;\n    }\n\n    NetDef netdef;\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"MPSCNNPackedInt8BGRANHWCToNCHWCStylizerPreprocess\");\n      op.add_input(\"X_cpu\");\n      op.add_input(\"mean\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"noise_std\");\n        arg.set_f(0.00001);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"noise_size\");\n        arg.set_i(512);\n      }\n\n      op.add_output(\"Y_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyFromMPSCNN\");\n      op.add_input(\"Y_mtl\");\n      op.add_output(\"Y_cpu\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"PackedInt8BGRANHWCToNCHWCStylizerPreprocess\");\n      op.add_input(\"X_cpu\");\n      op.add_input(\"mean\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"noise_std\");\n        arg.set_f(0.00001);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"noise_size\");\n        arg.set_i(512);\n      }\n      op.add_output(\"Y_ref\");\n    }\n\n    ws.RunNetOnce(netdef);\n    const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n    const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n    CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n    for (auto i = 0; i < t1.size(); ++i) {\n      // FP16 <-> FP32 round trip, accumulation, etc.\n      const float t1_i = t1.data<float>()[i];\n      const float t2_i = t2.data<float>()[i];\n      CHECK_NEAR(t1_i, t2_i, 0.1);\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNDeprocess Test\";\n    Workspace ws;\n    {\n      auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(1, 3, 8, 24);\n      CPUContext ctx;\n      for (auto i = 0; i < t->size(); ++i) {\n        t->mutable_data<float>()[i] = rand() % 255;\n      }\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"mean\")->GetMutable<TensorCPU>();\n      t->Resize(3);\n      CPUContext ctx;\n      t->mutable_data<float>()[0] = 100;\n      t->mutable_data<float>()[1] = 50;\n      t->mutable_data<float>()[2] = 150;\n    }\n\n    NetDef netdef;\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyToMPSCNN\");\n      op.add_input(\"X_cpu\");\n      op.add_output(\"X_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"MPSCNNBRGNCHWCToPackedInt8BGRAStylizerDeprocess\");\n      op.add_input(\"X_mtl\");\n      op.add_input(\"mean\");\n      op.add_output(\"Y_cpu\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"BRGNCHWCToPackedInt8BGRAStylizerDeprocess\");\n      op.add_input(\"X_cpu\");\n      op.add_input(\"mean\");\n      op.add_output(\"Y_ref\");\n    }\n\n    ws.RunNetOnce(netdef);\n    const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n    const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n    CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n    for (auto i = 0; i < t1.size(); ++i) {\n      // FP16 <-> FP32 round trip, accumulation, etc.\n      const float t1_i = t1.data<uint8_t>()[i];\n      const float t2_i = t2.data<uint8_t>()[i];\n      CHECK_NEAR(t1_i, t2_i, 0.1);\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNDeprocess Test\";\n    Workspace ws;\n    {\n      auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(1, 3, 1280, 720);\n      CPUContext ctx;\n      for (auto i = 0; i < t->size(); ++i) {\n        t->mutable_data<float>()[i] = rand() % 1000 - 500;\n      }\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"mean\")->GetMutable<TensorCPU>();\n      t->Resize(3);\n      CPUContext ctx;\n      t->mutable_data<float>()[0] = 30;\n      t->mutable_data<float>()[1] = 40;\n      t->mutable_data<float>()[2] = 50;\n    }\n\n    NetDef netdef;\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyToMPSCNN\");\n      op.add_input(\"X_cpu\");\n      op.add_output(\"X_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"MPSCNNBRGNCHWCToPackedInt8BGRAStylizerDeprocess\");\n      op.add_input(\"X_mtl\");\n      op.add_input(\"mean\");\n      op.add_output(\"Y_cpu\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"BRGNCHWCToPackedInt8BGRAStylizerDeprocess\");\n      op.add_input(\"X_cpu\");\n      op.add_input(\"mean\");\n      op.add_output(\"Y_ref\");\n    }\n\n    ws.RunNetOnce(netdef);\n    const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n    const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n    CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n    for (auto i = 0; i < t1.size(); ++i) {\n      // FP16 <-> FP32 round trip, accumulation, etc.\n      const float t1_i = t1.data<uint8_t>()[i];\n      const float t2_i = t2.data<uint8_t>()[i];\n      CHECK_NEAR(t1_i, t2_i, 0.1);\n    }\n  }\n\n  @autoreleasepool {\n    for (const auto& batchSize : std::vector<int>{1, 2}) {\n      for (const auto& stride_h : std::vector<int>{1, 2, 3}) {\n        for (const auto& stride_w : std::vector<int>{1, 2, 3}) {\n          for (const auto& kernel_h : std::vector<int>{1, 3, 8}) {\n            for (const auto& kernel_w : std::vector<int>{1, 3, 8}) {\n              for (const auto& pad_l : std::vector<int>{0, kernel_w / 2}) {\n                for (const auto& pad_r : std::vector<int>{0, kernel_w / 2}) {\n                  for (const auto& pad_t : std::vector<int>{0, kernel_h / 2}) {\n                    for (const auto& pad_b :\n                         std::vector<int>{0, kernel_h / 2}) {\n                      // Waiting response from Apple\n                      if (kernel_h != kernel_w) {\n                        continue;\n                      }\n                      LOG(INFO) << \"MPSCNNConv Test\";\n                      Workspace ws;\n                      {\n                        auto* t =\n                            ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n                        t->Resize(batchSize, 12, 57, 72);\n                        CPUContext ctx;\n                        math::RandGaussian<float, CPUContext>(\n                            t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n                      }\n\n                      {\n                        auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n                        t->Resize(8, 12, kernel_h, kernel_w);\n                        CPUContext ctx;\n                        math::RandGaussian<float, CPUContext>(\n                            8 * 12 * kernel_h * kernel_w,\n                            0,\n                            1,\n                            t->mutable_data<float>(),\n                            &ctx);\n                      }\n\n                      {\n                        auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n                        t->Resize(8);\n                        CPUContext ctx;\n                        math::RandGaussian<float, CPUContext>(\n                            8, 0, 1, t->mutable_data<float>(), &ctx);\n                      }\n\n                      NetDef netdef;\n#define ADD_ARGS(op)                     \\\n  do {                                   \\\n    add_arg_str(op, \"order\", \"NCHW\");    \\\n    add_arg_int_list(                    \\\n        op,                              \\\n        std::vector<string>{\"stride_h\",  \\\n                            \"stride_w\",  \\\n                            \"pad_l\",     \\\n                            \"pad_r\",     \\\n                            \"pad_t\",     \\\n                            \"pad_b\",     \\\n                            \"kernel_w\",  \\\n                            \"kernel_h\"}, \\\n        std::vector<int>{stride_h,       \\\n                         stride_w,       \\\n                         pad_l,          \\\n                         pad_r,          \\\n                         pad_t,          \\\n                         pad_b,          \\\n                         kernel_w,       \\\n                         kernel_h});     \\\n  } while (false)\n                      {\n                        auto& op = *(netdef.add_op());\n                        op.set_type(\"CopyToMPSCNN\");\n                        op.add_input(\"X_cpu\");\n                        op.add_output(\"X_mtl\");\n                      }\n\n                      {\n                        auto& op = *(netdef.add_op());\n                        op.set_type(\"MPSCNNConv\");\n                        op.add_input(\"X_mtl\");\n                        op.add_input(\"W\");\n                        op.add_input(\"b\");\n                        ADD_ARGS(op);\n                        op.add_output(\"Y_mtl\");\n                      }\n\n                      {\n                        auto& op = *(netdef.add_op());\n                        op.set_type(\"CopyFromMPSCNN\");\n                        op.add_input(\"Y_mtl\");\n                        op.add_output(\"Y_cpu\");\n                      }\n\n                      {\n                        auto& op = *(netdef.add_op());\n                        op.set_type(\"Conv\");\n                        op.add_input(\"X_cpu\");\n                        op.add_input(\"W\");\n                        op.add_input(\"b\");\n                        ADD_ARGS(op);\n                        op.add_output(\"Y_ref\");\n                      }\n#undef ADD_ARGS\n                      ws.RunNetOnce(netdef);\n                      const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n                      const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n                      CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n                      for (auto i = 0; i < t1.size(); ++i) {\n                        // FP16 <-> FP32 round trip, accumulation, etc.\n                        const float t1_i = t1.data<float>()[i];\n                        const float t2_i = t2.data<float>()[i];\n                        CHECK_NEAR(t1_i, t2_i, 0.3);\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  @autoreleasepool {\n    bool runtimeAtLeastIOS11 = SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@\"11.0\");\n    if (runtimeAtLeastIOS11) {\n      for (const auto& batchSize : std::vector<int>{1, 2}) {\n        for (const auto& input_channels : std::vector<int>{32, 64, 128, 256}) {\n          for (const auto& channel_multiplier : std::vector<int>{1}) {\n            LOG(INFO) << \"MPSCNNDepthwiseConv Test\";\n            Workspace ws;\n            int output_channels = input_channels * channel_multiplier;\n            {\n              auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n              t->Resize(batchSize, input_channels, 57, 72);\n              CPUContext ctx;\n              math::RandGaussian<float, CPUContext>(\n                  t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n            }\n\n            {\n              auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n              t->Resize(output_channels, 1, 3, 3);\n              CPUContext ctx;\n              math::RandGaussian<float, CPUContext>(\n                  t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n            }\n\n            {\n              auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n              t->Resize(output_channels);\n              CPUContext ctx;\n              math::RandGaussian<float, CPUContext>(\n                  t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n            }\n\n            NetDef netdef;\n#define ADD_ARGS(op)                                      \\\n  do {                                                    \\\n    add_arg_str(op, \"order\", \"NCHW\");                     \\\n    add_arg_int_list(                                     \\\n        op,                                               \\\n        std::vector<string>{\"stride\", \"kernel\", \"group\"}, \\\n        std::vector<int>{1, 3, input_channels});          \\\n  } while (false)\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"CopyToMPSCNN\");\n              op.add_input(\"X_cpu\");\n              op.add_output(\"X_mtl\");\n            }\n\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"MPSCNNConv\");\n              op.add_input(\"X_mtl\");\n              op.add_input(\"W\");\n              op.add_input(\"b\");\n              ADD_ARGS(op);\n              op.add_output(\"Y_mtl\");\n            }\n\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"CopyFromMPSCNN\");\n              op.add_input(\"Y_mtl\");\n              op.add_output(\"Y_cpu\");\n            }\n\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"Conv\");\n              op.add_input(\"X_cpu\");\n              op.add_input(\"W\");\n              op.add_input(\"b\");\n              ADD_ARGS(op);\n              op.add_output(\"Y_ref\");\n            }\n#undef ADD_ARGS\n            ws.RunNetOnce(netdef);\n            const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n            const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n            CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n            for (auto i = 0; i < t1.size(); ++i) {\n              // FP16 <-> FP32 round trip, accumulation, etc.\n              const float t1_i = t1.data<float>()[i];\n              const float t2_i = t2.data<float>()[i];\n              CHECK_NEAR(t1_i, t2_i, 0.3);\n            }\n          }\n        }\n      }\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNConvRelu Test\";\n    Workspace ws;\n    {\n      auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(1, 12, 57, 72);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n      t->Resize(8, 12, 3, 3);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          8 * 12 * 3 * 3, 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n      t->Resize(8);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          8, 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    NetDef netdef;\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyToMPSCNN\");\n      op.add_input(\"X_cpu\");\n      op.add_output(\"X_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"MPSCNNConvRelu\");\n      op.add_input(\"X_mtl\");\n      op.add_input(\"W\");\n      op.add_input(\"b\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"order\");\n        arg.set_s(\"NCHW\");\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"kernel\");\n        arg.set_i(3);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad\");\n        arg.set_i(1);\n      }\n      op.add_output(\"Y_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyFromMPSCNN\");\n      op.add_input(\"Y_mtl\");\n      op.add_output(\"Y_cpu\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Conv\");\n      op.add_input(\"X_cpu\");\n      op.add_input(\"W\");\n      op.add_input(\"b\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"order\");\n        arg.set_s(\"NCHW\");\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"kernel\");\n        arg.set_i(3);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad\");\n        arg.set_i(1);\n      }\n      op.add_output(\"Y_ref\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Relu\");\n      op.add_input(\"Y_ref\");\n      op.add_output(\"Y_ref\");\n    }\n\n    ws.RunNetOnce(netdef);\n    const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n    const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n    CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n    for (auto i = 0; i < t1.size(); ++i) {\n      // FP16 <-> FP32 round trip, accumulation, etc.\n      const float t1_i = t1.data<float>()[i];\n      const float t2_i = t2.data<float>()[i];\n      CHECK_NEAR(t1_i, t2_i, 0.1);\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSConv Test\";\n    Workspace ws;\n    {\n      auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(1, 12, 57, 72);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n      t->Resize(8, 12, 3, 3);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          8 * 12 * 3 * 3, 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n      t->Resize(8);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          8, 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    NetDef netdef;\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyToMPSCNN\");\n      op.add_input(\"X_cpu\");\n      op.add_output(\"X_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"MPSCNNConv\");\n      op.add_input(\"X_mtl\");\n      op.add_input(\"W\");\n      op.add_input(\"b\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"order\");\n        arg.set_s(\"NCHW\");\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"kernel\");\n        arg.set_i(3);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad\");\n        arg.set_i(0);\n      }\n      op.add_output(\"Y_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyFromMPSCNN\");\n      op.add_input(\"Y_mtl\");\n      op.add_output(\"Y_cpu\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Conv\");\n      op.add_input(\"X_cpu\");\n      op.add_input(\"W\");\n      op.add_input(\"b\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"order\");\n        arg.set_s(\"NCHW\");\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"kernel\");\n        arg.set_i(3);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad\");\n        arg.set_i(0);\n      }\n      op.add_output(\"Y_ref\");\n    }\n\n    ws.RunNetOnce(netdef);\n    const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n    const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n    CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n    for (auto i = 0; i < t1.size(); ++i) {\n      // FP16 <-> FP32 round trip, accumulation, etc.\n      const float t1_i = t1.data<float>()[i];\n      const float t2_i = t2.data<float>()[i];\n      CHECK_NEAR(t1_i, t2_i, 0.1);\n    }\n  }\n\n  {\n    for (const auto& batchSize : {1, 2}) {\n      for (const auto& C : {1, 2}) {\n        for (const auto& M : {1, 2}) {\n          for (const auto& K : {3, 4}) {\n            for (const auto& P : {1, 2}) {\n              LOG(INFO) << \"MPSConv Test\";\n              Workspace ws;\n              {\n                auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n                t->Resize(batchSize, C, 12, 16);\n                CPUContext ctx;\n                math::RandGaussian<float, CPUContext>(\n                    t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n              }\n\n              {\n                auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n                t->Resize(M, C, K, K);\n                CPUContext ctx;\n                math::RandGaussian<float, CPUContext>(\n                    t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n              }\n\n              {\n                auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n                t->Resize(M);\n                CPUContext ctx;\n                math::RandGaussian<float, CPUContext>(\n                    t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n              }\n\n              NetDef netdef;\n              {\n                auto& op = *(netdef.add_op());\n                op.set_type(\"CopyToMPSCNN\");\n                op.add_input(\"X_cpu\");\n                op.add_output(\"X_mtl\");\n              }\n\n              {\n                auto& op = *(netdef.add_op());\n                op.set_type(\"MPSCNNConv\");\n                op.add_input(\"X_mtl\");\n                op.add_input(\"W\");\n                op.add_input(\"b\");\n                {\n                  auto& arg = *(op.add_arg());\n                  arg.set_name(\"order\");\n                  arg.set_s(\"NCHW\");\n                }\n                {\n                  auto& arg = *(op.add_arg());\n                  arg.set_name(\"kernel\");\n                  arg.set_i(K);\n                }\n                {\n                  auto& arg = *(op.add_arg());\n                  arg.set_name(\"pad\");\n                  arg.set_i(P);\n                }\n                op.add_output(\"Y_mtl\");\n              }\n\n              {\n                auto& op = *(netdef.add_op());\n                op.set_type(\"CopyFromMPSCNN\");\n                op.add_input(\"Y_mtl\");\n                op.add_output(\"Y_cpu\");\n              }\n\n              {\n                auto& op = *(netdef.add_op());\n                op.set_type(\"Conv\");\n                op.add_input(\"X_cpu\");\n                op.add_input(\"W\");\n                op.add_input(\"b\");\n                {\n                  auto& arg = *(op.add_arg());\n                  arg.set_name(\"order\");\n                  arg.set_s(\"NCHW\");\n                }\n                {\n                  auto& arg = *(op.add_arg());\n                  arg.set_name(\"kernel\");\n                  arg.set_i(K);\n                }\n                {\n                  auto& arg = *(op.add_arg());\n                  arg.set_name(\"pad\");\n                  arg.set_i(P);\n                }\n                op.add_output(\"Y_ref\");\n              }\n\n              ws.RunNetOnce(netdef);\n              const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n              const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n              CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n              for (auto i = 0; i < t1.size(); ++i) {\n                // FP16 <-> FP32 round trip, accumulation, etc.\n                const float t1_i = t1.data<float>()[i];\n                const float t2_i = t2.data<float>()[i];\n                CHECK_NEAR(t1_i, t2_i, 0.1);\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  {\n    for (const auto& batchSize : {1, 2}) {\n      for (const auto& group : {1, 2}) {\n        for (const auto& C : {8, 16}) {\n          for (const auto& M : {8, 16}) {\n            for (const auto& K : {3, 4}) {\n              for (const auto& P : {1, 2}) {\n                LOG(INFO) << \"MPSCNNConv Test - group\";\n                Workspace ws;\n                {\n                  auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n                  t->Resize(batchSize, C, 12, 16);\n                  CPUContext ctx;\n                  math::RandGaussian<float, CPUContext>(\n                      t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n                }\n\n                {\n                  auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n                  t->Resize(M, C / group, K, K);\n                  CPUContext ctx;\n                  math::RandGaussian<float, CPUContext>(\n                      t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n                }\n\n                {\n                  auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n                  t->Resize(M);\n                  CPUContext ctx;\n                  math::RandGaussian<float, CPUContext>(\n                      t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n                }\n\n                NetDef netdef;\n                {\n                  auto& op = *(netdef.add_op());\n                  op.set_type(\"CopyToMPSCNN\");\n                  op.add_input(\"X_cpu\");\n                  op.add_output(\"X_mtl\");\n                }\n\n                {\n                  auto& op = *(netdef.add_op());\n                  op.set_type(\"MPSCNNConv\");\n                  op.add_input(\"X_mtl\");\n                  op.add_input(\"W\");\n                  op.add_input(\"b\");\n                  {\n                    auto& arg = *(op.add_arg());\n                    arg.set_name(\"order\");\n                    arg.set_s(\"NCHW\");\n                  }\n                  {\n                    auto& arg = *(op.add_arg());\n                    arg.set_name(\"kernel\");\n                    arg.set_i(K);\n                  }\n                  {\n                    auto& arg = *(op.add_arg());\n                    arg.set_name(\"pad\");\n                    arg.set_i(P);\n                  }\n                  {\n                    auto& arg = *(op.add_arg());\n                    arg.set_name(\"group\");\n                    arg.set_i(group);\n                  }\n                  op.add_output(\"Y_mtl\");\n                }\n\n                {\n                  auto& op = *(netdef.add_op());\n                  op.set_type(\"CopyFromMPSCNN\");\n                  op.add_input(\"Y_mtl\");\n                  op.add_output(\"Y_cpu\");\n                }\n\n                {\n                  auto& op = *(netdef.add_op());\n                  op.set_type(\"Conv\");\n                  op.add_input(\"X_cpu\");\n                  op.add_input(\"W\");\n                  op.add_input(\"b\");\n                  {\n                    auto& arg = *(op.add_arg());\n                    arg.set_name(\"order\");\n                    arg.set_s(\"NCHW\");\n                  }\n                  {\n                    auto& arg = *(op.add_arg());\n                    arg.set_name(\"kernel\");\n                    arg.set_i(K);\n                  }\n                  {\n                    auto& arg = *(op.add_arg());\n                    arg.set_name(\"pad\");\n                    arg.set_i(P);\n                  }\n                  {\n                    auto& arg = *(op.add_arg());\n                    arg.set_name(\"group\");\n                    arg.set_i(group);\n                  }\n                  op.add_output(\"Y_ref\");\n                }\n\n                ws.RunNetOnce(netdef);\n                const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n                const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n                CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n                for (auto i = 0; i < t1.size(); ++i) {\n                  // FP16 <-> FP32 round trip, accumulation, etc.\n                  const float t1_i = t1.data<float>()[i];\n                  const float t2_i = t2.data<float>()[i];\n                  CHECK_NEAR(t1_i, t2_i, 0.1);\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNMul Test\";\n    Workspace ws;\n    {\n      auto* t = ws.CreateBlob(\"X0_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(1, 12, 57, 72);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"X1_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(72);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    NetDef netdef;\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyToMPSCNN\");\n      op.add_input(\"X0_cpu\");\n      op.add_output(\"X0_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"MPSCNNMul\");\n      op.add_input(\"X0_mtl\");\n      op.add_input(\"X1_cpu\");\n      op.add_output(\"Y_mtl\");\n      add_arg_int(op, \"broadcast\", 1);\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyFromMPSCNN\");\n      op.add_input(\"Y_mtl\");\n      op.add_output(\"Y_cpu\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Mul\");\n      op.add_input(\"X0_cpu\");\n      op.add_input(\"X1_cpu\");\n      op.add_output(\"Y_ref\");\n      add_arg_int(op, \"broadcast\", 1);\n    }\n\n    ws.RunNetOnce(netdef);\n    const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n    const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n    CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n    for (auto i = 0; i < t1.size(); ++i) {\n      // FP16 <-> FP32 round trip, accumulation, etc.\n      const float t1_i = t1.data<float>()[i];\n      const float t2_i = t2.data<float>()[i];\n      CHECK_NEAR(t1_i, t2_i, 0.02);\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNSub Test\";\n    Workspace ws;\n    {\n      auto* t = ws.CreateBlob(\"X0_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(1, 12, 57, 72);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"X1_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(72);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    NetDef netdef;\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyToMPSCNN\");\n      op.add_input(\"X0_cpu\");\n      op.add_output(\"X0_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"MPSCNNSub\");\n      op.add_input(\"X0_mtl\");\n      op.add_input(\"X1_cpu\");\n      op.add_output(\"Y_mtl\");\n      add_arg_int(op, \"broadcast\", 1);\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyFromMPSCNN\");\n      op.add_input(\"Y_mtl\");\n      op.add_output(\"Y_cpu\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Sub\");\n      op.add_input(\"X0_cpu\");\n      op.add_input(\"X1_cpu\");\n      op.add_output(\"Y_ref\");\n      add_arg_int(op, \"broadcast\", 1);\n    }\n\n    ws.RunNetOnce(netdef);\n    const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n    const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n    CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n    for (auto i = 0; i < t1.size(); ++i) {\n      // FP16 <-> FP32 round trip, accumulation, etc.\n      const float t1_i = t1.data<float>()[i];\n      const float t2_i = t2.data<float>()[i];\n      CHECK_NEAR(t1_i, t2_i, 0.01);\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSAdd Test\";\n    Workspace ws;\n    {\n      auto* t = ws.CreateBlob(\"X0_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(1, 12, 57, 72);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"X1_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(1, 12, 57, 72);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    NetDef netdef;\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyToMPSCNN\");\n      op.add_input(\"X0_cpu\");\n      op.add_output(\"X0_mtl\");\n      op.add_input(\"X1_cpu\");\n      op.add_output(\"X1_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"MPSCNNAdd\");\n      op.add_input(\"X0_mtl\");\n      op.add_input(\"X1_mtl\");\n      op.add_output(\"Y_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyFromMPSCNN\");\n      op.add_input(\"Y_mtl\");\n      op.add_output(\"Y_cpu\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Add\");\n      op.add_input(\"X0_cpu\");\n      op.add_input(\"X1_cpu\");\n      op.add_output(\"Y_ref\");\n    }\n\n    ws.RunNetOnce(netdef);\n    const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n    const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n    CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n    for (auto i = 0; i < t1.size(); ++i) {\n      // FP16 <-> FP32 round trip, accumulation, etc.\n      const float t1_i = t1.data<float>()[i];\n      const float t2_i = t2.data<float>()[i];\n      CHECK_NEAR(t1_i, t2_i, 0.01);\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSAdd Test\";\n    Workspace ws;\n    {\n      auto* t = ws.CreateBlob(\"X0_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(1, 12, 57, 72);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"X1_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(1, 12, 57, 72);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    NetDef netdef;\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyToMPSCNN\");\n      op.add_input(\"X0_cpu\");\n      op.add_output(\"X0_mtl\");\n      op.add_input(\"X1_cpu\");\n      op.add_output(\"X1_mtl\");\n\n      // First input is read twice.\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"__mpscnn_read_count__\");\n        arg.add_ints(2);\n        arg.add_ints(1);\n      }\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"MPSCNNAdd\");\n      op.add_input(\"X0_mtl\");\n      op.add_input(\"X1_mtl\");\n      op.add_output(\"X2_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"MPSCNNAdd\");\n      op.add_input(\"X0_mtl\");\n      op.add_input(\"X2_mtl\");\n      op.add_output(\"Y_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyFromMPSCNN\");\n      op.add_input(\"Y_mtl\");\n      op.add_output(\"Y_cpu\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Add\");\n      op.add_input(\"X0_cpu\");\n      op.add_input(\"X1_cpu\");\n      op.add_output(\"X2_cpu\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Add\");\n      op.add_input(\"X0_cpu\");\n      op.add_input(\"X2_cpu\");\n      op.add_output(\"Y_ref\");\n    }\n\n    ws.RunNetOnce(netdef);\n    const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n    const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n    CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n    for (auto i = 0; i < t1.size(); ++i) {\n      // FP16 <-> FP32 round trip, accumulation, etc.\n      const float t1_i = t1.data<float>()[i];\n      const float t2_i = t2.data<float>()[i];\n      CHECK_NEAR(t1_i, t2_i, 0.05);\n    }\n  }\n\n  {\n    for (const auto& n : {\"Relu\", \"Tanh\", \"Sigmoid\"}) {\n      LOG(INFO) << \"MPSCNNNeuron Test: \" << n;\n      Workspace ws;\n      {\n        auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n        t->Resize(1, 4, 12, 12);\n        CPUContext ctx;\n        math::RandGaussian<float, CPUContext>(\n            t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n      }\n\n      NetDef netdef;\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(\"CopyToMPSCNN\");\n        op.add_input(\"X_cpu\");\n        op.add_output(\"X_mtl\");\n      }\n\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(std::string(\"MPSCNN\") + n);\n        op.add_input(\"X_mtl\");\n        op.add_output(\"Y_mtl\");\n      }\n\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(\"CopyFromMPSCNN\");\n        op.add_input(\"Y_mtl\");\n        op.add_output(\"Y_cpu\");\n      }\n\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(n);\n        op.add_input(\"X_cpu\");\n        op.add_output(\"Y_ref\");\n      }\n\n      ws.RunNetOnce(netdef);\n      const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n      const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n      CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n      for (auto i = 0; i < t1.size(); ++i) {\n        // FP16 <-> FP32 round trip, accumulation, etc.\n        const float t1_i = t1.data<float>()[i];\n        const float t2_i = t2.data<float>()[i];\n        CHECK_NEAR(t1_i, t2_i, 0.02);\n      }\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNDropout Test\";\n    Workspace ws;\n    {\n      auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(1, 12, 57, 72);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(\n          t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    NetDef netdef;\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyToMPSCNN\");\n      op.add_input(\"X_cpu\");\n      op.add_output(\"X_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"MPSCNNDropout\");\n      op.add_input(\"X_mtl\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(OpSchema::Arg_IsTest);\n        arg.set_i(1);\n      }\n      op.add_output(\"Y_mtl\");\n      op.add_output(\"Y_mask_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyFromMPSCNN\");\n      op.add_input(\"Y_mtl\");\n      op.add_output(\"Y_cpu\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Dropout\");\n      op.add_input(\"X_cpu\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(OpSchema::Arg_IsTest);\n        arg.set_i(1);\n      }\n      op.add_output(\"Y_ref\");\n      op.add_output(\"Y_mask\");\n    }\n\n    ws.RunNetOnce(netdef);\n    const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n    const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n    CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n    LOG(INFO) << t1.dims();\n    for (auto i = 0; i < t1.size(); ++i) {\n      // FP16 <-> FP32 round trip, accumulation, etc.\n      const float t1_i = t1.data<float>()[i];\n      const float t2_i = t2.data<float>()[i];\n      CHECK_NEAR(t1_i, t2_i, 0.1);\n    }\n  }\n\n  {\n    for (const auto scale : std::vector<float>{1.0, 2.0, 0.0625}) {\n      for (const auto channels : std::vector<size_t>{1, 3, 5, 8}) {\n        for (const auto pool : std::vector<size_t>{1, 3, 7}) {\n          for (const auto sampling_ratio : std::vector<size_t>{0, 1, 2, 3}) {\n            LOG(INFO) << \"MPSCNNRoIWarp Test - sampling_ratio:\"\n                      << sampling_ratio << \"- pool: \" << pool\n                      << \" - scale: \" << scale;\n            Workspace ws;\n            {\n              auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n              t->Resize(1, channels, 40, 40);\n              CPUContext ctx;\n              math::RandGaussian<float, CPUContext>(\n                  t->size(), 4, 2, t->mutable_data<float>(), &ctx);\n            }\n            {\n              // Use the batch-first encoding (n, [bbox])\n              auto* t = ws.CreateBlob(\"R\")->GetMutable<TensorCPU>();\n              t->Resize(6, 5);\n              for (auto i = 0; i < t->dim32(0); ++i) {\n                t->mutable_data<float>()[5 * i + 0] = 0; // batch\n                t->mutable_data<float>()[5 * i + 1] = (i % 4 + 1) * 1.0 / scale;\n                t->mutable_data<float>()[5 * i + 2] = (i % 5 + 1) * 1.0 / scale;\n                t->mutable_data<float>()[5 * i + 3] = (i % 3 + 7) * 1.0 / scale;\n                t->mutable_data<float>()[5 * i + 4] = (i % 4 + 7) * 1.0 / scale;\n              }\n            }\n\n            NetDef netdef;\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"CopyToMPSCNN\");\n              op.add_input(\"X_cpu\");\n              op.add_output(\"X_mtl\");\n            }\n\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"MPSCNNRoIWarp\");\n              op.add_input(\"X_mtl\");\n              op.add_input(\"R\");\n              {\n                auto& arg = *(op.add_arg());\n                arg.set_name(\"sampling_ratio\");\n                arg.set_i(sampling_ratio);\n              }\n              {\n                auto& arg = *(op.add_arg());\n                arg.set_name(\"pooled_h\");\n                arg.set_i(pool);\n              }\n              {\n                auto& arg = *(op.add_arg());\n                arg.set_name(\"pooled_w\");\n                arg.set_i(pool);\n              }\n              {\n                auto& arg = *(op.add_arg());\n                arg.set_name(\"spatial_scale\");\n                arg.set_f(scale);\n              }\n              op.add_output(\"Y_mtl\");\n            }\n\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"CopyFromMPSCNN\");\n              op.add_input(\"Y_mtl\");\n              op.add_output(\"Y_cpu\");\n            }\n\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"RoIWarp\");\n              op.add_input(\"X_cpu\");\n              op.add_input(\"R\");\n              {\n                auto& arg = *(op.add_arg());\n                arg.set_name(\"sampling_ratio\");\n                arg.set_i(sampling_ratio);\n              }\n              {\n                auto& arg = *(op.add_arg());\n                arg.set_name(\"pooled_h\");\n                arg.set_i(pool);\n              }\n              {\n                auto& arg = *(op.add_arg());\n                arg.set_name(\"pooled_w\");\n                arg.set_i(pool);\n              }\n              {\n                auto& arg = *(op.add_arg());\n                arg.set_name(\"spatial_scale\");\n                arg.set_f(scale);\n              }\n              op.add_output(\"Y_ref\");\n            }\n\n            ws.RunNetOnce(netdef);\n            const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n            const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n\n            CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n            LOG(INFO) << t1.dims();\n            for (auto i = 0; i < t1.size(); ++i) {\n              // FP16 <-> FP32 round trip, accumulation, etc.\n              const float t1_i = t1.data<float>()[i];\n              const float t2_i = t2.data<float>()[i];\n              CHECK_NEAR(t1_i, t2_i, 0.1);\n            }\n          }\n        }\n      }\n    }\n  }\n\n  {\n    for (const auto scale : std::vector<float>{1.0, 2.0, 0.0625}) {\n      for (const auto pool : std::vector<size_t>{1, 3, 7}) {\n        LOG(INFO) << \"MPSCNNRoIWarp Test 2\";\n        Workspace ws;\n        {\n          auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n          t->Resize(1, 8, 40, 40);\n          CPUContext ctx;\n          math::RandGaussian<float, CPUContext>(\n              t->size(), 4, 2, t->mutable_data<float>(), &ctx);\n        }\n        {\n          auto* t = ws.CreateBlob(\"R\")->GetMutable<TensorCPU>();\n          t->Resize(6, 4);\n          for (auto i = 0; i < t->dim32(0); ++i) {\n            t->mutable_data<float>()[4 * i + 0] = (i % 4 + 1) * 1.0 / scale;\n            t->mutable_data<float>()[4 * i + 1] = (i % 5 + 1) * 1.0 / scale;\n            t->mutable_data<float>()[4 * i + 2] = (i % 3 + 7) * 1.0 / scale;\n            t->mutable_data<float>()[4 * i + 3] = (i % 4 + 7) * 1.0 / scale;\n          }\n        }\n\n        NetDef netdef;\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"CopyToMPSCNN\");\n          op.add_input(\"X_cpu\");\n          op.add_output(\"X_mtl\");\n        }\n\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"MPSCNNRoIWarp\");\n          op.add_input(\"X_mtl\");\n          op.add_input(\"R\");\n          {\n            auto& arg = *(op.add_arg());\n            arg.set_name(\"sampling_ratio\");\n            arg.set_i(1);\n          }\n          {\n            auto& arg = *(op.add_arg());\n            arg.set_name(\"pooled_h\");\n            arg.set_i(pool);\n          }\n          {\n            auto& arg = *(op.add_arg());\n            arg.set_name(\"pooled_w\");\n            arg.set_i(pool);\n          }\n          {\n            auto& arg = *(op.add_arg());\n            arg.set_name(\"spatial_scale\");\n            arg.set_f(scale);\n          }\n          op.add_output(\"Y_mtl\");\n        }\n\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"CopyFromMPSCNN\");\n          op.add_input(\"Y_mtl\");\n          op.add_output(\"Y_cpu\");\n        }\n\n        {\n          auto& op = *(netdef.add_op());\n          op.set_type(\"RoIWarp\");\n          op.add_input(\"X_cpu\");\n          op.add_input(\"R\");\n          {\n            auto& arg = *(op.add_arg());\n            arg.set_name(\"sampling_ratio\");\n            arg.set_i(1);\n          }\n          {\n            auto& arg = *(op.add_arg());\n            arg.set_name(\"pooled_h\");\n            arg.set_i(pool);\n          }\n          {\n            auto& arg = *(op.add_arg());\n            arg.set_name(\"pooled_w\");\n            arg.set_i(pool);\n          }\n          {\n            auto& arg = *(op.add_arg());\n            arg.set_name(\"spatial_scale\");\n            arg.set_f(scale);\n          }\n          op.add_output(\"Y_ref\");\n        }\n\n        ws.RunNetOnce(netdef);\n        const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n        const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n\n        CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n        LOG(INFO) << t1.dims();\n        for (auto i = 0; i < t1.size(); ++i) {\n          // FP16 <-> FP32 round trip, accumulation, etc.\n          const float t1_i = t1.data<float>()[i];\n          const float t2_i = t2.data<float>()[i];\n          CHECK_NEAR(t1_i, t2_i, 0.1);\n        }\n      }\n    }\n  }\n\n  {\n    for (const auto height_scale : std::vector<float>{1.0, 0.5, 1.7}) {\n      for (const auto width_scale : std::vector<float>{1.0, 0.5, 2.3}) {\n        for (const auto C : std::vector<float>{2, 7, 11}) {\n          for (const auto N : std::vector<float>{1, 2}) {\n            LOG(INFO) << \"MPSCNNResizeNearestOp Test\";\n            Workspace ws;\n            {\n              auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n              t->Resize(N, C, 37, 89);\n              CPUContext ctx;\n              math::RandGaussian<float, CPUContext>(\n                  t->size(), 4, 2, t->mutable_data<float>(), &ctx);\n            }\n            NetDef netdef;\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"CopyToMPSCNN\");\n              op.add_input(\"X_cpu\");\n              op.add_output(\"X_mtl\");\n            }\n\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"MPSCNNResizeNearest\");\n              op.add_input(\"X_mtl\");\n              {\n                auto& arg = *(op.add_arg());\n                arg.set_name(\"height_scale\");\n                arg.set_f(height_scale);\n              }\n              {\n                auto& arg = *(op.add_arg());\n                arg.set_name(\"width_scale\");\n                arg.set_f(width_scale);\n              }\n              op.add_output(\"Y_mtl\");\n            }\n\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"CopyFromMPSCNN\");\n              op.add_input(\"Y_mtl\");\n              op.add_output(\"Y_cpu\");\n            }\n\n            {\n              auto& op = *(netdef.add_op());\n              op.set_type(\"ResizeNearest\");\n              op.add_input(\"X_cpu\");\n              {\n                auto& arg = *(op.add_arg());\n                arg.set_name(\"height_scale\");\n                arg.set_f(height_scale);\n              }\n              {\n                auto& arg = *(op.add_arg());\n                arg.set_name(\"width_scale\");\n                arg.set_f(width_scale);\n              }\n              op.add_output(\"Y_ref\");\n            }\n\n            ws.RunNetOnce(netdef);\n            const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n            const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n\n            CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n            LOG(INFO) << t1.dims();\n            for (auto i = 0; i < t1.size(); ++i) {\n              // FP16 <-> FP32 round trip, accumulation, etc.\n              const float t1_i = t1.data<float>()[i];\n              const float t2_i = t2.data<float>()[i];\n              CHECK_NEAR(t1_i, t2_i, 0.1);\n            }\n          }\n        }\n      }\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNGenerateProposals Test: \\n\";\n    Workspace ws;\n    auto num_images = 1;\n    auto A = 2; // # anchors\n    auto H = 4; // height\n    auto W = 5; // width\n    vector<float> scores{\n        5.44218998e-03, 1.19207997e-03, 1.12379994e-03, 1.17181998e-03,\n        1.20544003e-03, 6.17993006e-04, 1.05261997e-05, 8.91025957e-06,\n        9.29536981e-09, 6.09605013e-05, 4.72735002e-04, 1.13482002e-10,\n        1.50015003e-05, 4.45032993e-06, 3.21612994e-08, 8.02662980e-04,\n        1.40488002e-04, 3.12508007e-07, 3.02616991e-06, 1.97759000e-08,\n        2.66913995e-02, 5.26766013e-03, 5.05053019e-03, 5.62100019e-03,\n        5.37420018e-03, 5.26280981e-03, 2.48894998e-04, 1.06842002e-04,\n        3.92931997e-06, 1.79388002e-03, 4.79440019e-03, 3.41609990e-07,\n        5.20430971e-04, 3.34090000e-05, 2.19159006e-07, 2.28786003e-03,\n        5.16703985e-05, 4.04523007e-06, 1.79227004e-06, 5.32449000e-08};\n    vector<float> bbx{\n        -1.65040009e-02, -1.84051003e-02, -1.85930002e-02, -2.08263006e-02,\n        -1.83814000e-02, -2.89172009e-02, -3.89706008e-02, -7.52277970e-02,\n        -1.54091999e-01, -2.55433004e-02, -1.77490003e-02, -1.10340998e-01,\n        -4.20190990e-02, -2.71421000e-02, 6.89801015e-03,  5.71171008e-02,\n        -1.75665006e-01, 2.30021998e-02,  3.08554992e-02,  -1.39333997e-02,\n        3.40579003e-01,  3.91070992e-01,  3.91624004e-01,  3.92527014e-01,\n        3.91445011e-01,  3.79328012e-01,  4.26631987e-01,  3.64892989e-01,\n        2.76894987e-01,  5.13985991e-01,  3.79999995e-01,  1.80457994e-01,\n        4.37402993e-01,  4.18545991e-01,  2.51549989e-01,  4.48318988e-01,\n        1.68564007e-01,  4.65440989e-01,  4.21891987e-01,  4.45928007e-01,\n        3.27155995e-03,  3.71480011e-03,  3.60032008e-03,  4.27092984e-03,\n        3.74579988e-03,  5.95752988e-03,  -3.14473989e-03, 3.52022005e-03,\n        -1.88564006e-02, 1.65188999e-03,  1.73791999e-03,  -3.56074013e-02,\n        -1.66615995e-04, 3.14146001e-03,  -1.11830998e-02, -5.35363983e-03,\n        6.49790000e-03,  -9.27671045e-03, -2.83346009e-02, -1.61233004e-02,\n        -2.15505004e-01, -2.19910994e-01, -2.20872998e-01, -2.12831005e-01,\n        -2.19145000e-01, -2.27687001e-01, -3.43973994e-01, -2.75869995e-01,\n        -3.19516987e-01, -2.50418007e-01, -2.48537004e-01, -5.08224010e-01,\n        -2.28724003e-01, -2.82402009e-01, -3.75815988e-01, -2.86352992e-01,\n        -5.28333001e-02, -4.43836004e-01, -4.55134988e-01, -4.34897989e-01,\n        -5.65053988e-03, -9.25739005e-04, -1.06790999e-03, -2.37016007e-03,\n        -9.71166010e-04, -8.90910998e-03, -1.17592998e-02, -2.08992008e-02,\n        -4.94231991e-02, 6.63906988e-03,  3.20469006e-03,  -6.44695014e-02,\n        -3.11607006e-03, 2.02738005e-03,  1.48096997e-02,  4.39785011e-02,\n        -8.28424022e-02, 3.62076014e-02,  2.71668993e-02,  1.38250999e-02,\n        6.76669031e-02,  1.03252999e-01,  1.03255004e-01,  9.89722982e-02,\n        1.03646003e-01,  4.79663983e-02,  1.11014001e-01,  9.31736007e-02,\n        1.15768999e-01,  1.04014002e-01,  -8.90677981e-03, 1.13103002e-01,\n        1.33085996e-01,  1.25405997e-01,  1.50051996e-01,  -1.13038003e-01,\n        7.01059997e-02,  1.79651007e-01,  1.41055003e-01,  1.62841007e-01,\n        -1.00247003e-02, -8.17587040e-03, -8.32176022e-03, -8.90108012e-03,\n        -8.13035015e-03, -1.77263003e-02, -3.69572006e-02, -3.51580009e-02,\n        -5.92143014e-02, -1.80795006e-02, -5.46086021e-03, -4.10550982e-02,\n        -1.83081999e-02, -2.15411000e-02, -1.17953997e-02, 3.33894007e-02,\n        -5.29635996e-02, -6.97528012e-03, -3.15250992e-03, -3.27355005e-02,\n        1.29676998e-01,  1.16080999e-01,  1.15947001e-01,  1.21797003e-01,\n        1.16089001e-01,  1.44875005e-01,  1.15617000e-01,  1.31586999e-01,\n        1.74735002e-02,  1.21973999e-01,  1.31596997e-01,  2.48907991e-02,\n        6.18605018e-02,  1.12855002e-01,  -6.99798986e-02, 9.58312973e-02,\n        1.53593004e-01,  -8.75087008e-02, -4.92327996e-02, -3.32239009e-02};\n    vector<float> im_info{60, 80, 0.166667};\n    vector<float> anchors{-38, -16, 53, 31, -120, -120, 135, 135};\n    {\n      auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(num_images, A, H, W);\n      for (auto i = 0; i < t->size(); ++i) {\n        t->mutable_data<float>()[i] = scores[i];\n      }\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"bbox_delta_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(num_images, 4 * A, H, W);\n      for (auto i = 0; i < t->size(); ++i) {\n        t->mutable_data<float>()[i] = bbx[i];\n      }\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"im_info\")->GetMutable<TensorCPU>();\n      t->Resize(num_images, 3);\n      for (auto i = 0; i < t->size(); ++i) {\n        t->mutable_data<float>()[i] = im_info[i];\n      }\n    }\n\n    {\n      auto* t = ws.CreateBlob(\"anchors\")->GetMutable<TensorCPU>();\n      t->Resize(A, 4);\n      for (auto i = 0; i < t->size(); ++i) {\n        t->mutable_data<float>()[i] = anchors[i];\n      }\n    }\n\n    NetDef netdef;\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"MPSCNNGenerateProposalsCPP\");\n      op.add_input(\"X_cpu\");\n      op.add_input(\"bbox_delta_cpu\");\n      op.add_input(\"im_info\");\n      op.add_input(\"anchors\");\n      op.add_output(\"rois\");\n      op.add_output(\"rois_probs\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"GenerateProposalsCPP\");\n      op.add_input(\"X_cpu\");\n      op.add_input(\"bbox_delta_cpu\");\n      op.add_input(\"im_info\");\n      op.add_input(\"anchors\");\n      op.add_output(\"rois_ref\");\n      op.add_output(\"rois_probs_ref\");\n    }\n\n    ws.RunNetOnce(netdef);\n    const auto& t2 = ws.GetBlob(\"rois\")->Get<TensorCPU>();\n    const auto& t1 = ws.GetBlob(\"rois_ref\")->Get<TensorCPU>();\n\n    const auto& t4 = ws.GetBlob(\"rois_probs\")->Get<TensorCPU>();\n    const auto& t3 = ws.GetBlob(\"rois_probs_ref\")->Get<TensorCPU>();\n\n    LOG(INFO) << \"t1: \" << t1.size() << \" t2: \" << t2.size();\n\n    const float HALF_MIN_VAL = 6.103515625e-05;\n    for (auto i = 0; i < fmin(t1.size(), t2.size()); ++i) {\n      // FP16 <-> FP32 round trip, accumulation, etc.\n      const float t1_i = t1.data<float>()[i];\n      const float t2_i = t2.data<float>()[i];\n      const float t3_i = t3.data<float>()[i / 5];\n      if (t3_i - HALF_MIN_VAL * 2 > 0) {\n        LOG(INFO) << i << \" \" << t1_i << \" \" << t2_i << \" \" << t3_i;\n        CHECK_NEAR(t1_i, t2_i, 0.1);\n      }\n    }\n\n    for (auto i = 0; i < fmin(t3.size(), t4.size()); ++i) {\n      // FP16 <-> FP32 round trip, accumulation, etc.\n      const float t3_i = t3.data<float>()[i];\n      const float t4_i = t4.data<float>()[i];\n      LOG(INFO) << i << \" \" << t3_i;\n      CHECK_NEAR(t3_i, t4_i, 0.1);\n    }\n  }\n\n  {\n    for (const auto& batchSize : std::vector<size_t>{1, 2}) {\n      LOG(INFO) << \"MPSCNNSoftmax Test\";\n      Workspace ws;\n      {\n        auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n        // Only works for spatial dimension of (1, 1) - weird.\n        t->Resize(batchSize, 12, 1, 1);\n        CPUContext ctx;\n        math::RandGaussian<float, CPUContext>(\n            t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n      }\n\n      NetDef netdef;\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(\"CopyToMPSCNN\");\n        op.add_input(\"X_cpu\");\n        op.add_output(\"X_mtl\");\n      }\n\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(\"MPSCNNSoftmax\");\n        op.add_input(\"X_mtl\");\n        op.add_output(\"Y_mtl\");\n      }\n\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(\"CopyFromMPSCNN\");\n        op.add_input(\"Y_mtl\");\n        op.add_output(\"Y_cpu\");\n      }\n\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(\"Softmax\");\n        op.add_input(\"X_cpu\");\n        op.add_output(\"Y_ref\");\n      }\n\n      ws.RunNetOnce(netdef);\n      const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n      const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n      CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n      LOG(INFO) << t1.dims();\n      for (auto i = 0; i < t1.size(); ++i) {\n        // FP16 <-> FP32 round trip, accumulation, etc.\n        const float t1_i = t1.data<float>()[i];\n        const float t2_i = t2.data<float>()[i];\n        CHECK_NEAR(t1_i, t2_i, 0.1);\n      }\n    }\n  }\n\n  @autoreleasepool {\n    for (const auto& inputChannels : std::vector<size_t>{3, 8}) {\n      for (const auto& outputChannels : std::vector<size_t>{3, 8}) {\n        for (const auto& batchSize : std::vector<size_t>{1, 2}) {\n          for (const auto& stride_h : std::vector<int>{1, 2, 3}) {\n            for (const auto& stride_w : std::vector<int>{1, 2, 3}) {\n              for (const auto& kernel_h : std::vector<int>{3}) {\n                for (const auto& kernel_w : std::vector<int>{3}) {\n                  for (const auto& pad_l : std::vector<int>{0, kernel_w / 2}) {\n                    for (const auto& pad_r :\n                         std::vector<int>{0, kernel_w / 2}) {\n                      for (const auto& pad_t :\n                           std::vector<int>{0, kernel_h / 2}) {\n                        for (const auto& pad_b :\n                             std::vector<int>{0, kernel_h / 2}) {\n                          for (const auto& adj : {0, 1, 2, 3}) {\n                            if (adj >= fmin(stride_h, stride_w)) {\n                              continue;\n                            }\n\n                            LOG(INFO) << \"MPSConvTranspose Test\";\n                            Workspace ws;\n                            {\n                              auto* t = ws.CreateBlob(\"X_cpu\")\n                                            ->GetMutable<TensorCPU>();\n                              t->Resize(batchSize, inputChannels, 8, 12);\n                              CPUContext ctx;\n                              math::RandGaussian<float, CPUContext>(\n                                  t->size(),\n                                  0,\n                                  1,\n                                  t->mutable_data<float>(),\n                                  &ctx);\n                            }\n\n                            {\n                              auto* t =\n                                  ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n                              t->Resize(\n                                  inputChannels,\n                                  outputChannels,\n                                  kernel_h,\n                                  kernel_w);\n                              CPUContext ctx;\n                              math::RandGaussian<float, CPUContext>(\n                                  t->size(),\n                                  0,\n                                  1,\n                                  t->mutable_data<float>(),\n                                  &ctx);\n                            }\n\n                            {\n                              auto* t =\n                                  ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n                              t->Resize(outputChannels);\n                              CPUContext ctx;\n                              math::RandGaussian<float, CPUContext>(\n                                  t->size(),\n                                  0,\n                                  1,\n                                  t->mutable_data<float>(),\n                                  &ctx);\n                            }\n\n                            NetDef netdef;\n                            {\n                              auto& op = *(netdef.add_op());\n                              op.set_type(\"CopyToMPSCNN\");\n                              op.add_input(\"X_cpu\");\n                              op.add_output(\"X_mtl\");\n                            }\n\n                            {\n                              auto& op = *(netdef.add_op());\n                              op.set_type(\"MPSCNNConvTranspose\");\n                              op.add_input(\"X_mtl\");\n                              op.add_input(\"W\");\n                              op.add_input(\"b\");\n#define ADD_ARGS(op)                    \\\n  do {                                  \\\n    add_arg_str(op, \"order\", \"NCHW\");   \\\n    add_arg_int_list(                   \\\n        op,                             \\\n        std::vector<string>{\"kernel_h\", \\\n                            \"kernel_w\", \\\n                            \"pad_t\",    \\\n                            \"pad_b\",    \\\n                            \"pad_l\",    \\\n                            \"pad_r\",    \\\n                            \"stride_w\", \\\n                            \"stride_h\", \\\n                            \"adj\"},     \\\n        std::vector<int>{kernel_h,      \\\n                         kernel_w,      \\\n                         pad_t,         \\\n                         pad_b,         \\\n                         pad_l,         \\\n                         pad_r,         \\\n                         stride_w,      \\\n                         stride_h,      \\\n                         adj});         \\\n  } while (false)\n                              ADD_ARGS(op);\n                              op.add_output(\"Y_mtl\");\n                            }\n\n                            {\n                              auto& op = *(netdef.add_op());\n                              op.set_type(\"CopyFromMPSCNN\");\n                              op.add_input(\"Y_mtl\");\n                              op.add_output(\"Y_cpu\");\n                            }\n\n                            {\n                              auto& op = *(netdef.add_op());\n                              op.set_type(\"ConvTranspose\");\n                              op.add_input(\"X_cpu\");\n                              op.add_input(\"W\");\n                              op.add_input(\"b\");\n                              ADD_ARGS(op);\n                              op.add_output(\"Y_ref\");\n                            }\n#undef ADD_ARGS\n\n                            ws.RunNetOnce(netdef);\n                            const auto& t2 =\n                                ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n                            const auto& t1 =\n                                ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n                            CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n                            LOG(INFO) << t1.dims();\n                            for (auto i = 0; i < t1.size(); ++i) {\n                              // FP16 <-> FP32 round trip, accumulation, etc.\n                              const float t1_i = t1.data<float>()[i];\n                              const float t2_i = t2.data<float>()[i];\n                              constexpr float tol = 2.0e-2;\n                              CHECK(\n                                  std::abs(t1_i - t2_i) <=\n                                  (tol + tol * std::abs(t1_i)))\n                                  << t1_i << \", \" << t2_i;\n                            }\n                          }\n                        }\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  {\n    for (const auto array : std::vector<bool>{true, false}) {\n      for (auto numInputs = 2; numInputs <= 4; numInputs++) {\n        for (const auto batchSize : std::vector<size_t>{1, 2}) {\n          auto mtl = [&](size_t i) {\n            return std::string(\"X_mtl_\") + std::to_string(i);\n          };\n          auto cpu = [&](size_t i) {\n            return std::string(\"X_cpu_\") + std::to_string(i);\n          };\n\n          LOG(INFO) << \"MPSCNNConcat Test\" << array << \", \" << numInputs << \", \"\n                    << batchSize;\n          Workspace ws;\n          for (auto i = 0; i < numInputs; ++i) {\n            auto* t = ws.CreateBlob(cpu(i))->GetMutable<TensorCPU>();\n            t->Resize(batchSize, array ? (i + 1) * 4 : 4, 10, 10);\n            CPUContext ctx;\n            math::RandGaussian<float, CPUContext>(\n                t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n          }\n\n          NetDef netdef;\n          {\n            auto& op = *(netdef.add_op());\n            op.set_type(\"CopyToMPSCNN\");\n            for (auto i = 0; i < numInputs; ++i) {\n              op.add_input(cpu(i));\n              op.add_output(mtl(i));\n            }\n          }\n\n          {\n            auto& op = *(netdef.add_op());\n            op.set_type(\"MPSCNNConcat\");\n            for (auto i = 0; i < numInputs; ++i) {\n              op.add_input(mtl(i));\n            }\n            {\n              auto& arg = *(op.add_arg());\n              arg.set_name(\"order\");\n              arg.set_s(\"NCHW\");\n            }\n            op.add_output(\"Y_mtl\");\n            op.add_output(\"Y_mtl_mask\");\n          }\n\n          {\n            auto& op = *(netdef.add_op());\n            op.set_type(\"CopyFromMPSCNN\");\n            op.add_input(\"Y_mtl\");\n            op.add_output(\"Y_cpu\");\n          }\n\n          {\n            auto& op = *(netdef.add_op());\n            op.set_type(\"Concat\");\n            for (auto i = 0; i < numInputs; ++i) {\n              op.add_input(cpu(i));\n            }\n            {\n              auto& arg = *(op.add_arg());\n              arg.set_name(\"order\");\n              arg.set_s(\"NCHW\");\n            }\n\n            op.add_output(\"Y_ref\");\n            op.add_output(\"Y_ref_mask\");\n          }\n\n          ws.RunNetOnce(netdef);\n          const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n          const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n          CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n          LOG(INFO) << t1.dims();\n          for (auto i = 0; i < t1.size(); ++i) {\n            // FP16 <-> FP32 round trip, accumulation, etc.\n            const float t1_i = t1.data<float>()[i];\n            const float t2_i = t2.data<float>()[i];\n            CHECK_NEAR(t1_i, t2_i, 0.1);\n          }\n        }\n      }\n    }\n  }\n\n  {\n    for (const auto channelCount : std::vector<size_t>{1, 2, 3, 4}) {\n      for (auto numInputs = 2; numInputs <= 4; numInputs++) {\n        for (const auto batchSize : std::vector<size_t>{1, 2}) {\n          auto mtl = [&](size_t i) {\n            return std::string(\"X_mtl_\") + std::to_string(i);\n          };\n          auto cpu = [&](size_t i) {\n            return std::string(\"X_cpu_\") + std::to_string(i);\n          };\n\n          LOG(INFO) << \"MPSCNNConcat(edge case) Test\" << channelCount << \", \"\n                    << numInputs << \", \" << batchSize;\n          Workspace ws;\n          for (auto i = 0; i < numInputs; ++i) {\n            auto* t = ws.CreateBlob(cpu(i))->GetMutable<TensorCPU>();\n            t->Resize(batchSize, channelCount, 9, 17);\n            CPUContext ctx;\n            math::RandGaussian<float, CPUContext>(\n                t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n          }\n\n          NetDef netdef;\n          {\n            auto& op = *(netdef.add_op());\n            op.set_type(\"CopyToMPSCNN\");\n            for (auto i = 0; i < numInputs; ++i) {\n              op.add_input(cpu(i));\n              op.add_output(mtl(i));\n            }\n          }\n\n          {\n            auto& op = *(netdef.add_op());\n            op.set_type(\"MPSCNNConcat\");\n            for (auto i = 0; i < numInputs; ++i) {\n              op.add_input(mtl(i));\n            }\n            {\n              auto& arg = *(op.add_arg());\n              arg.set_name(\"order\");\n              arg.set_s(\"NCHW\");\n            }\n            op.add_output(\"Y_mtl\");\n            op.add_output(\"Y_mtl_mask\");\n          }\n\n          {\n            auto& op = *(netdef.add_op());\n            op.set_type(\"CopyFromMPSCNN\");\n            op.add_input(\"Y_mtl\");\n            op.add_output(\"Y_cpu\");\n          }\n\n          {\n            auto& op = *(netdef.add_op());\n            op.set_type(\"Concat\");\n            for (auto i = 0; i < numInputs; ++i) {\n              op.add_input(cpu(i));\n            }\n            {\n              auto& arg = *(op.add_arg());\n              arg.set_name(\"order\");\n              arg.set_s(\"NCHW\");\n            }\n\n            op.add_output(\"Y_ref\");\n            op.add_output(\"Y_ref_mask\");\n          }\n\n          ws.RunNetOnce(netdef);\n          const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n\n          const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n          CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n          LOG(INFO) << t1.dims();\n          for (auto i = 0; i < t1.size(); ++i) {\n            // FP16 <-> FP32 round trip, accumulation, etc.\n            const float t1_i = t1.data<float>()[i];\n            const float t2_i = t2.data<float>()[i];\n            CHECK_NEAR(t1_i, t2_i, 0.1);\n          }\n        }\n      }\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNReadCount Test\";\n    NetDef netdef;\n    {\n      auto& op = *(netdef.add_op());\n      op.add_input(\"X_cpu\");\n      op.add_output(\"X_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.add_input(\"X_mtl\");\n      op.add_output(\"X_mtl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.add_input(\"X_mtl\");\n      op.add_output(\"Y\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.add_input(\"X_mtl\");\n      op.add_output(\"X_mtl\");\n    }\n    netdef = annotateDefWithReadCounts(netdef);\n    auto rc = [&](size_t i) -> size_t {\n      auto* arg = GetMutableArgument(\n          \"__mpscnn_read_count__\", false, netdef.mutable_op(i));\n      if (!arg) {\n        return 1;\n      }\n      return arg->i();\n    };\n    CHECK_EQ(rc(0), 1);\n    CHECK_EQ(rc(1), 2);\n    CHECK_EQ(rc(2), 1);\n    CHECK_EQ(rc(3), 1);\n  }\n\n  {\n    for (const auto& computeOp : std::vector<std::string>{\"FC\", \"Conv\"}) {\n      LOG(INFO) << \"MPSCNNRewriteForMetal Fusion/Copy Test\";\n      NetDef netdef;\n      netdef.add_external_input(\"X\");\n      netdef.add_external_output(\"Y\");\n      // These two ops can be fused.\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(computeOp);\n        op.add_input(\"X\");\n        op.add_input(\"W\");\n        op.add_input(\"b\");\n        op.add_output(\"Y\");\n      }\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(\"Relu\");\n        op.add_input(\"Y\");\n        op.add_output(\"Y\");\n      }\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(computeOp);\n        op.add_input(\"X2\");\n        op.add_input(\"W\");\n        op.add_input(\"b\");\n        op.add_output(\"Y2\");\n      }\n      {\n        auto& op = *(netdef.add_op());\n        op.set_type(\"Relu\");\n        op.add_input(\"Y2\");\n        op.add_output(\"Y\");\n      }\n      netdef = rewriteForMetal(netdef);\n      auto ty = [&](size_t i) { return netdef.op(i).type(); };\n      auto i0 = [&](size_t i) { return netdef.op(i).input(0); };\n      auto o0 = [&](size_t i) { return netdef.op(i).output(0); };\n      CHECK_EQ(netdef.op_size(), 4);\n      CHECK_EQ(ty(0), \"CopyToMPSCNN\");\n      CHECK_EQ(ty(1), std::string(\"MPSCNN\") + computeOp + std::string(\"Relu\"));\n      CHECK_EQ(ty(2), std::string(\"MPSCNN\") + computeOp + std::string(\"Relu\"));\n      CHECK_EQ(ty(3), \"CopyFromMPSCNN\");\n      CHECK_EQ(i0(0), \"X\");\n      CHECK_EQ(i0(1), o0(0));\n      CHECK_EQ(i0(2), \"X2\");\n      CHECK_EQ(o0(2), i0(3));\n      CHECK_EQ(o0(3), \"Y\");\n      CHECK_EQ(netdef.external_input(0), \"X\");\n      CHECK_EQ(netdef.external_output(0), \"Y\");\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNRewriteForMetal Failure Test\";\n    NetDef netdef;\n    netdef.add_external_input(\"X\");\n    netdef.add_external_output(\"Y\");\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Conv\");\n      op.add_input(\"X\");\n      op.add_input(\"W\");\n      op.add_input(\"b\");\n      op.add_output(\"Y1\");\n    }\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Conv\");\n      op.add_input(\"X\");\n      op.add_input(\"W\");\n      op.add_input(\"b\");\n      op.add_output(\"Y2\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Concat\");\n      op.add_input(\"Y1\");\n      op.add_input(\"Y2\");\n      op.add_output(\"Y\");\n    }\n    try {\n      netdef = rewriteForMetal(netdef);\n      CHECK(false) << \"Shouldn't reach here, due to multiple usages of X\";\n    } catch (const std::exception& e) {\n      // Nothing.\n    }\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNRewriteForMetal out-of-place Fusion Test\";\n    NetDef netdef;\n    netdef.add_external_input(\"X\");\n    netdef.add_external_output(\"Z\");\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Conv\");\n      op.add_input(\"X\");\n      op.add_input(\"W\");\n      op.add_input(\"b\");\n      op.add_output(\"Y\");\n    }\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Relu\");\n      op.add_input(\"Y\");\n      op.add_output(\"Z\");\n    }\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Relu\");\n      op.add_input(\"Z\");\n      op.add_output(\"Z\");\n    }\n    netdef = rewriteForMetal(netdef);\n    CHECK_EQ(netdef.op_size(), 4);\n    auto ty = [&](size_t i) { return netdef.op(i).type(); };\n    auto i0 = [&](size_t i) { return netdef.op(i).input(0); };\n    auto o0 = [&](size_t i) { return netdef.op(i).output(0); };\n    CHECK_EQ(ty(0), \"CopyToMPSCNN\");\n    CHECK_EQ(ty(1), \"MPSCNNConvRelu\");\n    CHECK_EQ(ty(2), \"MPSCNNRelu\");\n    CHECK_EQ(ty(3), \"CopyFromMPSCNN\");\n    CHECK_EQ(i0(1), o0(0));\n    CHECK_EQ(o0(1), \"Z\");\n    CHECK_EQ(i0(2), \"Z\");\n    CHECK_EQ(o0(2), i0(3));\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNRewriteForMetal out-of-place fusion failure test\";\n    NetDef netdef;\n    netdef.add_external_input(\"X\");\n    netdef.add_external_output(\"Z\");\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Conv\");\n      op.add_input(\"X\");\n      op.add_input(\"W\");\n      op.add_input(\"b\");\n      op.add_output(\"Y\");\n    }\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Relu\");\n      op.add_input(\"Y\");\n      op.add_output(\"Z\");\n    }\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Relu\");\n      op.add_input(\"Y\");\n      op.add_output(\"Z\");\n    }\n    netdef = rewriteForMetal(netdef);\n    CHECK_EQ(netdef.op_size(), 5);\n    auto ty = [&](size_t i) { return netdef.op(i).type(); };\n    auto i0 = [&](size_t i) { return netdef.op(i).input(0); };\n    auto o0 = [&](size_t i) { return netdef.op(i).output(0); };\n    CHECK_EQ(ty(0), \"CopyToMPSCNN\");\n    CHECK_EQ(ty(1), \"MPSCNNConv\");\n    CHECK_EQ(ty(2), \"MPSCNNRelu\");\n    CHECK_EQ(ty(3), \"MPSCNNRelu\");\n    CHECK_EQ(ty(4), \"CopyFromMPSCNN\");\n    CHECK_EQ(i0(1), o0(0));\n    CHECK_EQ(o0(1), \"Y\");\n    CHECK_EQ(i0(2), o0(1));\n    CHECK_EQ(o0(2), \"Z\");\n    CHECK_EQ(i0(3), o0(1));\n    CHECK_EQ(o0(3), i0(4));\n  }\n\n  {\n    LOG(INFO) << \"MPSCNNRewriteForMetal PreProcess/Deprocess Test\";\n    NetDef netdef;\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"PackedInt8BGRANHWCToNCHWCStylizerPreprocess\");\n      op.add_input(\"X\");\n      op.add_output(\"Y\");\n    }\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Relu\");\n      op.add_input(\"Y\");\n      op.add_output(\"Y\");\n    }\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"BRGNCHWCToPackedInt8BGRAStylizerDeprocess\");\n      op.add_input(\"Y\");\n      op.add_output(\"Z\");\n    }\n    netdef = rewriteForMetal(netdef);\n    auto ty = [&](size_t i) { return netdef.op(i).type(); };\n    auto i0 = [&](size_t i) { return netdef.op(i).input(0); };\n    auto o0 = [&](size_t i) { return netdef.op(i).output(0); };\n    CHECK_EQ(netdef.op_size(), 3);\n    CHECK_EQ(ty(0), \"MPSCNNPackedInt8BGRANHWCToNCHWCStylizerPreprocess\");\n    CHECK_EQ(ty(1), \"MPSCNNRelu\");\n    CHECK_EQ(ty(2), \"MPSCNNBRGNCHWCToPackedInt8BGRAStylizerDeprocess\");\n    CHECK_EQ(i0(0), \"X\");\n    CHECK_EQ(i0(1), o0(0));\n    CHECK_EQ(i0(2), o0(1));\n    CHECK_EQ(o0(2), \"Z\");\n  }\n  LOG(INFO) << \"All MPSCNN tests passed.\";\n}\n\nNetDef truncateAfter(NetDef def, size_t idx) {\n  // idx = 0, net = 10 -> remove 9\n  // idx = 0, net = 1 -> remove 0\n  const auto toRemove = def.op_size() - idx - 1;\n  for (auto i = 0; i < toRemove; ++i) {\n    def.mutable_op()->RemoveLast();\n  }\n  CHECK_EQ(def.op_size(), idx + 1);\n  return def;\n}\n\nNetDef addMPSCNNCopyFinalizer(NetDef def) {\n  CHECK_GE(def.op_size(), 1);\n  const auto name = def.mutable_op(def.op_size() - 1)->output(0);\n  def.mutable_op(def.op_size() - 1)->set_output(0, \"METAL_COPIER\");\n  {\n    auto& op = *(def.add_op());\n    op.set_type(\"CopyFromMPSCNN\");\n    op.add_input(\"METAL_COPIER\");\n    op.add_output(name);\n  }\n  return def;\n}\n\nvoid compareModels(const NetDef& initNet, NetDef predictNet) {\n  auto* arg = predictNet.mutable_op(0)->mutable_arg(0);\n  CHECK_EQ(arg->name(), \"noise_std\");\n  arg->set_f(0.000001);\n\n  NetDef metalPredictNet;\n  CAFFE_ENFORCE(tryConvertToMPSCNN(initNet, predictNet, &metalPredictNet));\n\n  // TODO: consider last op as well.\n  for (auto i = 0; i < predictNet.op_size(); ++i) {\n    auto truncatedPredictNet = truncateAfter(predictNet, i);\n    auto truncatedMetalPredictNet = truncateAfter(metalPredictNet, i);\n    // For all but the last op, we need to add a copy op.\n    if (i != predictNet.op_size() - 1) {\n      truncatedMetalPredictNet =\n          addMPSCNNCopyFinalizer(truncatedMetalPredictNet);\n    }\n\n    dumpDef(truncatedPredictNet);\n    dumpDef(truncatedMetalPredictNet);\n\n    Workspace cws;\n    cws.RunNetOnce(initNet);\n    {\n      auto* t =\n          cws.CreateBlob(predictNet.external_input(0))->GetMutable<TensorCPU>();\n      t->Resize(1, 224, 224, 4);\n      for (auto i = 0; i < t->size(); ++i) {\n        t->mutable_data<uint8_t>()[i] = i % 225;\n      }\n    }\n    cws.RunNetOnce(truncatedPredictNet);\n\n    Workspace mws;\n    mws.RunNetOnce(initNet);\n    {\n      auto* t =\n          mws.CreateBlob(predictNet.external_input(0))->GetMutable<TensorCPU>();\n      t->Resize(1, 224, 224, 4);\n      for (auto i = 0; i < t->size(); ++i) {\n        t->mutable_data<uint8_t>()[i] = i % 225;\n      }\n    }\n    mws.RunNetOnce(truncatedMetalPredictNet);\n\n    const auto name =\n        truncatedPredictNet.op(truncatedPredictNet.op_size() - 1).output(0);\n\n    LOG(INFO) << \"Checking correspondence for name: \" << name << \", idx: \" << i;\n    {\n      const auto& mt = mws.GetBlob(name)->Get<TensorCPU>();\n      const auto& ct = cws.GetBlob(name)->Get<TensorCPU>();\n      CHECK_EQ(mt.dims(), ct.dims());\n      for (auto j = 0; j < mt.size(); ++j) {\n        if (mt.IsType<float>()) {\n          if (j < 10) {\n            LOG(INFO) << \"i: \" << i << \", j: \" << j\n                      << \", CPU: \" << ct.data<float>()[j]\n                      << \", MTL: \" << mt.data<float>()[j];\n          }\n          CHECK_NEAR(mt.data<float>()[j], ct.data<float>()[j], 5);\n        } else {\n          CHECK(mt.IsType<uint8_t>());\n          if (j < 10) {\n            LOG(INFO) << \"i: \" << i << \", j: \" << j\n                      << \", CPU: \" << ct.data<uint8_t>()[j]\n                      << \", MTL: \" << mt.data<uint8_t>()[j];\n          }\n          CHECK_NEAR(mt.data<uint8_t>()[j], ct.data<uint8_t>()[j], 5);\n        }\n      }\n    }\n  }\n}\nvoid verifyRewrite(\n    const NetDef& initNet,\n    const NetDef& net,\n    std::vector<int> inputDims) {\n  NetDef metalPredictNet;\n  NetDef predictNet = setSpecialArgs(net);\n  CAFFE_ENFORCE(tryConvertToMPSCNNIntermediateCopies(\n      initNet, predictNet, &metalPredictNet));\n  dumpDef(predictNet);\n  dumpDef(metalPredictNet);\n\n#define RUN_NET(ws, predictNet)                                               \\\n  ws.RunNetOnce(initNet);                                                     \\\n  {                                                                           \\\n    auto* t =                                                                 \\\n        ws.CreateBlob(predictNet.external_input(0))->GetMutable<TensorCPU>(); \\\n    t->Resize(inputDims);                                                     \\\n    CPUContext ctx;                                                           \\\n    math::RandGaussian<float, CPUContext>(                                    \\\n        t->size(), 0, 1, t->mutable_data<float>(), &ctx);                     \\\n  }                                                                           \\\n  ws.RunNetOnce(predictNet);\n\n  // initialize\n  getMPSCNNContext();\n\n  Workspace cws;\n  RUN_NET(cws, predictNet);\n\n  Workspace mws;\n  RUN_NET(mws, metalPredictNet);\n\n  for (auto i = 0; i < predictNet.external_output_size(); i++) {\n    auto blobName = predictNet.external_output(i);\n    LOG(INFO) << \"Checking output blob:\" << blobName;\n    const auto& mt = mws.GetBlob(blobName)->Get<TensorCPU>();\n    const auto& ct = cws.GetBlob(blobName)->Get<TensorCPU>();\n    if (mt.size() == 0 || ct.size() == 0) {\n      LOG(INFO) << \"One of the operator failed.\";\n      return;\n    }\n    // CHECK_EQ(mt.dims(), ct.dims());\n    for (auto j = 0; j < fmin(mt.size(), ct.size()); ++j) {\n      if (mt.IsType<float>()) {\n        if (j < 10) {\n          LOG(INFO) << \"i: \" << i << \", j: \" << j\n                    << \", CPU: \" << ct.data<float>()[j]\n                    << \", MTL: \" << mt.data<float>()[j];\n        }\n        // Disabling check for now because of precision issues\n        // CHECK_NEAR(mt.data<float>()[j], ct.data<float>()[j], 5);\n      } else {\n        LOG(INFO) << \"Type uint8_t\";\n        CHECK(mt.IsType<uint8_t>());\n        if (j < 10) {\n          LOG(INFO) << \"i: \" << i << \", j: \" << j\n                    << \", CPU: \" << ct.data<uint8_t>()[j]\n                    << \", MTL: \" << mt.data<uint8_t>()[j];\n        }\n        // Disabling check for now.\n        // CHECK_NEAR(mt.data<uint8_t>()[j], ct.data<uint8_t>()[j], 5);\n      }\n    }\n  }\n  LOG(INFO) << \"rewrite test passed.\";\n}\n}\n\n#endif\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/pool_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include \"gtest/gtest.h\"\n\n#include <cmath>\n#include <random>\n\nnamespace caffe2 {\n\nnamespace {\n\nvoid AddNoiseInput(const vector<TIndex>& shape, const string& name, Workspace* ws) {\n  DeviceOption option;\n  CPUContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(shape);\n\n  math::RandGaussian<float, CPUContext>(\n      tensor->size(), 0.0f, 3.0f, tensor->mutable_data<float>(), &context);\n  for (auto i = 0; i < tensor->size(); ++i) {\n    tensor->mutable_data<float>()[i] =\n        std::min(-5.0f, std::max(5.0f, tensor->mutable_data<float>()[i]));\n  }\n}\n\nvoid compareMaxPooling(int N,\n                       int C,\n                       int H,\n                       int W,\n                       int kernelH,\n                       int kernelW,\n                       int strideH,\n                       int strideW,\n                       int padT,\n                       int padL,\n                       int padB,\n                       int padR,\n                       float maxRelErr = 1.0e-5f,\n                       float absErrForRelErrFailure = 1.0e-5f) {\n  Workspace ws;\n\n  OperatorDef def1;\n  def1.set_name(\"test\");\n  def1.set_type(\"MaxPool\");\n  def1.add_input(\"X\");\n  def1.add_output(\"Y\");\n\n  def1.add_arg()->CopyFrom(MakeArgument(\"kernel_h\", kernelH));\n  def1.add_arg()->CopyFrom(MakeArgument(\"kernel_w\", kernelW));\n  def1.add_arg()->CopyFrom(MakeArgument(\"stride_h\", strideH));\n  def1.add_arg()->CopyFrom(MakeArgument(\"stride_w\", strideW));\n  def1.add_arg()->CopyFrom(MakeArgument(\"pad_t\", padT));\n  def1.add_arg()->CopyFrom(MakeArgument(\"pad_l\", padL));\n  def1.add_arg()->CopyFrom(MakeArgument(\"pad_b\", padB));\n  def1.add_arg()->CopyFrom(MakeArgument(\"pad_r\", padR));\n\n  AddNoiseInput(vector<TIndex>{N, C, H, W}, \"X\", &ws);\n\n  unique_ptr<OperatorBase> op1(CreateOperator(def1, &ws));\n  EXPECT_NE(nullptr, op1.get());\n  EXPECT_TRUE(op1->Run());\n\n  const auto& X = ws.GetBlob(\"X\")->Get<TensorCPU>();\n  const auto& Y = ws.GetBlob(\"Y\")->Get<TensorCPU>();\n\n  // Compare all output points\n  for (int n = 0; n < Y.dim32(0); ++n) {\n    for (int c = 0; c < Y.dim32(1); ++c) {\n      for (int ph = 0; ph < Y.dim32(2); ++ph) {\n        for (int pw = 0; pw < Y.dim32(3); ++pw) {\n          // Reference implementations\n          int hstart = ph * strideH - padT;\n          int wstart = pw * strideW - padL;\n          int hend = std::min(hstart + kernelH, H);\n          int wend = std::min(wstart + kernelW, W);\n          hstart = std::max(hstart, 0);\n          wstart = std::max(wstart, 0);\n          const int pool_index = ph * Y.dim32(3) + pw;\n          float v = std::numeric_limits<float>::lowest();\n          for (int h = hstart; h < hend; ++h) {\n            for (int w = wstart; w < wend; ++w) {\n              const auto* Xdata =\n                  X.data<float>() + n * X.dim(1) * X.dim(2) * X.dim(3) + c * X.dim(2) * X.dim(3);\n              const int input_index = h * W + w;\n              v = std::max(v, Xdata[input_index]);\n            }\n          }\n          EXPECT_EQ(Y.data<float>()[n * Y.dim(1) * Y.dim(2) * Y.dim(3) + c * Y.dim(2) * Y.dim(3) +\n                                    ph * Y.dim(3) + pw],\n                    v);\n        }\n      }\n    }\n  }\n}\n\nint randInt(int a, int b) {\n  static std::random_device rd;\n  static std::mt19937 gen(rd());\n  return std::uniform_int_distribution<int>(a, b)(gen);\n}\n\nvoid runMaxPool(int kernel, int stride, int pad) {\n  int N = randInt(1, 2);\n  int C = randInt(1, 12);\n  int H = randInt(50, 100);\n  int W = randInt(50, 100);\n  int planesOut = randInt(1, 6);\n\n  compareMaxPooling(N, C, H, W, kernel, kernel, stride, stride, pad, pad, pad, pad);\n}\n\nTEST(PoolOp, MaxPool2x2s2p0Randomized) {\n  for (int i = 0; i < 40; ++i) {\n    runMaxPool(2, 2, 0);\n  }\n}\n\nTEST(PoolOp, MaxPool4x4s3p2Randomized) {\n  for (int i = 0; i < 40; ++i) {\n    runMaxPool(4, 3, 2);\n  }\n}\n\nTEST(PoolOp, MaxPool2x2s2p0Special) {\n  // 2x2s2p0 where H/W % 4 == 0\n  compareMaxPooling(2, 10, 40, 40, 2, 2, 2, 2, 0, 0, 0, 0, 0.05f, 0.1f);\n\n  // 2x2s2p0 where H/W % 4 != 0\n  compareMaxPooling(2, 10, 39, 39, 2, 2, 2, 2, 0, 0, 0, 0, 0.05f, 0.1f);\n\n  // 2x2s2p0 where H/W % 16 == 0\n  compareMaxPooling(2, 10, 64, 64, 2, 2, 2, 2, 0, 0, 0, 0, 0.05f, 0.1f);\n}\n\nTEST(PoolOp, MaxPoolFullyRandomized) {\n  for (auto i = 0; i < 40; ++i) {\n    auto kernelH = randInt(1, 5);\n    auto kernelW = randInt(1, 5);\n    auto strideH = randInt(1, 5);\n    auto strideW = randInt(1, 5);\n    auto padL = randInt(0, kernelW - 1);\n    auto padR = randInt(0, kernelW - 1);\n    auto padT = randInt(0, kernelH - 1);\n    auto padB = randInt(0, kernelH - 1);\n    auto H = randInt(std::max(1, kernelH - padT - padB), 100);\n    auto W = randInt(std::max(1, kernelW - padL - padR), 100);\n    auto C = randInt(1, 10);\n    auto N = randInt(1, 2);\n    compareMaxPooling(\n        N, C, H, W, kernelH, kernelW, strideH, strideW, padT, padL, padB, padR);\n  }\n}\n} // unnamed namespace\n\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/ios/resize_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include \"gtest/gtest.h\"\n\n#include <cmath>\n#include <random>\n\nnamespace caffe2 {\n\nnamespace {\n\nvoid AddNoiseInput(const vector<TIndex>& shape, const string& name, Workspace* ws) {\n  DeviceOption option;\n  CPUContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(shape);\n\n  math::RandGaussian<float, CPUContext>(\n      tensor->size(), 0.0f, 3.0f, tensor->mutable_data<float>(), &context);\n  for (auto i = 0; i < tensor->size(); ++i) {\n    tensor->mutable_data<float>()[i] =\n        std::min(-5.0f, std::max(5.0f, tensor->mutable_data<float>()[i]));\n  }\n}\n\nvoid compareResizeNeareast(int N,\n                       int C,\n                       int H,\n                       int W,\n                       float wscale,\n                       float hscale) {\n  Workspace ws;\n\n  OperatorDef def1;\n  def1.set_name(\"test\");\n  def1.set_type(\"ResizeNearest\");\n  def1.add_input(\"X\");\n  def1.add_output(\"Y\");\n\n  def1.add_arg()->CopyFrom(MakeArgument(\"width_scale\", wscale));\n  def1.add_arg()->CopyFrom(MakeArgument(\"height_scale\", hscale));\n\n  AddNoiseInput(vector<TIndex>{N, C, H, W}, \"X\", &ws);\n\n  unique_ptr<OperatorBase> op1(CreateOperator(def1, &ws));\n  EXPECT_NE(nullptr, op1.get());\n  EXPECT_TRUE(op1->Run());\n\n  const auto& X = ws.GetBlob(\"X\")->Get<TensorCPU>();\n  const auto& Y = ws.GetBlob(\"Y\")->Get<TensorCPU>();\n\n  const float* Xdata = X.data<float>();\n  const float* Ydata = Y.data<float>();\n\n  int outW = W * wscale;\n  int outH = H * hscale;\n\n  // Compare all output points\n  for (int n = 0; n < N; ++n) {\n    for (int c = 0; c < C; ++c) {\n      for (int ph = 0; ph < outH; ++ph) {\n        const int iny = std::min((int)(ph / hscale), (H - 1));\n        for (int pw = 0; pw < outW; ++pw) {\n          const int inx = std::min((int)(pw / wscale), (W - 1));\n          const float v = Xdata[iny * W + inx];\n          EXPECT_EQ(Ydata[outW * ph + pw], v);\n        }\n      }\n      Xdata += H * W;\n      Ydata += outW * outH;\n    }\n  }\n}\n\nint randInt(int a, int b) {\n  static std::random_device rd;\n  static std::mt19937 gen(rd());\n  return std::uniform_int_distribution<int>(a, b)(gen);\n}\n\nTEST(ResizeNearestOp, ResizeNearest2x) {\n  for (auto i = 0; i < 40; ++i) {\n    auto H = randInt(1, 100);\n    auto W = randInt(1, 100);\n    auto C = randInt(1, 10);\n    auto N = randInt(1, 2);\n    compareResizeNeareast(N, C, H, W, 2.0f, 2.0f);\n  }\n}\n\n} // unnamed namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/libopencl-stub/Android.mk",
    "content": "# Android makefile\n# Build this using ndk as\n# ndk-build NDK_PROJECT_PATH=.  APP_BUILD_SCRIPT=Android.mk\n#\n\nLOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := libOpenCL\nLOCAL_C_INCLUDES := $(LOCAL_PATH)/include/\nLOCAL_SRC_FILES :=  src/libopencl.c\nLOCAL_CFLAGS   = -fPIC -O2\n\ninclude $(BUILD_STATIC_LIBRARY)\n\n"
  },
  {
    "path": "caffe2/mobile/contrib/libopencl-stub/LICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org>"
  },
  {
    "path": "caffe2/mobile/contrib/libopencl-stub/Makefile",
    "content": "CC = gcc\nRANLIB = ranlib\n\nLIBSRC = src/libopencl.c\nLIBOBJ=$(LIBSRC:.c=.o)\n\nCFLAGS = -O2 -fPIC -I ./include -Wall\n\nLIBOPENCL = libOpenCL.a\nTARGETS = $(LIBOPENCL)\n\nall: $(TARGETS)\n\nlibopencl.o: libopencl.c\n\t$(CC) $(CFLAGS) -c libopencl.c -o libopencl.o\n\n$(TARGETS): $(LIBOBJ)\n\tar rcs $(LIBOPENCL) src/libopencl.o\n\t$(RANLIB) $(LIBOPENCL)\n\nclean:\n\trm -f $(TARGETS) $(LIBOBJ)\n\n"
  },
  {
    "path": "caffe2/mobile/contrib/libopencl-stub/README.md",
    "content": "libopencl-stub\n==============\n\nA stub opecl library that dynamically dlopen/dlsyms opencl implementations at runtime based on environment variables. Will be useful when opencl implementations are installed in non-standard paths (say pocl on android)\n\n\n\n LIBOPENCL_SO_PATH      -- Path to opencl so that will be searched first\n \n LIBOPENCL_SO_PATH_2    -- Searched second\n \n LIBOPENCL_SO_PATH_3    -- Searched third\n \n LIBOPENCL_SO_PATH_4    -- Searched fourth\n \n \n \n\nDefault paths will be searched otherwise\n\n"
  },
  {
    "path": "caffe2/mobile/contrib/libopencl-stub/include/CL/cl.h",
    "content": "/*******************************************************************************\n * Copyright (c) 2008 - 2012 The Khronos Group Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and/or associated documentation files (the\n * \"Materials\"), to deal in the Materials without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Materials, and to\n * permit persons to whom the Materials are furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Materials.\n *\n * THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.\n ******************************************************************************/\n\n#ifndef __OPENCL_CL_H\n#define __OPENCL_CL_H\n\n#ifdef __APPLE__\n#include <OpenCL/cl_platform.h>\n#else\n#include <CL/cl_platform.h>\n#endif\t\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/******************************************************************************/\n\ntypedef struct _cl_platform_id *    cl_platform_id;\ntypedef struct _cl_device_id *      cl_device_id;\ntypedef struct _cl_context *        cl_context;\ntypedef struct _cl_command_queue *  cl_command_queue;\ntypedef struct _cl_mem *            cl_mem;\ntypedef struct _cl_program *        cl_program;\ntypedef struct _cl_kernel *         cl_kernel;\ntypedef struct _cl_event *          cl_event;\ntypedef struct _cl_sampler *        cl_sampler;\n\ntypedef cl_uint             cl_bool;                     /* WARNING!  Unlike cl_ types in cl_platform.h, cl_bool is not guaranteed to be the same size as the bool in kernels. */\ntypedef cl_ulong            cl_bitfield;\ntypedef cl_bitfield         cl_device_type;\ntypedef cl_uint             cl_platform_info;\ntypedef cl_uint             cl_device_info;\ntypedef cl_bitfield         cl_device_fp_config;\ntypedef cl_uint             cl_device_mem_cache_type;\ntypedef cl_uint             cl_device_local_mem_type;\ntypedef cl_bitfield         cl_device_exec_capabilities;\ntypedef cl_bitfield         cl_command_queue_properties;\ntypedef intptr_t            cl_device_partition_property;\ntypedef cl_bitfield         cl_device_affinity_domain;\n\ntypedef intptr_t            cl_context_properties;\ntypedef cl_uint             cl_context_info;\ntypedef cl_uint             cl_command_queue_info;\ntypedef cl_uint             cl_channel_order;\ntypedef cl_uint             cl_channel_type;\ntypedef cl_bitfield         cl_mem_flags;\ntypedef cl_uint             cl_mem_object_type;\ntypedef cl_uint             cl_mem_info;\ntypedef cl_bitfield         cl_mem_migration_flags;\ntypedef cl_uint             cl_image_info;\ntypedef cl_uint             cl_buffer_create_type;\ntypedef cl_uint             cl_addressing_mode;\ntypedef cl_uint             cl_filter_mode;\ntypedef cl_uint             cl_sampler_info;\ntypedef cl_bitfield         cl_map_flags;\ntypedef cl_uint             cl_program_info;\ntypedef cl_uint             cl_program_build_info;\ntypedef cl_uint             cl_program_binary_type;\ntypedef cl_int              cl_build_status;\ntypedef cl_uint             cl_kernel_info;\ntypedef cl_uint             cl_kernel_arg_info;\ntypedef cl_uint             cl_kernel_arg_address_qualifier;\ntypedef cl_uint             cl_kernel_arg_access_qualifier;\ntypedef cl_bitfield         cl_kernel_arg_type_qualifier;\ntypedef cl_uint             cl_kernel_work_group_info;\ntypedef cl_uint             cl_event_info;\ntypedef cl_uint             cl_command_type;\ntypedef cl_uint             cl_profiling_info;\n\n\ntypedef struct _cl_image_format {\n    cl_channel_order        image_channel_order;\n    cl_channel_type         image_channel_data_type;\n} cl_image_format;\n\ntypedef struct _cl_image_desc {\n    cl_mem_object_type      image_type;\n    size_t                  image_width;\n    size_t                  image_height;\n    size_t                  image_depth;\n    size_t                  image_array_size;\n    size_t                  image_row_pitch;\n    size_t                  image_slice_pitch;\n    cl_uint                 num_mip_levels;\n    cl_uint                 num_samples;\n    cl_mem                  buffer;\n} cl_image_desc;\n\ntypedef struct _cl_buffer_region {\n    size_t                  origin;\n    size_t                  size;\n} cl_buffer_region;\n\n\n/******************************************************************************/\n\n/* Error Codes */\n#define CL_SUCCESS                                  0\n#define CL_DEVICE_NOT_FOUND                         -1\n#define CL_DEVICE_NOT_AVAILABLE                     -2\n#define CL_COMPILER_NOT_AVAILABLE                   -3\n#define CL_MEM_OBJECT_ALLOCATION_FAILURE            -4\n#define CL_OUT_OF_RESOURCES                         -5\n#define CL_OUT_OF_HOST_MEMORY                       -6\n#define CL_PROFILING_INFO_NOT_AVAILABLE             -7\n#define CL_MEM_COPY_OVERLAP                         -8\n#define CL_IMAGE_FORMAT_MISMATCH                    -9\n#define CL_IMAGE_FORMAT_NOT_SUPPORTED               -10\n#define CL_BUILD_PROGRAM_FAILURE                    -11\n#define CL_MAP_FAILURE                              -12\n#define CL_MISALIGNED_SUB_BUFFER_OFFSET             -13\n#define CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST -14\n#define CL_COMPILE_PROGRAM_FAILURE                  -15\n#define CL_LINKER_NOT_AVAILABLE                     -16\n#define CL_LINK_PROGRAM_FAILURE                     -17\n#define CL_DEVICE_PARTITION_FAILED                  -18\n#define CL_KERNEL_ARG_INFO_NOT_AVAILABLE            -19\n\n#define CL_INVALID_VALUE                            -30\n#define CL_INVALID_DEVICE_TYPE                      -31\n#define CL_INVALID_PLATFORM                         -32\n#define CL_INVALID_DEVICE                           -33\n#define CL_INVALID_CONTEXT                          -34\n#define CL_INVALID_QUEUE_PROPERTIES                 -35\n#define CL_INVALID_COMMAND_QUEUE                    -36\n#define CL_INVALID_HOST_PTR                         -37\n#define CL_INVALID_MEM_OBJECT                       -38\n#define CL_INVALID_IMAGE_FORMAT_DESCRIPTOR          -39\n#define CL_INVALID_IMAGE_SIZE                       -40\n#define CL_INVALID_SAMPLER                          -41\n#define CL_INVALID_BINARY                           -42\n#define CL_INVALID_BUILD_OPTIONS                    -43\n#define CL_INVALID_PROGRAM                          -44\n#define CL_INVALID_PROGRAM_EXECUTABLE               -45\n#define CL_INVALID_KERNEL_NAME                      -46\n#define CL_INVALID_KERNEL_DEFINITION                -47\n#define CL_INVALID_KERNEL                           -48\n#define CL_INVALID_ARG_INDEX                        -49\n#define CL_INVALID_ARG_VALUE                        -50\n#define CL_INVALID_ARG_SIZE                         -51\n#define CL_INVALID_KERNEL_ARGS                      -52\n#define CL_INVALID_WORK_DIMENSION                   -53\n#define CL_INVALID_WORK_GROUP_SIZE                  -54\n#define CL_INVALID_WORK_ITEM_SIZE                   -55\n#define CL_INVALID_GLOBAL_OFFSET                    -56\n#define CL_INVALID_EVENT_WAIT_LIST                  -57\n#define CL_INVALID_EVENT                            -58\n#define CL_INVALID_OPERATION                        -59\n#define CL_INVALID_GL_OBJECT                        -60\n#define CL_INVALID_BUFFER_SIZE                      -61\n#define CL_INVALID_MIP_LEVEL                        -62\n#define CL_INVALID_GLOBAL_WORK_SIZE                 -63\n#define CL_INVALID_PROPERTY                         -64\n#define CL_INVALID_IMAGE_DESCRIPTOR                 -65\n#define CL_INVALID_COMPILER_OPTIONS                 -66\n#define CL_INVALID_LINKER_OPTIONS                   -67\n#define CL_INVALID_DEVICE_PARTITION_COUNT           -68\n\n/* OpenCL Version */\n#define CL_VERSION_1_0                              1\n#define CL_VERSION_1_1                              1\n#define CL_VERSION_1_2                              1\n\n/* cl_bool */\n#define CL_FALSE                                    0\n#define CL_TRUE                                     1\n#define CL_BLOCKING                                 CL_TRUE\n#define CL_NON_BLOCKING                             CL_FALSE\n\n/* cl_platform_info */\n#define CL_PLATFORM_PROFILE                         0x0900\n#define CL_PLATFORM_VERSION                         0x0901\n#define CL_PLATFORM_NAME                            0x0902\n#define CL_PLATFORM_VENDOR                          0x0903\n#define CL_PLATFORM_EXTENSIONS                      0x0904\n\n/* cl_device_type - bitfield */\n#define CL_DEVICE_TYPE_DEFAULT                      (1 << 0)\n#define CL_DEVICE_TYPE_CPU                          (1 << 1)\n#define CL_DEVICE_TYPE_GPU                          (1 << 2)\n#define CL_DEVICE_TYPE_ACCELERATOR                  (1 << 3)\n#define CL_DEVICE_TYPE_CUSTOM                       (1 << 4)\n#define CL_DEVICE_TYPE_ALL                          0xFFFFFFFF\n\n/* cl_device_info */\n#define CL_DEVICE_TYPE                              0x1000\n#define CL_DEVICE_VENDOR_ID                         0x1001\n#define CL_DEVICE_MAX_COMPUTE_UNITS                 0x1002\n#define CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS          0x1003\n#define CL_DEVICE_MAX_WORK_GROUP_SIZE               0x1004\n#define CL_DEVICE_MAX_WORK_ITEM_SIZES               0x1005\n#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR       0x1006\n#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT      0x1007\n#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT        0x1008\n#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG       0x1009\n#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT      0x100A\n#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE     0x100B\n#define CL_DEVICE_MAX_CLOCK_FREQUENCY               0x100C\n#define CL_DEVICE_ADDRESS_BITS                      0x100D\n#define CL_DEVICE_MAX_READ_IMAGE_ARGS               0x100E\n#define CL_DEVICE_MAX_WRITE_IMAGE_ARGS              0x100F\n#define CL_DEVICE_MAX_MEM_ALLOC_SIZE                0x1010\n#define CL_DEVICE_IMAGE2D_MAX_WIDTH                 0x1011\n#define CL_DEVICE_IMAGE2D_MAX_HEIGHT                0x1012\n#define CL_DEVICE_IMAGE3D_MAX_WIDTH                 0x1013\n#define CL_DEVICE_IMAGE3D_MAX_HEIGHT                0x1014\n#define CL_DEVICE_IMAGE3D_MAX_DEPTH                 0x1015\n#define CL_DEVICE_IMAGE_SUPPORT                     0x1016\n#define CL_DEVICE_MAX_PARAMETER_SIZE                0x1017\n#define CL_DEVICE_MAX_SAMPLERS                      0x1018\n#define CL_DEVICE_MEM_BASE_ADDR_ALIGN               0x1019\n#define CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE          0x101A\n#define CL_DEVICE_SINGLE_FP_CONFIG                  0x101B\n#define CL_DEVICE_GLOBAL_MEM_CACHE_TYPE             0x101C\n#define CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE         0x101D\n#define CL_DEVICE_GLOBAL_MEM_CACHE_SIZE             0x101E\n#define CL_DEVICE_GLOBAL_MEM_SIZE                   0x101F\n#define CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE          0x1020\n#define CL_DEVICE_MAX_CONSTANT_ARGS                 0x1021\n#define CL_DEVICE_LOCAL_MEM_TYPE                    0x1022\n#define CL_DEVICE_LOCAL_MEM_SIZE                    0x1023\n#define CL_DEVICE_ERROR_CORRECTION_SUPPORT          0x1024\n#define CL_DEVICE_PROFILING_TIMER_RESOLUTION        0x1025\n#define CL_DEVICE_ENDIAN_LITTLE                     0x1026\n#define CL_DEVICE_AVAILABLE                         0x1027\n#define CL_DEVICE_COMPILER_AVAILABLE                0x1028\n#define CL_DEVICE_EXECUTION_CAPABILITIES            0x1029\n#define CL_DEVICE_QUEUE_PROPERTIES                  0x102A\n#define CL_DEVICE_NAME                              0x102B\n#define CL_DEVICE_VENDOR                            0x102C\n#define CL_DRIVER_VERSION                           0x102D\n#define CL_DEVICE_PROFILE                           0x102E\n#define CL_DEVICE_VERSION                           0x102F\n#define CL_DEVICE_EXTENSIONS                        0x1030\n#define CL_DEVICE_PLATFORM                          0x1031\n#define CL_DEVICE_DOUBLE_FP_CONFIG                  0x1032\n/* 0x1033 reserved for CL_DEVICE_HALF_FP_CONFIG */\n#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF       0x1034\n#define CL_DEVICE_HOST_UNIFIED_MEMORY               0x1035\n#define CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR          0x1036\n#define CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT         0x1037\n#define CL_DEVICE_NATIVE_VECTOR_WIDTH_INT           0x1038\n#define CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG          0x1039\n#define CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT         0x103A\n#define CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE        0x103B\n#define CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF          0x103C\n#define CL_DEVICE_OPENCL_C_VERSION                  0x103D\n#define CL_DEVICE_LINKER_AVAILABLE                  0x103E\n#define CL_DEVICE_BUILT_IN_KERNELS                  0x103F\n#define CL_DEVICE_IMAGE_MAX_BUFFER_SIZE             0x1040\n#define CL_DEVICE_IMAGE_MAX_ARRAY_SIZE              0x1041\n#define CL_DEVICE_PARENT_DEVICE                     0x1042\n#define CL_DEVICE_PARTITION_MAX_SUB_DEVICES         0x1043\n#define CL_DEVICE_PARTITION_PROPERTIES              0x1044\n#define CL_DEVICE_PARTITION_AFFINITY_DOMAIN         0x1045\n#define CL_DEVICE_PARTITION_TYPE                    0x1046\n#define CL_DEVICE_REFERENCE_COUNT                   0x1047\n#define CL_DEVICE_PREFERRED_INTEROP_USER_SYNC       0x1048\n#define CL_DEVICE_PRINTF_BUFFER_SIZE                0x1049\n#define CL_DEVICE_IMAGE_PITCH_ALIGNMENT             0x104A\n#define CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT      0x104B\n\n/* cl_device_fp_config - bitfield */\n#define CL_FP_DENORM                                (1 << 0)\n#define CL_FP_INF_NAN                               (1 << 1)\n#define CL_FP_ROUND_TO_NEAREST                      (1 << 2)\n#define CL_FP_ROUND_TO_ZERO                         (1 << 3)\n#define CL_FP_ROUND_TO_INF                          (1 << 4)\n#define CL_FP_FMA                                   (1 << 5)\n#define CL_FP_SOFT_FLOAT                            (1 << 6)\n#define CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT         (1 << 7)\n\n/* cl_device_mem_cache_type */\n#define CL_NONE                                     0x0\n#define CL_READ_ONLY_CACHE                          0x1\n#define CL_READ_WRITE_CACHE                         0x2\n\n/* cl_device_local_mem_type */\n#define CL_LOCAL                                    0x1\n#define CL_GLOBAL                                   0x2\n\n/* cl_device_exec_capabilities - bitfield */\n#define CL_EXEC_KERNEL                              (1 << 0)\n#define CL_EXEC_NATIVE_KERNEL                       (1 << 1)\n\n/* cl_command_queue_properties - bitfield */\n#define CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE      (1 << 0)\n#define CL_QUEUE_PROFILING_ENABLE                   (1 << 1)\n\n/* cl_context_info  */\n#define CL_CONTEXT_REFERENCE_COUNT                  0x1080\n#define CL_CONTEXT_DEVICES                          0x1081\n#define CL_CONTEXT_PROPERTIES                       0x1082\n#define CL_CONTEXT_NUM_DEVICES                      0x1083\n\n/* cl_context_properties */\n#define CL_CONTEXT_PLATFORM                         0x1084\n#define CL_CONTEXT_INTEROP_USER_SYNC                0x1085\n\n/* cl_device_partition_property */\n#define CL_DEVICE_PARTITION_EQUALLY                 0x1086\n#define CL_DEVICE_PARTITION_BY_COUNTS               0x1087\n#define CL_DEVICE_PARTITION_BY_COUNTS_LIST_END      0x0\n#define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN      0x1088\n\n/* cl_device_affinity_domain */\n#define CL_DEVICE_AFFINITY_DOMAIN_NUMA                     (1 << 0)\n#define CL_DEVICE_AFFINITY_DOMAIN_L4_CACHE                 (1 << 1)\n#define CL_DEVICE_AFFINITY_DOMAIN_L3_CACHE                 (1 << 2)\n#define CL_DEVICE_AFFINITY_DOMAIN_L2_CACHE                 (1 << 3)\n#define CL_DEVICE_AFFINITY_DOMAIN_L1_CACHE                 (1 << 4)\n#define CL_DEVICE_AFFINITY_DOMAIN_NEXT_PARTITIONABLE       (1 << 5)\n\n/* cl_command_queue_info */\n#define CL_QUEUE_CONTEXT                            0x1090\n#define CL_QUEUE_DEVICE                             0x1091\n#define CL_QUEUE_REFERENCE_COUNT                    0x1092\n#define CL_QUEUE_PROPERTIES                         0x1093\n\n/* cl_mem_flags - bitfield */\n#define CL_MEM_READ_WRITE                           (1 << 0)\n#define CL_MEM_WRITE_ONLY                           (1 << 1)\n#define CL_MEM_READ_ONLY                            (1 << 2)\n#define CL_MEM_USE_HOST_PTR                         (1 << 3)\n#define CL_MEM_ALLOC_HOST_PTR                       (1 << 4)\n#define CL_MEM_COPY_HOST_PTR                        (1 << 5)\n// reserved                                         (1 << 6)\n#define CL_MEM_HOST_WRITE_ONLY                      (1 << 7)\n#define CL_MEM_HOST_READ_ONLY                       (1 << 8)\n#define CL_MEM_HOST_NO_ACCESS                       (1 << 9)\n\n/* cl_mem_migration_flags - bitfield */\n#define CL_MIGRATE_MEM_OBJECT_HOST                  (1 << 0)\n#define CL_MIGRATE_MEM_OBJECT_CONTENT_UNDEFINED     (1 << 1)\n\n/* cl_channel_order */\n#define CL_R                                        0x10B0\n#define CL_A                                        0x10B1\n#define CL_RG                                       0x10B2\n#define CL_RA                                       0x10B3\n#define CL_RGB                                      0x10B4\n#define CL_RGBA                                     0x10B5\n#define CL_BGRA                                     0x10B6\n#define CL_ARGB                                     0x10B7\n#define CL_INTENSITY                                0x10B8\n#define CL_LUMINANCE                                0x10B9\n#define CL_Rx                                       0x10BA\n#define CL_RGx                                      0x10BB\n#define CL_RGBx                                     0x10BC\n#define CL_DEPTH                                    0x10BD\n#define CL_DEPTH_STENCIL                            0x10BE\n\n/* cl_channel_type */\n#define CL_SNORM_INT8                               0x10D0\n#define CL_SNORM_INT16                              0x10D1\n#define CL_UNORM_INT8                               0x10D2\n#define CL_UNORM_INT16                              0x10D3\n#define CL_UNORM_SHORT_565                          0x10D4\n#define CL_UNORM_SHORT_555                          0x10D5\n#define CL_UNORM_INT_101010                         0x10D6\n#define CL_SIGNED_INT8                              0x10D7\n#define CL_SIGNED_INT16                             0x10D8\n#define CL_SIGNED_INT32                             0x10D9\n#define CL_UNSIGNED_INT8                            0x10DA\n#define CL_UNSIGNED_INT16                           0x10DB\n#define CL_UNSIGNED_INT32                           0x10DC\n#define CL_HALF_FLOAT                               0x10DD\n#define CL_FLOAT                                    0x10DE\n#define CL_UNORM_INT24                              0x10DF\n\n/* cl_mem_object_type */\n#define CL_MEM_OBJECT_BUFFER                        0x10F0\n#define CL_MEM_OBJECT_IMAGE2D                       0x10F1\n#define CL_MEM_OBJECT_IMAGE3D                       0x10F2\n#define CL_MEM_OBJECT_IMAGE2D_ARRAY                 0x10F3\n#define CL_MEM_OBJECT_IMAGE1D                       0x10F4\n#define CL_MEM_OBJECT_IMAGE1D_ARRAY                 0x10F5\n#define CL_MEM_OBJECT_IMAGE1D_BUFFER                0x10F6\n\n/* cl_mem_info */\n#define CL_MEM_TYPE                                 0x1100\n#define CL_MEM_FLAGS                                0x1101\n#define CL_MEM_SIZE                                 0x1102\n#define CL_MEM_HOST_PTR                             0x1103\n#define CL_MEM_MAP_COUNT                            0x1104\n#define CL_MEM_REFERENCE_COUNT                      0x1105\n#define CL_MEM_CONTEXT                              0x1106\n#define CL_MEM_ASSOCIATED_MEMOBJECT                 0x1107\n#define CL_MEM_OFFSET                               0x1108\n\n/* cl_image_info */\n#define CL_IMAGE_FORMAT                             0x1110\n#define CL_IMAGE_ELEMENT_SIZE                       0x1111\n#define CL_IMAGE_ROW_PITCH                          0x1112\n#define CL_IMAGE_SLICE_PITCH                        0x1113\n#define CL_IMAGE_WIDTH                              0x1114\n#define CL_IMAGE_HEIGHT                             0x1115\n#define CL_IMAGE_DEPTH                              0x1116\n#define CL_IMAGE_ARRAY_SIZE                         0x1117\n#define CL_IMAGE_BUFFER                             0x1118\n#define CL_IMAGE_NUM_MIP_LEVELS                     0x1119\n#define CL_IMAGE_NUM_SAMPLES                        0x111A\n\n/* cl_addressing_mode */\n#define CL_ADDRESS_NONE                             0x1130\n#define CL_ADDRESS_CLAMP_TO_EDGE                    0x1131\n#define CL_ADDRESS_CLAMP                            0x1132\n#define CL_ADDRESS_REPEAT                           0x1133\n#define CL_ADDRESS_MIRRORED_REPEAT                  0x1134\n\n/* cl_filter_mode */\n#define CL_FILTER_NEAREST                           0x1140\n#define CL_FILTER_LINEAR                            0x1141\n\n/* cl_sampler_info */\n#define CL_SAMPLER_REFERENCE_COUNT                  0x1150\n#define CL_SAMPLER_CONTEXT                          0x1151\n#define CL_SAMPLER_NORMALIZED_COORDS                0x1152\n#define CL_SAMPLER_ADDRESSING_MODE                  0x1153\n#define CL_SAMPLER_FILTER_MODE                      0x1154\n\n/* cl_map_flags - bitfield */\n#define CL_MAP_READ                                 (1 << 0)\n#define CL_MAP_WRITE                                (1 << 1)\n#define CL_MAP_WRITE_INVALIDATE_REGION              (1 << 2)\n\n/* cl_program_info */\n#define CL_PROGRAM_REFERENCE_COUNT                  0x1160\n#define CL_PROGRAM_CONTEXT                          0x1161\n#define CL_PROGRAM_NUM_DEVICES                      0x1162\n#define CL_PROGRAM_DEVICES                          0x1163\n#define CL_PROGRAM_SOURCE                           0x1164\n#define CL_PROGRAM_BINARY_SIZES                     0x1165\n#define CL_PROGRAM_BINARIES                         0x1166\n#define CL_PROGRAM_NUM_KERNELS                      0x1167\n#define CL_PROGRAM_KERNEL_NAMES                     0x1168\n\n/* cl_program_build_info */\n#define CL_PROGRAM_BUILD_STATUS                     0x1181\n#define CL_PROGRAM_BUILD_OPTIONS                    0x1182\n#define CL_PROGRAM_BUILD_LOG                        0x1183\n#define CL_PROGRAM_BINARY_TYPE                      0x1184\n\n/* cl_program_binary_type */\n#define CL_PROGRAM_BINARY_TYPE_NONE                 0x0\n#define CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT      0x1\n#define CL_PROGRAM_BINARY_TYPE_LIBRARY              0x2\n#define CL_PROGRAM_BINARY_TYPE_EXECUTABLE           0x4\n\n/* cl_build_status */\n#define CL_BUILD_SUCCESS                            0\n#define CL_BUILD_NONE                               -1\n#define CL_BUILD_ERROR                              -2\n#define CL_BUILD_IN_PROGRESS                        -3\n\n/* cl_kernel_info */\n#define CL_KERNEL_FUNCTION_NAME                     0x1190\n#define CL_KERNEL_NUM_ARGS                          0x1191\n#define CL_KERNEL_REFERENCE_COUNT                   0x1192\n#define CL_KERNEL_CONTEXT                           0x1193\n#define CL_KERNEL_PROGRAM                           0x1194\n#define CL_KERNEL_ATTRIBUTES                        0x1195\n\n/* cl_kernel_arg_info */\n#define CL_KERNEL_ARG_ADDRESS_QUALIFIER             0x1196\n#define CL_KERNEL_ARG_ACCESS_QUALIFIER              0x1197\n#define CL_KERNEL_ARG_TYPE_NAME                     0x1198\n#define CL_KERNEL_ARG_TYPE_QUALIFIER                0x1199\n#define CL_KERNEL_ARG_NAME                          0x119A\n\n/* cl_kernel_arg_address_qualifier */\n#define CL_KERNEL_ARG_ADDRESS_GLOBAL                0x119B\n#define CL_KERNEL_ARG_ADDRESS_LOCAL                 0x119C\n#define CL_KERNEL_ARG_ADDRESS_CONSTANT              0x119D\n#define CL_KERNEL_ARG_ADDRESS_PRIVATE               0x119E\n\n/* cl_kernel_arg_access_qualifier */\n#define CL_KERNEL_ARG_ACCESS_READ_ONLY              0x11A0\n#define CL_KERNEL_ARG_ACCESS_WRITE_ONLY             0x11A1\n#define CL_KERNEL_ARG_ACCESS_READ_WRITE             0x11A2\n#define CL_KERNEL_ARG_ACCESS_NONE                   0x11A3\n\n/* cl_kernel_arg_type_qualifer */\n#define CL_KERNEL_ARG_TYPE_NONE                     0\n#define CL_KERNEL_ARG_TYPE_CONST                    (1 << 0)\n#define CL_KERNEL_ARG_TYPE_RESTRICT                 (1 << 1)\n#define CL_KERNEL_ARG_TYPE_VOLATILE                 (1 << 2)\n\n/* cl_kernel_work_group_info */\n#define CL_KERNEL_WORK_GROUP_SIZE                   0x11B0\n#define CL_KERNEL_COMPILE_WORK_GROUP_SIZE           0x11B1\n#define CL_KERNEL_LOCAL_MEM_SIZE                    0x11B2\n#define CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE 0x11B3\n#define CL_KERNEL_PRIVATE_MEM_SIZE                  0x11B4\n#define CL_KERNEL_GLOBAL_WORK_SIZE                  0x11B5\n\n/* cl_event_info  */\n#define CL_EVENT_COMMAND_QUEUE                      0x11D0\n#define CL_EVENT_COMMAND_TYPE                       0x11D1\n#define CL_EVENT_REFERENCE_COUNT                    0x11D2\n#define CL_EVENT_COMMAND_EXECUTION_STATUS           0x11D3\n#define CL_EVENT_CONTEXT                            0x11D4\n\n/* cl_command_type */\n#define CL_COMMAND_NDRANGE_KERNEL                   0x11F0\n#define CL_COMMAND_TASK                             0x11F1\n#define CL_COMMAND_NATIVE_KERNEL                    0x11F2\n#define CL_COMMAND_READ_BUFFER                      0x11F3\n#define CL_COMMAND_WRITE_BUFFER                     0x11F4\n#define CL_COMMAND_COPY_BUFFER                      0x11F5\n#define CL_COMMAND_READ_IMAGE                       0x11F6\n#define CL_COMMAND_WRITE_IMAGE                      0x11F7\n#define CL_COMMAND_COPY_IMAGE                       0x11F8\n#define CL_COMMAND_COPY_IMAGE_TO_BUFFER             0x11F9\n#define CL_COMMAND_COPY_BUFFER_TO_IMAGE             0x11FA\n#define CL_COMMAND_MAP_BUFFER                       0x11FB\n#define CL_COMMAND_MAP_IMAGE                        0x11FC\n#define CL_COMMAND_UNMAP_MEM_OBJECT                 0x11FD\n#define CL_COMMAND_MARKER                           0x11FE\n#define CL_COMMAND_ACQUIRE_GL_OBJECTS               0x11FF\n#define CL_COMMAND_RELEASE_GL_OBJECTS               0x1200\n#define CL_COMMAND_READ_BUFFER_RECT                 0x1201\n#define CL_COMMAND_WRITE_BUFFER_RECT                0x1202\n#define CL_COMMAND_COPY_BUFFER_RECT                 0x1203\n#define CL_COMMAND_USER                             0x1204\n#define CL_COMMAND_BARRIER                          0x1205\n#define CL_COMMAND_MIGRATE_MEM_OBJECTS              0x1206\n#define CL_COMMAND_FILL_BUFFER                      0x1207\n#define CL_COMMAND_FILL_IMAGE                       0x1208\n\n/* command execution status */\n#define CL_COMPLETE                                 0x0\n#define CL_RUNNING                                  0x1\n#define CL_SUBMITTED                                0x2\n#define CL_QUEUED                                   0x3\n\n/* cl_buffer_create_type  */\n#define CL_BUFFER_CREATE_TYPE_REGION                0x1220\n\n/* cl_profiling_info  */\n#define CL_PROFILING_COMMAND_QUEUED                 0x1280\n#define CL_PROFILING_COMMAND_SUBMIT                 0x1281\n#define CL_PROFILING_COMMAND_START                  0x1282\n#define CL_PROFILING_COMMAND_END                    0x1283\n\n/********************************************************************************************************/\n\n/* Platform API */\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetPlatformIDs(cl_uint          /* num_entries */,\n                 cl_platform_id * /* platforms */,\n                 cl_uint *        /* num_platforms */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetPlatformInfo(cl_platform_id   /* platform */,\n                  cl_platform_info /* param_name */,\n                  size_t           /* param_value_size */,\n                  void *           /* param_value */,\n                  size_t *         /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\n/* Device APIs */\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetDeviceIDs(cl_platform_id   /* platform */,\n               cl_device_type   /* device_type */,\n               cl_uint          /* num_entries */,\n               cl_device_id *   /* devices */,\n               cl_uint *        /* num_devices */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetDeviceInfo(cl_device_id    /* device */,\n                cl_device_info  /* param_name */,\n                size_t          /* param_value_size */,\n                void *          /* param_value */,\n                size_t *        /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclCreateSubDevices(cl_device_id                         /* in_device */,\n                   const cl_device_partition_property * /* properties */,\n                   cl_uint                              /* num_devices */,\n                   cl_device_id *                       /* out_devices */,\n                   cl_uint *                            /* num_devices_ret */) CL_API_SUFFIX__VERSION_1_2;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclRetainDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclReleaseDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2;\n\n/* Context APIs  */\nextern CL_API_ENTRY cl_context CL_API_CALL\nclCreateContext(const cl_context_properties * /* properties */,\n                cl_uint                 /* num_devices */,\n                const cl_device_id *    /* devices */,\n                void (CL_CALLBACK * /* pfn_notify */)(const char *, const void *, size_t, void *),\n                void *                  /* user_data */,\n                cl_int *                /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_context CL_API_CALL\nclCreateContextFromType(const cl_context_properties * /* properties */,\n                        cl_device_type          /* device_type */,\n                        void (CL_CALLBACK *     /* pfn_notify*/ )(const char *, const void *, size_t, void *),\n                        void *                  /* user_data */,\n                        cl_int *                /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclRetainContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclReleaseContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetContextInfo(cl_context         /* context */,\n                 cl_context_info    /* param_name */,\n                 size_t             /* param_value_size */,\n                 void *             /* param_value */,\n                 size_t *           /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\n/* Command Queue APIs */\nextern CL_API_ENTRY cl_command_queue CL_API_CALL\nclCreateCommandQueue(cl_context                     /* context */,\n                     cl_device_id                   /* device */,\n                     cl_command_queue_properties    /* properties */,\n                     cl_int *                       /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclRetainCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclReleaseCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetCommandQueueInfo(cl_command_queue      /* command_queue */,\n                      cl_command_queue_info /* param_name */,\n                      size_t                /* param_value_size */,\n                      void *                /* param_value */,\n                      size_t *              /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\n/* Memory Object APIs */\nextern CL_API_ENTRY cl_mem CL_API_CALL\nclCreateBuffer(cl_context   /* context */,\n               cl_mem_flags /* flags */,\n               size_t       /* size */,\n               void *       /* host_ptr */,\n               cl_int *     /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_mem CL_API_CALL\nclCreateSubBuffer(cl_mem                   /* buffer */,\n                  cl_mem_flags             /* flags */,\n                  cl_buffer_create_type    /* buffer_create_type */,\n                  const void *             /* buffer_create_info */,\n                  cl_int *                 /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1;\n\nextern CL_API_ENTRY cl_mem CL_API_CALL\nclCreateImage(cl_context              /* context */,\n              cl_mem_flags            /* flags */,\n              const cl_image_format * /* image_format */,\n              const cl_image_desc *   /* image_desc */,\n              void *                  /* host_ptr */,\n              cl_int *                /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclRetainMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclReleaseMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetSupportedImageFormats(cl_context           /* context */,\n                           cl_mem_flags         /* flags */,\n                           cl_mem_object_type   /* image_type */,\n                           cl_uint              /* num_entries */,\n                           cl_image_format *    /* image_formats */,\n                           cl_uint *            /* num_image_formats */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetMemObjectInfo(cl_mem           /* memobj */,\n                   cl_mem_info      /* param_name */,\n                   size_t           /* param_value_size */,\n                   void *           /* param_value */,\n                   size_t *         /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetImageInfo(cl_mem           /* image */,\n               cl_image_info    /* param_name */,\n               size_t           /* param_value_size */,\n               void *           /* param_value */,\n               size_t *         /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclSetMemObjectDestructorCallback(  cl_mem /* memobj */,\n                                    void (CL_CALLBACK * /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/),\n                                    void * /*user_data */ )             CL_API_SUFFIX__VERSION_1_1;\n\n/* Sampler APIs */\nextern CL_API_ENTRY cl_sampler CL_API_CALL\nclCreateSampler(cl_context          /* context */,\n                cl_bool             /* normalized_coords */,\n                cl_addressing_mode  /* addressing_mode */,\n                cl_filter_mode      /* filter_mode */,\n                cl_int *            /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclRetainSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclReleaseSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetSamplerInfo(cl_sampler         /* sampler */,\n                 cl_sampler_info    /* param_name */,\n                 size_t             /* param_value_size */,\n                 void *             /* param_value */,\n                 size_t *           /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\n/* Program Object APIs  */\nextern CL_API_ENTRY cl_program CL_API_CALL\nclCreateProgramWithSource(cl_context        /* context */,\n                          cl_uint           /* count */,\n                          const char **     /* strings */,\n                          const size_t *    /* lengths */,\n                          cl_int *          /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_program CL_API_CALL\nclCreateProgramWithBinary(cl_context                     /* context */,\n                          cl_uint                        /* num_devices */,\n                          const cl_device_id *           /* device_list */,\n                          const size_t *                 /* lengths */,\n                          const unsigned char **         /* binaries */,\n                          cl_int *                       /* binary_status */,\n                          cl_int *                       /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_program CL_API_CALL\nclCreateProgramWithBuiltInKernels(cl_context            /* context */,\n                                  cl_uint               /* num_devices */,\n                                  const cl_device_id *  /* device_list */,\n                                  const char *          /* kernel_names */,\n                                  cl_int *              /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclRetainProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclReleaseProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclBuildProgram(cl_program           /* program */,\n               cl_uint              /* num_devices */,\n               const cl_device_id * /* device_list */,\n               const char *         /* options */,\n               void (CL_CALLBACK *  /* pfn_notify */)(cl_program /* program */, void * /* user_data */),\n               void *               /* user_data */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclCompileProgram(cl_program           /* program */,\n                 cl_uint              /* num_devices */,\n                 const cl_device_id * /* device_list */,\n                 const char *         /* options */,\n                 cl_uint              /* num_input_headers */,\n                 const cl_program *   /* input_headers */,\n                 const char **        /* header_include_names */,\n                 void (CL_CALLBACK *  /* pfn_notify */)(cl_program /* program */, void * /* user_data */),\n                 void *               /* user_data */) CL_API_SUFFIX__VERSION_1_2;\n\nextern CL_API_ENTRY cl_program CL_API_CALL\nclLinkProgram(cl_context           /* context */,\n              cl_uint              /* num_devices */,\n              const cl_device_id * /* device_list */,\n              const char *         /* options */,\n              cl_uint              /* num_input_programs */,\n              const cl_program *   /* input_programs */,\n              void (CL_CALLBACK *  /* pfn_notify */)(cl_program /* program */, void * /* user_data */),\n              void *               /* user_data */,\n              cl_int *             /* errcode_ret */ ) CL_API_SUFFIX__VERSION_1_2;\n\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclUnloadPlatformCompiler(cl_platform_id /* platform */) CL_API_SUFFIX__VERSION_1_2;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetProgramInfo(cl_program         /* program */,\n                 cl_program_info    /* param_name */,\n                 size_t             /* param_value_size */,\n                 void *             /* param_value */,\n                 size_t *           /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetProgramBuildInfo(cl_program            /* program */,\n                      cl_device_id          /* device */,\n                      cl_program_build_info /* param_name */,\n                      size_t                /* param_value_size */,\n                      void *                /* param_value */,\n                      size_t *              /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\n/* Kernel Object APIs */\nextern CL_API_ENTRY cl_kernel CL_API_CALL\nclCreateKernel(cl_program      /* program */,\n               const char *    /* kernel_name */,\n               cl_int *        /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclCreateKernelsInProgram(cl_program     /* program */,\n                         cl_uint        /* num_kernels */,\n                         cl_kernel *    /* kernels */,\n                         cl_uint *      /* num_kernels_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclRetainKernel(cl_kernel    /* kernel */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclReleaseKernel(cl_kernel   /* kernel */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclSetKernelArg(cl_kernel    /* kernel */,\n               cl_uint      /* arg_index */,\n               size_t       /* arg_size */,\n               const void * /* arg_value */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetKernelInfo(cl_kernel       /* kernel */,\n                cl_kernel_info  /* param_name */,\n                size_t          /* param_value_size */,\n                void *          /* param_value */,\n                size_t *        /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetKernelArgInfo(cl_kernel       /* kernel */,\n                   cl_uint         /* arg_indx */,\n                   cl_kernel_arg_info  /* param_name */,\n                   size_t          /* param_value_size */,\n                   void *          /* param_value */,\n                   size_t *        /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_2;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetKernelWorkGroupInfo(cl_kernel                  /* kernel */,\n                         cl_device_id               /* device */,\n                         cl_kernel_work_group_info  /* param_name */,\n                         size_t                     /* param_value_size */,\n                         void *                     /* param_value */,\n                         size_t *                   /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\n/* Event Object APIs */\nextern CL_API_ENTRY cl_int CL_API_CALL\nclWaitForEvents(cl_uint             /* num_events */,\n                const cl_event *    /* event_list */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetEventInfo(cl_event         /* event */,\n               cl_event_info    /* param_name */,\n               size_t           /* param_value_size */,\n               void *           /* param_value */,\n               size_t *         /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_event CL_API_CALL\nclCreateUserEvent(cl_context    /* context */,\n                  cl_int *      /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclRetainEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclReleaseEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclSetUserEventStatus(cl_event   /* event */,\n                     cl_int     /* execution_status */) CL_API_SUFFIX__VERSION_1_1;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclSetEventCallback( cl_event    /* event */,\n                    cl_int      /* command_exec_callback_type */,\n                    void (CL_CALLBACK * /* pfn_notify */)(cl_event, cl_int, void *),\n                    void *      /* user_data */) CL_API_SUFFIX__VERSION_1_1;\n\n/* Profiling APIs */\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetEventProfilingInfo(cl_event            /* event */,\n                        cl_profiling_info   /* param_name */,\n                        size_t              /* param_value_size */,\n                        void *              /* param_value */,\n                        size_t *            /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\n/* Flush and Finish APIs */\nextern CL_API_ENTRY cl_int CL_API_CALL\nclFlush(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclFinish(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0;\n\n/* Enqueued Commands APIs */\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueReadBuffer(cl_command_queue    /* command_queue */,\n                    cl_mem              /* buffer */,\n                    cl_bool             /* blocking_read */,\n                    size_t              /* offset */,\n                    size_t              /* size */,\n                    void *              /* ptr */,\n                    cl_uint             /* num_events_in_wait_list */,\n                    const cl_event *    /* event_wait_list */,\n                    cl_event *          /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueReadBufferRect(cl_command_queue    /* command_queue */,\n                        cl_mem              /* buffer */,\n                        cl_bool             /* blocking_read */,\n                        const size_t *      /* buffer_offset */,\n                        const size_t *      /* host_offset */,\n                        const size_t *      /* region */,\n                        size_t              /* buffer_row_pitch */,\n                        size_t              /* buffer_slice_pitch */,\n                        size_t              /* host_row_pitch */,\n                        size_t              /* host_slice_pitch */,\n                        void *              /* ptr */,\n                        cl_uint             /* num_events_in_wait_list */,\n                        const cl_event *    /* event_wait_list */,\n                        cl_event *          /* event */) CL_API_SUFFIX__VERSION_1_1;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueWriteBuffer(cl_command_queue   /* command_queue */,\n                     cl_mem             /* buffer */,\n                     cl_bool            /* blocking_write */,\n                     size_t             /* offset */,\n                     size_t             /* size */,\n                     const void *       /* ptr */,\n                     cl_uint            /* num_events_in_wait_list */,\n                     const cl_event *   /* event_wait_list */,\n                     cl_event *         /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueWriteBufferRect(cl_command_queue    /* command_queue */,\n                         cl_mem              /* buffer */,\n                         cl_bool             /* blocking_write */,\n                         const size_t *      /* buffer_offset */,\n                         const size_t *      /* host_offset */,\n                         const size_t *      /* region */,\n                         size_t              /* buffer_row_pitch */,\n                         size_t              /* buffer_slice_pitch */,\n                         size_t              /* host_row_pitch */,\n                         size_t              /* host_slice_pitch */,\n                         const void *        /* ptr */,\n                         cl_uint             /* num_events_in_wait_list */,\n                         const cl_event *    /* event_wait_list */,\n                         cl_event *          /* event */) CL_API_SUFFIX__VERSION_1_1;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueFillBuffer(cl_command_queue   /* command_queue */,\n                    cl_mem             /* buffer */,\n                    const void *       /* pattern */,\n                    size_t             /* pattern_size */,\n                    size_t             /* offset */,\n                    size_t             /* size */,\n                    cl_uint            /* num_events_in_wait_list */,\n                    const cl_event *   /* event_wait_list */,\n                    cl_event *         /* event */) CL_API_SUFFIX__VERSION_1_2;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueCopyBuffer(cl_command_queue    /* command_queue */,\n                    cl_mem              /* src_buffer */,\n                    cl_mem              /* dst_buffer */,\n                    size_t              /* src_offset */,\n                    size_t              /* dst_offset */,\n                    size_t              /* size */,\n                    cl_uint             /* num_events_in_wait_list */,\n                    const cl_event *    /* event_wait_list */,\n                    cl_event *          /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueCopyBufferRect(cl_command_queue    /* command_queue */,\n                        cl_mem              /* src_buffer */,\n                        cl_mem              /* dst_buffer */,\n                        const size_t *      /* src_origin */,\n                        const size_t *      /* dst_origin */,\n                        const size_t *      /* region */,\n                        size_t              /* src_row_pitch */,\n                        size_t              /* src_slice_pitch */,\n                        size_t              /* dst_row_pitch */,\n                        size_t              /* dst_slice_pitch */,\n                        cl_uint             /* num_events_in_wait_list */,\n                        const cl_event *    /* event_wait_list */,\n                        cl_event *          /* event */) CL_API_SUFFIX__VERSION_1_1;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueReadImage(cl_command_queue     /* command_queue */,\n                   cl_mem               /* image */,\n                   cl_bool              /* blocking_read */,\n                   const size_t *       /* origin[3] */,\n                   const size_t *       /* region[3] */,\n                   size_t               /* row_pitch */,\n                   size_t               /* slice_pitch */,\n                   void *               /* ptr */,\n                   cl_uint              /* num_events_in_wait_list */,\n                   const cl_event *     /* event_wait_list */,\n                   cl_event *           /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueWriteImage(cl_command_queue    /* command_queue */,\n                    cl_mem              /* image */,\n                    cl_bool             /* blocking_write */,\n                    const size_t *      /* origin[3] */,\n                    const size_t *      /* region[3] */,\n                    size_t              /* input_row_pitch */,\n                    size_t              /* input_slice_pitch */,\n                    const void *        /* ptr */,\n                    cl_uint             /* num_events_in_wait_list */,\n                    const cl_event *    /* event_wait_list */,\n                    cl_event *          /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueFillImage(cl_command_queue   /* command_queue */,\n                   cl_mem             /* image */,\n                   const void *       /* fill_color */,\n                   const size_t *     /* origin[3] */,\n                   const size_t *     /* region[3] */,\n                   cl_uint            /* num_events_in_wait_list */,\n                   const cl_event *   /* event_wait_list */,\n                   cl_event *         /* event */) CL_API_SUFFIX__VERSION_1_2;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueCopyImage(cl_command_queue     /* command_queue */,\n                   cl_mem               /* src_image */,\n                   cl_mem               /* dst_image */,\n                   const size_t *       /* src_origin[3] */,\n                   const size_t *       /* dst_origin[3] */,\n                   const size_t *       /* region[3] */,\n                   cl_uint              /* num_events_in_wait_list */,\n                   const cl_event *     /* event_wait_list */,\n                   cl_event *           /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueCopyImageToBuffer(cl_command_queue /* command_queue */,\n                           cl_mem           /* src_image */,\n                           cl_mem           /* dst_buffer */,\n                           const size_t *   /* src_origin[3] */,\n                           const size_t *   /* region[3] */,\n                           size_t           /* dst_offset */,\n                           cl_uint          /* num_events_in_wait_list */,\n                           const cl_event * /* event_wait_list */,\n                           cl_event *       /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueCopyBufferToImage(cl_command_queue /* command_queue */,\n                           cl_mem           /* src_buffer */,\n                           cl_mem           /* dst_image */,\n                           size_t           /* src_offset */,\n                           const size_t *   /* dst_origin[3] */,\n                           const size_t *   /* region[3] */,\n                           cl_uint          /* num_events_in_wait_list */,\n                           const cl_event * /* event_wait_list */,\n                           cl_event *       /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY void * CL_API_CALL\nclEnqueueMapBuffer(cl_command_queue /* command_queue */,\n                   cl_mem           /* buffer */,\n                   cl_bool          /* blocking_map */,\n                   cl_map_flags     /* map_flags */,\n                   size_t           /* offset */,\n                   size_t           /* size */,\n                   cl_uint          /* num_events_in_wait_list */,\n                   const cl_event * /* event_wait_list */,\n                   cl_event *       /* event */,\n                   cl_int *         /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY void * CL_API_CALL\nclEnqueueMapImage(cl_command_queue  /* command_queue */,\n                  cl_mem            /* image */,\n                  cl_bool           /* blocking_map */,\n                  cl_map_flags      /* map_flags */,\n                  const size_t *    /* origin[3] */,\n                  const size_t *    /* region[3] */,\n                  size_t *          /* image_row_pitch */,\n                  size_t *          /* image_slice_pitch */,\n                  cl_uint           /* num_events_in_wait_list */,\n                  const cl_event *  /* event_wait_list */,\n                  cl_event *        /* event */,\n                  cl_int *          /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueUnmapMemObject(cl_command_queue /* command_queue */,\n                        cl_mem           /* memobj */,\n                        void *           /* mapped_ptr */,\n                        cl_uint          /* num_events_in_wait_list */,\n                        const cl_event *  /* event_wait_list */,\n                        cl_event *        /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueMigrateMemObjects(cl_command_queue       /* command_queue */,\n                           cl_uint                /* num_mem_objects */,\n                           const cl_mem *         /* mem_objects */,\n                           cl_mem_migration_flags /* flags */,\n                           cl_uint                /* num_events_in_wait_list */,\n                           const cl_event *       /* event_wait_list */,\n                           cl_event *             /* event */) CL_API_SUFFIX__VERSION_1_2;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueNDRangeKernel(cl_command_queue /* command_queue */,\n                       cl_kernel        /* kernel */,\n                       cl_uint          /* work_dim */,\n                       const size_t *   /* global_work_offset */,\n                       const size_t *   /* global_work_size */,\n                       const size_t *   /* local_work_size */,\n                       cl_uint          /* num_events_in_wait_list */,\n                       const cl_event * /* event_wait_list */,\n                       cl_event *       /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueTask(cl_command_queue  /* command_queue */,\n              cl_kernel         /* kernel */,\n              cl_uint           /* num_events_in_wait_list */,\n              const cl_event *  /* event_wait_list */,\n              cl_event *        /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueNativeKernel(cl_command_queue  /* command_queue */,\n\t\t\t\t\t  void (CL_CALLBACK * /*user_func*/)(void *),\n                      void *            /* args */,\n                      size_t            /* cb_args */,\n                      cl_uint           /* num_mem_objects */,\n                      const cl_mem *    /* mem_list */,\n                      const void **     /* args_mem_loc */,\n                      cl_uint           /* num_events_in_wait_list */,\n                      const cl_event *  /* event_wait_list */,\n                      cl_event *        /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueMarkerWithWaitList(cl_command_queue /* command_queue */,\n                            cl_uint           /* num_events_in_wait_list */,\n                            const cl_event *  /* event_wait_list */,\n                            cl_event *        /* event */) CL_API_SUFFIX__VERSION_1_2;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueBarrierWithWaitList(cl_command_queue /* command_queue */,\n                             cl_uint           /* num_events_in_wait_list */,\n                             const cl_event *  /* event_wait_list */,\n                             cl_event *        /* event */) CL_API_SUFFIX__VERSION_1_2;\n\n\n/* Extension function access\n *\n * Returns the extension function address for the given function name,\n * or NULL if a valid function can not be found.  The client must\n * check to make sure the address is not NULL, before using or\n * calling the returned function address.\n */\nextern CL_API_ENTRY void * CL_API_CALL\nclGetExtensionFunctionAddressForPlatform(cl_platform_id /* platform */,\n                                         const char *   /* func_name */) CL_API_SUFFIX__VERSION_1_2;\n\n\n// Deprecated OpenCL 1.1 APIs\nextern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL\nclCreateImage2D(cl_context              /* context */,\n                cl_mem_flags            /* flags */,\n                const cl_image_format * /* image_format */,\n                size_t                  /* image_width */,\n                size_t                  /* image_height */,\n                size_t                  /* image_row_pitch */,\n                void *                  /* host_ptr */,\n                cl_int *                /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED;\n\nextern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL\nclCreateImage3D(cl_context              /* context */,\n                cl_mem_flags            /* flags */,\n                const cl_image_format * /* image_format */,\n                size_t                  /* image_width */,\n                size_t                  /* image_height */,\n                size_t                  /* image_depth */,\n                size_t                  /* image_row_pitch */,\n                size_t                  /* image_slice_pitch */,\n                void *                  /* host_ptr */,\n                cl_int *                /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED;\n\nextern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL\nclEnqueueMarker(cl_command_queue    /* command_queue */,\n                cl_event *          /* event */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED;\n\nextern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL\nclEnqueueWaitForEvents(cl_command_queue /* command_queue */,\n                        cl_uint          /* num_events */,\n                        const cl_event * /* event_list */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED;\n\nextern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL\nclEnqueueBarrier(cl_command_queue /* command_queue */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED;\n\nextern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL\nclUnloadCompiler(void) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED;\n\nextern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED void * CL_API_CALL\nclGetExtensionFunctionAddress(const char * /* func_name */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED;\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif  /* __OPENCL_CL_H */\n"
  },
  {
    "path": "caffe2/mobile/contrib/libopencl-stub/include/CL/cl.hpp",
    "content": "/*******************************************************************************\n * Copyright (c) 2008-2013 The Khronos Group Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and/or associated documentation files (the\n * \"Materials\"), to deal in the Materials without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Materials, and to\n * permit persons to whom the Materials are furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Materials.\n *\n * THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.\n ******************************************************************************/\n\n/*! \\file\n *\n *   \\brief C++ bindings for OpenCL 1.0 (rev 48), OpenCL 1.1 (rev 33) and \n *       OpenCL 1.2 (rev 15)    \n *   \\author Benedict R. Gaster, Laurent Morichetti and Lee Howes\n *   \n *   Additions and fixes from:\n *       Brian Cole, March 3rd 2010 and April 2012 \n *       Matt Gruenke, April 2012.\n *       Bruce Merry, February 2013.\n *       Tom Deakin and Simon McIntosh-Smith, July 2013\n *   \n *   \\version 1.2.6\n *   \\date August 2013\n *\n *   Optional extension support\n *\n *         cl\n *         cl_ext_device_fission\n *\t\t\t\t#define USE_CL_DEVICE_FISSION\n */\n\n/*! \\mainpage\n * \\section intro Introduction\n * For many large applications C++ is the language of choice and so it seems\n * reasonable to define C++ bindings for OpenCL.\n *\n *\n * The interface is contained with a single C++ header file \\em cl.hpp and all\n * definitions are contained within the namespace \\em cl. There is no additional\n * requirement to include \\em cl.h and to use either the C++ or original C\n * bindings it is enough to simply include \\em cl.hpp.\n *\n * The bindings themselves are lightweight and correspond closely to the\n * underlying C API. Using the C++ bindings introduces no additional execution\n * overhead.\n *\n * For detail documentation on the bindings see:\n *\n * The OpenCL C++ Wrapper API 1.2 (revision 09)\n *  http://www.khronos.org/registry/cl/specs/opencl-cplusplus-1.2.pdf\n *\n * \\section example Example\n *\n * The following example shows a general use case for the C++\n * bindings, including support for the optional exception feature and\n * also the supplied vector and string classes, see following sections for\n * decriptions of these features.\n *\n * \\code\n * #define __CL_ENABLE_EXCEPTIONS\n * \n * #if defined(__APPLE__) || defined(__MACOSX)\n * #include <OpenCL/cl.hpp>\n * #else\n * #include <CL/cl.hpp>\n * #endif\n * #include <cstdio>\n * #include <cstdlib>\n * #include <iostream>\n * \n *  const char * helloStr  = \"__kernel void \"\n *                           \"hello(void) \"\n *                           \"{ \"\n *                           \"  \"\n *                           \"} \";\n * \n *  int\n *  main(void)\n *  {\n *     cl_int err = CL_SUCCESS;\n *     try {\n *\n *       std::vector<cl::Platform> platforms;\n *       cl::Platform::get(&platforms);\n *       if (platforms.size() == 0) {\n *           std::cout << \"Platform size 0\\n\";\n *           return -1;\n *       }\n *\n *       cl_context_properties properties[] = \n *          { CL_CONTEXT_PLATFORM, (cl_context_properties)(platforms[0])(), 0};\n *       cl::Context context(CL_DEVICE_TYPE_CPU, properties); \n * \n *       std::vector<cl::Device> devices = context.getInfo<CL_CONTEXT_DEVICES>();\n * \n *       cl::Program::Sources source(1,\n *           std::make_pair(helloStr,strlen(helloStr)));\n *       cl::Program program_ = cl::Program(context, source);\n *       program_.build(devices);\n * \n *       cl::Kernel kernel(program_, \"hello\", &err);\n * \n *       cl::Event event;\n *       cl::CommandQueue queue(context, devices[0], 0, &err);\n *       queue.enqueueNDRangeKernel(\n *           kernel, \n *           cl::NullRange, \n *           cl::NDRange(4,4),\n *           cl::NullRange,\n *           NULL,\n *           &event); \n * \n *       event.wait();\n *     }\n *     catch (cl::Error err) {\n *        std::cerr \n *           << \"ERROR: \"\n *           << err.what()\n *           << \"(\"\n *           << err.err()\n *           << \")\"\n *           << std::endl;\n *     }\n * \n *    return EXIT_SUCCESS;\n *  }\n * \n * \\endcode\n *\n */\n#ifndef CL_HPP_\n#define CL_HPP_\n\n#ifdef _WIN32\n\n#include <windows.h>\n#include <malloc.h>\n#include <iterator>\n#include <intrin.h>\n\n#if defined(__CL_ENABLE_EXCEPTIONS)\n#include <exception>\n#endif // #if defined(__CL_ENABLE_EXCEPTIONS)\n\n#pragma push_macro(\"max\")\n#undef max\n#if defined(USE_DX_INTEROP)\n#include <CL/cl_d3d10.h>\n#include <CL/cl_dx9_media_sharing.h>\n#endif\n#endif // _WIN32\n\n// \n#if defined(USE_CL_DEVICE_FISSION)\n#include <CL/cl_ext.h>\n#endif\n\n#if defined(__APPLE__) || defined(__MACOSX)\n#include <OpenGL/OpenGL.h>\n#include <OpenCL/opencl.h>\n#include <libkern/OSAtomic.h>\n#elif defined(__ANDROID__)\n#include <GLES/gl.h>\n#include <CL/opencl.h>\n#else\n#include <GL/gl.h>\n#include <CL/opencl.h>\n#endif // !__APPLE__\n\n// To avoid accidentally taking ownership of core OpenCL types\n// such as cl_kernel constructors are made explicit\n// under OpenCL 1.2\n#if defined(CL_VERSION_1_2) && !defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n#define __CL_EXPLICIT_CONSTRUCTORS explicit\n#else // #if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n#define __CL_EXPLICIT_CONSTRUCTORS \n#endif // #if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n\n// Define deprecated prefixes and suffixes to ensure compilation\n// in case they are not pre-defined\n#if !defined(CL_EXT_PREFIX__VERSION_1_1_DEPRECATED)\n#define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED  \n#endif // #if !defined(CL_EXT_PREFIX__VERSION_1_1_DEPRECATED)\n#if !defined(CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED)\n#define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED\n#endif // #if !defined(CL_EXT_PREFIX__VERSION_1_1_DEPRECATED)\n\n#if !defined(CL_CALLBACK)\n#define CL_CALLBACK\n#endif //CL_CALLBACK\n\n#include <utility>\n#include <limits>\n\n#if !defined(__NO_STD_VECTOR)\n#include <vector>\n#endif\n\n#if !defined(__NO_STD_STRING)\n#include <string>\n#endif \n\n#if defined(linux) || defined(__APPLE__) || defined(__MACOSX) || defined(__ANDROID__)\n#include <alloca.h>\n\n#endif // linux\n\n#include <cstring>\n\n\n/*! \\namespace cl\n *\n * \\brief The OpenCL C++ bindings are defined within this namespace.\n *\n */\nnamespace cl {\n\nclass Memory;\n\n/**\n * Deprecated APIs for 1.2\n */\n#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) || (defined(CL_VERSION_1_1) && !defined(CL_VERSION_1_2)) \n#define __INIT_CL_EXT_FCN_PTR(name) \\\n    if(!pfn_##name) { \\\n        pfn_##name = (PFN_##name) \\\n            clGetExtensionFunctionAddress(#name); \\\n        if(!pfn_##name) { \\\n        } \\\n    }\n#endif // #if defined(CL_VERSION_1_1)\n\n#if defined(CL_VERSION_1_2)\n#define __INIT_CL_EXT_FCN_PTR_PLATFORM(platform, name) \\\n    if(!pfn_##name) { \\\n        pfn_##name = (PFN_##name) \\\n            clGetExtensionFunctionAddressForPlatform(platform, #name); \\\n        if(!pfn_##name) { \\\n        } \\\n    }\n#endif // #if defined(CL_VERSION_1_1)\n\nclass Program;\nclass Device;\nclass Context;\nclass CommandQueue;\nclass Memory;\nclass Buffer;\n\n#if defined(__CL_ENABLE_EXCEPTIONS)\n/*! \\brief Exception class \n * \n *  This may be thrown by API functions when __CL_ENABLE_EXCEPTIONS is defined.\n */\nclass Error : public std::exception\n{\nprivate:\n    cl_int err_;\n    const char * errStr_;\npublic:\n    /*! \\brief Create a new CL error exception for a given error code\n     *  and corresponding message.\n     * \n     *  \\param err error code value.\n     *\n     *  \\param errStr a descriptive string that must remain in scope until\n     *                handling of the exception has concluded.  If set, it\n     *                will be returned by what().\n     */\n    Error(cl_int err, const char * errStr = NULL) : err_(err), errStr_(errStr)\n    {}\n\n    ~Error() throw() {}\n\n    /*! \\brief Get error string associated with exception\n     *\n     * \\return A memory pointer to the error message string.\n     */\n    virtual const char * what() const throw ()\n    {\n        if (errStr_ == NULL) {\n            return \"empty\";\n        }\n        else {\n            return errStr_;\n        }\n    }\n\n    /*! \\brief Get error code associated with exception\n     *\n     *  \\return The error code.\n     */\n    cl_int err(void) const { return err_; }\n};\n\n#define __ERR_STR(x) #x\n#else\n#define __ERR_STR(x) NULL\n#endif // __CL_ENABLE_EXCEPTIONS\n\n\nnamespace detail\n{\n#if defined(__CL_ENABLE_EXCEPTIONS)\nstatic inline cl_int errHandler (\n    cl_int err,\n    const char * errStr = NULL)\n{\n    if (err != CL_SUCCESS) {\n        throw Error(err, errStr);\n    }\n    return err;\n}\n#else\nstatic inline cl_int errHandler (cl_int err, const char * errStr = NULL)\n{\n    (void) errStr; // suppress unused variable warning\n    return err;\n}\n#endif // __CL_ENABLE_EXCEPTIONS\n}\n\n\n\n//! \\cond DOXYGEN_DETAIL\n#if !defined(__CL_USER_OVERRIDE_ERROR_STRINGS)\n#define __GET_DEVICE_INFO_ERR               __ERR_STR(clGetDeviceInfo)\n#define __GET_PLATFORM_INFO_ERR             __ERR_STR(clGetPlatformInfo)\n#define __GET_DEVICE_IDS_ERR                __ERR_STR(clGetDeviceIDs)\n#define __GET_PLATFORM_IDS_ERR              __ERR_STR(clGetPlatformIDs)\n#define __GET_CONTEXT_INFO_ERR              __ERR_STR(clGetContextInfo)\n#define __GET_EVENT_INFO_ERR                __ERR_STR(clGetEventInfo)\n#define __GET_EVENT_PROFILE_INFO_ERR        __ERR_STR(clGetEventProfileInfo)\n#define __GET_MEM_OBJECT_INFO_ERR           __ERR_STR(clGetMemObjectInfo)\n#define __GET_IMAGE_INFO_ERR                __ERR_STR(clGetImageInfo)\n#define __GET_SAMPLER_INFO_ERR              __ERR_STR(clGetSamplerInfo)\n#define __GET_KERNEL_INFO_ERR               __ERR_STR(clGetKernelInfo)\n#if defined(CL_VERSION_1_2)\n#define __GET_KERNEL_ARG_INFO_ERR               __ERR_STR(clGetKernelArgInfo)\n#endif // #if defined(CL_VERSION_1_2)\n#define __GET_KERNEL_WORK_GROUP_INFO_ERR    __ERR_STR(clGetKernelWorkGroupInfo)\n#define __GET_PROGRAM_INFO_ERR              __ERR_STR(clGetProgramInfo)\n#define __GET_PROGRAM_BUILD_INFO_ERR        __ERR_STR(clGetProgramBuildInfo)\n#define __GET_COMMAND_QUEUE_INFO_ERR        __ERR_STR(clGetCommandQueueInfo)\n\n#define __CREATE_CONTEXT_ERR                __ERR_STR(clCreateContext)\n#define __CREATE_CONTEXT_FROM_TYPE_ERR      __ERR_STR(clCreateContextFromType)\n#define __GET_SUPPORTED_IMAGE_FORMATS_ERR   __ERR_STR(clGetSupportedImageFormats)\n\n#define __CREATE_BUFFER_ERR                 __ERR_STR(clCreateBuffer)\n#define __COPY_ERR                          __ERR_STR(cl::copy)\n#define __CREATE_SUBBUFFER_ERR              __ERR_STR(clCreateSubBuffer)\n#define __CREATE_GL_BUFFER_ERR              __ERR_STR(clCreateFromGLBuffer)\n#define __CREATE_GL_RENDER_BUFFER_ERR       __ERR_STR(clCreateFromGLBuffer)\n#define __GET_GL_OBJECT_INFO_ERR            __ERR_STR(clGetGLObjectInfo)\n#if defined(CL_VERSION_1_2)\n#define __CREATE_IMAGE_ERR                  __ERR_STR(clCreateImage)\n#define __CREATE_GL_TEXTURE_ERR             __ERR_STR(clCreateFromGLTexture)\n#define __IMAGE_DIMENSION_ERR               __ERR_STR(Incorrect image dimensions)\n#endif // #if defined(CL_VERSION_1_2)\n#define __CREATE_SAMPLER_ERR                __ERR_STR(clCreateSampler)\n#define __SET_MEM_OBJECT_DESTRUCTOR_CALLBACK_ERR __ERR_STR(clSetMemObjectDestructorCallback)\n\n#define __CREATE_USER_EVENT_ERR             __ERR_STR(clCreateUserEvent)\n#define __SET_USER_EVENT_STATUS_ERR         __ERR_STR(clSetUserEventStatus)\n#define __SET_EVENT_CALLBACK_ERR            __ERR_STR(clSetEventCallback)\n#define __WAIT_FOR_EVENTS_ERR               __ERR_STR(clWaitForEvents)\n\n#define __CREATE_KERNEL_ERR                 __ERR_STR(clCreateKernel)\n#define __SET_KERNEL_ARGS_ERR               __ERR_STR(clSetKernelArg)\n#define __CREATE_PROGRAM_WITH_SOURCE_ERR    __ERR_STR(clCreateProgramWithSource)\n#define __CREATE_PROGRAM_WITH_BINARY_ERR    __ERR_STR(clCreateProgramWithBinary)\n#if defined(CL_VERSION_1_2)\n#define __CREATE_PROGRAM_WITH_BUILT_IN_KERNELS_ERR    __ERR_STR(clCreateProgramWithBuiltInKernels)\n#endif // #if defined(CL_VERSION_1_2)\n#define __BUILD_PROGRAM_ERR                 __ERR_STR(clBuildProgram)\n#if defined(CL_VERSION_1_2)\n#define __COMPILE_PROGRAM_ERR                  __ERR_STR(clCompileProgram)\n\n#endif // #if defined(CL_VERSION_1_2)\n#define __CREATE_KERNELS_IN_PROGRAM_ERR     __ERR_STR(clCreateKernelsInProgram)\n\n#define __CREATE_COMMAND_QUEUE_ERR          __ERR_STR(clCreateCommandQueue)\n#define __SET_COMMAND_QUEUE_PROPERTY_ERR    __ERR_STR(clSetCommandQueueProperty)\n#define __ENQUEUE_READ_BUFFER_ERR           __ERR_STR(clEnqueueReadBuffer)\n#define __ENQUEUE_READ_BUFFER_RECT_ERR      __ERR_STR(clEnqueueReadBufferRect)\n#define __ENQUEUE_WRITE_BUFFER_ERR          __ERR_STR(clEnqueueWriteBuffer)\n#define __ENQUEUE_WRITE_BUFFER_RECT_ERR     __ERR_STR(clEnqueueWriteBufferRect)\n#define __ENQEUE_COPY_BUFFER_ERR            __ERR_STR(clEnqueueCopyBuffer)\n#define __ENQEUE_COPY_BUFFER_RECT_ERR       __ERR_STR(clEnqueueCopyBufferRect)\n#define __ENQUEUE_FILL_BUFFER_ERR           __ERR_STR(clEnqueueFillBuffer)\n#define __ENQUEUE_READ_IMAGE_ERR            __ERR_STR(clEnqueueReadImage)\n#define __ENQUEUE_WRITE_IMAGE_ERR           __ERR_STR(clEnqueueWriteImage)\n#define __ENQUEUE_COPY_IMAGE_ERR            __ERR_STR(clEnqueueCopyImage)\n#define __ENQUEUE_FILL_IMAGE_ERR           __ERR_STR(clEnqueueFillImage)\n#define __ENQUEUE_COPY_IMAGE_TO_BUFFER_ERR  __ERR_STR(clEnqueueCopyImageToBuffer)\n#define __ENQUEUE_COPY_BUFFER_TO_IMAGE_ERR  __ERR_STR(clEnqueueCopyBufferToImage)\n#define __ENQUEUE_MAP_BUFFER_ERR            __ERR_STR(clEnqueueMapBuffer)\n#define __ENQUEUE_MAP_IMAGE_ERR             __ERR_STR(clEnqueueMapImage)\n#define __ENQUEUE_UNMAP_MEM_OBJECT_ERR      __ERR_STR(clEnqueueUnMapMemObject)\n#define __ENQUEUE_NDRANGE_KERNEL_ERR        __ERR_STR(clEnqueueNDRangeKernel)\n#define __ENQUEUE_TASK_ERR                  __ERR_STR(clEnqueueTask)\n#define __ENQUEUE_NATIVE_KERNEL             __ERR_STR(clEnqueueNativeKernel)\n#if defined(CL_VERSION_1_2)\n#define __ENQUEUE_MIGRATE_MEM_OBJECTS_ERR   __ERR_STR(clEnqueueMigrateMemObjects)\n#endif // #if defined(CL_VERSION_1_2)\n\n#define __ENQUEUE_ACQUIRE_GL_ERR            __ERR_STR(clEnqueueAcquireGLObjects)\n#define __ENQUEUE_RELEASE_GL_ERR            __ERR_STR(clEnqueueReleaseGLObjects)\n\n\n#define __RETAIN_ERR                        __ERR_STR(Retain Object)\n#define __RELEASE_ERR                       __ERR_STR(Release Object)\n#define __FLUSH_ERR                         __ERR_STR(clFlush)\n#define __FINISH_ERR                        __ERR_STR(clFinish)\n#define __VECTOR_CAPACITY_ERR               __ERR_STR(Vector capacity error)\n\n/**\n * CL 1.2 version that uses device fission.\n */\n#if defined(CL_VERSION_1_2)\n#define __CREATE_SUB_DEVICES                __ERR_STR(clCreateSubDevices)\n#else\n#define __CREATE_SUB_DEVICES                __ERR_STR(clCreateSubDevicesEXT)\n#endif // #if defined(CL_VERSION_1_2)\n\n/**\n * Deprecated APIs for 1.2\n */\n#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) || (defined(CL_VERSION_1_1) && !defined(CL_VERSION_1_2)) \n#define __ENQUEUE_MARKER_ERR                __ERR_STR(clEnqueueMarker)\n#define __ENQUEUE_WAIT_FOR_EVENTS_ERR       __ERR_STR(clEnqueueWaitForEvents)\n#define __ENQUEUE_BARRIER_ERR               __ERR_STR(clEnqueueBarrier)\n#define __UNLOAD_COMPILER_ERR               __ERR_STR(clUnloadCompiler)\n#define __CREATE_GL_TEXTURE_2D_ERR          __ERR_STR(clCreateFromGLTexture2D)\n#define __CREATE_GL_TEXTURE_3D_ERR          __ERR_STR(clCreateFromGLTexture3D)\n#define __CREATE_IMAGE2D_ERR                __ERR_STR(clCreateImage2D)\n#define __CREATE_IMAGE3D_ERR                __ERR_STR(clCreateImage3D)\n#endif // #if defined(CL_VERSION_1_1)\n\n#endif // __CL_USER_OVERRIDE_ERROR_STRINGS\n//! \\endcond\n\n/**\n * CL 1.2 marker and barrier commands\n */\n#if defined(CL_VERSION_1_2)\n#define __ENQUEUE_MARKER_WAIT_LIST_ERR                __ERR_STR(clEnqueueMarkerWithWaitList)\n#define __ENQUEUE_BARRIER_WAIT_LIST_ERR               __ERR_STR(clEnqueueBarrierWithWaitList)\n#endif // #if defined(CL_VERSION_1_2)\n\n#if !defined(__USE_DEV_STRING) && !defined(__NO_STD_STRING)\ntypedef std::string STRING_CLASS;\n#elif !defined(__USE_DEV_STRING) \n\n/*! \\class string\n * \\brief Simple string class, that provides a limited subset of std::string\n * functionality but avoids many of the issues that come with that class.\n \n *  \\note Deprecated. Please use std::string as default or\n *  re-define the string class to match the std::string\n *  interface by defining STRING_CLASS\n */\nclass CL_EXT_PREFIX__VERSION_1_1_DEPRECATED string CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED\n{\nprivate:\n    ::size_t size_;\n    char * str_;\npublic:\n    //! \\brief Constructs an empty string, allocating no memory.\n    string(void) : size_(0), str_(NULL)\n    {\n    }\n\n    /*! \\brief Constructs a string populated from an arbitrary value of\n     *  specified size.\n     * \n     *  An extra '\\0' is added, in case none was contained in str.\n     *\n     *  \\param str the initial value of the string instance.  Note that '\\0'     \n     *             characters receive no special treatment.  If NULL,\n     *             the string is left empty, with a size of 0.\n     *\n     *  \\param size the number of characters to copy from str.\n     */\n    string(const char * str, ::size_t size) :\n        size_(size),\n        str_(NULL)\n    {\n        if( size > 0 ) {\n            str_ = new char[size_+1];\n            if (str_ != NULL) {\n                memcpy(str_, str, size_  * sizeof(char));\n                str_[size_] = '\\0';\n            }\n            else {\n                size_ = 0;\n            }\n        }\n    }\n\n    /*! \\brief Constructs a string populated from a null-terminated value.\n     *\n     *  \\param str the null-terminated initial value of the string instance.\n     *             If NULL, the string is left empty, with a size of 0.\n     */\n    string(const char * str) :\n        size_(0),\n        str_(NULL)\n    {\n        if( str ) {\n            size_= ::strlen(str);\n        }\n        if( size_ > 0 ) {\n            str_ = new char[size_ + 1];\n            if (str_ != NULL) {\n                memcpy(str_, str, (size_ + 1) * sizeof(char));\n            }\n        }\n    }\n\n    void resize( ::size_t n )\n    {\n        if( size_ == n ) {\n            return;\n        }\n        if (n == 0) {\n            if( str_ ) {\n                delete [] str_;\n            }\n            str_ = NULL;\n            size_ = 0;\n        } \n        else {\n            char *newString = new char[n + 1];\n            int copySize = n;\n            if( size_ < n ) {\n                copySize = size_;\n            }\n            size_ = n;\n            \n            if(str_) {\n                memcpy(newString, str_, (copySize + 1) * sizeof(char));\n            }\n            if( copySize < size_ ) {\n                memset(newString + copySize, 0, size_ - copySize);\n            }\n            newString[size_] = '\\0';\n\n            delete [] str_;\n            str_ = newString;\n        }\n    }\n\n    const char& operator[] ( ::size_t pos ) const\n    {\n        return str_[pos];\n    }\n\n    char& operator[] ( ::size_t pos )\n    {\n        return str_[pos];\n    }\n\n    /*! \\brief Copies the value of another string to this one.\n     *\n     *  \\param rhs the string to copy.\n     *\n     *  \\returns a reference to the modified instance.\n     */\n    string& operator=(const string& rhs)\n    {\n        if (this == &rhs) {\n            return *this;\n        }\n\n        if( str_ != NULL ) {\n            delete [] str_;\n            str_ = NULL;\n            size_ = 0;\n        }\n\n        if (rhs.size_ == 0 || rhs.str_ == NULL) {\n            str_ = NULL;\n            size_ = 0;\n        } \n        else {\n            str_ = new char[rhs.size_ + 1];\n            size_ = rhs.size_;\n            \n            if (str_ != NULL) {\n                memcpy(str_, rhs.str_, (size_ + 1) * sizeof(char));\n            }\n            else {\n                size_ = 0;\n            }\n        }\n\n        return *this;\n    }\n\n    /*! \\brief Constructs a string by copying the value of another instance.\n     *\n     *  \\param rhs the string to copy.\n     */\n    string(const string& rhs) :\n        size_(0),\n        str_(NULL)\n    {\n        *this = rhs;\n    }\n\n    //! \\brief Destructor - frees memory used to hold the current value.\n    ~string()\n    {\n        delete[] str_;\n        str_ = NULL;\n    }\n    \n    //! \\brief Queries the length of the string, excluding any added '\\0's.\n    ::size_t size(void) const   { return size_; }\n\n    //! \\brief Queries the length of the string, excluding any added '\\0's.\n    ::size_t length(void) const { return size(); }\n\n    /*! \\brief Returns a pointer to the private copy held by this instance,\n     *  or \"\" if empty/unset.\n     */\n    const char * c_str(void) const { return (str_) ? str_ : \"\";}\n};\ntypedef cl::string STRING_CLASS;\n#endif // #elif !defined(__USE_DEV_STRING) \n\n#if !defined(__USE_DEV_VECTOR) && !defined(__NO_STD_VECTOR)\n#define VECTOR_CLASS std::vector\n#elif !defined(__USE_DEV_VECTOR) \n#define VECTOR_CLASS cl::vector \n\n#if !defined(__MAX_DEFAULT_VECTOR_SIZE)\n#define __MAX_DEFAULT_VECTOR_SIZE 10\n#endif\n\n/*! \\class vector\n * \\brief Fixed sized vector implementation that mirroring \n *\n *  \\note Deprecated. Please use std::vector as default or\n *  re-define the vector class to match the std::vector\n *  interface by defining VECTOR_CLASS\n\n *  \\note Not recommended for use with custom objects as\n *  current implementation will construct N elements\n *\n * std::vector functionality.\n *  \\brief Fixed sized vector compatible with std::vector.\n *\n *  \\note\n *  This differs from std::vector<> not just in memory allocation,\n *  but also in terms of when members are constructed, destroyed,\n *  and assigned instead of being copy constructed.\n *\n *  \\param T type of element contained in the vector.\n *\n *  \\param N maximum size of the vector.\n */\ntemplate <typename T, unsigned int N = __MAX_DEFAULT_VECTOR_SIZE>\nclass CL_EXT_PREFIX__VERSION_1_1_DEPRECATED vector CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED\n{\nprivate:\n    T data_[N];\n    unsigned int size_;\n\npublic:\n    //! \\brief Constructs an empty vector with no memory allocated.\n    vector() :  \n        size_(static_cast<unsigned int>(0))\n    {}\n\n    //! \\brief Deallocates the vector's memory and destroys all of its elements.\n    ~vector() \n    {\n        clear();\n    }\n\n    //! \\brief Returns the number of elements currently contained.\n    unsigned int size(void) const\n    {\n        return size_;\n    }\n    \n    /*! \\brief Empties the vector of all elements.\n     *  \\note\n     *  This does not deallocate memory but will invoke destructors\n     *  on contained elements.\n     */\n    void clear()\n    {\n        while(!empty()) {\n            pop_back();\n        }\n    }\n\n    /*! \\brief Appends an element after the last valid element.\n     * Calling this on a vector that has reached capacity will throw an \n     * exception if exceptions are enabled.\n     */\n    void push_back (const T& x)\n    { \n        if (size() < N) {    \n            new (&data_[size_]) T(x);\n            size_++;\n        } else {\n            detail::errHandler(CL_MEM_OBJECT_ALLOCATION_FAILURE, __VECTOR_CAPACITY_ERR);\n        }\n    }\n\n    /*! \\brief Removes the last valid element from the vector.\n     * Calling this on an empty vector will throw an exception\n     * if exceptions are enabled.\n     */\n    void pop_back(void)\n    {\n        if (size_ != 0) {\n            --size_;\n            data_[size_].~T();\n        } else {\n            detail::errHandler(CL_MEM_OBJECT_ALLOCATION_FAILURE, __VECTOR_CAPACITY_ERR);\n        }\n    }\n  \n    /*! \\brief Constructs with a value copied from another.\n     *\n     *  \\param vec the vector to copy.\n     */\n    vector(const vector<T, N>& vec) : \n        size_(vec.size_)\n    {\n        if (size_ != 0) {\t\n            assign(vec.begin(), vec.end());\n        }\n    } \n\n    /*! \\brief Constructs with a specified number of initial elements.\n     *\n     *  \\param size number of initial elements.\n     *\n     *  \\param val value of initial elements.\n     */\n    vector(unsigned int size, const T& val = T()) :\n        size_(0)\n    {\n        for (unsigned int i = 0; i < size; i++) {\n            push_back(val);\n        }\n    }\n\n    /*! \\brief Overwrites the current content with that copied from another\n     *         instance.\n     *\n     *  \\param rhs vector to copy.\n     *\n     *  \\returns a reference to this.\n     */\n    vector<T, N>& operator=(const vector<T, N>& rhs)\n    {\n        if (this == &rhs) {\n            return *this;\n        }\n\n        if (rhs.size_ != 0) {\t\n            assign(rhs.begin(), rhs.end());\n        } else {\n            clear();\n        }\n    \n        return *this;\n    }\n\n    /*! \\brief Tests equality against another instance.\n     *\n     *  \\param vec the vector against which to compare.\n     */\n    bool operator==(vector<T,N> &vec)\n    {\n        if (size() != vec.size()) {\n            return false;\n        }\n\n        for( unsigned int i = 0; i < size(); ++i ) {\n            if( operator[](i) != vec[i] ) {\n                return false;\n            }\n        }\n        return true;\n    }\n  \n    //! \\brief Conversion operator to T*.\n    operator T* ()             { return data_; }\n\n    //! \\brief Conversion operator to const T*.\n    operator const T* () const { return data_; }\n   \n    //! \\brief Tests whether this instance has any elements.\n    bool empty (void) const\n    {\n        return size_==0;\n    }\n  \n    //! \\brief Returns the maximum number of elements this instance can hold.\n    unsigned int max_size (void) const\n    {\n        return N;\n    }\n\n    //! \\brief Returns the maximum number of elements this instance can hold.\n    unsigned int capacity () const\n    {\n        return N;\n    }\n\n    /*! \\brief Returns a reference to a given element.\n     *\n     *  \\param index which element to access.     *\n     *  \\note\n     *  The caller is responsible for ensuring index is >= 0 and < size().\n     */\n    T& operator[](int index)\n    {\n        return data_[index];\n    }\n  \n    /*! \\brief Returns a const reference to a given element.\n     *\n     *  \\param index which element to access.\n     *\n     *  \\note\n     *  The caller is responsible for ensuring index is >= 0 and < size().\n     */\n    const T& operator[](int index) const\n    {\n        return data_[index];\n    }\n  \n    /*! \\brief Assigns elements of the vector based on a source iterator range.\n     *\n     *  \\param start Beginning iterator of source range\n     *  \\param end Enditerator of source range\n     *\n     *  \\note\n     *  Will throw an exception if exceptions are enabled and size exceeded.\n     */\n    template<class I>\n    void assign(I start, I end)\n    {\n        clear();   \n        while(start != end) {\n            push_back(*start);\n            start++;\n        }\n    }\n\n    /*! \\class iterator\n     * \\brief Const iterator class for vectors\n     */\n    class iterator\n    {\n    private:\n        const vector<T,N> *vec_;\n        int index_;\n\n        /**\n         * Internal iterator constructor to capture reference\n         * to the vector it iterates over rather than taking \n         * the vector by copy.\n         */\n        iterator (const vector<T,N> &vec, int index) :\n            vec_(&vec)\n        {            \n            if( !vec.empty() ) {\n                index_ = index;\n            } else {\n                index_ = -1;\n            }\n        }\n\n    public:\n        iterator(void) : \n            index_(-1),\n            vec_(NULL)\n        {\n        }\n\n        iterator(const iterator& rhs) :\n            vec_(rhs.vec_),\n            index_(rhs.index_)\n        {\n        }\n\n        ~iterator(void) {}\n\n        static iterator begin(const cl::vector<T,N> &vec)\n        {\n            iterator i(vec, 0);\n\n            return i;\n        }\n\n        static iterator end(const cl::vector<T,N> &vec)\n        {\n            iterator i(vec, vec.size());\n\n            return i;\n        }\n    \n        bool operator==(iterator i)\n        {\n            return ((vec_ == i.vec_) && \n                    (index_ == i.index_));\n        }\n\n        bool operator!=(iterator i)\n        {\n            return (!(*this==i));\n        }\n\n        iterator& operator++()\n        {\n            ++index_;\n            return *this;\n        }\n\n        iterator operator++(int)\n        {\n            iterator retVal(*this);\n            ++index_;\n            return retVal;\n        }\n\n        iterator& operator--()\n        {\n            --index_;\n            return *this;\n        }\n\n        iterator operator--(int)\n        {\n            iterator retVal(*this);\n            --index_;\n            return retVal;\n        }\n\n        const T& operator *() const\n        {\n            return (*vec_)[index_];\n        }\n    };\n\n    iterator begin(void)\n    {\n        return iterator::begin(*this);\n    }\n\n    iterator begin(void) const\n    {\n        return iterator::begin(*this);\n    }\n\n    iterator end(void)\n    {\n        return iterator::end(*this);\n    }\n\n    iterator end(void) const\n    {\n        return iterator::end(*this);\n    }\n\n    T& front(void)\n    {\n        return data_[0];\n    }\n\n    T& back(void)\n    {\n        return data_[size_];\n    }\n\n    const T& front(void) const\n    {\n        return data_[0];\n    }\n\n    const T& back(void) const\n    {\n        return data_[size_-1];\n    }\n};  \n#endif // #if !defined(__USE_DEV_VECTOR) && !defined(__NO_STD_VECTOR)\n\n\n\n\n\nnamespace detail {\n#define __DEFAULT_NOT_INITIALIZED 1 \n#define __DEFAULT_BEING_INITIALIZED 2\n#define __DEFAULT_INITIALIZED 4\n\n    /*\n     * Compare and exchange primitives are needed for handling of defaults\n    */\n    inline int compare_exchange(volatile int * dest, int exchange, int comparand)\n    {\n#ifdef _WIN32\n        return (int)(InterlockedCompareExchange(\n           (volatile long*)dest, \n           (long)exchange, \n           (long)comparand));\n#elif defined(__APPLE__) || defined(__MACOSX)\n\t\treturn OSAtomicOr32Orig((uint32_t)exchange, (volatile uint32_t*)dest);\n#else // !_WIN32 || defined(__APPLE__) || defined(__MACOSX)\n        return (__sync_val_compare_and_swap(\n            dest, \n            comparand, \n            exchange));\n#endif // !_WIN32\n    }\n\n    inline void fence() { __sync_synchronize(); }\n} // namespace detail\n\n    \n/*! \\brief class used to interface between C++ and\n *  OpenCL C calls that require arrays of size_t values, whose\n *  size is known statically.\n */\ntemplate <int N>\nclass size_t\n{ \nprivate:\n    ::size_t data_[N];\n\npublic:\n    //! \\brief Initialize size_t to all 0s\n    size_t()\n    {\n        for( int i = 0; i < N; ++i ) {\n            data_[i] = 0;\n        }\n    }\n\n    ::size_t& operator[](int index)\n    {\n        return data_[index];\n    }\n\n    const ::size_t& operator[](int index) const\n    {\n        return data_[index];\n    }\n\n    //! \\brief Conversion operator to T*.\n    operator ::size_t* ()             { return data_; }\n\n    //! \\brief Conversion operator to const T*.\n    operator const ::size_t* () const { return data_; }\n};\n\nnamespace detail {\n\n// Generic getInfoHelper. The final parameter is used to guide overload\n// resolution: the actual parameter passed is an int, which makes this\n// a worse conversion sequence than a specialization that declares the\n// parameter as an int.\ntemplate<typename Functor, typename T>\ninline cl_int getInfoHelper(Functor f, cl_uint name, T* param, long)\n{\n    return f(name, sizeof(T), param, NULL);\n}\n\n// Specialized getInfoHelper for VECTOR_CLASS params\ntemplate <typename Func, typename T>\ninline cl_int getInfoHelper(Func f, cl_uint name, VECTOR_CLASS<T>* param, long)\n{\n    ::size_t required;\n    cl_int err = f(name, 0, NULL, &required);\n    if (err != CL_SUCCESS) {\n        return err;\n    }\n\n    T* value = (T*) alloca(required);\n    err = f(name, required, value, NULL);\n    if (err != CL_SUCCESS) {\n        return err;\n    }\n\n    param->assign(&value[0], &value[required/sizeof(T)]);\n    return CL_SUCCESS;\n}\n\n/* Specialization for reference-counted types. This depends on the\n * existence of Wrapper<T>::cl_type, and none of the other types having the\n * cl_type member. Note that simplify specifying the parameter as Wrapper<T>\n * does not work, because when using a derived type (e.g. Context) the generic\n * template will provide a better match.\n */\ntemplate <typename Func, typename T>\ninline cl_int getInfoHelper(Func f, cl_uint name, VECTOR_CLASS<T>* param, int, typename T::cl_type = 0)\n{\n    ::size_t required;\n    cl_int err = f(name, 0, NULL, &required);\n    if (err != CL_SUCCESS) {\n        return err;\n    }\n\n    typename T::cl_type * value = (typename T::cl_type *) alloca(required);\n    err = f(name, required, value, NULL);\n    if (err != CL_SUCCESS) {\n        return err;\n    }\n\n    ::size_t elements = required / sizeof(typename T::cl_type);\n    param->assign(&value[0], &value[elements]);\n    for (::size_t i = 0; i < elements; i++)\n    {\n        if (value[i] != NULL)\n        {\n            err = (*param)[i].retain();\n            if (err != CL_SUCCESS) {\n                return err;\n            }\n        }\n    }\n    return CL_SUCCESS;\n}\n\n// Specialized for getInfo<CL_PROGRAM_BINARIES>\ntemplate <typename Func>\ninline cl_int getInfoHelper(Func f, cl_uint name, VECTOR_CLASS<char *>* param, int)\n{\n    cl_int err = f(name, param->size() * sizeof(char *), &(*param)[0], NULL);\n\n    if (err != CL_SUCCESS) {\n        return err;\n    }\n\n    return CL_SUCCESS;\n}\n\n// Specialized GetInfoHelper for STRING_CLASS params\ntemplate <typename Func>\ninline cl_int getInfoHelper(Func f, cl_uint name, STRING_CLASS* param, long)\n{\n    ::size_t required;\n    cl_int err = f(name, 0, NULL, &required);\n    if (err != CL_SUCCESS) {\n        return err;\n    }\n\n    char* value = (char*) alloca(required);\n    err = f(name, required, value, NULL);\n    if (err != CL_SUCCESS) {\n        return err;\n    }\n\n    *param = value;\n    return CL_SUCCESS;\n}\n\n// Specialized GetInfoHelper for cl::size_t params\ntemplate <typename Func, ::size_t N>\ninline cl_int getInfoHelper(Func f, cl_uint name, size_t<N>* param, long)\n{\n    ::size_t required;\n    cl_int err = f(name, 0, NULL, &required);\n    if (err != CL_SUCCESS) {\n        return err;\n    }\n\n    ::size_t* value = (::size_t*) alloca(required);\n    err = f(name, required, value, NULL);\n    if (err != CL_SUCCESS) {\n        return err;\n    }\n\n    for(int i = 0; i < N; ++i) {\n        (*param)[i] = value[i];\n    }\n\n    return CL_SUCCESS;\n}\n\ntemplate<typename T> struct ReferenceHandler;\n\n/* Specialization for reference-counted types. This depends on the\n * existence of Wrapper<T>::cl_type, and none of the other types having the\n * cl_type member. Note that simplify specifying the parameter as Wrapper<T>\n * does not work, because when using a derived type (e.g. Context) the generic\n * template will provide a better match.\n */\ntemplate<typename Func, typename T>\ninline cl_int getInfoHelper(Func f, cl_uint name, T* param, int, typename T::cl_type = 0)\n{\n    typename T::cl_type value;\n    cl_int err = f(name, sizeof(value), &value, NULL);\n    if (err != CL_SUCCESS) {\n        return err;\n    }\n    *param = value;\n    if (value != NULL)\n    {\n        err = param->retain();\n        if (err != CL_SUCCESS) {\n            return err;\n        }\n    }\n    return CL_SUCCESS;\n}\n\n#define __PARAM_NAME_INFO_1_0(F) \\\n    F(cl_platform_info, CL_PLATFORM_PROFILE, STRING_CLASS) \\\n    F(cl_platform_info, CL_PLATFORM_VERSION, STRING_CLASS) \\\n    F(cl_platform_info, CL_PLATFORM_NAME, STRING_CLASS) \\\n    F(cl_platform_info, CL_PLATFORM_VENDOR, STRING_CLASS) \\\n    F(cl_platform_info, CL_PLATFORM_EXTENSIONS, STRING_CLASS) \\\n    \\\n    F(cl_device_info, CL_DEVICE_TYPE, cl_device_type) \\\n    F(cl_device_info, CL_DEVICE_VENDOR_ID, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_MAX_COMPUTE_UNITS, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_MAX_WORK_GROUP_SIZE, ::size_t) \\\n    F(cl_device_info, CL_DEVICE_MAX_WORK_ITEM_SIZES, VECTOR_CLASS< ::size_t>) \\\n    F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_MAX_CLOCK_FREQUENCY, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_ADDRESS_BITS, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_MAX_READ_IMAGE_ARGS, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_MAX_WRITE_IMAGE_ARGS, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_MAX_MEM_ALLOC_SIZE, cl_ulong) \\\n    F(cl_device_info, CL_DEVICE_IMAGE2D_MAX_WIDTH, ::size_t) \\\n    F(cl_device_info, CL_DEVICE_IMAGE2D_MAX_HEIGHT, ::size_t) \\\n    F(cl_device_info, CL_DEVICE_IMAGE3D_MAX_WIDTH, ::size_t) \\\n    F(cl_device_info, CL_DEVICE_IMAGE3D_MAX_HEIGHT, ::size_t) \\\n    F(cl_device_info, CL_DEVICE_IMAGE3D_MAX_DEPTH, ::size_t) \\\n    F(cl_device_info, CL_DEVICE_IMAGE_SUPPORT, cl_bool) \\\n    F(cl_device_info, CL_DEVICE_MAX_PARAMETER_SIZE, ::size_t) \\\n    F(cl_device_info, CL_DEVICE_MAX_SAMPLERS, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_MEM_BASE_ADDR_ALIGN, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_SINGLE_FP_CONFIG, cl_device_fp_config) \\\n    F(cl_device_info, CL_DEVICE_GLOBAL_MEM_CACHE_TYPE, cl_device_mem_cache_type) \\\n    F(cl_device_info, CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE, cl_uint)\\\n    F(cl_device_info, CL_DEVICE_GLOBAL_MEM_CACHE_SIZE, cl_ulong) \\\n    F(cl_device_info, CL_DEVICE_GLOBAL_MEM_SIZE, cl_ulong) \\\n    F(cl_device_info, CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE, cl_ulong) \\\n    F(cl_device_info, CL_DEVICE_MAX_CONSTANT_ARGS, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_LOCAL_MEM_TYPE, cl_device_local_mem_type) \\\n    F(cl_device_info, CL_DEVICE_LOCAL_MEM_SIZE, cl_ulong) \\\n    F(cl_device_info, CL_DEVICE_ERROR_CORRECTION_SUPPORT, cl_bool) \\\n    F(cl_device_info, CL_DEVICE_PROFILING_TIMER_RESOLUTION, ::size_t) \\\n    F(cl_device_info, CL_DEVICE_ENDIAN_LITTLE, cl_bool) \\\n    F(cl_device_info, CL_DEVICE_AVAILABLE, cl_bool) \\\n    F(cl_device_info, CL_DEVICE_COMPILER_AVAILABLE, cl_bool) \\\n    F(cl_device_info, CL_DEVICE_EXECUTION_CAPABILITIES, cl_device_exec_capabilities) \\\n    F(cl_device_info, CL_DEVICE_QUEUE_PROPERTIES, cl_command_queue_properties) \\\n    F(cl_device_info, CL_DEVICE_PLATFORM, cl_platform_id) \\\n    F(cl_device_info, CL_DEVICE_NAME, STRING_CLASS) \\\n    F(cl_device_info, CL_DEVICE_VENDOR, STRING_CLASS) \\\n    F(cl_device_info, CL_DRIVER_VERSION, STRING_CLASS) \\\n    F(cl_device_info, CL_DEVICE_PROFILE, STRING_CLASS) \\\n    F(cl_device_info, CL_DEVICE_VERSION, STRING_CLASS) \\\n    F(cl_device_info, CL_DEVICE_EXTENSIONS, STRING_CLASS) \\\n    \\\n    F(cl_context_info, CL_CONTEXT_REFERENCE_COUNT, cl_uint) \\\n    F(cl_context_info, CL_CONTEXT_DEVICES, VECTOR_CLASS<Device>) \\\n    F(cl_context_info, CL_CONTEXT_PROPERTIES, VECTOR_CLASS<cl_context_properties>) \\\n    \\\n    F(cl_event_info, CL_EVENT_COMMAND_QUEUE, cl::CommandQueue) \\\n    F(cl_event_info, CL_EVENT_COMMAND_TYPE, cl_command_type) \\\n    F(cl_event_info, CL_EVENT_REFERENCE_COUNT, cl_uint) \\\n    F(cl_event_info, CL_EVENT_COMMAND_EXECUTION_STATUS, cl_uint) \\\n    \\\n    F(cl_profiling_info, CL_PROFILING_COMMAND_QUEUED, cl_ulong) \\\n    F(cl_profiling_info, CL_PROFILING_COMMAND_SUBMIT, cl_ulong) \\\n    F(cl_profiling_info, CL_PROFILING_COMMAND_START, cl_ulong) \\\n    F(cl_profiling_info, CL_PROFILING_COMMAND_END, cl_ulong) \\\n    \\\n    F(cl_mem_info, CL_MEM_TYPE, cl_mem_object_type) \\\n    F(cl_mem_info, CL_MEM_FLAGS, cl_mem_flags) \\\n    F(cl_mem_info, CL_MEM_SIZE, ::size_t) \\\n    F(cl_mem_info, CL_MEM_HOST_PTR, void*) \\\n    F(cl_mem_info, CL_MEM_MAP_COUNT, cl_uint) \\\n    F(cl_mem_info, CL_MEM_REFERENCE_COUNT, cl_uint) \\\n    F(cl_mem_info, CL_MEM_CONTEXT, cl::Context) \\\n    \\\n    F(cl_image_info, CL_IMAGE_FORMAT, cl_image_format) \\\n    F(cl_image_info, CL_IMAGE_ELEMENT_SIZE, ::size_t) \\\n    F(cl_image_info, CL_IMAGE_ROW_PITCH, ::size_t) \\\n    F(cl_image_info, CL_IMAGE_SLICE_PITCH, ::size_t) \\\n    F(cl_image_info, CL_IMAGE_WIDTH, ::size_t) \\\n    F(cl_image_info, CL_IMAGE_HEIGHT, ::size_t) \\\n    F(cl_image_info, CL_IMAGE_DEPTH, ::size_t) \\\n    \\\n    F(cl_sampler_info, CL_SAMPLER_REFERENCE_COUNT, cl_uint) \\\n    F(cl_sampler_info, CL_SAMPLER_CONTEXT, cl::Context) \\\n    F(cl_sampler_info, CL_SAMPLER_NORMALIZED_COORDS, cl_addressing_mode) \\\n    F(cl_sampler_info, CL_SAMPLER_ADDRESSING_MODE, cl_filter_mode) \\\n    F(cl_sampler_info, CL_SAMPLER_FILTER_MODE, cl_bool) \\\n    \\\n    F(cl_program_info, CL_PROGRAM_REFERENCE_COUNT, cl_uint) \\\n    F(cl_program_info, CL_PROGRAM_CONTEXT, cl::Context) \\\n    F(cl_program_info, CL_PROGRAM_NUM_DEVICES, cl_uint) \\\n    F(cl_program_info, CL_PROGRAM_DEVICES, VECTOR_CLASS<Device>) \\\n    F(cl_program_info, CL_PROGRAM_SOURCE, STRING_CLASS) \\\n    F(cl_program_info, CL_PROGRAM_BINARY_SIZES, VECTOR_CLASS< ::size_t>) \\\n    F(cl_program_info, CL_PROGRAM_BINARIES, VECTOR_CLASS<char *>) \\\n    \\\n    F(cl_program_build_info, CL_PROGRAM_BUILD_STATUS, cl_build_status) \\\n    F(cl_program_build_info, CL_PROGRAM_BUILD_OPTIONS, STRING_CLASS) \\\n    F(cl_program_build_info, CL_PROGRAM_BUILD_LOG, STRING_CLASS) \\\n    \\\n    F(cl_kernel_info, CL_KERNEL_FUNCTION_NAME, STRING_CLASS) \\\n    F(cl_kernel_info, CL_KERNEL_NUM_ARGS, cl_uint) \\\n    F(cl_kernel_info, CL_KERNEL_REFERENCE_COUNT, cl_uint) \\\n    F(cl_kernel_info, CL_KERNEL_CONTEXT, cl::Context) \\\n    F(cl_kernel_info, CL_KERNEL_PROGRAM, cl::Program) \\\n    \\\n    F(cl_kernel_work_group_info, CL_KERNEL_WORK_GROUP_SIZE, ::size_t) \\\n    F(cl_kernel_work_group_info, CL_KERNEL_COMPILE_WORK_GROUP_SIZE, cl::size_t<3>) \\\n    F(cl_kernel_work_group_info, CL_KERNEL_LOCAL_MEM_SIZE, cl_ulong) \\\n    \\\n    F(cl_command_queue_info, CL_QUEUE_CONTEXT, cl::Context) \\\n    F(cl_command_queue_info, CL_QUEUE_DEVICE, cl::Device) \\\n    F(cl_command_queue_info, CL_QUEUE_REFERENCE_COUNT, cl_uint) \\\n    F(cl_command_queue_info, CL_QUEUE_PROPERTIES, cl_command_queue_properties)\n\n#if defined(CL_VERSION_1_1)\n#define __PARAM_NAME_INFO_1_1(F) \\\n    F(cl_context_info, CL_CONTEXT_NUM_DEVICES, cl_uint)\\\n    F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_INT, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_DOUBLE_FP_CONFIG, cl_device_fp_config) \\\n    F(cl_device_info, CL_DEVICE_HALF_FP_CONFIG, cl_device_fp_config) \\\n    F(cl_device_info, CL_DEVICE_HOST_UNIFIED_MEMORY, cl_bool) \\\n    F(cl_device_info, CL_DEVICE_OPENCL_C_VERSION, STRING_CLASS) \\\n    \\\n    F(cl_mem_info, CL_MEM_ASSOCIATED_MEMOBJECT, cl::Memory) \\\n    F(cl_mem_info, CL_MEM_OFFSET, ::size_t) \\\n    \\\n    F(cl_kernel_work_group_info, CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE, ::size_t) \\\n    F(cl_kernel_work_group_info, CL_KERNEL_PRIVATE_MEM_SIZE, cl_ulong) \\\n    \\\n    F(cl_event_info, CL_EVENT_CONTEXT, cl::Context)\n#endif // CL_VERSION_1_1\n\n    \n#if defined(CL_VERSION_1_2)\n#define __PARAM_NAME_INFO_1_2(F) \\\n    F(cl_image_info, CL_IMAGE_BUFFER, cl::Buffer) \\\n    \\\n    F(cl_program_info, CL_PROGRAM_NUM_KERNELS, ::size_t) \\\n    F(cl_program_info, CL_PROGRAM_KERNEL_NAMES, STRING_CLASS) \\\n    \\\n    F(cl_program_build_info, CL_PROGRAM_BINARY_TYPE, cl_program_binary_type) \\\n    \\\n    F(cl_kernel_info, CL_KERNEL_ATTRIBUTES, STRING_CLASS) \\\n    \\\n    F(cl_kernel_arg_info, CL_KERNEL_ARG_ADDRESS_QUALIFIER, cl_kernel_arg_address_qualifier) \\\n    F(cl_kernel_arg_info, CL_KERNEL_ARG_ACCESS_QUALIFIER, cl_kernel_arg_access_qualifier) \\\n    F(cl_kernel_arg_info, CL_KERNEL_ARG_TYPE_NAME, STRING_CLASS) \\\n    F(cl_kernel_arg_info, CL_KERNEL_ARG_NAME, STRING_CLASS) \\\n    \\\n    F(cl_device_info, CL_DEVICE_PARENT_DEVICE, cl_device_id) \\\n    F(cl_device_info, CL_DEVICE_PARTITION_PROPERTIES, VECTOR_CLASS<cl_device_partition_property>) \\\n    F(cl_device_info, CL_DEVICE_PARTITION_TYPE, VECTOR_CLASS<cl_device_partition_property>)  \\\n    F(cl_device_info, CL_DEVICE_REFERENCE_COUNT, cl_uint) \\\n    F(cl_device_info, CL_DEVICE_PREFERRED_INTEROP_USER_SYNC, ::size_t) \\\n    F(cl_device_info, CL_DEVICE_PARTITION_AFFINITY_DOMAIN, cl_device_affinity_domain) \\\n    F(cl_device_info, CL_DEVICE_BUILT_IN_KERNELS, STRING_CLASS)\n#endif // #if defined(CL_VERSION_1_2)\n\n#if defined(USE_CL_DEVICE_FISSION)\n#define __PARAM_NAME_DEVICE_FISSION(F) \\\n    F(cl_device_info, CL_DEVICE_PARENT_DEVICE_EXT, cl_device_id) \\\n    F(cl_device_info, CL_DEVICE_PARTITION_TYPES_EXT, VECTOR_CLASS<cl_device_partition_property_ext>) \\\n    F(cl_device_info, CL_DEVICE_AFFINITY_DOMAINS_EXT, VECTOR_CLASS<cl_device_partition_property_ext>) \\\n    F(cl_device_info, CL_DEVICE_REFERENCE_COUNT_EXT , cl_uint) \\\n    F(cl_device_info, CL_DEVICE_PARTITION_STYLE_EXT, VECTOR_CLASS<cl_device_partition_property_ext>)\n#endif // USE_CL_DEVICE_FISSION\n\ntemplate <typename enum_type, cl_int Name>\nstruct param_traits {};\n\n#define __CL_DECLARE_PARAM_TRAITS(token, param_name, T) \\\nstruct token;                                        \\\ntemplate<>                                           \\\nstruct param_traits<detail:: token,param_name>       \\\n{                                                    \\\n    enum { value = param_name };                     \\\n    typedef T param_type;                            \\\n};\n\n__PARAM_NAME_INFO_1_0(__CL_DECLARE_PARAM_TRAITS)\n#if defined(CL_VERSION_1_1)\n__PARAM_NAME_INFO_1_1(__CL_DECLARE_PARAM_TRAITS)\n#endif // CL_VERSION_1_1\n#if defined(CL_VERSION_1_2)\n__PARAM_NAME_INFO_1_2(__CL_DECLARE_PARAM_TRAITS)\n#endif // CL_VERSION_1_1\n\n#if defined(USE_CL_DEVICE_FISSION)\n__PARAM_NAME_DEVICE_FISSION(__CL_DECLARE_PARAM_TRAITS);\n#endif // USE_CL_DEVICE_FISSION\n\n#ifdef CL_PLATFORM_ICD_SUFFIX_KHR\n__CL_DECLARE_PARAM_TRAITS(cl_platform_info, CL_PLATFORM_ICD_SUFFIX_KHR, STRING_CLASS)\n#endif\n\n#ifdef CL_DEVICE_PROFILING_TIMER_OFFSET_AMD\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_PROFILING_TIMER_OFFSET_AMD, cl_ulong)\n#endif\n\n#ifdef CL_DEVICE_GLOBAL_FREE_MEMORY_AMD\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_GLOBAL_FREE_MEMORY_AMD, VECTOR_CLASS< ::size_t>)\n#endif\n#ifdef CL_DEVICE_SIMD_PER_COMPUTE_UNIT_AMD\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_SIMD_PER_COMPUTE_UNIT_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_SIMD_WIDTH_AMD\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_SIMD_WIDTH_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_SIMD_INSTRUCTION_WIDTH_AMD\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_SIMD_INSTRUCTION_WIDTH_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_WAVEFRONT_WIDTH_AMD\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_WAVEFRONT_WIDTH_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_GLOBAL_MEM_CHANNELS_AMD\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_GLOBAL_MEM_CHANNELS_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_GLOBAL_MEM_CHANNEL_BANKS_AMD\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_GLOBAL_MEM_CHANNEL_BANKS_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_GLOBAL_MEM_CHANNEL_BANK_WIDTH_AMD\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_GLOBAL_MEM_CHANNEL_BANK_WIDTH_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_LOCAL_MEM_SIZE_PER_COMPUTE_UNIT_AMD\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_LOCAL_MEM_SIZE_PER_COMPUTE_UNIT_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_LOCAL_MEM_BANKS_AMD\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_LOCAL_MEM_BANKS_AMD, cl_uint)\n#endif\n\n#ifdef CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV, cl_uint)\n#endif\n#ifdef CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV, cl_uint)\n#endif\n#ifdef CL_DEVICE_REGISTERS_PER_BLOCK_NV\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_REGISTERS_PER_BLOCK_NV, cl_uint)\n#endif\n#ifdef CL_DEVICE_WARP_SIZE_NV\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_WARP_SIZE_NV, cl_uint)\n#endif\n#ifdef CL_DEVICE_GPU_OVERLAP_NV\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_GPU_OVERLAP_NV, cl_bool)\n#endif\n#ifdef CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV, cl_bool)\n#endif\n#ifdef CL_DEVICE_INTEGRATED_MEMORY_NV\n__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_INTEGRATED_MEMORY_NV, cl_bool)\n#endif\n\n// Convenience functions\n\ntemplate <typename Func, typename T>\ninline cl_int\ngetInfo(Func f, cl_uint name, T* param)\n{\n    return getInfoHelper(f, name, param, 0);\n}\n\ntemplate <typename Func, typename Arg0>\nstruct GetInfoFunctor0\n{\n    Func f_; const Arg0& arg0_;\n    cl_int operator ()(\n        cl_uint param, ::size_t size, void* value, ::size_t* size_ret)\n    { return f_(arg0_, param, size, value, size_ret); }\n};\n\ntemplate <typename Func, typename Arg0, typename Arg1>\nstruct GetInfoFunctor1\n{\n    Func f_; const Arg0& arg0_; const Arg1& arg1_;\n    cl_int operator ()(\n        cl_uint param, ::size_t size, void* value, ::size_t* size_ret)\n    { return f_(arg0_, arg1_, param, size, value, size_ret); }\n};\n\ntemplate <typename Func, typename Arg0, typename T>\ninline cl_int\ngetInfo(Func f, const Arg0& arg0, cl_uint name, T* param)\n{\n    GetInfoFunctor0<Func, Arg0> f0 = { f, arg0 };\n    return getInfoHelper(f0, name, param, 0);\n}\n\ntemplate <typename Func, typename Arg0, typename Arg1, typename T>\ninline cl_int\ngetInfo(Func f, const Arg0& arg0, const Arg1& arg1, cl_uint name, T* param)\n{\n    GetInfoFunctor1<Func, Arg0, Arg1> f0 = { f, arg0, arg1 };\n    return getInfoHelper(f0, name, param, 0);\n}\n\ntemplate<typename T>\nstruct ReferenceHandler\n{ };\n\n#if defined(CL_VERSION_1_2)\n/**\n * OpenCL 1.2 devices do have retain/release.\n */\ntemplate <>\nstruct ReferenceHandler<cl_device_id>\n{\n    /**\n     * Retain the device.\n     * \\param device A valid device created using createSubDevices\n     * \\return \n     *   CL_SUCCESS if the function executed successfully.\n     *   CL_INVALID_DEVICE if device was not a valid subdevice\n     *   CL_OUT_OF_RESOURCES\n     *   CL_OUT_OF_HOST_MEMORY\n     */\n    static cl_int retain(cl_device_id device)\n    { return ::clRetainDevice(device); }\n    /**\n     * Retain the device.\n     * \\param device A valid device created using createSubDevices\n     * \\return \n     *   CL_SUCCESS if the function executed successfully.\n     *   CL_INVALID_DEVICE if device was not a valid subdevice\n     *   CL_OUT_OF_RESOURCES\n     *   CL_OUT_OF_HOST_MEMORY\n     */\n    static cl_int release(cl_device_id device)\n    { return ::clReleaseDevice(device); }\n};\n#else // #if defined(CL_VERSION_1_2)\n/**\n * OpenCL 1.1 devices do not have retain/release.\n */\ntemplate <>\nstruct ReferenceHandler<cl_device_id>\n{\n    // cl_device_id does not have retain().\n    static cl_int retain(cl_device_id)\n    { return CL_SUCCESS; }\n    // cl_device_id does not have release().\n    static cl_int release(cl_device_id)\n    { return CL_SUCCESS; }\n};\n#endif // #if defined(CL_VERSION_1_2)\n\ntemplate <>\nstruct ReferenceHandler<cl_platform_id>\n{\n    // cl_platform_id does not have retain().\n    static cl_int retain(cl_platform_id)\n    { return CL_SUCCESS; }\n    // cl_platform_id does not have release().\n    static cl_int release(cl_platform_id)\n    { return CL_SUCCESS; }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_context>\n{\n    static cl_int retain(cl_context context)\n    { return ::clRetainContext(context); }\n    static cl_int release(cl_context context)\n    { return ::clReleaseContext(context); }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_command_queue>\n{\n    static cl_int retain(cl_command_queue queue)\n    { return ::clRetainCommandQueue(queue); }\n    static cl_int release(cl_command_queue queue)\n    { return ::clReleaseCommandQueue(queue); }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_mem>\n{\n    static cl_int retain(cl_mem memory)\n    { return ::clRetainMemObject(memory); }\n    static cl_int release(cl_mem memory)\n    { return ::clReleaseMemObject(memory); }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_sampler>\n{\n    static cl_int retain(cl_sampler sampler)\n    { return ::clRetainSampler(sampler); }\n    static cl_int release(cl_sampler sampler)\n    { return ::clReleaseSampler(sampler); }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_program>\n{\n    static cl_int retain(cl_program program)\n    { return ::clRetainProgram(program); }\n    static cl_int release(cl_program program)\n    { return ::clReleaseProgram(program); }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_kernel>\n{\n    static cl_int retain(cl_kernel kernel)\n    { return ::clRetainKernel(kernel); }\n    static cl_int release(cl_kernel kernel)\n    { return ::clReleaseKernel(kernel); }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_event>\n{\n    static cl_int retain(cl_event event)\n    { return ::clRetainEvent(event); }\n    static cl_int release(cl_event event)\n    { return ::clReleaseEvent(event); }\n};\n\n\n// Extracts version number with major in the upper 16 bits, minor in the lower 16\nstatic cl_uint getVersion(const char *versionInfo)\n{\n    int highVersion = 0;\n    int lowVersion = 0;\n    int index = 7;\n    while(versionInfo[index] != '.' ) {\n        highVersion *= 10;\n        highVersion += versionInfo[index]-'0';\n        ++index;\n    }\n    ++index;\n    while(versionInfo[index] != ' ' ) {\n        lowVersion *= 10;\n        lowVersion += versionInfo[index]-'0';\n        ++index;\n    }\n    return (highVersion << 16) | lowVersion;\n}\n\nstatic cl_uint getPlatformVersion(cl_platform_id platform)\n{\n    ::size_t size = 0;\n    clGetPlatformInfo(platform, CL_PLATFORM_VERSION, 0, NULL, &size);\n    char *versionInfo = (char *) alloca(size);\n    clGetPlatformInfo(platform, CL_PLATFORM_VERSION, size, &versionInfo[0], &size);\n    return getVersion(versionInfo);\n}\n\nstatic cl_uint getDevicePlatformVersion(cl_device_id device)\n{\n    cl_platform_id platform;\n    clGetDeviceInfo(device, CL_DEVICE_PLATFORM, sizeof(platform), &platform, NULL);\n    return getPlatformVersion(platform);\n}\n\n#if defined(CL_VERSION_1_2) && defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\nstatic cl_uint getContextPlatformVersion(cl_context context)\n{\n    // The platform cannot be queried directly, so we first have to grab a\n    // device and obtain its context\n    ::size_t size = 0;\n    clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &size);\n    if (size == 0)\n        return 0;\n    cl_device_id *devices = (cl_device_id *) alloca(size);\n    clGetContextInfo(context, CL_CONTEXT_DEVICES, size, devices, NULL);\n    return getDevicePlatformVersion(devices[0]);\n}\n#endif // #if defined(CL_VERSION_1_2) && defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n\ntemplate <typename T>\nclass Wrapper\n{\npublic:\n    typedef T cl_type;\n\nprotected:\n    cl_type object_;\n\npublic:\n    Wrapper() : object_(NULL) { }\n\n    Wrapper(const cl_type &obj) : object_(obj) { }\n\n    ~Wrapper()\n    {\n        if (object_ != NULL) { release(); }\n    }\n\n    Wrapper(const Wrapper<cl_type>& rhs)\n    {\n        object_ = rhs.object_;\n        if (object_ != NULL) { detail::errHandler(retain(), __RETAIN_ERR); }\n    }\n\n    Wrapper<cl_type>& operator = (const Wrapper<cl_type>& rhs)\n    {\n        if (object_ != NULL) { detail::errHandler(release(), __RELEASE_ERR); }\n        object_ = rhs.object_;\n        if (object_ != NULL) { detail::errHandler(retain(), __RETAIN_ERR); }\n        return *this;\n    }\n\n    Wrapper<cl_type>& operator = (const cl_type &rhs)\n    {\n        if (object_ != NULL) { detail::errHandler(release(), __RELEASE_ERR); }\n        object_ = rhs;\n        return *this;\n    }\n\n    cl_type operator ()() const { return object_; }\n\n    cl_type& operator ()() { return object_; }\n\nprotected:\n    template<typename Func, typename U>\n    friend inline cl_int getInfoHelper(Func, cl_uint, U*, int, typename U::cl_type);\n\n    cl_int retain() const\n    {\n        return ReferenceHandler<cl_type>::retain(object_);\n    }\n\n    cl_int release() const\n    {\n        return ReferenceHandler<cl_type>::release(object_);\n    }\n};\n\ntemplate <>\nclass Wrapper<cl_device_id>\n{\npublic:\n    typedef cl_device_id cl_type;\n\nprotected:\n    cl_type object_;\n    bool referenceCountable_;\n\n    static bool isReferenceCountable(cl_device_id device)\n    {\n        bool retVal = false;\n        if (device != NULL) {\n            int version = getDevicePlatformVersion(device);\n            if(version > ((1 << 16) + 1)) {\n                retVal = true;\n            }\n        }\n        return retVal;\n    }\n\npublic:\n    Wrapper() : object_(NULL), referenceCountable_(false) \n    { \n    }\n    \n    Wrapper(const cl_type &obj) : object_(obj), referenceCountable_(false) \n    {\n        referenceCountable_ = isReferenceCountable(obj); \n    }\n\n    ~Wrapper()\n    {\n        if (object_ != NULL) { release(); }\n    }\n    \n    Wrapper(const Wrapper<cl_type>& rhs)\n    {\n        object_ = rhs.object_;\n        referenceCountable_ = isReferenceCountable(object_); \n        if (object_ != NULL) { detail::errHandler(retain(), __RETAIN_ERR); }\n    }\n\n    Wrapper<cl_type>& operator = (const Wrapper<cl_type>& rhs)\n    {\n        if (object_ != NULL) { detail::errHandler(release(), __RELEASE_ERR); }\n        object_ = rhs.object_;\n        referenceCountable_ = rhs.referenceCountable_;\n        if (object_ != NULL) { detail::errHandler(retain(), __RETAIN_ERR); }\n        return *this;\n    }\n\n    Wrapper<cl_type>& operator = (const cl_type &rhs)\n    {\n        if (object_ != NULL) { detail::errHandler(release(), __RELEASE_ERR); }\n        object_ = rhs;\n        referenceCountable_ = isReferenceCountable(object_); \n        return *this;\n    }\n\n    cl_type operator ()() const { return object_; }\n\n    cl_type& operator ()() { return object_; }\n\nprotected:\n    template<typename Func, typename U>\n    friend inline cl_int getInfoHelper(Func, cl_uint, U*, int, typename U::cl_type);\n\n    template<typename Func, typename U>\n    friend inline cl_int getInfoHelper(Func, cl_uint, VECTOR_CLASS<U>*, int, typename U::cl_type);\n\n    cl_int retain() const\n    {\n        if( referenceCountable_ ) {\n            return ReferenceHandler<cl_type>::retain(object_);\n        }\n        else {\n            return CL_SUCCESS;\n        }\n    }\n\n    cl_int release() const\n    {\n        if( referenceCountable_ ) {\n            return ReferenceHandler<cl_type>::release(object_);\n        }\n        else {\n            return CL_SUCCESS;\n        }\n    }\n};\n\n} // namespace detail\n//! \\endcond\n\n/*! \\stuct ImageFormat\n *  \\brief Adds constructors and member functions for cl_image_format.\n *\n *  \\see cl_image_format\n */\nstruct ImageFormat : public cl_image_format\n{\n    //! \\brief Default constructor - performs no initialization.\n    ImageFormat(){}\n\n    //! \\brief Initializing constructor.\n    ImageFormat(cl_channel_order order, cl_channel_type type)\n    {\n        image_channel_order = order;\n        image_channel_data_type = type;\n    }\n\n    //! \\brief Assignment operator.\n    ImageFormat& operator = (const ImageFormat& rhs)\n    {\n        if (this != &rhs) {\n            this->image_channel_data_type = rhs.image_channel_data_type;\n            this->image_channel_order     = rhs.image_channel_order;\n        }\n        return *this;\n    }\n};\n\n/*! \\brief Class interface for cl_device_id.\n *\n *  \\note Copies of these objects are inexpensive, since they don't 'own'\n *        any underlying resources or data structures.\n *\n *  \\see cl_device_id\n */\nclass Device : public detail::Wrapper<cl_device_id>\n{\npublic:\n    //! \\brief Default constructor - initializes to NULL.\n    Device() : detail::Wrapper<cl_type>() { }\n\n    /*! \\brief Copy constructor.\n     * \n     *  This simply copies the device ID value, which is an inexpensive operation.\n     */\n    Device(const Device& device) : detail::Wrapper<cl_type>(device) { }\n\n    /*! \\brief Constructor from cl_device_id.\n     * \n     *  This simply copies the device ID value, which is an inexpensive operation.\n     */\n    Device(const cl_device_id &device) : detail::Wrapper<cl_type>(device) { }\n\n    /*! \\brief Returns the first device on the default context.\n     *\n     *  \\see Context::getDefault()\n     */\n    static Device getDefault(cl_int * err = NULL);\n\n    /*! \\brief Assignment operator from Device.\n     * \n     *  This simply copies the device ID value, which is an inexpensive operation.\n     */\n    Device& operator = (const Device& rhs)\n    {\n        if (this != &rhs) {\n            detail::Wrapper<cl_type>::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment operator from cl_device_id.\n     * \n     *  This simply copies the device ID value, which is an inexpensive operation.\n     */\n    Device& operator = (const cl_device_id& rhs)\n    {\n        detail::Wrapper<cl_type>::operator=(rhs);\n        return *this;\n    }\n\n    //! \\brief Wrapper for clGetDeviceInfo().\n    template <typename T>\n    cl_int getInfo(cl_device_info name, T* param) const\n    {\n        return detail::errHandler(\n            detail::getInfo(&::clGetDeviceInfo, object_, name, param),\n            __GET_DEVICE_INFO_ERR);\n    }\n\n    //! \\brief Wrapper for clGetDeviceInfo() that returns by value.\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_device_info, name>::param_type\n    getInfo(cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n            detail::cl_device_info, name>::param_type param;\n        cl_int result = getInfo(name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n\n    /**\n     * CL 1.2 version\n     */\n#if defined(CL_VERSION_1_2)\n    //! \\brief Wrapper for clCreateSubDevicesEXT().\n    cl_int createSubDevices(\n        const cl_device_partition_property * properties,\n        VECTOR_CLASS<Device>* devices)\n    {\n        cl_uint n = 0;\n        cl_int err = clCreateSubDevices(object_, properties, 0, NULL, &n);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __CREATE_SUB_DEVICES);\n        }\n\n        cl_device_id* ids = (cl_device_id*) alloca(n * sizeof(cl_device_id));\n        err = clCreateSubDevices(object_, properties, n, ids, NULL);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __CREATE_SUB_DEVICES);\n        }\n\n        devices->assign(&ids[0], &ids[n]);\n        return CL_SUCCESS;\n    }\n#endif // #if defined(CL_VERSION_1_2)\n\n/**\n * CL 1.1 version that uses device fission.\n */\n#if defined(CL_VERSION_1_1)\n#if defined(USE_CL_DEVICE_FISSION)\n    cl_int createSubDevices(\n        const cl_device_partition_property_ext * properties,\n        VECTOR_CLASS<Device>* devices)\n    {\n        typedef CL_API_ENTRY cl_int \n            ( CL_API_CALL * PFN_clCreateSubDevicesEXT)(\n                cl_device_id /*in_device*/,\n                const cl_device_partition_property_ext * /* properties */,\n                cl_uint /*num_entries*/,\n                cl_device_id * /*out_devices*/,\n                cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1;\n\n        static PFN_clCreateSubDevicesEXT pfn_clCreateSubDevicesEXT = NULL;\n        __INIT_CL_EXT_FCN_PTR(clCreateSubDevicesEXT);\n\n        cl_uint n = 0;\n        cl_int err = pfn_clCreateSubDevicesEXT(object_, properties, 0, NULL, &n);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __CREATE_SUB_DEVICES);\n        }\n\n        cl_device_id* ids = (cl_device_id*) alloca(n * sizeof(cl_device_id));\n        err = pfn_clCreateSubDevicesEXT(object_, properties, n, ids, NULL);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __CREATE_SUB_DEVICES);\n        }\n\n        devices->assign(&ids[0], &ids[n]);\n        return CL_SUCCESS;\n    }\n#endif // #if defined(USE_CL_DEVICE_FISSION)\n#endif // #if defined(CL_VERSION_1_1)\n};\n\n/*! \\brief Class interface for cl_platform_id.\n *\n *  \\note Copies of these objects are inexpensive, since they don't 'own'\n *        any underlying resources or data structures.\n *\n *  \\see cl_platform_id\n */\nclass Platform : public detail::Wrapper<cl_platform_id>\n{\npublic:\n    //! \\brief Default constructor - initializes to NULL.\n    Platform() : detail::Wrapper<cl_type>()  { }\n\n    /*! \\brief Copy constructor.\n     * \n     *  This simply copies the platform ID value, which is an inexpensive operation.\n     */\n    Platform(const Platform& platform) : detail::Wrapper<cl_type>(platform) { }\n\n    /*! \\brief Constructor from cl_platform_id.\n     * \n     *  This simply copies the platform ID value, which is an inexpensive operation.\n     */\n    Platform(const cl_platform_id &platform) : detail::Wrapper<cl_type>(platform) { }\n\n    /*! \\brief Assignment operator from Platform.\n     * \n     *  This simply copies the platform ID value, which is an inexpensive operation.\n     */\n    Platform& operator = (const Platform& rhs)\n    {\n        if (this != &rhs) {\n            detail::Wrapper<cl_type>::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment operator from cl_platform_id.\n     * \n     *  This simply copies the platform ID value, which is an inexpensive operation.\n     */\n    Platform& operator = (const cl_platform_id& rhs)\n    {\n        detail::Wrapper<cl_type>::operator=(rhs);\n        return *this;\n    }\n\n    //! \\brief Wrapper for clGetPlatformInfo().\n    cl_int getInfo(cl_platform_info name, STRING_CLASS* param) const\n    {\n        return detail::errHandler(\n            detail::getInfo(&::clGetPlatformInfo, object_, name, param),\n            __GET_PLATFORM_INFO_ERR);\n    }\n\n    //! \\brief Wrapper for clGetPlatformInfo() that returns by value.\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_platform_info, name>::param_type\n    getInfo(cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n            detail::cl_platform_info, name>::param_type param;\n        cl_int result = getInfo(name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n\n    /*! \\brief Gets a list of devices for this platform.\n     * \n     *  Wraps clGetDeviceIDs().\n     */\n    cl_int getDevices(\n        cl_device_type type,\n        VECTOR_CLASS<Device>* devices) const\n    {\n        cl_uint n = 0;\n        if( devices == NULL ) {\n            return detail::errHandler(CL_INVALID_ARG_VALUE, __GET_DEVICE_IDS_ERR);\n        }\n        cl_int err = ::clGetDeviceIDs(object_, type, 0, NULL, &n);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __GET_DEVICE_IDS_ERR);\n        }\n\n        cl_device_id* ids = (cl_device_id*) alloca(n * sizeof(cl_device_id));\n        err = ::clGetDeviceIDs(object_, type, n, ids, NULL);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __GET_DEVICE_IDS_ERR);\n        }\n\n        devices->assign(&ids[0], &ids[n]);\n        return CL_SUCCESS;\n    }\n\n#if defined(USE_DX_INTEROP)\n   /*! \\brief Get the list of available D3D10 devices.\n     *\n     *  \\param d3d_device_source.\n     *\n     *  \\param d3d_object.\n     *\n     *  \\param d3d_device_set.\n     *\n     *  \\param devices returns a vector of OpenCL D3D10 devices found. The cl::Device\n     *  values returned in devices can be used to identify a specific OpenCL\n     *  device. If \\a devices argument is NULL, this argument is ignored.\n     *\n     *  \\return One of the following values:\n     *    - CL_SUCCESS if the function is executed successfully.\n     *\n     *  The application can query specific capabilities of the OpenCL device(s)\n     *  returned by cl::getDevices. This can be used by the application to\n     *  determine which device(s) to use.\n     *\n     * \\note In the case that exceptions are enabled and a return value\n     * other than CL_SUCCESS is generated, then cl::Error exception is\n     * generated.\n     */\n    cl_int getDevices(\n        cl_d3d10_device_source_khr d3d_device_source,\n        void *                     d3d_object,\n        cl_d3d10_device_set_khr    d3d_device_set,\n        VECTOR_CLASS<Device>* devices) const\n    {\n        typedef CL_API_ENTRY cl_int (CL_API_CALL *PFN_clGetDeviceIDsFromD3D10KHR)(\n            cl_platform_id platform, \n            cl_d3d10_device_source_khr d3d_device_source, \n            void * d3d_object,\n            cl_d3d10_device_set_khr d3d_device_set,\n            cl_uint num_entries,\n            cl_device_id * devices,\n            cl_uint* num_devices);\n\n        if( devices == NULL ) {\n            return detail::errHandler(CL_INVALID_ARG_VALUE, __GET_DEVICE_IDS_ERR);\n        }\n\n        static PFN_clGetDeviceIDsFromD3D10KHR pfn_clGetDeviceIDsFromD3D10KHR = NULL;\n        __INIT_CL_EXT_FCN_PTR_PLATFORM(object_, clGetDeviceIDsFromD3D10KHR);\n\n        cl_uint n = 0;\n        cl_int err = pfn_clGetDeviceIDsFromD3D10KHR(\n            object_, \n            d3d_device_source, \n            d3d_object,\n            d3d_device_set, \n            0, \n            NULL, \n            &n);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __GET_DEVICE_IDS_ERR);\n        }\n\n        cl_device_id* ids = (cl_device_id*) alloca(n * sizeof(cl_device_id));\n        err = pfn_clGetDeviceIDsFromD3D10KHR(\n            object_, \n            d3d_device_source, \n            d3d_object,\n            d3d_device_set,\n            n, \n            ids, \n            NULL);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __GET_DEVICE_IDS_ERR);\n        }\n\n        devices->assign(&ids[0], &ids[n]);\n        return CL_SUCCESS;\n    }\n#endif\n\n    /*! \\brief Gets a list of available platforms.\n     * \n     *  Wraps clGetPlatformIDs().\n     */\n    static cl_int get(\n        VECTOR_CLASS<Platform>* platforms)\n    {\n        cl_uint n = 0;\n\n        if( platforms == NULL ) {\n            return detail::errHandler(CL_INVALID_ARG_VALUE, __GET_PLATFORM_IDS_ERR);\n        }\n\n        cl_int err = ::clGetPlatformIDs(0, NULL, &n);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __GET_PLATFORM_IDS_ERR);\n        }\n\n        cl_platform_id* ids = (cl_platform_id*) alloca(\n            n * sizeof(cl_platform_id));\n        err = ::clGetPlatformIDs(n, ids, NULL);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __GET_PLATFORM_IDS_ERR);\n        }\n\n        platforms->assign(&ids[0], &ids[n]);\n        return CL_SUCCESS;\n    }\n\n    /*! \\brief Gets the first available platform.\n     * \n     *  Wraps clGetPlatformIDs(), returning the first result.\n     */\n    static cl_int get(\n        Platform * platform)\n    {\n        cl_uint n = 0;\n\n        if( platform == NULL ) {\n            return detail::errHandler(CL_INVALID_ARG_VALUE, __GET_PLATFORM_IDS_ERR);\n        }\n\n        cl_int err = ::clGetPlatformIDs(0, NULL, &n);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __GET_PLATFORM_IDS_ERR);\n        }\n\n        cl_platform_id* ids = (cl_platform_id*) alloca(\n            n * sizeof(cl_platform_id));\n        err = ::clGetPlatformIDs(n, ids, NULL);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __GET_PLATFORM_IDS_ERR);\n        }\n\n        *platform = ids[0];\n        return CL_SUCCESS;\n    }\n\n    /*! \\brief Gets the first available platform, returning it by value.\n     * \n     *  Wraps clGetPlatformIDs(), returning the first result.\n     */\n    static Platform get(\n        cl_int * errResult = NULL)\n    {\n        Platform platform;\n        cl_uint n = 0;\n        cl_int err = ::clGetPlatformIDs(0, NULL, &n);\n        if (err != CL_SUCCESS) {\n            detail::errHandler(err, __GET_PLATFORM_IDS_ERR);\n            if (errResult != NULL) {\n                *errResult = err;\n            }\n        }\n\n        cl_platform_id* ids = (cl_platform_id*) alloca(\n            n * sizeof(cl_platform_id));\n        err = ::clGetPlatformIDs(n, ids, NULL);\n\n        if (err != CL_SUCCESS) {\n            detail::errHandler(err, __GET_PLATFORM_IDS_ERR);\n        }\n\n        if (errResult != NULL) {\n            *errResult = err;\n        }\n        \n        return ids[0];\n    }\n\n    static Platform getDefault( \n        cl_int *errResult = NULL )\n    {\n        return get(errResult);\n    }\n\n    \n#if defined(CL_VERSION_1_2)\n    //! \\brief Wrapper for clUnloadCompiler().\n    cl_int\n    unloadCompiler()\n    {\n        return ::clUnloadPlatformCompiler(object_);\n    }\n#endif // #if defined(CL_VERSION_1_2)\n}; // class Platform\n\n/**\n * Deprecated APIs for 1.2\n */\n#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) || (defined(CL_VERSION_1_1) && !defined(CL_VERSION_1_2))\n/**\n * Unload the OpenCL compiler.\n * \\note Deprecated for OpenCL 1.2. Use Platform::unloadCompiler instead.\n */\ninline CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int\nUnloadCompiler() CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED;\ninline cl_int\nUnloadCompiler()\n{\n    return ::clUnloadCompiler();\n}\n#endif // #if defined(CL_VERSION_1_1)\n\n/*! \\brief Class interface for cl_context.\n *\n *  \\note Copies of these objects are shallow, meaning that the copy will refer\n *        to the same underlying cl_context as the original.  For details, see\n *        clRetainContext() and clReleaseContext().\n *\n *  \\see cl_context\n */\nclass Context \n    : public detail::Wrapper<cl_context>\n{\nprivate:\n    static volatile int default_initialized_;\n    static Context default_;\n    static volatile cl_int default_error_;\npublic:\n    /*! \\brief Destructor.\n     *\n     *  This calls clReleaseContext() on the value held by this instance.\n     */\n    ~Context() { }\n\n    /*! \\brief Constructs a context including a list of specified devices.\n     *\n     *  Wraps clCreateContext().\n     */\n    Context(\n        const VECTOR_CLASS<Device>& devices,\n        cl_context_properties* properties = NULL,\n        void (CL_CALLBACK * notifyFptr)(\n            const char *,\n            const void *,\n            ::size_t,\n            void *) = NULL,\n        void* data = NULL,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n\n        ::size_t numDevices = devices.size();\n        cl_device_id* deviceIDs = (cl_device_id*) alloca(numDevices * sizeof(cl_device_id));\n        for( ::size_t deviceIndex = 0; deviceIndex < numDevices; ++deviceIndex ) {\n            deviceIDs[deviceIndex] = (devices[deviceIndex])();\n        }\n\n        object_ = ::clCreateContext(\n            properties, (cl_uint) numDevices,\n            deviceIDs,\n            notifyFptr, data, &error);\n\n        detail::errHandler(error, __CREATE_CONTEXT_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    Context(\n        const Device& device,\n        cl_context_properties* properties = NULL,\n        void (CL_CALLBACK * notifyFptr)(\n            const char *,\n            const void *,\n            ::size_t,\n            void *) = NULL,\n        void* data = NULL,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n\n        cl_device_id deviceID = device();\n\n        object_ = ::clCreateContext(\n            properties, 1,\n            &deviceID,\n            notifyFptr, data, &error);\n\n        detail::errHandler(error, __CREATE_CONTEXT_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    /*! \\brief Constructs a context including all or a subset of devices of a specified type.\n     *\n     *  Wraps clCreateContextFromType().\n     */\n    Context(\n        cl_device_type type,\n        cl_context_properties* properties = NULL,\n        void (CL_CALLBACK * notifyFptr)(\n            const char *,\n            const void *,\n            ::size_t,\n            void *) = NULL,\n        void* data = NULL,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n\n#if !defined(__APPLE__) || !defined(__MACOS)\n        cl_context_properties prop[4] = {CL_CONTEXT_PLATFORM, 0, 0, 0 };\n\n        if (properties == NULL) {\n            // Get a valid platform ID as we cannot send in a blank one\n            VECTOR_CLASS<Platform> platforms;\n            error = Platform::get(&platforms);\n            if (error != CL_SUCCESS) {\n                detail::errHandler(error, __CREATE_CONTEXT_FROM_TYPE_ERR);\n                if (err != NULL) {\n                    *err = error;\n                }\n                return;\n            }\n\n            // Check the platforms we found for a device of our specified type\n            cl_context_properties platform_id = 0;\n            for (unsigned int i = 0; i < platforms.size(); i++) {\n\n                VECTOR_CLASS<Device> devices;\n\n#if defined(__CL_ENABLE_EXCEPTIONS)\n                try {\n#endif\n\n                    error = platforms[i].getDevices(type, &devices);\n\n#if defined(__CL_ENABLE_EXCEPTIONS)\n                } catch (Error) {}\n    // Catch if exceptions are enabled as we don't want to exit if first platform has no devices of type\n    // We do error checking next anyway, and can throw there if needed\n#endif\n\n                // Only squash CL_SUCCESS and CL_DEVICE_NOT_FOUND\n                if (error != CL_SUCCESS && error != CL_DEVICE_NOT_FOUND) {\n                    detail::errHandler(error, __CREATE_CONTEXT_FROM_TYPE_ERR);\n                    if (err != NULL) {\n                        *err = error;\n                    }\n                }\n\n                if (devices.size() > 0) {\n                    platform_id = (cl_context_properties)platforms[i]();\n                    break;\n                }\n            }\n\n            if (platform_id == 0) {\n                detail::errHandler(CL_DEVICE_NOT_FOUND, __CREATE_CONTEXT_FROM_TYPE_ERR);\n                if (err != NULL) {\n                    *err = CL_DEVICE_NOT_FOUND;\n                }\n                return;\n            }\n\n            prop[1] = platform_id;\n            properties = &prop[0];\n        }\n#endif\n        object_ = ::clCreateContextFromType(\n            properties, type, notifyFptr, data, &error);\n\n        detail::errHandler(error, __CREATE_CONTEXT_FROM_TYPE_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    /*! \\brief Returns a singleton context including all devices of CL_DEVICE_TYPE_DEFAULT.\n     *\n     *  \\note All calls to this function return the same cl_context as the first.\n     */\n    static Context getDefault(cl_int * err = NULL) \n    {\n        int state = detail::compare_exchange(\n            &default_initialized_, \n            __DEFAULT_BEING_INITIALIZED, __DEFAULT_NOT_INITIALIZED);\n        \n        if (state & __DEFAULT_INITIALIZED) {\n            if (err != NULL) {\n                *err = default_error_;\n            }\n            return default_;\n        }\n\n        if (state & __DEFAULT_BEING_INITIALIZED) {\n              // Assume writes will propagate eventually...\n              while(default_initialized_ != __DEFAULT_INITIALIZED) {\n                  detail::fence();\n              }\n\n            if (err != NULL) {\n                *err = default_error_;\n            }\n            return default_;\n        }\n\n        cl_int error;\n        default_ = Context(\n            CL_DEVICE_TYPE_DEFAULT,\n            NULL,\n            NULL,\n            NULL,\n            &error);\n\n        detail::fence();\n\n        default_error_ = error;\n        // Assume writes will propagate eventually...\n        default_initialized_ = __DEFAULT_INITIALIZED;\n\n        detail::fence();\n\n        if (err != NULL) {\n            *err = default_error_;\n        }\n        return default_;\n\n    }\n\n    //! \\brief Default constructor - initializes to NULL.\n    Context() : detail::Wrapper<cl_type>() { }\n\n    /*! \\brief Copy constructor.\n     * \n     *  This calls clRetainContext() on the parameter's cl_context.\n     */\n    Context(const Context& context) : detail::Wrapper<cl_type>(context) { }\n\n    /*! \\brief Constructor from cl_context - takes ownership.\n     * \n     *  This effectively transfers ownership of a refcount on the cl_context\n     *  into the new Context object.\n     */\n    __CL_EXPLICIT_CONSTRUCTORS Context(const cl_context& context) : detail::Wrapper<cl_type>(context) { }\n\n    /*! \\brief Assignment operator from Context.\n     * \n     *  This calls clRetainContext() on the parameter and clReleaseContext() on\n     *  the previous value held by this instance.\n     */\n    Context& operator = (const Context& rhs)\n    {\n        if (this != &rhs) {\n            detail::Wrapper<cl_type>::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment operator from cl_context - takes ownership.\n     * \n     *  This effectively transfers ownership of a refcount on the rhs and calls\n     *  clReleaseContext() on the value previously held by this instance.\n     */\n    Context& operator = (const cl_context& rhs)\n    {\n        detail::Wrapper<cl_type>::operator=(rhs);\n        return *this;\n    }\n\n    //! \\brief Wrapper for clGetContextInfo().\n    template <typename T>\n    cl_int getInfo(cl_context_info name, T* param) const\n    {\n        return detail::errHandler(\n            detail::getInfo(&::clGetContextInfo, object_, name, param),\n            __GET_CONTEXT_INFO_ERR);\n    }\n\n    //! \\brief Wrapper for clGetContextInfo() that returns by value.\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_context_info, name>::param_type\n    getInfo(cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n            detail::cl_context_info, name>::param_type param;\n        cl_int result = getInfo(name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n\n    /*! \\brief Gets a list of supported image formats.\n     *  \n     *  Wraps clGetSupportedImageFormats().\n     */\n    cl_int getSupportedImageFormats(\n        cl_mem_flags flags,\n        cl_mem_object_type type,\n        VECTOR_CLASS<ImageFormat>* formats) const\n    {\n        cl_uint numEntries;\n        cl_int err = ::clGetSupportedImageFormats(\n           object_, \n           flags,\n           type, \n           0, \n           NULL, \n           &numEntries);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __GET_SUPPORTED_IMAGE_FORMATS_ERR);\n        }\n\n        ImageFormat* value = (ImageFormat*)\n            alloca(numEntries * sizeof(ImageFormat));\n        err = ::clGetSupportedImageFormats(\n            object_, \n            flags, \n            type, \n            numEntries,\n            (cl_image_format*) value, \n            NULL);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __GET_SUPPORTED_IMAGE_FORMATS_ERR);\n        }\n\n        formats->assign(&value[0], &value[numEntries]);\n        return CL_SUCCESS;\n    }\n};\n\ninline Device Device::getDefault(cl_int * err)\n{\n    cl_int error;\n    Device device;\n\n    Context context = Context::getDefault(&error);\n    detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n\n    if (error != CL_SUCCESS) {\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n    else {\n        device = context.getInfo<CL_CONTEXT_DEVICES>()[0];\n        if (err != NULL) {\n            *err = CL_SUCCESS;\n        }\n    }\n\n    return device;\n}\n\n\n#ifdef _WIN32\n__declspec(selectany) volatile int Context::default_initialized_ = __DEFAULT_NOT_INITIALIZED;\n__declspec(selectany) Context Context::default_;\n__declspec(selectany) volatile cl_int Context::default_error_ = CL_SUCCESS;\n#else\n__attribute__((weak)) volatile int Context::default_initialized_ = __DEFAULT_NOT_INITIALIZED;\n__attribute__((weak)) Context Context::default_;\n__attribute__((weak)) volatile cl_int Context::default_error_ = CL_SUCCESS;\n#endif\n\n/*! \\brief Class interface for cl_event.\n *\n *  \\note Copies of these objects are shallow, meaning that the copy will refer\n *        to the same underlying cl_event as the original.  For details, see\n *        clRetainEvent() and clReleaseEvent().\n *\n *  \\see cl_event\n */\nclass Event : public detail::Wrapper<cl_event>\n{\npublic:\n    /*! \\brief Destructor.\n     *\n     *  This calls clReleaseEvent() on the value held by this instance.\n     */\n    ~Event() { }\n \n    //! \\brief Default constructor - initializes to NULL.\n    Event() : detail::Wrapper<cl_type>() { }\n\n    /*! \\brief Copy constructor.\n     * \n     *  This calls clRetainEvent() on the parameter's cl_event.\n     */\n    Event(const Event& event) : detail::Wrapper<cl_type>(event) { }\n\n    /*! \\brief Constructor from cl_event - takes ownership.\n     * \n     *  This effectively transfers ownership of a refcount on the cl_event\n     *  into the new Event object.\n     */\n    Event(const cl_event& event) : detail::Wrapper<cl_type>(event) { }\n\n    /*! \\brief Assignment operator from cl_event - takes ownership.\n     *\n     *  This effectively transfers ownership of a refcount on the rhs and calls\n     *  clReleaseEvent() on the value previously held by this instance.\n     */\n    Event& operator = (const Event& rhs)\n    {\n        if (this != &rhs) {\n            detail::Wrapper<cl_type>::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment operator from cl_event.\n     * \n     *  This calls clRetainEvent() on the parameter and clReleaseEvent() on\n     *  the previous value held by this instance.\n     */\n    Event& operator = (const cl_event& rhs)\n    {\n        detail::Wrapper<cl_type>::operator=(rhs);\n        return *this;\n    }\n\n    //! \\brief Wrapper for clGetEventInfo().\n    template <typename T>\n    cl_int getInfo(cl_event_info name, T* param) const\n    {\n        return detail::errHandler(\n            detail::getInfo(&::clGetEventInfo, object_, name, param),\n            __GET_EVENT_INFO_ERR);\n    }\n\n    //! \\brief Wrapper for clGetEventInfo() that returns by value.\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_event_info, name>::param_type\n    getInfo(cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n            detail::cl_event_info, name>::param_type param;\n        cl_int result = getInfo(name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n\n    //! \\brief Wrapper for clGetEventProfilingInfo().\n    template <typename T>\n    cl_int getProfilingInfo(cl_profiling_info name, T* param) const\n    {\n        return detail::errHandler(detail::getInfo(\n            &::clGetEventProfilingInfo, object_, name, param),\n            __GET_EVENT_PROFILE_INFO_ERR);\n    }\n\n    //! \\brief Wrapper for clGetEventProfilingInfo() that returns by value.\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_profiling_info, name>::param_type\n    getProfilingInfo(cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n            detail::cl_profiling_info, name>::param_type param;\n        cl_int result = getProfilingInfo(name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n\n    /*! \\brief Blocks the calling thread until this event completes.\n     * \n     *  Wraps clWaitForEvents().\n     */\n    cl_int wait() const\n    {\n        return detail::errHandler(\n            ::clWaitForEvents(1, &object_),\n            __WAIT_FOR_EVENTS_ERR);\n    }\n\n#if defined(CL_VERSION_1_1)\n    /*! \\brief Registers a user callback function for a specific command execution status.\n     *\n     *  Wraps clSetEventCallback().\n     */\n    cl_int setCallback(\n        cl_int type,\n        void (CL_CALLBACK * pfn_notify)(cl_event, cl_int, void *),\t\t\n        void * user_data = NULL)\n    {\n        return detail::errHandler(\n            ::clSetEventCallback(\n                object_,\n                type,\n                pfn_notify,\n                user_data), \n            __SET_EVENT_CALLBACK_ERR);\n    }\n#endif\n\n    /*! \\brief Blocks the calling thread until every event specified is complete.\n     * \n     *  Wraps clWaitForEvents().\n     */\n    static cl_int\n    waitForEvents(const VECTOR_CLASS<Event>& events)\n    {\n        return detail::errHandler(\n            ::clWaitForEvents(\n                (cl_uint) events.size(), (cl_event*)&events.front()),\n            __WAIT_FOR_EVENTS_ERR);\n    }\n};\n\n#if defined(CL_VERSION_1_1)\n/*! \\brief Class interface for user events (a subset of cl_event's).\n * \n *  See Event for details about copy semantics, etc.\n */\nclass UserEvent : public Event\n{\npublic:\n    /*! \\brief Constructs a user event on a given context.\n     *\n     *  Wraps clCreateUserEvent().\n     */\n    UserEvent(\n        const Context& context,\n        cl_int * err = NULL)\n    {\n        cl_int error;\n        object_ = ::clCreateUserEvent(\n            context(),\n            &error);\n\n        detail::errHandler(error, __CREATE_USER_EVENT_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    //! \\brief Default constructor - initializes to NULL.\n    UserEvent() : Event() { }\n\n    //! \\brief Copy constructor - performs shallow copy.\n    UserEvent(const UserEvent& event) : Event(event) { }\n\n    //! \\brief Assignment Operator - performs shallow copy.\n    UserEvent& operator = (const UserEvent& rhs)\n    {\n        if (this != &rhs) {\n            Event::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Sets the execution status of a user event object.\n     *\n     *  Wraps clSetUserEventStatus().\n     */\n    cl_int setStatus(cl_int status)\n    {\n        return detail::errHandler(\n            ::clSetUserEventStatus(object_,status), \n            __SET_USER_EVENT_STATUS_ERR);\n    }\n};\n#endif\n\n/*! \\brief Blocks the calling thread until every event specified is complete.\n * \n *  Wraps clWaitForEvents().\n */\ninline static cl_int\nWaitForEvents(const VECTOR_CLASS<Event>& events)\n{\n    return detail::errHandler(\n        ::clWaitForEvents(\n            (cl_uint) events.size(), (cl_event*)&events.front()),\n        __WAIT_FOR_EVENTS_ERR);\n}\n\n/*! \\brief Class interface for cl_mem.\n *\n *  \\note Copies of these objects are shallow, meaning that the copy will refer\n *        to the same underlying cl_mem as the original.  For details, see\n *        clRetainMemObject() and clReleaseMemObject().\n *\n *  \\see cl_mem\n */\nclass Memory : public detail::Wrapper<cl_mem>\n{\npublic:\n \n    /*! \\brief Destructor.\n     *\n     *  This calls clReleaseMemObject() on the value held by this instance.\n     */\n    ~Memory() {}\n\n    //! \\brief Default constructor - initializes to NULL.\n    Memory() : detail::Wrapper<cl_type>() { }\n\n    /*! \\brief Copy constructor - performs shallow copy.\n     * \n     *  This calls clRetainMemObject() on the parameter's cl_mem.\n     */\n    Memory(const Memory& memory) : detail::Wrapper<cl_type>(memory) { }\n\n    /*! \\brief Constructor from cl_mem - takes ownership.\n     * \n     *  This effectively transfers ownership of a refcount on the cl_mem\n     *  into the new Memory object.\n     */\n    __CL_EXPLICIT_CONSTRUCTORS Memory(const cl_mem& memory) : detail::Wrapper<cl_type>(memory) { }\n\n    /*! \\brief Assignment operator from Memory.\n     * \n     *  This calls clRetainMemObject() on the parameter and clReleaseMemObject()\n     *  on the previous value held by this instance.\n     */\n    Memory& operator = (const Memory& rhs)\n    {\n        if (this != &rhs) {\n            detail::Wrapper<cl_type>::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment operator from cl_mem - takes ownership.\n     *\n     *  This effectively transfers ownership of a refcount on the rhs and calls\n     *  clReleaseMemObject() on the value previously held by this instance.\n     */\n    Memory& operator = (const cl_mem& rhs)\n    {\n        detail::Wrapper<cl_type>::operator=(rhs);\n        return *this;\n    }\n\n    //! \\brief Wrapper for clGetMemObjectInfo().\n    template <typename T>\n    cl_int getInfo(cl_mem_info name, T* param) const\n    {\n        return detail::errHandler(\n            detail::getInfo(&::clGetMemObjectInfo, object_, name, param),\n            __GET_MEM_OBJECT_INFO_ERR);\n    }\n\n    //! \\brief Wrapper for clGetMemObjectInfo() that returns by value.\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_mem_info, name>::param_type\n    getInfo(cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n            detail::cl_mem_info, name>::param_type param;\n        cl_int result = getInfo(name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n\n#if defined(CL_VERSION_1_1)\n    /*! \\brief Registers a callback function to be called when the memory object\n     *         is no longer needed.\n     *\n     *  Wraps clSetMemObjectDestructorCallback().\n     *\n     *  Repeated calls to this function, for a given cl_mem value, will append\n     *  to the list of functions called (in reverse order) when memory object's\n     *  resources are freed and the memory object is deleted.\n     *\n     *  \\note\n     *  The registered callbacks are associated with the underlying cl_mem\n     *  value - not the Memory class instance.\n     */\n    cl_int setDestructorCallback(\n        void (CL_CALLBACK * pfn_notify)(cl_mem, void *),\t\t\n        void * user_data = NULL)\n    {\n        return detail::errHandler(\n            ::clSetMemObjectDestructorCallback(\n                object_,\n                pfn_notify,\n                user_data), \n            __SET_MEM_OBJECT_DESTRUCTOR_CALLBACK_ERR);\n    }\n#endif\n\n};\n\n// Pre-declare copy functions\nclass Buffer;\ntemplate< typename IteratorType >\ncl_int copy( IteratorType startIterator, IteratorType endIterator, cl::Buffer &buffer );\ntemplate< typename IteratorType >\ncl_int copy( const cl::Buffer &buffer, IteratorType startIterator, IteratorType endIterator );\ntemplate< typename IteratorType >\ncl_int copy( const CommandQueue &queue, IteratorType startIterator, IteratorType endIterator, cl::Buffer &buffer );\ntemplate< typename IteratorType >\ncl_int copy( const CommandQueue &queue, const cl::Buffer &buffer, IteratorType startIterator, IteratorType endIterator );\n\n\n/*! \\brief Class interface for Buffer Memory Objects.\n * \n *  See Memory for details about copy semantics, etc.\n *\n *  \\see Memory\n */\nclass Buffer : public Memory\n{\npublic:\n\n    /*! \\brief Constructs a Buffer in a specified context.\n     *\n     *  Wraps clCreateBuffer().\n     *\n     *  \\param host_ptr Storage to be used if the CL_MEM_USE_HOST_PTR flag was\n     *                  specified.  Note alignment & exclusivity requirements.\n     */\n    Buffer(\n        const Context& context,\n        cl_mem_flags flags,\n        ::size_t size,\n        void* host_ptr = NULL,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n        object_ = ::clCreateBuffer(context(), flags, size, host_ptr, &error);\n\n        detail::errHandler(error, __CREATE_BUFFER_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    /*! \\brief Constructs a Buffer in the default context.\n     *\n     *  Wraps clCreateBuffer().\n     *\n     *  \\param host_ptr Storage to be used if the CL_MEM_USE_HOST_PTR flag was\n     *                  specified.  Note alignment & exclusivity requirements.\n     *\n     *  \\see Context::getDefault()\n     */\n    Buffer(\n         cl_mem_flags flags,\n        ::size_t size,\n        void* host_ptr = NULL,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n\n        Context context = Context::getDefault(err);\n\n        object_ = ::clCreateBuffer(context(), flags, size, host_ptr, &error);\n\n        detail::errHandler(error, __CREATE_BUFFER_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    /*!\n     * \\brief Construct a Buffer from a host container via iterators.\n     * IteratorType must be random access.\n     * If useHostPtr is specified iterators must represent contiguous data.\n     */\n    template< typename IteratorType >\n    Buffer(\n        IteratorType startIterator,\n        IteratorType endIterator,\n        bool readOnly,\n        bool useHostPtr = false,\n        cl_int* err = NULL)\n    {\n        typedef typename std::iterator_traits<IteratorType>::value_type DataType;\n        cl_int error;\n\n        cl_mem_flags flags = 0;\n        if( readOnly ) {\n            flags |= CL_MEM_READ_ONLY;\n        }\n        else {\n            flags |= CL_MEM_READ_WRITE;\n        }\n        if( useHostPtr ) {\n            flags |= CL_MEM_USE_HOST_PTR;\n        }\n        \n        ::size_t size = sizeof(DataType)*(endIterator - startIterator);\n\n        Context context = Context::getDefault(err);\n\n        if( useHostPtr ) {\n            object_ = ::clCreateBuffer(context(), flags, size, static_cast<DataType*>(&*startIterator), &error);\n        } else {\n            object_ = ::clCreateBuffer(context(), flags, size, 0, &error);\n        }\n\n        detail::errHandler(error, __CREATE_BUFFER_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n\n        if( !useHostPtr ) {\n            error = cl::copy(startIterator, endIterator, *this);\n            detail::errHandler(error, __CREATE_BUFFER_ERR);\n            if (err != NULL) {\n                *err = error;\n            }\n        }\n    }\n\n    /*!\n     * \\brief Construct a Buffer from a host container via iterators using a specified context.\n     * IteratorType must be random access.\n     * If useHostPtr is specified iterators must represent contiguous data.\n     */\n    template< typename IteratorType >\n    Buffer(const Context &context, IteratorType startIterator, IteratorType endIterator,\n        bool readOnly, bool useHostPtr = false, cl_int* err = NULL);\n\n    //! \\brief Default constructor - initializes to NULL.\n    Buffer() : Memory() { }\n\n    /*! \\brief Copy constructor - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Buffer(const Buffer& buffer) : Memory(buffer) { }\n\n    /*! \\brief Constructor from cl_mem - takes ownership.\n     *\n     *  See Memory for further details.\n     */\n    __CL_EXPLICIT_CONSTRUCTORS Buffer(const cl_mem& buffer) : Memory(buffer) { }\n\n    /*! \\brief Assignment from Buffer - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Buffer& operator = (const Buffer& rhs)\n    {\n        if (this != &rhs) {\n            Memory::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment from cl_mem - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Buffer& operator = (const cl_mem& rhs)\n    {\n        Memory::operator=(rhs);\n        return *this;\n    }\n\n#if defined(CL_VERSION_1_1)\n    /*! \\brief Creates a new buffer object from this.\n     *\n     *  Wraps clCreateSubBuffer().\n     */\n    Buffer createSubBuffer(\n        cl_mem_flags flags,\n        cl_buffer_create_type buffer_create_type,\n        const void * buffer_create_info,\n        cl_int * err = NULL)\n    {\n        Buffer result;\n        cl_int error;\n        result.object_ = ::clCreateSubBuffer(\n            object_, \n            flags, \n            buffer_create_type, \n            buffer_create_info, \n            &error);\n\n        detail::errHandler(error, __CREATE_SUBBUFFER_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n\n        return result;\n    }\t\t\n#endif\n};\n\n#if defined (USE_DX_INTEROP)\n/*! \\brief Class interface for creating OpenCL buffers from ID3D10Buffer's.\n *\n *  This is provided to facilitate interoperability with Direct3D.\n * \n *  See Memory for details about copy semantics, etc.\n *\n *  \\see Memory\n */\nclass BufferD3D10 : public Buffer\n{\npublic:\n    typedef CL_API_ENTRY cl_mem (CL_API_CALL *PFN_clCreateFromD3D10BufferKHR)(\n    cl_context context, cl_mem_flags flags, ID3D10Buffer*  buffer,\n    cl_int* errcode_ret);\n\n    /*! \\brief Constructs a BufferD3D10, in a specified context, from a\n     *         given ID3D10Buffer.\n     *\n     *  Wraps clCreateFromD3D10BufferKHR().\n     */\n    BufferD3D10(\n        const Context& context,\n        cl_mem_flags flags,\n        ID3D10Buffer* bufobj,\n        cl_int * err = NULL)\n    {\n        static PFN_clCreateFromD3D10BufferKHR pfn_clCreateFromD3D10BufferKHR = NULL;\n\n#if defined(CL_VERSION_1_2)\n        vector<cl_context_properties> props = context.getInfo<CL_CONTEXT_PROPERTIES>();\n        cl_platform platform = -1;\n        for( int i = 0; i < props.size(); ++i ) {\n            if( props[i] == CL_CONTEXT_PLATFORM ) {\n                platform = props[i+1];\n            }\n        }\n        __INIT_CL_EXT_FCN_PTR_PLATFORM(platform, clCreateFromD3D10BufferKHR);\n#endif\n#if defined(CL_VERSION_1_1)\n        __INIT_CL_EXT_FCN_PTR(clCreateFromD3D10BufferKHR);\n#endif\n\n        cl_int error;\n        object_ = pfn_clCreateFromD3D10BufferKHR(\n            context(),\n            flags,\n            bufobj,\n            &error);\n\n        detail::errHandler(error, __CREATE_GL_BUFFER_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    //! \\brief Default constructor - initializes to NULL.\n    BufferD3D10() : Buffer() { }\n\n    /*! \\brief Copy constructor - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    BufferD3D10(const BufferD3D10& buffer) : Buffer(buffer) { }\n\n    /*! \\brief Constructor from cl_mem - takes ownership.\n     *\n     *  See Memory for further details.\n     */\n    __CL_EXPLICIT_CONSTRUCTORS BufferD3D10(const cl_mem& buffer) : Buffer(buffer) { }\n\n    /*! \\brief Assignment from BufferD3D10 - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    BufferD3D10& operator = (const BufferD3D10& rhs)\n    {\n        if (this != &rhs) {\n            Buffer::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment from cl_mem - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    BufferD3D10& operator = (const cl_mem& rhs)\n    {\n        Buffer::operator=(rhs);\n        return *this;\n    }\n};\n#endif\n\n/*! \\brief Class interface for GL Buffer Memory Objects.\n *\n *  This is provided to facilitate interoperability with OpenGL.\n * \n *  See Memory for details about copy semantics, etc.\n * \n *  \\see Memory\n */\nclass BufferGL : public Buffer\n{\npublic:\n    /*! \\brief Constructs a BufferGL in a specified context, from a given\n     *         GL buffer.\n     *\n     *  Wraps clCreateFromGLBuffer().\n     */\n    BufferGL(\n        const Context& context,\n        cl_mem_flags flags,\n        GLuint bufobj,\n        cl_int * err = NULL)\n    {\n        cl_int error;\n        object_ = ::clCreateFromGLBuffer(\n            context(),\n            flags,\n            bufobj,\n            &error);\n\n        detail::errHandler(error, __CREATE_GL_BUFFER_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    //! \\brief Default constructor - initializes to NULL.\n    BufferGL() : Buffer() { }\n\n    /*! \\brief Copy constructor - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    BufferGL(const BufferGL& buffer) : Buffer(buffer) { }\n\n    /*! \\brief Constructor from cl_mem - takes ownership.\n     *\n     *  See Memory for further details.\n     */\n    __CL_EXPLICIT_CONSTRUCTORS BufferGL(const cl_mem& buffer) : Buffer(buffer) { }\n\n    /*! \\brief Assignment from BufferGL - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    BufferGL& operator = (const BufferGL& rhs)\n    {\n        if (this != &rhs) {\n            Buffer::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment from cl_mem - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    BufferGL& operator = (const cl_mem& rhs)\n    {\n        Buffer::operator=(rhs);\n        return *this;\n    }\n\n    //! \\brief Wrapper for clGetGLObjectInfo().\n    cl_int getObjectInfo(\n        cl_gl_object_type *type,\n        GLuint * gl_object_name)\n    {\n        return detail::errHandler(\n            ::clGetGLObjectInfo(object_,type,gl_object_name),\n            __GET_GL_OBJECT_INFO_ERR);\n    }\n};\n\n/*! \\brief Class interface for GL Render Buffer Memory Objects.\n *\n *  This is provided to facilitate interoperability with OpenGL.\n * \n *  See Memory for details about copy semantics, etc.\n * \n *  \\see Memory\n */\nclass BufferRenderGL : public Buffer\n{\npublic:\n    /*! \\brief Constructs a BufferRenderGL in a specified context, from a given\n     *         GL Renderbuffer.\n     *\n     *  Wraps clCreateFromGLRenderbuffer().\n     */\n    BufferRenderGL(\n        const Context& context,\n        cl_mem_flags flags,\n        GLuint bufobj,\n        cl_int * err = NULL)\n    {\n        cl_int error;\n        object_ = ::clCreateFromGLRenderbuffer(\n            context(),\n            flags,\n            bufobj,\n            &error);\n\n        detail::errHandler(error, __CREATE_GL_RENDER_BUFFER_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    //! \\brief Default constructor - initializes to NULL.\n    BufferRenderGL() : Buffer() { }\n\n    /*! \\brief Copy constructor - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    BufferRenderGL(const BufferGL& buffer) : Buffer(buffer) { }\n\n    /*! \\brief Constructor from cl_mem - takes ownership.\n     *\n     *  See Memory for further details.\n     */\n    __CL_EXPLICIT_CONSTRUCTORS BufferRenderGL(const cl_mem& buffer) : Buffer(buffer) { }\n\n    /*! \\brief Assignment from BufferGL - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    BufferRenderGL& operator = (const BufferRenderGL& rhs)\n    {\n        if (this != &rhs) {\n            Buffer::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment from cl_mem - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    BufferRenderGL& operator = (const cl_mem& rhs)\n    {\n        Buffer::operator=(rhs);\n        return *this;\n    }\n\n    //! \\brief Wrapper for clGetGLObjectInfo().\n    cl_int getObjectInfo(\n        cl_gl_object_type *type,\n        GLuint * gl_object_name)\n    {\n        return detail::errHandler(\n            ::clGetGLObjectInfo(object_,type,gl_object_name),\n            __GET_GL_OBJECT_INFO_ERR);\n    }\n};\n\n/*! \\brief C++ base class for Image Memory objects.\n *\n *  See Memory for details about copy semantics, etc.\n * \n *  \\see Memory\n */\nclass Image : public Memory\n{\nprotected:\n    //! \\brief Default constructor - initializes to NULL.\n    Image() : Memory() { }\n\n    /*! \\brief Copy constructor - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image(const Image& image) : Memory(image) { }\n\n    /*! \\brief Constructor from cl_mem - takes ownership.\n     *\n     *  See Memory for further details.\n     */\n    __CL_EXPLICIT_CONSTRUCTORS Image(const cl_mem& image) : Memory(image) { }\n\n    /*! \\brief Assignment from Image - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image& operator = (const Image& rhs)\n    {\n        if (this != &rhs) {\n            Memory::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment from cl_mem - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image& operator = (const cl_mem& rhs)\n    {\n        Memory::operator=(rhs);\n        return *this;\n    }\n\npublic:\n    //! \\brief Wrapper for clGetImageInfo().\n    template <typename T>\n    cl_int getImageInfo(cl_image_info name, T* param) const\n    {\n        return detail::errHandler(\n            detail::getInfo(&::clGetImageInfo, object_, name, param),\n            __GET_IMAGE_INFO_ERR);\n    }\n    \n    //! \\brief Wrapper for clGetImageInfo() that returns by value.\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_image_info, name>::param_type\n    getImageInfo(cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n            detail::cl_image_info, name>::param_type param;\n        cl_int result = getImageInfo(name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n};\n\n#if defined(CL_VERSION_1_2)\n/*! \\brief Class interface for 1D Image Memory objects.\n *\n *  See Memory for details about copy semantics, etc.\n * \n *  \\see Memory\n */\nclass Image1D : public Image\n{\npublic:\n    /*! \\brief Constructs a 1D Image in a specified context.\n     *\n     *  Wraps clCreateImage().\n     */\n    Image1D(\n        const Context& context,\n        cl_mem_flags flags,\n        ImageFormat format,\n        ::size_t width,\n        void* host_ptr = NULL,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n        cl_image_desc desc =\n        {\n            CL_MEM_OBJECT_IMAGE1D,\n            width,\n            0, 0, 0, 0, 0, 0, 0, 0\n        };\n        object_ = ::clCreateImage(\n            context(), \n            flags, \n            &format, \n            &desc, \n            host_ptr, \n            &error);\n\n        detail::errHandler(error, __CREATE_IMAGE_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    //! \\brief Default constructor - initializes to NULL.\n    Image1D() { }\n\n    /*! \\brief Copy constructor - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image1D(const Image1D& image1D) : Image(image1D) { }\n\n    /*! \\brief Constructor from cl_mem - takes ownership.\n     *\n     *  See Memory for further details.\n     */\n    __CL_EXPLICIT_CONSTRUCTORS Image1D(const cl_mem& image1D) : Image(image1D) { }\n\n    /*! \\brief Assignment from Image1D - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image1D& operator = (const Image1D& rhs)\n    {\n        if (this != &rhs) {\n            Image::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment from cl_mem - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image1D& operator = (const cl_mem& rhs)\n    {\n        Image::operator=(rhs);\n        return *this;\n    }\n};\n\n/*! \\class Image1DBuffer\n * \\brief Image interface for 1D buffer images.\n */\nclass Image1DBuffer : public Image\n{\npublic:\n    Image1DBuffer(\n        const Context& context,\n        cl_mem_flags flags,\n        ImageFormat format,\n        ::size_t width,\n        const Buffer &buffer,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n        cl_image_desc desc =\n        {\n            CL_MEM_OBJECT_IMAGE1D_BUFFER,\n            width,\n            0, 0, 0, 0, 0, 0, 0,\n            buffer()\n        };\n        object_ = ::clCreateImage(\n            context(), \n            flags, \n            &format, \n            &desc, \n            NULL, \n            &error);\n\n        detail::errHandler(error, __CREATE_IMAGE_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    Image1DBuffer() { }\n\n    Image1DBuffer(const Image1DBuffer& image1D) : Image(image1D) { }\n\n    __CL_EXPLICIT_CONSTRUCTORS Image1DBuffer(const cl_mem& image1D) : Image(image1D) { }\n\n    Image1DBuffer& operator = (const Image1DBuffer& rhs)\n    {\n        if (this != &rhs) {\n            Image::operator=(rhs);\n        }\n        return *this;\n    }\n\n    Image1DBuffer& operator = (const cl_mem& rhs)\n    {\n        Image::operator=(rhs);\n        return *this;\n    }\n};\n\n/*! \\class Image1DArray\n * \\brief Image interface for arrays of 1D images.\n */\nclass Image1DArray : public Image\n{\npublic:\n    Image1DArray(\n        const Context& context,\n        cl_mem_flags flags,\n        ImageFormat format,\n        ::size_t arraySize,\n        ::size_t width,\n        ::size_t rowPitch,\n        void* host_ptr = NULL,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n        cl_image_desc desc =\n        {\n            CL_MEM_OBJECT_IMAGE1D_ARRAY,\n            width,\n            0, 0,  // height, depth (unused)\n            arraySize,\n            rowPitch,\n            0, 0, 0, 0\n        };\n        object_ = ::clCreateImage(\n            context(), \n            flags, \n            &format, \n            &desc, \n            host_ptr, \n            &error);\n\n        detail::errHandler(error, __CREATE_IMAGE_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    Image1DArray() { }\n\n    Image1DArray(const Image1DArray& imageArray) : Image(imageArray) { }\n\n    __CL_EXPLICIT_CONSTRUCTORS Image1DArray(const cl_mem& imageArray) : Image(imageArray) { }\n\n    Image1DArray& operator = (const Image1DArray& rhs)\n    {\n        if (this != &rhs) {\n            Image::operator=(rhs);\n        }\n        return *this;\n    }\n\n    Image1DArray& operator = (const cl_mem& rhs)\n    {\n        Image::operator=(rhs);\n        return *this;\n    }\n};\n#endif // #if defined(CL_VERSION_1_2)\n\n\n/*! \\brief Class interface for 2D Image Memory objects.\n *\n *  See Memory for details about copy semantics, etc.\n * \n *  \\see Memory\n */\nclass Image2D : public Image\n{\npublic:\n    /*! \\brief Constructs a 1D Image in a specified context.\n     *\n     *  Wraps clCreateImage().\n     */\n    Image2D(\n        const Context& context,\n        cl_mem_flags flags,\n        ImageFormat format,\n        ::size_t width,\n        ::size_t height,\n        ::size_t row_pitch = 0,\n        void* host_ptr = NULL,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n        bool useCreateImage;\n\n#if defined(CL_VERSION_1_2) && defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n        // Run-time decision based on the actual platform\n        {\n            cl_uint version = detail::getContextPlatformVersion(context());\n            useCreateImage = (version >= 0x10002); // OpenCL 1.2 or above\n        }\n#elif defined(CL_VERSION_1_2)\n        useCreateImage = true;\n#else\n        useCreateImage = false;\n#endif\n\n#if defined(CL_VERSION_1_2)\n        if (useCreateImage)\n        {\n            cl_image_desc desc =\n            {\n                CL_MEM_OBJECT_IMAGE2D,\n                width,\n                height,\n                0, 0, // depth, array size (unused)\n                row_pitch,\n                0, 0, 0, 0\n            };\n            object_ = ::clCreateImage(\n                context(),\n                flags,\n                &format,\n                &desc,\n                host_ptr,\n                &error);\n\n            detail::errHandler(error, __CREATE_IMAGE_ERR);\n            if (err != NULL) {\n                *err = error;\n            }\n        }\n#endif // #if defined(CL_VERSION_1_2)\n#if !defined(CL_VERSION_1_2) || defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n        if (!useCreateImage)\n        {\n            object_ = ::clCreateImage2D(\n                context(), flags,&format, width, height, row_pitch, host_ptr, &error);\n\n            detail::errHandler(error, __CREATE_IMAGE2D_ERR);\n            if (err != NULL) {\n                *err = error;\n            }\n        }\n#endif // #if !defined(CL_VERSION_1_2) || defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n    }\n\n    //! \\brief Default constructor - initializes to NULL.\n    Image2D() { }\n\n    /*! \\brief Copy constructor - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image2D(const Image2D& image2D) : Image(image2D) { }\n\n    /*! \\brief Constructor from cl_mem - takes ownership.\n     *\n     *  See Memory for further details.\n     */\n    __CL_EXPLICIT_CONSTRUCTORS Image2D(const cl_mem& image2D) : Image(image2D) { }\n\n    /*! \\brief Assignment from Image2D - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image2D& operator = (const Image2D& rhs)\n    {\n        if (this != &rhs) {\n            Image::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment from cl_mem - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image2D& operator = (const cl_mem& rhs)\n    {\n        Image::operator=(rhs);\n        return *this;\n    }\n};\n\n\n#if !defined(CL_VERSION_1_2)\n/*! \\brief Class interface for GL 2D Image Memory objects.\n *\n *  This is provided to facilitate interoperability with OpenGL.\n * \n *  See Memory for details about copy semantics, etc.\n * \n *  \\see Memory\n *  \\note Deprecated for OpenCL 1.2. Please use ImageGL instead.\n */\nclass CL_EXT_PREFIX__VERSION_1_1_DEPRECATED Image2DGL CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED : public Image2D\n{\npublic:\n    /*! \\brief Constructs an Image2DGL in a specified context, from a given\n     *         GL Texture.\n     *\n     *  Wraps clCreateFromGLTexture2D().\n     */\n    Image2DGL(\n        const Context& context,\n        cl_mem_flags flags,\n        GLenum target,\n        GLint  miplevel,\n        GLuint texobj,\n        cl_int * err = NULL)\n    {\n        cl_int error;\n        object_ = ::clCreateFromGLTexture2D(\n            context(),\n            flags,\n            target,\n            miplevel,\n            texobj,\n            &error);\n\n        detail::errHandler(error, __CREATE_GL_TEXTURE_2D_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n\n    }\n    \n    //! \\brief Default constructor - initializes to NULL.\n    Image2DGL() : Image2D() { }\n\n    /*! \\brief Copy constructor - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image2DGL(const Image2DGL& image) : Image2D(image) { }\n\n    /*! \\brief Constructor from cl_mem - takes ownership.\n     *\n     *  See Memory for further details.\n     */\n    __CL_EXPLICIT_CONSTRUCTORS Image2DGL(const cl_mem& image) : Image2D(image) { }\n\n    /*! \\brief Assignment from Image2DGL - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image2DGL& operator = (const Image2DGL& rhs)\n    {\n        if (this != &rhs) {\n            Image2D::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment from cl_mem - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image2DGL& operator = (const cl_mem& rhs)\n    {\n        Image2D::operator=(rhs);\n        return *this;\n    }\n};\n#endif // #if !defined(CL_VERSION_1_2)\n\n#if defined(CL_VERSION_1_2)\n/*! \\class Image2DArray\n * \\brief Image interface for arrays of 2D images.\n */\nclass Image2DArray : public Image\n{\npublic:\n    Image2DArray(\n        const Context& context,\n        cl_mem_flags flags,\n        ImageFormat format,\n        ::size_t arraySize,\n        ::size_t width,\n        ::size_t height,\n        ::size_t rowPitch,\n        ::size_t slicePitch,\n        void* host_ptr = NULL,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n        cl_image_desc desc =\n        {\n            CL_MEM_OBJECT_IMAGE2D_ARRAY,\n            width,\n            height,\n            0,       // depth (unused)\n            arraySize,\n            rowPitch,\n            slicePitch,\n            0, 0, 0\n        };\n        object_ = ::clCreateImage(\n            context(), \n            flags, \n            &format, \n            &desc, \n            host_ptr, \n            &error);\n\n        detail::errHandler(error, __CREATE_IMAGE_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    Image2DArray() { }\n\n    Image2DArray(const Image2DArray& imageArray) : Image(imageArray) { }\n\n    __CL_EXPLICIT_CONSTRUCTORS Image2DArray(const cl_mem& imageArray) : Image(imageArray) { }\n\n    Image2DArray& operator = (const Image2DArray& rhs)\n    {\n        if (this != &rhs) {\n            Image::operator=(rhs);\n        }\n        return *this;\n    }\n\n    Image2DArray& operator = (const cl_mem& rhs)\n    {\n        Image::operator=(rhs);\n        return *this;\n    }\n};\n#endif // #if defined(CL_VERSION_1_2)\n\n/*! \\brief Class interface for 3D Image Memory objects.\n *\n *  See Memory for details about copy semantics, etc.\n * \n *  \\see Memory\n */\nclass Image3D : public Image\n{\npublic:\n    /*! \\brief Constructs a 3D Image in a specified context.\n     *\n     *  Wraps clCreateImage().\n     */\n    Image3D(\n        const Context& context,\n        cl_mem_flags flags,\n        ImageFormat format,\n        ::size_t width,\n        ::size_t height,\n        ::size_t depth,\n        ::size_t row_pitch = 0,\n        ::size_t slice_pitch = 0,\n        void* host_ptr = NULL,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n        bool useCreateImage;\n\n#if defined(CL_VERSION_1_2) && defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n        // Run-time decision based on the actual platform\n        {\n            cl_uint version = detail::getContextPlatformVersion(context());\n            useCreateImage = (version >= 0x10002); // OpenCL 1.2 or above\n        }\n#elif defined(CL_VERSION_1_2)\n        useCreateImage = true;\n#else\n        useCreateImage = false;\n#endif\n\n#if defined(CL_VERSION_1_2)\n        if (useCreateImage)\n        {\n            cl_image_desc desc =\n            {\n                CL_MEM_OBJECT_IMAGE3D,\n                width,\n                height,\n                depth,\n                0,      // array size (unused)\n                row_pitch,\n                slice_pitch,\n                0, 0, 0\n            };\n            object_ = ::clCreateImage(\n                context(), \n                flags, \n                &format, \n                &desc, \n                host_ptr, \n                &error);\n\n            detail::errHandler(error, __CREATE_IMAGE_ERR);\n            if (err != NULL) {\n                *err = error;\n            }\n        }\n#endif  // #if defined(CL_VERSION_1_2)\n#if !defined(CL_VERSION_1_2) || defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n        if (!useCreateImage)\n        {\n            object_ = ::clCreateImage3D(\n                context(), flags, &format, width, height, depth, row_pitch,\n                slice_pitch, host_ptr, &error);\n\n            detail::errHandler(error, __CREATE_IMAGE3D_ERR);\n            if (err != NULL) {\n                *err = error;\n            }\n        }\n#endif // #if !defined(CL_VERSION_1_2) || defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n    }\n\n    //! \\brief Default constructor - initializes to NULL.\n    Image3D() { }\n\n    /*! \\brief Copy constructor - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image3D(const Image3D& image3D) : Image(image3D) { }\n\n    /*! \\brief Constructor from cl_mem - takes ownership.\n     *\n     *  See Memory for further details.\n     */\n    __CL_EXPLICIT_CONSTRUCTORS Image3D(const cl_mem& image3D) : Image(image3D) { }\n\n    /*! \\brief Assignment from Image3D - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image3D& operator = (const Image3D& rhs)\n    {\n        if (this != &rhs) {\n            Image::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment from cl_mem - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image3D& operator = (const cl_mem& rhs)\n    {\n        Image::operator=(rhs);\n        return *this;\n    }\n};\n\n#if !defined(CL_VERSION_1_2)\n/*! \\brief Class interface for GL 3D Image Memory objects.\n *\n *  This is provided to facilitate interoperability with OpenGL.\n * \n *  See Memory for details about copy semantics, etc.\n * \n *  \\see Memory\n */\nclass Image3DGL : public Image3D\n{\npublic:\n    /*! \\brief Constructs an Image3DGL in a specified context, from a given\n     *         GL Texture.\n     *\n     *  Wraps clCreateFromGLTexture3D().\n     */\n    Image3DGL(\n        const Context& context,\n        cl_mem_flags flags,\n        GLenum target,\n        GLint  miplevel,\n        GLuint texobj,\n        cl_int * err = NULL)\n    {\n        cl_int error;\n        object_ = ::clCreateFromGLTexture3D(\n            context(),\n            flags,\n            target,\n            miplevel,\n            texobj,\n            &error);\n\n        detail::errHandler(error, __CREATE_GL_TEXTURE_3D_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    //! \\brief Default constructor - initializes to NULL.\n    Image3DGL() : Image3D() { }\n\n    /*! \\brief Copy constructor - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image3DGL(const Image3DGL& image) : Image3D(image) { }\n\n    /*! \\brief Constructor from cl_mem - takes ownership.\n     *\n     *  See Memory for further details.\n     */\n    __CL_EXPLICIT_CONSTRUCTORS Image3DGL(const cl_mem& image) : Image3D(image) { }\n\n    /*! \\brief Assignment from Image3DGL - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image3DGL& operator = (const Image3DGL& rhs)\n    {\n        if (this != &rhs) {\n            Image3D::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment from cl_mem - performs shallow copy.\n     *\n     *  See Memory for further details.\n     */\n    Image3DGL& operator = (const cl_mem& rhs)\n    {\n        Image3D::operator=(rhs);\n        return *this;\n    }\n};\n#endif // #if !defined(CL_VERSION_1_2)\n\n#if defined(CL_VERSION_1_2)\n/*! \\class ImageGL\n * \\brief general image interface for GL interop.\n * We abstract the 2D and 3D GL images into a single instance here\n * that wraps all GL sourced images on the grounds that setup information\n * was performed by OpenCL anyway.\n */\nclass ImageGL : public Image\n{\npublic:\n    ImageGL(\n        const Context& context,\n        cl_mem_flags flags,\n        GLenum target,\n        GLint  miplevel,\n        GLuint texobj,\n        cl_int * err = NULL)\n    {\n        cl_int error;\n        object_ = ::clCreateFromGLTexture(\n            context(), \n            flags, \n            target,\n            miplevel,\n            texobj,\n            &error);\n\n        detail::errHandler(error, __CREATE_GL_TEXTURE_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    ImageGL() : Image() { }\n\n    ImageGL(const ImageGL& image) : Image(image) { }\n\n    __CL_EXPLICIT_CONSTRUCTORS ImageGL(const cl_mem& image) : Image(image) { }\n\n    ImageGL& operator = (const ImageGL& rhs)\n    {\n        if (this != &rhs) {\n            Image::operator=(rhs);\n        }\n        return *this;\n    }\n\n    ImageGL& operator = (const cl_mem& rhs)\n    {\n        Image::operator=(rhs);\n        return *this;\n    }\n};\n#endif // #if defined(CL_VERSION_1_2)\n\n/*! \\brief Class interface for cl_sampler.\n *\n *  \\note Copies of these objects are shallow, meaning that the copy will refer\n *        to the same underlying cl_sampler as the original.  For details, see\n *        clRetainSampler() and clReleaseSampler().\n *\n *  \\see cl_sampler \n */\nclass Sampler : public detail::Wrapper<cl_sampler>\n{\npublic:\n    /*! \\brief Destructor.\n     *\n     *  This calls clReleaseSampler() on the value held by this instance.\n     */\n    ~Sampler() { }\n\n    //! \\brief Default constructor - initializes to NULL.\n    Sampler() { }\n\n    /*! \\brief Constructs a Sampler in a specified context.\n     *\n     *  Wraps clCreateSampler().\n     */\n    Sampler(\n        const Context& context,\n        cl_bool normalized_coords,\n        cl_addressing_mode addressing_mode,\n        cl_filter_mode filter_mode,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n        object_ = ::clCreateSampler(\n            context(), \n            normalized_coords,\n            addressing_mode,\n            filter_mode,\n            &error);\n\n        detail::errHandler(error, __CREATE_SAMPLER_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    /*! \\brief Copy constructor - performs shallow copy.\n     * \n     *  This calls clRetainSampler() on the parameter's cl_sampler.\n     */\n    Sampler(const Sampler& sampler) : detail::Wrapper<cl_type>(sampler) { }\n\n    /*! \\brief Constructor from cl_sampler - takes ownership.\n     * \n     *  This effectively transfers ownership of a refcount on the cl_sampler\n     *  into the new Sampler object.\n     */\n    Sampler(const cl_sampler& sampler) : detail::Wrapper<cl_type>(sampler) { }\n\n    /*! \\brief Assignment operator from Sampler.\n     * \n     *  This calls clRetainSampler() on the parameter and clReleaseSampler()\n     *  on the previous value held by this instance.\n     */\n    Sampler& operator = (const Sampler& rhs)\n    {\n        if (this != &rhs) {\n            detail::Wrapper<cl_type>::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment operator from cl_sampler - takes ownership.\n     *\n     *  This effectively transfers ownership of a refcount on the rhs and calls\n     *  clReleaseSampler() on the value previously held by this instance.\n     */\n    Sampler& operator = (const cl_sampler& rhs)\n    {\n        detail::Wrapper<cl_type>::operator=(rhs);\n        return *this;\n    }\n\n    //! \\brief Wrapper for clGetSamplerInfo().\n    template <typename T>\n    cl_int getInfo(cl_sampler_info name, T* param) const\n    {\n        return detail::errHandler(\n            detail::getInfo(&::clGetSamplerInfo, object_, name, param),\n            __GET_SAMPLER_INFO_ERR);\n    }\n\n    //! \\brief Wrapper for clGetSamplerInfo() that returns by value.\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_sampler_info, name>::param_type\n    getInfo(cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n            detail::cl_sampler_info, name>::param_type param;\n        cl_int result = getInfo(name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n};\n\nclass Program;\nclass CommandQueue;\nclass Kernel;\n\n//! \\brief Class interface for specifying NDRange values.\nclass NDRange\n{\nprivate:\n    size_t<3> sizes_;\n    cl_uint dimensions_;\n\npublic:\n    //! \\brief Default constructor - resulting range has zero dimensions.\n    NDRange()\n        : dimensions_(0)\n    { }\n\n    //! \\brief Constructs one-dimensional range.\n    NDRange(::size_t size0)\n        : dimensions_(1)\n    {\n        sizes_[0] = size0;\n    }\n\n    //! \\brief Constructs two-dimensional range.\n    NDRange(::size_t size0, ::size_t size1)\n        : dimensions_(2)\n    {\n        sizes_[0] = size0;\n        sizes_[1] = size1;\n    }\n\n    //! \\brief Constructs three-dimensional range.\n    NDRange(::size_t size0, ::size_t size1, ::size_t size2)\n        : dimensions_(3)\n    {\n        sizes_[0] = size0;\n        sizes_[1] = size1;\n        sizes_[2] = size2;\n    }\n\n    /*! \\brief Conversion operator to const ::size_t *.\n     *  \n     *  \\returns a pointer to the size of the first dimension.\n     */\n    operator const ::size_t*() const { \n        return (const ::size_t*) sizes_; \n    }\n\n    //! \\brief Queries the number of dimensions in the range.\n    ::size_t dimensions() const { return dimensions_; }\n};\n\n//! \\brief A zero-dimensional range.\nstatic const NDRange NullRange;\n\n//! \\brief Local address wrapper for use with Kernel::setArg\nstruct LocalSpaceArg\n{\n    ::size_t size_;\n};\n\nnamespace detail {\n\ntemplate <typename T>\nstruct KernelArgumentHandler\n{\n    static ::size_t size(const T&) { return sizeof(T); }\n    static T* ptr(T& value) { return &value; }\n};\n\ntemplate <>\nstruct KernelArgumentHandler<LocalSpaceArg>\n{\n    static ::size_t size(const LocalSpaceArg& value) { return value.size_; }\n    static void* ptr(LocalSpaceArg&) { return NULL; }\n};\n\n} \n//! \\endcond\n\n/*! __local\n * \\brief Helper function for generating LocalSpaceArg objects.\n * Deprecated. Replaced with Local.\n */\ninline CL_EXT_PREFIX__VERSION_1_1_DEPRECATED LocalSpaceArg\n__local(::size_t size) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED;\ninline LocalSpaceArg\n__local(::size_t size)\n{\n    LocalSpaceArg ret = { size };\n    return ret;\n}\n\n/*! Local\n * \\brief Helper function for generating LocalSpaceArg objects.\n */\ninline LocalSpaceArg\nLocal(::size_t size)\n{\n    LocalSpaceArg ret = { size };\n    return ret;\n}\n\n//class KernelFunctor;\n\n/*! \\brief Class interface for cl_kernel.\n *\n *  \\note Copies of these objects are shallow, meaning that the copy will refer\n *        to the same underlying cl_kernel as the original.  For details, see\n *        clRetainKernel() and clReleaseKernel().\n *\n *  \\see cl_kernel\n */\nclass Kernel : public detail::Wrapper<cl_kernel>\n{\npublic:\n    inline Kernel(const Program& program, const char* name, cl_int* err = NULL);\n\n    /*! \\brief Destructor.\n     *\n     *  This calls clReleaseKernel() on the value held by this instance.\n     */\n    ~Kernel() { }\n\n    //! \\brief Default constructor - initializes to NULL.\n    Kernel() { }\n\n    /*! \\brief Copy constructor - performs shallow copy.\n     * \n     *  This calls clRetainKernel() on the parameter's cl_kernel.\n     */\n    Kernel(const Kernel& kernel) : detail::Wrapper<cl_type>(kernel) { }\n\n    /*! \\brief Constructor from cl_kernel - takes ownership.\n     * \n     *  This effectively transfers ownership of a refcount on the cl_kernel\n     *  into the new Kernel object.\n     */\n    __CL_EXPLICIT_CONSTRUCTORS Kernel(const cl_kernel& kernel) : detail::Wrapper<cl_type>(kernel) { }\n\n    /*! \\brief Assignment operator from Kernel.\n     * \n     *  This calls clRetainKernel() on the parameter and clReleaseKernel()\n     *  on the previous value held by this instance.\n     */\n    Kernel& operator = (const Kernel& rhs)\n    {\n        if (this != &rhs) {\n            detail::Wrapper<cl_type>::operator=(rhs);\n        }\n        return *this;\n    }\n\n    /*! \\brief Assignment operator from cl_kernel - takes ownership.\n     *\n     *  This effectively transfers ownership of a refcount on the rhs and calls\n     *  clReleaseKernel() on the value previously held by this instance.\n     */\n    Kernel& operator = (const cl_kernel& rhs)\n    {\n        detail::Wrapper<cl_type>::operator=(rhs);\n        return *this;\n    }\n\n    template <typename T>\n    cl_int getInfo(cl_kernel_info name, T* param) const\n    {\n        return detail::errHandler(\n            detail::getInfo(&::clGetKernelInfo, object_, name, param),\n            __GET_KERNEL_INFO_ERR);\n    }\n\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_kernel_info, name>::param_type\n    getInfo(cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n            detail::cl_kernel_info, name>::param_type param;\n        cl_int result = getInfo(name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n\n#if defined(CL_VERSION_1_2)\n    template <typename T>\n    cl_int getArgInfo(cl_uint argIndex, cl_kernel_arg_info name, T* param) const\n    {\n        return detail::errHandler(\n            detail::getInfo(&::clGetKernelArgInfo, object_, argIndex, name, param),\n            __GET_KERNEL_ARG_INFO_ERR);\n    }\n\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_kernel_arg_info, name>::param_type\n    getArgInfo(cl_uint argIndex, cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n            detail::cl_kernel_arg_info, name>::param_type param;\n        cl_int result = getArgInfo(argIndex, name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n#endif // #if defined(CL_VERSION_1_2)\n\n    template <typename T>\n    cl_int getWorkGroupInfo(\n        const Device& device, cl_kernel_work_group_info name, T* param) const\n    {\n        return detail::errHandler(\n            detail::getInfo(\n                &::clGetKernelWorkGroupInfo, object_, device(), name, param),\n                __GET_KERNEL_WORK_GROUP_INFO_ERR);\n    }\n\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_kernel_work_group_info, name>::param_type\n        getWorkGroupInfo(const Device& device, cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n        detail::cl_kernel_work_group_info, name>::param_type param;\n        cl_int result = getWorkGroupInfo(device, name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n\n    template <typename T>\n    cl_int setArg(cl_uint index, T value)\n    {\n        return detail::errHandler(\n            ::clSetKernelArg(\n                object_,\n                index,\n                detail::KernelArgumentHandler<T>::size(value),\n                detail::KernelArgumentHandler<T>::ptr(value)),\n            __SET_KERNEL_ARGS_ERR);\n    }\n\n    cl_int setArg(cl_uint index, ::size_t size, void* argPtr)\n    {\n        return detail::errHandler(\n            ::clSetKernelArg(object_, index, size, argPtr),\n            __SET_KERNEL_ARGS_ERR);\n    }\n};\n\n/*! \\class Program\n * \\brief Program interface that implements cl_program.\n */\nclass Program : public detail::Wrapper<cl_program>\n{\npublic:\n    typedef VECTOR_CLASS<std::pair<const void*, ::size_t> > Binaries;\n    typedef VECTOR_CLASS<std::pair<const char*, ::size_t> > Sources;\n\n    Program(\n        const STRING_CLASS& source,\n\t\tbool build = false,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n\n        const char * strings = source.c_str();\n        const ::size_t length  = source.size();\n\n        Context context = Context::getDefault(err);\n\n        object_ = ::clCreateProgramWithSource(\n            context(), (cl_uint)1, &strings, &length, &error);\n\n        detail::errHandler(error, __CREATE_PROGRAM_WITH_SOURCE_ERR);\n\n        if (error == CL_SUCCESS && build) {\n\n            error = ::clBuildProgram(\n                object_,\n                0,\n                NULL,\n                \"\",\n                NULL,\n                NULL);\n\n            detail::errHandler(error, __BUILD_PROGRAM_ERR);\n        }\n\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    Program(\n        const Context& context,\n        const STRING_CLASS& source,\n        bool build = false,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n\n        const char * strings = source.c_str();\n        const ::size_t length  = source.size();\n\n        object_ = ::clCreateProgramWithSource(\n            context(), (cl_uint)1, &strings, &length, &error);\n\n        detail::errHandler(error, __CREATE_PROGRAM_WITH_SOURCE_ERR);\n\n        if (error == CL_SUCCESS && build) {\n\n            error = ::clBuildProgram(\n                object_,\n                0,\n                NULL,\n                \"\",\n                NULL,\n                NULL);\n\n            detail::errHandler(error, __BUILD_PROGRAM_ERR);\n        }\n\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    Program(\n        const Context& context,\n        const Sources& sources,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n\n        const ::size_t n = (::size_t)sources.size();\n        ::size_t* lengths = (::size_t*) alloca(n * sizeof(::size_t));\n        const char** strings = (const char**) alloca(n * sizeof(const char*));\n\n        for (::size_t i = 0; i < n; ++i) {\n            strings[i] = sources[(int)i].first;\n            lengths[i] = sources[(int)i].second;\n        }\n\n        object_ = ::clCreateProgramWithSource(\n            context(), (cl_uint)n, strings, lengths, &error);\n\n        detail::errHandler(error, __CREATE_PROGRAM_WITH_SOURCE_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    /**\n     * Construct a program object from a list of devices and a per-device list of binaries.\n     * \\param context A valid OpenCL context in which to construct the program.\n     * \\param devices A vector of OpenCL device objects for which the program will be created.\n     * \\param binaries A vector of pairs of a pointer to a binary object and its length.\n     * \\param binaryStatus An optional vector that on completion will be resized to\n     *   match the size of binaries and filled with values to specify if each binary\n     *   was successfully loaded.\n     *   Set to CL_SUCCESS if the binary was successfully loaded.\n     *   Set to CL_INVALID_VALUE if the length is 0 or the binary pointer is NULL.\n     *   Set to CL_INVALID_BINARY if the binary provided is not valid for the matching device.\n     * \\param err if non-NULL will be set to CL_SUCCESS on successful operation or one of the following errors:\n     *   CL_INVALID_CONTEXT if context is not a valid context.\n     *   CL_INVALID_VALUE if the length of devices is zero; or if the length of binaries does not match the length of devices; \n     *     or if any entry in binaries is NULL or has length 0.\n     *   CL_INVALID_DEVICE if OpenCL devices listed in devices are not in the list of devices associated with context.\n     *   CL_INVALID_BINARY if an invalid program binary was encountered for any device. binaryStatus will return specific status for each device.\n     *   CL_OUT_OF_HOST_MEMORY if there is a failure to allocate resources required by the OpenCL implementation on the host.\n     */\n    Program(\n        const Context& context,\n        const VECTOR_CLASS<Device>& devices,\n        const Binaries& binaries,\n        VECTOR_CLASS<cl_int>* binaryStatus = NULL,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n        \n        const ::size_t numDevices = devices.size();\n        \n        // Catch size mismatch early and return\n        if(binaries.size() != numDevices) {\n            error = CL_INVALID_VALUE;\n            detail::errHandler(error, __CREATE_PROGRAM_WITH_BINARY_ERR);\n            if (err != NULL) {\n                *err = error;\n            }\n            return;\n        }\n\n        ::size_t* lengths = (::size_t*) alloca(numDevices * sizeof(::size_t));\n        const unsigned char** images = (const unsigned char**) alloca(numDevices * sizeof(const unsigned char**));\n\n        for (::size_t i = 0; i < numDevices; ++i) {\n            images[i] = (const unsigned char*)binaries[i].first;\n            lengths[i] = binaries[(int)i].second;\n        }\n\n        cl_device_id* deviceIDs = (cl_device_id*) alloca(numDevices * sizeof(cl_device_id));\n        for( ::size_t deviceIndex = 0; deviceIndex < numDevices; ++deviceIndex ) {\n            deviceIDs[deviceIndex] = (devices[deviceIndex])();\n        }\n\n        if(binaryStatus) {\n            binaryStatus->resize(numDevices);\n        }\n        \n        object_ = ::clCreateProgramWithBinary(\n            context(), (cl_uint) devices.size(),\n            deviceIDs,\n            lengths, images, binaryStatus != NULL\n               ? &binaryStatus->front()\n               : NULL, &error);\n\n        detail::errHandler(error, __CREATE_PROGRAM_WITH_BINARY_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    \n#if defined(CL_VERSION_1_2)\n    /**\n     * Create program using builtin kernels.\n     * \\param kernelNames Semi-colon separated list of builtin kernel names\n     */\n    Program(\n        const Context& context,\n        const VECTOR_CLASS<Device>& devices,\n        const STRING_CLASS& kernelNames,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n\n\n        ::size_t numDevices = devices.size();\n        cl_device_id* deviceIDs = (cl_device_id*) alloca(numDevices * sizeof(cl_device_id));\n        for( ::size_t deviceIndex = 0; deviceIndex < numDevices; ++deviceIndex ) {\n            deviceIDs[deviceIndex] = (devices[deviceIndex])();\n        }\n        \n        object_ = ::clCreateProgramWithBuiltInKernels(\n            context(), \n            (cl_uint) devices.size(),\n            deviceIDs,\n            kernelNames.c_str(), \n            &error);\n\n        detail::errHandler(error, __CREATE_PROGRAM_WITH_BUILT_IN_KERNELS_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n#endif // #if defined(CL_VERSION_1_2)\n\n    Program() { }\n\n    Program(const Program& program) : detail::Wrapper<cl_type>(program) { }\n\n    __CL_EXPLICIT_CONSTRUCTORS Program(const cl_program& program) : detail::Wrapper<cl_type>(program) { }\n\n    Program& operator = (const Program& rhs)\n    {\n        if (this != &rhs) {\n            detail::Wrapper<cl_type>::operator=(rhs);\n        }\n        return *this;\n    }\n\n    Program& operator = (const cl_program& rhs)\n    {\n        detail::Wrapper<cl_type>::operator=(rhs);\n        return *this;\n    }\n\n    cl_int build(\n        const VECTOR_CLASS<Device>& devices,\n        const char* options = NULL,\n        void (CL_CALLBACK * notifyFptr)(cl_program, void *) = NULL,\n        void* data = NULL) const\n    {\n        ::size_t numDevices = devices.size();\n        cl_device_id* deviceIDs = (cl_device_id*) alloca(numDevices * sizeof(cl_device_id));\n        for( ::size_t deviceIndex = 0; deviceIndex < numDevices; ++deviceIndex ) {\n            deviceIDs[deviceIndex] = (devices[deviceIndex])();\n        }\n\n        return detail::errHandler(\n            ::clBuildProgram(\n                object_,\n                (cl_uint)\n                devices.size(),\n                deviceIDs,\n                options,\n                notifyFptr,\n                data),\n                __BUILD_PROGRAM_ERR);\n    }\n\n    cl_int build(\n        const char* options = NULL,\n        void (CL_CALLBACK * notifyFptr)(cl_program, void *) = NULL,\n        void* data = NULL) const\n    {\n        return detail::errHandler(\n            ::clBuildProgram(\n                object_,\n                0,\n                NULL,\n                options,\n                notifyFptr,\n                data),\n                __BUILD_PROGRAM_ERR);\n    }\n\n#if defined(CL_VERSION_1_2)\n\tcl_int compile(\n        const char* options = NULL,\n        void (CL_CALLBACK * notifyFptr)(cl_program, void *) = NULL,\n        void* data = NULL) const\n    {\n        return detail::errHandler(\n            ::clCompileProgram(\n                object_,\n                0,\n                NULL,\n                options,\n\t\t\t\t0,\n\t\t\t\tNULL,\n\t\t\t\tNULL,\n                notifyFptr,\n                data),\n                __COMPILE_PROGRAM_ERR);\n    }\n#endif\n\n    template <typename T>\n    cl_int getInfo(cl_program_info name, T* param) const\n    {\n        return detail::errHandler(\n            detail::getInfo(&::clGetProgramInfo, object_, name, param),\n            __GET_PROGRAM_INFO_ERR);\n    }\n\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_program_info, name>::param_type\n    getInfo(cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n            detail::cl_program_info, name>::param_type param;\n        cl_int result = getInfo(name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n\n    template <typename T>\n    cl_int getBuildInfo(\n        const Device& device, cl_program_build_info name, T* param) const\n    {\n        return detail::errHandler(\n            detail::getInfo(\n                &::clGetProgramBuildInfo, object_, device(), name, param),\n                __GET_PROGRAM_BUILD_INFO_ERR);\n    }\n\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_program_build_info, name>::param_type\n    getBuildInfo(const Device& device, cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n            detail::cl_program_build_info, name>::param_type param;\n        cl_int result = getBuildInfo(device, name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n\n    cl_int createKernels(VECTOR_CLASS<Kernel>* kernels)\n    {\n        cl_uint numKernels;\n        cl_int err = ::clCreateKernelsInProgram(object_, 0, NULL, &numKernels);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __CREATE_KERNELS_IN_PROGRAM_ERR);\n        }\n\n        Kernel* value = (Kernel*) alloca(numKernels * sizeof(Kernel));\n        err = ::clCreateKernelsInProgram(\n            object_, numKernels, (cl_kernel*) value, NULL);\n        if (err != CL_SUCCESS) {\n            return detail::errHandler(err, __CREATE_KERNELS_IN_PROGRAM_ERR);\n        }\n\n        kernels->assign(&value[0], &value[numKernels]);\n        return CL_SUCCESS;\n    }\n};\n\n#if defined(CL_VERSION_1_2)\ninline Program linkProgram(\n    Program input1,\n    Program input2,\n    const char* options = NULL,\n    void (CL_CALLBACK * notifyFptr)(cl_program, void *) = NULL,\n    void* data = NULL,\n    cl_int* err = NULL) \n{\n    cl_int err_local = CL_SUCCESS;\n\n    cl_program programs[2] = { input1(), input2() };\n\n    Context ctx = input1.getInfo<CL_PROGRAM_CONTEXT>();\n\n    cl_program prog = ::clLinkProgram(\n        ctx(),\n        0,\n        NULL,\n        options,\n        2,\n        programs,\n        notifyFptr,\n        data,\n        &err_local);\n\n    detail::errHandler(err_local,__COMPILE_PROGRAM_ERR);\n    if (err != NULL) {\n        *err = err_local;\n    }\n\n    return Program(prog);\n}\n\ninline Program linkProgram(\n    VECTOR_CLASS<Program> inputPrograms,\n    const char* options = NULL,\n    void (CL_CALLBACK * notifyFptr)(cl_program, void *) = NULL,\n    void* data = NULL,\n    cl_int* err = NULL) \n{\n    cl_int err_local = CL_SUCCESS;\n\n    cl_program * programs = (cl_program*) alloca(inputPrograms.size() * sizeof(cl_program));\n\n    if (programs != NULL) {\n        for (unsigned int i = 0; i < inputPrograms.size(); i++) {\n          programs[i] = inputPrograms[i]();\n        }\n    } \n\n    cl_program prog = ::clLinkProgram(\n        Context::getDefault()(),\n        0,\n        NULL,\n        options,\n        (cl_uint)inputPrograms.size(),\n        programs,\n        notifyFptr,\n        data,\n        &err_local);\n\n    detail::errHandler(err_local,__COMPILE_PROGRAM_ERR);\n    if (err != NULL) {\n        *err = err_local;\n    }\n\n    return Program(prog);\n}\n#endif\n\ntemplate<>\ninline VECTOR_CLASS<char *> cl::Program::getInfo<CL_PROGRAM_BINARIES>(cl_int* err) const\n{\n    VECTOR_CLASS< ::size_t> sizes = getInfo<CL_PROGRAM_BINARY_SIZES>();\n    VECTOR_CLASS<char *> binaries;\n    for (VECTOR_CLASS< ::size_t>::iterator s = sizes.begin(); s != sizes.end(); ++s) \n    {\n        char *ptr = NULL;\n        if (*s != 0) \n            ptr = new char[*s];\n        binaries.push_back(ptr);\n    }\n    \n    cl_int result = getInfo(CL_PROGRAM_BINARIES, &binaries);\n    if (err != NULL) {\n        *err = result;\n    }\n    return binaries;\n}\n\ninline Kernel::Kernel(const Program& program, const char* name, cl_int* err)\n{\n    cl_int error;\n\n    object_ = ::clCreateKernel(program(), name, &error);\n    detail::errHandler(error, __CREATE_KERNEL_ERR);\n\n    if (err != NULL) {\n        *err = error;\n    }\n\n}\n\n/*! \\class CommandQueue\n * \\brief CommandQueue interface for cl_command_queue.\n */\nclass CommandQueue : public detail::Wrapper<cl_command_queue>\n{\nprivate:\n    static volatile int default_initialized_;\n    static CommandQueue default_;\n    static volatile cl_int default_error_;\npublic:\n   CommandQueue(\n        cl_command_queue_properties properties,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n\n        Context context = Context::getDefault(&error);\n        detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n\n        if (error != CL_SUCCESS) {\n            if (err != NULL) {\n                *err = error;\n            }\n        }\n        else {\n            Device device = context.getInfo<CL_CONTEXT_DEVICES>()[0];\n\n            object_ = ::clCreateCommandQueue(\n                context(), device(), properties, &error);\n\n            detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n            if (err != NULL) {\n                *err = error;\n            }\n        }\n    }\n    /*!\n    * \\brief Constructs a CommandQueue for an implementation defined device in the given context\n    */\n    explicit CommandQueue(\n        const Context& context,\n        cl_command_queue_properties properties = 0,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n        VECTOR_CLASS<cl::Device> devices;\n        error = context.getInfo(CL_CONTEXT_DEVICES, &devices);\n\n        detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n\n        if (error != CL_SUCCESS)\n        {\n            if (err != NULL) {\n                *err = error;\n            }\n            return;\n        }\n\n        object_ = ::clCreateCommandQueue(context(), devices[0](), properties, &error);\n\n        detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n\n        if (err != NULL) {\n            *err = error;\n        }\n\n    }\n\n    CommandQueue(\n        const Context& context,\n        const Device& device,\n        cl_command_queue_properties properties = 0,\n        cl_int* err = NULL)\n    {\n        cl_int error;\n        object_ = ::clCreateCommandQueue(\n            context(), device(), properties, &error);\n\n        detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n\n    static CommandQueue getDefault(cl_int * err = NULL) \n    {\n        int state = detail::compare_exchange(\n            &default_initialized_, \n            __DEFAULT_BEING_INITIALIZED, __DEFAULT_NOT_INITIALIZED);\n        \n        if (state & __DEFAULT_INITIALIZED) {\n            if (err != NULL) {\n                *err = default_error_;\n            }\n            return default_;\n        }\n\n        if (state & __DEFAULT_BEING_INITIALIZED) {\n              // Assume writes will propagate eventually...\n              while(default_initialized_ != __DEFAULT_INITIALIZED) {\n                  detail::fence();\n              }\n\n            if (err != NULL) {\n                *err = default_error_;\n            }\n            return default_;\n        }\n\n        cl_int error;\n\n        Context context = Context::getDefault(&error);\n        detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n\n        if (error != CL_SUCCESS) {\n            if (err != NULL) {\n                *err = error;\n            }\n        }\n        else {\n            Device device = context.getInfo<CL_CONTEXT_DEVICES>()[0];\n\n            default_ = CommandQueue(context, device, 0, &error);\n\n            detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n            if (err != NULL) {\n                *err = error;\n            }\n        }\n\n        detail::fence();\n\n        default_error_ = error;\n        // Assume writes will propagate eventually...\n        default_initialized_ = __DEFAULT_INITIALIZED;\n\n        detail::fence();\n\n        if (err != NULL) {\n            *err = default_error_;\n        }\n        return default_;\n\n    }\n\n    CommandQueue() { }\n\n    CommandQueue(const CommandQueue& commandQueue) : detail::Wrapper<cl_type>(commandQueue) { }\n\n    CommandQueue(const cl_command_queue& commandQueue) : detail::Wrapper<cl_type>(commandQueue) { }\n\n    CommandQueue& operator = (const CommandQueue& rhs)\n    {\n        if (this != &rhs) {\n            detail::Wrapper<cl_type>::operator=(rhs);\n        }\n        return *this;\n    }\n\n    CommandQueue& operator = (const cl_command_queue& rhs)\n    {\n        detail::Wrapper<cl_type>::operator=(rhs);\n        return *this;\n    }\n\n    template <typename T>\n    cl_int getInfo(cl_command_queue_info name, T* param) const\n    {\n        return detail::errHandler(\n            detail::getInfo(\n                &::clGetCommandQueueInfo, object_, name, param),\n                __GET_COMMAND_QUEUE_INFO_ERR);\n    }\n\n    template <cl_int name> typename\n    detail::param_traits<detail::cl_command_queue_info, name>::param_type\n    getInfo(cl_int* err = NULL) const\n    {\n        typename detail::param_traits<\n            detail::cl_command_queue_info, name>::param_type param;\n        cl_int result = getInfo(name, &param);\n        if (err != NULL) {\n            *err = result;\n        }\n        return param;\n    }\n\n    cl_int enqueueReadBuffer(\n        const Buffer& buffer,\n        cl_bool blocking,\n        ::size_t offset,\n        ::size_t size,\n        void* ptr,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueReadBuffer(\n                object_, buffer(), blocking, offset, size,\n                ptr,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_READ_BUFFER_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    cl_int enqueueWriteBuffer(\n        const Buffer& buffer,\n        cl_bool blocking,\n        ::size_t offset,\n        ::size_t size,\n        const void* ptr,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueWriteBuffer(\n                object_, buffer(), blocking, offset, size,\n                ptr,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n                __ENQUEUE_WRITE_BUFFER_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    cl_int enqueueCopyBuffer(\n        const Buffer& src,\n        const Buffer& dst,\n        ::size_t src_offset,\n        ::size_t dst_offset,\n        ::size_t size,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueCopyBuffer(\n                object_, src(), dst(), src_offset, dst_offset, size,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQEUE_COPY_BUFFER_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    cl_int enqueueReadBufferRect(\n        const Buffer& buffer,\n        cl_bool blocking,\n        const size_t<3>& buffer_offset,\n        const size_t<3>& host_offset,\n        const size_t<3>& region,\n        ::size_t buffer_row_pitch,\n        ::size_t buffer_slice_pitch,\n        ::size_t host_row_pitch,\n        ::size_t host_slice_pitch,\n        void *ptr,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueReadBufferRect(\n                object_, \n                buffer(), \n                blocking, \n                (const ::size_t *)buffer_offset,\n                (const ::size_t *)host_offset,\n                (const ::size_t *)region,\n                buffer_row_pitch,\n                buffer_slice_pitch,\n                host_row_pitch,\n                host_slice_pitch,\n                ptr,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n                __ENQUEUE_READ_BUFFER_RECT_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    cl_int enqueueWriteBufferRect(\n        const Buffer& buffer,\n        cl_bool blocking,\n        const size_t<3>& buffer_offset,\n        const size_t<3>& host_offset,\n        const size_t<3>& region,\n        ::size_t buffer_row_pitch,\n        ::size_t buffer_slice_pitch,\n        ::size_t host_row_pitch,\n        ::size_t host_slice_pitch,\n        void *ptr,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueWriteBufferRect(\n                object_, \n                buffer(), \n                blocking, \n                (const ::size_t *)buffer_offset,\n                (const ::size_t *)host_offset,\n                (const ::size_t *)region,\n                buffer_row_pitch,\n                buffer_slice_pitch,\n                host_row_pitch,\n                host_slice_pitch,\n                ptr,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n                __ENQUEUE_WRITE_BUFFER_RECT_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    cl_int enqueueCopyBufferRect(\n        const Buffer& src,\n        const Buffer& dst,\n        const size_t<3>& src_origin,\n        const size_t<3>& dst_origin,\n        const size_t<3>& region,\n        ::size_t src_row_pitch,\n        ::size_t src_slice_pitch,\n        ::size_t dst_row_pitch,\n        ::size_t dst_slice_pitch,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueCopyBufferRect(\n                object_, \n                src(), \n                dst(), \n                (const ::size_t *)src_origin, \n                (const ::size_t *)dst_origin, \n                (const ::size_t *)region,\n                src_row_pitch,\n                src_slice_pitch,\n                dst_row_pitch,\n                dst_slice_pitch,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQEUE_COPY_BUFFER_RECT_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n#if defined(CL_VERSION_1_2)\n    /**\n     * Enqueue a command to fill a buffer object with a pattern\n     * of a given size. The pattern is specified a as vector.\n     * \\tparam PatternType The datatype of the pattern field. \n     *     The pattern type must be an accepted OpenCL data type.\n     */\n    template<typename PatternType>\n    cl_int enqueueFillBuffer(\n        const Buffer& buffer,\n        PatternType pattern,\n        ::size_t offset,\n        ::size_t size,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueFillBuffer(\n                object_, \n                buffer(),\n                static_cast<void*>(&pattern),\n                sizeof(PatternType), \n                offset, \n                size,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n                __ENQUEUE_FILL_BUFFER_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n#endif // #if defined(CL_VERSION_1_2)\n\n    cl_int enqueueReadImage(\n        const Image& image,\n        cl_bool blocking,\n        const size_t<3>& origin,\n        const size_t<3>& region,\n        ::size_t row_pitch,\n        ::size_t slice_pitch,\n        void* ptr,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueReadImage(\n                object_, image(), blocking, (const ::size_t *) origin,\n                (const ::size_t *) region, row_pitch, slice_pitch, ptr,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_READ_IMAGE_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    cl_int enqueueWriteImage(\n        const Image& image,\n        cl_bool blocking,\n        const size_t<3>& origin,\n        const size_t<3>& region,\n        ::size_t row_pitch,\n        ::size_t slice_pitch,\n        void* ptr,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueWriteImage(\n                object_, image(), blocking, (const ::size_t *) origin,\n                (const ::size_t *) region, row_pitch, slice_pitch, ptr,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_WRITE_IMAGE_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    cl_int enqueueCopyImage(\n        const Image& src,\n        const Image& dst,\n        const size_t<3>& src_origin,\n        const size_t<3>& dst_origin,\n        const size_t<3>& region,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueCopyImage(\n                object_, src(), dst(), (const ::size_t *) src_origin,\n                (const ::size_t *)dst_origin, (const ::size_t *) region,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_COPY_IMAGE_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n#if defined(CL_VERSION_1_2)\n    /**\n     * Enqueue a command to fill an image object with a specified color.\n     * \\param fillColor is the color to use to fill the image.\n     *     This is a four component RGBA floating-point color value if\n     *     the image channel data type is not an unnormalized signed or\n     *     unsigned data type.\n     */\n    cl_int enqueueFillImage(\n        const Image& image,\n        cl_float4 fillColor,\n        const size_t<3>& origin,\n        const size_t<3>& region,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueFillImage(\n                object_, \n                image(),\n                static_cast<void*>(&fillColor), \n                (const ::size_t *) origin, \n                (const ::size_t *) region,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n                __ENQUEUE_FILL_IMAGE_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    /**\n     * Enqueue a command to fill an image object with a specified color.\n     * \\param fillColor is the color to use to fill the image.\n     *     This is a four component RGBA signed integer color value if\n     *     the image channel data type is an unnormalized signed integer\n     *     type.\n     */\n    cl_int enqueueFillImage(\n        const Image& image,\n        cl_int4 fillColor,\n        const size_t<3>& origin,\n        const size_t<3>& region,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueFillImage(\n                object_, \n                image(),\n                static_cast<void*>(&fillColor), \n                (const ::size_t *) origin, \n                (const ::size_t *) region,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n                __ENQUEUE_FILL_IMAGE_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    /**\n     * Enqueue a command to fill an image object with a specified color.\n     * \\param fillColor is the color to use to fill the image.\n     *     This is a four component RGBA unsigned integer color value if\n     *     the image channel data type is an unnormalized unsigned integer\n     *     type.\n     */\n    cl_int enqueueFillImage(\n        const Image& image,\n        cl_uint4 fillColor,\n        const size_t<3>& origin,\n        const size_t<3>& region,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueFillImage(\n                object_, \n                image(),\n                static_cast<void*>(&fillColor), \n                (const ::size_t *) origin, \n                (const ::size_t *) region,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n                __ENQUEUE_FILL_IMAGE_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n#endif // #if defined(CL_VERSION_1_2)\n\n    cl_int enqueueCopyImageToBuffer(\n        const Image& src,\n        const Buffer& dst,\n        const size_t<3>& src_origin,\n        const size_t<3>& region,\n        ::size_t dst_offset,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueCopyImageToBuffer(\n                object_, src(), dst(), (const ::size_t *) src_origin,\n                (const ::size_t *) region, dst_offset,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_COPY_IMAGE_TO_BUFFER_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    cl_int enqueueCopyBufferToImage(\n        const Buffer& src,\n        const Image& dst,\n        ::size_t src_offset,\n        const size_t<3>& dst_origin,\n        const size_t<3>& region,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueCopyBufferToImage(\n                object_, src(), dst(), src_offset,\n                (const ::size_t *) dst_origin, (const ::size_t *) region,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_COPY_BUFFER_TO_IMAGE_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    void* enqueueMapBuffer(\n        const Buffer& buffer,\n        cl_bool blocking,\n        cl_map_flags flags,\n        ::size_t offset,\n        ::size_t size,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL,\n        cl_int* err = NULL) const\n    {\n        cl_int error;\n        void * result = ::clEnqueueMapBuffer(\n            object_, buffer(), blocking, flags, offset, size,\n            (events != NULL) ? (cl_uint) events->size() : 0,\n            (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n            (cl_event*) event,\n            &error);\n\n        detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n        return result;\n    }\n\n    void* enqueueMapImage(\n        const Image& buffer,\n        cl_bool blocking,\n        cl_map_flags flags,\n        const size_t<3>& origin,\n        const size_t<3>& region,\n        ::size_t * row_pitch,\n        ::size_t * slice_pitch,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL,\n        cl_int* err = NULL) const\n    {\n        cl_int error;\n        void * result = ::clEnqueueMapImage(\n            object_, buffer(), blocking, flags,\n            (const ::size_t *) origin, (const ::size_t *) region,\n            row_pitch, slice_pitch,\n            (events != NULL) ? (cl_uint) events->size() : 0,\n            (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n            (cl_event*) event,\n            &error);\n\n        detail::errHandler(error, __ENQUEUE_MAP_IMAGE_ERR);\n        if (err != NULL) {\n              *err = error;\n        }\n        return result;\n    }\n\n    cl_int enqueueUnmapMemObject(\n        const Memory& memory,\n        void* mapped_ptr,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueUnmapMemObject(\n                object_, memory(), mapped_ptr,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n#if defined(CL_VERSION_1_2)\n    /**\n     * Enqueues a marker command which waits for either a list of events to complete, \n     * or all previously enqueued commands to complete.\n     *\n     * Enqueues a marker command which waits for either a list of events to complete, \n     * or if the list is empty it waits for all commands previously enqueued in command_queue \n     * to complete before it completes. This command returns an event which can be waited on, \n     * i.e. this event can be waited on to insure that all events either in the event_wait_list \n     * or all previously enqueued commands, queued before this command to command_queue, \n     * have completed.\n     */\n    cl_int enqueueMarkerWithWaitList(\n        const VECTOR_CLASS<Event> *events = 0,\n        Event *event = 0)\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueMarkerWithWaitList(\n                object_,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_MARKER_WAIT_LIST_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    /**\n     * A synchronization point that enqueues a barrier operation.\n     *\n     * Enqueues a barrier command which waits for either a list of events to complete, \n     * or if the list is empty it waits for all commands previously enqueued in command_queue \n     * to complete before it completes. This command blocks command execution, that is, any \n     * following commands enqueued after it do not execute until it completes. This command \n     * returns an event which can be waited on, i.e. this event can be waited on to insure that \n     * all events either in the event_wait_list or all previously enqueued commands, queued \n     * before this command to command_queue, have completed.\n     */\n    cl_int enqueueBarrierWithWaitList(\n        const VECTOR_CLASS<Event> *events = 0,\n        Event *event = 0)\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueBarrierWithWaitList(\n                object_,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_BARRIER_WAIT_LIST_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n    \n    /**\n     * Enqueues a command to indicate with which device a set of memory objects\n     * should be associated.\n     */\n    cl_int enqueueMigrateMemObjects(\n        const VECTOR_CLASS<Memory> &memObjects,\n        cl_mem_migration_flags flags,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL\n        )\n    {\n        cl_event tmp;\n        \n        cl_mem* localMemObjects = static_cast<cl_mem*>(alloca(memObjects.size() * sizeof(cl_mem)));\n        for( int i = 0; i < (int)memObjects.size(); ++i ) {\n            localMemObjects[i] = memObjects[i]();\n        }\n\n\n        cl_int err = detail::errHandler(\n            ::clEnqueueMigrateMemObjects(\n                object_, \n                (cl_uint)memObjects.size(), \n                static_cast<const cl_mem*>(localMemObjects),\n                flags,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n#endif // #if defined(CL_VERSION_1_2)\n\n    cl_int enqueueNDRangeKernel(\n        const Kernel& kernel,\n        const NDRange& offset,\n        const NDRange& global,\n        const NDRange& local = NullRange,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueNDRangeKernel(\n                object_, kernel(), (cl_uint) global.dimensions(),\n                offset.dimensions() != 0 ? (const ::size_t*) offset : NULL,\n                (const ::size_t*) global,\n                local.dimensions() != 0 ? (const ::size_t*) local : NULL,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_NDRANGE_KERNEL_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    cl_int enqueueTask(\n        const Kernel& kernel,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueTask(\n                object_, kernel(),\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_TASK_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n    cl_int enqueueNativeKernel(\n        void (CL_CALLBACK *userFptr)(void *),\n        std::pair<void*, ::size_t> args,\n        const VECTOR_CLASS<Memory>* mem_objects = NULL,\n        const VECTOR_CLASS<const void*>* mem_locs = NULL,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL) const\n    {\n        cl_mem * mems = (mem_objects != NULL && mem_objects->size() > 0) \n            ? (cl_mem*) alloca(mem_objects->size() * sizeof(cl_mem))\n            : NULL;\n\n        if (mems != NULL) {\n            for (unsigned int i = 0; i < mem_objects->size(); i++) {\n                mems[i] = ((*mem_objects)[i])();\n            }\n        }\n\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            ::clEnqueueNativeKernel(\n                object_, userFptr, args.first, args.second,\n                (mem_objects != NULL) ? (cl_uint) mem_objects->size() : 0,\n                mems,\n                (mem_locs != NULL) ? (const void **) &mem_locs->front() : NULL,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_NATIVE_KERNEL);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n\n/**\n * Deprecated APIs for 1.2\n */\n#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) || (defined(CL_VERSION_1_1) && !defined(CL_VERSION_1_2)) \n    CL_EXT_PREFIX__VERSION_1_1_DEPRECATED \n    cl_int enqueueMarker(Event* event = NULL) const CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED\n    {\n        return detail::errHandler(\n            ::clEnqueueMarker(object_, (cl_event*) event),\n            __ENQUEUE_MARKER_ERR);\n    }\n\n    CL_EXT_PREFIX__VERSION_1_1_DEPRECATED\n    cl_int enqueueWaitForEvents(const VECTOR_CLASS<Event>& events) const CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED\n    {\n        return detail::errHandler(\n            ::clEnqueueWaitForEvents(\n                object_,\n                (cl_uint) events.size(),\n                (const cl_event*) &events.front()),\n            __ENQUEUE_WAIT_FOR_EVENTS_ERR);\n    }\n#endif // #if defined(CL_VERSION_1_1)\n\n    cl_int enqueueAcquireGLObjects(\n         const VECTOR_CLASS<Memory>* mem_objects = NULL,\n         const VECTOR_CLASS<Event>* events = NULL,\n         Event* event = NULL) const\n     {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n             ::clEnqueueAcquireGLObjects(\n                 object_,\n                 (mem_objects != NULL) ? (cl_uint) mem_objects->size() : 0,\n                 (mem_objects != NULL) ? (const cl_mem *) &mem_objects->front(): NULL,\n                 (events != NULL) ? (cl_uint) events->size() : 0,\n                 (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                 (event != NULL) ? &tmp : NULL),\n             __ENQUEUE_ACQUIRE_GL_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n     }\n\n    cl_int enqueueReleaseGLObjects(\n         const VECTOR_CLASS<Memory>* mem_objects = NULL,\n         const VECTOR_CLASS<Event>* events = NULL,\n         Event* event = NULL) const\n     {\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n             ::clEnqueueReleaseGLObjects(\n                 object_,\n                 (mem_objects != NULL) ? (cl_uint) mem_objects->size() : 0,\n                 (mem_objects != NULL) ? (const cl_mem *) &mem_objects->front(): NULL,\n                 (events != NULL) ? (cl_uint) events->size() : 0,\n                 (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n                 (event != NULL) ? &tmp : NULL),\n             __ENQUEUE_RELEASE_GL_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n     }\n\n#if defined (USE_DX_INTEROP)\ntypedef CL_API_ENTRY cl_int (CL_API_CALL *PFN_clEnqueueAcquireD3D10ObjectsKHR)(\n    cl_command_queue command_queue, cl_uint num_objects,\n    const cl_mem* mem_objects, cl_uint num_events_in_wait_list,\n    const cl_event* event_wait_list, cl_event* event);\ntypedef CL_API_ENTRY cl_int (CL_API_CALL *PFN_clEnqueueReleaseD3D10ObjectsKHR)(\n    cl_command_queue command_queue, cl_uint num_objects,\n    const cl_mem* mem_objects,  cl_uint num_events_in_wait_list,\n    const cl_event* event_wait_list, cl_event* event);\n\n    cl_int enqueueAcquireD3D10Objects(\n         const VECTOR_CLASS<Memory>* mem_objects = NULL,\n         const VECTOR_CLASS<Event>* events = NULL,\n         Event* event = NULL) const\n    {\n        static PFN_clEnqueueAcquireD3D10ObjectsKHR pfn_clEnqueueAcquireD3D10ObjectsKHR = NULL;\n#if defined(CL_VERSION_1_2)\n        cl_context context = getInfo<CL_QUEUE_CONTEXT>();\n        cl::Device device(getInfo<CL_QUEUE_DEVICE>());\n        cl_platform_id platform = device.getInfo<CL_DEVICE_PLATFORM>();\n        __INIT_CL_EXT_FCN_PTR_PLATFORM(platform, clEnqueueAcquireD3D10ObjectsKHR);\n#endif\n#if defined(CL_VERSION_1_1)\n        __INIT_CL_EXT_FCN_PTR(clEnqueueAcquireD3D10ObjectsKHR);\n#endif\n        \n        cl_event tmp;\n        cl_int err = detail::errHandler(\n             pfn_clEnqueueAcquireD3D10ObjectsKHR(\n                 object_,\n                 (mem_objects != NULL) ? (cl_uint) mem_objects->size() : 0,\n                 (mem_objects != NULL) ? (const cl_mem *) &mem_objects->front(): NULL,\n                 (events != NULL) ? (cl_uint) events->size() : 0,\n                 (events != NULL) ? (cl_event*) &events->front() : NULL,\n                 (event != NULL) ? &tmp : NULL),\n             __ENQUEUE_ACQUIRE_GL_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n     }\n\n    cl_int enqueueReleaseD3D10Objects(\n         const VECTOR_CLASS<Memory>* mem_objects = NULL,\n         const VECTOR_CLASS<Event>* events = NULL,\n         Event* event = NULL) const\n    {\n        static PFN_clEnqueueReleaseD3D10ObjectsKHR pfn_clEnqueueReleaseD3D10ObjectsKHR = NULL;\n#if defined(CL_VERSION_1_2)\n        cl_context context = getInfo<CL_QUEUE_CONTEXT>();\n        cl::Device device(getInfo<CL_QUEUE_DEVICE>());\n        cl_platform_id platform = device.getInfo<CL_DEVICE_PLATFORM>();\n        __INIT_CL_EXT_FCN_PTR_PLATFORM(platform, clEnqueueReleaseD3D10ObjectsKHR);\n#endif // #if defined(CL_VERSION_1_2)\n#if defined(CL_VERSION_1_1)\n        __INIT_CL_EXT_FCN_PTR(clEnqueueReleaseD3D10ObjectsKHR);\n#endif // #if defined(CL_VERSION_1_1)\n\n        cl_event tmp;\n        cl_int err = detail::errHandler(\n            pfn_clEnqueueReleaseD3D10ObjectsKHR(\n                object_,\n                (mem_objects != NULL) ? (cl_uint) mem_objects->size() : 0,\n                (mem_objects != NULL) ? (const cl_mem *) &mem_objects->front(): NULL,\n                (events != NULL) ? (cl_uint) events->size() : 0,\n                (events != NULL) ? (cl_event*) &events->front() : NULL,\n                (event != NULL) ? &tmp : NULL),\n            __ENQUEUE_RELEASE_GL_ERR);\n\n        if (event != NULL && err == CL_SUCCESS)\n            *event = tmp;\n\n        return err;\n    }\n#endif\n\n/**\n * Deprecated APIs for 1.2\n */\n#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS) || (defined(CL_VERSION_1_1) && !defined(CL_VERSION_1_2)) \n    CL_EXT_PREFIX__VERSION_1_1_DEPRECATED\n    cl_int enqueueBarrier() const CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED\n    {\n        return detail::errHandler(\n            ::clEnqueueBarrier(object_),\n            __ENQUEUE_BARRIER_ERR);\n    }\n#endif // #if defined(CL_VERSION_1_1)\n\n    cl_int flush() const\n    {\n        return detail::errHandler(::clFlush(object_), __FLUSH_ERR);\n    }\n\n    cl_int finish() const\n    {\n        return detail::errHandler(::clFinish(object_), __FINISH_ERR);\n    }\n};\n\n#ifdef _WIN32\n__declspec(selectany) volatile int CommandQueue::default_initialized_ = __DEFAULT_NOT_INITIALIZED;\n__declspec(selectany) CommandQueue CommandQueue::default_;\n__declspec(selectany) volatile cl_int CommandQueue::default_error_ = CL_SUCCESS;\n#else\n__attribute__((weak)) volatile int CommandQueue::default_initialized_ = __DEFAULT_NOT_INITIALIZED;\n__attribute__((weak)) CommandQueue CommandQueue::default_;\n__attribute__((weak)) volatile cl_int CommandQueue::default_error_ = CL_SUCCESS;\n#endif\n\ntemplate< typename IteratorType >\nBuffer::Buffer(\n    const Context &context,\n    IteratorType startIterator,\n    IteratorType endIterator,\n    bool readOnly,\n    bool useHostPtr,\n    cl_int* err)\n{\n    typedef typename std::iterator_traits<IteratorType>::value_type DataType;\n    cl_int error;\n\n    cl_mem_flags flags = 0;\n    if( readOnly ) {\n        flags |= CL_MEM_READ_ONLY;\n    }\n    else {\n        flags |= CL_MEM_READ_WRITE;\n    }\n    if( useHostPtr ) {\n        flags |= CL_MEM_USE_HOST_PTR;\n    }\n    \n    ::size_t size = sizeof(DataType)*(endIterator - startIterator);\n\n    if( useHostPtr ) {\n        object_ = ::clCreateBuffer(context(), flags, size, static_cast<DataType*>(&*startIterator), &error);\n    } else {\n        object_ = ::clCreateBuffer(context(), flags, size, 0, &error);\n    }\n\n    detail::errHandler(error, __CREATE_BUFFER_ERR);\n    if (err != NULL) {\n        *err = error;\n    }\n\n    if( !useHostPtr ) {\n        CommandQueue queue(context, 0, &error);\n        detail::errHandler(error, __CREATE_BUFFER_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n\n        error = cl::copy(queue, startIterator, endIterator, *this);\n        detail::errHandler(error, __CREATE_BUFFER_ERR);\n        if (err != NULL) {\n            *err = error;\n        }\n    }\n}\n\ninline cl_int enqueueReadBuffer(\n    const Buffer& buffer,\n    cl_bool blocking,\n    ::size_t offset,\n    ::size_t size,\n    void* ptr,\n    const VECTOR_CLASS<Event>* events = NULL,\n    Event* event = NULL)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n\n    if (error != CL_SUCCESS) {\n        return error;\n    }\n\n    return queue.enqueueReadBuffer(buffer, blocking, offset, size, ptr, events, event);\n}\n\ninline cl_int enqueueWriteBuffer(\n        const Buffer& buffer,\n        cl_bool blocking,\n        ::size_t offset,\n        ::size_t size,\n        const void* ptr,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n\n    if (error != CL_SUCCESS) {\n        return error;\n    }\n\n    return queue.enqueueWriteBuffer(buffer, blocking, offset, size, ptr, events, event);\n}\n\ninline void* enqueueMapBuffer(\n        const Buffer& buffer,\n        cl_bool blocking,\n        cl_map_flags flags,\n        ::size_t offset,\n        ::size_t size,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL,\n        cl_int* err = NULL)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n    detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR);\n    if (err != NULL) {\n        *err = error;\n    }\n\n    void * result = ::clEnqueueMapBuffer(\n            queue(), buffer(), blocking, flags, offset, size,\n            (events != NULL) ? (cl_uint) events->size() : 0,\n            (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n            (cl_event*) event,\n            &error);\n\n    detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR);\n    if (err != NULL) {\n        *err = error;\n    }\n    return result;\n}\n\ninline cl_int enqueueUnmapMemObject(\n    const Memory& memory,\n    void* mapped_ptr,\n    const VECTOR_CLASS<Event>* events = NULL,\n    Event* event = NULL)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n    detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR);\n    if (error != CL_SUCCESS) {\n        return error;\n    }\n\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n        ::clEnqueueUnmapMemObject(\n            queue(), memory(), mapped_ptr,\n            (events != NULL) ? (cl_uint) events->size() : 0,\n            (events != NULL && events->size() > 0) ? (cl_event*) &events->front() : NULL,\n            (event != NULL) ? &tmp : NULL),\n        __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n        *event = tmp;\n\n    return err;\n}\n\ninline cl_int enqueueCopyBuffer(\n        const Buffer& src,\n        const Buffer& dst,\n        ::size_t src_offset,\n        ::size_t dst_offset,\n        ::size_t size,\n        const VECTOR_CLASS<Event>* events = NULL,\n        Event* event = NULL)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n\n    if (error != CL_SUCCESS) {\n        return error;\n    }\n\n    return queue.enqueueCopyBuffer(src, dst, src_offset, dst_offset, size, events, event);\n}\n\n/**\n * Blocking copy operation between iterators and a buffer.\n * Host to Device.\n * Uses default command queue.\n */\ntemplate< typename IteratorType >\ninline cl_int copy( IteratorType startIterator, IteratorType endIterator, cl::Buffer &buffer )\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n    if (error != CL_SUCCESS)\n        return error;\n\n    return cl::copy(queue, startIterator, endIterator, buffer);\n}\n\n/**\n * Blocking copy operation between iterators and a buffer.\n * Device to Host.\n * Uses default command queue.\n */\ntemplate< typename IteratorType >\ninline cl_int copy( const cl::Buffer &buffer, IteratorType startIterator, IteratorType endIterator )\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n    if (error != CL_SUCCESS)\n        return error;\n\n    return cl::copy(queue, buffer, startIterator, endIterator);\n}\n\n/**\n * Blocking copy operation between iterators and a buffer.\n * Host to Device.\n * Uses specified queue.\n */\ntemplate< typename IteratorType >\ninline cl_int copy( const CommandQueue &queue, IteratorType startIterator, IteratorType endIterator, cl::Buffer &buffer )\n{\n    typedef typename std::iterator_traits<IteratorType>::value_type DataType;\n    cl_int error;\n    \n    ::size_t length = endIterator-startIterator;\n    ::size_t byteLength = length*sizeof(DataType);\n\n    DataType *pointer = \n        static_cast<DataType*>(queue.enqueueMapBuffer(buffer, CL_TRUE, CL_MAP_WRITE, 0, byteLength, 0, 0, &error));\n    // if exceptions enabled, enqueueMapBuffer will throw\n    if( error != CL_SUCCESS ) {\n        return error;\n    }\n#if defined(_MSC_VER)\n    std::copy(\n        startIterator, \n        endIterator, \n        stdext::checked_array_iterator<DataType*>(\n            pointer, length));\n#else\n    std::copy(startIterator, endIterator, pointer);\n#endif\n    Event endEvent;\n    error = queue.enqueueUnmapMemObject(buffer, pointer, 0, &endEvent);\n    // if exceptions enabled, enqueueUnmapMemObject will throw\n    if( error != CL_SUCCESS ) { \n        return error;\n    }\n    endEvent.wait();\n    return CL_SUCCESS;\n}\n\n/**\n * Blocking copy operation between iterators and a buffer.\n * Device to Host.\n * Uses specified queue.\n */\ntemplate< typename IteratorType >\ninline cl_int copy( const CommandQueue &queue, const cl::Buffer &buffer, IteratorType startIterator, IteratorType endIterator )\n{\n    typedef typename std::iterator_traits<IteratorType>::value_type DataType;\n    cl_int error;\n        \n    ::size_t length = endIterator-startIterator;\n    ::size_t byteLength = length*sizeof(DataType);\n\n    DataType *pointer = \n        static_cast<DataType*>(queue.enqueueMapBuffer(buffer, CL_TRUE, CL_MAP_READ, 0, byteLength, 0, 0, &error));\n    // if exceptions enabled, enqueueMapBuffer will throw\n    if( error != CL_SUCCESS ) {\n        return error;\n    }\n    std::copy(pointer, pointer + length, startIterator);\n    Event endEvent;\n    error = queue.enqueueUnmapMemObject(buffer, pointer, 0, &endEvent);\n    // if exceptions enabled, enqueueUnmapMemObject will throw\n    if( error != CL_SUCCESS ) { \n        return error;\n    }\n    endEvent.wait();\n    return CL_SUCCESS;\n}\n\n#if defined(CL_VERSION_1_1)\ninline cl_int enqueueReadBufferRect(\n    const Buffer& buffer,\n    cl_bool blocking,\n    const size_t<3>& buffer_offset,\n    const size_t<3>& host_offset,\n    const size_t<3>& region,\n    ::size_t buffer_row_pitch,\n    ::size_t buffer_slice_pitch,\n    ::size_t host_row_pitch,\n    ::size_t host_slice_pitch,\n    void *ptr,\n    const VECTOR_CLASS<Event>* events = NULL,\n    Event* event = NULL)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n\n    if (error != CL_SUCCESS) {\n        return error;\n    }\n\n    return queue.enqueueReadBufferRect(\n        buffer, \n        blocking, \n        buffer_offset, \n        host_offset,\n        region,\n        buffer_row_pitch,\n        buffer_slice_pitch,\n        host_row_pitch,\n        host_slice_pitch,\n        ptr, \n        events, \n        event);\n}\n\ninline cl_int enqueueWriteBufferRect(\n    const Buffer& buffer,\n    cl_bool blocking,\n    const size_t<3>& buffer_offset,\n    const size_t<3>& host_offset,\n    const size_t<3>& region,\n    ::size_t buffer_row_pitch,\n    ::size_t buffer_slice_pitch,\n    ::size_t host_row_pitch,\n    ::size_t host_slice_pitch,\n    void *ptr,\n    const VECTOR_CLASS<Event>* events = NULL,\n    Event* event = NULL)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n\n    if (error != CL_SUCCESS) {\n        return error;\n    }\n\n    return queue.enqueueWriteBufferRect(\n        buffer, \n        blocking, \n        buffer_offset, \n        host_offset,\n        region,\n        buffer_row_pitch,\n        buffer_slice_pitch,\n        host_row_pitch,\n        host_slice_pitch,\n        ptr, \n        events, \n        event);\n}\n\ninline cl_int enqueueCopyBufferRect(\n    const Buffer& src,\n    const Buffer& dst,\n    const size_t<3>& src_origin,\n    const size_t<3>& dst_origin,\n    const size_t<3>& region,\n    ::size_t src_row_pitch,\n    ::size_t src_slice_pitch,\n    ::size_t dst_row_pitch,\n    ::size_t dst_slice_pitch,\n    const VECTOR_CLASS<Event>* events = NULL,\n    Event* event = NULL)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n\n    if (error != CL_SUCCESS) {\n        return error;\n    }\n\n    return queue.enqueueCopyBufferRect(\n        src,\n        dst,\n        src_origin,\n        dst_origin,\n        region,\n        src_row_pitch,\n        src_slice_pitch,\n        dst_row_pitch,\n        dst_slice_pitch,\n        events, \n        event);\n}\n#endif\n\ninline cl_int enqueueReadImage(\n    const Image& image,\n    cl_bool blocking,\n    const size_t<3>& origin,\n    const size_t<3>& region,\n    ::size_t row_pitch,\n    ::size_t slice_pitch,\n    void* ptr,\n    const VECTOR_CLASS<Event>* events = NULL,\n    Event* event = NULL) \n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n\n    if (error != CL_SUCCESS) {\n        return error;\n    }\n\n    return queue.enqueueReadImage(\n        image,\n        blocking,\n        origin,\n        region,\n        row_pitch,\n        slice_pitch,\n        ptr,\n        events, \n        event);\n}\n\ninline cl_int enqueueWriteImage(\n    const Image& image,\n    cl_bool blocking,\n    const size_t<3>& origin,\n    const size_t<3>& region,\n    ::size_t row_pitch,\n    ::size_t slice_pitch,\n    void* ptr,\n    const VECTOR_CLASS<Event>* events = NULL,\n    Event* event = NULL)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n\n    if (error != CL_SUCCESS) {\n        return error;\n    }\n\n    return queue.enqueueWriteImage(\n        image,\n        blocking,\n        origin,\n        region,\n        row_pitch,\n        slice_pitch,\n        ptr,\n        events, \n        event);\n}\n\ninline cl_int enqueueCopyImage(\n    const Image& src,\n    const Image& dst,\n    const size_t<3>& src_origin,\n    const size_t<3>& dst_origin,\n    const size_t<3>& region,\n    const VECTOR_CLASS<Event>* events = NULL,\n    Event* event = NULL)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n\n    if (error != CL_SUCCESS) {\n        return error;\n    }\n\n    return queue.enqueueCopyImage(\n        src,\n        dst,\n        src_origin,\n        dst_origin,\n        region,\n        events,\n        event);\n}\n\ninline cl_int enqueueCopyImageToBuffer(\n    const Image& src,\n    const Buffer& dst,\n    const size_t<3>& src_origin,\n    const size_t<3>& region,\n    ::size_t dst_offset,\n    const VECTOR_CLASS<Event>* events = NULL,\n    Event* event = NULL)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n\n    if (error != CL_SUCCESS) {\n        return error;\n    }\n\n    return queue.enqueueCopyImageToBuffer(\n        src,\n        dst,\n        src_origin,\n        region,\n        dst_offset,\n        events,\n        event);\n}\n\ninline cl_int enqueueCopyBufferToImage(\n    const Buffer& src,\n    const Image& dst,\n    ::size_t src_offset,\n    const size_t<3>& dst_origin,\n    const size_t<3>& region,\n    const VECTOR_CLASS<Event>* events = NULL,\n    Event* event = NULL)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n\n    if (error != CL_SUCCESS) {\n        return error;\n    }\n\n    return queue.enqueueCopyBufferToImage(\n        src,\n        dst,\n        src_offset,\n        dst_origin,\n        region,\n        events,\n        event);\n}\n\n\ninline cl_int flush(void)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n\n    if (error != CL_SUCCESS) {\n        return error;\n    }\n\n    return queue.flush();\n}\n\ninline cl_int finish(void)\n{\n    cl_int error;\n    CommandQueue queue = CommandQueue::getDefault(&error);\n\n    if (error != CL_SUCCESS) {\n        return error;\n    } \n\n\n    return queue.finish();\n}\n\n// Kernel Functor support\n// New interface as of September 2011\n// Requires the C++11 std::tr1::function (note do not support TR1)\n// Visual Studio 2010 and GCC 4.2\n\nstruct EnqueueArgs\n{\n    CommandQueue queue_;\n    const NDRange offset_;\n    const NDRange global_;\n    const NDRange local_;\n    VECTOR_CLASS<Event> events_;\n\n    EnqueueArgs(NDRange global) : \n      queue_(CommandQueue::getDefault()),\n      offset_(NullRange), \n      global_(global),\n      local_(NullRange)\n    {\n\n    }\n\n    EnqueueArgs(NDRange global, NDRange local) : \n      queue_(CommandQueue::getDefault()),\n      offset_(NullRange), \n      global_(global),\n      local_(local)\n    {\n\n    }\n\n    EnqueueArgs(NDRange offset, NDRange global, NDRange local) : \n      queue_(CommandQueue::getDefault()),\n      offset_(offset), \n      global_(global),\n      local_(local)\n    {\n\n    }\n\n    EnqueueArgs(Event e, NDRange global) : \n      queue_(CommandQueue::getDefault()),\n      offset_(NullRange), \n      global_(global),\n      local_(NullRange)\n    {\n        events_.push_back(e);\n    }\n\n    EnqueueArgs(Event e, NDRange global, NDRange local) : \n      queue_(CommandQueue::getDefault()),\n      offset_(NullRange), \n      global_(global),\n      local_(local)\n    {\n        events_.push_back(e);\n    }\n\n    EnqueueArgs(Event e, NDRange offset, NDRange global, NDRange local) : \n      queue_(CommandQueue::getDefault()),\n      offset_(offset), \n      global_(global),\n      local_(local)\n    {\n        events_.push_back(e);\n    }\n\n    EnqueueArgs(const VECTOR_CLASS<Event> &events, NDRange global) : \n      queue_(CommandQueue::getDefault()),\n      offset_(NullRange), \n      global_(global),\n      local_(NullRange),\n      events_(events)\n    {\n\n    }\n\n    EnqueueArgs(const VECTOR_CLASS<Event> &events, NDRange global, NDRange local) : \n      queue_(CommandQueue::getDefault()),\n      offset_(NullRange), \n      global_(global),\n      local_(local),\n      events_(events)\n    {\n\n    }\n\n    EnqueueArgs(const VECTOR_CLASS<Event> &events, NDRange offset, NDRange global, NDRange local) : \n      queue_(CommandQueue::getDefault()),\n      offset_(offset), \n      global_(global),\n      local_(local),\n      events_(events)\n    {\n\n    }\n\n    EnqueueArgs(CommandQueue &queue, NDRange global) : \n      queue_(queue),\n      offset_(NullRange), \n      global_(global),\n      local_(NullRange)\n    {\n\n    }\n\n    EnqueueArgs(CommandQueue &queue, NDRange global, NDRange local) : \n      queue_(queue),\n      offset_(NullRange), \n      global_(global),\n      local_(local)\n    {\n\n    }\n\n    EnqueueArgs(CommandQueue &queue, NDRange offset, NDRange global, NDRange local) : \n      queue_(queue),\n      offset_(offset), \n      global_(global),\n      local_(local)\n    {\n\n    }\n\n    EnqueueArgs(CommandQueue &queue, Event e, NDRange global) : \n      queue_(queue),\n      offset_(NullRange), \n      global_(global),\n      local_(NullRange)\n    {\n        events_.push_back(e);\n    }\n\n    EnqueueArgs(CommandQueue &queue, Event e, NDRange global, NDRange local) : \n      queue_(queue),\n      offset_(NullRange), \n      global_(global),\n      local_(local)\n    {\n        events_.push_back(e);\n    }\n\n    EnqueueArgs(CommandQueue &queue, Event e, NDRange offset, NDRange global, NDRange local) : \n      queue_(queue),\n      offset_(offset), \n      global_(global),\n      local_(local)\n    {\n        events_.push_back(e);\n    }\n\n    EnqueueArgs(CommandQueue &queue, const VECTOR_CLASS<Event> &events, NDRange global) : \n      queue_(queue),\n      offset_(NullRange), \n      global_(global),\n      local_(NullRange),\n      events_(events)\n    {\n\n    }\n\n    EnqueueArgs(CommandQueue &queue, const VECTOR_CLASS<Event> &events, NDRange global, NDRange local) : \n      queue_(queue),\n      offset_(NullRange), \n      global_(global),\n      local_(local),\n      events_(events)\n    {\n\n    }\n\n    EnqueueArgs(CommandQueue &queue, const VECTOR_CLASS<Event> &events, NDRange offset, NDRange global, NDRange local) : \n      queue_(queue),\n      offset_(offset), \n      global_(global),\n      local_(local),\n      events_(events)\n    {\n\n    }\n};\n\nnamespace detail {\n\nclass NullType {};\n\ntemplate<int index, typename T0>\nstruct SetArg\n{\n    static void set (Kernel kernel, T0 arg)\n    {\n        kernel.setArg(index, arg);\n    }\n};  \n\ntemplate<int index>\nstruct SetArg<index, NullType>\n{\n    static void set (Kernel, NullType)\n    { \n    }\n};\n\ntemplate <\n   typename T0,   typename T1,   typename T2,   typename T3,\n   typename T4,   typename T5,   typename T6,   typename T7,\n   typename T8,   typename T9,   typename T10,   typename T11,\n   typename T12,   typename T13,   typename T14,   typename T15,\n   typename T16,   typename T17,   typename T18,   typename T19,\n   typename T20,   typename T21,   typename T22,   typename T23,\n   typename T24,   typename T25,   typename T26,   typename T27,\n   typename T28,   typename T29,   typename T30,   typename T31\n>\nclass KernelFunctorGlobal\n{\nprivate:\n    Kernel kernel_;\n\npublic:\n   KernelFunctorGlobal(\n        Kernel kernel) :\n            kernel_(kernel)\n    {}\n\n   KernelFunctorGlobal(\n        const Program& program,\n        const STRING_CLASS name,\n        cl_int * err = NULL) :\n            kernel_(program, name.c_str(), err)\n    {}\n\n    Event operator() (\n        const EnqueueArgs& args,\n        T0 t0,\n        T1 t1 = NullType(),\n        T2 t2 = NullType(),\n        T3 t3 = NullType(),\n        T4 t4 = NullType(),\n        T5 t5 = NullType(),\n        T6 t6 = NullType(),\n        T7 t7 = NullType(),\n        T8 t8 = NullType(),\n        T9 t9 = NullType(),\n        T10 t10 = NullType(),\n        T11 t11 = NullType(),\n        T12 t12 = NullType(),\n        T13 t13 = NullType(),\n        T14 t14 = NullType(),\n        T15 t15 = NullType(),\n        T16 t16 = NullType(),\n        T17 t17 = NullType(),\n        T18 t18 = NullType(),\n        T19 t19 = NullType(),\n        T20 t20 = NullType(),\n        T21 t21 = NullType(),\n        T22 t22 = NullType(),\n        T23 t23 = NullType(),\n        T24 t24 = NullType(),\n        T25 t25 = NullType(),\n        T26 t26 = NullType(),\n        T27 t27 = NullType(),\n        T28 t28 = NullType(),\n        T29 t29 = NullType(),\n        T30 t30 = NullType(),\n        T31 t31 = NullType()\n        )\n    {\n        Event event;\n        SetArg<0, T0>::set(kernel_, t0);\n        SetArg<1, T1>::set(kernel_, t1);\n        SetArg<2, T2>::set(kernel_, t2);\n        SetArg<3, T3>::set(kernel_, t3);\n        SetArg<4, T4>::set(kernel_, t4);\n        SetArg<5, T5>::set(kernel_, t5);\n        SetArg<6, T6>::set(kernel_, t6);\n        SetArg<7, T7>::set(kernel_, t7);\n        SetArg<8, T8>::set(kernel_, t8);\n        SetArg<9, T9>::set(kernel_, t9);\n        SetArg<10, T10>::set(kernel_, t10);\n        SetArg<11, T11>::set(kernel_, t11);\n        SetArg<12, T12>::set(kernel_, t12);\n        SetArg<13, T13>::set(kernel_, t13);\n        SetArg<14, T14>::set(kernel_, t14);\n        SetArg<15, T15>::set(kernel_, t15);\n        SetArg<16, T16>::set(kernel_, t16);\n        SetArg<17, T17>::set(kernel_, t17);\n        SetArg<18, T18>::set(kernel_, t18);\n        SetArg<19, T19>::set(kernel_, t19);\n        SetArg<20, T20>::set(kernel_, t20);\n        SetArg<21, T21>::set(kernel_, t21);\n        SetArg<22, T22>::set(kernel_, t22);\n        SetArg<23, T23>::set(kernel_, t23);\n        SetArg<24, T24>::set(kernel_, t24);\n        SetArg<25, T25>::set(kernel_, t25);\n        SetArg<26, T26>::set(kernel_, t26);\n        SetArg<27, T27>::set(kernel_, t27);\n        SetArg<28, T28>::set(kernel_, t28);\n        SetArg<29, T29>::set(kernel_, t29);\n        SetArg<30, T30>::set(kernel_, t30);\n        SetArg<31, T31>::set(kernel_, t31);\n        \n        args.queue_.enqueueNDRangeKernel(\n            kernel_,\n            args.offset_,\n            args.global_,\n            args.local_,\n            &args.events_,\n            &event);\n        \n        return event;\n    }\n\n};\n\n//------------------------------------------------------------------------------------------------------\n\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18,\n\ttypename T19,\n\ttypename T20,\n\ttypename T21,\n\ttypename T22,\n\ttypename T23,\n\ttypename T24,\n\ttypename T25,\n\ttypename T26,\n\ttypename T27,\n\ttypename T28,\n\ttypename T29,\n\ttypename T30,\n\ttypename T31>\nstruct functionImplementation_\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25,\n\t\tT26,\n\t\tT27,\n\t\tT28,\n\t\tT29,\n\t\tT30,\n\t\tT31> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 32))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25,\n\t\tT26,\n\t\tT27,\n\t\tT28,\n\t\tT29,\n\t\tT30,\n\t\tT31);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18,\n\t\tT19 arg19,\n\t\tT20 arg20,\n\t\tT21 arg21,\n\t\tT22 arg22,\n\t\tT23 arg23,\n\t\tT24 arg24,\n\t\tT25 arg25,\n\t\tT26 arg26,\n\t\tT27 arg27,\n\t\tT28 arg28,\n\t\tT29 arg29,\n\t\tT30 arg30,\n\t\tT31 arg31)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18,\n\t\t\targ19,\n\t\t\targ20,\n\t\t\targ21,\n\t\t\targ22,\n\t\t\targ23,\n\t\t\targ24,\n\t\t\targ25,\n\t\t\targ26,\n\t\t\targ27,\n\t\t\targ28,\n\t\t\targ29,\n\t\t\targ30,\n\t\t\targ31);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18,\n\ttypename T19,\n\ttypename T20,\n\ttypename T21,\n\ttypename T22,\n\ttypename T23,\n\ttypename T24,\n\ttypename T25,\n\ttypename T26,\n\ttypename T27,\n\ttypename T28,\n\ttypename T29,\n\ttypename T30>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tT18,\n\tT19,\n\tT20,\n\tT21,\n\tT22,\n\tT23,\n\tT24,\n\tT25,\n\tT26,\n\tT27,\n\tT28,\n\tT29,\n\tT30,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25,\n\t\tT26,\n\t\tT27,\n\t\tT28,\n\t\tT29,\n\t\tT30,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 31))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25,\n\t\tT26,\n\t\tT27,\n\t\tT28,\n\t\tT29,\n\t\tT30);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18,\n\t\tT19 arg19,\n\t\tT20 arg20,\n\t\tT21 arg21,\n\t\tT22 arg22,\n\t\tT23 arg23,\n\t\tT24 arg24,\n\t\tT25 arg25,\n\t\tT26 arg26,\n\t\tT27 arg27,\n\t\tT28 arg28,\n\t\tT29 arg29,\n\t\tT30 arg30)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18,\n\t\t\targ19,\n\t\t\targ20,\n\t\t\targ21,\n\t\t\targ22,\n\t\t\targ23,\n\t\t\targ24,\n\t\t\targ25,\n\t\t\targ26,\n\t\t\targ27,\n\t\t\targ28,\n\t\t\targ29,\n\t\t\targ30);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18,\n\ttypename T19,\n\ttypename T20,\n\ttypename T21,\n\ttypename T22,\n\ttypename T23,\n\ttypename T24,\n\ttypename T25,\n\ttypename T26,\n\ttypename T27,\n\ttypename T28,\n\ttypename T29>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tT18,\n\tT19,\n\tT20,\n\tT21,\n\tT22,\n\tT23,\n\tT24,\n\tT25,\n\tT26,\n\tT27,\n\tT28,\n\tT29,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25,\n\t\tT26,\n\t\tT27,\n\t\tT28,\n\t\tT29,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 30))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25,\n\t\tT26,\n\t\tT27,\n\t\tT28,\n\t\tT29);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18,\n\t\tT19 arg19,\n\t\tT20 arg20,\n\t\tT21 arg21,\n\t\tT22 arg22,\n\t\tT23 arg23,\n\t\tT24 arg24,\n\t\tT25 arg25,\n\t\tT26 arg26,\n\t\tT27 arg27,\n\t\tT28 arg28,\n\t\tT29 arg29)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18,\n\t\t\targ19,\n\t\t\targ20,\n\t\t\targ21,\n\t\t\targ22,\n\t\t\targ23,\n\t\t\targ24,\n\t\t\targ25,\n\t\t\targ26,\n\t\t\targ27,\n\t\t\targ28,\n\t\t\targ29);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18,\n\ttypename T19,\n\ttypename T20,\n\ttypename T21,\n\ttypename T22,\n\ttypename T23,\n\ttypename T24,\n\ttypename T25,\n\ttypename T26,\n\ttypename T27,\n\ttypename T28>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tT18,\n\tT19,\n\tT20,\n\tT21,\n\tT22,\n\tT23,\n\tT24,\n\tT25,\n\tT26,\n\tT27,\n\tT28,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25,\n\t\tT26,\n\t\tT27,\n\t\tT28,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 29))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25,\n\t\tT26,\n\t\tT27,\n\t\tT28);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18,\n\t\tT19 arg19,\n\t\tT20 arg20,\n\t\tT21 arg21,\n\t\tT22 arg22,\n\t\tT23 arg23,\n\t\tT24 arg24,\n\t\tT25 arg25,\n\t\tT26 arg26,\n\t\tT27 arg27,\n\t\tT28 arg28)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18,\n\t\t\targ19,\n\t\t\targ20,\n\t\t\targ21,\n\t\t\targ22,\n\t\t\targ23,\n\t\t\targ24,\n\t\t\targ25,\n\t\t\targ26,\n\t\t\targ27,\n\t\t\targ28);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18,\n\ttypename T19,\n\ttypename T20,\n\ttypename T21,\n\ttypename T22,\n\ttypename T23,\n\ttypename T24,\n\ttypename T25,\n\ttypename T26,\n\ttypename T27>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tT18,\n\tT19,\n\tT20,\n\tT21,\n\tT22,\n\tT23,\n\tT24,\n\tT25,\n\tT26,\n\tT27,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25,\n\t\tT26,\n\t\tT27,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 28))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25,\n\t\tT26,\n\t\tT27);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18,\n\t\tT19 arg19,\n\t\tT20 arg20,\n\t\tT21 arg21,\n\t\tT22 arg22,\n\t\tT23 arg23,\n\t\tT24 arg24,\n\t\tT25 arg25,\n\t\tT26 arg26,\n\t\tT27 arg27)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18,\n\t\t\targ19,\n\t\t\targ20,\n\t\t\targ21,\n\t\t\targ22,\n\t\t\targ23,\n\t\t\targ24,\n\t\t\targ25,\n\t\t\targ26,\n\t\t\targ27);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18,\n\ttypename T19,\n\ttypename T20,\n\ttypename T21,\n\ttypename T22,\n\ttypename T23,\n\ttypename T24,\n\ttypename T25,\n\ttypename T26>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tT18,\n\tT19,\n\tT20,\n\tT21,\n\tT22,\n\tT23,\n\tT24,\n\tT25,\n\tT26,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25,\n\t\tT26,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 27))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25,\n\t\tT26);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18,\n\t\tT19 arg19,\n\t\tT20 arg20,\n\t\tT21 arg21,\n\t\tT22 arg22,\n\t\tT23 arg23,\n\t\tT24 arg24,\n\t\tT25 arg25,\n\t\tT26 arg26)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18,\n\t\t\targ19,\n\t\t\targ20,\n\t\t\targ21,\n\t\t\targ22,\n\t\t\targ23,\n\t\t\targ24,\n\t\t\targ25,\n\t\t\targ26);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18,\n\ttypename T19,\n\ttypename T20,\n\ttypename T21,\n\ttypename T22,\n\ttypename T23,\n\ttypename T24,\n\ttypename T25>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tT18,\n\tT19,\n\tT20,\n\tT21,\n\tT22,\n\tT23,\n\tT24,\n\tT25,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 26))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tT25);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18,\n\t\tT19 arg19,\n\t\tT20 arg20,\n\t\tT21 arg21,\n\t\tT22 arg22,\n\t\tT23 arg23,\n\t\tT24 arg24,\n\t\tT25 arg25)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18,\n\t\t\targ19,\n\t\t\targ20,\n\t\t\targ21,\n\t\t\targ22,\n\t\t\targ23,\n\t\t\targ24,\n\t\t\targ25);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18,\n\ttypename T19,\n\ttypename T20,\n\ttypename T21,\n\ttypename T22,\n\ttypename T23,\n\ttypename T24>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tT18,\n\tT19,\n\tT20,\n\tT21,\n\tT22,\n\tT23,\n\tT24,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 25))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tT24);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18,\n\t\tT19 arg19,\n\t\tT20 arg20,\n\t\tT21 arg21,\n\t\tT22 arg22,\n\t\tT23 arg23,\n\t\tT24 arg24)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18,\n\t\t\targ19,\n\t\t\targ20,\n\t\t\targ21,\n\t\t\targ22,\n\t\t\targ23,\n\t\t\targ24);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18,\n\ttypename T19,\n\ttypename T20,\n\ttypename T21,\n\ttypename T22,\n\ttypename T23>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tT18,\n\tT19,\n\tT20,\n\tT21,\n\tT22,\n\tT23,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 24))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tT23);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18,\n\t\tT19 arg19,\n\t\tT20 arg20,\n\t\tT21 arg21,\n\t\tT22 arg22,\n\t\tT23 arg23)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18,\n\t\t\targ19,\n\t\t\targ20,\n\t\t\targ21,\n\t\t\targ22,\n\t\t\targ23);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18,\n\ttypename T19,\n\ttypename T20,\n\ttypename T21,\n\ttypename T22>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tT18,\n\tT19,\n\tT20,\n\tT21,\n\tT22,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 23))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tT22);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18,\n\t\tT19 arg19,\n\t\tT20 arg20,\n\t\tT21 arg21,\n\t\tT22 arg22)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18,\n\t\t\targ19,\n\t\t\targ20,\n\t\t\targ21,\n\t\t\targ22);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18,\n\ttypename T19,\n\ttypename T20,\n\ttypename T21>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tT18,\n\tT19,\n\tT20,\n\tT21,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 22))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tT21);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18,\n\t\tT19 arg19,\n\t\tT20 arg20,\n\t\tT21 arg21)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18,\n\t\t\targ19,\n\t\t\targ20,\n\t\t\targ21);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18,\n\ttypename T19,\n\ttypename T20>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tT18,\n\tT19,\n\tT20,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 21))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tT20);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18,\n\t\tT19 arg19,\n\t\tT20 arg20)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18,\n\t\t\targ19,\n\t\t\targ20);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18,\n\ttypename T19>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tT18,\n\tT19,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 20))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tT19);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18,\n\t\tT19 arg19)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18,\n\t\t\targ19);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17,\n\ttypename T18>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tT18,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 19))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tT18);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17,\n\t\tT18 arg18)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17,\n\t\t\targ18);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16,\n\ttypename T17>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tT17,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 18))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tT17);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16,\n\t\tT17 arg17)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16,\n\t\t\targ17);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15,\n\ttypename T16>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tT16,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 17))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tT16);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15,\n\t\tT16 arg16)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15,\n\t\t\targ16);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14,\n\ttypename T15>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tT15,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 16))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tT15);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14,\n\t\tT15 arg15)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14,\n\t\t\targ15);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13,\n\ttypename T14>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tT14,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 15))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tT14);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13,\n\t\tT14 arg14)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13,\n\t\t\targ14);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12,\n\ttypename T13>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tT13,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 14))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tT13);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12,\n\t\tT13 arg13)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12,\n\t\t\targ13);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11,\n\ttypename T12>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tT12,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 13))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tT12);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11,\n\t\tT12 arg12)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11,\n\t\t\targ12);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10,\n\ttypename T11>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tT11,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 12))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tT11);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10,\n\t\tT11 arg11)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10,\n\t\t\targ11);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9,\n\ttypename T10>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tT10,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 11))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tT10);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9,\n\t\tT10 arg10)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9,\n\t\t\targ10);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8,\n\ttypename T9>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tT9,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 10))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tT9);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8,\n\t\tT9 arg9)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8,\n\t\t\targ9);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7,\n\ttypename T8>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tT8,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 9))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tT8);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7,\n\t\tT8 arg8)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7,\n\t\t\targ8);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6,\n\ttypename T7>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tT7,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 8))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tT7);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6,\n\t\tT7 arg7)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6,\n\t\t\targ7);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5,\n\ttypename T6>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tT6,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 7))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tT6);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5,\n\t\tT6 arg6)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5,\n\t\t\targ6);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4,\n\ttypename T5>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tT5,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 6))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tT5);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4,\n\t\tT5 arg5)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4,\n\t\t\targ5);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3,\n\ttypename T4>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tT4,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 5))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tT4);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3,\n\t\tT4 arg4)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3,\n\t\t\targ4);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2,\n\ttypename T3>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tT3,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 4))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tT3);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2,\n\t\tT3 arg3)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t\targ3);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1,\n\ttypename T2>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tT2,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tT2,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 3))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1,\n\t\tT2);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1,\n\t\tT2 arg2)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0,\n\ttypename T1>\nstruct functionImplementation_\n<\tT0,\n\tT1,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tT1,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 2))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0,\n\t\tT1);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0,\n\t\tT1 arg1)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0,\n\t\t\targ1);\n\t}\n\n\n};\n\ntemplate<\n\ttypename T0>\nstruct functionImplementation_\n<\tT0,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType,\n\tNullType>\n{\n\ttypedef detail::KernelFunctorGlobal<\n\t\tT0,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType,\n\t\tNullType> FunctorType;\n\n    FunctorType functor_;\n\n    functionImplementation_(const FunctorType &functor) :\n        functor_(functor)\n    {\n    \n        #if (defined(_WIN32) && defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 1))\n        // Fail variadic expansion for dev11\n        static_assert(0, \"Visual Studio has a hard limit of argument count for a std::function expansion. Please define _VARIADIC_MAX to be 10. If you need more arguments than that VC12 and below cannot support it.\");\n        #endif\n            \n    }\n\n\t//! \\brief Return type of the functor\n\ttypedef Event result_type;\n\n\t//! \\brief Function signature of kernel functor with no event dependency.\n\ttypedef Event type_(\n\t\tconst EnqueueArgs&,\n\t\tT0);\n\n\tEvent operator()(\n\t\tconst EnqueueArgs& enqueueArgs,\n\t\tT0 arg0)\n\t{\n\t\treturn functor_(\n\t\t\tenqueueArgs,\n\t\t\targ0);\n\t}\n\n\n};\n\n\n\n\n\n} // namespace detail\n\n//----------------------------------------------------------------------------------------------\n\ntemplate <\n   typename T0,   typename T1 = detail::NullType,   typename T2 = detail::NullType,\n   typename T3 = detail::NullType,   typename T4 = detail::NullType,\n   typename T5 = detail::NullType,   typename T6 = detail::NullType,\n   typename T7 = detail::NullType,   typename T8 = detail::NullType,\n   typename T9 = detail::NullType,   typename T10 = detail::NullType,\n   typename T11 = detail::NullType,   typename T12 = detail::NullType,\n   typename T13 = detail::NullType,   typename T14 = detail::NullType,\n   typename T15 = detail::NullType,   typename T16 = detail::NullType,\n   typename T17 = detail::NullType,   typename T18 = detail::NullType,\n   typename T19 = detail::NullType,   typename T20 = detail::NullType,\n   typename T21 = detail::NullType,   typename T22 = detail::NullType,\n   typename T23 = detail::NullType,   typename T24 = detail::NullType,\n   typename T25 = detail::NullType,   typename T26 = detail::NullType,\n   typename T27 = detail::NullType,   typename T28 = detail::NullType,\n   typename T29 = detail::NullType,   typename T30 = detail::NullType,\n   typename T31 = detail::NullType\n>\nstruct make_kernel :\n    public detail::functionImplementation_<\n               T0,   T1,   T2,   T3,\n               T4,   T5,   T6,   T7,\n               T8,   T9,   T10,   T11,\n               T12,   T13,   T14,   T15,\n               T16,   T17,   T18,   T19,\n               T20,   T21,   T22,   T23,\n               T24,   T25,   T26,   T27,\n               T28,   T29,   T30,   T31\n    >\n{\npublic:\n\ttypedef detail::KernelFunctorGlobal<             \n\t\t       T0,   T1,   T2,   T3,\n               T4,   T5,   T6,   T7,\n               T8,   T9,   T10,   T11,\n               T12,   T13,   T14,   T15,\n               T16,   T17,   T18,   T19,\n               T20,   T21,   T22,   T23,\n               T24,   T25,   T26,   T27,\n               T28,   T29,   T30,   T31\n    > FunctorType;\n\n    make_kernel(\n        const Program& program,\n        const STRING_CLASS name,\n        cl_int * err = NULL) :\n           detail::functionImplementation_<\n                    T0,   T1,   T2,   T3,\n                       T4,   T5,   T6,   T7,\n                       T8,   T9,   T10,   T11,\n                       T12,   T13,   T14,   T15,\n                       T16,   T17,   T18,   T19,\n                       T20,   T21,   T22,   T23,\n                       T24,   T25,   T26,   T27,\n                       T28,   T29,   T30,   T31\n           >(\n            FunctorType(program, name, err)) \n    {}\n\n    make_kernel(\n        const Kernel kernel) :\n           detail::functionImplementation_<\n                    T0,   T1,   T2,   T3,\n                       T4,   T5,   T6,   T7,\n                       T8,   T9,   T10,   T11,\n                       T12,   T13,   T14,   T15,\n                       T16,   T17,   T18,   T19,\n                       T20,   T21,   T22,   T23,\n                       T24,   T25,   T26,   T27,\n                       T28,   T29,   T30,   T31\n           >(\n            FunctorType(kernel)) \n    {}    \n};\n\n\n//----------------------------------------------------------------------------------------------------------------------\n\n#undef __ERR_STR\n#if !defined(__CL_USER_OVERRIDE_ERROR_STRINGS)\n#undef __GET_DEVICE_INFO_ERR\n#undef __GET_PLATFORM_INFO_ERR\n#undef __GET_DEVICE_IDS_ERR\n#undef __GET_CONTEXT_INFO_ERR\n#undef __GET_EVENT_INFO_ERR\n#undef __GET_EVENT_PROFILE_INFO_ERR\n#undef __GET_MEM_OBJECT_INFO_ERR\n#undef __GET_IMAGE_INFO_ERR\n#undef __GET_SAMPLER_INFO_ERR\n#undef __GET_KERNEL_INFO_ERR\n#undef __GET_KERNEL_ARG_INFO_ERR\n#undef __GET_KERNEL_WORK_GROUP_INFO_ERR\n#undef __GET_PROGRAM_INFO_ERR\n#undef __GET_PROGRAM_BUILD_INFO_ERR\n#undef __GET_COMMAND_QUEUE_INFO_ERR\n\n#undef __CREATE_CONTEXT_ERR\n#undef __CREATE_CONTEXT_FROM_TYPE_ERR\n#undef __GET_SUPPORTED_IMAGE_FORMATS_ERR\n\n#undef __CREATE_BUFFER_ERR\n#undef __CREATE_SUBBUFFER_ERR\n#undef __CREATE_IMAGE2D_ERR\n#undef __CREATE_IMAGE3D_ERR\n#undef __CREATE_SAMPLER_ERR\n#undef __SET_MEM_OBJECT_DESTRUCTOR_CALLBACK_ERR\n\n#undef __CREATE_USER_EVENT_ERR\n#undef __SET_USER_EVENT_STATUS_ERR\n#undef __SET_EVENT_CALLBACK_ERR\n#undef __SET_PRINTF_CALLBACK_ERR\n\n#undef __WAIT_FOR_EVENTS_ERR\n\n#undef __CREATE_KERNEL_ERR\n#undef __SET_KERNEL_ARGS_ERR\n#undef __CREATE_PROGRAM_WITH_SOURCE_ERR\n#undef __CREATE_PROGRAM_WITH_BINARY_ERR\n#undef __CREATE_PROGRAM_WITH_BUILT_IN_KERNELS_ERR\n#undef __BUILD_PROGRAM_ERR\n#undef __CREATE_KERNELS_IN_PROGRAM_ERR\n\n#undef __CREATE_COMMAND_QUEUE_ERR\n#undef __SET_COMMAND_QUEUE_PROPERTY_ERR\n#undef __ENQUEUE_READ_BUFFER_ERR\n#undef __ENQUEUE_WRITE_BUFFER_ERR\n#undef __ENQUEUE_READ_BUFFER_RECT_ERR\n#undef __ENQUEUE_WRITE_BUFFER_RECT_ERR\n#undef __ENQEUE_COPY_BUFFER_ERR\n#undef __ENQEUE_COPY_BUFFER_RECT_ERR\n#undef __ENQUEUE_READ_IMAGE_ERR\n#undef __ENQUEUE_WRITE_IMAGE_ERR\n#undef __ENQUEUE_COPY_IMAGE_ERR\n#undef __ENQUEUE_COPY_IMAGE_TO_BUFFER_ERR\n#undef __ENQUEUE_COPY_BUFFER_TO_IMAGE_ERR\n#undef __ENQUEUE_MAP_BUFFER_ERR\n#undef __ENQUEUE_MAP_IMAGE_ERR\n#undef __ENQUEUE_UNMAP_MEM_OBJECT_ERR\n#undef __ENQUEUE_NDRANGE_KERNEL_ERR\n#undef __ENQUEUE_TASK_ERR\n#undef __ENQUEUE_NATIVE_KERNEL\n\n#undef __CL_EXPLICIT_CONSTRUCTORS\n\n#undef __UNLOAD_COMPILER_ERR\n#endif //__CL_USER_OVERRIDE_ERROR_STRINGS\n\n#undef __CL_FUNCTION_TYPE\n\n// Extensions\n/**\n * Deprecated APIs for 1.2\n */\n#if defined(CL_VERSION_1_1)\n#undef __INIT_CL_EXT_FCN_PTR\n#endif // #if defined(CL_VERSION_1_1)\n#undef __CREATE_SUB_DEVICES\n\n#if defined(USE_CL_DEVICE_FISSION)\n#undef __PARAM_NAME_DEVICE_FISSION\n#endif // USE_CL_DEVICE_FISSION\n\n#undef __DEFAULT_NOT_INITIALIZED \n#undef __DEFAULT_BEING_INITIALIZED \n#undef __DEFAULT_INITIALIZED\n\n} // namespace cl\n\n#ifdef _WIN32\n#pragma pop_macro(\"max\")\n#endif // _WIN32\n\n#endif // CL_HPP_\n"
  },
  {
    "path": "caffe2/mobile/contrib/libopencl-stub/include/CL/cl_ext.h",
    "content": "/*******************************************************************************\n * Copyright (c) 2008 - 2012 The Khronos Group Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and/or associated documentation files (the\n * \"Materials\"), to deal in the Materials without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Materials, and to\n * permit persons to whom the Materials are furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Materials.\n *\n * THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.\n ******************************************************************************/\n\n/* $Revision: 11928 $ on $Date: 2010-07-13 09:04:56 -0700 (Tue, 13 Jul 2010) $ */\n\n/* cl_ext.h contains OpenCL extensions which don't have external */\n/* (OpenGL, D3D) dependencies.                                   */\n\n#ifndef __CL_EXT_H\n#define __CL_EXT_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifdef __APPLE__\n\t#include <OpenCL/cl.h>\n    #include <AvailabilityMacros.h>\n#else\n\t#include <CL/cl.h>\n#endif\n\n/* cl_khr_fp16 extension - no extension #define since it has no functions  */\n#define CL_DEVICE_HALF_FP_CONFIG                    0x1033\n\n/* Memory object destruction\n *\n * Apple extension for use to manage externally allocated buffers used with cl_mem objects with CL_MEM_USE_HOST_PTR\n *\n * Registers a user callback function that will be called when the memory object is deleted and its resources \n * freed. Each call to clSetMemObjectCallbackFn registers the specified user callback function on a callback \n * stack associated with memobj. The registered user callback functions are called in the reverse order in \n * which they were registered. The user callback functions are called and then the memory object is deleted \n * and its resources freed. This provides a mechanism for the application (and libraries) using memobj to be \n * notified when the memory referenced by host_ptr, specified when the memory object is created and used as \n * the storage bits for the memory object, can be reused or freed.\n *\n * The application may not call CL api's with the cl_mem object passed to the pfn_notify.\n *\n * Please check for the \"cl_APPLE_SetMemObjectDestructor\" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS)\n * before using.\n */\n#define cl_APPLE_SetMemObjectDestructor 1\ncl_int\tCL_API_ENTRY clSetMemObjectDestructorAPPLE(  cl_mem /* memobj */, \n                                        void (* /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), \n                                        void * /*user_data */ )             CL_EXT_SUFFIX__VERSION_1_0;  \n\n\n/* Context Logging Functions\n *\n * The next three convenience functions are intended to be used as the pfn_notify parameter to clCreateContext().\n * Please check for the \"cl_APPLE_ContextLoggingFunctions\" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS)\n * before using.\n *\n * clLogMessagesToSystemLog fowards on all log messages to the Apple System Logger \n */\n#define cl_APPLE_ContextLoggingFunctions 1\nextern void CL_API_ENTRY clLogMessagesToSystemLogAPPLE(  const char * /* errstr */, \n                                            const void * /* private_info */, \n                                            size_t       /* cb */, \n                                            void *       /* user_data */ )  CL_EXT_SUFFIX__VERSION_1_0;\n\n/* clLogMessagesToStdout sends all log messages to the file descriptor stdout */\nextern void CL_API_ENTRY clLogMessagesToStdoutAPPLE(   const char * /* errstr */, \n                                          const void * /* private_info */, \n                                          size_t       /* cb */, \n                                          void *       /* user_data */ )    CL_EXT_SUFFIX__VERSION_1_0;\n\n/* clLogMessagesToStderr sends all log messages to the file descriptor stderr */\nextern void CL_API_ENTRY clLogMessagesToStderrAPPLE(   const char * /* errstr */, \n                                          const void * /* private_info */, \n                                          size_t       /* cb */, \n                                          void *       /* user_data */ )    CL_EXT_SUFFIX__VERSION_1_0;\n\n\n/************************ \n* cl_khr_icd extension *                                                  \n************************/\n#define cl_khr_icd 1\n\n/* cl_platform_info                                                        */\n#define CL_PLATFORM_ICD_SUFFIX_KHR                  0x0920\n\n/* Additional Error Codes                                                  */\n#define CL_PLATFORM_NOT_FOUND_KHR                   -1001\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclIcdGetPlatformIDsKHR(cl_uint          /* num_entries */,\n                       cl_platform_id * /* platforms */,\n                       cl_uint *        /* num_platforms */);\n\ntypedef CL_API_ENTRY cl_int (CL_API_CALL *clIcdGetPlatformIDsKHR_fn)(\n    cl_uint          /* num_entries */,\n    cl_platform_id * /* platforms */,\n    cl_uint *        /* num_platforms */);\n\n\n/* Extension: cl_khr_image2D_buffer\n *\n * This extension allows a 2D image to be created from a cl_mem buffer without a copy.\n * The type associated with a 2D image created from a buffer in an OpenCL program is image2d_t.\n * Both the sampler and sampler-less read_image built-in functions are supported for 2D images\n * and 2D images created from a buffer.  Similarly, the write_image built-ins are also supported\n * for 2D images created from a buffer.\n *\n * When the 2D image from buffer is created, the client must specify the width,\n * height, image format (i.e. channel order and channel data type) and optionally the row pitch\n *\n * The pitch specified must be a multiple of CL_DEVICE_IMAGE_PITCH_ALIGNMENT pixels.\n * The base address of the buffer must be aligned to CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT pixels.\n */\n    \n/*************************************\n * cl_khr_initalize_memory extension *\n *************************************/\n    \n#define CL_CONTEXT_MEMORY_INITIALIZE_KHR            0x200E\n    \n    \n/**************************************\n * cl_khr_terminate_context extension *\n **************************************/\n    \n#define CL_DEVICE_TERMINATE_CAPABILITY_KHR          0x200F\n#define CL_CONTEXT_TERMINATE_KHR                    0x2010\n\n#define cl_khr_terminate_context 1\nextern CL_API_ENTRY cl_int CL_API_CALL clTerminateContextKHR(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2;\n\ntypedef CL_API_ENTRY cl_int (CL_API_CALL *clTerminateContextKHR_fn)(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2;\n    \n    \n/*\n * Extension: cl_khr_spir\n *\n * This extension adds support to create an OpenCL program object from a \n * Standard Portable Intermediate Representation (SPIR) instance\n */\n\n/******************************************\n* cl_nv_device_attribute_query extension *\n******************************************/\n/* cl_nv_device_attribute_query extension - no extension #define since it has no functions */\n#define CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV       0x4000\n#define CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV       0x4001\n#define CL_DEVICE_REGISTERS_PER_BLOCK_NV            0x4002\n#define CL_DEVICE_WARP_SIZE_NV                      0x4003\n#define CL_DEVICE_GPU_OVERLAP_NV                    0x4004\n#define CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV            0x4005\n#define CL_DEVICE_INTEGRATED_MEMORY_NV              0x4006\n\n\n/*********************************\n* cl_amd_device_attribute_query *\n*********************************/\n#define CL_DEVICE_PROFILING_TIMER_OFFSET_AMD        0x4036\n\n#ifdef CL_VERSION_1_1\n   /***********************************\n    * cl_ext_device_fission extension *\n    ***********************************/\n    #define cl_ext_device_fission   1\n    \n    extern CL_API_ENTRY cl_int CL_API_CALL\n    clReleaseDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; \n    \n    typedef CL_API_ENTRY cl_int \n    (CL_API_CALL *clReleaseDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1;\n\n    extern CL_API_ENTRY cl_int CL_API_CALL\n    clRetainDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; \n    \n    typedef CL_API_ENTRY cl_int \n    (CL_API_CALL *clRetainDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1;\n\n    typedef cl_ulong  cl_device_partition_property_ext;\n    extern CL_API_ENTRY cl_int CL_API_CALL\n    clCreateSubDevicesEXT(  cl_device_id /*in_device*/,\n                            const cl_device_partition_property_ext * /* properties */,\n                            cl_uint /*num_entries*/,\n                            cl_device_id * /*out_devices*/,\n                            cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1;\n\n    typedef CL_API_ENTRY cl_int \n    ( CL_API_CALL * clCreateSubDevicesEXT_fn)(  cl_device_id /*in_device*/,\n                                                const cl_device_partition_property_ext * /* properties */,\n                                                cl_uint /*num_entries*/,\n                                                cl_device_id * /*out_devices*/,\n                                                cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1;\n\n    /* cl_device_partition_property_ext */\n    #define CL_DEVICE_PARTITION_EQUALLY_EXT             0x4050\n    #define CL_DEVICE_PARTITION_BY_COUNTS_EXT           0x4051\n    #define CL_DEVICE_PARTITION_BY_NAMES_EXT            0x4052\n    #define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN_EXT  0x4053\n    \n    /* clDeviceGetInfo selectors */\n    #define CL_DEVICE_PARENT_DEVICE_EXT                 0x4054\n    #define CL_DEVICE_PARTITION_TYPES_EXT               0x4055\n    #define CL_DEVICE_AFFINITY_DOMAINS_EXT              0x4056\n    #define CL_DEVICE_REFERENCE_COUNT_EXT               0x4057\n    #define CL_DEVICE_PARTITION_STYLE_EXT               0x4058\n    \n    /* error codes */\n    #define CL_DEVICE_PARTITION_FAILED_EXT              -1057\n    #define CL_INVALID_PARTITION_COUNT_EXT              -1058\n    #define CL_INVALID_PARTITION_NAME_EXT               -1059\n    \n    /* CL_AFFINITY_DOMAINs */\n    #define CL_AFFINITY_DOMAIN_L1_CACHE_EXT             0x1\n    #define CL_AFFINITY_DOMAIN_L2_CACHE_EXT             0x2\n    #define CL_AFFINITY_DOMAIN_L3_CACHE_EXT             0x3\n    #define CL_AFFINITY_DOMAIN_L4_CACHE_EXT             0x4\n    #define CL_AFFINITY_DOMAIN_NUMA_EXT                 0x10\n    #define CL_AFFINITY_DOMAIN_NEXT_FISSIONABLE_EXT     0x100\n    \n    /* cl_device_partition_property_ext list terminators */\n    #define CL_PROPERTIES_LIST_END_EXT                  ((cl_device_partition_property_ext) 0)\n    #define CL_PARTITION_BY_COUNTS_LIST_END_EXT         ((cl_device_partition_property_ext) 0)\n    #define CL_PARTITION_BY_NAMES_LIST_END_EXT          ((cl_device_partition_property_ext) 0 - 1)\n\n\n\n#endif /* CL_VERSION_1_1 */\n\n#ifdef __cplusplus\n}\n#endif\n\n\n#endif /* __CL_EXT_H */\n"
  },
  {
    "path": "caffe2/mobile/contrib/libopencl-stub/include/CL/cl_gl.h",
    "content": "/**********************************************************************************\n * Copyright (c) 2008 - 2012 The Khronos Group Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and/or associated documentation files (the\n * \"Materials\"), to deal in the Materials without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Materials, and to\n * permit persons to whom the Materials are furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Materials.\n *\n * THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.\n **********************************************************************************/\n\n#ifndef __OPENCL_CL_GL_H\n#define __OPENCL_CL_GL_H\n\n#ifdef __APPLE__\n#include <OpenCL/cl.h>\n#else\n#include <CL/cl.h>\n#endif\t\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef cl_uint     cl_gl_object_type;\ntypedef cl_uint     cl_gl_texture_info;\ntypedef cl_uint     cl_gl_platform_info;\ntypedef struct __GLsync *cl_GLsync;\n\n/* cl_gl_object_type = 0x2000 - 0x200F enum values are currently taken           */\n#define CL_GL_OBJECT_BUFFER                     0x2000\n#define CL_GL_OBJECT_TEXTURE2D                  0x2001\n#define CL_GL_OBJECT_TEXTURE3D                  0x2002\n#define CL_GL_OBJECT_RENDERBUFFER               0x2003\n#define CL_GL_OBJECT_TEXTURE2D_ARRAY            0x200E\n#define CL_GL_OBJECT_TEXTURE1D                  0x200F\n#define CL_GL_OBJECT_TEXTURE1D_ARRAY            0x2010\n#define CL_GL_OBJECT_TEXTURE_BUFFER             0x2011\n\n/* cl_gl_texture_info           */\n#define CL_GL_TEXTURE_TARGET                    0x2004\n#define CL_GL_MIPMAP_LEVEL                      0x2005\n#define CL_GL_NUM_SAMPLES                       0x2012\n\n\nextern CL_API_ENTRY cl_mem CL_API_CALL\nclCreateFromGLBuffer(cl_context     /* context */,\n                     cl_mem_flags   /* flags */,\n                     cl_GLuint      /* bufobj */,\n                     int *          /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_mem CL_API_CALL\nclCreateFromGLTexture(cl_context      /* context */,\n                      cl_mem_flags    /* flags */,\n                      cl_GLenum       /* target */,\n                      cl_GLint        /* miplevel */,\n                      cl_GLuint       /* texture */,\n                      cl_int *        /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2;\n    \nextern CL_API_ENTRY cl_mem CL_API_CALL\nclCreateFromGLRenderbuffer(cl_context   /* context */,\n                           cl_mem_flags /* flags */,\n                           cl_GLuint    /* renderbuffer */,\n                           cl_int *     /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetGLObjectInfo(cl_mem                /* memobj */,\n                  cl_gl_object_type *   /* gl_object_type */,\n                  cl_GLuint *           /* gl_object_name */) CL_API_SUFFIX__VERSION_1_0;\n                  \nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetGLTextureInfo(cl_mem               /* memobj */,\n                   cl_gl_texture_info   /* param_name */,\n                   size_t               /* param_value_size */,\n                   void *               /* param_value */,\n                   size_t *             /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueAcquireGLObjects(cl_command_queue      /* command_queue */,\n                          cl_uint               /* num_objects */,\n                          const cl_mem *        /* mem_objects */,\n                          cl_uint               /* num_events_in_wait_list */,\n                          const cl_event *      /* event_wait_list */,\n                          cl_event *            /* event */) CL_API_SUFFIX__VERSION_1_0;\n\nextern CL_API_ENTRY cl_int CL_API_CALL\nclEnqueueReleaseGLObjects(cl_command_queue      /* command_queue */,\n                          cl_uint               /* num_objects */,\n                          const cl_mem *        /* mem_objects */,\n                          cl_uint               /* num_events_in_wait_list */,\n                          const cl_event *      /* event_wait_list */,\n                          cl_event *            /* event */) CL_API_SUFFIX__VERSION_1_0;\n\n\n// Deprecated OpenCL 1.1 APIs\nextern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL\nclCreateFromGLTexture2D(cl_context      /* context */,\n                        cl_mem_flags    /* flags */,\n                        cl_GLenum       /* target */,\n                        cl_GLint        /* miplevel */,\n                        cl_GLuint       /* texture */,\n                        cl_int *        /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED;\n    \nextern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL\nclCreateFromGLTexture3D(cl_context      /* context */,\n                        cl_mem_flags    /* flags */,\n                        cl_GLenum       /* target */,\n                        cl_GLint        /* miplevel */,\n                        cl_GLuint       /* texture */,\n                        cl_int *        /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED;\n    \n/* cl_khr_gl_sharing extension  */\n    \n#define cl_khr_gl_sharing 1\n    \ntypedef cl_uint     cl_gl_context_info;\n    \n/* Additional Error Codes  */\n#define CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR  -1000\n    \n/* cl_gl_context_info  */\n#define CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR    0x2006\n#define CL_DEVICES_FOR_GL_CONTEXT_KHR           0x2007\n    \n/* Additional cl_context_properties  */\n#define CL_GL_CONTEXT_KHR                       0x2008\n#define CL_EGL_DISPLAY_KHR                      0x2009\n#define CL_GLX_DISPLAY_KHR                      0x200A\n#define CL_WGL_HDC_KHR                          0x200B\n#define CL_CGL_SHAREGROUP_KHR                   0x200C\n    \nextern CL_API_ENTRY cl_int CL_API_CALL\nclGetGLContextInfoKHR(const cl_context_properties * /* properties */,\n                      cl_gl_context_info            /* param_name */,\n                      size_t                        /* param_value_size */,\n                      void *                        /* param_value */,\n                      size_t *                      /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0;\n    \ntypedef CL_API_ENTRY cl_int (CL_API_CALL *clGetGLContextInfoKHR_fn)(\n    const cl_context_properties * properties,\n    cl_gl_context_info            param_name,\n    size_t                        param_value_size,\n    void *                        param_value,\n    size_t *                      param_value_size_ret);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif  /* __OPENCL_CL_GL_H */\n"
  },
  {
    "path": "caffe2/mobile/contrib/libopencl-stub/include/CL/cl_gl_ext.h",
    "content": "/**********************************************************************************\n * Copyright (c) 2008-2012 The Khronos Group Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and/or associated documentation files (the\n * \"Materials\"), to deal in the Materials without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Materials, and to\n * permit persons to whom the Materials are furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Materials.\n *\n * THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.\n **********************************************************************************/\n\n/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */\n\n/* cl_gl_ext.h contains vendor (non-KHR) OpenCL extensions which have           */\n/* OpenGL dependencies.                                                         */\n\n#ifndef __OPENCL_CL_GL_EXT_H\n#define __OPENCL_CL_GL_EXT_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifdef __APPLE__\n    #include <OpenCL/cl_gl.h>\n#else\n    #include <CL/cl_gl.h>\n#endif\n\n/*\n * For each extension, follow this template\n *  cl_VEN_extname extension  */\n/* #define cl_VEN_extname 1\n * ... define new types, if any\n * ... define new tokens, if any\n * ... define new APIs, if any\n *\n *  If you need GLtypes here, mirror them with a cl_GLtype, rather than including a GL header\n *  This allows us to avoid having to decide whether to include GL headers or GLES here.\n */\n\n/* \n *  cl_khr_gl_event  extension\n *  See section 9.9 in the OpenCL 1.1 spec for more information\n */\n#define CL_COMMAND_GL_FENCE_SYNC_OBJECT_KHR     0x200D\n\nextern CL_API_ENTRY cl_event CL_API_CALL\nclCreateEventFromGLsyncKHR(cl_context           /* context */,\n                           cl_GLsync            /* cl_GLsync */,\n                           cl_int *             /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1;\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\t/* __OPENCL_CL_GL_EXT_H  */\n"
  },
  {
    "path": "caffe2/mobile/contrib/libopencl-stub/include/CL/cl_platform.h",
    "content": "/**********************************************************************************\n * Copyright (c) 2008-2012 The Khronos Group Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and/or associated documentation files (the\n * \"Materials\"), to deal in the Materials without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Materials, and to\n * permit persons to whom the Materials are furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Materials.\n *\n * THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.\n **********************************************************************************/\n\n/* $Revision: 11803 $ on $Date: 2010-06-25 10:02:12 -0700 (Fri, 25 Jun 2010) $ */\n\n#ifndef __CL_PLATFORM_H\n#define __CL_PLATFORM_H\n\n#ifdef __APPLE__\n    /* Contains #defines for AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER below */\n    #include <AvailabilityMacros.h>\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#if defined(_WIN32)\n    #define CL_API_ENTRY\n    #define CL_API_CALL     __stdcall\n    #define CL_CALLBACK     __stdcall\n#else\n    #define CL_API_ENTRY\n    #define CL_API_CALL\n    #define CL_CALLBACK\n#endif\n\n#ifdef __APPLE__\n    #define CL_EXTENSION_WEAK_LINK       __attribute__((weak_import))\n    #define CL_API_SUFFIX__VERSION_1_0                  AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER\n    #define CL_EXT_SUFFIX__VERSION_1_0                  CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER\n    #define CL_API_SUFFIX__VERSION_1_1                  AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER\n    #define GCL_API_SUFFIX__VERSION_1_1                 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER\n    #define CL_EXT_SUFFIX__VERSION_1_1                  CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER\n    #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED       CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_7\n    \n    #ifdef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER\n        #define CL_API_SUFFIX__VERSION_1_2              AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER\n        #define GCL_API_SUFFIX__VERSION_1_2             AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER\n        #define CL_EXT_SUFFIX__VERSION_1_2              CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER\n        #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED\n        #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED   CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_8\n    #else\n        #warning  This path should never happen outside of internal operating system development.  AvailabilityMacros do not function correctly here!\n        #define CL_API_SUFFIX__VERSION_1_2              AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER\n        #define GCL_API_SUFFIX__VERSION_1_2             AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER\n        #define CL_EXT_SUFFIX__VERSION_1_2              CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER\n        #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED   CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER\n    #endif\n#else\n    #define CL_EXTENSION_WEAK_LINK  \n    #define CL_API_SUFFIX__VERSION_1_0\n    #define CL_EXT_SUFFIX__VERSION_1_0\n    #define CL_API_SUFFIX__VERSION_1_1\n    #define CL_EXT_SUFFIX__VERSION_1_1\n    #define CL_API_SUFFIX__VERSION_1_2\n    #define CL_EXT_SUFFIX__VERSION_1_2\n    \n    #ifdef __GNUC__\n        #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS\n            #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED\n            #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED    \n        #else\n            #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED __attribute__((deprecated))\n            #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED    \n        #endif\n    \n        #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS\n            #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED    \n            #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED    \n        #else\n            #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED __attribute__((deprecated))\n            #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED    \n        #endif\n    #elif _WIN32\n        #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS\n            #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED    \n            #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED    \n        #else\n            #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED \n            #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED __declspec(deprecated)     \n        #endif\n    \n        #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS\n            #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED\n            #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED    \n        #else\n            #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED \n            #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED __declspec(deprecated)     \n        #endif\n    #else\n        #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED\n        #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED\n    \n        #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED\n        #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED\n    #endif\n#endif\n\n#if (defined (_WIN32) && defined(_MSC_VER))\n\n/* scalar types  */\ntypedef signed   __int8         cl_char;\ntypedef unsigned __int8         cl_uchar;\ntypedef signed   __int16        cl_short;\ntypedef unsigned __int16        cl_ushort;\ntypedef signed   __int32        cl_int;\ntypedef unsigned __int32        cl_uint;\ntypedef signed   __int64        cl_long;\ntypedef unsigned __int64        cl_ulong;\n\ntypedef unsigned __int16        cl_half;\ntypedef float                   cl_float;\ntypedef double                  cl_double;\n\n/* Macro names and corresponding values defined by OpenCL */\n#define CL_CHAR_BIT         8\n#define CL_SCHAR_MAX        127\n#define CL_SCHAR_MIN        (-127-1)\n#define CL_CHAR_MAX         CL_SCHAR_MAX\n#define CL_CHAR_MIN         CL_SCHAR_MIN\n#define CL_UCHAR_MAX        255\n#define CL_SHRT_MAX         32767\n#define CL_SHRT_MIN         (-32767-1)\n#define CL_USHRT_MAX        65535\n#define CL_INT_MAX          2147483647\n#define CL_INT_MIN          (-2147483647-1)\n#define CL_UINT_MAX         0xffffffffU\n#define CL_LONG_MAX         ((cl_long) 0x7FFFFFFFFFFFFFFFLL)\n#define CL_LONG_MIN         ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL)\n#define CL_ULONG_MAX        ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL)\n\n#define CL_FLT_DIG          6\n#define CL_FLT_MANT_DIG     24\n#define CL_FLT_MAX_10_EXP   +38\n#define CL_FLT_MAX_EXP      +128\n#define CL_FLT_MIN_10_EXP   -37\n#define CL_FLT_MIN_EXP      -125\n#define CL_FLT_RADIX        2\n#define CL_FLT_MAX          340282346638528859811704183484516925440.0f\n#define CL_FLT_MIN          1.175494350822287507969e-38f\n#define CL_FLT_EPSILON      0x1.0p-23f\n\n#define CL_DBL_DIG          15\n#define CL_DBL_MANT_DIG     53\n#define CL_DBL_MAX_10_EXP   +308\n#define CL_DBL_MAX_EXP      +1024\n#define CL_DBL_MIN_10_EXP   -307\n#define CL_DBL_MIN_EXP      -1021\n#define CL_DBL_RADIX        2\n#define CL_DBL_MAX          179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0\n#define CL_DBL_MIN          2.225073858507201383090e-308\n#define CL_DBL_EPSILON      2.220446049250313080847e-16\n\n#define  CL_M_E             2.718281828459045090796\n#define  CL_M_LOG2E         1.442695040888963387005\n#define  CL_M_LOG10E        0.434294481903251816668\n#define  CL_M_LN2           0.693147180559945286227\n#define  CL_M_LN10          2.302585092994045901094\n#define  CL_M_PI            3.141592653589793115998\n#define  CL_M_PI_2          1.570796326794896557999\n#define  CL_M_PI_4          0.785398163397448278999\n#define  CL_M_1_PI          0.318309886183790691216\n#define  CL_M_2_PI          0.636619772367581382433\n#define  CL_M_2_SQRTPI      1.128379167095512558561\n#define  CL_M_SQRT2         1.414213562373095145475\n#define  CL_M_SQRT1_2       0.707106781186547572737\n\n#define  CL_M_E_F           2.71828174591064f\n#define  CL_M_LOG2E_F       1.44269502162933f\n#define  CL_M_LOG10E_F      0.43429449200630f\n#define  CL_M_LN2_F         0.69314718246460f\n#define  CL_M_LN10_F        2.30258512496948f\n#define  CL_M_PI_F          3.14159274101257f\n#define  CL_M_PI_2_F        1.57079637050629f\n#define  CL_M_PI_4_F        0.78539818525314f\n#define  CL_M_1_PI_F        0.31830987334251f\n#define  CL_M_2_PI_F        0.63661974668503f\n#define  CL_M_2_SQRTPI_F    1.12837922573090f\n#define  CL_M_SQRT2_F       1.41421353816986f\n#define  CL_M_SQRT1_2_F     0.70710676908493f\n\n#define CL_NAN              (CL_INFINITY - CL_INFINITY)\n#define CL_HUGE_VALF        ((cl_float) 1e50)\n#define CL_HUGE_VAL         ((cl_double) 1e500)\n#define CL_MAXFLOAT         CL_FLT_MAX\n#define CL_INFINITY         CL_HUGE_VALF\n\n#else\n\n#include <stdint.h>\n\n/* scalar types  */\ntypedef int8_t          cl_char;\ntypedef uint8_t         cl_uchar;\ntypedef int16_t         cl_short    __attribute__((aligned(2)));\ntypedef uint16_t        cl_ushort   __attribute__((aligned(2)));\ntypedef int32_t         cl_int      __attribute__((aligned(4)));\ntypedef uint32_t        cl_uint     __attribute__((aligned(4)));\ntypedef int64_t         cl_long     __attribute__((aligned(8)));\ntypedef uint64_t        cl_ulong    __attribute__((aligned(8)));\n\ntypedef uint16_t        cl_half     __attribute__((aligned(2)));\ntypedef float           cl_float    __attribute__((aligned(4)));\ntypedef double          cl_double   __attribute__((aligned(8)));\n\n/* Macro names and corresponding values defined by OpenCL */\n#define CL_CHAR_BIT         8\n#define CL_SCHAR_MAX        127\n#define CL_SCHAR_MIN        (-127-1)\n#define CL_CHAR_MAX         CL_SCHAR_MAX\n#define CL_CHAR_MIN         CL_SCHAR_MIN\n#define CL_UCHAR_MAX        255\n#define CL_SHRT_MAX         32767\n#define CL_SHRT_MIN         (-32767-1)\n#define CL_USHRT_MAX        65535\n#define CL_INT_MAX          2147483647\n#define CL_INT_MIN          (-2147483647-1)\n#define CL_UINT_MAX         0xffffffffU\n#define CL_LONG_MAX         ((cl_long) 0x7FFFFFFFFFFFFFFFLL)\n#define CL_LONG_MIN         ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL)\n#define CL_ULONG_MAX        ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL)\n\n#define CL_FLT_DIG          6\n#define CL_FLT_MANT_DIG     24\n#define CL_FLT_MAX_10_EXP   +38\n#define CL_FLT_MAX_EXP      +128\n#define CL_FLT_MIN_10_EXP   -37\n#define CL_FLT_MIN_EXP      -125\n#define CL_FLT_RADIX        2\n#define CL_FLT_MAX          0x1.fffffep127f\n#define CL_FLT_MIN          0x1.0p-126f\n#define CL_FLT_EPSILON      0x1.0p-23f\n\n#define CL_DBL_DIG          15\n#define CL_DBL_MANT_DIG     53\n#define CL_DBL_MAX_10_EXP   +308\n#define CL_DBL_MAX_EXP      +1024\n#define CL_DBL_MIN_10_EXP   -307\n#define CL_DBL_MIN_EXP      -1021\n#define CL_DBL_RADIX        2\n#define CL_DBL_MAX          0x1.fffffffffffffp1023\n#define CL_DBL_MIN          0x1.0p-1022\n#define CL_DBL_EPSILON      0x1.0p-52\n\n#define  CL_M_E             2.718281828459045090796\n#define  CL_M_LOG2E         1.442695040888963387005\n#define  CL_M_LOG10E        0.434294481903251816668\n#define  CL_M_LN2           0.693147180559945286227\n#define  CL_M_LN10          2.302585092994045901094\n#define  CL_M_PI            3.141592653589793115998\n#define  CL_M_PI_2          1.570796326794896557999\n#define  CL_M_PI_4          0.785398163397448278999\n#define  CL_M_1_PI          0.318309886183790691216\n#define  CL_M_2_PI          0.636619772367581382433\n#define  CL_M_2_SQRTPI      1.128379167095512558561\n#define  CL_M_SQRT2         1.414213562373095145475\n#define  CL_M_SQRT1_2       0.707106781186547572737\n\n#define  CL_M_E_F           2.71828174591064f\n#define  CL_M_LOG2E_F       1.44269502162933f\n#define  CL_M_LOG10E_F      0.43429449200630f\n#define  CL_M_LN2_F         0.69314718246460f\n#define  CL_M_LN10_F        2.30258512496948f\n#define  CL_M_PI_F          3.14159274101257f\n#define  CL_M_PI_2_F        1.57079637050629f\n#define  CL_M_PI_4_F        0.78539818525314f\n#define  CL_M_1_PI_F        0.31830987334251f\n#define  CL_M_2_PI_F        0.63661974668503f\n#define  CL_M_2_SQRTPI_F    1.12837922573090f\n#define  CL_M_SQRT2_F       1.41421353816986f\n#define  CL_M_SQRT1_2_F     0.70710676908493f\n\n#if defined( __GNUC__ )\n   #define CL_HUGE_VALF     __builtin_huge_valf()\n   #define CL_HUGE_VAL      __builtin_huge_val()\n   #define CL_NAN           __builtin_nanf( \"\" )\n#else\n   #define CL_HUGE_VALF     ((cl_float) 1e50)\n   #define CL_HUGE_VAL      ((cl_double) 1e500)\n   float nanf( const char * );\n   #define CL_NAN           nanf( \"\" )  \n#endif\n#define CL_MAXFLOAT         CL_FLT_MAX\n#define CL_INFINITY         CL_HUGE_VALF\n\n#endif\n\n#include <stddef.h>\n\n/* Mirror types to GL types. Mirror types allow us to avoid deciding which 87s to load based on whether we are using GL or GLES here. */\ntypedef unsigned int cl_GLuint;\ntypedef int          cl_GLint;\ntypedef unsigned int cl_GLenum;\n\n/*\n * Vector types \n *\n *  Note:   OpenCL requires that all types be naturally aligned. \n *          This means that vector types must be naturally aligned.\n *          For example, a vector of four floats must be aligned to\n *          a 16 byte boundary (calculated as 4 * the natural 4-byte \n *          alignment of the float).  The alignment qualifiers here\n *          will only function properly if your compiler supports them\n *          and if you don't actively work to defeat them.  For example,\n *          in order for a cl_float4 to be 16 byte aligned in a struct,\n *          the start of the struct must itself be 16-byte aligned. \n *\n *          Maintaining proper alignment is the user's responsibility.\n */\n\n/* Define basic vector types */\n#if defined( __VEC__ )\n   #include <altivec.h>   /* may be omitted depending on compiler. AltiVec spec provides no way to detect whether the header is required. */\n   typedef vector unsigned char     __cl_uchar16;\n   typedef vector signed char       __cl_char16;\n   typedef vector unsigned short    __cl_ushort8;\n   typedef vector signed short      __cl_short8;\n   typedef vector unsigned int      __cl_uint4;\n   typedef vector signed int        __cl_int4;\n   typedef vector float             __cl_float4;\n   #define  __CL_UCHAR16__  1\n   #define  __CL_CHAR16__   1\n   #define  __CL_USHORT8__  1\n   #define  __CL_SHORT8__   1\n   #define  __CL_UINT4__    1\n   #define  __CL_INT4__     1\n   #define  __CL_FLOAT4__   1\n#endif\n\n#if defined( __SSE__ )\n    #if defined( __MINGW64__ )\n        #include <intrin.h>\n    #else\n        #include <xmmintrin.h>\n    #endif\n    #if defined( __GNUC__ )\n        typedef float __cl_float4   __attribute__((vector_size(16)));\n    #else\n        typedef __m128 __cl_float4;\n    #endif\n    #define __CL_FLOAT4__   1\n#endif\n\n#if defined( __SSE2__ )\n    #if defined( __MINGW64__ )\n        #include <intrin.h>\n    #else\n        #include <emmintrin.h>\n    #endif\n    #if defined( __GNUC__ )\n        typedef cl_uchar    __cl_uchar16    __attribute__((vector_size(16)));\n        typedef cl_char     __cl_char16     __attribute__((vector_size(16)));\n        typedef cl_ushort   __cl_ushort8    __attribute__((vector_size(16)));\n        typedef cl_short    __cl_short8     __attribute__((vector_size(16)));\n        typedef cl_uint     __cl_uint4      __attribute__((vector_size(16)));\n        typedef cl_int      __cl_int4       __attribute__((vector_size(16)));\n        typedef cl_ulong    __cl_ulong2     __attribute__((vector_size(16)));\n        typedef cl_long     __cl_long2      __attribute__((vector_size(16)));\n        typedef cl_double   __cl_double2    __attribute__((vector_size(16)));\n    #else\n        typedef __m128i __cl_uchar16;\n        typedef __m128i __cl_char16;\n        typedef __m128i __cl_ushort8;\n        typedef __m128i __cl_short8;\n        typedef __m128i __cl_uint4;\n        typedef __m128i __cl_int4;\n        typedef __m128i __cl_ulong2;\n        typedef __m128i __cl_long2;\n        typedef __m128d __cl_double2;\n    #endif\n    #define __CL_UCHAR16__  1\n    #define __CL_CHAR16__   1\n    #define __CL_USHORT8__  1\n    #define __CL_SHORT8__   1\n    #define __CL_INT4__     1\n    #define __CL_UINT4__    1\n    #define __CL_ULONG2__   1\n    #define __CL_LONG2__    1\n    #define __CL_DOUBLE2__  1\n#endif\n\n#if defined( __MMX__ )\n    #include <mmintrin.h>\n    #if defined( __GNUC__ )\n        typedef cl_uchar    __cl_uchar8     __attribute__((vector_size(8)));\n        typedef cl_char     __cl_char8      __attribute__((vector_size(8)));\n        typedef cl_ushort   __cl_ushort4    __attribute__((vector_size(8)));\n        typedef cl_short    __cl_short4     __attribute__((vector_size(8)));\n        typedef cl_uint     __cl_uint2      __attribute__((vector_size(8)));\n        typedef cl_int      __cl_int2       __attribute__((vector_size(8)));\n        typedef cl_ulong    __cl_ulong1     __attribute__((vector_size(8)));\n        typedef cl_long     __cl_long1      __attribute__((vector_size(8)));\n        typedef cl_float    __cl_float2     __attribute__((vector_size(8)));\n    #else\n        typedef __m64       __cl_uchar8;\n        typedef __m64       __cl_char8;\n        typedef __m64       __cl_ushort4;\n        typedef __m64       __cl_short4;\n        typedef __m64       __cl_uint2;\n        typedef __m64       __cl_int2;\n        typedef __m64       __cl_ulong1;\n        typedef __m64       __cl_long1;\n        typedef __m64       __cl_float2;\n    #endif\n    #define __CL_UCHAR8__   1\n    #define __CL_CHAR8__    1\n    #define __CL_USHORT4__  1\n    #define __CL_SHORT4__   1\n    #define __CL_INT2__     1\n    #define __CL_UINT2__    1\n    #define __CL_ULONG1__   1\n    #define __CL_LONG1__    1\n    #define __CL_FLOAT2__   1\n#endif\n\n#if defined( __AVX__ )\n    #if defined( __MINGW64__ )\n        #include <intrin.h>\n    #else\n        #include <immintrin.h> \n    #endif\n    #if defined( __GNUC__ )\n        typedef cl_float    __cl_float8     __attribute__((vector_size(32)));\n        typedef cl_double   __cl_double4    __attribute__((vector_size(32)));\n    #else\n        typedef __m256      __cl_float8;\n        typedef __m256d     __cl_double4;\n    #endif\n    #define __CL_FLOAT8__   1\n    #define __CL_DOUBLE4__  1\n#endif\n\n/* Define alignment keys */\n#if defined( __GNUC__ )\n    #define CL_ALIGNED(_x)          __attribute__ ((aligned(_x)))\n#elif defined( _WIN32) && (_MSC_VER)\n    /* Alignment keys neutered on windows because MSVC can't swallow function arguments with alignment requirements     */\n    /* http://msdn.microsoft.com/en-us/library/373ak2y1%28VS.71%29.aspx                                                 */\n    /* #include <crtdefs.h>                                                                                             */\n    /* #define CL_ALIGNED(_x)          _CRT_ALIGN(_x)                                                                   */\n    #define CL_ALIGNED(_x)\n#else\n   #warning  Need to implement some method to align data here\n   #define  CL_ALIGNED(_x)\n#endif\n\n/* Indicate whether .xyzw, .s0123 and .hi.lo are supported */\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n    /* .xyzw and .s0123...{f|F} are supported */\n    #define CL_HAS_NAMED_VECTOR_FIELDS 1\n    /* .hi and .lo are supported */\n    #define CL_HAS_HI_LO_VECTOR_FIELDS 1\n#endif\n\n/* Define cl_vector types */\n\n/* ---- cl_charn ---- */\ntypedef union\n{\n    cl_char  CL_ALIGNED(2) s[2];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_char  x, y; };\n   __extension__ struct{ cl_char  s0, s1; };\n   __extension__ struct{ cl_char  lo, hi; };\n#endif\n#if defined( __CL_CHAR2__) \n    __cl_char2     v2;\n#endif\n}cl_char2;\n\ntypedef union\n{\n    cl_char  CL_ALIGNED(4) s[4];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_char  x, y, z, w; };\n   __extension__ struct{ cl_char  s0, s1, s2, s3; };\n   __extension__ struct{ cl_char2 lo, hi; };\n#endif\n#if defined( __CL_CHAR2__) \n    __cl_char2     v2[2];\n#endif\n#if defined( __CL_CHAR4__) \n    __cl_char4     v4;\n#endif\n}cl_char4;\n\n/* cl_char3 is identical in size, alignment and behavior to cl_char4. See section 6.1.5. */\ntypedef  cl_char4  cl_char3;\n\ntypedef union\n{\n    cl_char   CL_ALIGNED(8) s[8];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_char  x, y, z, w; };\n   __extension__ struct{ cl_char  s0, s1, s2, s3, s4, s5, s6, s7; };\n   __extension__ struct{ cl_char4 lo, hi; };\n#endif\n#if defined( __CL_CHAR2__) \n    __cl_char2     v2[4];\n#endif\n#if defined( __CL_CHAR4__) \n    __cl_char4     v4[2];\n#endif\n#if defined( __CL_CHAR8__ )\n    __cl_char8     v8;\n#endif\n}cl_char8;\n\ntypedef union\n{\n    cl_char  CL_ALIGNED(16) s[16];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_char  x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; };\n   __extension__ struct{ cl_char  s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; };\n   __extension__ struct{ cl_char8 lo, hi; };\n#endif\n#if defined( __CL_CHAR2__) \n    __cl_char2     v2[8];\n#endif\n#if defined( __CL_CHAR4__) \n    __cl_char4     v4[4];\n#endif\n#if defined( __CL_CHAR8__ )\n    __cl_char8     v8[2];\n#endif\n#if defined( __CL_CHAR16__ )\n    __cl_char16    v16;\n#endif\n}cl_char16;\n\n\n/* ---- cl_ucharn ---- */\ntypedef union\n{\n    cl_uchar  CL_ALIGNED(2) s[2];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_uchar  x, y; };\n   __extension__ struct{ cl_uchar  s0, s1; };\n   __extension__ struct{ cl_uchar  lo, hi; };\n#endif\n#if defined( __cl_uchar2__) \n    __cl_uchar2     v2;\n#endif\n}cl_uchar2;\n\ntypedef union\n{\n    cl_uchar  CL_ALIGNED(4) s[4];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_uchar  x, y, z, w; };\n   __extension__ struct{ cl_uchar  s0, s1, s2, s3; };\n   __extension__ struct{ cl_uchar2 lo, hi; };\n#endif\n#if defined( __CL_UCHAR2__) \n    __cl_uchar2     v2[2];\n#endif\n#if defined( __CL_UCHAR4__) \n    __cl_uchar4     v4;\n#endif\n}cl_uchar4;\n\n/* cl_uchar3 is identical in size, alignment and behavior to cl_uchar4. See section 6.1.5. */\ntypedef  cl_uchar4  cl_uchar3;\n\ntypedef union\n{\n    cl_uchar   CL_ALIGNED(8) s[8];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_uchar  x, y, z, w; };\n   __extension__ struct{ cl_uchar  s0, s1, s2, s3, s4, s5, s6, s7; };\n   __extension__ struct{ cl_uchar4 lo, hi; };\n#endif\n#if defined( __CL_UCHAR2__) \n    __cl_uchar2     v2[4];\n#endif\n#if defined( __CL_UCHAR4__) \n    __cl_uchar4     v4[2];\n#endif\n#if defined( __CL_UCHAR8__ )\n    __cl_uchar8     v8;\n#endif\n}cl_uchar8;\n\ntypedef union\n{\n    cl_uchar  CL_ALIGNED(16) s[16];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_uchar  x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; };\n   __extension__ struct{ cl_uchar  s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; };\n   __extension__ struct{ cl_uchar8 lo, hi; };\n#endif\n#if defined( __CL_UCHAR2__) \n    __cl_uchar2     v2[8];\n#endif\n#if defined( __CL_UCHAR4__) \n    __cl_uchar4     v4[4];\n#endif\n#if defined( __CL_UCHAR8__ )\n    __cl_uchar8     v8[2];\n#endif\n#if defined( __CL_UCHAR16__ )\n    __cl_uchar16    v16;\n#endif\n}cl_uchar16;\n\n\n/* ---- cl_shortn ---- */\ntypedef union\n{\n    cl_short  CL_ALIGNED(4) s[2];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_short  x, y; };\n   __extension__ struct{ cl_short  s0, s1; };\n   __extension__ struct{ cl_short  lo, hi; };\n#endif\n#if defined( __CL_SHORT2__) \n    __cl_short2     v2;\n#endif\n}cl_short2;\n\ntypedef union\n{\n    cl_short  CL_ALIGNED(8) s[4];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_short  x, y, z, w; };\n   __extension__ struct{ cl_short  s0, s1, s2, s3; };\n   __extension__ struct{ cl_short2 lo, hi; };\n#endif\n#if defined( __CL_SHORT2__) \n    __cl_short2     v2[2];\n#endif\n#if defined( __CL_SHORT4__) \n    __cl_short4     v4;\n#endif\n}cl_short4;\n\n/* cl_short3 is identical in size, alignment and behavior to cl_short4. See section 6.1.5. */\ntypedef  cl_short4  cl_short3;\n\ntypedef union\n{\n    cl_short   CL_ALIGNED(16) s[8];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_short  x, y, z, w; };\n   __extension__ struct{ cl_short  s0, s1, s2, s3, s4, s5, s6, s7; };\n   __extension__ struct{ cl_short4 lo, hi; };\n#endif\n#if defined( __CL_SHORT2__) \n    __cl_short2     v2[4];\n#endif\n#if defined( __CL_SHORT4__) \n    __cl_short4     v4[2];\n#endif\n#if defined( __CL_SHORT8__ )\n    __cl_short8     v8;\n#endif\n}cl_short8;\n\ntypedef union\n{\n    cl_short  CL_ALIGNED(32) s[16];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_short  x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; };\n   __extension__ struct{ cl_short  s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; };\n   __extension__ struct{ cl_short8 lo, hi; };\n#endif\n#if defined( __CL_SHORT2__) \n    __cl_short2     v2[8];\n#endif\n#if defined( __CL_SHORT4__) \n    __cl_short4     v4[4];\n#endif\n#if defined( __CL_SHORT8__ )\n    __cl_short8     v8[2];\n#endif\n#if defined( __CL_SHORT16__ )\n    __cl_short16    v16;\n#endif\n}cl_short16;\n\n\n/* ---- cl_ushortn ---- */\ntypedef union\n{\n    cl_ushort  CL_ALIGNED(4) s[2];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_ushort  x, y; };\n   __extension__ struct{ cl_ushort  s0, s1; };\n   __extension__ struct{ cl_ushort  lo, hi; };\n#endif\n#if defined( __CL_USHORT2__) \n    __cl_ushort2     v2;\n#endif\n}cl_ushort2;\n\ntypedef union\n{\n    cl_ushort  CL_ALIGNED(8) s[4];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_ushort  x, y, z, w; };\n   __extension__ struct{ cl_ushort  s0, s1, s2, s3; };\n   __extension__ struct{ cl_ushort2 lo, hi; };\n#endif\n#if defined( __CL_USHORT2__) \n    __cl_ushort2     v2[2];\n#endif\n#if defined( __CL_USHORT4__) \n    __cl_ushort4     v4;\n#endif\n}cl_ushort4;\n\n/* cl_ushort3 is identical in size, alignment and behavior to cl_ushort4. See section 6.1.5. */\ntypedef  cl_ushort4  cl_ushort3;\n\ntypedef union\n{\n    cl_ushort   CL_ALIGNED(16) s[8];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_ushort  x, y, z, w; };\n   __extension__ struct{ cl_ushort  s0, s1, s2, s3, s4, s5, s6, s7; };\n   __extension__ struct{ cl_ushort4 lo, hi; };\n#endif\n#if defined( __CL_USHORT2__) \n    __cl_ushort2     v2[4];\n#endif\n#if defined( __CL_USHORT4__) \n    __cl_ushort4     v4[2];\n#endif\n#if defined( __CL_USHORT8__ )\n    __cl_ushort8     v8;\n#endif\n}cl_ushort8;\n\ntypedef union\n{\n    cl_ushort  CL_ALIGNED(32) s[16];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_ushort  x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; };\n   __extension__ struct{ cl_ushort  s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; };\n   __extension__ struct{ cl_ushort8 lo, hi; };\n#endif\n#if defined( __CL_USHORT2__) \n    __cl_ushort2     v2[8];\n#endif\n#if defined( __CL_USHORT4__) \n    __cl_ushort4     v4[4];\n#endif\n#if defined( __CL_USHORT8__ )\n    __cl_ushort8     v8[2];\n#endif\n#if defined( __CL_USHORT16__ )\n    __cl_ushort16    v16;\n#endif\n}cl_ushort16;\n\n/* ---- cl_intn ---- */\ntypedef union\n{\n    cl_int  CL_ALIGNED(8) s[2];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_int  x, y; };\n   __extension__ struct{ cl_int  s0, s1; };\n   __extension__ struct{ cl_int  lo, hi; };\n#endif\n#if defined( __CL_INT2__) \n    __cl_int2     v2;\n#endif\n}cl_int2;\n\ntypedef union\n{\n    cl_int  CL_ALIGNED(16) s[4];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_int  x, y, z, w; };\n   __extension__ struct{ cl_int  s0, s1, s2, s3; };\n   __extension__ struct{ cl_int2 lo, hi; };\n#endif\n#if defined( __CL_INT2__) \n    __cl_int2     v2[2];\n#endif\n#if defined( __CL_INT4__) \n    __cl_int4     v4;\n#endif\n}cl_int4;\n\n/* cl_int3 is identical in size, alignment and behavior to cl_int4. See section 6.1.5. */\ntypedef  cl_int4  cl_int3;\n\ntypedef union\n{\n    cl_int   CL_ALIGNED(32) s[8];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_int  x, y, z, w; };\n   __extension__ struct{ cl_int  s0, s1, s2, s3, s4, s5, s6, s7; };\n   __extension__ struct{ cl_int4 lo, hi; };\n#endif\n#if defined( __CL_INT2__) \n    __cl_int2     v2[4];\n#endif\n#if defined( __CL_INT4__) \n    __cl_int4     v4[2];\n#endif\n#if defined( __CL_INT8__ )\n    __cl_int8     v8;\n#endif\n}cl_int8;\n\ntypedef union\n{\n    cl_int  CL_ALIGNED(64) s[16];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_int  x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; };\n   __extension__ struct{ cl_int  s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; };\n   __extension__ struct{ cl_int8 lo, hi; };\n#endif\n#if defined( __CL_INT2__) \n    __cl_int2     v2[8];\n#endif\n#if defined( __CL_INT4__) \n    __cl_int4     v4[4];\n#endif\n#if defined( __CL_INT8__ )\n    __cl_int8     v8[2];\n#endif\n#if defined( __CL_INT16__ )\n    __cl_int16    v16;\n#endif\n}cl_int16;\n\n\n/* ---- cl_uintn ---- */\ntypedef union\n{\n    cl_uint  CL_ALIGNED(8) s[2];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_uint  x, y; };\n   __extension__ struct{ cl_uint  s0, s1; };\n   __extension__ struct{ cl_uint  lo, hi; };\n#endif\n#if defined( __CL_UINT2__) \n    __cl_uint2     v2;\n#endif\n}cl_uint2;\n\ntypedef union\n{\n    cl_uint  CL_ALIGNED(16) s[4];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_uint  x, y, z, w; };\n   __extension__ struct{ cl_uint  s0, s1, s2, s3; };\n   __extension__ struct{ cl_uint2 lo, hi; };\n#endif\n#if defined( __CL_UINT2__) \n    __cl_uint2     v2[2];\n#endif\n#if defined( __CL_UINT4__) \n    __cl_uint4     v4;\n#endif\n}cl_uint4;\n\n/* cl_uint3 is identical in size, alignment and behavior to cl_uint4. See section 6.1.5. */\ntypedef  cl_uint4  cl_uint3;\n\ntypedef union\n{\n    cl_uint   CL_ALIGNED(32) s[8];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_uint  x, y, z, w; };\n   __extension__ struct{ cl_uint  s0, s1, s2, s3, s4, s5, s6, s7; };\n   __extension__ struct{ cl_uint4 lo, hi; };\n#endif\n#if defined( __CL_UINT2__) \n    __cl_uint2     v2[4];\n#endif\n#if defined( __CL_UINT4__) \n    __cl_uint4     v4[2];\n#endif\n#if defined( __CL_UINT8__ )\n    __cl_uint8     v8;\n#endif\n}cl_uint8;\n\ntypedef union\n{\n    cl_uint  CL_ALIGNED(64) s[16];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_uint  x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; };\n   __extension__ struct{ cl_uint  s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; };\n   __extension__ struct{ cl_uint8 lo, hi; };\n#endif\n#if defined( __CL_UINT2__) \n    __cl_uint2     v2[8];\n#endif\n#if defined( __CL_UINT4__) \n    __cl_uint4     v4[4];\n#endif\n#if defined( __CL_UINT8__ )\n    __cl_uint8     v8[2];\n#endif\n#if defined( __CL_UINT16__ )\n    __cl_uint16    v16;\n#endif\n}cl_uint16;\n\n/* ---- cl_longn ---- */\ntypedef union\n{\n    cl_long  CL_ALIGNED(16) s[2];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_long  x, y; };\n   __extension__ struct{ cl_long  s0, s1; };\n   __extension__ struct{ cl_long  lo, hi; };\n#endif\n#if defined( __CL_LONG2__) \n    __cl_long2     v2;\n#endif\n}cl_long2;\n\ntypedef union\n{\n    cl_long  CL_ALIGNED(32) s[4];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_long  x, y, z, w; };\n   __extension__ struct{ cl_long  s0, s1, s2, s3; };\n   __extension__ struct{ cl_long2 lo, hi; };\n#endif\n#if defined( __CL_LONG2__) \n    __cl_long2     v2[2];\n#endif\n#if defined( __CL_LONG4__) \n    __cl_long4     v4;\n#endif\n}cl_long4;\n\n/* cl_long3 is identical in size, alignment and behavior to cl_long4. See section 6.1.5. */\ntypedef  cl_long4  cl_long3;\n\ntypedef union\n{\n    cl_long   CL_ALIGNED(64) s[8];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_long  x, y, z, w; };\n   __extension__ struct{ cl_long  s0, s1, s2, s3, s4, s5, s6, s7; };\n   __extension__ struct{ cl_long4 lo, hi; };\n#endif\n#if defined( __CL_LONG2__) \n    __cl_long2     v2[4];\n#endif\n#if defined( __CL_LONG4__) \n    __cl_long4     v4[2];\n#endif\n#if defined( __CL_LONG8__ )\n    __cl_long8     v8;\n#endif\n}cl_long8;\n\ntypedef union\n{\n    cl_long  CL_ALIGNED(128) s[16];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_long  x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; };\n   __extension__ struct{ cl_long  s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; };\n   __extension__ struct{ cl_long8 lo, hi; };\n#endif\n#if defined( __CL_LONG2__) \n    __cl_long2     v2[8];\n#endif\n#if defined( __CL_LONG4__) \n    __cl_long4     v4[4];\n#endif\n#if defined( __CL_LONG8__ )\n    __cl_long8     v8[2];\n#endif\n#if defined( __CL_LONG16__ )\n    __cl_long16    v16;\n#endif\n}cl_long16;\n\n\n/* ---- cl_ulongn ---- */\ntypedef union\n{\n    cl_ulong  CL_ALIGNED(16) s[2];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_ulong  x, y; };\n   __extension__ struct{ cl_ulong  s0, s1; };\n   __extension__ struct{ cl_ulong  lo, hi; };\n#endif\n#if defined( __CL_ULONG2__) \n    __cl_ulong2     v2;\n#endif\n}cl_ulong2;\n\ntypedef union\n{\n    cl_ulong  CL_ALIGNED(32) s[4];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_ulong  x, y, z, w; };\n   __extension__ struct{ cl_ulong  s0, s1, s2, s3; };\n   __extension__ struct{ cl_ulong2 lo, hi; };\n#endif\n#if defined( __CL_ULONG2__) \n    __cl_ulong2     v2[2];\n#endif\n#if defined( __CL_ULONG4__) \n    __cl_ulong4     v4;\n#endif\n}cl_ulong4;\n\n/* cl_ulong3 is identical in size, alignment and behavior to cl_ulong4. See section 6.1.5. */\ntypedef  cl_ulong4  cl_ulong3;\n\ntypedef union\n{\n    cl_ulong   CL_ALIGNED(64) s[8];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_ulong  x, y, z, w; };\n   __extension__ struct{ cl_ulong  s0, s1, s2, s3, s4, s5, s6, s7; };\n   __extension__ struct{ cl_ulong4 lo, hi; };\n#endif\n#if defined( __CL_ULONG2__) \n    __cl_ulong2     v2[4];\n#endif\n#if defined( __CL_ULONG4__) \n    __cl_ulong4     v4[2];\n#endif\n#if defined( __CL_ULONG8__ )\n    __cl_ulong8     v8;\n#endif\n}cl_ulong8;\n\ntypedef union\n{\n    cl_ulong  CL_ALIGNED(128) s[16];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_ulong  x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; };\n   __extension__ struct{ cl_ulong  s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; };\n   __extension__ struct{ cl_ulong8 lo, hi; };\n#endif\n#if defined( __CL_ULONG2__) \n    __cl_ulong2     v2[8];\n#endif\n#if defined( __CL_ULONG4__) \n    __cl_ulong4     v4[4];\n#endif\n#if defined( __CL_ULONG8__ )\n    __cl_ulong8     v8[2];\n#endif\n#if defined( __CL_ULONG16__ )\n    __cl_ulong16    v16;\n#endif\n}cl_ulong16;\n\n\n/* --- cl_floatn ---- */\n\ntypedef union\n{\n    cl_float  CL_ALIGNED(8) s[2];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_float  x, y; };\n   __extension__ struct{ cl_float  s0, s1; };\n   __extension__ struct{ cl_float  lo, hi; };\n#endif\n#if defined( __CL_FLOAT2__) \n    __cl_float2     v2;\n#endif\n}cl_float2;\n\ntypedef union\n{\n    cl_float  CL_ALIGNED(16) s[4];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_float   x, y, z, w; };\n   __extension__ struct{ cl_float   s0, s1, s2, s3; };\n   __extension__ struct{ cl_float2  lo, hi; };\n#endif\n#if defined( __CL_FLOAT2__) \n    __cl_float2     v2[2];\n#endif\n#if defined( __CL_FLOAT4__) \n    __cl_float4     v4;\n#endif\n}cl_float4;\n\n/* cl_float3 is identical in size, alignment and behavior to cl_float4. See section 6.1.5. */\ntypedef  cl_float4  cl_float3;\n\ntypedef union\n{\n    cl_float   CL_ALIGNED(32) s[8];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_float   x, y, z, w; };\n   __extension__ struct{ cl_float   s0, s1, s2, s3, s4, s5, s6, s7; };\n   __extension__ struct{ cl_float4  lo, hi; };\n#endif\n#if defined( __CL_FLOAT2__) \n    __cl_float2     v2[4];\n#endif\n#if defined( __CL_FLOAT4__) \n    __cl_float4     v4[2];\n#endif\n#if defined( __CL_FLOAT8__ )\n    __cl_float8     v8;\n#endif\n}cl_float8;\n\ntypedef union\n{\n    cl_float  CL_ALIGNED(64) s[16];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_float  x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; };\n   __extension__ struct{ cl_float  s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; };\n   __extension__ struct{ cl_float8 lo, hi; };\n#endif\n#if defined( __CL_FLOAT2__) \n    __cl_float2     v2[8];\n#endif\n#if defined( __CL_FLOAT4__) \n    __cl_float4     v4[4];\n#endif\n#if defined( __CL_FLOAT8__ )\n    __cl_float8     v8[2];\n#endif\n#if defined( __CL_FLOAT16__ )\n    __cl_float16    v16;\n#endif\n}cl_float16;\n\n/* --- cl_doublen ---- */\n\ntypedef union\n{\n    cl_double  CL_ALIGNED(16) s[2];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_double  x, y; };\n   __extension__ struct{ cl_double s0, s1; };\n   __extension__ struct{ cl_double lo, hi; };\n#endif\n#if defined( __CL_DOUBLE2__) \n    __cl_double2     v2;\n#endif\n}cl_double2;\n\ntypedef union\n{\n    cl_double  CL_ALIGNED(32) s[4];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_double  x, y, z, w; };\n   __extension__ struct{ cl_double  s0, s1, s2, s3; };\n   __extension__ struct{ cl_double2 lo, hi; };\n#endif\n#if defined( __CL_DOUBLE2__) \n    __cl_double2     v2[2];\n#endif\n#if defined( __CL_DOUBLE4__) \n    __cl_double4     v4;\n#endif\n}cl_double4;\n\n/* cl_double3 is identical in size, alignment and behavior to cl_double4. See section 6.1.5. */\ntypedef  cl_double4  cl_double3;\n\ntypedef union\n{\n    cl_double   CL_ALIGNED(64) s[8];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_double  x, y, z, w; };\n   __extension__ struct{ cl_double  s0, s1, s2, s3, s4, s5, s6, s7; };\n   __extension__ struct{ cl_double4 lo, hi; };\n#endif\n#if defined( __CL_DOUBLE2__) \n    __cl_double2     v2[4];\n#endif\n#if defined( __CL_DOUBLE4__) \n    __cl_double4     v4[2];\n#endif\n#if defined( __CL_DOUBLE8__ )\n    __cl_double8     v8;\n#endif\n}cl_double8;\n\ntypedef union\n{\n    cl_double  CL_ALIGNED(128) s[16];\n#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )\n   __extension__ struct{ cl_double  x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; };\n   __extension__ struct{ cl_double  s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; };\n   __extension__ struct{ cl_double8 lo, hi; };\n#endif\n#if defined( __CL_DOUBLE2__) \n    __cl_double2     v2[8];\n#endif\n#if defined( __CL_DOUBLE4__) \n    __cl_double4     v4[4];\n#endif\n#if defined( __CL_DOUBLE8__ )\n    __cl_double8     v8[2];\n#endif\n#if defined( __CL_DOUBLE16__ )\n    __cl_double16    v16;\n#endif\n}cl_double16;\n\n/* Macro to facilitate debugging \n * Usage:\n *   Place CL_PROGRAM_STRING_DEBUG_INFO on the line before the first line of your source. \n *   The first line ends with:   CL_PROGRAM_STRING_DEBUG_INFO \\\"\n *   Each line thereafter of OpenCL C source must end with: \\n\\\n *   The last line ends in \";\n *\n *   Example:\n *\n *   const char *my_program = CL_PROGRAM_STRING_DEBUG_INFO \"\\\n *   kernel void foo( int a, float * b )             \\n\\\n *   {                                               \\n\\\n *      // my comment                                \\n\\\n *      *b[ get_global_id(0)] = a;                   \\n\\\n *   }                                               \\n\\\n *   \";\n *\n * This should correctly set up the line, (column) and file information for your source \n * string so you can do source level debugging.\n */\n#define  __CL_STRINGIFY( _x )               # _x\n#define  _CL_STRINGIFY( _x )                __CL_STRINGIFY( _x )\n#define  CL_PROGRAM_STRING_DEBUG_INFO       \"#line \"  _CL_STRINGIFY(__LINE__) \" \\\"\" __FILE__ \"\\\" \\n\\n\" \n  \n#ifdef __cplusplus\n}\n#endif\n\n#endif  /* __CL_PLATFORM_H  */\n"
  },
  {
    "path": "caffe2/mobile/contrib/libopencl-stub/include/CL/opencl.h",
    "content": "/*******************************************************************************\n * Copyright (c) 2008-2012 The Khronos Group Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and/or associated documentation files (the\n * \"Materials\"), to deal in the Materials without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Materials, and to\n * permit persons to whom the Materials are furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Materials.\n *\n * THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.\n ******************************************************************************/\n\n/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */\n\n#ifndef __OPENCL_H\n#define __OPENCL_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifdef __APPLE__\n\n#include <OpenCL/cl.h>\n#include <OpenCL/cl_gl.h>\n#include <OpenCL/cl_gl_ext.h>\n#include <OpenCL/cl_ext.h>\n\n#else\n\n#include <CL/cl.h>\n#include <CL/cl_gl.h>\n#include <CL/cl_gl_ext.h>\n#include <CL/cl_ext.h>\n\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif  /* __OPENCL_H   */\n\n"
  },
  {
    "path": "caffe2/mobile/contrib/libopencl-stub/include/libopencl.h",
    "content": "#ifndef LIBOPENCL_STUB_H\n#define LIBOPENCL_STUB_H\n\n#include <stdlib.h>\n#include <sys/stat.h>\n#include <dlfcn.h>\n#include <CL/cl.h>\n#include <CL/cl_gl.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef void (*f_pfn_notify)(const char *, const void *, size_t, void *);\n\ntypedef cl_int (*f_clGetPlatformIDs) (cl_uint, cl_platform_id *, cl_uint *);\n\ntypedef cl_int (*f_clGetPlatformInfo) (cl_platform_id, cl_platform_info, size_t, void *, size_t *);\n\ntypedef cl_int (*f_clGetDeviceIDs) (cl_platform_id, cl_device_type, cl_uint, cl_device_id *, cl_uint *);\n\ntypedef cl_int (*f_clGetDeviceInfo) (cl_device_id, cl_device_info, size_t, void *, size_t *);\n\ntypedef cl_int (*f_clCreateSubDevices) (cl_device_id, const cl_device_partition_property *,\n\t\t\t\t\tcl_uint, cl_device_id *, cl_uint *);\n\ntypedef cl_int (*f_clRetainDevice) (cl_device_id);\n\ntypedef cl_int (*f_clReleaseDevice) (cl_device_id);\n\ntypedef cl_context (*f_clCreateContext) (const cl_context_properties *, cl_uint, const cl_device_id *,\n                \t\t\tf_pfn_notify, void *, cl_int *);\n\ntypedef cl_context (*f_clCreateContextFromType) (const cl_context_properties *, cl_device_type,\n                        \t\tf_pfn_notify, void *, cl_int *);\n\ntypedef cl_int (*f_clRetainContext) (cl_context);\n\ntypedef cl_int (*f_clReleaseContext) (cl_context);\n\ntypedef cl_int (*f_clGetContextInfo) (cl_context, cl_context_info, size_t, void *, size_t *);\n\ntypedef cl_command_queue (*f_clCreateCommandQueue) (cl_context, cl_device_id, cl_command_queue_properties, cl_int *);\n\ntypedef cl_int (*f_clRetainCommandQueue) (cl_command_queue);\n\ntypedef cl_int (*f_clReleaseCommandQueue) (cl_command_queue);\n\ntypedef cl_int (*f_clGetCommandQueueInfo) (cl_command_queue, cl_command_queue_info, size_t, void *, size_t *);\n\ntypedef cl_mem (*f_clCreateBuffer) (cl_context, cl_mem_flags, size_t, void *, cl_int *);\n\ntypedef cl_mem (*f_clCreateSubBuffer) (cl_mem, cl_mem_flags, cl_buffer_create_type, const void *, cl_int *);\n\ntypedef cl_mem (*f_clCreateImage) (cl_context, cl_mem_flags, const cl_image_format *, const cl_image_desc *, void *, cl_int *);\n\ntypedef cl_int (*f_clRetainMemObject) (cl_mem);\n\ntypedef cl_int (*f_clReleaseMemObject) (cl_mem);\n\ntypedef cl_int (*f_clGetMemObjectInfo) (cl_mem, cl_mem_info, size_t, void *, size_t *);\n\ntypedef cl_int (*f_clGetImageInfo) (cl_mem, cl_image_info, size_t, void *, size_t *);\n\ntypedef cl_int (*f_clSetMemObjectDestructorCallback) (cl_mem, void (*pfn_notify)( cl_mem memobj, void* user_data), void *);\n\ntypedef cl_int (*f_clGetSupportedImageFormats) (cl_context, cl_mem_flags, cl_mem_object_type, cl_uint, cl_image_format *, cl_uint *);\n\ntypedef cl_sampler (*f_clCreateSampler) (cl_context, cl_bool, cl_addressing_mode, cl_filter_mode, cl_int *);\n\ntypedef cl_int (*f_clRetainSampler) (cl_sampler);\n\ntypedef cl_int (*f_clReleaseSampler) (cl_sampler);\n\ntypedef cl_int (*f_clGetSamplerInfo) (cl_sampler, cl_sampler_info, size_t, void *, size_t *);\n\ntypedef cl_program (*f_clCreateProgramWithSource) (cl_context, cl_uint, const char **, const size_t *, cl_int *);\n\ntypedef cl_program (*f_clCreateProgramWithBinary) (cl_context, cl_uint, const cl_device_id *,\n        const size_t *, const unsigned char **, cl_int *, cl_int *);\n\ntypedef cl_program (*f_clCreateProgramWithBuiltInKernels) (cl_context, cl_uint, const cl_device_id *, const char *, cl_int *);\n\ntypedef cl_int (*f_clRetainProgram) (cl_program);\n\ntypedef cl_int (*f_clReleaseProgram) (cl_program);\n\ntypedef cl_int (*f_clBuildProgram) (cl_program, cl_uint, const cl_device_id *, const char *,\n        void (*pfn_notify)(cl_program program, void * user_data), void *);\n\ntypedef cl_int (*f_clCompileProgram) (cl_program, cl_uint, const cl_device_id *, const char *, cl_uint, const cl_program *,\n        const char **, void (*pfn_notify)(cl_program program, void * user_data), void *);\n\ntypedef cl_program (*f_clLinkProgram) (cl_context, cl_uint, const cl_device_id *, const char *, cl_uint, const cl_program *,\n                    void (*pfn_notify)(cl_program program, void * user_data), void *, cl_int *);\n\ntypedef cl_int (*f_clUnloadPlatformCompiler)(cl_platform_id);\n\ntypedef cl_int (*f_clGetProgramInfo) (cl_program, cl_program_info, size_t, void *, size_t *);\n\ntypedef cl_int (*f_clGetProgramBuildInfo) (cl_program, cl_device_id, cl_program_build_info, size_t, void *, size_t *);\n\ntypedef cl_kernel (*f_clCreateKernel) (cl_program, const char *, cl_int *);\n\ntypedef cl_int (*f_clCreateKernelsInProgram) (cl_program, cl_uint, cl_kernel *, cl_uint *);\n\ntypedef cl_int (*f_clRetainKernel) (cl_kernel);\n\ntypedef cl_int (*f_clReleaseKernel) (cl_kernel);\n\ntypedef cl_int (*f_clSetKernelArg) (cl_kernel, cl_uint, size_t,const void *);\n\ntypedef cl_int (*f_clGetKernelInfo) (cl_kernel, cl_kernel_info, size_t, void *, size_t *);\n\ntypedef cl_int (*f_clGetKernelArgInfo) (cl_kernel, cl_uint, cl_kernel_arg_info, size_t, void *, size_t *);\n\ntypedef cl_int (*f_clGetKernelWorkGroupInfo) (cl_kernel, cl_device_id, cl_kernel_work_group_info, size_t, void *, size_t *);\n\ntypedef cl_int (*f_clWaitForEvents) (cl_uint, const cl_event *);\n\ntypedef cl_int (*f_clGetEventInfo) (cl_event, cl_event_info, size_t, void *, size_t *);\n\ntypedef cl_event (*f_clCreateUserEvent) (cl_context, cl_int *);\n\ntypedef cl_int (*f_clRetainEvent) (cl_event);\n\ntypedef cl_int (*f_clReleaseEvent) (cl_event);\n\ntypedef cl_int (*f_clSetUserEventStatus) (cl_event, cl_int);\n\ntypedef cl_int (*f_clSetEventCallback) (cl_event, cl_int, void (*pfn_notify)(cl_event, cl_int, void *), void *);\n\ntypedef cl_int (*f_clGetEventProfilingInfo) (cl_event, cl_profiling_info, size_t, void *, size_t *);\n\ntypedef cl_int (*f_clFlush) (cl_command_queue);\n\ntypedef cl_int (*f_clFinish) (cl_command_queue);\n\ntypedef cl_int (*f_clEnqueueReadBuffer) (cl_command_queue, cl_mem, cl_bool, size_t, size_t, void *, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueReadBufferRect) (cl_command_queue, cl_mem, cl_bool, const size_t *, const size_t *, const size_t *,\n                            size_t, size_t, size_t, size_t, void *, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueWriteBuffer) (cl_command_queue, cl_mem, cl_bool, size_t, size_t, const void *, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueWriteBufferRect) (cl_command_queue, cl_mem, cl_bool, const size_t *, const size_t *, const size_t *,\n                            size_t, size_t, size_t, size_t, const void *, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueFillBuffer) (cl_command_queue, cl_mem, const void *, size_t, size_t, size_t, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueCopyBuffer) (cl_command_queue, cl_mem, cl_mem, size_t, size_t, size_t, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueCopyBufferRect) (cl_command_queue, cl_mem, cl_mem, const size_t *, const size_t *, const size_t *,\n                            size_t, size_t, size_t, size_t, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueReadImage) (cl_command_queue, cl_mem, cl_bool, const size_t *, const size_t *,\n\t\t\t\t\t\t\tsize_t, size_t, void *, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueWriteImage) (cl_command_queue, cl_mem, cl_bool, const size_t *, const size_t *,\n\t\t\t\t\t\t\tsize_t, size_t, const void *, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueFillImage) (cl_command_queue, cl_mem, const void *, const size_t *, const size_t *, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueCopyImage) (cl_command_queue, cl_mem, cl_mem, const size_t *, const size_t *, const size_t *,\n          cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueCopyImageToBuffer) (cl_command_queue, cl_mem, cl_mem, const size_t *, const size_t *, size_t, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueCopyBufferToImage) (cl_command_queue, cl_mem, cl_mem, size_t, const size_t *, const size_t *, cl_uint, const cl_event *, cl_event *);\n\ntypedef void * (*f_clEnqueueMapBuffer) (cl_command_queue, cl_mem, cl_bool, cl_map_flags, size_t,\n\t\t\t\t\t\tsize_t, cl_uint, const cl_event *, cl_event *, cl_int *);\n\ntypedef void * (*f_clEnqueueMapImage) (cl_command_queue, cl_mem, cl_bool, cl_map_flags, const size_t *, const size_t *,\n                  size_t *, size_t *, cl_uint, const cl_event *, cl_event *, cl_int *);\n\ntypedef cl_int (*f_clEnqueueUnmapMemObject) (cl_command_queue, cl_mem, void *, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueMigrateMemObjects)(cl_command_queue, cl_uint, const cl_mem *, cl_mem_migration_flags,\n\t\t\t\t\t\tcl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueNDRangeKernel)(cl_command_queue, cl_kernel, cl_uint, const size_t *, const size_t *,\n                       const size_t *, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueTask)(cl_command_queue, cl_kernel, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueNativeKernel)(cl_command_queue, void (*user_func)(void *),  void *, size_t,\n                      cl_uint, const cl_mem *, const void **, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueMarkerWithWaitList)(cl_command_queue, cl_uint, const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueBarrierWithWaitList)(cl_command_queue, cl_uint, const cl_event *, cl_event *);\n\ntypedef void * (*f_clGetExtensionFunctionAddressForPlatform)(cl_platform_id, const char *);\n\ntypedef cl_mem (*f_clCreateImage2D)(cl_context, cl_mem_flags,const cl_image_format *, size_t, size_t,\n                \t\t\t\tsize_t, void *, cl_int *);\n\ntypedef cl_mem (*f_clCreateImage3D)(cl_context, cl_mem_flags, const cl_image_format *, size_t,\n                \t\tsize_t, size_t, size_t, size_t, void *, cl_int *);\n\ntypedef cl_int (*f_clEnqueueMarker)(cl_command_queue, cl_event *);\n\ntypedef cl_int(*f_clEnqueueWaitForEvents)(cl_command_queue, cl_uint, const cl_event *);\n\ntypedef cl_int (*f_clEnqueueBarrier)(cl_command_queue);\n\ntypedef cl_int (*f_clUnloadCompiler)(void);\n\ntypedef void * (*f_clGetExtensionFunctionAddress)(const char *);\n\ntypedef cl_mem (*f_clCreateFromGLBuffer) (cl_context, cl_mem_flags, cl_GLuint, int *);\n\ntypedef cl_mem (*f_clCreateFromGLTexture) (cl_context, cl_mem_flags, cl_GLenum, cl_GLint, cl_GLuint, cl_int *);\n\ntypedef cl_mem (*f_clCreateFromGLRenderbuffer) (cl_context, cl_mem_flags, cl_GLuint, cl_int *);\n\ntypedef cl_int (*f_clGetGLObjectInfo) (cl_mem memobj, cl_gl_object_type *, cl_GLuint *);\n\ntypedef cl_int (*f_clGetGLTextureInfo) (cl_mem, cl_gl_texture_info, size_t, void *, size_t *);\n\ntypedef cl_int (*f_clEnqueueAcquireGLObjects) (cl_command_queue, cl_uint, const cl_mem *, cl_uint,\n                                        const cl_event *, cl_event *);\n\ntypedef cl_int (*f_clEnqueueReleaseGLObjects) (cl_command_queue, cl_uint, const cl_mem *, cl_uint,\n                                        const cl_event *, cl_event *);\n\ntypedef cl_mem (*f_clCreateFromGLTexture2D) (cl_context, cl_mem_flags, cl_GLenum, cl_GLint, cl_GLuint, cl_int *);\n\ntypedef cl_mem (*f_clCreateFromGLTexture3D) (cl_context, cl_mem_flags, cl_GLenum, cl_GLint, cl_GLuint, cl_int *);\n\n//typedef cl_uint     cl_gl_context_info;\ntypedef cl_int (*f_clGetGLContextInfoKHR) (const cl_context_properties *, cl_gl_context_info, size_t,\n                                        void *, size_t *);\n\n// Additional api to reset currently opened opencl shared-object\n// Subsequent calls will use newly set environment variables\nvoid stubOpenclReset();\n\n// Helper function to get the path to libOpenCL.so\nint open_libopencl_so();\ncl_int get_libopencl_path(char** cl_path);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif    // LIBOPENCL_STUB_H\n"
  },
  {
    "path": "caffe2/mobile/contrib/libopencl-stub/src/libopencl.c",
    "content": "/*\n *   Stub libopencl that dlsyms into actual library based on environment variable\n *\n *   LIBOPENCL_SO_PATH      -- Path to opencl so that will be searched first\n *   LIBOPENCL_SO_PATH_2    -- Searched second\n *   LIBOPENCL_SO_PATH_3    -- Searched third\n *   LIBOPENCL_SO_PATH_4    -- Searched fourth\n *\n *   If none of these are set, default system paths will be considered\n**/\n\n#include \"libopencl.h\"\n\n\n#if defined(__APPLE__) || defined(__MACOSX)\nstatic const char *default_so_paths[] = {\n  \"libOpenCL.so\"\n};\n#elif defined(__ANDROID__)\nstatic const char *default_so_paths[] = {\n  \"/system/lib/libOpenCL.so\",\n  \"/system/vendor/lib/libOpenCL.so\",\n  \"/system/vendor/lib/egl/libGLES_mali.so\",\n  \"/system/vendor/lib/libPVROCL.so\",\n  \"/data/data/org.pocl.libs/files/lib/libpocl.so\",\n  \"libOpenCL.so\"\n};\n#elif defined(_WIN32)\nstatic const char *default_so_paths[] = {\n  \"OpenCL.dll\"\n};\n#elif defined(__linux__)\nstatic const char *default_so_paths[] = {\n  \"/usr/lib/libOpenCL.so\",\n  \"/usr/local/lib/libOpenCL.so\",\n  \"/usr/local/lib/libpocl.so\",\n  \"/usr/lib64/libOpenCL.so\",\n  \"/usr/lib32/libOpenCL.so\",\n  \"libOpenCL.so\"\n};\n#endif\n\nstatic void *so_handle = NULL;\n\n\nstatic int access_file(const char *filename)\n{\n  struct stat buffer;\n  return (stat(filename, &buffer) == 0);\n}\n\nint open_libopencl_so()\n{\n  char *path = NULL, *str = NULL;\n  int i;\n\n  if((str=getenv(\"LIBOPENCL_SO_PATH\")) && access_file(str)) {\n    path = str;\n  }\n  else if((str=getenv(\"LIBOPENCL_SO_PATH_2\")) && access_file(str)) {\n    path = str;\n  }\n  else if((str=getenv(\"LIBOPENCL_SO_PATH_3\")) && access_file(str)) {\n    path = str;\n  }\n  else if((str=getenv(\"LIBOPENCL_SO_PATH_4\")) && access_file(str)) {\n    path = str;\n  }\n\n  if(!path)\n  {\n    for(i=0; i<(sizeof(default_so_paths) / sizeof(char*)); i++)\n    {\n      if(access_file(default_so_paths[i]))\n      {\n        path = (char *) default_so_paths[i];\n        break;\n      }\n    }\n  }\n\n  if(path)\n  {\n    so_handle = dlopen(path, RTLD_LAZY);\n    if(so_handle) {\n      return 0;\n    }\n  }\n\n  return -1;\n}\n\ncl_int get_libopencl_path(char** cl_path)\n{\n  char *path = NULL, *str = NULL;\n  int i;\n\n  if((str=getenv(\"LIBOPENCL_SO_PATH\")) && access_file(str)) {\n    path = str;\n  }\n  else if((str=getenv(\"LIBOPENCL_SO_PATH_2\")) && access_file(str)) {\n    path = str;\n  }\n  else if((str=getenv(\"LIBOPENCL_SO_PATH_3\")) && access_file(str)) {\n    path = str;\n  }\n  else if((str=getenv(\"LIBOPENCL_SO_PATH_4\")) && access_file(str)) {\n    path = str;\n  }\n\n  if(!path)\n  {\n    for(i=0; i<(sizeof(default_so_paths) / sizeof(char*)); i++)\n    {\n      if(access_file(default_so_paths[i]))\n      {\n        path = (char *) default_so_paths[i];\n        break;\n      }\n    }\n  }\n\n  if (path)\n  {\n    *cl_path = strndup(path, strlen(path));\n    return CL_SUCCESS;\n  }\n  return CL_INVALID_PLATFORM;\n}\n\nvoid stubOpenclReset()\n{\n  if(so_handle)\n    dlclose(so_handle);\n\n  so_handle = NULL;\n}\n\ncl_int\nclGetPlatformIDs(cl_uint          num_entries,\n                 cl_platform_id * platforms,\n                 cl_uint *        num_platforms)\n{\n  f_clGetPlatformIDs func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetPlatformIDs) dlsym(so_handle, \"clGetPlatformIDs\");\n  if(func) {\n    return func(num_entries, platforms, num_platforms);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_int\nclGetPlatformInfo(cl_platform_id   platform,\n                  cl_platform_info param_name,\n                  size_t           param_value_size,\n                  void *           param_value,\n                  size_t *         param_value_size_ret)\n{\n  f_clGetPlatformInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetPlatformInfo) dlsym(so_handle, \"clGetPlatformInfo\");\n  if(func) {\n    return func(platform, param_name, param_value_size, param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_int\nclGetDeviceIDs(cl_platform_id   platform,\n               cl_device_type   device_type,\n               cl_uint          num_entries,\n               cl_device_id *   devices,\n               cl_uint *        num_devices)\n{\n  f_clGetDeviceIDs func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetDeviceIDs) dlsym(so_handle, \"clGetDeviceIDs\");\n  if(func) {\n    return func(platform, device_type, num_entries, devices, num_devices);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetDeviceInfo(cl_device_id    device,\n                cl_device_info  param_name,\n                size_t          param_value_size,\n                void *          param_value,\n                size_t *        param_value_size_ret)\n{\n  f_clGetDeviceInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetDeviceInfo) dlsym(so_handle, \"clGetDeviceInfo\");\n  if(func) {\n    return func(device, param_name, param_value_size, param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclCreateSubDevices(cl_device_id                         in_device,\n                   const cl_device_partition_property * properties,\n                   cl_uint                              num_devices,\n                   cl_device_id *                       out_devices,\n                   cl_uint *                            num_devices_ret)\n{\n  f_clCreateSubDevices func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateSubDevices) dlsym(so_handle, \"clCreateSubDevices\");\n  if(func) {\n    return func(in_device, properties, num_devices, out_devices, num_devices_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclRetainDevice(cl_device_id device)\n{\n  f_clRetainDevice func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clRetainDevice) dlsym(so_handle, \"clRetainDevice\");\n  if(func) {\n    return func(device);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclReleaseDevice(cl_device_id device)\n{\n  f_clReleaseDevice func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clReleaseDevice) dlsym(so_handle, \"clReleaseDevice\");\n  if(func) {\n    return func(device);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_context\nclCreateContext(const cl_context_properties * properties,\n                cl_uint                 num_devices,\n                const cl_device_id *    devices,\n                void (*pfn_notify)(const char *, const void *, size_t, void *),\n                void *                  user_data,\n                cl_int *                errcode_ret)\n{\n  f_clCreateContext func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateContext) dlsym(so_handle, \"clCreateContext\");\n  if(func) {\n    return func(properties, num_devices, devices, pfn_notify, user_data, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_context\nclCreateContextFromType(const cl_context_properties * properties,\n                        cl_device_type          device_type,\n                        void (*pfn_notify )(const char *, const void *, size_t, void *),\n                        void *                  user_data,\n                        cl_int *                errcode_ret)\n{\n  f_clCreateContextFromType func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateContextFromType) dlsym(so_handle, \"clCreateContextFromType\");\n  if(func) {\n    return func(properties, device_type, pfn_notify, user_data, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_int\nclRetainContext(cl_context context)\n{\n  f_clRetainContext func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clRetainContext) dlsym(so_handle, \"clRetainContext\");\n  if(func) {\n    return func(context);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclReleaseContext(cl_context context)\n{\n  f_clReleaseContext func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clReleaseContext) dlsym(so_handle, \"clReleaseContext\");\n  if(func) {\n    return func(context);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetContextInfo(cl_context         context,\n                 cl_context_info    param_name,\n                 size_t             param_value_size,\n                 void *             param_value,\n                 size_t *           param_value_size_ret)\n{\n  f_clGetContextInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetContextInfo) dlsym(so_handle, \"clGetContextInfo\");\n  if(func) {\n    return func(context, param_name, param_value_size,\n                param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_command_queue\nclCreateCommandQueue(cl_context                     context,\n                     cl_device_id                   device,\n                     cl_command_queue_properties    properties,\n                     cl_int *                       errcode_ret)\n{\n  f_clCreateCommandQueue func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateCommandQueue) dlsym(so_handle, \"clCreateCommandQueue\");\n  if(func) {\n    return func(context, device, properties, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_int\nclRetainCommandQueue(cl_command_queue command_queue)\n{\n  f_clRetainCommandQueue func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clRetainCommandQueue) dlsym(so_handle, \"clRetainCommandQueue\");\n  if(func) {\n    return func(command_queue);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclReleaseCommandQueue(cl_command_queue command_queue)\n{\n  f_clReleaseCommandQueue func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clReleaseCommandQueue) dlsym(so_handle, \"clReleaseCommandQueue\");\n  if(func) {\n    return func(command_queue);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetCommandQueueInfo(cl_command_queue      command_queue,\n                      cl_command_queue_info param_name,\n                      size_t                param_value_size,\n                      void *                param_value,\n                      size_t *              param_value_size_ret)\n{\n  f_clGetCommandQueueInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetCommandQueueInfo) dlsym(so_handle, \"clGetCommandQueueInfo\");\n  if(func) {\n    return func(command_queue, param_name, param_value_size,\n                param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_mem\nclCreateBuffer(cl_context   context,\n               cl_mem_flags flags,\n               size_t       size,\n               void *       host_ptr,\n               cl_int *     errcode_ret)\n{\n  f_clCreateBuffer func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateBuffer) dlsym(so_handle, \"clCreateBuffer\");\n  if(func) {\n    return func(context, flags, size, host_ptr, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_mem\nclCreateSubBuffer(cl_mem                   buffer,\n                  cl_mem_flags             flags,\n                  cl_buffer_create_type    buffer_create_type,\n                  const void *             buffer_create_info,\n                  cl_int *                 errcode_ret)\n{\n  f_clCreateSubBuffer func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateSubBuffer) dlsym(so_handle, \"clCreateSubBuffer\");\n  if(func) {\n    return func(buffer, flags, buffer_create_type,\n                buffer_create_info, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_mem\nclCreateImage(cl_context              context,\n              cl_mem_flags            flags,\n              const cl_image_format * image_format,\n              const cl_image_desc *   image_desc,\n              void *                  host_ptr,\n              cl_int *                errcode_ret)\n{\n  f_clCreateImage func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateImage) dlsym(so_handle, \"clCreateImage\");\n  if(func) {\n    return func(context, flags, image_format, image_desc,\n                host_ptr, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_int\nclRetainMemObject(cl_mem memobj)\n{\n  f_clRetainMemObject func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clRetainMemObject) dlsym(so_handle, \"clRetainMemObject\");\n  if(func) {\n    return func(memobj);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclReleaseMemObject(cl_mem memobj)\n{\n  f_clReleaseMemObject func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clReleaseMemObject) dlsym(so_handle, \"clReleaseMemObject\");\n  if(func) {\n    return func(memobj);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetSupportedImageFormats(cl_context           context,\n                           cl_mem_flags         flags,\n                           cl_mem_object_type   image_type,\n                           cl_uint              num_entries,\n                           cl_image_format *    image_formats,\n                           cl_uint *            num_image_formats)\n{\n  f_clGetSupportedImageFormats func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetSupportedImageFormats) dlsym(so_handle, \"clGetSupportedImageFormats\");\n  if(func) {\n    return func(context, flags, image_type, num_entries,\n                image_formats, num_image_formats);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetMemObjectInfo(cl_mem           memobj,\n                   cl_mem_info      param_name,\n                   size_t           param_value_size,\n                   void *           param_value,\n                   size_t *         param_value_size_ret)\n{\n  f_clGetMemObjectInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetMemObjectInfo) dlsym(so_handle, \"clGetMemObjectInfo\");\n  if(func) {\n    return func(memobj, param_name, param_value_size,\n                param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetImageInfo(cl_mem           image,\n               cl_image_info    param_name,\n               size_t           param_value_size,\n               void *           param_value,\n               size_t *         param_value_size_ret)\n{\n  f_clGetImageInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetImageInfo) dlsym(so_handle, \"clGetImageInfo\");\n  if(func) {\n    return func(image, param_name, param_value_size,\n                param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclSetMemObjectDestructorCallback(  cl_mem memobj,\n                                   void (*pfn_notify)( cl_mem memobj, void* user_data),\n                                   void * user_data )\n{\n  f_clSetMemObjectDestructorCallback func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clSetMemObjectDestructorCallback) dlsym(so_handle, \"clSetMemObjectDestructorCallback\");\n  if(func) {\n    return func(memobj, pfn_notify, user_data);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_sampler\nclCreateSampler(cl_context          context,\n                cl_bool             normalized_coords,\n                cl_addressing_mode  addressing_mode,\n                cl_filter_mode      filter_mode,\n                cl_int *            errcode_ret)\n{\n  f_clCreateSampler func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateSampler) dlsym(so_handle, \"clCreateSampler\");\n  if(func) {\n    return func(context, normalized_coords, addressing_mode, filter_mode, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_int\nclRetainSampler(cl_sampler sampler)\n{\n  f_clRetainSampler func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clRetainSampler) dlsym(so_handle, \"clRetainSampler\");\n  if(func) {\n    return func(sampler);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclReleaseSampler(cl_sampler sampler)\n{\n  f_clReleaseSampler func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clReleaseSampler) dlsym(so_handle, \"clReleaseSampler\");\n  if(func) {\n    return func(sampler);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetSamplerInfo(cl_sampler         sampler,\n                 cl_sampler_info    param_name,\n                 size_t             param_value_size,\n                 void *             param_value,\n                 size_t *           param_value_size_ret)\n{\n  f_clGetSamplerInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetSamplerInfo) dlsym(so_handle, \"clGetSamplerInfo\");\n  if(func) {\n    return func(sampler, param_name, param_value_size, param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_program\nclCreateProgramWithSource(cl_context        context,\n                          cl_uint           count,\n                          const char **     strings,\n                          const size_t *    lengths,\n                          cl_int *          errcode_ret)\n{\n  f_clCreateProgramWithSource func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateProgramWithSource) dlsym(so_handle, \"clCreateProgramWithSource\");\n  if(func) {\n    return func(context, count, strings, lengths, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\n\n\ncl_program\nclCreateProgramWithBinary(cl_context                     context,\n                          cl_uint                        num_devices,\n                          const cl_device_id *           device_list,\n                          const size_t *                 lengths,\n                          const unsigned char **         binaries,\n                          cl_int *                       binary_status,\n                          cl_int *                       errcode_ret)\n{\n  f_clCreateProgramWithBinary func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateProgramWithBinary) dlsym(so_handle, \"clCreateProgramWithBinary\");\n  if(func) {\n    return func(context, num_devices, device_list, lengths, binaries, binary_status, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_program\nclCreateProgramWithBuiltInKernels(cl_context            context,\n                                  cl_uint               num_devices,\n                                  const cl_device_id *  device_list,\n                                  const char *          kernel_names,\n                                  cl_int *              errcode_ret)\n{\n  f_clCreateProgramWithBuiltInKernels func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateProgramWithBuiltInKernels) dlsym(so_handle, \"clCreateProgramWithBuiltInKernels\");\n  if(func) {\n    return func(context, num_devices, device_list, kernel_names, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_int\nclRetainProgram(cl_program program)\n{\n  f_clRetainProgram func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clRetainProgram) dlsym(so_handle, \"clRetainProgram\");\n  if(func) {\n    return func(program);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclReleaseProgram(cl_program program)\n{\n  f_clReleaseProgram func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clReleaseProgram) dlsym(so_handle, \"clReleaseProgram\");\n  if(func) {\n    return func(program);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclBuildProgram(cl_program           program,\n               cl_uint              num_devices,\n               const cl_device_id * device_list,\n               const char *         options,\n               void (*pfn_notify)(cl_program program, void * user_data),\n               void *               user_data)\n{\n  f_clBuildProgram func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clBuildProgram) dlsym(so_handle, \"clBuildProgram\");\n  if(func) {\n    return func(program, num_devices, device_list, options, pfn_notify, user_data);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclCompileProgram(cl_program           program,\n                 cl_uint              num_devices,\n                 const cl_device_id * device_list,\n                 const char *         options,\n                 cl_uint              num_input_headers,\n                 const cl_program *   input_headers,\n                 const char **        header_include_names,\n                 void (*pfn_notify)(cl_program program, void * user_data),\n                 void *               user_data)\n{\n  f_clCompileProgram func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCompileProgram) dlsym(so_handle, \"clCompileProgram\");\n  if(func) {\n    return func(program, num_devices, device_list, options, num_input_headers, input_headers,\n                header_include_names, pfn_notify, user_data);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_program\nclLinkProgram(cl_context           context,\n              cl_uint              num_devices,\n              const cl_device_id * device_list,\n              const char *         options,\n              cl_uint              num_input_programs,\n              const cl_program *   input_programs,\n              void (*pfn_notify)(cl_program program, void * user_data),\n              void *               user_data,\n              cl_int *             errcode_ret)\n{\n  f_clLinkProgram func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clLinkProgram) dlsym(so_handle, \"clLinkProgram\");\n  if(func) {\n    return func(context, num_devices, device_list, options, num_input_programs,\n                input_programs, pfn_notify, user_data, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\n\ncl_int\nclUnloadPlatformCompiler(cl_platform_id platform)\n{\n  f_clUnloadPlatformCompiler func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clUnloadPlatformCompiler) dlsym(so_handle, \"clUnloadPlatformCompiler\");\n  if(func) {\n    return func(platform);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetProgramInfo(cl_program         program,\n                 cl_program_info    param_name,\n                 size_t             param_value_size,\n                 void *             param_value,\n                 size_t *           param_value_size_ret)\n{\n  f_clGetProgramInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetProgramInfo) dlsym(so_handle, \"clGetProgramInfo\");\n  if(func) {\n    return func(program, param_name, param_value_size,\n                param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetProgramBuildInfo(cl_program            program,\n                      cl_device_id          device,\n                      cl_program_build_info param_name,\n                      size_t                param_value_size,\n                      void *                param_value,\n                      size_t *              param_value_size_ret)\n{\n  f_clGetProgramBuildInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetProgramBuildInfo) dlsym(so_handle, \"clGetProgramBuildInfo\");\n  if(func) {\n    return func(program, device, param_name, param_value_size,\n                param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_kernel\nclCreateKernel(cl_program      program,\n               const char *    kernel_name,\n               cl_int *        errcode_ret)\n{\n  f_clCreateKernel func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateKernel) dlsym(so_handle, \"clCreateKernel\");\n  if(func) {\n    return func(program, kernel_name, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_int\nclCreateKernelsInProgram(cl_program     program,\n                         cl_uint        num_kernels,\n                         cl_kernel *    kernels,\n                         cl_uint *      num_kernels_ret)\n{\n  f_clCreateKernelsInProgram func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateKernelsInProgram) dlsym(so_handle, \"clCreateKernelsInProgram\");\n  if(func) {\n    return func(program, num_kernels, kernels, num_kernels_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclRetainKernel(cl_kernel    kernel)\n{\n  f_clRetainKernel func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clRetainKernel) dlsym(so_handle, \"clRetainKernel\");\n  if(func) {\n    return func(kernel);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclReleaseKernel(cl_kernel   kernel)\n{\n  f_clReleaseKernel func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clReleaseKernel) dlsym(so_handle, \"clReleaseKernel\");\n  if(func) {\n    return func(kernel);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclSetKernelArg(cl_kernel    kernel,\n               cl_uint      arg_index,\n               size_t       arg_size,\n               const void * arg_value)\n{\n  f_clSetKernelArg func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clSetKernelArg) dlsym(so_handle, \"clSetKernelArg\");\n  if(func) {\n    return func(kernel, arg_index, arg_size, arg_value);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetKernelInfo(cl_kernel       kernel,\n                cl_kernel_info  param_name,\n                size_t          param_value_size,\n                void *          param_value,\n                size_t *        param_value_size_ret)\n{\n  f_clGetKernelInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetKernelInfo) dlsym(so_handle, \"clGetKernelInfo\");\n  if(func) {\n    return func(kernel, param_name, param_value_size, param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetKernelArgInfo(cl_kernel       kernel,\n                   cl_uint         arg_indx,\n                   cl_kernel_arg_info  param_name,\n                   size_t          param_value_size,\n                   void *          param_value,\n                   size_t *        param_value_size_ret)\n{\n  f_clGetKernelArgInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetKernelArgInfo) dlsym(so_handle, \"clGetKernelArgInfo\");\n  if(func) {\n    return func(kernel, arg_indx, param_name, param_value_size,\n                param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetKernelWorkGroupInfo(cl_kernel                  kernel,\n                         cl_device_id               device,\n                         cl_kernel_work_group_info  param_name,\n                         size_t                     param_value_size,\n                         void *                     param_value,\n                         size_t *                   param_value_size_ret)\n{\n  f_clGetKernelWorkGroupInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetKernelWorkGroupInfo) dlsym(so_handle, \"clGetKernelWorkGroupInfo\");\n  if(func) {\n    return func(kernel, device, param_name, param_value_size, param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_int\nclWaitForEvents(cl_uint             num_events,\n                const cl_event *    event_list)\n{\n  f_clWaitForEvents func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clWaitForEvents) dlsym(so_handle, \"clWaitForEvents\");\n  if(func) {\n    return func(num_events, event_list);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_int\nclGetEventInfo(cl_event         event,\n               cl_event_info    param_name,\n               size_t           param_value_size,\n               void *           param_value,\n               size_t *         param_value_size_ret)\n{\n  f_clGetEventInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetEventInfo) dlsym(so_handle, \"clGetEventInfo\");\n  if(func) {\n    return func(event, param_name, param_value_size, param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_event\nclCreateUserEvent(cl_context    context,\n                  cl_int *      errcode_ret)\n{\n  f_clCreateUserEvent func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateUserEvent) dlsym(so_handle, \"clCreateUserEvent\");\n  if(func) {\n    return func(context, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_int\nclRetainEvent(cl_event event)\n{\n  f_clRetainEvent func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clRetainEvent) dlsym(so_handle, \"clRetainEvent\");\n  if(func) {\n    return func(event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclReleaseEvent(cl_event event)\n{\n  f_clReleaseEvent func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clReleaseEvent) dlsym(so_handle, \"clReleaseEvent\");\n  if(func) {\n    return func(event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclSetUserEventStatus(cl_event   event,\n                     cl_int     execution_status)\n{\n  f_clSetUserEventStatus func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clSetUserEventStatus) dlsym(so_handle, \"clSetUserEventStatus\");\n  if(func) {\n    return func(event, execution_status);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclSetEventCallback( cl_event    event,\n                    cl_int      command_exec_callback_type,\n                    void (*pfn_notify)(cl_event, cl_int, void *),\n                    void *      user_data)\n{\n  f_clSetEventCallback func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clSetEventCallback) dlsym(so_handle, \"clSetEventCallback\");\n  if(func) {\n    return func(event, command_exec_callback_type, pfn_notify, user_data);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetEventProfilingInfo(cl_event            event,\n                        cl_profiling_info   param_name,\n                        size_t              param_value_size,\n                        void *              param_value,\n                        size_t *            param_value_size_ret)\n{\n  f_clGetEventProfilingInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetEventProfilingInfo) dlsym(so_handle, \"clGetEventProfilingInfo\");\n  if(func) {\n    return func(event, param_name, param_value_size, param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclFlush(cl_command_queue command_queue)\n{\n  f_clFlush func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clFlush) dlsym(so_handle, \"clFlush\");\n  if(func) {\n    return func(command_queue);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclFinish(cl_command_queue command_queue)\n{\n  f_clFinish func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clFinish) dlsym(so_handle, \"clFinish\");\n  if(func) {\n    return func(command_queue);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_int\nclEnqueueReadBuffer(cl_command_queue    command_queue,\n                    cl_mem              buffer,\n                    cl_bool             blocking_read,\n                    size_t              offset,\n                    size_t              size,\n                    void *              ptr,\n                    cl_uint             num_events_in_wait_list,\n                    const cl_event *    event_wait_list,\n                    cl_event *          event)\n{\n  f_clEnqueueReadBuffer func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueReadBuffer) dlsym(so_handle, \"clEnqueueReadBuffer\");\n  if(func) {\n    return func(command_queue, buffer, blocking_read, offset, size, ptr,\n                num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueReadBufferRect(cl_command_queue    command_queue,\n                        cl_mem              buffer,\n                        cl_bool             blocking_read,\n                        const size_t *      buffer_offset,\n                        const size_t *      host_offset,\n                        const size_t *      region,\n                        size_t              buffer_row_pitch,\n                        size_t              buffer_slice_pitch,\n                        size_t              host_row_pitch,\n                        size_t              host_slice_pitch,\n                        void *              ptr,\n                        cl_uint             num_events_in_wait_list,\n                        const cl_event *    event_wait_list,\n                        cl_event *          event)\n{\n  f_clEnqueueReadBufferRect func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueReadBufferRect) dlsym(so_handle, \"clEnqueueReadBufferRect\");\n  if(func) {\n    return func(command_queue, buffer, blocking_read, buffer_offset, host_offset, region,\n                buffer_row_pitch, buffer_slice_pitch, host_row_pitch, host_slice_pitch, ptr,\n                num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueWriteBuffer(cl_command_queue   command_queue,\n                     cl_mem             buffer,\n                     cl_bool            blocking_write,\n                     size_t             offset,\n                     size_t             size,\n                     const void *       ptr,\n                     cl_uint            num_events_in_wait_list,\n                     const cl_event *   event_wait_list,\n                     cl_event *         event)\n{\n  f_clEnqueueWriteBuffer func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueWriteBuffer) dlsym(so_handle, \"clEnqueueWriteBuffer\");\n  if(func) {\n    return func(command_queue, buffer, blocking_write, offset, size, ptr,\n                num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_int\nclEnqueueWriteBufferRect(cl_command_queue    command_queue,\n                         cl_mem              buffer,\n                         cl_bool             blocking_write,\n                         const size_t *      buffer_offset,\n                         const size_t *      host_offset,\n                         const size_t *      region,\n                         size_t              buffer_row_pitch,\n                         size_t              buffer_slice_pitch,\n                         size_t              host_row_pitch,\n                         size_t              host_slice_pitch,\n                         const void *        ptr,\n                         cl_uint             num_events_in_wait_list,\n                         const cl_event *    event_wait_list,\n                         cl_event *          event)\n{\n  f_clEnqueueWriteBufferRect func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueWriteBufferRect) dlsym(so_handle, \"clEnqueueWriteBufferRect\");\n  if(func) {\n    return func(command_queue, buffer, blocking_write, buffer_offset, host_offset, region,\n                buffer_row_pitch, buffer_slice_pitch, host_row_pitch, host_slice_pitch,\n                ptr, num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_int\nclEnqueueFillBuffer(cl_command_queue   command_queue,\n                    cl_mem             buffer,\n                    const void *       pattern,\n                    size_t             pattern_size,\n                    size_t             offset,\n                    size_t             size,\n                    cl_uint            num_events_in_wait_list,\n                    const cl_event *   event_wait_list,\n                    cl_event *         event)\n{\n  f_clEnqueueFillBuffer func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueFillBuffer) dlsym(so_handle, \"clEnqueueFillBuffer\");\n  if(func) {\n    return func(command_queue, buffer, pattern, pattern_size, offset, size,\n                num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueCopyBuffer(cl_command_queue    command_queue,\n                    cl_mem              src_buffer,\n                    cl_mem              dst_buffer,\n                    size_t              src_offset,\n                    size_t              dst_offset,\n                    size_t              size,\n                    cl_uint             num_events_in_wait_list,\n                    const cl_event *    event_wait_list,\n                    cl_event *          event)\n{\n  f_clEnqueueCopyBuffer func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueCopyBuffer) dlsym(so_handle, \"clEnqueueCopyBuffer\");\n  if(func) {\n    return func(command_queue, src_buffer, dst_buffer, src_offset, dst_offset, size,\n                num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\n\ncl_int\nclEnqueueCopyBufferRect(cl_command_queue    command_queue,\n                        cl_mem              src_buffer,\n                        cl_mem              dst_buffer,\n                        const size_t *      src_origin,\n                        const size_t *      dst_origin,\n                        const size_t *      region,\n                        size_t              src_row_pitch,\n                        size_t              src_slice_pitch,\n                        size_t              dst_row_pitch,\n                        size_t              dst_slice_pitch,\n                        cl_uint             num_events_in_wait_list,\n                        const cl_event *    event_wait_list,\n                        cl_event *          event)\n{\n  f_clEnqueueCopyBufferRect func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueCopyBufferRect) dlsym(so_handle, \"clEnqueueCopyBufferRect\");\n  if(func) {\n    return func(command_queue, src_buffer, dst_buffer, src_origin, dst_origin, region, src_row_pitch,\n                src_slice_pitch, dst_row_pitch, dst_slice_pitch, num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueReadImage(cl_command_queue     command_queue,\n                   cl_mem               image,\n                   cl_bool              blocking_read,\n                   const size_t *       origin,\n                   const size_t *       region,\n                   size_t               row_pitch,\n                   size_t               slice_pitch,\n                   void *               ptr,\n                   cl_uint              num_events_in_wait_list,\n                   const cl_event *     event_wait_list,\n                   cl_event *           event)\n{\n  f_clEnqueueReadImage func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueReadImage) dlsym(so_handle, \"clEnqueueReadImage\");\n  if(func) {\n    return func(command_queue, image, blocking_read, origin, region, row_pitch, slice_pitch,\n                ptr, num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueWriteImage(cl_command_queue    command_queue,\n                    cl_mem              image,\n                    cl_bool             blocking_write,\n                    const size_t *      origin,\n                    const size_t *      region,\n                    size_t              input_row_pitch,\n                    size_t              input_slice_pitch,\n                    const void *        ptr,\n                    cl_uint             num_events_in_wait_list,\n                    const cl_event *    event_wait_list,\n                    cl_event *          event)\n{\n  f_clEnqueueWriteImage func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueWriteImage) dlsym(so_handle, \"clEnqueueWriteImage\");\n  if(func) {\n    return func(command_queue, image, blocking_write, origin, region, input_row_pitch, input_slice_pitch, ptr,\n                num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_int\nclEnqueueFillImage(cl_command_queue   command_queue,\n                   cl_mem             image,\n                   const void *       fill_color,\n                   const size_t *     origin,\n                   const size_t *     region,\n                   cl_uint            num_events_in_wait_list,\n                   const cl_event *   event_wait_list,\n                   cl_event *         event)\n{\n  f_clEnqueueFillImage func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueFillImage) dlsym(so_handle, \"clEnqueueFillImage\");\n  if(func) {\n    return func(command_queue, image, fill_color, origin, region, num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueCopyImage(cl_command_queue     command_queue,\n                   cl_mem               src_image,\n                   cl_mem               dst_image,\n                   const size_t *       src_origin,\n                   const size_t *       dst_origin,\n                   const size_t *       region,\n                   cl_uint              num_events_in_wait_list,\n                   const cl_event *     event_wait_list,\n                   cl_event *           event)\n{\n  f_clEnqueueCopyImage func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueCopyImage) dlsym(so_handle, \"clEnqueueCopyImage\");\n  if(func) {\n    return func(command_queue, src_image, dst_image, src_origin, dst_origin, region,\n                num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueCopyImageToBuffer(cl_command_queue command_queue,\n                           cl_mem           src_image,\n                           cl_mem           dst_buffer,\n                           const size_t *   src_origin,\n                           const size_t *   region,\n                           size_t           dst_offset,\n                           cl_uint          num_events_in_wait_list,\n                           const cl_event * event_wait_list,\n                           cl_event *       event)\n{\n  f_clEnqueueCopyImageToBuffer func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueCopyImageToBuffer) dlsym(so_handle, \"clEnqueueCopyImageToBuffer\");\n  if(func) {\n    return func(command_queue, src_image, dst_buffer, src_origin, region, dst_offset,\n                num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_int\nclEnqueueCopyBufferToImage(cl_command_queue command_queue,\n                           cl_mem           src_buffer,\n                           cl_mem           dst_image,\n                           size_t           src_offset,\n                           const size_t *   dst_origin,\n                           const size_t *   region,\n                           cl_uint          num_events_in_wait_list,\n                           const cl_event * event_wait_list,\n                           cl_event *       event)\n{\n  f_clEnqueueCopyBufferToImage func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueCopyBufferToImage) dlsym(so_handle, \"clEnqueueCopyBufferToImage\");\n  if(func) {\n    return func(command_queue, src_buffer, dst_image, src_offset, dst_origin, region,\n                num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\nvoid *\nclEnqueueMapBuffer(cl_command_queue command_queue,\n                   cl_mem           buffer,\n                   cl_bool          blocking_map,\n                   cl_map_flags     map_flags,\n                   size_t           offset,\n                   size_t           size,\n                   cl_uint          num_events_in_wait_list,\n                   const cl_event * event_wait_list,\n                   cl_event *       event,\n                   cl_int *         errcode_ret)\n{\n  f_clEnqueueMapBuffer func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueMapBuffer) dlsym(so_handle, \"clEnqueueMapBuffer\");\n  if(func) {\n    return func(command_queue, buffer, blocking_map, map_flags, offset, size,\n                num_events_in_wait_list, event_wait_list, event, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\nvoid *\nclEnqueueMapImage(cl_command_queue  command_queue,\n                  cl_mem            image,\n                  cl_bool           blocking_map,\n                  cl_map_flags      map_flags,\n                  const size_t *    origin,\n                  const size_t *    region,\n                  size_t *          image_row_pitch,\n                  size_t *          image_slice_pitch,\n                  cl_uint           num_events_in_wait_list,\n                  const cl_event *  event_wait_list,\n                  cl_event *        event,\n                  cl_int *          errcode_ret)\n{\n  f_clEnqueueMapImage func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueMapImage) dlsym(so_handle, \"clEnqueueMapImage\");\n  if(func) {\n    return func(command_queue, image, blocking_map, map_flags, origin, region, image_row_pitch,\n                image_slice_pitch, num_events_in_wait_list, event_wait_list, event, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_int\nclEnqueueUnmapMemObject(cl_command_queue command_queue,\n                        cl_mem           memobj,\n                        void *           mapped_ptr,\n                        cl_uint          num_events_in_wait_list,\n                        const cl_event *  event_wait_list,\n                        cl_event *        event)\n{\n  f_clEnqueueUnmapMemObject func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueUnmapMemObject) dlsym(so_handle, \"clEnqueueUnmapMemObject\");\n  if(func) {\n    return func(command_queue, memobj, mapped_ptr, num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueMigrateMemObjects(cl_command_queue       command_queue,\n                           cl_uint                num_mem_objects,\n                           const cl_mem *         mem_objects,\n                           cl_mem_migration_flags flags,\n                           cl_uint                num_events_in_wait_list,\n                           const cl_event *       event_wait_list,\n                           cl_event *             event)\n{\n  f_clEnqueueMigrateMemObjects func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueMigrateMemObjects) dlsym(so_handle, \"clEnqueueMigrateMemObjects\");\n  if(func) {\n    return func(command_queue, num_mem_objects, mem_objects, flags, num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueNDRangeKernel(cl_command_queue command_queue,\n                       cl_kernel        kernel,\n                       cl_uint          work_dim,\n                       const size_t *   global_work_offset,\n                       const size_t *   global_work_size,\n                       const size_t *   local_work_size,\n                       cl_uint          num_events_in_wait_list,\n                       const cl_event * event_wait_list,\n                       cl_event *       event)\n{\n  f_clEnqueueNDRangeKernel func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueNDRangeKernel) dlsym(so_handle, \"clEnqueueNDRangeKernel\");\n  if(func) {\n    return func(command_queue, kernel, work_dim, global_work_offset, global_work_size, local_work_size,\n                num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueTask(cl_command_queue  command_queue,\n              cl_kernel         kernel,\n              cl_uint           num_events_in_wait_list,\n              const cl_event *  event_wait_list,\n              cl_event *        event)\n{\n  f_clEnqueueTask func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueTask) dlsym(so_handle, \"clEnqueueTask\");\n  if(func) {\n    return func(command_queue, kernel, num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueNativeKernel(cl_command_queue  command_queue,\n                      void (*user_func)(void *),\n                      void *            args,\n                      size_t            cb_args,\n                      cl_uint           num_mem_objects,\n                      const cl_mem *    mem_list,\n                      const void **     args_mem_loc,\n                      cl_uint           num_events_in_wait_list,\n                      const cl_event *  event_wait_list,\n                      cl_event *        event)\n{\n  f_clEnqueueNativeKernel func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueNativeKernel) dlsym(so_handle, \"clEnqueueNativeKernel\");\n  if(func) {\n    return func(command_queue, user_func, args, cb_args, num_mem_objects, mem_list,\n                args_mem_loc, num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueMarkerWithWaitList(cl_command_queue command_queue,\n                            cl_uint           num_events_in_wait_list,\n                            const cl_event *  event_wait_list,\n                            cl_event *        event)\n{\n  f_clEnqueueMarkerWithWaitList func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueMarkerWithWaitList) dlsym(so_handle, \"clEnqueueMarkerWithWaitList\");\n  if(func) {\n    return func(command_queue, num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueBarrierWithWaitList(cl_command_queue command_queue,\n                             cl_uint           num_events_in_wait_list,\n                             const cl_event *  event_wait_list,\n                             cl_event *        event)\n{\n  f_clEnqueueBarrierWithWaitList func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueBarrierWithWaitList) dlsym(so_handle, \"clEnqueueBarrierWithWaitList\");\n  if(func) {\n    return func(command_queue, num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\nvoid *\nclGetExtensionFunctionAddressForPlatform(cl_platform_id platform,\n                                         const char *   func_name)\n{\n  f_clGetExtensionFunctionAddressForPlatform func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetExtensionFunctionAddressForPlatform) dlsym(so_handle, \"clGetExtensionFunctionAddressForPlatform\");\n  if(func) {\n    return func(platform, func_name);\n  } else {\n    return NULL;\n  }\n}\n\n\ncl_mem\nclCreateImage2D(cl_context              context,\n                cl_mem_flags            flags,\n                const cl_image_format * image_format,\n                size_t                  image_width,\n                size_t                  image_height,\n                size_t                  image_row_pitch,\n                void *                  host_ptr,\n                cl_int *                errcode_ret)\n{\n  f_clCreateImage2D func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateImage2D) dlsym(so_handle, \"clCreateImage2D\");\n  if(func) {\n    return func(context, flags, image_format, image_width, image_height,\n                image_row_pitch, host_ptr, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_mem\nclCreateImage3D(cl_context              context,\n                cl_mem_flags            flags,\n                const cl_image_format * image_format,\n                size_t                  image_width,\n                size_t                  image_height,\n                size_t                  image_depth,\n                size_t                  image_row_pitch,\n                size_t                  image_slice_pitch,\n                void *                  host_ptr,\n                cl_int *                errcode_ret)\n{\n  f_clCreateImage3D func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateImage3D) dlsym(so_handle, \"clCreateImage3D\");\n  if(func) {\n    return func(context, flags, image_format, image_width, image_height, image_depth,\n                image_row_pitch, image_slice_pitch, host_ptr, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_int\nclEnqueueMarker(cl_command_queue    command_queue,\n                cl_event *          event)\n{\n  f_clEnqueueMarker func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueMarker) dlsym(so_handle, \"clEnqueueMarker\");\n  if(func) {\n    return func(command_queue, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueWaitForEvents(cl_command_queue command_queue,\n                       cl_uint          num_events,\n                       const cl_event * event_list)\n{\n  f_clEnqueueWaitForEvents func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueWaitForEvents) dlsym(so_handle, \"clEnqueueWaitForEvents\");\n  if(func) {\n    return func(command_queue, num_events, event_list);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueBarrier(cl_command_queue command_queue)\n{\n  f_clEnqueueBarrier func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueBarrier) dlsym(so_handle, \"clEnqueueBarrier\");\n  if(func) {\n    return func(command_queue);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclUnloadCompiler(void)\n{\n  f_clUnloadCompiler func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clUnloadCompiler) dlsym(so_handle, \"clUnloadCompiler\");\n  if(func) {\n    return func();\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\nvoid *\nclGetExtensionFunctionAddress(const char * func_name)\n{\n  f_clGetExtensionFunctionAddress func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetExtensionFunctionAddress) dlsym(so_handle, \"clGetExtensionFunctionAddress\");\n  if(func) {\n    return func(func_name);\n  } else {\n    return NULL;\n  }\n}\n\n\ncl_mem\nclCreateFromGLBuffer(cl_context     context,\n                     cl_mem_flags   flags,\n                     cl_GLuint      bufobj,\n                     int *          errcode_ret)\n{\n  f_clCreateFromGLBuffer func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateFromGLBuffer) dlsym(so_handle, \"clCreateFromGLBuffer\");\n  if(func) {\n    return func(context, flags, bufobj, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_mem\nclCreateFromGLTexture(cl_context      context,\n                      cl_mem_flags    flags,\n                      cl_GLenum       target,\n                      cl_GLint        miplevel,\n                      cl_GLuint       texture,\n                      cl_int *        errcode_ret)\n{\n  f_clCreateFromGLTexture func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateFromGLTexture) dlsym(so_handle, \"clCreateFromGLTexture\");\n  if(func) {\n    return func(context, flags, target, miplevel, texture, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_mem\nclCreateFromGLRenderbuffer(cl_context   context,\n                           cl_mem_flags flags,\n                           cl_GLuint    renderbuffer,\n                           cl_int *     errcode_ret)\n{\n  f_clCreateFromGLRenderbuffer func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateFromGLRenderbuffer) dlsym(so_handle, \"clCreateFromGLRenderbuffer\");\n  if(func) {\n    return func(context, flags, renderbuffer, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_int\nclGetGLObjectInfo(cl_mem                memobj,\n                  cl_gl_object_type *   gl_object_type,\n                  cl_GLuint *           gl_object_name)\n{\n  f_clGetGLObjectInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetGLObjectInfo) dlsym(so_handle, \"clGetGLObjectInfo\");\n  if(func) {\n    return func(memobj, gl_object_type, gl_object_name);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclGetGLTextureInfo(cl_mem               memobj,\n                   cl_gl_texture_info   param_name,\n                   size_t               param_value_size,\n                   void *               param_value,\n                   size_t *             param_value_size_ret)\n{\n  f_clGetGLTextureInfo func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetGLTextureInfo) dlsym(so_handle, \"clGetGLTextureInfo\");\n  if(func) {\n    return func(memobj, param_name, param_value_size, param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueAcquireGLObjects(cl_command_queue      command_queue,\n                          cl_uint               num_objects,\n                          const cl_mem *        mem_objects,\n                          cl_uint               num_events_in_wait_list,\n                          const cl_event *      event_wait_list,\n                          cl_event *            event)\n{\n  f_clEnqueueAcquireGLObjects func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueAcquireGLObjects) dlsym(so_handle, \"clEnqueueAcquireGLObjects\");\n  if(func) {\n    return func(command_queue, num_objects, mem_objects, num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\ncl_int\nclEnqueueReleaseGLObjects(cl_command_queue      command_queue,\n                          cl_uint               num_objects,\n                          const cl_mem *        mem_objects,\n                          cl_uint               num_events_in_wait_list,\n                          const cl_event *      event_wait_list,\n                          cl_event *            event)\n{\n  f_clEnqueueReleaseGLObjects func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clEnqueueReleaseGLObjects) dlsym(so_handle, \"clEnqueueReleaseGLObjects\");\n  if(func) {\n    return func(command_queue, num_objects, mem_objects, num_events_in_wait_list, event_wait_list, event);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n\n\ncl_mem\nclCreateFromGLTexture2D(cl_context      context,\n                        cl_mem_flags    flags,\n                        cl_GLenum       target,\n                        cl_GLint        miplevel,\n                        cl_GLuint       texture,\n                        cl_int *        errcode_ret)\n{\n  f_clCreateFromGLTexture2D func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateFromGLTexture2D) dlsym(so_handle, \"clCreateFromGLTexture2D\");\n  if(func) {\n    return func(context, flags, target, miplevel, texture, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_mem\nclCreateFromGLTexture3D(cl_context      context,\n                        cl_mem_flags    flags,\n                        cl_GLenum       target,\n                        cl_GLint        miplevel,\n                        cl_GLuint       texture,\n                        cl_int *        errcode_ret)\n{\n  f_clCreateFromGLTexture3D func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clCreateFromGLTexture3D) dlsym(so_handle, \"clCreateFromGLTexture3D\");\n  if(func) {\n    return func(context, flags, target, miplevel, texture, errcode_ret);\n  } else {\n    return NULL;\n  }\n}\n\ncl_int\nclGetGLContextInfoKHR(const cl_context_properties * properties,\n                      cl_gl_context_info            param_name,\n                      size_t                        param_value_size,\n                      void *                        param_value,\n                      size_t *                      param_value_size_ret)\n{\n  f_clGetGLContextInfoKHR func;\n\n  if(!so_handle)\n    open_libopencl_so();\n\n  func = (f_clGetGLContextInfoKHR) dlsym(so_handle, \"clGetGLContextInfoKHR\");\n  if(func) {\n    return func(properties, param_name, param_value_size, param_value, param_value_size_ret);\n  } else {\n    return CL_INVALID_PLATFORM;\n  }\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/libvulkan-stub/include/libvulkan-stub.h",
    "content": "/*\n* Copyright (c) 2016-2017, ARM Limited and Contributors\n*\n* SPDX-License-Identifier: MIT\n* \n* Permission is hereby granted, free of charge,\n* to any person obtaining a copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation the rights to\n* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,\n* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n* \n* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n* \n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n*/\n\n/* This header is autogenerated by vulkan_loader_generator.py */\n#ifndef VULKAN_SYMBOL_WRAPPER_H\n#define VULKAN_SYMBOL_WRAPPER_H\n#define VK_NO_PROTOTYPES\n#include <vulkan/vulkan.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nextern PFN_vkCreateInstance vulkanSymbolWrapper_vkCreateInstance;\n#define vkCreateInstance vulkanSymbolWrapper_vkCreateInstance\nextern PFN_vkEnumerateInstanceExtensionProperties vulkanSymbolWrapper_vkEnumerateInstanceExtensionProperties;\n#define vkEnumerateInstanceExtensionProperties vulkanSymbolWrapper_vkEnumerateInstanceExtensionProperties\nextern PFN_vkEnumerateInstanceLayerProperties vulkanSymbolWrapper_vkEnumerateInstanceLayerProperties;\n#define vkEnumerateInstanceLayerProperties vulkanSymbolWrapper_vkEnumerateInstanceLayerProperties\nextern PFN_vkDestroyInstance vulkanSymbolWrapper_vkDestroyInstance;\n#define vkDestroyInstance vulkanSymbolWrapper_vkDestroyInstance\nextern PFN_vkEnumeratePhysicalDevices vulkanSymbolWrapper_vkEnumeratePhysicalDevices;\n#define vkEnumeratePhysicalDevices vulkanSymbolWrapper_vkEnumeratePhysicalDevices\nextern PFN_vkGetPhysicalDeviceFeatures vulkanSymbolWrapper_vkGetPhysicalDeviceFeatures;\n#define vkGetPhysicalDeviceFeatures vulkanSymbolWrapper_vkGetPhysicalDeviceFeatures\nextern PFN_vkGetPhysicalDeviceFormatProperties vulkanSymbolWrapper_vkGetPhysicalDeviceFormatProperties;\n#define vkGetPhysicalDeviceFormatProperties vulkanSymbolWrapper_vkGetPhysicalDeviceFormatProperties\nextern PFN_vkGetPhysicalDeviceImageFormatProperties vulkanSymbolWrapper_vkGetPhysicalDeviceImageFormatProperties;\n#define vkGetPhysicalDeviceImageFormatProperties vulkanSymbolWrapper_vkGetPhysicalDeviceImageFormatProperties\nextern PFN_vkGetPhysicalDeviceProperties vulkanSymbolWrapper_vkGetPhysicalDeviceProperties;\n#define vkGetPhysicalDeviceProperties vulkanSymbolWrapper_vkGetPhysicalDeviceProperties\nextern PFN_vkGetPhysicalDeviceQueueFamilyProperties vulkanSymbolWrapper_vkGetPhysicalDeviceQueueFamilyProperties;\n#define vkGetPhysicalDeviceQueueFamilyProperties vulkanSymbolWrapper_vkGetPhysicalDeviceQueueFamilyProperties\nextern PFN_vkGetPhysicalDeviceMemoryProperties vulkanSymbolWrapper_vkGetPhysicalDeviceMemoryProperties;\n#define vkGetPhysicalDeviceMemoryProperties vulkanSymbolWrapper_vkGetPhysicalDeviceMemoryProperties\nextern PFN_vkGetDeviceProcAddr vulkanSymbolWrapper_vkGetDeviceProcAddr;\n#define vkGetDeviceProcAddr vulkanSymbolWrapper_vkGetDeviceProcAddr\nextern PFN_vkCreateDevice vulkanSymbolWrapper_vkCreateDevice;\n#define vkCreateDevice vulkanSymbolWrapper_vkCreateDevice\nextern PFN_vkDestroyDevice vulkanSymbolWrapper_vkDestroyDevice;\n#define vkDestroyDevice vulkanSymbolWrapper_vkDestroyDevice\nextern PFN_vkEnumerateDeviceExtensionProperties vulkanSymbolWrapper_vkEnumerateDeviceExtensionProperties;\n#define vkEnumerateDeviceExtensionProperties vulkanSymbolWrapper_vkEnumerateDeviceExtensionProperties\nextern PFN_vkEnumerateDeviceLayerProperties vulkanSymbolWrapper_vkEnumerateDeviceLayerProperties;\n#define vkEnumerateDeviceLayerProperties vulkanSymbolWrapper_vkEnumerateDeviceLayerProperties\nextern PFN_vkGetDeviceQueue vulkanSymbolWrapper_vkGetDeviceQueue;\n#define vkGetDeviceQueue vulkanSymbolWrapper_vkGetDeviceQueue\nextern PFN_vkQueueSubmit vulkanSymbolWrapper_vkQueueSubmit;\n#define vkQueueSubmit vulkanSymbolWrapper_vkQueueSubmit\nextern PFN_vkQueueWaitIdle vulkanSymbolWrapper_vkQueueWaitIdle;\n#define vkQueueWaitIdle vulkanSymbolWrapper_vkQueueWaitIdle\nextern PFN_vkDeviceWaitIdle vulkanSymbolWrapper_vkDeviceWaitIdle;\n#define vkDeviceWaitIdle vulkanSymbolWrapper_vkDeviceWaitIdle\nextern PFN_vkAllocateMemory vulkanSymbolWrapper_vkAllocateMemory;\n#define vkAllocateMemory vulkanSymbolWrapper_vkAllocateMemory\nextern PFN_vkFreeMemory vulkanSymbolWrapper_vkFreeMemory;\n#define vkFreeMemory vulkanSymbolWrapper_vkFreeMemory\nextern PFN_vkMapMemory vulkanSymbolWrapper_vkMapMemory;\n#define vkMapMemory vulkanSymbolWrapper_vkMapMemory\nextern PFN_vkUnmapMemory vulkanSymbolWrapper_vkUnmapMemory;\n#define vkUnmapMemory vulkanSymbolWrapper_vkUnmapMemory\nextern PFN_vkFlushMappedMemoryRanges vulkanSymbolWrapper_vkFlushMappedMemoryRanges;\n#define vkFlushMappedMemoryRanges vulkanSymbolWrapper_vkFlushMappedMemoryRanges\nextern PFN_vkInvalidateMappedMemoryRanges vulkanSymbolWrapper_vkInvalidateMappedMemoryRanges;\n#define vkInvalidateMappedMemoryRanges vulkanSymbolWrapper_vkInvalidateMappedMemoryRanges\nextern PFN_vkGetDeviceMemoryCommitment vulkanSymbolWrapper_vkGetDeviceMemoryCommitment;\n#define vkGetDeviceMemoryCommitment vulkanSymbolWrapper_vkGetDeviceMemoryCommitment\nextern PFN_vkBindBufferMemory vulkanSymbolWrapper_vkBindBufferMemory;\n#define vkBindBufferMemory vulkanSymbolWrapper_vkBindBufferMemory\nextern PFN_vkBindImageMemory vulkanSymbolWrapper_vkBindImageMemory;\n#define vkBindImageMemory vulkanSymbolWrapper_vkBindImageMemory\nextern PFN_vkGetBufferMemoryRequirements vulkanSymbolWrapper_vkGetBufferMemoryRequirements;\n#define vkGetBufferMemoryRequirements vulkanSymbolWrapper_vkGetBufferMemoryRequirements\nextern PFN_vkGetImageMemoryRequirements vulkanSymbolWrapper_vkGetImageMemoryRequirements;\n#define vkGetImageMemoryRequirements vulkanSymbolWrapper_vkGetImageMemoryRequirements\nextern PFN_vkGetImageSparseMemoryRequirements vulkanSymbolWrapper_vkGetImageSparseMemoryRequirements;\n#define vkGetImageSparseMemoryRequirements vulkanSymbolWrapper_vkGetImageSparseMemoryRequirements\nextern PFN_vkGetPhysicalDeviceSparseImageFormatProperties vulkanSymbolWrapper_vkGetPhysicalDeviceSparseImageFormatProperties;\n#define vkGetPhysicalDeviceSparseImageFormatProperties vulkanSymbolWrapper_vkGetPhysicalDeviceSparseImageFormatProperties\nextern PFN_vkQueueBindSparse vulkanSymbolWrapper_vkQueueBindSparse;\n#define vkQueueBindSparse vulkanSymbolWrapper_vkQueueBindSparse\nextern PFN_vkCreateFence vulkanSymbolWrapper_vkCreateFence;\n#define vkCreateFence vulkanSymbolWrapper_vkCreateFence\nextern PFN_vkDestroyFence vulkanSymbolWrapper_vkDestroyFence;\n#define vkDestroyFence vulkanSymbolWrapper_vkDestroyFence\nextern PFN_vkResetFences vulkanSymbolWrapper_vkResetFences;\n#define vkResetFences vulkanSymbolWrapper_vkResetFences\nextern PFN_vkGetFenceStatus vulkanSymbolWrapper_vkGetFenceStatus;\n#define vkGetFenceStatus vulkanSymbolWrapper_vkGetFenceStatus\nextern PFN_vkWaitForFences vulkanSymbolWrapper_vkWaitForFences;\n#define vkWaitForFences vulkanSymbolWrapper_vkWaitForFences\nextern PFN_vkCreateSemaphore vulkanSymbolWrapper_vkCreateSemaphore;\n#define vkCreateSemaphore vulkanSymbolWrapper_vkCreateSemaphore\nextern PFN_vkDestroySemaphore vulkanSymbolWrapper_vkDestroySemaphore;\n#define vkDestroySemaphore vulkanSymbolWrapper_vkDestroySemaphore\nextern PFN_vkCreateEvent vulkanSymbolWrapper_vkCreateEvent;\n#define vkCreateEvent vulkanSymbolWrapper_vkCreateEvent\nextern PFN_vkDestroyEvent vulkanSymbolWrapper_vkDestroyEvent;\n#define vkDestroyEvent vulkanSymbolWrapper_vkDestroyEvent\nextern PFN_vkGetEventStatus vulkanSymbolWrapper_vkGetEventStatus;\n#define vkGetEventStatus vulkanSymbolWrapper_vkGetEventStatus\nextern PFN_vkSetEvent vulkanSymbolWrapper_vkSetEvent;\n#define vkSetEvent vulkanSymbolWrapper_vkSetEvent\nextern PFN_vkResetEvent vulkanSymbolWrapper_vkResetEvent;\n#define vkResetEvent vulkanSymbolWrapper_vkResetEvent\nextern PFN_vkCreateQueryPool vulkanSymbolWrapper_vkCreateQueryPool;\n#define vkCreateQueryPool vulkanSymbolWrapper_vkCreateQueryPool\nextern PFN_vkDestroyQueryPool vulkanSymbolWrapper_vkDestroyQueryPool;\n#define vkDestroyQueryPool vulkanSymbolWrapper_vkDestroyQueryPool\nextern PFN_vkGetQueryPoolResults vulkanSymbolWrapper_vkGetQueryPoolResults;\n#define vkGetQueryPoolResults vulkanSymbolWrapper_vkGetQueryPoolResults\nextern PFN_vkCreateBuffer vulkanSymbolWrapper_vkCreateBuffer;\n#define vkCreateBuffer vulkanSymbolWrapper_vkCreateBuffer\nextern PFN_vkDestroyBuffer vulkanSymbolWrapper_vkDestroyBuffer;\n#define vkDestroyBuffer vulkanSymbolWrapper_vkDestroyBuffer\nextern PFN_vkCreateBufferView vulkanSymbolWrapper_vkCreateBufferView;\n#define vkCreateBufferView vulkanSymbolWrapper_vkCreateBufferView\nextern PFN_vkDestroyBufferView vulkanSymbolWrapper_vkDestroyBufferView;\n#define vkDestroyBufferView vulkanSymbolWrapper_vkDestroyBufferView\nextern PFN_vkCreateImage vulkanSymbolWrapper_vkCreateImage;\n#define vkCreateImage vulkanSymbolWrapper_vkCreateImage\nextern PFN_vkDestroyImage vulkanSymbolWrapper_vkDestroyImage;\n#define vkDestroyImage vulkanSymbolWrapper_vkDestroyImage\nextern PFN_vkGetImageSubresourceLayout vulkanSymbolWrapper_vkGetImageSubresourceLayout;\n#define vkGetImageSubresourceLayout vulkanSymbolWrapper_vkGetImageSubresourceLayout\nextern PFN_vkCreateImageView vulkanSymbolWrapper_vkCreateImageView;\n#define vkCreateImageView vulkanSymbolWrapper_vkCreateImageView\nextern PFN_vkDestroyImageView vulkanSymbolWrapper_vkDestroyImageView;\n#define vkDestroyImageView vulkanSymbolWrapper_vkDestroyImageView\nextern PFN_vkCreateShaderModule vulkanSymbolWrapper_vkCreateShaderModule;\n#define vkCreateShaderModule vulkanSymbolWrapper_vkCreateShaderModule\nextern PFN_vkDestroyShaderModule vulkanSymbolWrapper_vkDestroyShaderModule;\n#define vkDestroyShaderModule vulkanSymbolWrapper_vkDestroyShaderModule\nextern PFN_vkCreatePipelineCache vulkanSymbolWrapper_vkCreatePipelineCache;\n#define vkCreatePipelineCache vulkanSymbolWrapper_vkCreatePipelineCache\nextern PFN_vkDestroyPipelineCache vulkanSymbolWrapper_vkDestroyPipelineCache;\n#define vkDestroyPipelineCache vulkanSymbolWrapper_vkDestroyPipelineCache\nextern PFN_vkGetPipelineCacheData vulkanSymbolWrapper_vkGetPipelineCacheData;\n#define vkGetPipelineCacheData vulkanSymbolWrapper_vkGetPipelineCacheData\nextern PFN_vkMergePipelineCaches vulkanSymbolWrapper_vkMergePipelineCaches;\n#define vkMergePipelineCaches vulkanSymbolWrapper_vkMergePipelineCaches\nextern PFN_vkCreateGraphicsPipelines vulkanSymbolWrapper_vkCreateGraphicsPipelines;\n#define vkCreateGraphicsPipelines vulkanSymbolWrapper_vkCreateGraphicsPipelines\nextern PFN_vkCreateComputePipelines vulkanSymbolWrapper_vkCreateComputePipelines;\n#define vkCreateComputePipelines vulkanSymbolWrapper_vkCreateComputePipelines\nextern PFN_vkDestroyPipeline vulkanSymbolWrapper_vkDestroyPipeline;\n#define vkDestroyPipeline vulkanSymbolWrapper_vkDestroyPipeline\nextern PFN_vkCreatePipelineLayout vulkanSymbolWrapper_vkCreatePipelineLayout;\n#define vkCreatePipelineLayout vulkanSymbolWrapper_vkCreatePipelineLayout\nextern PFN_vkDestroyPipelineLayout vulkanSymbolWrapper_vkDestroyPipelineLayout;\n#define vkDestroyPipelineLayout vulkanSymbolWrapper_vkDestroyPipelineLayout\nextern PFN_vkCreateSampler vulkanSymbolWrapper_vkCreateSampler;\n#define vkCreateSampler vulkanSymbolWrapper_vkCreateSampler\nextern PFN_vkDestroySampler vulkanSymbolWrapper_vkDestroySampler;\n#define vkDestroySampler vulkanSymbolWrapper_vkDestroySampler\nextern PFN_vkCreateDescriptorSetLayout vulkanSymbolWrapper_vkCreateDescriptorSetLayout;\n#define vkCreateDescriptorSetLayout vulkanSymbolWrapper_vkCreateDescriptorSetLayout\nextern PFN_vkDestroyDescriptorSetLayout vulkanSymbolWrapper_vkDestroyDescriptorSetLayout;\n#define vkDestroyDescriptorSetLayout vulkanSymbolWrapper_vkDestroyDescriptorSetLayout\nextern PFN_vkCreateDescriptorPool vulkanSymbolWrapper_vkCreateDescriptorPool;\n#define vkCreateDescriptorPool vulkanSymbolWrapper_vkCreateDescriptorPool\nextern PFN_vkDestroyDescriptorPool vulkanSymbolWrapper_vkDestroyDescriptorPool;\n#define vkDestroyDescriptorPool vulkanSymbolWrapper_vkDestroyDescriptorPool\nextern PFN_vkResetDescriptorPool vulkanSymbolWrapper_vkResetDescriptorPool;\n#define vkResetDescriptorPool vulkanSymbolWrapper_vkResetDescriptorPool\nextern PFN_vkAllocateDescriptorSets vulkanSymbolWrapper_vkAllocateDescriptorSets;\n#define vkAllocateDescriptorSets vulkanSymbolWrapper_vkAllocateDescriptorSets\nextern PFN_vkFreeDescriptorSets vulkanSymbolWrapper_vkFreeDescriptorSets;\n#define vkFreeDescriptorSets vulkanSymbolWrapper_vkFreeDescriptorSets\nextern PFN_vkUpdateDescriptorSets vulkanSymbolWrapper_vkUpdateDescriptorSets;\n#define vkUpdateDescriptorSets vulkanSymbolWrapper_vkUpdateDescriptorSets\nextern PFN_vkCreateFramebuffer vulkanSymbolWrapper_vkCreateFramebuffer;\n#define vkCreateFramebuffer vulkanSymbolWrapper_vkCreateFramebuffer\nextern PFN_vkDestroyFramebuffer vulkanSymbolWrapper_vkDestroyFramebuffer;\n#define vkDestroyFramebuffer vulkanSymbolWrapper_vkDestroyFramebuffer\nextern PFN_vkCreateRenderPass vulkanSymbolWrapper_vkCreateRenderPass;\n#define vkCreateRenderPass vulkanSymbolWrapper_vkCreateRenderPass\nextern PFN_vkDestroyRenderPass vulkanSymbolWrapper_vkDestroyRenderPass;\n#define vkDestroyRenderPass vulkanSymbolWrapper_vkDestroyRenderPass\nextern PFN_vkGetRenderAreaGranularity vulkanSymbolWrapper_vkGetRenderAreaGranularity;\n#define vkGetRenderAreaGranularity vulkanSymbolWrapper_vkGetRenderAreaGranularity\nextern PFN_vkCreateCommandPool vulkanSymbolWrapper_vkCreateCommandPool;\n#define vkCreateCommandPool vulkanSymbolWrapper_vkCreateCommandPool\nextern PFN_vkDestroyCommandPool vulkanSymbolWrapper_vkDestroyCommandPool;\n#define vkDestroyCommandPool vulkanSymbolWrapper_vkDestroyCommandPool\nextern PFN_vkResetCommandPool vulkanSymbolWrapper_vkResetCommandPool;\n#define vkResetCommandPool vulkanSymbolWrapper_vkResetCommandPool\nextern PFN_vkAllocateCommandBuffers vulkanSymbolWrapper_vkAllocateCommandBuffers;\n#define vkAllocateCommandBuffers vulkanSymbolWrapper_vkAllocateCommandBuffers\nextern PFN_vkFreeCommandBuffers vulkanSymbolWrapper_vkFreeCommandBuffers;\n#define vkFreeCommandBuffers vulkanSymbolWrapper_vkFreeCommandBuffers\nextern PFN_vkBeginCommandBuffer vulkanSymbolWrapper_vkBeginCommandBuffer;\n#define vkBeginCommandBuffer vulkanSymbolWrapper_vkBeginCommandBuffer\nextern PFN_vkEndCommandBuffer vulkanSymbolWrapper_vkEndCommandBuffer;\n#define vkEndCommandBuffer vulkanSymbolWrapper_vkEndCommandBuffer\nextern PFN_vkResetCommandBuffer vulkanSymbolWrapper_vkResetCommandBuffer;\n#define vkResetCommandBuffer vulkanSymbolWrapper_vkResetCommandBuffer\nextern PFN_vkCmdBindPipeline vulkanSymbolWrapper_vkCmdBindPipeline;\n#define vkCmdBindPipeline vulkanSymbolWrapper_vkCmdBindPipeline\nextern PFN_vkCmdSetViewport vulkanSymbolWrapper_vkCmdSetViewport;\n#define vkCmdSetViewport vulkanSymbolWrapper_vkCmdSetViewport\nextern PFN_vkCmdSetScissor vulkanSymbolWrapper_vkCmdSetScissor;\n#define vkCmdSetScissor vulkanSymbolWrapper_vkCmdSetScissor\nextern PFN_vkCmdSetLineWidth vulkanSymbolWrapper_vkCmdSetLineWidth;\n#define vkCmdSetLineWidth vulkanSymbolWrapper_vkCmdSetLineWidth\nextern PFN_vkCmdSetDepthBias vulkanSymbolWrapper_vkCmdSetDepthBias;\n#define vkCmdSetDepthBias vulkanSymbolWrapper_vkCmdSetDepthBias\nextern PFN_vkCmdSetBlendConstants vulkanSymbolWrapper_vkCmdSetBlendConstants;\n#define vkCmdSetBlendConstants vulkanSymbolWrapper_vkCmdSetBlendConstants\nextern PFN_vkCmdSetDepthBounds vulkanSymbolWrapper_vkCmdSetDepthBounds;\n#define vkCmdSetDepthBounds vulkanSymbolWrapper_vkCmdSetDepthBounds\nextern PFN_vkCmdSetStencilCompareMask vulkanSymbolWrapper_vkCmdSetStencilCompareMask;\n#define vkCmdSetStencilCompareMask vulkanSymbolWrapper_vkCmdSetStencilCompareMask\nextern PFN_vkCmdSetStencilWriteMask vulkanSymbolWrapper_vkCmdSetStencilWriteMask;\n#define vkCmdSetStencilWriteMask vulkanSymbolWrapper_vkCmdSetStencilWriteMask\nextern PFN_vkCmdSetStencilReference vulkanSymbolWrapper_vkCmdSetStencilReference;\n#define vkCmdSetStencilReference vulkanSymbolWrapper_vkCmdSetStencilReference\nextern PFN_vkCmdBindDescriptorSets vulkanSymbolWrapper_vkCmdBindDescriptorSets;\n#define vkCmdBindDescriptorSets vulkanSymbolWrapper_vkCmdBindDescriptorSets\nextern PFN_vkCmdBindIndexBuffer vulkanSymbolWrapper_vkCmdBindIndexBuffer;\n#define vkCmdBindIndexBuffer vulkanSymbolWrapper_vkCmdBindIndexBuffer\nextern PFN_vkCmdBindVertexBuffers vulkanSymbolWrapper_vkCmdBindVertexBuffers;\n#define vkCmdBindVertexBuffers vulkanSymbolWrapper_vkCmdBindVertexBuffers\nextern PFN_vkCmdDraw vulkanSymbolWrapper_vkCmdDraw;\n#define vkCmdDraw vulkanSymbolWrapper_vkCmdDraw\nextern PFN_vkCmdDrawIndexed vulkanSymbolWrapper_vkCmdDrawIndexed;\n#define vkCmdDrawIndexed vulkanSymbolWrapper_vkCmdDrawIndexed\nextern PFN_vkCmdDrawIndirect vulkanSymbolWrapper_vkCmdDrawIndirect;\n#define vkCmdDrawIndirect vulkanSymbolWrapper_vkCmdDrawIndirect\nextern PFN_vkCmdDrawIndexedIndirect vulkanSymbolWrapper_vkCmdDrawIndexedIndirect;\n#define vkCmdDrawIndexedIndirect vulkanSymbolWrapper_vkCmdDrawIndexedIndirect\nextern PFN_vkCmdDispatch vulkanSymbolWrapper_vkCmdDispatch;\n#define vkCmdDispatch vulkanSymbolWrapper_vkCmdDispatch\nextern PFN_vkCmdDispatchIndirect vulkanSymbolWrapper_vkCmdDispatchIndirect;\n#define vkCmdDispatchIndirect vulkanSymbolWrapper_vkCmdDispatchIndirect\nextern PFN_vkCmdCopyBuffer vulkanSymbolWrapper_vkCmdCopyBuffer;\n#define vkCmdCopyBuffer vulkanSymbolWrapper_vkCmdCopyBuffer\nextern PFN_vkCmdCopyImage vulkanSymbolWrapper_vkCmdCopyImage;\n#define vkCmdCopyImage vulkanSymbolWrapper_vkCmdCopyImage\nextern PFN_vkCmdBlitImage vulkanSymbolWrapper_vkCmdBlitImage;\n#define vkCmdBlitImage vulkanSymbolWrapper_vkCmdBlitImage\nextern PFN_vkCmdCopyBufferToImage vulkanSymbolWrapper_vkCmdCopyBufferToImage;\n#define vkCmdCopyBufferToImage vulkanSymbolWrapper_vkCmdCopyBufferToImage\nextern PFN_vkCmdCopyImageToBuffer vulkanSymbolWrapper_vkCmdCopyImageToBuffer;\n#define vkCmdCopyImageToBuffer vulkanSymbolWrapper_vkCmdCopyImageToBuffer\nextern PFN_vkCmdUpdateBuffer vulkanSymbolWrapper_vkCmdUpdateBuffer;\n#define vkCmdUpdateBuffer vulkanSymbolWrapper_vkCmdUpdateBuffer\nextern PFN_vkCmdFillBuffer vulkanSymbolWrapper_vkCmdFillBuffer;\n#define vkCmdFillBuffer vulkanSymbolWrapper_vkCmdFillBuffer\nextern PFN_vkCmdClearColorImage vulkanSymbolWrapper_vkCmdClearColorImage;\n#define vkCmdClearColorImage vulkanSymbolWrapper_vkCmdClearColorImage\nextern PFN_vkCmdClearDepthStencilImage vulkanSymbolWrapper_vkCmdClearDepthStencilImage;\n#define vkCmdClearDepthStencilImage vulkanSymbolWrapper_vkCmdClearDepthStencilImage\nextern PFN_vkCmdClearAttachments vulkanSymbolWrapper_vkCmdClearAttachments;\n#define vkCmdClearAttachments vulkanSymbolWrapper_vkCmdClearAttachments\nextern PFN_vkCmdResolveImage vulkanSymbolWrapper_vkCmdResolveImage;\n#define vkCmdResolveImage vulkanSymbolWrapper_vkCmdResolveImage\nextern PFN_vkCmdSetEvent vulkanSymbolWrapper_vkCmdSetEvent;\n#define vkCmdSetEvent vulkanSymbolWrapper_vkCmdSetEvent\nextern PFN_vkCmdResetEvent vulkanSymbolWrapper_vkCmdResetEvent;\n#define vkCmdResetEvent vulkanSymbolWrapper_vkCmdResetEvent\nextern PFN_vkCmdWaitEvents vulkanSymbolWrapper_vkCmdWaitEvents;\n#define vkCmdWaitEvents vulkanSymbolWrapper_vkCmdWaitEvents\nextern PFN_vkCmdPipelineBarrier vulkanSymbolWrapper_vkCmdPipelineBarrier;\n#define vkCmdPipelineBarrier vulkanSymbolWrapper_vkCmdPipelineBarrier\nextern PFN_vkCmdBeginQuery vulkanSymbolWrapper_vkCmdBeginQuery;\n#define vkCmdBeginQuery vulkanSymbolWrapper_vkCmdBeginQuery\nextern PFN_vkCmdEndQuery vulkanSymbolWrapper_vkCmdEndQuery;\n#define vkCmdEndQuery vulkanSymbolWrapper_vkCmdEndQuery\nextern PFN_vkCmdResetQueryPool vulkanSymbolWrapper_vkCmdResetQueryPool;\n#define vkCmdResetQueryPool vulkanSymbolWrapper_vkCmdResetQueryPool\nextern PFN_vkCmdWriteTimestamp vulkanSymbolWrapper_vkCmdWriteTimestamp;\n#define vkCmdWriteTimestamp vulkanSymbolWrapper_vkCmdWriteTimestamp\nextern PFN_vkCmdCopyQueryPoolResults vulkanSymbolWrapper_vkCmdCopyQueryPoolResults;\n#define vkCmdCopyQueryPoolResults vulkanSymbolWrapper_vkCmdCopyQueryPoolResults\nextern PFN_vkCmdPushConstants vulkanSymbolWrapper_vkCmdPushConstants;\n#define vkCmdPushConstants vulkanSymbolWrapper_vkCmdPushConstants\nextern PFN_vkCmdBeginRenderPass vulkanSymbolWrapper_vkCmdBeginRenderPass;\n#define vkCmdBeginRenderPass vulkanSymbolWrapper_vkCmdBeginRenderPass\nextern PFN_vkCmdNextSubpass vulkanSymbolWrapper_vkCmdNextSubpass;\n#define vkCmdNextSubpass vulkanSymbolWrapper_vkCmdNextSubpass\nextern PFN_vkCmdEndRenderPass vulkanSymbolWrapper_vkCmdEndRenderPass;\n#define vkCmdEndRenderPass vulkanSymbolWrapper_vkCmdEndRenderPass\nextern PFN_vkCmdExecuteCommands vulkanSymbolWrapper_vkCmdExecuteCommands;\n#define vkCmdExecuteCommands vulkanSymbolWrapper_vkCmdExecuteCommands\nextern PFN_vkDestroySurfaceKHR vulkanSymbolWrapper_vkDestroySurfaceKHR;\n#define vkDestroySurfaceKHR vulkanSymbolWrapper_vkDestroySurfaceKHR\nextern PFN_vkGetPhysicalDeviceSurfaceSupportKHR vulkanSymbolWrapper_vkGetPhysicalDeviceSurfaceSupportKHR;\n#define vkGetPhysicalDeviceSurfaceSupportKHR vulkanSymbolWrapper_vkGetPhysicalDeviceSurfaceSupportKHR\nextern PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vulkanSymbolWrapper_vkGetPhysicalDeviceSurfaceCapabilitiesKHR;\n#define vkGetPhysicalDeviceSurfaceCapabilitiesKHR vulkanSymbolWrapper_vkGetPhysicalDeviceSurfaceCapabilitiesKHR\nextern PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vulkanSymbolWrapper_vkGetPhysicalDeviceSurfaceFormatsKHR;\n#define vkGetPhysicalDeviceSurfaceFormatsKHR vulkanSymbolWrapper_vkGetPhysicalDeviceSurfaceFormatsKHR\nextern PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vulkanSymbolWrapper_vkGetPhysicalDeviceSurfacePresentModesKHR;\n#define vkGetPhysicalDeviceSurfacePresentModesKHR vulkanSymbolWrapper_vkGetPhysicalDeviceSurfacePresentModesKHR\nextern PFN_vkCreateSwapchainKHR vulkanSymbolWrapper_vkCreateSwapchainKHR;\n#define vkCreateSwapchainKHR vulkanSymbolWrapper_vkCreateSwapchainKHR\nextern PFN_vkDestroySwapchainKHR vulkanSymbolWrapper_vkDestroySwapchainKHR;\n#define vkDestroySwapchainKHR vulkanSymbolWrapper_vkDestroySwapchainKHR\nextern PFN_vkGetSwapchainImagesKHR vulkanSymbolWrapper_vkGetSwapchainImagesKHR;\n#define vkGetSwapchainImagesKHR vulkanSymbolWrapper_vkGetSwapchainImagesKHR\nextern PFN_vkAcquireNextImageKHR vulkanSymbolWrapper_vkAcquireNextImageKHR;\n#define vkAcquireNextImageKHR vulkanSymbolWrapper_vkAcquireNextImageKHR\nextern PFN_vkQueuePresentKHR vulkanSymbolWrapper_vkQueuePresentKHR;\n#define vkQueuePresentKHR vulkanSymbolWrapper_vkQueuePresentKHR\nextern PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vulkanSymbolWrapper_vkGetPhysicalDeviceDisplayPropertiesKHR;\n#define vkGetPhysicalDeviceDisplayPropertiesKHR vulkanSymbolWrapper_vkGetPhysicalDeviceDisplayPropertiesKHR\nextern PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vulkanSymbolWrapper_vkGetPhysicalDeviceDisplayPlanePropertiesKHR;\n#define vkGetPhysicalDeviceDisplayPlanePropertiesKHR vulkanSymbolWrapper_vkGetPhysicalDeviceDisplayPlanePropertiesKHR\nextern PFN_vkGetDisplayPlaneSupportedDisplaysKHR vulkanSymbolWrapper_vkGetDisplayPlaneSupportedDisplaysKHR;\n#define vkGetDisplayPlaneSupportedDisplaysKHR vulkanSymbolWrapper_vkGetDisplayPlaneSupportedDisplaysKHR\nextern PFN_vkGetDisplayModePropertiesKHR vulkanSymbolWrapper_vkGetDisplayModePropertiesKHR;\n#define vkGetDisplayModePropertiesKHR vulkanSymbolWrapper_vkGetDisplayModePropertiesKHR\nextern PFN_vkCreateDisplayModeKHR vulkanSymbolWrapper_vkCreateDisplayModeKHR;\n#define vkCreateDisplayModeKHR vulkanSymbolWrapper_vkCreateDisplayModeKHR\nextern PFN_vkGetDisplayPlaneCapabilitiesKHR vulkanSymbolWrapper_vkGetDisplayPlaneCapabilitiesKHR;\n#define vkGetDisplayPlaneCapabilitiesKHR vulkanSymbolWrapper_vkGetDisplayPlaneCapabilitiesKHR\nextern PFN_vkCreateDisplayPlaneSurfaceKHR vulkanSymbolWrapper_vkCreateDisplayPlaneSurfaceKHR;\n#define vkCreateDisplayPlaneSurfaceKHR vulkanSymbolWrapper_vkCreateDisplayPlaneSurfaceKHR\nextern PFN_vkCreateSharedSwapchainsKHR vulkanSymbolWrapper_vkCreateSharedSwapchainsKHR;\n#define vkCreateSharedSwapchainsKHR vulkanSymbolWrapper_vkCreateSharedSwapchainsKHR\nextern PFN_vkCreateDebugReportCallbackEXT vulkanSymbolWrapper_vkCreateDebugReportCallbackEXT;\n#define vkCreateDebugReportCallbackEXT vulkanSymbolWrapper_vkCreateDebugReportCallbackEXT\nextern PFN_vkDestroyDebugReportCallbackEXT vulkanSymbolWrapper_vkDestroyDebugReportCallbackEXT;\n#define vkDestroyDebugReportCallbackEXT vulkanSymbolWrapper_vkDestroyDebugReportCallbackEXT\nextern PFN_vkDebugReportMessageEXT vulkanSymbolWrapper_vkDebugReportMessageEXT;\n#define vkDebugReportMessageEXT vulkanSymbolWrapper_vkDebugReportMessageEXT\nextern PFN_vkDebugMarkerSetObjectTagEXT vulkanSymbolWrapper_vkDebugMarkerSetObjectTagEXT;\n#define vkDebugMarkerSetObjectTagEXT vulkanSymbolWrapper_vkDebugMarkerSetObjectTagEXT\nextern PFN_vkDebugMarkerSetObjectNameEXT vulkanSymbolWrapper_vkDebugMarkerSetObjectNameEXT;\n#define vkDebugMarkerSetObjectNameEXT vulkanSymbolWrapper_vkDebugMarkerSetObjectNameEXT\nextern PFN_vkCmdDebugMarkerBeginEXT vulkanSymbolWrapper_vkCmdDebugMarkerBeginEXT;\n#define vkCmdDebugMarkerBeginEXT vulkanSymbolWrapper_vkCmdDebugMarkerBeginEXT\nextern PFN_vkCmdDebugMarkerEndEXT vulkanSymbolWrapper_vkCmdDebugMarkerEndEXT;\n#define vkCmdDebugMarkerEndEXT vulkanSymbolWrapper_vkCmdDebugMarkerEndEXT\nextern PFN_vkCmdDebugMarkerInsertEXT vulkanSymbolWrapper_vkCmdDebugMarkerInsertEXT;\n#define vkCmdDebugMarkerInsertEXT vulkanSymbolWrapper_vkCmdDebugMarkerInsertEXT\nextern PFN_vkCmdDrawIndirectCountAMD vulkanSymbolWrapper_vkCmdDrawIndirectCountAMD;\n#define vkCmdDrawIndirectCountAMD vulkanSymbolWrapper_vkCmdDrawIndirectCountAMD\nextern PFN_vkCmdDrawIndexedIndirectCountAMD vulkanSymbolWrapper_vkCmdDrawIndexedIndirectCountAMD;\n#define vkCmdDrawIndexedIndirectCountAMD vulkanSymbolWrapper_vkCmdDrawIndexedIndirectCountAMD\nextern PFN_vkGetPhysicalDeviceExternalImageFormatPropertiesNV vulkanSymbolWrapper_vkGetPhysicalDeviceExternalImageFormatPropertiesNV;\n#define vkGetPhysicalDeviceExternalImageFormatPropertiesNV vulkanSymbolWrapper_vkGetPhysicalDeviceExternalImageFormatPropertiesNV\n\nVkBool32 vulkanSymbolWrapperInitLoader(void);\nvoid vulkanSymbolWrapperInit(PFN_vkGetInstanceProcAddr getInstanceProcAddr);\nPFN_vkGetInstanceProcAddr vulkanSymbolWrapperInstanceProcAddr(void);\nvoid vulkanSymbolWrapperReset(void);\nVkBool32 vulkanSymbolWrapperLoadGlobalSymbols(void);\nVkBool32 vulkanSymbolWrapperLoadCoreInstanceSymbols(VkInstance instance);\nVkBool32 vulkanSymbolWrapperLoadCoreSymbols(VkInstance instance);\nVkBool32 vulkanSymbolWrapperLoadCoreDeviceSymbols(VkDevice device);\nVkBool32 vulkanSymbolWrapperLoadInstanceSymbol(VkInstance instance, const char *name, PFN_vkVoidFunction *ppSymbol);\nVkBool32 vulkanSymbolWrapperLoadDeviceSymbol(VkDevice device, const char *name, PFN_vkVoidFunction *ppSymbol);\n\n#define VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, name, pfn) vulkanSymbolWrapperLoadInstanceSymbol(instance, name, (PFN_vkVoidFunction*) &(pfn))\n#define VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(instance, name) vulkanSymbolWrapperLoadInstanceSymbol(instance, #name, (PFN_vkVoidFunction*) & name)\n#define VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, name, pfn) vulkanSymbolWrapperLoadDeviceSymbol(device, name, (PFN_vkVoidFunction*) &(pfn))\n#define VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_EXTENSION_SYMBOL(device, name) vulkanSymbolWrapperLoadDeviceSymbol(device, #name, (PFN_vkVoidFunction*) & name)\n\n\n#ifdef __cplusplus\n}\n#endif\n#endif\n\n"
  },
  {
    "path": "caffe2/mobile/contrib/libvulkan-stub/include/vulkan/vk_platform.h",
    "content": "//\n// File: vk_platform.h\n//\n/*\n** Copyright (c) 2014-2015 The Khronos Group Inc.\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\");\n** you may not use this file except in compliance with the License.\n** You may obtain a copy of the License at\n**\n**     http://www.apache.org/licenses/LICENSE-2.0\n**\n** Unless required by applicable law or agreed to in writing, software\n** distributed under the License is distributed on an \"AS IS\" BASIS,\n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n** See the License for the specific language governing permissions and\n** limitations under the License.\n*/\n\n\n#ifndef VK_PLATFORM_H_\n#define VK_PLATFORM_H_\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif // __cplusplus\n\n/*\n***************************************************************************************************\n*   Platform-specific directives and type declarations\n***************************************************************************************************\n*/\n\n/* Platform-specific calling convention macros.\n *\n * Platforms should define these so that Vulkan clients call Vulkan commands\n * with the same calling conventions that the Vulkan implementation expects.\n *\n * VKAPI_ATTR - Placed before the return type in function declarations.\n *              Useful for C++11 and GCC/Clang-style function attribute syntax.\n * VKAPI_CALL - Placed after the return type in function declarations.\n *              Useful for MSVC-style calling convention syntax.\n * VKAPI_PTR  - Placed between the '(' and '*' in function pointer types.\n *\n * Function declaration:  VKAPI_ATTR void VKAPI_CALL vkCommand(void);\n * Function pointer type: typedef void (VKAPI_PTR *PFN_vkCommand)(void);\n */\n#if defined(_WIN32)\n    // On Windows, Vulkan commands use the stdcall convention\n    #define VKAPI_ATTR\n    #define VKAPI_CALL __stdcall\n    #define VKAPI_PTR  VKAPI_CALL\n#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH < 7\n    #error \"Vulkan isn't supported for the 'armeabi' NDK ABI\"\n#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7 && defined(__ARM_32BIT_STATE)\n    // On Android 32-bit ARM targets, Vulkan functions use the \"hardfloat\"\n    // calling convention, i.e. float parameters are passed in registers. This\n    // is true even if the rest of the application passes floats on the stack,\n    // as it does by default when compiling for the armeabi-v7a NDK ABI.\n    #define VKAPI_ATTR __attribute__((pcs(\"aapcs-vfp\")))\n    #define VKAPI_CALL\n    #define VKAPI_PTR  VKAPI_ATTR\n#else\n    // On other platforms, use the default calling convention\n    #define VKAPI_ATTR\n    #define VKAPI_CALL\n    #define VKAPI_PTR\n#endif\n\n#include <stddef.h>\n\n#if !defined(VK_NO_STDINT_H)\n    #if defined(_MSC_VER) && (_MSC_VER < 1600)\n        typedef signed   __int8  int8_t;\n        typedef unsigned __int8  uint8_t;\n        typedef signed   __int16 int16_t;\n        typedef unsigned __int16 uint16_t;\n        typedef signed   __int32 int32_t;\n        typedef unsigned __int32 uint32_t;\n        typedef signed   __int64 int64_t;\n        typedef unsigned __int64 uint64_t;\n    #else\n        #include <stdint.h>\n    #endif\n#endif // !defined(VK_NO_STDINT_H)\n\n#ifdef __cplusplus\n} // extern \"C\"\n#endif // __cplusplus\n\n// Platform-specific headers required by platform window system extensions.\n// These are enabled prior to #including \"vulkan.h\". The same enable then\n// controls inclusion of the extension interfaces in vulkan.h.\n\n#ifdef VK_USE_PLATFORM_ANDROID_KHR\n#include <android/native_window.h>\n#endif\n\n#ifdef VK_USE_PLATFORM_MIR_KHR\n#include <mir_toolkit/client_types.h>\n#endif\n\n#ifdef VK_USE_PLATFORM_WAYLAND_KHR\n#include <wayland-client.h>\n#endif\n\n#ifdef VK_USE_PLATFORM_WIN32_KHR\n#include <windows.h>\n#endif\n\n#ifdef VK_USE_PLATFORM_XLIB_KHR\n#include <X11/Xlib.h>\n#endif\n\n#ifdef VK_USE_PLATFORM_XCB_KHR\n#include <xcb/xcb.h>\n#endif\n\n#endif\n"
  },
  {
    "path": "caffe2/mobile/contrib/libvulkan-stub/include/vulkan/vulkan.h",
    "content": "#ifndef VULKAN_H_\n#define VULKAN_H_ 1\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/*\n** Copyright (c) 2015-2016 The Khronos Group Inc.\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\");\n** you may not use this file except in compliance with the License.\n** You may obtain a copy of the License at\n**\n**     http://www.apache.org/licenses/LICENSE-2.0\n**\n** Unless required by applicable law or agreed to in writing, software\n** distributed under the License is distributed on an \"AS IS\" BASIS,\n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n** See the License for the specific language governing permissions and\n** limitations under the License.\n*/\n\n/*\n** This header is generated from the Khronos Vulkan XML API Registry.\n**\n*/\n\n\n#define VK_VERSION_1_0 1\n#include \"vk_platform.h\"\n\n#define VK_MAKE_VERSION(major, minor, patch) \\\n    (((major) << 22) | ((minor) << 12) | (patch))\n\n// DEPRECATED: This define has been removed. Specific version defines (e.g. VK_API_VERSION_1_0), or the VK_MAKE_VERSION macro, should be used instead.\n//#define VK_API_VERSION VK_MAKE_VERSION(1, 0, 0)\n\n// Vulkan 1.0 version number\n#define VK_API_VERSION_1_0 VK_MAKE_VERSION(1, 0, 0)\n\n#define VK_VERSION_MAJOR(version) ((uint32_t)(version) >> 22)\n#define VK_VERSION_MINOR(version) (((uint32_t)(version) >> 12) & 0x3ff)\n#define VK_VERSION_PATCH(version) ((uint32_t)(version) & 0xfff)\n// Version of this file\n#define VK_HEADER_VERSION 29\n\n\n#define VK_NULL_HANDLE 0\n        \n\n\n#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;\n\n\n#if !defined(VK_DEFINE_NON_DISPATCHABLE_HANDLE)\n#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)\n        #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object;\n#else\n        #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;\n#endif\n#endif\n        \n\n\ntypedef uint32_t VkFlags;\ntypedef uint32_t VkBool32;\ntypedef uint64_t VkDeviceSize;\ntypedef uint32_t VkSampleMask;\n\nVK_DEFINE_HANDLE(VkInstance)\nVK_DEFINE_HANDLE(VkPhysicalDevice)\nVK_DEFINE_HANDLE(VkDevice)\nVK_DEFINE_HANDLE(VkQueue)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSemaphore)\nVK_DEFINE_HANDLE(VkCommandBuffer)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkFence)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDeviceMemory)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkBuffer)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkImage)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkEvent)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkQueryPool)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkBufferView)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkImageView)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkShaderModule)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPipelineCache)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPipelineLayout)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkRenderPass)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPipeline)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorSetLayout)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSampler)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorPool)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorSet)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkFramebuffer)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkCommandPool)\n\n#define VK_LOD_CLAMP_NONE                 1000.0f\n#define VK_REMAINING_MIP_LEVELS           (~0U)\n#define VK_REMAINING_ARRAY_LAYERS         (~0U)\n#define VK_WHOLE_SIZE                     (~0ULL)\n#define VK_ATTACHMENT_UNUSED              (~0U)\n#define VK_TRUE                           1\n#define VK_FALSE                          0\n#define VK_QUEUE_FAMILY_IGNORED           (~0U)\n#define VK_SUBPASS_EXTERNAL               (~0U)\n#define VK_MAX_PHYSICAL_DEVICE_NAME_SIZE  256\n#define VK_UUID_SIZE                      16\n#define VK_MAX_MEMORY_TYPES               32\n#define VK_MAX_MEMORY_HEAPS               16\n#define VK_MAX_EXTENSION_NAME_SIZE        256\n#define VK_MAX_DESCRIPTION_SIZE           256\n\n\ntypedef enum VkPipelineCacheHeaderVersion {\n    VK_PIPELINE_CACHE_HEADER_VERSION_ONE = 1,\n    VK_PIPELINE_CACHE_HEADER_VERSION_BEGIN_RANGE = VK_PIPELINE_CACHE_HEADER_VERSION_ONE,\n    VK_PIPELINE_CACHE_HEADER_VERSION_END_RANGE = VK_PIPELINE_CACHE_HEADER_VERSION_ONE,\n    VK_PIPELINE_CACHE_HEADER_VERSION_RANGE_SIZE = (VK_PIPELINE_CACHE_HEADER_VERSION_ONE - VK_PIPELINE_CACHE_HEADER_VERSION_ONE + 1),\n    VK_PIPELINE_CACHE_HEADER_VERSION_MAX_ENUM = 0x7FFFFFFF\n} VkPipelineCacheHeaderVersion;\n\ntypedef enum VkResult {\n    VK_SUCCESS = 0,\n    VK_NOT_READY = 1,\n    VK_TIMEOUT = 2,\n    VK_EVENT_SET = 3,\n    VK_EVENT_RESET = 4,\n    VK_INCOMPLETE = 5,\n    VK_ERROR_OUT_OF_HOST_MEMORY = -1,\n    VK_ERROR_OUT_OF_DEVICE_MEMORY = -2,\n    VK_ERROR_INITIALIZATION_FAILED = -3,\n    VK_ERROR_DEVICE_LOST = -4,\n    VK_ERROR_MEMORY_MAP_FAILED = -5,\n    VK_ERROR_LAYER_NOT_PRESENT = -6,\n    VK_ERROR_EXTENSION_NOT_PRESENT = -7,\n    VK_ERROR_FEATURE_NOT_PRESENT = -8,\n    VK_ERROR_INCOMPATIBLE_DRIVER = -9,\n    VK_ERROR_TOO_MANY_OBJECTS = -10,\n    VK_ERROR_FORMAT_NOT_SUPPORTED = -11,\n    VK_ERROR_FRAGMENTED_POOL = -12,\n    VK_ERROR_SURFACE_LOST_KHR = -1000000000,\n    VK_ERROR_NATIVE_WINDOW_IN_USE_KHR = -1000000001,\n    VK_SUBOPTIMAL_KHR = 1000001003,\n    VK_ERROR_OUT_OF_DATE_KHR = -1000001004,\n    VK_ERROR_INCOMPATIBLE_DISPLAY_KHR = -1000003001,\n    VK_ERROR_VALIDATION_FAILED_EXT = -1000011001,\n    VK_ERROR_INVALID_SHADER_NV = -1000012000,\n    VK_RESULT_BEGIN_RANGE = VK_ERROR_FRAGMENTED_POOL,\n    VK_RESULT_END_RANGE = VK_INCOMPLETE,\n    VK_RESULT_RANGE_SIZE = (VK_INCOMPLETE - VK_ERROR_FRAGMENTED_POOL + 1),\n    VK_RESULT_MAX_ENUM = 0x7FFFFFFF\n} VkResult;\n\ntypedef enum VkStructureType {\n    VK_STRUCTURE_TYPE_APPLICATION_INFO = 0,\n    VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO = 1,\n    VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO = 2,\n    VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO = 3,\n    VK_STRUCTURE_TYPE_SUBMIT_INFO = 4,\n    VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO = 5,\n    VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE = 6,\n    VK_STRUCTURE_TYPE_BIND_SPARSE_INFO = 7,\n    VK_STRUCTURE_TYPE_FENCE_CREATE_INFO = 8,\n    VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO = 9,\n    VK_STRUCTURE_TYPE_EVENT_CREATE_INFO = 10,\n    VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO = 11,\n    VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO = 12,\n    VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO = 13,\n    VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO = 14,\n    VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO = 15,\n    VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO = 16,\n    VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO = 17,\n    VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO = 18,\n    VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO = 19,\n    VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO = 20,\n    VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO = 21,\n    VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO = 22,\n    VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO = 23,\n    VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO = 24,\n    VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO = 25,\n    VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO = 26,\n    VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO = 27,\n    VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO = 28,\n    VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO = 29,\n    VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO = 30,\n    VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO = 31,\n    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO = 32,\n    VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO = 33,\n    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO = 34,\n    VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET = 35,\n    VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET = 36,\n    VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO = 37,\n    VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO = 38,\n    VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO = 39,\n    VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO = 40,\n    VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO = 41,\n    VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO = 42,\n    VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO = 43,\n    VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER = 44,\n    VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER = 45,\n    VK_STRUCTURE_TYPE_MEMORY_BARRIER = 46,\n    VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO = 47,\n    VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO = 48,\n    VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR = 1000001000,\n    VK_STRUCTURE_TYPE_PRESENT_INFO_KHR = 1000001001,\n    VK_STRUCTURE_TYPE_DISPLAY_MODE_CREATE_INFO_KHR = 1000002000,\n    VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR = 1000002001,\n    VK_STRUCTURE_TYPE_DISPLAY_PRESENT_INFO_KHR = 1000003000,\n    VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000,\n    VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR = 1000005000,\n    VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,\n    VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000,\n    VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR = 1000008000,\n    VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,\n    VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT = 1000011000,\n    VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_RASTERIZATION_ORDER_AMD = 1000018000,\n    VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT = 1000022000,\n    VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_TAG_INFO_EXT = 1000022001,\n    VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT = 1000022002,\n    VK_STRUCTURE_TYPE_DEDICATED_ALLOCATION_IMAGE_CREATE_INFO_NV = 1000026000,\n    VK_STRUCTURE_TYPE_DEDICATED_ALLOCATION_BUFFER_CREATE_INFO_NV = 1000026001,\n    VK_STRUCTURE_TYPE_DEDICATED_ALLOCATION_MEMORY_ALLOCATE_INFO_NV = 1000026002,\n    VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO_NV = 1000056000,\n    VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_NV = 1000056001,\n    VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_NV = 1000057000,\n    VK_STRUCTURE_TYPE_EXPORT_MEMORY_WIN32_HANDLE_INFO_NV = 1000057001,\n    VK_STRUCTURE_TYPE_WIN32_KEYED_MUTEX_ACQUIRE_RELEASE_INFO_NV = 1000058000,\n    VK_STRUCTURE_TYPE_VALIDATION_FLAGS_EXT = 1000061000,\n    VK_STRUCTURE_TYPE_BEGIN_RANGE = VK_STRUCTURE_TYPE_APPLICATION_INFO,\n    VK_STRUCTURE_TYPE_END_RANGE = VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO,\n    VK_STRUCTURE_TYPE_RANGE_SIZE = (VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO - VK_STRUCTURE_TYPE_APPLICATION_INFO + 1),\n    VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF\n} VkStructureType;\n\ntypedef enum VkSystemAllocationScope {\n    VK_SYSTEM_ALLOCATION_SCOPE_COMMAND = 0,\n    VK_SYSTEM_ALLOCATION_SCOPE_OBJECT = 1,\n    VK_SYSTEM_ALLOCATION_SCOPE_CACHE = 2,\n    VK_SYSTEM_ALLOCATION_SCOPE_DEVICE = 3,\n    VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE = 4,\n    VK_SYSTEM_ALLOCATION_SCOPE_BEGIN_RANGE = VK_SYSTEM_ALLOCATION_SCOPE_COMMAND,\n    VK_SYSTEM_ALLOCATION_SCOPE_END_RANGE = VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE,\n    VK_SYSTEM_ALLOCATION_SCOPE_RANGE_SIZE = (VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE - VK_SYSTEM_ALLOCATION_SCOPE_COMMAND + 1),\n    VK_SYSTEM_ALLOCATION_SCOPE_MAX_ENUM = 0x7FFFFFFF\n} VkSystemAllocationScope;\n\ntypedef enum VkInternalAllocationType {\n    VK_INTERNAL_ALLOCATION_TYPE_EXECUTABLE = 0,\n    VK_INTERNAL_ALLOCATION_TYPE_BEGIN_RANGE = VK_INTERNAL_ALLOCATION_TYPE_EXECUTABLE,\n    VK_INTERNAL_ALLOCATION_TYPE_END_RANGE = VK_INTERNAL_ALLOCATION_TYPE_EXECUTABLE,\n    VK_INTERNAL_ALLOCATION_TYPE_RANGE_SIZE = (VK_INTERNAL_ALLOCATION_TYPE_EXECUTABLE - VK_INTERNAL_ALLOCATION_TYPE_EXECUTABLE + 1),\n    VK_INTERNAL_ALLOCATION_TYPE_MAX_ENUM = 0x7FFFFFFF\n} VkInternalAllocationType;\n\ntypedef enum VkFormat {\n    VK_FORMAT_UNDEFINED = 0,\n    VK_FORMAT_R4G4_UNORM_PACK8 = 1,\n    VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2,\n    VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3,\n    VK_FORMAT_R5G6B5_UNORM_PACK16 = 4,\n    VK_FORMAT_B5G6R5_UNORM_PACK16 = 5,\n    VK_FORMAT_R5G5B5A1_UNORM_PACK16 = 6,\n    VK_FORMAT_B5G5R5A1_UNORM_PACK16 = 7,\n    VK_FORMAT_A1R5G5B5_UNORM_PACK16 = 8,\n    VK_FORMAT_R8_UNORM = 9,\n    VK_FORMAT_R8_SNORM = 10,\n    VK_FORMAT_R8_USCALED = 11,\n    VK_FORMAT_R8_SSCALED = 12,\n    VK_FORMAT_R8_UINT = 13,\n    VK_FORMAT_R8_SINT = 14,\n    VK_FORMAT_R8_SRGB = 15,\n    VK_FORMAT_R8G8_UNORM = 16,\n    VK_FORMAT_R8G8_SNORM = 17,\n    VK_FORMAT_R8G8_USCALED = 18,\n    VK_FORMAT_R8G8_SSCALED = 19,\n    VK_FORMAT_R8G8_UINT = 20,\n    VK_FORMAT_R8G8_SINT = 21,\n    VK_FORMAT_R8G8_SRGB = 22,\n    VK_FORMAT_R8G8B8_UNORM = 23,\n    VK_FORMAT_R8G8B8_SNORM = 24,\n    VK_FORMAT_R8G8B8_USCALED = 25,\n    VK_FORMAT_R8G8B8_SSCALED = 26,\n    VK_FORMAT_R8G8B8_UINT = 27,\n    VK_FORMAT_R8G8B8_SINT = 28,\n    VK_FORMAT_R8G8B8_SRGB = 29,\n    VK_FORMAT_B8G8R8_UNORM = 30,\n    VK_FORMAT_B8G8R8_SNORM = 31,\n    VK_FORMAT_B8G8R8_USCALED = 32,\n    VK_FORMAT_B8G8R8_SSCALED = 33,\n    VK_FORMAT_B8G8R8_UINT = 34,\n    VK_FORMAT_B8G8R8_SINT = 35,\n    VK_FORMAT_B8G8R8_SRGB = 36,\n    VK_FORMAT_R8G8B8A8_UNORM = 37,\n    VK_FORMAT_R8G8B8A8_SNORM = 38,\n    VK_FORMAT_R8G8B8A8_USCALED = 39,\n    VK_FORMAT_R8G8B8A8_SSCALED = 40,\n    VK_FORMAT_R8G8B8A8_UINT = 41,\n    VK_FORMAT_R8G8B8A8_SINT = 42,\n    VK_FORMAT_R8G8B8A8_SRGB = 43,\n    VK_FORMAT_B8G8R8A8_UNORM = 44,\n    VK_FORMAT_B8G8R8A8_SNORM = 45,\n    VK_FORMAT_B8G8R8A8_USCALED = 46,\n    VK_FORMAT_B8G8R8A8_SSCALED = 47,\n    VK_FORMAT_B8G8R8A8_UINT = 48,\n    VK_FORMAT_B8G8R8A8_SINT = 49,\n    VK_FORMAT_B8G8R8A8_SRGB = 50,\n    VK_FORMAT_A8B8G8R8_UNORM_PACK32 = 51,\n    VK_FORMAT_A8B8G8R8_SNORM_PACK32 = 52,\n    VK_FORMAT_A8B8G8R8_USCALED_PACK32 = 53,\n    VK_FORMAT_A8B8G8R8_SSCALED_PACK32 = 54,\n    VK_FORMAT_A8B8G8R8_UINT_PACK32 = 55,\n    VK_FORMAT_A8B8G8R8_SINT_PACK32 = 56,\n    VK_FORMAT_A8B8G8R8_SRGB_PACK32 = 57,\n    VK_FORMAT_A2R10G10B10_UNORM_PACK32 = 58,\n    VK_FORMAT_A2R10G10B10_SNORM_PACK32 = 59,\n    VK_FORMAT_A2R10G10B10_USCALED_PACK32 = 60,\n    VK_FORMAT_A2R10G10B10_SSCALED_PACK32 = 61,\n    VK_FORMAT_A2R10G10B10_UINT_PACK32 = 62,\n    VK_FORMAT_A2R10G10B10_SINT_PACK32 = 63,\n    VK_FORMAT_A2B10G10R10_UNORM_PACK32 = 64,\n    VK_FORMAT_A2B10G10R10_SNORM_PACK32 = 65,\n    VK_FORMAT_A2B10G10R10_USCALED_PACK32 = 66,\n    VK_FORMAT_A2B10G10R10_SSCALED_PACK32 = 67,\n    VK_FORMAT_A2B10G10R10_UINT_PACK32 = 68,\n    VK_FORMAT_A2B10G10R10_SINT_PACK32 = 69,\n    VK_FORMAT_R16_UNORM = 70,\n    VK_FORMAT_R16_SNORM = 71,\n    VK_FORMAT_R16_USCALED = 72,\n    VK_FORMAT_R16_SSCALED = 73,\n    VK_FORMAT_R16_UINT = 74,\n    VK_FORMAT_R16_SINT = 75,\n    VK_FORMAT_R16_SFLOAT = 76,\n    VK_FORMAT_R16G16_UNORM = 77,\n    VK_FORMAT_R16G16_SNORM = 78,\n    VK_FORMAT_R16G16_USCALED = 79,\n    VK_FORMAT_R16G16_SSCALED = 80,\n    VK_FORMAT_R16G16_UINT = 81,\n    VK_FORMAT_R16G16_SINT = 82,\n    VK_FORMAT_R16G16_SFLOAT = 83,\n    VK_FORMAT_R16G16B16_UNORM = 84,\n    VK_FORMAT_R16G16B16_SNORM = 85,\n    VK_FORMAT_R16G16B16_USCALED = 86,\n    VK_FORMAT_R16G16B16_SSCALED = 87,\n    VK_FORMAT_R16G16B16_UINT = 88,\n    VK_FORMAT_R16G16B16_SINT = 89,\n    VK_FORMAT_R16G16B16_SFLOAT = 90,\n    VK_FORMAT_R16G16B16A16_UNORM = 91,\n    VK_FORMAT_R16G16B16A16_SNORM = 92,\n    VK_FORMAT_R16G16B16A16_USCALED = 93,\n    VK_FORMAT_R16G16B16A16_SSCALED = 94,\n    VK_FORMAT_R16G16B16A16_UINT = 95,\n    VK_FORMAT_R16G16B16A16_SINT = 96,\n    VK_FORMAT_R16G16B16A16_SFLOAT = 97,\n    VK_FORMAT_R32_UINT = 98,\n    VK_FORMAT_R32_SINT = 99,\n    VK_FORMAT_R32_SFLOAT = 100,\n    VK_FORMAT_R32G32_UINT = 101,\n    VK_FORMAT_R32G32_SINT = 102,\n    VK_FORMAT_R32G32_SFLOAT = 103,\n    VK_FORMAT_R32G32B32_UINT = 104,\n    VK_FORMAT_R32G32B32_SINT = 105,\n    VK_FORMAT_R32G32B32_SFLOAT = 106,\n    VK_FORMAT_R32G32B32A32_UINT = 107,\n    VK_FORMAT_R32G32B32A32_SINT = 108,\n    VK_FORMAT_R32G32B32A32_SFLOAT = 109,\n    VK_FORMAT_R64_UINT = 110,\n    VK_FORMAT_R64_SINT = 111,\n    VK_FORMAT_R64_SFLOAT = 112,\n    VK_FORMAT_R64G64_UINT = 113,\n    VK_FORMAT_R64G64_SINT = 114,\n    VK_FORMAT_R64G64_SFLOAT = 115,\n    VK_FORMAT_R64G64B64_UINT = 116,\n    VK_FORMAT_R64G64B64_SINT = 117,\n    VK_FORMAT_R64G64B64_SFLOAT = 118,\n    VK_FORMAT_R64G64B64A64_UINT = 119,\n    VK_FORMAT_R64G64B64A64_SINT = 120,\n    VK_FORMAT_R64G64B64A64_SFLOAT = 121,\n    VK_FORMAT_B10G11R11_UFLOAT_PACK32 = 122,\n    VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 = 123,\n    VK_FORMAT_D16_UNORM = 124,\n    VK_FORMAT_X8_D24_UNORM_PACK32 = 125,\n    VK_FORMAT_D32_SFLOAT = 126,\n    VK_FORMAT_S8_UINT = 127,\n    VK_FORMAT_D16_UNORM_S8_UINT = 128,\n    VK_FORMAT_D24_UNORM_S8_UINT = 129,\n    VK_FORMAT_D32_SFLOAT_S8_UINT = 130,\n    VK_FORMAT_BC1_RGB_UNORM_BLOCK = 131,\n    VK_FORMAT_BC1_RGB_SRGB_BLOCK = 132,\n    VK_FORMAT_BC1_RGBA_UNORM_BLOCK = 133,\n    VK_FORMAT_BC1_RGBA_SRGB_BLOCK = 134,\n    VK_FORMAT_BC2_UNORM_BLOCK = 135,\n    VK_FORMAT_BC2_SRGB_BLOCK = 136,\n    VK_FORMAT_BC3_UNORM_BLOCK = 137,\n    VK_FORMAT_BC3_SRGB_BLOCK = 138,\n    VK_FORMAT_BC4_UNORM_BLOCK = 139,\n    VK_FORMAT_BC4_SNORM_BLOCK = 140,\n    VK_FORMAT_BC5_UNORM_BLOCK = 141,\n    VK_FORMAT_BC5_SNORM_BLOCK = 142,\n    VK_FORMAT_BC6H_UFLOAT_BLOCK = 143,\n    VK_FORMAT_BC6H_SFLOAT_BLOCK = 144,\n    VK_FORMAT_BC7_UNORM_BLOCK = 145,\n    VK_FORMAT_BC7_SRGB_BLOCK = 146,\n    VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK = 147,\n    VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK = 148,\n    VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK = 149,\n    VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK = 150,\n    VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK = 151,\n    VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK = 152,\n    VK_FORMAT_EAC_R11_UNORM_BLOCK = 153,\n    VK_FORMAT_EAC_R11_SNORM_BLOCK = 154,\n    VK_FORMAT_EAC_R11G11_UNORM_BLOCK = 155,\n    VK_FORMAT_EAC_R11G11_SNORM_BLOCK = 156,\n    VK_FORMAT_ASTC_4x4_UNORM_BLOCK = 157,\n    VK_FORMAT_ASTC_4x4_SRGB_BLOCK = 158,\n    VK_FORMAT_ASTC_5x4_UNORM_BLOCK = 159,\n    VK_FORMAT_ASTC_5x4_SRGB_BLOCK = 160,\n    VK_FORMAT_ASTC_5x5_UNORM_BLOCK = 161,\n    VK_FORMAT_ASTC_5x5_SRGB_BLOCK = 162,\n    VK_FORMAT_ASTC_6x5_UNORM_BLOCK = 163,\n    VK_FORMAT_ASTC_6x5_SRGB_BLOCK = 164,\n    VK_FORMAT_ASTC_6x6_UNORM_BLOCK = 165,\n    VK_FORMAT_ASTC_6x6_SRGB_BLOCK = 166,\n    VK_FORMAT_ASTC_8x5_UNORM_BLOCK = 167,\n    VK_FORMAT_ASTC_8x5_SRGB_BLOCK = 168,\n    VK_FORMAT_ASTC_8x6_UNORM_BLOCK = 169,\n    VK_FORMAT_ASTC_8x6_SRGB_BLOCK = 170,\n    VK_FORMAT_ASTC_8x8_UNORM_BLOCK = 171,\n    VK_FORMAT_ASTC_8x8_SRGB_BLOCK = 172,\n    VK_FORMAT_ASTC_10x5_UNORM_BLOCK = 173,\n    VK_FORMAT_ASTC_10x5_SRGB_BLOCK = 174,\n    VK_FORMAT_ASTC_10x6_UNORM_BLOCK = 175,\n    VK_FORMAT_ASTC_10x6_SRGB_BLOCK = 176,\n    VK_FORMAT_ASTC_10x8_UNORM_BLOCK = 177,\n    VK_FORMAT_ASTC_10x8_SRGB_BLOCK = 178,\n    VK_FORMAT_ASTC_10x10_UNORM_BLOCK = 179,\n    VK_FORMAT_ASTC_10x10_SRGB_BLOCK = 180,\n    VK_FORMAT_ASTC_12x10_UNORM_BLOCK = 181,\n    VK_FORMAT_ASTC_12x10_SRGB_BLOCK = 182,\n    VK_FORMAT_ASTC_12x12_UNORM_BLOCK = 183,\n    VK_FORMAT_ASTC_12x12_SRGB_BLOCK = 184,\n    VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG = 1000054000,\n    VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG = 1000054001,\n    VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG = 1000054002,\n    VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG = 1000054003,\n    VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG = 1000054004,\n    VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG = 1000054005,\n    VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG = 1000054006,\n    VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG = 1000054007,\n    VK_FORMAT_BEGIN_RANGE = VK_FORMAT_UNDEFINED,\n    VK_FORMAT_END_RANGE = VK_FORMAT_ASTC_12x12_SRGB_BLOCK,\n    VK_FORMAT_RANGE_SIZE = (VK_FORMAT_ASTC_12x12_SRGB_BLOCK - VK_FORMAT_UNDEFINED + 1),\n    VK_FORMAT_MAX_ENUM = 0x7FFFFFFF\n} VkFormat;\n\ntypedef enum VkImageType {\n    VK_IMAGE_TYPE_1D = 0,\n    VK_IMAGE_TYPE_2D = 1,\n    VK_IMAGE_TYPE_3D = 2,\n    VK_IMAGE_TYPE_BEGIN_RANGE = VK_IMAGE_TYPE_1D,\n    VK_IMAGE_TYPE_END_RANGE = VK_IMAGE_TYPE_3D,\n    VK_IMAGE_TYPE_RANGE_SIZE = (VK_IMAGE_TYPE_3D - VK_IMAGE_TYPE_1D + 1),\n    VK_IMAGE_TYPE_MAX_ENUM = 0x7FFFFFFF\n} VkImageType;\n\ntypedef enum VkImageTiling {\n    VK_IMAGE_TILING_OPTIMAL = 0,\n    VK_IMAGE_TILING_LINEAR = 1,\n    VK_IMAGE_TILING_BEGIN_RANGE = VK_IMAGE_TILING_OPTIMAL,\n    VK_IMAGE_TILING_END_RANGE = VK_IMAGE_TILING_LINEAR,\n    VK_IMAGE_TILING_RANGE_SIZE = (VK_IMAGE_TILING_LINEAR - VK_IMAGE_TILING_OPTIMAL + 1),\n    VK_IMAGE_TILING_MAX_ENUM = 0x7FFFFFFF\n} VkImageTiling;\n\ntypedef enum VkPhysicalDeviceType {\n    VK_PHYSICAL_DEVICE_TYPE_OTHER = 0,\n    VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU = 1,\n    VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU = 2,\n    VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU = 3,\n    VK_PHYSICAL_DEVICE_TYPE_CPU = 4,\n    VK_PHYSICAL_DEVICE_TYPE_BEGIN_RANGE = VK_PHYSICAL_DEVICE_TYPE_OTHER,\n    VK_PHYSICAL_DEVICE_TYPE_END_RANGE = VK_PHYSICAL_DEVICE_TYPE_CPU,\n    VK_PHYSICAL_DEVICE_TYPE_RANGE_SIZE = (VK_PHYSICAL_DEVICE_TYPE_CPU - VK_PHYSICAL_DEVICE_TYPE_OTHER + 1),\n    VK_PHYSICAL_DEVICE_TYPE_MAX_ENUM = 0x7FFFFFFF\n} VkPhysicalDeviceType;\n\ntypedef enum VkQueryType {\n    VK_QUERY_TYPE_OCCLUSION = 0,\n    VK_QUERY_TYPE_PIPELINE_STATISTICS = 1,\n    VK_QUERY_TYPE_TIMESTAMP = 2,\n    VK_QUERY_TYPE_BEGIN_RANGE = VK_QUERY_TYPE_OCCLUSION,\n    VK_QUERY_TYPE_END_RANGE = VK_QUERY_TYPE_TIMESTAMP,\n    VK_QUERY_TYPE_RANGE_SIZE = (VK_QUERY_TYPE_TIMESTAMP - VK_QUERY_TYPE_OCCLUSION + 1),\n    VK_QUERY_TYPE_MAX_ENUM = 0x7FFFFFFF\n} VkQueryType;\n\ntypedef enum VkSharingMode {\n    VK_SHARING_MODE_EXCLUSIVE = 0,\n    VK_SHARING_MODE_CONCURRENT = 1,\n    VK_SHARING_MODE_BEGIN_RANGE = VK_SHARING_MODE_EXCLUSIVE,\n    VK_SHARING_MODE_END_RANGE = VK_SHARING_MODE_CONCURRENT,\n    VK_SHARING_MODE_RANGE_SIZE = (VK_SHARING_MODE_CONCURRENT - VK_SHARING_MODE_EXCLUSIVE + 1),\n    VK_SHARING_MODE_MAX_ENUM = 0x7FFFFFFF\n} VkSharingMode;\n\ntypedef enum VkImageLayout {\n    VK_IMAGE_LAYOUT_UNDEFINED = 0,\n    VK_IMAGE_LAYOUT_GENERAL = 1,\n    VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL = 2,\n    VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL = 3,\n    VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL = 4,\n    VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL = 5,\n    VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL = 6,\n    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL = 7,\n    VK_IMAGE_LAYOUT_PREINITIALIZED = 8,\n    VK_IMAGE_LAYOUT_PRESENT_SRC_KHR = 1000001002,\n    VK_IMAGE_LAYOUT_BEGIN_RANGE = VK_IMAGE_LAYOUT_UNDEFINED,\n    VK_IMAGE_LAYOUT_END_RANGE = VK_IMAGE_LAYOUT_PREINITIALIZED,\n    VK_IMAGE_LAYOUT_RANGE_SIZE = (VK_IMAGE_LAYOUT_PREINITIALIZED - VK_IMAGE_LAYOUT_UNDEFINED + 1),\n    VK_IMAGE_LAYOUT_MAX_ENUM = 0x7FFFFFFF\n} VkImageLayout;\n\ntypedef enum VkImageViewType {\n    VK_IMAGE_VIEW_TYPE_1D = 0,\n    VK_IMAGE_VIEW_TYPE_2D = 1,\n    VK_IMAGE_VIEW_TYPE_3D = 2,\n    VK_IMAGE_VIEW_TYPE_CUBE = 3,\n    VK_IMAGE_VIEW_TYPE_1D_ARRAY = 4,\n    VK_IMAGE_VIEW_TYPE_2D_ARRAY = 5,\n    VK_IMAGE_VIEW_TYPE_CUBE_ARRAY = 6,\n    VK_IMAGE_VIEW_TYPE_BEGIN_RANGE = VK_IMAGE_VIEW_TYPE_1D,\n    VK_IMAGE_VIEW_TYPE_END_RANGE = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY,\n    VK_IMAGE_VIEW_TYPE_RANGE_SIZE = (VK_IMAGE_VIEW_TYPE_CUBE_ARRAY - VK_IMAGE_VIEW_TYPE_1D + 1),\n    VK_IMAGE_VIEW_TYPE_MAX_ENUM = 0x7FFFFFFF\n} VkImageViewType;\n\ntypedef enum VkComponentSwizzle {\n    VK_COMPONENT_SWIZZLE_IDENTITY = 0,\n    VK_COMPONENT_SWIZZLE_ZERO = 1,\n    VK_COMPONENT_SWIZZLE_ONE = 2,\n    VK_COMPONENT_SWIZZLE_R = 3,\n    VK_COMPONENT_SWIZZLE_G = 4,\n    VK_COMPONENT_SWIZZLE_B = 5,\n    VK_COMPONENT_SWIZZLE_A = 6,\n    VK_COMPONENT_SWIZZLE_BEGIN_RANGE = VK_COMPONENT_SWIZZLE_IDENTITY,\n    VK_COMPONENT_SWIZZLE_END_RANGE = VK_COMPONENT_SWIZZLE_A,\n    VK_COMPONENT_SWIZZLE_RANGE_SIZE = (VK_COMPONENT_SWIZZLE_A - VK_COMPONENT_SWIZZLE_IDENTITY + 1),\n    VK_COMPONENT_SWIZZLE_MAX_ENUM = 0x7FFFFFFF\n} VkComponentSwizzle;\n\ntypedef enum VkVertexInputRate {\n    VK_VERTEX_INPUT_RATE_VERTEX = 0,\n    VK_VERTEX_INPUT_RATE_INSTANCE = 1,\n    VK_VERTEX_INPUT_RATE_BEGIN_RANGE = VK_VERTEX_INPUT_RATE_VERTEX,\n    VK_VERTEX_INPUT_RATE_END_RANGE = VK_VERTEX_INPUT_RATE_INSTANCE,\n    VK_VERTEX_INPUT_RATE_RANGE_SIZE = (VK_VERTEX_INPUT_RATE_INSTANCE - VK_VERTEX_INPUT_RATE_VERTEX + 1),\n    VK_VERTEX_INPUT_RATE_MAX_ENUM = 0x7FFFFFFF\n} VkVertexInputRate;\n\ntypedef enum VkPrimitiveTopology {\n    VK_PRIMITIVE_TOPOLOGY_POINT_LIST = 0,\n    VK_PRIMITIVE_TOPOLOGY_LINE_LIST = 1,\n    VK_PRIMITIVE_TOPOLOGY_LINE_STRIP = 2,\n    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST = 3,\n    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP = 4,\n    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN = 5,\n    VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY = 6,\n    VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY = 7,\n    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY = 8,\n    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY = 9,\n    VK_PRIMITIVE_TOPOLOGY_PATCH_LIST = 10,\n    VK_PRIMITIVE_TOPOLOGY_BEGIN_RANGE = VK_PRIMITIVE_TOPOLOGY_POINT_LIST,\n    VK_PRIMITIVE_TOPOLOGY_END_RANGE = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST,\n    VK_PRIMITIVE_TOPOLOGY_RANGE_SIZE = (VK_PRIMITIVE_TOPOLOGY_PATCH_LIST - VK_PRIMITIVE_TOPOLOGY_POINT_LIST + 1),\n    VK_PRIMITIVE_TOPOLOGY_MAX_ENUM = 0x7FFFFFFF\n} VkPrimitiveTopology;\n\ntypedef enum VkPolygonMode {\n    VK_POLYGON_MODE_FILL = 0,\n    VK_POLYGON_MODE_LINE = 1,\n    VK_POLYGON_MODE_POINT = 2,\n    VK_POLYGON_MODE_BEGIN_RANGE = VK_POLYGON_MODE_FILL,\n    VK_POLYGON_MODE_END_RANGE = VK_POLYGON_MODE_POINT,\n    VK_POLYGON_MODE_RANGE_SIZE = (VK_POLYGON_MODE_POINT - VK_POLYGON_MODE_FILL + 1),\n    VK_POLYGON_MODE_MAX_ENUM = 0x7FFFFFFF\n} VkPolygonMode;\n\ntypedef enum VkFrontFace {\n    VK_FRONT_FACE_COUNTER_CLOCKWISE = 0,\n    VK_FRONT_FACE_CLOCKWISE = 1,\n    VK_FRONT_FACE_BEGIN_RANGE = VK_FRONT_FACE_COUNTER_CLOCKWISE,\n    VK_FRONT_FACE_END_RANGE = VK_FRONT_FACE_CLOCKWISE,\n    VK_FRONT_FACE_RANGE_SIZE = (VK_FRONT_FACE_CLOCKWISE - VK_FRONT_FACE_COUNTER_CLOCKWISE + 1),\n    VK_FRONT_FACE_MAX_ENUM = 0x7FFFFFFF\n} VkFrontFace;\n\ntypedef enum VkCompareOp {\n    VK_COMPARE_OP_NEVER = 0,\n    VK_COMPARE_OP_LESS = 1,\n    VK_COMPARE_OP_EQUAL = 2,\n    VK_COMPARE_OP_LESS_OR_EQUAL = 3,\n    VK_COMPARE_OP_GREATER = 4,\n    VK_COMPARE_OP_NOT_EQUAL = 5,\n    VK_COMPARE_OP_GREATER_OR_EQUAL = 6,\n    VK_COMPARE_OP_ALWAYS = 7,\n    VK_COMPARE_OP_BEGIN_RANGE = VK_COMPARE_OP_NEVER,\n    VK_COMPARE_OP_END_RANGE = VK_COMPARE_OP_ALWAYS,\n    VK_COMPARE_OP_RANGE_SIZE = (VK_COMPARE_OP_ALWAYS - VK_COMPARE_OP_NEVER + 1),\n    VK_COMPARE_OP_MAX_ENUM = 0x7FFFFFFF\n} VkCompareOp;\n\ntypedef enum VkStencilOp {\n    VK_STENCIL_OP_KEEP = 0,\n    VK_STENCIL_OP_ZERO = 1,\n    VK_STENCIL_OP_REPLACE = 2,\n    VK_STENCIL_OP_INCREMENT_AND_CLAMP = 3,\n    VK_STENCIL_OP_DECREMENT_AND_CLAMP = 4,\n    VK_STENCIL_OP_INVERT = 5,\n    VK_STENCIL_OP_INCREMENT_AND_WRAP = 6,\n    VK_STENCIL_OP_DECREMENT_AND_WRAP = 7,\n    VK_STENCIL_OP_BEGIN_RANGE = VK_STENCIL_OP_KEEP,\n    VK_STENCIL_OP_END_RANGE = VK_STENCIL_OP_DECREMENT_AND_WRAP,\n    VK_STENCIL_OP_RANGE_SIZE = (VK_STENCIL_OP_DECREMENT_AND_WRAP - VK_STENCIL_OP_KEEP + 1),\n    VK_STENCIL_OP_MAX_ENUM = 0x7FFFFFFF\n} VkStencilOp;\n\ntypedef enum VkLogicOp {\n    VK_LOGIC_OP_CLEAR = 0,\n    VK_LOGIC_OP_AND = 1,\n    VK_LOGIC_OP_AND_REVERSE = 2,\n    VK_LOGIC_OP_COPY = 3,\n    VK_LOGIC_OP_AND_INVERTED = 4,\n    VK_LOGIC_OP_NO_OP = 5,\n    VK_LOGIC_OP_XOR = 6,\n    VK_LOGIC_OP_OR = 7,\n    VK_LOGIC_OP_NOR = 8,\n    VK_LOGIC_OP_EQUIVALENT = 9,\n    VK_LOGIC_OP_INVERT = 10,\n    VK_LOGIC_OP_OR_REVERSE = 11,\n    VK_LOGIC_OP_COPY_INVERTED = 12,\n    VK_LOGIC_OP_OR_INVERTED = 13,\n    VK_LOGIC_OP_NAND = 14,\n    VK_LOGIC_OP_SET = 15,\n    VK_LOGIC_OP_BEGIN_RANGE = VK_LOGIC_OP_CLEAR,\n    VK_LOGIC_OP_END_RANGE = VK_LOGIC_OP_SET,\n    VK_LOGIC_OP_RANGE_SIZE = (VK_LOGIC_OP_SET - VK_LOGIC_OP_CLEAR + 1),\n    VK_LOGIC_OP_MAX_ENUM = 0x7FFFFFFF\n} VkLogicOp;\n\ntypedef enum VkBlendFactor {\n    VK_BLEND_FACTOR_ZERO = 0,\n    VK_BLEND_FACTOR_ONE = 1,\n    VK_BLEND_FACTOR_SRC_COLOR = 2,\n    VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR = 3,\n    VK_BLEND_FACTOR_DST_COLOR = 4,\n    VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR = 5,\n    VK_BLEND_FACTOR_SRC_ALPHA = 6,\n    VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA = 7,\n    VK_BLEND_FACTOR_DST_ALPHA = 8,\n    VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA = 9,\n    VK_BLEND_FACTOR_CONSTANT_COLOR = 10,\n    VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR = 11,\n    VK_BLEND_FACTOR_CONSTANT_ALPHA = 12,\n    VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA = 13,\n    VK_BLEND_FACTOR_SRC_ALPHA_SATURATE = 14,\n    VK_BLEND_FACTOR_SRC1_COLOR = 15,\n    VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR = 16,\n    VK_BLEND_FACTOR_SRC1_ALPHA = 17,\n    VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA = 18,\n    VK_BLEND_FACTOR_BEGIN_RANGE = VK_BLEND_FACTOR_ZERO,\n    VK_BLEND_FACTOR_END_RANGE = VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA,\n    VK_BLEND_FACTOR_RANGE_SIZE = (VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA - VK_BLEND_FACTOR_ZERO + 1),\n    VK_BLEND_FACTOR_MAX_ENUM = 0x7FFFFFFF\n} VkBlendFactor;\n\ntypedef enum VkBlendOp {\n    VK_BLEND_OP_ADD = 0,\n    VK_BLEND_OP_SUBTRACT = 1,\n    VK_BLEND_OP_REVERSE_SUBTRACT = 2,\n    VK_BLEND_OP_MIN = 3,\n    VK_BLEND_OP_MAX = 4,\n    VK_BLEND_OP_BEGIN_RANGE = VK_BLEND_OP_ADD,\n    VK_BLEND_OP_END_RANGE = VK_BLEND_OP_MAX,\n    VK_BLEND_OP_RANGE_SIZE = (VK_BLEND_OP_MAX - VK_BLEND_OP_ADD + 1),\n    VK_BLEND_OP_MAX_ENUM = 0x7FFFFFFF\n} VkBlendOp;\n\ntypedef enum VkDynamicState {\n    VK_DYNAMIC_STATE_VIEWPORT = 0,\n    VK_DYNAMIC_STATE_SCISSOR = 1,\n    VK_DYNAMIC_STATE_LINE_WIDTH = 2,\n    VK_DYNAMIC_STATE_DEPTH_BIAS = 3,\n    VK_DYNAMIC_STATE_BLEND_CONSTANTS = 4,\n    VK_DYNAMIC_STATE_DEPTH_BOUNDS = 5,\n    VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK = 6,\n    VK_DYNAMIC_STATE_STENCIL_WRITE_MASK = 7,\n    VK_DYNAMIC_STATE_STENCIL_REFERENCE = 8,\n    VK_DYNAMIC_STATE_BEGIN_RANGE = VK_DYNAMIC_STATE_VIEWPORT,\n    VK_DYNAMIC_STATE_END_RANGE = VK_DYNAMIC_STATE_STENCIL_REFERENCE,\n    VK_DYNAMIC_STATE_RANGE_SIZE = (VK_DYNAMIC_STATE_STENCIL_REFERENCE - VK_DYNAMIC_STATE_VIEWPORT + 1),\n    VK_DYNAMIC_STATE_MAX_ENUM = 0x7FFFFFFF\n} VkDynamicState;\n\ntypedef enum VkFilter {\n    VK_FILTER_NEAREST = 0,\n    VK_FILTER_LINEAR = 1,\n    VK_FILTER_CUBIC_IMG = 1000015000,\n    VK_FILTER_BEGIN_RANGE = VK_FILTER_NEAREST,\n    VK_FILTER_END_RANGE = VK_FILTER_LINEAR,\n    VK_FILTER_RANGE_SIZE = (VK_FILTER_LINEAR - VK_FILTER_NEAREST + 1),\n    VK_FILTER_MAX_ENUM = 0x7FFFFFFF\n} VkFilter;\n\ntypedef enum VkSamplerMipmapMode {\n    VK_SAMPLER_MIPMAP_MODE_NEAREST = 0,\n    VK_SAMPLER_MIPMAP_MODE_LINEAR = 1,\n    VK_SAMPLER_MIPMAP_MODE_BEGIN_RANGE = VK_SAMPLER_MIPMAP_MODE_NEAREST,\n    VK_SAMPLER_MIPMAP_MODE_END_RANGE = VK_SAMPLER_MIPMAP_MODE_LINEAR,\n    VK_SAMPLER_MIPMAP_MODE_RANGE_SIZE = (VK_SAMPLER_MIPMAP_MODE_LINEAR - VK_SAMPLER_MIPMAP_MODE_NEAREST + 1),\n    VK_SAMPLER_MIPMAP_MODE_MAX_ENUM = 0x7FFFFFFF\n} VkSamplerMipmapMode;\n\ntypedef enum VkSamplerAddressMode {\n    VK_SAMPLER_ADDRESS_MODE_REPEAT = 0,\n    VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT = 1,\n    VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE = 2,\n    VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER = 3,\n    VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE = 4,\n    VK_SAMPLER_ADDRESS_MODE_BEGIN_RANGE = VK_SAMPLER_ADDRESS_MODE_REPEAT,\n    VK_SAMPLER_ADDRESS_MODE_END_RANGE = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,\n    VK_SAMPLER_ADDRESS_MODE_RANGE_SIZE = (VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER - VK_SAMPLER_ADDRESS_MODE_REPEAT + 1),\n    VK_SAMPLER_ADDRESS_MODE_MAX_ENUM = 0x7FFFFFFF\n} VkSamplerAddressMode;\n\ntypedef enum VkBorderColor {\n    VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK = 0,\n    VK_BORDER_COLOR_INT_TRANSPARENT_BLACK = 1,\n    VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK = 2,\n    VK_BORDER_COLOR_INT_OPAQUE_BLACK = 3,\n    VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE = 4,\n    VK_BORDER_COLOR_INT_OPAQUE_WHITE = 5,\n    VK_BORDER_COLOR_BEGIN_RANGE = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,\n    VK_BORDER_COLOR_END_RANGE = VK_BORDER_COLOR_INT_OPAQUE_WHITE,\n    VK_BORDER_COLOR_RANGE_SIZE = (VK_BORDER_COLOR_INT_OPAQUE_WHITE - VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK + 1),\n    VK_BORDER_COLOR_MAX_ENUM = 0x7FFFFFFF\n} VkBorderColor;\n\ntypedef enum VkDescriptorType {\n    VK_DESCRIPTOR_TYPE_SAMPLER = 0,\n    VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER = 1,\n    VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE = 2,\n    VK_DESCRIPTOR_TYPE_STORAGE_IMAGE = 3,\n    VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER = 4,\n    VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER = 5,\n    VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER = 6,\n    VK_DESCRIPTOR_TYPE_STORAGE_BUFFER = 7,\n    VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC = 8,\n    VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC = 9,\n    VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT = 10,\n    VK_DESCRIPTOR_TYPE_BEGIN_RANGE = VK_DESCRIPTOR_TYPE_SAMPLER,\n    VK_DESCRIPTOR_TYPE_END_RANGE = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,\n    VK_DESCRIPTOR_TYPE_RANGE_SIZE = (VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT - VK_DESCRIPTOR_TYPE_SAMPLER + 1),\n    VK_DESCRIPTOR_TYPE_MAX_ENUM = 0x7FFFFFFF\n} VkDescriptorType;\n\ntypedef enum VkAttachmentLoadOp {\n    VK_ATTACHMENT_LOAD_OP_LOAD = 0,\n    VK_ATTACHMENT_LOAD_OP_CLEAR = 1,\n    VK_ATTACHMENT_LOAD_OP_DONT_CARE = 2,\n    VK_ATTACHMENT_LOAD_OP_BEGIN_RANGE = VK_ATTACHMENT_LOAD_OP_LOAD,\n    VK_ATTACHMENT_LOAD_OP_END_RANGE = VK_ATTACHMENT_LOAD_OP_DONT_CARE,\n    VK_ATTACHMENT_LOAD_OP_RANGE_SIZE = (VK_ATTACHMENT_LOAD_OP_DONT_CARE - VK_ATTACHMENT_LOAD_OP_LOAD + 1),\n    VK_ATTACHMENT_LOAD_OP_MAX_ENUM = 0x7FFFFFFF\n} VkAttachmentLoadOp;\n\ntypedef enum VkAttachmentStoreOp {\n    VK_ATTACHMENT_STORE_OP_STORE = 0,\n    VK_ATTACHMENT_STORE_OP_DONT_CARE = 1,\n    VK_ATTACHMENT_STORE_OP_BEGIN_RANGE = VK_ATTACHMENT_STORE_OP_STORE,\n    VK_ATTACHMENT_STORE_OP_END_RANGE = VK_ATTACHMENT_STORE_OP_DONT_CARE,\n    VK_ATTACHMENT_STORE_OP_RANGE_SIZE = (VK_ATTACHMENT_STORE_OP_DONT_CARE - VK_ATTACHMENT_STORE_OP_STORE + 1),\n    VK_ATTACHMENT_STORE_OP_MAX_ENUM = 0x7FFFFFFF\n} VkAttachmentStoreOp;\n\ntypedef enum VkPipelineBindPoint {\n    VK_PIPELINE_BIND_POINT_GRAPHICS = 0,\n    VK_PIPELINE_BIND_POINT_COMPUTE = 1,\n    VK_PIPELINE_BIND_POINT_BEGIN_RANGE = VK_PIPELINE_BIND_POINT_GRAPHICS,\n    VK_PIPELINE_BIND_POINT_END_RANGE = VK_PIPELINE_BIND_POINT_COMPUTE,\n    VK_PIPELINE_BIND_POINT_RANGE_SIZE = (VK_PIPELINE_BIND_POINT_COMPUTE - VK_PIPELINE_BIND_POINT_GRAPHICS + 1),\n    VK_PIPELINE_BIND_POINT_MAX_ENUM = 0x7FFFFFFF\n} VkPipelineBindPoint;\n\ntypedef enum VkCommandBufferLevel {\n    VK_COMMAND_BUFFER_LEVEL_PRIMARY = 0,\n    VK_COMMAND_BUFFER_LEVEL_SECONDARY = 1,\n    VK_COMMAND_BUFFER_LEVEL_BEGIN_RANGE = VK_COMMAND_BUFFER_LEVEL_PRIMARY,\n    VK_COMMAND_BUFFER_LEVEL_END_RANGE = VK_COMMAND_BUFFER_LEVEL_SECONDARY,\n    VK_COMMAND_BUFFER_LEVEL_RANGE_SIZE = (VK_COMMAND_BUFFER_LEVEL_SECONDARY - VK_COMMAND_BUFFER_LEVEL_PRIMARY + 1),\n    VK_COMMAND_BUFFER_LEVEL_MAX_ENUM = 0x7FFFFFFF\n} VkCommandBufferLevel;\n\ntypedef enum VkIndexType {\n    VK_INDEX_TYPE_UINT16 = 0,\n    VK_INDEX_TYPE_UINT32 = 1,\n    VK_INDEX_TYPE_BEGIN_RANGE = VK_INDEX_TYPE_UINT16,\n    VK_INDEX_TYPE_END_RANGE = VK_INDEX_TYPE_UINT32,\n    VK_INDEX_TYPE_RANGE_SIZE = (VK_INDEX_TYPE_UINT32 - VK_INDEX_TYPE_UINT16 + 1),\n    VK_INDEX_TYPE_MAX_ENUM = 0x7FFFFFFF\n} VkIndexType;\n\ntypedef enum VkSubpassContents {\n    VK_SUBPASS_CONTENTS_INLINE = 0,\n    VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS = 1,\n    VK_SUBPASS_CONTENTS_BEGIN_RANGE = VK_SUBPASS_CONTENTS_INLINE,\n    VK_SUBPASS_CONTENTS_END_RANGE = VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS,\n    VK_SUBPASS_CONTENTS_RANGE_SIZE = (VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS - VK_SUBPASS_CONTENTS_INLINE + 1),\n    VK_SUBPASS_CONTENTS_MAX_ENUM = 0x7FFFFFFF\n} VkSubpassContents;\n\ntypedef VkFlags VkInstanceCreateFlags;\n\ntypedef enum VkFormatFeatureFlagBits {\n    VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT = 0x00000001,\n    VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT = 0x00000002,\n    VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT = 0x00000004,\n    VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT = 0x00000008,\n    VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT = 0x00000010,\n    VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT = 0x00000020,\n    VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT = 0x00000040,\n    VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT = 0x00000080,\n    VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT = 0x00000100,\n    VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT = 0x00000200,\n    VK_FORMAT_FEATURE_BLIT_SRC_BIT = 0x00000400,\n    VK_FORMAT_FEATURE_BLIT_DST_BIT = 0x00000800,\n    VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT = 0x00001000,\n    VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_CUBIC_BIT_IMG = 0x00002000,\n    VK_FORMAT_FEATURE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkFormatFeatureFlagBits;\ntypedef VkFlags VkFormatFeatureFlags;\n\ntypedef enum VkImageUsageFlagBits {\n    VK_IMAGE_USAGE_TRANSFER_SRC_BIT = 0x00000001,\n    VK_IMAGE_USAGE_TRANSFER_DST_BIT = 0x00000002,\n    VK_IMAGE_USAGE_SAMPLED_BIT = 0x00000004,\n    VK_IMAGE_USAGE_STORAGE_BIT = 0x00000008,\n    VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT = 0x00000010,\n    VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT = 0x00000020,\n    VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT = 0x00000040,\n    VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT = 0x00000080,\n    VK_IMAGE_USAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkImageUsageFlagBits;\ntypedef VkFlags VkImageUsageFlags;\n\ntypedef enum VkImageCreateFlagBits {\n    VK_IMAGE_CREATE_SPARSE_BINDING_BIT = 0x00000001,\n    VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT = 0x00000002,\n    VK_IMAGE_CREATE_SPARSE_ALIASED_BIT = 0x00000004,\n    VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT = 0x00000008,\n    VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT = 0x00000010,\n    VK_IMAGE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkImageCreateFlagBits;\ntypedef VkFlags VkImageCreateFlags;\n\ntypedef enum VkSampleCountFlagBits {\n    VK_SAMPLE_COUNT_1_BIT = 0x00000001,\n    VK_SAMPLE_COUNT_2_BIT = 0x00000002,\n    VK_SAMPLE_COUNT_4_BIT = 0x00000004,\n    VK_SAMPLE_COUNT_8_BIT = 0x00000008,\n    VK_SAMPLE_COUNT_16_BIT = 0x00000010,\n    VK_SAMPLE_COUNT_32_BIT = 0x00000020,\n    VK_SAMPLE_COUNT_64_BIT = 0x00000040,\n    VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkSampleCountFlagBits;\ntypedef VkFlags VkSampleCountFlags;\n\ntypedef enum VkQueueFlagBits {\n    VK_QUEUE_GRAPHICS_BIT = 0x00000001,\n    VK_QUEUE_COMPUTE_BIT = 0x00000002,\n    VK_QUEUE_TRANSFER_BIT = 0x00000004,\n    VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008,\n    VK_QUEUE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkQueueFlagBits;\ntypedef VkFlags VkQueueFlags;\n\ntypedef enum VkMemoryPropertyFlagBits {\n    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT = 0x00000001,\n    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT = 0x00000002,\n    VK_MEMORY_PROPERTY_HOST_COHERENT_BIT = 0x00000004,\n    VK_MEMORY_PROPERTY_HOST_CACHED_BIT = 0x00000008,\n    VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT = 0x00000010,\n    VK_MEMORY_PROPERTY_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkMemoryPropertyFlagBits;\ntypedef VkFlags VkMemoryPropertyFlags;\n\ntypedef enum VkMemoryHeapFlagBits {\n    VK_MEMORY_HEAP_DEVICE_LOCAL_BIT = 0x00000001,\n    VK_MEMORY_HEAP_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkMemoryHeapFlagBits;\ntypedef VkFlags VkMemoryHeapFlags;\ntypedef VkFlags VkDeviceCreateFlags;\ntypedef VkFlags VkDeviceQueueCreateFlags;\n\ntypedef enum VkPipelineStageFlagBits {\n    VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT = 0x00000001,\n    VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT = 0x00000002,\n    VK_PIPELINE_STAGE_VERTEX_INPUT_BIT = 0x00000004,\n    VK_PIPELINE_STAGE_VERTEX_SHADER_BIT = 0x00000008,\n    VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT = 0x00000010,\n    VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT = 0x00000020,\n    VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT = 0x00000040,\n    VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT = 0x00000080,\n    VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT = 0x00000100,\n    VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT = 0x00000200,\n    VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT = 0x00000400,\n    VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT = 0x00000800,\n    VK_PIPELINE_STAGE_TRANSFER_BIT = 0x00001000,\n    VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT = 0x00002000,\n    VK_PIPELINE_STAGE_HOST_BIT = 0x00004000,\n    VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT = 0x00008000,\n    VK_PIPELINE_STAGE_ALL_COMMANDS_BIT = 0x00010000,\n    VK_PIPELINE_STAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkPipelineStageFlagBits;\ntypedef VkFlags VkPipelineStageFlags;\ntypedef VkFlags VkMemoryMapFlags;\n\ntypedef enum VkImageAspectFlagBits {\n    VK_IMAGE_ASPECT_COLOR_BIT = 0x00000001,\n    VK_IMAGE_ASPECT_DEPTH_BIT = 0x00000002,\n    VK_IMAGE_ASPECT_STENCIL_BIT = 0x00000004,\n    VK_IMAGE_ASPECT_METADATA_BIT = 0x00000008,\n    VK_IMAGE_ASPECT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkImageAspectFlagBits;\ntypedef VkFlags VkImageAspectFlags;\n\ntypedef enum VkSparseImageFormatFlagBits {\n    VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT = 0x00000001,\n    VK_SPARSE_IMAGE_FORMAT_ALIGNED_MIP_SIZE_BIT = 0x00000002,\n    VK_SPARSE_IMAGE_FORMAT_NONSTANDARD_BLOCK_SIZE_BIT = 0x00000004,\n    VK_SPARSE_IMAGE_FORMAT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkSparseImageFormatFlagBits;\ntypedef VkFlags VkSparseImageFormatFlags;\n\ntypedef enum VkSparseMemoryBindFlagBits {\n    VK_SPARSE_MEMORY_BIND_METADATA_BIT = 0x00000001,\n    VK_SPARSE_MEMORY_BIND_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkSparseMemoryBindFlagBits;\ntypedef VkFlags VkSparseMemoryBindFlags;\n\ntypedef enum VkFenceCreateFlagBits {\n    VK_FENCE_CREATE_SIGNALED_BIT = 0x00000001,\n    VK_FENCE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkFenceCreateFlagBits;\ntypedef VkFlags VkFenceCreateFlags;\ntypedef VkFlags VkSemaphoreCreateFlags;\ntypedef VkFlags VkEventCreateFlags;\ntypedef VkFlags VkQueryPoolCreateFlags;\n\ntypedef enum VkQueryPipelineStatisticFlagBits {\n    VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_VERTICES_BIT = 0x00000001,\n    VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_PRIMITIVES_BIT = 0x00000002,\n    VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT = 0x00000004,\n    VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_INVOCATIONS_BIT = 0x00000008,\n    VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_PRIMITIVES_BIT = 0x00000010,\n    VK_QUERY_PIPELINE_STATISTIC_CLIPPING_INVOCATIONS_BIT = 0x00000020,\n    VK_QUERY_PIPELINE_STATISTIC_CLIPPING_PRIMITIVES_BIT = 0x00000040,\n    VK_QUERY_PIPELINE_STATISTIC_FRAGMENT_SHADER_INVOCATIONS_BIT = 0x00000080,\n    VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_CONTROL_SHADER_PATCHES_BIT = 0x00000100,\n    VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT = 0x00000200,\n    VK_QUERY_PIPELINE_STATISTIC_COMPUTE_SHADER_INVOCATIONS_BIT = 0x00000400,\n    VK_QUERY_PIPELINE_STATISTIC_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkQueryPipelineStatisticFlagBits;\ntypedef VkFlags VkQueryPipelineStatisticFlags;\n\ntypedef enum VkQueryResultFlagBits {\n    VK_QUERY_RESULT_64_BIT = 0x00000001,\n    VK_QUERY_RESULT_WAIT_BIT = 0x00000002,\n    VK_QUERY_RESULT_WITH_AVAILABILITY_BIT = 0x00000004,\n    VK_QUERY_RESULT_PARTIAL_BIT = 0x00000008,\n    VK_QUERY_RESULT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkQueryResultFlagBits;\ntypedef VkFlags VkQueryResultFlags;\n\ntypedef enum VkBufferCreateFlagBits {\n    VK_BUFFER_CREATE_SPARSE_BINDING_BIT = 0x00000001,\n    VK_BUFFER_CREATE_SPARSE_RESIDENCY_BIT = 0x00000002,\n    VK_BUFFER_CREATE_SPARSE_ALIASED_BIT = 0x00000004,\n    VK_BUFFER_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkBufferCreateFlagBits;\ntypedef VkFlags VkBufferCreateFlags;\n\ntypedef enum VkBufferUsageFlagBits {\n    VK_BUFFER_USAGE_TRANSFER_SRC_BIT = 0x00000001,\n    VK_BUFFER_USAGE_TRANSFER_DST_BIT = 0x00000002,\n    VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT = 0x00000004,\n    VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT = 0x00000008,\n    VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT = 0x00000010,\n    VK_BUFFER_USAGE_STORAGE_BUFFER_BIT = 0x00000020,\n    VK_BUFFER_USAGE_INDEX_BUFFER_BIT = 0x00000040,\n    VK_BUFFER_USAGE_VERTEX_BUFFER_BIT = 0x00000080,\n    VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT = 0x00000100,\n    VK_BUFFER_USAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkBufferUsageFlagBits;\ntypedef VkFlags VkBufferUsageFlags;\ntypedef VkFlags VkBufferViewCreateFlags;\ntypedef VkFlags VkImageViewCreateFlags;\ntypedef VkFlags VkShaderModuleCreateFlags;\ntypedef VkFlags VkPipelineCacheCreateFlags;\n\ntypedef enum VkPipelineCreateFlagBits {\n    VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT = 0x00000001,\n    VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT = 0x00000002,\n    VK_PIPELINE_CREATE_DERIVATIVE_BIT = 0x00000004,\n    VK_PIPELINE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkPipelineCreateFlagBits;\ntypedef VkFlags VkPipelineCreateFlags;\ntypedef VkFlags VkPipelineShaderStageCreateFlags;\n\ntypedef enum VkShaderStageFlagBits {\n    VK_SHADER_STAGE_VERTEX_BIT = 0x00000001,\n    VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT = 0x00000002,\n    VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT = 0x00000004,\n    VK_SHADER_STAGE_GEOMETRY_BIT = 0x00000008,\n    VK_SHADER_STAGE_FRAGMENT_BIT = 0x00000010,\n    VK_SHADER_STAGE_COMPUTE_BIT = 0x00000020,\n    VK_SHADER_STAGE_ALL_GRAPHICS = 0x0000001F,\n    VK_SHADER_STAGE_ALL = 0x7FFFFFFF,\n    VK_SHADER_STAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkShaderStageFlagBits;\ntypedef VkFlags VkPipelineVertexInputStateCreateFlags;\ntypedef VkFlags VkPipelineInputAssemblyStateCreateFlags;\ntypedef VkFlags VkPipelineTessellationStateCreateFlags;\ntypedef VkFlags VkPipelineViewportStateCreateFlags;\ntypedef VkFlags VkPipelineRasterizationStateCreateFlags;\n\ntypedef enum VkCullModeFlagBits {\n    VK_CULL_MODE_NONE = 0,\n    VK_CULL_MODE_FRONT_BIT = 0x00000001,\n    VK_CULL_MODE_BACK_BIT = 0x00000002,\n    VK_CULL_MODE_FRONT_AND_BACK = 0x00000003,\n    VK_CULL_MODE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkCullModeFlagBits;\ntypedef VkFlags VkCullModeFlags;\ntypedef VkFlags VkPipelineMultisampleStateCreateFlags;\ntypedef VkFlags VkPipelineDepthStencilStateCreateFlags;\ntypedef VkFlags VkPipelineColorBlendStateCreateFlags;\n\ntypedef enum VkColorComponentFlagBits {\n    VK_COLOR_COMPONENT_R_BIT = 0x00000001,\n    VK_COLOR_COMPONENT_G_BIT = 0x00000002,\n    VK_COLOR_COMPONENT_B_BIT = 0x00000004,\n    VK_COLOR_COMPONENT_A_BIT = 0x00000008,\n    VK_COLOR_COMPONENT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkColorComponentFlagBits;\ntypedef VkFlags VkColorComponentFlags;\ntypedef VkFlags VkPipelineDynamicStateCreateFlags;\ntypedef VkFlags VkPipelineLayoutCreateFlags;\ntypedef VkFlags VkShaderStageFlags;\ntypedef VkFlags VkSamplerCreateFlags;\ntypedef VkFlags VkDescriptorSetLayoutCreateFlags;\n\ntypedef enum VkDescriptorPoolCreateFlagBits {\n    VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT = 0x00000001,\n    VK_DESCRIPTOR_POOL_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkDescriptorPoolCreateFlagBits;\ntypedef VkFlags VkDescriptorPoolCreateFlags;\ntypedef VkFlags VkDescriptorPoolResetFlags;\ntypedef VkFlags VkFramebufferCreateFlags;\ntypedef VkFlags VkRenderPassCreateFlags;\n\ntypedef enum VkAttachmentDescriptionFlagBits {\n    VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT = 0x00000001,\n    VK_ATTACHMENT_DESCRIPTION_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkAttachmentDescriptionFlagBits;\ntypedef VkFlags VkAttachmentDescriptionFlags;\ntypedef VkFlags VkSubpassDescriptionFlags;\n\ntypedef enum VkAccessFlagBits {\n    VK_ACCESS_INDIRECT_COMMAND_READ_BIT = 0x00000001,\n    VK_ACCESS_INDEX_READ_BIT = 0x00000002,\n    VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT = 0x00000004,\n    VK_ACCESS_UNIFORM_READ_BIT = 0x00000008,\n    VK_ACCESS_INPUT_ATTACHMENT_READ_BIT = 0x00000010,\n    VK_ACCESS_SHADER_READ_BIT = 0x00000020,\n    VK_ACCESS_SHADER_WRITE_BIT = 0x00000040,\n    VK_ACCESS_COLOR_ATTACHMENT_READ_BIT = 0x00000080,\n    VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT = 0x00000100,\n    VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT = 0x00000200,\n    VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT = 0x00000400,\n    VK_ACCESS_TRANSFER_READ_BIT = 0x00000800,\n    VK_ACCESS_TRANSFER_WRITE_BIT = 0x00001000,\n    VK_ACCESS_HOST_READ_BIT = 0x00002000,\n    VK_ACCESS_HOST_WRITE_BIT = 0x00004000,\n    VK_ACCESS_MEMORY_READ_BIT = 0x00008000,\n    VK_ACCESS_MEMORY_WRITE_BIT = 0x00010000,\n    VK_ACCESS_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkAccessFlagBits;\ntypedef VkFlags VkAccessFlags;\n\ntypedef enum VkDependencyFlagBits {\n    VK_DEPENDENCY_BY_REGION_BIT = 0x00000001,\n    VK_DEPENDENCY_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkDependencyFlagBits;\ntypedef VkFlags VkDependencyFlags;\n\ntypedef enum VkCommandPoolCreateFlagBits {\n    VK_COMMAND_POOL_CREATE_TRANSIENT_BIT = 0x00000001,\n    VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT = 0x00000002,\n    VK_COMMAND_POOL_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkCommandPoolCreateFlagBits;\ntypedef VkFlags VkCommandPoolCreateFlags;\n\ntypedef enum VkCommandPoolResetFlagBits {\n    VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT = 0x00000001,\n    VK_COMMAND_POOL_RESET_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkCommandPoolResetFlagBits;\ntypedef VkFlags VkCommandPoolResetFlags;\n\ntypedef enum VkCommandBufferUsageFlagBits {\n    VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT = 0x00000001,\n    VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT = 0x00000002,\n    VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT = 0x00000004,\n    VK_COMMAND_BUFFER_USAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkCommandBufferUsageFlagBits;\ntypedef VkFlags VkCommandBufferUsageFlags;\n\ntypedef enum VkQueryControlFlagBits {\n    VK_QUERY_CONTROL_PRECISE_BIT = 0x00000001,\n    VK_QUERY_CONTROL_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkQueryControlFlagBits;\ntypedef VkFlags VkQueryControlFlags;\n\ntypedef enum VkCommandBufferResetFlagBits {\n    VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT = 0x00000001,\n    VK_COMMAND_BUFFER_RESET_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkCommandBufferResetFlagBits;\ntypedef VkFlags VkCommandBufferResetFlags;\n\ntypedef enum VkStencilFaceFlagBits {\n    VK_STENCIL_FACE_FRONT_BIT = 0x00000001,\n    VK_STENCIL_FACE_BACK_BIT = 0x00000002,\n    VK_STENCIL_FRONT_AND_BACK = 0x00000003,\n    VK_STENCIL_FACE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n} VkStencilFaceFlagBits;\ntypedef VkFlags VkStencilFaceFlags;\n\ntypedef void* (VKAPI_PTR *PFN_vkAllocationFunction)(\n    void*                                       pUserData,\n    size_t                                      size,\n    size_t                                      alignment,\n    VkSystemAllocationScope                     allocationScope);\n\ntypedef void* (VKAPI_PTR *PFN_vkReallocationFunction)(\n    void*                                       pUserData,\n    void*                                       pOriginal,\n    size_t                                      size,\n    size_t                                      alignment,\n    VkSystemAllocationScope                     allocationScope);\n\ntypedef void (VKAPI_PTR *PFN_vkFreeFunction)(\n    void*                                       pUserData,\n    void*                                       pMemory);\n\ntypedef void (VKAPI_PTR *PFN_vkInternalAllocationNotification)(\n    void*                                       pUserData,\n    size_t                                      size,\n    VkInternalAllocationType                    allocationType,\n    VkSystemAllocationScope                     allocationScope);\n\ntypedef void (VKAPI_PTR *PFN_vkInternalFreeNotification)(\n    void*                                       pUserData,\n    size_t                                      size,\n    VkInternalAllocationType                    allocationType,\n    VkSystemAllocationScope                     allocationScope);\n\ntypedef void (VKAPI_PTR *PFN_vkVoidFunction)(void);\n\ntypedef struct VkApplicationInfo {\n    VkStructureType    sType;\n    const void*        pNext;\n    const char*        pApplicationName;\n    uint32_t           applicationVersion;\n    const char*        pEngineName;\n    uint32_t           engineVersion;\n    uint32_t           apiVersion;\n} VkApplicationInfo;\n\ntypedef struct VkInstanceCreateInfo {\n    VkStructureType             sType;\n    const void*                 pNext;\n    VkInstanceCreateFlags       flags;\n    const VkApplicationInfo*    pApplicationInfo;\n    uint32_t                    enabledLayerCount;\n    const char* const*          ppEnabledLayerNames;\n    uint32_t                    enabledExtensionCount;\n    const char* const*          ppEnabledExtensionNames;\n} VkInstanceCreateInfo;\n\ntypedef struct VkAllocationCallbacks {\n    void*                                   pUserData;\n    PFN_vkAllocationFunction                pfnAllocation;\n    PFN_vkReallocationFunction              pfnReallocation;\n    PFN_vkFreeFunction                      pfnFree;\n    PFN_vkInternalAllocationNotification    pfnInternalAllocation;\n    PFN_vkInternalFreeNotification          pfnInternalFree;\n} VkAllocationCallbacks;\n\ntypedef struct VkPhysicalDeviceFeatures {\n    VkBool32    robustBufferAccess;\n    VkBool32    fullDrawIndexUint32;\n    VkBool32    imageCubeArray;\n    VkBool32    independentBlend;\n    VkBool32    geometryShader;\n    VkBool32    tessellationShader;\n    VkBool32    sampleRateShading;\n    VkBool32    dualSrcBlend;\n    VkBool32    logicOp;\n    VkBool32    multiDrawIndirect;\n    VkBool32    drawIndirectFirstInstance;\n    VkBool32    depthClamp;\n    VkBool32    depthBiasClamp;\n    VkBool32    fillModeNonSolid;\n    VkBool32    depthBounds;\n    VkBool32    wideLines;\n    VkBool32    largePoints;\n    VkBool32    alphaToOne;\n    VkBool32    multiViewport;\n    VkBool32    samplerAnisotropy;\n    VkBool32    textureCompressionETC2;\n    VkBool32    textureCompressionASTC_LDR;\n    VkBool32    textureCompressionBC;\n    VkBool32    occlusionQueryPrecise;\n    VkBool32    pipelineStatisticsQuery;\n    VkBool32    vertexPipelineStoresAndAtomics;\n    VkBool32    fragmentStoresAndAtomics;\n    VkBool32    shaderTessellationAndGeometryPointSize;\n    VkBool32    shaderImageGatherExtended;\n    VkBool32    shaderStorageImageExtendedFormats;\n    VkBool32    shaderStorageImageMultisample;\n    VkBool32    shaderStorageImageReadWithoutFormat;\n    VkBool32    shaderStorageImageWriteWithoutFormat;\n    VkBool32    shaderUniformBufferArrayDynamicIndexing;\n    VkBool32    shaderSampledImageArrayDynamicIndexing;\n    VkBool32    shaderStorageBufferArrayDynamicIndexing;\n    VkBool32    shaderStorageImageArrayDynamicIndexing;\n    VkBool32    shaderClipDistance;\n    VkBool32    shaderCullDistance;\n    VkBool32    shaderFloat64;\n    VkBool32    shaderInt64;\n    VkBool32    shaderInt16;\n    VkBool32    shaderResourceResidency;\n    VkBool32    shaderResourceMinLod;\n    VkBool32    sparseBinding;\n    VkBool32    sparseResidencyBuffer;\n    VkBool32    sparseResidencyImage2D;\n    VkBool32    sparseResidencyImage3D;\n    VkBool32    sparseResidency2Samples;\n    VkBool32    sparseResidency4Samples;\n    VkBool32    sparseResidency8Samples;\n    VkBool32    sparseResidency16Samples;\n    VkBool32    sparseResidencyAliased;\n    VkBool32    variableMultisampleRate;\n    VkBool32    inheritedQueries;\n} VkPhysicalDeviceFeatures;\n\ntypedef struct VkFormatProperties {\n    VkFormatFeatureFlags    linearTilingFeatures;\n    VkFormatFeatureFlags    optimalTilingFeatures;\n    VkFormatFeatureFlags    bufferFeatures;\n} VkFormatProperties;\n\ntypedef struct VkExtent3D {\n    uint32_t    width;\n    uint32_t    height;\n    uint32_t    depth;\n} VkExtent3D;\n\ntypedef struct VkImageFormatProperties {\n    VkExtent3D            maxExtent;\n    uint32_t              maxMipLevels;\n    uint32_t              maxArrayLayers;\n    VkSampleCountFlags    sampleCounts;\n    VkDeviceSize          maxResourceSize;\n} VkImageFormatProperties;\n\ntypedef struct VkPhysicalDeviceLimits {\n    uint32_t              maxImageDimension1D;\n    uint32_t              maxImageDimension2D;\n    uint32_t              maxImageDimension3D;\n    uint32_t              maxImageDimensionCube;\n    uint32_t              maxImageArrayLayers;\n    uint32_t              maxTexelBufferElements;\n    uint32_t              maxUniformBufferRange;\n    uint32_t              maxStorageBufferRange;\n    uint32_t              maxPushConstantsSize;\n    uint32_t              maxMemoryAllocationCount;\n    uint32_t              maxSamplerAllocationCount;\n    VkDeviceSize          bufferImageGranularity;\n    VkDeviceSize          sparseAddressSpaceSize;\n    uint32_t              maxBoundDescriptorSets;\n    uint32_t              maxPerStageDescriptorSamplers;\n    uint32_t              maxPerStageDescriptorUniformBuffers;\n    uint32_t              maxPerStageDescriptorStorageBuffers;\n    uint32_t              maxPerStageDescriptorSampledImages;\n    uint32_t              maxPerStageDescriptorStorageImages;\n    uint32_t              maxPerStageDescriptorInputAttachments;\n    uint32_t              maxPerStageResources;\n    uint32_t              maxDescriptorSetSamplers;\n    uint32_t              maxDescriptorSetUniformBuffers;\n    uint32_t              maxDescriptorSetUniformBuffersDynamic;\n    uint32_t              maxDescriptorSetStorageBuffers;\n    uint32_t              maxDescriptorSetStorageBuffersDynamic;\n    uint32_t              maxDescriptorSetSampledImages;\n    uint32_t              maxDescriptorSetStorageImages;\n    uint32_t              maxDescriptorSetInputAttachments;\n    uint32_t              maxVertexInputAttributes;\n    uint32_t              maxVertexInputBindings;\n    uint32_t              maxVertexInputAttributeOffset;\n    uint32_t              maxVertexInputBindingStride;\n    uint32_t              maxVertexOutputComponents;\n    uint32_t              maxTessellationGenerationLevel;\n    uint32_t              maxTessellationPatchSize;\n    uint32_t              maxTessellationControlPerVertexInputComponents;\n    uint32_t              maxTessellationControlPerVertexOutputComponents;\n    uint32_t              maxTessellationControlPerPatchOutputComponents;\n    uint32_t              maxTessellationControlTotalOutputComponents;\n    uint32_t              maxTessellationEvaluationInputComponents;\n    uint32_t              maxTessellationEvaluationOutputComponents;\n    uint32_t              maxGeometryShaderInvocations;\n    uint32_t              maxGeometryInputComponents;\n    uint32_t              maxGeometryOutputComponents;\n    uint32_t              maxGeometryOutputVertices;\n    uint32_t              maxGeometryTotalOutputComponents;\n    uint32_t              maxFragmentInputComponents;\n    uint32_t              maxFragmentOutputAttachments;\n    uint32_t              maxFragmentDualSrcAttachments;\n    uint32_t              maxFragmentCombinedOutputResources;\n    uint32_t              maxComputeSharedMemorySize;\n    uint32_t              maxComputeWorkGroupCount[3];\n    uint32_t              maxComputeWorkGroupInvocations;\n    uint32_t              maxComputeWorkGroupSize[3];\n    uint32_t              subPixelPrecisionBits;\n    uint32_t              subTexelPrecisionBits;\n    uint32_t              mipmapPrecisionBits;\n    uint32_t              maxDrawIndexedIndexValue;\n    uint32_t              maxDrawIndirectCount;\n    float                 maxSamplerLodBias;\n    float                 maxSamplerAnisotropy;\n    uint32_t              maxViewports;\n    uint32_t              maxViewportDimensions[2];\n    float                 viewportBoundsRange[2];\n    uint32_t              viewportSubPixelBits;\n    size_t                minMemoryMapAlignment;\n    VkDeviceSize          minTexelBufferOffsetAlignment;\n    VkDeviceSize          minUniformBufferOffsetAlignment;\n    VkDeviceSize          minStorageBufferOffsetAlignment;\n    int32_t               minTexelOffset;\n    uint32_t              maxTexelOffset;\n    int32_t               minTexelGatherOffset;\n    uint32_t              maxTexelGatherOffset;\n    float                 minInterpolationOffset;\n    float                 maxInterpolationOffset;\n    uint32_t              subPixelInterpolationOffsetBits;\n    uint32_t              maxFramebufferWidth;\n    uint32_t              maxFramebufferHeight;\n    uint32_t              maxFramebufferLayers;\n    VkSampleCountFlags    framebufferColorSampleCounts;\n    VkSampleCountFlags    framebufferDepthSampleCounts;\n    VkSampleCountFlags    framebufferStencilSampleCounts;\n    VkSampleCountFlags    framebufferNoAttachmentsSampleCounts;\n    uint32_t              maxColorAttachments;\n    VkSampleCountFlags    sampledImageColorSampleCounts;\n    VkSampleCountFlags    sampledImageIntegerSampleCounts;\n    VkSampleCountFlags    sampledImageDepthSampleCounts;\n    VkSampleCountFlags    sampledImageStencilSampleCounts;\n    VkSampleCountFlags    storageImageSampleCounts;\n    uint32_t              maxSampleMaskWords;\n    VkBool32              timestampComputeAndGraphics;\n    float                 timestampPeriod;\n    uint32_t              maxClipDistances;\n    uint32_t              maxCullDistances;\n    uint32_t              maxCombinedClipAndCullDistances;\n    uint32_t              discreteQueuePriorities;\n    float                 pointSizeRange[2];\n    float                 lineWidthRange[2];\n    float                 pointSizeGranularity;\n    float                 lineWidthGranularity;\n    VkBool32              strictLines;\n    VkBool32              standardSampleLocations;\n    VkDeviceSize          optimalBufferCopyOffsetAlignment;\n    VkDeviceSize          optimalBufferCopyRowPitchAlignment;\n    VkDeviceSize          nonCoherentAtomSize;\n} VkPhysicalDeviceLimits;\n\ntypedef struct VkPhysicalDeviceSparseProperties {\n    VkBool32    residencyStandard2DBlockShape;\n    VkBool32    residencyStandard2DMultisampleBlockShape;\n    VkBool32    residencyStandard3DBlockShape;\n    VkBool32    residencyAlignedMipSize;\n    VkBool32    residencyNonResidentStrict;\n} VkPhysicalDeviceSparseProperties;\n\ntypedef struct VkPhysicalDeviceProperties {\n    uint32_t                            apiVersion;\n    uint32_t                            driverVersion;\n    uint32_t                            vendorID;\n    uint32_t                            deviceID;\n    VkPhysicalDeviceType                deviceType;\n    char                                deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE];\n    uint8_t                             pipelineCacheUUID[VK_UUID_SIZE];\n    VkPhysicalDeviceLimits              limits;\n    VkPhysicalDeviceSparseProperties    sparseProperties;\n} VkPhysicalDeviceProperties;\n\ntypedef struct VkQueueFamilyProperties {\n    VkQueueFlags    queueFlags;\n    uint32_t        queueCount;\n    uint32_t        timestampValidBits;\n    VkExtent3D      minImageTransferGranularity;\n} VkQueueFamilyProperties;\n\ntypedef struct VkMemoryType {\n    VkMemoryPropertyFlags    propertyFlags;\n    uint32_t                 heapIndex;\n} VkMemoryType;\n\ntypedef struct VkMemoryHeap {\n    VkDeviceSize         size;\n    VkMemoryHeapFlags    flags;\n} VkMemoryHeap;\n\ntypedef struct VkPhysicalDeviceMemoryProperties {\n    uint32_t        memoryTypeCount;\n    VkMemoryType    memoryTypes[VK_MAX_MEMORY_TYPES];\n    uint32_t        memoryHeapCount;\n    VkMemoryHeap    memoryHeaps[VK_MAX_MEMORY_HEAPS];\n} VkPhysicalDeviceMemoryProperties;\n\ntypedef struct VkDeviceQueueCreateInfo {\n    VkStructureType             sType;\n    const void*                 pNext;\n    VkDeviceQueueCreateFlags    flags;\n    uint32_t                    queueFamilyIndex;\n    uint32_t                    queueCount;\n    const float*                pQueuePriorities;\n} VkDeviceQueueCreateInfo;\n\ntypedef struct VkDeviceCreateInfo {\n    VkStructureType                    sType;\n    const void*                        pNext;\n    VkDeviceCreateFlags                flags;\n    uint32_t                           queueCreateInfoCount;\n    const VkDeviceQueueCreateInfo*     pQueueCreateInfos;\n    uint32_t                           enabledLayerCount;\n    const char* const*                 ppEnabledLayerNames;\n    uint32_t                           enabledExtensionCount;\n    const char* const*                 ppEnabledExtensionNames;\n    const VkPhysicalDeviceFeatures*    pEnabledFeatures;\n} VkDeviceCreateInfo;\n\ntypedef struct VkExtensionProperties {\n    char        extensionName[VK_MAX_EXTENSION_NAME_SIZE];\n    uint32_t    specVersion;\n} VkExtensionProperties;\n\ntypedef struct VkLayerProperties {\n    char        layerName[VK_MAX_EXTENSION_NAME_SIZE];\n    uint32_t    specVersion;\n    uint32_t    implementationVersion;\n    char        description[VK_MAX_DESCRIPTION_SIZE];\n} VkLayerProperties;\n\ntypedef struct VkSubmitInfo {\n    VkStructureType                sType;\n    const void*                    pNext;\n    uint32_t                       waitSemaphoreCount;\n    const VkSemaphore*             pWaitSemaphores;\n    const VkPipelineStageFlags*    pWaitDstStageMask;\n    uint32_t                       commandBufferCount;\n    const VkCommandBuffer*         pCommandBuffers;\n    uint32_t                       signalSemaphoreCount;\n    const VkSemaphore*             pSignalSemaphores;\n} VkSubmitInfo;\n\ntypedef struct VkMemoryAllocateInfo {\n    VkStructureType    sType;\n    const void*        pNext;\n    VkDeviceSize       allocationSize;\n    uint32_t           memoryTypeIndex;\n} VkMemoryAllocateInfo;\n\ntypedef struct VkMappedMemoryRange {\n    VkStructureType    sType;\n    const void*        pNext;\n    VkDeviceMemory     memory;\n    VkDeviceSize       offset;\n    VkDeviceSize       size;\n} VkMappedMemoryRange;\n\ntypedef struct VkMemoryRequirements {\n    VkDeviceSize    size;\n    VkDeviceSize    alignment;\n    uint32_t        memoryTypeBits;\n} VkMemoryRequirements;\n\ntypedef struct VkSparseImageFormatProperties {\n    VkImageAspectFlags          aspectMask;\n    VkExtent3D                  imageGranularity;\n    VkSparseImageFormatFlags    flags;\n} VkSparseImageFormatProperties;\n\ntypedef struct VkSparseImageMemoryRequirements {\n    VkSparseImageFormatProperties    formatProperties;\n    uint32_t                         imageMipTailFirstLod;\n    VkDeviceSize                     imageMipTailSize;\n    VkDeviceSize                     imageMipTailOffset;\n    VkDeviceSize                     imageMipTailStride;\n} VkSparseImageMemoryRequirements;\n\ntypedef struct VkSparseMemoryBind {\n    VkDeviceSize               resourceOffset;\n    VkDeviceSize               size;\n    VkDeviceMemory             memory;\n    VkDeviceSize               memoryOffset;\n    VkSparseMemoryBindFlags    flags;\n} VkSparseMemoryBind;\n\ntypedef struct VkSparseBufferMemoryBindInfo {\n    VkBuffer                     buffer;\n    uint32_t                     bindCount;\n    const VkSparseMemoryBind*    pBinds;\n} VkSparseBufferMemoryBindInfo;\n\ntypedef struct VkSparseImageOpaqueMemoryBindInfo {\n    VkImage                      image;\n    uint32_t                     bindCount;\n    const VkSparseMemoryBind*    pBinds;\n} VkSparseImageOpaqueMemoryBindInfo;\n\ntypedef struct VkImageSubresource {\n    VkImageAspectFlags    aspectMask;\n    uint32_t              mipLevel;\n    uint32_t              arrayLayer;\n} VkImageSubresource;\n\ntypedef struct VkOffset3D {\n    int32_t    x;\n    int32_t    y;\n    int32_t    z;\n} VkOffset3D;\n\ntypedef struct VkSparseImageMemoryBind {\n    VkImageSubresource         subresource;\n    VkOffset3D                 offset;\n    VkExtent3D                 extent;\n    VkDeviceMemory             memory;\n    VkDeviceSize               memoryOffset;\n    VkSparseMemoryBindFlags    flags;\n} VkSparseImageMemoryBind;\n\ntypedef struct VkSparseImageMemoryBindInfo {\n    VkImage                           image;\n    uint32_t                          bindCount;\n    const VkSparseImageMemoryBind*    pBinds;\n} VkSparseImageMemoryBindInfo;\n\ntypedef struct VkBindSparseInfo {\n    VkStructureType                             sType;\n    const void*                                 pNext;\n    uint32_t                                    waitSemaphoreCount;\n    const VkSemaphore*                          pWaitSemaphores;\n    uint32_t                                    bufferBindCount;\n    const VkSparseBufferMemoryBindInfo*         pBufferBinds;\n    uint32_t                                    imageOpaqueBindCount;\n    const VkSparseImageOpaqueMemoryBindInfo*    pImageOpaqueBinds;\n    uint32_t                                    imageBindCount;\n    const VkSparseImageMemoryBindInfo*          pImageBinds;\n    uint32_t                                    signalSemaphoreCount;\n    const VkSemaphore*                          pSignalSemaphores;\n} VkBindSparseInfo;\n\ntypedef struct VkFenceCreateInfo {\n    VkStructureType       sType;\n    const void*           pNext;\n    VkFenceCreateFlags    flags;\n} VkFenceCreateInfo;\n\ntypedef struct VkSemaphoreCreateInfo {\n    VkStructureType           sType;\n    const void*               pNext;\n    VkSemaphoreCreateFlags    flags;\n} VkSemaphoreCreateInfo;\n\ntypedef struct VkEventCreateInfo {\n    VkStructureType       sType;\n    const void*           pNext;\n    VkEventCreateFlags    flags;\n} VkEventCreateInfo;\n\ntypedef struct VkQueryPoolCreateInfo {\n    VkStructureType                  sType;\n    const void*                      pNext;\n    VkQueryPoolCreateFlags           flags;\n    VkQueryType                      queryType;\n    uint32_t                         queryCount;\n    VkQueryPipelineStatisticFlags    pipelineStatistics;\n} VkQueryPoolCreateInfo;\n\ntypedef struct VkBufferCreateInfo {\n    VkStructureType        sType;\n    const void*            pNext;\n    VkBufferCreateFlags    flags;\n    VkDeviceSize           size;\n    VkBufferUsageFlags     usage;\n    VkSharingMode          sharingMode;\n    uint32_t               queueFamilyIndexCount;\n    const uint32_t*        pQueueFamilyIndices;\n} VkBufferCreateInfo;\n\ntypedef struct VkBufferViewCreateInfo {\n    VkStructureType            sType;\n    const void*                pNext;\n    VkBufferViewCreateFlags    flags;\n    VkBuffer                   buffer;\n    VkFormat                   format;\n    VkDeviceSize               offset;\n    VkDeviceSize               range;\n} VkBufferViewCreateInfo;\n\ntypedef struct VkImageCreateInfo {\n    VkStructureType          sType;\n    const void*              pNext;\n    VkImageCreateFlags       flags;\n    VkImageType              imageType;\n    VkFormat                 format;\n    VkExtent3D               extent;\n    uint32_t                 mipLevels;\n    uint32_t                 arrayLayers;\n    VkSampleCountFlagBits    samples;\n    VkImageTiling            tiling;\n    VkImageUsageFlags        usage;\n    VkSharingMode            sharingMode;\n    uint32_t                 queueFamilyIndexCount;\n    const uint32_t*          pQueueFamilyIndices;\n    VkImageLayout            initialLayout;\n} VkImageCreateInfo;\n\ntypedef struct VkSubresourceLayout {\n    VkDeviceSize    offset;\n    VkDeviceSize    size;\n    VkDeviceSize    rowPitch;\n    VkDeviceSize    arrayPitch;\n    VkDeviceSize    depthPitch;\n} VkSubresourceLayout;\n\ntypedef struct VkComponentMapping {\n    VkComponentSwizzle    r;\n    VkComponentSwizzle    g;\n    VkComponentSwizzle    b;\n    VkComponentSwizzle    a;\n} VkComponentMapping;\n\ntypedef struct VkImageSubresourceRange {\n    VkImageAspectFlags    aspectMask;\n    uint32_t              baseMipLevel;\n    uint32_t              levelCount;\n    uint32_t              baseArrayLayer;\n    uint32_t              layerCount;\n} VkImageSubresourceRange;\n\ntypedef struct VkImageViewCreateInfo {\n    VkStructureType            sType;\n    const void*                pNext;\n    VkImageViewCreateFlags     flags;\n    VkImage                    image;\n    VkImageViewType            viewType;\n    VkFormat                   format;\n    VkComponentMapping         components;\n    VkImageSubresourceRange    subresourceRange;\n} VkImageViewCreateInfo;\n\ntypedef struct VkShaderModuleCreateInfo {\n    VkStructureType              sType;\n    const void*                  pNext;\n    VkShaderModuleCreateFlags    flags;\n    size_t                       codeSize;\n    const uint32_t*              pCode;\n} VkShaderModuleCreateInfo;\n\ntypedef struct VkPipelineCacheCreateInfo {\n    VkStructureType               sType;\n    const void*                   pNext;\n    VkPipelineCacheCreateFlags    flags;\n    size_t                        initialDataSize;\n    const void*                   pInitialData;\n} VkPipelineCacheCreateInfo;\n\ntypedef struct VkSpecializationMapEntry {\n    uint32_t    constantID;\n    uint32_t    offset;\n    size_t      size;\n} VkSpecializationMapEntry;\n\ntypedef struct VkSpecializationInfo {\n    uint32_t                           mapEntryCount;\n    const VkSpecializationMapEntry*    pMapEntries;\n    size_t                             dataSize;\n    const void*                        pData;\n} VkSpecializationInfo;\n\ntypedef struct VkPipelineShaderStageCreateInfo {\n    VkStructureType                     sType;\n    const void*                         pNext;\n    VkPipelineShaderStageCreateFlags    flags;\n    VkShaderStageFlagBits               stage;\n    VkShaderModule                      module;\n    const char*                         pName;\n    const VkSpecializationInfo*         pSpecializationInfo;\n} VkPipelineShaderStageCreateInfo;\n\ntypedef struct VkVertexInputBindingDescription {\n    uint32_t             binding;\n    uint32_t             stride;\n    VkVertexInputRate    inputRate;\n} VkVertexInputBindingDescription;\n\ntypedef struct VkVertexInputAttributeDescription {\n    uint32_t    location;\n    uint32_t    binding;\n    VkFormat    format;\n    uint32_t    offset;\n} VkVertexInputAttributeDescription;\n\ntypedef struct VkPipelineVertexInputStateCreateInfo {\n    VkStructureType                             sType;\n    const void*                                 pNext;\n    VkPipelineVertexInputStateCreateFlags       flags;\n    uint32_t                                    vertexBindingDescriptionCount;\n    const VkVertexInputBindingDescription*      pVertexBindingDescriptions;\n    uint32_t                                    vertexAttributeDescriptionCount;\n    const VkVertexInputAttributeDescription*    pVertexAttributeDescriptions;\n} VkPipelineVertexInputStateCreateInfo;\n\ntypedef struct VkPipelineInputAssemblyStateCreateInfo {\n    VkStructureType                            sType;\n    const void*                                pNext;\n    VkPipelineInputAssemblyStateCreateFlags    flags;\n    VkPrimitiveTopology                        topology;\n    VkBool32                                   primitiveRestartEnable;\n} VkPipelineInputAssemblyStateCreateInfo;\n\ntypedef struct VkPipelineTessellationStateCreateInfo {\n    VkStructureType                           sType;\n    const void*                               pNext;\n    VkPipelineTessellationStateCreateFlags    flags;\n    uint32_t                                  patchControlPoints;\n} VkPipelineTessellationStateCreateInfo;\n\ntypedef struct VkViewport {\n    float    x;\n    float    y;\n    float    width;\n    float    height;\n    float    minDepth;\n    float    maxDepth;\n} VkViewport;\n\ntypedef struct VkOffset2D {\n    int32_t    x;\n    int32_t    y;\n} VkOffset2D;\n\ntypedef struct VkExtent2D {\n    uint32_t    width;\n    uint32_t    height;\n} VkExtent2D;\n\ntypedef struct VkRect2D {\n    VkOffset2D    offset;\n    VkExtent2D    extent;\n} VkRect2D;\n\ntypedef struct VkPipelineViewportStateCreateInfo {\n    VkStructureType                       sType;\n    const void*                           pNext;\n    VkPipelineViewportStateCreateFlags    flags;\n    uint32_t                              viewportCount;\n    const VkViewport*                     pViewports;\n    uint32_t                              scissorCount;\n    const VkRect2D*                       pScissors;\n} VkPipelineViewportStateCreateInfo;\n\ntypedef struct VkPipelineRasterizationStateCreateInfo {\n    VkStructureType                            sType;\n    const void*                                pNext;\n    VkPipelineRasterizationStateCreateFlags    flags;\n    VkBool32                                   depthClampEnable;\n    VkBool32                                   rasterizerDiscardEnable;\n    VkPolygonMode                              polygonMode;\n    VkCullModeFlags                            cullMode;\n    VkFrontFace                                frontFace;\n    VkBool32                                   depthBiasEnable;\n    float                                      depthBiasConstantFactor;\n    float                                      depthBiasClamp;\n    float                                      depthBiasSlopeFactor;\n    float                                      lineWidth;\n} VkPipelineRasterizationStateCreateInfo;\n\ntypedef struct VkPipelineMultisampleStateCreateInfo {\n    VkStructureType                          sType;\n    const void*                              pNext;\n    VkPipelineMultisampleStateCreateFlags    flags;\n    VkSampleCountFlagBits                    rasterizationSamples;\n    VkBool32                                 sampleShadingEnable;\n    float                                    minSampleShading;\n    const VkSampleMask*                      pSampleMask;\n    VkBool32                                 alphaToCoverageEnable;\n    VkBool32                                 alphaToOneEnable;\n} VkPipelineMultisampleStateCreateInfo;\n\ntypedef struct VkStencilOpState {\n    VkStencilOp    failOp;\n    VkStencilOp    passOp;\n    VkStencilOp    depthFailOp;\n    VkCompareOp    compareOp;\n    uint32_t       compareMask;\n    uint32_t       writeMask;\n    uint32_t       reference;\n} VkStencilOpState;\n\ntypedef struct VkPipelineDepthStencilStateCreateInfo {\n    VkStructureType                           sType;\n    const void*                               pNext;\n    VkPipelineDepthStencilStateCreateFlags    flags;\n    VkBool32                                  depthTestEnable;\n    VkBool32                                  depthWriteEnable;\n    VkCompareOp                               depthCompareOp;\n    VkBool32                                  depthBoundsTestEnable;\n    VkBool32                                  stencilTestEnable;\n    VkStencilOpState                          front;\n    VkStencilOpState                          back;\n    float                                     minDepthBounds;\n    float                                     maxDepthBounds;\n} VkPipelineDepthStencilStateCreateInfo;\n\ntypedef struct VkPipelineColorBlendAttachmentState {\n    VkBool32                 blendEnable;\n    VkBlendFactor            srcColorBlendFactor;\n    VkBlendFactor            dstColorBlendFactor;\n    VkBlendOp                colorBlendOp;\n    VkBlendFactor            srcAlphaBlendFactor;\n    VkBlendFactor            dstAlphaBlendFactor;\n    VkBlendOp                alphaBlendOp;\n    VkColorComponentFlags    colorWriteMask;\n} VkPipelineColorBlendAttachmentState;\n\ntypedef struct VkPipelineColorBlendStateCreateInfo {\n    VkStructureType                               sType;\n    const void*                                   pNext;\n    VkPipelineColorBlendStateCreateFlags          flags;\n    VkBool32                                      logicOpEnable;\n    VkLogicOp                                     logicOp;\n    uint32_t                                      attachmentCount;\n    const VkPipelineColorBlendAttachmentState*    pAttachments;\n    float                                         blendConstants[4];\n} VkPipelineColorBlendStateCreateInfo;\n\ntypedef struct VkPipelineDynamicStateCreateInfo {\n    VkStructureType                      sType;\n    const void*                          pNext;\n    VkPipelineDynamicStateCreateFlags    flags;\n    uint32_t                             dynamicStateCount;\n    const VkDynamicState*                pDynamicStates;\n} VkPipelineDynamicStateCreateInfo;\n\ntypedef struct VkGraphicsPipelineCreateInfo {\n    VkStructureType                                  sType;\n    const void*                                      pNext;\n    VkPipelineCreateFlags                            flags;\n    uint32_t                                         stageCount;\n    const VkPipelineShaderStageCreateInfo*           pStages;\n    const VkPipelineVertexInputStateCreateInfo*      pVertexInputState;\n    const VkPipelineInputAssemblyStateCreateInfo*    pInputAssemblyState;\n    const VkPipelineTessellationStateCreateInfo*     pTessellationState;\n    const VkPipelineViewportStateCreateInfo*         pViewportState;\n    const VkPipelineRasterizationStateCreateInfo*    pRasterizationState;\n    const VkPipelineMultisampleStateCreateInfo*      pMultisampleState;\n    const VkPipelineDepthStencilStateCreateInfo*     pDepthStencilState;\n    const VkPipelineColorBlendStateCreateInfo*       pColorBlendState;\n    const VkPipelineDynamicStateCreateInfo*          pDynamicState;\n    VkPipelineLayout                                 layout;\n    VkRenderPass                                     renderPass;\n    uint32_t                                         subpass;\n    VkPipeline                                       basePipelineHandle;\n    int32_t                                          basePipelineIndex;\n} VkGraphicsPipelineCreateInfo;\n\ntypedef struct VkComputePipelineCreateInfo {\n    VkStructureType                    sType;\n    const void*                        pNext;\n    VkPipelineCreateFlags              flags;\n    VkPipelineShaderStageCreateInfo    stage;\n    VkPipelineLayout                   layout;\n    VkPipeline                         basePipelineHandle;\n    int32_t                            basePipelineIndex;\n} VkComputePipelineCreateInfo;\n\ntypedef struct VkPushConstantRange {\n    VkShaderStageFlags    stageFlags;\n    uint32_t              offset;\n    uint32_t              size;\n} VkPushConstantRange;\n\ntypedef struct VkPipelineLayoutCreateInfo {\n    VkStructureType                 sType;\n    const void*                     pNext;\n    VkPipelineLayoutCreateFlags     flags;\n    uint32_t                        setLayoutCount;\n    const VkDescriptorSetLayout*    pSetLayouts;\n    uint32_t                        pushConstantRangeCount;\n    const VkPushConstantRange*      pPushConstantRanges;\n} VkPipelineLayoutCreateInfo;\n\ntypedef struct VkSamplerCreateInfo {\n    VkStructureType         sType;\n    const void*             pNext;\n    VkSamplerCreateFlags    flags;\n    VkFilter                magFilter;\n    VkFilter                minFilter;\n    VkSamplerMipmapMode     mipmapMode;\n    VkSamplerAddressMode    addressModeU;\n    VkSamplerAddressMode    addressModeV;\n    VkSamplerAddressMode    addressModeW;\n    float                   mipLodBias;\n    VkBool32                anisotropyEnable;\n    float                   maxAnisotropy;\n    VkBool32                compareEnable;\n    VkCompareOp             compareOp;\n    float                   minLod;\n    float                   maxLod;\n    VkBorderColor           borderColor;\n    VkBool32                unnormalizedCoordinates;\n} VkSamplerCreateInfo;\n\ntypedef struct VkDescriptorSetLayoutBinding {\n    uint32_t              binding;\n    VkDescriptorType      descriptorType;\n    uint32_t              descriptorCount;\n    VkShaderStageFlags    stageFlags;\n    const VkSampler*      pImmutableSamplers;\n} VkDescriptorSetLayoutBinding;\n\ntypedef struct VkDescriptorSetLayoutCreateInfo {\n    VkStructureType                        sType;\n    const void*                            pNext;\n    VkDescriptorSetLayoutCreateFlags       flags;\n    uint32_t                               bindingCount;\n    const VkDescriptorSetLayoutBinding*    pBindings;\n} VkDescriptorSetLayoutCreateInfo;\n\ntypedef struct VkDescriptorPoolSize {\n    VkDescriptorType    type;\n    uint32_t            descriptorCount;\n} VkDescriptorPoolSize;\n\ntypedef struct VkDescriptorPoolCreateInfo {\n    VkStructureType                sType;\n    const void*                    pNext;\n    VkDescriptorPoolCreateFlags    flags;\n    uint32_t                       maxSets;\n    uint32_t                       poolSizeCount;\n    const VkDescriptorPoolSize*    pPoolSizes;\n} VkDescriptorPoolCreateInfo;\n\ntypedef struct VkDescriptorSetAllocateInfo {\n    VkStructureType                 sType;\n    const void*                     pNext;\n    VkDescriptorPool                descriptorPool;\n    uint32_t                        descriptorSetCount;\n    const VkDescriptorSetLayout*    pSetLayouts;\n} VkDescriptorSetAllocateInfo;\n\ntypedef struct VkDescriptorImageInfo {\n    VkSampler        sampler;\n    VkImageView      imageView;\n    VkImageLayout    imageLayout;\n} VkDescriptorImageInfo;\n\ntypedef struct VkDescriptorBufferInfo {\n    VkBuffer        buffer;\n    VkDeviceSize    offset;\n    VkDeviceSize    range;\n} VkDescriptorBufferInfo;\n\ntypedef struct VkWriteDescriptorSet {\n    VkStructureType                  sType;\n    const void*                      pNext;\n    VkDescriptorSet                  dstSet;\n    uint32_t                         dstBinding;\n    uint32_t                         dstArrayElement;\n    uint32_t                         descriptorCount;\n    VkDescriptorType                 descriptorType;\n    const VkDescriptorImageInfo*     pImageInfo;\n    const VkDescriptorBufferInfo*    pBufferInfo;\n    const VkBufferView*              pTexelBufferView;\n} VkWriteDescriptorSet;\n\ntypedef struct VkCopyDescriptorSet {\n    VkStructureType    sType;\n    const void*        pNext;\n    VkDescriptorSet    srcSet;\n    uint32_t           srcBinding;\n    uint32_t           srcArrayElement;\n    VkDescriptorSet    dstSet;\n    uint32_t           dstBinding;\n    uint32_t           dstArrayElement;\n    uint32_t           descriptorCount;\n} VkCopyDescriptorSet;\n\ntypedef struct VkFramebufferCreateInfo {\n    VkStructureType             sType;\n    const void*                 pNext;\n    VkFramebufferCreateFlags    flags;\n    VkRenderPass                renderPass;\n    uint32_t                    attachmentCount;\n    const VkImageView*          pAttachments;\n    uint32_t                    width;\n    uint32_t                    height;\n    uint32_t                    layers;\n} VkFramebufferCreateInfo;\n\ntypedef struct VkAttachmentDescription {\n    VkAttachmentDescriptionFlags    flags;\n    VkFormat                        format;\n    VkSampleCountFlagBits           samples;\n    VkAttachmentLoadOp              loadOp;\n    VkAttachmentStoreOp             storeOp;\n    VkAttachmentLoadOp              stencilLoadOp;\n    VkAttachmentStoreOp             stencilStoreOp;\n    VkImageLayout                   initialLayout;\n    VkImageLayout                   finalLayout;\n} VkAttachmentDescription;\n\ntypedef struct VkAttachmentReference {\n    uint32_t         attachment;\n    VkImageLayout    layout;\n} VkAttachmentReference;\n\ntypedef struct VkSubpassDescription {\n    VkSubpassDescriptionFlags       flags;\n    VkPipelineBindPoint             pipelineBindPoint;\n    uint32_t                        inputAttachmentCount;\n    const VkAttachmentReference*    pInputAttachments;\n    uint32_t                        colorAttachmentCount;\n    const VkAttachmentReference*    pColorAttachments;\n    const VkAttachmentReference*    pResolveAttachments;\n    const VkAttachmentReference*    pDepthStencilAttachment;\n    uint32_t                        preserveAttachmentCount;\n    const uint32_t*                 pPreserveAttachments;\n} VkSubpassDescription;\n\ntypedef struct VkSubpassDependency {\n    uint32_t                srcSubpass;\n    uint32_t                dstSubpass;\n    VkPipelineStageFlags    srcStageMask;\n    VkPipelineStageFlags    dstStageMask;\n    VkAccessFlags           srcAccessMask;\n    VkAccessFlags           dstAccessMask;\n    VkDependencyFlags       dependencyFlags;\n} VkSubpassDependency;\n\ntypedef struct VkRenderPassCreateInfo {\n    VkStructureType                   sType;\n    const void*                       pNext;\n    VkRenderPassCreateFlags           flags;\n    uint32_t                          attachmentCount;\n    const VkAttachmentDescription*    pAttachments;\n    uint32_t                          subpassCount;\n    const VkSubpassDescription*       pSubpasses;\n    uint32_t                          dependencyCount;\n    const VkSubpassDependency*        pDependencies;\n} VkRenderPassCreateInfo;\n\ntypedef struct VkCommandPoolCreateInfo {\n    VkStructureType             sType;\n    const void*                 pNext;\n    VkCommandPoolCreateFlags    flags;\n    uint32_t                    queueFamilyIndex;\n} VkCommandPoolCreateInfo;\n\ntypedef struct VkCommandBufferAllocateInfo {\n    VkStructureType         sType;\n    const void*             pNext;\n    VkCommandPool           commandPool;\n    VkCommandBufferLevel    level;\n    uint32_t                commandBufferCount;\n} VkCommandBufferAllocateInfo;\n\ntypedef struct VkCommandBufferInheritanceInfo {\n    VkStructureType                  sType;\n    const void*                      pNext;\n    VkRenderPass                     renderPass;\n    uint32_t                         subpass;\n    VkFramebuffer                    framebuffer;\n    VkBool32                         occlusionQueryEnable;\n    VkQueryControlFlags              queryFlags;\n    VkQueryPipelineStatisticFlags    pipelineStatistics;\n} VkCommandBufferInheritanceInfo;\n\ntypedef struct VkCommandBufferBeginInfo {\n    VkStructureType                          sType;\n    const void*                              pNext;\n    VkCommandBufferUsageFlags                flags;\n    const VkCommandBufferInheritanceInfo*    pInheritanceInfo;\n} VkCommandBufferBeginInfo;\n\ntypedef struct VkBufferCopy {\n    VkDeviceSize    srcOffset;\n    VkDeviceSize    dstOffset;\n    VkDeviceSize    size;\n} VkBufferCopy;\n\ntypedef struct VkImageSubresourceLayers {\n    VkImageAspectFlags    aspectMask;\n    uint32_t              mipLevel;\n    uint32_t              baseArrayLayer;\n    uint32_t              layerCount;\n} VkImageSubresourceLayers;\n\ntypedef struct VkImageCopy {\n    VkImageSubresourceLayers    srcSubresource;\n    VkOffset3D                  srcOffset;\n    VkImageSubresourceLayers    dstSubresource;\n    VkOffset3D                  dstOffset;\n    VkExtent3D                  extent;\n} VkImageCopy;\n\ntypedef struct VkImageBlit {\n    VkImageSubresourceLayers    srcSubresource;\n    VkOffset3D                  srcOffsets[2];\n    VkImageSubresourceLayers    dstSubresource;\n    VkOffset3D                  dstOffsets[2];\n} VkImageBlit;\n\ntypedef struct VkBufferImageCopy {\n    VkDeviceSize                bufferOffset;\n    uint32_t                    bufferRowLength;\n    uint32_t                    bufferImageHeight;\n    VkImageSubresourceLayers    imageSubresource;\n    VkOffset3D                  imageOffset;\n    VkExtent3D                  imageExtent;\n} VkBufferImageCopy;\n\ntypedef union VkClearColorValue {\n    float       float32[4];\n    int32_t     int32[4];\n    uint32_t    uint32[4];\n} VkClearColorValue;\n\ntypedef struct VkClearDepthStencilValue {\n    float       depth;\n    uint32_t    stencil;\n} VkClearDepthStencilValue;\n\ntypedef union VkClearValue {\n    VkClearColorValue           color;\n    VkClearDepthStencilValue    depthStencil;\n} VkClearValue;\n\ntypedef struct VkClearAttachment {\n    VkImageAspectFlags    aspectMask;\n    uint32_t              colorAttachment;\n    VkClearValue          clearValue;\n} VkClearAttachment;\n\ntypedef struct VkClearRect {\n    VkRect2D    rect;\n    uint32_t    baseArrayLayer;\n    uint32_t    layerCount;\n} VkClearRect;\n\ntypedef struct VkImageResolve {\n    VkImageSubresourceLayers    srcSubresource;\n    VkOffset3D                  srcOffset;\n    VkImageSubresourceLayers    dstSubresource;\n    VkOffset3D                  dstOffset;\n    VkExtent3D                  extent;\n} VkImageResolve;\n\ntypedef struct VkMemoryBarrier {\n    VkStructureType    sType;\n    const void*        pNext;\n    VkAccessFlags      srcAccessMask;\n    VkAccessFlags      dstAccessMask;\n} VkMemoryBarrier;\n\ntypedef struct VkBufferMemoryBarrier {\n    VkStructureType    sType;\n    const void*        pNext;\n    VkAccessFlags      srcAccessMask;\n    VkAccessFlags      dstAccessMask;\n    uint32_t           srcQueueFamilyIndex;\n    uint32_t           dstQueueFamilyIndex;\n    VkBuffer           buffer;\n    VkDeviceSize       offset;\n    VkDeviceSize       size;\n} VkBufferMemoryBarrier;\n\ntypedef struct VkImageMemoryBarrier {\n    VkStructureType            sType;\n    const void*                pNext;\n    VkAccessFlags              srcAccessMask;\n    VkAccessFlags              dstAccessMask;\n    VkImageLayout              oldLayout;\n    VkImageLayout              newLayout;\n    uint32_t                   srcQueueFamilyIndex;\n    uint32_t                   dstQueueFamilyIndex;\n    VkImage                    image;\n    VkImageSubresourceRange    subresourceRange;\n} VkImageMemoryBarrier;\n\ntypedef struct VkRenderPassBeginInfo {\n    VkStructureType        sType;\n    const void*            pNext;\n    VkRenderPass           renderPass;\n    VkFramebuffer          framebuffer;\n    VkRect2D               renderArea;\n    uint32_t               clearValueCount;\n    const VkClearValue*    pClearValues;\n} VkRenderPassBeginInfo;\n\ntypedef struct VkDispatchIndirectCommand {\n    uint32_t    x;\n    uint32_t    y;\n    uint32_t    z;\n} VkDispatchIndirectCommand;\n\ntypedef struct VkDrawIndexedIndirectCommand {\n    uint32_t    indexCount;\n    uint32_t    instanceCount;\n    uint32_t    firstIndex;\n    int32_t     vertexOffset;\n    uint32_t    firstInstance;\n} VkDrawIndexedIndirectCommand;\n\ntypedef struct VkDrawIndirectCommand {\n    uint32_t    vertexCount;\n    uint32_t    instanceCount;\n    uint32_t    firstVertex;\n    uint32_t    firstInstance;\n} VkDrawIndirectCommand;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateInstance)(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance);\ntypedef void (VKAPI_PTR *PFN_vkDestroyInstance)(VkInstance instance, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkEnumeratePhysicalDevices)(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices);\ntypedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFeatures)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures* pFeatures);\ntypedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFormatProperties)(VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties* pFormatProperties);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceImageFormatProperties)(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkImageTiling tiling, VkImageUsageFlags usage, VkImageCreateFlags flags, VkImageFormatProperties* pImageFormatProperties);\ntypedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceProperties)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties);\ntypedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceQueueFamilyProperties)(VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties* pQueueFamilyProperties);\ntypedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceMemoryProperties)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties);\ntypedef PFN_vkVoidFunction (VKAPI_PTR *PFN_vkGetInstanceProcAddr)(VkInstance instance, const char* pName);\ntypedef PFN_vkVoidFunction (VKAPI_PTR *PFN_vkGetDeviceProcAddr)(VkDevice device, const char* pName);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateDevice)(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice);\ntypedef void (VKAPI_PTR *PFN_vkDestroyDevice)(VkDevice device, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkEnumerateInstanceExtensionProperties)(const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties);\ntypedef VkResult (VKAPI_PTR *PFN_vkEnumerateDeviceExtensionProperties)(VkPhysicalDevice physicalDevice, const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties);\ntypedef VkResult (VKAPI_PTR *PFN_vkEnumerateInstanceLayerProperties)(uint32_t* pPropertyCount, VkLayerProperties* pProperties);\ntypedef VkResult (VKAPI_PTR *PFN_vkEnumerateDeviceLayerProperties)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkLayerProperties* pProperties);\ntypedef void (VKAPI_PTR *PFN_vkGetDeviceQueue)(VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue* pQueue);\ntypedef VkResult (VKAPI_PTR *PFN_vkQueueSubmit)(VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence);\ntypedef VkResult (VKAPI_PTR *PFN_vkQueueWaitIdle)(VkQueue queue);\ntypedef VkResult (VKAPI_PTR *PFN_vkDeviceWaitIdle)(VkDevice device);\ntypedef VkResult (VKAPI_PTR *PFN_vkAllocateMemory)(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory);\ntypedef void (VKAPI_PTR *PFN_vkFreeMemory)(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkMapMemory)(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData);\ntypedef void (VKAPI_PTR *PFN_vkUnmapMemory)(VkDevice device, VkDeviceMemory memory);\ntypedef VkResult (VKAPI_PTR *PFN_vkFlushMappedMemoryRanges)(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges);\ntypedef VkResult (VKAPI_PTR *PFN_vkInvalidateMappedMemoryRanges)(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges);\ntypedef void (VKAPI_PTR *PFN_vkGetDeviceMemoryCommitment)(VkDevice device, VkDeviceMemory memory, VkDeviceSize* pCommittedMemoryInBytes);\ntypedef VkResult (VKAPI_PTR *PFN_vkBindBufferMemory)(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset);\ntypedef VkResult (VKAPI_PTR *PFN_vkBindImageMemory)(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset);\ntypedef void (VKAPI_PTR *PFN_vkGetBufferMemoryRequirements)(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements);\ntypedef void (VKAPI_PTR *PFN_vkGetImageMemoryRequirements)(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements);\ntypedef void (VKAPI_PTR *PFN_vkGetImageSparseMemoryRequirements)(VkDevice device, VkImage image, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements* pSparseMemoryRequirements);\ntypedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceSparseImageFormatProperties)(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkSampleCountFlagBits samples, VkImageUsageFlags usage, VkImageTiling tiling, uint32_t* pPropertyCount, VkSparseImageFormatProperties* pProperties);\ntypedef VkResult (VKAPI_PTR *PFN_vkQueueBindSparse)(VkQueue queue, uint32_t bindInfoCount, const VkBindSparseInfo* pBindInfo, VkFence fence);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateFence)(VkDevice device, const VkFenceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkFence* pFence);\ntypedef void (VKAPI_PTR *PFN_vkDestroyFence)(VkDevice device, VkFence fence, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkResetFences)(VkDevice device, uint32_t fenceCount, const VkFence* pFences);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetFenceStatus)(VkDevice device, VkFence fence);\ntypedef VkResult (VKAPI_PTR *PFN_vkWaitForFences)(VkDevice device, uint32_t fenceCount, const VkFence* pFences, VkBool32 waitAll, uint64_t timeout);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateSemaphore)(VkDevice device, const VkSemaphoreCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSemaphore* pSemaphore);\ntypedef void (VKAPI_PTR *PFN_vkDestroySemaphore)(VkDevice device, VkSemaphore semaphore, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateEvent)(VkDevice device, const VkEventCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkEvent* pEvent);\ntypedef void (VKAPI_PTR *PFN_vkDestroyEvent)(VkDevice device, VkEvent event, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetEventStatus)(VkDevice device, VkEvent event);\ntypedef VkResult (VKAPI_PTR *PFN_vkSetEvent)(VkDevice device, VkEvent event);\ntypedef VkResult (VKAPI_PTR *PFN_vkResetEvent)(VkDevice device, VkEvent event);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateQueryPool)(VkDevice device, const VkQueryPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkQueryPool* pQueryPool);\ntypedef void (VKAPI_PTR *PFN_vkDestroyQueryPool)(VkDevice device, VkQueryPool queryPool, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetQueryPoolResults)(VkDevice device, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, size_t dataSize, void* pData, VkDeviceSize stride, VkQueryResultFlags flags);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateBuffer)(VkDevice device, const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer);\ntypedef void (VKAPI_PTR *PFN_vkDestroyBuffer)(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateBufferView)(VkDevice device, const VkBufferViewCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBufferView* pView);\ntypedef void (VKAPI_PTR *PFN_vkDestroyBufferView)(VkDevice device, VkBufferView bufferView, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateImage)(VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage);\ntypedef void (VKAPI_PTR *PFN_vkDestroyImage)(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator);\ntypedef void (VKAPI_PTR *PFN_vkGetImageSubresourceLayout)(VkDevice device, VkImage image, const VkImageSubresource* pSubresource, VkSubresourceLayout* pLayout);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateImageView)(VkDevice device, const VkImageViewCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImageView* pView);\ntypedef void (VKAPI_PTR *PFN_vkDestroyImageView)(VkDevice device, VkImageView imageView, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateShaderModule)(VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkShaderModule* pShaderModule);\ntypedef void (VKAPI_PTR *PFN_vkDestroyShaderModule)(VkDevice device, VkShaderModule shaderModule, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreatePipelineCache)(VkDevice device, const VkPipelineCacheCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPipelineCache* pPipelineCache);\ntypedef void (VKAPI_PTR *PFN_vkDestroyPipelineCache)(VkDevice device, VkPipelineCache pipelineCache, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetPipelineCacheData)(VkDevice device, VkPipelineCache pipelineCache, size_t* pDataSize, void* pData);\ntypedef VkResult (VKAPI_PTR *PFN_vkMergePipelineCaches)(VkDevice device, VkPipelineCache dstCache, uint32_t srcCacheCount, const VkPipelineCache* pSrcCaches);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateGraphicsPipelines)(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkGraphicsPipelineCreateInfo* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateComputePipelines)(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkComputePipelineCreateInfo* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines);\ntypedef void (VKAPI_PTR *PFN_vkDestroyPipeline)(VkDevice device, VkPipeline pipeline, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreatePipelineLayout)(VkDevice device, const VkPipelineLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPipelineLayout* pPipelineLayout);\ntypedef void (VKAPI_PTR *PFN_vkDestroyPipelineLayout)(VkDevice device, VkPipelineLayout pipelineLayout, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateSampler)(VkDevice device, const VkSamplerCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSampler* pSampler);\ntypedef void (VKAPI_PTR *PFN_vkDestroySampler)(VkDevice device, VkSampler sampler, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateDescriptorSetLayout)(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorSetLayout* pSetLayout);\ntypedef void (VKAPI_PTR *PFN_vkDestroyDescriptorSetLayout)(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateDescriptorPool)(VkDevice device, const VkDescriptorPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorPool* pDescriptorPool);\ntypedef void (VKAPI_PTR *PFN_vkDestroyDescriptorPool)(VkDevice device, VkDescriptorPool descriptorPool, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkResetDescriptorPool)(VkDevice device, VkDescriptorPool descriptorPool, VkDescriptorPoolResetFlags flags);\ntypedef VkResult (VKAPI_PTR *PFN_vkAllocateDescriptorSets)(VkDevice device, const VkDescriptorSetAllocateInfo* pAllocateInfo, VkDescriptorSet* pDescriptorSets);\ntypedef VkResult (VKAPI_PTR *PFN_vkFreeDescriptorSets)(VkDevice device, VkDescriptorPool descriptorPool, uint32_t descriptorSetCount, const VkDescriptorSet* pDescriptorSets);\ntypedef void (VKAPI_PTR *PFN_vkUpdateDescriptorSets)(VkDevice device, uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites, uint32_t descriptorCopyCount, const VkCopyDescriptorSet* pDescriptorCopies);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateFramebuffer)(VkDevice device, const VkFramebufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkFramebuffer* pFramebuffer);\ntypedef void (VKAPI_PTR *PFN_vkDestroyFramebuffer)(VkDevice device, VkFramebuffer framebuffer, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateRenderPass)(VkDevice device, const VkRenderPassCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass);\ntypedef void (VKAPI_PTR *PFN_vkDestroyRenderPass)(VkDevice device, VkRenderPass renderPass, const VkAllocationCallbacks* pAllocator);\ntypedef void (VKAPI_PTR *PFN_vkGetRenderAreaGranularity)(VkDevice device, VkRenderPass renderPass, VkExtent2D* pGranularity);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateCommandPool)(VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool);\ntypedef void (VKAPI_PTR *PFN_vkDestroyCommandPool)(VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkResetCommandPool)(VkDevice device, VkCommandPool commandPool, VkCommandPoolResetFlags flags);\ntypedef VkResult (VKAPI_PTR *PFN_vkAllocateCommandBuffers)(VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers);\ntypedef void (VKAPI_PTR *PFN_vkFreeCommandBuffers)(VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers);\ntypedef VkResult (VKAPI_PTR *PFN_vkBeginCommandBuffer)(VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo);\ntypedef VkResult (VKAPI_PTR *PFN_vkEndCommandBuffer)(VkCommandBuffer commandBuffer);\ntypedef VkResult (VKAPI_PTR *PFN_vkResetCommandBuffer)(VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags);\ntypedef void (VKAPI_PTR *PFN_vkCmdBindPipeline)(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipeline pipeline);\ntypedef void (VKAPI_PTR *PFN_vkCmdSetViewport)(VkCommandBuffer commandBuffer, uint32_t firstViewport, uint32_t viewportCount, const VkViewport* pViewports);\ntypedef void (VKAPI_PTR *PFN_vkCmdSetScissor)(VkCommandBuffer commandBuffer, uint32_t firstScissor, uint32_t scissorCount, const VkRect2D* pScissors);\ntypedef void (VKAPI_PTR *PFN_vkCmdSetLineWidth)(VkCommandBuffer commandBuffer, float lineWidth);\ntypedef void (VKAPI_PTR *PFN_vkCmdSetDepthBias)(VkCommandBuffer commandBuffer, float depthBiasConstantFactor, float depthBiasClamp, float depthBiasSlopeFactor);\ntypedef void (VKAPI_PTR *PFN_vkCmdSetBlendConstants)(VkCommandBuffer commandBuffer, const float blendConstants[4]);\ntypedef void (VKAPI_PTR *PFN_vkCmdSetDepthBounds)(VkCommandBuffer commandBuffer, float minDepthBounds, float maxDepthBounds);\ntypedef void (VKAPI_PTR *PFN_vkCmdSetStencilCompareMask)(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, uint32_t compareMask);\ntypedef void (VKAPI_PTR *PFN_vkCmdSetStencilWriteMask)(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, uint32_t writeMask);\ntypedef void (VKAPI_PTR *PFN_vkCmdSetStencilReference)(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, uint32_t reference);\ntypedef void (VKAPI_PTR *PFN_vkCmdBindDescriptorSets)(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t firstSet, uint32_t descriptorSetCount, const VkDescriptorSet* pDescriptorSets, uint32_t dynamicOffsetCount, const uint32_t* pDynamicOffsets);\ntypedef void (VKAPI_PTR *PFN_vkCmdBindIndexBuffer)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkIndexType indexType);\ntypedef void (VKAPI_PTR *PFN_vkCmdBindVertexBuffers)(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets);\ntypedef void (VKAPI_PTR *PFN_vkCmdDraw)(VkCommandBuffer commandBuffer, uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance);\ntypedef void (VKAPI_PTR *PFN_vkCmdDrawIndexed)(VkCommandBuffer commandBuffer, uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex, int32_t vertexOffset, uint32_t firstInstance);\ntypedef void (VKAPI_PTR *PFN_vkCmdDrawIndirect)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, uint32_t drawCount, uint32_t stride);\ntypedef void (VKAPI_PTR *PFN_vkCmdDrawIndexedIndirect)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, uint32_t drawCount, uint32_t stride);\ntypedef void (VKAPI_PTR *PFN_vkCmdDispatch)(VkCommandBuffer commandBuffer, uint32_t x, uint32_t y, uint32_t z);\ntypedef void (VKAPI_PTR *PFN_vkCmdDispatchIndirect)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset);\ntypedef void (VKAPI_PTR *PFN_vkCmdCopyBuffer)(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, uint32_t regionCount, const VkBufferCopy* pRegions);\ntypedef void (VKAPI_PTR *PFN_vkCmdCopyImage)(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageCopy* pRegions);\ntypedef void (VKAPI_PTR *PFN_vkCmdBlitImage)(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageBlit* pRegions, VkFilter filter);\ntypedef void (VKAPI_PTR *PFN_vkCmdCopyBufferToImage)(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkBufferImageCopy* pRegions);\ntypedef void (VKAPI_PTR *PFN_vkCmdCopyImageToBuffer)(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkBuffer dstBuffer, uint32_t regionCount, const VkBufferImageCopy* pRegions);\ntypedef void (VKAPI_PTR *PFN_vkCmdUpdateBuffer)(VkCommandBuffer commandBuffer, VkBuffer dstBuffer, VkDeviceSize dstOffset, VkDeviceSize dataSize, const void* pData);\ntypedef void (VKAPI_PTR *PFN_vkCmdFillBuffer)(VkCommandBuffer commandBuffer, VkBuffer dstBuffer, VkDeviceSize dstOffset, VkDeviceSize size, uint32_t data);\ntypedef void (VKAPI_PTR *PFN_vkCmdClearColorImage)(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout imageLayout, const VkClearColorValue* pColor, uint32_t rangeCount, const VkImageSubresourceRange* pRanges);\ntypedef void (VKAPI_PTR *PFN_vkCmdClearDepthStencilImage)(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout imageLayout, const VkClearDepthStencilValue* pDepthStencil, uint32_t rangeCount, const VkImageSubresourceRange* pRanges);\ntypedef void (VKAPI_PTR *PFN_vkCmdClearAttachments)(VkCommandBuffer commandBuffer, uint32_t attachmentCount, const VkClearAttachment* pAttachments, uint32_t rectCount, const VkClearRect* pRects);\ntypedef void (VKAPI_PTR *PFN_vkCmdResolveImage)(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageResolve* pRegions);\ntypedef void (VKAPI_PTR *PFN_vkCmdSetEvent)(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask);\ntypedef void (VKAPI_PTR *PFN_vkCmdResetEvent)(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask);\ntypedef void (VKAPI_PTR *PFN_vkCmdWaitEvents)(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers);\ntypedef void (VKAPI_PTR *PFN_vkCmdPipelineBarrier)(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers);\ntypedef void (VKAPI_PTR *PFN_vkCmdBeginQuery)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t query, VkQueryControlFlags flags);\ntypedef void (VKAPI_PTR *PFN_vkCmdEndQuery)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t query);\ntypedef void (VKAPI_PTR *PFN_vkCmdResetQueryPool)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount);\ntypedef void (VKAPI_PTR *PFN_vkCmdWriteTimestamp)(VkCommandBuffer commandBuffer, VkPipelineStageFlagBits pipelineStage, VkQueryPool queryPool, uint32_t query);\ntypedef void (VKAPI_PTR *PFN_vkCmdCopyQueryPoolResults)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, VkBuffer dstBuffer, VkDeviceSize dstOffset, VkDeviceSize stride, VkQueryResultFlags flags);\ntypedef void (VKAPI_PTR *PFN_vkCmdPushConstants)(VkCommandBuffer commandBuffer, VkPipelineLayout layout, VkShaderStageFlags stageFlags, uint32_t offset, uint32_t size, const void* pValues);\ntypedef void (VKAPI_PTR *PFN_vkCmdBeginRenderPass)(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin, VkSubpassContents contents);\ntypedef void (VKAPI_PTR *PFN_vkCmdNextSubpass)(VkCommandBuffer commandBuffer, VkSubpassContents contents);\ntypedef void (VKAPI_PTR *PFN_vkCmdEndRenderPass)(VkCommandBuffer commandBuffer);\ntypedef void (VKAPI_PTR *PFN_vkCmdExecuteCommands)(VkCommandBuffer commandBuffer, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(\n    const VkInstanceCreateInfo*                 pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkInstance*                                 pInstance);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyInstance(\n    VkInstance                                  instance,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices(\n    VkInstance                                  instance,\n    uint32_t*                                   pPhysicalDeviceCount,\n    VkPhysicalDevice*                           pPhysicalDevices);\n\nVKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFeatures(\n    VkPhysicalDevice                            physicalDevice,\n    VkPhysicalDeviceFeatures*                   pFeatures);\n\nVKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFormatProperties(\n    VkPhysicalDevice                            physicalDevice,\n    VkFormat                                    format,\n    VkFormatProperties*                         pFormatProperties);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceImageFormatProperties(\n    VkPhysicalDevice                            physicalDevice,\n    VkFormat                                    format,\n    VkImageType                                 type,\n    VkImageTiling                               tiling,\n    VkImageUsageFlags                           usage,\n    VkImageCreateFlags                          flags,\n    VkImageFormatProperties*                    pImageFormatProperties);\n\nVKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceProperties(\n    VkPhysicalDevice                            physicalDevice,\n    VkPhysicalDeviceProperties*                 pProperties);\n\nVKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceQueueFamilyProperties(\n    VkPhysicalDevice                            physicalDevice,\n    uint32_t*                                   pQueueFamilyPropertyCount,\n    VkQueueFamilyProperties*                    pQueueFamilyProperties);\n\nVKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceMemoryProperties(\n    VkPhysicalDevice                            physicalDevice,\n    VkPhysicalDeviceMemoryProperties*           pMemoryProperties);\n\nVKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(\n    VkInstance                                  instance,\n    const char*                                 pName);\n\nVKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(\n    VkDevice                                    device,\n    const char*                                 pName);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(\n    VkPhysicalDevice                            physicalDevice,\n    const VkDeviceCreateInfo*                   pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkDevice*                                   pDevice);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyDevice(\n    VkDevice                                    device,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(\n    const char*                                 pLayerName,\n    uint32_t*                                   pPropertyCount,\n    VkExtensionProperties*                      pProperties);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(\n    VkPhysicalDevice                            physicalDevice,\n    const char*                                 pLayerName,\n    uint32_t*                                   pPropertyCount,\n    VkExtensionProperties*                      pProperties);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties(\n    uint32_t*                                   pPropertyCount,\n    VkLayerProperties*                          pProperties);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties(\n    VkPhysicalDevice                            physicalDevice,\n    uint32_t*                                   pPropertyCount,\n    VkLayerProperties*                          pProperties);\n\nVKAPI_ATTR void VKAPI_CALL vkGetDeviceQueue(\n    VkDevice                                    device,\n    uint32_t                                    queueFamilyIndex,\n    uint32_t                                    queueIndex,\n    VkQueue*                                    pQueue);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit(\n    VkQueue                                     queue,\n    uint32_t                                    submitCount,\n    const VkSubmitInfo*                         pSubmits,\n    VkFence                                     fence);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkQueueWaitIdle(\n    VkQueue                                     queue);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkDeviceWaitIdle(\n    VkDevice                                    device);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkAllocateMemory(\n    VkDevice                                    device,\n    const VkMemoryAllocateInfo*                 pAllocateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkDeviceMemory*                             pMemory);\n\nVKAPI_ATTR void VKAPI_CALL vkFreeMemory(\n    VkDevice                                    device,\n    VkDeviceMemory                              memory,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkMapMemory(\n    VkDevice                                    device,\n    VkDeviceMemory                              memory,\n    VkDeviceSize                                offset,\n    VkDeviceSize                                size,\n    VkMemoryMapFlags                            flags,\n    void**                                      ppData);\n\nVKAPI_ATTR void VKAPI_CALL vkUnmapMemory(\n    VkDevice                                    device,\n    VkDeviceMemory                              memory);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkFlushMappedMemoryRanges(\n    VkDevice                                    device,\n    uint32_t                                    memoryRangeCount,\n    const VkMappedMemoryRange*                  pMemoryRanges);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkInvalidateMappedMemoryRanges(\n    VkDevice                                    device,\n    uint32_t                                    memoryRangeCount,\n    const VkMappedMemoryRange*                  pMemoryRanges);\n\nVKAPI_ATTR void VKAPI_CALL vkGetDeviceMemoryCommitment(\n    VkDevice                                    device,\n    VkDeviceMemory                              memory,\n    VkDeviceSize*                               pCommittedMemoryInBytes);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(\n    VkDevice                                    device,\n    VkBuffer                                    buffer,\n    VkDeviceMemory                              memory,\n    VkDeviceSize                                memoryOffset);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory(\n    VkDevice                                    device,\n    VkImage                                     image,\n    VkDeviceMemory                              memory,\n    VkDeviceSize                                memoryOffset);\n\nVKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements(\n    VkDevice                                    device,\n    VkBuffer                                    buffer,\n    VkMemoryRequirements*                       pMemoryRequirements);\n\nVKAPI_ATTR void VKAPI_CALL vkGetImageMemoryRequirements(\n    VkDevice                                    device,\n    VkImage                                     image,\n    VkMemoryRequirements*                       pMemoryRequirements);\n\nVKAPI_ATTR void VKAPI_CALL vkGetImageSparseMemoryRequirements(\n    VkDevice                                    device,\n    VkImage                                     image,\n    uint32_t*                                   pSparseMemoryRequirementCount,\n    VkSparseImageMemoryRequirements*            pSparseMemoryRequirements);\n\nVKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceSparseImageFormatProperties(\n    VkPhysicalDevice                            physicalDevice,\n    VkFormat                                    format,\n    VkImageType                                 type,\n    VkSampleCountFlagBits                       samples,\n    VkImageUsageFlags                           usage,\n    VkImageTiling                               tiling,\n    uint32_t*                                   pPropertyCount,\n    VkSparseImageFormatProperties*              pProperties);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkQueueBindSparse(\n    VkQueue                                     queue,\n    uint32_t                                    bindInfoCount,\n    const VkBindSparseInfo*                     pBindInfo,\n    VkFence                                     fence);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateFence(\n    VkDevice                                    device,\n    const VkFenceCreateInfo*                    pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkFence*                                    pFence);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyFence(\n    VkDevice                                    device,\n    VkFence                                     fence,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkResetFences(\n    VkDevice                                    device,\n    uint32_t                                    fenceCount,\n    const VkFence*                              pFences);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetFenceStatus(\n    VkDevice                                    device,\n    VkFence                                     fence);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkWaitForFences(\n    VkDevice                                    device,\n    uint32_t                                    fenceCount,\n    const VkFence*                              pFences,\n    VkBool32                                    waitAll,\n    uint64_t                                    timeout);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateSemaphore(\n    VkDevice                                    device,\n    const VkSemaphoreCreateInfo*                pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkSemaphore*                                pSemaphore);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroySemaphore(\n    VkDevice                                    device,\n    VkSemaphore                                 semaphore,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateEvent(\n    VkDevice                                    device,\n    const VkEventCreateInfo*                    pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkEvent*                                    pEvent);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyEvent(\n    VkDevice                                    device,\n    VkEvent                                     event,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetEventStatus(\n    VkDevice                                    device,\n    VkEvent                                     event);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkSetEvent(\n    VkDevice                                    device,\n    VkEvent                                     event);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkResetEvent(\n    VkDevice                                    device,\n    VkEvent                                     event);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateQueryPool(\n    VkDevice                                    device,\n    const VkQueryPoolCreateInfo*                pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkQueryPool*                                pQueryPool);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyQueryPool(\n    VkDevice                                    device,\n    VkQueryPool                                 queryPool,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetQueryPoolResults(\n    VkDevice                                    device,\n    VkQueryPool                                 queryPool,\n    uint32_t                                    firstQuery,\n    uint32_t                                    queryCount,\n    size_t                                      dataSize,\n    void*                                       pData,\n    VkDeviceSize                                stride,\n    VkQueryResultFlags                          flags);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateBuffer(\n    VkDevice                                    device,\n    const VkBufferCreateInfo*                   pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkBuffer*                                   pBuffer);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyBuffer(\n    VkDevice                                    device,\n    VkBuffer                                    buffer,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateBufferView(\n    VkDevice                                    device,\n    const VkBufferViewCreateInfo*               pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkBufferView*                               pView);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyBufferView(\n    VkDevice                                    device,\n    VkBufferView                                bufferView,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateImage(\n    VkDevice                                    device,\n    const VkImageCreateInfo*                    pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkImage*                                    pImage);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyImage(\n    VkDevice                                    device,\n    VkImage                                     image,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR void VKAPI_CALL vkGetImageSubresourceLayout(\n    VkDevice                                    device,\n    VkImage                                     image,\n    const VkImageSubresource*                   pSubresource,\n    VkSubresourceLayout*                        pLayout);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateImageView(\n    VkDevice                                    device,\n    const VkImageViewCreateInfo*                pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkImageView*                                pView);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyImageView(\n    VkDevice                                    device,\n    VkImageView                                 imageView,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateShaderModule(\n    VkDevice                                    device,\n    const VkShaderModuleCreateInfo*             pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkShaderModule*                             pShaderModule);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyShaderModule(\n    VkDevice                                    device,\n    VkShaderModule                              shaderModule,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreatePipelineCache(\n    VkDevice                                    device,\n    const VkPipelineCacheCreateInfo*            pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkPipelineCache*                            pPipelineCache);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyPipelineCache(\n    VkDevice                                    device,\n    VkPipelineCache                             pipelineCache,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetPipelineCacheData(\n    VkDevice                                    device,\n    VkPipelineCache                             pipelineCache,\n    size_t*                                     pDataSize,\n    void*                                       pData);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkMergePipelineCaches(\n    VkDevice                                    device,\n    VkPipelineCache                             dstCache,\n    uint32_t                                    srcCacheCount,\n    const VkPipelineCache*                      pSrcCaches);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateGraphicsPipelines(\n    VkDevice                                    device,\n    VkPipelineCache                             pipelineCache,\n    uint32_t                                    createInfoCount,\n    const VkGraphicsPipelineCreateInfo*         pCreateInfos,\n    const VkAllocationCallbacks*                pAllocator,\n    VkPipeline*                                 pPipelines);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateComputePipelines(\n    VkDevice                                    device,\n    VkPipelineCache                             pipelineCache,\n    uint32_t                                    createInfoCount,\n    const VkComputePipelineCreateInfo*          pCreateInfos,\n    const VkAllocationCallbacks*                pAllocator,\n    VkPipeline*                                 pPipelines);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyPipeline(\n    VkDevice                                    device,\n    VkPipeline                                  pipeline,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreatePipelineLayout(\n    VkDevice                                    device,\n    const VkPipelineLayoutCreateInfo*           pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkPipelineLayout*                           pPipelineLayout);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyPipelineLayout(\n    VkDevice                                    device,\n    VkPipelineLayout                            pipelineLayout,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateSampler(\n    VkDevice                                    device,\n    const VkSamplerCreateInfo*                  pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkSampler*                                  pSampler);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroySampler(\n    VkDevice                                    device,\n    VkSampler                                   sampler,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorSetLayout(\n    VkDevice                                    device,\n    const VkDescriptorSetLayoutCreateInfo*      pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkDescriptorSetLayout*                      pSetLayout);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyDescriptorSetLayout(\n    VkDevice                                    device,\n    VkDescriptorSetLayout                       descriptorSetLayout,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorPool(\n    VkDevice                                    device,\n    const VkDescriptorPoolCreateInfo*           pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkDescriptorPool*                           pDescriptorPool);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyDescriptorPool(\n    VkDevice                                    device,\n    VkDescriptorPool                            descriptorPool,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkResetDescriptorPool(\n    VkDevice                                    device,\n    VkDescriptorPool                            descriptorPool,\n    VkDescriptorPoolResetFlags                  flags);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkAllocateDescriptorSets(\n    VkDevice                                    device,\n    const VkDescriptorSetAllocateInfo*          pAllocateInfo,\n    VkDescriptorSet*                            pDescriptorSets);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkFreeDescriptorSets(\n    VkDevice                                    device,\n    VkDescriptorPool                            descriptorPool,\n    uint32_t                                    descriptorSetCount,\n    const VkDescriptorSet*                      pDescriptorSets);\n\nVKAPI_ATTR void VKAPI_CALL vkUpdateDescriptorSets(\n    VkDevice                                    device,\n    uint32_t                                    descriptorWriteCount,\n    const VkWriteDescriptorSet*                 pDescriptorWrites,\n    uint32_t                                    descriptorCopyCount,\n    const VkCopyDescriptorSet*                  pDescriptorCopies);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateFramebuffer(\n    VkDevice                                    device,\n    const VkFramebufferCreateInfo*              pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkFramebuffer*                              pFramebuffer);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyFramebuffer(\n    VkDevice                                    device,\n    VkFramebuffer                               framebuffer,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass(\n    VkDevice                                    device,\n    const VkRenderPassCreateInfo*               pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkRenderPass*                               pRenderPass);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyRenderPass(\n    VkDevice                                    device,\n    VkRenderPass                                renderPass,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR void VKAPI_CALL vkGetRenderAreaGranularity(\n    VkDevice                                    device,\n    VkRenderPass                                renderPass,\n    VkExtent2D*                                 pGranularity);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateCommandPool(\n    VkDevice                                    device,\n    const VkCommandPoolCreateInfo*              pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkCommandPool*                              pCommandPool);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyCommandPool(\n    VkDevice                                    device,\n    VkCommandPool                               commandPool,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkResetCommandPool(\n    VkDevice                                    device,\n    VkCommandPool                               commandPool,\n    VkCommandPoolResetFlags                     flags);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkAllocateCommandBuffers(\n    VkDevice                                    device,\n    const VkCommandBufferAllocateInfo*          pAllocateInfo,\n    VkCommandBuffer*                            pCommandBuffers);\n\nVKAPI_ATTR void VKAPI_CALL vkFreeCommandBuffers(\n    VkDevice                                    device,\n    VkCommandPool                               commandPool,\n    uint32_t                                    commandBufferCount,\n    const VkCommandBuffer*                      pCommandBuffers);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkBeginCommandBuffer(\n    VkCommandBuffer                             commandBuffer,\n    const VkCommandBufferBeginInfo*             pBeginInfo);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkEndCommandBuffer(\n    VkCommandBuffer                             commandBuffer);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkResetCommandBuffer(\n    VkCommandBuffer                             commandBuffer,\n    VkCommandBufferResetFlags                   flags);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdBindPipeline(\n    VkCommandBuffer                             commandBuffer,\n    VkPipelineBindPoint                         pipelineBindPoint,\n    VkPipeline                                  pipeline);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdSetViewport(\n    VkCommandBuffer                             commandBuffer,\n    uint32_t                                    firstViewport,\n    uint32_t                                    viewportCount,\n    const VkViewport*                           pViewports);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdSetScissor(\n    VkCommandBuffer                             commandBuffer,\n    uint32_t                                    firstScissor,\n    uint32_t                                    scissorCount,\n    const VkRect2D*                             pScissors);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdSetLineWidth(\n    VkCommandBuffer                             commandBuffer,\n    float                                       lineWidth);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdSetDepthBias(\n    VkCommandBuffer                             commandBuffer,\n    float                                       depthBiasConstantFactor,\n    float                                       depthBiasClamp,\n    float                                       depthBiasSlopeFactor);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdSetBlendConstants(\n    VkCommandBuffer                             commandBuffer,\n    const float                                 blendConstants[4]);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdSetDepthBounds(\n    VkCommandBuffer                             commandBuffer,\n    float                                       minDepthBounds,\n    float                                       maxDepthBounds);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdSetStencilCompareMask(\n    VkCommandBuffer                             commandBuffer,\n    VkStencilFaceFlags                          faceMask,\n    uint32_t                                    compareMask);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdSetStencilWriteMask(\n    VkCommandBuffer                             commandBuffer,\n    VkStencilFaceFlags                          faceMask,\n    uint32_t                                    writeMask);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdSetStencilReference(\n    VkCommandBuffer                             commandBuffer,\n    VkStencilFaceFlags                          faceMask,\n    uint32_t                                    reference);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdBindDescriptorSets(\n    VkCommandBuffer                             commandBuffer,\n    VkPipelineBindPoint                         pipelineBindPoint,\n    VkPipelineLayout                            layout,\n    uint32_t                                    firstSet,\n    uint32_t                                    descriptorSetCount,\n    const VkDescriptorSet*                      pDescriptorSets,\n    uint32_t                                    dynamicOffsetCount,\n    const uint32_t*                             pDynamicOffsets);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdBindIndexBuffer(\n    VkCommandBuffer                             commandBuffer,\n    VkBuffer                                    buffer,\n    VkDeviceSize                                offset,\n    VkIndexType                                 indexType);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdBindVertexBuffers(\n    VkCommandBuffer                             commandBuffer,\n    uint32_t                                    firstBinding,\n    uint32_t                                    bindingCount,\n    const VkBuffer*                             pBuffers,\n    const VkDeviceSize*                         pOffsets);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdDraw(\n    VkCommandBuffer                             commandBuffer,\n    uint32_t                                    vertexCount,\n    uint32_t                                    instanceCount,\n    uint32_t                                    firstVertex,\n    uint32_t                                    firstInstance);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexed(\n    VkCommandBuffer                             commandBuffer,\n    uint32_t                                    indexCount,\n    uint32_t                                    instanceCount,\n    uint32_t                                    firstIndex,\n    int32_t                                     vertexOffset,\n    uint32_t                                    firstInstance);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirect(\n    VkCommandBuffer                             commandBuffer,\n    VkBuffer                                    buffer,\n    VkDeviceSize                                offset,\n    uint32_t                                    drawCount,\n    uint32_t                                    stride);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexedIndirect(\n    VkCommandBuffer                             commandBuffer,\n    VkBuffer                                    buffer,\n    VkDeviceSize                                offset,\n    uint32_t                                    drawCount,\n    uint32_t                                    stride);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdDispatch(\n    VkCommandBuffer                             commandBuffer,\n    uint32_t                                    x,\n    uint32_t                                    y,\n    uint32_t                                    z);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdDispatchIndirect(\n    VkCommandBuffer                             commandBuffer,\n    VkBuffer                                    buffer,\n    VkDeviceSize                                offset);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdCopyBuffer(\n    VkCommandBuffer                             commandBuffer,\n    VkBuffer                                    srcBuffer,\n    VkBuffer                                    dstBuffer,\n    uint32_t                                    regionCount,\n    const VkBufferCopy*                         pRegions);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdCopyImage(\n    VkCommandBuffer                             commandBuffer,\n    VkImage                                     srcImage,\n    VkImageLayout                               srcImageLayout,\n    VkImage                                     dstImage,\n    VkImageLayout                               dstImageLayout,\n    uint32_t                                    regionCount,\n    const VkImageCopy*                          pRegions);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdBlitImage(\n    VkCommandBuffer                             commandBuffer,\n    VkImage                                     srcImage,\n    VkImageLayout                               srcImageLayout,\n    VkImage                                     dstImage,\n    VkImageLayout                               dstImageLayout,\n    uint32_t                                    regionCount,\n    const VkImageBlit*                          pRegions,\n    VkFilter                                    filter);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdCopyBufferToImage(\n    VkCommandBuffer                             commandBuffer,\n    VkBuffer                                    srcBuffer,\n    VkImage                                     dstImage,\n    VkImageLayout                               dstImageLayout,\n    uint32_t                                    regionCount,\n    const VkBufferImageCopy*                    pRegions);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdCopyImageToBuffer(\n    VkCommandBuffer                             commandBuffer,\n    VkImage                                     srcImage,\n    VkImageLayout                               srcImageLayout,\n    VkBuffer                                    dstBuffer,\n    uint32_t                                    regionCount,\n    const VkBufferImageCopy*                    pRegions);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdUpdateBuffer(\n    VkCommandBuffer                             commandBuffer,\n    VkBuffer                                    dstBuffer,\n    VkDeviceSize                                dstOffset,\n    VkDeviceSize                                dataSize,\n    const void*                                 pData);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdFillBuffer(\n    VkCommandBuffer                             commandBuffer,\n    VkBuffer                                    dstBuffer,\n    VkDeviceSize                                dstOffset,\n    VkDeviceSize                                size,\n    uint32_t                                    data);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdClearColorImage(\n    VkCommandBuffer                             commandBuffer,\n    VkImage                                     image,\n    VkImageLayout                               imageLayout,\n    const VkClearColorValue*                    pColor,\n    uint32_t                                    rangeCount,\n    const VkImageSubresourceRange*              pRanges);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdClearDepthStencilImage(\n    VkCommandBuffer                             commandBuffer,\n    VkImage                                     image,\n    VkImageLayout                               imageLayout,\n    const VkClearDepthStencilValue*             pDepthStencil,\n    uint32_t                                    rangeCount,\n    const VkImageSubresourceRange*              pRanges);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdClearAttachments(\n    VkCommandBuffer                             commandBuffer,\n    uint32_t                                    attachmentCount,\n    const VkClearAttachment*                    pAttachments,\n    uint32_t                                    rectCount,\n    const VkClearRect*                          pRects);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdResolveImage(\n    VkCommandBuffer                             commandBuffer,\n    VkImage                                     srcImage,\n    VkImageLayout                               srcImageLayout,\n    VkImage                                     dstImage,\n    VkImageLayout                               dstImageLayout,\n    uint32_t                                    regionCount,\n    const VkImageResolve*                       pRegions);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdSetEvent(\n    VkCommandBuffer                             commandBuffer,\n    VkEvent                                     event,\n    VkPipelineStageFlags                        stageMask);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdResetEvent(\n    VkCommandBuffer                             commandBuffer,\n    VkEvent                                     event,\n    VkPipelineStageFlags                        stageMask);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdWaitEvents(\n    VkCommandBuffer                             commandBuffer,\n    uint32_t                                    eventCount,\n    const VkEvent*                              pEvents,\n    VkPipelineStageFlags                        srcStageMask,\n    VkPipelineStageFlags                        dstStageMask,\n    uint32_t                                    memoryBarrierCount,\n    const VkMemoryBarrier*                      pMemoryBarriers,\n    uint32_t                                    bufferMemoryBarrierCount,\n    const VkBufferMemoryBarrier*                pBufferMemoryBarriers,\n    uint32_t                                    imageMemoryBarrierCount,\n    const VkImageMemoryBarrier*                 pImageMemoryBarriers);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier(\n    VkCommandBuffer                             commandBuffer,\n    VkPipelineStageFlags                        srcStageMask,\n    VkPipelineStageFlags                        dstStageMask,\n    VkDependencyFlags                           dependencyFlags,\n    uint32_t                                    memoryBarrierCount,\n    const VkMemoryBarrier*                      pMemoryBarriers,\n    uint32_t                                    bufferMemoryBarrierCount,\n    const VkBufferMemoryBarrier*                pBufferMemoryBarriers,\n    uint32_t                                    imageMemoryBarrierCount,\n    const VkImageMemoryBarrier*                 pImageMemoryBarriers);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdBeginQuery(\n    VkCommandBuffer                             commandBuffer,\n    VkQueryPool                                 queryPool,\n    uint32_t                                    query,\n    VkQueryControlFlags                         flags);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdEndQuery(\n    VkCommandBuffer                             commandBuffer,\n    VkQueryPool                                 queryPool,\n    uint32_t                                    query);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdResetQueryPool(\n    VkCommandBuffer                             commandBuffer,\n    VkQueryPool                                 queryPool,\n    uint32_t                                    firstQuery,\n    uint32_t                                    queryCount);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdWriteTimestamp(\n    VkCommandBuffer                             commandBuffer,\n    VkPipelineStageFlagBits                     pipelineStage,\n    VkQueryPool                                 queryPool,\n    uint32_t                                    query);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdCopyQueryPoolResults(\n    VkCommandBuffer                             commandBuffer,\n    VkQueryPool                                 queryPool,\n    uint32_t                                    firstQuery,\n    uint32_t                                    queryCount,\n    VkBuffer                                    dstBuffer,\n    VkDeviceSize                                dstOffset,\n    VkDeviceSize                                stride,\n    VkQueryResultFlags                          flags);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdPushConstants(\n    VkCommandBuffer                             commandBuffer,\n    VkPipelineLayout                            layout,\n    VkShaderStageFlags                          stageFlags,\n    uint32_t                                    offset,\n    uint32_t                                    size,\n    const void*                                 pValues);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdBeginRenderPass(\n    VkCommandBuffer                             commandBuffer,\n    const VkRenderPassBeginInfo*                pRenderPassBegin,\n    VkSubpassContents                           contents);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdNextSubpass(\n    VkCommandBuffer                             commandBuffer,\n    VkSubpassContents                           contents);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdEndRenderPass(\n    VkCommandBuffer                             commandBuffer);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdExecuteCommands(\n    VkCommandBuffer                             commandBuffer,\n    uint32_t                                    commandBufferCount,\n    const VkCommandBuffer*                      pCommandBuffers);\n#endif\n\n#define VK_KHR_surface 1\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSurfaceKHR)\n\n#define VK_KHR_SURFACE_SPEC_VERSION       25\n#define VK_KHR_SURFACE_EXTENSION_NAME     \"VK_KHR_surface\"\n#define VK_COLORSPACE_SRGB_NONLINEAR_KHR  VK_COLOR_SPACE_SRGB_NONLINEAR_KHR\n\n\ntypedef enum VkColorSpaceKHR {\n    VK_COLOR_SPACE_SRGB_NONLINEAR_KHR = 0,\n    VK_COLOR_SPACE_BEGIN_RANGE_KHR = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,\n    VK_COLOR_SPACE_END_RANGE_KHR = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,\n    VK_COLOR_SPACE_RANGE_SIZE_KHR = (VK_COLOR_SPACE_SRGB_NONLINEAR_KHR - VK_COLOR_SPACE_SRGB_NONLINEAR_KHR + 1),\n    VK_COLOR_SPACE_MAX_ENUM_KHR = 0x7FFFFFFF\n} VkColorSpaceKHR;\n\ntypedef enum VkPresentModeKHR {\n    VK_PRESENT_MODE_IMMEDIATE_KHR = 0,\n    VK_PRESENT_MODE_MAILBOX_KHR = 1,\n    VK_PRESENT_MODE_FIFO_KHR = 2,\n    VK_PRESENT_MODE_FIFO_RELAXED_KHR = 3,\n    VK_PRESENT_MODE_BEGIN_RANGE_KHR = VK_PRESENT_MODE_IMMEDIATE_KHR,\n    VK_PRESENT_MODE_END_RANGE_KHR = VK_PRESENT_MODE_FIFO_RELAXED_KHR,\n    VK_PRESENT_MODE_RANGE_SIZE_KHR = (VK_PRESENT_MODE_FIFO_RELAXED_KHR - VK_PRESENT_MODE_IMMEDIATE_KHR + 1),\n    VK_PRESENT_MODE_MAX_ENUM_KHR = 0x7FFFFFFF\n} VkPresentModeKHR;\n\n\ntypedef enum VkSurfaceTransformFlagBitsKHR {\n    VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR = 0x00000001,\n    VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR = 0x00000002,\n    VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR = 0x00000004,\n    VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR = 0x00000008,\n    VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_BIT_KHR = 0x00000010,\n    VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR = 0x00000020,\n    VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR = 0x00000040,\n    VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR = 0x00000080,\n    VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR = 0x00000100,\n    VK_SURFACE_TRANSFORM_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF\n} VkSurfaceTransformFlagBitsKHR;\ntypedef VkFlags VkSurfaceTransformFlagsKHR;\n\ntypedef enum VkCompositeAlphaFlagBitsKHR {\n    VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR = 0x00000001,\n    VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR = 0x00000002,\n    VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR = 0x00000004,\n    VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR = 0x00000008,\n    VK_COMPOSITE_ALPHA_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF\n} VkCompositeAlphaFlagBitsKHR;\ntypedef VkFlags VkCompositeAlphaFlagsKHR;\n\ntypedef struct VkSurfaceCapabilitiesKHR {\n    uint32_t                         minImageCount;\n    uint32_t                         maxImageCount;\n    VkExtent2D                       currentExtent;\n    VkExtent2D                       minImageExtent;\n    VkExtent2D                       maxImageExtent;\n    uint32_t                         maxImageArrayLayers;\n    VkSurfaceTransformFlagsKHR       supportedTransforms;\n    VkSurfaceTransformFlagBitsKHR    currentTransform;\n    VkCompositeAlphaFlagsKHR         supportedCompositeAlpha;\n    VkImageUsageFlags                supportedUsageFlags;\n} VkSurfaceCapabilitiesKHR;\n\ntypedef struct VkSurfaceFormatKHR {\n    VkFormat           format;\n    VkColorSpaceKHR    colorSpace;\n} VkSurfaceFormatKHR;\n\n\ntypedef void (VKAPI_PTR *PFN_vkDestroySurfaceKHR)(VkInstance instance, VkSurfaceKHR surface, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, VkSurfaceKHR surface, VkBool32* pSupported);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR)(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* pSurfaceCapabilities);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceFormatsKHR)(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfacePresentModesKHR)(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pPresentModeCount, VkPresentModeKHR* pPresentModes);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR void VKAPI_CALL vkDestroySurfaceKHR(\n    VkInstance                                  instance,\n    VkSurfaceKHR                                surface,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceSupportKHR(\n    VkPhysicalDevice                            physicalDevice,\n    uint32_t                                    queueFamilyIndex,\n    VkSurfaceKHR                                surface,\n    VkBool32*                                   pSupported);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilitiesKHR(\n    VkPhysicalDevice                            physicalDevice,\n    VkSurfaceKHR                                surface,\n    VkSurfaceCapabilitiesKHR*                   pSurfaceCapabilities);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceFormatsKHR(\n    VkPhysicalDevice                            physicalDevice,\n    VkSurfaceKHR                                surface,\n    uint32_t*                                   pSurfaceFormatCount,\n    VkSurfaceFormatKHR*                         pSurfaceFormats);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfacePresentModesKHR(\n    VkPhysicalDevice                            physicalDevice,\n    VkSurfaceKHR                                surface,\n    uint32_t*                                   pPresentModeCount,\n    VkPresentModeKHR*                           pPresentModes);\n#endif\n\n#define VK_KHR_swapchain 1\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSwapchainKHR)\n\n#define VK_KHR_SWAPCHAIN_SPEC_VERSION     68\n#define VK_KHR_SWAPCHAIN_EXTENSION_NAME   \"VK_KHR_swapchain\"\n\ntypedef VkFlags VkSwapchainCreateFlagsKHR;\n\ntypedef struct VkSwapchainCreateInfoKHR {\n    VkStructureType                  sType;\n    const void*                      pNext;\n    VkSwapchainCreateFlagsKHR        flags;\n    VkSurfaceKHR                     surface;\n    uint32_t                         minImageCount;\n    VkFormat                         imageFormat;\n    VkColorSpaceKHR                  imageColorSpace;\n    VkExtent2D                       imageExtent;\n    uint32_t                         imageArrayLayers;\n    VkImageUsageFlags                imageUsage;\n    VkSharingMode                    imageSharingMode;\n    uint32_t                         queueFamilyIndexCount;\n    const uint32_t*                  pQueueFamilyIndices;\n    VkSurfaceTransformFlagBitsKHR    preTransform;\n    VkCompositeAlphaFlagBitsKHR      compositeAlpha;\n    VkPresentModeKHR                 presentMode;\n    VkBool32                         clipped;\n    VkSwapchainKHR                   oldSwapchain;\n} VkSwapchainCreateInfoKHR;\n\ntypedef struct VkPresentInfoKHR {\n    VkStructureType          sType;\n    const void*              pNext;\n    uint32_t                 waitSemaphoreCount;\n    const VkSemaphore*       pWaitSemaphores;\n    uint32_t                 swapchainCount;\n    const VkSwapchainKHR*    pSwapchains;\n    const uint32_t*          pImageIndices;\n    VkResult*                pResults;\n} VkPresentInfoKHR;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateSwapchainKHR)(VkDevice device, const VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain);\ntypedef void (VKAPI_PTR *PFN_vkDestroySwapchainKHR)(VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks* pAllocator);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainImagesKHR)(VkDevice device, VkSwapchainKHR swapchain, uint32_t* pSwapchainImageCount, VkImage* pSwapchainImages);\ntypedef VkResult (VKAPI_PTR *PFN_vkAcquireNextImageKHR)(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t* pImageIndex);\ntypedef VkResult (VKAPI_PTR *PFN_vkQueuePresentKHR)(VkQueue queue, const VkPresentInfoKHR* pPresentInfo);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateSwapchainKHR(\n    VkDevice                                    device,\n    const VkSwapchainCreateInfoKHR*             pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkSwapchainKHR*                             pSwapchain);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroySwapchainKHR(\n    VkDevice                                    device,\n    VkSwapchainKHR                              swapchain,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainImagesKHR(\n    VkDevice                                    device,\n    VkSwapchainKHR                              swapchain,\n    uint32_t*                                   pSwapchainImageCount,\n    VkImage*                                    pSwapchainImages);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkAcquireNextImageKHR(\n    VkDevice                                    device,\n    VkSwapchainKHR                              swapchain,\n    uint64_t                                    timeout,\n    VkSemaphore                                 semaphore,\n    VkFence                                     fence,\n    uint32_t*                                   pImageIndex);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkQueuePresentKHR(\n    VkQueue                                     queue,\n    const VkPresentInfoKHR*                     pPresentInfo);\n#endif\n\n#define VK_KHR_display 1\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDisplayKHR)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDisplayModeKHR)\n\n#define VK_KHR_DISPLAY_SPEC_VERSION       21\n#define VK_KHR_DISPLAY_EXTENSION_NAME     \"VK_KHR_display\"\n\n\ntypedef enum VkDisplayPlaneAlphaFlagBitsKHR {\n    VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR = 0x00000001,\n    VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR = 0x00000002,\n    VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR = 0x00000004,\n    VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR = 0x00000008,\n    VK_DISPLAY_PLANE_ALPHA_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF\n} VkDisplayPlaneAlphaFlagBitsKHR;\ntypedef VkFlags VkDisplayPlaneAlphaFlagsKHR;\ntypedef VkFlags VkDisplayModeCreateFlagsKHR;\ntypedef VkFlags VkDisplaySurfaceCreateFlagsKHR;\n\ntypedef struct VkDisplayPropertiesKHR {\n    VkDisplayKHR                  display;\n    const char*                   displayName;\n    VkExtent2D                    physicalDimensions;\n    VkExtent2D                    physicalResolution;\n    VkSurfaceTransformFlagsKHR    supportedTransforms;\n    VkBool32                      planeReorderPossible;\n    VkBool32                      persistentContent;\n} VkDisplayPropertiesKHR;\n\ntypedef struct VkDisplayModeParametersKHR {\n    VkExtent2D    visibleRegion;\n    uint32_t      refreshRate;\n} VkDisplayModeParametersKHR;\n\ntypedef struct VkDisplayModePropertiesKHR {\n    VkDisplayModeKHR              displayMode;\n    VkDisplayModeParametersKHR    parameters;\n} VkDisplayModePropertiesKHR;\n\ntypedef struct VkDisplayModeCreateInfoKHR {\n    VkStructureType                sType;\n    const void*                    pNext;\n    VkDisplayModeCreateFlagsKHR    flags;\n    VkDisplayModeParametersKHR     parameters;\n} VkDisplayModeCreateInfoKHR;\n\ntypedef struct VkDisplayPlaneCapabilitiesKHR {\n    VkDisplayPlaneAlphaFlagsKHR    supportedAlpha;\n    VkOffset2D                     minSrcPosition;\n    VkOffset2D                     maxSrcPosition;\n    VkExtent2D                     minSrcExtent;\n    VkExtent2D                     maxSrcExtent;\n    VkOffset2D                     minDstPosition;\n    VkOffset2D                     maxDstPosition;\n    VkExtent2D                     minDstExtent;\n    VkExtent2D                     maxDstExtent;\n} VkDisplayPlaneCapabilitiesKHR;\n\ntypedef struct VkDisplayPlanePropertiesKHR {\n    VkDisplayKHR    currentDisplay;\n    uint32_t        currentStackIndex;\n} VkDisplayPlanePropertiesKHR;\n\ntypedef struct VkDisplaySurfaceCreateInfoKHR {\n    VkStructureType                   sType;\n    const void*                       pNext;\n    VkDisplaySurfaceCreateFlagsKHR    flags;\n    VkDisplayModeKHR                  displayMode;\n    uint32_t                          planeIndex;\n    uint32_t                          planeStackIndex;\n    VkSurfaceTransformFlagBitsKHR     transform;\n    float                             globalAlpha;\n    VkDisplayPlaneAlphaFlagBitsKHR    alphaMode;\n    VkExtent2D                        imageExtent;\n} VkDisplaySurfaceCreateInfoKHR;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceDisplayPropertiesKHR)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayPropertiesKHR* pProperties);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayPlanePropertiesKHR* pProperties);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetDisplayPlaneSupportedDisplaysKHR)(VkPhysicalDevice physicalDevice, uint32_t planeIndex, uint32_t* pDisplayCount, VkDisplayKHR* pDisplays);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetDisplayModePropertiesKHR)(VkPhysicalDevice physicalDevice, VkDisplayKHR display, uint32_t* pPropertyCount, VkDisplayModePropertiesKHR* pProperties);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateDisplayModeKHR)(VkPhysicalDevice physicalDevice, VkDisplayKHR display, const VkDisplayModeCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDisplayModeKHR* pMode);\ntypedef VkResult (VKAPI_PTR *PFN_vkGetDisplayPlaneCapabilitiesKHR)(VkPhysicalDevice physicalDevice, VkDisplayModeKHR mode, uint32_t planeIndex, VkDisplayPlaneCapabilitiesKHR* pCapabilities);\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateDisplayPlaneSurfaceKHR)(VkInstance instance, const VkDisplaySurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceDisplayPropertiesKHR(\n    VkPhysicalDevice                            physicalDevice,\n    uint32_t*                                   pPropertyCount,\n    VkDisplayPropertiesKHR*                     pProperties);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceDisplayPlanePropertiesKHR(\n    VkPhysicalDevice                            physicalDevice,\n    uint32_t*                                   pPropertyCount,\n    VkDisplayPlanePropertiesKHR*                pProperties);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetDisplayPlaneSupportedDisplaysKHR(\n    VkPhysicalDevice                            physicalDevice,\n    uint32_t                                    planeIndex,\n    uint32_t*                                   pDisplayCount,\n    VkDisplayKHR*                               pDisplays);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetDisplayModePropertiesKHR(\n    VkPhysicalDevice                            physicalDevice,\n    VkDisplayKHR                                display,\n    uint32_t*                                   pPropertyCount,\n    VkDisplayModePropertiesKHR*                 pProperties);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateDisplayModeKHR(\n    VkPhysicalDevice                            physicalDevice,\n    VkDisplayKHR                                display,\n    const VkDisplayModeCreateInfoKHR*           pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkDisplayModeKHR*                           pMode);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkGetDisplayPlaneCapabilitiesKHR(\n    VkPhysicalDevice                            physicalDevice,\n    VkDisplayModeKHR                            mode,\n    uint32_t                                    planeIndex,\n    VkDisplayPlaneCapabilitiesKHR*              pCapabilities);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateDisplayPlaneSurfaceKHR(\n    VkInstance                                  instance,\n    const VkDisplaySurfaceCreateInfoKHR*        pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkSurfaceKHR*                               pSurface);\n#endif\n\n#define VK_KHR_display_swapchain 1\n#define VK_KHR_DISPLAY_SWAPCHAIN_SPEC_VERSION 9\n#define VK_KHR_DISPLAY_SWAPCHAIN_EXTENSION_NAME \"VK_KHR_display_swapchain\"\n\ntypedef struct VkDisplayPresentInfoKHR {\n    VkStructureType    sType;\n    const void*        pNext;\n    VkRect2D           srcRect;\n    VkRect2D           dstRect;\n    VkBool32           persistent;\n} VkDisplayPresentInfoKHR;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateSharedSwapchainsKHR)(VkDevice device, uint32_t swapchainCount, const VkSwapchainCreateInfoKHR* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchains);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateSharedSwapchainsKHR(\n    VkDevice                                    device,\n    uint32_t                                    swapchainCount,\n    const VkSwapchainCreateInfoKHR*             pCreateInfos,\n    const VkAllocationCallbacks*                pAllocator,\n    VkSwapchainKHR*                             pSwapchains);\n#endif\n\n#ifdef VK_USE_PLATFORM_XLIB_KHR\n#define VK_KHR_xlib_surface 1\n#include <X11/Xlib.h>\n\n#define VK_KHR_XLIB_SURFACE_SPEC_VERSION  6\n#define VK_KHR_XLIB_SURFACE_EXTENSION_NAME \"VK_KHR_xlib_surface\"\n\ntypedef VkFlags VkXlibSurfaceCreateFlagsKHR;\n\ntypedef struct VkXlibSurfaceCreateInfoKHR {\n    VkStructureType                sType;\n    const void*                    pNext;\n    VkXlibSurfaceCreateFlagsKHR    flags;\n    Display*                       dpy;\n    Window                         window;\n} VkXlibSurfaceCreateInfoKHR;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateXlibSurfaceKHR)(VkInstance instance, const VkXlibSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);\ntypedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, Display* dpy, VisualID visualID);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateXlibSurfaceKHR(\n    VkInstance                                  instance,\n    const VkXlibSurfaceCreateInfoKHR*           pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkSurfaceKHR*                               pSurface);\n\nVKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceXlibPresentationSupportKHR(\n    VkPhysicalDevice                            physicalDevice,\n    uint32_t                                    queueFamilyIndex,\n    Display*                                    dpy,\n    VisualID                                    visualID);\n#endif\n#endif /* VK_USE_PLATFORM_XLIB_KHR */\n\n#ifdef VK_USE_PLATFORM_XCB_KHR\n#define VK_KHR_xcb_surface 1\n#include <xcb/xcb.h>\n\n#define VK_KHR_XCB_SURFACE_SPEC_VERSION   6\n#define VK_KHR_XCB_SURFACE_EXTENSION_NAME \"VK_KHR_xcb_surface\"\n\ntypedef VkFlags VkXcbSurfaceCreateFlagsKHR;\n\ntypedef struct VkXcbSurfaceCreateInfoKHR {\n    VkStructureType               sType;\n    const void*                   pNext;\n    VkXcbSurfaceCreateFlagsKHR    flags;\n    xcb_connection_t*             connection;\n    xcb_window_t                  window;\n} VkXcbSurfaceCreateInfoKHR;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateXcbSurfaceKHR)(VkInstance instance, const VkXcbSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);\ntypedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, xcb_connection_t* connection, xcb_visualid_t visual_id);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateXcbSurfaceKHR(\n    VkInstance                                  instance,\n    const VkXcbSurfaceCreateInfoKHR*            pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkSurfaceKHR*                               pSurface);\n\nVKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceXcbPresentationSupportKHR(\n    VkPhysicalDevice                            physicalDevice,\n    uint32_t                                    queueFamilyIndex,\n    xcb_connection_t*                           connection,\n    xcb_visualid_t                              visual_id);\n#endif\n#endif /* VK_USE_PLATFORM_XCB_KHR */\n\n#ifdef VK_USE_PLATFORM_WAYLAND_KHR\n#define VK_KHR_wayland_surface 1\n#include <wayland-client.h>\n\n#define VK_KHR_WAYLAND_SURFACE_SPEC_VERSION 5\n#define VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME \"VK_KHR_wayland_surface\"\n\ntypedef VkFlags VkWaylandSurfaceCreateFlagsKHR;\n\ntypedef struct VkWaylandSurfaceCreateInfoKHR {\n    VkStructureType                   sType;\n    const void*                       pNext;\n    VkWaylandSurfaceCreateFlagsKHR    flags;\n    struct wl_display*                display;\n    struct wl_surface*                surface;\n} VkWaylandSurfaceCreateInfoKHR;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateWaylandSurfaceKHR)(VkInstance instance, const VkWaylandSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);\ntypedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, struct wl_display* display);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateWaylandSurfaceKHR(\n    VkInstance                                  instance,\n    const VkWaylandSurfaceCreateInfoKHR*        pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkSurfaceKHR*                               pSurface);\n\nVKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceWaylandPresentationSupportKHR(\n    VkPhysicalDevice                            physicalDevice,\n    uint32_t                                    queueFamilyIndex,\n    struct wl_display*                          display);\n#endif\n#endif /* VK_USE_PLATFORM_WAYLAND_KHR */\n\n#ifdef VK_USE_PLATFORM_MIR_KHR\n#define VK_KHR_mir_surface 1\n#include <mir_toolkit/client_types.h>\n\n#define VK_KHR_MIR_SURFACE_SPEC_VERSION   4\n#define VK_KHR_MIR_SURFACE_EXTENSION_NAME \"VK_KHR_mir_surface\"\n\ntypedef VkFlags VkMirSurfaceCreateFlagsKHR;\n\ntypedef struct VkMirSurfaceCreateInfoKHR {\n    VkStructureType               sType;\n    const void*                   pNext;\n    VkMirSurfaceCreateFlagsKHR    flags;\n    MirConnection*                connection;\n    MirSurface*                   mirSurface;\n} VkMirSurfaceCreateInfoKHR;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateMirSurfaceKHR)(VkInstance instance, const VkMirSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);\ntypedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceMirPresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, MirConnection* connection);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateMirSurfaceKHR(\n    VkInstance                                  instance,\n    const VkMirSurfaceCreateInfoKHR*            pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkSurfaceKHR*                               pSurface);\n\nVKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceMirPresentationSupportKHR(\n    VkPhysicalDevice                            physicalDevice,\n    uint32_t                                    queueFamilyIndex,\n    MirConnection*                              connection);\n#endif\n#endif /* VK_USE_PLATFORM_MIR_KHR */\n\n#ifdef VK_USE_PLATFORM_ANDROID_KHR\n#define VK_KHR_android_surface 1\n#include <android/native_window.h>\n\n#define VK_KHR_ANDROID_SURFACE_SPEC_VERSION 6\n#define VK_KHR_ANDROID_SURFACE_EXTENSION_NAME \"VK_KHR_android_surface\"\n\ntypedef VkFlags VkAndroidSurfaceCreateFlagsKHR;\n\ntypedef struct VkAndroidSurfaceCreateInfoKHR {\n    VkStructureType                   sType;\n    const void*                       pNext;\n    VkAndroidSurfaceCreateFlagsKHR    flags;\n    ANativeWindow*                    window;\n} VkAndroidSurfaceCreateInfoKHR;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateAndroidSurfaceKHR)(VkInstance instance, const VkAndroidSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateAndroidSurfaceKHR(\n    VkInstance                                  instance,\n    const VkAndroidSurfaceCreateInfoKHR*        pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkSurfaceKHR*                               pSurface);\n#endif\n#endif /* VK_USE_PLATFORM_ANDROID_KHR */\n\n#ifdef VK_USE_PLATFORM_WIN32_KHR\n#define VK_KHR_win32_surface 1\n#include <windows.h>\n\n#define VK_KHR_WIN32_SURFACE_SPEC_VERSION 5\n#define VK_KHR_WIN32_SURFACE_EXTENSION_NAME \"VK_KHR_win32_surface\"\n\ntypedef VkFlags VkWin32SurfaceCreateFlagsKHR;\n\ntypedef struct VkWin32SurfaceCreateInfoKHR {\n    VkStructureType                 sType;\n    const void*                     pNext;\n    VkWin32SurfaceCreateFlagsKHR    flags;\n    HINSTANCE                       hinstance;\n    HWND                            hwnd;\n} VkWin32SurfaceCreateInfoKHR;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateWin32SurfaceKHR)(VkInstance instance, const VkWin32SurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);\ntypedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateWin32SurfaceKHR(\n    VkInstance                                  instance,\n    const VkWin32SurfaceCreateInfoKHR*          pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkSurfaceKHR*                               pSurface);\n\nVKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceWin32PresentationSupportKHR(\n    VkPhysicalDevice                            physicalDevice,\n    uint32_t                                    queueFamilyIndex);\n#endif\n#endif /* VK_USE_PLATFORM_WIN32_KHR */\n\n#define VK_KHR_sampler_mirror_clamp_to_edge 1\n#define VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_SPEC_VERSION 1\n#define VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME \"VK_KHR_sampler_mirror_clamp_to_edge\"\n\n\n#define VK_EXT_debug_report 1\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDebugReportCallbackEXT)\n\n#define VK_EXT_DEBUG_REPORT_SPEC_VERSION  3\n#define VK_EXT_DEBUG_REPORT_EXTENSION_NAME \"VK_EXT_debug_report\"\n#define VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT\n\n\ntypedef enum VkDebugReportObjectTypeEXT {\n    VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT = 0,\n    VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT = 1,\n    VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT = 2,\n    VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT = 3,\n    VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT = 4,\n    VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT = 5,\n    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT = 6,\n    VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT = 7,\n    VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT = 8,\n    VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT = 9,\n    VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT = 10,\n    VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT = 11,\n    VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT = 12,\n    VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT = 13,\n    VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT = 14,\n    VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT = 15,\n    VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT = 16,\n    VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT = 17,\n    VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT = 18,\n    VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT = 19,\n    VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT = 20,\n    VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT = 21,\n    VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT = 22,\n    VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT = 23,\n    VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT = 24,\n    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT = 25,\n    VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT = 26,\n    VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT = 27,\n    VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_EXT = 28,\n    VK_DEBUG_REPORT_OBJECT_TYPE_BEGIN_RANGE_EXT = VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT,\n    VK_DEBUG_REPORT_OBJECT_TYPE_END_RANGE_EXT = VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_EXT,\n    VK_DEBUG_REPORT_OBJECT_TYPE_RANGE_SIZE_EXT = (VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_EXT - VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT + 1),\n    VK_DEBUG_REPORT_OBJECT_TYPE_MAX_ENUM_EXT = 0x7FFFFFFF\n} VkDebugReportObjectTypeEXT;\n\ntypedef enum VkDebugReportErrorEXT {\n    VK_DEBUG_REPORT_ERROR_NONE_EXT = 0,\n    VK_DEBUG_REPORT_ERROR_CALLBACK_REF_EXT = 1,\n    VK_DEBUG_REPORT_ERROR_BEGIN_RANGE_EXT = VK_DEBUG_REPORT_ERROR_NONE_EXT,\n    VK_DEBUG_REPORT_ERROR_END_RANGE_EXT = VK_DEBUG_REPORT_ERROR_CALLBACK_REF_EXT,\n    VK_DEBUG_REPORT_ERROR_RANGE_SIZE_EXT = (VK_DEBUG_REPORT_ERROR_CALLBACK_REF_EXT - VK_DEBUG_REPORT_ERROR_NONE_EXT + 1),\n    VK_DEBUG_REPORT_ERROR_MAX_ENUM_EXT = 0x7FFFFFFF\n} VkDebugReportErrorEXT;\n\n\ntypedef enum VkDebugReportFlagBitsEXT {\n    VK_DEBUG_REPORT_INFORMATION_BIT_EXT = 0x00000001,\n    VK_DEBUG_REPORT_WARNING_BIT_EXT = 0x00000002,\n    VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT = 0x00000004,\n    VK_DEBUG_REPORT_ERROR_BIT_EXT = 0x00000008,\n    VK_DEBUG_REPORT_DEBUG_BIT_EXT = 0x00000010,\n    VK_DEBUG_REPORT_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF\n} VkDebugReportFlagBitsEXT;\ntypedef VkFlags VkDebugReportFlagsEXT;\n\ntypedef VkBool32 (VKAPI_PTR *PFN_vkDebugReportCallbackEXT)(\n    VkDebugReportFlagsEXT                       flags,\n    VkDebugReportObjectTypeEXT                  objectType,\n    uint64_t                                    object,\n    size_t                                      location,\n    int32_t                                     messageCode,\n    const char*                                 pLayerPrefix,\n    const char*                                 pMessage,\n    void*                                       pUserData);\n\n\ntypedef struct VkDebugReportCallbackCreateInfoEXT {\n    VkStructureType                 sType;\n    const void*                     pNext;\n    VkDebugReportFlagsEXT           flags;\n    PFN_vkDebugReportCallbackEXT    pfnCallback;\n    void*                           pUserData;\n} VkDebugReportCallbackCreateInfoEXT;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkCreateDebugReportCallbackEXT)(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback);\ntypedef void (VKAPI_PTR *PFN_vkDestroyDebugReportCallbackEXT)(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator);\ntypedef void (VKAPI_PTR *PFN_vkDebugReportMessageEXT)(VkInstance instance, VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugReportCallbackEXT(\n    VkInstance                                  instance,\n    const VkDebugReportCallbackCreateInfoEXT*   pCreateInfo,\n    const VkAllocationCallbacks*                pAllocator,\n    VkDebugReportCallbackEXT*                   pCallback);\n\nVKAPI_ATTR void VKAPI_CALL vkDestroyDebugReportCallbackEXT(\n    VkInstance                                  instance,\n    VkDebugReportCallbackEXT                    callback,\n    const VkAllocationCallbacks*                pAllocator);\n\nVKAPI_ATTR void VKAPI_CALL vkDebugReportMessageEXT(\n    VkInstance                                  instance,\n    VkDebugReportFlagsEXT                       flags,\n    VkDebugReportObjectTypeEXT                  objectType,\n    uint64_t                                    object,\n    size_t                                      location,\n    int32_t                                     messageCode,\n    const char*                                 pLayerPrefix,\n    const char*                                 pMessage);\n#endif\n\n#define VK_NV_glsl_shader 1\n#define VK_NV_GLSL_SHADER_SPEC_VERSION    1\n#define VK_NV_GLSL_SHADER_EXTENSION_NAME  \"VK_NV_glsl_shader\"\n\n\n#define VK_IMG_filter_cubic 1\n#define VK_IMG_FILTER_CUBIC_SPEC_VERSION  1\n#define VK_IMG_FILTER_CUBIC_EXTENSION_NAME \"VK_IMG_filter_cubic\"\n\n\n#define VK_AMD_rasterization_order 1\n#define VK_AMD_RASTERIZATION_ORDER_SPEC_VERSION 1\n#define VK_AMD_RASTERIZATION_ORDER_EXTENSION_NAME \"VK_AMD_rasterization_order\"\n\n\ntypedef enum VkRasterizationOrderAMD {\n    VK_RASTERIZATION_ORDER_STRICT_AMD = 0,\n    VK_RASTERIZATION_ORDER_RELAXED_AMD = 1,\n    VK_RASTERIZATION_ORDER_BEGIN_RANGE_AMD = VK_RASTERIZATION_ORDER_STRICT_AMD,\n    VK_RASTERIZATION_ORDER_END_RANGE_AMD = VK_RASTERIZATION_ORDER_RELAXED_AMD,\n    VK_RASTERIZATION_ORDER_RANGE_SIZE_AMD = (VK_RASTERIZATION_ORDER_RELAXED_AMD - VK_RASTERIZATION_ORDER_STRICT_AMD + 1),\n    VK_RASTERIZATION_ORDER_MAX_ENUM_AMD = 0x7FFFFFFF\n} VkRasterizationOrderAMD;\n\ntypedef struct VkPipelineRasterizationStateRasterizationOrderAMD {\n    VkStructureType            sType;\n    const void*                pNext;\n    VkRasterizationOrderAMD    rasterizationOrder;\n} VkPipelineRasterizationStateRasterizationOrderAMD;\n\n\n\n#define VK_AMD_shader_trinary_minmax 1\n#define VK_AMD_SHADER_TRINARY_MINMAX_SPEC_VERSION 1\n#define VK_AMD_SHADER_TRINARY_MINMAX_EXTENSION_NAME \"VK_AMD_shader_trinary_minmax\"\n\n\n#define VK_AMD_shader_explicit_vertex_parameter 1\n#define VK_AMD_SHADER_EXPLICIT_VERTEX_PARAMETER_SPEC_VERSION 1\n#define VK_AMD_SHADER_EXPLICIT_VERTEX_PARAMETER_EXTENSION_NAME \"VK_AMD_shader_explicit_vertex_parameter\"\n\n\n#define VK_EXT_debug_marker 1\n#define VK_EXT_DEBUG_MARKER_SPEC_VERSION  3\n#define VK_EXT_DEBUG_MARKER_EXTENSION_NAME \"VK_EXT_debug_marker\"\n\ntypedef struct VkDebugMarkerObjectNameInfoEXT {\n    VkStructureType               sType;\n    const void*                   pNext;\n    VkDebugReportObjectTypeEXT    objectType;\n    uint64_t                      object;\n    const char*                   pObjectName;\n} VkDebugMarkerObjectNameInfoEXT;\n\ntypedef struct VkDebugMarkerObjectTagInfoEXT {\n    VkStructureType               sType;\n    const void*                   pNext;\n    VkDebugReportObjectTypeEXT    objectType;\n    uint64_t                      object;\n    uint64_t                      tagName;\n    size_t                        tagSize;\n    const void*                   pTag;\n} VkDebugMarkerObjectTagInfoEXT;\n\ntypedef struct VkDebugMarkerMarkerInfoEXT {\n    VkStructureType    sType;\n    const void*        pNext;\n    const char*        pMarkerName;\n    float              color[4];\n} VkDebugMarkerMarkerInfoEXT;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkDebugMarkerSetObjectTagEXT)(VkDevice device, VkDebugMarkerObjectTagInfoEXT* pTagInfo);\ntypedef VkResult (VKAPI_PTR *PFN_vkDebugMarkerSetObjectNameEXT)(VkDevice device, VkDebugMarkerObjectNameInfoEXT* pNameInfo);\ntypedef void (VKAPI_PTR *PFN_vkCmdDebugMarkerBeginEXT)(VkCommandBuffer commandBuffer, VkDebugMarkerMarkerInfoEXT* pMarkerInfo);\ntypedef void (VKAPI_PTR *PFN_vkCmdDebugMarkerEndEXT)(VkCommandBuffer commandBuffer);\ntypedef void (VKAPI_PTR *PFN_vkCmdDebugMarkerInsertEXT)(VkCommandBuffer commandBuffer, VkDebugMarkerMarkerInfoEXT* pMarkerInfo);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectTagEXT(\n    VkDevice                                    device,\n    VkDebugMarkerObjectTagInfoEXT*              pTagInfo);\n\nVKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectNameEXT(\n    VkDevice                                    device,\n    VkDebugMarkerObjectNameInfoEXT*             pNameInfo);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerBeginEXT(\n    VkCommandBuffer                             commandBuffer,\n    VkDebugMarkerMarkerInfoEXT*                 pMarkerInfo);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerEndEXT(\n    VkCommandBuffer                             commandBuffer);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerInsertEXT(\n    VkCommandBuffer                             commandBuffer,\n    VkDebugMarkerMarkerInfoEXT*                 pMarkerInfo);\n#endif\n\n#define VK_AMD_gcn_shader 1\n#define VK_AMD_GCN_SHADER_SPEC_VERSION    1\n#define VK_AMD_GCN_SHADER_EXTENSION_NAME  \"VK_AMD_gcn_shader\"\n\n\n#define VK_NV_dedicated_allocation 1\n#define VK_NV_DEDICATED_ALLOCATION_SPEC_VERSION 1\n#define VK_NV_DEDICATED_ALLOCATION_EXTENSION_NAME \"VK_NV_dedicated_allocation\"\n\ntypedef struct VkDedicatedAllocationImageCreateInfoNV {\n    VkStructureType    sType;\n    const void*        pNext;\n    VkBool32           dedicatedAllocation;\n} VkDedicatedAllocationImageCreateInfoNV;\n\ntypedef struct VkDedicatedAllocationBufferCreateInfoNV {\n    VkStructureType    sType;\n    const void*        pNext;\n    VkBool32           dedicatedAllocation;\n} VkDedicatedAllocationBufferCreateInfoNV;\n\ntypedef struct VkDedicatedAllocationMemoryAllocateInfoNV {\n    VkStructureType    sType;\n    const void*        pNext;\n    VkImage            image;\n    VkBuffer           buffer;\n} VkDedicatedAllocationMemoryAllocateInfoNV;\n\n\n\n#define VK_AMD_draw_indirect_count 1\n#define VK_AMD_DRAW_INDIRECT_COUNT_SPEC_VERSION 1\n#define VK_AMD_DRAW_INDIRECT_COUNT_EXTENSION_NAME \"VK_AMD_draw_indirect_count\"\n\ntypedef void (VKAPI_PTR *PFN_vkCmdDrawIndirectCountAMD)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride);\ntypedef void (VKAPI_PTR *PFN_vkCmdDrawIndexedIndirectCountAMD)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirectCountAMD(\n    VkCommandBuffer                             commandBuffer,\n    VkBuffer                                    buffer,\n    VkDeviceSize                                offset,\n    VkBuffer                                    countBuffer,\n    VkDeviceSize                                countBufferOffset,\n    uint32_t                                    maxDrawCount,\n    uint32_t                                    stride);\n\nVKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexedIndirectCountAMD(\n    VkCommandBuffer                             commandBuffer,\n    VkBuffer                                    buffer,\n    VkDeviceSize                                offset,\n    VkBuffer                                    countBuffer,\n    VkDeviceSize                                countBufferOffset,\n    uint32_t                                    maxDrawCount,\n    uint32_t                                    stride);\n#endif\n\n#define VK_AMD_negative_viewport_height 1\n#define VK_AMD_NEGATIVE_VIEWPORT_HEIGHT_SPEC_VERSION 0\n#define VK_AMD_NEGATIVE_VIEWPORT_HEIGHT_EXTENSION_NAME \"VK_AMD_negative_viewport_height\"\n\n\n#define VK_AMD_gpu_shader_half_float 1\n#define VK_AMD_GPU_SHADER_HALF_FLOAT_SPEC_VERSION 1\n#define VK_AMD_GPU_SHADER_HALF_FLOAT_EXTENSION_NAME \"VK_AMD_gpu_shader_half_float\"\n\n\n#define VK_AMD_shader_ballot 1\n#define VK_AMD_SHADER_BALLOT_SPEC_VERSION 0\n#define VK_AMD_SHADER_BALLOT_EXTENSION_NAME \"VK_AMD_shader_ballot\"\n\n\n#define VK_IMG_format_pvrtc 1\n#define VK_IMG_FORMAT_PVRTC_SPEC_VERSION  1\n#define VK_IMG_FORMAT_PVRTC_EXTENSION_NAME \"VK_IMG_format_pvrtc\"\n\n\n#define VK_NV_external_memory_capabilities 1\n#define VK_NV_EXTERNAL_MEMORY_CAPABILITIES_SPEC_VERSION 1\n#define VK_NV_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME \"VK_NV_external_memory_capabilities\"\n\n\ntypedef enum VkExternalMemoryHandleTypeFlagBitsNV {\n    VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT_NV = 0x00000001,\n    VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT_NV = 0x00000002,\n    VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_IMAGE_BIT_NV = 0x00000004,\n    VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_IMAGE_KMT_BIT_NV = 0x00000008,\n    VK_EXTERNAL_MEMORY_HANDLE_TYPE_FLAG_BITS_MAX_ENUM_NV = 0x7FFFFFFF\n} VkExternalMemoryHandleTypeFlagBitsNV;\ntypedef VkFlags VkExternalMemoryHandleTypeFlagsNV;\n\ntypedef enum VkExternalMemoryFeatureFlagBitsNV {\n    VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT_NV = 0x00000001,\n    VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT_NV = 0x00000002,\n    VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT_NV = 0x00000004,\n    VK_EXTERNAL_MEMORY_FEATURE_FLAG_BITS_MAX_ENUM_NV = 0x7FFFFFFF\n} VkExternalMemoryFeatureFlagBitsNV;\ntypedef VkFlags VkExternalMemoryFeatureFlagsNV;\n\ntypedef struct VkExternalImageFormatPropertiesNV {\n    VkImageFormatProperties              imageFormatProperties;\n    VkExternalMemoryFeatureFlagsNV       externalMemoryFeatures;\n    VkExternalMemoryHandleTypeFlagsNV    exportFromImportedHandleTypes;\n    VkExternalMemoryHandleTypeFlagsNV    compatibleHandleTypes;\n} VkExternalImageFormatPropertiesNV;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalImageFormatPropertiesNV)(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkImageTiling tiling, VkImageUsageFlags usage, VkImageCreateFlags flags, VkExternalMemoryHandleTypeFlagsNV externalHandleType, VkExternalImageFormatPropertiesNV* pExternalImageFormatProperties);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceExternalImageFormatPropertiesNV(\n    VkPhysicalDevice                            physicalDevice,\n    VkFormat                                    format,\n    VkImageType                                 type,\n    VkImageTiling                               tiling,\n    VkImageUsageFlags                           usage,\n    VkImageCreateFlags                          flags,\n    VkExternalMemoryHandleTypeFlagsNV           externalHandleType,\n    VkExternalImageFormatPropertiesNV*          pExternalImageFormatProperties);\n#endif\n\n#define VK_NV_external_memory 1\n#define VK_NV_EXTERNAL_MEMORY_SPEC_VERSION 1\n#define VK_NV_EXTERNAL_MEMORY_EXTENSION_NAME \"VK_NV_external_memory\"\n\ntypedef struct VkExternalMemoryImageCreateInfoNV {\n    VkStructureType                      sType;\n    const void*                          pNext;\n    VkExternalMemoryHandleTypeFlagsNV    handleTypes;\n} VkExternalMemoryImageCreateInfoNV;\n\ntypedef struct VkExportMemoryAllocateInfoNV {\n    VkStructureType                      sType;\n    const void*                          pNext;\n    VkExternalMemoryHandleTypeFlagsNV    handleTypes;\n} VkExportMemoryAllocateInfoNV;\n\n\n\n#ifdef VK_USE_PLATFORM_WIN32_KHR\n#define VK_NV_external_memory_win32 1\n#define VK_NV_EXTERNAL_MEMORY_WIN32_SPEC_VERSION 1\n#define VK_NV_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME \"VK_NV_external_memory_win32\"\n\ntypedef struct VkImportMemoryWin32HandleInfoNV {\n    VkStructureType                      sType;\n    const void*                          pNext;\n    VkExternalMemoryHandleTypeFlagsNV    handleType;\n    HANDLE                               handle;\n} VkImportMemoryWin32HandleInfoNV;\n\ntypedef struct VkExportMemoryWin32HandleInfoNV {\n    VkStructureType               sType;\n    const void*                   pNext;\n    const SECURITY_ATTRIBUTES*    pAttributes;\n    DWORD                         dwAccess;\n} VkExportMemoryWin32HandleInfoNV;\n\n\ntypedef VkResult (VKAPI_PTR *PFN_vkGetMemoryWin32HandleNV)(VkDevice device, VkDeviceMemory memory, VkExternalMemoryHandleTypeFlagsNV handleType, HANDLE* pHandle);\n\n#ifndef VK_NO_PROTOTYPES\nVKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryWin32HandleNV(\n    VkDevice                                    device,\n    VkDeviceMemory                              memory,\n    VkExternalMemoryHandleTypeFlagsNV           handleType,\n    HANDLE*                                     pHandle);\n#endif\n#endif /* VK_USE_PLATFORM_WIN32_KHR */\n\n#ifdef VK_USE_PLATFORM_WIN32_KHR\n#define VK_NV_win32_keyed_mutex 1\n#define VK_NV_WIN32_KEYED_MUTEX_SPEC_VERSION 1\n#define VK_NV_WIN32_KEYED_MUTEX_EXTENSION_NAME \"VK_NV_win32_keyed_mutex\"\n\ntypedef struct VkWin32KeyedMutexAcquireReleaseInfoNV {\n    VkStructureType          sType;\n    const void*              pNext;\n    uint32_t                 acquireCount;\n    const VkDeviceMemory*    pAcquireSyncs;\n    const uint64_t*          pAcquireKeys;\n    const uint32_t*          pAcquireTimeoutMilliseconds;\n    uint32_t                 releaseCount;\n    const VkDeviceMemory*    pReleaseSyncs;\n    const uint64_t*          pReleaseKeys;\n} VkWin32KeyedMutexAcquireReleaseInfoNV;\n\n\n#endif /* VK_USE_PLATFORM_WIN32_KHR */\n\n#define VK_EXT_validation_flags 1\n#define VK_EXT_VALIDATION_FLAGS_SPEC_VERSION 1\n#define VK_EXT_VALIDATION_FLAGS_EXTENSION_NAME \"VK_EXT_validation_flags\"\n\n\ntypedef enum VkValidationCheckEXT {\n    VK_VALIDATION_CHECK_ALL_EXT = 0,\n    VK_VALIDATION_CHECK_BEGIN_RANGE_EXT = VK_VALIDATION_CHECK_ALL_EXT,\n    VK_VALIDATION_CHECK_END_RANGE_EXT = VK_VALIDATION_CHECK_ALL_EXT,\n    VK_VALIDATION_CHECK_RANGE_SIZE_EXT = (VK_VALIDATION_CHECK_ALL_EXT - VK_VALIDATION_CHECK_ALL_EXT + 1),\n    VK_VALIDATION_CHECK_MAX_ENUM_EXT = 0x7FFFFFFF\n} VkValidationCheckEXT;\n\ntypedef struct VkValidationFlagsEXT {\n    VkStructureType          sType;\n    const void*              pNext;\n    uint32_t                 disabledValidationCheckCount;\n    VkValidationCheckEXT*    pDisabledValidationChecks;\n} VkValidationFlagsEXT;\n\n\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "caffe2/mobile/contrib/libvulkan-stub/src/libvulkan-stub.c",
    "content": "/*\n* Copyright (c) 2016-2017, ARM Limited and Contributors\n*\n* SPDX-License-Identifier: MIT\n*\n* Permission is hereby granted, free of charge,\n* to any person obtaining a copy of this software and associated documentation files (the \"Software\"),\n* to deal in the Software without restriction, including without limitation the rights to\n* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,\n* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n*/\n\n/* This header is autogenerated by vulkan_loader_generator.py */\n#include \"libvulkan-stub.h\"\n\nPFN_vkCreateInstance vulkanSymbolWrapper_vkCreateInstance;\nPFN_vkEnumerateInstanceExtensionProperties vulkanSymbolWrapper_vkEnumerateInstanceExtensionProperties;\nPFN_vkEnumerateInstanceLayerProperties vulkanSymbolWrapper_vkEnumerateInstanceLayerProperties;\nPFN_vkDestroyInstance vulkanSymbolWrapper_vkDestroyInstance;\nPFN_vkEnumeratePhysicalDevices vulkanSymbolWrapper_vkEnumeratePhysicalDevices;\nPFN_vkGetPhysicalDeviceFeatures vulkanSymbolWrapper_vkGetPhysicalDeviceFeatures;\nPFN_vkGetPhysicalDeviceFormatProperties vulkanSymbolWrapper_vkGetPhysicalDeviceFormatProperties;\nPFN_vkGetPhysicalDeviceImageFormatProperties vulkanSymbolWrapper_vkGetPhysicalDeviceImageFormatProperties;\nPFN_vkGetPhysicalDeviceProperties vulkanSymbolWrapper_vkGetPhysicalDeviceProperties;\nPFN_vkGetPhysicalDeviceQueueFamilyProperties vulkanSymbolWrapper_vkGetPhysicalDeviceQueueFamilyProperties;\nPFN_vkGetPhysicalDeviceMemoryProperties vulkanSymbolWrapper_vkGetPhysicalDeviceMemoryProperties;\nPFN_vkGetDeviceProcAddr vulkanSymbolWrapper_vkGetDeviceProcAddr;\nPFN_vkCreateDevice vulkanSymbolWrapper_vkCreateDevice;\nPFN_vkDestroyDevice vulkanSymbolWrapper_vkDestroyDevice;\nPFN_vkEnumerateDeviceExtensionProperties vulkanSymbolWrapper_vkEnumerateDeviceExtensionProperties;\nPFN_vkEnumerateDeviceLayerProperties vulkanSymbolWrapper_vkEnumerateDeviceLayerProperties;\nPFN_vkGetDeviceQueue vulkanSymbolWrapper_vkGetDeviceQueue;\nPFN_vkQueueSubmit vulkanSymbolWrapper_vkQueueSubmit;\nPFN_vkQueueWaitIdle vulkanSymbolWrapper_vkQueueWaitIdle;\nPFN_vkDeviceWaitIdle vulkanSymbolWrapper_vkDeviceWaitIdle;\nPFN_vkAllocateMemory vulkanSymbolWrapper_vkAllocateMemory;\nPFN_vkFreeMemory vulkanSymbolWrapper_vkFreeMemory;\nPFN_vkMapMemory vulkanSymbolWrapper_vkMapMemory;\nPFN_vkUnmapMemory vulkanSymbolWrapper_vkUnmapMemory;\nPFN_vkFlushMappedMemoryRanges vulkanSymbolWrapper_vkFlushMappedMemoryRanges;\nPFN_vkInvalidateMappedMemoryRanges vulkanSymbolWrapper_vkInvalidateMappedMemoryRanges;\nPFN_vkGetDeviceMemoryCommitment vulkanSymbolWrapper_vkGetDeviceMemoryCommitment;\nPFN_vkBindBufferMemory vulkanSymbolWrapper_vkBindBufferMemory;\nPFN_vkBindImageMemory vulkanSymbolWrapper_vkBindImageMemory;\nPFN_vkGetBufferMemoryRequirements vulkanSymbolWrapper_vkGetBufferMemoryRequirements;\nPFN_vkGetImageMemoryRequirements vulkanSymbolWrapper_vkGetImageMemoryRequirements;\nPFN_vkGetImageSparseMemoryRequirements vulkanSymbolWrapper_vkGetImageSparseMemoryRequirements;\nPFN_vkGetPhysicalDeviceSparseImageFormatProperties vulkanSymbolWrapper_vkGetPhysicalDeviceSparseImageFormatProperties;\nPFN_vkQueueBindSparse vulkanSymbolWrapper_vkQueueBindSparse;\nPFN_vkCreateFence vulkanSymbolWrapper_vkCreateFence;\nPFN_vkDestroyFence vulkanSymbolWrapper_vkDestroyFence;\nPFN_vkResetFences vulkanSymbolWrapper_vkResetFences;\nPFN_vkGetFenceStatus vulkanSymbolWrapper_vkGetFenceStatus;\nPFN_vkWaitForFences vulkanSymbolWrapper_vkWaitForFences;\nPFN_vkCreateSemaphore vulkanSymbolWrapper_vkCreateSemaphore;\nPFN_vkDestroySemaphore vulkanSymbolWrapper_vkDestroySemaphore;\nPFN_vkCreateEvent vulkanSymbolWrapper_vkCreateEvent;\nPFN_vkDestroyEvent vulkanSymbolWrapper_vkDestroyEvent;\nPFN_vkGetEventStatus vulkanSymbolWrapper_vkGetEventStatus;\nPFN_vkSetEvent vulkanSymbolWrapper_vkSetEvent;\nPFN_vkResetEvent vulkanSymbolWrapper_vkResetEvent;\nPFN_vkCreateQueryPool vulkanSymbolWrapper_vkCreateQueryPool;\nPFN_vkDestroyQueryPool vulkanSymbolWrapper_vkDestroyQueryPool;\nPFN_vkGetQueryPoolResults vulkanSymbolWrapper_vkGetQueryPoolResults;\nPFN_vkCreateBuffer vulkanSymbolWrapper_vkCreateBuffer;\nPFN_vkDestroyBuffer vulkanSymbolWrapper_vkDestroyBuffer;\nPFN_vkCreateBufferView vulkanSymbolWrapper_vkCreateBufferView;\nPFN_vkDestroyBufferView vulkanSymbolWrapper_vkDestroyBufferView;\nPFN_vkCreateImage vulkanSymbolWrapper_vkCreateImage;\nPFN_vkDestroyImage vulkanSymbolWrapper_vkDestroyImage;\nPFN_vkGetImageSubresourceLayout vulkanSymbolWrapper_vkGetImageSubresourceLayout;\nPFN_vkCreateImageView vulkanSymbolWrapper_vkCreateImageView;\nPFN_vkDestroyImageView vulkanSymbolWrapper_vkDestroyImageView;\nPFN_vkCreateShaderModule vulkanSymbolWrapper_vkCreateShaderModule;\nPFN_vkDestroyShaderModule vulkanSymbolWrapper_vkDestroyShaderModule;\nPFN_vkCreatePipelineCache vulkanSymbolWrapper_vkCreatePipelineCache;\nPFN_vkDestroyPipelineCache vulkanSymbolWrapper_vkDestroyPipelineCache;\nPFN_vkGetPipelineCacheData vulkanSymbolWrapper_vkGetPipelineCacheData;\nPFN_vkMergePipelineCaches vulkanSymbolWrapper_vkMergePipelineCaches;\nPFN_vkCreateGraphicsPipelines vulkanSymbolWrapper_vkCreateGraphicsPipelines;\nPFN_vkCreateComputePipelines vulkanSymbolWrapper_vkCreateComputePipelines;\nPFN_vkDestroyPipeline vulkanSymbolWrapper_vkDestroyPipeline;\nPFN_vkCreatePipelineLayout vulkanSymbolWrapper_vkCreatePipelineLayout;\nPFN_vkDestroyPipelineLayout vulkanSymbolWrapper_vkDestroyPipelineLayout;\nPFN_vkCreateSampler vulkanSymbolWrapper_vkCreateSampler;\nPFN_vkDestroySampler vulkanSymbolWrapper_vkDestroySampler;\nPFN_vkCreateDescriptorSetLayout vulkanSymbolWrapper_vkCreateDescriptorSetLayout;\nPFN_vkDestroyDescriptorSetLayout vulkanSymbolWrapper_vkDestroyDescriptorSetLayout;\nPFN_vkCreateDescriptorPool vulkanSymbolWrapper_vkCreateDescriptorPool;\nPFN_vkDestroyDescriptorPool vulkanSymbolWrapper_vkDestroyDescriptorPool;\nPFN_vkResetDescriptorPool vulkanSymbolWrapper_vkResetDescriptorPool;\nPFN_vkAllocateDescriptorSets vulkanSymbolWrapper_vkAllocateDescriptorSets;\nPFN_vkFreeDescriptorSets vulkanSymbolWrapper_vkFreeDescriptorSets;\nPFN_vkUpdateDescriptorSets vulkanSymbolWrapper_vkUpdateDescriptorSets;\nPFN_vkCreateFramebuffer vulkanSymbolWrapper_vkCreateFramebuffer;\nPFN_vkDestroyFramebuffer vulkanSymbolWrapper_vkDestroyFramebuffer;\nPFN_vkCreateRenderPass vulkanSymbolWrapper_vkCreateRenderPass;\nPFN_vkDestroyRenderPass vulkanSymbolWrapper_vkDestroyRenderPass;\nPFN_vkGetRenderAreaGranularity vulkanSymbolWrapper_vkGetRenderAreaGranularity;\nPFN_vkCreateCommandPool vulkanSymbolWrapper_vkCreateCommandPool;\nPFN_vkDestroyCommandPool vulkanSymbolWrapper_vkDestroyCommandPool;\nPFN_vkResetCommandPool vulkanSymbolWrapper_vkResetCommandPool;\nPFN_vkAllocateCommandBuffers vulkanSymbolWrapper_vkAllocateCommandBuffers;\nPFN_vkFreeCommandBuffers vulkanSymbolWrapper_vkFreeCommandBuffers;\nPFN_vkBeginCommandBuffer vulkanSymbolWrapper_vkBeginCommandBuffer;\nPFN_vkEndCommandBuffer vulkanSymbolWrapper_vkEndCommandBuffer;\nPFN_vkResetCommandBuffer vulkanSymbolWrapper_vkResetCommandBuffer;\nPFN_vkCmdBindPipeline vulkanSymbolWrapper_vkCmdBindPipeline;\nPFN_vkCmdSetViewport vulkanSymbolWrapper_vkCmdSetViewport;\nPFN_vkCmdSetScissor vulkanSymbolWrapper_vkCmdSetScissor;\nPFN_vkCmdSetLineWidth vulkanSymbolWrapper_vkCmdSetLineWidth;\nPFN_vkCmdSetDepthBias vulkanSymbolWrapper_vkCmdSetDepthBias;\nPFN_vkCmdSetBlendConstants vulkanSymbolWrapper_vkCmdSetBlendConstants;\nPFN_vkCmdSetDepthBounds vulkanSymbolWrapper_vkCmdSetDepthBounds;\nPFN_vkCmdSetStencilCompareMask vulkanSymbolWrapper_vkCmdSetStencilCompareMask;\nPFN_vkCmdSetStencilWriteMask vulkanSymbolWrapper_vkCmdSetStencilWriteMask;\nPFN_vkCmdSetStencilReference vulkanSymbolWrapper_vkCmdSetStencilReference;\nPFN_vkCmdBindDescriptorSets vulkanSymbolWrapper_vkCmdBindDescriptorSets;\nPFN_vkCmdBindIndexBuffer vulkanSymbolWrapper_vkCmdBindIndexBuffer;\nPFN_vkCmdBindVertexBuffers vulkanSymbolWrapper_vkCmdBindVertexBuffers;\nPFN_vkCmdDraw vulkanSymbolWrapper_vkCmdDraw;\nPFN_vkCmdDrawIndexed vulkanSymbolWrapper_vkCmdDrawIndexed;\nPFN_vkCmdDrawIndirect vulkanSymbolWrapper_vkCmdDrawIndirect;\nPFN_vkCmdDrawIndexedIndirect vulkanSymbolWrapper_vkCmdDrawIndexedIndirect;\nPFN_vkCmdDispatch vulkanSymbolWrapper_vkCmdDispatch;\nPFN_vkCmdDispatchIndirect vulkanSymbolWrapper_vkCmdDispatchIndirect;\nPFN_vkCmdCopyBuffer vulkanSymbolWrapper_vkCmdCopyBuffer;\nPFN_vkCmdCopyImage vulkanSymbolWrapper_vkCmdCopyImage;\nPFN_vkCmdBlitImage vulkanSymbolWrapper_vkCmdBlitImage;\nPFN_vkCmdCopyBufferToImage vulkanSymbolWrapper_vkCmdCopyBufferToImage;\nPFN_vkCmdCopyImageToBuffer vulkanSymbolWrapper_vkCmdCopyImageToBuffer;\nPFN_vkCmdUpdateBuffer vulkanSymbolWrapper_vkCmdUpdateBuffer;\nPFN_vkCmdFillBuffer vulkanSymbolWrapper_vkCmdFillBuffer;\nPFN_vkCmdClearColorImage vulkanSymbolWrapper_vkCmdClearColorImage;\nPFN_vkCmdClearDepthStencilImage vulkanSymbolWrapper_vkCmdClearDepthStencilImage;\nPFN_vkCmdClearAttachments vulkanSymbolWrapper_vkCmdClearAttachments;\nPFN_vkCmdResolveImage vulkanSymbolWrapper_vkCmdResolveImage;\nPFN_vkCmdSetEvent vulkanSymbolWrapper_vkCmdSetEvent;\nPFN_vkCmdResetEvent vulkanSymbolWrapper_vkCmdResetEvent;\nPFN_vkCmdWaitEvents vulkanSymbolWrapper_vkCmdWaitEvents;\nPFN_vkCmdPipelineBarrier vulkanSymbolWrapper_vkCmdPipelineBarrier;\nPFN_vkCmdBeginQuery vulkanSymbolWrapper_vkCmdBeginQuery;\nPFN_vkCmdEndQuery vulkanSymbolWrapper_vkCmdEndQuery;\nPFN_vkCmdResetQueryPool vulkanSymbolWrapper_vkCmdResetQueryPool;\nPFN_vkCmdWriteTimestamp vulkanSymbolWrapper_vkCmdWriteTimestamp;\nPFN_vkCmdCopyQueryPoolResults vulkanSymbolWrapper_vkCmdCopyQueryPoolResults;\nPFN_vkCmdPushConstants vulkanSymbolWrapper_vkCmdPushConstants;\nPFN_vkCmdBeginRenderPass vulkanSymbolWrapper_vkCmdBeginRenderPass;\nPFN_vkCmdNextSubpass vulkanSymbolWrapper_vkCmdNextSubpass;\nPFN_vkCmdEndRenderPass vulkanSymbolWrapper_vkCmdEndRenderPass;\nPFN_vkCmdExecuteCommands vulkanSymbolWrapper_vkCmdExecuteCommands;\nPFN_vkDestroySurfaceKHR vulkanSymbolWrapper_vkDestroySurfaceKHR;\nPFN_vkGetPhysicalDeviceSurfaceSupportKHR vulkanSymbolWrapper_vkGetPhysicalDeviceSurfaceSupportKHR;\nPFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vulkanSymbolWrapper_vkGetPhysicalDeviceSurfaceCapabilitiesKHR;\nPFN_vkGetPhysicalDeviceSurfaceFormatsKHR vulkanSymbolWrapper_vkGetPhysicalDeviceSurfaceFormatsKHR;\nPFN_vkGetPhysicalDeviceSurfacePresentModesKHR vulkanSymbolWrapper_vkGetPhysicalDeviceSurfacePresentModesKHR;\nPFN_vkCreateSwapchainKHR vulkanSymbolWrapper_vkCreateSwapchainKHR;\nPFN_vkDestroySwapchainKHR vulkanSymbolWrapper_vkDestroySwapchainKHR;\nPFN_vkGetSwapchainImagesKHR vulkanSymbolWrapper_vkGetSwapchainImagesKHR;\nPFN_vkAcquireNextImageKHR vulkanSymbolWrapper_vkAcquireNextImageKHR;\nPFN_vkQueuePresentKHR vulkanSymbolWrapper_vkQueuePresentKHR;\nPFN_vkGetPhysicalDeviceDisplayPropertiesKHR vulkanSymbolWrapper_vkGetPhysicalDeviceDisplayPropertiesKHR;\nPFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vulkanSymbolWrapper_vkGetPhysicalDeviceDisplayPlanePropertiesKHR;\nPFN_vkGetDisplayPlaneSupportedDisplaysKHR vulkanSymbolWrapper_vkGetDisplayPlaneSupportedDisplaysKHR;\nPFN_vkGetDisplayModePropertiesKHR vulkanSymbolWrapper_vkGetDisplayModePropertiesKHR;\nPFN_vkCreateDisplayModeKHR vulkanSymbolWrapper_vkCreateDisplayModeKHR;\nPFN_vkGetDisplayPlaneCapabilitiesKHR vulkanSymbolWrapper_vkGetDisplayPlaneCapabilitiesKHR;\nPFN_vkCreateDisplayPlaneSurfaceKHR vulkanSymbolWrapper_vkCreateDisplayPlaneSurfaceKHR;\nPFN_vkCreateSharedSwapchainsKHR vulkanSymbolWrapper_vkCreateSharedSwapchainsKHR;\nPFN_vkCreateDebugReportCallbackEXT vulkanSymbolWrapper_vkCreateDebugReportCallbackEXT;\nPFN_vkDestroyDebugReportCallbackEXT vulkanSymbolWrapper_vkDestroyDebugReportCallbackEXT;\nPFN_vkDebugReportMessageEXT vulkanSymbolWrapper_vkDebugReportMessageEXT;\nPFN_vkDebugMarkerSetObjectTagEXT vulkanSymbolWrapper_vkDebugMarkerSetObjectTagEXT;\nPFN_vkDebugMarkerSetObjectNameEXT vulkanSymbolWrapper_vkDebugMarkerSetObjectNameEXT;\nPFN_vkCmdDebugMarkerBeginEXT vulkanSymbolWrapper_vkCmdDebugMarkerBeginEXT;\nPFN_vkCmdDebugMarkerEndEXT vulkanSymbolWrapper_vkCmdDebugMarkerEndEXT;\nPFN_vkCmdDebugMarkerInsertEXT vulkanSymbolWrapper_vkCmdDebugMarkerInsertEXT;\nPFN_vkCmdDrawIndirectCountAMD vulkanSymbolWrapper_vkCmdDrawIndirectCountAMD;\nPFN_vkCmdDrawIndexedIndirectCountAMD vulkanSymbolWrapper_vkCmdDrawIndexedIndirectCountAMD;\nPFN_vkGetPhysicalDeviceExternalImageFormatPropertiesNV vulkanSymbolWrapper_vkGetPhysicalDeviceExternalImageFormatPropertiesNV;\n\n#ifndef _WIN32\n#include <dlfcn.h>\nstatic void *dylib;\n#endif\n\nVkBool32 vulkanSymbolWrapperInitLoader(void)\n{\n#ifndef _WIN32\n  if (!dylib)\n  {\n    dylib = dlopen(\"libvulkan.so\", RTLD_LOCAL | RTLD_NOW);\n  }\n\n  if (dylib)\n  {\n    PFN_vkGetInstanceProcAddr gpa = (PFN_vkGetInstanceProcAddr)(dlsym(dylib, \"vkGetInstanceProcAddr\"));\n    if (gpa)\n    {\n      vulkanSymbolWrapperInit(gpa);\n    }\n    else\n    {\n      return VK_FALSE;\n    }\n  }\n\n  return dylib != NULL ? VK_TRUE : VK_FALSE;\n#else\n  #error \"Platform-specific implementation required\"\n#endif\n}\n\nstatic PFN_vkGetInstanceProcAddr GetInstanceProcAddr;\nvoid vulkanSymbolWrapperInit(PFN_vkGetInstanceProcAddr getInstanceProcAddr)\n{\n    GetInstanceProcAddr = getInstanceProcAddr;\n}\n\nPFN_vkGetInstanceProcAddr vulkanSymbolWrapperInstanceProcAddr(void)\n{\n    return GetInstanceProcAddr;\n}\n\nvoid vulkanSymbolWrapperReset(void)\n{\n#ifndef _WIN32\n  if (dylib) {\n    dlclose(dylib);\n  }\n  dylib = NULL;\n#else\n  #error \"Platform-specific implementation required\"\n#endif\n  GetInstanceProcAddr = NULL;\n}\n\n\nVkBool32 vulkanSymbolWrapperLoadInstanceSymbol(VkInstance instance, const char *name, PFN_vkVoidFunction *ppSymbol)\n{\n    *ppSymbol = GetInstanceProcAddr(instance, name);\n    return *ppSymbol != NULL;\n}\n\nVkBool32 vulkanSymbolWrapperLoadDeviceSymbol(VkDevice device, const char *name, PFN_vkVoidFunction *ppSymbol)\n{\n    *ppSymbol = vkGetDeviceProcAddr(device, name);\n    return *ppSymbol != NULL;\n}\n\nVkBool32 vulkanSymbolWrapperLoadGlobalSymbols(void)\n{\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(NULL, \"vkCreateInstance\", vkCreateInstance)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(NULL, \"vkEnumerateInstanceExtensionProperties\", vkEnumerateInstanceExtensionProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(NULL, \"vkEnumerateInstanceLayerProperties\", vkEnumerateInstanceLayerProperties)) return VK_FALSE;\n    return VK_TRUE;\n}\n\nVkBool32 vulkanSymbolWrapperLoadCoreSymbols(VkInstance instance)\n{\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyInstance\", vkDestroyInstance)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkEnumeratePhysicalDevices\", vkEnumeratePhysicalDevices)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceFeatures\", vkGetPhysicalDeviceFeatures)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceFormatProperties\", vkGetPhysicalDeviceFormatProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceImageFormatProperties\", vkGetPhysicalDeviceImageFormatProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceProperties\", vkGetPhysicalDeviceProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceQueueFamilyProperties\", vkGetPhysicalDeviceQueueFamilyProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceMemoryProperties\", vkGetPhysicalDeviceMemoryProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetDeviceProcAddr\", vkGetDeviceProcAddr)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateDevice\", vkCreateDevice)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyDevice\", vkDestroyDevice)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkEnumerateDeviceExtensionProperties\", vkEnumerateDeviceExtensionProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkEnumerateDeviceLayerProperties\", vkEnumerateDeviceLayerProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetDeviceQueue\", vkGetDeviceQueue)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkQueueSubmit\", vkQueueSubmit)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkQueueWaitIdle\", vkQueueWaitIdle)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDeviceWaitIdle\", vkDeviceWaitIdle)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkAllocateMemory\", vkAllocateMemory)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkFreeMemory\", vkFreeMemory)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkMapMemory\", vkMapMemory)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkUnmapMemory\", vkUnmapMemory)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkFlushMappedMemoryRanges\", vkFlushMappedMemoryRanges)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkInvalidateMappedMemoryRanges\", vkInvalidateMappedMemoryRanges)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetDeviceMemoryCommitment\", vkGetDeviceMemoryCommitment)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkBindBufferMemory\", vkBindBufferMemory)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkBindImageMemory\", vkBindImageMemory)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetBufferMemoryRequirements\", vkGetBufferMemoryRequirements)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetImageMemoryRequirements\", vkGetImageMemoryRequirements)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetImageSparseMemoryRequirements\", vkGetImageSparseMemoryRequirements)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceSparseImageFormatProperties\", vkGetPhysicalDeviceSparseImageFormatProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkQueueBindSparse\", vkQueueBindSparse)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateFence\", vkCreateFence)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyFence\", vkDestroyFence)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkResetFences\", vkResetFences)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetFenceStatus\", vkGetFenceStatus)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkWaitForFences\", vkWaitForFences)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateSemaphore\", vkCreateSemaphore)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroySemaphore\", vkDestroySemaphore)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateEvent\", vkCreateEvent)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyEvent\", vkDestroyEvent)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetEventStatus\", vkGetEventStatus)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkSetEvent\", vkSetEvent)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkResetEvent\", vkResetEvent)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateQueryPool\", vkCreateQueryPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyQueryPool\", vkDestroyQueryPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetQueryPoolResults\", vkGetQueryPoolResults)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateBuffer\", vkCreateBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyBuffer\", vkDestroyBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateBufferView\", vkCreateBufferView)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyBufferView\", vkDestroyBufferView)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateImage\", vkCreateImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyImage\", vkDestroyImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetImageSubresourceLayout\", vkGetImageSubresourceLayout)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateImageView\", vkCreateImageView)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyImageView\", vkDestroyImageView)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateShaderModule\", vkCreateShaderModule)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyShaderModule\", vkDestroyShaderModule)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreatePipelineCache\", vkCreatePipelineCache)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyPipelineCache\", vkDestroyPipelineCache)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPipelineCacheData\", vkGetPipelineCacheData)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkMergePipelineCaches\", vkMergePipelineCaches)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateGraphicsPipelines\", vkCreateGraphicsPipelines)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateComputePipelines\", vkCreateComputePipelines)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyPipeline\", vkDestroyPipeline)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreatePipelineLayout\", vkCreatePipelineLayout)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyPipelineLayout\", vkDestroyPipelineLayout)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateSampler\", vkCreateSampler)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroySampler\", vkDestroySampler)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateDescriptorSetLayout\", vkCreateDescriptorSetLayout)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyDescriptorSetLayout\", vkDestroyDescriptorSetLayout)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateDescriptorPool\", vkCreateDescriptorPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyDescriptorPool\", vkDestroyDescriptorPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkResetDescriptorPool\", vkResetDescriptorPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkAllocateDescriptorSets\", vkAllocateDescriptorSets)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkFreeDescriptorSets\", vkFreeDescriptorSets)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkUpdateDescriptorSets\", vkUpdateDescriptorSets)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateFramebuffer\", vkCreateFramebuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyFramebuffer\", vkDestroyFramebuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateRenderPass\", vkCreateRenderPass)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyRenderPass\", vkDestroyRenderPass)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetRenderAreaGranularity\", vkGetRenderAreaGranularity)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateCommandPool\", vkCreateCommandPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyCommandPool\", vkDestroyCommandPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkResetCommandPool\", vkResetCommandPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkAllocateCommandBuffers\", vkAllocateCommandBuffers)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkFreeCommandBuffers\", vkFreeCommandBuffers)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkBeginCommandBuffer\", vkBeginCommandBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkEndCommandBuffer\", vkEndCommandBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkResetCommandBuffer\", vkResetCommandBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdBindPipeline\", vkCmdBindPipeline)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdSetViewport\", vkCmdSetViewport)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdSetScissor\", vkCmdSetScissor)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdSetLineWidth\", vkCmdSetLineWidth)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdSetDepthBias\", vkCmdSetDepthBias)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdSetBlendConstants\", vkCmdSetBlendConstants)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdSetDepthBounds\", vkCmdSetDepthBounds)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdSetStencilCompareMask\", vkCmdSetStencilCompareMask)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdSetStencilWriteMask\", vkCmdSetStencilWriteMask)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdSetStencilReference\", vkCmdSetStencilReference)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdBindDescriptorSets\", vkCmdBindDescriptorSets)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdBindIndexBuffer\", vkCmdBindIndexBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdBindVertexBuffers\", vkCmdBindVertexBuffers)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdDraw\", vkCmdDraw)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdDrawIndexed\", vkCmdDrawIndexed)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdDrawIndirect\", vkCmdDrawIndirect)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdDrawIndexedIndirect\", vkCmdDrawIndexedIndirect)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdDispatch\", vkCmdDispatch)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdDispatchIndirect\", vkCmdDispatchIndirect)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdCopyBuffer\", vkCmdCopyBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdCopyImage\", vkCmdCopyImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdBlitImage\", vkCmdBlitImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdCopyBufferToImage\", vkCmdCopyBufferToImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdCopyImageToBuffer\", vkCmdCopyImageToBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdUpdateBuffer\", vkCmdUpdateBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdFillBuffer\", vkCmdFillBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdClearColorImage\", vkCmdClearColorImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdClearDepthStencilImage\", vkCmdClearDepthStencilImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdClearAttachments\", vkCmdClearAttachments)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdResolveImage\", vkCmdResolveImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdSetEvent\", vkCmdSetEvent)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdResetEvent\", vkCmdResetEvent)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdWaitEvents\", vkCmdWaitEvents)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdPipelineBarrier\", vkCmdPipelineBarrier)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdBeginQuery\", vkCmdBeginQuery)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdEndQuery\", vkCmdEndQuery)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdResetQueryPool\", vkCmdResetQueryPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdWriteTimestamp\", vkCmdWriteTimestamp)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdCopyQueryPoolResults\", vkCmdCopyQueryPoolResults)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdPushConstants\", vkCmdPushConstants)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdBeginRenderPass\", vkCmdBeginRenderPass)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdNextSubpass\", vkCmdNextSubpass)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdEndRenderPass\", vkCmdEndRenderPass)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCmdExecuteCommands\", vkCmdExecuteCommands)) return VK_FALSE;\n    return VK_TRUE;\n}\n\nVkBool32 vulkanSymbolWrapperLoadCoreInstanceSymbols(VkInstance instance)\n{\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkDestroyInstance\", vkDestroyInstance)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkEnumeratePhysicalDevices\", vkEnumeratePhysicalDevices)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceFeatures\", vkGetPhysicalDeviceFeatures)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceFormatProperties\", vkGetPhysicalDeviceFormatProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceImageFormatProperties\", vkGetPhysicalDeviceImageFormatProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceProperties\", vkGetPhysicalDeviceProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceQueueFamilyProperties\", vkGetPhysicalDeviceQueueFamilyProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceMemoryProperties\", vkGetPhysicalDeviceMemoryProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetDeviceProcAddr\", vkGetDeviceProcAddr)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkCreateDevice\", vkCreateDevice)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkEnumerateDeviceExtensionProperties\", vkEnumerateDeviceExtensionProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkEnumerateDeviceLayerProperties\", vkEnumerateDeviceLayerProperties)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, \"vkGetPhysicalDeviceSparseImageFormatProperties\", vkGetPhysicalDeviceSparseImageFormatProperties)) return VK_FALSE;\n    return VK_TRUE;\n}\n\nVkBool32 vulkanSymbolWrapperLoadCoreDeviceSymbols(VkDevice device)\n{\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyDevice\", vkDestroyDevice)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkGetDeviceQueue\", vkGetDeviceQueue)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkQueueSubmit\", vkQueueSubmit)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkQueueWaitIdle\", vkQueueWaitIdle)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDeviceWaitIdle\", vkDeviceWaitIdle)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkAllocateMemory\", vkAllocateMemory)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkFreeMemory\", vkFreeMemory)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkMapMemory\", vkMapMemory)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkUnmapMemory\", vkUnmapMemory)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkFlushMappedMemoryRanges\", vkFlushMappedMemoryRanges)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkInvalidateMappedMemoryRanges\", vkInvalidateMappedMemoryRanges)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkGetDeviceMemoryCommitment\", vkGetDeviceMemoryCommitment)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkBindBufferMemory\", vkBindBufferMemory)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkBindImageMemory\", vkBindImageMemory)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkGetBufferMemoryRequirements\", vkGetBufferMemoryRequirements)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkGetImageMemoryRequirements\", vkGetImageMemoryRequirements)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkGetImageSparseMemoryRequirements\", vkGetImageSparseMemoryRequirements)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkQueueBindSparse\", vkQueueBindSparse)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateFence\", vkCreateFence)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyFence\", vkDestroyFence)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkResetFences\", vkResetFences)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkGetFenceStatus\", vkGetFenceStatus)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkWaitForFences\", vkWaitForFences)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateSemaphore\", vkCreateSemaphore)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroySemaphore\", vkDestroySemaphore)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateEvent\", vkCreateEvent)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyEvent\", vkDestroyEvent)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkGetEventStatus\", vkGetEventStatus)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkSetEvent\", vkSetEvent)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkResetEvent\", vkResetEvent)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateQueryPool\", vkCreateQueryPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyQueryPool\", vkDestroyQueryPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkGetQueryPoolResults\", vkGetQueryPoolResults)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateBuffer\", vkCreateBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyBuffer\", vkDestroyBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateBufferView\", vkCreateBufferView)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyBufferView\", vkDestroyBufferView)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateImage\", vkCreateImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyImage\", vkDestroyImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkGetImageSubresourceLayout\", vkGetImageSubresourceLayout)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateImageView\", vkCreateImageView)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyImageView\", vkDestroyImageView)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateShaderModule\", vkCreateShaderModule)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyShaderModule\", vkDestroyShaderModule)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreatePipelineCache\", vkCreatePipelineCache)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyPipelineCache\", vkDestroyPipelineCache)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkGetPipelineCacheData\", vkGetPipelineCacheData)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkMergePipelineCaches\", vkMergePipelineCaches)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateGraphicsPipelines\", vkCreateGraphicsPipelines)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateComputePipelines\", vkCreateComputePipelines)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyPipeline\", vkDestroyPipeline)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreatePipelineLayout\", vkCreatePipelineLayout)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyPipelineLayout\", vkDestroyPipelineLayout)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateSampler\", vkCreateSampler)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroySampler\", vkDestroySampler)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateDescriptorSetLayout\", vkCreateDescriptorSetLayout)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyDescriptorSetLayout\", vkDestroyDescriptorSetLayout)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateDescriptorPool\", vkCreateDescriptorPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyDescriptorPool\", vkDestroyDescriptorPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkResetDescriptorPool\", vkResetDescriptorPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkAllocateDescriptorSets\", vkAllocateDescriptorSets)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkFreeDescriptorSets\", vkFreeDescriptorSets)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkUpdateDescriptorSets\", vkUpdateDescriptorSets)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateFramebuffer\", vkCreateFramebuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyFramebuffer\", vkDestroyFramebuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateRenderPass\", vkCreateRenderPass)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyRenderPass\", vkDestroyRenderPass)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkGetRenderAreaGranularity\", vkGetRenderAreaGranularity)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCreateCommandPool\", vkCreateCommandPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkDestroyCommandPool\", vkDestroyCommandPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkResetCommandPool\", vkResetCommandPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkAllocateCommandBuffers\", vkAllocateCommandBuffers)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkFreeCommandBuffers\", vkFreeCommandBuffers)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkBeginCommandBuffer\", vkBeginCommandBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkEndCommandBuffer\", vkEndCommandBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkResetCommandBuffer\", vkResetCommandBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdBindPipeline\", vkCmdBindPipeline)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdSetViewport\", vkCmdSetViewport)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdSetScissor\", vkCmdSetScissor)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdSetLineWidth\", vkCmdSetLineWidth)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdSetDepthBias\", vkCmdSetDepthBias)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdSetBlendConstants\", vkCmdSetBlendConstants)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdSetDepthBounds\", vkCmdSetDepthBounds)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdSetStencilCompareMask\", vkCmdSetStencilCompareMask)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdSetStencilWriteMask\", vkCmdSetStencilWriteMask)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdSetStencilReference\", vkCmdSetStencilReference)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdBindDescriptorSets\", vkCmdBindDescriptorSets)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdBindIndexBuffer\", vkCmdBindIndexBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdBindVertexBuffers\", vkCmdBindVertexBuffers)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdDraw\", vkCmdDraw)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdDrawIndexed\", vkCmdDrawIndexed)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdDrawIndirect\", vkCmdDrawIndirect)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdDrawIndexedIndirect\", vkCmdDrawIndexedIndirect)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdDispatch\", vkCmdDispatch)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdDispatchIndirect\", vkCmdDispatchIndirect)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdCopyBuffer\", vkCmdCopyBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdCopyImage\", vkCmdCopyImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdBlitImage\", vkCmdBlitImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdCopyBufferToImage\", vkCmdCopyBufferToImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdCopyImageToBuffer\", vkCmdCopyImageToBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdUpdateBuffer\", vkCmdUpdateBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdFillBuffer\", vkCmdFillBuffer)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdClearColorImage\", vkCmdClearColorImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdClearDepthStencilImage\", vkCmdClearDepthStencilImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdClearAttachments\", vkCmdClearAttachments)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdResolveImage\", vkCmdResolveImage)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdSetEvent\", vkCmdSetEvent)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdResetEvent\", vkCmdResetEvent)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdWaitEvents\", vkCmdWaitEvents)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdPipelineBarrier\", vkCmdPipelineBarrier)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdBeginQuery\", vkCmdBeginQuery)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdEndQuery\", vkCmdEndQuery)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdResetQueryPool\", vkCmdResetQueryPool)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdWriteTimestamp\", vkCmdWriteTimestamp)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdCopyQueryPoolResults\", vkCmdCopyQueryPoolResults)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdPushConstants\", vkCmdPushConstants)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdBeginRenderPass\", vkCmdBeginRenderPass)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdNextSubpass\", vkCmdNextSubpass)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdEndRenderPass\", vkCmdEndRenderPass)) return VK_FALSE;\n    if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, \"vkCmdExecuteCommands\", vkCmdExecuteCommands)) return VK_FALSE;\n    return VK_TRUE;\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/nnapi/CMakeLists.txt",
    "content": "if (USE_NNAPI AND ANDROID)\n  set(Caffe2_CONTRIB_NNAPI_CPU_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/dlnnapi.c\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/nnapi.cc\"\n  )\n  set(Caffe2_CONTRIB_NNAPI_TEST_CPU_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/nnapi_benchmark.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/nnapi_test.cc\"\n  )\n\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS}\n    ${Caffe2_CONTRIB_NNAPI_CPU_SRC} PARENT_SCOPE)\n  set(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS}\n    ${Caffe2_CONTRIB_NNAPI_TEST_CPU_SRC} PARENT_SCOPE)\nendif()\n"
  },
  {
    "path": "caffe2/mobile/contrib/nnapi/NeuralNetworks.h",
    "content": "/*\n * Copyright (C) 2017 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @addtogroup NeuralNetworks\n * @{\n */\n\n/**\n * @file NeuralNetworks.h\n */\n\n#ifndef ANDROID_ML_NN_RUNTIME_NEURAL_NETWORKS_H\n#define ANDROID_ML_NN_RUNTIME_NEURAL_NETWORKS_H\n\n/******************************************************************\n *\n * IMPORTANT NOTICE:\n *\n *   This file is part of Android's set of stable system headers\n *   exposed by the Android NDK (Native Development Kit).\n *\n *   Third-party source AND binary code relies on the definitions\n *   here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.\n *\n *   - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)\n *   - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS\n *   - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY\n *   - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES\n */\n\n#if __ANDROID_API__ >= __ANDROID_API_O_MR1__\n\n#include <stddef.h>\n#include <stdint.h>\n#include <sys/cdefs.h>\n\n__BEGIN_DECLS\n\n/**\n * Operand types.\n *\n * The type of operands that can be added to a model.\n *\n * Although we define many types, most operators accept just a few\n * types. Most used are {@link ANEURALNETWORKS_TENSOR_FLOAT32},\n * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM},\n * and {@link ANEURALNETWORKS_INT32}.\n */\ntypedef enum {\n    /** The following entries are used to declare scalars. */\n\n    /** A 32 bit floating point scalar value. */\n    ANEURALNETWORKS_FLOAT32 = 0,\n    /** A signed 32 bit integer scalar value. */\n    ANEURALNETWORKS_INT32 = 1,\n    /** An unsigned 32 bit integer scalar value. */\n    ANEURALNETWORKS_UINT32 = 2,\n\n    /** The following entries are used to declare tensors. */\n\n    /** A tensor of 32 bit floating point values. */\n    ANEURALNETWORKS_TENSOR_FLOAT32 = 3,\n    /** A tensor of 32 bit integer values. */\n    ANEURALNETWORKS_TENSOR_INT32 = 4,\n    /** A tensor of 8 bit integers that represent real numbers.\n     *\n     * Attached to this tensor are two numbers that can be used to convert\n     * the 8 bit integer to the real value and vice versa.  These two numbers are:\n     * - scale: a 32 bit non-negative floating point value.\n     * - zeroPoint: an 32 bit integer, in range [0, 255].\n     *\n     * The formula is:\n     * real_value = (integer_value - zeroPoint) * scale.\n     */\n    ANEURALNETWORKS_TENSOR_QUANT8_ASYMM = 5,\n} OperandCode;\n\n/**\n * Operation types.\n *\n * The type of operations that can be added to a model.\n */\ntypedef enum {\n    /** Adds two tensors, element-wise.\n     *\n     * Takes two input tensors of identical type and compatible dimensions. The output\n     * is the sum of both input tensors, optionally modified by an activation function.\n     *\n     * Two dimensions are compatible when:\n     *     1. they are equal, or\n     *     2. one of them is 1\n     *\n     * The size of the output is the maximum size along each dimension of the input operands.\n     * It starts with the trailing dimensions, and works its way forward.\n     *\n     * Example:\n     *\n     *     input1.dimension = {4, 1, 2}\n     *     input2.dimension = {5, 4, 3, 1}\n     *     output.dimension = {5, 4, 3, 2}\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: up to 4\n     *\n     * Inputs:\n     * * 0: A tensor.\n     * * 1: A tensor of the same type, and compatible dimensions as input0.\n     * * 2: An INT32 value, and has to be one of the {@link FuseCode} values.\n     *      Specifies the activation to invoke on the result of each addition.\n     *\n     * Outputs:\n     * * 0: The sum, a tensor of the same type as input0.\n     */\n    ANEURALNETWORKS_ADD = 0,\n\n    /** Performs a 2-D average pooling operation.\n     *\n     * The output dimensions are functions of the filter dimensions, stride, and padding.\n     *\n     * The values in the output tensor are computed as:\n     *\n     *     output[batch, row, col, channel] =\n     *         sum_{i, j}(input[batch, row + i, col + j, channel]) / sum(1)\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: 4, with \"NHWC\" (i.e., Num_samples, Height, Width, and Channels)\n     * data layout.\n     *\n     * Both explicit padding and implicit padding are supported.\n     *\n     * Inputs (explicit padding):\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input.\n     * * 1: An INT32 value, specifying the padding on the left, in the ‘width’ dimension.\n     * * 2: An INT32 value, specifying the padding on the right,in the ‘width’ dimension.\n     * * 3: An INT32 value, specifying the padding on the top, in the ‘height’ dimension.\n     * * 4: An INT32 value, specifying the padding on the bottom, in the ‘height’ dimension.\n     * * 5: An INT32 value, specifying the stride when walking through input\n     *      in the ‘width’ dimension.\n     * * 6: An INT32 value, specifying the stride when walking through input\n     *      in the ‘height’ dimension.\n     * * 7: An INT32 value, specifying the filter width.\n     * * 8: An INT32 value, specifying the filter height.\n     * * 9: An INT32 value, and has to be one of the {@link FuseCode} values.\n     *      Specifies the activation to invoke on the result of each addition.\n     *\n     * Inputs (implicit padding):\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input.\n     * * 1: An INT32 value, specifying the implicit padding scheme, has to be one of the\n     *      {@link PaddingCode} values.\n     * * 2: An INT32 value, specifying the stride when walking through input\n     *      in the ‘width’ dimension.\n     * * 3: An INT32 value, specifying the stride when walking through input\n     *      in the ‘height’ dimension.\n     * * 4: An INT32 value, specifying the filter width.\n     * * 5: An INT32 value, specifying the filter height.\n     * * 6: An INT32 value, and has to be one of the {@link FuseCode} values.\n     *      Specifies the activation to invoke on the result of each addition.\n     *\n     * Outputs:\n     * * 0: The output 4-D tensor, of shape [batches, out_height, out_width, depth].\n     */\n    ANEURALNETWORKS_AVERAGE_POOL_2D = 1,\n\n    /** Concatenates the input tensors along the given dimension.\n     *\n     * The input tensors must have identical type and the same dimensions except the\n     * dimension along the concatenation axis.\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: up to 4\n     *\n     * Inputs:\n     * * 0 ~ n-1: The list of n input tensors, of shape [D0, D1, ..., Daxis(i), ..., Dm].\n     *            For inputs of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, all\n     *            input tensors must have the same scale and zeroPoint.\n     * * n: An INT32 value, specifying the concatenation axis.\n     *\n     * Outputs:\n     * * 0: The output, a tensor of the same type as the input tensors.\n     *      The output shape is [D0, D1, ..., sum(Daxis(i)), ..., Dm].\n     */\n    ANEURALNETWORKS_CONCATENATION = 2,\n\n    /** Performs an 2-D convolution operation.\n     *\n     * The CONV_2D op sweeps a 2-D filter that can mix channels together over a batch of\n     * images, applying the filter to each window of each image of the appropriate size.\n     *\n     * The output dimensions are functions of the filter dimensions, stride, and padding.\n     *\n     * The values in the output tensor are computed as:\n     *\n     *     output[batch, row, col, channel] =\n     *         sum_{i, j} (\n     *             input[batch, row + i, col + j, k] *\n     *             filter[channel, row + i, col + j, k] +\n     *             bias[channel]\n     *         )\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: 4, with \"NHWC\" data layout.\n     *\n     * Both explicit padding and implicit padding are supported.\n     *\n     * Inputs (explicit padding):\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], specifying the input.\n     * * 1: A 4-D tensor, of shape [depth_out, filter_height, filter_width, depth_in],\n     *      specifying the filter.\n     * * 2: A 1-D tensor, of shape [depth_out], specifying the bias.\n     *      For input tensor of {@link ANEURALNETWORKS_TENSOR_FLOAT32} type, the bias should\n     *      also be of {@link ANEURALNETWORKS_TENSOR_FLOAT32}.\n     *      For input tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the bias\n     *      should be of {@link ANEURALNETWORKS_TENSOR_INT32}, with zeroPoint of 0 and\n     *      bias_scale == input_scale * filter_scale.\n     * * 3: An INT32 value, specifying the padding on the left, in the ‘width’ dimension.\n     * * 4: An INT32 value, specifying the padding on the right,in the ‘width’ dimension.\n     * * 5: An INT32 value, specifying the padding on the top, in the ‘height’ dimension.\n     * * 6: An INT32 value, specifying the padding on the bottom, in the ‘height’ dimension.\n     * * 7: An INT32 value, specifying the stride when walking through input\n     *      in the ‘width’ dimension.\n     * * 8: An INT32 value, specifying the stride when walking through input\n     *      in the ‘height’ dimension.\n     * * 9: An INT32 value, and has to be one of the {@link FuseCode} values.\n     *      Specifies the activation to invoke on the result of each addition.\n     *\n     * Inputs (implicit padding):\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], specifying the input.\n     * * 1: A 4-D tensor, of shape [depth_out, filter_height, filter_width, depth_in],\n     *      specifying the filter.\n     * * 2: A 1-D tensor, of shape [depth_out], specifying the bias.\n     *      For input tensor of {@link ANEURALNETWORKS_TENSOR_FLOAT32} type, the bias should\n     *      also be of {@link ANEURALNETWORKS_TENSOR_FLOAT32}.\n     *      For input tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the bias\n     *      should be of {@link ANEURALNETWORKS_TENSOR_INT32}, with zeroPoint of 0 and\n     *      bias_scale == input_scale * filter_scale.\n     * * 3: An INT32 value, specifying the implicit padding scheme, has to be one of the\n     *      {@link PaddingCode} values.\n     * * 4: An INT32 value, specifying the stride when walking through input\n     *      in the ‘width’ dimension.\n     * * 5: An INT32 value, specifying the stride when walking through input\n     *      in the ‘height’ dimension.\n     * * 6: An INT32 value, and has to be one of the {@link FuseCode} values.\n     *      Specifies the activation to invoke on the result of each addition.\n     *\n     * Outputs:\n     * * 0: The output 4-D tensor, of shape [batches, out_height, out_width, depth_out].\n     *      For output tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the following\n     *      condition must be satisfied: output_scale > input_scale * filter_scale.\n     */\n    ANEURALNETWORKS_CONV_2D = 3,\n\n    /** Performs a depthwise 2-D convolution operation.\n     *\n     * Given an input tensor of shape [batches, height, width, depth_in] and a filter\n     * tensor of shape [1, filter_height, filter_width, depth_out] containing\n     * depth_out convolutional filters of depth 1, DEPTHWISE_CONV applies a different\n     * filter to each input channel (expanding from 1 channel to channel_multiplier channels\n     * for each), then concatenates the results together.\n     *\n     * The output has depth_out = depth_in * depth_multiplier channels.\n     * The output dimensions are functions of the filter dimensions, stride, and padding.\n     *\n     * The values in the output tensor are computed as:\n     *\n     *     output[b, i, j, k * channel_multiplier + q] =\n     *         sum_{di, dj} (\n     *             input[b, strides[1] * i + di, strides[2] * j + dj, k] *\n     *             filter[1, di, dj, k * channel_multiplier + q]\n     *         )\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: 4, with \"NHWC\" data layout.\n     *\n     * Both explicit padding and implicit padding are supported.\n     *\n     * Inputs (explicit padding):\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], specifying the input.\n     * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out],\n     *      specifying the filter.\n     * * 2: A 1-D tensor, of shape [depth_out], specifying the bias.\n     *      For input tensor of {@link ANEURALNETWORKS_TENSOR_FLOAT32} type, the bias should\n     *      also be of {@link ANEURALNETWORKS_TENSOR_FLOAT32}.\n     *      For input tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the bias\n     *      should be of {@link ANEURALNETWORKS_TENSOR_INT32}, with zeroPoint of 0 and\n     *      bias_scale == input_scale * filter_scale.\n     * * 3: An INT32 value, specifying the padding on the left, in the ‘width’ dimension.\n     * * 4: An INT32 value, specifying the padding on the right,in the ‘width’ dimension.\n     * * 5: An INT32 value, specifying the padding on the top, in the ‘height’ dimension.\n     * * 6: An INT32 value, specifying the padding on the bottom, in the ‘height’ dimension.\n     * * 7: An INT32 value, specifying the stride when walking through input\n     *      in the ‘width’ dimension.\n     * * 8: An INT32 value, specifying the stride when walking through input\n     *      in the ‘height’ dimension.\n     * * 9: An INT32 value, specifying the depthwise multiplier.\n     * * 10: An INT32 value, and has to be one of the {@link FuseCode} values.\n     *       Specifies the activation to invoke on the result of each addition.\n     *\n     * Inputs (explicit padding):\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], specifying the input.\n     * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out],\n     *      specifying the filter.\n     * * 2: A 1-D tensor, of shape [depth_out], specifying the bias.\n     *      For input tensor of {@link ANEURALNETWORKS_TENSOR_FLOAT32} type, the bias should\n     *      also be of {@link ANEURALNETWORKS_TENSOR_FLOAT32}.\n     *      For input tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the bias\n     *      should be of {@link ANEURALNETWORKS_TENSOR_INT32}, with zeroPoint of 0 and\n     *      bias_scale == input_scale * filter_scale.\n     * * 3: An INT32 value, specifying the implicit padding scheme, has to be one of the\n     *      {@link PaddingCode} values.\n     * * 4: An INT32 value, specifying the stride when walking through input\n     *      in the ‘width’ dimension.\n     * * 5: An INT32 value, specifying the stride when walking through input\n     *      in the ‘height’ dimension.\n     * * 6: An INT32 value, specifying the depthwise multiplier.\n     * * 7: An INT32 value, and has to be one of the {@link FuseCode} values.\n     *       Specifies the activation to invoke on the result of each addition.\n     *\n     * Outputs:\n     * * 0: The output 4-D tensor, of shape [batches, out_height, out_width, depth_out].\n     *      For output tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the following\n     *      condition must be satisfied: output_scale > input_scale * filter_scale.\n     */\n    ANEURALNETWORKS_DEPTHWISE_CONV_2D = 4,\n\n    /** Rearranges data from depth into blocks of spatial data.\n     *\n     * More specifically, this op outputs a copy of the input tensor where values from\n     * the depth dimension are moved in spatial blocks to the height and width dimensions.\n     * The value block_size indicates the input block size and how the data is moved.\n     *\n     * Chunks of data of size block_size * block_size from depth are rearranged into\n     * non-overlapping blocks of size block_size x block_size.\n     *\n     * The width of the output tensor is input_depth * block_size, whereas the height is\n     * input_height * block_size.\n     * The depth of the input tensor must be divisible by block_size * block_size\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: 4, with \"NHWC\" data layout.\n     *\n     * Inputs:\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], specifying the input.\n     * * 1: An INT32 value, specifying the block_size. block_size must be >=1 and\n     *      block_size * block_size must be a divisor of the input depth.\n     *\n     * Outputs:\n     * * 0: The output 4-D tensor, of shape [batch, height*block_size, width*block_size,\n     *      depth/(block_size*block_size)].\n     */\n    ANEURALNETWORKS_DEPTH_TO_SPACE = 5,\n\n    /** Dequantizes the input tensor.\n     *\n     * The formula is:\n     *\n     *     output = (input - zeroPoint) * scale.\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: up to 4\n     *\n     * Inputs:\n     * * 0: A tensor of type {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}.\n     *\n     * Outputs:\n     * * 0: The output tensor of same shape as input0, but with type\n     *      {@link ANEURALNETWORKS_TENSOR_FLOAT32}.\n     */\n    ANEURALNETWORKS_DEQUANTIZE = 6,\n\n    /** Looks up sub-tensors in the input tensor.\n     *\n     * This operator takes for input a tensor of values (Values) and\n     * a one-dimensional tensor of selection indices (Lookups).\n     * The output tensor is the concatenation of sub-tensors of Values as\n     * selected by Lookups.\n     *\n     * Think of Values as being sliced along its first dimension:\n     * The entries in Lookups select which slices are concatenated together\n     * to create the output tensor.\n     *\n     * For example, if Values has shape of [40, 200, 300] and\n     * Lookups has shape of [3], we would expect all three values\n     * found in Lookups to be  between 0 and 39. The resulting tensor will\n     * have shape of [3, 200, 300].\n     *\n     * If a value in Lookups is out of bounds, the operation will fail\n     * and an error will be reported.\n     *\n     * Inputs:\n     * * 0: Lookups. A 1-D tensor of {@link ANEURALNETWORKS_TENSOR_INT32} type.\n     *      The values are indices into the first dimension of Values.\n     * * 1: Values. An n-D tensor, where n >= 2, from which sub-tensors are\n     *      extracted.\n     *\n     * Output:\n     * * 0: A n-D tensor with the same rank and shape as the Values\n     *      tensor, except for the first dimension which has the same size\n     *      as Lookups' only dimension.\n     */\n    ANEURALNETWORKS_EMBEDDING_LOOKUP = 7,\n\n    /** Computes element-wise floor() on the input tensor.\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     *\n     * Supported tensor rank: up to 4\n     *\n     * Inputs:\n     * * 0: A tensor.\n     *\n     * Outputs:\n     * * 0: The output tensor, of the same type and dimensions as the input tensor.\n     */\n    ANEURALNETWORKS_FLOOR = 8,\n\n    /** Denotes a fully (densely) connected layer, which connects all elements in the input\n     * tensor with each element in the output tensor.\n     *\n     * This layer implements the operation:\n     *\n     *     outputs = activation(inputs * weights’ + bias)\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: up to 4.\n     *\n     * Inputs:\n     * * 0: A tensor, specifying the input. If rank is greater than 2, then it gets flattened to\n     *      a 2-D Tensor. The 2-D Tensor is handled as if dimensions corresponded to shape\n     *      [batch_size, input_size], where “batch_size” corresponds to the batching dimension,\n     *      and “input_size” is the size of the input.\n     * * 1: A 2-D tensor, specifying the weights, of shape [num_units, input_size], where\n     *      \"num_units\" corresponds to the number of output nodes.\n     * * 2: A 1-D tensor, of shape [num_units], specifying the bias.\n     *      For input tensor of {@link ANEURALNETWORKS_TENSOR_FLOAT32} type, the bias should\n     *      also be of {@link ANEURALNETWORKS_TENSOR_FLOAT32}.\n     *      For input tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the bias\n     *      should be of {@link ANEURALNETWORKS_TENSOR_INT32}, with zeroPoint of 0 and\n     *      bias_scale == input_scale * filter_scale.\n     * * 3: An INT32 value, and has to be one of the {@link FuseCode} values.\n     *      Specifies the activation to invoke on the result of each addition.\n     *\n     * Outputs:\n     * * 0: The output tensor, of shape [batch_size, num_units].\n     *      For output tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the following\n     *      condition must be satisfied: output_scale > input_scale * filter_scale.\n     */\n    ANEURALNETWORKS_FULLY_CONNECTED = 9,\n\n    /** Looks up sub-tensors in the input tensor using a key-value map.\n     *\n     * This operator takes for input a tensor of values (Values),\n     * a one-dimensional tensor of selection values (Lookups) and\n     * a one-dimensional tensor that maps these values to Values\n     * indexes. The output tensor is the concatenation of sub-tensors of\n     * Values as selected by Lookups via Keys.\n     *\n     * Think of Values as being sliced along its outer-most dimension.\n     * The output is a concatenation of selected slices, with one slice\n     * for each entry of Lookups. The slice selected is the one at the\n     * same index as the Maps entry that matches the value in Lookups.\n     *\n     * For a hit, the corresponding sub-tensor of Values is included\n     * in the Output tensor.  For a miss, the corresponding sub-tensor in\n     * Output will have zero values.\n     *\n     * For example, if Values has shape of [40, 200, 300],\n     * Keys should have a shape of [40]. If Lookups tensor has shape\n     * of [3], we're concatenating three slices, so the resulting tensor\n     * will have the shape of [3, 200, 300]. If the first entry in\n     * Lookups has the value 123456, we'll look for that value in Keys tensor.\n     * If the sixth entry of Keys contains 123456, we'll select the sixth\n     * slice of Values. If no entry in Keys has 123456, a slice of zeroes\n     * will be concatenated.\n     *\n     * Inputs:\n     * * 0: Lookups. A 1-D {@link ANEURALNETWORKS_TENSOR_INT32} tensor with shape [ k ].\n     * * 1: Keys. A 1-D {@link ANEURALNETWORKS_TENSOR_INT32} tensor with shape [ n ];\n     *      Keys and Values pair represent a map, i.e., the ith element\n     *      in Keys (Keys[i]) is the key to select the ith sub-tensor\n     *      in Values (Values[i]), where 0 <= i <= n-1.\n     *      Keys tensor *MUST* be sorted in ascending order.\n     * * 2: Values. A tensor with shape of [ n, … ]; i.e., the first dimension must be n.\n     *\n     * Outputs:\n     * * 0: Output. A tensor with shape [ k …].\n     * * 1: Hits. A boolean tensor with shape [ k ] indicates whether the lookup\n     *      hits (True) or not (False).\n     *      Stored as {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} with offset 0 and scale 1.0f.\n     *      A non-zero byte represents True, a hit. A zero indicates otherwise.\n     */\n    ANEURALNETWORKS_HASHTABLE_LOOKUP = 10,\n\n    /** Applies L2 normalization along the depth dimension.\n     *\n     * The values in the output tensor are computed as:\n     *\n     *     output[batch, row, col, channel] =\n     *         input[batch, row, col, channel] /\n     *         sqrt(sum_{c} pow(input[batch, row, col, c], 2))\n     *\n     * For input tensor with more dimensions, independently normalizes each 1-D slice along dimension dim.\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     *\n     * Supported tensor rank: 4, with \"NHWC\" data layout (i.e., Num_samples, Height, Width, and Channels).\n     *\n     * Inputs:\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth].\n     *\n     * Outputs:\n     * * 0: The output 4-D tensor, of shape [batches, out_height, out_width, depth].\n     */\n    ANEURALNETWORKS_L2_NORMALIZATION = 11,\n\n    /** Performs an 2-D L2 pooling operation.\n     *\n     * The output dimensions are functions of the filter dimensions, stride, and padding.\n     *\n     * The values in the output tensor are computed as:\n     *\n     *     output[batch, row, col, channel] =\n     *         sqrt(sum_{i, j} pow(input[batch, row + i, col + j, channel], 2) / sum(1))\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     *\n     * Supported tensor rank: 4, with \"NHWC\" data layout.\n     *\n     * Both explicit padding and implicit padding are supported.\n     *\n     * Inputs (explicit padding):\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input.\n     * * 1: An INT32 value, specifying the padding on the left, in the ‘width’ dimension.\n     * * 2: An INT32 value, specifying the padding on the right,in the ‘width’ dimension.\n     * * 3: An INT32 value, specifying the padding on the top, in the ‘height’ dimension.\n     * * 4: An INT32 value, specifying the padding on the bottom, in the ‘height’ dimension.\n     * * 5: An INT32 value, specifying the stride when walking through input\n     *      in the ‘width’ dimension.\n     * * 6: An INT32 value, specifying the stride when walking through input\n     *      in the ‘height’ dimension.\n     * * 7: An INT32 value, specifying the filter width.\n     * * 8: An INT32 value, specifying the filter height.\n     * * 9: An INT32 value, and has to be one of the {@link FuseCode} values.\n     *      Specifies the activation to invoke on the result of each addition.\n     *\n     * Inputs (implicit padding):\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input.\n     * * 1: An INT32 value, specifying the implicit padding scheme, has to be one of the\n     *      {@link PaddingCode} values.\n     * * 2: An INT32 value, specifying the stride when walking through input\n     *      in the ‘width’ dimension.\n     * * 3: An INT32 value, specifying the stride when walking through input\n     *      in the ‘height’ dimension.\n     * * 4: An INT32 value, specifying the filter width.\n     * * 5: An INT32 value, specifying the filter height.\n     * * 6: An INT32 value, and has to be one of the {@link FuseCode} values.\n     *      Specifies the activation to invoke on the result of each addition.\n     *\n     * Outputs:\n     * * 0: The output 4-D tensor, of shape [batches, out_height, out_width, depth].\n     */\n    ANEURALNETWORKS_L2_POOL_2D = 12,\n\n    /** Applies Local Response Normalization along the depth dimension.\n     *\n     * The 4-D input tensor is treated as a 3-D array of 1-D vectors (along the last\n     * dimension), and each vector is normalized independently. Within a given vector,\n     * each component is divided by the weighted, squared sum of inputs within depth_radius.\n     *\n     * The output is calculated using this formula:\n     *\n     *     sqr_sum[a, b, c, d] =\n     *         sum(pow(input[a, b, c, d - depth_radius : d + depth_radius + 1], 2)\n     *     output = input / pow((bias + alpha * sqr_sum), beta)\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     *\n     * Supported tensor rank: 4, with \"NHWC\" data layout.\n     *\n     * Inputs:\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input.\n     * * 1: An INT32 value, specifying the radius of the normalization window.\n     * * 2: A FLOAT32 value, specifying the bias, must not be zero.\n     * * 3: A FLOAT32 value, specifying the scale factor, alpha.\n     * * 4: A FLOAT32 value, specifying the exponent, beta.\n     *\n     * Outputs:\n     * * 0: The output tensor of same shape as input0.\n     */\n    ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION = 13,\n\n    /** Computes sigmoid activation on the input tensor element-wise.\n     *\n     * The output is calculated using this formula:\n     *\n     *     output = 1 / (1 + exp(-input))\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: up to 4.\n     *\n     * Inputs:\n     * * 0: A tensor, specifying the input.\n     *\n     * Outputs:\n     * * 0: The output tensor of same shape as input0.\n     *      For {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type,\n     *      the scale must be 1.f / 256 and the zeroPoint must be 0.\n     */\n    ANEURALNETWORKS_LOGISTIC = 14,\n\n    /**\n     * Projects an input to a bit vector via locality senstive hashing.\n     *\n     * Inputs:\n     * * 0: Hash functions. Dim.size == 2, DataType: Float.\n     *            Tensor[0].Dim[0]: Number of hash functions.\n     *            Tensor[0].Dim[1]: Number of seeds per hash functions.\n     *            Tensor[0].Dim[1] <= 32 in sparse case.\n     *\n     * * 1: Input. Dim.size >= 1, no restriction on DataType.\n     * * 2: Weight. Optional. Dim.size == 1, DataType: Float.\n     *     If not set, each input element is considered to have the same weight of\n     *     1.0.\n     *     Tensor[1].Dim[0] == Tensor[2].Dim[0]\n     * * 3: Type:\n     *        Sparse: Value LSHProjectionType_SPARSE(=1).\n     *          Computed bit vector is considered to be sparse.\n     *          Each output element is an int32 made up of multiple bits computed from\n     *          hash functions.\n     *\n     *        Dense: Value LSHProjectionType_DENSE(=2).\n     *          Computed bit vector is considered to be dense. Each output element\n     *          represents a bit and can take the value of either 0 or 1.\n     *\n     * Outputs:\n     * * 0: If the projection type is sparse:\n     *        Output.Dim == { Tensor[0].Dim[0] }\n     *        A tensor of int32 that represents hash signatures.\n     *      If the projection type is Dense:\n     *        Output.Dim == { Tensor[0].Dim[0] * Tensor[0].Dim[1] }\n     *        A flattened tensor that represents projected bit vectors.\n     */\n    ANEURALNETWORKS_LSH_PROJECTION = 15,\n\n    /**\n     * Long short-term memory unit (LSTM) recurrent network layer.\n     *\n     * The default non-peephole implementation is based on:\n     * http://deeplearning.cs.cmu.edu/pdfs/Hochreiter97_lstm.pdf\n     * S. Hochreiter and J. Schmidhuber. \"Long Short-Term Memory\". Neural\n     * Computation, 9(8):1735-1780, 1997.\n     *\n     * The peephole implementation is based on:\n     * https://research.google.com/pubs/archive/43905.pdf\n     * Hasim Sak, Andrew Senior, and Francoise Beaufays. \"Long short-term memory\n     * recurrent neural network architectures for large scale acoustic modeling.\"\n     * INTERSPEECH, 2014.\n     *\n     * The coupling of input and forget gate (CIFG) is based on:\n     * http://arxiv.org/pdf/1503.04069.pdf\n     * Greff et al. \"LSTM: A Search Space Odyssey\"\n     *\n     * The class has the following independently optional inputs:\n     * * If input gate (if CIFG): “input_to_forget_weights”,\n     *   “recurrent_to_input_weights”, “cell_to_input_weights”, “input_gate_bias”.\n     * * If no peephole connections: “cell_to_input_weights”,\n     *   “cell_to_forget_weights”, “cell_to_output_weights”.\n     * * If no projection layer: “projection_weights” and “projection_bias”.\n     * * If no projection bias: “projection_bias”.\n     *\n     * Supported tensor types (type T):\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     *\n     * Inputs:\n     * * 0: Input.\n     *      A 2-D tensor of type T, of shape [batch_size, input_size], where\n     *      “batch_size” corresponds to the batching dimension, and “input_size”\n     *      is the size of the input.\n     * * 1: input_to_input_weights.\n     *      A 2-D tensor of type T, of shape [num_units, input_size], where\n     *      “num_units” corresponds to the number of cell units.\n     * * 2: input_to_forget_weights.\n     *      A 2-D tensor of type T, of shape [num_units, input_size].\n     * * 3: input_to_cell_weights.\n     *      A 2-D tensor of type T, of shape [num_units, input_size].\n     * * 4: input_to_output_weights.\n     *      A 2-D tensor of type T, of shape [num_units, input_size].\n     * * 5: recurrent_to_input_weights.\n     *      A 2-D tensor of type T, of shape [num_units, output_size], where\n     *      “output_size” corresponds to either the number of cell units (i.e.,\n     *      “num_units”), or the second dimension of the “projection_weights”, if\n     *      defined.\n     * * 6: recurrent_to_forget_weights.\n     *      A 2-D tensor of type T, of shape [num_units, output_size].\n     * * 7: recurrent_to_cell_weights.\n     *      A 2-D tensor of type T, of shape [num_units, output_size].\n     * * 8: recurrent_to_output_weights.\n     *      A 2-D tensor of type T, of shape [num_units, output_size].\n     * * 9: cell_to_input_weights.\n     *      A 1-D tensor of type T, of shape [num_units].\n     * * 10:cell_to_forget_weights.\n     *      A 1-D tensor of type T, of shape [num_units].\n     * * 11:cell_to_output_weights.\n     *      A 1-D tensor of type T, of shape [num_units].\n     * * 12:input_gate_bias.\n     *      A 1-D tensor of type T, of shape [num_units].\n     * * 13:forget_gate_bias.\n     *      A 1-D tensor of type T, of shape [num_units].\n     * * 14:cell_bias.\n     *      A 1-D tensor of type T, of shape [num_units].\n     * * 15:output_gate_bias.\n     *      A 1-D tensor of type T, of shape [num_units].\n     * * 16:projection_weights.\n     *      A 2-D tensor of type T, of shape [output_size, num_units].\n     * * 17:projection_bias.\n     *      A 1-D tensor of type T, of shape [output_size].\n     * * 18: output_state (in).\n     *      A 2-D tensor of type T, of shape [batch_size, output_size].\n     * * 19: cell_state (in).\n     *      A 2-D tensor of type T, of shape [batch_size, num_units].\n     * * 20:fused_activation_function.\n     *      An optional {@link FuseCode} value indicating the activation\n     *      function.\n     *      If “NONE” is specified then it results in a linear activation.\n     * * 21:cell_clip.\n     *      A clipping threshold for the cell state, such that values are bound\n     *      within [-cell_clip, cell_clip]. If set to 0.0 then clipping is\n     *      disabled.\n     * * 22:proj_clip.\n     *      A clipping threshold for the output from the projection layer, such\n     *      that values are bound within [-proj_clip, proj_clip]. If set to 0.0\n     *      then clipping is disabled.\n     *\n     * Outputs:\n     * * 0: scratch_buffer.\n     *      A 3-D tensor of type T, of shape [batch_size, num_cell, 4].\n     * * 1: output_state (out).\n     *      A 2-D tensor of type T, of shape [batch_size, output_size].\n     * * 2: cell_state (out).\n     *      A 2-D tensor of type T, of shape [batch_size, num_units].\n     * * 3: output.\n     *      A 2-D tensor of type T, of shape [batch_size, output_size]. This is\n     *      effectively the same as the current “output_state” value.\n     */\n    ANEURALNETWORKS_LSTM = 16,\n\n    /** Performs an 2-D max pooling operation.\n     *\n     * The output dimensions are functions of the filter dimensions, stride, and padding.\n     *\n     * The values in the output tensor are computed as:\n     *\n     *     output[batch, row, col, channel] =\n     *         max_{i, j} (input[batch, row + i, col + j, channel])\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: 4, with \"NHWC\" data layout.\n     *\n     * Both explicit padding and implicit padding are supported.\n     *\n     * Inputs (explicit padding):\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input.\n     * * 1: An INT32 value, specifying the padding on the left, in the ‘width’ dimension.\n     * * 2: An INT32 value, specifying the padding on the right,in the ‘width’ dimension.\n     * * 3: An INT32 value, specifying the padding on the top, in the ‘height’ dimension.\n     * * 4: An INT32 value, specifying the padding on the bottom, in the ‘height’ dimension.\n     * * 5: An INT32 value, specifying the stride when walking through input\n     *      in the ‘width’ dimension.\n     * * 6: An INT32 value, specifying the stride when walking through input\n     *      in the ‘height’ dimension.\n     * * 7: An INT32 value, specifying the filter width.\n     * * 8: An INT32 value, specifying the filter height.\n     * * 9: An INT32 value, and has to be one of the {@link FuseCode} values.\n     *      Specifies the activation to invoke on the result of each addition.\n     *\n     * Inputs (implicit padding):\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input.\n     * * 1: An INT32 value, specifying the implicit padding scheme, has to be one of the\n     *      {@link PaddingCode} values.\n     * * 2: An INT32 value, specifying the stride when walking through input\n     *      in the ‘width’ dimension.\n     * * 3: An INT32 value, specifying the stride when walking through input\n     *      in the ‘height’ dimension.\n     * * 4: An INT32 value, specifying the filter width.\n     * * 5: An INT32 value, specifying the filter height.\n     * * 6: An INT32 value, and has to be one of the {@link FuseCode} values.\n     *      Specifies the activation to invoke on the result of each addition.\n     *\n     * Outputs:\n     * * 0: The output 4-D tensor, of shape [batches, out_height, out_width, depth].\n     */\n    ANEURALNETWORKS_MAX_POOL_2D = 17,\n\n    /** Multiplies two tensors, element-wise.\n     *\n     * Takes two input tensors of identical type and compatible dimensions. The output\n     * is the product of both input tensors, optionally modified by an activation function.\n     *\n     * Two dimensions are compatible when:\n     *     1. they are equal, or\n     *     2. one of them is 1\n     *\n     * The size of the resulting output is the maximum size along each dimension of the\n     * input operands. It starts with the trailing dimensions, and works its way forward.\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: up to 4\n     *\n     * Inputs:\n     * * 0: A tensor.\n     * * 1: A tensor of the same type, and compatible dimensions as input0.\n     * * 2: An INT32 value, and has to be one of the {@link FuseCode} values.\n     *      Specifies the activation to invoke on the result of each addition.\n     *\n     * Outputs:\n     * * 0: The product, a tensor of the same type as input0.\n     *      For output tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the following\n     *      condition must be satisfied: output_scale > input1_scale * input2_scale.\n     */\n    ANEURALNETWORKS_MUL = 18,\n\n    /** Computes rectified linear activation on the input tensor element-wise.\n     *\n     * The output is calculated using this formula:\n     *\n     *     output = max(0, input)\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: up to 4.\n     *\n     * Inputs:\n     * * 0: A tensor, specifying the input.\n     *\n     * Outputs:\n     * * 0: The output tensor of same shape as input0.\n     */\n    ANEURALNETWORKS_RELU = 19,\n\n    /** Computes rectified linear 1 activation on the input tensor element-wise.\n     *\n     * The output is calculated using this formula:\n     *\n     *     output = min(1.f, max(-1.f, input))\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: up to 4.\n     *\n     * Inputs:\n     * * 0: A tensor, specifying the input.\n     *\n     * Outputs:\n     * * 0: The output tensor of same shape as input0.\n     */\n    ANEURALNETWORKS_RELU1 = 20,\n\n    /** Computes rectified linear 6 activation on the input tensor element-wise.\n     *\n     * The output is calculated using this formula:\n     *\n     *     output = min(6, max(0, input))\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: up to 4.\n     *\n     * Inputs:\n     * * 0: A tensor, specifying the input.\n     *\n     * Outputs:\n     * * 0: The output tensor of same shape as input0.\n     */\n    ANEURALNETWORKS_RELU6 = 21,\n\n    /** Reshapes a tensor.\n     *\n     * Given tensor, this operation returns a tensor that has the same values as tensor,\n     * but with a newly specified shape.\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: up to 4.\n     *\n     * Inputs:\n     * * 0: A tensor, specifying the tensor to be reshaped.\n     * * 1: A 1-D tensor of type {@link ANEURALNETWORKS_TENSOR_INT32}, defining the shape\n     *      of the output tensor. The number of elements implied by shape must be the same\n     *      as the number of elements in the input tensor.\n     *\n     * Outputs:\n     * * 0: The output tensor, of shape specified by the input shape.\n     */\n    ANEURALNETWORKS_RESHAPE = 22,\n\n    /** Resizes images to given size using the bilinear interpretation.\n     *\n     * Resized images will be distorted if their output aspect ratio is not the same as\n     * input aspect ratio.\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     *\n     * Supported tensor rank: 4, with \"NHWC\" data layout.\n     *\n     * Inputs:\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input.\n     * * 1: An INT32 value, specifying the output height of the output tensor.\n     * * 2: An INT32 value, specifying the output width of the output tensor.\n     *\n     * Outputs:\n     * * 0: The output 4-D tensor, of shape [batches, new_height, new_width, depth].\n     */\n    ANEURALNETWORKS_RESIZE_BILINEAR = 23,\n\n    /**\n     * A basic recurrent neural network layer.\n     *\n     * This layer implements the operation:\n     * outputs = state = activation(inputs * input_weights + state * recurrent_weights + bias)\n     *\n     * Where:\n     * * “input_weights” is a weight matrix that multiplies the inputs;\n     * * “recurrent_weights” is a weight matrix that multiplies the current\n     *    “state” which itself is the output from the previous time step\n     *    computation;\n     * * “bias” is a bias vector (added to each output vector in the batch);\n     * * “activation” is the function passed as the “fused_activation_function”\n     *   argument (if not “NONE”).\n     *\n     * Supported tensor types (Type T):\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     *\n     * Inputs:\n     * * 0: input.\n     *      A 2-D tensor of type T, of shape [batch_size, input_size], where\n     *      “batch_size” corresponds to the batching dimension, and “input_size” is\n     *      the size of the input.\n     * * 1: weights.\n     *      A 2-D tensor of type T, of shape [num_units, input_size], where\n     *      “num_units” corresponds to the number of units.\n     * * 2: recurrent_weights.\n     *      A 2-D tensor of type T, of shape [num_units, num_units], with columns\n     *      corresponding to the weights from each unit.\n     * * 3: bias.\n     *      A 1-D tensor of type T, of shape [num_units].\n     * * 4: hidden state (in).\n     *      A 2-D tensor of type T, of shape [batch_size, num_units].\n     * * 5: fused_activation_function.\n     *      An optional {@link FuseCode} value indicating the activation\n     *      function. If “NONE” is specified then it results in a linear\n     *      activation.\n     *\n     * Outputs:\n     * * 0: hidden state (out).\n     *      A 2-D tensor of type T, of shape [batch_size, num_units].\n     *\n     * * 1: output.\n     *      A 2-D tensor of type T, of shape [batch_size, num_units]. This is\n     *      effectively the same as the current state value.\n     */\n    ANEURALNETWORKS_RNN = 24,\n\n    /** Computes the softmax activation on the input tensor element-wise, per batch, by\n     * normalizing the input vector so the maximum coefficient is zero.\n     *\n     * The output is calculated using this formula:\n     *\n     *     output[batch, i] =\n     *         exp((input[batch, i] - max(input[batch, :])) * beta) /\n     *         sum_{k}{exp((input[batch, k] - max(input[batch, :])) * beta)}\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: 2 or 4.\n     *\n     * Inputs:\n     * * 0: A 2-D or 4-D tensor, specifying the tensor to be reshaped.\n     * * 1: A FLOAT32 value, specifying the positive scaling factor for the exponent, beta.\n     *\n     * Outputs:\n     * * 0: The output tensor of same shape as input0.\n     *      For {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type,\n     *      the scale must be 1.f / 256 and the zeroPoint must be 0.\n     */\n    ANEURALNETWORKS_SOFTMAX = 25,\n\n    /** Rearranges blocks of spatial data, into depth.\n     *\n     * More specifically, this op outputs a copy of the input tensor where values from\n     * the height and width dimensions are moved to the depth dimension.\n     * The value block_size indicates the input block size and how the data is moved.\n     *\n     * Chunks of data of size block_size * block_size from depth are rearranged into\n     * non-overlapping blocks of size block_size x block_size.\n     *\n     * The depth of the output tensor is input_depth * block_size * block_size.\n     * The input tensor's height and width must be divisible by block_size.\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}\n     *\n     * Supported tensor rank: 4, with \"NHWC\" data layout.\n     *\n     * Inputs:\n     * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], specifying the input.\n     * * 1: An INT32 value, specifying the block_size. block_size must be >=1 and\n     *      block_size must be a divisor of both the input height and width.\n     *\n     * Outputs:\n     * * 0: The output 4-D tensor, of shape [batch, height/block_size, width/block_size,\n     *      depth*block_size*block_size].\n     */\n    ANEURALNETWORKS_SPACE_TO_DEPTH = 26,\n\n    /**\n     * SVDF op is a kind of stateful layer derived from the notion that a\n     * densely connected layer that's processing a sequence of input frames can\n     * be approximated by using a singular value decomposition of each of its\n     * nodes. The implementation is based on:\n     *\n     * https://research.google.com/pubs/archive/43813.pdf\n     *\n     * P. Nakkiran, R. Alvarez, R. Prabhavalkar, C. Parada.\n     * “Compressing Deep Neural Networks using a Rank-Constrained Topology”.\n     * INTERSPEECH, 2015.\n     *\n     * It processes the incoming input using a 2-stage filtering mechanism:\n     * * stage 1 performs filtering on the \"features\" dimension, whose outputs get\n     *   pushed into a memory of fixed-size memory_size.\n     * * stage 2 performs filtering on the \"time\" dimension of the memory_size\n     *   memoized outputs of stage 1.\n     *\n     * Specifically, for rank 1, this layer implements the operation:\n     *\n     *    memory = push(conv1d(inputs, weights_feature, feature_dim,\n     *                  \"ANEURALNETWORKS_PADDING_VALID\"));\n     *    outputs = activation(memory * weights_time + bias);\n     *\n     * Where:\n     * * “weights_feature” is a weights matrix that processes the inputs (by\n     *   convolving the input with every “feature filter”), and whose outputs get\n     *   pushed, stacked in order, into the fixed-size “memory” (the oldest entry\n     *   gets dropped);\n     * * “weights_time” is a weights matrix that processes the “memory” (by a\n     *   batched matrix multiplication on the num_units);\n     * * “bias” is an optional bias vector (added to each output vector in the\n     *   batch); and\n     * * “activation” is the function passed as the “fused_activation_function”\n     *   argument (if not “NONE”).\n     *\n     * Each rank adds a dimension to the weights matrices by means of stacking\n     * the filters.\n     *\n     * Supported tensor types (type T):\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     *\n     * Inputs:\n     * * 0: input.\n     *      A 2-D tensor of type T, of shape [batch_size, input_size], where\n     *      “batch_size” corresponds to the batching dimension, and “input_size” is\n     *      the size of the input.\n     * * 1: weights_feature.\n     *      A 2-D tensor of type T, of shape [num_units, input_size], where\n     *      “num_units” corresponds to the number of units.\n     * * 2: weights_time.\n     *      A 2-D tensor of type T, of shape [num_units, memory_size], where\n     *      “memory_size” corresponds to the fixed-size of the memory.\n     * * 3: bias.\n     *      An optional 1-D tensor of type T, of shape [num_units].\n     * * 4: state (in).\n     *      A 2-D tensor of type T, of shape [batch_size, (memory_size - 1) * num_units * rank].\n     * * 5: rank.\n     *      The rank of the SVD approximation.\n     * * 6: fused_activation_function.\n     *      An optional {@link FuseCode} value indicating the activation function.\n     *      If “NONE” is specified then it results in a linear activation.\n     *\n     * Outputs:\n     * * 0: state (out).\n     *      A 2-D tensor of type T, of shape [batch_size, (memory_size - 1) * num_units * rank].\n     * * 1: output.\n     *      A 2-D tensor of type T, of shape [batch_size, num_units].\n     */\n    ANEURALNETWORKS_SVDF = 27,\n\n    /** Computes hyperbolic tangent of input tensor element-wise.\n     *\n     * The output is calculated using this formula:\n     *\n     *     output = tanh(input)\n     *\n     * Supported tensor types:\n     * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}\n     *\n     * Supported tensor rank: up to 4.\n     *\n     * Inputs:\n     * * 0: A tensor, specifying the input.\n     *\n     * Outputs:\n     * * 0: The output tensor of same shape as input0.\n     */\n    ANEURALNETWORKS_TANH = 28,\n} OperationCode;\n\n/**\n * Fused activation function types.\n *\n */\ntypedef enum {\n    /** NO fused activation function. */\n    ANEURALNETWORKS_FUSED_NONE = 0,\n    /** Fused ReLU activation function. */\n    ANEURALNETWORKS_FUSED_RELU = 1,\n    /** Fused ReLU1 activation function. */\n    ANEURALNETWORKS_FUSED_RELU1 = 2,\n    /** Fused ReLU6 activation function. */\n    ANEURALNETWORKS_FUSED_RELU6 = 3,\n} FuseCode;\n\n/**\n * Implicit padding algorithms.\n *\n */\ntypedef enum {\n    /**\n     * SAME padding.\n     * Padding on both ends are the \"same\":\n     *     padding_to_beginning =  total_padding / 2\n     *     padding_to_end       = (total_padding + 1)/2.\n     * i.e., for even number of padding, padding to both ends are exactly\n     * the same; for odd number of padding, padding to the ending is bigger\n     * than the padding to the beginning by 1.\n     *\n     * total_padding is a function of input, stride and filter size.\n     * It could be computed as follows:\n     *    out_size = (input + stride - 1) / stride;\n     *    needed_input = (out_size - 1) * stride + filter_size\n     *    total_padding = max(0, needed_input - output_size)\n     *  The computation is the same for the horizontal and vertical directions.\n     */\n    ANEURALNETWORKS_PADDING_SAME = 1,\n\n    /**\n     * VALID padding.\n     * No padding. When the input size is not evenly divisible by\n     * the filter size, the input at the end that could not fill\n     * the whole filter tile will simply be ignored.\n     */\n    ANEURALNETWORKS_PADDING_VALID = 2,\n} PaddingCode;\n\n/**\n * Execution preferences.\n */\ntypedef enum {\n    /**\n     * Prefer executing in a way that minimizes battery drain.\n     * This is desirable for compilations that will be executed often.\n     */\n    ANEURALNETWORKS_PREFER_LOW_POWER = 0,\n    /**\n     * Prefer returning a single answer as fast as possible, even if this causes\n     * more power consumption.\n     */\n    ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER = 1,\n    /**\n     * Prefer maximizing the throughput of successive frames, for example when\n     * processing successive frames coming from the camera.\n     */\n    ANEURALNETWORKS_PREFER_SUSTAINED_SPEED = 2,\n} PreferenceCode;\n\n/**\n * Result codes.\n */\ntypedef enum {\n    ANEURALNETWORKS_NO_ERROR = 0,\n    ANEURALNETWORKS_OUT_OF_MEMORY = 1,\n    ANEURALNETWORKS_INCOMPLETE = 2,\n    ANEURALNETWORKS_UNEXPECTED_NULL = 3,\n    ANEURALNETWORKS_BAD_DATA = 4,\n    ANEURALNETWORKS_OP_FAILED = 5,\n    ANEURALNETWORKS_UNMAPPABLE = 5,\n    ANEURALNETWORKS_BAD_STATE = 6,\n} ResultCode;\n\n/**\n * For {@link ANeuralNetworksModel_setOperandValue}, values with a\n * length smaller or equal to this will be immediately copied into\n * the model. The size is in bytes.\n */\nenum {\n    ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES = 128\n};\n\n/**\n * ANeuralNetworksMemory is an opaque type that represents memory.\n *\n * This type is used to represent shared memory, memory mapped files,\n * and similar memories.\n *\n * By using shared memory, a program can efficiently communicate to the\n * runtime and drivers the tensors that define a model. See\n * {@link ANeuralNetworksModel_setOperandValueFromMemory}. An application\n * should typically create one shared memory object that contains every tensor\n * needed to define a model. {@link ANeuralNetworksMemory_createFromFd} can be\n * used to create shared memory from a file handle. {@link ANeuralNetworksMemory_createShared}\n * can be used to directly created shared memory.\n *\n * Memory objects can also be used to specify the input and output arguments of\n * an execution. See {@link ANeuralNetworksExecution_setInputFromMemory}\n * and {@link ANeuralNetworksExecution_setOutputFromMemory}.\n */\ntypedef struct ANeuralNetworksMemory ANeuralNetworksMemory;\n\n/**\n * ANeuralNetworksModel is an opaque type that contains a description of the\n * mathematical operations that constitute the model.\n *\n * <p>The model will be built by calling<ul>\n * <li>{@link ANeuralNetworksModel_create},</li>\n * <li>{@link ANeuralNetworksModel_addOperation},</li>\n * <li>{@link ANeuralNetworksModel_addOperand},</li>\n * </ul>\n *\n * A model is completed by calling {@link ANeuralNetworksModel_finish}.\n * A model is destroyed by calling {@link ANeuralNetworksModel_free}.\n *\n * <p>A model cannot be modified once {@link ANeuralNetworksModel_finish}\n * has been called on it.</p>\n *\n * <p>It is the application's responsibility to make sure that only one thread\n * modifies a model at a given time. It is however safe for more than one\n * thread to use the model once {@link ANeuralNetworksModel_finish} has returned.</p>\n *\n * <p>It is also the application's responsibility to ensure that there are no other\n * uses of the model after calling {@link ANeuralNetworksModel_free}.\n * This includes any compilation or execution object created using the model.</p>\n */\ntypedef struct ANeuralNetworksModel ANeuralNetworksModel;\n\n/**\n * ANeuralNetworksCompilation is an opaque type that can be used to compile\n * a machine learning model.\n *\n * <p>To use:<ul>\n *    <li>Create a new compilation instance by calling the\n *        {@link ANeuralNetworksCompilation_create} function.</li>\n *    <li>Set any desired properties on the compilation (for example,\n *        {@link ANeuralNetworksCompilation_setPreference}).</li>\n *    <li>Complete the compilation with {@link ANeuralNetworksCompilation_finish}.</li>\n *    <li>Use the compilation as many times as needed\n *        with {@link ANeuralNetworksExecution_create}.</li>\n *    <li>Destroy the compilation with {@link ANeuralNetworksCompilation_free}\n *        once all executions using the compilation have completed.</li></ul></p>\n *\n * A compilation is completed by calling {@link ANeuralNetworksCompilation_finish}.\n * A compilation is destroyed by calling {@link ANeuralNetworksCompilation_free}.\n *\n * <p>A compilation cannot be modified once {@link ANeuralNetworksCompilation_finish}\n * has been called on it.</p>\n *\n * <p>It is the application's responsibility to make sure that only\n * one thread modifies a compilation at a given time. It is however\n * safe for more than one thread to use the compilation once\n * {@link ANeuralNetworksCompilation_finish} has returned.</p>\n *\n * <p>It is also the application's responsibility to ensure that there are no other\n * uses of the compilation after calling {@link ANeuralNetworksCompilation_free}.\n * This includes any execution object created using the compilation.</p>\n */\ntypedef struct ANeuralNetworksCompilation ANeuralNetworksCompilation;\n\n/**\n * ANeuralNetworksExecution is an opaque type that can be used to apply a machine\n * learning model to a set of inputs.\n *\n * <p>To use:<ul>\n *    <li>Create a new execution instance by calling the\n *        {@link ANeuralNetworksExecution_create} function.</li>\n *    <li>Associate data to the model inputs with\n *        {@link ANeuralNetworksExecution_setInput} or\n *        {@link ANeuralNetworksExecution_setInputFromMemory}.</li>\n *    <li>Associate output buffers to the model outputs with\n *        {@link ANeuralNetworksExecution_setOutput} or\n *        {@link ANeuralNetworksExecution_setOutputFromMemory}.</li>\n *    <li>Apply the model with {@link ANeuralNetworksExecution_startCompute}.</li>\n *    <li>Wait for the execution to complete with {@link\n *        ANeuralNetworksEvent_wait}.</li>\n *    <li>Destroy the execution with\n *        {@link ANeuralNetworksExecution_free}.</li></ul></p>\n *\n * <p>An execution cannot be modified once {@link ANeuralNetworksExecution_startCompute}\n * has been called on it.</p>\n *\n * <p>An execution can be applied to a model with\n * {@link ANeuralNetworksExecution_startCompute} only once. Create new executions\n * to do new evaluations of the model.</p>\n *\n * <p>It is the application's responsibility to make sure that only one thread\n * modifies an execution at a given time. It is however safe for more than one\n * thread to use {@link ANeuralNetworksEvent_wait} at the same time.</p>\n *\n * <p>It is also the application's responsibility to ensure that there are no other\n * uses of the request after calling {@link ANeuralNetworksExecution_free}.</p>\n */\ntypedef struct ANeuralNetworksExecution ANeuralNetworksExecution;\n\n/**\n * ANeuralNetworksOperandType describes the type of an operand.\n * This structure is used to describe both scalars and tensors.\n */\ntypedef struct ANeuralNetworksOperandType {\n    /** The data type, e.g ANEURALNETWORKS_INT8. */\n    int32_t type;\n    /** The number of dimensions. It should be 0 for scalars. */\n    uint32_t dimensionCount;\n    /** The dimensions of the tensor. It should be nullptr for scalars. */\n    const uint32_t* dimensions;\n    /** These two fields are only used for quantized tensors.\n     * They should be zero for scalars and non-fixed point tensors.\n     * The dequantized value of each entry is (value - zeroPoint) * scale.\n     */\n    float scale;\n    int32_t zeroPoint;\n} ANeuralNetworksOperandType;\n\ntypedef int32_t ANeuralNetworksOperationType;\n\n/**\n * ANeuralNetworksEvent is an opaque type that represents an event\n * that will be signaled once an execution completes.\n */\ntypedef struct ANeuralNetworksEvent ANeuralNetworksEvent;\n\n\n/**\n * Creates a shared memory object from a file descriptor.\n *\n * The shared memory is backed by a file descriptor via mmap.\n * See {@link ANeuralNetworksMemory} for a description on how to use\n * this shared memory.\n *\n * @param size The requested size in bytes.\n *             Must not be larger than the file size.\n * @param prot The desired memory protection for the mapping.\n *             It is either PROT_NONE or the bitwise OR of one or\n *             more of the following flags: PROT_READ, PROT_WRITE.\n * @param fd The requested file descriptor.\n *           The file descriptor has to be mmap-able. The file\n *           descriptor will be duplicated.\n * @param offset The offset to the beginning of the file of the area to map.\n *               The offset has to be aligned to a page size.\n * @param memory The memory object to be created.\n *               Set to NULL if unsuccessful.\n *\n * @return ANEURALNETWORKS_NO_ERROR if the request completed normally.\n */\nint ANeuralNetworksMemory_createFromFd(size_t size, int protect, int fd, size_t offset,\n                                       ANeuralNetworksMemory** memory);\n\n/**\n * Delete a memory object.\n *\n * Destroys the object used by the run time to keep track of the memory.\n * This will free the underlying actual memory if no other code has open\n * handles to this memory.\n *\n * @param memory The memory object to be freed.\n */\nvoid ANeuralNetworksMemory_free(ANeuralNetworksMemory* memory);\n\n/**\n * Create an empty {@link ANeuralNetworksModel}.\n *\n * <p>This only creates the object. Computation is performed once\n * {@link ANeuralNetworksExecution_startCompute} is invoked.\n *\n * The model should be constructed with calls to\n * {@link ANeuralNetworksModel_addOperation} and\n * {@link ANeuralNetworksModel_addOperand}\n *\n * <p>{@link ANeuralNetworksModel_finish} should be called once the model\n * has been fully constructed.</p>\n *\n * <p>{@link ANeuralNetworksModel_free} should be called once the model\n * is no longer needed.</p>\n *\n * @param model The {@link ANeuralNetworksModel} to be created.\n *              Set to NULL if unsuccessful.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful.\n */\nint ANeuralNetworksModel_create(ANeuralNetworksModel** model);\n\n/**\n * Destroy a model.\n *\n * The model need not have been finished by a call to\n * {@link ANeuralNetworksModel_finish}.\n *\n * See {@link ANeuralNetworksModel} for information on multithreaded usage.\n *\n * @param model The model to be destroyed. Passing NULL is acceptable and\n *              results in no operation.\n */\nvoid ANeuralNetworksModel_free(ANeuralNetworksModel* model);\n\n/**\n * Indicate that we have finished modifying a model. Required before\n * calling {@link ANeuralNetworksCompilation_create}.\n *\n * An application is responsible to make sure that no other thread uses\n * the model at the same time.\n *\n * This function must only be called once for a given model.\n *\n * See {@link ANeuralNetworksModel} for information on multithreaded usage.\n *\n * @param model The model to be finished.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful.\n */\nint ANeuralNetworksModel_finish(ANeuralNetworksModel* model);\n\n/**\n * Add an operand to a model.\n *\n * The order in which the operands are added is important. The first one added\n * to a model will have the index value 0, the second 1, etc. These indexes are\n * used as operand identifiers in {@link ANeuralNetworksModel_addOperation},\n * {@link ANeuralNetworksExecution_setInput},\n * {@link ANeuralNetworksExecution_setInputFromMemory},\n * {@link ANeuralNetworksExecution_setOutput},\n * {@link ANeuralNetworksExecution_setOutputFromMemory} and\n * {@link ANeuralNetworksExecution_setOperandValue}.\n *\n * To build a model that can accomodate inputs of various sizes, as you may want\n * to do for a CNN, set the size of the dimensions that will vary at run time to 0.\n * If you do so, provide the full dimensions when calling\n * {@link ANeuralNetworksExecution_setInput} or {@link ANeuralNetworksExecution_setInputFromMemory}.\n *\n * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has been\n * called will return an error.\n *\n * See {@link ANeuralNetworksModel} for information on multithreaded usage.\n *\n * @param model The model to be modified.\n * @param type The {@link ANeuralNetworksOperandType} that describes the shape\n * of the operand.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful.\n */\nint ANeuralNetworksModel_addOperand(ANeuralNetworksModel* model,\n                                    const ANeuralNetworksOperandType* type);\n\n/**\n * Sets an operand to a constant value.\n *\n * Values of length smaller or equal to\n * {@link ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES}\n * are immediately copied into the model.\n *\n * For values of length greater than {@link ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES},\n * a pointer to the buffer is stored within the model. The application is responsible\n * for not changing the content of this region until all executions using this model\n * have completed. As the data may be copied during processing, modifying the data\n * after this call yields undefined results.\n *\n * For large tensors, using {@link ANeuralNetworksModel_setOperandValueFromMemory}\n * is likely to be more efficient.\n *\n * To indicate that an optional operand should be considered missing,\n * pass nullptr for buffer and 0 for length.\n *\n * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has been\n * called will return an error.\n *\n * See {@link ANeuralNetworksModel} for information on multithreaded usage.\n *\n * @param model The model to be modified.\n * @param index The index of the model operand we're setting.\n * @param buffer A pointer to the data to use.\n * @param length The size in bytes of the data value.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful.\n */\nint ANeuralNetworksModel_setOperandValue(ANeuralNetworksModel* model, int32_t index,\n                                         const void* buffer, size_t length);\n\n/**\n * Sets an operand to a value stored in a memory object.\n *\n * The content of the memory is not copied. A reference to that memory is stored\n * inside the model. The application is responsible for not changing the content\n * of the memory region until all executions using this model have completed.\n * As the data may be copied during processing, modifying the data after this call\n * yields undefined results.\n *\n * To indicate that an optional operand should be considered missing,\n * use {@link ANeuralNetworksModel_setOperandValue} instead, passing nullptr for buffer.\n *\n * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has been\n * called will return an error.\n *\n * See {@link ANeuralNetworksModel} for information on multithreaded usage.\n *\n * @param model The model to be modified.\n * @param index The index of the model operand we're setting.\n * @param buffer A pointer to the data to use.\n * @param memory The memory containing the data.\n * @param offset This specifies the location of the data within the memory.\n *               The offset is in bytes from the start of memory.\n * @param length The size in bytes of the data value.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful.\n */\nint ANeuralNetworksModel_setOperandValueFromMemory(ANeuralNetworksModel* model, int32_t index,\n                                                   const ANeuralNetworksMemory* memory,\n                                                   size_t offset, size_t length);\n\n/**\n * Add an operation to a model.\n *\n * @param model The model to be modified.\n * @param type The type of the operation.\n * @param inputCount The number of entries in the inputs array.\n * @param inputs An array of indexes identifying each operand.\n * @param outputCount The number of entries in the outputs array.\n * @param outputs An array of indexes identifying each operand.\n *\n * The operands specified by inputs and outputs must have been\n * previously added by calls to {@link ANeuralNetworksModel_addOperand}.\n *\n * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has been\n * called will return an error.\n *\n * See {@link ANeuralNetworksModel} for information on multithreaded usage.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful.\n */\nint ANeuralNetworksModel_addOperation(ANeuralNetworksModel* model,\n                                      ANeuralNetworksOperationType type, uint32_t inputCount,\n                                      const uint32_t* inputs, uint32_t outputCount,\n                                      const uint32_t* outputs);\n\n/**\n * Specfifies which operands will be the model's inputs and outputs.\n *\n * An operand cannot be used for both input and output. Doing so will\n * return an error.\n *\n * @param model The model to be modified.\n * @param inputCount The number of entries in the inputs array.\n * @param inputs An array of indexes identifying the input operands.\n * @param outputCount The number of entries in the outputs array.\n * @param outputs An array of indexes identifying the output operands.\n *\n * The operands specified by inputs and outputs must have been\n * previously added by calls to {@link ANeuralNetworksModel_addOperand}.\n *\n * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has been\n * called will return an error.\n *\n * See {@link ANeuralNetworksModel} for information on multithreaded usage.\n *\n */\nint ANeuralNetworksModel_identifyInputsAndOutputs(ANeuralNetworksModel* model, uint32_t inputCount,\n                                                  const uint32_t* inputs, uint32_t outputCount,\n                                                  const uint32_t* outputs);\n\n/**\n * Create a {@link ANeuralNetworksCompilation} to compile the given model.\n *\n * <p>This only creates the object. Compilation is only performed once\n * {@link ANeuralNetworksCompilation_finish} is invoked.</p>\n *\n * <p>{@link ANeuralNetworksCompilation_finish} should be called once\n * all desired properties have been set on the compilation.</p>\n *\n * <p>{@link ANeuralNetworksModel_free} should be called once the compilation\n * is no longer needed.</p>\n *\n * <p>The provided model must outlive the compilation.</p>\n *\n * The model must already have been finished by a call to\n * {@link ANeuralNetworksModel_finish}.\n *\n * See {@link ANeuralNetworksCompilation} for information on multithreaded usage.\n *\n * @param model The {@link ANeuralNetworksModel} to be compiled.\n * @param compilation The newly created object or NULL if unsuccessful.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA\n *         if the model is invalid.\n */\nint ANeuralNetworksCompilation_create(ANeuralNetworksModel* model,\n                                      ANeuralNetworksCompilation** compilation);\n\n/**\n * Destroy a compilation.\n *\n * The compilation need not have been finished by a call to\n * {@link ANeuralNetworksModel_finish}.\n *\n * See {@link ANeuralNetworksCompilation} for information on multithreaded usage.\n *\n * @param compilation The compilation to be destroyed. Passing NULL is acceptable and\n *                    results in no operation.\n */\nvoid ANeuralNetworksCompilation_free(ANeuralNetworksCompilation* compilation);\n\n/**\n * Sets the execution preference.\n *\n * <p>Provides guidance to the runtime when trade-offs are possible.</p>\n *\n * See {@link ANeuralNetworksCompilation} for information on multithreaded usage.\n *\n * @param compilation The compilation to be modified.\n * @param preference Either {@link PREFER_LOW_POWER},\n *                  {@link PREFER_SINGLE_FAST_ANSWER}, or\n *                  {@link PREFER_SUSTAINED_SPEED}.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful.\n */\nint ANeuralNetworksCompilation_setPreference(ANeuralNetworksCompilation* compilation,\n                                             int32_t preference);\n\n/**\n * Indicate that we have finished modifying a compilation. Required before\n * calling {@link ANeuralNetworksExecution_create}.\n *\n * An application is responsible to make sure that no other thread uses\n * the compilation at the same time.\n *\n * This function must only be called once for a given compilation.\n *\n * See {@link ANeuralNetworksCompilation} for information on multithreaded usage.\n *\n * @param compilation The compilation to be finished.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful.\n */\nint ANeuralNetworksCompilation_finish(ANeuralNetworksCompilation* compilation);\n\n/**\n * Create a {@link ANeuralNetworksExecution} to apply the given compilation.\n * This only creates the object. Computation is only performed once\n * {@link ANeuralNetworksExecution_startCompute} is invoked.\n *\n * <p>The provided compilation must outlive the execution.</p>\n *\n * See {@link ANeuralNetworksExecution} for information on multithreaded usage.\n *\n * @param compilation The {@link ANeuralNetworksCompilation} to be evaluated.\n * @param execution The newly created object or NULL if unsuccessful.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA\n *         if the compilation is invalid.\n */\nint ANeuralNetworksExecution_create(ANeuralNetworksCompilation* compilation,\n                                    ANeuralNetworksExecution** execution);\n\n/**\n * Destroy an execution.\n *\n * <p>If called on an execution for which\n * {@link ANeuralNetworksExecution_startCompute} has been called, the\n * function will return immediately but will mark the execution to be deleted\n * once the computation completes. The related {@link ANeuralNetworksEvent}\n * will be signaled and the {@link ANeuralNetworksEvent_wait} will return\n * ANEURALNETWORKS_ERROR_DELETED.\n *\n * See {@link ANeuralNetworksExecution} for information on multithreaded usage.\n *\n * @param execution The execution to be destroyed. Passing NULL is acceptable and\n *                  results in no operation.\n */\nvoid ANeuralNetworksExecution_free(ANeuralNetworksExecution* execution);\n\n/**\n * Associate a user buffer with an input of the model of the\n * {@link ANeuralNetworksExecution}.\n *\n * <p>The provided buffer must outlive the execution.</p>\n *\n * If the input is optional, you can indicate that it is omitted by\n * passing nullptr for buffer and 0 for length.\n *\n * See {@link ANeuralNetworksExecution} for information on multithreaded usage.\n *\n * @param execution The execution to be modified.\n * @param index The index of the input argument we are setting. It is\n *              an index into the lists passed to\n *              {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is not\n *              the index associated with {@link ANeuralNetworksModel_addOperand}.\n * @param type The type of the operand. This should be used to specify the\n *             dimensions that were set to 0 when the operand was added to the\n *             model. All other properties of the type must be the same as\n *             specified in the model. If the type is the same as specified\n *             when the model was built, NULL can be passed.\n * @param buffer The buffer containing the data.\n * @param length The length in bytes of the buffer.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if the\n *         name is not recognized or the buffer is too small for the input.\n */\nint ANeuralNetworksExecution_setInput(ANeuralNetworksExecution* execution, int32_t index,\n                                      const ANeuralNetworksOperandType* type, const void* buffer,\n                                      size_t length);\n\n/**\n * Associate part of a memory object with an input of the model of the\n * {@link ANeuralNetworksExecution}.\n *\n * <p>The provided memory must outlive the execution.</p>\n *\n * If the input is optional, you can indicate that it is omitted by\n * using @{Link ANeuralNetworks_setInput} instead, passing nullptr for buffer\n * and 0 for length.\n *\n * See {@link ANeuralNetworksExecution} for information on multithreaded usage.\n *\n * @param execution The execution to be modified.\n * @param index The index of the input argument we are setting. It is\n *              an index into the lists passed to\n *              {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is not\n *              the index associated with {@link ANeuralNetworksModel_addOperand}.\n * @param type The type of the operand. This can be used to specify the\n *             dimensions that were set to 0 when the operand was added to the\n *             model. All other values must be the same as specified in the\n *             model. If the type is the same as specified when the model\n *             was built, NULL can be passed.\n * @param memory The memory containing the data.\n * @param offset This specifies the location of the data whithin the memory.\n *               The offset is in bytes from the start of memory.\n * @param length The size in bytes of the data value.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if the\n *         name is not recognized or the buffer is too small for the input.\n */\nint ANeuralNetworksExecution_setInputFromMemory(ANeuralNetworksExecution* execution, int32_t index,\n                                                const ANeuralNetworksOperandType* type,\n                                                const ANeuralNetworksMemory* memory, size_t offset,\n                                                size_t length);\n\n/**\n * Associate a user buffer with an output of the model of the\n * {@link ANeuralNetworksExecution}.\n *\n * If the output is optional, you can indicate that it is omitted by\n * passing nullptr for buffer and 0 for length.\n *\n * <p>The provided buffer must outlive the execution.</p>\n *\n * See {@link ANeuralNetworksExecution} for information on multithreaded usage.\n *\n * @param execution The execution to be modified.\n * @param index The index of the output argument we are setting. It is\n *              an index into the lists passed to\n *              {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is not\n *              the index associated with {@link ANeuralNetworksModel_addOperand}.\n * @param type The type of the operand. This can be used to specify the\n *             dimensions that were set to 0 when the operand was added to the\n *             model. All other values must be the same as specified in the\n *             model. If the type is the same as specified when the model\n *             was built, NULL can be passed.\n * @param buffer The buffer where the data is to be written.\n * @param length The length in bytes of the buffer.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if the\n *         name is not recognized or the buffer is too small for the output.\n */\nint ANeuralNetworksExecution_setOutput(ANeuralNetworksExecution* execution, int32_t index,\n                                       const ANeuralNetworksOperandType* type, void* buffer,\n                                       size_t length);\n\n/**\n * Associate part of a memory object with an output of the model of the\n * {@link ANeuralNetworksExecution}.\n *\n * If the output is optional, you can indicate that it is omitted by\n * using @{Link ANeuralNetworks_setOutput} instead, passing nullptr for buffer\n * and 0 for length.\n *\n * <p>The provided memory must outlive the execution.</p>\n *\n * See {@link ANeuralNetworksExecution} for information on multithreaded usage.\n *\n * @param execution The execution to be modified.\n * @param index The index of the output argument we are setting. It is\n *              an index into the lists passed to\n *              {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is not\n *              the index associated with {@link ANeuralNetworksModel_addOperand}.\n * @param type The type of the operand. This can be used to specify the\n *             dimensions that were set to 0 when the operand was added to the\n *             model. All other values must be the same as specified in the\n *             model. If the type is the same as specified when the model\n *             was built, NULL can be passed.\n * @param memory The memory where the data is to be stored.\n * @param offset This specifies the location of the data whithin the memory.\n *               The offset is in bytes from the start of memory.\n * @param length The length in bytes of the data value.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if the\n *         name is not recognized or the buffer is too small for the output.\n */\nint ANeuralNetworksExecution_setOutputFromMemory(ANeuralNetworksExecution* execution, int32_t index,\n                                                 const ANeuralNetworksOperandType* type,\n                                                 const ANeuralNetworksMemory* memory, size_t offset,\n                                                 size_t length);\n\n/**\n * Schedule evaluation of the execution.\n *\n * <p>Schedules evaluation of the execution. Once the model has been\n * applied and the outputs are ready to be consumed, the returned event will be\n * signaled. Use {@link ANeuralNetworksEvent_wait} to wait for that event.\n * </p>\n *\n * Multiple executions can be scheduled and evaluated concurrently. The\n * runtime makes no guarantee on the ordering of completion of\n * executions. If it's important to the application, the application\n * should enforce the ordering by using\n * {@link ANeuralNetworksEvent_wait}.\n *\n * ANeuralNetworksEvent_wait must be called to recuperate the resources used\n * by the execution.\n *\n * See {@link ANeuralNetworksExecution} for information on multithreaded usage.\n *\n * @param execution The execution to be scheduled and executed.\n * @param event The event that will be signaled on completion. event is set to\n *              NULL if there's an error.\n *\n * @return ANEURALNETWORKS_NO_ERROR if successful.\n */\nint ANeuralNetworksExecution_startCompute(ANeuralNetworksExecution* execution,\n                                          ANeuralNetworksEvent** event);\n\n/**\n * Waits until the execution completes.\n *\n * More than one thread can wait on an event. When the execution completes,\n * all threads will be released.\n *\n * See {@link ANeuralNetworksExecution} for information on multithreaded usage.\n *\n * @return ANEURALNETWORKS_NO_ERROR if the execution completed normally.\n */\nint ANeuralNetworksEvent_wait(ANeuralNetworksEvent* event);\n\n/**\n * Destroys the event.\n *\n * See {@link ANeuralNetworksExecution} for information on multithreaded usage.\n */\nvoid ANeuralNetworksEvent_free(ANeuralNetworksEvent* event);\n\n__END_DECLS\n\n#endif  //  __ANDROID_API__ >= 27\n\n#endif  // ANDROID_ML_NN_RUNTIME_NEURAL_NETWORKS_H\n\n/** @} */\n"
  },
  {
    "path": "caffe2/mobile/contrib/nnapi/dlnnapi.c",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <dlfcn.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"dlnnapi.h\"\n\n#define DLNNAPI_DEBUG_LOG 0\n#if DLNNAPI_DEBUG_LOG\n#include <android/log.h>\n#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, \"NNAPI\", __VA_ARGS__)\n#endif\n\n#define TAG_API_27 \"\\x01\"\n\n/* clang-format off */\nstatic const char function_names[] =\n    TAG_API_27 \"ANeuralNetworksMemory_createFromFd\\0\"\n    TAG_API_27 \"ANeuralNetworksMemory_free\\0\"\n    TAG_API_27 \"ANeuralNetworksModel_create\\0\"\n    TAG_API_27 \"ANeuralNetworksModel_finish\\0\"\n    TAG_API_27 \"ANeuralNetworksModel_free\\0\"\n    TAG_API_27 \"ANeuralNetworksCompilation_create\\0\"\n    TAG_API_27 \"ANeuralNetworksCompilation_free\\0\"\n    TAG_API_27 \"ANeuralNetworksCompilation_setPreference\\0\"\n    TAG_API_27 \"ANeuralNetworksCompilation_finish\\0\"\n    TAG_API_27 \"ANeuralNetworksModel_addOperand\\0\"\n    TAG_API_27 \"ANeuralNetworksModel_setOperandValue\\0\"\n    TAG_API_27 \"ANeuralNetworksModel_setOperandValueFromMemory\\0\"\n    TAG_API_27 \"ANeuralNetworksModel_addOperation\\0\"\n    TAG_API_27 \"ANeuralNetworksModel_identifyInputsAndOutputs\\0\"\n    TAG_API_27 \"ANeuralNetworksExecution_create\\0\"\n    TAG_API_27 \"ANeuralNetworksExecution_free\\0\"\n    TAG_API_27 \"ANeuralNetworksExecution_setInput\\0\"\n    TAG_API_27 \"ANeuralNetworksExecution_setInputFromMemory\\0\"\n    TAG_API_27 \"ANeuralNetworksExecution_setOutput\\0\"\n    TAG_API_27 \"ANeuralNetworksExecution_setOutputFromMemory\\0\"\n    TAG_API_27 \"ANeuralNetworksExecution_startCompute\\0\"\n    TAG_API_27 \"ANeuralNetworksEvent_wait\\0\"\n    TAG_API_27 \"ANeuralNetworksEvent_free\\0\";\n/* clang-format on */\n\nbool dlnnapi_load(struct dlnnapi* nnapi, uint32_t flags) {\n  if (nnapi == NULL) {\n    return false;\n  }\n\n  memset(nnapi, 0, sizeof(struct dlnnapi));\n  if (!(flags & DLNNAPI_FLAG_VERSION_27)) {\n    /* No supported NNAPI version is requested */\n    return false;\n  }\n\n  /* Clear libdl error state */\n  dlerror();\n\n  nnapi->handle = dlopen(\"libneuralnetworks.so\", RTLD_LAZY | RTLD_LOCAL);\n  if (nnapi->handle != NULL) {\n#if DLNNAPI_DEBUG_LOG\n    LOGI(\"note: loaded libneuralnetworks.so\\n\");\n#endif\n\n    uint8_t version_flags = (uint8_t)(flags & DLNNAPI_FLAG_VERSION_MASK);\n    const char* function_name = function_names;\n    for (size_t i = 0; i < DLNNAPI_FUNCTION_COUNT; i++) {\n      const uint8_t tag = (uint8_t)*function_name++;\n      if ((tag & version_flags) != 0) {\n        void* function = dlsym(nnapi->handle, function_name);\n        if (function == NULL) {\n#if DLNNAPI_DEBUG_LOG\n          LOGI(\n              \"note: failed to locate %s in libneuralnetworks.so: %s\\n\",\n              function_name,\n              dlerror());\n#endif\n          version_flags &= ~tag;\n          if (version_flags == 0) {\n            goto failed;\n          }\n        }\n        nnapi->functions[i] = function;\n      }\n\n      function_name += strlen(function_name) + 1;\n    }\n    nnapi->flags = (uint32_t)version_flags;\n\n    return true;\n  }\n#if DLNNAPI_DEBUG_LOG\n  LOGI(\"note: failed to load libneuralnetworks.so: %s\\n\", dlerror());\n#endif\n\nfailed:\n  dlnnapi_free(nnapi);\n  return false;\n}\n\nvoid dlnnapi_free(struct dlnnapi* nnapi) {\n  if (nnapi != NULL) {\n    if (nnapi->handle != NULL) {\n      /* Clear libdl error state */\n      dlerror();\n      if (dlclose(nnapi->handle) != 0) {\n#if DLNNAPI_DEBUG_LOG\n        LOGI(\"note: failed to unload libneuralnetworks.so: %s\\n\", dlerror());\n#endif\n      }\n    }\n    memset(nnapi, 0, sizeof(struct dlnnapi));\n  }\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/nnapi/dlnnapi.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdbool.h>\n#include <stdint.h>\n\n#include \"NeuralNetworks.h\"\n\n#define DLNNAPI_FUNCTION_COUNT 23\n\n#define DLNNAPI_FLAG_VERSION_MASK 0xFF\n/* Android 8.1, API 27 version */\n#define DLNNAPI_FLAG_VERSION_27 0x01\n\n/* clang-format off */\n/* nn api function types */\ntypedef int (*ANeuralNetworksMemory_createFromFd_fn)(size_t size, int protect, int fd, size_t offset, ANeuralNetworksMemory** memory);\n\ntypedef void (*ANeuralNetworksMemory_free_fn)(ANeuralNetworksMemory* memory);\n\ntypedef int (*ANeuralNetworksModel_create_fn)(ANeuralNetworksModel** model);\n\ntypedef int (*ANeuralNetworksModel_finish_fn)(ANeuralNetworksModel* model);\n\ntypedef void (*ANeuralNetworksModel_free_fn)(ANeuralNetworksModel* model);\n\ntypedef int (*ANeuralNetworksCompilation_create_fn)(ANeuralNetworksModel* model, ANeuralNetworksCompilation** compilation);\n\ntypedef void (*ANeuralNetworksCompilation_free_fn)(ANeuralNetworksCompilation* compilation);\n\ntypedef int (*ANeuralNetworksCompilation_setPreference_fn)(ANeuralNetworksCompilation* compilation, int32_t preference);\n\ntypedef int (*ANeuralNetworksCompilation_finish_fn)(ANeuralNetworksCompilation* compilation);\n\ntypedef int (*ANeuralNetworksModel_addOperand_fn)(ANeuralNetworksModel* model, const ANeuralNetworksOperandType* type);\n\ntypedef int (*ANeuralNetworksModel_setOperandValue_fn)(ANeuralNetworksModel* model, int32_t index, const void* buffer, size_t length);\n\ntypedef int (*ANeuralNetworksModel_setOperandValueFromMemory_fn)(ANeuralNetworksModel* model, int32_t index, const ANeuralNetworksMemory* memory, size_t offset, size_t length);\n\ntypedef int (*ANeuralNetworksModel_addOperation_fn)(ANeuralNetworksModel* model, ANeuralNetworksOperationType type, uint32_t inputCount, const uint32_t* inputs, uint32_t outputCount, const uint32_t* outputs);\n\ntypedef int (*ANeuralNetworksModel_identifyInputsAndOutputs_fn)(ANeuralNetworksModel* model, uint32_t inputCount, const uint32_t* inputs, uint32_t outputCount, const uint32_t* outputs);\n\ntypedef int (*ANeuralNetworksExecution_create_fn)(ANeuralNetworksCompilation* compilation, ANeuralNetworksExecution** execution);\n\ntypedef void (*ANeuralNetworksExecution_free_fn)(ANeuralNetworksExecution* execution);\n\ntypedef int (*ANeuralNetworksExecution_setInput_fn)(ANeuralNetworksExecution* execution, int32_t index, const ANeuralNetworksOperandType* type, const void* buffer, size_t length);\n\ntypedef int (*ANeuralNetworksExecution_setInputFromMemory_fn)(ANeuralNetworksExecution* execution, int32_t index, const ANeuralNetworksOperandType* type, const ANeuralNetworksMemory* memory, size_t offset, size_t length);\n\ntypedef int (*ANeuralNetworksExecution_setOutput_fn)(ANeuralNetworksExecution* execution, int32_t index, const ANeuralNetworksOperandType* type, void* buffer, size_t length);\n\ntypedef int (*ANeuralNetworksExecution_setOutputFromMemory_fn)(ANeuralNetworksExecution* execution, int32_t index, const ANeuralNetworksOperandType* type, const ANeuralNetworksMemory* memory, size_t offset, size_t length);\n\ntypedef int (*ANeuralNetworksExecution_startCompute_fn)(ANeuralNetworksExecution* execution, ANeuralNetworksEvent** event);\n\ntypedef int (*ANeuralNetworksEvent_wait_fn)(ANeuralNetworksEvent* event);\n\ntypedef void (*ANeuralNetworksEvent_free_fn)(ANeuralNetworksEvent* event);\n\nstruct dlnnapi {\n\tvoid* handle;\n\tuint32_t flags;\n\tunion {\n\t\tstruct {\n\t\t\t/* ndk-r16b */\n\t\t\tANeuralNetworksMemory_createFromFd_fn             ANeuralNetworksMemory_createFromFd;\n\t\t\tANeuralNetworksMemory_free_fn                     ANeuralNetworksMemory_free;\n\t\t\tANeuralNetworksModel_create_fn                    ANeuralNetworksModel_create;\n\t\t\tANeuralNetworksModel_finish_fn                    ANeuralNetworksModel_finish;\n\t\t\tANeuralNetworksModel_free_fn                      ANeuralNetworksModel_free;\n\t\t\tANeuralNetworksCompilation_create_fn              ANeuralNetworksCompilation_create;\n\t\t\tANeuralNetworksCompilation_free_fn                ANeuralNetworksCompilation_free;\n\t\t\tANeuralNetworksCompilation_setPreference_fn       ANeuralNetworksCompilation_setPreference;\n\t\t\tANeuralNetworksCompilation_finish_fn              ANeuralNetworksCompilation_finish;\n\t\t\tANeuralNetworksModel_addOperand_fn                ANeuralNetworksModel_addOperand;\n\t\t\tANeuralNetworksModel_setOperandValue_fn           ANeuralNetworksModel_setOperandValue;\n\t\t\tANeuralNetworksModel_setOperandValueFromMemory_fn ANeuralNetworksModel_setOperandValueFromMemory;\n\t\t\tANeuralNetworksModel_addOperation_fn              ANeuralNetworksModel_addOperation;\n\t\t\tANeuralNetworksModel_identifyInputsAndOutputs_fn  ANeuralNetworksModel_identifyInputsAndOutputs;\n\t\t\tANeuralNetworksExecution_create_fn                ANeuralNetworksExecution_create;\n\t\t\tANeuralNetworksExecution_free_fn                  ANeuralNetworksExecution_free;\n\t\t\tANeuralNetworksExecution_setInput_fn              ANeuralNetworksExecution_setInput;\n\t\t\tANeuralNetworksExecution_setInputFromMemory_fn    ANeuralNetworksExecution_setInputFromMemory;\n\t\t\tANeuralNetworksExecution_setOutput_fn             ANeuralNetworksExecution_setOutput;\n\t\t\tANeuralNetworksExecution_setOutputFromMemory_fn   ANeuralNetworksExecution_setOutputFromMemory;\n\t\t\tANeuralNetworksExecution_startCompute_fn          ANeuralNetworksExecution_startCompute;\n\t\t\tANeuralNetworksEvent_wait_fn                      ANeuralNetworksEvent_wait;\n\t\t\tANeuralNetworksEvent_free_fn                      ANeuralNetworksEvent_free;\n\t\t};\n\t\tvoid* functions[DLNNAPI_FUNCTION_COUNT];\n\t};\n};\n/* clang-format on */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nbool dlnnapi_load(struct dlnnapi* nnapi, uint32_t flags);\nvoid dlnnapi_free(struct dlnnapi* nnapi);\n\n#ifdef __cplusplus\n} /* extern \"C\" */\n#endif\n"
  },
  {
    "path": "caffe2/mobile/contrib/nnapi/nnapi.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\n#include \"nnapi.h\"\n\nnamespace {\n// Bug: ANEURALNETWORKS_UNMAPPABLE and ANEURALNETWORKS_OP_FAILED share the same\n// enum value\nvoid reportError(int result_code) {\n  switch (result_code) {\n    case ANEURALNETWORKS_NO_ERROR:\n      break;\n    case ANEURALNETWORKS_OUT_OF_MEMORY:\n      CAFFE_THROW(\"out of memory\");\n    case ANEURALNETWORKS_INCOMPLETE:\n      CAFFE_THROW(\"incomplete\");\n    case ANEURALNETWORKS_UNEXPECTED_NULL:\n      CAFFE_THROW(\"unexpected null\");\n    case ANEURALNETWORKS_BAD_DATA:\n      CAFFE_THROW(\"bad data\");\n    case ANEURALNETWORKS_OP_FAILED:\n      CAFFE_THROW(\"op failed or unmappable\");\n    case ANEURALNETWORKS_BAD_STATE:\n      CAFFE_THROW(\"bad state\");\n    default:\n      CAFFE_THROW(\"unknown error\");\n  }\n}\n} // namespace\n\nnamespace caffe2 {\n\nbool NNApi::loadNNApiLibrary() {\n  return dlnnapi_load(&libnnapi_, DLNNAPI_FLAG_VERSION_27);\n}\n\nNNApi::~NNApi() {\n  if (run_end_) {\n    libnnapi_.ANeuralNetworksEvent_free(run_end_);\n  }\n  if (run_) {\n    libnnapi_.ANeuralNetworksExecution_free(run_);\n  }\n  if (compilation_) {\n    libnnapi_.ANeuralNetworksCompilation_free(compilation_);\n  }\n  if (model_) {\n    libnnapi_.ANeuralNetworksModel_free(model_);\n  }\n}\n\nbool NNApi::run(const TensorVector& inputs, TensorVector* outputs) {\n  CAFFE_ENFORCE(inputs.size() <= run_net_.external_input_size());\n  try {\n    init(inputs, outputs);\n  } catch (const std::exception& e) {\n    LOG(ERROR) << \"Error duing model initialization: \" << e.what();\n    return false;\n  }\n\n  try {\n    VLOG(1) << \"Start compute\";\n    int result_code =\n        libnnapi_.ANeuralNetworksExecution_startCompute(run_, &run_end_);\n    if (result_code != ANEURALNETWORKS_NO_ERROR) {\n      reportError(result_code);\n    }\n    result_code = libnnapi_.ANeuralNetworksEvent_wait(run_end_);\n    if (result_code != ANEURALNETWORKS_NO_ERROR) {\n      reportError(result_code);\n    }\n    VLOG(1) << \"Finish compute\";\n  } catch (const std::exception& e) {\n    LOG(ERROR) << \"Error during model run: \" << e.what();\n    return false;\n  }\n  return true;\n}\n\nvoid NNApi::getConvPoolArgs(const ArgumentHelper& helper, ConvPoolArgs& args) {\n  std::vector<int> kernel(helper.GetRepeatedArgument<int>(\"kernels\"));\n  std::vector<int> stride(helper.GetRepeatedArgument<int>(\"strides\"));\n  std::vector<int> pads(helper.GetRepeatedArgument<int>(\"pads\"));\n\n  // Get old arguments values\n  if (helper.HasArgument(\"kernel\")) {\n    kernel.resize(2, helper.GetSingleArgument<int>(\"kernel\", 0));\n  } else if (helper.HasArgument(\"kernelh\") && helper.HasArgument(\"kernelw\")) {\n    kernel.push_back(helper.GetSingleArgument<int>(\"kernelh\", 0));\n    kernel.push_back(helper.GetSingleArgument<int>(\"kernelw\", 0));\n  }\n\n  if (helper.HasArgument(\"stride\")) {\n    stride.resize(2, helper.GetSingleArgument<int>(\"stride\", 0));\n  } else if (helper.HasArgument(\"stride_h\") && helper.HasArgument(\"stride_w\")) {\n    stride.push_back(helper.GetSingleArgument<int>(\"stride_h\", 0));\n    stride.push_back(helper.GetSingleArgument<int>(\"stride_w\", 0));\n  }\n\n  if (helper.HasArgument(\"pad\")) {\n    pads.resize(4, helper.GetSingleArgument<int>(\"pad\", 0));\n  } else if (\n      helper.HasArgument(\"pad_t\") && helper.HasArgument(\"pad_l\") &&\n      helper.HasArgument(\"pad_b\") && helper.HasArgument(\"pad_r\")) {\n    pads.push_back(helper.GetSingleArgument<int>(\"pad_t\", 0));\n    pads.push_back(helper.GetSingleArgument<int>(\"pad_l\", 0));\n    pads.push_back(helper.GetSingleArgument<int>(\"pad_b\", 0));\n    pads.push_back(helper.GetSingleArgument<int>(\"pad_r\", 0));\n  }\n\n  // Commit values\n  args.kernel_h = kernel.size() > 0 ? kernel[0] : 1;\n  args.kernel_w = kernel.size() > 1 ? kernel[1] : args.kernel_h;\n  args.stride_x = stride.size() > 0 ? stride[0] : 1;\n  args.stride_y = stride.size() > 1 ? stride[1] : 1;\n  args.pad_t = pads.size() > 0 ? pads[0] : 0;\n  args.pad_l = pads.size() > 1 ? pads[1] : 0;\n  args.pad_b = pads.size() > 2 ? pads[2] : 0;\n  args.pad_r = pads.size() > 3 ? pads[3] : 0;\n}\n\nvoid NNApi::addPooling(\n    const OperatorDef& op,\n    OperationCode op_code,\n    bool fuse_relu)\n// clang-format off\n{\n  // clang-format on\n  VLOG(1) << \"Add AveragePool to NN model\";\n  CAFFE_ENFORCE_EQ(op.input_size(), 1);\n  CAFFE_ENFORCE_EQ(op.output_size(), 1);\n  ArgumentHelper helper(op);\n  StorageOrder order = StringToStorageOrder(\n      helper.GetSingleArgument<std::string>(\"order\", \"NCHW\"));\n  if (order == NCHW) {\n    CAFFE_THROW(\"NN API supports NHWC only\");\n  }\n\n  ConvPoolArgs args;\n  getConvPoolArgs(helper, args);\n  CAFFE_ENFORCE_EQ(\n      args.stride_x,\n      args.stride_y,\n      \"NN API only supports stride_x == stride_y\");\n\n  // add input operands to model\n  const uint32_t input_indices_count = 10;\n  const uint32_t output_indices_count = 1;\n  uint32_t input_indices[input_indices_count];\n  uint32_t output_indices[output_indices_count];\n\n  uint32_t idx = 0;\n  // input\n  const std::string& input = op.input(0);\n  const std::vector<uint32_t>& input_dims = tensor_dims_[input];\n  input_indices[idx++] = operand_map_[input];\n\n  CAFFE_ENFORCE_EQ(input_dims.size(), 4);\n  uint32_t batches = input_dims[0];\n  uint32_t input_height = input_dims[1];\n  uint32_t input_width = input_dims[2];\n  uint32_t channel = input_dims[3];\n\n  // pads in the order of left, right, top, bottom\n  input_indices[idx++] = addScalarOperand(args.pad_l);\n  input_indices[idx++] = addScalarOperand(args.pad_r);\n  input_indices[idx++] = addScalarOperand(args.pad_t);\n  input_indices[idx++] = addScalarOperand(args.pad_b);\n\n  // strides\n  input_indices[idx++] = addScalarOperand(args.stride_x);\n  input_indices[idx++] = addScalarOperand(args.stride_y);\n\n  // kernel size\n  input_indices[idx++] = addScalarOperand(args.kernel_h);\n  input_indices[idx++] = addScalarOperand(args.kernel_w);\n\n  // fuse relu\n  FuseCode fuse = fuse_relu ? FuseCode::ANEURALNETWORKS_FUSED_RELU\n                            : FuseCode::ANEURALNETWORKS_FUSED_NONE;\n  input_indices[idx] = addScalarOperand(fuse);\n\n  // output\n  uint32_t output_height =\n      (input_height - args.kernel_h + args.pad_t + args.pad_b) / args.stride_y +\n      1;\n  uint32_t output_width =\n      (input_width - args.kernel_w + args.pad_l + args.pad_r) / args.stride_x +\n      1;\n\n  float output_scale = helper.GetSingleArgument<float>(\"output_scale\", 1.0);\n  int output_zero_point = helper.GetSingleArgument<int>(\"output_zero_point\", 0);\n\n  std::vector<uint32_t> dims({batches, output_height, output_width, channel});\n  output_indices[0] = addTensorOperand(\n      op.output(0), tensor_type_, dims, output_scale, output_zero_point);\n\n  int result_code = libnnapi_.ANeuralNetworksModel_addOperation(\n      model_, op_code, input_indices_count, input_indices, 1, output_indices);\n  if (result_code != ANEURALNETWORKS_NO_ERROR) {\n    reportError(result_code);\n  }\n}\n\nvoid NNApi::addConv(const OperatorDef& op, bool fuse_relu) {\n  VLOG(1) << \"Add Conv to NN model\";\n  CAFFE_ENFORCE_EQ(op.input_size(), 3);\n  CAFFE_ENFORCE_EQ(op.output_size(), 1);\n\n  ArgumentHelper helper(op);\n  StorageOrder order = StringToStorageOrder(\n      helper.GetSingleArgument<std::string>(\"order\", \"NCHW\"));\n  CAFFE_ENFORCE_EQ(order, NHWC, \"NN API supports NHWC only\");\n\n  // input\n  const std::string& input = op.input(0);\n  const std::vector<uint32_t>& input_dims = tensor_dims_[input];\n\n  CAFFE_ENFORCE_EQ(input_dims.size(), 4);\n  uint32_t batches = input_dims[0];\n  uint32_t input_height = input_dims[1];\n  uint32_t input_width = input_dims[2];\n  uint32_t input_channel = input_dims[3];\n\n  uint32_t group = helper.GetSingleArgument<int>(\"group\", 1);\n\n  bool run_depthwise = false;\n  if (group > 1) {\n    CAFFE_ENFORCE_EQ(\n        group,\n        input_channel,\n        \"NN API doesn't support non-depthwise convolution with groups\");\n    run_depthwise = true;\n  }\n\n  ConvPoolArgs args;\n  getConvPoolArgs(helper, args);\n\n  CAFFE_ENFORCE_EQ(\n      args.stride_x,\n      args.stride_y,\n      \"NN API only supports stride_x == stride_y\");\n\n  vector<int> dilation(helper.GetRepeatedArgument<int>(\"dilations\"));\n  if (helper.HasArgument(\"dilation\")) {\n    dilation.resize(2, helper.GetSingleArgument<int>(\"dilation\", 0));\n  } else if (\n      helper.HasArgument(\"dilationh\") && helper.HasArgument(\"dilationw\")) {\n    dilation.push_back(helper.GetSingleArgument<int>(\"dilation_h\", 0));\n    dilation.push_back(helper.GetSingleArgument<int>(\"dilation_w\", 0));\n  }\n\n  for (auto d : dilation) {\n    CAFFE_ENFORCE_EQ(d, 1, \"NN API only supports dialation == 1\");\n  }\n\n  // add input operands to model\n  const uint32_t input_indices_count = run_depthwise ? 11 : 10;\n  const uint32_t output_indices_count = 1;\n  uint32_t input_indices[input_indices_count];\n  uint32_t output_indices[output_indices_count];\n\n  uint32_t idx = 0;\n  // input\n  input_indices[idx++] = operand_map_[input];\n\n  // weight\n  const std::string& weight_name = op.input(1);\n  const auto& weight = ws_.GetBlob(weight_name)->Get<TensorCPU>();\n  std::vector<uint32_t> weight_dims;\n  for (auto dim : weight.dims()) {\n    weight_dims.push_back(dim);\n  }\n  CAFFE_ENFORCE_EQ(weight_dims.size(), 4);\n  uint32_t num_kernels = weight_dims[0];\n  uint32_t kernel_h = weight_dims[1];\n  uint32_t kernel_w = weight_dims[2];\n  uint32_t kernel_depth = weight_dims[3];\n  CAFFE_ENFORCE_EQ(input_channel, kernel_depth);\n  if (run_depthwise) {\n    CAFFE_ENFORCE_EQ(num_kernels, 1);\n  }\n\n  float weight_scale = helper.GetSingleArgument<float>(\"weight_scale\", 1.0);\n  int weight_zero_point = helper.GetSingleArgument<int>(\"weight_zero_point\", 0);\n\n  uint32_t weight_idx = addTensorOperand(\n      weight_name, tensor_type_, weight_dims, weight_scale, weight_zero_point);\n\n  int result_code = libnnapi_.ANeuralNetworksModel_setOperandValue(\n      model_, weight_idx, weight.raw_data(), weight.nbytes());\n  if (result_code != ANEURALNETWORKS_NO_ERROR) {\n    reportError(result_code);\n  }\n  input_indices[idx++] = weight_idx;\n\n  // bias\n  const std::string& bias_name = op.input(2);\n  const auto& bias = ws_.GetBlob(bias_name)->Get<TensorCPU>();\n  std::vector<uint32_t> bias_dims;\n  CAFFE_ENFORCE_EQ(bias.ndim(), 1);\n  uint32_t bias_size = bias.dim(0);\n  if (!run_depthwise) {\n    CAFFE_ENFORCE_EQ(num_kernels, bias_size);\n  } else {\n    CAFFE_ENFORCE_EQ(kernel_depth, bias_size);\n  }\n  bias_dims.push_back(bias_size);\n\n  OperandCode bias_type = tensor_type_ == ANEURALNETWORKS_TENSOR_FLOAT32\n      ? ANEURALNETWORKS_TENSOR_FLOAT32\n      : ANEURALNETWORKS_TENSOR_INT32;\n  if (bias_type == ANEURALNETWORKS_TENSOR_FLOAT32) {\n    CAFFE_ENFORCE(bias.IsType<float>());\n  } else if (bias_type == ANEURALNETWORKS_TENSOR_INT32) {\n    CAFFE_ENFORCE(bias.IsType<int>());\n  }\n  uint32_t bias_idx = addTensorOperand(bias_name, bias_type, bias_dims);\n\n  result_code = libnnapi_.ANeuralNetworksModel_setOperandValue(\n      model_, bias_idx, bias.raw_data(), bias.nbytes());\n  if (result_code != ANEURALNETWORKS_NO_ERROR) {\n    reportError(result_code);\n  }\n  input_indices[idx++] = bias_idx;\n\n  // pads in the order of left, right, top, bottom\n  input_indices[idx++] = addScalarOperand(args.pad_l);\n  input_indices[idx++] = addScalarOperand(args.pad_r);\n  input_indices[idx++] = addScalarOperand(args.pad_t);\n  input_indices[idx++] = addScalarOperand(args.pad_b);\n\n  // strides\n  input_indices[idx++] = addScalarOperand(args.stride_x);\n  input_indices[idx++] = addScalarOperand(args.stride_y);\n\n  // depth_wise\n  if (run_depthwise) {\n    // depthwise multiplier == 1\n    input_indices[idx++] = addScalarOperand(1);\n  }\n\n  // fuse relu\n  FuseCode fuse = fuse_relu ? FuseCode::ANEURALNETWORKS_FUSED_RELU\n                            : FuseCode::ANEURALNETWORKS_FUSED_NONE;\n  input_indices[idx] = addScalarOperand(fuse);\n\n  // output\n  uint32_t output_channel = run_depthwise ? kernel_depth : num_kernels;\n  uint32_t output_height =\n      (input_height - args.kernel_h + args.pad_t + args.pad_b) / args.stride_y +\n      1;\n  uint32_t output_width =\n      (input_width - args.kernel_w + args.pad_l + args.pad_r) / args.stride_x +\n      1;\n\n  float output_scale = helper.GetSingleArgument<float>(\"output_scale\", 1.0);\n  int output_zero_point = helper.GetSingleArgument<int>(\"output_zero_point\", 0);\n\n  std::vector<uint32_t> dims(\n      {batches, output_height, output_width, output_channel});\n  output_indices[0] = addTensorOperand(\n      op.output(0), tensor_type_, dims, output_scale, output_zero_point);\n  if (run_depthwise) {\n    CAFFE_ENFORCE_EQ(input_indices_count, 11);\n    result_code = libnnapi_.ANeuralNetworksModel_addOperation(\n        model_,\n        ANEURALNETWORKS_DEPTHWISE_CONV_2D,\n        input_indices_count,\n        input_indices,\n        output_indices_count,\n        output_indices);\n    if (result_code != ANEURALNETWORKS_NO_ERROR) {\n      reportError(result_code);\n    }\n  } else {\n    CAFFE_ENFORCE_EQ(input_indices_count, 10);\n    result_code = libnnapi_.ANeuralNetworksModel_addOperation(\n        model_,\n        ANEURALNETWORKS_CONV_2D,\n        input_indices_count,\n        input_indices,\n        output_indices_count,\n        output_indices);\n    if (result_code != ANEURALNETWORKS_NO_ERROR) {\n      reportError(result_code);\n    }\n  }\n}\n\nvoid NNApi::addRelu(const OperatorDef& op) {\n  VLOG(1) << \"Add Relu to NN model\";\n  CAFFE_ENFORCE_EQ(op.input_size(), 1);\n  CAFFE_ENFORCE_EQ(op.output_size(), 1);\n  const std::string& input = op.input(0);\n  uint32_t input_idx = operand_map_[input];\n\n  ArgumentHelper helper(op);\n  float output_scale = helper.GetSingleArgument<float>(\"output_scale\", 1.0);\n  int output_zero_point = helper.GetSingleArgument<int>(\"output_zero_point\", 0);\n\n  uint32_t output_idx = addTensorOperand(\n      op.output(0),\n      tensor_type_,\n      tensor_dims_[input],\n      output_scale,\n      output_zero_point);\n\n  int result_code = libnnapi_.ANeuralNetworksModel_addOperation(\n      model_, ANEURALNETWORKS_RELU, 1, &input_idx, 1, &output_idx);\n  if (result_code != ANEURALNETWORKS_NO_ERROR) {\n    reportError(result_code);\n  }\n}\n\nvoid NNApi::addSoftmax(const OperatorDef& op) {\n  VLOG(1) << \"Add Softmax to NN model\";\n  ArgumentHelper helper(op);\n  CAFFE_ENFORCE_EQ(\n      helper.GetSingleArgument<int>(\"axis\", 1),\n      1,\n      \"NN API only supports axis == 1\");\n\n  uint32_t input_indices[2];\n  const std::string& input = op.input(0);\n  input_indices[0] = operand_map_[input];\n  const auto& input_dims = tensor_dims_[input];\n  CAFFE_ENFORCE(\n      input_dims.size() == 2 || input_dims.size() == 4,\n      \"Supported tensor rank: 2 or 4\");\n\n  // the positive scaling factor for the exponent, beta\n  const float scale = 1.0;\n  input_indices[1] = addFloatOperand(scale);\n\n  float output_scale = helper.GetSingleArgument<float>(\"output_scale\", 1.0);\n  int output_zero_point = helper.GetSingleArgument<int>(\"output_zero_point\", 0);\n  if (tensor_type_ == ANEURALNETWORKS_TENSOR_QUANT8_ASYMM) {\n    CAFFE_ENFORCE_EQ(output_scale, 1.f / 256);\n    CAFFE_ENFORCE_EQ(output_zero_point, 0);\n  }\n  uint32_t output_idx = addTensorOperand(\n      op.output(0),\n      tensor_type_,\n      tensor_dims_[input],\n      output_scale,\n      output_zero_point);\n\n  int result_code = libnnapi_.ANeuralNetworksModel_addOperation(\n      model_, ANEURALNETWORKS_SOFTMAX, 2, input_indices, 1, &output_idx);\n  if (result_code != ANEURALNETWORKS_NO_ERROR) {\n    reportError(result_code);\n  }\n}\n\n// int32_t\nuint32_t NNApi::addScalarOperand(int32_t val) {\n  ANeuralNetworksOperandType scalar;\n  scalar.type = ANEURALNETWORKS_INT32;\n  scalar.scale = 0;\n  scalar.zeroPoint = 0;\n  scalar.dimensionCount = 0;\n  scalar.dimensions = NULL;\n  int result_code = libnnapi_.ANeuralNetworksModel_addOperand(model_, &scalar);\n  if (result_code != ANEURALNETWORKS_NO_ERROR) {\n    reportError(result_code);\n  }\n\n  result_code = libnnapi_.ANeuralNetworksModel_setOperandValue(\n      model_, operand_idx, &val, sizeof(val));\n  if (result_code != ANEURALNETWORKS_NO_ERROR) {\n    reportError(result_code);\n  }\n\n  VLOG(1) << \"Added scalar, \" << val << \", at \" << operand_idx;\n  return operand_idx++;\n}\n\n// float32\nuint32_t NNApi::addFloatOperand(float val) {\n  ANeuralNetworksOperandType scalar;\n  scalar.type = ANEURALNETWORKS_TENSOR_FLOAT32;\n  scalar.scale = 0;\n  scalar.zeroPoint = 0;\n  scalar.dimensionCount = 0;\n  scalar.dimensions = NULL;\n  int result_code = libnnapi_.ANeuralNetworksModel_addOperand(model_, &scalar);\n  if (result_code != ANEURALNETWORKS_NO_ERROR) {\n    reportError(result_code);\n  }\n\n  result_code = libnnapi_.ANeuralNetworksModel_setOperandValue(\n      model_, operand_idx, &val, sizeof(val));\n  if (result_code != ANEURALNETWORKS_NO_ERROR) {\n    reportError(result_code);\n  }\n\n  VLOG(1) << \"Added scalar, \" << val << \", at \" << operand_idx;\n  return operand_idx++;\n}\n\nuint32_t NNApi::addTensorOperand(\n    const std::string& blob,\n    OperandCode type,\n    std::vector<uint32_t>& dims,\n    float scale,\n    int32_t zero_point)\n// clang-format off\n{\n  // clang-format on\n  auto found = operand_map_.find(blob);\n  if (found == operand_map_.end()) {\n    ANeuralNetworksOperandType tensor;\n    tensor.type = type;\n    tensor.scale = scale;\n    tensor.zeroPoint = zero_point;\n    tensor.dimensionCount = dims.size();\n    tensor.dimensions = dims.data();\n\n    int result_code =\n        libnnapi_.ANeuralNetworksModel_addOperand(model_, &tensor);\n    if (result_code != ANEURALNETWORKS_NO_ERROR) {\n      reportError(result_code);\n    }\n\n    operand_map_[blob] = operand_idx++;\n    tensor_dims_[blob] = dims;\n    VLOG(1) << \"Added operand, \" << blob << \", at \" << operand_map_[blob];\n  }\n  return operand_map_[blob];\n}\n\nvoid NNApi::init(const TensorVector& inputs, TensorVector* outputs) {\n  // model\n  if (!model_) {\n    int result_code = libnnapi_.ANeuralNetworksModel_create(&model_);\n    if (result_code != ANEURALNETWORKS_NO_ERROR) {\n      reportError(result_code);\n    }\n    if (!model_) {\n      CAFFE_THROW(\"Failed to create NN model\");\n    } else {\n      LOG(INFO) << \"Created NN model\";\n    }\n\n    ArgumentHelper helper(run_net_);\n    float scale = helper.GetSingleArgument<float>(\"scale\", 1.0);\n    int zero_point = helper.GetSingleArgument<int>(\"zero_point\", 0);\n\n    // add external input dimension\n    for (int i = 0; i < inputs.size(); i++) {\n      if (inputs[i]->IsType<float>()) {\n        tensor_type_ = ANEURALNETWORKS_TENSOR_FLOAT32;\n      } else if (inputs[i]->IsType<uint8_t>()) {\n        tensor_type_ = ANEURALNETWORKS_TENSOR_QUANT8_ASYMM;\n      } else {\n        CAFFE_THROW(\"Unsupported tensor type\");\n      }\n      const std::string& input_blob = run_net_.external_input(i);\n      std::vector<uint32_t> dims;\n      for (auto dim : inputs[i]->dims()) {\n        dims.push_back(dim);\n      }\n      addTensorOperand(input_blob, tensor_type_, dims, scale, zero_point);\n    }\n\n    // add operands and operations\n    for (const auto& op : run_net_.op()) {\n      if (operator_map_.count(op.type()) == 0) {\n        CAFFE_THROW(\"Unsupported operator\");\n      }\n      switch (operator_map_[op.type()]) {\n        case AVERAGEPOOL:\n          addPooling(op, ANEURALNETWORKS_AVERAGE_POOL_2D);\n          break;\n        case CONV:\n          addConv(op);\n          break;\n        case MAXPOOL:\n          addPooling(op, ANEURALNETWORKS_MAX_POOL_2D);\n          break;\n        case RELU:\n          addRelu(op);\n          break;\n        case SOFTMAX:\n          addSoftmax(op);\n          break;\n        default:\n          CAFFE_THROW(\"Unsupported operator\");\n          break;\n      }\n    }\n\n    // model inputs and outputs\n    int output_size = run_net_.external_output_size();\n    std::vector<uint32_t> input_indices(inputs.size());\n    std::vector<uint32_t> output_indices(output_size);\n    for (int i = 0; i < inputs.size(); i++) {\n      input_indices[i] = operand_map_[run_net_.external_input(i)];\n    }\n    for (int i = 0; i < output_size; i++) {\n      output_indices[i] = operand_map_[run_net_.external_output(i)];\n    }\n\n    result_code = libnnapi_.ANeuralNetworksModel_identifyInputsAndOutputs(\n        model_,\n        inputs.size(),\n        input_indices.data(),\n        output_size,\n        output_indices.data());\n    if (result_code != ANEURALNETWORKS_NO_ERROR) {\n      reportError(result_code);\n    }\n\n    result_code = libnnapi_.ANeuralNetworksModel_finish(model_);\n    if (result_code != ANEURALNETWORKS_NO_ERROR) {\n      reportError(result_code);\n    }\n\n    LOG(INFO) << \"Finish creating model\";\n\n    // compile\n    if (!compilation_) {\n      result_code =\n          libnnapi_.ANeuralNetworksCompilation_create(model_, &compilation_);\n      if (result_code != ANEURALNETWORKS_NO_ERROR) {\n        reportError(result_code);\n      }\n\n      result_code = libnnapi_.ANeuralNetworksCompilation_setPreference(\n          compilation_, preference_);\n      if (result_code != ANEURALNETWORKS_NO_ERROR) {\n        reportError(result_code);\n      }\n\n      result_code = libnnapi_.ANeuralNetworksCompilation_finish(compilation_);\n      if (result_code != ANEURALNETWORKS_NO_ERROR) {\n        reportError(result_code);\n      }\n\n      LOG(INFO) << \"Finish compilation\";\n    }\n\n    // pre-execution\n    if (!run_) {\n      result_code =\n          libnnapi_.ANeuralNetworksExecution_create(compilation_, &run_);\n      if (result_code != ANEURALNETWORKS_NO_ERROR) {\n        reportError(result_code);\n      }\n      LOG(INFO) << \"Created model execution\";\n    }\n\n    // set external input and output\n    for (int i = 0; i < inputs.size(); i++) {\n      result_code = libnnapi_.ANeuralNetworksExecution_setInput(\n          run_, i, NULL, inputs[i]->raw_data(), inputs[i]->size());\n      if (result_code != ANEURALNETWORKS_NO_ERROR) {\n        reportError(result_code);\n      }\n\n      VLOG(1) << \"Set external input \" << i << \" at \" << inputs[i]->raw_data()\n              << \", size = \" << inputs[i]->size();\n    }\n    // allocate memory for outputs\n    for (int i = 0; i < output_size; i++) {\n      const std::string& blob = run_net_.external_output(i);\n      if (operand_map_.find(blob) == operand_map_.end()) {\n        CAFFE_THROW(\"Unknown external output, \", blob);\n      }\n      uint32_t idx = operand_map_[blob];\n      if (tensor_dims_.find(blob) == tensor_dims_.end()) {\n        CAFFE_THROW(\"Operand dimension unknown\");\n      }\n      std::vector<int> output_dims;\n      for (auto dim : tensor_dims_[blob]) {\n        output_dims.push_back(dim);\n      }\n\n      auto* tensor = ws_.CreateBlob(blob)->GetMutable<TensorCPU>();\n      tensor->Resize(output_dims);\n      outputs->push_back(tensor);\n\n      if (tensor_type_ == ANEURALNETWORKS_TENSOR_FLOAT32) {\n        result_code = libnnapi_.ANeuralNetworksExecution_setOutput(\n            run_,\n            i,\n            NULL,\n            (void*)tensor->template mutable_data<float>(),\n            tensor->size());\n        if (result_code != ANEURALNETWORKS_NO_ERROR) {\n          reportError(result_code);\n        }\n\n      } else {\n        result_code = libnnapi_.ANeuralNetworksExecution_setOutput(\n            run_,\n            i,\n            NULL,\n            (void*)tensor->template mutable_data<uint8_t>(),\n            tensor->size());\n        if (result_code != ANEURALNETWORKS_NO_ERROR) {\n          reportError(result_code);\n        }\n      }\n\n      VLOG(1) << \"Set external output \" << i << \" at \" << tensor->raw_data()\n              << \", size = \" << tensor->size();\n    }\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/nnapi/nnapi.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\n#include \"NeuralNetworks.h\"\n#include \"dlnnapi.h\"\n\nnamespace caffe2 {\n\nclass NNApi {\n public:\n  using TensorVector = std::vector<TensorCPU*>;\n\n  // Three different modes:\n  // ANEURALNETWORKS_PREFER_LOW_POWER\n  // ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER\n  // ANEURALNETWORKS_PREFER_SUSTAINED_SPEED\n  NNApi(\n      const NetDef& init_net,\n      const NetDef& run_net,\n      Workspace* ws = nullptr,\n      const PreferenceCode pref = ANEURALNETWORKS_PREFER_SUSTAINED_SPEED)\n      : order_(StorageOrder::NHWC),\n        preference_(pref),\n        run_net_(run_net),\n        ws_(ws) {\n    if (!loadNNApiLibrary()) {\n      CAFFE_THROW(\"NNApi is not supported\");\n    }\n    CAFFE_ENFORCE(ws_.RunNetOnce(init_net));\n  }\n\n  ~NNApi();\n\n  bool loadNNApiLibrary();\n\n  bool run(const TensorVector& inputs, TensorVector* outputs);\n\n private:\n  dlnnapi libnnapi_;\n  ANeuralNetworksModel* model_{nullptr};\n  ANeuralNetworksCompilation* compilation_{nullptr};\n  ANeuralNetworksExecution* run_{nullptr};\n  ANeuralNetworksEvent* run_end_{nullptr};\n  StorageOrder order_;\n  PreferenceCode preference_;\n  NetDef run_net_;\n  Workspace ws_;\n  OperandCode tensor_type_;\n  uint32_t operand_idx{0};\n  std::unordered_map<std::string, uint32_t> operand_map_;\n  // dimensions for the tensors\n  std::unordered_map<std::string, std::vector<uint32_t>> tensor_dims_;\n\n  // mapping of the operator name \"Conv\" to OperatorType CONV\n  enum OperatorType {\n    AVERAGEPOOL,\n    CONV,\n    MAXPOOL,\n    RELU,\n    SOFTMAX,\n  };\n\n  std::unordered_map<std::string, OperatorType> operator_map_{\n      {\"AveragePool\", AVERAGEPOOL},\n      {\"Conv\", CONV},\n      {\"MaxPool\", MAXPOOL},\n      {\"Relu\", RELU},\n      {\"Softmax\", SOFTMAX}};\n\n  struct ConvPoolArgs {\n    int kernel_h{0};\n    int kernel_w{0};\n    int stride_x{0};\n    int stride_y{0};\n    int pad_t{0};\n    int pad_l{0};\n    int pad_b{0};\n    int pad_r{0};\n  };\n\n  void getConvPoolArgs(const ArgumentHelper& helper, ConvPoolArgs& args);\n\n  uint32_t addScalarOperand(int32_t val);\n\n  uint32_t addFloatOperand(float val);\n\n  uint32_t addTensorOperand(\n      const std::string& blob,\n      OperandCode type,\n      std::vector<uint32_t>& dims,\n      float scale = 1.0,\n      int32_t zero_point = 0);\n\n  // lazily initialize model_ in run()\n  void init(const TensorVector& inputs, TensorVector* outputs);\n\n  void addConv(const OperatorDef& op, bool fuse_relu = false);\n\n  void addPooling(\n      const OperatorDef& op,\n      OperationCode op_code,\n      bool fuse_relu = false);\n\n  void addRelu(const OperatorDef& op);\n\n  void addSoftmax(const OperatorDef& op);\n};\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/nnapi/nnapi_benchmark.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n \n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include \"nnapi.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\nstatic double benchmark_conv_caffe2(\n    Workspace* ws,\n    int N,\n    int C,\n    int H,\n    int W,\n    int K,\n    int kernel,\n    int group,\n    int warmup = 5,\n    int run = 10,\n    std::string engine = \"NNPACK\") {\n  caffe2::Workspace localWs;\n  if (!ws) {\n    ws = &localWs;\n  }\n  {\n    auto* t = ws->CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, C, H, W);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n  {\n    auto* t = ws->CreateBlob(\"W\")->GetMutable<TensorCPU>();\n    if (group == 1) {\n      t->Resize(K, C, kernel, kernel);\n    } else {\n      t->Resize(K, 1, kernel, kernel);\n    }\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n  {\n    auto* t = ws->CreateBlob(\"B\")->GetMutable<TensorCPU>();\n    t->Resize(K);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n\n  OperatorDef op;\n  {\n    op.set_type(\"Conv\");\n    op.add_input(\"X_cpu\");\n    op.add_input(\"W\");\n    op.add_input(\"B\");\n    op.add_output(\"Y_cpu\");\n    op.set_engine(engine);\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"order\");\n      arg.set_s(\"NCHW\");\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"convolution_transform_strategy\");\n      arg.set_s(\"PRECOMPUTE\");\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"kernel\");\n      arg.set_i(kernel);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"group\");\n      arg.set_i(group);\n    }\n  }\n\n  // NNPack\n  std::unique_ptr<caffe2::OperatorBase> op1(CreateOperator(op, ws));\n\n  Timer timer;\n  CAFFE_ENFORCE(op1->Run());\n  for (int i = 0; i < warmup; i++) {\n    op1->Run();\n  }\n  timer.Start();\n  for (int i = 0; i < run; i++) {\n    op1->Run();\n  }\n  return double(timer.MilliSeconds()) / run;\n}\n\nstatic double benchmark_conv_nnapi(\n    Workspace* ws,\n    int N,\n    int C,\n    int H,\n    int W,\n    int K,\n    int kernel,\n    int group,\n    int warmup = 5,\n    int run = 10) {\n  caffe2::Workspace localWs;\n  if (!ws) {\n    ws = &localWs;\n  }\n  {\n    auto* t = ws->CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, H, W, C);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n  {\n    auto* t = ws->CreateBlob(\"W\")->GetMutable<TensorCPU>();\n    if (group > 1) {\n      CAFFE_ENFORCE_EQ(C, group);\n      t->Resize(1, kernel, kernel, C);\n    } else {\n      t->Resize(K, kernel, kernel, C);\n    }\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n  {\n    auto* t = ws->CreateBlob(\"B\")->GetMutable<TensorCPU>();\n    t->Resize(K);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n\n  NetDef netdef;\n  {\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Conv\");\n      op.add_input(\"X_cpu\");\n      op.add_input(\"W\");\n      op.add_input(\"B\");\n      op.add_output(\"Y_cpu\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"order\");\n        arg.set_s(\"NHWC\");\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"kernel\");\n        arg.set_i(kernel);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"group\");\n        arg.set_i(group);\n      }\n    }\n    netdef.add_external_input(\"X_cpu\");\n    netdef.add_external_input(\"W\");\n    netdef.add_external_input(\"B\");\n    netdef.add_external_output(\"Y_cpu\");\n  }\n\n  // NN API\n  NetDef initNet;\n  NNApi model(initNet, netdef, ws);\n  std::vector<TensorCPU*> inputs, outputs;\n  inputs.push_back(ws->GetBlob(\"X_cpu\")->GetMutable<TensorCPU>());\n  CAFFE_ENFORCE(model.run(inputs, &outputs));\n\n  for (int i = 0; i < warmup; i++) {\n    model.run(inputs, &outputs);\n  }\n  Timer timer;\n  timer.Start();\n  for (int i = 0; i < run; i++) {\n    model.run(inputs, &outputs);\n  }\n  return double(timer.MilliSeconds()) / run;\n}\n\nstatic double benchmark_conv_nnapi_int8(\n    Workspace* ws,\n    int N,\n    int C,\n    int H,\n    int W,\n    int K,\n    int kernel,\n    int group,\n    int warmup = 5,\n    int run = 10) {\n  caffe2::Workspace localWs;\n  if (!ws) {\n    ws = &localWs;\n  }\n  {\n    auto* t = ws->CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, H, W, C);\n    for (int i = 0; i < t->size(); i++) {\n      t->mutable_data<uint8_t>()[i] = rand() % 10;\n    }\n  }\n  {\n    auto* t = ws->CreateBlob(\"W\")->GetMutable<TensorCPU>();\n    if (group > 1) {\n      CAFFE_ENFORCE_EQ(C, group);\n      t->Resize(1, kernel, kernel, C);\n    } else {\n      t->Resize(K, kernel, kernel, C);\n    }\n    for (int i = 0; i < t->size(); i++) {\n      t->mutable_data<uint8_t>()[i] = rand() % 10;\n    }\n  }\n\n  // For input tensor of ANEURALNETWORKS_TENSOR_QUANT8_ASYMM type, the bias\n  // should be of ANEURALNETWORKS_TENSOR_INT32, with zeroPoint of 0 and\n  // bias_scale == input_scale * filter_scale.\n  {\n    auto* t = ws->CreateBlob(\"B\")->GetMutable<TensorCPU>();\n    t->Resize(K);\n    for (int i = 0; i < t->size(); i++) {\n      t->mutable_data<int32_t>()[i] = rand() % 10;\n    }\n  }\n\n  NetDef netdef;\n  {\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Conv\");\n      op.add_input(\"X_cpu\");\n      op.add_input(\"W\");\n      op.add_input(\"B\");\n      op.add_output(\"Y_cpu\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"order\");\n        arg.set_s(\"NHWC\");\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"kernel\");\n        arg.set_i(kernel);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"group\");\n        arg.set_i(group);\n      }\n      // Hack\n      // for weight tensor\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"weight_scale\");\n        arg.set_f(1.0);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"weight_zero_point\");\n        arg.set_i(0);\n      }\n      // for output tensor\n      // For output tensor of ANEURALNETWORKS_TENSOR_QUANT8_ASYMM type, the\n      // following condition must be satisfied: output_scale > input_scale *\n      // filter_scale\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"output_scale\");\n        arg.set_f(2.0);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"output_zero_point\");\n        arg.set_i(0);\n      }\n    }\n    netdef.add_external_input(\"X_cpu\");\n    netdef.add_external_input(\"W\");\n    netdef.add_external_input(\"B\");\n    netdef.add_external_output(\"Y_cpu\");\n    // scale and zero_point for the input tensor\n    {\n      auto& arg = *(netdef.add_arg());\n      arg.set_name(\"scale\");\n      arg.set_f(1.0);\n    }\n    {\n      auto& arg = *(netdef.add_arg());\n      arg.set_name(\"zero_point\");\n      arg.set_i(0);\n    }\n  }\n\n  // NN API\n  NetDef initNet;\n  NNApi model(initNet, netdef, ws);\n  std::vector<TensorCPU*> inputs, outputs;\n  inputs.push_back(ws->GetBlob(\"X_cpu\")->GetMutable<TensorCPU>());\n  CAFFE_ENFORCE(model.run(inputs, &outputs));\n\n  for (int i = 0; i < warmup; i++) {\n    model.run(inputs, &outputs);\n  }\n  Timer timer;\n  timer.Start();\n  for (int i = 0; i < run; i++) {\n    model.run(inputs, &outputs);\n  }\n  return double(timer.MilliSeconds()) / run;\n}\n\n} // namespace\n\n} // namespace caffe2\n\nint main(int argc, char** argv) {\n  caffe2::Workspace ws;\n  ws.GetThreadPool()->setMinWorkSize(0);\n\n  int warmup = 2, mainrun = 10;\n  // float32\n  for (int space : {14, 26, 52, 104}) {\n    for (int input_channel : {64, 128, 256, 512}) {\n      for (int kernel : {1, 3}) {\n        int output_channel = input_channel;\n        const double cpu_time = caffe2::benchmark_conv_caffe2(\n            &ws,\n            1,\n            input_channel,\n            space,\n            space,\n            output_channel,\n            kernel,\n            1,\n            warmup,\n            mainrun,\n            \"NNPACK\");\n        const double nn_time_fp32 = caffe2::benchmark_conv_nnapi(\n            &ws,\n            1,\n            input_channel,\n            space,\n            space,\n            output_channel,\n            kernel,\n            1,\n            warmup,\n            mainrun);\n        const double nn_time_int8 = caffe2::benchmark_conv_nnapi_int8(\n            &ws,\n            1,\n            input_channel,\n            space,\n            space,\n            output_channel,\n            kernel,\n            1,\n            warmup,\n            mainrun);\n        const double flops = double(input_channel) * output_channel * kernel *\n            kernel * (kernel == 1 ? space : space - 2) *\n            (kernel == 1 ? space : space - 2) * 2;\n        printf(\n            \"Conv: X: %ix%i  \\tC: %i -> %i\\tK: %ix%i\\t32b\"\n            \"NNPACK GFLOPS: %.2f\\t32b\"\n            \"NN-API GFLOPS: %.2f\\t8b\"\n            \"NN-API GOPS: %.2f\\n\",\n            space,\n            space,\n            input_channel,\n            output_channel,\n            kernel,\n            kernel,\n            flops / cpu_time / 1E6,\n            flops / nn_time_fp32 / 1E6,\n            flops / nn_time_int8 / 1E6);\n      }\n    }\n  }\n  fflush(stdout);\n\n  // depthwise\n  for (int space : {14, 26, 52, 104}) {\n    for (int channel : {64, 128, 256, 512}) {\n      for (int kernel : {3}) {\n        const double cpu_time = caffe2::benchmark_conv_caffe2(\n            &ws,\n            1,\n            channel,\n            space,\n            space,\n            channel,\n            kernel,\n            channel,\n            warmup,\n            mainrun,\n            \"DEPTHWISE_3x3\");\n        const double nn_time_fp32_dwise = caffe2::benchmark_conv_nnapi(\n            &ws,\n            1,\n            channel,\n            space,\n            space,\n            channel,\n            kernel,\n            channel,\n            warmup,\n            mainrun);\n        const double nn_time_int8_dwise = caffe2::benchmark_conv_nnapi_int8(\n            &ws,\n            1,\n            channel,\n            space,\n            space,\n            channel,\n            kernel,\n            channel,\n            warmup,\n            mainrun);\n        const double dwise_bandwidth = sizeof(float) * double(channel) *\n            (2 * (space - 2) * (space - 2) + kernel * kernel);\n        printf(\n            \"Conv: X: %ix%i  \\tC: %i -> %i\\tK: %ix%i\\t32b\"\n            \"Caffe2 Dwise GB/s: %.2f\\t32b\"\n            \"NN-API Dwise GB/s: %.2f\\t8b\"\n            \"NN-API Dwise GB/s: %.2f\\n\",\n            space,\n            space,\n            channel,\n            channel,\n            kernel,\n            kernel,\n            dwise_bandwidth / cpu_time / 1E6,\n            dwise_bandwidth / nn_time_fp32_dwise / 1E6,\n            dwise_bandwidth / sizeof(float) / nn_time_int8_dwise / 1E6);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/nnapi/nnapi_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\n#include \"NeuralNetworks.h\"\n#include \"nnapi.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n// CPU: t1, NN-API: t2\nvoid checkError(const TensorCPU& t1, const TensorCPU& t2, float error) {\n  CAFFE_ENFORCE_EQ(\n      t1.dims(),\n      t2.dims(),\n      \"t1.size() = \",\n      t1.size(),\n      \", t2.size() = \",\n      t2.size());\n  float maxError = 0, minError = 0;\n  if (t1.template IsType<float>()) {\n    for (auto i = 0; i < t1.size(); ++i) {\n      const float t1_i = t1.template data<float>()[i];\n      const float t2_i = t2.template data<float>()[i];\n      EXPECT_NEAR(t1_i, t2_i, error);\n      float err = t1_i - t2_i;\n      if (err > maxError) {\n        maxError = err;\n      } else if (err < minError) {\n        minError = err;\n      }\n    }\n  } else if (t1.template IsType<uint8_t>()) {\n    for (auto i = 0; i < t1.size(); ++i) {\n      const uint8_t t1_i = t1.template data<uint8_t>()[i];\n      const uint8_t t2_i = t2.template data<uint8_t>()[i];\n      EXPECT_NEAR(t1_i, t2_i, error);\n      float err = t1_i - t2_i;\n      if (err > maxError) {\n        maxError = err;\n      } else if (err < minError) {\n        minError = err;\n      }\n    }\n  }\n  LOG(ERROR) << \"maxError = \" << maxError << \", minError = \" << minError;\n}\n\nstatic void test_relu(int N, int C, int H, int W) {\n  // CPU reference\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, H, W, C);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n  NetDef netdef;\n  {\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Relu\");\n      op.add_input(\"X_cpu\");\n      op.add_output(\"Y_cpu\");\n    }\n    netdef.add_external_input(\"X_cpu\");\n    netdef.add_external_output(\"Y_cpu\");\n  }\n  ws.RunNetOnce(netdef);\n  const auto& t_cpu = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // cpu\n\n  // NN API\n  netdef.mutable_op(0)->set_output(0, \"Y_nn\");\n  netdef.set_external_output(0, \"Y_nn\");\n  NetDef initNet;\n  NNApi model(initNet, netdef, &ws);\n  std::vector<TensorCPU*> inputs, outputs;\n  inputs.push_back(ws.GetBlob(\"X_cpu\")->GetMutable<TensorCPU>());\n  EXPECT_TRUE(model.run(inputs, &outputs));\n  const auto& t_nn = *outputs[0];\n\n  checkError(t_cpu, t_nn, 0.01);\n}\n\nstatic void test_conv_NHWC(\n    int N,\n    int C,\n    int H,\n    int W,\n    int K,\n    int kernel,\n    int pad_t,\n    int pad_l,\n    int pad_b,\n    int pad_r,\n    int stride_h,\n    int stride_w) {\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, H, W, C);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n  {\n    auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n    t->Resize(K, kernel, kernel, C);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n  {\n    auto* t = ws.CreateBlob(\"B\")->GetMutable<TensorCPU>();\n    t->Resize(K);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n\n  NetDef netdef;\n  {\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Conv\");\n      op.add_input(\"X_cpu\");\n      op.add_input(\"W\");\n      op.add_input(\"B\");\n      op.add_output(\"Y_cpu\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"order\");\n        arg.set_s(\"NHWC\");\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"kernel\");\n        arg.set_i(kernel);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_t\");\n        arg.set_i(pad_t);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_l\");\n        arg.set_i(pad_l);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_b\");\n        arg.set_i(pad_b);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_r\");\n        arg.set_i(pad_r);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"stride_h\");\n        arg.set_i(stride_h);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"stride_w\");\n        arg.set_i(stride_w);\n      }\n    }\n    netdef.add_external_input(\"X_cpu\");\n    netdef.add_external_input(\"W\");\n    netdef.add_external_input(\"B\");\n    netdef.add_external_output(\"Y_cpu\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t_cpu = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // cpu\n\n  // NN API\n  netdef.mutable_op(0)->set_output(0, \"Y_nn\");\n  netdef.set_external_output(0, \"Y_nn\");\n  NetDef initNet;\n  NNApi model(initNet, netdef, &ws);\n  std::vector<TensorCPU*> inputs, outputs;\n  inputs.push_back(ws.GetBlob(\"X_cpu\")->GetMutable<TensorCPU>());\n  EXPECT_TRUE(model.run(inputs, &outputs));\n  const auto& t_nn = *outputs[0];\n\n  checkError(t_cpu, t_nn, 0.01);\n}\n\nstatic void test_depthwise_conv_NHWC(\n    int N,\n    int C,\n    int H,\n    int W,\n    int D,\n    int kernel,\n    int pad_t,\n    int pad_l,\n    int pad_b,\n    int pad_r,\n    int stride_h,\n    int stride_w) {\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, H, W, C);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n  {\n    auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n    t->Resize(1, kernel, kernel, D);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n  {\n    auto* t = ws.CreateBlob(\"B\")->GetMutable<TensorCPU>();\n    t->Resize(D);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n\n  NetDef netdef_cpu;\n  {\n    // X: NHWC -> NCHW\n    {\n      auto& op = *(netdef_cpu.add_op());\n      op.set_type(\"Transpose\");\n      op.add_input(\"X_cpu\");\n      op.add_output(\"X_t\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"axes\");\n        arg.add_ints(0);\n        arg.add_ints(3);\n        arg.add_ints(1);\n        arg.add_ints(2);\n      }\n    }\n    // X: MHWC -> CMHW\n    {\n      auto& op = *(netdef_cpu.add_op());\n      op.set_type(\"Transpose\");\n      op.add_input(\"W\");\n      op.add_output(\"W_t\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"axes\");\n        arg.add_ints(3);\n        arg.add_ints(0);\n        arg.add_ints(1);\n        arg.add_ints(2);\n      }\n    }\n    {\n      auto& op = *(netdef_cpu.add_op());\n      op.set_type(\"Conv\");\n      op.add_input(\"X_t\");\n      op.add_input(\"W_t\");\n      op.add_input(\"B\");\n      op.add_output(\"Y_t\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"order\");\n        arg.set_s(\"NCHW\");\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"kernel\");\n        arg.set_i(kernel);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_t\");\n        arg.set_i(pad_t);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_l\");\n        arg.set_i(pad_l);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_b\");\n        arg.set_i(pad_b);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_r\");\n        arg.set_i(pad_r);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"stride_h\");\n        arg.set_i(stride_h);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"stride_w\");\n        arg.set_i(stride_w);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"group\");\n        arg.set_i(C);\n      }\n    }\n    // Y: NCHW -> NHWC\n    {\n      auto& op = *(netdef_cpu.add_op());\n      op.set_type(\"Transpose\");\n      op.add_input(\"Y_t\");\n      op.add_output(\"Y_cpu\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"axes\");\n        arg.add_ints(0);\n        arg.add_ints(2);\n        arg.add_ints(3);\n        arg.add_ints(1);\n      }\n    }\n    netdef_cpu.add_external_input(\"X_cpu\");\n    netdef_cpu.add_external_input(\"W\");\n    netdef_cpu.add_external_input(\"B\");\n    netdef_cpu.add_external_output(\"Y_cpu\");\n  }\n\n  NetDef netdef;\n  {\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Conv\");\n      op.add_input(\"X_cpu\");\n      op.add_input(\"W\");\n      op.add_input(\"B\");\n      op.add_output(\"Y_nn\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"order\");\n        arg.set_s(\"NHWC\");\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"kernel\");\n        arg.set_i(kernel);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_t\");\n        arg.set_i(pad_t);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_l\");\n        arg.set_i(pad_l);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_b\");\n        arg.set_i(pad_b);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_r\");\n        arg.set_i(pad_r);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"stride_h\");\n        arg.set_i(stride_h);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"stride_w\");\n        arg.set_i(stride_w);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"group\");\n        arg.set_i(C);\n      }\n    }\n    netdef.add_external_input(\"X_cpu\");\n    netdef.add_external_input(\"W\");\n    netdef.add_external_input(\"B\");\n    netdef.add_external_output(\"Y_nn\");\n  }\n\n  ws.RunNetOnce(netdef_cpu);\n  const auto& t_cpu = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // cpu\n\n  // NN API\n  NetDef initNet;\n  NNApi model(initNet, netdef, &ws);\n  std::vector<TensorCPU*> inputs, outputs;\n  inputs.push_back(ws.GetBlob(\"X_cpu\")->GetMutable<TensorCPU>());\n  EXPECT_TRUE(model.run(inputs, &outputs));\n  const auto& t_nn = *outputs[0];\n\n  checkError(t_cpu, t_nn, 0.01);\n}\n\nstatic void test_pooling(\n    std::string type,\n    int N,\n    int C,\n    int H,\n    int W,\n    int kernel,\n    int pad_t,\n    int pad_l,\n    int pad_b,\n    int pad_r,\n    int stride_h,\n    int stride_w) {\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, H, W, C);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n\n  NetDef netdef;\n  {\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(type);\n      op.add_input(\"X_cpu\");\n      op.add_output(\"Y_cpu\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"order\");\n        arg.set_s(\"NHWC\");\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"kernel\");\n        arg.set_i(kernel);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_t\");\n        arg.set_i(pad_t);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_l\");\n        arg.set_i(pad_l);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_b\");\n        arg.set_i(pad_b);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_r\");\n        arg.set_i(pad_r);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"stride_h\");\n        arg.set_i(stride_h);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"stride_w\");\n        arg.set_i(stride_w);\n      }\n    }\n    netdef.add_external_input(\"X_cpu\");\n    netdef.add_external_output(\"Y_cpu\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t_cpu = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // cpu\n\n  // NN API\n  netdef.mutable_op(0)->set_output(0, \"Y_nn\");\n  netdef.set_external_output(0, \"Y_nn\");\n  NetDef initNet;\n  NNApi model(initNet, netdef, &ws);\n  std::vector<TensorCPU*> inputs, outputs;\n  inputs.push_back(ws.GetBlob(\"X_cpu\")->GetMutable<TensorCPU>());\n  EXPECT_TRUE(model.run(inputs, &outputs));\n  const auto& t_nn = *outputs[0];\n\n  checkError(t_cpu, t_nn, 0.01);\n}\n\nstatic void test_softmax(int N, int C, int H = 1, int W = 1) {\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    if (H == 1 && W == 1) {\n      t->Resize(N, C);\n    } else {\n      t->Resize(N, H, W, C);\n    }\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n\n  NetDef netdef;\n  {\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"Softmax\");\n      op.add_input(\"X_cpu\");\n      op.add_output(\"Y_cpu\");\n    }\n    netdef.add_external_input(\"X_cpu\");\n    netdef.add_external_output(\"Y_cpu\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t_cpu = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // cpu\n\n  // NN API\n  netdef.mutable_op(0)->set_output(0, \"Y_nn\");\n  netdef.set_external_output(0, \"Y_nn\");\n  NetDef initNet;\n  NNApi model(initNet, netdef, &ws);\n  std::vector<TensorCPU*> inputs, outputs;\n  inputs.push_back(ws.GetBlob(\"X_cpu\")->GetMutable<TensorCPU>());\n  EXPECT_TRUE(model.run(inputs, &outputs));\n  const auto& t_nn = *outputs[0];\n\n  checkError(t_cpu, t_nn, 0.01);\n}\n\nTEST(NNApi, TestConv) {\n  for (int C : {13, 32}) {\n    for (int M : {4, 7, 17}) {\n      for (int W : {13, 104}) {\n        for (int K : {1, 3, 5}) {\n          for (int P : {0, K - 1}) {\n            for (int S : {1, 2}) {\n              test_conv_NHWC(1, C, W, W, M, K, P, P, P, P, S, S);\n            }\n          }\n        }\n      }\n    }\n  }\n  // Test for asymmetric padding\n  // NN API only supports stride_x == stride_y\n  test_conv_NHWC(1, 3, 26, 26, 7, 3, 1, 2, 2, 0, 1, 1);\n  test_conv_NHWC(1, 3, 26, 26, 7, 3, 1, 2, 0, 2, 2, 2);\n  test_conv_NHWC(1, 3, 26, 26, 7, 3, 1, 1, 2, 1, 2, 2);\n  test_conv_NHWC(1, 3, 26, 26, 7, 3, 1, 1, 0, 1, 1, 1);\n}\n\nTEST(NNApi, Depthwise) {\n  for (int C : {13, 32}) {\n    for (int W : {13, 104}) {\n      for (int K : {1, 3}) {\n        for (int P : {0, K - 1}) {\n          for (int S : {1, 2}) {\n            test_depthwise_conv_NHWC(1, C, W, W, C, K, P, P, P, P, S, S);\n          }\n        }\n      }\n    }\n  }\n  // Test for asymmetric padding\n  // NN API only supports stride_x == stride_y\n  test_depthwise_conv_NHWC(1, 3, 26, 26, 3, 3, 1, 2, 2, 0, 1, 1);\n  test_depthwise_conv_NHWC(1, 3, 26, 26, 3, 3, 1, 2, 0, 2, 2, 2);\n  test_depthwise_conv_NHWC(1, 3, 26, 26, 3, 3, 1, 1, 2, 1, 2, 2);\n  test_depthwise_conv_NHWC(1, 3, 26, 26, 3, 3, 1, 1, 0, 1, 1, 1);\n}\n\nTEST(NNApi, TestRelu) {\n  test_relu(1, 4, 10, 10);\n  test_relu(1, 16, 128, 128);\n}\n\nTEST(NNApi, TestAveragePool) {\n  for (int C : {13, 32}) {\n    for (int W : {13, 104}) {\n      for (int K : {1, 3}) {\n        for (int P : {0, K - 1}) {\n          for (int S : {1, 2}) {\n            test_pooling(\"AveragePool\", 1, C, W, W, K, P, P, P, P, S, S);\n          }\n        }\n      }\n    }\n  }\n  test_pooling(\"AveragePool\", 1, 3, 26, 26, 3, 1, 2, 2, 0, 1, 1);\n  test_pooling(\"AveragePool\", 1, 3, 26, 26, 3, 1, 2, 0, 2, 2, 2);\n  test_pooling(\"AveragePool\", 1, 3, 26, 26, 3, 1, 1, 2, 1, 2, 2);\n  test_pooling(\"AveragePool\", 1, 3, 26, 26, 3, 1, 1, 0, 1, 1, 1);\n}\n\nTEST(NNApi, TestMaxPool) {\n  for (int C : {13, 32}) {\n    for (int W : {13, 104}) {\n      for (int K : {1, 3}) {\n        for (int P : {0, K - 1}) {\n          for (int S : {1, 2}) {\n            test_pooling(\"MaxPool\", 1, C, W, W, K, P, P, P, P, S, S);\n          }\n        }\n      }\n    }\n  }\n  test_pooling(\"MaxPool\", 1, 3, 26, 26, 3, 1, 2, 2, 0, 1, 1);\n  test_pooling(\"MaxPool\", 1, 3, 26, 26, 3, 1, 2, 0, 2, 2, 2);\n  test_pooling(\"MaxPool\", 1, 3, 26, 26, 3, 1, 1, 2, 1, 2, 2);\n  test_pooling(\"MaxPool\", 1, 3, 26, 26, 3, 1, 1, 0, 1, 1, 1);\n}\n\nTEST(NNApi, TestSoftmax) {\n  test_softmax(1, 100);\n  test_softmax(2, 17);\n\n  // NN API doesn't seem to work for 4D tensor\n  // test_softmax(1, 100, 13, 13);\n  // test_softmax(5, 17, 13, 13);\n}\n\n} // namespace\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/CMakeLists.txt",
    "content": "if(USE_MOBILE_OPENGL AND (ANDROID OR IOS))\n  add_subdirectory(core)\n  add_subdirectory(operators)\n\n  if (ANDROID)\n    add_subdirectory(android)\n  endif()\n\n  if (IOS)\n    add_subdirectory(ios)\n  endif()\nendif()\n\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/android/AndroidGLContext.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"AndroidGLContext.h\"\n#include \"caffe2/core/logging.h\"\n#include \"gl3stub.h\"\n#include <regex>\n\nnamespace {\n\nstatic const std::unordered_map<std::string, GL_Renderer>& renderer_map() {\n  static std::unordered_map<std::string, GL_Renderer> m = {\n      {\"Adreno\", Adreno},\n      {\"Mali\", Mali},\n      {\"NVIDIA\", Tegra} /*, {\"PowerVR\", PowerVR} */};\n  return m;\n}\n\n} // namespace\n\nEGLContext AndroidGLContext::create_opengl_thread_context() {\n  EGLSurface surface = EGL_NO_SURFACE;\n  EGLContext context = EGL_NO_CONTEXT;\n  EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);\n  if (display == EGL_NO_DISPLAY) {\n    // We failed to get a display\n    CAFFE_THROW(\"Problem with OpenGL context\");\n    return context;\n  }\n\n  EGLint major;\n  EGLint minor;\n  eglInitialize(display, &major, &minor);\n\n  const EGLint configAttr[] = {EGL_RENDERABLE_TYPE,\n                               EGL_OPENGL_ES2_BIT,\n                               EGL_SURFACE_TYPE,\n                               EGL_PBUFFER_BIT, // we create a pixelbuffer surface\n                               EGL_NONE};\n\n  EGLint numConfig;\n  EGLConfig eglConfig;\n  if (!eglChooseConfig(display, configAttr, &eglConfig, 1, &numConfig)) {\n    // We failed to find a suitable config\n    eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);\n    eglTerminate(display);\n    display = EGL_NO_DISPLAY;\n    CAFFE_THROW(\"Problem with OpenGL context\");\n    return context;\n  }\n\n  const EGLint ctxAttr[] = {EGL_CONTEXT_CLIENT_VERSION,\n                            2, // very important!\n                            EGL_NONE};\n\n  // Create an EGL context based on the chosen configuration.\n  context = eglCreateContext(display, eglConfig, EGL_NO_CONTEXT, ctxAttr);\n\n  // We need a surface. For most mixed JNI/Java based apps it is suggested\n  // that we pass a Java surface through JNI and extract the surface\n  // Pure NDK apps get passed the android_app structure which includes a surface\n  // We want our own OpenGL context for the current thread.\n  // Here we create a fake 1x1 'pixel buffer' surface.\n  // We don't expecting to run vertex or fragment shaders.\n\n  const EGLint surfaceAttr[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};\n\n  surface = eglCreatePbufferSurface(display, eglConfig, surfaceAttr);\n\n  // Bind context, draw and surface to current thread\n  eglMakeCurrent(display, surface, surface, context);\n\n  // Bind the API for this context.  In our case we want to use OpenGL_ES\n  eglBindAPI(EGL_OPENGL_ES_API);\n  return context;\n}\n\nbool AndroidGLContext::opengl_thread_context_exists() {\n  return eglGetCurrentContext() != EGL_NO_CONTEXT;\n}\n\nbool AndroidGLContext::release_opengl_thread_context() {\n  EGLContext display = eglGetCurrentDisplay();\n  if (display != EGL_NO_DISPLAY) {\n    if (_eglcontext != EGL_NO_CONTEXT) {\n      eglDestroyContext(display, _eglcontext);\n      _eglcontext = EGL_NO_CONTEXT;\n    }\n    EGLSurface surface = eglGetCurrentSurface(EGL_DRAW);\n    if (surface != EGL_NO_SURFACE) {\n      eglDestroySurface(display, surface);\n      surface = EGL_NO_SURFACE;\n    }\n    surface = eglGetCurrentSurface(EGL_READ);\n    if (surface != EGL_NO_SURFACE) {\n      eglDestroySurface(display, surface);\n      surface = EGL_NO_SURFACE;\n    }\n    eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);\n    eglTerminate(display);\n    display = EGL_NO_DISPLAY;\n  }\n  eglReleaseThread();\n  return true;\n}\n\nvoid AndroidGLContext::init_gles3() {\n  if (!gl3stubInit()) {\n    CAFFE_THROW(\"OpenGL ES 3 not initialized\");\n  } else {\n    LOG(INFO) << \"OpenGL ES 3 successfully enabled\";\n  }\n}\n\nGL_Renderer AndroidGLContext::get_platform() {\n  std::string rendererStr((const char*)glGetString(GL_RENDERER));\n  std::regex regStr(\"^[A-Za-z]*\");\n  std::smatch matchs;\n  if (std::regex_search(rendererStr, matchs, regStr)) {\n    const std::string renderer = *matchs.begin();\n    auto found = renderer_map().find(renderer);\n    if (found != renderer_map().end()) {\n      return found->second;\n    }\n  }\n  CAFFE_THROW(\"Unsupported GPU renderer\");\n}\n\nAndroidGLContext::AndroidGLContext() {\n  if (!opengl_thread_context_exists()) {\n    _eglcontext = create_opengl_thread_context();\n    LOG(INFO) << \"New EGLContext created\";\n\n    if (!supportOpenGLES3(&half_float_supported)) {\n      CAFFE_THROW(\"OpenGL ES 3 not supported\");\n    }\n\n    if (!isSupportedDevice()) {\n      LOG(ERROR) << \"Device not fully supported\";\n    }\n  } else {\n    _eglcontext = EGL_NO_CONTEXT;\n    LOG(INFO) << \"Reusing EGLContext, make sure OpenGL ES 3 is supported\";\n  }\n  static std::once_flag once;\n  std::call_once(once, [&]() { init_gles3(); });\n}\n\nAndroidGLContext::~AndroidGLContext() {\n  if (_eglcontext != EGL_NO_CONTEXT) {\n    release_opengl_thread_context();\n  }\n}\n\nvoid AndroidGLContext::set_context() {}\n\nvoid AndroidGLContext::reset_context() {}\n\nvoid AndroidGLContext::flush_context() {}\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/android/AndroidGLContext.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"../core/GLContext.h\"\n#include \"../core/GLTexture.h\"\n#include <unordered_map>\n\nenum GL_Renderer { Adreno, Mali, Tegra /*, PowerVR */ };\n\nclass AndroidGLContext : public GLContext {\n private:\n  EGLContext _eglcontext;\n\n  EGLContext create_opengl_thread_context();\n  bool opengl_thread_context_exists();\n  bool release_opengl_thread_context();\n\n public:\n  AndroidGLContext();\n  ~AndroidGLContext();\n  void set_context();\n  void reset_context();\n  void flush_context();\n  void init_gles3();\n  GL_Renderer get_platform();\n};\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/android/CMakeLists.txt",
    "content": "file(GLOB_RECURSE tmp *.cc *.c)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/android/GLContext.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"AndroidGLContext.h\"\n\nstd::unique_ptr<GLContext> GLContext::_glcontext = nullptr;\n\nvoid GLContext::initGLContext() {\n  if (_glcontext == nullptr) {\n    _glcontext.reset(new AndroidGLContext());\n  }\n}\n\nGLContext* GLContext::getGLContext() {\n  if (_glcontext == nullptr) {\n    initGLContext();\n  }\n  return _glcontext.get();\n}\n\nvoid GLContext::deleteGLContext() { _glcontext.reset(nullptr); }\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/android/GLImageAllocator.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLImageAllocator.h\"\n#include \"../core/arm_neon_support.h\"\n\ntemplate <typename T>\nGLImageAllocator<T>* GLImageAllocator<T>::newGLImageAllocator() {\n  return new GLImageAllocator<T>();\n}\n\ntemplate GLImageAllocator<float16_t>* GLImageAllocator<float16_t>::newGLImageAllocator();\ntemplate GLImageAllocator<uint8_t>* GLImageAllocator<uint8_t>::newGLImageAllocator();\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/android/arm_neon_support.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include <arm_neon.h>\ntypedef __fp16 float16_t;\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/android/gl3stub.c",
    "content": "\n// clang-format off\n\n#include <EGL/egl.h>\n#include \"gl3stub.h\"\n\nGLboolean gl3stubInit() {\n    #define FIND_PROC(s) s = (void*)eglGetProcAddress(#s)\n    FIND_PROC(glReadBuffer);\n    FIND_PROC(glDrawRangeElements);\n    FIND_PROC(glTexImage3D);\n    FIND_PROC(glTexSubImage3D);\n    FIND_PROC(glCopyTexSubImage3D);\n    FIND_PROC(glCompressedTexImage3D);\n    FIND_PROC(glCompressedTexSubImage3D);\n    FIND_PROC(glGenQueries);\n    FIND_PROC(glDeleteQueries);\n    FIND_PROC(glIsQuery);\n    FIND_PROC(glBeginQuery);\n    FIND_PROC(glEndQuery);\n    FIND_PROC(glGetQueryiv);\n    FIND_PROC(glGetQueryObjectuiv);\n    FIND_PROC(glUnmapBuffer);\n    FIND_PROC(glGetBufferPointerv);\n    FIND_PROC(glDrawBuffers);\n    FIND_PROC(glUniformMatrix2x3fv);\n    FIND_PROC(glUniformMatrix3x2fv);\n    FIND_PROC(glUniformMatrix2x4fv);\n    FIND_PROC(glUniformMatrix4x2fv);\n    FIND_PROC(glUniformMatrix3x4fv);\n    FIND_PROC(glUniformMatrix4x3fv);\n    FIND_PROC(glBlitFramebuffer);\n    FIND_PROC(glRenderbufferStorageMultisample);\n    FIND_PROC(glFramebufferTextureLayer);\n    FIND_PROC(glMapBufferRange);\n    FIND_PROC(glFlushMappedBufferRange);\n    FIND_PROC(glBindVertexArray);\n    FIND_PROC(glDeleteVertexArrays);\n    FIND_PROC(glGenVertexArrays);\n    FIND_PROC(glIsVertexArray);\n    FIND_PROC(glGetIntegeri_v);\n    FIND_PROC(glBeginTransformFeedback);\n    FIND_PROC(glEndTransformFeedback);\n    FIND_PROC(glBindBufferRange);\n    FIND_PROC(glBindBufferBase);\n    FIND_PROC(glTransformFeedbackVaryings);\n    FIND_PROC(glGetTransformFeedbackVarying);\n    FIND_PROC(glVertexAttribIPointer);\n    FIND_PROC(glGetVertexAttribIiv);\n    FIND_PROC(glGetVertexAttribIuiv);\n    FIND_PROC(glVertexAttribI4i);\n    FIND_PROC(glVertexAttribI4ui);\n    FIND_PROC(glVertexAttribI4iv);\n    FIND_PROC(glVertexAttribI4uiv);\n    FIND_PROC(glGetUniformuiv);\n    FIND_PROC(glGetFragDataLocation);\n    FIND_PROC(glUniform1ui);\n    FIND_PROC(glUniform2ui);\n    FIND_PROC(glUniform3ui);\n    FIND_PROC(glUniform4ui);\n    FIND_PROC(glUniform1uiv);\n    FIND_PROC(glUniform2uiv);\n    FIND_PROC(glUniform3uiv);\n    FIND_PROC(glUniform4uiv);\n    FIND_PROC(glClearBufferiv);\n    FIND_PROC(glClearBufferuiv);\n    FIND_PROC(glClearBufferfv);\n    FIND_PROC(glClearBufferfi);\n    FIND_PROC(glGetStringi);\n    FIND_PROC(glCopyBufferSubData);\n    FIND_PROC(glGetUniformIndices);\n    FIND_PROC(glGetActiveUniformsiv);\n    FIND_PROC(glGetUniformBlockIndex);\n    FIND_PROC(glGetActiveUniformBlockiv);\n    FIND_PROC(glGetActiveUniformBlockName);\n    FIND_PROC(glUniformBlockBinding);\n    FIND_PROC(glDrawArraysInstanced);\n    FIND_PROC(glDrawElementsInstanced);\n    FIND_PROC(glFenceSync);\n    FIND_PROC(glIsSync);\n    FIND_PROC(glDeleteSync);\n    FIND_PROC(glClientWaitSync);\n    FIND_PROC(glWaitSync);\n    FIND_PROC(glGetInteger64v);\n    FIND_PROC(glGetSynciv);\n    FIND_PROC(glGetInteger64i_v);\n    FIND_PROC(glGetBufferParameteri64v);\n    FIND_PROC(glGenSamplers);\n    FIND_PROC(glDeleteSamplers);\n    FIND_PROC(glIsSampler);\n    FIND_PROC(glBindSampler);\n    FIND_PROC(glSamplerParameteri);\n    FIND_PROC(glSamplerParameteriv);\n    FIND_PROC(glSamplerParameterf);\n    FIND_PROC(glSamplerParameterfv);\n    FIND_PROC(glGetSamplerParameteriv);\n    FIND_PROC(glGetSamplerParameterfv);\n    FIND_PROC(glVertexAttribDivisor);\n    FIND_PROC(glBindTransformFeedback);\n    FIND_PROC(glDeleteTransformFeedbacks);\n    FIND_PROC(glGenTransformFeedbacks);\n    FIND_PROC(glIsTransformFeedback);\n    FIND_PROC(glPauseTransformFeedback);\n    FIND_PROC(glResumeTransformFeedback);\n    FIND_PROC(glGetProgramBinary);\n    FIND_PROC(glProgramBinary);\n    FIND_PROC(glProgramParameteri);\n    FIND_PROC(glInvalidateFramebuffer);\n    FIND_PROC(glInvalidateSubFramebuffer);\n    FIND_PROC(glTexStorage2D);\n    FIND_PROC(glTexStorage3D);\n    FIND_PROC(glGetInternalformativ);\n\n    // Bind GL_EXT_texture_border_clamp\n\n    FIND_PROC(glTexParameterIivEXT);\n    FIND_PROC(glTexParameterIuivEXT);\n    FIND_PROC(glGetTexParameterIivEXT);\n    FIND_PROC(glGetTexParameterIuivEXT);\n    FIND_PROC(glSamplerParameterIivEXT);\n    FIND_PROC(glSamplerParameterIuivEXT);\n    FIND_PROC(glGetSamplerParameterIivEXT);\n    FIND_PROC(glGetSamplerParameterIuivEXT);\n\n    #undef FIND_PROC\n\n    if (!glReadBuffer ||\n        !glDrawRangeElements ||\n        !glTexImage3D ||\n        !glTexSubImage3D ||\n        !glCopyTexSubImage3D ||\n        !glCompressedTexImage3D ||\n        !glCompressedTexSubImage3D ||\n        !glGenQueries ||\n        !glDeleteQueries ||\n        !glIsQuery ||\n        !glBeginQuery ||\n        !glEndQuery ||\n        !glGetQueryiv ||\n        !glGetQueryObjectuiv ||\n        !glUnmapBuffer ||\n        !glGetBufferPointerv ||\n        !glDrawBuffers ||\n        !glUniformMatrix2x3fv ||\n        !glUniformMatrix3x2fv ||\n        !glUniformMatrix2x4fv ||\n        !glUniformMatrix4x2fv ||\n        !glUniformMatrix3x4fv ||\n        !glUniformMatrix4x3fv ||\n        !glBlitFramebuffer ||\n        !glRenderbufferStorageMultisample ||\n        !glFramebufferTextureLayer ||\n        !glMapBufferRange ||\n        !glFlushMappedBufferRange ||\n        !glBindVertexArray ||\n        !glDeleteVertexArrays ||\n        !glGenVertexArrays ||\n        !glIsVertexArray ||\n        !glGetIntegeri_v ||\n        !glBeginTransformFeedback ||\n        !glEndTransformFeedback ||\n        !glBindBufferRange ||\n        !glBindBufferBase ||\n        !glTransformFeedbackVaryings ||\n        !glGetTransformFeedbackVarying ||\n        !glVertexAttribIPointer ||\n        !glGetVertexAttribIiv ||\n        !glGetVertexAttribIuiv ||\n        !glVertexAttribI4i ||\n        !glVertexAttribI4ui ||\n        !glVertexAttribI4iv ||\n        !glVertexAttribI4uiv ||\n        !glGetUniformuiv ||\n        !glGetFragDataLocation ||\n        !glUniform1ui ||\n        !glUniform2ui ||\n        !glUniform3ui ||\n        !glUniform4ui ||\n        !glUniform1uiv ||\n        !glUniform2uiv ||\n        !glUniform3uiv ||\n        !glUniform4uiv ||\n        !glClearBufferiv ||\n        !glClearBufferuiv ||\n        !glClearBufferfv ||\n        !glClearBufferfi ||\n        !glGetStringi ||\n        !glCopyBufferSubData ||\n        !glGetUniformIndices ||\n        !glGetActiveUniformsiv ||\n        !glGetUniformBlockIndex ||\n        !glGetActiveUniformBlockiv ||\n        !glGetActiveUniformBlockName ||\n        !glUniformBlockBinding ||\n        !glDrawArraysInstanced ||\n        !glDrawElementsInstanced ||\n        !glFenceSync ||\n        !glIsSync ||\n        !glDeleteSync ||\n        !glClientWaitSync ||\n        !glWaitSync ||\n        !glGetInteger64v ||\n        !glGetSynciv ||\n        !glGetInteger64i_v ||\n        !glGetBufferParameteri64v ||\n        !glGenSamplers ||\n        !glDeleteSamplers ||\n        !glIsSampler ||\n        !glBindSampler ||\n        !glSamplerParameteri ||\n        !glSamplerParameteriv ||\n        !glSamplerParameterf ||\n        !glSamplerParameterfv ||\n        !glGetSamplerParameteriv ||\n        !glGetSamplerParameterfv ||\n        !glVertexAttribDivisor ||\n        !glBindTransformFeedback ||\n        !glDeleteTransformFeedbacks ||\n        !glGenTransformFeedbacks ||\n        !glIsTransformFeedback ||\n        !glPauseTransformFeedback ||\n        !glResumeTransformFeedback ||\n        !glGetProgramBinary ||\n        !glProgramBinary ||\n        !glProgramParameteri ||\n        !glInvalidateFramebuffer ||\n        !glInvalidateSubFramebuffer ||\n        !glTexStorage2D ||\n        !glTexStorage3D ||\n        !glGetInternalformativ)\n    {\n        return GL_FALSE;\n    }\n\n    return GL_TRUE;\n}\n\n/* Function pointer definitions */\nGL_APICALL void           (* GL_APIENTRY glReadBuffer) (GLenum mode);\nGL_APICALL void           (* GL_APIENTRY glDrawRangeElements) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid* indices);\nGL_APICALL void           (* GL_APIENTRY glTexImage3D) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid* pixels);\nGL_APICALL void           (* GL_APIENTRY glTexSubImage3D) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid* pixels);\nGL_APICALL void           (* GL_APIENTRY glCopyTexSubImage3D) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height);\nGL_APICALL void           (* GL_APIENTRY glCompressedTexImage3D) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid* data);\nGL_APICALL void           (* GL_APIENTRY glCompressedTexSubImage3D) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid* data);\nGL_APICALL void           (* GL_APIENTRY glGenQueries) (GLsizei n, GLuint* ids);\nGL_APICALL void           (* GL_APIENTRY glDeleteQueries) (GLsizei n, const GLuint* ids);\nGL_APICALL GLboolean      (* GL_APIENTRY glIsQuery) (GLuint id);\nGL_APICALL void           (* GL_APIENTRY glBeginQuery) (GLenum target, GLuint id);\nGL_APICALL void           (* GL_APIENTRY glEndQuery) (GLenum target);\nGL_APICALL void           (* GL_APIENTRY glGetQueryiv) (GLenum target, GLenum pname, GLint* params);\nGL_APICALL void           (* GL_APIENTRY glGetQueryObjectuiv) (GLuint id, GLenum pname, GLuint* params);\nGL_APICALL GLboolean      (* GL_APIENTRY glUnmapBuffer) (GLenum target);\nGL_APICALL void           (* GL_APIENTRY glGetBufferPointerv) (GLenum target, GLenum pname, GLvoid** params);\nGL_APICALL void           (* GL_APIENTRY glDrawBuffers) (GLsizei n, const GLenum* bufs);\nGL_APICALL void           (* GL_APIENTRY glUniformMatrix2x3fv) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);\nGL_APICALL void           (* GL_APIENTRY glUniformMatrix3x2fv) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);\nGL_APICALL void           (* GL_APIENTRY glUniformMatrix2x4fv) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);\nGL_APICALL void           (* GL_APIENTRY glUniformMatrix4x2fv) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);\nGL_APICALL void           (* GL_APIENTRY glUniformMatrix3x4fv) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);\nGL_APICALL void           (* GL_APIENTRY glUniformMatrix4x3fv) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);\nGL_APICALL void           (* GL_APIENTRY glBlitFramebuffer) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);\nGL_APICALL void           (* GL_APIENTRY glRenderbufferStorageMultisample) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);\nGL_APICALL void           (* GL_APIENTRY glFramebufferTextureLayer) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);\nGL_APICALL GLvoid*        (* GL_APIENTRY glMapBufferRange) (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);\nGL_APICALL void           (* GL_APIENTRY glFlushMappedBufferRange) (GLenum target, GLintptr offset, GLsizeiptr length);\nGL_APICALL void           (* GL_APIENTRY glBindVertexArray) (GLuint array);\nGL_APICALL void           (* GL_APIENTRY glDeleteVertexArrays) (GLsizei n, const GLuint* arrays);\nGL_APICALL void           (* GL_APIENTRY glGenVertexArrays) (GLsizei n, GLuint* arrays);\nGL_APICALL GLboolean      (* GL_APIENTRY glIsVertexArray) (GLuint array);\nGL_APICALL void           (* GL_APIENTRY glGetIntegeri_v) (GLenum target, GLuint index, GLint* data);\nGL_APICALL void           (* GL_APIENTRY glBeginTransformFeedback) (GLenum primitiveMode);\nGL_APICALL void           (* GL_APIENTRY glEndTransformFeedback) (void);\nGL_APICALL void           (* GL_APIENTRY glBindBufferRange) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);\nGL_APICALL void           (* GL_APIENTRY glBindBufferBase) (GLenum target, GLuint index, GLuint buffer);\nGL_APICALL void           (* GL_APIENTRY glTransformFeedbackVaryings) (GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode);\nGL_APICALL void           (* GL_APIENTRY glGetTransformFeedbackVarying) (GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size, GLenum* type, GLchar* name);\nGL_APICALL void           (* GL_APIENTRY glVertexAttribIPointer) (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid* pointer);\nGL_APICALL void           (* GL_APIENTRY glGetVertexAttribIiv) (GLuint index, GLenum pname, GLint* params);\nGL_APICALL void           (* GL_APIENTRY glGetVertexAttribIuiv) (GLuint index, GLenum pname, GLuint* params);\nGL_APICALL void           (* GL_APIENTRY glVertexAttribI4i) (GLuint index, GLint x, GLint y, GLint z, GLint w);\nGL_APICALL void           (* GL_APIENTRY glVertexAttribI4ui) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w);\nGL_APICALL void           (* GL_APIENTRY glVertexAttribI4iv) (GLuint index, const GLint* v);\nGL_APICALL void           (* GL_APIENTRY glVertexAttribI4uiv) (GLuint index, const GLuint* v);\nGL_APICALL void           (* GL_APIENTRY glGetUniformuiv) (GLuint program, GLint location, GLuint* params);\nGL_APICALL GLint          (* GL_APIENTRY glGetFragDataLocation) (GLuint program, const GLchar *name);\nGL_APICALL void           (* GL_APIENTRY glUniform1ui) (GLint location, GLuint v0);\nGL_APICALL void           (* GL_APIENTRY glUniform2ui) (GLint location, GLuint v0, GLuint v1);\nGL_APICALL void           (* GL_APIENTRY glUniform3ui) (GLint location, GLuint v0, GLuint v1, GLuint v2);\nGL_APICALL void           (* GL_APIENTRY glUniform4ui) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);\nGL_APICALL void           (* GL_APIENTRY glUniform1uiv) (GLint location, GLsizei count, const GLuint* value);\nGL_APICALL void           (* GL_APIENTRY glUniform2uiv) (GLint location, GLsizei count, const GLuint* value);\nGL_APICALL void           (* GL_APIENTRY glUniform3uiv) (GLint location, GLsizei count, const GLuint* value);\nGL_APICALL void           (* GL_APIENTRY glUniform4uiv) (GLint location, GLsizei count, const GLuint* value);\nGL_APICALL void           (* GL_APIENTRY glClearBufferiv) (GLenum buffer, GLint drawbuffer, const GLint* value);\nGL_APICALL void           (* GL_APIENTRY glClearBufferuiv) (GLenum buffer, GLint drawbuffer, const GLuint* value);\nGL_APICALL void           (* GL_APIENTRY glClearBufferfv) (GLenum buffer, GLint drawbuffer, const GLfloat* value);\nGL_APICALL void           (* GL_APIENTRY glClearBufferfi) (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);\nGL_APICALL const GLubyte* (* GL_APIENTRY glGetStringi) (GLenum name, GLuint index);\nGL_APICALL void           (* GL_APIENTRY glCopyBufferSubData) (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size);\nGL_APICALL void           (* GL_APIENTRY glGetUniformIndices) (GLuint program, GLsizei uniformCount, const GLchar* const* uniformNames, GLuint* uniformIndices);\nGL_APICALL void           (* GL_APIENTRY glGetActiveUniformsiv) (GLuint program, GLsizei uniformCount, const GLuint* uniformIndices, GLenum pname, GLint* params);\nGL_APICALL GLuint         (* GL_APIENTRY glGetUniformBlockIndex) (GLuint program, const GLchar* uniformBlockName);\nGL_APICALL void           (* GL_APIENTRY glGetActiveUniformBlockiv) (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params);\nGL_APICALL void           (* GL_APIENTRY glGetActiveUniformBlockName) (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length, GLchar* uniformBlockName);\nGL_APICALL void           (* GL_APIENTRY glUniformBlockBinding) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);\nGL_APICALL void           (* GL_APIENTRY glDrawArraysInstanced) (GLenum mode, GLint first, GLsizei count, GLsizei instanceCount);\nGL_APICALL void           (* GL_APIENTRY glDrawElementsInstanced) (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLsizei instanceCount);\nGL_APICALL GLsync         (* GL_APIENTRY glFenceSync) (GLenum condition, GLbitfield flags);\nGL_APICALL GLboolean      (* GL_APIENTRY glIsSync) (GLsync sync);\nGL_APICALL void           (* GL_APIENTRY glDeleteSync) (GLsync sync);\nGL_APICALL GLenum         (* GL_APIENTRY glClientWaitSync) (GLsync sync, GLbitfield flags, GLuint64 timeout);\nGL_APICALL void           (* GL_APIENTRY glWaitSync) (GLsync sync, GLbitfield flags, GLuint64 timeout);\nGL_APICALL void           (* GL_APIENTRY glGetInteger64v) (GLenum pname, GLint64* params);\nGL_APICALL void           (* GL_APIENTRY glGetSynciv) (GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values);\nGL_APICALL void           (* GL_APIENTRY glGetInteger64i_v) (GLenum target, GLuint index, GLint64* data);\nGL_APICALL void           (* GL_APIENTRY glGetBufferParameteri64v) (GLenum target, GLenum pname, GLint64* params);\nGL_APICALL void           (* GL_APIENTRY glGenSamplers) (GLsizei count, GLuint* samplers);\nGL_APICALL void           (* GL_APIENTRY glDeleteSamplers) (GLsizei count, const GLuint* samplers);\nGL_APICALL GLboolean      (* GL_APIENTRY glIsSampler) (GLuint sampler);\nGL_APICALL void           (* GL_APIENTRY glBindSampler) (GLuint unit, GLuint sampler);\nGL_APICALL void           (* GL_APIENTRY glSamplerParameteri) (GLuint sampler, GLenum pname, GLint param);\nGL_APICALL void           (* GL_APIENTRY glSamplerParameteriv) (GLuint sampler, GLenum pname, const GLint* param);\nGL_APICALL void           (* GL_APIENTRY glSamplerParameterf) (GLuint sampler, GLenum pname, GLfloat param);\nGL_APICALL void           (* GL_APIENTRY glSamplerParameterfv) (GLuint sampler, GLenum pname, const GLfloat* param);\nGL_APICALL void           (* GL_APIENTRY glGetSamplerParameteriv) (GLuint sampler, GLenum pname, GLint* params);\nGL_APICALL void           (* GL_APIENTRY glGetSamplerParameterfv) (GLuint sampler, GLenum pname, GLfloat* params);\nGL_APICALL void           (* GL_APIENTRY glVertexAttribDivisor) (GLuint index, GLuint divisor);\nGL_APICALL void           (* GL_APIENTRY glBindTransformFeedback) (GLenum target, GLuint id);\nGL_APICALL void           (* GL_APIENTRY glDeleteTransformFeedbacks) (GLsizei n, const GLuint* ids);\nGL_APICALL void           (* GL_APIENTRY glGenTransformFeedbacks) (GLsizei n, GLuint* ids);\nGL_APICALL GLboolean      (* GL_APIENTRY glIsTransformFeedback) (GLuint id);\nGL_APICALL void           (* GL_APIENTRY glPauseTransformFeedback) (void);\nGL_APICALL void           (* GL_APIENTRY glResumeTransformFeedback) (void);\nGL_APICALL void           (* GL_APIENTRY glGetProgramBinary) (GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, GLvoid* binary);\nGL_APICALL void           (* GL_APIENTRY glProgramBinary) (GLuint program, GLenum binaryFormat, const GLvoid* binary, GLsizei length);\nGL_APICALL void           (* GL_APIENTRY glProgramParameteri) (GLuint program, GLenum pname, GLint value);\nGL_APICALL void           (* GL_APIENTRY glInvalidateFramebuffer) (GLenum target, GLsizei numAttachments, const GLenum* attachments);\nGL_APICALL void           (* GL_APIENTRY glInvalidateSubFramebuffer) (GLenum target, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y, GLsizei width, GLsizei height);\nGL_APICALL void           (* GL_APIENTRY glTexStorage2D) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);\nGL_APICALL void           (* GL_APIENTRY glTexStorage3D) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth);\nGL_APICALL void           (* GL_APIENTRY glGetInternalformativ) (GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params);\n\n// GL_EXT_texture_border_clamp\n\nGL_APICALL void           (* GL_APIENTRY  glTexParameterIivEXT) (GLenum target, GLenum pname, const GLint *params);\nGL_APICALL void           (* GL_APIENTRY  glTexParameterIuivEXT) (GLenum target, GLenum pname, const GLuint *params);\nGL_APICALL void           (* GL_APIENTRY  glGetTexParameterIivEXT) (GLenum target, GLenum pname, GLint *params);\nGL_APICALL void           (* GL_APIENTRY  glGetTexParameterIuivEXT) (GLenum target, GLenum pname, GLuint *params);\nGL_APICALL void           (* GL_APIENTRY  glSamplerParameterIivEXT) (GLuint sampler, GLenum pname, const GLint *param);\nGL_APICALL void           (* GL_APIENTRY  glSamplerParameterIuivEXT) (GLuint sampler, GLenum pname, const GLuint *param);\nGL_APICALL void           (* GL_APIENTRY  glGetSamplerParameterIivEXT) (GLuint sampler, GLenum pname, GLint *params);\nGL_APICALL void           (* GL_APIENTRY  glGetSamplerParameterIuivEXT) (GLuint sampler, GLenum pname, GLuint *params);\n\n// End GL_EXT_texture_border_clamp\n\n// clang-format on\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/android/gl3stub.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#ifndef __gl3_h_\n#define __gl3_h_\n\n/*\n * stub gl3.h for dynamic loading, based on:\n * gl3.h last updated on $Date: 2013-02-12 14:37:24 -0800 (Tue, 12 Feb 2013) $\n *\n * Changes:\n * - Added #include <GLES2/gl2.h>\n * - Removed duplicate OpenGL ES 2.0 declarations\n * - Converted OpenGL ES 3.0 function prototypes to function pointer\n *   declarations\n * - Added gl3stubInit() declaration\n */\n\n#include <GLES2/gl2.h>\n#include <android/api-level.h>\n\n// clang-format off\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* Call this function before calling any OpenGL ES 3.0 functions. It will\n * return GL_TRUE if the OpenGL ES 3.0 was successfully initialized, GL_FALSE\n * otherwise. */\nGLboolean gl3stubInit();\n\n/*-------------------------------------------------------------------------\n * Data type definitions\n *-----------------------------------------------------------------------*/\n\n/* OpenGL ES 3.0 */\n\ntypedef unsigned short   GLhalf;\n#if __ANDROID_API__ <= 19\ntypedef khronos_int64_t  GLint64;\ntypedef khronos_uint64_t GLuint64;\ntypedef struct __GLsync *GLsync;\n#endif\n\n/*-------------------------------------------------------------------------\n * Token definitions\n *-----------------------------------------------------------------------*/\n\n/* OpenGL ES core versions */\n#define GL_ES_VERSION_3_0                                1\n\n/* OpenGL ES 3.0 */\n\n#define GL_READ_BUFFER                                   0x0C02\n#define GL_UNPACK_ROW_LENGTH                             0x0CF2\n#define GL_UNPACK_SKIP_ROWS                              0x0CF3\n#define GL_UNPACK_SKIP_PIXELS                            0x0CF4\n#define GL_PACK_ROW_LENGTH                               0x0D02\n#define GL_PACK_SKIP_ROWS                                0x0D03\n#define GL_PACK_SKIP_PIXELS                              0x0D04\n#define GL_COLOR                                         0x1800\n#define GL_DEPTH                                         0x1801\n#define GL_STENCIL                                       0x1802\n#define GL_RED                                           0x1903\n#define GL_RGB8                                          0x8051\n#define GL_RGBA8                                         0x8058\n#define GL_RGB10_A2                                      0x8059\n#define GL_TEXTURE_BINDING_3D                            0x806A\n#define GL_UNPACK_SKIP_IMAGES                            0x806D\n#define GL_UNPACK_IMAGE_HEIGHT                           0x806E\n#define GL_TEXTURE_3D                                    0x806F\n#define GL_TEXTURE_WRAP_R                                0x8072\n#define GL_MAX_3D_TEXTURE_SIZE                           0x8073\n#define GL_UNSIGNED_INT_2_10_10_10_REV                   0x8368\n#define GL_MAX_ELEMENTS_VERTICES                         0x80E8\n#define GL_MAX_ELEMENTS_INDICES                          0x80E9\n#define GL_TEXTURE_MIN_LOD                               0x813A\n#define GL_TEXTURE_MAX_LOD                               0x813B\n#define GL_TEXTURE_BASE_LEVEL                            0x813C\n#define GL_TEXTURE_MAX_LEVEL                             0x813D\n#define GL_MIN                                           0x8007\n#define GL_MAX                                           0x8008\n#define GL_DEPTH_COMPONENT24                             0x81A6\n#define GL_MAX_TEXTURE_LOD_BIAS                          0x84FD\n#define GL_TEXTURE_COMPARE_MODE                          0x884C\n#define GL_TEXTURE_COMPARE_FUNC                          0x884D\n#define GL_CURRENT_QUERY                                 0x8865\n#define GL_QUERY_RESULT                                  0x8866\n#define GL_QUERY_RESULT_AVAILABLE                        0x8867\n#define GL_BUFFER_MAPPED                                 0x88BC\n#define GL_BUFFER_MAP_POINTER                            0x88BD\n#define GL_STREAM_READ                                   0x88E1\n#define GL_STREAM_COPY                                   0x88E2\n#define GL_STATIC_READ                                   0x88E5\n#define GL_STATIC_COPY                                   0x88E6\n#define GL_DYNAMIC_READ                                  0x88E9\n#define GL_DYNAMIC_COPY                                  0x88EA\n#define GL_MAX_DRAW_BUFFERS                              0x8824\n#define GL_DRAW_BUFFER0                                  0x8825\n#define GL_DRAW_BUFFER1                                  0x8826\n#define GL_DRAW_BUFFER2                                  0x8827\n#define GL_DRAW_BUFFER3                                  0x8828\n#define GL_DRAW_BUFFER4                                  0x8829\n#define GL_DRAW_BUFFER5                                  0x882A\n#define GL_DRAW_BUFFER6                                  0x882B\n#define GL_DRAW_BUFFER7                                  0x882C\n#define GL_DRAW_BUFFER8                                  0x882D\n#define GL_DRAW_BUFFER9                                  0x882E\n#define GL_DRAW_BUFFER10                                 0x882F\n#define GL_DRAW_BUFFER11                                 0x8830\n#define GL_DRAW_BUFFER12                                 0x8831\n#define GL_DRAW_BUFFER13                                 0x8832\n#define GL_DRAW_BUFFER14                                 0x8833\n#define GL_DRAW_BUFFER15                                 0x8834\n#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS               0x8B49\n#define GL_MAX_VERTEX_UNIFORM_COMPONENTS                 0x8B4A\n#define GL_SAMPLER_3D                                    0x8B5F\n#define GL_SAMPLER_2D_SHADOW                             0x8B62\n#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT               0x8B8B\n#define GL_PIXEL_PACK_BUFFER                             0x88EB\n#define GL_PIXEL_UNPACK_BUFFER                           0x88EC\n#define GL_PIXEL_PACK_BUFFER_BINDING                     0x88ED\n#define GL_PIXEL_UNPACK_BUFFER_BINDING                   0x88EF\n#define GL_FLOAT_MAT2x3                                  0x8B65\n#define GL_FLOAT_MAT2x4                                  0x8B66\n#define GL_FLOAT_MAT3x2                                  0x8B67\n#define GL_FLOAT_MAT3x4                                  0x8B68\n#define GL_FLOAT_MAT4x2                                  0x8B69\n#define GL_FLOAT_MAT4x3                                  0x8B6A\n#define GL_SRGB                                          0x8C40\n#define GL_SRGB8                                         0x8C41\n#define GL_SRGB8_ALPHA8                                  0x8C43\n#define GL_COMPARE_REF_TO_TEXTURE                        0x884E\n#define GL_MAJOR_VERSION                                 0x821B\n#define GL_MINOR_VERSION                                 0x821C\n#define GL_NUM_EXTENSIONS                                0x821D\n#define GL_RGBA32F                                       0x8814\n#define GL_RGB32F                                        0x8815\n#define GL_RGBA16F                                       0x881A\n#define GL_RGB16F                                        0x881B\n#define GL_VERTEX_ATTRIB_ARRAY_INTEGER                   0x88FD\n#define GL_MAX_ARRAY_TEXTURE_LAYERS                      0x88FF\n#define GL_MIN_PROGRAM_TEXEL_OFFSET                      0x8904\n#define GL_MAX_PROGRAM_TEXEL_OFFSET                      0x8905\n#define GL_MAX_VARYING_COMPONENTS                        0x8B4B\n#define GL_TEXTURE_2D_ARRAY                              0x8C1A\n#define GL_TEXTURE_BINDING_2D_ARRAY                      0x8C1D\n#define GL_R11F_G11F_B10F                                0x8C3A\n#define GL_UNSIGNED_INT_10F_11F_11F_REV                  0x8C3B\n#define GL_RGB9_E5                                       0x8C3D\n#define GL_UNSIGNED_INT_5_9_9_9_REV                      0x8C3E\n#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH         0x8C76\n#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE                0x8C7F\n#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS    0x8C80\n#define GL_TRANSFORM_FEEDBACK_VARYINGS                   0x8C83\n#define GL_TRANSFORM_FEEDBACK_BUFFER_START               0x8C84\n#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE                0x8C85\n#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN         0x8C88\n#define GL_RASTERIZER_DISCARD                            0x8C89\n#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A\n#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS       0x8C8B\n#define GL_INTERLEAVED_ATTRIBS                           0x8C8C\n#define GL_SEPARATE_ATTRIBS                              0x8C8D\n#define GL_TRANSFORM_FEEDBACK_BUFFER                     0x8C8E\n#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING             0x8C8F\n#define GL_RGBA32UI                                      0x8D70\n#define GL_RGB32UI                                       0x8D71\n#define GL_RGBA16UI                                      0x8D76\n#define GL_RGB16UI                                       0x8D77\n#define GL_RGBA8UI                                       0x8D7C\n#define GL_RGB8UI                                        0x8D7D\n#define GL_RGBA32I                                       0x8D82\n#define GL_RGB32I                                        0x8D83\n#define GL_RGBA16I                                       0x8D88\n#define GL_RGB16I                                        0x8D89\n#define GL_RGBA8I                                        0x8D8E\n#define GL_RGB8I                                         0x8D8F\n#define GL_RED_INTEGER                                   0x8D94\n#define GL_RGB_INTEGER                                   0x8D98\n#define GL_RGBA_INTEGER                                  0x8D99\n#define GL_SAMPLER_2D_ARRAY                              0x8DC1\n#define GL_SAMPLER_2D_ARRAY_SHADOW                       0x8DC4\n#define GL_SAMPLER_CUBE_SHADOW                           0x8DC5\n#define GL_UNSIGNED_INT_VEC2                             0x8DC6\n#define GL_UNSIGNED_INT_VEC3                             0x8DC7\n#define GL_UNSIGNED_INT_VEC4                             0x8DC8\n#define GL_INT_SAMPLER_2D                                0x8DCA\n#define GL_INT_SAMPLER_3D                                0x8DCB\n#define GL_INT_SAMPLER_CUBE                              0x8DCC\n#define GL_INT_SAMPLER_2D_ARRAY                          0x8DCF\n#define GL_UNSIGNED_INT_SAMPLER_2D                       0x8DD2\n#define GL_UNSIGNED_INT_SAMPLER_3D                       0x8DD3\n#define GL_UNSIGNED_INT_SAMPLER_CUBE                     0x8DD4\n#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY                 0x8DD7\n#define GL_BUFFER_ACCESS_FLAGS                           0x911F\n#define GL_BUFFER_MAP_LENGTH                             0x9120\n#define GL_BUFFER_MAP_OFFSET                             0x9121\n#define GL_DEPTH_COMPONENT32F                            0x8CAC\n#define GL_DEPTH32F_STENCIL8                             0x8CAD\n#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV                0x8DAD\n#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING         0x8210\n#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE         0x8211\n#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE               0x8212\n#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE             0x8213\n#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE              0x8214\n#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE             0x8215\n#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE             0x8216\n#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE           0x8217\n#define GL_FRAMEBUFFER_DEFAULT                           0x8218\n#define GL_FRAMEBUFFER_UNDEFINED                         0x8219\n#define GL_DEPTH_STENCIL_ATTACHMENT                      0x821A\n#define GL_DEPTH_STENCIL                                 0x84F9\n#define GL_UNSIGNED_INT_24_8                             0x84FA\n#define GL_DEPTH24_STENCIL8                              0x88F0\n#define GL_UNSIGNED_NORMALIZED                           0x8C17\n#define GL_DRAW_FRAMEBUFFER_BINDING                      GL_FRAMEBUFFER_BINDING\n#define GL_READ_FRAMEBUFFER                              0x8CA8\n#define GL_DRAW_FRAMEBUFFER                              0x8CA9\n#define GL_READ_FRAMEBUFFER_BINDING                      0x8CAA\n#define GL_RENDERBUFFER_SAMPLES                          0x8CAB\n#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER          0x8CD4\n#define GL_MAX_COLOR_ATTACHMENTS                         0x8CDF\n#define GL_COLOR_ATTACHMENT1                             0x8CE1\n#define GL_COLOR_ATTACHMENT2                             0x8CE2\n#define GL_COLOR_ATTACHMENT3                             0x8CE3\n#define GL_COLOR_ATTACHMENT4                             0x8CE4\n#define GL_COLOR_ATTACHMENT5                             0x8CE5\n#define GL_COLOR_ATTACHMENT6                             0x8CE6\n#define GL_COLOR_ATTACHMENT7                             0x8CE7\n#define GL_COLOR_ATTACHMENT8                             0x8CE8\n#define GL_COLOR_ATTACHMENT9                             0x8CE9\n#define GL_COLOR_ATTACHMENT10                            0x8CEA\n#define GL_COLOR_ATTACHMENT11                            0x8CEB\n#define GL_COLOR_ATTACHMENT12                            0x8CEC\n#define GL_COLOR_ATTACHMENT13                            0x8CED\n#define GL_COLOR_ATTACHMENT14                            0x8CEE\n#define GL_COLOR_ATTACHMENT15                            0x8CEF\n#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE            0x8D56\n#define GL_MAX_SAMPLES                                   0x8D57\n#define GL_HALF_FLOAT                                    0x140B\n#define GL_MAP_READ_BIT                                  0x0001\n#define GL_MAP_WRITE_BIT                                 0x0002\n#define GL_MAP_INVALIDATE_RANGE_BIT                      0x0004\n#define GL_MAP_INVALIDATE_BUFFER_BIT                     0x0008\n#define GL_MAP_FLUSH_EXPLICIT_BIT                        0x0010\n#define GL_MAP_UNSYNCHRONIZED_BIT                        0x0020\n#define GL_RG                                            0x8227\n#define GL_RG_INTEGER                                    0x8228\n#define GL_R8                                            0x8229\n#define GL_RG8                                           0x822B\n#define GL_R16F                                          0x822D\n#define GL_R32F                                          0x822E\n#define GL_RG16F                                         0x822F\n#define GL_RG32F                                         0x8230\n#define GL_R8I                                           0x8231\n#define GL_R8UI                                          0x8232\n#define GL_R16I                                          0x8233\n#define GL_R16UI                                         0x8234\n#define GL_R32I                                          0x8235\n#define GL_R32UI                                         0x8236\n#define GL_RG8I                                          0x8237\n#define GL_RG8UI                                         0x8238\n#define GL_RG16I                                         0x8239\n#define GL_RG16UI                                        0x823A\n#define GL_RG32I                                         0x823B\n#define GL_RG32UI                                        0x823C\n#define GL_VERTEX_ARRAY_BINDING                          0x85B5\n#define GL_R8_SNORM                                      0x8F94\n#define GL_RG8_SNORM                                     0x8F95\n#define GL_RGB8_SNORM                                    0x8F96\n#define GL_RGBA8_SNORM                                   0x8F97\n#define GL_SIGNED_NORMALIZED                             0x8F9C\n#define GL_PRIMITIVE_RESTART_FIXED_INDEX                 0x8D69\n#define GL_COPY_READ_BUFFER                              0x8F36\n#define GL_COPY_WRITE_BUFFER                             0x8F37\n#define GL_COPY_READ_BUFFER_BINDING                      GL_COPY_READ_BUFFER\n#define GL_COPY_WRITE_BUFFER_BINDING                     GL_COPY_WRITE_BUFFER\n#define GL_UNIFORM_BUFFER                                0x8A11\n#define GL_UNIFORM_BUFFER_BINDING                        0x8A28\n#define GL_UNIFORM_BUFFER_START                          0x8A29\n#define GL_UNIFORM_BUFFER_SIZE                           0x8A2A\n#define GL_MAX_VERTEX_UNIFORM_BLOCKS                     0x8A2B\n#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS                   0x8A2D\n#define GL_MAX_COMBINED_UNIFORM_BLOCKS                   0x8A2E\n#define GL_MAX_UNIFORM_BUFFER_BINDINGS                   0x8A2F\n#define GL_MAX_UNIFORM_BLOCK_SIZE                        0x8A30\n#define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS        0x8A31\n#define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS      0x8A33\n#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT               0x8A34\n#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH          0x8A35\n#define GL_ACTIVE_UNIFORM_BLOCKS                         0x8A36\n#define GL_UNIFORM_TYPE                                  0x8A37\n#define GL_UNIFORM_SIZE                                  0x8A38\n#define GL_UNIFORM_NAME_LENGTH                           0x8A39\n#define GL_UNIFORM_BLOCK_INDEX                           0x8A3A\n#define GL_UNIFORM_OFFSET                                0x8A3B\n#define GL_UNIFORM_ARRAY_STRIDE                          0x8A3C\n#define GL_UNIFORM_MATRIX_STRIDE                         0x8A3D\n#define GL_UNIFORM_IS_ROW_MAJOR                          0x8A3E\n#define GL_UNIFORM_BLOCK_BINDING                         0x8A3F\n#define GL_UNIFORM_BLOCK_DATA_SIZE                       0x8A40\n#define GL_UNIFORM_BLOCK_NAME_LENGTH                     0x8A41\n#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS                 0x8A42\n#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES          0x8A43\n#define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER     0x8A44\n#define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER   0x8A46\n#define GL_INVALID_INDEX                                 0xFFFFFFFFu\n#define GL_MAX_VERTEX_OUTPUT_COMPONENTS                  0x9122\n#define GL_MAX_FRAGMENT_INPUT_COMPONENTS                 0x9125\n#define GL_MAX_SERVER_WAIT_TIMEOUT                       0x9111\n#define GL_OBJECT_TYPE                                   0x9112\n#define GL_SYNC_CONDITION                                0x9113\n#define GL_SYNC_STATUS                                   0x9114\n#define GL_SYNC_FLAGS                                    0x9115\n#define GL_SYNC_FENCE                                    0x9116\n#define GL_SYNC_GPU_COMMANDS_COMPLETE                    0x9117\n#define GL_UNSIGNALED                                    0x9118\n#define GL_SIGNALED                                      0x9119\n#define GL_ALREADY_SIGNALED                              0x911A\n#define GL_TIMEOUT_EXPIRED                               0x911B\n#define GL_CONDITION_SATISFIED                           0x911C\n#define GL_WAIT_FAILED                                   0x911D\n#define GL_SYNC_FLUSH_COMMANDS_BIT                       0x00000001\n#define GL_TIMEOUT_IGNORED                               0xFFFFFFFFFFFFFFFFull\n#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR                   0x88FE\n#define GL_ANY_SAMPLES_PASSED                            0x8C2F\n#define GL_ANY_SAMPLES_PASSED_CONSERVATIVE               0x8D6A\n#define GL_SAMPLER_BINDING                               0x8919\n#define GL_RGB10_A2UI                                    0x906F\n#define GL_TEXTURE_SWIZZLE_R                             0x8E42\n#define GL_TEXTURE_SWIZZLE_G                             0x8E43\n#define GL_TEXTURE_SWIZZLE_B                             0x8E44\n#define GL_TEXTURE_SWIZZLE_A                             0x8E45\n#define GL_GREEN                                         0x1904\n#define GL_BLUE                                          0x1905\n#define GL_INT_2_10_10_10_REV                            0x8D9F\n#define GL_TRANSFORM_FEEDBACK                            0x8E22\n#define GL_TRANSFORM_FEEDBACK_PAUSED                     0x8E23\n#define GL_TRANSFORM_FEEDBACK_ACTIVE                     0x8E24\n#define GL_TRANSFORM_FEEDBACK_BINDING                    0x8E25\n#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT               0x8257\n#define GL_PROGRAM_BINARY_LENGTH                         0x8741\n#define GL_NUM_PROGRAM_BINARY_FORMATS                    0x87FE\n#define GL_PROGRAM_BINARY_FORMATS                        0x87FF\n#define GL_COMPRESSED_R11_EAC                            0x9270\n#define GL_COMPRESSED_SIGNED_R11_EAC                     0x9271\n#define GL_COMPRESSED_RG11_EAC                           0x9272\n#define GL_COMPRESSED_SIGNED_RG11_EAC                    0x9273\n#define GL_COMPRESSED_RGB8_ETC2                          0x9274\n#define GL_COMPRESSED_SRGB8_ETC2                         0x9275\n#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2      0x9276\n#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2     0x9277\n#define GL_COMPRESSED_RGBA8_ETC2_EAC                     0x9278\n#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC              0x9279\n#define GL_TEXTURE_IMMUTABLE_FORMAT                      0x912F\n#define GL_MAX_ELEMENT_INDEX                             0x8D6B\n#define GL_NUM_SAMPLE_COUNTS                             0x9380\n#define GL_TEXTURE_IMMUTABLE_LEVELS                      0x82DF\n\n/*-------------------------------------------------------------------------\n * Entrypoint definitions\n *-----------------------------------------------------------------------*/\n\n/* OpenGL ES 3.0 */\n\nextern GL_APICALL void           (* GL_APIENTRY glReadBuffer) (GLenum mode);\nextern GL_APICALL void           (* GL_APIENTRY glDrawRangeElements) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid* indices);\nextern GL_APICALL void           (* GL_APIENTRY glTexImage3D) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid* pixels);\nextern GL_APICALL void           (* GL_APIENTRY glTexSubImage3D) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid* pixels);\nextern GL_APICALL void           (* GL_APIENTRY glCopyTexSubImage3D) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height);\nextern GL_APICALL void           (* GL_APIENTRY glCompressedTexImage3D) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid* data);\nextern GL_APICALL void           (* GL_APIENTRY glCompressedTexSubImage3D) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid* data);\nextern GL_APICALL void           (* GL_APIENTRY glGenQueries) (GLsizei n, GLuint* ids);\nextern GL_APICALL void           (* GL_APIENTRY glDeleteQueries) (GLsizei n, const GLuint* ids);\nextern GL_APICALL GLboolean      (* GL_APIENTRY glIsQuery) (GLuint id);\nextern GL_APICALL void           (* GL_APIENTRY glBeginQuery) (GLenum target, GLuint id);\nextern GL_APICALL void           (* GL_APIENTRY glEndQuery) (GLenum target);\nextern GL_APICALL void           (* GL_APIENTRY glGetQueryiv) (GLenum target, GLenum pname, GLint* params);\nextern GL_APICALL void           (* GL_APIENTRY glGetQueryObjectuiv) (GLuint id, GLenum pname, GLuint* params);\nextern GL_APICALL GLboolean      (* GL_APIENTRY glUnmapBuffer) (GLenum target);\nextern GL_APICALL void           (* GL_APIENTRY glGetBufferPointerv) (GLenum target, GLenum pname, GLvoid** params);\nextern GL_APICALL void           (* GL_APIENTRY glDrawBuffers) (GLsizei n, const GLenum* bufs);\nextern GL_APICALL void           (* GL_APIENTRY glUniformMatrix2x3fv) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);\nextern GL_APICALL void           (* GL_APIENTRY glUniformMatrix3x2fv) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);\nextern GL_APICALL void           (* GL_APIENTRY glUniformMatrix2x4fv) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);\nextern GL_APICALL void           (* GL_APIENTRY glUniformMatrix4x2fv) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);\nextern GL_APICALL void           (* GL_APIENTRY glUniformMatrix3x4fv) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);\nextern GL_APICALL void           (* GL_APIENTRY glUniformMatrix4x3fv) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);\nextern GL_APICALL void           (* GL_APIENTRY glBlitFramebuffer) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);\nextern GL_APICALL void           (* GL_APIENTRY glRenderbufferStorageMultisample) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);\nextern GL_APICALL void           (* GL_APIENTRY glFramebufferTextureLayer) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);\nextern GL_APICALL GLvoid*        (* GL_APIENTRY glMapBufferRange) (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);\nextern GL_APICALL void           (* GL_APIENTRY glFlushMappedBufferRange) (GLenum target, GLintptr offset, GLsizeiptr length);\nextern GL_APICALL void           (* GL_APIENTRY glBindVertexArray) (GLuint array);\nextern GL_APICALL void           (* GL_APIENTRY glDeleteVertexArrays) (GLsizei n, const GLuint* arrays);\nextern GL_APICALL void           (* GL_APIENTRY glGenVertexArrays) (GLsizei n, GLuint* arrays);\nextern GL_APICALL GLboolean      (* GL_APIENTRY glIsVertexArray) (GLuint array);\nextern GL_APICALL void           (* GL_APIENTRY glGetIntegeri_v) (GLenum target, GLuint index, GLint* data);\nextern GL_APICALL void           (* GL_APIENTRY glBeginTransformFeedback) (GLenum primitiveMode);\nextern GL_APICALL void           (* GL_APIENTRY glEndTransformFeedback) (void);\nextern GL_APICALL void           (* GL_APIENTRY glBindBufferRange) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);\nextern GL_APICALL void           (* GL_APIENTRY glBindBufferBase) (GLenum target, GLuint index, GLuint buffer);\nextern GL_APICALL void           (* GL_APIENTRY glTransformFeedbackVaryings) (GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode);\nextern GL_APICALL void           (* GL_APIENTRY glGetTransformFeedbackVarying) (GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size, GLenum* type, GLchar* name);\nextern GL_APICALL void           (* GL_APIENTRY glVertexAttribIPointer) (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid* pointer);\nextern GL_APICALL void           (* GL_APIENTRY glGetVertexAttribIiv) (GLuint index, GLenum pname, GLint* params);\nextern GL_APICALL void           (* GL_APIENTRY glGetVertexAttribIuiv) (GLuint index, GLenum pname, GLuint* params);\nextern GL_APICALL void           (* GL_APIENTRY glVertexAttribI4i) (GLuint index, GLint x, GLint y, GLint z, GLint w);\nextern GL_APICALL void           (* GL_APIENTRY glVertexAttribI4ui) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w);\nextern GL_APICALL void           (* GL_APIENTRY glVertexAttribI4iv) (GLuint index, const GLint* v);\nextern GL_APICALL void           (* GL_APIENTRY glVertexAttribI4uiv) (GLuint index, const GLuint* v);\nextern GL_APICALL void           (* GL_APIENTRY glGetUniformuiv) (GLuint program, GLint location, GLuint* params);\nextern GL_APICALL GLint          (* GL_APIENTRY glGetFragDataLocation) (GLuint program, const GLchar *name);\nextern GL_APICALL void           (* GL_APIENTRY glUniform1ui) (GLint location, GLuint v0);\nextern GL_APICALL void           (* GL_APIENTRY glUniform2ui) (GLint location, GLuint v0, GLuint v1);\nextern GL_APICALL void           (* GL_APIENTRY glUniform3ui) (GLint location, GLuint v0, GLuint v1, GLuint v2);\nextern GL_APICALL void           (* GL_APIENTRY glUniform4ui) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);\nextern GL_APICALL void           (* GL_APIENTRY glUniform1uiv) (GLint location, GLsizei count, const GLuint* value);\nextern GL_APICALL void           (* GL_APIENTRY glUniform2uiv) (GLint location, GLsizei count, const GLuint* value);\nextern GL_APICALL void           (* GL_APIENTRY glUniform3uiv) (GLint location, GLsizei count, const GLuint* value);\nextern GL_APICALL void           (* GL_APIENTRY glUniform4uiv) (GLint location, GLsizei count, const GLuint* value);\nextern GL_APICALL void           (* GL_APIENTRY glClearBufferiv) (GLenum buffer, GLint drawbuffer, const GLint* value);\nextern GL_APICALL void           (* GL_APIENTRY glClearBufferuiv) (GLenum buffer, GLint drawbuffer, const GLuint* value);\nextern GL_APICALL void           (* GL_APIENTRY glClearBufferfv) (GLenum buffer, GLint drawbuffer, const GLfloat* value);\nextern GL_APICALL void           (* GL_APIENTRY glClearBufferfi) (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);\nextern GL_APICALL const GLubyte* (* GL_APIENTRY glGetStringi) (GLenum name, GLuint index);\nextern GL_APICALL void           (* GL_APIENTRY glCopyBufferSubData) (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size);\nextern GL_APICALL void           (* GL_APIENTRY glGetUniformIndices) (GLuint program, GLsizei uniformCount, const GLchar* const* uniformNames, GLuint* uniformIndices);\nextern GL_APICALL void           (* GL_APIENTRY glGetActiveUniformsiv) (GLuint program, GLsizei uniformCount, const GLuint* uniformIndices, GLenum pname, GLint* params);\nextern GL_APICALL GLuint         (* GL_APIENTRY glGetUniformBlockIndex) (GLuint program, const GLchar* uniformBlockName);\nextern GL_APICALL void           (* GL_APIENTRY glGetActiveUniformBlockiv) (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params);\nextern GL_APICALL void           (* GL_APIENTRY glGetActiveUniformBlockName) (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length, GLchar* uniformBlockName);\nextern GL_APICALL void           (* GL_APIENTRY glUniformBlockBinding) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);\nextern GL_APICALL void           (* GL_APIENTRY glDrawArraysInstanced) (GLenum mode, GLint first, GLsizei count, GLsizei instanceCount);\nextern GL_APICALL void           (* GL_APIENTRY glDrawElementsInstanced) (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLsizei instanceCount);\nextern GL_APICALL GLsync         (* GL_APIENTRY glFenceSync) (GLenum condition, GLbitfield flags);\nextern GL_APICALL GLboolean      (* GL_APIENTRY glIsSync) (GLsync sync);\nextern GL_APICALL void           (* GL_APIENTRY glDeleteSync) (GLsync sync);\nextern GL_APICALL GLenum         (* GL_APIENTRY glClientWaitSync) (GLsync sync, GLbitfield flags, GLuint64 timeout);\nextern GL_APICALL void           (* GL_APIENTRY glWaitSync) (GLsync sync, GLbitfield flags, GLuint64 timeout);\nextern GL_APICALL void           (* GL_APIENTRY glGetInteger64v) (GLenum pname, GLint64* params);\nextern GL_APICALL void           (* GL_APIENTRY glGetSynciv) (GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values);\nextern GL_APICALL void           (* GL_APIENTRY glGetInteger64i_v) (GLenum target, GLuint index, GLint64* data);\nextern GL_APICALL void           (* GL_APIENTRY glGetBufferParameteri64v) (GLenum target, GLenum pname, GLint64* params);\nextern GL_APICALL void           (* GL_APIENTRY glGenSamplers) (GLsizei count, GLuint* samplers);\nextern GL_APICALL void           (* GL_APIENTRY glDeleteSamplers) (GLsizei count, const GLuint* samplers);\nextern GL_APICALL GLboolean      (* GL_APIENTRY glIsSampler) (GLuint sampler);\nextern GL_APICALL void           (* GL_APIENTRY glBindSampler) (GLuint unit, GLuint sampler);\nextern GL_APICALL void           (* GL_APIENTRY glSamplerParameteri) (GLuint sampler, GLenum pname, GLint param);\nextern GL_APICALL void           (* GL_APIENTRY glSamplerParameteriv) (GLuint sampler, GLenum pname, const GLint* param);\nextern GL_APICALL void           (* GL_APIENTRY glSamplerParameterf) (GLuint sampler, GLenum pname, GLfloat param);\nextern GL_APICALL void           (* GL_APIENTRY glSamplerParameterfv) (GLuint sampler, GLenum pname, const GLfloat* param);\nextern GL_APICALL void           (* GL_APIENTRY glGetSamplerParameteriv) (GLuint sampler, GLenum pname, GLint* params);\nextern GL_APICALL void           (* GL_APIENTRY glGetSamplerParameterfv) (GLuint sampler, GLenum pname, GLfloat* params);\nextern GL_APICALL void           (* GL_APIENTRY glVertexAttribDivisor) (GLuint index, GLuint divisor);\nextern GL_APICALL void           (* GL_APIENTRY glBindTransformFeedback) (GLenum target, GLuint id);\nextern GL_APICALL void           (* GL_APIENTRY glDeleteTransformFeedbacks) (GLsizei n, const GLuint* ids);\nextern GL_APICALL void           (* GL_APIENTRY glGenTransformFeedbacks) (GLsizei n, GLuint* ids);\nextern GL_APICALL GLboolean      (* GL_APIENTRY glIsTransformFeedback) (GLuint id);\nextern GL_APICALL void           (* GL_APIENTRY glPauseTransformFeedback) (void);\nextern GL_APICALL void           (* GL_APIENTRY glResumeTransformFeedback) (void);\nextern GL_APICALL void           (* GL_APIENTRY glGetProgramBinary) (GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, GLvoid* binary);\nextern GL_APICALL void           (* GL_APIENTRY glProgramBinary) (GLuint program, GLenum binaryFormat, const GLvoid* binary, GLsizei length);\nextern GL_APICALL void           (* GL_APIENTRY glProgramParameteri) (GLuint program, GLenum pname, GLint value);\nextern GL_APICALL void           (* GL_APIENTRY glInvalidateFramebuffer) (GLenum target, GLsizei numAttachments, const GLenum* attachments);\nextern GL_APICALL void           (* GL_APIENTRY glInvalidateSubFramebuffer) (GLenum target, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y, GLsizei width, GLsizei height);\nextern GL_APICALL void           (* GL_APIENTRY glTexStorage2D) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);\nextern GL_APICALL void           (* GL_APIENTRY glTexStorage3D) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth);\nextern GL_APICALL void           (* GL_APIENTRY glGetInternalformativ) (GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params);\n\n#ifndef GL_EXT_texture_border_clamp\n#define GL_EXT_texture_border_clamp 1\n#define GL_TEXTURE_BORDER_COLOR_EXT       0x1004\n#define GL_CLAMP_TO_BORDER_EXT            0x812D\nextern GL_APICALL void           (* GL_APIENTRY  glTexParameterIivEXT) (GLenum target, GLenum pname, const GLint *params);\nextern GL_APICALL void           (* GL_APIENTRY  glTexParameterIuivEXT) (GLenum target, GLenum pname, const GLuint *params);\nextern GL_APICALL void           (* GL_APIENTRY  glGetTexParameterIivEXT) (GLenum target, GLenum pname, GLint *params);\nextern GL_APICALL void           (* GL_APIENTRY  glGetTexParameterIuivEXT) (GLenum target, GLenum pname, GLuint *params);\nextern GL_APICALL void           (* GL_APIENTRY  glSamplerParameterIivEXT) (GLuint sampler, GLenum pname, const GLint *param);\nextern GL_APICALL void           (* GL_APIENTRY  glSamplerParameterIuivEXT) (GLuint sampler, GLenum pname, const GLuint *param);\nextern GL_APICALL void           (* GL_APIENTRY  glGetSamplerParameterIivEXT) (GLuint sampler, GLenum pname, GLint *params);\nextern GL_APICALL void           (* GL_APIENTRY  glGetSamplerParameterIuivEXT) (GLuint sampler, GLenum pname, GLuint *params);\n#endif /* GL_EXT_texture_border_clamp */\n\n#ifdef __cplusplus\n}\n#endif\n// clang-format on\n\n#endif\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/CMakeLists.txt",
    "content": "file(GLOB_RECURSE tmp *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/DataTransfer.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"DataTransfer.h\"\n#include \"GLLogging.h\"\n\n#include \"caffe2/core/common.h\"\n\ninline uint16x4x4_t vld4_u16_aligned16(const uint16_t* address) {\n  return vld4_u16(static_cast<const uint16_t*>(__builtin_assume_aligned(address, 16)));\n}\n\ninline uint16x4_t vld1_u16_aligned8(const uint16_t* address) {\n  return vld1_u16(static_cast<const uint16_t*>(__builtin_assume_aligned(address, 8)));\n}\n\ninline void vst4_u16_aligned16(uint16_t* address, uint16x4x4_t data) {\n  vst4_u16(static_cast<uint16_t*>(__builtin_assume_aligned(address, 16)), data);\n}\n\ninline void vst1_u16_aligned8(uint16_t* address, uint16x4_t data) {\n  vst1_u16(static_cast<uint16_t*>(__builtin_assume_aligned(address, 8)), data);\n}\n\ntemplate <int input_channels>\nstatic void interleaveSlice(\n    void* output, const float* input, size_t width, size_t height, size_t row_stride) {\n  const float* input_r = input;\n  const float* input_g = input_r + height * width;\n  const float* input_b = input_g + height * width;\n  const float* input_a = input_b + height * width;\n  uint16_t* output_f16 = static_cast<uint16_t*>(output);\n  if (width >= 4) {\n    for (size_t y = 0; y < height; y++) {\n      size_t nx = width;\n      while (nx >= 4) {\n        const uint16x4_t r = uint16x4_t(vcvt_f16_f32(vld1q_f32(input_r)));\n        input_r += 4;\n        uint16x4_t g, b, a;\n        g = b = a = vdup_n_u16(0);\n        if (input_channels >= 2) {\n          g = uint16x4_t(vcvt_f16_f32(vld1q_f32(input_g)));\n          input_g += 4;\n          if (input_channels >= 3) {\n            b = uint16x4_t(vcvt_f16_f32(vld1q_f32(input_b)));\n            input_b += 4;\n            if (input_channels >= 4) {\n              a = uint16x4_t(vcvt_f16_f32(vld1q_f32(input_a)));\n              input_a += 4;\n            }\n          }\n        }\n\n        const uint16x4x4_t rgba = (uint16x4x4_t){{r, g, b, a}};\n        vst4_u16_aligned16(output_f16, rgba);\n        output_f16 += 4 * 4;\n\n        nx -= 4;\n      }\n      if (nx != 0) {\n        output_f16 -= (4 - nx) * 4;\n        input_r -= 4 - nx;\n        if (input_channels >= 2) {\n          input_g -= 4 - nx;\n          if (input_channels >= 3) {\n            input_b -= 4 - nx;\n            if (input_channels >= 4) {\n              input_a -= 4 - nx;\n            }\n          }\n        }\n\n        const uint16x4_t r = uint16x4_t(vcvt_f16_f32(vld1q_f32(input_r)));\n        input_r += 4;\n        uint16x4_t g, b, a;\n        g = b = a = vdup_n_u16(0);\n        if (input_channels >= 2) {\n          g = uint16x4_t(vcvt_f16_f32(vld1q_f32(input_g)));\n          input_g += 4;\n          if (input_channels >= 3) {\n            b = uint16x4_t(vcvt_f16_f32(vld1q_f32(input_b)));\n            input_b += 4;\n            if (input_channels >= 4) {\n              a = uint16x4_t(vcvt_f16_f32(vld1q_f32(input_a)));\n              input_a += 4;\n            }\n          }\n        }\n\n        const uint16x4x4_t rgba = (uint16x4x4_t){{r, g, b, a}};\n        vst4_u16_aligned16(output_f16, rgba);\n        output_f16 += 4 * 4;\n      }\n      output_f16 += (row_stride - width) * 4;\n    }\n  } else {\n    for (size_t y = 0; y < height; y++) {\n      for (size_t x = 0; x < width; x++) {\n        float32x4_t rgba = vld1q_dup_f32(input_r++);\n        if (input_channels >= 2) {\n          rgba = vld1q_lane_f32(input_g++, rgba, 1);\n          if (input_channels >= 3) {\n            rgba = vld1q_lane_f32(input_b++, rgba, 2);\n            if (input_channels >= 4) {\n              rgba = vld1q_lane_f32(input_a++, rgba, 3);\n            }\n          }\n        }\n        vst1_u16_aligned8(output_f16, uint16x4_t(vcvt_f16_f32(rgba)));\n        output_f16 += 4;\n      }\n      output_f16 += (row_stride - width) * 4;\n    }\n  }\n}\n\nvoid interleaveSlice(void* output,\n                     const float* input,\n                     size_t width,\n                     size_t height,\n                     size_t row_stride,\n                     uint16_t input_channels) {\n  switch (input_channels) {\n  case 1:\n    interleaveSlice<1>(output, input, width, height, row_stride);\n    break;\n  case 2:\n    interleaveSlice<2>(output, input, width, height, row_stride);\n    break;\n  case 3:\n    interleaveSlice<3>(output, input, width, height, row_stride);\n    break;\n  case 4:\n    interleaveSlice<4>(output, input, width, height, row_stride);\n    break;\n  }\n}\n\ntemplate <int output_channels>\nstatic void deInterleaveSlice(\n    float* output, const void* input, size_t width, size_t height, size_t row_stride) {\n  float* output_r = output;\n  float* output_g = output_r + height * width;\n  float* output_b = output_g + height * width;\n  float* output_a = output_b + height * width;\n  const uint16_t* input_f16 = static_cast<const uint16_t*>(input);\n  if (width >= 4) {\n    for (size_t y = 0; y < height; y++) {\n      size_t nx = width;\n      while (nx >= 4) {\n        const uint16x4x4_t rgba = vld4_u16_aligned16(input_f16);\n        input_f16 += 4 * 4;\n        const float32x4_t r = vcvt_f32_f16(float16x4_t(rgba.val[0]));\n        vst1q_f32(output_r, r);\n        output_r += 4;\n        if (output_channels >= 2) {\n          const float32x4_t g = vcvt_f32_f16(float16x4_t(rgba.val[1]));\n          vst1q_f32(output_g, g);\n          output_g += 4;\n          if (output_channels >= 3) {\n            const float32x4_t b = vcvt_f32_f16(float16x4_t(rgba.val[2]));\n            vst1q_f32(output_b, b);\n            output_b += 4;\n            if (output_channels >= 4) {\n              const float32x4_t a = vcvt_f32_f16(float16x4_t(rgba.val[3]));\n              vst1q_f32(output_a, a);\n              output_a += 4;\n            }\n          }\n        }\n\n        nx -= 4;\n      }\n      if (nx != 0) {\n        input_f16 -= (4 - nx) * 4;\n        output_r -= 4 - nx;\n        if (output_channels >= 2) {\n          output_g -= 4 - nx;\n          if (output_channels >= 3) {\n            output_b -= 4 - nx;\n            if (output_channels >= 4) {\n              output_a -= 4 - nx;\n            }\n          }\n        }\n\n        const uint16x4x4_t rgba = vld4_u16_aligned16(input_f16);\n        input_f16 += 4 * 4;\n        const float32x4_t r = vcvt_f32_f16(float16x4_t(rgba.val[0]));\n        vst1q_f32(output_r, r);\n        output_r += 4;\n        if (output_channels >= 2) {\n          const float32x4_t g = vcvt_f32_f16(float16x4_t(rgba.val[1]));\n          vst1q_f32(output_g, g);\n          output_g += 4;\n          if (output_channels >= 3) {\n            const float32x4_t b = vcvt_f32_f16(float16x4_t(rgba.val[2]));\n            vst1q_f32(output_b, b);\n            output_b += 4;\n            if (output_channels >= 4) {\n              const float32x4_t a = vcvt_f32_f16(float16x4_t(rgba.val[3]));\n              vst1q_f32(output_a, a);\n              output_a += 4;\n            }\n          }\n        }\n      }\n      input_f16 += (row_stride - width) * 4;\n    }\n  } else {\n    for (size_t y = 0; y < height; y++) {\n      for (size_t x = 0; x < width; x++) {\n        const float32x4_t rgba = vcvt_f32_f16(float16x4_t(vld1_u16_aligned8(input_f16)));\n        input_f16 += 4;\n        vst1q_lane_f32(output_r++, rgba, 0);\n        if (output_channels >= 2) {\n          vst1q_lane_f32(output_g++, rgba, 1);\n          if (output_channels >= 3) {\n            vst1q_lane_f32(output_b++, rgba, 2);\n            if (output_channels >= 4) {\n              vst1q_lane_f32(output_a++, rgba, 3);\n            }\n          }\n        }\n      }\n      input_f16 += (row_stride - width) * 4;\n    }\n  }\n}\n\nvoid deInterleaveSlice(float* output,\n                       const void* input,\n                       size_t width,\n                       size_t height,\n                       size_t row_stride,\n                       uint32_t output_channels) {\n  switch (output_channels) {\n  case 1:\n    deInterleaveSlice<1>(output, input, width, height, row_stride);\n    break;\n  case 2:\n    deInterleaveSlice<2>(output, input, width, height, row_stride);\n    break;\n  case 3:\n    deInterleaveSlice<3>(output, input, width, height, row_stride);\n    break;\n  case 4:\n    deInterleaveSlice<4>(output, input, width, height, row_stride);\n    break;\n  }\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/DataTransfer.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"arm_neon_support.h\"\n\nvoid interleaveSlice(void* output,\n                     const float* input,\n                     size_t width,\n                     size_t height,\n                     size_t row_stride,\n                     uint16_t input_channels);\nvoid deInterleaveSlice(float* output,\n                       const void* input,\n                       size_t width,\n                       size_t height,\n                       size_t input_stride,\n                       uint32_t output_channels);\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GL.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n#include \"caffe2/core/common.h\"\n\n#if CAFFE2_IOS\n#include <OpenGLES/ES3/gl.h>\n#include <OpenGLES/ES3/glext.h>\n#elif CAFFE2_ANDROID\n#include <EGL/egl.h>\n#include <GLES2/gl2.h>\n#include \"caffe2/mobile/contrib/opengl/android/gl3stub.h\"\n#endif\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLContext.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"caffe2/core/logging.h\"\n\n#include \"GL.h\"\n#include \"GLContext.h\"\n#include \"GLLogging.h\"\n\n#include <sstream>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n\n#if CAFFE2_IOS\n#include \"sys/utsname.h\"\n#include <regex>\n#endif\n\nvoid getOpenGLESVersion(int& major, int& minor) {\n  glGetIntegerv(GL_MAJOR_VERSION, &major);\n  glGetIntegerv(GL_MINOR_VERSION, &minor);\n}\n\nbool checkOpenGLExtensions(std::string gl_ext_str) {\n  static std::unordered_set<std::string> extensions;\n  if (extensions.empty()) {\n    const caffe2::string extension_str((const char*)glGetString(GL_EXTENSIONS));\n    LOG(INFO) << \"GL_EXTENSIONS: \" << extension_str;\n\n    std::stringstream ss(extension_str);\n    while (!ss.eof()) {\n      std::string extension;\n      ss >> extension;\n      extensions.insert(extension);\n    }\n  }\n\n  return extensions.count(gl_ext_str) > 0;\n}\n\nbool GLContext::GL_EXT_texture_border_clamp_defined() {\n  static int major = 0, minor = 0;\n  if (major == 0) {\n    getOpenGLESVersion(major, minor);\n  }\n\n  if (major == 3 && minor == 2) {\n    return true;\n  }\n\n  return checkOpenGLExtensions(\"GL_EXT_texture_border_clamp\") || // Most common\n         checkOpenGLExtensions(\"GL_OES_texture_border_clamp\");\n}\n\nbool supportOpenGLES3(bool* half_float_supported) {\n  int major = 0, minor = 0;\n  getOpenGLESVersion(major, minor);\n\n  LOG(INFO) << \"GL_VERSION: OpenGL ES \" << major << \".\" << minor;\n\n  if (major < 3) {\n    LOG(ERROR) << \"OpenGL ES 3.0 not supported\";\n    return false;\n  }\n\n  if (!checkOpenGLExtensions(\"GL_EXT_color_buffer_half_float\")) {\n    LOG(ERROR) << \"GL_EXT_color_buffer_half_float is not available\";\n    if (half_float_supported) {\n      *half_float_supported = false;\n    }\n  }\n  return true;\n}\n\n#if CAFFE2_IOS\nint iPhoneVersion() {\n  static int version = 0;\n  static std::once_flag once;\n  std::call_once(once, [&]() {\n    struct utsname systemInfo;\n    uname(&systemInfo);\n    std::string iphone_ver_str = systemInfo.machine;\n    LOG(INFO) << systemInfo.machine;\n\n    if (iphone_ver_str.find(\"iPhone\") != std::string::npos) {\n      std::regex regStr(\"([0-9]+)\");\n      std::smatch matchs;\n      if (std::regex_search(iphone_ver_str, matchs, regStr)) {\n        version = stoi(matchs[0]);\n      }\n    }\n  });\n  return version;\n}\n#endif\n\n#if CAFFE2_ANDROID\n// whitelist of supported GPUs\nbool isSupportedRenderer() {\n  static std::unordered_set<std::string> supported_renderers = {\n      \"Adreno (TM) 540\",\n      \"Adreno (TM) 530\",\n      \"Adreno (TM) 510\",\n      \"Adreno (TM) 430\",\n      \"Adreno (TM) 418\",\n      \"Mali-G71\",\n      \"Mali-T880\",\n      \"NVIDIA Tegra\"};\n  std::string rendererStr((const char*)glGetString(GL_RENDERER));\n  LOG(INFO) << \"GL_RENDERER: \" << rendererStr;\n\n  int start = rendererStr.find_first_not_of(\" \");\n  int end = rendererStr.find_last_not_of(\" \");\n  rendererStr = rendererStr.substr(start, end - start + 1);\n  return supported_renderers.count(rendererStr) > 0;\n}\n#endif\n\nbool isSupportedDevice() {\n#if CAFFE2_IOS\n  return iPhoneVersion() >= 7; // iPhone 6 and up\n#elif CAFFE2_ANDROID\n  return isSupportedRenderer();\n#else\n  return false;\n#endif\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLContext.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n#include \"GLTexture.h\"\n#include \"caffe2/core/common.h\"\n#include <functional>\n\nclass GLContext {\n private:\n  static std::unique_ptr<GLContext> _glcontext;\n  std::function<const GLTexture*(const int width, const int height)> foreignTextureAllocator =\n      nullptr;\n\n protected:\n  bool half_float_supported = true;\n\n public:\n  virtual void set_context() = 0;\n  virtual void reset_context() = 0;\n  virtual void flush_context() = 0;\n  virtual ~GLContext(){};\n\n  static void initGLContext();\n  static GLContext* getGLContext();\n  static void deleteGLContext();\n\n  static bool GL_EXT_texture_border_clamp_defined();\n\n  inline bool halfFloatTextureSupported() { return half_float_supported; }\n\n  void setTextureAllocator(\n      std::function<const GLTexture*(const int width, const int height)> textureAllocator) {\n    foreignTextureAllocator = textureAllocator;\n  }\n\n  std::function<const GLTexture*(const int width, const int height)> getTextureAllocator() {\n    return foreignTextureAllocator;\n  }\n};\n\nbool supportOpenGLES3(bool* hfs = nullptr);\n\nbool isSupportedDevice();\n\n#if CAFFE2_IOS\nint iPhoneVersion();\n#endif\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLFilter.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"GLFilter.h\"\n#include <sstream>\n\nGLFilter::GLFilter(const std::string _kernel_name,\n                   const std::string _vertex_shader,\n                   const std::string _fragment_shader,\n                   const std::vector<binding*> uniforms,\n                   const std::vector<binding*> uniform_blocks,\n                   const std::vector<binding*> attributes,\n                   const replacements_t& _replacements)\n    : kernel_name(_kernel_name),\n      uniforms_(uniforms),\n      uniform_blocks_(uniform_blocks),\n      attributes_(attributes) {\n  // shader program\n  if (createProgram(_vertex_shader.c_str(),\n                    process_replacements(_fragment_shader, _replacements).c_str(),\n                    &program)) {\n    gl_log(GL_VERBOSE, \"created program %d\\n\", program);\n  } else {\n    releaseBuffers();\n\n    throwRuntimeError(\n        [&](std::stringstream& errmsg) { errmsg << \"Problem initializing OpenGL program\"; });\n  }\n}\n\nconst char* shader_utils = R\"GLSL(\n#define unpackHalf4x16(pd) vec4(unpackHalf2x16(pd.x), unpackHalf2x16(pd.y))\n#define packHalf4x16(pd) uvec2(packHalf2x16(pd.xy), packHalf2x16(pd.zw))\n)GLSL\";\n\nconst char* half_float_texture_utils = R\"GLSL(\nprecision mediump sampler2D;\n\n#define TEXTURE_OUTPUT(_loc, _var) \\\n        layout(location = _loc) out mediump vec4 _var\n#define TEXTURE_INPUT(_var) \\\n        uniform sampler2D _var\n#define TEXTURE_LOAD(_input, _coord) \\\n        texelFetch((_input), (_coord), 0)\n#define TEXTURE_STORE(_val) \\\n        (_val)\n)GLSL\";\n\nconst char* half_float_compat_texture_utils = R\"GLSL(\nprecision highp usampler2D;\n\n#define TEXTURE_OUTPUT(_loc, _var) \\\n        layout(location = _loc) out highp uvec2 _var\n#define TEXTURE_INPUT(_var) \\\n        uniform usampler2D _var\n#define TEXTURE_LOAD(_input, _coord) \\\n        unpackHalf4x16(texelFetch((_input), (_coord), 0).xy)\n#define TEXTURE_STORE(_val) \\\n        (uvec2(packHalf4x16((_val))))\n)GLSL\";\n\nstd::string GLFilter::process_replacements(std::string shader,\n                                           const replacements_t& replacements) const {\n  for (auto&& replacement : replacements) {\n    std::string tag = \"$(\" + replacement.first + \")\";\n    std::string value = replacement.second;\n\n    size_t position = shader.find(tag);\n    if (position != std::string::npos) {\n      shader.replace(position, tag.size(), value);\n    } else {\n      throwRuntimeError(\n          [&](std::stringstream& errmsg) { errmsg << \"Couldn't find replacement tag: \" << tag; });\n    }\n  }\n\n  // Add some #defines for convenience\n  std::string version_tag = \"#version 300 es\";\n  if (GLContext::getGLContext()->halfFloatTextureSupported()) {\n    shader.insert(shader.find(version_tag) + version_tag.size(), half_float_texture_utils);\n  } else {\n    shader.insert(shader.find(version_tag) + version_tag.size(), half_float_compat_texture_utils);\n  }\n  shader.insert(shader.find(version_tag) + version_tag.size(), shader_utils);\n  return shader;\n}\n\ntemplate <typename T>\nvoid GLFilter::attach_uniform_buffer(const binding* block,\n                                     GLuint bindingPoint,\n                                     std::function<void(T*, size_t)> loader) {\n  if (block->location >= 0) {\n    if (bindingPoint < kMaxUniformBlocks) {\n      if (uniformBlock[bindingPoint] == 0) {\n        // Associate the uniform block index with a binding point\n        glUniformBlockBinding(program, block->location, bindingPoint);\n\n        // Get the size of block\n        glGetActiveUniformBlockiv(program, block->location, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize[bindingPoint]);\n\n        // Create and fill a buffer object\n        glGenBuffers(1, &uniformBlock[bindingPoint]);\n\n        gl_log(GL_VERBOSE, \"created uniform buffer block %d\\n\", uniformBlock[bindingPoint]);\n      }\n\n      // Fill a buffer object\n      glBindBuffer(GL_UNIFORM_BUFFER, uniformBlock[bindingPoint]);\n      glBufferData(GL_UNIFORM_BUFFER, blockSize[bindingPoint], NULL, GL_DYNAMIC_DRAW);\n\n      checkGLError([&](std::stringstream& errmsg) {\n        errmsg << \"Unable to bind uniform buffer \" << block->name << \":\" << block->location\n               << \" at binding point \" << bindingPoint;\n      });\n\n      T* blockData = (T*)glMapBufferRange(\n          GL_UNIFORM_BUFFER, 0, blockSize[bindingPoint], GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);\n      if (blockData != NULL) {\n        // Copy the data into the mapped buffer\n        if (loader)\n          loader(blockData, blockSize[bindingPoint]);\n\n        // Unmap the buffer\n        if (glUnmapBuffer(GL_UNIFORM_BUFFER) == GL_TRUE) {\n          // Bind the buffer object to the uniform block binding point\n          glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, uniformBlock[bindingPoint]);\n        } else {\n          throwRuntimeError([&](std::stringstream& errmsg) { errmsg << \"Error unmapping element buffer object\"; });\n        }\n      } else {\n        throwRuntimeError([&](std::stringstream& errmsg) {\n          errmsg << \"Error mapping element buffer object, blockSize: \" << blockSize;\n        });\n      }\n\n      glBindBuffer(GL_UNIFORM_BUFFER, 0);\n    } else {\n      throwRuntimeError([&](std::stringstream& errmsg) {\n        errmsg << \"Uniform block binding point out of range: \" << bindingPoint << \", should be < \"\n               << kMaxUniformBlocks;\n      });\n    }\n  } else {\n    throwRuntimeError([&](std::stringstream& errmsg) { errmsg << \"unbound uniform block\"; });\n  }\n}\n\ntemplate void GLFilter::attach_uniform_buffer<float16_t>(const binding* block,\n                                                         GLuint bindingPoint,\n                                                         std::function<void(float16_t*, size_t)> loader);\n\nstatic const GLenum unused_capability[] = {GL_CULL_FACE,\n                                           GL_BLEND,\n                                           GL_DITHER,\n                                           GL_STENCIL_TEST,\n                                           GL_DEPTH_TEST,\n                                           GL_SCISSOR_TEST,\n                                           GL_POLYGON_OFFSET_FILL,\n                                           GL_SAMPLE_ALPHA_TO_COVERAGE,\n                                           GL_SAMPLE_COVERAGE};\n\nvoid GLFilter::run(const std::vector<texture_attachment>& input,\n                   const std::vector<const GLTexture*>& output,\n                   std::function<void(void)> uniforms_initializer,\n                   int width,\n                   int height) {\n  const int first_texture_id = GL_TEXTURE0;\n\n  GLint defaultFramebuffer = 0;\n  glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFramebuffer);\n\n  gl_log(GL_VERBOSE,\n         \"GLFilter::run %s - inputs: %d, outputs: %d, width: %d, height: %d\\n\",\n         kernel_name.c_str(),\n         input.size(),\n         output.size(),\n         width,\n         height);\n\n  if (output.size() > 4) {\n    throwRuntimeError([&](std::stringstream& errmsg) {\n      errmsg << \"Too many output textures: \" << output.size() << \", should be <= 4\";\n    });\n  }\n\n  if (frameBuffer == 0) {\n    // create the frame buffer\n    glGenFramebuffers(1, &frameBuffer);\n    gl_log(GL_VERBOSE, \"created frame buffer %d\\n\", frameBuffer);\n  }\n\n  glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);\n  checkGLError([&](std::stringstream& errmsg) { errmsg << \"glBindFramebuffer\"; });\n\n  // Set up the output textures\n  for (int i = 0; i < output.size(); i++) {\n    GLenum target = output[i]->target();\n    GLuint texture = output[i]->name();\n\n    glBindTexture(target, texture);\n    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, target, texture, 0);\n\n    checkGLError([&](std::stringstream& errmsg) {\n      errmsg << \"Unable to connect output texture \" << texture << \" at color attachment \" << i;\n    });\n\n    gl_log(GL_VERBOSE, \"connected output texture %d to color attachment %d\\n\", texture, i);\n  }\n\n  // Bind the output textures to the frame buffer attachments\n  if (!frame_buffer_initialized) {\n    const int attachments_number = output.size();\n    const GLenum attachments[4] = {\n        GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3};\n\n    glDrawBuffers(attachments_number, attachments);\n\n    int fbs = glCheckFramebufferStatus(GL_FRAMEBUFFER);\n\n    if (fbs != GL_FRAMEBUFFER_COMPLETE) {\n      throwRuntimeError(\n          [&](std::stringstream& errmsg) { errmsg << \"Frame buffer incomplete: \" << fbs; });\n    }\n\n    frame_buffer_initialized = true;\n  }\n\n  glUseProgram(program);\n  checkGLError([&](std::stringstream& errmsg) { errmsg << \"glUseProgram\"; });\n\n  // Set up the input textures\n  GLenum texture_idx = first_texture_id;\n  for (int i = 0; i < input.size(); i++, texture_idx++) {\n    if (input[i].uniform->location >= 0) {\n      GLenum target = input[i].texture->target();\n      GLuint texture = input[i].texture->name();\n\n      glActiveTexture(texture_idx);\n      glBindTexture(target, texture);\n      glUniform1i(input[i].uniform->location, texture_idx - GL_TEXTURE0);\n\n      checkGLError([&](std::stringstream& errmsg) {\n        errmsg << \": Unable to attach input texture \" << texture << \" to uniform \"\n               << input[i].uniform->name << \":\" << input[i].uniform->location << \" at index \"\n               << texture_idx - GL_TEXTURE0;\n      });\n\n      gl_log(GL_VERBOSE,\n             \"connected input texture %d to texture unit %d\\n\",\n             texture,\n             texture_idx - GL_TEXTURE0);\n    } else {\n      gl_log(GL_VERBOSE, \"something wrong happened when i = %d\\n\", i);\n    }\n  }\n\n  // Caller supplied uniforms initializer\n  if (uniforms_initializer) {\n    uniforms_initializer();\n\n    checkGLError([&](std::stringstream& errmsg) {\n      errmsg << \"errors in the uniforms initializer callback\";\n    });\n  }\n\n  // Validate program\n  if (check_opengl_errors && !validateProgram(program)) {\n    throwRuntimeError(\n        [&](std::stringstream& errmsg) { errmsg << \"Couldn't validate OpenGL program\"; });\n  }\n\n  glViewport(0, 0, width, height);\n\n  // Disable stuff we don't need and make sure that we have all the channels ebabled\n  for (int i = 0; i < sizeof(unused_capability) / sizeof(GLenum); i++) {\n    glDisable(unused_capability[i]);\n  }\n  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);\n\n  // glDrawElements should be more efficient, but on iOS glDrawArrays is faster.\n\n  const bool useDrawArrays = true;\n\n  if (useDrawArrays) {\n    enum { ATTRIB_VERTEX, ATTRIB_TEXTUREPOSITON, NUM_ATTRIBUTES };\n\n    static const GLfloat squareVertices[] = {\n        -1.0f,\n        -1.0f, // bottom left\n        1.0f,\n        -1.0f, // bottom right\n        -1.0f,\n        1.0f, // top left\n        1.0f,\n        1.0f, // top right\n    };\n\n    static const float textureVertices[] = {\n        0.0f,\n        0.0f, // bottom left\n        1.0f,\n        0.0f, // bottom right\n        0.0f,\n        1.0f, // top left\n        1.0f,\n        1.0f, // top right\n    };\n\n    glBindBuffer(GL_ARRAY_BUFFER, 0);\n    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices);\n    glEnableVertexAttribArray(ATTRIB_VERTEX);\n    checkGLError(\n        [&](std::stringstream& errmsg) { errmsg << \"glEnableVertexAttribArray(ATTRIB_VERTEX)\"; });\n\n    glVertexAttribPointer(ATTRIB_TEXTUREPOSITON, 2, GL_FLOAT, 0, 0, textureVertices);\n    glEnableVertexAttribArray(ATTRIB_TEXTUREPOSITON);\n    checkGLError([&](std::stringstream& errmsg) {\n      errmsg << \"glEnableVertexAttribArray(ATTRIB_TEXTUREPOSITON)\";\n    });\n\n    gl_log(GL_VERBOSE, \"Calling glDrawArrays\\n\");\n    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n\n    checkGLError([&](std::stringstream& errmsg) { errmsg << \"glDrawArrays\"; });\n  } else {\n    // Run the shaders on the output geometry\n    static const GLfloat vVertices[] = {\n        -1.0f, -1.0f, 0.0f, // Position 0\n        0.0f,  0.0f, // TexCoord 0\n        -1.0f, 1.0f,  0.0f, // Position 1\n        0.0f,  1.0f, // TexCoord 1\n        1.0f,  1.0f,  0.0f, // Position 2\n        1.0f,  1.0f, // TexCoord 2\n        1.0f,  -1.0f, 0.0f, // Position 3\n        1.0f,  0.0f // TexCoord 3\n    };\n    static const GLushort indices[] = {0, 1, 2, 0, 2, 3};\n\n    // Load the vertex position\n    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), vVertices);\n    // Load the texture coordinate\n    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &vVertices[3]);\n\n    glEnableVertexAttribArray(0);\n    glEnableVertexAttribArray(1);\n\n    gl_log(GL_VERBOSE, \"Calling glDrawElements\\n\");\n    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);\n\n    checkGLError([&](std::stringstream& errmsg) { errmsg << \"glDrawElements\"; });\n  }\n\n#if CAFFE2_ANDROID\n  glFlush();\n#endif\n\n  // Unbind the current texture - Man, this is expensive!\n  for (int i = texture_idx - 1; i >= first_texture_id; i--) {\n    gl_log(GL_VERBOSE, \"unbinding texture unit %d\\n\", i - GL_TEXTURE0);\n    glActiveTexture(i);\n    glBindTexture(GL_TEXTURE_2D, 0);\n\n    checkGLError([&](std::stringstream& errmsg) {\n      errmsg << \"Error unbinding texture unit \" << i - GL_TEXTURE0;\n    });\n  }\n\n  glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);\n}\n\nvoid GLFilter::releaseBuffers() {\n  for (int i = 0; i < kMaxUniformBlocks; i++) {\n    if (uniformBlock[i]) {\n      gl_log(GL_VERBOSE, \"deleting uniform buffer block %d\\n\", uniformBlock[i]);\n      glDeleteBuffers(1, &uniformBlock[i]);\n      uniformBlock[i] = 0;\n    }\n  }\n  if (frameBuffer) {\n    gl_log(GL_VERBOSE, \"deleting frame buffer %d\\n\", frameBuffer);\n    glDeleteFramebuffers(1, &frameBuffer);\n    frameBuffer = 0;\n  }\n}\n\nvoid GLFilter::deleteProgram() {\n  if (program) {\n    gl_log(GL_VERBOSE, \"deleting program %d\\n\", program);\n    glDeleteProgram(program);\n    program = 0;\n  }\n}\n\nvoid GLFilter::deleteBindings() {\n  for (binding* uniform : uniforms_) {\n    delete uniform;\n  }\n  for (binding* uniform_block : uniform_blocks_) {\n    delete uniform_block;\n  }\n  for (binding* attribute : attributes_) {\n    delete attribute;\n  }\n}\n\n// Simple vertex shader setting up the coordinates system\nconst char* GLFilter::vertex_shader = R\"GLSL(#version 300 es\n\n  layout(location = 0) in vec4 a_position;\n  layout(location = 1) in vec2 a_texCoord;\n  out vec2 v_texCoord;\n\n  void main()\n  {\n     gl_Position = a_position;\n     v_texCoord = a_texCoord;\n  }\n)GLSL\";\n\nbool GLFilter::createProgram(const GLchar* vertSource,\n                             const GLchar* fragSource,\n                             GLuint* program) const {\n  GLuint vertShader = 0, fragShader = 0, prog = 0, status = 1;\n\n  // Clear the error state. We check error state later in the function and\n  // want to capture only errors in filter program initialization.\n  glGetError();\n\n  // Create shader program\n  prog = glCreateProgram();\n\n  // Create and compile vertex shader\n  status *= compileShader(GL_VERTEX_SHADER, 1, &vertSource, &vertShader);\n\n  // Create and compile fragment shader\n  status *= compileShader(GL_FRAGMENT_SHADER, 1, &fragSource, &fragShader);\n\n  // Attach vertex shader to program\n  glAttachShader(prog, vertShader);\n\n  // Attach fragment shader to program\n  glAttachShader(prog, fragShader);\n\n  // Bind attribute locations\n  // This needs to be done prior to linking\n  for (auto&& attribute : attributes_) {\n    glBindAttribLocation(prog, attribute->location, attribute->name.c_str());\n\n    checkGLError([&](std::stringstream& errmsg) {\n      errmsg << \"Couldn't bind attribute: \" << attribute->name << \" at location \"\n             << attribute->location;\n    });\n  }\n\n  // Link program\n  status *= linkProgram(prog);\n\n  // Get locations of uniforms\n  if (status) {\n    for (auto&& uniform : uniforms_) {\n      uniform->location = glGetUniformLocation(prog, uniform->name.c_str());\n\n      checkGLError([&](std::stringstream& errmsg) {\n        errmsg << \"Couldn't resolve uniform: \" << uniform->name;\n      });\n    }\n\n    for (auto&& uniform_block : uniform_blocks_) {\n      uniform_block->location = glGetUniformBlockIndex(prog, uniform_block->name.c_str());\n      gl_log(GL_VERBOSE,\n             \"Getting location for uniform block: %s, location: %d\\n\",\n             uniform_block->name.c_str(),\n             uniform_block->location);\n\n      checkGLError([&](std::stringstream& errmsg) {\n        errmsg << \"Couldn't resolve uniform block: \" << uniform_block->name;\n      });\n    }\n\n    *program = prog;\n  }\n\n  // Release vertex and fragment shaders\n  if (vertShader) {\n    glDetachShader(prog, vertShader);\n    glDeleteShader(vertShader);\n  }\n  if (fragShader) {\n    glDetachShader(prog, fragShader);\n    glDeleteShader(fragShader);\n  }\n\n  return status == 1;\n}\n\n#include <stdlib.h>\n\n/* Compile a shader from the provided source(s) */\nGLint GLFilter::compileShader(GLenum target,\n                              GLsizei count,\n                              const GLchar** sources,\n                              GLuint* shader) const {\n  GLint status = 1;\n\n  *shader = glCreateShader(target);\n  glShaderSource(*shader, count, sources, NULL);\n  glCompileShader(*shader);\n\n  GLint logLength = 0;\n  glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);\n  if (logLength > 0) {\n    std::vector<GLchar> log(logLength);\n    glGetShaderInfoLog(*shader, logLength, &logLength, &log[0]);\n    gl_log(GL_ERR, \"Shader compile log:\\n%s\", &log[0]);\n  }\n\n  glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);\n  if (status == 0) {\n    int i;\n\n    gl_log(GL_ERR, \"Failed to compile shader:\\n\");\n    for (i = 0; i < count; i++)\n      gl_log(GL_ERR, \"%s\", sources[i]);\n  }\n\n  return status;\n}\n\n/* Link a program with all currently attached shaders */\nGLint GLFilter::linkProgram(GLuint program) const {\n  GLint status = 1;\n\n  glLinkProgram(program);\n\n  GLint logLength = 0;\n  glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);\n  if (logLength > 0) {\n    std::vector<GLchar> log(logLength);\n    glGetProgramInfoLog(program, logLength, &logLength, &log[0]);\n    gl_log(GL_ERR, \"Program link log:\\n%s\", &log[0]);\n  }\n\n  glGetProgramiv(program, GL_LINK_STATUS, &status);\n  if (status == 0)\n    gl_log(GL_ERR, \"Failed to link program %d\\n\", program);\n\n  return status;\n}\n\n/* Validate a program (for i.e. inconsistent samplers) */\nGLint GLFilter::validateProgram(GLuint program) const {\n  GLint status = 1;\n\n  glValidateProgram(program);\n\n  GLint logLength = 0;\n  glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);\n  if (logLength > 0) {\n    std::vector<GLchar> log(logLength);\n    glGetProgramInfoLog(program, logLength, &logLength, &log[0]);\n    gl_log(GL_ERR, \"Program validate log:\\n%s\", &log[0]);\n  }\n\n  glGetProgramiv(program, GL_VALIDATE_STATUS, &status);\n  if (status == 0)\n    gl_log(GL_ERR, \"Failed to validate program %d\\n\", program);\n\n  return status;\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLFilter.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"GLContext.h\"\n#include \"GLTexture.h\"\n#include \"arm_neon_support.h\"\n\n#include <functional>\n#include <string>\n#include <vector>\n\n#define BINDING(variableName) (variableName = new binding{#variableName})\n#define ATTRIBUTE(variableName, value) (variableName = new binding{#variableName, value})\n\nclass GLFilter {\n protected:\n  const std::string kernel_name;\n  GLuint program = 0;\n  GLuint frameBuffer = 0;\n  static constexpr int kMaxUniformBlocks = 12;\n  GLuint uniformBlock[kMaxUniformBlocks] = {0};\n  GLint blockSize[kMaxUniformBlocks]     = {0};\n  bool frame_buffer_initialized = false;\n\n  // glGetError() can be expensive, we should turn error checking off when we're done with debugging\n\n  static constexpr bool check_opengl_errors = true;\n\npublic:\n  typedef std::vector<std::pair<std::string, std::string>> replacements_t;\n\n  struct binding {\n    const std::string name;\n    GLint location;\n  };\n\n  struct texture_attachment {\n    const GLTexture* texture;\n    const binding* uniform;\n  };\n\n  GLFilter(const std::string kernel_name,\n           const std::string vertex_shader,\n           const std::string fragment_shader,\n           const std::vector<binding*> uniforms,\n           const std::vector<binding*> uniform_blocks = {},\n           const std::vector<binding*> attributes = {},\n           const replacements_t& replacements = {});\n\n  // TODO: The set and reset context need to be commented out for unit testing\n  ~GLFilter() {\n    releaseBuffers();\n    deleteProgram();\n    deleteBindings();\n  }\n\n  void throwRuntimeError(std::function<void(std::stringstream& errmsg)> error_formatter) const {\n    std::stringstream errmsg;\n    errmsg << kernel_name << \": \";\n    error_formatter(errmsg);\n    throw std::runtime_error(errmsg.str());\n  }\n\n  void checkGLError(std::function<void(std::stringstream& errmsg)> error_formatter) const {\n    if (check_opengl_errors) {\n      GLenum glError = glGetError();\n      if (glError != GL_NO_ERROR) {\n        throwRuntimeError([&](std::stringstream& errmsg) {\n          error_formatter(errmsg);\n          errmsg << \", \" << glError;\n        });\n      }\n    }\n  }\n\n  template <typename T>\n  void attach_uniform_buffer(const binding* block,\n                             GLuint bindingPoint, std::function<void(T*, size_t)> loader);\n\n  void run(const std::vector<texture_attachment>& input,\n           const std::vector<const GLTexture*>& output,\n           std::function<void(void)> uniforms_initializer,\n           int width,\n           int height);\n\n  void releaseBuffers();\n  void deleteProgram();\n  void deleteBindings();\n\n  static const char* vertex_shader;\n\n private:\n  const std::vector<binding*> uniforms_;\n  const std::vector<binding*> uniform_blocks_;\n  const std::vector<binding*> attributes_;\n\n  std::string process_replacements(std::string source, const replacements_t& replacements) const;\n\n  bool createProgram(const GLchar* vertSource, const GLchar* fragSource, GLuint* program) const;\n\n  GLint compileShader(GLenum target, GLsizei count, const GLchar** sources, GLuint* shader) const;\n  GLint linkProgram(GLuint program) const;\n  GLint validateProgram(GLuint program) const;\n};\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLImage.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"GLImage.h\"\n#include \"arm_neon_support.h\"\n#include \"caffe2/core/typeid.h\"\n\nnamespace caffe2 {\nCAFFE_KNOWN_TYPE(GLImage<float>);\nCAFFE_KNOWN_TYPE(GLImage<uint8_t>);\nCAFFE_KNOWN_TYPE(GLImageVector<float>);\nCAFFE_KNOWN_TYPE(GLImageVector<uint8_t>);\n#ifdef __ARM_NEON__\nCAFFE_KNOWN_TYPE(GLImage<float16_t>);\nCAFFE_KNOWN_TYPE(GLImageVector<float16_t>);\n#endif\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLImage.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"GLTexture.h\"\n#include \"caffe2/core/logging.h\"\n\n#include <functional>\n#include <vector>\n\ntemplate <typename T>\nclass GLImage {\n public:\n  const int width;\n  const int height;\n  const int channels;\n  const int data_size;\n\n  const int tile_x;\n  const int tile_y;\n  const int texture_width;\n  const int texture_height;\n  const int slices;\n\n  const std::vector<const GLTexture*> textures;\n\n  constexpr static int slice_channels = 4;\n\n  static constexpr int channels_to_slices(int channels, int tile_x, int tile_y) {\n    return ((channels + slice_channels - 1) / slice_channels + tile_x * tile_y - 1) /\n           (tile_x * tile_y);\n  }\n\n  static const std::vector<const GLTexture*> allocate_textures(\n      int slices, std::function<const GLTexture*(int slice)> texture_loader) {\n    std::vector<const GLTexture*> textures;\n    for (int i = 0; i < slices; i++) {\n      textures.push_back(texture_loader(i));\n    }\n    return textures;\n  }\n\n  GLImage(int _width,\n          int _height,\n          int _channels,\n          int _tile_x,\n          int _tile_y,\n          std::function<const GLTexture*(int slice)> texture_loader)\n      : width(_width),\n        height(_height),\n        channels(_channels),\n        data_size(sizeof(T)),\n        tile_x(_tile_x),\n        tile_y(_tile_y),\n        texture_width(_width * _tile_x),\n        texture_height(_height * _tile_y),\n        slices(channels_to_slices(_channels, _tile_x, _tile_y)),\n        textures(allocate_textures(slices, texture_loader)) {\n    CAFFE_ENFORCE_EQ(\n        slices, ((channels + 3) / 4 + tile_x * tile_y - 1) / (tile_x * tile_y));\n  }\n\n  GLImage(int _width,\n          int _height,\n          int _channels,\n          int _tile_x,\n          int _tile_y,\n          bool _destroy,\n          std::function<const GLTexture*(int slice)> texture_loader)\n      : width(_width),\n        height(_height),\n        channels(_channels),\n        data_size(sizeof(T)),\n        tile_x(_tile_x),\n        tile_y(_tile_y),\n        texture_width(_width * _tile_x),\n        texture_height(_height * _tile_y),\n        slices(channels_to_slices(_channels, _tile_x, _tile_y)),\n        textures(allocate_textures(slices, texture_loader)) {\n    CAFFE_ENFORCE_EQ(slices * tile_x * tile_y, (channels + 3) / 4);\n  }\n\n  GLImage()\n      : width(0),\n        height(0),\n        channels(0),\n        data_size(sizeof(T)),\n        tile_x(0),\n        tile_y(0),\n        texture_width(0),\n        texture_height(0),\n        slices(0){};\n\n  virtual ~GLImage() {\n    gl_log(GL_VERBOSE, \"deleting GLImage\\n\");\n    for (auto&& texture : textures) {\n      delete texture;\n    }\n  }\n};\n\ntemplate <typename T>\nclass GLImageVector {\n private:\n  std::vector<GLImage<T>*> images_;\n  int num_images_ = 0;\n  int width_ = 0;\n  int height_ = 0;\n  int channels_ = 0;\n  int tile_x_ = 0;\n  int tile_y_ = 0;\n\n public:\n  GLImage<T>* operator[](int index) const {\n    CAFFE_ENFORCE_LT(index, num_images_, \"Out of bounds when accessing GLImageVector\");\n    return images_[index];\n  }\n\n  void push_back(GLImage<T>* image) {\n    CAFFE_ENFORCE_EQ(image->channels, channels_);\n    CAFFE_ENFORCE_EQ(image->width, width_);\n    CAFFE_ENFORCE_EQ(image->height, height_);\n    CAFFE_ENFORCE_EQ(image->tile_x, tile_x_);\n    CAFFE_ENFORCE_EQ(image->tile_y, tile_y_);\n    images_.push_back(image);\n    CAFFE_ENFORCE_LE(images_.size(), num_images_);\n  }\n\n  int size() const { return images_.size(); }\n  int channels() const { return channels_; }\n  int width() const { return width_; }\n  int height() const { return height_; }\n  int tile_x() const { return tile_x_; }\n  int tile_y() const { return tile_y_; }\n  int slices() const { return size() > 0 ? images_[0]->slices : 0; }\n\n  GLImageVector(int num_images, int width, int height, int channels, int tile_x = 1, int tile_y = 1)\n      : num_images_(num_images),\n        width_(width),\n        height_(height),\n        channels_(channels),\n        tile_x_(tile_x),\n        tile_y_(tile_y) {}\n\n  GLImageVector() {}\n\n  ~GLImageVector() {\n    for (int i = 0; i < images_.size(); i++) {\n      delete images_[i];\n    }\n  }\n};\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLImageAllocator.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"GLImageAllocator.h\"\n#include \"arm_neon_support.h\"\n\ntemplate <class T>\nGLImageVector<T>* GLImageAllocator<T>::newImage(\n    int num_images, int width, int height, int channels, int tile_x, int tile_y, bool is_output) {\n  GLImageVector<T>* images =\n      new GLImageVector<T>(num_images, width, height, channels, tile_x, tile_y);\n  for (int i = 0; i < num_images; i++) {\n    images->push_back(\n        new GLImage<T>(width, height, channels, tile_x, tile_y, [&](int slice) -> const GLTexture* {\n          bool usePadding = is_output;\n          return new GLPlainTexture(type, nullptr, width * tile_x, height * tile_y, usePadding);\n        }));\n  }\n  return images;\n}\n\ntemplate <class T>\nGLImageVector<T>* GLImageAllocator<T>::newImage(\n    int num_images,\n    int width,\n    int height,\n    int channels,\n    int tile_x,\n    int tile_y,\n    std::function<const GLTexture*(const int width, const int height)> textureAllocator) {\n  GLImageVector<T>* images =\n      new GLImageVector<T>(num_images, width, height, channels, tile_x, tile_y);\n  for (int i = 0; i < num_images; i++) {\n    images->push_back(\n        new GLImage<T>(width, height, channels, tile_x, tile_y, [&](int slice) -> const GLTexture* {\n          return textureAllocator(width, height);\n        }));\n  }\n  return images;\n}\n\ntemplate <class T>\nGLImageVector<T>* GLImageAllocator<T>::ShareTexture(const GLuint textureID,\n                                                    int num_images,\n                                                    int width,\n                                                    int height,\n                                                    int channels,\n                                                    int tile_x,\n                                                    int tile_y) {\n  GLImageVector<T>* images =\n      new GLImageVector<T>(num_images, width, height, channels, tile_x, tile_y);\n  for (int i = 0; i < num_images; i++) {\n    images->push_back(\n        new GLImage<T>(width, height, channels, tile_x, tile_y, [&](int slice) -> const GLTexture* {\n          return new GLPlainTexture(\n              GLImageAllocator<T>::type, textureID, width * tile_x, height * tile_y);\n        }));\n  }\n  return images;\n}\n\ntemplate <>\nconst GLTexture::Type& GLImageAllocator<float16_t>::type = GLTexture::FP16;\ntemplate <>\nconst GLTexture::Type& GLImageAllocator<uint8_t>::type = GLTexture::UI8;\n\ntemplate class GLImageAllocator<float16_t>;\ntemplate class GLImageAllocator<uint8_t>;\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLImageAllocator.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"GLImage.h\"\n#include \"GLPlainTexture.h\"\n\ntemplate <class T>\nclass GLImageAllocator {\n public:\n  static const GLTexture::Type& type;\n\n  GLImageAllocator() { gl_log(GL_VERBOSE, \"%s\\n\", __PRETTY_FUNCTION__); }\n\n  virtual ~GLImageAllocator() { gl_log(GL_VERBOSE, \"%s\\n\", __PRETTY_FUNCTION__); }\n\n  virtual GLImageVector<T>* newImage(\n      int num_images, int width, int height, int channels, int tile_x, int tile_y, bool is_output);\n\n  virtual GLImageVector<T>* newImage(\n      int num_images,\n      int width,\n      int height,\n      int channels,\n      int tile_x,\n      int tile_y,\n      std::function<const GLTexture*(const int width, const int height)> textureAllocator);\n\n  virtual GLImageVector<T>* ShareTexture(const GLuint textureID,\n                                         int num_images,\n                                         int width,\n                                         int height,\n                                         int channels,\n                                         int tile_x = 1,\n                                         int tile_y = 1);\n\n  static GLImageAllocator<T>* newGLImageAllocator();\n};\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLLogging.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include <stdarg.h>\n#include <stdio.h>\n\nenum { GL_ERR = -1, GL_LOG = 0, GL_VERBOSE = 1 };\n\nstatic constexpr int GL_LOG_LEVEL = GL_LOG;\n\nstatic inline int gl_log(int level, const char* format, ...) {\n  int r = 0;\n  if (level <= GL_LOG_LEVEL) {\n    va_list args;\n    va_start(args, format);\n    r = vfprintf(stderr, format, args);\n    va_end(args);\n  }\n  return r;\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLPBO.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"GLPBO.h\"\n\n#include \"caffe2/core/logging.h\"\n\nGLPBO::~GLPBO() {\n  if (pboId != 0) {\n    gl_log(GL_LOG, \"deleting PBO buffer %d\\n\", pboId);\n    glDeleteBuffers(1, &pboId);\n    pboId = 0;\n  }\n  if (pboFrameBuffer != 0) {\n    gl_log(GL_LOG, \"deleting PBO frame buffer %d\\n\", pboFrameBuffer);\n    glDeleteFramebuffers(1, &pboFrameBuffer);\n    pboFrameBuffer = 0;\n  }\n}\n\nGLPBO* GLPBO::pboContext = NULL;\n\nGLPBO* GLPBO::getContext() {\n  if (pboContext == NULL) {\n    pboContext = new GLPBO();\n  }\n  return pboContext;\n}\n\nvoid GLPBO::mapTextureData(GLuint _textureId,\n                           GLsizei _width,\n                           GLsizei _height,\n                           GLsizei _stride,\n                           GLsizei _channels,\n                           const GLTexture::Type& _type,\n                           std::function<void(const void* buffer,\n                                              size_t width,\n                                              size_t height,\n                                              size_t stride,\n                                              size_t channels,\n                                              const GLTexture::Type& type)> process) {\n  GLint defaultFramebuffer = 0;\n  glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFramebuffer);\n\n  if (pboFrameBuffer == 0) {\n    glGenFramebuffers(1, &pboFrameBuffer);\n    gl_log(GL_VERBOSE, \"created PBO frame buffer %d\\n\", pboFrameBuffer);\n  }\n\n  glBindFramebuffer(GL_FRAMEBUFFER, pboFrameBuffer);\n\n  glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _textureId, 0);\n\n  int fbs = glCheckFramebufferStatus(GL_FRAMEBUFFER);\n  if (fbs != GL_FRAMEBUFFER_COMPLETE) {\n    std::stringstream errmsg;\n    errmsg << \": Frame buffer incomplete: \" << fbs;\n    throw std::runtime_error(errmsg.str());\n  }\n\n  if (pboId == 0) {\n    glGenBuffers(1, &pboId);\n    gl_log(GL_VERBOSE, \"created PBO buffer %d\\n\", pboId);\n  }\n  glBindBuffer(GL_PIXEL_PACK_BUFFER, pboId);\n\n  size_t buffer_size = _stride * _height * _channels * _type.dataSize();\n\n  if (buffer_size > pboSize) {\n    LOG(INFO) << \"Allocating PBO of capacity \" << buffer_size;\n\n    glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, NULL, GL_DYNAMIC_READ);\n    pboSize = buffer_size;\n  }\n\n  glReadBuffer(GL_COLOR_ATTACHMENT0);\n  glReadPixels(0, 0, _stride, _height, _type.format, _type.type, 0);\n\n  GLhalf* ptr = (GLhalf*)glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, buffer_size, GL_MAP_READ_BIT);\n\n  if (ptr) {\n    process(ptr, _width, _height, _stride, _channels, _type);\n  } else {\n    std::stringstream errmsg;\n    errmsg << \": glMapBufferRange using PBO incomplete\";\n    throw std::runtime_error(errmsg.str());\n  }\n\n  // Unmap buffer\n  glUnmapBuffer(GL_PIXEL_PACK_BUFFER);\n  glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);\n\n  // Bind to the default FrameBuffer\n  glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLPBO.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"GLTexture.h\"\n#include <functional>\n\nclass GLPBO {\n  GLuint pboId = 0;\n  GLuint pboSize = 0;\n  GLuint pboFrameBuffer = 0;\n\n  ~GLPBO();\n\n  static GLPBO* pboContext;\n\n public:\n  void mapTextureData(GLuint _textureId,\n                      GLsizei _width,\n                      GLsizei _height,\n                      GLsizei _stride,\n                      GLsizei _channels,\n                      const GLTexture::Type& type,\n                      std::function<void(const void* buffer,\n                                         size_t width,\n                                         size_t height,\n                                         size_t stride,\n                                         size_t channels,\n                                         const GLTexture::Type& type)> process);\n\n  static GLPBO* getContext();\n};\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLPlainTexture.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"GLPlainTexture.h\"\n#include \"GLPBO.h\"\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/timer.h\"\n\n#define half_float_supported (GLContext::getGLContext()->halfFloatTextureSupported())\n\n#define FIXED_TYPE(_t) (((_t).type != GL_HALF_FLOAT || half_float_supported) ? (_t) : GLTexture::FP16_COMPAT)\n\nGLPlainTexture::GLPlainTexture(\n    const Type& type, const void* input, GLsizei width, GLsizei height, bool use_padding, GLint filter, GLint wrap)\n    : GLTexture(FIXED_TYPE(type), width, height, use_padding, filter, wrap) {\n  //  caffe2::Timer timer;\n  //  timer.Start();\n  glGenTextures(1, &_textureId);\n  glBindTexture(GL_TEXTURE_2D, _textureId);\n  glTexImage2D(GL_TEXTURE_2D, 0, _type.internalFormat, _stride, _height, 0, _type.format, _type.type, input);\n\n  gl_log(\n      GL_VERBOSE,\n      \"GLPlainTexture() - allocated textureId %d, internalFormat: 0x%X, format: 0x%X, type: 0x%X\\n\",\n      _textureId,\n      _type.internalFormat,\n      _type.format,\n      _type.type);\n\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _filter);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _filter);\n\n#if GL_EXT_texture_border_clamp\n  GLfloat borderColor[] = {0.0f, 0.0f, 0.0f, 0.0f};\n  glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR_EXT, borderColor);\n  // Set the texture to use the border clamp wrapping mode.\n  _wrap = GL_CLAMP_TO_BORDER_EXT;\n#endif\n\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _wrap);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _wrap);\n\n  glBindTexture(GL_TEXTURE_2D, 0);\n  //  LOG(INFO) << \"glTexImage2D takes \" << timer.MilliSeconds() << \" ms\";\n}\n\nGLPlainTexture::GLPlainTexture(\n    const Type& type, const GLuint textureID, GLsizei width, GLsizei height, bool use_padding, GLint filter, GLint wrap)\n    : GLTexture(FIXED_TYPE(type), width, height, use_padding, filter, wrap) {\n  _textureId = textureID;\n  isOwner = false;\n  gl_log(\n      GL_VERBOSE,\n      \"GLPlainTexture() - wrapped textureId %d, internalFormat: 0x%X, format: 0x%X, type: 0x%X\\n\",\n      _textureId,\n      _type.internalFormat,\n      _type.format,\n      _type.type);\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLPlainTexture.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"GLContext.h\"\n#include \"GLTexture.h\"\n\nclass GLPlainTexture : public GLTexture {\n private:\n  bool isOwner = true;\n\n public:\n  GLPlainTexture(const Type& type,\n                 const void* input,\n                 GLsizei width,\n                 GLsizei height,\n                 bool use_padding = false,\n                 GLint filter = GL_NEAREST,\n                 GLint wrap = GL_CLAMP_TO_EDGE);\n\n  GLPlainTexture(const Type& type,\n                 const GLuint textureID,\n                 GLsizei width,\n                 GLsizei height,\n                 bool use_padding = false,\n                 GLint filter = GL_NEAREST,\n                 GLint wrap = GL_CLAMP_TO_EDGE);\n\n  ~GLPlainTexture() {\n    if (glIsTexture(_textureId)) {\n      if (isOwner) {\n        gl_log(GL_VERBOSE, \"~GLPlainTexture() - deleting texture %d\\n\", _textureId);\n        glDeleteTextures(1, &_textureId);\n      }\n    } else {\n      gl_log(GL_ERR, \"not deleting texture %d\\n\", _textureId);\n    }\n  }\n\n  GLuint name() const { return _textureId; };\n\n  GLenum target() const { return GL_TEXTURE_2D; };\n\n  bool flipped() const { return false; };\n};\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLPredictor.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"GLPredictor.h\"\n#include \"GLContext.h\"\n#include \"rewrite_net.h\"\n#include <vector>\n\nnamespace caffe2 {\n\ntemplate <class T>\nvoid shareInputGLImage(Workspace* ws, const std::string& name, GLImageVector<T>* input) {\n  auto* blob = ws->GetBlob(name);\n  CAFFE_ENFORCE(blob, \"Blob: \", name, \" does not exist\");\n  blob->ShareExternal<GLImageVector<T>>(input);\n}\n\ntemplate <class T>\nconst GLImageVector<T>* extractOutputGLImage(Workspace* ws, const std::string& name) {\n  auto* blob = ws->GetBlob(name);\n  CAFFE_ENFORCE(blob, \"Blob: \", name, \" does not exist\");\n  return &blob->template Get<GLImageVector<T>>();\n}\n\nconst NetDef create_gl_run_net(const NetDef& init_net,\n                               const NetDef& run_net,\n                               bool use_texture_input) {\n  NetDef gl_run_net;\n  if (!tryConvertToOpenGL(init_net, run_net, &gl_run_net, use_texture_input)) {\n    CAFFE_THROW(\"Failed to convert model to OpenGL\");\n  }\n  return gl_run_net;\n}\n\nGLPredictor::GLPredictor(const NetDef& init_net,\n                         const NetDef& run_net,\n                         bool use_texture_input,\n                         Workspace* parent)\n    : Predictor(init_net, create_gl_run_net(init_net, run_net, use_texture_input), parent) {}\n\nGLPredictor::~GLPredictor() {}\n\ntemplate <class T>\nbool GLPredictor::run(std::vector<GLImageVector<T>*>& inputs,\n                      std::vector<const GLImageVector<T>*>* outputs) {\n  const NetDef& run_net_ = Predictor::def();\n  CAFFE_ENFORCE(inputs.size() <= run_net_.external_input_size());\n  for (auto i = 0; i < inputs.size(); ++i) {\n    shareInputGLImage<T>(Predictor::ws(), run_net_.external_input(i), inputs[i]);\n  }\n\n  if (!Predictor::ws()->RunNet(run_net_.name())) {\n    return false;\n  }\n\n  for (auto i = 0; i < run_net_.external_output_size(); ++i) {\n    outputs->push_back(extractOutputGLImage<T>(Predictor::ws(), run_net_.external_output(i)));\n  }\n\n  return true;\n}\n\ntemplate bool GLPredictor::run(std::vector<GLImageVector<uint8_t>*>& inputs,\n                               std::vector<const GLImageVector<uint8_t>*>* outputs);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLPredictor.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"GLImage.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/predictor.h\"\n\nnamespace caffe2 {\nclass GLPredictor : public Predictor {\n public:\n  GLPredictor(const NetDef& init_net,\n              const NetDef& run_net,\n              bool use_texture_input = false,\n              Workspace* parent = nullptr);\n\n  template <class T>\n  bool run(std::vector<GLImageVector<T>*>& inputs, std::vector<const GLImageVector<T>*>* outputs);\n\n  ~GLPredictor();\n};\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLTexture.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"GLTexture.h\"\n#include \"DataTransfer.h\"\n#include \"GLPBO.h\"\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/timer.h\"\n\n#if CAFFE2_ANDROID && defined(__ARM_NEON__)\n\n#include \"../android/AndroidGLContext.h\"\n\n// https://community.arm.com/thread/10002\nvoid arm_memcpy(volatile unsigned char* dst, volatile unsigned char* src, int sz) {\n  if (sz & 63) {\n    sz = (sz & -64) + 64;\n  }\n\n  asm volatile(\n      \"NEONCopyPLD: \\n\"\n      \" VLDM %[src]!,{d0-d7} \\n\"\n      \" VSTM %[dst]!,{d0-d7} \\n\"\n      \" SUBS %[sz],%[sz],#0x40 \\n\"\n      \" BGT NEONCopyPLD \\n\"\n      : [dst] \"+r\"(dst), [src] \"+r\"(src), [sz] \"+r\"(sz)\n      :\n      : \"d0\", \"d1\", \"d2\", \"d3\", \"d4\", \"d5\", \"d6\", \"d7\", \"cc\", \"memory\");\n}\n#endif\n\nconst GLTexture::Type GLTexture::FP16 = {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT};\nconst GLTexture::Type GLTexture::UI8 = {GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE};\nconst GLTexture::Type GLTexture::FP16_COMPAT = {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT};\n\nvoid GLTexture::map_read(std::function<void(const void* buffer,\n                                            size_t width,\n                                            size_t height,\n                                            size_t stride,\n                                            size_t channels,\n                                            const Type& type)> process) const {\n  GLPBO* pbo = GLPBO::getContext();\n  pbo->mapTextureData(_textureId, _width, _height, _stride, _channels, _type, process);\n}\n\nvoid GLTexture::map_load(std::function<void(void* buffer,\n                                            size_t width,\n                                            size_t height,\n                                            size_t stride,\n                                            size_t channels,\n                                            const Type& type)> process) const {\n  const int alignment = 32; // 4 * _type.dataSize();\n  void* buffer = nullptr;\n  size_t buffer_size = _width * _height * _channels * _type.dataSize();\n\n#ifdef __ANDROID__\n  buffer = (void*)memalign(alignment, buffer_size);\n#else\n  posix_memalign((void**)&buffer, alignment, buffer_size);\n#endif\n  CAFFE_ENFORCE(buffer);\n\n  process(buffer, _width, _height, _width, _channels, _type);\n  loadData(buffer);\n  free(buffer);\n}\n\nvoid GLTexture::loadData(const void* pixels) const {\n  glBindTexture(GL_TEXTURE_2D, _textureId);\n  glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _width, _height, _type.format, _type.type, pixels);\n  glBindTexture(GL_TEXTURE_2D, 0);\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/GLTexture.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n#include \"GL.h\"\n#include \"GLLogging.h\"\n\nclass GLTexture {\n public:\n  struct Type {\n    const GLenum internalFormat;\n    const GLenum format;\n    const GLenum type;\n\n    int dataSize() const {\n      switch (type) {\n      case GL_UNSIGNED_INT:\n        return 4;\n      case GL_HALF_FLOAT:\n        return 2;\n      case GL_UNSIGNED_BYTE:\n        return 1;\n      default:\n        throw std::runtime_error(\"Unknown Texture Type\");\n      }\n    }\n\n    int channels() const {\n      switch (format) {\n      case GL_R8:\n        return 1;\n      case GL_RG8:\n        return 2;\n      // case GL_BGRA:\n      case GL_RG_INTEGER:\n      case GL_RGBA:\n        return 4;\n      default:\n        throw std::runtime_error(\"Unknown Texture Format\");\n      }\n    }\n  };\n\n  static const Type FP16;\n  static const Type FP16_COMPAT;\n  static const Type UI8;\n\n protected:\n  const Type& _type;\n\n  const GLsizei _width;\n  const GLsizei _height;\n  const GLsizei _stride;\n  const GLsizei _channels;\n  const bool _use_padding;\n\n  GLint _filter;\n  GLint _wrap;\n  GLuint _textureId;\n\n public:\n  GLTexture(const Type& type,\n            int width,\n            int height,\n            int stride,\n            bool use_padding,\n            GLint filter,\n            GLint wrap)\n      : _type(type),\n        _width(width),\n        _height(height),\n        _stride(stride),\n        _channels(type.channels()),\n        _use_padding(use_padding),\n        _filter(filter),\n        _wrap(wrap) {}\n\n  GLTexture(const Type& type, int width, int height, bool use_padding, GLint filter, GLint wrap)\n      : GLTexture(type,\n                  width,\n                  height,\n                  use_padding ? (width + 7) / 8 * 8 : width,\n                  use_padding,\n                  filter,\n                  wrap) {}\n\n  virtual ~GLTexture() {}\n  virtual GLuint name() const = 0;\n  virtual GLenum target() const = 0;\n  virtual bool flipped() const = 0;\n\n  virtual void map_read(std::function<void(const void* buffer,\n                                           size_t width,\n                                           size_t height,\n                                           size_t stride,\n                                           size_t channels,\n                                           const Type& type)> process) const;\n\n  virtual void map_load(std::function<void(void* buffer,\n                                           size_t width,\n                                           size_t height,\n                                           size_t stride,\n                                           size_t channels,\n                                           const Type& type)> process) const;\n\n  void loadData(const void* pixels) const;\n};\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/ImageAllocator.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"GLImageAllocator.h\"\n\nnamespace caffe2 {\n\ntemplate <class T>\nclass ImageAllocator {\n  GLImageAllocator<T>* glImageAllocator;\n\n public:\n  ImageAllocator() : glImageAllocator(GLImageAllocator<T>::newGLImageAllocator()) {}\n\n  virtual ~ImageAllocator() { delete glImageAllocator; }\n\n  GLImageVector<T>* newImage(\n      int num_images, int width, int height, int channels, bool is_output = false) {\n    const int tile_x = 1, tile_y = 1;\n    return glImageAllocator->newImage(\n        num_images, width, height, channels, tile_x, tile_y, is_output);\n  }\n\n  GLImageVector<T>* newImage(int num_images,\n                             int width,\n                             int height,\n                             int channels,\n                             int tile_x,\n                             int tile_y,\n                             bool is_output = false) {\n    return glImageAllocator->newImage(\n        num_images, width, height, channels, tile_x, tile_y, is_output);\n  }\n\n  GLImageVector<T>* newImage(\n      int num_images,\n      int width,\n      int height,\n      int channels,\n      int tile_x,\n      int tile_y,\n      std::function<const GLTexture*(const int width, const int height)> textureAllocator) {\n    return glImageAllocator->newImage(\n        num_images, width, height, channels, tile_x, tile_y, textureAllocator);\n  }\n};\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/arm_neon_support.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"caffe2/core/common.h\"\n\n#ifdef __ARM_NEON__\n#if CAFFE2_IOS\n#include \"arm_neon.h\"\n#elif CAFFE2_ANDROID\n#include \"caffe2/mobile/contrib/opengl/android/arm_neon_support.h\"\n#endif\n#endif\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/rewrite_net.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"rewrite_net.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include <unordered_map>\n#include <unordered_set>\n\n#ifdef CAFFE2_ANDROID\n#include \"../android/AndroidGLContext.h\"\n#endif\n\nnamespace caffe2 {\n\nstruct Analysis {\n  struct SSA {\n    using BlobVersions = std::unordered_map<std::string, size_t>;\n    BlobVersions inVersions;\n    BlobVersions outVersions;\n  };\n  std::vector<SSA> ssa;\n  std::unordered_map<std::string, std::unordered_map<size_t, std::vector<size_t>>> inUsages;\n};\n\nstatic Analysis analyzeNet(const NetDef& net) {\n  Analysis::SSA::BlobVersions frontier;\n  Analysis analysis;\n\n  auto play = [&](size_t i, const OperatorDef& op) {\n    Analysis::SSA::BlobVersions inVersions;\n    for (const auto& s : op.input()) {\n      inVersions[s] = frontier[s];\n      analysis.inUsages[s][frontier[s]].push_back(i);\n    }\n    Analysis::SSA::BlobVersions outVersions;\n    for (const auto& s : op.output()) {\n      if (frontier.find(s) != frontier.end()) {\n        frontier[s] += 1;\n      }\n      outVersions[s] = frontier[s];\n    }\n    analysis.ssa.push_back(Analysis::SSA{inVersions, outVersions});\n  };\n\n  for (auto i = 0; i < net.op_size(); ++i) {\n    play(i, net.op(i));\n  }\n  return analysis;\n}\n\nstatic void insertCopyToGPUOp(NetDef& predictNet, const std::string& cpu_blob) {\n  auto* op = predictNet.add_op();\n  op->set_name(\"CopyToOpenGL\");\n  op->set_type(\"CopyToOpenGL\");\n  op->add_input(cpu_blob);\n  op->add_output(cpu_blob + \"_M\");\n}\n\nstatic void insertCopyFromGPUOp(NetDef& predictNet, const std::string& cpu_blob) {\n  // add argument \"is_last\" to the last op to signal this is the last operator before the\n  // CopyFromOpenGL op\n  auto* last_op = predictNet.mutable_op(predictNet.op_size() - 1);\n  auto* arg = last_op->add_arg();\n  arg->set_name(\"is_last\");\n  arg->set_i(1);\n\n  auto* op = predictNet.add_op();\n  op->set_name(\"CopyFromOpenGL\");\n  op->set_type(\"CopyFromOpenGL\");\n  op->add_input(cpu_blob + \"_M\");\n  op->add_output(cpu_blob);\n}\n\nstatic NetDef insertInputOutputCopyOps(const NetDef& def, std::unordered_set<std::string>& glOps) {\n  // Do some validation of the outputs. For this version, we require:\n  // - a single input (first element of external_input()) is consumed by the NetDef\n  // - a single output (first element of external_output()) is produced by the NetDef.\n  // - the input is consumed by def.op(0), and this is the only consumer.\n  // - the output is produced by def.op(-1).\n  CAFFE_ENFORCE_GE(def.external_input_size(), 1);\n  CAFFE_ENFORCE_GE(def.external_output_size(), 1);\n  auto analysis = analyzeNet(def);\n  // enforce a single use of the input blob.\n  CAFFE_ENFORCE_GE(def.op_size(), 1);\n\n  const auto& inputBlob = def.external_input(0);\n  // Enforce that the input blob has a single usage - in the first operator.\n  CAFFE_ENFORCE(analysis.inUsages[inputBlob][0] == (std::vector<size_t>{0}));\n  // Enforce that the external_output(0) blob is produced by the last operator in this sequence.\n  const auto& outputBlob = def.external_output(0);\n  CAFFE_ENFORCE(analysis.ssa.back().outVersions.find(outputBlob) !=\n                analysis.ssa.back().outVersions.end());\n  const auto& outputBlobVersion = analysis.ssa.back().outVersions[outputBlob];\n  // This should hold true by definition of the SSA analysis.\n  CAFFE_ENFORCE(analysis.inUsages[outputBlob].find(outputBlobVersion) ==\n                analysis.inUsages[outputBlob].end());\n\n  NetDef mdef;\n  mdef.CopyFrom(def);\n  mdef.clear_op();\n\n  std::unordered_map<std::string, std::set<size_t>> cpu_blobs, gpu_blobs;\n  cpu_blobs[def.external_input(0)].insert(0);\n\n  for (auto i = 0; i < def.op_size(); i++) {\n    const auto& currentOp = def.op(i);\n    if (glOps.count(currentOp.type()) > 0) {\n      // OpenGL Op\n      // insert copyToOpenGLOp\n      for (auto j = 0; j < currentOp.input_size(); j++) {\n        auto& input = currentOp.input(j);\n        auto version = analysis.ssa[i].inVersions[input];\n        if (cpu_blobs[input].count(version) > 0) {\n          insertCopyToGPUOp(mdef, input);\n          gpu_blobs[input].insert(version);\n          cpu_blobs[input].erase(version);\n        }\n        // Only the first input should be OpenGL texture\n        // Otherwise, copyToOpenGLOp will be inserted for the weights,\n        // which are outputs of QuantDecode\n        if (currentOp.type().find(\"OpenGLConv\") == 0) {\n          if (j == 0) {\n            break;\n          }\n        }\n      }\n\n      auto* op = mdef.add_op();\n      op->CopyFrom(currentOp);\n\n      // swap input blob\n      for (auto j = 0; j < currentOp.input_size(); j++) {\n        auto& input = currentOp.input(j);\n        auto version = analysis.ssa[i].inVersions[input];\n        if (gpu_blobs[input].count(version) > 0) {\n          op->set_input(j, input + \"_M\");\n        }\n      }\n\n      // swap output blob\n      for (auto j = 0; j < currentOp.output_size(); j++) {\n        auto& output = currentOp.output(j);\n        auto version = analysis.ssa[i].outVersions[output];\n        op->set_output(j, output + \"_M\");\n        gpu_blobs[output].insert(version);\n      }\n      // insert copyFromOpenGLOp after the last op if the last op is an OpenGL op\n      if (i == def.op_size() - 1) {\n        insertCopyFromGPUOp(mdef, currentOp.output(0));\n      }\n    } else {\n      // CPU Op\n      // insert copyFromOpenGLOp\n      for (auto j = 0; j < currentOp.input_size(); j++) {\n        auto& input = currentOp.input(j);\n        auto version = analysis.ssa[i].inVersions[input];\n        if (gpu_blobs[input].count(version) > 0) {\n          insertCopyFromGPUOp(mdef, input);\n        }\n      }\n      auto* op = mdef.add_op();\n      op->CopyFrom(currentOp);\n      for (auto j = 0; j < currentOp.output_size(); j++) {\n        auto& output = currentOp.output(j);\n        auto version = analysis.ssa[i].outVersions[output];\n        cpu_blobs[output].insert(version);\n      }\n    }\n  }\n  return mdef;\n}\n\nstatic bool tryFuseAdjacentOps(const OperatorDef& currentOp,\n                               const OperatorDef& nextOp,\n                               OperatorDef* fusedOp,\n                               std::unordered_set<std::string>& glOps) {\n  // Check for possible invalid opportunities.\n  if (currentOp.output_size() != 1 || nextOp.output_size() != 1) {\n    return false;\n  }\n  // The fused op cannot be inplace\n  if (currentOp.output(0) != nextOp.input(0) || currentOp.input(0) == nextOp.output(0)) {\n    return false;\n  }\n\n  static const std::map<std::pair<std::string, std::string>, std::string> fusionOpportunities = {\n      {{\"OpenGLInstanceNorm\", \"OpenGLPRelu\"}, \"OpenGLInstanceNormPRelu\"},\n      {{\"OpenGLConv\", \"OpenGLPRelu\"}, \"OpenGLConvPRelu\"},\n      {{\"OpenGLConv\", \"OpenGLRelu\"}, \"OpenGLConvRelu\"},\n      {{\"OpenGLConvTranspose\", \"OpenGLPRelu\"}, \"OpenGLConvTransposePRelu\"}};\n  auto it = fusionOpportunities.find({currentOp.type(), nextOp.type()});\n  if (it == fusionOpportunities.end()) {\n    return false;\n  }\n\n  glOps.insert(it->second);\n  fusedOp->CopyFrom(currentOp);\n  fusedOp->set_output(0, nextOp.output(0));\n  fusedOp->set_type(it->second);\n  for (auto i = 1; i < nextOp.input_size(); i++) {\n    fusedOp->add_input(nextOp.input(i));\n  }\n  return true;\n}\n\nstatic NetDef runOpenGLFusion(const NetDef& def, std::unordered_set<std::string>& glOps) {\n  CHECK_GE(def.op_size(), 1);\n  NetDef mdef;\n  mdef.CopyFrom(def);\n  mdef.clear_op();\n  auto i = 0;\n\n  while (i < def.op_size()) {\n    if (i == def.op_size() - 1) {\n      VLOG(2) << \"Last operator, skipping\";\n      auto* op = mdef.add_op();\n      op->CopyFrom(def.op(i));\n      i += 1;\n      continue;\n    }\n\n    const auto& currentOp = def.op(i);\n    const auto& nextOp = def.op(i + 1);\n    OperatorDef fusedOp;\n    if (tryFuseAdjacentOps(currentOp, nextOp, &fusedOp, glOps)) {\n      VLOG(2) << \"Found an adjacent fusion for: \" << currentOp.type() << \", \" << nextOp.type();\n      // We can fuse.\n      auto* op = mdef.add_op();\n      op->CopyFrom(fusedOp);\n      i += 2;\n      continue;\n    }\n    VLOG(2) << \"No fusion available for: \" << currentOp.type() << \", \" << nextOp.type();\n    // Just emit the current type.\n    auto* op = mdef.add_op();\n    op->CopyFrom(currentOp);\n    i += 1;\n  }\n  return mdef;\n}\n\nvoid dumpDefForOpenGL(const NetDef& d) {\n  for (const auto& op : d.op()) {\n    LOG(INFO) << op.input(0) << \" -> \" << op.type() << \" -> \" << op.output(0);\n  }\n}\n\n// // For debugging\n// void dumpDefForOpenGL(const NetDef &net) {\n//  for (const auto &op : net.op()) {\n//    printf(\"***Operator: %s\\n\", op.type().c_str());\n//    for (auto input : op.input()) {\n//      printf(\"\\tInput: %s\\n\", input.c_str());\n//    }\n//\n//    for (auto output : op.output()) {\n//      printf(\"\\tOutput: %s\\n\", output.c_str());\n//    }\n//  }\n//}\n\nNetDef rewritePredictNetForOpenGL(const NetDef& predictNet, bool useTextureInput, bool useTiling, bool runFusion) {\n  CAFFE_ENFORCE_GE(predictNet.op_size(), 1);\n  NetDef net;\n  net.CopyFrom(predictNet);\n\n  std::unordered_map<std::string, std::string> replacements(\n      {{\"OpenGLPackedInt8BGRANHWCToNCHWCStylizerPreprocess\",\n        useTextureInput ? \"OpenGLTextureToTextureStylizerPreprocess\"\n                        : \"OpenGLTensorToTextureStylizerPreprocess\"},\n       {\"OpenGLBRGNCHWCToPackedInt8BGRAStylizerDeprocess\",\n        useTextureInput ? \"OpenGLTextureToTextureStylizerDeprocess\"\n                        : \"OpenGLTextureToTensorStylizerDeprocess\"}});\n\n  std::unordered_set<std::string> openGLOps; // Used to insert copy ops\n  bool needCopyOps = false;\n\n  const auto& opKeyList = CPUOperatorRegistry()->Keys();\n  auto opKeySet = std::set<std::string>(opKeyList.begin(), opKeyList.end());\n\n#ifdef CAFFE2_ANDROID\n  // TODO: debug InstanceNorm models on Mali devices\n  AndroidGLContext* context = (AndroidGLContext*)GLContext::getGLContext();\n  if (context->get_platform() == Mali) {\n    opKeySet.erase(\"OpenGLInstanceNorm\");\n    opKeySet.erase(\"OpenGLInstanceNormPRelu\");\n  }\n#endif\n  for (auto i = 0; i < net.op_size(); ++i) {\n    auto* op = net.mutable_op(i);\n    string openGLOp = std::string(\"OpenGL\") + op->type();\n    if (replacements.count(openGLOp) > 0) {\n      openGLOp = replacements[openGLOp];\n    }\n\n    if (opKeySet.find(openGLOp) != opKeySet.end()) {\n      op->set_type(openGLOp);\n      openGLOps.insert(openGLOp);\n\n      if (useTiling) {\n        auto* arg = op->add_arg();\n        arg->set_name(\"tiling\");\n        arg->set_i(1);\n      }\n    } else {\n      needCopyOps = true;\n    }\n  }\n\n  if (useTextureInput && needCopyOps) {\n    CAFFE_THROW(\"OpenGL operator missing\");\n  }\n\n  if (runFusion) {\n    net = runOpenGLFusion(net, openGLOps);\n  }\n\n  if (net.op(0).type() == replacements[\"OpenGLPackedInt8BGRANHWCToNCHWCStylizerPreprocess\"]) {\n    // For end-to-end testing\n    if (net.op(net.op_size() - 1).type() !=\n        replacements[\"OpenGLBRGNCHWCToPackedInt8BGRAStylizerDeprocess\"]) {\n      auto* last_op = net.mutable_op(net.op_size() - 1);\n      auto output = last_op->output(0) + \"M\";\n      last_op->set_output(0, output);\n      auto* copy_op = net.add_op();\n      copy_op->set_name(\"CopyFromOpenGL\");\n      copy_op->set_type(\"CopyFromOpenGL\");\n      copy_op->add_input(output);\n      // rename output blob in case input and output blob has the same name\n      copy_op->add_output(net.external_output(0));\n    }\n  } else {\n    if (!useTextureInput) {\n      needCopyOps = true;\n    }\n  }\n\n  // copy ops are needed when the input is not a texture\n  if (needCopyOps) {\n    // For non style transfer cases\n    net = insertInputOutputCopyOps(net, openGLOps);\n  }\n\n  return net;\n}\n\nbool tryConvertToOpenGL(const NetDef& initNet,\n                        const NetDef& predictNet,\n                        NetDef* glPredictNet,\n                        bool useTextureInput,\n                        bool useTiling,\n                        bool runFusion) {\n  try {\n    // Throws if unsupported operators are found.\n    *glPredictNet = rewritePredictNetForOpenGL(predictNet, useTextureInput, useTiling, runFusion);\n    dumpDefForOpenGL(*glPredictNet);\n    // Throws if unsupported parameters are found.\n    Workspace ws;\n    ws.RunNetOnce(initNet);\n    ws.CreateNet(*glPredictNet);\n    LOG(INFO) << \"OpenGL is successfully enabled\";\n    return true;\n  } catch (const std::exception& e) {\n    LOG(ERROR) << \"Caught exception trying to convert NetDef to OpenGL: \" << e.what();\n    return false;\n  }\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/core/rewrite_net.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n#include \"GLPredictor.h\"\n#include \"caffe2/core/predictor.h\"\n\nnamespace caffe2 {\nbool tryConvertToOpenGL(const NetDef& initNet,\n                        const NetDef& predictNet,\n                        NetDef* glPredictNet,\n                        bool useTextureInput = false,\n                        bool useTiling       = false,\n                        bool runFusion       = true);\n\n// Exposed for testing\nNetDef rewritePredictNetForOpenGL(const NetDef& predictNet,\n                                  bool useTextureInput = false,\n                                  bool useTiling       = false,\n                                  bool runFusion       = true);\nvoid dumpDefForOpenGL(const NetDef& net);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/ios/CMakeLists.txt",
    "content": "file(GLOB_RECURSE tmp *.mm *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/ios/GLContext.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"IOSGLContext.h\"\n\nstd::unique_ptr<GLContext> GLContext::_glcontext = nullptr;\n\nvoid GLContext::initGLContext() {\n  if (_glcontext == nullptr) {\n    _glcontext.reset(new IOSGLContext());\n  }\n}\n\nGLContext* GLContext::getGLContext() {\n  if (_glcontext == nullptr) {\n    initGLContext();\n  }\n  return _glcontext.get();\n}\n\nvoid GLContext::deleteGLContext() { _glcontext.reset(nullptr); }\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/ios/GLImageAllocator.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"IOSGLImageAllocator.h\"\n#include <arm_neon.h>\n\ntemplate <typename T>\nGLImageAllocator<T>* GLImageAllocator<T>::newGLImageAllocator() {\n  return new IOSGLImageAllocator<T>();\n}\n\ntemplate GLImageAllocator<float16_t>* GLImageAllocator<float16_t>::newGLImageAllocator();\ntemplate GLImageAllocator<uint8_t>* GLImageAllocator<uint8_t>::newGLImageAllocator();\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/ios/IOSGLContext.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"../core/GLContext.h\"\n#include \"../core/GLTexture.h\"\n\n#import <CoreVideo/CoreVideo.h>\n\nclass IOSGLContext : public GLContext {\n  void* oglContext;\n  void* oldContext;\n  CVOpenGLESTextureCacheRef textureCache;\n\n public:\n  IOSGLContext();\n  ~IOSGLContext();\n\n  const GLTexture* createNewTexture(CVPixelBufferRef pixelBuffer, const GLTexture::Type& type);\n  void set_context();\n  void reset_context();\n  void flush_context();\n};\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/ios/IOSGLContext.mm",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"IOSGLContext.h\"\n#include \"IOSGLTexture.h\"\n#import <sstream>\n\n#import <OpenGLES/EAGL.h>\n\nIOSGLContext::IOSGLContext() {\n  auto const currentContext = [EAGLContext currentContext];\n  oldContext = (void*)CFBridgingRetain(currentContext);\n\n  if (currentContext != nil && [currentContext API] == kEAGLRenderingAPIOpenGLES3) {\n    oglContext = (void*)CFBridgingRetain(currentContext);\n\n    gl_log(GL_LOG, \"Reusing current context %p\\n\", oglContext);\n  } else {\n    oglContext =\n        (void*)CFBridgingRetain([[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]);\n\n    gl_log(GL_LOG, \"Created a new context %p\\n\", oglContext);\n  }\n\n  if (!oglContext) {\n    throw std::runtime_error(\"Problem with OpenGL context\");\n  }\n\n  set_context();\n  textureCache = NULL;\n  CVReturn err = CVOpenGLESTextureCacheCreate(\n      kCFAllocatorDefault, NULL, (__bridge EAGLContext*)oglContext, NULL, &textureCache);\n\n  if (err) {\n    std::stringstream errmsg;\n    errmsg << \"Error at CVOpenGLESTextureCacheCreate \" << err;\n    throw std::runtime_error(errmsg.str());\n  }\n}\n\nIOSGLContext::~IOSGLContext() {\n  gl_log(GL_VERBOSE, \"~IOSGLContext()\");\n\n  set_context();\n  if (textureCache) {\n    CFRelease(textureCache);\n    textureCache = 0;\n  }\n  reset_context();\n\n  // Explicitly release only after we `reset_context` since otherwise we are going to read from a\n  // dangling pointer.\n  if (oglContext) {\n    CFBridgingRelease(oglContext);\n  }\n  if (oldContext) {\n    CFBridgingRelease(oldContext);\n  }\n}\n\nconst GLTexture* IOSGLContext::createNewTexture(CVPixelBufferRef pixelBuffer,\n                                                const GLTexture::Type& type) {\n  return new IOSGLTexture(type, textureCache, pixelBuffer);\n}\n\nvoid IOSGLContext::set_context() {\n  auto const currentContext = [EAGLContext currentContext];\n\n  if ((__bridge void*)currentContext != oglContext) {\n    if (![EAGLContext setCurrentContext:(__bridge EAGLContext*)oglContext]) {\n      throw std::runtime_error(\"Problem setting OpenGL context\");\n    }\n    GLenum glError = glGetError();\n    if (glError != GL_NO_ERROR) {\n      gl_log(GL_ERR, \"There is an error: 0x%X\\n\", glError);\n    }\n    gl_log(GL_VERBOSE, \"Set context to %p\\n\", oglContext);\n  }\n}\n\nvoid IOSGLContext::reset_context() {\n  EAGLContext* currentContext = [EAGLContext currentContext];\n\n  if ((__bridge void*)currentContext != oldContext) {\n    GLenum glError = glGetError();\n    if (glError != GL_NO_ERROR) {\n      gl_log(GL_ERR, \"There is an error before: 0x%X\\n\", glError);\n    }\n    if (![EAGLContext setCurrentContext:(__bridge EAGLContext*)oldContext]) {\n      throw std::runtime_error(\"Problem setting OpenGL context\");\n    }\n    glError = glGetError();\n    if (glError != GL_NO_ERROR) {\n      gl_log(GL_ERR, \"There is an error after: 0x%X\\n\", glError);\n    }\n    gl_log(GL_VERBOSE, \"Reset context to %p\\n\", oldContext);\n  }\n}\n\nvoid IOSGLContext::flush_context() { CVOpenGLESTextureCacheFlush(textureCache, 0); }\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/ios/IOSGLImageAllocator.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"IOSGLImageAllocator.h\"\n\n#include \"../core/GLImage.h\"\n#include \"../core/GLImageAllocator.h\"\n#include \"../core/GLPlainTexture.h\"\n\n#include \"IOSGLContext.h\"\n#include \"IOSGLTexture.h\"\n\n#include \"../core/arm_neon_support.h\"\n\ntemplate <class T>\nGLImageVector<T>* IOSGLImageAllocator<T>::newImage(int num_images,\n                                                   int width,\n                                                   int height,\n                                                   int channels,\n                                                   int tile_x,\n                                                   int tile_y,\n                                                   bool useCVPixelBuffer) {\n  GLImageVector<T>* output_images =\n      new GLImageVector<T>(num_images, width, height, channels, tile_x, tile_y);\n  if (useCVPixelBuffer) {\n    IOSGLContext* gl_context = (IOSGLContext*)GLContext::getGLContext();\n    for (int i = 0; i < num_images; i++) {\n      GLImage<T>* output_image = new GLImage<T>(\n          width, height, channels, tile_x, tile_y, [&](int slice) -> const GLTexture* {\n            gl_log(GL_VERBOSE,\n                   \"%s pixelbuffers.size(): %ld\\n\",\n                   __PRETTY_FUNCTION__,\n                   pixelbuffers.size());\n\n            CVPixelBufferRef buffer = NULL;\n            int slices = (channels + 3) / 4;\n            int slice_index = i * slices + slice;\n            if (pixelbuffers.size() < slice_index + 1) {\n              const int texture_width = width * tile_x;\n              const int texture_height = height * tile_y;\n              buffer =\n                  IOSGLTexture::createCVPixelBuffer(pixelFormat, texture_width, texture_height);\n              gl_log(GL_VERBOSE,\n                     \"created a new buffer %p for image %d slice %d of dimensions %dx%d\\n\",\n                     buffer,\n                     i,\n                     slice,\n                     texture_width,\n                     texture_height);\n              pixelbuffers.push_back(buffer);\n            } else {\n              buffer = pixelbuffers[slice_index];\n\n              gl_log(GL_VERBOSE, \"reused buffer %p for image %d slice %d\\n\", buffer, i, slice);\n            }\n\n            return gl_context->createNewTexture(buffer, GLImageAllocator<T>::type);\n          });\n      output_images->push_back(output_image);\n    }\n  } else {\n    for (int i = 0; i < num_images; i++) {\n      GLImage<T>* image = new GLImage<T>(\n          width, height, channels, tile_x, tile_y, [&](int slice) -> const GLTexture* {\n            return new GLPlainTexture(\n                GLImageAllocator<T>::type, nullptr, width * tile_x, height * tile_y);\n          });\n      output_images->push_back(image);\n    }\n  }\n  return output_images;\n}\n\ntemplate <>\nconst FourCharCode IOSGLImageAllocator<float16_t>::pixelFormat = kCVPixelFormatType_64RGBAHalf;\ntemplate <>\nconst FourCharCode IOSGLImageAllocator<uint8_t>::pixelFormat = kCVPixelFormatType_32BGRA;\n\ntemplate class IOSGLImageAllocator<float16_t>;\ntemplate class IOSGLImageAllocator<uint8_t>;\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/ios/IOSGLImageAllocator.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"../core/GLImageAllocator.h\"\n\n#import <CoreVideo/CoreVideo.h>\n\ntemplate <class T>\nclass IOSGLImageAllocator : public GLImageAllocator<T> {\n  static const GLTexture::Type& type;\n\n  std::vector<CVPixelBufferRef> pixelbuffers;\n\n public:\n  static const FourCharCode pixelFormat;\n\n  IOSGLImageAllocator() : GLImageAllocator<T>() { gl_log(GL_VERBOSE, \"%s\\n\", __PRETTY_FUNCTION__); }\n\n  ~IOSGLImageAllocator() {\n    gl_log(GL_VERBOSE, \"%s\\n\", __PRETTY_FUNCTION__);\n\n    for (auto&& pixelbuffer : pixelbuffers) {\n      CFRelease(pixelbuffer);\n    }\n  }\n\n  GLImageVector<T>* newImage(int num_images,\n                             int width,\n                             int height,\n                             int channels,\n                             int tile_x,\n                             int tile_y,\n                             bool useCVPixelBuffer);\n};\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/ios/IOSGLTexture.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"../core/GLContext.h\"\n#include \"../core/GLTexture.h\"\n\n#import <CoreVideo/CoreVideo.h>\n\nclass IOSGLTexture : public GLTexture {\n  CVOpenGLESTextureRef textureRef;\n\n  IOSGLTexture(const Type& type,\n               CVOpenGLESTextureCacheRef textureCache,\n               CVPixelBufferRef sourceImage,\n               GLint _filter = GL_NEAREST,\n               GLint _wrap = GL_CLAMP_TO_EDGE);\n\n  friend class IOSGLContext;\n\n public:\n  const CVPixelBufferRef sourceImage;\n\n  ~IOSGLTexture() { CFRelease(textureRef); }\n\n  void map_buffer(std::function<void(void* buffer,\n                                     size_t width,\n                                     size_t height,\n                                     size_t stride,\n                                     size_t channels,\n                                     const Type& type)> process) const;\n\n  virtual void map_read(std::function<void(const void* buffer,\n                                           size_t width,\n                                           size_t height,\n                                           size_t stride,\n                                           size_t channels,\n                                           const Type& type)> process) const;\n\n  virtual void map_load(std::function<void(void* buffer,\n                                           size_t width,\n                                           size_t height,\n                                           size_t stride,\n                                           size_t channels,\n                                           const Type& type)> process) const;\n\n  GLuint name() const { return CVOpenGLESTextureGetName(textureRef); }\n  GLenum target() const { return CVOpenGLESTextureGetTarget(textureRef); };\n  bool flipped() const { return CVOpenGLESTextureIsFlipped(textureRef); };\n\n  static CVPixelBufferRef createCVPixelBuffer(OSType pixelType, int32_t width, int32_t height);\n};\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/ios/IOSGLTexture.mm",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"IOSGLTexture.h\"\n#include \"../core/DataTransfer.h\"\n\nIOSGLTexture::IOSGLTexture(const Type& type,\n                           CVOpenGLESTextureCacheRef textureCache,\n                           CVPixelBufferRef _sourceImage,\n                           GLint filter,\n                           GLint wrap)\n    : GLTexture(type,\n                CVPixelBufferGetWidth(_sourceImage),\n                CVPixelBufferGetHeight(_sourceImage),\n                CVPixelBufferGetBytesPerRow(_sourceImage) / (type.channels() * type.dataSize()),\n                false,\n                filter,\n                wrap),\n      sourceImage(_sourceImage) {\n  CVReturn err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,\n                                                              textureCache,\n                                                              _sourceImage,\n                                                              NULL,\n                                                              GL_TEXTURE_2D,\n                                                              _type.internalFormat,\n                                                              _width,\n                                                              _height,\n                                                              _type.format,\n                                                              _type.type,\n                                                              0,\n                                                              &textureRef);\n\n  if (!textureRef || err) {\n    gl_log(GL_ERR,\n           \"something went wrong, sourceImage: %p, width: %d, height: %d, filter: %d, wrap: %d\\n\",\n           _sourceImage,\n           _width,\n           _height,\n           filter,\n           wrap);\n  }\n  _textureId = name();\n  gl_log(\n      GL_VERBOSE,\n      \"IOSGLTexture() - allocated textureId %d, internalFormat: 0x%X, format: 0x%X, type: 0x%X\\n\",\n      _textureId,\n      _type.internalFormat,\n      _type.format,\n      _type.type);\n\n  glActiveTexture(GL_TEXTURE0);\n  glBindTexture(GL_TEXTURE_2D, _textureId);\n\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);\n\n#if GL_EXT_texture_border_clamp\n  GLfloat borderColor[] = {0.0f, 0.0f, 0.0f, 0.0f};\n  glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR_EXT, borderColor);\n  // Set the texture to use the border clamp wrapping mode.\n  wrap = GL_CLAMP_TO_BORDER_EXT;\n#endif\n\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap);\n\n  glBindTexture(GL_TEXTURE_2D, 0);\n}\n\nCVPixelBufferRef IOSGLTexture::createCVPixelBuffer(OSType pixelFormat,\n                                                   int32_t width,\n                                                   int32_t height) {\n  NSDictionary* pixelBufferAttributes = @{\n    (id)kCVPixelBufferPixelFormatTypeKey : @(pixelFormat),\n    (id)kCVPixelFormatOpenGLESCompatibility : @YES,\n    (id)kCVPixelBufferIOSurfacePropertiesKey : @{/*empty dictionary*/}\n  };\n\n  CVPixelBufferRef buffer = NULL;\n  CVPixelBufferCreate(kCFAllocatorDefault,\n                      width,\n                      height,\n                      pixelFormat,\n                      (__bridge CFDictionaryRef)(pixelBufferAttributes),\n                      &buffer);\n  return buffer;\n}\n\nvoid IOSGLTexture::map_buffer(std::function<void(void* buffer,\n                                                 size_t width,\n                                                 size_t height,\n                                                 size_t stride,\n                                                 size_t channels,\n                                                 const Type& type)> process) const {\n  if (CVPixelBufferLockBaseAddress(sourceImage, 0) == kCVReturnSuccess) {\n    void* buffer = CVPixelBufferGetBaseAddress(sourceImage);\n    int buffer_stride = CVPixelBufferGetBytesPerRow(sourceImage) / (_channels * _type.dataSize());\n    process(buffer, _width, _height, buffer_stride, _channels, _type);\n\n    CVPixelBufferUnlockBaseAddress(sourceImage, 0);\n  }\n}\n\nvoid IOSGLTexture::map_load(std::function<void(void* buffer,\n                                               size_t width,\n                                               size_t height,\n                                               size_t stride,\n                                               size_t channels,\n                                               const Type& type)> process) const {\n  map_buffer(process);\n}\n\nvoid IOSGLTexture::map_read(std::function<void(const void* buffer,\n                                               size_t width,\n                                               size_t height,\n                                               size_t stride,\n                                               size_t channels,\n                                               const Type& type)> process) const {\n  // TODO: why is glFlush() only necessary when running tests\n  glFlush();\n\n  map_buffer(process);\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/CMakeLists.txt",
    "content": "file(GLOB_RECURSE tmp *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLAdd.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n#include \"../core/ImageAllocator.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include <iostream>\n#include <vector>\n\nclass GLAdd : public GLFilter {\n public:\n  binding* inputData[2];\n  binding* outputSize;\n\n  GLAdd()\n      : GLFilter(\"GLAdd\",\n                 vertex_shader,\n                 fragment_shader,\n                 std::vector<binding*>(\n                     {BINDING(outputSize), BINDING(inputData[0]), BINDING(inputData[1])}),\n                 {/* no uniform blocks */},\n                 {/* no attributes */},\n                 {/* no replacements */}) {}\n\n  template <typename T>\n  void add(const GLImageVector<T>& input_image0,\n           const GLImageVector<T>& input_image1,\n           const GLImageVector<T>& output_image);\n\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\n\nconst char* GLAdd::fragment_shader = R\"GLSL(#version 300 es\n\nprecision mediump float;\nprecision mediump int;\n\nin highp vec2 v_texCoord;\n\nuniform ivec2 outputSize;\n\nTEXTURE_INPUT(inputData[2]);\nTEXTURE_OUTPUT(0, outputData);\n\nvoid main() {\n    ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n    vec4 A = TEXTURE_LOAD(inputData[0], texelCoord);\n    vec4 B = TEXTURE_LOAD(inputData[1], texelCoord);\n    vec4 value = A + B;\n    outputData = TEXTURE_STORE(value);\n}\n\n)GLSL\";\n\ntemplate <typename T>\nvoid GLAdd::add(const GLImageVector<T>& input_images0,\n                const GLImageVector<T>& input_images1,\n                const GLImageVector<T>& output_images) {\n  const int num_images = input_images0.size();\n  for (int i = 0; i < num_images; i++) {\n    GLImage<T>* input_image0 = input_images0[i];\n    GLImage<T>* input_image1 = input_images1[i];\n    int input_slices = input_image0->slices;\n    GLImage<T>* output_image = output_images[i];\n    int output_slices = output_image->slices;\n\n    for (int is = 0; is < input_slices; is++) {\n      std::vector<texture_attachment> input_attachments;\n      input_attachments.push_back({input_image0->textures[is], inputData[0]});\n      input_attachments.push_back({input_image1->textures[is], inputData[1]});\n\n      run(input_attachments,\n          {output_image->textures.begin() + is, output_image->textures.begin() + is + 1},\n          [&]() { glUniform2i(outputSize->location, output_image->texture_width, output_image->texture_height); },\n          output_image->texture_width,\n          output_image->texture_height);\n    }\n  }\n}\n\nnamespace caffe2 {\ntemplate <typename T>\nclass OpenGLAddOp final : public Operator<CPUContext>, ImageAllocator<T> {\n public:\n  OpenGLAddOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(OperatorBase::HasArgument(\"broadcast\") == false,\n                           \"OpenGLAdd does not support broadcast\");\n\n    OPERATOR_NEEDS_FEATURE(OperatorBase::HasArgument(\"axis\") == false, \"OpenGLAdd does not support axis\");\n  }\n\n  bool RunOnDevice() override {\n    const GLImageVector<T>& input0 = Inputs()[0]->template Get<GLImageVector<T>>();\n    const GLImageVector<T>& input1 = Inputs()[1]->template Get<GLImageVector<T>>();\n\n    CAFFE_ENFORCE_EQ(input0.size(), input1.size());\n\n    const int num_images = input0.size();\n    const int input_channels = input0.channels();\n    const int input_width = input0.width();\n    const int input_height = input0.height();\n    const int input_tile_x   = input0.tile_x();\n    const int input_tile_y   = input0.tile_y();\n\n    CAFFE_ENFORCE_EQ(input1.channels(), input_channels);\n    CAFFE_ENFORCE_EQ(input1.width(), input_width);\n    CAFFE_ENFORCE_EQ(input1.height(), input_height);\n    CAFFE_ENFORCE_EQ(input1.tile_x(), input_tile_x);\n    CAFFE_ENFORCE_EQ(input1.tile_y(), input_tile_y);\n\n    const int output_channels = input_channels;\n    const int output_width = input_width;\n    const int output_height = input_height;\n    const int output_tile_x   = input_tile_x;\n    const int output_tile_y   = input_tile_y;\n\n    int is_last = OperatorBase::GetSingleArgument<int>(\"is_last\", 0);\n\n    GLImageVector<T>* output = ImageAllocator<T>::newImage(\n        num_images, output_width, output_height, output_channels, output_tile_x, output_tile_y, is_last);\n\n    if (!_add) {\n      _add.reset(new GLAdd());\n    }\n\n    _add->add(input0, input1, *output);\n\n    Outputs()[0]->Reset(output);\n\n    return true;\n  }\n\n private:\n  std::unique_ptr<GLAdd> _add;\n};\n\nREGISTER_CPU_OPERATOR(OpenGLAdd, OpenGLAddOp<float16_t>);\nOPERATOR_SCHEMA(OpenGLAdd).NumInputs(2).NumOutputs(1);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLConcat.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n#include \"../core/ImageAllocator.h\"\n#include \"gl_tiling_utils.h\"\n\n#include <iostream>\n#include <vector>\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/utils/math.h\"\n\nclass GLConcat : public GLFilter {\n public:\n  bool tiling_;\n  binding* inputData;\n  binding* outputSize;\n  binding* inputTileRange;\n  binding* input_tile_x;\n\n  GLConcat(tile_descriptor output_tile_geometries, bool tiling = false)\n      : GLFilter(\"GLConcat\",\n                 vertex_shader,\n                 fragment_shader,\n                 std::vector<binding*>(\n                     {BINDING(outputSize), BINDING(inputData), BINDING(inputTileRange), BINDING(input_tile_x)}),\n                 {/* no uniform blocks */},\n                 {/* no attributes */},\n                 {{\"TILING\", caffe2::to_string(tiling)},\n                  {\"OUTPUT_TILES\", caffe2::to_string(output_tile_geometries.tiles)},\n                  {\"OUTPUT_TILE_X\", caffe2::to_string(output_tile_geometries.tile_dims.x)},\n                  {\"OUTPUT_TILE_WIDTH\", caffe2::to_string(output_tile_geometries.tile_size.x)},\n                  {\"OUTPUT_TILE_HEIGHT\", caffe2::to_string(output_tile_geometries.tile_size.y)}}),\n        tiling_(tiling) {}\n\n  template <typename T>\n  void concat(const GLImageVector<T>** input_images, const GLImageVector<T>& output_image, int size);\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\n\nconst char* GLConcat::fragment_shader = R\"GLSL(#version 300 es\n#define TILING                      $(TILING)\n\n// tiling\n#define OUTPUT_TILES                $(OUTPUT_TILES)\n#define OUTPUT_TILE_X               $(OUTPUT_TILE_X)\n#define OUTPUT_TILE_WIDTH           $(OUTPUT_TILE_WIDTH)\n#define OUTPUT_TILE_HEIGHT          $(OUTPUT_TILE_HEIGHT)\n\nprecision mediump float;\nprecision mediump int;\n\nin highp vec2 v_texCoord;\nTEXTURE_INPUT(inputData);\nTEXTURE_OUTPUT(0, outputData);\n\nuniform ivec2 outputSize;\nuniform ivec2 inputTileRange; // (]\nuniform int input_tile_x;\n\n#if TILING\nconst ivec2 outputTileSize = ivec2(OUTPUT_TILE_WIDTH, OUTPUT_TILE_HEIGHT);\n\nvoid main() {\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n  ivec2 tile = texelCoord / outputTileSize; // 2D output tile idx\n  ivec2 tileCoord = texelCoord % outputTileSize; // in-tile coordinates\n  int tileNum = OUTPUT_TILE_X * tile.y + tile.x; // 1D output tile idx\n\n  if (tileNum >= inputTileRange.x && tileNum < inputTileRange.y) {\n    tileNum = tileNum - inputTileRange.x;\n    texelCoord = ivec2(tileNum % input_tile_x, tileNum / input_tile_x)  * ivec2(OUTPUT_TILE_WIDTH, OUTPUT_TILE_HEIGHT) + tileCoord;\n    vec4 value = TEXTURE_LOAD(inputData, texelCoord);\n    outputData = TEXTURE_STORE(value);\n  } else {\n    // early termination\n    discard;\n  }\n}\n\n#else\nvoid main() {\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n  vec4 value = TEXTURE_LOAD(inputData, texelCoord);\n  outputData = TEXTURE_STORE(value);\n}\n#endif\n\n)GLSL\";\n\ntemplate <typename T>\nvoid GLConcat::concat(const GLImageVector<T>** input_images, const GLImageVector<T>& output_images, int input_size) {\n  for (int k = 0; k < output_images.size(); k++) {\n    GLImage<T>* output_image = output_images[k];\n\n    int is = 0, os = 0;\n    for (int i = 0; i < input_size; i++) {\n      for (int j = 0; j < input_images[i]->slices(); j++) {\n        GLImage<T>* input_image = (*input_images[i])[k];\n        std::vector<texture_attachment> input_attachments;\n        input_attachments.push_back({input_image->textures[j], inputData});\n\n        run(input_attachments,\n            {output_image->textures.begin() + os, output_image->textures.begin() + os + 1},\n            [&]() {\n              glUniform2i(outputSize->location, output_image->texture_width, output_image->texture_height);\n              glUniform2i(inputTileRange->location, is, is + input_image->tile_x * input_image->tile_y);\n              glUniform1i(input_tile_x->location, input_image->tile_x);\n            },\n            output_image->texture_width,\n            output_image->texture_height);\n        if (!tiling_) {\n          os++; // for tiling, you always write to the same texture\n        }\n        is += input_image->tile_x * input_image->tile_y;\n      }\n    }\n  }\n}\n\nnamespace caffe2 {\ntemplate <typename T>\nclass OpenGLConcatOp final : public Operator<CPUContext>, ImageAllocator<T> {\n public:\n  OpenGLConcatOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        order_(StringToStorageOrder(OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    OPERATOR_NEEDS_FEATURE(this->order_ == StorageOrder::NCHW, \"OpenGL only supports NCHW order.\");\n  }\n\n  bool RunOnDevice() override {\n    const GLImageVector<T>& input0 = Inputs()[0]->template Get<GLImageVector<T>>();\n    const int num_images = input0.size();\n\n    const GLImageVector<T>** input_images = new const GLImageVector<T>*[Inputs().size()];\n    input_images[0] = &input0;\n    int channelCount = input0.channels();\n\n    bool tiling = OperatorBase::GetSingleArgument<int>(\"tiling\", 0);\n\n    // Only supports input channels divisible by 4 for now\n    CAFFE_ENFORCE_EQ(input0.channels() % 4, 0);\n    for (auto i = 1; i < Inputs().size(); i++) {\n      const GLImageVector<T>& inputi = Inputs()[i]->template Get<GLImageVector<T>>();\n      channelCount += inputi.channels();\n      CAFFE_ENFORCE_EQ(num_images, inputi.size());\n      CAFFE_ENFORCE_EQ(inputi.channels() % 4, 0);\n      CAFFE_ENFORCE_EQ(input0.width(), inputi.width());\n      CAFFE_ENFORCE_EQ(input0.height(), inputi.height());\n      input_images[i] = &inputi;\n\n      if (inputi.tile_x() > 1 || inputi.tile_y() > 1) {\n        tiling = true;\n      }\n    }\n\n    const int input_width = input0.width();\n    const int input_height = input0.height();\n\n    const int output_channels = channelCount;\n    const int output_width = input_width;\n    const int output_height = input_height;\n\n    int output_tile_x = 1;\n    int output_tile_y = 1;\n    if (tiling) {\n      computeOutputTiles(output_channels, output_tile_x, output_tile_y);\n    }\n\n    int is_last = OperatorBase::GetSingleArgument<int>(\"is_last\", 0);\n\n    GLImageVector<T>* output = ImageAllocator<T>::newImage(\n        num_images, output_width, output_height, output_channels, output_tile_x, output_tile_y, is_last);\n    if (!_concat) {\n      tile_descriptor output_tile_geometries{\n          {output_tile_x, output_tile_y}, {output_width, output_height}, output_tile_x * output_tile_y};\n      _concat.reset(new GLConcat(output_tile_geometries, tiling));\n    }\n\n    _concat->concat(input_images, *output, Inputs().size());\n    delete[] input_images;\n    Outputs()[0]->Reset(output);\n\n    return true;\n  }\n\n private:\n  StorageOrder order_;\n  std::unique_ptr<GLConcat> _concat;\n};\n\nREGISTER_CPU_OPERATOR(OpenGLConcat, OpenGLConcatOp<float16_t>);\nOPERATOR_SCHEMA(OpenGLConcat).NumInputs(2, 4).NumOutputs(1, 2);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLConvolution.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"GLConvolution.h\"\n#include \"../core/GLContext.h\"\n#include \"../core/ImageAllocator.h\"\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/operators/conv_transpose_unpool_op_base.h\"\n#include <iostream>\n#include <vector>\n\n#define MaxOutputTileBatchSize 2\n\n// MARK: GLSL\nconst char* GLConvolution::fragment_shader = R\"GLSL(#version 300 es\n#define TILED_CONVOLUTION           $(TILED_CONVOLUTION)\n#define TRANSPOSED_CONVOLUTION      $(TRANSPOSED_CONVOLUTION)\n\n// batching\n#define INPUT_BATCH_SIZE            $(INPUT_BATCH_SIZE)\n#define OUTPUT_BATCH_SIZE           $(OUTPUT_BATCH_SIZE)\n\n// tiling\n#define INPUT_TILES                 $(INPUT_TILES)\n#define OUTPUT_TILES                $(OUTPUT_TILES)\n#define INPUT_TILE_WIDTH            $(INPUT_TILE_WIDTH)\n#define INPUT_TILE_HEIGHT           $(INPUT_TILE_HEIGHT)\n#define OUTPUT_TILE_WIDTH           $(OUTPUT_TILE_WIDTH)\n#define OUTPUT_TILE_HEIGHT          $(OUTPUT_TILE_HEIGHT)\n#define INPUT_TILE_X                $(INPUT_TILE_X)\n#define OUTPUT_TILE_X               $(OUTPUT_TILE_X)\n#define INPUT_TILE_CHUNK_SIZE       $(INPUT_TILE_CHUNK_SIZE)\n#define OUTPUT_TILE_CHUNK_SIZE      $(OUTPUT_TILE_CHUNK_SIZE)\n#define OUTPUT_TILE_BATCH_SIZE      $(OUTPUT_TILE_BATCH_SIZE)\n\n#define BOUNDS_CHECK_MODE           $(BOUNDS_CHECK_MODE)\n\n// common\nconst ivec2 input_padding = ivec2($(INPUT_PADDING_X), $(INPUT_PADDING_Y));\nconst ivec2 input_stride = ivec2($(INPUT_STRIDE_X), $(INPUT_STRIDE_Y));\nconst ivec2 kernel_size = ivec2($(KERNEL_SIZE_X), $(KERNEL_SIZE_Y));\n\nprecision mediump float;\nprecision mediump int;\nprecision mediump sampler2D;\n\nin highp vec2 v_texCoord;\n\n#define unpackKernel(pk) \\\n  mat4(vec4(unpackHalf2x16(pk.packed_data[0].x), unpackHalf2x16(pk.packed_data[0].y)), \\\n       vec4(unpackHalf2x16(pk.packed_data[0].z), unpackHalf2x16(pk.packed_data[0].w)), \\\n       vec4(unpackHalf2x16(pk.packed_data[1].x), unpackHalf2x16(pk.packed_data[1].y)), \\\n       vec4(unpackHalf2x16(pk.packed_data[1].z), unpackHalf2x16(pk.packed_data[1].w)))\n\n#if BOUNDS_CHECK_MODE == 0\n  #define IN_BOUNDS(p, p0, p1) (true)\n#else\n  #define IN_BOUNDS(p, p0, p1) (all(greaterThanEqual(p, p0)) && all(lessThan(p, p1)))\n#endif\n\n#if TILED_CONVOLUTION\n// Tiled convolution\nconst ivec2 inputTileSize = ivec2(INPUT_TILE_WIDTH, INPUT_TILE_HEIGHT);\nconst ivec2 outputTileSize = ivec2(OUTPUT_TILE_WIDTH, OUTPUT_TILE_HEIGHT);\n\nuniform ivec2 outputSize;\nuniform bool accumulate;\nuniform bool fusePRelu;\n\nuniform ivec2 inputTileRange;\n\nTEXTURE_INPUT(inputData[1]);\nTEXTURE_INPUT(previousData[1]);\n\nstruct packedKernel {\n  highp uvec4 packed_data[2];\n};\n\nstruct kernel {\n  packedKernel data[kernel_size.x * kernel_size.y];\n};\n\nlayout (std140) uniform Kernel_block {\n  kernel kernel_data[INPUT_TILE_CHUNK_SIZE * OUTPUT_TILE_CHUNK_SIZE];\n} kernel_block[OUTPUT_TILE_BATCH_SIZE];\n\nlayout (std140) uniform bias_block {\n  highp uvec4 bias[(OUTPUT_TILES + 1) / 2];\n};\n\nlayout (std140) uniform prelu_scale_block {\n  highp uvec4 scale[(OUTPUT_TILES + 1) / 2];\n};\n\nTEXTURE_OUTPUT(0, outputData0);\n\n#if TRANSPOSED_CONVOLUTION\n\n#define CONVOLUTION(ib) { \\\n  ivec2 p0 = (input_padding + input_stride - tileCoord % input_stride) % input_stride; \\\n  for (int y = p0.y; y < kernel_size.y; y += input_stride.y) { \\\n    for (int x = p0.x; x < kernel_size.x; x += input_stride.x) { \\\n      int i = y * kernel_size.x + x; \\\n      ivec2 idx = tileCoord + ivec2(x, y) - input_padding; \\\n      if IN_BOUNDS(idx, ivec2(0), inputTileSize * input_stride) { \\\n        vec4 data = TEXTURE_LOAD(inputData[0], inputTileOffset + idx / input_stride); \\\n        mediump mat4 k = unpackKernel(kernel_block[ib].kernel_data[kernelIdx].data[i]); \\\n        sum += k * data; \\\n      } \\\n    } \\\n  } \\\n}\n\n#else\n\n#define CONVOLUTION(ib) { \\\n  for (int y = 0, i = 0; y < kernel_size.y; y++) { \\\n    for (int x = 0; x < kernel_size.x; x++, i++) { \\\n      ivec2 idx = tileCoord + ivec2(x, y); \\\n      if IN_BOUNDS(idx, ivec2(0), inputTileSize) { \\\n        vec4 data = TEXTURE_LOAD(inputData[0], inputTileOffset + idx); \\\n        mediump mat4 k = unpackKernel(kernel_block[ib].kernel_data[kernelIdx].data[i]); \\\n        sum += k * data; \\\n      } \\\n    } \\\n  } \\\n}\n#endif // TRANSPOSED_CONVOLUTION\n\nvoid main() {\n  ivec2 inputSize = textureSize(inputData[0], 0);\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n\n  ivec2 tile = texelCoord / outputTileSize; // 2D output tile idx\n  ivec2 tileCoord = texelCoord % outputTileSize; // in-tile coordinates\n\n  int tileNum = OUTPUT_TILE_X * tile.y + tile.x; // 1D output tile idx\n\n#if !TRANSPOSED_CONVOLUTION\n  tileCoord = input_stride * tileCoord - input_padding;\n#endif\n\n  highp vec4 sum = vec4(0);\n\n  for (int tile_idx = inputTileRange.x; tile_idx < inputTileRange.y; tile_idx++) {\n    int inTileX = tile_idx % INPUT_TILE_X;\n    int inTileY = tile_idx / INPUT_TILE_X;\n    int inTileId = tile_idx % INPUT_TILE_CHUNK_SIZE; // normalized input tile idx, used to index the kernel\n\n    int kernelIdx = OUTPUT_TILE_CHUNK_SIZE * inTileId + tileNum % OUTPUT_TILE_CHUNK_SIZE;\n    ivec2 inputTileOffset = ivec2(inTileX, inTileY) * inputTileSize;\n\n    int outputChunkIdx = tileNum / OUTPUT_TILE_CHUNK_SIZE;\n    if (outputChunkIdx == 0) {\n      CONVOLUTION(0);\n    }\n#if OUTPUT_TILE_BATCH_SIZE > 1\n    else if (outputChunkIdx == 1) {\n      CONVOLUTION(1);\n    }\n#if OUTPUT_TILE_BATCH_SIZE > 2\n    else if (outputChunkIdx == 2) {\n      CONVOLUTION(2);\n    }\n#if OUTPUT_TILE_BATCH_SIZE > 3\n    else if (outputChunkIdx == 3) {\n      CONVOLUTION(3);\n    }\n#if OUTPUT_TILE_BATCH_SIZE > 4\n    else if (outputChunkIdx == 4) {\n      CONVOLUTION(4);\n    }\n#if OUTPUT_TILE_BATCH_SIZE > 5\n    else if (outputChunkIdx == 5) {\n      CONVOLUTION(5);\n    }\n#if OUTPUT_TILE_BATCH_SIZE > 6\n    else if (outputChunkIdx == 6) {\n      CONVOLUTION(6);\n    }\n#if OUTPUT_TILE_BATCH_SIZE > 7\n    else if (outputChunkIdx == 7) {\n      CONVOLUTION(7);\n    }\n#endif\n#endif\n#endif\n#endif\n#endif\n#endif\n#endif\n  }\n\n  vec4 biasValue = (tileNum % 2 == 0) ? unpackHalf4x16(bias[tileNum/2].xy) : unpackHalf4x16(bias[tileNum/2].zw);\n  vec4 prevData = TEXTURE_LOAD(previousData[0], texelCoord);\n  vec4 value = sum + (accumulate ? prevData : biasValue);\n\n  vec4 preluValue = (tileNum % 2 == 0) ? unpackHalf4x16(scale[tileNum/2].xy) : unpackHalf4x16(scale[tileNum/2].zw);\n\n  vec4 o0 = fusePRelu ? mix(value * preluValue, value, vec4(greaterThan(value, vec4(0)))) : value;\n  outputData0 = TEXTURE_STORE(o0);\n}\n\n#else\n\n// batched convolution\n\nuniform ivec2 outputSize;\nuniform bool accumulate;\nuniform bool fusePRelu;\n\nTEXTURE_INPUT(inputData[INPUT_BATCH_SIZE]);\nTEXTURE_INPUT(previousData[OUTPUT_BATCH_SIZE]);\n\nstruct packedKernel {\n  highp uvec4 packed_data[2];\n};\n\nstruct kernel {\n  packedKernel data[kernel_size.x * kernel_size.y];\n};\n\nlayout (std140) uniform Kernel_block {\n  kernel kernel_data[OUTPUT_BATCH_SIZE];\n} kernel_block[INPUT_BATCH_SIZE];\n\nlayout (std140) uniform bias_block {\n  highp uvec4 bias[(OUTPUT_BATCH_SIZE + 1) / 2];\n};\n\nlayout (std140) uniform prelu_scale_block {\n  highp uvec4 scale[(OUTPUT_BATCH_SIZE + 1) / 2];\n};\n\nTEXTURE_OUTPUT(0, outputData0);\n#if OUTPUT_BATCH_SIZE > 1\nTEXTURE_OUTPUT(1, outputData1);\n#if OUTPUT_BATCH_SIZE > 2\nTEXTURE_OUTPUT(2, outputData2);\n#if OUTPUT_BATCH_SIZE > 3\nTEXTURE_OUTPUT(3, outputData3);\n#endif\n#endif\n#endif\n\n#if TRANSPOSED_CONVOLUTION\n#define CONVOLUTION(ib) { \\\n  ivec2 p0 = (input_padding + input_stride - texelCoord % input_stride) % input_stride; \\\n  for (int y = p0.y; y < kernel_size.y; y += input_stride.y) { \\\n    for (int x = p0.x; x < kernel_size.x; x += input_stride.x) { \\\n      int i = y * kernel_size.x + x; \\\n      ivec2 idx = texelCoord + ivec2(x, y) - input_padding; \\\n      if IN_BOUNDS(idx, ivec2(0), inputSize * input_stride) { \\\n        vec4 data = TEXTURE_LOAD(inputData[ib], idx / input_stride); \\\n        for (int ob = 0; ob < OUTPUT_BATCH_SIZE; ob++) { \\\n          mediump mat4 k = unpackKernel(kernel_block[ib].kernel_data[ob].data[i]); \\\n          sum[ob] += k * data; \\\n        } \\\n      } \\\n    } \\\n  } \\\n}\n\n#else\n\n#define CONVOLUTION(ib) { \\\n  for (int y = 0, i = 0; y < kernel_size.y; y++) { \\\n    for (int x = 0; x < kernel_size.x; x++, i++) { \\\n      ivec2 idx = coord + ivec2(x, y); \\\n      if IN_BOUNDS(idx, ivec2(0), inputSize) { \\\n        vec4 data = TEXTURE_LOAD(inputData[ib], idx); \\\n        for (int ob = 0; ob < OUTPUT_BATCH_SIZE; ob++) { \\\n          mediump mat4 k = unpackKernel(kernel_block[ib].kernel_data[ob].data[i]); \\\n          sum[ob] += k * data; \\\n        } \\\n      } \\\n    } \\\n  } \\\n}\n\n#endif // TRANSPOSED_CONVOLUTION\n\nvoid main() {\n  ivec2 inputSize = textureSize(inputData[0], 0);\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n\n#if !TRANSPOSED_CONVOLUTION\n  ivec2 coord = input_stride * texelCoord - input_padding;\n#endif\n\n  highp vec4 sum[OUTPUT_BATCH_SIZE] = vec4[OUTPUT_BATCH_SIZE](vec4(0)\n#if OUTPUT_BATCH_SIZE > 1\n                                                                       , vec4(0)\n#if OUTPUT_BATCH_SIZE > 2\n                                                                       , vec4(0)\n#if OUTPUT_BATCH_SIZE > 3\n                                                                       , vec4(0)\n#endif\n#endif\n#endif\n                                                                       );\n\n      CONVOLUTION(0);\n#if INPUT_BATCH_SIZE > 1\n      CONVOLUTION(1);\n#if INPUT_BATCH_SIZE > 2\n      CONVOLUTION(2);\n#if INPUT_BATCH_SIZE > 3\n      CONVOLUTION(3);\n#if INPUT_BATCH_SIZE > 4\n      CONVOLUTION(4);\n#if INPUT_BATCH_SIZE > 5\n      CONVOLUTION(5);\n#if INPUT_BATCH_SIZE > 6\n      CONVOLUTION(6);\n#if INPUT_BATCH_SIZE > 7\n      CONVOLUTION(7);\n#endif\n#endif\n#endif\n#endif\n#endif\n#endif\n#endif\n\n  vec4 prev0 = TEXTURE_LOAD(previousData[0], texelCoord);\n  vec4 value = sum[0] + (accumulate ? prev0: unpackHalf4x16(bias[0].xy));\n  vec4 o0 = fusePRelu ? mix(value * unpackHalf4x16(scale[0].xy), value, vec4(greaterThan(value, vec4(0)))) : value;\n  outputData0 = TEXTURE_STORE(o0);\n#if OUTPUT_BATCH_SIZE > 1\n  vec4 prev1 = TEXTURE_LOAD(previousData[1], texelCoord);\n  value = sum[1] + (accumulate ? prev1 : unpackHalf4x16(bias[0].zw));\n  vec4 o1 = fusePRelu ? mix(value * unpackHalf4x16(scale[0].zw), value, vec4(greaterThan(value, vec4(0)))) : value;\n  outputData1 = TEXTURE_STORE(o1);\n#if OUTPUT_BATCH_SIZE > 2\n  vec4 prev2 = TEXTURE_LOAD(previousData[2], texelCoord);\n  value = sum[2] + (accumulate ? prev2 : unpackHalf4x16(bias[1].xy));\n  vec4 o2 = fusePRelu ? mix(value * unpackHalf4x16(scale[1].xy), value, vec4(greaterThan(value, vec4(0)))) : value;\n  outputData2 = TEXTURE_STORE(o2);\n#if OUTPUT_BATCH_SIZE > 3\n  vec4 prev3 = TEXTURE_LOAD(previousData[3], texelCoord);\n  value = sum[3] + (accumulate ? prev3: unpackHalf4x16(bias[1].zw));\n  vec4 o3 = fusePRelu ? mix(value * unpackHalf4x16(scale[1].zw), value, vec4(greaterThan(value, vec4(0)))) : value;\n  outputData3 = TEXTURE_STORE(o3);\n#endif\n#endif\n#endif\n}\n\n#endif // TILED_CONVOLUTION\n\n)GLSL\";\n\nvoid GLConvolution::pack_kernel_data_for_bached_conv(\n    float16_t* data,\n    size_t size,\n    int input_channels,\n    int output_channels,\n    int is,\n    int os,\n    int ib) {\n  typedef float16_t(packedKernel)[output_batch_size][geometry.kernel_size.y]\n                                 [geometry.kernel_size.x][4][4];\n  packedKernel& packed_kernel_data = *reinterpret_cast<packedKernel*>(data);\n\n  const int batch_input_channels = std::min(4, input_channels - 4 * (is + ib));\n  for (int ob = 0; ob < output_batch_size; ob++) {\n    const int batch_output_channels =\n        std::min(4, output_channels - 4 * (os + ob));\n    for (int out = 0; out < batch_output_channels; out++) {\n      for (int in = 0; in < batch_input_channels; in++) {\n        for (int y = 0; y < geometry.kernel_size.y; y++) {\n          for (int x = 0; x < geometry.kernel_size.x; x++) {\n            // clang-format off\n            if (geometry.transposed) {\n              typedef float(kernelTensor)[input_channels][output_channels][geometry.kernel_size.y][geometry.kernel_size.x];\n              const kernelTensor& kernel_data = *reinterpret_cast<const kernelTensor*>(kernel);\n              packed_kernel_data[ob][y][x][in][out] =\n              kernel_data[4 * (is + ib) + in][4 * (os + ob) + out][geometry.kernel_size.y - 1 - y][geometry.kernel_size.x - 1 - x];\n            } else {\n              typedef float(kernelTensor)[output_channels][input_channels][geometry.kernel_size.y][geometry.kernel_size.x];\n              const kernelTensor& kernel_data = *reinterpret_cast<const kernelTensor*>(kernel);\n              packed_kernel_data[ob][y][x][in][out] = kernel_data[4 * (os + ob) + out][4 * (is + ib) + in][y][x];\n            }\n            // clang-format on\n          }\n        }\n      }\n    }\n  }\n}\n\nvoid GLConvolution::pack_kernel_data_for_tiled_conv(\n    float16_t* data, // destination\n    size_t size,\n    int input_channels,\n    int output_channels,\n    point input_tile_range,\n    point output_tile_range) {\n  typedef float16_t(\n      packedKernel)[input_tile_chunk_size][output_tile_chunk_size]\n                   [geometry.kernel_size.y][geometry.kernel_size.x][4][4];\n  packedKernel& packed_kernel_data = *reinterpret_cast<packedKernel*>(data);\n\n  for (int it = input_tile_range.x; it < input_tile_range.y; it++) {\n    for (int ot = output_tile_range.x; ot < output_tile_range.y; ot++) {\n      for (int y = 0; y < geometry.kernel_size.y; y++) {\n        for (int x = 0; x < geometry.kernel_size.x; x++) {\n          for (int out = 0; out < std::min(4, (output_channels - ot * 4));\n               out++) {\n            for (int in = 0; in < std::min(4, (input_channels - it * 4));\n                 in++) {\n              // clang-format off\n              if (geometry.transposed) {\n                typedef float(kernelTensor)[input_channels][output_channels][geometry.kernel_size.y][geometry.kernel_size.x];\n                const kernelTensor& kernel_data = *reinterpret_cast<const kernelTensor*>(kernel);\n                packed_kernel_data[it - input_tile_range.x][ot - output_tile_range.x][y][x][in][out] =\n                kernel_data[4 * it + in] [4 * ot + out][geometry.kernel_size.y - 1 - y][geometry.kernel_size.x - 1 - x];\n              } else {\n                typedef float(kernelTensor)[output_channels][input_channels][geometry.kernel_size.y][geometry.kernel_size.x];\n                const kernelTensor& kernel_data = *reinterpret_cast<const kernelTensor*>(kernel);\n                packed_kernel_data[it - input_tile_range.x][ot - output_tile_range.x][y][x][in][out] =\n                kernel_data[4 * ot + out][4 * it + in][y][x];\n              }\n              // clang-format on\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\ntemplate <typename T>\nvoid GLConvolution::convolution(\n    const GLImageVector<T>& input_images,\n    const GLImageVector<T>& output_images) {\n  if (tiling) {\n    run_tiled_conv(input_images, output_images);\n  } else {\n    run_batched_conv(input_images, output_images);\n  }\n}\n\ntemplate <typename T>\nvoid GLConvolution::run_batched_conv(\n    const GLImageVector<T>& input_images,\n    const GLImageVector<T>& output_images) {\n  for (int i = 0; i < input_images.size(); i++) {\n    GLImage<T>* input_image = input_images[i];\n    GLImage<T>* output_image = output_images[i];\n    int input_slices = input_image->slices;\n    int output_slices = output_image->slices;\n\n    for (int is = 0; is < input_slices; is += input_batch_size) {\n      for (int os = 0; os < output_slices; os += output_batch_size) {\n        const int output_channels_per_batch =\n            std::min(4 * output_batch_size, geometry.output_channels - 4 * os);\n\n        gl_log(\n            GL_VERBOSE,\n            \"GLConvolution::convolution - is: %d, os: %d\\n\",\n            is,\n            os);\n\n        // Note the order of the binding point needs to be the same as in the\n        // constructor\n        int binding_point = 0;\n\n        // bias\n        attach_uniform_buffer<float16_t>(\n            bias_block, binding_point++, [&](float16_t* data, size_t size) {\n              CAFFE_ENFORCE_GE(\n                  size,\n                  output_channels_per_batch * sizeof(float16_t),\n                  \"Bias buffer size too small\");\n              for (int ob = 0; ob < output_channels_per_batch; ob++) {\n                data[ob] = bias[4 * os + ob];\n              }\n            });\n\n        // kernel weights\n        for (int ib = 0; ib < input_batch_size; ib++) {\n          attach_uniform_buffer<float16_t>(\n              kernel_block[ib],\n              binding_point++,\n              [&](float16_t* data, size_t size) {\n                CAFFE_ENFORCE_EQ(\n                    size,\n                    4 * (4 * output_batch_size) * geometry.kernel_size.y *\n                        geometry.kernel_size.x * sizeof(float16_t),\n                    \"Kernel size mismatch\");\n                pack_kernel_data_for_bached_conv(\n                    data,\n                    size,\n                    input_image->channels,\n                    output_image->channels,\n                    is,\n                    os,\n                    ib);\n              });\n        }\n\n        // PRelu scale\n        if (prelu_scale != nullptr && is == input_slices - input_batch_size) {\n          attach_uniform_buffer<float16_t>(\n              prelu_scale_block,\n              binding_point++,\n              [&](float16_t* data, size_t size) {\n                CAFFE_ENFORCE_GE(\n                    size,\n                    output_channels_per_batch * sizeof(float16_t),\n                    \"PRelu buffer size too small\");\n                for (int ob = 0; ob < output_channels_per_batch; ob++) {\n                  data[ob] = prelu_scale_size == geometry.output_channels\n                      ? prelu_scale[4 * os + ob]\n                      : prelu_scale[0];\n                }\n              });\n        }\n\n        std::vector<texture_attachment> input_attachments;\n        for (int ib = 0; ib < input_batch_size; ib++) {\n          input_attachments.push_back(\n              {input_image->textures[is + ib], inputData[ib]});\n        }\n        for (int ob = 0; ob < output_batch_size; ob++) {\n          input_attachments.push_back(\n              {output_image->textures[os + ob], previousData[ob]});\n        }\n\n        run(input_attachments,\n            {output_image->textures.begin() + os,\n             output_image->textures.begin() + os + output_batch_size},\n            [&]() {\n              glUniform2i(\n                  outputSize->location,\n                  output_image->texture_width,\n                  output_image->texture_height);\n              glUniform2i(inputTileRange->location, 0, 1);\n              glUniform1i(accumulate->location, is != 0);\n              glUniform1i(\n                  fusePRelu->location,\n                  prelu_scale != nullptr &&\n                      (is == input_slices - input_batch_size));\n            },\n            output_image->texture_width,\n            output_image->texture_height);\n      }\n    }\n  }\n}\n\ntemplate <typename T>\nvoid GLConvolution::run_tiled_conv(\n    const GLImageVector<T>& input_images,\n    const GLImageVector<T>& output_images) {\n  for (int i = 0; i < input_images.size(); i++) {\n    GLImage<T>* input_image = input_images[i];\n    GLImage<T>* output_image = output_images[i];\n    int input_slices = input_image->slices;\n    int output_slices = output_image->slices;\n    int input_tile_x = input_image->tile_x;\n    int input_tile_y = input_image->tile_y;\n    int input_tiles = input_image->tile_x * input_image->tile_y;\n    int output_tiles = output_image->tile_x * output_image->tile_y;\n\n    for (int ib = 0, it = 0; it < input_tiles;\n         ib++, it += input_tile_chunk_size) {\n      // Note the order of the binding point needs to be the same as in the\n      // constructor\n      int binding_point = 0;\n\n      // bias\n      attach_uniform_buffer<float16_t>(\n          bias_block, binding_point++, [&](float16_t* data, size_t size) {\n            CAFFE_ENFORCE_GE(\n                size,\n                geometry.output_channels * sizeof(float16_t),\n                \"Bias buffer size too small\");\n            for (int ob = 0; ob < geometry.output_channels; ob++) {\n              data[ob] = bias[ob];\n            }\n          });\n\n      // kernel weights\n      for (int ob = 0, ot = 0; ot < output_tiles;\n           ob++, ot += output_tile_chunk_size) {\n        attach_uniform_buffer<float16_t>(\n            kernel_block[ob],\n            binding_point++,\n            [&](float16_t* data, size_t size) {\n              CAFFE_ENFORCE_EQ(\n                  size,\n                  (4 * input_tile_chunk_size) * (4 * output_tile_chunk_size) *\n                      geometry.kernel_size.y * geometry.kernel_size.x *\n                      sizeof(float16_t),\n                  \"Kernel size mismatch\");\n              pack_kernel_data_for_tiled_conv(\n                  data,\n                  size,\n                  input_image->channels,\n                  output_image->channels,\n                  {it, std::min(it + input_tile_chunk_size, input_tiles)},\n                  {ot, std::min(ot + output_tile_chunk_size, output_tiles)});\n            });\n      }\n\n      // PRelu scale\n      if (prelu_scale != nullptr && ib == input_tile_batch_size - 1) {\n        attach_uniform_buffer<float16_t>(\n            prelu_scale_block,\n            binding_point++,\n            [&](float16_t* data, size_t size) {\n              CAFFE_ENFORCE_GE(\n                  size,\n                  geometry.output_channels * sizeof(float16_t),\n                  \"PRelu buffer size too small\");\n              for (int ob = 0; ob < geometry.output_channels; ob++) {\n                data[ob] = prelu_scale_size == geometry.output_channels\n                    ? prelu_scale[ob]\n                    : prelu_scale[0];\n              }\n            });\n      }\n\n      std::vector<texture_attachment> input_attachments(\n          {{input_image->textures[0], inputData[0]},\n           {output_image->textures[0], previousData[0]}});\n\n      run(input_attachments,\n          {output_image->textures[0]},\n          [&]() {\n            glUniform2i(\n                outputSize->location,\n                output_image->texture_width,\n                output_image->texture_height);\n            // [inputTileFrom, inputTileTo)\n            glUniform2i(\n                inputTileRange->location,\n                it,\n                std::min(it + input_tile_chunk_size, input_tiles));\n\n            glUniform1i(accumulate->location, it != 0);\n            glUniform1i(\n                fusePRelu->location,\n                prelu_scale != nullptr && (ib == input_tile_batch_size - 1));\n          },\n          output_image->texture_width,\n          output_image->texture_height);\n    }\n  }\n}\n\nnamespace caffe2 {\n\ntemplate <typename OPBase>\nstatic void computeOutputHW(OPBase* op, int H, int W, int* OH, int* OW) {\n  Tensor<CPUContext> input, output;\n  input.Resize(1, 1, H, W);\n  op->SetOutputSize(input, &output, 1);\n  CAFFE_ENFORCE_EQ(output.ndim(), 4);\n  *OH = output.dim(2);\n  *OW = output.dim(3);\n}\n\nstatic int computeOutputTileChunkSize(int output_tile_x,\n                                      int output_tile_y,\n                                      int kernel_width,\n                                      int kernel_height) {\n  static const int maxUniformBlockBufferSize = 16 * 1024;\n  return std::min(\n      output_tile_x * output_tile_y,\n      maxUniformBlockBufferSize / 4 /\n          (4 * kernel_width * kernel_height * (int)sizeof(float16_t)));\n}\n\nstatic int computeInputTileChunkSize(\n    int input_tile_x,\n    int input_tile_y,\n    int output_tile_chunk_size,\n    int kernel_width,\n    int kernel_height) {\n  static const int maxUniformBlockBufferSize = 16 * 1024;\n  return std::min(\n      input_tile_x * input_tile_y,\n      maxUniformBlockBufferSize / 4 /\n          (4 * output_tile_chunk_size * kernel_width * kernel_height *\n           (int)sizeof(float16_t)));\n}\n\n// Todo: optimize input/output batch size and use of uniforms/textures for\n// kernel data\nstatic void computeBatchSizes(\n    GLConvolution::descriptor& geometry,\n    int& input_batch_size,\n    int& output_batch_size) {\n  int kernel_size = std::max(geometry.kernel_size.x, geometry.kernel_size.y);\n  int input_slices = (geometry.input_channels + 3) / 4;\n  int output_slices = (geometry.output_channels + 3) / 4;\n\n#if CAFFE2_ANDROID\n  input_batch_size = input_slices % 2 == 0 ? 2 : 1;\n  output_batch_size = output_slices % 2 == 0 ? 2 : 1;\n#else\n  if (iPhoneVersion() >= 8) {\n    // iPhone 6S and up\n    input_batch_size =\n        /* input_slices % 8 == 0 ? 8 : */ input_slices % 4 == 0\n            ? 4\n            : input_slices % 3 == 0 ? 3 : input_slices % 2 == 0 ? 2 : 1;\n    output_batch_size = output_slices % 4 == 0\n        ? 4\n        : output_slices % 3 == 0 ? 3 : output_slices % 2 == 0 ? 2 : 1;\n  }\n#endif\n}\n\ntemplate <class T, bool fusePRelu, bool fuseRelu>\nclass OpenGLConvOp final : public ConvPoolOpBase<CPUContext>, ImageAllocator<T> {\n public:\n  USE_OPERATOR_BASE_FUNCTIONS;\n  OpenGLConvOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(this->order_ == StorageOrder::NCHW, \"OpenGL only supports NCHW order.\");\n    OPERATOR_NEEDS_FEATURE(group_ == 1, \"OpenGL only supports group == 1\");\n    OPERATOR_NEEDS_FEATURE(\n        dilation_h() == 1 && dilation_w() == 1,\n        \"OpenGL only supports dialation == 1\");\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    const GLImageVector<T>& input = Inputs()[INPUT]->template Get<GLImageVector<T>>();\n    auto& filter = Input(FILTER);\n    auto& bias = Input(BIAS);\n\n    const int num_images = input.size();\n    const int input_channels = input.channels();\n    const int input_width = input.width();\n    const int input_height = input.height();\n\n    CAFFE_ENFORCE(filter.ndim(), 4);\n    const int M = filter.dim32(0);\n    const int kernel_width = filter.dim32(2);\n    const int kernel_height = filter.dim32(3);\n\n    CAFFE_ENFORCE(filter.dim32(1) == input_channels, \"\");\n    CAFFE_ENFORCE(filter.dim32(2) == kernel_h(), \"\");\n    CAFFE_ENFORCE(filter.dim32(3) == kernel_w(), \"\");\n    CAFFE_ENFORCE(bias.ndim() == 1, \"\");\n    CAFFE_ENFORCE(bias.dim32(0) == M, \"\");\n\n    int output_height;\n    int output_width;\n    const int output_channels = M;\n    computeOutputHW(this, input_height, input_width, &output_height, &output_width);\n\n    float val = 0;\n    const float* prelu_scale = nullptr;\n    int prelu_scale_size = 0;\n    if (fusePRelu) {\n      auto& prelu = Input(PRELU);\n      prelu_scale = prelu.template data<float>();\n      prelu_scale_size = prelu.size();\n    } else if (fuseRelu) {\n      prelu_scale = &val;\n      prelu_scale_size = 1;\n    }\n\n    const int input_tile_x = input.tile_x(), input_tile_y = input.tile_y();\n    int output_tile_x = 1, output_tile_y = 1;\n    int input_tiles = input_tile_x * input_tile_y, output_tiles = 1;\n    int input_tile_chunk_size = 1, output_tile_chunk_size = 1;\n    int input_tile_batch_size = 1, output_tile_batch_size = 1;\n\n    const bool tiling = GetSingleArgument<int>(\"tiling\", input_tile_x > 1 || input_tile_y > 1);\n\n    if (tiling) {\n      // Turn on tiling\n      CAFFE_ENFORCE_EQ(input.slices(), 1, \"Input needs to be tiled in a single texture\");\n      computeOutputTiles(output_channels, output_tile_x, output_tile_y);\n      output_tiles = output_tile_x * output_tile_y;\n\n      output_tile_chunk_size = computeOutputTileChunkSize(\n          output_tile_x, output_tile_y, kernel_width, kernel_height);\n      output_tile_batch_size = std::max(\n          MaxOutputTileBatchSize,\n          (output_tiles + output_tile_chunk_size - 1) / output_tile_chunk_size);\n      output_tile_chunk_size = (output_tiles + output_tile_batch_size - 1) / output_tile_batch_size;\n      output_tile_batch_size = (output_tiles + output_tile_chunk_size - 1) / output_tile_chunk_size;\n\n      input_tile_chunk_size = computeInputTileChunkSize(\n          input_tile_x,\n          input_tile_y,\n          output_tile_chunk_size,\n          kernel_width,\n          kernel_height);\n      input_tile_batch_size = (input_tiles + input_tile_chunk_size - 1) / input_tile_chunk_size;\n      // input_tile_chunk_size = (input_tiles + input_tile_batch_size - 1) /\n      // input_tile_batch_size;\n    }\n    CAFFE_ENFORCE_GT(input_tile_chunk_size, 0);\n    CAFFE_ENFORCE_GT(output_tile_chunk_size, 0);\n    CAFFE_ENFORCE_LE(output_tile_batch_size, 8);\n\n    int is_last = GetSingleArgument<int>(\"is_last\", 0);\n\n    GLImageVector<T>* output = ImageAllocator<T>::newImage(\n        num_images,\n        output_width,\n        output_height,\n        output_channels,\n        output_tile_x,\n        output_tile_y,\n        is_last);\n\n    // TODO: figure out the dilation business\n    GLConvolution::descriptor geometry{input_channels,\n                                       output_channels,\n                                       {kernel_width, kernel_height},\n                                       {input_width, input_height},\n                                       {output_width, output_height},\n                                       {input_tile_x, input_tile_y},\n                                       {output_tile_x, output_tile_y},\n                                       {pad_l(), pad_t()},\n                                       {stride_w(), stride_h()},\n                                       false};\n\n    if (!conv) {\n      int input_batch_size = 1, output_batch_size = 1;\n      if (!tiling) {\n        computeBatchSizes(geometry, input_batch_size, output_batch_size);\n        input_batch_size =\n            GetSingleArgument<int>(\"input_batch_size\", input_batch_size);\n        output_batch_size = GetSingleArgument<int>(\"output_batch_size\", output_batch_size);\n      }\n\n      LOG(INFO) << input_channels << \": \" << input_height << \" X \"\n                << input_width << \" => \" << output_channels << \": \"\n                << output_height << \" X \" << output_width\n                << \" Kernel: \" << kernel_width << \"X\" << kernel_height;\n      if (tiling) {\n        LOG(INFO) << \"Tiling: \" << input_tile_x << \" X \" << input_tile_y\n                  << \" => \" << output_tile_x << \" X \" << output_tile_y\n                  << \", Texture size: \" << input_width * input_tile_x << \" X \"\n                  << input_height * input_tile_y << \" => \"\n                  << output_width * output_tile_x << \" X \"\n                  << output_height * output_tile_y\n                  << \", Input tile batch size: \" << input_tile_batch_size;\n      } else {\n        LOG(INFO) << \"input_batch_size = \" << input_batch_size\n                  << \", output_batch_size = \" << output_batch_size;\n      }\n\n      conv.reset(new GLConvolution(geometry,\n                                   filter.template data<float>(),\n                                   bias.template data<float>(),\n                                   prelu_scale,\n                                   prelu_scale_size,\n                                   input_batch_size,\n                                   output_batch_size,\n                                   input_tiles,\n                                   output_tiles,\n                                   input_tile_chunk_size,\n                                   output_tile_chunk_size,\n                                   input_tile_batch_size,\n                                   output_tile_batch_size,\n                                   tiling));\n    }\n\n    conv->convolution(input, *output);\n\n    Outputs()[0]->Reset(output);\n\n    return true;\n  }\n\n private:\n  std::unique_ptr<GLConvolution> conv;\n\n  INPUT_TAGS(INPUT, FILTER, BIAS, PRELU);\n};\n\nREGISTER_CPU_OPERATOR(OpenGLConv, OpenGLConvOp<float16_t, false, false>);\nOPERATOR_SCHEMA(OpenGLConv).NumInputs(3).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(OpenGLConvPRelu, OpenGLConvOp<float16_t, true, false>);\nOPERATOR_SCHEMA(OpenGLConvPRelu).NumInputs(4).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(OpenGLConvRelu, OpenGLConvOp<float16_t, false, true>);\nOPERATOR_SCHEMA(OpenGLConvRelu).NumInputs(3).NumOutputs(1);\n\ntemplate <class T, bool fusePRelu, bool fuseRelu>\nclass OpenGLConvTransposeOp final : public ConvTransposeUnpoolBase<CPUContext>, ImageAllocator<T> {\n public:\n  USE_OPERATOR_BASE_FUNCTIONS;\n  OpenGLConvTransposeOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvTransposeUnpoolBase<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(this->order_ == StorageOrder::NCHW, \"OpenGL only supports NCHW order.\");\n    OPERATOR_NEEDS_FEATURE(\n        adj_h() == 0 && adj_w() == 0,\n        \"OpenGL only supports adj_h == 1 and adj_w == 1\");\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    const GLImageVector<T>& input = Inputs()[INPUT]->template Get<GLImageVector<T>>();\n    auto& filter = Input(FILTER);\n    auto& bias = Input(BIAS);\n\n    const int num_images = input.size();\n    const int input_channels = input.channels();\n    const int input_width = input.width();\n    const int input_height = input.height();\n\n    CAFFE_ENFORCE(filter.ndim() == 4, \"filter must be 4D tensor\");\n    const int M = filter.dim32(0);\n    const int C = filter.dim32(1);\n    const int kernel_width = filter.dim32(2);\n    const int kernel_height = filter.dim32(3);\n\n    CAFFE_ENFORCE(input_channels == M, \"filter number must be equal to input channel number\");\n    CAFFE_ENFORCE(filter.dim32(2) == kernel_h(), \"filter height must be equal to kernel height\");\n    CAFFE_ENFORCE(filter.dim32(3) == kernel_w(), \"filter width must be equal to kernel width\");\n    CAFFE_ENFORCE(bias.ndim() == 1, \"bias must be 1D tensor\");\n    CAFFE_ENFORCE(bias.dim32(0) == C, \"bias dimension must be equal to output channel number\");\n\n    int output_height;\n    int output_width;\n    const int output_channels = C;\n    computeOutputHW(this, input_height, input_width, &output_height, &output_width);\n\n    float val = 0;\n    const float* prelu_scale = nullptr;\n    int prelu_scale_size = 0;\n    if (fusePRelu) {\n      auto& prelu = Input(PRELU);\n      prelu_scale = prelu.template data<float>();\n      prelu_scale_size = prelu.size();\n    } else if (fuseRelu) {\n      prelu_scale = &val;\n      prelu_scale_size = 1;\n    }\n\n    const int input_tile_x = input.tile_x(), input_tile_y = input.tile_y();\n    int output_tile_x = 1, output_tile_y = 1;\n    int input_tiles = input_tile_x * input_tile_y, output_tiles = 1;\n    int input_tile_chunk_size = 1, output_tile_chunk_size = 1,\n        input_tile_batch_size = 1, output_tile_batch_size = 1;\n\n    const bool tiling = GetSingleArgument<int>(\"tiling\", input_tile_x > 1 || input_tile_y > 1);\n\n    if (tiling) {\n      // Turn on tiling\n      CAFFE_ENFORCE_EQ(input.slices(), 1, \"Input needs to be tiled in a single texture\");\n      computeOutputTiles(output_channels, output_tile_x, output_tile_y);\n      output_tiles = output_tile_x * output_tile_y;\n\n      output_tile_chunk_size = computeOutputTileChunkSize(\n          output_tile_x, output_tile_y, kernel_width, kernel_height);\n      output_tile_batch_size = std::max(\n          MaxOutputTileBatchSize,\n          (output_tiles + output_tile_chunk_size - 1) / output_tile_chunk_size);\n      output_tile_chunk_size = (output_tiles + output_tile_batch_size - 1) / output_tile_batch_size;\n      output_tile_batch_size = (output_tiles + output_tile_chunk_size - 1) / output_tile_chunk_size;\n\n      input_tile_chunk_size = computeInputTileChunkSize(\n          input_tile_x,\n          input_tile_y,\n          output_tile_chunk_size,\n          kernel_width,\n          kernel_height);\n      input_tile_batch_size = (input_tiles + input_tile_chunk_size - 1) / input_tile_chunk_size;\n      // input_tile_chunk_size = (input_tiles + input_tile_batch_size - 1) /\n      // input_tile_batch_size;\n    }\n    CAFFE_ENFORCE_GT(input_tile_chunk_size, 0);\n    CAFFE_ENFORCE_GT(output_tile_chunk_size, 0);\n    CAFFE_ENFORCE_LE(output_tile_batch_size, 8);\n\n    int is_last = GetSingleArgument<int>(\"is_last\", 0);\n\n    GLImageVector<T>* output = ImageAllocator<T>::newImage(\n        num_images,\n        output_width,\n        output_height,\n        output_channels,\n        output_tile_x,\n        output_tile_y,\n        is_last);\n\n    // TODO: figure out the adj business\n    GLConvolution::descriptor geometry{input_channels,\n                                       output_channels,\n                                       {kernel_width, kernel_height},\n                                       {input_width, input_height},\n                                       {output_width, output_height},\n                                       {input_tile_x, input_tile_y},\n                                       {output_tile_x, output_tile_y},\n                                       {pad_l(), pad_t()},\n                                       {stride_w(), stride_h()},\n                                       true};\n\n    if (!conv) {\n      int input_batch_size = 1, output_batch_size = 1;\n      if (!tiling) {\n        computeBatchSizes(geometry, input_batch_size, output_batch_size);\n        input_batch_size =\n            GetSingleArgument<int>(\"input_batch_size\", input_batch_size);\n        output_batch_size = GetSingleArgument<int>(\"output_batch_size\", output_batch_size);\n      }\n\n      LOG(INFO) << input_channels << \": \" << input_height << \" X \"\n                << input_width << \" => \" << output_channels << \": \"\n                << output_height << \" X \" << output_width\n                << \" Kernel: \" << kernel_width << \"X\" << kernel_height;\n\n      if (tiling) {\n        LOG(INFO) << \"Tiling: \" << input_tile_x << \" X \" << input_tile_y\n                  << \" => \" << output_tile_x << \" X \" << output_tile_y\n                  << \", Texture size: \" << input_width * input_tile_x << \" X \"\n                  << input_height * input_tile_y << \" => \"\n                  << output_width * output_tile_x << \" X \"\n                  << output_height * output_tile_y\n                  << \", Input tile batch size: \" << input_tile_batch_size;\n      } else {\n        LOG(INFO) << \"input_batch_size = \" << input_batch_size\n                  << \", output_batch_size = \" << output_batch_size;\n      }\n\n      conv.reset(new GLConvolution(geometry,\n                                   filter.template data<float>(),\n                                   bias.template data<float>(),\n                                   prelu_scale,\n                                   prelu_scale_size,\n                                   input_batch_size,\n                                   output_batch_size,\n                                   input.tile_x() * input.tile_y(),\n                                   output->tile_x() * output->tile_y(),\n                                   input_tile_chunk_size,\n                                   output_tile_chunk_size,\n                                   input_tile_batch_size,\n                                   output_tile_batch_size,\n                                   tiling));\n    }\n\n    conv->convolution(input, *output);\n\n    Outputs()[0]->Reset(output);\n\n    return true;\n  }\n\n private:\n  std::unique_ptr<GLConvolution> conv;\n\n  INPUT_TAGS(INPUT, FILTER, BIAS, PRELU);\n};\n\nREGISTER_CPU_OPERATOR(OpenGLConvTranspose, OpenGLConvTransposeOp<float16_t, false, false>);\nOPERATOR_SCHEMA(OpenGLConvTranspose).NumInputs(3).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(OpenGLConvTransposePRelu, OpenGLConvTransposeOp<float16_t, true, false>);\nOPERATOR_SCHEMA(OpenGLConvTransposePRelu).NumInputs(4).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(OpenGLConvTransposeRelu, OpenGLConvTransposeOp<float16_t, false, true>);\nOPERATOR_SCHEMA(OpenGLConvTransposeRelu).NumInputs(3).NumOutputs(1);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLConvolution.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n#include \"gl_tiling_utils.h\"\n\nclass GLConvolution : public GLFilter {\n public:\n  static constexpr int MaxInputBatchSize = 8;\n  static constexpr int MaxOutputBatchSize = 4;\n\n  struct descriptor {\n    int input_channels;\n    int output_channels;\n    point kernel_size;\n    point input_tile_size;\n    point output_tile_size;\n    point input_tile_grid_size;\n    point output_tile_grid_size;\n    point input_padding;\n    point input_stride;\n    bool transposed;\n  };\n\n  const float* kernel;\n  const float* bias;\n  const float* prelu_scale;\n\n  binding* inputData[MaxInputBatchSize];\n  binding* previousData[MaxOutputBatchSize];\n  binding* outputSize;\n  binding* accumulate;\n  binding* fusePRelu;\n  binding* kernel_block[MaxInputBatchSize];\n  binding* bias_block;\n  binding* prelu_scale_block;\n  binding* inputTileRange;\n\n  const descriptor geometry;\n  const int prelu_scale_size;\n  const int input_batch_size;\n  const int output_batch_size;\n  const int input_tiles;\n  const int output_tiles;\n  const int input_tile_chunk_size;\n  const int output_tile_chunk_size;\n  const int input_tile_batch_size;\n  const int output_tile_batch_size;\n  const bool tiling;\n\n  static const char* fragment_shader;\n\n  GLConvolution(\n      const descriptor& _geometry,\n      const float* _kernel,\n      const float* _bias,\n      const float* _prelu_scale = nullptr,\n      int _prelu_scale_size = 0,\n      int _input_batch_size = 1,\n      int _output_batch_size = 1,\n      int _input_tiles = 1,\n      int _output_tiles = 1,\n      int _input_tile_chunk_size = 1,\n      int _output_tile_chunk_size = 1,\n      int _input_tile_batch_size = 1,\n      int _output_tile_batch_size = 1,\n      bool _tiling = false)\n      : GLFilter(\n            \"GLConvolution\",\n            vertex_shader,\n            fragment_shader,\n            input_bindings(_input_batch_size, _output_batch_size),\n            uniform_blocks_bindings(\n                _input_batch_size,\n                _output_batch_size,\n                _output_tile_batch_size,\n                _prelu_scale != nullptr),\n            {/* no attributes */},\n            {{\"KERNEL_SIZE_X\", caffe2::to_string(_geometry.kernel_size.x)},\n             {\"KERNEL_SIZE_Y\", caffe2::to_string(_geometry.kernel_size.y)},\n             {\"INPUT_BATCH_SIZE\", caffe2::to_string(_input_batch_size)},\n             {\"OUTPUT_BATCH_SIZE\", caffe2::to_string(_output_batch_size)},\n             {\"INPUT_TILES\", caffe2::to_string(_input_tiles)},\n             {\"OUTPUT_TILES\", caffe2::to_string(_output_tiles)},\n             {\"INPUT_TILE_WIDTH\",\n              caffe2::to_string(_geometry.input_tile_size.x)},\n             {\"INPUT_TILE_HEIGHT\",\n              caffe2::to_string(_geometry.input_tile_size.y)},\n             {\"OUTPUT_TILE_WIDTH\",\n              caffe2::to_string(_geometry.output_tile_size.x)},\n             {\"OUTPUT_TILE_HEIGHT\",\n              caffe2::to_string(_geometry.output_tile_size.y)},\n             {\"INPUT_TILE_X\",\n              caffe2::to_string(_geometry.input_tile_grid_size.x)},\n             {\"OUTPUT_TILE_X\",\n              caffe2::to_string(_geometry.output_tile_grid_size.x)},\n             {\"INPUT_TILE_CHUNK_SIZE\",\n              caffe2::to_string(_input_tile_chunk_size)},\n             {\"OUTPUT_TILE_CHUNK_SIZE\",\n              caffe2::to_string(_output_tile_chunk_size)},\n             {\"OUTPUT_TILE_BATCH_SIZE\",\n              caffe2::to_string(_output_tile_batch_size)},\n             {\"TILED_CONVOLUTION\", caffe2::to_string(_tiling)},\n             {\"INPUT_PADDING_X\",\n              caffe2::to_string(\n                  _geometry.transposed\n                      ? _geometry.kernel_size.x - 1 - _geometry.input_padding.x\n                      : _geometry.input_padding.x)},\n             {\"INPUT_PADDING_Y\",\n              caffe2::to_string(\n                  _geometry.transposed\n                      ? _geometry.kernel_size.y - 1 - _geometry.input_padding.y\n                      : _geometry.input_padding.y)},\n             {\"INPUT_STRIDE_X\", caffe2::to_string(_geometry.input_stride.x)},\n             {\"INPUT_STRIDE_Y\", caffe2::to_string(_geometry.input_stride.y)},\n             {\"TRANSPOSED_CONVOLUTION\",\n              caffe2::to_string(_geometry.transposed)},\n             {\"BOUNDS_CHECK_MODE\",\n              caffe2::to_string(bounds_check_mode(_tiling, _geometry))}}),\n        kernel(_kernel),\n        bias(_bias),\n        prelu_scale(_prelu_scale),\n        geometry(_geometry),\n        prelu_scale_size(_prelu_scale_size),\n        input_batch_size(_input_batch_size),\n        output_batch_size(_output_batch_size),\n        input_tiles(_input_tiles),\n        output_tiles(_output_tiles),\n        input_tile_chunk_size(_input_tile_chunk_size),\n        output_tile_chunk_size(_output_tile_chunk_size),\n        input_tile_batch_size(_input_tile_batch_size),\n        output_tile_batch_size(_output_tile_batch_size),\n        tiling(_tiling) {}\n\n  ~GLConvolution() {}\n\n  template <typename T>\n  void convolution(\n      const GLImageVector<T>& input_images,\n      const GLImageVector<T>& output_images);\n\n private:\n  /*\n   * Computes BOUNDS_CHECK_MODE for the convolution parameters.\n   *\n   * @retval 0 if bounds check can be skipped\n   * @retval non-zero if bounds check can not be skipped\n   */\n  inline static int bounds_check_mode(bool tiling, const descriptor& geometry) {\n    if (tiling) {\n      return 1;\n    }\n\n    int input_padding_x = geometry.input_padding.x,\n        input_padding_y = geometry.input_padding.y;\n    if (geometry.transposed) {\n      input_padding_x = geometry.kernel_size.x - 1 - input_padding_x;\n      input_padding_y = geometry.kernel_size.y - 1 - input_padding_y;\n    }\n\n    if (GLContext::getGLContext()->GL_EXT_texture_border_clamp_defined() ||\n        (input_padding_x == 0 && input_padding_y == 0)) {\n      return 0;\n    } else {\n      return 1;\n    }\n  }\n\n  const std::vector<binding*> input_bindings(\n      int input_batch_size,\n      int output_batch_size) {\n    std::vector<binding*> bindings({BINDING(outputSize),\n                                    BINDING(accumulate),\n                                    BINDING(fusePRelu),\n                                    BINDING(inputTileRange)});\n\n    for (int i = 0; i < input_batch_size; i++) {\n      bindings.push_back(\n          inputData[i] =\n              new binding{\"inputData[\" + caffe2::to_string(i) + \"]\"});\n    }\n\n    for (int i = 0; i < output_batch_size; i++) {\n      bindings.push_back(\n          previousData[i] =\n              new binding{\"previousData[\" + caffe2::to_string(i) + \"]\"});\n    }\n\n    return bindings;\n  }\n\n  const std::vector<binding*> uniform_blocks_bindings(\n      int input_batch_size,\n      int output_batch_size,\n      int output_tile_batch_size,\n      bool fuse_prelu) {\n    std::vector<binding*> bindings({BINDING(bias_block)});\n    if (fuse_prelu) {\n      bindings.push_back(BINDING(prelu_scale_block));\n    }\n\n    for (int i = 0; i < std::max(input_batch_size, output_tile_batch_size);\n         i++) {\n      bindings.push_back(\n          kernel_block[i] =\n              new binding{\"Kernel_block[\" + caffe2::to_string(i) + \"]\"});\n    }\n\n    return bindings;\n  }\n\n  void pack_kernel_data_for_bached_conv(\n      float16_t* data,\n      size_t size,\n      int input_channels,\n      int output_channels,\n      int is,\n      int os,\n      int ib);\n\n  void pack_kernel_data_for_tiled_conv(\n      float16_t* data, // destination\n      size_t size,\n      int input_channels,\n      int output_channels,\n      point input_tile_range,\n      point output_tile_range);\n\n  template <typename T>\n  void run_batched_conv(\n      const GLImageVector<T>& input_images,\n      const GLImageVector<T>& output_images);\n\n  template <typename T>\n  void run_tiled_conv(\n      const GLImageVector<T>& input_images,\n      const GLImageVector<T>& output_images);\n};\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLCopyOps.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n\n#include \"../core/DataTransfer.h\"\n#include \"../core/GLContext.h\"\n#include \"../core/GLImage.h\"\n#include \"../core/GLPlainTexture.h\"\n#include \"../core/ImageAllocator.h\"\n\n#include <algorithm>\n\nnamespace caffe2 {\ntemplate <class T>\nclass CopyToOpenGLOp final : public Operator<CPUContext>, ImageAllocator<T> {\n public:\n  CopyToOpenGLOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    // caffe2::Timer timer;\n    const TensorCPU& X = Input(0);\n    const int num_images = X.dim32(0);\n    const int input_channels = X.dim32(1);\n    const int input_width = X.dim32(3);\n    const int input_height = X.dim32(2);\n    const int input_size = input_width * input_height;\n\n    // set up the OpenGL context\n    GLContext::getGLContext()->set_context();\n\n    const float* input = X.template data<float>();\n\n    int tile_x = GetSingleArgument<int>(\"tile_x\", 1);\n    int tile_y = GetSingleArgument<int>(\"tile_y\", 1);\n\n    GLImageVector<T>* output_image = ImageAllocator<T>::newImage(num_images,\n                                                                 input_width,\n                                                                 input_height,\n                                                                 input_channels,\n                                                                 tile_x,\n                                                                 tile_y,\n#if CAFFE2_IOS\n                                                                 true\n#else\n                                                                 false\n#endif\n    );\n\n    if (output_image->tile_x() > 1 || output_image->tile_y() > 1) {\n      LOG(INFO) << \"CopyToOpenGLOp tiling: \" << output_image->tile_x() << \":\"\n                << output_image->tile_y();\n    }\n\n    Outputs()[0]->Reset(output_image);\n\n    for (int i = 0; i < num_images; i++) {\n      const auto textures = (*output_image)[i]->textures;\n      for (int slice = 0; slice < textures.size(); slice++) {\n        // timer.Start();\n\n        textures[slice]->map_load([&](void* buffer,\n                                      size_t width,\n                                      size_t height,\n                                      size_t stride,\n                                      size_t channels,\n                                      const GLTexture::Type& type) {\n          for (int y = 0; y < tile_y; y++) {\n            for (int x = 0; x < tile_x; x++) {\n              const int tiles = slice * tile_x * tile_y + y * tile_x + x;\n              const int slice_channels = std::min(4, input_channels - 4 * tiles);\n              interleaveSlice(\n                  (float16_t*)buffer + 4 * (y * input_height * stride + x * input_width),\n                  &input[i * input_channels * input_size + 4 * tiles * input_size],\n                  input_width,\n                  input_height,\n                  stride, // texture stride\n                  slice_channels);\n            }\n          }\n        });\n        // LOG(INFO) << \"Texture uploading takes \" << timer.MilliSeconds() << \" ms\";\n      }\n    }\n\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(CopyToOpenGL, CopyToOpenGLOp<float16_t>);\nOPERATOR_SCHEMA(CopyToOpenGL).NumInputs(1).NumOutputs(1).AllowInplace({{0, 0}});\n\ntemplate <class T>\nclass CopyFromOpenGLOp final : public Operator<CPUContext> {\n public:\n  CopyFromOpenGLOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    caffe2::Timer timer;\n    const GLImageVector<T>& X = Inputs()[0]->template Get<GLImageVector<T>>();\n    const int num_images = X.size();\n    const int input_channels = X.channels();\n    const int input_width = X.width();\n    const int input_height = X.height();\n\n    TensorCPU* Y = Output(0);\n    Y->Resize(num_images, input_channels, input_height, input_width);\n    const int output_width = input_width;\n    const int output_height = input_height;\n    const int output_size = input_width * input_height;\n\n    float* output = Y->mutable_data<float>();\n\n    const int tile_x = X.tile_x();\n    const int tile_y = X.tile_y();\n    for (int i = 0; i < num_images; i++) {\n      for (int slice = 0; slice < X[i]->slices; slice++) {\n        timer.Start();\n        const GLTexture* texture = X[i]->textures[slice];\n\n        texture->map_read([&](const void* buffer,\n                              size_t width,\n                              size_t height,\n                              size_t stride,\n                              size_t channels,\n                              const GLTexture::Type& type) {\n          //#if CAFFE2_ANDROID && defined(__ARM_NEON__)\n          //        if (static_cast<AndroidGLContext*>(GLContext::getGLContext())->get_platform() ==\n          //        Mali) {\n          //          caffe2::Timer timer;\n          //          timer.Start();\n          //          float16_t* copy_buffer = (float16_t*)malloc(_capacity);\n          //          arm_memcpy(\n          //              (volatile unsigned char*)copy_buffer, (volatile unsigned char*)buffer,\n          //              _capacity);\n          //          deInterleaveSlice(\n          //              output + 4 * slice * output_size, copy_buffer, width, height, stride,\n          //              slice_channels);\n          //          free(copy_buffer);\n          //          LOG(INFO) << \"memcpy takes \" << timer.MilliSeconds() << \" ms\";\n          //        } else\n          //#endif\n          {\n            gl_log(GL_VERBOSE,\n                   \"calling deInterleaveSlice width: %d, height: %d, stride: %d, channels: %d\\n\",\n                   width,\n                   height,\n                   stride,\n                   channels);\n\n            for (int y = 0; y < tile_y; y++) {\n              for (int x = 0; x < tile_x; x++) {\n                const int tiles = slice * tile_x * tile_y + y * tile_x + x;\n                const int slice_channels = std::min(4, input_channels - 4 * tiles);\n                deInterleaveSlice(\n                    output + i * input_channels * output_size + 4 * tiles * output_size,\n                    (float16_t*)buffer + 4 * (y * input_height * stride + x * input_width),\n                    input_width,\n                    input_height,\n                    stride,\n                    slice_channels);\n              }\n            }\n          }\n        });\n      }\n    }\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(CopyFromOpenGL, CopyFromOpenGLOp<float16_t>);\nOPERATOR_SCHEMA(CopyFromOpenGL).NumInputs(1).NumOutputs(1).AllowInplace({{0, 0}});\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLInstanceNorm.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n#include \"../core/ImageAllocator.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include <iostream>\n#include <vector>\n\nclass GLReduce : public GLFilter {\n public:\n  binding* inputSize;\n  binding* outputSize;\n  binding* tileSize;\n  binding* inv_pixel_count;\n  binding* epsilon;\n  binding* inputData;\n  binding* averageData;\n\n  bool compute_inv_stdev;\n  bool compute_norm;\n\n  const std::vector<binding*> input_bindings(bool compute_norm_) {\n    std::vector<binding*> bindings({BINDING(inputSize),\n                                    BINDING(outputSize),\n                                    BINDING(tileSize),\n                                    BINDING(inv_pixel_count),\n                                    BINDING(epsilon),\n                                    BINDING(inputData)});\n    if (compute_norm_) {\n      bindings.push_back(BINDING(averageData));\n    }\n    return bindings;\n  }\n\n  GLReduce(bool compute_inv_stdev_ = false, bool compute_norm_ = false)\n      : GLFilter(\"GLReduce\",\n                 vertex_shader,\n                 fragment_shader,\n                 input_bindings(compute_norm_),\n                 {/* no uniform_blocks_bindings */},\n                 {/* no attributes */},\n                 {{\"COMPUTE_INV_STDEV\", caffe2::to_string((int)compute_inv_stdev_)},\n                  {\"COMPUTE_NORM\", caffe2::to_string((int)compute_norm_)}}),\n        compute_inv_stdev(compute_inv_stdev_),\n        compute_norm(compute_norm_) {}\n\n  template <typename T>\n  void reduce(const GLImage<T>* input_image,\n              const GLImage<T>* output_image,\n              int tile_size_x,\n              int tile_size_y,\n              float inv_pixel_count_ = 1.0,\n              float epsilon_ = 0.0);\n\n  template <typename T>\n  void norm(const GLImage<T>* input_image,\n            const GLImage<T>* avg_image,\n            const GLImage<T>* output_image,\n            int tile_size_x,\n            int tile_size_y,\n            float inv_pixel_count_);\n\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\n\nconst char* GLReduce::fragment_shader = R\"GLSL(#version 300 es\n\n#define COMPUTE_INV_STDEV $(COMPUTE_INV_STDEV)\n#define COMPUTE_NORM $(COMPUTE_NORM)\n\nprecision mediump float;\nprecision mediump int;\n\nin highp vec2 v_texCoord;\n\nuniform ivec2 inputSize;\nuniform ivec2 outputSize;\nuniform ivec2 tileSize;\nuniform float inv_pixel_count;\nuniform float epsilon;\n\n#if COMPUTE_NORM\nTEXTURE_INPUT(averageData);\n#endif\n\nTEXTURE_INPUT(inputData);\nTEXTURE_OUTPUT(0, outputData);\n\nvoid main() {\n  ivec2 outputCoord = ivec2(v_texCoord * vec2(outputSize));\n  ivec2 texelCoord = outputCoord * tileSize;\n  ivec2 sumArea = min(tileSize, inputSize - texelCoord);\n  highp vec4 sum = vec4(0.0);\n\n#if COMPUTE_NORM\n  vec4 avg = TEXTURE_LOAD(averageData, ivec2(0));\n#endif\n\n  for (int y = 0; y < sumArea.y; y++) {\n    for (int x = 0; x < sumArea.x; x++) {\n      ivec2 idx = texelCoord + ivec2(x, y);\n      vec4 val = TEXTURE_LOAD(inputData, idx);\n#if COMPUTE_NORM\n      val -= avg;\n      sum += val * val;\n#else\n      sum += val;\n#endif\n    }\n  }\n\n#if COMPUTE_INV_STDEV\n  outputData = TEXTURE_STORE(inversesqrt(sum * vec4(inv_pixel_count) + vec4(epsilon)));\n#elif COMPUTE_NORM\n  outputData = TEXTURE_STORE(sum * vec4(inv_pixel_count));\n#else\n  outputData = TEXTURE_STORE(sum * vec4(inv_pixel_count) + vec4(epsilon));\n#endif\n}\n\n)GLSL\";\n\ntemplate <typename T>\nvoid GLReduce::reduce(const GLImage<T>* input_image,\n                      const GLImage<T>* output_image,\n                      int tile_size_x,\n                      int tile_size_y,\n                      float inv_pixel_count_,\n                      float epsilon_) {\n  int input_slices = input_image->slices;\n  int output_slices = output_image->slices;\n\n  for (int is = 0; is < input_slices; is++) {\n    std::vector<texture_attachment> input_attachments({{input_image->textures[is], inputData}});\n\n    run(input_attachments,\n        {output_image->textures.begin() + is, output_image->textures.begin() + is + 1},\n        [&]() {\n          glUniform2i(inputSize->location, input_image->width, input_image->height);\n          glUniform2i(outputSize->location, output_image->width, output_image->height);\n          glUniform2i(tileSize->location, tile_size_x, tile_size_y);\n          glUniform1f(inv_pixel_count->location, inv_pixel_count_);\n          glUniform1f(epsilon->location, epsilon_);\n        },\n        output_image->width,\n        output_image->height);\n  }\n}\n\ntemplate <typename T>\nvoid GLReduce::norm(const GLImage<T>* input_image,\n                    const GLImage<T>* avg_image,\n                    const GLImage<T>* output_image,\n                    int tile_size_x,\n                    int tile_size_y,\n                    float inv_pixel_count_) {\n  int input_slices = input_image->slices;\n  int output_slices = output_image->slices;\n\n  for (int is = 0; is < input_slices; is++) {\n    std::vector<texture_attachment> input_attachments(\n        {{input_image->textures[is], inputData}, {avg_image->textures[is], averageData}});\n\n    run(input_attachments,\n        {output_image->textures.begin() + is, output_image->textures.begin() + is + 1},\n        [&]() {\n          glUniform2i(inputSize->location, input_image->width, input_image->height);\n          glUniform2i(outputSize->location, output_image->width, output_image->height);\n          glUniform2i(tileSize->location, tile_size_x, tile_size_y);\n          glUniform1f(inv_pixel_count->location, inv_pixel_count_);\n        },\n        output_image->width,\n        output_image->height);\n  }\n}\n\nclass GLScale : public GLFilter {\n public:\n  binding* outputSize;\n  binding* inputData;\n  binding* averageData;\n  binding* normData;\n\n  binding* scale_factor;\n  binding* bias_factor;\n  binding* prelu_scale_factor;\n\n  const int channels;\n  const float* scale;\n  const float* bias;\n  const float* prelu_scale;\n  const int prelu_size;\n\n  const std::vector<binding*> input_bindings(bool fuse_prelu) {\n    std::vector<binding*> bindings({BINDING(outputSize),\n                                    BINDING(scale_factor),\n                                    BINDING(bias_factor),\n                                    BINDING(inputData),\n                                    BINDING(averageData),\n                                    BINDING(normData)});\n    if (fuse_prelu) {\n      bindings.push_back(prelu_scale_factor = new binding({\"prelu_scale_factor\"}));\n    }\n    return bindings;\n  }\n\n  GLScale(const int _channels,\n          const float* _scale,\n          const float* _bias,\n          const float* _prelu_scale = nullptr,\n          const int _prelu_size = 0)\n      : GLFilter(\"GLScale\",\n                 vertex_shader,\n                 fragment_shader,\n                 input_bindings(_prelu_scale != nullptr),\n                 {/* no uniform blocks */},\n                 {/* no attributes */},\n                 {{\"FUSE_PRELU\", caffe2::to_string(_prelu_scale != nullptr)}}),\n        channels(_channels),\n        scale(_scale),\n        bias(_bias),\n        prelu_scale(_prelu_scale),\n        prelu_size(_prelu_size) {}\n\n  template <typename T>\n  void scale_and_shift(const GLImage<T>* input_image,\n                       const GLImage<T>* avg_image,\n                       const GLImage<T>* norm_image,\n                       const GLImage<T>* output_image);\n\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\n\nconst char* GLScale::fragment_shader = R\"GLSL(#version 300 es\n\n#define FUSE_PRELU $(FUSE_PRELU)\n\nprecision mediump float;\nprecision mediump int;\n\nin highp vec2 v_texCoord;\nuniform ivec2 outputSize;\nuniform vec4 scale_factor;\nuniform vec4 bias_factor;\n\n#if FUSE_PRELU\nuniform vec4 prelu_scale_factor;\n#endif\n\nTEXTURE_INPUT(inputData);\nTEXTURE_INPUT(averageData);\nTEXTURE_INPUT(normData);\nTEXTURE_OUTPUT(0, outputData);\n\nvoid main() {\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n\n  vec4 val = TEXTURE_LOAD(inputData, texelCoord);\n  vec4 avg = TEXTURE_LOAD(averageData, ivec2(0));\n  vec4 inv_stdev = TEXTURE_LOAD(normData, ivec2(0));\n\n#if FUSE_PRELU\n  vec4 result = (val - avg) * inv_stdev * scale_factor + bias_factor;\n  vec4 o = mix(result * prelu_scale_factor, result, vec4(greaterThan(result, vec4(0))));\n  outputData = TEXTURE_STORE(o);\n#else\n  vec4 o = (val - avg) * inv_stdev * scale_factor + bias_factor;\n  outputData = TEXTURE_STORE(o);\n#endif\n}\n\n)GLSL\";\n\ntemplate <typename T>\nvoid GLScale::scale_and_shift(const GLImage<T>* input_image,\n                              const GLImage<T>* avg_image,\n                              const GLImage<T>* norm_image,\n                              const GLImage<T>* output_image) {\n  int input_slices = input_image->slices;\n  int output_slices = output_image->slices;\n\n  for (int is = 0; is < input_slices; is++) {\n    std::vector<texture_attachment> input_attachments({{input_image->textures[is], inputData},\n                                                       {avg_image->textures[is], averageData},\n                                                       {norm_image->textures[is], normData}});\n\n    run(input_attachments,\n        {output_image->textures.begin() + is, output_image->textures.begin() + is + 1},\n        [&]() {\n          glUniform2i(outputSize->location, output_image->width, output_image->height);\n          glUniform4f(scale_factor->location,\n                      scale[4 * is],\n                      channels > 4 * is + 1 ? scale[4 * is + 1] : 0,\n                      channels > 4 * is + 2 ? scale[4 * is + 2] : 0,\n                      channels > 4 * is + 3 ? scale[4 * is + 3] : 0);\n          glUniform4f(bias_factor->location,\n                      bias[4 * is],\n                      channels > 4 * is + 1 ? bias[4 * is + 1] : 0,\n                      channels > 4 * is + 2 ? bias[4 * is + 2] : 0,\n                      channels > 4 * is + 3 ? bias[4 * is + 3] : 0);\n          if (prelu_scale != nullptr) {\n            glUniform4f(prelu_scale_factor->location,\n                        prelu_size == channels ? prelu_scale[4 * is] : prelu_scale[0],\n                        channels > 4 * is + 1 && prelu_size == channels ? prelu_scale[4 * is + 1]\n                                                                        : prelu_scale[0],\n                        channels > 4 * is + 2 && prelu_size == channels ? prelu_scale[4 * is + 2]\n                                                                        : prelu_scale[0],\n                        channels > 4 * is + 3 && prelu_size == channels ? prelu_scale[4 * is + 3]\n                                                                        : prelu_scale[0]);\n          }\n        },\n        output_image->width,\n        output_image->height);\n  }\n}\n\nnamespace caffe2 {\ntemplate <class T, bool FUSE_PRELU>\nclass OpenGLInstanceNormPReluOp final : public Operator<CPUContext>, ImageAllocator<T> {\n public:\n  OpenGLInstanceNormPReluOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        epsilon_(OperatorBase::GetSingleArgument<float>(\"epsilon\", 1e-5)),\n        order_(StringToStorageOrder(OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CAFFE_ENFORCE(epsilon_ >= 0, \"Must pass a nonnegative epsilon.\");\n    OPERATOR_NEEDS_FEATURE(this->order_ == StorageOrder::NCHW, \"Metal only supports NCHW order.\");\n  }\n\n  bool RunOnDevice() override {\n    const GLImageVector<T>& input = Inputs()[INPUT]->template Get<GLImageVector<T>>();\n    const int num_images = input.size();\n    const int input_channels = input.channels();\n    const int input_width = input.width();\n    const int input_height = input.height();\n\n    const int output_channels = input_channels;\n    const int output_width = input_width;\n    const int output_height = input_height;\n\n    int is_last = OperatorBase::GetSingleArgument<int>(\"is_last\", 0);\n\n    const int tile_size_x = 16;\n    const int tile_size_y = 16;\n    int avg_buf_width = input_width;\n    int avg_buf_height = input_height;\n\n    vector<GLImageVector<T>*> reduce_buf;\n    while (reduce_buf.size() == 0 ||\n           (avg_buf_width > tile_size_x && avg_buf_height > tile_size_y)) {\n      avg_buf_width = (avg_buf_width + tile_size_x - 1) / tile_size_x;\n      avg_buf_height = (avg_buf_height + tile_size_y - 1) / tile_size_y;\n\n      reduce_buf.push_back(\n          ImageAllocator<T>::newImage(1, avg_buf_width, avg_buf_height, output_channels));\n    }\n\n    GLImageVector<T>* avg = ImageAllocator<T>::newImage(num_images, 1, 1, output_channels);\n    GLImageVector<T>* inv_stdev = ImageAllocator<T>::newImage(num_images, 1, 1, output_channels);\n    GLImageVector<T>* output = ImageAllocator<T>::newImage(\n        num_images, output_width, output_height, output_channels, is_last);\n    const float* prelu_data = nullptr;\n    int prelu_size = 0;\n    if (FUSE_PRELU) {\n      DCHECK_EQ(InputSize(), 4);\n      const auto& prelu_scale = Input(PRELU);\n      prelu_data = prelu_scale.template data<float>();\n      prelu_size = prelu_scale.size();\n    } else {\n      DCHECK_EQ(InputSize(), 3);\n    }\n\n    const auto& scale = Input(SCALE);\n    const auto& bias = Input(BIAS);\n\n    if (!f_reduce) {\n      f_reduce.reset(new GLReduce());\n      f_norm.reset(new GLReduce(false, true));\n      f_stdDev.reset(new GLReduce(true, false));\n      f_scale.reset(new GLScale(input_channels,\n                                scale.template data<float>(),\n                                bias.template data<float>(),\n                                prelu_data,\n                                prelu_size));\n    }\n\n    for (int i = 0; i < num_images; i++) {\n      for (int k = 0; k < reduce_buf.size() + 1; k++) {\n        const GLImage<T>* in = k == 0 ? input[i] : (*reduce_buf[k - 1])[0];\n        GLImage<T>* out = k == reduce_buf.size() ? (*avg)[i] : (*reduce_buf[k])[0];\n\n        float norm = k < reduce_buf.size()\n                         ? 1.0 / (tile_size_x * tile_size_y)\n                         : (float)pow(tile_size_x * tile_size_y, reduce_buf.size()) /\n                               (float)(input_width * input_height);\n        const int running_tile_size_x = k < reduce_buf.size() ? tile_size_x : in->width;\n        const int running_tile_size_y = k < reduce_buf.size() ? tile_size_y : in->height;\n        f_reduce->reduce(in, out, running_tile_size_x, running_tile_size_y, norm);\n      }\n\n      for (int k = 0; k < reduce_buf.size() + 1; k++) {\n        const GLImage<T>* in = k == 0 ? input[i] : (*reduce_buf[k - 1])[0];\n        GLImage<T>* out = k == reduce_buf.size() ? (*inv_stdev)[i] : (*reduce_buf[k])[0];\n\n        float norm = k < reduce_buf.size()\n                         ? 1.0 / (tile_size_x * tile_size_y)\n                         : (float)pow(tile_size_x * tile_size_y, reduce_buf.size()) /\n                               (float)(input_width * input_height);\n\n        if (k == 0) {\n          f_norm->norm(in, (*avg)[i], out, tile_size_x, tile_size_y, norm);\n        } else if (k < reduce_buf.size()) {\n          f_reduce->reduce(in, out, tile_size_x, tile_size_y, norm);\n        } else {\n          const int running_tile_size_x = k < reduce_buf.size() ? tile_size_x : in->width;\n          const int running_tile_size_y = k < reduce_buf.size() ? tile_size_y : in->height;\n          f_stdDev->reduce(in, out, running_tile_size_x, running_tile_size_y, norm, epsilon_);\n        }\n      }\n\n      f_scale->scale_and_shift(input[i], (*avg)[i], (*inv_stdev)[i], (*output)[i]);\n    }\n    Outputs()[OUTPUT]->Reset(output);\n    if (OutputSize() > 1) {\n      Outputs()[MEAN]->Reset(avg);\n      Outputs()[INV_STDEV]->Reset(inv_stdev);\n    } else {\n      delete avg;\n      delete inv_stdev;\n    }\n    for (auto&& rb : reduce_buf) {\n      delete rb;\n    }\n\n    return true;\n  }\n\n private:\n  float epsilon_;\n  StorageOrder order_;\n  std::unique_ptr<GLReduce> f_reduce;\n  std::unique_ptr<GLReduce> f_norm;\n  std::unique_ptr<GLReduce> f_stdDev;\n  std::unique_ptr<GLScale> f_scale;\n\n  INPUT_TAGS(INPUT, SCALE, BIAS, PRELU);\n  OUTPUT_TAGS(OUTPUT, MEAN, INV_STDEV);\n};\n\nREGISTER_CPU_OPERATOR(OpenGLInstanceNorm, OpenGLInstanceNormPReluOp<float16_t, false>);\nOPERATOR_SCHEMA(OpenGLInstanceNorm).NumInputs(3, 4).NumOutputs(1, 3).AllowInplace({{0, 0}});\nREGISTER_CPU_OPERATOR(OpenGLInstanceNormPRelu, OpenGLInstanceNormPReluOp<float16_t, true>);\nOPERATOR_SCHEMA(OpenGLInstanceNormPRelu).NumInputs(3, 4).NumOutputs(1, 3).AllowInplace({{0, 0}});\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLMul.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n#include \"../core/ImageAllocator.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n\nclass GLMul : public GLFilter {\n public:\n  binding* outputSize;\n  binding* inputData;\n  binding* B;\n\n  GLMul()\n      : GLFilter(\"GLMul\",\n                 vertex_shader,\n                 fragment_shader,\n                 std::vector<binding*>({BINDING(outputSize), BINDING(inputData), BINDING(B)}),\n                 {/* no uniform blocks */},\n                 {/* no attributes */},\n                 {/* no replacements */}) {}\n\n  template <typename T>\n  void mul(const GLImageVector<T>& input_images, const GLImageVector<T>& output_images, float b);\n\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\n\nconst char* GLMul::fragment_shader = R\"GLSL(#version 300 es\n\nprecision mediump float;\nprecision mediump int;\n\nin highp vec2 v_texCoord;\n\nuniform ivec2 outputSize;\nuniform vec4 B;\n\nTEXTURE_INPUT(inputData);\nTEXTURE_OUTPUT(0, outputData);\n\nvoid main() {\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n  vec4 A = TEXTURE_LOAD(inputData, texelCoord);\n  outputData = TEXTURE_STORE(A * B);\n}\n\n)GLSL\";\n\ntemplate <typename T>\nvoid GLMul::mul(const GLImageVector<T>& input_images,\n                const GLImageVector<T>& output_images,\n                float b) {\n  for (int i = 0; i < input_images.size(); i++) {\n    auto input_image = input_images[i];\n    auto output_image = output_images[i];\n    int input_slices = input_image->slices;\n    int output_slices = output_image->slices;\n\n    for (int is = 0; is < input_slices; is++) {\n      run(std::vector<texture_attachment>({{input_image->textures[is], inputData}}),\n          {output_image->textures.begin() + is, output_image->textures.begin() + is + 1},\n          [&]() {\n            glUniform2i(outputSize->location, output_image->width, output_image->height);\n            glUniform4f(B->location, b, b, b, b);\n          },\n          output_image->width,\n          output_image->height);\n    }\n  }\n}\n\nnamespace caffe2 {\ntemplate <class T>\nclass OpenGLMulOp final : public Operator<CPUContext>, ImageAllocator<T> {\n public:\n  OpenGLMulOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(OperatorBase::GetSingleArgument<int>(\"broadcast\", 0) == 1,\n                           \"OpenGLMul only supports broadcast\");\n\n    OPERATOR_NEEDS_FEATURE(OperatorBase::HasArgument(\"axis\") == false,\n                           \"OpenGLMul does not support axis\");\n  }\n\n  bool RunOnDevice() override {\n    const GLImageVector<T>& input = Inputs()[0]->template Get<GLImageVector<T>>();\n    const auto& B = Input(1);\n    CAFFE_ENFORCE_EQ(B.size(), 1); // only scalar is supported\n\n    const int num_images = input.size();\n    const auto output_height = input.height();\n    const auto output_width = input.width();\n    const int output_channels = input.channels();\n\n    int is_last = OperatorBase::GetSingleArgument<int>(\"is_last\", 0);\n\n    GLImageVector<T>* output = ImageAllocator<T>::newImage(\n        num_images, output_width, output_height, output_channels, is_last);\n\n    if (!_mult) {\n      _mult.reset(new GLMul());\n    }\n\n    _mult->mul(input, *output, B.template data<float>()[0]);\n\n    Outputs()[0]->Reset(output);\n\n    return true;\n  }\n\n private:\n  std::unique_ptr<GLMul> _mult;\n};\n\nREGISTER_CPU_OPERATOR(OpenGLMul, OpenGLMulOp<float16_t>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLNormPlanarYUV.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n#include \"../core/ImageAllocator.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include <iostream>\n#include <vector>\n\nclass GLNormPlanarYUV : public GLFilter {\n public:\n  const float* mean;\n  const float* std;\n\n  binding* inputData;\n  binding* outputSize;\n  binding* mean_data;\n  binding* std_data;\n\n  GLNormPlanarYUV(const float* _mean, const float* _std)\n      : GLFilter(\"GLNormPlanarYUV\",\n                 vertex_shader,\n                 fragment_shader,\n                 std::vector<binding*>({BINDING(inputData),\n                                        BINDING(outputSize),\n                                        BINDING(mean_data),\n                                        BINDING(std_data)}), // input bindings\n                 {/* no uniform blocks */},\n                 {/* no attributes */},\n                 {}),\n        mean(_mean),\n        std(_std) {}\n\n  template <typename T>\n  void normalize(const GLImageVector<T>& input_images, const GLImageVector<T>& output_images);\n\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\n\nconst char* GLNormPlanarYUV::fragment_shader = R\"GLSL(#version 300 es\n\nprecision mediump float;\nprecision mediump int;\n\nin highp vec2 v_texCoord;\n\nuniform ivec2 outputSize;\nuniform vec4 mean_data;\nuniform vec4 std_data;\n\nTEXTURE_INPUT(inputData);\nTEXTURE_OUTPUT(0, outputData);\n\nvoid main() {\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n  vec4 value = TEXTURE_LOAD(inputData, texelCoord);\n  outputData = TEXTURE_STORE((value - mean_data) / std_data);\n}\n\n)GLSL\";\n\ntemplate <class T>\nvoid GLNormPlanarYUV::normalize(const GLImageVector<T>& input_images,\n                                const GLImageVector<T>& output_images) {\n  int num_images = input_images.size();\n  for (int i = 0; i < num_images; i++) {\n    GLImage<T>* input_image = input_images[i];\n    GLImage<T>* output_image = output_images[i];\n    int input_slices = input_image->slices;\n    int output_slices = output_image->slices;\n\n    for (int is = 0; is < input_slices; is++) {\n\n      std::vector<texture_attachment> input_attachments({{input_image->textures[is], inputData}});\n\n      run(input_attachments,\n          {output_image->textures.begin() + is, output_image->textures.begin() + is + 1},\n          [&]() {\n            glUniform2i(outputSize->location, output_image->width, output_image->height);\n            glUniform4f(mean_data->location, mean[0], mean[1], mean[2], 0.0);\n            glUniform4f(std_data->location, std[0], std[1], std[2], 1.0);\n          },\n          output_image->width,\n          output_image->height);\n    }\n  }\n}\n\nnamespace caffe2 {\ntemplate <typename T>\nclass GLNormPlanarYUVOp final : public Operator<CPUContext>, ImageAllocator<T> {\n public:\n  GLNormPlanarYUVOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        order_(StringToStorageOrder(OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    OPERATOR_NEEDS_FEATURE(this->order_ == StorageOrder::NCHW, \"OpenGL only supports NCHW order.\");\n  }\n\n  bool RunOnDevice() override {\n    const GLImageVector<T>& input = Inputs()[0]->template Get<GLImageVector<T>>();\n    const int num_images = input.size();\n    const int input_channels = input.channels();\n    const int input_width = input.width();\n    const int input_height = input.height();\n\n    const int output_channels = input_channels;\n    const int output_width = input_width;\n    const int output_height = input_height;\n\n    int is_last = OperatorBase::GetSingleArgument<int>(\"is_last\", 0);\n\n    GLImageVector<T>* output = ImageAllocator<T>::newImage(\n        num_images, output_width, output_height, output_channels, is_last);\n\n    const auto& M = Input(1); // mean\n    const auto& S = Input(2); // standard deviation\n    CAFFE_ENFORCE(input_channels == M.dim(1));\n    CAFFE_ENFORCE(input_channels == S.dim(1));\n\n    if (!_normPlanarYUV) {\n      _normPlanarYUV.reset(new GLNormPlanarYUV(M.template data<float>(), S.template data<float>()));\n    }\n\n    _normPlanarYUV->normalize(input, *output);\n\n    Outputs()[0]->Reset(output);\n\n    return true;\n  }\n\n private:\n  StorageOrder order_;\n  std::unique_ptr<GLNormPlanarYUV> _normPlanarYUV;\n};\n\nREGISTER_CPU_OPERATOR(OpenGLNormalizePlanarYUV, GLNormPlanarYUVOp<float16_t>);\nOPERATOR_SCHEMA(OpenGLNormalizePlanarYUV).NumInputs(3).NumOutputs(1);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLPRelu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n#include \"../core/ImageAllocator.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include <iostream>\n#include <vector>\n\nclass GLPRelu : public GLFilter {\n public:\n  typedef enum { PRelu = 0, Relu = 1 } ReluType;\n\n  const float* scale;\n\n  binding* inputData;\n  binding* scale_block;\n\n  const int scale_size;\n  const int channels;\n  const int output_tile_x;\n  const int output_tile_y;\n  const int output_tile_width;\n  const int output_tile_height;\n\n  GLPRelu(\n      const float* _scale,\n      const int _scale_size,\n      const int _channels,\n      int _output_tile_x,\n      int _output_tile_y,\n      int _output_tile_width,\n      int _output_tile_height)\n      : GLFilter(\n            \"GLPRelu\",\n            vertex_shader,\n            fragment_shader,\n            std::vector<binding*>({BINDING(inputData)}),\n            std::vector<binding*>({BINDING(scale_block)}),\n            {/* no attributes */},\n            {{\"USE_RELU\", caffe2::to_string(PRelu)},\n             {\"OUTPUT_TILES\",\n              caffe2::to_string(_output_tile_x * _output_tile_y)},\n             {\"OUTPUT_TILE_X\", caffe2::to_string(_output_tile_x)},\n             {\"OUTPUT_TILE_WIDTH\", caffe2::to_string(_output_tile_width)},\n             {\"OUTPUT_TILE_HEIGHT\", caffe2::to_string(_output_tile_height)},\n             {\"TILED_PRELU\",\n              caffe2::to_string(_output_tile_x > 1 || _output_tile_y > 1)}}),\n        scale(_scale),\n        scale_size(_scale_size),\n        channels(_channels),\n        output_tile_x(_output_tile_x),\n        output_tile_y(_output_tile_y),\n        output_tile_width(_output_tile_width),\n        output_tile_height(_output_tile_height) {}\n\n  GLPRelu(const int _channels)\n      : GLFilter(\"GLRelu\",\n                 vertex_shader,\n                 fragment_shader,\n                 std::vector<binding*>({BINDING(inputData)}),\n                 {/* no uniform blocks */},\n                 {/* no attributes */},\n                 {{\"USE_RELU\", caffe2::to_string(Relu)},\n                  {\"OUTPUT_TILES\", caffe2::to_string(1)},\n                  {\"OUTPUT_TILE_X\", caffe2::to_string(1)},\n                  {\"OUTPUT_TILE_WIDTH\", caffe2::to_string(1)},\n                  {\"OUTPUT_TILE_HEIGHT\", caffe2::to_string(1)},\n                  {\"TILED_PRELU\", caffe2::to_string(0)}}),\n        scale(nullptr),\n        scale_block(nullptr),\n        scale_size(0),\n        channels(_channels),\n        output_tile_x(1),\n        output_tile_y(1),\n        output_tile_width(1),\n        output_tile_height(1) {}\n\n  template <typename T>\n  void prelu(const GLImageVector<T>& input_images,\n             const GLImageVector<T>& output_images,\n             GLPRelu::ReluType reluType);\n\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\n\nconst char* GLPRelu::fragment_shader = R\"GLSL(#version 300 es\n#define TILED_PRELU                 $(TILED_PRELU)\n#define USE_RELU                    $(USE_RELU)\n\n// tiling\n#define OUTPUT_TILES                $(OUTPUT_TILES)\n#define OUTPUT_TILE_X               $(OUTPUT_TILE_X)\n#define OUTPUT_TILE_WIDTH           $(OUTPUT_TILE_WIDTH)\n#define OUTPUT_TILE_HEIGHT          $(OUTPUT_TILE_HEIGHT)\n\n// common\nprecision mediump float;\nprecision highp int;\n\nTEXTURE_INPUT(inputData);\nTEXTURE_OUTPUT(0, outputData);\n\nin highp vec2 v_texCoord;\n\n#if USE_RELU\n\n// Relu\nvoid main() {\n  ivec2 inputSize = textureSize(inputData, 0);\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(inputSize));\n  vec4 value = TEXTURE_LOAD(inputData, texelCoord);\n  outputData = TEXTURE_STORE(max(value, vec4(0.0)));\n}\n\n#else\n\n#if TILED_PRELU\nconst ivec2 outputTileSize = ivec2(OUTPUT_TILE_WIDTH, OUTPUT_TILE_HEIGHT);\n\nlayout (std140) uniform scale_block {\n  highp uvec4 scale[(OUTPUT_TILES + 1) / 2];\n};\n\nvoid main() {\n  ivec2 inputSize = textureSize(inputData, 0);\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(inputSize));\n\n  ivec2 tile = texelCoord / outputTileSize; // 2D output tile idx\n  int tileNum = OUTPUT_TILE_X * tile.y + tile.x; // 1D output tile idx\n\n  // outputData = value > 0 ? value : value * weight;\n  vec4 value = TEXTURE_LOAD(inputData, texelCoord);\n  vec4 preluValue = (tileNum % 2 == 0) ? unpackHalf4x16(scale[tileNum/2].xy) : unpackHalf4x16(scale[tileNum/2].zw);\n  value = mix(value * preluValue, value, vec4(greaterThan(value, vec4(0))));\n  outputData = TEXTURE_STORE(value);\n}\n#else\nlayout (std140) uniform scale_block {\n  highp uvec4 scale;\n};\nvoid main() {\n  ivec2 inputSize = textureSize(inputData, 0);\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(inputSize));\n\n  // outputData = value > 0 ? value : value * weight;\n  vec4 value = TEXTURE_LOAD(inputData, texelCoord);\n  value = mix(value * unpackHalf4x16(scale.xy), value, vec4(greaterThan(value, vec4(0))));\n  outputData = TEXTURE_STORE(value);\n}\n#endif // TILED_PRELU\n\n#endif // USE_RELU\n\n)GLSL\";\n\ntemplate <typename T>\nvoid GLPRelu::prelu(const GLImageVector<T>& input_images,\n                    const GLImageVector<T>& output_images,\n                    GLPRelu::ReluType reluType) {\n  int num_images = input_images.size();\n  for (int i = 0; i < num_images; i++) {\n    GLImage<T>* input_image = input_images[i];\n    GLImage<T>* output_image = output_images[i];\n    int input_slices = input_image->slices;\n    int output_slices = output_image->slices;\n\n    for (int is = 0; is < input_slices; is++) {\n      if (reluType == PRelu) {\n        attach_uniform_buffer<float16_t>(scale_block, 0, [&](float16_t* data, size_t size) {\n          int output_tiles = output_tile_x * output_tile_y;\n          for (int j = 0, k = 4 * is * output_tiles;\n               k < std::min(channels, 4 * (is + 1) * output_tiles);\n               j++, k++) {\n            data[j] = scale_size == channels ? scale[k] : scale[0];\n          }\n        });\n      }\n\n      std::vector<texture_attachment> input_attachments;\n\n      input_attachments.push_back({input_image->textures[is], inputData});\n\n      run(input_attachments,\n          {output_image->textures.begin() + is, output_image->textures.begin() + is + 1},\n          [&]() {},\n          output_image->texture_width,\n          output_image->texture_height);\n    }\n  }\n}\n\nnamespace caffe2 {\ntemplate <typename T, GLPRelu::ReluType reluType>\nclass OpenGLPReluOp final : public Operator<CPUContext>, ImageAllocator<T> {\n public:\n  OpenGLPReluOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        order_(StringToStorageOrder(OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    OPERATOR_NEEDS_FEATURE(this->order_ == StorageOrder::NCHW, \"OpenGL only supports NCHW order.\");\n  }\n\n  bool RunOnDevice() override {\n    const GLImageVector<T>& input = Inputs()[0]->template Get<GLImageVector<T>>();\n    const int num_images = input.size();\n    const int input_channels = input.channels();\n    const int input_width = input.width();\n    const int input_height = input.height();\n\n    const int output_channels = input_channels;\n    const int output_width = input_width;\n    const int output_height = input_height;\n\n    int is_last = OperatorBase::GetSingleArgument<int>(\"is_last\", 0);\n\n    const int input_tile_x = input.tile_x(), input_tile_y = input.tile_y();\n    const int output_tile_x = input_tile_x, output_tile_y = input_tile_y;\n    if (input_tile_x > 1 || input_tile_y > 1) {\n      CAFFE_ENFORCE_EQ(input.slices(), 1, \"Input needs to be tiled in a single texture\");\n    }\n\n    GLImageVector<T>* output = ImageAllocator<T>::newImage(num_images,\n                                                           output_width,\n                                                           output_height,\n                                                           output_channels,\n                                                           output_tile_x,\n                                                           output_tile_y,\n                                                           is_last);\n\n    const auto* scale = reluType == GLPRelu::PRelu ? &Input(1) : nullptr;\n\n    if (!_prelu) {\n      if (reluType == GLPRelu::PRelu) {\n        _prelu.reset(new GLPRelu(scale->template data<float>(),\n                                 scale->size(),\n                                 input_channels,\n                                 output_tile_x,\n                                 output_tile_y,\n                                 output_width,\n                                 output_height));\n      } else {\n        _prelu.reset(new GLPRelu(input_channels));\n      }\n    }\n\n    _prelu->prelu(input, *output, reluType);\n\n    Outputs()[0]->Reset(output);\n\n    return true;\n  }\n\n private:\n  StorageOrder order_;\n  std::unique_ptr<GLPRelu> _prelu;\n};\n\nREGISTER_CPU_OPERATOR(OpenGLPRelu, OpenGLPReluOp<float16_t, GLPRelu::PRelu>);\nOPERATOR_SCHEMA(OpenGLPRelu)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape();\nREGISTER_CPU_OPERATOR(OpenGLRelu, OpenGLPReluOp<float16_t, GLPRelu::Relu>);\nOPERATOR_SCHEMA(OpenGLRelu)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape();\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLPadImage.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n#include \"../core/ImageAllocator.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n\nclass GLPadImage : public GLFilter {\n public:\n  binding* padSize;\n  binding* inputSize;\n  binding* outputSize;\n  binding* inputData;\n\n  GLPadImage()\n      : GLFilter(\n            \"GLPadImage\",\n            vertex_shader,\n            fragment_shader,\n            std::vector<binding*>(\n                {BINDING(padSize), BINDING(inputSize), BINDING(outputSize), BINDING(inputData)}),\n            {/* no uniform blocks */},\n            {/* no attributes */},\n            {/* no replacements */}) {}\n\n  template <typename T>\n  void pad(const GLImageVector<T>& input_images,\n           const GLImageVector<T>& output_images,\n           const int pad_l,\n           const int pad_t);\n\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\n\nconst char* GLPadImage::fragment_shader = R\"GLSL(#version 300 es\n\nprecision mediump float;\nprecision mediump int;\n\nin highp vec2 v_texCoord;\n\nuniform ivec2 padSize;\nuniform ivec2 inputSize;\nuniform ivec2 outputSize;\n\nTEXTURE_INPUT(inputData);\nTEXTURE_OUTPUT(0, outputData);\n\nvoid main() {\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize)) - padSize;\n  texelCoord = max(texelCoord, -texelCoord);\n  texelCoord = min(texelCoord, ivec2(2) * (inputSize - 1) - texelCoord);\n  vec4 value = TEXTURE_LOAD(inputData, texelCoord);\n  outputData = TEXTURE_STORE(value);\n}\n\n)GLSL\";\n\ntemplate <typename T>\nvoid GLPadImage::pad(const GLImageVector<T>& input_images,\n                     const GLImageVector<T>& output_images,\n                     const int pad_l,\n                     const int pad_t) {\n  for (int i = 0; i < input_images.size(); i++) {\n    auto input_image = input_images[i];\n    auto output_image = output_images[i];\n    int input_slices = input_image->slices;\n    int output_slices = output_image->slices;\n\n    for (int is = 0; is < input_slices; is++) {\n      run(std::vector<texture_attachment>({{input_image->textures[is], inputData}}),\n          {output_image->textures.begin() + is, output_image->textures.begin() + is + 1},\n          [&]() {\n            glUniform2i(inputSize->location, input_image->width, input_image->height);\n            glUniform2i(outputSize->location, output_image->width, output_image->height);\n            glUniform2i(padSize->location, pad_l, pad_t);\n          },\n          output_image->width,\n          output_image->height);\n    }\n  }\n}\n\nnamespace caffe2 {\n\ntemplate <typename OPBase>\nstatic void computeOutputHW(OPBase* op, int H, int W, int* OH, int* OW) {\n  Tensor<CPUContext> input, output;\n  input.Resize(1, 1, H, W);\n  op->SetOutputSize(input, &output, 1);\n  CAFFE_ENFORCE_EQ(output.ndim(), 4);\n  *OH = output.dim(2);\n  *OW = output.dim(3);\n}\n\ntemplate <class T>\nclass OpenGLPadImageOp final : public ConvPoolOpBase<CPUContext>, ImageAllocator<T> {\n public:\n  OpenGLPadImageOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CPUContext>(operator_def, ws),\n        mode_(OperatorBase::GetSingleArgument<string>(\"mode\", \"\")) {\n    OPERATOR_NEEDS_FEATURE(order_ == StorageOrder::NCHW, \"OpenGL only supports NCHW order.\");\n    OPERATOR_NEEDS_FEATURE(mode_ == \"reflect\", \"OpenGL only supports reflection\");\n\n    CAFFE_ENFORCE(legacy_pad_ == LegacyPadding::NOTSET,\n                  \"Padding layer only supports explicit pad values.\");\n    CAFFE_ENFORCE(dilation_h() == 1 && dilation_w() == 1,\n                  \"Pooling op does not support dilation right now.\");\n    CAFFE_ENFORCE(stride_h() == 1 && stride_w() == 1,\n                  \"Pooling op does not support stride right now.\");\n    // Pad op does not use kernel sizes, so we set it to 1 for computing the\n    // output size.\n    kernel_.assign(pads_.size() / 2, 1);\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    const GLImageVector<T>& input = Inputs()[0]->template Get<GLImageVector<T>>();\n\n    const int num_images = input.size();\n    const int input_width = input.width();\n    const int input_height = input.height();\n    const int input_channels = input.channels();\n    const int output_channels = input_channels;\n\n    int output_height, output_width;\n    computeOutputHW(this, input_height, input_width, &output_height, &output_width);\n\n    int is_last = OperatorBase::GetSingleArgument<int>(\"is_last\", 0);\n\n    GLImageVector<T>* output = ImageAllocator<T>::newImage(\n        num_images, output_width, output_height, output_channels, is_last);\n\n    if (!padImage_) {\n      padImage_.reset(new GLPadImage());\n      LOG(INFO) << input_channels << \": \" << input_height << \" X \" << input_width << \" => \"\n                << output_channels << \": \" << output_height << \" X \" << output_width;\n      LOG(INFO) << \"Padmode: \" << mode_ << \", pad_l = \" << pad_l() << \", pad_r = \" << pad_r() << \", pad_t = \" << pad_t()\n                << \", pad_b = \" << pad_b();\n    }\n\n    padImage_->pad(input, *output, pad_l(), pad_t());\n\n    Outputs()[0]->Reset(output);\n\n    return true;\n  }\n\n private:\n  std::string mode_;\n  std::unique_ptr<GLPadImage> padImage_;\n};\n\nREGISTER_CPU_OPERATOR(OpenGLPadImage, OpenGLPadImageOp<float16_t>);\nOPERATOR_SCHEMA(OpenGLPadImage).NumInputs(1).NumOutputs(1);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLPool.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n#include \"../core/ImageAllocator.h\"\n\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/operators/pool_op.h\"\n\nclass GLPool : public GLFilter {\n public:\n  typedef enum { AveragePool, MaxPool } PoolType;\n\n  struct point {\n    int x;\n    int y;\n  };\n\n  struct descriptor {\n    int channels;\n    point kernel_size;\n    point input_padding;\n    point input_stride;\n    point input_tile_size;\n    point output_tile_size;\n  };\n\n  binding* inputData;\n  binding* kernelSize;\n  binding* outputSize;\n\n  const descriptor geometry;\n\n  GLPool(const descriptor& _geometry, PoolType poolType, bool _tiling)\n      : GLFilter(\n            \"GLPool\",\n            vertex_shader,\n            fragment_shader,\n            {\n                BINDING(inputData),\n                BINDING(kernelSize),\n                BINDING(outputSize),\n            },\n            {/* no uniform blocks */},\n            {/* no attributes */},\n            {{\"KERNEL_SIZE_X\", caffe2::to_string(_geometry.kernel_size.x)},\n             {\"KERNEL_SIZE_Y\", caffe2::to_string(_geometry.kernel_size.y)},\n             {\"INPUT_PADDING_X\", caffe2::to_string(_geometry.input_padding.x)},\n             {\"INPUT_PADDING_Y\", caffe2::to_string(_geometry.input_padding.y)},\n             {\"INPUT_STRIDE_X\", caffe2::to_string(_geometry.input_stride.x)},\n             {\"INPUT_STRIDE_Y\", caffe2::to_string(_geometry.input_stride.y)},\n             {\"INPUT_TILE_WIDTH\",\n              caffe2::to_string(_geometry.input_tile_size.x)},\n             {\"INPUT_TILE_HEIGHT\",\n              caffe2::to_string(_geometry.input_tile_size.y)},\n             {\"OUTPUT_TILE_WIDTH\",\n              caffe2::to_string(_geometry.output_tile_size.x)},\n             {\"OUTPUT_TILE_HEIGHT\",\n              caffe2::to_string(_geometry.output_tile_size.y)},\n             {\"TILED_POOLING\", caffe2::to_string(_tiling)},\n             {\"MAX_POOL\", caffe2::to_string(poolType == MaxPool)},\n             {\"BOUNDS_CHECK_MODE\", caffe2::to_string(1)}}),\n        geometry(_geometry) {}\n  ~GLPool() {}\n\n  void pool(const GLImageVector<float16_t>& input_images,\n            const GLImageVector<float16_t>& output_images) {\n    for (int i = 0; i < input_images.size(); i++) {\n      auto input_image = input_images[i];\n      auto output_image = output_images[i];\n      int input_slices = input_image->slices;\n      int output_slices = output_image->slices;\n\n      for (int is = 0; is < input_slices; is++) {\n        run({{input_image->textures[is], inputData}},\n            {output_image->textures[is]},\n            [&]() {\n              glUniform2i(outputSize->location, output_image->texture_width, output_image->texture_height);\n              glUniform2i(kernelSize->location, geometry.kernel_size.x, geometry.kernel_size.y);\n            },\n            output_image->texture_width,\n            output_image->texture_height);\n      }\n    }\n  }\n\n private:\n  /*\n   * Computes BOUNDS_CHECK_MODE for the convolution parameters.\n   *\n   * @retval 0 if bounds check can be skipped\n   * @retval non-zero if bounds check can not be skipped\n   */\n  inline static int bounds_check_mode(bool tiling, const descriptor& geometry) {\n    if (tiling) {\n      return 1;\n    }\n\n    if (GLContext::getGLContext()->GL_EXT_texture_border_clamp_defined() ||\n        (geometry.input_padding.x == 0 && geometry.input_padding.y == 0)) {\n      return 0;\n    } else {\n      return 1;\n    }\n  }\n\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\nconst char* GLPool::fragment_shader = R\"GLSL(#version 300 es\n#define TILED_POOLING           $(TILED_POOLING)\n#define MAX_POOL                $(MAX_POOL)\n\n// tiling\n#define INPUT_TILE_WIDTH            $(INPUT_TILE_WIDTH)\n#define INPUT_TILE_HEIGHT           $(INPUT_TILE_HEIGHT)\n#define OUTPUT_TILE_WIDTH           $(OUTPUT_TILE_WIDTH)\n#define OUTPUT_TILE_HEIGHT          $(OUTPUT_TILE_HEIGHT)\n\n#define BOUNDS_CHECK_MODE           $(BOUNDS_CHECK_MODE)\n\nprecision mediump float;\nprecision mediump int;\n\nin highp vec2 v_texCoord;\n\nconst ivec2 input_padding = ivec2($(INPUT_PADDING_X), $(INPUT_PADDING_Y));\nconst ivec2 input_stride = ivec2($(INPUT_STRIDE_X), $(INPUT_STRIDE_Y));\nconst ivec2 kernel_size = ivec2($(KERNEL_SIZE_X), $(KERNEL_SIZE_Y));\n\nuniform ivec2 kernelSize;\nuniform ivec2 outputSize;\n\nTEXTURE_INPUT(inputData);\nTEXTURE_OUTPUT(0, outputData);\n\n#if BOUNDS_CHECK_MODE == 0\n  #define IN_BOUNDS(p, p0, p1) (true)\n#else\n  #define IN_BOUNDS(p, p0, p1) (all(greaterThanEqual(p, p0)) && all(lessThan(p, p1)))\n#endif\n\n// MIN_FLOAT is -2^14, which is the minimum precision requirement for mediump in OpenGL ES 3.0\nconst float MIN_FLOAT = -exp2(14.0);\n\n#if TILED_POOLING\n\nconst ivec2 inputTileSize = ivec2(INPUT_TILE_WIDTH, INPUT_TILE_HEIGHT);\nconst ivec2 outputTileSize = ivec2(OUTPUT_TILE_WIDTH, OUTPUT_TILE_HEIGHT);\n\n// tiled pooling\n#if MAX_POOL\n\n#define POOL { \\\n  pool = vec4(MIN_FLOAT); \\\n  for (int y = 0; y < kernelSize.y; y++) { \\\n    for (int x = 0; x < kernelSize.x; x++) { \\\n      ivec2 idx = tileCoord + ivec2(x, y); \\\n      if IN_BOUNDS(idx, ivec2(0), inputTileSize) { \\\n        vec4 data = TEXTURE_LOAD(inputData, inputTileOffset + idx); \\\n        pool = max(pool, data); \\\n      } \\\n    } \\\n  } \\\n}\n\n#else\n\n#define POOL { \\\n  int count = 0; \\\n  for (int y = 0; y < kernelSize.y; y++) { \\\n    for (int x = 0; x < kernelSize.x; x++) { \\\n      ivec2 idx = tileCoord + ivec2(x, y); \\\n      if IN_BOUNDS(idx, ivec2(0), inputTileSize) { \\\n        vec4 data = TEXTURE_LOAD(inputData, inputTileOffset + idx); \\\n        pool += data;\\\n        count += 1; \\\n      } \\\n    } \\\n  } \\\n  pool = pool / float(count); \\\n}\n\n#endif // MAX_POOL\n\nvoid main() {\n  ivec2 inputSize = textureSize(inputData, 0);\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n\n  ivec2 tile = texelCoord / outputTileSize; // 2D output tile idx\n  ivec2 tileCoord = texelCoord % outputTileSize; // in-tile coordinates\n  tileCoord = input_stride * tileCoord - input_padding;\n\n  ivec2 inputTileOffset = tile * inputTileSize;\n\n#if MAX_POOL\n  vec4 pool = vec4(0);\n#else\n  highp vec4 pool = vec4(0);\n#endif\n\n  POOL;\n\n  outputData = TEXTURE_STORE(pool);\n}\n\n#else\n\n// no tiling\n#if MAX_POOL\n\n#define POOL { \\\n  pool = vec4(MIN_FLOAT); \\\n  for (int y = 0; y < kernelSize.y; y++) { \\\n    for (int x = 0; x < kernelSize.x; x++) { \\\n      ivec2 idx = texelCoord + ivec2(x, y); \\\n      if IN_BOUNDS(idx, ivec2(0), inputSize) { \\\n        vec4 data = TEXTURE_LOAD(inputData, idx); \\\n        pool = max(pool, data); \\\n      } \\\n    } \\\n  } \\\n}\n\n#else\n\n#define POOL { \\\n  int count = 0; \\\n  for (int y = 0; y < kernelSize.y; y++) { \\\n    for (int x = 0; x < kernelSize.x; x++) { \\\n      ivec2 idx = texelCoord + ivec2(x, y); \\\n      if IN_BOUNDS(idx, ivec2(0), inputSize) { \\\n        vec4 data = TEXTURE_LOAD(inputData, idx); \\\n        pool += data; \\\n        count += 1; \\\n      } \\\n    } \\\n  } \\\n  pool = pool / float(count); \\\n}\n\n#endif // MAX_POOL\n\nvoid main() {\n  ivec2 inputSize = textureSize(inputData, 0);\n  ivec2 texelCoord = input_stride * ivec2(v_texCoord * vec2(outputSize)) - input_padding;\n#if MAX_POOL\n  vec4 pool = vec4(0);\n#else\n  highp vec4 pool = vec4(0);\n#endif\n\n  POOL;\n\n  outputData = TEXTURE_STORE(pool);\n}\n#endif // TILED_POOLING\n\n)GLSL\";\n\nnamespace caffe2 {\n\ntemplate <typename OPBase>\nstatic void computeOutputHW(OPBase* op, int H, int W, int* OH, int* OW) {\n  Tensor<CPUContext> input, output;\n  input.Resize(1, 1, H, W);\n  op->SetOutputSize(input, &output, 1);\n  CAFFE_ENFORCE_EQ(output.ndim(), 4);\n  *OH = output.dim(2);\n  *OW = output.dim(3);\n}\n\ntemplate <typename T, GLPool::PoolType poolType>\nclass GLPoolOp final : public ConvPoolOpBase<CPUContext>, ImageAllocator<float16_t> {\n public:\n  GLPoolOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(order_ == StorageOrder::NCHW, \"OpenGL only supports NCHW order.\");\n    CAFFE_ENFORCE(dilation_h() == 1 && dilation_w() == 1,\n                  \"Pooling op does not support dilation right now.\");\n    if (!global_pooling_) {\n      CAFFE_ENFORCE(pad_t() < kernel_h() && pad_b() < kernel_h() && pad_l() < kernel_w() &&\n                        pad_r() < kernel_w(),\n                    \"Pad should be smaller than kernel.\");\n    }\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    const GLImageVector<T>& input = OperatorBase::Inputs()[0]->template Get<GLImageVector<T>>();\n    const int num_images = input.size();\n    const int input_channels = input.channels();\n    const int input_width = input.width();\n    const int input_height = input.height();\n\n    int output_height;\n    int output_width;\n    const int output_channels = input_channels;\n\n    computeOutputHW(this, input_height, input_width, &output_height, &output_width);\n\n    int is_last = OperatorBase::GetSingleArgument<int>(\"is_last\", 0);\n\n    const int input_tile_x = input.tile_x(), input_tile_y = input.tile_y();\n    const int output_tile_x = input_tile_x, output_tile_y = input_tile_y;\n\n    GLImageVector<T>* output = ImageAllocator<T>::newImage(\n        num_images, output_width, output_height, output_channels, output_tile_x, output_tile_y, is_last);\n\n    GLPool::descriptor geometry{input_channels,\n                                {kernel_w(), kernel_h()},\n                                {pad_l(), pad_t()},\n                                {stride_w(), stride_h()},\n                                {input_width, input_height},\n                                {output_height, output_width}};\n\n    if (!glPool_) {\n      LOG(INFO) << input_channels << \": \" << input_height << \" X \" << input_width << \" => \" << output_channels << \": \"\n                << output_height << \" X \" << output_width << \" Kernel: \" << kernel_w() << \"X\" << kernel_h()\n                << \" Tiling: \" << input_tile_x << \"X\" << input_tile_y;\n\n      glPool_.reset(new GLPool(geometry, poolType, input_tile_x > 1 || input_tile_y > 1));\n    }\n\n    glPool_->pool(input, *output);\n\n    OperatorBase::Outputs()[0]->Reset(output);\n\n    return true;\n  }\n\n private:\n  std::unique_ptr<GLPool> glPool_;\n};\n\nnamespace {\nREGISTER_CPU_OPERATOR(OpenGLAveragePool, GLPoolOp<float16_t, GLPool::AveragePool>);\nREGISTER_CPU_OPERATOR(OpenGLMaxPool, GLPoolOp<float16_t, GLPool::MaxPool>);\nOPERATOR_SCHEMA(OpenGLAveragePool).NumInputs(1).NumOutputs(1);\nOPERATOR_SCHEMA(OpenGLMaxPool).NumInputs(1).NumOutputs(1);\n}; // namespace\n}; // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLResize.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n#include \"../core/ImageAllocator.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include <iostream>\n#include <vector>\n\nclass GLResizeNearest : public GLFilter {\n public:\n  binding* inputData;\n  binding* outputSize;\n  binding* scale_reverse;\n\n  GLResizeNearest()\n      : GLFilter(\"GLResizeNearest\",\n                 vertex_shader,\n                 fragment_shader,\n                 std::vector<binding*>({BINDING(outputSize), BINDING(scale_reverse), BINDING(inputData)}),\n                 {/* no uniform blocks*/},\n                 {/* no attributes */},\n                 {/* replacements */}) {}\n\n  template <typename T>\n  void resize(const GLImageVector<T>& input_images,\n              const GLImageVector<T>& output_images,\n              float width_scale_rev,\n              float height_scale_rev);\n\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\n\nconst char* GLResizeNearest::fragment_shader = R\"GLSL(#version 300 es\n\nprecision mediump float;\nprecision mediump int;\n\nin highp vec2 v_texCoord;\n\nuniform ivec2 outputSize;\nuniform highp vec2 scale_reverse;\n\nTEXTURE_INPUT(inputData);\nTEXTURE_OUTPUT(0, outputData);\n\nvoid main() {\n  // it clamps to the edge by default\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize) * scale_reverse);\n  vec4 value = TEXTURE_LOAD(inputData, texelCoord);\n  outputData = TEXTURE_STORE(value);\n}\n)GLSL\";\n\ntemplate <typename T>\nvoid GLResizeNearest::resize(const GLImageVector<T>& input_images,\n                             const GLImageVector<T>& output_images,\n                             float width_scale_rev,\n                             float height_scale_rev) {\n  for (int i = 0; i < input_images.size(); i++) {\n    auto input_image = input_images[i];\n    auto output_image = output_images[i];\n    int input_slices = input_image->slices;\n    int output_slices = output_image->slices;\n\n    for (int is = 0; is < input_slices; is++) {\n      std::vector<texture_attachment> input_attachments({{input_image->textures[is], inputData}});\n\n      run(input_attachments,\n          {output_image->textures.begin() + is, output_image->textures.begin() + is + 1},\n          [&]() {\n            glUniform2i(outputSize->location, output_image->texture_width, output_image->texture_height);\n            glUniform2f(scale_reverse->location, width_scale_rev, height_scale_rev);\n          },\n          output_image->texture_width,\n          output_image->texture_height);\n    }\n  }\n}\n\nnamespace caffe2 {\n\ntemplate <class T>\nclass OpenGLResizeNearestOp final : public Operator<CPUContext>, ImageAllocator<T> {\n public:\n  OpenGLResizeNearestOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws), width_scale_(1), height_scale_(1) {\n    if (HasArgument(\"width_scale\")) {\n      width_scale_ = static_cast<float>(OperatorBase::GetSingleArgument<float>(\"width_scale\", 1));\n    }\n    if (HasArgument(\"height_scale\")) {\n      height_scale_ = static_cast<float>(OperatorBase::GetSingleArgument<float>(\"height_scale\", 1));\n    }\n  }\n\n  bool RunOnDevice() override {\n    const GLImageVector<T>& input = Inputs()[0]->template Get<GLImageVector<T>>();\n    const int num_images = input.size();\n    const int input_width = input.width();\n    const int input_height = input.height();\n    const int input_channels = input.channels();\n\n    const int output_width = input_width * width_scale_;\n    const int output_height = input_height * height_scale_;\n    const int output_channels = input_channels;\n\n    const int input_tile_x = input.tile_x(), input_tile_y = input.tile_y();\n    const int output_tile_x = input_tile_x, output_tile_y = input_tile_y;\n\n    int is_last = OperatorBase::GetSingleArgument<int>(\"is_last\", 0);\n    GLImageVector<T>* output = ImageAllocator<T>::newImage(\n        num_images, output_width, output_height, output_channels, output_tile_x, output_tile_y, is_last);\n\n    if (!resizeNearest_) {\n      resizeNearest_.reset(new GLResizeNearest());\n    }\n    resizeNearest_->resize(input, *output, 1.0 / width_scale_, 1.0 / height_scale_);\n    Outputs()[0]->Reset(output);\n\n    return true;\n  }\n\n protected:\n  float width_scale_;\n  float height_scale_;\n  std::unique_ptr<GLResizeNearest> resizeNearest_;\n};\n\nREGISTER_CPU_OPERATOR(OpenGLResizeNearest, OpenGLResizeNearestOp<float16_t>);\nOPERATOR_SCHEMA(OpenGLResizeNearest).NumInputs(1).NumOutputs(1);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLSigmoid.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n#include \"../core/ImageAllocator.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include <iostream>\n#include <vector>\n\ntypedef enum { Sigmoid, Tanh } OpType;\n\nclass GLSigmoid : public GLFilter {\n public:\n  binding* inputData;\n  binding* outputSize;\n\n  GLSigmoid(OpType opType)\n      : GLFilter(\"GLSigmoid\",\n                 vertex_shader,\n                 fragment_shader,\n                 {BINDING(outputSize), BINDING(inputData)},\n                 {/* no uniform blocks */},\n                 {/* no attributes */},\n                 {{\"SIGMOID\", caffe2::to_string(opType == Sigmoid)},\n                  {\"TANH\", caffe2::to_string(opType == Tanh)}}) {}\n\n  template <typename T>\n  void sigmoid(const GLImageVector<T>& input_images, const GLImageVector<T>& output_images);\n\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\n\nconst char* GLSigmoid::fragment_shader = R\"GLSL(#version 300 es\n#define SIGMOID $(SIGMOID)\n#define TANH $(TANH)\n\nprecision mediump float;\nprecision mediump int;\n\nin highp vec2 v_texCoord;\n\nuniform ivec2 outputSize;\n\nTEXTURE_INPUT(inputData);\nTEXTURE_OUTPUT(0, outputData);\n\nvoid main() {\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n  vec4 value = TEXTURE_LOAD(inputData, ivec2(texelCoord));\n#if SIGMOID\n  value = vec4(1.0) / (vec4(1.0) + exp(-value));\n  outputData = TEXTURE_STORE(value);\n#elif TANH\n  value = tanh(value);\n  outputData = TEXTURE_STORE(value);\n#endif\n}\n\n)GLSL\";\n\ntemplate <typename T>\nvoid GLSigmoid::sigmoid(const GLImageVector<T>& input_images,\n                        const GLImageVector<T>& output_images) {\n  for (int i = 0; i < input_images.size(); i++) {\n    auto input_image = input_images[i];\n    auto output_image = output_images[i];\n    int input_slices = input_image->slices;\n    int output_slices = output_image->slices;\n\n    for (int is = 0; is < input_slices; is++) {\n      run(std::vector<texture_attachment>({{input_image->textures[is], inputData}}),\n          {output_image->textures.begin() + is, output_image->textures.begin() + is + 1},\n          [&]() { glUniform2i(outputSize->location, output_image->width, output_image->height); },\n          output_image->width,\n          output_image->height);\n    }\n  }\n}\n\nnamespace caffe2 {\ntemplate <typename T, OpType opType>\nclass OpenGLSigmoidOp final : public Operator<CPUContext>, ImageAllocator<T> {\n public:\n  OpenGLSigmoidOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    const GLImageVector<T>& input = Inputs()[0]->template Get<GLImageVector<T>>();\n    const int num_images = input.size();\n    const int input_channels = input.channels();\n    const int input_width = input.width();\n    const int input_height = input.height();\n\n    const int output_channels = input_channels;\n    const int output_width = input_width;\n    const int output_height = input_height;\n\n    int is_last = OperatorBase::GetSingleArgument<int>(\"is_last\", 0);\n\n    GLImageVector<T>* output = ImageAllocator<T>::newImage(\n        num_images, output_width, output_height, output_channels, is_last);\n\n    if (!_sigmoid) {\n      _sigmoid.reset(new GLSigmoid(opType));\n    }\n\n    _sigmoid->sigmoid(input, *output);\n\n    Outputs()[0]->Reset(output);\n\n    return true;\n  }\n\n private:\n  std::unique_ptr<GLSigmoid> _sigmoid;\n};\n\nREGISTER_CPU_OPERATOR(OpenGLSigmoid, OpenGLSigmoidOp<float16_t, Sigmoid>);\nOPERATOR_SCHEMA(OpenGLSigmoid)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape();\n\nREGISTER_CPU_OPERATOR(OpenGLTanh, OpenGLSigmoidOp<float16_t, Tanh>);\nOPERATOR_SCHEMA(OpenGLTanh)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape();\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLSoftmax.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n\n#include \"caffe2/core/timer.h\"\n#include <iostream>\n#include <vector>\n\nclass GLSoftmaxReduce : public GLFilter {\n public:\n  binding* inputTileSize;\n  binding* outputSize;\n  binding* outputTileSize;\n  binding* tileSize;\n  binding* spatialTileSize;\n  binding* inputTileRange;\n  binding* inputData;\n  binding* maxData;\n  binding* sumData;\n\n  const std::vector<binding*> input_bindings() {\n    std::vector<binding*> bindings({BINDING(inputTileSize),\n                                    BINDING(outputSize),\n                                    BINDING(outputTileSize),\n                                    BINDING(tileSize),\n                                    BINDING(spatialTileSize),\n                                    BINDING(inputTileRange),\n                                    BINDING(inputData),\n                                    BINDING(maxData),\n                                    BINDING(sumData)});\n    return bindings;\n  }\n\n  GLSoftmaxReduce(\n      bool compute_sum_ = false,\n      bool tiled = false,\n      int input_tile_x = 1)\n      : GLFilter(\n            \"GLSoftmaxReduce\",\n            vertex_shader,\n            fragment_shader,\n            input_bindings(),\n            {/* no uniform_blocks_bindings */},\n            {/* no attributes */},\n            {{\"COMPUTE_SUM\", caffe2::to_string((int)compute_sum_)},\n             {\"INPUT_TILE_X\", caffe2::to_string(input_tile_x)},\n             {\"TILED_SOFTMAX\", caffe2::to_string(int(tiled))}}) {}\n\n  template <typename T>\n  void reduce(const GLImage<T>* input_image,\n              const GLImage<T>* output_image,\n              int tile_size_x,\n              int tile_size_y);\n\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\n\nconst char* GLSoftmaxReduce::fragment_shader = R\"GLSL(#version 300 es\n\n#define TILED_SOFTMAX $(TILED_SOFTMAX)\n#define INPUT_TILE_X $(INPUT_TILE_X)\n// Compute sum or max\n#define COMPUTE_SUM $(COMPUTE_SUM)\n\nprecision highp float;\nprecision mediump int;\n\nin highp vec2 v_texCoord;\n\nuniform ivec2 inputTileSize;\nuniform ivec2 outputSize;\nuniform ivec2 outputTileSize;\nuniform ivec2 spatialTileSize;\nuniform ivec2 tileSize;\nuniform ivec2 inputTileRange;\n\nTEXTURE_INPUT(inputData);\nTEXTURE_OUTPUT(0, outputData);\n\n#if TILED_SOFTMAX\nvoid main() {\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n  ivec2 tile = texelCoord / outputTileSize; // 2D output tile idx\n  ivec2 tileCoord = texelCoord % outputTileSize; // in-tile coordinates\n  ivec2 sumArea = min(spatialTileSize, inputTileSize - tileCoord * spatialTileSize);\n\n  vec4 result = vec4(0.0);\n  for (int tileIdx = inputTileRange.x; tileIdx < inputTileRange.y; tileIdx++) {\n    int inTileX = tileIdx % INPUT_TILE_X;\n    int inTileY = tileIdx / INPUT_TILE_X;\n    ivec2 inputTileOffset = ivec2(inTileX, inTileY) * inputTileSize;\n    for (int y = 0; y < sumArea.y; y++) {\n      for (int x = 0; x < sumArea.x; x++) {\n        ivec2 idx = tileCoord + ivec2(x, y);\n        vec4 val = TEXTURE_LOAD(inputData, inputTileOffset + idx);\n  #if COMPUTE_SUM\n        result += val;\n  #else\n        result = max(result, val);\n  #endif\n      }\n    }\n  }\n\n  outputData = TEXTURE_STORE(result);\n}\n#else\nvoid main() {\n  ivec2 outputCoord = ivec2(v_texCoord * vec2(outputTileSize));\n  ivec2 texelCoord = outputCoord * spatialTileSize;\n  ivec2 sumArea = min(spatialTileSize, inputTileSize - texelCoord);\n  vec4 result = vec4(0.0);\n\n  for (int y = 0; y < sumArea.y; y++) {\n    for (int x = 0; x < sumArea.x; x++) {\n      ivec2 idx = texelCoord + ivec2(x, y);\n      vec4 val = TEXTURE_LOAD(inputData, idx);\n#if COMPUTE_SUM\n      result += val;\n#else\n      result = max(result, val);\n#endif\n    }\n  }\n\n  outputData = TEXTURE_STORE(result);\n}\n#endif\n)GLSL\";\n\ntemplate <typename T>\nvoid GLSoftmaxReduce::reduce(const GLImage<T>* input_image,\n                             const GLImage<T>* output_image,\n                             int tile_size_x,\n                             int tile_size_y) {\n  int input_slices = input_image->slices;\n  int output_slices = output_image->slices;\n\n  for (int is = 0; is < input_slices; is++) {\n    std::vector<texture_attachment> input_attachments({{input_image->textures[is], inputData}});\n    run(input_attachments,\n        {output_image->textures.begin() + is,\n         output_image->textures.begin() + is + 1},\n        [&]() {\n          glUniform2i(\n              inputTileSize->location, input_image->width, input_image->height);\n          glUniform2i(\n              outputSize->location,\n              output_image->texture_width,\n              output_image->texture_height);\n          glUniform2i(\n              outputTileSize->location,\n              output_image->width,\n              output_image->height);\n          glUniform2i(\n              tileSize->location, input_image->tile_x, input_image->tile_y);\n          glUniform2i(spatialTileSize->location, tile_size_x, tile_size_y);\n          glUniform2i(\n              inputTileRange->location,\n              0,\n              std::min(\n                  (input_image->channels + 3) / 4,\n                  input_image->tile_x * input_image->tile_y));\n        },\n        output_image->texture_width,\n        output_image->texture_height);\n  }\n}\n\nclass GLSoftmaxScale : public GLFilter {\n public:\n  binding* outputSize;\n  binding* inputData;\n  binding* maxData;\n  binding* sumData;\n\n  const std::vector<binding*> input_bindings() {\n    std::vector<binding*> bindings(\n        {BINDING(outputSize), BINDING(inputData), BINDING(maxData), BINDING(sumData)});\n    return bindings;\n  }\n\n  GLSoftmaxScale(bool _compute_exp = false, bool tiled = false)\n      : GLFilter(\n            \"GLSoftmaxScale\",\n            vertex_shader,\n            fragment_shader,\n            input_bindings(),\n            {/* no uniform blocks */},\n            {/* no attributes */},\n            {{\"COMPUTE_EXP\", caffe2::to_string((int)_compute_exp)},\n             {\"TILED_SOFTMAX\", caffe2::to_string((int)tiled)}}) {}\n\n  template <typename T>\n  void scale(const GLImage<T>* input_image,\n             const GLImage<T>* max_image,\n             const GLImage<T>* sum_image,\n             const GLImage<T>* output_image);\n\n  static const char* fragment_shader;\n};\n\ntemplate <typename T>\nvoid GLSoftmaxScale::scale(const GLImage<T>* input_image,\n                           const GLImage<T>* max_image,\n                           const GLImage<T>* sum_image,\n                           const GLImage<T>* output_image) {\n  int input_slices = input_image->slices;\n  int output_slices = output_image->slices;\n\n  for (int is = 0; is < input_slices; is++) {\n    std::vector<texture_attachment> input_attachments({{input_image->textures[is], inputData},\n                                                       {max_image->textures[is], maxData},\n                                                       {sum_image->textures[is], sumData}});\n    run(input_attachments,\n        {output_image->textures.begin() + is,\n         output_image->textures.begin() + is + 1},\n        [&]() {\n          glUniform2i(\n              outputSize->location,\n              output_image->texture_width,\n              output_image->texture_height);\n        },\n        output_image->texture_width,\n        output_image->texture_height);\n  }\n}\n\n// MARK: GLSL\n\nconst char* GLSoftmaxScale::fragment_shader = R\"GLSL(#version 300 es\n\n#define COMPUTE_EXP $(COMPUTE_EXP)\n#define TILED_SOFTMAX $(TILED_SOFTMAX)\n\nprecision highp float;\nprecision mediump int;\n\nin highp vec2 v_texCoord;\nuniform ivec2 outputSize;\n\nTEXTURE_INPUT(inputData);\nTEXTURE_INPUT(maxData);\nTEXTURE_INPUT(sumData);\nTEXTURE_OUTPUT(0, outputData);\n\nvoid main() {\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n  vec4 val = TEXTURE_LOAD(inputData, texelCoord);\n#if COMPUTE_EXP\n  vec4 maxVal = TEXTURE_LOAD(maxData, ivec2(0));\n  #if TILED_SOFTMAX\n    float singleMax = max(max(max(maxVal.x, maxVal.y), maxVal.z), maxVal.w);\n    maxVal = vec4(singleMax, singleMax, singleMax, singleMax);\n    outputData = TEXTURE_STORE(exp(val - maxVal));\n  #else\n    outputData = TEXTURE_STORE(exp(val - maxVal));\n  #endif\n\n#else\n  vec4 sumVal = TEXTURE_LOAD(sumData, ivec2(0));\n  #if TILED_SOFTMAX\n    float singleSum = sumVal.x + sumVal.y + sumVal.z + sumVal.w;\n    sumVal = vec4(singleSum, singleSum, singleSum, singleSum);\n    outputData = TEXTURE_STORE(val / sumVal);\n  #else\n    outputData = TEXTURE_STORE(val / sumVal);\n  #endif\n#endif\n\n}\n)GLSL\";\n\n#include \"../core/ImageAllocator.h\"\n#include \"caffe2/core/operator.h\"\n\n#ifndef CAFFE2_MOBILE\n#error \"Caffe2 mobile state not defined\"\n#endif\n\n#if CAFFE2_MOBILE\n\nnamespace caffe2 {\ntemplate <class T>\nclass OpenGLSoftmax final : public Operator<CPUContext>, ImageAllocator<T> {\n public:\n  OpenGLSoftmax(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        order_(StringToStorageOrder(OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    OPERATOR_NEEDS_FEATURE(this->order_ == StorageOrder::NCHW, \"OpenGL only supports NCHW order.\");\n  }\n\n  bool RunOnDevice() override {\n    const GLImageVector<T>& input = Inputs()[INPUT]->template Get<GLImageVector<T>>();\n    const int num_images = input.size();\n    const int input_channels = input.channels();\n    const int input_width = input.width();\n    const int input_height = input.height();\n\n    const int output_channels = input_channels;\n    const int output_width = input_width;\n    const int output_height = input_height;\n\n    int is_last = OperatorBase::GetSingleArgument<int>(\"is_last\", 0);\n    // For tiling\n    const int input_tile_x = input.tile_x(), input_tile_y = input.tile_y();\n    const int output_tile_x = input_tile_x, output_tile_y = input_tile_y;\n    const bool tiled = input_tile_x > 1 || input_tile_y > 1;\n    if (tiled) {\n      CAFFE_ENFORCE_EQ(\n          input.slices(), 1, \"Input needs to be tiled in a single texture\");\n    }\n\n    CAFFE_ENFORCE(\n        tiled || input_channels == 1,\n        \"Softmax only works for input_channel == 1 or input_channel > 1 with tiling enabled.\");\n\n    // for spatial dimension\n    const int tile_size_x = 16;\n    const int tile_size_y = 16;\n\n    int max_buf_width = input_width;\n    int max_buf_height = input_height;\n    int max_buf_channels = input_channels;\n    vector<GLImageVector<T>*> reduce_buf;\n\n    while (reduce_buf.size() == 0 || (max_buf_height > tile_size_y)) {\n      max_buf_width = (max_buf_width + tile_size_x - 1) / tile_size_x;\n      max_buf_height = (max_buf_height + tile_size_y - 1) / tile_size_y;\n      if (tiled) {\n        // since we are summing over all the channels within a channel tile\n        max_buf_channels =\n            (max_buf_channels + input_tile_x * input_tile_y - 1) /\n            (input_tile_x + input_tile_y);\n      }\n      reduce_buf.push_back(ImageAllocator<T>::newImage(\n          1,\n          max_buf_width,\n          max_buf_height,\n          max_buf_channels,\n          output_tile_x,\n          output_tile_y));\n    }\n\n    GLImageVector<T>* max = ImageAllocator<T>::newImage(num_images, 1, 1, 1);\n    GLImageVector<T>* sum = ImageAllocator<T>::newImage(num_images, 1, 1, 1);\n    GLImageVector<T>* after_exp = ImageAllocator<T>::newImage(\n        num_images,\n        output_width,\n        output_height,\n        output_channels,\n        output_tile_x,\n        output_tile_y);\n    GLImageVector<T>* output_images = ImageAllocator<T>::newImage(\n        num_images,\n        output_width,\n        output_height,\n        output_channels,\n        output_tile_x,\n        output_tile_y,\n        is_last);\n\n    if (!f_max) {\n      f_max.reset(new GLSoftmaxReduce(false, tiled, input_tile_x));\n      f_exp.reset(new GLSoftmaxScale(true, tiled));\n      f_sum.reset(new GLSoftmaxReduce(true, tiled, input_tile_x));\n      f_scale.reset(new GLSoftmaxScale(false, tiled));\n    }\n\n    for (int i = 0; i < num_images; i++) {\n      auto input_image = input[i];\n      auto max_image = (*max)[i];\n      auto sum_image = (*sum)[i];\n      auto after_exp_image = (*after_exp)[i];\n      auto output_image = (*output_images)[i];\n      // Get Max\n      for (int ir = 0; ir < reduce_buf.size() + 1; ir++) {\n        const GLImage<T>* in = ir == 0 ? input_image : (*reduce_buf[ir - 1])[0];\n        GLImage<T>* out = ir == reduce_buf.size() ? max_image : (*reduce_buf[ir])[0];\n\n        const int running_tile_size_x =\n            ir < reduce_buf.size() ? tile_size_x : in->width;\n        const int running_tile_size_y =\n            ir < reduce_buf.size() ? tile_size_y : in->height;\n        f_max->reduce(in, out, running_tile_size_x, running_tile_size_y);\n      }\n      // scale vals by exp(x - max)\n      f_exp->scale(input_image, max_image, sum_image, after_exp_image);\n\n      // Get sum of the exp\n      for (int ir = 0; ir < reduce_buf.size() + 1; ir++) {\n        const GLImage<T>* in = ir == 0 ? after_exp_image : (*reduce_buf[ir - 1])[0];\n        GLImage<T>* out = ir == reduce_buf.size() ? sum_image : (*reduce_buf[ir])[0];\n        const int running_tile_size_x = ir < reduce_buf.size() ? tile_size_x : in->width;\n        const int running_tile_size_y = ir < reduce_buf.size() ? tile_size_y : in->height;\n        f_sum->reduce(in, out, running_tile_size_x, running_tile_size_y);\n      }\n\n      // Scale(softmax)\n      f_scale->scale(after_exp_image, max_image, sum_image, output_image);\n    }\n\n    Outputs()[OUTPUT]->Reset(output_images);\n\n    delete sum;\n    delete max;\n    delete after_exp;\n    for (auto&& rb : reduce_buf) {\n      delete rb;\n    }\n    return true;\n  }\n\n private:\n  StorageOrder order_;\n  std::unique_ptr<GLSoftmaxReduce> f_max;\n  std::unique_ptr<GLSoftmaxScale> f_exp;\n  std::unique_ptr<GLSoftmaxReduce> f_sum;\n  std::unique_ptr<GLSoftmaxScale> f_scale;\n\n  INPUT_TAGS(INPUT, FILTER, BIAS);\n  OUTPUT_TAGS(OUTPUT);\n};\n\nREGISTER_CPU_OPERATOR(OpenGLSoftmax, OpenGLSoftmax<float16_t>);\nOPERATOR_SCHEMA(OpenGLSoftmax)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape();\n} // namespace caffe2\n#endif // CAFFE2_MOBILE\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLStylizer.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLContext.h\"\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n#include \"../core/ImageAllocator.h\"\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nenum InputFormat { BGRA = 0, RGBA = 1 };\n\nclass GLStylizer : public GLFilter {\n  binding* inputData;\n  binding* outputSize;\n  binding* mean;\n  binding* noise_std;\n  bool deprocess;\n\n public:\n  GLStylizer(bool _deprocess = false, InputFormat input_format = BGRA)\n      : GLFilter(_deprocess ? \"GLDeStylizer\" : \"GLStylizer\",\n                 vertex_shader,\n                 fragment_shader,\n                 std::vector<binding*>({BINDING(inputData), BINDING(mean), BINDING(noise_std), BINDING(outputSize)}),\n                 {/* no uniform blocks */},\n                 {/* no attributes */},\n                 {{\"DEPROCESS\", caffe2::to_string(_deprocess)}, {\"RGBAINPUT\", caffe2::to_string(input_format)}}),\n        deprocess(_deprocess) {}\n\n  template <typename T1, typename T2>\n  void stylize(const GLImage<T1>* input_image,\n               const GLImage<T2>* output_image,\n               const float mean_values[3],\n               float noise_std_value);\n\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\n\nconst char* GLStylizer::fragment_shader = R\"GLSL(#version 300 es\n\n#define DEPROCESS         $(DEPROCESS)\n#define RGBAINPUT         $(RGBAINPUT)\n\nprecision mediump float;\nprecision mediump int;\nprecision mediump sampler2D;\n\nin highp vec2 v_texCoord;\n\nuniform ivec2 outputSize;\n\nuniform vec3 mean;\nuniform float noise_std;\n\n#if DEPROCESS\nTEXTURE_INPUT(inputData);\nlayout(location = 0) out mediump vec4 outputData;\n#else\nuniform sampler2D inputData;\nTEXTURE_OUTPUT(0, outputData);\n#endif\n\n#if !DEPROCESS\n// http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/\n\nhighp float rand(vec2 co) {\n  highp float a = 12.9898;\n  highp float b = 78.233;\n  highp float c = 43758.5453;\n  highp float dt = dot(co.xy, vec2(a, b));\n  highp float sn = mod(dt, 3.14);\n  return fract(sin(sn) * c);\n}\n#endif\n\n// In AR Engine, input/output a RBGA texture; otherwise, BGRA tensor => texture\n#if RGBAINPUT\nvoid main() {\n#if DEPROCESS\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n  vec4 val = TEXTURE_LOAD(inputData, texelCoord);\n  outputData = vec4((val.rgb + mean) / 255.0, 1.0).bgra;\n#else\n  outputData = TEXTURE_STORE(vec4(255.0 * texture(inputData, v_texCoord).bgr - mean + vec3(noise_std * rand(v_texCoord)), 0.0));\n#endif\n}\n#else\nvoid main() {\n#if DEPROCESS\n  ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n  vec4 val = TEXTURE_LOAD(inputData, texelCoord);\n  outputData = vec4((val.rgb + mean) / 255.0, 1.0);\n#else\n  outputData = TEXTURE_STORE(vec4(255.0 * texture(inputData, v_texCoord).rgb - mean + vec3(noise_std * rand(v_texCoord)), 0.0));\n#endif\n}\n#endif\n)GLSL\";\n\ntemplate <typename T1, typename T2>\nvoid GLStylizer::stylize(const GLImage<T1>* input_image,\n                         const GLImage<T2>* output_image,\n                         const float mean_values[3],\n                         float noise_std_value) {\n  int input_slices = input_image->slices;\n  int output_slices = output_image->slices;\n\n  run(std::vector<texture_attachment>({{input_image->textures[0], inputData}}),\n      {output_image->textures[0]},\n      [&]() {\n        glUniform2i(outputSize->location, output_image->width, output_image->height);\n        glUniform3f(mean->location, mean_values[0], mean_values[1], mean_values[2]);\n        if (!deprocess) {\n          glUniform1f(noise_std->location, noise_std_value);\n        }\n      },\n      output_image->width,\n      output_image->height);\n}\n\nnamespace caffe2 {\nclass OpenGLTensorToTextureStylizerPreprocessOp : public Operator<CPUContext>,\n                                                  ImageAllocator<uint8_t>,\n                                                  ImageAllocator<float16_t> {\n public:\n  // Expect this many channels as input\n  static constexpr int kInputChannels = 4;\n\n  // Expect this many channels as output\n  static constexpr int kOutputChannels = 3;\n\n  USE_OPERATOR_BASE_FUNCTIONS;\n\n  OpenGLTensorToTextureStylizerPreprocessOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() {\n    const auto& input = Input(0);\n    const auto& mean = Input(1);\n\n    CAFFE_ENFORCE(input.ndim() == 4);\n\n    const int num_images = input.dim32(0);\n    const int input_height = input.dim32(1);\n    const int input_width = input.dim32(2);\n    const int input_channels = input.dim32(3);\n\n    CAFFE_ENFORCE(input.dim32(0) == 1); // N == 1\n    CAFFE_ENFORCE(input_channels == kInputChannels);\n    CAFFE_ENFORCE(mean.size() == kOutputChannels); // Assume BGR or BGRA\n\n    // get the buffers from input tensors\n    const float* mean_buffer = mean.template data<float>();\n    const uint8_t* input_buffer = input.template data<uint8_t>();\n\n    // set up the OpenGL context\n    GLContext::getGLContext()->set_context();\n\n    GLImageVector<float16_t>* output_images = ImageAllocator<float16_t>::newImage(num_images,\n                                                                                  input_width,\n                                                                                  input_height,\n                                                                                  kOutputChannels,\n#if CAFFE2_IOS\n                                                                                  true\n#else\n                                                                                  false\n#endif\n    );\n    const int tile_x = 1, tile_y = 1;\n    GLImageVector<uint8_t>* input_images = ImageAllocator<uint8_t>::newImage(\n        num_images, input_width, input_height, kInputChannels, tile_x, tile_y, false);\n    for (int i = 0; i < num_images; i++) {\n      auto input_image = (*input_images)[i];\n      auto output_image = (*output_images)[i];\n      const GLTexture* inputTexture = input_image->textures[0];\n      inputTexture->loadData(input_buffer);\n\n      if (!glStylizer_) {\n        glStylizer_.reset(new GLStylizer());\n      }\n\n      glStylizer_->stylize(\n          input_image, output_image, mean_buffer, GetSingleArgument<float>(\"noise_std\", 10.0));\n    }\n    delete input_images;\n    Outputs()[0]->Reset(output_images);\n\n    return true;\n  }\n\n private:\n  std::unique_ptr<GLStylizer> glStylizer_;\n};\n\ntemplate <InputFormat inputFormat>\nclass OpenGLTextureToTextureStylizerPreprocessOp : public Operator<CPUContext>,\n                                                   ImageAllocator<uint8_t>,\n                                                   ImageAllocator<float16_t> {\n public:\n  // Expect this many channels as input\n  static constexpr int kInputChannels = 4;\n\n  // Expect this many channels as output\n  static constexpr int kOutputChannels = 3;\n\n  USE_OPERATOR_BASE_FUNCTIONS;\n\n  OpenGLTextureToTextureStylizerPreprocessOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() {\n    const GLImageVector<uint8_t>& input = Inputs()[0]->template Get<GLImageVector<uint8_t>>();\n    const auto& mean = Input(1);\n\n    const int num_images = input.size();\n    const int input_height = input.height();\n    const int input_width = input.width();\n    const int input_channels = input.channels();\n\n    CAFFE_ENFORCE_GT(num_images, 0);\n    CAFFE_ENFORCE(input[0]->slices == 1); // N == 1\n    CAFFE_ENFORCE(input_channels == kInputChannels);\n    CAFFE_ENFORCE(mean.size() == kOutputChannels); // Assume BGR or BGRA\n\n    // get the buffers from input tensors\n    const float* mean_buffer = mean.template data<float>();\n\n    GLImageVector<float16_t>* output_images = ImageAllocator<float16_t>::newImage(\n        num_images, input_width, input_height, kOutputChannels, false);\n\n    if (!glStylizer_) {\n      glStylizer_.reset(new GLStylizer(false, inputFormat));\n    }\n    for (int i = 0; i < num_images; i++) {\n      auto input_image = input[i];\n      auto output_image = (*output_images)[i];\n      glStylizer_->stylize(\n          input_image, output_image, mean_buffer, GetSingleArgument<float>(\"noise_std\", 10.0));\n    }\n    Outputs()[0]->Reset(output_images);\n\n    return true;\n  }\n\n private:\n  std::unique_ptr<GLStylizer> glStylizer_;\n};\n\nREGISTER_CPU_OPERATOR(OpenGLTensorToTextureStylizerPreprocess,\n                      OpenGLTensorToTextureStylizerPreprocessOp);\nOPERATOR_SCHEMA(OpenGLTensorToTextureStylizerPreprocess).NumInputs(2).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(OpenGLTextureToTextureStylizerPreprocess,\n                      OpenGLTextureToTextureStylizerPreprocessOp<RGBA>);\nOPERATOR_SCHEMA(OpenGLTextureToTextureStylizerPreprocess).NumInputs(2).NumOutputs(1);\n\nclass OpenGLTextureToTensorStylizerDeprocessOp : public Operator<CPUContext>,\n                                                 ImageAllocator<uint8_t> {\n public:\n  using Operator<CPUContext>::Operator;\n\n  // Expect this many channels as input\n  static constexpr int kInputChannels = 3;\n\n  // Expect this many channels as output\n  static constexpr int kOutputChannels = 4;\n\n  bool RunOnDevice() {\n    const GLImageVector<float16_t>& input = Inputs()[0]->template Get<GLImageVector<float16_t>>();\n    const auto& mean = Input(1);\n    auto* output = Output(0);\n\n    const int num_images = input.size(), channels = input.channels(), height = input.height(),\n              width = input.width();\n    // Assume BGR or BGRA\n    CAFFE_ENFORCE(mean.size() == kInputChannels);\n    CAFFE_ENFORCE(channels == kInputChannels);\n    // RGB\n    output->Resize(num_images, height, width, kOutputChannels);\n\n    const auto* mean_data = mean.template data<float>();\n    auto* output_buffer = output->template mutable_data<uint8_t>();\n\n    GLImageVector<uint8_t>* output_images =\n        ImageAllocator<uint8_t>::newImage(num_images, width, height, kOutputChannels, true);\n\n    if (!glStylizer_) {\n      glStylizer_.reset(new GLStylizer(true));\n    }\n\n    for (int i = 0; i < num_images; i++) {\n      auto input_image = input[i];\n      auto output_image = (*output_images)[i];\n      glStylizer_->stylize(input_image, output_image, mean_data, 0);\n\n      output_image->textures[0]->map_read([&](const void* buffer,\n                                              size_t width,\n                                              size_t height,\n                                              size_t stride,\n                                              size_t channels,\n                                              const GLTexture::Type& type) {\n        if (width == stride) {\n          memcpy(output_buffer, buffer, channels * width * height);\n        } else {\n          typedef uint8_t(input_data_t)[height][stride][channels];\n          typedef uint8_t(output_data_t)[height][width][channels];\n\n          const input_data_t& input_data = *reinterpret_cast<const input_data_t*>(buffer);\n          output_data_t& output_data = *reinterpret_cast<output_data_t*>(output_buffer);\n\n          for (int y = 0; y < height; y++) {\n            memcpy(output_data[y], input_data[y], channels * width);\n          }\n        }\n      });\n    }\n    delete output_images;\n\n    return true;\n  }\n\n private:\n  std::unique_ptr<GLStylizer> glStylizer_;\n};\n\ntemplate <InputFormat inputFormat>\nclass OpenGLTextureToTextureStylizerDeprocessOp : public Operator<CPUContext>,\n                                                  ImageAllocator<uint8_t> {\n public:\n  using Operator<CPUContext>::Operator;\n\n  // Expect this many channels as input\n  static constexpr int kInputChannels = 3;\n\n  // Expect this many channels as output\n  static constexpr int kOutputChannels = 4;\n\n  bool RunOnDevice() {\n    const GLImageVector<float16_t>& input = Inputs()[0]->template Get<GLImageVector<float16_t>>();\n    const auto& mean = Input(1);\n\n    const int num_images = input.size(), channels = input.channels(), height = input.height(),\n              width = input.width();\n\n    CAFFE_ENFORCE(mean.size() == kInputChannels);\n    CAFFE_ENFORCE(channels == kInputChannels);\n\n    const auto* mean_data = mean.template data<float>();\n\n    // Use foreignTextureAllocator inside GLContext\n    // glDeleteTexture will not be called from inside caffe2 for this texture\n    GLImageVector<uint8_t>* output_images;\n    auto textureAllocator = GLContext::getGLContext()->getTextureAllocator();\n    const int tile_x = 1, tile_y = 1;\n    if (textureAllocator != nullptr) {\n      output_images = ImageAllocator<uint8_t>::newImage(\n          num_images, width, height, kOutputChannels, tile_x, tile_y, textureAllocator);\n    } else {\n      // fallback when textureAllocator is not set\n      output_images = ImageAllocator<uint8_t>::newImage(num_images, width, height, kOutputChannels);\n    }\n\n    if (!glStylizer_) {\n      glStylizer_.reset(new GLStylizer(true, inputFormat));\n    }\n\n    for (int i = 0; i < num_images; i++) {\n      auto input_image = input[i];\n      auto output_image = (*output_images)[i];\n      glStylizer_->stylize(input_image, output_image, mean_data, 0);\n    }\n\n    Outputs()[0]->Reset(output_images);\n\n    return true;\n  }\n\n private:\n  std::unique_ptr<GLStylizer> glStylizer_;\n};\n\nREGISTER_CPU_OPERATOR(OpenGLTextureToTensorStylizerDeprocess,\n                      OpenGLTextureToTensorStylizerDeprocessOp);\nOPERATOR_SCHEMA(OpenGLTextureToTensorStylizerDeprocess).NumInputs(2).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(OpenGLTextureToTextureStylizerDeprocess,\n                      OpenGLTextureToTextureStylizerDeprocessOp<RGBA>);\nOPERATOR_SCHEMA(OpenGLTextureToTextureStylizerDeprocess).NumInputs(2).NumOutputs(1);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/GLSub.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"../core/GLFilter.h\"\n#include \"../core/GLImage.h\"\n#include \"../core/ImageAllocator.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include <iostream>\n#include <vector>\n\nclass GLSub : public GLFilter {\n public:\n  binding* inputData[2];\n  binding* outputSize;\n\n  GLSub()\n      : GLFilter(\"GLSub\",\n                 vertex_shader,\n                 fragment_shader,\n                 std::vector<binding*>({BINDING(outputSize), BINDING(inputData[0]), BINDING(inputData[1])}),\n                 {/* no uniform blocks */},\n                 {/* no attributes */},\n                 {/* no replacements */}) {}\n\n  template <typename T>\n  void sub(const GLImageVector<T>& input_image0,\n           const GLImageVector<T>& input_image1,\n           const GLImageVector<T>& output_image);\n\n  static const char* fragment_shader;\n};\n\n// MARK: GLSL\n\nconst char* GLSub::fragment_shader = R\"GLSL(#version 300 es\n\nprecision mediump float;\nprecision mediump int;\n\nin highp vec2 v_texCoord;\n\nuniform ivec2 outputSize;\n\nTEXTURE_INPUT(inputData[2]);\nTEXTURE_OUTPUT(0, outputData);\n\nvoid main() {\n    ivec2 texelCoord = ivec2(v_texCoord * vec2(outputSize));\n    vec4 A = TEXTURE_LOAD(inputData[0], texelCoord);\n    vec4 B = TEXTURE_LOAD(inputData[1], texelCoord);\n    vec4 value = A - B;\n    outputData = TEXTURE_STORE(value);}\n\n)GLSL\";\n\ntemplate <typename T>\nvoid GLSub::sub(const GLImageVector<T>& input_images0,\n                const GLImageVector<T>& input_images1,\n                const GLImageVector<T>& output_images) {\n  const int num_images = input_images0.size();\n  for (int i = 0; i < num_images; i++) {\n    GLImage<T>* input_image0 = input_images0[i];\n    GLImage<T>* input_image1 = input_images1[i];\n    int input_slices = input_image0->slices;\n    GLImage<T>* output_image = output_images[i];\n    int output_slices = output_image->slices;\n\n    for (int is = 0; is < input_slices; is++) {\n      std::vector<texture_attachment> input_attachments;\n      input_attachments.push_back({input_image0->textures[is], inputData[0]});\n      input_attachments.push_back({input_image1->textures[is], inputData[1]});\n\n      run(input_attachments,\n          {output_image->textures.begin() + is, output_image->textures.begin() + is + 1},\n          [&]() { glUniform2i(outputSize->location, output_image->width, output_image->height); },\n          output_image->width,\n          output_image->height);\n    }\n  }\n}\n\nnamespace caffe2 {\ntemplate <typename T>\nclass OpenGLSubOp final : public Operator<CPUContext>, ImageAllocator<T> {\n public:\n  OpenGLSubOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(OperatorBase::HasArgument(\"broadcast\") == false, \"OpenGLSub does not support broadcast\");\n\n    OPERATOR_NEEDS_FEATURE(OperatorBase::HasArgument(\"axis\") == false, \"OpenGLSub does not support axis\");\n  }\n\n  bool RunOnDevice() override {\n    const GLImageVector<T>& input0 = Inputs()[0]->template Get<GLImageVector<T>>();\n    const GLImageVector<T>& input1 = Inputs()[1]->template Get<GLImageVector<T>>();\n\n    CAFFE_ENFORCE_EQ(input0.size(), input1.size());\n\n    const int num_images = input0.size();\n    const int input_channels = input0.channels();\n    const int input_width = input0.width();\n    const int input_height = input0.height();\n    CAFFE_ENFORCE_EQ(input1.channels(), input_channels);\n    CAFFE_ENFORCE_EQ(input1.width(), input_width);\n    CAFFE_ENFORCE_EQ(input1.height(), input_height);\n\n    const int output_channels = input_channels;\n    const int output_width = input_width;\n    const int output_height = input_height;\n\n    int is_last = OperatorBase::GetSingleArgument<int>(\"is_last\", 0);\n\n    GLImageVector<T>* output = ImageAllocator<T>::newImage(\n        num_images, output_width, output_height, output_channels, is_last);\n\n    if (!_sub) {\n      _sub.reset(new GLSub());\n    }\n\n    _sub->sub(input0, input1, *output);\n\n    Outputs()[0]->Reset(output);\n\n    return true;\n  }\n\n private:\n  std::unique_ptr<GLSub> _sub;\n};\n\nREGISTER_CPU_OPERATOR(OpenGLSub, OpenGLSubOp<float16_t>);\nOPERATOR_SCHEMA(OpenGLSub).NumInputs(2).NumOutputs(1);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/operators/gl_tiling_utils.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n#include <cmath>\n\nstruct point {\n  int x;\n  int y;\n};\n\nstruct tile_descriptor {\n  point tile_dims;\n  point tile_size;\n  int tiles;\n};\n\nnamespace caffe2 {\ninline static void squareFactors(int N, int& r1, int& r2) {\n  int f = sqrt(N);\n\n  if (f * f == N) {\n    r1 = r2 = f;\n  } else {\n    while (N % f != 0) {\n      f--;\n    }\n    r1 = N / f;\n    r2 = f;\n  }\n}\n\ninline static void computeOutputTiles(int output_channels, int& output_tile_x, int& output_tile_y) {\n  squareFactors((output_channels + 3) / 4, output_tile_x, output_tile_y);\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/test/TestGLConvolution.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/utils/math.h\"\n\n#include \"../core/GL.h\"\n#include \"../core/GLLogging.h\"\n#include \"../core/arm_neon_support.h\"\n#include \"../operators/gl_tiling_utils.h\"\n#include \"TestGLConvolution.h\"\n\n#include <vector>\n\nvoid AddNoiseInput(const std::vector<caffe2::TIndex>& shape,\n                   const std::string& name,\n                   caffe2::Workspace* ws) {\n  caffe2::CPUContext context;\n  caffe2::Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<caffe2::TensorCPU>();\n  tensor->Resize(shape);\n\n  caffe2::math::RandGaussian<float, caffe2::CPUContext>(\n      tensor->size(), 0.0f, 10.0f, tensor->mutable_data<float>(), &context);\n}\n\ndouble BenchOp(const std::string& typ,\n               int inputC,\n               int outputC,\n               int kW,\n               int kH,\n               int stride,\n               int inW,\n               int inH,\n               bool transposed,\n               caffe2::Workspace* ws = nullptr) {\n  caffe2::Workspace localWs;\n  if (!ws) {\n    ws = &localWs;\n  }\n\n  const char* engine = transposed ? \"MOBILE\" : \"NNPACK\";\n\n  caffe2::OperatorDef def1;\n  def1.set_name(\"test\");\n  def1.set_type(typ);\n  def1.set_engine(engine);\n  def1.add_input(\"X\");\n  def1.add_input(\"W\");\n  def1.add_input(\"B\");\n  def1.add_output(\"Y\");\n\n  def1.add_arg()->CopyFrom(caffe2::MakeArgument(\"kernel_h\", kH));\n  def1.add_arg()->CopyFrom(caffe2::MakeArgument(\"kernel_w\", kW));\n  def1.add_arg()->CopyFrom(caffe2::MakeArgument(\"stride_h\", stride));\n  def1.add_arg()->CopyFrom(caffe2::MakeArgument(\"stride_w\", stride));\n  def1.add_arg()->CopyFrom(caffe2::MakeArgument(\"pad_t\", 0));\n  def1.add_arg()->CopyFrom(caffe2::MakeArgument(\"pad_l\", 0));\n  def1.add_arg()->CopyFrom(caffe2::MakeArgument(\"pad_b\", 0));\n  def1.add_arg()->CopyFrom(caffe2::MakeArgument(\"pad_r\", 0));\n  def1.add_arg()->CopyFrom(caffe2::MakeArgument(\"convolution_transform_strategy\", std::string(\"PRECOMPUTE\")));\n\n  AddNoiseInput(std::vector<caffe2::TIndex>{1, inputC, inH, inW}, \"X\", ws);\n  if (transposed) {\n    AddNoiseInput(std::vector<caffe2::TIndex>{inputC, outputC, kH, kW}, \"W\", ws);\n  } else {\n    AddNoiseInput(std::vector<caffe2::TIndex>{outputC, inputC, kH, kW}, \"W\", ws);\n  }\n  AddNoiseInput(std::vector<caffe2::TIndex>{outputC}, \"B\", ws);\n\n  std::unique_ptr<caffe2::OperatorBase> op1(CreateOperator(def1, ws));\n\n  // Measure one iteration\n  caffe2::Timer timer;\n  timer.Start();\n\n  op1->Run();\n\n  float one_iteration = timer.MilliSeconds();\n\n  int target_iterations = std::max((int)(1000 / one_iteration), 1);\n  int warmup_iterations = std::max((int)(200 / one_iteration), 1);\n\n  // warm up\n  for (int i = 0; i < warmup_iterations; i++) {\n    op1->Run();\n  }\n\n  timer.Start();\n\n  int runs = target_iterations;\n  for (int i = 0; i < runs; i++) {\n    op1->Run();\n  }\n\n  auto total_t = timer.MilliSeconds();\n\n  gl_log(GL_LOG,\n         \"%s(%d -> %d, %dx%d - %dx%d - %s) took: %.4f ms/iter\\n\",\n         typ.c_str(),\n         inputC,\n         outputC,\n         inW,\n         inH,\n         kW,\n         kH,\n         engine,\n         timer.MilliSeconds() / (float)runs);\n  return double(total_t) / runs;\n}\n\ntemplate <typename T>\nstatic double BenchGLConvolution(int input_channels,\n                                 int output_channels,\n                                 int kernel_width,\n                                 int kernel_height,\n                                 int input_width,\n                                 int input_height,\n                                 int input_padding,\n                                 int input_stride,\n                                 bool transposed,\n                                 caffe2::Workspace* ws = nullptr) {\n  int tile_x = 1, tile_y = 1;\n  caffe2::squareFactors((input_channels + 3) / 4, tile_x, tile_y);\n\n  gl_log(GL_LOG, \"Input Tiles Factors: %d, %d\\n\", tile_x, tile_y);\n\n  caffe2::Workspace localWs;\n  if (!ws) {\n    ws = &localWs;\n  }\n\n  AddNoiseInput(\n      std::vector<caffe2::TIndex>{1, input_channels, input_height, input_width}, \"X_cpu\", ws);\n  if (transposed) {\n    AddNoiseInput(\n        std::vector<caffe2::TIndex>{input_channels, output_channels, kernel_height, kernel_width},\n        \"W\",\n        ws);\n  } else {\n    AddNoiseInput(\n        std::vector<caffe2::TIndex>{output_channels, input_channels, kernel_height, kernel_width},\n        \"W\",\n        ws);\n  }\n  AddNoiseInput(std::vector<caffe2::TIndex>{output_channels}, \"b\", ws);\n\n  caffe2::NetDef netdef;\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_x\");\n      arg.set_i(tile_x);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_y\");\n      arg.set_i(tile_y);\n    }\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(transposed ? \"OpenGLConvTranspose\" : \"OpenGLConv\");\n    op.add_input(\"X_gl\");\n    {\n      op.add_input(\"W\");\n      op.add_input(\"b\");\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"order\");\n      arg.set_s(\"NCHW\");\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"kernel\");\n      arg.set_i(kernel_height);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"pad\");\n      arg.set_i(input_padding);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"stride\");\n      arg.set_i(input_stride);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"is_last\");\n      arg.set_i(1);\n    }\n    op.add_output(\"Y_gl\");\n  }\n\n  std::vector<std::unique_ptr<caffe2::OperatorBase>> ops;\n\n  for (auto& op : netdef.op()) {\n    ops.push_back(CreateOperator(op, ws));\n  }\n\n  // Run the Copy Operator\n  ops[0]->Run();\n\n  // Make sure the tested operator is precompiled\n  ops[1]->Run();\n  glFinish();\n\n  // Measure one iteration\n  caffe2::Timer timer;\n  timer.Start();\n\n  ops[1]->Run();\n  glFinish();\n\n  float one_iteration = timer.MilliSeconds();\n\n  int target_iterations = std::max((int)(1000 / one_iteration), 1);\n  int warmup_iterations = std::max((int)(200 / one_iteration), 1);\n\n  // warm up\n  for (int i = 0; i < warmup_iterations; i++) {\n    ops[1]->Run();\n  }\n  glFinish();\n\n  timer.Start();\n\n  int runs = target_iterations;\n  for (int i = 0; i < runs; i++) {\n    ops[1]->Run();\n  }\n  glFinish();\n\n  const double gpuIterTime = double(timer.MilliSeconds()) / runs;\n\n  gl_log(GL_LOG,\n         \"%s(%d -> %d, %dx%d - %dx%d - OpenGL) took: %.4f ms/iter\\n\",\n         transposed ? \"ConvTranspose\" : \"Conv\",\n         input_channels,\n         output_channels,\n         input_width,\n         input_height,\n         kernel_width,\n         kernel_height,\n         gpuIterTime);\n\n  return gpuIterTime;\n}\n\nvoid TestGLConvolution() {\n  caffe2::Workspace ws;\n  ws.GetThreadPool()->setMinWorkSize(0);\n\n  // small input sizes\n  // std::vector<int> sizes({14, 26, 52, 104});\n  // std::vector<int> channels({128, 64}); // not working for 512 and 256 channels yet\n  // std::vector<int> channels({512, 256, 128, 64});\n\n  // large input sizes\n  // std::vector<int> sizes({208, 312, 416, 720, 1080});\n  // std::vector<int> channels({16, 4});\n  //\n  std::vector<int> sizes({14, 26, 52, 104, 208});\n  // std::vector<int> channels({24, 16, 4});\n\n  //  std::vector<int> sizes({14});\n  std::vector<int> channels({32, 64, 128, 192, 256, 384, 512});\n\n  std::vector<int> kernels({3});\n\n  bool transposed = false;\n\n  int stride = 1;\n\n  for (const auto& space : sizes) {\n    for (const auto& input_channel : channels) {\n      int output_channel = input_channel;\n      /* for (const auto& output_channel : channels) */ {\n        for (const auto& kernel : kernels) {\n          const double gpuIterTime = BenchGLConvolution<float16_t>(\n              input_channel, output_channel, kernel, kernel, space, space, 0, stride, transposed, &ws);\n          const double cpuIterTime = BenchOp(transposed ? \"ConvTranspose\" : \"Conv\",\n                                             input_channel,\n                                             output_channel,\n                                             kernel,\n                                             kernel,\n                                             stride,\n                                             space,\n                                             space,\n                                             transposed,\n                                             &ws);\n          const double flops       = double(input_channel) * output_channel * kernel * kernel *\n                               (kernel == 1 ? space : space - 2) * (kernel == 1 ? space : space - 2) * 2;\n          // gl_log(GL_LOG,\n          printf(\n              \"Conv: X: %ix%i  \\tC: %i -> %i\\tK: %ix%i\\t16b GPU GFLOPS: %.2f\\t32b CPU GFLOPS:\"\n              \"%.2f\\tratio: \"\n              \"%.2f\\n\",\n              space,\n              space,\n              input_channel,\n              output_channel,\n              kernel,\n              kernel,\n              flops / gpuIterTime / 1E6,\n              flops / cpuIterTime / 1E6,\n              cpuIterTime / gpuIterTime);\n        }\n      }\n    }\n  }\n\n  //  // ConvTranspose\n  //  BenchGLConvolution<float16_t>(16, 16, 3, 3, 640, 360, 0, 2, true);\n  //  BenchGLConvolution<float16_t>(16, 16, 4, 4, 640, 360, 0, 2, true);\n  //  BenchGLConvolution<float16_t>(16, 16, 5, 5, 640, 360, 0, 2, true);\n  //  BenchGLConvolution<float16_t>(16, 16, 6, 6, 640, 360, 0, 2, true);\n  //  BenchGLConvolution<float16_t>(16, 16, 7, 7, 640, 360, 0, 2, true);\n  //  BenchGLConvolution<float16_t>(16, 16, 8, 8, 640, 360, 0, 2, true);\n  //  BenchGLConvolution<float16_t>(16, 16, 9, 9, 640, 360, 0, 2, true);\n  //\n  //  BenchOp(\"ConvTranspose\", 16, 16, 3, 3, 2, 640, 360, true);\n  //  BenchOp(\"ConvTranspose\", 16, 16, 4, 4, 2, 640, 360, true);\n  //  BenchOp(\"ConvTranspose\", 16, 16, 5, 5, 2, 640, 360, true);\n  //  BenchOp(\"ConvTranspose\", 16, 16, 6, 6, 2, 640, 360, true);\n  //  BenchOp(\"ConvTranspose\", 16, 16, 7, 7, 2, 640, 360, true);\n  //  BenchOp(\"ConvTranspose\", 16, 16, 8, 8, 2, 640, 360, true);\n  //  BenchOp(\"ConvTranspose\", 16, 16, 9, 9, 2, 640, 360, true);\n  //\n  //  // Conv\n  //  BenchGLConvolution<float16_t>(16, 16, 3, 3, 1280, 720, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(16, 16, 4, 4, 1280, 720, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(16, 16, 5, 5, 1280, 720, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(16, 16, 6, 6, 1280, 720, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(16, 16, 7, 7, 1280, 720, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(16, 16, 8, 8, 1280, 720, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(16, 16, 9, 9, 1280, 720, 0, 1, false);\n  //\n  //  BenchOp(\"Conv\", 16, 16, 3, 3, 1, 1280, 720, false);\n  //  BenchOp(\"Conv\", 16, 16, 4, 4, 1, 1280, 720, false);\n  //  BenchOp(\"Conv\", 16, 16, 5, 5, 1, 1280, 720, false);\n  //  BenchOp(\"Conv\", 16, 16, 6, 6, 1, 1280, 720, false);\n  //  BenchOp(\"Conv\", 16, 16, 7, 7, 1, 1280, 720, false);\n  //  BenchOp(\"Conv\", 16, 16, 8, 8, 1, 1280, 720, false);\n  //  BenchOp(\"Conv\", 16, 16, 9, 9, 1, 1280, 720, false);\n\n  //  BenchGLConvolution<float16_t>(16, 16, 3, 3, 80, 45, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(16, 16, 3, 3, 160, 90, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(16, 16, 3, 3, 320, 180, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(16, 16, 3, 3, 640, 360, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(16, 16, 3, 3, 1280, 720, 0, 1, false);\n  //\n  //  BenchOp(\"Conv\", 16, 16, 3, 3, 1, 80, 45, false);\n  //  BenchOp(\"Conv\", 16, 16, 3, 3, 1, 160, 90, false);\n  //  BenchOp(\"Conv\", 16, 16, 3, 3, 1, 320, 180, false);\n  //  BenchOp(\"Conv\", 16, 16, 3, 3, 1, 640, 360, false);\n  //  BenchOp(\"Conv\", 16, 16, 3, 3, 1, 1280, 720, false);\n  //\n  //  BenchGLConvolution<float16_t>(128, 128, 3, 3, 14, 14, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(256, 256, 3, 3, 14, 14, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(128, 128, 3, 3, 28, 28, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(256, 256, 3, 3, 28, 28, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(128, 128, 3, 3, 56, 56, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(256, 256, 3, 3, 56, 56, 0, 1, false);\n  //  BenchGLConvolution<float16_t>(64, 64, 7, 7, 128, 128, 0, 1, false);\n  //\n  //  BenchOp(\"Conv\", 128, 128, 3, 3, 1, 14, 14, false);\n  //  BenchOp(\"Conv\", 256, 256, 3, 3, 1, 14, 14, false);\n  //  BenchOp(\"Conv\", 128, 128, 3, 3, 1, 28, 28, false);\n  //  BenchOp(\"Conv\", 256, 256, 3, 3, 1, 28, 28, false);\n  //  BenchOp(\"Conv\", 128, 128, 3, 3, 1, 56, 56, false);\n  //  BenchOp(\"Conv\", 256, 256, 3, 3, 1, 56, 56, false);\n  //  BenchOp(\"Conv\", 64, 64, 7, 7, 1, 128, 128, false);\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/test/TestGLConvolution.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\nvoid TestGLConvolution();\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/test/opengl_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"opengl_test.h\"\n\n#include \"../core/GLContext.h\"\n#include \"../core/GLImageAllocator.h\"\n#include \"../core/GLLogging.h\"\n#include \"../core/ImageAllocator.h\"\n#include \"../core/arm_neon_support.h\"\n#include \"../core/rewrite_net.h\"\n#include \"../operators/gl_tiling_utils.h\"\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\n#ifdef CAFFE2_USE_MPSCNN\n#include \"caffe2/mobile/contrib/ios/mpscnn/mpscnn.h\"\n#endif\n\n#define DEBUGGING false\n\nnamespace caffe2 {\n\ntemplate <class T>\nfloat absolute_error(T t1, T t2) {\n  return std::abs((float)t1 - (float)t2);\n}\n\ntemplate <class T>\nfloat relative_error(T t1, T t2) {\n  return t2 != 0 ? absolute_error(t1, t2) / (float)t2 : 1;\n}\n\n// OpenGL: t1, CPU: t2\nvoid checkError1D(const TensorCPU& t1, const TensorCPU& t2, float error) {\n  CAFFE_ENFORCE_EQ(t1.size(), t2.size());\n#if DEBUGGING\n  gl_log(GL_LOG, \"OpenGL output:\\n\");\n  for (int i = 0; i < t1.size(); i++) {\n    gl_log(GL_LOG, \"%.5f\\t\", t1.template data<float>()[i]);\n  }\n  gl_log(GL_LOG, \"\\n\");\n  gl_log(GL_LOG, \"CPU output:\\n\");\n  for (int i = 0; i < t2.size(); i++) {\n    gl_log(GL_LOG, \"%.5f\\t\", t2.template data<float>()[i]);\n  }\n  gl_log(GL_LOG, \"\\n\");\n\n#else\n  int count = 0;\n  if (t1.template IsType<float>()) {\n    for (auto i = 0; i < t1.size(); ++i) {\n      const float t1_i = t1.template data<float>()[i];\n      const float t2_i = t2.template data<float>()[i];\n\n      if (!(absolute_error(t1_i, t2_i) <= error || relative_error(t1_i, t2_i) <= 0.08)) {\n        gl_log(GL_ERR,\n               \"i: %d, GL: %.2f, CPU: %.2f, absolute error: %.2f, relative error: %.2f%%\\n\",\n               i,\n               t1_i,\n               t2_i,\n               absolute_error(t1_i, t2_i),\n               relative_error(t1_i, t2_i) * 100);\n        if (count++ == 10) {\n          CAFFE_THROW(\"--- Test Failed ---\");\n        }\n      }\n    }\n  }\n#endif\n}\n\n// OpenGL: t1, CPU: t2\nvoid checkError(const TensorCPU& t1, const TensorCPU& t2, float error) {\n  CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n#if DEBUGGING\n  gl_log(GL_LOG, \"opengl_test output\\n\");\n  gl_log(GL_LOG, \"\\nOpenGL output:\\n\");\n  for (int i = 0; i < t1.size(); i++) {\n    if (t1.ndim() > 2 && i % t1.dim(2) == 0) {\n      gl_log(GL_LOG, \"\\n\");\n    }\n    if (t1.ndim() > 2 && i != 0 && i % (4 * t2.dim(2) * t2.dim(3)) == 0) {\n      gl_log(GL_LOG, \"\\n\");\n    }\n    if (t1.template IsType<float>()) {\n      const float t1_i = t1.template data<float>()[i];\n      gl_log(GL_LOG, \"%.3f\\t\", t1_i);\n    } else if (t1.template IsType<uint8_t>()) {\n      const uint8_t t1_i = t1.template data<uint8_t>()[i];\n      gl_log(GL_LOG, \"%.3d\\t\", (int)t1_i);\n    }\n  }\n\n  gl_log(GL_LOG, \"\\nCPU output:\\n\");\n  for (int i = 0; i < t2.size(); i++) {\n    if (t2.ndim() > 2 && i % t2.dim(2) == 0)\n      gl_log(GL_LOG, \"\\n\");\n    if (t2.ndim() > 2 && i != 0 && i % (4 * t2.dim(2) * t2.dim(3)) == 0)\n      gl_log(GL_LOG, \"\\n\");\n    if (t2.template IsType<float>()) {\n      const float t2_i = t2.template data<float>()[i];\n      gl_log(GL_LOG, \"%.3f\\t\", t2_i);\n    } else if (t2.template IsType<uint8_t>()) {\n      const uint8_t t2_i = t2.template data<uint8_t>()[i];\n      gl_log(GL_LOG, \"%.3d\\t\", (int)t2_i);\n    }\n  }\n  gl_log(GL_LOG, \"\\n\");\n#else\n\n  int count = 0;\n  float maxError = 0, minError = 0;\n  if (t1.template IsType<float>()) {\n    for (auto i = 0; i < t1.size(); ++i) {\n      const float t1_i = t1.template data<float>()[i];\n      const float t2_i = t2.template data<float>()[i];\n      if (!(absolute_error(t1_i, t2_i) <= error || relative_error(t1_i, t2_i) <= 0.08)) {\n        if (count < 10) {\n          gl_log(GL_ERR,\n                 \"i: %d, GL: %.2f, CPU: %.2f, absolute error: %.2f, relative error: %.2f%%\\n\",\n                 i,\n                 t1_i,\n                 t2_i,\n                 absolute_error(t1_i, t2_i),\n                 relative_error(t1_i, t2_i) * 100);\n        } else {\n          CAFFE_THROW(\"--- Test Failed ---\");\n        }\n        count++;\n      }\n      float err = t1_i - t2_i;\n      if (err > maxError) {\n        maxError = err;\n      } else if (err < minError) {\n        minError = err;\n      }\n    }\n  } else if (t1.template IsType<uint8_t>()) {\n    for (auto i = 0; i < t1.size(); ++i) {\n      const uint8_t t1_i = t1.template data<uint8_t>()[i];\n      const uint8_t t2_i = t2.template data<uint8_t>()[i];\n      if (!(absolute_error(t1_i, t2_i) <= error || relative_error(t1_i, t2_i) <= 0.08)) {\n        if (count < 10) {\n          gl_log(GL_ERR,\n                 \"i: %d, GL: %d, CPU: %d, absolute error: %.2f, relative error: %.2f%%\\n\",\n                 i,\n                 t1_i,\n                 t2_i,\n                 absolute_error(t1_i, t2_i),\n                 relative_error(t1_i, t2_i) * 100);\n        } else {\n          CAFFE_THROW(\"--- Test Failed ---\");\n        }\n        count++;\n      }\n      float err = t1_i - t2_i;\n      if (err > maxError) {\n        maxError = err;\n      } else if (err < minError) {\n        minError = err;\n      }\n    }\n  }\n  gl_log(GL_LOG,\n         \"#errors = %d in %d, maxError = %f, minError = %f\\n\",\n         count,\n         (int)t1.size(),\n         maxError,\n         minError);\n#endif\n}\n\nvoid testOpenGLCopyOps(int N, int C, int H, int W, float error, int tile_x = 1, int tile_y = 1) {\n  LOG(INFO) << \"OPENGLCopyFrom/To Test\";\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, C, H, W);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n\n    // Note: may overflow for half precision\n    //    float *data = t->mutable_data<float>();\n    //    for (int i = 0; i < t->size(); i++) {\n    //      data[i] = i;\n    //    }\n  }\n\n  NetDef netdef;\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_x\");\n      arg.set_i(tile_x);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_y\");\n      arg.set_i(tile_y);\n    }\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"X_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t1 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // OpenGL\n  const auto& t2 = ws.GetBlob(\"X_cpu\")->Get<TensorCPU>(); // CPU\n  CAFFE_ENFORCE_EQ(t1.dims(), t2.dims());\n\n  checkError(t1, t2, error);\n}\n\ntypedef enum {\n  AveragePool,\n  MaxPool,\n  Conv,\n  ConvTranspose,\n  ConvPRelu,\n  ConvTransposePRelu,\n  ConvRelu,\n  ConvTransposeRelu\n} PoolOp;\n\nconst char* glPoolOperationName[] = {\"OpenGLAveragePool\",\n                                     \"OpenGLMaxPool\",\n                                     \"OpenGLConv\",\n                                     \"OpenGLConvTranspose\",\n                                     \"OpenGLConvPRelu\",\n                                     \"OpenGLConvTransposePRelu\",\n                                     \"OpenGLConvRelu\",\n                                     \"OpenGLConvTransposeRelu\"};\n\nconst char* cpuPoolOperationName[] = {\"AveragePool\",\n                                      \"MaxPool\",\n                                      \"Conv\",\n                                      \"ConvTranspose\",\n                                      \"Conv\",\n                                      \"ConvTranspose\",\n                                      \"Conv\",\n                                      \"ConvTranspose\"};\n\nvoid testOpenGLConv(int N,\n                    int C,\n                    int H,\n                    int W,\n                    int K, // output_channels\n                    int kernel_h,\n                    int kernel_w,\n                    int pad,\n                    int stride,\n                    PoolOp poolOp,\n                    float error,\n                    bool random_input     = true,\n                    int input_batch_size  = 1,\n                    int output_batch_size = 1,\n                    int input_tile_x      = 1,\n                    int input_tile_y      = 1,\n                    bool tiling           = false) {\n  LOG(INFO) << \"OpenGL Conv Test: \"\n            << \"input C: \" << C << \", output C: \" << K << \", H: \" << H << \", W: \" << W\n            << \", K: \" << kernel_w << \"x\" << kernel_h << \", P: \" << pad << \", S: \" << stride\n            << \" Op: \" << glPoolOperationName[poolOp];\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, C, H, W);\n    CPUContext ctx;\n    if (random_input) {\n      math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    } else {\n      float* data = t->mutable_data<float>();\n      for (int i = 0; i < t->size(); i++) {\n        data[i] = 1;\n      }\n    }\n#if 0\n  gl_log(GL_LOG, \"Input tensor:\");\n  for (int i = 0; i < t->size(); i++) {\n    const float t1_i = t->data<float>()[i];\n    if (i % t->dim(3) == 0)\n      gl_log(GL_LOG, \"\\n\");\n    if (i % (4 * t->dim(2) * t->dim(3)) == 0)\n      gl_log(GL_LOG, \"-------------------------------\\n\");\n    gl_log(GL_LOG, \"%.3f\\t\", t1_i);\n  }\n  gl_log(GL_LOG, \"\\n\\n\");\n#endif\n  }\n\n  if (poolOp != AveragePool && poolOp != MaxPool) {\n    auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n    if (poolOp != ConvTranspose && poolOp != ConvTransposePRelu && poolOp != ConvTransposeRelu) {\n      t->Resize(K, C, kernel_h, kernel_w);\n    } else {\n      t->Resize(C, K, kernel_h, kernel_w);\n    }\n    CPUContext ctx;\n    if (random_input) {\n      math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    } else {\n      float* data = t->mutable_data<float>();\n      // Set the weights to all 1s\n      //      for (int i = 0; i < t->size(); i++) {\n      //        data[i] = 1;\n      //      }\n\n      // Set the weights to 1s, 2s, 3s... for channel 0, 1, 2, 3...\n      int j = 0;\n      for (int i = 0; i < t->size(); i++) {\n        if (i % (C * kernel_h * kernel_w) == 0) {\n          j++;\n        }\n        data[i] = j;\n      }\n    }\n\n#if 0\n    gl_log(GL_LOG, \"Kernel (printing only the first line for each output channel):\");\n    for (int i = 0; i < t->size(); i++) {\n      if (i == 0 || i % (t->dim(1) * t->dim(2) * t->dim(3)) == 0) {\n        gl_log(GL_LOG, \"\\n\");\n        for (int j = 0; j < t->dim(3); j++) {\n          const float t1_i = t->data<float>()[i + j];\n          gl_log(GL_LOG, \"%.3f\\t\", t1_i);\n        }\n      }\n    }\n    gl_log(GL_LOG, \"\\n\");\n#endif\n\n    // bias\n    {\n      auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n      t->Resize(K);\n      CPUContext ctx;\n      if (random_input) {\n        math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n      } else {\n        // Set bias to 1\n        float* data = t->mutable_data<float>();\n        for (int i = 0; i < t->size(); i++) {\n          data[i] = i + 1;\n        }\n      }\n#if 0\n    gl_log(GL_LOG, \"Bias:\\n\");\n    for (int i = 0; i < t->size(); i++) {\n      const float t1_i = t->data<float>()[i];\n      gl_log(GL_LOG, \"%.3f\\t\", t1_i);\n    }\n    gl_log(GL_LOG, \"\\n\");\n#endif\n    }\n  }\n\n  if (poolOp == ConvPRelu || poolOp == ConvTransposePRelu) {\n    auto* t = ws.CreateBlob(\"p\")->GetMutable<TensorCPU>();\n    t->Resize(K);\n    CPUContext ctx;\n    if (random_input) {\n      math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    } else {\n      // Set prelu scale to i + 1\n      float* data = t->mutable_data<float>();\n      for (int i = 0; i < t->size(); i++) {\n        data[i] = -0.5;\n      }\n    }\n  }\n\n  NetDef netdef;\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_x\");\n      arg.set_i(input_tile_x);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_y\");\n      arg.set_i(input_tile_y);\n    }\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(glPoolOperationName[poolOp]);\n    op.add_input(\"X_gl\");\n    if (poolOp != AveragePool && poolOp != MaxPool) {\n      op.add_input(\"W\");\n      op.add_input(\"b\");\n    }\n    if (poolOp == ConvPRelu || poolOp == ConvTransposePRelu) {\n      op.add_input(\"p\");\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"order\");\n      arg.set_s(\"NCHW\");\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"kernel\");\n      arg.set_i(kernel_h);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"pad\");\n      arg.set_i(pad);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"stride\");\n      arg.set_i(stride);\n    }\n    if (poolOp != AveragePool && poolOp != MaxPool) {\n      if (tiling) {\n        {\n          auto& arg = *(op.add_arg());\n          arg.set_name(\"tiling\");\n          arg.set_i(1);\n        }\n      } else {\n        {\n          auto& arg = *(op.add_arg());\n          arg.set_name(\"input_batch_size\");\n          arg.set_i(input_batch_size);\n        }\n        {\n          auto& arg = *(op.add_arg());\n          arg.set_name(\"output_batch_size\");\n          arg.set_i(output_batch_size);\n        }\n      }\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"is_last\");\n      arg.set_i(1);\n    }\n    op.add_output(\"Y_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(cpuPoolOperationName[poolOp]);\n\n    op.add_input(\"X_cpu\");\n    if (poolOp != AveragePool && poolOp != MaxPool) {\n      op.add_input(\"W\");\n      op.add_input(\"b\");\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"order\");\n      arg.set_s(\"NCHW\");\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"kernel\");\n      arg.set_i(kernel_h);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"pad\");\n      arg.set_i(pad);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"stride\");\n      arg.set_i(stride);\n    }\n    op.add_output(\"Y_ref\");\n  }\n  if (poolOp == ConvPRelu || poolOp == ConvTransposePRelu) {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"PRelu\");\n    op.add_input(\"Y_ref\");\n    op.add_input(\"p\");\n    op.add_output(\"Y_ref\");\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"order\");\n      arg.set_s(\"NCHW\");\n    }\n  } else if (poolOp == ConvRelu || poolOp == ConvTransposeRelu) {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"Relu\");\n    op.add_input(\"Y_ref\");\n    op.add_output(\"Y_ref\");\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"order\");\n      arg.set_s(\"NCHW\");\n    }\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t1 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // OpenGL\n  const auto& t2 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // CPU\n\n  checkError(t1, t2, error);\n}\n\nvoid testOpenGLPRelu(\n    int N, int C, int H, int W, int prelu_size, int input_tile_x, int input_tile_y, float error) {\n  LOG(INFO) << \"OpenGL PRelu Test \"\n            << \"C: \" << C << \", H: \" << H << \", W: \" << W;\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, C, H, W);\n    CPUContext ctx;\n    // Too noisy.\n    math::RandGaussian<float, CPUContext>(t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n\n  // prelu scale\n  {\n    auto* t = ws.CreateBlob(\"p\")->GetMutable<TensorCPU>();\n    t->Resize(prelu_size);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n  }\n\n  NetDef netdef;\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_x\");\n      arg.set_i(input_tile_x);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_y\");\n      arg.set_i(input_tile_y);\n    }\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLPRelu\");\n    op.add_input(\"X_gl\");\n    op.add_input(\"p\");\n    op.add_output(\"Y_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"PRelu\");\n    op.add_input(\"X_cpu\");\n    op.add_input(\"p\");\n    auto& arg = *(op.add_arg());\n    arg.set_name(\"order\");\n    arg.set_s(\"NCHW\");\n    op.add_output(\"Y_ref\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // openGL\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // CPU\n\n  checkError(ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(), ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(), error);\n}\n\nvoid testOpenGLRelu(int N, int C, int H, int W, int input_tile_x, int input_tile_y, float error) {\n  LOG(INFO) << \"OpenGL Relu Test \"\n            << \"C: \" << C << \", H: \" << H << \", W: \" << W;\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, C, H, W);\n    CPUContext ctx;\n    // Too noisy.\n    math::RandGaussian<float, CPUContext>(t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n\n  NetDef netdef;\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_x\");\n      arg.set_i(input_tile_x);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_y\");\n      arg.set_i(input_tile_y);\n    }\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLRelu\");\n    op.add_input(\"X_gl\");\n    op.add_output(\"Y_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"Relu\");\n    op.add_input(\"X_cpu\");\n    auto& arg = *(op.add_arg());\n    arg.set_name(\"order\");\n    arg.set_s(\"NCHW\");\n    op.add_output(\"Y_ref\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // openGL\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // CPU\n\n  checkError(ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(), ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(), error);\n}\n\nvoid testOpenGLAdd(int N, int C, int H, int W, float error = 0.1, int input_tile_x = 1, int input_tile_y = 1) {\n  LOG(INFO) << \"OpenGL Add Test \"\n            << \"C: \" << C << \", H: \" << H << \", W: \" << W;\n  Workspace ws;\n  {\n    auto* t0 = ws.CreateBlob(\"X_cpu0\")->GetMutable<TensorCPU>();\n    t0->Resize(N, C, H, W);\n    CPUContext ctx0;\n    // Too noisy.\n    math::RandGaussian<float, CPUContext>(t0->size(), 0, 30, t0->mutable_data<float>(), &ctx0);\n\n    auto* t1 = ws.CreateBlob(\"X_cpu1\")->GetMutable<TensorCPU>();\n    t1->Resize(N, C, H, W);\n    CPUContext ctx1;\n    // Too noisy.\n    math::RandGaussian<float, CPUContext>(t1->size(), 0, 30, t1->mutable_data<float>(), &ctx1);\n  }\n\n  NetDef netdef;\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu0\");\n    op.add_output(\"X_gl0\");\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_x\");\n      arg.set_i(input_tile_x);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_y\");\n      arg.set_i(input_tile_y);\n    }\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu1\");\n    op.add_output(\"X_gl1\");\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_x\");\n      arg.set_i(input_tile_x);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"tile_y\");\n      arg.set_i(input_tile_y);\n    }\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLAdd\");\n    op.add_input(\"X_gl0\");\n    op.add_input(\"X_gl1\");\n    op.add_output(\"Y_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"Add\");\n    op.add_input(\"X_cpu0\");\n    op.add_input(\"X_cpu1\");\n    auto& arg = *(op.add_arg());\n    arg.set_name(\"order\");\n    arg.set_s(\"NCHW\");\n    op.add_output(\"Y_ref\");\n  }\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // openGL\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // CPU\n\n  checkError(t1, t2, error);\n}\n\nvoid testOpenGLSub(int N, int C, int H, int W, float error = 0.1) {\n  LOG(INFO) << \"OpenGL Sub Test \"\n            << \"C: \" << C << \", H: \" << H << \", W: \" << W;\n\n  Workspace ws;\n  {\n    auto* t0 = ws.CreateBlob(\"X_cpu0\")->GetMutable<TensorCPU>();\n    t0->Resize(N, C, H, W);\n    CPUContext ctx0;\n    // Too noisy.\n    math::RandGaussian<float, CPUContext>(t0->size(), 0, 30, t0->mutable_data<float>(), &ctx0);\n\n    auto* t1 = ws.CreateBlob(\"X_cpu1\")->GetMutable<TensorCPU>();\n    t1->Resize(N, C, H, W);\n    CPUContext ctx1;\n    // Too noisy.\n    math::RandGaussian<float, CPUContext>(t1->size(), 0, 30, t1->mutable_data<float>(), &ctx1);\n  }\n\n  NetDef netdef;\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu0\");\n    op.add_output(\"X_gl0\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu1\");\n    op.add_output(\"X_gl1\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLSub\");\n    op.add_input(\"X_gl0\");\n    op.add_input(\"X_gl1\");\n    op.add_output(\"Y_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"Sub\");\n    op.add_input(\"X_cpu0\");\n    op.add_input(\"X_cpu1\");\n    auto& arg = *(op.add_arg());\n    arg.set_name(\"order\");\n    arg.set_s(\"NCHW\");\n    op.add_output(\"Y_ref\");\n  }\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // openGL\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // CPU\n  checkError(t2, t1, error);\n}\n\nvoid testOpenGLConcat(int N, std::vector<int> Cs, int H, int W, bool tiling = false, float error = 0.1) {\n  LOG(INFO) << \"OpenGL Concat Test \"\n            << \"H: \" << H << \", W: \" << W;\n  Workspace ws;\n  for (int i = 0; i < Cs.size(); i++) {\n    auto* t = ws.CreateBlob(\"X_cpu\" + caffe2::to_string(i))->GetMutable<TensorCPU>();\n    t->Resize(N, Cs[i], H, W);\n    CPUContext ctx0;\n    // Too noisy.\n    math::RandGaussian<float, CPUContext>(t->size(), 0, 30, t->mutable_data<float>(), &ctx0);\n  }\n\n  NetDef netdef;\n  for (int i = 0; i < Cs.size(); i++) {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\" + caffe2::to_string(i));\n    op.add_output(\"X_gl\" + caffe2::to_string(i));\n    if (tiling) {\n      int tile_x = 1, tile_y = 1;\n      computeOutputTiles(Cs[i], tile_x, tile_y);\n      printf(\"Cs[i] = %d, tile_x = %d, tile_y = %d\\n\", Cs[i], tile_x, tile_y);\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"tile_x\");\n        arg.set_i(tile_x);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"tile_y\");\n        arg.set_i(tile_y);\n      }\n    }\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLConcat\");\n    for (int i = 0; i < Cs.size(); i++) {\n      op.add_input(\"X_gl\" + caffe2::to_string(i));\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"order\");\n      arg.set_s(\"NCHW\");\n    }\n    op.add_output(\"Y_gl\");\n    op.add_output(\"Y_gl_mask\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"Concat\");\n    for (int i = 0; i < Cs.size(); i++) {\n      op.add_input(\"X_cpu\" + caffe2::to_string(i));\n    }\n    auto& arg = *(op.add_arg());\n    arg.set_name(\"order\");\n    arg.set_s(\"NCHW\");\n    op.add_output(\"Y_ref\");\n    op.add_output(\"Y_ref_mask\");\n  }\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // openGL\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // CPU\n\n  checkError(ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(), ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(), error);\n}\n\nvoid testOpenGLSigmoid(int N, int C, int H, int W, float error) {\n  LOG(INFO) << \"OpenGL Sigmoid Test \"\n            << \"C: \" << C << \", H: \" << H << \", W: \" << W;\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, C, H, W);\n    CPUContext ctx;\n    // Too noisy.\n    math::RandGaussian<float, CPUContext>(t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n  }\n\n  NetDef netdef;\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLSigmoid\");\n    op.add_input(\"X_gl\");\n    op.add_output(\"Y_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"Sigmoid\");\n    op.add_input(\"X_cpu\");\n    auto& arg = *(op.add_arg());\n    arg.set_name(\"order\");\n    arg.set_s(\"NCHW\");\n    op.add_output(\"Y_ref\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // openGL\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // CPU\n\n  checkError(ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(), ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(), error);\n}\n\nvoid testOpenGLTanh(int N, int C, int H, int W, float error) {\n  LOG(INFO) << \"OpenGL Tanh Test \"\n            << \"C: \" << C << \", H: \" << H << \", W: \" << W;\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, C, H, W);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(t->size(), 0, 2, t->mutable_data<float>(), &ctx);\n  }\n\n  NetDef netdef;\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLTanh\");\n    op.add_input(\"X_gl\");\n    op.add_output(\"Y_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"Tanh\");\n    op.add_input(\"X_cpu\");\n    auto& arg = *(op.add_arg());\n    arg.set_name(\"order\");\n    arg.set_s(\"NCHW\");\n    op.add_output(\"Y_ref\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // openGL\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // CPU\n\n  checkError(ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(), ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(), error);\n}\n\nvoid testOpenGLMul(int N, int C, int H, int W, float error) {\n  LOG(INFO) << \"OpenGL Mul Test \"\n            << \"C: \" << C << \", H: \" << H << \", W: \" << W;\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, C, H, W);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(t->size(), -10, 10, t->mutable_data<float>(), &ctx);\n  }\n\n  {\n    auto* t = ws.CreateBlob(\"B\")->GetMutable<TensorCPU>();\n    t->Resize(1);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(t->size(), -10, 10, t->mutable_data<float>(), &ctx);\n  }\n\n  NetDef netdef;\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLMul\");\n    op.add_input(\"X_gl\");\n    op.add_input(\"B\");\n    op.add_output(\"Y_gl\");\n\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"broadcast\");\n      arg.set_i(1);\n    }\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"Mul\");\n    op.add_input(\"X_cpu\");\n    op.add_input(\"B\");\n\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"broadcast\");\n      arg.set_i(1);\n    }\n\n    op.add_output(\"Y_ref\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // openGL\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // CPU\n\n  checkError(ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(), ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(), error);\n}\n\nvoid testOpenGLSoftmax(int N, int D, float error, bool tiled = false) {\n  LOG(INFO) << \"OpenGL Softmax Test \"\n            << \"N: \" << N << \" D: \" << D << \" Tiled:\" << tiled;\n  Workspace ws;\n  auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n  {\n    t->Resize(N, D);\n    CPUContext ctx;\n    // Too noisy.\n    math::RandGaussian<float, CPUContext>(\n        t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n  }\n\n  NetDef netdef;\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"Reshape\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_reshaped\");\n    op.add_output(\"old_shape\");\n    auto& arg = *(op.add_arg());\n    arg.set_name(\"shape\");\n    if (tiled) {\n      arg.add_ints(N);\n      arg.add_ints(D);\n      arg.add_ints(1);\n      arg.add_ints(1);\n    } else {\n      arg.add_ints(N);\n      arg.add_ints(1);\n      arg.add_ints(D);\n      arg.add_ints(1);\n    }\n  }\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_reshaped\");\n    op.add_output(\"X_gl\");\n    if (tiled) {\n      int tile_x = 1, tile_y = 1;\n      squareFactors((D + 3) / 4, tile_x, tile_y);\n      auto& argx = *(op.add_arg());\n      argx.set_name(\"tile_x\");\n      argx.set_i(tile_x);\n      auto& argy = *(op.add_arg());\n      argy.set_name(\"tile_y\");\n      argy.set_i(tile_y);\n    }\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLSoftmax\");\n    op.add_input(\"X_gl\");\n    op.add_output(\"Y_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu0\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"Reshape\");\n    op.add_input(\"Y_cpu0\");\n    op.add_output(\"Y_cpu\");\n    op.add_output(\"old_shape\");\n    auto& arg = *(op.add_arg());\n    arg.set_name(\"shape\");\n    arg.add_ints(N);\n    arg.add_ints(D);\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"Softmax\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"Y_ref\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // OpenGL\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // CPU\n  checkError(ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(), ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(), error);\n}\n\nvoid testOpenGLInstanceNorm(int N, int C, int H, int W, float error) {\n  LOG(INFO) << \"OpenGL InstanceNorm Test \"\n            << \"C: \" << C << \", H: \" << H << \", W: \" << W;\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, C, H, W);\n    CPUContext ctx;\n    // Too noisy.\n    math::RandGaussian<float, CPUContext>(t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n    //    for (auto i = 0; i < t->size(); ++i) {\n    //      t->mutable_data<float>()[i] = 0.001;\n    //    }\n  }\n\n  // scale\n  {\n    auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n    t->Resize(C);\n    CPUContext ctx;\n    for (auto i = 0; i < t->size(); ++i) {\n      t->mutable_data<float>()[i] = (i + 1) / t->size();\n    }\n  }\n  // bias\n  {\n    auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n    t->Resize(C);\n    CPUContext ctx;\n    for (auto i = 0; i < t->size(); ++i) {\n      t->mutable_data<float>()[i] = 8 - 2 * i;\n    }\n  }\n\n  NetDef netdef;\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLInstanceNorm\");\n    op.add_input(\"X_gl\");\n    op.add_input(\"W\");\n    op.add_input(\"b\");\n    op.add_output(\"Y_gl\");\n    op.add_output(\"Mean_gl\");\n    op.add_output(\"InvStdev_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Mean_gl\");\n    op.add_output(\"Mean_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"InvStdev_gl\");\n    op.add_output(\"InvStdev_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"InstanceNorm\");\n    op.add_input(\"X_cpu\");\n    op.add_input(\"W\");\n    op.add_input(\"b\");\n    auto& arg = *(op.add_arg());\n    arg.set_name(\"order\");\n    arg.set_s(\"NCHW\");\n    op.add_output(\"Y_ref\");\n    op.add_output(\"Mean_ref\");\n    op.add_output(\"InvStdev_ref\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // openGL\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // CPU\n\n  LOG(INFO) << \"Check mean\";\n  checkError1D(\n      ws.GetBlob(\"Mean_cpu\")->Get<TensorCPU>(), ws.GetBlob(\"Mean_ref\")->Get<TensorCPU>(), 0.001);\n  LOG(INFO) << \"Check inv_stdev\";\n  checkError1D(ws.GetBlob(\"InvStdev_cpu\")->Get<TensorCPU>(),\n               ws.GetBlob(\"InvStdev_ref\")->Get<TensorCPU>(),\n               0.001);\n  LOG(INFO) << \"Check instance norm\";\n  checkError(ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(), ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(), error);\n}\n\nvoid testOpenGLInstanceNormPRelu(int N, int C, int H, int W, float error) {\n  LOG(INFO) << \"OpenGL InstanceNormPRelu Test \"\n            << \"C: \" << C << \", H: \" << H << \", W: \" << W;\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, C, H, W);\n    CPUContext ctx;\n    // Too noisy.\n    math::RandGaussian<float, CPUContext>(t->size(), 0, 30, t->mutable_data<float>(), &ctx);\n    //    for (auto i = 0; i < t->size(); ++i) {\n    //      t->mutable_data<float>()[i] = 0.001;\n    //    }\n  }\n\n  // scale\n  {\n    auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n    t->Resize(C);\n    CPUContext ctx;\n    for (auto i = 0; i < t->size(); ++i) {\n      t->mutable_data<float>()[i] = (i + 1) / t->size();\n    }\n  }\n  // bias\n  {\n    auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n    t->Resize(C);\n    CPUContext ctx;\n    for (auto i = 0; i < t->size(); ++i) {\n      t->mutable_data<float>()[i] = 8 - 2 * i;\n    }\n  }\n  // prelu scale\n  {\n    auto* t = ws.CreateBlob(\"p\")->GetMutable<TensorCPU>();\n    t->Resize(C);\n    CPUContext ctx;\n    math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n  }\n\n  NetDef netdef;\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLInstanceNormPRelu\");\n    op.add_input(\"X_gl\");\n    op.add_input(\"W\");\n    op.add_input(\"b\");\n    op.add_input(\"p\");\n    op.add_output(\"Y_gl\");\n    op.add_output(\"Mean_gl\");\n    op.add_output(\"InvStdev_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Mean_gl\");\n    op.add_output(\"Mean_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"InvStdev_gl\");\n    op.add_output(\"InvStdev_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"InstanceNorm\");\n    op.add_input(\"X_cpu\");\n    op.add_input(\"W\");\n    op.add_input(\"b\");\n    auto& arg = *(op.add_arg());\n    arg.set_name(\"order\");\n    arg.set_s(\"NCHW\");\n    op.add_output(\"Y_ref\");\n    op.add_output(\"Mean_ref\");\n    op.add_output(\"InvStdev_ref\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"PRelu\");\n    op.add_input(\"Y_ref\");\n    op.add_input(\"p\");\n    auto& arg = *(op.add_arg());\n    arg.set_name(\"order\");\n    arg.set_s(\"NCHW\");\n    op.add_output(\"Y_ref\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // openGL\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // CPU\n\n  LOG(INFO) << \"Check mean\";\n  checkError1D(\n      ws.GetBlob(\"Mean_cpu\")->Get<TensorCPU>(), ws.GetBlob(\"Mean_ref\")->Get<TensorCPU>(), 0.001);\n  LOG(INFO) << \"Check inv_stdev\";\n  checkError1D(ws.GetBlob(\"InvStdev_cpu\")->Get<TensorCPU>(),\n               ws.GetBlob(\"InvStdev_ref\")->Get<TensorCPU>(),\n               0.001);\n  LOG(INFO) << \"Check instance norm\";\n  checkError(ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(), ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(), error);\n}\n\nvoid OpenGL_speedtest(int N,\n                      int C,\n                      int H,\n                      int W,\n                      int K,\n                      int kernel_h,\n                      int kernel_w,\n                      int pad,\n                      float error,\n                      bool random_input = true) {\n  LOG(INFO) << \"OpenGL Conv Speed Test \"\n            << \" C: \" << C << \" H: \" << H << \" W: \" << W;\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, C, H, W);\n    CPUContext ctx;\n    if (random_input) {\n      math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    } else {\n      float* data = t->mutable_data<float>();\n      for (int i = 0; i < t->size(); i++) {\n        data[i] = 1;\n      }\n    }\n  }\n\n  {\n    auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n    t->Resize(K, C, kernel_h, kernel_w);\n    CPUContext ctx;\n    if (random_input) {\n      math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    } else {\n      float* data = t->mutable_data<float>();\n      for (int i = 0; i < t->size(); i++) {\n        data[i] = 1;\n      }\n    }\n  }\n\n  {\n    auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n    t->Resize(K);\n    CPUContext ctx;\n    if (random_input) {\n      math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    } else {\n      float* data = t->mutable_data<float>();\n      for (int i = 0; i < t->size(); i++) {\n        data[i] = 1;\n      }\n    }\n  }\n\n  NetDef netdef;\n  netdef.set_name(\"Test net\");\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLConv\");\n    op.add_input(\"X_gl\");\n    op.add_input(\"W\");\n    op.add_input(\"b\");\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"order\");\n      arg.set_s(\"NCHW\");\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"kernel\");\n      arg.set_i(kernel_h);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"pad\");\n      arg.set_i(pad);\n    }\n    op.add_output(\"Y_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  CAFFE_ENFORCE(ws.RunNetOnce(netdef));\n  caffe2::NetBase* net = ws.CreateNet(netdef);\n  CHECK_NOTNULL(net);\n  CAFFE_ENFORCE(net->Run());\n  net->TEST_Benchmark(1, 4, true);\n}\n\nvoid testOpenGLPadImage(\n    int N, int C, int H, int W, int pad_l, int pad_r, int pad_t, int pad_b, float error) {\n  LOG(INFO) << \"OpenGLPadImage Test\";\n  {\n    Workspace ws;\n    {\n      auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(N, C, H, W);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n      //      for (auto i = 0; i < t->size(); ++i) {\n      //        t->mutable_data<float>()[i] = i + 1;\n      //      }\n    }\n\n    NetDef netdef;\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyToOpenGL\");\n      op.add_input(\"X_cpu\");\n      op.add_output(\"X_gl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"OpenGLPadImage\");\n      op.add_input(\"X_gl\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_l\");\n        arg.set_i(pad_l);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_r\");\n        arg.set_i(pad_r);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_t\");\n        arg.set_i(pad_t);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_b\");\n        arg.set_i(pad_b);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"mode\");\n        arg.set_s(\"reflect\");\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"is_last\");\n        arg.set_i(1);\n      }\n      op.add_output(\"Y_gl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyFromOpenGL\");\n      op.add_input(\"Y_gl\");\n      op.add_output(\"Y_cpu\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"PadImage\");\n      op.add_input(\"X_cpu\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_l\");\n        arg.set_i(pad_l);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_r\");\n        arg.set_i(pad_r);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_t\");\n        arg.set_i(pad_t);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"pad_b\");\n        arg.set_i(pad_b);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"mode\");\n        arg.set_s(\"reflect\");\n      }\n      op.add_output(\"Y_ref\");\n    }\n\n    ws.RunNetOnce(netdef);\n\n    const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // opengl\n    const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // cpu\n    checkError(t2, t1, error);\n  }\n}\n\nvoid testOpenGLResize(int N,\n                      int C,\n                      int H,\n                      int W,\n                      int width_scale,\n                      int height_scale,\n                      float error,\n                      int input_tile_x = 1,\n                      int input_tile_y = 1) {\n  LOG(INFO) << \"OpenGLResize Test\";\n  {\n    Workspace ws;\n    {\n      auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n      t->Resize(N, C, H, W);\n      CPUContext ctx;\n      math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    }\n\n    NetDef netdef;\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyToOpenGL\");\n      op.add_input(\"X_cpu\");\n      op.add_output(\"X_gl\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"tile_x\");\n        arg.set_i(input_tile_x);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"tile_y\");\n        arg.set_i(input_tile_y);\n      }\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"OpenGLResizeNearest\");\n      op.add_input(\"X_gl\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"width_scale\");\n        arg.set_f(width_scale);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"height_scale\");\n        arg.set_f(height_scale);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"is_last\");\n        arg.set_i(1);\n      }\n      op.add_output(\"Y_gl\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"CopyFromOpenGL\");\n      op.add_input(\"Y_gl\");\n      op.add_output(\"Y_cpu\");\n    }\n\n    {\n      auto& op = *(netdef.add_op());\n      op.set_type(\"ResizeNearest\");\n      op.add_input(\"X_cpu\");\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"width_scale\");\n        arg.set_f(width_scale);\n      }\n      {\n        auto& arg = *(op.add_arg());\n        arg.set_name(\"height_scale\");\n        arg.set_f(height_scale);\n      }\n      op.add_output(\"Y_ref\");\n    }\n\n    ws.RunNetOnce(netdef);\n\n    const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // opengl\n    const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // cpu\n    checkError(t2, t1, error);\n  }\n}\n\nvoid testOpenGLPreprocess(int N, int C, int H, int W, float error) {\n  LOG(INFO) << \"OpenGL Preprocess Test\";\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, H, W, C);\n    CPUContext ctx;\n    for (auto i = 0; i < t->size(); ++i) {\n      t->mutable_data<uint8_t>()[i] = rand() % 255;\n    }\n  }\n\n  {\n    auto* t = ws.CreateBlob(\"mean\")->GetMutable<TensorCPU>();\n    t->Resize(3);\n    CPUContext ctx;\n    t->mutable_data<float>()[0] = 100;\n    t->mutable_data<float>()[1] = 50;\n    t->mutable_data<float>()[2] = 150;\n  }\n\n  NetDef netdef;\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLTensorToTextureStylizerPreprocess\");\n    op.add_input(\"X_cpu\");\n    op.add_input(\"mean\");\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"noise_std\");\n      arg.set_f(0.00001);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"noise_size\");\n      arg.set_i(512);\n    }\n\n    op.add_output(\"Y_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"PackedInt8BGRANHWCToNCHWCStylizerPreprocess\");\n    op.add_input(\"X_cpu\");\n    op.add_input(\"mean\");\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"noise_std\");\n      arg.set_f(0.00001);\n    }\n    {\n      auto& arg = *(op.add_arg());\n      arg.set_name(\"noise_size\");\n      arg.set_i(512);\n    }\n    op.add_output(\"Y_ref\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>(); // openGL\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>(); // cpu\n  checkError(t2, t1, error);\n}\n\nvoid testOpenGLDeprocess(int N, int C, int H, int W, float error) {\n  LOG(INFO) << \"OpenGLDeprocess Test\";\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, C, H, W);\n    CPUContext ctx;\n    for (auto i = 0; i < t->size(); ++i) {\n      t->mutable_data<float>()[i] = rand() % 1000 - 500;\n    }\n  }\n\n  {\n    auto* t = ws.CreateBlob(\"mean\")->GetMutable<TensorCPU>();\n    t->Resize(3);\n    CPUContext ctx;\n    t->mutable_data<float>()[0] = 30;\n    t->mutable_data<float>()[1] = 40;\n    t->mutable_data<float>()[2] = 50;\n  }\n\n  NetDef netdef;\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLTextureToTensorStylizerDeprocess\");\n    op.add_input(\"X_gl\");\n    op.add_input(\"mean\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"BRGNCHWCToPackedInt8BGRAStylizerDeprocess\");\n    op.add_input(\"X_cpu\");\n    op.add_input(\"mean\");\n    op.add_output(\"Y_ref\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n  checkError(t2, t1, error);\n}\n\nvoid testOpenGLNormPlanarYUV(int N, int C, int H, int W, float error) {\n  LOG(INFO) << \"OpenGLNormPlanarYUV Test\";\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, 3, H, W);\n    CPUContext ctx;\n    for (auto i = 0; i < t->size(); ++i) {\n      t->mutable_data<float>()[i] = rand() % 1000 - 500;\n    }\n  }\n\n  {\n    auto* t = ws.CreateBlob(\"mean\")->GetMutable<TensorCPU>();\n    t->Resize(1, 3);\n    CPUContext ctx;\n    t->mutable_data<float>()[0] = 30;\n    t->mutable_data<float>()[1] = 40;\n    t->mutable_data<float>()[2] = 50;\n  }\n\n  {\n    auto* t = ws.CreateBlob(\"stdev\")->GetMutable<TensorCPU>();\n    t->Resize(1, 3);\n    CPUContext ctx;\n    t->mutable_data<float>()[0] = 6;\n    t->mutable_data<float>()[1] = 7;\n    t->mutable_data<float>()[2] = 8;\n  }\n\n  NetDef netdef;\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"OpenGLNormalizePlanarYUV\");\n    op.add_input(\"X_gl\");\n    op.add_input(\"mean\");\n    op.add_input(\"stdev\");\n    op.add_output(\"Y_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"Y_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"NormalizePlanarYUV\");\n    op.add_input(\"X_cpu\");\n    op.add_input(\"mean\");\n    op.add_input(\"stdev\");\n    op.add_output(\"Y_ref\");\n  }\n\n  ws.RunNetOnce(netdef);\n  const auto& t2 = ws.GetBlob(\"Y_cpu\")->Get<TensorCPU>();\n  const auto& t1 = ws.GetBlob(\"Y_ref\")->Get<TensorCPU>();\n  checkError(t2, t1, error);\n}\n\nvoid OpenGL_copyops_speedtest(int N,\n                              int C,\n                              int H,\n                              int W,\n                              int K,\n                              int kernel_h,\n                              int kernel_w,\n                              int pad,\n                              float error,\n                              bool random_input = true) {\n  LOG(INFO) << \"OpenGL CopyOps Speed Test\";\n  Workspace ws;\n  {\n    auto* t = ws.CreateBlob(\"X_cpu\")->GetMutable<TensorCPU>();\n    t->Resize(N, C, H, W);\n    CPUContext ctx;\n    if (random_input) {\n      math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    } else {\n      float* data = t->mutable_data<float>();\n      for (int i = 0; i < t->size(); i++) {\n        data[i] = 1;\n      }\n    }\n  }\n\n  {\n    auto* t = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n    t->Resize(K, C, kernel_h, kernel_w);\n    CPUContext ctx;\n    if (random_input) {\n      math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    } else {\n      float* data = t->mutable_data<float>();\n      for (int i = 0; i < t->size(); i++) {\n        data[i] = 1;\n      }\n    }\n  }\n\n  {\n    auto* t = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n    t->Resize(K);\n    CPUContext ctx;\n    if (random_input) {\n      math::RandGaussian<float, CPUContext>(t->size(), 0, 1, t->mutable_data<float>(), &ctx);\n    } else {\n      float* data = t->mutable_data<float>();\n      for (int i = 0; i < t->size(); i++) {\n        data[i] = 1;\n      }\n    }\n  }\n\n  NetDef netdef;\n  netdef.set_name(\"Test net\");\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyToOpenGL\");\n    op.add_input(\"X_cpu\");\n    op.add_output(\"X_gl\");\n  }\n\n  {\n    auto& op = *(netdef.add_op());\n    op.set_type(\"CopyFromOpenGL\");\n    op.add_input(\"X_gl\");\n    op.add_output(\"Y_cpu\");\n  }\n\n  caffe2::NetBase* net = ws.CreateNet(netdef);\n  CHECK_NOTNULL(net);\n  net->TEST_Benchmark(1, 4, true);\n}\n\nstatic NetDef truncateAfter(NetDef def, size_t idx) {\n  // idx = 0, net = 10 -> remove 9\n  // idx = 0, net = 1 -> remove 0\n  const auto toRemove = def.op_size() - idx - 1;\n  for (auto i = 0; i < toRemove; ++i) {\n    def.mutable_op()->RemoveLast();\n  }\n  CHECK_EQ(def.op_size(), idx + 1);\n  return def;\n}\n\nvoid compareModelsForOpenGL(std::string name,\n                            const NetDef& initNet,\n                            NetDef predictNet,\n                            int width,\n                            int height,\n                            int channel,\n                            std::string input_type,\n                            std::string input_order) {\n\n  if (name == \"styleTransfer\") {\n    for (int i = 0; i < predictNet.mutable_op(0)->arg_size(); i++) {\n      auto* arg = predictNet.mutable_op(0)->mutable_arg(i);\n      if (arg->name() == \"noise_std\") {\n        arg->set_f(0);\n      }\n    }\n  }\n\n  for (auto i = 0; i < predictNet.op_size(); ++i) {\n    auto truncatedPredictNet = truncateAfter(predictNet, i);\n\n    // Change the last blob to external_output(0) for the predict net\n    auto output_blob = \"_OUTPUT_BLOB__\";\n    truncatedPredictNet.set_external_output(0, output_blob);\n    truncatedPredictNet.mutable_op(truncatedPredictNet.op_size() - 1)->set_output(0, output_blob);\n\n    NetDef truncatedOpenGLPredictNet = rewritePredictNetForOpenGL(truncatedPredictNet);\n\n    //    LOG(INFO) << \"truncatedPredictNet\";\n    //    dumpDefForOpenGL(truncatedPredictNet);\n    //\n    LOG(INFO) << \"truncatedOpenGLPredictNet\";\n    dumpDefForOpenGL(truncatedOpenGLPredictNet);\n\n    CPUContext ctx;\n    Workspace cws;\n    cws.RunNetOnce(initNet);\n\n    auto* t_cpu = cws.CreateBlob(truncatedPredictNet.external_input(0))->GetMutable<TensorCPU>();\n    if (name == \"styleTransfer\") {\n      CAFFE_ENFORCE_EQ(input_order, \"NHWC\");\n      CAFFE_ENFORCE_EQ(input_type, \"uint8_t\");\n      t_cpu->Resize(1, height, width, channel);\n      for (auto i = 0; i < t_cpu->size(); ++i) {\n        t_cpu->mutable_data<uint8_t>()[i] = i % 255;\n      }\n    } else if (name == \"segmentation\") {\n      CAFFE_ENFORCE_EQ(input_order, \"NCHW\");\n      CAFFE_ENFORCE_EQ(input_type, \"float\");\n      t_cpu->Resize(1, channel, height, width);\n      float* input = t_cpu->mutable_data<float>();\n      const int size = width * height;\n      // Limit input range to YUV\n      math::RandGaussian<float, CPUContext>(size, 0.5, 0.15, input, &ctx); // Y: 0 ~ 1\n      math::RandGaussian<float, CPUContext>(size, 0, 0.12, input + size, &ctx); // U: -0.436 ~ 0.436\n      math::RandGaussian<float, CPUContext>(\n          size, 0, 0.2, input + 2 * size, &ctx); // V: -0.615 ~ 0.615\n    } else if (name == \"denoiser\") {\n      CAFFE_ENFORCE_EQ(input_order, \"NCHW\");\n      CAFFE_ENFORCE_EQ(input_type, \"float\");\n      t_cpu->Resize(1, channel, height, width);\n      float* input = t_cpu->mutable_data<float>();\n      const int spatial_size = width * height;\n      math::RandGaussian<float, CPUContext>(spatial_size, 0, 0.33, input, &ctx); // R Channel\n      math::RandGaussian<float, CPUContext>(\n          spatial_size, 0, 0.33, input + spatial_size, &ctx); // G Channel\n      math::RandGaussian<float, CPUContext>(\n          spatial_size, 0, 0.33, input + 2 * spatial_size, &ctx); // B Channel\n      // Clamp Range of input [-1, +1]\n      for (auto i = 0; i < t_cpu->size(); ++i) {\n        input[i] = input[i] > 1 ? 1 : input[i] < -1 ? -1 : input[i];\n      }\n    } else {\n      CAFFE_THROW(\"CompareModels only works with style transfer and segmentation now\");\n    }\n\n    Workspace mws;\n    mws.RunNetOnce(initNet);\n\n    auto* t_gl =\n        mws.CreateBlob(truncatedOpenGLPredictNet.external_input(0))->GetMutable<TensorCPU>();\n    if (name == \"styleTransfer\") {\n      CAFFE_ENFORCE_EQ(input_order, \"NHWC\");\n      CAFFE_ENFORCE_EQ(input_type, \"uint8_t\");\n      t_gl->Resize(1, height, width, channel);\n      uint8_t* input = t_gl->mutable_data<uint8_t>();\n      memcpy(input, t_cpu->mutable_data<uint8_t>(), t_cpu->capacity_nbytes());\n    } else if (name == \"segmentation\") {\n      CAFFE_ENFORCE_EQ(input_order, \"NCHW\");\n      CAFFE_ENFORCE_EQ(input_type, \"float\");\n      t_gl->Resize(1, channel, height, width);\n      float* input = t_gl->mutable_data<float>();\n      memcpy(input, t_cpu->mutable_data<float>(), t_cpu->capacity_nbytes());\n    } else if (name == \"denoiser\") {\n      CAFFE_ENFORCE_EQ(input_order, \"NCHW\");\n      CAFFE_ENFORCE_EQ(input_type, \"float\");\n      t_gl->Resize(1, channel, height, width);\n      float* input = t_gl->mutable_data<float>();\n      memcpy(input, t_cpu->mutable_data<float>(), t_cpu->capacity_nbytes());\n    }\n\n    cws.RunNetOnce(truncatedPredictNet);\n    mws.RunNetOnce(truncatedOpenGLPredictNet);\n\n    const auto m_name =\n        truncatedOpenGLPredictNet.op(truncatedOpenGLPredictNet.op_size() - 1).output(0);\n    const auto c_name = truncatedPredictNet.op(truncatedPredictNet.op_size() - 1).output(0);\n\n    LOG(INFO) << \"Checking correspondence for name: \" << m_name << \", idx: \" << i;\n    {\n      const auto& mt = mws.GetBlob(m_name)->Get<TensorCPU>(); // GPU\n      const auto& ct = cws.GetBlob(c_name)->Get<TensorCPU>(); // CPU\n      if (name == \"denoiser\") {\n        checkError(mt, ct, 0.02); // 1% of Scale\n        LOG(INFO) << \"Error Check Completed for Denoiser Layer: \" << i;\n      } else {\n        checkError(mt, ct, 1);\n      }\n    }\n  }\n}\n\nvoid compareBatchedToTiledModels(std::string name,\n                                 const NetDef& initNet,\n                                 NetDef predictNet,\n                                 int width,\n                                 int height,\n                                 int channel,\n                                 std::string input_type,\n                                 std::string input_order) {\n\n  if (name == \"styleTransfer\") {\n    for (int i = 0; i < predictNet.mutable_op(0)->arg_size(); i++) {\n      auto* arg = predictNet.mutable_op(0)->mutable_arg(i);\n      if (arg->name() == \"noise_std\") {\n        arg->set_f(0);\n      }\n    }\n  }\n\n  for (auto i = 19; i < predictNet.op_size(); ++i) {\n    auto truncatedPredictNet = truncateAfter(predictNet, i);\n\n    // Change the last blob to external_output(0) for the predict net\n    auto output_blob = \"_OUTPUT_BLOB__\";\n    truncatedPredictNet.set_external_output(0, output_blob);\n    truncatedPredictNet.mutable_op(truncatedPredictNet.op_size() - 1)->set_output(0, output_blob);\n\n    NetDef bachedNet = rewritePredictNetForOpenGL(truncatedPredictNet, false, false);\n    NetDef tiledNet = rewritePredictNetForOpenGL(truncatedPredictNet, false, true);\n\n    LOG(INFO) << \"truncatedPredictNet\";\n    dumpDefForOpenGL(truncatedPredictNet);\n\n    LOG(INFO) << \"truncatedOpenGLPredictNet\";\n    dumpDefForOpenGL(bachedNet);\n\n    CPUContext ctx;\n\n    Workspace tws;\n    tws.RunNetOnce(initNet);\n\n    auto* t_batch = tws.CreateBlob(bachedNet.external_input(0))->GetMutable<TensorCPU>();\n    if (name == \"styleTransfer\") {\n      CAFFE_ENFORCE_EQ(input_order, \"NHWC\");\n      CAFFE_ENFORCE_EQ(input_type, \"uint8_t\");\n      t_batch->Resize(1, height, width, channel);\n      for (auto i = 0; i < t_batch->size(); ++i) {\n        t_batch->mutable_data<uint8_t>()[i] = i % 255;\n      }\n    } else if (name == \"segmentation\") {\n      CAFFE_ENFORCE_EQ(input_order, \"NCHW\");\n      CAFFE_ENFORCE_EQ(input_type, \"float\");\n      t_batch->Resize(1, channel, height, width);\n      float* input = t_batch->mutable_data<float>();\n      const int size = width * height;\n      // Limit input range to YUV\n      math::RandGaussian<float, CPUContext>(size, 0.5, 0.15, input, &ctx); // Y: 0 ~ 1\n      math::RandGaussian<float, CPUContext>(size, 0, 0.12, input + size, &ctx); // U: -0.436 ~ 0.436\n      math::RandGaussian<float, CPUContext>(\n          size, 0, 0.2, input + 2 * size, &ctx); // V: -0.615 ~ 0.615\n    } else {\n      CAFFE_THROW(\"CompareModels only works with style transfer and segmentation now\");\n    }\n\n    Workspace bws;\n    bws.RunNetOnce(initNet);\n\n    auto* t_tiling = bws.CreateBlob(tiledNet.external_input(0))->GetMutable<TensorCPU>();\n    if (name == \"styleTransfer\") {\n      CAFFE_ENFORCE_EQ(input_order, \"NHWC\");\n      CAFFE_ENFORCE_EQ(input_type, \"uint8_t\");\n      t_tiling->Resize(1, height, width, channel);\n      uint8_t* input = t_tiling->mutable_data<uint8_t>();\n      memcpy(input, t_batch->mutable_data<uint8_t>(), t_batch->capacity_nbytes());\n\n    } else if (name == \"segmentation\") {\n      CAFFE_ENFORCE_EQ(input_order, \"NCHW\");\n      CAFFE_ENFORCE_EQ(input_type, \"float\");\n      t_tiling->Resize(1, channel, height, width);\n      float* input = t_tiling->mutable_data<float>();\n      memcpy(input, t_batch->mutable_data<float>(), t_batch->capacity_nbytes());\n    }\n\n    bws.RunNetOnce(bachedNet);\n    tws.RunNetOnce(tiledNet);\n\n    const auto batch_name = bachedNet.op(bachedNet.op_size() - 1).output(0);\n    const auto tile_name = tiledNet.op(tiledNet.op_size() - 1).output(0);\n\n    LOG(INFO) << \"Checking correspondence for name: \" << batch_name << \", idx: \" << i;\n    {\n      const auto& bt = bws.GetBlob(batch_name)->Get<TensorCPU>(); // GPU\n      const auto& tt = tws.GetBlob(tile_name)->Get<TensorCPU>(); // CPU\n      checkError(bt, tt, 0.01);\n    }\n  }\n}\n\nint runModelBenchmarks(caffe2::NetDef& init_net,\n                       caffe2::NetDef& predict_net,\n                       int warm_up_runs,\n                       int main_runs,\n                       int channel,\n                       int height,\n                       int width,\n                       std::string input_type,\n                       std::string input_order,\n                       std::string engine, // \"CPU\", \"OPENGL\", or \"MPSCNN\"\n                       bool run_individual,\n                       bool use_texture_input,\n                       bool use_tiling,\n                       bool run_fusion) {\n  std::unique_ptr<caffe2::Workspace> workspace(new caffe2::Workspace());\n\n  // caffe2::dumpDefForOpenGL(init_net);\n  caffe2::dumpDefForOpenGL(predict_net);\n\n  CAFFE_ENFORCE(workspace->RunNetOnce(init_net));\n  caffe2::NetDef net_def;\n\n  // rewrite network\n  if (engine == \"CPU\") {\n    net_def.CopyFrom(predict_net);\n  } else if (engine == \"OPENGL\") {\n    if (!caffe2::tryConvertToOpenGL(init_net, predict_net, &net_def, use_texture_input, use_tiling, run_fusion)) {\n      CAFFE_THROW(\"Failed to convert to openGL. Benchmark failed to run\");\n      return -1;\n    }\n  } else if (engine == \"MPSCNN\") {\n#ifdef CAFFE2_USE_MPSCNN\n    if (!caffe2::tryConvertToMPSCNN(init_net, predict_net, &net_def)) {\n      CAFFE_THROW(\"Failed to convert to MPSCNN. Benchmark failed to run\");\n      return -1;\n    }\n#else\n    CAFFE_THROW(\"MPSCNN not enabled. Benchmark failed to run\");\n    return -1;\n#endif\n  } else {\n    CAFFE_THROW(\"Unsupported engine. Benchmark failed to run\");\n    return -1;\n  }\n\n  if (!net_def.has_name()) {\n    net_def.set_name(\"benchmark\");\n  }\n  caffe2::NetBase* net = workspace->CreateNet(net_def);\n\n  // create input blob\n  if (engine == \"CPU\" || engine == \"MPSCNN\" || !use_texture_input) {\n    caffe2::TensorCPU* b;\n    if (!net_def.external_input_size()) {\n      b = workspace->CreateBlob(\"data\")->GetMutable<caffe2::TensorCPU>();\n    } else {\n      b = workspace->CreateBlob(net_def.external_input(0))->GetMutable<caffe2::TensorCPU>();\n    }\n\n    if (input_order == \"NCHW\") {\n      b->Resize(std::vector<int32_t>(\n          {1, static_cast<int>(channel), static_cast<int>(height), static_cast<int>(width)}));\n    } else if (input_order == \"NHWC\") {\n      b->Resize(std::vector<int32_t>(\n          {1, static_cast<int>(height), static_cast<int>(width), static_cast<int>(channel)}));\n    } else {\n      CAFFE_THROW(\"Unknown input order: \", input_order);\n    }\n    if (input_type == \"uint8_t\") {\n      b->mutable_data<uint8_t>();\n    } else if (input_type == \"float\") {\n      b->mutable_data<float>();\n    } else {\n      CAFFE_THROW(\"Unknown input type: \", input_type);\n    }\n  } else {\n    const int tile_x = 1, tile_y = 1;\n    Blob* blob = nullptr;\n    if (!net_def.external_input_size()) {\n      blob = workspace->CreateBlob(\"data\");\n    } else {\n      blob = workspace->CreateBlob(net_def.external_input(0));\n    }\n    if (input_type == \"float\") {\n      ImageAllocator<float16_t> allocator;\n      GLImageVector<float16_t>* output_image = allocator.newImage(1,\n                                                                  width,\n                                                                  height,\n                                                                  channel,\n                                                                  tile_x,\n                                                                  tile_y,\n#if CAFFE2_IOS\n                                                                  true\n#else\n                                                                  false\n#endif\n      );\n      blob->Reset(output_image);\n      for (auto& texture : (*output_image)[0]->textures) {\n        texture->map_load([&](void* buffer,\n                              size_t width,\n                              size_t height,\n                              size_t stride,\n                              size_t channels,\n                              const GLTexture::Type& type) {});\n      }\n    } else {\n      ImageAllocator<uint8_t> allocator;\n      GLImageVector<uint8_t>* output_image = allocator.newImage(1,\n                                                                width,\n                                                                height,\n                                                                channel,\n                                                                tile_x,\n                                                                tile_y,\n#if CAFFE2_IOS\n                                                                true\n#else\n                                                                false\n#endif\n      );\n      blob->Reset(output_image);\n      for (auto& texture : (*output_image)[0]->textures) {\n        texture->map_load([&](void* buffer,\n                              size_t width,\n                              size_t height,\n                              size_t stride,\n                              size_t channels,\n                              const GLTexture::Type& type) {});\n      }\n    }\n  }\n\n  // run benchmark\n  if (engine == \"CPU\" || engine == \"MPSCNN\") {\n    CHECK_NOTNULL(net);\n    CAFFE_ENFORCE(net->Run());\n    net->TEST_Benchmark(warm_up_runs, main_runs, run_individual);\n  } else if (engine == \"OPENGL\") {\n    CHECK_NOTNULL(net);\n    CAFFE_ENFORCE(net->Run());\n\n    for (int i = 0; i < warm_up_runs; i++) {\n      net->Run();\n    }\n    glFinish();\n\n    Timer timer;\n    timer.Start();\n    for (int i = 0; i < main_runs; i++) {\n      net->Run();\n    }\n    if (use_texture_input) {\n      glFinish();\n    }\n\n    double iter_time = (double)timer.MilliSeconds() / main_runs;\n    LOG(INFO) << \"Main run finished. Milliseconds per iter: \" << iter_time\n              << \". Iters per second: \" << 1000.0 / iter_time;\n\n    if (run_individual) {\n      std::vector<std::unique_ptr<caffe2::OperatorBase>> ops;\n\n      for (auto& op : net_def.op()) {\n        ops.push_back(CreateOperator(op, workspace.get()));\n        ops.back()->Run(); // warm up\n      }\n\n      for (int k = 0; k < ops.size(); k++) {\n        timer.Start();\n        for (int i = 0; i < main_runs; i++) {\n          ops[k]->Run();\n        }\n        glFinish();\n\n        LOG(INFO) << \"Operator #\" << k << \" \" << net_def.op(k).type() << \": \"\n                  << (double)timer.MilliSeconds() / main_runs;\n      }\n    }\n  }\n\n  return 0;\n}\n\ntemplate <typename T>\nvoid testGLTextureTypes() {\n  gl_log(GL_LOG, \"Executing %s...\\n\", __PRETTY_FUNCTION__);\n\n  GLImageAllocator<T>* allocator = GLImageAllocator<T>::newGLImageAllocator();\n\n  GLImageVector<T>* image = allocator->newImage(1, 10, 10, 4, 1, 1, true);\n\n  const GLTexture* texture = (*image)[0]->textures[0];\n\n  texture->map_load([&](void* buffer,\n                        size_t width,\n                        size_t height,\n                        size_t stride,\n                        size_t channels,\n                        const GLTexture::Type& type) {\n    T* buffer_data = (T*)buffer;\n\n    for (int y = 0; y < height; y++) {\n      for (int x = 0; x < width; x++) {\n        for (int c = 0; c < channels; c++) {\n          buffer_data[channels * (y * stride + x) + c] = x + y;\n        }\n      }\n    }\n  });\n\n  texture->map_read([&](const void* buffer,\n                        size_t width,\n                        size_t height,\n                        size_t stride,\n                        size_t channels,\n                        const GLTexture::Type& type) {\n    const T* buffer_data = (const T*)buffer;\n\n    for (int y = 0; y < height; y++) {\n      for (int x = 0; x < width; x++) {\n        gl_log(GL_LOG, \"%d, \", (int)buffer_data[channels * (y * stride + x) + 0]);\n      }\n      gl_log(GL_LOG, \"\\n\");\n    }\n  });\n  delete image;\n  delete allocator;\n  gl_log(GL_LOG, \"...done with %s\\n\", __PRETTY_FUNCTION__);\n}\n\nvoid testOpenGL() {\n  {\n    // Test a bunch of different tiled convolutions\n    std::vector<int> channels({3, 4, 6, 8, 12, 16, 32, 64, 128, 256, 512});\n\n    for (const auto& input_channels : channels) {\n      int tile_x = 1, tile_y = 1;\n      squareFactors((input_channels + 3) / 4, tile_x, tile_y);\n\n      for (const auto& output_channels : channels) {\n        for (int size = 5; size < 8; size *= 2) {\n          testOpenGLConv(1,\n                         input_channels,\n                         size,\n                         size,\n                         output_channels,\n                         3,\n                         3,\n                         0,\n                         1,\n                         Conv,\n                         0.1 * input_channels / 8,\n                         true,\n                         1,\n                         1,\n                         tile_x,\n                         tile_y,\n                         true);\n        }\n\n        for (int size = 5; size < 16; size *= 2) {\n          testOpenGLConv(1,\n                         input_channels,\n                         size,\n                         size,\n                         output_channels,\n                         3,\n                         3,\n                         0,\n                         1,\n                         ConvTranspose,\n                         0.1 * input_channels / 8,\n                         true,\n                         1,\n                         1,\n                         tile_x,\n                         tile_y,\n                         true);\n        }\n      }\n    }\n\n    // Test various paddings and strides with tiled convolution\n    for (int kernel_size = 1; kernel_size <= 5; kernel_size++) {\n      for (int pad = 0; pad < kernel_size; pad++) {\n        for (int stride = 1; stride <= 8; stride++) {\n          testOpenGLConv(1,\n                         16,\n                         100,\n                         100,\n                         16,\n                         kernel_size,\n                         kernel_size,\n                         pad,\n                         stride,\n                         Conv,\n                         0.5,\n                         true,\n                         1,\n                         1,\n                         2,\n                         2,\n                         true);\n        }\n\n        for (int stride = 1; stride <= 8; stride++) {\n          testOpenGLConv(1,\n                         16,\n                         100,\n                         100,\n                         16,\n                         kernel_size,\n                         kernel_size,\n                         pad,\n                         stride,\n                         ConvTranspose,\n                         0.5,\n                         true,\n                         1,\n                         1,\n                         2,\n                         2,\n                         true);\n        }\n      }\n    }\n\n    // Test a bunch of batched convolutions\n    for (int kernel_size = 1; kernel_size <= 8; kernel_size++) {\n      for (int stride = 1; stride <= 8; stride++) {\n        testOpenGLConv(1,\n                       16,\n                       10,\n                       10,\n                       16,\n                       kernel_size,\n                       kernel_size,\n                       0,\n                       stride,\n                       ConvTranspose,\n                       0.5 * (1 + kernel_size / 3.0),\n                       true,\n                       1,\n                       1);\n      }\n\n      for (int stride = 1; stride <= 8; stride++) {\n        testOpenGLConv(1,\n                       16,\n                       10,\n                       10,\n                       16,\n                       kernel_size,\n                       kernel_size,\n                       0,\n                       stride,\n                       Conv,\n                       0.5 * (1 + kernel_size / 3.0),\n                       true,\n                       1,\n                       1);\n      }\n    }\n    for (const auto& channel : channels) {\n      int tile_x = 1, tile_y = 1;\n      squareFactors((channel + 3) / 4, tile_x, tile_y);\n      // clang-format off\n      testOpenGLConv(1, channel, 10, 10, channel, 3, 3, 0, 1, ConvPRelu, 0.1 * channel / 8, true, 1, 1, tile_x, tile_y, true);\n      testOpenGLConv(1, channel, 10, 10, channel, 3, 3, 0, 1, ConvTransposePRelu, 0.1 * channel / 8, true, 1, 1, tile_x, tile_y, true);\n      testOpenGLConv(1, channel, 10, 10, channel, 3, 3, 0, 1, ConvRelu, 0.1 * channel / 8, true, 1, 1, tile_x, tile_y, true);\n      testOpenGLConv(1, channel, 10, 10, channel, 3, 3, 0, 1, ConvTransposeRelu, 0.1 * channel / 8, true, 1, 1, tile_x, tile_y, true);\n\n      testOpenGLPRelu(1, channel, 13, 4, channel, tile_x, tile_y, 0.1);\n      testOpenGLRelu(1, channel, 4, 17, tile_x, tile_y, 0.1);\n      testOpenGLConv(1, channel, 16, 16, channel, 3, 3, 0, 2, MaxPool, 0.01, true, 1, 1, tile_x, tile_y, true);\n      testOpenGLConv(1, channel, 16, 16, channel, 3, 3, 0, 2, AveragePool, 0.01, true, 1, 1, tile_x, tile_y, true);\n      testOpenGLAdd(1, channel, 14, 8, 0.1, tile_x, tile_y);\n      testOpenGLResize(1, channel, 16, 16, 2, 2, 0.1, tile_x, tile_y);\n      // clang-format on\n    }\n  }\n\n  {\n    testGLTextureTypes<uint8_t>();\n    testGLTextureTypes<float16_t>();\n\n    testOpenGLCopyOps(1, 4, 4, 4, 1e-2);\n    testOpenGLCopyOps(1, 3, 4, 4, 1e-2);\n    testOpenGLCopyOps(1, 2, 4, 4, 1e-2);\n    testOpenGLCopyOps(1, 1, 4, 4, 1e-2);\n    testOpenGLCopyOps(1, 4, 2, 2, 1e-2);\n    testOpenGLCopyOps(1, 4, 4, 4, 1e-2);\n    testOpenGLCopyOps(1, 4, 1, 1, 1e-2);\n    testOpenGLCopyOps(1, 4, 8, 8, 1e-2);\n    testOpenGLCopyOps(1, 6, 8, 3, 1e-2);\n    testOpenGLCopyOps(1, 4, 1, 2, 1e-2);\n    testOpenGLCopyOps(1, 8, 6, 1, 1e-2);\n    testOpenGLCopyOps(1, 8, 13, 18, 1e-2);\n    testOpenGLCopyOps(1, 16, 13, 18, 1e-2);\n    testOpenGLCopyOps(1, 13, 128, 90, 1e-2);\n    testOpenGLCopyOps(1, 16, 1280, 720, 1e-2);\n\n    testOpenGLCopyOps(1, 16, 4, 4, 1e-2, 2, 2);\n    testOpenGLCopyOps(1, 64, 16, 16, 1e-2, 2, 2);\n    testOpenGLCopyOps(1, 48, 13, 17, 1e-2, 3, 2);\n    testOpenGLCopyOps(1, 512, 1, 1, 1e-2, 4, 16);\n    testOpenGLCopyOps(1, 256, 7, 7, 1e-2, 8, 8);\n    testOpenGLCopyOps(1, 20, 13, 17, 1e-2, 5, 1);\n\n    // Test pooling operators\n    LOG(INFO) << \"Test pooling operators\";\n    testOpenGLConv(1, 4, 5, 5, 4, 3, 3, 0, 1, AveragePool, 0.01, true);\n    testOpenGLConv(1, 4, 5, 5, 4, 5, 5, 0, 1, AveragePool, 0.5, true);\n\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 0, 2, AveragePool, 0.01, true);\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 1, 2, AveragePool, 0.01, true);\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 2, 2, AveragePool, 0.01, true);\n\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 0, 2, MaxPool, 0.01, true);\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 1, 2, MaxPool, 0.01, true);\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 2, 2, MaxPool, 0.01, true);\n\n    // Test strided convolution\n    LOG(INFO) << \"Test strided convolution\";\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 0, 2, Conv, 0.5, true, 1, 1);\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 1, 2, Conv, 0.5, true, 1, 1);\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 2, 2, Conv, 0.5, true, 1, 1);\n\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 0, 3, Conv, 0.5, true, 1, 1);\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 1, 3, Conv, 0.5, true, 1, 1);\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 2, 3, Conv, 0.5, true, 1, 1);\n\n    // Test input batching\n    LOG(INFO) << \"Test input batching\";\n    testOpenGLConv(1, 4, 5, 5, 4, 3, 3, 0, 1, Conv, 0.5, false, 1, 1);\n    testOpenGLConv(1, 8, 5, 5, 4, 3, 3, 0, 1, Conv, 0.5, false, 2, 1);\n    testOpenGLConv(1, 12, 5, 5, 4, 3, 3, 0, 1, Conv, 0.5, false, 3, 1);\n    testOpenGLConv(1, 16, 5, 5, 4, 3, 3, 0, 1, Conv, 0.5, false, 4, 1);\n\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 0, 1, Conv, 1, true, 1, 1); // use random input\n    testOpenGLConv(1, 8, 10, 10, 4, 3, 3, 0, 1, Conv, 1, true, 2, 1); // use random input\n    testOpenGLConv(1, 12, 10, 10, 4, 3, 3, 0, 1, Conv, 2, true, 3, 1); // use random input\n    testOpenGLConv(1, 16, 10, 10, 4, 3, 3, 0, 1, Conv, 2, true, 4, 1); // use random input\n    testOpenGLConv(1, 32, 10, 10, 4, 3, 3, 0, 1, Conv, 4, true, 4, 1); // use random input\n\n    // Test output batching\n    LOG(INFO) << \"Test output batching\";\n    testOpenGLConv(1, 4, 5, 5, 4, 3, 3, 0, 1, Conv, 0.5, false, 1, 1);\n    testOpenGLConv(1, 4, 5, 5, 8, 3, 3, 0, 1, Conv, 0.5, false, 1, 2);\n    testOpenGLConv(1, 4, 5, 5, 12, 3, 3, 0, 1, Conv, 0.5, false, 1, 3);\n    testOpenGLConv(1, 4, 5, 5, 16, 3, 3, 0, 1, Conv, 0.5, false, 1, 4);\n\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 0, 1, Conv, 0.5, true, 1, 1); // use random input\n    testOpenGLConv(1, 4, 10, 10, 8, 3, 3, 0, 1, Conv, 1.5, true, 1, 2); // use random input\n    testOpenGLConv(1, 4, 10, 10, 12, 3, 3, 0, 1, Conv, 0.5, true, 1, 3); // use random input\n    testOpenGLConv(1, 4, 10, 10, 16, 3, 3, 0, 1, Conv, 0.5, true, 1, 4); // use random input\n\n    // Test both\n    LOG(INFO) << \"Test both input and output batching\";\n    testOpenGLConv(1, 4, 5, 5, 4, 3, 3, 0, 1, Conv, 0.5, false, 1, 1);\n    testOpenGLConv(1, 8, 5, 5, 8, 3, 3, 0, 1, Conv, 0.5, false, 2, 2);\n    testOpenGLConv(1, 12, 5, 5, 12, 3, 3, 0, 1, Conv, 0.5, false, 3, 3);\n\n    testOpenGLConv(1, 4, 10, 10, 4, 3, 3, 0, 1, Conv, 0.5, true, 1, 1); // use random input\n    testOpenGLConv(1, 8, 10, 10, 8, 3, 3, 0, 1, Conv, 1, true, 2, 2); // use random input\n    testOpenGLConv(1, 12, 10, 10, 12, 3, 3, 0, 1, Conv, 2, true, 3, 3); // use random input\n    testOpenGLConv(1, 16, 10, 10, 16, 3, 3, 0, 1, Conv, 4, true, 4, 4); // use random input\n\n    // Test different combination of batching\n    LOG(INFO) << \"Test mixed input and output batching sizes\";\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, false, 1, 2);\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, false, 2, 2);\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, false, 1, 4);\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, false, 2, 4);\n\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, false, 1, 1);\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, false, 2, 1);\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, false, 4, 1);\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, false, 4, 2);\n\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, true, 1, 1); // use random input\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, true, 2, 1); // use random input\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, true, 4, 1); // use random input\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, true, 4, 2); // use random input\n\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, true, 1, 1);\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, true, 2, 1);\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, true, 4, 1);\n    testOpenGLConv(1, 16, 3, 3, 16, 3, 3, 0, 1, Conv, 4, true, 4, 2);\n\n    testOpenGLConv(1, 16, 10, 10, 16, 3, 3, 0, 1, Conv, 4, true, 1, 1); // use random input\n    testOpenGLConv(1, 16, 10, 10, 16, 3, 3, 0, 1, Conv, 4, true, 1, 2); // use random input\n    testOpenGLConv(1, 16, 10, 10, 16, 3, 3, 0, 1, Conv, 4, true, 2, 1); // use random input\n    testOpenGLConv(1, 16, 10, 10, 16, 3, 3, 0, 1, Conv, 4, true, 2, 2); // use random input\n    testOpenGLConv(1, 16, 10, 10, 16, 3, 3, 0, 1, Conv, 4, true, 4, 1); // use random input\n    testOpenGLConv(1, 16, 10, 10, 16, 3, 3, 0, 1, Conv, 4, true, 1, 4); // use random input\n\n    // Test input/output channels\n    for (int i = 0; i < 4; i++) {\n      testOpenGLConv(1, 6, 10, 10, i, 3, 3, 0, 1, Conv, 4, true, 1, 1); // use random input\n      testOpenGLConv(1, 6, 10, 10, i, 3, 3, 0, 1, Conv, 4, true, 2, 1); // use random input\n    }\n\n    // Test large input size\n    LOG(INFO) << \"Test large input size\";\n    testOpenGLConv(1, 4, 1280, 720, 4, 3, 3, 0, 1, Conv, 1, true, 1, 1); // use random input\n    testOpenGLConv(1, 16, 1280, 720, 16, 3, 3, 0, 1, Conv, 4, true, 4, 4); // use random input\n\n    // Test non standard input size\n    testOpenGLConv(1, 16, 125, 73, 16, 3, 3, 0, 1, Conv, 4, true, 1, 1); // use random input\n    testOpenGLConv(1, 16, 127, 71, 16, 3, 3, 0, 1, Conv, 4, true, 4, 4); // use random input\n\n    // Test for different kernel size\n    LOG(INFO) << \"Test kernel sizes 4 to 6\";\n    for (int w = 4; w < 7; w++) {\n      testOpenGLConv(1, 4, 128, 72, 4, w, w, 0, 1, Conv, 4 * (w / 3.0) * (w / 3.0), true, 1, 1);\n    }\n\n    // Test for random failures\n    for (int i = 0; i < 10; i++) {\n      testOpenGLConv(1, 6, 111, 111, 3, 3, 3, 0, 2, ConvTranspose, 0.5, true, 2, 1);\n      testOpenGLConv(1, 16, 56, 56, 6, 4, 4, 0, 2, ConvTranspose, 0.5, true, 2, 2);\n    }\n\n    LOG(INFO) << \"Test OpenGL ConvPRelu\";\n    testOpenGLConv(1, 16, 6, 6, 16, 3, 3, 0, 1, ConvPRelu, 2, true, 1, 1);\n    testOpenGLConv(1, 4, 6, 6, 4, 3, 3, 0, 1, ConvPRelu, 1, true, 1, 1);\n    testOpenGLConv(1, 8, 6, 6, 8, 3, 3, 0, 1, ConvPRelu, 2, true, 2, 2);\n    testOpenGLConv(1, 16, 16, 16, 16, 3, 3, 0, 1, ConvPRelu, 4, true, 4, 4);\n    testOpenGLConv(1, 12, 16, 16, 8, 3, 3, 0, 1, ConvPRelu, 4, true, 3, 1);\n    testOpenGLConv(1, 16, 1280, 720, 16, 3, 3, 0, 1, ConvPRelu, 4, true, 4, 4);\n    testOpenGLConv(1, 16, 1280, 720, 16, 3, 3, 0, 1, ConvPRelu, 4, true, 1, 1);\n\n    LOG(INFO) << \"Test OpenGL ConvTransposePRelu\";\n    testOpenGLConv(1, 16, 6, 6, 16, 3, 3, 0, 1, ConvTransposePRelu, 2, true, 1, 1);\n    testOpenGLConv(1, 4, 6, 6, 4, 3, 3, 0, 1, ConvTransposePRelu, 1, true, 1, 1);\n    testOpenGLConv(1, 8, 6, 6, 8, 3, 3, 0, 1, ConvTransposePRelu, 2, true, 2, 2);\n    testOpenGLConv(1, 16, 16, 16, 16, 3, 3, 0, 1, ConvTransposePRelu, 4, true, 4, 4);\n    testOpenGLConv(1, 12, 16, 16, 8, 3, 3, 0, 1, ConvTransposePRelu, 4, true, 3, 1);\n    testOpenGLConv(1, 16, 1280, 720, 16, 3, 3, 0, 1, ConvTransposePRelu, 4, true, 4, 4);\n    testOpenGLConv(1, 16, 1280, 720, 16, 3, 3, 0, 1, ConvTransposePRelu, 4, true, 1, 1);\n\n    LOG(INFO) << \"Test OpenGL ConvRelu\";\n    testOpenGLConv(1, 16, 6, 6, 16, 3, 3, 0, 1, ConvRelu, 2, true, 1, 1);\n    testOpenGLConv(1, 4, 6, 6, 4, 3, 3, 0, 1, ConvRelu, 1, true, 1, 1);\n    testOpenGLConv(1, 8, 6, 6, 8, 3, 3, 0, 1, ConvRelu, 2, true, 2, 2);\n    testOpenGLConv(1, 16, 16, 16, 16, 3, 3, 0, 1, ConvRelu, 4, true, 4, 4);\n    testOpenGLConv(1, 12, 16, 16, 8, 3, 3, 0, 1, ConvRelu, 4, true, 3, 1);\n    testOpenGLConv(1, 16, 1280, 720, 16, 3, 3, 0, 1, ConvRelu, 4, true, 4, 4);\n    testOpenGLConv(1, 16, 1280, 720, 16, 3, 3, 0, 1, ConvRelu, 4, true, 1, 1);\n\n    LOG(INFO) << \"Test OpenGL ConvTransposeRelu\";\n    testOpenGLConv(1, 16, 6, 6, 16, 3, 3, 0, 1, ConvTransposeRelu, 2, true, 1, 1);\n    testOpenGLConv(1, 4, 6, 6, 4, 3, 3, 0, 1, ConvTransposeRelu, 1, true, 1, 1);\n    testOpenGLConv(1, 8, 6, 6, 8, 3, 3, 0, 1, ConvTransposeRelu, 2, true, 2, 2);\n    testOpenGLConv(1, 16, 16, 16, 16, 3, 3, 0, 1, ConvTransposeRelu, 4, true, 4, 4);\n    testOpenGLConv(1, 12, 16, 16, 8, 3, 3, 0, 1, ConvTransposeRelu, 4, true, 3, 1);\n    testOpenGLConv(1, 16, 1280, 720, 16, 3, 3, 0, 1, ConvTransposeRelu, 4, true, 4, 4);\n    testOpenGLConv(1, 16, 1280, 720, 16, 3, 3, 0, 1, ConvTransposeRelu, 4, true, 1, 1);\n\n    LOG(INFO) << \"Test OpenGL PRelu\";\n    testOpenGLPRelu(1, 4, 16, 16, 4, 1, 1, 0.1);\n    testOpenGLPRelu(1, 16, 16, 16, 1, 1, 1, 0.1);\n    testOpenGLPRelu(1, 12, 16, 16, 1, 1, 1, 0.1);\n    testOpenGLPRelu(1, 6, 640, 360, 6, 1, 1, 0.1);\n\n    LOG(INFO) << \"Test OpenGL Relu\";\n    testOpenGLRelu(1, 4, 16, 16, 1, 1, 0.1);\n    testOpenGLRelu(1, 16, 16, 16, 1, 1, 0.1);\n    testOpenGLRelu(1, 6, 640, 360, 1, 1, 0.1);\n\n    LOG(INFO) << \"Test OpenGL Add\";\n    testOpenGLAdd(1, 16, 640, 360, 0.1);\n    testOpenGLAdd(1, 12, 640, 360, 0.1);\n\n    LOG(INFO) << \"Test OpenGL Sub\";\n    testOpenGLSub(1, 16, 640, 360, 0.1);\n    testOpenGLSub(1, 12, 640, 360, 0.1);\n\n    LOG(INFO) << \"Test OpenGL Sigmoid\";\n    testOpenGLSigmoid(1, 4, 16, 16, 0.1);\n    testOpenGLSigmoid(1, 12, 64, 48, 0.1);\n    testOpenGLSigmoid(1, 6, 640, 360, 0.1);\n\n    LOG(INFO) << \"Test OpenGL Tanh\";\n    testOpenGLTanh(1, 4, 16, 16, 0.1);\n    testOpenGLTanh(1, 12, 64, 48, 0.1);\n    testOpenGLTanh(1, 6, 640, 360, 0.1);\n\n    LOG(INFO) << \"Test OpenGL Mul\";\n    testOpenGLMul(1, 4, 16, 16, 0.1);\n    testOpenGLMul(1, 12, 64, 48, 0.1);\n    testOpenGLMul(1, 6, 640, 360, 0.1);\n\n    LOG(INFO) << \"Test OpenGL Concat\";\n    testOpenGLConcat(1, std::vector<int>{4, 4}, 16, 16);\n    testOpenGLConcat(1, std::vector<int>{4, 4, 4}, 16, 16);\n    testOpenGLConcat(1, std::vector<int>{4, 4, 4, 4}, 16, 16);\n    testOpenGLConcat(1, std::vector<int>{8, 4, 12}, 16, 16);\n    testOpenGLConcat(1, std::vector<int>{12, 16, 8}, 16, 16);\n    testOpenGLConcat(1, std::vector<int>{60, 24, 36}, 16, 16);\n\n    testOpenGLConcat(1, std::vector<int>{12, 16, 8}, 16, 16, true);\n    testOpenGLConcat(1, std::vector<int>{60, 24, 36}, 16, 16, true);\n\n    LOG(INFO) << \"Test OpenGL Softmax\";\n    testOpenGLSoftmax(1, 100, 0.1);\n    testOpenGLSoftmax(1, 500, 0.1);\n    testOpenGLSoftmax(1, 1000, 0.1);\n    testOpenGLSoftmax(1, 5000, 0.1);\n\n    LOG(INFO) << \"Test OpenGL InstanceNorm\";\n    testOpenGLInstanceNorm(1, 4, 16, 16, 0.2);\n    testOpenGLInstanceNorm(1, 4, 20, 20, 0.2);\n    testOpenGLInstanceNorm(1, 4, 128, 128, 0.2);\n    testOpenGLInstanceNorm(1, 12, 120, 140, 0.3);\n    testOpenGLInstanceNorm(1, 3, 120, 140, 0.2);\n    testOpenGLInstanceNorm(1, 4, 192, 192, 0.2);\n\n    testOpenGLInstanceNorm(1, 4, 258, 198, 0.2);\n    testOpenGLInstanceNorm(1, 8, 338, 198, 0.2);\n    testOpenGLInstanceNorm(1, 12, 334, 194, 0.2);\n    testOpenGLInstanceNorm(1, 16, 324, 184, 0.2);\n    testOpenGLInstanceNorm(1, 6, 640, 360, 0.2);\n\n    LOG(INFO) << \"Test OpenGL InstanceNormPRelu\";\n    testOpenGLInstanceNormPRelu(1, 4, 16, 16, 0.2);\n    testOpenGLInstanceNormPRelu(1, 4, 20, 20, 0.2);\n    testOpenGLInstanceNormPRelu(1, 4, 128, 128, 0.2);\n    testOpenGLInstanceNormPRelu(1, 12, 120, 140, 0.3);\n    testOpenGLInstanceNormPRelu(1, 3, 120, 140, 0.2);\n    testOpenGLInstanceNormPRelu(1, 4, 192, 192, 0.2);\n\n    testOpenGLInstanceNormPRelu(1, 4, 258, 198, 0.2);\n    testOpenGLInstanceNormPRelu(1, 8, 338, 198, 0.2);\n    testOpenGLInstanceNormPRelu(1, 12, 334, 194, 0.2);\n    testOpenGLInstanceNormPRelu(1, 16, 324, 184, 0.2);\n    testOpenGLInstanceNormPRelu(1, 6, 640, 360, 0.2);\n\n    LOG(INFO) << \"Test OpenGL ResizeNearest\";\n    testOpenGLResize(1, 4, 16, 16, 1, 1, 0.1);\n    testOpenGLResize(1, 4, 16, 16, 2, 2, 0.1);\n    testOpenGLResize(1, 4, 16, 16, 3, 3, 0.1);\n    testOpenGLResize(1, 4, 16, 16, 4, 4, 0.1);\n    testOpenGLResize(1, 16, 25, 25, 3, 3, 0.1);\n    testOpenGLResize(1, 16, 25, 25, 3, 3, 0.1);\n    testOpenGLResize(1, 12, 25, 25, 3, 3, 0.1);\n    testOpenGLResize(1, 4, 720, 1280, 3, 3, 0.1);\n\n    // debug style transfer\n    // conv\n    testOpenGLConv(1, 3, 82, 82, 8, 9, 9, 0, 1, Conv, 4, true, 1, 1);\n    testOpenGLConv(1, 8, 74, 74, 8, 3, 3, 0, 1, Conv, 4, true, 1, 1);\n    testOpenGLConv(1, 8, 82, 82, 12, 3, 3, 0, 1, Conv, 4, true, 1, 1);\n    testOpenGLConv(1, 12, 82, 82, 12, 3, 3, 0, 1, Conv, 4, true, 1, 1);\n\n    // convtranspose\n    testOpenGLConv(1, 16, 56, 56, 6, 4, 4, 0, 2, ConvTranspose, 0.5, true, 2, 2);\n    testOpenGLConv(1, 6, 112, 112, 3, 4, 4, 0, 2, ConvTranspose, 0.5, true, 2, 1);\n\n    LOG(INFO) << \"Test OpenGL PadImage\";\n    testOpenGLPadImage(1, 3, 11, 11, 0, 1, 0, 1, 0.001);\n    testOpenGLPadImage(1, 3, 50, 80, 0, 1, 0, 1, 0.001);\n    testOpenGLPadImage(1, 12, 50, 80, 10, 9, 10, 9, 0.001);\n\n    LOG(INFO) << \"Test OpenGL Preprocess\";\n    testOpenGLPreprocess(1, 4, 8, 8, 0.20);\n    testOpenGLPreprocess(1, 4, 1280, 720, 0.20);\n\n    LOG(INFO) << \"Test OpenGL Deprocess\";\n    testOpenGLDeprocess(1, 3, 8, 8, 0.01);\n    testOpenGLDeprocess(1, 3, 1280, 720, 0.01);\n\n    LOG(INFO) << \"Test OpenGL NormalizePlanarYUV\";\n    testOpenGLNormPlanarYUV(1, 3, 8, 8, 0.01);\n    testOpenGLNormPlanarYUV(1, 3, 192, 192, 0.01);\n\n    //  for (int i = 0; i < 4; i += 1) {\n    //    LOG(INFO) << \"C: \" << 4 << \", H: \" << 1280 + i << \", W: \" << 720 + i;\n    //    OpenGL_copyops_speedtest(1, 4, 1280, 720 + i, 4, 3, 3, 0, 0.5);\n    //  }\n\n    //  for (int i = 0; i < 1; i += 1) {\n    //    LOG(INFO) << \"C: \" << 16 << \", H: \" << 1280 + i << \", W: \" << 720 + i;\n    //    OpenGL_copyops_speedtest(1, 16, 1280, 720 + i, 16, 3, 3, 0, 0.5);\n    //  }\n    //\n    //  for (int i = 0; i < 9; i += 1) {\n    //    LOG(INFO) << \"C: \" << 16 << \", H: \" << 1280 + i << \", W: \" << 720 + i;\n    //    OpenGL_speedtest(1, 16, 1280, 720 + i, 16, 3, 3, 0, 0.5);\n    //  }\n\n    // Multi-Batch Tests\n    LOG(INFO) << \"Test OpenGL Multi-batch Support\";\n    testOpenGLCopyOps(2, 4, 4, 4, 1e-2);\n    testOpenGLCopyOps(3, 4, 4, 4, 1e-2);\n    testOpenGLCopyOps(5, 4, 4, 4, 1e-2);\n    testOpenGLConv(2, 4, 5, 5, 4, 3, 3, 0, 1, AveragePool, 0.01, true);\n    testOpenGLConv(2, 4, 10, 10, 4, 3, 3, 0, 2, MaxPool, 0.01, true);\n    testOpenGLConv(3, 4, 10, 10, 4, 3, 3, 0, 2, Conv, 0.5, true, 1, 1);\n    testOpenGLConv(5, 4, 10, 10, 4, 3, 3, 0, 2, Conv, 0.5, true, 1, 1);\n    testOpenGLConv(7, 4, 10, 10, 4, 3, 3, 0, 2, Conv, 0.5, true, 1, 1);\n    testOpenGLConv(11, 4, 10, 10, 4, 3, 3, 0, 2, Conv, 0.5, true, 1, 1);\n    testOpenGLConv(12, 4, 10, 10, 4, 3, 3, 0, 2, Conv, 0.5, true, 1, 1);\n    testOpenGLConv(21, 4, 10, 10, 4, 3, 3, 0, 2, Conv, 0.5, true, 1, 1);\n    testOpenGLConv(50, 4, 10, 10, 4, 3, 3, 0, 2, Conv, 0.5, true, 1, 1);\n    testOpenGLConv(3, 4, 10, 10, 4, 3, 3, 0, 2, ConvTranspose, 0.5, true, 1, 1);\n    testOpenGLConv(3, 16, 6, 6, 16, 3, 3, 0, 1, ConvPRelu, 2, true, 1, 1);\n    testOpenGLConv(3, 16, 6, 6, 16, 3, 3, 0, 1, ConvTransposePRelu, 2, true, 1, 1);\n\n    testOpenGLPRelu(3, 4, 16, 16, 4, 1, 1, 0.1);\n    testOpenGLPRelu(5, 4, 16, 16, 4, 1, 1, 0.1);\n\n    testOpenGLRelu(3, 4, 16, 16, 1, 1, 0.1);\n    testOpenGLRelu(7, 4, 16, 16, 1, 1, 0.1);\n\n    testOpenGLAdd(3, 16, 640, 360, 0.1);\n    testOpenGLAdd(9, 16, 640, 360, 0.1);\n\n    testOpenGLSigmoid(3, 4, 16, 16, 0.1);\n    testOpenGLSigmoid(11, 4, 16, 16, 0.1);\n\n    testOpenGLInstanceNorm(3, 4, 16, 16, 0.2);\n    testOpenGLInstanceNorm(13, 4, 16, 16, 0.2);\n\n    testOpenGLInstanceNormPRelu(3, 4, 16, 16, 0.2);\n    testOpenGLInstanceNormPRelu(15, 4, 16, 16, 0.2);\n\n    testOpenGLResize(3, 4, 16, 16, 1, 1, 0.1);\n    testOpenGLResize(16, 4, 16, 16, 1, 1, 0.1);\n\n    testOpenGLPadImage(3, 3, 4, 4, 0, 1, 0, 1, 0.01);\n    testOpenGLPadImage(23, 3, 4, 4, 0, 1, 0, 1, 0.01);\n\n    testOpenGLSoftmax(3, 1000, 0.1);\n    testOpenGLSoftmax(27, 100, 0.1);\n\n    testOpenGLNormPlanarYUV(4, 3, 192, 192, 0.01);\n\n    // Test Tiling\n    testOpenGLSoftmax(3, 1000, 0.1, true);\n    testOpenGLSoftmax(9, 523, 0.1, true);\n    testOpenGLSoftmax(27, 100, 0.1, true);\n  }\n\n  LOG(INFO) << \"End of OpenGL tests\";\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/opengl/test/opengl_test.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\nvoid testOpenGL();\nvoid compareModelsForOpenGL(std::string name,\n                            const NetDef& initNet,\n                            NetDef predictNet,\n                            int width,\n                            int height,\n                            int channel,\n                            std::string input_type,\n                            std::string input_order);\n\nvoid compareBatchedToTiledModels(std::string name,\n                                 const NetDef& initNet,\n                                 NetDef predictNet,\n                                 int width,\n                                 int height,\n                                 int channel,\n                                 std::string input_type,\n                                 std::string input_order);\n\nint runModelBenchmarks(caffe2::NetDef& init_net,\n                       caffe2::NetDef& predict_net,\n                       int warm_up_runs,\n                       int main_runs,\n                       int channel,\n                       int height,\n                       int width,\n                       std::string input_type,\n                       std::string input_order,\n                       std::string engine,\n                       bool run_individual    = false,\n                       bool use_texture_input = false,\n                       bool use_tiling        = false,\n                       bool run_fusion        = true);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/snpe/CMakeLists.txt",
    "content": "if(USE_SNPE AND ANDROID)\n  file(GLOB_RECURSE tmp *.cc)\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp} PARENT_SCOPE)\nendif()\n"
  },
  {
    "path": "caffe2/mobile/contrib/snpe/README.md",
    "content": "# Snapdragon NPE Support\n\n## Build\n\nUse the typical build_android script, but include a couple Cmake options to enable Snapdragon NPE:\n\n    NPE_HEADERS=/path/to/snpe-1.2.2/include/\n    NPE_LOCATION=/path/to/snpe-1.2.2/lib/arm-android-gcc4.9/libSNPE.so\n    ./scripts/build_android.sh -DUSE_SNPE=ON -DSNPE_LOCATION=$NPE_LOCATION -DSNPE_HEADERS=$NPE_HEADERS\n\nthis will enable the Snapdragon NPE Operator, which is a Caffe2 operator that can execute NPE `dlc` files.\n\n## Usage\n\nFollow Qualcomm's instructions to convert a model into `dlc` format. You can then use the `dlc` as a Caffe2 operator in Python:\n\n    with open('submodel.dlc', 'rb') as f:\n        dlc = f.read()\n\n    op = core.CreateOperator('SNPE', ['data_in'], ['data_out'],\n             arg=[\n                 utils.MakeArgument(\"model_buffer\", dlc),\n                 utils.MakeArgument(\"input_name\", \"data\") # Assuming DLC's first layer takes in 'data'\n             ]\n         )\n\nand adding the operator to your model as you would normally.\n\n## Debug\n\n`libSNPE.so` is a shared library that is loaded at runtime.  You may need to specify the location of the library on your Android device when running standalone binaries.  The runtime assumes it will be able to `dlopen()` a file named `libSNPE.so` at the location specified by `gSNPELocation()`.  Either change this value at runtime or use an environment variable such as `LD_LIBRARY_PATH`.\n\nYou also need `libgnustl_shared.so` from Android NDK to be loaded in order to run standalone binaries. \n"
  },
  {
    "path": "caffe2/mobile/contrib/snpe/snpe_ffi.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"snpe_ffi.h\"\n\n#include \"DiagLog/IDiagLog.hpp\"\n#include \"zdl/DlContainer/IDlContainer.hpp\"\n#include \"zdl/DlSystem/ITensorFactory.hpp\"\n#include \"zdl/DlSystem/DlError.hpp\"\n#include \"zdl/SNPE/SNPE.hpp\"\n#include \"zdl/SNPE/SNPEBuilder.hpp\"\n#include \"zdl/SNPE/SNPEFactory.hpp\"\n\n// Stringify input.\n#define S_(x) #x\n#define S(x) S_(x)\n\n#define SNPE_ENFORCE(condition)                                                             \\\n  do {                                                                                      \\\n    if (!(condition)) {                                                                     \\\n      throw std::runtime_error(std::string(\"Exception in SNPE: \") + std::string(__FILE__) + \\\n                               std::string(\":\") + std::string(S(__LINE__)) +                 \\\n                               zdl::DlSystem::getLastErrorString());                        \\\n    }                                                                                       \\\n  } while (0);\n\nstruct SNPEContext {\n public:\n  SNPEContext(const std::vector<uint8_t>& buffer, const char* input_name, bool enable_logging=false) {\n    container_ = zdl::DlContainer::IDlContainer::open(buffer);\n    SNPE_ENFORCE(container_);\n\n    zdl::SNPE::SNPEBuilder snpeBuilder(container_.get());\n    SNPE_ENFORCE(zdl::SNPE::SNPEFactory::isRuntimeAvailable(zdl::DlSystem::Runtime_t::GPU));\n\n    dnn_ = snpeBuilder.setOutputLayers({}) // Just the last one is fine.\n                      .setRuntimeProcessor(zdl::DlSystem::Runtime_t::GPU)\n\t\t\t\t\t\t\t\t\t\t\t.setPerformanceProfile(zdl::DlSystem::PerformanceProfile_t::HIGH_PERFORMANCE)\n                      .build();\n\n    if (enable_logging) {\n      auto logger_opt = dnn_->getDiagLogInterface();\n      if (!logger_opt) throw std::runtime_error(\"SNPE failed to obtain logging interface\");\n      auto logger = *logger_opt;\n      auto opts = logger->getOptions();\n      opts.LogFileDirectory = \"/data/local/tmp/\";\n      SNPE_ENFORCE(logger->setOptions(opts));\n      SNPE_ENFORCE(logger->start());\n    }\n\n    SNPE_ENFORCE(dnn_);\n\n    inputDims_ = dnn_->getInputDimensions(input_name);\n\n    inputTensor_ = zdl::SNPE::SNPEFactory::getTensorFactory().createTensor(inputDims_);\n\n    SNPE_ENFORCE(dnn_->getOutputLayerNames() && (*dnn_->getOutputLayerNames()).size() >= 1);\n  }\n\n  const zdl::DlSystem::Optional<zdl::DlSystem::TensorShape>& getInputDims() const { return inputDims_; };\n\n  const std::vector<std::vector<size_t>>& run(const float* inputData, size_t count) {\n    SNPE_ENFORCE(inputData);\n\n    // Copy input data.\n    memcpy(inputTensor_->begin().dataPointer(), inputData, (count * sizeof(float)));\n    SNPE_ENFORCE(inputTensor_.get());\n\n    // Execute graph in the SNPE runtime.\n    SNPE_ENFORCE(dnn_->execute(inputTensor_.get(), outputTensors_));\n\n    SNPE_ENFORCE(outputTensors_.size() >= 1);\n    for (auto name : outputTensors_.getTensorNames()) {\n      const auto& outputTensor = outputTensors_.getTensor(name);\n      auto dims = outputTensor->getShape().getDimensions();\n      outputDims_.push_back(std::vector<size_t>(dims, dims + outputTensor->getShape().rank()));\n    }\n\n    return outputDims_;\n  }\n\n  void copyOutputTo(float* outputData) {\n    const auto& outputTensor = outputTensors_.getTensor(*outputTensors_.getTensorNames().begin());\n    SNPE_ENFORCE(outputTensor);\n    memcpy(outputData, outputTensor->begin().dataPointer(), (outputTensor->getSize() * sizeof(float)));\n  }\n\n private:\n  std::shared_ptr<zdl::DlContainer::IDlContainer> container_;\n  std::shared_ptr<zdl::SNPE::SNPE> dnn_;\n  zdl::DlSystem::Optional<zdl::DlSystem::TensorShape> inputDims_;\n  std::vector<std::vector<size_t>> outputDims_;\n  std::shared_ptr<zdl::DlSystem::ITensor> inputTensor_;\n  zdl::DlSystem::TensorMap outputTensors_;\n};\n\nextern \"C\" {\n\nbool snpe_has_gpu() {\n  return zdl::SNPE::SNPEFactory::isRuntimeAvailable(zdl::DlSystem::Runtime_t::GPU);\n}\n\nvoid* snpe_create(const uint8_t* container, size_t size, const char* input_name) {\n  std::vector<uint8_t> buffer(container, container + size);\n  return new SNPEContext(buffer, input_name);\n}\n\nvoid snpe_destroy(void* ctx) { delete ((SNPEContext*)ctx); }\n\nvoid snpe_get_input_dims(void* ctx, size_t const** dims, size_t* size) {\n  const auto& inputDims = ((SNPEContext*)ctx)->getInputDims();\n  *dims = (*inputDims).getDimensions();\n  *size = (*inputDims).rank();\n}\n\nvoid snpe_run(void* ctx,\n              const float* inputData,\n              size_t inputSize,\n              size_t const** outputDims,\n              size_t* outputSize) {\n\n  const auto& outputDims_ = ((SNPEContext*)ctx)->run(inputData, inputSize);\n  SNPE_ENFORCE(outputDims_.size() >= 1);\n\n  *outputDims = outputDims_[0].data();\n  *outputSize = outputDims_[0].size();\n}\n\nvoid snpe_copy_output_to(void* ctx, float* outputData) {\n  ((SNPEContext*)ctx)->copyOutputTo(outputData);\n}\n\n} // extern \"C\"\n\n"
  },
  {
    "path": "caffe2/mobile/contrib/snpe/snpe_ffi.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_SNPE_FFI_H_\n#define CAFFE2_SNPE_FFI_H_\n\n#include <stdint.h>\n#include <string>\n\nnamespace caffe2 {\n\nstd::string& gSNPELocation();\n\nconst char* const snpe_ffi_so = \"libsnpe_ffi.so\";\n\n}\n\nextern \"C\" {\n\nbool snpe_has_gpu();\n\nvoid* snpe_create(const uint8_t* container, size_t size, const char* input_name);\n\nvoid snpe_destroy(void* ctx);\n\nvoid snpe_get_input_dims(void* ctx, size_t const** dims, size_t* size);\n\nvoid snpe_run(void* ctx,\n              const float* inputData,\n              size_t inputSize,\n              size_t const** outputDims,\n              size_t* outputSize);\n\nvoid snpe_copy_output_to(void* ctx, float* outputData);\n\n}\n\n#endif  // CAFFE2_SNPE_FFI_H_\n"
  },
  {
    "path": "caffe2/mobile/contrib/snpe/snpe_globals.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"snpe_ffi.h\"\n#include <atomic>\n#include <mutex>\n\nnamespace caffe2 {\n\nstatic std::once_flag flag;\nstd::string& gSNPELocation() {\n  static std::string g_snpe_location;\n  std::call_once(flag, [](){\n    g_snpe_location = \"\";\n  });\n  return g_snpe_location;\n}\n\n}\n\n"
  },
  {
    "path": "caffe2/mobile/contrib/snpe/snpe_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include \"snpe_ffi.h\"\n#include <dlfcn.h>\n\nnamespace caffe2 {\n\ntemplate <typename T>\nusing deleted_unique_ptr = std::unique_ptr<T, std::function<void(T*)>>;\n\nclass SNPEOp final : public Operator<CPUContext> {\n public:\n  SNPEOp(const OperatorDef& def, Workspace* ws) : Operator<CPUContext>(def, ws),\n        model_buffer_(OperatorBase::GetSingleArgument<string>(\"model_buffer\", \"\")),\n        input_name_(OperatorBase::GetSingleArgument<string>(\"input_name\", \"data\"))\n  {\n    CAFFE_ENFORCE(gSNPELocation() != \"\", \"SNPE library \\\"\", gSNPELocation(), \"\\\" does not exist.\");\n    std::ostringstream snpe_ffi;\n    snpe_ffi << gSNPELocation() << \"/\" << snpe_ffi_so;\n    handle_ = deleted_unique_ptr<void>(dlopen(snpe_ffi.str().c_str(), RTLD_LAZY), [](void* handle) {\n      if (handle) {\n        dlclose(handle);\n      }\n    });\n    if (!handle_.get()) {\n      std::cerr << dlerror() << std::endl;\n    }\n\n    OPERATOR_NEEDS_FEATURE(handle_.get(), \"Couldn't find \", snpe_ffi.str());\n\n#define X(n)                                    \\\n  dlerror();                                    \\\n  auto* n##_f = (decltype(&n))dlsym(handle_.get(), #n); \\\n  OPERATOR_NEEDS_FEATURE(n##_f, dlerror());\n\n    {\n      X(snpe_has_gpu);\n      X(snpe_create);\n      X(snpe_destroy);\n      X(snpe_get_input_dims);\n      X(snpe_run);\n      X(snpe_copy_output_to);\n    }\n\n    X(snpe_has_gpu);\n    OPERATOR_NEEDS_FEATURE(snpe_has_gpu_f(), \"No GPU found, cannot use SNPE.\");\n\n    X(snpe_create)\n#undef X\n\n// Redefine to use CAFFE_ENFORCE instead of OPERATOR_NEEDS_FEATURE.\n\n#define X(n)                                              \\\n      dlerror();                                          \\\n      auto* n##_f = (decltype(&n))dlsym(handle_.get(), #n); \\\n      CAFFE_ENFORCE(n##_f, dlerror());\n\n    CAFFE_ENFORCE(def.input_size(), \"No inputs.\");\n    if (input_name_ == \"\") {\n      input_name_ = def.input().Get(0);\n\t\t}\n    ctx_ = deleted_unique_ptr<void>(snpe_create_f(reinterpret_cast<const unsigned char *>(model_buffer_.data()),\n          model_buffer_.length(), input_name_.c_str()), [this](void* ctx) {\n      if (ctx) {\n        X(snpe_destroy);\n        snpe_destroy_f(ctx);\n      }\n    });\n  }\n\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE(gSNPELocation() != \"\", \"SNPE library was never loaded.\");\n\n    X(snpe_get_input_dims);\n    size_t const* dims;\n    size_t dimSize;\n    snpe_get_input_dims_f(ctx_.get(), &dims, &dimSize);\n    if (Input(0).ndim() != dimSize) {\n      if (dimSize == 3 && dimSize == Input(0).ndim() - 1 && Input(0).dim32(0) == 1) {\n        const int C = Input(0).dim32(1);\n        const int H = Input(0).dim32(2);\n        const int W = Input(0).dim32(3);\n        if (dims[0] != C ||\n            dims[1] != H ||\n            dims[2] != W) {\n          CAFFE_THROW(\"Input size must match what SNPE expects, which in this case is: \",\n              dims[0], \" \", dims[1], \" \", dims[2]);\n        }\n      } else {\n        CAFFE_THROW(\"SNPE input dimensions are not compatible.\");\n      }\n    } else {\n      for (auto i = 0; i < Input(0).ndim(); ++i) {\n        CAFFE_ENFORCE_EQ(dims[i], Input(0).dim32(i), \"SNPE input dimension is not compatible.\");\n      }\n\t\t}\n\n    X(snpe_run);\n    CAFFE_ENFORCE(ctx_.get(), \"SNPE context doesn't exist.\");\n    snpe_run_f(ctx_.get(), Input(0).data<float>(), Input(0).size(), &dims, &dimSize);\n\n    std::vector<int64_t> outputDims(dimSize + 1);\n    outputDims[0] = 1;\n    for (auto i = 0; i < dimSize; ++i) {\n      outputDims[i+1] = dims[i];\n    };\n\n    Output(0)->Resize(outputDims);\n    X(snpe_copy_output_to);\n    snpe_copy_output_to_f(ctx_.get(), Output(0)->mutable_data<float>());\n\n    CAFFE_ENFORCE(Output(0)->data<float>(), \"nullptr where output should be!\\n\");\n    return true;\n  }\n\n private:\n  string model_buffer_;\n  string input_name_;\n  deleted_unique_ptr<void> handle_;\n  // needs to be destroyed *before* handle_\n  deleted_unique_ptr<void> ctx_;\n};\n\nREGISTER_CPU_OPERATOR(SNPE, SNPEOp);\n}\n\n#undef X\n"
  },
  {
    "path": "caffe2/mobile/contrib/snpe/snpe_op_benchmark.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifdef __ARM_NEON__\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\n#define TEST_REAL_DATA 0\n// If you want to test with real data you may want to grab this\n// script P57273314 and a 227x227 png of a cat or something.\n#if TEST_REAL_DATA\n#include \"data_chw.h\"\n#include \"data_hwc.h\"\n#define POPULATE_DATA(_n, _s, _l) do {\\\n  Blob* _blob = ws.CreateBlob((_n));\\\n  auto* _tensor = _blob->GetMutable<TensorCPU>();\\\n  _tensor->Resize((_s));\\\n  memcpy(_tensor->mutable_data<float>(), data_##_l, _tensor->nbytes());\\\n} while(0)\n#else\n// Rough test on static data\n#define POPULATE_DATA(_n, _s, _l) do {\\\n  Blob* _blob = ws.CreateBlob((_n));\\\n  auto* _tensor = _blob->GetMutable<TensorCPU>();\\\n  _tensor->Resize((_s));\\\n  memset(_tensor->mutable_data<float>(), 1, _tensor->nbytes());\\\n} while(0)\n#endif\n\n#include <cmath>\n#include <random>\n#include <iostream>\n#include <fstream>\n\nnamespace caffe2 {\n\nvoid AddConstInput(const vector<TIndex>& shape,\n                   const float value,\n                   const string& name,\n                   Workspace* ws) {\n  DeviceOption option;\n  CPUContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(shape);\n  math::Set<float, CPUContext>(tensor->size(), value,\n                               tensor->mutable_data<float>(),\n                               &context);\n}\n\nvoid AddNoiseInput(const vector<TIndex>& shape,\n                   const string& name,\n                   Workspace* ws) {\n  DeviceOption option;\n  CPUContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(shape);\n\n  math::RandGaussian<float, CPUContext>(\n    tensor->size(),\n    0.0f, 10.0f,\n    tensor->mutable_data<float>(),\n    &context);\n}\n\n\nfloat snpe_run(int iters, Workspace& ws) {\n  const int H = 227;\n  const int W = 227;\n  const int C = 3;\n\n  POPULATE_DATA(\"X_snpe\", (caffe2::vector<caffe2::TIndex>{H, W, C}), hwc);\n  \n  OperatorDef def;\n  def.set_name(\"snpe_test\");\n  def.set_type(\"SNPE\");\n  def.add_input(\"X_snpe\");\n  def.add_output(\"snpeout\");\n  std::ostringstream model_buffer;\n  std::ifstream file(\"/data/local/tmp/squeeze_net.dlc\", std::ios::in|std::ios::binary);\n  CAFFE_ENFORCE(file.is_open(), \"Couldn't open test model.\");\n  model_buffer << file.rdbuf();\n  CAFFE_ENFORCE(model_buffer.str().length() > 0, \"Couldn't load model into string.\");\n  def.add_arg()->CopyFrom(MakeArgument(\"model_buffer\", model_buffer.str()));\n\n  unique_ptr<OperatorBase> op(CreateOperator(def, &ws));\n  assert(op.get());\n  Timer timer;\n  timer.Start();\n  for (auto i = 0; i < iters; ++i) {\n    op->Run();\n  }\n  return timer.MicroSeconds();\n}\n\nfloat caffe2_run(int iters, Workspace& ws) {\n  NetDef init_net;\n  NetDef predict_net;\n\n  const int N = 1;\n  const int H = 227;\n  const int W = 227;\n  const int C = 3;\n\n  ReadProtoFromBinaryFile(\"/data/local/tmp/squeeze_init_net.pb\", &init_net);\n  ReadProtoFromBinaryFile(\"/data/local/tmp/squeeze_predict_net.pb\", &predict_net);\n  ws.RunNetOnce(init_net);\n  POPULATE_DATA(\"data\", (caffe2::vector<caffe2::TIndex>{N, C, H, W}), chw);\n  predict_net.set_name(\"SqueezeNet\");\n  ws.CreateNet(predict_net);\n\n  // Timing caffe2\n  Timer timer;\n  timer.Start();\n  for (auto i = 0; i < iters; ++i) {\n    ws.RunNet(\"SqueezeNet\");\n  }\n  float us = timer.MicroSeconds();\n\n  OperatorDef copy_def;\n  copy_def.set_type(\"Copy\");\n  copy_def.set_name(\"Copy\");\n  copy_def.add_input(\"softmaxout\");\n  copy_def.add_output(\"caffe2out\");\n  unique_ptr<OperatorBase> copy_op(CreateOperator(copy_def, &ws));\n  copy_op->Run();\n  return us;\n}\n\n} // caffe2\n\nint main(int argc, char** argv) {\n  caffe2::GlobalInit(&argc, &argv);\n  caffe2::Workspace ws;\n  int iters = 50;\n\n  std::cout << \"Testing caffe2...\";\n  float t_caffe2 = caffe2::caffe2_run(iters, ws);\n  std::cout << \"done!\\nTesting snpe...\";\n  float t_snpe = caffe2::snpe_run(iters, ws);\n  std::cout << \"done!\\n\";\n\n  caffe2::Blob* caffe2_out_blob = ws.GetBlob(\"caffe2out\");\n  auto& caffe2_tensor = caffe2_out_blob->Get<caffe2::TensorCPU>();\n  caffe2::Blob* snpe_out_blob = ws.GetBlob(\"snpeout\");\n  auto& snpe_tensor = snpe_out_blob->Get<caffe2::TensorCPU>();\n\n  CAFFE_ENFORCE(snpe_tensor.size() == caffe2_tensor.size(), \"Outputs are not the same!\\n\");\n\n  float total_diff = 0;\n  float KL_divergence = 0;\n  float JS_divergence = 0;\n  float max = 0;\n  int max_index = 0;\n\n  for (auto i = 0; i < snpe_tensor.size(); ++i) {\n    auto Q = caffe2_tensor.data<float>()[i];\n    auto P = snpe_tensor.data<float>()[i];\n    if (Q > max) {\n      max = Q;\n      max_index = i;\n    }\n    auto diff = fabs(P - Q);\n    auto avg = P + Q / 2;\n    if (P && Q) {\n      KL_divergence += P * log(P / Q);\n      JS_divergence += 0.5 * P * log(P / Q) + 0.5 * Q * log(Q / P);\n    }\n    total_diff += diff;\n    if (diff / avg > 0.10 && avg > 0.01) { // 10% difference and a non trivial confidence\n      std::cout << \"Diff: \" << diff << \" (\" << P << \" vs \" << Q << \")\\n\";\n    }\n  }\n\n  float avg_diff = total_diff; // Avg difference as percentage (not a great metric)\n  printf(\"Average difference is %f%%\\n\", avg_diff * 100);\n  printf(\"JS Divergence is %f\\n\", JS_divergence); // Jensen-Shannon\n  printf(\"KL Divergence is %f\\n\", KL_divergence); // Kullback–Leibler\n  printf(\"Predicted %d with %f%% confidence\\n\", max_index, max * 100);\n\n  printf (\"Caffe2: %f microseconds.\\n\", t_caffe2);\n  printf (\"SNPE: %f microseconds.\\n\", t_snpe);\n  printf (\"SNPE impl %fx faster\\n\", t_caffe2/t_snpe);\n  return 0;\n}\n#else\n// Compile for different targets.\nint main() {\n  return 0;\n}\n#endif\n"
  },
  {
    "path": "caffe2/mobile/contrib/ulp2/ulp.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"ulp.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"ulp_neon.h\"\n\nnamespace caffe2 {\n\nvoid uniformQuantize2b1b(const TensorCPU& X,\n                         const std::vector<std::unique_ptr<TensorCPU>>& XQ,\n                         float offset,\n                         float inter_center_distance) {\n  CAFFE_ENFORCE_GT(X.ndim(), 1);\n  const auto N = X.size_to_dim(X.ndim() - 1);\n  auto C = X.size() / N;\n  const auto QC = divRoundUp(C,  8);\n  auto XQs = X.dims();\n  XQs[X.ndim() - 1] = QC;\n  CAFFE_ENFORCE_EQ(XQ.size(), k2b1bXBits);\n  for (auto i = 0; i < k2b1bXBits; ++i) {\n    XQ[i]->Resize(XQs);\n  }\n  const float* Xdata = X.data<float>();\n  std::array<uint8_t*, k2b1bXBits> XQdata;\n  for (auto i = 0; i < k2b1bXBits; ++i) {\n    XQdata[i] = XQ[i]->mutable_data<uint8_t>();\n  }\n  for (auto n = 0; n < N; ++n) {\n    for (auto qc = 0; qc < QC; ++qc) {\n      // compute the block in X.\n      std::array<uint8_t, k2b1bXBits> p = {{0, 0}};\n      for (auto b = 0; b < 8; ++b) {\n        const auto c = qc * 8 + b;\n        if (c < C) {\n          float v = Xdata[qc * 8 + b + C * n];\n          if (v < offset) {\n            // zero'd already.\n          } else if (v < offset + inter_center_distance) {\n            p[0] |= 1 << b;\n          } else if (v < offset + 2 * inter_center_distance) {\n            p[1] |= 1 << b;\n          } else {\n            p[0] |= 1 << b;\n            p[1] |= 1 << b;\n          }\n        }\n      }\n      for (auto i = 0; i < k2b1bXBits; ++i) {\n        XQdata[i][qc + QC * n] = p[i];\n      }\n    }\n  }\n}\n\nvoid qconv(const ConvArgs& args,\n           const TensorCPU& X,\n           const TensorCPU& W,\n           const TensorCPU* b,\n           TensorCPU* Y) {\n  const auto N = X.dim32(0);\n  const auto IH = X.dim32(1);\n  const auto IW = X.dim32(2);\n  const auto KH = W.dim32(1);\n  const auto KW = W.dim32(2);\n  const auto KC = W.dim32(3);\n  Y->Resize(X.dim32(0),\n            (X.dim32(1) - KH + args.pad_t + args.pad_b) / args.stride_h + 1,\n            (X.dim32(2) - KW + args.pad_l + args.pad_r) / args.stride_w + 1,\n            W.dim32(0));\n  const auto OH = Y->dim32(1);\n  const auto OW = Y->dim32(2);\n  const auto OC = Y->dim32(3);\n\n  CAFFE_ENFORCE_EQ(W.dim32(3), X.dim32(3));\n\n  const auto* Xdata = X.data<uint8_t>();\n  const auto* Wdata = W.data<uint8_t>();\n  auto* Ydata = Y->mutable_data<float>();\n  for (size_t n = 0; n < N; ++n) {\n    for (size_t oh = 0; oh < OH; ++oh) {\n      for (size_t ow = 0; ow < OW; ++ow) {\n        for (size_t oc = 0; oc < OC; ++oc) {\n          float acc = 0.0;\n          for (size_t kh = 0; kh < KH; ++kh) {\n            const int32_t ih = (int32_t)kh + (int32_t)args.stride_h * oh - (int32_t)args.pad_t;\n            for (size_t kw = 0; kw < KW; ++kw) {\n              const int32_t iw = (int32_t)kw + (int32_t)args.stride_w * ow - (int32_t)args.pad_l;\n              for (size_t kc = 0; kc < KC; ++kc) {\n                const uint8_t w = Wdata[kc + KC * kw + KC * KW * kh + KC * KW * KH * oc];\n                // Use unsigned integer math to avoid multiple comparisons (>= H, < 0).\n                if ((size_t)ih >= (size_t)IH || (size_t)iw >= (size_t)IW) {\n                  acc += __builtin_popcount(0 ^ w);\n                } else {\n                  const uint8_t x =\n                      Xdata[kc + KC * (size_t)iw + KC * IW * (size_t)ih + n * KC * IW * IH];\n                  const uint8_t w = Wdata[kc + KC * kw + KC * KW * kh + KC * KW * KH * oc];\n                  acc += __builtin_popcount(x ^ w);\n                }\n              }\n            }\n          }\n          Ydata[oc + OC * ow + OC * OW * oh + n * OC * OW * OH] =\n              KW * KH * KC * 8 - 2 * acc + (b ? b->data<float>()[oc] : 0.0);\n          ;\n        }\n      }\n    }\n  }\n}\n\nvoid qpad_zero(const ConvArgs& args, const TensorCPU& X, TensorCPU* Y) {\n  CAFFE_ENFORCE_EQ(args.stride_h, 1);\n  CAFFE_ENFORCE_EQ(args.stride_w, 1);\n  const auto* Xdata = X.data<uint8_t>();\n  Y->Resize(X.dim32(0),\n            X.dim32(1) + args.pad_t + args.pad_b,\n            X.dim32(2) + args.pad_l + args.pad_r,\n            X.dim32(3));\n  auto* Ydata = Y->mutable_data<uint8_t>();\n  ::memset(Ydata, Y->nbytes(), 0);\n  const auto C = Y->dim32(3);\n  const auto XrowSize = X.dim32(3) * X.dim32(2);\n  const auto YrowSize = Y->dim32(3) * Y->dim32(2);\n  math::CopyMatrix<CPUContext>(1,\n                               X.dim32(1),\n                               XrowSize,\n                               Xdata,\n                               XrowSize,\n                               Ydata + C * args.pad_l + YrowSize * args.pad_t,\n                               YrowSize,\n                               nullptr);\n}\n\nvoid signQuantize(const TensorCPU& X, TensorCPU* XQ) {\n  CAFFE_ENFORCE_GT(X.ndim(), 1);\n  const auto N = X.size_to_dim(X.ndim() - 1);\n  auto C = X.size() / N;\n  const auto QC = divRoundUp(C,  8);\n  auto XQs = X.dims();\n  XQs[X.ndim() - 1] = QC;\n  XQ->Resize(XQs);\n  const float* Xdata = X.data<float>();\n  uint8_t* XQdata = XQ->mutable_data<uint8_t>();\n  for (auto n = 0; n < N; ++n) {\n    for (auto qc = 0; qc < QC; ++qc) {\n      // compute the block in X.\n      uint8_t p = 0;\n      for (auto b = 0; b < 8; ++b) {\n        const auto c = qc * 8 + b;\n        if (c < C) {\n          p |= (Xdata[c + C * n] > 0) << b;\n        }\n      }\n      XQdata[qc + QC * n] = p;\n    }\n  }\n}\n\nvoid filterNormalization11(const TensorCPU& WQ, TensorCPU* WQN) {\n  const auto F = WQ.dim32(0);\n  // In our NEON kernel we read up to TileSize, so align allocation to TileSize elements.\n  WQN->Resize(divRoundUp(F, kGEMMTileSize) * kGEMMTileSize);\n  const auto WQs = WQ.size() / F;\n  const auto WQbits = 8 * WQs;\n  const auto* WQdata = WQ.data<uint8_t>();\n  auto* WQNdata = WQN->mutable_data<float>();\n  for (auto f = 0; f < F; ++f) {\n    int32_t bitSum = 0;\n    for (auto j = 0; j < WQs; ++j) {\n      bitSum += __builtin_popcount(WQdata[f * WQs + j]);\n    }\n    DCHECK_LE(bitSum, WQbits);\n    WQNdata[f] = 2 * bitSum - WQbits;\n  }\n}\n\nvoid filterNormalizationL1(const TensorCPU& W, TensorCPU* WL1) {\n  const auto F = W.dim32(0);\n  WL1->Resize(F);\n  const auto Ws = W.size() / F;\n  const auto* Wdata = W.data<float>();\n  auto* WL1data = WL1->mutable_data<float>();\n  for (auto f = 0; f < F; ++f) {\n    double l1sum = 0.0;\n    for (auto j = 0; j < Ws; ++j) {\n      l1sum += std::abs(Wdata[f * Ws + j]);\n    }\n    WL1data[f] = l1sum / Ws;\n  }\n}\n\nvoid qim2col(const ConvArgs& args, const TensorCPU& XQ, const TensorCPU& WQ, TensorCPU* XQcol) {\n  // TODO: pass pre-resized output?\n  // TODO: handle strides?\n\n  CAFFE_ENFORCE_EQ(XQ.dim32(3), WQ.dim32(3));\n  const size_t N = XQ.dim32(0);\n  const size_t IH = XQ.dim32(1);\n  const size_t IW = XQ.dim32(2);\n  const size_t KH = WQ.dim32(1);\n  const size_t KW = WQ.dim32(2);\n  const size_t KC = WQ.dim32(3);\n\n  XQcol->Resize(XQ.dim32(0),\n                (XQ.dim32(1) - KH + args.pad_t + args.pad_b) / args.stride_h + 1,\n                (XQ.dim32(2) - KW + args.pad_l + args.pad_r) / args.stride_w + 1,\n                KH * KW * KC);\n\n  if (args.pad_l == 0 && args.pad_r == 0 && args.pad_b == 0 && args.pad_t == 0 &&\n      args.stride_h == 1 && args.stride_w == 1 && KH == 1 && KW == 1) {\n    CAFFE_ENFORCE_EQ(XQ.size(), XQcol->size());\n    XQcol->ShareExternalPointer(const_cast<uint8_t*>(XQ.data<uint8_t>()), XQ.size());\n    return;\n  }\n  const size_t OH = XQcol->dim32(1);\n  const size_t OW = XQcol->dim32(2);\n\n  const uint8_t* XQdata = XQ.data<uint8_t>();\n  uint8_t* XQcoldata = XQcol->mutable_data<uint8_t>();\n  for (size_t n = 0; n < N; ++n) {\n    for (size_t oh = 0; oh < OH; ++oh) {\n      int32_t h_pad = (int32_t)(args.stride_h * oh) - (int32_t)args.pad_t;\n      for (size_t ow = 0; ow < OW; ++ow) {\n        int32_t w_pad = (int32_t)(args.stride_w * ow) - (int32_t)args.pad_l;\n        for (size_t kh = 0; kh < KH; ++kh) {\n          int32_t ih = (int32_t)kh + h_pad;\n          if ((size_t)ih < (size_t)IH && (size_t)w_pad < (size_t)IW &&\n              (size_t)((int32_t)w_pad + (int32_t)KW) < (size_t)IW) {\n            // We can do a larger memcpy, of size KW * KC\n            size_t off = kh * KW * KC + ow * KH * KW * KC + oh * KH * KW * KC * OW +\n                         n * KH * KW * KC * OW * OH;\n            std::memcpy(&XQcoldata[off],\n                        &XQdata[((int32_t)w_pad) * KC + ih * IW * KC + n * IW * KC * IH],\n                        KW * KC);\n          } else {\n            for (size_t kw = 0; kw < KW; ++kw) {\n              int32_t iw = (int32_t)kw + w_pad;\n              // Use unsigned integer math to avoid multiple comparisons (>= H, < 0).\n              size_t off = kw * KC + kh * KW * KC + ow * KH * KW * KC + oh * KH * KW * KC * OW +\n                           n * KH * KW * KC * OW * OH;\n              if ((size_t)ih < (size_t)IH && (size_t)iw < (size_t)IW) {\n                std::memcpy(\n                    &XQcoldata[off], &XQdata[iw * KC + ih * IW * KC + n * KC * IW * IH], KC);\n              } else {\n                // This should be simply padded with zero.\n                std::memset(&XQcoldata[off], 0, KC);\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\nstd::unique_ptr<QConvState> create2b1bConvState(Workspace* ws,\n                                                const TensorCPU& W,\n                                                const TensorCPU* b) {\n  auto state = caffe2::make_unique<QConvState>();\n  state->XQs.resize(k2b1bXBits);\n  state->YQs.resize(k2b1bXBits);\n  for (auto i = 0; i < k2b1bXBits; ++i) {\n    state->XQs[i] = caffe2::make_unique<TensorCPU>();\n    state->YQs[i] = caffe2::make_unique<TensorCPU>();\n  }\n  state->WQ = caffe2::make_unique<TensorCPU>();\n  state->WQN = caffe2::make_unique<TensorCPU>();\n  state->WQL1Norm = caffe2::make_unique<TensorCPU>();\n  state->scratch = caffe2::make_unique<TensorCPU>();\n  state->scratchColBuffer = caffe2::make_unique<TensorCPU>();\n\n  signQuantize(W, state->WQ.get());\n  filterNormalization11(*(state->WQ), state->WQN.get());\n  filterNormalizationL1(W, state->WQL1Norm.get());\n  // TODO: incorporate center distance normalization.\n  // Since inputs to convs are [0, 1, 2, 3], instead of [0, x, 2 * x, ...],\n  // we can just uniformly rescale the outputs by x, i.e.,\n  // for (auto i = 0; i < r->WQL1Norm.size(); ++i) {\n  //   r->WQL1Norm.mutable_data<float>()[i] *= center_distance;\n  // }\n  state->parallelFor = [ws](size_t range, std::function<void(size_t)> f) {\n#if CAFFE2_MOBILE\n    ws->GetThreadPool()->run([&](int, size_t v) { f(v); }, range);\n#else\n    for (size_t v = 0; v < range; ++v) {\n      f(v);\n    }\n#endif\n  };\n  if (b) {\n    state->bias = caffe2::make_unique<TensorCPU>(*b);\n  }\n  return state;\n}\n\nvoid run2b1bConvGeneric(QConvState* state, const ConvArgs& args, const TensorCPU& X, TensorCPU* Y) {\n#ifdef __ARM_NEON__\n  if (run2b1bConvNeon(state, args, X, Y)) {\n    return;\n  }\n#endif\n  uniformQuantize2b1b(X, state->XQs, 0.5, 1.0);\n  for (auto i = 0; i < k2b1bXBits; ++i) {\n    qconv(args, *(state->XQs[i]), *(state->WQ), nullptr, state->YQs[i].get());\n  }\n  Y->ResizeLike(*(state->YQs[0]));\n  const auto F = state->WQ->dim(0);\n  const auto N = Y->size() / F;\n  run2b1bUnification(state,\n                     N,\n                     F,\n                     state->WQN->data<float>(),\n                     state->YQs[0]->data<float>(),\n                     state->YQs[1]->data<float>(),\n                     F,\n                     Y->mutable_data<float>(),\n                     F,\n                     state->bias ? state->bias->data<float>() : nullptr);\n}\n\nvoid run2b1bUnification(QConvState* state,\n                        size_t N,\n                        size_t C,\n                        const float* WQNVdata,\n                        const float* YQs0Vdata,\n                        const float* YQs1Vdata,\n                        size_t YQstride,\n                        float* Ydata,\n                        size_t Ystride,\n                        const float* bias) {\n  ConstEigenVectorArrayMap<float> WQNV(WQNVdata, C);\n\n  for (size_t j = 0; j < N; ++j) {\n    ConstEigenVectorArrayMap<float> YQs0V(YQs0Vdata + YQstride * j, C);\n    ConstEigenVectorArrayMap<float> YQs1V(YQs1Vdata + YQstride * j, C);\n    EigenVectorArrayMap<float> YNV(Ydata + Ystride * j, C);\n    if (bias) {\n      ConstEigenVectorArrayMap<float> BV(bias, C);\n      YNV = (std::pow<float>(2, k2b1bXBits) - 1) / 2 * WQNV + std::pow<float>(2, -1) * YQs0V +\n            std::pow<float>(2, 0) * YQs1V + BV;\n    } else {\n      YNV = (std::pow<float>(2, k2b1bXBits) - 1) / 2 * WQNV + std::pow<float>(2, -1) * YQs0V +\n            std::pow<float>(2, 0) * YQs1V;\n    }\n  }\n}\n\nclass QConvOp final : public ConvPoolOpBase<CPUContext> {\n public:\n  QConvOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CPUContext>(operator_def, ws), ws_(ws) {\n    OPERATOR_NEEDS_FEATURE(this->order_ == StorageOrder::NHWC, \"QConvOp only supports NHWC order\");\n    OPERATOR_NEEDS_FEATURE(this->dilation_h() == 1, \"\");\n    OPERATOR_NEEDS_FEATURE(this->dilation_w() == 1, \"\");\n    OPERATOR_NEEDS_FEATURE(this->group_ == 1, \"\");\n  }\n\n  bool RunOnDeviceWithOrderNHWC() override {\n    auto& X = Input(0);\n    auto& filter = Input(1);\n    const auto* bias = InputSize() == 3 ? &Input(2) : nullptr;\n    auto* Y = Output(0);\n\n    // TODO: Support multiple quantization methods instead of assuming 2b1b.\n    if (!state_) {\n      state_ = create2b1bConvState(ws_, filter, bias);\n    }\n    ConvArgs args;\n    args.pad_l = this->pad_l();\n    args.pad_t = this->pad_t();\n    args.pad_b = this->pad_b();\n    args.pad_r = this->pad_r();\n    args.stride_h = this->stride_h();\n    args.stride_w = this->stride_w();\n    run2b1bConvGeneric(state_.get(), args, X, Y);\n    return true;\n  }\n\n private:\n  std::unique_ptr<QConvState> state_;\n  Workspace* ws_;\n};\n\nREGISTER_CPU_OPERATOR(QConv, QConvOp);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/ulp2/ulp.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\nconstexpr size_t k2b1bXBits = 2;\n\nstruct ConvArgs {\n  int stride_w{1};\n  int stride_h{1};\n  int pad_l{0};\n  int pad_t{0};\n  int pad_b{0};\n  int pad_r{0};\n};\n\nusing ParallelFor = std::function<void(size_t, std::function<void(size_t)>)>;\n\nstruct QConvState {\n  std::vector<std::unique_ptr<TensorCPU>> XQs;\n  std::vector<std::unique_ptr<TensorCPU>> YQs;\n  std::unique_ptr<TensorCPU> WQ;\n  // architecture-dependent whether packing is used.\n  std::unique_ptr<TensorCPU> WQPacked;\n  std::unique_ptr<TensorCPU> WQN;\n  std::unique_ptr<TensorCPU> WQL1Norm;\n  // Useful for e.g. incomplete tiles\n  std::unique_ptr<TensorCPU> scratch;\n  std::unique_ptr<TensorCPU> scratchColBuffer;\n\n  std::unique_ptr<TensorCPU> bias;\n\n  ParallelFor parallelFor{nullptr};\n};\n\nvoid uniformQuantize2b1b(const TensorCPU& X,\n                         const std::vector<std::unique_ptr<TensorCPU>>& XQ,\n                         float offset,\n                         float inter_center_distance);\n\nvoid qpad_zero(const ConvArgs& args, const TensorCPU& X, TensorCPU* Y);\n\ninline size_t divRoundUp(size_t x, size_t d) { return (x + d - 1) / d; }\n\nvoid signQuantize(const TensorCPU& X, TensorCPU* XQ);\nvoid filterNormalization11(const TensorCPU& WQ, TensorCPU* WQN);\nvoid filterNormalizationL1(const TensorCPU& W, TensorCPU* WL1);\nstd::unique_ptr<QConvState> create2b1bConvState(Workspace* ws,\n                                                const TensorCPU& W,\n                                                const TensorCPU* b);\nvoid run2b1bConvGeneric(QConvState* state, const ConvArgs& args, const TensorCPU& X, TensorCPU* Y);\nvoid qconv(\n    const ConvArgs& args, const TensorCPU& X, const TensorCPU& W, const TensorCPU* b, TensorCPU* Y);\nvoid qim2col(const ConvArgs& args, const TensorCPU& XQ, const TensorCPU& WQ, TensorCPU* XQcol);\n\nvoid run2b1bUnification(QConvState* state,\n                        size_t N,\n                        size_t C,\n                        const float* WQNVdata,\n                        const float* YQs0Vdata,\n                        const float* YQs1Vdata,\n                        size_t YQstride,\n                        float* Ydata,\n                        size_t Ystride,\n                        const float* bias);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/ulp2/ulp_neon.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"ulp_neon.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n// TODO: tune this with cache size detection code. Changing to 32 helps on some\n// devices (Snapdragon 820).\nconstexpr size_t kL1CacheSizeBytes = 16 * 1024;\n\n#ifdef __ARM_NEON__\n\n// Applies 2-bit uniform quantization to the floating point data at Xdata,\n// storing QC bytes into XQdata (i.e. reading 8 * QC floats from Xdata).\n// Requires QC to be a multiple of 8.\ninline void quantize2bNeon(size_t QC,\n                           const float* __restrict__ Xdata,\n                           float offset,\n                           float inter_center_distance,\n                           std::array<uint8_t*, k2b1bXBits> XQdata) {\n  DCHECK_EQ(QC % 8, 0);\n  const auto offset_plus_2_inter_center_distance = vdupq_n_f32(offset + 2 * inter_center_distance);\n  const auto offset_plus_inter_center_distance = vdupq_n_f32(offset + inter_center_distance);\n  const auto offset_ = vdupq_n_f32(offset);\n  const uint8x8_t shifts = {1 << 0, 1 << 1, 1 << 2, 1 << 3, 1 << 4, 1 << 5, 1 << 6, 1 << 7};\n\n  for (size_t qc = 0; qc < QC; qc += 8) {\n    std::array<std::array<uint8x8_t, 8>, k2b1bXBits> ps;\n    for (auto i = 0; i < k2b1bXBits; ++i) {\n      for (auto j = 0; j < 8; ++j) {\n        ps[i][j] = vdup_n_u8(0);\n      }\n    }\n\n    for (auto j = 0; j < 8; ++j) {\n      const auto x0 = vld1q_f32(&Xdata[qc * 8 + j * 8 + 0]);\n      const auto x1 = vld1q_f32(&Xdata[qc * 8 + j * 8 + 4]);\n\n      // logic.\n      // if (v >= offset + inter_center_distance) {\n      //   p[1] |= 1 << b;\n      // } else {\n      //   p[1] |= 0 << b;\n      // }\n\n      // if ((v >= offset && v < offset + inter_center_distance) ||\n      //     (v >= offset * 2 * inter_center_distance)) {\n      //   p[0] |= 1 << b;\n      // } else {\n      //   p[0] |= 0 << b;\n      // }\n\n      auto join = [](uint32x4_t a, uint32x4_t b) -> uint8x8_t {\n        return vmovn_u16(vcombine_u16(vmovn_u32(a), vmovn_u32(b)));\n      };\n\n      const auto x_geq_offset_plus_2_inter_center_distance =\n          join(vcgeq_s32(vreinterpretq_s32_f32(x0),\n                         vreinterpretq_s32_f32(offset_plus_2_inter_center_distance)),\n               vcgeq_s32(vreinterpretq_s32_f32(x1),\n                         vreinterpretq_s32_f32(offset_plus_2_inter_center_distance)));\n      const auto x_ge_offset =\n          join(vcgeq_s32(vreinterpretq_s32_f32(x0), vreinterpretq_s32_f32(offset_)),\n               vcgeq_s32(vreinterpretq_s32_f32(x1), vreinterpretq_s32_f32(offset_)));\n\n      const auto x_lt_offset_plus_inter_center_distance =\n          join(vcltq_s32(vreinterpretq_s32_f32(x0),\n                         vreinterpretq_s32_f32(offset_plus_inter_center_distance)),\n               vcltq_s32(vreinterpretq_s32_f32(x1),\n                         vreinterpretq_s32_f32(offset_plus_inter_center_distance)));\n\n      const auto p1_mask = vmvn_u8(x_lt_offset_plus_inter_center_distance);\n      const auto p0_mask = vorr_u8(vand_u8(x_ge_offset, x_lt_offset_plus_inter_center_distance),\n                                   x_geq_offset_plus_2_inter_center_distance);\n      ps[0][j] = vand_u8(shifts, p0_mask);\n      ps[1][j] = vand_u8(shifts, p1_mask);\n    }\n\n    for (auto i = 0; i < 2; ++i) {\n      const auto p01 = vpadd_u8(ps[i][0], ps[i][1]);\n      const auto p23 = vpadd_u8(ps[i][2], ps[i][3]);\n      const auto p45 = vpadd_u8(ps[i][4], ps[i][5]);\n      const auto p67 = vpadd_u8(ps[i][6], ps[i][7]);\n      const auto p0123 = vpadd_u8(p01, p23);\n      const auto p4567 = vpadd_u8(p45, p67);\n      vst1_u8(XQdata[i] + qc, vpadd_u8(p0123, p4567));\n    }\n  }\n}\n\nvoid uniformQuantize2b1bNeon(QConvState* state,\n                             const TensorCPU& X,\n                             const std::vector<std::unique_ptr<TensorCPU>>& XQ,\n                             float offset,\n                             float inter_center_distance) {\n  CAFFE_ENFORCE_GT(X.ndim(), 1);\n  const size_t C = X.dim32(X.ndim() - 1);\n  const size_t N = X.size() / C;\n  const size_t QC = divRoundUp(C, 8);\n  auto XQs = X.dims();\n  XQs[X.ndim() - 1] = QC;\n  CAFFE_ENFORCE_EQ(XQ.size(), k2b1bXBits);\n  for (auto i = 0; i < k2b1bXBits; ++i) {\n    XQ[i]->Resize(XQs);\n  }\n  const float* Xdata = X.data<float>();\n  std::array<uint8_t*, k2b1bXBits> XQdata;\n  for (size_t i = 0; i < k2b1bXBits; ++i) {\n    XQdata[i] = XQ[i]->mutable_data<uint8_t>();\n  }\n  CAFFE_ENFORCE_GT(offset, 0);\n  CAFFE_ENFORCE_GT(inter_center_distance, 0);\n  size_t QCUnroll = ((C / 8) / 8) * 8;\n  // Each worker loads an L1 cache sized block.\n  // We read/write B * K * 4 + 2 * B * (K / 8), so to fit inside C, we have\n  // B = 4 * C / 17 K.\n  // QCUnroll = 0;\n  const size_t rowsPerBlock =\n      std::max<size_t>(std::floor<size_t>(double(4 * kL1CacheSizeBytes) / double(17 * C)), 1);\n  state->parallelFor(divRoundUp(N, rowsPerBlock), [&](size_t nb) {\n    for (size_t n = nb * rowsPerBlock; n < std::min<size_t>(nb * rowsPerBlock + rowsPerBlock, N);\n         ++n) {\n      std::array<uint8_t*, k2b1bXBits> XQoff = {{\n          XQdata[0] + 0 + QC * n, XQdata[1] + 0 + QC * n,\n      }};\n      quantize2bNeon(QCUnroll, &Xdata[0 + C * n], offset, inter_center_distance, XQoff);\n      for (size_t qc = QCUnroll; qc < QC; ++qc) {\n        // compute the block in X.\n        std::array<uint8_t, k2b1bXBits> p = {{0, 0}};\n        for (size_t b = 0; b < 8; ++b) {\n          const size_t c = qc * 8 + b;\n          if (c < C) {\n            float v = Xdata[c + C * n];\n            if (v < offset) {\n              // zero'd already.\n            } else if (v < offset + inter_center_distance) {\n              p[0] |= 1 << b;\n            } else if (v < offset + 2 * inter_center_distance) {\n              p[1] |= 1 << b;\n            } else {\n              p[0] |= 1 << b;\n              p[1] |= 1 << b;\n            }\n          }\n        }\n        for (auto i = 0; i < k2b1bXBits; ++i) {\n          XQdata[i][qc + QC * n] = p[i];\n        }\n      }\n    }\n  });\n}\n\ntemplate <size_t TileSize, size_t TileDepthBytes>\nvoid uniformQuantize2b1bNeonPacked(QConvState* state,\n                                   const TensorCPU& X,\n                                   const std::vector<std::unique_ptr<TensorCPU>>& XQ,\n                                   float offset,\n                                   float inter_center_distance) {\n  const size_t M = X.size_to_dim(3);\n  const size_t K = X.size() / M;\n  const size_t QK = divRoundUp(K, 8);\n  const size_t numTiles = divRoundUp(M, TileSize);\n  const size_t numTilesDepth = divRoundUp(QK, TileDepthBytes);\n  for (size_t i = 0; i < k2b1bXBits; ++i) {\n    XQ[i]->Resize(numTiles, numTilesDepth, TileSize, TileDepthBytes);\n  }\n  const float* Xdata = X.data<float>();\n  std::array<uint8_t*, k2b1bXBits> XQdata;\n  for (auto i = 0; i < k2b1bXBits; ++i) {\n    XQdata[i] = XQ[i]->mutable_data<uint8_t>();\n  }\n  CAFFE_ENFORCE_GT(offset, 0);\n  CAFFE_ENFORCE_GT(inter_center_distance, 0);\n  // Each worker loads an L1 cache sized block.\n  // We read/write B * K * TileSize * 4 + 2 * B * TileSize * (K / 8), so to fit inside C, we have\n  // B = 4 * C / (17 * K * TileSize).\n  const size_t tilesPerBlock = std::max<size_t>(\n      std::floor<size_t>(double(4 * kL1CacheSizeBytes) / double(17 * K * TileSize)), 1);\n  state->parallelFor(divRoundUp(numTiles, tilesPerBlock), [&](size_t nb) {\n    for (size_t i = nb * tilesPerBlock;\n         i < std::min<size_t>(nb * tilesPerBlock + tilesPerBlock, numTiles);\n         ++i) {\n      for (size_t j = 0; j < numTilesDepth; ++j) {\n        if (i != numTiles - 1 && j != numTilesDepth - 1) {\n          // we have a full tile. Just memcpy.\n          for (auto ii = 0; ii < TileSize; ++ii) {\n            size_t m = i * TileSize + ii;\n            size_t k = j * TileDepthBytes * 8;\n            std::array<uint8_t*, k2b1bXBits> XQoff = {\n                {XQdata[0] + TileDepthBytes * ii + TileDepthBytes * TileSize * j +\n                     TileSize * TileDepthBytes * numTilesDepth * i,\n                 XQdata[1] + TileDepthBytes * ii + TileDepthBytes * TileSize * j +\n                     TileSize * TileDepthBytes * numTilesDepth * i}};\n            quantize2bNeon(TileDepthBytes, &Xdata[m * K + k], offset, inter_center_distance, XQoff);\n          }\n        } else {\n          for (size_t ii = 0; ii < TileSize; ++ii) {\n            size_t m = i * TileSize + ii;\n            size_t k = j * TileDepthBytes * 8;\n            std::array<uint8_t*, k2b1bXBits> XQoff = {\n                {XQdata[0] + TileDepthBytes * ii + TileDepthBytes * TileSize * j +\n                     TileSize * TileDepthBytes * numTilesDepth * i,\n                 XQdata[1] + TileDepthBytes * ii + TileDepthBytes * TileSize * j +\n                     TileSize * TileDepthBytes * numTilesDepth * i}};\n            if (m < M && k + TileDepthBytes * 8 <= K) {\n              // We can just read the stripe directly.\n              quantize2bNeon(\n                  TileDepthBytes, &Xdata[m * K + k], offset, inter_center_distance, XQoff);\n            } else {\n              // We need to pad the stripe to the full amount read by\n              // quantize2bNeon.\n              std::array<float, 8 * TileDepthBytes> Xpad = {{0}};\n              if (m < M) {\n                std::copy(&Xdata[m * K + k], &Xdata[m * K + K], Xpad.begin());\n              }\n              quantize2bNeon(TileDepthBytes, Xpad.data(), offset, inter_center_distance, XQoff);\n            }\n          }\n        }\n      }\n    }\n  });\n}\n\n// Packs a matrix (of size MxK) into a tiled array of size\n// (M/TileSize)x(K/TileDepthBytes)xTileSizexTileDepthBytes.\ntemplate <size_t TileSize, size_t TileDepthBytes>\nvoid qpack_tiles(QConvState* state, const TensorCPU& X, size_t axis, TensorCPU* XP) {\n  const size_t M = X.size_to_dim(axis);\n  const size_t QK = X.size() / M;\n  const size_t numTiles = divRoundUp(M, TileSize);\n  const size_t numTilesDepth = divRoundUp(QK, TileDepthBytes);\n  XP->Resize(numTiles, numTilesDepth, TileSize, TileDepthBytes);\n\n  const auto* __restrict__ Xdata = X.data<uint8_t>();\n  auto* __restrict__ XPdata = XP->mutable_data<uint8_t>();\n  // Load L1 sized tiles per thread.\n  // We read/write 2 * B * QK * TileSize bytes, so\n  // B = C / (2 * QK * TileSize)\n  const size_t tilesPerBlock = std::max<size_t>(\n      std::floor<size_t>(double(kL1CacheSizeBytes) / double(2 * TileSize * QK)), 1);\n  state->parallelFor(divRoundUp(numTiles, tilesPerBlock), [&](size_t nb) {\n    for (size_t i = nb * tilesPerBlock;\n         i < std::min<size_t>(nb * tilesPerBlock + tilesPerBlock, numTiles);\n         ++i) {\n      for (size_t j = 0; j < numTilesDepth; ++j) {\n        if (i != numTiles - 1 && j != numTilesDepth - 1) {\n          // we have a full tile. Just memcpy.\n          for (auto ii = 0; ii < TileSize; ++ii) {\n            auto m = i * TileSize + ii;\n            auto qk = j * TileDepthBytes;\n            std::memcpy(&XPdata[TileDepthBytes * ii + TileDepthBytes * TileSize * j +\n                                TileSize * TileDepthBytes * numTilesDepth * i],\n                        &Xdata[m * QK + qk],\n                        TileDepthBytes);\n          }\n        } else {\n          for (size_t ii = 0; ii < TileSize; ++ii) {\n            for (size_t jj = 0; jj < TileDepthBytes; ++jj) {\n              size_t m = i * TileSize + ii;\n              size_t qk = j * TileDepthBytes + jj;\n              uint8_t pval = 0;\n              if (m < M && qk < QK) {\n                // get value from X\n                pval = Xdata[m * QK + qk];\n              }\n              XPdata[jj + TileDepthBytes * ii + TileDepthBytes * TileSize * j +\n                     TileSize * TileDepthBytes * numTilesDepth * i] = pval;\n            }\n          }\n        }\n      }\n    }\n  });\n}\n\n// Computes the kUnrollM x kUnrollM tile of a GEMM by multiplying two packed\n// slices of size (kUnrolLMxK). These tiles are constructed by the qpack_tiles\n// function, which packs an input array of size [M][K] into an\n// [M/TileSize][K/TileDepthBytes][TileSize][TileDepthBytes], which ensures all\n// the array accesses in this function is contiguous.\ntemplate <size_t kUnrollM, size_t kUnrollN, size_t TileDepthBytes, typename F>\nvoid qgess_packed(const uint8_t* __restrict__ Ablock,\n                  const uint8_t* __restrict__ Bblock,\n                  float* __restrict__ Cblock,\n                  const size_t Cstride,\n                  const size_t QK,\n                  const size_t Nstart,\n                  F&& f) {\n  static_assert(kUnrollN % 8 == 0, \"\");\n  static_assert(TileDepthBytes == 16, \"\");\n  DCHECK_EQ(QK % 16, 0);\n  uint16x8_t acc[kUnrollM][kUnrollN / 8];\n  for (size_t mm = 0; mm < kUnrollM; ++mm) {\n    for (size_t nn = 0; nn < kUnrollN / 8; ++nn) {\n      acc[mm][nn] = vdupq_n_u16(0);\n    }\n  }\n  size_t qk = 0;\n  const size_t QK16Unroll = (QK / 16) * 16;\n  for (; qk < QK16Unroll; qk += 16) {\n    uint8x16_t Areg[kUnrollM];\n    for (size_t mm = 0; mm < kUnrollM; ++mm) {\n      Areg[mm] = vld1q_u8(Ablock);\n      Ablock += 16;\n    }\n\n    for (size_t nn = 0; nn < kUnrollN / 8; ++nn) {\n      uint8x16_t Breg[8];\n      for (size_t nnn = 0; nnn < 8; ++nnn) {\n        Breg[nnn] = vld1q_u8(Bblock);\n        Bblock += 16;\n      }\n      for (size_t mm = 0; mm < kUnrollM; ++mm) {\n        uint8x16_t cnts[8];\n        for (size_t nnn = 0; nnn < 8; ++nnn) {\n          cnts[nnn] = vcntq_u8(veorq_u8(Breg[nnn], Areg[mm]));\n        }\n        uint8x8_t ps[8];\n        for (size_t nnn = 0; nnn < 8; ++nnn) {\n          ps[nnn] = vadd_u8(vget_low_u8(cnts[nnn]), vget_high_u8(cnts[nnn]));\n        }\n        uint8x8_t pss[4];\n        for (size_t nnn = 0; nnn < 4; ++nnn) {\n          pss[nnn] = vpadd_u8(ps[2 * nnn], ps[2 * nnn + 1]);\n        }\n        uint8x8_t psss[2];\n        for (size_t nnn = 0; nnn < 2; ++nnn) {\n          psss[nnn] = vpadd_u8(pss[2 * nnn], pss[2 * nnn + 1]);\n        }\n        uint8x16_t out = vcombine_u8(psss[0], psss[1]);\n        acc[mm][nn] = vpadalq_u8(acc[mm][nn], out);\n      }\n    }\n  }\n\n  for (size_t mm = 0; mm < kUnrollM; ++mm) {\n    auto* Crow = Cblock + mm * Cstride;\n    for (size_t nn = 0; nn < kUnrollN / 8; ++nn) {\n      const int32x4_t K_ = vdupq_n_s32(QK * 8);\n      const int16x4_t two = vdup_n_s16(2);\n      const int16x4_t acc0123_l = vreinterpret_s16_u16(vget_low_u16(acc[mm][nn]));\n      const int16x4_t acc0123_h = vreinterpret_s16_u16(vget_high_u16(acc[mm][nn]));\n      const int32x4_t K_minus_2_acc0123_l = vmlsl_s16(K_, two, acc0123_l);\n      const int32x4_t K_minus_2_acc0123_h = vmlsl_s16(K_, two, acc0123_h);\n      f(Crow + nn * 8 + 0, vcvtq_f32_s32(K_minus_2_acc0123_l), Nstart + nn * 8 + 0);\n      f(Crow + nn * 8 + 4, vcvtq_f32_s32(K_minus_2_acc0123_h), Nstart + nn * 8 + 4);\n    }\n  }\n}\n\n// Computes the (normal + transpose) matrix-matrix product of two -1/1 binary\n// matrices, laid out in the standard format.\ntemplate <size_t TileSize, size_t TileDepthBytes, typename F>\ninline void qgemm_nt_packed(\n    QConvState* state, const TensorCPU& A, const TensorCPU& B, TensorCPU* C, F&& f = F()) {\n  CAFFE_ENFORCE_EQ(A.ndim(), 4);\n  CAFFE_ENFORCE_EQ(B.ndim(), 4);\n  CAFFE_ENFORCE_EQ(A.dim(2), TileSize);\n  CAFFE_ENFORCE_EQ(B.dim(2), TileSize);\n  CAFFE_ENFORCE_EQ(A.dim(3), TileDepthBytes);\n  CAFFE_ENFORCE_EQ(B.dim(3), TileDepthBytes);\n  const size_t MT = A.dim(0);\n  const size_t NT = B.dim(0);\n  const size_t M = MT * TileSize;\n  const size_t N = NT * TileSize;\n\n  const size_t QKT = A.dim(1);\n  const size_t K = QKT * 8 * TileDepthBytes;\n  const size_t QK = K / 8;\n  CAFFE_ENFORCE_EQ(A.dim(1), B.dim(1));\n  C->Resize(M, N);\n  const auto* Adata = A.data<uint8_t>();\n  const auto* Bdata = B.data<uint8_t>();\n  auto* Cdata = C->mutable_data<float>();\n\n  // Assume TxT tile. Each input slice is of size T x (K/8) bytes, and the output\n  // is a tile of size T x T x sizeof(float) bytes. We want the sum of this to fit\n  // in L1 cache. This means for a block number of tiles B , we load B * T * K /\n  // 8 + B * T * K / 8 + B * B * T * T * sizeof(float).\n\n  // If cache size = C, we get\n  // B = 1/(32 * T) (sqrt(256 C + K^2) - K)\n  // taking floor (by integer division), gives the result.\n\n  // Assume 16KB L1 cache.\n  size_t tilesPerBlock =\n      std::floor((std::sqrt(256 * kL1CacheSizeBytes + K * K) - K) / (32 * TileSize));\n  if (tilesPerBlock < 1) {\n    tilesPerBlock = 1;\n  }\n  CAFFE_ENFORCE_LT(K, std::pow(2, 16));\n  CAFFE_ENFORCE_EQ(M % TileSize, 0);\n  CAFFE_ENFORCE_EQ(N % TileSize, 0);\n  const size_t MNumTiles = M / TileSize;\n  const size_t NNumTiles = N / TileSize;\n  const size_t MNumBlocks = divRoundUp(MNumTiles, tilesPerBlock);\n  const size_t NNumBlocks = divRoundUp(NNumTiles, tilesPerBlock);\n\n  state->parallelFor(MNumBlocks * NNumBlocks, [&](size_t mn) {\n    const size_t mBlockIdx = mn / NNumBlocks;\n    const size_t nBlockIdx = mn % NNumBlocks;\n    const size_t mTileStart = mBlockIdx * tilesPerBlock;\n    const size_t nTileStart = nBlockIdx * tilesPerBlock;\n    for (size_t mBlockTileIdx = 0;\n         mBlockTileIdx < tilesPerBlock && mBlockTileIdx + mTileStart < MNumTiles;\n         ++mBlockTileIdx) {\n      const size_t mTileIdx = mBlockTileIdx + mTileStart;\n      for (size_t nBlockTileIdx = 0;\n           nBlockTileIdx < tilesPerBlock && nBlockTileIdx + nTileStart < NNumTiles;\n           ++nBlockTileIdx) {\n        const size_t nTileIdx = nBlockTileIdx + nTileStart;\n        // A layout: [M/TileSize][QK / TileDepth][TileSize][TileDepth]\n        // C layout: [M/TileSize][TileSize][N/TileSize][TileSize]\n        const auto* Ablock = &Adata[mTileIdx * QK * TileSize];\n        const auto* Bblock = &Bdata[nTileIdx * QK * TileSize];\n        auto* Cblock = &Cdata[mTileIdx * TileSize * N + nTileIdx * TileSize];\n        const size_t Cstride = N;\n        qgess_packed<TileSize, TileSize, TileDepthBytes, F>(\n            Ablock, Bblock, Cblock, Cstride, QK, nTileIdx * TileSize, std::forward<F>(f));\n      }\n    }\n  });\n}\n\nvoid run2b1bConvIm2ColGEMM(QConvState* state,\n                           const ConvArgs& args,\n                           const TensorCPU& X,\n                           TensorCPU* Y) {\n  // TODO: packing + quantization in same block.\n  const size_t KH = state->WQ->dim32(1);\n  const size_t KW = state->WQ->dim32(2);\n  const size_t OH = (X.dim32(1) - KH + args.pad_t + args.pad_b) / args.stride_h + 1;\n  const size_t OW = (X.dim32(2) - KW + args.pad_l + args.pad_r) / args.stride_w + 1;\n  const size_t OC = state->WQ->dim32(0);\n  const size_t QK = KH * KW * divRoundUp(X.dim32(3), 8);\n  Y->Resize(X.dim32(0), OH, OW, OC);\n  if (!state->WQPacked) {\n    state->WQPacked = caffe2::make_unique<TensorCPU>();\n    qpack_tiles<kGEMMTileSize, kGEMMTileDepthBytes>(state, *(state->WQ), 1, state->WQPacked.get());\n    CAFFE_ENFORCE_EQ(state->WQPacked->dim32(0), divRoundUp(OC, kGEMMTileSize));\n    CAFFE_ENFORCE_EQ(state->WQPacked->dim32(1), divRoundUp(QK, kGEMMTileDepthBytes));\n    CAFFE_ENFORCE_EQ(state->WQPacked->dim32(2), kGEMMTileSize);\n    CAFFE_ENFORCE_EQ(state->WQPacked->dim32(3), kGEMMTileDepthBytes);\n\n    // We can fuse the bias addition into the filter normalization. We can\n    // replace the bias + 3/2 normalization factor by replacing normalization\n    // with (2/3 bias + normalization), and setting bias to zero.\n    if (state->bias) {\n      for (size_t i = 0; i < state->bias->size(); ++i) {\n        state->WQN->mutable_data<float>()[i] += 2.0f / 3 * state->bias->data<float>()[i];\n      }\n    }\n    state->bias.reset();\n\n    // If we have to pad when we pack our weight tiles, then we need to adjust\n    // the normalization factor by the number of zeros that we added.\n    const size_t QKPadding = divRoundUp(QK, kGEMMTileDepthBytes) * kGEMMTileDepthBytes - QK;\n    if (QKPadding != 0) {\n      for (size_t i = 0; i < state->WQN->size(); ++i) {\n        state->WQN->mutable_data<float>()[i] -= QKPadding * 8;\n      }\n    }\n  }\n  CAFFE_ENFORCE(!state->bias.get());\n  // Since 1x1s are so common, we fuse the quantization + packing steps.\n  const bool is_1x1 = KH == 1 && KW == 1 && args.pad_l == 0 && args.pad_r == 0 && args.pad_b == 0 &&\n                      args.pad_t == 0 && args.stride_h == 1 && args.stride_w == 1;\n\n  if (is_1x1) {\n    CAFFE_ENFORCE_EQ(OH, X.dim32(1));\n    CAFFE_ENFORCE_EQ(OW, X.dim32(2));\n    uniformQuantize2b1bNeonPacked<kGEMMTileSize, kGEMMTileDepthBytes>(\n        state, X, state->XQs, 0.5, 1.0);\n  } else {\n    uniformQuantize2b1bNeon(state, X, state->XQs, 0.5, 1.0);\n  }\n  TensorCPU* YQ0 = state->YQs[0].get();\n\n  if (state->WQ->dim32(0) % kGEMMTileSize == 0) {\n    // We can run inplace by operating on our Y vector, and then shrinking Y.\n    YQ0 = Y;\n  }\n\n  for (size_t i = 0; i < k2b1bXBits; ++i) {\n    const auto& XQ = *(state->XQs[i]);\n    if (!is_1x1) {\n      qim2col(args, XQ, *(state->WQ), state->scratchColBuffer.get());\n      qpack_tiles<kGEMMTileSize, kGEMMTileDepthBytes>(\n          state, *(state->scratchColBuffer), 3, state->scratch.get());\n    }\n\n    {\n      const auto* __restrict__ WQNdata = state->WQN->data<float>();\n      switch (i) {\n      case 0:\n        qgemm_nt_packed<kGEMMTileSize, kGEMMTileDepthBytes>(\n            state,\n            is_1x1 ? XQ : *(state->scratch),\n            *(state->WQPacked),\n            YQ0,\n            [WQNdata](float* __restrict__ acc, float32x4_t value, size_t channel) {\n              // acc[c] = 3/2 WQN[c] + 1/2 value[c];\n              const float32x4_t _32 = vdupq_n_f32(3.0f / 2);\n              const float32x4_t _12 = vdupq_n_f32(1.0f / 2);\n              const float32x4_t WQNc_32 = vmulq_f32(_32, vld1q_f32(WQNdata + channel));\n              const float32x4_t WQNc_32_value_12 = vmlaq_f32(WQNc_32, _12, value);\n              vst1q_f32(acc, WQNc_32_value_12);\n            });\n        break;\n      case 1:\n        qgemm_nt_packed<kGEMMTileSize, kGEMMTileDepthBytes>(\n            state,\n            is_1x1 ? XQ : *(state->scratch),\n            *(state->WQPacked),\n            YQ0,\n            [](float* __restrict__ acc, float32x4_t value, size_t channel) {\n              const float32x4_t curr = vld1q_f32(acc);\n              vst1q_f32(acc, vaddq_f32(curr, value));\n            });\n        break;\n      }\n    }\n  }\n\n  if (YQ0 != Y) {\n    // In this case, the stride does not match, so we need to copy the output\n    // data into the contiguous Y matrix.\n    const size_t F = state->WQ->dim(0);\n    const size_t N = Y->size() / F;\n    const size_t NP = YQ0->dim32(0);\n    const size_t FP = YQ0->dim32(1);\n    math::CopyMatrix<CPUContext>(\n        sizeof(float), N, F, YQ0->data<float>(), FP, Y->mutable_data<float>(), F, nullptr);\n  } else {\n    CAFFE_ENFORCE_EQ(Y->dim32(0), divRoundUp(X.dim32(0) * OH * OW, kGEMMTileSize) * kGEMMTileSize);\n    CAFFE_ENFORCE_EQ(Y->dim32(1), OC);\n    Y->Shrink(X.dim32(0) * OH * OW);\n    Y->Reshape(std::vector<TIndex>{{TIndex(X.dim(0)), TIndex(OH), TIndex(OW), TIndex(OC)}});\n  }\n}\n\nbool run2b1bConvNeon(QConvState* state, const ConvArgs& args, const TensorCPU& X, TensorCPU* Y) {\n  // TODO: insert specialized cases (e.g. depthwise convolutions, the direct\n  // convolution.\n  CAFFE_ENFORCE_EQ(X.ndim(), 4);\n  run2b1bConvIm2ColGEMM(state, args, X, Y);\n  return true;\n}\n\n#endif\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mobile/contrib/ulp2/ulp_neon.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"ulp.h\"\n\nnamespace caffe2 {\n\nconstexpr size_t kGEMMTileSize = 64;\nconstexpr size_t kGEMMTileDepthBytes = 16;\n\nbool run2b1bConvNeon(QConvState* state, const ConvArgs& args, const TensorCPU& X, TensorCPU* Y);\n}\n"
  },
  {
    "path": "caffe2/mobile/contrib/ulp2/ulp_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"ulp.h\"\n#include \"ulp_neon.h\"\n#include \"gtest/gtest.h\"\n\nnamespace caffe2 {\n\nvoid conv(const ConvArgs& args,\n          const TensorCPU& X,\n          const TensorCPU& W,\n          const TensorCPU* b,\n          TensorCPU* Y) {\n  const auto N = X.dim32(0);\n  const auto IH = X.dim32(1);\n  const auto IW = X.dim32(2);\n  const auto KH = W.dim32(1);\n  const auto KW = W.dim32(2);\n  const auto IC = W.dim32(3);\n  Y->Resize(X.dim32(0),\n            (X.dim32(1) - KH + args.pad_t + args.pad_b) / args.stride_h + 1,\n            (X.dim32(2) - KW + args.pad_l + args.pad_r) / args.stride_w + 1,\n            W.dim32(0));\n  CHECK_EQ(W.dim32(3), X.dim32(3));\n  const auto OH = Y->dim32(1);\n  const auto OW = Y->dim32(2);\n  const auto OC = Y->dim32(3);\n\n  const auto* Xdata = X.data<float>();\n  const auto* Wdata = W.data<float>();\n  auto* Ydata = Y->mutable_data<float>();\n  for (auto n = 0; n < N; ++n) {\n    for (auto oh = 0; oh < OH; ++oh) {\n      for (auto ow = 0; ow < OW; ++ow) {\n        for (auto oc = 0; oc < OC; ++oc) {\n          float acc = b ? b->data<float>()[oc] : 0.0;\n          for (int kh = 0; kh < KH; ++kh) {\n            for (int kw = 0; kw < KW; ++kw) {\n              for (int ic = 0; ic < IC; ++ic) {\n                if (kh + args.stride_h * oh - args.pad_t < 0 ||\n                    kh + args.stride_h * oh - args.pad_t >= IH ||\n                    kw + args.stride_w * ow - args.pad_l < 0 ||\n                    kw + args.stride_w * ow - args.pad_l >= IW) {\n                  continue;\n                }\n                const auto x =\n                    Xdata[ic + IC * (kw + args.stride_w * ow - args.pad_l) +\n                          IC * IW * (kh + args.stride_h * oh - args.pad_t) + n * IC * IW * IH];\n                const auto w = Wdata[ic + IC * kw + IC * KW * kh + IC * KW * KH * oc];\n                acc += x * w;\n              }\n            }\n          }\n          Ydata[oc + OC * ow + OC * OW * oh + n * OC * OW * OH] = acc;\n        }\n      }\n    }\n  }\n}\n\nint randInt(int a, int b) {\n  std::random_device rd;\n  std::default_random_engine gen(rd());\n  return std::uniform_int_distribution<int>(a, b)(gen);\n}\n\nTensorCPU genTensor11(std::vector<TIndex> shape) {\n  TensorCPU r;\n  r.Resize(shape);\n\n  std::random_device rd;\n  std::default_random_engine gen(rd());\n  std::uniform_real_distribution<float> dis(0, 1);\n\n  for (auto i = 0; i < r.size(); ++i) {\n    r.mutable_data<float>()[i] = dis(gen) > 0.5 ? -1.0 : 1.0;\n  };\n  return r;\n}\n\nTensorCPU genTensorUniform11(std::vector<TIndex> shape) {\n  TensorCPU r;\n  r.Resize(shape);\n\n  std::random_device rd;\n  std::default_random_engine gen(rd());\n  std::uniform_real_distribution<float> dis(-5.0, 5.0);\n\n  for (auto i = 0; i < r.size(); ++i) {\n    r.mutable_data<float>()[i] = dis(gen);\n  };\n  return r;\n}\n\nTensorCPU genTensor0123(std::vector<TIndex> shape) {\n  TensorCPU r;\n  r.Resize(shape);\n\n  std::random_device rd;\n  std::default_random_engine gen(rd());\n  std::uniform_real_distribution<float> dis(0.1, 3.9);\n\n  for (auto i = 0; i < r.size(); ++i) {\n    r.mutable_data<float>()[i] = std::floor(dis(gen));\n  };\n  return r;\n}\n\nTEST(ULP, QPadZero) {\n  ConvArgs args;\n  args.pad_l = 1;\n  args.pad_r = 1;\n  args.pad_t = 1;\n  args.pad_b = 1;\n\n  const auto ICQ = 1;\n\n  auto X = genTensor11({1, 10, 10, ICQ * 8});\n  TensorCPU XQ, XQPad;\n  signQuantize(X, &XQ);\n  qpad_zero(args, XQ, &XQPad);\n\n  EXPECT_EQ(XQ.dim32(0), XQPad.dim32(0));\n  EXPECT_EQ(XQ.dim32(1), XQPad.dim32(1) - 2 * args.pad_l);\n  EXPECT_EQ(XQ.dim32(2), XQPad.dim32(2) - 2 * args.pad_t);\n  EXPECT_EQ(XQ.dim32(3), XQPad.dim32(3));\n  EXPECT_EQ(XQ.dim32(3), ICQ);\n  EXPECT_EQ(XQPad.dim32(3), ICQ);\n  const auto* XQdata = XQ.data<uint8_t>();\n  const auto* XQPaddata = XQPad.data<uint8_t>();\n  for (auto oh = 0; oh < XQPad.dim32(1); ++oh) {\n    for (auto ow = 0; ow < XQPad.dim32(2); ++ow) {\n      for (auto icq = 0; icq < ICQ; ++icq) {\n        auto ih = oh - args.pad_l;\n        auto iw = ow - args.pad_t;\n        if (ih < 0 || ih >= XQ.dim32(1) || iw < 0 || iw >= XQ.dim32(2)) {\n          EXPECT_EQ(XQPaddata[icq + ICQ * ow + ICQ * XQPad.dim32(2) * oh], 0);\n        } else {\n          EXPECT_EQ(XQPaddata[icq + ICQ * ow + ICQ * XQPad.dim32(2) * oh],\n                    XQdata[icq + ICQ * iw + ICQ * XQ.dim32(2) * ih]);\n        }\n      }\n    }\n  }\n}\n\ninline void gemmNT(int M, int N, int K, const float* A, const float* B, float* C) {\n  for (auto m = 0; m < M; ++m) {\n    for (auto n = 0; n < N; ++n) {\n      float acc = 0.0;\n      for (auto k = 0; k < K; ++k) {\n        acc += A[m * K + k] * B[n * K + k];\n      }\n      C[m * N + n] = acc;\n    }\n  }\n}\n\ninline void qgemmNT(int M, int N, int K, const uint8_t* A, const uint8_t* B, float* C) {\n  CHECK_EQ(K % 8, 0);\n  const int QK = K / 8;\n  for (auto m = 0; m < M; ++m) {\n    for (auto n = 0; n < N; ++n) {\n      float acc = 0.0;\n      for (auto qk = 0; qk < QK; ++qk) {\n        uint8_t mk = A[m * QK + qk];\n        uint8_t nk = B[n * QK + qk];\n        auto cnt = __builtin_popcount(mk ^ nk);\n        acc += cnt;\n      }\n      C[m * N + n] = K - 2 * acc;\n    }\n  }\n}\n\nvoid gemmTest(TIndex M, TIndex N, TIndex K) {\n  auto X = genTensor11({M, K});\n  auto W = genTensor11({N, K});\n  TensorCPU XQ, WQ, YQ, Y;\n  {\n    signQuantize(X, &XQ);\n    signQuantize(W, &WQ);\n    YQ.Resize(M, N);\n    qgemmNT(M, N, K, XQ.data<uint8_t>(), WQ.data<uint8_t>(), YQ.mutable_data<float>());\n  }\n  {\n    Y.Resize(M, N);\n    gemmNT(M, N, K, X.data<float>(), W.data<float>(), Y.mutable_data<float>());\n  }\n  EXPECT_TRUE(Y.dims() == YQ.dims());\n  for (auto i = 0; i < Y.size(); ++i) {\n    EXPECT_NEAR(Y.data<float>()[i], YQ.data<float>()[i], 1e-3);\n  }\n}\n\nTEST(QConv, GemmTest) {\n  gemmTest(8, 64, 64);\n  gemmTest(16, 64, 256);\n  gemmTest(24, 128, 192);\n  gemmTest(32, 64, 64);\n  gemmTest(40, 64, 128);\n  gemmTest(64, 64, 256);\n}\n\nTEST(QConv, ConvTest) {\n  int S = 9;\n  int IC = 16;\n  int OC = 28;\n  int K = 3;\n  auto X = genTensor11({1, S, S, IC});\n  auto W = genTensor11({OC, K, K, IC});\n  TensorCPU XQ, WQ, YQ, Y;\n  {\n    signQuantize(X, &XQ);\n    signQuantize(W, &WQ);\n    qconv(ConvArgs{}, XQ, WQ, nullptr, &YQ);\n  }\n  { conv(ConvArgs{}, X, W, nullptr, &Y); }\n  EXPECT_TRUE(Y.dims() == YQ.dims());\n  for (auto i = 0; i < Y.size(); ++i) {\n    EXPECT_NEAR(Y.data<float>()[i], YQ.data<float>()[i], 1e-3);\n  }\n}\n\nvoid ConvTest2b1b(int IC, int KH, int KW, int H, int W, int OC, int N, ConvArgs args) {\n  args.stride_h = std::min(args.stride_h, KH);\n  args.stride_w = std::min(args.stride_w, KW);\n  args.pad_l = std::min(args.pad_l, KW - 1);\n  args.pad_r = std::min(args.pad_r, KW - 1);\n  args.pad_t = std::min(args.pad_t, KH - 1);\n  args.pad_b = std::min(args.pad_b, KH - 1);\n\n  LOG(INFO) << \"IC: \" << IC << \", KH: \" << KH << \", KW: \" << KW << \", H: \" << H << \", W: \" << W\n            << \", OC: \" << OC << \", N: \" << N << \", pad_l: \" << args.pad_l\n            << \", pad_r: \" << args.pad_r << \", pad_t: \" << args.pad_t << \", pad_b: \" << args.pad_b\n            << \", stride_h: \" << args.stride_h << \", stride_w: \" << args.stride_w;\n  auto X = genTensor0123({N, H, W, IC});\n  auto W_ = genTensor11({OC, KH, KW, IC});\n  auto bias = genTensorUniform11({OC});\n  TensorCPU Y, YQ, Y2b1b, YOP;\n\n  {\n    std::vector<std::unique_ptr<TensorCPU>> XQs(k2b1bXBits);\n    std::vector<std::unique_ptr<TensorCPU>> YQs(k2b1bXBits);\n    for (auto i = 0; i < k2b1bXBits; ++i) {\n      XQs[i] = caffe2::make_unique<TensorCPU>();\n      YQs[i] = caffe2::make_unique<TensorCPU>();\n    }\n    TensorCPU WQN, WQ;\n    uniformQuantize2b1b(X, XQs, 0.5, 1.0);\n    signQuantize(W_, &WQ);\n    filterNormalization11(WQ, &WQN);\n    for (auto i = 0; i < XQs.size(); ++i) {\n      qconv(args, *(XQs[i]), WQ, nullptr, YQs[i].get());\n    }\n    YQ.ResizeLike(*YQs[0]);\n    const auto F = WQ.dim(0);\n    const auto N = YQ.size() / F;\n\n    run2b1bUnification(nullptr,\n                       N,\n                       F,\n                       WQN.data<float>(),\n                       YQs[0]->data<float>(),\n                       YQs[1]->data<float>(),\n                       F,\n                       YQ.mutable_data<float>(),\n                       F,\n                       bias.data<float>());\n  }\n\n  {\n    Workspace ws;\n    auto state = create2b1bConvState(&ws, W_, &bias);\n    run2b1bConvGeneric(state.get(), args, X, &Y2b1b);\n  }\n  {\n    Workspace ws;\n    OperatorDef def;\n    def.set_type(\"QConv\");\n    def.add_input(\"X\");\n    def.add_input(\"W\");\n    def.add_input(\"b\");\n    def.add_output(\"Y\");\n    def.add_arg()->CopyFrom(MakeArgument(\"kernel_h\", KH));\n    def.add_arg()->CopyFrom(MakeArgument(\"order\", std::string(\"NHWC\")));\n    def.add_arg()->CopyFrom(MakeArgument(\"kernel_w\", KW));\n    def.add_arg()->CopyFrom(MakeArgument(\"stride_h\", args.stride_h));\n    def.add_arg()->CopyFrom(MakeArgument(\"stride_w\", args.stride_w));\n    def.add_arg()->CopyFrom(MakeArgument(\"pad_l\", args.pad_l));\n    def.add_arg()->CopyFrom(MakeArgument(\"pad_r\", args.pad_r));\n    def.add_arg()->CopyFrom(MakeArgument(\"pad_t\", args.pad_t));\n    def.add_arg()->CopyFrom(MakeArgument(\"pad_b\", args.pad_b));\n    auto* Xws = ws.CreateBlob(\"X\")->GetMutable<TensorCPU>();\n    Xws->ResizeLike(X);\n    Xws->ShareExternalPointer(X.mutable_data<float>(), X.size());\n    auto* Wws = ws.CreateBlob(\"W\")->GetMutable<TensorCPU>();\n    Wws->ResizeLike(W_);\n    Wws->ShareExternalPointer(W_.mutable_data<float>(), W_.size());\n    auto* bws = ws.CreateBlob(\"b\")->GetMutable<TensorCPU>();\n    bws->ResizeLike(bias);\n    bws->ShareExternalPointer(bias.mutable_data<float>(), bias.size());\n    ws.RunOperatorOnce(def);\n    YOP.CopyFrom<CPUContext>(ws.GetBlob(\"Y\")->Get<TensorCPU>());\n  }\n\n  { conv(args, X, W_, &bias, &Y); }\n\n  EXPECT_TRUE(Y.dims() == YQ.dims());\n  EXPECT_TRUE(Y.dims() == Y2b1b.dims());\n  EXPECT_TRUE(Y.dims() == YOP.dims());\n\n  // for (auto i = 0; i < Y.size(); ++i) {\n  //   LOG(INFO) << \"i: \" << i << \", y[i]: \" << Y.data<float>()[i]\n  //             << \", y2b1b[i]: \" << Y2b1b.data<float>()[i] << \", yq[i]: \" << YQ.data<float>()[i];\n  // }\n\n  for (auto i = 0; i < Y.size(); ++i) {\n    EXPECT_NEAR(Y.data<float>()[i], YQ.data<float>()[i], 1e-3);\n  }\n\n  for (auto i = 0; i < Y.size(); ++i) {\n    EXPECT_NEAR(Y.data<float>()[i], Y2b1b.data<float>()[i], 1e-3);\n  }\n\n  for (auto i = 0; i < Y.size(); ++i) {\n    EXPECT_NEAR(Y.data<float>()[i], YOP.data<float>()[i], 1e-3);\n  }\n}\n\nConvArgs ca(size_t pad = 0, size_t stride = 1) {\n  ConvArgs r;\n  r.pad_l = pad;\n  r.pad_r = pad;\n  r.pad_t = pad;\n  r.pad_b = pad;\n  r.stride_w = stride;\n  r.stride_h = stride;\n  return r;\n}\n\nTEST(QConv, 2b1bConvTest) {\n  ConvTest2b1b(40, 3, 4, 10, 10, 32, 1, ca());\n  ConvTest2b1b(59, 1, 1, 1, 1, 1, 1, ca());\n  ConvTest2b1b(59, 2, 2, 3, 3, 1, 1, ca());\n  ConvTest2b1b(59, 2, 2, 3, 3, 64, 1, ca());\n  ConvTest2b1b(64, 1, 1, 1, 1, 1, 1, ca());\n  ConvTest2b1b(64, 1, 1, 1, 1, 64, 1, ca());\n  ConvTest2b1b(64, 2, 2, 3, 3, 1, 1, ca());\n  ConvTest2b1b(64, 1, 1, 3, 3, 1, 1, ca());\n  ConvTest2b1b(128, 1, 1, 1, 1, 128, 1, ca());\n  ConvTest2b1b(128, 1, 1, 8, 8, 8, 1, ca());\n  ConvTest2b1b(128, 3, 3, 25, 100, 16, 1, ca());\n  ConvTest2b1b(64, 3, 3, 10, 10, 8, 1, ca());\n  ConvTest2b1b(128, 1, 3, 10, 10, 16, 1, ca());\n  ConvTest2b1b(256, 3, 3, 14, 17, 128, 1, ca());\n  ConvTest2b1b(512, 3, 3, 3, 3, 3, 1, ca());\n  ConvTest2b1b(64, 5, 5, 14, 17, 15, 1, ca(1, 2));\n  ConvTest2b1b(64, 1, 3, 14, 17, 32, 1, ca());\n  ConvTest2b1b(64, 2, 1, 14, 17, 7, 1, ca());\n  ConvTest2b1b(128, 1, 1, 14, 17, 128, 1, ca());\n  ConvTest2b1b(128, 1, 1, 14, 17, 32, 1, ca());\n}\n\nTEST(QConv, 2b1bInputPackingTest) {\n  ConvTest2b1b(64, 1, 1, 1, 1, 128, 1, ca());\n  ConvTest2b1b(8, 1, 1, 1, 1, 1, 1, ca());\n  ConvTest2b1b(2, 1, 1, 1, 1, 1, 1, ca());\n  ConvTest2b1b(2, 1, 1, 3, 3, 1, 1, ca());\n  ConvTest2b1b(2, 2, 2, 3, 3, 1, 1, ca());\n}\n\nTEST(QConv, 2b1bConvTestRandomized) {\n  auto rca = []() {\n    ConvArgs r;\n    r.pad_l = randInt(0, 3);\n    r.pad_r = randInt(0, 3);\n    r.pad_t = randInt(0, 3);\n    r.pad_b = randInt(0, 3);\n    r.stride_w = randInt(1, 3);\n    r.stride_h = randInt(1, 3);\n    return r;\n  };\n  for (auto i = 0; i < 10; ++i) {\n    ConvTest2b1b(randInt(1, 64) * 8,\n                 randInt(1, 4),\n                 randInt(1, 4),\n                 randInt(5, 12),\n                 randInt(5, 12),\n                 randInt(1, 64),\n                 randInt(1, 2),\n                 rca());\n    // Test 3x3 path.\n    ConvTest2b1b(randInt(1, 64) * 8,\n                 3,\n                 3,\n                 randInt(5, 12),\n                 randInt(5, 12),\n                 randInt(1, 64),\n                 randInt(1, 2),\n                 rca());\n\n    // Test 3x3s2 path.\n    ConvTest2b1b(randInt(1, 64) * 8,\n                 3,\n                 3,\n                 randInt(5, 12),\n                 randInt(5, 12),\n                 randInt(1, 64),\n                 randInt(1, 2),\n                 rca());\n    // Test 3x3 path with packing.\n    ConvTest2b1b(randInt(1, 64) * 8,\n                 3,\n                 3,\n                 randInt(5, 12),\n                 randInt(5, 12),\n                 randInt(1, 8) * kGEMMTileSize,\n                 randInt(1, 2),\n                 rca());\n    // Test 1x1 path\n    ConvTest2b1b(randInt(1, 64) * 8,\n                 1,\n                 1,\n                 randInt(5, 12),\n                 randInt(5, 12),\n                 randInt(1, 64),\n                 randInt(1, 2),\n                 ca());\n\n    // Test 1x1 with direct packing\n    ConvTest2b1b(randInt(1, 64) * 8,\n                 1,\n                 1,\n                 randInt(5, 12),\n                 randInt(5, 12),\n                 randInt(1, 4) * kGEMMTileSize,\n                 randInt(1, 2),\n                 ca());\n    // Entirely arbitrary, no padding codepath.\n    ConvTest2b1b(randInt(1, 64) * 8,\n                 randInt(1, 4),\n                 randInt(1, 4),\n                 randInt(5, 12),\n                 randInt(5, 12),\n                 randInt(1, 128),\n                 randInt(1, 2),\n                 rca());\n    // Entirely arbitrary, mixed codepath.\n    ConvTest2b1b(randInt(1, 64),\n                 randInt(1, 4),\n                 randInt(1, 4),\n                 randInt(5, 12),\n                 randInt(5, 12),\n                 randInt(1, 128),\n                 randInt(1, 2),\n                 rca());\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/mpi/CMakeLists.txt",
    "content": "if(USE_MPI AND MPI_CXX_FOUND)\n    set(Caffe2_MPI_CPU_SRC\n        \"${CMAKE_CURRENT_SOURCE_DIR}/mpi_common.cc\"\n        \"${CMAKE_CURRENT_SOURCE_DIR}/mpi_ops.cc\"\n        # TODO: properly compile this together with python.\n        # \"${CMAKE_CURRENT_SOURCE_DIR}/mpi_python.cc\"\n    )\n    set(Caffe2_MPI_GPU_SRC\n        \"${CMAKE_CURRENT_SOURCE_DIR}/mpi_ops_gpu.cc\"\n    )\n    set(Caffe2_MPI_CPU_TEST_SRC\n        \"${CMAKE_CURRENT_SOURCE_DIR}/mpi_test.cc\"\n    )\n    set(Caffe2_MPI_GPU_TEST_SRC\n        \"${CMAKE_CURRENT_SOURCE_DIR}/mpi_gpu_test.cc\"\n    )\n\n    # pass up to calling script\n    set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${Caffe2_MPI_CPU_SRC})\n    set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\n    set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${Caffe2_MPI_GPU_SRC})\n    set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\n    set(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${Caffe2_MPI_CPU_TEST_SRC})\n    set(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\n    set(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} ${Caffe2_MPI_GPU_TEST_SRC})\n    set(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\nelse()\n    message(STATUS \"MPI operators skipped due to no MPI support\")\nendif()\n"
  },
  {
    "path": "caffe2/mpi/mpi_common.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/mpi/mpi_common.h\"\n\n#include <thread>\n\n#include \"caffe2/core/typeid.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\nCAFFE_KNOWN_TYPE(MPICommonWorldWrapper);\n\nstatic std::mutex gCaffe2MPIMutex;\n\nstd::mutex& MPIMutex() {\n  return gCaffe2MPIMutex;\n}\n\nstatic MPI_Comm gCaffe2MPIComm = MPI_COMM_WORLD;\n\nMPI_Comm GlobalMPIComm() {\n  return gCaffe2MPIComm;\n}\n\nvoid SetGlobalMPIComm(MPI_Comm new_comm) {\n  if (gCaffe2MPIComm != MPI_COMM_WORLD) {\n    MPI_Comm_free(&gCaffe2MPIComm);\n  }\n  gCaffe2MPIComm = new_comm;\n}\n\nint MPICommSize(MPI_Comm comm) {\n  int comm_size;\n  MPI_CHECK(MPI_Comm_size(comm, &comm_size));\n  return comm_size;\n}\n\nint MPICommRank(MPI_Comm comm) {\n  int comm_rank;\n  MPI_CHECK(MPI_Comm_rank(comm, &comm_rank));\n  return comm_rank;\n}\n\n/**\n * Helper function used to setup MPI intercommunicator.\n */\nstatic MPI_Comm AssimilateComm(MPI_Comm intra, MPI_Comm inter) {\n  MPI_Comm peer = MPI_COMM_NULL;\n  MPI_Comm newInterComm = MPI_COMM_NULL;\n  MPI_Comm newIntraComm = MPI_COMM_NULL;\n\n  // The spawned rank will be the \"high\" rank in the new intra-comm\n  int high = (MPI_COMM_NULL == intra) ? 1 : 0;\n\n  // If this is one of the (two) ranks in the inter-comm,\n  // create a new intra-comm from the inter-comm\n  if (MPI_COMM_NULL != inter) {\n    MPI_CHECK(MPI_Intercomm_merge(inter, high, &peer));\n  } else {\n    peer = MPI_COMM_NULL;\n  }\n\n  // Create a new inter-comm between the pre-existing intra-comm\n  // (all of it, not only rank zero), and the remote (spawned) rank,\n  // using the just-created intra-comm as the peer communicator.\n  int tag = 12345;\n  if (MPI_COMM_NULL != intra) {\n    // This task is a member of the pre-existing intra-comm\n    MPI_CHECK(MPI_Intercomm_create(intra, 0, peer, 1, tag, &newInterComm));\n  } else {\n    // This is the remote (spawned) task\n    MPI_CHECK(\n        MPI_Intercomm_create(MPI_COMM_SELF, 0, peer, 0, tag, &newInterComm));\n  }\n\n  // Now convert this inter-comm into an intra-comm\n  MPI_CHECK(MPI_Intercomm_merge(newInterComm, high, &newIntraComm));\n\n  // Clean up the intermediaries\n  if (MPI_COMM_NULL != peer) {\n    MPI_CHECK(MPI_Comm_free(&peer));\n  }\n  MPI_CHECK(MPI_Comm_free(&newInterComm));\n\n  // Delete the original intra-comm\n  if (MPI_COMM_NULL != intra && MPI_COMM_WORLD != intra &&\n      GlobalMPIComm() != intra) {\n    MPI_CHECK(MPI_Comm_free(&intra));\n  }\n\n  // Return the new intra-comm\n  return newIntraComm;\n}\n\nvoid MPISetupPeers(\n    const int replicas,\n    const string& role,\n    const string& job_path) {\n  int flag;\n  MPI_Initialized(&flag);\n  if (!flag) {\n    int mpi_ret;\n    MPI_CHECK(MPI_Init_thread(nullptr, nullptr, MPI_THREAD_MULTIPLE, &mpi_ret));\n    if (mpi_ret != MPI_THREAD_MULTIPLE && mpi_ret != MPI_THREAD_SERIALIZED) {\n      LOG(FATAL) << \"This test requires the underlying MPI to support the \"\n                 << \"MPI_THREAD_SERIALIZED or MPI_THREAD_MULTIPLE mode.\";\n      return;\n    }\n  }\n\n  if (MPICommSize(MPI_COMM_WORLD) != 1) {\n    LOG(ERROR) << \"MPI_COMM_WORLD size is not 1: did you already run \"\n                  \"MPISetupPeers? Note that if you execute your program with \"\n                  \"mpirun to launch multiple local processes, you should not \"\n                  \"call MPISetupPeers.\";\n    return;\n  }\n\n  if (role == \"server\") {\n    // Open a port to accept connections.\n    char port_name[MPI_MAX_PORT_NAME] = {'\\0'};\n    MPI_CHECK(MPI_Open_port(MPI_INFO_NULL, port_name));\n    VLOG(1) << \"MPI server: port: \" << port_name;\n\n    // Writes the port name to the file.\n    CHECK(WriteStringToFile(std::string(port_name), job_path.c_str()));\n    VLOG(1) << \"MPI server: wrote to file: \" << job_path;\n\n    int comm_size = MPICommSize(GlobalMPIComm());\n    while (comm_size < replicas) {\n      MPI_Comm icomm;\n      VLOG(1) << \"MPI server: waiting for client \"\n              << \"(\" << comm_size << \"/\" << replicas << \" have connected)\";\n      MPI_CHECK(\n          MPI_Comm_accept(port_name, MPI_INFO_NULL, 0, MPI_COMM_SELF, &icomm));\n      VLOG(1) << \"MPI server: accepted client\";\n      MPI_Comm new_intra_comm = AssimilateComm(GlobalMPIComm(), icomm);\n      SetGlobalMPIComm(new_intra_comm);\n      comm_size = MPICommSize(new_intra_comm);\n    }\n  } else {\n    // Opens the job path file to obtain server address.\n    std::string port_name;\n    while (!ReadStringFromFile(job_path.c_str(), &port_name) ||\n           port_name.length() == 0) {\n      /* sleep override */\n      std::this_thread::sleep_for(std::chrono::seconds(1));\n    }\n\n    // Connect to server.\n    MPI_Comm icomm;\n    VLOG(1) << \"MPI client: connecting to port: \" << port_name;\n    MPI_CHECK(MPI_Comm_connect(\n        const_cast<char*>(port_name.c_str()),\n        MPI_INFO_NULL,\n        0,\n        GlobalMPIComm(),\n        &icomm));\n\n    VLOG(1) << \"MPI client: connected\";\n\n    // Join the server's reference intracommunicator.\n    MPI_Comm new_intra_comm = AssimilateComm(MPI_COMM_NULL, icomm);\n    SetGlobalMPIComm(new_intra_comm);\n\n    // Let other clients join the intracommunicator we're now a part of.\n    while (MPICommSize(GlobalMPIComm()) < replicas) {\n      MPI_Comm comm = AssimilateComm(GlobalMPIComm(), MPI_COMM_NULL);\n      SetGlobalMPIComm(comm);\n    }\n  }\n\n  // After all peers have assimilated, do a barrier.\n  MPI_Barrier(GlobalMPIComm());\n  VLOG(1) << \"MPI using a communicator of size: \"\n          << MPICommSize(GlobalMPIComm());\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/mpi/mpi_common.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_MPI_MPI_COMMON_H_\n#define CAFFE2_MPI_MPI_COMMON_H_\n\n#include <mpi.h>\n#include <mutex>\n\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\n\ninline void CheckInitializedMPI() {\n  int flag;\n  MPI_Initialized(&flag);\n  CAFFE_ENFORCE(flag, \"MPI does not seem to have been initialized.\");\n}\n\ntemplate <typename T> class MPIDataTypeWrapper;\n\n#define MPI_DATATYPE_WRAPPER(c_type, mpi_type)                                 \\\n  template<> class MPIDataTypeWrapper<c_type> {                                \\\n   public:                                                                     \\\n    inline static MPI_Datatype type() { return mpi_type; }                     \\\n  };\n\nMPI_DATATYPE_WRAPPER(char, MPI_CHAR)\nMPI_DATATYPE_WRAPPER(float, MPI_FLOAT)\nMPI_DATATYPE_WRAPPER(double, MPI_DOUBLE)\n// Note(Yangqing): as necessary, add more specializations.\n#undef MPI_DATATYPE_WRAPPER\n\n// For all Caffe MPI calls, we will wrap it inside an MPI mutex lock guard.\nstd::mutex& MPIMutex();\n\n#define MPI_CHECK(condition)                                 \\\n  do {                                                       \\\n    std::lock_guard<std::mutex> guard(::caffe2::MPIMutex()); \\\n    int error = (condition);                                 \\\n    CAFFE_ENFORCE(                                           \\\n        error == MPI_SUCCESS,                                \\\n        \"Caffe2 MPI Error at: \",                             \\\n        __FILE__,                                            \\\n        \":\",                                                 \\\n        __LINE__,                                            \\\n        \": \",                                                \\\n        error);                                              \\\n  } while (0)\n\n/**\n * @brief Gets the global MPI communicator used by Caffe2. In default, this\n * is MPI_COMM_WORLD unless you call SetGlobalMPIComm().\n */\nMPI_Comm GlobalMPIComm();\n\n/**\n * @brief Sets the global MPI communicator. Caffe2 takes over the ownership\n * of the passed in communicator.\n */\nvoid SetGlobalMPIComm(MPI_Comm new_comm);\n\n/**\n * @brief A helper function to return the size of the given communicator.\n */\nint MPICommSize(MPI_Comm comm);\n\n/**\n * @brief A helper function to return the rank of the given communicator.\n */\nint MPICommRank(MPI_Comm comm);\n\n/**\n * @brief A simple wrapper over an MPI common world.\n */\nclass MPICommonWorldWrapper {\n public:\n  /**\n   * @brief Creates a common world wrapper.\n   *\n   * The new common world is created by taking the existing communicator\n   * passed in as src_comm, and splitting it using the color and the rank\n   * specified. In default, we will split from Caffe2's global communicator,\n   * and use color 0 as well as rank implicitly given by src_comm. As a result,\n   * the default constructor basically creates a comm identical to the source\n   * comm world.\n   */\n  explicit MPICommonWorldWrapper(\n      MPI_Comm src_comm = MPI_COMM_NULL,\n      int color = 0,\n      int rank = -1) {\n    if (src_comm == MPI_COMM_NULL) {\n      src_comm = GlobalMPIComm();\n    }\n    if (rank == -1) {\n      MPI_CHECK(MPI_Comm_rank(src_comm, &rank));\n    }\n    MPI_CHECK(MPI_Comm_split(src_comm, color, rank, &comm_));\n    MPI_CHECK(MPI_Comm_size(comm_, &size_));\n    MPI_CHECK(MPI_Comm_rank(comm_, &rank_));\n  }\n\n  ~MPICommonWorldWrapper() {\n    int ret;\n    MPI_CHECK(MPI_Finalized(&ret));\n    if (!ret) {\n      MPI_Comm_free(&comm_);\n    }\n  }\n\n  /**\n   * @brief Returns the common world held by the wrapper.\n   */\n  inline MPI_Comm comm() const {\n    return comm_;\n  }\n  /**\n   * @brief Returns the size of the world.\n   */\n  inline int size() const {\n    return size_;\n  }\n  /**\n   * @brief Returns the rank of this process in the world.\n   */\n  inline int rank() const {\n    return rank_;\n  }\n\n private:\n  MPI_Comm comm_;\n  int size_;\n  int rank_;\n};\n\n/**\n * A function used to perform peer setup so one does not need to use\n * mpirun / mpiexec to run the binary. Note that if you use mpirun or mpiexec\n * to set up the common world, do not use this function - MPI_Init would have\n * already set that up.\n *\n * This also assumes that you have a common path (like NFS) that multiple\n * instances can read from.\n *\n * Inputs:\n *   replicas (int): the number of replicas that mpi will run with.\n *   role (string): the role of this process, \"server\" or \"client\".\n *   job_path (string): a file name that the server will write its port into\n *       and the clients will read the server's port from.\n */\nvoid MPISetupPeers(\n    const int replicas,\n    const string& role,\n    const string& job_path);\n}  // namespace caffe2\n\n#endif  // CAFFE2_MPI_MPI_COMMON_H_\n"
  },
  {
    "path": "caffe2/mpi/mpi_gpu_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/mpi/mpi_common.h\"\n#include \"google/protobuf/text_format.h\"\n#include <gtest/gtest.h>\n\nCAFFE2_DEFINE_string(\n    caffe_test_root, \"gen/\", \"The root of the caffe test folder.\");\n\nnamespace caffe2 {\n\nconst char kBcastNet[] = R\"NET(\n  name: \"bcast\"\n  op {\n    output: \"comm\"\n    type: \"MPICreateCommonWorld\"\n  }\n  op {\n    output: \"X\"\n    type: \"ConstantFill\"\n    arg {\n      name: \"shape\"\n      ints: 10\n    }\n    arg {\n      name: \"value\"\n      f: 0.0\n    }\n  }\n  op {\n    input: \"comm\"\n    input: \"X\"\n    output: \"X\"\n    type: \"MPIBroadcast\"\n    arg {\n      name: \"root\"\n      i: 0\n    }\n  }\n  device_option {\n    device_type: 1\n  }\n)NET\";\n\nTEST(MPITest, TestMPIBroadcast) {\n  NetDef net_def;\n  CHECK(google::protobuf::TextFormat::ParseFromString(\n      string(kBcastNet), &net_def));\n  // Let's set the network's constant fill value to be the mpi rank.\n  auto* arg = net_def.mutable_op(1)->mutable_arg(1);\n  CAFFE_ENFORCE_EQ(arg->name(), \"value\");\n  int rank;\n  MPI_Comm_rank(MPI_COMM_WORLD, &rank);\n  arg->set_f(rank);\n  int size;\n  MPI_Comm_size(MPI_COMM_WORLD, &size);\n\n  for (int root = 0; root < size; ++root) {\n    net_def.mutable_op(2)->mutable_arg(0)->set_i(root);\n    Workspace ws;\n    unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n    EXPECT_NE(nullptr, net.get());\n    EXPECT_TRUE(net->Run());\n    // Let's test the value.\n    auto& X = ws.GetBlob(\"X\")->Get<TensorCUDA>();\n    TensorCPU X_cpu(X);\n    EXPECT_EQ(X.size(), 10);\n    for (int i = 0; i < X.size(); ++i) {\n      EXPECT_EQ(X_cpu.data<float>()[i], root);\n    }\n  }\n}\n\nconst char kReduceNet[] = R\"NET(\n  name: \"reduce\"\n  op {\n    output: \"comm\"\n    type: \"MPICreateCommonWorld\"\n  }\n  op {\n    output: \"X\"\n    type: \"ConstantFill\"\n    arg {\n      name: \"shape\"\n      ints: 10\n    }\n    arg {\n      name: \"value\"\n      f: 0.0\n    }\n  }\n  op {\n    input: \"comm\"\n    input: \"X\"\n    output: \"X_reduced\"\n    type: \"MPIReduce\"\n    arg {\n      name: \"root\"\n      i: 0\n    }\n  }\n  device_option {\n    device_type: 1\n  }\n)NET\";\n\nTEST(MPITest, TestMPIReduce) {\n  NetDef net_def;\n  CHECK(google::protobuf::TextFormat::ParseFromString(\n      string(kReduceNet), &net_def));\n  // Let's set the network's constant fill value to be the mpi rank.\n  auto* arg = net_def.mutable_op(1)->mutable_arg(1);\n  CAFFE_ENFORCE_EQ(arg->name(), \"value\");\n  int rank0;\n  MPI_Comm_rank(MPI_COMM_WORLD, &rank0);\n  arg->set_f(rank0);\n  int size0;\n  MPI_Comm_size(MPI_COMM_WORLD, &size0);\n\n  for (int root = 0; root < size0; ++root) {\n    net_def.mutable_op(2)->mutable_arg(0)->set_i(root);\n    Workspace ws;\n    unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n    EXPECT_NE(nullptr, net.get());\n    EXPECT_TRUE(net->Run());\n    int rank;\n    MPI_Comm_rank(MPI_COMM_WORLD, &rank);\n    int size;\n    MPI_Comm_size(MPI_COMM_WORLD, &size);\n    if (rank == root) {\n      // Let's test the value.\n      auto& X = ws.GetBlob(\"X_reduced\")->Get<TensorCUDA>();\n      EXPECT_EQ(X.size(), 10);\n      int expected_result = size * (size - 1) / 2;\n      TensorCPU X_cpu(X);\n      for (int i = 0; i < X.size(); ++i) {\n        EXPECT_EQ(X_cpu.data<float>()[i], expected_result);\n      }\n    }\n  }\n}\n\nconst char kMPIAllgatherNet[] = R\"NET(\n  name: \"allgather\"\n  op {\n    output: \"comm\"\n    type: \"MPICreateCommonWorld\"\n  }\n  op {\n    output: \"X\"\n    type: \"ConstantFill\"\n    arg {\n      name: \"shape\"\n      ints: 2\n      ints: 10\n    }\n    arg {\n      name: \"value\"\n      f: 0.0\n    }\n  }\n  op {\n    input: \"comm\"\n    input: \"X\"\n    output: \"X_gathered\"\n    type: \"MPIAllgather\"\n  }\n  device_option {\n    device_type: 1\n  }\n)NET\";\n\nTEST(MPITest, TestMPIAllgather) {\n  NetDef net_def;\n  CHECK(google::protobuf::TextFormat::ParseFromString(\n      string(kMPIAllgatherNet), &net_def));\n  // Let's set the network's constant fill value to be the mpi rank.\n  auto* arg = net_def.mutable_op(1)->mutable_arg(1);\n  CAFFE_ENFORCE_EQ(arg->name(), \"value\");\n  int rank;\n  MPI_Comm_rank(MPI_COMM_WORLD, &rank);\n  arg->set_f(rank);\n  int size;\n  MPI_Comm_size(MPI_COMM_WORLD, &size);\n\n  Workspace ws;\n  unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n  EXPECT_NE(nullptr, net.get());\n  EXPECT_TRUE(net->Run());\n  // Let's test the value.\n  auto& X = ws.GetBlob(\"X\")->Get<TensorCUDA>();\n  TensorCPU X_cpu(X);\n  EXPECT_EQ(X.size(), 20);\n  for (int i = 0; i < X.size(); ++i) {\n    EXPECT_EQ(X_cpu.data<float>()[i], rank);\n  }\n  auto& X_gathered = ws.GetBlob(\"X_gathered\")->Get<TensorCUDA>();\n  EXPECT_EQ(X_gathered.size(), 20 * size);\n  EXPECT_EQ(X_gathered.dim(0), 2 * size);\n  EXPECT_EQ(X_gathered.dim(1), 10);\n  TensorCPU X_gathered_cpu(X_gathered);\n  for (int i = 0; i < X_gathered.size(); ++i) {\n    EXPECT_EQ(X_gathered_cpu.data<float>()[i], i / 20);\n  }\n}\n\nconst char kMPIAllreduceNet[] = R\"NET(\n  name: \"allreduce\"\n  op {\n    output: \"comm\"\n    type: \"MPICreateCommonWorld\"\n  }\n  op {\n    output: \"X\"\n    type: \"ConstantFill\"\n    arg {\n      name: \"shape\"\n      ints: 10\n    }\n    arg {\n      name: \"value\"\n      f: 0.0\n    }\n  }\n  op {\n    input: \"comm\"\n    input: \"X\"\n    output: \"X_reduced\"\n    type: \"MPIAllreduce\"\n  }\n  device_option {\n    device_type: 1\n  }\n)NET\";\n\nTEST(MPITest, TestMPIAllreduce) {\n  NetDef net_def;\n  CHECK(google::protobuf::TextFormat::ParseFromString(\n      string(kMPIAllreduceNet), &net_def));\n  // Let's set the network's constant fill value to be the mpi rank.\n  auto* arg = net_def.mutable_op(1)->mutable_arg(1);\n  CAFFE_ENFORCE_EQ(arg->name(), \"value\");\n  int rank;\n  MPI_Comm_rank(MPI_COMM_WORLD, &rank);\n  arg->set_f(rank);\n  int size;\n  MPI_Comm_size(MPI_COMM_WORLD, &size);\n\n  Workspace ws;\n  unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n  EXPECT_NE(nullptr, net.get());\n  EXPECT_TRUE(net->Run());\n  // Let's test the value.\n  auto& X = ws.GetBlob(\"X\")->Get<TensorCUDA>();\n  EXPECT_EQ(X.size(), 10);\n  TensorCPU X_cpu(X);\n  for (int i = 0; i < X.size(); ++i) {\n    EXPECT_EQ(X_cpu.data<float>()[i], rank);\n  }\n  auto& X_reduced = ws.GetBlob(\"X_reduced\")->Get<TensorCUDA>();\n  EXPECT_EQ(X_reduced.size(), 10);\n  int expected_result = size * (size - 1) / 2;\n  TensorCPU X_reduced_cpu(X_reduced);\n  for (int i = 0; i < X_reduced.size(); ++i) {\n    EXPECT_EQ(X_reduced_cpu.data<float>()[i], expected_result);\n  }\n}\n\nconst char kInPlaceMPIAllreduceNet[] = R\"NET(\n  name: \"allreduce\"\n  op {\n    output: \"comm\"\n    type: \"MPICreateCommonWorld\"\n  }\n  op {\n    output: \"X\"\n    type: \"ConstantFill\"\n    arg {\n      name: \"shape\"\n      ints: 10\n    }\n    arg {\n      name: \"value\"\n      f: 0.0\n    }\n  }\n  op {\n    input: \"comm\"\n    input: \"X\"\n    output: \"X\"\n    type: \"MPIAllreduce\"\n  }\n  device_option {\n    device_type: 1\n  }\n)NET\";\n\nTEST(MPITest, TestInPlaceMPIAllreduce) {\n  NetDef net_def;\n  CHECK(google::protobuf::TextFormat::ParseFromString(\n      string(kInPlaceMPIAllreduceNet), &net_def));\n  // Let's set the network's constant fill value to be the mpi rank.\n  auto* arg = net_def.mutable_op(1)->mutable_arg(1);\n  CAFFE_ENFORCE_EQ(arg->name(), \"value\");\n  int rank;\n  MPI_Comm_rank(MPI_COMM_WORLD, &rank);\n  arg->set_f(rank);\n  int size;\n  MPI_Comm_size(MPI_COMM_WORLD, &size);\n\n  Workspace ws;\n  unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n  EXPECT_NE(nullptr, net.get());\n  EXPECT_TRUE(net->Run());\n  auto& X_reduced = ws.GetBlob(\"X\")->Get<TensorCUDA>();\n  EXPECT_EQ(X_reduced.size(), 10);\n  int expected_result = size * (size - 1) / 2;\n  TensorCPU X_reduced_cpu(X_reduced);\n  for (int i = 0; i < X_reduced.size(); ++i) {\n    EXPECT_EQ(X_reduced_cpu.data<float>()[i], expected_result);\n  }\n}\n\n}  // namespace caffe2\n\n\nGTEST_API_ int main(int argc, char **argv) {\n  int mpi_ret;\n  MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &mpi_ret);\n  testing::InitGoogleTest(&argc, argv);\n  caffe2::GlobalInit(&argc, &argv);\n  int test_result = RUN_ALL_TESTS();\n  MPI_Finalize();\n  return test_result;\n}\n"
  },
  {
    "path": "caffe2/mpi/mpi_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/mpi/mpi_ops.h\"\n\nnamespace caffe2 {\n\nOPERATOR_SCHEMA(MPICreateCommonWorld)\n  .NumInputs(0)\n  .NumOutputs(1);\nOPERATOR_SCHEMA(MPIBroadcast)\n  .NumInputs(2)\n  .NumOutputs(1)\n  .EnforceInplace({{1, 0}});\nOPERATOR_SCHEMA(MPIReduce)\n  .NumInputs(2)\n  .NumOutputs(1);\nOPERATOR_SCHEMA(MPIAllgather)\n  .NumInputs(2)\n  .NumOutputs(1);\nOPERATOR_SCHEMA(MPIAllreduce)\n  .NumInputs(2)\n  .NumOutputs(1)\n  .AllowInplace({{1, 0}});\nOPERATOR_SCHEMA(MPISendTensor);\nOPERATOR_SCHEMA(MPIReceiveTensor);\n\nREGISTER_CPU_OPERATOR(MPICreateCommonWorld, MPICreateCommonWorldOp<CPUContext>);\nREGISTER_CPU_OPERATOR(MPIBroadcast, MPIBroadcastOp<CPUContext>);\nREGISTER_CPU_OPERATOR(MPIReduce, MPIReduceOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(MPIAllgather, MPIAllgatherOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(MPIAllreduce, MPIAllreduceOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(MPISendTensor, MPISendTensorOp<CPUContext>);\nREGISTER_CPU_OPERATOR(MPIReceiveTensor, MPIReceiveTensorOp<CPUContext>);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/mpi/mpi_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_MPI_MPI_OPS_H_\n#define CAFFE2_MPI_MPI_OPS_H_\n\n#include <mpi.h>\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/mpi/mpi_common.h\"\n\nnamespace caffe2 {\n\n// TODO(jiayq): if needed, write up the use of color and key with MPI split.\n// Currently, the operator simply creates a communicator that has the\n// same topology as the Caffe2 global communicator.\ntemplate <class Context>\nclass MPICreateCommonWorldOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  MPICreateCommonWorldOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    OperatorBase::Outputs()[0]->Reset(new MPICommonWorldWrapper());\n    return true;\n  }\n};\n\ntemplate <class Context>\nclass MPIBroadcastOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  MPIBroadcastOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        root_(OperatorBase::template GetSingleArgument<int>(\"root\", 0)) {}\n  ~MPIBroadcastOp() {}\n\n  bool RunOnDevice() override {\n    MPI_Comm comm = OperatorBase::Input<MPICommonWorldWrapper>(0).comm();\n    CAFFE_ENFORCE(\n        OperatorBase::OutputIsType<Tensor<Context>>(0),\n        \"Output is of wrong type.\");\n    auto* output = Output(0);\n    // Make sure that output is already allocated.\n    CAFFE_ENFORCE(\n        output->size() > 0,\n        \"Broadcast op uses in-place operation so the output \"\n        \"should be already allocated.\");\n    MPI_CHECK(MPI_Bcast(\n        output->raw_mutable_data(),\n        output->nbytes(),\n        MPIDataTypeWrapper<char>::type(),\n        root_,\n        comm));\n    return true;\n  }\n\n protected:\n  int root_;\n};\n\n// MPIReduceOp does Reduce using MPI. Currently, only SUM is supported.\ntemplate <typename T, class Context>\nclass MPIReduceOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  MPIReduceOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        root_(OperatorBase::template GetSingleArgument<int>(\"root\", 0)) {}\n  ~MPIReduceOp() {}\n\n  bool RunOnDevice() override {\n    MPI_Comm comm = OperatorBase::Input<MPICommonWorldWrapper>(0).comm();\n    auto& input = Input(1);\n    auto* output = Output(0);\n    output->ResizeLike(input);\n    MPI_CHECK(MPI_Reduce(\n        const_cast<T*>(input.template data<T>()),\n        output->template mutable_data<T>(),\n        input.size(),\n        MPIDataTypeWrapper<T>::type(),\n        MPI_SUM,\n        root_,\n        comm));\n    return true;\n  }\n\n protected:\n  int root_;\n};\n\n// MPIAllgatherOp does MPIAllgather using MPI.\ntemplate <typename T, class Context>\nclass MPIAllgatherOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(MPIAllgatherOp);\n\n  bool RunOnDevice() override {\n    MPI_Comm comm = OperatorBase::Input<MPICommonWorldWrapper>(0).comm();\n    auto& input = Input(1);\n    auto* output = Output(0);\n    vector<TIndex> output_dims = input.dims();\n    output_dims[0] *= OperatorBase::Input<MPICommonWorldWrapper>(0).size();\n    output->Resize(output_dims);\n    MPI_CHECK(MPI_Allgather(\n        const_cast<T*>(input.template data<T>()),\n        input.size(),\n        MPIDataTypeWrapper<T>::type(),\n        output->template mutable_data<T>(),\n        input.size(),\n        MPIDataTypeWrapper<T>::type(),\n        comm));\n    return true;\n  }\n};\n\n// MPIAllreduceOp does MPIAllreduce using MPI. Currently, only SUM is supported.\ntemplate <typename T, class Context>\nclass MPIAllreduceOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(MPIAllreduceOp);\n\n  bool RunOnDevice() override {\n    MPI_Comm comm = OperatorBase::Input<MPICommonWorldWrapper>(0).comm();\n    auto& input = Input(1);\n    auto* output = Output(0);\n    output->ResizeLike(input);\n    void* source;\n    if (output->template mutable_data<T>() == input.template data<T>()) {\n      // We are doing in-place call. Special case handling.\n      source = MPI_IN_PLACE;\n    } else {\n      // Normal allreduce takes the source from the input.\n      source = const_cast<T*>(input.template data<T>());\n    }\n    MPI_CHECK(MPI_Allreduce(\n        source,\n        output->template mutable_data<T>(),\n        input.size(),\n        MPIDataTypeWrapper<T>::type(),\n        MPI_SUM,\n        comm));\n    return true;\n  }\n};\n\ntemplate <class Context>\nclass MPISendTensorOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  MPISendTensorOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        OP_SINGLE_ARG(int, \"dst\", dst_, MPI_ANY_SOURCE),\n        OP_SINGLE_ARG(int, \"tag\", tag_, MPI_ANY_TAG),\n        OP_SINGLE_ARG(bool, \"raw_buffer\", raw_buffer_, false) {\n    CAFFE_ENFORCE(raw_buffer_, \"non-raw-buffer transfer not supported yet.\");\n    CAFFE_ENFORCE(\n        dst_ != MPI_ANY_SOURCE || def.input_size() == 4,\n        \"You should explicitly specify the to rank either via \"\n        \"argument or via input blobs.\");\n    CAFFE_ENFORCE(\n        tag_ != MPI_ANY_TAG || def.input_size() == 4,\n        \"You should explicitly specify the tag either via \"\n        \"argument or via input blobs.\");\n  }\n\n  bool RunOnDevice() override {\n    MPI_Comm comm = OperatorBase::Input<MPICommonWorldWrapper>(COMM).comm();\n    auto& input = Input(INPUT);\n    if (InputSize() == 4) {\n      dst_ = OperatorBase::Input<TensorCPU>(DST).template data<int>()[0];\n      tag_ = OperatorBase::Input<TensorCPU>(TAG).template data<int>()[0];\n    }\n    if (raw_buffer_) {\n      // We need to do a const cast to cope with the fact that, before OpenMPI\n      // 1.7, MPI_Send expects a non-const pointer although it uses it in a\n      // const way.\n      MPI_CHECK(MPI_Send(\n          const_cast<void*>(input.raw_data()),\n          input.nbytes(),\n          MPI_CHAR,\n          dst_,\n          tag_,\n          comm));\n    } else {\n      CAFFE_NOT_IMPLEMENTED;\n    }\n    return true;\n  }\n\n protected:\n  int dst_;\n  int tag_;\n  bool raw_buffer_;\n\n  INPUT_TAGS(COMM, INPUT, DST, TAG);\n};\n\ntemplate <class Context>\nclass MPIReceiveTensorOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  MPIReceiveTensorOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        OP_SINGLE_ARG(int, \"src\", src_, MPI_ANY_SOURCE),\n        OP_SINGLE_ARG(int, \"tag\", tag_, MPI_ANY_TAG),\n        OP_SINGLE_ARG(bool, \"raw_buffer\", raw_buffer_, false) {\n    CAFFE_ENFORCE(raw_buffer_, \"non-raw-buffer transfer not supported yet.\");\n  }\n\n  bool RunOnDevice() override {\n    MPI_Comm comm = OperatorBase::Input<MPICommonWorldWrapper>(COMM).comm();\n    if (InputSize() == 4) {\n      src_ = OperatorBase::Input<TensorCPU>(SRC_IN).template data<int>()[0];\n      tag_ = OperatorBase::Input<TensorCPU>(TAG_IN).template data<int>()[0];\n    }\n    MPI_Status status;\n    if (raw_buffer_) {\n      auto* output = Output(OUTPUT);\n      MPI_CHECK(MPI_Recv(\n          output->raw_mutable_data(),\n          output->nbytes(),\n          MPI_CHAR,\n          src_,\n          tag_,\n          comm,\n          &status));\n    } else {\n      CAFFE_NOT_IMPLEMENTED;\n    }\n    auto* src_out = OperatorBase::Output<TensorCPU>(SRC_OUT);\n    src_out->Resize();\n    src_out->template mutable_data<int>()[0] = status.MPI_SOURCE;\n    auto* tag_out = OperatorBase::Output<TensorCPU>(TAG_OUT);\n    tag_out->Resize();\n    tag_out->template mutable_data<int>()[0] = status.MPI_TAG;\n    return true;\n  }\n\n protected:\n  int src_;\n  int tag_;\n  bool raw_buffer_;\n  INPUT_TAGS(COMM, INPUT, SRC_IN, TAG_IN);\n  OUTPUT_TAGS(OUTPUT, SRC_OUT, TAG_OUT);\n};\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_MPI_MPI_OPS_H_\n"
  },
  {
    "path": "caffe2/mpi/mpi_ops_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/mpi/mpi_ops.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/operator_fallback_gpu.h\"\n\n\nnamespace caffe2 {\n\n// Here is a bunch of MPI macro definitions that allow us to see if the MPI\n// version supports CUDA aware MPI functions or not.\n\n#if OPEN_MPI\n#define CAFFE2_OMPI_VERSION \\\n  OMPI_MAJOR_VERSION * 10000 + OMPI_MINOR_VERSION * 100 + OMPI_RELEASE_VERSION\n#if CAFFE2_OMPI_VERSION >= 20000\n// OpenMPI 2.x now supports compile time check whether CUDA is supported.\n#include \"mpi-ext.h\" /* Needed for CUDA-aware check */\n#if MPIX_CUDA_AWARE_SUPPORT\n#define CAFFE2_HAS_CUDA_MPI_BASICS 1\n#define CAFFE2_HAS_CUDA_MPI_ALLREDUCE 1\n#endif // MPIX_CUDA_AWARE_SUPPORT\n#else // CAFFE2_OMPI_VERSION >= 2000\n// In the case of OpenMPI 1.x, we don't have compile-time flags to\n// figure out if CUDA is supported; as a result, we will assume that\n// the user has built OpenMPI with CUDA support.\n// CUDA-aware MPIBroadcast is introduced after OpenMPI 1.7.\n#if CAFFE2_OMPI_VERSION >= 10700\n#define CAFFE2_HAS_CUDA_MPI_BASICS 1\n#else // CAFFE2_OMPI_VERSION >= 10700\n#define CAFFE2_HAS_CUDA_MPI_BASICS 0\n#endif // CAFFE2_OMPI_VERSION >= 10700\n// CUDA-aware MPIAllreduce is introduced after OpenMPI 1.8.5.\n#if CAFFE2_OMPI_VERSION >= 10805\n#define CAFFE2_HAS_CUDA_MPI_ALLREDUCE 1\n#else // CAFFE2_OMPI_VERSION >= 10805\n#define CAFFE2_HAS_CUDA_MPI_ALLREDUCE 0\n#endif // CAFFE2_OMPI_VERSION >= 10805\n#endif // CAFFE2_OMPI_VERSION >= 2000\n#else // !OPEN_MPI\n// We have not really tested against other MPI environments, so let's go for a\n// safe path and basically say we don't have cuda-aware functions.\n#define CAFFE2_HAS_CUDA_MPI_BASICS 0\n#define CAFFE2_HAS_CUDA_MPI_ALLREDUCE 0\n#endif // OPEN_MPI\n\n// We allow a macro to force using fallback functions.\n#ifdef CAFFE2_FORCE_FALLBACK_CUDA_MPI\n#undef CAFFE2_HAS_CUDA_MPI_BASICS\n#undef CAFFE2_HAS_CUDA_MPI_ALLREDUCE\n#define CAFFE2_HAS_CUDA_MPI_BASICS 0\n#define CAFFE2_HAS_CUDA_MPI_ALLREDUCE 0\n#endif // CAFFE2_FORCE_FALLBACK_CUDA_MPI\n\nREGISTER_CUDA_OPERATOR(\n    MPICreateCommonWorld,\n    MPICreateCommonWorldOp<CUDAContext>);\n#if CAFFE2_HAS_CUDA_MPI_BASICS\nREGISTER_CUDA_OPERATOR(MPIBroadcast, MPIBroadcastOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(MPIReduce, MPIReduceOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(MPIAllgather, MPIAllgatherOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(MPISendTensor, MPISendTensorOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(MPIReceiveTensor, MPIReceiveTensorOp<CUDAContext>);\n#else\nREGISTER_CUDA_OPERATOR(MPIBroadcast, GPUFallbackOp<MPIBroadcastOp<CPUContext>>);\nREGISTER_CUDA_OPERATOR(\n    MPIReduce,\n    GPUFallbackOp<MPIReduceOp<float, CPUContext>>);\nREGISTER_CUDA_OPERATOR(\n    MPIAllgather,\n    GPUFallbackOp<MPIAllgatherOp<float, CPUContext>>);\nREGISTER_CUDA_OPERATOR(\n    MPISendTensor,\n    GPUFallbackOp<MPISendTensorOp<CPUContext>>);\nREGISTER_CUDA_OPERATOR(\n    MPIReceiveTensor,\n    GPUFallbackOp<MPIReceiveTensorOp<CPUContext>, SkipIndices<1, 2>>);\n#endif\n\n#if CAFFE2_HAS_CUDA_MPI_ALLREDUCE\nREGISTER_CUDA_OPERATOR(MPIAllreduce, MPIAllreduceOp<float, CUDAContext>);\n#else\nREGISTER_CUDA_OPERATOR(\n    MPIAllreduce,\n    GPUFallbackOp<MPIAllreduceOp<float, CPUContext>>);\n#endif\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/mpi/mpi_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/mpi/mpi_common.h\"\n#include \"google/protobuf/text_format.h\"\n#include <gtest/gtest.h>\n\nCAFFE2_DEFINE_string(\n    caffe_test_root, \"gen/\", \"The root of the caffe test folder.\");\n\nnamespace caffe2 {\n\nconst char kBcastNet[] = R\"NET(\n  name: \"bcast\"\n  op {\n    output: \"comm\"\n    type: \"MPICreateCommonWorld\"\n  }\n  op {\n    output: \"X\"\n    type: \"ConstantFill\"\n    arg {\n      name: \"shape\"\n      ints: 10\n    }\n    arg {\n      name: \"value\"\n      f: 0.0\n    }\n  }\n  op {\n    input: \"comm\"\n    input: \"X\"\n    output: \"X\"\n    type: \"MPIBroadcast\"\n    arg {\n      name: \"root\"\n      i: 0\n    }\n  }\n)NET\";\n\nTEST(MPITest, TestMPIBroadcast) {\n  NetDef net_def;\n  CHECK(google::protobuf::TextFormat::ParseFromString(\n      string(kBcastNet), &net_def));\n  // Let's set the network's constant fill value to be the mpi rank.\n  auto* arg = net_def.mutable_op(1)->mutable_arg(1);\n  CAFFE_ENFORCE_EQ(arg->name(), \"value\");\n  int rank;\n  MPI_Comm_rank(MPI_COMM_WORLD, &rank);\n  arg->set_f(rank);\n  int size;\n  MPI_Comm_size(MPI_COMM_WORLD, &size);\n\n  for (int root = 0; root < size; ++root) {\n    net_def.mutable_op(2)->mutable_arg(0)->set_i(root);\n    Workspace ws;\n    unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n    EXPECT_NE(nullptr, net.get());\n    EXPECT_TRUE(net->Run());\n    // Let's test the value.\n    auto& X = ws.GetBlob(\"X\")->Get<TensorCPU>();\n    EXPECT_EQ(X.size(), 10);\n    for (int i = 0; i < X.size(); ++i) {\n      EXPECT_EQ(X.data<float>()[i], root);\n    }\n  }\n}\n\nconst char kReduceNet[] = R\"NET(\n  name: \"reduce\"\n  op {\n    output: \"comm\"\n    type: \"MPICreateCommonWorld\"\n  }\n  op {\n    output: \"X\"\n    type: \"ConstantFill\"\n    arg {\n      name: \"shape\"\n      ints: 10\n    }\n    arg {\n      name: \"value\"\n      f: 0.0\n    }\n  }\n  op {\n    input: \"comm\"\n    input: \"X\"\n    output: \"X_reduced\"\n    type: \"MPIReduce\"\n    arg {\n      name: \"root\"\n      i: 0\n    }\n  }\n)NET\";\n\nTEST(MPITest, TestMPIReduce) {\n  NetDef net_def;\n  CHECK(google::protobuf::TextFormat::ParseFromString(\n      string(kReduceNet), &net_def));\n  // Let's set the network's constant fill value to be the mpi rank.\n  auto* arg = net_def.mutable_op(1)->mutable_arg(1);\n  CAFFE_ENFORCE_EQ(arg->name(), \"value\");\n  int rank0;\n  MPI_Comm_rank(MPI_COMM_WORLD, &rank0);\n  arg->set_f(rank0);\n  int size0;\n  MPI_Comm_size(MPI_COMM_WORLD, &size0);\n\n  for (int root = 0; root < size0; ++root) {\n    net_def.mutable_op(2)->mutable_arg(0)->set_i(root);\n    Workspace ws;\n    unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n    EXPECT_NE(nullptr, net.get());\n    EXPECT_TRUE(net->Run());\n    int rank;\n    MPI_Comm_rank(MPI_COMM_WORLD, &rank);\n    int size;\n    MPI_Comm_size(MPI_COMM_WORLD, &size);\n    if (rank == root) {\n      // Let's test the value.\n      auto& X = ws.GetBlob(\"X_reduced\")->Get<TensorCPU>();\n      EXPECT_EQ(X.size(), 10);\n      int expected_result = size * (size - 1) / 2;\n      for (int i = 0; i < X.size(); ++i) {\n        EXPECT_EQ(X.data<float>()[i], expected_result);\n      }\n    }\n  }\n}\n\nconst char kMPIAllgatherNet[] = R\"NET(\n  name: \"allgather\"\n  op {\n    output: \"comm\"\n    type: \"MPICreateCommonWorld\"\n  }\n  op {\n    output: \"X\"\n    type: \"ConstantFill\"\n    arg {\n      name: \"shape\"\n      ints: 2\n      ints: 10\n    }\n    arg {\n      name: \"value\"\n      f: 0.0\n    }\n  }\n  op {\n    input: \"comm\"\n    input: \"X\"\n    output: \"X_gathered\"\n    type: \"MPIAllgather\"\n  }\n)NET\";\n\nTEST(MPITest, TestMPIAllgather) {\n  NetDef net_def;\n  CHECK(google::protobuf::TextFormat::ParseFromString(\n      string(kMPIAllgatherNet), &net_def));\n  // Let's set the network's constant fill value to be the mpi rank.\n  auto* arg = net_def.mutable_op(1)->mutable_arg(1);\n  CAFFE_ENFORCE_EQ(arg->name(), \"value\");\n  int rank;\n  MPI_Comm_rank(MPI_COMM_WORLD, &rank);\n  arg->set_f(rank);\n  int size;\n  MPI_Comm_size(MPI_COMM_WORLD, &size);\n\n  Workspace ws;\n  unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n  EXPECT_NE(nullptr, net.get());\n  EXPECT_TRUE(net->Run());\n  // Let's test the value.\n  auto& X = ws.GetBlob(\"X\")->Get<TensorCPU>();\n  EXPECT_EQ(X.size(), 20);\n  for (int i = 0; i < X.size(); ++i) {\n    EXPECT_EQ(X.data<float>()[i], rank);\n  }\n  auto& X_gathered = ws.GetBlob(\"X_gathered\")->Get<TensorCPU>();\n  EXPECT_EQ(X_gathered.size(), 20 * size);\n  EXPECT_EQ(X_gathered.dim(0), 2 * size);\n  EXPECT_EQ(X_gathered.dim(1), 10);\n  for (int i = 0; i < X_gathered.size(); ++i) {\n    EXPECT_EQ(X_gathered.data<float>()[i], i / 20);\n  }\n}\n\nconst char kMPIAllreduceNet[] = R\"NET(\n  name: \"allreduce\"\n  op {\n    output: \"comm\"\n    type: \"MPICreateCommonWorld\"\n  }\n  op {\n    output: \"X\"\n    type: \"ConstantFill\"\n    arg {\n      name: \"shape\"\n      ints: 10\n    }\n    arg {\n      name: \"value\"\n      f: 0.0\n    }\n  }\n  op {\n    input: \"comm\"\n    input: \"X\"\n    output: \"X_reduced\"\n    type: \"MPIAllreduce\"\n  }\n)NET\";\n\nTEST(MPITest, TestMPIAllreduce) {\n  NetDef net_def;\n  CHECK(google::protobuf::TextFormat::ParseFromString(\n      string(kMPIAllreduceNet), &net_def));\n  // Let's set the network's constant fill value to be the mpi rank.\n  auto* arg = net_def.mutable_op(1)->mutable_arg(1);\n  CAFFE_ENFORCE_EQ(arg->name(), \"value\");\n  int rank;\n  MPI_Comm_rank(MPI_COMM_WORLD, &rank);\n  arg->set_f(rank);\n  int size;\n  MPI_Comm_size(MPI_COMM_WORLD, &size);\n\n  Workspace ws;\n  unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n  EXPECT_NE(nullptr, net.get());\n  EXPECT_TRUE(net->Run());\n  // Let's test the value.\n  auto& X = ws.GetBlob(\"X\")->Get<TensorCPU>();\n  EXPECT_EQ(X.size(), 10);\n  for (int i = 0; i < X.size(); ++i) {\n    EXPECT_EQ(X.data<float>()[i], rank);\n  }\n  auto& X_reduced = ws.GetBlob(\"X_reduced\")->Get<TensorCPU>();\n  EXPECT_EQ(X_reduced.size(), 10);\n  int expected_result = size * (size - 1) / 2;\n  for (int i = 0; i < X_reduced.size(); ++i) {\n    EXPECT_EQ(X_reduced.data<float>()[i], expected_result);\n  }\n}\n\nconst char kInPlaceMPIAllreduceNet[] = R\"NET(\n  name: \"allreduce\"\n  op {\n    output: \"comm\"\n    type: \"MPICreateCommonWorld\"\n  }\n  op {\n    output: \"X\"\n    type: \"ConstantFill\"\n    arg {\n      name: \"shape\"\n      ints: 10\n    }\n    arg {\n      name: \"value\"\n      f: 0.0\n    }\n  }\n  op {\n    input: \"comm\"\n    input: \"X\"\n    output: \"X\"\n    type: \"MPIAllreduce\"\n  }\n)NET\";\n\nTEST(MPITest, TestInPlaceMPIAllreduce) {\n  NetDef net_def;\n  CHECK(google::protobuf::TextFormat::ParseFromString(\n      string(kInPlaceMPIAllreduceNet), &net_def));\n  // Let's set the network's constant fill value to be the mpi rank.\n  auto* arg = net_def.mutable_op(1)->mutable_arg(1);\n  CAFFE_ENFORCE_EQ(arg->name(), \"value\");\n  int rank;\n  MPI_Comm_rank(MPI_COMM_WORLD, &rank);\n  arg->set_f(rank);\n  int size;\n  MPI_Comm_size(MPI_COMM_WORLD, &size);\n\n  Workspace ws;\n  unique_ptr<NetBase> net(CreateNet(net_def, &ws));\n  EXPECT_NE(nullptr, net.get());\n  EXPECT_TRUE(net->Run());\n  auto& X_reduced = ws.GetBlob(\"X\")->Get<TensorCPU>();\n  EXPECT_EQ(X_reduced.size(), 10);\n  int expected_result = size * (size - 1) / 2;\n  for (int i = 0; i < X_reduced.size(); ++i) {\n    EXPECT_EQ(X_reduced.data<float>()[i], expected_result);\n  }\n}\n\n}  // namespace caffe2\n\n\nGTEST_API_ int main(int argc, char **argv) {\n  int mpi_ret;\n  MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &mpi_ret);\n  testing::InitGoogleTest(&argc, argv);\n  caffe2::GlobalInit(&argc, &argv);\n  int test_result = RUN_ALL_TESTS();\n  MPI_Finalize();\n  return test_result;\n}\n"
  },
  {
    "path": "caffe2/observers/CMakeLists.txt",
    "content": "if(USE_OBSERVERS)\n  message(STATUS \"Include Observer library\")\n  set(Caffe2_CONTRIB_OBSERVERS_CPU_SRC\n    \"${CMAKE_CURRENT_SOURCE_DIR}/time_observer.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/runcnt_observer.cc\"\n  )\n\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${Caffe2_CONTRIB_OBSERVERS_CPU_SRC})\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\n\n  # ---[ CPU test files\n  file(GLOB tmp *_test.cc)\n  set(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${tmp})\n  set(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nendif()\n"
  },
  {
    "path": "caffe2/observers/README.md",
    "content": "# Observers\n\n## Usage\n\nObservers are a small framework that allow users to attach code to the execution of SimpleNets and Operators.\n\nAn example of an Observer is the `TimeObserver`, used as follows:\n\n### C++\n\n```\nunique_ptr<TimeObserver<NetBase>> net_ob =\n    make_unique<TimeObserver<NetBase>>(net.get());\nauto* ob = net->AttachObserver(std::move(net_ob));\nnet->Run();\nLOG(INFO) << \"av time children: \" << ob->average_time_children();\nLOG(INFO) << \"av time: \" << ob->average_time();\n```\n  \n### Python\n\n```\nmodel.net.AttachTimeObserver()\nws.RunNet(model.net)\nob = model.net.GetObserver()\n\nprint(\"av time children:\", ob.average_time_children())\nprint(\"av time:\", ob.average_time())\n```\n\n\n## Implementing An Observer\n\nTo implement an observer you must inherit from `ObserverBase` and implement the `Start` and `Stop` functions.\n\nObservers are instantiated with a `subject` of a generic type, such as a `Net` or `Operator`.  The observer framework is built to be generic enough to \"observe\" various other types, however.\n"
  },
  {
    "path": "caffe2/observers/operator_attaching_net_observer.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/observer.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\n// Thin class that attaches the observer to all operators in the net\ntemplate <typename TOpObserver, typename TNetObserver>\nclass OperatorAttachingNetObserver : public ObserverBase<NetBase> {\n public:\n  explicit OperatorAttachingNetObserver(\n      NetBase* subject_,\n      TNetObserver* netObserver)\n      : ObserverBase<NetBase>(subject_) {\n    const auto& operators = subject_->GetOperators();\n    for (auto* op : operators) {\n      auto observer = caffe2::make_unique<TOpObserver>(op, netObserver);\n      const auto* ob = observer.get();\n      op->AttachObserver(std::move(observer));\n      operator_observers_.push_back(ob);\n    }\n  }\n  virtual ~OperatorAttachingNetObserver(){};\n\n protected:\n  std::vector<const TOpObserver*> operator_observers_;\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/observers/runcnt_observer.cc",
    "content": "#include \"runcnt_observer.h\"\n\nnamespace caffe2 {\n\nRunCountOperatorObserver::RunCountOperatorObserver(\n    OperatorBase* op,\n    RunCountNetObserver* netObserver)\n    : ObserverBase<OperatorBase>(op), netObserver_(netObserver) {\n  CAFFE_ENFORCE(netObserver_, \"Observers can't operate outside of the net\");\n}\n\nstd::unique_ptr<ObserverBase<OperatorBase>> RunCountOperatorObserver::copy(\n    OperatorBase* subject) {\n  return std::unique_ptr<ObserverBase<OperatorBase>>(\n      new RunCountOperatorObserver(subject, netObserver_));\n}\n\nstd::string RunCountNetObserver::debugInfo() {\n#if CAFFE2_ANDROID\n  // workaround\n  int foo = cnt_;\n  return \"This operator runs \" + caffe2::to_string(foo) + \" times.\";\n#else\n  return \"This operator runs \" + caffe2::to_string(cnt_) + \" times.\";\n#endif\n}\n\nvoid RunCountNetObserver::Start() {}\n\nvoid RunCountNetObserver::Stop() {}\n\nvoid RunCountOperatorObserver::Start() {\n  ++netObserver_->cnt_;\n}\nvoid RunCountOperatorObserver::Stop() {}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/observers/runcnt_observer.h",
    "content": "#pragma once\n\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/observer.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/observers/operator_attaching_net_observer.h\"\n\nnamespace caffe2 {\n\nclass RunCountNetObserver;\nclass RunCountOperatorObserver final : public ObserverBase<OperatorBase> {\n public:\n  explicit RunCountOperatorObserver(OperatorBase* op) = delete;\n  RunCountOperatorObserver(OperatorBase* op, RunCountNetObserver* netObserver);\n  ~RunCountOperatorObserver() {}\n\n  std::unique_ptr<ObserverBase<OperatorBase>> copy(\n      OperatorBase* subject) override;\n\n private:\n  void Start() override;\n  void Stop() override;\n\n private:\n  RunCountNetObserver* netObserver_;\n};\n\nclass RunCountNetObserver final : public OperatorAttachingNetObserver<\n                                      RunCountOperatorObserver,\n                                      RunCountNetObserver> {\n public:\n  explicit RunCountNetObserver(NetBase* subject_)\n      : OperatorAttachingNetObserver<\n            RunCountOperatorObserver,\n            RunCountNetObserver>(subject_, this),\n        cnt_(0) {}\n  ~RunCountNetObserver() {}\n\n  std::string debugInfo() override;\n\n  friend class RunCountOperatorObserver;\n\n private:\n  void Start() override;\n  void Stop() override;\n\n protected:\n  std::atomic<int> cnt_;\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/observers/time_observer.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"time_observer.h\"\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\n\nvoid TimeObserver::Start() {\n  start_time_ = timer_.MilliSeconds();\n  ++iterations_;\n}\n\nvoid TimeObserver::Stop() {\n  double current_run = timer_.MilliSeconds() - start_time_;\n  total_time_ += current_run;\n  VLOG(1) << \"This net iteration took \" << current_run << \" ms to complete.\\n\";\n}\n\nvoid TimeOperatorObserver::Start() {\n  start_time_ = timer_.MilliSeconds();\n  ++iterations_;\n}\n\nvoid TimeOperatorObserver::Stop() {\n  double current_run = timer_.MilliSeconds() - start_time_;\n  total_time_ += current_run;\n  VLOG(1) << \"This operator iteration took \" << current_run\n          << \" ms to complete.\\n\";\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/observers/time_observer.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_CONTRIB_OBSERVERS_TIME_OBSERVER_H_\n#define CAFFE2_CONTRIB_OBSERVERS_TIME_OBSERVER_H_\n\n#include <unordered_map>\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/observer.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/observers/operator_attaching_net_observer.h\"\n\nnamespace caffe2 {\n\nclass TimeObserver;\nclass TimeCounter {\n public:\n  explicit TimeCounter() {}\n  inline float average_time() const {\n    return total_time_ / iterations_;\n  }\n\n protected:\n  Timer timer_;\n  float start_time_ = 0.0f;\n  float total_time_ = 0.0f;\n  int iterations_ = 0;\n};\n\nclass TimeOperatorObserver final : public TimeCounter,\n                                   public ObserverBase<OperatorBase> {\n public:\n  explicit TimeOperatorObserver(OperatorBase* subject) = delete;\n  explicit TimeOperatorObserver(\n      OperatorBase* subject,\n      TimeObserver* /* unused */)\n      : ObserverBase<OperatorBase>(subject) {}\n\n  std::unique_ptr<ObserverBase<OperatorBase>> copy(\n      OperatorBase* subject) override {\n    return std::unique_ptr<ObserverBase<OperatorBase>>(\n        new TimeOperatorObserver(subject, nullptr));\n  }\n\n private:\n  void Start() override;\n  void Stop() override;\n};\n\nclass TimeObserver final\n    : public TimeCounter,\n      public OperatorAttachingNetObserver<TimeOperatorObserver, TimeObserver> {\n public:\n  explicit TimeObserver(NetBase* subject)\n      : OperatorAttachingNetObserver<TimeOperatorObserver, TimeObserver>(\n            subject,\n            this) {}\n\n  float average_time_children() const {\n    float sum = 0.0f;\n    for (const auto* observer : operator_observers_) {\n      sum += observer->average_time();\n    }\n    return sum / subject_->GetOperators().size();\n  }\n\n private:\n  void Start() override;\n  void Stop() override;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_CONTRIB_OBSERVERS_TIME_OBSERVER_H_\n"
  },
  {
    "path": "caffe2/observers/time_observer_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/observer.h\"\n#include \"caffe2/core/operator.h\"\n#include \"time_observer.h\"\n\n#include <google/protobuf/text_format.h>\n#include <gtest/gtest.h>\n#include <chrono>\n#include <thread>\n\nnamespace caffe2 {\n\nnamespace {\n\nclass SleepOp final : public OperatorBase {\n public:\n  using OperatorBase::OperatorBase;\n  bool Run(int /* unused */) override {\n    StartAllObservers();\n    std::this_thread::sleep_for(std::chrono::milliseconds(3000));\n    StopAllObservers();\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(SleepOp, SleepOp);\nREGISTER_CUDA_OPERATOR(SleepOp, SleepOp);\n\nOPERATOR_SCHEMA(SleepOp)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\n\nunique_ptr<NetBase> CreateNetTestHelper(Workspace* ws) {\n  NetDef net_def;\n  {\n    auto& op = *(net_def.add_op());\n    op.set_type(\"SleepOp\");\n    op.add_input(\"in\");\n    op.add_output(\"hidden\");\n  }\n  {\n    auto& op = *(net_def.add_op());\n    op.set_type(\"SleepOp\");\n    op.add_input(\"hidden\");\n    op.add_output(\"out\");\n  }\n  net_def.add_external_input(\"in\");\n  net_def.add_external_output(\"out\");\n\n  return CreateNet(net_def, ws);\n}\n} // namespace\n\nTEST(TimeObserverTest, Test3Seconds) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n  NetDef net_def;\n  unique_ptr<NetBase> net(CreateNetTestHelper(&ws));\n  auto net_ob = caffe2::make_unique<TimeObserver>(net.get());\n  const auto* ob = net_ob.get();\n  net->AttachObserver(std::move(net_ob));\n  net->Run();\n  CAFFE_ENFORCE(ob);\n  LOG(INFO) << \"av time children: \" << ob->average_time_children();\n  LOG(INFO) << \"av time: \" << ob->average_time();\n  CAFFE_ENFORCE(ob->average_time_children() > 3000);\n  CAFFE_ENFORCE(ob->average_time_children() < 3500);\n  CAFFE_ENFORCE(ob->average_time() > 6000);\n  CAFFE_ENFORCE(ob->average_time() < 6500);\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/onnx/CMakeLists.txt",
    "content": "# ---[ CPU files.\nfile(GLOB tmp *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp})\n\n# ---[ Send the lists to the parent scope.\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/onnx/backend.cc",
    "content": "#include \"backend.h\"\n#include \"device.h\"\n#include \"helper.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"onnx/optimizer/optimize.h\"\n#include \"onnx/defs/schema.h\"\n#include \"onnx/checker.h\"\n\n#include \"google/protobuf/io/coded_stream.h\"\n#include \"google/protobuf/io/zero_copy_stream_impl_lite.h\"\n\n#include <cassert>\n#include <cmath>\n#include <iostream>\n#include <sstream>\n#include <unordered_map>\n#include <unordered_set>\n\nnamespace caffe2 { namespace onnx {\n\nnamespace {\n\nconstexpr static int kKnownOpsetVersion = 5;\n\nbool AlmostEqual(double a, double b) {\n  constexpr static double kEps = 1e-15;\n  return (fabs(a -  b) < kEps);\n}\n\ntemplate <class T>\nbool TryConvertingTensorRawValues(const TensorProto &onnx_tensor,\n                                  ::google::protobuf::RepeatedField<T> *field) {\n  if (!onnx_tensor.has_raw_data()) {\n    return false;\n  }\n\n  size_t raw_size = onnx_tensor.raw_data().size();\n  assert(raw_size % sizeof(T) == 0);\n\n  size_t num_elements = raw_size / sizeof(T);\n  const void *src_ptr =\n      static_cast<const void *>(onnx_tensor.raw_data().data());\n  field->Resize(num_elements, 0);\n  void *target_ptr = static_cast<void *>(field->mutable_data());\n  memcpy(target_ptr, src_ptr, raw_size);\n\n  return true;\n}\n\nbool IsOperator(const std::string& op_type) {\n  // pull in all the operators upon first invocation\n  // Intentional leaky\n  static std::set<std::string>* ops_ =\n      new std::set<std::string>(caffe2::GetRegisteredOperators());\n  return ops_->count(caffe2::OpRegistryKey(op_type, \"DEFAULT\"));\n}\n\n//TODO We probably should have only one copy of this function (copied from pybind_state.cc)\nbool ParseProtobufFromLargeString(const std::string &str, ::google::protobuf::Message *proto) {\n  ::google::protobuf::io::ArrayInputStream input_stream(str.data(), str.size());\n  ::google::protobuf::io::CodedInputStream coded_stream(&input_stream);\n  // Set PlanDef message size limit to 1G.\n  coded_stream.SetTotalBytesLimit(1024LL << 20, 512LL << 20);\n  return proto->ParseFromCodedStream(&coded_stream);\n}\n\ncaffe2::DeviceOption GetDeviceOption(const Device &onnx_device) {\n  static const std::unordered_map<DeviceType, caffe2::DeviceType> m = {\n    {DeviceType::CPU, caffe2::DeviceType::CPU},\n    {DeviceType::CUDA, caffe2::DeviceType::CUDA}\n  };\n  caffe2::DeviceOption d;\n  d.set_device_type(static_cast<int32_t>(m.at(onnx_device.type)));\n  d.set_cuda_gpu_id(onnx_device.device_id);\n  return d;\n}\n\nModelProto OptimizeOnnx(const ModelProto &input, bool init) {\n  std::vector<std::string> passes{\"fuse_consecutive_transposes\",\n                                  \"eliminate_nop_transpose\",\n                                  \"fuse_transpose_into_gemm\"};\n\n  if (init) {\n    passes.emplace_back(\"split_init\");\n  } else {\n    passes.emplace_back(\"split_predict\");\n  }\n  return ONNX_NAMESPACE::optimization::Optimize(input, passes);\n}\n\ntemplate <class T, class U>\nU LookUpWithDefault(const std::unordered_map<T, U> &map, const T &key,\n                    const U &default_value) {\n  const auto it = map.find(key);\n  if (it == map.end()) {\n    return default_value;\n  } else {\n    return it->second;\n  }\n}\n\nconst ONNX_NAMESPACE::OpSchema &GetOnnxSchema(const std::string &key) {\n  const auto *schema = ONNX_NAMESPACE::OpSchemaRegistry::Schema(key);\n  if (!schema) {\n    throw std::runtime_error(\"No ONNX schema registered for '\" + key + \"'!\");\n  }\n  return *schema;\n}\n\nvoid UpdateNames(const caffe2::OperatorDef& op) {\n  for (const auto& n : op.input()) {\n    DummyName::AddName(n);\n  }\n  for (const auto& n : op.output()) {\n    DummyName::AddName(n);\n  }\n}\n\nvoid BuildOperator(\n    caffe2::OperatorDef* c2_op,\n    const std::string &op_type,\n    const std::vector<std::string> &inputs,\n    const std::vector<std::string> &outputs,\n    const std::vector<caffe2::Argument>& args) {\n  c2_op->set_name(\"\");\n  c2_op->set_type(op_type);\n  for (const auto& input: inputs) {\n    c2_op->add_input(input);\n  }\n  for (const auto& output: outputs) {\n    c2_op->add_output(output);\n  }\n  for (const auto& arg: args) {\n    auto* tmp = c2_op->add_arg();\n    tmp->CopyFrom(arg);\n  }\n}\n\nvoid BuildOperator(\n    caffe2::OperatorDef* c2_op,\n    const std::string &op_type,\n    const std::vector<std::string> &inputs,\n    const std::vector<std::string> &outputs) {\n  std::vector<caffe2::Argument> empty;\n  BuildOperator(c2_op, op_type, inputs, outputs, empty);\n}\n\nvoid CopyOnnxAttrValueToCaffe2Arg(\n    caffe2::Argument* arg,\n    const AttributeProto& attr) {\n  if (attr.has_f()) {\n    arg->set_f(attr.f());\n  } else if (attr.has_i()) {\n    arg->set_i(attr.i());\n  } else if (attr.has_s()) {\n    arg->set_s(attr.s());\n  } else if (attr.has_t()) {\n    // For proto, we convert it to serialized string\n    std::string buffer;\n    attr.t().SerializeToString(&buffer);\n    arg->set_s(buffer);\n  } else if (attr.floats_size()) {\n    arg->mutable_floats()->CopyFrom(attr.floats());\n  } else if (attr.ints_size()) {\n    arg->mutable_ints()->CopyFrom(attr.ints());\n  } else if (attr.strings_size()) {\n    arg->mutable_strings()->CopyFrom(attr.strings());\n  } else {\n    throw std::runtime_error(\n        caffe2::MakeString(\"Unsupported ONNX attribute: \", attr.name()));\n  }\n}\n} // namespace\n\nOnnxAttributes::OnnxAttributes(const NodeProto &node) {\n  for(const auto& attr: node.attribute()) {\n    onnx_attrs_.emplace(attr.name(), &attr);\n  }\n}\n\ntemplate <> int64_t OnnxAttributes::get(const std::string &key) const {\n  int64_t value = 0;\n  const auto it = onnx_attrs_.find(key);\n  if (it != onnx_attrs_.end()) {\n    const AttributeProto &attr = *it->second;\n    value = attr.i();\n  }\n  return value;\n}\n\ntemplate <> float OnnxAttributes::get(const std::string &key) const {\n  float value = 0.0;\n  const auto it = onnx_attrs_.find(key);\n  if (it != onnx_attrs_.end()) {\n    const AttributeProto &attr = *it->second;\n    value = attr.f();\n  }\n  return value;\n}\n\ntemplate <>\n::google::protobuf::RepeatedPtrField<std::string>\nOnnxAttributes::get(const std::string &key) const {\n ::google::protobuf::RepeatedPtrField<std::string> value;\n const auto it = onnx_attrs_.find(key);\n if (it != onnx_attrs_.end()) {\n   const AttributeProto& attr = *it->second;\n   value.CopyFrom(attr.strings());\n }\n return value;\n}\n\ntemplate <>\n::google::protobuf::RepeatedField<::google::protobuf::int64>\nOnnxAttributes::get(const std::string &key) const {\n  ::google::protobuf::RepeatedField<::google::protobuf::int64> value;\n  const auto it = onnx_attrs_.find(key);\n  if (it != onnx_attrs_.end()) {\n    const AttributeProto &attr = *it->second;\n    value.CopyFrom(attr.ints());\n  }\n  return value;\n}\n\ntemplate <>\nconst TensorProto *OnnxAttributes::get(const std::string &key) const {\n  const TensorProto *value = nullptr;\n  const auto it = onnx_attrs_.find(key);\n  if (it != onnx_attrs_.end()) {\n    const AttributeProto &attr = *it->second;\n    value = &attr.t();\n  }\n  return value;\n}\n\n::google::protobuf::RepeatedPtrField<caffe2::Argument>\nOnnxAttributes::OnnxAttrToCaffe2Arg(\n    std::function<std::string(const std::string &)> mapper) const {\n  ::google::protobuf::RepeatedPtrField<caffe2::Argument> args;\n  for (const auto& kv: onnx_attrs_) {\n    // If the attribute was rewritten, we use it instead. Note that the\n    // rewritten attibute still has the unmapped name\n    const auto &attr = rewritten_onnx_attrs_.count(kv.first)\n                           ? rewritten_onnx_attrs_.at(kv.first)\n                           : (*kv.second);\n    auto* arg = args.Add();\n    arg->set_name(mapper(attr.name()));\n    CopyOnnxAttrValueToCaffe2Arg(arg, attr);\n  }\n  for (const auto& kv:rewritten_onnx_attrs_) {\n    // If rewritten attribute doesn't appear in the original attributes, this is\n    // a newlly added one and we need to add this to argument too\n    if (!onnx_attrs_.count(kv.first)) {\n      const auto& attr = kv.second;\n      auto* arg = args.Add();\n      arg->set_name(mapper(attr.name()));\n      CopyOnnxAttrValueToCaffe2Arg(arg, attr);\n    }\n  }\n\n  return args;\n}\n\nconst std::unordered_map<std::string, int>&\nCaffe2Backend::get_broken_operators() const {\n  const static std::unordered_map<std::string, int> kBrokenOperators{};\n  return kBrokenOperators;\n}\n\n// Temporary hack for RNN related operators, as we don't have C++ interface in\n// C2 to build those operators yet\nconst std::unordered_set<std::string>& Caffe2Backend::get_rnn_operators() const {\n  const static std::unordered_set<std::string> kRNNOperators{\n      \"LSTM\", \"GRU\", \"RNN\"};\n  return kRNNOperators;\n}\n\n// Operators that are different between Caffe2 and\n// ONNX but only in their name.\n// In most cases, this should be empty - as the effort of ONNX is\n// to unify the operator definitions.\nconst std::unordered_map<std::string, std::string>&\nCaffe2Backend::get_renamed_operators() const {\n  const static std::unordered_map<std::string, std::string> kRenamedOperators{\n      {\"Caffe2ConvTranspose\", \"ConvTranspose\"},\n      {\"GlobalMaxPool\", \"MaxPool\"},\n      {\"GlobalAveragePool\", \"AveragePool\"},\n      {\"Pad\", \"PadImage\"},\n      {\"Neg\", \"Negative\"},\n      {\"BatchNormalization\", \"SpatialBN\"},\n      {\"InstanceNormalization\", \"InstanceNorm\"},\n      {\"MatMul\", \"BatchMatMul\"},\n      {\"Upsample\", \"ResizeNearest\"},\n      {\"Identity\", \"Copy\"},\n      {\"InstanceNormalization\", \"InstanceNorm\"},\n      {\"Equal\", \"EQ\"},\n      {\"Less\", \"LT\"},\n      {\"Greater\", \"GT\"},\n      {\"Unsqueeze\", \"ExpandDims\"}};\n  return kRenamedOperators;\n}\n\nconst std::unordered_map<std::string, std::string>&\nCaffe2Backend::get_renamed_attrs() const {\n  const static std::unordered_map<std::string, std::string> kRenamedAttrs{\n      {\"kernel_shape\", \"kernels\"}};\n  return kRenamedAttrs;\n}\n\nconst std::\n    unordered_map<std::string, std::unordered_map<std::string, std::string>>&\n    Caffe2Backend::get_per_op_renamed_attrs() const {\n  const static std::\n      unordered_map<std::string, std::unordered_map<std::string, std::string>>\n          kPerOpRenamedAttrs = {{\"Squeeze\", {{\"axes\", \"dims\"}}},\n                                {\"Unsqueeze\", {{\"axes\", \"dims\"}}},\n                                {\"Transpose\", {{\"perm\", \"axes\"}}},\n                                {\"Upsample\", {{\"mode\", \"\"}}},\n                                {\"ConvTranspose\", {{\"output_padding\", \"adjs\"}}},\n                                {\"Selu\", {{\"gamma\", \"scale\"}}}};\n\n  return kPerOpRenamedAttrs;\n}\n\n// operators whose behavior is different beyond renaming\n// the value is an attribute of this class that is a\n// function from ToffeIR node_def to caffe2 op_def\nconst std::unordered_map<std::string, Caffe2Backend::SpecialOpConverter>&\nCaffe2Backend::get_special_operators() const {\n  const static std::\n      unordered_map<std::string, Caffe2Backend::SpecialOpConverter>\n          kSpecialOperators = {\n              {\"Constant\", &Caffe2Backend::CreateConstant},\n              {\"Conv\", &Caffe2Backend::CreateConvePoolOpBase},\n              {\"AveragePool\", &Caffe2Backend::CreateConvePoolOpBase},\n              {\"GlobalAveragePool\", &Caffe2Backend::CreateConvePoolOpBase},\n              {\"GlobalMaxPool\", &Caffe2Backend::CreateConvePoolOpBase},\n              {\"MaxPool\", &Caffe2Backend::CreateConvePoolOpBase},\n              {\"Reshape\", &Caffe2Backend::CreateReshape},\n              {\"Gather\", &Caffe2Backend::CreateGather},\n              {\"Gemm\", &Caffe2Backend::CreateGemm},\n              {\"Pad\", &Caffe2Backend::CreatePad},\n              {\"Concat\", &Caffe2Backend::CreateConcat},\n              {\"LogSoftmax\", &Caffe2Backend::CreateLogSoftmax},\n              {\"Slice\", &Caffe2Backend::CreateSlice},\n              {\"Sqrt\", &Caffe2Backend::CreateSqrt},\n              {\"Reciprocal\", &Caffe2Backend::CreateReciprocal},\n              {\"MatMul\", &Caffe2Backend::CreateMatMul}};\n  return kSpecialOperators;\n}\n\n//============================\n// Special Operator Converters\n//============================\n\nCaffe2Ops Caffe2Backend::CreateConstant(const ModelProto &init_model,\n                                        const ModelProto &pred_model,\n                                        OnnxNode *onnx_node,\n                                        int opset_version) {\n  assert(onnx_node->node.output_size() == 1);\n\n  Caffe2Ops ret;\n  auto *c2_op = ret.ops.Add();\n  const auto *value =\n      onnx_node->attributes.get<const TensorProto *>(\"value\");\n  BuildTensorFillingOp(c2_op, *value, onnx_node->node.output(0));\n\n  return ret;\n}\n\n//  Note [Caffe2 ConvPoolOpBase]\n//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n//  To understand what is going on here, we have to talk a little bit about\n//  Caffe2's internals.\n//\n//  First, it's important to know that all of Caffe2's pooling and convolution\n//  operators inherit from \"ConvPoolOpBase\", which is an abstract class that\n//  defines all of the attributes (kernels, dilations, strides, etc) which one\n//  sees on these operators.  Unfortunately, Caffe2's documentation generator\n//  doesn't know how to handle cases like this, so for example, if you look at\n//  the docs for MaxPool at\n//  <https://caffe2.ai/docs/operators-catalogue.html#maxpool> you won't see any\n//  of the attributes.  You have to go source diving to find the information; in\n//  particular, you want to look at:\n//  https://github.com/caffe2/caffe2/blob/master/caffe2/operators/conv_pool_op_base.h\n//  This class handles *global* pooling as well.\n//\n//  Second, it's important to know what Caffe2 expects for padding, which can\n//  be somewhat difficult to understand from the code because Caffe2 handles\n//  both singular/pluralized spellings of padding, and there is also legacy\n//  padding business.  The short version of the story is that, for NON-legacy\n//  padding (which is what we want to output), padding is expected to be\n//  *twice* the size of kernels.  So if you have a 2D convolution, Caffe2\n//  will accept two values in 'kernels', but FOUR values in 'pads';\n//  furthermore, this is *mandatory.*\n//\n//  Finally, ConvPoolOpBase is not the only class of it's kind; there is\n//  also ConvTransposeUnpoolBase, which backs ConvTranspose.  So don't\n//  be tricked by the fact that Conv and ConvTranspose have similar\n//  parameters; they exercise different codepaths and need to be handled\n//  differently.\nCaffe2Ops Caffe2Backend::CreateConvePoolOpBase(const ModelProto &init_model,\n                                               const ModelProto &pred_model,\n                                               OnnxNode *onnx_node,\n                                               int opset_version) {\n  const auto& node = onnx_node->node;\n  auto& attributes = onnx_node->attributes;\n  if (node.op_type().find(\"Global\") == 0) {\n    auto* attr = attributes.AddRewrittenAttibute(\"global_pooling\");\n    attr->set_i(1);\n  }\n\n  if (attributes.HasAttribute(\"kernel_shape\") && attributes.HasAttribute(\"pads\")) {\n    auto kernel_shape =\n        attributes\n            .get<::google::protobuf::RepeatedField<::google::protobuf::int64>>(\n                \"kernel_shape\");\n    auto pads =\n        attributes\n            .get<::google::protobuf::RepeatedField<::google::protobuf::int64>>(\n                \"pads\");\n    if (kernel_shape.size() == pads.size()) {\n      // Caffe2 requires pads to be twice the size of kernels.\n      auto* attr = attributes.AddRewrittenAttibute(\"pads\");\n      attr->mutable_ints()->CopyFrom(pads);\n      attr->mutable_ints()->MergeFrom(pads);\n    }\n  }\n\n  return CommonOnnxNodeToCaffe2Ops(init_model, pred_model, onnx_node,\n                                  opset_version);\n}\n\nCaffe2Ops Caffe2Backend::CreateReshape(const ModelProto &init_model,\n                                       const ModelProto &pred_model,\n                                       OnnxNode *onnx_node, int opset_version) {\n  auto c2_op = CommonOnnxNodeToCaffe2Ops(init_model, pred_model, onnx_node,\n                                        opset_version);\n  assert(c2_op.ops.size() == 1);\n  auto* op = c2_op.ops.Mutable(0);\n  op->add_output(DummyName::NewDummyName());\n\n  return c2_op;\n}\n\nCaffe2Ops Caffe2Backend::CreateSqrt(\n    const ModelProto& init_model,\n    const ModelProto& pred_model,\n    OnnxNode* onnx_node,\n    int opset_version) {\n  const auto& node = onnx_node->node;\n  if (node.input_size() != 1 || node.output_size() != 1) {\n    throw std::runtime_error(\"Caffe2 Sqrt should have 1 input and 1 output\");\n  }\n\n  Caffe2Ops ret;\n  auto *c2_op = ret.ops.Add();\n\n  caffe2::Argument exponent;\n  exponent.set_name(\"exponent\");\n  exponent.set_f(0.5);\n  BuildOperator(c2_op, \"Pow\", {node.input(0)}, {node.output(0)}, {exponent});\n  return ret;\n}\n\nCaffe2Ops Caffe2Backend::CreateReciprocal(\n    const ModelProto& init_model,\n    const ModelProto& pred_model,\n    OnnxNode* onnx_node,\n    int opset_version) {\n  const auto& node = onnx_node->node;\n  if (node.input_size() != 1 || node.output_size() != 1) {\n    throw std::runtime_error(\"Caffe2 Reciprocal should have 1 input and 1 output\");\n  }\n\n  Caffe2Ops ret;\n  auto *c2_op = ret.ops.Add();\n\n  caffe2::Argument exponent;\n  exponent.set_name(\"exponent\");\n  exponent.set_f(-1.0);\n  BuildOperator(c2_op, \"Pow\", {node.input(0)}, {node.output(0)}, {exponent});\n  return ret;\n}\n\nCaffe2Ops Caffe2Backend::CreateGather(\n    const ModelProto& init_model,\n    const ModelProto& pred_model,\n    OnnxNode* onnx_node,\n    int opset_version) {\n  const auto& node = onnx_node->node;\n  if (node.input_size() < 2 || node.output_size() < 1) {\n    throw std::runtime_error(\"Caffe2 Gather should have 2 inputs and 1 output\");\n  }\n\n  Caffe2Ops ret;\n  auto *c2_op = ret.ops.Add();\n\n  std::vector<std::string> inputs;\n  inputs.emplace_back(node.input(0));\n  inputs.emplace_back(node.input(1));\n  std::vector<std::string> outputs;\n  outputs.emplace_back(node.output(0));\n\n  auto axis = onnx_node->attributes.get<int64_t>(\"axis\", 0L);\n  if (axis == 0) {\n    BuildOperator(c2_op, \"Gather\", inputs, outputs);\n  } else if (axis == 1) {\n    BuildOperator(c2_op, \"BatchGather\", inputs, outputs);\n  } else {\n    throw std::runtime_error(caffe2::MakeString(\n        \"Caffe2 only supports Gather with axis being 1 or 2, \",\n        \"whereas axis is \", axis));\n  }\n\n  return ret;\n}\n\nCaffe2Ops Caffe2Backend::CreateGemm(const ModelProto &init_model,\n                                    const ModelProto &pred_model,\n                                    OnnxNode *onnx_node, int opset_version) {\n  const auto& node = onnx_node->node;\n  if (node.input_size() < 3 || node.output_size() < 1) {\n    throw std::runtime_error(\"Caffe2 Gemm should have 3 inputs and 1 output\");\n  }\n\n  Caffe2Ops ret;\n  auto input_a = node.input(0);\n  auto input_b = node.input(1);\n  auto input_c = node.input(2);\n  auto output = node.output(0);\n\n  auto alpha = onnx_node->attributes.get<float>(\"alpha\", 1.0);\n  auto beta = onnx_node->attributes.get<float>(\"beta\", 1.0);\n  if (!AlmostEqual(alpha, 1)) {\n     auto scaled_a = DummyName::NewDummyName();\n     caffe2::Argument scale;\n     scale.set_name(\"scale\");\n     scale.set_f(alpha);\n\n     auto *c2_op = ret.ops.Add();\n     BuildOperator(c2_op, \"Scale\", {input_a}, {scaled_a}, {scale});\n     input_a = scaled_a;\n  }\n  if (!AlmostEqual(beta, 1)) {\n     auto scaled_c = DummyName::NewDummyName();\n     caffe2::Argument scale;\n     scale.set_name(\"scale\");\n     scale.set_f(beta);\n\n     auto *c2_op = ret.ops.Add();\n     BuildOperator(c2_op, \"Scale\", {input_c}, {scaled_c}, {scale});\n     input_c = scaled_c;\n  }\n\n  auto trans_a = onnx_node->attributes.get<int64_t>(\"transA\", 0L);\n  auto trans_b = onnx_node->attributes.get<int64_t>(\"transB\", 0L);\n  auto broadcast = onnx_node->attributes.get<int64_t>(\"broadcast\", 0L);\n  if ( (!trans_a) && trans_b && broadcast) {\n    auto *c2_op = ret.ops.Add();\n    BuildOperator(c2_op, \"FC\", {input_a, input_b, input_c}, {output});\n  } else {\n    auto ab = DummyName::NewDummyName();\n    caffe2::Argument arg_trans_a;\n    arg_trans_a.set_name(\"trans_a\");\n    arg_trans_a.set_i(trans_a);\n    caffe2::Argument arg_trans_b;\n    arg_trans_b.set_name(\"trans_b\");\n    arg_trans_b.set_i(trans_b);\n    caffe2::Argument arg_broadcast;\n    arg_broadcast.set_name(\"broadcast\");\n    arg_broadcast.set_i(broadcast);\n\n    auto *c2_op = ret.ops.Add();\n    BuildOperator(c2_op, \"MatMul\", {input_a, input_b}, {ab},\n                  {arg_trans_a, arg_trans_b});\n    c2_op = ret.ops.Add();\n    BuildOperator(c2_op, \"Add\", {ab, input_c}, {output},\n                  {arg_broadcast});\n  }\n\n  return ret;\n}\n\nCaffe2Ops Caffe2Backend::CreatePad(const ModelProto &init_model,\n                                   const ModelProto &pred_model,\n                                   OnnxNode *onnx_node, int opset_version) {\n  const auto& node = onnx_node->node;\n  auto& attributes = onnx_node->attributes;\n  ::google::protobuf::RepeatedField<::google::protobuf::int64> pads;\n  std::string pad_name = opset_version < 2 ? \"paddings\" : \"pads\";\n  pads = attributes\n             .get<::google::protobuf::RepeatedField<::google::protobuf::int64>>(\n                 pad_name);\n  std::string str;\n  std::stringstream ss;\n  ss << \"[\";\n  for (const auto& i: pads) {\n    ss << i << \", \";\n  }\n  ss << \"]\";\n  str = ss.str();\n\n  // Guard the invalid (negative) pads attribute.\n  for (const auto i : pads) {\n    if (i < 0) {\n      throw std::runtime_error(caffe2::MakeString(\n          \"ONNX does not support negative pads in Pad, but get \", str));\n    }\n  }\n\n  // first two dim is for batch and channel. Note that now all the values are\n  // non-negative\n  if (!(pads.size() == 8 &&\n        (pads.Get(0) + pads.Get(1) + pads.Get(4) + pads.Get(5) == 0))) {\n    throw std::runtime_error(caffe2::MakeString(\n        \"Caffe2 only supports padding 2D Tensor, whereas padding is \", str));\n  }\n\n  // rewrite the padding info\n  auto* attr = attributes.AddRewrittenAttibute(pad_name);\n  attr->add_ints(pads.Get(2));\n  attr->add_ints(pads.Get(3));\n  attr->add_ints(pads.Get(6));\n  attr->add_ints(pads.Get(7));\n\n  return CommonOnnxNodeToCaffe2Ops(init_model, pred_model, onnx_node,\n                                  opset_version);\n}\n\n// TODO: Caffe2 Concat has an extra output. It should be only\n// used when doing training, so we should change Caffe2 to allow\n// 1 output.\nCaffe2Ops Caffe2Backend::CreateConcat(const ModelProto &init_model,\n                                      const ModelProto &pred_model,\n                                      OnnxNode *onnx_node, int opset_version) {\n  auto c2_op = CommonOnnxNodeToCaffe2Ops(init_model, pred_model, onnx_node,\n                                        opset_version);\n  assert(c2_op.ops.size() == 1);\n  auto* op = c2_op.ops.Mutable(0);\n  op->add_output(DummyName::NewDummyName());\n\n  return c2_op;\n}\n\nCaffe2Ops Caffe2Backend::CreateLogSoftmax(const ModelProto &init_model,\n                                          const ModelProto &pred_model,\n                                          OnnxNode *onnx_node,\n                                          int opset_version) {\n  const auto& node = onnx_node->node;\n  if (node.input_size() < 1 || node.output_size() < 1) {\n    throw std::runtime_error(\"LogSoftmax should have 1 input and 1 output\");\n  }\n  auto axis = onnx_node->attributes.get<int64_t>(\"axis\", 1L);\n  caffe2::Argument arg_axis;\n  arg_axis.set_name(\"axis\");\n  arg_axis.set_i(axis);\n  auto softmax_a = DummyName::NewDummyName();\n\n  Caffe2Ops ret;\n  auto *c2_op = ret.ops.Add();\n  BuildOperator(c2_op, \"Softmax\", {node.input(0)}, {softmax_a}, {arg_axis});\n  c2_op = ret.ops.Add();\n  BuildOperator(c2_op, \"Log\", {softmax_a}, {node.output(0)});\n\n  return ret;\n}\n\n\nCaffe2Ops Caffe2Backend::CreateSlice(const ModelProto &init_model,\n                                          const ModelProto &pred_model,\n                                          OnnxNode *onnx_node,int opset_version) {\n  auto op_tmp = CommonOnnxNodeToCaffe2Ops(init_model, pred_model, onnx_node,\n                                        opset_version);\n  assert(op_tmp.ops.size() == 1);\n  auto* op = op_tmp.ops.Mutable(0);\n  std::unordered_map<std::string, caffe2::Argument*> args;\n  for (auto& arg: *op->mutable_arg()) {\n    args.emplace(arg.name(), &arg);\n  }\n\n  caffe2::Argument starts_vals;\n  starts_vals.set_name(\"values\");\n  auto pos = args.find(\"starts\");\n  if (pos != args.end()) {\n    for (auto i: pos->second->ints()) {\n      starts_vals.add_ints(i);\n    }\n    args.erase(pos);\n  }\n\n  caffe2::Argument ends_vals;\n  ends_vals.set_name(\"values\");\n  pos = args.find(\"ends\");\n  if (pos != args.end()) {\n    for (auto i: pos->second->ints()) {\n      ends_vals.add_ints(i < 0 ? i - 1 : i);\n    }\n    args.erase(pos);\n  }\n\n  caffe2::Argument axes_vals;\n  axes_vals.set_name(\"values\");\n  pos = args.find(\"axes\");\n  if (pos != args.end()) {\n    for (auto i: pos->second->ints()) {\n      axes_vals.add_ints(i);\n    }\n    args.erase(pos);\n  } else {\n    auto ndim = starts_vals.ints_size();\n    for (int64_t i = 0; i < ndim; ++i) {\n      axes_vals.add_ints(i);\n    }\n  }\n\n  assert(op->input_size() >= 1);\n  auto data = op->input(0);\n  auto shape_tensor = DummyName::NewDummyName();\n  Caffe2Ops ret;\n\n  auto *c2_op = ret.ops.Add();\n  BuildOperator(c2_op, \"Shape\", {data}, {shape_tensor});\n\n  auto axes_tensor = DummyName::NewDummyName();\n  c2_op = ret.ops.Add();\n  {\n    caffe2::Argument shape;\n    shape.set_name(\"shape\");\n    shape.add_ints(axes_vals.ints_size());\n    BuildOperator(c2_op, \"GivenTensorIntFill\", {}, {axes_tensor},\n                  {shape, axes_vals});\n  }\n\n  auto starts_vals_tensor = DummyName::NewDummyName();\n  auto starts_tensor = DummyName::NewDummyName();\n  auto casted_starts_tensor = DummyName::NewDummyName();\n  c2_op = ret.ops.Add();\n  {\n    caffe2::Argument shape_starts;\n    shape_starts.set_name(\"shape\");\n    shape_starts.add_ints(starts_vals.ints_size());\n    BuildOperator(c2_op, \"GivenTensorInt64Fill\", {}, {starts_vals_tensor},\n                  {shape_starts, starts_vals});\n  }\n\n  caffe2::Argument dtype;\n  dtype.set_name(\"dtype\");\n  dtype.set_i(static_cast<int64_t>(caffe2::TensorProto::INT64));\n  caffe2::Argument constant;\n  constant.set_name(\"value\");\n  constant.set_i(0);\n  c2_op = ret.ops.Add();\n  BuildOperator(c2_op, \"ConstantFill\", {shape_tensor}, {starts_tensor},\n                {dtype, constant});\n  c2_op = ret.ops.Add();\n  BuildOperator(c2_op, \"ScatterAssign\",\n                {starts_tensor, axes_tensor, starts_vals_tensor},\n                {starts_tensor});\n  // Slice only accepts starts as int\n  caffe2::Argument to;\n  to.set_name(\"to\");\n  to.set_i(static_cast<int64_t>(caffe2::TensorProto::INT32));\n  c2_op = ret.ops.Add();\n  BuildOperator(c2_op, \"Cast\", {starts_tensor}, {casted_starts_tensor}, {to});\n\n  auto ends_vals_tensor = DummyName::NewDummyName();\n  auto ends_tensor = DummyName::NewDummyName();\n  auto casted_ends_tensor = DummyName::NewDummyName();\n  c2_op = ret.ops.Add();\n  {\n    caffe2::Argument shape_ends;\n    shape_ends.set_name(\"shape\");\n    shape_ends.add_ints(ends_vals.ints_size());\n    BuildOperator(c2_op, \"GivenTensorInt64Fill\", {}, {ends_vals_tensor},\n                  {shape_ends, ends_vals});\n  }\n\n  constant.set_i(-1);\n  c2_op = ret.ops.Add();\n  BuildOperator(c2_op, \"ConstantFill\", {shape_tensor}, {ends_tensor},\n                {dtype, constant});\n  c2_op = ret.ops.Add();\n  BuildOperator(c2_op, \"ScatterAssign\",\n                {ends_tensor, axes_tensor, ends_vals_tensor}, {ends_tensor});\n  // Slice only accepts ends as int\n  c2_op = ret.ops.Add();\n  BuildOperator(c2_op, \"Cast\", {ends_tensor}, {casted_ends_tensor}, {to});\n\n  // attach the original op at the end\n  c2_op = ret.ops.Add();\n  c2_op->CopyFrom(*op);\n  c2_op->mutable_input()->Clear();\n  c2_op->add_input(data);\n  c2_op->add_input(casted_starts_tensor);\n  c2_op->add_input(casted_ends_tensor);\n  c2_op->mutable_arg()->Clear();\n  for (const auto& kv: args) {\n    c2_op->add_arg()->CopyFrom(*kv.second);\n  }\n\n  return ret;\n}\n\nCaffe2Ops Caffe2Backend::CreateMatMul(\n    const ModelProto& init_model,\n    const ModelProto& pred_model,\n    OnnxNode* onnx_node,\n    int opset_version) {\n  const auto& node = onnx_node->node;\n  if (node.input_size() != 2) {\n    throw std::runtime_error(\"MatMul should have 2 inputs\");\n  }\n\n  auto c2_op = CommonOnnxNodeToCaffe2Ops(init_model, pred_model, onnx_node,\n                                         opset_version);\n  assert(c2_op.ops.size() == 1);\n  auto* op = c2_op.ops.Mutable(0);\n  auto* broadcast_arg = op->add_arg();\n  broadcast_arg->set_name(\"broadcast\");\n  broadcast_arg->set_i(1);\n\n  return c2_op;\n}\n\n//==============================================\n// Rest of the member funtions for Caffe2Backend\n//==============================================\nvoid Caffe2Backend::InplaceRewrite(GraphProto* graph) {\n  auto* nodes = graph->mutable_node();\n  auto renamed = InplaceRewrite(nodes);\n  for (int i = 0; i < graph->output_size(); ++i) {\n    graph->mutable_output(i)->set_name(LookUpWithDefault(\n        renamed, graph->output(i).name(), graph->output(i).name()));\n  }\n}\n\n//  \\brief currently we use this to translate ONNX-style\n//  consumed_input annotations to Caffe2-style in place\n//  updates (use same input and output names).\nstd::unordered_map<std::string, std::string> Caffe2Backend::InplaceRewrite(\n    ::google::protobuf::RepeatedPtrField<NodeProto> *nodes) {\n  std::unordered_map<std::string, std::string> renamed;\n  for (auto& node: *nodes) {\n    for (auto& input: *node.mutable_input()) {\n      input = LookUpWithDefault(renamed, input, input);\n    }\n\n    // Get `consumed_inputs` attribute and remove it from node attributes\n    ::google::protobuf::RepeatedField<::google::protobuf::int64>\n        consumed_inputs;\n    int found_idx = -1;\n    for (int i = 0; i < node.attribute_size(); ++i) {\n      const auto& attr = node.attribute(i);\n      if (attr.name() == \"consumed_inputs\") {\n        consumed_inputs.CopyFrom(attr.ints());\n        found_idx = i;\n        break;\n      }\n    }\n    if (found_idx >= 0 && found_idx < node.attribute_size()) {\n      if (found_idx != node.attribute_size() - 1) {\n        node.mutable_attribute(found_idx)->CopyFrom(\n            node.attribute(node.attribute_size() - 1));\n      }\n      node.mutable_attribute()->RemoveLast();\n    }\n\n    std::set<int> output_indexes;\n    for (int i = 0; i < node.output_size(); ++i) {\n      output_indexes.insert(i);\n    }\n    const auto& schema = GetOnnxSchema(node.op_type());\n\n    int i = 0;\n    for (const auto& consumed: consumed_inputs) {\n      if (!consumed) {\n        ++i;\n        continue;\n      }\n\n      auto output_idx = schema.consumed(i).second;\n      // consumed outputs are not always present\n      // for instance batch norm in test mode\n      // does not return the consumed inputs\n      if (output_idx < node.output_size()) {\n        output_indexes.erase(output_idx);\n        auto old_val = node.output(output_idx);\n        auto new_val = node.input(i);\n        node.set_output(output_idx, new_val);\n        renamed[old_val] = new_val;\n      }\n      ++i;\n    }\n\n    for (auto idx: output_indexes) {\n      auto name = node.output(idx);\n      node.set_output(idx, LookUpWithDefault(renamed, name, name));\n    }\n  }\n\n  return renamed;\n}\n\nstd::unordered_set<std::string>\nCaffe2Backend::AllNamesInGraph(const GraphProto &graph) {\n  std::unordered_set<std::string> names;\n\n  for(const auto& input: graph.input()) {\n    names.emplace(input.name());\n  }\n  for(const auto& output: graph.output()) {\n    names.emplace(output.name());\n  }\n  for (const auto& node: graph.node()) {\n    for (const auto &n : node.input()) {\n      names.emplace(n);\n    }\n    for (const auto &n : node.output()) {\n      names.emplace(n);\n    }\n  }\n\n  return names;\n}\n\n//  This translator performs the basic translation of ONNX nodes into\n//  Caffe2 operators.  Besides doing a straightforward marshalling from\n//  one format to another, it also does these extra things:\n//\n//    - Renames operators based on 'renamed_operators'\n//    - Renames attributes based on 'renamed_attrs' and\n//      'get_per_op_renamed_attrs'\n//\n//  If you're writing a custom translator, consider calling this first,\n//  and then fixing things up further.\nCaffe2Ops Caffe2Backend::CommonOnnxNodeToCaffe2Ops(\n    const ModelProto &init_model, const ModelProto &pred_model,\n    OnnxNode *onnx_node, int opset_version) {\n  Caffe2Ops ret;\n  auto* c2_op = ret.ops.Add();\n\n  const auto& node = onnx_node->node;\n  c2_op->mutable_input()->MergeFrom(node.input());\n  c2_op->mutable_output()->MergeFrom(node.output());\n  c2_op->set_name(node.name());\n\n  const auto onnx_op_type = node.op_type();\n  auto broken_version = LookUpWithDefault(\n      get_broken_operators(), onnx_op_type, std::numeric_limits<int>::max());\n  if (broken_version <= opset_version) {\n    throw std::runtime_error(\n        caffe2::MakeString(\"Don't know how to translate op \", onnx_op_type,\n                           \" in ONNX operator set v\", opset_version,\n                           \" (I only support prior to v\", broken_version));\n  }\n  c2_op->set_type(\n      LookUpWithDefault(get_renamed_operators(), onnx_op_type, onnx_op_type));\n  if (!IsOperator(c2_op->type())) {\n    throw std::runtime_error(\n        caffe2::MakeString(\"Don't know how to translate op \", onnx_op_type));\n  }\n\n  auto mapper = [&, this](const std::string& k) {\n    const auto it = get_per_op_renamed_attrs().find(onnx_op_type);\n    if (it != get_per_op_renamed_attrs().end()) {\n      const auto it_op = it->second.find(k);\n      if (it_op != it->second.end()) {\n        return it_op->second;\n      }\n    }\n    const auto it_global = get_renamed_attrs().find(k);\n    if (it_global != get_renamed_attrs().end()) {\n      return it_global->second;\n    }\n    return k;\n  };\n  c2_op->mutable_arg()->MergeFrom(\n      onnx_node->attributes.OnnxAttrToCaffe2Arg(mapper));\n\n  return ret;\n}\n\nCaffe2Ops Caffe2Backend::ConvertNode(const std::string& node_str, int opset_version) {\n  ::google::protobuf::RepeatedPtrField<NodeProto> nodes;\n  auto* n = nodes.Add();\n  ParseProtobufFromLargeString(node_str, n);\n  InplaceRewrite(&nodes);\n  ModelProto init_model;\n  ModelProto pred_model;\n  OnnxNode onnx_node = OnnxNode(nodes.Get(0));\n  return OnnxNodeToCaffe2Ops(init_model, pred_model, &onnx_node, opset_version);\n}\n\nCaffe2Ops Caffe2Backend::OnnxNodeToCaffe2Ops(const ModelProto &init_model,\n                                            const ModelProto &pred_model,\n                                            OnnxNode* onnx_node,\n                                            int opset_version) {\n  if (get_special_operators().count(onnx_node->node.op_type())) {\n    return (this->*get_special_operators().at(onnx_node->node.op_type()))(\n        init_model, pred_model, onnx_node, opset_version);\n  } else {\n    return CommonOnnxNodeToCaffe2Ops(init_model, pred_model, onnx_node,\n                                     opset_version);\n  }\n}\n\nvoid Caffe2Backend::OnnxToCaffe2(\n    caffe2::NetDef *init_net, caffe2::NetDef *pred_net,\n    const ModelProto &onnx_model, const std::string &device,\n    int opset_version, bool include_initializers,\n    const std::vector<Caffe2Ops> &extras) {\n  auto device_option = GetDeviceOption(Device(device));\n\n  ModelProto init_model = OptimizeOnnx(onnx_model, true);\n  InplaceRewrite(init_model.mutable_graph());\n\n  ModelProto pred_model = OptimizeOnnx(onnx_model, false);\n  InplaceRewrite(pred_model.mutable_graph());\n\n  init_net->set_name(onnx_model.graph().name() + \"_init\");\n  pred_net->set_name(onnx_model.graph().name() + \"_predict\");\n\n  // Convert initializer if necessary\n  if (include_initializers) {\n    for (const auto& tp: onnx_model.graph().initializer()) {\n      auto* c2_op = init_net->add_op();\n      BuildTensorFillingOp(c2_op, tp);\n    }\n  }\n\n  auto name_set =AllNamesInGraph(init_model.graph());\n  auto name_set_pred = AllNamesInGraph(pred_model.graph());\n  name_set.insert(name_set_pred.begin(), name_set_pred.end());\n  DummyName::Reset(name_set);\n\n  size_t idx_extra = 0;\n  auto converter = [&](const ModelProto &model, caffe2::NetDef *net) mutable {\n    net->mutable_device_option()->CopyFrom(device_option);\n    for (const auto &node : model.graph().node()) {\n      auto *init_net_tmp = include_initializers ? init_net : net;\n      // For RNN operators, we rely on Python code to convert them for us, and\n      // we simply deserilize the string. This is hack and eventually we want to\n      // get rid of this to have one flow. Note that we need to update the dummy\n      // name generator to avoid having duplicated names between Python and C++\n      // generated dummies\n      if (get_rnn_operators().count(node.op_type())) {\n        if (idx_extra < extras.size()) {\n          const auto &c2ops = extras[idx_extra++];\n          for (const auto& op: c2ops.init_ops) {\n            UpdateNames(op);\n          }\n          init_net_tmp->mutable_op()->MergeFrom(c2ops.init_ops);\n          for (const auto& op: c2ops.ops) {\n            UpdateNames(op);\n          }\n          net->mutable_op()->MergeFrom(c2ops.ops);\n          for (const auto& input: c2ops.interface_blobs) {\n            DummyName::AddName(input);\n          }\n          net->mutable_external_input()->MergeFrom(c2ops.interface_blobs);\n        } else {\n          throw std::runtime_error(\n              caffe2::MakeString(\"Don't know how to convert \", node.op_type(),\n                                 \" without enough extra preconverted string\"));\n        }\n      } else {\n        auto onnx_node = OnnxNode(node);\n        auto c2ops = OnnxNodeToCaffe2Ops(init_model, pred_model, &onnx_node,\n                                         opset_version);\n        init_net_tmp->mutable_op()->MergeFrom(c2ops.init_ops);\n        net->mutable_op()->MergeFrom(c2ops.ops);\n        net->mutable_external_input()->MergeFrom(c2ops.interface_blobs);\n      }\n    }\n\n    for (const auto& value: model.graph().output()) {\n      net->add_external_output(value.name());\n    }\n    for (const auto& value: model.graph().input()) {\n      net->add_external_input(value.name());\n    }\n  };\n\n  converter(init_model, init_net);\n  converter(pred_model, pred_net);\n}\n\nCaffe2BackendRep*\nCaffe2Backend::Prepare(const std::string &onnx_model_str,\n                       const std::string &device,\n                       const std::vector<Caffe2Ops> &extras) {\n  Caffe2BackendRep* rep = new Caffe2BackendRep();\n  ModelProto onnx_model;\n  ParseProtobufFromLargeString(onnx_model_str, &onnx_model);\n  ONNX_NAMESPACE::checker::check_model(onnx_model);\n\n  int opset_version = -1;\n  for (const auto& imp: onnx_model.opset_import()) {\n    if ((!imp.has_domain()) || imp.domain().empty()) {\n      opset_version = imp.version();\n      if (opset_version > kKnownOpsetVersion) {\n        std::cout\n            << \"This version of onnx-caffe2 targets ONNX operator set version \"\n            << kKnownOpsetVersion\n            << \", but the model we are trying to import uses version \"\n            << opset_version << \".  We will try to import it anyway, \"\n            << \"but if the model uses operators which had BC-breaking changes \"\n               \"in the intervening versions, import will fail.\" << std::endl;\n      }\n    } else {\n      std::cout << \"Unrecognized operator set \" << opset_version << std::endl;\n    }\n  }\n  if (opset_version< 0) {\n    if (onnx_model.ir_version() >= 0x00000003) {\n      throw std::runtime_error(\n          \"Model with IR version >= 3 did not specify ONNX operator set \"\n          \"version (onnx-caffe2 requires it)\");\n    } else {\n      opset_version = 1;\n    }\n  }\n\n  //TODO: avoid extra copy by directly feed initialiers to backend blobs\n  OnnxToCaffe2(&rep->init_net(), &rep->pred_net(), onnx_model, device,\n               opset_version, true, extras);\n\n  // Get a list of uninitialized inputs to help with the inference setup\n  auto& uninitialized_inputs = rep->uninitialized_inputs();\n  std::unordered_set<std::string> initialized_inputs;\n  for (const auto& tp: onnx_model.graph().initializer()) {\n    initialized_inputs.emplace(tp.name());\n  }\n  for (const auto& input: onnx_model.graph().input()) {\n    if (!initialized_inputs.count(input.name())) {\n      uninitialized_inputs.emplace_back(input.name());\n    }\n  }\n\n  return rep;\n}\n\nvoid Caffe2Backend::BuildTensorFillingOp(caffe2::OperatorDef *c2_op,\n                                         const TensorProto &onnx_tensor,\n                                         const std::string &name) {\n  auto fill_name = name.empty() ? onnx_tensor.name() : name;\n  CAFFE_ENFORCE(!fill_name.empty());\n\n  if (onnx_tensor.has_segment()) {\n    throw std::runtime_error(\"Currently not supporting loading segments.\");\n  }\n\n  auto* c2_values = c2_op->add_arg();\n  c2_values->set_name(\"values\");\n\n  if (onnx_tensor.data_type() == TensorProto::FLOAT) {\n    c2_op->set_type(\"GivenTensorFill\");\n    auto* floats = c2_values->mutable_floats();\n    if (!TryConvertingTensorRawValues<float>(onnx_tensor, floats)) {\n      floats->CopyFrom(onnx_tensor.float_data());\n    }\n  } else if (onnx_tensor.data_type() == TensorProto::DOUBLE) {\n    c2_op->set_type(\"GivenTensorDoubleFill\");\n    ::google::protobuf::RepeatedField<double> tmp;\n    const ::google::protobuf::RepeatedField<double>* src = &tmp;\n    if (!TryConvertingTensorRawValues<double>(onnx_tensor, &tmp)) {\n      src = &onnx_tensor.double_data();\n    } else {\n      for (const auto i: *src) {\n        c2_values->add_floats(i);\n      }\n    }\n  } else if (onnx_tensor.data_type() == TensorProto::INT64) {\n    c2_op->set_type(\"GivenTensorInt64Fill\");\n    auto *ints = c2_values->mutable_ints();\n    if (!TryConvertingTensorRawValues<::google::protobuf::int64>(onnx_tensor,\n                                                                    ints)) {\n      ints->CopyFrom(onnx_tensor.int64_data());\n    }\n  } else if (onnx_tensor.data_type() == TensorProto::UINT32) {\n    c2_op->set_type(\"GivenTensorInt64Fill\");\n    ::google::protobuf::RepeatedField<::google::protobuf::uint64> tmp;\n    const ::google::protobuf::RepeatedField<::google::protobuf::uint64>* src = &tmp;\n    if (!TryConvertingTensorRawValues<::google::protobuf::uint64>(onnx_tensor, &tmp)) {\n      src = &onnx_tensor.uint64_data();\n    } else {\n      for (const auto i: *src) {\n        c2_values->add_ints(i);\n      }\n    }\n  } else if (onnx_tensor.data_type() == TensorProto::BOOL ||\n             onnx_tensor.data_type() == TensorProto::UINT8 ||\n             onnx_tensor.data_type() == TensorProto::INT8 ||\n             onnx_tensor.data_type() == TensorProto::UINT16 ||\n             onnx_tensor.data_type() == TensorProto::INT16 ||\n             onnx_tensor.data_type() == TensorProto::INT32) {\n    c2_op->set_type(onnx_tensor.data_type() == TensorProto::BOOL\n                       ? \"GivenTensorBoolFill\"\n                       : \"GivenTensorIntFill\");\n    ::google::protobuf::RepeatedField<::google::protobuf::int32> tmp;\n    const ::google::protobuf::RepeatedField<::google::protobuf::int32>* src = &tmp;\n    if (!TryConvertingTensorRawValues<::google::protobuf::int32>(onnx_tensor, &tmp)) {\n      src = &onnx_tensor.int32_data();\n    } else {\n      for (const auto i: *src) {\n        c2_values->add_ints(i);\n      }\n    }\n  } else if (onnx_tensor.data_type() == TensorProto::STRING) {\n    c2_op->set_type(\"GivenTensorStringFill\");\n    auto * strings = c2_values->mutable_strings();\n    strings->CopyFrom(onnx_tensor.string_data());\n  } else {\n    throw std::runtime_error(\n        \"unrecognized tensor type: \" +\n        TensorProto::DataType_Name(onnx_tensor.data_type()));\n  }\n\n  auto* c2_shape = c2_op->add_arg();\n  c2_shape->set_name(\"shape\");\n  for(const auto d: onnx_tensor.dims()) {\n    c2_shape->add_ints(d);\n  }\n  c2_op->add_output(fill_name);\n}\n\n}}\n"
  },
  {
    "path": "caffe2/onnx/backend.h",
    "content": "#pragma once\n\n#include \"backend_rep.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"device.h\"\n#include \"onnx/onnx_pb.h\"\n\n#include <google/protobuf/text_format.h>\n#include <functional>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n\nnamespace caffe2 { namespace onnx {\n\nusing ONNX_NAMESPACE::AttributeProto;\nusing ONNX_NAMESPACE::NodeProto;\nusing ONNX_NAMESPACE::GraphProto;\nusing ONNX_NAMESPACE::ModelProto;\nusing ONNX_NAMESPACE::TensorProto;\n\nstruct Caffe2Ops {\n  ::google::protobuf::RepeatedPtrField<caffe2::OperatorDef> init_ops;\n  ::google::protobuf::RepeatedPtrField<caffe2::OperatorDef> ops;\n  ::google::protobuf::RepeatedPtrField<std::string> interface_blobs;\n};\n\n// A convenient class to query attributes of a NodeProto. Note that the\n// NodeProto can not be modified during the query of OnnxAttributes object\nclass OnnxAttributes {\n public:\n  OnnxAttributes(const NodeProto& node);\n\n  bool HasAttribute(const std::string& key) const {\n    return onnx_attrs_.count(key);\n  }\n\n  AttributeProto* AddRewrittenAttibute(const std::string& key) {\n    auto tmp = rewritten_onnx_attrs_.emplace(key, AttributeProto());\n    auto& attr = tmp.first->second;\n    attr.set_name(key);\n    return &attr;\n  }\n\n  ::google::protobuf::RepeatedPtrField<caffe2::Argument> OnnxAttrToCaffe2Arg(\n      std::function<std::string(const std::string&)> mapper) const;\n\n  // Get attribute given attribute name, specialied on data type T. Note that\n  // the return value is copied\n  template <typename T>\n  T get(const std::string& key) const;\n\n  template <typename T>\n  T get(const std::string& key, const T& default_value) const {\n    if (onnx_attrs_.count(key)) {\n      return get<T>(key);\n    } else {\n      return default_value;\n    }\n  }\n\n private:\n  std::unordered_map<std::string, const AttributeProto*> onnx_attrs_;\n  std::unordered_map<std::string, AttributeProto> rewritten_onnx_attrs_;\n};\n\ntemplate <>\nint64_t OnnxAttributes::get(const std::string& key) const;\ntemplate <>\nfloat OnnxAttributes::get(const std::string& key) const;\n\ntemplate <>\n::google::protobuf::RepeatedPtrField<std::string> OnnxAttributes::get(\n    const std::string& key) const;\n\ntemplate <>\n::google::protobuf::RepeatedField<::google::protobuf::int64>\nOnnxAttributes::get(const std::string& key) const;\n\ntemplate <>\nconst TensorProto* OnnxAttributes::get(const std::string& key) const;\n\n// convenient class for onnx node\nstruct OnnxNode {\n  OnnxNode(const NodeProto& node_in)\n      : node(node_in), attributes(node_in) {}\n\n  const NodeProto& node;\n\n  OnnxAttributes attributes;\n};\n\nclass Caffe2Backend {\n public:\n  Caffe2BackendRep* Prepare(\n      const std::string& onnx_model_str,\n      const std::string& device,\n      const std::vector<Caffe2Ops>& extras);\n\n  Caffe2Ops ConvertNode(const std::string& node_str, int opset_version);\n\n private:\n  using SpecialOpConverter = Caffe2Ops (Caffe2Backend::*)(\n      const ModelProto&,\n      const ModelProto&,\n      OnnxNode*,\n      int);\n\n  void OnnxToCaffe2(\n      caffe2::NetDef* init_net,\n      caffe2::NetDef* pred_net,\n      const ModelProto& onnx_model,\n      const std::string& device,\n      int opset_version,\n      bool include_initializers,\n      const std::vector<Caffe2Ops>& extras);\n\n  Caffe2Ops OnnxNodeToCaffe2Ops(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  std::unordered_set<std::string> AllNamesInGraph(\n      const GraphProto& graph);\n\n  void InplaceRewrite(GraphProto* graph);\n\n  std::unordered_map<std::string, std::string> InplaceRewrite(\n      ::google::protobuf::RepeatedPtrField<NodeProto>* nodes);\n\n  void BuildTensorFillingOp(\n      caffe2::OperatorDef* c2_op,\n      const TensorProto& onnx_tensor,\n      const std::string& name = \"\");\n\n  Caffe2Ops CommonOnnxNodeToCaffe2Ops(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  Caffe2Ops CreateConstant(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  Caffe2Ops CreateConvePoolOpBase(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  Caffe2Ops CreateReshape(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  Caffe2Ops CreateGather(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  Caffe2Ops CreateGemm(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  Caffe2Ops CreatePad(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  Caffe2Ops CreateConcat(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  Caffe2Ops CreateLogSoftmax(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  Caffe2Ops CreateSlice(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  Caffe2Ops CreateSqrt(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  Caffe2Ops CreateReciprocal(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  Caffe2Ops CreateMatMul(\n      const ModelProto& init_model,\n      const ModelProto& pred_model,\n      OnnxNode* onnx_node,\n      int opset_version);\n\n  // LUT related getters\n  const std::unordered_map<std::string, std::string>& get_renamed_operators() const;\n  const std::unordered_set<std::string>& get_rnn_operators() const;\n  const std::unordered_map<std::string, int>& get_broken_operators() const;\n  const std::unordered_map<std::string, std::string>& get_renamed_attrs() const;\n  const std::\n      unordered_map<std::string, std::unordered_map<std::string, std::string>>&\n      get_per_op_renamed_attrs() const;\n  const std::unordered_map<std::string, Caffe2Backend::SpecialOpConverter>&\n  get_special_operators() const;\n};\n\n}}\n"
  },
  {
    "path": "caffe2/onnx/backend_rep.cc",
    "content": "#include \"backend_rep.h\"\n#include \"caffe2/core/common.h\"\n\n#include <iostream>\n\nnamespace caffe2 { namespace onnx {\n\nvoid Caffe2BackendRep::CheckInit() {\n  if (!predictor_) {\n    predictor_ = caffe2::make_unique<caffe2::Predictor>(init_net_, pred_net_);\n    init_net_.Clear();\n    pred_net_.Clear();\n  }\n}\n\n\nvoid Caffe2BackendRep::Run(\n    const caffe2::Predictor::TensorVector& inputs,\n    caffe2::Predictor::TensorVector* outputs) {\n  CheckInit();\n  predictor_->run(inputs, outputs);\n}\n\nvoid Caffe2BackendRep::RunMap(\n    const caffe2::Predictor::TensorMap& inputs,\n    caffe2::Predictor::TensorVector* outputs) {\n  CheckInit();\n  predictor_->run_map(inputs, outputs);\n}\n\n}}\n"
  },
  {
    "path": "caffe2/onnx/backend_rep.h",
    "content": "#pragma once\n\n#include \"caffe2/core/predictor.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace caffe2 { namespace onnx {\nclass Caffe2BackendRep {\n public:\n  void Run(\n      const caffe2::Predictor::TensorVector& inputs,\n      caffe2::Predictor::TensorVector* outputs);\n  void RunMap(\n      const caffe2::Predictor::TensorMap& inputs,\n      caffe2::Predictor::TensorVector* outputs);\n\n  caffe2::NetDef& init_net() {\n    return init_net_;\n  }\n  caffe2::NetDef& pred_net() {\n    return pred_net_;\n  }\n  std::vector<std::string>& uninitialized_inputs() {\n    return uninitialized_inputs_;\n  }\n\n  const caffe2::NetDef& init_net() const {\n    return init_net_;\n  }\n  const caffe2::NetDef& pred_net() const {\n    return pred_net_;\n  }\n  const std::vector<std::string>& uninitialized_inputs() const {\n    return uninitialized_inputs_;\n  }\n\n private:\n  void CheckInit();\n\n  caffe2::NetDef init_net_;\n  caffe2::NetDef pred_net_;\n  std::vector<std::string> uninitialized_inputs_;\n  std::unique_ptr<caffe2::Predictor> predictor_{nullptr};\n};\n}}\n"
  },
  {
    "path": "caffe2/onnx/device.cc",
    "content": "#include \"device.h\"\n\n#include <cstdlib>\n#include <unordered_map>\n\nnamespace caffe2 { namespace onnx {\nstatic const std::unordered_map<std::string, DeviceType> kDeviceMap = {\n  {\"CPU\", DeviceType::CPU},\n  {\"CUDA\", DeviceType::CUDA}\n};\n\nDevice::Device(const std::string &spec) {\n  auto pos = spec.find_first_of(':');\n  type = kDeviceMap.at(spec.substr(0, pos - 1));\n  device_id = atoi(spec.substr(pos + 1).c_str());\n}\n}}\n\n"
  },
  {
    "path": "caffe2/onnx/device.h",
    "content": "#pragma once\n\n#include <functional>\n#include <string>\n\nnamespace caffe2 { namespace onnx {\n\nenum class DeviceType {CPU=0, CUDA=1};\n\nstruct Device {\n  Device(const std::string& spec);\n  DeviceType type;\n  int device_id{-1};\n};\n\n}}\n\nnamespace std {\ntemplate <> struct hash<caffe2::onnx::DeviceType> {\n  std::size_t operator()(const caffe2::onnx::DeviceType &k) const {\n    return std::hash<int>()(static_cast<int>(k));\n  }\n};\n} // namespace std\n"
  },
  {
    "path": "caffe2/onnx/helper.cc",
    "content": "#include \"helper.h\"\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 { namespace onnx  {\n\nsize_t DummyName::counter_ = 0;\n\nstd::unordered_set<std::string>& DummyName::get_used_names() {\n  static std::unordered_set<std::string> used_names;\n  return used_names;\n}\n\nstd::string DummyName::NewDummyName() {\n  while (true) {\n    const std::string name = caffe2::MakeString(\"OC2_DUMMY_\", counter_++);\n    auto ret = get_used_names().insert(name);\n    if (ret.second) {\n      return name;\n    }\n  }\n}\n\nvoid DummyName::Reset(const std::unordered_set<std::string> &used_names) {\n  auto& names = get_used_names();\n  names = used_names;\n  counter_ = 0;\n}\n\n}}\n"
  },
  {
    "path": "caffe2/onnx/helper.h",
    "content": "#pragma once\n\n#include <set>\n#include <string>\n#include <unordered_set>\n\nnamespace caffe2 { namespace onnx {\nclass DummyName {\n  public:\n    static std::string NewDummyName();\n\n    static void Reset(const std::unordered_set<std::string>& used_names);\n\n    static void AddName(const std::string& new_used) {\n      get_used_names().insert(new_used);\n    }\n\n   private:\n     static std::unordered_set<std::string>& get_used_names();\n     static size_t counter_;\n};\n\n}}\n"
  },
  {
    "path": "caffe2/operators/CMakeLists.txt",
    "content": "# ---[ GPU files\n# ------[ cuDNN\nfile(GLOB tmp *_cudnn.cc)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# ------[ general GPU\nfile(GLOB tmp *_gpu.cc)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# ------[ CUDA sources\nfile(GLOB tmp *.cu)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# exclude test files\nfile(GLOB tmp *_test.cc)\nexclude(Caffe2_GPU_SRCS \"${Caffe2_GPU_SRCS}\" ${tmp})\n\n# ---[ CPU files.\nfile(GLOB tmp *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp})\n# exclude test files and gpu files\nfile(GLOB tmp *_test.cc)\nexclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${tmp})\nexclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${Caffe2_GPU_SRCS})\n\n# ---[ GPU test files\n# ------[ cuDNN\nfile(GLOB tmp *_cudnn_test.cc)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} ${tmp})\n# ------[ general GPU\nfile(GLOB tmp *_gpu_test.cc)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} ${tmp})\n\n# ---[ CPU test files\nfile(GLOB tmp *_test.cc)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${tmp})\nexclude(Caffe2_CPU_TEST_SRCS \"${Caffe2_CPU_TEST_SRCS}\" ${Caffe2_GPU_TEST_SRCS})\n\n# ---[ Send the lists to the parent scope.\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/operators/abs_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nstruct AbsCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* device_context) {\n    math::Abs<T, CPUContext>(n, x, y, device_context);\n  }\n};\n\nstruct AbsGradientCPUFunctor {\n  template <typename T>\n  inline void\n  Run(const int n, const T* x, const T* dy, T* dx, CPUContext* /* unused */) {\n    ConstEigenVectorArrayMap<T> dyM(dy, n);\n    ConstEigenVectorArrayMap<T> xM(x, n);\n    EigenVectorMap<T>(dx, n) =\n        (xM == T(0)).select(T(0), (xM > T(0)).select(dyM, -dyM));\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    Abs,\n    UnaryElementwiseOp<TensorTypes<float>, CPUContext, AbsCPUFunctor>);\nREGISTER_CPU_OPERATOR(\n    AbsGradient,\n    BinaryElementwiseOp<\n        TensorTypes<float>,\n        CPUContext,\n        WithoutBroadcast<AbsGradientCPUFunctor>>);\n\nOPERATOR_SCHEMA(Abs)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nCalculates the absolute value of the given input tensor, element-wise.\n)DOC\")\n    .Input(0, \"input\", \"Input tensor\")\n    .Output(\n        0,\n        \"output\",\n        \"The absolute value of the input tensor computed element-wise\");\n\nOPERATOR_SCHEMA(AbsGradient).NumInputs(2).NumOutputs(1).IdenticalTypeAndShape();\n\nclass GetAbsGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"AbsGradient\",\n        \"\",\n        std::vector<string>{I(0), GO(0)},\n        std::vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Abs, GetAbsGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/abs_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cmath>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void AbsKernel(const int N, const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = fabs(X[i]);\n  }\n}\n\ntemplate <typename T>\n__global__ void AbsGradientKernel(const int N, const T* X, const T* dY, T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dX[i] = X[i] == T(0) ? T(0) : (X[i] > T(0) ? dY[i] : -dY[i]);\n  }\n}\n\nstruct AbsCUDAFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CUDAContext* device_context) {\n    AbsKernel<T>\n        <<<CAFFE_GET_BLOCKS(n),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           device_context->cuda_stream()>>>(n, x, y);\n    return;\n  }\n};\n\nstruct AbsGradientCUDAFunctor {\n  template <typename T>\n  inline void Run(\n      const int n,\n      const T* x,\n      const T* dy,\n      T* dx,\n      CUDAContext* device_context) {\n    AbsGradientKernel<T>\n        <<<CAFFE_GET_BLOCKS(n),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           device_context->cuda_stream()>>>(n, x, dy, dx);\n    return;\n  }\n};\n\nREGISTER_CUDA_OPERATOR(\n    Abs,\n    UnaryElementwiseOp<TensorTypes<float>, CUDAContext, AbsCUDAFunctor>);\nREGISTER_CUDA_OPERATOR(\n    AbsGradient,\n    BinaryElementwiseOp<\n        TensorTypes<float>,\n        CUDAContext,\n        WithoutBroadcast<AbsGradientCUDAFunctor>>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/accumulate_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/accumulate_op.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(Accumulate, AccumulateOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Accumulate)\n  .NumInputs(1)\n  .NumOutputs(1)\n  .IdenticalTypeAndShape()\n  .SetDoc(R\"DOC(\nAccumulate operator accumulates the input tensor to the output tensor. If the\noutput tensor already has the right size, we add to it; otherwise, we first\ninitialize the output tensor to all zeros, and then do accumulation. Any\nfurther calls to the operator, given that no one else fiddles with the output\nin the interim, will do simple accumulations.\nAccumulation is done using Axpby operation as shown:\n  Y = 1*X + gamma*Y\nwhere X is the input tensor, Y is the output tensor and gamma is the multiplier\nargument.\n)DOC\")\n  .Arg(\"gamma\", \"(float, default 1.0) Accumulation multiplier\")\n  .Input(0, \"input\", \"The input tensor that has to be accumulated to the \"\n         \"output tensor. If the output size is not the same as input size, the \"\n         \"output tensor is first reshaped and initialized to zero, and only \"\n         \"then, accumulation is done.\")\n  .Output(0, \"output\", \"Accumulated output tensor\");\n\nSHOULD_NOT_DO_GRADIENT(Accumulate);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/accumulate_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/accumulate_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(Accumulate, AccumulateOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/accumulate_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_ACCUMULATE_OP_H_\n#define CAFFE2_OPERATORS_ACCUMULATE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass AccumulateOp final : public Operator<Context> {\n public:\n  AccumulateOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        gamma_(static_cast<T>(\n            OperatorBase::template GetSingleArgument<float>(\"gamma\", 1.0))) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = Output(0);\n    if (output->dims() != input.dims()) {\n      LOG(INFO) << \"Reshaping and initializing output.\";\n      output->ResizeLike(input);\n      math::Set<T, Context>(\n          output->size(), 0, output->template mutable_data<T>(), &context_);\n    }\n    math::Axpby<T, Context>(\n        input.size(),\n        static_cast<T>(1),\n        input.template data<T>(),\n        gamma_,\n        output->template mutable_data<T>(),\n        &context_);\n    return true;\n  }\n\n protected:\n  T gamma_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_ACCUMULATE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/accuracy_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/accuracy_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool AccuracyOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(PREDICTION);\n  auto& label = Input(LABEL);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE_EQ(X.ndim(), 2);\n  int N = X.dim32(0);\n  int D = X.dim32(1);\n  CAFFE_ENFORCE_EQ(label.ndim(), 1);\n  CAFFE_ENFORCE_EQ(label.dim32(0), N);\n  Y->Resize(vector<TIndex>());\n  const auto* Xdata = X.data<float>();\n  const auto* labelData = label.data<int>();\n  const int top_k = top_k_;\n  int correct = 0;\n\n  // it's equivalent to using a stable sorting algorithm to sort the\n  // classes (with their predictions as key) and then check whether\n  // the label is within the first top_k slots.\n  for (int i = 0; i < N; ++i) {\n    auto label_i = labelData[i];\n    auto label_pred = Xdata[i * D + label_i];\n    int ngt = 1;\n    for (int j = 0; j < D; ++j) {\n      auto pred = Xdata[i * D + j];\n      if ((pred > label_pred) || (pred == label_pred && j < label_i)) {\n        if (++ngt > top_k) {\n          break;\n        }\n      }\n    }\n    if (ngt <= top_k) {\n      ++correct;\n    }\n  }\n  CAFFE_ENFORCE_LE(correct, N);\n  *(Y->mutable_data<float>()) = static_cast<float>(correct) / N;\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(Accuracy, AccuracyOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Accuracy)\n  .NumInputs(2)\n  .NumOutputs(1)\n  .ScalarType(TensorProto::FLOAT)\n  .SetDoc(R\"DOC(\nAccuracy takes two inputs- predictions and labels, and returns a float\naccuracy value for the batch. Predictions are expected in the form of 2-D tensor\ncontaining a batch of scores for various classes, and labels are expected in the\n form of 1-D tensor containing true label indices of samples in the batch. If\nthe score for the label index in the predictions is the highest among all\nclasses, it is considered a correct prediction.\n)DOC\")\n  .Arg(\n      \"top_k\",\n      \"Count as correct by comparing the true label to the top k scoring \"\n      \"classes (default 1: only compare to the top scoring class i.e. argmax)\")\n  .Input(0, \"predictions\", \"2-D tensor (Tensor<float>) of size \"\n         \"(num_batches x num_classes) containing scores\")\n  .Input(1, \"labels\", \"1-D tensor (Tensor<int>) of size (num_batches) having \"\n        \"the indices of true labels\")\n  .Output(0, \"accuracy\", \"1-D tensor (Tensor<float>) of size 1 containing \"\n          \"accuracy\");\n\nSHOULD_NOT_DO_GRADIENT(Accuracy);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/accuracy_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/accuracy_op.h\"\n#include \"caffe2/utils/math.h\"\n\n#include <cub/block/block_reduce.cuh>\n\nnamespace caffe2 {\n\nnamespace {\n__global__ void AccuracyKernel(\n    const int N,\n    const int D,\n    const int top_k,\n    const float* Xdata,\n    const int* labelData,\n    float* accuracy) {\n  typedef cub::BlockReduce<int, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  int correct = 0;\n  for (int row = blockIdx.x; row < N; row += gridDim.x) {\n    const int label = labelData[row];\n    const float label_pred = Xdata[row * D + label];\n    int ngt = 0;\n    for (int col = threadIdx.x; col < D; col += blockDim.x) {\n      const float pred = Xdata[row * D + col];\n      if (pred > label_pred || (pred == label_pred && col <= label)) {\n        ++ngt;\n      }\n    }\n    ngt = BlockReduce(temp_storage).Sum(ngt);\n    if (ngt <= top_k) {\n      ++correct;\n    }\n    __syncthreads();\n  }\n  if (threadIdx.x == 0) {\n    atomicAdd(accuracy, static_cast<float>(correct));\n  }\n}\n\n__global__ void AccuracyDivideKernel(const int N, float* accuracy) {\n  *accuracy /= N;\n}\n}  // namespace\n\ntemplate <>\nbool AccuracyOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(PREDICTION);\n  auto& label = Input(LABEL);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE_EQ(X.ndim(), 2);\n  int N = X.dim32(0);\n  int D = X.dim32(1);\n  CAFFE_ENFORCE_EQ(label.ndim(), 1);\n  CAFFE_ENFORCE_EQ(label.dim32(0), N);\n  Y->Resize(vector<TIndex>());\n  float* Ydata = Y->mutable_data<float>();\n  math::Set<float, CUDAContext>(1, 0, Ydata, &context_);\n  AccuracyKernel<<<\n      std::min(CAFFE_MAXIMUM_NUM_BLOCKS, N),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N, D, top_k_, X.data<float>(), label.data<int>(), Ydata);\n  // This is going to be executed only in one single kernel. Not very beautiful,\n  // but probably we have to do this?\n  AccuracyDivideKernel<<<1, 1, 0, context_.cuda_stream()>>>(\n      N, Ydata);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Accuracy, AccuracyOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/accuracy_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_ACCURACY_OP_H_\n#define CAFFE2_OPERATORS_ACCURACY_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass AccuracyOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  AccuracyOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        top_k_(OperatorBase::GetSingleArgument<int>(\"top_k\", 1)) {}\n        \n  bool RunOnDevice() override;\n\n protected:\n  int top_k_; \n  INPUT_TAGS(PREDICTION, LABEL);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_ACCURACY_OP_H_\n"
  },
  {
    "path": "caffe2/operators/apmeter_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/apmeter_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nvoid APMeterOp<float, CPUContext>::BufferPredictions(\n    const float* XData,\n    const int* labelData,\n    int N,\n    int D) {\n  if (buffers_.empty()) {\n    // Initialize the buffer\n    buffers_.resize(D, std::vector<BufferDataType>(buffer_size_));\n  }\n  DCHECK_EQ(buffers_.size(), D);\n\n  // Fill atmose buffer_size_ data at a time, so truncate the input if needed\n  if (N > buffer_size_) {\n    XData = XData + (N - buffer_size_) * D;\n    labelData = labelData + (N - buffer_size_) * D;\n    N = buffer_size_;\n  }\n\n  // Reclaim space if not enough space in the buffer to hold new data\n  int space_to_reclaim = buffer_used_ + N - buffer_size_;\n  if (space_to_reclaim > 0) {\n    for (auto& buffer : buffers_) {\n      std::rotate(\n          buffer.begin(), buffer.begin() + space_to_reclaim, buffer.end());\n    }\n    buffer_used_ -= space_to_reclaim;\n  }\n\n  // Fill the buffer\n  for (int i = 0; i < D; i++) {\n    for (int j = 0; j < N; j++) {\n      buffers_[i][buffer_used_ + j].first = XData[j * D + i];\n      buffers_[i][buffer_used_ + j].second = labelData[j * D + i];\n    }\n  }\n\n  buffer_used_ += N;\n}\n\ntemplate <>\nbool APMeterOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(PREDICTION);\n  auto& label = Input(LABEL);\n  auto* Y = Output(0);\n  // Check dimensions\n  DCHECK_EQ(X.ndim(), 2);\n  int N = X.dim32(0);\n  int D = X.dim32(1);\n  DCHECK_EQ(label.ndim(), 2);\n  DCHECK_EQ(label.dim32(0), N);\n  DCHECK_EQ(label.dim32(1), D);\n  Y->Resize(D);\n\n  const auto* Xdata = X.data<float>();\n  const auto* labelData = label.data<int>();\n  auto* Ydata = Y->mutable_data<float>();\n\n  BufferPredictions(Xdata, labelData, N, D);\n\n  // Calculate AP for each class\n  for (int i = 0; i < D; i++) {\n    auto& buffer = buffers_[i];\n    // Sort predictions by score\n    std::stable_sort(\n        buffer.begin(),\n        buffer.begin() + buffer_used_,\n        [](const BufferDataType& p1, const BufferDataType& p2) {\n          return p1.first > p2.first;\n        });\n    // Calculate cumulative precision for each sample\n    float tp_sum = 0.0;\n    float precision_sum = 0.0;\n    int ntruth = 0;\n    for (int j = 0; j < buffer_used_; j++) {\n      tp_sum += buffer[j].second;\n      if (buffer[j].second == 1) {\n        ntruth += 1;\n        precision_sum += tp_sum / (j + 1);\n      }\n    }\n\n    // Calculate AP\n    Ydata[i] = precision_sum / std::max(1, ntruth);\n  }\n\n  return true;\n}\n\nnamespace {\nREGISTER_CPU_OPERATOR(APMeter, APMeterOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(APMeter)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .ScalarType(TensorProto::FLOAT)\n    .SetDoc(R\"DOC(\nAPMeter computes Average Precision for binary or multi-class classification.\nIt takes two inputs: prediction scores P of size (n_samples x n_classes), and\ntrue labels Y of size (n_samples x n_classes). It returns a single float number\nper class for the average precision of that class.\n)DOC\")\n    .Arg(\n        \"buffer_size\",\n        \"(int32_t) indicates how many predictions should the op buffer. \"\n        \"defaults to 1000\")\n    .Input(\n        0,\n        \"predictions\",\n        \"2-D tensor (Tensor<float>) of size (num_samples x\"\n        \"num_classes) containing prediction scores\")\n    .Input(\n        1,\n        \"labels\",\n        \"2-D tensor (Tensor<int>) of size (num_samples) \"\n        \"containing true labels for each sample\")\n    .Output(\n        0,\n        \"AP\",\n        \"1-D tensor (Tensor<float>) of size num_classes containing \"\n        \"average precision for each class\");\n\nSHOULD_NOT_DO_GRADIENT(APMeter);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/apmeter_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_MAP_OP_H_\n#define CAFFE2_MAP_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass APMeterOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  APMeterOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        buffer_size_(\n            OperatorBase::GetSingleArgument<int32_t>(\"buffer_size\", 1000)),\n        buffer_used_(0) {}\n\n  bool RunOnDevice() override;\n\n protected:\n  using BufferDataType = std::pair<float, int>;\n  // Buffer the predictions for each class\n  std::vector<std::vector<BufferDataType>> buffers_;\n  // Capacity of the buffer\n  int buffer_size_;\n  // Used buffer\n  int buffer_used_;\n\n  INPUT_TAGS(PREDICTION, LABEL);\n\n protected:\n  // Buffer predictions for N sample and D classes\n  void\n  BufferPredictions(const float* Xdata, const int* labelData, int N, int D);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_MAP_OP_H_\n"
  },
  {
    "path": "caffe2/operators/assert_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#include \"caffe2/operators/assert_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Assert, AssertOp<CPUContext>);\n\nOPERATOR_SCHEMA(Assert)\n    .NumInputs(1)\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(\nAssertion op. Takes in a tensor of bools, ints, longs, or long longs and checks\nif all values are true when coerced into a boolean. In other words, for non-bool\ntypes this asserts that all values in the tensor are non-zero.\n\t)DOC\")\n    .Arg(\n        \"error_msg\",\n        \"An error message to print when the assert fails.\",\n        false);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/assert_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/assert_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(Assert, AssertOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/assert_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef CAFFE2_OPERATORS_ASSERT_OP_H_\n#define CAFFE2_OPERATORS_ASSERT_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass AssertOp final : public Operator<Context> {\n public:\n  AssertOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        error_msg_(\n            OperatorBase::GetSingleArgument<std::string>(\"error_msg\", \"\")) {}\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  template <typename T>\n  bool DoRunWithType() {\n    // Copy into CPU context for comparison\n    cmp_tensor_.CopyFrom(Input(0));\n    auto* cmp_data = cmp_tensor_.template data<T>();\n\n    for (TIndex i = 0; i < cmp_tensor_.size(); ++i) {\n      CAFFE_ENFORCE((bool)cmp_data[i], [&]() {\n        std::stringstream ss;\n        ss << \"Assert failed for element \" << i\n           << \" in tensor, value: \" << cmp_data[i] << \"\\n\";\n        if (!error_msg_.empty()) {\n          ss << \"Error message: \" << error_msg_;\n        }\n        return ss.str();\n      }());\n    }\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<long, int, bool>>::call(this, Input(0));\n  }\n\n private:\n  TensorCPU cmp_tensor_;\n  std::string error_msg_;\n};\n\n} // namespace caffe2\n\n#endif /* CAFFE2_OPERATORS_ASSERT_OP_H_ */\n"
  },
  {
    "path": "caffe2/operators/atomic_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <mutex>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\nnamespace fb {\nnamespace {\n\nclass CreateMutexOp final : public Operator<CPUContext> {\n public:\n  CreateMutexOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    *OperatorBase::Output<std::unique_ptr<std::mutex>>(0) =\n        std::unique_ptr<std::mutex>(new std::mutex);\n    return true;\n  }\n};\n\nclass AtomicFetchAddOp final : public Operator<CPUContext> {\n public:\n  AtomicFetchAddOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& mutex = OperatorBase::Input<std::unique_ptr<std::mutex>>(0);\n    auto& a = Input(1);\n    auto& b = Input(2);\n    auto* c = Output(0);\n    auto* d = Output(1);\n    c->Resize(std::vector<TIndex>());\n    d->Resize(std::vector<TIndex>());\n    auto* aPtr = a.data<int32_t>();\n    auto* bPtr = b.data<int32_t>();\n    auto* cPtr = c->mutable_data<int32_t>();\n    auto* dPtr = d->mutable_data<int32_t>();\n    std::lock_guard<std::mutex> lg(*mutex);\n    *dPtr = *aPtr;\n    *cPtr = *aPtr + *bPtr;\n    return true;\n  }\n};\n\nclass CreateAtomicBoolOp final : public Operator<CPUContext> {\n public:\n  using Operator::Operator;\n\n  bool RunOnDevice() override {\n    *OperatorBase::Output<std::unique_ptr<std::atomic<bool>>>(0) =\n        std::unique_ptr<std::atomic<bool>>(new std::atomic<bool>(false));\n    return true;\n  }\n};\n\nclass ConditionalSetAtomicBoolOp final : public Operator<CPUContext> {\n public:\n  using Operator::Operator;\n\n  bool RunOnDevice() override {\n    auto& ptr =\n        OperatorBase::Input<std::unique_ptr<std::atomic<bool>>>(ATOMIC_BOOL);\n    if (Input(CONDITION).data<bool>()[0]) {\n      ptr->store(true);\n    }\n    return true;\n  }\n\n private:\n  INPUT_TAGS(ATOMIC_BOOL, CONDITION);\n};\n\nclass CheckAtomicBoolOp final : public Operator<CPUContext> {\n public:\n  using Operator::Operator;\n\n  bool RunOnDevice() override {\n    auto& ptr = OperatorBase::Input<std::unique_ptr<std::atomic<bool>>>(0);\n    Output(0)->Resize(1);\n    *Output(0)->mutable_data<bool>() = ptr->load();\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(CreateMutex, CreateMutexOp);\nREGISTER_CPU_OPERATOR(AtomicFetchAdd, AtomicFetchAddOp);\n\nREGISTER_CPU_OPERATOR(CreateAtomicBool, CreateAtomicBoolOp);\nREGISTER_CPU_OPERATOR(ConditionalSetAtomicBool, ConditionalSetAtomicBoolOp);\nREGISTER_CPU_OPERATOR(CheckAtomicBool, CheckAtomicBoolOp);\n\nOPERATOR_SCHEMA(CreateMutex)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(\"Creates an unlocked mutex and returns it in a unique_ptr blob.\")\n    .Output(0, \"mutex_ptr\", \"Blob containing a std::unique_ptr<mutex>.\");\n\nOPERATOR_SCHEMA(AtomicFetchAdd)\n    .NumInputs(3)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nGiven a mutex and two int32 scalar tensors, performs an atomic fetch add\nby mutating the first argument and adding it to the second input\nargument. Returns the updated integer and the value prior to the update.\n)DOC\")\n    .Input(0, \"mutex_ptr\", \"Blob containing to a unique_ptr<mutex>\")\n    .Input(1, \"mut_value\", \"Value to be mutated after the sum.\")\n    .Input(2, \"increment\", \"Value to add to the first operand.\")\n    .Output(0, \"mut_value\", \"Mutated value after sum. Usually same as input 1.\")\n    .Output(1, \"fetched_value\", \"Value of the first operand before sum.\")\n    .AllowInplace({{1, 0}});\n\nOPERATOR_SCHEMA(CreateAtomicBool)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(\"Create an unique_ptr blob to hold an atomic<bool>\")\n    .Output(0, \"atomic_bool\", \"Blob containing a unique_ptr<atomic<bool>>\");\n\nOPERATOR_SCHEMA(ConditionalSetAtomicBool)\n    .NumInputs(2)\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(\nSet an atomic<bool> to true if the given condition bool variable is true\n    )DOC\")\n    .Input(0, \"atomic_bool\", \"Blob containing a unique_ptr<atomic<bool>>\")\n    .Input(1, \"condition\", \"Blob containing a bool\");\n\nOPERATOR_SCHEMA(CheckAtomicBool)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(\"Copy the value of an atomic<bool> to a bool\")\n    .Input(0, \"atomic_bool\", \"Blob containing a unique_ptr<atomic<bool>>\")\n    .Output(0, \"value\", \"Copy of the value for the atomic<bool>\");\n\nSHOULD_NOT_DO_GRADIENT(CreateMutex);\nSHOULD_NOT_DO_GRADIENT(AtomicFetchAdd);\nSHOULD_NOT_DO_GRADIENT(CreateAtomicBool);\nSHOULD_NOT_DO_GRADIENT(ConditionalSetAtomicBool);\nSHOULD_NOT_DO_GRADIENT(CheckAtomicBool);\n}\n}\n}\n"
  },
  {
    "path": "caffe2/operators/batch_box_cox_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/batch_box_cox_op.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\n#ifdef CAFFE2_USE_MKL\n#include <mkl.h>\n#endif // CAFFE2_USE_MKL\n\nnamespace caffe2 {\n\n#ifdef CAFFE2_USE_MKL\nnamespace {\n\n// Helpers for copying parameters.\ntemplate <typename T>\nvoid TileArrayIntoVector(const T* a, int D, int K, vector<T>* b) {\n  b->resize(K * D);\n  for (int k = 0; k < K; k++) {\n    std::copy(a, a + D, b->begin() + k * D);\n  }\n}\n\nvoid TileIndicesInPlace(vector<int>* v, int D, int K) {\n  int n = v->size();\n  v->resize(K * n);\n  for (int k = 1; k < K; k++) {\n    for (int j = 0; j < n; j++) {\n      (*v)[k * n + j] = (*v)[j] + k * D;\n    }\n  }\n}\n\n// MKL VML function templates.\ntemplate <typename T>\nvoid PackV(const int N, const T* a, const int* ia, T* y);\ntemplate <typename T>\nvoid UnpackV(const int N, const T* a, T* y, const int* iy);\ntemplate <typename T>\nvoid Pow(const int N, const T* a, const T* b, T* y);\n\n#define DELEGATE_PACKV_FUNCTION(T, OriginalFunc)                \\\n  template <>                                                   \\\n  void PackV<T>(const int N, const T* a, const int* ia, T* y) { \\\n    OriginalFunc(N, a, ia, y);                                  \\\n  }\nDELEGATE_PACKV_FUNCTION(float, vsPackV)\nDELEGATE_PACKV_FUNCTION(double, vdPackV)\n#undef DELEGATE_PACKV_FUNCTION\n\n#define DELEGATE_UNPACKV_FUNCTION(T, OriginalFunc)                \\\n  template <>                                                     \\\n  void UnpackV<T>(const int N, const T* a, T* y, const int* iy) { \\\n    OriginalFunc(N, a, y, iy);                                    \\\n  }\nDELEGATE_UNPACKV_FUNCTION(float, vsUnpackV)\nDELEGATE_UNPACKV_FUNCTION(double, vdUnpackV)\n#undef DELEGATE_UNPACKV_FUNCTION\n\n#define DELEGATE_SIMPLE_BINARY_FUNCTION(T, Funcname, OriginalFunc) \\\n  template <>                                                      \\\n  void Funcname<T>(const int N, const T* a, const T* b, T* y) {    \\\n    OriginalFunc(N, a, b, y);                                      \\\n  }\nDELEGATE_SIMPLE_BINARY_FUNCTION(float, Pow, vsPow)\nDELEGATE_SIMPLE_BINARY_FUNCTION(double, Pow, vdPow)\n#undef DELEGATE_SIMPLE_BINARY_FUNCTION\n\n} // namespace\n#endif // CAFFE2_USE_MKL\n\ntemplate <>\ntemplate <typename T>\nbool BatchBoxCoxOp<CPUContext>::DoRunWithType() {\n  auto& data = Input(DATA);\n  auto& lambda1 = Input(LAMBDA1);\n  auto& lambda2 = Input(LAMBDA2);\n  CAFFE_ENFORCE_GE(data.ndim(), 1);\n  auto N = data.dim(0);\n  auto D = data.size_from_dim(1);\n\n  auto* output = Output(0);\n  output->ResizeLike(Input(DATA));\n  auto* output_ptr = output->template mutable_data<T>();\n\n  if (data.size() <= 0) {\n    return true;\n  }\n\n  CAFFE_ENFORCE_EQ(lambda1.size(), D);\n  CAFFE_ENFORCE_EQ(lambda2.size(), D);\n\n  const auto* data_ptr = data.template data<T>();\n  const auto* lambda1_ptr = lambda1.template data<T>();\n  const auto* lambda2_ptr = lambda2.template data<T>();\n\n  const T k_eps = static_cast<T>(1e-6);\n\n#ifdef CAFFE2_USE_MKL\n  if (min_block_size_ < 1) {\n    BoxCoxNaive(N, D, data_ptr, lambda1_ptr, lambda2_ptr, k_eps, output_ptr);\n  } else {\n    // Find zero-valued columns, since they get special treatment.\n    nonzeros_.clear();\n    zeros_.clear();\n    nonzeros_.reserve(D);\n    zeros_.reserve(D);\n    for (TIndex j = 0; j < D; j++) {\n      if (lambda1_ptr[j] == 0) {\n        zeros_.push_back(j);\n      } else {\n        nonzeros_.push_back(j);\n      }\n    }\n\n    // Process K rows at a time for effective vectorization with small rows.\n    const int K = std::min(N, (min_block_size_ + D - 1) / D);\n\n    // Avoid copying data if all lambda1 values are zero, or if all are nonzero.\n    // In each of the three cases here, when K > 1, first process batches of K\n    // rows by replicating the input parameters K times. Then finish row-by-row.\n    TypedCachedBuffers<T>& b = GetBuffers<T>();\n    if (nonzeros_.size() == D) {\n      TIndex i = 0;\n      if (K > 1) {\n        TileArrayIntoVector(lambda1_ptr, D, K, &b.lambda1_);\n        TileArrayIntoVector(lambda2_ptr, D, K, &b.lambda2_);\n        DCHECK_EQ(K * D, b.lambda1_.size());\n        DCHECK_EQ(K * D, b.lambda2_.size());\n        for (; i < N - K + 1; i += K, data_ptr += K * D, output_ptr += K * D) {\n          BoxCoxNonzeroLambda(\n              K * D,\n              data_ptr,\n              b.lambda1_.data(),\n              b.lambda2_.data(),\n              k_eps,\n              output_ptr);\n        }\n      }\n      for (; i < N; i++, data_ptr += D, output_ptr += D) {\n        BoxCoxNonzeroLambda(\n            D, data_ptr, lambda1_ptr, lambda2_ptr, k_eps, output_ptr);\n      }\n    } else if (zeros_.size() == D) {\n      TIndex i = 0;\n      if (K > 1) {\n        TileArrayIntoVector(lambda2_ptr, D, K, &b.lambda2_z_);\n        DCHECK_EQ(K * D, b.lambda2_z_.size());\n        for (; i < N - K + 1; i += K, data_ptr += K * D, output_ptr += K * D) {\n          BoxCoxZeroLambda(\n              K * D, data_ptr, b.lambda2_z_.data(), k_eps, output_ptr);\n        }\n      }\n      for (; i < N; i++, data_ptr += D, output_ptr += D) {\n        BoxCoxZeroLambda(D, data_ptr, lambda2_ptr, k_eps, output_ptr);\n      }\n    } else { // General case of mixed zero and non-zero lambda1 values.\n      int n = nonzeros_.size();\n      if (K > 1) {\n        TileIndicesInPlace(&nonzeros_, 0, K);\n        TileIndicesInPlace(&zeros_, 0, K);\n      }\n\n      // Gather parameter values into contiguous memory.\n      b.lambda1_.resize(nonzeros_.size());\n      b.lambda2_.resize(nonzeros_.size());\n      b.lambda2_z_.resize(zeros_.size());\n      PackV(nonzeros_.size(), lambda1_ptr, nonzeros_.data(), b.lambda1_.data());\n      PackV(nonzeros_.size(), lambda2_ptr, nonzeros_.data(), b.lambda2_.data());\n      PackV(zeros_.size(), lambda2_ptr, zeros_.data(), b.lambda2_z_.data());\n\n      TIndex i = 0;\n      b.accumulator_.resize(std::max(nonzeros_.size(), zeros_.size()));\n      if (K > 1) {\n        // Truncate to original size, and re-tile with offsets this time.\n        nonzeros_.resize(n);\n        zeros_.resize(D - n);\n        TileIndicesInPlace(&nonzeros_, D, K);\n        TileIndicesInPlace(&zeros_, D, K);\n        DCHECK_EQ(nonzeros_.size(), b.lambda1_.size());\n        DCHECK_EQ(nonzeros_.size(), b.lambda2_.size());\n        DCHECK_EQ(zeros_.size(), b.lambda2_z_.size());\n        for (; i < N - K + 1; i += K, data_ptr += K * D, output_ptr += K * D) {\n          BoxCoxMixedLambda(\n              data_ptr,\n              nonzeros_,\n              zeros_,\n              b.lambda1_.data(),\n              b.lambda2_.data(),\n              b.lambda2_z_.data(),\n              k_eps,\n              b.accumulator_.data(),\n              output_ptr);\n        }\n        // Truncate to original size.\n        nonzeros_.resize(n);\n        zeros_.resize(D - n);\n      }\n      for (; i < N; i++, data_ptr += D, output_ptr += D) {\n        BoxCoxMixedLambda(\n            data_ptr,\n            nonzeros_,\n            zeros_,\n            b.lambda1_.data(),\n            b.lambda2_.data(),\n            b.lambda2_z_.data(),\n            k_eps,\n            b.accumulator_.data(),\n            output_ptr);\n      }\n    }\n  }\n#else // CAFFE2_USE_MKL\n  BoxCoxNaive(N, D, data_ptr, lambda1_ptr, lambda2_ptr, k_eps, output_ptr);\n#endif // CAFFE2_USE_MKL\n  return true;\n}\n\ntemplate <>\ntemplate <typename T>\nvoid BatchBoxCoxOp<CPUContext>::BoxCoxNaive(\n    TIndex N,\n    TIndex D,\n    const T* data_ptr,\n    const T* lambda1_ptr,\n    const T* lambda2_ptr,\n    T k_eps,\n    T* output_ptr) {\n  for (TIndex i = 0; i < N; i++) {\n    for (TIndex j = 0; j < D; j++, data_ptr++, output_ptr++) {\n      T lambda1_v = lambda1_ptr[j];\n      T lambda2_v = lambda2_ptr[j];\n      T tmp = std::max(*data_ptr + lambda2_v, k_eps);\n      if (lambda1_v == 0) {\n        *output_ptr = std::log(tmp);\n      } else {\n        *output_ptr = (std::pow(tmp, lambda1_v) - 1) / lambda1_v;\n      }\n    }\n  }\n}\n\n#ifdef CAFFE2_USE_MKL\n\ntemplate <>\ntemplate <typename T>\nvoid BatchBoxCoxOp<CPUContext>::BoxCoxNonzeroLambda(\n    TIndex D,\n    const T* data_ptr,\n    const T* lambda1,\n    const T* lambda2,\n    T k_eps,\n    T* out) {\n  caffe2::math::Add(D, data_ptr, lambda2, out, &context_);\n  for (TIndex j = 0; j < D; j++) {\n    out[j] = std::max(out[j], k_eps);\n  }\n  Pow(D, out, lambda1, out);\n  for (TIndex j = 0; j < D; j++) {\n    out[j] -= 1.0;\n  }\n  caffe2::math::Div(D, out, lambda1, out, &context_);\n}\n\ntemplate <>\ntemplate <typename T>\nvoid BatchBoxCoxOp<CPUContext>::BoxCoxZeroLambda(\n    TIndex D,\n    const T* data_ptr,\n    const T* lambda2,\n    T k_eps,\n    T* output_ptr) {\n  caffe2::math::Add(D, data_ptr, lambda2, output_ptr, &context_);\n  for (TIndex j = 0; j < D; j++) {\n    output_ptr[j] = std::max(output_ptr[j], k_eps);\n  }\n  caffe2::math::Log(D, output_ptr, output_ptr, &context_);\n}\n\ntemplate <>\ntemplate <typename T>\nvoid BatchBoxCoxOp<CPUContext>::BoxCoxMixedLambda(\n    const T* data_ptr,\n    const vector<int>& nonzeros,\n    const vector<int>& zeros,\n    const T* lambda1,\n    const T* lambda2,\n    const T* lambda2_z,\n    T k_eps,\n    T* buffer,\n    T* output_ptr) {\n  PackV(nonzeros.size(), data_ptr, nonzeros.data(), buffer);\n  BoxCoxNonzeroLambda(nonzeros.size(), buffer, lambda1, lambda2, k_eps, buffer);\n  UnpackV(nonzeros.size(), buffer, output_ptr, nonzeros.data());\n\n  PackV(zeros.size(), data_ptr, zeros.data(), buffer);\n  BoxCoxZeroLambda(zeros.size(), buffer, lambda2_z, k_eps, buffer);\n  UnpackV(zeros.size(), buffer, output_ptr, zeros.data());\n}\n\n// Helpers to access cached buffers.\n#define DEFINE_CACHED_BUFFERS(T, tag)                                         \\\n  template <>                                                                 \\\n  template <>                                                                 \\\n  BatchBoxCoxOp<CPUContext>::TypedCachedBuffers<T>&                           \\\n  BatchBoxCoxOp<CPUContext>::GetBuffers<T>() {                                \\\n    if (!buffers_ || buffers_->type_ != tag) {                                \\\n      buffers_.reset(new BatchBoxCoxOp<CPUContext>::TypedCachedBuffers<T>()); \\\n      buffers_->type_ = tag;                                                  \\\n    }                                                                         \\\n    return *static_cast<TypedCachedBuffers<T>*>(buffers_.get());              \\\n  }\nDEFINE_CACHED_BUFFERS(float, 1);\nDEFINE_CACHED_BUFFERS(double, 2);\n#undef DEFINE_CACHED_BUFFERS\n\n#endif // CAFFE2_USE_MKL\n\nnamespace {\n\nREGISTER_CPU_OPERATOR(BatchBoxCox, BatchBoxCoxOp<CPUContext>);\nOPERATOR_SCHEMA(BatchBoxCox)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInput(0)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nInput `data` is a N * D matrix. Apply box-cox transform for each column.\n`lambda1` and `lambda2` is of size D that defines the hyper-parameters for\nthe transform of each column `x` of the input `data`:\n\n    ln(x + lambda2), if lambda1 == 0\n    ((x + lambda2)^lambda1 - 1)/lambda1, if lambda1 != 0\n\n)DOC\")\n    .Input(0, \"data\", \"input float or double N * D matrix\")\n    .Input(1, \"lambda1\", \"tensor of size D with the same type as data\")\n    .Input(2, \"lambda2\", \"tensor of size D with the same type as data\")\n    .Output(0, \"output\", \"output matrix that applied box-cox transform\");\n\nGRADIENT_NOT_IMPLEMENTED_YET(BatchBoxCox);\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/batch_box_cox_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE_OPERATORS_BATCH_BOX_COX_OPS_H_\n#define CAFFE_OPERATORS_BATCH_BOX_COX_OPS_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass BatchBoxCoxOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  BatchBoxCoxOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        min_block_size_(\n            OperatorBase::GetSingleArgument<int>(\"min_block_size\", 256)) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<float, double>>::call(this, Input(DATA));\n  }\n\n  template <typename T>\n  bool DoRunWithType();\n\n protected:\n  template <typename T>\n  void BoxCoxNaive(\n      TIndex N,\n      TIndex D,\n      const T* data_ptr,\n      const T* lambda1_ptr,\n      const T* lambda2_ptr,\n      T k_eps,\n      T* output_ptr);\n\n#ifdef CAFFE2_USE_MKL\n  template <typename T>\n  void BoxCoxNonzeroLambda(\n      TIndex D,\n      const T* data_ptr,\n      const T* lambda1,\n      const T* lambda2,\n      T k_eps,\n      T* output_ptr);\n\n  template <typename T>\n  void BoxCoxZeroLambda(\n      TIndex D,\n      const T* data_ptr,\n      const T* lambda2,\n      T k_eps,\n      T* output_ptr);\n\n  template <typename T>\n  void BoxCoxMixedLambda(\n      const T* data_ptr,\n      const vector<int>& nonzeros,\n      const vector<int>& zeros,\n      const T* lambda1,\n      const T* lambda2,\n      const T* lambda2_z,\n      T k_eps,\n      T* buffer,\n      T* output_ptr);\n\n  vector<int> nonzeros_, zeros_;\n\n  // Buffers used by the MKL version are cached across calls.\n  struct CachedBuffers {\n    virtual ~CachedBuffers() {}\n    int type_;\n  };\n  template <typename T>\n  struct TypedCachedBuffers : public CachedBuffers {\n    vector<T> lambda1_, lambda2_, lambda2_z_;\n    vector<T> accumulator_;\n  };\n  template <typename T>\n  TypedCachedBuffers<T>& GetBuffers();\n  unique_ptr<CachedBuffers> buffers_;\n\n#endif // CAFFE2_USE_MKL\n\n  int min_block_size_;\n\n  INPUT_TAGS(DATA, LAMBDA1, LAMBDA2);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE_OPERATORS_BATCH_BOX_COX_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/batch_gather_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/batch_gather_ops.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(BatchGather, BatchGatherOp<CPUContext>);\nREGISTER_CPU_OPERATOR(BatchGatherGradient, BatchGatherGradientOp<CPUContext>);\n\nOPERATOR_SCHEMA(BatchGather)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      vector<TensorShape> out(1);\n      ArgumentHelper helper(def);\n\n      vector<int> output_dims;\n      const auto& data_dims = GetDimsVector(in[0]);\n      const auto& indices_dims = GetDimsVector(in[1]);\n      output_dims.push_back(data_dims[0]);\n      output_dims.insert(\n          output_dims.end(), indices_dims.begin(), indices_dims.end());\n      output_dims.insert(\n          output_dims.end(), data_dims.begin() + 2, data_dims.end());\n\n      out[0] = CreateTensorShape(output_dims, TensorProto::FLOAT);\n      return out;\n    })\n    .SetDoc(R\"DOC(\nBatch gather operation, first dimension in DATA is the batch size.\nGiven DATA tensor of rank r >= 2, and INDICES tensor of rank q >= 1, gather\nentries of the outer-most dimension of DATA indexed by INDICES, and concatenate\nthem in an output tensor of rank (q - 1) + (r - 1).\n\nExample:\n  DATA  = [\n      [1.0, 1.2, 2.4, 4.5],\n      [2.3, 3.4, 3.6, 2.3],\n      [4.5, 5.7, 1.2, 4.5],\n  ]\n  INDICES = [\n      [0, 2],\n  ]\n  OUTPUT = [\n      [1.0, 2.4],\n      [2.3, 3.6],\n      [4.5, 1.2],\n  ]\n)DOC\")\n    .Input(0, \"DATA\", \"Tensor of rank r >= 2.\")\n    .Input(1, \"INDICES\", \"Tensor of int32/int64 indices, of any rank q.\")\n    .Output(0, \"OUTPUT\", \"Tensor of rank (q - 1) + (r - 1).\");\n\nOPERATOR_SCHEMA(BatchGatherGradient).NumInputs(3).NumOutputs(1);\n\nclass GetBatchGatherGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    using Op = BatchGatherOp<CPUContext>;\n    return SingleGradientDef(\n        \"BatchGatherGradient\",\n        \"\",\n        vector<string>{I(Op::DATA), I(Op::INDICES), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(BatchGather, GetBatchGatherGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/batch_gather_ops.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <fstream>\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/batch_gather_ops.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T_INDEX, typename TData>\n__global__ void BatchGatherKernel(\n    const TData* src_base,\n    TData* out,\n    const T_INDEX* indices,\n    const int M,\n    const int N,\n    const int data_batch_size,\n    const int gathered_batch_size,\n    const int block_size) {\n  const int begin_idx = blockIdx.x * blockDim.x + threadIdx.x;\n  const int num_items = M * N * block_size;\n  for (int s = begin_idx; s < num_items; s += blockDim.x * gridDim.x) {\n    const int k = s % block_size;\n    const int j = s / block_size % N;\n    const int i = s / block_size / N;\n    const T_INDEX idx = indices[j];\n    const float* src_offset = src_base + i * data_batch_size + idx * block_size;\n    float* dst_offset = out + i * gathered_batch_size + j * block_size;\n    dst_offset[k] = src_offset[k];\n  }\n}\n\ntemplate <>\nbool BatchGatherOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n      this, OperatorBase::Input<TensorCUDA>(INDICES));\n}\n\ntemplate <>\ntemplate <typename TInd>\nbool BatchGatherOp<CUDAContext>::DoRunWithType() {\n  auto& data = Input(DATA);\n  auto& indices = Input(INDICES);\n  auto* output = Output(0);\n\n  vector<TIndex> shape;\n  shape.push_back(data.dim(0));\n  shape.insert(shape.end(), indices.dims().begin(), indices.dims().end());\n  shape.insert(shape.end(), data.dims().begin() + 2, data.dims().end());\n  output->Resize(shape);\n\n  const int block_size = data.size_from_dim(2);\n  const int N = indices.size();\n  const auto data_batch_size = data.size_from_dim(1);\n  const auto gathered_batch_size = N * data.size_from_dim(2);\n  const TInd* idxs = indices.template data<TInd>();\n  auto src_base = static_cast<const float*>(data.raw_data());\n  auto out = static_cast<float*>(output->raw_mutable_data(data.meta()));\n  const int M = data.dim32(0);\n\n  BatchGatherKernel<<<\n      std::min(M, CAFFE_MAXIMUM_NUM_BLOCKS),\n      std::min(N * block_size, CAFFE_CUDA_NUM_THREADS),\n      0,\n      context_.cuda_stream()>>>(\n      src_base,\n      out,\n      idxs,\n      M,\n      N,\n      data_batch_size,\n      gathered_batch_size,\n      block_size);\n  return true;\n}\n\ntemplate <typename T_INDEX, typename TData>\n__global__ void BatchGatherGradientKernel(\n    const TData* grad_data,\n    TData* out,\n    const T_INDEX* indices,\n    const int M,\n    const int N,\n    const int data_batch_size,\n    const int gathered_batch_size,\n    const int block_size) {\n  int begin_idx = blockIdx.x * blockDim.x + threadIdx.x;\n  int num_items = M * N * block_size;\n  for (int s = begin_idx; s < num_items; s += blockDim.x * gridDim.x) {\n    const int k = s % block_size;\n    const int j = s / block_size % N;\n    const int i = s / block_size / N;\n    const T_INDEX idx = indices[j];\n    const float* src_offset =\n        grad_data + i * gathered_batch_size + j * block_size;\n    float* dst_offset = out + i * data_batch_size + idx * block_size;\n    atomicAdd(dst_offset + k, src_offset[k]);\n  }\n}\n\ntemplate <>\nbool BatchGatherGradientOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n      this, OperatorBase::Input<TensorCUDA>(INDICES));\n}\n\ntemplate <>\ntemplate <typename TInd>\nbool BatchGatherGradientOp<CUDAContext>::DoRunWithType() {\n  return DispatchHelper<\n      TensorTypes2<float, GenericTensorImplementation>,\n      TInd>::call(this, OperatorBase::Input<TensorCUDA>(DATA));\n}\n\ntemplate <>\ntemplate <typename TInd, typename TData>\nbool BatchGatherGradientOp<CUDAContext>::DoRunWithType2() {\n  auto& data = Input(DATA);\n  auto& indices = Input(INDICES);\n  auto& grad = Input(GRAD);\n  auto* output = Output(0);\n\n  CAFFE_ENFORCE_EQ(data.dim(0), grad.dim(0), \"batch sizes should be the same\");\n\n  output->ResizeLike(data);\n  auto* out_data = output->template mutable_data<float>();\n  math::Set<float, CUDAContext>(output->size(), 0, out_data, &context_);\n\n  const auto* grad_data = grad.template data<float>();\n\n  const int M = grad.dim32(0);\n  const int block_size = data.size_from_dim(2);\n  const int N = indices.size();\n  const auto data_batch_size = data.size_from_dim(1);\n  const auto gathered_batch_size = N * data.size_from_dim(2);\n  const TInd* idxs = indices.template data<TInd>();\n\n  BatchGatherGradientKernel<<<\n      std::min(M, CAFFE_MAXIMUM_NUM_BLOCKS),\n      std::min(N * block_size, CAFFE_CUDA_NUM_THREADS),\n      0,\n      context_.cuda_stream()>>>(\n      grad_data,\n      out_data,\n      idxs,\n      M,\n      N,\n      data_batch_size,\n      gathered_batch_size,\n      block_size);\n\n  return true;\n}\n\ntemplate <>\ntemplate <typename TInd>\nbool BatchGatherGradientOp<CUDAContext>::DoRunWithOtherType2() {\n  CAFFE_THROW(\n      \"BatchGatherGradient is not implemented on tensor of type \",\n      Input(DATA).meta().name(),\n      \"Consider adding it a type in the list DispatchHelper or implementing \"\n      \"a generic version (which won't work for duplicated indices though)\");\n}\n\nREGISTER_CUDA_OPERATOR(BatchGather, BatchGatherOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(BatchGatherGradient, BatchGatherGradientOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/batch_gather_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_BATCH_GATHER_OPS_H_\n#define CAFFE2_OPERATORS_BATCH_GATHER_OPS_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass BatchGatherOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(BatchGatherOp)\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, OperatorBase::Input<TensorCPU>(INDICES));\n  }\n\n  template <typename TInd>\n  bool DoRunWithType() {\n    auto& data = Input(DATA);\n    auto& indices = Input(INDICES);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_GE(data.ndim(), 2, \"DATA should be at least 2-D\");\n\n    vector<TIndex> shape;\n    shape.push_back(data.dim(0));\n    shape.insert(shape.end(), indices.dims().begin(), indices.dims().end());\n    shape.insert(shape.end(), data.dims().begin() + 2, data.dims().end());\n    output->Resize(shape);\n\n    auto block_size = data.size_from_dim(2);\n    auto block_bytesize = block_size * data.meta().itemsize();\n    auto N = indices.size();\n    auto data_batch_bytesize = data.size_from_dim(1) * data.meta().itemsize();\n    auto gathered_batch_bytesize =\n        N * data.size_from_dim(2) * data.meta().itemsize();\n    const TInd* idxs = indices.template data<TInd>();\n    auto src_base = static_cast<const char*>(data.raw_data());\n    auto out = static_cast<char*>(output->raw_mutable_data(data.meta()));\n\n    for (auto batch = 0; batch < data.dim(0); ++batch) {\n      for (auto i = 0; i < N; ++i) {\n        auto idx = idxs[i];\n        CAFFE_ENFORCE(\n            0 <= idx && idx < data.dim(1),\n            \"INDICES element is out of DATA bounds, id=\",\n            idx,\n            \" data_dim=\",\n            data.dim(1));\n        auto src =\n            src_base + idx * block_bytesize + batch * data_batch_bytesize;\n        auto dst = out + i * block_bytesize + batch * gathered_batch_bytesize;\n        context_.template CopyItems<Context, Context>(\n            data.meta(), block_size, src, dst);\n      }\n    }\n    return true;\n  }\n\n  INPUT_TAGS(DATA, INDICES);\n};\n\ntemplate <class Context>\nclass BatchGatherGradientOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(BatchGatherGradientOp);\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, OperatorBase::Input<TensorCPU>(INDICES));\n  }\n\n  template <typename TInd>\n  bool DoRunWithType() {\n    return DispatchHelper<\n        TensorTypes2<float, GenericTensorImplementation>,\n        TInd>::call(this, Input(DATA));\n  }\n\n  template <typename TInd, typename TData>\n  bool DoRunWithType2() {\n    auto& data = Input(DATA);\n    auto& indices = Input(INDICES);\n    auto& grad = Input(GRAD);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_GE(data.ndim(), 2, \"DATA should be at least 2-D\");\n    CAFFE_ENFORCE_EQ(\n        data.dim(0), grad.dim(0), \"batch sizes should be the same\");\n\n    output->ResizeLike(data);\n    TData* out_data = output->template mutable_data<TData>();\n    if (data.size() <= 0) {\n      return true;\n    }\n\n    memset(out_data, 0, output->nbytes());\n\n    const TData* grad_data = grad.template data<TData>();\n\n    auto block_size = data.size_from_dim(2);\n    auto N = indices.size();\n    auto data_batch_size = data.size_from_dim(1);\n    auto gathered_batch_size = N * data.size_from_dim(2);\n    const TInd* idxs = indices.template data<TInd>();\n\n    for (auto batch = 0; batch < grad.dim(0); ++batch) {\n      for (auto i = 0; i < N; ++i) {\n        auto idx = idxs[i];\n        CAFFE_ENFORCE(\n            0 <= idx && idx < data.dim(1),\n            \"INDICES element is out of DATA bounds, id=\",\n            idx,\n            \" data_dim=\",\n            data.dim(1));\n        math::Add(\n            block_size,\n            out_data + idx * block_size + batch * data_batch_size,\n            grad_data + i * block_size + batch * gathered_batch_size,\n            out_data + idx * block_size + batch * data_batch_size,\n            &context_);\n      }\n    }\n    return true;\n  }\n\n  template <typename TInd>\n  bool DoRunWithOtherType2() {\n    CAFFE_THROW(\n        \"BatchGatherGradient is not implemented on tensor of type \",\n        Input(DATA).meta().name(),\n        \"Consider adding it a type in the list DispatchHelper or implementing \"\n        \"a generic version (which won't work for duplicated indices though)\");\n  }\n\n  INPUT_TAGS(DATA, INDICES, GRAD);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_BATCH_GATHER_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/batch_matmul_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/batch_matmul_op.h\"\n#include \"caffe2/core/operator_schema.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(BatchMatMul, BatchMatMulOp<CPUContext>);\n\nOPERATOR_SCHEMA(BatchMatMul)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nBatch Matrix multiplication Yi = Ai * Bi, where A has shape (dim0, dim1, ... M, K),\nB has shape (dim0, dim1, ... K, N), Y has shape (dim0, dim1, ... M, N) and i ranges\nfrom 0 to (dim0 * dim1 ...) - 1. rank(A) == rank(B) >= 2. In case of A and B being\ntwo diemnsional, it behaves like normal matrix multiplication.\n)DOC\")\n    .Input(0, \"A\", \"tensor of shape (dim0, dim1 ... M, K)\")\n    .Input(1, \"B\", \"tensor of shpae (dim0, dim2 ... K, N)\")\n    .Output(0, \"Y\", \"tensor of shape (dim0, dim1 ... M, N)\")\n    .Arg(\n        \"trans_a\",\n        \"Pass 1 to transpose the last two dimensions of A before \"\n        \"doing multiplication\")\n    .Arg(\n        \"trans_b\",\n        \"Pass 1 to transpose the last two dimensions of B before \"\n        \"doing multiplication\")\n    .Arg(\n        \"broadcast\",\n        \"Pass 1 to allow broadcasting of dimensions. Behavior is the same as numpy.matmul. Gradient is currently not supported when running in broadcast mode.\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      ArgumentHelper helper(def);\n      bool broadcast = helper.GetSingleArgument<int>(\"broadcast\", 0);\n      if (!broadcast) {\n        const auto ndim = in[0].dims_size();\n        CAFFE_ENFORCE_GE(ndim, 2);\n        int a_dim0;\n        int b_dim1;\n        if (helper.GetSingleArgument<int>(\"trans_a\", 0)) {\n          a_dim0 = in[0].dims(ndim - 1);\n        } else {\n          a_dim0 = in[0].dims(ndim - 2);\n        }\n\n        if (helper.GetSingleArgument<int>(\"trans_b\", 0)) {\n          b_dim1 = in[1].dims(ndim - 2);\n        } else {\n          b_dim1 = in[1].dims(ndim - 1);\n        }\n\n        auto output_dims =\n            vector<TIndex>{in[0].dims().begin(), in[0].dims().end()};\n        output_dims[ndim - 2] = a_dim0;\n        output_dims[ndim - 1] = b_dim1;\n\n        return vector<TensorShape>{\n            CreateTensorShape(vector<TIndex>{output_dims}, in[0].data_type())};\n      } else {\n        auto ndims_A = in[0].dims_size();\n        auto ndims_B = in[1].dims_size();\n        std::vector<TIndex> dims_A(ndims_A), dims_B(ndims_B);\n        for (int i = 0; i < ndims_A; ++i) {\n          dims_A[i] = in[0].dims(i);\n        }\n        for (int i = 0; i < ndims_B; ++i) {\n          dims_B[i] = in[1].dims(i);\n        }\n        bool A_broadcasted = false, B_broadcasted = false;\n        if (ndims_A == 1) {\n          dims_A.insert(dims_A.begin(), 1);\n          ndims_A = 2;\n          A_broadcasted = true;\n        }\n        if (ndims_B == 1) {\n          dims_B.push_back(1);\n          ndims_B = 2;\n          B_broadcasted = true;\n        }\n        size_t M, N, K, K_dim;\n        if (helper.GetSingleArgument<int>(\"trans_a\", 0)) {\n          M = dims_A[ndims_A - 1];\n          K = dims_A[ndims_A - 2];\n          K_dim = ndims_A - 2;\n        } else {\n          M = dims_A[ndims_A - 2];\n          K = dims_A[ndims_A - 1];\n          K_dim = ndims_A - 1;\n        }\n        if (helper.GetSingleArgument<int>(\"trans_b\", 0)) {\n          N = dims_B[ndims_B - 2];\n        } else {\n          N = dims_B[ndims_B - 1];\n        }\n\n        std::vector<TIndex> new_dims;\n        if (ndims_A >= ndims_B) {\n          new_dims.assign(dims_A.begin(), dims_A.end() - 2);\n        } else {\n          new_dims.assign(dims_B.begin(), dims_B.end() - 2);\n        }\n        if (!A_broadcasted) {\n          new_dims.push_back(M);\n        }\n        if (!B_broadcasted) {\n          new_dims.push_back(N);\n        }\n        if (A_broadcasted && B_broadcasted) {\n          new_dims.push_back(1);\n        }\n        return vector<TensorShape>{\n            CreateTensorShape(vector<TIndex>{new_dims}, in[0].data_type())};\n      }\n    });\n\nclass GetBatchMatMulGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE_EQ(def_.input_size(), 2);\n\n    bool broadcast = false;\n    if (ArgumentHelper::HasArgument(Def(), \"broadcast\")) {\n      broadcast = GetArgument(Def(), \"broadcast\").i();\n    }\n    CAFFE_ENFORCE(\n        !broadcast,\n        \"Gradient is currently not supported with \"\n        \"broadcast=1 for BatchMatMul.\");\n\n    bool trans_a = 0;\n    bool trans_b = 0;\n\n    if (ArgumentHelper::HasArgument(Def(), \"trans_a\")) {\n      trans_a = GetArgument(Def(), \"trans_a\").i();\n    }\n    if (ArgumentHelper::HasArgument(Def(), \"trans_b\")) {\n      trans_b = GetArgument(Def(), \"trans_b\").i();\n    }\n\n    auto no_trans_arg = vector<Argument>();\n    auto trans_a_arg = vector<Argument>{MakeArgument<int>(\"trans_a\", 1)};\n    auto trans_b_arg = vector<Argument>{MakeArgument<int>(\"trans_b\", 1)};\n    auto trans_both_arg = vector<Argument>{MakeArgument<int>(\"trans_a\", 1),\n                                           MakeArgument<int>(\"trans_b\", 1)};\n\n    if (ArgumentHelper::HasArgument(Def(), \"use_scratch\")) {\n      no_trans_arg.push_back(MakeArgument<int>(\"use_scratch\", 1));\n      trans_a_arg.push_back(MakeArgument<int>(\"use_scratch\", 1));\n      trans_b_arg.push_back(MakeArgument<int>(\"use_scratch\", 1));\n      trans_both_arg.push_back(MakeArgument<int>(\"use_scratch\", 1));\n    }\n\n    if (trans_a) {\n      if (trans_b) {\n        // A'B':\n        // dA = B'G', dB = G'A'\n        return vector<OperatorDef>{CreateOperatorDef(\n                                       \"BatchMatMul\",\n                                       \"\",\n                                       vector<string>{I(1), GO(0)},\n                                       vector<string>{GI(0)},\n                                       trans_both_arg),\n                                   CreateOperatorDef(\n                                       \"BatchMatMul\",\n                                       \"\",\n                                       vector<string>{GO(0), I(0)},\n                                       vector<string>{GI(1)},\n                                       trans_both_arg)};\n      } else {\n        // A'B:\n        // dA = BG', dB = AG\n        return vector<OperatorDef>{CreateOperatorDef(\n                                       \"BatchMatMul\",\n                                       \"\",\n                                       vector<string>{I(1), GO(0)},\n                                       vector<string>{GI(0)},\n                                       trans_b_arg),\n                                   CreateOperatorDef(\n                                       \"BatchMatMul\",\n                                       \"\",\n                                       vector<string>{I(0), GO(0)},\n                                       vector<string>{GI(1)},\n                                       no_trans_arg)};\n      }\n    } else {\n      if (trans_b) {\n        // AB':\n        // dA = GB, dB = G'A\n        return vector<OperatorDef>{CreateOperatorDef(\n                                       \"BatchMatMul\",\n                                       \"\",\n                                       vector<string>{GO(0), I(1)},\n                                       vector<string>{GI(0)},\n                                       no_trans_arg),\n                                   CreateOperatorDef(\n                                       \"BatchMatMul\",\n                                       \"\",\n                                       vector<string>{GO(0), I(0)},\n                                       vector<string>{GI(1)},\n                                       trans_a_arg)};\n      } else {\n        // AB:\n        // dA = GB', dB = A'G\n        return vector<OperatorDef>{CreateOperatorDef(\n                                       \"BatchMatMul\",\n                                       \"\",\n                                       vector<string>{GO(0), I(1)},\n                                       vector<string>{GI(0)},\n                                       trans_b_arg),\n                                   CreateOperatorDef(\n                                       \"BatchMatMul\",\n                                       \"\",\n                                       vector<string>{I(0), GO(0)},\n                                       vector<string>{GI(1)},\n                                       trans_a_arg)};\n      }\n    }\n  }\n\n  bool CopyArguments() const override {\n    return false;\n  }\n};\n\nREGISTER_GRADIENT(BatchMatMul, GetBatchMatMulGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/batch_matmul_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/batch_matmul_op.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool BatchMatMulOp<CUDAContext, DefaultEngine>::RunOnDevice() {\n    return DispatchHelper<TensorTypes<float, float16>>::call(this, Input(0));\n}\n\nREGISTER_CUDA_OPERATOR(BatchMatMul, BatchMatMulOp<CUDAContext>);\n\n#if CUDA_VERSION >= 9000\n\ntemplate <>\nbool BatchMatMulOp<CUDAContext, TensorCoreEngine>::RunOnDevice() {\n    return DispatchHelper<TensorTypes<float, float16>>::call(this, Input(0));\n}\n\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(\n    BatchMatMul,\n    TENSORCORE,\n    BatchMatMulOp<CUDAContext, TensorCoreEngine>);\n#endif\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/batch_matmul_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef CAFFE2_OPERATORS_MATMUL_OP_H_\n#define CAFFE2_OPERATORS_MATMUL_OP_H_\n\n#include <sstream>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context, class Engine = DefaultEngine>\nclass BatchMatMulOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  BatchMatMulOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        trans_a_(OperatorBase::GetSingleArgument<int>(\"trans_a\", 0)),\n        trans_b_(OperatorBase::GetSingleArgument<int>(\"trans_b\", 0)),\n        broadcast_(OperatorBase::GetSingleArgument<int>(\"broadcast\", 0)),\n        use_scratch_(OperatorBase::GetSingleArgument<int>(\"use_scratch\", 0)) {\n    if (use_scratch_) {\n      scratch_ = std::make_shared<Tensor<Context>>();\n    }\n  }\n\n  ~BatchMatMulOp() {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<float>>::call(this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    const auto& A = Input(0);\n    const auto& B = Input(1);\n    auto* Y = Output(0);\n\n    auto ndims_A = A.ndim();\n    auto dims_A = A.dims();\n    auto ndims_B = B.ndim();\n    auto dims_B = B.dims();\n\n    auto noBroadcastErrorMsg = [](size_t dim1, size_t dim2) {\n      std::stringstream ss;\n      ss << \"Inputs with dimensions A = \";\n      ss << dim1;\n      ss << \" and B = \";\n      ss << dim2;\n      ss << \" is not supported with broadcast=0. Did you forget to set the \"\n            \"broadcast flag?\";\n      return ss.str();\n    };\n\n    // These should all be false if we're not broadcasting.\n    bool dimMismatch = ndims_A != ndims_B;\n    bool dimsLessThan1D = ndims_A < 2;\n    CAFFE_ENFORCE(\n        broadcast_ || (!dimMismatch && !dimsLessThan1D),\n        noBroadcastErrorMsg(ndims_A, ndims_B));\n\n    auto* data_A = A.template data<T>();\n    auto* data_B = B.template data<T>();\n\n    auto dimMismatchErrorString = [](size_t dimnum1,\n                                     size_t dim1,\n                                     size_t dimnum2,\n                                     size_t dim2,\n                                     bool trans_a,\n                                     bool trans_b) {\n      std::stringstream ss;\n      ss << \"Expected dimension \";\n      ss << dimnum1;\n      ss << \" of tensor A with value \";\n      ss << dim1;\n      ss << \" to match dimension \";\n      ss << dimnum2;\n      ss << \" of tensor B with value \";\n      ss << dim2;\n      ss << \". trans_a = \";\n      ss << trans_a;\n      ss << \" trans_b = \";\n      ss << trans_b;\n      return ss.str();\n    };\n\n    if (ndims_A == 1 && ndims_B == 1) {\n      // vector-vector\n      CAFFE_ENFORCE_EQ(\n          dims_A[0],\n          dims_B[0],\n          \"Vector-vector product requires each of the vectors to \"\n          \"be the same size.\");\n      Y->Resize(1);\n      math::Dot<T, Context>(\n          dims_A[0], data_A, data_B, Y->template mutable_data<T>(), &context_);\n    } else {\n      bool A_broadcasted = false, B_broadcasted = false;\n      if (ndims_A == 1) {\n        dims_A.insert(dims_A.begin(), 1);\n        ndims_A = 2;\n        A_broadcasted = true;\n      }\n      if (ndims_B == 1) {\n        dims_B.push_back(1);\n        ndims_B = 2;\n        B_broadcasted = true;\n      }\n      // matrix-matrix with batches\n      // [B1..., M, K] * [B2..., K, N] -> [B..., M, N]\n      // In the event that A or B are one-dimensional, the trailing or leading\n      // 1 is not added to the output tensor's size.\n\n      // First step: partition the tensors into inner and outer blocks.\n      // Ignoring the last two dimensions of A and B, ensure that one of the\n      // tensors' dimensions is a suffix of the other. For example,\n      // [4, x, x] is a suffix of [2, 3, 4, x, x]. In this example, the\n      // dimensions of size 2 and 3 will be broadcasted, so we partition into\n      // 2*3=6 individual instances of batched GEMM with A and B \\in [4, x, x].\n      size_t num_inner_dims = std::min(ndims_A, ndims_B);\n      for (size_t i = 2; i < num_inner_dims; ++i) {\n        auto first_r_itr = dims_A.rbegin();\n        auto second_r_itr = dims_B.rbegin();\n        CAFFE_ENFORCE_EQ(\n            *(first_r_itr + i),\n            *(second_r_itr + i),\n            dimMismatchErrorString(\n                ndims_A - i - 1,\n                *(first_r_itr + i),\n                ndims_B - i - 1,\n                *(second_r_itr + i),\n                trans_a_,\n                trans_b_));\n      }\n      size_t num_outer_dims = std::max(ndims_A, ndims_B) - num_inner_dims;\n\n      // Standard M, N, and K parameters respecting GEMM API and transpose\n      // flags\n      size_t M, N, K, K_dim;\n      if (trans_a_) {\n        M = dims_A[ndims_A - 1];\n        K = dims_A[ndims_A - 2];\n        K_dim = ndims_A - 2;\n      } else {\n        M = dims_A[ndims_A - 2];\n        K = dims_A[ndims_A - 1];\n        K_dim = ndims_A - 1;\n      }\n      if (trans_b_) {\n        N = dims_B[ndims_B - 2];\n        CAFFE_ENFORCE_EQ(\n            K,\n            dims_B[ndims_B - 1],\n            dimMismatchErrorString(\n                K_dim,\n                K,\n                ndims_B - 1,\n                dims_B[ndims_B - 1],\n                trans_a_,\n                trans_b_));\n      } else {\n        N = dims_B[ndims_B - 1];\n        CAFFE_ENFORCE_EQ(\n            K,\n            dims_B[ndims_B - 2],\n            dimMismatchErrorString(\n                K_dim,\n                K,\n                ndims_B - 2,\n                dims_B[ndims_B - 2],\n                trans_a_,\n                trans_b_));\n      }\n\n      // Calculate output tensor shapes [B..., (M), (N)]\n      // Batch dimensions will be broadcasted out to those of the longer tensor\n      // A or B. Either M or N are optional if A or B, respectively are 1-D.\n      std::vector<TIndex> new_dims;\n      if (ndims_A >= ndims_B) {\n        new_dims.assign(dims_A.begin(), dims_A.end() - 2);\n      } else {\n        new_dims.assign(dims_B.begin(), dims_B.end() - 2);\n      }\n      if (!A_broadcasted) {\n        new_dims.push_back(M);\n      } else {\n        new_dims.push_back(1);\n      }\n      if (!B_broadcasted) {\n        new_dims.push_back(N);\n      } else {\n        new_dims.push_back(1);\n      }\n\n      // Calculate strides. Continuing our example above,\n      //   [4, M, K] * [2, 3, 4, K, N] = [2, 3, 4, M, N]\n      // We calculate this as follows:\n      //   1) Treat the outer batch dimensions as flattened, i.e. view the B\n      //      tensor here as [6, 4, K, N] and Y as [6, 4, M, N]. The same rea-\n      //      soning is analogous for the case where # dims A >= # dims B.\n      //   2) Perform this operation:\n      //        for i in range(6):\n      //          Y[i, :, :, :] = BatchMatMul(A, B[i, :, :, :])\n      size_t A_stride = 1; // How far to increment A pointer each itr\n      size_t B_stride = 1; // How far to increment B pointer each itr\n      size_t Y_stride = 1; // How far to increment Y pointer each itr\n      // How many \"inner batches\" we have. That is, the product of sizes for\n      // the slices excluding M, K, and N, for their respective matrices.\n      size_t num_sub_batches = 1;\n      if (ndims_A >= ndims_B) {\n        auto first_r_itr = dims_A.rbegin();\n        auto output_r_itr = new_dims.rbegin();\n        for (size_t i = 0; i < num_inner_dims; ++i) {\n          A_stride *= *(first_r_itr + i);\n          Y_stride *= *(output_r_itr + i);\n          if (i >= 2) {\n            num_sub_batches *= *(first_r_itr + i);\n          }\n        }\n        B_stride = 0;\n      } else {\n        A_stride = 0;\n        auto second_r_itr = dims_B.rbegin();\n        auto output_r_itr = new_dims.rbegin();\n        for (size_t i = 0; i < num_inner_dims; ++i) {\n          B_stride *= *(second_r_itr + i);\n          Y_stride *= *(output_r_itr + i);\n          if (i >= 2) {\n            num_sub_batches *= *(second_r_itr + i);\n          }\n        }\n      }\n\n      size_t num_outer_batches = 1;\n      for (size_t i = 0; i < num_outer_dims; ++i) {\n        num_outer_batches *= new_dims[i];\n      }\n\n      // Mutually exclusive since otherwise we would've taken the vector-vector\n      // path above\n      if (A_broadcasted) {\n        new_dims.erase(new_dims.end() - 2);\n      } else if (B_broadcasted) {\n        new_dims.erase(new_dims.end() - 1);\n      }\n\n      // Allocate output tensor\n      Y->Resize(new_dims);\n      auto* Y_data = Y->template mutable_data<T>();\n\n      // Zero batch dimension indicates no elements\n      if (num_sub_batches == 0 || num_outer_batches == 0) {\n        return true;\n      }\n\n      // TODO(T23893772): doing this in a loop is likely going to be slow on GPU\n      for (size_t p = 0; p < num_outer_batches; ++p) {\n        math::GemmBatched<T, Context, Engine>(\n            trans_a_ ? CblasTrans : CblasNoTrans,\n            trans_b_ ? CblasTrans : CblasNoTrans,\n            num_sub_batches,\n            M,\n            N,\n            K,\n            1.0f,\n            data_A + p * A_stride,\n            data_B + p * B_stride,\n            0.0f,\n            Y_data + p * Y_stride,\n            &context_,\n            use_scratch_ ? scratch_.get() : nullptr);\n      }\n    }\n    return true;\n  }\n\n protected:\n  bool trans_a_;\n  bool trans_b_;\n  bool broadcast_;\n\n  bool use_scratch_;\n  std::shared_ptr<Tensor<Context>> scratch_;\n};\n\n} // namespace caffe2\n\n#endif /* CAFFE2_OPERATORS_MATMUL_OP_H_ */\n"
  },
  {
    "path": "caffe2/operators/batch_matmul_op_gpu_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <memory>\n#include <vector>\n\n#include <gtest/gtest.h>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/batch_matmul_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\nclass BatchMatMulOpGPUTest : public testing::Test {\n protected:\n  void SetUp() override {\n    if (!HasCudaGPU()) {\n      return;\n    }\n    option_.set_device_type(CUDA);\n    cuda_context_ = make_unique<CUDAContext>(option_);\n    def_.set_name(\"test\");\n    def_.set_type(\"BatchMatMul\");\n    def_.add_input(\"A\");\n    def_.add_input(\"B\");\n    def_.add_output(\"Y\");\n    def_.mutable_device_option()->set_device_type(CUDA);\n  }\n\n  void AddConstInput(\n      const std::vector<TIndex>& dims,\n      const float value,\n      const string& name) {\n    Blob* blob = ws_.CreateBlob(name);\n    auto* tensor = blob->GetMutable<Tensor<CUDAContext>>();\n    tensor->Resize(dims);\n    math::Set<float, CUDAContext>(\n        tensor->size(),\n        value,\n        tensor->mutable_data<float>(),\n        cuda_context_.get());\n  }\n\n  void VerifyOutput(const std::vector<TIndex>& dims, const float value) const {\n    const Blob* Y_blob = ws_.GetBlob(\"Y\");\n    ASSERT_NE(nullptr, Y_blob);\n    const auto& Y = Y_blob->Get<Tensor<CUDAContext>>();\n    TensorCPU Y_cpu(Y);\n    const auto& Y_dims = Y_cpu.dims();\n    ASSERT_EQ(dims.size(), Y_dims.size());\n    for (std::size_t i = 0; i < dims.size(); ++i) {\n      ASSERT_EQ(dims[i], Y_dims[i]);\n    }\n    for (int i = 0; i < Y_cpu.size(); ++i) {\n      EXPECT_FLOAT_EQ(value, Y_cpu.data<float>()[i]);\n    }\n  }\n\n  DeviceOption option_;\n  std::unique_ptr<CUDAContext> cuda_context_;\n  Workspace ws_;\n  OperatorDef def_;\n};\n\nTEST_F(BatchMatMulOpGPUTest, BatchMatMulOpGPUNormalTest) {\n  if (!HasCudaGPU()) {\n    return;\n  }\n  AddConstInput(std::vector<TIndex>{3, 5, 10}, 1.0f, \"A\");\n  AddConstInput(std::vector<TIndex>{3, 10, 6}, 1.0f, \"B\");\n  std::unique_ptr<OperatorBase> op(CreateOperator(def_, &ws_));\n  ASSERT_NE(nullptr, op);\n  ASSERT_TRUE(op->Run());\n  VerifyOutput(std::vector<TIndex>{3, 5, 6}, 10.0f);\n}\n\nTEST_F(BatchMatMulOpGPUTest, BatchMatMulOpGPUBroadcastTest) {\n  if (!HasCudaGPU()) {\n    return;\n  }\n  auto* arg = def_.add_arg();\n  arg->set_name(\"broadcast\");\n  arg->set_i(1);\n  AddConstInput(std::vector<TIndex>{3, 5, 10}, 1.0f, \"A\");\n  AddConstInput(std::vector<TIndex>{2, 3, 10, 6}, 1.0f, \"B\");\n  std::unique_ptr<OperatorBase> op(CreateOperator(def_, &ws_));\n  ASSERT_NE(nullptr, op);\n  ASSERT_TRUE(op->Run());\n  VerifyOutput(std::vector<TIndex>{2, 3, 5, 6}, 10.0f);\n}\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/batch_matmul_op_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <memory>\n#include <vector>\n\n#include <gtest/gtest.h>\n\n#include \"caffe2/operators/batch_matmul_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\nclass BatchMatMulOpTest : public testing::Test {\n protected:\n  void SetUp() override {\n    cpu_context_ = make_unique<CPUContext>(option_);\n    def_.set_name(\"test\");\n    def_.set_type(\"BatchMatMul\");\n    def_.add_input(\"A\");\n    def_.add_input(\"B\");\n    def_.add_output(\"Y\");\n  }\n\n  void AddConstInput(\n      const std::vector<TIndex>& dims,\n      const float value,\n      const string& name) {\n    Blob* blob = ws_.CreateBlob(name);\n    auto* tensor = blob->GetMutable<TensorCPU>();\n    tensor->Resize(dims);\n    math::Set<float, CPUContext>(\n        tensor->size(),\n        value,\n        tensor->mutable_data<float>(),\n        cpu_context_.get());\n  }\n\n  void VerifyOutput(const std::vector<TIndex>& dims, const float value) const {\n    const Blob* Y_blob = ws_.GetBlob(\"Y\");\n    ASSERT_NE(nullptr, Y_blob);\n    const auto& Y = Y_blob->Get<TensorCPU>();\n    const auto& Y_dims = Y.dims();\n    ASSERT_EQ(dims.size(), Y_dims.size());\n    for (std::size_t i = 0; i < dims.size(); ++i) {\n      ASSERT_EQ(dims[i], Y_dims[i]);\n    }\n    for (int i = 0; i < Y.size(); ++i) {\n      EXPECT_FLOAT_EQ(value, Y.data<float>()[i]);\n    }\n  }\n\n  DeviceOption option_;\n  std::unique_ptr<CPUContext> cpu_context_;\n  Workspace ws_;\n  OperatorDef def_;\n};\n\nTEST_F(BatchMatMulOpTest, BatchMatMulOpNormalTest) {\n  AddConstInput(std::vector<TIndex>{3, 5, 10}, 1.0f, \"A\");\n  AddConstInput(std::vector<TIndex>{3, 10, 6}, 1.0f, \"B\");\n  std::unique_ptr<OperatorBase> op(CreateOperator(def_, &ws_));\n  ASSERT_NE(nullptr, op);\n  ASSERT_TRUE(op->Run());\n  VerifyOutput(std::vector<TIndex>{3, 5, 6}, 10.0f);\n}\n\nTEST_F(BatchMatMulOpTest, BatchMatMulOpBroadcastTest) {\n  auto* arg = def_.add_arg();\n  arg->set_name(\"broadcast\");\n  arg->set_i(1);\n  AddConstInput(std::vector<TIndex>{3, 5, 10}, 1.0f, \"A\");\n  AddConstInput(std::vector<TIndex>{2, 3, 10, 6}, 1.0f, \"B\");\n  std::unique_ptr<OperatorBase> op(CreateOperator(def_, &ws_));\n  ASSERT_NE(nullptr, op);\n  ASSERT_TRUE(op->Run());\n  VerifyOutput(std::vector<TIndex>{2, 3, 5, 6}, 10.0f);\n}\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/batch_sparse_to_dense_op.cc",
    "content": "#include \"batch_sparse_to_dense_op.h\"\n\n#include \"caffe2/core/context.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nbool BatchSparseToDenseOp<T, Context>::RunOnDevice() {\n  auto& lengths = Input(LENGTHS);\n  auto& indices = Input(INDICES);\n  auto& values = Input(VALUES);\n  auto* output = Output(0);\n  CAFFE_ENFORCE_EQ(indices.size(), values.size());\n  CAFFE_ENFORCE_EQ(lengths.ndim(), 1);\n  CAFFE_ENFORCE_EQ(indices.ndim(), 1);\n\n  const TIndex* lengths_data = lengths.template data<TIndex>();\n  const TIndex* indices_data = indices.template data<TIndex>();\n  const T* values_data = values.template data<T>();\n  TIndex batch_size = lengths.size();\n  TIndex lengths_sum = 0;\n  math::Sum<TIndex, Context>(batch_size, lengths_data, &lengths_sum, &context_);\n  CAFFE_ENFORCE_EQ(lengths_sum, indices.size());\n\n  vector<TIndex> output_shape = {batch_size};\n  if (InputSize() == 4) {\n    auto& shaper = Input(3);\n    CAFFE_ENFORCE_EQ(shaper.ndim(), 2);\n    if (dense_last_dim_ == -1) {\n      dense_last_dim_ = shaper.dim(1);\n    } else {\n      CAFFE_ENFORCE(\n          dense_last_dim_ == shaper.dim(1),\n          \"The last dim argument is not aligned with the shape input last dim\");\n    }\n  } else {\n    CAFFE_ENFORCE(dense_last_dim_ >= 1, \"The last dim of dense must be >= 1\");\n  }\n  output_shape.push_back(dense_last_dim_);\n  output->Resize(output_shape);\n  T* output_data = output->template mutable_data<T>();\n  math::Set(\n      output->size(), static_cast<T>(default_value_), output_data, &context_);\n\n  TIndex k = 0;\n  for (TIndex i = 0; i < batch_size; ++i) {\n    for (TIndex j = 0; j < lengths_data[i]; ++j) {\n      CAFFE_ENFORCE(\n          indices_data[k] < dense_last_dim_,\n          \"An indice (\",\n          indices_data[k],\n          \") is larger then last dim of dense (\",\n          dense_last_dim_,\n          \").\");\n      output_data[i * dense_last_dim_ + indices_data[k]] = values_data[k];\n      k += 1;\n    }\n  }\n\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool BatchDenseToSparseOp<T, Context>::RunOnDevice() {\n  auto& lengths = Input(LENGTHS);\n  auto& indices = Input(INDICES);\n  auto& dense = Input(DENSE);\n  auto* output = Output(0);\n  CAFFE_ENFORCE_EQ(lengths.ndim(), 1);\n  CAFFE_ENFORCE_EQ(indices.ndim(), 1);\n  CAFFE_ENFORCE_EQ(dense.ndim(), 2);\n  const TIndex* lengths_data = lengths.template data<TIndex>();\n  const TIndex* indices_data = indices.template data<TIndex>();\n  const T* dense_data = dense.template data<T>();\n\n  TIndex batch_size = lengths.size();\n  TIndex lengths_sum = 0;\n  math::Sum<TIndex, Context>(batch_size, lengths_data, &lengths_sum, &context_);\n  CAFFE_ENFORCE_EQ(lengths_sum, indices.size());\n\n  CAFFE_ENFORCE_EQ(batch_size, dense.dim(0));\n  dense_last_dim_ = dense.dim(1);\n  vector<TIndex> output_shape = indices.dims();\n  output->Resize(output_shape);\n  T* output_data = output->template mutable_data<T>();\n\n  TIndex k = 0;\n  for (TIndex i = 0; i < batch_size; ++i) {\n    for (TIndex j = 0; j < lengths_data[i]; ++j) {\n      CAFFE_ENFORCE(\n          indices_data[k] < dense.dim(1),\n          \"An indice (\",\n          indices_data[k],\n          \") is larger then last dim of dense (\",\n          dense.dim(1),\n          \").\");\n      output_data[k] = dense_data[i * dense.dim(1) + indices_data[k]];\n      k += 1;\n    }\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(\n    BatchSparseToDense,\n    BatchSparseToDenseOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(BatchSparseToDense)\n    .NumInputs(3, 4)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nConvert sparse matrix representation into dense matrix.\n\nA sparse matrix is represented by `lengths` vector, `indices` vector,\nand `values` vector. Each element in `lengths` vector (lengths[`i`]) represents\nthe number of indices in this batch (batch `i`).\nWith in each batch, `indices` should not have duplicate number.\n\nFor example, with input:\n\n  lengths = [2, 3, 1]\n  indices = [0, 1, 2, 3, 4, 5]\n  values =  [6, 7, 8, 9, 10, 11]\n  dense_dim = 6\n  default_value = 0\n\nThe output is:\n\n  output = [[6, 7, 0, 0, 0,  0],\n            [0, 0, 8, 9, 10, 0],\n            [0, 0, 0, 0, 0, 11]]\n\nafter running this operator.\n)DOC\")\n    .Input(\n        0,\n        \"lengths\",\n        \"Flatten tensor, used to break down indices and values into per batch indices and values.\")\n    .Input(\n        1,\n        \"indices\",\n        \"Flatten tensor of total size = \\\\sum lengths, containing the indices \")\n    .Input(2, \"values\", \"Data tensor, dimension has to match `indices`\")\n    .Input(\n        3,\n        \"output_shape_inference\",\n        \"Optional, a dense tensor whose shape define the output shape\")\n    .Output(\n        0,\n        \"dense\",\n        \"2-D dense tensor, with 1st dim = len(lengths), 2nd dim = dense_last_dim\"\n        \"in the arg list, the tensor is of the same data type as `values`.\"\n        \"Missing values are filled with default_value\")\n    .Arg(\n        \"dense_last_dim\",\n        \"Optional, output dense last dimension. \"\n        \"If both this argument and output_shape_inference are set, \"\n        \"it should be consistent with output_shape_inference's last dim\")\n    .Arg(\n        \"default_value\",\n        \"Optional, missing values are filled with this value.\"\n        \"default_value = 0 when not set\");\n\nREGISTER_CPU_OPERATOR(\n    BatchDenseToSparse,\n    BatchDenseToSparseOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(BatchDenseToSparse)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nThis Op is a inverse of BatchSparseToDenseOp.\nBasically, given a `lengths` vector, a `indices` vector,\nand a dense matrix `dense`, output `value` vector so that, along with\n`lengths` vector and `indices` vector, forms a sparse representation\nof the dense matrix.\n\nA sparse matrix is represented by `lengths` vector, `indices` vector,\nand `values` vector. Each element in `lengths` vector (lengths[`i`]) represents\nthe number of indices in this batch (batch `i`).\nWith in each batch, `indices` should not have duplicate number.\n\nFor example, with input:\n\n  lengths = [2, 3, 1]\n  indices = [0, 1, 2, 3, 4, 5]\n  output = [[6, 7, 0, 0, 0,  0],\n            [0, 0, 8, 9, 10, 0],\n            [0, 0, 0, 0, 0, 11]]\n\nThe output is:\n\n  values = [6, 7, 8, 9, 10, 11]\n\nafter running this operator.\n)DOC\")\n    .Input(\n        0,\n        \"lengths\",\n        \"Flatten lengths, Used to break down indices into per batch indices\")\n    .Input(\n        1,\n        \"indices\",\n        \"Flatten indices, tensor of total size = \\\\sum lengths, containing the indices \")\n    .Input(\n        2,\n        \"dense\",\n        \"dense 2-D tensor, first dim = len(lengths), last dim > Any(indices)\")\n    .Output(\n        0,\n        \"values\",\n        \"Values, tensor of the same size as `indices` and same data type as dense tensor.\");\n\nnamespace {\n\nclass GetBatchSparseToDenseGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"BatchDenseToSparse\",\n        \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(2)});\n  }\n};\n\nclass GetBatchDenseToSparseGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"BatchSparseToDense\",\n        \"\",\n        vector<string>{I(0), I(1), GO(0), I(2)},\n        vector<string>{GI(2)});\n  }\n};\n\nREGISTER_GRADIENT(BatchSparseToDense, GetBatchSparseToDenseGradient);\nREGISTER_GRADIENT(BatchDenseToSparse, GetBatchDenseToSparseGradient);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/batch_sparse_to_dense_op.h",
    "content": "// Copyright 2004-present Facebook. All Rights Reserved.\n\n#ifndef CAFFE2_OPERATORS_BATCH_SPARSE_TO_DENSE_OP_H_\n#define CAFFE2_OPERATORS_BATCH_SPARSE_TO_DENSE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass BatchSparseToDenseOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  BatchSparseToDenseOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        OP_SINGLE_ARG(TIndex, \"dense_last_dim\", dense_last_dim_, -1),\n        OP_SINGLE_ARG(T, \"default_value\", default_value_, static_cast<T>(0)) {}\n  bool RunOnDevice() override;\n\n private:\n  TIndex dense_last_dim_;\n  T default_value_;\n  INPUT_TAGS(LENGTHS, INDICES, VALUES);\n};\n\ntemplate <typename T, class Context>\nclass BatchDenseToSparseOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  BatchDenseToSparseOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  bool RunOnDevice() override;\n\n private:\n  TIndex dense_last_dim_;\n  INPUT_TAGS(LENGTHS, INDICES, DENSE);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_BATCH_SPARSE_TO_DENSE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/bbox_transform_op.cc",
    "content": "#include \"bbox_transform_op.h\"\n#include \"caffe2/operators/generate_proposals_op_util_boxes.h\"\n\n#ifdef CAFFE2_USE_MKL\n#include \"caffe2/mkl/operators/operator_fallback_mkl.h\"\n#endif // CAFFE2_USE_MKL\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(BBoxTransform, BBoxTransformOp<float, CPUContext>);\n\n#ifdef CAFFE2_HAS_MKL_DNN\nREGISTER_MKL_OPERATOR(\n    BBoxTransform,\n    mkl::MKLFallbackOp<BBoxTransformOp<float, CPUContext>>);\n#endif // CAFFE2_HAS_MKL_DNN\n\n// Input: box, delta Output: box\nOPERATOR_SCHEMA(BBoxTransform)\n    .NumInputs(3)\n    .NumOutputs(1, 2)\n    .SetDoc(R\"DOC(\nTransform proposal bounding boxes to target bounding box using bounding box\n    regression deltas.\n)DOC\")\n    .Arg(\"weights\", \"vector<float> weights [wx, wy, ww, wh] for the deltas\")\n    .Arg(\n        \"apply_scale\",\n        \"bool (default true), transform the boxes to the scaled image space\"\n        \" after applying the bbox deltas.\"\n        \"Set to false to match the detectron code, set to true for keypoint\"\n        \" models and for backward compatibility\")\n    .Arg(\n        \"correct_transform_coords\",\n        \"bool (default false), Correct bounding box transform coordates,\"\n        \" see bbox_transform() in boxes.py \"\n        \"Set to true to match the detectron code, set to false for backward\"\n        \" compatibility\")\n    .Input(\n        0,\n        \"rois\",\n        \"Bounding box proposals in pixel coordinates, \"\n        \"Size (M, 4), format [x1, y1, x2, y2], or\"\n        \"Size (M, 5), format [batch_index, x1, y1, x2, y2]. \"\n        \"If proposals from multiple images in a batch are present, they \"\n        \"should be grouped sequentially and in incremental order.\")\n    .Input(\n        1,\n        \"deltas\",\n        \"bounding box translations and scales,\"\n        \"size (M, 4*K), format [dx, dy, dw, dh], K = # classes\")\n    .Input(\n        2,\n        \"im_info\",\n        \"Image dimensions, size (batch_size, 3), \"\n        \"format [img_height, img_width, img_scale]\")\n    .Output(\n        0,\n        \"box_out\",\n        \"Pixel coordinates of the transformed bounding boxes,\"\n        \"Size (M, 4*K), format [x1, y1, x2, y2]\")\n    .Output(\n        1,\n        \"roi_batch_splits\",\n        \"Tensor of shape (batch_size) with each element denoting the number \"\n        \"of RoIs belonging to the corresponding image in batch\");\n\nSHOULD_NOT_DO_GRADIENT(BBoxTransform);\n} // namespace\n\ntemplate <>\nbool BBoxTransformOp<float, CPUContext>::RunOnDevice() {\n  const auto& roi_in = Input(0);\n  const auto& delta_in = Input(1);\n  const auto& iminfo_in = Input(2);\n  auto* box_out = Output(0);\n\n  const int N = roi_in.dim32(0);\n  CAFFE_ENFORCE_EQ(roi_in.ndim(), 2);\n  CAFFE_ENFORCE(roi_in.dim32(1) == 4 || roi_in.dim32(1) == 5);\n\n  CAFFE_ENFORCE_EQ(delta_in.ndim(), 2);\n  CAFFE_ENFORCE_EQ(delta_in.dim32(0), N);\n  CAFFE_ENFORCE_EQ(delta_in.dim32(1) % 4, 0);\n  const int num_classes = delta_in.dim32(1) / 4;\n\n  CAFFE_ENFORCE_EQ(iminfo_in.ndim(), 2);\n  CAFFE_ENFORCE_EQ(iminfo_in.dim32(1), 3);\n  const int batch_size = iminfo_in.dim32(0);\n\n  DCHECK_EQ(weights_.size(), 4);\n\n  Eigen::Map<const ERArrXXf> boxes0(\n      roi_in.data<float>(), roi_in.dim32(0), roi_in.dim32(1));\n  Eigen::Map<const ERArrXXf> deltas0(\n      delta_in.data<float>(), delta_in.dim32(0), delta_in.dim32(1));\n\n  // Count the number of RoIs per batch\n  vector<int> num_rois_per_batch(batch_size, 0);\n  if (roi_in.dim32(1) == 4) {\n    CAFFE_ENFORCE_EQ(batch_size, 1);\n    num_rois_per_batch[0] = N;\n  } else {\n    const auto& roi_batch_ids = boxes0.col(0);\n    for (int i = 0; i < roi_batch_ids.size(); ++i) {\n      const int roi_batch_id = roi_batch_ids(i);\n      CAFFE_ENFORCE_LT(roi_batch_id, batch_size);\n      num_rois_per_batch[roi_batch_id]++;\n    }\n  }\n\n  CAFFE_ENFORCE_EQ(iminfo_in.dims(), (vector<TIndex>{batch_size, 3}));\n  Eigen::Map<const ERArrXXf> iminfo(\n      iminfo_in.data<float>(), iminfo_in.dim(0), iminfo_in.dim(1));\n\n  box_out->ResizeLike(delta_in);\n  Eigen::Map<ERArrXXf> new_boxes(\n      box_out->mutable_data<float>(), box_out->dim32(0), box_out->dim32(1));\n\n  // We assume roi_in and delta_in over multiple batches are grouped\n  // together in increasing order as generated by GenerateProposalsOp\n  int offset = 0;\n  for (int i = 0; i < batch_size; ++i) {\n    const int num_rois = num_rois_per_batch[i];\n    const auto& cur_iminfo = iminfo.row(i);\n    const float scale_before = cur_iminfo(2);\n    const float scale_after = apply_scale_ ? cur_iminfo(2) : 1.0;\n    int img_h = int(cur_iminfo(0) / scale_before + 0.5);\n    int img_w = int(cur_iminfo(1) / scale_before + 0.5);\n\n    const auto& cur_boxes =\n        boxes0.rightCols(4).block(offset, 0, num_rois, 4) / scale_before;\n    for (int k = 0; k < num_classes; k++) {\n      const auto& cur_deltas = deltas0.block(offset, k * 4, num_rois, 4);\n      const auto& trans_boxes = utils::bbox_transform(\n          cur_boxes,\n          cur_deltas,\n          weights_,\n          utils::BBOX_XFORM_CLIP_DEFAULT,\n          correct_transform_coords_);\n      const auto& clip_boxes = utils::clip_boxes(trans_boxes, img_h, img_w);\n      new_boxes.block(offset, k * 4, num_rois, 4) = clip_boxes * scale_after;\n    }\n\n    offset += num_rois;\n  }\n\n  if (OutputSize() > 1) {\n    auto* roi_batch_splits = Output(1);\n    roi_batch_splits->Resize(batch_size);\n    Eigen::Map<EArrXf> roi_batch_splits_map(\n        roi_batch_splits->mutable_data<float>(), batch_size);\n    roi_batch_splits_map =\n        Eigen::Map<const EArrXi>(num_rois_per_batch.data(), batch_size)\n            .cast<float>();\n  }\n\n  return true;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/bbox_transform_op.h",
    "content": "// Copyright 2004-present Facebook. All Rights Reserved.\n\n#ifndef BBOX_TRANSFORM_OP_H_\n#define BBOX_TRANSFORM_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass BBoxTransformOp final : public Operator<Context> {\n public:\n  BBoxTransformOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        weights_(OperatorBase::GetRepeatedArgument<T>(\n            \"weights\",\n            vector<T>{1.0f, 1.0f, 1.0f, 1.0f})),\n        apply_scale_(\n            OperatorBase::GetSingleArgument<bool>(\"apply_scale\", true)),\n        correct_transform_coords_(OperatorBase::GetSingleArgument<bool>(\n            \"correct_transform_coords\",\n            false)) {\n    CAFFE_ENFORCE_EQ(\n        weights_.size(),\n        4,\n        \"weights size \" + caffe2::to_string(weights_.size()) + \"must be 4.\");\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  // weights [wx, wy, ww, wh] to apply to the regression target\n  vector<T> weights_;\n  // Transform the boxes to the scaled image space after applying the bbox\n  //   deltas.\n  // Set to false to match the detectron code, set to true for the keypoint\n  //   model and for backward compatibility\n  bool apply_scale_{true};\n  // Correct bounding box transform coordates, see bbox_transform() in boxes.py\n  // Set to true to match the detectron code, set to false for backward\n  //   compatibility\n  bool correct_transform_coords_{false};\n};\n\n} // namespace caffe2\n\n#endif // BBOX_TRANSFORM_OP_H_\n"
  },
  {
    "path": "caffe2/operators/boolean_mask_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/boolean_mask_ops.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\nnamespace {\n\ntemplate <class Context>\nclass BooleanMaskLengthsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  BooleanMaskLengthsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& lengths = Input(0);\n    auto& mask = Input(1);\n    auto* lengthsOut = Output(0);\n    CAFFE_ENFORCE(lengths.ndim() == 1);\n    CAFFE_ENFORCE(mask.ndim() == 1);\n    const auto* lengthsPtr = lengths.template data<T>();\n    const auto* maskPtr = mask.template data<bool>();\n    auto totalLength =\n        std::accumulate(lengthsPtr, lengthsPtr + lengths.size(), 0);\n    CAFFE_ENFORCE(mask.size() == totalLength);\n    lengthsOut->ResizeLike(lengths);\n    auto* lengthsOutPtr = lengthsOut->template mutable_data<T>();\n    int p = 0;\n    for (int i = 0; i < lengths.size(); ++i) {\n      T lengthOut = 0;\n      for (int j = 0; j < lengthsPtr[i]; ++j) {\n        if (maskPtr[p++]) {\n          ++lengthOut;\n        }\n      }\n      lengthsOutPtr[i] = lengthOut;\n    }\n    return true;\n  }\n};\n} // namespace\n\ntemplate <>\nbool BooleanMaskOp<CPUContext>::RunOnDevice() {\n  auto& data = Input(0);\n  auto& mask = Input(1);\n  auto* dataOut = Output(0);\n  CAFFE_ENFORCE(data.ndim() >= 1);\n  CAFFE_ENFORCE_EQ(mask.ndim(), 1);\n  CAFFE_ENFORCE(data.dims()[0] == mask.dims()[0]);\n\n  const auto* maskPtr = mask.template data<bool>();\n  int numOutputs = 0;\n  int outerSize = mask.size();\n  for (int i = 0; i < outerSize; ++i) {\n    if (maskPtr[i]) {\n      ++numOutputs;\n    }\n  }\n  std::vector<TIndex> outShape;\n  outShape.push_back(numOutputs);\n  outShape.insert(outShape.end(), data.dims().begin() + 1, data.dims().end());\n  dataOut->Resize(outShape);\n  auto* outPtr = (char*)dataOut->raw_mutable_data(data.meta());\n\n  int64_t* out_vec = nullptr;\n  if (OutputSize() == 2) {\n    auto* indicesOut = Output(1);\n    indicesOut->Resize(numOutputs);\n    out_vec = indicesOut->template mutable_data<int64_t>();\n  }\n\n  if (numOutputs == 0) {\n    return true;\n  }\n  const auto innerSize = data.size_from_dim(1);\n  const auto innerSizeBytes = innerSize * data.meta().itemsize();\n\n  TIndex lastStart = -1;\n  const auto* inPtr = (char*)data.raw_data();\n  TIndex outStart = 0;\n\n  for (TIndex i = 0;; ++i) {\n    // mask was true and either a) became false, or b) sequence finished\n    if (lastStart != -1 && ((i >= outerSize) || !maskPtr[i])) {\n      const auto* src = inPtr + lastStart * innerSizeBytes;\n      auto* dst = outPtr + outStart * innerSizeBytes;\n      int numItems = i - lastStart;\n      context_.template CopyItems<CPUContext, CPUContext>(\n          data.meta(), numItems * innerSize, src, dst);\n      outStart += numItems;\n      lastStart = -1;\n    }\n    if (i >= outerSize) {\n      break;\n    }\n    // mask was false and became true\n    if (lastStart == -1 && maskPtr[i]) {\n      lastStart = i;\n    }\n    if (maskPtr[i] && OutputSize() == 2) {\n      *(out_vec++) = i;\n    }\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(BooleanMask, BooleanMaskOp<CPUContext>);\nREGISTER_CPU_OPERATOR(BooleanMaskLengths, BooleanMaskLengthsOp<CPUContext>);\n\nOPERATOR_SCHEMA(BooleanMask)\n    .NumInputs(2)\n    .NumOutputs(1, 2)\n    .SetDoc(R\"DOC(\nGiven a data tensor and a 1D boolean mask tensor, returns a tensor containing\nonly the elements corresponding to positions where the mask is true.\n)DOC\")\n    .Input(0, \"data\", \"The 1D, original data tensor.\")\n    .Input(1, \"mask\", \"A tensor of bools of same shape as `data`.\")\n    .Output(0, \"masked_data\", \"A tensor of same type as `data`.\")\n    .Output(1, \"masked_indices\", \"A tensor for indices.\");\n\nOPERATOR_SCHEMA(BooleanMaskLengths)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven a tensor of int32 segment lengths and a mask (boolean) tensor, return\nthe segment lengths of a corresponding segmented tensor after BooleanMask is\napplied.\n)DOC\")\n    .Input(0, \"lengths\", \"A 1D int32 tensor representing segment lengths.\")\n    .Input(1, \"mask\", \"A 1D bool tensor of values to keep.\")\n    .Output(0, \"masked_lengths\", \"Segment lengths of a masked tensor.\");\n\nNO_GRADIENT(BooleanMask)\nNO_GRADIENT(BooleanMaskLengths);\n\nconst float minf = -1.0f * std::numeric_limits<float>::infinity();\n\n// Template this on a functor object so we can generate different\n// implementations at compile time and have a better chance of inlining\ntemplate <typename Functor>\nvoid MaskWithFunctor(\n    size_t N,\n    size_t M,\n    int B,\n    const float* in,\n    Functor fn,\n    float fill_val,\n    float* out) {\n  if (B >= 0) { // with batching\n    // collapse tensor to 3-dim view [B, N, M] where:\n    // B is product of dims up to and including batch\n    // N is product of dims between batch and axis, exclusive\n    // M is product of dimensions at/after axis\n    // then mask each batch [i, :, :] (note that this is N x M matrix)\n    for (int i = 0; i < B; ++i) {\n      for (int j = 0; j < N; ++j) {\n        for (int k = 0; k < M; ++k) {\n          // when [i, :, :] is laid out in row major order\n          // N * M * i + M * j + k is index of entry in N x M matrix\n          // with coordinates (row = j, col = k)\n          auto val = in[N * M * i + M * j + k];\n          out[N * M * i + M * j + k] = (fn(j, k, val) ? fill_val : val);\n        }\n      }\n    }\n  } else { // without batching\n    // TODO(T20952436): vector implementation\n    // collapse tensor to 2-dim view [N, M], where\n    // N is product of dimensions before axis\n    // M is product of dimensions at/after axis\n    // and mask N by M matrix\n    for (int i = 0; i < N; ++i) {\n      for (int j = 0; j < M; ++j) {\n        auto val = in[M * i + j];\n        out[M * i + j] = (fn(i, j, val) ? fill_val : val);\n      }\n    }\n  }\n}\n\n// Repeat masking along continuous segments (right axes) of size D\ntemplate <typename Functor>\nvoid RepeatedMaskWithFunctor(\n    size_t N,\n    size_t M,\n    int D,\n    const float* in,\n    Functor fn,\n    float fill_val,\n    float* out) {\n  for (int i = 0; i < N; ++i) {\n    for (int j = 0; j < M; ++j) {\n      for (int k = 0; k < D; ++k) {\n        auto val = in[M * D * i + D * j + k];\n        out[M * D * i + D * j + k] = (fn(i, j, val) ? fill_val : val);\n      }\n    }\n  }\n}\n\nnamespace {\n\nclass SequenceFunctor {\n public:\n  explicit SequenceFunctor(const int* sl, const size_t len)\n      : sl_(sl), len_(len) {}\n  bool operator()(int i, int j, float /* val*/) {\n    CAFFE_ENFORCE(i < len_, \"Out of bound.\");\n    return j >= sl_[i];\n  }\n\n private:\n  const int* sl_;\n  const size_t len_;\n};\n\nclass WindowFunctor {\n public:\n  explicit WindowFunctor(const int* c, int r) : c(c), r(r) {}\n  bool operator()(int i, int j, float /* val*/) {\n    return j > c[i] + r || j < c[i] - r;\n  }\n\n private:\n  const int* c;\n  const int r;\n};\n\nclass UpperFunctor {\n public:\n  bool operator()(int i, int j, float /* val */) {\n    return j > i;\n  }\n};\n\nclass LowerFunctor {\n public:\n  bool operator()(int i, int j, float /* val */) {\n    return j < i;\n  }\n};\n\nclass UpperDiagFunctor {\n public:\n  bool operator()(int i, int j, float /* val */) {\n    return j >= i;\n  }\n};\n\nclass LowerDiagFunctor {\n public:\n  bool operator()(int i, int j, float /* val */) {\n    return j <= i;\n  }\n};\n\n} // namespace\n\ntemplate <>\nbool SequenceMaskOp<CPUContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<float>>::call(this, Input(0));\n}\n\ntemplate <>\ntemplate <class T>\nbool SequenceMaskOp<CPUContext>::DoRunWithType() {\n  const Tensor<CPUContext>* input = &Input(0);\n  const Tensor<CPUContext>* sequence_lengths = nullptr;\n  const Tensor<CPUContext>* window_centers = nullptr;\n\n  if (mode_ == \"sequence\") {\n    sequence_lengths = &Input(1);\n  } else if (mode_ == \"window\") {\n    window_centers = &Input(1);\n  }\n\n  auto* output = Output(0);\n  output->ResizeLike(*input);\n\n  const auto canonical_axis = input->canonical_axis_index(axis_);\n\n  // canonical_batch is non-negative if batching, -1 otherwise\n  int canonical_batch = -1;\n  if ((HasArgument(\"batch\"))) {\n    canonical_batch = input->canonical_axis_index(batch_);\n  }\n\n  // make sure batch < axis\n  if (canonical_batch >= 0) {\n    CAFFE_ENFORCE_LT(canonical_batch, canonical_axis);\n  }\n\n  // if no batch, then left is product of dims up to axis\n  // otherwise, left is product of dims between batch and axis\n  const int left =\n      (canonical_batch >= 0\n           ? input->size_between_dim(canonical_batch, canonical_axis)\n           : input->size_to_dim(canonical_axis));\n  const int right = input->size_from_dim(canonical_axis);\n\n  // product of dims from 1 to batch\n  const int batch_dim =\n      (canonical_batch >= 0\n           ? input->size_to_dim(canonical_batch) * input->dim(canonical_batch)\n           : -1);\n\n  T fill_val = convert::To<float, T>(grad_ ? 0.0f : fill_val_);\n  if (mode_ == \"sequence\") {\n    CAFFE_ENFORCE(\n        sequence_lengths, \"Sequence length not provided for mode 'sequence'!\");\n    if (HasArgument(\"repeat_from_axis\")) {\n      const int canonical_repeat_from =\n          input->canonical_axis_index(repeat_from_);\n      const int repeated_dims = input->size_from_dim(canonical_repeat_from);\n      const int masked_dims = right / repeated_dims;\n      RepeatedMaskWithFunctor(\n          left,\n          masked_dims,\n          repeated_dims,\n          input->data<T>(),\n          SequenceFunctor(\n              sequence_lengths->data<int>(), sequence_lengths->size()),\n          fill_val,\n          output->mutable_data<T>());\n    } else {\n      MaskWithFunctor(\n          left,\n          right,\n          batch_dim,\n          input->data<T>(),\n          SequenceFunctor(\n              sequence_lengths->data<int>(), sequence_lengths->size()),\n          fill_val,\n          output->mutable_data<T>());\n    }\n  } else if (mode_ == \"window\") {\n    MaskWithFunctor(\n        left,\n        right,\n        batch_dim,\n        input->data<T>(),\n        WindowFunctor(window_centers->data<int>(), radius_),\n        fill_val,\n        output->mutable_data<T>());\n  } else if (mode_ == \"upper\") {\n    MaskWithFunctor(\n        left,\n        right,\n        batch_dim,\n        input->data<T>(),\n        UpperFunctor(),\n        fill_val,\n        output->mutable_data<T>());\n  } else if (mode_ == \"lower\") {\n    MaskWithFunctor(\n        left,\n        right,\n        batch_dim,\n        input->data<T>(),\n        LowerFunctor(),\n        fill_val,\n        output->mutable_data<T>());\n  } else if (mode_ == \"upperdiag\") {\n    MaskWithFunctor(\n        left,\n        right,\n        batch_dim,\n        input->data<T>(),\n        UpperDiagFunctor(),\n        fill_val,\n        output->mutable_data<T>());\n  } else if (mode_ == \"lowerdiag\") {\n    MaskWithFunctor(\n        left,\n        right,\n        batch_dim,\n        input->data<T>(),\n        LowerDiagFunctor(),\n        fill_val,\n        output->mutable_data<T>());\n  } else {\n    CAFFE_ENFORCE(false, \"Unsupported mode for SequenceMaskOp!\");\n    return false;\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(SequenceMask, SequenceMaskOp<CPUContext>);\n\nOPERATOR_SCHEMA(SequenceMask)\n    .NumInputs(1, 2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nMask op designed for use in attention mechanisms for sequence modeling tasks.\nSupports batching: given batch_dim, collapses dims 0 through batch_dim into a\nsingle dimension, e.g. if tensor dims are [4,2,1,3,4] and batch_dim=2, first\ncollapse tensor to [4*2*1,3,4], then mask each batch [i,:,:].\n\n\nTwo current operating modes:\n\n\n1) Given a 2D input tensor and 1D tensor of sequence lengths, for each row i in\nthe input tensor, set elements in that row to -inf if their column index\nj >= sequence_lengths[i]. This mode takes two inputs and argument mode =\n'sequence'\n\n\n2) Triangular mask. Given row index i and column index j, set elements to -inf\ngiven the following conditions:\n\n      mode='upper', x_ij = -inf if j < i\n      mode='lower', x_ij = -inf if j > i\n      mode='upperdiag', x_ij = -inf if j <= i\n      mode='lowerdiag', x_ij = -inf if j >= i\n\nThis mode takes one input.\n\n\n3) Window Mask. Given a 2D input tensor and 1D tensor of window centers,\nfor each row i in the input tensor, set elements in that row to -inf\nif their column index j outside [center - radius, center + radius].\nThis mode takes two inputs and argument mode = 'sequence'.\nArgument 'radius' should be provided.\n)DOC\")\n    .Input(0, \"input\", \"Tensor to apply masking to\")\n    .Input(1, \"sequence_lengths\", \"1D Tensor of sequence lengths for mode #1\")\n    .Output(0, \"masked_tensor\", \"Input tensor with masking applied\")\n    .Arg(\n        \"mode\",\n        \"(string) Mode selection. Possible values: \"\n        \"'sequence', 'upper', 'lower', 'upperdiag', 'lowerdiag'\")\n    .Arg(\n        \"axis\",\n        \"(int) Beginning axis of row elements. All dimensions to the left \"\n        \"will be treated as row indices and those to the right (inclusive) \"\n        \"will be treated as column indices in the 2D mask\")\n    .Arg(\"grad\", \"(bool) operate in gradient mode\")\n    .Arg(\"radius\", \"(int) radius of windows in window mode\")\n    .Arg(\"batch\", \"(int) batch dimension of tensor (optional)\")\n    .Arg(\n        \"repeat_from_axis\",\n        \"(int) used when mask should be repeated for \"\n        \"one or more data dimensions (beginning at this axis).  \"\n        \"(currently only supported for sequence mode without batch argument)\");\n\nclass GetSequenceMaskGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<Argument> args;\n    args.reserve(Def().arg().size());\n    for (const auto& x : Def().arg()) {\n      args.push_back(x);\n    }\n    args.push_back(MakeArgument<bool>(\"grad\", true));\n    if (def_.input_size() == 1) {\n      return SingleGradientDef(\n          \"SequenceMask\",\n          \"\",\n          vector<string>{GO(0)},\n          vector<string>{GI(0)},\n          args);\n    } else {\n      return SingleGradientDef(\n          \"SequenceMask\",\n          \"\",\n          vector<string>{GO(0), I(1)},\n          vector<string>{GI(0)},\n          args);\n    }\n  }\n\n  bool CopyArguments() const override {\n    return false;\n  }\n};\n\nREGISTER_GRADIENT(SequenceMask, GetSequenceMaskGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/boolean_mask_ops.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/boolean_mask_ops.h\"\n\n#include <cub/cub.cuh>\n\nnamespace caffe2 {\n\nnamespace {\ntemplate <typename T>\n__global__ void BooleanMaskCopyKernel(\n    const TIndex numOfOutput,\n    const TIndex numBytes,\n    const TIndex* indices,\n    const T* src,\n    T* dest) {\n  for (TIndex i = blockIdx.x; i < numOfOutput; i += gridDim.x) {\n    const auto srcBase = indices[i] * numBytes;\n    const auto destBase = i * numBytes;\n    for (TIndex j = threadIdx.x; j < numBytes; j += blockDim.x) {\n      dest[destBase + j] = src[srcBase + j];\n    }\n  }\n}\n}\n\ntemplate <>\nclass BooleanMaskOp<CUDAContext> final : public Operator<CUDAContext> {\n public:\n  BooleanMaskOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    const auto& src = Input(0);\n    const auto& mask = Input(1);\n    auto* dest = Output(0);\n\n    CAFFE_ENFORCE(src.ndim() >= 1);\n    CAFFE_ENFORCE_EQ(mask.ndim(), 1);\n    CAFFE_ENFORCE(src.dims()[0] == mask.dims()[0]);\n\n    const auto* maskData = mask.data<bool>();\n    const auto outerSize = mask.dims()[0];\n    indices_.Resize(outerSize);\n    auto* indicesData = indices_.mutable_data<TIndex>();\n\n    size_t numBytes = 0;\n    cub::CountingInputIterator<int> itr(0);\n    cub::DeviceSelect::Flagged(\n        nullptr,\n        numBytes,\n        itr,\n        maskData,\n        indicesData,\n        static_cast<TIndex*>(nullptr),\n        outerSize,\n        context_.cuda_stream());\n\n    auto numTIndex =\n        static_cast<TIndex>((numBytes + sizeof(TIndex) - 1) / sizeof(TIndex));\n    // allocate one more TIndex at the end of scratch for storing numOfOutput\n    scratch_.Resize(numTIndex + 1);\n    auto* scratchData = scratch_.mutable_data<TIndex>();\n    auto* numOfOutputData = scratchData + numTIndex;\n\n    cub::DeviceSelect::Flagged(\n        static_cast<void*>(scratchData),\n        numBytes,\n        itr,\n        maskData,\n        indicesData,\n        numOfOutputData,\n        outerSize,\n        context_.cuda_stream());\n\n    // Copy numOfOutput from gpu to cpu\n    TIndex numOfOutput;\n    context_.Copy<TIndex, CUDAContext, CPUContext>(\n        1, numOfOutputData, &numOfOutput);\n\n    indices_.Resize(numOfOutput);\n    std::vector<TIndex> dims = src.dims();\n    dims[0] = numOfOutput;\n    dest->Resize(dims);\n    auto* destData = (char*)dest->raw_mutable_data(src.meta());\n    const auto* srcData = (char*)src.raw_data();\n    if (OutputSize() == 2) {\n      auto* indicesOut = Output(1);\n      indicesOut->Resize(numOfOutput);\n      indicesOut->mutable_data<TIndex>();\n    }\n\n    if (numOfOutput > 0) {\n      BooleanMaskCopyKernel<<<\n          min(numOfOutput, static_cast<TIndex>(CAFFE_MAXIMUM_NUM_BLOCKS)),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          numOfOutput,\n          src.size_from_dim(1) * src.meta().itemsize(),\n          indicesData,\n          srcData,\n          destData);\n\n      if (OutputSize() == 2) {\n        Output(1)->CopyFrom(indices_, &context_);\n      }\n    }\n\n    return true;\n  }\n\n private:\n  Tensor<CUDAContext> indices_;\n  Tensor<CUDAContext> scratch_;\n};\n\nREGISTER_CUDA_OPERATOR(BooleanMask, BooleanMaskOp<CUDAContext>);\n\nnamespace {\n\n#define minf (-1.0f * std::numeric_limits<float>::infinity())\n\ntemplate <typename T>\n__global__ void sequenceMaskKernel(\n    int N,\n    int M,\n    int B,\n    const T* in,\n    const int* seq_lengths,\n    T fill_val,\n    T* out) {\n  if (B >= 0) {\n    CUDA_1D_KERNEL_LOOP(index, B * N * M) {\n      int k = index % M;\n      int j = (index - k) / M % N;\n      int i = (index - M * j - k) / (N * M);\n\n      int ind = N * M * i + M * j + k;\n      out[ind] = (k >= seq_lengths[j] ? fill_val : in[ind]);\n    }\n  } else {\n    CUDA_1D_KERNEL_LOOP(index, N * M) {\n      int i = index / M;\n      int j = index % M;\n\n      out[index] = (j >= seq_lengths[i] ? fill_val : in[index]);\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void repeatedSequenceMaskKernel(\n    int N,\n    int M,\n    int D,\n    const T* in,\n    const int* seq_lengths,\n    T fill_val,\n    T* out) {\n  CUDA_1D_KERNEL_LOOP(index, N * M * D) {\n    int i = index / (D * M);\n    int j = (index / D) % M;\n\n    out[index] = (j >= seq_lengths[i] ? fill_val : in[index]);\n  }\n}\n\ntemplate <typename T>\n__global__ void windowMaskKernel(\n    int N,\n    int M,\n    int B,\n    const T* in,\n    const int* window_centers,\n    const int radius,\n    T fill_val,\n    T* out) {\n  if (B >= 0) {\n    CUDA_1D_KERNEL_LOOP(index, B * N * M) {\n      int k = index % M;\n      int j = (index - k) / M % N;\n      int i = (index - M * j - k) / (N * M);\n\n      int ind = N * M * i + M * j + k;\n      out[ind] =\n          (k < window_centers[j] - radius || k > window_centers[j] + radius\n               ? fill_val\n               : in[ind]);\n    }\n  } else {\n    CUDA_1D_KERNEL_LOOP(index, N * M) {\n      int i = index / M;\n      int j = index % M;\n\n      out[index] =\n          (j < window_centers[i] - radius || j > window_centers[i] + radius\n               ? fill_val\n               : in[index]);\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void\nupperMaskKernel(int N, int M, int B, const T* in, T fill_val, T* out) {\n  if (B >= 0) {\n    CUDA_1D_KERNEL_LOOP(index, B * N * M) {\n      int k = index % M;\n      int j = (index - k) / M % N;\n      int i = (index - M * j - k) / (N * M);\n\n      int ind = N * M * i + M * j + k;\n      out[ind] = (k > j ? fill_val : in[ind]);\n    }\n  } else {\n    CUDA_1D_KERNEL_LOOP(index, N * M) {\n      int i = index / M;\n      int j = index % M;\n\n      out[index] = (j > i ? fill_val : in[index]);\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void\nlowerMaskKernel(int N, int M, int B, const T* in, T fill_val, T* out) {\n  if (B >= 0) {\n    CUDA_1D_KERNEL_LOOP(index, B * N * M) {\n      int k = index % M;\n      int j = (index - k) / M % N;\n      int i = (index - M * j - k) / (N * M);\n\n      int ind = N * M * i + M * j + k;\n      out[ind] = (k < j ? fill_val : in[ind]);\n    }\n  } else {\n    CUDA_1D_KERNEL_LOOP(index, N * M) {\n      int i = index / M;\n      int j = index % M;\n\n      out[index] = (j < i ? fill_val : in[index]);\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void\nupperDiagMaskKernel(int N, int M, int B, const T* in, T fill_val, T* out) {\n  if (B >= 0) {\n    CUDA_1D_KERNEL_LOOP(index, B * N * M) {\n      int k = index % M;\n      int j = (index - k) / M % N;\n      int i = (index - M * j - k) / (N * M);\n\n      int ind = N * M * i + M * j + k;\n      out[ind] = (k >= j ? fill_val : in[ind]);\n    }\n  } else {\n    CUDA_1D_KERNEL_LOOP(index, N * M) {\n      int i = index / M;\n      int j = index % M;\n\n      out[index] = (j >= i ? fill_val : in[index]);\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void\nlowerDiagMaskKernel(int N, int M, int B, const T* in, T fill_val, T* out) {\n  if (B >= 0) {\n    CUDA_1D_KERNEL_LOOP(index, B * N * M) {\n      int k = index % M;\n      int j = (index - k) / M % N;\n      int i = (index - M * j - k) / (N * M);\n\n      int ind = N * M * i + M * j + k;\n      out[ind] = (k <= j ? fill_val : in[ind]);\n    }\n  } else {\n    CUDA_1D_KERNEL_LOOP(index, N * M) {\n      int i = index / M;\n      int j = index % M;\n\n      out[index] = (j <= i ? fill_val : in[index]);\n    }\n  }\n}\n\n} // namespace\n\ntemplate <>\nbool SequenceMaskOp<CUDAContext>::RunOnDevice() {\n    return DispatchHelper<TensorTypes<float16, float>>::call(this, Input(0));\n}\n\ntemplate <>\ntemplate <class T>\nbool SequenceMaskOp<CUDAContext>::DoRunWithType() {\n  const Tensor<CUDAContext>* input = &Input(0);\n  const Tensor<CUDAContext>* sequence_lengths = nullptr;\n  const Tensor<CUDAContext>* window_centers = nullptr;\n\n  if (mode_ == \"sequence\") {\n    sequence_lengths = &Input(1);\n  } else if (mode_ == \"window\") {\n    window_centers = &Input(1);\n  }\n\n  auto* output = Output(0);\n  output->ResizeLike(*input);\n\n  const auto canonical_axis = input->canonical_axis_index(axis_);\n\n  // canonical_batch is non-negative if batching, -1 otherwise\n  int canonical_batch = -1;\n  if ((HasArgument(\"batch\"))) {\n    canonical_batch = input->canonical_axis_index(batch_);\n  }\n\n  // make sure batch < axis\n  if (canonical_batch >= 0) {\n    CAFFE_ENFORCE_LT(canonical_batch, canonical_axis);\n  }\n\n  // if no batch, then left is product of dims up to axis\n  // otherwise, left is product of dims between batch and axis\n  const int left =\n      (canonical_batch >= 0\n           ? input->size_between_dim(canonical_batch, canonical_axis)\n           : input->size_to_dim(canonical_axis));\n  const int right = input->size_from_dim(canonical_axis);\n\n  // product of dims from 1 to batch\n  const int batch_dim =\n      (canonical_batch >= 0\n           ? input->size_to_dim(canonical_batch) * input->dim(canonical_batch)\n           : -1);\n\n  T fill_val = convert::To<float, T>(grad_ ? 0.0f : fill_val_);\n  if (mode_ == \"sequence\") {\n    if (HasArgument(\"repeat_from_axis\")) {\n      const int canonical_repeat_from =\n          input->canonical_axis_index(repeat_from_);\n      const int repeated_dims = input->size_from_dim(canonical_repeat_from);\n      const int masked_dims = right / repeated_dims;\n      repeatedSequenceMaskKernel<<<\n          CAFFE_GET_BLOCKS(left * right),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          left,\n          masked_dims,\n          repeated_dims,\n          input->data<T>(),\n          sequence_lengths->data<int>(),\n          fill_val,\n          output->mutable_data<T>());\n    } else {\n      sequenceMaskKernel<<<\n          CAFFE_GET_BLOCKS(left * right),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          left,\n          right,\n          batch_dim,\n          input->data<T>(),\n          sequence_lengths->data<int>(),\n          fill_val,\n          output->mutable_data<T>());\n    }\n  } else if (mode_ == \"window\") {\n    windowMaskKernel<<<\n        CAFFE_GET_BLOCKS(left * right),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        left,\n        right,\n        batch_dim,\n        input->data<T>(),\n        window_centers->data<int>(),\n        radius_,\n        fill_val,\n        output->mutable_data<T>());\n  } else if (mode_ == \"upper\") {\n    upperMaskKernel<<<\n        CAFFE_GET_BLOCKS(left * right),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        left,\n        right,\n        batch_dim,\n        input->data<T>(),\n        fill_val,\n        output->mutable_data<T>());\n  } else if (mode_ == \"lower\") {\n    lowerMaskKernel<<<\n        CAFFE_GET_BLOCKS(left * right),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        left,\n        right,\n        batch_dim,\n        input->data<T>(),\n        fill_val,\n        output->mutable_data<T>());\n  } else if (mode_ == \"upperdiag\") {\n    upperDiagMaskKernel<<<\n        CAFFE_GET_BLOCKS(left * right),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        left,\n        right,\n        batch_dim,\n        input->data<T>(),\n        fill_val,\n        output->mutable_data<T>());\n  } else if (mode_ == \"lowerdiag\") {\n    lowerDiagMaskKernel<<<\n        CAFFE_GET_BLOCKS(left * right),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        left,\n        right,\n        batch_dim,\n        input->data<T>(),\n        fill_val,\n        output->mutable_data<T>());\n  } else {\n    CAFFE_ENFORCE(false, \"Unsupported mode for SequenceMaskOp!\");\n  }\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(SequenceMask, SequenceMaskOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/boolean_mask_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef BOOLEAN_MASK_OPS_H\n#define BOOLEAN_MASK_OPS_H\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/conversions.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass BooleanMaskOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  BooleanMaskOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override;\n};\n\ntemplate <class Context>\nclass SequenceMaskOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  explicit SequenceMaskOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        axis_(OperatorBase::GetSingleArgument<int>(\"axis\", 1)),\n        radius_(OperatorBase::GetSingleArgument<int>(\"radius\", 10)),\n        grad_(OperatorBase::GetSingleArgument<bool>(\"grad\", false)),\n        fill_val_(OperatorBase::GetSingleArgument<float>(\n            \"fill_val\",\n            -1.0f * std::numeric_limits<float>::infinity())) {\n    // Mode argument is required\n    mode_ = GetArgument(operator_def, \"mode\").s();\n    // batch argument is optional, but if not given, we don't want a default val\n    if (HasArgument(\"batch\")) {\n      batch_ = GetArgument(operator_def, \"batch\").i();\n    }\n\n    if (HasArgument(\"repeat_from_axis\")) {\n      CAFFE_ENFORCE(\n          mode_ == \"sequence\",\n          \"repeat_from_axis currently only supported in sequence mode.\");\n      CAFFE_ENFORCE(\n          !HasArgument(\"batch\"),\n          \"repeat_from_axis and batch not currently supported together.\");\n      repeat_from_ =\n          OperatorBase::GetSingleArgument<int>(\"repeat_from_axis\", -1);\n    }\n  }\n\n  bool RunOnDevice() override;\n\n  template <typename T>\n  bool DoRunWithType();\n\n private:\n  int axis_;\n  int radius_;\n  std::string mode_;\n  bool grad_;\n  float fill_val_;\n  int batch_;\n  int repeat_from_;\n};\n\n} // namespace caffe2\n\n#endif\n"
  },
  {
    "path": "caffe2/operators/boolean_unmask_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/boolean_unmask_ops.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool BooleanUnmaskOp<CPUContext>::RunOnDevice() {\n  int maskSize = Input(0).size();\n  int numMasks = InputSize() / 2;\n  auto& valueMeta = Input(1).meta();\n\n  auto* valuesOut = Output(0);\n  valuesOut->Resize(maskSize);\n  auto* valuesOutPtr = (char*)valuesOut->raw_mutable_data(valueMeta);\n\n  std::vector<int> nextValueIndices(numMasks, 0);\n  for (int maskOffset = 0; maskOffset < maskSize; ++maskOffset) {\n    bool maskFound = false;\n    for (int maskIndex = 0; maskIndex < numMasks; ++maskIndex) {\n      auto& mask = Input(maskIndex * 2);\n      CAFFE_ENFORCE_EQ(mask.ndim(), 1);\n      CAFFE_ENFORCE_EQ(mask.size(), maskSize);\n      const auto* maskPtr = mask.template data<bool>();\n\n      auto& values = Input(maskIndex * 2 + 1);\n      CAFFE_ENFORCE_EQ(values.ndim(), 1);\n      const auto* valuesPtr = (char*)values.raw_data();\n\n      if (maskPtr[maskOffset]) {\n        auto& valueIndex = nextValueIndices[maskIndex];\n        CAFFE_ENFORCE_LT(valueIndex, values.size());\n        auto* src = valuesPtr + (valueIndex++) * valueMeta.itemsize();\n        auto* dst = valuesOutPtr + maskOffset * valueMeta.itemsize();\n        std::copy(src, src + valueMeta.itemsize(), dst);\n        maskFound = true;\n        break;\n      }\n    }\n    CAFFE_ENFORCE(\n        maskFound, \"All masks have False at position \", maskOffset, \".\");\n  }\n  // check all indices match value length\n  for (int i = 0; i < numMasks; ++i) {\n    auto& values = Input(i * 2 + 1);\n    CAFFE_ENFORCE_EQ(\n        values.size(),\n        nextValueIndices[i],\n        \"The number of true at mask \",\n        i,\n        \" does not match the corresponding value size.\");\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(BooleanUnmask, BooleanUnmaskOp<CPUContext>);\n\nOPERATOR_SCHEMA(BooleanUnmask)\n    .NumInputs([](int n) { return n > 0 && n % 2 == 0; })\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven a series of mask and values, reconstruct values together according\nto masks.\n\nA comprehensive example:\n  mask1   = True, False, True, False, False\n  values1 = 1.0, 3.0\n  mask2   = False, True, False, False, False\n  values2 = 2.0\n  mask3   = False, False, False, True, True\n  values3 = 4.0, 5.0\n\nReconstruct by:\n  output = net.BooleanUnmask([mask1, values1, mask2, values2, mask3, values3], [\"output\"])\n\nWe get:\n  output = 1.0, 2.0, 3.0, 4.0, 5.0\n\nNote that for all mask positions, there must be at least one True. If for a\nfield there are multiple True's, we will accept the first value. For example:\n\n\nExample 1:\n  mask1   = True, False\n  values1 = 1.0\n  mask2   = False, False\n  values2 =\n\nThis is not allowed:\n  output = net.BooleanUnmask([mask1, values1, mask2, values2], [\"output\"])\n\nExample 2:\n  mask1   = True, False\n  values1 = 1.0\n  mask2   = True, True\n  values2 = 2.0, 2.0\n\n  output = net.BooleanUnmask([mask1, values1, mask2, values2], [\"output\"])\n\nWe get:\n  output = 1.0, 2.0\n)DOC\")\n    .Output(0, \"unmasked_data\", \"The final reconstructed unmasked data\");\n\nNO_GRADIENT(BooleanUnmask)\n}\n"
  },
  {
    "path": "caffe2/operators/boolean_unmask_ops.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/boolean_unmask_ops.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n__global__ void ComputeIndicesKernel(\n    const int numMasks,\n    const int maskSize,\n    int* indices,\n    bool* const masks[]) {\n  CUDA_1D_KERNEL_LOOP(i, maskSize) {\n    for (int j = 0; j < numMasks; ++j) {\n      if (masks[j][i]) {\n        indices[i] = j;\n        return;\n      }\n    }\n    CUDA_KERNEL_ASSERT(false);\n  }\n}\n\n__global__ void FillValuesKernel(\n    const int numMasks,\n    const int maskSize,\n    const size_t itemSize,\n    const int* indices,\n    char* const values[],\n    int valueSizes[],\n    char* dest) {\n  CUDA_1D_KERNEL_LOOP(j, numMasks) {\n    int k = 0;\n    for (int i = 0; i < maskSize; ++i) {\n      if (indices[i] == j) {\n        for (int h = 0; h < itemSize; ++h) {\n          dest[i * itemSize + h] = values[j][k * itemSize + h];\n        }\n        ++k;\n      }\n    }\n    CUDA_KERNEL_ASSERT(valueSizes[j] == k);\n  }\n}\n\n} // namespace\n\ntemplate <>\nclass BooleanUnmaskOp<CUDAContext> final : public Operator<CUDAContext> {\n public:\n  BooleanUnmaskOp(const OperatorDef& def, Workspace* ws)\n      : Operator<CUDAContext>(def, ws) {}\n\n  bool RunOnDevice() override {\n    int maskSize = Input(0).size();\n    int numMasks = InputSize() / 2;\n    const auto& meta = Input(1).meta();\n\n    auto* out = Output(0);\n    out->Resize(maskSize);\n    auto* dest = (char*)out->raw_mutable_data(meta);\n\n    hostMasks_.Resize(numMasks);\n    auto* hostMasksData = hostMasks_.mutable_data<bool*>();\n    hostValues_.Resize(numMasks);\n    auto* hostValuesData = hostValues_.mutable_data<char*>();\n    hostValueSizes_.Resize(numMasks);\n    auto* hostValueSizesData = hostValueSizes_.mutable_data<int>();\n    for (int i = 0; i < numMasks; ++i) {\n      auto& mask = Input(i * 2);\n      CAFFE_ENFORCE_EQ(mask.ndim(), 1);\n      CAFFE_ENFORCE_EQ(mask.size(), maskSize);\n      hostMasksData[i] = const_cast<bool*>(mask.data<bool>());\n\n      const auto& value = Input(i * 2 + 1);\n      CAFFE_ENFORCE_EQ(value.ndim(), 1);\n      hostValuesData[i] = (char*)value.raw_data();\n      hostValueSizesData[i] = value.size();\n    }\n    masks_.CopyFrom(hostMasks_, &context_);\n    values_.CopyFrom(hostValues_, &context_);\n    valueSizes_.CopyFrom(hostValueSizes_, &context_);\n\n    indices_.Resize(maskSize);\n    auto* indicesData = indices_.mutable_data<int>();\n\n    ComputeIndicesKernel<<<\n        min(maskSize, CAFFE_MAXIMUM_NUM_BLOCKS),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        numMasks, maskSize, indicesData, masks_.data<bool*>());\n\n    auto* valueSizesData = valueSizes_.mutable_data<int>();\n    FillValuesKernel<<<\n        min(numMasks, CAFFE_MAXIMUM_NUM_BLOCKS),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        numMasks,\n        maskSize,\n        meta.itemsize(),\n        indicesData,\n        values_.data<char*>(),\n        valueSizesData,\n        dest);\n\n    return true;\n  }\n\n private:\n  Tensor<CUDAContext> indices_;\n  Tensor<CUDAContext> masks_;\n  Tensor<CUDAContext> values_;\n  Tensor<CUDAContext> valueSizes_;\n\n  Tensor<CPUContext> hostMasks_;\n  Tensor<CPUContext> hostValues_;\n  Tensor<CPUContext> hostValueSizes_;\n};\n\nREGISTER_CUDA_OPERATOR(BooleanUnmask, BooleanUnmaskOp<CUDAContext>);\n\n} // caffe2\n"
  },
  {
    "path": "caffe2/operators/boolean_unmask_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef BOOLEAN_UNMASK_OPS_H\n#define BOOLEAN_UNMASK_OPS_H\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass BooleanUnmaskOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(BooleanUnmaskOp)\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n};\n\n} // namespace caffe2\n\n#endif\n"
  },
  {
    "path": "caffe2/operators/boolean_unmask_ops_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n\n#include <gtest/gtest.h>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/core/operator.h\"\n\nCAFFE2_DECLARE_string(caffe_test_root);\n\nnamespace caffe2 {\n\ntemplate <class DataT>\nstatic void AddScalarInput(\n    const DataT& value,\n    const string& name,\n    Workspace* ws,\n    bool isEmpty = false) {\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  if (!isEmpty) {\n    tensor->Resize(vector<TIndex>{1});\n    *(tensor->mutable_data<DataT>()) = value;\n  } else {\n    tensor->Resize(vector<TIndex>{0});\n    tensor->mutable_data<DataT>();\n  }\n  return;\n}\n\n// Test case for BooleanUnmask operator\n//  mask1:   [ false ]\n//  values1: [ ]\n//  mask2:   [ true ]\n//  values2: [ 1.0 ]\n//\n//  Expected Output: [ 1.0 ]\nTEST(BooleanUnmaskTest, Test) {\n  Workspace ws;\n  OperatorDef def;\n\n  def.set_name(\"test\");\n  def.set_type(\"BooleanUnmask\");\n\n  def.add_input(\"mask1\");\n  def.add_input(\"values1\");\n  def.add_input(\"mask2\");\n  def.add_input(\"values2\");\n\n  def.add_output(\"unmasked_data\");\n\n  AddScalarInput(false, \"mask1\", &ws);\n  AddScalarInput(float(), \"values1\", &ws, true);\n  AddScalarInput(true, \"mask2\", &ws);\n  AddScalarInput(1.0f, \"values2\", &ws);\n\n  unique_ptr<OperatorBase> op(CreateOperator(def, &ws));\n  EXPECT_NE(nullptr, op.get());\n\n  EXPECT_TRUE(op->Run());\n\n  Blob* unmasked_data_blob = ws.GetBlob(\"unmasked_data\");\n  EXPECT_NE(nullptr, unmasked_data_blob);\n\n  auto& unmasked_data = unmasked_data_blob->Get<TensorCPU>();\n  EXPECT_EQ(unmasked_data.size(), 1);\n\n  CHECK_EQ(unmasked_data.data<float>()[0], 1.0f);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/box_with_nms_limit_op.cc",
    "content": "#include \"box_with_nms_limit_op.h\"\n#include \"caffe2/utils/eigen_utils.h\"\n#include \"generate_proposals_op_util_nms.h\"\n\n#ifdef CAFFE2_USE_MKL\n#include \"caffe2/mkl/operators/operator_fallback_mkl.h\"\n#endif // CAFFE2_USE_MKL\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <class Derived, class Func>\nvector<int> filter_with_indices(\n    const Eigen::ArrayBase<Derived>& array,\n    const vector<int>& indices,\n    const Func& func) {\n  vector<int> ret;\n  for (auto& cur : indices) {\n    if (func(array[cur])) {\n      ret.push_back(cur);\n    }\n  }\n  return ret;\n}\n\n} // namespace\n\ntemplate <>\nbool BoxWithNMSLimitOp<CPUContext>::RunOnDevice() {\n  const auto& tscores = Input(0);\n  const auto& tboxes = Input(1);\n  auto* out_scores = Output(0);\n  auto* out_boxes = Output(1);\n  auto* out_classes = Output(2);\n\n  // tscores: (num_boxes, num_classes), 0 for background\n  if (tscores.ndim() == 4) {\n    CAFFE_ENFORCE_EQ(tscores.dim(2), 1, tscores.dim(2));\n    CAFFE_ENFORCE_EQ(tscores.dim(3), 1, tscores.dim(3));\n  } else {\n    CAFFE_ENFORCE_EQ(tscores.ndim(), 2, tscores.ndim());\n  }\n  CAFFE_ENFORCE(tscores.template IsType<float>(), tscores.meta().name());\n  // tboxes: (num_boxes, num_classes * 4)\n  if (tboxes.ndim() == 4) {\n    CAFFE_ENFORCE_EQ(tboxes.dim(2), 1, tboxes.dim(2));\n    CAFFE_ENFORCE_EQ(tboxes.dim(3), 1, tboxes.dim(3));\n  } else {\n    CAFFE_ENFORCE_EQ(tboxes.ndim(), 2, tboxes.ndim());\n  }\n  CAFFE_ENFORCE(tboxes.template IsType<float>(), tboxes.meta().name());\n\n  int N = tscores.dim(0);\n  int num_classes = tscores.dim(1);\n\n  CAFFE_ENFORCE_EQ(N, tboxes.dim(0));\n  CAFFE_ENFORCE_EQ(num_classes * 4, tboxes.dim(1));\n\n  int batch_size = 1;\n  vector<float> batch_splits_default(1, tscores.dim(0));\n  const float* batch_splits_data = batch_splits_default.data();\n  if (InputSize() > 2) {\n    // tscores and tboxes have items from multiple images in a batch. Get the\n    // corresponding batch splits from input.\n    const auto& tbatch_splits = Input(2);\n    CAFFE_ENFORCE_EQ(tbatch_splits.ndim(), 1);\n    batch_size = tbatch_splits.dim(0);\n    batch_splits_data = tbatch_splits.data<float>();\n  }\n  Eigen::Map<const EArrXf> batch_splits(batch_splits_data, batch_size);\n  CAFFE_ENFORCE_EQ(batch_splits.sum(), N);\n\n  out_scores->Resize(0);\n  out_boxes->Resize(0, 4);\n  out_classes->Resize(0);\n\n  TensorCPU* out_keeps = nullptr;\n  TensorCPU* out_keeps_size = nullptr;\n  if (OutputSize() > 4) {\n    out_keeps = Output(4);\n    out_keeps_size = Output(5);\n    out_keeps->Resize(0);\n    out_keeps_size->Resize(batch_size, num_classes);\n  }\n\n  vector<int> total_keep_per_batch(batch_size);\n  int offset = 0;\n  for (int b = 0; b < batch_splits.size(); ++b) {\n    int num_boxes = batch_splits(b);\n    Eigen::Map<const ERArrXXf> scores(\n        tscores.data<float>() + offset * tscores.dim(1),\n        num_boxes,\n        tscores.dim(1));\n    Eigen::Map<const ERArrXXf> boxes(\n        tboxes.data<float>() + offset * tboxes.dim(1),\n        num_boxes,\n        tboxes.dim(1));\n\n    // To store updated scores if SoftNMS is used\n    ERArrXXf soft_nms_scores(num_boxes, tscores.dim(1));\n    vector<vector<int>> keeps(num_classes);\n\n    // Perform nms to each class\n    // skip j = 0, because it's the background class\n    int total_keep_count = 0;\n    for (int j = 1; j < num_classes; j++) {\n      auto cur_scores = scores.col(j);\n      auto inds = utils::GetArrayIndices(cur_scores > score_thres_);\n      auto cur_boxes = boxes.block(0, j * 4, boxes.rows(), 4);\n\n      if (soft_nms_enabled_) {\n        auto cur_soft_nms_scores = soft_nms_scores.col(j);\n        keeps[j] = utils::soft_nms_cpu(\n            &cur_soft_nms_scores,\n            cur_boxes,\n            cur_scores,\n            inds,\n            soft_nms_sigma_,\n            nms_thres_,\n            soft_nms_min_score_thres_,\n            soft_nms_method_);\n      } else {\n        std::sort(\n            inds.data(),\n            inds.data() + inds.size(),\n            [&cur_scores](int lhs, int rhs) {\n              return cur_scores(lhs) > cur_scores(rhs);\n            });\n        keeps[j] = utils::nms_cpu(cur_boxes, cur_scores, inds, nms_thres_);\n      }\n      total_keep_count += keeps[j].size();\n    }\n\n    if (soft_nms_enabled_) {\n      // Re-map scores to the updated SoftNMS scores\n      new (&scores) Eigen::Map<const ERArrXXf>(\n          soft_nms_scores.data(),\n          soft_nms_scores.rows(),\n          soft_nms_scores.cols());\n    }\n\n    // Limit to max_per_image detections *over all classes*\n    if (detections_per_im_ > 0 && total_keep_count > detections_per_im_) {\n      // merge all scores together and sort\n      auto get_all_scores_sorted = [&scores, &keeps, total_keep_count]() {\n        EArrXf ret(total_keep_count);\n\n        int ret_idx = 0;\n        for (int i = 1; i < keeps.size(); i++) {\n          auto& cur_keep = keeps[i];\n          auto cur_scores = scores.col(i);\n          auto cur_ret = ret.segment(ret_idx, cur_keep.size());\n          utils::GetSubArray(cur_scores, utils::AsEArrXt(keeps[i]), &cur_ret);\n          ret_idx += cur_keep.size();\n        }\n\n        std::sort(ret.data(), ret.data() + ret.size());\n\n        return ret;\n      };\n\n      // Compute image thres based on all classes\n      auto all_scores_sorted = get_all_scores_sorted();\n      DCHECK_GT(all_scores_sorted.size(), detections_per_im_);\n      auto image_thresh =\n          all_scores_sorted[all_scores_sorted.size() - detections_per_im_];\n\n      total_keep_count = 0;\n      // filter results with image_thresh\n      for (int j = 1; j < num_classes; j++) {\n        auto& cur_keep = keeps[j];\n        auto cur_scores = scores.col(j);\n        keeps[j] = filter_with_indices(\n            cur_scores, cur_keep, [&image_thresh](float sc) {\n              return sc >= image_thresh;\n            });\n        total_keep_count += keeps[j].size();\n      }\n    }\n    total_keep_per_batch[b] = total_keep_count;\n\n    // Write results\n    int cur_start_idx = out_scores->dim(0);\n    out_scores->Extend(total_keep_count, 50, &context_);\n    out_boxes->Extend(total_keep_count, 50, &context_);\n    out_classes->Extend(total_keep_count, 50, &context_);\n\n    int cur_out_idx = 0;\n    for (int j = 1; j < num_classes; j++) {\n      auto cur_scores = scores.col(j);\n      auto cur_boxes = boxes.block(0, j * 4, boxes.rows(), 4);\n      auto& cur_keep = keeps[j];\n      Eigen::Map<EArrXf> cur_out_scores(\n          out_scores->mutable_data<float>() + cur_start_idx + cur_out_idx,\n          cur_keep.size());\n      Eigen::Map<ERArrXXf> cur_out_boxes(\n          out_boxes->mutable_data<float>() + (cur_start_idx + cur_out_idx) * 4,\n          cur_keep.size(),\n          4);\n      Eigen::Map<EArrXf> cur_out_classes(\n          out_classes->mutable_data<float>() + cur_start_idx + cur_out_idx,\n          cur_keep.size());\n\n      utils::GetSubArray(\n          cur_scores, utils::AsEArrXt(cur_keep), &cur_out_scores);\n      utils::GetSubArrayRows(\n          cur_boxes, utils::AsEArrXt(cur_keep), &cur_out_boxes);\n      for (int k = 0; k < cur_keep.size(); k++) {\n        cur_out_classes[k] = static_cast<float>(j);\n      }\n\n      cur_out_idx += cur_keep.size();\n    }\n\n    if (out_keeps) {\n      out_keeps->Extend(total_keep_count, 50, &context_);\n\n      Eigen::Map<EArrXi> out_keeps_arr(\n          out_keeps->mutable_data<int>() + cur_start_idx, total_keep_count);\n      Eigen::Map<EArrXi> cur_out_keeps_size(\n          out_keeps_size->mutable_data<int>() + b * num_classes, num_classes);\n\n      cur_out_idx = 0;\n      for (int j = 0; j < num_classes; j++) {\n        out_keeps_arr.segment(cur_out_idx, keeps[j].size()) =\n            utils::AsEArrXt(keeps[j]);\n        cur_out_keeps_size[j] = keeps[j].size();\n        cur_out_idx += keeps[j].size();\n      }\n    }\n\n    offset += num_boxes;\n  }\n\n  if (OutputSize() > 3) {\n    auto* batch_splits_out = Output(3);\n    batch_splits_out->Resize(batch_size);\n    Eigen::Map<EArrXf> batch_splits_out_map(\n        batch_splits_out->mutable_data<float>(), batch_size);\n    batch_splits_out_map =\n        Eigen::Map<const EArrXi>(total_keep_per_batch.data(), batch_size)\n            .cast<float>();\n  }\n\n  return true;\n}\n\nnamespace {\n\nREGISTER_CPU_OPERATOR(BoxWithNMSLimit, BoxWithNMSLimitOp<CPUContext>);\n\n#ifdef CAFFE2_HAS_MKL_DNN\nREGISTER_MKL_OPERATOR(\n    BoxWithNMSLimit,\n    mkl::MKLFallbackOp<BoxWithNMSLimitOp<CPUContext>>);\n#endif // CAFFE2_HAS_MKL_DNN\n\nOPERATOR_SCHEMA(BoxWithNMSLimit)\n    .NumInputs(2, 3)\n    .NumOutputs(3, 6)\n    .SetDoc(R\"DOC(\nApply NMS to each class (except background) and limit the number of\nreturned boxes.\n)DOC\")\n    .Arg(\"score_thresh\", \"(float) TEST.SCORE_THRESH\")\n    .Arg(\"nms\", \"(float) TEST.NMS\")\n    .Arg(\"detections_per_im\", \"(int) TEST.DEECTIONS_PER_IM\")\n    .Arg(\"soft_nms_enabled\", \"(bool) TEST.SOFT_NMS.ENABLED\")\n    .Arg(\"soft_nms_method\", \"(string) TEST.SOFT_NMS.METHOD\")\n    .Arg(\"soft_nms_sigma\", \"(float) TEST.SOFT_NMS.SIGMA\")\n    .Arg(\n        \"soft_nms_min_score_thres\",\n        \"(float) Lower bound on updated scores to discard boxes\")\n    .Input(0, \"scores\", \"Scores, size (count, num_classes)\")\n    .Input(\n        1,\n        \"boxes\",\n        \"Bounding box for each class, size (count, num_classes * 4)\")\n    .Input(\n        2,\n        \"batch_splits\",\n        \"Tensor of shape (batch_size) with each element denoting the number \"\n        \"of RoIs/boxes belonging to the corresponding image in batch. \"\n        \"Sum should add up to total count of scores/boxes.\")\n    .Output(0, \"scores\", \"Filtered scores, size (n)\")\n    .Output(1, \"boxes\", \"Filtered boxes, size (n, 4)\")\n    .Output(2, \"classes\", \"Class id for each filtered score/box, size (n)\")\n    .Output(\n        3,\n        \"batch_splits\",\n        \"Output batch splits for scores/boxes after applying NMS\")\n    .Output(4, \"keeps\", \"Optional filtered indices, size (n)\")\n    .Output(\n        5,\n        \"keeps_size\",\n        \"Optional number of filtered indices per class, size (num_classes)\");\n\nSHOULD_NOT_DO_GRADIENT(BoxWithNMSLimit);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/box_with_nms_limit_op.h",
    "content": "// Copyright 2004-present Facebook. All Rights Reserved.\n\n#ifndef BOX_WITH_NMS_AND_LIMIT_OP_H_\n#define BOX_WITH_NMS_AND_LIMIT_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\n// C++ implementation of function insert_box_results_with_nms_and_limit()\ntemplate <class Context>\nclass BoxWithNMSLimitOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  BoxWithNMSLimitOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        score_thres_(\n            OperatorBase::GetSingleArgument<float>(\"score_thresh\", 0.05)),\n        nms_thres_(OperatorBase::GetSingleArgument<float>(\"nms\", 0.3)),\n        detections_per_im_(\n            OperatorBase::GetSingleArgument<int>(\"detections_per_im\", 100)),\n        soft_nms_enabled_(\n            OperatorBase::GetSingleArgument<bool>(\"soft_nms_enabled\", false)),\n        soft_nms_method_str_(OperatorBase::GetSingleArgument<std::string>(\n            \"soft_nms_method\",\n            \"linear\")),\n        soft_nms_sigma_(\n            OperatorBase::GetSingleArgument<float>(\"soft_nms_sigma\", 0.5)),\n        soft_nms_min_score_thres_(OperatorBase::GetSingleArgument<float>(\n            \"soft_nms_min_score_thres\",\n            0.001)) {\n    CAFFE_ENFORCE(\n        soft_nms_method_str_ == \"linear\" || soft_nms_method_str_ == \"gaussian\",\n        \"Unexpected soft_nms_method\");\n    soft_nms_method_ = (soft_nms_method_str_ == \"linear\") ? 1 : 2;\n  }\n\n  ~BoxWithNMSLimitOp() {}\n\n  bool RunOnDevice() override;\n\n protected:\n  // TEST.SCORE_THRESH\n  float score_thres_ = 0.05;\n  // TEST.NMS\n  float nms_thres_ = 0.3;\n  // TEST.DETECTIONS_PER_IM\n  int detections_per_im_ = 100;\n  // TEST.SOFT_NMS.ENABLED\n  bool soft_nms_enabled_ = false;\n  // TEST.SOFT_NMS.METHOD\n  std::string soft_nms_method_str_ = \"linear\";\n  unsigned int soft_nms_method_ = 1; // linear\n  // TEST.SOFT_NMS.SIGMA\n  float soft_nms_sigma_ = 0.5;\n  // Lower-bound on updated scores to discard boxes\n  float soft_nms_min_score_thres_ = 0.001;\n};\n\n} // namespace caffe2\n#endif // BOX_WITH_NMS_AND_LIMIT_OP_H_\n"
  },
  {
    "path": "caffe2/operators/cast_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/cast_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\ntemplate <typename DstType, typename SrcType>\nbool CastOp<CPUContext>::DoRunWithType() {\n  auto& input = Input(0);\n  auto* output = Output(0);\n  output->ResizeLike(input);\n  const auto* data = input.template data<SrcType>();\n  auto* out = output->template mutable_data<DstType>();\n  auto N = input.size();\n  for (TIndex i = 0; i < N; ++i) {\n    out[i] = static_cast<DstType>(data[i]);\n  }\n  return true;\n}\n\ntemplate <>\nvoid CastOp<CPUContext>::SetBody(TensorProto_DataType to) {\n  switch (to) {\n    case TensorProto_DataType_FLOAT:\n      // body_ = &CastOp::DoRunIncFp16WithDstType<float>;\n      body_ = &CastOp<CPUContext>::DoRunWithDstType<float>;\n      break;\n    case TensorProto_DataType_INT32:\n      body_ = &CastOp<CPUContext>::DoRunWithDstType<int>;\n      break;\n    case TensorProto_DataType_BYTE:\n      LOG(FATAL) << \"BYTE is deprecated\";\n      break;\n    case TensorProto_DataType_STRING:\n      CAFFE_THROW(\"Casting to and from strings is not supported yet\");\n      // break;\n    case TensorProto_DataType_BOOL:\n      body_ = &CastOp<CPUContext>::DoRunWithDstType<bool>;\n      break;\n    case TensorProto_DataType_UINT8:\n      body_ = &CastOp<CPUContext>::DoRunWithDstType<uint8_t>;\n      break;\n    case TensorProto_DataType_INT8:\n      body_ = &CastOp<CPUContext>::DoRunWithDstType<int8_t>;\n      break;\n    case TensorProto_DataType_UINT16:\n      body_ = &CastOp<CPUContext>::DoRunWithDstType<uint16_t>;\n      break;\n    case TensorProto_DataType_INT16:\n      body_ = &CastOp<CPUContext>::DoRunWithDstType<int16_t>;\n      break;\n    case TensorProto_DataType_INT64:\n      body_ = &CastOp<CPUContext>::DoRunWithDstType<int64_t>;\n      break;\n    case TensorProto_DataType_FLOAT16:\n      CAFFE_THROW(\"Casting to and from float16 on CPU is not supported yet\");\n      // break;\n    case TensorProto_DataType_DOUBLE:\n      //body_ = &CastOp::DoRunIncFp16WithDstType<double>;\n      body_ = &CastOp<CPUContext>::DoRunWithDstType<double>;\n      break;\n    case TensorProto_DataType_UNDEFINED:\n      CAFFE_THROW(\"Cast op must have 'to' argument of type DataType\");\n      // break;\n    default:\n      CAFFE_THROW(\"Unexpected 'to' argument value: \", to);\n  }\n}\n\ntemplate <>\ntemplate <typename DstType>\nbool CastOp<CPUContext>::DoRunWithDstType() {\n  return DispatchHelper<\n      TensorTypes<\n          float,\n          int32_t,\n          bool,\n          uint8_t,\n          int8_t,\n          uint16_t,\n          int16_t,\n          int64_t,\n          double>,\n      DstType>::call(this, Input(0));\n}\n\nREGISTER_CPU_OPERATOR(Cast, CastOp<CPUContext>);\n\nOPERATOR_SCHEMA(Cast)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(\n        [](const OperatorDef& def, const vector<TensorShape>& in) {\n          ArgumentHelper helper(def);\n          vector<TensorShape> out;\n          out.push_back(in[0]);\n          out[0].set_data_type(cast::GetCastDataType(helper, \"to\"));\n          return out;\n        })\n    .SetDoc(R\"DOC(\nThe operator casts the elements of a given input tensor to a data type\nspecified by the 'to' argument and returns an output tensor of the same size in\nthe converted type. The 'to' argument must be one of the data types specified\nin the 'DataType' enum field in the TensorProto message. If the 'to' argument\nis not provided or is not one of the enumerated types in DataType, Caffe2\nthrows an Enforce error.\n\nNOTE: Casting to and from strings is not supported yet.\n)DOC\")\n    .Arg(\n        \"to\",\n        \"The data type to which the elements of the input tensor are cast.\"\n        \"Strictly must be one of the types from DataType enum in TensorProto\")\n    .Input(0, \"input\", \"Input tensor to be cast.\")\n    .Output(\n        0,\n        \"output\",\n        \"Output tensor with the same shape as input with type \"\n        \"specified by the 'to' argument\");\n\n// Some Casts are compatible with gradients, but for now we don't support it\n// GRADIENT_NOT_IMPLEMENTED_YET(Cast);\n\nclass GetCastGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n\n    vector<OperatorDef> defs = SingleGradientDef(\"Cast\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n\n    // now modify the arguments in defs[0]\n    ArgumentHelper argsHelper(def_);\n\n    auto to_name = cast::GetCastDataType(argsHelper, \"to\");\n\n    CAFFE_ENFORCE(\n        argsHelper.HasSingleArgumentOfType<string>(\"from_type\") ||\n            argsHelper.HasSingleArgumentOfType<int>(\"from_type\"),\n        \"Argument 'from_type' of type int or string\"\n        \" is required to get the gradient of CastOp\");\n\n    auto from_name = cast::GetCastDataType(argsHelper, \"from_type\");\n    Argument *to = defs[0].add_arg();\n    to->set_name(\"to\");\n    to->set_i(from_name);\n\n    Argument *from = defs[0].add_arg();\n    from->set_name(\"from_type\");\n    from->set_i(to_name);\n\n    return defs;\n  }\n\n  bool CopyArguments() const override {\n    return false;\n  }\n};\n\nREGISTER_GRADIENT(Cast, GetCastGradient);\n\n\n\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/cast_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/cast_op.h\"\n#include \"caffe2/utils/conversions.h\"\n\nnamespace caffe2 {\n\ntemplate <typename DstType, typename SrcType>\n__global__ void CastKernel(const int N, const SrcType* X, DstType* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    // Y[i] = static_cast<DstType>(X[i]);\n    Y[i] = convert::To<SrcType, DstType>(X[i]);\n  }\n}\n\ntemplate <>\ntemplate <typename DstType, typename SrcType>\nbool CastOp<CUDAContext>::DoRunWithType() {\n  auto& input = Input(0);\n  auto* output = Output(0);\n  output->ResizeLike(input);\n  const auto* data = input.template data<SrcType>();\n  auto* out = output->template mutable_data<DstType>();\n  DCHECK(input.size() < INT_MAX);\n  int N = input.size();\n  if (N == 0) {\n    // skip the rest of the computation if input is empty\n    return true;\n  }\n  CastKernel<DstType, SrcType>\n      <<<CAFFE_GET_BLOCKS(N),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(N, data, out);\n  return true;\n}\n\ntemplate <>\ntemplate <typename DstType>\nbool CastOp<CUDAContext>::DoRunWithDstType() {\n  return DispatchHelper<\n      TensorTypes<\n          float,\n          int32_t,\n          bool,\n          uint8_t,\n          int8_t,\n          uint16_t,\n          int16_t,\n          int64_t,\n          double>,\n      DstType>::call(this, Input(0));\n}\n\n// specific version that allows for casting to fp16\ntemplate <>\ntemplate <>\nbool CastOp<CUDAContext>::DoRunWithDstType<float>() {\n  return DispatchHelper<\n      TensorTypes<\n          float,\n          float16,\n          int32_t,\n          bool,\n          uint8_t,\n          int8_t,\n          uint16_t,\n          int16_t,\n          int64_t,\n          double>,\n      float /* DstType */>::call(this, Input(0));\n}\n\n// specific version for casting _from_ fp16\ntemplate <>\ntemplate <>\nbool CastOp<CUDAContext>::DoRunWithDstType<float16>() {\n  return DispatchHelper<\n      TensorTypes<\n          float,\n          float16>,\n      float16 /* DstType */>::call(this, Input(0));\n}\ntemplate <>\nvoid CastOp<CUDAContext>::SetBody(TensorProto_DataType to) {\n  switch (to) {\n    case TensorProto_DataType_FLOAT:\n      body_ = &CastOp<CUDAContext>::DoRunWithDstType<float>;\n      break;\n    case TensorProto_DataType_INT32:\n      body_ = &CastOp<CUDAContext>::DoRunWithDstType<int>;\n      break;\n    case TensorProto_DataType_BYTE:\n      LOG(FATAL) << \"BYTE is deprecated\";\n      break;\n    case TensorProto_DataType_STRING:\n      CAFFE_THROW(\"Casting to and from strings is not supported yet\");\n      // break;\n    case TensorProto_DataType_BOOL:\n      body_ = &CastOp<CUDAContext>::DoRunWithDstType<bool>;\n      break;\n    case TensorProto_DataType_UINT8:\n      body_ = &CastOp<CUDAContext>::DoRunWithDstType<uint8_t>;\n      break;\n    case TensorProto_DataType_INT8:\n      body_ = &CastOp<CUDAContext>::DoRunWithDstType<int8_t>;\n      break;\n    case TensorProto_DataType_UINT16:\n      body_ = &CastOp<CUDAContext>::DoRunWithDstType<uint16_t>;\n      break;\n    case TensorProto_DataType_INT16:\n      body_ = &CastOp<CUDAContext>::DoRunWithDstType<int16_t>;\n      break;\n    case TensorProto_DataType_INT64:\n      body_ = &CastOp<CUDAContext>::DoRunWithDstType<int64_t>;\n      break;\n    case TensorProto_DataType_FLOAT16:\n      body_ = &CastOp<CUDAContext>::DoRunWithDstType<float16>;\n      break;\n    case TensorProto_DataType_DOUBLE:\n      body_ = &CastOp<CUDAContext>::DoRunWithDstType<double>;\n      break;\n    case TensorProto_DataType_UNDEFINED:\n      CAFFE_THROW(\"Cast op must have 'to' argument of type DataType\");\n      // break;\n    default:\n      CAFFE_THROW(\"Unexpected 'to' argument value: \", to);\n  }\n}\n\nREGISTER_CUDA_OPERATOR(Cast, CastOp<CUDAContext>);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/cast_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/cast.h\"\n#include \"caffe2/utils/conversions.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/types.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass CastOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  CastOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    const ArgumentHelper helper(operator_def);\n    TensorProto_DataType to = cast::GetCastDataType(helper, \"to\");\n    TensorProto_DataType from = cast::GetCastDataType(helper, \"from_type\");\n\n    SetBody(to);\n  }\n\n  bool RunOnDevice() override {\n    return (this->*body_)();\n  }\n\n  // Allow for Context-specific implementations\n  void SetBody(TensorProto_DataType to);\n\n  template <typename DstType>\n  bool DoRunWithDstType();\n\n  template <typename DstType, typename SrcType>\n  bool DoRunWithType() {\n    auto& input = Input(0);\n    auto* output = Output(0);\n    output->ResizeLike(input);\n    const auto* data = input.template data<SrcType>();\n    auto* out = output->template mutable_data<DstType>();\n    auto N = input.size();\n    for (TIndex i = 0; i < N; ++i) {\n      out[i] = static_cast<DstType>(data[i]);\n    }\n    return true;\n  }\n\n private:\n  bool (CastOp::*body_)();\n};\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/ceil_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/ceil_op.h\"\n\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Ceil, CeilOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Ceil)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nCeil takes one input data (Tensor<T>) and produces one output data\n(Tensor<T>) where the ceil function, y = ceil(x), is applied to\nthe tensor elementwise. Currently supports only float32.\n)DOC\")\n    .Input(0, \"X\", \"ND input tensor\")\n    .Output(0, \"Y\", \"ND input tensor\");\n\n// TODO: Write gradient for this when needed\nGRADIENT_NOT_IMPLEMENTED_YET(Ceil);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/ceil_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/ceil_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void CeilKernel(const int N, const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = std::ceil(X[i]);\n  }\n}\n\ntemplate <>\nbool CeilOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE_GT(X.size(), 0);\n  Y->ResizeLike(X);\n  CeilKernel<<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(), X.data<float>(), Y->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Ceil, CeilOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/ceil_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CEIL_OP_H_\n#define CAFFE2_OPERATORS_CEIL_OP_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass CeilOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(CeilOp);\n\n  bool RunOnDevice() override {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n    Y->ResizeLike(X);\n\n    const float* Xdata = X.template data<float>();\n    float* Ydata = Y->template mutable_data<float>();\n    for (int i = 0; i < X.size(); ++i) {\n      Ydata[i] = std::ceil(Xdata[i]);\n    }\n    return true;\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CEIL_OP_H_\n"
  },
  {
    "path": "caffe2/operators/channel_backprop_stats_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/channel_backprop_stats_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool ChannelBackpropStatsOp<CPUContext>::RunOnDevice() {\n  const auto& X = Input(INPUT);\n  const auto& dY = Input(OUTPUT_GRAD);\n  CAFFE_ENFORCE(X.ndim() >= 3 && X.ndim() <= 5);\n  const int N = X.dim32(0);\n  const int C = X.dim32(1);\n  const int H = X.dim32(2);\n  const int W = X.ndim() > 3 ? X.dim32(3) : 1;\n  const int D = X.ndim() > 4 ? X.dim32(4) : 1;\n\n  const int sampleSize = H * W * D;\n\n  Output(SCALE_GRAD)->Resize(C);\n  Output(BIAS_GRAD)->Resize(C);\n  auto* dScale = Output(SCALE_GRAD);\n  auto* dBias = Output(BIAS_GRAD);\n\n  ConstEigenArrayMap<float> X_arr(X.data<float>(), sampleSize, N * C);\n  ConstEigenArrayMap<float> dY_arr(dY.data<float>(), sampleSize, N * C);\n  ConstEigenVectorArrayMap<float> mean_arr(Input(SAVED_MEAN).data<float>(), C);\n  ConstEigenVectorArrayMap<float> inv_stddev_arr(\n      Input(SAVED_INV_STDDEV).data<float>(), C);\n  EigenVectorArrayMap<float> dBias_arr(dBias->mutable_data<float>(), C);\n  EigenVectorArrayMap<float> dScale_arr(dScale->mutable_data<float>(), C);\n\n  dBias_arr.setZero();\n  dScale_arr.setZero();\n\n  for (int nc = 0; nc < N * C; ++nc) {\n    int c = nc % C;\n    dBias_arr(c) += dY_arr.col(nc).sum();\n    dScale_arr(c) +=\n        ((X_arr.col(nc) - mean_arr(c)) * inv_stddev_arr(c) * dY_arr.col(nc))\n            .sum();\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(ChannelBackpropStats, ChannelBackpropStatsOp<CPUContext>);\n\nOPERATOR_SCHEMA(ChannelBackpropStats)\n    .NumInputs(4)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nGiven an input tensor in NCHW format, the gradient for the output of SpatialBN\nand the per-channel mean and inverse std var vectors for the input, computes the\nper-channel bias and scale gradient to be used during the backward pass for\nsubsequent spatial batch normalization gradient calculation. Typically, the\nresults of this op are subsequently reduced over multiple devices to obtain\nstatistics over a larger batch size in cases where the batch size for a single\nmodel copy is too low to yield the full benefit of batch normalization. The\nresulting bias and scale can then be plugged back into SpatialBNGradient to get\nresults over the larger batch size )DOC\")\n    .Input(0, \"X\", \"The input 4-dimensional tensor of shape NCHW\")\n    .Input(\n        1,\n        \"mean\",\n        \"The mean saved from the forward pass as a 1-dimensional \"\n        \"tensor of size C.\")\n    .Input(\n        2,\n        \"inv_std\",\n        \"The saved inverse standard deviation as a 1-dimensional tensor \"\n        \"of size C.\")\n    .Input(\n        3,\n        \"output_grad\",\n        \"Gradient for the output layer of SpatialBN, here used as input \"\n        \"because we are on the backward pass\")\n    .Output(0, \"scale_grad\", \"Gradient for the scale vector\")\n    .Output(1, \"bias_grad\", \"Gradient for the bias vector\");\nSHOULD_NOT_DO_GRADIENT(ChannelBackpropStats);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/channel_backprop_stats_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CHANNEL_BACKPROP_STATS_OP_H\n#define CHANNEL_BACKPROP_STATS_OP_H\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass ChannelBackpropStatsOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ChannelBackpropStatsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  ~ChannelBackpropStatsOp() {}\n\n  bool RunOnDevice() override {\n    return true;\n  }\n\n protected:\n  INPUT_TAGS(INPUT, SAVED_MEAN, SAVED_INV_STDDEV, OUTPUT_GRAD);\n  OUTPUT_TAGS(SCALE_GRAD, BIAS_GRAD);\n};\n\n} // namespace caffe2\n\n#endif\n"
  },
  {
    "path": "caffe2/operators/channel_shuffle_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"channel_shuffle_op.h\"\n\nnamespace caffe2 {\n\nclass GetChannelShuffleGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        def_.type() + \"Gradient\",\n        \"\",\n        vector<string>{GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_CPU_OPERATOR(ChannelShuffle, ChannelShuffleOp<CPUContext>);\nREGISTER_CPU_OPERATOR(\n    ChannelShuffleGradient,\n    ChannelShuffleGradientOp<CPUContext>);\nREGISTER_GRADIENT(ChannelShuffle, GetChannelShuffleGradient);\nOPERATOR_SCHEMA(ChannelShuffle)\n    .IdenticalTypeAndShape()\n    .NumInputs(1)\n    .NumOutputs(1);\nOPERATOR_SCHEMA(ChannelShuffleGradient)\n    .IdenticalTypeAndShape()\n    .NumInputs(1)\n    .NumOutputs(1);\n}\n"
  },
  {
    "path": "caffe2/operators/channel_shuffle_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n#include \"caffe2/operators/conv_pool_op_base.h\"\n\nnamespace caffe2 {\n\ntemplate <typename Context>\nclass ChannelShuffleOp final : public ConvPoolOpBase<Context> {\n public:\n  USE_OPERATOR_FUNCTIONS(Context);\n  ChannelShuffleOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<Context>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(\n        this->order_ == StorageOrder::NCHW,\n        \"ChannelShuffleOp only supports NCHW order\");\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    const auto& X = Input(0);\n    auto* Y = Output(0);\n    Y->ResizeLike(X);\n    const auto C = X.dim32(1);\n    CAFFE_ENFORCE(C % this->group_ == 0, \"\");\n    const auto K = C / this->group_;\n    const auto S = X.dim32(2) * X.dim32(3);\n    const auto G = this->group_;\n    for (auto n = 0; n < X.dim32(0); ++n) {\n      for (auto g = 0; g < G; ++g) {\n        // Scatter the group g block (of size KxS) to output channels\n        // g + 0 * G, g + 1 * G, g + 2 * G, g + G * (K - 1) etc.\n        math::CopyMatrix<Context>(\n            X.itemsize(),\n            K,\n            S,\n            X.template data<float>() + g * K * S + n * C * S,\n            S,\n            Y->template mutable_data<float>() + g * S + n * C * S,\n            G * S,\n            &context_,\n            X.meta().copy());\n      }\n    }\n    return true;\n  }\n};\n\ntemplate <typename Context>\nclass ChannelShuffleGradientOp final : public ConvPoolOpBase<Context> {\n public:\n  USE_OPERATOR_FUNCTIONS(Context);\n  ChannelShuffleGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<Context>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(\n        this->order_ == StorageOrder::NCHW,\n        \"ChannelShuffleOp only supports NCHW order\");\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override {\n    const auto& dY = Input(0);\n    auto* dX = Output(0);\n    dX->ResizeLike(dY);\n    const auto C = dY.dim32(1);\n    CAFFE_ENFORCE(C % this->group_ == 0, \"\");\n    const auto K = C / this->group_;\n    const auto S = dY.dim32(2) * dY.dim32(3);\n    const auto G = this->group_;\n    for (auto n = 0; n < dY.dim32(0); ++n) {\n      for (auto g = 0; g < G; ++g) {\n        // Gather the group g block (of size KxS) from output channels\n        // g + 0 * G, g + 1 * G, g + 2 * G, g + G * (K - 1) etc.\n        math::CopyMatrix<Context>(\n            dY.itemsize(),\n            K,\n            S,\n            dY.template data<float>() + g * S + n * C * S,\n            G * S,\n            dX->template mutable_data<float>() + g * K * S + n * C * S,\n            S,\n            &context_,\n            dY.meta().copy());\n      }\n    }\n    return true;\n  }\n};\n}\n"
  },
  {
    "path": "caffe2/operators/channel_shuffle_op_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"channel_shuffle_op.h\"\n\nnamespace caffe2 {\n\n__global__ void ChannelShuffleKernel(\n    const int N,\n    const int S,\n    const int C,\n    const int G,\n    const int K,\n    const float* Xdata,\n    float* Ydata) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    const int out_s = i % S;\n    const int i_2 = i / S;\n    const int out_c = i_2 % C;\n    const int n = i_2 / C;\n\n    const int g = out_c % G;\n    const int k = out_c / G;\n    const int in_c = k + K * g;\n    Ydata[out_s + S * out_c + S * C * n] = Xdata[out_s + S * in_c + S * C * n];\n  }\n}\n\ntemplate <>\nbool ChannelShuffleOp<CUDAContext>::RunOnDeviceWithOrderNCHW() {\n  const auto& X = Input(0);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n  const auto C = X.dim32(1);\n  CAFFE_ENFORCE(C % this->group_ == 0, \"\");\n  const auto K = C / this->group_;\n  const auto S = X.dim32(2) * X.dim32(3);\n  const auto G = this->group_;\n  ChannelShuffleKernel<<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(), S, C, G, K, X.data<float>(), Y->mutable_data<float>());\n  return true;\n}\n\ntemplate <>\nbool ChannelShuffleGradientOp<CUDAContext>::RunOnDeviceWithOrderNCHW() {\n  const auto& dY = Input(0);\n  auto* dX = Output(0);\n  dX->ResizeLike(dY);\n  const auto C = dY.dim32(1);\n  CAFFE_ENFORCE(C % this->group_ == 0, \"\");\n  const auto K = C / this->group_;\n  const auto S = dY.dim32(2) * dY.dim32(3);\n  const auto G = this->group_;\n  ChannelShuffleKernel<<<\n      CAFFE_GET_BLOCKS(dY.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      dY.size(), S, C, K, G, dY.data<float>(), dX->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(ChannelShuffle, ChannelShuffleOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    ChannelShuffleGradient,\n    ChannelShuffleGradientOp<CUDAContext>);\n}\n"
  },
  {
    "path": "caffe2/operators/channel_stats_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/channel_stats_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool ChannelStatsOp<CPUContext>::RunOnDevice() {\n  const auto& X = Input(INPUT);\n  CAFFE_ENFORCE(X.ndim() >= 3 && X.ndim() <= 5);\n  const int N = X.dim32(0);\n  const int C = X.dim32(1);\n  const int H = X.dim32(2);\n  const int W = X.ndim() > 3 ? X.dim32(3) : 1;\n  const int D = X.ndim() > 4 ? X.dim32(4) : 1;\n\n  const int sampleSize = H * W * D;\n\n  Output(SUM)->Resize(C);\n  Output(SUMSQ)->Resize(C);\n  EigenVectorArrayMap<float> sum(Output(SUM)->mutable_data<float>(), C);\n  EigenVectorArrayMap<float> sumsq(Output(SUMSQ)->mutable_data<float>(), C);\n\n  sum.setZero();\n  sumsq.setZero();\n  ConstEigenArrayMap<float> X_arr(X.data<float>(), sampleSize, N * C);\n  auto index = 0;\n  for (int n = 0; n < N; ++n) {\n    for (int c = 0; c < C; ++c) {\n      sum(c) += X_arr.col(index).sum();\n      sumsq(c) += X_arr.col(index).matrix().squaredNorm();\n      index++;\n    }\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(ChannelStats, ChannelStatsOp<CPUContext>);\n\nOPERATOR_SCHEMA(ChannelStats)\n    .NumInputs(1)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nGiven an input tensor in NCHW format, computes the sum of all elements per\nchannel and the sum of all elements squared per channel. These values can be\nreduced across multiple batches and used to obtain the mean and variance across\nthe full set of batches. Using the new mean and variance as input to SpatialBN\nhas the effect of changing the batch size over which SpatialBN is applied.\n)DOC\")\n\n    .Input(0, \"X\", \"The input 4-dimensional tensor of shape NCHW\")\n    .Output(\n        0,\n        \"sum\",\n        \"The output 1-dimensional tensor of size C containing the sum of \"\n        \"elements of X per channel.\")\n    .Output(\n        1,\n        \"sumsq\",\n        \"The output 1-dimensional tensor of size C containing the sum of \"\n        \"elements squared per channel.\");\nSHOULD_NOT_DO_GRADIENT(ChannelStats);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/channel_stats_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/channel_stats_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n// based on \"Optimizing Parallel Reduction in CUDA\" by Mark Harris\n\n// note - volatile keyword is needed to allow doing a warp reduction without\n// synchronization on recent architectures\ntemplate <unsigned int blockSize>\n__device__ void warpReduce(volatile float* sdata, unsigned int tid) {\n  // note - the if statements are \"free\" as they are resolved at compile time\n  if (blockSize >= 64)\n    sdata[tid] += sdata[tid + 32];\n  if (blockSize >= 32)\n    sdata[tid] += sdata[tid + 16];\n  if (blockSize >= 16)\n    sdata[tid] += sdata[tid + 8];\n  if (blockSize >= 8)\n    sdata[tid] += sdata[tid + 4];\n  if (blockSize >= 4)\n    sdata[tid] += sdata[tid + 2];\n  if (blockSize >= 2)\n    sdata[tid] += sdata[tid + 1];\n}\n\ntemplate <unsigned int blockSize>\n__global__ void ChannelStatsBlockKernel(\n    int N,\n    int C,\n    int valsPerChannel,\n    const float* inputData,\n    float* sums,\n    float* sumsq) {\n  __shared__ float sumData[blockSize];\n  __shared__ float sumSqData[blockSize];\n\n  auto tid = threadIdx.x;\n  auto numBlocksPerChannel = (valsPerChannel + blockSize - 1) / blockSize;\n  auto localBlockIndex = blockIdx.x % numBlocksPerChannel;\n  auto inputIndex = (blockIdx.x / numBlocksPerChannel) * valsPerChannel +\n      localBlockIndex * blockSize + tid;\n\n  sumData[tid] = 0;\n  sumSqData[tid] = 0;\n\n  if (localBlockIndex * blockSize + tid < valsPerChannel) {\n    sumData[tid] += inputData[inputIndex];\n    sumSqData[tid] += inputData[inputIndex] * inputData[inputIndex];\n  }\n\n  __syncthreads();\n  if (blockSize >= 512) {\n    if (tid < 256) {\n      sumData[tid] += sumData[tid + 256];\n      sumSqData[tid] += sumSqData[tid + 256];\n    }\n    __syncthreads();\n  }\n  if (blockSize >= 256) {\n    if (tid < 128) {\n      sumData[tid] += sumData[tid + 128];\n      sumSqData[tid] += sumSqData[tid + 128];\n    }\n    __syncthreads();\n  }\n  if (blockSize >= 128) {\n    if (tid < 64) {\n      sumData[tid] += sumData[tid + 64];\n      sumSqData[tid] += sumSqData[tid + 64];\n    }\n    __syncthreads();\n  }\n\n  if (tid < 32) {\n    warpReduce<blockSize>(sumData, tid);\n    warpReduce<blockSize>(sumSqData, tid);\n  }\n\n  // output block data sorted by C to simplify second reduction\n  if (tid == 0) {\n    auto n = blockIdx.x / numBlocksPerChannel / C;\n    auto c = (blockIdx.x / numBlocksPerChannel) % C;\n    auto outputIndex = (c * N + n) * numBlocksPerChannel + localBlockIndex;\n    sums[outputIndex] = sumData[0];\n    sumsq[outputIndex] = sumSqData[0];\n  }\n}\n\ntemplate <unsigned int blockSize>\n__global__ void ChannelStatsFinalSumsKernel(\n    int N,\n    int C,\n    int numSumsPerChannel,\n    const float* sumsScratch,\n    const float* sumsqScratch,\n    float* channelSums,\n    float* channelSumsq) {\n  __shared__ float sumData[blockSize];\n  __shared__ float sumSqData[blockSize];\n\n  auto tid = threadIdx.x;\n  auto inputIndex = blockIdx.x * N * numSumsPerChannel + tid;\n  sumData[tid] = 0;\n  sumSqData[tid] = 0;\n  for (auto i = inputIndex; i < (blockIdx.x + 1) * N * numSumsPerChannel;\n       i += blockSize) {\n    sumData[tid] += sumsScratch[i];\n    sumSqData[tid] += sumsqScratch[i];\n  }\n  __syncthreads();\n  if (blockSize >= 512) {\n    if (tid < 256) {\n      sumData[tid] += sumData[tid + 256];\n      sumSqData[tid] += sumSqData[tid + 256];\n    }\n    __syncthreads();\n  }\n  if (blockSize >= 256) {\n    if (tid < 128) {\n      sumData[tid] += sumData[tid + 128];\n      sumSqData[tid] += sumSqData[tid + 128];\n    }\n    __syncthreads();\n  }\n  if (blockSize >= 128) {\n    if (tid < 64) {\n      sumData[tid] += sumData[tid + 64];\n      sumSqData[tid] += sumSqData[tid + 64];\n    }\n    __syncthreads();\n  }\n  if (tid < 32) {\n    warpReduce<blockSize>(sumData, tid);\n    warpReduce<blockSize>(sumSqData, tid);\n  }\n\n  if (tid == 0) {\n    channelSums[blockIdx.x] = sumData[0];\n    channelSumsq[blockIdx.x] = sumSqData[0];\n  }\n}\n} // namespace\n\ntemplate <>\nbool ChannelStatsOp<CUDAContext>::RunOnDevice() {\n  const auto& X = Input(INPUT);\n  CAFFE_ENFORCE(X.ndim() >= 3 && X.ndim() <= 5);\n  const int N = X.dim32(0);\n  const int C = X.dim32(1);\n  const int H = X.dim32(2);\n  const int W = X.ndim() > 3 ? X.dim32(3) : 1;\n  const int D = X.ndim() > 4 ? X.dim32(4) : 1;\n\n  auto sum = Output(SUM);\n  auto sumsq = Output(SUMSQ);\n\n  const auto X_arr = X.data<float>();\n  const auto valsPerChannel = H * W * D;\n\n  const auto numBlocksPerChannel = CAFFE_GET_BLOCKS(valsPerChannel);\n  const auto numBlocksTotal = numBlocksPerChannel * N * C;\n\n  sumScratch_.Resize(numBlocksTotal);\n  sumsqScratch_.Resize(numBlocksTotal);\n\n  sum->Resize(C);\n  sumsq->Resize(C);\n\n  ChannelStatsBlockKernel<CAFFE_CUDA_NUM_THREADS>\n      <<<numBlocksTotal, CAFFE_CUDA_NUM_THREADS, 0, context_.cuda_stream()>>>(\n          N,\n          C,\n          valsPerChannel,\n          X_arr,\n          sumScratch_.mutable_data<float>(),\n          sumsqScratch_.mutable_data<float>());\n\n  ChannelStatsFinalSumsKernel<CAFFE_CUDA_NUM_THREADS>\n      <<<C, CAFFE_CUDA_NUM_THREADS, 0, context_.cuda_stream()>>>(\n          N,\n          C,\n          numBlocksPerChannel,\n          sumScratch_.data<float>(),\n          sumsqScratch_.data<float>(),\n          sum->mutable_data<float>(),\n          sumsq->mutable_data<float>());\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(ChannelStats, ChannelStatsOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/channel_stats_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CHANNEL_STATS_OP_H\n#define CAFFE2_OPERATORS_CHANNEL_STATS_OP_H\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass ChannelStatsOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ChannelStatsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  ~ChannelStatsOp() {}\n\n  bool RunOnDevice() override {\n    return true;\n  }\n\n protected:\n  INPUT_TAGS(INPUT);\n  OUTPUT_TAGS(SUM, SUMSQ);\n\n  Tensor<Context> sumScratch_;\n  Tensor<Context> sumsqScratch_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CHANNEL_STATS_OP_H\n"
  },
  {
    "path": "caffe2/operators/clip_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/clip_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool ClipOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n  EigenVectorMap<float>(Y->mutable_data<float>(), Y->size()) =\n      ConstEigenVectorMap<float>(X.data<float>(), X.size())\n          .cwiseMax(min_)\n          .cwiseMin(max_);\n  return true;\n}\n\ntemplate <>\nbool ClipGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  CAFFE_ENFORCE_GT(Y.size(), 0);\n  CAFFE_ENFORCE_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n  const float* Ydata = Y.data<float>();\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  for (int i = 0; i < Y.size(); ++i) {\n    dXdata[i] = dYdata[i] * (Ydata[i] > min_ && Ydata[i] < max_);\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(Clip, ClipOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(ClipGradient, ClipGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Clip)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nClip operator limits the given input within an interval. The interval is\nspecified with arguments 'min' and 'max'. They default to\nnumeric_limits::lowest() and numeric_limits::max() respectively. The clipping\noperation can be done in in-place fashion too, where the input and output blobs\nare the same.\n)DOC\")\n    .Arg(\"min\", \"Minimum value, under which element is replaced by min\")\n    .Arg(\"max\", \"Maximum value, above which element is replaced by max\")\n    .Input(\n        0,\n        \"input\",\n        \"Input tensor (Tensor<float>) containing elements to be\"\n        \"clipped\")\n    .Input(\n        1,\n        \"output\",\n        \"Output tensor (Tensor<float>) containing clipped\"\n        \"input elements\");\n\nOPERATOR_SCHEMA(ClipGradient).NumInputs(2).NumOutputs(1).AllowInplace({{1, 0}});\n\nclass GetClipGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"ClipGradient\", \"\",\n        vector<string>{O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Clip, GetClipGradient);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/clip_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/clip_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\ntemplate <typename T>\n__device__ T cuda_min(T x, T y);\ntemplate <typename T>\n__device__ T cuda_max(T x, T y);\ntemplate <>\n__device__ float cuda_min(float x, float y) { return fminf(x, y); }\ntemplate <>\n__device__ float cuda_max(float x, float y) { return fmaxf(x, y); }\n\n// Disabled since we don't use it right now.\n/*\ntemplate <>\n__device__ double cuda_min(double x, double y) { return fmin(x, y); }\ntemplate <>\n__device__ double cuda_max(double x, double y) { return fmax(x, y); }\n*/\n\n\ntemplate <typename T>\n__global__ void ClipKernel(const int N, const T minval, const T maxval,\n                           const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = cuda_min<T>(cuda_max<T>(X[i], minval), maxval);\n  }\n}\n\ntemplate <typename T>\n__global__ void ClipGradientKernel(const int N,  const T minval,\n                                   const T maxval, const T* Y,\n                                   const T* dY, T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dX[i] = dY[i] * (Y[i] > minval && Y[i] < maxval);\n  }\n}\n}  // namespace\n\ntemplate <>\nbool ClipOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE_GT(X.size(), 0);\n  Y->ResizeLike(X);\n  ClipKernel<<<CAFFE_GET_BLOCKS(X.size()), CAFFE_CUDA_NUM_THREADS,\n               0, context_.cuda_stream()>>>(\n      X.size(), min_, max_, X.data<float>(), Y->mutable_data<float>());\n  return true;\n}\n\ntemplate <>\nbool ClipGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  CAFFE_ENFORCE_GT(Y.size(), 0);\n  CAFFE_ENFORCE_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n  ClipGradientKernel<<<CAFFE_GET_BLOCKS(Y.size()), CAFFE_CUDA_NUM_THREADS,\n                       0, context_.cuda_stream()>>>(\n      Y.size(), min_, max_, Y.data<float>(), dY.data<float>(),\n      dX->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Clip, ClipOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(ClipGradient, ClipGradientOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/clip_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CLIP_OP_H_\n#define CAFFE2_OPERATORS_CLIP_OP_H_\n\n#include <limits>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass ClipOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ClipOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        min_(std::numeric_limits<T>::lowest()),\n        max_(std::numeric_limits<T>::max()) {\n    if (HasArgument(\"min\")) {\n      min_ = static_cast<T>(OperatorBase::GetSingleArgument<float>(\"min\", 0));\n    }\n    if (HasArgument(\"max\")) {\n      max_ = static_cast<T>(OperatorBase::GetSingleArgument<float>(\"max\", 0));\n    }\n  }\n\n  bool RunOnDevice() override;\n\n protected:\n  T min_;\n  T max_;\n};\n\ntemplate <typename T, class Context>\nclass ClipGradientOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ClipGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        min_(std::numeric_limits<T>::lowest()),\n        max_(std::numeric_limits<T>::max()) {\n    if (HasArgument(\"min\")) {\n      min_ = static_cast<T>(OperatorBase::GetSingleArgument<float>(\"min\", 0));\n    }\n    if (HasArgument(\"max\")) {\n      max_ = static_cast<T>(OperatorBase::GetSingleArgument<float>(\"max\", 0));\n    }\n  }\n\n  bool RunOnDevice() override;\n\n protected:\n  T min_;\n  T max_;\n  // Input: Y, dY; Output: dX\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CLIP_OP_H_\n"
  },
  {
    "path": "caffe2/operators/communicator_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/no_default_engine_op.h\"\n\nnamespace caffe2 {\n\nOPERATOR_SCHEMA(CreateCommonWorld)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nCreates a common world for communication operators.\n)DOC\")\n    .Input(0, \"kv_handler\", \"Key/value handler for rendezvous (optional).\")\n    .Output(0, \"comm_world\", \"A common world for collective operations.\")\n    .Arg(\"size\", \"(int) size of the common world.\")\n    .Arg(\"rank\", \"(int) rank of this node in the common world.\");\n\nOPERATOR_SCHEMA(CloneCommonWorld)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nClones existing common world.\n)DOC\")\n    .Input(0, \"existing_comm_world\", \"Existing common world to clone.\")\n    .Output(0, \"comm_world\", \"A common world for collective operations.\");\n\nOPERATOR_SCHEMA(DestroyCommonWorld)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .EnforceInplace({{0, 0}})\n    .SetDoc(\"Closes all connections managed by a common world.\")\n    .Input(0, \"common_world\", \"The common world to be destroyed.\");\n\nOPERATOR_SCHEMA(Broadcast)\n    .NumInputsOutputs([](int in, int out) {\n      return in >= 2 && out == (in - 1);\n    })\n    .EnforceInplace([](int in, int out) { return (in - 1) == out; })\n    .InputsCanCrossDevices()\n    .IdenticalTypeAndShapeOfInput(0)\n    .SetDoc(R\"DOC(\nDoes a broadcast operation from the root node to every other node. The tensor\non each node should have been pre-created with the same shape and data type.\n)DOC\")\n    .Input(0, \"comm_world\", \"The common world.\")\n    .Input(1, \"X\", \"A tensor to be broadcasted.\")\n    .Output(0, \"X\", \"In-place as input 1.\")\n    .Arg(\"root\", \"(int, default 0) the root to run broadcast from.\");\n\nOPERATOR_SCHEMA(Reduce)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .InputsCanCrossDevices()\n    .IdenticalTypeAndShapeOfInput(0)\n    .SetDoc(R\"DOC(\nDoes a reduce operation from every node to the root node. Currently only\nSum is supported.\n)DOC\")\n    .Input(0, \"comm_world\", \"The common world.\")\n    .Input(1, \"X\", \"A tensor to be reduced.\")\n    .Output(0, \"Y\", \"The reduced result on root, not set for other nodes.\")\n    .Arg(\"root\", \"(int, default 0) the root to run reduce into.\");\n\nOPERATOR_SCHEMA(Allreduce)\n    .NumInputsOutputs([](int in, int out) {\n      return in >= 2 && out == (in - 1);\n    })\n    .EnforceInplace([](int in, int out) { return (in - 1) == out; })\n    .IdenticalTypeAndShapeOfInput(0)\n    .InputsCanCrossDevices()\n    .SetDoc(R\"DOC(\nDoes an allreduce operation among the nodes. Currently only Sum is supported.\n)DOC\")\n    .Input(0, \"comm_world\", \"The common world.\")\n    .Input(1, \"X\", \"A tensor to be allreduced.\")\n    .Output(0, \"Y\", \"The allreduced tensor, same on all nodes.\");\n\nOPERATOR_SCHEMA(ReduceScatter)\n    .NumInputsOutputs([](int in, int out) {\n      return in >= 2 && out == (in - 1);\n    })\n    .EnforceInplace([](int in, int out) { return (in - 1) == out; })\n    .IdenticalTypeAndShapeOfInput(0)\n    .InputsCanCrossDevices()\n    .SetDoc(R\"DOC(\nDoes reduce-scatter operation among the nodes. Currently only Sum is supported.\n)DOC\")\n    .Input(0, \"comm_world\", \"The common world.\")\n    .Input(1, \"X\", \"A tensor to be reduce-scattered.\")\n    .Output(0, \"Y\", \"The reduced tensor, scattered on all nodes.\");\n\nOPERATOR_SCHEMA(Allgather)\n    .NumInputs(2, INT_MAX)\n    .NumOutputs(1)\n    .InputsCanCrossDevices()\n    .SetDoc(R\"DOC(\nDoes an allgather operation among the nodes.\n)DOC\")\n    .Input(0, \"comm_world\", \"The common world.\")\n    .Input(1, \"X\", \"A tensor to be allgathered.\")\n    .Output(0, \"Y\", \"The allgathered tensor, same on all nodes.\");\n\nOPERATOR_SCHEMA(Barrier)\n    .NumInputs(1)\n    .SetDoc(R\"DOC(\nDoes a barrier operation among the nodes.\n)DOC\")\n    .Input(0, \"comm_world\", \"The common world.\");\n\nOPERATOR_SCHEMA(SendTensor)\n    .NumInputs({2, 4})\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(\nSends the tensor to another node.\n)DOC\")\n    .Input(0, \"comm_world\", \"The common world.\")\n    .Input(1, \"X\", \"A tensor to be allgathered.\")\n    .Input(\n        2,\n        \"dst\",\n        \"An int CPUtensor of size 1 specifying the rank. If \"\n        \"given, this overrides the 'to' argument of the op.\")\n    .Input(\n        3,\n        \"tag\",\n        \"An int CPUtensor of size 1 specifying the tag to \"\n        \"send the tensor with. This overrides the 'tag' \"\n        \"argument of the op.\")\n    .Arg(\"dst\", \"The rank to send the tensor to.\")\n    .Arg(\"tag\", \"(int) a tag to send the tensor with.\")\n    .Arg(\n        \"raw_buffer\",\n        \"(bool) if set, only send the content and assume that the receiver \"\n        \"has already known the tensor's shape and information.\");\n\nOPERATOR_SCHEMA(ReceiveTensor)\n    .NumInputs({2, 4})\n    .NumOutputs(3)\n    .EnforceInplace({{1, 0}})\n    .AllowInplace({{2, 1}, {3, 2}})\n    .SetDoc(R\"DOC(\nReceives the tensor from another node.\n)DOC\")\n    .Input(0, \"comm_world\", \"The common world.\")\n    .Input(\n        1,\n        \"Y\",\n        \"In-place output. If raw_buffer is specified, \"\n        \"Y should have pre-allocated data and type..\")\n    .Input(\n        2,\n        \"src\",\n        \"An int CPUtensor of size 1 specifying the rank. If \"\n        \"given, this overrides the 'from' argument of the op.\")\n    .Input(\n        3,\n        \"tag\",\n        \"An int CPUtensor of size 1 specifying the tag to \"\n        \"send the tensor with. This overrides the 'tag' \"\n        \"argument of the op.\")\n    .Output(0, \"Y\", \"The received tensor.\")\n    .Output(\n        1,\n        \"src\",\n        \"The sender that sent the message as a CPUTensor \"\n        \"of size 1 and of type int.\")\n    .Output(\n        2,\n        \"tag\",\n        \"The tag that the message is sent with as a CPUTensor \"\n        \"of size 1 and of type int.\")\n    .Arg(\"src\", \"(int) he rank to receive the tensor from.\")\n    .Arg(\"tag\", \"(int) a tag to receive the tensor with.\")\n    .Arg(\n        \"raw_buffer\",\n        \"(bool) if set, only send the content and assume that the receiver \"\n        \"has already known the tensor's shape and information.\");\n\nSHOULD_NOT_DO_GRADIENT(CreateCommonWorld);\nSHOULD_NOT_DO_GRADIENT(CloneCommonWorld);\nSHOULD_NOT_DO_GRADIENT(DestroyCommonWorld);\nSHOULD_NOT_DO_GRADIENT(Broadcast);\nSHOULD_NOT_DO_GRADIENT(Reduce);\nSHOULD_NOT_DO_GRADIENT(Allgather);\nSHOULD_NOT_DO_GRADIENT(Allreduce);\nSHOULD_NOT_DO_GRADIENT(ReduceScatter);\nSHOULD_NOT_DO_GRADIENT(Barrier);\nSHOULD_NOT_DO_GRADIENT(SendTensor);\nSHOULD_NOT_DO_GRADIENT(ReceiveTensor);\n\n// Communication operators do not have default engines.\nREGISTER_CPU_OPERATOR(CreateCommonWorld, NoDefaultEngineOp<CPUContext>);\nREGISTER_CPU_OPERATOR(CloneCommonWorld, NoDefaultEngineOp<CPUContext>);\nREGISTER_CPU_OPERATOR(DestroyCommonWorld, NoDefaultEngineOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Broadcast, NoDefaultEngineOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Reduce, NoDefaultEngineOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Allgather, NoDefaultEngineOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Allreduce, NoDefaultEngineOp<CPUContext>);\nREGISTER_CPU_OPERATOR(ReduceScatter, NoDefaultEngineOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Barrier, NoDefaultEngineOp<CPUContext>);\nREGISTER_CPU_OPERATOR(SendTensor, NoDefaultEngineOp<CPUContext>);\nREGISTER_CPU_OPERATOR(ReceiveTensor, NoDefaultEngineOp<CPUContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/communicator_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/no_default_engine_op.h\"\n\nnamespace caffe2 {\n// Communication operators do not have default engines.\nREGISTER_CUDA_OPERATOR(CreateCommonWorld, NoDefaultEngineOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(CloneCommonWorld, NoDefaultEngineOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(Broadcast, NoDefaultEngineOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(Reduce, NoDefaultEngineOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(Allgather, NoDefaultEngineOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(Allreduce, NoDefaultEngineOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(SendTensor, NoDefaultEngineOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(ReceiveTensor, NoDefaultEngineOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/concat_split_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/concat_split_op.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(Split, SplitOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Concat, ConcatOp<CPUContext>);\nOPERATOR_SCHEMA(Split)\n    .NumInputs(1, 2)\n    .NumOutputs(1, INT_MAX)\n    .Input(0, \"input\", \"The tensor to split\")\n    .Input(1, \"split\", \"Optional list of output lengths (see also arg 'split')\")\n    .Arg(\"axis\", \"Which axis to split on\")\n    .Arg(\"split\", \"length of each output\")\n    .Arg(\"order\", \"Either NHWC or NCWH, will split on C axis, defaults to NCHW\")\n    .SetDoc(R\"DOC(\nSplit a tensor into a list of tensors, along the specified\n'axis'. The lengths of the split can be specified using argument 'axis' or\noptional second input blob to the operator. Otherwise, the tensor is split\nto equal sized parts.\n)DOC\");\n\nnamespace {\nOpSchema::Cost CostInferenceForConcat(\n    const OperatorDef& def,\n    const vector<TensorShape>& in) {\n  ArgumentHelper helper(def);\n  const int axis = helper.HasArgument(\"axis\")\n      ? helper.GetSingleArgument<int>(\"axis\", -1)\n      : GetDimFromOrderString(\n            helper.GetSingleArgument<string>(\"order\", \"NCHW\"));\n  bool add_axis = helper.GetSingleArgument<int>(\"add_axis\", 0) != 0;\n  const int canonical_axis = canonical_axis_index_(axis, in[0].dims_size());\n  CAFFE_ENFORCE_GT(in.size(), 0);\n  vector<int> out_shape(in[0].dims().begin(), in[0].dims().end());\n  if (add_axis) {\n    out_shape.insert(out_shape.begin() + canonical_axis, in.size());\n  } else {\n    for (int i = 1; i < in.size(); ++i) {\n      out_shape[canonical_axis] += in[i].dims(canonical_axis);\n    }\n  }\n  int size = 1;\n  for (auto& s : out_shape) {\n    size *= s;\n  }\n\n  struct OpSchema::Cost cost;\n  cost.flops = 0;\n  cost.bytes_moved = size * sizeof(float);\n  cost.params_bytes = 0;\n  return cost;\n}\n} // namespace\n\nOPERATOR_SCHEMA(Concat)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(2)\n    .Arg(\"axis\", \"Which axis to concat on\")\n    .Arg(\"order\", \"Either NHWC or NCHW, will concat on C axis, defaults to NCHW\")\n    .Arg(\n        \"add_axis\",\n        \"Pass 1 to add the axis specified in arg 'axis' to all \"\n        \"input tensors\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      ArgumentHelper helper(def);\n      const int axis = helper.HasArgument(\"axis\")\n          ? helper.GetSingleArgument<int>(\"axis\", -1)\n          : GetDimFromOrderString(\n                helper.GetSingleArgument<string>(\"order\", \"NCHW\"));\n      bool add_axis = helper.GetSingleArgument<int>(\"add_axis\", 0) != 0;\n      const int canonical_axis = canonical_axis_index_(axis, in[0].dims_size());\n      CAFFE_ENFORCE_GT(in.size(), 0);\n      vector<int> split_shape(1, in.size());\n      vector<int> out_shape(in[0].dims().begin(), in[0].dims().end());\n      if (add_axis) {\n        for (int i = 1; i < in.size(); ++i) {\n          CAFFE_ENFORCE_EQ(\n              in[0].dims().size(),\n              in[i].dims().size(),\n              \"All inputs of Concat should have same dims when add_axis = 1. \"\n              \"Got different sizes for inputs 0 and \",\n              i);\n          for (int j = 0; j < in[0].dims().size(); ++j) {\n            CAFFE_ENFORCE_EQ(\n                in[0].dims(j),\n                in[i].dims(j),\n                \"All inputs of Concat should have same dims when add_axis = 1. \"\n                \"Got different dims for inputs 0 and \",\n                i,\n                \". At dim: \",\n                j);\n          }\n        }\n        out_shape.insert(out_shape.begin() + canonical_axis, in.size());\n      } else {\n        for (int i = 1; i < in.size(); ++i) {\n          CAFFE_ENFORCE_EQ(\n              in[0].dims().size(),\n              in[i].dims().size(),\n              \"All inputs of Concat should have same dims except \"\n              \"canonical_axis dim that is equal to \",\n              canonical_axis,\n              \"Got different sizes for inputs 0 and \",\n              i);\n          for (int j = 0; j < in[0].dims().size(); ++j) {\n            if (j == canonical_axis) {\n              continue;\n            }\n            CAFFE_ENFORCE_EQ(\n                in[0].dims(j),\n                in[i].dims(j),\n                \"All inputs of Concat should have same dims except \"\n                \"canonical_axis dim that is equal to \",\n                canonical_axis,\n                \"Got different dims for inputs 0 and \",\n                i,\n                \". At dim: \",\n                j);\n          }\n        }\n\n        for (int i = 1; i < in.size(); ++i) {\n          out_shape[canonical_axis] += in[i].dims(canonical_axis);\n        }\n      }\n      if (def.output_size() == 1) {\n        return vector<TensorShape>{\n            CreateTensorShape(out_shape, in[0].data_type())};\n      }\n      return vector<TensorShape>{\n          CreateTensorShape(out_shape, in[0].data_type()),\n          CreateTensorShape(split_shape, TensorProto::INT32)};\n    })\n    .CostInferenceFunction(CostInferenceForConcat)\n    .SetDoc(\"Concatenate a list of tensors into a single tensor\")\n    .Output(0, \"concat_result\", \"Concatenated tensor\")\n    .Output(1, \"split_info\", \"The dimensions of the inputs.\");\n\n// Backward compatibility names.\nREGISTER_CPU_OPERATOR(DepthSplit, SplitOp<CPUContext>);\nREGISTER_CPU_OPERATOR(DepthConcat, ConcatOp<CPUContext>);\nOPERATOR_SCHEMA(DepthSplit)\n    .NumInputs(1, 2)\n    .NumOutputs(1, INT_MAX)\n    .SetDoc(\"Backward compatible operator name for Split.\");\nOPERATOR_SCHEMA(DepthConcat)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(2)\n    .SetDoc(\"Backward compatible operator name for Concat.\");\n\nclass GetSplitGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> output_grads;\n    for (int i = 0; i < def_.output_size(); ++i) {\n      if (!GradOut(i).IsEmpty()) {\n        output_grads.push_back(GO(i));\n      }\n    }\n    if (output_grads.empty()) {\n      return {};\n    }\n    return SingleGradientDef(\n        \"Concat\", \"\", output_grads,\n        vector<string>{GI(0), \"_\" + GI(0) + \"_dims\"});\n  }\n};\nREGISTER_GRADIENT(Split, GetSplitGradient);\nREGISTER_GRADIENT(DepthSplit, GetSplitGradient);\n\nclass GetConcatGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    if (GradOut(0).IsEmpty()) {\n      return {};\n    }\n    vector<string> grads;\n    for (int i = 0; i < def_.input_size(); ++i) {\n      grads.push_back(GI(i));\n    }\n    return SingleGradientDef(\n        \"Split\", \"\", vector<string>{GO(0), O(1)}, grads);\n  }\n};\nREGISTER_GRADIENT(Concat, GetConcatGradient);\nREGISTER_GRADIENT(DepthConcat, GetConcatGradient);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/concat_split_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CONCAT_SPLIT_OP_H_\n#define CAFFE2_OPERATORS_CONCAT_SPLIT_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nnamespace {\ninline int GetDimFromOrderString(const string& str) {\n  auto order = StringToStorageOrder(str);\n  switch (order) {\n    case StorageOrder::NHWC:\n      return 3;\n    case StorageOrder::NCHW:\n      return 1;\n    default:\n      CAFFE_THROW(\"Unsupported storage order: \", str);\n      return -1;\n  }\n}\n} // namespace\n\ntemplate <class Context>\nclass SplitOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SplitOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        split_(OperatorBase::GetRepeatedArgument<int>(\"split\")) {\n    CAFFE_ENFORCE(\n      !(OperatorBase::HasArgument(\"axis\") && OperatorBase::HasArgument(\"order\")),\n        \"You shouldn't specify both the dim to split, and the order \"\n        \"in the case of 4-D images.\");\n    if (OperatorBase::HasArgument(\"axis\")) {\n      axis_ = OperatorBase::GetSingleArgument<int>(\"axis\", -1);\n      // only exists for computing the gradient of a Concat with 'add_axis'\n      add_axis_ = OperatorBase::GetSingleArgument<int>(\"add_axis\", 0);\n    } else {\n      axis_ = GetDimFromOrderString(\n          OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"));\n      add_axis_ = 0;\n    }\n  }\n\n  bool RunOnDevice() override;\n\n protected:\n  int axis_;\n  int add_axis_;\n  vector<int> split_;\n  // Input: X, optionally split\n  // The split tensor is stored in CPU.\n};\n\ntemplate <class Context>\nclass ConcatOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ConcatOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    CAFFE_ENFORCE(\n      !(OperatorBase::HasArgument(\"axis\") && OperatorBase::HasArgument(\"order\")),\n        \"You shouldn't specify both the dim to concat, and the order \"\n        \"in the case of 4-D images.\");\n    if (OperatorBase::HasArgument(\"axis\")) {\n      axis_ = OperatorBase::GetSingleArgument<int>(\"axis\", -1);\n      add_axis_ = OperatorBase::GetSingleArgument<int>(\"add_axis\", 0);\n    } else {\n      axis_ = GetDimFromOrderString(\n          OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"));\n      add_axis_ = 0;\n    }\n  }\n\n  bool RunOnDevice() override;\n\n protected:\n  int axis_;\n  int add_axis_;\n  // Input: a number of tensors. Output: Y, split\n  // The split are stored in CPU.\n};\n\n// Implementations\ntemplate <class Context>\nbool SplitOp<Context>::RunOnDevice() {\n  auto& input = Input(0);\n  int canonical_axis = input.canonical_axis_index(axis_);\n  CAFFE_ENFORCE_LT(\n      canonical_axis, input.ndim(), \"Axis not in input ndim range.\");\n  const int input_channels = input.dim32(canonical_axis);\n  const int* axis_data;\n  vector<int> equal_split;\n  if (InputSize() == 2) {\n    // We obtain split from the input tensor.\n    CAFFE_ENFORCE_EQ(\n        split_.size(),\n        0,\n        \"If you set split with an input blob, do not pass in \"\n        \"split in the argument.\");\n    auto& split_tensor = OperatorBase::Input<TensorCPU>(1);\n    CAFFE_ENFORCE_EQ(split_tensor.size(), OutputSize());\n    axis_data = split_tensor.template data<int>();\n  } else if (split_.size() == 0) {\n    CAFFE_ENFORCE_EQ(\n        input_channels % OutputSize(),\n        0,\n        \"If you did not specify split explicitly, the number of \"\n        \"input channels should be divisible by the output size.\");\n    equal_split.resize(OutputSize(), input_channels / OutputSize());\n    axis_data = equal_split.data();\n  } else {\n    // We obtain split from the parameters.\n    CAFFE_ENFORCE_EQ(\n        split_.size(),\n        OutputSize(),\n        \"The number of splits specified should be equal to the \"\n        \"number of outputs.\");\n    axis_data = split_.data();\n  }\n\n  CAFFE_ENFORCE_EQ(\n      add_axis_ ? OutputSize()\n                : std::accumulate(axis_data, axis_data + OutputSize(), 0),\n      input_channels,\n      \"Sum of split dimensions do not match: should be \",\n      input_channels);\n  vector<TIndex> output_dims(input.dims());\n  int before = 1, after = 1;\n  for (int i = 0; i < canonical_axis; ++i) {\n    before *= input.dim32(i);\n  }\n  for (int i = canonical_axis + 1; i < input.ndim(); ++i) {\n    after *= input.dim32(i);\n  }\n  if (add_axis_) {\n    output_dims.erase(output_dims.begin() + canonical_axis);\n  }\n  size_t input_offset = 0;\n  for (int i = 0; i < OutputSize(); ++i) {\n    auto* output = Output(i);\n    auto axis_dim = add_axis_ ? 1 : axis_data[i];\n    if (!add_axis_) {\n      output_dims[canonical_axis] = axis_data[i];\n    }\n    output->Resize(output_dims);\n    math::CopyMatrix<Context>(\n        input.itemsize(),\n        before,\n        axis_dim * after,\n        static_cast<const char*>(input.raw_data()) + input_offset,\n        input.dim32(canonical_axis) * after,\n        output->raw_mutable_data(input.meta()),\n        axis_dim * after,\n        &context_,\n        input.meta().copy());\n    input_offset += axis_dim * after * input.itemsize();\n  }\n  return true;\n}\n\ntemplate <class Context>\nbool ConcatOp<Context>::RunOnDevice() {\n  auto* output = Output(0);\n  TensorCPU* split = OperatorBase::Output<TensorCPU>(1);\n  split->Resize(vector<TIndex>(1, InputSize()));\n  int* axis_data = split->template mutable_data<int>();\n  auto& input_zero = Input(0);\n  int adj_size = input_zero.ndim() + (add_axis_ ? 1 : 0);\n  int canonical_axis = canonical_axis_index_(axis_, adj_size);\n  CAFFE_ENFORCE_LT(\n      canonical_axis,\n      adj_size,\n      \"Axis not in input ndim range.\");\n  for (int i = 1; i < InputSize(); ++i) {\n    CAFFE_ENFORCE(\n        Input(i).meta() == input_zero.meta(),\n        \"All inputs must have the same type, expected: \",\n        input_zero.meta().name(),\n        \" but got: \",\n        Input(i).meta().name(),\n        \" for input: \",\n        i);\n  }\n\n  int before = 1, after = 1;\n  vector<TIndex> output_dims(input_zero.dims());\n  for (int i = 0; i < input_zero.ndim(); ++i) {\n    if (i == canonical_axis && !add_axis_) {\n      continue;\n    }\n    int dim = input_zero.dim32(i);\n    if (i < canonical_axis) {\n      before *= dim;\n    } else { // i > canonical_axis || i == canonical_axis && add_axis_\n      after *= dim;\n    }\n    // check the input dims are compatible.\n    for (int j = 1; j < InputSize(); ++j) {\n      int dim_j = Input(j).dim32(i);\n      CAFFE_ENFORCE(\n          dim == dim_j,\n          \"Expect dimension = \",\n          dim,\n          \" got \",\n          dim_j,\n          \" at axis = \",\n          i,\n          \" for input: \",\n          j,\n          \". The input tensors can only have different dimensions \"\n          \"when arg 'add_axis' = 0 and along the axis = \",\n          canonical_axis,\n          \" <\",\n          Input(0).dims(),\n          \"> vs <\",\n          Input(j).dims(),\n          \">.\");\n    }\n  }\n\n  int output_channels = 0;\n  for (int i = 0; i < InputSize(); ++i) {\n    axis_data[i] = add_axis_ ? 1 : Input(i).dim32(canonical_axis);\n    output_channels += axis_data[i];\n  }\n  if (add_axis_) {\n    output_dims.insert(output_dims.begin() + canonical_axis, output_channels);\n  } else {\n    output_dims[canonical_axis] = output_channels;\n  }\n  output->Resize(output_dims);\n  size_t output_offset = 0;\n  for (int i = 0; i < InputSize(); ++i) {\n    auto& input = Input(i);\n    auto axis_dim = add_axis_ ? 1 : input.dim32(canonical_axis);\n    math::CopyMatrix<Context>(\n        input.itemsize(),\n        before,\n        axis_dim * after,\n        input.raw_data(),\n        axis_dim * after,\n        static_cast<char*>(output->raw_mutable_data(input_zero.meta())) +\n            output_offset,\n        output_channels * after,\n        &context_,\n        input_zero.meta().copy());\n    output_offset += axis_dim * after * input.itemsize();\n  }\n  return true;\n}\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CONCAT_SPLIT_OP_H_\n"
  },
  {
    "path": "caffe2/operators/concat_split_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/concat_split_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(Split, SplitOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(Concat, ConcatOp<CUDAContext>);\n\n// Backward compatibility settings\nREGISTER_CUDA_OPERATOR(DepthSplit, SplitOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(DepthConcat, ConcatOp<CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/conditional_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/conditional_op.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool ConditionalOp<CPUContext>::RunOnDevice() {\n  auto& condition = Input(0);\n  auto& dataT = Input(1);\n  auto& dataF = Input(2);\n\n  // verify the inputs shape\n  CAFFE_ENFORCE_EQ(condition.ndim(), 1);\n  CAFFE_ENFORCE(dataT.ndim() >= 1);\n  CAFFE_ENFORCE(dataT.dims()[0] == condition.dims()[0]);\n  CAFFE_ENFORCE_EQ(dataT.ndim(), dataF.ndim());\n  for (size_t i = 0; i < dataT.dims().size(); i++) {\n    CAFFE_ENFORCE(dataT.dims().at(i) == dataF.dims().at(i));\n  }\n  const auto innerSize = dataT.size_from_dim(1);\n  const auto innerSizeBytes = innerSize * dataT.meta().itemsize();\n  CAFFE_ENFORCE(innerSize * dataF.meta().itemsize() == innerSizeBytes);\n\n  // initialize output shape\n  auto* dataOut = Output(0);\n  const auto* condPtr = condition.template data<bool>();\n  dataOut->ResizeLike(dataT);\n  auto* outPtr = (char*)dataOut->raw_mutable_data(dataT.meta());\n\n  // perform conditional op along first dimension\n  const auto* ptrT = (char*)dataT.raw_data();\n  const auto* ptrF = (char*)dataF.raw_data();\n  for (TIndex i = 0; i < condition.size(); i++) {\n    auto* dst = outPtr + i * innerSizeBytes;\n    if (condPtr[i]) {\n      context_.template CopyItems<CPUContext, CPUContext>(\n          dataT.meta(), innerSize, ptrT + i * innerSizeBytes, dst);\n    } else {\n      context_.template CopyItems<CPUContext, CPUContext>(\n          dataF.meta(), innerSize, ptrF + i * innerSizeBytes, dst);\n    }\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(Conditional, ConditionalOp<CPUContext>);\n\nOPERATOR_SCHEMA(Conditional)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven a 1-D tensor of boolean values, apply conditional operator along the first\ndimension of DataT and DataF and return DataO. Note, DataT and DataF must\nhave the exact same shape and type.\n)DOC\")\n    .Input(0, \"Condition\", \"Boolean tensor to select DataT or DataF\")\n    .Input(1, \"DataT\", \"Data to use when True\")\n    .Input(2, \"DataF\", \"Data to use when False\")\n    .Output(0, \"DataO\", \"Output data after applying ConditionalOp\");\n\nNO_GRADIENT(Conditional);\n\n} // caffe2\n"
  },
  {
    "path": "caffe2/operators/conditional_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#ifndef CONDITIONAL_OP_H\n#define CONDITIONAL_OP_H\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass ConditionalOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ConditionalOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override;\n};\n\n} // caffe2\n\n#endif\n"
  },
  {
    "path": "caffe2/operators/conv_gradient_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/conv_op.h\"\n#include \"caffe2/operators/conv_op_impl.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(ConvGradient, ConvGradientOp<float, CPUContext>);\nOPERATOR_SCHEMA(ConvGradient).NumInputs(2, 3).NumOutputs(1, 3);\n\nREGISTER_CPU_OPERATOR(Conv1DGradient, ConvGradientOp<float, CPUContext>);\nOPERATOR_SCHEMA(Conv1DGradient).NumInputs(2, 3).NumOutputs(1, 3);\n\nREGISTER_CPU_OPERATOR(Conv2DGradient, ConvGradientOp<float, CPUContext>);\nOPERATOR_SCHEMA(Conv2DGradient).NumInputs(2, 3).NumOutputs(1, 3);\n\nREGISTER_CPU_OPERATOR(Conv3DGradient, ConvGradientOp<float, CPUContext>);\nOPERATOR_SCHEMA(Conv3DGradient).NumInputs(2, 3).NumOutputs(1, 3);\n\nclass GetConvGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE(def_.input_size() == 3 || def_.input_size() == 2);\n\n    ArgumentHelper argsHelper(def_);\n\n    auto compute_dX = !argsHelper.GetSingleArgument<bool>(\"no_gradient_to_input\", 0);\n\n    if (def_.input_size() == 3) {\n      if (compute_dX) {\n        return SingleGradientDef(\n            def_.type() + \"Gradient\",\n            \"\",\n            vector<string>{I(0), I(1), GO(0)},\n            vector<string>{GI(1), GI(2), GI(0)});\n      } else {\n        return SingleGradientDef(\n            def_.type() + \"Gradient\",\n            \"\",\n            vector<string>{I(0), I(1), GO(0)},\n            vector<string>{GI(1), GI(2)});\n      }\n    } else {\n      if (compute_dX) {\n        return SingleGradientDef(\n            def_.type() + \"Gradient\",\n            \"\",\n            vector<string>{I(0), I(1), GO(0)},\n            vector<string>{GI(1), GI(0)},\n            vector<Argument>{MakeArgument<int>(\"no_bias\", 1)});\n      } else {\n        return SingleGradientDef(\n            def_.type() + \"Gradient\",\n            \"\",\n            vector<string>{I(0), I(1), GO(0)},\n            vector<string>{GI(1)},\n            vector<Argument>{MakeArgument<int>(\"no_bias\", 1)});\n      }\n    }\n  }\n};\nREGISTER_GRADIENT(Conv, GetConvGradient);\nREGISTER_GRADIENT(Conv1D, GetConvGradient);\nREGISTER_GRADIENT(Conv2D, GetConvGradient);\nREGISTER_GRADIENT(Conv3D, GetConvGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/conv_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/conv_op.h\"\n#include \"caffe2/operators/conv_op_impl.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n\nnamespace caffe2 {\n\nconst char* kConvDoc = R\"DOC(\nNote that other parameters, such as the stride and\nkernel size, or the pads' sizes in each direction are not necessary for input\nbecause they are provided by the ConvPoolOpBase operator. Various dimension\nchecks are done implicitly, and the sizes are specified in the Input docs for\nthis operator. As is expected, the filter is convolved with a subset of the\nimage and the bias is added; this is done throughout the image data and the\noutput is computed. As a side note on the implementation layout:\nconv_op_impl.h is the templated implementation of the conv_op.h file, which is\nwhy they are separate files.\n)DOC\";\n\nstd::function<void(OpSchema&)> ConvDocGenerator(const char* dim) {\n  return [=](OpSchema& schema) {\n    string doc = R\"DOC(\nThe convolution operator consumes an input vector, a {dim}filter blob\nand a bias blob and computes the output. {conv_doc})DOC\";\n    ReplaceAll(doc, \"{dim}\", dim);\n    ReplaceAll(doc, \"{conv_doc}\", kConvDoc);\n    schema.SetDoc(doc);\n    schema.Input(\n        0,\n        \"X\",\n        \"Input data blob from previous layer; has size (N x C x H x W), \"\n        \"where N is the batch size, C is the number of channels, \"\n        \"and H and W are the height and width. Note that this is for the NCHW \"\n        \"usage. On the other hand, the NHWC Op has a different set of \"\n        \"dimension constraints. \");\n    schema.Input(\n        1,\n        \"filter\",\n        \"The filter blob that will be used in the \"\n        \"convolutions; has size (M x C x kH x kW), where C is the number of \"\n        \"channels, and kH and kW are the height and width of the kernel.\");\n    schema.Input(\n        2,\n        \"bias\",\n        \"The 1D bias blob that is added through the \"\n        \"convolution; has size (M).\");\n    schema.Output(\n        0,\n        \"Y\",\n        \"Output data blob that contains the result of the \"\n        \"convolution. The output dimensions are functions of the kernel size, \"\n        \"stride size, and pad lengths.\"\n        \"\");\n  };\n}\nREGISTER_CPU_OPERATOR(Conv, ConvOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Conv)\n    .NumInputs(2, 3)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForConv)\n    .CostInferenceFunction(OpSchema::CostInferenceFunctionType(\n        ConvPoolOpBase<CPUContext>::CostInferenceForConv))\n    .FillUsing(ConvDocGenerator(\"\"));\n\nREGISTER_CPU_OPERATOR(Conv1D, ConvOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Conv1D)\n    .NumInputs(2, 3)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForConv)\n    .FillUsing(ConvDocGenerator(\"1D \"));\n\nREGISTER_CPU_OPERATOR(Conv2D, ConvOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Conv2D)\n    .NumInputs(2, 3)\n    .NumOutputs(1)\n    .CostInferenceFunction(OpSchema::CostInferenceFunctionType(\n        ConvPoolOpBase<CPUContext>::CostInferenceForConv))\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForConv)\n    .FillUsing(ConvDocGenerator(\"2D \"));\n\nREGISTER_CPU_OPERATOR(Conv3D, ConvOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Conv3D)\n    .NumInputs(2, 3)\n    .NumOutputs(1)\n    .CostInferenceFunction(OpSchema::CostInferenceFunctionType(\n        ConvPoolOpBase<CPUContext>::CostInferenceForConv))\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForConv)\n    .FillUsing(ConvDocGenerator(\"3D \"));\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/conv_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CONV_OP_H_\n#define CAFFE2_OPERATORS_CONV_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_op_shared.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n\nCAFFE2_DECLARE_bool(caffe2_force_shared_col_buffer);\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass ConvOp final : public ConvPoolOpBase<Context> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(Context);\n  ConvOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<Context>(operator_def, ws) {\n    // Since this is the default convolution implementation, we will\n    // use CAFFE_ENFORCE instead of OPERATOR_NEEDS_FEATURE.\n    CAFFE_ENFORCE(\n        group_ == 1 || order_ == StorageOrder::NCHW,\n        \"Group convolution only supports NCHW order right now.\");\n\n    // Create shared buffer mutex in the constructor\n    // to avoid race-condition in DAGNet.\n    if (FLAGS_caffe2_force_shared_col_buffer || shared_buffer_) {\n      createSharedBuffer<Context>(ws_);\n    }\n  }\n  ~ConvOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n private:\n  Tensor<Context> col_buffer_;\n  Tensor<Context> bias_multiplier_;\n  Tensor<Context> img_shape_device_;\n  Tensor<Context> col_buffer_shape_device_;\n  // Input: X, W, b\n  // Output: Y\n  INPUT_TAGS(INPUT, FILTER, BIAS);\n};\n\ntemplate <typename T, class Context>\nclass ConvGradientOp final : public ConvPoolOpBase<Context> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(Context);\n  ConvGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<Context>(operator_def, ws),\n        no_bias_(OperatorBase::GetSingleArgument<int>(\"no_bias\", 0)) {\n    CAFFE_ENFORCE(\n        !(no_bias_ && OutputSize() == 3),\n        \"If bias is not present, you should not have 3 grad output.\");\n    CAFFE_ENFORCE(\n        group_ == 1 || order_ == StorageOrder::NCHW,\n        \"Group convolution only supports NCHW order right now.\");\n  }\n  ~ConvGradientOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n private:\n  Tensor<Context> col_buffer_;\n  Tensor<Context> bias_multiplier_;\n  Tensor<Context> img_shape_device_;\n  Tensor<Context> col_buffer_shape_device_;\n  bool no_bias_;\n  // input: X, W, dY\n  // output: dW, db, and optionally dX\n  INPUT_TAGS(INPUT, FILTER, OUTPUT_GRAD);\n  OUTPUT_TAGS(FILTER_GRAD, BIAS_OR_INPUT_GRAD, INPUT_GRAD);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CONV_OP_H_\n"
  },
  {
    "path": "caffe2/operators/conv_op_cache_cudnn.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/conv_op_cache_cudnn.h\"\n\n#include <cudnn.h>\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\ntemplate class AlgorithmsCache<cudnnConvolutionFwdAlgo_t>;\ntemplate class AlgorithmsCache<cudnnConvolutionBwdFilterAlgo_t>;\ntemplate class AlgorithmsCache<cudnnConvolutionBwdDataAlgo_t>;\ntemplate class AlgorithmsCache<int>; // For testing.\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/conv_op_cache_cudnn.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CONV_OP_CACHE_H_\n#define CAFFE2_OPERATORS_CONV_OP_CACHE_H_\n\n#include <functional>\n#include <unordered_map>\n#include <vector>\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\ntemplate <typename TAlgorithm>\nclass AlgorithmsCache {\n public:\n  // Caches the best algorithm for a given\n  // combination of tensor dimensions & compute data type.\n  //\n  TAlgorithm getAlgorithm(\n      const std::vector<TIndex>& tensorDimensions1,\n      const std::vector<TIndex>& tensorDimensions2,\n      int algorithmFlags, // Differentiate between algorithms with different\n                          // parameters in a generic way\n      std::function<TAlgorithm()> generatingFunc);\n\n private:\n  std::unordered_map<int64_t, TAlgorithm> hash_;\n};\n\ntemplate <typename TAlgorithm>\nTAlgorithm AlgorithmsCache<TAlgorithm>::getAlgorithm(\n    const std::vector<TIndex>& tensorDimensions1,\n    const std::vector<TIndex>& tensorDimensions2,\n    int algorithmFlags,\n    std::function<TAlgorithm()> generatingFunc) {\n  int64_t seed = 0;\n  // Hash all of the inputs, which we wiill then use to try and look up\n  // a previously discovered algorithm, or fall back to generating a new one.\n  std::hash<TIndex> hashFn;\n  for (const auto num : tensorDimensions1) {\n    // Copied from boost::hash_combine.\n    // Adding 1 to differentiate between first and second vector.\n    seed ^= hashFn(num) + 0x9e3779b9 + (seed << 6) + (seed >> 2) + 1;\n  }\n\n  for (const auto num : tensorDimensions2) {\n    // Copied from boost::hash_combine.\n    seed ^= hashFn(num) + 0x9e3779b9 + (seed << 6) + (seed >> 2);\n  }\n\n  // Adding 2 to differentiate from previous vectors\n  seed ^= hashFn(algorithmFlags) + 0x9e3779b9 + (seed << 6) + (seed >> 2) + 2;\n\n  if (seed == 0) {\n    return generatingFunc();\n  }\n\n  if (hash_.find(seed) == hash_.end()) {\n    TAlgorithm value = generatingFunc();\n    hash_[seed] = value;\n  }\n\n  return hash_[seed];\n}\n} // namespace caffe2\n\n#endif\n"
  },
  {
    "path": "caffe2/operators/conv_op_cache_cudnn_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <vector>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/operators/conv_op_cache_cudnn.h\"\n#include <gtest/gtest.h>\n\nCAFFE2_DECLARE_string(caffe_test_root);\n\nnamespace caffe2 {\n\nTEST(AlgorithmsCacheTest, CachesCorrectly) {\n  AlgorithmsCache<int> cache;\n  int result = cache.getAlgorithm(\n      std::vector<TIndex>(1), std::vector<TIndex>(1), 0, []() { return 5; });\n  EXPECT_EQ(result, 5);\n\n  int res2 = cache.getAlgorithm(\n      std::vector<TIndex>(1), std::vector<TIndex>(1), 0, []() { return 10; });\n\n  EXPECT_EQ(res2, 5);\n}\n\nTEST(AlgorithmsCacheTest, KeysDifferIfOneVectorIsEmpty) {\n  AlgorithmsCache<int> cache;\n  int result = cache.getAlgorithm(\n      std::vector<TIndex>(1, 10), std::vector<TIndex>(), 0, []() { return 5; });\n  EXPECT_EQ(result, 5);\n\n  int res2 = cache.getAlgorithm(\n      std::vector<TIndex>(), std::vector<TIndex>(1, 10), 0, []() {\n        return 10;\n      });\n\n  EXPECT_EQ(res2, 10);\n}\n\nTEST(AlgorithmsCacheTest, KeysDifferIfFlagsAreDifferent) {\n  AlgorithmsCache<int> cache;\n  int result = cache.getAlgorithm(\n      std::vector<TIndex>{2, 3, 4}, std::vector<TIndex>{5, 6}, 123, []() {\n        return 5;\n      });\n  EXPECT_EQ(result, 5);\n\n  int res2 = cache.getAlgorithm(\n      std::vector<TIndex>{2, 3, 4}, std::vector<TIndex>{5, 6}, 456, []() {\n        return 10;\n      });\n\n  EXPECT_EQ(res2, 10);\n\n  int res3 = cache.getAlgorithm(\n      std::vector<TIndex>{2, 3, 4}, std::vector<TIndex>{5, 6}, 456, []() {\n        return 15;\n      });\n\n  EXPECT_EQ(res3, 10);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/conv_op_cudnn.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/cudnn_wrappers.h\"\n#include \"caffe2/operators/conv_op.h\"\n#include \"caffe2/operators/conv_op_cache_cudnn.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/operators/op_utils_cudnn.h\"\n\nnamespace caffe2 {\n\nclass CudnnConvOpBase : public ConvPoolOpBase<CUDAContext> {\n public:\n  CudnnConvOpBase(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CUDAContext>(operator_def, ws),\n        cudnn_wrapper_(&context_),\n        cudnn_ws_nbytes_limit_(OperatorBase::GetSingleArgument<size_t>(\n            \"ws_nbytes_limit\",\n            kCONV_CUDNN_WORKSPACE_LIMIT_BYTES)),\n        exhaustive_search_(\n            OperatorBase::GetSingleArgument<int>(\"exhaustive_search\", 0)),\n        deterministic_(\n            OperatorBase::GetSingleArgument<int>(\"deterministic\", 0)),\n        cudnn_state_(OperatorBase::GetSingleArgument<int>(\"cudnn_state\", 0)),\n        force_algo_(OperatorBase::GetRepeatedArgument<int>(\"force_algo\", vector<int>{-1,-1,-1})),\n        enable_tensor_core_(OperatorBase::GetSingleArgument<bool>(\"enable_tensor_core\", 1)) {\n    CHECK(!deterministic_ || !exhaustive_search_);\n    CAFFE_ENFORCE(group_ > 0);\n    CAFFE_ENFORCE(!deterministic_ || !exhaustive_search_);\n    for (int i = 0; i < kernel_.size(); ++i) {\n      OPERATOR_NEEDS_FEATURE(\n          pads_[i] == pads_[kernel_.size() + i],\n          \"The current padding scheme leads to unequal padding on the left \"\n          \"and right, which is not supported by cudnn.\");\n    }\n    // dilated convolution supported by some algorithms in cuDNN v6\n#if !(CUDNN_VERSION_MIN(6,0,0))\n    OPERATOR_NEEDS_FEATURE(\n        dilation_h() == 1 && dilation_w() == 1,\n        \"The cudnn convolution does not support dilation yet.\");\n#endif\n\n#if CUDNN_VERSION_MIN(7, 0, 0)\n    // verify TensorCore math is supported\n    enable_tensor_core_ &= TensorCoreAvailable();\n#else\n    enable_tensor_core_ = false;\n#endif\n\n    bool individual_force_algo = OperatorBase::HasArgument(\"force_algo_fwd\") ||\n                                 OperatorBase::HasArgument(\"force_algo_dgrad\") ||\n                                 OperatorBase::HasArgument(\"force_algo_wgrad\");\n    if (OperatorBase::HasArgument(\"force_algo\")) {\n      CAFFE_ENFORCE(!individual_force_algo,\n                   \"Cannot specify both force_algo and any of\",\n                   \"force_algo_fwd, force_algo_dgrad, force_algo_wgrad\");\n    } else {\n      force_algo_ = std::vector<int>{-1,-1,-1};\n      force_algo_[ALGO_FWD] =\n          OperatorBase::GetSingleArgument<int>(\"force_algo_fwd\", -1);\n      force_algo_[ALGO_DGRAD] =\n          OperatorBase::GetSingleArgument<int>(\"force_algo_dgrad\", -1);\n      force_algo_[ALGO_WGRAD] =\n          OperatorBase::GetSingleArgument<int>(\"force_algo_wgrad\", -1);\n    }\n\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&bottom_desc_));\n    CUDNN_ENFORCE(cudnnCreateFilterDescriptor(&filter_desc_));\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&bias_desc_));\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&top_desc_));\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&top_desc_for_bias_));\n    CUDNN_ENFORCE(cudnnCreateConvolutionDescriptor(&conv_desc_));\n  }\n\n  ~CudnnConvOpBase() {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(bottom_desc_));\n    CUDNN_ENFORCE(cudnnDestroyFilterDescriptor(filter_desc_));\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(bias_desc_));\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(top_desc_));\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(top_desc_for_bias_));\n    CUDNN_ENFORCE(cudnnDestroyConvolutionDescriptor(conv_desc_));\n  }\n\n protected:\n  // A helper function to set up the tensor Nd desriptor, depending on the order\n  // the group and the type given.\n  template <typename T>\n  void SetTensorNdDescriptorWithGroup(\n      int size,\n      cudnnTensorDescriptor_t tensorDesc,\n      int N,\n      int C,\n      int H,\n      int W,\n      int D) {\n#if CUDNN_VERSION_MIN(7, 0, 0)\n    const int CC = C;\n#else\n    const int CC = C / group_;\n#endif\n    switch (order_) {\n      case StorageOrder::NHWC:\n        if (size == 4) {\n          CUDNN_ENFORCE(cudnnSetTensor4dDescriptorEx(\n              tensorDesc,\n              cudnnTypeWrapper<T>::type,\n              N,\n              CC,\n              H,\n              W,\n              H * W * C,\n              1,\n              W * C,\n              C));\n        } else {\n          vector<int> dims = {N, H, W, D, CC};\n          vector<int> strides = {H * W * D * CC, W * D * CC, D * CC, CC, 1};\n          CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n              tensorDesc,\n              cudnnTypeWrapper<T>::type,\n              size > 3 ? size : 4,\n              dims.data(),\n              strides.data()));\n        }\n        break;\n      case StorageOrder::NCHW:\n        if (size == 4) {\n          CUDNN_ENFORCE(cudnnSetTensor4dDescriptorEx(\n              tensorDesc,\n              cudnnTypeWrapper<T>::type,\n              N,\n              CC,\n              H,\n              W,\n              C * H * W,\n              H * W,\n              W,\n              1));\n        } else {\n          vector<int> dims = {N, CC, H, W, D};\n          vector<int> strides = {CC * H * W * D, H * W * D, W * D, D, 1};\n          CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n              tensorDesc,\n              cudnnTypeWrapper<T>::type,\n              size > 3 ? size : 4,\n              dims.data(),\n              strides.data()));\n        }\n        break;\n      default:\n        LOG(FATAL) << \"Unknown storage order: \" << order_;\n    }\n  }\n\n  void DuplicateConvDesc(\n      cudnnConvolutionDescriptor_t input,\n      size_t kernelDims,\n      size_t dilationDims,\n      cudnnConvolutionDescriptor_t copy) {\n    if (kernelDims == 2) {\n      cudnnConvolutionMode_t mode;\n      cudnnDataType_t dataType;\n      int pad_height = 0;\n      int pad_width = 0;\n      int stride_height = 0;\n      int stride_width = 0;\n      int dilation_height = 0;\n      int dilation_width = 0;\n\n#if CUDNN_VERSION_MIN(6, 0, 0)\n      CUDNN_ENFORCE(cudnnGetConvolution2dDescriptor(\n          input,\n          &pad_height,\n          &pad_width,\n          &stride_height,\n          &stride_width,\n          &dilation_height,\n          &dilation_width,\n          &mode,\n          &dataType\n          ));\n#else\n      CUDNN_ENFORCE(cudnnGetConvolution2dDescriptor(\n          input,\n          &pad_height,\n          &pad_width,\n          &stride_height,\n          &stride_width,\n          &dilation_height,\n          &dilation_width,\n          &mode\n          ));\n#endif\n\n#if CUDNN_VERSION_MIN(6, 0, 0)\n      CUDNN_ENFORCE(cudnnSetConvolution2dDescriptor(\n          copy,\n          pad_height,\n          pad_width,\n          stride_height,\n          stride_width,\n          dilation_height,\n          dilation_width,\n          mode,\n          dataType\n          ));\n#else\n      CUDNN_ENFORCE(cudnnSetConvolution2dDescriptor(\n          copy,\n          pad_height,\n          pad_width,\n          stride_height,\n          stride_width,\n          dilation_height,\n          dilation_width,\n          mode\n          ));\n#endif\n    } else {\n      cudnnConvolutionMode_t mode;\n      cudnnDataType_t dataType;\n      int arrayLength = 0;\n      vector<int> ones(dilationDims, 1);\n      CUDNN_ENFORCE(cudnnGetConvolutionNdDescriptor(\n          input,\n          kernel_.size(),\n          &arrayLength,\n          pads_.data(),\n          stride_.data(),\n          ones.data(),\n          &mode,\n          &dataType));\n\n      CUDNN_ENFORCE(cudnnSetConvolutionNdDescriptor(\n          copy,\n          kernel_.size(),\n          pads_.data(),\n          stride_.data(),\n          ones.data(),\n          mode,\n          dataType));\n    }\n  }\n\n  template <typename T>\n  cudnnDataType_t DetermineComputeTypeFromInput(const T& X) {\n    const cudaDeviceProp& prop = GetDeviceProperty(0);\n    cudnnDataType_t computeType = CUDNN_DATA_FLOAT;\n    if (X.template IsType<float16>()) {\n      if (float16_compute_ && prop.major >= 6) {\n        VLOG(1) << \"CUDNN Convolution: float16_compute specified and \"\n                << \"supported, input data is float16 - using float16 \"\n                << \"compute.\";\n        computeType = CUDNN_DATA_HALF;\n      } else if (float16_compute_) {\n        VLOG(1) << \"CUDNN Convolution: float16_compute specified but\"\n                << \"not supported, input data is float16 - using float32 \"\n                << \"compute.\";\n      } else {\n        VLOG(1) << \"CUDNN Convolution: float16_compute not specified but \"\n                << \"input data is float16 - using float32 compute.\";\n      }\n    } else {\n      VLOG(1) << \"CUDNN Convolution: using float32 compute.\";\n    }\n    return computeType;\n  }\n\n  void SetConvDescFromArguments() {\n#if CUDNN_VERSION_MIN(6, 0, 0)\n    if (kernel_.size() == 2) {\n      CUDNN_ENFORCE(cudnnSetConvolution2dDescriptor(\n          conv_desc_,\n          pad_t(),\n          pad_l(),\n          stride_h(),\n          stride_w(),\n          dilation_h(),\n          dilation_w(),\n          CUDNN_CROSS_CORRELATION,\n          compute_type_));\n    } else {\n      CUDNN_ENFORCE(cudnnSetConvolutionNdDescriptor(\n          conv_desc_,\n          kernel_.size(),\n          pads_.data(),\n          stride_.data(),\n          dilation_.data(),\n          CUDNN_CROSS_CORRELATION,\n          compute_type_));\n    }\n#else\n    if (kernel_.size() == 2) {\n      CUDNN_ENFORCE(cudnnSetConvolution2dDescriptor(\n          conv_desc_,\n          pad_t(),\n          pad_l(),\n          stride_h(),\n          stride_w(),\n          1,\n          1,\n          CUDNN_CROSS_CORRELATION));\n    } else {\n      vector<int> ones(dilation_.size(), 1);\n      CUDNN_ENFORCE(cudnnSetConvolutionNdDescriptor(\n          conv_desc_,\n          kernel_.size(),\n          pads_.data(),\n          stride_.data(),\n          ones.data(),\n          CUDNN_CROSS_CORRELATION,\n          compute_type_));\n    }\n#endif\n  }\n\n  void SetConvDescComputeType(\n      cudnnConvolutionDescriptor_t conv_desc,\n      cudnnDataType_t math) {\n    if (kernel_.size() == 2) {\n      cudnnConvolutionMode_t mode;\n      cudnnDataType_t dataType;\n      int pad_height = 0;\n      int pad_width = 0;\n      int stride_height = 0;\n      int stride_width = 0;\n      int dilation_height = 0;\n      int dilation_width = 0;\n\n#if CUDNN_VERSION_MIN(6, 0, 0)\n      CUDNN_ENFORCE(cudnnGetConvolution2dDescriptor(\n          conv_desc,\n          &pad_height,\n          &pad_width,\n          &stride_height,\n          &stride_width,\n          &dilation_height,\n          &dilation_width,\n          &mode,\n          &dataType\n          ));\n#else\n      CUDNN_ENFORCE(cudnnGetConvolution2dDescriptor(\n          conv_desc,\n          &pad_height,\n          &pad_width,\n          &stride_height,\n          &stride_width,\n          &dilation_height,\n          &dilation_width,\n          &mode\n          ));\n#endif\n\n#if CUDNN_VERSION_MIN(6, 0, 0)\n      CUDNN_ENFORCE(cudnnSetConvolution2dDescriptor(\n          conv_desc,\n          pad_height,\n          pad_width,\n          stride_height,\n          stride_width,\n          dilation_height,\n          dilation_width,\n          mode,\n          math\n          ));\n#else\n      CUDNN_ENFORCE(cudnnSetConvolution2dDescriptor(\n          conv_desc,\n          pad_height,\n          pad_width,\n          stride_height,\n          stride_width,\n          dilation_height,\n          dilation_width,\n          mode\n          ));\n#endif\n    } else {\n      cudnnConvolutionMode_t mode;\n      cudnnDataType_t dataType;\n      int arrayLength = 0;\n      vector<int> ones(dilation_.size(), 1);\n      CUDNN_ENFORCE(cudnnGetConvolutionNdDescriptor(\n          conv_desc,\n          kernel_.size(),\n          &arrayLength,\n          pads_.data(),\n          stride_.data(),\n          ones.data(),\n          &mode,\n          &dataType));\n\n      CUDNN_ENFORCE(cudnnSetConvolutionNdDescriptor(\n          conv_desc,\n          kernel_.size(),\n          pads_.data(),\n          stride_.data(),\n          ones.data(),\n          mode,\n          math));\n    }\n  }\n\n  vector<TIndex> cudnn_input_dims_;\n  vector<TIndex> cudnn_filter_dims_;\n\n  CuDNNWrapper cudnn_wrapper_;\n  cudnnTensorDescriptor_t bottom_desc_;\n  cudnnFilterDescriptor_t filter_desc_;\n  cudnnTensorDescriptor_t bias_desc_;\n  cudnnTensorDescriptor_t top_desc_;\n  // top desc for bias add in case we do group convolution\n  cudnnTensorDescriptor_t top_desc_for_bias_;\n  cudnnConvolutionDescriptor_t conv_desc_;\n  const size_t cudnn_ws_nbytes_limit_;\n  size_t cudnn_ws_nbytes_;\n  bool exhaustive_search_;\n  bool deterministic_;\n  size_t cudnn_state_;\n  vector<int> force_algo_; // stored as FWD, dFILTER, dDATA\n  bool enable_tensor_core_;\n  cudnnDataType_t compute_type_;\n};\n\nclass CudnnConvOp final : public CudnnConvOpBase {\n public:\n  CudnnConvOp(const OperatorDef& operator_def, Workspace* ws)\n      : CudnnConvOpBase(operator_def, ws) {}\n\n  ~CudnnConvOp() {}\n\n  template <typename T_X, typename T_W, typename T_B, typename T_Y>\n  bool DoRunWithType();\n\n  bool RunOnDevice() override;\n\n private:\n  cudnnConvolutionFwdAlgo_t algo_;\n  using ConvFwdAlgorithmWithCost = std::tuple<cudnnConvolutionFwdAlgo_t, float>;\n  AlgorithmsCache<ConvFwdAlgorithmWithCost> algo_cache_;\n  // Input: X, W, b\n  // Output: Y\n  INPUT_TAGS(INPUT, FILTER, BIAS);\n};\n\nclass CudnnConvGradientOp final : public CudnnConvOpBase {\n public:\n  CudnnConvGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : CudnnConvOpBase(operator_def, ws),\n        no_bias_(OperatorBase::GetSingleArgument<int>(\"no_bias\", 0)) {\n    CAFFE_ENFORCE(\n        !(no_bias_ && OutputSize() == 3),\n        \"If bias is not present, you should not have 3 grad output.\");\n\n    CUDNN_ENFORCE(cudnnCreateConvolutionDescriptor(&bwd_data_conv_desc_));\n    CUDNN_ENFORCE(cudnnCreateConvolutionDescriptor(&bwd_filter_conv_desc_));\n  }\n\n  ~CudnnConvGradientOp() {\n    CUDNN_ENFORCE(cudnnDestroyConvolutionDescriptor(bwd_data_conv_desc_));\n    CUDNN_ENFORCE(cudnnDestroyConvolutionDescriptor(bwd_filter_conv_desc_));\n  }\n\n  template <\n      typename T_X,\n      typename T_DY,\n      typename T_W,\n      typename T_B,\n      typename T_DX,\n      typename T_DW,\n      typename T_DB>\n  bool DoRunWithType();\n\n  bool RunOnDevice() override;\n\n private:\n  cudnnConvolutionDescriptor_t bwd_filter_conv_desc_;\n  cudnnConvolutionDescriptor_t bwd_data_conv_desc_;\n  cudnnConvolutionBwdFilterAlgo_t bwd_filter_algo_;\n  cudnnConvolutionBwdDataAlgo_t bwd_data_algo_;\n  using ConvBwdFilterAlgorithmWithCost =\n      std::tuple<cudnnConvolutionBwdFilterAlgo_t, float>;\n  using ConvBwdDataAlgorithmWithCost =\n      std::tuple<cudnnConvolutionBwdDataAlgo_t, float>;\n  AlgorithmsCache<ConvBwdFilterAlgorithmWithCost> filter_algo_cache_;\n  AlgorithmsCache<ConvBwdDataAlgorithmWithCost> data_algo_cache_;\n  bool no_bias_;\n  // input: X, W, dY\n  // output: dW, db, and optionally dX\n  INPUT_TAGS(INPUT, FILTER, OUTPUT_GRAD);\n  OUTPUT_TAGS(FILTER_GRAD, BIAS_OR_INPUT_GRAD, INPUT_GRAD);\n};\n\n////////////////////////////////////////////////////////////////////////////////\n// Implementations\n////////////////////////////////////////////////////////////////////////////////\n\nstatic constexpr std::array<cudnnDataType_t, 2> kComputeTypesToTry = {\n    CUDNN_DATA_FLOAT,\n    CUDNN_DATA_HALF};\nstatic constexpr std::array<const char*, 2> kComputePassNames = {\n    \"fp32 compute\",\n    \"fp16 compute\"};\n\ntemplate <typename T_X, typename T_W, typename T_B, typename T_Y>\nbool CudnnConvOp::DoRunWithType() {\n  auto& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  auto* Y = Output(0);\n\n  // Figure out the output shape\n  CAFFE_ENFORCE(X.ndim() >= 3 && X.ndim() <= 5);\n  CAFFE_ENFORCE(filter.ndim() >= 3 && filter.ndim() <= 5);\n  const int M = filter.dim32(0);\n  ConvPoolOpBase<CUDAContext>::SetOutputSize(X, Y, M);\n  int N = 0, C = 0, H = 0, W = 0, D = 0, H_out = 0, W_out = 0, D_out = 0;\n  int group_offset_X = 0, group_offset_Y = 0;\n\n  switch (order_) {\n    case StorageOrder::NHWC:\n      N = X.dim32(0);\n      H = X.dim32(1);\n      W = X.ndim() > 3 ? X.dim32(2) : 1;\n      D = X.ndim() > 4 ? X.dim32(3) : 1;\n      C = X.dim32(X.ndim() - 1);\n      H_out = Y->dim32(1);\n      W_out = Y->ndim() > 3 ? Y->dim32(2) : 1;\n      D_out = Y->ndim() > 4 ? Y->dim32(3) : 1;\n      for (int i = 0; i < kernel_.size(); ++i) {\n        CAFFE_ENFORCE_EQ(filter.dim32(i + 1), kernel_[i]);\n      }\n      CAFFE_ENFORCE_EQ(filter.dim32(filter.ndim() - 1), C / group_);\n      group_offset_X = C / group_;\n      group_offset_Y = M / group_;\n      break;\n    case StorageOrder::NCHW:\n      N = X.dim32(0);\n      C = X.dim32(1);\n      H = X.dim32(2);\n      W = X.ndim() > 3 ? X.dim32(3) : 1;\n      D = X.ndim() > 4 ? X.dim32(4) : 1;\n      H_out = Y->dim32(2);\n      W_out = Y->ndim() > 3 ? Y->dim32(3) : 1;\n      D_out = Y->ndim() > 4 ? Y->dim32(4) : 1;\n      CAFFE_ENFORCE_EQ(filter.dim32(1), C / group_);\n      for (int i = 0; i < kernel_.size(); ++i) {\n        CAFFE_ENFORCE_EQ(filter.dim32(i + 2), kernel_[i]);\n      }\n      group_offset_X = C / group_ * H * W * D;\n      group_offset_Y = M / group_ * H_out * W_out * D_out;\n      break;\n    default:\n      LOG(FATAL) << \"Unknown storage order: \" << order_;\n  }\n\n  CAFFE_ENFORCE(\n      C % group_ == 0,\n      \"If you set group, the number of input channels should be divisible \"\n      \"by group.\");\n  CAFFE_ENFORCE(\n      M % group_ == 0,\n      \"If you set group, the number of output channels should be divisible \"\n      \"by group.\");\n\n  int group_offset_filter = filter.size() / group_;\n\n  // Set up the cudnn algorithms & workspace if necessary\n  bool input_changed = (X.dims() != cudnn_input_dims_);\n  bool filter_changed = (filter.dims() != cudnn_filter_dims_);\n  if (input_changed || filter_changed) {\n    VLOG(1) << \"Changing the cudnn descriptor configurations.\";\n    if (input_changed) {\n      cudnn_input_dims_ = X.dims();\n      SetTensorNdDescriptorWithGroup<T_X>(\n          X.ndim(), bottom_desc_, N, C, H, W, D);\n    }\n    if (filter_changed) {\n      cudnn_filter_dims_ = filter.dims();\n      if (kernel_.size() == 2) {\n#if CUDNN_VERSION_MIN(7, 0, 0)\n        const int MM = M;\n#else\n        const int MM = M / group_;\n#endif\n        CUDNN_ENFORCE(cudnnSetFilter4dDescriptor(\n            filter_desc_,\n            cudnnTypeWrapper<T_W>::type,\n            GetCudnnTensorFormat(order_),\n            MM,\n            C / group_,\n            kernel_h(),\n            kernel_w()));\n      } else {\n        vector<int> dims(filter.dims().begin(), filter.dims().end());\n        dims[0] /= group_;\n#if !CUDNN_VERSION_MIN(7, 0, 0)\n        order_ == StorageOrder::NCHW ? dims[1] /= group_\n                                     : dims[filter.ndim() - 1] /= group_;\n#endif\n        dims[filter.ndim() - 1] /= group_;\n        CUDNN_ENFORCE(cudnnSetFilterNdDescriptor(\n            filter_desc_,\n            cudnnTypeWrapper<T_W>::type,\n            GetCudnnTensorFormat(order_),\n            dims.size(),\n            dims.data()));\n      }\n      if (InputSize() == 3) {\n        if (kernel_.size() == 2) {\n          CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n              bias_desc_,\n              GetCudnnTensorFormat(order_),\n              cudnnTypeWrapper<T_B>::type,\n              1,\n              M,\n              1,\n              1));\n        } else {\n          std::vector<int> bias_dims(X.ndim(), 1);\n          bias_dims[1] = M;\n          std::vector<int> strides = {M, 1, 1, 1, 1, 1};\n          CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n              bias_desc_,\n              cudnnTypeWrapper<T_B>::type,\n              X.ndim() > 3 ? X.ndim() : 4,\n              bias_dims.data(),\n              strides.data()));\n        }\n      }\n    }\n    // Set the output\n    SetTensorNdDescriptorWithGroup<T_Y>(\n        X.ndim(), top_desc_, N, M, H_out, W_out, D_out);\n    // Set the output with descriptor useful for bias addition in one run.\n    if (kernel_.size() == 2) {\n      CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n          top_desc_for_bias_,\n          GetCudnnTensorFormat(order_),\n          cudnnTypeWrapper<T_B>::type,\n          N,\n          M,\n          H_out,\n          W_out));\n    } else {\n      vector<int> dims = {N, M, H_out, W_out, D_out};\n      vector<int> strides = {M * H_out * W_out * D_out,\n                             H_out * W_out * D_out,\n                             W_out * D_out,\n                             D_out,\n                             1};\n      CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n          top_desc_for_bias_,\n          cudnnTypeWrapper<T_B>::type,\n          X.ndim() > 3 ? X.ndim() : 4,\n          dims.data(),\n          strides.data()));\n    }\n\n    compute_type_ = DetermineComputeTypeFromInput(X);\n    SetConvDescFromArguments();\n\n#if CUDNN_VERSION_MIN(7, 0, 0)\n    if (enable_tensor_core_) {\n      CUDNN_ENFORCE(\n          cudnnSetConvolutionMathType(conv_desc_, CUDNN_TENSOR_OP_MATH));\n    }\n\n    // enable cuDNN conv groups\n    CUDNN_CHECK(cudnnSetConvolutionGroupCount(conv_desc_, group_));\n#endif\n\n    if (force_algo_[ALGO_FWD] >= 0) {\n      algo_ = (cudnnConvolutionFwdAlgo_t)force_algo_[ALGO_FWD];\n    } else if (deterministic_) {\n      algo_ = CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_PRECOMP_GEMM;\n    } else if (exhaustive_search_) {\n      // Even when FP16 compute is supported and requested, try FP32\n      // because it may be faster. However, if FP32 compute is specified,\n      // FP16 is not a suitable alternative - early out from the loop.\n      std::array<ConvFwdAlgorithmWithCost, 2> algosToCompare;\n      for (int i = 0; i < 2; i++) {\n        SetConvDescComputeType(conv_desc_, kComputeTypesToTry[i]);\n\n        algosToCompare[i] = algo_cache_.getAlgorithm(\n            X.dims(), filter.dims(), kComputeTypesToTry[i], [&]() {\n              VLOG(1) << \"CUDNN Convolution fwd: doing exhaustive \"\n                      << \"search for \" << kComputePassNames[i];\n              // When we do an exhaustive search, we will ignore the workspace\n              // size limit and simply go for the fastest algorithm. If you\n              // happen to run out of memory later, you will be on your own...\n              int returned_algo_count;\n              std::array<cudnnConvolutionFwdAlgoPerf_t, kNUM_CUDNN_FWD_ALGS>\n                  fwd_perf_stat;\n\n              // no need to clean up workspace,\n              cudnn_wrapper_.with_cudnn_state(\n                  cudnn_state_, [&](CuDNNState* state) {\n                    // Actually run the search.\n                    CUDNN_ENFORCE(cudnnFindConvolutionForwardAlgorithmEx(\n                        state->cudnn_handle(),\n                        bottom_desc_,\n                        X.template data<T_X>(),\n                        filter_desc_,\n                        filter.template data<T_W>(),\n                        conv_desc_,\n                        top_desc_,\n                        Y->template mutable_data<T_Y>(),\n                        kNUM_CUDNN_FWD_ALGS,\n                        &returned_algo_count,\n                        fwd_perf_stat.data(),\n                        state->workspace().get(cudnn_ws_nbytes_limit_),\n                        cudnn_ws_nbytes_limit_));\n                  });\n              LogCuDNNPerfStats(fwd_perf_stat, returned_algo_count);\n              float algo_time = fwd_perf_stat[0].status == CUDNN_STATUS_SUCCESS\n                  ? fwd_perf_stat[0].time\n                  : 1e10;\n              return ConvFwdAlgorithmWithCost(fwd_perf_stat[0].algo, algo_time);\n            });\n\n        // When set to fp32 compute, don't try fp16\n        if (compute_type_ == CUDNN_DATA_FLOAT) {\n          break;\n        }\n      }\n\n      if (compute_type_ == CUDNN_DATA_FLOAT) {\n        // For FP32 compute, just use the best FP32 algorithm\n        algo_ = std::get<0>(algosToCompare[0]);\n      } else {\n        // For FP16 compute, choose algo with fastest execution\n        int bestAlgoIndex =\n            (std::get<1>(algosToCompare[0]) < std::get<1>(algosToCompare[1]))\n            ? 0\n            : 1;\n        algo_ = std::get<0>(algosToCompare[bestAlgoIndex]);\n        SetConvDescComputeType(conv_desc_, kComputeTypesToTry[bestAlgoIndex]);\n      }\n    } else {\n      // Get the convolution algorithm based on the workspace limit.\n      CUDNN_ENFORCE(cudnnGetConvolutionForwardAlgorithm(\n          cudnn_wrapper_.inline_cudnn_handle(),\n          bottom_desc_,\n          filter_desc_,\n          conv_desc_,\n          top_desc_,\n          CUDNN_CONVOLUTION_FWD_SPECIFY_WORKSPACE_LIMIT,\n          cudnn_ws_nbytes_limit_,\n          &algo_));\n    }\n    CUDNN_ENFORCE(cudnnGetConvolutionForwardWorkspaceSize(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        bottom_desc_,\n        filter_desc_,\n        conv_desc_,\n        top_desc_,\n        algo_,\n        &cudnn_ws_nbytes_));\n    VLOG(1) << \"CuDNN algorithm: \" << algo_;\n    VLOG(1) << \"CuDNN workspace size: \" << cudnn_ws_nbytes_;\n  }\n\n  // Now, actually run the computation.\n  // Run directly through cuDNN if possible\n#if CUDNN_VERSION_MIN(7,0,0)\n  cudnn_wrapper_.with_cudnn_state(cudnn_state_, [&](CuDNNState* state) {\n    CUDNN_ENFORCE(cudnnConvolutionForward(\n        state->cudnn_handle(),\n        cudnnTypeWrapper<T_X>::kOne(),\n        bottom_desc_,\n        X.template data<T_X>(),\n        filter_desc_,\n        filter.template data<T_W>(),\n        conv_desc_,\n        algo_,\n        state->workspace().get(cudnn_ws_nbytes_),\n        cudnn_ws_nbytes_,\n        cudnnTypeWrapper<T_Y>::kZero(),\n        top_desc_,\n        Y->template mutable_data<T_Y>()));\n  });\n#else\n  // otherwise manually run through groups\n  for (int i = 0; i < group_; ++i) {\n    cudnn_wrapper_.with_cudnn_state(cudnn_state_, [&](CuDNNState* state) {\n      CUDNN_ENFORCE(cudnnConvolutionForward(\n          state->cudnn_handle(),\n          cudnnTypeWrapper<T_X>::kOne(),\n          bottom_desc_,\n          X.template data<T_X>() + i * group_offset_X,\n          filter_desc_,\n          filter.template data<T_W>() + i * group_offset_filter,\n          conv_desc_,\n          algo_,\n          state->workspace().get(cudnn_ws_nbytes_),\n          cudnn_ws_nbytes_,\n          cudnnTypeWrapper<T_Y>::kZero(),\n          top_desc_,\n          Y->template mutable_data<T_Y>() + i * group_offset_Y));\n    });\n  }\n#endif\n  // Bias\n  if (InputSize() == 3) {\n    auto& bias = Input(BIAS);\n\n    CAFFE_ENFORCE_EQ(bias.ndim(), 1);\n    CAFFE_ENFORCE_EQ(bias.dim32(0), M);\n\n    CUDNN_ENFORCE(cudnnAddTensor(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        cudnnTypeWrapper<T_B>::kOne(),\n        bias_desc_,\n        bias.template data<T_B>(),\n        cudnnTypeWrapper<T_Y>::kOne(),\n        top_desc_for_bias_,\n        Y->template mutable_data<T_Y>()));\n  }\n  // Done.\n  return true;\n}\n\nbool CudnnConvOp::RunOnDevice() {\n  if (Input(0).IsType<float>()) {\n    return DoRunWithType<\n        float, // X\n        float, // W\n        float, // B\n        float>(); // Y\n  } else if (Input(0).IsType<float16>()) {\n    return DoRunWithType<\n        float16, // X\n        float16, // W\n        float16, // B\n        float16>(); // Y\n  } else {\n    LOG(FATAL) << \"Only float (32bit) and float16 are supported by \"\n               << \"cudnn convolution, but input \" << debug_def().input(0)\n               << \" has [\" << Input(0).meta().name() << \"]\";\n  }\n  return true;\n}\n\ntemplate <\n    typename T_X,\n    typename T_DY,\n    typename T_W,\n    typename T_B,\n    typename T_DX,\n    typename T_DW,\n    typename T_DB>\nbool CudnnConvGradientOp::DoRunWithType() {\n  auto& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  auto& dY = Input(OUTPUT_GRAD);\n  auto* dfilter = Output(FILTER_GRAD);\n\n  CAFFE_ENFORCE(X.ndim() >= 3 && X.ndim() <= 5);\n  CAFFE_ENFORCE(filter.ndim() >= 3 && filter.ndim() <= 5);\n\n  const int M = filter.dim32(0);\n  int N = 0, C = 0, H = 0, W = 0, D = 0, H_out = 0, W_out = 0, D_out = 0;\n  int group_offset_X = 0, group_offset_Y = 0;\n\n  switch (order_) {\n    case StorageOrder::NHWC:\n      N = X.dim32(0);\n      H = X.dim32(1);\n      W = X.ndim() > 3 ? X.dim32(2) : 1;\n      D = X.ndim() > 4 ? X.dim32(3) : 1;\n      C = X.dim32(X.ndim() - 1);\n      H_out = dY.dim32(1);\n      W_out = dY.ndim() > 3 ? dY.dim32(2) : 1;\n      D_out = dY.ndim() > 4 ? dY.dim32(3) : 1;\n      for (int i = 0; i < kernel_.size(); ++i) {\n        CAFFE_ENFORCE_EQ(filter.dim32(i + 1), kernel_[i]);\n      }\n      CAFFE_ENFORCE_EQ(filter.dim32(filter.ndim() - 1), C / group_);\n      group_offset_X = C / group_;\n      group_offset_Y = M / group_;\n      break;\n    case StorageOrder::NCHW:\n      N = X.dim32(0);\n      C = X.dim32(1);\n      H = X.dim32(2);\n      W = X.ndim() > 3 ? X.dim32(3) : 1;\n      D = X.ndim() > 4 ? X.dim32(4) : 1;\n      H_out = dY.dim32(2);\n      W_out = dY.ndim() > 3 ? dY.dim32(3) : 1;\n      D_out = dY.ndim() > 4 ? dY.dim32(4) : 1;\n      CAFFE_ENFORCE_EQ(filter.dim32(1), C / group_);\n      for (int i = 0; i < kernel_.size(); ++i) {\n        CAFFE_ENFORCE_EQ(filter.dim32(i + 2), kernel_[i]);\n      }\n      group_offset_X = C / group_ * H * W * D;\n      group_offset_Y = M / group_ * H_out * W_out * D_out;\n      break;\n    default:\n      LOG(FATAL) << \"Unknown storage order: \" << order_;\n  }\n\n  CAFFE_ENFORCE(\n      C % group_ == 0,\n      \"If you set group, the number of input channels should be divisible \"\n      \"by group.\");\n  CAFFE_ENFORCE(\n      M % group_ == 0,\n      \"If you set group, the number of output channels should be divisible \"\n      \"by group.\");\n\n  int group_offset_filter = filter.size() / group_;\n  if (kernel_.size() == 1) {\n    ConvPoolOpBase<CUDAContext>::ComputePads({H});\n  } else if (kernel_.size() == 2) {\n    ConvPoolOpBase<CUDAContext>::ComputePads({H, W});\n  } else if (kernel_.size() == 3) {\n    ConvPoolOpBase<CUDAContext>::ComputePads({H, W, D});\n  } else {\n    CAFFE_THROW(\"Unsupported kernel size:\", kernel_.size());\n  }\n  dfilter->ResizeLike(filter);\n\n  // Set up the cudnn algorithms & workspace if necessary\n  bool input_changed = (X.dims() != cudnn_input_dims_);\n  bool filter_changed = (filter.dims() != cudnn_filter_dims_);\n  if (input_changed || filter_changed) {\n    VLOG(1) << \"Changing the cudnn descriptor configurations.\";\n    if (input_changed) {\n      cudnn_input_dims_ = X.dims();\n      SetTensorNdDescriptorWithGroup<T_X>(\n          X.ndim(), bottom_desc_, N, C, H, W, D);\n    }\n    if (filter_changed) {\n      cudnn_filter_dims_ = filter.dims();\n      if (kernel_.size() == 2) {\n#if CUDNN_VERSION_MIN(7, 0, 0)\n        const int MM = M;\n#else\n        const int MM = M / group_;\n#endif\n        CUDNN_ENFORCE(cudnnSetFilter4dDescriptor(\n            filter_desc_,\n            cudnnTypeWrapper<T_W>::type,\n            GetCudnnTensorFormat(order_),\n            MM,\n            C / group_,\n            kernel_h(),\n            kernel_w()));\n      } else {\n        vector<int> dims(filter.dims().begin(), filter.dims().end());\n#if !CUDNN_VERSION_MIN(7, 0, 0)\n        dims[0] /= group_;\n#endif\n        order_ == StorageOrder::NCHW ? dims[1] /= group_\n                                     : dims[filter.ndim() - 1] /= group_;\n        CUDNN_ENFORCE(cudnnSetFilterNdDescriptor(\n            filter_desc_,\n            cudnnTypeWrapper<T_W>::type,\n            GetCudnnTensorFormat(order_),\n            dims.size(),\n            dims.data()));\n      }\n      if (!no_bias_) {\n        if (kernel_.size() == 2) {\n          CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n              bias_desc_,\n              GetCudnnTensorFormat(order_),\n              cudnnTypeWrapper<T_B>::type,\n              1,\n              M,\n              1,\n              1));\n        } else {\n          std::vector<int> bias_dims(X.ndim(), 1);\n          bias_dims[1] = M;\n          std::vector<int> strides = {M, 1, 1, 1, 1, 1};\n          CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n              bias_desc_,\n              cudnnTypeWrapper<T_B>::type,\n              X.ndim() > 3 ? X.ndim() : 4,\n              bias_dims.data(),\n              strides.data()));\n        }\n      }\n    }\n    // Set the output\n    SetTensorNdDescriptorWithGroup<T_DX>(\n        X.ndim(), top_desc_, N, M, H_out, W_out, D_out);\n    // Set the output with descriptor useful for bias addition in one run.\n    if (kernel_.size() == 2) {\n      CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n          top_desc_for_bias_,\n          GetCudnnTensorFormat(order_),\n          cudnnTypeWrapper<T_B>::type,\n          N,\n          M,\n          H_out,\n          W_out));\n    } else {\n      vector<int> dims = {N, M, H_out, W_out, D_out};\n      vector<int> strides = {M * H_out * W_out * D_out,\n                             H_out * W_out * D_out,\n                             W_out * D_out,\n                             D_out,\n                             1};\n      CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n          top_desc_for_bias_,\n          cudnnTypeWrapper<T_B>::type,\n          X.ndim() > 3 ? X.ndim() : 4,\n          dims.data(),\n          strides.data()));\n    }\n\n    compute_type_ = DetermineComputeTypeFromInput(X);\n    SetConvDescFromArguments();\n\n    DuplicateConvDesc(\n        conv_desc_, kernel_.size(), dilation_.size(), bwd_filter_conv_desc_);\n    DuplicateConvDesc(\n        conv_desc_, kernel_.size(), dilation_.size(), bwd_data_conv_desc_);\n\n#if CUDNN_VERSION_MIN(7, 0, 0)\n    if (enable_tensor_core_) {\n      CUDNN_ENFORCE(cudnnSetConvolutionMathType(\n          bwd_filter_conv_desc_, CUDNN_TENSOR_OP_MATH));\n      CUDNN_ENFORCE(cudnnSetConvolutionMathType(\n          bwd_data_conv_desc_, CUDNN_TENSOR_OP_MATH));\n    }\n\n    // set cuDNN groups if appropriate\n    CUDNN_CHECK(cudnnSetConvolutionGroupCount(bwd_filter_conv_desc_, group_));\n    CUDNN_CHECK(cudnnSetConvolutionGroupCount(bwd_data_conv_desc_, group_));\n#endif\n\n    // Choose dW algorithm\n    if (force_algo_[ALGO_WGRAD] >= 0) {\n      bwd_filter_algo_ =\n          (cudnnConvolutionBwdFilterAlgo_t)force_algo_[ALGO_WGRAD];\n    } else if (deterministic_) {\n      bwd_filter_algo_ = CUDNN_CONVOLUTION_BWD_FILTER_ALGO_1;\n    } else if (exhaustive_search_) {\n      // Even when FP16 compute is supported and requested, try FP32\n      // because it may be faster. However, if FP32 compute is specified,\n      // FP16 is not a suitable alternative - early out from the loop.\n      std::array<ConvBwdFilterAlgorithmWithCost, 2> algosToCompare;\n      for (int i = 0; i < 2; i++) {\n        SetConvDescComputeType(bwd_filter_conv_desc_, kComputeTypesToTry[i]);\n\n        algosToCompare[i] = filter_algo_cache_.getAlgorithm(\n            X.dims(), filter.dims(), kComputeTypesToTry[i], [&]() {\n              VLOG(1) << \"CUDNN Convolution bwd: doing filter exhaustive\"\n                      << \"search for \" << kComputePassNames[i];\n              // When we do an exhaustive search, we will ignore the workspace\n              // size limit and simply go for the fastest algorithm. If you\n              // happen to run out of memory later, you will be on your own...\n              int returned_algo_count;\n              // We clean up the current workspace memory so that the forward\n              // algorithm is free to allocate memory.\n              // Actually run the search.\n              std::array<\n                  cudnnConvolutionBwdFilterAlgoPerf_t,\n                  kNUM_CUDNN_BWD_FILTER_ALGS>\n                  filter_perf_stat;\n\n              cudnn_wrapper_.with_cudnn_state(\n                  cudnn_state_, [&](CuDNNState* state) {\n                    CUDNN_ENFORCE(cudnnFindConvolutionBackwardFilterAlgorithmEx(\n                        state->cudnn_handle(),\n                        bottom_desc_,\n                        X.template data<T_X>(),\n                        top_desc_,\n                        dY.template data<T_DY>(),\n                        bwd_filter_conv_desc_,\n                        filter_desc_,\n                        dfilter->template mutable_data<T_DW>(),\n                        kNUM_CUDNN_BWD_FILTER_ALGS,\n                        &returned_algo_count,\n                        filter_perf_stat.data(),\n                        state->workspace().get(cudnn_ws_nbytes_limit_),\n                        cudnn_ws_nbytes_limit_));\n                  });\n              LogCuDNNPerfStats(filter_perf_stat, returned_algo_count);\n              float algo_time =\n                  filter_perf_stat[0].status == CUDNN_STATUS_SUCCESS\n                  ? filter_perf_stat[0].time\n                  : 1e10;\n              return ConvBwdFilterAlgorithmWithCost(\n                  filter_perf_stat[0].algo, algo_time);\n            });\n\n        // When set to fp32 compute, don't try fp16\n        if (compute_type_ == CUDNN_DATA_FLOAT) {\n          break;\n        }\n      }\n\n      if (compute_type_ == CUDNN_DATA_FLOAT) {\n        // For FP32 compute, just use the best FP32 algorithm\n        bwd_filter_algo_ = std::get<0>(algosToCompare[0]);\n      } else {\n        // For FP16 compute, choose algo with fastest execution\n        int bestAlgoIndex =\n            (std::get<1>(algosToCompare[0]) < std::get<1>(algosToCompare[1]))\n            ? 0\n            : 1;\n        bwd_filter_algo_ = std::get<0>(algosToCompare[bestAlgoIndex]);\n        SetConvDescComputeType(\n            bwd_filter_conv_desc_, kComputeTypesToTry[bestAlgoIndex]);\n      }\n    } else {\n      // choose backward algorithm for filter\n      CUDNN_ENFORCE(cudnnGetConvolutionBackwardFilterAlgorithm(\n          cudnn_wrapper_.inline_cudnn_handle(),\n          bottom_desc_,\n          top_desc_,\n          bwd_filter_conv_desc_,\n          filter_desc_,\n          CUDNN_CONVOLUTION_BWD_FILTER_SPECIFY_WORKSPACE_LIMIT,\n          cudnn_ws_nbytes_limit_,\n          &bwd_filter_algo_));\n    }\n    // Pick dX algo if needed\n    if (OutputSize() == 3 || (no_bias_ && (OutputSize() == 2))) {\n      if (force_algo_[ALGO_DGRAD] >= 0) {\n        bwd_data_algo_ = (cudnnConvolutionBwdDataAlgo_t)force_algo_[ALGO_DGRAD];\n      } else if (deterministic_) {\n        bwd_data_algo_ = CUDNN_CONVOLUTION_BWD_DATA_ALGO_1;\n      } else if (exhaustive_search_) {\n        // Even when FP16 compute is supported and requested, try FP32\n        // because it may be faster. However, if FP32 compute is specified,\n        // FP16 is not a suitable alternative - early out from the loop.\n        std::array<ConvBwdDataAlgorithmWithCost, 2> algosToCompare;\n        for (int i = 0; i < 2; i++) {\n          SetConvDescComputeType(bwd_data_conv_desc_, kComputeTypesToTry[i]);\n\n          algosToCompare[i] = data_algo_cache_.getAlgorithm(\n              X.dims(), filter.dims(), kComputeTypesToTry[i], [&]() {\n                VLOG(1) << \"CUDNN Convolution bwd: doing data exhaustive\"\n                        << \"search for \" << kComputePassNames[i];\n                int returned_algo_count;\n\n                std::array<\n                    cudnnConvolutionBwdDataAlgoPerf_t,\n                    kNUM_CUDNN_BWD_DATA_ALGS>\n                    data_perf_stat;\n                cudnn_wrapper_.with_cudnn_state(\n                    cudnn_state_, [&](CuDNNState* state) {\n                      auto* dX =\n                          Output(no_bias_ ? BIAS_OR_INPUT_GRAD : INPUT_GRAD);\n                      dX->ResizeLike(X);\n                      const T_W* filter_data = filter.template data<T_W>();\n                      const T_DY* dYdata = dY.template data<T_DY>();\n                      T_DX* dXdata = dX->template mutable_data<T_DX>();\n                      CUDNN_ENFORCE(cudnnFindConvolutionBackwardDataAlgorithmEx(\n                          state->cudnn_handle(),\n                          filter_desc_,\n                          filter_data,\n                          top_desc_,\n                          dYdata,\n                          bwd_data_conv_desc_,\n                          bottom_desc_,\n                          dXdata,\n                          kNUM_CUDNN_BWD_DATA_ALGS,\n                          &returned_algo_count,\n                          data_perf_stat.data(),\n                          state->workspace().get(cudnn_ws_nbytes_limit_),\n                          cudnn_ws_nbytes_limit_));\n                    });\n\n                LogCuDNNPerfStats(data_perf_stat, returned_algo_count);\n                float algo_time =\n                    data_perf_stat[0].status == CUDNN_STATUS_SUCCESS\n                    ? data_perf_stat[0].time\n                    : 1e10;\n                return ConvBwdDataAlgorithmWithCost(\n                    data_perf_stat[0].algo, algo_time);\n              });\n\n          // When set to fp32 compute, don't try fp16\n          if (compute_type_ == CUDNN_DATA_FLOAT) {\n            break;\n          }\n        }\n\n        if (compute_type_ == CUDNN_DATA_FLOAT) {\n          // For FP32 compute, just use the best FP32 algorithm\n          bwd_data_algo_ = std::get<0>(algosToCompare[0]);\n        } else {\n          // For FP16 compute, choose algo with fastest execution\n          int bestAlgoIndex =\n              (std::get<1>(algosToCompare[0]) < std::get<1>(algosToCompare[1]))\n              ? 0\n              : 1;\n          bwd_data_algo_ = std::get<0>(algosToCompare[bestAlgoIndex]);\n          SetConvDescComputeType(\n              bwd_data_conv_desc_, kComputeTypesToTry[bestAlgoIndex]);\n        }\n      } else {\n        CUDNN_ENFORCE(cudnnGetConvolutionBackwardDataAlgorithm(\n            cudnn_wrapper_.inline_cudnn_handle(),\n            filter_desc_,\n            top_desc_,\n            bwd_data_conv_desc_,\n            bottom_desc_,\n            CUDNN_CONVOLUTION_BWD_DATA_SPECIFY_WORKSPACE_LIMIT,\n            cudnn_ws_nbytes_limit_,\n            &bwd_data_algo_));\n      }\n    }\n\n    // get workspace size for backwards filter algorithm\n    size_t bwd_filter_ws_size, bwd_data_ws_size;\n\n    CUDNN_ENFORCE(cudnnGetConvolutionBackwardFilterWorkspaceSize(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        bottom_desc_,\n        top_desc_,\n        bwd_filter_conv_desc_,\n        filter_desc_,\n        bwd_filter_algo_,\n        &bwd_filter_ws_size));\n    if (OutputSize() == 3 || (no_bias_ && (OutputSize() == 2))) {\n      // get workspace size for backwards data algorithm\n      CUDNN_ENFORCE(cudnnGetConvolutionBackwardDataWorkspaceSize(\n          cudnn_wrapper_.inline_cudnn_handle(),\n          filter_desc_,\n          top_desc_,\n          bwd_data_conv_desc_,\n          bottom_desc_,\n          bwd_data_algo_,\n          &bwd_data_ws_size));\n    } else {\n      bwd_data_ws_size = 0;\n    }\n    cudnn_ws_nbytes_ = std::max(bwd_filter_ws_size, bwd_data_ws_size);\n\n    VLOG(1) << \"CuDNN bwd data & filter algorithm: \" << bwd_data_algo_ << \", \"\n            << bwd_filter_algo_;\n    VLOG(1) << \"CuDNN workspace size: \" << cudnn_ws_nbytes_;\n  }\n\n  // Now, actually run the computation.\n  if (!no_bias_) {\n    auto* dbias = Output(BIAS_OR_INPUT_GRAD);\n    dbias->Resize(M);\n    CUDNN_ENFORCE(cudnnConvolutionBackwardBias(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        cudnnTypeWrapper<T_DY>::kOne(),\n        top_desc_for_bias_,\n        dY.template data<T_DY>(),\n        cudnnTypeWrapper<T_DB>::kZero(),\n        bias_desc_,\n        dbias->template mutable_data<T_DB>()));\n  }\n\n#if CUDNN_VERSION_MIN(7, 0, 0)\n  cudnn_wrapper_.with_cudnn_state(cudnn_state_, [&](CuDNNState* state) {\n    CUDNN_ENFORCE(cudnnConvolutionBackwardFilter(\n        state->cudnn_handle(),\n        cudnnTypeWrapper<T_X>::kOne(),\n        bottom_desc_,\n        X.template data<T_X>(),\n        top_desc_,\n        dY.template data<T_DY>(),\n        bwd_filter_conv_desc_,\n        bwd_filter_algo_,\n        state->workspace().get(cudnn_ws_nbytes_),\n        cudnn_ws_nbytes_,\n        cudnnTypeWrapper<T_DW>::kZero(),\n        filter_desc_,\n        dfilter->template mutable_data<T_DW>()));\n    if (OutputSize() == 3 || (no_bias_ && (OutputSize() == 2))) {\n      // Compute the gradient w.r.t. the input.\n      auto* dX = Output(no_bias_ ? BIAS_OR_INPUT_GRAD : INPUT_GRAD);\n      dX->ResizeLike(X);\n      CUDNN_ENFORCE(cudnnConvolutionBackwardData(\n          state->cudnn_handle(),\n          cudnnTypeWrapper<T_W>::kOne(),\n          filter_desc_,\n          filter.template data<T_W>(),\n          top_desc_,\n          dY.template data<T_DY>(),\n          bwd_data_conv_desc_,\n          bwd_data_algo_,\n          state->workspace().get(cudnn_ws_nbytes_),\n          cudnn_ws_nbytes_,\n          cudnnTypeWrapper<T_DX>::kZero(),\n          bottom_desc_,\n          dX->template mutable_data<T_DX>()));\n    }\n  });\n#else\n  for (int i = 0; i < group_; ++i) {\n    cudnn_wrapper_.with_cudnn_state(cudnn_state_, [&](CuDNNState* state) {\n      CUDNN_ENFORCE(cudnnConvolutionBackwardFilter(\n          state->cudnn_handle(),\n          cudnnTypeWrapper<T_X>::kOne(),\n          bottom_desc_,\n          X.template data<T_X>() + i * group_offset_X,\n          top_desc_,\n          dY.template data<T_DY>() + i * group_offset_Y,\n          bwd_filter_conv_desc_,\n          bwd_filter_algo_,\n          state->workspace().get(cudnn_ws_nbytes_),\n          cudnn_ws_nbytes_,\n          cudnnTypeWrapper<T_DW>::kZero(),\n          filter_desc_,\n          dfilter->template mutable_data<T_DW>() + i * group_offset_filter));\n      if (OutputSize() == 3 || (no_bias_ && (OutputSize() == 2))) {\n        // Compute the gradient w.r.t. the input.\n        auto* dX = Output(no_bias_ ? BIAS_OR_INPUT_GRAD : INPUT_GRAD);\n        dX->ResizeLike(X);\n        CUDNN_ENFORCE(cudnnConvolutionBackwardData(\n            state->cudnn_handle(),\n            cudnnTypeWrapper<T_W>::kOne(),\n            filter_desc_,\n            filter.template data<T_W>() + i * group_offset_filter,\n            top_desc_,\n            dY.template data<T_DY>() + i * group_offset_Y,\n            bwd_data_conv_desc_,\n            bwd_data_algo_,\n            state->workspace().get(cudnn_ws_nbytes_),\n            cudnn_ws_nbytes_,\n            cudnnTypeWrapper<T_DX>::kZero(),\n            bottom_desc_,\n            dX->template mutable_data<T_DX>() + i * group_offset_X));\n      }\n    });\n  }\n#endif\n  return true;\n}\n\n// TODO(Yangqing): a lot of the function contents are very similar. Consider\n// consolidating them.\nbool CudnnConvGradientOp::RunOnDevice() {\n  if (Input(0).IsType<float>()) {\n    return DoRunWithType<\n        float, //  X\n        float, // dY\n        float, //  W\n        float, //  b\n        float, // dX\n        float, // dW\n        float>(); // db\n  } else if (Input(0).IsType<float16>()) {\n    return DoRunWithType<\n        float16, //  X\n        float16, // dY\n        float16, //  W\n        float16, //  b\n        float16, // dX\n        float16, // dW\n        float16>(); // db\n  } else {\n    LOG(FATAL) << \"Unsupported input types\";\n  }\n  return true;\n}\n\nREGISTER_CUDNN_OPERATOR(Conv, CudnnConvOp);\nREGISTER_CUDNN_OPERATOR(ConvGradient, CudnnConvGradientOp);\n\nREGISTER_CUDNN_OPERATOR(Conv1D, CudnnConvOp);\nREGISTER_CUDNN_OPERATOR(Conv1DGradient, CudnnConvGradientOp);\n\nREGISTER_CUDNN_OPERATOR(Conv2D, CudnnConvOp);\nREGISTER_CUDNN_OPERATOR(Conv2DGradient, CudnnConvGradientOp);\n\nREGISTER_CUDNN_OPERATOR(Conv3D, CudnnConvOp);\nREGISTER_CUDNN_OPERATOR(Conv3DGradient, CudnnConvGradientOp);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/conv_op_eigen.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n\n#include \"Eigen/Core\"\n\n#if !EIGEN_VERSION_AT_LEAST(3, 3, 0)\n#error \"Caffe2 requires Eigen to be at least 3.3.0.\";\n#endif\n\n#include \"unsupported/Eigen/CXX11/Tensor\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\nclass EigenConvOp final : public ConvPoolOpBase<CPUContext> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(CPUContext);\n  EigenConvOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CPUContext>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(group_ == 1, \"Group convolution not supported yet.\");\n  }\n  ~EigenConvOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n private:\n  INPUT_TAGS(INPUT, FILTER, BIAS);\n};\n\n// The NCHW implementation: we do explicit transposes before and after, which\n// are not ideal but provides a compatible path instead of throwing the error.\ntemplate <typename T>\nbool EigenConvOp<T>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  auto* Y = Output(0);\n  const int N = X.dim32(0), C = X.dim32(1), H = X.dim32(2), W = X.dim32(3);\n  CAFFE_ENFORCE(4 == filter.ndim());\n  const int M = filter.dim32(0);\n  CAFFE_ENFORCE(filter.dim32(1) == C);\n  CAFFE_ENFORCE(filter.dim32(2) == kernel_h());\n  CAFFE_ENFORCE(filter.dim32(3) == kernel_w());\n  ConvPoolOpBase<CPUContext>::SetOutputSize(X, Y, filter.dim32(0));\n  Eigen::array<TIndex, 4> kernel_shuffles\n      { {TIndex(2), TIndex(3), TIndex(1), TIndex(0)} };\n  Eigen::array<TIndex, 4> input_shuffles\n      { {TIndex(0), TIndex(2), TIndex(3), TIndex(1)} };\n\n  Eigen::Tensor<T, 4, Eigen::RowMajor> filter_tensor =\n      Eigen::TensorMap<Eigen::Tensor<T, 4, Eigen::RowMajor>>(\n          const_cast<T*>(filter.template data<T>()),\n          M,\n          C,\n          kernel_h(),\n          kernel_w())\n          .shuffle(kernel_shuffles);\n  Eigen::Tensor<T, 4, Eigen::RowMajor> X_tensor =\n      Eigen::TensorMap<Eigen::Tensor<T, 4, Eigen::RowMajor>>(\n          const_cast<T*>(X.template data<T>()), N, C, H, W)\n          .shuffle(input_shuffles);\n\n  // For Eigen, the definition of row and col actually correspond to width\n  // and height instead of the other way round, so notice how we pass the\n  // stride, pad and dilation values.\n  typedef typename Eigen::internal::traits<\n      Eigen::Tensor<T, 4, Eigen::RowMajor>>::Index TensorIndex;\n  Eigen::array<Eigen::IndexPair<TensorIndex>, 1> contract_dims;\n  contract_dims[0] = Eigen::IndexPair<TensorIndex>(1, 0);\n\n  Eigen::DSizes<TensorIndex, 2> pre_contract_dims;\n  pre_contract_dims[1] = kernel_h() * kernel_w() * C;\n  pre_contract_dims[0] = Y->size() / M;\n\n  Eigen::DSizes<TensorIndex, 2> kernel_dims;\n  kernel_dims[0] = kernel_h() * kernel_w() * C;\n  kernel_dims[1] = M;\n\n  Eigen::array<TensorIndex, 4> bcast_dims;\n  bcast_dims[0] = N;\n  bcast_dims[1] = Y->dim32(1);\n  bcast_dims[2] = Y->dim32(2);\n  bcast_dims[3] = 1;\n\n  Eigen::Tensor<T, 4, Eigen::RowMajor> Y_tensor(\n      Y->dim32(0), Y->dim32(2), Y->dim32(3), Y->dim32(1));\n  Y_tensor = X_tensor\n                 .extract_image_patches(\n                     kernel_w(),\n                     kernel_h(),\n                     stride_w(),\n                     stride_h(),\n                     dilation_w(),\n                     dilation_h(),\n                     1,\n                     1,\n                     pad_l(),\n                     pad_r(),\n                     pad_t(),\n                     pad_b(),\n                     0)\n                 .reshape(pre_contract_dims)\n                 .contract(filter_tensor.reshape(kernel_dims), contract_dims)\n                 .reshape(Y_tensor.dimensions());\n  if (InputSize() == 3) {\n    auto& bias = Input(BIAS);\n    CAFFE_ENFORCE(1 == bias.ndim());\n    CAFFE_ENFORCE(bias.dim32(0) == M);\n    // It seems that the bias broadcast is still slower so let's do the\n    // following for now.\n    EigenArrayMap<T> Y_arr(\n        Y_tensor.data(), static_cast<TIndex>(M), Y->size() / M);\n    ConstEigenVectorArrayMap<T> bias_arr(bias.template data<T>(), M);\n    Y_arr = Y_arr.colwise() + bias_arr;\n  }\n\n  // Do a last transpose.\n  Eigen::array<TIndex, 4> output_shuffles\n      { {TIndex(0), TIndex(3), TIndex(1), TIndex(2) } };\n\n  Eigen::TensorMap<Eigen::Tensor<T, 4, Eigen::RowMajor>>(\n      Y->template mutable_data<T>(), N, M, Y->dim32(2), Y->dim32(3)) =\n      Y_tensor.shuffle(output_shuffles);\n  return true;\n}\n\ntemplate <typename T>\nbool EigenConvOp<T>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  auto* Y = Output(0);\n  const int N = X.dim32(0), H = X.dim32(1), W = X.dim32(2), C = X.dim32(3);\n  CAFFE_ENFORCE(4 == filter.ndim());\n  const int M = filter.dim32(0);\n  CAFFE_ENFORCE(filter.dim32(1) == kernel_h());\n  CAFFE_ENFORCE(filter.dim32(2) == kernel_w());\n  CAFFE_ENFORCE(filter.dim32(3) == C);\n  ConvPoolOpBase<CPUContext>::SetOutputSize(X, Y, filter.dim32(0));\n  // Eigen expects filter to be of shape (kernel_h, kernel_w, C, M) for\n  // optimization purposes, so we will create a temp one.\n  Eigen::Array<T, Eigen::Dynamic, Eigen::Dynamic> temp_filter(\n      M, kernel_h() * kernel_w() * C);\n  temp_filter = ConstEigenArrayMap<T>(\n                    filter.template data<T>(), kernel_h() * kernel_w() * C, M)\n                    .transpose();\n\n  // Create tensor maps, and call spatial convolution.\n  // TODO(jiayq): right now we const cast away the const pointer, but we will\n  // need to figure out how to properly do a const tensormap.\n  Eigen::TensorMap<Eigen::Tensor<T, 4, Eigen::RowMajor>> X_tensor(\n      const_cast<T*>(X.template data<T>()), N, H, W, C);\n  Eigen::TensorMap<Eigen::Tensor<T, 4, Eigen::RowMajor>> Y_tensor(\n      Y->template mutable_data<T>(), N, Y->dim32(1), Y->dim32(2), M);\n  Eigen::TensorMap<Eigen::Tensor<T, 4, Eigen::RowMajor>> filter_tensor(\n      const_cast<T*>(temp_filter.data()), kernel_h(), kernel_w(), C, M);\n\n  // For Eigen, the definition of row and col actually correspond to width\n  // and height instead of the other way round, so notice how we pass the\n  // stride, pad and dilation values.\n  typedef typename Eigen::internal::traits<\n      Eigen::Tensor<T, 4, Eigen::RowMajor>>::Index TensorIndex;\n  Eigen::array<Eigen::IndexPair<TensorIndex>, 1> contract_dims;\n  contract_dims[0] = Eigen::IndexPair<TensorIndex>(1, 0);\n\n  Eigen::DSizes<TensorIndex, 2> pre_contract_dims;\n  pre_contract_dims[1] = kernel_h() * kernel_w() * C;\n  pre_contract_dims[0] = Y->size() / M;\n\n  Eigen::DSizes<TensorIndex, 2> kernel_dims;\n  kernel_dims[0] = kernel_h() * kernel_w() * C;\n  kernel_dims[1] = M;\n\n  Eigen::array<TensorIndex, 4> bcast_dims;\n  bcast_dims[0] = N;\n  bcast_dims[1] = Y->dim32(1);\n  bcast_dims[2] = Y->dim32(2);\n  bcast_dims[3] = 1;\n\n  Y_tensor = X_tensor\n                 .extract_image_patches(\n                     kernel_w(),\n                     kernel_h(),\n                     stride_w(),\n                     stride_h(),\n                     dilation_w(),\n                     dilation_h(),\n                     1,\n                     1,\n                     pad_l(),\n                     pad_r(),\n                     pad_t(),\n                     pad_b(),\n                     0)\n                 .reshape(pre_contract_dims)\n                 .contract(filter_tensor.reshape(kernel_dims), contract_dims)\n                 .reshape(Y_tensor.dimensions());\n\n  if (InputSize() == 3) {\n    auto& bias = Input(BIAS);\n    CAFFE_ENFORCE(1 == bias.ndim());\n    CAFFE_ENFORCE(bias.dim32(0) == M);\n    Eigen::TensorMap<Eigen::Tensor<T, 4, Eigen::RowMajor>> bias_tensor(\n        const_cast<T*>(bias.template data<T>()), 1, 1, 1, M);\n    // It seems that the bias broadcast is still slower so let's do the\n    // following for now.\n    EigenArrayMap<T> Y_arr(\n        Y->template mutable_data<T>(), static_cast<TIndex>(M), Y->size() / M);\n    ConstEigenVectorArrayMap<T> bias_arr(bias.template data<T>(), M);\n    Y_arr = Y_arr.colwise() + bias_arr;\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR_WITH_ENGINE(Conv, EIGEN, EigenConvOp<float>);\nREGISTER_CPU_OPERATOR_WITH_ENGINE(Conv1D, EIGEN, EigenConvOp<float>);\nREGISTER_CPU_OPERATOR_WITH_ENGINE(Conv2D, EIGEN, EigenConvOp<float>);\nREGISTER_CPU_OPERATOR_WITH_ENGINE(Conv3D, EIGEN, EigenConvOp<float>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/conv_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/conv_op.h\"\n#include \"caffe2/operators/conv_op_impl.h\"\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(Conv, ConvOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(ConvGradient, ConvGradientOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(Conv1D, ConvOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(Conv1DGradient, ConvGradientOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(Conv2D, ConvOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(Conv2DGradient, ConvGradientOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(Conv3D, ConvOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(Conv3DGradient, ConvGradientOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/conv_op_impl.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// conv_op_impl.h is the templated implementation of the conv_op.h file.\n#ifndef CAFFE2_OPERATORS_CONV_OP_IMPL_H_\n#define CAFFE2_OPERATORS_CONV_OP_IMPL_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_op.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nbool ConvOp<T, Context>::RunOnDeviceWithOrderNCHW() {\n  const Tensor<Context>& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  Tensor<Context>* Y = Output(0);\n  const int N = X.dim32(0), C = X.dim32(1);\n  CAFFE_ENFORCE_EQ(X.ndim(), filter.ndim());\n  const int M = filter.dim32(0);\n  CAFFE_ENFORCE(\n      C == filter.dim32(1) * group_,\n      \"Convolution op: input channels does not match: # of input channels \",\n      C,\n      \" is not equal to kernel channels * group:\",\n      filter.dim32(1),\n      \"*\",\n      group_);\n  CAFFE_ENFORCE(\n      M % group_ == 0,\n      \"The number of output channels is not divisible by group.\");\n\n  int kernel_dims_size = 1;\n  for (int i = 0; i < kernel_.size(); ++i) {\n    CAFFE_ENFORCE(filter.dim32(i + 2) == kernel_[i]);\n    kernel_dims_size *= kernel_[i];\n  }\n\n  ConvPoolOpBase<Context>::SetOutputSize(X, Y, filter.dim32(0));\n\n  const vector<int> input_dims = GetDims(X);\n  const vector<int> output_dims = GetDims(*Y);\n  const int input_image_size = this->GetDimsSize(X);\n  const int output_image_size = this->GetDimsSize(*Y);\n\n  vector<int> img_shape;\n  img_shape.assign(X.dims().begin() + 1, X.dims().end());\n\n  vector<int> buffer_shape;\n  buffer_shape.push_back(C / group_ * kernel_dims_size);\n  buffer_shape.insert(\n      buffer_shape.end(), output_dims.begin(), output_dims.end());\n\n  if (kernel_.size() != 2) {\n    SetDeviceTensor(img_shape, &img_shape_device_);\n    SetDeviceTensor(buffer_shape, &col_buffer_shape_device_);\n  }\n\n  const int col_buffer_size =\n      (C / group_) * kernel_dims_size * output_image_size;\n\n  // The dimension of each kernel\n  const int kernel_dim = C / group_ * kernel_dims_size;\n  // The offset corresponding to a single input image, and a single output\n  // image.\n  const int input_offset = C / group_ * input_image_size;\n  const int output_offset = Y->size() / Y->dim32(0) / group_;\n  const int filter_offset = filter.size() / group_;\n\n  // The col buffer is stored in CHW order as well - kernel_dim, and the height\n  // and width.\n  const T* Xdata = X.template data<T>();\n  if (InputSize() == 3) {\n    const auto& bias = Input(BIAS);\n    CAFFE_ENFORCE(bias.ndim() == 1);\n    CAFFE_ENFORCE(bias.dim32(0) == M);\n    ConvPoolOpBase<Context>::template SetBiasMultiplier<T>(\n        output_image_size, &bias_multiplier_);\n  }\n  T* Ydata = Y->template mutable_data<T>();\n\n  auto f = [&](Tensor<Context>* col_buffer) {\n    col_buffer->Resize(buffer_shape);\n    T* col_buffer_data = col_buffer->template mutable_data<T>();\n    // Im2col, followed by gemm.\n    for (int image_id = 0; image_id < N; ++image_id) {\n      for (int group_id = 0; group_id < group_; ++group_id) {\n        if (kernel_.size() == 2) {\n          math::Im2col<T, Context, StorageOrder::NCHW>(\n              Xdata + group_id * input_offset,\n              C / group_,\n              input_dims[0],\n              input_dims[1],\n              kernel_h(),\n              kernel_w(),\n              dilation_h(),\n              dilation_w(),\n              pad_t(),\n              pad_l(),\n              pad_b(),\n              pad_r(),\n              stride_h(),\n              stride_w(),\n              col_buffer_data,\n              &context_);\n        } else {\n          math::Im2colNd<T, Context, StorageOrder::NCHW>(\n              Xdata + group_id * input_offset,\n              img_shape_device_.template data<int>(),\n              col_buffer_shape_device_.template data<int>(),\n              C * input_image_size,\n              col_buffer_size,\n              kernel_device_.template data<int>(),\n              stride_device_.template data<int>(),\n              dilation_device_.template data<int>(),\n              pads_device_.template data<int>(),\n              kernel_.size(),\n              col_buffer_data,\n              &context_);\n        }\n        // Weight term\n        math::Gemm<T, Context>(\n            CblasNoTrans,\n            CblasNoTrans,\n            M / group_,\n            output_image_size,\n            kernel_dim,\n            1,\n            filter.template data<T>() + group_id * filter_offset,\n            col_buffer_data,\n            0,\n            Ydata + group_id * output_offset,\n            &context_);\n      }\n      if (InputSize() == 3) {\n        // Bias term can be carried out outside the group definition\n        // to be efficient.\n        auto* bias_data = Input(BIAS).template data<T>();\n        math::Gemm<T, Context>(\n            CblasNoTrans,\n            CblasNoTrans,\n            M,\n            output_image_size,\n            1,\n            1,\n            bias_data,\n            bias_multiplier_.template data<T>(),\n            1,\n            Ydata,\n            &context_);\n      }\n      Xdata += input_offset * group_;\n      Ydata += output_offset * group_;\n    }\n  };\n\n  if (FLAGS_caffe2_force_shared_col_buffer || shared_buffer_) {\n    runWithSharedBuffer<Context>(ws_, f);\n  } else {\n    f(&col_buffer_);\n  }\n  return true;\n}\n\n// The implementations.\ntemplate <typename T, class Context>\nbool ConvOp<T, Context>::RunOnDeviceWithOrderNHWC() {\n  const Tensor<Context>& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  Tensor<Context>* Y = Output(0);\n  const int N = X.dim32(0), H = X.dim32(1), W = X.dim32(2), C = X.dim32(3);\n\n  CAFFE_ENFORCE_EQ(\n      kernel_.size(),\n      2,\n      \"Only 2d convolution is supported for NHWC storage type\");\n\n  CAFFE_ENFORCE(X.ndim(), filter.ndim());\n  const int M = filter.dim32(0);\n  CAFFE_ENFORCE(filter.dim32(1) == kernel_h());\n  CAFFE_ENFORCE(filter.dim32(2) == kernel_w());\n  CAFFE_ENFORCE(filter.dim32(3) == C);\n\n  ConvPoolOpBase<Context>::SetOutputSize(X, Y, filter.dim32(0));\n  // The dimension of each kernel\n  const int kernel_dim = kernel_h() * kernel_w() * C;\n  // The offset corresponding to a single input image, and a single output\n  // image.\n  const int input_offset = H * W * C;\n  const int output_offset = Y->size() / Y->dim32(0);\n  // The output image size is the spatial size of the output.\n  const int output_image_size = Y->dim32(1) * Y->dim32(2);\n  // The col buffer is stored in HWC order as well - kernel_dim, and the height\n  // and width.\n  const T* Xdata = X.template data<T>();\n  T* Ydata = Y->template mutable_data<T>();\n  // Specialized path for 1 by 1 convolution with stride 1, pad 0 - we\n  // can skip im2col.\n  if (kernel_dim == C && Y->dim32(1) == X.dim32(1) &&\n      Y->dim32(2) == X.dim32(2) && stride_h() == 1 && stride_w() == 1 &&\n      pad_t() == 0 && pad_b() == 0 && pad_l() == 0 && pad_r() == 0) {\n    math::Gemm<T, Context>(\n        CblasNoTrans,\n        CblasTrans,\n        N * H * W,\n        M,\n        C,\n        1,\n        Xdata,\n        filter.template data<T>(),\n        0,\n        Ydata,\n        &context_);\n    if (InputSize() == 3) {\n      auto& bias = Input(BIAS);\n      CAFFE_ENFORCE(1 == bias.ndim());\n      CAFFE_ENFORCE(bias.dim32(0) == M);\n      if (bias_multiplier_.size() != N * H * W) {\n        // If the helper bias multiplier is not M, reshape and fill it with one.\n        bias_multiplier_.Resize(vector<TIndex>(1, N * H * W));\n        math::Set<T, Context>(\n            N * H * W,\n            static_cast<T>(1),\n            bias_multiplier_.template mutable_data<T>(),\n            &context_);\n      }\n      math::Gemm<T, Context>(\n          CblasNoTrans,\n          CblasNoTrans,\n          N * H * W,\n          M,\n          1,\n          1,\n          bias_multiplier_.template data<T>(),\n          bias.template data<T>(),\n          1,\n          Ydata,\n          &context_);\n    }\n  } else {\n    if (InputSize() == 3) {\n      const auto& bias = Input(BIAS);\n      CAFFE_ENFORCE(1 == bias.ndim());\n      CAFFE_ENFORCE(bias.dim32(0) == M);\n      ConvPoolOpBase<Context>::template SetBiasMultiplier<T>(\n          output_image_size, &bias_multiplier_);\n    }\n    auto f = [&](Tensor<Context>* col_buffer) {\n      col_buffer->Resize(\n          vector<TIndex>{Y->dim32(1), Y->dim32(2), kernel_h(), kernel_w(), C});\n      T* col_buffer_data = col_buffer->template mutable_data<T>();\n      // Im2col, followed by gemm.\n      for (int image_id = 0; image_id < N; ++image_id) {\n        math::Im2col<T, Context, StorageOrder::NHWC>(\n            Xdata,\n            C,\n            H,\n            W,\n            kernel_h(),\n            kernel_w(),\n            dilation_h(),\n            dilation_w(),\n            pad_t(),\n            pad_l(),\n            pad_b(),\n            pad_r(),\n            stride_h(),\n            stride_w(),\n            col_buffer_data,\n            &context_);\n        // Weight term\n        math::Gemm<T, Context>(\n            CblasNoTrans,\n            CblasTrans,\n            output_image_size,\n            M,\n            kernel_dim,\n            1,\n            col_buffer_data,\n            filter.template data<T>(),\n            0,\n            Ydata,\n            &context_);\n        if (InputSize() == 3) {\n          // Bias term\n          math::Gemm<T, Context>(\n              CblasNoTrans,\n              CblasNoTrans,\n              output_image_size,\n              M,\n              1,\n              1,\n              bias_multiplier_.template data<T>(),\n              Input(BIAS).template data<T>(),\n              1,\n              Ydata,\n              &context_);\n        }\n        Xdata += input_offset;\n        Ydata += output_offset;\n      }\n    };\n    if (FLAGS_caffe2_force_shared_col_buffer || shared_buffer_) {\n      runWithSharedBuffer<Context>(ws_, f);\n    } else {\n      f(&col_buffer_);\n    }\n  }\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool ConvGradientOp<T, Context>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  auto& dY = Input(OUTPUT_GRAD);\n  auto* dfilter = Output(FILTER_GRAD);\n  const int N = X.dim32(0), C = X.dim32(1);\n\n  const vector<int> input_dims = this->GetDims(X);\n  const int input_image_size = this->GetDimsSize(X);\n\n  const vector<int> output_dims = this->GetDims(dY);\n  // The output image size is the spatial size of the output.\n  const int output_image_size = this->GetDimsSize(dY);\n\n  ConvPoolOpBase<Context>::ComputePads(input_dims);\n  CAFFE_ENFORCE_EQ(X.ndim(), filter.ndim());\n  const int M = filter.dim32(0);\n  CAFFE_ENFORCE(filter.dim32(1) * group_ == C);\n\n  int kernel_dims_size = 1;\n  for (int i = 0; i < kernel_.size(); ++i) {\n    CAFFE_ENFORCE(filter.dim32(i + 2) == kernel_[i]);\n    kernel_dims_size *= kernel_[i];\n  }\n\n  CAFFE_ENFORCE(M % group_ == 0);\n  dfilter->ResizeLike(filter);\n  // The dimension of each kernel\n  const int kernel_dim = C / group_ * kernel_dims_size;\n  // The offset corresponding to a single input image, and a single output\n  // image.\n  const int input_offset = C / group_ * input_image_size;\n  const int output_offset = dY.size() / dY.dim32(0) / group_;\n  const int filter_offset = filter.size() / group_;\n  // The col buffer is stored in CHW order as well - kernel_dim, and the height\n  // and width.\n\n  vector<int> img_shape;\n  img_shape.assign(X.dims().begin() + 1, X.dims().end());\n  vector<int> col_buffer_shape;\n  col_buffer_shape.push_back(C / group_ * kernel_dims_size);\n  col_buffer_shape.insert(\n      col_buffer_shape.end(), output_dims.begin(), output_dims.end());\n  col_buffer_.Resize(col_buffer_shape);\n\n  if (kernel_.size() != 2) {\n    SetDeviceTensor(img_shape, &img_shape_device_);\n    SetDeviceTensor(col_buffer_shape, &col_buffer_shape_device_);\n  }\n\n  const int col_buffer_size =\n      (C / group_) * kernel_dims_size * output_image_size;\n  const T* Xdata = X.template data<T>();\n  const T* filter_data = filter.template data<T>();\n  const T* dYdata = dY.template data<T>();\n  T* col_buffer_data = col_buffer_.template mutable_data<T>();\n  T* dfilter_data = dfilter->template mutable_data<T>();\n\n  // Pre-setting the gradients to zero.\n  math::Set<T, Context>(dfilter->size(), 0, dfilter_data, &context_);\n\n  T* dbias_data = nullptr;\n  if (!no_bias_) {\n    auto* dbias = Output(BIAS_OR_INPUT_GRAD);\n    dbias->Resize(M);\n    if (bias_multiplier_.size() != output_image_size) {\n      // If the helper bias multiplier is not M, reshape and fill it with one.\n      bias_multiplier_.Resize(vector<TIndex>(1, output_image_size));\n      math::Set<T, Context>(\n          output_image_size,\n          static_cast<T>(1),\n          bias_multiplier_.template mutable_data<T>(),\n          &context_);\n    }\n    dbias_data = dbias->template mutable_data<T>();\n    math::Set<T, Context>(dbias->size(), 0, dbias_data, &context_);\n  }\n\n  for (int image_id = 0; image_id < N; ++image_id) {\n    for (int group_id = 0; group_id < group_; ++group_id) {\n      // When we compute the gradient with respect to the filters, we need to do\n      // im2col to allow gemm-type computation.\n      if (kernel_.size() == 2) {\n        math::Im2col<T, Context, StorageOrder::NCHW>(\n            Xdata + group_id * input_offset,\n            C / group_,\n            input_dims[0],\n            input_dims[1],\n            kernel_h(),\n            kernel_w(),\n            dilation_h(),\n            dilation_w(),\n            pad_t(),\n            pad_l(),\n            pad_b(),\n            pad_r(),\n            stride_h(),\n            stride_w(),\n            col_buffer_data,\n            &context_);\n      } else {\n        math::Im2colNd<T, Context, StorageOrder::NCHW>(\n            Xdata + group_id * input_offset,\n            img_shape_device_.template data<int>(),\n            col_buffer_shape_device_.template data<int>(),\n            C * input_image_size,\n            col_buffer_size,\n            kernel_device_.template data<int>(),\n            stride_device_.template data<int>(),\n            dilation_device_.template data<int>(),\n            pads_device_.template data<int>(),\n            kernel_.size(),\n            col_buffer_data,\n            &context_);\n      }\n      // Gradient with respect to filter.\n      math::Gemm<T, Context>(\n          CblasNoTrans,\n          CblasTrans,\n          M / group_,\n          kernel_dim,\n          output_image_size,\n          1,\n          dYdata + group_id * output_offset,\n          col_buffer_data,\n          1,\n          dfilter_data + group_id * filter_offset,\n          &context_);\n    }\n    if (!no_bias_) {\n      // Gradient with respect to bias can be computed independent from group.\n      math::Gemv<T, Context>(\n          CblasNoTrans,\n          M,\n          output_image_size,\n          1,\n          dYdata,\n          bias_multiplier_.template data<T>(),\n          1,\n          dbias_data,\n          &context_);\n    }\n    Xdata += input_offset * group_;\n    dYdata += output_offset * group_;\n  }\n  if (OutputSize() == 3 || (no_bias_ && (OutputSize() == 2))) {\n    // Compute the gradient w.r.t. the input.\n    auto* dX = Output(no_bias_ ? BIAS_OR_INPUT_GRAD : INPUT_GRAD);\n    dX->ResizeLike(X);\n    T* dXdata = dX->template mutable_data<T>();\n    dYdata = dY.template data<T>();\n    for (int image_id = 0; image_id < N; ++image_id) {\n      for (int group_id = 0; group_id < group_; ++group_id) {\n        // Compute gradient into col_buffer.\n        math::Gemm<T, Context>(\n            CblasTrans,\n            CblasNoTrans,\n            kernel_dim,\n            output_image_size,\n            M / group_,\n            1,\n            filter_data + group_id * filter_offset,\n            dYdata,\n            0,\n            col_buffer_data,\n            &context_);\n        if (kernel_.size() == 2) {\n          math::Col2im<T, Context, StorageOrder::NCHW>(\n              col_buffer_data,\n              C / group_,\n              input_dims[0],\n              input_dims[1],\n              kernel_h(),\n              kernel_w(),\n              dilation_h(),\n              dilation_w(),\n              pad_t(),\n              pad_l(),\n              pad_b(),\n              pad_r(),\n              stride_h(),\n              stride_w(),\n              dXdata,\n              &context_);\n        } else {\n          math::Col2imNd<T, Context, StorageOrder::NCHW>(\n              col_buffer_data,\n              img_shape_device_.template data<int>(),\n              col_buffer_shape_device_.template data<int>(),\n              C * input_image_size,\n              col_buffer_size,\n              kernel_device_.template data<int>(),\n              stride_device_.template data<int>(),\n              dilation_device_.template data<int>(),\n              pads_device_.template data<int>(),\n              kernel_.size(),\n              dXdata,\n              &context_);\n        }\n        dXdata += input_offset;\n        dYdata += output_offset;\n      }\n    }\n  }\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool ConvGradientOp<T, Context>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  auto& dY = Input(OUTPUT_GRAD);\n  auto* dfilter = Output(FILTER_GRAD);\n\n  const int N = X.dim32(0), H = X.dim32(1), W = X.dim32(2), C = X.dim32(3);\n  ConvPoolOpBase<Context>::ComputePads({H, W});\n  CAFFE_ENFORCE(4 == filter.ndim());\n  const int M = filter.dim32(0);\n  CAFFE_ENFORCE(filter.dim32(1) == kernel_h());\n  CAFFE_ENFORCE(filter.dim32(2) == kernel_w());\n  CAFFE_ENFORCE(filter.dim32(3) == C);\n  dfilter->ResizeLike(filter);\n\n  // The dimension of each kernel\n  const int kernel_dim = kernel_h() * kernel_w() * C;\n  // The offset corresponding to a single input image, and a single output\n  // image.\n  const int input_offset = H * W * C;\n  const int output_offset = dY.size() / dY.dim32(0);\n  // The output image size is the spatial size of the output.\n  const int output_image_size = dY.dim32(1) * dY.dim32(2);\n  // The col buffer is stored in CHW order as well - kernel_dim, and the height\n  // and width.\n  col_buffer_.Resize(output_image_size, kernel_dim);\n\n  const T* Xdata = X.template data<T>();\n  const T* const filter_data = filter.template data<T>();\n  const T* const dYdata = dY.template data<T>();\n  T* col_buffer_data = col_buffer_.template mutable_data<T>();\n  T* dfilter_data = dfilter->template mutable_data<T>();\n\n  // Pre-setting the gradients to zero.\n  math::Set<T, Context>(dfilter->size(), 0, dfilter_data, &context_);\n\n  T* dbias_data = nullptr;\n  if (!no_bias_) {\n    auto* dbias = Output(BIAS_OR_INPUT_GRAD);\n    dbias->Resize(M);\n    dbias_data = dbias->template mutable_data<T>();\n    math::Set<T, Context>(dbias->size(), 0, dbias_data, &context_);\n    if (bias_multiplier_.size() != output_image_size) {\n      // If the helper bias multiplier is not M, reshape and fill it with one.\n      bias_multiplier_.Resize(vector<TIndex>(1, output_image_size));\n      math::Set<T, Context>(\n          output_image_size,\n          static_cast<T>(1),\n          bias_multiplier_.template mutable_data<T>(),\n          &context_);\n    }\n  }\n\n  for (int image_id = 0; image_id < N; ++image_id) {\n    // When we compute the gradient with respect to the filters, we need to do\n    // im2col to allow gemm-type computation.\n    math::Im2col<T, Context, StorageOrder::NHWC>(\n        Xdata,\n        C,\n        H,\n        W,\n        kernel_h(),\n        kernel_w(),\n        dilation_h(),\n        dilation_w(),\n        pad_t(),\n        pad_l(),\n        pad_b(),\n        pad_r(),\n        stride_h(),\n        stride_w(),\n        col_buffer_data,\n        &context_);\n    // Gradient with respect to filter.\n    math::Gemm<T, Context>(\n        CblasTrans,\n        CblasNoTrans,\n        M,\n        kernel_dim,\n        output_image_size,\n        1,\n        dYdata + output_offset * image_id,\n        col_buffer_data,\n        1,\n        dfilter_data,\n        &context_);\n    if (!no_bias_) {\n      // Gradient with respect to bias\n      math::Gemv<T, Context>(\n          CblasTrans,\n          output_image_size,\n          M,\n          1,\n          dYdata + output_offset * image_id,\n          bias_multiplier_.template data<T>(),\n          1,\n          dbias_data,\n          &context_);\n    }\n    Xdata += input_offset;\n  }\n\n  if (OutputSize() == 3 || (no_bias_ && (OutputSize() == 2))) {\n    // Compute the gradient w.r.t. the input.\n    auto* dX = Output(no_bias_ ? BIAS_OR_INPUT_GRAD : INPUT_GRAD);\n    dX->ResizeLike(X);\n    T* dXdata = dX->template mutable_data<T>();\n    for (int image_id = 0; image_id < N; ++image_id) {\n      // Compute gradient into col_buffer.\n      math::Gemm<T, Context>(\n          CblasNoTrans,\n          CblasNoTrans,\n          output_image_size,\n          kernel_dim,\n          M,\n          1,\n          dYdata + output_offset * image_id,\n          filter_data,\n          0,\n          col_buffer_data,\n          &context_);\n      math::Col2im<T, Context, StorageOrder::NHWC>(\n          col_buffer_data,\n          C,\n          H,\n          W,\n          kernel_h(),\n          kernel_w(),\n          dilation_h(),\n          dilation_w(),\n          pad_t(),\n          pad_l(),\n          pad_b(),\n          pad_r(),\n          stride_h(),\n          stride_w(),\n          dXdata,\n          &context_);\n      dXdata += input_offset;\n    }\n  }\n  return true;\n}\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CONV_OP_IMPL_H_\n"
  },
  {
    "path": "caffe2/operators/conv_op_shared.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"conv_op_shared.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/core/workspace.h\"\n\nCAFFE2_DEFINE_bool(\n    caffe2_force_shared_col_buffer,\n    false,\n    \"Always use the shared col buffer\");\n\nnamespace caffe2 {\n\ntemplate <>\nvoid createSharedBuffer<CPUContext>(Workspace* ws) {\n  auto* mutexPtr = ws->CreateBlob(\"__CAFFE2_SHARED_CONV_BUFFER_CPU_MUTEX__\")\n                       ->GetMutable<std::unique_ptr<std::mutex>>();\n  mutexPtr->reset(new std::mutex());\n  ws->CreateBlob(\"__CAFFE2_SHARED_CONV_BUFFER_CPU__\");\n}\n\ntemplate <>\nvoid runWithSharedBuffer(\n    Workspace* ws,\n    std::function<void(Tensor<CPUContext>* buffer)> f) {\n  auto* mutexBlob = ws->GetBlob(\"__CAFFE2_SHARED_CONV_BUFFER_CPU_MUTEX__\");\n  CAFFE_ENFORCE(mutexBlob, \"Must call createSharedBuffer() first\");\n\n  auto* mutexPtr = mutexBlob->GetMutable<std::unique_ptr<std::mutex>>();\n  std::lock_guard<std::mutex> g(**mutexPtr);\n  auto* buffer =\n      ws->GetBlob(\"__CAFFE2_SHARED_CONV_BUFFER_CPU__\")->GetMutable<TensorCPU>();\n  f(buffer);\n}\n}\n"
  },
  {
    "path": "caffe2/operators/conv_op_shared.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CONV_OP_SHARED_H_\n#define CAFFE2_OPERATORS_CONV_OP_SHARED_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/workspace.h\"\n\nnamespace caffe2 {\n\n/**\n * Creates a mutex and shared buffer in the workspace.\n * Not thread-safe, must be called from the constructor.\n */\ntemplate <typename Context>\nvoid createSharedBuffer(Workspace* ws);\n\n/**\n * Thread-safe, can be invoked from RunOnDevice() to serialize\n * access to shared buffer.\n */\ntemplate <typename Context>\nvoid runWithSharedBuffer(\n    Workspace* ws,\n    std::function<void(Tensor<Context>* buffer)> f);\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CONV_OP_SHARED_H_\n"
  },
  {
    "path": "caffe2/operators/conv_op_shared_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"conv_op_shared.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nvoid createSharedBuffer<CUDAContext>(Workspace* ws) {\n  auto* mutexPtr = ws->CreateBlob(\"__CAFFE2_SHARED_CONV_BUFFER_CUDA_MUTEX__\")\n                       ->GetMutable<std::unique_ptr<std::mutex>>();\n  mutexPtr->reset(new std::mutex());\n  ws->CreateBlob(\"__CAFFE2_SHARED_CONV_BUFFER_CUDA__\");\n}\n\ntemplate <>\nvoid runWithSharedBuffer(\n    Workspace* ws,\n    std::function<void(Tensor<CUDAContext>* buffer)> f) {\n  auto* mutexBlob = ws->GetBlob(\"__CAFFE2_SHARED_CONV_BUFFER_CUDA_MUTEX__\");\n  CAFFE_ENFORCE(mutexBlob, \"Must call createSharedBuffer() first\");\n\n  auto* mutexPtr = mutexBlob->GetMutable<std::unique_ptr<std::mutex>>();\n  std::lock_guard<std::mutex> g(**mutexPtr);\n  auto* buffer = ws->GetBlob(\"__CAFFE2_SHARED_CONV_BUFFER_CUDA__\")\n                     ->GetMutable<TensorCUDA>();\n  f(buffer);\n}\n}\n"
  },
  {
    "path": "caffe2/operators/conv_pool_op_base.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CONV_POOL_OP_BASE_H_\n#define CAFFE2_OPERATORS_CONV_POOL_OP_BASE_H_\n\n#include <vector>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/proto/caffe2_legacy.pb.h\"\n#include \"caffe2/utils/math.h\"\n\n// This macro is here just to allow us to experiment with padding values that\n// determines, when we have an odd number of pads, which side gets the one\n// additional pad value, the head side, or the tail side. Setting it to false\n// will enable the TensorFlow behavior, and setting it to true will enable\n// a behavior more consistent with Caffe and CuDNN.\n// This only affects the case when you set legacy pad to VALID or SAME. The\n// behavior inherits from the early designs of Google's CNN implementation,\n// where padding values are implicitly calculated instead of explicitly\n// specified. This is still the case with TensorFlow. Many frameworks have\n// followed a slightly different approach of explicitly giving padding values,\n// in which case the value of this constant value does not matter.\nconst bool CAFFE2_PAD_HEAD_MORE = false;\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass ConvPoolOpBase : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ConvPoolOpBase(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        legacy_pad_(\n            static_cast<LegacyPadding>(OperatorBase::GetSingleArgument<int>(\n                \"legacy_pad\",\n                LegacyPadding::NOTSET))),\n        global_pooling_(\n            OperatorBase::GetSingleArgument<int>(\"global_pooling\", 0)),\n        kernel_(OperatorBase::GetRepeatedArgument<int>(\"kernels\")),\n        dilation_(OperatorBase::GetRepeatedArgument<int>(\"dilations\")),\n        stride_(OperatorBase::GetRepeatedArgument<int>(\"strides\")),\n        pads_(OperatorBase::GetRepeatedArgument<int>(\"pads\")),\n        float16_compute_(\n            OperatorBase::GetSingleArgument<bool>(\"float16_compute\", false)),\n        group_(OperatorBase::GetSingleArgument<int>(\"group\", 1)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))),\n        shared_buffer_(\n            OperatorBase::GetSingleArgument<int>(\"shared_buffer\", 0)),\n        ws_(ws) {\n    // For the padding, they should either be the legacy padding strategy\n    // (VALID or SAME), or an explicit, non-negative value.\n    if (legacy_pad_ == LegacyPadding::VALID ||\n        legacy_pad_ == LegacyPadding::SAME) {\n      CAFFE_ENFORCE(\n          !OperatorBase::HasArgument(\"pads\"),\n          \"If you use legacy padding VALID or SAME, you should not specify \"\n          \"any specific padding values.\");\n    }\n\n    // Get old arguments values.\n    if (OperatorBase::HasArgument(\"kernel\")) {\n      kernel_.resize(2, OperatorBase::GetSingleArgument<int>(\"kernel\", 0));\n    } else if (\n        OperatorBase::HasArgument(\"kernel_h\") &&\n        OperatorBase::HasArgument(\"kernel_w\")) {\n      kernel_.push_back(OperatorBase::GetSingleArgument<int>(\"kernel_h\", 0));\n      kernel_.push_back(OperatorBase::GetSingleArgument<int>(\"kernel_w\", 0));\n    }\n\n    if (OperatorBase::HasArgument(\"stride\")) {\n      stride_.resize(2, OperatorBase::GetSingleArgument<int>(\"stride\", 0));\n    } else if (\n        OperatorBase::HasArgument(\"stride_h\") &&\n        OperatorBase::HasArgument(\"stride_w\")) {\n      stride_.push_back(OperatorBase::GetSingleArgument<int>(\"stride_h\", 0));\n      stride_.push_back(OperatorBase::GetSingleArgument<int>(\"stride_w\", 0));\n    }\n\n    if (OperatorBase::HasArgument(\"dilation\")) {\n      dilation_.resize(2, OperatorBase::GetSingleArgument<int>(\"dilation\", 0));\n    } else if (\n        OperatorBase::HasArgument(\"dilation_h\") &&\n        OperatorBase::HasArgument(\"dilation_w\")) {\n      dilation_.push_back(\n          OperatorBase::GetSingleArgument<int>(\"dilation_h\", 0));\n      dilation_.push_back(\n          OperatorBase::GetSingleArgument<int>(\"dilation_w\", 0));\n    }\n\n    if (OperatorBase::HasArgument(\"pad\")) {\n      CAFFE_ENFORCE(\n          legacy_pad_ != LegacyPadding::VALID &&\n              legacy_pad_ != LegacyPadding::SAME,\n          \"If you use legacy padding VALID or SAME, you should not specify \"\n          \"any specific padding values.\");\n      pads_.resize(4, OperatorBase::GetSingleArgument<int>(\"pad\", 0));\n    } else if (\n        OperatorBase::HasArgument(\"pad_t\") &&\n        OperatorBase::HasArgument(\"pad_l\") &&\n        OperatorBase::HasArgument(\"pad_b\") &&\n        OperatorBase::HasArgument(\"pad_r\")) {\n      CAFFE_ENFORCE(\n          legacy_pad_ != LegacyPadding::VALID &&\n              legacy_pad_ != LegacyPadding::SAME,\n          \"If you use legacy padding VALID or SAME, you should not specify \"\n          \"any specific padding values.\");\n      pads_.push_back(OperatorBase::GetSingleArgument<int>(\"pad_t\", 0));\n      pads_.push_back(OperatorBase::GetSingleArgument<int>(\"pad_l\", 0));\n      pads_.push_back(OperatorBase::GetSingleArgument<int>(\"pad_b\", 0));\n      pads_.push_back(OperatorBase::GetSingleArgument<int>(\"pad_r\", 0));\n    }\n\n    // Fill default values.\n    if (kernel_.size() == 0) {\n      kernel_.assign({0, 0});\n    }\n\n    if (stride_.size() == 0) {\n      stride_.resize(kernel_.size(), 1);\n    }\n\n    if (pads_.size() == 0) {\n      pads_.resize(kernel_.size() * 2, 0);\n    }\n\n    if (dilation_.size() == 0) {\n      dilation_.resize(kernel_.size(), 1);\n    }\n\n    CAFFE_ENFORCE_EQ(stride_.size(), kernel_.size());\n    CAFFE_ENFORCE_EQ(dilation_.size(), kernel_.size());\n\n    if (legacy_pad_ != LegacyPadding::VALID &&\n        legacy_pad_ != LegacyPadding::SAME) {\n      CAFFE_ENFORCE_EQ(pads_.size(), 2 * kernel_.size());\n    }\n\n    if (global_pooling_) {\n      for (int dim = 0; dim < kernel_.size(); ++dim) {\n        CAFFE_ENFORCE(\n            pads_[2 * dim] == 0 && pads_[2 * dim + 1] == 0 &&\n                dilation_[dim] == 1 && stride_[dim] == 1,\n            \"If global_pooling is set pad, dilation and stride shouldn't be set.\");\n      }\n    }\n\n    AllocateAndCopy(kernel_, kernel_device_);\n    AllocateAndCopy(stride_, stride_device_);\n    AllocateAndCopy(dilation_, dilation_device_);\n    AllocateAndCopy(pads_, pads_device_);\n\n    // Check kernel only if we are doing conv or pooling. The reason is that a\n    // few other ops, like PadImage, are also using this base class. We really\n    // need to clean this up.\n    if (operator_def.name().find(\"Conv\") == 0 ||\n        operator_def.name().find(\"Pool\") != std::string::npos) {\n      for (int dim = 0; dim < kernel_.size(); ++dim) {\n        CAFFE_ENFORCE_GE(pads_[dim], 0);\n        CAFFE_ENFORCE_GE(pads_[kernel_.size() + dim], 0);\n        CAFFE_ENFORCE(\n            kernel_[dim],\n            \"If you are doing convolution or pooling, you will need to set \"\n            \"explicitly the kernel size.\");\n      }\n    }\n\n    for (int dim = 0; dim < kernel_.size(); ++dim) {\n      CAFFE_ENFORCE_GE(kernel_[dim], 0);\n      CAFFE_ENFORCE_GE(dilation_[dim], 0);\n      CAFFE_ENFORCE_GE(stride_[dim], 0);\n    }\n\n    if (group_ != 1) {\n      for (int dim = 0; dim < kernel_.size(); ++dim) {\n        CAFFE_ENFORCE_EQ(\n            dilation_[dim],\n            1,\n            \"When group is used, dilation should not be set at the same time.\");\n      }\n    }\n  }\n\n  // Returns the input image dimensions for the current storage order type.\n  vector<int> GetDims(const Tensor<Context>& input) {\n    vector<int> dims;\n    switch (order_) {\n      case StorageOrder::NCHW:\n        dims.assign(input.dims().begin() + 2, input.dims().end());\n        break;\n      case StorageOrder::NHWC:\n        dims.assign(input.dims().begin() + 1, input.dims().end() - 1);\n        break;\n      default:\n        CAFFE_THROW(\"Unknown storage order : \", order_);\n    }\n    return dims;\n  }\n\n  // Returns the size of the input image for the current storage type.\n  int GetDimsSize(const Tensor<Context>& input) {\n    int size = 0;\n    switch (order_) {\n      case StorageOrder::NCHW:\n        size = std::accumulate(\n            input.dims().begin() + 2,\n            input.dims().end(),\n            1,\n            std::multiplies<int>());\n        break;\n      case StorageOrder::NHWC:\n        size = std::accumulate(\n            input.dims().begin() + 1,\n            input.dims().end() - 1,\n            1,\n            std::multiplies<int>());\n        break;\n      default:\n        CAFFE_THROW(\"Unknown storage order : \", order_);\n    }\n    return size;\n  }\n\n  // Sets the output size. The output channel is manually provided since\n  // it may not be identical to the input channels.\n  // This function can be used in the forward functions to obtain the output\n  // sizes.\n  // Note(jiayq): the templatization of this function is mainly to help\n  // implementations that do not use first-class Tensor objects, such as the\n  // MKL operator. One can still call this function with dummy\n  // Tensor<CPUContext> objects in order to obtain the sizes.\n  template <typename AlternativeContext>\n  void SetOutputSize(\n      const Tensor<AlternativeContext>& input,\n      Tensor<AlternativeContext>* output,\n      int output_channel) {\n    CAFFE_ENFORCE(input.size() > 0);\n    vector<int> output_dims;\n    int N = input.dim32(0);\n    bool channel_first;\n    InferOutputSize(\n        input.dims(),\n        output_channel,\n        order_,\n        global_pooling_,\n        legacy_pad_,\n        N,\n        kernel_,\n        output_dims,\n        dilation_,\n        stride_,\n        pads_,\n        channel_first);\n\n    if (channel_first) {\n      output_dims.insert(output_dims.begin(), {N, output_channel});\n    } else {\n      output_dims.insert(output_dims.begin(), N);\n      output_dims.push_back(output_channel);\n    }\n    output->Resize(output_dims);\n  }\n\n  // Helper function that is also called from OperatorSchema. Modified\n  // kernel parameters and output output_dims and channel_first.\n  static inline void InferOutputSize(\n      vector<TIndex> input_dims,\n      int /*output_channel*/,\n      StorageOrder order,\n      bool global_pooling,\n      LegacyPadding legacy_pad,\n      int /*N*/,\n      vector<int>& kernel,\n      vector<int>& output_dims,\n      const vector<int>& dilation,\n      const vector<int>& stride,\n      vector<int>& pads,\n      bool& channel_first) {\n    channel_first = false; // initialized to suppress compiler warning.\n    vector<TIndex> dims;\n    switch (order) {\n      case StorageOrder::NHWC:\n        channel_first = false;\n        dims.assign(input_dims.begin() + 1, input_dims.end() - 1);\n        break;\n      case StorageOrder::NCHW:\n        // Old Caffe order.\n        channel_first = true;\n        dims.assign(input_dims.begin() + 2, input_dims.end());\n        break;\n      default:\n        CAFFE_THROW(\"Unknown Storage order: \", order);\n    }\n\n    if (global_pooling) {\n      kernel.assign(dims.begin(), dims.end());\n      output_dims.assign(dims.size(), 1);\n    } else {\n      for (int dim = 0; dim < dims.size(); ++dim) {\n        int dim_size = 0;\n        ComputeSizeAndPad(\n            dims[dim],\n            stride[dim],\n            kernel[dim],\n            dilation[dim],\n            legacy_pad,\n            &pads[dim],\n            &pads[dims.size() + dim],\n            &dim_size);\n        output_dims.push_back(dim_size);\n      }\n    }\n  }\n\n  // ComputePads could be used in backward functions to figure out the padding\n  // values for the given input.\n  void ComputePads(const vector<int>& dims) {\n    if (global_pooling_) {\n      kernel_ = dims;\n    } else if (legacy_pad_ != LegacyPadding::NOTSET) {\n      int output_unused;\n      for (int dim = 0; dim < dims.size(); ++dim) {\n        ComputeSizeAndPad(\n            dims[dim],\n            stride_[dim],\n            kernel_[dim],\n            dilation_[dim],\n            legacy_pad_,\n            &pads_[dim],\n            &pads_[dims.size() + dim],\n            &output_unused);\n      }\n    }\n  }\n\n  void SetDeviceTensor(const std::vector<int>& data, Tensor<Context>* tensor) {\n    bool reset_tensor_device_ = false;\n\n    if (tensor->size() != data.size()) {\n      tensor->Resize(data.size());\n      reset_tensor_device_ = true;\n    } else {\n      const int* tensor_data = tensor->template data<int>();\n      for (int d_i = 0; d_i < data.size(); ++d_i) {\n        if (tensor_data[d_i] != data[d_i]) {\n          reset_tensor_device_ = true;\n          break;\n        }\n      }\n    }\n\n    if (reset_tensor_device_) {\n      context_.template Copy<int, CPUContext, Context>(\n          data.size(), data.data(), tensor->template mutable_data<int>());\n    }\n  }\n\n  template <typename T>\n  void SetBiasMultiplier(const int size, Tensor<Context>* bias_multiplier_) {\n    if (bias_multiplier_->size() != size) {\n      // If the helper bias multiplier is not image size, reshape and fill it\n      // with one.\n      bias_multiplier_->Resize(std::vector<TIndex>{size});\n      math::Set<T, Context>(\n          size,\n          static_cast<T>(1),\n          bias_multiplier_->template mutable_data<T>(),\n          &context_);\n    }\n  }\n\n  bool RunOnDevice() override {\n    if (!global_pooling_) {\n      for (int dim = 0; dim < kernel_.size(); ++dim) {\n        CAFFE_ENFORCE_GT(kernel_[dim], 0);\n      }\n    }\n    switch (order_) {\n      case StorageOrder::NHWC:\n        // VLOG(2) << \"Running NHWC\";\n        return RunOnDeviceWithOrderNHWC();\n      case StorageOrder::NCHW:\n        // VLOG(2) << \"Running NCHW\";\n        return RunOnDeviceWithOrderNCHW();\n      default:\n        CAFFE_THROW(\"Unknown Storage order: \", order_);\n    }\n  }\n\n  // The actual function that does the computation, if the different\n  // storage order leads to different implementations.\n  virtual bool RunOnDeviceWithOrderNHWC() {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n  virtual bool RunOnDeviceWithOrderNCHW() {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n  static struct OpSchema::Cost CostInferenceForConv(\n      const OperatorDef& def,\n      const vector<TensorShape>& inputs) {\n    struct OpSchema::Cost c;\n    const TensorShape X = inputs[0];\n    const TensorShape W = inputs[1];\n    const TensorShape Y = TensorInferenceForConv(def, inputs)[0];\n    ArgumentHelper helper(def);\n    const auto order =\n        StringToStorageOrder(helper.GetSingleArgument<string>(\"order\", \"NCHW\"));\n\n    unsigned long long N;\n    unsigned long long Y_t = 1;\n    unsigned long long Y_h;\n    unsigned long long Y_w;\n    unsigned long long kernel_t = 1;\n    unsigned long long kernel_h;\n    unsigned long long kernel_w;\n    unsigned long long in_channels;\n    unsigned long long out_channels;\n\n    N = X.dims(0);\n    if (X.dims_size() == 5) {\n      // 3D convolution\n      CAFFE_ENFORCE_EQ(order, StorageOrder::NCHW, \"Conv3D only supports NCHW\");\n      Y_t = Y.dims(2);\n      Y_h = Y.dims(3);\n      Y_w = Y.dims(4);\n      kernel_t = W.dims(2);\n      kernel_h = W.dims(3);\n      kernel_w = W.dims(4);\n      in_channels = W.dims(1);\n      out_channels = W.dims(0);\n    } else {\n      // 2D convolution\n      CAFFE_ENFORCE_EQ(X.dims_size(), 4, \"Conv2D should have 4D input tensor\");\n      if (order == StorageOrder::NHWC) {\n        Y_h = Y.dims(1);\n        Y_w = Y.dims(2);\n        kernel_h = W.dims(1);\n        kernel_w = W.dims(2);\n        in_channels = W.dims(3);\n        out_channels = W.dims(0);\n      } else {\n        Y_h = Y.dims(2);\n        Y_w = Y.dims(3);\n        kernel_h = W.dims(2);\n        kernel_w = W.dims(3);\n        in_channels = W.dims(1);\n        out_channels = W.dims(0);\n      }\n    }\n    // grouping is NOT properly handled yet\n    c.flops = N * Y_t * Y_h * Y_w * kernel_t * kernel_w * kernel_h *\n        in_channels * out_channels * 2;\n    c.bytes_moved = N * out_channels * Y_t * Y_h * Y_w * sizeof(float);\n    c.params_bytes = out_channels * in_channels * kernel_t * kernel_h *\n        kernel_w * sizeof(float);\n    return c;\n  }\n\n  static vector<TensorShape> TensorInferenceForSchema(\n      const OperatorDef& def,\n      const vector<TensorShape>& in,\n      int output_channel) {\n    ArgumentHelper helper(def);\n    CAFFE_ENFORCE_GT(in.size(), 0);\n    CAFFE_ENFORCE_GT(in[0].dims_size(), 0);\n    int N = in[0].dims(0);\n    bool channel_first;\n    vector<int> pads = helper.GetRepeatedArgument<int>(\"pads\");\n    vector<int> kernel = helper.GetRepeatedArgument<int>(\"kernels\");\n    vector<int> strides = helper.GetRepeatedArgument<int>(\"strides\");\n    vector<int> dilations = helper.GetRepeatedArgument<int>(\"dilation\");\n    if (helper.HasArgument(\"pad\")) {\n      pads.resize(4, helper.GetSingleArgument<int>(\"pad\", 0));\n    } else if (\n        helper.HasArgument(\"pad_t\") && helper.HasArgument(\"pad_l\") &&\n        helper.HasArgument(\"pad_b\") && helper.HasArgument(\"pad_r\")) {\n      pads.push_back(helper.GetSingleArgument<int>(\"pad_t\", 0));\n      pads.push_back(helper.GetSingleArgument<int>(\"pad_l\", 0));\n      pads.push_back(helper.GetSingleArgument<int>(\"pad_b\", 0));\n      pads.push_back(helper.GetSingleArgument<int>(\"pad_r\", 0));\n    }\n\n    if (helper.HasArgument(\"kernel\")) {\n      kernel.resize(2, helper.GetSingleArgument<int>(\"kernel\", 1));\n    } else if (\n        helper.HasArgument(\"kernel_h\") && helper.HasArgument(\"kernel_w\")) {\n      kernel.push_back(helper.GetSingleArgument<int>(\"kernel_h\", 1));\n      kernel.push_back(helper.GetSingleArgument<int>(\"kernel_w\", 1));\n    }\n\n    if (helper.HasArgument(\"stride\")) {\n      strides.resize(2, helper.GetSingleArgument<int>(\"stride\", 1));\n    } else if (\n        helper.HasArgument(\"stride_h\") && helper.HasArgument(\"stride_w\")) {\n      strides.push_back(helper.GetSingleArgument<int>(\"stride_h\", 1));\n      strides.push_back(helper.GetSingleArgument<int>(\"stride_w\", 1));\n    }\n\n    if (helper.HasArgument(\"dilation\")) {\n      strides.resize(2, helper.GetSingleArgument<int>(\"dilation\", 1));\n    } else if (\n        helper.HasArgument(\"dilation_h\") && helper.HasArgument(\"dilation_w\")) {\n      strides.push_back(helper.GetSingleArgument<int>(\"dilation_h\", 1));\n      strides.push_back(helper.GetSingleArgument<int>(\"dilation_w\", 1));\n    }\n\n    auto check_and_set_default_value =\n        [](vector<int>& vec, int size, int value) {\n          if (vec.size() == 0) {\n            vec.resize(size, value);\n          }\n        };\n\n    check_and_set_default_value(kernel, 2, 1);\n    check_and_set_default_value(strides, kernel.size(), 1);\n    check_and_set_default_value(pads, kernel.size() * 2, 0);\n    check_and_set_default_value(dilations, kernel.size(), 1);\n\n    vector<int> output_dims;\n    ConvPoolOpBase<CPUContext>::InferOutputSize(\n        GetDimsVector(in[0]),\n        output_channel,\n        StringToStorageOrder(helper.GetSingleArgument<string>(\"order\", \"NCHW\")),\n        helper.GetSingleArgument<int>(\"global_pooling\", 0),\n        static_cast<LegacyPadding>(\n            helper.GetSingleArgument<int>(\"legacy_pad\", LegacyPadding::NOTSET)),\n        N,\n        kernel,\n        output_dims,\n        dilations,\n        strides,\n        pads,\n        channel_first);\n    vector<TensorShape> out(1);\n    if (channel_first) {\n      output_dims.insert(output_dims.begin(), {N, output_channel});\n    } else {\n      output_dims.push_back(output_channel);\n      output_dims.insert(output_dims.begin(), N);\n    }\n\n    out[0] = CreateTensorShape(output_dims, TensorProto::FLOAT);\n    return out;\n  }\n\n  static vector<TensorShape> TensorInferenceForConv(\n      const OperatorDef& def,\n      const vector<TensorShape>& in) {\n    return TensorInferenceForSchema(def, in, in[1].dims(0));\n  }\n\n  static vector<TensorShape> TensorInferenceForPool(\n      const OperatorDef& def,\n      const vector<TensorShape>& in) {\n    ArgumentHelper helper(def);\n    auto order =\n        StringToStorageOrder(helper.GetSingleArgument<string>(\"order\", \"NCHW\"));\n    int num_channels =\n        (order == StorageOrder::NCHW ? in[0].dims(1) : in[0].dims(3));\n    return TensorInferenceForSchema(def, in, num_channels);\n  }\n\n  virtual ~ConvPoolOpBase() {}\n\n protected:\n  LegacyPadding legacy_pad_;\n  bool global_pooling_;\n  vector<int> kernel_;\n  vector<int> dilation_;\n  vector<int> stride_;\n  vector<int> pads_;\n\n  bool float16_compute_;\n\n  // We need the above parameters to be available for the devices.\n  Tensor<Context> kernel_device_;\n  Tensor<Context> dilation_device_;\n  Tensor<Context> stride_device_;\n  Tensor<Context> pads_device_;\n\n  int group_;\n  StorageOrder order_;\n  bool shared_buffer_;\n  Workspace* ws_;\n\n  static inline void ComputeSizeAndPad(\n      const int in_size,\n      const int stride,\n      const int kernel,\n      const int dilation,\n      LegacyPadding legacy_pad,\n      int* pad_head,\n      int* pad_tail,\n      int* out_size) {\n    const int dkernel = dilation * (kernel - 1) + 1;\n    switch (legacy_pad) {\n      case LegacyPadding::NOTSET:\n        // We will just use the direct padding head and tail values, but we\n        // will verify that they are non-negative.\n        CAFFE_ENFORCE_GE(in_size + *pad_head + *pad_tail, dkernel);\n        *out_size = static_cast<int>(\n            static_cast<float>(in_size + *pad_head + *pad_tail - dkernel) /\n                stride +\n            1);\n        break;\n      case LegacyPadding::VALID:\n        *pad_head = 0;\n        *pad_tail = 0;\n        *out_size = (in_size - dkernel) / stride + 1;\n        break;\n      case LegacyPadding::SAME: {\n        CAFFE_ENFORCE(\n            1 == dilation, \"Dilation not supported for legacy padding.\");\n        int legacy_target_size = (in_size + stride - 1) / stride;\n        int pad_needed = (legacy_target_size - 1) * stride + kernel - in_size;\n        if (CAFFE2_PAD_HEAD_MORE) {\n          *pad_head = (pad_needed + 1) / 2;\n        } else {\n          *pad_head = pad_needed / 2;\n        }\n        *pad_tail = pad_needed - *pad_head;\n        *out_size = (in_size + pad_needed - dkernel) / stride + 1;\n        break;\n      }\n      case LegacyPadding::CAFFE_LEGACY_POOLING:\n        // This is in order to adapt Caffe's pooling padding case. In this case,\n        // we will only use pad_head and will compute pad_tail to match the\n        // old caffe pooling strategy. Also see caffe2_legacy.proto for more\n        // details.\n        CAFFE_ENFORCE_GE(*pad_head, 0);\n        // Here, notice that caffe casts UP while caffe2 casts DOWN for the\n        // output size computation.\n        *out_size = std::ceil(\n            static_cast<float>(in_size + *pad_head * 2 - kernel) / stride + 1);\n        // If we have padding, caffe also ensures that the last pooling starts\n        // strictly inside the image (instead of at the padding); otherwise clip\n        // the last.\n        if (*pad_head > 0 && (*out_size - 1) * stride >= in_size + *pad_head) {\n          --*out_size;\n        }\n        // Now, compare the output size with the standard Caffe2 output size.\n        // The\n        // caffe2 standard output size should always be no larger than the\n        // output\n        // size of caffe.\n        int standard_out_size = static_cast<int>(\n            static_cast<float>(in_size + *pad_head * 2 - kernel) / stride + 1);\n        CAFFE_ENFORCE_GE(\n            *out_size,\n            standard_out_size,\n            \"This should never happen. If this happens, double check the logic \"\n            \"above.\");\n        if (*out_size > standard_out_size) {\n          LOG(WARNING)\n              << \"You are hitting a case where Caffe's legacy padding calculation \"\n                 \"is hit. This leads to inefficient and sometimes incorrect \"\n                 \"results. We are keeping this behavior for backward compatibility\"\n                 \", but you are strongly recommended to move away from it.\";\n        }\n        *pad_tail = *pad_head + stride * (*out_size - standard_out_size);\n        break;\n    }\n  }\n\n  // Accessors for 2D conv params.\n\n  inline int pad_t() const {\n    return pads_[0];\n  }\n\n  inline int pad_l() const {\n    return pads_[1];\n  }\n\n  inline int pad_b() const {\n    return pads_[2];\n  }\n\n  inline int pad_r() const {\n    return pads_[3];\n  }\n\n  inline int kernel_h() const {\n    return kernel_[0];\n  }\n\n  inline int kernel_w() const {\n    return kernel_[1];\n  }\n\n  inline int stride_h() const {\n    return stride_[0];\n  }\n\n  inline int stride_w() const {\n    return stride_[1];\n  }\n\n  inline int dilation_h() const {\n    return dilation_[0];\n  }\n\n  inline int dilation_w() const {\n    return dilation_[1];\n  }\n\n private:\n inline void AllocateAndCopy(const vector<int>& vec, Tensor<Context>& tensor) {\n      tensor.Resize(vec.size());\n      context_.template Copy<int, CPUContext, Context>(\n          vec.size(), vec.data(), tensor.template mutable_data<int>());\n }\n\n#define USE_CONV_POOL_BASE_FUNCTIONS(Context)      \\\n  USE_OPERATOR_FUNCTIONS(Context);                 \\\n  using ConvPoolOpBase<Context>::pads_;            \\\n  using ConvPoolOpBase<Context>::pads_device_;     \\\n  using ConvPoolOpBase<Context>::pad_t;            \\\n  using ConvPoolOpBase<Context>::pad_l;            \\\n  using ConvPoolOpBase<Context>::pad_b;            \\\n  using ConvPoolOpBase<Context>::pad_r;            \\\n  using ConvPoolOpBase<Context>::legacy_pad_;      \\\n  using ConvPoolOpBase<Context>::global_pooling_;  \\\n  using ConvPoolOpBase<Context>::kernel_;          \\\n  using ConvPoolOpBase<Context>::kernel_device_;   \\\n  using ConvPoolOpBase<Context>::kernel_h;         \\\n  using ConvPoolOpBase<Context>::kernel_w;         \\\n  using ConvPoolOpBase<Context>::dilation_;        \\\n  using ConvPoolOpBase<Context>::dilation_device_; \\\n  using ConvPoolOpBase<Context>::dilation_h;       \\\n  using ConvPoolOpBase<Context>::dilation_w;       \\\n  using ConvPoolOpBase<Context>::stride_;          \\\n  using ConvPoolOpBase<Context>::stride_device_;   \\\n  using ConvPoolOpBase<Context>::stride_h;         \\\n  using ConvPoolOpBase<Context>::stride_w;         \\\n  using ConvPoolOpBase<Context>::group_;           \\\n  using ConvPoolOpBase<Context>::order_;           \\\n  using ConvPoolOpBase<Context>::shared_buffer_;   \\\n  using ConvPoolOpBase<Context>::GetDims;          \\\n  using ConvPoolOpBase<Context>::GetDimsSize;      \\\n  using ConvPoolOpBase<Context>::SetDeviceTensor;  \\\n  using ConvPoolOpBase<Context>::ws_\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CONV_POOL_OP_BASE_H_\n"
  },
  {
    "path": "caffe2/operators/conv_transpose_gradient_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/conv_transpose_op.h\"\n#include \"caffe2/operators/conv_transpose_op_impl.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(\n    ConvTransposeGradient,\n    ConvTransposeGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(ConvTransposeGradient).NumInputs(3).NumOutputs(1, 3);\n\nclass GetConvTransposeGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    auto compute_dX =\n        !ArgumentHelper::GetSingleArgument(def_, \"no_gradient_to_input\", false);\n\n    CAFFE_ENFORCE(3 == def_.input_size() || 2 == def_.input_size());\n    if (def_.input_size() == 3 && compute_dX) {\n      return SingleGradientDef(\n          \"ConvTransposeGradient\",\n          \"\",\n          vector<string>{I(0), I(1), GO(0)},\n          vector<string>{GI(1), GI(2), GI(0)});\n    } else if (def_.input_size() == 3) {\n      return SingleGradientDef(\n          \"ConvTransposeGradient\",\n          \"\",\n          vector<string>{I(0), I(1), GO(0)},\n          vector<string>{GI(1), GI(2)});\n    } else if (compute_dX) {\n      return SingleGradientDef(\n          \"ConvTransposeGradient\",\n          \"\",\n          vector<string>{I(0), I(1), GO(0)},\n          vector<string>{GI(1), GI(0)},\n          vector<Argument>{MakeArgument<bool>(\"no_bias\", true)});\n    } else {\n      return SingleGradientDef(\n          \"ConvTransposeGradient\",\n          \"\",\n          vector<string>{I(0), I(1), GO(0)},\n          vector<string>{GI(1)},\n          vector<Argument>{MakeArgument<bool>(\"no_bias\", true)});\n    }\n  }\n};\nREGISTER_GRADIENT(ConvTranspose, GetConvTransposeGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/conv_transpose_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/conv_transpose_op.h\"\n#include \"caffe2/operators/conv_transpose_op_impl.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(ConvTranspose, ConvTransposeOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(ConvTranspose)\n    .NumInputs(2, 3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nThe transposed convolution consumes an input vector, the filter blob, and\nthe bias blob, and computes the output. Note that other parameters, such as\nthe stride and kernel size, or the pads' sizes in each direction are not\nnecessary for input because they are provided by the\nConvTransposeUnpoolOpBase operator. Various dimension checks are done\nimplicitly, and the sizes are specified in the Input docs for this operator.\nAs is expected, the filter is deconvolved with a subset of the\nimage and the bias is added; this is done throughout the image data and the\noutput is computed. As a side note on the implementation layout:\nconv_transpose_op_impl.h is the templated implementation of the\nconv_transpose_op.h file, which is why they are separate files.\n  )DOC\")\n    .Input(\n        0,\n        \"X\",\n        \"Input data blob from previous layer; has size \"\n        \"(N x C x H x W), where N is the batch size, C is the number of channels, and\"\n        \" H and W are the height and width. Note that this is for the NCHW usage. On \"\n        \"the other hand, the NHWC Op has a different set of dimension constraints.\")\n    .Input(\n        1,\n        \"filter\",\n        \"The filter blob that will be used in the transposed \"\n        \"convolution; has size (M x C x kH x kW), where C is the number of channels,\"\n        \" and kH and kW are the height and width of the kernel.\")\n    .Input(\n        2,\n        \"bias\",\n        \"The 1D bias blob that is added through the convolution;\"\n        \"has size (C). Optional, if not passed, will treat it as all 0.\")\n    .Output(\n        0,\n        \"Y\",\n        \"Output data blob that contains the result of the \"\n        \"transposed convolution. The output dimensions are functions of the kernel\"\n        \" size, stride size, and pad lengths.\");\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/conv_transpose_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CONV_TRANSPOSE_OP_H_\n#define CAFFE2_OPERATORS_CONV_TRANSPOSE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_transpose_unpool_op_base.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass ConvTransposeOp final : public ConvTransposeUnpoolBase<Context> {\n public:\n  USE_CONV_TRANSPOSE_UNPOOL_BASE_FUNCTIONS(Context);\n  ConvTransposeOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvTransposeUnpoolBase<Context>(operator_def, ws) {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n private:\n  Tensor<Context> col_buffer_;\n  Tensor<Context> bias_multiplier_;\n  // Input: X, W, b\n  // Output: Y\n  INPUT_TAGS(INPUT, FILTER, BIAS);\n};\n\ntemplate <typename T, class Context>\nclass ConvTransposeGradientOp final : public ConvTransposeUnpoolBase<Context> {\n public:\n  USE_CONV_TRANSPOSE_UNPOOL_BASE_FUNCTIONS(Context);\n  ConvTransposeGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvTransposeUnpoolBase<Context>(operator_def, ws),\n        no_bias_(OperatorBase::GetSingleArgument<bool>(\"no_bias\", false)) {\n    CAFFE_ENFORCE(\n        !(no_bias_ && OutputSize() == 3),\n        \"If bias is not present, you should not have 3 grad output.\");\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n private:\n  Tensor<Context> col_buffer_;\n  Tensor<Context> bias_multiplier_;\n  const bool no_bias_;\n  // input: X, W, dY\n  // output: dW, optionally db and dX\n  INPUT_TAGS(INPUT, FILTER, OUTPUT_GRAD);\n  OUTPUT_TAGS(FILTER_GRAD, BIAS_OR_INPUT_GRAD, INPUT_GRAD);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CONV_TRANSPOSE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/conv_transpose_op_cudnn.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/cudnn_wrappers.h\"\n#include \"caffe2/operators/conv_op_cache_cudnn.h\"\n#include \"caffe2/operators/conv_transpose_op.h\"\n#include \"caffe2/operators/op_utils_cudnn.h\"\n\nnamespace caffe2 {\n\nclass CudnnConvTransposeOpBase : public ConvTransposeUnpoolBase<CUDAContext> {\n public:\n  CudnnConvTransposeOpBase(const OperatorDef& operator_def, Workspace* ws)\n      : ConvTransposeUnpoolBase<CUDAContext>(operator_def, ws),\n        cudnn_wrapper_(&context_),\n        cudnn_ws_nbytes_limit_(OperatorBase::GetSingleArgument<size_t>(\n            \"ws_nbytes_limit\",\n            kCONV_CUDNN_WORKSPACE_LIMIT_BYTES)),\n        exhaustive_search_(\n            OperatorBase::GetSingleArgument<int>(\"exhaustive_search\", 0)),\n        deterministic_(\n            OperatorBase::GetSingleArgument<int>(\"deterministic\", 0)),\n        cudnn_state_(OperatorBase::GetSingleArgument<int>(\"cudnn_state\", 0)),\n        force_algo_(OperatorBase::GetRepeatedArgument<int>(\n            \"force_algo\",\n            vector<int>{-1, -1, -1})),\n        enable_tensor_core_(\n            OperatorBase::GetSingleArgument<bool>(\"enable_tensor_core\", 1)) {\n    CAFFE_ENFORCE(!deterministic_ || !exhaustive_search_);\n\n    bool individual_force_algo = OperatorBase::HasArgument(\"force_algo_fwd\") ||\n        OperatorBase::HasArgument(\"force_algo_dgrad\") ||\n        OperatorBase::HasArgument(\"force_algo_wgrad\");\n    if (OperatorBase::HasArgument(\"force_algo\")) {\n      CAFFE_ENFORCE(\n          !individual_force_algo,\n          \"Cannot specify both force_algo and any of\",\n          \"force_algo_fwd, force_algo_dgrad, force_algo_wgrad\");\n    } else {\n      force_algo_ = std::vector<int>{-1, -1, -1};\n      force_algo_[ALGO_FWD] =\n          OperatorBase::GetSingleArgument<int>(\"force_algo_fwd\", -1);\n      force_algo_[ALGO_DGRAD] =\n          OperatorBase::GetSingleArgument<int>(\"force_algo_dgrad\", -1);\n      force_algo_[ALGO_WGRAD] =\n          OperatorBase::GetSingleArgument<int>(\"force_algo_wgrad\", -1);\n    }\n\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&bottom_desc_));\n    CUDNN_ENFORCE(cudnnCreateFilterDescriptor(&filter_desc_));\n    if (InputSize() == 3) {\n      CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&bias_desc_));\n    }\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&top_desc_));\n    CUDNN_ENFORCE(cudnnCreateConvolutionDescriptor(&conv_desc_));\n  }\n\n  ~CudnnConvTransposeOpBase() {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(bottom_desc_));\n    CUDNN_ENFORCE(cudnnDestroyFilterDescriptor(filter_desc_));\n    if (InputSize() == 3) {\n      CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(bias_desc_));\n    }\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(top_desc_));\n    CUDNN_ENFORCE(cudnnDestroyConvolutionDescriptor(conv_desc_));\n  }\n\n protected:\n  vector<TIndex> cudnn_input_dims_;\n  vector<TIndex> cudnn_filter_dims_;\n\n  CuDNNWrapper cudnn_wrapper_;\n  cudnnTensorDescriptor_t bottom_desc_;\n  cudnnFilterDescriptor_t filter_desc_;\n  cudnnTensorDescriptor_t bias_desc_;\n  cudnnTensorDescriptor_t top_desc_;\n  cudnnConvolutionDescriptor_t conv_desc_;\n  const size_t cudnn_ws_nbytes_limit_;\n  size_t cudnn_ws_nbytes_;\n  bool exhaustive_search_;\n  bool deterministic_;\n  size_t cudnn_state_;\n  vector<int> force_algo_; // stored as FWD, dFILTER, dDATA\n  bool enable_tensor_core_;\n};\n\ntemplate <typename T>\nclass CudnnConvTransposeOp final : public CudnnConvTransposeOpBase {\n public:\n  CudnnConvTransposeOp(const OperatorDef& operator_def, Workspace* ws)\n      : CudnnConvTransposeOpBase(operator_def, ws) {}\n\n  ~CudnnConvTransposeOp() {}\n\n  bool RunOnDevice() override;\n\n private:\n  AlgorithmsCache<cudnnConvolutionBwdDataAlgo_t> data_algo_cache_;\n  cudnnConvolutionBwdDataAlgo_t bwd_data_algo_;\n  // Input: X, W, b\n  // Output: Y\n  INPUT_TAGS(INPUT, FILTER, BIAS);\n};\n\ntemplate <typename T>\nclass CudnnConvTransposeGradientOp final : public CudnnConvTransposeOpBase {\n public:\n  CudnnConvTransposeGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : CudnnConvTransposeOpBase(operator_def, ws),\n        no_bias_(OperatorBase::GetSingleArgument<bool>(\"no_bias\", false)) {\n    CAFFE_ENFORCE(\n        !(no_bias_ && OutputSize() == 3),\n        \"If bias is not present, you should not have 3 grad output.\");\n  }\n\n  ~CudnnConvTransposeGradientOp() {}\n\n  bool RunOnDevice() override;\n\n private:\n  cudnnConvolutionFwdAlgo_t algo_;\n  cudnnConvolutionBwdFilterAlgo_t bwd_filter_algo_;\n  AlgorithmsCache<cudnnConvolutionFwdAlgo_t> forward_algo_cache_;\n  AlgorithmsCache<cudnnConvolutionBwdFilterAlgo_t> filter_algo_cache_;\n  const bool no_bias_;\n  // input: X, W, dY\n  // output: dW, optionally db and dX\n  INPUT_TAGS(INPUT, FILTER, OUTPUT_GRAD);\n  OUTPUT_TAGS(FILTER_GRAD, BIAS_OR_INPUT_GRAD, INPUT_GRAD);\n};\n\n////////////////////////////////////////////////////////////////////////////////\n// Implementations\n////////////////////////////////////////////////////////////////////////////////\n\ntemplate <typename T>\nbool CudnnConvTransposeOp<T>::RunOnDevice() {\n  auto& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  auto* Y = Output(0);\n  int C = 0;\n  switch (order_) {\n    case StorageOrder::NHWC:\n      C = filter.dim32(3);\n      break;\n    case StorageOrder::NCHW:\n      C = filter.dim32(1);\n      break;\n    default:\n      LOG(FATAL) << \"Unknown storage order: \" << order_;\n  }\n  ConvTransposeUnpoolBase<CUDAContext>::SetOutputSize(X, Y, C);\n\n  int N = 0, M = 0, H = 0, W = 0, H_out = 0, W_out = 0;\n  switch (order_) {\n    case StorageOrder::NHWC:\n      N = X.dim32(0);\n      H = X.dim32(1);\n      W = X.dim32(2);\n      M = X.dim32(3);\n      H_out = Y->dim32(1);\n      W_out = Y->dim32(2);\n      CAFFE_ENFORCE_EQ(filter.dim32(1), kernel_h());\n      CAFFE_ENFORCE_EQ(filter.dim32(1), kernel_h());\n      CAFFE_ENFORCE_EQ(filter.dim32(2), kernel_w());\n      CAFFE_ENFORCE_EQ(filter.dim32(3), C);\n      break;\n    case StorageOrder::NCHW:\n      N = X.dim32(0);\n      M = X.dim32(1);\n      H = X.dim32(2);\n      W = X.dim32(3);\n      H_out = Y->dim32(2);\n      W_out = Y->dim32(3);\n      CAFFE_ENFORCE_EQ(filter.dim32(1), C);\n      CAFFE_ENFORCE_EQ(filter.dim32(2), kernel_h());\n      CAFFE_ENFORCE_EQ(filter.dim32(3), kernel_w());\n      break;\n    default:\n      LOG(FATAL) << \"Unknown storage order: \" << order_;\n  }\n\n  if (InputSize() == 3) {\n    auto& bias = Input(BIAS);\n    CAFFE_ENFORCE_EQ(bias.ndim(), 1);\n    CAFFE_ENFORCE_EQ(bias.dim32(0), C);\n  }\n\n  // Set up the cudnn algorithms & workspace if necessary\n  bool input_changed = (X.dims() != cudnn_input_dims_);\n  bool filter_changed = (filter.dims() != cudnn_filter_dims_);\n\n  if (input_changed || filter_changed) {\n    VLOG(1) << \"Changing the cudnn descriptor configurations.\";\n    if (input_changed) {\n      cudnn_input_dims_ = X.dims();\n      CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n          bottom_desc_,\n          GetCudnnTensorFormat(order_),\n          cudnnTypeWrapper<T>::type,\n          N,\n          M,\n          H,\n          W));\n    }\n    if (filter_changed) {\n      cudnn_filter_dims_ = filter.dims();\n      CUDNN_ENFORCE(cudnnSetFilter4dDescriptor(\n          filter_desc_,\n          cudnnTypeWrapper<T>::type,\n          GetCudnnTensorFormat(order_),\n          M,\n          C,\n          kernel_h(),\n          kernel_w()));\n      if (InputSize() == 3) {\n        CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n            bias_desc_,\n            GetCudnnTensorFormat(order_),\n            cudnnTypeWrapper<T>::type,\n            1,\n            C,\n            1,\n            1));\n      }\n    }\n    // Set the output\n    CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n        top_desc_,\n        GetCudnnTensorFormat(order_),\n        cudnnTypeWrapper<T>::type,\n        N,\n        C,\n        H_out,\n        W_out));\n    // Set the convolution descriptor\n    CAFFE_ENFORCE_EQ(\n        pad_t(),\n        pad_b(),\n        \"The current padding scheme leads to unequal padding on the top and \"\n        \"bottom, which is not supported by cudnn.\");\n    CAFFE_ENFORCE_EQ(\n        pad_l(),\n        pad_r(),\n        \"The current padding scheme leads to unequal padding on the left \"\n        \"and right, which is not supported by cudnn.\");\n    // Set the convolution descriptor\n#if CUDNN_VERSION_MIN(6,0,0)\n    CUDNN_ENFORCE(cudnnSetConvolution2dDescriptor(\n        conv_desc_,\n        pad_t(),\n        pad_l(),\n        stride_h(),\n        stride_w(),\n        1,\n        1,\n        CUDNN_CROSS_CORRELATION,\n        cudnnTypeWrapper<T>::type));\n#else\n    CUDNN_ENFORCE(cudnnSetConvolution2dDescriptor(\n        conv_desc_,\n        pad_t(),\n        pad_l(),\n        stride_h(),\n        stride_w(),\n        1,\n        1,\n        CUDNN_CROSS_CORRELATION));\n#endif\n#if CUDNN_VERSION_MIN(7, 0, 0)\n    // enable TensorCore math if desired\n    enable_tensor_core_ &= TensorCoreAvailable();\n    if (enable_tensor_core_) {\n      CUDNN_ENFORCE(\n          cudnnSetConvolutionMathType(conv_desc_, CUDNN_TENSOR_OP_MATH));\n    }\n#endif\n    if (force_algo_[ALGO_DGRAD] >= 0) {\n      bwd_data_algo_ = (cudnnConvolutionBwdDataAlgo_t)force_algo_[ALGO_DGRAD];\n    } else if (deterministic_) {\n      bwd_data_algo_ = CUDNN_CONVOLUTION_BWD_DATA_ALGO_1;\n    } else if (exhaustive_search_) {\n      bwd_data_algo_ =\n          data_algo_cache_.getAlgorithm(X.dims(), filter.dims(), 0, [&]() {\n            int returned_algo_count;\n            std::array<\n                cudnnConvolutionBwdDataAlgoPerf_t,\n                kNUM_CUDNN_BWD_DATA_ALGS>\n                data_perf_stat;\n            cudnn_wrapper_.with_cudnn_state(\n                cudnn_state_, [&](CuDNNState* state) {\n                  state->workspace().reset();\n                  CUDNN_ENFORCE(cudnnFindConvolutionBackwardDataAlgorithm(\n                      state->cudnn_handle(),\n                      filter_desc_,\n                      bottom_desc_,\n                      conv_desc_,\n                      top_desc_,\n                      kNUM_CUDNN_BWD_DATA_ALGS,\n                      &returned_algo_count,\n                      data_perf_stat.data()));\n                });\n\n            LogCuDNNPerfStats(data_perf_stat, returned_algo_count);\n            return data_perf_stat[0].algo;\n          });\n    } else {\n      CUDNN_ENFORCE(cudnnGetConvolutionBackwardDataAlgorithm(\n          cudnn_wrapper_.inline_cudnn_handle(),\n          filter_desc_,\n          bottom_desc_,\n          conv_desc_,\n          top_desc_,\n          CUDNN_CONVOLUTION_BWD_DATA_SPECIFY_WORKSPACE_LIMIT,\n          cudnn_ws_nbytes_limit_,\n          &bwd_data_algo_));\n    }\n\n    size_t bwd_data_ws_size;\n    CUDNN_ENFORCE(cudnnGetConvolutionBackwardDataWorkspaceSize(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        filter_desc_,\n        bottom_desc_,\n        conv_desc_,\n        top_desc_,\n        bwd_data_algo_,\n        &bwd_data_ws_size));\n    cudnn_ws_nbytes_ = bwd_data_ws_size;\n    VLOG(1) << \"CuDNN algorithm: \" << bwd_data_algo_;\n    VLOG(1) << \"CuDNN workspace size: \" << bwd_data_ws_size;\n  }\n\n  // Now, actually run the computation.\n  // Filter\n  cudnn_wrapper_.with_cudnn_state(cudnn_state_, [&](CuDNNState* state) {\n    CUDNN_ENFORCE(cudnnConvolutionBackwardData(\n        state->cudnn_handle(),\n        cudnnTypeWrapper<T>::kOne(),\n        filter_desc_,\n        filter.template data<T>(),\n        bottom_desc_,\n        X.template data<T>(),\n        conv_desc_,\n        bwd_data_algo_,\n        state->workspace().get(cudnn_ws_nbytes_),\n        cudnn_ws_nbytes_,\n        cudnnTypeWrapper<T>::kZero(),\n        top_desc_,\n        Y->template mutable_data<T>()));\n  });\n  // Bias\n  if (InputSize() == 3) {\n    CUDNN_ENFORCE(cudnnAddTensor(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        cudnnTypeWrapper<T>::kOne(),\n        bias_desc_,\n        Input(BIAS).template data<T>(),\n        cudnnTypeWrapper<T>::kOne(),\n        top_desc_,\n        Y->template mutable_data<T>()));\n  }\n  // Done.\n  return true;\n}\n\n// TODO(Yangqing): a lot of the function contents are very similar. Consider\n// consolidating them.\ntemplate <typename T>\nbool CudnnConvTransposeGradientOp<T>::RunOnDevice() {\n  auto& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  auto& dY = Input(OUTPUT_GRAD);\n  auto* dfilter = Output(FILTER_GRAD);\n  CAFFE_ENFORCE_EQ(X.ndim(), 4);\n  CAFFE_ENFORCE_EQ(filter.ndim(), 4);\n  int C = 0;\n  switch (order_) {\n    case StorageOrder::NHWC:\n      C = filter.dim32(3);\n      break;\n    case StorageOrder::NCHW:\n      C = filter.dim32(1);\n      break;\n    default:\n      LOG(FATAL) << \"Unknown storage order: \" << order_;\n  }\n\n  int N = 0, M = 0, H = 0, W = 0, H_out = 0, W_out = 0;\n  switch (order_) {\n    case StorageOrder::NHWC:\n      N = X.dim32(0);\n      H = X.dim32(1);\n      W = X.dim32(2);\n      M = X.dim32(3);\n      H_out = dY.dim32(1);\n      W_out = dY.dim32(2);\n      CAFFE_ENFORCE_EQ(filter.dim32(1), kernel_h());\n      CAFFE_ENFORCE_EQ(filter.dim32(1), kernel_h());\n      CAFFE_ENFORCE_EQ(filter.dim32(2), kernel_w());\n      CAFFE_ENFORCE_EQ(filter.dim32(3), C);\n      break;\n    case StorageOrder::NCHW:\n      N = X.dim32(0);\n      M = X.dim32(1);\n      H = X.dim32(2);\n      W = X.dim32(3);\n      H_out = dY.dim32(2);\n      W_out = dY.dim32(3);\n      CAFFE_ENFORCE_EQ(filter.dim32(1), C);\n      CAFFE_ENFORCE_EQ(filter.dim32(2), kernel_h());\n      CAFFE_ENFORCE_EQ(filter.dim32(3), kernel_w());\n      break;\n    default:\n      LOG(FATAL) << \"Unknown storage order: \" << order_;\n  }\n  // Since we only handle LegacyPadding::NOTSET, we don't need to\n  // compute padding.\n  dfilter->ResizeLike(filter);\n\n  // Set up the cudnn algorithms & workspace if necessary\n  bool input_changed = (X.dims() != cudnn_input_dims_);\n  bool filter_changed = (filter.dims() != cudnn_filter_dims_);\n  if (input_changed || filter_changed) {\n    VLOG(1) << \"Changing the cudnn descriptor configurations.\";\n    if (input_changed) {\n      cudnn_input_dims_ = X.dims();\n      CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n          bottom_desc_,\n          GetCudnnTensorFormat(order_),\n          cudnnTypeWrapper<T>::type,\n          N,\n          M,\n          H,\n          W));\n    }\n    if (filter_changed) {\n      cudnn_filter_dims_ = filter.dims();\n      CUDNN_ENFORCE(cudnnSetFilter4dDescriptor(\n          filter_desc_,\n          cudnnTypeWrapper<T>::type,\n          GetCudnnTensorFormat(order_),\n          M,\n          C,\n          kernel_h(),\n          kernel_w()));\n      if (!no_bias_) {\n        CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n            bias_desc_,\n            GetCudnnTensorFormat(order_),\n            cudnnTypeWrapper<T>::type,\n            1,\n            C,\n            1,\n            1));\n      }\n    }\n    // Set the output\n    CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n        top_desc_,\n        GetCudnnTensorFormat(order_),\n        cudnnTypeWrapper<T>::type,\n        N,\n        C,\n        H_out,\n        W_out));\n    // Set the convolution descriptor\n    CAFFE_ENFORCE_EQ(\n        pad_t(),\n        pad_b(),\n        \"The current padding scheme leads to unequal padding on the top and \"\n        \"bottom, which is not supported by cudnn.\");\n    CAFFE_ENFORCE_EQ(\n        pad_l(),\n        pad_r(),\n        \"The current padding scheme leads to unequal padding on the left \"\n        \"and right, which is not supported by cudnn.\");\n#if CUDNN_VERSION_MIN(6,0,0)\n    CUDNN_ENFORCE(cudnnSetConvolution2dDescriptor(\n        conv_desc_,\n        pad_t(),\n        pad_l(),\n        stride_h(),\n        stride_w(),\n        1,\n        1,\n        CUDNN_CROSS_CORRELATION,\n        cudnnTypeWrapper<T>::type));\n#else\n    CUDNN_ENFORCE(cudnnSetConvolution2dDescriptor(\n        conv_desc_,\n        pad_t(),\n        pad_l(),\n        stride_h(),\n        stride_w(),\n        1,\n        1,\n        CUDNN_CROSS_CORRELATION));\n#endif\n#if CUDNN_VERSION_MIN(7, 0, 0)\n    // enable TensorCore math if desired\n    enable_tensor_core_ &= TensorCoreAvailable();\n    if (enable_tensor_core_) {\n      CUDNN_ENFORCE(\n          cudnnSetConvolutionMathType(conv_desc_, CUDNN_TENSOR_OP_MATH));\n    }\n#endif\n    if (force_algo_[ALGO_WGRAD] >= 0) {\n      bwd_filter_algo_ =\n          (cudnnConvolutionBwdFilterAlgo_t)force_algo_[ALGO_WGRAD];\n    } else if (deterministic_) {\n      algo_ = CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_PRECOMP_GEMM;\n      bwd_filter_algo_ = CUDNN_CONVOLUTION_BWD_FILTER_ALGO_1;\n    } else if (exhaustive_search_) {\n      bwd_filter_algo_ =\n          filter_algo_cache_.getAlgorithm(X.dims(), filter.dims(), 0, [&]() {\n            LOG(INFO) << \"CUDNN Convolution bwd: doing exhaustive search.\";\n            // When we do an exhaustive search, we will ignore the workspace\n            // size\n            // limit and simply go for the fastest algorithm. If you happen to\n            // run\n            // out of memory later, you will be on your own...\n            int returned_algo_count;\n            // We clean up the current workspace memory so that the forward\n            // algorithm\n            // is free to allocate memory.\n            // Actually run the search.\n            std::array<\n                cudnnConvolutionBwdFilterAlgoPerf_t,\n                kNUM_CUDNN_BWD_FILTER_ALGS>\n                filter_perf_stat;\n\n            cudnn_wrapper_.with_cudnn_state(\n                cudnn_state_, [&](CuDNNState* state) {\n                  state->workspace().reset();\n                  CUDNN_ENFORCE(cudnnFindConvolutionBackwardFilterAlgorithm(\n                      state->cudnn_handle(),\n                      top_desc_,\n                      bottom_desc_,\n                      conv_desc_,\n                      filter_desc_,\n                      kNUM_CUDNN_BWD_FILTER_ALGS,\n                      &returned_algo_count,\n                      filter_perf_stat.data()));\n                });\n            LogCuDNNPerfStats(filter_perf_stat, returned_algo_count);\n            return filter_perf_stat[0].algo;\n          });\n\n      algo_ =\n          forward_algo_cache_.getAlgorithm(X.dims(), filter.dims(), 0, [&]() {\n            int returned_algo_count;\n            std::array<cudnnConvolutionFwdAlgoPerf_t, kNUM_CUDNN_FWD_ALGS>\n                fwd_perf_stat;\n            cudnn_wrapper_.with_cudnn_state(\n                cudnn_state_, [&](CuDNNState* state) {\n                  state->workspace().reset();\n                  CUDNN_ENFORCE(cudnnFindConvolutionForwardAlgorithm(\n                      state->cudnn_handle(),\n                      top_desc_,\n                      filter_desc_,\n                      conv_desc_,\n                      bottom_desc_,\n                      kNUM_CUDNN_BWD_DATA_ALGS,\n                      &returned_algo_count,\n                      fwd_perf_stat.data()));\n                });\n\n            LogCuDNNPerfStats(fwd_perf_stat, returned_algo_count);\n            return fwd_perf_stat[0].algo;\n          });\n    } else {\n      // choose backward algorithm for filter\n      CUDNN_ENFORCE(cudnnGetConvolutionBackwardFilterAlgorithm(\n          cudnn_wrapper_.inline_cudnn_handle(),\n          top_desc_,\n          bottom_desc_,\n          conv_desc_,\n          filter_desc_,\n          CUDNN_CONVOLUTION_BWD_FILTER_SPECIFY_WORKSPACE_LIMIT,\n          cudnn_ws_nbytes_limit_,\n          &bwd_filter_algo_));\n      // choose backward algo for data\n      CUDNN_ENFORCE(cudnnGetConvolutionForwardAlgorithm(\n          cudnn_wrapper_.inline_cudnn_handle(),\n          top_desc_,\n          filter_desc_,\n          conv_desc_,\n          bottom_desc_,\n          CUDNN_CONVOLUTION_FWD_SPECIFY_WORKSPACE_LIMIT,\n          cudnn_ws_nbytes_limit_,\n          &algo_));\n    }\n    // get workspace for backwards filter algorithm\n    size_t bwd_filter_ws_size, fwd_ws_size;\n    CUDNN_ENFORCE(cudnnGetConvolutionBackwardFilterWorkspaceSize(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        top_desc_,\n        bottom_desc_,\n        conv_desc_,\n        filter_desc_,\n        bwd_filter_algo_,\n        &bwd_filter_ws_size));\n    // get workspace for backwards data algorithm\n    CUDNN_ENFORCE(cudnnGetConvolutionForwardWorkspaceSize(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        top_desc_,\n        filter_desc_,\n        conv_desc_,\n        bottom_desc_,\n        algo_,\n        &fwd_ws_size));\n    cudnn_ws_nbytes_ = std::max(bwd_filter_ws_size, fwd_ws_size);\n\n    VLOG(1) << \"CuDNN bwd algorithm: \" << bwd_filter_algo_ << \", \" << algo_;\n    VLOG(1) << \"CuDNN workspace size: \" << cudnn_ws_nbytes_;\n  }\n\n  // Now, actually run the computation.\n  if (!no_bias_) {\n    auto* dbias = Output(BIAS_OR_INPUT_GRAD);\n    dbias->Resize(C);\n    CUDNN_ENFORCE(cudnnConvolutionBackwardBias(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        cudnnTypeWrapper<T>::kOne(),\n        top_desc_,\n        dY.template data<T>(),\n        cudnnTypeWrapper<T>::kZero(),\n        bias_desc_,\n        dbias->template mutable_data<T>()));\n  }\n\n  cudnn_wrapper_.with_cudnn_state(cudnn_state_, [&](CuDNNState* state) {\n    CUDNN_ENFORCE(cudnnConvolutionBackwardFilter(\n        state->cudnn_handle(),\n        cudnnTypeWrapper<T>::kOne(),\n        top_desc_,\n        dY.template data<T>(),\n        bottom_desc_,\n        X.template data<T>(),\n        conv_desc_,\n        bwd_filter_algo_,\n        state->workspace().get(cudnn_ws_nbytes_),\n        cudnn_ws_nbytes_,\n        cudnnTypeWrapper<T>::kZero(),\n        filter_desc_,\n        dfilter->template mutable_data<T>()));\n\n    if (OutputSize() == 3 || (no_bias_ && (OutputSize() == 2))) {\n      // Compute the gradient w.r.t. the input.\n      auto* dX = Output(no_bias_ ? BIAS_OR_INPUT_GRAD : INPUT_GRAD);\n      dX->ResizeLike(X);\n      CUDNN_ENFORCE(cudnnConvolutionForward(\n          state->cudnn_handle(),\n          cudnnTypeWrapper<T>::kOne(),\n          top_desc_,\n          dY.template data<T>(),\n          filter_desc_,\n          filter.template data<T>(),\n          conv_desc_,\n          algo_,\n          state->workspace().get(cudnn_ws_nbytes_),\n          cudnn_ws_nbytes_,\n          cudnnTypeWrapper<T>::kZero(),\n          bottom_desc_,\n          dX->template mutable_data<T>()));\n    }\n  });\n  return true;\n}\n\nREGISTER_CUDNN_OPERATOR(ConvTranspose, CudnnConvTransposeOp<float>);\nREGISTER_CUDNN_OPERATOR(\n    ConvTransposeGradient,\n    CudnnConvTransposeGradientOp<float>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/conv_transpose_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/conv_transpose_op.h\"\n#include \"caffe2/operators/conv_transpose_op_impl.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(ConvTranspose, ConvTransposeOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    ConvTransposeGradient,\n    ConvTransposeGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/conv_transpose_op_impl.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// conv_transpose_op_impl.h is the templated implementation of the\n// conv_transpose_op.h file.\n#ifndef CAFFE2_OPERATORS_CONV_TRANSPOSE_OP_IMPL_H_\n#define CAFFE2_OPERATORS_CONV_TRANSPOSE_OP_IMPL_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_op_shared.h\"\n#include \"caffe2/operators/conv_transpose_op.h\"\n#include \"caffe2/operators/conv_transpose_unpool_op_base.h\"\n#include \"caffe2/utils/math.h\"\n\nCAFFE2_DECLARE_bool(caffe2_force_shared_col_buffer);\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nbool ConvTransposeOp<T, Context>::RunOnDeviceWithOrderNCHW() {\n  const Tensor<Context>& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  Tensor<Context>* Y = Output(0);\n  const int N = X.dim32(0), M = X.dim32(1), H = X.dim32(2), W = X.dim32(3);\n  CAFFE_ENFORCE(filter.ndim() == 4, \"filter must be 4D tensor\");\n  CAFFE_ENFORCE(\n      filter.dim32(0) == M,\n      \"filter number must be equal to input channel number\");\n  const int C = filter.dim32(1);\n  CAFFE_ENFORCE(\n      filter.dim32(2) == this->kernel_h(),\n      \"filter height must be equal to kernel height\");\n  CAFFE_ENFORCE(\n      filter.dim32(3) == this->kernel_w(),\n      \"filter width must be equal to kernel width\");\n  ConvTransposeUnpoolBase<Context>::SetOutputSize(X, Y, C);\n\n  const int kernel_dim = C * this->kernel_h() * this->kernel_w();\n  const int input_image_size = H * W;\n  const int output_image_size = Y->dim32(2) * Y->dim32(3);\n\n#ifndef __ARM_NEON__\n  if (InputSize() == 3) {\n    auto& bias = Input(BIAS);\n    CAFFE_ENFORCE(bias.ndim() == 1, \"bias must be 1D tensor\");\n    CAFFE_ENFORCE(\n        bias.dim32(0) == C,\n        \"bias dimension must be equal to output channel number\");\n    if (bias_multiplier_.size() != output_image_size) {\n      bias_multiplier_.Resize(vector<TIndex>(1, output_image_size));\n      T* bm_data = bias_multiplier_.template mutable_data<T>();\n      math::Set<T, Context>(\n          output_image_size,\n          static_cast<T>(1),\n          bm_data,\n          &context_);\n    }\n  }\n#endif // !__ARM_NEON__\n\n  const T* Xdata = X.template data<T>();\n  const T* filter_data = filter.template data<T>();\n  T* Ydata = Y->template mutable_data<T>();\n\n  auto f = [&](Tensor<Context>* col_buffer) {\n    col_buffer->Resize(\n        vector<TIndex>{C, this->kernel_h(), this->kernel_w(), H, W});\n    T* col_buffer_data = col_buffer->template mutable_data<T>();\n    for (auto image_id = 0; image_id < N; ++image_id) {\n      // Weight term\n      math::Gemm<T, Context>(\n          CblasTrans,\n          CblasNoTrans,\n          kernel_dim,\n          input_image_size,\n          M,\n          1,\n          filter_data,\n          Xdata,\n          0,\n          col_buffer_data,\n          &context_);\n\n      // Col2im\n      math::Col2im<T, Context, StorageOrder::NCHW>(\n          col_buffer_data,\n          C,\n          Y->dim32(2),\n          Y->dim32(3),\n          this->kernel_h(),\n          this->kernel_w(),\n          1,\n          1,\n          this->pad_t(),\n          this->pad_l(),\n          this->pad_b(),\n          this->pad_r(),\n          this->stride_h(),\n          this->stride_w(),\n          Ydata,\n          &context_);\n\n      // Bias term\n      if (InputSize() == 3) {\n        const T* bias_data = Input(BIAS).template data<T>();\n#ifndef __ARM_NEON__\n        const T* bm_data = bias_multiplier_.template data<T>();\n        math::Gemm<T, Context>(\n            CblasNoTrans,\n            CblasNoTrans,\n            C,\n            output_image_size,\n            1,\n            1,\n            bias_data,\n            bm_data,\n            1,\n            Ydata,\n            &context_);\n#else\n        math::BiasCHW<T, Context>(\n            bias_data,\n            C,\n            output_image_size,\n            Ydata,\n            &context_);\n#endif // !__ARM_NEON__\n      }\n\n      Xdata += M * H * W;\n      Ydata += Y->size() / Y->dim32(0);\n    }\n  };\n  if (FLAGS_caffe2_force_shared_col_buffer || shared_buffer_) {\n    runWithSharedBuffer<Context>(ws_, f);\n  } else {\n    f(&col_buffer_);\n  }\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool ConvTransposeOp<T, Context>::RunOnDeviceWithOrderNHWC() {\n  const Tensor<Context>& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  Tensor<Context>* Y = Output(0);\n  const auto N = X.dim32(0), H = X.dim32(1), W = X.dim32(2), M = X.dim32(3);\n  CAFFE_ENFORCE(filter.ndim() == 4, \"filter must be 4D tensor\");\n  CAFFE_ENFORCE(\n      filter.dim32(0) == M,\n      \"filter number must be equal to input channel number\");\n  CAFFE_ENFORCE(\n      filter.dim32(1) == this->kernel_h(),\n      \"filter height must be equal to kernel height\");\n  CAFFE_ENFORCE(\n      filter.dim32(2) == this->kernel_w(),\n      \"filter width must be equal to kernel width\");\n  const int C = filter.dim32(3);\n  ConvTransposeUnpoolBase<Context>::SetOutputSize(X, Y, C);\n\n  const auto kernel_dim = C * this->kernel_h() * this->kernel_w();\n  const auto input_image_size = H * W;\n  const auto output_image_size = Y->dim32(1) * Y->dim32(2);\n\n  if (InputSize() == 3) {\n    auto& bias = Input(BIAS);\n    CAFFE_ENFORCE(bias.ndim() == 1, \"bias must be 1D tensor\");\n    CAFFE_ENFORCE(\n        bias.dim32(0) == C,\n        \"bias dimension must be equal to output channel number\");\n    if (bias_multiplier_.size() != output_image_size) {\n      bias_multiplier_.Resize(vector<TIndex>(1, output_image_size));\n      T* bm_data = bias_multiplier_.template mutable_data<T>();\n      math::Set<T, Context>(\n          output_image_size,\n          static_cast<T>(1),\n          bm_data,\n          &context_);\n    }\n  }\n  const T* Xdata = X.template data<T>();\n  const T* filter_data = filter.template data<T>();\n  T* Ydata = Y->template mutable_data<T>();\n\n  auto f = [&](Tensor<Context>* /*col_buffer*/) {\n    col_buffer_.Resize(\n        vector<TIndex>{H, W, this->kernel_h(), this->kernel_w(), C});\n    T* col_buffer_data = col_buffer_.template mutable_data<T>();\n    for (auto image_id = 0; image_id < N; ++image_id) {\n      // Weight term\n      math::Gemm<T, Context>(\n          CblasNoTrans,\n          CblasNoTrans,\n          input_image_size,\n          kernel_dim,\n          M,\n          1,\n          Xdata,\n          filter_data,\n          0,\n          col_buffer_data,\n          &context_);\n      // Col2im\n      math::Col2im<T, Context, StorageOrder::NHWC>(\n          col_buffer_data,\n          C,\n          Y->dim32(1),\n          Y->dim32(2),\n          this->kernel_h(),\n          this->kernel_w(),\n          1,\n          1,\n          this->pad_t(),\n          this->pad_l(),\n          this->pad_b(),\n          this->pad_r(),\n          this->stride_h(),\n          this->stride_w(),\n          Ydata,\n          &context_);\n      // Bias term\n      if (InputSize() == 3) {\n        const T* bm_data = bias_multiplier_.template data<T>();\n        const T* bias_data = Input(BIAS).template data<T>();\n        math::Gemm<T, Context>(\n            CblasNoTrans,\n            CblasNoTrans,\n            output_image_size,\n            C,\n            1,\n            1,\n            bm_data,\n            bias_data,\n            1,\n            Ydata,\n            &context_);\n      }\n      Xdata += M * H * W;\n      Ydata += Y->size() / Y->dim32(0);\n    }\n  };\n  if (FLAGS_caffe2_force_shared_col_buffer || shared_buffer_) {\n    runWithSharedBuffer<Context>(ws_, f);\n  } else {\n    f(&col_buffer_);\n  }\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool ConvTransposeGradientOp<T, Context>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  auto& dY = Input(OUTPUT_GRAD);\n  auto* dfilter = Output(FILTER_GRAD);\n  const int N = X.dim32(0), M = X.dim32(1), H = X.dim32(2), W = X.dim32(3);\n  // We only handle LegacyPadding::NOTSET case and ignore cases of\n  // LegacyPadding::VALID and LegacyPadding::SAME\n  // Thus, we don't need to manually compute padding values\n  // We simply use the values from the user\n  CAFFE_ENFORCE(filter.ndim() == 4);\n  const int C = filter.dim32(1);\n  CAFFE_ENFORCE(\n      filter.dim32(2) == this->kernel_h(),\n      \"filter height must be equal to kernel height\");\n  CAFFE_ENFORCE(\n      filter.dim32(3) == this->kernel_w(),\n      \"filter width must be equal to kernel width\");\n  dfilter->ResizeLike(filter);\n\n  const int kernel_dim = C * this->kernel_h() * this->kernel_w();\n  const int output_image_size = dY.dim32(2) * dY.dim32(3);\n  // The col buffer is stored in CHW order as well\n  col_buffer_.Resize(\n      vector<TIndex>{C, this->kernel_h(), this->kernel_w(), H, W});\n  if (!no_bias_) {\n    auto* dbias = Output(BIAS_OR_INPUT_GRAD);\n    dbias->Resize(C);\n    if (bias_multiplier_.size() != output_image_size) {\n      bias_multiplier_.Resize(1, output_image_size);\n      T* bm_data = bias_multiplier_.template mutable_data<T>();\n      math::Set<T, Context>(\n          output_image_size,\n          static_cast<T>(1),\n          bm_data,\n          &context_);\n    }\n  }\n  T* col_buffer_data = col_buffer_.template mutable_data<T>();\n  const T* Xdata = X.template data<T>();\n  const T* filter_data = filter.template data<T>();\n  const T* dYdata = dY.template data<T>();\n  T* dfilter_data = dfilter->template mutable_data<T>();\n  // Pre-setting the gradients to zero\n  math::Set<T, Context>(dfilter->size(), 0, dfilter_data, &context_);\n  if (!no_bias_) {\n    auto* dbias = Output(BIAS_OR_INPUT_GRAD);\n    T* dbias_data = dbias->template mutable_data<T>();\n    math::Set<T, Context>(dbias->size(), 0, dbias_data, &context_);\n  }\n  for (auto image_id = 0; image_id < N; ++image_id) {\n    // gradient w.r.t. filters. Im2col followed by Gemm\n    // Im2col.\n    math::Im2col<T, Context, StorageOrder::NCHW>(\n        dYdata,\n        C,\n        dY.dim32(2),\n        dY.dim32(3),\n        this->kernel_h(),\n        this->kernel_w(),\n        1,\n        1,\n        this->pad_t(),\n        this->pad_l(),\n        this->pad_b(),\n        this->pad_r(),\n        this->stride_h(),\n        this->stride_w(),\n        col_buffer_data,\n        &context_);\n    // Gemm\n    math::Gemm<T, Context>(\n        CblasNoTrans,\n        CblasTrans,\n        M,\n        kernel_dim,\n        H * W,\n        1,\n        Xdata,\n        col_buffer_data,\n        1,\n        dfilter_data,\n        &context_);\n    // gradient w.r.t. bias\n    if (!no_bias_) {\n      const T* bm_data = bias_multiplier_.template data<T>();\n      T* input_grad_data = Output(BIAS_OR_INPUT_GRAD)->template mutable_data<T>();\n      math::Gemm<T, Context>(\n          CblasNoTrans,\n          CblasNoTrans,\n          C,\n          1,\n          output_image_size,\n          1,\n          dYdata,\n          bm_data,\n          1,\n          input_grad_data,\n          &context_);\n    }\n    dYdata += dY.size() / dY.dim32(0);\n    Xdata += X.size() / X.dim32(0);\n  }\n  if (OutputSize() == 3 || (no_bias_ && (OutputSize() == 2))) {\n    // Compute gradients w.r.t. the input\n    // Since we have changed dYdata in the above loop, we will need to reset.\n    dYdata = dY.template data<T>();\n    auto* dX = Output(no_bias_ ? BIAS_OR_INPUT_GRAD : INPUT_GRAD);\n    dX->ResizeLike(X);\n    T* dXdata = dX->template mutable_data<T>();\n    for (auto image_id = 0; image_id < N; ++image_id) {\n      // Im2col.\n      // TODO(zyan3): Probably duplicate work as in gradient computation\n      // w.r.t filters\n      math::Im2col<T, Context, StorageOrder::NCHW>(\n          dYdata,\n          C,\n          dY.dim32(2),\n          dY.dim32(3),\n          this->kernel_h(),\n          this->kernel_w(),\n          1,\n          1,\n          this->pad_t(),\n          this->pad_l(),\n          this->pad_b(),\n          this->pad_r(),\n          this->stride_h(),\n          this->stride_w(),\n          col_buffer_data,\n          &context_);\n      // Gemm\n      math::Gemm<T, Context>(\n          CblasNoTrans,\n          CblasNoTrans,\n          M,\n          H * W,\n          kernel_dim,\n          1,\n          filter_data,\n          col_buffer_data,\n          0,\n          dXdata,\n          &context_);\n      dYdata += dY.size() / dY.dim32(0);\n      dXdata += X.size() / X.dim32(0);\n    }\n  }\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool ConvTransposeGradientOp<T, Context>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  auto& dY = Input(OUTPUT_GRAD);\n  auto* dfilter = Output(FILTER_GRAD);\n  const int N = X.dim32(0), H = X.dim32(1), W = X.dim32(2), M = X.dim32(3);\n  // We only handle LegacyPadding::NOTSET case and ignore cases of\n  // LegacyPadding::VALID and LegacyPadding::SAME\n  // Thus, we don't need to manually compute padding values\n  // We simply use the values from the user\n  CAFFE_ENFORCE(filter.ndim() == 4, \"filter must be 4D tensor\");\n  CAFFE_ENFORCE(\n      filter.dim32(1) == this->kernel_h(),\n      \"filter height must be equal to kernel height\");\n  CAFFE_ENFORCE(\n      filter.dim32(2) == this->kernel_w(),\n      \"filter width must be equal to kernel width\");\n  const int C = filter.dim32(3);\n  dfilter->ResizeLike(filter);\n\n  const int kernel_dim = C * this->kernel_h() * this->kernel_w();\n  const int output_image_size = dY.dim32(1) * dY.dim32(2);\n  // The col buffer is stored in HWC order as well\n  col_buffer_.Resize(\n      vector<TIndex>{H, W, this->kernel_h(), this->kernel_w(), C});\n  if (!no_bias_) {\n    auto* dbias = Output(BIAS_OR_INPUT_GRAD);\n    dbias->Resize(C);\n    if (bias_multiplier_.size() != output_image_size) {\n      bias_multiplier_.Resize(1, output_image_size);\n      T* bm_data = bias_multiplier_.template mutable_data<T>();\n      math::Set<T, Context>(\n          output_image_size,\n          static_cast<T>(1),\n          bm_data,\n          &context_);\n    }\n  }\n  T* col_buffer_data = col_buffer_.template mutable_data<T>();\n  const T* Xdata = X.template data<T>();\n  const T* filter_data = filter.template data<T>();\n  const T* dYdata = dY.template data<T>();\n  T* dfilter_data = dfilter->template mutable_data<T>();\n  // Pre-setting the gradients to zero\n  math::Set<T, Context>(dfilter->size(), 0, dfilter_data, &context_);\n  if (!no_bias_) {\n    auto* dbias = Output(BIAS_OR_INPUT_GRAD);\n    T* dbias_data = dbias->template mutable_data<T>();\n    math::Set<T, Context>(dbias->size(), 0, dbias_data, &context_);\n  }\n  for (auto image_id = 0; image_id < N; ++image_id) {\n    // gradient w.r.t. filters. Im2col followed by Gemm\n    // Im2col.\n    math::Im2col<T, Context, StorageOrder::NHWC>(\n        dYdata,\n        C,\n        dY.dim32(1),\n        dY.dim32(2),\n        this->kernel_h(),\n        this->kernel_w(),\n        1,\n        1,\n        this->pad_t(),\n        this->pad_l(),\n        this->pad_b(),\n        this->pad_r(),\n        this->stride_h(),\n        this->stride_w(),\n        col_buffer_data,\n        &context_);\n    // Gemm\n    math::Gemm<T, Context>(\n        CblasTrans,\n        CblasNoTrans,\n        M,\n        kernel_dim,\n        H * W,\n        1,\n        Xdata,\n        col_buffer_data,\n        1,\n        dfilter_data,\n        &context_);\n    // gradients w.r.t. bias\n    if (!no_bias_) {\n      const T* bm_data = bias_multiplier_.template data<T>();\n      T* input_grad_data = Output(BIAS_OR_INPUT_GRAD)->template mutable_data<T>();\n      math::Gemm<T, Context>(\n          CblasTrans,\n          CblasNoTrans,\n          C,\n          1,\n          output_image_size,\n          1,\n          dYdata,\n          bm_data,\n          1,\n          input_grad_data,\n          &context_);\n    }\n    dYdata += dY.size() / dY.dim32(0);\n    Xdata += X.size() / X.dim32(0);\n  }\n  if (OutputSize() == 3 || (no_bias_ && (OutputSize() == 2))) {\n    // Compute gradients w.r.t. the input\n    // Since we have changed dYdata in the above loop, we will need to reset.\n    dYdata = dY.template data<T>();\n    auto* dX = Output(no_bias_ ? BIAS_OR_INPUT_GRAD : INPUT_GRAD);\n    dX->ResizeLike(X);\n    T* dXdata = dX->template mutable_data<T>();\n    for (auto image_id = 0; image_id < N; ++image_id) {\n      // Im2col.\n      // TODO(zyan3): Probably duplicate work as in gradient computation\n      // w.r.t filters\n      math::Im2col<T, Context, StorageOrder::NHWC>(\n          dYdata,\n          C,\n          dY.dim32(1),\n          dY.dim32(2),\n          this->kernel_h(),\n          this->kernel_w(),\n          1,\n          1,\n          this->pad_t(),\n          this->pad_l(),\n          this->pad_b(),\n          this->pad_r(),\n          this->stride_h(),\n          this->stride_w(),\n          col_buffer_data,\n          &context_);\n      // Gemm\n      math::Gemm<T, Context>(\n          CblasNoTrans,\n          CblasTrans,\n          H * W,\n          M,\n          kernel_dim,\n          1,\n          col_buffer_data,\n          filter_data,\n          0,\n          dXdata,\n          &context_);\n      dYdata += dY.size() / dY.dim32(0);\n      dXdata += X.size() / X.dim32(0);\n    }\n  }\n  return true;\n}\n\n} // namespace caffe2\n#endif // CAFFE2_OPERATORS_CONV_TRANSPOSE_OP_IMPL_H_\n"
  },
  {
    "path": "caffe2/operators/conv_transpose_op_mobile.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/conv_transpose_op_mobile.h\"\n#include \"caffe2/operators/conv_transpose_op_mobile_impl.h\"\n\nnamespace caffe2 {\n\n#ifndef CAFFE2_MOBILE\n#error \"mobile build state not defined\"\n#endif\n\n#if CAFFE2_MOBILE\n// mobile-only implementation (tiled + vectorized + multithreaded)\nREGISTER_CPU_OPERATOR_WITH_ENGINE(\n    ConvTranspose,\n    MOBILE,\n    ConvTransposeMobileOp<float, CPUContext>);\n#endif\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/conv_transpose_op_mobile.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CONV_TRANSPOSE_MOBILE_OP_H_\n#define CAFFE2_OPERATORS_CONV_TRANSPOSE_MOBILE_OP_H_\n\n#include \"caffe2/core/common.h\"\n\n#ifndef CAFFE2_MOBILE\n#error \"mobile build state not defined\"\n#endif\n\n#if CAFFE2_MOBILE\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_transpose_unpool_op_base.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass ConvTransposeMobileOp final : public ConvTransposeUnpoolBase<Context> {\n public:\n  USE_CONV_TRANSPOSE_UNPOOL_BASE_FUNCTIONS(Context);\n  ConvTransposeMobileOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvTransposeUnpoolBase<Context>(operator_def, ws) {\n    OPERATOR_NEEDS_FEATURE(order_ == StorageOrder::NCHW, \"Only NCHW order is supported right now.\");\n    OPERATOR_NEEDS_FEATURE(\n        this->pad_l() == 0, \"operator does not handle row width padding\");\n    OPERATOR_NEEDS_FEATURE(\n        this->pad_r() == 0, \"operator does not handle row width padding\");\n    OPERATOR_NEEDS_FEATURE(this->stride_w() <= 4, \"stride width must be <= 4\");\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n private:\n  // We store a numThreasds per-worker  tiles of Y, and numThreads per-worker threadBuffer for the\n  // gemm output, laid out in that order.\n  TensorCPU threadBuffer_;\n\n  // Input: X, W, b\n  // Output: Y\n  INPUT_TAGS(INPUT, FILTER, BIAS);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_MOBILE\n\n#endif // CAFFE2_OPERATORS_CONV_TRANSPOSE_MOBILE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/conv_transpose_op_mobile_impl.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// conv_transpose_op_impl.h is the templated implementation of the\n// conv_transpose_op.h file.\n#ifndef CAFFE2_OPERATORS_CONV_TRANSPOSE_MOBILE_OP_IMPL_H_\n#define CAFFE2_OPERATORS_CONV_TRANSPOSE_MOBILE_OP_IMPL_H_\n\n#include \"caffe2/core/common.h\"\n\n#ifndef CAFFE2_MOBILE\n#error \"mobile build state not defined\"\n#endif\n\n#if CAFFE2_MOBILE\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/operators/conv_op_shared.h\"\n#include \"caffe2/operators/conv_transpose_op_mobile.h\"\n#include \"caffe2/utils/cpu_neon.h\"\n#include \"caffe2/utils/fixed_divisor.h\"\n#include \"caffe2/utils/math.h\"\n\nCAFFE2_DECLARE_bool(caffe2_force_shared_col_buffer);\n\nnamespace caffe2 {\n\ntemplate <typename T, typename Context>\nvoid runTileContiguous(\n    int tileId,\n    int N,\n    int M,\n    int H,\n    int W,\n    int outputH,\n    int outputW,\n    int C,\n    int kernelH,\n    int kernelW,\n    int strideH,\n    int strideW,\n    int padT,\n    const T* filterData,\n    const T* Xdata,\n    T* colBufferData,\n    T* Ydata,\n    Context* context) {\n  // The tile size is exactly the length of a single row\n  int tileSize = W;\n\n  auto kernelDataSize = C * kernelH * kernelW;\n  auto currentTileStart = tileSize * tileId;\n\n  // gemm tile\n  math::GemmEx<T, Context>(\n      CblasTrans,\n      CblasNoTrans,\n      kernelDataSize,\n      tileSize,\n      M,\n      1,\n      filterData,\n      kernelDataSize,\n      Xdata + currentTileStart,\n      H * W,\n      0,\n      colBufferData,\n      tileSize,\n      context);\n\n  // col2im tile\n  // We assume that there is no padding in the columns (padL and padR\n  // == 0).\n  // FIXME: it is actually possible for us to handle padding, figure\n  // out how to adjust the bounds\n\n  // We write into Y in a de-interleaved fashion; in other words,\n  // every column (mod strideW) == 0 together in one block,\n  // every column (mod strideW) == 1 in another,\n  // ... and so on.\n  int colBlockSize = (W + kernelW / strideW);\n  int numColBlocks = strideW;\n\n  for (int c = 0; c < kernelDataSize; ++c) {\n    int w_offset = c % kernelW;\n    int h_offset = (c / kernelW) % kernelH;\n    int c_im = c / kernelH / kernelW;\n\n    // Each row is a separate tile that we handle. First determine the\n    // row into which we are writing the output.\n    // We can properly handle padding for the rows.\n    int rowY = tileId * strideH - padT + h_offset;\n\n    // If this row is out of bounds, then skip it\n    if (!math::is_a_ge_zero_and_a_lt_b(rowY, outputH)) {\n      continue;\n    }\n\n    // FIXME: we don't actually handle a dynamic padL > 0\n    constexpr int kPadL = 0;\n    int colOffsetStart = -kPadL + w_offset;\n    int colBlockY = colOffsetStart % strideW;\n\n    // However, within a block we may not start writing at offset\n    // 0. The offset at which we begin writing is determined by\n    // colOffsetStart\n    int colWithinBlockOffsetY = colOffsetStart / strideW;\n\n    // So, this is where we begin reading/writing in Y\n    int colY = colBlockY * colBlockSize + colWithinBlockOffsetY;\n\n    // This is the complete offset into Y from the start\n    // Each row has strideW blocks of size colBlockSize\n    int offsetY = rowY * colBlockSize * numColBlocks + colY;\n\n    T* colBufferPointer = colBufferData + c * tileSize;\n    T* yPointer =\n        Ydata + c_im * outputH * (colBlockSize * numColBlocks) + offsetY;\n\n    int b = 0;\n#ifdef __ARM_NEON__\n    // We vectorize the loop within the row\n    {\n      constexpr int kUnroll = (sizeof(float32x4_t) / sizeof(float)) * 4;\n      int limit = (tileSize / kUnroll) * kUnroll;\n\n      for (; b < limit; b += kUnroll) {\n        float32x4_t cb0 = vld1q_f32(colBufferPointer + 0);\n        float32x4_t cb1 = vld1q_f32(colBufferPointer + 4);\n        float32x4_t cb2 = vld1q_f32(colBufferPointer + 8);\n        float32x4_t cb3 = vld1q_f32(colBufferPointer + 12);\n\n        float32x4_t y0 = vld1q_f32(yPointer + 0);\n        float32x4_t y1 = vld1q_f32(yPointer + 4);\n        float32x4_t y2 = vld1q_f32(yPointer + 8);\n        float32x4_t y3 = vld1q_f32(yPointer + 12);\n\n        y0 = vaddq_f32(y0, cb0);\n        y1 = vaddq_f32(y1, cb1);\n        y2 = vaddq_f32(y2, cb2);\n        y3 = vaddq_f32(y3, cb3);\n\n        vst1q_f32(yPointer + 0, y0);\n        vst1q_f32(yPointer + 4, y1);\n        vst1q_f32(yPointer + 8, y2);\n        vst1q_f32(yPointer + 12, y3);\n\n        colBufferPointer += kUnroll;\n        yPointer += kUnroll;\n      }\n    }\n\n    {\n      constexpr int kUnroll = (sizeof(float32x4_t) / sizeof(float));\n      int limit = (tileSize / kUnroll) * kUnroll;\n\n      for (; b < limit; b += kUnroll) {\n        float32x4_t cb0 = vld1q_f32(colBufferPointer);\n        float32x4_t y0 = vld1q_f32(yPointer);\n\n        y0 = vaddq_f32(y0, cb0);\n\n        vst1q_f32(yPointer, y0);\n\n        colBufferPointer += kUnroll;\n        yPointer += kUnroll;\n      }\n    }\n#endif\n\n    // Handle un-vectorizable epilogue\n    for (; b < tileSize; ++b) {\n      *yPointer += *colBufferPointer;\n      ++yPointer;\n      ++colBufferPointer;\n    }\n  }\n}\n\ntemplate <typename T, int N>\nstruct StoreInterleaved {};\n\ntemplate <>\nstruct StoreInterleaved<float, 1> {\n#ifdef __ARM_NEON__\n  inline static void store(float* p, float32x4_t v[1]) {\n    vst1q_f32(p, v[0]);\n  }\n#endif\n\n  inline static void store(float* p, float v[1]) {\n    p[0] = v[0];\n  }\n};\n\ntemplate <>\nstruct StoreInterleaved<float, 2> {\n#ifdef __ARM_NEON__\n  inline static void store(float* p, float32x4_t v[2]) {\n    float32x4x2_t x = {{v[0], v[1]}};\n    vst2q_f32(p, x);\n  }\n#endif\n\n  inline static void store(float* p, float v[2]) {\n    p[0] = v[0];\n    p[1] = v[1];\n  }\n};\n\ntemplate <>\nstruct StoreInterleaved<float, 3> {\n#ifdef __ARM_NEON__\n  inline static void store(float* p, float32x4_t v[3]) {\n    float32x4x3_t x = {{v[0], v[1], v[2]}};\n    vst3q_f32(p, x);\n  }\n#endif\n\n  inline static void store(float* p, float v[3]) {\n    p[0] = v[0];\n    p[1] = v[1];\n    p[2] = v[2];\n  }\n};\n\ntemplate <>\nstruct StoreInterleaved<float, 4> {\n#ifdef __ARM_NEON__\n  inline static void store(float* p, float32x4_t v[4]) {\n    float32x4x4_t x = {{v[0], v[1], v[2], v[3]}};\n    vst4q_f32(p, x);\n  }\n#endif\n\n  inline static void store(float* p, float v[4]) {\n    p[0] = v[0];\n    p[1] = v[1];\n    p[2] = v[2];\n    p[3] = v[3];\n  }\n};\n\ntemplate <int kStrideW>\nvoid reinterleaveRows(\n    const float* src,\n    const float* bias,\n    int c,\n    int h,\n    float* dst,\n    int outputC,\n    int outputH,\n    int outputW,\n    int inputW,\n    int kernelW,\n    int strideW,\n    int adjH) {\n  // Each row in src is of the form:\n  // [w mod strideW == 0 elements]...[w mod strideW == strideW - 1\n  // elements]\n  // We need to re-interleave the values and write them in the output\n  int colBlockSize = inputW + kernelW / kStrideW;\n  int noAdjOutputW = (inputW - 1) * kStrideW + kernelW;\n\n  int point = c * outputH + h;\n  src += point * colBlockSize * kStrideW;\n  dst += point * outputW;\n\n  float b = bias ? bias[c] : 0;\n#ifdef __ARM_NEON__\n  float32x4_t biasV = vdupq_n_f32(b);\n#endif\n\n  int w = 0;\n#ifdef __ARM_NEON__\n  constexpr int kUnroll = (sizeof(float32x4_t) / sizeof(float)) * 2;\n  int limit = ((inputW - 1) / kUnroll) * kUnroll;\n\n  for (; w < limit; w += kUnroll) {\n    // We need to interleave in terms of kStrideW units\n    float32x4_t v0[kStrideW];\n    float32x4_t v1[kStrideW];\n\n    for (int i = 0; i < kStrideW; ++i) {\n      v0[i] = vld1q_f32(src + i * colBlockSize);\n      v1[i] = vld1q_f32(src + i * colBlockSize + 4);\n    }\n\n    // add per-channel bias\n    for (int i = 0; i < kStrideW; ++i) {\n      v0[i] = vaddq_f32(v0[i], biasV);\n      v1[i] = vaddq_f32(v1[i], biasV);\n    }\n\n    // Write interleaved into the output\n    StoreInterleaved<float, kStrideW>::store(dst + 0 * kStrideW, v0);\n    StoreInterleaved<float, kStrideW>::store(dst + 4 * kStrideW, v1);\n\n    src += kUnroll;\n    dst += kUnroll * kStrideW;\n  }\n#endif\n\n  // Handle non-vectorizable remainder\n  for (; w < inputW - 1; ++w) {\n    float v[kStrideW];\n\n    for (int i = 0; i < kStrideW; ++i) {\n      v[i] = src[i * colBlockSize];\n    }\n\n    // add per-channel bias\n    for (int i = 0; i < kStrideW; ++i) {\n      v[i] += b;\n    }\n\n    // Write interleaved into the output\n    StoreInterleaved<float, kStrideW>::store(dst, v);\n\n    src += 1;\n    dst += kStrideW;\n  }\n\n  // We have handled 0 .. (inputW - 1) * stride inclusive so far.\n  // Handle the remainder\n  int outputPoint = (inputW - 1) * kStrideW;\n  int block = 0;\n\n  // Output width may include adjustment into which we don't\n  // write; ignore it\n  while (outputPoint < noAdjOutputW) {\n    float v = src[block * colBlockSize];\n    dst[0] = v + b;\n    ++outputPoint;\n    dst += 1;\n\n    ++block;\n    if (block >= kStrideW) {\n      block = 0;\n      src += 1;\n    }\n  }\n\n  // Remainder of the buffer comprised of just the `adj` must have\n  // bias added\n  for (; outputPoint < outputW; ++outputPoint) {\n    dst[0] = b;\n    dst += 1;\n  }\n}\n\ntemplate <int N, typename T, typename Context>\nvoid reinterleaveMultithreaded(\n    const T* y0,\n    const T* bias_data,\n    T* y,\n    int outputC,\n    int outputH,\n    int outputW,\n    int inputW,\n    int kernelW,\n    int strideW,\n    int adjH,\n    ThreadPool* pool) {\n  // # channels times height\n  size_t totalTiles = (size_t)outputC * outputH;\n  FixedDivisor<int> divOutputH(outputH);\n\n#define REINTERLEAVE(N)  \\\n  do {                   \\\n    reinterleaveRows<N>( \\\n        y0,              \\\n        bias_data,       \\\n        c,               \\\n        h,               \\\n        y,               \\\n        outputC,         \\\n        outputH,         \\\n        outputW,         \\\n        inputW,          \\\n        kernelW,         \\\n        strideW,         \\\n        adjH);           \\\n  } while (false)\n\n  std::function<void(int, size_t)> fnReinterleave = [&](int threadId,\n                                                        size_t tileId) {\n    int h;\n    int c;\n    divOutputH.divMod((int)tileId, c, h);\n\n    REINTERLEAVE(N);\n  };\n\n#undef REINTERLEAVE\n\n  pool->run(fnReinterleave, totalTiles);\n}\n\n#ifdef __ARM_NEON__\ntemplate <int N>\nstruct SumMultiple {\n  static void sumInto(float* acc, float** toSum, size_t size);\n};\n\ntemplate <>\nstruct SumMultiple<1> {\n  static void sumInto(float* acc, float** toSum, size_t size) {\n    constexpr int kUnroll = (sizeof(float32x4_t) / sizeof(float));\n    int limit = (size / kUnroll) * kUnroll;\n\n    auto toSum0 = toSum[0];\n\n    size_t i = 0;\n    for (; i < limit; i += kUnroll) {\n      float32x4_t v0 = vld1q_f32_aligned(acc + i);\n      float32x4_t v1 = vld1q_f32_aligned(toSum0 + i);\n\n      v0 = vaddq_f32(v0, v1);\n\n      vst1q_f32_aligned(acc + i, v0);\n    }\n\n    for (; i < size; ++i) {\n      float v0 = acc[i];\n      float v1 = toSum0[i];\n\n      v0 += v1;\n\n      acc[i] = v0;\n    }\n  }\n};\n\ntemplate <>\nstruct SumMultiple<2> {\n  static void sumInto(float* acc, float** toSum, size_t size) {\n    constexpr int kUnroll = (sizeof(float32x4_t) / sizeof(float));\n    int limit = (size / kUnroll) * kUnroll;\n\n    auto toSum0 = toSum[0];\n    auto toSum1 = toSum[1];\n\n    size_t i = 0;\n    for (; i < limit; i += kUnroll) {\n      float32x4_t v0 = vld1q_f32_aligned(acc + i);\n      float32x4_t v1 = vld1q_f32_aligned(toSum0 + i);\n      float32x4_t v2 = vld1q_f32_aligned(toSum1 + i);\n\n      v0 = vaddq_f32(v0, v1);\n      v0 = vaddq_f32(v0, v2);\n\n      vst1q_f32_aligned(acc + i, v0);\n    }\n\n    for (; i < size; ++i) {\n      float v0 = acc[i];\n      float v1 = toSum0[i];\n      float v2 = toSum1[i];\n\n      v0 += v1;\n      v0 += v2;\n\n      acc[i] = v0;\n    }\n  }\n};\n\ntemplate <>\nstruct SumMultiple<3> {\n  static void sumInto(float* acc, float** toSum, size_t size) {\n    constexpr int kUnroll = (sizeof(float32x4_t) / sizeof(float));\n    int limit = (size / kUnroll) * kUnroll;\n\n    auto toSum0 = toSum[0];\n    auto toSum1 = toSum[1];\n    auto toSum2 = toSum[2];\n\n    size_t i = 0;\n    for (; i < limit; i += kUnroll) {\n      float32x4_t v0 = vld1q_f32_aligned(acc + i);\n      float32x4_t v1 = vld1q_f32_aligned(toSum0 + i);\n      float32x4_t v2 = vld1q_f32_aligned(toSum1 + i);\n      float32x4_t v3 = vld1q_f32_aligned(toSum2 + i);\n\n      v0 = vaddq_f32(v0, v1);\n      v2 = vaddq_f32(v2, v3);\n      v0 = vaddq_f32(v0, v2);\n\n      vst1q_f32_aligned(acc + i, v0);\n    }\n\n    for (; i < size; ++i) {\n      float v0 = acc[i];\n      float v1 = toSum0[i];\n      float v2 = toSum1[i];\n      float v3 = toSum2[i];\n\n      v0 += v1;\n      v2 += v3;\n      v0 += v2;\n\n      acc[i] = v0;\n    }\n  }\n};\n#endif\n\n// Performs acc[i] += sum_j toSum_j[i] pointwise\nvoid sumInto(float* acc, std::vector<float*>& toSum, size_t size) {\n#ifdef __ARM_NEON__\n  if (toSum.size() == 1) {\n    SumMultiple<1>::sumInto(acc, toSum.data(), size);\n    return;\n  } else if (toSum.size() == 2) {\n    SumMultiple<2>::sumInto(acc, toSum.data(), size);\n    return;\n  } else if (toSum.size() == 3) {\n    SumMultiple<3>::sumInto(acc, toSum.data(), size);\n    return;\n  }\n#endif\n\n  // Otherwise, use fallback implementation\n  EigenVectorArrayMap<float> accT(acc, size);\n\n  for (auto p : toSum) {\n    accT += ConstEigenVectorArrayMap<float>(p, size);\n  }\n}\n\ntemplate <typename T, class Context>\nbool ConvTransposeMobileOp<T, Context>::RunOnDeviceWithOrderNCHW() {\n  const Tensor<Context>& X = Input(INPUT);\n  auto& filter = Input(FILTER);\n  Tensor<Context>* Y = Output(0);\n  const int N = X.dim32(0), M = X.dim32(1), H = X.dim32(2), W = X.dim32(3);\n  CAFFE_ENFORCE(filter.ndim() == 4, \"filter must be 4D tensor\");\n  CAFFE_ENFORCE(\n      filter.dim32(0) == M,\n      \"filter number must be equal to input channel number\");\n  const int C = filter.dim32(1);\n  CAFFE_ENFORCE(\n      filter.dim32(2) == this->kernel_h(),\n      \"filter height must be equal to kernel height\");\n  CAFFE_ENFORCE(\n      filter.dim32(3) == this->kernel_w(),\n      \"filter width must be equal to kernel width\");\n  if (InputSize() == 3) {\n    auto& bias = Input(BIAS);\n    CAFFE_ENFORCE(bias.ndim() == 1, \"bias must be 1D tensor\");\n    CAFFE_ENFORCE(\n        bias.dim32(0) == C,\n        \"bias dimension must be equal to output channel number\");\n  }\n\n  ConvTransposeUnpoolBase<Context>::SetOutputSize(X, Y, C);\n\n  const int outputH = Y->dim32(2);\n  const int outputW = Y->dim32(3);\n  const int outputPlaneSize = outputH * outputW;\n  const int outputBatchElementSize = Y->dim32(1) * outputPlaneSize;\n\n  auto Xdata = X.template data<T>();\n  auto Ydata = Y->template mutable_data<T>();\n\n  auto pool = ws_->GetThreadPool();\n  auto numThreads = pool->getNumThreads();\n\n  // Initialize per-thread buffers for output\n  // The main thread will write directly into the output Y, we just\n  // need buffers for the worker threads\n  size_t colBlockSize = W + this->kernel_w() / this->stride_w();\n  size_t threadYBufferSize = C * outputH * colBlockSize * this->stride_w();\n  // Require 16 byte alignment, so 4-element alignment as these are floats.\n  size_t threadYBufferSizeAligned =\n      ((C * outputH * colBlockSize * this->stride_w() + 3) / 4) * 4;\n  size_t threadColBufferSize = C * this->kernel_h() * this->kernel_w() * W;\n\n  // Work around GCC 4.9 bug when this is declared inside the inner lambda.\n  auto runLocalTile = [&](TensorCPU* threadBuffer,\n                          int threadId,\n                          size_t tileId) {\n    auto localYData = threadBuffer->template mutable_data<T>() +\n        threadId * threadYBufferSizeAligned;\n\n    auto localColBufferData = threadBuffer->template mutable_data<T>() +\n        numThreads * threadYBufferSizeAligned + threadId * threadColBufferSize;\n\n    runTileContiguous<T, Context>(\n        tileId,\n        N,\n        M,\n        H,\n        W,\n        outputH,\n        outputW,\n        C,\n        this->kernel_h(),\n        this->kernel_w(),\n        this->stride_h(),\n        this->stride_w(),\n        this->pad_t(),\n        filter.template data<T>(),\n        Xdata,\n        localColBufferData,\n        localYData,\n        &context_);\n  };\n\n  auto f = [&](Tensor<Context>* threadBuffer) {\n    threadBuffer->Resize(\n        numThreads * threadYBufferSizeAligned +\n        numThreads * threadColBufferSize);\n    // Group together thread buffers for accumulation\n    std::vector<T*> toSum(numThreads - 1);\n    for (int i = 1; i < numThreads; ++i) {\n      toSum[i - 1] = threadBuffer->template mutable_data<T>() +\n          i * threadYBufferSizeAligned;\n    }\n\n    for (auto image_id = 0; image_id < N; ++image_id) {\n      // Each time through, we have to reset all per-thread output\n      // buffers, since the output buffer is only per-batch element\n      // The column buffers are overwritten by the matrix multiplication\n      // each time, so we need not clear them out each round\n      math::Set<T, Context>(\n          numThreads * threadYBufferSizeAligned,\n          0,\n          threadBuffer->template mutable_data<T>(),\n          &context_);\n\n      // Run tiled gemm and col2im in our threadpool; all of these tiles\n      // are guaranteed to be full tiles\n      // Each tile handles a single row of the input\n      pool->run(\n          [&](int threadId, int tileId) {\n            runLocalTile(threadBuffer, threadId, tileId);\n          },\n          H);\n\n      // We need to accumulate the per-thread results into the output\n      // Y; the first worker thread (main thread) already produced its\n      // results in Y\n      sumInto(\n          threadBuffer->template mutable_data<T>(), toSum, threadYBufferSize);\n\n// y0 now contains the final output, but it is in deinterleaved\n// form. We have to re-interleave it to produce the final form in Y\n// This operation also handles adding the per-channel bias.\n#define REINTERLEAVE(N)                                              \\\n  do {                                                               \\\n    reinterleaveMultithreaded<N, T, Context>(                        \\\n        threadBuffer->template mutable_data<T>(),                    \\\n        InputSize() == 3 ? Input(BIAS).template data<T>() : nullptr, \\\n        Ydata,                                                       \\\n        Y->dim32(1),                                                 \\\n        Y->dim32(2),                                                 \\\n        Y->dim32(3),                                                 \\\n        W,                                                           \\\n        this->kernel_w(),                                            \\\n        this->stride_w(),                                            \\\n        this->adj_h(),                                               \\\n        pool);                                                       \\\n  } while (false)\n\n      if (this->stride_w() == 1) {\n        REINTERLEAVE(1);\n      } else if (this->stride_w() == 2) {\n        REINTERLEAVE(2);\n      } else if (this->stride_w() == 3) {\n        REINTERLEAVE(3);\n      } else if (this->stride_w() == 4) {\n        REINTERLEAVE(4);\n      }\n\n#undef REINTERLEAVE\n\n      Xdata += M * H * W;\n      Ydata += Y->size() / Y->dim32(0);\n    }\n  };\n  if (FLAGS_caffe2_force_shared_col_buffer || shared_buffer_) {\n    runWithSharedBuffer<Context>(ws_, f);\n  } else {\n    f(&threadBuffer_);\n  }\n\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool ConvTransposeMobileOp<T, Context>::RunOnDeviceWithOrderNHWC() {\n  CAFFE_THROW(\"Not implemented.\");\n}\n\n} // namespace caffe2\n\n#endif // CAFFE2_MOBILE\n\n#endif // CAFFE2_OPERATORS_CONV_TRANSPOSE_MOBILE_OP_IMPL_H_\n"
  },
  {
    "path": "caffe2/operators/conv_transpose_op_mobile_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\n#include \"gtest/gtest.h\"\n#include <cmath>\n#include <random>\n\nnamespace caffe2 {\n\nvoid AddConstInput(const vector<TIndex>& shape,\n                   const float value,\n                   const string& name,\n                   Workspace* ws) {\n  DeviceOption option;\n  CPUContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(shape);\n  math::Set<float, CPUContext>(tensor->size(), value,\n                               tensor->mutable_data<float>(),\n                               &context);\n}\n\nvoid AddNoiseInput(const vector<TIndex>& shape,\n                   const string& name,\n                   Workspace* ws) {\n  DeviceOption option;\n  CPUContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(shape);\n\n  math::RandGaussian<float, CPUContext>(\n    tensor->size(),\n    0.0f, 10.0f,\n    tensor->mutable_data<float>(),\n    &context);\n}\n\ninline float relativeError(float a, float b) {\n  return std::abs(a - b) / (0.5f * (std::abs(a) + std::abs(b)));\n}\n\nvoid compare(int N, int inputC, int H, int W,\n             int outputC,\n             int kernelH, int kernelW, int strideH, int strideW,\n             int padT, int padL, int padB, int padR,\n             int adjH, int adjW,\n             float maxRelErr, float absErrForRelErrFailure) {\n  LOG(INFO) <<\n    \"running N \" << N << \" inputC \" << inputC << \" H \" << H << \" W \" << W <<\n    \" outputC \" << outputC <<\n    \" kernelH \" << kernelH << \" kernelW \" << kernelW <<\n    \" strideH \" << strideH << \" strideW \" << strideW <<\n    \" padT \" << padT << \" padL \" << padL <<\n    \" padB \" << padB << \" padR \" << padR <<\n    \" adjH \" << adjH << \" adjW \" << adjW;\n\n  Workspace ws;\n\n  OperatorDef def1;\n  def1.set_name(\"test\");\n  def1.set_type(\"ConvTranspose\");\n  def1.set_engine(\"MOBILE\");\n  def1.add_input(\"X\");\n  def1.add_input(\"W\");\n  def1.add_input(\"B\");\n  def1.add_output(\"Y1\");\n\n  def1.add_arg()->CopyFrom(MakeArgument(\"kernel_h\", kernelH));\n  def1.add_arg()->CopyFrom(MakeArgument(\"kernel_w\", kernelW));\n  def1.add_arg()->CopyFrom(MakeArgument(\"stride_h\", strideH));\n  def1.add_arg()->CopyFrom(MakeArgument(\"stride_w\", strideW));\n  def1.add_arg()->CopyFrom(MakeArgument(\"pad_t\", padT));\n  def1.add_arg()->CopyFrom(MakeArgument(\"pad_l\", padL));\n  def1.add_arg()->CopyFrom(MakeArgument(\"pad_b\", padB));\n  def1.add_arg()->CopyFrom(MakeArgument(\"pad_r\", padR));\n  def1.add_arg()->CopyFrom(MakeArgument(\"adj_h\", adjH));\n  def1.add_arg()->CopyFrom(MakeArgument(\"adj_w\", adjW));\n\n  AddNoiseInput(vector<TIndex>{N, inputC, H, W}, \"X\", &ws);\n  AddNoiseInput(vector<TIndex>{inputC, outputC, kernelH, kernelW}, \"W\", &ws);\n  AddNoiseInput(vector<TIndex>{outputC}, \"B\", &ws);\n\n  unique_ptr<OperatorBase> op1(CreateOperator(def1, &ws));\n  EXPECT_NE(nullptr, op1.get());\n\n  OperatorDef def2;\n  def2.set_name(\"test\");\n  def2.set_type(\"ConvTranspose\");\n  def2.add_input(\"X\");\n  def2.add_input(\"W\");\n  def2.add_input(\"B\");\n  def2.add_output(\"Y2\");\n\n  def2.add_arg()->CopyFrom(MakeArgument(\"kernel_h\", kernelH));\n  def2.add_arg()->CopyFrom(MakeArgument(\"kernel_w\", kernelW));\n  def2.add_arg()->CopyFrom(MakeArgument(\"stride_h\", strideH));\n  def2.add_arg()->CopyFrom(MakeArgument(\"stride_w\", strideW));\n  def2.add_arg()->CopyFrom(MakeArgument(\"pad_t\", padT));\n  def2.add_arg()->CopyFrom(MakeArgument(\"pad_l\", padL));\n  def2.add_arg()->CopyFrom(MakeArgument(\"pad_b\", padB));\n  def2.add_arg()->CopyFrom(MakeArgument(\"pad_r\", padR));\n  def2.add_arg()->CopyFrom(MakeArgument(\"adj_h\", adjH));\n  def2.add_arg()->CopyFrom(MakeArgument(\"adj_w\", adjW));\n\n  unique_ptr<OperatorBase> op2(CreateOperator(def2, &ws));\n  EXPECT_NE(nullptr, op2.get());\n\n  EXPECT_TRUE(op1->Run());\n  Blob* Y1blob = ws.GetBlob(\"Y1\");\n  EXPECT_NE(nullptr, Y1blob);\n  auto& Y1 = Y1blob->Get<TensorCPU>();\n\n  EXPECT_TRUE(op2->Run());\n  Blob* Y2blob = ws.GetBlob(\"Y2\");\n  EXPECT_NE(nullptr, Y2blob);\n  auto& Y2 = Y2blob->Get<TensorCPU>();\n\n  // Compare all output points\n  for (int n = 0; n < Y1.dim32(0); ++n) {\n    for (int c = 0; c < Y1.dim32(1); ++c) {\n      for (int h = 0; h < Y1.dim32(2); ++h) {\n        for (int w = 0; w < Y1.dim32(3); ++w) {\n          int offset =\n            n * Y1.dim32(1) * Y1.dim32(2) * Y1.dim32(3) +\n            c * Y1.dim32(2) * Y1.dim32(3) +\n            h * Y1.dim32(3) +\n            w;\n\n          auto v1 = Y1.data<float>()[offset];\n          auto v2 = Y2.data<float>()[offset];\n\n          float relErr = relativeError(v1, v2);\n          float absErr = std::abs(v1 - v2);\n\n          // For small values / small difference, the relative error\n          // can be huge but the absolute error will be small\n          EXPECT_TRUE(relErr <= maxRelErr ||\n                      (relErr > maxRelErr &&\n                       absErr <= absErrForRelErrFailure)) <<\n            v1 << \" \" << v2 << \" (rel err \" << relErr << \") \" <<\n            \"(\" << n << \" \" << c << \" \" << h << \" \" << w << \") \" <<\n            \"running N \" << N << \" inputC \" << inputC <<\n            \" H \" << H << \" W \" << W <<\n            \" outputC \" << outputC <<\n            \" kernelH \" << kernelH << \" kernelW \" << kernelW <<\n            \" strideH \" << strideH << \" strideW \" << strideW <<\n            \" padT \" << padT << \" padL \" << padL <<\n            \" padB \" << padB << \" padR \" << padR <<\n            \" adjH \" << adjH << \" adjW \" << adjW;\n\n        }\n      }\n    }\n  }\n}\n\n} // namespace caffe2\n\nint randInt(int a, int b) {\n  static std::random_device rd;\n  static std::mt19937 gen(rd());\n\n  return std::uniform_int_distribution<int>(a, b)(gen);\n}\n\n// TODO(#14383029) cblas_sgemm not yet implemented on limited mobile cases.\n#if __ARM_NEON__ && !defined(CAFFE2_FB_LIMITED_MOBILE_CAPABILITY)\nTEST(ConvTransposeMobile, Test) {\n  for (int i = 0; i < 10; ++i) {\n    int n = randInt(1, 3);\n    int planesIn = randInt(1, 10);\n    int h = randInt(10, 200);\n    int w = randInt(10, 200);\n    int planesOut = randInt(1, 10);\n    int kernelH = randInt(2, 5);\n    int kernelW = randInt(2, 5);\n    int strideH = randInt(1, 4);\n    int strideW = randInt(1, 4);\n    int padT = randInt(0, 3);\n    int padB = randInt(0, 3);\n    int padL = 0;\n    int padR = 0;\n    int adjH = randInt(0, 3);\n    if (adjH >= strideH) { adjH = strideH - 1; }\n    int adjW = randInt(0, 3);\n    if (adjW >= strideW) { adjW = strideW - 1; }\n\n    caffe2::compare(n, planesIn, h, w,\n                    planesOut,\n                    kernelH, kernelW,\n                    strideH, strideW,\n                    padT, padL, padB, padR,\n                    adjH, adjW, 0.002f, 0.001f);\n  }\n}\n#endif\n"
  },
  {
    "path": "caffe2/operators/conv_transpose_unpool_op_base.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CONV_TRANSPOSE_UNPOOL_OP_BASE_H_\n#define CAFFE2_OPERATORS_CONV_TRANSPOSE_UNPOOL_OP_BASE_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_op_shared.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/proto/caffe2_legacy.pb.h\"\n#include \"caffe2/utils/math.h\"\n\nCAFFE2_DECLARE_bool(caffe2_force_shared_col_buffer);\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass ConvTransposeUnpoolBase : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ConvTransposeUnpoolBase(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        legacy_pad_(\n            static_cast<LegacyPadding>(OperatorBase::GetSingleArgument<int>(\n                \"legacy_pad\",\n                LegacyPadding::NOTSET))),\n        kernel_(OperatorBase::GetRepeatedArgument<int>(\"kernels\")),\n        stride_(OperatorBase::GetRepeatedArgument<int>(\"strides\")),\n        pads_(OperatorBase::GetRepeatedArgument<int>(\"pads\")),\n        adj_(OperatorBase::GetRepeatedArgument<int>(\"adjs\")),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))),\n        shared_buffer_(\n            OperatorBase::GetSingleArgument<int>(\"shared_buffer\", 0)),\n        ws_(ws) {\n    // For the padding, they should either be the legacy padding strategy\n    // (VALID or SAME), or an explicit, non-negative value.\n    if (legacy_pad_ == LegacyPadding::VALID ||\n        legacy_pad_ == LegacyPadding::SAME) {\n      CAFFE_ENFORCE(\n          !OperatorBase::HasArgument(\"pads\"),\n          \"If you use legacy padding VALID or SAME, you should not specify \"\n          \"any specific padding values.\");\n    }\n    // Get old arguments values.\n    if (OperatorBase::HasArgument(\"kernel\")) {\n      kernel_.resize(2, OperatorBase::GetSingleArgument<int>(\"kernel\", 0));\n    } else if (\n        OperatorBase::HasArgument(\"kernel_h\") &&\n        OperatorBase::HasArgument(\"kernel_w\")) {\n      kernel_.push_back(OperatorBase::GetSingleArgument<int>(\"kernel_h\", 0));\n      kernel_.push_back(OperatorBase::GetSingleArgument<int>(\"kernel_w\", 0));\n    }\n\n    if (OperatorBase::HasArgument(\"stride\")) {\n      stride_.resize(2, OperatorBase::GetSingleArgument<int>(\"stride\", 0));\n    } else if (\n        OperatorBase::HasArgument(\"stride_h\") &&\n        OperatorBase::HasArgument(\"stride_w\")) {\n      stride_.push_back(OperatorBase::GetSingleArgument<int>(\"stride_h\", 0));\n      stride_.push_back(OperatorBase::GetSingleArgument<int>(\"stride_w\", 0));\n    }\n\n    if (OperatorBase::HasArgument(\"adj\")) {\n      adj_.resize(2, OperatorBase::GetSingleArgument<int>(\"adj\", 0));\n    } else if (\n        OperatorBase::HasArgument(\"adj_h\") &&\n        OperatorBase::HasArgument(\"adj_w\")) {\n      adj_.push_back(OperatorBase::GetSingleArgument<int>(\"adj_h\", 0));\n      adj_.push_back(OperatorBase::GetSingleArgument<int>(\"adj_w\", 0));\n    }\n\n    if (OperatorBase::HasArgument(\"pad\")) {\n      CAFFE_ENFORCE(\n          legacy_pad_ != LegacyPadding::VALID &&\n              legacy_pad_ != LegacyPadding::SAME,\n          \"If you use legacy padding VALID or SAME, you should not specify \"\n          \"any specific padding values.\");\n      pads_.resize(4, OperatorBase::GetSingleArgument<int>(\"pad\", 0));\n    } else if (\n        OperatorBase::HasArgument(\"pad_t\") &&\n        OperatorBase::HasArgument(\"pad_l\") &&\n        OperatorBase::HasArgument(\"pad_b\") &&\n        OperatorBase::HasArgument(\"pad_r\")) {\n      CAFFE_ENFORCE(\n          legacy_pad_ != LegacyPadding::VALID &&\n              legacy_pad_ != LegacyPadding::SAME,\n          \"If you use legacy padding VALID or SAME, you should not specify \"\n          \"any specific padding values.\");\n      pads_.push_back(OperatorBase::GetSingleArgument<int>(\"pad_t\", 0));\n      pads_.push_back(OperatorBase::GetSingleArgument<int>(\"pad_l\", 0));\n      pads_.push_back(OperatorBase::GetSingleArgument<int>(\"pad_b\", 0));\n      pads_.push_back(OperatorBase::GetSingleArgument<int>(\"pad_r\", 0));\n    }\n\n    // Fill default values.\n    if (kernel_.size() == 0) {\n      kernel_.assign({0, 0});\n    }\n\n    if (stride_.size() == 0) {\n      stride_.resize(kernel_.size(), 1);\n    }\n\n    if (pads_.size() == 0) {\n      pads_.resize(kernel_.size() * 2, 0);\n    }\n\n    if (adj_.size() == 0) {\n      adj_.resize(kernel_.size(), 0);\n    }\n\n    CAFFE_ENFORCE_EQ(stride_.size(), kernel_.size());\n    CAFFE_ENFORCE_EQ(adj_.size(), kernel_.size());\n\n    if (legacy_pad_ != LegacyPadding::VALID &&\n        legacy_pad_ != LegacyPadding::SAME) {\n      CAFFE_ENFORCE_EQ(pads_.size(), 2 * kernel_.size());\n    }\n\n    for (int dim = 0; dim < kernel_.size(); ++dim) {\n      CAFFE_ENFORCE_GT(kernel_[dim], 0);\n      CAFFE_ENFORCE_GT(stride_[dim], 0);\n      CAFFE_ENFORCE_GE(adj_[dim], 0);\n      CAFFE_ENFORCE_LE(adj_[dim], stride_[dim]);\n    }\n\n    // Create shared buffer mutex in the constructor\n    // to avoid race-condition in DAGNet.\n    if (FLAGS_caffe2_force_shared_col_buffer || shared_buffer_) {\n      createSharedBuffer<Context>(ws_);\n    }\n  }\n  // Sets the output size. The output channel is manually specified.\n  void SetOutputSize(\n      const Tensor<Context>& input,\n      Tensor<Context>* output,\n      int output_channel) {\n    CAFFE_ENFORCE(4 == input.ndim());\n    CAFFE_ENFORCE(input.size() > 0);\n    int N = input.dim32(0);\n    bool channel_first = false; // initialized to suppress compiler warning.\n    int H = 0, W = 0; // initialized to suppress compiler warning.\n    int M = 0;\n    switch (order_) {\n      case StorageOrder::NHWC:\n        channel_first = false;\n        H = input.dim32(1);\n        W = input.dim32(2);\n        M = input.dim32(3);\n        break;\n      case StorageOrder::NCHW:\n        channel_first = true;\n        M = input.dim32(1);\n        H = input.dim32(2);\n        W = input.dim32(3);\n        break;\n      default:\n        LOG(FATAL) << \"Unknown Storage order: \" << order_;\n    }\n    int output_height = 0, output_width = 0;\n    ComputeSizeAndPad(\n        H,\n        stride_[0],\n        kernel_[0],\n        adj_[0],\n        &pads_[0],\n        &pads_[2],\n        &output_height);\n    ComputeSizeAndPad(\n        W,\n        stride_[1],\n        kernel_[1],\n        adj_[1],\n        &pads_[1],\n        &pads_[3],\n        &output_width);\n    if (channel_first) {\n      output->Resize(N, output_channel, output_height, output_width);\n    } else {\n      output->Resize(N, output_height, output_width, output_channel);\n    }\n    VLOG(2) << \"In: N \" << N << \" M \" << M << \" H \" << H << \" W \" << W;\n    VLOG(2) << \"Out: output_channel \" << output_channel << \" H \"\n            << output_height << \" W \" << output_width;\n  }\n\n  bool RunOnDevice() override {\n    switch (order_) {\n      case StorageOrder::NHWC:\n        return RunOnDeviceWithOrderNHWC();\n      case StorageOrder::NCHW:\n        return RunOnDeviceWithOrderNCHW();\n      default:\n        LOG(FATAL) << \"Unknown storage order: \" << order_;\n    }\n    // To suppress old compiler warnings\n    return true;\n  }\n\n  virtual bool RunOnDeviceWithOrderNCHW() {\n    CAFFE_THROW(\"Not implemented\");\n  }\n\n  virtual bool RunOnDeviceWithOrderNHWC() {\n    CAFFE_THROW(\"Not implemented\");\n  }\n\n  virtual ~ConvTransposeUnpoolBase() {}\n\n private:\n  LegacyPadding legacy_pad_;\n  int pad_;\n\n protected:\n  vector<int> kernel_;\n  vector<int> stride_;\n  vector<int> pads_;\n  vector<int> adj_;\n  StorageOrder order_;\n  bool shared_buffer_;\n  Workspace* ws_;\n\n  // Accessors for 2D conv params.\n\n  inline int pad_t() const {\n    return pads_[0];\n  }\n\n  inline int pad_l() const {\n    return pads_[1];\n  }\n\n  inline int pad_b() const {\n    return pads_[2];\n  }\n\n  inline int pad_r() const {\n    return pads_[3];\n  }\n\n  inline int kernel_h() const {\n    return kernel_[0];\n  }\n\n  inline int kernel_w() const {\n    return kernel_[1];\n  }\n\n  inline int stride_h() const {\n    return stride_[0];\n  }\n\n  inline int stride_w() const {\n    return stride_[1];\n  }\n\n  inline int adj_h() const {\n    return adj_[0];\n  }\n\n  inline int adj_w() const {\n    return adj_[1];\n  }\n\n  inline void ComputeSizeAndPad(\n      const int in_size,\n      const int stride,\n      const int kernel,\n      const int adj,\n      int* pad_head,\n      int* pad_tail,\n      int* out_size) {\n    switch (legacy_pad_) {\n      case LegacyPadding::NOTSET:\n        CAFFE_ENFORCE(*pad_head >= 0);\n        CAFFE_ENFORCE(*pad_tail >= 0);\n        *out_size =\n            (in_size - 1) * stride + kernel + adj - *pad_head - *pad_tail;\n        break;\n      // We handle cases of LegacyPadding::VALID and LegacyPadding::SAME\n      // the same way\n      case LegacyPadding::VALID:\n      case LegacyPadding::SAME:\n        *pad_head = 0;\n        *pad_tail = 0;\n        *out_size = (in_size - 1) * stride + kernel + adj;\n        break;\n      case LegacyPadding::CAFFE_LEGACY_POOLING:\n        LOG(FATAL) << \"CAFFE_LEGACY_POOLING is no longer supported.\";\n        break;\n    }\n  }\n};\n\n#define USE_CONV_TRANSPOSE_UNPOOL_BASE_FUNCTIONS(Context) \\\n  USE_OPERATOR_FUNCTIONS(Context);                        \\\n  using ConvTransposeUnpoolBase<Context>::kernel_;        \\\n  using ConvTransposeUnpoolBase<Context>::stride_;        \\\n  using ConvTransposeUnpoolBase<Context>::pads_;          \\\n  using ConvTransposeUnpoolBase<Context>::adj_;           \\\n  using ConvTransposeUnpoolBase<Context>::order_;         \\\n  using ConvTransposeUnpoolBase<Context>::shared_buffer_; \\\n  using ConvTransposeUnpoolBase<Context>::ws_\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CONV_TRANSPOSE_UNPOOL_OP_BASE_H_\n"
  },
  {
    "path": "caffe2/operators/cos_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nstruct CosCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* device_context) {\n    math::Cos<T, CPUContext>(n, x, y, device_context);\n  }\n};\n\nstruct CosGradientCPUFunctor {\n  template <typename T>\n  inline void\n  Run(const int n, const T* x, const T* dy, T* dx, CPUContext* /* unused */) {\n    ConstEigenVectorArrayMap<T> dyM(dy, n);\n    ConstEigenVectorArrayMap<T> xM(x, n);\n    EigenVectorMap<T>(dx, n) = -dyM * sin(xM);\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    Cos,\n    UnaryElementwiseOp<TensorTypes<float>, CPUContext, CosCPUFunctor>);\nREGISTER_CPU_OPERATOR(\n    CosGradient,\n    BinaryElementwiseOp<\n        TensorTypes<float>,\n        CPUContext,\n        WithoutBroadcast<CosGradientCPUFunctor>>);\n\nOPERATOR_SCHEMA(Cos)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nCalculates the cosine of the given input tensor, element-wise.\n)DOC\")\n    .Input(0, \"input\", \"Input tensor\")\n    .Output(\n        0,\n        \"output\",\n        \"The cosine of the input tensor computed element-wise\");\n\nOPERATOR_SCHEMA(CosGradient).NumInputs(2).NumOutputs(1).IdenticalTypeAndShape();\n\nclass GetCosGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"CosGradient\",\n        \"\",\n        std::vector<string>{I(0), GO(0)},\n        std::vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Cos, GetCosGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/cos_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cmath>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void CosKernel(const int N, const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = cos(X[i]);\n  }\n}\n\ntemplate <typename T>\n__global__ void CosGradientKernel(const int N, const T* X, const T* dY, T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dX[i] = -dY[i] * sin(X[i]);\n  }\n}\n\nstruct CosCUDAFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CUDAContext* device_context) {\n    CosKernel<T>\n        <<<CAFFE_GET_BLOCKS(n),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           device_context->cuda_stream()>>>(n, x, y);\n    return;\n  }\n};\n\nstruct CosGradientCUDAFunctor {\n  template <typename T>\n  inline void Run(\n      const int n,\n      const T* x,\n      const T* dy,\n      T* dx,\n      CUDAContext* device_context) {\n    CosGradientKernel<T>\n        <<<CAFFE_GET_BLOCKS(n),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           device_context->cuda_stream()>>>(n, x, dy, dx);\n    return;\n  }\n};\n\nREGISTER_CUDA_OPERATOR(\n    Cos,\n    UnaryElementwiseOp<TensorTypes<float>, CUDAContext, CosCUDAFunctor>);\nREGISTER_CUDA_OPERATOR(\n    CosGradient,\n    BinaryElementwiseOp<\n        TensorTypes<float>,\n        CUDAContext,\n        WithoutBroadcast<CosGradientCUDAFunctor>>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/cosine_embedding_criterion_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/cosine_embedding_criterion_op.h\"\n\n#include <algorithm>\n\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool CosineEmbeddingCriterionOp<CPUContext>::RunOnDevice() {\n  auto& S = Input(0);\n  auto& Y = Input(1);\n  auto* output = Output(0);\n  CAFFE_ENFORCE(\n      S.size() == Y.size(),\n      \"The embedding and label should have the same size.\");\n  output->ResizeLike(S);\n\n  const float* Sdata = S.data<float>();\n  const int* Ydata = Y.data<int>();\n  float* output_data = output->mutable_data<float>();\n  for (int i = 0; i < S.size(); ++i) {\n    output_data[i] =\n        Ydata[i] == 1 ? (1.f - Sdata[i]) : std::max(0.f, Sdata[i] - margin_);\n  }\n  return true;\n}\n\ntemplate <>\nbool CosineEmbeddingCriterionGradientOp<CPUContext>::RunOnDevice() {\n  auto& S = Input(0);\n  auto& Y = Input(1);\n  auto& dOutput = Input(2);\n  auto* dS = Output(0);\n\n  dS->ResizeLike(S);\n\n  const float* Sdata = S.data<float>();\n  const int* Ydata = Y.data<int>();\n  const float* dOutput_data = dOutput.data<float>();\n  float* dSdata = dS->mutable_data<float>();\n  for (int i = 0; i < S.size(); ++i) {\n    dSdata[i] = dOutput_data[i] *\n        (Ydata[i] == 1 ? -1.f : static_cast<float>(Sdata[i] >= margin_));\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(\n    CosineEmbeddingCriterion,\n    CosineEmbeddingCriterionOp<CPUContext>);\nREGISTER_CPU_OPERATOR(\n    CosineEmbeddingCriterionGradient,\n    CosineEmbeddingCriterionGradientOp<CPUContext>);\n\nOPERATOR_SCHEMA(CosineEmbeddingCriterion)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nCosineEmbeddingCriterion takes two inputs: the similarity value and\nthe label, and computes the elementwise criterion output as\n\n  output = 1 - s,               if y == 1\n           max(0, s - margin),  if y == -1\n)DOC\")\n    .Input(0, \"S\", \"The cosine similarity as a 1-dim TensorCPU.\")\n    .Input(1, \"Y\", \"The label as a 1-dim TensorCPU with int value of 1 or -1.\")\n    .Output(0, \"loss\", \"The output loss with the same dimensionality as S.\");\n\nOPERATOR_SCHEMA(CosineEmbeddingCriterionGradient).NumInputs(3).NumOutputs(1);\n\nclass GetCosineEmbeddingCriterionGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"CosineEmbeddingCriterionGradient\",\n        \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(\n    CosineEmbeddingCriterion,\n    GetCosineEmbeddingCriterionGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/cosine_embedding_criterion_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/cosine_embedding_criterion_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\n\n__global__ void CECKernel(\n    const int N, const float* S, const int* Y, const float margin,\n    float* output) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    output[i] = Y[i] == 1 ? (1. - S[i]) : max(0.f, S[i] - margin);\n  }\n}\n\n__global__ void CECGradientKernel(\n    const int N, const float* S, const int* Y, const float* dOutput,\n    const float margin, float* dS) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dS[i] = dOutput[i] * (Y[i] == 1 ? -1 : static_cast<float>(S[i] >= margin));\n  }\n}\n}  // namespace\n\ntemplate <>\nbool CosineEmbeddingCriterionOp<CUDAContext>::RunOnDevice() {\n  auto& S = Input(0);\n  auto& Y = Input(1);\n  auto* output = Output(0);\n  CAFFE_ENFORCE(S.size() == Y.size(),\n                \"The embedding and label should have the same size.\");\n  output->ResizeLike(S);\n\n  const float* Sdata = S.data<float>();\n  const int* Ydata = Y.data<int>();\n  float* output_data = output->mutable_data<float>();\n \n  CECKernel<<<CAFFE_GET_BLOCKS(S.size()), CAFFE_CUDA_NUM_THREADS,\n              0, context_.cuda_stream()>>>(\n      S.size(), Sdata, Ydata, margin_, output_data);\n  return true;\n}\n\ntemplate <>\nbool CosineEmbeddingCriterionGradientOp<CUDAContext>::RunOnDevice() {\n  auto& S = Input(0);\n  auto& Y = Input(1);\n  auto& dOutput = Input(2);\n  auto* dS = Output(0);\n\n  dS->ResizeLike(S);\n\n  const float* Sdata = S.data<float>();\n  const int* Ydata = Y.data<int>();\n  const float* dOutput_data = dOutput.data<float>();\n  float* dSdata = dS->mutable_data<float>();\n  CECGradientKernel<<<CAFFE_GET_BLOCKS(S.size()), CAFFE_CUDA_NUM_THREADS,\n                      0, context_.cuda_stream()>>>(\n      S.size(), Sdata, Ydata, dOutput_data, margin_, dSdata);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(\n    CosineEmbeddingCriterion,\n    CosineEmbeddingCriterionOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    CosineEmbeddingCriterionGradient,\n    CosineEmbeddingCriterionGradientOp<CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/cosine_embedding_criterion_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_COSINE_EMBEDDING_CRITERION_OP_H_\n#define CAFFE2_OPERATORS_COSINE_EMBEDDING_CRITERION_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass CosineEmbeddingCriterionOp final : public Operator<Context> {\n public:\n  CosineEmbeddingCriterionOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        OP_SINGLE_ARG(float, \"margin\", margin_, 0.0) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  float margin_;\n};\n\ntemplate <class Context>\nclass CosineEmbeddingCriterionGradientOp final : public Operator<Context> {\n public:\n  CosineEmbeddingCriterionGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        OP_SINGLE_ARG(float, \"margin\", margin_, 0.0) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  float margin_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_COSINE_EMBEDDING_CRITERION_OP_H_\n"
  },
  {
    "path": "caffe2/operators/counter_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"counter_ops.h\"\n\n#include \"caffe2/core/blob_serialization.h\"\n\nnamespace caffe2 {\nnamespace {\n/**\n *  @brief CounterSerializer is the serializer for Counter type.\n *\n * CounterSerializer takes in a blob that contains a Counter, and serializes\n * it into a BlobProto protocol buffer. At the moment only int64_t counters are\n * supported (since it's the only once that is really used).\n *\n */\nclass CounterSerializer : public BlobSerializerBase {\n public:\n  CounterSerializer() {}\n  ~CounterSerializer() {}\n\n  void Serialize(\n      const Blob& blob,\n      const string& name,\n      SerializationAcceptor acceptor) override {\n    CAFFE_ENFORCE(blob.IsType<std::unique_ptr<Counter<int64_t>>>());\n\n    BlobProto blob_proto;\n    blob_proto.set_name(name);\n    blob_proto.set_type(\"std::unique_ptr<Counter<int64_t>>\");\n    TensorProto& proto = *blob_proto.mutable_tensor();\n    proto.set_name(name);\n    proto.set_data_type(TensorProto_DataType_INT64);\n    proto.add_dims(1);\n    proto.add_int64_data(\n        blob.template Get<std::unique_ptr<Counter<int64_t>>>()->retrieve());\n    acceptor(name, blob_proto.SerializeAsString());\n  }\n};\n\n/**\n * @brief CounterDeserializer is the deserializer for Counters.\n *\n */\nclass CounterDeserializer : public BlobDeserializerBase {\n public:\n  void Deserialize(const BlobProto& proto, Blob* blob) override {\n    auto tensorProto = proto.tensor();\n    CAFFE_ENFORCE_EQ(tensorProto.dims_size(), 1, \"Unexpected size of dims\");\n    CAFFE_ENFORCE_EQ(tensorProto.dims(0), 1, \"Unexpected value of dims\");\n    CAFFE_ENFORCE_EQ(\n        tensorProto.data_type(),\n        TensorProto_DataType_INT64,\n        \"Only int64_t counters supported\");\n    CAFFE_ENFORCE_EQ(\n        tensorProto.int64_data_size(), 1, \"Unexpected size of data\");\n    *blob->GetMutable<std::unique_ptr<Counter<int64_t>>>() =\n        caffe2::make_unique<Counter<int64_t>>(tensorProto.int64_data(0));\n  }\n};\n}\n\n// TODO(jiayq): deprecate these ops & consolidate them with\n// IterOp/AtomicIterOp\n\nREGISTER_CPU_OPERATOR(CreateCounter, CreateCounterOp<int64_t, CPUContext>);\nREGISTER_CPU_OPERATOR(ResetCounter, ResetCounterOp<int64_t, CPUContext>);\nREGISTER_CPU_OPERATOR(CountDown, CountDownOp<int64_t, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    CheckCounterDone,\n    CheckCounterDoneOp<int64_t, CPUContext>);\nREGISTER_CPU_OPERATOR(CountUp, CountUpOp<int64_t, CPUContext>);\nREGISTER_CPU_OPERATOR(RetrieveCount, RetrieveCountOp<int64_t, CPUContext>);\n\nOPERATOR_SCHEMA(CreateCounter)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nCreates a count-down counter with initial value specified by the 'init_count'\nargument.\n)DOC\")\n    .Output(0, \"counter\", \"A blob pointing to an instance of a new counter.\")\n    .Arg(\"init_count\", \"Initial count for the counter, must be >= 0.\");\n\nOPERATOR_SCHEMA(ResetCounter)\n    .NumInputs(1)\n    .NumOutputs(0, 1)\n    .SetDoc(R\"DOC(\nResets a count-down counter with initial value specified by the 'init_count'\nargument.\n)DOC\")\n    .Input(0, \"counter\", \"A blob pointing to an instance of a new counter.\")\n    .Output(0, \"previous_value\", \"(optional) Previous value of the counter.\")\n    .Arg(\"init_count\", \"Resets counter to this value, must be >= 0.\");\n\nOPERATOR_SCHEMA(CountDown)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nIf the internal count value > 0, decreases count value by 1 and outputs false,\notherwise outputs true.\n)DOC\")\n    .Input(0, \"counter\", \"A blob pointing to an instance of a counter.\")\n    .Output(0, \"done\", \"false unless the internal count is zero.\");\n\nOPERATOR_SCHEMA(CheckCounterDone)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nIf the internal count value <= 0, outputs true, otherwise outputs false,\n)DOC\")\n    .Input(0, \"counter\", \"A blob pointing to an instance of a counter.\")\n    .Output(0, \"done\", \"true if the internal count is zero or negative.\");\n\nOPERATOR_SCHEMA(CountUp)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nIncreases count value by 1 and outputs the previous value atomically\n)DOC\")\n    .Input(0, \"counter\", \"A blob pointing to an instance of a counter.\")\n    .Output(0, \"previous_count\", \"count value BEFORE this operation\");\n\nOPERATOR_SCHEMA(RetrieveCount)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .ScalarType(TensorProto::INT64)\n    .SetDoc(R\"DOC(\nRetrieve the current value from the counter.\n)DOC\")\n    .Input(0, \"counter\", \"A blob pointing to an instance of a counter.\")\n    .Output(0, \"count\", \"current count value.\");\n\nSHOULD_NOT_DO_GRADIENT(CreateCounter);\nSHOULD_NOT_DO_GRADIENT(ResetCounter);\nSHOULD_NOT_DO_GRADIENT(CountDown);\nSHOULD_NOT_DO_GRADIENT(CountUp);\nSHOULD_NOT_DO_GRADIENT(RetrieveCount);\n\nCAFFE_KNOWN_TYPE(std::unique_ptr<Counter<int64_t>>);\nREGISTER_BLOB_SERIALIZER(\n    (TypeMeta::Id<std::unique_ptr<Counter<int64_t>>>()),\n    CounterSerializer);\nREGISTER_BLOB_DESERIALIZER(\n    std::unique_ptr<Counter<int64_t>>,\n    CounterDeserializer);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/counter_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_COUNTER_OPS_H\n#define CAFFE2_OPERATORS_COUNTER_OPS_H\n\n#include <atomic>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\ntemplate <typename T>\nclass Counter {\n public:\n  explicit Counter(T count) : count_(count) {}\n  bool countDown() {\n    if (count_-- > 0) {\n      return false;\n    }\n    return true;\n  }\n\n  T countUp() {\n    return count_++;\n  }\n\n  T retrieve() const {\n    return count_.load();\n  }\n\n  T checkIfDone() const {\n    return (count_.load() <= 0);\n  }\n\n  T reset(T init_count) {\n    return count_.exchange(init_count);\n  }\n\n private:\n  std::atomic<T> count_;\n};\n\n// TODO(jiayq): deprecate these ops & consolidate them with IterOp/AtomicIterOp\n\ntemplate <typename T, class Context>\nclass CreateCounterOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  CreateCounterOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        init_count_(OperatorBase::GetSingleArgument<T>(\"init_count\", 0)) {\n    CAFFE_ENFORCE_LE(0, init_count_, \"negative init_count is not permitted.\");\n  }\n\n  bool RunOnDevice() override {\n    *OperatorBase::Output<std::unique_ptr<Counter<T>>>(0) =\n        std::unique_ptr<Counter<T>>(new Counter<T>(init_count_));\n    return true;\n  }\n\n private:\n  T init_count_ = 0;\n};\n\ntemplate <typename T, class Context>\nclass ResetCounterOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ResetCounterOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        init_count_(OperatorBase::GetSingleArgument<T>(\"init_count\", 0)) {\n    CAFFE_ENFORCE_LE(0, init_count_, \"negative init_count is not permitted.\");\n  }\n\n  bool RunOnDevice() override {\n    auto& counterPtr = OperatorBase::Input<std::unique_ptr<Counter<T>>>(0);\n    auto previous = counterPtr->reset(init_count_);\n    if (OutputSize() == 1) {\n      auto* output = OperatorBase::Output<TensorCPU>(0);\n      output->Resize();\n      *output->template mutable_data<T>() = previous;\n    }\n    return true;\n  }\n\n private:\n  T init_count_;\n};\n\n// Will always use TensorCPU regardless the Context\ntemplate <typename T, class Context>\nclass CountDownOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  CountDownOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& counterPtr = OperatorBase::Input<std::unique_ptr<Counter<T>>>(0);\n    auto* output = OperatorBase::Output<TensorCPU>(0);\n    output->Resize(std::vector<int>{});\n    *output->template mutable_data<bool>() = counterPtr->countDown();\n    return true;\n  }\n};\n\n// Will always use TensorCPU regardless the Context\ntemplate <typename T, class Context>\nclass CheckCounterDoneOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  CheckCounterDoneOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& counterPtr = OperatorBase::Input<std::unique_ptr<Counter<T>>>(0);\n    auto* output = OperatorBase::Output<TensorCPU>(0);\n    output->Resize(std::vector<int>{});\n    *output->template mutable_data<bool>() = counterPtr->checkIfDone();\n    return true;\n  }\n};\n\n// Will always use TensorCPU regardless the Context\ntemplate <typename T, class Context>\nclass CountUpOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  CountUpOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& counterPtr = OperatorBase::Input<std::unique_ptr<Counter<T>>>(0);\n    auto* output = OperatorBase::Output<TensorCPU>(0);\n    output->Resize(std::vector<int>{});\n    *output->template mutable_data<T>() = counterPtr->countUp();\n    return true;\n  }\n};\n\n// Will always use TensorCPU regardless the Context\ntemplate <typename T, class Context>\nclass RetrieveCountOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  RetrieveCountOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& counterPtr = OperatorBase::Input<std::unique_ptr<Counter<T>>>(0);\n    auto* output = OperatorBase::Output<TensorCPU>(0);\n    output->Resize(std::vector<int>{});\n    *output->template mutable_data<T>() = counterPtr->retrieve();\n    return true;\n  }\n};\n\n} // namespace caffe2\n#endif // CAFFE2_OPERATORS_COUNTER_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/counter_ops_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"counter_ops.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(CreateCounter, CreateCounterOp<int64_t, CUDAContext>);\nREGISTER_CUDA_OPERATOR(ResetCounter, ResetCounterOp<int64_t, CUDAContext>);\nREGISTER_CUDA_OPERATOR(CountDown, CountDownOp<int64_t, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    CheckCounterDone,\n    CheckCounterDoneOp<int64_t, CUDAContext>);\nREGISTER_CUDA_OPERATOR(CountUp, CountUpOp<int64_t, CUDAContext>);\nREGISTER_CUDA_OPERATOR(RetrieveCount, RetrieveCountOp<int64_t, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/create_scope_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/create_scope_op.h\"\n\nCAFFE2_DEFINE_bool(\n    caffe2_workspace_stack_debug,\n    false,\n    \"Enable debug checks for CreateScope's workspace stack\");\n\nnamespace caffe2 {\nCAFFE_KNOWN_TYPE(detail::WorkspaceStack);\n\ntemplate <>\nbool CreateScopeOp<CPUContext>::RunOnDevice() {\n  auto* ws_stack = OperatorBase::Output<detail::WorkspaceStack>(0);\n  ws_stack->clear();\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(CreateScope, CreateScopeOp<CPUContext>);\n\nSHOULD_NOT_DO_GRADIENT(CreateScope);\n\nOPERATOR_SCHEMA(CreateScope).NumInputs(0).NumOutputs(1).SetDoc(R\"DOC(\n'CreateScope' operator initializes and outputs empty scope that is used\nby Do operator to store local blobs\n    )DOC\");\n\ntemplate <>\nbool HasScopeOp<CPUContext>::RunOnDevice() {\n  const auto& ws_stack = OperatorBase::Input<detail::WorkspaceStack>(0);\n  auto* output = Output(0);\n  output->Resize(1);\n  bool* output_value = output->template mutable_data<bool>();\n  *output_value = !ws_stack.empty();\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(HasScope, HasScopeOp<CPUContext>);\n\nSHOULD_NOT_DO_GRADIENT(HasScope);\n\nOPERATOR_SCHEMA(HasScope).NumInputs(1).NumOutputs(1).SetDoc(R\"DOC(\nChecks whether scope blob has any saved scopes left\n    )DOC\");\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/create_scope_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CREATE_SCOPE_OP_H_\n#define CAFFE2_OPERATORS_CREATE_SCOPE_OP_H_\n\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <vector>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nCAFFE2_DECLARE_bool(caffe2_workspace_stack_debug);\n\nnamespace caffe2 {\nnamespace detail {\n\n/*\n * Keeps track of forward and backward gradient workspaces in stack,\n * reuses previously created workspaces, non-thread safe\n */\nclass WorkspaceStack {\n public:\n  explicit WorkspaceStack() : parent_ws_(nullptr), top_(-1) {}\n\n  std::shared_ptr<Workspace> pushForwardWorkspace(Workspace* parent_ws) {\n    return pushForwardWorkspace(\n        parent_ws, std::unordered_map<std::string, std::string>());\n  }\n\n  std::shared_ptr<Workspace> pushForwardWorkspace(\n      Workspace* parent_ws,\n      const std::unordered_map<std::string, std::string>& blob_bindings) {\n    checkStack();\n    if (FLAGS_caffe2_workspace_stack_debug) {\n      if (parent_ws_) {\n        CAFFE_ENFORCE_EQ(parent_ws_, parent_ws, \"Parent workspace mismatch\");\n      } else {\n        parent_ws_ = parent_ws;\n      }\n      if (!blob_bindings_.empty()) {\n        checkBindingsMatch(blob_bindings_, blob_bindings);\n      } else {\n        blob_bindings_ = blob_bindings;\n      }\n    }\n\n    if (top_ == workspaces_.size() - 1) {\n      workspaces_.push_back(\n          std::make_shared<Workspace>(parent_ws, blob_bindings));\n    } else {\n      // when reusing workspace, make sure copies of external blobs are\n      // removed and blob bindings are set\n      auto& workspace = workspaces_[top_ + 1];\n      const auto& local_blobs = workspace->LocalBlobs();\n      std::unordered_set<std::string> local_blobs_set;\n      local_blobs_set.insert(local_blobs.begin(), local_blobs.end());\n      bool found_local_copy = false;\n      for (const auto& blob_pair : blob_bindings) {\n        if (local_blobs_set.count(blob_pair.first)) {\n          workspace->RemoveBlob(blob_pair.first);\n          found_local_copy = true;\n        }\n      }\n      if (found_local_copy) {\n        workspace->AddBlobMapping(parent_ws, blob_bindings);\n      }\n    }\n\n    return workspaces_[++top_];\n  }\n\n  std::shared_ptr<Workspace> popGradientWorkspace(\n      Workspace* parent_ws,\n      const std::unordered_map<std::string, std::string>& grad_blob_bindings) {\n    checkStack();\n    if (FLAGS_caffe2_workspace_stack_debug) {\n      if (parent_ws_) {\n        CAFFE_ENFORCE_EQ(parent_ws_, parent_ws, \"Parent workspace mismatch\");\n      } else {\n        parent_ws_ = parent_ws;\n      }\n      if (!grad_blob_bindings_.empty()) {\n        checkBindingsMatch(grad_blob_bindings_, grad_blob_bindings);\n      } else {\n        grad_blob_bindings_ = grad_blob_bindings;\n      }\n    }\n\n    if (top_ < 0) {\n      return nullptr;\n    }\n    auto& grad_workspace = workspaces_[top_];\n    grad_workspace->AddBlobMapping(parent_ws, grad_blob_bindings, true);\n    --top_;\n    return grad_workspace;\n  }\n\n  std::shared_ptr<Workspace> reuseLastForwardWorkspace(Workspace* parent_ws) {\n    return reuseLastForwardWorkspace(\n        parent_ws, std::unordered_map<std::string, std::string>());\n  }\n\n  std::shared_ptr<Workspace> reuseLastForwardWorkspace(\n      Workspace* parent_ws,\n      const std::unordered_map<std::string, std::string>& blob_bindings) {\n    checkStack();\n    if (top_ < 0) {\n      return nullptr;\n    }\n    workspaces_[top_]->AddBlobMapping(parent_ws, blob_bindings);\n    return workspaces_[top_];\n  }\n\n  void clear() {\n    checkStack();\n    top_ = -1;\n  }\n\n  bool empty() const {\n    return top_ < 0;\n  }\n\n private:\n  void checkStack() const {\n    CAFFE_ENFORCE_GT(\n        (int)workspaces_.size(), top_, \"Corrupted workspaces stack\");\n  }\n\n  void checkBindingsMatch(\n      const std::unordered_map<std::string, std::string>& bindings,\n      const std::unordered_map<std::string, std::string>& test_bindings) const {\n    CAFFE_ENFORCE_EQ(\n        bindings.size(), test_bindings.size(), \"Blob bindings mismatch\");\n    for (const auto& blob_binding : bindings) {\n      CAFFE_ENFORCE(\n          test_bindings.count(blob_binding.first), \"Blob bindings mismatch\");\n      CAFFE_ENFORCE_EQ(\n          test_bindings.at(blob_binding.first),\n          blob_binding.second,\n          \"Blob bindings mismatch\");\n    }\n  }\n\n  std::unordered_map<std::string, std::string> blob_bindings_;\n  std::unordered_map<std::string, std::string> grad_blob_bindings_;\n  Workspace* parent_ws_;\n  int top_;\n  std::vector<std::shared_ptr<Workspace>> workspaces_;\n};\n}\n\ntemplate <class Context>\nclass CreateScopeOp final : public Operator<Context> {\n public:\n  CreateScopeOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n};\n\ntemplate <class Context>\nclass HasScopeOp final : public Operator<Context> {\n public:\n  HasScopeOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CREATE_SCOPE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/cross_entropy_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/cross_entropy_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\ninline float sigmoid_xent_forward(float lgt, float tgt) {\n  return lgt * (tgt - (lgt >= 0)) - log(1 + exp(lgt - 2 * lgt * (lgt >= 0)));\n}\n\ninline float sigmoid_xent_backward(float lgt, float tgt) {\n  return tgt - 1. / (1. + exp(-lgt));\n}\n}\n\ntemplate <>\nbool LabelCrossEntropyOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& label = Input(1);\n  auto* Y = Output(0);\n  int N, D;\n  if (X.ndim() > 1) {\n    N = X.dim32(0);\n    D = X.size_from_dim(1);\n  } else {\n    N = 1;\n    D = X.dim32(0);\n  }\n  CAFFE_ENFORCE(\n      (label.ndim() == 1) || (label.ndim() == 2 && label.dim32(1) == 1));\n  CAFFE_ENFORCE_EQ(label.dim32(0), N);\n  Y->Resize(N);\n  const auto* Xdata = X.data<float>();\n  const auto* labelData = label.data<int>();\n  auto* Ydata = Y->mutable_data<float>();\n  CAFFE_ENFORCE(\n      (ConstEigenVectorArrayMap<int>(labelData, N) < D).all() &&\n          (ConstEigenVectorArrayMap<int>(labelData, N) >= 0).all(),\n      \"Label seems to be outside of supported range. Supported labels are in \"\n      \"range [0,\",\n      D,\n      \")\");\n  for (int i = 0; i < N; ++i) {\n    Ydata[i] = -log(std::max(Xdata[i * D + labelData[i]], kLOG_THRESHOLD()));\n  }\n  return true;\n}\n\ntemplate <>\nbool SigmoidCrossEntropyWithLogitsOp<float, CPUContext>::RunOnDevice() {\n  auto& logits = Input(0);\n  auto& targets = Input(1);\n  CAFFE_ENFORCE_EQ(logits.dims(), targets.dims());\n  const auto inner_size = logits.ndim() > 0 ? logits.dims().back() : 1;\n  const auto outer_size = logits.size() / inner_size;\n\n  auto* out = Output(0);\n  if (logits.ndim() == 0) {\n    out->Resize(std::vector<TIndex>{});\n  } else {\n    std::vector<TIndex> dims(logits.dims().begin(), logits.dims().end() - 1);\n    out->Resize(dims);\n  }\n  auto* out_ptr = out->mutable_data<float>();\n\n  auto* logits_ptr = logits.data<float>();\n  auto* targets_ptr = targets.data<float>();\n\n  auto in_idx = 0;\n  for (int i = 0; i < outer_size; ++i) {\n    float value = 0;\n    for (int j = 0; j < inner_size; ++j) {\n      value += sigmoid_xent_forward(logits_ptr[in_idx], targets_ptr[in_idx]);\n      ++in_idx;\n    }\n    out_ptr[i] = -value / inner_size;\n  }\n  return true;\n}\n\ntemplate <>\nbool SigmoidCrossEntropyWithLogitsGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& g = Input(0);\n  auto& logits = Input(1);\n  auto& targets = Input(2);\n  CAFFE_ENFORCE(logits.dims() == targets.dims());\n  const auto inner_size = logits.ndim() > 0 ? logits.dims().back() : 1;\n  const auto outer_size = logits.size() / inner_size;\n  CAFFE_ENFORCE(g.size() == outer_size);\n\n  auto* out = Output(0);\n  out->ResizeLike(logits);\n  auto* out_ptr = out->mutable_data<float>();\n\n  auto* logits_ptr = logits.data<float>();\n  auto* targets_ptr = targets.data<float>();\n  auto* g_ptr = g.data<float>();\n\n  auto in_idx = 0;\n  for (int i = 0; i < outer_size; ++i) {\n    auto g_factor = -g_ptr[i] / inner_size;\n    for (int j = 0; j < inner_size; ++j) {\n      out_ptr[in_idx] = g_factor *\n          sigmoid_xent_backward(logits_ptr[in_idx], targets_ptr[in_idx]);\n      ++in_idx;\n    }\n  }\n  return true;\n}\n\ntemplate <>\nbool WeightedSigmoidCrossEntropyWithLogitsOp<float, CPUContext>::RunOnDevice() {\n  auto& logits = Input(0);\n  auto& targets = Input(1);\n  auto& weights = Input(2);\n  CAFFE_ENFORCE(logits.dims() == targets.dims());\n  CAFFE_ENFORCE(weights.dims() == targets.dims());\n  const auto inner_size = logits.ndim() > 0 ? logits.dims().back() : 1;\n  const auto outer_size = logits.size() / inner_size;\n\n  auto* out = Output(0);\n  if (logits.ndim() == 0) {\n    out->Resize(std::vector<TIndex>{});\n  } else {\n    std::vector<TIndex> dims(logits.dims().begin(), logits.dims().end() - 1);\n    out->Resize(dims);\n  }\n  auto* out_ptr = out->mutable_data<float>();\n\n  auto* logits_ptr = logits.data<float>();\n  auto* targets_ptr = targets.data<float>();\n  auto* weights_ptr = weights.data<float>();\n\n  auto in_idx = 0;\n  for (int i = 0; i < outer_size; ++i) {\n    float value = 0;\n    for (int j = 0; j < inner_size; ++j) {\n      value += sigmoid_xent_forward(logits_ptr[in_idx], targets_ptr[in_idx]) *\n          weights_ptr[in_idx];\n      ++in_idx;\n    }\n    out_ptr[i] = -value / inner_size;\n  }\n  return true;\n}\n\ntemplate <>\nbool WeightedSigmoidCrossEntropyWithLogitsGradientOp<float, CPUContext>::\n    RunOnDevice() {\n  auto& g = Input(0);\n  auto& logits = Input(1);\n  auto& targets = Input(2);\n  auto& weights = Input(3);\n  CAFFE_ENFORCE(logits.dims() == targets.dims());\n  CAFFE_ENFORCE(weights.dims() == targets.dims());\n  const auto inner_size = logits.ndim() > 0 ? logits.dims().back() : 1;\n  const auto outer_size = logits.size() / inner_size;\n  CAFFE_ENFORCE(g.size() == outer_size);\n\n  auto* out = Output(0);\n  out->ResizeLike(logits);\n  auto* out_ptr = out->mutable_data<float>();\n\n  auto* logits_ptr = logits.data<float>();\n  auto* targets_ptr = targets.data<float>();\n  auto* weights_ptr = weights.data<float>();\n  auto* g_ptr = g.data<float>();\n\n  auto in_idx = 0;\n  for (int i = 0; i < outer_size; ++i) {\n    auto g_factor = -g_ptr[i] / inner_size;\n    for (int j = 0; j < inner_size; ++j) {\n      out_ptr[in_idx] = g_factor *\n          sigmoid_xent_backward(logits_ptr[in_idx], targets_ptr[in_idx]) *\n          weights_ptr[in_idx];\n      ++in_idx;\n    }\n  }\n  return true;\n}\n\ntemplate <>\nbool LabelCrossEntropyGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& label = Input(1);\n  auto& dY = Input(2);\n  auto* dX = Output(0);\n  int N, D;\n  if (X.ndim() > 1) {\n    N = X.dim32(0);\n    D = X.size_from_dim(1);\n  } else {\n    N = 1;\n    D = X.dim32(0);\n  }\n  CAFFE_ENFORCE(\n      (label.ndim() == 1) || (label.ndim() == 2 && label.dim32(1) == 1));\n  CAFFE_ENFORCE_EQ(label.dim32(0), N);\n  CAFFE_ENFORCE_EQ(dY.ndim(), 1);\n  CAFFE_ENFORCE_EQ(dY.dim32(0), N);\n  dX->ResizeLike(X);\n  math::Set<float, CPUContext>(dX->size(), 0.f, dX->mutable_data<float>(),\n                               &context_);\n  const float* Xdata = X.data<float>();\n  const float* dYdata = dY.data<float>();\n  const int* labelData = label.data<int>();\n  float* dXdata = dX->mutable_data<float>();\n  for (int i = 0; i < N; ++i) {\n    dXdata[i * D + labelData[i]] =\n        - dYdata[i] / std::max(Xdata[i * D + labelData[i]], kLOG_THRESHOLD());\n  }\n  return true;\n}\n\ntemplate <>\nbool MakeTwoClassOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  auto shape = X.dims();\n  shape.push_back(2);\n  TIndex N = X.size();\n  Y->Resize(shape);\n  const auto* Xdata = X.data<float>();\n  auto* Ydata = Y->mutable_data<float>();\n  for (TIndex i = 0; i < N; ++i) {\n    DCHECK_GE(Xdata[i], 0.0);\n    DCHECK_LE(Xdata[i], 1.0);\n    Ydata[i * 2] = 1.0 - Xdata[i];\n    Ydata[i * 2 + 1] = Xdata[i];\n  }\n  return true;\n}\n\ntemplate <>\nbool MakeTwoClassGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& dY = Input(0);\n  auto* dX = Output(0);\n  auto shape = dY.dims();\n  CAFFE_ENFORCE_GE(shape.size(), 1);\n  CAFFE_ENFORCE_EQ(shape.back(), 2);\n  shape.pop_back();\n  dX->Resize(shape);\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  TIndex N = dX->size();\n  // use eigen?\n  for (TIndex i = 0; i < N; ++i) {\n    dXdata[i] = dYdata[i * 2 + 1] - dYdata[i * 2];\n  }\n  return true;\n}\n\ntemplate <>\nbool CrossEntropyOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& label = Input(1);\n  auto* Y = Output(0);\n  int N, D;\n  if (X.ndim() > 1) {\n    N = X.dim32(0);\n    D = X.size_from_dim(1);\n  } else {\n    N = 1;\n    D = X.dim32(0);\n  }\n  CAFFE_ENFORCE(\n      (label.ndim() == 1) || (label.ndim() == 2 && label.dim32(1) == D));\n  CAFFE_ENFORCE_EQ(label.dim32(0), N);\n  Y->Resize(vector<TIndex>{N});\n  const float* Xdata = X.data<float>();\n  const float* labelData = label.data<float>();\n  auto* Ydata = Y->mutable_data<float>();\n  CAFFE_ENFORCE(\n      (ConstEigenArrayMap<float>(labelData, D, N) <= 1.0f).all() &&\n          (ConstEigenArrayMap<float>(labelData, D, N) >= 0.0f).all(),\n      \"Soft label seems incorrect: label value should be a probability \",\n      \"between 0 and 1.0. You may be using the wrong cross entropy operator; \",\n      \"use LabelCrossEntropy if the labels are integers whose values are at \",\n      \"most the number of classes, \",\n      D,\n      \".\");\n  EigenArrayMap<float>(Ydata, 1, N) =\n      -(ConstEigenArrayMap<float>(labelData, D, N) *\n        ConstEigenArrayMap<float>(Xdata, D, N).cwiseMax(kLOG_THRESHOLD()).log())\n           .colwise()\n           .sum();\n  return true;\n}\n\ntemplate <>\nbool CrossEntropyGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& label = Input(1);\n  auto& dY = Input(2);\n  auto* dX = Output(0);\n  int N, D;\n  if (X.ndim() > 1) {\n    N = X.dim32(0);\n    D = X.size_from_dim(1);\n  } else {\n    N = 1;\n    D = X.dim32(0);\n  }\n  CAFFE_ENFORCE(\n      (label.ndim() == 1) || (label.ndim() == 2 && label.dim32(1) == D));\n  CAFFE_ENFORCE_EQ(label.dim32(0), N);\n  CAFFE_ENFORCE_EQ(dY.ndim(), 1);\n  CAFFE_ENFORCE_EQ(dY.dim32(0), N);\n  dX->ResizeLike(X);\n  math::Set<float, CPUContext>(\n    dX->size(), 0.f, dX->mutable_data<float>(), &context_);\n  const float* Xdata = X.data<float>();\n  const float* dYdata = dY.data<float>();\n  const float* labelData = label.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  EigenArrayMap<float>(dXdata, D, N) =\n      (ConstEigenArrayMap<float>(labelData, D, N) /\n       ConstEigenArrayMap<float>(Xdata, D, N).cwiseMax(kLOG_THRESHOLD()))\n          .rowwise() *\n      (-ConstEigenVectorArrayMap<float>(dYdata, N).transpose());\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(LabelCrossEntropy,\n                      LabelCrossEntropyOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(LabelCrossEntropyGradient,\n                      LabelCrossEntropyGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LabelCrossEntropy)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInputDim(0, 0)\n    .SetDoc(R\"DOC(\nOperator computes the cross entropy between the input and the label set. In\n practice, it is most commonly used at the end of models, after the SoftMax\n operator and before the AveragedLoss operator. Note that LabelCrossEntropy\n assumes that the label provided is either a 1D array of size N (batch size), or\n a 2D array of size N x 1 (batch size). Each entry in the label vector indicates\n which is the correct class; as such, each entry must be between 0 and D - 1,\n inclusive, where D is the total number of classes. The formula used is:\n\n                            Y[i] = -log(X[i][j])\n\n where (i, j) is the classifier's prediction of the jth class (the correct one),\n and i is the batch size. Each log has a lower limit for numerical stability.\n)DOC\")\n    .Input(\n        0,\n        \"X\",\n        \"Input blob from the previous layer, which is almost always \"\n        \"the result of a softmax operation; X is a 2D array of size N x D, where N \"\n        \"is the batch size and D is the number of classes\")\n    .Input(1, \"label\", \"Blob containing the labels used to compare the input\")\n    .Output(0, \"Y\", \"Output blob after the cross entropy computation\");\nOPERATOR_SCHEMA(LabelCrossEntropyGradient)\n  .NumInputs(3)\n  .NumOutputs(1);\n\nclass GetLabelCrossEntropyGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"LabelCrossEntropyGradient\", \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(LabelCrossEntropy, GetLabelCrossEntropyGradient);\n\nREGISTER_CPU_OPERATOR(MakeTwoClass,\n                      MakeTwoClassOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(MakeTwoClassGradient,\n                      MakeTwoClassGradientOp<float, CPUContext>);\n\nREGISTER_CPU_OPERATOR(\n    SigmoidCrossEntropyWithLogits,\n    SigmoidCrossEntropyWithLogitsOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    SigmoidCrossEntropyWithLogitsGradient,\n    SigmoidCrossEntropyWithLogitsGradientOp<float, CPUContext>);\n\nREGISTER_CPU_OPERATOR(\n    WeightedSigmoidCrossEntropyWithLogits,\n    WeightedSigmoidCrossEntropyWithLogitsOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    WeightedSigmoidCrossEntropyWithLogitsGradient,\n    WeightedSigmoidCrossEntropyWithLogitsGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(MakeTwoClass)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(\n        [](const OperatorDef& /* unused */, const vector<TensorShape>& in) {\n          vector<TensorShape> out(1);\n          out[0].add_dims(in[0].dims(0));\n          out[0].add_dims(2);\n          return out;\n        })\n    .SetDoc(R\"DOC(\nGiven a vector of probabilities, this operator transforms this into a 2-column\n matrix with complimentary probabilities for binary classification. In explicit\n terms, given the vector X, the output Y is vstack(1 - X, X).\n  )DOC\")\n    .Input(0, \"X\", \"Input vector of probabilities\")\n    .Output(\n        0,\n        \"Y\",\n        \"2-column matrix with complimentary probabilities of X for \"\n        \"binary classification\");\n\nOPERATOR_SCHEMA(MakeTwoClassGradient)\n  .NumInputs(1)\n  .NumOutputs(1);\n\nOPERATOR_SCHEMA(SigmoidCrossEntropyWithLogits)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInputDim(0, 0)\n    .SetDoc(R\"DOC(\nGiven two matrices logits and targets, of same shape,\n(batch_size, num_classes), computes the sigmoid cross entropy between the two.\nReturns a tensor of shape (batch_size,) of losses for each example.\n)DOC\")\n    .Input(0, \"logits\", \"matrix of logits for each example and class.\")\n    .Input(1, \"targets\", \"matrix of targets, same shape as logits.\")\n    .Output(0, \"xentropy\", \"Vector with the total xentropy for each example.\");\n\nOPERATOR_SCHEMA(SigmoidCrossEntropyWithLogitsGradient)\n    .NumInputs(3)\n    .NumOutputs(1);\n\nOPERATOR_SCHEMA(WeightedSigmoidCrossEntropyWithLogits)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInputDim(0, 0)\n    .SetDoc(R\"DOC(\nGiven three matrices: logits, targets, weights, all of the same shape,\n(batch_size, num_classes), computes the weighted sigmoid cross entropy between\nlogits and targets. Specifically, at each position r,c, this computes\nweights[r, c] * crossentropy(sigmoid(logits[r, c]), targets[r, c]), and then\naverages over each row.\nReturns a tensor of shape (batch_size,) of losses for each example.\n)DOC\")\n    .Input(0, \"logits\", \"matrix of logits for each example and class.\")\n    .Input(1, \"targets\", \"matrix of targets, same shape as logits.\")\n    .Input(2, \"weights\", \"matrix of weights, same shape as logits.\")\n    .Output(0, \"xentropy\", \"Vector with the total xentropy for each example.\");\n\nOPERATOR_SCHEMA(WeightedSigmoidCrossEntropyWithLogitsGradient)\n    .NumInputs(4)\n    .NumOutputs(1);\n\nstruct GetMakeTwoClassGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"MakeTwoClassGradient\",\n        \"\",\n        vector<string>{GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(MakeTwoClass, GetMakeTwoClassGradient);\n\nstruct GetSigmoidCrossEntropyWithLogitsGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SigmoidCrossEntropyWithLogitsGradient\",\n        \"\",\n        vector<string>{GO(0), I(0), I(1)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(\n    SigmoidCrossEntropyWithLogits,\n    GetSigmoidCrossEntropyWithLogitsGradient);\n\nstruct GetWeightedSigmoidCrossEntropyWithLogitsGradient\n    : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"WeightedSigmoidCrossEntropyWithLogitsGradient\",\n        \"\",\n        vector<string>{GO(0), I(0), I(1), I(2)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(\n    WeightedSigmoidCrossEntropyWithLogits,\n    GetWeightedSigmoidCrossEntropyWithLogitsGradient);\n\nREGISTER_CPU_OPERATOR(CrossEntropy,\n                      CrossEntropyOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(CrossEntropyGradient,\n                      CrossEntropyGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(CrossEntropy)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInputDim(0, 0)\n    .SetDoc(R\"DOC(\nOperator computes the cross entropy between the input and the label set. In\n practice, it is most commonly used at the end of models, after the SoftMax\n operator and before the AveragedLoss operator. Note that CrossEntropy\n assumes that the soft labels provided is a 2D array of size N x D\n (batch size x number of classes). Each entry in the 2D label corresponds to\n the soft label for the input, where each element represents the correct\n probability of the class being selected. As such, each element must be between\n 0 and 1, and all elements in an entry must sum to 1. The formula used is:\n\n                Y[i] = sum_j (label[i][j] * log(X[i][j]))\n\n where (i, j) is the classifier's prediction of the jth class (the correct one),\n and i is the batch size. Each log has a lower limit for numerical stability.\n)DOC\")\n    .Input(\n        0,\n        \"X\",\n        \"Input blob from the previous layer, which is almost always \"\n        \"the result of a softmax operation; X is a 2D array of size N x D, where N \"\n        \"is the batch size and D is the number of classes\")\n    .Input(1, \"label\", \"Blob containing the labels used to compare the input\")\n    .Output(0, \"Y\", \"Output blob after the cross entropy computation\");\nOPERATOR_SCHEMA(CrossEntropyGradient)\n  .NumInputs(3)\n  .NumOutputs(1);\n\nclass GetCrossEntropyGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"CrossEntropyGradient\", \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(CrossEntropy, GetCrossEntropyGradient);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/cross_entropy_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <assert.h>\n#include <cub/block/block_reduce.cuh>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/cross_entropy_op.h\"\n#include \"caffe2/operators/operator_fallback_gpu.h\"\n\nnamespace caffe2 {\n\nnamespace {\n__global__ void LabelCrossEntropyKernel(\n    const int N, const int D, const float* Xdata, const int* labeldata,\n    const float log_threshold, float* Ydata) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    CUDA_KERNEL_ASSERT(labeldata[i] >= 0 && labeldata[i] < D);\n    Ydata[i] = -logf(max(Xdata[i * D + labeldata[i]], log_threshold));\n  }\n}\n__global__ void LabelCrossEntropyGradientKernel(\n    const int N, const int D, const float* Xdata, const int* labeldata,\n    const float* dYdata, const float log_threshold, float* dXdata) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    int idx = i * D + labeldata[i];\n    dXdata[idx] = - dYdata[i] / max(Xdata[idx], log_threshold);\n  }\n}\n}  // namespace\n\ntemplate <>\nbool LabelCrossEntropyOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& label = Input(1);\n  auto* Y = Output(0);\n  int N, D;\n  if (X.ndim() > 1) {\n    N = X.dim32(0);\n    D = X.size_from_dim(1);\n  } else {\n    N = 1;\n    D = X.dim32(0);\n  }\n  CAFFE_ENFORCE(\n      (label.ndim() == 1) || (label.ndim() == 2 && label.dim32(1) == 1));\n  CAFFE_ENFORCE_EQ(label.dim32(0), N);\n  Y->Resize(vector<TIndex>(size_t(1), N));\n  LabelCrossEntropyKernel<<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS,\n                            0, context_.cuda_stream()>>>(\n      N, D, X.data<float>(), label.data<int>(), kLOG_THRESHOLD(),\n      Y->mutable_data<float>());\n  return true;\n}\n\ntemplate <>\nbool LabelCrossEntropyGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& label = Input(1);\n  auto& dY = Input(2);\n  auto* dX = Output(0);\n  int N, D;\n  if (X.ndim() > 1) {\n    N = X.dim32(0);\n    D = X.size_from_dim(1);\n  } else {\n    N = 1;\n    D = X.dim32(0);\n  }\n  CAFFE_ENFORCE(\n      (label.ndim() == 1) || (label.ndim() == 2 && label.dim32(1) == 1));\n  CAFFE_ENFORCE_EQ(label.dim32(0), N);\n  CAFFE_ENFORCE_EQ(dY.ndim(), 1);\n  CAFFE_ENFORCE_EQ(dY.dim32(0), N);\n  dX->ResizeLike(X);\n  math::Set<float, CUDAContext>(\n      dX->size(), 0.f, dX->mutable_data<float>(), &context_);\n  LabelCrossEntropyGradientKernel<<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS,\n                                    0, context_.cuda_stream()>>>(\n      N, D, X.data<float>(), label.data<int>(), dY.data<float>(),\n      kLOG_THRESHOLD(), dX->mutable_data<float>());\n  return true;\n}\n\nnamespace {\n__global__ void MakeTwoClassKernel(\n    const int N, const float* Xdata, float* Ydata) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Ydata[i * 2] = 1.0 - Xdata[i];\n    Ydata[i * 2 + 1] = Xdata[i];\n  }\n}\n__global__ void MakeTwoClassGradientKernel(\n    const int N, const float* dYdata, float* dXdata) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dXdata[i] = dYdata[i * 2 + 1] - dYdata[i * 2];\n  }\n}\n}  // namespace\n\ntemplate <>\nbool MakeTwoClassOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  auto shape = X.dims();\n  shape.push_back(2);\n  CAFFE_ENFORCE_LT(X.size(), std::numeric_limits<int>::max() / 2);\n  Y->Resize(shape);\n  int N = X.size();\n  MakeTwoClassKernel<<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS,\n                       0, context_.cuda_stream()>>>(\n      N, X.data<float>(), Y->mutable_data<float>());\n  return true;\n}\n\ntemplate <>\nbool MakeTwoClassGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& dY = Input(0);\n  auto* dX = Output(0);\n  auto shape = dY.dims();\n  CAFFE_ENFORCE_GE(shape.size(), 1);\n  CAFFE_ENFORCE_EQ(shape.back(), 2);\n  shape.pop_back();\n  CAFFE_ENFORCE_LT(dY.size(), std::numeric_limits<int>::max());\n  dX->Resize(shape);\n  int N = dX->size();\n  MakeTwoClassGradientKernel<<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS,\n                               0, context_.cuda_stream()>>>(\n      N, dY.data<float>(), dX->mutable_data<float>());\n  return true;\n}\n\nnamespace {\n\n__device__ float sigmoid_xent_forward(float lgt, float tgt) {\n  return lgt * (tgt - (lgt >= 0)) - log(1 + exp(lgt - 2 * lgt * (lgt >= 0)));\n}\n\n__device__ float sigmoid_xent_backward(float lgt, float tgt) {\n  return tgt - 1. / (1. + exp(-lgt));\n}\n\n__global__ void SigmoidCrossEntropyWithLogitsKernel(\n    const int outer_size,\n    const int inner_size,\n    const float* logits_ptr,\n    const float* targets_ptr,\n    float* out_ptr) {\n  int i = blockIdx.x;\n  int last_idx = (i + 1) * inner_size;\n  float value = 0;\n  for (int in_idx = i * inner_size + threadIdx.x; in_idx < last_idx;\n       in_idx += blockDim.x) {\n    value += sigmoid_xent_forward(logits_ptr[in_idx], targets_ptr[in_idx]);\n  }\n\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  float sum = BlockReduce(temp_storage).Sum(value);\n  if (threadIdx.x == 0) {\n    out_ptr[i] = -sum / inner_size;\n  }\n}\n\n__global__ void SigmoidCrossEntropyGradientWithLogitsKernel(\n    const int outer_size,\n    const int inner_size,\n    const float* g_ptr,\n    const float* logits_ptr,\n    const float* targets_ptr,\n    float* out_ptr) {\n  CUDA_1D_KERNEL_LOOP(in_idx, outer_size * inner_size) {\n    int i = in_idx / inner_size;\n    auto g_factor = -g_ptr[i] / inner_size;\n    out_ptr[in_idx] = g_factor *\n        sigmoid_xent_backward(logits_ptr[in_idx], targets_ptr[in_idx]);\n  }\n}\n} // namespace\n\ntemplate <>\nbool SigmoidCrossEntropyWithLogitsOp<float, CUDAContext>::RunOnDevice() {\n  auto& logits = Input(0);\n  auto& targets = Input(1);\n  CAFFE_ENFORCE(logits.dims() == targets.dims());\n  const auto inner_size = logits.ndim() > 0 ? logits.dims().back() : 1;\n  const auto outer_size = logits.size() / inner_size;\n\n  auto* out = Output(0);\n  if (logits.ndim() == 0) {\n    out->Resize(std::vector<TIndex>{});\n  } else {\n    std::vector<TIndex> dims(logits.dims().begin(), logits.dims().end() - 1);\n    out->Resize(dims);\n  }\n  auto* out_ptr = out->mutable_data<float>();\n\n  auto* logits_ptr = logits.data<float>();\n  auto* targets_ptr = targets.data<float>();\n\n  SigmoidCrossEntropyWithLogitsKernel<<<\n      outer_size,\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      outer_size, inner_size, logits_ptr, targets_ptr, out_ptr);\n  return true;\n}\n\ntemplate <>\nbool SigmoidCrossEntropyWithLogitsGradientOp<float, CUDAContext>::\n    RunOnDevice() {\n  auto& g = Input(0);\n  auto& logits = Input(1);\n  auto& targets = Input(2);\n  CAFFE_ENFORCE(logits.dims() == targets.dims());\n  const auto inner_size = logits.ndim() > 0 ? logits.dims().back() : 1;\n  const auto outer_size = logits.size() / inner_size;\n  CAFFE_ENFORCE(g.size() == outer_size);\n\n  auto* out = Output(0);\n  out->ResizeLike(logits);\n  auto* out_ptr = out->mutable_data<float>();\n\n  auto* logits_ptr = logits.data<float>();\n  auto* targets_ptr = targets.data<float>();\n  auto* g_ptr = g.data<float>();\n\n  SigmoidCrossEntropyGradientWithLogitsKernel<<<\n      CAFFE_GET_BLOCKS(outer_size * inner_size),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      outer_size, inner_size, g_ptr, logits_ptr, targets_ptr, out_ptr);\n  return true;\n}\n\nnamespace {\n\n__global__ void WeightedSigmoidCrossEntropyWithLogitsKernel(\n    const int outer_size,\n    const int inner_size,\n    const float* logits_ptr,\n    const float* targets_ptr,\n    const float* weights_ptr,\n    float* out_ptr) {\n  int i = blockIdx.x;\n  int last_idx = (i + 1) * inner_size;\n  float value = 0;\n  for (int in_idx = i * inner_size + threadIdx.x; in_idx < last_idx;\n       in_idx += blockDim.x) {\n    value += sigmoid_xent_forward(logits_ptr[in_idx], targets_ptr[in_idx]) *\n        weights_ptr[in_idx];\n  }\n\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  float sum = BlockReduce(temp_storage).Sum(value);\n  if (threadIdx.x == 0) {\n    out_ptr[i] = -sum / inner_size;\n  }\n}\n\n__global__ void WeightedSigmoidCrossEntropyGradientWithLogitsKernel(\n    const int outer_size,\n    const int inner_size,\n    const float* g_ptr,\n    const float* logits_ptr,\n    const float* targets_ptr,\n    const float* weights_ptr,\n    float* out_ptr) {\n  CUDA_1D_KERNEL_LOOP(in_idx, outer_size * inner_size) {\n    int i = in_idx / inner_size;\n    auto g_factor = -g_ptr[i] / inner_size;\n    out_ptr[in_idx] = g_factor *\n        sigmoid_xent_backward(logits_ptr[in_idx], targets_ptr[in_idx]) *\n        weights_ptr[in_idx];\n  }\n}\n} // namespace\n\ntemplate <>\nbool WeightedSigmoidCrossEntropyWithLogitsOp<float, CUDAContext>::\n    RunOnDevice() {\n  auto& logits = Input(0);\n  auto& targets = Input(1);\n  auto& weights = Input(2);\n  CAFFE_ENFORCE(logits.dims() == targets.dims());\n  CAFFE_ENFORCE(weights.dims() == targets.dims());\n  const auto inner_size = logits.ndim() > 0 ? logits.dims().back() : 1;\n  const auto outer_size = logits.size() / inner_size;\n\n  auto* out = Output(0);\n  if (logits.ndim() == 0) {\n    out->Resize(std::vector<TIndex>{});\n  } else {\n    std::vector<TIndex> dims(logits.dims().begin(), logits.dims().end() - 1);\n    out->Resize(dims);\n  }\n  auto* out_ptr = out->mutable_data<float>();\n\n  auto* logits_ptr = logits.data<float>();\n  auto* targets_ptr = targets.data<float>();\n  auto* weights_ptr = weights.data<float>();\n\n  WeightedSigmoidCrossEntropyWithLogitsKernel<<<\n      outer_size,\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      outer_size, inner_size, logits_ptr, targets_ptr, weights_ptr, out_ptr);\n  return true;\n}\n\ntemplate <>\nbool WeightedSigmoidCrossEntropyWithLogitsGradientOp<float, CUDAContext>::\n    RunOnDevice() {\n  auto& g = Input(0);\n  auto& logits = Input(1);\n  auto& targets = Input(2);\n  auto& weights = Input(3);\n  CAFFE_ENFORCE(logits.dims() == targets.dims());\n  CAFFE_ENFORCE(weights.dims() == targets.dims());\n  const auto inner_size = logits.ndim() > 0 ? logits.dims().back() : 1;\n  const auto outer_size = logits.size() / inner_size;\n  CAFFE_ENFORCE(g.size() == outer_size);\n\n  auto* out = Output(0);\n  out->ResizeLike(logits);\n  auto* out_ptr = out->mutable_data<float>();\n\n  auto* logits_ptr = logits.data<float>();\n  auto* targets_ptr = targets.data<float>();\n  auto* weights_ptr = weights.data<float>();\n  auto* g_ptr = g.data<float>();\n\n  WeightedSigmoidCrossEntropyGradientWithLogitsKernel<<<\n      CAFFE_GET_BLOCKS(outer_size * inner_size),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      outer_size,\n      inner_size,\n      g_ptr,\n      logits_ptr,\n      targets_ptr,\n      weights_ptr,\n      out_ptr);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(LabelCrossEntropy,\n                       LabelCrossEntropyOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(LabelCrossEntropyGradient,\n                       LabelCrossEntropyGradientOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(\n    SigmoidCrossEntropyWithLogits,\n    SigmoidCrossEntropyWithLogitsOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    SigmoidCrossEntropyWithLogitsGradient,\n    SigmoidCrossEntropyWithLogitsGradientOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(\n    WeightedSigmoidCrossEntropyWithLogits,\n    WeightedSigmoidCrossEntropyWithLogitsOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    WeightedSigmoidCrossEntropyWithLogitsGradient,\n    WeightedSigmoidCrossEntropyWithLogitsGradientOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(MakeTwoClass,\n                       MakeTwoClassOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(MakeTwoClassGradient,\n                       MakeTwoClassGradientOp<float, CUDAContext>);\n\n//TODO(surya) Add full GPU/CUDA support for the CrossEntropyOp\nREGISTER_CUDA_OPERATOR(CrossEntropy,\n                       GPUFallbackOp<CrossEntropyOp<float, CPUContext>>);\nREGISTER_CUDA_OPERATOR(CrossEntropyGradient,\n                       GPUFallbackOp<CrossEntropyGradientOp<float, CPUContext>>);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/cross_entropy_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CROSS_ENTROPY_OP_H_\n#define CAFFE2_OPERATORS_CROSS_ENTROPY_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass LabelCrossEntropyOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(LabelCrossEntropyOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n protected:\n  static constexpr T kLOG_THRESHOLD() {\n    return static_cast<T>(1e-20);\n  }\n  // Input: X, label\n  // Output: Y\n};\n\ntemplate <typename T, class Context>\nclass LabelCrossEntropyGradientOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(LabelCrossEntropyGradientOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n protected:\n  // Input: X, label, dY\n  // Ouptut: dX. There is no gradient with respect to the label.\n  static constexpr T kLOG_THRESHOLD() {\n    return static_cast<T>(1e-20);\n  }\n};\n\n// Hacky: turns a vector of probabilities into a 2-column matrix with\n// complimentary probabilities for binary classification\ntemplate <typename T, class Context>\nclass MakeTwoClassOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(MakeTwoClassOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n protected:\n  // Input: X\n  // Output: Y = vstack(1-X, X)\n};\n\ntemplate <typename T, class Context>\nclass MakeTwoClassGradientOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(MakeTwoClassGradientOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n protected:\n  // Input: dY\n  // Ouptut: dX\n};\n\ntemplate <typename T, class Context>\nclass SigmoidCrossEntropyWithLogitsOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(SigmoidCrossEntropyWithLogitsOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n};\n\ntemplate <typename T, class Context>\nclass SigmoidCrossEntropyWithLogitsGradientOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(SigmoidCrossEntropyWithLogitsGradientOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n};\n\ntemplate <typename T, class Context>\nclass WeightedSigmoidCrossEntropyWithLogitsOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(WeightedSigmoidCrossEntropyWithLogitsOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n};\n\ntemplate <typename T, class Context>\nclass WeightedSigmoidCrossEntropyWithLogitsGradientOp final\n    : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(WeightedSigmoidCrossEntropyWithLogitsGradientOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n};\n\ntemplate <typename T, class Context>\nclass CrossEntropyOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(CrossEntropyOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n protected:\n  // Input: X, label\n  // Output: Y\n  static constexpr T kLOG_THRESHOLD() {\n    return static_cast<T>(1e-20);\n  }\n};\n\ntemplate <typename T, class Context>\nclass CrossEntropyGradientOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(CrossEntropyGradientOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n protected:\n  // Input: X, label, dY\n  // Ouptut: dX. There is no gradient with respect to the label.\n  static constexpr T kLOG_THRESHOLD() {\n    return static_cast<T>(1e-20);\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CROSS_ENTROPY_OP_H_\n"
  },
  {
    "path": "caffe2/operators/dataset_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/dataset_ops.h\"\n\n#include <memory>\n#include <mutex>\n#include <string>\n#include <vector>\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/string_utils.h\"\n\nnamespace caffe2 {\n\nCAFFE_KNOWN_TYPE(std::unique_ptr<dataset_ops::TreeCursor>);\nCAFFE_KNOWN_TYPE(dataset_ops::TensorVectorPtr<CPUContext>);\nCAFFE_KNOWN_TYPE(dataset_ops::SharedTensorVectorPtr);\n\nnamespace dataset_ops {\nnamespace {\n\nconst char kDatasetFieldSeparator = ':';\nconst char* kDatasetLengthField = \"lengths\";\n\n// how much percent to grow the dataset when needed\nconst int kDatasetGrowthPct = 40;\n\n} // namespace\n\nTreeIterator::TreeIterator(const std::vector<std::string>& fields) {\n  // populate field vector and split field names\n  fields_.resize(fields.size());\n  std::vector<std::vector<std::string>> nameParts(fields_.size());\n  for (int i = 0; i < fields.size(); ++i) {\n    auto& field = fields_.at(i);\n    field.name = fields[i];\n    field.id = i;\n    field.lengthFieldId = -1;\n    nameParts.at(i) = split(kDatasetFieldSeparator, field.name);\n  }\n\n  // populate lengthFields\n  for (const auto& field : fields_) {\n    const auto& parts = nameParts.at(field.id);\n    if (!parts.empty() && parts.back() == kDatasetLengthField) {\n      lengthFieldIds_.push_back(field.id);\n    }\n  }\n\n  // find length-field with maximum prefix matching for each field\n  for (auto& field : fields_) {\n    // by default, we are matching against the root domain\n    int maxMatchLevel = 1;\n    int maxMatchLengthFieldId = -1;\n    for (int j = 0; j < numLengthFields(); ++j) {\n      const auto& lenField = lengthField(j);\n      // a length field can't have itself as its length field\n      if (field.id == lenField.id) {\n        continue;\n      }\n      auto lf = nameParts.at(lenField.id);\n      auto lfEnd = lf.end() - 1;\n      // check whether this lengthField is a prefix for this field name\n      if (std::mismatch(lf.begin(), lfEnd, nameParts.at(field.id).begin())\n              .first != lfEnd) {\n        continue;\n      }\n      if (lf.size() > maxMatchLevel) {\n        maxMatchLevel = lf.size();\n        maxMatchLengthFieldId = j;\n      }\n    }\n    field.lengthFieldId = maxMatchLengthFieldId;\n  }\n\n  // check that fields are topologically sorted\n  // (no length field depends on a length defined afterwards)\n  for (const auto& field : fields_) {\n    const auto* lengthField = lengthFieldFor(field);\n    CAFFE_ENFORCE(\n        (lengthField == nullptr) || (lengthField->id < field.id),\n        \"Error: Field \",\n        field.id,\n        \" (\",\n        field.name,\n        \") \",\n        \"depends on a field defined afterwards: \",\n        lengthField->id,\n        \" (\",\n        lengthField->name,\n        \").\");\n  }\n}\n\nvoid TreeIterator::advance(\n    const std::vector<const TLength*>& lengths,\n    std::vector<TOffset>& offsets,\n    std::vector<TOffset>& sizes,\n    std::vector<TOffset>& limits,\n    TOffset num) {\n  std::vector<TOffset> newOffsets;\n  CAFFE_ENFORCE_EQ(lengths.size(), numLengthFields());\n  CAFFE_ENFORCE_EQ(offsets.size(), numOffsetFields());\n  sizes.resize(offsets.size());\n  newOffsets.resize(offsets.size());\n  // first index, top level\n  {\n    auto limit = limits[0];\n    auto offset = offsets[0];\n    CAFFE_ENFORCE(limit >= offset, \"Tried to advance past end of cursor.\");\n    TOffset total = std::min(limit - offset, num);\n    sizes[0] = total;\n    newOffsets[0] = offset + total;\n  }\n  // child indices\n  for (int j = 1; j < numOffsetFields(); ++j) {\n    TOffset total = 0;\n    int parentOffsetId = offsetFieldIdFor(lengthField(j - 1));\n    const TLength* length = lengths[j - 1] + offsets[parentOffsetId];\n    for (int k = 0; k < sizes[parentOffsetId]; ++k) {\n      total += *(length++);\n    }\n    auto offset = offsets[j];\n    CAFFE_ENFORCE(\n        offset + total <= limits[j],\n        \"Inconsistent field length: \",\n        \"tried to advance past the end of field \",\n        j);\n    sizes[j] = total;\n    newOffsets[j] = offset + total;\n  }\n  offsets = newOffsets;\n}\n\nTreeWalker::TreeWalker(const vector<const Blob*>& inputs, TreeCursor& cursor)\n    : inputs_(inputs), cursor_(cursor), sizes_(cursor.it.numOffsetFields()) {\n  CAFFE_ENFORCE_EQ(inputs.size(), cursor.it.fields().size());\n  if (cursor.offsets.empty()) {\n    cursor.offsets.assign(cursor.it.numOffsetFields(), 0);\n  }\n\n  for (int fieldId = 0; fieldId < cursor_.it.fields().size(); ++fieldId) {\n    fields_.emplace_back(*this, fieldId);\n  }\n\n  gatherLengthData();\n\n  gatherSizeLimits();\n\n  // The invariant we hold is that we are always one step ahead\n  advance();\n}\n\nvoid TreeWalker::advance() {\n  prevOffsets_ = cursor_.offsets;\n  cursor_.it.advance(lengths_, cursor_.offsets, sizes_, limits_, 1);\n}\n\nstd::vector<TIndex> TreeWalker::fieldDim(int fieldId) const {\n  auto tensorDim = input(fieldId).dims();\n  tensorDim[0] = sizes_[lengthIdx(fieldId)];\n  return tensorDim;\n}\n\nvoid* TreeWalker::fieldPtr(int fieldId) const {\n  auto& in = input(fieldId);\n  return (char*)in.raw_data() +\n      offset(fieldId) * in.size_from_dim(1) * in.meta().itemsize();\n}\n\nvoid TreeWalker::gatherLengthData() {\n  static const TLength lenZero = 0;\n  lengths_.resize(cursor_.it.numLengthFields());\n  for (int i = 0; i < lengths_.size(); ++i) {\n    auto& in = input(cursor_.it.lengthField(i).id);\n    if (in.size() > 0) {\n      lengths_[i] = in.data<int>();\n    } else {\n      lengths_[i] = &lenZero;\n    }\n  }\n}\n\nvoid TreeWalker::gatherSizeLimits() {\n  limits_.assign(sizes_.size(), std::numeric_limits<TOffset>::max());\n  for (auto fieldId = 0; fieldId < cursor_.it.fields().size(); ++fieldId) {\n    auto lengthFieldIdx = lengthIdx(fieldId);\n    limits_[lengthFieldIdx] =\n        std::min(limits_[lengthFieldIdx], (TOffset)input(fieldId).dims()[0]);\n  }\n}\n\nnamespace {\n\nclass CreateTreeCursorOp : public Operator<CPUContext> {\n public:\n  CreateTreeCursorOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        fields_(OperatorBase::GetRepeatedArgument<std::string>(\"fields\")) {}\n\n  bool RunOnDevice() override {\n    *OperatorBase::Output<std::unique_ptr<TreeCursor>>(0) =\n        std::unique_ptr<TreeCursor>(new TreeCursor(TreeIterator(fields_)));\n    return true;\n  }\n\n private:\n  std::vector<std::string> fields_;\n};\n\nclass ResetCursorOp : public Operator<CPUContext> {\n public:\n  ResetCursorOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& cursor = OperatorBase::Input<std::unique_ptr<TreeCursor>>(0);\n    std::lock_guard<std::mutex> lock(cursor->mutex_);\n    cursor->offsets.clear();\n    return true;\n  }\n};\n\nclass CheckDatasetConsistencyOp : public Operator<CPUContext> {\n public:\n  CheckDatasetConsistencyOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        iterator_(OperatorBase::GetRepeatedArgument<std::string>(\"fields\")) {}\n\n  bool RunOnDevice() override {\n    std::vector<const TLength*> lengths;\n    std::vector<TOffset> limits;\n    std::vector<TOffset> sizes;\n    std::vector<TOffset> offsets;\n    CAFFE_ENFORCE(\n        InputSize() == iterator_.fields().size(),\n        \"Invalid number of fields. Expected \",\n        iterator_.fields().size(),\n        \", got \",\n        InputSize());\n    sizes.resize(iterator_.numOffsetFields());\n    // gather length data\n    lengths.resize(iterator_.numLengthFields());\n    for (int i = 0; i < lengths.size(); ++i) {\n      lengths[i] = Input(iterator_.lengthField(i).id).data<TLength>();\n    }\n    // gather size limits\n    limits.assign(sizes.size(), std::numeric_limits<TOffset>::max());\n    for (int i = 0; i < iterator_.fields().size(); ++i) {\n      int lengthIdx = iterator_.fields()[i].lengthFieldId + 1;\n      CAFFE_ENFORCE_GT(Input(i).ndim(), 0);\n      TOffset size = (TOffset)Input(i).dims()[0];\n      if (limits[lengthIdx] == std::numeric_limits<TOffset>::max()) {\n        limits[lengthIdx] = size;\n      } else {\n        CAFFE_ENFORCE(\n            limits[lengthIdx] == size,\n            \"Inconsistent sizes for fields belonging to same domain.\",\n            \" Field: \",\n            i,\n            \" (\",\n            iterator_.fields()[i].name,\n            \"); Length field index: \",\n            lengthIdx,\n            \"); Previous size: \",\n            limits[lengthIdx],\n            \"; New size: \",\n            size);\n      }\n    }\n    // advance to the end\n    offsets.assign(sizes.size(), 0);\n    iterator_.advance(lengths, offsets, sizes, limits, limits[0]);\n    for (int i = 0; i < limits.size(); ++i) {\n      CAFFE_ENFORCE(limits[i] == offsets[i]);\n    }\n    return true;\n  }\n\n private:\n  TreeIterator iterator_;\n};\n\nclass PackRecordsOp : public Operator<CPUContext> {\n public:\n  PackRecordsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        fields_(OperatorBase::GetRepeatedArgument<std::string>(\"fields\")) {}\n\n  bool RunOnDevice() override {\n    // There should be one input per field\n    CAFFE_ENFORCE_EQ(InputSize(), fields_.size());\n    CAFFE_ENFORCE_EQ(OutputSize(), 1);\n\n    TreeCursor cursor((TreeIterator(fields_)));\n\n    TreeWalker walker(Inputs(), cursor);\n\n    Output(0)->Resize(walker.size());\n\n    // Output(0)->raw_mutable_data(TypeMeta::Make<SharedTensorVectorPtr>()));\n    auto* dst = Output(0)->mutable_data<SharedTensorVectorPtr>();\n\n    for (int batchId = 0; batchId < walker.size(); ++batchId) {\n      dst[batchId] = std::make_shared<std::vector<TensorCPU>>();\n      dst[batchId]->reserve(walker.fields().size());\n\n      for (const auto& field : walker.fields()) {\n        dst[batchId]->emplace_back(field.dim());\n        auto& tensor = dst[batchId]->back();\n        context_.template CopyItems<CPUContext, CPUContext>(\n            field.meta(),\n            tensor.size(),\n            field.ptr() /* src */,\n            tensor.raw_mutable_data(field.meta()) /* dst */);\n      }\n\n      walker.advance();\n    }\n\n    return true;\n  }\n\n private:\n  std::vector<std::string> fields_;\n};\n\nclass UnPackRecordsOp : public Operator<CPUContext> {\n public:\n  UnPackRecordsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        fields_(OperatorBase::GetRepeatedArgument<std::string>(\"fields\")) {}\n\n  bool RunOnDevice() override {\n    const auto* inputs = Input(0).template data<SharedTensorVectorPtr>();\n    const auto numRows = Input(0).size();\n\n    CAFFE_ENFORCE_GE(numRows, 0);\n\n    auto numTensors = OutputSize();\n\n    // Precomputer the output sizes to avoid resizing\n    std::vector<std::vector<TIndex>> outputDims(numTensors);\n    std::vector<const TypeMeta*> metas(numTensors);\n\n    CAFFE_ENFORCE(\n        numRows > 0 || InputSize() > 1,\n        \"Unpacking empty record without shape will leave output blobs in \"\n        \"undefined state.\");\n\n    if (InputSize() == 1) {\n      getShapeAndMetaFromInput(outputDims, metas);\n    } else {\n      getShapeAndMetaFromPrototypeBlobs(outputDims, metas);\n    }\n\n    for (int i = 0; i < numRows; ++i) {\n      CAFFE_ENFORCE(inputs[i]);\n      for (int j = 0; j < inputs[i]->size(); ++j) {\n        const auto& input = inputs[i]->at(j);\n\n        // Checks to ensure that dimensions/sizes match\n        CAFFE_ENFORCE_EQ(outputDims[j].size(), input.ndim());\n        CAFFE_ENFORCE(*metas[j] == input.meta());\n        // We look from first dimension, because we concat on the first.\n        for (int k = 1; k < input.ndim(); ++k) {\n          CAFFE_ENFORCE_EQ(input.dims()[k], outputDims[j][k]);\n        }\n\n        outputDims[j][0] += input.dim(0);\n      }\n    }\n\n    // Resize to the final output size\n    std::vector<void*> destinations(numTensors);\n    for (int i = 0; i < numTensors; ++i) {\n      Output(i)->Resize(outputDims[i]);\n      destinations[i] = Output(i)->raw_mutable_data(*metas[i]);\n    }\n\n    for (int i = 0; i < numRows; ++i) {\n      for (int j = 0; j < numTensors; ++j) {\n        const auto& input = inputs[i]->at(j);\n\n        context_.CopyItems<CPUContext, CPUContext>(\n            *metas[j],\n            input.size(),\n            input.raw_data() /* src */,\n            destinations[j] /* dst */\n        );\n\n        destinations[j] =\n            (char*)destinations[j] + input.size() * input.itemsize();\n      }\n    }\n\n    return true;\n  }\n\n private:\n  void getShapeAndMetaFromInput(\n      std::vector<std::vector<TIndex>>& outputDims,\n      std::vector<const TypeMeta*>& metas) {\n    const auto* inputs = Input(0).template data<SharedTensorVectorPtr>();\n\n    const auto& inputZero = inputs[0];\n    CAFFE_ENFORCE(inputZero);\n\n    const auto numTensors = inputZero->size();\n\n    CAFFE_ENFORCE_EQ(numTensors, fields_.size());\n    CAFFE_ENFORCE_EQ(numTensors, OutputSize());\n\n    for (int i = 0; i < numTensors; ++i) {\n      outputDims[i] = inputZero->at(i).dims();\n      outputDims[i][0] = 0;\n      metas[i] = &inputZero->at(i).meta();\n    }\n  }\n\n  void getShapeAndMetaFromPrototypeBlobs(\n      std::vector<std::vector<TIndex>>& outputDims,\n      std::vector<const TypeMeta*>& metas) {\n    const auto numTensors = fields_.size();\n    CAFFE_ENFORCE_EQ(numTensors, InputSize() - 1);\n    CAFFE_ENFORCE_EQ(numTensors, OutputSize());\n    for (int i = 0; i < numTensors; ++i) {\n      const auto& input = Input(i + 1);\n      outputDims[i] = input.dims();\n      outputDims[i][0] = 0;\n      metas[i] = &input.meta();\n    }\n  }\n\n  std::vector<std::string> fields_;\n};\n\nclass ReadNextBatchOp : public Operator<CPUContext> {\n public:\n  ReadNextBatchOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        batchSize_(OperatorBase::GetSingleArgument<int>(\"batch_size\", 1)),\n        enforceBatchSize_(OperatorBase::GetSingleArgument<bool>(\n            \"enforce_batch_size\",\n            false)) {}\n\n  bool RunOnDevice() override {\n    auto& cursor = OperatorBase::Input<std::unique_ptr<TreeCursor>>(0);\n    CAFFE_ENFORCE(InputSize() == cursor->it.fields().size() + 1);\n    std::vector<const TLength*> lengths;\n    std::vector<TOffset> limits;\n    std::vector<TOffset> sizes;\n    std::vector<TOffset> offsets;\n    TLength lenZero = 0;\n    sizes.resize(cursor->it.numOffsetFields());\n    // gather length data\n    lengths.resize(cursor->it.numLengthFields());\n    for (int i = 0; i < lengths.size(); ++i) {\n      auto& a = Input(cursor->it.lengthField(i).id + 1);\n      if (a.size() > 0) {\n        lengths[i] = a.data<int>();\n      } else {\n        lengths[i] = &lenZero;\n      }\n    }\n    // gather size limits\n    limits.assign(sizes.size(), std::numeric_limits<TOffset>::max());\n    for (int i = 0; i < cursor->it.fields().size(); ++i) {\n      int lengthFieldIdx = cursor->it.fields()[i].lengthFieldId + 1;\n      limits[lengthFieldIdx] =\n          std::min(limits[lengthFieldIdx], (TOffset)Input(i + 1).dims()[0]);\n    }\n    // advance cursor\n    {\n      std::lock_guard<std::mutex> lock(cursor->mutex_);\n      if (cursor->offsets.empty()) {\n        cursor->offsets.assign(sizes.size(), 0);\n      }\n      offsets = cursor->offsets;\n      cursor->it.advance(lengths, cursor->offsets, sizes, limits, batchSize_);\n      if (enforceBatchSize_ && sizes[0] < batchSize_) {\n        // if we enforce batch_size but don't have enough rows left to\n        // complete a full batch, return empty for all columns.\n        // This signals end of dataset to the caller.\n        sizes.assign(sizes.size(), 0);\n      }\n    }\n    // gather data\n    std::vector<TIndex> outDim;\n    for (int i = 0; i < cursor->it.fields().size(); ++i) {\n      auto lengthIdx = cursor->it.fields()[i].lengthFieldId + 1;\n      auto size = sizes[lengthIdx];\n      auto offset = offsets[lengthIdx];\n      auto& in = Input(i + 1);\n      auto innerSize = in.size_from_dim(1);\n      outDim = in.dims();\n      outDim[0] = size;\n      auto* out = Output(i);\n      out->Resize(outDim);\n      void* src =\n          (char*)in.raw_data() + offset * innerSize * in.meta().itemsize();\n      void* dst = out->raw_mutable_data(in.meta()); // create the tensor\n      if (out->size() == 0) {\n        continue;\n      }\n      context_.template CopyItems<CPUContext, CPUContext>(\n          in.meta(), out->size(), src, dst);\n    }\n    return true;\n  }\n  int batchSize_;\n  bool enforceBatchSize_;\n};\n\nclass ComputeOffsetOp : public Operator<CPUContext> {\n public:\n  ComputeOffsetOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& cursor = OperatorBase::Input<std::unique_ptr<TreeCursor>>(0);\n    CAFFE_ENFORCE(InputSize() == cursor->it.fields().size() + 1);\n    auto* out = Output(0);\n    std::vector<const TLength*> lengths;\n    std::vector<TOffset> limits;\n    std::vector<TOffset> sizes;\n    std::vector<TOffset> offsets;\n    TLength lenZero = 0;\n    sizes.resize(cursor->it.numOffsetFields());\n    // gather length data\n    lengths.resize(cursor->it.numLengthFields());\n    for (int i = 0; i < lengths.size(); ++i) {\n      auto& a = Input(cursor->it.lengthField(i).id + 1);\n      if (a.size() > 0) {\n        lengths[i] = a.data<int>();\n      } else {\n        lengths[i] = &lenZero;\n      }\n    }\n    // gather size limits\n    limits.assign(sizes.size(), std::numeric_limits<TOffset>::max());\n    for (int i = 0; i < cursor->it.fields().size(); ++i) {\n      int lengthFieldIdx = cursor->it.fields()[i].lengthFieldId + 1;\n      limits[lengthFieldIdx] =\n          std::min(limits[lengthFieldIdx], (TOffset)Input(i + 1).dims()[0]);\n    }\n    out->Resize(limits.at(0) + 1, sizes.size());\n    auto* out_data = out->mutable_data<int64_t>();\n    for (int k = 0; k <= limits.at(0); k++) {\n      // advance cursor\n      if (cursor->offsets.empty()) {\n        cursor->offsets.assign(sizes.size(), 0);\n      }\n      // write output\n      std::copy(cursor->offsets.begin(), cursor->offsets.end(), out_data);\n      out_data += sizes.size();\n      cursor->it.advance(lengths, cursor->offsets, sizes, limits, 1);\n    }\n    cursor->offsets.assign(sizes.size(), 0); // reSet after getting meta info\n    return true;\n  }\n};\n\nclass SortAndShuffleOp : public Operator<CPUContext> {\n public:\n  SortAndShuffleOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        sort_by_field_idx_(\n            OperatorBase::GetSingleArgument<int>(\"sort_by_field_idx\", 1)),\n        batch_size_(OperatorBase::GetSingleArgument<int>(\"batch_size\", 1)),\n        shuffle_size_(OperatorBase::GetSingleArgument<int>(\"shuffle_size\", 1)) {\n  }\n\n  bool RunOnDevice() override {\n    auto& cursor = OperatorBase::Input<std::unique_ptr<TreeCursor>>(0);\n    CAFFE_ENFORCE(InputSize() == cursor->it.fields().size() + 1);\n    CAFFE_ENFORCE(-1 <= sort_by_field_idx_);\n    CAFFE_ENFORCE(cursor->it.fields().size() - sort_by_field_idx_ > 0);\n    int size;\n    if (sort_by_field_idx_ != -1) {\n      size = Input(sort_by_field_idx_ + 1).dims()[0];\n    } else {\n      size = Input(1).dims()[0];\n    }\n\n    CAFFE_ENFORCE(\n        batch_size_ > 0 && shuffle_size_ > 0 &&\n        0 < batch_size_ * shuffle_size_);\n    // adjust shuffle_size_ if it is too large\n    if (batch_size_ * shuffle_size_ > size) {\n      shuffle_size_ = size / batch_size_;\n    }\n\n    int num_batch = size / batch_size_;\n    auto* out = Output(0);\n    out->Resize(size);\n    auto* out_data = out->mutable_data<int64_t>();\n\n    vector<int> shuffle_idx(size);\n    iota(shuffle_idx.begin(), shuffle_idx.end(), 0);\n\n    if (sort_by_field_idx_ != -1) {\n      auto& sortblob = Input(sort_by_field_idx_ + 1);\n      auto* sortdata = sortblob.data<int>();\n      // must sort by a field at the root level\n      CAFFE_ENFORCE(\n          cursor->it.fields()[sort_by_field_idx_].lengthFieldId == -1);\n      sort(shuffle_idx.begin(), shuffle_idx.end(), [&sortdata](int i1, int i2) {\n        return sortdata[i1] < sortdata[i2];\n      });\n    }\n\n    if (batch_size_ * shuffle_size_ > 1) {\n      int offset = 0;\n      while (offset + batch_size_ * shuffle_size_ < size) {\n        std::shuffle(\n            shuffle_idx.begin() + offset,\n            shuffle_idx.begin() + offset + batch_size_ * shuffle_size_,\n            std::default_random_engine());\n        offset += batch_size_ * shuffle_size_;\n      }\n    }\n\n    vector<int> batch_idx(num_batch);\n    iota(batch_idx.begin(), batch_idx.end(), 0);\n    std::shuffle(\n        batch_idx.begin(), batch_idx.end(), std::default_random_engine());\n\n    for (int i = 0; i < num_batch; i++) {\n      std::copy(\n          shuffle_idx.begin() + batch_idx[i] * batch_size_,\n          shuffle_idx.begin() + (batch_idx[i] + 1) * batch_size_,\n          out_data);\n      out_data += batch_size_;\n    }\n    std::copy(\n        shuffle_idx.begin() + num_batch * batch_size_,\n        shuffle_idx.end(),\n        out_data);\n\n    return true;\n  }\n\n  int sort_by_field_idx_;\n  int batch_size_;\n  int shuffle_size_;\n};\n\nclass ReadRandomBatchOp : public Operator<CPUContext> {\n public:\n  ReadRandomBatchOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        batchSize_(OperatorBase::GetSingleArgument<int>(\"batch_size\", 1)),\n        enforceBatchSize_(\n            OperatorBase::GetSingleArgument<bool>(\"enforce_batch_size\", false)),\n        loopOver_(OperatorBase::GetSingleArgument<bool>(\"loop_over\", false)) {}\n  bool RunOnDevice() override {\n    auto& cursor = OperatorBase::Input<std::unique_ptr<TreeCursor>>(0);\n    auto& idxblob = Input(1);\n    auto& offsetsmat = Input(2);\n    CAFFE_ENFORCE(InputSize() == cursor->it.fields().size() + 3);\n    auto idxvec = idxblob.template data<int64_t>();\n    auto& offsetdim = offsetsmat.dims();\n    // gather data\n    std::vector<TIndex> outDim;\n    int64_t idx;\n    {\n      std::lock_guard<std::mutex> lock(cursor->mutex_);\n      cursor->offsets.resize(1);\n      idx = cursor->offsets.at(0);\n      // if we want to enforce batch size but we dont have a complete\n      // batch, skip the last rows.\n      if (enforceBatchSize_ && idx + batchSize_ > idxblob.size()) {\n        idx = idxblob.size();\n      }\n      if (loopOver_ && idx >= idxblob.size()) {\n        cursor->offsets.at(0) = 0;\n        idx = 0;\n      }\n      cursor->offsets.at(0) += batchSize_;\n    }\n\n    for (int i = 0; i < cursor->it.fields().size(); ++i) {\n      auto lengthIdx = cursor->it.fields()[i].lengthFieldId + 1;\n      auto& in = Input(i + 3);\n      outDim = in.dims();\n      outDim.at(0) = 0;\n      auto idxbegin = idx;\n      for (int j = 0; j < batchSize_; ++j) {\n        if (idx >= idxblob.size()) {\n          break;\n        }\n        CAFFE_ENFORCE(\n            (idxvec[idx] + 1) * offsetdim[1] + lengthIdx < offsetsmat.size(),\n            \"Out of bound when trying to get elem from offsetsmat\");\n        auto offsetptr = offsetsmat.template data<TOffset>() +\n            idxvec[idx] * offsetdim[1] + lengthIdx;\n        auto offset = *offsetptr;\n        auto size = *(offsetptr + offsetdim[1]) - offset;\n        outDim.at(0) += size; // accumulate over the batch\n        idx++;\n      }\n      idx = idxbegin; // reSet\n      auto* out = Output(i);\n      out->Resize(outDim);\n      if (out->size() == 0) {\n        continue;\n      }\n      auto dst = static_cast<char*>(out->raw_mutable_data(in.meta()));\n      int block_size = in.size() / in.dim(0);\n      auto block_bytesize = in.size_from_dim(1) * in.meta().itemsize();\n      CAFFE_ENFORCE(\n          block_bytesize == in.nbytes() / in.dim(0),\n          \"block_bytesize should be consistent with data dim\");\n      auto src_base = static_cast<const char*>(in.raw_data());\n      int start = 0;\n      for (int j = 0; j < batchSize_; ++j) {\n        if (idx >= idxblob.size()) {\n          break;\n        }\n        auto offsetptr = offsetsmat.template data<TOffset>() +\n            idxvec[idx] * offsetdim[1] + lengthIdx;\n        auto offset = *offsetptr;\n        auto size = *(offsetptr + offsetdim[1]) - offset;\n        // copy data\n        auto src = src_base + offset * block_bytesize;\n        context_.template CopyItems<CPUContext, CPUContext>(\n            in.meta(), size * block_size, src, dst + start * block_bytesize);\n        start += size;\n        idx++;\n      }\n      idx = idxbegin; // reSet\n    }\n    return true;\n  }\n  int batchSize_;\n  bool enforceBatchSize_;\n  bool loopOver_;\n};\n\ntemplate <class Context>\nclass AppendOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  AppendOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& a = Input(0);\n    auto& b = Input(1);\n    auto* c = Output(0);\n    CAFFE_ENFORCE(b.ndim() >= 1);\n    if (a.size() == 0 && a.dim(0) == 0) {\n      c->CopyFrom(b);\n      return true;\n    }\n    CAFFE_ENFORCE(&a == c, \"First argument must be in-place.\");\n    CAFFE_ENFORCE(c->ndim() == b.ndim());\n    CAFFE_ENFORCE(b.ndim() == c->ndim());\n    CAFFE_ENFORCE(a.meta() == b.meta());\n    for (int i = 1; i < a.ndim(); ++i) {\n      CAFFE_ENFORCE(a.dims()[i] == b.dims()[i]);\n    }\n    auto oldSize = c->size();\n    c->Extend(b.dims()[0], kDatasetGrowthPct, &context_);\n    auto* dst = (char*)c->raw_mutable_data() + oldSize * b.meta().itemsize();\n    context_.template CopyItems<Context, Context>(\n        b.meta(), b.size(), b.raw_data(), dst);\n    return true;\n  }\n};\n\ntemplate <class Context>\nclass AtomicAppendOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  AtomicAppendOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& mutex = OperatorBase::Input<std::unique_ptr<std::mutex>>(0);\n    const auto numFields = (InputSize() - 1) / 2;\n    CAFFE_ENFORCE(OutputSize() == numFields);\n\n    std::lock_guard<std::mutex> guard(*mutex);\n\n    // 1: checks\n    for (int i = 0; i < numFields; ++i) {\n      auto& a = Input(1 + i);\n      auto& b = Input(1 + i + numFields);\n      auto* c = Output(i);\n      CAFFE_ENFORCE(b.ndim() >= 1);\n      if (a.size() == 0) {\n        continue;\n      }\n      CAFFE_ENFORCE(\n          (void*)&a == (void*)c, \"Appended-to arguments must be in-place.\");\n      CAFFE_ENFORCE(c->ndim() == b.ndim());\n      CAFFE_ENFORCE(b.ndim() == c->ndim());\n      CAFFE_ENFORCE(a.meta() == b.meta());\n      for (int j = 1; j < a.ndim(); ++j) {\n        CAFFE_ENFORCE(a.dims()[j] == b.dims()[j]);\n      }\n    }\n\n    // 2: copies\n    for (int i = 0; i < numFields; ++i) {\n      auto& a = Input(1 + i);\n      auto& b = Input(1 + i + numFields);\n      auto* c = Output(i);\n      if (a.size() == 0 && a.dim(0) == 0) {\n        c->CopyFrom(b);\n        continue;\n      }\n      auto oldSize = c->size();\n      c->Extend(b.dims()[0], kDatasetGrowthPct, &context_);\n      auto* dst = (char*)c->raw_mutable_data() + oldSize * b.meta().itemsize();\n      context_.template CopyItems<Context, Context>(\n          b.meta(), b.size(), b.raw_data(), dst);\n    }\n    return true;\n  }\n};\n\ntemplate <class Context>\nclass CreateTensorVectorOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using Operator<Context>::Operator;\n\n  bool RunOnDevice() override {\n    auto ptr = make_unique<std::vector<Tensor<Context>>>();\n    *OperatorBase::Output<TensorVectorPtr<Context>>(TENSOR_VECTOR) =\n        std::move(ptr);\n    return true;\n  }\n\n private:\n  OUTPUT_TAGS(TENSOR_VECTOR);\n};\n\ntemplate <class Context>\nclass TensorVectorSizeOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(TensorVectorSizeOp);\n\n  bool RunOnDevice() override {\n    auto& vector_ptr =\n        OperatorBase::Input<TensorVectorPtr<Context>>(TENSOR_VECTOR);\n    auto* size = Output(SIZE);\n    size->Resize();\n    // 32-bit should be enough here\n    *size->template mutable_data<int32_t>() = vector_ptr->size();\n    return true;\n  }\n\n private:\n  INPUT_TAGS(TENSOR_VECTOR);\n  OUTPUT_TAGS(SIZE);\n};\n\ntemplate <class Context>\nclass ConcatTensorVectorOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using Operator<Context>::Operator;\n\n  bool RunOnDevice() override {\n    const TensorVectorPtr<Context>& tensorVector =\n        OperatorBase::Input<TensorVectorPtr<Context>>(TENSOR_VECTOR);\n\n    auto* tensor = Output(TENSOR);\n    CAFFE_ENFORCE(!tensorVector->empty());\n\n    vector<TIndex> outputDims(tensorVector->at(0).dims());\n    CAFFE_ENFORCE(outputDims.size() > 0);\n    for (int i = 1; i < tensorVector->size(); i++) {\n      // the tensor shapes are the same except for the first dimension\n      for (int j = 1; j < tensorVector->at(i).ndim(); j++) {\n        CAFFE_ENFORCE(outputDims[j] == tensorVector->at(i).dims()[j]);\n      }\n      CAFFE_ENFORCE(tensorVector->at(0).meta() == tensorVector->at(i).meta());\n      outputDims[0] += tensorVector->at(i).dims()[0];\n    }\n\n    tensor->Resize(outputDims);\n    TIndex offset = 0;\n    auto* dst = (char*)tensor->raw_mutable_data(tensorVector->at(0).meta());\n\n    for (const auto& t : *tensorVector) {\n      context_.template CopyItems<Context, Context>(\n          t.meta(), t.size(), t.raw_data(), dst + offset);\n      offset += t.nbytes();\n    }\n\n    return true;\n  }\n\n private:\n  INPUT_TAGS(TENSOR_VECTOR);\n  OUTPUT_TAGS(TENSOR);\n};\n\ntemplate <class Context>\nclass CollectTensorOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  CollectTensorOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        numToCollect_(\n            OperatorBase::GetSingleArgument<int>(\"num_to_collect\", -1)),\n        numVisited_(0) {\n    CAFFE_ENFORCE(numToCollect_ > 0);\n  }\n\n  bool RunOnDevice() override {\n    int pos = -1;\n    if (numVisited_ < numToCollect_) {\n      // append\n      pos = numVisited_;\n    } else {\n      auto& gen = context_.RandGenerator();\n      // uniform between [0, numVisited_]\n      std::uniform_int_distribution<int> uniformDist(0, numVisited_);\n      pos = uniformDist(gen);\n      if (pos >= numToCollect_) {\n        // discard\n        pos = -1;\n      }\n    }\n\n    for (int i = 0; i < OutputSize(); ++i) {\n      // TENSOR_VECTOR_IN is enforced inplace with TENSOR_VECTOR_OUT\n      TensorVectorPtr<Context>& tensorVector =\n          *OperatorBase::Output<TensorVectorPtr<Context>>(i);\n\n      if (numVisited_ >= numToCollect_) {\n        CAFFE_ENFORCE(\n            tensorVector->size() == numToCollect_,\n            \"TensorVecotor size = \",\n            tensorVector->size(),\n            \" is different from numToCollect = \",\n            numToCollect_);\n      }\n\n      const auto& tensor = Input(OutputSize() + i);\n\n      if (pos < 0) {\n        // discard\n        CAFFE_ENFORCE(numVisited_ >= numToCollect_);\n      } else if (pos >= tensorVector->size()) {\n        // append\n        tensorVector->push_back(Tensor<Context>());\n        tensorVector->back().template CopyFrom<Context, Context>(\n            tensor, &context_);\n      } else {\n        // replace\n        tensorVector->at(pos).template CopyFrom<Context, Context>(\n            tensor, &context_);\n      }\n    }\n\n    numVisited_++;\n    return true;\n  }\n\n private:\n  // number of tensors to collect\n  int numToCollect_;\n  // number of tensors visited\n  int numVisited_;\n};\n\nclass TrimDatasetOp : public Operator<CPUContext> {\n public:\n  TrimDatasetOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        iterator_(OperatorBase::GetRepeatedArgument<std::string>(\"fields\")),\n        multiple_of_(OperatorBase::GetSingleArgument<int>(\"multiple_of\", 1)) {\n    CAFFE_ENFORCE_GE(multiple_of_, 1);\n  }\n\n  bool RunOnDevice() override {\n    TreeCursor cursor(iterator_);\n    TreeWalker walker(Inputs(), cursor);\n\n    int trimmedSize = (walker.size() / multiple_of_) * multiple_of_;\n    if (trimmedSize == walker.size()) {\n      // we already satisfy the condition\n      return true;\n    }\n    // advance desired number of records\n    for (int i = 0; i < trimmedSize; ++i) {\n      walker.advance();\n    }\n    // trim each column to the offset\n    for (int col = 0; col < walker.fields().size(); ++col) {\n      auto newOuterSize = walker.fields().at(col).offset();\n      Output(col)->Shrink(newOuterSize);\n    }\n    return true;\n  }\n\n private:\n  TreeIterator iterator_;\n  int multiple_of_;\n};\n\nREGISTER_CPU_OPERATOR(CreateTreeCursor, CreateTreeCursorOp);\nREGISTER_CPU_OPERATOR(ResetCursor, ResetCursorOp);\nREGISTER_CPU_OPERATOR(ReadNextBatch, ReadNextBatchOp);\nREGISTER_CPU_OPERATOR(ComputeOffset, ComputeOffsetOp);\nREGISTER_CPU_OPERATOR(SortAndShuffle, SortAndShuffleOp);\nREGISTER_CPU_OPERATOR(ReadRandomBatch, ReadRandomBatchOp);\nREGISTER_CPU_OPERATOR(CheckDatasetConsistency, CheckDatasetConsistencyOp);\nREGISTER_CPU_OPERATOR(Append, AppendOp<CPUContext>);\nREGISTER_CPU_OPERATOR(AtomicAppend, AtomicAppendOp<CPUContext>);\nREGISTER_CPU_OPERATOR(CreateTensorVector, CreateTensorVectorOp<CPUContext>);\nREGISTER_CPU_OPERATOR(TensorVectorSize, TensorVectorSizeOp<CPUContext>);\nREGISTER_CPU_OPERATOR(ConcatTensorVector, ConcatTensorVectorOp<CPUContext>);\nREGISTER_CPU_OPERATOR(CollectTensor, CollectTensorOp<CPUContext>);\nREGISTER_CPU_OPERATOR(PackRecords, PackRecordsOp);\nREGISTER_CPU_OPERATOR(UnPackRecords, UnPackRecordsOp);\nREGISTER_CPU_OPERATOR(TrimDataset, TrimDatasetOp);\n\nOPERATOR_SCHEMA(CreateTreeCursor)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nCreates a cursor to iterate through a list of tensors, where some of those\ntensors contains the lengths in a nested schema. The schema is determined by\nthe `fields` arguments.\n\nFor example, to represent the following schema:\n\n  Struct(\n      a=Int(),\n      b=List(List(Int),\n      c=List(\n          Struct(\n             c1=String,\n             c2=List(Int),\n          ),\n      ),\n  )\n\nthe field list will be:\n  [\n      \"a\",\n      \"b:lengths\",\n      \"b:values:lengths\",\n      \"b:values:values\",\n      \"c:lengths\",\n      \"c:c1\",\n      \"c:c2:lengths\",\n      \"c:c2:values\",\n  ]\n\nAnd for the following instance of the struct:\n\n  Struct(\n      a=3,\n      b=[[4, 5], [6, 7, 8], [], [9]],\n      c=[\n          Struct(c1='alex', c2=[10, 11]),\n          Struct(c1='bob', c2=[12]),\n      ],\n  )\n\nThe values of the fields will be:\n  {\n      \"a\": [3],\n      \"b:lengths\": [4],\n      \"b:values:lengths\": [2, 3, 0, 1],\n      \"b:values:values\": [4, 5, 6, 7, 8, 9],\n      \"c:lengths\": [2],\n      \"c:c1\": [\"alex\", \"bob\"],\n      \"c:c2:lengths\": [2, 1],\n      \"c:c2:values\", [10, 11, 12],\n  }\n\nIn general, every field name in the format \"{prefix}:lengths\" defines a domain\n\"{prefix}\", and every subsequent field in the format \"{prefix}:{field}\" will\nbe in that domain, and the length of the domain is provided for each entry of\nthe parent domain. In the example, \"b:lengths\" defines a domain of length 4, so\nevery field under domain \"b\" will have 4 entries.\nThe \"lengths\" field for a given domain must appear before any reference to\nthat domain.\n\nReturns a pointer to an instance of the Cursor, which keeps the current offset\non each of the domains defined by `fields`. Cursor also ensures thread-safety\nsuch that ReadNextBatch and ResetCursor can be used safely in parallel.\n\nA cursor does not contain data per se, so calls to ReadNextBatch actually need\nto pass a list of blobs containing the data to read for each one of the fields.\n)DOC\")\n    .Output(0, \"cursor\", \"A blob pointing to an instance of a new TreeCursor.\")\n    .Arg(\n        \"fields\",\n        \"A list of strings each one representing a field of the dataset.\");\n\nOPERATOR_SCHEMA(ResetCursor)\n    .NumInputs(1)\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(\nResets the offsets for the given TreeCursor. This operation is thread safe.\n)DOC\")\n    .Input(0, \"cursor\", \"A blob containing a pointer to the cursor.\");\n\nOPERATOR_SCHEMA(ReadNextBatch)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1, INT_MAX)\n    .SetDoc(R\"DOC(\nRead the next batch of examples out of the given cursor and data blobs.\n\nInput(0) is a blob pointing to a TreeCursor, and\n[Input(1),... Input(num_fields)] a list of tensors containing the data for\neach field of the dataset.\n\nReadNextBatch is thread safe.\n)DOC\")\n    .Input(0, \"cursor\", \"A blob containing a pointer to the cursor.\")\n    .Input(1, \"dataset_field_0\", \"First dataset field\")\n    .Output(0, \"field_0\", \"Tensor containing the next batch for field 0.\")\n    .Arg(\"batch_size\", \"Number of top-level entries to read.\");\n\nOPERATOR_SCHEMA(ComputeOffset)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nCompute the offsets matrix given cursor and data blobs. Need to be ran at\nbeginning or after reseting cursor\n\nInput(0) is a blob pointing to a TreeCursor, and\n[Input(1),... Input(num_fields)] a list of tensors containing the data for\neach field of the dataset.\n\nComputeOffset is thread safe.\n)DOC\")\n    .Input(0, \"cursor\", \"A blob containing a pointer to the cursor.\")\n    .Input(1, \"dataset_field_0\", \"First dataset field\")\n    .Output(0, \"field_0\", \"Tensor containing offset info for this chunk.\");\n\nOPERATOR_SCHEMA(SortAndShuffle)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nCompute the sorted indices given a field index to sort by and break the sorted\nindices into chunks of shuffle_size * batch_size and shuffle each chunk,\nfinally we shuffle between batches. If sort_by_field_idx is -1 we skip sort.\n\nFor example, we have data sorted as\n1,2,3,4,5,6,7,8,9,10,11,12\n\nand batchSize = 2 and shuffleSize = 3, when we shuffle we get:\n[3,1,4,6,5,2] [12,10,11,8,9,7]\n\nAfter this we will shuffle among different batches with size 2\n[3,1],[4,6],[5,2],[12,10],[11,8],[9,7]\n\nWe may end up with something like\n[9,7],[5,2],[12,10],[4,6],[3,1],[11,8]\n\nInput(0) is a blob pointing to a TreeCursor, and\n[Input(1),... Input(num_fields)] a list of tensors containing the data for\neach field of the dataset.\n\nSortAndShuffle is thread safe.\n)DOC\")\n    .Input(0, \"cursor\", \"A blob containing a pointer to the cursor.\")\n    .Input(1, \"dataset_field_0\", \"First dataset field\")\n    .Output(0, \"indices\", \"Tensor containing sorted indices.\");\n\nOPERATOR_SCHEMA(ReadRandomBatch)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1, INT_MAX)\n    .SetDoc(R\"DOC(\nRead the next batch of examples out of the given cursor,\nidx blob, offset matrix and data blobs.\n\nInput(0) is a blob pointing to a TreeCursor,\nInput(1) is a blob pointing to the shuffled idx\nInput(2) is a blob pointing to the offset matrix and\n[Input(3),... Input(num_fields)] a list of tensors containing the data for\neach field of the dataset.\n\nReadRandomBatch is thread safe.\n)DOC\")\n    .Input(0, \"cursor\", \"A blob containing a pointer to the cursor.\")\n    .Input(1, \"idx\", \"idx with a shuffled order.\")\n    .Input(2, \"offsetsmat\", \"offset matrix containing length offset info.\")\n    .Input(3, \"dataset_field_0\", \"First dataset field\")\n    .Output(0, \"field_0\", \"Tensor containing the next batch for field 0.\")\n    .Arg(\"batch_size\", \"Number of top-level entries to read.\")\n    .Arg(\"loop_over\", \"(bool) Repeat the dataset indefinitely\");\n\nOPERATOR_SCHEMA(CheckDatasetConsistency)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(\nChecks that the given data fields represents a consistent dataset under\nthe schema specified by the `fields` argument. Operator fails if the fields\nare not consistent. If data is consistent, each field's data can be safely\nappended to an existing dataset, keeping it consistent.\n)DOC\")\n    .Input(0, \"field_0\", \"Data for field 0.\")\n    .Arg(\n        \"fields\",\n        \"List of strings representing the string names in the format\"\n        \"specified in the doc for CreateTreeCursor.\");\n\nOPERATOR_SCHEMA(Append)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .EnforceInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nAppend input 2 to the end of input 1.\nInput 1 must be the same as output, that is, it is required to be in-place.\nInput 1 may have to be re-allocated in order for accommodate to the new size.\nCurrently, an exponential growth ratio is used in order to ensure amortized\nconstant time complexity.\nAll except the outer-most dimension must be the same between input 1 and 2.\n)DOC\")\n    .Input(0, \"dataset\", \"The tensor to be appended to.\")\n    .Input(1, \"new_data\", \"Tensor to append to the end of dataset.\")\n    .Output(0, \"dataset\", \"Same as input 0, representing the mutated tensor.\");\n\nOPERATOR_SCHEMA(AtomicAppend)\n    .NumInputs(3, INT_MAX)\n    .NumOutputs(1, INT_MAX)\n    .AllowInplace([](int in, int out) { return in == out + 1; });\n\nOPERATOR_SCHEMA(CreateTensorVector)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(\"Create a std::unique_ptr<std::vector<Tensor> >\");\n\nOPERATOR_SCHEMA(TensorVectorSize)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(\"Get the size of the input vector\")\n    .Input(0, \"tensor vector\", \"std::unique_ptr<std::vector<Tensor> >\")\n    .Output(0, \"size\", \"int32_t size\");\n\nOPERATOR_SCHEMA(ConcatTensorVector)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nConcat Tensors in the std::unique_ptr<std::vector<Tensor> >\nalong the first dimension.\n    )DOC\")\n    .Input(0, \"vector of Tensor\", \"std::unique_ptr<std::vector<Tensor> >\")\n    .Output(0, \"tensor\", \"tensor after concatenating\");\n\nOPERATOR_SCHEMA(CollectTensor)\n    .NumInputs([](int n) { return n > 0 && n % 2 == 0; })\n    .NumOutputs(1, INT_MAX)\n    .NumInputsOutputs([](int in, int out) { return in == out * 2; })\n    .EnforceInplace([](int in, int out) { return in == out; })\n    .SetDoc(R\"DOC(\nCollect tensor into tensor vector by reservoir sampling,\nargument num_to_collect indicates the max number of tensors that will be\ncollected. The first half of the inputs are tensor vectors, which are also the\noutputs. The second half of the inputs are the tensors to be collected into each\nvector (in the same order). The input tensors are collected in all-or-none\nmanner. If they are collected, they will be placed at the same index in the\noutput vectors.\n)DOC\")\n    .Arg(\"num_to_collect\", \"The max number of tensors to collect\");\n\nOPERATOR_SCHEMA(PackRecords)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven a dataset under a schema specified by the `fields` argument will pack all\nthe input tensors into one, where each tensor element represents a row of data\n(batch of size 1). This format allows easier use with the rest of Caffe2\noperators.\n)DOC\")\n    .Arg(\n        \"fields\",\n        \"List of strings representing the string names in the format\"\n        \"specified in the doc for CreateTreeCursor.\")\n    .Output(\n        0,\n        \"tensor\",\n        \"One dimensional tensor having a complex type of SharedTensorVectorPtr.\"\n        \" In order to reverse it back to the original input it has to be \"\n        \"inserted into UnPackRecordsOp.\");\n\nOPERATOR_SCHEMA(TrimDataset)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1, INT_MAX)\n    .SetDoc(R\"DOC(\nTrim the given dataset inplace, given the dataset blobs and the field specs.\nTrimming happens such that the dataset will contain the largest possible number\nof records that is a multiple of the 'multiple_of' argument.\n)DOC\")\n    .EnforceInplace([](int input, int output) { return input == output; })\n    .Arg(\n        \"fields\",\n        \"List of strings representing the string names in the format\"\n        \"specified in the doc for CreateTreeCursor.\");\n\nOPERATOR_SCHEMA(UnPackRecords)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1, INT_MAX)\n    .SetDoc(R\"DOC(\nGiven a packed dataset (packed by the PackRecordsOp) and the `fields` argument\ndescribing the datasets schema returns the original dataset format. Number of\nreturned tensors is equal to the number of fields in the `fields` argument.\n\nThe first input is the packed tensor to be unpacked. Optionally, you can provide\nprototype tensors to give the expected shapes of the output tensors. This is\nhelpful when you expected to unpack empty tensor, e.g., output of a sampling\nprocess.\n)DOC\")\n    .Arg(\n        \"fields\",\n        \"List of strings representing the string names in the format\"\n        \"specified in the doc for CreateTreeCursor.\")\n    .Input(0, \"packed_tensor\", \"The tensor to be unpacked\");\n\nSHOULD_NOT_DO_GRADIENT(CreateTreeCursor);\nSHOULD_NOT_DO_GRADIENT(ResetCursor);\nSHOULD_NOT_DO_GRADIENT(ReadNextBatch);\nSHOULD_NOT_DO_GRADIENT(ComputeOffset);\nSHOULD_NOT_DO_GRADIENT(ReadRandomBatch);\nSHOULD_NOT_DO_GRADIENT(CheckDatasetConsistency);\nSHOULD_NOT_DO_GRADIENT(Append);\nSHOULD_NOT_DO_GRADIENT(AtomicAppend);\nSHOULD_NOT_DO_GRADIENT(CreateTensorVector);\nSHOULD_NOT_DO_GRADIENT(TensorVectorSize);\nSHOULD_NOT_DO_GRADIENT(ConcatTensorVector);\nSHOULD_NOT_DO_GRADIENT(CollectTensor);\nSHOULD_NOT_DO_GRADIENT(UnPackRecords);\nSHOULD_NOT_DO_GRADIENT(PackRecords);\n\nclass TreeCursorSerializer : public BlobSerializerBase {\n public:\n  TreeCursorSerializer() {}\n  ~TreeCursorSerializer() {}\n\n  void Serialize(\n      const Blob& blob,\n      const string& name,\n      SerializationAcceptor acceptor) override {\n    auto& cursor = blob.template Get<std::unique_ptr<TreeCursor>>();\n    BlobProto blob_proto;\n\n    // serialize offsets as a tensor\n    if (cursor->offsets.size() > 0) {\n      Blob offsets_blob;\n      auto* offsets = offsets_blob.template GetMutable<Tensor<CPUContext>>();\n      offsets->Resize(cursor->offsets.size());\n      std::copy(\n          cursor->offsets.begin(),\n          cursor->offsets.end(),\n          offsets->mutable_data<TOffset>());\n      TensorSerializer<CPUContext> ser;\n      ser.Serialize(\n          *offsets, name, blob_proto.mutable_tensor(), 0, offsets->size());\n    }\n    blob_proto.set_name(name);\n    blob_proto.set_type(\"std::unique_ptr<TreeCursor>\");\n\n    // serialize field names in the content\n    std::ostringstream os;\n    for (const auto& field : cursor->it.fields()) {\n      os << field.name << \" \";\n    }\n    blob_proto.set_content(os.str());\n\n    acceptor(name, blob_proto.SerializeAsString());\n  }\n};\n\nclass TreeCursorDeserializer : public BlobDeserializerBase {\n public:\n  void Deserialize(const BlobProto& proto, Blob* blob) override {\n    // deserialize the offsets\n    TensorDeserializer<CPUContext> deser;\n    Blob offset_blob;\n    deser.Deserialize(proto, &offset_blob);\n    auto& offsets = offset_blob.template Get<Tensor<CPUContext>>();\n    auto* offsets_ptr = offsets.data<TOffset>();\n\n    // deserialize the field names\n    std::vector<std::string> fieldNames;\n    std::istringstream is(proto.content());\n    std::string field;\n    while (true) {\n      is >> field;\n      if (is.eof()) {\n        break;\n      }\n      fieldNames.push_back(field);\n    }\n    TreeIterator it(fieldNames);\n\n    auto* base = blob->template GetMutable<std::unique_ptr<TreeCursor>>();\n    (*base).reset(new TreeCursor(it));\n    (*base)->offsets.assign(offsets_ptr, offsets_ptr + offsets.size());\n  }\n};\n\nREGISTER_BLOB_SERIALIZER(\n    (TypeMeta::Id<std::unique_ptr<TreeCursor>>()),\n    TreeCursorSerializer);\nREGISTER_BLOB_DESERIALIZER(std::unique_ptr<TreeCursor>, TreeCursorDeserializer);\n\n} // namespace\n\nvoid SharedTensorVectorPtrSerializer::Serialize(\n    const Blob& blob,\n    const string& name,\n    BlobSerializerBase::SerializationAcceptor acceptor) {\n  /* This is dummy serialize that doesn't save anything. If saving the content\n  is desired in future use case, you can change this serializer. Note: special\n  care need to be taken for the parameter initialization of\n  LastNWindowCollectorOp and ReservoirSamplingOp if this serializer actually\n  saves the content.\n  */\n  CAFFE_ENFORCE(blob.IsType<std::shared_ptr<std::vector<TensorCPU>>>());\n  BlobProto blob_proto;\n  blob_proto.set_name(name);\n  blob_proto.set_type(\"std::shared_ptr<std::vector<TensorCPU>>\");\n  blob_proto.set_content(\"\");\n  acceptor(name, blob_proto.SerializeAsString());\n};\n\nvoid SharedTensorVectorPtrDeserializer::Deserialize(\n    const BlobProto& /* unused */,\n    Blob* blob) {\n  /* This is dummy deserialize which creates a nullptr\n   */\n  blob->GetMutable<std::shared_ptr<std::vector<TensorCPU>>>();\n}\n\nREGISTER_BLOB_SERIALIZER(\n    (TypeMeta::Id<std::shared_ptr<std::vector<TensorCPU>>>()),\n    SharedTensorVectorPtrSerializer);\n\nREGISTER_BLOB_DESERIALIZER(\n    std::shared_ptr<std::vector<TensorCPU>>,\n    SharedTensorVectorPtrDeserializer);\n\n} // namespace dataset_ops\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/dataset_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_DATASET_OPS_H_\n#define CAFFE2_OPERATORS_DATASET_OPS_H_\n\n#include <memory>\n#include <mutex>\n#include <string>\n#include <vector>\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\nnamespace dataset_ops {\n\n// used for lengths tensors in the dataset\nusing TLength = int32_t;\n// used for all internal dataset operations (offsets, sizes to read, etc.)\nusing TOffset = int64_t;\n\n/**\n * Provides functionality to iterate across a list of tensors where some\n * of those tensors represent lengths in a hierarchical structure.\n */\nclass TreeIterator {\n public:\n  struct FieldDesc {\n    int id;\n    int lengthFieldId = -1;\n    std::string name;\n  };\n\n  explicit TreeIterator(const std::vector<std::string>& fields);\n\n  void advance(\n      const std::vector<const TLength*>& lengths,\n      std::vector<TOffset>& offsets,\n      std::vector<TOffset>& sizes,\n      std::vector<TOffset>& limits,\n      TOffset num);\n\n  // Corresponds to the number of fields that have \"length\" as its last name\n  int numLengthFields() const {\n    return lengthFieldIds_.size();\n  }\n\n  // Corresponds to the number of length fields + 1 (for the top-level domain)\n  int numOffsetFields() const {\n    return numLengthFields() + 1;\n  }\n\n  // Get lengthField description for the given field\n  const FieldDesc* lengthFieldFor(const FieldDesc& desc) {\n    return (desc.lengthFieldId == -1)\n        ? nullptr\n        : &fields_.at(lengthFieldIds_.at(desc.lengthFieldId));\n  }\n\n  // Get lengthField description for the given lengthFieldId, where\n  // 0 <= lengthFieldId < numLengthFields()\n  const FieldDesc& lengthField(int lengthFieldId) {\n    return fields_.at(lengthFieldIds_.at(lengthFieldId));\n  }\n\n  // Returns the index into the 'offset' vector for the given field.\n  int offsetFieldIdFor(const FieldDesc& fieldDesc) {\n    return fieldDesc.lengthFieldId + 1;\n  }\n\n  // Returns the field description for all fields.\n  const std::vector<FieldDesc>& fields() {\n    return fields_;\n  }\n\n  const std::vector<int>& lengthFieldIds() const {\n    return lengthFieldIds_;\n  }\n\n private:\n  // Description of each field\n  std::vector<FieldDesc> fields_;\n  // Index into fields_ above for the fields that are lengths.\n  std::vector<int> lengthFieldIds_;\n};\n\nclass TreeCursor {\n public:\n  explicit TreeCursor(const TreeIterator& iterator) : it(iterator) {}\n  std::vector<TOffset> offsets;\n  std::mutex mutex_;\n  TreeIterator it;\n};\n\n/**\n * Simple wrapper class allowing an easy traversal of the tensors representing\n * the hirerarchical structure.\n */\nclass TreeWalker {\n public:\n  TreeWalker(const vector<const Blob*>& inputs, TreeCursor& cursor);\n\n  // Returns the number of records in a dataset\n  inline TOffset size() const {\n    return limits_.at(0);\n  }\n\n  void advance();\n\n private:\n  inline const TensorCPU& input(int32_t idx) const {\n    return inputs_[idx]->Get<TensorCPU>();\n  }\n\n  // TODO: Change to fieldDesc\n  inline const TreeIterator::FieldDesc& field(int idx) const {\n    return cursor_.it.fields().at(idx);\n  }\n\n  inline int lengthIdx(int fieldId) const {\n    return field(fieldId).lengthFieldId + 1;\n  }\n\n  inline TOffset offset(int fieldId) const {\n    return prevOffsets_[lengthIdx(fieldId)];\n  }\n\n  std::vector<TIndex> fieldDim(int fieldId) const;\n\n  void* fieldPtr(int fieldId) const;\n\n public:\n  // Simple Proxy class to expose nicer API for field access\n  class Field {\n   public:\n    Field(TreeWalker& walker, int fieldId)\n        : walker_(walker), fieldId_(fieldId) {}\n\n    inline std::vector<TIndex> dim() const {\n      return walker_.fieldDim(fieldId_);\n    }\n\n    inline TIndex size() const {\n      TIndex size = 1;\n      for (const auto d : dim()) {\n        size *= d;\n      }\n      return size;\n    }\n\n    inline const TypeMeta& meta() const {\n      return walker_.input(fieldId_).meta();\n    }\n\n    inline void* ptr() const {\n      return walker_.fieldPtr(fieldId_);\n    }\n\n    int fieldId() const {\n      return fieldId_;\n    }\n\n    inline TOffset offset() const {\n      return walker_.offset(fieldId_);\n    }\n\n   private:\n    const TreeWalker& walker_;\n    const int fieldId_;\n  };\n\n  // Notice that a reference is returned. If advance() is called the fields will\n  // be updated to represent the new state.\n  inline const std::vector<Field>& fields() const {\n    return fields_;\n  }\n\n private:\n  void gatherLengthData();\n\n  void gatherSizeLimits();\n\n  const vector<const Blob*>& inputs_;\n  TreeCursor& cursor_;\n  std::vector<Field> fields_;\n\n  std::vector<const TLength*> lengths_;\n  std::vector<TOffset> limits_;\n  std::vector<TOffset> sizes_;\n  std::vector<TOffset> offsets_;\n  std::vector<TOffset> prevOffsets_;\n};\n\nusing SharedTensorVectorPtr = std::shared_ptr<std::vector<TensorCPU>>;\n\ntemplate <class Context>\nusing TensorVectorPtr = std::unique_ptr<std::vector<Tensor<Context>>>;\n\nclass SharedTensorVectorPtrSerializer : public BlobSerializerBase {\n public:\n  void Serialize(\n      const Blob& blob,\n      const string& name,\n      BlobSerializerBase::SerializationAcceptor acceptor) override;\n};\n\nclass SharedTensorVectorPtrDeserializer : public BlobDeserializerBase {\n public:\n  void Deserialize(const BlobProto& proto, Blob* blob) override;\n};\n\n} // namespace dataset_ops\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_DATASET_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/deform_conv_gradient_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/operators/deform_conv_op.h\"\n#include \"caffe2/operators/deform_conv_op_impl.h\"\n\nnamespace caffe2 {\n\nOPERATOR_SCHEMA(DeformConvGradient).NumInputs(4, 4).NumOutputs(2, 4);\n\nnamespace {\n\nclass GetDeformConvGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE(def_.input_size() == 3 || def_.input_size() == 4);\n\n    ArgumentHelper argsHelper(def_);\n\n    auto compute_dX =\n        !argsHelper.GetSingleArgument<bool>(\"no_gradient_to_input\", 0);\n\n    if (def_.input_size() == 4) {\n      if (compute_dX) {\n        return SingleGradientDef(\n            \"DeformConvGradient\",\n            \"\",\n            vector<string>{I(0), I(1), I(2), GO(0)},\n            vector<string>{GI(1), GI(2), GI(3), GI(0)});\n      } else {\n        return SingleGradientDef(\n            \"DeformConvGradient\",\n            \"\",\n            vector<string>{I(0), I(1), I(2), GO(0)},\n            vector<string>{GI(1), GI(2), GI(3)});\n      }\n    } else {\n      if (compute_dX) {\n        return SingleGradientDef(\n            \"DeformConvGradient\",\n            \"\",\n            vector<string>{I(0), I(1), I(2), GO(0)},\n            vector<string>{GI(1), GI(2), GI(0)},\n            vector<Argument>{MakeArgument<int>(\"no_bias\", 1)});\n      } else {\n        return SingleGradientDef(\n            \"DeformConvGradient\",\n            \"\",\n            vector<string>{I(0), I(1), I(2), GO(0)},\n            vector<string>{GI(1), GI(2)},\n            vector<Argument>{MakeArgument<int>(\"no_bias\", 1)});\n      }\n    }\n  }\n};\nREGISTER_GRADIENT(DeformConv, GetDeformConvGradient);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/deform_conv_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/deform_conv_op.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/operators/deform_conv_op_impl.h\"\n\nnamespace caffe2 {\n\nOPERATOR_SCHEMA(DeformConv)\n    .NumInputs(3, 4)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForConv)\n    .SetDoc(R\"DOC(\nDeformable convolution operator consumes an input vector, the kernel offsets\nblob, the filter blob and the bias blob and computes the output. Other\nparameters, such as the stride and kernel size, or the pads' sizes in each\ndirection are not necessary for input because they are provided by the\nConvPoolOpBase operator. Various dimension checks are done implicitly, and the\nsizes are specified in the Input docs for this operator. As is expected, the\nfilter is convolved with a subset of the image using the deformed kernel as\nspecified by offsets blob and the bias is added; this is done throughout the\nimage data and the output is computed.\n  )DOC\")\n    .Input(\n        0,\n        \"X\",\n        \"Input data blob from previous layer; has size \"\n        \"(N x C x H x W), where N is the batch size, C is the number of channels, and\"\n        \" H and W are the height and width. Note that this is for the NCHW usage. On \"\n        \"the other hand, the NHWC Op has a different set of dimension constraints.\")\n    .Input(\n        1,\n        \"offset\",\n        \"Offsets blob that specifies the deformed shape of the \"\n        \"kernel; consists of 2d offsets for each kernel element, one full set per \"\n        \"each output element; therefore has size (N x 2*kH*kW x H' x W') where N is \"\n        \"the batch size, kH and kW are the height and width of the kernel, H' and W' \"\n        \"are the output blob dimensions.\")\n    .Input(\n        2,\n        \"filter\",\n        \"The filter blob that will be used in the convolutions; \"\n        \"has size (M x C x kH x kW), where C is the number of channels, and kH and \"\n        \"kW are the height and width of the kernel.\")\n    .Input(\n        3,\n        \"bias\",\n        \"The 1D bias blob that is added through the convolution; \"\n        \"has size (M).\")\n    .Output(\n        0,\n        \"Y\",\n        \"Output data blob that contains the result of the \"\n        \"convolution. The output dimensions are functions of the kernel size, \"\n        \"stride size, and pad lengths.\"\n        \"\");\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/deform_conv_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*!\n ******************* BEGIN Caffe Copyright Notice and Disclaimer ****************\n *\n * COPYRIGHT\n *\n * All contributions by the University of California:\n * Copyright (c) 2014-2017 The Regents of the University of California (Regents)\n * All rights reserved.\n *\n * All other contributions:\n * Copyright (c) 2014-2017, the respective contributors\n * All rights reserved.\n *\n * Caffe uses a shared copyright model: each contributor holds copyright over\n * their contributions to Caffe. The project versioning records all such\n * contribution and copyright details. If a contributor wants to further mark\n * their specific copyright on a particular contribution, they should indicate\n * their copyright solely in the commit message of the change when it is\n * committed.\n *\n * LICENSE\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n * list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\n * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * CONTRIBUTION AGREEMENT\n *\n * By contributing to the BVLC/caffe repository through pull-request, comment,\n * or otherwise, the contributor releases their content to the\n * license and copyright terms herein.\n *\n ***************** END Caffe Copyright Notice and Disclaimer ********************\n *\n * Copyright (c) 2017 Microsoft\n * Licensed under The Apache-2.0 License [see LICENSE for details]\n * \\file deformable_im2col.cuh\n * \\brief Function definitions of converting an image to\n * column matrix based on kernel, padding, dilation, and offset.\n * These functions are mainly used in deformable convolution operators.\n * \\ref: https://arxiv.org/abs/1703.06211\n * \\author Yuwen Xiong, Haozhi Qi, Jifeng Dai\n */\n\n#include <cub/block/block_reduce.cuh>\n#include <vector>\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/deform_conv_op.h\"\n#include \"caffe2/operators/deform_conv_op_impl.h\"\n\nnamespace caffe2 {\n\ntypedef TIndex index_t;\ntypedef std::vector<TIndex> TShape;\n\ntemplate <typename DType>\n__device__ DType deformable_im2col_bilinear(\n    const DType* bottom_data,\n    const int data_width,\n    const int height,\n    const int width,\n    DType h,\n    DType w) {\n  int h_low = floor(h);\n  int w_low = floor(w);\n  int h_high;\n  int w_high;\n  if (h_low >= height - 1) {\n    h_high = h_low = height - 1;\n    h = (DType)h_low;\n  } else {\n    h_high = h_low + 1;\n  }\n\n  if (w_low >= width - 1) {\n    w_high = w_low = width - 1;\n    w = (DType)w_low;\n  } else {\n    w_high = w_low + 1;\n  }\n\n  DType lh = h - h_low;\n  DType lw = w - w_low;\n  DType hh = 1 - lh, hw = 1 - lw;\n\n  DType v1 = bottom_data[h_low * data_width + w_low];\n  DType v2 = bottom_data[h_low * data_width + w_high];\n  DType v3 = bottom_data[h_high * data_width + w_low];\n  DType v4 = bottom_data[h_high * data_width + w_high];\n  DType w1 = hh * hw, w2 = hh * lw, w3 = lh * hw, w4 = lh * lw;\n\n  DType val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);\n  return val;\n}\n\ntemplate <typename DType>\n__device__ DType get_gradient_weight(\n    DType argmax_h,\n    DType argmax_w,\n    const int h,\n    const int w,\n    const int height,\n    const int width) {\n  if (argmax_h < 0 || argmax_h > height || argmax_w < 0 || argmax_w > width) {\n    // empty\n    return 0;\n  }\n\n  argmax_h = max(argmax_h, (DType)0.0f);\n  argmax_w = max(argmax_w, (DType)0.0f);\n\n  int argmax_h_low = (int)argmax_h;\n  int argmax_w_low = (int)argmax_w;\n  int argmax_h_high;\n  int argmax_w_high;\n  if (argmax_h_low >= height - 1) {\n    argmax_h_high = argmax_h_low = height - 1;\n    argmax_h = (DType)argmax_h_low;\n  } else {\n    argmax_h_high = argmax_h_low + 1;\n  }\n  if (argmax_w_low >= width - 1) {\n    argmax_w_high = argmax_w_low = width - 1;\n    argmax_w = (DType)argmax_w_low;\n  } else {\n    argmax_w_high = argmax_w_low + 1;\n  }\n  DType weight = 0;\n  if (h == argmax_h_low) {\n    if (w == argmax_w_low) {\n      weight = (h + 1 - argmax_h) * (w + 1 - argmax_w);\n    } else if (w == argmax_w_high) {\n      weight = (h + 1 - argmax_h) * (argmax_w + 1 - w);\n    }\n  } else if (h == argmax_h_high) {\n    if (w == argmax_w_low) {\n      weight = (argmax_h + 1 - h) * (w + 1 - argmax_w);\n    } else if (w == argmax_w_high) {\n      weight = (argmax_h + 1 - h) * (argmax_w + 1 - w);\n    }\n  }\n  return weight;\n}\n\ntemplate <typename DType>\n__device__ DType get_coordinate_weight(\n    DType argmax_h,\n    DType argmax_w,\n    const int height,\n    const int width,\n    const DType* im_data,\n    const int data_width,\n    const int bp_dir) {\n  if (argmax_h < 0 || argmax_h > height || argmax_w < 0 || argmax_w > width) {\n    // empty\n    return 0;\n  }\n\n  if (argmax_h < 0)\n    argmax_h = 0;\n  if (argmax_w < 0)\n    argmax_w = 0;\n\n  int argmax_h_low = (int)argmax_h;\n  int argmax_w_low = (int)argmax_w;\n  int argmax_h_high;\n  int argmax_w_high;\n  if (argmax_h_low >= height - 1) {\n    argmax_h_high = argmax_h_low = height - 1;\n    argmax_h = (DType)argmax_h_low;\n  } else {\n    argmax_h_high = argmax_h_low + 1;\n  }\n  if (argmax_w_low >= width - 1) {\n    argmax_w_high = argmax_w_low = width - 1;\n    argmax_w = (DType)argmax_w_low;\n  } else {\n    argmax_w_high = argmax_w_low + 1;\n  }\n  DType weight = 0;\n\n  if (bp_dir == 0) {\n    weight += -1 * (argmax_w_low + 1 - argmax_w) *\n        im_data[argmax_h_low * data_width + argmax_w_low];\n    weight += -1 * (argmax_w - argmax_w_low) *\n        im_data[argmax_h_low * data_width + argmax_w_high];\n    weight += (argmax_w_low + 1 - argmax_w) *\n        im_data[argmax_h_high * data_width + argmax_w_low];\n    weight += (argmax_w - argmax_w_low) *\n        im_data[argmax_h_high * data_width + argmax_w_high];\n  } else if (bp_dir == 1) {\n    weight += -1 * (argmax_h_low + 1 - argmax_h) *\n        im_data[argmax_h_low * data_width + argmax_w_low];\n    weight += (argmax_h_low + 1 - argmax_h) *\n        im_data[argmax_h_low * data_width + argmax_w_high];\n    weight += -1 * (argmax_h - argmax_h_low) *\n        im_data[argmax_h_high * data_width + argmax_w_low];\n    weight += (argmax_h - argmax_h_low) *\n        im_data[argmax_h_high * data_width + argmax_w_high];\n  }\n\n  return weight;\n}\n\n/*!\n * \\brief deformable_im2col gpu kernel.\n * DO NOT call this directly. Use wrapper function im2col() instead;\n */\ntemplate <typename DType>\n__global__ void deformable_im2col_gpu_kernel(\n    const int n,\n    const DType* data_im,\n    const DType* data_offset,\n    const int height,\n    const int width,\n    const int kernel_h,\n    const int kernel_w,\n    const int pad_h,\n    const int pad_w,\n    const int stride_h,\n    const int stride_w,\n    const int dilation_h,\n    const int dilation_w,\n    const int channel_per_deformable_group,\n    const int height_col,\n    const int width_col,\n    DType* data_col) {\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    // index index of output matrix\n    const int w_col = index % width_col;\n    const int h_col = (index / width_col) % height_col;\n    const int c_im = (index / width_col) / height_col;\n    const int c_col = c_im * kernel_h * kernel_w;\n\n    // compute deformable group index\n    const int deformable_group_index = c_im / channel_per_deformable_group;\n\n    const int h_in = h_col * stride_h - pad_h;\n    const int w_in = w_col * stride_w - pad_w;\n    DType* data_col_ptr =\n        data_col + (c_col * height_col + h_col) * width_col + w_col;\n    const DType* data_im_ptr = data_im + (c_im * height + h_in) * width + w_in;\n    const DType* data_offset_ptr = data_offset +\n        deformable_group_index * 2 * kernel_h * kernel_w * height_col *\n            width_col;\n\n    for (int i = 0; i < kernel_h; ++i) {\n      for (int j = 0; j < kernel_w; ++j) {\n        const int data_offset_h_ptr =\n            ((2 * (i * kernel_w + j)) * height_col + h_col) * width_col + w_col;\n        const int data_offset_w_ptr =\n            ((2 * (i * kernel_w + j) + 1) * height_col + h_col) * width_col +\n            w_col;\n        const DType offset_h = data_offset_ptr[data_offset_h_ptr];\n        const DType offset_w = data_offset_ptr[data_offset_w_ptr];\n        DType val = static_cast<DType>(0);\n        const DType h_im = h_in + i * dilation_h + offset_h;\n        const DType w_im = w_in + j * dilation_w + offset_w;\n        if (h_im >= 0 && w_im >= 0 && h_im < height && w_im < width) {\n          const DType map_h = i * dilation_h + offset_h;\n          const DType map_w = j * dilation_w + offset_w;\n          const int cur_height = height - h_in;\n          const int cur_width = width - w_in;\n          val = deformable_im2col_bilinear(\n              data_im_ptr, width, cur_height, cur_width, map_h, map_w);\n        }\n        *data_col_ptr = val;\n        data_col_ptr += height_col * width_col;\n      }\n    }\n  }\n}\n\n/*!\\brief\n * cpu function of deformable_im2col algorithm\n * \\param s device stream\n * \\param data_im pointer of an image (C, H, W, ...) in the image batch\n * \\param data_offset pointer of offset (C, H, W, ...) in the offset batch\n * \\param im_shape input image shape in dimensions (N, C, H, W,)\n * \\param col_shape column buffer shape (#channels, output_im_height,\n * output_im_width, ...) \\param kernel_shape kernel filter shape \\param pad pad\n * shape \\param stride stride shape \\param dilation dilation shape \\param\n * deformable_group #offset group that deformable convolution use \\param\n * data_col column buffer pointer\n */\ntemplate <typename DType, typename Context>\nvoid DeformConvOpBase<DType, Context>::DeformableIm2col(\n    const DType* data_im,\n    const DType* data_offset,\n    const std::vector<TIndex>& im_shape,\n    const std::vector<TIndex>& col_shape,\n    DType* data_col) {\n  CHECK_LT(2, CAFFE_CUDA_NUM_THREADS);\n  CAFFE_ENFORCE_EQ(pad_t(), pad_b());\n  CAFFE_ENFORCE_EQ(pad_l(), pad_r());\n  const int pad_h = pad_t();\n  const int pad_w = pad_l();\n  index_t channel_per_deformable_group = im_shape[1] / deformable_group_;\n  index_t num_kernels = im_shape[1] * size_from_dim_(1, col_shape);\n  deformable_im2col_gpu_kernel<DType>\n      <<<CAFFE_GET_BLOCKS(num_kernels),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(\n          num_kernels,\n          data_im,\n          data_offset,\n          im_shape[2],\n          im_shape[3],\n          kernel_h(),\n          kernel_w(),\n          pad_h,\n          pad_w,\n          stride_h(),\n          stride_w(),\n          dilation_h(),\n          dilation_w(),\n          channel_per_deformable_group,\n          col_shape[1],\n          col_shape[2],\n          data_col);\n}\n\n/*!\n * \\brief deformable_col2im gpu kernel.\n * \\brief DO NOT call this directly. Use wrapper function deformable_col2im()\n * instead;\n */\ntemplate <typename DType>\n__global__ void deformable_col2im_gpu_kernel(\n    const int n,\n    const DType* data_col,\n    const DType* data_offset,\n    const int channels,\n    const int height,\n    const int width,\n    const int kernel_h,\n    const int kernel_w,\n    const int pad_h,\n    const int pad_w,\n    const int stride_h,\n    const int stride_w,\n    const int dilation_h,\n    const int dilation_w,\n    const int channel_per_deformable_group,\n    const int height_col,\n    const int width_col,\n    DType* grad_im) {\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    const int j = (index / width_col / height_col) % kernel_w;\n    const int i = (index / width_col / height_col / kernel_w) % kernel_h;\n    const int c = index / width_col / height_col / kernel_w / kernel_h;\n    // compute the start and end of the output\n\n    const int deformable_group_index = c / channel_per_deformable_group;\n\n    int w_out = index % width_col;\n    int h_out = (index / width_col) % height_col;\n    int w_in = w_out * stride_w - pad_w;\n    int h_in = h_out * stride_h - pad_h;\n\n    const DType* data_offset_ptr = data_offset +\n        deformable_group_index * 2 * kernel_h * kernel_w * height_col *\n            width_col;\n    const int data_offset_h_ptr =\n        ((2 * (i * kernel_w + j)) * height_col + h_out) * width_col + w_out;\n    const int data_offset_w_ptr =\n        ((2 * (i * kernel_w + j) + 1) * height_col + h_out) * width_col + w_out;\n    const DType offset_h = data_offset_ptr[data_offset_h_ptr];\n    const DType offset_w = data_offset_ptr[data_offset_w_ptr];\n    const DType cur_inv_h_data = h_in + i * dilation_h + offset_h;\n    const DType cur_inv_w_data = w_in + j * dilation_w + offset_w;\n\n    const DType cur_top_grad = data_col[index];\n    const int cur_h = (int)cur_inv_h_data;\n    const int cur_w = (int)cur_inv_w_data;\n    for (int dy = -2; dy <= 2; dy++) {\n      for (int dx = -2; dx <= 2; dx++) {\n        if (cur_h + dy >= 0 && cur_h + dy < height && cur_w + dx >= 0 &&\n            cur_w + dx < width && abs(cur_inv_h_data - (cur_h + dy)) < 1 &&\n            abs(cur_inv_w_data - (cur_w + dx)) < 1) {\n          int cur_bottom_grad_pos =\n              (c * height + cur_h + dy) * width + cur_w + dx;\n          DType weight = get_gradient_weight(\n              cur_inv_h_data,\n              cur_inv_w_data,\n              cur_h + dy,\n              cur_w + dx,\n              height,\n              width);\n          atomicAdd(grad_im + cur_bottom_grad_pos, weight * cur_top_grad);\n        }\n      }\n    }\n  }\n}\n\n/*!\\brief\n * gpu function of deformable_col2im algorithm\n * \\param s device stream\n * \\param data_col start pointer of the column buffer to be filled\n * \\param data_offset pointer of offset (C, H, W, ...) in the offset batch\n * \\param im_shape input image shape in dimensions (N, C, H, W,)\n * \\param col_shape column buffer shape\n * \\param kernel_shape kernel filter shape\n * \\param pad pad shape\n * \\param stride stride shape\n * \\param dilation dilation shape\n * \\param deformable_group #offset group that deformable convolution use\n * \\param grad_im pointer of a image (C, H, W,...) in the image batch\n */\ntemplate <typename DType, typename Context>\nvoid DeformConvOpBase<DType, Context>::DeformableCol2im(\n    const DType* data_col,\n    const DType* data_offset,\n    const std::vector<TIndex>& im_shape,\n    const std::vector<TIndex>& col_shape,\n    DType* grad_im) {\n  CAFFE_ENFORCE_EQ(pad_t(), pad_b());\n  CAFFE_ENFORCE_EQ(pad_l(), pad_r());\n  const int pad_h = pad_t();\n  const int pad_w = pad_l();\n  index_t im_size = size_from_dim_(1, im_shape);\n  index_t channel_per_deformable_group = im_shape[1] / deformable_group_;\n  index_t num_kernels = size_from_dim_(0, col_shape);\n  // num_axes should be smaller than block size\n  CHECK_LT(2, CAFFE_CUDA_NUM_THREADS);\n  // To avoid involving atomic operations, we will launch one kernel per\n  // bottom dimension, and then in the kernel add up the top dimensions.\n  // NOLINT_NEXT_LINE(whitespace/operators)\n  deformable_col2im_gpu_kernel<DType>\n      <<<CAFFE_GET_BLOCKS(num_kernels),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(\n          num_kernels,\n          data_col,\n          data_offset,\n          im_shape[1],\n          im_shape[2],\n          im_shape[3],\n          kernel_h(),\n          kernel_w(),\n          pad_h,\n          pad_w,\n          stride_h(),\n          stride_w(),\n          dilation_h(),\n          dilation_w(),\n          channel_per_deformable_group,\n          col_shape[1],\n          col_shape[2],\n          grad_im);\n}\n\n/*!\n * \\brief deformable_col2im_coord gpu kernel.\n * \\brief DO NOT call this directly. Use wrapper function\n * deformable_col2im_coord() instead;\n */\ntemplate <typename DType>\n__global__ void deformable_col2im_coord_gpu_kernel(\n    const int n,\n    const DType* data_col,\n    const DType* data_im,\n    const DType* data_offset,\n    const int channels,\n    const int height,\n    const int width,\n    const int kernel_h,\n    const int kernel_w,\n    const int pad_h,\n    const int pad_w,\n    const int stride_h,\n    const int stride_w,\n    const int dilation_h,\n    const int dilation_w,\n    const int channel_per_deformable_group,\n    const int height_col,\n    const int width_col,\n    DType* grad_offset) {\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    DType val = 0;\n    int w = index % width_col;\n    int h = (index / width_col) % height_col;\n    int c = index / width_col / height_col;\n    // compute the start and end of the output\n\n    const int deformable_group_index = c / (2 * kernel_h * kernel_w);\n    const int col_step = kernel_h * kernel_w;\n    int cnt = 0;\n    const DType* data_col_ptr = data_col +\n        deformable_group_index * channel_per_deformable_group * width_col *\n            height_col;\n    const DType* data_im_ptr = data_im +\n        deformable_group_index * channel_per_deformable_group / kernel_h /\n            kernel_w * height * width;\n    const DType* data_offset_ptr = data_offset +\n        deformable_group_index * 2 * kernel_h * kernel_w * height_col *\n            width_col;\n\n    const int offset_c = c - deformable_group_index * 2 * kernel_h * kernel_w;\n\n    for (int col_c = (offset_c / 2); col_c < channel_per_deformable_group;\n         col_c += col_step) {\n      const int col_pos = ((col_c * height_col) + h) * width_col + w;\n      const int bp_dir = offset_c % 2;\n\n      int j = (col_pos / width_col / height_col) % kernel_w;\n      int i = (col_pos / width_col / height_col / kernel_w) % kernel_h;\n      int w_out = col_pos % width_col;\n      int h_out = (col_pos / width_col) % height_col;\n      int w_in = w_out * stride_w - pad_w;\n      int h_in = h_out * stride_h - pad_h;\n      const int data_offset_h_ptr =\n          (((2 * (i * kernel_w + j)) * height_col + h_out) * width_col + w_out);\n      const int data_offset_w_ptr =\n          (((2 * (i * kernel_w + j) + 1) * height_col + h_out) * width_col +\n           w_out);\n      const DType offset_h = data_offset_ptr[data_offset_h_ptr];\n      const DType offset_w = data_offset_ptr[data_offset_w_ptr];\n      DType inv_h = h_in + i * dilation_h + offset_h;\n      DType inv_w = w_in + j * dilation_w + offset_w;\n      if (inv_h < 0 || inv_w < 0 || inv_h >= height || inv_w >= width) {\n        inv_h = inv_w = -1;\n      }\n      const DType weight = get_coordinate_weight(\n          inv_h,\n          inv_w,\n          height,\n          width,\n          data_im_ptr + cnt * height * width,\n          width,\n          bp_dir);\n      val += weight * data_col_ptr[col_pos];\n      cnt += 1;\n    }\n\n    grad_offset[index] = val;\n  }\n}\n\n/*!\\brief\n * gpu function of deformable_col2im_coord algorithm\n * \\param s device stream\n * \\param data_col start pointer of the column buffer to be filled\n * \\param data_im pointer of an image (C, H, W, ...) in the image batch\n * \\param data_offset pointer of offset (C, H, W, ...) in the offset batch\n * \\param im_shape input image shape in dimensions (N, C, H, W,)\n * \\param col_shape column buffer shape\n * \\param kernel_shape kernel filter shape\n * \\param pad pad shape\n * \\param stride stride shape\n * \\param dilation dilation shape\n * \\param deformable_group #offset group that deformable convolution use\n * \\param grad_offset pointer of the offset (C, H, W,...) in the offset batch\n */\ntemplate <typename DType, typename Context>\nvoid DeformConvOpBase<DType, Context>::DeformableCol2imCoord(\n    const DType* data_col,\n    const DType* data_im,\n    const DType* data_offset,\n    const std::vector<TIndex>& im_shape,\n    const std::vector<TIndex>& col_shape,\n    DType* grad_offset) {\n  CAFFE_ENFORCE_EQ(pad_t(), pad_b());\n  CAFFE_ENFORCE_EQ(pad_l(), pad_r());\n  const int pad_h = pad_t();\n  const int pad_w = pad_l();\n  index_t num_kernels = col_shape[1] * col_shape[2] * 2 * kernel_h() *\n      kernel_w() * deformable_group_;\n  index_t channel_per_deformable_group = col_shape[0] / deformable_group_;\n  // num_axes should be smaller than block size\n  CHECK_LT(2, CAFFE_CUDA_NUM_THREADS);\n  // To avoid involving atomic operations, we will launch one kernel per\n  // bottom dimension, and then in the kernel add up the top dimensions.\n  // NOLINT_NEXT_LINE(whitespace/operators)\n  deformable_col2im_coord_gpu_kernel<DType>\n      <<<CAFFE_GET_BLOCKS(num_kernels),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(\n          num_kernels,\n          data_col,\n          data_im,\n          data_offset,\n          im_shape[1],\n          im_shape[2],\n          im_shape[3],\n          kernel_h(),\n          kernel_w(),\n          pad_h,\n          pad_w,\n          stride_h(),\n          stride_w(),\n          dilation_h(),\n          dilation_w(),\n          channel_per_deformable_group,\n          col_shape[1],\n          col_shape[2],\n          grad_offset);\n}\n\nREGISTER_CUDA_OPERATOR(DeformConv, DeformConvOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    DeformConvGradient,\n    DeformConvGradientOp<float, CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/deform_conv_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_DEFORM_CONV_OP_H_\n#define CAFFE2_OPERATORS_DEFORM_CONV_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_op_shared.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n\nCAFFE2_DECLARE_bool(caffe2_force_shared_col_buffer);\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass DeformConvOpBase : public ConvPoolOpBase<Context> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(Context);\n  DeformConvOpBase(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<Context>(operator_def, ws),\n        deformable_group_(\n            OperatorBase::GetSingleArgument<int>(\"deformable_group\", 1)) {}\n  ~DeformConvOpBase() {}\n\n protected:\n  void DeformableIm2col(\n      const T* data_im,\n      const T* data_offset,\n      const std::vector<TIndex>& im_shape,\n      const std::vector<TIndex>& col_shape,\n      T* data_col);\n  void DeformableCol2im(\n      const T* data_col,\n      const T* data_offset,\n      const std::vector<TIndex>& im_shape,\n      const std::vector<TIndex>& col_shape,\n      T* grad_im);\n  void DeformableCol2imCoord(\n      const T* data_col,\n      const T* data_im,\n      const T* data_offset,\n      const std::vector<TIndex>& im_shape,\n      const std::vector<TIndex>& col_shape,\n      T* grad_offset);\n\n protected:\n  int deformable_group_;\n\n#define USE_DEFORMABLE_CONV_BASE_FUNCTIONS(T, Context)   \\\n  USE_CONV_POOL_BASE_FUNCTIONS(Context);                 \\\n  using DeformConvOpBase<T, Context>::deformable_group_; \\\n  using DeformConvOpBase<T, Context>::DeformableIm2col;  \\\n  using DeformConvOpBase<T, Context>::DeformableCol2im;  \\\n  using DeformConvOpBase<T, Context>::DeformableCol2imCoord\n};\n\ntemplate <typename T, class Context>\nclass DeformConvOp final : public DeformConvOpBase<T, Context> {\n public:\n  USE_DEFORMABLE_CONV_BASE_FUNCTIONS(T, Context);\n\n  DeformConvOp(const OperatorDef& operator_def, Workspace* ws)\n      : DeformConvOpBase<T, Context>(operator_def, ws) {\n    // Create shared buffer mutex in the constructor\n    // to avoid race-condition in DAGNet.\n    if (FLAGS_caffe2_force_shared_col_buffer || shared_buffer_) {\n      createSharedBuffer<Context>(ws_);\n    }\n  }\n  ~DeformConvOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n\n private:\n  Tensor<Context> col_buffer_;\n  Tensor<Context> bias_multiplier_;\n  Tensor<Context> img_shape_device_;\n  Tensor<Context> col_buffer_shape_device_;\n  // Input: X, o, W, b\n  // Output: Y\n  INPUT_TAGS(INPUT, OFFSET, FILTER, BIAS);\n};\n\ntemplate <typename T, class Context>\nclass DeformConvGradientOp final : public DeformConvOpBase<T, Context> {\n public:\n  USE_DEFORMABLE_CONV_BASE_FUNCTIONS(T, Context);\n\n  DeformConvGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : DeformConvOpBase<T, Context>(operator_def, ws),\n        no_bias_(OperatorBase::GetSingleArgument<int>(\"no_bias\", 0)) {\n    CAFFE_ENFORCE(\n        !(no_bias_ && OutputSize() == 4),\n        \"If bias is not present, you should not have 4 grad output.\");\n  }\n  ~DeformConvGradientOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n\n private:\n  Tensor<Context> col_buffer_;\n  Tensor<Context> bias_multiplier_;\n  Tensor<Context> img_shape_device_;\n  Tensor<Context> col_buffer_shape_device_;\n  bool no_bias_;\n  // input: X, W, dY\n  // output: dO, dW, db, and optionally dX\n  INPUT_TAGS(INPUT, OFFSET, FILTER, OUTPUT_GRAD);\n  OUTPUT_TAGS(OFFSET_GRAD, FILTER_GRAD, BIAS_OR_INPUT_GRAD, INPUT_GRAD);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_DEFORM_CONV_OP_H_\n"
  },
  {
    "path": "caffe2/operators/deform_conv_op_impl.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// conv_op_impl.h is the templated implementation of the conv_op.h file.\n#ifndef CAFFE2_OPERATORS_DEFORM_CONV_OP_IMPL_H_\n#define CAFFE2_OPERATORS_DEFORM_CONV_OP_IMPL_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/operators/deform_conv_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nbool DeformConvOp<T, Context>::RunOnDeviceWithOrderNCHW() {\n  const Tensor<Context>& X = Input(INPUT);\n  const Tensor<Context>& offset = Input(OFFSET);\n  auto& filter = Input(FILTER);\n  Tensor<Context>* Y = Output(0);\n  const int N = X.dim32(0), C = X.dim32(1);\n  CAFFE_ENFORCE_EQ(X.ndim(), filter.ndim());\n  const int M = filter.dim32(0);\n  CAFFE_ENFORCE(\n      C == filter.dim32(1) * group_,\n      \"Convolution op: input channels does not match: # of input channels \",\n      C,\n      \" is not equal to kernel channels * group:\",\n      filter.dim32(1),\n      \"*\",\n      group_);\n  CAFFE_ENFORCE(\n      M % group_ == 0,\n      \"The number of output channels is not divisible by group.\");\n  CAFFE_ENFORCE(\n      kernel_.size() == 2,\n      \"Deformable convolution only supports 2d kernel, has \",\n      kernel_.size(),\n      \"d kernel.\");\n  CAFFE_ENFORCE(\n      offset.ndim() == 4,\n      \"Deformable convolution only supports 4d offset, has \",\n      offset.ndim(),\n      \"d offset.\");\n  CAFFE_ENFORCE_EQ(offset.dim32(0), N);\n  CAFFE_ENFORCE(\n      C % deformable_group_ == 0,\n      \"The number of input channels \",\n      C,\n      \" is not divisible by deformable group \",\n      deformable_group_);\n  CAFFE_ENFORCE(\n      M % deformable_group_ == 0,\n      \"The number of output channels \",\n      M,\n      \" is not divisible by deformable group \",\n      deformable_group_);\n  CAFFE_ENFORCE(\n      offset.dim32(1) == 2 * kernel_h() * kernel_w() * deformable_group_,\n      \"Deformable convolution: offset 1st dimension must equal \"\n      \"2 * kernel_h * kernel_w * deformable_group: 2 * \",\n      kernel_h(),\n      \" * \",\n      kernel_w(),\n      \" * \",\n      deformable_group_);\n\n  CAFFE_ENFORCE_EQ(\n      offset.dim32(2),\n      (X.dim32(2) + pad_t() + pad_b() - (dilation_h() * (kernel_h() - 1) + 1)) /\n              stride_h() +\n          1);\n  CAFFE_ENFORCE_EQ(\n      offset.dim32(3),\n      (X.dim32(3) + pad_l() + pad_r() - (dilation_w() * (kernel_w() - 1) + 1)) /\n              stride_w() +\n          1);\n\n  int kernel_dims_size = 1;\n  for (int i = 0; i < kernel_.size(); ++i) {\n    CAFFE_ENFORCE(filter.dim32(i + 2) == kernel_[i]);\n    kernel_dims_size *= kernel_[i];\n  }\n\n  ConvPoolOpBase<Context>::SetOutputSize(X, Y, filter.dim32(0));\n\n  const vector<int> input_dims = GetDims(X);\n  const vector<int> output_dims = GetDims(*Y);\n  const int input_image_size = this->GetDimsSize(X);\n  const int output_image_size = this->GetDimsSize(*Y);\n\n  vector<int> img_shape;\n  img_shape.assign(X.dims().begin() + 1, X.dims().end());\n\n  vector<int> buffer_shape;\n  buffer_shape.push_back(C / group_ * kernel_dims_size);\n  buffer_shape.insert(\n      buffer_shape.end(), output_dims.begin(), output_dims.end());\n\n  // The dimension of each kernel\n  const int kernel_dim = C / group_ * kernel_dims_size;\n  // The offset corresponding to a single input image, and a single output\n  // image.\n  const int input_offset = C / group_ * input_image_size;\n  const int output_offset = M / group_ * output_image_size;\n  const int offset_offset = offset.size() / offset.dim32(0);\n  const int filter_offset = filter.size() / group_;\n\n  // The col buffer is stored in CHW order as well - kernel_dim, and the height\n  // and width.\n  const T* Xdata = X.template data<T>();\n  const T* offset_data = offset.template data<T>();\n\n  if (InputSize() == 4) {\n    auto& bias = Input(BIAS);\n    CAFFE_ENFORCE(bias.ndim() == 1);\n    CAFFE_ENFORCE(bias.dim32(0) == M);\n    if (bias_multiplier_.size() != output_image_size) {\n      // If the helper bias multiplier is not image size, reshape and fill it\n      // with\n      // one.\n      bias_multiplier_.Resize(vector<TIndex>(1, output_image_size));\n      math::Set<T, Context>(\n          output_image_size,\n          static_cast<T>(1),\n          bias_multiplier_.template mutable_data<T>(),\n          &context_);\n    }\n  }\n  T* Ydata = Y->template mutable_data<T>();\n  const T* bias_data = nullptr;\n  if (InputSize() == 4) {\n    bias_data = Input(BIAS).template data<T>();\n  }\n\n  auto f = [&](Tensor<Context>* col_buffer) {\n    col_buffer->Resize(buffer_shape);\n    T* col_buffer_data = col_buffer->template mutable_data<T>();\n    // Im2col, followed by gemm.\n    for (int image_id = 0; image_id < N; ++image_id) {\n      for (int group_id = 0; group_id < group_; ++group_id) {\n        DeformableIm2col(\n            Xdata + group_id * input_offset,\n            offset_data,\n            X.dims(),\n            col_buffer->dims(),\n            col_buffer_data);\n        // Weight term\n        math::Gemm<T, Context>(\n            CblasNoTrans,\n            CblasNoTrans,\n            M / group_,\n            output_image_size,\n            kernel_dim,\n            1,\n            filter.template data<T>() + group_id * filter_offset,\n            col_buffer_data,\n            0,\n            Ydata + group_id * output_offset,\n            &context_);\n      }\n      if (bias_data) {\n        math::Gemm<T, Context>(\n            CblasNoTrans,\n            CblasNoTrans,\n            M,\n            output_image_size,\n            1,\n            1,\n            bias_data,\n            bias_multiplier_.template data<T>(),\n            1,\n            Ydata,\n            &context_);\n      }\n      Xdata += input_offset * group_;\n      Ydata += output_offset * group_;\n      offset_data += offset_offset;\n    }\n  };\n\n  if (FLAGS_caffe2_force_shared_col_buffer || shared_buffer_) {\n    runWithSharedBuffer<Context>(ws_, f);\n  } else {\n    f(&col_buffer_);\n  }\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool DeformConvGradientOp<T, Context>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(INPUT);\n  auto& offset = Input(OFFSET);\n  auto& filter = Input(FILTER);\n  auto& dY = Input(OUTPUT_GRAD);\n  auto* dfilter = Output(FILTER_GRAD);\n  auto* doffset = Output(OFFSET_GRAD);\n  const int N = X.dim32(0), C = X.dim32(1);\n\n  const vector<int> input_dims = this->GetDims(X);\n  const int input_image_size = this->GetDimsSize(X);\n\n  const vector<int> output_dims = this->GetDims(dY);\n  // The output image size is the spatial size of the output.\n  const int output_image_size = this->GetDimsSize(dY);\n\n  ConvPoolOpBase<Context>::ComputePads(input_dims);\n  CAFFE_ENFORCE_EQ(X.ndim(), filter.ndim());\n  const int M = filter.dim32(0);\n  CAFFE_ENFORCE(filter.dim32(1) * group_ == C);\n\n  CAFFE_ENFORCE(\n      kernel_.size() == 2,\n      \"Deformable convolution only supports 2d kernel, has \",\n      kernel_.size(),\n      \"d kernel.\");\n  CAFFE_ENFORCE(\n      offset.ndim() == 4,\n      \"Deformable convolution only supports 4d offset, has \",\n      offset.ndim(),\n      \"d offset.\");\n  CAFFE_ENFORCE_EQ(offset.dim32(0), N);\n  CAFFE_ENFORCE(\n      C % deformable_group_ == 0,\n      \"The number of input channels \",\n      C,\n      \" is not divisible by deformable group \",\n      deformable_group_);\n  CAFFE_ENFORCE(\n      M % deformable_group_ == 0,\n      \"The number of output channels \",\n      M,\n      \" is not divisible by deformable group \",\n      deformable_group_);\n  CAFFE_ENFORCE(\n      offset.dim32(1) == 2 * kernel_h() * kernel_w() * deformable_group_,\n      \"Deformable convolution: offset 1st dimension must equal \"\n      \"2 * kernel_h * kernel_w * deformable_group: 2 * \",\n      kernel_h(),\n      \" * \",\n      kernel_w(),\n      \" * \",\n      deformable_group_);\n\n  CAFFE_ENFORCE_EQ(\n      offset.dim32(2),\n      (X.dim32(2) + pad_t() + pad_b() - (dilation_h() * (kernel_h() - 1) + 1)) /\n              stride_h() +\n          1);\n  CAFFE_ENFORCE_EQ(\n      offset.dim32(3),\n      (X.dim32(3) + pad_l() + pad_r() - (dilation_w() * (kernel_w() - 1) + 1)) /\n              stride_w() +\n          1);\n\n  int kernel_dims_size = 1;\n  for (int i = 0; i < kernel_.size(); ++i) {\n    CAFFE_ENFORCE(filter.dim32(i + 2) == kernel_[i]);\n    kernel_dims_size *= kernel_[i];\n  }\n\n  CAFFE_ENFORCE(M % group_ == 0);\n  dfilter->ResizeLike(filter);\n  doffset->ResizeLike(offset);\n\n  // The dimension of each kernel\n  const int kernel_dim = C / group_ * kernel_dims_size;\n  // The offset corresponding to a single input image, and a single output\n  // image.\n  const int input_offset = C / group_ * input_image_size;\n  const int output_offset = M / group_ * output_image_size;\n  const int offset_offset = offset.size() / offset.dim32(0);\n  const int filter_offset = filter.size() / group_;\n\n  // The col buffer is stored in CHW order as well - kernel_dim, and the\n  // height and width.\n  vector<TIndex> img_shape;\n  img_shape.assign(X.dims().begin() + 1, X.dims().end());\n  vector<TIndex> col_buffer_shape;\n  col_buffer_shape.push_back(C * kernel_dims_size);\n  col_buffer_shape.insert(\n      col_buffer_shape.end(), output_dims.begin(), output_dims.end());\n  col_buffer_.Resize(col_buffer_shape);\n\n  const int col_buffer_offset = col_buffer_.size() / group_;\n\n  const T* Xdata = X.template data<T>();\n  const T* filter_data = filter.template data<T>();\n  const T* offset_data = offset.template data<T>();\n  const T* dYdata = dY.template data<T>();\n  T* col_buffer_data = col_buffer_.template mutable_data<T>();\n  T* dfilter_data = dfilter->template mutable_data<T>();\n  T* doffset_data = doffset->template mutable_data<T>();\n\n  // Pre-setting the gradients to zero.\n  math::Set<T, Context>(dfilter->size(), 0, dfilter_data, &context_);\n\n  T* dbias_data = nullptr;\n  if (!no_bias_) {\n    auto* dbias = Output(BIAS_OR_INPUT_GRAD);\n    dbias->Resize(M);\n    if (bias_multiplier_.size() != output_image_size) {\n      // If the helper bias multiplier is not M, reshape and fill it with one.\n      bias_multiplier_.Resize(vector<TIndex>(1, output_image_size));\n      math::Set<T, Context>(\n          output_image_size,\n          static_cast<T>(1),\n          bias_multiplier_.template mutable_data<T>(),\n          &context_);\n    }\n    dbias_data = dbias->template mutable_data<T>();\n    math::Set<T, Context>(dbias->size(), 0, dbias_data, &context_);\n  }\n\n  T* dXdata = nullptr;\n  if (OutputSize() == 4 || (no_bias_ && (OutputSize() == 3))) {\n    auto* dX = Output(no_bias_ ? BIAS_OR_INPUT_GRAD : INPUT_GRAD);\n    dX->ResizeLike(X);\n    dXdata = dX->template mutable_data<T>();\n    math::Set<T, Context>(dX->size(), 0, dXdata, &context_);\n  }\n\n  for (int image_id = 0; image_id < N; ++image_id) {\n    for (int group_id = 0; group_id < group_; ++group_id) {\n      math::Gemm<T, Context>(\n          CblasTrans,\n          CblasNoTrans,\n          kernel_dim,\n          output_image_size,\n          M / group_,\n          1,\n          filter_data + group_id * filter_offset,\n          dYdata + group_id * output_offset,\n          0,\n          col_buffer_data + group_id * col_buffer_offset,\n          &context_);\n    }\n\n    // Gradient with respect to offsets\n    DeformableCol2imCoord(\n        col_buffer_data,\n        Xdata,\n        offset_data,\n        X.dims(),\n        col_buffer_shape,\n        doffset_data);\n\n    // Gradient with respect to input data\n    if (dXdata) {\n      DeformableCol2im(\n          col_buffer_data, offset_data, X.dims(), col_buffer_shape, dXdata);\n      dXdata += input_offset * group_;\n    }\n\n    // Gradient with respect to filter\n    DeformableIm2col(\n        Xdata, offset_data, X.dims(), col_buffer_shape, col_buffer_data);\n\n    for (int group_id = 0; group_id < group_; ++group_id) {\n      math::Gemm<T, Context>(\n          CblasNoTrans,\n          CblasTrans,\n          M / group_,\n          kernel_dim,\n          output_image_size,\n          1,\n          dYdata + group_id * output_offset,\n          col_buffer_data + group_id * col_buffer_offset,\n          1,\n          dfilter_data + group_id * filter_offset,\n          &context_);\n    }\n\n    // Gradient with respect to bias\n    if (dbias_data) {\n      math::Gemv<T, Context>(\n          CblasNoTrans,\n          M,\n          output_image_size,\n          1,\n          dYdata,\n          bias_multiplier_.template data<T>(),\n          1,\n          dbias_data,\n          &context_);\n    }\n\n    Xdata += input_offset * group_;\n    dYdata += output_offset * group_;\n    offset_data += offset_offset;\n    doffset_data += offset_offset;\n  }\n\n  return true;\n}\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_DEFORM_CONV_OP_IMPL_H_\n"
  },
  {
    "path": "caffe2/operators/distance_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/distance_op.h\"\n\nnamespace caffe2 {\n\ntemplate<>\nbool SquaredL2DistanceOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto* distance = Output(0);\n  CAFFE_ENFORCE_EQ(X.ndim(), Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE_EQ(X.dim32(i), Y.dim32(i));\n  }\n  int N = X.ndim() > 0 ? X.dim32(0) : 1;\n  distance->Resize(N);\n  int D = N > 0 ? X.size() / N : 0;\n  float* distance_data = distance->mutable_data<float>();\n  const float* X_data = X.data<float>();\n  const float* Y_data = Y.data<float>();\n  for (int i = 0; i < N; ++i) {\n    float Xscale, Yscale, cross;\n    math::Dot<float, CPUContext>(\n        D, X_data + i * D, X_data + i * D, &Xscale, &context_);\n    math::Dot<float, CPUContext>(\n        D, Y_data + i * D, Y_data + i * D, &Yscale, &context_);\n    math::Dot<float, CPUContext>(\n        D, X_data + i * D, Y_data + i * D, &cross, &context_);\n    distance_data[i] = (Xscale + Yscale) * 0.5 - cross;\n  }\n  return true;\n}\n\ntemplate <>\nbool L1DistanceOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto* distance = Output(0);\n  CAFFE_ENFORCE_EQ(X.ndim(), Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE_EQ(X.dim32(i), Y.dim32(i));\n  }\n  int N = X.ndim() > 0 ? X.dim32(0) : 1;\n  distance->Resize(N);\n  int D = N > 0 ? X.size() / N : 0;\n\n  const float* X_data = X.data<float>();\n  const float* Y_data = Y.data<float>();\n\n  for (int i = 0; i < N; ++i) {\n    (distance->mutable_data<float>())[i] =\n        (ConstEigenVectorMap<float>(X_data + i * D, D).array() -\n         ConstEigenVectorMap<float>(Y_data + i * D, D).array())\n            .abs()\n            .sum();\n  }\n  return true;\n}\n\ntemplate <>\nbool L1DistanceGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dDistance = Input(2);\n  auto* dX = Output(0);\n  auto* dY = Output(1);\n  CAFFE_ENFORCE_EQ(X.ndim(), Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE_EQ(X.dim32(i), Y.dim32(i));\n  }\n  int N = X.ndim() > 0 ? X.dim32(0) : 1;\n  int D = N > 0 ? X.size() / N : 0;\n  CAFFE_ENFORCE(X.ndim() == Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE(X.dim32(i) == Y.dim32(i));\n  }\n  CAFFE_ENFORCE(dDistance.ndim() == 1);\n  CAFFE_ENFORCE(dDistance.dim32(0) == N);\n  dX->ResizeLike(X);\n  dY->ResizeLike(Y);\n\n  for (int i = 0; i < N; ++i) {\n    auto offset = i * D;\n    for (int j = 0; j < D; ++j) {\n      const float temp =\n          (X.data<float>())[offset + j] - (Y.data<float>())[offset + j];\n      const float kEps = 1e-12f;\n      if (temp < -kEps) {\n        dX->mutable_data<float>()[offset + j] = -(dDistance.data<float>())[i];\n        dY->mutable_data<float>()[offset + j] = (dDistance.data<float>())[i];\n      } else if (temp > kEps) {\n        dX->mutable_data<float>()[offset + j] = (dDistance.data<float>())[i];\n        dY->mutable_data<float>()[offset + j] = -(dDistance.data<float>())[i];\n      } else {\n        dX->mutable_data<float>()[offset + j] = 0;\n        dY->mutable_data<float>()[offset + j] = 0;\n      }\n    }\n  }\n  return true;\n}\n\ntemplate <>\nbool CosineSimilarityOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(X_IN);\n  auto& Y = Input(Y_IN);\n  auto* result = Output(COS_OUT);\n  CAFFE_ENFORCE_EQ(X.ndim(), Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE_EQ(X.dim32(i), Y.dim32(i));\n  }\n  const int N = X.ndim() > 0 ? X.dim32(0) : 1;\n  const int D = X.size_from_dim(1);\n  result->Resize(N);\n  float* result_data = result->mutable_data<float>();\n  const float* X_data = X.data<float>();\n  const float* Y_data = Y.data<float>();\n  float X2, Y2;\n  const float kEps = 1e-12f;\n  for (int i = 0; i < N; ++i) { // TODO: multithreading\n    auto offset = i * D;\n    math::Dot<float, CPUContext>(\n        D, X_data + offset, X_data + offset, &X2, &context_);\n    math::Dot<float, CPUContext>(\n        D, Y_data + offset, Y_data + offset, &Y2, &context_);\n    math::Dot<float, CPUContext>(\n        D, X_data + offset, Y_data + offset, result_data + i, &context_);\n    result_data[i] /= std::sqrt(std::max(X2, kEps) * std::max(Y2, kEps));\n  }\n  return true;\n}\n\ntemplate <>\nbool CosineSimilarityGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(X_IN);\n  auto& Y = Input(Y_IN);\n  auto& dCos = Input(DER_COS_IN);\n  auto* dX = Output(DER_X_OUT);\n  auto* dY = Output(DER_Y_OUT);\n  const int N = X.ndim() > 0 ? X.dim32(0) : 1;\n  const int D = X.size_from_dim(1);\n  CAFFE_ENFORCE(X.ndim() == Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE(X.dim32(i) == Y.dim32(i));\n  }\n  CAFFE_ENFORCE(dCos.ndim() == 1);\n  CAFFE_ENFORCE(dCos.dim32(0) == N);\n  dX->ResizeLike(X);\n  dY->ResizeLike(Y);\n\n  const auto* X_data = X.template data<float>();\n  const auto* Y_data = Y.template data<float>();\n  const auto* dCos_data = dCos.template data<float>();\n  auto* dX_data = dX->template mutable_data<float>();\n  auto* dY_data = dY->template mutable_data<float>();\n  float XN, YN, XY;\n  const float kEps = 1e-12f;\n  for (int i = 0; i < N; ++i) { // TODO: multithreading\n    auto offset = i * D;\n\n    // TODO: cache these result from the forward pass\n    // ||x||\n    math::Dot<float, CPUContext>(\n        D, X_data + offset, X_data + offset, &XN, &context_);\n    XN = std::sqrt(std::max(XN, kEps));\n    // ||y||\n    math::Dot<float, CPUContext>(\n        D, Y_data + offset, Y_data + offset, &YN, &context_);\n    YN = std::sqrt(std::max(YN, kEps));\n    // ||x|| * || y ||\n    float XYN = XN * YN;\n    // x^Ty\n    math::Dot<float, CPUContext>(\n        D, X_data + offset, Y_data + offset, &XY, &context_);\n\n    math::Scale<float, CPUContext>(\n        D, dCos_data[i] / XYN, Y_data + offset, dX_data + offset, &context_);\n    math::Axpy(\n        D,\n        -dCos_data[i] * XY / (XN * XN * XYN),\n        X_data + offset,\n        dX_data + offset,\n        &context_);\n\n    math::Scale<float, CPUContext>(\n        D, dCos_data[i] / XYN, X_data + offset, dY_data + offset, &context_);\n    math::Axpy(\n        D,\n        -dCos_data[i] * XY / (YN * YN * XYN),\n        Y_data + offset,\n        dY_data + offset,\n        &context_);\n  }\n\n  return true;\n}\n\ntemplate <>\nbool DotProductOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(X_IN);\n  auto& Y = Input(Y_IN);\n  auto* result = Output(DOT_OUT);\n  CAFFE_ENFORCE_EQ(X.ndim(), Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE_EQ(X.dim32(i), Y.dim32(i), \"dimension at \", i);\n  }\n  int N, D;\n  if (X.size() > 0) {\n    N = X.ndim() > 0 ? X.dim32(0) : 1;\n    D = X.size() / N;\n  } else {\n    N = 0;\n    D = 0;\n  }\n  result->Resize(N);\n  float* result_data = result->template mutable_data<float>();\n  const float* X_data = X.template data<float>();\n  const float* Y_data = Y.template data<float>();\n  for (int i = 0; i < N; ++i) { // TODO: multithreading\n    auto offset = i * D;\n    math::Dot<float, CPUContext>(\n        D, X_data + offset, Y_data + offset, result_data + i, &context_);\n  }\n  return true;\n}\n\ntemplate <>\nbool DotProductGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(X_IN);\n  auto& Y = Input(Y_IN);\n  auto& dDot = Input(DER_DOT_IN);\n  auto* dX = Output(DER_X_OUT);\n  auto* dY = Output(DER_Y_OUT);\n  int N, D;\n  if (X.size() > 0) {\n    N = X.ndim() > 0 ? X.dim32(0) : 1;\n    D = X.size() / N;\n  } else {\n    N = 0;\n    D = 0;\n  }\n  CAFFE_ENFORCE(X.ndim() == Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE(X.dim32(i) == Y.dim32(i));\n  }\n  CAFFE_ENFORCE(dDot.ndim() == 1);\n  CAFFE_ENFORCE(dDot.dim32(0) == N);\n  dX->ResizeLike(X);\n  dY->ResizeLike(Y);\n\n  const auto* X_data = X.template data<float>();\n  const auto* Y_data = Y.template data<float>();\n  const auto* dDot_data = dDot.template data<float>();\n  auto* dX_data = dX->template mutable_data<float>();\n  auto* dY_data = dY->template mutable_data<float>();\n  for (int i = 0; i < N; ++i) { // TODO: multithreading\n    auto offset = i * D;\n    math::Scale<float, CPUContext>(\n        D, dDot_data[i], X_data + offset, dY_data + offset, &context_);\n    math::Scale<float, CPUContext>(\n        D, dDot_data[i], Y_data + offset, dX_data + offset, &context_);\n  }\n  return true;\n}\n\ntemplate <>\nbool DotProductWithPaddingOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(X_IN);\n  auto& Y = Input(Y_IN);\n  auto* result = Output(DOT_OUT);\n  CAFFE_ENFORCE_EQ(X.ndim(), Y.ndim());\n  CAFFE_ENFORCE_EQ(X.dim32(0), Y.dim32(0));\n\n  int N, D, DX, DY, restD;\n  if (X.size() > 0) {\n    N = X.ndim() > 0 ? X.dim32(0) : 1;\n    DX = X.size() / N;\n    DY = Y.size() / N;\n  } else {\n    N = 0;\n    DX = 0;\n    DY = 0;\n  }\n\n  D = std::min(DX, DY);\n  restD = std::max(DX, DY) - D;\n  result->Resize(N);\n  float* result_data = result->mutable_data<float>();\n  const float* X_data = X.data<float>();\n  const float* Y_data = Y.data<float>();\n  for (int i = 0; i < N; ++i) { // TODO: multithreading\n    auto offsetX = i * DX, offsetY = i * DY;\n    if (replicate_) {\n      // L_ for longer vector and S_ for shorter vector\n      const float *L_data, *S_data;\n      int DL, DS;\n      if (DX > DY) {\n        L_data = X_data + offsetX;\n        S_data = Y_data + offsetY;\n        DL = DX;\n        DS = DY;\n      } else {\n        L_data = Y_data + offsetY;\n        S_data = X_data + offsetX;\n        DL = DY;\n        DS = DX;\n      }\n      float sum = 0.0;\n      float tmp = 0.0;\n      for (int j = 0; j < DL / DS; j++) {\n        math::Dot<float, CPUContext>(\n            DS, L_data + j * DS, S_data, &tmp, &context_);\n        sum += tmp;\n      }\n      *(result_data + i) = sum;\n    } else {\n      math::Dot<float, CPUContext>(\n          D, X_data + offsetX, Y_data + offsetY, result_data + i, &context_);\n    }\n\n    if (!replicate_ && DX != DY) {\n      const float* rest_data;\n      float rest_sum = 0;\n      if (DX > DY) {\n        rest_data = X_data + offsetX + D;\n      } else {\n        rest_data = Y_data + offsetY + D;\n      }\n      math::Sum<float, CPUContext>(restD, rest_data, &rest_sum, &context_);\n      result_data[i] += rest_sum * pad_value_;\n    }\n  }\n  return true;\n}\n\n// L2\nREGISTER_CPU_OPERATOR(SquaredL2Distance,\n                      SquaredL2DistanceOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(SquaredL2DistanceGradient,\n                      SquaredL2DistanceGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(SquaredL2Distance)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInputDim(0, 0)\n    .SetDoc(R\"DOC(\nGiven two input float tensors X, Y, and produces one output float tensor\nof the L2 difference between X and Y that is computed as ||(X - Y)^2 / 2||.\n)DOC\")\n    .Input(0, \"X\", \"1D or 2D input tensor\")\n    .Input(1, \"Y\", \"1D or 2D input tensor (must have the same shape as X)\")\n    .Output(0, \"Z\", \"1D output tensor\");\n\nOPERATOR_SCHEMA(SquaredL2DistanceGradient).NumInputs(3).NumOutputs(2);\n\nclass GetSquaredL2DistanceGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SquaredL2DistanceGradient\", \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(0), GI(1)});\n  }\n};\nREGISTER_GRADIENT(SquaredL2Distance, GetSquaredL2DistanceGradient);\n\n// L1\nREGISTER_CPU_OPERATOR(L1Distance, L1DistanceOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    L1DistanceGradient,\n    L1DistanceGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(L1Distance)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInputDim(0, 0)\n    .SetDoc(R\"DOC(\nGiven two input float tensors X, Y, and produces one output float tensor\nof the L1 difference between X and Y, computed as L1(x,y) = sum over |x-y|\n)DOC\")\n    .Input(0, \"X\", \"1D or 2D input tensor\")\n    .Input(1, \"Y\", \"1D or 2D input tensor (must have the same shape as X)\")\n    .Output(0, \"Z\", \"1D output tensor\");\n\nOPERATOR_SCHEMA(L1DistanceGradient).NumInputs(3).NumOutputs(2);\n\nclass GetL1DistanceGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"L1DistanceGradient\",\n        \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(0), GI(1)});\n  }\n};\n\nREGISTER_GRADIENT(L1Distance, GetL1DistanceGradient);\n\n// Dot Product\nREGISTER_CPU_OPERATOR(DotProduct, DotProductOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    DotProductGradient,\n    DotProductGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(DotProduct)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInputDim(0, 0)\n    .SetDoc(R\"DOC(\nGiven two input float tensors X, Y, and produces one output float tensor\nof the dot product between X and Y.\n)DOC\")\n    .Input(0, \"X\", \"1D or 2D input tensor\")\n    .Input(1, \"Y\", \"1D or 2D input tensor (must have the same shape as X)\")\n    .Output(0, \"Z\", \"1D output tensor\");\n\nOPERATOR_SCHEMA(DotProductGradient).NumInputs(3).NumOutputs(2);\n\nclass GetDotProductGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"DotProductGradient\",\n        \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(0), GI(1)});\n  }\n};\nREGISTER_GRADIENT(DotProduct, GetDotProductGradient);\n\n// Cosine Similarity\nREGISTER_CPU_OPERATOR(CosineSimilarity, CosineSimilarityOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    CosineSimilarityGradient,\n    CosineSimilarityGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(CosineSimilarity)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInputDim(0, 0)\n    .SetDoc(R\"DOC(\nGiven two input float tensors X, Y, and produces one output float tensor\nof the cosine similarity between X and Y.\n)DOC\")\n    .Input(0, \"X\", \"1D or 2D input tensor\")\n    .Input(1, \"Y\", \"1D or 2D input tensor (must have the same shape as X)\")\n    .Output(0, \"Z\", \"1D output tensor\");\n\nOPERATOR_SCHEMA(CosineSimilarityGradient).NumInputs(3).NumOutputs(2);\n\nclass GetCosineSimilarityGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"CosineSimilarityGradient\",\n        \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(0), GI(1)});\n  }\n};\nREGISTER_GRADIENT(CosineSimilarity, GetCosineSimilarityGradient);\n\n// Dot Product allows padding\nREGISTER_CPU_OPERATOR(\n    DotProductWithPadding,\n    DotProductWithPaddingOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    DotProductWithPaddingGradient,\n    DotProductWithPaddingGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(DotProductWithPadding)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven two input float tensors X, Y with different shapes and produces one\noutput float tensor of the dot product between X and Y. We currently support\ntwo kinds of strategies to achieve this. Before doing normal dot_product 1)\npad the smaller tensor (using pad_value) to the same shape as the other one.\n2) replicate the smaller tensor to the same shape as the other one. Note the\nfirst dimension of X, Y must be equal. Only the second dimension of X or Y\ncan be padded.\n)DOC\")\n    .Input(0, \"X\", \"1D or 2D input tensor\")\n    .Input(1, \"Y\", \"1D or 2D input tensor\")\n    .Output(0, \"Z\", \"1D output tensor\")\n    .IdenticalTypeAndShapeOfInputDim(0, 0)\n    .Arg(\"pad_value\", \"the padding value for tensors with smaller dimension\")\n    .Arg(\"replicate\", \"whether to replicate the smaller tensor or not\");\n\nOPERATOR_SCHEMA(DotProductWithPaddingGradient).NumInputs(3).NumOutputs(2);\n\nclass GetDotProductWithPaddingGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    float pad_value = 0;\n    bool replicate = false;\n    if (ArgumentHelper::HasArgument(Def(), \"pad_value\")) {\n      pad_value = GetArgument(Def(), \"pad_value\").f();\n    }\n    if (ArgumentHelper::HasArgument(Def(), \"replicate\")) {\n      replicate = GetArgument(Def(), \"replicate\").i();\n    }\n\n    const auto dot_arg =\n        vector<Argument>{MakeArgument<float>(\"pad_value\", pad_value),\n                         MakeArgument<bool>(\"replicate\", replicate)};\n\n    return SingleGradientDef(\n        \"DotProductWithPaddingGradient\",\n        \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(0), GI(1)},\n        dot_arg);\n  }\n};\nREGISTER_GRADIENT(DotProductWithPadding, GetDotProductWithPaddingGradient);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/distance_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cub/block/block_reduce.cuh>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/distance_op.h\"\n#include \"caffe2/utils/conversions.h\"\n\n#include <cub/block/block_reduce.cuh>\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <typename T>\n__global__ void SquaredL2DistanceKernel(\n    const int N, const int D, const T* X, const T* Y, T* distance) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n\n  for (int i = blockIdx.x; i < N; i += gridDim.x) {\n    float dist = 0.0;\n    for (int j = threadIdx.x; j < D; j += blockDim.x) {\n      T diff = X[i * D + j] - Y[i * D + j];\n      dist += diff * diff;\n    }\n\n    float total_dist = BlockReduce(temp_storage).Sum(dist);\n    __syncthreads();\n    if (threadIdx.x == 0) {\n      distance[i] = total_dist / 2.0;\n    }\n  }\n}\n}  // namespace\n\ntemplate <>\nbool SquaredL2DistanceOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto* distance = Output(0);\n  CAFFE_ENFORCE_EQ(X.ndim(), Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE_EQ(\n        X.dim32(i),\n        Y.dim32(i),\n        \"Mismatch in dimensions\",\n        X.dims(),\n        \" / \",\n        Y.dims());\n  }\n  int N = X.ndim() > 0 ? X.dim32(0) : 1;\n  int D = X.size() / N;\n  distance->Resize(vector<TIndex>(size_t(1), N));\n  SquaredL2DistanceKernel<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N, D, X.data<float>(), Y.data<float>(), distance->mutable_data<float>());\n  return true;\n}\n\nnamespace {\ntemplate <typename T>\n__global__ void\nStripedScaleKernel(const int N, const int D, const T* alpha, const T* x, T* y) {\n  CUDA_1D_KERNEL_LOOP(i, N * D) {\n    int k = i / D;\n    y[i] = x[i] * alpha[k];\n  }\n}\n}\n\ntemplate <>\nbool SquaredL2DistanceGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dDistance = Input(2);\n  auto* dX = Output(0);\n  auto* dY = Output(1);\n  int N = X.ndim() > 0 ? X.dim32(0) : 1;\n  int D = N > 0 ? X.size() / N : 0;\n  CAFFE_ENFORCE(X.ndim() == Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE_EQ(\n        X.dim32(i),\n        Y.dim32(i),\n        \"Mismatch on dimensions: \",\n        X.dims(),\n        \" / \",\n        Y.dims());\n  }\n  CAFFE_ENFORCE_EQ(dDistance.ndim(), 1);\n  CAFFE_ENFORCE_EQ(dDistance.dim32(0), N);\n  dX->ResizeLike(X);\n  dY->ResizeLike(Y);\n  math::Sub<float, CUDAContext>(\n      X.size(),\n      X.data<float>(),\n      Y.data<float>(),\n      dX->mutable_data<float>(),\n      &context_);\n\n  StripedScaleKernel<float><<<\n      CAFFE_GET_BLOCKS(N * D),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      D,\n      dDistance.data<float>(),\n      dX->data<float>(),\n      dX->mutable_data<float>());\n\n  // The gradient of the other side is basically the negative.\n  math::Scale<float, CUDAContext>(\n      X.size(), -1, dX->data<float>(), dY->mutable_data<float>(), &context_);\n  return true;\n}\n\nnamespace {\ntemplate <typename T>\n__global__ void L1DistanceKernel(\n    const int N,\n    const int D,\n    const T* X,\n    const T* Y,\n    T* distance) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n\n  for (int i = blockIdx.x; i < N; i += gridDim.x) {\n    float sum = 0.0f;\n    for (int j = threadIdx.x; j < D; j += blockDim.x) {\n      sum +=\n          abs(convert::To<T, float>(X[i * D + j]) -\n              convert::To<T, float>(Y[i * D + j]));\n    }\n\n    float aggregate = BlockReduce(temp_storage).Sum(sum);\n    __syncthreads();\n    if (threadIdx.x == 0) {\n      distance[i] = aggregate;\n    }\n  }\n}\n} // namespace\n\ntemplate <>\nbool L1DistanceOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto* distance = Output(0);\n  CAFFE_ENFORCE_EQ(X.ndim(), Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE_EQ(X.dim32(i), Y.dim32(i));\n  }\n  const int N = X.ndim() > 0 ? X.dim32(0) : 1;\n  const int D = N > 0 ? X.size() / N : 0;\n  distance->Resize(vector<TIndex>(size_t(1), N));\n  L1DistanceKernel<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N, D, X.data<float>(), Y.data<float>(), distance->mutable_data<float>());\n\n  return true;\n}\n\nnamespace {\ntemplate <typename T>\n__global__ void L1DistanceGradientKernel(\n    const int N,\n    const int D,\n    const T* X,\n    const T* Y,\n    const T* dDistance,\n    T* dX,\n    T* dY) {\n  CUDA_1D_KERNEL_LOOP(i, N * D) {\n    constexpr float kEps = 1e-12;\n    int k = i / D;\n    if (X[i] - Y[i] < -kEps) {\n      dX[i] = -dDistance[k];\n      dY[i] = dDistance[k];\n    } else if (X[i] - Y[i] > kEps) {\n      dX[i] = dDistance[k];\n      dY[i] = -dDistance[k];\n    } else {\n      dX[i] = 0;\n      dY[i] = 0;\n    }\n  }\n}\n} // namespace\n\ntemplate <>\nbool L1DistanceGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dDistance = Input(2);\n  auto* dX = Output(0);\n  auto* dY = Output(1);\n  int N = X.ndim() > 0 ? X.dim32(0) : 1;\n  int D = N > 0 ? X.size() / N : 0;\n  CAFFE_ENFORCE(X.ndim() == Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE_EQ(\n        X.dim32(i),\n        Y.dim32(i),\n        \"Mismatch on dimensions: \",\n        X.dims(),\n        \" / \",\n        Y.dims());\n  }\n  CAFFE_ENFORCE_EQ(dDistance.ndim(), 1);\n  CAFFE_ENFORCE_EQ(dDistance.dim32(0), N);\n  dX->ResizeLike(X);\n  dY->ResizeLike(Y);\n\n  L1DistanceGradientKernel<<<\n      CAFFE_GET_BLOCKS(N * D),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      D,\n      X.data<float>(),\n      Y.data<float>(),\n      dDistance.data<float>(),\n      dX->mutable_data<float>(),\n      dY->mutable_data<float>());\n\n  return true;\n}\n\nnamespace {\ntemplate <typename T>\n__global__ void\nDotProductKernel(const int N, const int D, const T* X, const T* Y, T* result) {\n  for (int i = blockIdx.x; i < N; i += gridDim.x) {\n    T partialSum = 0;\n    int offset = i * D;\n    for (int j = threadIdx.x; j < D; j += blockDim.x) {\n      partialSum += X[offset + j] * Y[offset + j];\n    }\n\n    typedef cub::BlockReduce<T, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n    __shared__ typename BlockReduce::TempStorage temp_storage;\n    T sum = BlockReduce(temp_storage).Sum(partialSum);\n    __syncthreads();\n    if (threadIdx.x == 0) {\n      result[i] = sum;\n    }\n  }\n}\n\n// X.size() = N*D, Y.size() = N\ntemplate <typename T>\n__global__ void\nBatchedMul(const int N, const int D, const T* X, const T* Y, T* result) {\n  CUDA_1D_KERNEL_LOOP(i, N * D) {\n    result[i] = X[i] * Y[i / D];\n  }\n}\n\n// X.size() = N*D, Y.size() = N\ntemplate <typename T>\n__global__ void Scale2AxpyScale(\n    const int N,\n    const T* scale,\n    const T* XY,\n    const T* XN,\n    T* result) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    result[i] = -scale[i] * XY[i] / (XN[i] * XN[i]);\n  }\n}\n\n// X.size() = X*N, alpha.size() = N, Y.size() = X*N\ntemplate <typename T>\n__global__ void\nBatchedAxpy(const int N, const int D, const T* alpha, const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N * D) {\n    Y[i] += X[i] * alpha[i / D];\n  }\n}\n\n} // namespace\n\ntemplate <>\nbool CosineSimilarityOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(X_IN);\n  auto& Y = Input(Y_IN);\n  auto* result = Output(COS_OUT);\n  CAFFE_ENFORCE_EQ(X.ndim(), Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE_EQ(X.dim32(i), Y.dim32(i));\n  }\n  const int N = X.ndim() > 0 ? X.dim32(0) : 1;\n  const int D = X.size_from_dim(1);\n  result->Resize(N);\n  float* result_data = result->mutable_data<float>();\n  const float* X_data = X.data<float>();\n  const float* Y_data = Y.data<float>();\n  // Auxiliary arrays, one allocation of memory\n  aux_.Resize(2 * N);\n  float* aux_data = aux_.mutable_data<float>();\n  float* x2 = aux_data;\n  float* y2 = aux_data + N;\n  float* scale = x2;\n  const float kEps = 1e-12f;\n\n  DotProductKernel<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, D, X_data, X_data, x2);\n  DotProductKernel<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, D, Y_data, Y_data, y2);\n  DotProductKernel<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, D, X_data, Y_data, result_data);\n  math::Maximum<float, CUDAContext>(N, kEps, x2, x2, &context_);\n  math::Maximum<float, CUDAContext>(N, kEps, y2, y2, &context_);\n  math::Mul(N, x2, y2, scale, &context_);\n  math::InvSqrt(N, scale, scale, &context_);\n  math::Mul(N, result_data, scale, result_data, &context_);\n  return true;\n}\n\ntemplate <>\nbool CosineSimilarityGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(X_IN);\n  auto& Y = Input(Y_IN);\n  auto& dCos = Input(DER_COS_IN);\n  auto* dX = Output(DER_X_OUT);\n  auto* dY = Output(DER_Y_OUT);\n  const int N = X.ndim() > 0 ? X.dim32(0) : 1;\n  const int D = X.size_from_dim(1);\n  CAFFE_ENFORCE(X.ndim() == Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE(X.dim32(i) == Y.dim32(i));\n  }\n  CAFFE_ENFORCE(dCos.ndim() == 1);\n  CAFFE_ENFORCE(dCos.dim32(0) == N);\n  dX->ResizeLike(X);\n  dY->ResizeLike(Y);\n\n  const auto* X_data = X.data<float>();\n  const auto* Y_data = Y.data<float>();\n  const auto* dCos_data = dCos.data<float>();\n  auto* dX_data = dX->mutable_data<float>();\n  auto* dY_data = dY->mutable_data<float>();\n\n  // one memory allocation, a few arrays\n  aux_.Resize(6 * N);\n  float* aux_data = aux_.mutable_data<float>();\n  float* xn = aux_data;\n  float* yn = aux_data + N;\n  float* xy = aux_data + 2 * N;\n  float* xyn = aux_data + 3 * N;\n  float* scale = aux_data + 4 * N;\n  float* axpy_scale = aux_data + 5 * N;\n  float kEps = 1e-12f;\n\n  // ||x||\n  DotProductKernel<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, D, X_data, X_data, xn);\n  math::Maximum<float, CUDAContext>(N, kEps, xn, xn, &context_);\n  math::Sqrt<float, CUDAContext>(N, xn, xn, &context_);\n  // ||y||\n  DotProductKernel<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, D, Y_data, Y_data, yn);\n  math::Maximum<float, CUDAContext>(N, kEps, yn, yn, &context_);\n  math::Sqrt<float, CUDAContext>(N, yn, yn, &context_);\n  // ||x|| * || y ||\n  math::Mul<float, CUDAContext>(N, xn, yn, xyn, &context_);\n\n  DotProductKernel<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, D, X_data, Y_data, xy);\n  math::Div<float, CUDAContext>(N, dCos_data, xyn, scale, &context_);\n  // dX\n  BatchedMul<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, D, Y_data, scale, dX_data);\n  Scale2AxpyScale<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, scale, xy, xn, axpy_scale);\n  BatchedAxpy<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, D, axpy_scale, X_data, dX_data);\n  // dY\n  BatchedMul<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, D, X_data, scale, dY_data);\n  Scale2AxpyScale<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, scale, xy, yn, axpy_scale);\n  BatchedAxpy<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, D, axpy_scale, Y_data, dY_data);\n\n  return true;\n}\n\ntemplate <>\nbool DotProductOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(X_IN);\n  auto& Y = Input(Y_IN);\n  auto* result = Output(DOT_OUT);\n  CAFFE_ENFORCE_EQ(X.ndim(), Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE_EQ(X.dim32(i), Y.dim32(i));\n  }\n  int N, D;\n  if (X.size() > 0) {\n    N = X.ndim() > 0 ? X.dim32(0) : 1;\n    D = X.size() / N;\n  } else {\n    N = 0;\n    D = 0;\n  }\n  result->Resize(N);\n\n  DotProductKernel<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N, D, X.data<float>(), Y.data<float>(), result->mutable_data<float>());\n\n  return true;\n}\n\nnamespace {\ntemplate <typename T>\n__global__ void DotProductGradientKernel(\n    const int N,\n    const int D,\n    const T* X,\n    const T* Y,\n    const T* dDot,\n    T* dX,\n    T* dY) {\n  CUDA_1D_KERNEL_LOOP(i, N * D) {\n    T scale = dDot[i / D];\n    dX[i] = Y[i] * scale;\n    dY[i] = X[i] * scale;\n  }\n}\n} // namespace\n\ntemplate <>\nbool DotProductGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(X_IN);\n  auto& Y = Input(Y_IN);\n  auto& dDot = Input(DER_DOT_IN);\n  auto* dX = Output(DER_X_OUT);\n  auto* dY = Output(DER_Y_OUT);\n  int N, D;\n  if (X.size() > 0) {\n    N = X.ndim() > 0 ? X.dim32(0) : 1;\n    D = X.size() / N;\n  } else {\n    N = 0;\n    D = 0;\n  }\n  CAFFE_ENFORCE(X.ndim() == Y.ndim());\n  for (int i = 0; i < X.ndim(); ++i) {\n    CAFFE_ENFORCE(X.dim32(i) == Y.dim32(i));\n  }\n  CAFFE_ENFORCE(dDot.ndim() == 1);\n  CAFFE_ENFORCE(dDot.dim32(0) == N);\n  dX->ResizeLike(X);\n  dY->ResizeLike(Y);\n  DotProductGradientKernel<<<\n      CAFFE_GET_BLOCKS(N * D),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      D,\n      X.data<float>(),\n      Y.data<float>(),\n      dDot.data<float>(),\n      dX->mutable_data<float>(),\n      dY->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(SquaredL2Distance,\n                       SquaredL2DistanceOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(SquaredL2DistanceGradient,\n                       SquaredL2DistanceGradientOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(L1Distance, L1DistanceOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    L1DistanceGradient,\n    L1DistanceGradientOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(DotProduct, DotProductOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    DotProductGradient,\n    DotProductGradientOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(\n    CosineSimilarity,\n    CosineSimilarityOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    CosineSimilarityGradient,\n    CosineSimilarityGradientOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/distance_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_DISTANCE_OP_H_\n#define CAFFE2_OPERATORS_DISTANCE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SquaredL2DistanceOp : public Operator<Context> {\n public:\n  SquaredL2DistanceOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  // Input: X, Y; Output: Distance\n};\n\ntemplate <typename T, class Context>\nclass SquaredL2DistanceGradientOp final : public Operator<Context> {\n public:\n  SquaredL2DistanceGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    auto& X = Input(0);\n    auto& Y = Input(1);\n    auto& dDistance = Input(2);\n    auto* dX = Output(0);\n    auto* dY = Output(1);\n    int N = X.ndim() > 0 ? X.dim32(0) : 1;\n    int D = N > 0 ? X.size() / N : 0;\n    CAFFE_ENFORCE(X.ndim() == Y.ndim());\n    for (int i = 0; i < X.ndim(); ++i) {\n      CAFFE_ENFORCE(X.dim32(i) == Y.dim32(i));\n    }\n    CAFFE_ENFORCE(dDistance.ndim() == 1);\n    CAFFE_ENFORCE(dDistance.dim32(0) == N);\n    dX->ResizeLike(X);\n    dY->ResizeLike(Y);\n    math::Sub<T, Context>(\n        X.size(),\n        X.template data<T>(),\n        Y.template data<T>(),\n        dX->template mutable_data<T>(),\n        &context_);\n    for (int i = 0; i < N; ++i) {\n      math::Scale<T, Context>(\n          D,\n          dDistance.template data<T>() + i,\n          dX->template data<T>() + i * D,\n          dX->template mutable_data<T>() + i * D,\n          &context_);\n    }\n    // The gradient of the other side is basically the negative.\n    math::Scale<T, Context>(\n        X.size(),\n        -1,\n        dX->template data<T>(),\n        dY->template mutable_data<T>(),\n        &context_);\n    return true;\n  }\n\n protected:\n  // Input: X, Y, dDistance; Output: dX, dY\n};\n\ntemplate <typename T, class Context>\nclass L1DistanceOp : public Operator<Context> {\n public:\n  L1DistanceOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  // Input: X, Y; Output: Distance\n};\n\ntemplate <typename T, class Context>\nclass L1DistanceGradientOp : public Operator<Context> {\n public:\n  L1DistanceGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  // Input: X, Y, dDistance; Output: dX, dY\n};\n\ntemplate <typename T, class Context>\nclass DotProductOp : public Operator<Context> {\n public:\n  DotProductOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  INPUT_TAGS(X_IN, Y_IN);\n  OUTPUT_TAGS(DOT_OUT);\n};\n\ntemplate <typename T, class Context>\nclass DotProductGradientOp final : public Operator<Context> {\n public:\n  DotProductGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  INPUT_TAGS(X_IN, Y_IN, DER_DOT_IN);\n  OUTPUT_TAGS(DER_X_OUT, DER_Y_OUT);\n};\n\ntemplate <typename T, class Context>\nclass DotProductWithPaddingOp : public Operator<Context> {\n public:\n  DotProductWithPaddingOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        pad_value_(OperatorBase::GetSingleArgument<float>(\"pad_value\", 0.0)),\n        replicate_(OperatorBase::GetSingleArgument<bool>(\"replicate\", false)) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  float pad_value_;\n  bool replicate_;\n  INPUT_TAGS(X_IN, Y_IN);\n  OUTPUT_TAGS(DOT_OUT);\n};\n\ntemplate <typename T, class Context>\nclass CosineSimilarityOp : public Operator<Context> {\n public:\n  CosineSimilarityOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  INPUT_TAGS(X_IN, Y_IN);\n  OUTPUT_TAGS(COS_OUT);\n\n private:\n  Tensor<Context> aux_;\n};\n\ntemplate <typename T, class Context>\nclass CosineSimilarityGradientOp final : public Operator<Context> {\n public:\n  CosineSimilarityGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  INPUT_TAGS(X_IN, Y_IN, DER_COS_IN);\n  OUTPUT_TAGS(DER_X_OUT, DER_Y_OUT);\n\n private:\n  Tensor<Context> aux_;\n};\n\ntemplate <typename T, class Context>\nclass DotProductWithPaddingGradientOp final : public Operator<Context> {\n public:\n  DotProductWithPaddingGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        pad_value_(OperatorBase::GetSingleArgument<float>(\"pad_value\", 0.0)),\n        replicate_(OperatorBase::GetSingleArgument<bool>(\"replicate\", false)) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    auto& X = Input(X_IN);\n    auto& Y = Input(Y_IN);\n    auto& dDot = Input(DER_DOT_IN);\n    auto* dX = Output(DER_X_OUT);\n    auto* dY = Output(DER_Y_OUT);\n    int N, D, DX, DY, restD;\n    if (X.size() > 0) {\n      N = X.ndim() > 0 ? X.dim32(0) : 1;\n      DX = X.size() / N;\n      DY = Y.size() / N;\n    } else {\n      N = 0;\n      DX = 0;\n      DY = 0;\n    }\n    CAFFE_ENFORCE(!replicate_ || DX % DY == 0 || DY % DX == 0);\n    D = std::min(DX, DY);\n    restD = std::max(DX, DY) - D;\n    CAFFE_ENFORCE_EQ(X.ndim(), Y.ndim());\n    CAFFE_ENFORCE_EQ(X.dim32(0), Y.dim32(0));\n    CAFFE_ENFORCE_EQ(dDot.ndim(), 1);\n    CAFFE_ENFORCE_EQ(dDot.dim32(0), N);\n    dX->ResizeLike(X);\n    dY->ResizeLike(Y);\n\n    const auto* X_data = X.template data<T>();\n    const auto* Y_data = Y.template data<T>();\n    const auto* dDot_data = dDot.template data<T>();\n    auto* dX_data = dX->template mutable_data<T>();\n    auto* dY_data = dY->template mutable_data<T>();\n    for (int i = 0; i < N; ++i) { // TODO: multithreading\n      auto offsetX = i * DX;\n      auto offsetY = i * DY;\n      if (replicate_) {\n        // L_ for longer vector and S_ for shorter vector\n        const T *L_data, *S_data;\n        T *dL_data, *dS_data;\n        int DL, DS;\n        if (DX > DY) {\n          L_data = X_data + offsetX;\n          S_data = Y_data + offsetY;\n          dL_data = dX_data + offsetX;\n          dS_data = dY_data + offsetY;\n          DL = DX;\n          DS = DY;\n        } else {\n          L_data = Y_data + offsetY;\n          S_data = X_data + offsetX;\n          dL_data = dY_data + offsetY;\n          dS_data = dX_data + offsetX;\n          DL = DY;\n          DS = DX;\n        }\n\n        // TODO: get rid of temp memory use\n        std::vector<T> tmp_data(DS);\n        math::Set<T, Context>(DS, 0.0, dS_data, &context_);\n        for (int j = 0; j < DL / DS; j++) {\n          math::Scale<T, Context>(\n              DS, dDot_data[i], S_data, dL_data + j * DS, &context_);\n          math::Scale<T, Context>(\n              DS, dDot_data[i], L_data + j * DS, tmp_data.data(), &context_);\n          math::Axpy<T, Context>(DS, 1.0, tmp_data.data(), dS_data, &context_);\n        }\n      } else {\n        math::Scale<T, Context>(\n            D, dDot_data[i], X_data + offsetX, dY_data + offsetY, &context_);\n        math::Scale<T, Context>(\n            D, dDot_data[i], Y_data + offsetY, dX_data + offsetX, &context_);\n      }\n\n      if (!replicate_ && DX != DY) {\n        T* rest_data;\n        if (DX > DY) {\n          rest_data = dX_data + offsetX + D;\n        } else {\n          rest_data = dY_data + offsetY + D;\n        }\n        auto pad_gradient = dDot_data[i] * pad_value_;\n        math::Set<T, Context>(restD, pad_gradient, rest_data, &context_);\n      }\n    }\n\n    return true;\n  }\n\n protected:\n  float pad_value_;\n  bool replicate_;\n  INPUT_TAGS(X_IN, Y_IN, DER_DOT_IN);\n  OUTPUT_TAGS(DER_X_OUT, DER_Y_OUT);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_DISTANCE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/do_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/do_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Do, DoOp<CPUContext>);\n\nOPERATOR_SCHEMA(Do)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1, INT_MAX)\n    .SetDoc(R\"DOC(\n'Do' control operator, executes a subnet in a separate workspace.\nLast blobs in the input and output lists should be the same blob created with\nCreateScope op. Arguments 'inner_blobs' and 'outer_blobs_idx' provide a mapping\nbetween selected inner blob names and corresponding outer blob indices.\n    )DOC\")\n    .Arg(\"net\", \"Subnet with blob bindings\")\n    .Arg(\n        \"inner_blobs\",\n        \"List of inner net blob names to bind to outer workspace\")\n    .Arg(\n        \"outer_blobs_idx\",\n        \"Indices of corresponding outer workspace blobs, \"\n        \"in order: operator inputs, operator outputs (skipping workspace blobs)\")\n    .Arg(\n        \"saved_fwd_blobs\",\n        \"List of blobs from the forward Do operator workspace needed \"\n        \"in backward pass, used in gradient Do operator\")\n    .Arg(\n        \"reuse_workspace\",\n        \"Whether to reuse workspace or create a new one in a given scope\")\n    .AllowInplace([](int in, int out) -> bool { return true; });\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/do_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_DO_OP_H_\n#define CAFFE2_OPERATORS_DO_OP_H_\n\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <vector>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/create_scope_op.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass DoOp final : public Operator<Context> {\n public:\n  DoOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws), parent_ws_(ws) {\n    CAFFE_ENFORCE(\n        this->template HasSingleArgumentOfType<NetDef>(\"net\"),\n        \"net must be specified in Do operator\");\n    net_def_ = this->template GetSingleArgument<NetDef>(\"net\", NetDef());\n    is_gradient_op_ = operator_def.is_gradient_op();\n    copy_external_blobs_ =\n        this->template GetSingleArgument<bool>(\"copy_external_blobs\", false);\n    reuse_workspace_ =\n        this->template GetSingleArgument<bool>(\"reuse_workspace\", false);\n    CAFFE_ENFORCE(\n        !(is_gradient_op_ && reuse_workspace_),\n        \"Gradient Do op requires use of stacked workspaces\");\n    CAFFE_ENFORCE(\n        !(copy_external_blobs_ && reuse_workspace_),\n        \"Reuse workspace and copy external blobs simultaneously in Do op\");\n\n    const auto& inner_blobs =\n        this->template GetRepeatedArgument<std::string>(\"inner_blobs\");\n    const auto& outer_blobs_idx =\n        this->template GetRepeatedArgument<int>(\"outer_blobs_idx\");\n    CAFFE_ENFORCE_EQ(\n        inner_blobs.size(),\n        outer_blobs_idx.size(),\n        \"Invalid blob bindings: different inner/outer blobs lengths\");\n\n    const auto& outer_blob_names = checkAndGetOuterNames(operator_def);\n    std::unordered_set<std::string> used_outer_names;\n    for (size_t blob_idx = 0; blob_idx < inner_blobs.size(); ++blob_idx) {\n      CAFFE_ENFORCE(\n          !blob_bindings_.count(inner_blobs[blob_idx]),\n          \"Invalid blob bindings: redefinition of inner blob \" +\n              inner_blobs[blob_idx]);\n      CAFFE_ENFORCE(\n          outer_blobs_idx[blob_idx] >= 0 &&\n              outer_blobs_idx[blob_idx] < outer_blob_names.size(),\n          \"Invalid blob bindings: outer blob index (\" +\n              caffe2::to_string(outer_blobs_idx[blob_idx]) + \", inner name: \" +\n              inner_blobs[blob_idx] + \") is out of bounds [0, \" +\n              caffe2::to_string(outer_blob_names.size() - 1) + \"]\");\n      const auto& outer_name = outer_blob_names[outer_blobs_idx[blob_idx]];\n      CAFFE_ENFORCE(\n          !used_outer_names.count(outer_name),\n          \"Reusage of outer name: \" + outer_name);\n      used_outer_names.insert(outer_name);\n      blob_bindings_[inner_blobs[blob_idx]] = outer_name;\n      forwarded_inner_blobs_.insert(inner_blobs[blob_idx]);\n    }\n    std::unordered_set<std::string> all_outer_names(\n        outer_blob_names.begin(), outer_blob_names.end());\n    CAFFE_ENFORCE_EQ(\n        used_outer_names.size(),\n        all_outer_names.size(),\n        \"Not all outer names are used in blob bindings\");\n  }\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    auto* ws_stack =\n        OperatorBase::Output<detail::WorkspaceStack>(OutputSize() - 1);\n    std::shared_ptr<Workspace> net_workspace;\n    if (is_gradient_op_) {\n      net_workspace =\n          ws_stack->popGradientWorkspace(parent_ws_, blob_bindings_);\n    } else {\n      if (reuse_workspace_ && !ws_stack->empty()) {\n        net_workspace =\n            ws_stack->reuseLastForwardWorkspace(parent_ws_, blob_bindings_);\n      } else {\n        net_workspace =\n            ws_stack->pushForwardWorkspace(parent_ws_, blob_bindings_);\n      }\n    }\n    CAFFE_ENFORCE(net_workspace, \"Failed to initialize Do op workspace\");\n\n    // TODO(iliacher): figure how to reuse existing net with a new workspace\n    auto* net = net_workspace->GetNet(net_def_.name());\n    if (!net) {\n      net = net_workspace->CreateNet(net_def_, true);\n    }\n    CAFFE_ENFORCE(net, \"Failed to initialize subnet\");\n    auto success = net->Run();\n    if (!is_gradient_op_ && copy_external_blobs_) {\n      net_workspace->template CopyForwardedTensors<Context>(\n          forwarded_inner_blobs_);\n    }\n    return success;\n  }\n\n private:\n  // returns vector of input blob names followed by output blob names in\n  // operator definition order; ensures that input (output) names are unique,\n  // checks number of input (output) blobs\n  std::vector<std::string> checkAndGetOuterNames(\n      const OperatorDef& operator_def) const {\n    auto input_names = getInputBlobNames(operator_def);\n    CAFFE_ENFORCE(!input_names.empty(), \"Expected at least one input blob\");\n    std::string input_ws_blob = input_names.back(); // copy\n    // removing blob that holds pointer op workspace\n    input_names.pop_back();\n\n    std::unordered_set<std::string> all_input_names(\n        input_names.begin(), input_names.end());\n    CAFFE_ENFORCE_EQ(\n        input_names.size(), all_input_names.size(), \"Duplicate input blobs\");\n\n    auto output_names = getOutputBlobNames(operator_def);\n    CAFFE_ENFORCE(!output_names.empty(), \"Expected at least one output blob\");\n    const auto& output_ws_blob = output_names.back();\n    CAFFE_ENFORCE_EQ(\n        input_ws_blob,\n        output_ws_blob,\n        \"Expected same input/output workspace blob\");\n    // remove blob that holds pointer to op workspace\n    output_names.pop_back();\n\n    std::unordered_set<std::string> all_output_names(\n        output_names.begin(), output_names.end());\n    CAFFE_ENFORCE_EQ(\n        output_names.size(), all_output_names.size(), \"Duplicate output blobs\");\n\n    std::vector<std::string> outer_blob_names;\n    outer_blob_names.reserve(input_names.size() + output_names.size());\n    outer_blob_names.insert(\n        outer_blob_names.end(), input_names.begin(), input_names.end());\n    outer_blob_names.insert(\n        outer_blob_names.end(), output_names.begin(), output_names.end());\n    return outer_blob_names;\n  }\n\n  std::vector<std::string> getInputBlobNames(\n      const OperatorDef& operator_def) const {\n    std::vector<std::string> names;\n    names.reserve(operator_def.input_size());\n    for (auto idx = 0; idx < operator_def.input_size(); ++idx) {\n      names.push_back(operator_def.input(idx));\n    }\n    return names;\n  }\n\n  std::vector<std::string> getOutputBlobNames(\n      const OperatorDef& operator_def) const {\n    std::vector<std::string> names;\n    names.reserve(operator_def.output_size());\n    for (auto idx = 0; idx < operator_def.output_size(); ++idx) {\n      names.push_back(operator_def.output(idx));\n    }\n    return names;\n  }\n\n  std::unordered_map<std::string, std::string> blob_bindings_;\n  std::unordered_set<std::string> forwarded_inner_blobs_;\n  bool is_gradient_op_;\n  bool copy_external_blobs_;\n  bool reuse_workspace_;\n  NetDef net_def_;\n  Workspace* parent_ws_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_DO_OP_H_\n"
  },
  {
    "path": "caffe2/operators/do_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/do_op.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(Do, DoOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/dropout_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/dropout_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool DropoutOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  Y->Resize(X.dims());\n  if (is_test_) {\n    if (Y != &X) {\n      context_.Copy<float, CPUContext, CPUContext>(\n          X.size(), X.data<float>(), Y->mutable_data<float>());\n    }\n    return true;\n  } else {\n    float scale = 1. / (1. - ratio_);\n    // mask=true means keep, and mask=false means not keep, so we will\n    // generate probability depending on 1-ratio.\n    std::bernoulli_distribution dist(1. - ratio_);\n    const float* Xdata = X.data<float>();\n    float* Ydata = Y->mutable_data<float>();\n    auto mask = Output(1);\n    mask->Resize(X.dims());\n    bool* mask_data = mask->mutable_data<bool>();\n    auto& gen = context_.RandGenerator();\n    for (int i = 0; i < X.size(); ++i) {\n      mask_data[i] = dist(gen);\n      Ydata[i] = Xdata[i] * scale * mask_data[i];\n    }\n    return true;\n  }\n}\n\ntemplate <>\nbool DropoutGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& dY = Input(0);\n  auto* dX = Output(0);\n  dX->Resize(dY.dims());\n  if (is_test_) {\n    if (dX != &dY) {\n      context_.Copy<float, CPUContext, CPUContext>(\n          dY.size(), dY.data<float>(), dX->mutable_data<float>());\n    }\n    return true;\n  } else {\n    auto& mask = Input(1);\n    CAFFE_ENFORCE_EQ(dY.size(), mask.size());\n    const float* dYdata = dY.data<float>();\n    const bool* mask_data = mask.data<bool>();\n    float* dXdata = dX->mutable_data<float>();\n    float scale = 1. / (1. - ratio_);\n    for (int i = 0; i < dY.size(); ++i) {\n      dXdata[i] = dYdata[i] * mask_data[i] * scale;\n    }\n    return true;\n  }\n}\n\nREGISTER_CPU_OPERATOR(Dropout, DropoutOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(DropoutGrad, DropoutGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Dropout)\n    .NumInputs(1)\n    .NumOutputs(1, 2)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      CAFFE_ENFORCE_EQ(1, in.size());\n      vector<TensorShape> out;\n      ArgumentHelper argsHelper(def);\n      out.push_back(in[0]);\n      auto output_mask = !argsHelper.GetSingleArgument<bool>(\"is_test\", 0);\n      if (output_mask) {\n        out.push_back(in[0]);\n        out[1].set_data_type(TensorProto_DataType_BOOL);\n      }\n      return out;\n    })\n    .SetDoc(R\"DOC(\nDropout takes one input data (Tensor<float>) and produces two Tensor outputs,\noutput (Tensor<float>) and mask (Tensor<bool>). Depending on whether it is in\ntest mode or not, the output Y will either be a random dropout, or a simple\ncopy of the input. Note that our implementation of Dropout does scaling in\nthe training phase, so during testing nothing needs to be done.\n)DOC\")\n    .Arg(\"ratio\", \"(float, default 0.5) the ratio of random dropout\")\n    .ArgIsTest(\n        \"(int) if nonzero, run dropout in test mode where \"\n        \"the output is simply Y = X.\")\n    .Input(0, \"data\", \"The input data as Tensor.\")\n    .Output(0, \"output\", \"The output.\")\n    .Output(\n        1,\n        \"mask\",\n        \"The output mask. If is_test is nonzero, this output is not filled.\");\n\nOPERATOR_SCHEMA(DropoutGrad)\n    .NumInputs(1, 2)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}});\n\nclass GetDropoutGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    ArgumentHelper argshelper(def_);\n    auto is_test = argshelper.GetSingleArgument<bool>(\"is_test\", 0);\n    if (is_test) {\n      return SingleGradientDef(\n          \"DropoutGrad\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n    } else {\n      return SingleGradientDef(\n          \"DropoutGrad\",\n          \"\",\n          vector<string>{GO(0), O(1)},\n          vector<string>{GI(0)});\n    }\n  }\n};\nREGISTER_GRADIENT(Dropout, GetDropoutGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/dropout_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/dropout_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n__global__ void DropoutKernel(\n    const int N,\n    const float ratio,\n    const float* Xdata,\n    float* Ydata,\n    bool* maskdata) {\n  const float scale = 1. / (1. - ratio);\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    maskdata[i] = (Ydata[i] > ratio);\n    Ydata[i] = Xdata[i] * scale * maskdata[i];\n  }\n}\n} // namespace\n\ntemplate <>\nbool DropoutOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  Y->Resize(X.dims());\n  if (is_test_) {\n    if (Y != &X) {\n      context_.Copy<float, CUDAContext, CUDAContext>(\n          X.size(), X.data<float>(), Y->mutable_data<float>());\n    }\n    return true;\n  } else {\n    // We do a simple trick here: since curand cannot generate random\n    // boolean numbers, we will generate into dY and write the result to\n    // mask.\n    float* Ydata = Y->mutable_data<float>();\n    auto* mask = Output(1);\n    mask->Resize(X.dims());\n    CAFFE_ENFORCE(X.data<float>() != Ydata, \"In-place GPU dropout is broken\");\n    CURAND_ENFORCE(\n        curandGenerateUniform(context_.curand_generator(), Ydata, X.size()));\n    DropoutKernel<<<\n        CAFFE_GET_BLOCKS(X.size()),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        X.size(), ratio_, X.data<float>(), Ydata, mask->mutable_data<bool>());\n    return true;\n  }\n}\n\nnamespace {\n__global__ void DropoutGradientKernel(\n    const int N,\n    const float* dYdata,\n    const bool* maskdata,\n    const float scale,\n    float* dXdata) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dXdata[i] = dYdata[i] * maskdata[i] * scale;\n  }\n}\n} // namespace\n\ntemplate <>\nbool DropoutGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& dY = Input(0);\n  auto* dX = Output(0);\n  dX->Resize(dY.dims());\n  if (is_test_) {\n    if (dX != &dY) {\n      context_.Copy<float, CUDAContext, CUDAContext>(\n          dY.size(), dY.data<float>(), dX->mutable_data<float>());\n    }\n    return true;\n  } else {\n    auto& mask = Input(1);\n    CAFFE_ENFORCE_EQ(dY.size(), mask.size());\n    const float scale = 1. / (1. - ratio_);\n    DropoutGradientKernel<<<\n        CAFFE_GET_BLOCKS(dY.size()),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        dY.size(),\n        dY.data<float>(),\n        mask.data<bool>(),\n        scale,\n        dX->mutable_data<float>());\n    return true;\n  }\n}\n\nREGISTER_CUDA_OPERATOR(Dropout, DropoutOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(DropoutGrad, DropoutGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/dropout_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_DROPOUT_OP_H_\n#define CAFFE2_OPERATORS_DROPOUT_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass DropoutOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  DropoutOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        ratio_(OperatorBase::GetSingleArgument<float>(\"ratio\", 0.5)),\n        is_test_(\n            OperatorBase::GetSingleArgument<int>(OpSchema::Arg_IsTest, 0)) {\n    CAFFE_ENFORCE_GE(ratio_, 0);\n    CAFFE_ENFORCE_LT(ratio_, 1);\n  }\n\n  bool RunOnDevice() override;\n\n protected:\n  float ratio_;\n  bool is_test_;\n  // Input: X; Output: Y, mask.\n};\n\ntemplate <typename T, class Context>\nclass DropoutGradientOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  DropoutGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        ratio_(OperatorBase::GetSingleArgument<float>(\"ratio\", 0.5)),\n        is_test_(\n            OperatorBase::GetSingleArgument<int>(OpSchema::Arg_IsTest, 0)) {\n    CAFFE_ENFORCE_GE(ratio_, 0);\n    CAFFE_ENFORCE_LT(ratio_, 1);\n  }\n\n  bool RunOnDevice() override;\n\n protected:\n  float ratio_;\n  bool is_test_;\n  // Input: dY, mask; Output: dX\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_DROPOUT_OP_H_\n"
  },
  {
    "path": "caffe2/operators/dropout_op_cudnn.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/cudnn_wrappers.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/types.h\"\n\nnamespace caffe2 {\n\n// cudnnRestoreDropoutDescriptor is needed for correctness and\n// doesn't exist prior to cuDNN v7\n#if CUDNN_VERSION_MIN(7,0,0)\n\nclass CuDNNDropoutOp final : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n\n  CuDNNDropoutOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws),\n        cudnn_wrapper_(&context_),\n        ratio_(OperatorBase::GetSingleArgument<float>(\"ratio\", 0.5)),\n        is_test_(\n            OperatorBase::GetSingleArgument<int>(OpSchema::Arg_IsTest, 0)),\n        states_initialized_(false),\n        random_seed_(operator_def.device_option().random_seed()) {\n    CAFFE_ENFORCE_GE(ratio_, 0);\n    CAFFE_ENFORCE_LT(ratio_, 1);\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&data_desc_));\n\n    CUDNN_ENFORCE(cudnnCreateDropoutDescriptor(&dropout_desc_));\n    CUDNN_ENFORCE(cudnnDropoutGetStatesSize(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        reinterpret_cast<size_t*>(&states_size_in_bytes_)));\n\n    if (!is_test_) {\n      scratch_blob_ = ws->CreateBlob(scratch_blob_name(operator_def.output(1)));\n      CAFFE_ENFORCE(scratch_blob_);\n    }\n  }\n\n  ~CuDNNDropoutOp() noexcept {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(data_desc_));\n    CUDNN_ENFORCE(cudnnDestroyDropoutDescriptor(dropout_desc_));\n  }\n\n  template <typename T, typename M>\n  bool DoRunWithType();\n\n  bool RunOnDevice() override;\n\n  static string scratch_blob_name(string mask_blob_name) {\n    return \"cudnn_dropout_scratch_\" + mask_blob_name;\n  }\n\n protected:\n  CuDNNWrapper cudnn_wrapper_;\n  cudnnTensorDescriptor_t data_desc_;\n  cudnnDropoutDescriptor_t dropout_desc_;\n\n  vector<TIndex> cudnn_input_dims_;\n\n  float ratio_;\n  bool is_test_;\n\n  Blob* scratch_blob_ = nullptr;\n\n  size_t states_size_in_bytes_, reserve_space_size_in_bytes_;\n  // Input: X, Output: Y, mask_and_states\n\n  // track whether states have been initialized - only needs to happen once\n  bool states_initialized_;\n\n  // random seed\n  unsigned long long random_seed_;\n};\n\nclass CuDNNDropoutGradientOp final : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  CuDNNDropoutGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws),\n        cudnn_wrapper_(&context_),\n        ratio_(OperatorBase::GetSingleArgument<float>(\"ratio\", 0.5)),\n        is_test_(\n            OperatorBase::GetSingleArgument<int>(OpSchema::Arg_IsTest, 0)),\n        states_initialized_(false),\n        random_seed_(operator_def.device_option().random_seed()) {\n    CAFFE_ENFORCE_GE(ratio_, 0);\n    CAFFE_ENFORCE_LT(ratio_, 1);\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&data_desc_));\n\n    CUDNN_ENFORCE(cudnnCreateDropoutDescriptor(&dropout_desc_));\n    CUDNN_ENFORCE(cudnnDropoutGetStatesSize(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        reinterpret_cast<size_t*>(&states_size_in_bytes_)));\n\n    // Share scratch with the forward op\n    scratch_blob_ =\n        ws->GetBlob(CuDNNDropoutOp::scratch_blob_name(operator_def.input(1)));\n    CAFFE_ENFORCE(scratch_blob_);\n  }\n\n  ~CuDNNDropoutGradientOp() noexcept {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(data_desc_));\n    CUDNN_ENFORCE(cudnnDestroyDropoutDescriptor(dropout_desc_));\n  }\n\n  template <typename T, typename M>\n  bool DoRunWithType();\n\n  bool RunOnDevice() override;\n\n protected:\n  CuDNNWrapper cudnn_wrapper_;\n  cudnnTensorDescriptor_t data_desc_;\n  cudnnDropoutDescriptor_t dropout_desc_;\n\n  vector<TIndex> cudnn_input_dims_;\n\n  Blob* scratch_blob_;\n\n  float ratio_;\n  bool is_test_;\n\n  size_t states_size_in_bytes_, reserve_space_size_in_bytes_;\n  // Input: dY, mask_and_states, Output: dX\n\n  // only need to initialize states once (size is static)\n  bool states_initialized_;\n\n  unsigned long long random_seed_;\n};\n\ntemplate <typename T, typename M>\nbool CuDNNDropoutOp::DoRunWithType() {\n  const auto& X = Input(0);\n  auto* Y = Output(0);\n\n  auto size_prod = 1;\n  for (auto dim : X.dims()) {\n    size_prod *= dim;\n  }\n  // now actually run the computation\n  if (is_test_) {\n    if (Y != &X) {\n      context_.Copy<T, CUDAContext, CUDAContext>(\n          X.size(), X.template data<T>(), Y->template mutable_data<T>());\n    }\n    return true;\n  } else {\n    auto* mask = Output(1);\n    // Reshape tensor descriptors if necessary\n    if (X.dims() != cudnn_input_dims_ && !is_test_) {\n      CAFFE_ENFORCE(scratch_blob_);\n      Tensor<CUDAContext>* states =\n          scratch_blob_->GetMutable<Tensor<CUDAContext>>();\n      cudnn_input_dims_ = X.dims();\n      CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n          data_desc_,\n          GetCudnnTensorFormat(StorageOrder::NCHW),\n          cudnnTypeWrapper<T>::type,\n          size_prod,\n          1,\n          1,\n          1));\n\n      // get the reserve space we need\n      CUDNN_ENFORCE(cudnnDropoutGetReserveSpaceSize(\n          data_desc_, &reserve_space_size_in_bytes_));\n\n      mask->Resize(reserve_space_size_in_bytes_);\n      states->Resize(states_size_in_bytes_);\n\n      if (!states_initialized_) {\n        // set the dropout descriptor (note: need to allocate the states data\n        // before acquiring the mutex)\n        uint8_t* states_data = states->mutable_data<uint8_t>();\n        {\n          // Need to protect  as clashes with NCCL\n          std::lock_guard<std::mutex> lk(CUDAContext::mutex());\n          CUDNN_ENFORCE(cudnnSetDropoutDescriptor(\n              dropout_desc_,\n              cudnn_wrapper_.inline_cudnn_handle(),\n              ratio_,\n              states_data,\n              states_size_in_bytes_,\n              random_seed_\n              ));\n        }\n        states_initialized_ = true;\n      }\n    }\n    CUDNN_ENFORCE(cudnnDropoutForward(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        dropout_desc_,\n        data_desc_,\n        X.template data<T>(),\n        data_desc_,\n        Y->template mutable_data<T>(),\n        mask->mutable_data<uint8_t>(),\n        reserve_space_size_in_bytes_));\n  }\n  return true;\n}\n\nbool CuDNNDropoutOp::RunOnDevice() {\n  // dispatch based on contents of tensor(s)\n  const auto& X = Input(0);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n\n  if (X.IsType<float>()) {\n    return DoRunWithType<float, float>();\n  } else if (X.IsType<float16>()) {\n    return DoRunWithType<float16, float>();\n  }\n  return false;\n}\n\ntemplate <typename T, typename M>\nbool CuDNNDropoutGradientOp::DoRunWithType() {\n  const auto& dY = Input(0);\n  const auto& mask = Input(1);\n  const Tensor<CUDAContext>& states = scratch_blob_->Get<Tensor<CUDAContext>>();\n  auto* dX = Output(0);\n\n  auto size_prod = 1;\n  for (auto dim : dY.dims()) {\n    size_prod *= dim;\n  }\n\n  if (!states_initialized_) {\n    // set the dropout descriptor\n    {\n      // Need to protect  as clashes with NCCL\n      std::lock_guard<std::mutex> lk(CUDAContext::mutex());\n      CUDNN_ENFORCE(cudnnRestoreDropoutDescriptor(\n          dropout_desc_,\n          cudnn_wrapper_.inline_cudnn_handle(),\n          ratio_,\n          const_cast<uint8_t*>(states.data<uint8_t>()),\n          states_size_in_bytes_,\n          random_seed_\n          ));\n    }\n    states_initialized_ = true;\n  }\n\n  if (dY.dims() != cudnn_input_dims_) {\n    cudnn_input_dims_ = dY.dims();\n    CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n        data_desc_,\n        GetCudnnTensorFormat(StorageOrder::NCHW),\n        cudnnTypeWrapper<T>::type,\n        size_prod,\n        1,\n        1,\n        1));\n\n    // get the reserve space we need\n    CUDNN_ENFORCE(cudnnDropoutGetReserveSpaceSize(\n        data_desc_, &reserve_space_size_in_bytes_));\n\n  }\n\n  // run the computation\n  void* mask_data = const_cast<void*>(mask.raw_data());\n  CUDNN_ENFORCE(cudnnDropoutBackward(\n      cudnn_wrapper_.inline_cudnn_handle(),\n      dropout_desc_,\n      data_desc_,\n      dY.data<T>(),\n      data_desc_,\n      dX->template mutable_data<T>(),\n      mask_data,\n      reserve_space_size_in_bytes_));\n  return true;\n}\n\nbool CuDNNDropoutGradientOp::RunOnDevice() {\n  // dispatch based on contents of tensor(s)\n  const auto& dY = Input(0);\n  auto* dX = Output(0);\n\n  dX->ResizeLike(dY);\n\n  if (dY.IsType<float>()) {\n    return DoRunWithType<float, float>();\n  } else if (dY.IsType<float16>()) {\n    return DoRunWithType<float16, float>();\n  }\n  return false;\n}\n\nnamespace {\nREGISTER_CUDNN_OPERATOR(Dropout, CuDNNDropoutOp);\nREGISTER_CUDNN_OPERATOR(DropoutGrad, CuDNNDropoutGradientOp);\n}\n\n#endif\n\n}; // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/elementwise_add_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\n// See the operations supported here:\n// https://eigen.tuxfamily.org/dox-devel/group__QuickRefPage.html\n#define EIGEN_ADD(x, y) ((x) + (y))\nEIGEN_FUNCTOR(Add, EIGEN_ADD, NumericTypes, SameTypeAsInput);\n#undef EIGEN_ADD\n}\n"
  },
  {
    "path": "caffe2/operators/elementwise_div_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\n// See the operations supported here:\n// https://eigen.tuxfamily.org/dox-devel/group__QuickRefPage.html\n#define EIGEN_DIV(x, y) ((x) / (y))\nEIGEN_FUNCTOR(Div, EIGEN_DIV, NumericTypes, SameTypeAsInput);\n#undef EIGEN_DIV\n\nvoid ElementWiseDivide(\n    CPUContext& /* unused context */,\n    const int n,\n    float* dXdata,\n    float* dYdata,\n    const float* dZdata,\n    const float* Ydata,\n    const float* Zdata) {\n  ConstEigenVectorArrayMap<float> dZdataVec(dZdata, n);\n  ConstEigenVectorArrayMap<float> YdataVec(Ydata, n);\n  ConstEigenVectorArrayMap<float> ZdataVec(Zdata, n);\n  EigenVectorArrayMap<float>(dXdata, n) = dZdataVec / YdataVec;\n  EigenVectorArrayMap<float>(dYdata, n) = - (dZdataVec * ZdataVec) / YdataVec;\n}\n\nREGISTER_CPU_OPERATOR(DivGradient, DivGradientOp<CPUContext>);\n}\n"
  },
  {
    "path": "caffe2/operators/elementwise_linear_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"elementwise_linear_op.h\"\n\nnamespace caffe2 {\n\ntemplate<>\nbool ElementwiseLinearOp<float, CPUContext>::RunOnDevice(){\n  const auto& X = Input(0);\n  const auto& a = Input(1);\n  const auto& b = Input(2);\n  auto* Y = Output(0);\n\n  const auto canonical_axis = X.canonical_axis_index(axis_);\n  const int N = X.size_to_dim(canonical_axis);\n  const int D = X.size_from_dim(canonical_axis);\n\n  CAFFE_ENFORCE_EQ(a.ndim(), 1, a.ndim());\n  CAFFE_ENFORCE_EQ(a.dim(0), D, a.ndim());\n  CAFFE_ENFORCE_EQ(b.ndim(), 1, b.ndim());\n  CAFFE_ENFORCE_EQ(b.dim(0), D, b.ndim());\n\n  Y->ResizeLike(X);\n\n  const float* X_data = X.data<float>();\n  const float* a_data = a.data<float>();\n  const float* b_data = b.data<float>();\n  float* Y_data = Y->mutable_data<float>();\n\n  int p = 0;\n  for (int n = 0; n < N; ++n) {\n    for (int d = 0; d < D; ++d) {\n      Y_data[p] = X_data[p] * a_data[d] + b_data[d];\n      p++;\n    }\n  }\n  return true;\n}\n\ntemplate<>\nbool ElementwiseLinearGradientOp<float, CPUContext>::RunOnDevice(){\n  const auto& g_o = Input(0);\n  const auto& X = Input(1);\n  const auto& a = Input(2);\n\n  const auto canonical_axis = X.canonical_axis_index(axis_);\n  const int N = X.size_to_dim(canonical_axis);\n  const int D = X.size_from_dim(canonical_axis);\n\n  CAFFE_ENFORCE_EQ(a.ndim(), 1, a.ndim());\n  CAFFE_ENFORCE_EQ(a.dim(0), D, a.ndim());\n\n  auto *g_X = Output(0);\n  auto *g_a = Output(1);\n  auto *g_b = Output(2);\n  g_X->ResizeLike(X);\n  g_a->ResizeLike(a);\n  g_b->ResizeLike(a);\n\n  const float* g_o_data = g_o.data<float>();\n  const float* X_data = X.data<float>();\n  const float* a_data = a.data<float>();\n  float* g_X_data = g_X->mutable_data<float>();\n  float* g_a_data = g_a->mutable_data<float>();\n  float* g_b_data = g_b->mutable_data<float>();\n\n  math::Set<float, CPUContext>(g_a->size(), 0.f, g_a_data, &context_);\n  math::Set<float, CPUContext>(g_b->size(), 0.f, g_b_data, &context_);\n\n  int p = 0;\n  for (int n = 0; n < N; ++n) {\n    for (int d = 0; d < D; ++d) {\n      g_X_data[p] = g_o_data[p] * a_data[d];\n      g_a_data[d] += g_o_data[p] * X_data[p];\n      g_b_data[d] += g_o_data[p];\n      p++;\n    }\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(\n  ElementwiseLinear,\n  ElementwiseLinearOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n  ElementwiseLinearGradient,\n  ElementwiseLinearGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(ElementwiseLinear)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven inputs X of size (N x D), w of size D and b of size D,\nthe op computes Y of size (N X D) where Y_{nd} = X_{nd} * w_d + b_d\n  )DOC\")\n    .Input(0, \"X\", \"2D input tensor of size (N X D) data\")\n    .Input(1, \"w\", \"1D scaling factors of size D\")\n    .Input(2, \"b\", \"1D biases of size D\")\n    .Output(0, \"Y\", \"2D output tensor\")\n    .Arg(\n        \"axis\",\n        \"default to 1; describes the axis of the inputs; \"\n        \"defaults to one because the 0th axis most likely describes \"\n        \"the batch_size\");\n\nOPERATOR_SCHEMA(ElementwiseLinearGradient)\n  .NumInputs(3)\n  .NumOutputs(3);\n\nstruct GetElementwiseLinearGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n      \"ElementwiseLinearGradient\",\n      \"\",\n      vector<string>{GO(0), I(0), I(1)},\n      vector<string>{GI(0), GI(1), GI(2)});\n    }\n};\n\nREGISTER_GRADIENT(\n  ElementwiseLinear,\n  GetElementwiseLinearGradient\n);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/elementwise_linear_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <assert.h>\n\n#include \"elementwise_linear_op.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/operator_fallback_gpu.h\"\n\n#include <cub/block/block_reduce.cuh>\n\nnamespace caffe2 {\n\nnamespace {\n__global__ void ElementwiseLinearKernel(const int N, const int D,\n  const float* X_data, const float* a_data, const float* b_data,\n  float* Y_data) {\n    CUDA_1D_KERNEL_LOOP(i, N * D) {\n      int d = i % D;\n      Y_data[i] = X_data[i] * a_data[d] + b_data[d];\n    }\n}\n\n__global__ void ElementwiseLinearGradientKernel(const int N, const int D,\n  const float* g_o_data, const float* X_data, const float* a_data,\n  float* g_X_data, float* g_a_data, float* g_b_data) {\n  int d = blockIdx.x; // One block per D\n\n  float g_a_sum = 0;\n  float g_b_sum = 0;\n  for (int n = threadIdx.x; n < N; n += blockDim.x) {\n    const float gox = g_o_data[n * D + d];\n    g_X_data[n * D + d] = gox * a_data[d];\n    g_a_sum += gox * X_data[n * D + d];\n    g_b_sum += gox;\n  }\n\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n\n  float g_a_sum_tot = BlockReduce(temp_storage).Sum(g_a_sum);\n  __syncthreads();\n  float g_b_sum_tot = BlockReduce(temp_storage).Sum(g_b_sum);\n\n  if (threadIdx.x == 0) {\n    g_a_data[d] = g_a_sum_tot;\n    g_b_data[d] = g_b_sum_tot;\n  }\n}\n\n}  // namespace\n\n\ntemplate<>\nbool ElementwiseLinearOp<float, CUDAContext>::RunOnDevice(){\n  const auto& X = Input(0);\n  const auto& a = Input(1);\n  const auto& b = Input(2);\n  auto* Y = Output(0);\n\n  const auto canonical_axis = X.canonical_axis_index(axis_);\n  const int N = X.size_to_dim(canonical_axis);\n  const int D = X.size_from_dim(canonical_axis);\n\n  CAFFE_ENFORCE_EQ(a.ndim(), 1, a.ndim());\n  CAFFE_ENFORCE_EQ(a.dim(0), D, a.ndim());\n  CAFFE_ENFORCE_EQ(b.ndim(), 1, b.ndim());\n  CAFFE_ENFORCE_EQ(b.dim(0), D, b.ndim());\n\n  Y->ResizeLike(X);\n\n  ElementwiseLinearKernel<<<CAFFE_GET_BLOCKS(N * D), CAFFE_CUDA_NUM_THREADS,\n                          0, context_.cuda_stream()>>>(\n    N, D, X.data<float>(), a.data<float>(), b.data<float>(),\n    Y->mutable_data<float>());\n  return true;\n}\n\n\ntemplate<>\nbool ElementwiseLinearGradientOp<float, CUDAContext>::RunOnDevice(){\n  const auto& g_o = Input(0);\n  const auto& X = Input(1);\n  const auto& a = Input(2);\n\n  const auto canonical_axis = X.canonical_axis_index(axis_);\n  const int N = X.size_to_dim(canonical_axis);\n  const int D = X.size_from_dim(canonical_axis);\n\n  CAFFE_ENFORCE_EQ(a.ndim(), 1, a.ndim());\n  CAFFE_ENFORCE_EQ(a.dim(0), D, a.ndim());\n\n  auto *g_X = Output(0);\n  auto *g_a = Output(1);\n  auto *g_b = Output(2);\n  g_X->ResizeLike(X);\n  g_a->ResizeLike(a);\n  g_b->ResizeLike(a);\n\n  float* g_a_data = g_a->mutable_data<float>();\n  float* g_b_data = g_b->mutable_data<float>();\n\n  ElementwiseLinearGradientKernel<<<\n      D,\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      D,\n      g_o.data<float>(),\n      X.data<float>(),\n      a.data<float>(),\n      g_X->mutable_data<float>(),\n      g_a_data,\n      g_b_data);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(ElementwiseLinear,\n                       ElementwiseLinearOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(ElementwiseLinearGradient,\n                       ElementwiseLinearGradientOp<float, CUDAContext>);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/elementwise_linear_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_ELEMENTWISE_LINEAR_OP_H_\n#define CAFFE2_OPERATORS_ELEMENTWISE_LINEAR_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nclass ElementwiseLinearOp final : public Operator<Context> {\n public:\n  ElementwiseLinearOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        axis_(OperatorBase::GetSingleArgument<int>(\"axis\", 1)) {}\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n protected:\n  int axis_;\n};\n\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nclass ElementwiseLinearGradientOp final : public Operator<Context> {\n public:\n  ElementwiseLinearGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        axis_(OperatorBase::GetSingleArgument<int>(\"axis\", 1)) {}\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n protected:\n  int axis_;\n};\n\n} // namespace caffe2\n\n#endif  // CAFFE2_OPERATORS_ELEMENTWISE_LINEAR_OP_H_\n"
  },
  {
    "path": "caffe2/operators/elementwise_logical_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_logical_ops.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(Where, WhereOp<CPUContext>);\n\n// Input: C, X, Y, output: Z\nOPERATOR_SCHEMA(Where)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .AllowInplace({{1, 2}})\n    .IdenticalTypeAndShapeOfInput(1)\n    .SetDoc(R\"DOC(\nOperator Where takes three input data (Tensor<bool>, Tensor<T>, Tensor<T>) and\nproduces one output data (Tensor<T>) where z = c ? x : y is applied elementwise.\n)DOC\")\n    .Input(0, \"C\", \"input tensor containing booleans\")\n    .Input(1, \"X\", \"input tensor\")\n    .Input(2, \"Y\", \"input tensor\")\n    .Output(0, \"Z\", \"output tensor\");\n\nSHOULD_NOT_DO_GRADIENT(Where);\n\nREGISTER_CPU_OPERATOR(IsMemberOf, IsMemberOfOp<CPUContext>);\n\n// Input: X, output: Y\nOPERATOR_SCHEMA(IsMemberOf)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(\n        [](const OperatorDef&, const vector<TensorShape>& input_types) {\n          vector<TensorShape> out(1);\n          out[0] = input_types[0];\n          out[0].set_data_type(TensorProto_DataType::TensorProto_DataType_BOOL);\n          return out;\n        })\n    .Arg(\"value\", \"Declare one value for the membership test.\")\n    .Arg(\n        \"dtype\",\n        \"The data type for the elements of the output tensor.\"\n        \"Strictly must be one of the types from DataType enum in TensorProto.\")\n    .SetDoc(R\"DOC(\nIsMemberOf takes input data (Tensor<T>) and a list of values as argument, and\nproduces one output data (Tensor<bool>) where the function `f(x) = x in values`,\nis applied to the data tensor elementwise.\n)DOC\")\n    .Input(0, \"X\", \"Input tensor of any shape\")\n    .Output(0, \"Y\", \"Output tensor (same size as X containing booleans)\");\n\nSHOULD_NOT_DO_GRADIENT(IsMemberOf);\n\n} // namespace\n\ntemplate <>\nstd::unordered_set<int32_t>& IsMemberOfValueHolder::get<int32_t>() {\n  return int32_values_;\n}\n\ntemplate <>\nstd::unordered_set<int64_t>& IsMemberOfValueHolder::get<int64_t>() {\n  return int64_values_;\n}\n\ntemplate <>\nstd::unordered_set<bool>& IsMemberOfValueHolder::get<bool>() {\n  return bool_values_;\n}\n\ntemplate <>\nstd::unordered_set<string>& IsMemberOfValueHolder::get<string>() {\n  return string_values_;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/elementwise_logical_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_ELEMENTWISE_LOGICAL_OPS_H_\n#define CAFFE2_OPERATORS_ELEMENTWISE_LOGICAL_OPS_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n\n#include <unordered_set>\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass WhereOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_FUNCTIONS(Context);\n  USE_DISPATCH_HELPER;\n\n  WhereOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        OP_SINGLE_ARG(bool, \"broadcast_on_rows\", enable_broadcast_, 0) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<\n        TensorTypes<float, double, int, long, std::string, bool>>::\n        call(this, Input(1));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& select = Input(0);\n    auto& left = Input(1);\n    auto& right = Input(2);\n    auto* output = Output(0);\n    if (enable_broadcast_) {\n      CAFFE_ENFORCE_EQ(select.ndim(), 1);\n      CAFFE_ENFORCE_EQ(select.dim(0), right.dim(0));\n      CAFFE_ENFORCE_EQ(left.dims(), right.dims());\n    } else {\n      CAFFE_ENFORCE_EQ(select.dims(), left.dims());\n      CAFFE_ENFORCE_EQ(select.dims(), right.dims());\n    }\n    output->ResizeLike(left);\n\n    const bool* select_data = select.template data<bool>();\n    const T* left_data = left.template data<T>();\n    const T* right_data = right.template data<T>();\n    T* output_data = output->template mutable_data<T>();\n\n    if (enable_broadcast_) {\n      size_t block_size = left.size_from_dim(1);\n      for (int i = 0; i < select.size(); i++) {\n        size_t offset = i * block_size;\n        if (select_data[i]) {\n          context_.template CopyItems<Context, Context>(\n              output->meta(),\n              block_size,\n              left_data + offset,\n              output_data + offset);\n        } else {\n          context_.template CopyItems<Context, Context>(\n              output->meta(),\n              block_size,\n              right_data + offset,\n              output_data + offset);\n        }\n      }\n    } else {\n      for (int i = 0; i < select.size(); ++i) {\n        output_data[i] = select_data[i] ? left_data[i] : right_data[i];\n      }\n    }\n    return true;\n  }\n\n private:\n  bool enable_broadcast_;\n};\n\nclass IsMemberOfValueHolder {\n  std::unordered_set<int32_t> int32_values_;\n  std::unordered_set<int64_t> int64_values_;\n  std::unordered_set<bool> bool_values_;\n  std::unordered_set<std::string> string_values_;\n  bool has_values_ = false;\n\n public:\n  template <typename T>\n  std::unordered_set<T>& get();\n\n  template <typename T>\n  void set(const std::vector<T>& args) {\n    has_values_ = true;\n    auto& values = get<T>();\n    values.insert(args.begin(), args.end());\n  }\n\n  bool has_values() {\n    return has_values_;\n  }\n};\n\ntemplate <class Context>\nclass IsMemberOfOp final : public Operator<Context> {\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_DISPATCH_HELPER;\n\n  static constexpr const char* VALUE_TAG = \"value\";\n\n public:\n  using TestableTypes = TensorTypes<int32_t, int64_t, bool, std::string>;\n\n  IsMemberOfOp(const OperatorDef& op, Workspace* ws)\n      : Operator<Context>(op, ws) {\n    auto dtype =\n        static_cast<TensorProto_DataType>(OperatorBase::GetSingleArgument<int>(\n            \"dtype\", TensorProto_DataType_UNDEFINED));\n    switch (dtype) {\n      case TensorProto_DataType_INT32:\n        values_.set(OperatorBase::GetRepeatedArgument<int32_t>(VALUE_TAG));\n        break;\n      case TensorProto_DataType_INT64:\n        values_.set(OperatorBase::GetRepeatedArgument<int64_t>(VALUE_TAG));\n        break;\n      case TensorProto_DataType_BOOL:\n        values_.set(OperatorBase::GetRepeatedArgument<bool>(VALUE_TAG));\n        break;\n      case TensorProto_DataType_STRING:\n        values_.set(OperatorBase::GetRepeatedArgument<std::string>(VALUE_TAG));\n        break;\n      case TensorProto_DataType_UNDEFINED:\n        // If dtype is not provided, values_ will be filled the first time that\n        // DoRunWithType is called.\n        break;\n      default:\n        CAFFE_THROW(\"Unexpected 'dtype' argument value: \", dtype);\n    }\n  }\n  virtual ~IsMemberOfOp() noexcept {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<\n        TensorTypes<int32_t, int64_t, bool, std::string>>::call(this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& input = Input(0);\n    auto* output = Output(0);\n    output->ResizeLike(input);\n\n    if (!values_.has_values()) {\n      values_.set(OperatorBase::GetRepeatedArgument<T>(VALUE_TAG));\n    }\n    const auto& values = values_.get<T>();\n\n    const T* input_data = input.template data<T>();\n    bool* output_data = output->template mutable_data<bool>();\n    for (int i = 0; i < input.size(); ++i) {\n      output_data[i] = values.find(input_data[i]) != values.end();\n    }\n    return true;\n  }\n\n protected:\n  IsMemberOfValueHolder values_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_ELEMENTWISE_LOGICAL_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/elementwise_mul_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\n// See the operations supported here:\n// https://eigen.tuxfamily.org/dox-devel/group__QuickRefPage.html\n#define EIGEN_MUL(x, y) ((x) * (y))\nEIGEN_FUNCTOR(Mul, EIGEN_MUL, NumericTypes, SameTypeAsInput);\n#undef EIGEN_MUL\n}\n"
  },
  {
    "path": "caffe2/operators/elementwise_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\n// For some comparison and logical operators, eigen does not have vectorized\n// math so we need to improvise.\n#define NAIVE_FUNCTOR(name, op, input_type, output_type)                       \\\n  struct Naive##name##Functor {                                                \\\n    template <int b_is_scalar, typename T, typename R>                         \\\n    inline void Run(size_t n, const T* a, const T* b, R* out, CPUContext*) {   \\\n      for (int i = 0; i < n; ++i) {                                            \\\n        out[i] = op(a[i], b[b_is_scalar ? 0 : i]);                             \\\n      }                                                                        \\\n    }                                                                          \\\n    template <typename T, typename R>                                          \\\n    void RunWithBroadcast(                                                     \\\n        const T* a,                                                            \\\n        const T* b,                                                            \\\n        R* out,                                                                \\\n        size_t pre,                                                            \\\n        size_t n,                                                              \\\n        CPUContext*) {                                                         \\\n      for (int i = 0; i < pre; ++i) {                                          \\\n        for (int j = 0; j < n; ++j) {                                          \\\n          out[i * n + j] = op(a[i * n + j], b[j]);                             \\\n        }                                                                      \\\n      }                                                                        \\\n    }                                                                          \\\n    template <typename T, typename R>                                          \\\n    void RunWithBroadcast2(                                                    \\\n        const T* a,                                                            \\\n        const T* b,                                                            \\\n        R* out,                                                                \\\n        size_t pre,                                                            \\\n        size_t n,                                                              \\\n        size_t post,                                                           \\\n        CPUContext*) {                                                         \\\n      for (int i = 0; i < pre; ++i) {                                          \\\n        for (int j = 0; j < n; ++j) {                                          \\\n          for (int k = 0; k < post; ++k) {                                     \\\n            out[(i * n + j) * post + k] = op(a[(i * n + j) * post + k], b[j]); \\\n          }                                                                    \\\n        }                                                                      \\\n      }                                                                        \\\n    }                                                                          \\\n  };                                                                           \\\n  REGISTER_CPU_OPERATOR(                                                       \\\n      name,                                                                    \\\n      BinaryElementwiseOp<                                                     \\\n          input_type,                                                          \\\n          CPUContext,                                                          \\\n          Naive##name##Functor,                                                \\\n          output_type>)\n\n#define NAIVE_LT(x, y) ((x) < (y))\nNAIVE_FUNCTOR(LT, NAIVE_LT, NumericTypes, FixedType<bool>);\n#undef NAIVE_LT\n#define NAIVE_LE(x, y) ((x) <= (y))\nNAIVE_FUNCTOR(LE, NAIVE_LE, NumericTypes, FixedType<bool>);\n#undef NAIVE_LE\n#define NAIVE_GT(x, y) ((x) > (y))\nNAIVE_FUNCTOR(GT, NAIVE_GT, NumericTypes, FixedType<bool>);\n#undef NAIVE_GT\n#define NAIVE_GE(x, y) ((x) >= (y))\nNAIVE_FUNCTOR(GE, NAIVE_GE, NumericTypes, FixedType<bool>);\n#undef NAIVE_GE\n#define NAIVE_EQ(x, y) ((x) == (y))\nNAIVE_FUNCTOR(EQ, NAIVE_EQ, IntBoolTypes, FixedType<bool>);\n#undef NAIVE_EQ\n#define NAIVE_AND(x, y) ((x) & (y))\nNAIVE_FUNCTOR(And, NAIVE_AND, BoolTypes, FixedType<bool>);\n#undef NAIVE_AND\n#define NAIVE_OR(x, y) ((x) | (y))\nNAIVE_FUNCTOR(Or, NAIVE_OR, BoolTypes, FixedType<bool>);\n#undef NAIVE_OR\n#define NAIVE_XOR(x, y) ((x) ^ (y))\nNAIVE_FUNCTOR(Xor, NAIVE_XOR, BoolTypes, FixedType<bool>);\n#undef NAIVE_XOR\n\nstruct NotFunctor {\n  inline void operator()(const int n, const bool* x, bool* y, CPUContext*) {\n    for (int i = 0; i < n; ++i) {\n      y[i] = !x[i];\n    }\n  }\n};\nREGISTER_CPU_OPERATOR(\n    Not,\n    UnaryElementwiseOp<BoolTypes, CPUContext, NotFunctor>);\n\ntemplate <typename T>\nvoid SRLHelper::sum2one(const T* x, T* y, size_t n) {\n  *y = ConstEigenArrayMap<T>(x, n, 1).sum();\n}\n\ntemplate <typename T>\nvoid SRLHelper::RunWithBroadcastFront(\n    const T* x,\n    T* y,\n    size_t pre,\n    size_t n,\n    CPUContext*) {\n  EigenArrayMap<T>(y, n, 1) = ConstEigenArrayMap<T>(x, n, pre).rowwise().sum();\n}\n\ntemplate <typename T>\nvoid SRLHelper::RunWithBroadcastBack(\n    const T* x,\n    T* y,\n    size_t post,\n    size_t n,\n    CPUContext*) {\n  EigenArrayMap<T>(y, 1, n) = ConstEigenArrayMap<T>(x, post, n).colwise().sum();\n}\n\ntemplate <typename T>\nvoid SRLHelper::RunWithBroadcast2(\n    const T* a,\n    T* y,\n    size_t pre,\n    size_t n,\n    size_t post,\n    CPUContext*) {\n  for (int i = 0; i < n; ++i) {\n    y[i] = 0;\n    for (int j = 0; j < pre; ++j) {\n      for (int k = 0; k < post; ++k) {\n        y[i] += a[(j * n + i) * post + k];\n      }\n    }\n  }\n}\n\ntemplate <>\ntemplate <typename T>\nbool SumReduceLikeOp<CPUContext>::DoRunWithType() {\n  const auto& A = Input(0);\n  const auto& B = Input(1);\n  auto* C = Output(0);\n  CAFFE_ENFORCE(&B != C, \"In-place is not allowed.\");\n  C->ResizeLike(B);\n  const T* Adata = A.template data<T>();\n  auto* Cdata = C->template mutable_data<T>();\n  if (B.size() == 1) {\n    auto count = A.size();\n    SRLHelper::sum2one<T>(Adata, Cdata, count);\n  } else {\n    size_t pre, n, post;\n    std::tie(pre, n, post) = calculate_broadcast_sizes(A, B, axis_);\n    if (post == 1) {\n      SRLHelper::RunWithBroadcastFront<T>(Adata, Cdata, pre, n, &context_);\n    } else if (pre == 1) {\n      SRLHelper::RunWithBroadcastBack<T>(Adata, Cdata, post, n, &context_);\n    } else {\n      SRLHelper::RunWithBroadcast2<T>(Adata, Cdata, pre, n, post, &context_);\n    }\n  }\n  return true;\n}\nREGISTER_CPU_OPERATOR(SumReduceLike, SumReduceLikeOp<CPUContext>);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/elementwise_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define CUB_STDERR\n#include <cub/block/block_load.cuh>\n#include <cub/block/block_reduce.cuh>\n#include <cub/device/device_reduce.cuh>\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/utils/conversions.h\"\n\nnamespace caffe2 {\n\n#define CUDA_FUNCTOR(name, op, input_type, output_type) \\\ntemplate <int b_is_scalar, typename T, typename R> \\\n__global__ void name##Kernel(const T* a, const T* b, R* out, int n) { \\\n  CUDA_1D_KERNEL_LOOP(i, n) { \\\n    out[i] = op(a[i], b[b_is_scalar ? 0 : i]); \\\n  } \\\n} \\\ntemplate <typename T, typename R> \\\n__global__ void name##BroadcastKernel( \\\n    const T* a, const T* b, R* out, int pre, int n) { \\\n  CUDA_1D_KERNEL_LOOP(i, pre * n) { \\\n    out[i] = op(a[i], b[i % n]); \\\n  } \\\n} \\\ntemplate <typename T, typename R> \\\n__global__ void name##Broadcast2Kernel( \\\n    const T* a, const T* b, R* out, int pre, int n, int post) { \\\n  CUDA_1D_KERNEL_LOOP(i, pre * n * post) { \\\n    out[i] = op(a[i], b[(i / post) % n]); \\\n  } \\\n} \\\n \\\nstruct Cuda##name##Functor { \\\n  template <bool b_is_scalar, typename T, typename R> \\\n  inline void Run( \\\n      size_t n, const T* a, const T* b, R* out, CUDAContext* context) { \\\n    name##Kernel<b_is_scalar, T, R><<<CAFFE_GET_BLOCKS(n), \\\n                                      CAFFE_CUDA_NUM_THREADS, \\\n                                      0, context->cuda_stream()>>>( \\\n        a, b, out, n); \\\n  } \\\n  template <typename T, typename R> \\\n  void RunWithBroadcast( \\\n      const T* a, const T* b, R* out, size_t pre, size_t n, \\\n      CUDAContext* context) { \\\n    name##BroadcastKernel<T, R><<<CAFFE_GET_BLOCKS(pre * n), \\\n                                  CAFFE_CUDA_NUM_THREADS, \\\n                                  0, context->cuda_stream()>>>( \\\n        a, b, out, pre, n); \\\n  } \\\n  template <typename T, typename R> \\\n  void RunWithBroadcast2( \\\n      const T* a, const T* b, R* out, size_t pre, size_t n, size_t post, \\\n      CUDAContext* context) { \\\n    name##Broadcast2Kernel<T, R><<<CAFFE_GET_BLOCKS(pre * n * post), \\\n                                   CAFFE_CUDA_NUM_THREADS, \\\n                                   0, context->cuda_stream()>>>( \\\n        a, b, out, pre, n, post); \\\n  } \\\n}; \\\nREGISTER_CUDA_OPERATOR( \\\n    name, BinaryElementwiseOp< \\\n        input_type, CUDAContext, Cuda##name##Functor, output_type>)\n\n#define CUDA_SUB(x, y) ((x) - (y))\nCUDA_FUNCTOR(Sub, CUDA_SUB, NumericTypes, SameTypeAsInput);\n#undef CUDA_SUB\n#define CUDA_MUL(x, y) ((x) * (y))\nCUDA_FUNCTOR(Mul, CUDA_MUL, NumericTypes, SameTypeAsInput);\n#undef CUDA_MUL\n#define CUDA_DIV(x, y) ((x) / (y))\nCUDA_FUNCTOR(Div, CUDA_DIV, NumericTypes, SameTypeAsInput);\n#undef CUDA_DIV\n#define CUDA_LT(x, y) ((x) < (y))\nCUDA_FUNCTOR(LT, CUDA_LT, NumericTypes, FixedType<bool>);\n#undef CUDA_LT\n#define CUDA_LE(x, y) ((x) <= (y))\nCUDA_FUNCTOR(LE, CUDA_LE, NumericTypes, FixedType<bool>);\n#undef CUDA_LE\n#define CUDA_GT(x, y) ((x) > (y))\nCUDA_FUNCTOR(GT, CUDA_GT, NumericTypes, FixedType<bool>);\n#undef CUDA_GT\n#define CUDA_GE(x, y) ((x) >= (y))\nCUDA_FUNCTOR(GE, CUDA_GE, NumericTypes, FixedType<bool>);\n#undef CUDA_GE\n#define CUDA_EQ(x, y) ((x) == (y))\nCUDA_FUNCTOR(EQ, CUDA_EQ, IntBoolTypes, FixedType<bool>);\n#undef CUDA_EQ\n#define CUDA_AND(x, y) ((x) & (y))\nCUDA_FUNCTOR(And, CUDA_AND, BoolTypes, FixedType<bool>);\n#undef CUDA_AND\n#define CUDA_OR(x, y) ((x) | (y))\nCUDA_FUNCTOR(Or, CUDA_OR, BoolTypes, FixedType<bool>);\n#undef CUDA_OR\n#define CUDA_XOR(x, y) ((x) ^ (y))\nCUDA_FUNCTOR(Xor, CUDA_XOR, BoolTypes, FixedType<bool>);\n#undef CUDA_XOR\n\n__global__ void NotKernel(const int n, const bool* x, bool* y) {\n  CUDA_1D_KERNEL_LOOP(i, n) {\n    y[i] = !x[i];\n  }\n}\nstruct CudaNotFunctor {\n  inline void operator()(\n      const int n, const bool* x, bool* y, CUDAContext* context) {\n    NotKernel<<<CAFFE_GET_BLOCKS(n), CAFFE_CUDA_NUM_THREADS, 0,\n                context->cuda_stream()>>>(n, x, y);\n  }\n};\nREGISTER_CUDA_OPERATOR(\n    Not,\n    UnaryElementwiseOp<BoolTypes, CUDAContext, CudaNotFunctor>);\n\n__global__ void DivKernel(const int n, float *dXdata, float *dYdata,\n                          const float *dZdata, const float *Ydata,\n                          const float *Zdata) {\n  CUDA_1D_KERNEL_LOOP(i, n) {\n    dXdata[i] = dZdata[i] / Ydata[i];\n    dYdata[i] = - (dZdata[i] * Zdata[i]) / Ydata[i];\n  }\n}\n\nvoid ElementWiseDivide(\n    CUDAContext& context,\n    const int n,\n    float* dXdata,\n    float* dYdata,\n    const float* dZdata,\n    const float* Ydata,\n    const float* Zdata) {\n  DivKernel<<<CAFFE_GET_BLOCKS(n), CAFFE_CUDA_NUM_THREADS, 0,\n              context.cuda_stream()>>>(n, dXdata, dYdata, dZdata, Ydata, Zdata);\n}\n\nREGISTER_CUDA_OPERATOR(DivGradient, DivGradientOp<CUDAContext>);\n\nnamespace {\n\ntemplate <typename T>\n__global__ void\nreduce_sum_like_post1(const T* g_idata, T* g_odata, int pre, int N) {\n  int n = blockIdx.x * blockDim.x + threadIdx.x;\n  if (n >= N) {\n    return;\n  }\n\n  float sum = 0.0;\n  for (int i = 0; i < pre; ++i) {\n    sum += convert::To<T, float>(g_idata[i * N + n]);\n  }\n\n  g_odata[n] = convert::To<float, T>(sum);\n}\n\ntemplate <typename T>\nvoid device_reduce(\n    const T* d_in,\n    T* d_out,\n    int N,\n    Tensor<CUDAContext>* buffer,\n    CUDAContext* context) {\n  // Determine temporary device storage requirements\n  size_t temp_storage_bytes = 0;\n  cub::DeviceReduce::Sum(\n      NULL, temp_storage_bytes, d_in, d_out, N, context->cuda_stream());\n\n  auto buffer_size = temp_storage_bytes / sizeof(T);\n  buffer_size += temp_storage_bytes % sizeof(T) != 0 ? 1 : 0;\n  buffer->Resize(buffer_size);\n  void* d_temp_storage = static_cast<void*>(buffer->template mutable_data<T>());\n  // Run sum-reduction\n  cub::DeviceReduce::Sum(\n      d_temp_storage,\n      temp_storage_bytes,\n      d_in,\n      d_out,\n      N,\n      context->cuda_stream());\n}\n\ntemplate <>\nvoid device_reduce<float16>(\n    const float16* in,\n    float16* out,\n    int N,\n    Tensor<CUDAContext>* buffer,\n    CUDAContext* context) {\n  auto buffer_size = 1;\n\n  if (buffer->size() != buffer_size) {\n    buffer->Resize(buffer_size);\n\n    math::Set<float16, CUDAContext>(\n        N,\n        convert::To<float,float16>(1.),\n        buffer->mutable_data<float16>(),\n        context);\n  }\n\n  CUBLAS_ENFORCE(cublasDotEx(\n              context->cublas_handle(),\n              N,\n              in,\n              CUDA_R_16F,\n              1,\n              buffer->data<float16>(),\n              CUDA_R_16F,\n              0,\n              out,\n              CUDA_R_16F,\n              CUDA_R_32F));\n}\n\ntemplate <typename T, int BLOCK_THREADS>\n__global__ void\nreduce_sum_like(const T* g_idata, T* g_odata, int pre, int N, int post) {\n  int n = blockIdx.x;\n  float sum = 0.0;\n  int limit = pre * post;\n  for (int i = threadIdx.x; i < limit; i += blockDim.x) {\n    int curPre = i / post;\n    int curPost = i % post;\n\n    sum += convert::To<T, float>(g_idata[curPre * N * post + n * post + curPost]);\n  }\n  // uses a shared memory reduction within block\n  typedef cub::BlockReduce<float, BLOCK_THREADS> BlockReduceT;\n  // Shared memory\n  __shared__ typename BlockReduceT::TempStorage temp_storage;\n  float aggregate = BlockReduceT(temp_storage).Sum(sum);\n  if (threadIdx.x == 0) {\n    g_odata[n] = convert::To<float, T>(aggregate);\n  }\n}\n} // namespace\n\ntemplate <>\ntemplate <typename T>\nbool SumReduceLikeOp<CUDAContext>::DoRunWithType() {\n  const auto& A = Input(0);\n  const auto& B = Input(1);\n  auto* C = Output(0);\n  auto count = A.size();\n  CAFFE_ENFORCE(&B != C, \"In-place is not allowed.\");\n  C->ResizeLike(B);\n  const T* Adata = A.template data<T>();\n  auto* Cdata = C->template mutable_data<T>();\n  if (B.size() == 1) {\n    device_reduce<T>(Adata, Cdata, count, &sum_buffer_, &context_);\n  } else {\n    size_t pre, n, post;\n    std::tie(pre, n, post) = calculate_broadcast_sizes(A, B, axis_);\n    // because we check shape(B) \\in shape(A) before,\n    // post and pre cannot be 1 at same time\n    if (post == 1) {\n      reduce_sum_like_post1<T>\n          <<<CAFFE_GET_BLOCKS(n),\n             CAFFE_CUDA_NUM_THREADS,\n             0,\n             context_.cuda_stream()>>>(Adata, Cdata, pre, n);\n    } else {\n      if (post >= 128) {\n        reduce_sum_like<T, 512>\n            <<<n, 512, 0, context_.cuda_stream()>>>(Adata, Cdata, pre, n, post);\n      } else if (post >= 64) {\n        reduce_sum_like<T, 128>\n            <<<n, 128, 0, context_.cuda_stream()>>>(Adata, Cdata, pre, n, post);\n      } else if (post >= 32) {\n        reduce_sum_like<T, 64>\n            <<<n, 64, 0, context_.cuda_stream()>>>(Adata, Cdata, pre, n, post);\n      } else {\n        reduce_sum_like<T, 32>\n            <<<n, 32, 0, context_.cuda_stream()>>>(Adata, Cdata, pre, n, post);\n      }\n    }\n  }\n  return true;\n}\n\ntemplate <>\nbool SumReduceLikeOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<float, float16>>::call(this, Input(0));\n}\n\nREGISTER_CUDA_OPERATOR(SumReduceLike, SumReduceLikeOp<CUDAContext>);\n\nnamespace {\n\ntemplate <bool is_scaler, typename T, typename M>\n__global__ void binary_add_kernel(const int N, const T* a, const T* b, T* r) {\n  CUDA_1D_KERNEL_LOOP(idx, N) {\n    r[idx] = convert::To<M, T>(\n        convert::To<T, M>(a[idx]) +\n        convert::To<T, M>(is_scaler ? b[0] : b[idx]));\n  }\n}\n\ntemplate <bool no_post, typename T, typename M>\n__global__ void binary_add_kernel_broadcast(\n    const T* a,\n    const T* b,\n    T* r,\n    const int pre,\n    const int post,\n    const int n) {\n  CUDA_1D_KERNEL_LOOP(idx, no_post ? pre * n : pre * post * n) {\n    r[idx] = convert::To<M, T>(\n        convert::To<T, M>(a[idx]) +\n        convert::To<T, M>(no_post ? b[idx % n] : b[(idx / post) % n]));\n  }\n}\n} // namespace\n\n// Actual Add operator, because the above macros are read-only.\nclass CUDAAddOp final : public Operator<CUDAContext> {\n public:\n  CUDAAddOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws),\n        OP_SINGLE_ARG(bool, \"broadcast\", enable_broadcast_, 0),\n        OP_SINGLE_ARG(int, \"axis\", axis_, -1),\n        OP_SINGLE_ARG(string, \"axis_str\", axis_str_, \"\"),\n        OP_SINGLE_ARG(string, \"order\", order_, \"NCHW\") {\n    // Figure out the correct axis to use.\n    if (enable_broadcast_) {\n      if (axis_ != -1) {\n        // Get axis from an explicit axis argument.\n        CAFFE_ENFORCE_EQ(\n            axis_str_.size(),\n            0,\n            \"Args axis and axis_str cannot be used simultaneously.\");\n      } else if (axis_str_.size()) {\n        // Get the axis index semantically.\n        CAFFE_ENFORCE_EQ(\n            axis_str_.size(), 1, \"Unsupported axis string\", axis_str_);\n        size_t semantic_axis_ = order_.find(axis_str_);\n        CAFFE_ENFORCE_NE(\n            semantic_axis_,\n            string::npos,\n            \"Unrecognizable axis string \",\n            axis_str_,\n            \" from order string \",\n            order_);\n        axis_ = semantic_axis_;\n      }\n    } else {\n      CAFFE_ENFORCE(\n          axis_ == -1 && axis_str_.size() == 0,\n          \"Do not specify axis or axis_str if broadcast is not enabled.\");\n    }\n  }\n\n  ~CUDAAddOp() {}\n\n  template <typename T, typename M>\n  bool DoRunWithType() {\n    auto& X0 = Input(0);\n    auto& X1 = Input(1);\n    auto* output = Output(0);\n\n    output->ResizeLike(X0);\n\n    const T* X0data = X0.template data<T>();\n    const T* X1data = X1.template data<T>();\n    T* outputData = output->template mutable_data<T>();\n\n    if (!enable_broadcast_) {\n      CAFFE_ENFORCE_EQ(\n          X0.dims(),\n          X1.dims(),\n          \"Dimension mismatch - did you forget to set broadcast=1?\");\n      binary_add_kernel<false, T, M><<<\n          CAFFE_GET_BLOCKS(X0.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(X0.size(), X0data, X1data, outputData);\n    } else if (X1.size() == 1) {\n      binary_add_kernel<true, T, M><<<\n          CAFFE_GET_BLOCKS(X0.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(X0.size(), X0data, X1data, outputData);\n    } else {\n      size_t pre, n, post;\n      std::tie(pre, n, post) = calculate_broadcast_sizes(X0, X1, axis_);\n      if (post == 1) {\n        binary_add_kernel_broadcast<true, T, M><<<\n            CAFFE_GET_BLOCKS(pre * n),\n            CAFFE_CUDA_NUM_THREADS,\n            0,\n            context_.cuda_stream()>>>(X0data, X1data, outputData, pre, post, n);\n      } else {\n        binary_add_kernel_broadcast<false, T, M><<<\n            CAFFE_GET_BLOCKS(pre * post * n),\n            CAFFE_CUDA_NUM_THREADS,\n            0,\n            context_.cuda_stream()>>>(X0data, X1data, outputData, pre, post, n);\n      }\n    }\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    if (Input(0).IsType<float>()) {\n      return DoRunWithType<float, float>();\n    } else if (Input(0).IsType<float16>()) {\n      return DoRunWithType<float16, float>();\n    } else if (Input(0).IsType<int32_t>()) {\n      return DoRunWithType<int32_t, int32_t>();\n    } else if (Input(0).IsType<int64_t>()) {\n      return DoRunWithType<int64_t, int64_t>();\n    } else {\n      return false;\n    }\n  }\n\n private:\n  bool enable_broadcast_;\n  int axis_;\n  string axis_str_;\n  string order_;\n};\n\nREGISTER_CUDA_OPERATOR(Add, CUDAAddOp);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/elementwise_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_ELEMENTWISE_OP_H_\n#define CAFFE2_OPERATORS_ELEMENTWISE_OP_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n\n#include <tuple>\n\nnamespace caffe2 {\n\nusing NumericTypes = TensorTypes<int32_t, int64_t, float, double>;\nusing IntTypes = TensorTypes<int32_t, int64_t>;\nusing BoolTypes = TensorTypes<bool>;\nusing IntBoolTypes = TensorTypes<int32_t, int64_t, bool>; // discrete types\n\nstruct SameTypeAsInput {\n  template <typename T>\n  using type = T;\n};\n\ntemplate <typename R>\nstruct FixedType {\n  template <typename T>\n  using type = R;\n};\n\ntemplate <\n    typename InputTypes,\n    class Context,\n    class Functor,\n    class TypeMap = SameTypeAsInput>\nclass UnaryElementwiseWithArgsOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  UnaryElementwiseWithArgsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws), functor_(*this) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<InputTypes>::call(this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& input = Input(0);\n    auto* output = Output(0);\n    output->ResizeLike(input);\n    using R = typename TypeMap::template type<T>;\n    functor_(\n        input.size(),\n        input.template data<T>(),\n        output->template mutable_data<R>(),\n        &context_);\n    return true;\n  }\n\n private:\n  Functor functor_;\n};\n\n/**\n * WithDefaultConstructor is a functor that can be used as the functor of an\n * UnaryElementwiseWithArgsOp. It simply forwards the operator() call into\n * another functor that doesn't accept arguments in its constructor.\n */\ntemplate <typename Functor>\nstruct WithDefaultConstructor {\n  explicit WithDefaultConstructor(OperatorBase& /*op*/) {}\n\n  template <typename In, typename Out, typename Context>\n  void operator()(int n, const In* in, Out* out, Context* c) {\n    Functor()(n, in, out, c);\n  }\n};\n\n/**\n * UnaryElementwiseOp is a wrapper around UnaryElementwiseWithArgsOp, with the\n * difference that it takes a functor with default constructor, e.g. that does\n * not need to take into consideration any arguments during operator creation.\n */\ntemplate <\n    typename InputTypes,\n    class Context,\n    class Functor,\n    class OutputType = SameTypeAsInput>\nusing UnaryElementwiseOp = UnaryElementwiseWithArgsOp<\n    InputTypes,\n    Context,\n    WithDefaultConstructor<Functor>,\n    OutputType>;\n\ntemplate <typename Context>\nstd::tuple<size_t, size_t, size_t> calculate_broadcast_sizes(\n    const Tensor<Context>& A,\n    const Tensor<Context>& B,\n    int axis) {\n  CAFFE_ENFORCE_GE(\n      A.ndim(),\n      B.ndim(),\n      \"If you are doing broadcasting, input1 should have \"\n      \"a smaller or equal number of dimensions.\");\n  if (axis == -1) {\n    axis = A.ndim() - B.ndim();\n  }\n  CAFFE_ENFORCE(\n      axis >= 0 && axis <= A.ndim() - B.ndim(),\n      \"Broadcast axis should be in the range of\"\n      \"[0, A.ndim() - B.ndim()], but axis = \",\n      axis);\n\n  int b_dim_start = 0;\n  while (b_dim_start < B.ndim() && B.dim(b_dim_start) == 1) {\n    ++b_dim_start;\n  }\n  int b_dim_end = B.ndim() - 1;\n  while (b_dim_end >= b_dim_start && B.dim(b_dim_end) == 1) {\n    --b_dim_end;\n  }\n  size_t pre = 1, n = 1, post = 1;\n  for (int i = 0; i < axis + b_dim_start; ++i) {\n    pre *= A.dim(i);\n  }\n  for (int i = b_dim_start; i <= b_dim_end; ++i) {\n    CAFFE_ENFORCE_EQ(\n        A.dim(i + axis), B.dim(i), \"Broadcast dimension mismatch.\");\n    n *= B.dim(i);\n  }\n  for (int i = axis + b_dim_end + 1; i < A.ndim(); ++i) {\n    post *= A.dim(i);\n  }\n  return std::make_tuple(pre, n, post);\n}\n\n/**\n * Performs a binary operation (e.g. +, - or /) with optional broadcast support.\n *\n * Functor specifies actual operation to be performed.\n *\n * If AllowBroadcast=false tensors has to be of exactly the same shape.\n *\n * If AllowBroadcast=true it support limited broadcasting of the right-hand-side\n * argument to match the shape of left-hand-side argument. Only suffix matching\n * is supported for now (1-dim expansion is allowed on both ends). E.g. this\n * will be accepted:\n * A dims: 2 3 4 5 6\n * B dims:   1 4 1\n *           ^\n *           |\n *          axis = 1\n */\ntemplate <\n    typename InputTypes,\n    class Context,\n    class Functor,\n    class TypeMap = SameTypeAsInput>\nclass BinaryElementwiseOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  BinaryElementwiseOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        OP_SINGLE_ARG(bool, \"broadcast\", enable_broadcast_, 0),\n        OP_SINGLE_ARG(int, \"axis\", axis_, -1),\n        OP_SINGLE_ARG(string, \"axis_str\", axis_str_, \"\"),\n        OP_SINGLE_ARG(string, \"order\", order_, \"NCHW\"),\n        functor_() {\n    // Figure out the correct axis to use.\n    if (enable_broadcast_) {\n      if (axis_ != -1) {\n        // Get axis from an explicit axis argument.\n        CAFFE_ENFORCE_EQ(\n            axis_str_.size(),\n            0,\n            \"Args axis and axis_str cannot be used simultaneously.\");\n      } else if (axis_str_.size()) {\n        // Get the axis index semantically.\n        CAFFE_ENFORCE_EQ(\n            axis_str_.size(), 1, \"Unsupported axis string\", axis_str_);\n        size_t semantic_axis_ = order_.find(axis_str_);\n        CAFFE_ENFORCE_NE(\n            semantic_axis_,\n            string::npos,\n            \"Unrecognizable axis string \",\n            axis_str_,\n            \" from order string \",\n            order_);\n        axis_ = semantic_axis_;\n      }\n    } else {\n      CAFFE_ENFORCE(\n          axis_ == -1 && axis_str_.size() == 0,\n          \"Do not specify axis or axis_str if broadcast is not enabled.\");\n    }\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<InputTypes>::call(this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    const auto& A = Input(0);\n    const auto& B = Input(1);\n    auto* C = Output(0);\n    CAFFE_ENFORCE(\n        &B != C || !enable_broadcast_,\n        \"In-place is allowed only with the first tensor when broadcasting\");\n    C->ResizeLike(A);\n    const T* Adata = A.template data<T>();\n    const T* Bdata = B.template data<T>();\n    auto* Cdata =\n        C->template mutable_data<typename TypeMap::template type<T>>();\n    if (!enable_broadcast_) {\n      CAFFE_ENFORCE_EQ(\n          A.dims(),\n          B.dims(),\n          \"Dimension mismatch - did you forget to set broadcast=1?\");\n      functor_.template Run<false>(A.size(), Adata, Bdata, Cdata, &context_);\n    } else if (B.size() == 1) {\n      functor_.template Run<true>(A.size(), Adata, Bdata, Cdata, &context_);\n    } else {\n      size_t pre, n, post;\n      std::tie(pre, n, post) = calculate_broadcast_sizes(A, B, axis_);\n      if (post == 1) {\n        functor_.RunWithBroadcast(Adata, Bdata, Cdata, pre, n, &context_);\n      } else {\n        functor_.RunWithBroadcast2(\n            Adata, Bdata, Cdata, pre, n, post, &context_);\n      }\n    }\n    return true;\n  }\n\n private:\n  bool enable_broadcast_;\n  int axis_;\n  string axis_str_;\n  string order_;\n  Functor functor_;\n};\n\ntemplate <typename Functor>\nstruct WithoutBroadcast {\n  template <bool b_is_scalar, typename T, typename R, typename Context>\n  inline void Run(size_t n, const T* a, const T* b, R* out, Context* c) {\n    if (b_is_scalar) {\n      CAFFE_THROW(\"Broadcast not supported.\");\n    } else {\n      Functor().Run(n, a, b, out, c);\n    }\n  }\n  template <typename T, typename R, typename Context>\n  inline void RunWithBroadcast(\n      const T* /*a*/,\n      const T* /*b*/,\n      R* /*out*/,\n      size_t /*pre*/,\n      size_t /*n*/,\n      Context*) {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n  template <typename T, typename R, typename Context>\n  inline void RunWithBroadcast2(\n      const T* /*a*/,\n      const T* /*b*/,\n      R* /*out*/,\n      size_t /*pre*/,\n      size_t /*n*/,\n      size_t /*post*/,\n      Context*) {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n};\n\n// Gradient operator for elementwise division.\ntemplate <class Context>\nclass DivGradientOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(DivGradientOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n};\n\nnamespace SRLHelper {\n\ntemplate <typename T>\nvoid sum2one(const T* a, T* y, size_t n);\n\ntemplate <typename T>\nvoid RunWithBroadcastFront(const T* a, T* y, size_t pre, size_t n, CPUContext*);\n\ntemplate <typename T>\nvoid RunWithBroadcastBack(const T* a, T* y, size_t post, size_t n, CPUContext*);\n\ntemplate <typename T>\nvoid RunWithBroadcast2(\n    const T* a,\n    T* y,\n    size_t pre,\n    size_t n,\n    size_t post,\n    CPUContext*);\n\n} // namespace SRLHelper\n\n// Sum reduction operator that is used for computing the gradient in cases\n// where the forward op is in broadcast mode.\ntemplate <class Context>\nclass SumReduceLikeOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SumReduceLikeOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        OP_SINGLE_ARG(int, \"axis\", axis_, -1),\n        OP_SINGLE_ARG(string, \"axis_str\", axis_str_, \"\"),\n        OP_SINGLE_ARG(string, \"order\", order_, \"NCHW\") {\n    if (axis_ != -1) {\n      // Get axis from an explicit axis argument.\n      CAFFE_ENFORCE_EQ(\n          axis_str_.size(),\n          0,\n          \"Args axis and axis_str cannot be used simultaneously.\");\n    } else if (axis_str_.size()) {\n      // Get the axis index semantically.\n      CAFFE_ENFORCE_EQ(\n          axis_str_.size(), 1, \"Unsupported axis string\", axis_str_);\n      size_t semantic_axis = order_.find(axis_str_);\n      CAFFE_ENFORCE_NE(\n          semantic_axis,\n          string::npos,\n          \"Unrecognizable axis string \",\n          axis_str_,\n          \" from order string \",\n          order_);\n      axis_ = semantic_axis;\n    }\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<float, double>>::call(this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType();\n\n private:\n  int axis_;\n  string axis_str_;\n  string order_;\n  Tensor<Context> ones_;\n  Tensor<Context> sum_buffer_;\n};\n\ntemplate <class Context>\nbool DivGradientOp<Context>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& Z = Input(1);\n  auto& dZ = Input(2);\n  auto* dX = Output(0);\n  auto* dY = Output(1);\n  CAFFE_ENFORCE_GT(Y.size(), 0);\n  CAFFE_ENFORCE_GT(Z.size(), 0);\n  dX->ResizeLike(Y);\n  dY->ResizeLike(Y);\n\n  const float* Ydata = Y.template data<float>();\n  const float* Zdata = Z.template data<float>();\n  const float* dZdata = dZ.template data<float>();\n  float* dXdata = dX->template mutable_data<float>();\n  float* dYdata = dY->template mutable_data<float>();\n\n  ElementWiseDivide(context_, Y.size(), dXdata, dYdata, dZdata, Ydata, Zdata);\n  return true;\n}\n\n// For arithmetic operators, Eigen provides a good way to vectorize even\n// when broadcasting.\n#define EIGEN_FUNCTOR(name, eigen_op, input_type, output_type)               \\\n  struct Eigen##name##Functor {                                              \\\n    template <int b_is_scalar, typename T, typename R>                       \\\n    inline void Run(size_t n, const T* a, const T* b, R* out, CPUContext*) { \\\n      if (b_is_scalar) {                                                     \\\n        EigenVectorArrayMap<R>(out, n) =                                     \\\n            eigen_op((ConstEigenVectorArrayMap<T>(a, n)), (b[0]));           \\\n      } else {                                                               \\\n        EigenVectorArrayMap<R>(out, n) = eigen_op(                           \\\n            (ConstEigenVectorArrayMap<T>(a, n)),                             \\\n            (ConstEigenVectorArrayMap<T>(b, n)));                            \\\n      }                                                                      \\\n    }                                                                        \\\n    template <typename T, typename R>                                        \\\n    void RunWithBroadcast(                                                   \\\n        const T* a,                                                          \\\n        const T* b,                                                          \\\n        R* out,                                                              \\\n        size_t pre,                                                          \\\n        size_t n,                                                            \\\n        CPUContext*) {                                                       \\\n      EigenArrayMap<R>(out, n, pre) = eigen_op(                              \\\n          (ConstEigenArrayMap<T>(a, n, pre).colwise()),                      \\\n          (ConstEigenVectorArrayMap<T>(b, n)));                              \\\n    }                                                                        \\\n    template <typename T, typename R>                                        \\\n    void RunWithBroadcast2(                                                  \\\n        const T* a,                                                          \\\n        const T* b,                                                          \\\n        R* out,                                                              \\\n        size_t pre,                                                          \\\n        size_t n,                                                            \\\n        size_t post,                                                         \\\n        CPUContext*) {                                                       \\\n      for (int i = 0; i < pre; ++i) {                                        \\\n        EigenArrayMap<R>(out + i * n * post, post, n) = eigen_op(            \\\n            (ConstEigenArrayMap<T>(a + i * n * post, post, n).rowwise()),    \\\n            (Eigen::Map<const Eigen::Array<T, 1, Eigen::Dynamic>>(b, n)));   \\\n      }                                                                      \\\n    }                                                                        \\\n  };                                                                         \\\n  REGISTER_CPU_OPERATOR(                                                     \\\n      name,                                                                  \\\n      BinaryElementwiseOp<                                                   \\\n          input_type,                                                        \\\n          CPUContext,                                                        \\\n          Eigen##name##Functor,                                              \\\n          output_type>)\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_ELEMENTWISE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/elementwise_op_gpu_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op_test.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/flags.h\"\n\nCAFFE2_DECLARE_string(caffe_test_root);\n\ntemplate <>\nvoid CopyVector<caffe2::CUDAContext>(const int N, const bool* x, bool* y) {\n  cudaMemcpy(y, x, N * sizeof(bool), cudaMemcpyHostToDevice);\n}\n\ntemplate <>\ncaffe2::OperatorDef CreateOperatorDef<caffe2::CUDAContext>() {\n  caffe2::OperatorDef def;\n  def.mutable_device_option()->set_device_type(caffe2::CUDA);\n  return def;\n}\n\nTEST(ElementwiseGPUTest, And) {\n  if (!caffe2::HasCudaGPU())\n    return;\n  elementwiseAnd<caffe2::CUDAContext>();\n}\n\nTEST(ElementwiseGPUTest, Or) {\n  if (!caffe2::HasCudaGPU())\n    return;\n  elementwiseOr<caffe2::CUDAContext>();\n}\n\nTEST(ElementwiseGPUTest, Xor) {\n  if (!caffe2::HasCudaGPU())\n    return;\n  elementwiseXor<caffe2::CUDAContext>();\n}\n\nTEST(ElementwiseGPUTest, Not) {\n  if (!caffe2::HasCudaGPU())\n    return;\n  elementwiseNot<caffe2::CUDAContext>();\n}\n"
  },
  {
    "path": "caffe2/operators/elementwise_op_schema.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/operator_gradient.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\nconst char* kBroadcastDoc = R\"DOC(\nIf necessary the right-hand-side argument will be broadcasted to match the\nshape of left-hand-side argument. When broadcasting is specified, the second\ntensor can either be of size 1 (a scalar value), or having its shape as a\ncontiguous subset of the first tensor's shape. The starting of the mutually\nequal shape is specified by the argument \"axis\", and if it is not set, suffix\nmatching is assumed. 1-dim expansion doesn't work yet.\n\nFor example, the following tensor shapes are supported (with broadcast=1):\n\n  shape(A) = (2, 3, 4, 5), shape(B) = (,), i.e. B is a scalar\n  shape(A) = (2, 3, 4, 5), shape(B) = (5,)\n  shape(A) = (2, 3, 4, 5), shape(B) = (4, 5)\n  shape(A) = (2, 3, 4, 5), shape(B) = (3, 4), with axis=1\n  shape(A) = (2, 3, 4, 5), shape(B) = (2), with axis=0\n\nArgument `broadcast=1` needs to be passed to enable broadcasting.\n)DOC\";\n\nstd::function<void(OpSchema&)> MathDocGenerator(const char* name) {\n  return [=](OpSchema& schema) {\n    string doc = R\"DOC(\nPerforms element-wise binary {name} (with limited broadcast support).\n{broadcast_doc})DOC\";\n    ReplaceAll(doc, \"{name}\", name);\n    ReplaceAll(doc, \"{broadcast_doc}\", kBroadcastDoc);\n    schema.SetDoc(doc);\n    schema.Arg(\"broadcast\", \"Pass 1 to enable broadcasting\");\n    schema.Arg(\n        \"axis\",\n        \"If set, defines the broadcast dimensions. See doc for details.\");\n    schema.Input(\n        0,\n        \"A\",\n        \"First operand, should share the type with the second operand.\");\n    schema.Input(\n        1,\n        \"B\",\n        \"Second operand. With broadcasting can be of smaller size than A. \"\n        \"If broadcasting is disabled it should be of the same size.\");\n    schema.Output(0, \"C\", \"Result, has same dimensions and type as A\");\n  };\n}\n\nOPERATOR_SCHEMA(Add)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}, {1, 0}})\n    .CostInferenceFunction(PointwiseCostInference<1>)\n    .IdenticalTypeAndShapeOfInput(0)\n    .FillUsing(MathDocGenerator(\"addition\"));\nOPERATOR_SCHEMA(Sub)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}, {1, 0}})\n    .CostInferenceFunction(PointwiseCostInference<1>)\n    .IdenticalTypeAndShapeOfInput(0)\n    .FillUsing(MathDocGenerator(\"subtraction\"));\nOPERATOR_SCHEMA(Mul)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}, {1, 0}})\n    .CostInferenceFunction(PointwiseCostInference<1>)\n    .IdenticalTypeAndShapeOfInput(0)\n    .FillUsing(MathDocGenerator(\"multiplication\"));\nOPERATOR_SCHEMA(Div)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .CostInferenceFunction(PointwiseCostInference<1>)\n    .IdenticalTypeAndShapeOfInput(0)\n    .FillUsing(MathDocGenerator(\"division\"));\nOPERATOR_SCHEMA(DivGradient).NumInputs(3).NumOutputs(2).AllowInplace({{0, 0}});\n\nOPERATOR_SCHEMA(SumReduceLike)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInput(0)\n    .SetDoc(R\"DOC(\nSumReduceLike operator takes 2 tensors as input. It performs reduce sum to the\nfirst input so that the output looks like the second one.\nIt assumes that the first input\nhas more dimensions than the second, and the dimensions of the second input is\nthe contiguous subset of the dimensions of the first.\nFor example, the following tensor shapes are supported:\n\n  shape(A) = (2, 3, 4, 5), shape(B) = (4, 5)\n  shape(A) = (2, 3, 4, 5), shape(B) = (,), i.e. B is a scalar\n  shape(A) = (2, 3, 4, 5), shape(B) = (3, 4), with axis=1\n  shape(A) = (2, 3, 2, 5), shape(B) = (2), with axis=0\n    )DOC\")\n    .Arg(\n        \"axis\",\n        \"If set, defines the starting dimension for reduction. Args `axis` and \"\n        \"`axis_str` cannot be used simultaneously.\")\n    .Arg(\n        \"axis_str\",\n        \"If set, it could only be N or C or H or W. `order` arg should also be \"\n        \"provided. It defines the reduction dimensions on NCHW or NHWC. Args \"\n        \"`axis` and `axis_str` cannot be used simultaneously.\")\n    .Arg(\"order\", \"Either NHWC or HCWH\")\n    .Input(\n        0,\n        \"A\",\n        \"First operand, should share the type with the second operand.\")\n    .Input(\n        1,\n        \"B\",\n        \"Second operand. With broadcasting can be of smaller size than A. \"\n        \"If broadcasting is disabled it should be of the same size.\")\n    .Output(0, \"C\", \"Result, has same dimensions and type as B\");\n\nclass GetAddGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    if (!ArgumentHelper::HasArgument(Def(), \"broadcast\")) {\n      SetDense(0, GO(0));\n      SetDense(1, GO(0));\n      return vector<OperatorDef>();\n    }\n    SetDense(0, GO(0));\n\n    return SingleGradientDef(\n        \"SumReduceLike\",\n        \"\",\n        vector<string>{GO(0), I(1)},\n        vector<string>{GI(1)});\n  }\n};\nREGISTER_GRADIENT(Add, GetAddGradient);\n\n// TODO(jiayq): Although we have Sub gradient implemented, we are still missing\n// the Negative unary operator to be implemented.\nclass GetSubGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    if (!ArgumentHelper::HasArgument(Def(), \"broadcast\")) {\n      SetDense(0, GO(0));\n      return SingleGradientDef(\n          \"Negative\", \"\", vector<string>{GO(0)}, vector<string>{GI(1)});\n    } else {\n      SetDense(0, GO(0));\n      vector<OperatorDef> grad_ops;\n      grad_ops.push_back(CreateOperatorDef(\n          \"Negative\",\n          \"\",\n          vector<string>{GO(0)},\n          vector<string>{GI(1) + \"_autogen_pre_red\"}));\n\n      Argument axis, axis_str, order;\n      if (ArgumentHelper::HasArgument(Def(), \"axis\")) {\n        axis = GetArgument(Def(), \"axis\");\n      } else {\n        axis = MakeArgument<int>(\"axis\", -1);\n      }\n      if (ArgumentHelper::HasArgument(Def(), \"axis_str\")) {\n        axis_str = GetArgument(Def(), \"axis_str\");\n      } else {\n        axis_str = MakeArgument<string>(\"axis_str\", \"\");\n      }\n      if (ArgumentHelper::HasArgument(Def(), \"order\")) {\n        order = GetArgument(Def(), \"order\");\n      } else {\n        order = MakeArgument<string>(\"order\", \"NCHW\");\n      }\n      grad_ops.push_back(CreateOperatorDef(\n          \"SumReduceLike\",\n          \"\",\n          vector<string>{GI(1) + \"_autogen_pre_red\", I(1)},\n          vector<string>{GI(1)},\n          vector<Argument>{axis, axis_str, order}));\n\n      return grad_ops;\n    }\n  }\n  // Make sure the broadcast argument is not copied over.\n  bool CopyArguments() const override {\n    return false;\n  }\n};\nREGISTER_GRADIENT(Sub, GetSubGradient);\n\nclass GetMulGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE(\n        Def().input(0) != Def().output(0) && Def().input(1) != Def().output(0),\n        \"Gradient computation cannot be carried out if Mul uses in-place \"\n        \"computation: \",\n        ProtoDebugString(Def()));\n    if (!ArgumentHelper::HasArgument(Def(), \"broadcast\")) {\n      return vector<OperatorDef>{\n          CreateOperatorDef(\n              \"Mul\", \"\", vector<string>{GO(0), I(1)}, vector<string>{GI(0)}),\n          CreateOperatorDef(\n              \"Mul\", \"\", vector<string>{GO(0), I(0)}, vector<string>{GI(1)})};\n    } else {\n      Argument broadcast, axis, axis_str, order;\n      if (ArgumentHelper::HasArgument(Def(), \"broadcast\")) {\n        broadcast = GetArgument(Def(), \"broadcast\");\n      } else {\n        broadcast = MakeArgument<int>(\"broadcast\", 0);\n      }\n      if (ArgumentHelper::HasArgument(Def(), \"axis\")) {\n        axis = GetArgument(Def(), \"axis\");\n      } else {\n        axis = MakeArgument<int>(\"axis\", -1);\n      }\n      if (ArgumentHelper::HasArgument(Def(), \"axis_str\")) {\n        axis_str = GetArgument(Def(), \"axis_str\");\n      } else {\n        axis_str = MakeArgument<string>(\"axis_str\", \"\");\n      }\n      if (ArgumentHelper::HasArgument(Def(), \"order\")) {\n        order = GetArgument(Def(), \"order\");\n      } else {\n        order = MakeArgument<string>(\"order\", \"NCHW\");\n      }\n\n      vector<OperatorDef> grad_ops;\n      grad_ops.push_back(CreateOperatorDef(\n          \"Mul\",\n          \"mul_grad_1st_op\",\n          vector<string>{GO(0), I(1)},\n          vector<string>{GI(0)},\n          vector<Argument>{broadcast, axis, axis_str, order}));\n      grad_ops.push_back(CreateOperatorDef(\n          \"Mul\",\n          \"mul_gradient_2nd_op\",\n          vector<string>{GO(0), I(0)},\n          vector<string>{GI(1) + \"_autogen_pre_red\"}));\n\n      grad_ops.push_back(CreateOperatorDef(\n          \"SumReduceLike\",\n          \"mul_with_broadcast_grad_3\",\n          vector<string>{GI(1) + \"_autogen_pre_red\", I(1)},\n          vector<string>{GI(1)},\n          vector<Argument>{axis, axis_str, order}));\n\n      return grad_ops;\n    }\n  }\n\n  // Make sure the broadcast argument is not copied over.\n  bool CopyArguments() const override {\n    return false;\n  }\n};\nREGISTER_GRADIENT(Mul, GetMulGradient);\n\nclass GetDivGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE(\n        !ArgumentHelper::HasArgument(Def(), \"broadcast\"),\n        \"Gradient not ready yet for Div with broadcasting.\");\n    return SingleGradientDef(\n        \"DivGradient\",\n        \"\",\n        vector<string>{I(1), O(0), GO(0)},\n        vector<string>{GI(0), GI(1)});\n  }\n};\nREGISTER_GRADIENT(Div, GetDivGradient);\n\nstd::function<void(OpSchema&)> ComparisonDocGenerator(\n    const char* name,\n    const char* desc) {\n  return [=](OpSchema& schema) {\n    string doc = R\"DOC(\nPerforms element-wise {desc} comparison `{name}` (with limited broadcast support).\n{broadcast_doc})DOC\";\n    ReplaceAll(doc, \"{name}\", name);\n    ReplaceAll(doc, \"{desc}\", desc);\n    ReplaceAll(doc, \"{broadcast_doc}\", kBroadcastDoc);\n    schema.SetDoc(doc);\n    schema.Arg(\"broadcast\", \"Pass 1 to enable broadcasting\");\n    schema.Arg(\n        \"axis\",\n        \"If set, defines the broadcast dimensions. See doc for details.\");\n    schema.Input(\n        0,\n        \"A\",\n        \"First operand, should share the type with the second operand.\");\n    schema.Input(\n        1,\n        \"B\",\n        \"Second operand. With broadcasting can be of smaller size than A. \"\n        \"If broadcasting is disabled it should be of the same size.\");\n    schema.Output(0, \"C\", \"Result, has same dimensions and A and type `bool`\");\n  };\n}\n\n#define CAFFE2_SCHEMA_FOR_BINARY_COMPARISON_OP(name, symbol, desc) \\\n  OPERATOR_SCHEMA(name).NumInputs(2).NumOutputs(1).FillUsing(      \\\n      ComparisonDocGenerator(symbol, desc));                       \\\n  SHOULD_NOT_DO_GRADIENT(name)\n\nCAFFE2_SCHEMA_FOR_BINARY_COMPARISON_OP(LT, \"<\", \"less than\");\nCAFFE2_SCHEMA_FOR_BINARY_COMPARISON_OP(LE, \"<=\", \"less or equal than\");\nCAFFE2_SCHEMA_FOR_BINARY_COMPARISON_OP(GT, \">\", \"greater than\");\nCAFFE2_SCHEMA_FOR_BINARY_COMPARISON_OP(GE, \">=\", \"greater or equal than\");\nCAFFE2_SCHEMA_FOR_BINARY_COMPARISON_OP(EQ, \"==\", \"equality\");\n\nstd::function<void(OpSchema&)> LogicalDocGenerator(const char* name) {\n  return [=](OpSchema& schema) {\n    string doc = R\"DOC(\nPerforms element-wise logical operation `{name}` (with limited broadcast support).\nBoth input operands should be of type `bool`.\n{broadcast_doc})DOC\";\n    ReplaceAll(doc, \"{name}\", name);\n    ReplaceAll(doc, \"{broadcast_doc}\", kBroadcastDoc);\n    schema.SetDoc(doc);\n    schema.Arg(\"broadcast\", \"Pass 1 to enable broadcasting\");\n    schema.Arg(\n        \"axis\",\n        \"If set, defines the broadcast dimensions. See doc for details.\");\n    schema.Input(0, \"A\", \"First operand.\");\n    schema.Input(\n        1,\n        \"B\",\n        \"Second operand. With broadcasting can be of smaller size than A. \"\n        \"If broadcasting is disabled it should be of the same size.\");\n    schema.Output(0, \"C\", \"Result, has same dimensions and A and type `bool`\");\n  };\n}\n\n#define CAFFE2_SCHEMA_FOR_BINARY_LOGICAL_OP(name, symbol) \\\n  OPERATOR_SCHEMA(name)                                   \\\n      .NumInputs(2)                                       \\\n      .NumOutputs(1)                                      \\\n      .AllowInplace({{0, 0}})                             \\\n      .FillUsing(LogicalDocGenerator(symbol));            \\\n  SHOULD_NOT_DO_GRADIENT(name)\n\nCAFFE2_SCHEMA_FOR_BINARY_LOGICAL_OP(Or, \"or\");\nCAFFE2_SCHEMA_FOR_BINARY_LOGICAL_OP(And, \"and\");\nCAFFE2_SCHEMA_FOR_BINARY_LOGICAL_OP(Xor, \"xor\");\n\nOPERATOR_SCHEMA(Not)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(Performs element-wise negation.)DOC\")\n    .Input(0, \"X\", \"Input tensor of type `bool`.\")\n    .Output(0, \"Y\", \"Output tensor of type `bool`.\");\nSHOULD_NOT_DO_GRADIENT(Not);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/elementwise_op_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op_test.h\"\n\n#include \"caffe2/core/flags.h\"\n\nCAFFE2_DECLARE_string(caffe_test_root);\n\ntemplate <>\nvoid CopyVector<caffe2::CPUContext, bool>(const int N, const bool* x, bool* y) {\n  memcpy(y, x, N * sizeof(bool));\n}\n\ntemplate <>\nvoid CopyVector<caffe2::CPUContext, int32_t>(\n    const int N,\n    const int32_t* x,\n    int32_t* y) {\n  memcpy(y, x, N * sizeof(int32_t));\n}\n\nTEST(ElementwiseCPUTest, And) {\n  elementwiseAnd<caffe2::CPUContext>();\n}\n\nTEST(ElementwiseTest, Or) {\n  elementwiseOr<caffe2::CPUContext>();\n}\n\nTEST(ElementwiseTest, Xor) {\n  elementwiseXor<caffe2::CPUContext>();\n}\n\nTEST(ElementwiseTest, Not) {\n  elementwiseNot<caffe2::CPUContext>();\n}\n\nTEST(ElementwiseTest, EQ) {\n  elementwiseEQ<caffe2::CPUContext>();\n}\n"
  },
  {
    "path": "caffe2/operators/elementwise_op_test.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_ELEMENTWISE_OP_TEST_H_\n#define CAFFE2_OPERATORS_ELEMENTWISE_OP_TEST_H_\n\n#include <iostream>\n#include <string>\n#include <vector>\n\n#include \"caffe2/operators/elementwise_op.h\"\n#include <gtest/gtest.h>\n\ntemplate <typename Context, typename T>\nvoid CopyVector(const int N, const T* x, T* y);\n\ntemplate <typename Context, typename I_Type, typename O_Type>\nvoid FillTensor(\n    caffe2::Workspace* ws,\n    const std::string& name,\n    const std::vector<caffe2::TIndex>& shape,\n    const std::vector<I_Type>& values) {\n  auto* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<caffe2::Tensor<Context>>();\n  tensor->Resize(shape);\n  auto* mutable_data = tensor->template mutable_data<O_Type>();\n  const O_Type* data = reinterpret_cast<const O_Type*>(values.data());\n  CopyVector<Context, O_Type>(values.size(), data, mutable_data);\n}\n\ntemplate <typename Context>\ncaffe2::OperatorDef CreateOperatorDef() {\n  caffe2::OperatorDef def;\n  return def;\n}\n\ntemplate <typename Context>\ncaffe2::OperatorDef DefineOperator(const std::string& op_type) {\n  caffe2::OperatorDef def = CreateOperatorDef<Context>();\n  def.set_name(\"test\");\n  def.set_type(op_type);\n  def.add_input(\"X\");\n  def.add_input(\"Y\");\n  def.add_output(\"Z\");\n  return def;\n}\n\ntemplate <typename Context>\nvoid elementwiseAnd() {\n  const int N = 4;\n  const int M = 2;\n  caffe2::Workspace ws;\n  auto def = DefineOperator<Context>(\"And\");\n  { // equal size\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"X\", {N}, {true, false, true, false});\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"Y\", {N}, {true, true, false, false});\n    std::unique_ptr<caffe2::OperatorBase> op(caffe2::CreateOperator(def, &ws));\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_TRUE(op->Run());\n    auto* blob = ws.GetBlob(\"Z\");\n    EXPECT_NE(nullptr, blob);\n    caffe2::TensorCPU Z(blob->Get<caffe2::Tensor<Context>>());\n    EXPECT_EQ(Z.size(), N);\n    std::vector<bool> result{true, false, false, false};\n    for (size_t i = 0; i < Z.size(); ++i) {\n      EXPECT_EQ(Z.template data<bool>()[i], result[i]);\n    }\n  }\n  { // broadcast\n    auto* arg = def.add_arg();\n    arg->set_name(\"broadcast\");\n    arg->set_i(1);\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"X\", {M, N}, {true, false, true, false, true, false, true, false});\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"Y\", {N}, {true, true, false, false});\n    std::unique_ptr<caffe2::OperatorBase> op(caffe2::CreateOperator(def, &ws));\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_TRUE(op->Run());\n    auto* blob = ws.GetBlob(\"Z\");\n    EXPECT_NE(nullptr, blob);\n    caffe2::TensorCPU Z(blob->Get<caffe2::Tensor<Context>>());\n    EXPECT_EQ(Z.size(), M * N);\n    std::vector<bool> result{\n        true, false, false, false, true, false, false, false};\n    for (size_t i = 0; i < Z.size(); ++i) {\n      EXPECT_EQ(Z.template data<bool>()[i], result[i]);\n    }\n  }\n}\n\ntemplate <typename Context>\nvoid elementwiseOr() {\n  const int N = 4;\n  const int M = 2;\n  caffe2::Workspace ws;\n  auto def = DefineOperator<Context>(\"Or\");\n  { // equal size\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"X\", {N}, {true, false, true, false});\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"Y\", {N}, {true, true, false, false});\n    std::unique_ptr<caffe2::OperatorBase> op(caffe2::CreateOperator(def, &ws));\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_TRUE(op->Run());\n    auto* blob = ws.GetBlob(\"Z\");\n    EXPECT_NE(nullptr, blob);\n    caffe2::TensorCPU Z(blob->Get<caffe2::Tensor<Context>>());\n    EXPECT_EQ(Z.size(), N);\n    std::vector<bool> result{true, true, true, false};\n    for (size_t i = 0; i < Z.size(); ++i) {\n      EXPECT_EQ(Z.template data<bool>()[i], result[i]);\n    }\n  }\n  { // broadcast\n    auto* arg = def.add_arg();\n    arg->set_name(\"broadcast\");\n    arg->set_i(1);\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"X\", {M, N}, {true, false, true, false, true, false, true, false});\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"Y\", {N}, {true, true, false, false});\n    std::unique_ptr<caffe2::OperatorBase> op(caffe2::CreateOperator(def, &ws));\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_TRUE(op->Run());\n    auto* blob = ws.GetBlob(\"Z\");\n    EXPECT_NE(nullptr, blob);\n    caffe2::TensorCPU Z(blob->Get<caffe2::Tensor<Context>>());\n    EXPECT_EQ(Z.size(), M * N);\n    std::vector<bool> result{true, true, true, false, true, true, true, false};\n    for (size_t i = 0; i < Z.size(); ++i) {\n      EXPECT_EQ(Z.template data<bool>()[i], result[i]);\n    }\n  }\n}\n\ntemplate <typename Context>\nvoid elementwiseXor() {\n  const int N = 4;\n  const int M = 2;\n  caffe2::Workspace ws;\n  auto def = DefineOperator<Context>(\"Xor\");\n  { // equal size\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"X\", {N}, {true, false, true, false});\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"Y\", {N}, {true, true, false, false});\n    std::unique_ptr<caffe2::OperatorBase> op(caffe2::CreateOperator(def, &ws));\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_TRUE(op->Run());\n    auto* blob = ws.GetBlob(\"Z\");\n    EXPECT_NE(nullptr, blob);\n    caffe2::TensorCPU Z(blob->Get<caffe2::Tensor<Context>>());\n    EXPECT_EQ(Z.size(), N);\n    std::vector<bool> result{false, true, true, false};\n    for (size_t i = 0; i < Z.size(); ++i) {\n      EXPECT_EQ(Z.template data<bool>()[i], result[i]);\n    }\n  }\n  { // broadcast\n    auto* arg = def.add_arg();\n    arg->set_name(\"broadcast\");\n    arg->set_i(1);\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"X\", {M, N}, {true, false, true, false, true, false, true, false});\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"Y\", {N}, {true, true, false, false});\n    std::unique_ptr<caffe2::OperatorBase> op(caffe2::CreateOperator(def, &ws));\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_TRUE(op->Run());\n    auto* blob = ws.GetBlob(\"Z\");\n    EXPECT_NE(nullptr, blob);\n    caffe2::TensorCPU Z(blob->Get<caffe2::Tensor<Context>>());\n    EXPECT_EQ(Z.size(), M * N);\n    std::vector<bool> result{\n        false, true, true, false, false, true, true, false};\n    for (size_t i = 0; i < Z.size(); ++i) {\n      EXPECT_EQ(Z.template data<bool>()[i], result[i]);\n    }\n  }\n}\n\ntemplate <typename Context>\nvoid elementwiseNot() {\n  const int N = 2;\n  caffe2::Workspace ws;\n  caffe2::OperatorDef def = CreateOperatorDef<Context>();\n  def.set_name(\"test\");\n  def.set_type(\"Not\");\n  def.add_input(\"X\");\n  def.add_output(\"Y\");\n  FillTensor<Context, uint8_t, bool>(&ws, \"X\", {N}, {true, false});\n  std::unique_ptr<caffe2::OperatorBase> op(caffe2::CreateOperator(def, &ws));\n  EXPECT_NE(nullptr, op.get());\n  EXPECT_TRUE(op->Run());\n  auto* blob = ws.GetBlob(\"Y\");\n  EXPECT_NE(nullptr, blob);\n  caffe2::TensorCPU Y(blob->Get<caffe2::Tensor<Context>>());\n  EXPECT_EQ(Y.size(), N);\n  std::vector<bool> result{false, true};\n  for (size_t i = 0; i < Y.size(); ++i) {\n    EXPECT_EQ(Y.template data<bool>()[i], result[i]);\n  }\n}\n\ntemplate <typename Context>\nvoid elementwiseEQ() {\n  const int N = 4;\n  const int M = 2;\n  caffe2::Workspace ws;\n  auto def = DefineOperator<Context>(\"EQ\");\n  { // equal size\n    FillTensor<Context, int32_t, int32_t>(&ws, \"X\", {N}, {1, 100, 5, -10});\n    FillTensor<Context, int32_t, int32_t>(&ws, \"Y\", {N}, {0, 100, 4, -10});\n    std::unique_ptr<caffe2::OperatorBase> op(caffe2::CreateOperator(def, &ws));\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_TRUE(op->Run());\n    auto* blob = ws.GetBlob(\"Z\");\n    EXPECT_NE(nullptr, blob);\n    caffe2::TensorCPU Z(blob->Get<caffe2::Tensor<Context>>());\n    EXPECT_EQ(Z.size(), N);\n    std::vector<bool> result{false, true, false, true};\n    for (size_t i = 0; i < Z.size(); ++i) {\n      EXPECT_EQ(Z.template data<bool>()[i], result[i]);\n    }\n  }\n  { // boolean\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"X\", {N}, {true, false, false, true});\n    FillTensor<Context, uint8_t, bool>(\n        &ws, \"Y\", {N}, {true, false, true, false});\n    std::unique_ptr<caffe2::OperatorBase> op(caffe2::CreateOperator(def, &ws));\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_TRUE(op->Run());\n    auto* blob = ws.GetBlob(\"Z\");\n    EXPECT_NE(nullptr, blob);\n    caffe2::TensorCPU Z(blob->Get<caffe2::Tensor<Context>>());\n    EXPECT_EQ(Z.size(), N);\n    std::vector<bool> result{true, true, false, false};\n    for (size_t i = 0; i < Z.size(); ++i) {\n      EXPECT_EQ(Z.template data<bool>()[i], result[i]);\n    }\n  }\n  { // broadcast\n    auto* arg = def.add_arg();\n    arg->set_name(\"broadcast\");\n    arg->set_i(1);\n    FillTensor<Context, int32_t, int32_t>(\n        &ws, \"X\", {M, N}, {1, 100, 5, -10, 3, 6, -1000, 33});\n    FillTensor<Context, int32_t, int32_t>(&ws, \"Y\", {N}, {1, 6, -1000, -10});\n    std::unique_ptr<caffe2::OperatorBase> op(caffe2::CreateOperator(def, &ws));\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_TRUE(op->Run());\n    auto* blob = ws.GetBlob(\"Z\");\n    EXPECT_NE(nullptr, blob);\n    caffe2::TensorCPU Z(blob->Get<caffe2::Tensor<Context>>());\n    EXPECT_EQ(Z.size(), M * N);\n    std::vector<bool> result{\n        true, false, false, true, false, true, true, false};\n    for (size_t i = 0; i < Z.size(); ++i) {\n      EXPECT_EQ(Z.template data<bool>()[i], result[i]);\n    }\n  }\n}\n\n#endif // CAFFE2_OPERATORS_ELEMENTWISE_OP_TEST_H_\n"
  },
  {
    "path": "caffe2/operators/elementwise_sub_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\n// See the operations supported here:\n// https://eigen.tuxfamily.org/dox-devel/group__QuickRefPage.html\n#define EIGEN_SUB(x, y) ((x) - (y))\nEIGEN_FUNCTOR(Sub, EIGEN_SUB, NumericTypes, SameTypeAsInput);\n#undef EIGEN_SUB\n}\n"
  },
  {
    "path": "caffe2/operators/elementwise_sum_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/utility_ops.h\"\n\nnamespace caffe2 {\n\nnamespace {\nOpSchema::Cost CostInferenceForSum(\n    const OperatorDef& def,\n    const vector<TensorShape>& in) {\n  struct OpSchema::Cost cost = PointwiseCostInference<1>(def, in);\n  cost.flops *= (in.size() - 1);\n  cost.params_bytes = 0;\n  return cost;\n}\n} // namespace\n\nREGISTER_CPU_OPERATOR(Sum, SumOp<CPUContext>);\n\nOPERATOR_SCHEMA(Sum)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .CostInferenceFunction(CostInferenceForSum)\n    .InputsCanCrossDevices()\n    .IdenticalTypeAndShapeOfInput(0)\n    .SetDoc(R\"DOC(\nElement-wise sum of each of the input tensors. The first input tensor can be\nused in-place as the output tensor, in which case the sum will be done in\nplace and results will be accumulated in input0. All inputs and outputs must\nhave the same shape and data type.\n)DOC\")\n    .Input(0, \"data_0\", \"First of the input tensors. Can be inplace.\")\n    .Output(0, \"sum\", \"Output tensor. Same dimension as inputs.\");\n}\n"
  },
  {
    "path": "caffe2/operators/elu_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elu_op.h\"\n\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool EluOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  // Otherwise inplace gradient and Elu dosen't make sense.\n  CAFFE_ENFORCE_GE(alpha_, 0);\n  Y->ResizeLike(X);\n  const auto* Xdata = X.template data<float>();\n  auto* Ydata = Y->template mutable_data<float>();\n  ConstEigenVectorArrayMap<float> Xvec(Xdata, X.size());\n  EigenVectorArrayMap<float> Yvec(Ydata, Y->size());\n  Yvec = Xvec.cwiseMax(0.f) + (alpha_ * (Xvec.exp() - 1.0f)).cwiseMin(0.f);\n  return true;\n}\n\ntemplate <>\nbool EluGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  DCHECK_GT(Y.size(), 0);\n  DCHECK_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n\n  const float* Ydata = Y.data<float>();\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  ConstEigenVectorArrayMap<float> Yvec(Ydata, Y.size());\n  ConstEigenVectorArrayMap<float> dYvec(dYdata, dY.size());\n  EigenVectorArrayMap<float> dXvec(dXdata, dX->size());\n  dXvec = (Yvec > 0).select(dYvec, dYvec * (Yvec + alpha_));\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(Elu, EluOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(EluGradient, EluGradientOp<float, CPUContext>);\n\n// Input: X, output: Y\nOPERATOR_SCHEMA(Elu)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\n\nElu takes one input data (Tensor<T>) and produces one output data\n(Tensor<T>) where the function `f(x) = alpha * (exp(x) - 1.) for x <\n0`, `f(x) = x for x >= 0`., is applied to the tensor elementwise.\n\n)DOC\")\n    .Input(0, \"X\", \"1D input tensor\")\n    .Output(0, \"Y\", \"1D input tensor\");\n\n// Input: Y, dY, output: dX\nOPERATOR_SCHEMA(EluGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{1, 0}})\n    .SetDoc(R\"DOC(\nEluGradient takes both Y and dY and uses this to update dX according to the\nchain rule and derivatives of the rectified linear function.\n)DOC\");\n\nclass GetEluGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        def_.type() + \"Gradient\",\n        \"\",\n        vector<string>{O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Elu, GetEluGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/elu_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elu_op.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\nnamespace {\n__global__ void\nelu_kernel(const int N, const float alpha, const float* x, float* y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    if (x[i] > 0) {\n      y[i] = x[i];\n    } else {\n      y[i] = alpha * (__expf(x[i]) - 1);\n    }\n  }\n}\n\n__global__ void elu_gradient_kernel(\n    const int N,\n    const float alpha,\n    const float* y,\n    const float* dy,\n    float* dx) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    if (y[i] > 0) {\n      dx[i] = dy[i];\n    } else {\n      dx[i] = dy[i] * (y[i] + alpha);\n    }\n  }\n}\n} // namespace\n\ntemplate <>\nbool EluOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  // Otherwise inplace gradient and Elu dosen't make sense.\n  CAFFE_ENFORCE_GE(alpha_, 0);\n  Y->ResizeLike(X);\n  const auto* Xdata = X.data<float>();\n  auto* Ydata = Y->mutable_data<float>();\n  elu_kernel<<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(X.size(), alpha_, Xdata, Ydata);\n  return true;\n}\n\ntemplate <>\nbool EluGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  DCHECK_GT(Y.size(), 0);\n  DCHECK_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n\n  const float* Ydata = Y.data<float>();\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  elu_gradient_kernel<<<\n      CAFFE_GET_BLOCKS(Y.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(Y.size(), alpha_, Ydata, dYdata, dXdata);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Elu, EluOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(EluGradient, EluGradientOp<float, CUDAContext>);\n}\n"
  },
  {
    "path": "caffe2/operators/elu_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass EluOp final : public Operator<Context> {\n public:\n  EluOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        alpha_(OperatorBase::GetSingleArgument<float>(\"alpha\", 1.0)) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  T alpha_;\n};\n\ntemplate <typename T, class Context>\nclass EluGradientOp final : public Operator<Context> {\n public:\n  EluGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        alpha_(OperatorBase::GetSingleArgument<float>(\"alpha\", 1.0)) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  T alpha_;\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/exp_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nstruct ExpCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* device_context) {\n    math::Exp<T, CPUContext>(n, x, y, device_context);\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    Exp,\n    UnaryElementwiseOp<TensorTypes<float>, CPUContext, ExpCPUFunctor>);\n\nOPERATOR_SCHEMA(Exp)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nCalculates the exponential of the given input tensor, element-wise. This\noperation can be done in an in-place fashion too, by providing the same input\nand output blobs.\n)DOC\")\n    .Input(0, \"input\", \"Input tensor\")\n    .Output(\n        0,\n        \"output\",\n        \"The exponential of the input tensor computed \"\n        \"element-wise\");\n\nclass GetExpGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"Mul\",\n        \"\",\n        std::vector<string>{O(0), GO(0)},\n        std::vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Exp, GetExpGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/exp_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cmath>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void ExpKernel(const int N, const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = __expf(X[i]);\n  }\n}\n\nstruct ExpCUDAFunctor {\n  template <typename T>\n  inline void operator()(const int n, const T* x,\n                         T* y, CUDAContext* device_context) {\n    ExpKernel<T><<<CAFFE_GET_BLOCKS(n), CAFFE_CUDA_NUM_THREADS,\n                    0, device_context->cuda_stream()>>>(n, x, y);\n    return;\n  }\n  inline bool InplaceAllowed() {\n    return true;\n  }\n};\n\nREGISTER_CUDA_OPERATOR(\n    Exp, UnaryElementwiseOp<TensorTypes<float>, CUDAContext, ExpCUDAFunctor>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/expand_squeeze_dims_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/expand_squeeze_dims_op.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(ExpandDims, ExpandDimsOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Squeeze, SqueezeOp<CPUContext>);\n\nOPERATOR_SCHEMA(ExpandDims)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      ArgumentHelper helper(def);\n      auto dims = helper.template GetRepeatedArgument<int>(\"dims\");\n      auto originalSize = dims.size();\n      CAFFE_ENFORCE(originalSize > 0, \"Parameter `dims` must be provided.\");\n\n      std::sort(dims.begin(), dims.end());\n      dims.erase(std::unique(dims.begin(), dims.end()), dims.end());\n      if (dims.size() < originalSize) {\n        LOG(WARNING) << \"Parameter `dims` has repeated dimensions.\";\n      }\n\n      CAFFE_ENFORCE(dims.front() >= 0, \"Dimension ids must be non-negative.\");\n      CAFFE_ENFORCE_GE(\n          in[0].dims_size() + dims.size(),\n          dims.back() + 1,\n          \"Input needs at least \",\n          (1 + dims.back() - dims.size()),\n          \" dimensions given `dims`.\");\n\n      vector<TensorShape> out(1);\n\n      int cur_pos = 0;\n      int idx = 0;\n      for (const auto new_dim : dims) {\n        for (int i = cur_pos; i < new_dim; i++) {\n          out[0].add_dims(in[0].dims(idx++));\n        }\n        out[0].add_dims(1);\n        cur_pos = new_dim + 1;\n      }\n      for (; idx < in[0].dims_size(); idx++) {\n        out[0].add_dims(in[0].dims(idx));\n      }\n      out[0].set_data_type(in[0].data_type());\n      return out;\n    })\n    .SetDoc(R\"DOC(\nInsert single-dimensional entries to the shape of a tensor.\nTakes one required argument `dims`, a list of dimensions that will be inserted.\nDimension indices in `dims` are as seen in the output tensor. For example:\n\n  Given a tensor such that tensor.Shape() = [3, 4, 5], then\n  ExpandDims(tensor, dims=[0, 4]).Shape() == [1, 3, 4, 5, 1])\n\nIf the same blob is provided in input and output, the operation is copy-free.\n)DOC\")\n    .Input(0, \"data\", \"Original tensor\")\n    .Output(0, \"expanded\", \"Reshaped tensor with same data as input.\");\n\nOPERATOR_SCHEMA(Squeeze)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nRemove single-dimensional entries from the shape of a tensor.\nTakes a parameter `dims` with a list of dimension to squeeze.\nIf the same blob is provided in input and output, the operation is copy-free.\nThis is the exact inverse operation of ExpandDims given the same `dims` arg.\n)DOC\")\n    .Input(0, \"data\", \"Tensors with at least max(dims) dimensions.\")\n    .Output(0, \"squeezed\", \"Reshaped tensor with same data as input.\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      ArgumentHelper helper(def);\n      auto dims = helper.template GetRepeatedArgument<int>(\"dims\");\n      auto originalSize = dims.size();\n      std::sort(dims.begin(), dims.end());\n      dims.erase(std::unique(dims.begin(), dims.end()), dims.end());\n      if (dims.size() < originalSize) {\n        LOG(WARNING) << \"Parameter `dims` has repeated dimensions.\";\n      }\n      CAFFE_ENFORCE(dims.front() >= 0, \"Dimension ids must be non-negative.\");\n\n      vector<TensorShape> out(1);\n      std::vector<int> newDims =\n          SqueezeOp<CPUContext>::ComputeDims(GetDimsVector(in[0]), dims);\n      out[0] = CreateTensorShape(newDims, in[0].data_type());\n      return out;\n    });\n\nclass GetSqueezeGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"ExpandDims\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Squeeze, GetSqueezeGradient);\n\nclass GetExpandDimsGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"Squeeze\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(ExpandDims, GetExpandDimsGradient);\n}\n"
  },
  {
    "path": "caffe2/operators/expand_squeeze_dims_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_EXPAND_SQUEEZE_DIMS_OP_H_\n#define CAFFE2_OPERATORS_EXPAND_SQUEEZE_DIMS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass ExpandDimsOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ExpandDimsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        dims_(OperatorBase::GetRepeatedArgument<int>(\"dims\")) {\n    auto originalSize = dims_.size();\n    CAFFE_ENFORCE(originalSize > 0, \"Parameter `dims` must be provided.\");\n    std::sort(dims_.begin(), dims_.end());\n    dims_.erase(std::unique(dims_.begin(), dims_.end()), dims_.end());\n    if (dims_.size() < originalSize) {\n      LOG(WARNING) << \"Parameter `dims` has repeated dimensions.\";\n    }\n    CAFFE_ENFORCE(dims_.front() >= 0, \"Dimension ids must be non-negative.\");\n  }\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = Output(0);\n    output->CopyFrom(input, &context_);\n    if (dims_.empty()) {\n      return true;\n    }\n\n    auto newDims = input.dims();\n    CAFFE_ENFORCE_GE(\n        input.dims().size() + dims_.size(),\n        dims_.back() + 1,\n        \"Input needs at least \",\n        (1 + dims_.back() - dims_.size()),\n        \" dimensions given `dims`.\");\n    for (const auto dim : dims_) {\n      newDims.insert(newDims.begin() + dim, 1);\n    }\n    output->Reshape(newDims);\n    return true;\n  }\n\n private:\n  vector<int> dims_;\n};\n\ntemplate <class Context>\nclass SqueezeOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SqueezeOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        dims_(OperatorBase::GetRepeatedArgument<int>(\"dims\")) {\n    auto originalSize = dims_.size();\n    CAFFE_ENFORCE(originalSize > 0, \"Parameter `dims` must be provided.\");\n\n    std::sort(dims_.begin(), dims_.end());\n    dims_.erase(std::unique(dims_.begin(), dims_.end()), dims_.end());\n    if (dims_.size() < originalSize) {\n      LOG(WARNING) << \"Parameter `dims` has repeated dimensions.\";\n    }\n    CAFFE_ENFORCE(dims_.front() >= 0, \"Dimension ids must be non-negative.\");\n  }\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = Output(0);\n    output->CopyFrom(input, &context_);\n\n    CAFFE_ENFORCE_GT(\n        input.ndim(),\n        dims_.back(),\n        \"Input needs at least \",\n        (dims_.back() + 1),\n        \" dimensions.\");\n\n    std::vector<int> newDims = ComputeDims(input.dims(), dims_);\n    output->Reshape(newDims);\n    return true;\n  }\n\n  static std::vector<int> ComputeDims(\n      std::vector<TIndex> inputDims,\n      std::vector<int> dims) {\n    int j = 0;\n    std::vector<int> newDims;\n    for (int i = 0; i < inputDims.size(); ++i) {\n      if (j < dims.size() && dims[j] == i) {\n        CAFFE_ENFORCE_EQ(\n            inputDims[i],\n            1,\n            \"Dimension \",\n            i,\n            \" of input must be 1\",\n            \" instead of \",\n            inputDims[i],\n            \".\");\n        ++j;\n        continue;\n      }\n      newDims.push_back(inputDims.at(i));\n    }\n    return newDims;\n  }\n\n private:\n  vector<int> dims_;\n\n public:\n  DISABLE_COPY_AND_ASSIGN(SqueezeOp);\n};\n} // namespace caffe2\n#endif // CAFFE2_OPERATORS_EXPAND_SQUEEZE_DIMS_OP_H_\n"
  },
  {
    "path": "caffe2/operators/expand_squeeze_dims_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/expand_squeeze_dims_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(Squeeze, SqueezeOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(ExpandDims, ExpandDimsOp<CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/extend_tensor_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <atomic>\n#include <mutex>\n#include <unordered_map>\n#include <vector>\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <class Context>\nclass ExtendTensorOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ExtendTensorOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        growthPct_(OperatorBase::GetSingleArgument<int>(\"growthPct\", 40)) {}\n\n  bool RunOnDevice() override {\n    auto& old_tensor = Input(0);\n    auto& indices = Input(1);\n    auto* new_tensor = Output(0);\n    CAFFE_ENFORCE(indices.ndim() >= 1);\n    CAFFE_ENFORCE(\n        &old_tensor == new_tensor, \"First argument must be in-place.\");\n    CAFFE_ENFORCE(new_tensor->ndim() == indices.ndim());\n    CAFFE_ENFORCE(indices.ndim() == new_tensor->ndim());\n\n    auto oldSize = new_tensor->size();\n    auto maxElem = 1 +\n        *(std::max_element(\n            indices.template data<int>(),\n            indices.template data<int>() + indices.size()));\n\n    auto extendSize = (TIndex)maxElem - oldSize;\n    if (extendSize > 0) {\n      new_tensor->Extend(extendSize, growthPct_, &context_);\n      if (!new_tensor->meta().ctor()) {\n        auto oldSizeBytes = oldSize * new_tensor->meta().itemsize();\n        auto* dst = (char*)new_tensor->raw_mutable_data() + oldSizeBytes;\n        math::Set<char, Context>(\n            new_tensor->nbytes() - oldSizeBytes, 0, dst, &context_);\n      }\n    }\n    return true;\n  }\n\n  int growthPct_;\n};\n\nREGISTER_CPU_OPERATOR(ExtendTensor, ExtendTensorOp<CPUContext>);\n\nOPERATOR_SCHEMA(ExtendTensor)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .EnforceInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nExtend input 0 if necessary based on max element in input 1.\nInput 0 must be the same as output, that is, it is required to be in-place.\nInput 0 may have to be re-allocated in order for accommodate to the new size.\nCurrently, an exponential growth ratio is used in order to ensure amortized\nconstant time complexity.\nAll except the outer-most dimension must be the same between input 0 and 1.\n)DOC\")\n    .Input(0, \"tensor\", \"The tensor to be extended.\")\n    .Input(\n        1,\n        \"new_indices\",\n        \"The size of tensor will be extended based on max element in \"\n        \"new_indices.\")\n    .Output(\n        0,\n        \"extended_tensor\",\n        \"Same as input 0, representing the mutated tensor.\");\n}\n} // namespace\n"
  },
  {
    "path": "caffe2/operators/feed_blob_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/feed_blob_op.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(FeedBlob, FeedBlobOp<CPUContext>);\nSHOULD_NOT_DO_GRADIENT(FeedBlob);\n\nOPERATOR_SCHEMA(FeedBlob)\n    .NumInputs(0, 0)\n    .NumOutputs(1, 1)\n    .SetDoc(R\"DOC(\nFeedBlobs the content of the blobs. The input and output blobs should be\none-to-one inplace.)DOC\")\n    .Arg(\n        \"value\",\n        \"(string) if provided then we will use this string as the value for the\"\n        \"provided output tensor\");\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/feed_blob_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FEED_BLOB_OP_H_\n#define CAFFE2_OPERATORS_FEED_BLOB_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass FeedBlobOp : public Operator<Context> {\n public:\n  FeedBlobOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {\n    CAFFE_ENFORCE(\n        OperatorBase::HasSingleArgumentOfType<string>(\"value\"),\n        \"value argument must exist and be passed as a string\");\n    value_ = OperatorBase::GetSingleArgument<string>(\"value\", \"\");\n  }\n\n  bool RunOnDevice() override {\n    *OperatorBase::Output<std::string>(0) = value_;\n    return true;\n  }\n\n private:\n  std::string value_;\n};\n\n} // namespace caffe2\n\n#endif\n"
  },
  {
    "path": "caffe2/operators/filler_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/filler_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool RangeFillOp<float, CPUContext>::Fill(\n    TensorCPU* output) {\n  float* data = output->mutable_data<float>();\n  for (int i = 0; i < output->size(); ++i) {\n    data[i] = i;\n  }\n  return true;\n}\n\ntemplate <>\ntemplate <typename T>\nbool DiagonalFillOp<CPUContext>::FillWithType(TensorCPU* output) {\n  VerifyOutputShape(output);\n  T value = OperatorBase::GetSingleArgument<T>(\"value\", 0);\n  auto* data = output->template mutable_data<T>();\n  // first fill everything with 0\n  math::Set<T, CPUContext>(output->size(), T(0), data, &context_);\n  // then calculate step size for diagonal\n  auto step = GetStepSize(output);\n  for (TIndex i = 0; i < output->size(); i += step) {\n    math::Set<T, CPUContext>(1, value, data, &context_);\n    data += step;\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(UniformFill, UniformFillOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(UniformIntFill, UniformFillOp<int, CPUContext>);\nREGISTER_CPU_OPERATOR(UniqueUniformFill, UniqueUniformFillOp<CPUContext>);\nREGISTER_CPU_OPERATOR(ConstantFill, ConstantFillOp<CPUContext>);\nREGISTER_CPU_OPERATOR(DiagonalFill, DiagonalFillOp<CPUContext>);\nREGISTER_CPU_OPERATOR(GaussianFill, GaussianFillOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(XavierFill, XavierFillOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(MSRAFill, MSRAFillOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(RangeFill, RangeFillOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(LengthsRangeFill, LengthsRangeFillOp<CPUContext>);\n\nOPERATOR_SCHEMA(ConstantFill)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(FillerTensorInference<>)\n    .SetDoc(R\"DOC(\nThe operator fills the elements of the output tensor with a constant value\nspecified by the 'value' argument.\n\nThe data type is specified by the 'dtype' argument. The 'dtype' argument must\nbe one of the data types specified in the 'DataType' enum field in the\nTensorProto message. If the 'dtype' argument is not provided, the data type of\n'value' is used.\n\nThe output tensor shape is specified by the 'shape' argument. If the number of\ninput is 1, the shape will be identical to that of the input at run time with\noptional additional dimensions appended at the end as specified by 'extra_shape'\nargument. In that case the 'shape' argument should not be set.\n\nIf input_as_shape is set to true, then the input should be a 1D tensor\ncontaining the desired output shape (the dimensions specified in extra_shape\nwill also be appended)\n\nNOTE: Currently, it supports data type of float, int32, int64, and bool.\n)DOC\")\n    .Arg(\"value\", \"The value for the elements of the output tensor.\")\n    .Arg(\n        \"dtype\",\n        \"The data type for the elements of the output tensor.\"\n        \"Strictly must be one of the types from DataType enum in TensorProto.\")\n    .Arg(\n        \"shape\",\n        \"The shape of the output tensor.\"\n        \"Cannot set the shape argument and pass in an input at the same time.\")\n    .Arg(\n        \"extra_shape\",\n        \"The additional dimensions appended at the end of the shape indicated\"\n        \"by the input blob.\"\n        \"Cannot set the extra_shape argument when there is no input blob.\")\n    .Arg(\n        \"input_as_shape\",\n        \"1D tensor containing the desired output shape.  First input must be in CPU context.\")\n    .Input(0, \"input\", \"Input tensor (optional) to provide shape information.\")\n    .Output(\n        0,\n        \"output\",\n        \"Output tensor of constant values specified by 'value'\"\n        \"argument and its type is specified by the 'dtype' argument\");\n\nOPERATOR_SCHEMA(DiagonalFill)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(FillerTensorInference<>)\n    .SetDoc(R\"DOC(\nThe operator fills the diagonal elements of the output tensor (>= 2D)\nwith a constant value specified by the 'value' argument, and others 0. If\nnumber of dimensions of the output tensor is greater than 2, all dimensions\nmust be equal.\n\nThe data type is specified by the 'dtype' argument. The 'dtype' argument must\nbe one of the data types specified in the 'DataType' enum field in the\nTensorProto message. If the 'dtype' argument is not provided, the data type of\n'value' is used.\n\nThe output tensor shape is specified by the 'shape' argument. If the number of\ninput is 1, the shape will be identical to that of the input at run time with\noptional additional dimensions appended at the end as specified by 'extra_shape'\nargument. In that case the 'shape' argument should not be set.\n\nIf input_as_shape is set to true, then the input should be a 1D tensor\ncontaining the desired output shape (the dimensions specified in extra_shape\nwill also be appended)\n\nNOTE: Currently, it supports data type of float, int32, int64, and bool.\n)DOC\")\n    .Arg(\"value\", \"The value for the elements of the output tensor.\")\n    .Arg(\n        \"dtype\",\n        \"The data type for the elements of the output tensor.\"\n        \"Strictly must be one of the types from DataType enum in TensorProto.\")\n    .Arg(\n        \"shape\",\n        \"The shape of the output tensor.\"\n        \"Cannot set the shape argument and pass in an input at the same time.\")\n    .Arg(\n        \"extra_shape\",\n        \"The additional dimensions appended at the end of the shape indicated\"\n        \"by the input blob.\"\n        \"Cannot set the extra_shape argument when there is no input blob.\")\n    .Arg(\"input_as_shape\", \"1D tensor containing the desired output shape\")\n    .Input(0, \"input\", \"Input tensor (optional) to provide shape information.\")\n    .Output(\n        0,\n        \"output\",\n        \"Output tensor\"\n        \"argument and its type is specified by the 'dtype' argument\");\n\nOPERATOR_SCHEMA(UniformFill)\n    .NumInputs({0, 1, 3})\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(FillerTensorInference<>)\n    .SetDoc(R\"DOC(\nFill the output tensor with FLOAT samples from uniform distribution [min, max].\n\nThe range can be defined either by arguments or input blobs. If the range is\ngiven by input blobs, you also need to give the shape as input. When the range\nis given as arguments, this operator enforces min <= max. When the range is\ngiven as inputs, the constraint is not enforced. When MAX < MIN, the first\ndimension of the output is set to 0. This behavior is allowed so that\ndynamically sampling indices into a dynamically sized tensor is possible.\n\nThe shape of the output can be given as argument or input.\n)DOC\")\n    .Arg(\"min\", \"minimum value, inclusive\")\n    .Arg(\"max\", \"maximum value, inclusive\")\n    .Arg(\"shape\", \"shape of the output, do not set when input_as_shape=1\")\n    .Arg(\n        \"input_as_shape\",\n        \"set to 1 to use the first input as shape. First input must be in CPU context.\")\n    .Input(\n        0,\n        \"SHAPE\",\n        \"1-D tensor of the shape of the output, \"\n        \"must be used with input_as_shape\")\n    .Input(1, \"MIN\", \"scalar blob of mininum value\")\n    .Input(2, \"MAX\", \"scalar blob of maximum value\")\n    .Output(0, \"OUTPUT\", \"output tensor\");\nOPERATOR_SCHEMA(UniformIntFill)\n    .NumInputs({0, 1, 3})\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(FillerTensorInference<>)\n    .SetDoc(R\"DOC(\nLike `UniformFill` but fill with INT32.\n)DOC\");\nOPERATOR_SCHEMA(UniqueUniformFill)\n    .NumInputs(0, 2)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(FillerTensorInference<>)\n    .SetDoc(R\"DOC(\nFill the output tensor with uniform samples between min and max (inclusive).\nIf the second input is given, its elements will be excluded from uniform\nsampling. Using the second input will require you to provide shape via the first\ninput.\n)DOC\")\n    .Arg(\"min\", \"Minimum value, inclusive\")\n    .Arg(\"max\", \"Maximum value, inclusive\")\n    .Arg(\n        \"dtype\",\n        \"The data type for the elements of the output tensor.\"\n        \"Strictly must be one of the types from DataType enum in TensorProto.\"\n        \"This only supports INT32 and INT64 now. If not set, assume INT32\")\n    .Arg(\n        \"shape\",\n        \"The shape of the output tensor.\"\n        \"Cannot set the shape argument and pass in an input at the same time.\")\n    .Arg(\n        \"extra_shape\",\n        \"The additional dimensions appended at the end of the shape indicated\"\n        \"by the input blob. \"\n        \"Cannot set the extra_shape argument when there is no input blob.\")\n    .Arg(\n        \"input_as_shape\",\n        \"1D tensor containing the desired output shape. First input must be in CPU context.\")\n    .Input(0, \"input\", \"Input tensor to provide shape information\")\n    .Input(\n        1,\n        \"avoid\",\n        \"(optional) Avoid elements in this tensor. Elements must be unique.\")\n    .Output(0, \"output\", \"Output tensor of unique uniform samples\");\nOPERATOR_SCHEMA(GaussianFill)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(FillerTensorInference<>);\nOPERATOR_SCHEMA(XavierFill)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(FillerTensorInference<>);\nOPERATOR_SCHEMA(MSRAFill)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(FillerTensorInference<>);\nOPERATOR_SCHEMA(RangeFill)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(FillerTensorInference<>);\n\nNO_GRADIENT(UniformFill);\nNO_GRADIENT(UniformIntFill);\nNO_GRADIENT(UniqueUniformFill);\nNO_GRADIENT(ConstantFill);\nNO_GRADIENT(DiagonalFill);\nNO_GRADIENT(GaussianFill);\nNO_GRADIENT(XavierFill);\nNO_GRADIENT(MSRAFill);\nNO_GRADIENT(RangeFill);\n\nOPERATOR_SCHEMA(LengthsRangeFill)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nConvert a length vector to a range sequence. For example, input=[4,3,1], the\noutput would be [0,1,2,3,0,1,2,0].\n)DOC\")\n    .Input(0, \"lengths\", \"1D tensor of int32 or int64 segment lengths.\")\n    .Output(\n        0,\n        \"range_sequence\",\n        \"1D tensor whose size is the sum of `lengths`\");\nNO_GRADIENT(LengthsRangeFill);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/filler_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cmath>\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/filler_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n__global__ void FillRangeKernel(const int n, float* data) {\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    data[index] = index;\n  }\n}\n\ntemplate <typename T>\n__global__ void FillDiagonalKernel(\n    const int num_diagonal_elements,\n    const TIndex step_size,\n    const T value,\n    T* data) {\n  CUDA_1D_KERNEL_LOOP(index, num_diagonal_elements) {\n    data[index * step_size] = value;\n  }\n}\n}\n\ntemplate <>\nbool RangeFillOp<float, CUDAContext>::Fill(TensorCUDA* output) {\n  int N = output->size();\n  FillRangeKernel<<<\n      CAFFE_GET_BLOCKS(N),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, output->mutable_data<float>());\n  return true;\n}\n\ntemplate <>\ntemplate <typename T>\nbool DiagonalFillOp<CUDAContext>::FillWithType(TensorCUDA* output) {\n  VerifyOutputShape(output);\n  auto* data = output->template mutable_data<T>();\n  int size = output->size();\n  // first fill everything with 0\n  math::Set<T, CUDAContext>(size, T(0), data, &context_);\n\n  T value = OperatorBase::GetSingleArgument<T>(\"value\", 0);\n  TIndex step_size = GetStepSize(output);\n  int num_diagonal_elements = ceil((float)size / step_size);\n\n  FillDiagonalKernel<<<\n      CAFFE_GET_BLOCKS(num_diagonal_elements),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(num_diagonal_elements, step_size, value, data);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(UniformFill, UniformFillOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(UniformIntFill, UniformFillOp<int, CUDAContext>);\nREGISTER_CUDA_OPERATOR(ConstantFill, ConstantFillOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(DiagonalFill, DiagonalFillOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(GaussianFill, GaussianFillOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(XavierFill, XavierFillOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(MSRAFill, MSRAFillOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(RangeFill, RangeFillOp<float, CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/filler_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FILLER_OP_H_\n#define CAFFE2_OPERATORS_FILLER_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n// FillerOp takes in either zero or one input.\n//\n// If the number of input is 1, the shape will be identical to that of the input\n// at run time with optional additional dimensions appended at the end as\n// specified by \"extra_shape\" argument. In that case the \"shape\" parameter\n// should not be set.\n//\n// If the number of inputs is 0, the full shape must be provided via \"shape\"\n// argument\ntemplate <class Context>\nclass FillerOp : public Operator<Context> {\n public:\n  FillerOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        shape_(ToVectorTIndex(OperatorBase::GetRepeatedArgument<int>(\"shape\"))),\n        extra_shape_(ToVectorTIndex(\n            OperatorBase::GetRepeatedArgument<int>(\"extra_shape\"))),\n        input_as_shape_(\n            OperatorBase::GetSingleArgument<bool>(\"input_as_shape\", false)) {\n    if (InputSize()) {\n      if (shape_.size() != 0) {\n        CAFFE_THROW(\n            \"Cannot set the shape argument and pass in an input at \"\n            \"the same time\");\n      }\n    } else {\n      if (!extra_shape_.empty()) {\n        CAFFE_THROW(\"Cannot set extra_shape when there is no input\");\n      }\n      if (input_as_shape_) {\n        CAFFE_THROW(\"An input must be given if input_as_shape is true\");\n      }\n      if (shape_.size() == 0 &&\n          OperatorBase::HasSingleArgumentOfType<int>(\"shape\")) {\n        CAFFE_THROW(\"Fill 'shape' argument was a scalar, list expected\");\n      }\n    }\n  }\n\n  virtual ~FillerOp() {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    auto* output = Operator<Context>::Output(0);\n    if (InputSize()) {\n      auto shape = vector<TIndex>{};\n      if (input_as_shape_) {\n        // Shape input must be in CPU context\n        auto& input = OperatorBase::Input<Tensor<CPUContext>>(0);\n        CAFFE_ENFORCE_EQ(\n            input.ndim(),\n            1,\n            \"When input_as_shape is true, the input must be a 1D tensor of \"\n            \"data type TIndex\");\n        auto* shape_data = input.template data<TIndex>();\n        shape.insert(shape.end(), shape_data, shape_data + input.dim32(0));\n      } else {\n        auto& input = Input(0);\n        shape.insert(shape.end(), input.dims().begin(), input.dims().end());\n      }\n      shape.insert(shape.end(), extra_shape_.begin(), extra_shape_.end());\n      output->Resize(shape);\n    } else {\n      output->Resize(shape_);\n    }\n    return Fill(output);\n  }\n\n  virtual bool Fill(Tensor<Context>* output) = 0;\n\n protected:\n  vector<TIndex> shape_;\n  vector<TIndex> extra_shape_;\n  bool input_as_shape_;\n};\n\ntemplate <typename T, class Context>\nclass UniformFillOp final : public FillerOp<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  UniformFillOp(const OperatorDef& operator_def, Workspace* ws)\n      : FillerOp<Context>(operator_def, ws),\n        min_(OperatorBase::template GetSingleArgument<T>(\"min\", 0)),\n        max_(OperatorBase::template GetSingleArgument<T>(\"max\", 1)) {\n    if (InputSize() == 3) {\n      CAFFE_ENFORCE(\n          !OperatorBase::HasSingleArgumentOfType<T>(\"min\"),\n          \"Cannot set both min arg and min input blob\");\n      CAFFE_ENFORCE(\n          !OperatorBase::HasSingleArgumentOfType<T>(\"max\"),\n          \"Cannot set both max arg and max input blob\");\n    } else {\n      CAFFE_ENFORCE_LT(\n          min_, max_, \"Max value should be bigger than min value.\");\n    }\n  }\n\n  bool Fill(Tensor<Context>* output) override {\n    T min = min_;\n    T max = max_;\n    if (InputSize() == 3) {\n      CAFFE_ENFORCE_EQ(1, Input(1).size(), \"min blob must be scalar\");\n      CAFFE_ENFORCE_EQ(1, Input(2).size(), \"max blob must be scalar\");\n      min = *Input(1).template data<T>();\n      max = *Input(2).template data<T>();\n      if (min > max) {\n        auto shape = output->dims();\n        shape[0] = 0;\n        output->Resize(shape);\n        output->template mutable_data<T>();\n        return true;\n      }\n    }\n    math::RandUniform<T, Context>(\n        output->size(),\n        min,\n        max,\n        output->template mutable_data<T>(),\n        &context_);\n    return true;\n  }\n\n private:\n  T min_;\n  T max_;\n};\n\ntemplate <class Context>\nclass UniqueUniformFillOp final : public FillerOp<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  UniqueUniformFillOp(const OperatorDef& operator_def, Workspace* ws)\n      : FillerOp<Context>(operator_def, ws) {\n    TensorProto_DataType dtype =\n        static_cast<TensorProto_DataType>(OperatorBase::GetSingleArgument<int>(\n            \"dtype\", TensorProto_DataType_INT32));\n\n    switch (dtype) {\n      case TensorProto_DataType_INT32:\n        CheckRange<int>();\n        body_ = &UniqueUniformFillOp::FillWithType<int>;\n        break;\n      case TensorProto_DataType_INT64:\n        CheckRange<int64_t>();\n        body_ = &UniqueUniformFillOp::FillWithType<int64_t>;\n        break;\n      case TensorProto_DataType_UNDEFINED:\n        CAFFE_THROW(\n            \"UniqueUniformFill op cannot have undefined 'dtype' argument\");\n      // break;\n      default:\n        CAFFE_THROW(\"Unexpected 'dtype' argument value: \", dtype);\n    }\n  }\n\n  bool Fill(Tensor<Context>* output) override {\n    return (this->*body_)(output);\n  }\n\n private:\n  template <typename T>\n  void CheckRange() {\n    CAFFE_ENFORCE(OperatorBase::HasSingleArgumentOfType<T>(\"min\"));\n    CAFFE_ENFORCE(OperatorBase::HasSingleArgumentOfType<T>(\"max\"));\n    CAFFE_ENFORCE_LT(\n        OperatorBase::GetSingleArgument<T>(\"min\", 0),\n        OperatorBase::GetSingleArgument<T>(\"max\", 0),\n        \"Max value should be bigger than min value.\");\n  }\n\n  template <typename T>\n  bool FillWithType(Tensor<Context>* output) {\n    T min = OperatorBase::GetSingleArgument<T>(\"min\", 0);\n    T max = OperatorBase::GetSingleArgument<T>(\"max\", 0);\n\n    const T* avoid_data = nullptr;\n    size_t avoid_size = 0;\n    if (InputSize() >= 2) {\n      auto& avoid = Input(1);\n      avoid_data = avoid.template data<T>();\n      avoid_size = avoid.size();\n    }\n    math::RandUniformUnique<T, Context>(\n        output->size(),\n        min,\n        max,\n        output->template mutable_data<T>(),\n        avoid_size,\n        avoid_data,\n        &context_);\n    return true;\n  }\n\n  bool (UniqueUniformFillOp::*body_)(Tensor<Context>* output);\n};\n\ntemplate <class Context>\nclass ConstantFillOp final : public FillerOp<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ConstantFillOp(const OperatorDef& operator_def, Workspace* ws)\n      : FillerOp<Context>(operator_def, ws) {\n    TensorProto_DataType dtype =\n        static_cast<TensorProto_DataType>(OperatorBase::GetSingleArgument<int>(\n            \"dtype\", TensorProto_DataType_FLOAT));\n\n    if (!OperatorBase::HasArgument(\"dtype\") &&\n        OperatorBase::HasArgument(\"value\")) {\n      // If 'dtype' is not provided, infer type based on the type of 'value'\n      // Currently, single argument contains either float, int64 or bytes\n      if (OperatorBase::HasSingleArgumentOfType<float>(\"value\")) {\n        dtype = TensorProto_DataType_FLOAT;\n      } else if (OperatorBase::HasSingleArgumentOfType<int64_t>(\"value\")) {\n        dtype = TensorProto_DataType_INT64;\n      } else {\n        CAFFE_THROW(\"Argument 'value' is of unexpected type\");\n      }\n      VLOG(1) << \"Argument 'dtype' is not provided. Assume the data type is \"\n              << \"the same as that of argument 'value': \" << dtype;\n    }\n\n    switch (dtype) {\n      case TensorProto_DataType_FLOAT:\n        body_ = &ConstantFillOp::FillWithType<float>;\n        break;\n      case TensorProto_DataType_DOUBLE:\n        body_ = &ConstantFillOp::FillWithType<double>;\n        break;\n      case TensorProto_DataType_BOOL:\n        body_ = &ConstantFillOp::FillWithType<bool>;\n        break;\n      case TensorProto_DataType_INT8:\n        body_ = &ConstantFillOp::FillWithType<int8_t>;\n        break;\n      case TensorProto_DataType_INT16:\n        body_ = &ConstantFillOp::FillWithType<int16_t>;\n        break;\n      case TensorProto_DataType_INT32:\n        body_ = &ConstantFillOp::FillWithType<int>;\n        break;\n      case TensorProto_DataType_INT64:\n        body_ = &ConstantFillOp::FillWithType<int64_t>;\n        break;\n      case TensorProto_DataType_UINT8:\n        body_ = &ConstantFillOp::FillWithType<uint8_t>;\n        break;\n      case TensorProto_DataType_UINT16:\n        body_ = &ConstantFillOp::FillWithType<uint16_t>;\n        break;\n      case TensorProto_DataType_STRING:\n        body_ = &ConstantFillOp::FillWithString;\n        break;\n      case TensorProto_DataType_UNDEFINED:\n        CAFFE_THROW(\"ConstantFill op cannot have undefined 'dtype' argument\");\n      // break;\n      default:\n        CAFFE_THROW(\"Unexpected 'dtype' argument value: \", dtype);\n    }\n  }\n\n  bool Fill(Tensor<Context>* output) override {\n    return (this->*body_)(output);\n  }\n\n  template <typename T>\n  bool FillWithType(Tensor<Context>* output) {\n    T value = OperatorBase::GetSingleArgument<T>(\"value\", 0);\n    auto* data = output->template mutable_data<T>();\n    if (output->size()) {\n      math::Set<T, Context>(output->size(), value, data, &context_);\n    }\n    return true;\n  }\n\n  bool FillWithString(Tensor<Context>* output) {\n    auto value = OperatorBase::GetSingleArgument<std::string>(\"value\", \"\");\n    auto* data = output->template mutable_data<std::string>();\n    for (int i = 0; i < output->size(); ++i) {\n      data[i] = value;\n    }\n    return true;\n  }\n\n private:\n  bool (ConstantFillOp::*body_)(Tensor<Context>* output);\n};\n\ntemplate <class Context>\nclass DiagonalFillOp final : public FillerOp<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  DiagonalFillOp(const OperatorDef& operator_def, Workspace* ws)\n      : FillerOp<Context>(operator_def, ws) {\n    TensorProto_DataType dtype =\n        static_cast<TensorProto_DataType>(OperatorBase::GetSingleArgument<int>(\n            \"dtype\", TensorProto_DataType_FLOAT));\n\n    if (!OperatorBase::HasArgument(\"dtype\") &&\n        OperatorBase::HasArgument(\"value\")) {\n      // If 'dtype' is not provided, infer type based on the type of 'value'\n      // Currently, single argument contains either float, int64 or bytes\n      if (OperatorBase::HasSingleArgumentOfType<float>(\"value\")) {\n        dtype = TensorProto_DataType_FLOAT;\n      } else if (OperatorBase::HasSingleArgumentOfType<int64_t>(\"value\")) {\n        dtype = TensorProto_DataType_INT64;\n      } else {\n        CAFFE_THROW(\"Argument 'value' is of unexpected type\");\n      }\n      VLOG(1) << \"Argument 'dtype' is not provided. Assume the data type is \"\n              << \"the same as that of argument 'value': \" << dtype;\n    }\n\n    switch (dtype) {\n      case TensorProto_DataType_FLOAT:\n        body_ = &DiagonalFillOp::FillWithType<float>;\n        break;\n      case TensorProto_DataType_DOUBLE:\n        body_ = &DiagonalFillOp::FillWithType<double>;\n        break;\n      case TensorProto_DataType_BOOL:\n        body_ = &DiagonalFillOp::FillWithType<bool>;\n        break;\n      case TensorProto_DataType_INT8:\n        body_ = &DiagonalFillOp::FillWithType<int8_t>;\n        break;\n      case TensorProto_DataType_INT16:\n        body_ = &DiagonalFillOp::FillWithType<int16_t>;\n        break;\n      case TensorProto_DataType_INT32:\n        body_ = &DiagonalFillOp::FillWithType<int>;\n        break;\n      case TensorProto_DataType_INT64:\n        body_ = &DiagonalFillOp::FillWithType<int64_t>;\n        break;\n      case TensorProto_DataType_UINT8:\n        body_ = &DiagonalFillOp::FillWithType<uint8_t>;\n        break;\n      case TensorProto_DataType_UINT16:\n        body_ = &DiagonalFillOp::FillWithType<uint16_t>;\n        break;\n      case TensorProto_DataType_UNDEFINED:\n        CAFFE_THROW(\"Cannot have undefined 'dtype' argument\");\n      default:\n        CAFFE_THROW(\"Unexpected 'dtype' argument value: \", dtype);\n    }\n  }\n\n  bool Fill(Tensor<Context>* output) override {\n    return (this->*body_)(output);\n  }\n\n  template <typename T>\n  bool FillWithType(Tensor<Context>* output);\n\n private:\n  void VerifyOutputShape(Tensor<Context>* output) {\n    CAFFE_ENFORCE(output->ndim() >= 2, \"Input shape must be >= 2D\");\n  }\n\n  TIndex GetStepSize(Tensor<Context>* output) {\n    TIndex step;\n    if (output->ndim() == 2) {\n      step = output->dim(1) + 1;\n    } else {\n      TIndex prev_i = output->dim(0);\n      for (auto i : output->dims()) {\n        if (i != prev_i) {\n          CAFFE_THROW(\"All dimensions of input must be of equal length\");\n        }\n      }\n      vector<TIndex> cumprod(output->ndim());\n      auto dims = output->dims();\n      std::partial_sum(\n          dims.begin(),\n          dims.end() - 1,\n          cumprod.begin(),\n          std::multiplies<TIndex>());\n      step = 1 +\n          std::accumulate(\n                 cumprod.begin(), cumprod.end(), static_cast<TIndex>(0));\n      VLOG(0) << step;\n    }\n    return step;\n  }\n\n  bool (DiagonalFillOp::*body_)(Tensor<Context>* output);\n};\n\ntemplate <typename T, class Context>\nclass GaussianFillOp final : public FillerOp<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  GaussianFillOp(const OperatorDef& operator_def, Workspace* ws)\n      : FillerOp<Context>(operator_def, ws),\n        mean_(OperatorBase::template GetSingleArgument<float>(\"mean\", 0)),\n        std_(OperatorBase::template GetSingleArgument<float>(\"std\", 1)) {\n    DCHECK_GT(std_, 0) << \"Standard deviation should be nonnegative.\";\n  }\n\n  bool Fill(Tensor<Context>* output) override {\n    math::RandGaussian<T, Context>(\n        output->size(),\n        mean_,\n        std_,\n        output->template mutable_data<T>(),\n        &context_);\n    return true;\n  }\n\n private:\n  T mean_;\n  T std_;\n};\n\ntemplate <typename T, class Context>\nclass XavierFillOp final : public FillerOp<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  XavierFillOp(const OperatorDef& operator_def, Workspace* ws)\n      : FillerOp<Context>(operator_def, ws) {}\n\n  bool Fill(Tensor<Context>* output) override {\n    const int fan_in = output->size() / output->dim32(0);\n    T scale = std::sqrt(T(3) / fan_in);\n    math::RandUniform<T, Context>(\n        output->size(),\n        -scale,\n        scale,\n        output->template mutable_data<T>(),\n        &context_);\n    return true;\n  }\n};\n\ntemplate <typename T, class Context>\nclass MSRAFillOp final : public FillerOp<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  MSRAFillOp(const OperatorDef& operator_def, Workspace* ws)\n      : FillerOp<Context>(operator_def, ws) {}\n\n  bool Fill(Tensor<Context>* output) override {\n    const int fan_out = output->size() / output->dim32(1);\n    T scale = std::sqrt(T(2) / fan_out);\n    math::RandGaussian<T, Context>(\n        output->size(),\n        0.0,\n        scale,\n        output->template mutable_data<T>(),\n        &context_);\n    return true;\n  }\n};\n\n// This is mostly used just as a debugging purpose stuff: it fills a tensor\n// sequentially with values 0, 1, 2..., which can then be used to check e.g.\n// reshape operations by allowing one to read the indices more easily.\ntemplate <typename T, class Context>\nclass RangeFillOp final : public FillerOp<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  RangeFillOp(const OperatorDef& operator_def, Workspace* ws)\n      : FillerOp<Context>(operator_def, ws) {}\n\n  bool Fill(Tensor<Context>* output) override;\n};\n\ntemplate <class Context>\nclass LengthsRangeFillOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(LengthsRangeFillOp);\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = Output(0);\n    auto* input_data = input.template data<int32_t>();\n\n    CAFFE_ENFORCE_EQ(input.ndim(), 1, \"Input must be a vector.\");\n\n    auto len_sum = std::accumulate(input_data, input_data + input.size(), 0);\n\n    output->Resize(len_sum);\n    auto* output_data = output->template mutable_data<int32_t>();\n\n    int32_t offset = 0;\n    for (int i = 0; i < input.size(); ++i) {\n      auto len = input_data[i];\n      auto start = output_data + offset;\n      std::iota(\n          start,\n          start + len,\n          0); // make the third argument the arg of this operator\n      offset += len;\n    }\n    return true;\n  }\n};\n\ntemplate <int VALUE_TYPE = TensorProto_DataType_FLOAT>\ninline std::vector<TensorShape> FillerTensorInference(\n    const OperatorDef& def,\n    const vector<TensorShape>& in) {\n  vector<TensorShape> out(1);\n  ArgumentHelper helper(def);\n  out[0].set_data_type(static_cast<TensorProto_DataType>(\n      helper.GetSingleArgument<int>(\"dtype\", VALUE_TYPE)));\n\n  if (in.size()) {\n    // TODO\n    bool input_as_shape =\n        helper.GetSingleArgument<bool>(\"input_as_shape\", false);\n    if (input_as_shape) {\n      out[0].set_unknown_shape(true);\n      return out;\n    }\n    for (int d : in[0].dims()) {\n      out[0].add_dims(d);\n    }\n  } else {\n    auto shape = helper.GetRepeatedArgument<int>(\"shape\");\n    for (int d : shape) {\n      out[0].add_dims(d);\n    }\n  }\n  return out;\n}\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_FILLER_OP_H_\n"
  },
  {
    "path": "caffe2/operators/filler_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/filler_op.h\"\n#include \"caffe2/operators/operator_fallback_gpu.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(\n    LengthsRangeFill,\n    GPUFallbackOp<LengthsRangeFillOp<CPUContext>>);\n}\n"
  },
  {
    "path": "caffe2/operators/find_duplicate_elements_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/find_duplicate_elements_op.h\"\n\nnamespace caffe2 {\nnamespace {\nREGISTER_CPU_OPERATOR(\n    FindDuplicateElements,\n    FindDuplicateElementsOp<CPUContext>);\n\nOPERATOR_SCHEMA(FindDuplicateElements)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nShrink the data tensor by removing data blocks with given zero-based indices in\nthe outermost dimension of the tensor. Indices are not assumed in any order or\nunique but with the range [0, blocks_size). Indices could be empty.\n  )DOC\")\n    .Input(0, \"data\", \"a 1-D tensor.\")\n    .Output(\n        0,\n        \"indices\",\n        \"indices of duplicate elements in data, excluding first occurrences.\");\n\nSHOULD_NOT_DO_GRADIENT(FindDuplicateElements);\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/find_duplicate_elements_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FIND_DUPLICATE_ELEMENTS_OP_H\n#define CAFFE2_OPERATORS_FIND_DUPLICATE_ELEMENTS_OP_H\n\n#include <unordered_map>\n#include <vector>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass FindDuplicateElementsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(FindDuplicateElementsOp);\n  USE_DISPATCH_HELPER;\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<float, double, int, long, std::string>>::\n        call(this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    const auto& data = Input(0);\n    CAFFE_ENFORCE(data.ndim() == 1, \"data should be 1-D.\");\n\n    const auto* data_ptr = data.template data<T>();\n    std::unordered_map<T, int64_t> dict;\n    std::vector<int64_t> dupIndices;\n    // i is the index of unique elements, j is the index of all elements\n    for (int64_t i = 0, j = 0; j < data.dims()[0]; ++i, ++j) {\n      bool retVal = dict.insert({data_ptr[j], i}).second;\n      if (!retVal) {\n        --i;\n        dupIndices.push_back(j);\n      }\n    }\n\n    const auto dupSize = dupIndices.size();\n    auto* output = Output(0);\n    output->Resize(dupSize);\n    auto* out_ptr = output->template mutable_data<int64_t>();\n    for (int64_t i = 0; i < dupSize; ++i) {\n      out_ptr[i] = dupIndices[i];\n    }\n\n    return true;\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_FIND_DUPLICATE_ELEMENTS_OP_H\n"
  },
  {
    "path": "caffe2/operators/find_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/find_op.h\"\n\nnamespace caffe2 {\n\nOPERATOR_SCHEMA(Find)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInput(1)\n    .Input(0, \"index\", \"Index (integers)\")\n    .Input(1, \"query\", \"Needles / query\")\n    .Output(\n        0,\n        \"query_indices\",\n        \"Indices of the needles in index or 'missing value'\")\n    .Arg(\"missing_value\", \"Placeholder for items that are not found\")\n    .SetDoc(R\"DOC(\nFinds elements of second input from first input,\noutputting the last (max) index for each query.\nIf query not find, inserts missing_value.\nSee IndexGet() for a version that modifies the index when\nvalues are not found.\n)DOC\");\n\nREGISTER_CPU_OPERATOR(Find, FindOp<CPUContext>)\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/find_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cub/block/block_reduce.cuh>\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/find_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void FindKernel(\n    int num_needles,\n    int idx_size,\n    const T* idx,\n    const T* needles,\n    int* out,\n    int missing_value) {\n  int needle_idx = blockIdx.x; // One cuda block per needle\n  T q = needles[needle_idx];\n  int res = (-1);\n  for (int j = threadIdx.x; j < idx_size; j += CAFFE_CUDA_NUM_THREADS) {\n    if (idx[j] == q) {\n      res = max(res, j);\n    }\n  }\n  typedef cub::BlockReduce<int, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  int min_res = BlockReduce(temp_storage).Reduce(res, cub::Max());\n  if (threadIdx.x == 0) {\n    out[needle_idx] = min_res == (-1) ? missing_value : min_res;\n  }\n}\n\ntemplate <>\ntemplate <typename T>\nbool FindOp<CUDAContext>::DoRunWithType() {\n  auto& idx = Input(0);\n  auto& needles = Input(1);\n  auto* res_indices = Output(0);\n  res_indices->ResizeLike(needles);\n\n  const T* idx_data = idx.data<T>();\n  const T* needles_data = needles.data<T>();\n  int* res_data = res_indices->mutable_data<int>();\n\n  FindKernel<\n      T><<<needles.size(), CAFFE_CUDA_NUM_THREADS, 0, context_.cuda_stream()>>>(\n      needles.size(),\n      idx.size(),\n      idx_data,\n      needles_data,\n      res_data,\n      missing_value_);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Find, FindOp<CUDAContext>)\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/find_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FIND_OP_H_\n#define CAFFE2_OPERATORS_FIND_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\n#include <unordered_map>\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass FindOp final : public Operator<Context> {\n public:\n  FindOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        missing_value_(\n            OperatorBase::GetSingleArgument<int>(\"missing_value\", -1)) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_DISPATCH_HELPER;\n\n  bool RunOnDevice() {\n    return DispatchHelper<TensorTypes<int, long>>::call(this, Input(0));\n  }\n\n protected:\n  template <typename T>\n  bool DoRunWithType() {\n    auto& idx = Input(0);\n    auto& needles = Input(1);\n    auto* res_indices = Output(0);\n    res_indices->ResizeLike(needles);\n\n    const T* idx_data = idx.template data<T>();\n    const T* needles_data = needles.template data<T>();\n    T* res_data = res_indices->template mutable_data<T>();\n    auto idx_size = idx.size();\n\n    // Use an arbitrary cut-off for when to use brute-force\n    // search. For larger needle sizes we first put the\n    // index into a map\n    if (needles.size() < 16) {\n      // Brute force O(nm)\n      for (int i = 0; i < needles.size(); i++) {\n        T x = needles_data[i];\n        T res = static_cast<T>(missing_value_);\n        for (int j = idx_size - 1; j >= 0; j--) {\n          if (idx_data[j] == x) {\n            res = j;\n            break;\n          }\n        }\n        res_data[i] = res;\n      }\n    } else {\n      // O(n + m)\n      std::unordered_map<T, int> idx_map;\n      for (int j = 0; j < idx_size; j++) {\n        idx_map[idx_data[j]] = j;\n      }\n      for (int i = 0; i < needles.size(); i++) {\n        T x = needles_data[i];\n        auto it = idx_map.find(x);\n        res_data[i] = (it == idx_map.end() ? missing_value_ : it->second);\n      }\n    }\n\n    return true;\n  }\n\n protected:\n  int missing_value_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_FIND_OP_H_\n"
  },
  {
    "path": "caffe2/operators/flatten_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/flatten_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Flatten, FlattenOp<CPUContext>);\n\nOPERATOR_SCHEMA(Flatten)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      ArgumentHelper helper(def);\n      const int axis = helper.GetSingleArgument<int>(\"axis\", 1);\n      vector<TensorShape> out(1);\n      TIndex outer = 1;\n      TIndex inner = 1;\n      std::size_t index = 0;\n      for (auto d : in[0].dims()) {\n        if (index < axis) {\n          outer *= d;\n        } else {\n          inner *= d;\n        }\n        ++index;\n      }\n      out[0].set_data_type(in[0].data_type());\n      out[0].add_dims(outer);\n      out[0].add_dims(inner);\n      return out;\n    })\n    .SetDoc(R\"DOC(\nFlattens the input tensor into a 2D matrix. If input tensor has shape\n(d_0, d_1, ... d_n) then the output will have shape\n(d_0 X d_1 ... d_(axis-1), d_axis X d_(axis+1) ... X dn)\n)DOC\")\n    .Input(0, \"input\", \"A tensor of rank >= axis.\")\n    .Output(\n        0,\n        \"output\",\n        \"A 2D tensor with the contents of the input tensor, \"\n        \"with input dimensions up to axis flattened to the outer dimension \"\n        \"of the output and remaining input dimensions flattened into the inner \"\n        \"dimension of the output.\")\n    .Arg(\n        \"axis\",\n        \"(Default to 1) Indicate up to which input dimensions \"\n        \"(exclusive) should be flattened to the outer dimension of the output\");\n\nclass GetFlattenGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"ResizeLike\", \"\", vector<string>{GO(0), I(0)}, vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(Flatten, GetFlattenGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/flatten_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FLATTEN_OP_H_\n#define CAFFE2_OPERATORS_FLATTEN_OP_H_\n\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass FlattenOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  FlattenOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        axis_(OperatorBase::GetSingleArgument<int>(\"axis\", 1)) {}\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = Output(0);\n    CAFFE_ENFORCE_GE(\n        input.dims().size(), axis_, \"The rank of the tensor must be >= axis.\");\n    output->Resize(input.size_to_dim(axis_), input.size_from_dim(axis_));\n    context_.template CopyItems<Context, Context>(\n        input.meta(),\n        input.size(),\n        input.raw_data(),\n        output->raw_mutable_data(input.meta()));\n    return true;\n  }\n\n private:\n  int axis_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_FLATTEN_OP_H_\n"
  },
  {
    "path": "caffe2/operators/flexible_top_k.cc",
    "content": "#include \"caffe2/operators/flexible_top_k.h\"\n\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <typename T>\nstruct ValueCmp {\n  bool operator()(\n      const std::pair<T, TIndex>& lhs,\n      const std::pair<T, TIndex>& rhs) {\n    return (\n        lhs.first > rhs.first ||\n        (lhs.first == rhs.first && lhs.second < rhs.second));\n  }\n};\n\n} // namespace\n\ntemplate <typename T, class Context>\nbool FlexibleTopKOp<T, Context>::RunOnDevice() {\n  auto& input = Input(0);\n  auto& k = Input(1);\n  auto* values = Output(0);\n  auto* indices = Output(1);\n\n  const T* input_data = input.template data<T>();\n  const TIndex* k_data = k.template data<TIndex>();\n\n  // get flatten shape of input\n  CAFFE_ENFORCE_GT(input.ndim(), 0);\n  vector<TIndex> input_dims = input.dims();\n  vector<TIndex> linear_shape = {\n      size_to_dim_(input_dims.size() - 1, input_dims), input_dims.back()};\n  CAFFE_ENFORCE_EQ(\n      linear_shape[0],\n      k.size(),\n      \"first n-1 dims of input data and K does not match.\");\n\n  TIndex output_size = 0;\n  for (TIndex i = 0; i < linear_shape[0]; ++i) {\n    CAFFE_ENFORCE(\n        linear_shape[1] >= k_data[i],\n        \"k should not be greater than last dim, error at index \",\n        i,\n        \", with value: \",\n        k_data[i]);\n    CAFFE_ENFORCE(\n        k_data[i] > 0,\n        \"k should be greater than 0, error at index \",\n        i,\n        \",  with value: \",\n        k_data[i]);\n    output_size += k_data[i];\n  }\n  values->Resize(output_size);\n  indices->Resize(output_size);\n  T* values_data = values->template mutable_data<T>();\n  TIndex* indices_data = indices->template mutable_data<TIndex>();\n\n  TIndex output_offset = 0;\n  // Sort preserving indices\n  for (TIndex i = 0; i < linear_shape[0]; ++i) {\n    // Build a min-heap, the heap element is pair of (value, idx)\n    // the top of the heap is the smallest value\n    std::priority_queue<\n        std::pair<T, TIndex>,\n        std::vector<std::pair<T, TIndex>>,\n        ValueCmp<T>>\n        PQ;\n\n    TIndex k_ = k_data[i];\n    for (TIndex j = 0; j < linear_shape[1]; ++j) {\n      const T value = input_data[i * linear_shape[1] + j];\n      if (PQ.size() < k_ || value > PQ.top().first) {\n        PQ.push(std::make_pair(value, j));\n      }\n      if (PQ.size() > k_) {\n        PQ.pop();\n      }\n    }\n    for (TIndex j = 0; j < k_; ++j) {\n      auto& pqElem = PQ.top();\n      values_data[output_offset + k_ - j - 1] = pqElem.first;\n      indices_data[output_offset + k_ - j - 1] = pqElem.second;\n      PQ.pop();\n    }\n    output_offset += k_;\n  }\n\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool FlexibleTopKGradientOp<T, Context>::RunOnDevice() {\n  auto& original_input = Input(0);\n  auto& k = Input(1);\n  auto& values = Input(2);\n  auto& indices = Input(3);\n  auto* output = Output(0);\n\n  const TIndex* k_data = k.template data<TIndex>();\n  const T* values_data = values.template data<T>();\n  const TIndex* indices_data = indices.template data<TIndex>();\n\n  // Resize output tensors to be as orignial_input size and initialized with 0\n  CAFFE_ENFORCE_GT(original_input.ndim(), 0);\n  vector<TIndex> original_dims = original_input.dims();\n  output->Resize(original_dims);\n  T* output_data = output->template mutable_data<T>();\n  math::Set<T, Context>(\n      output->size(), static_cast<T>(0), output_data, &context_);\n\n  TIndex index_offset = 0;\n  for (TIndex i = 0; i < k.size(); ++i) {\n    // offset of output_data\n    TIndex output_offset = i * original_dims.back();\n    for (TIndex j = 0; j < k_data[i]; ++j) {\n      TIndex index = indices_data[index_offset + j];\n      T value = values_data[index_offset + j];\n      output_data[output_offset + index] = value;\n    }\n    index_offset += k_data[i];\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(FlexibleTopK, FlexibleTopKOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    FlexibleTopKGradient,\n    FlexibleTopKGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(FlexibleTopK)\n    .NumInputs(2)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nGiven two tensors: X and K,\nretrieve the top K[..., 1] elements from X on the last dimension.\nX is an input tensor of shape [a_1, a_2, ..., a_n, r].\nK is an input tensor of shape [a_1, a_2, ..., a_n, 1],\nwhere for each element, r >= K[..., 1] > 0\nOutput two outputs:\n-Flatten values tensor of shape [ \\sum_i K[i, 1] ] which contains the values of\n the top K[..., 1]  elements along the last dimension\n-Flatten indices tensor of shape [ \\sum_i K[i, 1] ] which contains the indices\n of the top K[..., 1]  elements, flatten indices from the input tensor).\nThese two outputs should be used with the input K, so that we know which indices\nin X are picked.\n\nGiven two equivalent values, this operator uses the indices along the last dim-\nension as a tiebreaker. That is, the element with the lower index will appear\nfirst.\n    )DOC\")\n    .Input(0, \"X\", \"Tensor of shape [a_1, a_2, ..., a_n, r]\")\n    .Input(1, \"K\", \"Tensor of shape [a_1, a_2, ..., a_n, 1]\")\n    .Output(\n        0,\n        \"Flatten values\",\n        \"Tensor of shape [ \\\\sum_i K[i, 1] ] containing\"\n        \" top K[..., 1] values from the input tensor\")\n    .Output(\n        1,\n        \"Flatten indices\",\n        \"Tensor of shape [ \\\\sum_i K[i, 1] ] containing the indices \"\n        \"into the flatten input\");\n\nOPERATOR_SCHEMA(FlexibleTopKGradient).NumInputs(4).NumOutputs(1);\n\nclass GetFlexibleTopKGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"FlexibleTopKGradient\",\n        \"\",\n        vector<string>{I(0), I(1), GO(0), O(1)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(FlexibleTopK, GetFlexibleTopKGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/flexible_top_k.h",
    "content": "// Copyright 2004-present Facebook. All Rights Reserved.\n\n#ifndef CAFFE2_OPERATORS_FLEXIBLE_TOP_K_H_\n#define CAFFE2_OPERATORS_FLEXIBLE_TOP_K_H_\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass FlexibleTopKOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  FlexibleTopKOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override;\n};\n\ntemplate <typename T, class Context>\nclass FlexibleTopKGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  FlexibleTopKGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_FLEXIBLE_TOP_K_H_\n"
  },
  {
    "path": "caffe2/operators/floor_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/floor_op.h\"\n\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Floor, FloorOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Floor)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nFloor takes one input data (Tensor<T>) and produces one output data\n(Tensor<T>) where the floor function, y = floor(x), is applied to\nthe tensor elementwise. Currently supports only float32.\n)DOC\")\n    .Input(0, \"X\", \"ND input tensor\")\n    .Output(0, \"Y\", \"ND input tensor\");\n\n// TODO: Write gradient for this when needed\nGRADIENT_NOT_IMPLEMENTED_YET(Floor);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/floor_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/floor_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void FloorKernel(const int N, const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = std::floor(X[i]);\n  }\n}\n\ntemplate <>\nbool FloorOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE_GT(X.size(), 0);\n  Y->ResizeLike(X);\n  FloorKernel<<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(), X.data<float>(), Y->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Floor, FloorOp<float, CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/floor_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FLOOR_OP_H_\n#define CAFFE2_OPERATORS_FLOOR_OP_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass FloorOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(FloorOp);\n\n  bool RunOnDevice() override {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n    Y->ResizeLike(X);\n\n    const float* Xdata = X.template data<float>();\n    float* Ydata = Y->template mutable_data<float>();\n    for (int i = 0; i < X.size(); ++i) {\n      Ydata[i] = std::floor(Xdata[i]);\n    }\n    return true;\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_FLOOR_OP_H_\n"
  },
  {
    "path": "caffe2/operators/free_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/free_op.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(Free, FreeOp<CPUContext>);\nSHOULD_NOT_DO_GRADIENT(Free);\n\nOPERATOR_SCHEMA(Free)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1, INT_MAX)\n    .SameNumberOfOutput()\n    .EnforceOneToOneInplace()\n    .SetDoc(R\"DOC(\nFrees the content of the blobs. The input and output blobs should be\none-to-one inplace.)DOC\");\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/free_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FREE_OP_H_\n#define CAFFE2_OPERATORS_FREE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\n// FreeOp frees the content of the output blob. We allow it to take in input\n// blobs purely for the reason that it can \"wait\" on the input blobs to be\n// produced by some of the earlier operators before a free is called.\ntemplate <class Context>\nclass FreeOp : public Operator<Context> {\n public:\n  FreeOp(const OperatorDef& def, Workspace* ws) : Operator<Context>(def, ws) {}\n\n  bool RunOnDevice() override {\n    for (Blob* output : OperatorBase::Outputs()) {\n      output->Reset();\n    }\n    return true;\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_FREE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/free_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/free_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(Free, FreeOp<CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/fully_connected_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include \"caffe2/operators/fully_connected_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(FC, FullyConnectedOp<CPUContext>);\nREGISTER_CPU_OPERATOR(FCGradient, FullyConnectedGradientOp<CPUContext>);\n\nREGISTER_CPU_OPERATOR(\n    FCTransposed,\n    FullyConnectedOp<\n        CPUContext,\n        DefaultEngine,\n        false /* don't transpose weight */>);\nREGISTER_CPU_OPERATOR(\n    FCTransposedGradient,\n    FullyConnectedGradientOp<\n        CPUContext,\n        DefaultEngine,\n        false /* don't transpose weight */>);\n\nnamespace {\nstd::vector<TensorShape> FCShapeInference(\n    const OperatorDef& def,\n    const vector<TensorShape>& in,\n    bool pretransposed_weight) {\n  vector<TensorShape> out(1);\n  ArgumentHelper helper(def);\n\n  auto axis = helper.GetSingleArgument<int32_t>(\"axis\", 1);\n  const auto canonical_axis = canonical_axis_index_(axis, in[0].dims().size());\n  const int M = size_to_dim_(canonical_axis, GetDimsVector(in[0]));\n  auto axis_w = helper.GetSingleArgument<int32_t>(\"axis_w\", 1);\n  const int canonical_axis_w =\n      canonical_axis_index_(axis_w, in[1].dims().size());\n  const int N = pretransposed_weight\n      ? size_from_dim_(canonical_axis_w, GetDimsVector(in[1]))\n      : size_to_dim_(canonical_axis_w, GetDimsVector(in[1]));\n\n  vector<int> y_shape(in[0].dims().begin(), in[0].dims().end());\n  CAFFE_ENFORCE_LE(canonical_axis + 1, y_shape.size());\n  y_shape.resize(canonical_axis + 1);\n  y_shape[canonical_axis] = N;\n  out[0] = CreateTensorShape(y_shape, in[0].data_type());\n  return out;\n}\n\nOpSchema::Cost CostInferenceForFC(\n    const OperatorDef& def,\n    const vector<TensorShape>& in) {\n  struct OpSchema::Cost c;\n  ArgumentHelper helper(def);\n\n  auto axis = helper.GetSingleArgument<int32_t>(\"axis\", 1);\n  const auto canonical_axis = canonical_axis_index_(axis, in[0].dims().size());\n  const int M = size_to_dim_(canonical_axis, GetDimsVector(in[0]));\n  const int K = size_from_dim_(canonical_axis, GetDimsVector(in[0]));\n  auto axis_w = helper.GetSingleArgument<int32_t>(\"axis_w\", 1);\n  const int canonical_axis_w =\n      canonical_axis_index_(axis_w, in[1].dims().size());\n  const int N = size_to_dim_(canonical_axis_w, GetDimsVector(in[1]));\n  c.flops = 2 * K * M * N + M * N;\n  c.bytes_moved = M * N * sizeof(float);\n  c.params_bytes = (K * N + N) * sizeof(float);\n  return c;\n}\n} // namespace\n\nusing namespace std::placeholders;\nOPERATOR_SCHEMA(FCTransposed)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .TensorInferenceFunction(std::bind(FCShapeInference, _1, _2, true))\n    .SetDoc(R\"DOC(\nSame as FC, but weight matrix is supposed to be already pretransposed.\nFCTransposed stands for calling blass with no noTrans, noTrans\n)DOC\");\n\nOPERATOR_SCHEMA(FC)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .TensorInferenceFunction(std::bind(FCShapeInference, _1, _2, false))\n    .CostInferenceFunction(\n        OpSchema::CostInferenceFunctionType(CostInferenceForFC))\n    .SetDoc(R\"DOC(\nComputes the result of passing an input vector X into a fully\nconnected layer with 2D weight matrix W and 1D bias vector b. That is,\nthe layer computes Y = X * W^T + b, where X has size (M x K),\nW has size (N x K), b has size (N), and Y has size (M x N),\nwhere M is often the batch size.\n\n\nNOTE: X does not need to explicitly be a 2D vector; rather, it will be\ncoerced into one. For an arbitrary n-dimensional tensor\nX \\in [a_0, a_1, ...,a_{k-1}, a_k, ..., a_{n-1}] where a_i \\in N+ and k is\nthe axis provided, then X will be coerced into a 2-dimensional tensor with\ndimensions [a_0 * ... * a_{k-1}, a_k * ... * a_{n-1}]. For the default\ncase where axis=1, this means the X tensor will be coerced into a 2D tensor\nof dimensions [a_0, a_1 * ... * a_{n-1}], where a_0 is often the batch size.\nIn this situation, we must have a_0 = M and a_1 * ... * a_{n-1} = K.\nLastly, even though b is a 1D vector of size N, it is copied/resized to\nbe size (M x N) implicitly and added to each vector in the batch.\nEach of these dimensions must be matched correctly, or else the operator\nwill throw errors.\n)DOC\")\n    .Arg(\n        \"axis\",\n        \"(int32_t) default to 1; describes the axis of the inputs; \"\n        \"defaults to one because the 0th axis most likely describes \"\n        \"the batch_size\")\n    .Arg(\n        \"axis_w\",\n        \"(int32_t) default to 1; describes the axis of the weight matrix W; \"\n        \"defaults to one because the 0th axis most likely describes \"\n        \"the batch_size\")\n    .Arg(\"float16_compute\", \"Whether to use float-16 compute kernel\")\n    .Input(\n        0,\n        \"X\",\n        \"input tensor that's coerced into a 2D matrix of size (MxK) \"\n        \"as described above\")\n    .Input(\n        1,\n        \"W\",\n        \"A tensor that is coerced into a 2D blob of size (KxN) \"\n        \"containing fully connected weight matrix\")\n    .Input(2, \"b\", \"1D blob containing bias vector\")\n    .Output(0, \"Y\", \"2D output tensor\");\n\nOPERATOR_SCHEMA(FCGradient).NumInputs(3).NumOutputs(2, 3);\nOPERATOR_SCHEMA(FCTransposedGradient).NumInputs(3).NumOutputs(2, 3);\n\nnamespace {\n\nclass GetFCGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n\n  std::vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE_EQ(def_.input_size(), 3);\n    CAFFE_ENFORCE(def_.type() == \"FC\" || def_.type() == \"FCTransposed\");\n    return SingleGradientDef(\n        def_.type() + \"Gradient\",\n        \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(1), GI(2), GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(FC, GetFCGradient);\nREGISTER_GRADIENT(FCTransposed, GetFCGradient);\n\n} // namespace\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/fully_connected_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FULLY_CONNECTED_OP_H_\n#define CAFFE2_OPERATORS_FULLY_CONNECTED_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/conversions.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n// This is Caffe's InnerProductOp, with a name that fits its purpose better.\ntemplate <\n    class Context,\n    class Engine = DefaultEngine,\n    bool TransposeWeight = true>\nclass FullyConnectedOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  FullyConnectedOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        axis_(OperatorBase::GetSingleArgument<int32_t>(\"axis\", 1)),\n        axis_w_(OperatorBase::GetSingleArgument<int32_t>(\"axis_w\", 1)),\n        float16_compute_(\n            OperatorBase::GetSingleArgument<bool>(\"float16_compute\", false)) {}\n  ~FullyConnectedOp() {}\n\n  template <\n      typename T_X,\n      typename T_W,\n      typename T_B,\n      typename T_Y,\n      typename MATH>\n  bool DoRunWithType() {\n    const auto& X = Input(0);\n    const auto& W = Input(1);\n    const auto& b = Input(2);\n    auto* Y = Output(0);\n    CAFFE_ENFORCE(b.ndim() == 1, b.ndim());\n    // batch size\n    const auto canonical_axis = X.canonical_axis_index(axis_);\n    const auto M = X.size_to_dim(canonical_axis);\n    const auto K = X.size_from_dim(canonical_axis);\n    const auto canonical_axis_w = W.canonical_axis_index(axis_w_);\n    const int N = TransposeWeight ? W.size_to_dim(canonical_axis_w)\n                                  : W.size_from_dim(canonical_axis_w);\n\n    auto dimErrorString = [&]() {\n      return MakeString(\n          \"Dimension mismatch: \",\n          \"X: \",\n          X.dims(),\n          \", W: \",\n          W.dims(),\n          \", b: \",\n          b.dims(),\n          \", axis: \",\n          axis_,\n          \", M: \",\n          M,\n          \", N: \",\n          N,\n          \", K: \",\n          K);\n    };\n\n    // Error checking\n    CAFFE_ENFORCE(M == X.size() / K, dimErrorString());\n    CAFFE_ENFORCE(K == W.size() / N, dimErrorString());\n    CAFFE_ENFORCE(N == b.dim32(0), dimErrorString());\n    CAFFE_ENFORCE(N == b.size(), dimErrorString());\n\n    Y_shape_cache_ = X.dims();\n    // This is an invariant of canonical_axis, so we can DCHECK.\n    DCHECK_LE(canonical_axis + 1, Y_shape_cache_.size());\n    Y_shape_cache_.resize(canonical_axis + 1);\n    Y_shape_cache_[canonical_axis] = N;\n    Y->Resize(Y_shape_cache_);\n    CAFFE_ENFORCE(M * N == Y->size(), dimErrorString());\n\n    if (X.size() == 0) {\n      // skip the rest of the computation if X is empty\n      Y->template mutable_data<T_Y>();\n      return true;\n    }\n\n    // default to FLOAT as math.h does.\n    TensorProto::DataType math_type = TensorProto_DataType_FLOAT;\n    if (fp16_type<MATH>()) {\n      math_type = TensorProto_DataType_FLOAT16;\n    }\n\n    // W * x\n    math::Gemm<T_X, Context, Engine>(\n        CblasNoTrans,\n        TransposeWeight ? CblasTrans : CblasNoTrans,\n        M,\n        N,\n        K,\n        1,\n        X.template data<T_X>(),\n        W.template data<T_W>(),\n        0,\n        Y->template mutable_data<T_Y>(),\n        &context_,\n        math_type);\n    // Add bias term\n    if (bias_multiplier_.size() != M) {\n      // If the helper bias multiplier is not M, reshape and fill it with one.\n      bias_multiplier_.Resize(M);\n      math::Set<T_B, Context>(\n          M,\n          convert::To<float, T_B>(1),\n          bias_multiplier_.template mutable_data<T_B>(),\n          &context_);\n    }\n    math::Gemm<T_B, Context, Engine>(\n        CblasNoTrans,\n        CblasNoTrans,\n        M,\n        N,\n        1,\n        1,\n        bias_multiplier_.template data<T_B>(),\n        b.template data<T_B>(),\n        1,\n        Y->template mutable_data<T_Y>(),\n        &context_,\n        math_type);\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    return DoRunWithType<\n        float, // X\n        float, // W\n        float, // B\n        float, // Y\n        float>(); // Math\n  }\n\n protected:\n  size_t axis_{1};\n  size_t axis_w_{1};\n  // A local vector to cache the output shape so we don't need to recreate\n  // a vector object every time we run Run().\n  vector<TIndex> Y_shape_cache_;\n  Tensor<Context> bias_multiplier_;\n\n  bool float16_compute_;\n};\n\ntemplate <\n    class Context,\n    class Engine = DefaultEngine,\n    bool TransposeWeight = true>\nclass FullyConnectedGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  FullyConnectedGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        axis_(OperatorBase::GetSingleArgument<int32_t>(\"axis\", 1)),\n        axis_w_(OperatorBase::GetSingleArgument<int32_t>(\"axis_w\", 1)),\n        float16_compute_(\n            OperatorBase::GetSingleArgument<bool>(\"float16_compute\", false)) {}\n  ~FullyConnectedGradientOp() {}\n\n  template <\n      typename T_X,\n      typename T_W,\n      typename T_DY,\n      typename T_B,\n      typename T_DX,\n      typename T_DW,\n      typename T_DB,\n      typename MATH>\n  bool DoRunWithType() {\n    const auto& X = Input(0);\n    const auto& W = Input(1);\n    const auto& dY = Input(2);\n    // batch size\n    const auto canonical_axis = X.canonical_axis_index(axis_);\n    const int M = X.size_to_dim(canonical_axis);\n    const int K = X.size_from_dim(canonical_axis);\n    const auto canonical_axis_w = W.canonical_axis_index(axis_w_);\n    const int N = TransposeWeight ? W.size_to_dim(canonical_axis_w)\n                                  : W.size_from_dim(canonical_axis_w);\n    CAFFE_ENFORCE(M * K == X.size());\n    CAFFE_ENFORCE(K * N == W.size());\n\n    auto* dW = Output(0);\n    auto* db = Output(1);\n    dW->ResizeLike(W);\n    db->Resize(N);\n\n    if (X.size() == 0) {\n      // generate a zero blob for db and dW when X is empty\n      math::Set<T_DB, Context>(\n          db->size(),\n          convert::To<float, T_DB>(0),\n          db->template mutable_data<T_DB>(),\n          &context_);\n      math::Set<T_DW, Context>(\n          dW->size(),\n          convert::To<float, T_DW>(0),\n          dW->template mutable_data<T_DW>(),\n          &context_);\n\n      if (OutputSize() == 3) {\n        auto* dX = Output(2);\n        dX->ResizeLike(X);\n        dX->template mutable_data<T_DX>();\n      }\n\n      return true;\n    }\n\n    // default to FLOAT as math.h does.\n    TensorProto::DataType math_type = TensorProto_DataType_FLOAT;\n    if (fp16_type<MATH>()) {\n      math_type = TensorProto_DataType_FLOAT16;\n    }\n\n    // Compute dW\n    math::Gemm<T_DY, Context, Engine>(\n        CblasTrans,\n        CblasNoTrans,\n        TransposeWeight ? N : K,\n        TransposeWeight ? K : N,\n        M,\n        1,\n        TransposeWeight ? dY.template data<T_DY>() : X.template data<T_X>(),\n        TransposeWeight ? X.template data<T_X>() : dY.template data<T_DY>(),\n        0,\n        dW->template mutable_data<T_DW>(),\n        &context_,\n        math_type);\n    if (bias_multiplier_.size() != M) {\n      // If the helper bias multiplier is not M, reshape and fill it\n      // with one.\n      bias_multiplier_.Resize(M);\n      math::Set<T_B, Context>(\n          M,\n          convert::To<float, T_B>(1),\n          bias_multiplier_.template mutable_data<T_B>(),\n          &context_);\n    }\n    // Compute dB\n    math::Gemv<T_DY, Context>(\n        CblasTrans,\n        M,\n        N,\n        1,\n        dY.template data<T_DY>(),\n        bias_multiplier_.template data<T_B>(),\n        0,\n        db->template mutable_data<T_DB>(),\n        &context_);\n\n    // Compute dX\n    if (OutputSize() == 3) {\n      auto* dX = Output(2);\n      dX->ResizeLike(X);\n      math::Gemm<T_DX, Context, Engine>(\n          CblasNoTrans,\n          TransposeWeight ? CblasNoTrans : CblasTrans,\n          M,\n          K,\n          N,\n          1,\n          dY.template data<T_DY>(),\n          W.template data<T_W>(),\n          0,\n          dX->template mutable_data<T_DX>(),\n          &context_,\n          math_type);\n    }\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    return DoRunWithType<\n        float, //  X\n        float, //  W\n        float, // dY\n        float, //  B\n        float, // dX\n        float, // dW\n        float, // dB\n        float>(); // Math\n  }\n\n protected:\n  size_t axis_{1};\n  size_t axis_w_{1};\n  Tensor<Context> bias_multiplier_;\n  bool float16_compute_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_FULLY_CONNECTED_OP_H_\n"
  },
  {
    "path": "caffe2/operators/fully_connected_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/fully_connected_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\nconstexpr int kFp16CUDADevicePropMajor = 6;\n\ntemplate <class FullyConnectedOp>\nbool RunFullyConnectedOpOnCUDADevice(\n    const bool float16_compute,\n    FullyConnectedOp* op) {\n  if (op->Input(0).template IsType<float>()) {\n    return op->template DoRunWithType<\n        float, // X\n        float, // W\n        float, // B\n        float, // Y\n        float>(); // Math\n  } else if (op->Input(0).template IsType<float16>()) {\n    if (float16_compute) {\n      const cudaDeviceProp& prop = GetDeviceProperty(0);\n      if (prop.major >= kFp16CUDADevicePropMajor) {\n        return op->template DoRunWithType<\n            float16, // X\n            float16, // W\n            float16, // B\n            float16, // Y\n            float16>(); // Math\n      } else {\n        LOG(INFO) << \"CUDA Device does not support FP16 computation, \"\n                     \"falling back to FP32.\";\n        return op->template DoRunWithType<\n            float16, // X\n            float16, // W\n            float16, // B\n            float16, // Y\n            float>(); // Math\n      }\n    } else {\n      return op->template DoRunWithType<\n          float16, // X\n          float16, // W\n          float16, // B\n          float16, // Y\n          float>(); // Math\n    }\n  } else {\n    CAFFE_THROW(\"Unsupported type\");\n  }\n  return false;\n}\n\ntemplate <class FullyConnectedGradientOp>\nbool RunFullyConnectedGradientOpOnCUDADevice(\n    const bool float16_compute,\n    FullyConnectedGradientOp* op) {\n  if (op->Input(0).template IsType<float>()) {\n    return op->template DoRunWithType<\n        float, //  X\n        float, //  W\n        float, // dY\n        float, //  B\n        float, // dX\n        float, // dW\n        float, // dB\n        float>(); // Math\n  } else if (op->Input(0).template IsType<float16>()) {\n    if (float16_compute) {\n      const cudaDeviceProp& prop = GetDeviceProperty(0);\n      if (prop.major >= kFp16CUDADevicePropMajor) {\n        return op->template DoRunWithType<\n            float16, //  X\n            float16, //  W\n            float16, // dY\n            float16, //  B\n            float16, // dX\n            float16, // dW\n            float16, // dB\n            float16>(); // Math\n      } else {\n        LOG(INFO) << \"CUDA Device does not support FP16 computation, \"\n                     \"falling back to FP32.\";\n        return op->template DoRunWithType<\n            float16, //  X\n            float16, //  W\n            float16, // dY\n            float16, //  B\n            float16, // dX\n            float16, // dW\n            float16, // dB\n            float>(); // Math\n      }\n    } else {\n      return op->template DoRunWithType<\n          float16, //  X\n          float16, //  W\n          float16, // dY\n          float16, //  B\n          float16, // dX\n          float16, // dW\n          float16, // dB\n          float>(); // Math\n    }\n  } else {\n    CAFFE_THROW(\"Unsupported type\");\n  }\n  return false;\n}\n\n} // namespace\n\n// The RunFullyConnectedOpOnCUDADevice Function will use the pointer of current\n// op and the DoRunWithType will make sure to run the correct things.\ntemplate <>\nbool FullyConnectedOp<CUDAContext>::RunOnDevice() {\n  return RunFullyConnectedOpOnCUDADevice(float16_compute_, this);\n}\n\ntemplate <>\nbool FullyConnectedOp<\n    CUDAContext,\n    DefaultEngine,\n    false /* don't transpose weight */>::RunOnDevice() {\n  return RunFullyConnectedOpOnCUDADevice(float16_compute_, this);\n}\n\ntemplate <>\nbool FullyConnectedGradientOp<CUDAContext>::RunOnDevice() {\n  return RunFullyConnectedGradientOpOnCUDADevice(float16_compute_, this);\n}\n\ntemplate <>\nbool FullyConnectedGradientOp<\n    CUDAContext,\n    DefaultEngine,\n    false /* don't transpose weight */>::RunOnDevice() {\n  return RunFullyConnectedGradientOpOnCUDADevice(float16_compute_, this);\n}\n\n#if CUDA_VERSION >= 9000\n\n// Require these to be defined otherwise TensorCore FC ops will end\n// up calling the default FC implementation which doesn't have\n// fp16 support...\n\ntemplate <>\nbool FullyConnectedOp<CUDAContext, TensorCoreEngine>::RunOnDevice() {\n  return RunFullyConnectedOpOnCUDADevice(false /* float16_compute */, this);\n}\n\ntemplate <>\nbool FullyConnectedOp<\n    CUDAContext,\n    TensorCoreEngine,\n    false /* don't transpose weight */>::RunOnDevice() {\n  return RunFullyConnectedOpOnCUDADevice(false /* float16_compute */, this);\n}\n\ntemplate <>\nbool FullyConnectedGradientOp<CUDAContext, TensorCoreEngine>::RunOnDevice() {\n  return RunFullyConnectedGradientOpOnCUDADevice(\n      false /* float16_compute */, this);\n}\n\ntemplate <>\nbool FullyConnectedGradientOp<\n    CUDAContext,\n    TensorCoreEngine,\n    false /* don't transpose weight */>::RunOnDevice() {\n  return RunFullyConnectedGradientOpOnCUDADevice(\n      false /* float16_compute */, this);\n}\n\n#endif\n\nREGISTER_CUDA_OPERATOR(FC, FullyConnectedOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(FCGradient, FullyConnectedGradientOp<CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(\n    FCTransposed,\n    FullyConnectedOp<\n        CUDAContext,\n        DefaultEngine,\n        false /* don't transpose weight */>);\nREGISTER_CUDA_OPERATOR(\n    FCTransposedGradient,\n    FullyConnectedGradientOp<\n        CUDAContext,\n        DefaultEngine,\n        false /* don't transpose weight */>);\n\n#if CUDA_VERSION >= 9000\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(\n    FC,\n    TENSORCORE,\n    FullyConnectedOp<CUDAContext, TensorCoreEngine>);\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(\n    FCGradient,\n    TENSORCORE,\n    FullyConnectedGradientOp<CUDAContext, TensorCoreEngine>);\n\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(\n    FCTransposed,\n    TENSORCORE,\n    FullyConnectedOp<\n        CUDAContext,\n        TensorCoreEngine,\n        false /* don't transpose weight */>);\nREGISTER_CUDA_OPERATOR_WITH_ENGINE(\n    FCTransposedGradient,\n    TENSORCORE,\n    FullyConnectedGradientOp<\n        CUDAContext,\n        TensorCoreEngine,\n        false /* don't transpose weight */>);\n#endif\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/fused_rowwise_8bit_conversion_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/fused_rowwise_8bit_conversion_ops.h\"\n#include \"caffe2/core/registry.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(\n    FloatToFused8BitRowwiseQuantized,\n    FloatToFused8BitRowwiseQuantizedOp<CPUContext>);\nOPERATOR_SCHEMA(FloatToFused8BitRowwiseQuantized)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nApplies 8-bit row-wise quantization by determining the range\n(maximum - minimum) and offset (minimum value) of each row in the input\nmatrix, and then scaling each element to an 8-bit number between 0 and\n255. To later de-quantize values, the scale (range / 255) and offset\n(bias) are stored alongside the data. More precisely, the first 4 bytes\nof each row in the output matrix are a 32-bit float storing the scale,\nthe next 4 bytes store the bias as a 32-bit float, and all remaining\nbytes in the row encode single quantized values.)\n)DOC\")\n    .Input(0, \"input\", \"Float32 input data\")\n    .Output(0, \"output\", \"Fused scale, bias and quantized data\");\nNO_GRADIENT(FloatToFused8BitRowwiseQuantized);\n\nREGISTER_CPU_OPERATOR(\n    Fused8BitRowwiseQuantizedToFloat,\n    Fused8BitRowwiseQuantizedToFloatOp<CPUContext>);\nOPERATOR_SCHEMA(Fused8BitRowwiseQuantizedToFloat)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nDe-quantizes the result of the\nFloatToFused8BitRowwiseQuantized operator. The input is expected to\nencode the scale as a 32-bit float in the second to the last 4 bytes of each\nrow, followed by the bias as a 32-bit float in the next 4 bytes, and the\nquantized values in the preceding bytes of the row. The output is a\nmatrix containing only the values, but de-quantized. De-quantization is\nperformed by multiplying each value by its row's scale and bias\nparameters. The de-quantized values will thus not be exactly equal to\nthe original, un-quantized floating point values.\n)DOC\")\n    .Input(\n        0,\n        \"scale_bias_quantized_input\",\n        \"Fused scale, bias and quantized data\")\n    .Output(0, \"float_input\", \"Float32 data\");\nNO_GRADIENT(Fused8BitRowwiseQuantizedToFloat);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/fused_rowwise_8bit_conversion_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_FUSED_ROWWISE_8BIT_CONVERSION_OPS_H_\n#define CAFFE2_OPERATORS_FUSED_ROWWISE_8BIT_CONVERSION_OPS_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/reducer_functors.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n#define IS_LITTLE_ENDIAN                                      \\\n  [] {                                                        \\\n    const int32_t kValue = 1;                                 \\\n    return reinterpret_cast<const uint8_t*>(&kValue)[0] == 1; \\\n  }()\n\ntemplate <class Context>\nclass FloatToFused8BitRowwiseQuantizedOp : public Operator<Context> {\n public:\n  static constexpr float kEqualityThreshold = 1e-7f;\n  static constexpr float kEpsilon = 1e-8f;\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(FloatToFused8BitRowwiseQuantizedOp)\n\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE(IS_LITTLE_ENDIAN, \"Unsupported endianness\");\n\n    const auto& input = Input(DATA_FLOAT);\n    auto* output = Output(DATA_FUSED_SCALE_BIAS_INT8);\n\n    const auto input_rows = input.dim(0);\n    const auto input_columns = input.dim(1);\n    CAFFE_ENFORCE_EQ(input.ndim(), 2, \"Expect input to be a matrix\");\n\n    // The \"fused\" representation stores the scale and bias with the row-wise\n    // quantized data in one tensor. Since we quantize with 8 bits (1 byte) and\n    // represent the scale and bias with 32-bit floats, we'll use the last 8\n    // bytes of each row for scale (4 bytes) and bias (4 bytes).\n    // | ... int8 data ... | scale | bias |\n    // | number_of_columns |  4B   |  4B  |\n    const std::vector<TIndex> output_dimensions = {input_rows,\n                                                   input_columns + 8};\n    output->Resize(output_dimensions);\n\n    const auto* input_data = input.template data<float>();\n    auto* output_data = output->template mutable_data<uint8_t>();\n    const auto output_columns = output->dim(1);\n\n    for (size_t row = 0; row < input_rows; ++row) {\n      ConstEigenVectorArrayMap<float> input_row(\n          input_data + row * input_columns, input_columns);\n\n      uint8_t* output_row = output_data + row * output_columns;\n      EigenVectorArrayMap<uint8_t> output_row_values(output_row, input_columns);\n      EigenVectorArrayMap<float> output_row_scale_bias(\n          reinterpret_cast<float*>(output_row + input_columns), 2);\n\n      const float minimum_element = input_row.minCoeff();\n      const float maximum_element = input_row.maxCoeff();\n      const float range = maximum_element - minimum_element;\n\n      output_row_scale_bias(0) = range / 255.0f;\n      output_row_scale_bias(1) = minimum_element;\n      const auto inverse_scale = 255.0f / (range + kEpsilon);\n      output_row_values = ((input_row - minimum_element) * inverse_scale)\n                              .round()\n                              .cast<uint8_t>();\n    }\n\n    return true;\n  }\n\n private:\n  INPUT_TAGS(DATA_FLOAT);\n  OUTPUT_TAGS(DATA_FUSED_SCALE_BIAS_INT8);\n};\n\ntemplate <class Context>\nclass Fused8BitRowwiseQuantizedToFloatOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(Fused8BitRowwiseQuantizedToFloatOp)\n\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE(IS_LITTLE_ENDIAN, \"Unsupported endianness\");\n\n    const auto& input = Input(DATA_FUSED_SCALE_BIAS_INT8);\n    auto* output = Output(DATA_FLOAT);\n\n    const auto input_rows = input.dim(0);\n    const auto input_columns = input.dim(1);\n    CAFFE_ENFORCE_EQ(input.ndim(), 2, \"Expect input to be a matrix\");\n\n    // The last 8 bytes per row are the scale and the bias. The rest of\n    // input_columns is the number of values in the original row.\n    const std::vector<TIndex> output_dimensions = {input_rows,\n                                                   input_columns - 8};\n    output->Resize(output_dimensions);\n    const auto output_columns = output->dim(1);\n\n    const auto* input_data = input.template data<uint8_t>();\n    auto* output_data = output->template mutable_data<float>();\n\n    for (size_t row = 0; row < input_rows; ++row) {\n      const uint8_t* input_row = input_data + row * input_columns;\n      ConstEigenVectorArrayMap<uint8_t> input_row_values(\n          input_row, output_columns);\n      ConstEigenVectorArrayMap<float> input_row_scale_bias(\n          reinterpret_cast<const float*>(input_row + output_columns), 2);\n\n      EigenVectorArrayMap<float> output_row(\n          output_data + row * output_columns, output_columns);\n\n      output_row = input_row_values.cast<float>() * input_row_scale_bias(0) +\n          input_row_scale_bias(1);\n    }\n    return true;\n  }\n\n private:\n  INPUT_TAGS(DATA_FUSED_SCALE_BIAS_INT8);\n  OUTPUT_TAGS(DATA_FLOAT);\n};\n\n#undef IS_LITTLE_ENDIAN\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_FUSED_ROWWISE_8BIT_CONVERSION_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/gather_fused_8bit_rowwise_op.cc",
    "content": "#include \"caffe2/operators/gather_fused_8bit_rowwise_op.h\"\n\nnamespace caffe2 {\n\nOPERATOR_SCHEMA(GatherFused8BitRowwise)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nPerform the same operation as Gather, but operating on 8-bit rowwise quantized\nmatrices with fused storage (where each row stores quantized values, and then\nthe scale and offset).\nDATA needs to have rank 2 and INDICES needs to have rank 1.\n)DOC\")\n    .Input(\n        0,\n        \"DATA\",\n        \"uint8 tensor with rank 2 obtained with operator FloatToFused8BitRowwiseQuantized\")\n    .Input(\n        1,\n        \"INDICES\",\n        \"Integer vector containing indices of the first dimension of DATA for\"\n        \"the rows that are being gathered\")\n    .Output(0, \"OUTPUT\", \"output\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      vector<TensorShape> out(1);\n      for (auto d : in[1].dims()) {\n        out[0].add_dims(d);\n      }\n      for (int i = 1; i < in[0].dims_size(); ++i) {\n        out[0].add_dims(in[0].dims(i));\n      }\n      out[0].set_data_type(in[0].data_type());\n      return out;\n    });\n\nREGISTER_CPU_OPERATOR(\n    GatherFused8BitRowwise,\n    GatherFused8BitRowwiseOp<CPUContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/gather_fused_8bit_rowwise_op.h",
    "content": "#pragma once\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass GatherFused8BitRowwiseOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(GatherFused8BitRowwiseOp);\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, OperatorBase::Input<TensorCPU>(INDICES));\n  }\n\n  template <typename Index>\n  bool DoRunWithType() {\n    const auto& data = Input(DATA);\n    const auto& indices = Input(INDICES);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_EQ(data.ndim(), 2, \"DATA must be a matrix\");\n    CAFFE_ENFORCE_EQ(indices.ndim(), 1, \"INDICES must be a vector\");\n    CAFFE_ENFORCE_GT(data.dim(1), 8, \"DATA must have more than 8 columns\");\n    // Subtract 8 from the #columns of data for the 4 bytes for scale and 4\n    // bytes for bias that we use in the fused representation (per row).\n    const std::vector<TIndex> shape = {indices.dim(0), data.dim(1) - 8};\n    output->Resize(shape);\n\n    int block_size = shape[1];\n    auto block_bytesize = data.size_from_dim(1) * data.meta().itemsize();\n    int N = indices.size();\n\n    const uint8_t* src_base = data.template data<uint8_t>();\n    const Index* idxs = indices.template data<Index>();\n    auto out = output->template mutable_data<float>();\n\n    for (int i = 0; i < N; ++i) {\n      auto idx = idxs[i];\n      CAFFE_ENFORCE(\n          0 <= idx && idx < data.dim(0),\n          \"INDICES element is out of DATA bounds, id=\",\n          idx,\n          \" data_dim=\",\n          data.dim(0));\n      const uint8_t* src = src_base + idx * block_bytesize;\n      ConstEigenVectorArrayMap<uint8_t> input_row_values(src, shape[1]);\n      ConstEigenVectorArrayMap<float> input_row_scale_bias(\n          reinterpret_cast<const float*>(src + shape[1]), 2);\n\n      EigenVectorArrayMap<float> output_row(out + i * shape[1], shape[1]);\n\n      output_row = input_row_values.cast<float>() * input_row_scale_bias(0) +\n          input_row_scale_bias(1);\n    }\n    return true;\n  }\n\n  INPUT_TAGS(DATA, INDICES);\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/gather_ranges_to_dense_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/gather_ranges_to_dense_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\nOPERATOR_SCHEMA(GatherRangesToDense)\n    .NumInputs(2, 3)\n    .NumOutputs(1, INT_MAX)\n    .SetDoc(R\"DOC(\nGiven DATA tensor of rank 1, and RANGES tensor of rank 3, gather values\ncorresponding to each range into a separate output tensor. If the optional input\nKEY tensor is also given, the output will be sorted by KEY for each example.\n\nRANGES dimensions description:\n1: represents list of examples within a batch\n2: represents list features\n3: two values which are start and length or a range (to be applied on DATA)\n\nEach feature has fixed lengths which are passed as lengths argument and a\nseparate tensor will be produced for each feature.\ni.e. DATA.dim(1) = len(lengths) = NumOuptuts.\n\nMissing features (represented by empty ranges) filled with default_value.\n\nExample 1:\n  DATA  = [1, 2, 3, 4, 5, 6, 7, 8]\n  RANGES = [\n    [\n      [2, 4],\n      [0, 2],\n    ],\n    [\n      [0, 0],\n      [6, 2],\n    ]\n  ]\n  lengths = [4, 2]\n  OUTPUT[0] = [[3, 4, 5, 6], [0, 0, 0, 0]]\n  OUTPUT[1] = [[1, 2], [7, 8]]\n\nExample 2 (with KEY):\nDATA  = [1, 2, 3, 4, 5, 6, 7, 8]\nKEY   = [0, 1, 3, 2, 1, 0, 1, 0]\nRANGES = [\n  [\n    [2, 4],\n    [0, 2],\n  ],\n  [\n    [0, 0],\n    [6, 2],\n  ]\n]\nlengths = [4, 2]\nOUTPUT[0] = [[6, 5, 4, 3], [0, 0, 0, 0]]\nOUTPUT[1] = [[1, 2], [8, 7]]\n\nContrast Example 2 with Example 1. For each data point per feature, the values\nare sorted by the corresponding KEY.\n)DOC\")\n    .Input(0, \"DATA\", \"Tensor of rank 1.\")\n    .Input(\n        1,\n        \"RANGES\",\n        \"Tensor of int32/int64 ranges, of dims (N, M, 2). \"\n        \"Where N is number of examples and M is a size of each example. \"\n        \"Last dimention represents a range in the format (start, lengths)\")\n    .Input(2, \"KEY\", \"Tensor of rank 1 and type int64.\")\n    .Output(0, \"OUTPUT\", \"1-D tensor of size sum of range lengths\")\n    .Arg(\"lengths\", \"Expected lengths for ranges\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      ArgumentHelper helper(def);\n      auto lengths = helper.GetRepeatedArgument<int>(\"lengths\");\n      CAFFE_ENFORCE_EQ(in[0].dims_size(), 1, \"DATA should be 1-D tensor.\");\n      CAFFE_ENFORCE_EQ(in[1].dims_size(), 3, \"RANGES should be 3-D tensor.\");\n      if (in.size() > 2) {\n        CAFFE_ENFORCE_EQ(in[2].dims_size(), 1, \"KEY should be 1-D tensor.\");\n      }\n      CAFFE_ENFORCE_GT(lengths.size(), 0, \"lengths should be non-empty.\");\n      std::vector<TensorShape> out(lengths.size());\n      for (int i = 0; i < lengths.size(); ++i) {\n        out[i].set_data_type(in[0].data_type());\n        out[i].add_dims(in[1].dims(0));\n        out[i].add_dims(lengths[i]);\n      }\n      return out;\n    });\n\nREGISTER_CPU_OPERATOR(GatherRangesToDense, GatherRangesToDenseOp<CPUContext>);\nNO_GRADIENT(GatherRangesToDense);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/gather_ranges_to_dense_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_GATHER_RANGES_TO_DENSE_OPS_H_\n#define CAFFE2_OPERATORS_GATHER_RANGES_TO_DENSE_OPS_H_\n\n#include <math.h>\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/utils/math.h\"\n\n#include <map>\n#include <utility>\n\nnamespace caffe2 {\ntemplate <class Context>\nclass GatherRangesToDenseOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  GatherRangesToDenseOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        lengths_(OperatorBase::GetRepeatedArgument<int>(\"lengths\")) {\n    CAFFE_ENFORCE_GT(lengths_.size(), 0, \"There has to be at least one length\");\n    for (auto length : lengths_) {\n      CAFFE_ENFORCE_GT(length, 0, \"Each length should be positive\");\n    }\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, OperatorBase::Input<TensorCPU>(RANGES));\n  }\n\n  template <typename Index>\n  bool DoRunWithType() {\n    auto& data = Input(DATA);\n    auto& ranges = Input(RANGES);\n    CAFFE_ENFORCE_EQ(data.ndim(), 1, \"Data has to be 1-D\");\n    CAFFE_ENFORCE_EQ(ranges.ndim(), 3, \"Ranges has to be 3-D\");\n    if (InputSize() == 3) {\n      auto& key = Input(KEY);\n      CAFFE_ENFORCE_EQ(key.ndim(), 1, \"Key has to be 1-D\");\n      CAFFE_ENFORCE(\n          key.meta().template Match<int64_t>(), \"Key has to be type int64_t\");\n    }\n    CAFFE_ENFORCE_EQ(\n        ranges.dim(1),\n        lengths_.size(),\n        \"Nummber of ranges should match number of lengths\");\n    CAFFE_ENFORCE_EQ(\n        ranges.dim(1),\n        OutputSize(),\n        \"Nummber of ranges should match number of outputs\");\n    CAFFE_ENFORCE_EQ(\n        ranges.dim(2), 2, \"Ranges last dimension should be of size 2\");\n\n    auto* rawData = static_cast<const char*>(data.raw_data());\n    auto* rangesData = ranges.template data<Index>();\n    int rangesDataOffset = 0;\n    auto itemsize = data.meta().itemsize();\n\n    auto batchSize = ranges.dim(0);\n    vector<TIndex> outputDims{batchSize, 0};\n    vector<char*> outputRawData;\n    for (int i = 0; i < OutputSize(); ++i) {\n      auto* output = Output(i);\n      outputDims[1] = lengths_[i];\n      output->Resize(outputDims);\n      char* ptr = static_cast<char*>(output->raw_mutable_data(data.meta()));\n      memset(ptr, 0, output->nbytes());\n      outputRawData.push_back(ptr);\n    }\n\n    for (int i = 0; i < batchSize; ++i) {\n      for (int j = 0; j < OutputSize(); ++j) {\n        auto rangeStart = rangesData[rangesDataOffset++];\n        auto rangeLength = rangesData[rangesDataOffset++];\n        if (rangeLength == 0) {\n          // empty range, will be filled with zeros\n          continue;\n        }\n        CAFFE_ENFORCE_EQ(\n            rangeLength,\n            lengths_[j],\n            \"Range lengths missmatch for output #\",\n            j);\n\n        if (InputSize() == 2) {\n          context_.template CopyItems<Context, Context>(\n              data.meta(),\n              rangeLength,\n              rawData + rangeStart * itemsize,\n              outputRawData[j] + i * itemsize * lengths_[j]);\n        } else {\n          auto& key = Input(KEY);\n          auto* key_data = key.template data<int64_t>();\n          vector<std::pair<int64_t, const char*>> buffer;\n          for (int b_i = 0; b_i < rangeLength; ++b_i) {\n            int64_t one_key_item = key_data[rangeStart + b_i];\n            auto* one_data_item = rawData + (rangeStart + b_i) * itemsize;\n            buffer.emplace_back(one_key_item, one_data_item);\n          }\n          std::sort(\n              buffer.begin(),\n              buffer.end(),\n              [](const std::pair<int64_t, const char*>& left,\n                 const std::pair<int64_t, const char*>& right) {\n                return left.first < right.first;\n              });\n          for (int b_i = 0; b_i < rangeLength; ++b_i) {\n            // Since this CPU only, directly copy to the destination.\n            std::memcpy(\n                outputRawData[j] + (i * lengths_[j] + b_i) * itemsize,\n                buffer[b_i].second,\n                itemsize);\n          }\n        }\n      }\n    }\n    CAFFE_ENFORCE_EQ(rangesDataOffset, ranges.size());\n\n    return true;\n  }\n\n  INPUT_TAGS(DATA, RANGES, KEY);\n\n private:\n  vector<int> lengths_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_GATHER_RANGES_TO_DENSE_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/generate_proposals_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/generate_proposals_op.h\"\n#include \"caffe2/operators/generate_proposals_op_util_boxes.h\"\n#include \"generate_proposals_op_util_nms.h\"\n\n#ifdef CAFFE2_USE_MKL\n#include \"caffe2/mkl/operators/operator_fallback_mkl.h\"\n#endif // CAFFE2_USE_MKL\n\nnamespace caffe2 {\n\nnamespace {\n\n// Compute the 1-d index of a n-dimensional contiguous row-major tensor for\n//     a given n-dimensional index 'index'\nsize_t ComputeStartIndex(\n    const TensorCPU& tensor,\n    const std::vector<int>& index) {\n  DCHECK_EQ(index.size(), tensor.ndim());\n\n  size_t ret = 0;\n  for (int i = 0; i < index.size(); i++) {\n    ret += index[i] * tensor.size_from_dim(i + 1);\n  }\n\n  return ret;\n}\n\n// Get a sub tensor view from 'tensor' using data pointer from 'tensor'\ntemplate <class T>\nutils::ConstTensorView<T> GetSubTensorView(\n    const TensorCPU& tensor,\n    int dim0_start_index) {\n  DCHECK_EQ(tensor.meta().itemsize(), sizeof(T));\n\n  if (tensor.size() == 0) {\n    return utils::ConstTensorView<T>(nullptr, {});\n  }\n\n  std::vector<int> start_dims(tensor.ndim(), 0);\n  start_dims.at(0) = dim0_start_index;\n  auto st_idx = ComputeStartIndex(tensor, start_dims);\n  auto ptr = tensor.data<T>() + st_idx;\n\n  auto& input_dims = tensor.dims();\n  std::vector<int> ret_dims(input_dims.begin() + 1, input_dims.end());\n\n  utils::ConstTensorView<T> ret(ptr, ret_dims);\n  return ret;\n}\n\n} // namespace\n\nnamespace utils {\n\nERMatXf ComputeAllAnchors(\n    const TensorCPU& anchors,\n    int height,\n    int width,\n    float feat_stride) {\n  const auto K = height * width;\n  const auto A = anchors.dim(0);\n\n  ERMatXf shift_x = (ERVecXf::LinSpaced(width, 0.0, width - 1.0) * feat_stride)\n                        .replicate(height, 1);\n  ERMatXf shift_y = (EVecXf::LinSpaced(height, 0.0, height - 1.0) * feat_stride)\n                        .replicate(1, width);\n  Eigen::MatrixXf shifts(K, 4);\n  shifts << ConstEigenVectorMap<float>(shift_x.data(), shift_x.size()),\n      ConstEigenVectorMap<float>(shift_y.data(), shift_y.size()),\n      ConstEigenVectorMap<float>(shift_x.data(), shift_x.size()),\n      ConstEigenVectorMap<float>(shift_y.data(), shift_y.size());\n\n  // Broacast anchors over shifts to enumerate all anchors at all positions\n  // in the (H, W) grid:\n  //   - add A anchors of shape (1, A, 4) to\n  //   - K shifts of shape (K, 1, 4) to get\n  //   - all shifted anchors of shape (K, A, 4)\n  //   - reshape to (K*A, 4) shifted anchors\n  ConstEigenMatrixMap<float> anchors_vec(\n      anchors.template data<float>(), 1, A * 4);\n  // equivalent to python code\n  //  all_anchors = (\n  //        self._model.anchors.reshape((1, A, 4)) +\n  //        shifts.reshape((1, K, 4)).transpose((1, 0, 2)))\n  //    all_anchors = all_anchors.reshape((K * A, 4))\n  // all_anchors_vec: (K, A * 4)\n  ERMatXf all_anchors_vec =\n      anchors_vec.replicate(K, 1) + shifts.rowwise().replicate(A);\n\n  // use the following to reshape to (K * A, 4)\n  // Eigen::Map<const ERMatXf> all_anchors(all_anchors_vec.data(), K * A, 4);\n\n  return all_anchors_vec;\n}\n\n} // namespace utils\n\ntemplate <>\nvoid GenerateProposalsOp<CPUContext>::ProposalsForOneImage(\n    const Eigen::Array3f& im_info,\n    const Eigen::Map<const ERMatXf>& all_anchors,\n    const utils::ConstTensorView<float>& bbox_deltas_tensor,\n    const utils::ConstTensorView<float>& scores_tensor,\n    ERArrXXf* out_boxes,\n    EArrXf* out_probs) const {\n  const auto& pre_nms_topN = rpn_pre_nms_topN_;\n  const auto& post_nms_topN = rpn_post_nms_topN_;\n  const auto& nms_thresh = rpn_nms_thresh_;\n  const auto& min_size = rpn_min_size_;\n\n  // Transpose and reshape predicted bbox transformations to get them\n  // into the same order as the anchors:\n  //   - bbox deltas will be (4 * A, H, W) format from conv output\n  //   - transpose to (H, W, 4 * A)\n  //   - reshape to (H * W * A, 4) where rows are ordered by (H, W, A)\n  //     in slowest to fastest order to match the enumerated anchors\n  CAFFE_ENFORCE_EQ(bbox_deltas_tensor.ndim(), 3);\n  CAFFE_ENFORCE_EQ(bbox_deltas_tensor.dim(0) % 4, 0);\n  auto A = bbox_deltas_tensor.dim(0) / 4;\n  auto H = bbox_deltas_tensor.dim(1);\n  auto W = bbox_deltas_tensor.dim(2);\n  // equivalent to python code\n  //  bbox_deltas = bbox_deltas.transpose((1, 2, 0)).reshape((-1, 4))\n  ERArrXXf bbox_deltas(H * W * A, 4);\n  Eigen::Map<ERMatXf>(bbox_deltas.data(), H * W, 4 * A) =\n      Eigen::Map<const ERMatXf>(bbox_deltas_tensor.data(), A * 4, H * W)\n          .transpose();\n  CAFFE_ENFORCE_EQ(bbox_deltas.rows(), all_anchors.rows());\n\n  // - scores are (A, H, W) format from conv output\n  // - transpose to (H, W, A)\n  // - reshape to (H * W * A, 1) where rows are ordered by (H, W, A)\n  //   to match the order of anchors and bbox_deltas\n  CAFFE_ENFORCE_EQ(scores_tensor.ndim(), 3);\n  CAFFE_ENFORCE_EQ(scores_tensor.dims(), (vector<int>{A, H, W}));\n  // equivalent to python code\n  // scores = scores.transpose((1, 2, 0)).reshape((-1, 1))\n  EArrXf scores(scores_tensor.size());\n  Eigen::Map<ERMatXf>(scores.data(), H * W, A) =\n      Eigen::Map<const ERMatXf>(scores_tensor.data(), A, H * W).transpose();\n\n  // Transform anchors into proposals via bbox transformations\n  static const std::vector<float> bbox_weights{1.0, 1.0, 1.0, 1.0};\n  auto proposals = utils::bbox_transform(\n      all_anchors.array(),\n      bbox_deltas,\n      bbox_weights,\n      utils::BBOX_XFORM_CLIP_DEFAULT,\n      correct_transform_coords_);\n\n  // 2. clip proposals to image (may result in proposals with zero area\n  // that will be removed in the next step)\n  proposals = utils::clip_boxes(proposals, im_info[0], im_info[1]);\n\n  // 3. remove predicted boxes with either height or width < min_size\n  auto keep = utils::filter_boxes(proposals, min_size, im_info);\n  DCHECK_LE(keep.size(), scores.size());\n\n  // 4. sort all (proposal, score) pairs by score from highest to lowest\n  // 5. take top pre_nms_topN (e.g. 6000)\n  std::sort(keep.begin(), keep.end(), [&scores](int lhs, int rhs) {\n    return scores[lhs] > scores[rhs];\n  });\n\n  if (pre_nms_topN > 0 && pre_nms_topN < keep.size()) {\n    keep.resize(pre_nms_topN);\n  }\n\n  // 6. apply loose nms (e.g. threshold = 0.7)\n  // 7. take after_nms_topN (e.g. 300)\n  // 8. return the top proposals (-> RoIs top)\n  if (post_nms_topN > 0 && post_nms_topN < keep.size()) {\n    keep = utils::nms_cpu(proposals, scores, keep, nms_thresh, post_nms_topN);\n  } else {\n    keep = utils::nms_cpu(proposals, scores, keep, nms_thresh);\n  }\n\n  // Generate outputs\n  utils::GetSubArrayRows(proposals, utils::AsEArrXt(keep), out_boxes);\n  utils::GetSubArray(scores, utils::AsEArrXt(keep), out_probs);\n}\n\ntemplate <>\nbool GenerateProposalsOp<CPUContext>::RunOnDevice() {\n  const auto& scores = Input(0);\n  const auto& bbox_deltas = Input(1);\n  const auto& im_info_tensor = Input(2);\n  const auto& anchors = Input(3);\n  auto* out_rois = Output(0);\n  auto* out_rois_probs = Output(1);\n\n  CAFFE_ENFORCE_EQ(scores.ndim(), 4, scores.ndim());\n  CAFFE_ENFORCE(scores.template IsType<float>(), scores.meta().name());\n  const auto num_images = scores.dim(0);\n  const auto A = scores.dim(1);\n  const auto height = scores.dim(2);\n  const auto width = scores.dim(3);\n  const auto K = height * width;\n\n  // bbox_deltas: (num_images, A * 4, H, W)\n  CAFFE_ENFORCE_EQ(\n      bbox_deltas.dims(), (vector<TIndex>{num_images, 4 * A, height, width}));\n\n  // im_info_tensor: (num_images, 3), format [height, width, scale; ...]\n  CAFFE_ENFORCE_EQ(im_info_tensor.dims(), (vector<TIndex>{num_images, 3}));\n  CAFFE_ENFORCE(\n      im_info_tensor.template IsType<float>(), im_info_tensor.meta().name());\n\n  // anchors: (A, 4)\n  CAFFE_ENFORCE_EQ(anchors.dims(), (vector<TIndex>{A, 4}));\n  CAFFE_ENFORCE(anchors.template IsType<float>(), anchors.meta().name());\n\n  // Broadcast the anchors to all pixels\n  auto all_anchors_vec =\n      utils::ComputeAllAnchors(anchors, height, width, feat_stride_);\n  Eigen::Map<const ERMatXf> all_anchors(all_anchors_vec.data(), K * A, 4);\n\n  Eigen::Map<const ERArrXXf> im_info(\n      im_info_tensor.data<float>(),\n      im_info_tensor.dim(0),\n      im_info_tensor.dim(1));\n\n  const int roi_col_count = 5;\n  out_rois->Resize(0, roi_col_count);\n  out_rois_probs->Resize(0);\n\n  // Use openmp for acceleration?\n  for (int i = 0; i < num_images; i++) {\n    auto cur_im_info = im_info.row(i);\n    auto cur_bbox_deltas = GetSubTensorView<float>(bbox_deltas, i);\n    auto cur_scores = GetSubTensorView<float>(scores, i);\n\n    ERArrXXf im_i_boxes;\n    EArrXf im_i_probs;\n    ProposalsForOneImage(\n        cur_im_info,\n        all_anchors,\n        cur_bbox_deltas,\n        cur_scores,\n        &im_i_boxes,\n        &im_i_probs);\n\n    int csz = im_i_boxes.rows();\n    int cur_start_idx = out_rois->dim(0);\n\n    out_rois->Extend(csz, 50, &context_);\n    out_rois_probs->Extend(csz, 50, &context_);\n\n    // write rois\n    Eigen::Map<ERArrXXf> cur_rois(\n        out_rois->mutable_data<float>() + cur_start_idx * roi_col_count,\n        csz,\n        5);\n    cur_rois.col(0).setConstant(i);\n    cur_rois.block(0, 1, csz, 4) = im_i_boxes;\n\n    // write rois_probs\n    Eigen::Map<EArrXf>(\n        out_rois_probs->mutable_data<float>() + cur_start_idx, csz) =\n        im_i_probs;\n  }\n\n  return true;\n}\n\nnamespace {\n\nREGISTER_CPU_OPERATOR(GenerateProposals, GenerateProposalsOp<CPUContext>);\n// For backward compatibility\nREGISTER_CPU_OPERATOR(GenerateProposalsCPP, GenerateProposalsOp<CPUContext>);\n\n#ifdef CAFFE2_HAS_MKL_DNN\nREGISTER_MKL_OPERATOR(\n    GenerateProposals,\n    mkl::MKLFallbackOp<GenerateProposalsOp<CPUContext>>);\n// For backward compatibility\nREGISTER_MKL_OPERATOR(\n    GenerateProposalsCPP,\n    mkl::MKLFallbackOp<GenerateProposalsOp<CPUContext>>);\n#endif // CAFFE2_HAS_MKL_DNN\n\nOPERATOR_SCHEMA(GenerateProposals)\n    .NumInputs(4)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nGenerate bounding box proposals for Faster RCNN. The propoasls are generated for\na list of images based on image score 'score', bounding box regression result\n'deltas' as well as predefined bounding box shapes 'anchors'. Greedy\nnon-maximum suppression is applied to generate the final bounding boxes.\n)DOC\")\n    .Arg(\"spatial_scale\", \"(float) spatial scale\")\n    .Arg(\"pre_nms_topN\", \"(int) RPN_PRE_NMS_TOP_N\")\n    .Arg(\"post_nms_topN\", \"(int) RPN_POST_NMS_TOP_N\")\n    .Arg(\"nms_thresh\", \"(float) RPN_NMS_THRESH\")\n    .Arg(\"min_size\", \"(float) RPN_MIN_SIZE\")\n    .Input(0, \"scores\", \"Scores from conv layer, size (img_count, A, H, W)\")\n    .Input(\n        1,\n        \"bbox_deltas\",\n        \"Bounding box deltas from conv layer, \"\n        \"size (img_count, 4 * A, H, W)\")\n    .Input(\n        2,\n        \"im_info\",\n        \"Image info, size (img_count, 3), \"\n        \"format (height, width, scale)\")\n    .Input(3, \"anchors\", \"Bounding box anchors, size (A, 4)\")\n    .Output(\n        0,\n        \"rois\",\n        \"Proposals, size (n x 5), \"\n        \"format (image_index, x1, y1, x2, y2)\")\n    .Output(1, \"rois_probs\", \"scores of proposals, size (n)\");\n// For backward compatibility\nOPERATOR_SCHEMA(GenerateProposalsCPP).NumInputs(4).NumOutputs(2);\n\nSHOULD_NOT_DO_GRADIENT(GenerateProposals);\n// For backward compatibility\nSHOULD_NOT_DO_GRADIENT(GenerateProposalsCPP);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/generate_proposals_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_GENERATE_PROPOSALS_OP_H_\n#define CAFFE2_OPERATORS_GENERATE_PROPOSALS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/eigen_utils.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nnamespace utils {\n\n// A sub tensor view\ntemplate <class T>\nclass ConstTensorView {\n public:\n  ConstTensorView(const T* data, const std::vector<int>& dims)\n      : data_(data), dims_(dims) {}\n\n  int ndim() const {\n    return dims_.size();\n  }\n  const std::vector<int>& dims() const {\n    return dims_;\n  }\n  int dim(int i) const {\n    DCHECK_LE(i, dims_.size());\n    return dims_[i];\n  }\n  const T* data() const {\n    return data_;\n  }\n  size_t size() const {\n    return std::accumulate(\n        dims_.begin(), dims_.end(), 1, std::multiplies<size_t>());\n  }\n\n private:\n  const T* data_ = nullptr;\n  std::vector<int> dims_;\n};\n\n// Generate a list of bounding box shapes for each pixel based on predefined\n//     bounding box shapes 'anchors'.\n// anchors: predefined anchors, size(A, 4)\n// Return: all_anchors_vec: (H * W, A * 4)\n// Need to reshape to (H * W * A, 4) to match the format in python\nERMatXf ComputeAllAnchors(\n    const TensorCPU& anchors,\n    int height,\n    int width,\n    float feat_stride);\n\n} // namespace utils\n\n// C++ implementation of GenerateProposalsOp\n// Generate bounding box proposals for Faster RCNN. The propoasls are generated\n//     for a list of images based on image score 'score', bounding box\n//     regression result 'deltas' as well as predefined bounding box shapes\n//     'anchors'. Greedy non-maximum suppression is applied to generate the\n//     final bounding boxes.\n// Reference: detectron/lib/ops/generate_proposals.py\ntemplate <class Context>\nclass GenerateProposalsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  GenerateProposalsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        spatial_scale_(\n            OperatorBase::GetSingleArgument<float>(\"spatial_scale\", 1.0 / 16)),\n        feat_stride_(1.0 / spatial_scale_),\n        rpn_pre_nms_topN_(\n            OperatorBase::GetSingleArgument<int>(\"pre_nms_topN\", 6000)),\n        rpn_post_nms_topN_(\n            OperatorBase::GetSingleArgument<int>(\"post_nms_topN\", 300)),\n        rpn_nms_thresh_(\n            OperatorBase::GetSingleArgument<float>(\"nms_thresh\", 0.7f)),\n        rpn_min_size_(OperatorBase::GetSingleArgument<float>(\"min_size\", 16)),\n        correct_transform_coords_(OperatorBase::GetSingleArgument<bool>(\n            \"correct_transform_coords\",\n            false)) {}\n\n  ~GenerateProposalsOp() {}\n\n  bool RunOnDevice() override;\n\n  // Generate bounding box proposals for a given image\n  // im_info: [height, width, im_scale]\n  // all_anchors: (H * W * A, 4)\n  // bbox_deltas_tensor: (4 * A, H, W)\n  // scores_tensor: (A, H, W)\n  // out_boxes: (n, 5)\n  // out_probs: n\n  void ProposalsForOneImage(\n      const Eigen::Array3f& im_info,\n      const Eigen::Map<const ERMatXf>& all_anchors,\n      const utils::ConstTensorView<float>& bbox_deltas_tensor,\n      const utils::ConstTensorView<float>& scores_tensor,\n      ERArrXXf* out_boxes,\n      EArrXf* out_probs) const;\n\n protected:\n  // spatial_scale_ must be declared before feat_stride_\n  float spatial_scale_{1.0};\n  float feat_stride_{1.0};\n\n  // RPN_PRE_NMS_TOP_N\n  int rpn_pre_nms_topN_{6000};\n  // RPN_POST_NMS_TOP_N\n  int rpn_post_nms_topN_{300};\n  // RPN_NMS_THRESH\n  float rpn_nms_thresh_{0.7};\n  // RPN_MIN_SIZE\n  float rpn_min_size_{16};\n  // Correct bounding box transform coordates, see bbox_transform() in boxes.py\n  // Set to true to match the detectron code, set to false for backward\n  // compatibility\n  bool correct_transform_coords_{false};\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_GENERATE_PROPOSALS_OP_H_\n"
  },
  {
    "path": "caffe2/operators/generate_proposals_op_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/generate_proposals_op.h\"\n\n#include <gtest/gtest.h>\n#include \"caffe2/core/flags.h\"\n\nnamespace caffe2 {\n\nstatic void AddConstInput(\n    const vector<TIndex>& shape,\n    const float value,\n    const string& name,\n    Workspace* ws) {\n  DeviceOption option;\n  CPUContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(shape);\n  math::Set<float, CPUContext>(\n      tensor->size(), value, tensor->mutable_data<float>(), &context);\n  return;\n}\n\nstatic void AddLinSpacedInput(\n    const vector<TIndex>& shape,\n    const float min_val,\n    const float max_val,\n    const string& name,\n    Workspace* ws) {\n  DeviceOption option;\n  CPUContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(shape);\n  EigenVectorMap<float> tensor_vec(\n      tensor->mutable_data<float>(), tensor->size());\n  tensor_vec.setLinSpaced(min_val, max_val);\n\n  return;\n}\n\nstatic void AddInput(\n    const vector<TIndex>& shape,\n    const vector<float>& values,\n    const string& name,\n    Workspace* ws) {\n  DeviceOption option;\n  CPUContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(shape);\n  EigenVectorMap<float> tensor_vec(\n      tensor->mutable_data<float>(), tensor->size());\n  tensor_vec.array() = utils::AsEArrXt(values);\n\n  return;\n}\n\nTEST(GenerateProposalsTest, TestComputeAllAnchors) {\n  ERMatXf anchors(3, 4);\n  anchors << -38, -16, 53, 31, -84, -40, 99, 55, -176, -88, 191, 103;\n\n  int height = 4;\n  int width = 3;\n  float feat_stride = 16;\n  ERMatXf all_anchors_gt(36, 4);\n  all_anchors_gt << -38, -16, 53, 31, -84, -40, 99, 55, -176, -88, 191, 103,\n      -22, -16, 69, 31, -68, -40, 115, 55, -160, -88, 207, 103, -6, -16, 85, 31,\n      -52, -40, 131, 55, -144, -88, 223, 103, -38, 0, 53, 47, -84, -24, 99, 71,\n      -176, -72, 191, 119, -22, 0, 69, 47, -68, -24, 115, 71, -160, -72, 207,\n      119, -6, 0, 85, 47, -52, -24, 131, 71, -144, -72, 223, 119, -38, 16, 53,\n      63, -84, -8, 99, 87, -176, -56, 191, 135, -22, 16, 69, 63, -68, -8, 115,\n      87, -160, -56, 207, 135, -6, 16, 85, 63, -52, -8, 131, 87, -144, -56, 223,\n      135, -38, 32, 53, 79, -84, 8, 99, 103, -176, -40, 191, 151, -22, 32, 69,\n      79, -68, 8, 115, 103, -160, -40, 207, 151, -6, 32, 85, 79, -52, 8, 131,\n      103, -144, -40, 223, 151;\n\n  TensorCPU anchors_tensor(vector<TIndex>{anchors.rows(), anchors.cols()});\n  Eigen::Map<ERMatXf>(\n      anchors_tensor.mutable_data<float>(), anchors.rows(), anchors.cols()) =\n      anchors;\n\n  auto result =\n      utils::ComputeAllAnchors(anchors_tensor, height, width, feat_stride);\n  Eigen::Map<const ERMatXf> all_anchors_result(\n      result.data(), height * width * anchors.rows(), 4);\n\n  EXPECT_EQ((all_anchors_result - all_anchors_gt).norm(), 0);\n}\n\nTEST(GenerateProposalsTest, TestEmpty) {\n  Workspace ws;\n  OperatorDef def;\n  def.set_name(\"test\");\n  def.set_type(\"GenerateProposals\");\n  def.add_input(\"scores\");\n  def.add_input(\"bbox_deltas\");\n  def.add_input(\"im_info\");\n  def.add_input(\"anchors\");\n  def.add_output(\"rois\");\n  def.add_output(\"rois_probs\");\n  const int img_count = 3;\n  const int A = 4;\n  const int H = 10;\n  const int W = 8;\n  AddConstInput(vector<TIndex>{img_count, A, H, W}, 1., \"scores\", &ws);\n  AddLinSpacedInput(\n      vector<TIndex>{img_count, 4 * A, H, W}, 0, 10, \"bbox_deltas\", &ws);\n  AddConstInput(vector<TIndex>{img_count, 3}, 0.1, \"im_info\", &ws);\n  AddConstInput(vector<TIndex>{A, 4}, 1.0, \"anchors\", &ws);\n\n  def.add_arg()->CopyFrom(MakeArgument(\"spatial_scale\", 2.0f));\n\n  unique_ptr<OperatorBase> op(CreateOperator(def, &ws));\n  EXPECT_NE(nullptr, op.get());\n  EXPECT_TRUE(op->Run());\n  Blob* rois_blob = ws.GetBlob(\"rois\");\n  EXPECT_NE(nullptr, rois_blob);\n  auto& rois = rois_blob->Get<TensorCPU>();\n  EXPECT_EQ(rois.size(), 0);\n\n  Blob* rois_probs_blob = ws.GetBlob(\"rois_probs\");\n  EXPECT_NE(nullptr, rois_probs_blob);\n  auto& rois_probs = rois_probs_blob->Get<TensorCPU>();\n  EXPECT_EQ(rois_probs.size(), 0);\n}\n\nTEST(GenerateProposalsTest, TestRealDownSampled) {\n  Workspace ws;\n  OperatorDef def;\n  def.set_name(\"test\");\n  def.set_type(\"GenerateProposals\");\n  def.add_input(\"scores\");\n  def.add_input(\"bbox_deltas\");\n  def.add_input(\"im_info\");\n  def.add_input(\"anchors\");\n  def.add_output(\"rois\");\n  def.add_output(\"rois_probs\");\n  const int img_count = 1;\n  const int A = 2;\n  const int H = 4;\n  const int W = 5;\n\n  vector<float> scores{\n      5.44218998e-03f, 1.19207997e-03f, 1.12379994e-03f, 1.17181998e-03f,\n      1.20544003e-03f, 6.17993006e-04f, 1.05261997e-05f, 8.91025957e-06f,\n      9.29536981e-09f, 6.09605013e-05f, 4.72735002e-04f, 1.13482002e-10f,\n      1.50015003e-05f, 4.45032993e-06f, 3.21612994e-08f, 8.02662980e-04f,\n      1.40488002e-04f, 3.12508007e-07f, 3.02616991e-06f, 1.97759000e-08f,\n      2.66913995e-02f, 5.26766013e-03f, 5.05053019e-03f, 5.62100019e-03f,\n      5.37420018e-03f, 5.26280981e-03f, 2.48894998e-04f, 1.06842002e-04f,\n      3.92931997e-06f, 1.79388002e-03f, 4.79440019e-03f, 3.41609990e-07f,\n      5.20430971e-04f, 3.34090000e-05f, 2.19159006e-07f, 2.28786003e-03f,\n      5.16703985e-05f, 4.04523007e-06f, 1.79227004e-06f, 5.32449000e-08f};\n  vector<float> bbx{\n      -1.65040009e-02f, -1.84051003e-02f, -1.85930002e-02f, -2.08263006e-02f,\n      -1.83814000e-02f, -2.89172009e-02f, -3.89706008e-02f, -7.52277970e-02f,\n      -1.54091999e-01f, -2.55433004e-02f, -1.77490003e-02f, -1.10340998e-01f,\n      -4.20190990e-02f, -2.71421000e-02f, 6.89801015e-03f,  5.71171008e-02f,\n      -1.75665006e-01f, 2.30021998e-02f,  3.08554992e-02f,  -1.39333997e-02f,\n      3.40579003e-01f,  3.91070992e-01f,  3.91624004e-01f,  3.92527014e-01f,\n      3.91445011e-01f,  3.79328012e-01f,  4.26631987e-01f,  3.64892989e-01f,\n      2.76894987e-01f,  5.13985991e-01f,  3.79999995e-01f,  1.80457994e-01f,\n      4.37402993e-01f,  4.18545991e-01f,  2.51549989e-01f,  4.48318988e-01f,\n      1.68564007e-01f,  4.65440989e-01f,  4.21891987e-01f,  4.45928007e-01f,\n      3.27155995e-03f,  3.71480011e-03f,  3.60032008e-03f,  4.27092984e-03f,\n      3.74579988e-03f,  5.95752988e-03f,  -3.14473989e-03f, 3.52022005e-03f,\n      -1.88564006e-02f, 1.65188999e-03f,  1.73791999e-03f,  -3.56074013e-02f,\n      -1.66615995e-04f, 3.14146001e-03f,  -1.11830998e-02f, -5.35363983e-03f,\n      6.49790000e-03f,  -9.27671045e-03f, -2.83346009e-02f, -1.61233004e-02f,\n      -2.15505004e-01f, -2.19910994e-01f, -2.20872998e-01f, -2.12831005e-01f,\n      -2.19145000e-01f, -2.27687001e-01f, -3.43973994e-01f, -2.75869995e-01f,\n      -3.19516987e-01f, -2.50418007e-01f, -2.48537004e-01f, -5.08224010e-01f,\n      -2.28724003e-01f, -2.82402009e-01f, -3.75815988e-01f, -2.86352992e-01f,\n      -5.28333001e-02f, -4.43836004e-01f, -4.55134988e-01f, -4.34897989e-01f,\n      -5.65053988e-03f, -9.25739005e-04f, -1.06790999e-03f, -2.37016007e-03f,\n      -9.71166010e-04f, -8.90910998e-03f, -1.17592998e-02f, -2.08992008e-02f,\n      -4.94231991e-02f, 6.63906988e-03f,  3.20469006e-03f,  -6.44695014e-02f,\n      -3.11607006e-03f, 2.02738005e-03f,  1.48096997e-02f,  4.39785011e-02f,\n      -8.28424022e-02f, 3.62076014e-02f,  2.71668993e-02f,  1.38250999e-02f,\n      6.76669031e-02f,  1.03252999e-01f,  1.03255004e-01f,  9.89722982e-02f,\n      1.03646003e-01f,  4.79663983e-02f,  1.11014001e-01f,  9.31736007e-02f,\n      1.15768999e-01f,  1.04014002e-01f,  -8.90677981e-03f, 1.13103002e-01f,\n      1.33085996e-01f,  1.25405997e-01f,  1.50051996e-01f,  -1.13038003e-01f,\n      7.01059997e-02f,  1.79651007e-01f,  1.41055003e-01f,  1.62841007e-01f,\n      -1.00247003e-02f, -8.17587040e-03f, -8.32176022e-03f, -8.90108012e-03f,\n      -8.13035015e-03f, -1.77263003e-02f, -3.69572006e-02f, -3.51580009e-02f,\n      -5.92143014e-02f, -1.80795006e-02f, -5.46086021e-03f, -4.10550982e-02f,\n      -1.83081999e-02f, -2.15411000e-02f, -1.17953997e-02f, 3.33894007e-02f,\n      -5.29635996e-02f, -6.97528012e-03f, -3.15250992e-03f, -3.27355005e-02f,\n      1.29676998e-01f,  1.16080999e-01f,  1.15947001e-01f,  1.21797003e-01f,\n      1.16089001e-01f,  1.44875005e-01f,  1.15617000e-01f,  1.31586999e-01f,\n      1.74735002e-02f,  1.21973999e-01f,  1.31596997e-01f,  2.48907991e-02f,\n      6.18605018e-02f,  1.12855002e-01f,  -6.99798986e-02f, 9.58312973e-02f,\n      1.53593004e-01f,  -8.75087008e-02f, -4.92327996e-02f, -3.32239009e-02f};\n  vector<float> im_info{60, 80, 0.166667f};\n  vector<float> anchors{-38, -16, 53, 31, -120, -120, 135, 135};\n\n  ERMatXf rois_gt(9, 5);\n  rois_gt << 0, 0, 0, 79, 59, 0, 0, 5.0005703f, 52.63237f, 43.69501495f, 0,\n      24.13628387f, 7.51243401f, 79, 46.06628418f, 0, 0, 7.50924301f,\n      68.47792816f, 46.03357315f, 0, 0, 23.09477997f, 51.61448669f, 59, 0, 0,\n      39.52141571f, 52.44710541f, 59, 0, 23.57396317f, 29.98791885f, 79, 59, 0,\n      0, 41.90219116f, 79, 59, 0, 0, 23.30098343f, 79, 59;\n  vector<float> rois_probs_gt{2.66913995e-02f,\n                              5.44218998e-03f,\n                              1.20544003e-03f,\n                              1.19207997e-03f,\n                              6.17993006e-04f,\n                              4.72735002e-04f,\n                              6.09605013e-05f,\n                              1.50015003e-05f,\n                              8.91025957e-06f};\n\n  AddInput(vector<TIndex>{img_count, A, H, W}, scores, \"scores\", &ws);\n  AddInput(vector<TIndex>{img_count, 4 * A, H, W}, bbx, \"bbox_deltas\", &ws);\n  AddInput(vector<TIndex>{img_count, 3}, im_info, \"im_info\", &ws);\n  AddInput(vector<TIndex>{A, 4}, anchors, \"anchors\", &ws);\n\n  def.add_arg()->CopyFrom(MakeArgument(\"spatial_scale\", 1.0f / 16.0f));\n  def.add_arg()->CopyFrom(MakeArgument(\"pre_nms_topN\", 6000));\n  def.add_arg()->CopyFrom(MakeArgument(\"post_nms_topN\", 300));\n  def.add_arg()->CopyFrom(MakeArgument(\"nms_thresh\", 0.7f));\n  def.add_arg()->CopyFrom(MakeArgument(\"min_size\", 16.0f));\n\n  unique_ptr<OperatorBase> op(CreateOperator(def, &ws));\n  EXPECT_NE(nullptr, op.get());\n  EXPECT_TRUE(op->Run());\n\n  // test rois\n  Blob* rois_blob = ws.GetBlob(\"rois\");\n  EXPECT_NE(nullptr, rois_blob);\n  auto& rois = rois_blob->Get<TensorCPU>();\n  EXPECT_EQ(rois.dims(), (vector<TIndex>{rois_gt.rows(), rois_gt.cols()}));\n  auto rois_data =\n      Eigen::Map<const ERMatXf>(rois.data<float>(), rois.dim(0), rois.dim(1));\n  EXPECT_NEAR((rois_data.matrix() - rois_gt).cwiseAbs().maxCoeff(), 0, 1e-4);\n\n  // test rois_probs\n  Blob* rois_probs_blob = ws.GetBlob(\"rois_probs\");\n  EXPECT_NE(nullptr, rois_probs_blob);\n  auto& rois_probs = rois_probs_blob->Get<TensorCPU>();\n  EXPECT_EQ(rois_probs.dims(), (vector<TIndex>{TIndex(rois_probs_gt.size())}));\n  auto rois_probs_data =\n      ConstEigenVectorArrayMap<float>(rois_probs.data<float>(), rois.dim(0));\n  EXPECT_NEAR(\n      (rois_probs_data.matrix() - utils::AsEArrXt(rois_probs_gt).matrix())\n          .cwiseAbs()\n          .maxCoeff(),\n      0,\n      1e-4);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/generate_proposals_op_util_boxes.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_UTILS_BOXES_H_\n#define CAFFE2_OPERATORS_UTILS_BOXES_H_\n\n#include \"caffe2/utils/eigen_utils.h\"\n#include \"caffe2/utils/math.h\"\n\n// Bounding box utils for generate_proposals_op\n// Reference: detectron/lib/utils/boxes.py\n\nnamespace caffe2 {\nnamespace utils {\n\n// Default value for minimum bounding box width and height after bounding box\n//     transformation (bbox_transform()) in log-space\nconst float BBOX_XFORM_CLIP_DEFAULT = log(1000.0 / 16.0);\n\n// Forward transform that maps proposal boxes to ground-truth boxes using\n//     bounding-box regression deltas.\n// boxes: pixel coordinates of the bounding boxes\n//     size (M, 4), format [x1; y1; x2; y2], x2 >= x1, y2 >= y1\n// deltas: bounding box translations and scales\n//     size (M, 4), format [dx; dy; dw; dh]\n//     dx, dy: scale-invariant translation of the center of the bounding box\n//     dw, dh: log-space sclaing of the width and height of the bounding box\n// weights: weights [wx, wy, ww, wh] for the deltas\n// bbox_xform_clip: minimum bounding box width and height in log-space after\n//     transofmration\n// correct_transform_coords: Correct bounding box transform coordates. Set to\n//     true to match the detectron code, set to false for backward compatibility\n// return: pixel coordinates of the bounding boxes\n//     size (M, 4), format [x1; y1; x2; y2]\n// see \"Rich feature hierarchies for accurate object detection and semantic\n//     segmentation\" Appendix C for more details\n// reference: detectron/lib/utils/boxes.py bbox_transform()\ntemplate <class Derived1, class Derived2>\nEArrXXt<typename Derived1::Scalar> bbox_transform(\n    const Eigen::ArrayBase<Derived1>& boxes,\n    const Eigen::ArrayBase<Derived2>& deltas,\n    const std::vector<typename Derived2::Scalar>& weights =\n        std::vector<typename Derived2::Scalar>{1.0, 1.0, 1.0, 1.0},\n    const float bbox_xform_clip = BBOX_XFORM_CLIP_DEFAULT,\n    const bool correct_transform_coords = false) {\n  using T = typename Derived1::Scalar;\n  using EArrXX = EArrXXt<T>;\n  using EArrX = EArrXt<T>;\n\n  if (boxes.rows() == 0) {\n    return EArrXX::Zero(T(0), deltas.cols());\n  }\n\n  CAFFE_ENFORCE_EQ(boxes.rows(), deltas.rows());\n  CAFFE_ENFORCE_EQ(boxes.cols(), 4);\n  CAFFE_ENFORCE_EQ(deltas.cols(), 4);\n\n  EArrX widths = boxes.col(2) - boxes.col(0) + T(1.0);\n  EArrX heights = boxes.col(3) - boxes.col(1) + T(1.0);\n  auto ctr_x = boxes.col(0) + T(0.5) * widths;\n  auto ctr_y = boxes.col(1) + T(0.5) * heights;\n\n  auto dx = deltas.col(0).template cast<T>() / weights[0];\n  auto dy = deltas.col(1).template cast<T>() / weights[1];\n  auto dw =\n      (deltas.col(2).template cast<T>() / weights[2]).cwiseMin(bbox_xform_clip);\n  auto dh =\n      (deltas.col(3).template cast<T>() / weights[3]).cwiseMin(bbox_xform_clip);\n\n  EArrX pred_ctr_x = dx * widths + ctr_x;\n  EArrX pred_ctr_y = dy * heights + ctr_y;\n  EArrX pred_w = dw.exp() * widths;\n  EArrX pred_h = dh.exp() * heights;\n\n  T offset(correct_transform_coords ? 1.0 : 0.0);\n\n  EArrXX pred_boxes = EArrXX::Zero(deltas.rows(), deltas.cols());\n  // x1\n  pred_boxes.col(0) = pred_ctr_x - T(0.5) * pred_w;\n  // y1\n  pred_boxes.col(1) = pred_ctr_y - T(0.5) * pred_h;\n  // x2\n  pred_boxes.col(2) = pred_ctr_x + T(0.5) * pred_w - offset;\n  // y2\n  pred_boxes.col(3) = pred_ctr_y + T(0.5) * pred_h - offset;\n\n  return pred_boxes;\n}\n\n// Clip boxes to image boundaries\n// boxes: pixel coordinates of bounding box, size (M * 4)\ntemplate <class Derived>\nEArrXXt<typename Derived::Scalar>\nclip_boxes(const Eigen::ArrayBase<Derived>& boxes, int height, int width) {\n  CAFFE_ENFORCE_EQ(boxes.cols(), 4);\n\n  EArrXXt<typename Derived::Scalar> ret(boxes.rows(), boxes.cols());\n\n  // x1 >= 0 && x1 < width\n  ret.col(0) = boxes.col(0).cwiseMin(width - 1).cwiseMax(0);\n  // y1 >= 0 && y1 < height\n  ret.col(1) = boxes.col(1).cwiseMin(height - 1).cwiseMax(0);\n  // x2 >= 0 && x2 < width\n  ret.col(2) = boxes.col(2).cwiseMin(width - 1).cwiseMax(0);\n  // y2 >= 0 && y2 < height\n  ret.col(3) = boxes.col(3).cwiseMin(height - 1).cwiseMax(0);\n\n  return ret;\n}\n\n// Only keep boxes with both sides >= min_size and center within the image.\n// boxes: pixel coordinates of bounding box, size (M * 4)\n// im_info: [height, width, img_scale]\n// return: row indices for 'boxes'\ntemplate <class Derived>\nstd::vector<int> filter_boxes(\n    const Eigen::ArrayBase<Derived>& boxes,\n    double min_size,\n    const Eigen::Array3f& im_info) {\n  CAFFE_ENFORCE_EQ(boxes.cols(), 4);\n\n  // Scale min_size to match image scale\n  min_size *= im_info[2];\n\n  using T = typename Derived::Scalar;\n  using EArrX = EArrXt<T>;\n\n  EArrX ws = boxes.col(2) - boxes.col(0) + T(1);\n  EArrX hs = boxes.col(3) - boxes.col(1) + T(1);\n  EArrX x_ctr = boxes.col(0) + ws / T(2);\n  EArrX y_ctr = boxes.col(1) + hs / T(2);\n\n  EArrXb keep = (ws >= min_size) && (hs >= min_size) &&\n      (x_ctr < T(im_info[1])) && (y_ctr < T(im_info[0]));\n\n  return GetArrayIndices(keep);\n}\n\n} // namespace utils\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_UTILS_BOXES_H_\n"
  },
  {
    "path": "caffe2/operators/generate_proposals_op_util_boxes_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/generate_proposals_op_util_boxes.h\"\n\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\n\nTEST(UtilsBoxesTest, TestBboxTransformRandom) {\n  using EMatXf = Eigen::MatrixXf;\n\n  EMatXf bbox(5, 4);\n  bbox << 175.62031555, 20.91103172, 253.352005, 155.0145874, 169.24636841,\n      4.85241556, 228.8605957, 105.02092743, 181.77426147, 199.82876587,\n      192.88427734, 214.0255127, 174.36262512, 186.75761414, 296.19091797,\n      231.27906799, 22.73153877, 92.02596283, 135.5695343, 208.80291748;\n\n  EMatXf deltas(5, 4);\n  deltas << 0.47861834, 0.13992102, 0.14961673, 0.71495209, 0.29915856,\n      -0.35664671, 0.89018666, 0.70815367, -0.03852064, 0.44466892, 0.49492538,\n      0.71409376, 0.28052918, 0.02184832, 0.65289006, 1.05060139, -0.38172557,\n      -0.08533806, -0.60335309, 0.79052375;\n\n  EMatXf result_gt(5, 4);\n  result_gt << 206.94953073, -30.71519157, 298.3876512, 245.44846569,\n      143.8712194, -83.34289038, 291.50227513, 122.05339902, 177.43029521,\n      198.66623633, 197.29527254, 229.70308414, 152.25190373, 145.43156421,\n      388.21547899, 275.59425266, 5.06242193, 11.04094661, 67.32890274,\n      270.68622005;\n\n  const float BBOX_XFORM_CLIP = log(1000.0 / 16.0);\n  auto result = utils::bbox_transform(\n      bbox.array(),\n      deltas.array(),\n      std::vector<float>{1.0, 1.0, 1.0, 1.0},\n      BBOX_XFORM_CLIP);\n  EXPECT_NEAR((result.matrix() - result_gt).norm(), 0.0, 1e-4);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/generate_proposals_op_util_nms.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_UTILS_NMS_H_\n#define CAFFE2_OPERATORS_UTILS_NMS_H_\n\n#include <list>\n#include <vector>\n\n#include \"caffe2/utils/eigen_utils.h\"\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\nnamespace utils {\n\n// Greedy non-maximum suppression for proposed bounding boxes\n// Reject a bounding box if its region has an intersection-overunion (IoU)\n//    overlap with a higher scoring selected bounding box larger than a\n//    threshold.\n// Reference: detectron/lib/utils/cython_nms.pyx\n// proposals: pixel coordinates of proposed bounding boxes,\n//    size: (M, 4), format: [x1; y1; x2; y2]\n// scores: scores for each bounding box, size: (M, 1)\n// sorted_indices: indices that sorts the scores from high to low\n// return: row indices of the selected proposals\ntemplate <class Derived1, class Derived2>\nstd::vector<int> nms_cpu(\n    const Eigen::ArrayBase<Derived1>& proposals,\n    const Eigen::ArrayBase<Derived2>& scores,\n    const std::vector<int>& sorted_indices,\n    float thresh,\n    int topN = -1) {\n  CAFFE_ENFORCE_EQ(proposals.rows(), scores.rows());\n  CAFFE_ENFORCE_EQ(proposals.cols(), 4);\n  CAFFE_ENFORCE_EQ(scores.cols(), 1);\n  CAFFE_ENFORCE_LE(sorted_indices.size(), proposals.rows());\n\n  using EArrX = EArrXt<typename Derived1::Scalar>;\n\n  auto x1 = proposals.col(0);\n  auto y1 = proposals.col(1);\n  auto x2 = proposals.col(2);\n  auto y2 = proposals.col(3);\n\n  EArrX areas = (x2 - x1 + 1.0) * (y2 - y1 + 1.0);\n\n  EArrXi order = AsEArrXt(sorted_indices);\n  std::vector<int> keep;\n  int ci = 0;\n  while (order.size() > 0) {\n    // exit if already enough proposals\n    if (topN >= 0 && keep.size() >= topN) {\n      break;\n    }\n\n    int i = order[0];\n    keep.push_back(i);\n    ConstEigenVectorArrayMap<int> rest_indices(\n        order.data() + 1, order.size() - 1);\n    EArrX xx1 = GetSubArray(x1, rest_indices).cwiseMax(x1[i]);\n    EArrX yy1 = GetSubArray(y1, rest_indices).cwiseMax(y1[i]);\n    EArrX xx2 = GetSubArray(x2, rest_indices).cwiseMin(x2[i]);\n    EArrX yy2 = GetSubArray(y2, rest_indices).cwiseMin(y2[i]);\n\n    EArrX w = (xx2 - xx1 + 1.0).cwiseMax(0.0);\n    EArrX h = (yy2 - yy1 + 1.0).cwiseMax(0.0);\n    EArrX inter = w * h;\n    EArrX ovr = inter / (areas[i] + GetSubArray(areas, rest_indices) - inter);\n\n    // indices for sub array order[1:n]\n    auto inds = GetArrayIndices(ovr <= thresh);\n    order = GetSubArray(order, AsEArrXt(inds) + 1);\n  }\n\n  return keep;\n}\n\n// Greedy non-maximum suppression for proposed bounding boxes\n// Reject a bounding box if its region has an intersection-overunion (IoU)\n//    overlap with a higher scoring selected bounding box larger than a\n//    threshold.\n// Reference: detectron/lib/utils/cython_nms.pyx\n// proposals: pixel coordinates of proposed bounding boxes,\n//    size: (M, 4), format: [x1; y1; x2; y2]\n// scores: scores for each bounding box, size: (M, 1)\n// return: row indices of the selected proposals\ntemplate <class Derived1, class Derived2>\nstd::vector<int> nms_cpu(\n    const Eigen::ArrayBase<Derived1>& proposals,\n    const Eigen::ArrayBase<Derived2>& scores,\n    float thres) {\n  std::vector<int> indices(proposals.rows());\n  std::iota(indices.begin(), indices.end(), 0);\n  std::sort(\n      indices.data(),\n      indices.data() + indices.size(),\n      [&scores](int lhs, int rhs) { return scores(lhs) > scores(rhs); });\n\n  return nms_cpu(proposals, scores, indices, thres);\n}\n\n/**\n * Soft-NMS implementation as outlined in https://arxiv.org/abs/1704.04503.\n * Reference: detectron/lib/utils/cython_nms.pyx\n * out_scores: Output updated scores after applying Soft-NMS\n * proposals: pixel coordinates of proposed bounding boxes,\n *    size: (M, 4), format: [x1; y1; x2; y2]\n * scores: scores for each bounding box, size: (M, 1)\n * indices: Indices to consider within proposals and scores. Can be used\n *     to pre-filter proposals/scores based on some threshold.\n * sigma: Standard deviation for Gaussian\n * overlap_thresh: Similar to original NMS\n * score_thresh: If updated score falls below this thresh, discard proposal\n * method: 0 - Hard (original) NMS, 1 - Linear, 2 - Gaussian\n * return: row indices of the selected proposals\n */\ntemplate <class Derived1, class Derived2, class Derived3>\nstd::vector<int> soft_nms_cpu(\n    Eigen::ArrayBase<Derived3>* out_scores,\n    const Eigen::ArrayBase<Derived1>& proposals,\n    const Eigen::ArrayBase<Derived2>& scores,\n    const std::vector<int>& indices,\n    float sigma = 0.5,\n    float overlap_thresh = 0.3,\n    float score_thresh = 0.001,\n    unsigned int method = 1,\n    int topN = -1) {\n  CAFFE_ENFORCE_EQ(proposals.rows(), scores.rows());\n  CAFFE_ENFORCE_EQ(proposals.cols(), 4);\n  CAFFE_ENFORCE_EQ(scores.cols(), 1);\n\n  using EArrX = EArrXt<typename Derived1::Scalar>;\n\n  const auto& x1 = proposals.col(0);\n  const auto& y1 = proposals.col(1);\n  const auto& x2 = proposals.col(2);\n  const auto& y2 = proposals.col(3);\n\n  EArrX areas = (x2 - x1 + 1.0) * (y2 - y1 + 1.0);\n\n  // Initialize out_scores with original scores. Will be iteratively updated\n  // as Soft-NMS is applied.\n  *out_scores = scores;\n\n  std::vector<int> keep;\n  EArrXi pending = AsEArrXt(indices);\n  while (pending.size() > 0) {\n    // Exit if already enough proposals\n    if (topN >= 0 && keep.size() >= topN) {\n      break;\n    }\n\n    // Find proposal with max score among remaining proposals\n    int max_pos;\n    auto max_score = GetSubArray(*out_scores, pending).maxCoeff(&max_pos);\n    int i = pending[max_pos];\n    keep.push_back(i);\n\n    // Compute IoU of the remaining boxes with the identified max box\n    std::swap(pending(0), pending(max_pos));\n    const auto& rest_indices = pending.tail(pending.size() - 1);\n    EArrX xx1 = GetSubArray(x1, rest_indices).cwiseMax(x1[i]);\n    EArrX yy1 = GetSubArray(y1, rest_indices).cwiseMax(y1[i]);\n    EArrX xx2 = GetSubArray(x2, rest_indices).cwiseMin(x2[i]);\n    EArrX yy2 = GetSubArray(y2, rest_indices).cwiseMin(y2[i]);\n\n    EArrX w = (xx2 - xx1 + 1.0).cwiseMax(0.0);\n    EArrX h = (yy2 - yy1 + 1.0).cwiseMax(0.0);\n    EArrX inter = w * h;\n    EArrX ovr = inter / (areas[i] + GetSubArray(areas, rest_indices) - inter);\n\n    // Update scores based on computed IoU, overlap threshold and NMS method\n    for (int j = 0; j < rest_indices.size(); ++j) {\n      typename Derived2::Scalar weight;\n      switch (method) {\n        case 1: // Linear\n          weight = (ovr(j) > overlap_thresh) ? (1.0 - ovr(j)) : 1.0;\n          break;\n        case 2: // Gaussian\n          weight = std::exp(-1.0 * ovr(j) * ovr(j) / sigma);\n          break;\n        default: // Original NMS\n          weight = (ovr(j) > overlap_thresh) ? 0.0 : 1.0;\n      }\n      (*out_scores)(rest_indices[j]) *= weight;\n    }\n\n    // Discard boxes with new scores below min threshold and update pending\n    // indices\n    const auto& rest_scores = GetSubArray(*out_scores, rest_indices);\n    const auto& inds = GetArrayIndices(rest_scores >= score_thresh);\n    pending = GetSubArray(rest_indices, AsEArrXt(inds));\n  }\n\n  return keep;\n}\n\ntemplate <class Derived1, class Derived2, class Derived3>\nstd::vector<int> soft_nms_cpu(\n    Eigen::ArrayBase<Derived3>* out_scores,\n    const Eigen::ArrayBase<Derived1>& proposals,\n    const Eigen::ArrayBase<Derived2>& scores,\n    float sigma = 0.5,\n    float overlap_thresh = 0.3,\n    float score_thresh = 0.001,\n    unsigned int method = 1,\n    int topN = -1) {\n  std::vector<int> indices(proposals.rows());\n  std::iota(indices.begin(), indices.end(), 0);\n  return soft_nms_cpu(\n      out_scores,\n      proposals,\n      scores,\n      indices,\n      sigma,\n      overlap_thresh,\n      score_thresh,\n      method,\n      topN);\n}\n\n} // namespace utils\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_UTILS_NMS_H_\n"
  },
  {
    "path": "caffe2/operators/generate_proposals_op_util_nms_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"generate_proposals_op_util_nms.h\"\n\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\n\nTEST(UtilsNMSTest, TestNMS) {\n  Eigen::ArrayXXf input(5, 5);\n  input << 10, 10, 50, 60, 0.5, 11, 12, 48, 60, 0.7, 8, 9, 40, 50, 0.6, 100,\n      100, 150, 140, 0.9, 99, 110, 155, 139, 0.8;\n  std::vector<float> input_thresh{0.1f, 0.3f, 0.5f, 0.8f, 0.9f};\n  // ground truth generated based on detection.caffe2/lib/nms/py_cpu_nms.py\n  std::vector<std::vector<int>> output_gt{\n      {3, 1}, {3, 1}, {3, 1}, {3, 4, 1, 2}, {3, 4, 1, 2, 0}};\n\n  // test utils::nms_cpu without indices input\n  auto proposals = input.block(0, 0, input.rows(), 4);\n  auto scores = input.col(4);\n  for (int i = 0; i < input_thresh.size(); i++) {\n    auto cur_out = utils::nms_cpu(proposals, scores, input_thresh[i]);\n    EXPECT_EQ(output_gt[i], cur_out);\n  }\n\n  // test utils::nms_cpu with indices\n  std::vector<int> indices(proposals.rows());\n  std::iota(indices.begin(), indices.end(), 0);\n  std::sort(\n      indices.data(),\n      indices.data() + indices.size(),\n      [&scores](int lhs, int rhs) { return scores(lhs) > scores(rhs); });\n  for (int i = 0; i < input_thresh.size(); i++) {\n    auto cur_out = utils::nms_cpu(proposals, scores, indices, input_thresh[i]);\n    EXPECT_EQ(output_gt[i], cur_out);\n  }\n\n  // test utils::nms_cpu with topN\n  std::vector<int> top_n = {1, 1, 2, 2, 3};\n  auto gt_out = output_gt;\n  for (int i = 0; i < input_thresh.size(); i++) {\n    auto cur_out =\n        utils::nms_cpu(proposals, scores, indices, input_thresh[i], top_n[i]);\n    gt_out[i].resize(top_n[i]);\n    EXPECT_EQ(gt_out[i], cur_out);\n  }\n}\n\nTEST(UtilsNMSTest, TestSoftNMS) {\n  Eigen::ArrayXXf input(5, 5);\n  input.row(0) << 5.18349426e+02, 1.77783920e+02, 9.06085266e+02,\n      2.59163239e+02, 8.17906916e-01;\n  input.row(1) << 2.11392624e+02, 1.76144958e+02, 6.14215149e+02,\n      2.48934662e+02, 9.52467501e-01;\n  input.row(2) << 4.65724518e+02, 1.83594269e+02, 9.39000000e+02,\n      2.55136627e+02, 6.73921347e-01;\n  input.row(3) << 6.07164246e+02, 2.60230377e+02, 8.32768127e+02,\n      3.39919891e+02, 9.99834776e-01;\n  input.row(4) << 3.23936859e+02, 3.43427063e+02, 6.20561157e+02,\n      3.98286072e+02, 9.99737203e-01;\n\n  const auto& proposals = input.block(0, 0, input.rows(), 4);\n  const auto& scores = input.col(4);\n\n  vector<int> method{1, 1, 2, 2};\n  vector<float> overlap_thresh{0.1f, 0.3f, 0.1f, 0.3f};\n\n  // Ground truth generated based on\n  //   detectron/lib/utils/cython_nms.pyx\n  std::vector<int> keep_gt{3, 4, 1, 0, 2};\n\n  // Explicitly use colmajor order to match scores\n  Eigen::ArrayXXf scores_gt(5, 4);\n  // Linear, overlap_thresh=0.1\n  scores_gt.col(0) << 7.13657320e-01, 9.52467501e-01, 1.44501388e-01,\n      9.99834776e-01, 9.99737203e-01;\n  // Linear, overlap_thresh=0.3\n  scores_gt.col(1) << 8.17906916e-01, 9.52467501e-01, 1.76800430e-01,\n      9.99834776e-01, 9.99737203e-01;\n  // Gaussian, overlap_thresh=0.1\n  scores_gt.col(2) << 7.91758895e-01, 9.52467501e-01, 2.12320581e-01,\n      9.99834776e-01, 9.99737203e-01;\n  // Gaussian, overlap_thresh=0.3\n  scores_gt.col(3) << 7.91758895e-01, 9.52467501e-01, 2.12320581e-01,\n      9.99834776e-01, 9.99737203e-01;\n\n  Eigen::ArrayXf out_scores;\n  for (int i = 0; i < method.size(); ++i) {\n    LOG(INFO) << \"Testing SoftNMS with method=\" << method[i]\n              << \", overlap_thresh=\" << overlap_thresh[i];\n    const auto& expected_scores = scores_gt.col(i);\n\n    auto keep = utils::soft_nms_cpu(\n        &out_scores,\n        proposals,\n        scores,\n        0.5,\n        overlap_thresh[i],\n        0.0001,\n        method[i]);\n    EXPECT_EQ(keep, keep_gt);\n    {\n      auto diff = expected_scores - out_scores;\n      EXPECT_TRUE((diff.abs() < 1e-6).all());\n    }\n\n    // Test with topN\n    for (int topN = 1; topN <= 3; ++topN) {\n      keep = utils::soft_nms_cpu(\n          &out_scores,\n          proposals,\n          scores,\n          0.5,\n          overlap_thresh[i],\n          0.0001,\n          method[i],\n          topN);\n      std::vector<int> expected_keep(keep_gt.begin(), keep_gt.begin() + topN);\n      EXPECT_EQ(expected_keep, keep);\n    }\n\n    // Test with filtered indices\n    auto indices = utils::GetArrayIndices(scores >= 0.9);\n    keep = utils::soft_nms_cpu(\n        &out_scores,\n        proposals,\n        scores,\n        indices,\n        0.5,\n        overlap_thresh[i],\n        0.0001,\n        method[i]);\n    std::sort(keep.begin(), keep.end());\n    EXPECT_EQ(indices, keep);\n    {\n      const auto& expected = utils::GetSubArray(expected_scores, indices);\n      const auto& actual = utils::GetSubArray(out_scores, indices);\n      EXPECT_TRUE(((expected - actual).abs() < 1e-6).all());\n    }\n\n    // Test with high score_thresh\n    float score_thresh = 0.9;\n    keep = utils::soft_nms_cpu(\n        &out_scores,\n        proposals,\n        scores,\n        0.5,\n        overlap_thresh[i],\n        score_thresh,\n        method[i]);\n    {\n      auto expected_keep =\n          utils::GetArrayIndices(expected_scores >= score_thresh);\n      std::sort(keep.begin(), keep.end());\n      EXPECT_EQ(expected_keep, keep);\n\n      const auto& expected = utils::GetSubArray(expected_scores, expected_keep);\n      const auto& actual = utils::GetSubArray(out_scores, expected_keep);\n      EXPECT_TRUE(((expected - actual).abs() < 1e-6).all());\n    }\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/given_tensor_fill_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/given_tensor_fill_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(GivenTensorFill, GivenTensorFillOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    GivenTensorDoubleFill,\n    GivenTensorFillOp<double, CPUContext>);\nREGISTER_CPU_OPERATOR(GivenTensorBoolFill, GivenTensorFillOp<bool, CPUContext>);\nREGISTER_CPU_OPERATOR(GivenTensorIntFill, GivenTensorFillOp<int, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    GivenTensorInt64Fill,\n    GivenTensorFillOp<int64_t, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    GivenTensorStringFill,\n    GivenTensorFillOp<std::string, CPUContext>);\n\nNO_GRADIENT(GivenTensorFill);\nNO_GRADIENT(GivenTensorDoubleFill);\nNO_GRADIENT(GivenTensorBoolFill);\nNO_GRADIENT(GivenTensorIntFill);\nNO_GRADIENT(GivenTensorInt64Fill);\nNO_GRADIENT(GivenTensorStringFill);\n\nOPERATOR_SCHEMA(GivenTensorFill)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(FillerTensorInference<>);\nOPERATOR_SCHEMA(GivenTensorDoubleFill)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(\n        FillerTensorInference<TensorProto_DataType_DOUBLE>);\nOPERATOR_SCHEMA(GivenTensorBoolFill)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(FillerTensorInference<TensorProto_DataType_BOOL>);\nOPERATOR_SCHEMA(GivenTensorIntFill)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(FillerTensorInference<TensorProto_DataType_INT32>);\nOPERATOR_SCHEMA(GivenTensorInt64Fill)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(FillerTensorInference<TensorProto_DataType_INT64>);\nOPERATOR_SCHEMA(GivenTensorStringFill)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .TensorInferenceFunction(\n        FillerTensorInference<TensorProto_DataType_STRING>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/given_tensor_fill_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/given_tensor_fill_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(GivenTensorFill, GivenTensorFillOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    GivenTensorDoubleFill,\n    GivenTensorFillOp<double, CUDAContext>);\nREGISTER_CUDA_OPERATOR(GivenTensorIntFill, GivenTensorFillOp<int, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    GivenTensorInt64Fill,\n    GivenTensorFillOp<int64_t, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    GivenTensorBoolFill,\n    GivenTensorFillOp<bool, CUDAContext>);\n}\n"
  },
  {
    "path": "caffe2/operators/given_tensor_fill_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/filler_op.h\"\n#include \"caffe2/utils/cast.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass GivenTensorFillOp final : public FillerOp<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  GivenTensorFillOp(const OperatorDef& operator_def, Workspace* ws)\n      : FillerOp<Context>(operator_def, ws) {\n    const ArgumentHelper helper(operator_def);\n    // GivenTensorFillOp can be provided with a \"dtype\" arg if float is\n    // is specified as T. Otherwise, \"dtype\" is ignored.\n    // In the ideal world, we would get rid of templating of T at all, but we\n    // need to provide backwards compatibility.\n    if (!std::is_same<T, float>::value || !helper.HasArgument(\"dtype\")) {\n      ExtractValues<T>();\n    } else {\n      auto dtype = cast::GetCastDataType(helper, \"dtype\");\n      switch (dtype) {\n        case TensorProto_DataType_FLOAT:\n          ExtractValues<float>();\n          break;\n        case TensorProto_DataType_DOUBLE:\n          ExtractValues<double>();\n          break;\n        case TensorProto_DataType_BOOL:\n          ExtractValues<bool>();\n          break;\n        case TensorProto_DataType_INT32:\n          ExtractValues<int>();\n          break;\n        case TensorProto_DataType_INT64:\n          ExtractValues<int64_t>();\n          break;\n        case TensorProto_DataType_STRING:\n          ExtractValues<std::string>();\n          break;\n        case TensorProto_DataType_UNDEFINED:\n          CAFFE_THROW(\"Cannot have undefined 'dtype' argument\");\n        default:\n          CAFFE_THROW(\"Unexpected 'dtype' argument value: \", dtype);\n      }\n    }\n  }\n\n  bool Fill(Tensor<Context>* output) override {\n    return (this->*body_)(output);\n  }\n\n private:\n  template <typename Type>\n  void ExtractValues() {\n    auto source_values =\n        OperatorBase::template GetRepeatedArgument<Type>(\"values\");\n    values_.Resize(source_values.size());\n    Type* values_data = values_.template mutable_data<Type>();\n    for (int i = 0; i < source_values.size(); i++) {\n      values_data[i] = static_cast<Type>(source_values[i]);\n    }\n    body_ = &GivenTensorFillOp::FillWithType<Type>;\n  }\n\n  template <typename Type>\n  bool FillWithType(Tensor<Context>* output) {\n    DCHECK_EQ(output->size(), values_.size())\n        << \"output size: \" << output->size()\n        << \" given size: \" << values_.size();\n    auto* data = output->template mutable_data<Type>();\n    const Type* values_data = values_.template data<Type>();\n    if (output->size()) {\n      context_.template Copy<Type, CPUContext, Context>(\n          output->size(), values_data, data);\n    }\n    return true;\n  }\n\n  bool (GivenTensorFillOp::*body_)(Tensor<Context>* output);\n  TensorCPU values_;\n};\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/glu_op.cc",
    "content": "#include <math.h>\n\n#include \"caffe2/operators/glu_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\nfloat sigmoid(const float x) {\n  if (x >= 0) {\n    return 1. / (1. + exp(-x));\n  } else {\n    const float exp_x = exp(x);\n    return exp_x / (1 + exp_x);\n  }\n}\n} // namespace\n\ntemplate <>\nvoid GluOp<float, CPUContext>::ComputeGlu(\n    const int M,\n    const int split_dim,\n    const int N,\n    const float* Xdata,\n    float* Ydata) {\n  const int xStride = 2 * split_dim * N;\n  const int yStride = split_dim * N;\n  for (int i = 0; i < M; ++i) {\n    const int idx = i * xStride;\n    const int idy = i * yStride;\n    for (int j = 0; j < split_dim; ++j) {\n      const int jN = j * N;\n      const int jdx1 = idx + jN;\n      const int jdx2 = idx + (j + split_dim) * N;\n      const int jdy = idy + jN;\n      for (int k = 0; k < N; ++k) {\n        const float x1 = Xdata[jdx1 + k];\n        const float x2 = Xdata[jdx2 + k];\n        Ydata[jdy + k] = x1 * sigmoid(x2);\n      }\n    }\n  }\n}\n\nOPERATOR_SCHEMA(Glu)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nApplies gated linear unit to the input Tensor X. The output Y is half the size\nof the input X, so if the shape of X is [d1, d2, ..., N] shape of Y will be\n[d1, d2, ..., dn/2] and Y(:dn-1, i) = GLU(X(:dn-1, i), X(:dn-1, i+N/2)) =\nX(dn-1, i) * sigmoid(X(dn-1, i+N/2))\n)DOC\")\n    .Input(0, \"X\", \"1D input tensor\")\n    .Output(0, \"Y\", \"1D output tensor\");\n\nREGISTER_CPU_OPERATOR(Glu, GluOp<float, CPUContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/glu_op.cu",
    "content": "#include \"caffe2/operators/glu_op.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\nnamespace {\n__global__ void glu_kernel(\n    const int M,\n    const int split_dim_size,\n    const int N,\n    const float* Xdata,\n    float* Ydata) {\n  const int xOffset = 2 * split_dim_size * N;\n  const int yOffset = split_dim_size * N;\n  CUDA_1D_KERNEL_LOOP(index, M * split_dim_size * N) {\n    const int i = index / split_dim_size / N;\n    const int j = index / N % split_dim_size;\n    const int k = index % N;\n    const float x1 = Xdata[i * xOffset + j * N + k];\n    const float x2 = Xdata[i * xOffset + (j + split_dim_size) * N + k];\n    Ydata[i * yOffset + j * N + k] = x1 * (1. / (1. + exp(-x2)));\n  }\n}\n} // namespace\n\ntemplate <>\nvoid GluOp<float, CUDAContext>::ComputeGlu(\n    const int M,\n    const int split_dim_size,\n    const int N,\n    const float* x_data,\n    float* y_data) {\n  glu_kernel<<<\n      CAFFE_GET_BLOCKS(M * N * split_dim_size),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(M, split_dim_size, N, x_data, y_data);\n}\n\nREGISTER_CUDA_OPERATOR(Glu, GluOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/glu_op.h",
    "content": "#ifndef CAFFE2_OPERATOR_GLU_OP_H_\n#define CAFFE2_OPERATOR_GLU_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\ntemplate <typename T, class Context>\nclass GluOp final : public Operator<Context> {\n public:\n  GluOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        dim_(OperatorBase::GetSingleArgument<int>(\"dim\", -1)) {}\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n    vector<TIndex> Yshape;\n    Yshape.insert(Yshape.end(), X.dims().begin(), X.dims().end());\n    const int split_index = dim_ == -1 ? Yshape.size() - 1 : dim_;\n    CAFFE_ENFORCE(\n        Yshape[split_index] % 2 == 0,\n        \"Split dimension \",\n        Yshape[split_index],\n        \" should be divided by two\");\n    const int split_dim_size = Yshape[split_index] / 2;\n    const int M = X.size_to_dim(split_index);\n    const int N = X.size_from_dim(split_index + 1);\n    Yshape[split_index] = split_dim_size;\n    Y->Resize(Yshape);\n    ComputeGlu(\n        M,\n        split_dim_size,\n        N,\n        X.template data<T>(),\n        Y->template mutable_data<T>());\n    return true;\n  }\n\n protected:\n  void ComputeGlu(\n      const int M,\n      const int split_dim_size,\n      const int N,\n      const T* X,\n      T* output);\n\n private:\n  const int dim_;\n};\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATOR_GLU_OP_H_\n"
  },
  {
    "path": "caffe2/operators/gru_unit_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"gru_unit_op.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(GRUUnit, GRUUnitOp<float, CPUContext>);\nOPERATOR_SCHEMA(GRUUnit)\n    .NumInputs(3, 4)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGRUUnit computes the activations of a standard GRU,\nin a sequence-length aware fashion.\n\nConcretely, given the (fused) inputs X (TxNxD), the previous hidden\nstate (NxD), and the sequence lengths (N), computes the GRU\nactivations, avoiding computation if the input is invalid (as in, the\nvalue at X[t][n] >= seqLengths[n].\n\n)DOC\")\n    .Arg(\n        \"drop_states\",\n        \"Bool to determine if hidden state is zeroes or passed \"\n        \"along for timesteps past the given sequence_length.\")\n    .Arg(\n        \"sequence_lengths\",\n        \"When false, the sequence lengths input is left out, \"\n        \"and all following inputs are shifted left by one.\")\n    .Output(0, \"hidden\", \"The new GRU hidden state calculated by this op.\");\nREGISTER_CPU_OPERATOR(GRUUnitGradient, GRUUnitGradientOp<float, CPUContext>);\nOPERATOR_SCHEMA(GRUUnitGradient)\n    .NumInputs(5, 6)\n    .NumOutputs(2)\n    .Arg(\n        \"sequence_lengths\",\n        \"When false, the sequence lengths input is left out, \"\n        \"and all following inputs are shifted left by one.\");\n\nclass GetGRUUnitGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    if (GetFlagArgument(def_, \"sequence_lengths\", true)) {\n      return SingleGradientDef(\n          \"GRUUnitGradient\",\n          \"\",\n          vector<string>{I(0), I(1), I(2), I(3), O(0), GO(0)},\n          vector<string>{GI(0), GI(1)});\n    } else {\n      return SingleGradientDef(\n          \"GRUUnitGradient\",\n          \"\",\n          vector<string>{I(0), I(1), I(2), O(0), GO(0)},\n          vector<string>{GI(0), GI(1)});\n    }\n  }\n};\nREGISTER_GRADIENT(GRUUnit, GetGRUUnitGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/gru_unit_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_GRU_UNIT_OP_H_\n#define CAFFE2_OPERATORS_GRU_UNIT_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\nnamespace detail {\n\ntemplate <typename T>\ninline T sigmoid(T x) {\n  return 1.0f / (1.0f + exp(-x));\n}\n\ntemplate <typename T>\ninline T host_tanh(T x) {\n  return 2.0f * sigmoid(2.0f * x) - 1.0f;\n}\n\ntemplate <typename T, typename Context>\nvoid GRUUnit(\n    int N,\n    int D,\n    int t,\n    const T* H_prev,\n    const T* X,\n    const int32_t* seqLengths,\n    bool drop_states,\n    T* H,\n    Context* /*context*/) {\n  for (int n = 0; n < N; ++n) {\n    const bool valid = seqLengths == nullptr || t < seqLengths[n];\n\n    for (int d = 0; d < D; ++d) {\n      if (!valid) {\n        if (drop_states) {\n          H[d] = 0;\n        } else {\n          H[d] = H_prev[d];\n        }\n      } else {\n        const T update = X[1 * D + d];\n        const T output = X[2 * D + d];\n        T sigmoid_update = sigmoid(update);\n        H[d] = H_prev[d] * sigmoid_update +\n            host_tanh(output) * (1.0f - sigmoid_update);\n      }\n    }\n\n    H_prev += D;\n    X += 3 * D;\n    H += D;\n  }\n}\n\ntemplate <typename T, typename Context>\nvoid GRUUnitGradient(\n    int N,\n    int D,\n    int t,\n    const T* H_prev,\n    const T* X,\n    const int32_t* seqLengths,\n    const T* H,\n    const T* H_diff,\n    bool drop_states,\n    T* H_prev_diff,\n    T* X_diff,\n    Context* /*context*/) {\n  for (int n = 0; n < N; ++n) {\n    const bool valid = seqLengths == nullptr || t < seqLengths[n];\n\n    for (int d = 0; d < D; ++d) {\n      T* h_prev_diff = H_prev_diff + d;\n      T* reset_diff = X_diff + 0 * D + d;\n      T* update_diff = X_diff + 1 * D + d;\n      T* output_diff = X_diff + 2 * D + d;\n\n      if (!valid) {\n        if (drop_states) {\n          *h_prev_diff = 0;\n        } else {\n          *h_prev_diff = H_diff[d];\n        }\n        *reset_diff = 0;\n        *update_diff = 0;\n        *output_diff = 0;\n      } else {\n        // Calculate Gate Outputs\n        const T u = sigmoid(X[1 * D + d]);\n        const T o = host_tanh(X[2 * D + d]);\n\n        *h_prev_diff = H_diff[d] * u;\n        *reset_diff = 0; // 0 contribution to gradient from this operation\n        *update_diff = (H_diff[d] * H_prev[d] - H_diff[d] * o) * u * (1.0f - u);\n        *output_diff = H_diff[d] * (1.0f - u) * (1.0f - o * o);\n      }\n    }\n\n    H_prev += D;\n    X += 3 * D;\n    H += D;\n    H_diff += D;\n    X_diff += 3 * D;\n    H_prev_diff += D;\n  }\n}\n\n} // namespace detail\n\ntemplate <typename T, typename Context>\nclass GRUUnitOp : public Operator<Context> {\n public:\n  GRUUnitOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        drop_states_(OperatorBase::template GetSingleArgument<bool>(\n            \"drop_states\",\n            false)),\n        sequence_lengths_(OperatorBase::template GetSingleArgument<bool>(\n            \"sequence_lengths\",\n            true)) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // handle potentially-missing sequence lengths input\n    const size_t TIMESTEP = SEQ_LENGTHS + (sequence_lengths_ ? 1 : 0);\n\n    // Extract N\n    const auto N = Input(HIDDEN_T_M_1).dim(1);\n\n    // Gates: 1xNxG\n    const auto G = Input(GATES).dim(2);\n    const auto D = Input(HIDDEN_T_M_1).dim(2);\n\n    CAFFE_ENFORCE_EQ(3 * D, G);\n    const auto* H_prev = Input(HIDDEN_T_M_1).template data<T>();\n    const auto* X = Input(GATES).template data<T>();\n\n    const int32_t* seqLengths = nullptr;\n    if (sequence_lengths_) {\n      CAFFE_ENFORCE_EQ(Input(SEQ_LENGTHS).size(), N);\n      seqLengths = Input(SEQ_LENGTHS).template data<int32_t>();\n    }\n\n    const auto t = static_cast<OperatorBase*>(this)->\n      Input<Tensor<CPUContext>>(TIMESTEP).template data<int32_t>()[0];\n    Output(HIDDEN_T)->ResizeLike(Input(HIDDEN_T_M_1));\n    auto* H = Output(HIDDEN_T)->template mutable_data<T>();\n\n    detail::GRUUnit<T, Context>(\n        N, D, t, H_prev, X, seqLengths, drop_states_, H, &context_);\n    return true;\n  }\n\n protected:\n  INPUT_TAGS(HIDDEN_T_M_1, GATES, SEQ_LENGTHS);\n  // additional input tags are determined dynamically based on whether\n  // sequence_lengths is present.\n  OUTPUT_TAGS(HIDDEN_T);\n\n private:\n  bool drop_states_;\n  bool sequence_lengths_;\n};\n\ntemplate <typename T, typename Context>\nclass GRUUnitGradientOp : public Operator<Context> {\n public:\n  GRUUnitGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        drop_states_(OperatorBase::template GetSingleArgument<bool>(\n            \"drop_states\",\n            false)),\n        sequence_lengths_(OperatorBase::template GetSingleArgument<bool>(\n            \"sequence_lengths\",\n            true)) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // handle potentially-missing sequence lengths input\n    const size_t inputOffset = SEQ_LENGTHS + (sequence_lengths_ ? 1 : 0);\n    const size_t TIMESTEP = inputOffset;\n    const size_t HIDDEN_T = inputOffset + 1;\n    const size_t HIDDEN_T_GRAD = inputOffset + 2;\n\n    // Extract N\n    const auto N = Input(HIDDEN_T_M_1).dim(1);\n\n    // Gates: 1xNxG\n    const auto G = Input(GATES).dim(2);\n    const auto D = Input(HIDDEN_T_M_1).dim(2);\n\n    CAFFE_ENFORCE_EQ(3 * D, G);\n    const auto* H_prev = Input(HIDDEN_T_M_1).template data<T>();\n    const auto* X = Input(GATES).template data<T>();\n    const auto t = static_cast<OperatorBase*>(this)->\n      Input<Tensor<CPUContext>>(TIMESTEP).template data<int32_t>()[0];\n    const auto* H = Input(HIDDEN_T).template data<T>();\n    const auto* H_diff = Input(HIDDEN_T_GRAD).template data<T>();\n\n    const int32_t* seqLengths = nullptr;\n    if (sequence_lengths_) {\n      CAFFE_ENFORCE_EQ(Input(SEQ_LENGTHS).size(), N);\n      seqLengths = Input(SEQ_LENGTHS).template data<int32_t>();\n    }\n\n    Output(HIDDEN_T_M_1_GRAD)->ResizeLike(Input(HIDDEN_T_M_1));\n    auto* H_prev_diff = Output(HIDDEN_T_M_1_GRAD)->template mutable_data<T>();\n    Output(GATES_GRAD)->ResizeLike(Input(GATES));\n    auto* X_diff = Output(GATES_GRAD)->template mutable_data<T>();\n\n    detail::GRUUnitGradient<T, Context>(\n        N,\n        D,\n        t,\n        H_prev,\n        X,\n        seqLengths,\n        H,\n        H_diff,\n        drop_states_,\n        H_prev_diff,\n        X_diff,\n        &context_);\n    return true;\n  }\n\n protected:\n  INPUT_TAGS(HIDDEN_T_M_1, GATES, SEQ_LENGTHS);\n  OUTPUT_TAGS(HIDDEN_T_M_1_GRAD, GATES_GRAD);\n\n private:\n  bool drop_states_;\n  bool sequence_lengths_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_GRU_UNIT_OP_H_\n"
  },
  {
    "path": "caffe2/operators/gru_unit_op_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <algorithm>\n#include <cmath>\n#include <vector>\n#include \"caffe2/core/context_gpu.h\"\n#include \"gru_unit_op.h\"\n\nnamespace caffe2 {\n\nnamespace detail {\n\ntemplate <typename Dtype>\n__device__ Dtype cuda_sigmoid(const Dtype x) {\n  return Dtype(1) / (Dtype(1) + exp(-x));\n}\n\ntemplate <typename T>\n__global__ void GRUUnitKernel(\n    const int ND,\n    const int dim,\n    const int t,\n    const T* H_prev,\n    const T* X,\n    const int32_t* seqLengths,\n    bool drop_states,\n    T* H) {\n  // index is virtual thread ID in range [0, ND)\n  CUDA_1D_KERNEL_LOOP(index, ND) {\n    const int n = index / dim;\n    const int d = index % dim;\n    const bool valid = seqLengths == nullptr || t < seqLengths[n];\n    if (!valid) {\n      H[index] = H_prev[index] * !drop_states;\n    } else {\n      const T* X_offset = X + 3 * dim * n;\n      const T update = X_offset[1 * dim + d];\n      const T output = X_offset[2 * dim + d];\n      T sigmoid_update = cuda_sigmoid(update);\n      H[index] = H_prev[index] * sigmoid_update +\n          tanh(output) * (1.0f - sigmoid_update);\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void GRUUnitGradientKernel(\n    const int ND,\n    const int dim,\n    const int t,\n    const T* H_prev,\n    const T* X,\n    const int32_t* seqLengths,\n    const T* H,\n    const T* H_diff,\n    bool drop_states,\n    T* H_prev_diff,\n    T* X_diff) {\n  CUDA_1D_KERNEL_LOOP(index, ND) {\n    const int n = index / dim;\n    const bool valid = seqLengths == nullptr || t < seqLengths[n];\n    const int d = index % dim;\n    const T* X_offset = X + 3 * dim * n;\n    T* h_prev_diff = H_prev_diff + index;\n    T* X_diff_offset = X_diff + 3 * dim * n;\n    T* reset_diff = X_diff_offset + 0 * dim + d;\n    T* update_diff = X_diff_offset + 1 * dim + d;\n    T* output_diff = X_diff_offset + 2 * dim + d;\n\n    if (!valid) {\n      *h_prev_diff = H_diff[index] * !drop_states;\n      *reset_diff = 0;\n      *update_diff = 0;\n      *output_diff = 0;\n    } else {\n      const T u = cuda_sigmoid(X_offset[1 * dim + d]);\n      const T o = tanh(X_offset[2 * dim + d]);\n\n      *h_prev_diff = H_diff[index] * u;\n      *reset_diff = 0; // 0 contribution to gradient from this operation\n      *update_diff =\n          (H_diff[index] * H_prev[index] - H_diff[index] * o) * u * (1.0f - u);\n      *output_diff = H_diff[index] * (1.0f - u) * (1.0f - o * o);\n    }\n  }\n}\n\ntemplate <>\nvoid GRUUnit<float, CUDAContext>(\n    int N,\n    int D,\n    int t,\n    const float* H_prev,\n    const float* X,\n    const int32_t* seqLengths,\n    bool drop_states,\n    float* H,\n    CUDAContext* context) {\n  GRUUnitKernel<float>\n      <<<CAFFE_GET_BLOCKS(N * D),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context->cuda_stream()>>>(\n          N * D, D, t, H_prev, X, seqLengths, drop_states, H);\n}\n\ntemplate <>\nvoid GRUUnitGradient<float, CUDAContext>(\n    int N,\n    int D,\n    int t,\n    const float* H_prev,\n    const float* X,\n    const int32_t* seqLengths,\n    const float* H,\n    const float* H_diff,\n    bool drop_states,\n    float* H_prev_diff,\n    float* X_diff,\n    CUDAContext* context) {\n  GRUUnitGradientKernel<float>\n      <<<CAFFE_GET_BLOCKS(N * D),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context->cuda_stream()>>>(\n          N * D,\n          D,\n          t,\n          H_prev,\n          X,\n          seqLengths,\n          H,\n          H_diff,\n          drop_states,\n          H_prev_diff,\n          X_diff);\n}\n}\n\nREGISTER_CUDA_OPERATOR(GRUUnit, GRUUnitOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(GRUUnitGradient, GRUUnitGradientOp<float, CUDAContext>);\n}\n"
  },
  {
    "path": "caffe2/operators/h_softmax_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/h_softmax_op.h\"\n\n#include <queue>\n#include <stack>\n\nnamespace caffe2 {\n\ntemplate <>\nfloat HSoftmaxOp<float, CPUContext>::RunForwardSingle(const float* X,\n  const float* W, const float* b, int target, float* int_output,\n  const float* bias_multiplier, int dim_out, int dim_in,\n  int& int_output_offset) {\n\n  // W * x\n  float* fc_output_data = int_output + int_output_offset;\n\n  math::Gemm<float, CPUContext>(CblasNoTrans, CblasTrans, 1, dim_out, dim_in, 1,\n    X, W, 0, fc_output_data, &context_);\n  math::Gemv<float, CPUContext>(CblasNoTrans, dim_out, 1, 1,\n    b, bias_multiplier, 1, fc_output_data, &context_);\n\n  int_output_offset += dim_out;\n\n  //Softmax\n  float* softmax_output_data = int_output + int_output_offset;\n\n  if (scale_.size() != 1) {\n    scale_.Resize(1);\n  }\n  if (sum_multiplier_.size() != dim_out) {\n    sum_multiplier_.Resize(dim_out);\n    math::Set<float, CPUContext>(dim_out, 1.f,\n      sum_multiplier_.mutable_data<float>(), &context_);\n  }\n  math::RowwiseMax<float, CPUContext>(1, dim_out, fc_output_data,\n    scale_.mutable_data<float>(), &context_);\n\n  // Put the intermediate result X - max(X) into Y\n  context_.template Copy<float, CPUContext, CPUContext>(dim_out, fc_output_data,\n    softmax_output_data);\n  // Subtract the scale\n  math::Gemv<float, CPUContext>(CblasNoTrans, dim_out, 1, -1,\n    sum_multiplier_.data<float>(), scale_.data<float>(), 1, softmax_output_data,\n    &context_);\n\n  // Exponentiation\n  math::Exp<float, CPUContext>(dim_out, softmax_output_data,\n    softmax_output_data, &context_);\n  math::Gemv<float, CPUContext>(CblasNoTrans, 1, dim_out, 1,\n    softmax_output_data, sum_multiplier_.data<float>(), 0,\n    scale_.mutable_data<float>(), &context_);\n\n  // Do division\n  const float scale = *scale_.data<float>();\n  for (int j = 0; j < dim_out; ++j) {\n    softmax_output_data[j] /= scale;\n  }\n\n  int_output_offset += dim_out;\n\n  if (target < 0) {\n    return -1;\n  }\n  //Return cross entropy loss\n  return -log(std::max(softmax_output_data[target], kLOG_THRESHOLD()));\n}\n\n// Implementation for the CPU context.\ntemplate <>\nbool HSoftmaxOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  const auto& W = Input(1);\n  const auto& b = Input(2);\n  auto& label = Input(3);\n  auto* Y = Output(0);\n  auto* intermediate_output = Output(1);\n\n  // Batch size\n  int M = X.ndim() > 1 ? X.dim32(0) : 1;\n  // Input feature dimension\n  int K = X.size() / M;\n  CAFFE_ENFORCE_GE(W.ndim(), 2); // N*K\n  CAFFE_ENFORCE_EQ(b.ndim(), 1); // N\n  CAFFE_ENFORCE_EQ(K, W.size() / (W.dim32(0)));\n  // Sum of output dimensions of all hierarchy nodes\n  int N = W.dim32(0);\n  CAFFE_ENFORCE_EQ(N, b.dim32(0));\n  Y->Resize(M);\n  auto* Ydata = Y->mutable_data<float>();\n  math::Set<float, CPUContext>(M, 0.f, Ydata, &context_);\n  const auto* labeldata = label.data<int>();\n\n  auto hierarchy = getHierarchyForLabels(M, labeldata, hierarchy_all_map_);\n  int int_output_size = getIntermediateOutputSize(labeldata, M, hierarchy);\n  intermediate_output->Resize(int_output_size);\n  float * int_output_data = intermediate_output->mutable_data<float>();\n  int int_output_offset = 0;\n\n  if (bias_multiplier_.size() != M) {\n    bias_multiplier_.Resize(M);\n    math::Set<float, CPUContext>(M, static_cast<float>(1),\n        bias_multiplier_.mutable_data<float>(), &context_);\n  }\n\n  for (int sample = 0; sample < M; ++sample) {\n    int word_id = labeldata[sample];\n    const PathProto& path = hierarchy[word_id];\n    for (const PathNodeProto& node : path.path_nodes()) {\n      //Offset of node's weight matrix in W\n      int w_offset = node.index();\n      //Number of output dimensions in node's weight matrix\n      int w_length = node.length();\n      int target = node.target();\n      //Adding log probabilities\n      Ydata[sample] += RunForwardSingle(X.data<float>() + sample*K,\n        W.data<float>() + w_offset*K, b.data<float>() + w_offset, target,\n        int_output_data, bias_multiplier_.data<float>()+sample, w_length, K,\n        int_output_offset);\n    }\n  }\n  return true;\n}\n\ntemplate <>\nvoid HSoftmaxGradientOp<float, CPUContext>::RunBackwardSingle(const float* X,\n  const float* dY, const float* W, int target,\n  const float* int_output, float* dX, float* dW, float* db, float* dint_output,\n  int dim_in, int dim_out, int& int_output_offset) {\n\n  //Cross entropy\n  // dX_entropy is the dX for the cross entropy layer\n  float* dX_entropy = dint_output + int_output_offset - dim_out;\n  // X_entropy is the X for the cross entropy layer and Y for the softmax layer\n  const float* X_entropy = int_output + int_output_offset - dim_out;\n\n  math::Set<float, CPUContext>(dim_out, 0.f, dX_entropy, &context_);\n  dX_entropy[target] = - (*dY) / std::max(X_entropy[target], kLOG_THRESHOLD());\n\n  int_output_offset -= dim_out;\n\n  //Softmax\n  if (scale_.size() != 1) {\n    scale_.Resize(1);\n  }\n  float* scaledata = scale_.mutable_data<float>();\n\n  if (sum_multiplier_.size() != dim_out) {\n    sum_multiplier_.Resize(dim_out);\n    math::Set<float, CPUContext>(dim_out, 1.f,\n      sum_multiplier_.mutable_data<float>(), &context_);\n  }\n\n  float* dX_softmax = dint_output + int_output_offset - dim_out;\n  context_.Copy<float, CPUContext, CPUContext>(dim_out, dX_entropy, dX_softmax);\n\n  math::Dot<float, CPUContext>(dim_out, X_entropy, dX_entropy, scaledata,\n    &context_);\n  math::Gemv<float, CPUContext>(CblasTrans, 1, dim_out, -1,\n    sum_multiplier_.data<float>(), scaledata , 1, dX_softmax, &context_);\n  math::Mul<float, CPUContext>(dim_out, dX_softmax, X_entropy, dX_softmax,\n    &context_);\n\n  int_output_offset -= dim_out;\n\n  //FC\n  if (bias_multiplier_.size() != 1) {\n    // If the helper bias multiplier has not been created, reshape and fill\n    // it with 1\n    bias_multiplier_.Resize(1);\n    math::Set<float, CPUContext>(1, static_cast<float>(1),\n        bias_multiplier_.template mutable_data<float>(), &context_);\n  }\n\n  // Compute dW and add incrementally\n  // dW = dW + dX_softmax'*X\n  math::Gemm<float, CPUContext>(CblasTrans, CblasNoTrans, dim_out, dim_in, 1, 1,\n    dX_softmax, X, 1, dW, &context_);\n\n  // Compute dB and add incrementally\n  // db = db + dX_softmax*bias_multiplier_\n  math::Gemv<float, CPUContext>(CblasTrans, 1, dim_out, 1, dX_softmax,\n    bias_multiplier_.template data<float>(), 1, db, &context_);\n\n  // Compute dX and add incrementally\n  // dX = dX + W'dX_softmax\n  math::Gemv<float, CPUContext>(CblasTrans, dim_out, dim_in,\n    1, W, dX_softmax, 1, dX, &context_);\n}\n\n// Implementation for the CPU context.\ntemplate <>\nbool HSoftmaxGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  const auto& W = Input(1);\n  const auto& b = Input(2);\n  auto& label = Input(3);\n  auto& intermediate_output = Input(4);\n  auto& dY = Input(5);\n  auto* dX = Output(0);\n  auto* dW = Output(1);\n  auto* db = Output(2);\n  auto* dX_intermediate_output = Output(3);\n  dX->ResizeLike(X);\n  dW->ResizeLike(W);\n  db->ResizeLike(b);\n  dX_intermediate_output->ResizeLike(intermediate_output);\n\n  float* dX_data = dX->mutable_data<float>();\n  float* dW_data = dW->mutable_data<float>();\n  float* db_data = db->mutable_data<float>();\n  float* dOutput_data = dX_intermediate_output->mutable_data<float>();\n\n  math::Set<float, CPUContext>(X.size(), 0.f, dX_data, &context_);\n  math::Set<float, CPUContext>(W.size(), 0.f, dW_data, &context_);\n  math::Set<float, CPUContext>(b.size(), 0.f, db_data, &context_);\n  math::Set<float, CPUContext>(intermediate_output.size(), 0.f, dOutput_data,\n                               &context_);\n\n  // Batch size\n  int M = X.ndim() > 1 ? X.dim32(0) : 1;\n  // Input feature dimension\n  int K = X.size() / M;\n  const auto* labeldata = label.data<int>();\n\n  auto hierarchy = getHierarchyForLabels(M, labeldata, hierarchy_all_map_);\n  int output_offset = getIntermediateOutputSize(labeldata, M, hierarchy);\n\n  //Traverse backward to access intermediate_output generated by HSoftmaxOp\n  // sequentially in reverse order\n  for (int sample = M-1; sample >= 0; sample--) {\n    int word_id = labeldata[sample];\n    PathProto path = hierarchy[word_id];\n    for (auto node = path.path_nodes().rbegin();\n      node != path.path_nodes().rend(); node++) {\n      int w_offset = node->index();\n      int w_length = node->length();\n      int target = node->target();\n      RunBackwardSingle(X.data<float>() + sample*K, dY.data<float>() + sample,\n        W.data<float>() + w_offset*K, target, intermediate_output.data<float>(),\n        dX_data + sample*K, dW_data + w_offset*K, db_data + w_offset,\n        dOutput_data, K, w_length, output_offset);\n    }\n  }\n  return true;\n}\n\n// Implementation for the CPU context.\ntemplate <>\nbool HSoftmaxSearchOp<float, CPUContext>::pruning(\n    const float* X,\n    int sample,\n    int K,\n    const float* W,\n    const float* b,\n    const NodeProto& src_node,\n    NodeProto& dst_node,\n    float parent_score,\n    float beam) {\n  int w_length = src_node.children_size() + src_node.word_ids_size();\n  Tensor<CPUContext> intermediate_data;\n  intermediate_data.Resize(2 * w_length);\n  float* int_output_data = intermediate_data.template mutable_data<float>();\n  int int_output_offset = 0;\n  int w_offset = src_node.offset();\n\n  RunForwardSingle(\n      X + K * sample,\n      W + w_offset * K,\n      b + w_offset,\n      -1,\n      int_output_data,\n      bias_multiplier_.template data<float>() + sample,\n      w_length,\n      K,\n      int_output_offset);\n\n  float* softmax_output_data = int_output_data + w_length;\n  // real probabilities\n  for (int i = 0; i < w_length; i++) {\n    softmax_output_data[i] =\n        -log(std::max(softmax_output_data[i], kLOG_THRESHOLD())) + parent_score;\n  }\n  for (int i = 0; i < src_node.children_size(); i++) {\n    if (softmax_output_data[i] < parent_score + beam) {\n      dst_node.add_children();\n      int idx = dst_node.children_size() - 1;\n      CAFFE_ENFORCE(\n          src_node.children(i).has_offset(),\n          \"HSM Search require the field offset in NodeProte\");\n      dst_node.mutable_children(idx)->set_offset(src_node.children(i).offset());\n      CAFFE_ENFORCE(\n          src_node.children(i).has_name(),\n          \"HSM Search require the field name in NodeProte\");\n      dst_node.mutable_children(idx)->set_name(src_node.children(i).name());\n      dst_node.add_scores(softmax_output_data[i]);\n      pruning(\n          X,\n          sample,\n          K,\n          W,\n          b,\n          src_node.children(i),\n          *dst_node.mutable_children(idx),\n          softmax_output_data[i],\n          beam);\n    }\n  }\n\n  for (int i = src_node.children_size(); i < w_length; i++) {\n    if (softmax_output_data[i] < parent_score + beam) {\n      dst_node.add_word_ids(src_node.word_ids(i - src_node.children_size()));\n      dst_node.add_scores(softmax_output_data[i]);\n    }\n  }\n\n  return true;\n}\n\ntemplate <>\nbool HSoftmaxSearchOp<float, CPUContext>::extractNodes(\n    const NodeProto& node,\n    std::vector<std::pair<string, float>>& info) {\n  int i = 0;\n\n  for (const auto& n : node.children()) {\n    info.emplace_back(std::make_pair(n.name(), node.scores(i++)));\n  }\n  for (const int n : node.word_ids()) {\n    info.emplace_back(std::make_pair(caffe2::to_string(n), node.scores(i++)));\n  }\n\n  for (const auto& n : node.children()) {\n    extractNodes(n, info);\n  }\n  return true;\n}\n\n// Implementation for the CPU context.\ntemplate <>\nbool HSoftmaxSearchOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  const auto& W = Input(1);\n  const auto& b = Input(2);\n  auto* Y_names = Output(0);\n  auto* Y_scores = Output(1);\n  // Batch size\n  int M = X.ndim() > 1 ? X.dim32(0) : 1;\n  // Input feature dimension\n  int K = X.size() / M;\n  CAFFE_ENFORCE(W.ndim() == 2, \"Weight must be a matrix.\"); // N*K\n  CAFFE_ENFORCE(b.ndim() == 1, \"Bias must be a vector.\"); // N\n  CAFFE_ENFORCE(K == W.size() / (W.dim32(0)), \"feature dimension mismatch.\");\n  // Sum of output dimensions of all hierarchy nodes\n  int N = W.dim32(0);\n  CAFFE_ENFORCE(N == b.dim32(0), \"mismatch between Weight and Bias.\");\n  Y_names->Resize(M, top_n_);\n  Y_scores->Resize(M, top_n_);\n\n  if (bias_multiplier_.size() != M) {\n    bias_multiplier_.Resize(M);\n    math::Set<float, CPUContext>(\n        M,\n        static_cast<float>(1),\n        bias_multiplier_.mutable_data<float>(),\n        &context_);\n  }\n\n  for (int sample = 0; sample < M; ++sample) {\n    CAFFE_ENFORCE(\n        tree_.root_node().has_offset(),\n        \"HSM Search require the field offset in NodeProte\");\n    CAFFE_ENFORCE(\n        tree_.root_node().has_name(),\n        \"HSM Search require the field name in NodeProte\");\n\n    NodeProto dst_node;\n    dst_node.set_offset(tree_.root_node().offset());\n    dst_node.set_name(tree_.root_node().name());\n\n    pruning(\n        X.data<float>(),\n        sample,\n        K,\n        W.data<float>(),\n        b.data<float>(),\n        tree_.root_node(),\n        dst_node,\n        0,\n        beam_);\n\n    std::vector<std::pair<string, float>> info;\n    extractNodes(dst_node, info);\n    // saving the results for each sample.\n    std::partial_sort(\n        info.begin(),\n        info.begin() + (top_n_ < info.size() ? top_n_ : info.size() - 1),\n        info.end(),\n        [&](std::pair<string, float> a, std::pair<string, float> b) {\n          return a.second < b.second;\n        });\n    auto* y_name_data = Y_names->mutable_data<string>() + sample * top_n_;\n    auto* y_score_data = Y_scores->mutable_data<float>() + sample * top_n_;\n    for (int i = 0; i < top_n_; i++) {\n      if (i < info.size()) {\n        y_name_data[i] = info[i].first;\n        y_score_data[i] = info[i].second;\n      } else {\n        y_score_data[i] = 0;\n      }\n    }\n  }\n\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool HuffmanTreeHierarchyOp<T, Context>::RunOnDevice() {\n  const auto& Y = Input(0);\n  auto treeOutput = Output(0);\n  CAFFE_ENFORCE_EQ(Y.ndim(), 1, \"Input labels must be a vector.\");\n  const auto y_data = Y.template data<T>();\n  treeOutput->Resize(1);\n  std::vector<int> labelCounts;\n  labelCounts.resize(num_classes_, 0);\n  for (int i = 0; i < Y.dim32(0); ++i) {\n    // Labels are in range [0, num_classes]\n    const int label_index = y_data[i];\n    CAFFE_ENFORCE_LT(\n        label_index,\n        num_classes_,\n        \"Found an input label \",\n        label_index,\n        \" not in range [\",\n        0,\n        \",\",\n        num_classes_,\n        \"]\");\n    labelCounts[label_index]++;\n  }\n\n  std::priority_queue<Node, std::vector<Node>, NodeComparator> nodes;\n  std::vector<Node> huffmanTree;\n  std::vector<int> labelIndices;\n  labelIndices.resize(num_classes_);\n\n  int current_node_index = 0;\n  for (int i = 0; i < num_classes_; ++i) {\n    Node node(i, labelCounts[i]);\n    nodes.push(node);\n  }\n\n  // Extract node with minimum count and insert it in the tree array.\n  auto get_next_node = [&nodes, &huffmanTree, &labelIndices]() {\n    auto node = nodes.top();\n    int node_index = huffmanTree.size();\n    if (node.label != -1) {\n      labelIndices[node.label] = node_index;\n    }\n    nodes.pop();\n    huffmanTree.push_back(node);\n    return std::pair<int, Node>(node_index, node);\n  };\n\n  // Merge two nodes and insert the results in the queue.\n  auto merge_nodes = [&nodes](\n      const std::pair<int, Node>& node_l, const std::pair<int, Node>& node_r) {\n    Node node(-1, node_l.second.count + node_r.second.count);\n    node.left_ch_index = node_l.first;\n    node.right_ch_index = node_r.first;\n    nodes.push(node);\n  };\n\n  // Main loop for buttom up huffman tree construction.\n  while (!nodes.empty()) {\n    auto lNode = get_next_node();\n    if (!nodes.empty()) {\n      auto rNode = get_next_node();\n      merge_nodes(lNode, rNode);\n    }\n  }\n\n  auto is_leaf_node = [&huffmanTree](const int node_index) {\n    return huffmanTree[node_index].left_ch_index == -1 &&\n        huffmanTree[node_index].right_ch_index == -1;\n  };\n\n  auto get_node_label = [&huffmanTree](const int node_index) {\n    return huffmanTree[node_index].label;\n  };\n\n  // Build huffman tree.\n  int current_offset = 0;\n  std::function<void(int, NodeProto*)> build_tree = [&](\n      const int node_index, NodeProto* node) {\n    if (is_leaf_node(node_index) || node_index == -1) {\n      return;\n    }\n    const int left_ch_index = huffmanTree[node_index].left_ch_index;\n    const int right_ch_index = huffmanTree[node_index].right_ch_index;\n    if (left_ch_index != -1) {\n      if (is_leaf_node(left_ch_index)) {\n        node->add_word_ids(get_node_label(left_ch_index));\n      } else {\n        auto* ch_node = node->add_children();\n        ch_node->set_offset(current_offset);\n        current_offset += 2;\n        build_tree(left_ch_index, ch_node);\n      }\n    }\n    if (right_ch_index != -1) {\n      if (is_leaf_node(right_ch_index)) {\n        node->add_word_ids(get_node_label(right_ch_index));\n        current_offset++;\n      } else {\n        auto* ch_node = node->add_children();\n        ch_node->set_offset(current_offset);\n        current_offset += 2;\n        build_tree(right_ch_index, ch_node);\n      }\n    }\n  };\n\n  // The last element inserted in the tree is the root.\n  const int rootNodeIndex = huffmanTree.size() - 1;\n  NodeProto rootNode;\n  rootNode.set_offset(current_offset);\n  current_offset += 2;\n  build_tree(rootNodeIndex, &rootNode);\n  TreeProto treeProto;\n  *treeProto.mutable_root_node() = rootNode;\n\n  treeProto.SerializeToString(treeOutput->template mutable_data<string>());\n  return true;\n}\n\nnamespace {\nREGISTER_CPU_OPERATOR(HSoftmax, HSoftmaxOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(HSoftmaxGradient,\n  HSoftmaxGradientOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(HSoftmaxSearch, HSoftmaxSearchOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    HuffmanTreeHierarchy,\n    HuffmanTreeHierarchyOp<int64_t, CPUContext>);\n\nOPERATOR_SCHEMA(HSoftmax)\n  .NumInputs(4)\n  .NumOutputs(2)\n  .SetDoc(R\"DOC(\nHierarchical softmax is an operator which approximates the softmax operator\nwhile giving significant training speed gains and reasonably comparable\nperformance. In this operator, instead of calculating the probabilities of all\nthe classes, we calculate the probability of each step in the path from root to\nthe target word in the hierarchy.\n\nThe operator takes a 2-D tensor (Tensor<float>) containing a batch of layers, a\nset of parameters represented by the weight matrix and bias terms, and a 1-D\ntensor (Tensor<int>) holding labels, or the indices of the target class. The\nhierarchy has to be specified as an argument to the operator.\n\nThe operator returns a 1-D tensor holding the computed log probability of the\ntarget class and a 2-D tensor of intermediate outputs (from the weight matrix\nand softmax from each step in the path from root to target class) which will be\nused by the gradient operator to compute gradients for all samples in the batch.\n)DOC\")\n  .Arg(\"hierarchy\", \"Serialized HierarchyProto string containing list of \"\n  \"vocabulary words and their paths from root of hierarchy to the leaf\")\n  .Input(0, \"X\", \"Input data from previous layer\")\n  .Input(1, \"W\", \"2D blob containing 'stacked' fully connected weight \"\n  \"matrices. Each node in the hierarchy contributes one FC weight matrix if \"\n  \"it has children nodes. Dimension is N*D, D is input dimension of data (X), \"\n  \"N is sum of all output dimensions, or total number of nodes (excl root)\")\n  .Input(2, \"b\", \"1D blob with N parameters\")\n  .Input(3, \"labels\", \"int word_id of the target word\")\n  .Output(0, \"Y\", \"1-D of log probability outputs, one per sample\")\n  .Output(1, \"intermediate_output\", \"Extra blob to store the intermediate \"\n  \"FC and softmax outputs for each node in the hierarchical path of a word. \"\n  \"The outputs from samples are stored in consecutive blocks in the forward \"\n  \"pass and are used in reverse order in the backward gradientOp pass\");\n\nOPERATOR_SCHEMA(HSoftmaxGradient).NumInputs(6).NumOutputs(4);\n\nclass GetHSoftmaxGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"HSoftmaxGradient\", \"\",\n        //X, W, b, label, intermediate output, dY\n        vector<string>{I(0), I(1), I(2), I(3), O(1), GO(0)},\n        //dX, dW, db, dintermediate_output\n        vector<string>{GI(0), GI(1), GI(2), GO(1)});\n  }\n};\nREGISTER_GRADIENT(HSoftmax, GetHSoftmaxGradient);\n\nOPERATOR_SCHEMA(HSoftmaxSearch)\n    .NumInputs(3)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nHSoftmaxSearch is an operator to generate the most possible paths given a\nwell-trained model and input vector. Greedy algorithm is used for pruning the\nsearch tree.\n)DOC\")\n    .Arg(\n        \"tree\",\n        \"Serialized TreeProto string containing a tree \"\n        \"including all intermidate nodes and leafs. All nodes must have names \"\n        \"for correct outputs\")\n    .Arg(\n        \"beam\",\n        \"beam used for pruning tree. The pruning algorithm is that \"\n        \"only children, whose score is smaller than parent's score puls beam, \"\n        \"will be propagated. \")\n    .Arg(\"topN\", \"Number of nodes in outputs\")\n    .Input(0, \"X\", \"Input data from previous layer\")\n    .Input(1, \"W\", \"The matrix trained from Softmax Ops\")\n    .Input(2, \"b\", \"The bias traiend from Softmax Ops\")\n    .Output(\n        0,\n        \"Y_names\",\n        \"The name of selected nodes and leafs. \"\n        \"For nodes, it will be the name defined in the tree. \"\n        \"For leafs, it will be the index of the word in the tree.\")\n    .Output(1, \"Y_scores\", \"The corresponding scores of Y_names\");\nSHOULD_NOT_DO_GRADIENT(HSoftmaxSearch);\n\nOPERATOR_SCHEMA(HuffmanTreeHierarchy)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nHuffmanTreeHierarchy is an operator to generate huffman tree hierarchy given\nthe input labels. It returns the tree as seralized HierarchyProto\n)DOC\")\n    .Arg(\"num_classes\", \"The number of classes used to build the hierarchy.\")\n    .Input(0, \"Labels\", \"The labels vector\")\n    .Output(0, \"Hierarch\", \"Huffman coding hierarchy of the labels\");\n\nSHOULD_NOT_DO_GRADIENT(HuffmanTreeHierarchyOp);\n}  // namespace\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/h_softmax_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_H_SOFTMAX_OP_H_\n#define CAFFE2_OPERATORS_H_SOFTMAX_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/proto/hsm.pb.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, typename Context>\nclass HSoftmaxOpBase : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  HSoftmaxOpBase(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    HierarchyProto hierarchy;\n    CAFFE_ENFORCE(hierarchy.ParseFromString(\n        OperatorBase::GetSingleArgument<string>(\"hierarchy\", \"\")));\n    for (const auto& path : hierarchy.paths()) {\n      hierarchy_all_map_.emplace(path.word_id(), path);\n    }\n  }\n\n protected:\n  std::unordered_map<int, PathProto> hierarchy_all_map_;\n  Tensor<Context> scale_;\n  Tensor<Context> sum_multiplier_;\n  Tensor<Context> bias_multiplier_;\n  static constexpr T kLOG_THRESHOLD() {\n    return 1e-20f;\n  }\n  static std::unordered_map<int, PathProto> getHierarchyForLabels(\n      int M,\n      const int* labels,\n      const std::unordered_map<int, PathProto>& hierarchy_all_map) {\n    std::unordered_map<int, PathProto> hierarchy_map;\n    std::set<int> label_set = std::set<int>(labels, labels + M);\n    for (const auto& label : label_set) {\n      auto search = hierarchy_all_map.find(label);\n      CAFFE_ENFORCE(search != hierarchy_all_map.end(), \"incorrect label.\");\n      hierarchy_map.emplace(search->first, search->second);\n    }\n    return hierarchy_map;\n  }\n  int getIntermediateOutputSize(\n      const int* labels,\n      int M,\n      std::unordered_map<int, PathProto>& hierarchy) const {\n    int size = 0;\n    for (int label = 0; label < M; ++label) {\n      int word_id = labels[label];\n      const auto& path = hierarchy[word_id];\n      size += std::accumulate(\n          path.path_nodes().begin(),\n          path.path_nodes().end(),\n          0,\n          // Output of FC + Output of Softmax\n          [](int sz, PathNodeProto node) {\n            return sz + 2 * node.length();\n          });\n    }\n    return size;\n  }\n};\n\ntemplate <typename T, class Context>\nclass HSoftmaxOp : public HSoftmaxOpBase<T, Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using HSoftmaxOpBase<T, Context>::HSoftmaxOpBase;\n\n  bool RunOnDevice() override;\n\n protected:\n  float RunForwardSingle(\n      const float* X,\n      const float* W,\n      const float* b,\n      int target,\n      float* output,\n      const float* bias_multiplier,\n      int w_length,\n      int K,\n      int& output_offset);\n};\n\ntemplate <typename T, class Context>\nclass HSoftmaxGradientOp final : public HSoftmaxOpBase<T, Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using HSoftmaxOpBase<T, Context>::HSoftmaxOpBase;\n  bool RunOnDevice() override;\n\n private:\n  void RunBackwardSingle(\n      const float* X,\n      const float* dY,\n      const float* W,\n      int target,\n      const float* int_output,\n      float* dX,\n      float* dW,\n      float* db,\n      float* dOutput,\n      int dim_in,\n      int w_length,\n      int& output_offset);\n};\n\ntemplate <typename T, class Context>\nclass HSoftmaxSearchOp final : public HSoftmaxOp<T, Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  HSoftmaxSearchOp(const OperatorDef& operator_def, Workspace* ws)\n      : HSoftmaxOp<T, Context>(operator_def, ws),\n        top_n_(OperatorBase::GetSingleArgument<int>(\"topN\", 5)),\n        beam_(OperatorBase::GetSingleArgument<float>(\"beam\", 0.01f)) {\n    CAFFE_ENFORCE(tree_.ParseFromString(\n        OperatorBase::GetSingleArgument<string>(\"tree\", \"\")));\n  }\n  bool RunOnDevice() override;\n\n private:\n  int top_n_;\n  float beam_;\n  TreeProto tree_;\n  bool pruning(\n      const float* X,\n      int sample,\n      int K,\n      const float* W,\n      const float* b,\n      const NodeProto& src_node,\n      NodeProto& dst_node,\n      float parent_score,\n      float beam);\n  bool extractNodes(\n      const NodeProto& node,\n      std::vector<std::pair<string, float>>& info);\n};\n\ntemplate <typename T, class Context>\nclass HuffmanTreeHierarchyOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  HuffmanTreeHierarchyOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        num_classes_(OperatorBase::GetSingleArgument<int>(\"num_classes\", -1)) {}\n  bool RunOnDevice() override;\n\n private:\n  // Internal huffman tree data.\n  struct Node {\n    Node(T l, int count)\n        : label(l), count(count), left_ch_index(-1), right_ch_index(-1) {}\n    T label;\n    int count;\n    int left_ch_index;\n    int right_ch_index;\n  };\n\n  struct NodeComparator {\n    bool operator()(const Node& node_a, const Node& node_b) {\n      return node_a.count > node_b.count;\n    }\n  };\n\n  int num_classes_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SOFTMAX_OP_H_\n"
  },
  {
    "path": "caffe2/operators/half_float_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/half_float_ops.h\"\n\nnamespace caffe2 {\nOPERATOR_SCHEMA(FloatToHalf)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(\n        [](const OperatorDef& def, const vector<TensorShape>& in) {\n          vector<TensorShape> out;\n          const TensorShape& X = in[0];\n          out.push_back(X);\n          out[0].set_data_type(TensorProto_DataType_FLOAT16);\n\n          return out;\n        });\n\nOPERATOR_SCHEMA(HalfToFloat)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(\n        [](const OperatorDef& def, const vector<TensorShape>& in) {\n          vector<TensorShape> out;\n          const TensorShape& X = in[0];\n          out.push_back(X);\n          out[0].set_data_type(TensorProto_DataType_FLOAT);\n\n          return out;\n        });\nOPERATOR_SCHEMA(Float16ConstantFill)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .TensorInferenceFunction(Float16FillerTensorInference)\n    .Arg(\"value\", \"The value for the elements of the output tensor.\")\n    .Arg(\"shape\", \"The shape of the output tensor.\")\n    .Output(\n        0,\n        \"output\",\n        \"Output tensor of constant values specified by 'value'\");\n\nclass GetFloatToHalfGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"HalfToFloat\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(FloatToHalf, GetFloatToHalfGradient);\n\nclass GetHalfToFloatGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"FloatToHalf\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(HalfToFloat, GetHalfToFloatGradient);\nNO_GRADIENT(Float16ConstantFill);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/half_float_ops.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/half_float_ops.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\n#ifdef CAFFE_HAS_CUDA_FP16\n\nnamespace caffe2 {\nnamespace {\n__global__ void FloatToHalfKernel(const int N, const float* X, half* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = __float2half(X[i]);\n  }\n}\n\n__global__ void HalfToFloatKernel(const int N, const half* X, float* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = __half2float(X[i]);\n  }\n}\n}\n\ntemplate <>\nbool FloatToHalfOp<CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n  FloatToHalfKernel<<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(),\n      X.data<float>(),\n      reinterpret_cast<half*>(Y->mutable_data<float16>()));\n  return true;\n}\n\ntemplate <>\nbool HalfToFloatOp<CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n  HalfToFloatKernel<<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(),\n      reinterpret_cast<const half*>(X.data<float16>()),\n      Y->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(FloatToHalf, FloatToHalfOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(HalfToFloat, HalfToFloatOp<CUDAContext>);\n} // namespace caffe2\n\n#endif // CAFFE_HAS_CUDA_FP16\n"
  },
  {
    "path": "caffe2/operators/half_float_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_HALF_FLOAT_OPS_H_\n#define CAFFE2_OPERATORS_HALF_FLOAT_OPS_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass FloatToHalfOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(FloatToHalfOp);\n\n  bool RunOnDevice() override;\n};\n\ntemplate <class Context>\nclass HalfToFloatOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(HalfToFloatOp);\n\n  bool RunOnDevice() override;\n};\n\nclass Float16ConstantFillOp : public Operator<CPUContext> {\n public:\n  Float16ConstantFillOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        shape_(\n            ToVectorTIndex(OperatorBase::GetRepeatedArgument<int>(\"shape\"))) {}\n\n  USE_OPERATOR_FUNCTIONS(CPUContext);\n  virtual ~Float16ConstantFillOp() {}\n\n  bool RunOnDevice() override;\n\n private:\n  vector<TIndex> shape_;\n};\n\ninline std::vector<TensorShape> Float16FillerTensorInference(\n    const OperatorDef& def,\n    const vector<TensorShape>& in) {\n  vector<TensorShape> out(1);\n  ArgumentHelper helper(def);\n  out[0].set_data_type(static_cast<TensorProto_DataType>(\n      helper.GetSingleArgument<int>(\"dtype\", TensorProto_DataType_FLOAT)));\n  auto shape = helper.GetRepeatedArgument<int>(\"shape\");\n  for (int d : shape) {\n    out[0].add_dims(d);\n  }\n  return out;\n}\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_HALF_FLOAT_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/if_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/if_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(If, IfOp<CPUContext>);\n\nOPERATOR_SCHEMA(If)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .SetDoc(R\"DOC(\n'If' control operator, first input is a scalar boolean blob that stores condition\nvalue. Accepts 'then_net' (required) and 'else_net' (optional) arguments for 'then' and\n'else' subnets respectively. Subnets are executed in the same workspace as 'If'.\n    )DOC\")\n    .Arg(\"then_net\", \"Net executed when condition is true\")\n    .Arg(\"else_net\", \"Net executed when condition is false (optional)\")\n    .Input(0, \"condition\", \"Scalar boolean condition\")\n    .AllowInplace([](int in, int out) -> bool { return true; });\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/if_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_IF_OP_H_\n#define CAFFE2_OPERATORS_IF_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass IfOp final : public Operator<Context> {\n public:\n  IfOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    CAFFE_ENFORCE(\n        this->template HasSingleArgumentOfType<NetDef>(\"then_net\"),\n        \"then_net must be specified in If operator\");\n    auto then_net_def =\n        this->template GetSingleArgument<NetDef>(\"then_net\", NetDef());\n    then_net_ = CreateNet(then_net_def, ws);\n    CAFFE_ENFORCE(then_net_, \"Failed to initialize then subnet\");\n\n    if (this->template HasSingleArgumentOfType<NetDef>(\"else_net\")) {\n      auto else_net_def =\n          this->template GetSingleArgument<NetDef>(\"else_net\", NetDef());\n      else_net_ = CreateNet(else_net_def, ws);\n      CAFFE_ENFORCE(else_net_, \"Failed to initialize else subnet\");\n    }\n  }\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE(\n        this->template InputIsType<Tensor<Context>>(0),\n        \"Invalid condition in If operator: tensor expected\");\n\n    const auto& condition = Input(0);\n    CAFFE_ENFORCE_EQ(\n        condition.size(),\n        1,\n        \"Invalid condition tensor in If operator: single value expected\");\n\n    auto conditionValue = *condition.template data<bool>();\n    if (conditionValue) {\n      return then_net_->Run();\n    } else if (else_net_) {\n      return else_net_->Run();\n    }\n\n    return true;\n  }\n\n private:\n  std::unique_ptr<NetBase> then_net_;\n  std::unique_ptr<NetBase> else_net_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_IF_OP_H_\n"
  },
  {
    "path": "caffe2/operators/if_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/if_op.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(If, IfOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/im2col_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/im2col_op.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(Im2Col, Im2ColOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(Col2Im, Col2ImOp<float, CPUContext>);\n\nclass GetIm2ColGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"Col2Im\",\n        \"\",\n        std::vector<string>{O(0), I(0)},\n        std::vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Im2Col, GetIm2ColGradient);\n\nclass GetCol2ImGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"Im2Col\", \"\", std::vector<string>{O(0)}, std::vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Col2Im, GetCol2ImGradient);\n\nOPERATOR_SCHEMA(Im2Col)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(\"The Im2Col operator from Matlab.\")\n    .TensorInferenceFunction(\n        [](const OperatorDef& def, const vector<TensorShape>& in) {\n          ArgumentHelper helper(def);\n          auto pad = helper.GetSingleArgument<int>(\"pad\", 0);\n          auto kernel_h = helper.GetSingleArgument<int>(\n              \"kernel_h\", helper.GetSingleArgument<int>(\"kernel\", 0));\n          auto kernel_w = helper.GetSingleArgument<int>(\n              \"kernel_w\", helper.GetSingleArgument<int>(\"kernel\", 0));\n          auto dilation_h = helper.GetSingleArgument<int>(\n              \"dilation_h\", helper.GetSingleArgument<int>(\"dilation\", 1));\n          auto dilation_w = helper.GetSingleArgument<int>(\n              \"dilation_w\", helper.GetSingleArgument<int>(\"dilation\", 1));\n          auto stride_h = helper.GetSingleArgument<int>(\n              \"stride_h\", helper.GetSingleArgument<int>(\"stride\", 1));\n          auto stride_w = helper.GetSingleArgument<int>(\n              \"stride_w\", helper.GetSingleArgument<int>(\"stride\", 1));\n          auto order = StringToStorageOrder(\n              helper.GetSingleArgument<string>(\"order\", \"NCHW\"));\n\n          const TensorShape& X = in[0];\n          int N = 0, C = 0, H = 0, W = 0;\n          switch (order) {\n            case StorageOrder::NCHW:\n              N = X.dims(0);\n              C = X.dims(1);\n              H = X.dims(2);\n              W = X.dims(3);\n              break;\n            case StorageOrder::NHWC:\n              N = X.dims(0);\n              H = X.dims(1);\n              W = X.dims(2);\n              C = X.dims(3);\n              break;\n            default:\n              CAFFE_THROW(\"Unknown storage order: \", order);\n          }\n\n          const int dkernel_h = dilation_h * (kernel_h - 1) + 1;\n          const int dkernel_w = dilation_w * (kernel_w - 1) + 1;\n          CAFFE_ENFORCE(H >= dkernel_h);\n          CAFFE_ENFORCE(W >= dkernel_w);\n          const int out_h = (H + 2 * pad - dkernel_h) / stride_h + 1;\n          const int out_w = (W + 2 * pad - dkernel_w) / stride_w + 1;\n\n          vector<TensorShape> out(1);\n          switch (order) {\n            case StorageOrder::NCHW:\n              out[0] = CreateTensorShape(\n                  vector<int>{N, C * kernel_h * kernel_w, out_h, out_w},\n                  TensorProto::FLOAT);\n              break;\n            case StorageOrder::NHWC:\n              out[0] = CreateTensorShape(\n                  vector<int>{N, out_h, out_w, kernel_h * kernel_w * C},\n                  TensorProto::FLOAT);\n              break;\n            default:\n              CAFFE_THROW(\"Unknown storage order: \", order);\n          }\n\n          return out;\n        })\n    .Input(0, \"X\", \"4-tensor in NCHW or NHWC.\")\n    .Output(\n        0,\n        \"Y\",\n        \"4-tensor. For NCHW: N x (C x kH x kW) x outH x outW.\"\n        \"For NHWC: N x outH x outW x (kH x kW x C\");\n\nOPERATOR_SCHEMA(Col2Im).NumInputs(2).NumOutputs(1);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/im2col_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_IM2COL_OP_H_\n#define CAFFE2_OPERATORS_IM2COL_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass Im2ColOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  Im2ColOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        pad_(OperatorBase::GetSingleArgument<int>(\"pad\", 0)),\n        kernel_h_(OperatorBase::GetSingleArgument<int>(\n            \"kernel_h\",\n            OperatorBase::GetSingleArgument<int>(\"kernel\", 0))),\n        kernel_w_(OperatorBase::GetSingleArgument<int>(\n            \"kernel_w\",\n            OperatorBase::GetSingleArgument<int>(\"kernel\", 0))),\n        dilation_h_(OperatorBase::GetSingleArgument<int>(\n            \"dilation_h\",\n            OperatorBase::GetSingleArgument<int>(\"dilation\", 1))),\n        dilation_w_(OperatorBase::GetSingleArgument<int>(\n            \"dilation_w\",\n            OperatorBase::GetSingleArgument<int>(\"dilation\", 1))),\n        stride_h_(OperatorBase::GetSingleArgument<int>(\n            \"stride_h\",\n            OperatorBase::GetSingleArgument<int>(\"stride\", 1))),\n        stride_w_(OperatorBase::GetSingleArgument<int>(\n            \"stride_w\",\n            OperatorBase::GetSingleArgument<int>(\"stride\", 1))),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CAFFE_ENFORCE(kernel_h_ > 0);\n    CAFFE_ENFORCE(kernel_w_ > 0);\n    CAFFE_ENFORCE(dilation_h_ > 0);\n    CAFFE_ENFORCE(dilation_w_ > 0);\n    CAFFE_ENFORCE(stride_h_ > 0);\n    CAFFE_ENFORCE(stride_w_ > 0);\n    CAFFE_ENFORCE(pad_ >= 0);\n  }\n\n  bool RunOnDevice() override {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n    CAFFE_ENFORCE(4 == X.ndim());\n\n    int N = 0, C = 0, H = 0, W = 0;\n    switch (order_) {\n      case StorageOrder::NCHW:\n        N = X.dim32(0);\n        C = X.dim32(1);\n        H = X.dim32(2);\n        W = X.dim32(3);\n        break;\n      case StorageOrder::NHWC:\n        N = X.dim32(0);\n        H = X.dim32(1);\n        W = X.dim32(2);\n        C = X.dim32(3);\n        break;\n      default:\n        CAFFE_THROW(\"Unknown storage order: \", order_);\n    }\n\n    const int dkernel_h = dilation_h_ * (kernel_h_ - 1) + 1;\n    const int dkernel_w = dilation_w_ * (kernel_w_ - 1) + 1;\n    CAFFE_ENFORCE(H >= dkernel_h);\n    CAFFE_ENFORCE(W >= dkernel_w);\n    const int out_h = (H + 2 * pad_ - dkernel_h) / stride_h_ + 1;\n    const int out_w = (W + 2 * pad_ - dkernel_w) / stride_w_ + 1;\n\n    switch (order_) {\n      case StorageOrder::NCHW: {\n        Y->Resize(\n            std::vector<TIndex>{N, C * kernel_h_ * kernel_w_, out_h, out_w});\n\n        const size_t dx = X.size() / N;\n        const size_t dy = Y->size() / N;\n        for (int n = 0; n < N; ++n) {\n          const auto* xdata = X.template data<T>() + (n * dx);\n          auto* ydata = Y->template mutable_data<T>() + (n * dy);\n          math::Im2col<T, Context, StorageOrder::NCHW>(\n              xdata,\n              C,\n              H,\n              W,\n              kernel_h_,\n              kernel_w_,\n              dilation_h_,\n              dilation_w_,\n              pad_,\n              pad_,\n              pad_,\n              pad_,\n              stride_h_,\n              stride_w_,\n              ydata,\n              &context_);\n        }\n      }; break;\n      case StorageOrder::NHWC: {\n        Y->Resize(\n            std::vector<TIndex>{N, out_h, out_w, kernel_h_ * kernel_w_ * C});\n\n        const size_t dx = X.size() / N;\n        const size_t dy = Y->size() / N;\n        for (int n = 0; n < N; ++n) {\n          const auto* xdata = X.template data<T>() + (n * dx);\n          auto* ydata = Y->template mutable_data<T>() + (n * dy);\n          math::Im2col<T, Context, StorageOrder::NHWC>(\n              xdata,\n              C,\n              H,\n              W,\n              kernel_h_,\n              kernel_w_,\n              dilation_h_,\n              dilation_w_,\n              pad_,\n              pad_,\n              pad_,\n              pad_,\n              stride_h_,\n              stride_w_,\n              ydata,\n              &context_);\n        }\n      }; break;\n      default:\n        CAFFE_THROW(\"Unknown storage order: \", order_);\n    }\n\n    return true;\n  }\n\n private:\n  int pad_;\n  int kernel_h_;\n  int kernel_w_;\n  int dilation_h_;\n  int dilation_w_;\n  int stride_h_;\n  int stride_w_;\n  StorageOrder order_;\n};\n\ntemplate <typename T, class Context>\nclass Col2ImOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  Col2ImOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        pad_(OperatorBase::GetSingleArgument<int>(\"pad\", 0)),\n        kernel_h_(OperatorBase::GetSingleArgument<int>(\n            \"kernel_h\",\n            OperatorBase::GetSingleArgument<int>(\"kernel\", 0))),\n        kernel_w_(OperatorBase::GetSingleArgument<int>(\n            \"kernel_w\",\n            OperatorBase::GetSingleArgument<int>(\"kernel\", 0))),\n        dilation_h_(OperatorBase::GetSingleArgument<int>(\n            \"dilation_h\",\n            OperatorBase::GetSingleArgument<int>(\"dilation\", 1))),\n        dilation_w_(OperatorBase::GetSingleArgument<int>(\n            \"dilation_w\",\n            OperatorBase::GetSingleArgument<int>(\"dilation\", 1))),\n        stride_h_(OperatorBase::GetSingleArgument<int>(\n            \"stride_h\",\n            OperatorBase::GetSingleArgument<int>(\"stride\", 1))),\n        stride_w_(OperatorBase::GetSingleArgument<int>(\n            \"stride_w\",\n            OperatorBase::GetSingleArgument<int>(\"stride\", 1))),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CAFFE_ENFORCE(kernel_h_ > 0);\n    CAFFE_ENFORCE(kernel_w_ > 0);\n    CAFFE_ENFORCE(dilation_h_ > 0);\n    CAFFE_ENFORCE(dilation_w_ > 0);\n    CAFFE_ENFORCE(stride_h_ > 0);\n    CAFFE_ENFORCE(stride_w_ > 0);\n    CAFFE_ENFORCE(pad_ >= 0);\n  }\n\n  bool RunOnDevice() override {\n    auto& X = Input(0);\n    auto& Z = Input(1);\n    auto* Y = Output(0);\n    Y->ResizeLike(Z);\n    CAFFE_ENFORCE(4 == Y->ndim());\n\n    int N = 0, C = 0, H = 0, W = 0;\n    switch (order_) {\n      case StorageOrder::NCHW:\n        N = Y->dim32(0);\n        C = Y->dim32(1);\n        H = Y->dim32(2);\n        W = Y->dim32(3);\n        break;\n      case StorageOrder::NHWC:\n        N = Y->dim32(0);\n        H = Y->dim32(1);\n        W = Y->dim32(2);\n        C = Y->dim32(3);\n        break;\n      default:\n        CAFFE_THROW(\"Unknown storage order: \", order_);\n    }\n\n    const int dkernel_h = dilation_h_ * (kernel_h_ - 1) + 1;\n    const int dkernel_w = dilation_w_ * (kernel_w_ - 1) + 1;\n    CAFFE_ENFORCE(H >= dkernel_h);\n    CAFFE_ENFORCE(W >= dkernel_w);\n    const int out_h = (H + 2 * pad_ - dkernel_h) / stride_h_ + 1;\n    const int out_w = (W + 2 * pad_ - dkernel_w) / stride_w_ + 1;\n    CAFFE_ENFORCE(X.size() == N * kernel_h_ * kernel_w_ * C * out_h * out_w);\n\n    const size_t dx = X.size() / N;\n    const size_t dy = Y->size() / N;\n\n    // could template-specialize this, but it's test code...\n    switch (order_) {\n      case StorageOrder::NCHW: {\n        for (int n = 0; n < N; ++n) {\n          const auto* xdata = X.template data<T>() + (n * dx);\n          auto* ydata = Y->template mutable_data<T>() + (n * dy);\n          math::Col2im<T, Context, StorageOrder::NCHW>(\n              xdata,\n              C,\n              H,\n              W,\n              kernel_h_,\n              kernel_w_,\n              dilation_h_,\n              dilation_w_,\n              pad_,\n              pad_,\n              pad_,\n              pad_,\n              stride_h_,\n              stride_w_,\n              ydata,\n              &context_);\n        }\n      }; break;\n      case StorageOrder::NHWC: {\n        for (int n = 0; n < N; ++n) {\n          const auto* xdata = X.template data<T>() + (n * dx);\n          auto* ydata = Y->template mutable_data<T>() + (n * dy);\n          math::Col2im<T, Context, StorageOrder::NHWC>(\n              xdata,\n              C,\n              H,\n              W,\n              kernel_h_,\n              kernel_w_,\n              dilation_h_,\n              dilation_w_,\n              pad_,\n              pad_,\n              pad_,\n              pad_,\n              stride_h_,\n              stride_w_,\n              ydata,\n              &context_);\n        }\n      }; break;\n      default:\n        CAFFE_THROW(\"Unknown storage order: \", order_);\n    }\n\n    return true;\n  }\n\n private:\n  int pad_;\n  int kernel_h_;\n  int kernel_w_;\n  int dilation_h_;\n  int dilation_w_;\n  int stride_h_;\n  int stride_w_;\n  StorageOrder order_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_IM2COL_OP_H_\n"
  },
  {
    "path": "caffe2/operators/im2col_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/im2col_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(Im2Col, Im2ColOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(Col2Im, Col2ImOp<float, CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/index_hash_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/index_hash_ops.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(IndexHash, IndexHashOp<CPUContext>);\n\nOPERATOR_SCHEMA(IndexHash)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nThis operator translates a list of indices into a list of hashed indices.\nA seed can be fed as an argument to change the behavior of the hash function.\nIf a modulo is specified, all the hashed indices will be modulo the\nspecified number. All input and output indices are enforced to be positive.\n)DOC\")\n    .Input(0, \"Indices\", \"Input feature indices.\")\n    .Output(0, \"HashedIndices\", \"Hashed feature indices.\")\n    .Arg(\"seed\", \"seed for the hash function\")\n    .Arg(\"modulo\", \"must be > 0, hashed ids will be modulo this number\");\n\nSHOULD_NOT_DO_GRADIENT(IndexHash);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/index_hash_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_INDEX_HASH_OPS_H_\n#define CAFFE2_OPERATORS_INDEX_HASH_OPS_H_\n\n#include \"caffe2/core/asan.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass IndexHashOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  IndexHashOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        seed_(OperatorBase::GetSingleArgument<int64_t>(\"seed\", 0)),\n        modulo_(OperatorBase::GetSingleArgument<int64_t>(\"modulo\", 0)) {\n    CAFFE_ENFORCE_GT(modulo_, 0, \"MODULO should be > 0\");\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& indices = Input(INDICES);\n    auto* hashed_indices = Output(HASHED_INDICES);\n    hashed_indices->ResizeLike(indices);\n\n    CAFFE_ENFORCE_GE(\n        static_cast<int64_t>(std::numeric_limits<T>::max()),\n        modulo_,\n        \"MODULO shouldn't be larger than the numeric limit of the indices\");\n\n    auto N = indices.size();\n    auto* indices_data = indices.template data<T>();\n    auto* hashed_indices_data = hashed_indices->template mutable_data<T>();\n\n    for (auto i = 0; i < N; i++) {\n      hashed_indices_data[i] = hash(indices_data[i]);\n    }\n\n    return true;\n  }\n\n protected:\n  template <typename T>\n  T hash(T id) CAFFE2_NO_SANITIZE(\"signed-integer-overflow\") {\n    int8_t* bytes = (int8_t*)&id;\n    T hashed = seed_ * 0xDEADBEEF;\n    for (int i = 0; i < sizeof(T) / sizeof(int8_t); i++) {\n      hashed = hashed * 65537 + bytes[i];\n    }\n    hashed = static_cast<T>((modulo_ + hashed % modulo_) % modulo_);\n    return hashed;\n  }\n\n private:\n  INPUT_TAGS(INDICES);\n  OUTPUT_TAGS(HASHED_INDICES);\n\n  int64_t seed_;\n  int64_t modulo_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_INDEX_HASH_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/index_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <atomic>\n#include <limits>\n#include <mutex>\n#include <sstream>\n#include <unordered_map>\n#include <vector>\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\nnamespace {\nusing IndexKeyTypes = TensorTypes<int32_t, int64_t, std::string>;\nusing TIndexValue = int64_t;\n}  // namespace\n\nstruct IndexBase {\n public:\n  IndexBase(TIndexValue maxElements, const TypeMeta& type)\n    : maxElements_{maxElements}\n    , meta_(type)\n    , frozen_{false} {}\n\n  void Freeze() { frozen_ = true; }\n\n  bool isFrozen() const {\n    return frozen_;\n  }\n\n  int64_t maxElements() const {\n    return maxElements_;\n  }\n\n  virtual ~IndexBase() {}\n\n  const TypeMeta& Type() const { return meta_; }\n\n  TIndexValue Size() {\n    std::lock_guard<std::mutex> guard(dictMutex_);\n    return nextId_;\n  }\n\n protected:\n  int64_t maxElements_;\n  TypeMeta meta_;\n  TIndexValue nextId_{1}; // guarded by dictMutex_\n  std::atomic<bool> frozen_{false};\n  std::mutex dictMutex_;\n};\n\ntemplate<typename T>\nstruct Index: IndexBase {\n  explicit Index(TIndexValue maxElements)\n    : IndexBase(maxElements, TypeMeta::Make<T>()) {}\n\n  void Get(const T* keys, TIndexValue* values, size_t numKeys) {\n    if (frozen_) {\n      FrozenGet(keys, values, numKeys);\n      return;\n    }\n    std::lock_guard<std::mutex> lock(dictMutex_);\n    for (int i = 0; i < numKeys; ++i) {\n      auto it = dict_.find(keys[i]);\n      if (it != dict_.end()) {\n        values[i] = it->second;\n      } else if (nextId_ < maxElements_) {\n        auto newValue = nextId_++;\n        dict_.insert({keys[i], newValue});\n        values[i] = newValue;\n      } else {\n        CAFFE_THROW(\"Dict max size reached\");\n      }\n    }\n  }\n\n  bool Load(const T* keys, size_t numKeys) {\n    CAFFE_ENFORCE(\n        numKeys <= maxElements_,\n        \"Cannot load index: Tensor is larger than max_elements.\");\n    decltype(dict_) dict;\n    for (int i = 0; i < numKeys; ++i) {\n      CAFFE_ENFORCE(\n          dict.insert({keys[i], i + 1}).second,\n          \"Repeated elements found: cannot load into dictionary.\");\n    }\n    // assume no `get` is inflight while this happens\n    {\n      std::lock_guard<std::mutex> lock(dictMutex_);\n      // let the old dict get destructed outside of the lock\n      dict_.swap(dict);\n      nextId_ = numKeys + 1;\n    }\n    return true;\n  }\n\n  template<typename Ctx>\n  bool Store(Tensor<Ctx>* out) {\n    std::lock_guard<std::mutex> lock(dictMutex_);\n    out->Resize(nextId_ - 1);\n    auto outData = out->template mutable_data<T>();\n    for (const auto& entry : dict_) {\n      outData[entry.second - 1] = entry.first;\n    }\n    return true;\n  }\n\n private:\n  void FrozenGet(const T* keys, TIndexValue* values, size_t numKeys) {\n    for (int i = 0; i < numKeys; ++i) {\n      auto it = dict_.find(keys[i]);\n      values[i] = it != dict_.end() ? it->second : 0;\n    }\n  }\n\n  std::unordered_map<T, TIndexValue> dict_;\n};\n\n// TODO(azzolini): support sizes larger than int32\ntemplate<class T>\nclass IndexCreateOp: public Operator<CPUContext> {\n public:\n  IndexCreateOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        maxElements_(OperatorBase::GetSingleArgument<int>(\n            \"max_elements\",\n            std::numeric_limits<int>::max())) {}\n\n  bool RunOnDevice() override {\n    *OperatorBase::Output<std::unique_ptr<IndexBase>>(0) =\n      std::unique_ptr<IndexBase>(new Index<T>(maxElements_));\n    return true;\n  }\n\n private:\n  TIndexValue maxElements_;\n};\n\nclass IndexGetOp: public Operator<CPUContext> {\n public:\n  IndexGetOp(const OperatorDef& operator_def, Workspace* ws)\n   : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<IndexKeyTypes>::call(this, Input(1));\n  }\n  template <typename T>\n  bool DoRunWithType() {\n    auto& base = OperatorBase::Input<std::unique_ptr<IndexBase>>(0);\n    auto* dict = dynamic_cast_if_rtti<Index<T>*>(base.get());\n    CAFFE_ENFORCE(dict, \"Wrong dictionary type given input keys.\");\n    const auto& keys = Input(1);\n    auto* values = Output(0);\n    values->ResizeLike(keys);\n    dict->Get(keys.data<T>(), values->mutable_data<TIndexValue>(), keys.size());\n    return true;\n  }\n};\n\nclass IndexLoadOp: public Operator<CPUContext> {\n public:\n  IndexLoadOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        skipFirstEntry_(\n            OperatorBase::GetSingleArgument<int>(\"skip_first_entry\", 0)) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<IndexKeyTypes>::call(this, Input(1));\n  }\n  template <typename T>\n  bool DoRunWithType() {\n    auto& base = OperatorBase::Input<std::unique_ptr<IndexBase>>(0);\n    auto* dict = dynamic_cast_if_rtti<Index<T>*>(base.get());\n    CAFFE_ENFORCE(dict, \"Wrong dictionary type given input keys.\");\n    const auto& keys = Input(1);\n    const auto* keys_data = keys.data<T>();\n    auto keys_size = keys.size();\n    if (skipFirstEntry_) {\n      CAFFE_ENFORCE(keys.size() > 0);\n      ++keys_data;\n      --keys_size;\n    }\n    return dict->Load(keys_data, keys_size);\n  }\n\n private:\n  bool skipFirstEntry_;\n};\n\nclass IndexStoreOp: public Operator<CPUContext> {\n public:\n  IndexStoreOp(const OperatorDef& operator_def, Workspace* ws)\n   : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& base = OperatorBase::Input<std::unique_ptr<IndexBase>>(0);\n    return DispatchHelper<IndexKeyTypes>::call(this, base->Type());\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& base = OperatorBase::Input<std::unique_ptr<IndexBase>>(0);\n    auto* dict = dynamic_cast_if_rtti<Index<T>*>(base.get());\n    CAFFE_ENFORCE(dict);\n    return dict->Store(Output(0));\n  }\n};\n\nclass IndexFreezeOp: public Operator<CPUContext> {\n public:\n  IndexFreezeOp(const OperatorDef& operator_def, Workspace* ws)\n   : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& base = OperatorBase::Input<std::unique_ptr<IndexBase>>(0);\n    base->Freeze();\n    return true;\n  }\n};\n\nclass IndexSizeOp : public Operator<CPUContext> {\n public:\n  IndexSizeOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& base = OperatorBase::Input<std::unique_ptr<IndexBase>>(0);\n    auto* out = Output(0);\n    out->Resize(std::vector<TIndex>{});\n    *out->mutable_data<TIndexValue>() = base->Size();\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(IntIndexCreate, IndexCreateOp<int32_t>);\nREGISTER_CPU_OPERATOR(LongIndexCreate, IndexCreateOp<int64_t>);\nREGISTER_CPU_OPERATOR(StringIndexCreate, IndexCreateOp<std::string>);\n\nREGISTER_CPU_OPERATOR(IndexGet, IndexGetOp);\nREGISTER_CPU_OPERATOR(IndexLoad, IndexLoadOp);\nREGISTER_CPU_OPERATOR(IndexStore, IndexStoreOp);\nREGISTER_CPU_OPERATOR(IndexFreeze, IndexFreezeOp);\nREGISTER_CPU_OPERATOR(IndexSize, IndexSizeOp);\n\nOPERATOR_SCHEMA(IntIndexCreate)\n  .NumInputs(0)\n  .NumOutputs(1)\n  .SetDoc(R\"DOC(\nCreates a dictionary that maps int32 keys to consecutive integers\nfrom 1 to max_elements. Zero is reserved for unknown keys.\n)DOC\")\n  .Arg(\"max_elements\", \"Max number of elements, including the zero entry.\")\n  .Output(0, \"handler\", \"Pointer to an Index instance.\");\n\nOPERATOR_SCHEMA(LongIndexCreate)\n  .NumInputs(0)\n  .NumOutputs(1)\n  .SetDoc(R\"DOC(\nCreates a dictionary that maps int64 keys to consecutive integers\nfrom 1 to max_elements. Zero is reserved for unknown keys.\n)DOC\")\n  .Arg(\"max_elements\", \"Max number of elements, including the zero entry.\")\n  .Output(0, \"handler\", \"Pointer to an Index instance.\");\n\nOPERATOR_SCHEMA(StringIndexCreate)\n  .NumInputs(0)\n  .NumOutputs(1)\n  .SetDoc(R\"DOC(\nCreates a dictionary that maps string keys to consecutive integers\nfrom 1 to max_elements. Zero is reserved for unknown keys.\n)DOC\")\n  .Arg(\"max_elements\", \"Max number of elements, including the zero entry.\")\n  .Output(0, \"handle\", \"Pointer to an Index instance.\");\n\nOPERATOR_SCHEMA(IndexGet)\n  .NumInputs(2)\n  .NumOutputs(1)\n  .SetDoc(R\"DOC(\nGiven an index handle and a tensor of keys, return an Int tensor of same shape\ncontaining the indices for each of the keys. If the index is frozen, unknown\nentries are given index 0. Otherwise, new entries are added into the index.\nIf an insert is necessary but max_elements has been reached, fail.\n)DOC\")\n  .Input(0, \"handle\", \"Pointer to an Index instance.\")\n  .Input(1, \"keys\", \"Tensor of keys to be looked up.\")\n  .Output(0, \"indices\", \"Indices for each of the keys.\");\n\nOPERATOR_SCHEMA(IndexFreeze)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nFreezes the given index, disallowing creation of new index entries.\nShould not be called concurrently with IndexGet.\n)DOC\")\n    .Input(0, \"handle\", \"Pointer to an Index instance.\")\n    .Output(0, \"handle\", \"The input handle.\")\n    .EnforceInplace({{0, 0}});\n\nOPERATOR_SCHEMA(IndexLoad)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nLoads the index from the given 1-D tensor. Elements in the tensor will be given\nconsecutive indexes starting at 1. Fails if tensor contains repeated elements.\n)DOC\")\n    .Input(0, \"handle\", \"Pointer to an Index instance.\")\n    .Input(1, \"items\", \"1-D tensor with elements starting with index 1.\")\n    .Output(0, \"handle\", \"The input handle.\")\n    .EnforceInplace({{0, 0}})\n    .Arg(\n        \"skip_first_entry\",\n        \"If set, skips the first entry of the tensor. This allows \"\n        \"to load tensors that are aligned with an embedding, where the first \"\n        \"entry corresponds to the default 0 index entry.\");\n\nOPERATOR_SCHEMA(IndexStore)\n  .NumInputs(1)\n  .NumOutputs(1)\n  .SetDoc(R\"DOC(\nStores the keys of this index in a 1-D tensor. Since element 0 is reserved\nfor unknowns, the first element of the output tensor will be element of index 1.\n)DOC\")\n  .Input(0, \"handle\", \"Pointer to an Index instance.\")\n  .Output(0, \"items\", \"1-D tensor with elements starting with index 1.\");\n\nOPERATOR_SCHEMA(IndexSize)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nReturns the number of entries currently present in the index.\n)DOC\")\n    .Input(0, \"handle\", \"Pointer to an Index instance.\")\n    .Output(0, \"items\", \"Scalar int64 tensor with number of entries.\");\n\nNO_GRADIENT(IndexGetOp);\nNO_GRADIENT(IntIndexCreate);\nNO_GRADIENT(LongIndexCreate);\nNO_GRADIENT(StringIndexCreate);\nSHOULD_NOT_DO_GRADIENT(IndexFreeze);\nSHOULD_NOT_DO_GRADIENT(IndexLoad);\nSHOULD_NOT_DO_GRADIENT(IndexStore);\nSHOULD_NOT_DO_GRADIENT(IndexSize);\n\nclass IndexSerializer : public BlobSerializerBase {\n public:\n  IndexSerializer() {}\n  ~IndexSerializer() {}\n\n  void Serialize(\n      const Blob& blob,\n      const string& name,\n      SerializationAcceptor acceptor) override {\n    auto& base = blob.template Get<std::unique_ptr<IndexBase>>();\n    Blob tensor_blob;\n    auto* tensor_out = tensor_blob.template GetMutable<Tensor<CPUContext>>();\n\n    if (base->Type().Match<std::string>()) {\n      doStore<std::string>(base, tensor_out);\n    } else if (base->Type().Match<int32_t>()) {\n      doStore<int32_t>(base, tensor_out);\n    } else if (base->Type().Match<int64_t>()) {\n      doStore<int64_t>(base, tensor_out);\n    } else {\n      CAFFE_THROW(\"Index of this type can't be serialized.\");\n    }\n\n    CAFFE_ENFORCE(\n        tensor_out->size() <= std::numeric_limits<int32_t>::max(),\n        \"Index too large to be serialized.\");\n    BlobProto blob_proto;\n    TensorSerializer<CPUContext> ser;\n    ser.Serialize(\n        *tensor_out, name, blob_proto.mutable_tensor(), 0, tensor_out->size());\n    blob_proto.set_name(name);\n    blob_proto.set_type(\"std::unique_ptr<caffe2::IndexBase>\");\n\n    std::ostringstream os;\n    os << base->maxElements() << \" \" << base->isFrozen();\n    blob_proto.set_content(os.str());\n\n    acceptor(name, blob_proto.SerializeAsString());\n  }\n\n private:\n  template <typename T>\n  void doStore(\n      const std::unique_ptr<IndexBase>& base,\n      Tensor<CPUContext>* tensor_out) {\n    auto* dict = dynamic_cast_if_rtti<Index<T>*>(base.get());\n    CAFFE_ENFORCE(dict, \"Wrong dictionary type.\");\n    dict->Store(tensor_out);\n  }\n};\n\nclass IndexDeserializer : public BlobDeserializerBase {\n public:\n  void Deserialize(const BlobProto& proto, Blob* blob) override {\n    TensorDeserializer<CPUContext> deser;\n    Blob tensor_blob;\n    deser.Deserialize(proto, &tensor_blob);\n\n    std::istringstream is(proto.content());\n    int64_t maxElements{std::numeric_limits<int64_t>::max()};\n    bool isFrozen{false};\n    is >> maxElements >> isFrozen;\n\n    auto& tensor_in = tensor_blob.template Get<Tensor<CPUContext>>();\n    auto* base = blob->template GetMutable<std::unique_ptr<IndexBase>>();\n\n    if (tensor_in.IsType<std::string>()) {\n      doLoad<std::string>(base, maxElements, tensor_in);\n    } else if (tensor_in.IsType<int32_t>()) {\n      doLoad<int32_t>(base, maxElements, tensor_in);\n    } else if (tensor_in.IsType<int64_t>()) {\n      doLoad<int64_t>(base, maxElements, tensor_in);\n    } else {\n      CAFFE_THROW(\"Index of this type cannot be deserialized.\");\n    }\n\n    if (isFrozen) {\n      (*base)->Freeze();\n    }\n  }\n\n private:\n  template <typename T>\n  void doLoad(\n      std::unique_ptr<IndexBase>* base,\n      int64_t maxElements,\n      const Tensor<CPUContext>& tensor_in) {\n    base->reset(new Index<T>(maxElements));\n    auto* dict = dynamic_cast_if_rtti<Index<T>*>(base->get());\n    dict->Load(tensor_in.data<T>(), tensor_in.size());\n  }\n};\n\nCAFFE_KNOWN_TYPE(std::unique_ptr<caffe2::IndexBase>);\n\nREGISTER_BLOB_SERIALIZER(\n    (TypeMeta::Id<std::unique_ptr<caffe2::IndexBase>>()),\n    IndexSerializer);\nREGISTER_BLOB_DESERIALIZER(\n    std::unique_ptr<caffe2::IndexBase>,\n    IndexDeserializer);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/instance_norm_gradient_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/instance_norm_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, typename Context>\nbool InstanceNormGradientOp<T, Context>::RunOnDeviceWithOrderNHWC() {\n  const auto& input = Input(INPUT);\n  const auto& scale = Input(SCALE);\n  const auto& bias = Input(BIAS);\n  const auto& output_grad = Input(OUTPUT_GRAD);\n  const auto& mean = InputSize() >= 5 ? Input(MEAN) : mean_;\n  const auto& inv_stdev = InputSize() >= 6 ? Input(INV_STDEV) : inv_stdev_;\n  auto input_grad = Output(INPUT_GRAD);\n  auto scale_grad = Output(SCALE_GRAD);\n  auto bias_grad = Output(BIAS_GRAD);\n  CAFFE_ENFORCE_EQ(4, input.ndim());\n  const int N = input.dim32(0);\n  const int H = input.dim32(1);\n  const int W = input.dim32(2);\n  const int C = input.dim32(3);\n  CAFFE_ENFORCE_EQ(1, scale.ndim());\n  CAFFE_ENFORCE_EQ(C, scale.dim32(0));\n  CAFFE_ENFORCE_EQ(1, bias.ndim());\n  CAFFE_ENFORCE_EQ(C, bias.dim32(0));\n  CAFFE_ENFORCE_EQ(4, output_grad.ndim());\n  CAFFE_ENFORCE_EQ(N, output_grad.dim32(0));\n  CAFFE_ENFORCE_EQ(H, output_grad.dim32(1));\n  CAFFE_ENFORCE_EQ(W, output_grad.dim32(2));\n  CAFFE_ENFORCE_EQ(C, output_grad.dim32(3));\n  input_grad->ResizeLike(input);\n  scale_grad->ResizeLike(scale);\n  bias_grad->ResizeLike(bias);\n\n  ConstEigenVectorArrayMap<T> scale_arr(scale.template data<T>(), C);\n  ConstEigenVectorArrayMap<T> bias_arr(bias.template data<T>(), C);\n  EigenVectorArrayMap<T> scale_grad_arr(\n      scale_grad->template mutable_data<T>(), C);\n  EigenVectorArrayMap<T> bias_grad_arr(\n      bias_grad->template mutable_data<T>(), C);\n\n  // Resize before we get into the per-instance loop\n  if (InputSize() < 5) {\n    mean_.Resize(N, C);\n  }\n  if (InputSize() < 6) {\n    inv_stdev_.Resize(N, C);\n  }\n\n  // looping over per-instance and using Eigen blocks to extract out\n  // a chunk of channels\n  for (int n = 0; n < N; ++n) {\n    // All Eigen mats and arrs in here are per-instance.\n    ConstEigenArrayMap<T> input_mat(\n        input.template data<T>() + n * C * H * W, C, H * W);\n    ConstEigenArrayMap<T> output_grad_mat(\n        output_grad.template data<T>() + n * C * H * W, C, H * W);\n    EigenArrayMap<T> input_grad_mat(\n        input_grad->template mutable_data<T>() + n * C * H * W, C, H * W);\n\n    // Compute mean if it wasn't passed in\n    if (InputSize() < 5) {\n      EigenVectorArrayMap<T> mean_mutable_arr(\n          mean_.template mutable_data<T>() + n * C, C);\n      mean_mutable_arr = input_mat.rowwise().mean();\n    }\n    CAFFE_ENFORCE_EQ(2, mean.ndim());\n    CAFFE_ENFORCE_EQ(N, mean.dim32(0));\n    CAFFE_ENFORCE_EQ(C, mean.dim32(1));\n    ConstEigenVectorArrayMap<T> mean_arr(mean.template data<T>() + n * C, C);\n\n    // subtract mean\n    input_grad_mat = input_mat.colwise() - mean_arr;\n\n    // Compute 1 / stdev if it wasn't passed in\n    if (InputSize() < 6) {\n      EigenVectorArrayMap<T> inv_stdev_mutable_arr(\n          inv_stdev_.template mutable_data<T>() + n * C, C);\n\n      // Square the diffs along each channel and take the mean to get var\n      inv_stdev_mutable_arr = input_grad_mat.pow(2).rowwise().mean();\n      // sqrt to get stdev and take the inverse\n      inv_stdev_mutable_arr =\n          (inv_stdev_mutable_arr + epsilon_).sqrt().inverse();\n    }\n    CAFFE_ENFORCE_EQ(2, inv_stdev.ndim());\n    CAFFE_ENFORCE_EQ(N, inv_stdev.dim32(0));\n    CAFFE_ENFORCE_EQ(C, inv_stdev.dim32(1));\n\n    ConstEigenVectorArrayMap<T> inv_stdev_arr(\n        inv_stdev.template data<T>() + n * C, C);\n\n    // for each channel\n    // dl/dbias = sum_j dl/dy_j\n    bias_grad_arr += output_grad_mat.rowwise().sum();\n    // for each channel\n    // dl/dscale = sum_j dl/dy_j (x_j - mu) / stdev\n    scale_grad_arr +=\n        ((input_grad_mat.colwise() * inv_stdev_arr) * output_grad_mat)\n            .rowwise()\n            .sum();\n\n    // dl/dx_j = this gross thing\n    // Derived gradient and manually massaged it to minimize extra storage\n    // and number of vectorized calls.  Verified it with the autograd package\n    // in python.\n\n    // a = -1/(HW) sum_j dl/dy_j * (x_j - mu) / stdev^3\n    const auto temp = (inv_stdev_arr.pow(3) *\n                       (input_grad_mat * output_grad_mat).rowwise().mean() *\n                       -1).eval();\n    // b_j = a * (x_j - mu)\n    input_grad_mat.colwise() *= temp;\n\n    // c_j = b_j + dl/dy_j / stdev\n    input_grad_mat += output_grad_mat.colwise() * inv_stdev_arr;\n\n    // dl/dx_j = s * (c_j - mean(c_j))\n    const auto result_mean = input_grad_mat.rowwise().mean().eval();\n    input_grad_mat.colwise() -= result_mean;\n    input_grad_mat.colwise() *= scale_arr;\n  }\n\n  return true;\n}\n\ntemplate <typename T, typename Context>\nbool InstanceNormGradientOp<T, Context>::RunOnDeviceWithOrderNCHW() {\n  const auto& input = Input(INPUT);\n  const auto& scale = Input(SCALE);\n  const auto& bias = Input(BIAS);\n  const auto& output_grad = Input(OUTPUT_GRAD);\n  const auto& mean = InputSize() >= 5 ? Input(MEAN) : mean_;\n  const auto& inv_stdev = InputSize() >= 6 ? Input(INV_STDEV) : inv_stdev_;\n  auto input_grad = Output(INPUT_GRAD);\n  auto scale_grad = Output(SCALE_GRAD);\n  auto bias_grad = Output(BIAS_GRAD);\n  CAFFE_ENFORCE_EQ(4, input.ndim());\n  const int N = input.dim32(0);\n  const int C = input.dim32(1);\n  const int H = input.dim32(2);\n  const int W = input.dim32(3);\n  CAFFE_ENFORCE_EQ(1, scale.ndim());\n  CAFFE_ENFORCE_EQ(C, scale.dim32(0));\n  CAFFE_ENFORCE_EQ(1, bias.ndim());\n  CAFFE_ENFORCE_EQ(C, bias.dim32(0));\n  CAFFE_ENFORCE_EQ(4, output_grad.ndim());\n  CAFFE_ENFORCE_EQ(N, output_grad.dim32(0));\n  CAFFE_ENFORCE_EQ(C, output_grad.dim32(1));\n  CAFFE_ENFORCE_EQ(H, output_grad.dim32(2));\n  CAFFE_ENFORCE_EQ(W, output_grad.dim32(3));\n  input_grad->ResizeLike(input);\n  scale_grad->ResizeLike(scale);\n  bias_grad->ResizeLike(bias);\n\n  ConstEigenArrayMap<T> input_mat(input.template data<T>(), H * W, N * C);\n  ConstEigenVectorArrayMap<T> scale_arr(scale.template data<T>(), C);\n  ConstEigenVectorArrayMap<T> bias_arr(bias.template data<T>(), C);\n  ConstEigenArrayMap<T> output_grad_mat(\n      output_grad.template data<T>(), H * W, N * C);\n\n  EigenArrayMap<T> input_grad_mat(\n      input_grad->template mutable_data<T>(), H * W, N * C);\n  EigenVectorArrayMap<T> scale_grad_arr(\n      scale_grad->template mutable_data<T>(), C);\n  EigenVectorArrayMap<T> bias_grad_arr(\n      bias_grad->template mutable_data<T>(), C);\n\n  // Compute mean if it wasn't passed in\n  if (InputSize() < 5) {\n    mean_.Resize(N, C);\n    EigenVectorArrayMap<T> mean_mutable_arr(\n        mean_.template mutable_data<T>(), N * C);\n    mean_mutable_arr = input_mat.colwise().mean();\n  }\n  CAFFE_ENFORCE_EQ(2, mean.ndim());\n  CAFFE_ENFORCE_EQ(N, mean.dim32(0));\n  CAFFE_ENFORCE_EQ(C, mean.dim32(1));\n  ConstEigenVectorArrayMap<T> mean_arr(mean.template data<T>(), N * C);\n\n  // subtract mean\n  input_grad_mat = input_mat.rowwise() - mean_arr.transpose();\n\n  // compute 1 / stdev if not passed in\n  if (InputSize() < 6) {\n    inv_stdev_.Resize(N, C);\n    EigenVectorArrayMap<T> inv_stdev_mutable_arr(\n        inv_stdev_.template mutable_data<T>(), N * C);\n\n    // Square the diffs along each column and take mean to get var\n    inv_stdev_mutable_arr = input_grad_mat.pow(2).colwise().mean();\n    // sqrt to get stdev and then invert\n    inv_stdev_mutable_arr = (inv_stdev_mutable_arr + epsilon_).sqrt().inverse();\n  }\n  CAFFE_ENFORCE_EQ(2, inv_stdev.ndim());\n  CAFFE_ENFORCE_EQ(N, inv_stdev.dim32(0));\n  CAFFE_ENFORCE_EQ(C, inv_stdev.dim32(1));\n\n  ConstEigenVectorArrayMap<T> inv_stdev_arr(\n      inv_stdev.template data<T>(), N * C);\n\n  // Visit comments in the NHWC version about these gradients.  scale and bias\n  // grads are about the same, but the input grads no longer slice out one\n  // example at a time and instead vectorize across all N * C feature maps.\n\n  // scale and bias gradients\n  scale_grad_arr.setZero();\n  bias_grad_arr.setZero();\n  for (int n = 0; n < N; ++n) {\n    scale_grad_arr += ((input_grad_mat.rowwise() * inv_stdev_arr.transpose()) *\n                       output_grad_mat)\n                          .block(0, n * C, H * W, C)\n                          .colwise()\n                          .sum();\n    bias_grad_arr += output_grad_mat.block(0, n * C, H * W, C).colwise().sum();\n  }\n\n  // input gradient\n  const auto temp = ((inv_stdev_arr.pow(3).transpose() *\n                      (input_grad_mat * output_grad_mat).colwise().mean()) *\n                     -1).eval();\n  input_grad_mat.rowwise() *= temp;\n\n  input_grad_mat += output_grad_mat.rowwise() * inv_stdev_arr.transpose();\n\n  const auto result_mean = input_grad_mat.colwise().mean().eval();\n  input_grad_mat.rowwise() -= result_mean;\n\n  for (int n = 0; n < N; ++n) {\n    input_grad_mat.block(0, n * C, H * W, C).rowwise() *= scale_arr.transpose();\n  }\n\n  return true;\n}\n\nclass GetInstanceNormGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> inputs{I(0), I(1), I(2), GO(0)};\n    if (def_.output_size() >= 2) {\n      inputs.push_back(O(1));\n    }\n    if (def_.output_size() >= 3) {\n      inputs.push_back(O(2));\n    }\n    return SingleGradientDef(\n        \"InstanceNormGradient\",\n        \"\",\n        inputs,\n        vector<string>{GI(0), GI(1), GI(2)});\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    InstanceNormGradient,\n    InstanceNormGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(InstanceNormGradient).NumInputs(4, 6).NumOutputs(3);\n\nREGISTER_GRADIENT(InstanceNorm, GetInstanceNormGradient);\n}\n"
  },
  {
    "path": "caffe2/operators/instance_norm_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/instance_norm_op.h\"\n\nnamespace caffe2 {\n\n// Here lives two separate implementations of the forward and backward passes of\n// instance normalization, one for NHWC order and the other for NCHW order.\n// Two implementations allow us to make use of Eigen vectorized operations\n// without an expensive tensor transpose operation.\n\ntemplate <typename T, typename Context>\nbool InstanceNormOp<T, Context>::RunOnDeviceWithOrderNHWC() {\n  const auto& X = Input(INPUT);\n  auto* Y = Output(OUTPUT);\n  CAFFE_ENFORCE(Y != &X, \"Can't run InstanceNorm NHWC in-place\");\n  auto* mean = OutputSize() > 1 ? Output(MEAN) : &mean_;\n  auto* inv_stdev = OutputSize() > 1 ? Output(INV_STDEV) : &inv_stdev_;\n  const int N = X.dim32(0);\n  const int H = X.dim32(1);\n  const int W = X.dim32(2);\n  const int C = X.dim32(3);\n  const size_t offset = H * W * C;\n\n  CAFFE_ENFORCE_EQ(Input(SCALE).size(), C);\n  CAFFE_ENFORCE_EQ(Input(BIAS).size(), C);\n\n  Y->ResizeLike(X);\n  mean->Resize(N, C);\n  inv_stdev->Resize(N, C);\n  ConstEigenVectorArrayMap<T> scale(Input(SCALE).template data<T>(), C);\n  ConstEigenVectorArrayMap<T> bias(Input(BIAS).template data<T>(), C);\n  for (int n = 0; n < N; ++n) {\n    ConstEigenArrayMap<T> Xmat(X.template data<T>() + offset * n, C, H * W);\n    EigenArrayMap<T> Ymat(Y->template mutable_data<T>() + offset * n, C, H * W);\n    EigenVectorArrayMap<T> mean_arr(\n        mean->template mutable_data<T>() + n * C, C);\n    EigenVectorArrayMap<T> inv_stdev_arr(\n        inv_stdev->template mutable_data<T>() + n * C, C);\n\n    // The following effectively does the row wise mean computation:\n    //   mean_arr = Xmat.rowwise().mean();\n    // but manually vectorizes over columns.\n    mean_arr = Xmat.col(0);\n    for (int i = 1; i < H * W; ++i) {\n      mean_arr += Xmat.col(i);\n    }\n    mean_arr *= 1. / (H * W);\n    Ymat = Xmat.colwise() - mean_arr;\n    // The following effectively does row wise squared norm computation,\n    // but manually vectorizes over columns similar to the mean case.\n    inv_stdev_arr = Ymat.col(0) * Ymat.col(0);\n    for (int i = 1; i < H * W; ++i) {\n      inv_stdev_arr += Ymat.col(i) * Ymat.col(i);\n    }\n    inv_stdev_arr = (inv_stdev_arr / (H * W) + epsilon_).sqrt().inverse();\n    Ymat = (Ymat.colwise() * (inv_stdev_arr * scale)).colwise() + bias;\n  }\n  return true;\n}\n\ntemplate <typename T, typename Context>\nbool InstanceNormOp<T, Context>::RunOnDeviceWithOrderNCHW() {\n  const auto& X = Input(INPUT);\n  const auto& scale = Input(SCALE);\n  const auto& bias = Input(BIAS);\n  auto* Y = Output(OUTPUT);\n  auto* mean = OutputSize() > 1 ? Output(MEAN) : &mean_;\n  auto* inv_stdev = OutputSize() > 1 ? Output(INV_STDEV) : &inv_stdev_;\n  const int N = X.dim32(0);\n  const int C = X.dim32(1);\n  const int H = X.dim32(2);\n  const int W = X.dim32(3);\n\n  CAFFE_ENFORCE_EQ(scale.size(), C);\n  CAFFE_ENFORCE_EQ(bias.size(), C);\n\n  Y->ResizeLike(X);\n  mean->Resize(N, C);\n  inv_stdev->Resize(N, C);\n\n  const auto* Xdata = X.template data<T>();\n  auto* Ydata = Y->template mutable_data<T>();\n  const auto* scale_data = scale.template data<T>();\n  const auto* bias_data = bias.template data<T>();\n  auto* mean_data = mean->template mutable_data<T>();\n  auto* inv_stdev_data = inv_stdev->template mutable_data<T>();\n\n  // TODO: benchmark parallelization strategies.\n  for (auto i = 0; i < N * C; ++i) {\n    ConstEigenVectorArrayMap<T> Xi(Xdata + H * W * i, H * W);\n    const T Xi_mean = Xi.mean();\n    const T squared_norm = (Xi - Xi_mean).matrix().squaredNorm();\n    const T inv_stdev = 1.0 / std::sqrt(squared_norm / (H * W) + epsilon_);\n    mean_data[i] = Xi_mean;\n    inv_stdev_data[i] = inv_stdev;\n    EigenVectorArrayMap<T> Yi(Ydata + H * W * i, H * W);\n    const T channel_scale = inv_stdev * scale_data[i % C];\n    const T channel_shift = bias_data[i % C] - Xi_mean * channel_scale;\n    Yi = Xi * channel_scale + channel_shift;\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(InstanceNorm, InstanceNormOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(InstanceNorm)\n    .NumInputs(3)\n    .NumOutputs(1, 3)\n    .AllowInplace({{0,0}})\n    .SetDoc(R\"DOC(\nCarries out instance normalization as described in the paper\nhttps://arxiv.org/abs/1607.08022. Depending on the mode it is being run,\nthere are multiple cases for the number of outputs, which we list below:\n\n  * Output case #1: output\n  * Output case #2: output, saved_mean\n    - don't use, doesn't make sense but won't crash\n  * Output case #3: output, saved_mean, saved_inv_stdev\n    - Makes sense for training only\n\nFor training mode, type 3 is faster in the sense that for the backward\npass, it is able to reuse the saved mean and inv_stdev in the gradient\ncomputation.\n)DOC\")\n    .Arg(\"epsilon\", \"The epsilon value to use to avoid division by zero.\")\n    .Arg(\"order\", \"A StorageOrder string.\")\n    .Input(\n        0,\n        \"input\",\n        \"The input 4-dimensional tensor of shape NCHW or NHWC depending \"\n        \"on the order parameter.\")\n    .Input(1, \"scale\", \"The input 1-dimensional scale tensor of size C.\")\n    .Input(2, \"bias\", \"The input 1-dimensional bias tensor of size C.\")\n    .Output(\n        0,\n        \"output\",\n        \"The output 4-dimensional tensor of the same shape as input.\")\n    .Output(\n        1,\n        \"saved_mean\",\n        \"Optional saved mean used during training to speed up gradient \"\n        \"computation. Should not be used for testing.\")\n    .Output(\n        2,\n        \"saved_inv_stdev\",\n        \"Optional saved inverse stdev used during training to speed up \"\n        \"gradient computation. Should not be used for testing.\");\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/instance_norm_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cfloat>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/instance_norm_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n__global__ void InstanceNormMeanKernel(\n    int N,\n    int C,\n    int dim,\n    int N_stride,\n    int C_stride,\n    int dim_stride,\n    const float* input_data,\n    float* mean_data) {\n  CUDA_1D_KERNEL_LOOP(i, N * C) {\n    const auto n = i / C;\n    const auto c = i % C;\n    mean_data[i] = 0;\n    auto input_offset = input_data + n * N_stride + c * C_stride;\n    for (int j = 0; j < dim; ++j) {\n      mean_data[i] += *input_offset;\n      input_offset += dim_stride;\n    }\n    mean_data[i] /= dim;\n  }\n}\n\n__global__ void InstanceNormInvStdevKernel(\n    int N,\n    int C,\n    int dim,\n    int N_stride,\n    int C_stride,\n    int dim_stride,\n    float epsilon,\n    const float* input_data,\n    const float* mean_data,\n    float* inv_stdev_data) {\n  CUDA_1D_KERNEL_LOOP(i, N * C) {\n    const auto n = i / C;\n    const auto c = i % C;\n    inv_stdev_data[i] = 0;\n    auto input_offset = input_data + n * N_stride + c * C_stride;\n    for (int j = 0; j < dim; ++j) {\n      float diff = *input_offset - mean_data[i];\n      inv_stdev_data[i] += diff * diff;\n      input_offset += dim_stride;\n    }\n    inv_stdev_data[i] /= dim;\n    inv_stdev_data[i] += epsilon;\n    inv_stdev_data[i] = 1.0 / std::sqrt(inv_stdev_data[i]);\n  }\n}\n\n__global__ void InstanceNormKernel(\n    int N,\n    int C,\n    int dim,\n    int N_stride,\n    int C_stride,\n    int dim_stride,\n    const float* input_data,\n    const float* scale_data,\n    const float* bias_data,\n    const float* mean_data,\n    const float* inv_stdev_data,\n    float* output_data) {\n  CUDA_1D_KERNEL_LOOP(i, N * C * dim) {\n    auto index = i;\n    const auto j = index % dim;\n    index /= dim;\n    const auto c = index % C;\n    index /= C;\n    const auto n = index;\n\n    index = n * N_stride + c * C_stride + j * dim_stride;\n\n    const auto stat_idx = n * C + c;\n\n    output_data[index] = (input_data[index] - mean_data[stat_idx]) *\n            inv_stdev_data[stat_idx] * scale_data[c] +\n        bias_data[c];\n  }\n}\n\n__global__ void InstanceNormGradientKernel(\n    int N,\n    int C,\n    int dim,\n    int N_stride,\n    int C_stride,\n    int dim_stride,\n    const float* input_data,\n    const float* scale_data,\n    const float* bias_data,\n    const float* output_grad_data,\n    const float* mean_data,\n    const float* inv_stdev_data,\n    float* input_grad_data) {\n  CUDA_1D_KERNEL_LOOP(i, N * C) {\n    const auto n = i / C;\n    const auto c = i % C;\n\n    auto input_grad_offset = input_grad_data + n * N_stride + c * C_stride;\n    auto input_offset = input_data + n * N_stride + c * C_stride;\n    for (int j = 0; j < dim; ++j) {\n      *input_grad_offset = *input_offset - mean_data[i];\n      input_grad_offset += dim_stride;\n      input_offset += dim_stride;\n    }\n\n    auto temp = 0.0;\n    input_grad_offset = input_grad_data + n * N_stride + c * C_stride;\n    auto output_grad_offset = output_grad_data + n * N_stride + c * C_stride;\n    for (int j = 0; j < dim; ++j) {\n      temp += *input_grad_offset * *output_grad_offset;\n      input_grad_offset += dim_stride;\n      output_grad_offset += dim_stride;\n    }\n\n    temp *= -powf(inv_stdev_data[i], 3.0) / dim;\n\n    input_grad_offset = input_grad_data + n * N_stride + c * C_stride;\n    output_grad_offset = output_grad_data + n * N_stride + c * C_stride;\n    auto mean = 0.0;\n    for (int j = 0; j < dim; ++j) {\n      *input_grad_offset *= temp;\n      *input_grad_offset += *output_grad_offset * inv_stdev_data[i];\n      mean += *input_grad_offset;\n      input_grad_offset += dim_stride;\n      output_grad_offset += dim_stride;\n    }\n    mean /= dim;\n\n    input_grad_offset = input_grad_data + n * N_stride + c * C_stride;\n    for (int j = 0; j < dim; ++j) {\n      *input_grad_offset -= mean;\n      *input_grad_offset *= scale_data[c];\n      input_grad_offset += dim_stride;\n    }\n  }\n}\n\n__global__ void InstanceNormScaleBiasGradientKernel(\n    int N,\n    int C,\n    int dim,\n    int N_stride,\n    int C_stride,\n    int dim_stride,\n    const float* input_data,\n    const float* mean_data,\n    const float* output_grad_data,\n    const float* inv_stdev_data,\n    float* scale_grad_data,\n    float* bias_grad_data) {\n  CUDA_1D_KERNEL_LOOP(c, C) {\n    scale_grad_data[c] = 0;\n    bias_grad_data[c] = 0;\n    auto input_offset = input_data + c * C_stride;\n    auto output_grad_offset = output_grad_data + c * C_stride;\n    auto mean_offset = mean_data + c;\n    auto inv_stdev_offset = inv_stdev_data + c;\n    for (int n = 0; n < N; ++n) {\n      auto input_offset_inner = input_offset + n * N_stride;\n      auto output_grad_offset_inner = output_grad_offset + n * N_stride;\n      for (int i = 0; i < dim; ++i) {\n        scale_grad_data[c] += (*input_offset_inner - *mean_offset) *\n            *inv_stdev_offset * *output_grad_offset_inner;\n        bias_grad_data[c] += *output_grad_offset_inner;\n        input_offset_inner += dim_stride;\n        output_grad_offset_inner += dim_stride;\n      }\n      mean_offset += C;\n      inv_stdev_offset += C;\n    }\n  }\n}\n\n} // namespace\n\ntemplate <>\nbool InstanceNormOp<float, CUDAContext>::RunOnDeviceWithOrderNHWC() {\n  const auto& input = Input(INPUT);\n  const auto& scale = Input(SCALE);\n  const auto& bias = Input(BIAS);\n  auto output = Output(OUTPUT);\n  auto mean = OutputSize() >= 2 ? Output(MEAN) : &mean_;\n  auto inv_stdev = OutputSize() >= 3 ? Output(INV_STDEV) : &inv_stdev_;\n  CAFFE_ENFORCE_EQ(4, input.ndim());\n  const int N = input.dim32(0);\n  const int H = input.dim32(1);\n  const int W = input.dim32(2);\n  const int C = input.dim32(3);\n  CAFFE_ENFORCE_EQ(1, scale.ndim());\n  CAFFE_ENFORCE_EQ(C, scale.dim32(0));\n  CAFFE_ENFORCE_EQ(1, bias.ndim());\n  CAFFE_ENFORCE_EQ(C, bias.dim32(0));\n  output->ResizeLike(input);\n  mean->Resize(N, C);\n  inv_stdev->Resize(N, C);\n\n  const auto input_data = input.data<float>();\n  const auto scale_data = scale.data<float>();\n  const auto bias_data = bias.data<float>();\n  auto output_data = output->mutable_data<float>();\n  auto mean_data = mean->mutable_data<float>();\n  auto inv_stdev_data = inv_stdev->mutable_data<float>();\n\n  const auto dim = H * W;\n  const auto N_stride = C * H * W;\n  const auto C_stride = 1;\n  const auto dim_stride = C;\n\n  InstanceNormMeanKernel<<<\n      CAFFE_GET_BLOCKS(N * C),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N, C, dim, N_stride, C_stride, dim_stride, input_data, mean_data);\n\n  InstanceNormInvStdevKernel<<<\n      CAFFE_GET_BLOCKS(N * C),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      C,\n      dim,\n      N_stride,\n      C_stride,\n      dim_stride,\n      epsilon_,\n      input_data,\n      mean_data,\n      inv_stdev_data);\n\n  InstanceNormKernel<<<\n      CAFFE_GET_BLOCKS(N * C * H * W),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      C,\n      dim,\n      N_stride,\n      C_stride,\n      dim_stride,\n      input_data,\n      scale_data,\n      bias_data,\n      mean_data,\n      inv_stdev_data,\n      output_data);\n\n  return true;\n}\n\ntemplate <>\nbool InstanceNormOp<float, CUDAContext>::RunOnDeviceWithOrderNCHW() {\n  const auto& input = Input(INPUT);\n  const auto& scale = Input(SCALE);\n  const auto& bias = Input(BIAS);\n  auto output = Output(OUTPUT);\n  auto mean = OutputSize() >= 2 ? Output(MEAN) : &mean_;\n  auto inv_stdev = OutputSize() >= 3 ? Output(INV_STDEV) : &inv_stdev_;\n  CAFFE_ENFORCE_EQ(4, input.ndim());\n  const int N = input.dim32(0);\n  const int C = input.dim32(1);\n  const int H = input.dim32(2);\n  const int W = input.dim32(3);\n  CAFFE_ENFORCE_EQ(1, scale.ndim());\n  CAFFE_ENFORCE_EQ(C, scale.dim32(0));\n  CAFFE_ENFORCE_EQ(1, bias.ndim());\n  CAFFE_ENFORCE_EQ(C, bias.dim32(0));\n  output->ResizeLike(input);\n  mean->Resize(N, C);\n  inv_stdev->Resize(N, C);\n\n  const auto input_data = input.data<float>();\n  const auto scale_data = scale.data<float>();\n  const auto bias_data = bias.data<float>();\n  auto output_data = output->mutable_data<float>();\n  auto mean_data = mean->mutable_data<float>();\n  auto inv_stdev_data = inv_stdev->mutable_data<float>();\n\n  const auto dim = H * W;\n  const auto N_stride = C * H * W;\n  const auto C_stride = H * W;\n  const auto dim_stride = 1;\n\n  InstanceNormMeanKernel<<<\n      CAFFE_GET_BLOCKS(N * C),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N, C, dim, N_stride, C_stride, dim_stride, input_data, mean_data);\n\n  InstanceNormInvStdevKernel<<<\n      CAFFE_GET_BLOCKS(N * C),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      C,\n      dim,\n      N_stride,\n      C_stride,\n      dim_stride,\n      epsilon_,\n      input_data,\n      mean_data,\n      inv_stdev_data);\n\n  InstanceNormKernel<<<\n      CAFFE_GET_BLOCKS(N * C * H * W),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      C,\n      dim,\n      N_stride,\n      C_stride,\n      dim_stride,\n      input_data,\n      scale_data,\n      bias_data,\n      mean_data,\n      inv_stdev_data,\n      output_data);\n\n  return true;\n}\n\ntemplate <>\nbool InstanceNormGradientOp<float, CUDAContext>::RunOnDeviceWithOrderNHWC() {\n  const auto& input = Input(INPUT);\n  const auto& scale = Input(SCALE);\n  const auto& bias = Input(BIAS);\n  const auto& output_grad = Input(OUTPUT_GRAD);\n  const auto& mean = InputSize() >= 5 ? Input(MEAN) : mean_;\n  const auto& inv_stdev = InputSize() >= 6 ? Input(INV_STDEV) : inv_stdev_;\n  auto input_grad = Output(INPUT_GRAD);\n  auto scale_grad = Output(SCALE_GRAD);\n  auto bias_grad = Output(BIAS_GRAD);\n  CAFFE_ENFORCE_EQ(4, input.ndim());\n  const int N = input.dim32(0);\n  const int H = input.dim32(1);\n  const int W = input.dim32(2);\n  const int C = input.dim32(3);\n  CAFFE_ENFORCE_EQ(1, scale.ndim());\n  CAFFE_ENFORCE_EQ(C, scale.dim32(0));\n  CAFFE_ENFORCE_EQ(1, bias.ndim());\n  CAFFE_ENFORCE_EQ(C, bias.dim32(0));\n  CAFFE_ENFORCE_EQ(4, output_grad.ndim());\n  CAFFE_ENFORCE_EQ(N, output_grad.dim32(0));\n  CAFFE_ENFORCE_EQ(H, output_grad.dim32(1));\n  CAFFE_ENFORCE_EQ(W, output_grad.dim32(2));\n  CAFFE_ENFORCE_EQ(C, output_grad.dim32(3));\n  input_grad->ResizeLike(input);\n  scale_grad->ResizeLike(scale);\n  bias_grad->ResizeLike(bias);\n\n  const auto input_data = input.data<float>();\n  const auto scale_data = scale.data<float>();\n  const auto bias_data = bias.data<float>();\n  const auto output_grad_data = output_grad.data<float>();\n\n  auto input_grad_data = input_grad->mutable_data<float>();\n  auto scale_grad_data = scale_grad->mutable_data<float>();\n  auto bias_grad_data = bias_grad->mutable_data<float>();\n\n  const auto dim = H * W;\n  const auto N_stride = C * H * W;\n  const auto C_stride = 1;\n  const auto dim_stride = C;\n\n  if (InputSize() < 5) {\n    mean_.Resize(N, C);\n    auto mean_mutable_data = mean_.mutable_data<float>();\n    InstanceNormMeanKernel<<<\n        CAFFE_GET_BLOCKS(N * C),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        N,\n        C,\n        dim,\n        N_stride,\n        C_stride,\n        dim_stride,\n        input_data,\n        mean_mutable_data);\n  }\n  CAFFE_ENFORCE_EQ(2, mean.ndim());\n  CAFFE_ENFORCE_EQ(N, mean.dim32(0));\n  CAFFE_ENFORCE_EQ(C, mean.dim32(1));\n\n  const auto mean_data = mean.data<float>();\n\n  if (InputSize() < 6) {\n    inv_stdev_.Resize(N, C);\n    auto inv_stdev_mutable_data = inv_stdev_.mutable_data<float>();\n    InstanceNormInvStdevKernel<<<\n        CAFFE_GET_BLOCKS(N * C),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        N,\n        C,\n        dim,\n        N_stride,\n        C_stride,\n        dim_stride,\n        epsilon_,\n        input_data,\n        mean_data,\n        inv_stdev_mutable_data);\n  }\n  CAFFE_ENFORCE_EQ(2, inv_stdev.ndim());\n  CAFFE_ENFORCE_EQ(N, inv_stdev.dim32(0));\n  CAFFE_ENFORCE_EQ(C, inv_stdev.dim32(1));\n\n  const auto inv_stdev_data = inv_stdev.data<float>();\n\n  InstanceNormScaleBiasGradientKernel<<<\n      CAFFE_GET_BLOCKS(C),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      C,\n      dim,\n      N_stride,\n      C_stride,\n      dim_stride,\n      input_data,\n      mean_data,\n      output_grad_data,\n      inv_stdev_data,\n      scale_grad_data,\n      bias_grad_data);\n\n  InstanceNormGradientKernel<<<\n      CAFFE_GET_BLOCKS(N * C),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      C,\n      dim,\n      N_stride,\n      C_stride,\n      dim_stride,\n      input_data,\n      scale_data,\n      bias_data,\n      output_grad_data,\n      mean_data,\n      inv_stdev_data,\n      input_grad_data);\n\n  return true;\n}\n\ntemplate <>\nbool InstanceNormGradientOp<float, CUDAContext>::RunOnDeviceWithOrderNCHW() {\n  const auto& input = Input(INPUT);\n  const auto& scale = Input(SCALE);\n  const auto& bias = Input(BIAS);\n  const auto& output_grad = Input(OUTPUT_GRAD);\n  const auto& mean = InputSize() >= 5 ? Input(MEAN) : mean_;\n  const auto& inv_stdev = InputSize() >= 6 ? Input(INV_STDEV) : inv_stdev_;\n  auto input_grad = Output(INPUT_GRAD);\n  auto scale_grad = Output(SCALE_GRAD);\n  auto bias_grad = Output(BIAS_GRAD);\n  CAFFE_ENFORCE_EQ(4, input.ndim());\n  const int N = input.dim32(0);\n  const int C = input.dim32(1);\n  const int H = input.dim32(2);\n  const int W = input.dim32(3);\n  CAFFE_ENFORCE_EQ(1, scale.ndim());\n  CAFFE_ENFORCE_EQ(C, scale.dim32(0));\n  CAFFE_ENFORCE_EQ(1, bias.ndim());\n  CAFFE_ENFORCE_EQ(C, bias.dim32(0));\n  CAFFE_ENFORCE_EQ(4, output_grad.ndim());\n  CAFFE_ENFORCE_EQ(N, output_grad.dim32(0));\n  CAFFE_ENFORCE_EQ(C, output_grad.dim32(1));\n  CAFFE_ENFORCE_EQ(H, output_grad.dim32(2));\n  CAFFE_ENFORCE_EQ(W, output_grad.dim32(3));\n  input_grad->ResizeLike(input);\n  scale_grad->ResizeLike(scale);\n  bias_grad->ResizeLike(bias);\n\n  const auto input_data = input.data<float>();\n  const auto scale_data = scale.data<float>();\n  const auto bias_data = bias.data<float>();\n  const auto output_grad_data = output_grad.data<float>();\n\n  auto input_grad_data = input_grad->mutable_data<float>();\n  auto scale_grad_data = scale_grad->mutable_data<float>();\n  auto bias_grad_data = bias_grad->mutable_data<float>();\n\n  const auto dim = H * W;\n  const auto N_stride = C * H * W;\n  const auto C_stride = H * W;\n  const auto dim_stride = 1;\n\n  if (InputSize() < 5) {\n    mean_.Resize(N, C);\n    auto mean_mutable_data = mean_.mutable_data<float>();\n    InstanceNormMeanKernel<<<\n        CAFFE_GET_BLOCKS(N * C),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        N,\n        C,\n        dim,\n        N_stride,\n        C_stride,\n        dim_stride,\n        input_data,\n        mean_mutable_data);\n  }\n  CAFFE_ENFORCE_EQ(2, mean.ndim());\n  CAFFE_ENFORCE_EQ(N, mean.dim32(0));\n  CAFFE_ENFORCE_EQ(C, mean.dim32(1));\n\n  const auto mean_data = mean.data<float>();\n\n  if (InputSize() < 6) {\n    inv_stdev_.Resize(N, C);\n    auto inv_stdev_mutable_data = inv_stdev_.mutable_data<float>();\n    InstanceNormInvStdevKernel<<<\n        CAFFE_GET_BLOCKS(N * C),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        N,\n        C,\n        dim,\n        N_stride,\n        C_stride,\n        dim_stride,\n        epsilon_,\n        input_data,\n        mean_data,\n        inv_stdev_mutable_data);\n  }\n  CAFFE_ENFORCE_EQ(2, inv_stdev.ndim());\n  CAFFE_ENFORCE_EQ(N, inv_stdev.dim32(0));\n  CAFFE_ENFORCE_EQ(C, inv_stdev.dim32(1));\n\n  const auto inv_stdev_data = inv_stdev.data<float>();\n\n  InstanceNormScaleBiasGradientKernel<<<\n      CAFFE_GET_BLOCKS(C),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      C,\n      dim,\n      N_stride,\n      C_stride,\n      dim_stride,\n      input_data,\n      mean_data,\n      output_grad_data,\n      inv_stdev_data,\n      scale_grad_data,\n      bias_grad_data);\n\n  InstanceNormGradientKernel<<<\n      CAFFE_GET_BLOCKS(N * C),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      C,\n      dim,\n      N_stride,\n      C_stride,\n      dim_stride,\n      input_data,\n      scale_data,\n      bias_data,\n      output_grad_data,\n      mean_data,\n      inv_stdev_data,\n      input_grad_data);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(InstanceNorm, InstanceNormOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    InstanceNormGradient,\n    InstanceNormGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/instance_norm_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_INSTANCE_NORM_OP_H_\n#define CAFFE2_OPERATORS_INSTANCE_NORM_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass InstanceNormOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  InstanceNormOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        epsilon_(OperatorBase::GetSingleArgument<T>(\"epsilon\", 1e-5f)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CAFFE_ENFORCE(epsilon_ >= 0, \"Must pass a nonnegative epsilon.\");\n  }\n  ~InstanceNormOp() {}\n\n  bool RunOnDevice() {\n    switch (order_) {\n      case StorageOrder::NHWC:\n        return RunOnDeviceWithOrderNHWC();\n      case StorageOrder::NCHW:\n        return RunOnDeviceWithOrderNCHW();\n      default:\n        CAFFE_THROW(\"Unknown storage order: \", order_);\n    }\n  }\n\n  bool RunOnDeviceWithOrderNHWC();\n  bool RunOnDeviceWithOrderNCHW();\n\n protected:\n  // parameters\n  T epsilon_;\n  StorageOrder order_;\n\n  // temp results that get passed to the gradient, but are otherwise stored here\n  Tensor<Context> mean_;\n  Tensor<Context> inv_stdev_;\n\n  INPUT_TAGS(INPUT, SCALE, BIAS);\n  OUTPUT_TAGS(OUTPUT, MEAN, INV_STDEV);\n};\n\ntemplate <typename T, class Context>\nclass InstanceNormGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  InstanceNormGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        epsilon_(OperatorBase::GetSingleArgument<T>(\"epsilon\", 1e-5f)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CAFFE_ENFORCE(epsilon_ >= 0, \"Must pass a nonnegative epsilon.\");\n  }\n  ~InstanceNormGradientOp() {}\n\n  bool RunOnDevice() {\n    switch (order_) {\n      case StorageOrder::NHWC:\n        return RunOnDeviceWithOrderNHWC();\n      case StorageOrder::NCHW:\n        return RunOnDeviceWithOrderNCHW();\n      default:\n        CAFFE_THROW(\"Unknown storage order: \", order_);\n    }\n  }\n\n  bool RunOnDeviceWithOrderNHWC();\n  bool RunOnDeviceWithOrderNCHW();\n\n protected:\n  // parameters\n  T epsilon_;\n  StorageOrder order_;\n\n  // temp results that could get passed through to this gradient, but if not,\n  // are stored here\n  Tensor<Context> mean_;\n  Tensor<Context> inv_stdev_;\n\n  INPUT_TAGS(INPUT, SCALE, BIAS, OUTPUT_GRAD, MEAN, INV_STDEV);\n  OUTPUT_TAGS(INPUT_GRAD, SCALE_GRAD, BIAS_GRAD);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_INSTANCE_NORM_OP_H_\n"
  },
  {
    "path": "caffe2/operators/jsd_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/jsd_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\nstatic constexpr float kLOG_THRESHOLD() {\n  return 1e-20;\n}\n\ninline float logit(float p) {\n  // it computes log(p / (1-p))\n  // to avoid numeric issue, hard code p log(p) when p approaches 0\n  float x = std::min(std::max(p, kLOG_THRESHOLD()), 1 - kLOG_THRESHOLD());\n  return -log(1. / x - 1.);\n}\n\ninline float entropy(float p) {\n  if (p < kLOG_THRESHOLD() || 1 - p < kLOG_THRESHOLD()) {\n    return 0.;\n  } else {\n    float q = 1 - p;\n    return -p * log(p) - q * log(q);\n  }\n}\n} // namespace\n\ntemplate <>\nbool BernoulliJSDOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0); // predicted probabilities\n  auto& T = Input(1); // target probabilities\n  auto* L = Output(0); // JSD loss output\n  int N = X.size();\n  CAFFE_ENFORCE_EQ(T.size(), N);\n  L->ResizeLike(X);\n  auto* x_data = X.data<float>();\n  auto* t_data = T.data<float>();\n  auto* l_data = L->mutable_data<float>();\n  for (int i = 0; i < N; i++) {\n    auto p_mdl = x_data[i];\n    auto p_emp = t_data[i];\n    auto p_avg = (p_mdl + p_emp) / 2.;\n    auto jsd = entropy(p_avg) - (entropy(p_mdl) + entropy(p_emp)) / 2.;\n    l_data[i] = jsd;\n  }\n  return true;\n}\n\ntemplate <>\nbool BernoulliJSDGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& go = Input(0);\n  auto& X = Input(1);\n  auto& T = Input(2);\n  auto* gi = Output(0);\n  int N = X.size();\n  gi->ResizeLike(X);\n  auto* go_data = go.data<float>();\n  auto* x_data = X.data<float>();\n  auto* t_data = T.data<float>();\n  auto* gi_data = gi->mutable_data<float>();\n  for (int i = 0; i < N; i++) {\n    auto p_mdl = x_data[i];\n    auto p_emp = t_data[i];\n    auto p_avg = (p_mdl + p_emp) / 2.;\n    auto g_jsd = (logit(p_mdl) - logit(p_avg)) / 2.;\n    gi_data[i] = go_data[i] * g_jsd;\n  }\n  return true;\n}\nREGISTER_CPU_OPERATOR(BernoulliJSD, BernoulliJSDOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    BernoulliJSDGradient,\n    BernoulliJSDGradientOp<float, CPUContext>);\nOPERATOR_SCHEMA(BernoulliJSD)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nComputes the Jensen-Shannon divergence (JSD) between two Bernoulli distributions\nwhere each is parametrized by a single probability.\n)DOC\")\n    .Input(0, \"X\", \"array of probabilities for prediction\")\n    .Input(0, \"T\", \"array of probabilities for target\")\n    .Output(0, \"L\", \"array of JSD losses\");\nOPERATOR_SCHEMA(BernoulliJSDGradient).NumInputs(3).NumOutputs(1);\n\nclass GetBernoulliJSDGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"BernoulliJSDGradient\",\n        \"\",\n        vector<string>{GO(0), I(0), I(1)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(BernoulliJSD, GetBernoulliJSDGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/jsd_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_JSD_OP_H_\n#define CAFFE2_OPERATORS_JSD_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass BernoulliJSDOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(BernoulliJSDOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n};\n\ntemplate <typename T, class Context>\nclass BernoulliJSDGradientOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(BernoulliJSDGradientOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_JSD_OP_H_\n"
  },
  {
    "path": "caffe2/operators/key_split_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/key_split_ops.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(KeySplit, KeySplitOp<int64_t, CPUContext>);\nNO_GRADIENT(KeySplitOp);\nOPERATOR_SCHEMA(KeySplit).NumInputs(1).NumOutputs(1, INT_MAX);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/key_split_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <vector>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\ntemplate <typename T, class Context>\nclass KeySplitOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  KeySplitOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        categorical_limit_(\n            OperatorBase::GetSingleArgument<int>(\"categorical_limit\", 0)) {\n    CAFFE_ENFORCE_GT(categorical_limit_, 0);\n  }\n\n  bool RunOnDevice() override {\n    auto& keys = Input(0);\n    int N = keys.size();\n    const T* keys_data = keys.template data<T>();\n    std::vector<int> counts(categorical_limit_);\n    std::vector<int*> eids(categorical_limit_);\n    for (int k = 0; k < categorical_limit_; k++) {\n      counts[k] = 0;\n    }\n    for (int i = 0; i < N; i++) {\n      int k = keys_data[i];\n      CAFFE_ENFORCE_GT(categorical_limit_, k);\n      CAFFE_ENFORCE_GE(k, 0);\n      counts[k]++;\n    }\n    for (int k = 0; k < categorical_limit_; k++) {\n      auto* eid = Output(k);\n      eid->Resize(counts[k]);\n      eids[k] = eid->template mutable_data<int>();\n      counts[k] = 0;\n    }\n    for (int i = 0; i < N; i++) {\n      int k = keys_data[i];\n      eids[k][counts[k]++] = i;\n    }\n    return true;\n  }\n\n private:\n  int categorical_limit_;\n};\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/last_n_window_collector.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <memory>\n#include <string>\n#include <vector>\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\nnamespace {\n\ntemplate <class Context>\nclass LastNWindowCollectorOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  LastNWindowCollectorOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        numToCollect_(\n            OperatorBase::GetSingleArgument<int>(\"num_to_collect\", -1)) {\n    CAFFE_ENFORCE_GT(numToCollect_, 0);\n  }\n\n  bool RunOnDevice() override {\n    if (InputSize() > MUTEX) {\n      auto& mutex = OperatorBase::Input<std::unique_ptr<std::mutex>>(MUTEX);\n      std::lock_guard<std::mutex> guard(*mutex);\n      return collect();\n    } else {\n      return collect();\n    }\n  }\n\n private:\n  const int32_t numToCollect_;\n\n  bool collect() {\n    auto* output = Output(LAST_N);\n    const auto& input = Input(DATA);\n\n    CAFFE_ENFORCE_GE(input.ndim(), 1);\n    bool output_initialized = output->size() > 0 &&\n        (static_cast<std::shared_ptr<std::vector<TensorCPU>>*>(\n             output->raw_mutable_data(input.meta()))[0] != nullptr);\n    if (output_initialized) {\n      CAFFE_ENFORCE_EQ(output->ndim(), input.ndim());\n      for (size_t i = 1; i < input.ndim(); ++i) {\n        CAFFE_ENFORCE_EQ(output->dim(i), input.dim(i));\n      }\n    }\n\n    auto dims = input.dims();\n    auto num_entries = dims[0];\n\n    if (OutputSize() > NUM_VISITED) {\n      auto* num_visited_tensor = Output(NUM_VISITED);\n      CAFFE_ENFORCE_EQ(1, num_visited_tensor->size());\n      auto* num_visited = num_visited_tensor->template mutable_data<int64_t>();\n      if (!output_initialized) {\n        *num_visited = 0;\n      }\n      CAFFE_ENFORCE_GE(*num_visited, 0);\n      *num_visited += num_entries;\n    }\n\n    dims[0] = numToCollect_;\n    output->Reserve(dims, &context_);\n\n    if (num_entries == 0) {\n      if (!output_initialized) {\n        // Get both shape and meta\n        output->CopyFrom(input, &context_);\n      }\n      return true;\n    }\n\n    auto num_to_copy = std::min<int32_t>(num_entries, numToCollect_);\n    auto output_batch_size = output_initialized ? output->dim(0) : 0;\n    dims[0] = std::min<size_t>(numToCollect_, output_batch_size + num_to_copy);\n    if (output_batch_size < numToCollect_) {\n      output->Resize(dims);\n    }\n    auto* output_data =\n        static_cast<char*>(output->raw_mutable_data(input.meta()));\n\n    auto* next = Output(NEXT);\n    CAFFE_ENFORCE_EQ(0, next->ndim());\n    auto* next_data = next->template mutable_data<int32_t>();\n    if (!output_initialized) {\n      *next_data = 0;\n    }\n    CAFFE_ENFORCE_LT(*next_data, output->dim(0));\n\n    auto block_size = input.size_from_dim(1);\n    auto block_bytesize = block_size * input.itemsize();\n    const auto* input_data = static_cast<const char*>(input.raw_data());\n\n    if (num_entries > numToCollect_) {\n      // just copy the last N rows\n      context_.template CopyItems<Context, Context>(\n          input.meta(),\n          num_to_copy * block_size,\n          input_data + (num_entries - numToCollect_) * block_bytesize,\n          output_data);\n      *next_data = 0;\n      return true;\n    }\n    auto start = *next_data;\n    auto first_chunk_size =\n        std::min<size_t>(num_to_copy + start, numToCollect_) - start;\n    context_.template CopyItems<Context, Context>(\n        input.meta(),\n        first_chunk_size * block_size,\n        input_data,\n        output_data + start * block_bytesize);\n\n    context_.template CopyItems<Context, Context>(\n        input.meta(),\n        (num_to_copy - first_chunk_size) * block_size,\n        input_data + first_chunk_size * block_bytesize,\n        output_data);\n\n    *next_data = (start + num_to_copy) % numToCollect_;\n\n    return true;\n  }\n\n  INPUT_TAGS(LAST_N_IN, NEXT_IN, DATA, MUTEX, NUM_VISITED_IN);\n  OUTPUT_TAGS(LAST_N, NEXT, NUM_VISITED);\n};\n\nREGISTER_CPU_OPERATOR(LastNWindowCollector, LastNWindowCollectorOp<CPUContext>);\n\nOPERATOR_SCHEMA(LastNWindowCollector)\n    .NumInputs({3, 4, 5})\n    .NumOutputs(2, 3)\n    .EnforceInplace({{0, 0}, {1, 1}, {4, 2}})\n    .SetDoc(R\"DOC(\nCollect the last N rows from input data. The purpose is to keep track of data\naccross batches, so for example suppose the LastNWindowCollector is called\nsuccessively with the following input data\n\n  [1, 2, 3, 4]\n  [5, 6, 7]\n  [8, 9, 10, 11]\n\nAnd the number of items is set to 6, then the output after the 3rd call\nwill contain the following elements:\n\n  [6, 7, 8, 9, 10, 11]\n\nNo guarantee is made on the ordering of elements in input. So a valid value for\noutput could have been\n\n  [11, 10, 9, 8, 7, 6]\n\nAlso, this method works for any order tensor, treating the first dimension as\ninput rows and keeping the last N rows seen as input. So for instance:\n\n  [[1, 2], [2, 3], [3, 4], [4, 5]]\n  [[5, 6], [6, 7], [7, 8]]\n  [[8, 9], [9, 10], [10, 11], [11, 12]]\n\nA possible output would be\n\n  [[6, 7], [7, 8], [8, 9], [9, 10], [10, 11], [11, 12]]\n\nThis is not thread safe unless a mutex is given.\n)DOC\")\n    .Arg(\n        \"num_to_collect\",\n        \"The number of random samples to append for each positive samples\")\n    .Input(\n        0,\n        \"last-N buffer\",\n        \"The buffer for last-N record. Should be initialized to empty tensor\")\n    .Input(\n        1,\n        \"next cursor\",\n        \"The cursor pointing to the next position that should be replaced. \"\n        \"Should be initialized to 0.\")\n    .Input(2, \"DATA\", \"tensor to collect from\")\n    .Input(3, \"MUTEX\", \"(optional) mutex to use to make this thread-safe\")\n    .Input(4, \"NUM_VISITED\", \"\")\n    .Output(0, \"last-N buffer\", \"Data stored in sessions\")\n    .Output(1, \"next cursor\", \"Updated input cursor\")\n    .Output(2, \"NUM_VISITED\", \"number of records seen so far\");\nSHOULD_NOT_DO_GRADIENT(LastNWindowCollector);\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/layer_norm_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/layer_norm_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\ntemplate <typename T>\nusing EigenMatrixMapRowMajor = Eigen::Map<\n    Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>;\n\ntemplate <typename T>\nusing ConstEigenMatrixMapRowMajor = Eigen::Map<\n    const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>;\n} // namespace\n\ntemplate <>\ntemplate <>\nbool LayerNormOp<CPUContext>::DoRunWithType<float>() {\n  const auto& input = Input(0);\n  auto* output = Output(0);\n  auto* mean = Output(1);\n  auto* stdev = Output(2);\n\n  CAFFE_ENFORCE_GE(input.dims().size(), 2, \"LayerNorm requires input dim >= 2\");\n\n  const auto canonical_axis = input.canonical_axis_index(axis_);\n  const int left = input.size_to_dim(canonical_axis);\n  const int right = input.size_from_dim(canonical_axis);\n\n  output->ResizeLike(input);\n  std::vector<TIndex> stats_dims(\n      input.dims().begin(), input.dims().begin() + canonical_axis);\n  stats_dims.push_back(1);\n  mean->Resize(stats_dims);\n  stdev->Resize(stats_dims);\n\n  auto input_map = ConstEigenMatrixMapRowMajor<float>(\n      input.template data<float>(), left, right);\n  auto mean_map = EigenMatrixMapRowMajor<float>(\n      mean->template mutable_data<float>(), left, 1);\n  auto stdev_map = EigenMatrixMapRowMajor<float>(\n      stdev->template mutable_data<float>(), left, 1);\n  auto output_map = EigenMatrixMapRowMajor<float>(\n      output->template mutable_data<float>(), left, right);\n\n  auto sqr = [](float f) { return f * f; };\n  auto add_ep = [this](float f) { return f + epsilon_; };\n  auto fsqrt = [](float f) { return std::sqrt(f); };\n  // Calculate row-wise statistics\n  mean_map = input_map.rowwise().mean();\n  stdev_map =\n      (input_map.unaryExpr(sqr).rowwise().mean() - mean_map.unaryExpr(sqr))\n          .unaryExpr(add_ep)\n          .unaryExpr(fsqrt);\n  output_map = (input_map - mean_map.replicate(1, right))\n                   .cwiseQuotient(stdev_map.replicate(1, right));\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(LayerNorm, LayerNormOp<CPUContext>);\n\ntemplate <>\ntemplate <>\nbool LayerNormGradientOp<CPUContext>::DoRunWithType<float>() {\n  const auto& dout = Input(0);\n  const auto& norm_outputs = Input(1);\n  const auto& means = Input(2);\n  const auto& stdev = Input(3);\n  const auto& norm_inputs = Input(4);\n  auto* ginput = Output(0);\n\n  const auto canonical_axis = norm_inputs.canonical_axis_index(axis_);\n  const int left = norm_inputs.size_to_dim(canonical_axis);\n  const int right = norm_inputs.size_from_dim(canonical_axis);\n\n  ginput->ResizeLike(norm_inputs);\n\n  auto dout_map = ConstEigenMatrixMapRowMajor<float>(\n      dout.template data<float>(), left, right);\n  auto means_map =\n      ConstEigenMatrixMapRowMajor<float>(means.template data<float>(), left, 1);\n  auto stdev_map =\n      ConstEigenMatrixMapRowMajor<float>(stdev.template data<float>(), left, 1);\n  auto norm_inputs_map = ConstEigenMatrixMapRowMajor<float>(\n      norm_inputs.template data<float>(), left, right);\n  auto ginput_map = EigenMatrixMapRowMajor<float>(\n      ginput->template mutable_data<float>(), left, right);\n\n  // Helper functors\n  auto sqr = [](float f) { return f * f; };\n  auto recip = [](float f) { return 1.0f / f; };\n  auto neg_recip = [](float f) { return -1.0f / f; };\n\n  // Gradients - output block\n  // -1 / (stdev + epsilon)^2 * \\sum_j^D x_ij - mean * dout\n  // First part: -1 / (stdev + epsilon)^2\n  auto dstdev_end_0 = stdev_map.unaryExpr(sqr).unaryExpr(neg_recip);\n  // Second part: \\sum_j^D x_ij - mean * dout\n  auto dstdev_end_1 = (norm_inputs_map - means_map.replicate(1, right))\n                          .cwiseProduct(dout_map)\n                          .rowwise()\n                          .sum();\n  auto dstdev_end = dstdev_end_0.cwiseProduct(dstdev_end_1);\n  // \\sum_j^D -dout * 1/(std+epsilon)\n  auto dmean_end = stdev_map.unaryExpr(neg_recip)\n                       .replicate(1, right)\n                       .cwiseProduct(dout_map)\n                       .rowwise()\n                       .sum();\n  // 1.0 / (stdev + epsilon) * dout\n  auto dx_end =\n      stdev_map.unaryExpr(recip).replicate(1, right).cwiseProduct(dout_map);\n\n  // Gradients - standard deviation block\n  // -1.0*(mean / stdev) * dstdev_end\n  auto dmean_stdev = stdev_map.unaryExpr(neg_recip)\n                         .cwiseProduct(means_map)\n                         .replicate(1, right)\n                         .cwiseProduct(dstdev_end);\n  // (mean / (D*stdev)) * dstdev\n  auto dx_stdev = (1.0f / right) *\n      norm_inputs_map.cwiseQuotient(stdev_map.replicate(1, right))\n          .cwiseProduct(dstdev_end.replicate(1, right));\n\n  // Gradients - mean block\n  auto dmean = dmean_end + dmean_stdev;\n  auto dx_mean = (1.0f / right) * dmean.replicate(1, right);\n\n  ginput_map = dx_end + dx_stdev + dx_mean;\n\n  return true;\n}\n\nOPERATOR_SCHEMA(LayerNormGradient).NumInputs(5).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(LayerNormGradient, LayerNormGradientOp<CPUContext>);\n\nnamespace {\n\nclass GetLayerNormGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"LayerNormGradient\",\n        \"\",\n        vector<string>{GO(0), O(0), O(1), O(2), I(0)},\n        vector<string>{GI(0)});\n  }\n};\n\n}  // namespace\n\nREGISTER_GRADIENT(LayerNorm, GetLayerNormGradient);\n\nOPERATOR_SCHEMA(LayerNorm)\n    .NumInputs(1)\n    .NumOutputs(3)\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      vector<TensorShape> out(3);\n      auto input_dims_long = GetDimsVector(in[0]);\n      std::vector<int> input_dims(\n          input_dims_long.begin(), input_dims_long.end());\n      out[0] = CreateTensorShape(input_dims, TensorProto::FLOAT);\n\n      ArgumentHelper helper(def);\n\n      auto axis = helper.GetSingleArgument<int32_t>(\"axis\", 1);\n      const auto canonical_axis =\n          canonical_axis_index_(axis, in[0].dims().size());\n      std::vector<int> stat_dims(\n          input_dims.begin(), input_dims.begin() + canonical_axis);\n      stat_dims.push_back(1);\n      out[1] = CreateTensorShape(stat_dims, TensorProto::FLOAT);\n      out[2] = CreateTensorShape(stat_dims, TensorProto::FLOAT);\n      return out;\n    })\n    .SetDoc(R\"DOC(\nComputes layer normalization as described in https://arxiv.org/pdf/1607.06450.pdf.\nGiven an input vector x \\in [a_0, a_1, ...,a_{k-1}, a_k, ..., a_{n-1}],\nthis op treats dimensions a_k through a_{n-1} as feature vectors. For each\nfeature vector, the op contains the mean and standard deviation. Then,\nit returns the normalized values (with respect to the feature vector).\n\nNote that this op does not contain the scale an bias terms described in the\npaper. Simply follow this op with an FC op to add those. Concretely, this op\nimplements:\n\nh = \\frac{1}{\\sigma}(a - \\mu)\nwhere \\mu = \\frac{1}{H}\\sum_{i=1}^{H} a_i\nand \\sigma = \\sqrt{\\frac{1}{H}\\sum_{i=1}^{H}(a_i - \\mu)^2}\nwhere H is the number of hidden units (i.e. product of dimensions from 'axis'\nto the end.)\n)DOC\")\n    .Arg(\n        \"axis\",\n        \"(int) default to 1; Describes axis of the inputs. Defaults to one \"\n        \"because the 0th axis most likely describes the batch size\")\n    .Arg(\n        \"epsilon\",\n        \"(float) default to 0.001. Small value to be added to the stdev when\"\n        \" dividing out by that value. This prevents division by zero.\")\n    .Input(\n        0,\n        \"input\",\n        \"Input tensor which layer normalization will be applied to\")\n    .Output(0, \"output\", \"Normalized values\")\n    .Output(1, \"mean\", \"Mean values for each feature vector\")\n    .Output(2, \"stddev\", \"Standard deviations for each feature vector\");\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/layer_norm_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/layer_norm_op.h\"\n\n#include <cub/cub.cuh>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nnamespace {\ntemplate <typename T>\nstruct SqrTransform {\n  inline __host__ __device__ T operator()(const T v) const {\n    return v * v;\n  }\n};\n\n// X = X - Y^2\n__global__ void sqrtXMinusYSquaredKernel(\n    const int N,\n    float* x,\n    const float* y,\n    const float epsilon) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    x[i] = sqrtf(x[i] - y[i] * y[i] + epsilon);\n  }\n}\n\n// out[i, j] = (X[i, j] - mu[i]) / sigma[i]\n__global__ void normalizeKernel(\n    const int row_dim,\n    const int N,\n    const float* x,\n    const float* mu,\n    const float* sigma,\n    float* out) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    out[i] = (x[i] - mu[i / row_dim]) / (sigma[i / row_dim]);\n  }\n}\n\ntemplate <typename InputIterator_t>\nvoid allocScratchAndReduce(\n    InputIterator_t input,\n    float* output,\n    int num_segments,\n    int* seg_indices,\n    Tensor<CUDAContext>* scratch,\n    cudaStream_t stream) {\n  size_t temp_storage_bytes;\n  cub::DeviceSegmentedReduce::Sum(\n      nullptr, // To retrieve required temporary storage size\n      temp_storage_bytes, // size_t &temp_storage_bytes\n      input, // InputIteratorT d_i\n      output, // OutputIteratorT d_out\n      num_segments, // int num_segments\n      seg_indices, // int *d_begin_offsets\n      seg_indices + 1, // int *d_end_offsets\n      stream // cudaStream_t stream=0\n      );\n  size_t temp_storage_floats = temp_storage_bytes / sizeof(float) +\n      (temp_storage_bytes % sizeof(float) ? 1 : 0);\n  scratch->Resize(vector<size_t>{temp_storage_floats});\n\n  cub::DeviceSegmentedReduce::Sum(\n      scratch->mutable_data<float>(), // To retrieve required temporary storage\n                                      // size\n      temp_storage_bytes, // size_t &temp_storage_bytes\n      input, // InputIteratorT d_i\n      output, // OutputIteratorT d_out\n      num_segments, // int num_segments\n      seg_indices, // int *d_begin_offsets\n      seg_indices + 1, // int *d_end_offsets\n      stream // cudaStream_t stream=0\n      );\n}\n\n} //  namespace\n\ntemplate <>\ntemplate <>\nbool LayerNormOp<CUDAContext>::DoRunWithType<float>() {\n  const auto& input = Input(0);\n  auto* output = Output(0);\n  auto* mean = Output(1);\n  auto* stdev = Output(2);\n\n  CAFFE_ENFORCE_GE(input.dims().size(), 2, \"LayerNorm requires input dim >= 2\");\n\n  const auto canonical_axis = input.canonical_axis_index(axis_);\n  const int left = input.size_to_dim(canonical_axis);\n  const int right = input.size_from_dim(canonical_axis);\n\n  output->ResizeLike(input);\n  std::vector<TIndex> stats_dims(\n      input.dims().begin(), input.dims().begin() + canonical_axis);\n  stats_dims.push_back(1);\n  mean->Resize(stats_dims);\n  stdev->Resize(stats_dims);\n\n  std::vector<int> segs(left + 1);\n  std::iota(segs.begin(), segs.end(), 0);\n  std::transform(\n      segs.begin(),\n      segs.end(),\n      segs.begin(),\n      std::bind1st(std::multiplies<int>(), right));\n\n  seg_indices_.Resize(vector<size_t>{segs.size()});\n  context_.CopyBytes<CPUContext, CUDAContext>(\n      sizeof(int) * segs.size(),\n      static_cast<void*>(segs.data()),\n      static_cast<void*>(seg_indices_.mutable_data<int>()));\n\n  if (right == 1) {\n    mean->CopyFrom(input);\n    mean->Resize(stats_dims);\n    math::Set<float, CUDAContext>(\n        left, std::sqrt(epsilon_), stdev->mutable_data<float>(), &context_);\n  } else {\n    // Calculate row-wise means\n    // First stage: sum up feature vectors\n    allocScratchAndReduce(\n        input.data<float>(),\n        mean->mutable_data<float>(),\n        left,\n        seg_indices_.mutable_data<int>(),\n        &scratch_,\n        context_.cuda_stream());\n\n    // Second stage: Normalize by feature vector dim\n    math::Scale<float, CUDAContext>(\n        left,\n        1.0f / right,\n        mean->mutable_data<float>(),\n        mean->mutable_data<float>(),\n        &context_);\n\n    // Calculate row-wise standard deviation\n\n    // First stage: sum up row-wise squared values\n    SqrTransform<float> transform;\n    cub::TransformInputIterator<float, SqrTransform<float>, const float*> it(\n        input.data<float>(), transform);\n    allocScratchAndReduce(\n        it,\n        stdev->mutable_data<float>(),\n        left,\n        seg_indices_.mutable_data<int>(),\n        &scratch_,\n        context_.cuda_stream());\n\n    // Second stage: Normalize by feature vector dim\n    math::Scale<float, CUDAContext>(\n        left,\n        1.0f / right,\n        stdev->mutable_data<float>(),\n        stdev->mutable_data<float>(),\n        &context_);\n\n    // stddev = sqrt(E(x^2) - E(x)^2 + epsilon)\n    sqrtXMinusYSquaredKernel<<<\n        CAFFE_GET_BLOCKS(left),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        left,\n        stdev->mutable_data<float>(),\n        mean->mutable_data<float>(),\n        epsilon_);\n  }\n\n  // out[i, j] = (in[i,j] - mu[i]) / (sigma[i])\n  normalizeKernel<<<\n      CAFFE_GET_BLOCKS(left),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      right,\n      left * right,\n      input.data<float>(),\n      mean->data<float>(),\n      stdev->data<float>(),\n      output->mutable_data<float>());\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(LayerNorm, LayerNormOp<CUDAContext>);\n\nnamespace {\n// x : [N, D]\n// y : [N, 1]\n// z : [N, D]\n// (x - broadcast(y)) * z\n__global__ void zTimesXminusYbroadcast(\n    int N,\n    int D,\n    const float* x,\n    const float* y,\n    const float* z,\n    float* out) {\n  CUDA_1D_KERNEL_LOOP(i, N * D) {\n    out[i] = (x[i] - y[i / D]) * z[i];\n  }\n}\n\n__global__ void normalizeByNegStdev(\n    int N,\n    bool var,\n    const float* x,\n    const float* stdev,\n    float* out) {\n  if (var) {\n    CUDA_1D_KERNEL_LOOP(i, N) {\n      out[i] = (-1.0f * x[i]) / (stdev[i] * stdev[i]);\n    }\n  } else {\n    CUDA_1D_KERNEL_LOOP(i, N) {\n      out[i] = (-1.0f * x[i]) / (stdev[i]);\n    }\n  }\n}\n\n__global__ void gradientMegaKernel(\n    int N,\n    int D,\n    const float* stdev,\n    const float* X,\n    const float* dstdev,\n    const float* dmean,\n    const float* dout,\n    float* out) {\n  CUDA_1D_KERNEL_LOOP(i, N * D) {\n    out[i] = 1.0f / stdev[i / D] * dout[i] +\n        X[i] / (D * stdev[i / D]) * dstdev[i / D] + 1.0f / D * dmean[i / D];\n  }\n}\n\n#define PRINT(X, N, D) printTensor<<<1, 1, 0, context_.cuda_stream()>>>(X, N, D)\n\n} // namespace\n\ntemplate <>\ntemplate <>\nbool LayerNormGradientOp<CUDAContext>::DoRunWithType<float>() {\n  const auto& dout = Input(0);\n  const auto& norm_outputs = Input(1);\n  const auto& means = Input(2);\n  const auto& stdev = Input(3);\n  const auto& norm_inputs = Input(4);\n  auto* ginput = Output(0);\n\n  const auto canonical_axis = norm_inputs.canonical_axis_index(axis_);\n  const unsigned long left = norm_inputs.size_to_dim(canonical_axis);\n  const unsigned long right = norm_inputs.size_from_dim(canonical_axis);\n\n  ginput->ResizeLike(norm_inputs);\n  std::vector<TIndex> stats_dims(\n      norm_inputs.dims().begin(), norm_inputs.dims().begin() + canonical_axis);\n  stats_dims.push_back(1);\n  dmean_.Resize(stats_dims);\n  dstdev_.Resize(stats_dims);\n  gscratch_.Resize(std::vector<size_t>{left, right});\n\n  std::vector<int> segs(left + 1);\n  std::iota(segs.begin(), segs.end(), 0);\n  std::transform(\n      segs.begin(),\n      segs.end(),\n      segs.begin(),\n      std::bind1st(std::multiplies<int>(), right));\n\n  seg_indices_.Resize(vector<size_t>{segs.size()});\n  context_.CopyBytes<CPUContext, CUDAContext>(\n      sizeof(int) * segs.size(),\n      static_cast<void*>(segs.data()),\n      static_cast<void*>(seg_indices_.mutable_data<int>()));\n\n  // Calculate gradient of the standard deviation\n  // temp1 = (x - mean) * dout\n  zTimesXminusYbroadcast<<<\n      CAFFE_GET_BLOCKS(left * right),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      left,\n      right,\n      norm_inputs.data<float>(),\n      means.data<float>(),\n      dout.data<float>(),\n      gscratch_.mutable_data<float>());\n\n  dstdev_.Resize(vector<size_t>{left, 1});\n  // dstdev = reduce(temp1)\n  allocScratchAndReduce(\n      gscratch_.data<float>(),\n      dstdev_.mutable_data<float>(),\n      left,\n      seg_indices_.mutable_data<int>(),\n      &scratch_,\n      context_.cuda_stream());\n  // dstdev = -dstdev / sqrt(stdev)\n  normalizeByNegStdev<<<\n      CAFFE_GET_BLOCKS(left),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      left,\n      true,\n      dstdev_.data<float>(),\n      stdev.data<float>(),\n      dstdev_.mutable_data<float>());\n\n  // Calculate gradient of the mean\n  // dmean = reduce(dout)\n  allocScratchAndReduce(\n      dout.data<float>(),\n      dmean_.mutable_data<float>(),\n      left,\n      seg_indices_.mutable_data<int>(),\n      &scratch_,\n      context_.cuda_stream());\n  // mean * stdev\n  math::Mul(\n      left,\n      means.data<float>(),\n      dstdev_.data<float>(),\n      gscratch_.mutable_data<float>(),\n      &context_);\n  // [\\sum dout] + mean * stdev\n  math::Add(\n      left,\n      dmean_.data<float>(),\n      gscratch_.data<float>(),\n      dmean_.mutable_data<float>(),\n      &context_);\n  // -1 / std * [[\\sum dout] + mean * stdev]\n  normalizeByNegStdev<<<\n      CAFFE_GET_BLOCKS(left),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      left,\n      false,\n      dmean_.data<float>(),\n      stdev.data<float>(),\n      dmean_.mutable_data<float>());\n\n  // Calculate gradient of input\n  gradientMegaKernel<<<\n      CAFFE_GET_BLOCKS(left),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      left,\n      right,\n      stdev.data<float>(),\n      norm_inputs.data<float>(),\n      dstdev_.data<float>(),\n      dmean_.data<float>(),\n      dout.data<float>(),\n      ginput->mutable_data<float>());\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(LayerNormGradient, LayerNormGradientOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/layer_norm_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_LAYER_NORM_OP_H\n#define CAFFE2_OPERATORS_LAYER_NORM_OP_H\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass LayerNormOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  LayerNormOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        axis_(OperatorBase::GetSingleArgument<int>(\"axis\", 1)),\n        epsilon_(OperatorBase::GetSingleArgument<float>(\"epsilon\", 1e-5f)) {}\n  ~LayerNormOp() {}\n\n  template <typename T>\n  bool DoRunWithType();\n\n  bool RunOnDevice() override {\n    return DoRunWithType<float>();\n  }\n\n protected:\n  int axis_;\n  float epsilon_;\n\n  Tensor<Context> scratch_;\n  Tensor<Context> seg_indices_;\n};\n\ntemplate <class Context>\nclass LayerNormGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  LayerNormGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        axis_(OperatorBase::GetSingleArgument<int>(\"axis\", 1)),\n        epsilon_(OperatorBase::GetSingleArgument<float>(\"epsilon\", 0.001f)) {}\n  ~LayerNormGradientOp() {}\n\n  template <typename T>\n  bool DoRunWithType();\n\n  bool RunOnDevice() override {\n    return DoRunWithType<float>();\n  }\n\n protected:\n  int axis_;\n  float epsilon_;\n\n  Tensor<Context> scratch_;\n  Tensor<Context> gscratch_;\n  Tensor<Context> seg_indices_;\n  Tensor<Context> dstdev_;\n  Tensor<Context> dmean_;\n};\n\n} // namespace caffe2\n\n#endif /* CAFFE2_OPERATORS_LAYER_NORM_OP_H */\n"
  },
  {
    "path": "caffe2/operators/leaky_relu_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/leaky_relu_op.h\"\n\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool LeakyReluOp<float, CPUContext>::RunOnDevice() {\n  const auto& X = Input(0);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n  ConstEigenVectorMap<float> Xvec(X.template data<float>(), X.size());\n  EigenVectorMap<float> Yvec(Y->template mutable_data<float>(), Y->size());\n  Yvec = Xvec.cwiseMax(0.f) + Xvec.cwiseMin(0.f) * alpha_;\n  return true;\n}\n\ntemplate <>\nbool LeakyReluGradientOp<float, CPUContext>::RunOnDevice() {\n  const auto& Y = Input(0);\n  const auto& dY = Input(1);\n  auto* dX = Output(0);\n  dX->ResizeLike(Y);\n  CAFFE_ENFORCE_EQ(Y.size(), dY.size());\n  ConstEigenVectorMap<float> Yvec(Y.template data<float>(), Y.size());\n  ConstEigenVectorMap<float> dYvec(dY.template data<float>(), dY.size());\n  EigenVectorMap<float> dXvec(dX->template mutable_data<float>(), dX->size());\n  Eigen::VectorXf gtZero = (Yvec.array() >= 0.0f).cast<float>();\n  dXvec = dYvec.array() * gtZero.array() -\n      dYvec.array() * (gtZero.array() - 1.0f) * alpha_;\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(LeakyRelu, LeakyReluOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    LeakyReluGradient,\n    LeakyReluGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LeakyRelu)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .Arg(\"alpha\", \"Coefficient of leakage, default value is 0.01\")\n    .AllowInplace({{0, 0}})\n    .CostInferenceFunction(PointwiseCostInference<2>)\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nLeakyRelu takes input data (Tensor<T>) and an argument alpha, and produces one\noutput data (Tensor<T>) where the function `f(x) = alpha * x for x < 0`,\n`f(x) = x for x >= 0`, is applied to the data tensor elementwise.\n)DOC\")\n    .Input(0, \"X\", \"1D input tensor\")\n    .Output(0, \"Y\", \"1D input tensor\");\nOPERATOR_SCHEMA(LeakyReluGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{1, 0}})\n    .Arg(\"alpha\", \"Coefficient of leakage\");\n\nclass GetLeakyReluGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"LeakyReluGradient\",\n        \"\",\n        vector<string>{O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(LeakyRelu, GetLeakyReluGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/leaky_relu_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/leaky_relu_op.h\"\n\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\nnamespace {\ntemplate <typename T>\n__global__ void LeakyReluKernel(const int N, const T alpha, const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = X[i] >= 0 ? X[i] : X[i] * alpha;\n  }\n}\n\ntemplate <typename T>\n__global__ void LeakyReluGradientKernel(\n    const int N,\n    const T alpha,\n    const T* Y,\n    const T* dY,\n    T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dX[i] = Y[i] >= 0 ? dY[i] : dY[i] * alpha;\n  }\n}\n} // namespace\n\ntemplate <>\nbool LeakyReluOp<float, CUDAContext>::RunOnDevice() {\n  const auto& X = Input(0);\n  CAFFE_ENFORCE_GT(X.size(), 0);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n  LeakyReluKernel<<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(), alpha_, X.data<float>(), Y->mutable_data<float>());\n  return true;\n}\n\ntemplate <>\nbool LeakyReluGradientOp<float, CUDAContext>::RunOnDevice() {\n  const auto& Y = Input(0);\n  const auto& dY = Input(1);\n  auto* dX = Output(0);\n  dX->ResizeLike(Y);\n  CAFFE_ENFORCE_EQ(Y.size(), dY.size());\n  LeakyReluGradientKernel<<<\n      CAFFE_GET_BLOCKS(Y.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      Y.size(),\n      alpha_,\n      Y.data<float>(),\n      dY.data<float>(),\n      dX->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(LeakyRelu, LeakyReluOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    LeakyReluGradient,\n    LeakyReluGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/leaky_relu_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass LeakyReluOp : public Operator<Context> {\n public:\n  LeakyReluOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws), alpha_(0.01) {\n    if (HasArgument(\"alpha\")) {\n      alpha_ =\n          static_cast<T>(OperatorBase::GetSingleArgument<float>(\"alpha\", 0.01));\n    }\n  }\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  T alpha_;\n};\n\ntemplate <typename T, class Context>\nclass LeakyReluGradientOp final : public Operator<Context> {\n public:\n  LeakyReluGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws), alpha_(0.01) {\n    if (HasArgument(\"alpha\")) {\n      alpha_ =\n          static_cast<T>(OperatorBase::GetSingleArgument<float>(\"alpha\", 0.01));\n    }\n  }\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  T alpha_;\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/lengths_reducer_fused_8bit_rowwise_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/lengths_reducer_fused_8bit_rowwise_ops.h\"\n#include \"caffe2/core/registry.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(\n    SparseLengthsSumFused8BitRowwise,\n    SparseLengthsFused8BitRowwiseOp<CPUContext>);\nOPERATOR_SCHEMA(SparseLengthsSumFused8BitRowwise)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nPerforms the same operation as SparseLengthsSum, but operating on\n8-bit rowwise quantized matrices with fused storage (where each row\nstores quantized values, and then 4-byte scale and 4-byte bias).\n)DOC\")\n    .Input(\n        0,\n        \"DATA\",\n        \"uint8 tensor obtained with \"\n        \"operator FloatToFused8BitRowwiseQuantized\")\n    .Input(\n        1,\n        \"INDICES\",\n        \"Integer vector containing indices of the first \"\n        \"dimension of DATA for the slices that are being aggregated\")\n    .Input(\n        2,\n        \"LENGTHS\",\n        \"Vector with the same sum of elements as the first dimension of DATA\")\n    .Output(0, \"output\", \"output\");\nNO_GRADIENT(SparseLengthsSumFused8BitRowwise);\n\nREGISTER_CPU_OPERATOR(\n    SparseLengthsWeightedSumFused8BitRowwise,\n    SparseLengthsFused8BitRowwiseOp<CPUContext, /*with_weights=*/true>);\nOPERATOR_SCHEMA(SparseLengthsWeightedSumFused8BitRowwise)\n    .NumInputs(4)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nPerforms the same operation as SparseLengthsWeightedSum,\nbut operating on 8-bit rowwise quantized matrices with fused storage\n(where each row stores quantized values, and then 4-byte scale and 4-byte bias).\n)DOC\")\n    .Input(\n        0,\n        \"DATA\",\n        \"uint8 tensor obtained with \"\n        \"operator FloatToFused8BitRowwiseQuantized\")\n    .Input(\n        1,\n        \"INDICES\",\n        \"Integer vector containing indices of the first \"\n        \"dimension of DATA for the slices that are being aggregated\")\n    .Input(\n        2,\n        \"LENGTHS\",\n        \"Vector with the same sum of elements as the first dimension of DATA\")\n    .Input(\n        3,\n        \"WEIGHTS\",\n        \"Vector of weights to scale rows of DATA with before reduction\")\n    .Output(0, \"output\", \"output\");\n\nNO_GRADIENT(SparseLengthsWeightedSumFused8BitRowwise);\n\nREGISTER_CPU_OPERATOR(\n    SparseLengthsMeanFused8BitRowwise,\n    SparseLengthsFused8BitRowwiseOp<\n        CPUContext,\n        /*with_weights=*/false,\n        /*is_mean=*/true>);\nOPERATOR_SCHEMA(SparseLengthsMeanFused8BitRowwise)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nPerforms the same operation as SparseLengthsMean, but\noperating on 8-bit rowwise quantized matrices with fused storage\n(where each row stores quantized values, and then 4-byte scale and 4-byte bias).\n)DOC\")\n    .Input(\n        0,\n        \"DATA\",\n        \"uint8 tensor obtained with \"\n        \"operator FloatToFused8BitRowwiseQuantized\")\n    .Input(\n        1,\n        \"INDICES\",\n        \"Integer vector containing indices of the first \"\n        \"dimension of DATA for the slices that are being aggregated\")\n    .Input(\n        2,\n        \"LENGTHS\",\n        \"Vector with the same sum of elements as the first dimension of DATA\")\n    .Output(0, \"output\", \"output\");\nNO_GRADIENT(SparseLengthsMeanFused8BitRowwise);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/lengths_reducer_fused_8bit_rowwise_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_LENGTHS_REDUCER_FUSED_8BIT_ROWWISE_OPS_H_\n#define CAFFE2_OPERATORS_LENGTHS_REDUCER_FUSED_8BIT_ROWWISE_OPS_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/fused_rowwise_8bit_conversion_ops.h\"\n#include \"caffe2/operators/reducer_functors.h\"\n#include \"caffe2/perfkernels/fused_8bit_rowwise_embedding_lookup.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context, bool with_weights = 0, bool is_mean = 0>\nclass SparseLengthsFused8BitRowwiseOp : public Operator<Context> {\n public:\n  static_assert(\n      !(with_weights && is_mean),\n      \"Cannot have with_weights and is_mean a the same time\");\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(SparseLengthsFused8BitRowwiseOp)\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename IndexType>\n  bool DoRunWithType() {\n    const auto& data = Input(DATA);\n    const auto& indices = Input(INDICES);\n    const auto& lengths = Input(LENGTHS);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_EQ(indices.ndim(), 1, \"INDICES must be a vector\");\n    CAFFE_ENFORCE_EQ(lengths.ndim(), 1, \"LENGTHS must be a vector\");\n\n    const float* weights = nullptr;\n    if (with_weights) {\n      const auto& weights_input = Input(WEIGHTS);\n      CAFFE_ENFORCE_EQ(weights_input.ndim(), 1, \"WEIGHTS must be a vector\");\n      CAFFE_ENFORCE_EQ(\n          weights_input.size(),\n          indices.size(),\n          \"WEIGHTS should have the same length as INDICES.\");\n      weights = weights_input.template data<float>();\n    }\n\n    CAFFE_ENFORCE_GT(data.dim(1), 8, \"DATA must have more than 8 columns\");\n    // Subtract 8 from the #columns of data for the 4 bytes for scale and 4\n    // bytes for bias that we use in the fused representation (per row).\n    const std::vector<TIndex> shape = {lengths.dim(0), data.dim(1) - 8};\n    output->Resize(shape);\n\n    Fused8BitRowwiseEmbeddingLookup(\n        /*block_size=*/output->dim(1),\n        /*output_size=*/output->dim(0),\n        /*index_size=*/indices.size(),\n        /*data_size=*/data.dim(0),\n        /*input=*/data.template data<uint8_t>(),\n        /*indices=*/indices.template data<IndexType>(),\n        /*lengths=*/lengths.template data<int>(),\n        /*weights=*/weights,\n        /*normalize_by_lengths=*/is_mean,\n        /*out=*/output->template mutable_data<float>());\n\n    return true;\n  }\n\n private:\n  enum {\n    DATA = 0,\n    WEIGHTS = 1,\n    INDICES = 1 + with_weights,\n    LENGTHS = 2 + with_weights,\n  };\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_LENGTHS_REDUCER_FUSED_8BIT_ROWWISE_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/lengths_reducer_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/lengths_reducer_ops.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n// Use _STR option because the schema is declared using _STR version too in\n// generic fashion. Otherwise it'd break schema declaration check.\n// TODO(dzhulgakov): remove _STR when all lengths ops are off generic version.\n\nREGISTER_CPU_OPERATOR_STR(\n    \"SparseLengthsSum\",\n    CPUSparseLengthsReductionOp<float, TensorTypes<float, float16>, 0, 0>);\nREGISTER_CPU_OPERATOR_STR(\n    \"SparseLengthsWeightedSum\",\n    CPUSparseLengthsReductionOp<float, TensorTypes<float, float16>, 1, 0>);\nREGISTER_CPU_OPERATOR_STR(\n    \"SparseLengthsMean\",\n    CPUSparseLengthsReductionOp<float, TensorTypes<float, float16>, 0, 1>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/lengths_reducer_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/perfkernels/embedding_lookup.h\"\n\nnamespace caffe2 {\n\n// A templated class that implements SparseLengths[Sum,WeightedSum,Mean].\ntemplate <\n    typename T, // output type\n    class InputTypes, // supported input types, such as TensorTypes<float>\n    bool USE_WEIGHT = 0, // Whether it is SparseLengthsWeightedSum\n    bool USE_MEAN = 0 // Whether this is SparseLengthsMean\n    >\nclass CPUSparseLengthsReductionOp : public Operator<CPUContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CPUContext);\n  CPUSparseLengthsReductionOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {\n    static_assert(\n        !(USE_WEIGHT & USE_MEAN), \"Cannot both specify weight and mean.\");\n  }\n\n  ~CPUSparseLengthsReductionOp() {}\n\n  // Currently, we support float and float16 inputs for input data type, and\n  // int32_t and int64_t for the index type.\n\n  bool RunOnDevice() override {\n    return DispatchHelper<InputTypes>::call(this, Input(DATA));\n  }\n\n  template <typename InputType>\n  bool DoRunWithType() {\n    return DispatchHelper<TensorTypes2<int32_t, int64_t>, InputType>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename InputType, typename IndexType>\n  bool DoRunWithType2() {\n    auto& dataInput = Input(DATA);\n    auto& indicesInput = Input(INDICES);\n    auto& lengthsInput = Input(LENGTHS);\n\n    CAFFE_ENFORCE_EQ(1, indicesInput.ndim(), \"INDICES must be a vector\");\n    CAFFE_ENFORCE_EQ(1, lengthsInput.ndim(), \"LENGTHS must be a vector\");\n    const TIndex N = dataInput.dim(0);\n    const int D = dataInput.size_from_dim(1);\n    const TIndex M = lengthsInput.dim(0);\n    const TIndex indices_size = indicesInput.size();\n\n    auto* output = Output(0);\n    auto shape = dataInput.dims();\n    shape[0] = M;\n    output->Resize(shape);\n    T* out_data = output->template mutable_data<T>();\n\n    const InputType* in_data = dataInput.template data<InputType>();\n    const IndexType* indices = indicesInput.template data<IndexType>();\n    const int* lengths = lengthsInput.template data<int>();\n    const T* in_weight = nullptr;\n\n    if (USE_WEIGHT) { // static if\n      auto& weightInput = Input(WEIGHT);\n      CAFFE_ENFORCE_EQ(1, weightInput.ndim(), \"WEIGHT must be a vector\");\n      CAFFE_ENFORCE_EQ(\n          weightInput.size(),\n          indices_size,\n          \"Weight should have the same length as indices.\");\n      in_weight = weightInput.template data<T>();\n    }\n\n    // delegate work to perfkernel that branches based on architecture\n    EmbeddingLookup(\n        D,\n        M,\n        indices_size,\n        N,\n        in_data,\n        indices,\n        lengths,\n        in_weight,\n        nullptr, // scale_bias field is only used in SparseLengths8BitsRowwiseOp\n        USE_MEAN,\n        out_data);\n    return true;\n  }\n\n private:\n  enum {\n    DATA = 0, // Data input.\n    WEIGHT = 1, // Weight input used in SparseLengthsWeightedSum\n    INDICES = 1 + USE_WEIGHT, // 1 in SparseLengths[Sum,Mean] and\n                              // 2 in SparseLengthsWeightedSum\n    LENGTHS = 2 + USE_WEIGHT, // 2 in SparseLengths[Sum, Mean],\n                              // 3 in SparseLengthsWeightedSum\n  };\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/lengths_reducer_rowwise_8bit_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/lengths_reducer_rowwise_8bit_ops.h\"\n#include \"caffe2/core/registry.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(\n    Rowwise8BitQuantizedToFloat,\n    Rowwise8BitQuantizedToFloatOp<CPUContext>);\nREGISTER_CPU_OPERATOR(\n    FloatToRowwiseQuantized8Bits,\n    FloatToRowwiseQuantized8BitsOp<CPUContext>);\n\nREGISTER_CPU_OPERATOR(\n    SparseLengthsSum8BitsRowwise,\n    SparseLengths8BitsRowwiseOp<CPUContext>);\n\nREGISTER_CPU_OPERATOR(\n    SparseLengthsWeightedSum8BitsRowwise,\n    SparseLengths8BitsRowwiseOp<CPUContext, 1>);\n\nREGISTER_CPU_OPERATOR(\n    SparseLengthsMean8BitsRowwise,\n    SparseLengths8BitsRowwiseOp<CPUContext, 0, 1>);\n\nREGISTER_CPU_OPERATOR(\n    SparseLengthsWeightedMean8BitsRowwise,\n    SparseLengths8BitsRowwiseOp<CPUContext, 1, 1>);\n\nOPERATOR_SCHEMA(SparseLengthsSum8BitsRowwise)\n    .NumInputs(4)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nVariation of SparseLengthsSum operator, where DATA is\nstored using 8bits. DATA was quantized with 8Bit row-wise\nquantization (see doc to FloatToRowwiseQuantized8Bits operator). To\nrestore DATA from 8Bit, we use additional input that stores scales\nand biases.\n)DOC\")\n    .Input(\n        0,\n        \"DATA\",\n        \"uint8 tensor obtained with \"\n        \"operator FloatToRowwiseQuantized8Bits\")\n    .Input(\n        1,\n        \"INDICES\",\n        \"Integer vector containing indices of the first \"\n        \"dimension of DATA for the slices that are being aggregated\")\n    .Input(\n        2,\n        \"LENGTHS\",\n        \"Vector with the same sum of elements as the first dimension of DATA\")\n    .Input(\n        3,\n        \"scale_bias\",\n        \"Matrix of floats, each row r_i of which stores a pair \"\n        \"s_i, b_i -- scale and bias for i-th row\")\n\n    .Output(0, \"output\", \"output\");\n\nOPERATOR_SCHEMA(SparseLengthsWeightedSum8BitsRowwise)\n    .NumInputs(5)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nVariation of SparseLengthsWeightedSum operator, where\nDATA is stored using 8bits. DATA was quantized with 8Bit row-wise\nquantization (see doc to FloatToRowwiseQuantized8Bits operator). To\nrestore DATA from 8Bit, we use additional input that stores scales\nand biases.\n)DOC\")\n    .Input(\n        0,\n        \"DATA\",\n        \"uint8 tensor obtained with \"\n        \"operator FloatToRowwiseQuantized8Bits\")\n    .Input(\n        1,\n        \"SCALARS\",\n        \"Scalar multipliers for the input slices. Must \"\n        \"be a vector with the length matching the length of INDICES\")\n    .Input(\n        2,\n        \"INDICES\",\n        \"Integer vector containing indices of the first \"\n        \"dimension of DATA for the slices that are being aggregated\")\n    .Input(\n        3,\n        \"LENGTHS\",\n        \"Vector with the same sum of elements as the first dimension of DATA\")\n    .Input(\n        4,\n        \"scale_bias\",\n        \"Matrix of floats, each row r_i of which stores a pair \"\n        \"s_i, b_i -- scale and bias for i-th row\")\n    .Output(0, \"output\", \"output\");\n\nOPERATOR_SCHEMA(SparseLengthsMean8BitsRowwise)\n    .NumInputs(4)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nVariation of SparseLengthsMean operator, where DATA is\nstored using 8bits. DATA was quantized with 8Bit row-wise\nquantization (see doc to FloatToRowwiseQuantized8Bits operator). To\nrestore DATA from 8Bit, we use additional input that stores scales\nand biases.\n)DOC\")\n    .Input(\n        0,\n        \"DATA\",\n        \"uint8 tensor obtained with \"\n        \"operator FloatToRowwiseQuantized8Bits\")\n    .Input(\n        1,\n        \"INDICES\",\n        \"Integer vector containing indices of the first \"\n        \"dimension of DATA for the slices that are being aggregated\")\n    .Input(\n        2,\n        \"LENGTHS\",\n        \"Vector with the same sum of elements as the first dimension of DATA\")\n    .Input(\n        3,\n        \"scale_bias\",\n        \"Matrix of floats, each row r_i of which stores a pair \"\n        \"s_i, b_i -- scale and bias for i-th row\")\n\n    .Output(0, \"output\", \"output\");\n\nOPERATOR_SCHEMA(SparseLengthsWeightedMean8BitsRowwise)\n    .NumInputs(5)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nVariation of SparseLengthsWeightedMean operator, where\nDATA is stored using 8bits. DATA was quantized with 8Bit row-wise\nquantization (see doc to FloatToRowwiseQuantized8Bits operator). To\nrestore DATA from 8Bit, we use additional input that stores scales\nand biases.\n)DOC\")\n    .Input(\n        0,\n        \"DATA\",\n        \"uint8 tensor obtained with \"\n        \"operator FloatToRowwiseQuantized8Bits\")\n    .Input(\n        1,\n        \"SCALARS\",\n        \"Scalar multipliers for the input slices. Must \"\n        \"be a vector with the length matching the length of INDICES\")\n    .Input(\n        2,\n        \"INDICES\",\n        \"Integer vector containing indices of the first \"\n        \"dimension of DATA for the slices that are being aggregated\")\n    .Input(\n        3,\n        \"LENGTHS\",\n        \"Vector with the same sum of elements as the first dimension of DATA\")\n    .Input(\n        4,\n        \"scale_bias\",\n        \"Matrix of floats, each row r_i of which stores a pair \"\n        \"s_i, b_i -- scale and bias for i-th row\")\n    .Output(0, \"output\", \"output\");\n\nOPERATOR_SCHEMA(FloatToRowwiseQuantized8Bits)\n    .NumInputs(1)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nThis operator applies 8Bit row-wise quantization to\ninput tensor and returns quantized tensor. Row wise quantization of\ninput tensor is the following process. We take tensor of size\n(m_1, m_2,...,m_n), n >= 2, reshape it into matrix of size\n(m_1, m_2 x... x m_n) and apply row-wise quantization. After this,\nwe compute scale_i= (min_i - max_i) / 255 and  bias_i = min_i for\ni-th row r_i of reshaped matrix, where min_i and max_i --  minimum\nand maximum elements of i-th row, and quantize each element r_{ij} as\n0 <= round(r_ij - bias_i) / scale_i) < 256. Instead of input tensor\nwe obtain uint8 tensor and auxiliary information as scale and bias to\nrestore input tensor (with losses).\n)DOC\")\n    .Input(0, \"input\", \"input\")\n    .Output(0, \"quantized_input\", \"quantized_input\")\n    .Output(\n        1,\n        \"scale_bias\",\n        \"Matrix of floats, each row r_i of which stores a pair \"\n        \"s_i, b_i\");\n\nOPERATOR_SCHEMA(Rowwise8BitQuantizedToFloat)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven uint8 tensor, quantized using 8bit row-wise\nquantization, and auxiliary scales and biases, this operator\nrestores float tensor in the following way. We take input 8bits tensor\nof size  (m_1, m_2, ..., m_n), n >= 2, reshape it  into matrix of size\n(m_1, m_2 x... x m_n). We compute element r_{ij} of output matrix as\nr_{ij} * s_i + b_i and after this we reshape this output matrix into\noutput tensor of size (m_1, m_2, ..., m_n).\n)DOC\")\n    .Input(0, \"quantized_input\", \"quantized_input\")\n    .Input(\n        1,\n        \"scale_bias\",\n        \"Matrix of floats, each row r_i of which stores a pair \"\n        \"s_i, b_i -- scale and bias for i-th row\")\n    .Output(1, \"output\", \"output\");\n\nNO_GRADIENT(Rowwise8BitQuantizedToFloat);\nNO_GRADIENT(FloatToRowwiseQuantized8Bits);\nNO_GRADIENT(SparseLengthsSum8BitsRowwise);\nNO_GRADIENT(SparseLengthsWeightedSum8BitsRowwise);\nNO_GRADIENT(SparseLengthsMean8BitsRowwise);\nNO_GRADIENT(SparseLengthsWeightedMean8BitsRowwise);\n}\n"
  },
  {
    "path": "caffe2/operators/lengths_reducer_rowwise_8bit_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#ifndef CAFFE2_OPERATORS_LENGTHS_REDUCER_ROWWISE_8bits_OP_H_\n#define CAFFE2_OPERATORS_LENGTHS_REDUCER_ROWWISE_8bits_OP_H_\n// SparseLengthsSum8bits\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/reducer_functors.h\"\n#include \"caffe2/perfkernels/embedding_lookup.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nnamespace {\nconst float kEqualityThreshold = 1e-10f;\n}\n\ntemplate <\n    class Context,\n    bool USE_WEIGHTS = 0,\n    bool USE_MEAN = 0,\n    class OutDataT = float>\nclass SparseLengths8BitsRowwiseOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(SparseLengths8BitsRowwiseOp);\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename IndexType>\n  bool DoRunWithType() {\n    auto& dataInput = Input(DATA);\n    auto& lengthsInput = Input(LENGTHS);\n    auto* output = Output(0);\n    auto* scale_bias = Input(SCALE_BIAS).template data<float>();\n    CAFFE_ENFORCE_EQ(1, lengthsInput.ndim(), \"LENGTHS must be a vector\");\n    const TIndex outputSize = lengthsInput.dim(0);\n\n    auto& indicesInput = Input(INDICES);\n    CAFFE_ENFORCE_EQ(\n        2, Input(SCALE_BIAS).ndim(), \"scale_bias has to be matrix\");\n    CAFFE_ENFORCE_EQ(\n        dataInput.dim(0),\n        Input(SCALE_BIAS).dim(0),\n        \"scale_bias must have the same first dim as data\");\n    CAFFE_ENFORCE_EQ(\n        2,\n        Input(SCALE_BIAS).dim(1),\n        \"the second dim of scale_bias has to be equal to 2\");\n    CAFFE_ENFORCE_EQ(1, indicesInput.ndim(), \"INDICES must be a vector\");\n    const IndexType* indices = indicesInput.template data<IndexType>();\n    TIndex dataToReduceSize = indicesInput.dim(0);\n\n    const int* lengths = lengthsInput.template data<int>();\n    vector<TIndex> shape = dataInput.dims();\n    shape[0] = outputSize;\n    output->Resize(shape);\n    const float* w = nullptr;\n    if (USE_WEIGHTS) {\n      w = Input(WEIGHTS).template data<float>();\n    }\n    TIndex in_block_size = dataInput.size_from_dim(1);\n    OutDataT* out = output->template mutable_data<OutDataT>();\n    const uint8_t* input_data = dataInput.template data<uint8_t>();\n\n    // delegate work to perfkernel that branches based on architecture\n    const TIndex indices_size = indicesInput.size();\n    const TIndex N = dataInput.dim(0);\n    EmbeddingLookup(\n        in_block_size,\n        outputSize,\n        indices_size,\n        N, // embeding table length\n        input_data,\n        indices,\n        lengths,\n        w,\n        scale_bias,\n        USE_MEAN,\n        out);\n\n    return true;\n  }\n\n  enum {\n    DATA = 0,\n    WEIGHTS = 1,\n    INDICES = 1 + USE_WEIGHTS,\n    LENGTHS = 2 + USE_WEIGHTS,\n    SCALE_BIAS = 3 + USE_WEIGHTS\n  };\n};\n\ntemplate <class Context>\nclass FloatToRowwiseQuantized8BitsOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(FloatToRowwiseQuantized8BitsOp);\n  bool RunOnDevice() override {\n    auto& input = Input(DATA_FLOAT);\n    auto* output = Output(DATA_UINT8);\n    auto* scale_bias = Output(SCALE_BIAS);\n    auto* input_data = input.template data<float>();\n    output->ResizeLike(input);\n    vector<TIndex> scale_bias_dims = {input.dim(0), 2};\n    scale_bias->Resize(scale_bias_dims);\n    auto* output_data = output->template mutable_data<uint8_t>();\n    float* scale_bias_data = scale_bias->template mutable_data<float>();\n    size_t n_blocks = input.dim(0);\n    size_t block_size = input.size_from_dim(1);\n    for (size_t i = 0; i < n_blocks; ++i) {\n      ConstEigenVectorArrayMap<float> input_row(\n          input_data + i * block_size, block_size);\n      EigenVectorArrayMap<uint8_t> output_row(\n          output_data + i * block_size, block_size);\n      auto min_element = input_row.minCoeff();\n      auto max_element = input_row.maxCoeff();\n      if (max_element - min_element < kEqualityThreshold) {\n        scale_bias_data[2 * i] = 1.0f;\n        scale_bias_data[2 * i + 1] = min_element;\n        memset(output_data + i * block_size, 0, block_size);\n      } else {\n        scale_bias_data[2 * i] = (max_element - min_element) / 255.0f;\n        scale_bias_data[2 * i + 1] = min_element;\n        const float inv_scale = 1.0f / scale_bias_data[2 * i];\n        output_row = ((input_row - scale_bias_data[2 * i + 1]) * inv_scale)\n                         .round()\n                         .template cast<uint8_t>();\n      }\n    }\n    return true;\n  }\n\n private:\n  INPUT_TAGS(DATA_FLOAT);\n  OUTPUT_TAGS(DATA_UINT8, SCALE_BIAS);\n};\n\ntemplate <class Context>\nclass Rowwise8BitQuantizedToFloatOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(Rowwise8BitQuantizedToFloatOp);\n  bool RunOnDevice() override {\n    auto& input = Input(DATA_UINT8);\n    auto& scale_bias = Input(SCALE_BIAS);\n    auto* output = Output(DATA_FLOAT);\n    CAFFE_ENFORCE_EQ(2, scale_bias.ndim(), \"scale_bias has to be matrix\");\n    CAFFE_ENFORCE_EQ(\n        input.dim(0),\n        scale_bias.dim(0),\n        \"scale_bias must have the same first dim as data\");\n    CAFFE_ENFORCE_EQ(\n        2,\n        scale_bias.dim(1),\n        \"the second dim of scale_bias has to be equal to 2\");\n    output->ResizeLike(input);\n    auto* input_data = input.template data<uint8_t>();\n    auto* scale_bias_data = scale_bias.template data<float>();\n\n    auto* output_data = output->template mutable_data<float>();\n    size_t block_size = input.size_from_dim(1);\n    size_t n_blocks = input.dim(0);\n\n    for (size_t i = 0; i < n_blocks; ++i) {\n      ConstEigenVectorArrayMap<uint8_t> input_row(\n          input_data + i * block_size, block_size);\n      EigenVectorArrayMap<float> output_row(\n          output_data + i * block_size, block_size);\n      output_row = input_row.template cast<float>() * scale_bias_data[2 * i] +\n          scale_bias_data[2 * i + 1];\n    }\n    return true;\n  }\n\n private:\n  INPUT_TAGS(DATA_UINT8, SCALE_BIAS);\n  OUTPUT_TAGS(DATA_FLOAT);\n};\n}\n#endif // CAFFE2_OPERATORS_LENGTHS_REDUCER_ROWWISE_8bits_H_\n"
  },
  {
    "path": "caffe2/operators/lengths_tile_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/lengths_tile_op.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(LengthsTile, LengthsTileOp<CPUContext>);\n\nOPERATOR_SCHEMA(LengthsTile)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven DATA tensor of rank r >= 1, and LENGTHS tensor of rank 1, duplicate each\nentry of the outer-most dimension of DATA according to LENGTHS, and concatenate\nthem in an output tensor of rank r.\n\nExample:\n  DATA  = [\n      [1.0, 1.2],\n      [2.3, 3.4],\n      [4.5, 5.7],\n      [6.8, 7.9],\n  ]\n  LENGTHS = [0, 1, 3, 2]\n  OUTPUT = [\n      [2.3, 3.4],\n      [4.5, 5.7],\n      [4.5, 5.7],\n      [4.5, 5.7],\n      [6.8, 7.9],\n      [6.8, 7.9],\n  ]\n)DOC\")\n    .Input(\n        0,\n        \"DATA\",\n        \"Tensor of rank r >= 1. First dimension must be equal to the size of \"\n        \"lengths\")\n    .Input(1, \"LENGTHS\", \"Tensor of int32 lengths of rank 1\")\n    .Output(0, \"OUTPUT\", \"Tensor of rank r\");\n\nclass GetLengthsTileGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE_EQ(def_.input_size(), 2);\n    return SingleGradientDef(\n        \"LengthsSum\",\n        \"\",\n        // input 1 is the lengths used to repeat\n        // DATA in the forward pass\n        vector<string>{GO(0), I(1)},\n        // only concerned with the gradient on \"DATA\"\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(LengthsTile, GetLengthsTileGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/lengths_tile_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_LENGTHS_TILE_OP_H_\n#define CAFFE2_OPERATORS_LENGTHS_TILE_OP_H_\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass LengthsTileOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(LengthsTileOp);\n\n  bool RunOnDevice() override {\n    auto& data = Input(DATA);\n    auto& lengths = Input(LENGTHS);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_EQ(lengths.ndim(), 1, \"LENGTHS must be 1-D\");\n    CAFFE_ENFORCE_GE(data.ndim(), 1, \"DATA should be at least 1-D\");\n    CAFFE_ENFORCE_EQ(lengths.size(), data.dim(0));\n\n    // Context::CopyFrom and math::Sum need the same context to avoid race\n    // conditions\n    CPUContext cpuContext;\n    lengths_host_.CopyFrom(lengths, &cpuContext);\n    auto lengths_size = lengths_host_.size();\n    auto* lengths_data = lengths_host_.data<int32_t>();\n\n    int32_t total_length = 0;\n    math::Sum<int32_t, CPUContext>(\n        lengths_size, lengths_data, &total_length, &cpuContext);\n\n    auto shape = data.dims();\n    shape[0] = total_length;\n    output->Resize(shape);\n\n    auto block_bytesize = data.size_from_dim(1) * data.meta().itemsize();\n    auto src = static_cast<const char*>(data.raw_data());\n    auto out = static_cast<char*>(output->raw_mutable_data(data.meta()));\n\n    for (TIndex i = 0; i < lengths_size; ++i) {\n      auto length = lengths_data[i];\n      CAFFE_ENFORCE_GE(length, 0);\n      for (int32_t j = 0; j < length; ++j) {\n        context_.template CopyBytes<Context, Context>(block_bytesize, src, out);\n        out += block_bytesize;\n      }\n      src += block_bytesize;\n    }\n    return true;\n  }\n\n  INPUT_TAGS(DATA, LENGTHS);\n\n private:\n  TensorCPU lengths_host_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_LENGTHS_TILE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/lengths_tile_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/lengths_tile_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(LengthsTile, LengthsTileOp<CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/lengths_top_k_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/lengths_top_k_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nbool LengthsTopKOp<T, Context>::RunOnDevice() {\n  auto& X = Input(X_IN);\n  auto& Y = Input(Y_IN);\n  int N = Y.dim32(0);\n  const T* X_data = X.template data<T>();\n  const int* input_len = Y.template data<int>();\n  auto* output_topk_values = Output(TOPK_VALUES_OUT);\n  auto* output_topk_indices = Output(TOPK_INDICES_OUT);\n\n  output_topk_values->Resize(N * k_);\n  output_topk_indices->Resize(N * k_);\n  std::vector<int> output_dims = std::vector<int>({N, k_});\n  output_topk_values->Reshape(output_dims);\n  output_topk_indices->Reshape(output_dims);\n  T* output_topk_values_data = output_topk_values->template mutable_data<T>();\n  int* output_topk_indices_data =\n      output_topk_indices->template mutable_data<int>();\n\n  auto cmp = [](std::pair<T, TIndex>& lhs, std::pair<T, TIndex>& rhs) {\n    return lhs.first > rhs.first ||\n        (lhs.first == rhs.first && lhs.second < rhs.second);\n  };\n\n  // Sort preserving indices\n  int next_index = 0;\n  for (TIndex i = 0; i < N; ++i) {\n    // Build a min-heap, the heap element is pair of (value, idx)\n    // the top of the heap is the smallest value\n    std::priority_queue<\n        std::pair<T, TIndex>,\n        std::vector<std::pair<T, TIndex>>,\n        decltype(cmp)>\n        p_queue(cmp);\n\n    // Maintain the size of heap to be less or equal to k_, so the\n    // heap will hold the k_ largest values\n    for (TIndex j = 0; j < input_len[i]; ++j) {\n      const auto value = X_data[next_index++];\n      if (p_queue.size() < k_ || value > p_queue.top().first) {\n        p_queue.push(std::make_pair(value, j));\n      }\n      if (p_queue.size() > k_) {\n        p_queue.pop();\n      }\n    }\n\n    int last_index = p_queue.size();\n    for (TIndex j = 0; j < k_; ++j) {\n      if (p_queue.size() > 0) {\n        auto& pqElem = p_queue.top();\n        output_topk_values_data[i * k_ + last_index - j - 1] = pqElem.first;\n        output_topk_indices_data[i * k_ + last_index - j - 1] = pqElem.second;\n        p_queue.pop();\n      } else {\n        output_topk_values_data[i * k_ + j] = 0;\n        output_topk_indices_data[i * k_ + j] = -1;\n      }\n    }\n  }\n\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool LengthsTopKGradientOp<T, Context>::RunOnDevice() {\n  auto& input_len = Input(LENGTH_IN);\n  int N = input_len.size();\n  auto& input_indices = Input(INDICES_IN);\n  CAFFE_ENFORCE_GE(input_indices.ndim(), 2, \"input dim must be >= 2\");\n  CAFFE_ENFORCE_EQ(\n      input_indices.size(), N * k_, \"input_indices shape is not correct\");\n  auto& input_topk = Input(DER_TOPK_IN);\n  CAFFE_ENFORCE_EQ(\n      input_topk.size(), N * k_, \"input_topk shape is not correct\");\n  auto* X_out = Output(DER_X_OUT);\n\n  const int* input_len_data = input_len.template data<int>();\n  const int* input_indices_data = input_indices.template data<int>();\n  const T* input_topk_data = input_topk.template data<T>();\n\n  int num_indices = 0;\n  for (int i = 0; i < N; i++) {\n    num_indices += input_len_data[i];\n  }\n  X_out->Resize(num_indices);\n  std::vector<int> output_dims = std::vector<int>({num_indices});\n  X_out->Reshape(output_dims);\n  T* X_out_data = X_out->template mutable_data<T>();\n  math::Set<T, Context>(num_indices, 0.0, X_out_data, &context_);\n\n  int index_offset = 0;\n  for (int i = 0; i < N; i++) {\n    for (int j = 0; j < std::min(input_len_data[i], k_); j++) {\n      int cur_index = index_offset + input_indices_data[i * k_ + j];\n      CAFFE_ENFORCE_LT(\n          cur_index, num_indices, \"cur_index should be less than num_indices\");\n      X_out_data[cur_index] = input_topk_data[i * k_ + j];\n    }\n    index_offset += input_len_data[i];\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(LengthsTopK, LengthsTopKOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    LengthsTopKGradient,\n    LengthsTopKGradientOp<float, CPUContext>);\nOPERATOR_SCHEMA(LengthsTopK)\n    .NumInputs(2)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nApply TopK to each segment of the input tensor, where segments are defined by\ntheir LENGTHS, and concatenate them in an output tensor of\nshape=(SIZE(LENGTHs), k). In case there's less than k values in a segment,\nthe output value will be padded by 0, and the corresponding output indices will\nbe padded by -1.\n)DOC\")\n    .Input(\n        0,\n        \"DATA\",\n        \"Tensor of rank 1. First dimension must be equal to the sum of \"\n        \"lengths\")\n    .Input(1, \"LENGTHS\", \"Tensor of int32 lengths of rank 1\")\n    .Output(\n        0,\n        \"TopKValue\",\n        \"Output top k elements for each segment, with\"\n        \"shape=(SIZE(lengths), k)\")\n    .Output(\n        1,\n        \"TopKIndices\",\n        \"Output indices in DATA corresponding to value in TopKValue\")\n    .Arg(\n        \"k\",\n        \"the number of top values to return for each segment, if the number \"\n        \"of values is smaller than k, the values would be padded with 0 and \"\n        \"indices would be padded with -1.\");\nOPERATOR_SCHEMA(LengthsTopKGradient).NumInputs(3).NumOutputs(1);\n\nnamespace {\n\nclass GetLengthsTopKGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"LengthsTopKGradient\",\n        \"\",\n        vector<string>{I(1), O(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\n} // namespace\n\nREGISTER_GRADIENT(LengthsTopK, GetLengthsTopKGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/lengths_top_k_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#ifndef CAFFE2_OPERATORS_LENGTHS_TOP_K_OP_H_\n#define CAFFE2_OPERATORS_LENGTHS_TOP_K_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\ntemplate <typename T, class Context>\nclass LengthsTopKOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  LengthsTopKOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws), OP_SINGLE_ARG(int, \"k\", k_, -1) {\n    CAFFE_ENFORCE_GE(k_, 1, \"k argument must be >= 1\");\n  }\n\n  bool RunOnDevice() override;\n\n protected:\n  int k_;\n  INPUT_TAGS(X_IN, Y_IN);\n  OUTPUT_TAGS(TOPK_VALUES_OUT, TOPK_INDICES_OUT);\n};\n\ntemplate <typename T, class Context>\nclass LengthsTopKGradientOp : public Operator<Context> {\n public:\n  LengthsTopKGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws), OP_SINGLE_ARG(int, \"k\", k_, -1) {\n    CAFFE_ENFORCE_GE(k_, 1, \"k argument must be >= 1\");\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  int k_;\n  INPUT_TAGS(LENGTH_IN, INDICES_IN, DER_TOPK_IN);\n  OUTPUT_TAGS(DER_X_OUT);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_LENGTHS_TOP_K_OP_H_\n"
  },
  {
    "path": "caffe2/operators/listwise_l2r_op.cc",
    "content": "#include \"caffe2/operators/listwise_l2r_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n// Returns the indices that would sort an array. For example:\n//   data = [3, 1, 2, 4]\n//   return = [1, 2, 0, 3] (reverse = false)\n//   return = [3, 0, 2, 1] (reverse = true)\ntemplate <typename TDATA, typename TIDX>\nvoid arg_sort(const TDATA* data, TIDX* idx, const size_t N, bool reverse) {\n  std::function<bool(size_t, size_t)> cmp_lambda;\n  if (reverse) {\n    cmp_lambda = [data](size_t i, size_t j) { return data[i] > data[j]; };\n  } else {\n    cmp_lambda = [data](size_t i, size_t j) { return data[i] < data[j]; };\n  }\n  size_t n = 0;\n  std::generate(idx, idx + N, [&n] { return n++; });\n  std::sort(idx, idx + N, cmp_lambda);\n}\n\n#define PAIRWISE_DIFF(vec, N)                               \\\n  ((vec.matrix() * Eigen::MatrixXf::Ones(1, N) -            \\\n    Eigen::MatrixXf::Ones(N, 1) * vec.matrix().transpose()) \\\n       .array())\n\n#define CWISE_SIGM(vec) (1. / (1. + (-(vec)).exp()))\n\n#define CWISE_GT(vec1, vec2) ((vec1) > (vec2))\n\n#define CWISE_LT(vec1, vec2) ((vec1) < (vec2))\n\n#define CWISE_SIGN(vec) (CWISE_GT((vec), 0).cast<float>() * 2. - 1.)\n\n#define CWISE_LOG_SIGM(vec, huge) \\\n  (CWISE_GT((vec), (huge))        \\\n       .select(                   \\\n           0, CWISE_LT((vec), -(huge)).select(vec, CWISE_SIGM((vec)).log())))\n\n} // namespace\n\ntemplate <>\nvoid LambdaRankNdcgOp<float, CPUContext>::ResizeInvLogITensor(int size) {\n  int old_size = inv_log_i_.size();\n  int new_size = std::max(old_size, 1);\n  while (new_size < size) {\n    new_size <<= 1;\n  }\n  if (new_size != old_size) {\n    inv_log_i_.Resize(new_size);\n    auto* data = inv_log_i_.template mutable_data<float>();\n    EigenVectorArrayMap<float> vec(data, inv_log_i_.size());\n    const float log2f_ = std::log(2.f);\n    vec = log2f_ *\n        (Eigen::ArrayXf::LinSpaced(new_size, 2, 1 + new_size).log().inverse());\n  }\n  return;\n}\n\ntemplate <>\nvoid LambdaRankNdcgOp<float, CPUContext>::ComputeDiscounts(int* idx, int N) {\n  discount_.Resize(N);\n  auto* discount_data = discount_.template mutable_data<float>();\n  auto* inv_log_i_data = inv_log_i_.template mutable_data<float>();\n  for (int i = 0; i < N; i++) {\n    discount_data[idx[i]] = inv_log_i_data[i];\n  }\n  return;\n}\n\ntemplate <>\nbool LambdaRankNdcgOp<float, CPUContext>::RunOnDevice() {\n  auto& y = Input(PRED);\n  auto& r = Input(REL);\n  auto* loss = Output(LOSS);\n  auto* dy = Output(DPRED);\n\n  const auto* y_data = y.template data<float>();\n  const auto* r_data = r.template data<float>();\n  ConstEigenVectorArrayMap<float> y_vec(y_data, y.size());\n  ConstEigenVectorArrayMap<float> r_vec(r_data, r.size());\n  CAFFE_ENFORCE(y.ndim() == 1);\n  CAFFE_ENFORCE(y.size() == r.size());\n\n  int N = y.size();\n  ideal_idx_.Resize(N);\n  rank_idx_.Resize(N);\n  auto* rank_idx_data = rank_idx_.template mutable_data<int>();\n  auto* ideal_idx_data = ideal_idx_.template mutable_data<int>();\n  arg_sort(y_data, rank_idx_data, N, true);\n  arg_sort(r_data, ideal_idx_data, N, true);\n\n  const double log2f_ = std::log(2.f);\n  gain_.Resize(N);\n  auto* gain_data = gain_.template mutable_data<float>();\n  EigenVectorArrayMap<float> gain_vec(gain_data, gain_.size());\n  gain_vec = (r_vec * log2f_).exp();\n\n  ResizeInvLogITensor(N);\n  ComputeDiscounts(ideal_idx_data, N);\n  auto* ideal_discount_data = discount_.template mutable_data<float>();\n  EigenVectorArrayMap<float> ideal_discount_vec(\n      ideal_discount_data, discount_.size());\n  double idcg = (gain_vec * ideal_discount_vec).sum();\n  // in case that all docs in a session have zero ratings, idcg will be zero.\n  // For that case we will not normalize dcg.\n  if (idcg < 1e-5) {\n    idcg = 1.0;\n  }\n\n  ComputeDiscounts(rank_idx_data, N);\n  auto* discount_data = discount_.template mutable_data<float>();\n  EigenVectorArrayMap<float> discount_vec(discount_data, discount_.size());\n\n  lambda_.Resize(N * N);\n  auto* lambda_data = lambda_.template mutable_data<float>();\n  EigenArrayMap<float> lambda_mat(lambda_data, N, N);\n  // computes lambda weight (i, j) = abs(gain_dff * discount_diff)\n  lambda_mat =\n      (PAIRWISE_DIFF(discount_vec, N) * PAIRWISE_DIFF(gain_vec, N)).abs();\n\n  loss->Resize(1);\n  dy->Resize(N);\n  auto* loss_data = loss->template mutable_data<float>();\n  auto* dy_data = dy->template mutable_data<float>();\n  EigenVectorArrayMap<float> dy_vec(dy_data, dy->size());\n  // dy_i =\n  //    \\sum_j lambda_{i, j} -sign(i > j) * sigm( -sign(i > j)*(yi - yj) )\n  //                         |++ gradient of rank loss between i & j  ++|\n  dy_vec =\n      -(lambda_mat * CWISE_SIGN(PAIRWISE_DIFF(r_vec, N)) *\n        CWISE_SIGM(\n            -CWISE_SIGN(PAIRWISE_DIFF(r_vec, N)) * PAIRWISE_DIFF(y_vec, N)))\n           .rowwise()\n           .sum() /\n      idcg;\n  // loss = \\sum_{i, j} lambda_{i, j} rank_loss(i, j)\n  *loss_data =\n      -(lambda_mat *\n        CWISE_LOG_SIGM(\n            CWISE_SIGN(PAIRWISE_DIFF(r_vec, N)) * PAIRWISE_DIFF(y_vec, N), 100))\n           .sum() /\n      idcg;\n  return true;\n}\n\ntemplate <>\nbool LambdaRankNdcgGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& y = Input(Y);\n  auto& dy_cache = Input(DY_CACHE);\n  auto& dLoss = Input(DLOSS);\n  auto* dy = Output(DY);\n  CAFFE_ENFORCE(y.ndim() == 1);\n  CAFFE_ENFORCE(dy_cache.ndim() == 1);\n  CAFFE_ENFORCE(dy_cache.size() > 0);\n  CAFFE_ENFORCE(y.size() == dy_cache.size());\n  CAFFE_ENFORCE(dLoss.size() == 1);\n\n  ConstEigenVectorArrayMap<float> dy_cache_vec(\n      dy_cache.template data<float>(), dy_cache.size());\n  dy->Resize(dy_cache.size());\n  EigenVectorArrayMap<float> dy_vec(\n      dy->template mutable_data<float>(), dy->size());\n  float multiplier = dLoss.template data<float>()[0];\n  dy_vec = multiplier * dy_cache_vec;\n  return true;\n}\n\nnamespace {\n\nREGISTER_CPU_OPERATOR(LambdaRankNdcg, LambdaRankNdcgOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    LambdaRankNdcgGradient,\n    LambdaRankNdcgGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LambdaRankNdcg).NumInputs(2).NumOutputs(2).SetDoc(R\"DOC(\nIt implements the LambdaRank as appeared in Wu, Qiang, et al. \"Adapting boosting\nfor information retrieval measures.\" Information Retrieval 13.3 (2010): 254-270.\n\nThis method heuristically optimizes the NDCG.\n)DOC\");\nOPERATOR_SCHEMA(LambdaRankNdcgGradient).NumInputs(3).NumOutputs(1);\n\nclass GetLambdaRankNdcgGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"LambdaRankNdcgGradient\",\n        \"\",\n        vector<string>{I(0), O(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(LambdaRankNdcg, GetLambdaRankNdcgGradient);\n\n} // namespace\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/listwise_l2r_op.h",
    "content": "// Copyright 2004-present Facebook. All Rights Reserved.\n\n#pragma once\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass LambdaRankNdcgOp final : public Operator<Context> {\n public:\n  LambdaRankNdcgOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n private:\n  INPUT_TAGS(PRED, REL);\n  OUTPUT_TAGS(LOSS, DPRED);\n\n  void ResizeInvLogITensor(int);\n  void ComputeDiscounts(int*, int);\n  Tensor<Context> gain_;\n  Tensor<Context> discount_;\n  Tensor<Context> rank_idx_;\n  Tensor<Context> ideal_idx_;\n  Tensor<Context> lambda_;\n  Tensor<Context> inv_log_i_;\n};\n\ntemplate <typename T, class Context>\nclass LambdaRankNdcgGradientOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(LambdaRankNdcgGradientOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n private:\n  INPUT_TAGS(Y, DY_CACHE, DLOSS);\n  OUTPUT_TAGS(DY);\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/load_save_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/load_save_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nvoid LoadOp<CPUContext>::SetCurrentDevice(BlobProto* proto) {\n  if (proto->has_tensor()) {\n    proto->mutable_tensor()->mutable_device_detail()->set_device_type(CPU);\n  }\n}\n\nREGISTER_CPU_OPERATOR(DBExists, DBExistsOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Load, LoadOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Save, SaveOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Checkpoint, CheckpointOp<CPUContext>);\n// CPU Operator old name: do NOT use, we may deprecate this later.\nREGISTER_CPU_OPERATOR(Snapshot, CheckpointOp<CPUContext>);\n\nOPERATOR_SCHEMA(DBExists)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nChecks if the DB exists.\n)DOC\")\n    .Output(0, \"exists\", \"A scalar bool Tensor.\")\n    .Arg(\n        \"absolute_path\",\n        \"(int, default 0) if set, use the db path directly and do not prepend \"\n        \"the current root folder of the workspace.\")\n    .Arg(\"db_name\", \"(string) the path to the db to load.\")\n    .Arg(\"db_type\", \"(string) the type of the db.\");\n\nOPERATOR_SCHEMA(Load)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .SetDoc(R\"DOC(\nThe Load operator loads a set of serialized blobs from a db or multiple dbs. It\ntakes [0, infinity) number of inputs and [0, infinity) number of outputs, using\nthe db keys to match the db entries with the outputs.\n\nIf at least one input is passed, then it is assumed that that input blobs are a\nset of DBReaders to load from. Otherwise the db or dbs argument is used to load\nblobs from one single db or multiple dbs respectively. db_type argument is used\nto specify the type of the input db/dbs.\n)DOC\")\n    .Arg(\n        \"absolute_path\",\n        \"(int, default 0) if set, use the db path directly and do not prepend \"\n        \"the current root folder of the workspace.\")\n    .Arg(\n        \"add_prefix\",\n        \"(string, default=\\\"\\\") blobs will be prefixed with this when loading.\"\n        \"Useful for avoiding collisions with blobs existing in the workspace.\"\n        \"The output blob names specified to this op should include this prefix.\")\n    .Arg(\n        \"strip_prefix\",\n        \"(string, default=\\\"\\\") characters in the provided blob \"\n        \" names that match strip_prefix will be removed prior to loading.\"\n        \" Also, characters that precede strip_prefix will be removed. Useful \"\n        \" for removing device scope from blob names.\")\n    .Arg(\"db\", \"(string) the path to the db to load.\")\n    .Arg(\n        \"dbs\",\n        \"(list of strings) the paths to the dbs to load. This is used for loading\"\n        \" blobs from multiple databases. If it is set, argument in \\\"db\\\" will be\"\n        \" ignored.\")\n    .Arg(\"db_type\", \"(string) the type of the db.\")\n    .Arg(\n        \"keep_device\",\n        \"(int, default 0) if nonzero, the blobs are loaded into the device that \"\n        \"is specified in the serialized BlobProto. Otherwise, the device will be \"\n        \"set as the one that the Load operator is being run under.\")\n    .Arg(\n        \"load_all\",\n        \"(int, default 0) if nonzero, will load all blobs pointed to by the db \"\n        \"to the workspace overwriting/creating blobs as needed.\")\n    .Arg(\n        \"allow_incomplete\",\n        \"(bool, default false) if true, will allow not loading all the output \"\n        \"blobs specified in the outputs\")\n    .Arg(\n        \"source_blob_names\",\n        \"(list of strings) if set, used instead of output \"\n        \"blob names, to specify which blobs in the db shall be loaded. Must be \"\n        \"the same length as number of output blobs.\");\n\nOPERATOR_SCHEMA(Save)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(\nThe Save operator saves a set of blobs to a db. It takes [1, infinity) number\nof inputs and has no output. The contents of the inputs are written into the\ndb specified by the arguments.\n)DOC\")\n    .Arg(\n        \"absolute_path\",\n        \"(int, default 0) if set, use the db path directly and do not prepend \"\n        \"the current root folder of the workspace.\")\n     .Arg(\n         \"strip_prefix\",\n         \"(string, default=\\\"\\\") characters in the provided blob \"\n         \" names that match strip_prefix will be removed prior to saving.\"\n         \" Also, characters that precede strip_prefix will be removed. Useful \"\n         \" for removing device scope from blob names.\")\n    .Arg(\n        \"blob_name_overrides\",\n        \"(list of strings) if set, used instead of original \"\n        \"blob names. Must be the same length as number of blobs.\")\n    .Arg(\"db\", \"(string) the path to the db to load.\")\n    .Arg(\"db_type\", \"(string) the type of the db.\");\n\nOPERATOR_SCHEMA(Checkpoint)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(\nThe Checkpoint operator is similar to the Save operator, but allows one to save\nto db every few iterations, with a db name that is appended with the iteration\ncount. It takes [1, infinity) number of inputs and has no output. The first\ninput has to be a TensorCPU of type int and has size 1 (i.e. the iteration\ncounter). This is determined whether we need to do checkpointing.\n)DOC\")\n    .Arg(\n        \"absolute_path\",\n        \"(int, default 0) if set, use the db path directly and do not prepend \"\n        \"the current root folder of the workspace.\")\n    .Arg(\n        \"db\",\n        \"(string) a template string that one can combine with the \"\n        \"iteration to create the final db name. For example, \"\n        \"\\\"/home/lonestarr/checkpoint_%08d.db\\\"\")\n    .Arg(\"db_type\", \"(string) the type of the db.\")\n    .Arg(\n        \"every\",\n        \"(int, default 1) the checkpointing is carried out when \"\n        \"(iter mod every) is zero.\");\n\nOPERATOR_SCHEMA(Snapshot);\n\nNO_GRADIENT(Load);\nSHOULD_NOT_DO_GRADIENT(DBExists);\nSHOULD_NOT_DO_GRADIENT(Save);\nSHOULD_NOT_DO_GRADIENT(Checkpoint);\nSHOULD_NOT_DO_GRADIENT(Snapshot);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/load_save_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_LOAD_SAVE_OP_H_\n#define CAFFE2_OPERATORS_LOAD_SAVE_OP_H_\n\n#include <cstdio>\n#include <map>\n#include <unordered_set>\n\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\nnamespace {\nstruct BlobState {\n  int64_t total_size;\n  int64_t current_size;\n  bool is_tensor;\n  std::set<int32_t> seen_chunks_ids;\n\n  explicit BlobState(\n      int64_t total_size = 0,\n      int64_t current_size = 0,\n      bool is_tensor = false)\n      : total_size(total_size),\n        current_size(current_size),\n        is_tensor(is_tensor) {}\n};\n} // namespace\n\nusing db::Cursor;\nusing db::DB;\nusing db::Transaction;\n\ntemplate <class Context>\nclass DBExistsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  DBExistsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        ws_(ws),\n        absolute_path_(\n            OperatorBase::GetSingleArgument<int>(\"absolute_path\", false)),\n        db_name_(OperatorBase::GetSingleArgument<string>(\"db_name\", \"\")),\n        db_type_(OperatorBase::GetSingleArgument<string>(\"db_type\", \"\")) {}\n\n  bool RunOnDevice() override {\n    string full_db_name =\n        absolute_path_ ? db_name_ : (ws_->RootFolder() + \"/\" + db_name_);\n    auto* output = Output(0);\n    output->Resize();\n    bool* exists = output->template mutable_data<bool>();\n\n    *exists = caffe2::db::DBExists(db_type_, full_db_name);\n    return true;\n  }\n\n private:\n  Workspace* ws_;\n  bool absolute_path_;\n  std::string db_name_;\n  std::string db_type_;\n};\n\ntemplate <class Context>\nclass LoadOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  LoadOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        ws_(ws),\n        absolute_path_(\n            OperatorBase::GetSingleArgument<int>(\"absolute_path\", false)),\n        add_prefix_(OperatorBase::GetSingleArgument<string>(\"add_prefix\", \"\")),\n        strip_prefix_(\n            OperatorBase::GetSingleArgument<string>(\"strip_prefix\", \"\")),\n        db_name_(OperatorBase::GetSingleArgument<string>(\"db\", \"\")),\n        db_names_(OperatorBase::GetRepeatedArgument<string>(\"dbs\")),\n        db_type_(OperatorBase::GetSingleArgument<string>(\"db_type\", \"\")),\n        keep_device_(OperatorBase::GetSingleArgument<int>(\"keep_device\", 0)),\n        load_all_(OperatorBase::GetSingleArgument<int>(\"load_all\", 0)),\n        allow_incomplete_(\n            OperatorBase::GetSingleArgument<bool>(\"allow_incomplete\", false)),\n        blob_names_(\n            OperatorBase::GetRepeatedArgument<string>(\"source_blob_names\")) {\n    if (InputSize() == 0) {\n      CAFFE_ENFORCE_GT(db_type_.size(), 0, \"Must specify a db type.\");\n      if (db_names_.empty()) {\n        CAFFE_ENFORCE_GT(db_name_.size(), 0, \"Must specify a db name.\");\n        db_names_.push_back(db_name_);\n        db_name_ = \"\";\n      } else {\n        std::set<std::string> db_name_set;\n        for (const string& db_name : db_names_) {\n          CAFFE_ENFORCE_GT(db_name.size(), 0, \"Db name should not be empty.\");\n          CAFFE_ENFORCE(\n              db_name_set.insert(db_name).second,\n              \"Duplicated db name: \",\n              db_name);\n        }\n        db_name_ = \"\";\n      }\n    }\n    CAFFE_ENFORCE(blob_names_.empty() || blob_names_.size() == OutputSize(),\n      \"Number of output blobs and source_blob_names mismatch.\");\n    CAFFE_ENFORCE(blob_names_.empty() || strip_prefix_.empty(),\n        \"strip_prefix and source_blob_names are mutually exclusive.\");\n    CAFFE_ENFORCE(blob_names_.empty() || !load_all_,\n        \"cannot load_all_ while using source_blob_names.\");\n    if (!load_all_) {\n      // blob_names_ will be filled with ''source blob names'' in file/db\n      // if argument source_blob_names is not given, then blob_names_ is\n      // inferred from operator output\n      if(blob_names_.empty()) {\n        for (const string& name : operator_def.output()) {\n          blob_names_.push_back(name);\n        }\n      }\n      int idx = 0;\n      std::set<std::string> name_set;\n      for (const string& name : blob_names_) {\n        CAFFE_ENFORCE(name_set.insert(name).second,\n            \"Duplicated source blob name: \", name);\n        output_indices_[name] = idx++;\n      }\n    }\n  }\n\n  void SetCurrentDevice(BlobProto* proto);\n\n  bool RunOnDevice() override {\n    int total_loaded_blobs = 0;\n    std::unordered_map<string, BlobState> blob_states;\n    if (InputSize() > 0) {\n      for (int i = 0; i < InputSize(); ++i) {\n        const db::DBReader& reader = OperatorBase::Input<db::DBReader>(i);\n        extract(i, reader.cursor(), &blob_states, &total_loaded_blobs);\n      }\n    } else {\n      for (int i = 0; i < db_names_.size(); ++i) {\n        string full_db_name = absolute_path_\n            ? db_names_[i]\n            : (ws_->RootFolder() + \"/\" + db_names_[i]);\n        std::unique_ptr<DB> in_db(\n            caffe2::db::CreateDB(db_type_, full_db_name, caffe2::db::READ));\n        CAFFE_ENFORCE(in_db.get(), \"Cannot open db: \", full_db_name);\n        std::unique_ptr<Cursor> cursor(in_db->NewCursor());\n        extract(i, cursor.get(), &blob_states, &total_loaded_blobs);\n      }\n    }\n\n    validateBlobStates(blob_states);\n    // Loaded all the needed blobs.\n    if (load_all_ || total_loaded_blobs == OutputSize()) {\n      VLOG(1) << \"Loaded \" << total_loaded_blobs << \" blobs fully from db(s)\";\n      return true;\n    }\n\n    // Only loaded a subset of the blobs.\n    if (allow_incomplete_) {\n      VLOG(1) << \"Loaded \" << total_loaded_blobs << \" blobs out of \"\n              << OutputSize() << \" blobs from db(s).\";\n    } else {\n      for (const string& output_name : this->debug_def().output()) {\n        if (blob_states.count(output_name) == 0) {\n          LOG(ERROR) << \"Failed to load blob: \" << output_name;\n        }\n      }\n      CAFFE_THROW(\n          \"Expected to load \",\n          OutputSize(),\n          \" blobs, got \",\n          total_loaded_blobs,\n          \" only.\\n\");\n    }\n\n    return true;\n  }\n\n private:\n  void extract(\n      int db_id,\n      Cursor* cursor,\n      std::unordered_map<string, BlobState>* blob_states,\n      int* total_loaded_blobs) {\n    if (load_all_) {\n      extractAll(db_id, cursor, blob_states, total_loaded_blobs);\n    } else {\n      extractFrom(\n          db_id,\n          cursor,\n          OperatorBase::Outputs(),\n          blob_states,\n          total_loaded_blobs);\n    }\n  }\n\n  void extractAll(\n      int db_id,\n      Cursor* cursor,\n      std::unordered_map<string, BlobState>* blob_states,\n      int* total_loaded_blobs) {\n    CAFFE_ENFORCE(cursor, \"cursor is not valid\");\n    int loaded_blobs = 0;\n    for (; cursor->Valid(); cursor->Next()) {\n      const auto key = buildBlobNameFromDbKey(cursor->key());\n      if (key_to_dbid_.count(key) && key_to_dbid_[key] != db_id) {\n        CAFFE_THROW(\"Duplicate Key \", key, \" is found!\\n\");\n      } else {\n        key_to_dbid_[key] = db_id;\n      }\n\n      BlobProto proto;\n      CAFFE_ENFORCE(\n          proto.ParseFromString(cursor->value()), \"Couldn't parse Proto\");\n      if (!keep_device_) {\n        // If we are not keeping the device as the one specified in the\n        // proto, we will set the current device.\n        SetCurrentDevice(&proto);\n      }\n      Blob* blob = ws_->CreateBlob(key);\n      ProcessBlob(blob, proto, blob_states, key, &loaded_blobs);\n    }\n    *total_loaded_blobs += loaded_blobs;\n  }\n\n  void extractFrom(\n      int db_id,\n      Cursor* cursor,\n      const vector<Blob*>& outputs,\n      std::unordered_map<string, BlobState>* blob_states,\n      int* total_loaded_blobs) {\n    CAFFE_ENFORCE(cursor);\n    int loaded_blobs = 0;\n    for (; cursor->Valid(); cursor->Next()) {\n      const auto key = buildBlobNameFromDbKey(cursor->key());\n      if (!output_indices_.count(key)) {\n        VLOG(1) << \"Key \" << key << \" not used. Skipping.\";\n      } else {\n        if (key_to_dbid_.count(key) && key_to_dbid_[key] != db_id) {\n          CAFFE_THROW(\"Duplicate Key \", key, \" is found!\\n\");\n        } else {\n          key_to_dbid_[key] = db_id;\n        }\n\n        VLOG(2) << \"Deserializing blob \" << key;\n        BlobProto proto;\n        CAFFE_ENFORCE(proto.ParseFromString(cursor->value()));\n        if (!keep_device_) {\n          // If we are not keeping the device as the one specified in the\n          // proto, we will set the current device.\n          SetCurrentDevice(&proto);\n        }\n        auto blobIndex = output_indices_[key];\n        Blob* blob = outputs.at(blobIndex);\n        ProcessBlob(blob, proto, blob_states, key, &loaded_blobs);\n\n        if (*total_loaded_blobs + loaded_blobs == OutputSize()) {\n          break;\n        }\n      }\n    }\n\n    *total_loaded_blobs += loaded_blobs;\n  }\n\n  string buildBlobNameFromDbKey(const string& dbKey) {\n    string key = dbKey.substr(0, dbKey.find(kChunkIdSeparator));\n    if (!strip_prefix_.empty()) {\n      auto match_pos = key.find(strip_prefix_);\n      if (match_pos != string::npos) {\n        key = key.substr(match_pos + strip_prefix_.size());\n      }\n    }\n    key = add_prefix_ + key;\n    return key;\n  }\n\n private:\n  // We are tracking sizes of already read tensor parts while reading data\n  // chunks. This way we can make sure that all chunks were loaded in the end.\n  void ProcessBlob(\n      Blob* blob,\n      const BlobProto& proto,\n      std::unordered_map<string, BlobState>* blob_states_ptr,\n      const string& key,\n      int* loaded_blobs) {\n    auto& blob_states = *blob_states_ptr;\n    if (blob_states.count(key) == 0) {\n      // We reset the blob so that any existing content is destroyed. This\n      // is to guaranee correct device placement: if we are deserializing\n      // into a TensorCUDA, without explicit Reset we might be loading data\n      // into an existing TensorCUDA that has pre-allocated memory on a\n      // different GPU.\n      blob->Reset();\n    }\n    blob->Deserialize(proto);\n    if (proto.has_content_num_chunks()) {\n      if (!blob_states.count(key)) {\n        blob_states[key] = BlobState(proto.content_num_chunks());\n      }\n      CAFFE_ENFORCE(\n          blob_states[key]\n              .seen_chunks_ids.insert(proto.content_chunk_id())\n              .second,\n          \"Chunk with the same id has occured twice for: \",\n          key);\n      CAFFE_ENFORCE(\n          proto.content_chunk_id() >= 0 &&\n              proto.content_chunk_id() < blob_states[key].total_size,\n          \"Chunk id has to be not less than 0 and \"\n          \"less than content_num_chunks for key: \",\n          key);\n      blob_states[key].current_size++;\n      CAFFE_ENFORCE(\n          !blob_states[key].is_tensor,\n          \"Proto with content_chunks can not store tensor: \",\n          key);\n      CAFFE_ENFORCE(\n          blob_states[key].current_size <= blob_states[key].total_size,\n          \"Found an extra part for an already filled blob: \",\n          key);\n      if (blob_states[key].current_size == blob_states[key].total_size) {\n        (*loaded_blobs)++;\n      }\n      return;\n    }\n    if (!proto.has_tensor()) {\n      // If blob is divided into chunks the field content_chunks has to be set,\n      // otherwise only tensors can be seen multiple times as chunks.\n      CAFFE_ENFORCE(blob_states.count(key) == 0, \"Blob duplicated: \", key);\n      blob_states[key] = BlobState();\n      (*loaded_blobs)++;\n      return;\n    }\n    CAFFE_ENFORCE(proto.has_tensor());\n    if (blob_states.count(key)) {\n      CAFFE_ENFORCE(blob_states[key].is_tensor, \"Must be tensor \", key);\n      CAFFE_ENFORCE(\n          blob_states[key].current_size < blob_states[key].total_size,\n          \"Found an extra part for an already filled tensor: \",\n          key);\n      CAFFE_ENFORCE(\n          proto.tensor().has_segment(),\n          \"Partial tensor must have a segment: \",\n          key);\n      blob_states[key].current_size +=\n          proto.tensor().segment().end() - proto.tensor().segment().begin();\n      CAFFE_ENFORCE(\n          blob_states[key].current_size <= blob_states[key].total_size,\n          \"Tensor parts are bigger than target size for tensor: \",\n          key);\n    } else {\n      const auto& dims = proto.tensor().dims();\n      int64_t total_size = 1;\n      for (const auto& dim : dims) {\n        total_size *= dim;\n      }\n      auto current_size = total_size;\n      if (proto.tensor().has_segment()) {\n        current_size =\n            proto.tensor().segment().end() - proto.tensor().segment().begin();\n      }\n      blob_states[key] =\n          BlobState(total_size, current_size, true /* is_tensor */);\n    }\n\n    if (blob_states[key].current_size == blob_states[key].total_size) {\n      (*loaded_blobs)++;\n    }\n  }\n\n  void validateBlobStates(\n      const std::unordered_map<string, BlobState>& blob_states) {\n    for (const auto& iter : blob_states) {\n      const BlobState& blob_state = iter.second;\n      CAFFE_ENFORCE(\n          blob_state.current_size == blob_state.total_size,\n          \"Data size mismatch for blob \",\n          iter.first,\n          \". Expected: \",\n          blob_state.total_size,\n          \" Read: \",\n          blob_state.current_size);\n    }\n  }\n\n  Workspace* ws_;\n  bool absolute_path_;\n  string add_prefix_;\n  string strip_prefix_;\n  string db_name_;\n  std::vector<std::string> db_names_;\n  string db_type_;\n  bool keep_device_;\n  bool load_all_;\n  bool allow_incomplete_;\n  std::map<string, int> output_indices_;\n  std::map<string, int> key_to_dbid_;\n  std::vector<std::string> blob_names_;\n};\n\ntemplate <class Context>\nclass SaveOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SaveOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        ws_(ws),\n        absolute_path_(\n            OperatorBase::GetSingleArgument<int>(\"absolute_path\", false)),\n        strip_prefix_(\n            OperatorBase::GetSingleArgument<string>(\"strip_prefix\", \"\")),\n        db_name_(OperatorBase::GetSingleArgument<string>(\"db\", \"\")),\n        db_type_(OperatorBase::GetSingleArgument<string>(\"db_type\", \"\")),\n        blob_names_(\n            OperatorBase::GetRepeatedArgument<string>(\"blob_name_overrides\")) {\n    CAFFE_ENFORCE_GT(db_name_.size(), 0, \"Must specify a db name.\");\n    CAFFE_ENFORCE_GT(db_type_.size(), 0, \"Must specify a db type.\");\n    CAFFE_ENFORCE(\n        blob_names_.empty() ||\n            blob_names_.size() == OperatorBase::Inputs().size(),\n        \"Number of blobs and blob_name_overrides mismatch.\");\n    CAFFE_ENFORCE(\n        blob_names_.empty() || strip_prefix_.empty(),\n        \"strip_prefix and blob_name_overrides are mutually exclusive.\");\n\n    if (blob_names_.empty()) {\n      std::set<std::string> input_names;\n      blob_names_.resize(OperatorBase::Inputs().size());\n      for (int i = 0; i < blob_names_.size(); ++i) {\n        std::string name;\n        if (strip_prefix_.empty()) {\n          name = operator_def.input(i);\n        } else {\n          auto match_pos = operator_def.input(i).find(strip_prefix_);\n          if (match_pos == string::npos) {\n            name = operator_def.input(i);\n          } else {\n            name = operator_def.input(i).substr(\n                match_pos + strip_prefix_.size(), string::npos);\n          }\n        }\n        CAFFE_ENFORCE(\n            input_names.insert(name).second, \"Duplicated input: \", name);\n        blob_names_[i] = name;\n      }\n    }\n  }\n\n  bool RunOnDevice() override {\n    string full_db_name =\n        absolute_path_ ? db_name_ : (ws_->RootFolder() + \"/\" + db_name_);\n    std::unique_ptr<DB> out_db(\n        caffe2::db::CreateDB(db_type_, full_db_name, caffe2::db::NEW));\n    CAFFE_ENFORCE(out_db.get(), \"Cannot open db for writing: \", full_db_name);\n\n    BlobSerializerBase::SerializationAcceptor acceptor = [&](\n        const std::string& blobName, const std::string& data) {\n      // transaction should take care of locking\n      VLOG(2) << \"Sending \" << blobName << \" blob's data of size \"\n              << data.size() << \" to db\";\n      auto transaction = out_db->NewTransaction();\n      transaction->Put(blobName, data);\n      transaction->Commit();\n    };\n\n    const vector<const Blob*>& inputs = OperatorBase::Inputs();\n    for (int i = 0; i < inputs.size(); ++i) {\n      inputs[i]->Serialize(blob_names_[i], acceptor);\n    }\n    out_db->Close();\n    return true;\n  }\n\n private:\n  Workspace* ws_;\n  bool absolute_path_;\n  string strip_prefix_;\n  string db_name_;\n  string db_type_;\n  std::vector<std::string> blob_names_;\n};\n\ntemplate <typename... Ts>\nstring FormatString(const string& pattern, Ts... values) {\n  // Note(Yangqing): We believe that 1024 is enough, but who are we to assert\n  // that?\n  // As a result, if things go wrong, we'll just throw the towel and quit loud.\n  // Yeah, I know that there is snprintf, but it is not present in *some*\n  // platforms unfortunately.\n  char buffer[1024];\n  int written = sprintf(buffer, pattern.c_str(), values...);\n  if (written < 0 || written + 1 > 1024) {\n    LOG(FATAL) << \"FormatString fails: total bytes written \" << written;\n  }\n  return string(buffer);\n  /*\n   * The following is the snprintf version that is safe; enable it one day?\n  unsigned int required =\n      std::snprintf(nullptr, 0, pattern.c_str(), values...) + 1;\n  char bytes[required];\n  std::snprintf(bytes, required, pattern.c_str(), values...);\n  return string(bytes);\n  */\n}\n\n// CheckpointOp is a wrapper over a SaveFloatTensorOp that basically allows\n// flexible naming over iterations.\n// The file pattern in db_name should be a format string that can be passed into\n// sprintf with an int argument specifying the current iteration. An example:\n//     \"/path/to/my/checkpoint/checkpoint_at_%d.pb\"\ntemplate <class Context>\nclass CheckpointOp final : public Operator<Context> {\n public:\n  CheckpointOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        db_pattern_(OperatorBase::GetSingleArgument<string>(\"db\", \"\")),\n        every_(OperatorBase::GetSingleArgument<int>(\"every\", 1)),\n        ws_(ws),\n        save_op_def_(operator_def) {\n    CAFFE_ENFORCE_GT(\n        db_pattern_.size(), 0, \"Must specify a checkpoint file pattern.\");\n    CAFFE_ENFORCE_GT(every_, 0, \"Checkpoint interval should be positive.\");\n    if (every_ == 1) {\n      // Just issue a warning, but it's totally legal so we don't do anything.\n      LOG(WARNING) << \"It seems that we are checkpointting every iteration. \"\n                   << \"Is that intended?\";\n    }\n    save_op_def_.set_type(\"Save\");\n  }\n\n  bool RunOnDevice() override {\n    int64_t iter =\n        OperatorBase::Input<TensorCPU>(0).template data<int64_t>()[0];\n    if (iter % every_ == 0) {\n      GetMutableArgument(\"db\", true, &save_op_def_)\n          ->set_s(FormatString(db_pattern_, iter));\n      SaveOp<Context> sub_op(save_op_def_, ws_);\n      return sub_op.Run();\n    } else {\n      return true;\n    }\n  }\n\n private:\n  string db_pattern_;\n  int every_;\n  Workspace* ws_;\n  OperatorDef save_op_def_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_LOAD_SAVE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/load_save_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/load_save_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nvoid LoadOp<CUDAContext>::SetCurrentDevice(BlobProto* proto) {\n  if (proto->has_tensor()) {\n    auto* device_detail = proto->mutable_tensor()->mutable_device_detail();\n    device_detail->set_device_type(CUDA);\n    device_detail->set_cuda_gpu_id(CaffeCudaGetDevice());\n  }\n}\n\nREGISTER_CUDA_OPERATOR(Load, LoadOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(Save, SaveOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(Checkpoint, CheckpointOp<CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/local_response_normalization_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/local_response_normalization_op.h\"\n\nnamespace caffe2 {\n\ntemplate<>\nbool LRNOp<float, CPUContext>::RunOnDeviceWithOrderNCHW() {\n  // Note(Yangqing): this one is copied from my Caffe implementation.\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  DCHECK_EQ(X.ndim(), 4);\n  const int N = X.dim32(0);\n  const int C = X.dim32(1);\n  const int H = X.dim32(2);\n  const int W = X.dim32(3);\n  const int image_size = C * H * W;\n  const float* Xdata = X.data<float>();\n  Y->ResizeLike(X);\n  float* Ydata = Y->mutable_data<float>();\n\n  if (OutputSize() > 1) {\n    scale_ = Output(1);\n  } else {\n    if (!scale_) {\n      scale_ = &local_scale_tensor_;\n    }\n  }\n  scale_->ResizeLike(X);\n  float* scale_data = scale_->mutable_data<float>();\n  math::Set<float, CPUContext>(X.size(), bias_, scale_data, &context_);\n  TensorCPU padded_square(\n      vector<TIndex>{C + size_ - 1, H, W});\n  float* padded_square_data = padded_square.mutable_data<float>();\n  math::Set<float, CPUContext>(padded_square.size(), 0., padded_square_data,\n                               &context_);\n  const float alpha_over_size = alpha_ / size_;\n  // go through the images\n  for (int n = 0; n < N; ++n) {\n    // compute the padded square\n    math::Sqr<float, CPUContext>(image_size, Xdata + image_size * n,\n                                 padded_square_data + pre_pad_ * H * W,\n                                 &context_);\n    // Create the first channel scale\n    for (int c = 0; c < size_; ++c) {\n      math::Axpy<float, CPUContext>(\n          H * W, alpha_over_size, padded_square_data + c * H * W,\n          scale_data + image_size * n, &context_);\n    }\n    for (int c = 1; c < C; ++c) {\n      float* this_scale_slice = scale_data + n * image_size + c * H * W;\n      // copy previous scale\n      context_.Copy<float, CPUContext, CPUContext>(\n          H * W, this_scale_slice - H * W, this_scale_slice);\n      // add head\n      math::Axpy<float, CPUContext>(\n          H * W, alpha_over_size, padded_square_data + (c + size_ - 1) * H * W,\n          this_scale_slice, &context_);\n      // subtract tail\n      math::Axpy<float, CPUContext>(\n          H * W, -alpha_over_size, padded_square_data + (c - 1) * H * W,\n          this_scale_slice, &context_);\n    }\n  }\n  math::Powx<float, CPUContext>(\n      X.size(), scale_data, -beta_, Ydata, &context_);\n  math::Mul<float, CPUContext>(X.size(), Ydata, Xdata, Ydata, &context_);\n  return true;\n}\n\ntemplate<>\nbool LRNOp<float, CPUContext>::RunOnDeviceWithOrderNHWC() {\n  // Note(Yangqing): This one is copied from my Decaf implementation. How many\n  // variants have I written...?\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  DCHECK_EQ(X.ndim(), 4);\n  const int N = X.dim32(0);\n  const int H = X.dim32(1);\n  const int W = X.dim32(2);\n  const int C = X.dim32(3);\n  const int num_rows = N * H * W;\n  const float* Xdata = X.data<float>();\n  Y->ResizeLike(X);\n  float* Ydata = Y->mutable_data<float>();\n\n  if (OutputSize() > 1) {\n    scale_ = Output(1);\n  } else {\n    if (!scale_) {\n      scale_ = &local_scale_tensor_;\n    }\n  }\n  scale_->ResizeLike(X);\n  float* scale_data = scale_->mutable_data<float>();\n\n  TensorCPU padded_square(vector<TIndex>(1, C + size_ - 1));\n  float* padded_square_data = padded_square.mutable_data<float>();\n  math::Set<float, CPUContext>(padded_square.size(), 0., padded_square_data,\n                               &context_);\n  const float alpha_over_size = alpha_ / size_;\n\n  for (int n = 0; n < num_rows; ++n) {\n    for (int c = 0; c < C; ++c) {\n      padded_square_data[c + pre_pad_] =\n          Xdata[n * C + c] * Xdata[n * C + c] * alpha_over_size;\n    }\n    float accum_scale = 0.;\n    for (int i = 0; i < size_ - 1; ++i) {\n      accum_scale += padded_square_data[i];\n    }\n    for (int c = 0; c < C; ++c) {\n      accum_scale += padded_square_data[c + size_ - 1];\n      scale_data[n * C + c] = bias_ + accum_scale;\n      accum_scale -= padded_square_data[c];\n    }\n  }\n  math::Powx<float, CPUContext>(\n      X.size(), scale_data, -beta_, Ydata, &context_);\n  math::Mul<float, CPUContext>(X.size(), Ydata, Xdata, Ydata, &context_);\n  return true;\n}\n\ntemplate <>\nbool LRNGradientOp<float, CPUContext>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dY = Input(2);\n  auto* dX = Output(0);\n  DCHECK_EQ(X.ndim(), 4);\n  const int N = X.dim32(0);\n  const int C = X.dim32(1);\n  const int H = X.dim32(2);\n  const int W = X.dim32(3);\n  const int image_size = C * H * W;\n  // Loosely checking the size, assuming that the shapes will be the same as\n  // long as the sizes check out.\n  DCHECK_EQ(X.size(), Y.size());\n  DCHECK_EQ(X.size(), dY.size());\n  dX->ResizeLike(X);\n\n  const float* Xdata = X.data<float>();\n  const float* Ydata = Y.data<float>();\n  if (!scale_) {\n    scale_ = &local_scale_tensor_;\n  }\n  scale_->ResizeLike(X);\n  float* scale_data = scale_->mutable_data<float>();\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n\n  TensorCPU padded_ratio(\n      vector<TIndex>{C + size_ - 1, H, W});\n  float* padded_ratio_data = padded_ratio.mutable_data<float>();\n  // Compute scale(copied from LRNOp) - reusing padded_ratio\n  math::Set<float, CPUContext>(X.size(), bias_, scale_data, &context_);\n  math::Set<float, CPUContext>(padded_ratio.size(), 0., padded_ratio_data,\n                               &context_);\n  const float alpha_over_size = alpha_ / size_;\n  // go through the images\n  for (int n = 0; n < N; ++n) {\n    // compute the padded square\n    math::Sqr<float, CPUContext>(image_size, Xdata + image_size * n,\n                                 padded_ratio_data + pre_pad_ * H * W,\n                                 &context_);\n    // Create the first channel scale\n    for (int c = 0; c < size_; ++c) {\n      math::Axpy<float, CPUContext>(\n          H * W, alpha_over_size, padded_ratio_data + c * H * W,\n          scale_data + image_size * n, &context_);\n    }\n    for (int c = 1; c < C; ++c) {\n      float* this_scale_slice = scale_data + n * image_size + c * H * W;\n      // copy previous scale\n      context_.Copy<float, CPUContext, CPUContext>(\n          H * W, this_scale_slice - H * W, this_scale_slice);\n      // add head\n      math::Axpy<float, CPUContext>(\n          H * W, alpha_over_size, padded_ratio_data + (c + size_ - 1) * H * W,\n          this_scale_slice, &context_);\n      // subtract tail\n      math::Axpy<float, CPUContext>(\n          H * W, -alpha_over_size, padded_ratio_data + (c - 1) * H * W,\n          this_scale_slice, &context_);\n    }\n  }\n\n  math::Set<float, CPUContext>(padded_ratio.size(), 0., padded_ratio_data,\n                               &context_);\n  TensorCPU accum_ratio(vector<TIndex>{H, W});\n  float* accum_ratio_data = accum_ratio.mutable_data<float>();\n\n\n  const float cache_ratio = 2. * alpha_ * beta_ / size_;\n  const int inverse_pre_pad = size_ - (size_ + 1) / 2;\n\n  int offset = 0;\n  for (int n = 0; n < N; ++n) {\n    // first, compute diff_i * y_i / s_i\n    math::Mul<float, CPUContext>(\n        image_size, dYdata + offset, Ydata + offset,\n        padded_ratio_data + inverse_pre_pad * H * W, &context_);\n    math::Div<float, CPUContext>(\n        image_size, padded_ratio_data + inverse_pre_pad * H * W,\n        scale_data + offset,\n        padded_ratio_data + inverse_pre_pad * H * W, &context_);\n    // Now, compute the accumulated ratios and the bottom diff\n    math::Set<float, CPUContext>(accum_ratio.size(), 0., accum_ratio_data,\n                                 &context_);\n    for (int c = 0; c < size_ - 1; ++c) {\n      math::Axpy<float, CPUContext>(H * W, 1,\n                                    padded_ratio_data + c * H * W,\n                                    accum_ratio_data, &context_);\n    }\n    for (int c = 0; c < C; ++c) {\n      for (int hw = 0; hw < H * W; ++hw) {\n        accum_ratio_data[hw] += padded_ratio_data[(c + size_ - 1) * H * W + hw];\n        dXdata[offset] =\n            dYdata[offset] * pow(scale_data[offset], -beta_) -\n            cache_ratio * accum_ratio_data[hw] * Xdata[offset];\n        accum_ratio_data[hw] -= padded_ratio_data[c * H * W + hw];\n        ++offset;\n      }\n    }\n  }\n  return true;\n}\n\ntemplate <>\nbool LRNGradientOp<float, CPUContext>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dY = Input(2);\n  auto* dX = Output(0);\n  DCHECK_EQ(X.ndim(), 4);\n  const int N = X.dim32(0);\n  const int H = X.dim32(1);\n  const int W = X.dim32(2);\n  const int C = X.dim32(3);\n  const int num_rows = N * H * W;\n  const float* Xdata = X.data<float>();\n  // Loosely checking the size, assuming that the shapes will be the same as\n  // long as the sizes check out.\n  DCHECK_EQ(X.size(), Y.size());\n  DCHECK_EQ(X.size(), dY.size());\n  dX->ResizeLike(X);\n  if (!scale_) {\n    scale_ = &local_scale_tensor_;\n  }\n  scale_->ResizeLike(X);\n  TensorCPU padded_ratio(vector<TIndex>(1, C + size_ - 1));\n  float* padded_ratio_data = padded_ratio.mutable_data<float>();\n  float* scale_data = scale_->mutable_data<float>();\n  // Compute scale(copied from LRNOp) - reusing padded_ratio\n  math::Set<float, CPUContext>(X.size(), bias_, scale_data, &context_);\n  math::Set<float, CPUContext>(padded_ratio.size(), 0., padded_ratio_data,\n                               &context_);\n  const float alpha_over_size = alpha_ / size_;\n\n  for (int n = 0; n < num_rows; ++n) {\n    for (int c = 0; c < C; ++c) {\n      padded_ratio_data[c + pre_pad_] =\n          Xdata[n * C + c] * Xdata[n * C + c] * alpha_over_size;\n    }\n    float accum_scale = 0.;\n    for (int i = 0; i < size_ - 1; ++i) {\n      accum_scale += padded_ratio_data[i];\n    }\n    for (int c = 0; c < C; ++c) {\n      accum_scale += padded_ratio_data[c + size_ - 1];\n      scale_data[n * C + c] = bias_ + accum_scale;\n      accum_scale -= padded_ratio_data[c];\n    }\n  }\n\n  math::Set<float, CPUContext>(padded_ratio.size(), 0., padded_ratio_data,\n                               &context_);\n  // the ratio 2*alpha*beta/size\n  const float cache_ratio = 2. * alpha_ * beta_ / size_;\n  const float* Ydata = Y.data<float>();\n\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  for (int n = 0; n < num_rows; ++n) {\n    const int offset = n * C;\n    for (int c = 0; c < C; ++c) {\n      padded_ratio_data[c + pre_pad_] =\n          Ydata[offset + c] * dYdata[offset + c] / scale_data[offset + c];\n    }\n    float accum_ratio = 0.;\n    for (int c = 0; c < size_ - 1; ++c) {\n      accum_ratio += padded_ratio_data[c];\n    }\n    for (int c = 0; c < C; ++c) {\n      accum_ratio += padded_ratio_data[c + size_ - 1];\n      dXdata[offset + c] =\n          dYdata[offset + c] * pow(scale_data[offset + c], -beta_) -\n          cache_ratio * Xdata[offset + c] * accum_ratio;\n      accum_ratio -= padded_ratio_data[c];\n    }\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(LRN, LRNOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(LRNGradient, LRNGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LRN).NumInputs(1).NumOutputs(1,2);\nOPERATOR_SCHEMA(LRNGradient).NumInputs(3).NumOutputs(1);\n\nclass GetLRNGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n      \"LRNGradient\", \"\",\n      vector<string>{I(0), O(0), GO(0)},\n      vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(LRN, GetLRNGradient);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/local_response_normalization_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/local_response_normalization_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\ntemplate <typename T>\n__global__ void LRNFillScaleNCHW(const int nthreads, const T* in,\n    const int num, const int channels, const int height,\n    const int width, const int size, const T alpha_over_size,\n    const T bias, T* scale) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local offset\n    int w = index % width;\n    int h = (index / width) % height;\n    int n = index / width / height;\n    int offset = (n * channels * height + h) * width + w;\n    int step = height * width;\n    in += offset;\n    scale += offset;\n    int head = 0;\n    int pre_pad = (size - 1) / 2;\n    int post_pad = size - pre_pad - 1;\n    T accum_scale = 0;\n    // fill the scale at [n, :, h, w]\n    // accumulate values\n    while (head < post_pad) {\n      accum_scale += in[head * step] * in[head * step];\n      ++head;\n    }\n    // until we reach size, nothing needs to be subtracted\n    while (head < size) {\n      accum_scale += in[head * step] * in[head * step];\n      scale[(head - post_pad) * step] = bias + accum_scale * alpha_over_size;\n      ++head;\n    }\n    // both add and subtract\n    while (head < channels) {\n      accum_scale += in[head * step] * in[head * step];\n      accum_scale -= in[(head - size) * step] * in[(head - size) * step];\n      scale[(head - post_pad) * step] = bias + accum_scale * alpha_over_size;\n      ++head;\n    }\n    // subtract only\n    while (head < channels + post_pad) {\n      accum_scale -= in[(head - size) * step] * in[(head - size) * step];\n      scale[(head - post_pad) * step] = bias + accum_scale * alpha_over_size;\n      ++head;\n    }\n    // recover the pointers for the next loop.\n    in -= offset;\n    scale -= offset;\n  }\n}\n\ntemplate <typename T>\n__global__ void LRNFillScaleNHWC(const int nthreads, const T* in,\n    const int num, const int height, const int width,\n    const int channels, const int size, const T alpha_over_size,\n    const T bias, T* scale) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int c = index % channels;\n    int pre_pad = (size - 1) / 2;\n    scale[index] = 0;\n    for (int i = 0; i < size; ++i) {\n      int raw_idx = c + i - pre_pad;\n      if (raw_idx >= 0 && raw_idx < channels) {\n        scale[index] += in[index + i - pre_pad] * in[index + i - pre_pad];\n      }\n    }\n    scale[index] = bias + scale[index] * alpha_over_size;\n  }\n}\n\n// TODO(Yangqing): check if it would be faster to just put it into the previous\n// kernel.\ntemplate <typename T>\n__global__ void LRNComputeOutput(const int nthreads, const T* in,\n    const T* scale, const T negative_beta, T* out) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    out[index] = in[index] * pow(scale[index], negative_beta);\n  }\n}\n\ntemplate <typename T>\n__global__ void LRNComputeDiffNCHW(const int nthreads, const T* bottom_data,\n    const T* top_data, const T* scale, const T* top_diff,\n    const int num, const int channels, const int height,\n    const int width, const int size, const T negative_beta,\n    const T cache_ratio,\n    T* bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local offset\n    int w = index % width;\n    int h = (index / width) % height;\n    int n = index / width / height;\n    int offset = (n * channels * height + h) * width + w;\n    int step = height * width;\n    bottom_data += offset;\n    top_data += offset;\n    scale += offset;\n    top_diff += offset;\n    bottom_diff += offset;\n    int head = 0;\n    int pre_pad = size - (size + 1) / 2;\n    int post_pad = size - pre_pad - 1;\n    T accum_ratio = 0;\n    // accumulate values\n    while (head < post_pad) {\n      accum_ratio += top_diff[head * step] * top_data[head * step] /\n          scale[head * step];\n      ++head;\n    }\n    // until we reach size, nothing needs to be subtracted\n    while (head < size) {\n      accum_ratio += top_diff[head * step] * top_data[head * step] /\n          scale[head * step];\n      bottom_diff[(head - post_pad) * step] = top_diff[(head - post_pad) * step]\n          * pow(scale[(head - post_pad) * step], negative_beta) - cache_ratio *\n          bottom_data[(head - post_pad) * step] * accum_ratio;\n      ++head;\n    }\n    // both add and subtract\n    while (head < channels) {\n      accum_ratio += top_diff[head * step] * top_data[head * step] /\n          scale[head * step];\n      accum_ratio -= top_diff[(head - size) * step] *\n          top_data[(head - size) * step] / scale[(head - size) * step];\n      bottom_diff[(head - post_pad) * step] = top_diff[(head - post_pad) * step]\n          * pow(scale[(head - post_pad) * step], negative_beta) - cache_ratio *\n          bottom_data[(head - post_pad) * step] * accum_ratio;\n      ++head;\n    }\n    // subtract only\n    while (head < channels + post_pad) {\n      accum_ratio -= top_diff[(head - size) * step] *\n          top_data[(head - size) * step] / scale[(head - size) * step];\n      bottom_diff[(head - post_pad) * step] = top_diff[(head - post_pad) * step]\n          * pow(scale[(head - post_pad) * step], negative_beta) - cache_ratio *\n          bottom_data[(head - post_pad) * step] * accum_ratio;\n      ++head;\n    }\n    // recover pointer for next iteration.\n    bottom_data -= offset;\n    top_data -= offset;\n    scale -= offset;\n    top_diff -= offset;\n    bottom_diff -= offset;\n  }\n}\n\n// This local response normalization gradient does one sum per output location\n// and does not use the running trick for 1-d convolution: thus it might not be\n// the fastest implementation.\ntemplate <typename T>\n__global__ void LRNComputeDiffNHWC(const int nthreads, const T* bottom_data,\n    const T* top_data, const T* scale, const T* top_diff,\n    const int num, const int height, const int width, const int channels,\n    const int size, const T negative_beta, const T cache_ratio,\n    T* bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local channel offset\n    int c = index % channels;\n    int pre_pad = size / 2;\n    T accum_ratio = 0;\n    for (int i = -pre_pad; i < size - pre_pad; ++i) {\n      if (c + i >= 0 && c + i < channels) {\n        accum_ratio += top_diff[index + i] * top_data[index + i] /\n            scale[index + i];\n      }\n    }\n    bottom_diff[index] = top_diff[index] * pow(scale[index], negative_beta) -\n                         cache_ratio * bottom_data[index] * accum_ratio;\n  }\n}\n}  // namespace\n\ntemplate<>\nbool LRNOp<float, CUDAContext>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  DCHECK_EQ(X.ndim(), 4);\n  const int N = X.dim32(0);\n  const int C = X.dim32(1);\n  const int H = X.dim32(2);\n  const int W = X.dim32(3);\n  const float* Xdata = X.data<float>();\n  Y->ResizeLike(X);\n  float* Ydata = Y->mutable_data<float>();\n  if (OutputSize() > 1) {\n    scale_ = Output(1);\n  } else {\n    if (!scale_) {\n      scale_ = &local_scale_tensor_;\n    }\n  }\n  scale_->ResizeLike(X);\n  float* scale_data = scale_->mutable_data<float>();\n\n  int n_threads = N * H * W;\n  LRNFillScaleNCHW<float><<<CAFFE_GET_BLOCKS(n_threads), CAFFE_CUDA_NUM_THREADS,\n                        0, context_.cuda_stream()>>>(\n      n_threads, Xdata, N, C, H, W, size_, alpha_ / size_, bias_, scale_data);\n  n_threads = X.size();\n  LRNComputeOutput<float><<<CAFFE_GET_BLOCKS(n_threads), CAFFE_CUDA_NUM_THREADS,\n                            0, context_.cuda_stream()>>>(\n      n_threads, Xdata, scale_data, -beta_, Ydata);\n  return true;\n}\n\ntemplate<>\nbool LRNOp<float, CUDAContext>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  DCHECK_EQ(X.ndim(), 4);\n  const int N = X.dim32(0);\n  const int H = X.dim32(1);\n  const int W = X.dim32(2);\n  const int C = X.dim32(3);\n  const float* Xdata = X.data<float>();\n  Y->ResizeLike(X);\n  float* Ydata = Y->mutable_data<float>();\n  if (OutputSize() > 1) {\n    scale_ = Output(1);\n  } else {\n    if (!scale_) {\n      scale_ = &local_scale_tensor_;\n    }\n  }\n  scale_->ResizeLike(X);\n  float* scale_data = scale_->mutable_data<float>();\n\n  int n_threads = X.size();\n  LRNFillScaleNHWC<float><<<CAFFE_GET_BLOCKS(n_threads), CAFFE_CUDA_NUM_THREADS,\n                        0, context_.cuda_stream()>>>(\n      n_threads, Xdata, N, H, W, C, size_, alpha_ / size_, bias_, scale_data);\n  LRNComputeOutput<float><<<CAFFE_GET_BLOCKS(n_threads), CAFFE_CUDA_NUM_THREADS,\n                            0, context_.cuda_stream()>>>(\n      n_threads, Xdata, scale_data, -beta_, Ydata);\n  return true;\n}\n\ntemplate <>\nbool LRNGradientOp<float, CUDAContext>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dY = Input(2);\n  auto* dX = Output(0);\n  DCHECK_EQ(X.ndim(), 4);\n  const int N = X.dim32(0);\n  const int C = X.dim32(1);\n  const int H = X.dim32(2);\n  const int W = X.dim32(3);\n  // Loosely checking the size, assuming that the shapes will be the same as\n  // long as the sizes check out.\n  DCHECK_EQ(X.size(), Y.size());\n  DCHECK_EQ(X.size(), dY.size());\n  dX->ResizeLike(X);\n\n  const float* Xdata = X.data<float>();\n  const float* Ydata = Y.data<float>();\n  if (!scale_) {\n    scale_ = &local_scale_tensor_;\n  }\n  scale_->ResizeLike(X);\n  float* scale_data = scale_->mutable_data<float>();\n  int n_threads = N * H * W;\n  LRNFillScaleNCHW<float><<<CAFFE_GET_BLOCKS(n_threads), CAFFE_CUDA_NUM_THREADS,\n                        0, context_.cuda_stream()>>>(\n      n_threads, Xdata, N, C, H, W, size_, alpha_ / size_, bias_, scale_data);\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n\n  LRNComputeDiffNCHW<float><<<CAFFE_GET_BLOCKS(n_threads),\n                              CAFFE_CUDA_NUM_THREADS,\n                              0, context_.cuda_stream()>>>(\n      n_threads, Xdata, Ydata, scale_data, dYdata, N, C, H, W, size_, -beta_,\n      2.f * alpha_ * beta_ / size_, dXdata);\n  return true;\n}\n\ntemplate <>\nbool LRNGradientOp<float, CUDAContext>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dY = Input(2);\n  auto* dX = Output(0);\n  DCHECK_EQ(X.ndim(), 4);\n  const int N = X.dim32(0);\n  const int H = X.dim32(1);\n  const int W = X.dim32(2);\n  const int C = X.dim32(3);\n  const float* Xdata = X.data<float>();\n  // Loosely checking the size, assuming that the shapes will be the same as\n  // long as the sizes check out.\n  DCHECK_EQ(X.size(), Y.size());\n  DCHECK_EQ(X.size(), dY.size());\n  dX->ResizeLike(X);\n  if (!scale_) {\n    scale_ = &local_scale_tensor_;\n  }\n  scale_->ResizeLike(X);\n\n  float* scale_data = scale_->mutable_data<float>();\n  int n_threads = X.size();\n  LRNFillScaleNHWC<float><<<CAFFE_GET_BLOCKS(n_threads), CAFFE_CUDA_NUM_THREADS,\n                        0, context_.cuda_stream()>>>(\n      n_threads, Xdata, N, H, W, C, size_, alpha_ / size_, bias_, scale_data);\n\n  LRNComputeDiffNHWC<float><<<CAFFE_GET_BLOCKS(X.size()),\n                              CAFFE_CUDA_NUM_THREADS, 0,\n                              context_.cuda_stream()>>>(\n      X.size(), X.data<float>(), Y.data<float>(), scale_data,\n      dY.data<float>(),\n      X.dim32(0), X.dim32(1), X.dim32(2), X.dim32(3), size_, -beta_,\n      2.f * alpha_ * beta_ / size_, dX->mutable_data<float>());\n  return true;\n}\n\n\nREGISTER_CUDA_OPERATOR(LRN, LRNOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(LRNGradient, LRNGradientOp<float, CUDAContext>);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/local_response_normalization_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_LOCAL_RESPONSE_NORMALIZATION_OP_H_\n#define CAFFE2_OPERATORS_LOCAL_RESPONSE_NORMALIZATION_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass LRNOpBase : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  LRNOpBase(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        size_(OperatorBase::GetSingleArgument<int>(\"size\", 0)),\n        alpha_(OperatorBase::GetSingleArgument<float>(\"alpha\", 0)),\n        beta_(OperatorBase::GetSingleArgument<float>(\"beta\", 0)),\n        bias_(OperatorBase::GetSingleArgument<float>(\"bias\", 1)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))),\n        pre_pad_((size_ - 1) / 2) {\n    DCHECK_GT(size_, 0);\n    DCHECK_EQ(size_ % 2, 1);\n    DCHECK_GT(alpha_, 0);\n    DCHECK_GT(beta_, 0);\n  }\n\n  bool RunOnDevice() override {\n    switch (order_) {\n      case StorageOrder::NHWC:\n        return RunOnDeviceWithOrderNHWC();\n      case StorageOrder::NCHW:\n        return RunOnDeviceWithOrderNCHW();\n      default:\n        LOG(FATAL) << \"Unknown storage order: \" << order_;\n    }\n    // To suppress old compiler warnings\n    return true;\n  }\n\n  virtual bool RunOnDeviceWithOrderNCHW() = 0;\n  virtual bool RunOnDeviceWithOrderNHWC() = 0;\n\n protected:\n  const int size_;\n  const float alpha_;\n  const float beta_;\n  const float bias_;\n  const StorageOrder order_;\n  const int pre_pad_;\n  // Input: X; Output: Y, scale.\n};\n\ntemplate <typename T, class Context>\nclass LRNOp final : public LRNOpBase<T, Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  LRNOp(const OperatorDef& operator_def, Workspace* ws)\n      : LRNOpBase<T, Context>(operator_def, ws) {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n protected:\n  // Input: X; Output: Y, scale.\n  OUTPUT_TAGS(OUTPUT, SCALE);\n  Tensor<Context>* scale_ = nullptr;\n  Tensor<Context> local_scale_tensor_;\n};\n\ntemplate <typename T, class Context>\nclass LRNGradientOp final : public LRNOpBase<T, Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  LRNGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : LRNOpBase<T, Context>(operator_def, ws) {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n protected:\n  // Input: X, Y, scale, dY; Output: dX\n  INPUT_TAGS(INPUT, OUTPUT, SCALE, OUTPUT_GRAD);\n  Tensor<Context>* scale_ = nullptr;\n  Tensor<Context> local_scale_tensor_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_LOCAL_RESPONSE_NORMALIZATION_OP_H_\n"
  },
  {
    "path": "caffe2/operators/local_response_normalization_op_cudnn.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/cudnn_wrappers.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/types.h\"\n\nnamespace caffe2 {\n\nclass CuDNNLRNOp final : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n\n  CuDNNLRNOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws),\n        cudnn_wrapper_(&context_),\n        size_(OperatorBase::GetSingleArgument<int>(\"size\", 0)),\n        alpha_(OperatorBase::GetSingleArgument<float>(\"alpha\", 0)),\n        beta_(OperatorBase::GetSingleArgument<float>(\"beta\", 0)),\n        bias_(OperatorBase::GetSingleArgument<float>(\"bias\", 1)) {\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&data_desc_));\n\n    CUDNN_ENFORCE(cudnnCreateLRNDescriptor(&norm_desc_));\n    CUDNN_ENFORCE(\n        cudnnSetLRNDescriptor(norm_desc_, size_, alpha_, beta_, bias_));\n  }\n\n  ~CuDNNLRNOp() {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(data_desc_));\n    CUDNN_ENFORCE(cudnnDestroyLRNDescriptor(norm_desc_));\n  }\n\n  template <typename T, typename M>\n  bool DoRunWithType();\n\n  bool RunOnDevice() override;\n\n protected:\n  CuDNNWrapper cudnn_wrapper_;\n  cudnnTensorDescriptor_t data_desc_;\n  cudnnLRNDescriptor_t norm_desc_;\n\n  vector<TIndex> cudnn_input_dims_;\n\n  const int size_;\n  const float alpha_;\n  const float beta_;\n  const float bias_;\n\n  // Input: X, Output: Y\n};\n\nclass CuDNNLRNGradientOp final : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  CuDNNLRNGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws),\n        cudnn_wrapper_(&context_),\n        size_(OperatorBase::GetSingleArgument<int>(\"size\", 0)),\n        alpha_(OperatorBase::GetSingleArgument<float>(\"alpha\", 0)),\n        beta_(OperatorBase::GetSingleArgument<float>(\"beta\", 0)),\n        bias_(OperatorBase::GetSingleArgument<float>(\"bias\", 1)) {\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&data_desc_));\n\n    CUDNN_ENFORCE(cudnnCreateLRNDescriptor(&norm_desc_));\n    CUDNN_ENFORCE(\n        cudnnSetLRNDescriptor(norm_desc_, size_, alpha_, beta_, bias_));\n  }\n\n  ~CuDNNLRNGradientOp() {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(data_desc_));\n    CUDNN_ENFORCE(cudnnDestroyLRNDescriptor(norm_desc_));\n  }\n\n  template <typename T, typename M>\n  bool DoRunWithType();\n\n  bool RunOnDevice() override;\n\n protected:\n  CuDNNWrapper cudnn_wrapper_;\n  cudnnTensorDescriptor_t data_desc_;\n  cudnnLRNDescriptor_t norm_desc_;\n\n  vector<TIndex> cudnn_input_dims_;\n\n  const int size_;\n  const float alpha_;\n  const float beta_;\n  const float bias_;\n\n  // Input: X, Y, dY\n  // Output: dX\n};\n\ntemplate <typename T, typename M>\nbool CuDNNLRNOp::DoRunWithType() {\n  const auto& X = Input(0);\n  auto* Y = Output(0);\n\n  // Reshape tensor descriptors if necessary\n  if (X.dims() != cudnn_input_dims_) {\n    VLOG(1) << \"Setting descriptors\";\n    cudnn_input_dims_ = X.dims();\n    int C = 1, H = 1, W = 1;\n    // Normal 4-dimensional tensors for images.\n    C = X.dim32(1);\n    H = X.dim32(2);\n    W = X.dim32(3);\n    CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n        data_desc_,\n        GetCudnnTensorFormat(StorageOrder::NCHW),\n        cudnnTypeWrapper<T>::type,\n        X.dim32(0),\n        C,\n        H,\n        W));\n  }\n\n  // now actually run the computation\n  CUDNN_ENFORCE(cudnnLRNCrossChannelForward(\n      cudnn_wrapper_.inline_cudnn_handle(),\n      norm_desc_,\n      CUDNN_LRN_CROSS_CHANNEL_DIM1,\n      cudnnTypeWrapper<T>::kOne(),\n      data_desc_,\n      X.template data<T>(),\n      cudnnTypeWrapper<T>::kZero(),\n      data_desc_,\n      Y->template mutable_data<T>()));\n\n  return true;\n}\n\nbool CuDNNLRNOp::RunOnDevice() {\n  // dispatch based on contents of tensor(s)\n  const auto& X = Input(0);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n\n  if (X.IsType<float>()) {\n    return DoRunWithType<float, float>();\n  } else if (X.IsType<float16>()) {\n    return DoRunWithType<float16, float>();\n  } else {\n    CAFFE_THROW(\"Unsupported input type\");\n  }\n  return false;\n}\n\ntemplate <typename T, typename M>\nbool CuDNNLRNGradientOp::DoRunWithType() {\n  const auto& X = Input(0);\n  const auto& Y = Input(1);\n  const auto& dY = Input(2);\n  auto* dX = Output(0);\n\n  if (dY.dims() != cudnn_input_dims_) {\n    VLOG(1) << \"Setting descriptors\";\n    cudnn_input_dims_ = dY.dims();\n    int C = 1, H = 1, W = 1;\n    // Normal 4-dimensional tensors for images.\n    C = dY.dim32(1);\n    H = dY.dim32(2);\n    W = dY.dim32(3);\n    CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n        data_desc_,\n        GetCudnnTensorFormat(StorageOrder::NCHW),\n        cudnnTypeWrapper<T>::type,\n        dY.dim32(0),\n        C,\n        H,\n        W));\n  }\n\n  // run the computation\n  CUDNN_ENFORCE(cudnnLRNCrossChannelBackward(\n      cudnn_wrapper_.inline_cudnn_handle(),\n      norm_desc_,\n      CUDNN_LRN_CROSS_CHANNEL_DIM1,\n      cudnnTypeWrapper<T>::kOne(),\n      data_desc_,\n      Y.template data<T>(),\n      data_desc_,\n      dY.template data<T>(),\n      data_desc_,\n      X.template data<T>(),\n      cudnnTypeWrapper<T>::kZero(),\n      data_desc_,\n      dX->template mutable_data<T>()));\n  return true;\n}\n\nbool CuDNNLRNGradientOp::RunOnDevice() {\n  // dispatch based on contents of tensor(s)\n  const auto& X = Input(0);\n  const auto& Y = Input(1);\n  const auto& dY = Input(2);\n  auto* dX = Output(0);\n\n  dX->ResizeLike(dY);\n\n  if (dY.IsType<float>()) {\n    return DoRunWithType<float, float>();\n  } else if (dY.IsType<float16>()) {\n    return DoRunWithType<float16, float>();\n  } else {\n    CAFFE_THROW(\"Unsupported input type\");\n  }\n\n  return false;\n}\n\nnamespace {\nREGISTER_CUDNN_OPERATOR(LRN, CuDNNLRNOp);\nREGISTER_CUDNN_OPERATOR(LRNGradient, CuDNNLRNGradientOp);\n}\n\n}; // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/locally_connected_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n#include <vector>\n\n#include \"caffe2/operators/locally_connected_op.h\"\n#include \"caffe2/operators/locally_connected_op_impl.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\nconstexpr char kLCDoc[] = R\"DOC(\nNote that other parameters, such as the stride and\nkernel size, or the pads' sizes in each direction are not necessary for input\nbecause they are provided by the ConvPoolOpBase operator. Various dimension\nchecks are done implicitly, and the sizes are specified in the Input docs for\nthis operator. As is expected, the filter is locally connected with a subset of\nthe image and the bias is added; this is done throughout the image data and the\noutput is computed. As a side note on the implementation layout:\nlocally_connected_op_impl.h is the templated implementation of the\nlocally_connected_op.h file, which is why they are separate files.\n)DOC\";\n\nstd::function<void(OpSchema&)> LCDocGenerator(const char* dim) {\n  return [dim](OpSchema& schema) {\n    string doc = R\"DOC(\nThe locally connected operator consumes an input vector, a {dim}filter blob\nand a bias blob and computes the output. {lc_doc})DOC\";\n    ReplaceAll(doc, \"{dim}\", dim);\n    ReplaceAll(doc, \"{lc_doc}\", kLCDoc);\n    schema.SetDoc(doc);\n    schema.Input(\n        1,\n        \"filter\",\n        \"The filter blob that will be used in the locally connected op; \"\n        \"has size (YH * YW * M x C x kH x kW), where YH and YW are the height \"\n        \"and width of the output image, C is the number of channels, and kH \"\n        \"and kW are the height and width of the kernel.\");\n    schema.Input(\n        2,\n        \"bias\",\n        \"The 1D bias blob that is added through the locally connected op; \"\n        \"has size (YH * YW * M).\");\n    schema.Output(\n        0,\n        \"Y\",\n        \"Output data blob that contains the result of the locally connected op.\"\n        \"The output dimensions are functions of the kernel size, stride size, \"\n        \"and pad lengths.\"\n        \"\");\n  };\n}\n\n} // namespace\n\nREGISTER_CPU_OPERATOR(LC, LocallyConnectedOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LC)\n    .NumInputs(2, 3)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForConv)\n    .FillUsing(LCDocGenerator(\"\"));\n\nREGISTER_CPU_OPERATOR(LC1D, LocallyConnectedOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LC1D)\n    .NumInputs(2, 3)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForConv)\n    .FillUsing(LCDocGenerator(\"1D \"));\n\nREGISTER_CPU_OPERATOR(LC2D, LocallyConnectedOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LC2D)\n    .NumInputs(2, 3)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForConv)\n    .FillUsing(LCDocGenerator(\"2D \"));\n\nREGISTER_CPU_OPERATOR(LC3D, LocallyConnectedOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LC3D)\n    .NumInputs(2, 3)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForConv)\n    .FillUsing(LCDocGenerator(\"3D \"));\n\nREGISTER_CPU_OPERATOR(\n    LCGradient,\n    LocallyConnectedGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LCGradient).NumInputs(2, 3).NumOutputs(1, 3);\n\nREGISTER_CPU_OPERATOR(\n    LC1DGradient,\n    LocallyConnectedGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LC1DGradient).NumInputs(2, 3).NumOutputs(1, 3);\n\nREGISTER_CPU_OPERATOR(\n    LC2DGradient,\n    LocallyConnectedGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LC2DGradient).NumInputs(2, 3).NumOutputs(1, 3);\n\nREGISTER_CPU_OPERATOR(\n    LC3DGradient,\n    LocallyConnectedGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LC3DGradient).NumInputs(2, 3).NumOutputs(1, 3);\n\nnamespace {\n\nclass GetLocallyConnectedGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n\n  std::vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE(def_.input_size() == 3 || def_.input_size() == 2);\n    ArgumentHelper argsHelper(def_);\n    const bool compute_dX =\n        !argsHelper.GetSingleArgument<bool>(\"no_gradient_to_input\", 0);\n\n    if (def_.input_size() == 3) {\n      if (compute_dX) {\n        return SingleGradientDef(\n            def_.type() + \"Gradient\",\n            \"\",\n            std::vector<string>{I(0), I(1), GO(0)},\n            std::vector<string>{GI(1), GI(2), GI(0)});\n      } else {\n        return SingleGradientDef(\n            def_.type() + \"Gradient\",\n            \"\",\n            std::vector<string>{I(0), I(1), GO(0)},\n            std::vector<string>{GI(1), GI(2)});\n      }\n    } else {\n      if (compute_dX) {\n        return SingleGradientDef(\n            def_.type() + \"Gradient\",\n            \"\",\n            std::vector<string>{I(0), I(1), GO(0)},\n            std::vector<string>{GI(1), GI(0)},\n            std::vector<Argument>{MakeArgument<int>(\"no_bias\", 1)});\n      } else {\n        return SingleGradientDef(\n            def_.type() + \"Gradient\",\n            \"\",\n            std::vector<string>{I(0), I(1), GO(0)},\n            std::vector<string>{GI(1)},\n            std::vector<Argument>{MakeArgument<int>(\"no_bias\", 1)});\n      }\n    }\n  }\n};\n\n} // namespace\n\nREGISTER_GRADIENT(LC, GetLocallyConnectedGradient);\nREGISTER_GRADIENT(LC1D, GetLocallyConnectedGradient);\nREGISTER_GRADIENT(LC2D, GetLocallyConnectedGradient);\nREGISTER_GRADIENT(LC3D, GetLocallyConnectedGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/locally_connected_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_LOCALLY_CONNECTED_OP_H_\n#define CAFFE2_OPERATORS_LOCALLY_CONNECTED_OP_H_\n\n#include <vector>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_op_shared.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/operators/locally_connected_op_util.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass LocallyConnectedOp final : public ConvPoolOpBase<Context> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(Context);\n\n  LocallyConnectedOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<Context>(operator_def, ws) {\n    // Since this is the default locally connected implementation, we will\n    // use CAFFE_ENFORCE instead of OPERATOR_NEEDS_FEATURE.\n    CAFFE_ENFORCE(\n        group_ == 1 || order_ == StorageOrder::NCHW,\n        \"Group locally connected only supports NCHW order right now.\");\n  }\n\n  ~LocallyConnectedOp() = default;\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n private:\n  void RunOnDeviceWithOrderNCHWImpl(\n      const lc_op_util::ShapeParams& shape,\n      const T* X_data,\n      const T* filter_data,\n      const T* bias_data,\n      T* Y_data,\n      Tensor<Context>* column_buffer,\n      Tensor<Context>* column_transposed_buffer,\n      Tensor<Context>* output_buffer);\n\n  void RunOnDeviceWithOrderNHWCImpl(\n      const lc_op_util::ShapeParams& shape,\n      const T* X_data,\n      const T* filter_data,\n      const T* bias_data,\n      T* Y_data,\n      Tensor<Context>* column_buffer,\n      Tensor<Context>* column_transposed_buffer,\n      Tensor<Context>* Y_transposed_buffer);\n\n  void SetColumnBufferShape(\n      const int N,\n      const int C,\n      const int kernel_dim,\n      const std::vector<int>& output_image_dims,\n      std::vector<int>* column_dims,\n      std::vector<int>* column_transposed_dims);\n\n  void SetYTranposedBufferShape(\n      const std::vector<int>& Y_dims,\n      std::vector<int>* Y_transposed_dims);\n\n  Tensor<Context> bias_multiplier_;\n\n  // Buffer.\n  Tensor<Context> column_buffer_;\n  Tensor<Context> column_transposed_buffer_;\n  Tensor<Context> Y_transposed_buffer_;\n\n  // Dims devices.\n  Tensor<Context> X_dims_device_;\n  Tensor<Context> column_dims_device_;\n  Tensor<Context> column_transposed_dims_device_;\n  Tensor<Context> column_axes_device_;\n  Tensor<Context> Y_dims_device_;\n  Tensor<Context> Y_transposed_dims_device_;\n  Tensor<Context> Y_transposed_axes_device_;\n\n  // Input: X, W, b\n  // Output: Y\n  INPUT_TAGS(INPUT, FILTER, BIAS);\n};\n\ntemplate <typename T, class Context>\nclass LocallyConnectedGradientOp final : public ConvPoolOpBase<Context> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(Context);\n\n  LocallyConnectedGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<Context>(operator_def, ws),\n        no_bias_(OperatorBase::GetSingleArgument<int>(\"no_bias\", 0)) {\n    CAFFE_ENFORCE(\n        !(no_bias_ && OutputSize() == 3),\n        \"If bias is not present, you should not have 3 grad output.\");\n    CAFFE_ENFORCE(\n        group_ == 1 || order_ == StorageOrder::NCHW,\n        \"Group locally connected only supports NCHW order right now.\");\n  }\n\n  ~LocallyConnectedGradientOp() = default;\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n private:\n  void RunOnDeviceWithOrderNCHWImpl(\n      const lc_op_util::ShapeParams& shape,\n      const T* X_data,\n      const T* filter_data,\n      const T* dY_data,\n      T* dfilter_data,\n      T* dX_data,\n      T* dbias_data,\n      Tensor<Context>* column_buffer,\n      Tensor<Context>* column_transposed_buffer,\n      Tensor<Context>* dY_transposed_buffer);\n\n  void RunOnDeviceWithOrderNHWCImpl(\n      const lc_op_util::ShapeParams& shape,\n      const T* X_data,\n      const T* filter_data,\n      const T* dY_data,\n      T* dfilter_data,\n      T* dX_data,\n      T* dbias_data,\n      Tensor<Context>* column_buffer,\n      Tensor<Context>* column_transposed_buffer,\n      Tensor<Context>* dY_transposed_buffer);\n\n  void SetColumnBufferShape(\n      const int N,\n      const int C,\n      const int kernel_dim,\n      const std::vector<int>& output_image_dims,\n      std::vector<int>* column_dims,\n      std::vector<int>* column_transposed_dims);\n\n  void SetDYTranposedBufferShape(\n      const std::vector<int>& dY_dims,\n      std::vector<int>* dY_transposed_dims);\n\n  const bool no_bias_;\n\n  Tensor<Context> bias_multiplier_;\n\n  // Buffer.\n  Tensor<Context> column_buffer_;\n  Tensor<Context> column_transposed_buffer_;\n  Tensor<Context> dY_transposed_buffer_;\n\n  // Dims devices.\n  Tensor<Context> X_dims_device_;\n  Tensor<Context> column_dims_device_;\n  Tensor<Context> column_transposed_dims_device_;\n  Tensor<Context> column_axes_device_;\n  Tensor<Context> column_transposed_axes_device_;\n  Tensor<Context> dY_dims_device_;\n  Tensor<Context> dY_transposed_dims_device_;\n  Tensor<Context> dY_axes_device_;\n\n  // input: X, W, dY\n  // output: dW, db, and optionally dX\n  INPUT_TAGS(INPUT, FILTER, OUTPUT_GRAD);\n  OUTPUT_TAGS(FILTER_GRAD, BIAS_OR_INPUT_GRAD, INPUT_GRAD);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_LOCALLY_CONNECTED_OP_H_\n"
  },
  {
    "path": "caffe2/operators/locally_connected_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/locally_connected_op.h\"\n#include \"caffe2/operators/locally_connected_op_impl.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(LC, LocallyConnectedOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    LCGradient,\n    LocallyConnectedGradientOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(LC1D, LocallyConnectedOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    LC1DGradient,\n    LocallyConnectedGradientOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(LC2D, LocallyConnectedOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    LC2DGradient,\n    LocallyConnectedGradientOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(LC3D, LocallyConnectedOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    LC3DGradient,\n    LocallyConnectedGradientOp<float, CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/locally_connected_op_impl.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// locally_connected_impl.h is the templated implementation of the\n// locally_connected.h file.\n\n#ifndef CAFFE2_OPERATORS_LOCALLY_CONNECTED_OP_IMPL_H_\n#define CAFFE2_OPERATORS_LOCALLY_CONNECTED_OP_IMPL_H_\n\n#include <vector>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/operators/locally_connected_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nbool LocallyConnectedOp<T, Context>::RunOnDeviceWithOrderNCHW() {\n  const auto& X = Input(INPUT);\n  const auto& filter = Input(FILTER);\n  auto* Y = Output(0);\n  const int image_ndim = X.ndim() - 2;\n  CAFFE_ENFORCE_EQ(X.ndim() + image_ndim, filter.ndim());\n  lc_op_util::ShapeParams shape;\n  shape.N = X.dim32(0);\n  shape.C = X.dim32(1);\n  shape.M = filter.dim32(image_ndim);\n  CAFFE_ENFORCE(\n      shape.C == filter.dim32(image_ndim + 1) * group_,\n      \"Locally Connected op: input channels does not match: \"\n      \"# of input channels \",\n      shape.C,\n      \" is not equal to kernel channels * group:\",\n      filter.dim32(image_ndim + 1),\n      \"*\",\n      group_);\n  CAFFE_ENFORCE(\n      shape.M % group_ == 0,\n      \"The number of output channels is not divisible by group.\");\n\n  ConvPoolOpBase<Context>::SetOutputSize(X, Y, shape.M);\n  shape.input_image_size = GetDimsSize(X);\n  shape.output_image_size = GetDimsSize(*Y);\n  const std::vector<int> output_image_dims = GetDims(*Y);\n  for (int i = 0; i < image_ndim; ++i) {\n    CAFFE_ENFORCE(output_image_dims[i] == filter.dim32(i));\n  }\n\n  int kernel_dims_size = 1;\n  for (std::size_t i = 0; i < kernel_.size(); ++i) {\n    CAFFE_ENFORCE_EQ(filter.dim32(i + image_ndim + 2), kernel_[i]);\n    kernel_dims_size *= kernel_[i];\n  }\n\n  shape.input_image_dims = GetDims(X);\n  const std::vector<int> input_dims(X.dims().cbegin() + 1, X.dims().cend());\n  SetDeviceTensor(input_dims, &X_dims_device_);\n  shape.kernel_dim = shape.C / group_ * kernel_dims_size;\n\n  const std::vector<int> Y_dims(Y->dims().cbegin(), Y->dims().cend());\n  SetColumnBufferShape(\n      shape.N,\n      shape.C,\n      shape.kernel_dim,\n      output_image_dims,\n      &shape.column_dims,\n      &shape.column_transposed_dims);\n  SetYTranposedBufferShape(Y_dims, &shape.Y_transposed_dims);\n\n  const T* X_data = X.template data<T>();\n  const T* filter_data = filter.template data<T>();\n  const T* bias_data = nullptr;\n  if (InputSize() == 3) {\n    const auto& bias = Input(BIAS);\n    CAFFE_ENFORCE(bias.ndim() == image_ndim + 1);\n    for (int i = 0; i < image_ndim; ++i) {\n      CAFFE_ENFORCE(bias.dim32(i) == output_image_dims[i]);\n    }\n    CAFFE_ENFORCE(bias.dim32(image_ndim) == shape.M);\n    bias_data = bias.template data<T>();\n    ConvPoolOpBase<Context>::template SetBiasMultiplier<T>(\n        shape.N, &bias_multiplier_);\n  }\n  T* Y_data = Y->template mutable_data<T>();\n\n  RunOnDeviceWithOrderNCHWImpl(\n      shape,\n      X_data,\n      filter_data,\n      bias_data,\n      Y_data,\n      &column_buffer_,\n      &column_transposed_buffer_,\n      &Y_transposed_buffer_);\n\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool LocallyConnectedOp<T, Context>::RunOnDeviceWithOrderNHWC() {\n  const auto& X = Input(INPUT);\n  const auto& filter = Input(FILTER);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE_EQ(\n      kernel_.size(),\n      2,\n      \"Only 2d locally connected op is supported for NHWC storage type.\");\n  const int image_ndim = X.ndim() - 2;\n  CAFFE_ENFORCE_EQ(X.ndim() + image_ndim, filter.ndim());\n  lc_op_util::ShapeParams shape;\n  shape.N = X.dim32(0);\n  shape.C = X.dim32(3);\n  shape.input_image_dims = {X.dim32(1), X.dim32(2)};\n  shape.M = filter.dim32(image_ndim);\n  CAFFE_ENFORCE(filter.dim32(image_ndim + 1) == kernel_h());\n  CAFFE_ENFORCE(filter.dim32(image_ndim + 2) == kernel_w());\n  CAFFE_ENFORCE(filter.dim32(image_ndim + 3) == shape.C);\n  ConvPoolOpBase<Context>::SetOutputSize(X, Y, shape.M);\n\n  shape.input_image_size = GetDimsSize(X);\n  shape.output_image_size = GetDimsSize(*Y);\n  const std::vector<int> output_image_dims = GetDims(*Y);\n  for (int i = 0; i < image_ndim; ++i) {\n    CAFFE_ENFORCE(output_image_dims[i] == filter.dim32(i));\n  }\n\n  shape.kernel_dim = kernel_h() * kernel_w() * shape.C;\n  const std::vector<int> Y_dims(Y->dims().cbegin(), Y->dims().cend());\n  SetColumnBufferShape(\n      shape.N,\n      shape.C,\n      shape.kernel_dim,\n      output_image_dims,\n      &shape.column_dims,\n      &shape.column_transposed_dims);\n  SetYTranposedBufferShape(Y_dims, &shape.Y_transposed_dims);\n\n  const T* X_data = X.template data<T>();\n  const T* filter_data = filter.template data<T>();\n  const T* bias_data = nullptr;\n  if (InputSize() == 3) {\n    const auto& bias = Input(BIAS);\n    CAFFE_ENFORCE(bias.ndim() == image_ndim + 1);\n    for (int i = 0; i < image_ndim; ++i) {\n      CAFFE_ENFORCE(bias.dim32(i) == output_image_dims[i]);\n    }\n    CAFFE_ENFORCE(bias.dim32(image_ndim) == shape.M);\n    bias_data = bias.template data<T>();\n    ConvPoolOpBase<Context>::template SetBiasMultiplier<T>(\n        shape.N, &bias_multiplier_);\n  }\n  T* Y_data = Y->template mutable_data<T>();\n\n  RunOnDeviceWithOrderNHWCImpl(\n      shape,\n      X_data,\n      filter_data,\n      bias_data,\n      Y_data,\n      &column_buffer_,\n      &column_transposed_buffer_,\n      &Y_transposed_buffer_);\n\n  return true;\n}\n\ntemplate <typename T, class Context>\nvoid LocallyConnectedOp<T, Context>::RunOnDeviceWithOrderNCHWImpl(\n    const lc_op_util::ShapeParams& shape,\n    const T* X_data,\n    const T* filter_data,\n    const T* bias_data,\n    T* Y_data,\n    Tensor<Context>* column_buffer,\n    Tensor<Context>* column_transposed_buffer,\n    Tensor<Context>* Y_transposed_buffer) {\n  const int input_stride = shape.C / group_ * shape.input_image_size;\n  const int column_stride = shape.kernel_dim * shape.output_image_size;\n  column_buffer->Resize(shape.column_dims);\n  column_transposed_buffer->Resize(shape.column_transposed_dims);\n  Y_transposed_buffer->Resize(shape.Y_transposed_dims);\n  T* column_buffer_data = column_buffer->template mutable_data<T>();\n  T* Y_transposed_buffer_data = Y_transposed_buffer->template mutable_data<T>();\n\n  for (int image_id = 0; image_id < shape.N; ++image_id) {\n    for (int group_id = 0; group_id < group_; ++group_id) {\n      if (kernel_.size() == 2) {\n        math::Im2col<T, Context, StorageOrder::NCHW>(\n            X_data + group_id * input_stride,\n            shape.C / group_,\n            shape.input_image_dims[0],\n            shape.input_image_dims[1],\n            kernel_h(),\n            kernel_w(),\n            dilation_h(),\n            dilation_w(),\n            pad_t(),\n            pad_l(),\n            pad_b(),\n            pad_r(),\n            stride_h(),\n            stride_w(),\n            column_buffer_data + group_id * column_stride,\n            &context_);\n      } else {\n        math::Im2colNd<T, Context, StorageOrder::NCHW>(\n            X_data + group_id * input_stride,\n            X_dims_device_.template data<int>(),\n            column_dims_device_.template data<int>() + 1,\n            shape.C * shape.input_image_size,\n            column_stride,\n            kernel_device_.template data<int>(),\n            stride_device_.template data<int>(),\n            dilation_device_.template data<int>(),\n            pads_device_.template data<int>(),\n            kernel_.size(),\n            column_buffer_data + group_id * column_stride,\n            &context_);\n      }\n    }\n    X_data += input_stride * group_;\n    column_buffer_data += column_stride * group_;\n  }\n  math::Transpose(\n      shape.column_dims.size(),\n      column_dims_device_.template data<int>(),\n      column_transposed_dims_device_.template data<int>(),\n      column_axes_device_.template data<int>(),\n      column_buffer->size(),\n      column_buffer->template data<T>(),\n      column_transposed_buffer->template mutable_data<T>(),\n      &context_);\n  math::GemmBatched(\n      CblasNoTrans,\n      CblasNoTrans,\n      shape.output_image_size * group_,\n      shape.M / group_,\n      shape.N,\n      shape.kernel_dim,\n      1.0f,\n      filter_data,\n      column_transposed_buffer->template data<T>(),\n      0.0f,\n      Y_transposed_buffer_data,\n      &context_);\n  if (bias_data != nullptr) {\n    math::Gemm<T, Context>(\n        CblasNoTrans,\n        CblasNoTrans,\n        shape.output_image_size * shape.M,\n        shape.N,\n        1,\n        1.0,\n        bias_data,\n        bias_multiplier_.template data<T>(),\n        1.0,\n        Y_transposed_buffer_data,\n        &context_);\n  }\n  math::Transpose(\n      shape.Y_transposed_dims.size(),\n      Y_transposed_dims_device_.template data<int>(),\n      Y_dims_device_.template data<int>(),\n      Y_transposed_axes_device_.template data<int>(),\n      Y_transposed_buffer->size(),\n      Y_transposed_buffer_data,\n      Y_data,\n      &context_);\n}\n\ntemplate <typename T, class Context>\nvoid LocallyConnectedOp<T, Context>::RunOnDeviceWithOrderNHWCImpl(\n    const lc_op_util::ShapeParams& shape,\n    const T* X_data,\n    const T* filter_data,\n    const T* bias_data,\n    T* Y_data,\n    Tensor<Context>* column_buffer,\n    Tensor<Context>* column_transposed_buffer,\n    Tensor<Context>* Y_transposed_buffer) {\n  const int input_stride = shape.C * shape.input_image_size;\n  const int column_stride = shape.kernel_dim * shape.output_image_size;\n  column_buffer->Resize(shape.column_dims);\n  column_transposed_buffer->Resize(shape.column_transposed_dims);\n  Y_transposed_buffer->Resize(shape.Y_transposed_dims);\n  T* column_buffer_data = column_buffer->template mutable_data<T>();\n  T* Y_transposed_buffer_data = Y_transposed_buffer->template mutable_data<T>();\n  for (int image_id = 0; image_id < shape.N; ++image_id) {\n    math::Im2col<T, Context, StorageOrder::NHWC>(\n        X_data + image_id * input_stride,\n        shape.C,\n        shape.input_image_dims[0],\n        shape.input_image_dims[1],\n        kernel_h(),\n        kernel_w(),\n        dilation_h(),\n        dilation_w(),\n        pad_t(),\n        pad_l(),\n        pad_b(),\n        pad_r(),\n        stride_h(),\n        stride_w(),\n        column_buffer_data + image_id * column_stride,\n        &context_);\n  }\n  math::Transpose(\n      shape.column_dims.size(),\n      column_dims_device_.template data<int>(),\n      column_transposed_dims_device_.template data<int>(),\n      column_axes_device_.template data<int>(),\n      column_buffer->size(),\n      column_buffer->template data<T>(),\n      column_transposed_buffer->template mutable_data<T>(),\n      &context_);\n  math::GemmBatched(\n      CblasNoTrans,\n      CblasTrans,\n      shape.output_image_size,\n      shape.N,\n      shape.M,\n      shape.kernel_dim,\n      1.0f,\n      column_transposed_buffer->template data<T>(),\n      filter_data,\n      0.0f,\n      Y_transposed_buffer_data,\n      &context_);\n  math::Transpose(\n      shape.Y_transposed_dims.size(),\n      Y_transposed_dims_device_.template data<int>(),\n      Y_dims_device_.template data<int>(),\n      Y_transposed_axes_device_.template data<int>(),\n      Y_transposed_buffer->size(),\n      Y_transposed_buffer_data,\n      Y_data,\n      &context_);\n  if (bias_data != nullptr) {\n    math::Gemm<T, Context>(\n        CblasNoTrans,\n        CblasNoTrans,\n        shape.N,\n        shape.output_image_size * shape.M,\n        1,\n        1.0f,\n        bias_multiplier_.template data<T>(),\n        bias_data,\n        1.0f,\n        Y_data,\n        &context_);\n  }\n}\n\ntemplate <typename T, class Context>\nvoid LocallyConnectedOp<T, Context>::SetColumnBufferShape(\n    const int N,\n    const int C,\n    const int kernel_dim,\n    const std::vector<int>& output_image_dims,\n    std::vector<int>* column_dims,\n    std::vector<int>* column_transposed_dims) {\n  std::vector<int> column_axes;\n  lc_op_util::SetColumnBufferShapeImpl(\n      N,\n      kernel_dim,\n      order_,\n      output_image_dims,\n      column_dims,\n      column_transposed_dims,\n      &column_axes,\n      nullptr);\n  SetDeviceTensor(*column_dims, &column_dims_device_);\n  SetDeviceTensor(*column_transposed_dims, &column_transposed_dims_device_);\n  SetDeviceTensor(column_axes, &column_axes_device_);\n}\n\ntemplate <typename T, class Context>\nvoid LocallyConnectedOp<T, Context>::SetYTranposedBufferShape(\n    const std::vector<int>& Y_dims,\n    std::vector<int>* Y_transposed_dims) {\n  const int n_Y_dims = Y_dims.size();\n  Y_transposed_dims->resize(n_Y_dims);\n  std::vector<int> Y_transposed_axes(n_Y_dims);\n  if (order_ == StorageOrder::NCHW) {\n    for (int i = 0; i < n_Y_dims - 2; ++i) {\n      Y_transposed_dims->at(i) = Y_dims[i + 2];\n      Y_transposed_axes[i + 2] = i;\n    }\n    Y_transposed_dims->at(n_Y_dims - 2) = Y_dims[1];\n    Y_transposed_dims->at(n_Y_dims - 1) = Y_dims[0];\n    Y_transposed_axes[1] = n_Y_dims - 2;\n    Y_transposed_axes[0] = n_Y_dims - 1;\n  } else {\n    for (int i = 0; i < n_Y_dims - 2; ++i) {\n      Y_transposed_dims->at(i) = Y_dims[i + 1];\n      Y_transposed_axes[i + 1] = i;\n    }\n    Y_transposed_dims->at(n_Y_dims - 2) = Y_dims[0];\n    Y_transposed_dims->at(n_Y_dims - 1) = Y_dims[n_Y_dims - 1];\n    Y_transposed_axes[0] = n_Y_dims - 2;\n    Y_transposed_axes[n_Y_dims - 1] = n_Y_dims - 1;\n  }\n  SetDeviceTensor(Y_dims, &Y_dims_device_);\n  SetDeviceTensor(*Y_transposed_dims, &Y_transposed_dims_device_);\n  SetDeviceTensor(Y_transposed_axes, &Y_transposed_axes_device_);\n}\n\ntemplate <typename T, class Context>\nbool LocallyConnectedGradientOp<T, Context>::RunOnDeviceWithOrderNCHW() {\n  const auto& X = Input(INPUT);\n  const auto& filter = Input(FILTER);\n  const auto& dY = Input(OUTPUT_GRAD);\n  auto* dfilter = Output(FILTER_GRAD);\n  const int image_ndim = X.ndim() - 2;\n  CAFFE_ENFORCE_EQ(X.ndim() + image_ndim, filter.ndim());\n\n  lc_op_util::ShapeParams shape;\n  shape.N = X.dim32(0);\n  shape.C = X.dim32(1);\n  shape.M = filter.dim32(image_ndim);\n  CAFFE_ENFORCE(filter.dim32(image_ndim + 1) * group_ == shape.C);\n  CAFFE_ENFORCE(shape.M % group_ == 0);\n\n  shape.input_image_dims = GetDims(X);\n  shape.input_image_size = GetDimsSize(X);\n  const std::vector<int> output_image_dims = GetDims(dY);\n  shape.output_image_size = GetDimsSize(dY);\n  for (int i = 0; i < image_ndim; ++i) {\n    CAFFE_ENFORCE(output_image_dims[i] == filter.dim32(i));\n  }\n  ConvPoolOpBase<Context>::ComputePads(shape.input_image_dims);\n\n  int kernel_dims_size = 1;\n  for (std::size_t i = 0; i < kernel_.size(); ++i) {\n    CAFFE_ENFORCE_EQ(filter.dim32(i + image_ndim + 2), kernel_[i]);\n    kernel_dims_size *= kernel_[i];\n  }\n\n  const std::vector<int> X_dims(X.dims().cbegin() + 1, X.dims().cend());\n  SetDeviceTensor(X_dims, &X_dims_device_);\n  shape.kernel_dim = shape.C / group_ * kernel_dims_size;\n\n  const std::vector<int> dY_dims(dY.dims().cbegin(), dY.dims().cend());\n  SetColumnBufferShape(\n      shape.N,\n      shape.C,\n      shape.kernel_dim,\n      output_image_dims,\n      &shape.column_dims,\n      &shape.column_transposed_dims);\n  SetDYTranposedBufferShape(dY_dims, &shape.Y_transposed_dims);\n\n  dfilter->ResizeLike(filter);\n  const T* X_data = X.template data<T>();\n  const T* filter_data = filter.template data<T>();\n  const T* dY_data = dY.template data<T>();\n  T* dfilter_data = dfilter->template mutable_data<T>();\n  T* dX_data = nullptr;\n  T* dbias_data = nullptr;\n  if (OutputSize() == 3 || (no_bias_ && OutputSize() == 2)) {\n    auto* dX = Output(no_bias_ ? BIAS_OR_INPUT_GRAD : INPUT_GRAD);\n    dX->ResizeLike(X);\n    dX_data = dX->template mutable_data<T>();\n  }\n  if (!no_bias_) {\n    auto* dbias = Output(BIAS_OR_INPUT_GRAD);\n    std::vector<int> dbias_dims = output_image_dims;\n    dbias_dims.push_back(shape.M);\n    dbias->Resize(dbias_dims);\n    ConvPoolOpBase<Context>::template SetBiasMultiplier<T>(\n        shape.N, &bias_multiplier_);\n    dbias_data = dbias->template mutable_data<T>();\n  }\n  RunOnDeviceWithOrderNCHWImpl(\n      shape,\n      X_data,\n      filter_data,\n      dY_data,\n      dfilter_data,\n      dX_data,\n      dbias_data,\n      &column_buffer_,\n      &column_transposed_buffer_,\n      &dY_transposed_buffer_);\n\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool LocallyConnectedGradientOp<T, Context>::RunOnDeviceWithOrderNHWC() {\n  const auto& X = Input(INPUT);\n  const auto& filter = Input(FILTER);\n  const auto& dY = Input(OUTPUT_GRAD);\n  auto* dfilter = Output(FILTER_GRAD);\n  CAFFE_ENFORCE_EQ(\n      kernel_.size(),\n      2,\n      \"Only 2d locally connected op is supported for NHWC storage type.\");\n  const int image_ndim = X.ndim() - 2;\n  CAFFE_ENFORCE_EQ(X.ndim() + image_ndim, filter.ndim());\n  lc_op_util::ShapeParams shape;\n  shape.N = X.dim32(0);\n  shape.C = X.dim32(3);\n  shape.input_image_dims = {X.dim32(1), X.dim32(2)};\n  shape.M = filter.dim32(image_ndim);\n  CAFFE_ENFORCE(filter.dim32(image_ndim + 1) == kernel_h());\n  CAFFE_ENFORCE(filter.dim32(image_ndim + 2) == kernel_w());\n  CAFFE_ENFORCE(filter.dim32(image_ndim + 3) == shape.C);\n  ConvPoolOpBase<Context>::ComputePads(shape.input_image_dims);\n\n  shape.input_image_size = GetDimsSize(X);\n  shape.output_image_size = GetDimsSize(dY);\n  const std::vector<int> output_image_dims = GetDims(dY);\n  for (int i = 0; i < image_ndim; ++i) {\n    CAFFE_ENFORCE(output_image_dims[i] == filter.dim32(i));\n  }\n\n  shape.kernel_dim = kernel_h() * kernel_w() * shape.C;\n  const std::vector<int> dY_dims(dY.dims().cbegin(), dY.dims().cend());\n  SetColumnBufferShape(\n      shape.N,\n      shape.C,\n      shape.kernel_dim,\n      output_image_dims,\n      &shape.column_dims,\n      &shape.column_transposed_dims);\n  SetDYTranposedBufferShape(dY_dims, &shape.Y_transposed_dims);\n\n  dfilter->ResizeLike(filter);\n  const T* X_data = X.template data<T>();\n  const T* filter_data = filter.template data<T>();\n  const T* dY_data = dY.template data<T>();\n  T* dfilter_data = dfilter->template mutable_data<T>();\n  T* dX_data = nullptr;\n  T* dbias_data = nullptr;\n  if (OutputSize() == 3 || (no_bias_ && OutputSize() == 2)) {\n    auto* dX = Output(no_bias_ ? BIAS_OR_INPUT_GRAD : INPUT_GRAD);\n    dX->ResizeLike(X);\n    dX_data = dX->template mutable_data<T>();\n  }\n  if (!no_bias_) {\n    auto* dbias = Output(BIAS_OR_INPUT_GRAD);\n    std::vector<int> dbias_dims = output_image_dims;\n    dbias_dims.push_back(shape.M);\n    dbias->Resize(dbias_dims);\n    ConvPoolOpBase<Context>::template SetBiasMultiplier<T>(\n        shape.N, &bias_multiplier_);\n    dbias_data = dbias->template mutable_data<T>();\n  }\n  RunOnDeviceWithOrderNHWCImpl(\n      shape,\n      X_data,\n      filter_data,\n      dY_data,\n      dfilter_data,\n      dX_data,\n      dbias_data,\n      &column_buffer_,\n      &column_transposed_buffer_,\n      &dY_transposed_buffer_);\n\n  return true;\n}\n\ntemplate <typename T, class Context>\nvoid LocallyConnectedGradientOp<T, Context>::RunOnDeviceWithOrderNCHWImpl(\n    const lc_op_util::ShapeParams& shape,\n    const T* X_data,\n    const T* filter_data,\n    const T* dY_data,\n    T* dfilter_data,\n    T* dX_data,\n    T* dbias_data,\n    Tensor<Context>* column_buffer,\n    Tensor<Context>* column_transposed_buffer,\n    Tensor<Context>* dY_transposed_buffer) {\n  const int input_stride = shape.C * shape.input_image_size;\n  const int column_stride = shape.kernel_dim * shape.output_image_size;\n  column_buffer->Resize(shape.column_dims);\n  column_transposed_buffer->Resize(shape.column_transposed_dims);\n  dY_transposed_buffer->Resize(shape.Y_transposed_dims);\n  T* column_buffer_data = column_buffer->template mutable_data<T>();\n  T* dY_transposed_buffer_data =\n      dY_transposed_buffer->template mutable_data<T>();\n\n  for (int image_id = 0; image_id < shape.N; ++image_id) {\n    for (int group_id = 0; group_id < group_; ++group_id) {\n      if (kernel_.size() == 2) {\n        math::Im2col<T, Context, StorageOrder::NCHW>(\n            X_data + group_id * input_stride,\n            shape.C / group_,\n            shape.input_image_dims[0],\n            shape.input_image_dims[1],\n            kernel_h(),\n            kernel_w(),\n            dilation_h(),\n            dilation_w(),\n            pad_t(),\n            pad_l(),\n            pad_b(),\n            pad_r(),\n            stride_h(),\n            stride_w(),\n            column_buffer_data + group_id * column_stride,\n            &context_);\n      } else {\n        math::Im2colNd<T, Context, StorageOrder::NCHW>(\n            X_data + group_id * input_stride,\n            X_dims_device_.template data<int>(),\n            column_dims_device_.template data<int>() + 1,\n            shape.C * shape.input_image_size,\n            column_stride,\n            kernel_device_.template data<int>(),\n            stride_device_.template data<int>(),\n            dilation_device_.template data<int>(),\n            pads_device_.template data<int>(),\n            kernel_.size(),\n            column_buffer_data + group_id * column_stride,\n            &context_);\n      }\n    }\n    X_data += input_stride * group_;\n    column_buffer_data += column_stride * group_;\n  }\n  math::Transpose(\n      shape.column_dims.size(),\n      column_dims_device_.template data<int>(),\n      column_transposed_dims_device_.template data<int>(),\n      column_axes_device_.template data<int>(),\n      column_buffer->size(),\n      column_buffer->template data<T>(),\n      column_transposed_buffer->template mutable_data<T>(),\n      &context_);\n\n  math::Transpose(\n      shape.Y_transposed_dims.size(),\n      dY_dims_device_.template data<int>(),\n      dY_transposed_dims_device_.template data<int>(),\n      dY_axes_device_.template data<int>(),\n      dY_transposed_buffer->size(),\n      dY_data,\n      dY_transposed_buffer_data,\n      &context_);\n\n  // Gradient respect to filter.\n  math::GemmBatched(\n      CblasNoTrans,\n      CblasTrans,\n      shape.output_image_size * group_,\n      shape.M / group_,\n      shape.kernel_dim,\n      shape.N,\n      1.0f,\n      dY_transposed_buffer_data,\n      column_transposed_buffer->template data<T>(),\n      0.0f,\n      dfilter_data,\n      &context_);\n\n  if (dbias_data != nullptr) {\n    // Gradient respect to bias.\n    math::Gemv<T, Context>(\n        CblasNoTrans,\n        shape.output_image_size * shape.M,\n        shape.N,\n        1.0f,\n        dY_transposed_buffer_data,\n        bias_multiplier_.template data<T>(),\n        0.0f,\n        dbias_data,\n        &context_);\n  }\n\n  if (dX_data != nullptr) {\n    // Gradient respect to X.\n    math::GemmBatched(\n        CblasTrans,\n        CblasNoTrans,\n        shape.output_image_size * group_,\n        shape.kernel_dim,\n        shape.N,\n        shape.M / group_,\n        1.0f,\n        filter_data,\n        dY_transposed_buffer_data,\n        0.0f,\n        column_transposed_buffer->template mutable_data<T>(),\n        &context_);\n    math::Transpose(\n        shape.column_dims.size(),\n        column_transposed_dims_device_.template data<int>(),\n        column_dims_device_.template data<int>(),\n        column_transposed_axes_device_.template data<int>(),\n        column_transposed_buffer->size(),\n        column_transposed_buffer->template data<T>(),\n        column_buffer->template mutable_data<T>(),\n        &context_);\n    const T* const_column_buffer_data = column_buffer->template data<T>();\n    for (int image_id = 0; image_id < shape.N; ++image_id) {\n      for (int group_id = 0; group_id < group_; ++group_id) {\n        if (kernel_.size() == 2) {\n          math::Col2im<T, Context, StorageOrder::NCHW>(\n              const_column_buffer_data + group_id * column_stride,\n              shape.C / group_,\n              shape.input_image_dims[0],\n              shape.input_image_dims[1],\n              kernel_h(),\n              kernel_w(),\n              dilation_h(),\n              dilation_w(),\n              pad_t(),\n              pad_l(),\n              pad_b(),\n              pad_r(),\n              stride_h(),\n              stride_w(),\n              dX_data + group_id * input_stride,\n              &context_);\n        } else {\n          math::Col2imNd<T, Context, StorageOrder::NCHW>(\n              const_column_buffer_data + group_id * column_stride,\n              X_dims_device_.template data<int>(),\n              column_dims_device_.template data<int>() + 1,\n              shape.C * shape.input_image_size,\n              column_stride,\n              kernel_device_.template data<int>(),\n              stride_device_.template data<int>(),\n              dilation_device_.template data<int>(),\n              pads_device_.template data<int>(),\n              kernel_.size(),\n              dX_data + group_id * input_stride,\n              &context_);\n        }\n      }\n      dX_data += input_stride * group_;\n      const_column_buffer_data += column_stride * group_;\n    }\n  }\n}\n\ntemplate <typename T, class Context>\nvoid LocallyConnectedGradientOp<T, Context>::RunOnDeviceWithOrderNHWCImpl(\n    const lc_op_util::ShapeParams& shape,\n    const T* X_data,\n    const T* filter_data,\n    const T* dY_data,\n    T* dfilter_data,\n    T* dX_data,\n    T* dbias_data,\n    Tensor<Context>* column_buffer,\n    Tensor<Context>* column_transposed_buffer,\n    Tensor<Context>* dY_transposed_buffer) {\n  const int input_stride = shape.C * shape.input_image_size;\n  const int column_stride = shape.kernel_dim * shape.output_image_size;\n  column_buffer->Resize(shape.column_dims);\n  column_transposed_buffer->Resize(shape.column_transposed_dims);\n  dY_transposed_buffer->Resize(shape.Y_transposed_dims);\n  T* column_buffer_data = column_buffer->template mutable_data<T>();\n  T* dY_transposed_buffer_data =\n      dY_transposed_buffer->template mutable_data<T>();\n  for (int image_id = 0; image_id < shape.N; ++image_id) {\n    math::Im2col<T, Context, StorageOrder::NHWC>(\n        X_data + image_id * input_stride,\n        shape.C,\n        shape.input_image_dims[0],\n        shape.input_image_dims[1],\n        kernel_h(),\n        kernel_w(),\n        dilation_h(),\n        dilation_w(),\n        pad_t(),\n        pad_l(),\n        pad_b(),\n        pad_r(),\n        stride_h(),\n        stride_w(),\n        column_buffer_data + image_id * column_stride,\n        &context_);\n  }\n  math::Transpose(\n      shape.column_dims.size(),\n      column_dims_device_.template data<int>(),\n      column_transposed_dims_device_.template data<int>(),\n      column_axes_device_.template data<int>(),\n      column_buffer->size(),\n      column_buffer->template data<T>(),\n      column_transposed_buffer->template mutable_data<T>(),\n      &context_);\n\n  math::Transpose(\n      shape.Y_transposed_dims.size(),\n      dY_dims_device_.template data<int>(),\n      dY_transposed_dims_device_.template data<int>(),\n      dY_axes_device_.template data<int>(),\n      dY_transposed_buffer->size(),\n      dY_data,\n      dY_transposed_buffer_data,\n      &context_);\n\n  // Gradient respect to filter.\n  math::GemmBatched(\n      CblasTrans,\n      CblasNoTrans,\n      shape.output_image_size,\n      shape.M,\n      shape.kernel_dim,\n      shape.N,\n      1.0f,\n      dY_transposed_buffer_data,\n      column_transposed_buffer->template data<T>(),\n      0.0f,\n      dfilter_data,\n      &context_);\n\n  if (dbias_data != nullptr) {\n    // Gradient respect to bias.\n    math::Gemv<T, Context>(\n        CblasTrans,\n        shape.N,\n        shape.output_image_size * shape.M,\n        1.0f,\n        dY_data,\n        bias_multiplier_.template data<T>(),\n        0.0f,\n        dbias_data,\n        &context_);\n  }\n\n  if (dX_data != nullptr) {\n    // Gradient respect to X.\n    math::GemmBatched(\n        CblasNoTrans,\n        CblasNoTrans,\n        shape.output_image_size,\n        shape.N,\n        shape.kernel_dim,\n        shape.M,\n        1.0f,\n        dY_transposed_buffer_data,\n        filter_data,\n        0.0f,\n        column_transposed_buffer->template mutable_data<T>(),\n        &context_);\n    math::Transpose(\n        shape.column_dims.size(),\n        column_transposed_dims_device_.template data<int>(),\n        column_dims_device_.template data<int>(),\n        column_transposed_axes_device_.template data<int>(),\n        column_transposed_buffer->size(),\n        column_transposed_buffer->template data<T>(),\n        column_buffer->template mutable_data<T>(),\n        &context_);\n    const T* const_column_buffer_data = column_buffer->template data<T>();\n    for (int image_id = 0; image_id < shape.N; ++image_id) {\n      math::Col2im<T, Context, StorageOrder::NHWC>(\n          const_column_buffer_data,\n          shape.C,\n          shape.input_image_dims[0],\n          shape.input_image_dims[1],\n          kernel_h(),\n          kernel_w(),\n          dilation_h(),\n          dilation_w(),\n          pad_t(),\n          pad_l(),\n          pad_b(),\n          pad_r(),\n          stride_h(),\n          stride_w(),\n          dX_data,\n          &context_);\n      dX_data += input_stride;\n      const_column_buffer_data += column_stride;\n    }\n  }\n}\n\ntemplate <typename T, class Context>\nvoid LocallyConnectedGradientOp<T, Context>::SetColumnBufferShape(\n    const int N,\n    const int C,\n    const int kernel_dim,\n    const std::vector<int>& output_image_dims,\n    std::vector<int>* column_dims,\n    std::vector<int>* column_transposed_dims) {\n  std::vector<int> column_axes;\n  std::vector<int> column_transposed_axes;\n  lc_op_util::SetColumnBufferShapeImpl(\n      N,\n      kernel_dim,\n      order_,\n      output_image_dims,\n      column_dims,\n      column_transposed_dims,\n      &column_axes,\n      &column_transposed_axes);\n  SetDeviceTensor(*column_dims, &column_dims_device_);\n  SetDeviceTensor(*column_transposed_dims, &column_transposed_dims_device_);\n  SetDeviceTensor(column_axes, &column_axes_device_);\n  SetDeviceTensor(column_transposed_axes, &column_transposed_axes_device_);\n}\n\ntemplate <typename T, class Context>\nvoid LocallyConnectedGradientOp<T, Context>::SetDYTranposedBufferShape(\n    const std::vector<int>& dY_dims,\n    std::vector<int>* dY_transposed_dims) {\n  const int n_dY_dims = dY_dims.size();\n  dY_transposed_dims->resize(n_dY_dims);\n  std::vector<int> dY_axes(n_dY_dims);\n  if (order_ == StorageOrder::NCHW) {\n    for (int i = 0; i < n_dY_dims - 2; ++i) {\n      dY_transposed_dims->at(i) = dY_dims[i + 2];\n      dY_axes[i] = i + 2;\n    }\n    dY_transposed_dims->at(n_dY_dims - 2) = dY_dims[1];\n    dY_transposed_dims->at(n_dY_dims - 1) = dY_dims[0];\n    dY_axes[n_dY_dims - 2] = 1;\n    dY_axes[n_dY_dims - 1] = 0;\n  } else {\n    for (int i = 0; i < n_dY_dims - 2; ++i) {\n      dY_transposed_dims->at(i) = dY_dims[i + 1];\n      dY_axes[i] = i + 1;\n    }\n    dY_transposed_dims->at(n_dY_dims - 2) = dY_dims[0];\n    dY_transposed_dims->at(n_dY_dims - 1) = dY_dims[n_dY_dims - 1];\n    dY_axes[n_dY_dims - 2] = 0;\n    dY_axes[n_dY_dims - 1] = n_dY_dims - 1;\n  }\n  SetDeviceTensor(dY_dims, &dY_dims_device_);\n  SetDeviceTensor(*dY_transposed_dims, &dY_transposed_dims_device_);\n  SetDeviceTensor(dY_axes, &dY_axes_device_);\n}\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_LOCALLY_CONNECTED_OP_IMPL_H_\n"
  },
  {
    "path": "caffe2/operators/locally_connected_op_util.cc",
    "content": "#include \"caffe2/operators/locally_connected_op_util.h\"\n\nnamespace caffe2 {\nnamespace lc_op_util {\n\nvoid SetColumnBufferShapeImpl(\n    const int N,\n    const int kernel_dim,\n    const StorageOrder order,\n    const std::vector<int>& output_image_dims,\n    std::vector<int>* column_dims,\n    std::vector<int>* column_transposed_dims,\n    std::vector<int>* column_axes,\n    std::vector<int>* column_transposed_axes) {\n  const int n_column_dims = output_image_dims.size() + 2;\n  column_dims->resize(n_column_dims);\n  column_transposed_dims->resize(n_column_dims);\n  column_axes->resize(n_column_dims);\n  if (order == StorageOrder::NCHW) {\n    for (int i = 0; i < n_column_dims - 2; ++i) {\n      column_dims->at(i + 2) = output_image_dims[i];\n      column_transposed_dims->at(i) = output_image_dims[i];\n      column_axes->at(i) = i + 2;\n    }\n    column_dims->at(0) = N;\n    column_dims->at(1) = kernel_dim;\n    column_transposed_dims->at(n_column_dims - 2) = kernel_dim;\n    column_transposed_dims->at(n_column_dims - 1) = N;\n    column_axes->at(n_column_dims - 1) = 0;\n    column_axes->at(n_column_dims - 2) = 1;\n  } else {\n    for (int i = 0; i < n_column_dims - 2; ++i) {\n      column_dims->at(i + 1) = output_image_dims[i];\n      column_transposed_dims->at(i) = output_image_dims[i];\n      column_axes->at(i) = i + 1;\n    }\n    column_dims->at(0) = N;\n    column_dims->at(n_column_dims - 1) = kernel_dim;\n    column_transposed_dims->at(n_column_dims - 2) = N;\n    column_transposed_dims->at(n_column_dims - 1) = kernel_dim;\n    column_axes->at(n_column_dims - 2) = 0;\n    column_axes->at(n_column_dims - 1) = n_column_dims - 1;\n  }\n  if (column_transposed_axes != nullptr) {\n    column_transposed_axes->resize(n_column_dims);\n    for (int i = 0; i < n_column_dims; ++i) {\n      column_transposed_axes->at(column_axes->at(i)) = i;\n    }\n  }\n}\n\n} // namespace lc_op_util\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/locally_connected_op_util.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_LOCALLY_CONNECTED_OP_UTIL_H_\n#define CAFFE2_OPERATORS_LOCALLY_CONNECTED_OP_UTIL_H_\n\n#include <vector>\n\n#include \"caffe2/core/types.h\"\n\nnamespace caffe2 {\nnamespace lc_op_util {\n\nstruct ShapeParams {\n  int N;\n  int C;\n  int M;\n  int input_image_size;\n  int output_image_size;\n  int kernel_dim;\n  std::vector<int> input_image_dims;\n  std::vector<int> column_dims;\n  std::vector<int> column_transposed_dims;\n  std::vector<int> Y_transposed_dims;\n};\n\nstruct CUDAConvNetShapeParams {\n  int N;\n  int C;\n  int M;\n  int X_H;\n  int X_W;\n  int Y_H;\n  int Y_W;\n};\n\nvoid SetColumnBufferShapeImpl(\n    int N,\n    int kernel_dim,\n    StorageOrder order,\n    const std::vector<int>& output_image_dims,\n    std::vector<int>* column_dims,\n    std::vector<int>* column_transposed_dims,\n    std::vector<int>* column_axes,\n    std::vector<int>* column_transposed_axes);\n\n} // namespace lc_op_util\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_LOCALLY_CONNECTED_OP_UTIL_H_\n"
  },
  {
    "path": "caffe2/operators/log_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/math_ops.h\"\n#include \"caffe2/utils/math.h\"\n\n\nnamespace caffe2 {\n\nstruct LogCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* device_context) {\n    math::Log<T, CPUContext>(n, x, y, device_context);\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    Log,\n    UnaryElementwiseOp<TensorTypes<float>, CPUContext, LogCPUFunctor>);\n\nOPERATOR_SCHEMA(Log)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nCalculates the natural log of the given input tensor, element-wise. This\noperation can be done in an in-place fashion too, by providing the same input\nand output blobs.\n)DOC\")\n    .Input(0, \"input\", \"Input tensor\")\n    .Output(\n        0,\n        \"output\",\n        \"The natural log of the input tensor computed \"\n        \"element-wise\");\n\nclass GetLogGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"Div\",\n        \"\",\n        std::vector<string>{GO(0), I(0)},\n        std::vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Log, GetLogGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/log_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/math_ops.h\"\n\nnamespace caffe2 {\n\nstruct LogCUDAFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CUDAContext* device_context) {\n    math::Log<T, CUDAContext>(n, x, y, device_context);\n  }\n};\n\nREGISTER_CUDA_OPERATOR(\n    Log,\n    UnaryElementwiseOp<TensorTypes<float>, CUDAContext, LogCUDAFunctor>);\n}\n"
  },
  {
    "path": "caffe2/operators/logit_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#include \"caffe2/operators/logit_op.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\nstruct LogitCPUFunctor {\n  explicit LogitCPUFunctor(OperatorBase& op)\n      : eps_(op.GetSingleArgument<float>(\"eps\", 1e-6f)) {\n    CAFFE_ENFORCE_GT(eps_, 0.0);\n    CAFFE_ENFORCE_LT(eps_, 0.5);\n  }\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* /* unused */) {\n    ConstEigenArrayMap<T> X(x, n, 1);\n    EigenArrayMap<T> Y(y, n, 1);\n    const T k_one = 1.0;\n\n    Y = X.min(k_one - eps_);\n    Y = Y.max(eps_);\n    Y = (Y / (k_one - Y)).log();\n  }\n\n private:\n  float eps_;\n};\n\ntemplate <>\nbool LogitGradientOp<float, CPUContext>::RunOnDevice() {\n  const auto& X = Input(0);\n  const auto& dY = Input(1);\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n  int channels = X.dim32(X.ndim() - 1);\n  ConstEigenArrayMap<float> Xmat(\n      X.template data<float>(), channels, X.size() / channels);\n  ConstEigenArrayMap<float> dYmat(\n      dY.template data<float>(), channels, X.size() / channels);\n  EigenArrayMap<float> dXmat(\n      dX->template mutable_data<float>(), channels, X.size() / channels);\n  dXmat = (Xmat < eps_ || Xmat > 1.0 - eps_)\n              .select(0, dYmat * ((1 - Xmat) * Xmat).inverse());\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(\n    Logit,\n    UnaryElementwiseWithArgsOp<\n        TensorTypes<float>,\n        CPUContext,\n        LogitCPUFunctor>);\n\nREGISTER_CPU_OPERATOR(LogitGradient, LogitGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Logit)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nElementwise logit transform: logit(x) = log(x / (1 - x)), where x is the\ninput data clampped in (eps, 1-eps).\n)DOC\")\n    .Arg(\"eps (optional)\", \"small positive epsilon value, the default is 1e-6.\")\n    .Input(0, \"X\", \"input float tensor\")\n    .Output(0, \"Y\", \"output float tensor\");\n\nOPERATOR_SCHEMA(LogitGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .Input(0, \"X\", \"input float tensor\")\n    .Input(1, \"dY\", \"input float tensor\")\n    .Output(0, \"dX\", \"output float tensor\")\n    .Arg(\"eps\", \"small positive epsilon value, the default is 1e-6.\");\n\nclass GetLogitGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return vector<OperatorDef>{CreateOperatorDef(\n        \"LogitGradient\",\n        \"\",\n        std::vector<string>{I(0), GO(0)},\n        std::vector<string>{GI(0)})};\n  }\n};\n\nREGISTER_GRADIENT(Logit, GetLogitGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/logit_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/operators/logit_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void LogitKernel(const int N, const T* X, const float eps, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = fminf(X[i], (1.0 - eps));\n    Y[i] = fmaxf(Y[i], eps);\n    Y[i] = logf(Y[i] / (1.0 - Y[i]));\n  }\n}\n\n__global__ void LogitGradientKernel(\n    const int N,\n    const float* X,\n    const float* dY,\n    const float eps,\n    float* dX) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dX[i] = (X[i] < eps || X[i] > 1.0 - eps) ? 0 : (dY[i] / X[i] / (1 - X[i]));\n  }\n}\n\nstruct LogitCUDAFunctor {\n  explicit LogitCUDAFunctor(OperatorBase& op)\n      : eps_(op.GetSingleArgument<float>(\"eps\", 1e-6)) {\n    CAFFE_ENFORCE_GT(eps_, 0.0);\n    CAFFE_ENFORCE_LT(eps_, 0.5);\n  }\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CUDAContext* device_context) {\n    LogitKernel<T>\n        <<<CAFFE_GET_BLOCKS(n),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           device_context->cuda_stream()>>>(n, x, eps_, y);\n    return;\n  }\n\n private:\n  float eps_;\n};\n\ntemplate <>\nbool LogitGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n  int n = X.size();\n  LogitGradientKernel<<<\n      CAFFE_GET_BLOCKS(n),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      n, X.data<float>(), dY.data<float>(), eps_, dX->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(\n    Logit,\n    UnaryElementwiseWithArgsOp<\n        TensorTypes<float>,\n        CUDAContext,\n        LogitCUDAFunctor>);\nREGISTER_CUDA_OPERATOR(LogitGradient, LogitGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/logit_op.h",
    "content": "#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass LogitGradientOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  LogitGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        eps_(OperatorBase::GetSingleArgument<float>(\"eps\", 1e-6f)) {}\n  ~LogitGradientOp() {}\n\n  bool RunOnDevice() override;\n\n protected:\n  float eps_;\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/loss_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/loss_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(AveragedLoss, AveragedLoss<float, CPUContext>);\nREGISTER_CPU_OPERATOR(AveragedLossGradient,\n                      AveragedLossGradient<float, CPUContext>);\n\nOPERATOR_SCHEMA(AveragedLoss)\n  .NumInputs(1)\n  .NumOutputs(1)\n  .ScalarType(TensorProto::FLOAT)\n  .SetDoc(R\"DOC(\nAveragedLoss takes in a 1-D tensor as input and returns a single output float\nvalue which represents the average of input data (average of the losses).\n)DOC\")\n  .Input(0, \"input\", \"The input data as Tensor\")\n  .Output(0, \"output\", \"The output tensor of size 1 containing the averaged \"\n          \"value.\");\n\nOPERATOR_SCHEMA(AveragedLossGradient).NumInputs(2).NumOutputs(1);\n\nclass GetAveragedLossGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"AveragedLossGradient\", \"\",\n        vector<string>{I(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(AveragedLoss, GetAveragedLossGradient);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/loss_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/loss_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(AveragedLoss, AveragedLoss<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    AveragedLossGradient,\n    AveragedLossGradient<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/loss_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_LOSS_OP_H_\n#define CAFFE2_OPERATORS_LOSS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/reduction_ops.h\"\n#include \"caffe2/operators/utility_ops.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n// AveragedLoss takes in the input and produces the output loss value as\n// the average of the input.\ntemplate <typename T, class Context>\nclass AveragedLoss final : public SumElementsOp<T, Context> {\n public:\n  AveragedLoss(const OperatorDef& operator_def, Workspace* ws)\n      : SumElementsOp<T, Context>(operator_def, ws, true) {}\n  ~AveragedLoss() {}\n};\n\ntemplate <typename T, class Context>\nclass AveragedLossGradient final : public SumElementsGradientOp<T, Context> {\n public:\n  AveragedLossGradient(const OperatorDef& operator_def, Workspace* ws)\n      : SumElementsGradientOp<T, Context>(operator_def, ws, true) {}\n  ~AveragedLossGradient() {}\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_LOSS_OP_H_\n"
  },
  {
    "path": "caffe2/operators/lp_pool_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// TODO: reduce the apparent redundancy of all the code below.\n#include \"caffe2/operators/pool_op.h\"\n\nnamespace caffe2 {\n\nusing std::min;\nusing std::max;\n\nclass LpPool {};\n\ntemplate <>\nbool PoolOp<float, CPUContext, LpPool>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  ConvPoolOpBase::SetOutputSize(X, Y, X.dim32(1));\n  const auto p = OperatorBase::GetSingleArgument<float>(\"p\", 2.0);\n  const auto inv_p = 1.0 / p;\n\n  const float* Xdata = X.data<float>();\n  float* Ydata = Y->mutable_data<float>();\n  math::Set<float, CPUContext>(Y->size(), 0, Ydata, &context_);\n  // The main loop\n  int channels = X.dim32(1);\n  int height = X.dim32(2);\n  int width = X.dim32(3);\n  int pooled_height = Y->dim32(2);\n  int pooled_width = Y->dim32(3);\n\n  for (int n = 0; n < X.dim32(0); ++n) {\n    for (int c = 0; c < channels; ++c) {\n      for (int ph = 0; ph < pooled_height; ++ph) {\n        for (int pw = 0; pw < pooled_width; ++pw) {\n          int hstart = ph * stride_[0] - pads_[0];\n          int wstart = pw * stride_[1] - pads_[1];\n          int hend = min(hstart + kernel_[0], height);\n          int wend = min(wstart + kernel_[1], width);\n          hstart = max(hstart, 0);\n          wstart = max(wstart, 0);\n          const int pool_index = ph * pooled_width + pw;\n          for (int h = hstart; h < hend; ++h) {\n            for (int w = wstart; w < wend; ++w) {\n              const int input_index = h * width + w;\n              Ydata[pool_index] += std::pow(std::abs(Xdata[input_index]), p);\n            }\n          }\n          Ydata[pool_index] = std::pow(Ydata[pool_index], inv_p);\n        }\n      }\n      // Do offset.\n      Xdata += height * width;\n      Ydata += pooled_height * pooled_width;\n    }\n  }\n  return true;\n}\n\ntemplate <>\nbool PoolOp<float, CPUContext, LpPool>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  int height = X.dim32(1);\n  int width = X.dim32(2);\n  int channels = X.dim32(3);\n  ConvPoolOpBase::SetOutputSize(X, Y, channels);\n\n  const auto p = OperatorBase::GetSingleArgument<float>(\"p\", 2.0);\n  const auto inv_p = 1.0 / p;\n\n  const float* Xdata = X.data<float>();\n  float* Ydata = Y->mutable_data<float>();\n  math::Set<float, CPUContext>(Y->size(), 0, Ydata, &context_);\n  // The main loop\n  int pooled_height = Y->dim32(1);\n  int pooled_width = Y->dim32(2);\n  for (int n = 0; n < X.dim32(0); ++n) {\n    for (int ph = 0; ph < pooled_height; ++ph) {\n      for (int pw = 0; pw < pooled_width; ++pw) {\n        int hstart = ph * stride_[0] - pads_[0];\n        int wstart = pw * stride_[1] - pads_[1];\n        int hend = min(hstart + kernel_[0], height);\n        int wend = min(wstart + kernel_[1], width);\n        hstart = max(hstart, 0);\n        wstart = max(wstart, 0);\n        const int pool_index = (ph * pooled_width + pw) * channels;\n        for (int h = hstart; h < hend; ++h) {\n          for (int w = wstart; w < wend; ++w) {\n            const int input_index = (h * width + w) * channels;\n            for (int c = 0; c < channels; ++c) {\n              Ydata[pool_index + c] +=\n                  std::pow(std::abs(Xdata[input_index + c]), p);\n            }\n          }\n        }\n        for (int c = 0; c < channels; ++c) {\n          Ydata[pool_index + c] = std::pow(Ydata[pool_index + c], inv_p);\n        }\n      }\n    }\n    // Do offset.\n    Xdata += X.size() / X.dim32(0);\n    Ydata += Y->size() / Y->dim32(0);\n  }\n  return true;\n}\n\ntemplate <>\nbool PoolGradientOp<float, CPUContext, LpPool>::RunOnDeviceWithOrderNCHW() {\n  const auto& X = Input(0);\n  const auto& Y = Input(1);\n  auto& dY = Input(2);\n  auto* dX = Output(0);\n  const auto p = OperatorBase::GetSingleArgument<float>(\"p\", 2.0);\n  const auto inv_p = 1.0 / p;\n\n  // TODO(Yangqing): Add shape checks.\n  dX->ResizeLike(X);\n  math::Set<float, CPUContext>(\n      X.size(), 0, dX->mutable_data<float>(), &context_);\n  const float* dYdata = dY.data<float>();\n  const float* Xdata = X.data<float>();\n  const float* Ydata = Y.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n\n  int channels = X.dim32(1);\n  CAFFE_ENFORCE_EQ(channels, dY.dim32(1));\n  int height = X.dim32(2);\n  int width = X.dim32(3);\n  ConvPoolOpBase<CPUContext>::ComputePads({height, width});\n  int pooled_height = dY.dim32(2);\n  int pooled_width = dY.dim32(3);\n  // The main loop\n  for (int n = 0; n < X.dim32(0); ++n) {\n    for (int c = 0; c < channels; ++c) {\n      for (int ph = 0; ph < pooled_height; ++ph) {\n        for (int pw = 0; pw < pooled_width; ++pw) {\n          int hstart = ph * stride_[0] - pads_[0];\n          int wstart = pw * stride_[1] - pads_[1];\n          int hend = min(hstart + kernel_[0], height);\n          int wend = min(wstart + kernel_[1], width);\n          hstart = max(hstart, 0);\n          wstart = max(wstart, 0);\n          float scale = 1. / (hend - hstart) / (wend - wstart);\n          for (int h = hstart; h < hend; ++h) {\n            for (int w = wstart; w < wend; ++w) {\n              // gradient of p-norm is x_j * |x_j|^{p-2} / |x|_p^{p-1}\n              dXdata[h * width + w] += dYdata[ph * pooled_width + pw] *\n                  Xdata[h * width + w] *\n                  std::pow(std::abs(Xdata[h * width + w]), p - 2) /\n                  std::pow(Ydata[ph * pooled_width + pw], p - 1);\n            }\n          }\n        }\n      }\n      // offset\n      dXdata += height * width;\n      dYdata += pooled_height * pooled_width;\n      Ydata += pooled_height * pooled_width;\n      Xdata += height * width;\n    }\n  }\n  return true;\n}\n\ntemplate <>\nbool PoolGradientOp<float, CPUContext, LpPool>::RunOnDeviceWithOrderNHWC() {\n  const auto& X = Input(0);\n  const auto& Y = Input(1);\n  auto& dY = Input(2);\n  CAFFE_ENFORCE_EQ(dY.ndim(), 4);\n  auto* dX = Output(0);\n  // TODO(Yangqing): Add shape checks.\n  dX->ResizeLike(X);\n  math::Set<float, CPUContext>(\n      X.size(), 0, dX->mutable_data<float>(), &context_);\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  const float* Xdata = X.data<float>();\n  const float* Ydata = Y.data<float>();\n  // The main loop\n  int height = X.dim32(1);\n  int width = X.dim32(2);\n  ConvPoolOpBase<CPUContext>::ComputePads({height, width});\n  const auto p = OperatorBase::GetSingleArgument<float>(\"p\", 2.0);\n  const auto inv_p = 1.0 / p;\n\n  int pooled_height = dY.dim32(1);\n  int pooled_width = dY.dim32(2);\n  int channels = X.dim32(3);\n  CAFFE_ENFORCE_EQ(channels, dY.dim32(3));\n  for (int n = 0; n < X.dim32(0); ++n) {\n    for (int ph = 0; ph < pooled_height; ++ph) {\n      for (int pw = 0; pw < pooled_width; ++pw) {\n        int hstart = ph * stride_[0] - pads_[0];\n        int wstart = pw * stride_[1] - pads_[1];\n        int hend = min(hstart + kernel_[0], height);\n        int wend = min(wstart + kernel_[1], width);\n        hstart = max(hstart, 0);\n        wstart = max(wstart, 0);\n        float scale = 1. / (hend - hstart) / (wend - wstart);\n        for (int h = hstart; h < hend; ++h) {\n          for (int w = wstart; w < wend; ++w) {\n            for (int c = 0; c < channels; ++c) {\n              dXdata[(h * width + w) * channels + c] +=\n                  dYdata[(ph * pooled_width + pw) * channels + c] *\n                  Xdata[(h * width + w) * channels + c] *\n                  std::pow(\n                      std::abs(Xdata[(h * width + w) * channels + c]), p - 2) /\n                  std::pow(\n                      Ydata[(ph * pooled_width + pw) * channels + c], p - 1);\n            }\n          }\n        }\n      }\n    }\n    // offset\n    dXdata += X.size() / X.dim32(0);\n    dYdata += dY.size() / dY.dim32(0);\n    Xdata += X.size() / X.dim32(0);\n    Ydata += Y.size() / Y.dim32(0);\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(LpPool, PoolOp<float, CPUContext, LpPool>);\nREGISTER_CPU_OPERATOR(\n    LpPoolGradient,\n    PoolGradientOp<float, CPUContext, LpPool>);\n\nOPERATOR_SCHEMA(LpPool)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nLpPool consumes an input blob X and applies L-p pooling across the\nthe blob according to kernel sizes, stride sizes, and pad lengths defined by the\nConvPoolOpBase operator. L-p pooling consisting of taking the L-p norm of a\nsubset of the input tensor according to the kernel size and downsampling the\ndata into the output blob Y for further processing.\n)DOC\")\n    .Input(\n        0,\n        \"X\",\n        \"Input data tensor from the previous operator; dimensions \"\n        \"depend on whether the NCHW or NHWC operators are being used. For example, \"\n        \"in the former, the input has size (N x C x H x W), where N is the batch \"\n        \"size, C is the number of channels, and H and W are the height and the width \"\n        \"of the data. The corresponding permutation of dimensions is used in the \"\n        \"latter case. \")\n    .Output(\n        0,\n        \"Y\",\n        \"Output data tensor from L-p pooling across the input \"\n        \"tensor. Dimensions will vary based on various kernel, stride, and pad \"\n        \"sizes.\");\n\nOPERATOR_SCHEMA(LpPoolGradient).NumInputs(3).NumOutputs(1);\n\nclass GetPoolGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        def_.type() + \"Gradient\",\n        \"\",\n        vector<string>{I(0), O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(LpPool, GetPoolGradient);\n}\n"
  },
  {
    "path": "caffe2/operators/lp_pool_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// TODO: reduce the apparent redundancy of all the code below.\n#include <cfloat>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/pool_op.h\"\n\nnamespace caffe2 {\nnamespace {\nclass LpPool {};\n} // namespace\n\nnamespace {\ntemplate <typename T>\ninline __device__ T cuda_pow(T x, T y);\n\ntemplate <typename T>\ninline __device__ T cuda_abs(T x);\n\ntemplate <>\ninline __device__ float cuda_pow<float>(float x, float y) {\n  return powf(x, y);\n}\ntemplate <>\ninline __device__ double cuda_pow<double>(double x, double y) {\n  return pow(x, y);\n}\n\ntemplate <>\ninline __device__ float cuda_abs(float x) {\n  return fabsf(x);\n}\ntemplate <>\ninline __device__ double cuda_abs(double x) {\n  return fabs(x);\n}\n}\n\nnamespace {\ntemplate <typename T>\n__global__ void LpPoolForwardNCHW(\n    const int nthreads,\n    const T* bottom_data,\n    const int num,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l,\n    T* top_data,\n    const T p) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index;\n    int pw = n % pooled_width;\n    n /= pooled_width;\n    int ph = n % pooled_height;\n    n /= pooled_height;\n    int c = n % channels;\n    n /= channels;\n    int hstart = ph * stride_h - pad_t;\n    int wstart = pw * stride_w - pad_l;\n    int hend = min(hstart + kernel_h, height);\n    int wend = min(wstart + kernel_w, width);\n    hstart = max(hstart, 0);\n    wstart = max(wstart, 0);\n    top_data[index] = 0;\n    int bottom_offset = (n * channels + c) * height * width;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        top_data[index] += cuda_pow<T>(\n            cuda_abs(bottom_data[bottom_offset + h * width + w]), p);\n      }\n    }\n    top_data[index] = cuda_pow<T>(top_data[index], 1.0 / p);\n  }\n}\n\ntemplate <typename T>\n__global__ void LpPoolForwardNHWC(\n    const int nthreads,\n    const T* bottom_data,\n    const int num,\n    const int height,\n    const int width,\n    const int channels,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l,\n    T* top_data,\n    const T p) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int c = index % channels;\n    int pw = (index / channels) % pooled_width;\n    int ph = (index / channels / pooled_width) % pooled_height;\n    int n = index / channels / pooled_width / pooled_height;\n    int hstart = ph * stride_h - pad_t;\n    int wstart = pw * stride_w - pad_l;\n    int hend = min(hstart + kernel_h, height);\n    int wend = min(wstart + kernel_w, width);\n    hstart = max(hstart, 0);\n    wstart = max(wstart, 0);\n    T output = 0;\n    int bottom_offset = n * height * width * channels + c;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        output += cuda_pow<T>(\n            cuda_abs(bottom_data[bottom_offset + (h * width + w) * channels]),\n            p);\n      }\n    }\n    top_data[index] = cuda_pow<T>(output, 1.0 / p);\n  }\n}\n\ntemplate <typename T>\n__global__ void LpPoolBackwardNCHW(\n    const int nthreads,\n    const T* const top_diff,\n    const T* const top_data,\n    const T* const bottom_data,\n    const int num,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l,\n    T* const bottom_diff,\n    const int p) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int w = index % width + pad_l;\n    const int h = (index / width) % height + pad_t;\n    const int c = (index / width / height) % channels;\n    const int n = index / width / height / channels;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    const int pwstart = (w < kernel_w) ? 0 : (w - kernel_w) / stride_w + 1;\n    const int pwend = min(w / stride_w + 1, pooled_width);\n    T gradient = 0;\n    const T* const top_diff_slice =\n        top_diff + (n * channels + c) * pooled_height * pooled_width;\n    const T* const top_data_slice =\n        top_data + (n * channels + c) * pooled_height * pooled_width;\n\n    for (int ph = phstart; ph < phend; ++ph) {\n      for (int pw = pwstart; pw < pwend; ++pw) {\n        // figure out the pooling size\n        int hstart = ph * stride_h - pad_t;\n        int wstart = pw * stride_w - pad_l;\n        int hend = min(hstart + kernel_h, height);\n        int wend = min(wstart + kernel_w, width);\n        hstart = max(hstart, 0);\n        wstart = max(wstart, 0);\n        gradient += top_diff_slice[ph * pooled_width + pw] *\n            bottom_data[index] *\n            cuda_pow<T>(cuda_abs(bottom_data[index]), p - 2) /\n            cuda_pow<T>(top_data_slice[ph * pooled_width + pw], p - 1);\n      }\n    }\n    bottom_diff[index] = gradient;\n  }\n}\n\ntemplate <typename T>\n__global__ void LpPoolBackwardNHWC(\n    const int nthreads,\n    const T* const top_diff,\n    const T* const top_data,\n    const T* const bottom_data,\n    const int num,\n    const int height,\n    const int width,\n    const int channels,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l,\n    T* const bottom_diff,\n    const T p) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int c = index % channels;\n    const int w = index / channels % width + pad_l;\n    const int h = (index / channels / width) % height + pad_t;\n    const int n = index / channels / width / height;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    const int pwstart = (w < kernel_w) ? 0 : (w - kernel_w) / stride_w + 1;\n    const int pwend = min(w / stride_w + 1, pooled_width);\n    T gradient = 0;\n    const T* const top_diff_slice =\n        top_diff + n * pooled_height * pooled_width * channels + c;\n    const T* const top_data_slice =\n        top_data + n * pooled_height * pooled_width * channels + c;\n    for (int ph = phstart; ph < phend; ++ph) {\n      for (int pw = pwstart; pw < pwend; ++pw) {\n        // figure out the pooling size\n        int hstart = ph * stride_h - pad_t;\n        int wstart = pw * stride_w - pad_l;\n        int hend = min(hstart + kernel_h, height);\n        int wend = min(wstart + kernel_w, width);\n        hstart = max(hstart, 0);\n        wstart = max(wstart, 0);\n        gradient += top_diff_slice[(ph * pooled_width + pw) * channels] *\n            bottom_data[index] *\n            cuda_pow<T>(cuda_abs(bottom_data[index]), p - 2) /\n            cuda_pow<T>(top_data_slice[(ph * pooled_width + pw) * channels],\n                        p - 1);\n      }\n    }\n    bottom_diff[index] = gradient;\n  }\n}\n\n} // namespace\n\ntemplate <>\nbool PoolOp<float, CUDAContext, LpPool>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  ConvPoolOpBase<CUDAContext>::SetOutputSize(X, Y, X.dim32(1));\n  int output_size = Y->size();\n  LpPoolForwardNCHW<float><<<\n      CAFFE_GET_BLOCKS(output_size),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      output_size,\n      X.data<float>(),\n      X.dim32(0),\n      X.dim32(1),\n      X.dim32(2),\n      X.dim32(3),\n      Y->dim32(2),\n      Y->dim32(3),\n      kernel_h(),\n      kernel_w(),\n      stride_h(),\n      stride_w(),\n      pad_t(),\n      pad_l(),\n      Y->mutable_data<float>(),\n      OperatorBase::GetSingleArgument<float>(\"p\", 2.0));\n  return true;\n}\n\ntemplate <>\nbool PoolOp<float, CUDAContext, LpPool>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  ConvPoolOpBase<CUDAContext>::SetOutputSize(X, Y, X.dim32(3));\n  int output_size = Y->size();\n  LpPoolForwardNHWC<float><<<\n      CAFFE_GET_BLOCKS(output_size),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      output_size,\n      X.data<float>(),\n      X.dim32(0),\n      X.dim32(1),\n      X.dim32(2),\n      X.dim32(3),\n      Y->dim32(1),\n      Y->dim32(2),\n      kernel_h(),\n      kernel_w(),\n      stride_h(),\n      stride_w(),\n      pad_t(),\n      pad_l(),\n      Y->mutable_data<float>(),\n      OperatorBase::GetSingleArgument<float>(\"p\", 2.0));\n  return true;\n}\n\ntemplate <>\nbool PoolGradientOp<float, CUDAContext, LpPool>::\n    RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dY = Input(2);\n  CAFFE_ENFORCE_EQ(dY.ndim(), 4);\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n  ConvPoolOpBase<CUDAContext>::ComputePads({X.dim32(2), X.dim32(3)});\n  LpPoolBackwardNCHW<float><<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(),\n      dY.data<float>(),\n      Y.data<float>(),\n      X.data<float>(),\n      X.dim32(0),\n      X.dim32(1),\n      X.dim32(2),\n      X.dim32(3),\n      dY.dim32(2),\n      dY.dim32(3),\n      kernel_h(),\n      kernel_w(),\n      stride_h(),\n      stride_w(),\n      pad_t(),\n      pad_l(),\n      dX->mutable_data<float>(),\n      OperatorBase::GetSingleArgument<float>(\"p\", 2.0));\n  return true;\n}\n\ntemplate <>\nbool PoolGradientOp<float, CUDAContext, LpPool>::\n    RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dY = Input(2);\n  CAFFE_ENFORCE_EQ(dY.ndim(), 4);\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n  ConvPoolOpBase<CUDAContext>::ComputePads({X.dim32(1), X.dim32(2)});\n  LpPoolBackwardNHWC<float><<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(),\n      dY.data<float>(),\n      Y.data<float>(),\n      X.data<float>(),\n      X.dim32(0),\n      X.dim32(1),\n      X.dim32(2),\n      X.dim32(3),\n      dY.dim32(1),\n      dY.dim32(2),\n      kernel_h(),\n      kernel_w(),\n      stride_h(),\n      stride_w(),\n      pad_t(),\n      pad_l(),\n      dX->mutable_data<float>(),\n      OperatorBase::GetSingleArgument<float>(\"p\", 2.0));\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(LpPool, PoolOp<float, CUDAContext, LpPool>);\nREGISTER_CUDA_OPERATOR(\n    LpPoolGradient,\n    PoolGradientOp<float, CUDAContext, LpPool>);\n}\n"
  },
  {
    "path": "caffe2/operators/lpnorm_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/lpnorm_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool LpNormOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(X_IN);\n  auto* norm = Output(OUT);\n  norm->Resize(1);\n  const float* X_data = X.data<float>();\n  if (p_ == 1) {\n    *(norm->mutable_data<float>()) =\n        (ConstEigenVectorMap<float>(X_data, X.size()).array()).abs().sum();\n    // L1(x) = sum(|x|)\n  } else if (p_ == 2) {\n    *(norm->mutable_data<float>()) =\n        (ConstEigenVectorMap<float>(X_data, X.size()).array()).square().sum();\n    // L2(x) = (sum(|x|^2))\n  }\n  return true;\n}\n\ntemplate <>\nbool LpNormGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(X_IN);\n  auto& dnorm = Input(DER_NORM_IN);\n  auto* dX = Output(DER_X_OUT);\n  CAFFE_ENFORCE_EQ(dnorm.ndim(), 1);\n  CAFFE_ENFORCE_EQ(dnorm.dim32(0), 1);\n  dX->ResizeLike(X);\n  const float kEps = 1e-12f;\n\n  if (p_ == 1) {\n    // Todo: implement in eigen\n    for (int i = 0; i < X.size(); ++i) {\n      float temp = (X.data<float>())[i];\n      if (temp < -kEps) {\n        dX->mutable_data<float>()[i] = -(dnorm.data<float>())[0];\n      } else if (temp > kEps) {\n        dX->mutable_data<float>()[i] = (dnorm.data<float>())[0];\n      } else {\n        dX->mutable_data<float>()[i] = 0;\n      }\n    }\n  } else if (p_ == 2) {\n    EigenVectorMap<float>(dX->mutable_data<float>(), X.size()).array() =\n        ConstEigenVectorMap<float>(X.data<float>(), X.size()).array() * 2.0f *\n        (dnorm.data<float>())[0];\n  }\n\n  return true;\n}\n\nnamespace {\n// LpNorm\nREGISTER_CPU_OPERATOR(LpNorm, LpNormOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(LpNormGradient, LpNormGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LpNorm)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven one input float tensor X, and produces one output float tensor\nof the Lp norm of tensor X, computed as Lp(x) = sum over |x^p|,\nin which p is either 1 or 2(currently only supports l1 and l2 norm),\ndetermined by the argument p.\n)DOC\")\n    .Input(0, \"X\", \"1D input tensor\")\n    .Output(0, \"Z\", \"1D output tensor\")\n    .Arg(\"p\", \"Order of the norm in p-norm\");\n\nOPERATOR_SCHEMA(LpNormGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven one input float tensor X, derivative dout, and produces one output\nfloat tensor dX. dX is the derivative of the Lp norm of tensor X, computed as\ndx = d(sum over |x^p|)/dx, in which p is either 1 or 2(currently only\nsupports l1 and l2 norm) determined by the argument p.\n)DOC\")\n    .Input(0, \"X\", \"1D input tensor\")\n    .Input(1, \"dout\", \"1D input tensor\")\n    .Output(0, \"dx\", \"1D output tensor\")\n    .Arg(\"p\", \"Order of the norm in p-norm\");\n\nclass GetLpNormGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"LpNormGradient\",\n        \"\",\n        vector<string>{I(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(LpNorm, GetLpNormGradient);\n} // namespace\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/lpnorm_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_LPNORM_OP_H_\n#define CAFFE2_OPERATORS_LPNORM_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass LpNormOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  LpNormOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        p_(OperatorBase::GetSingleArgument<int>(\"p\", 2)) {\n    CAFFE_ENFORCE(p_ == 1 || p_ == 2, \"p should be either 1 or 2.\");\n  }\n\n  bool RunOnDevice() override;\n\n protected:\n  int p_;\n  INPUT_TAGS(X_IN);\n  OUTPUT_TAGS(OUT);\n  // Input: X; Output: Norm\n};\n\ntemplate <typename T, class Context>\nclass LpNormGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  LpNormGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        p_(OperatorBase::GetSingleArgument<int>(\"p\", 2)) {\n    CAFFE_ENFORCE(p_ == 1 || p_ == 2, \"p should be either 1 or 2.\");\n  }\n\n  bool RunOnDevice() override;\n\n protected:\n  int p_;\n  INPUT_TAGS(X_IN, DER_NORM_IN);\n  OUTPUT_TAGS(DER_X_OUT);\n  // Input: X, dNorm; Output: dX\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_LPNORM_OP_H_\n"
  },
  {
    "path": "caffe2/operators/lstm_unit_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"lstm_unit_op.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(LSTMUnit, LSTMUnitOp<CPUContext>);\nOPERATOR_SCHEMA(LSTMUnit)\n    .NumInputs(4, 5)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nLSTMUnit computes the activations of a standard LSTM (without peephole\nconnections), in a sequence-length aware fashion.\n\nConcretely, given the (fused) inputs X (TxNxD), the previous cell\nstate (NxD), and the sequence lengths (N), computes the LSTM\nactivations, avoiding computation if the input is invalid (as in, the\nvalue at X{t][n] >= seqLengths[n].\n\n)DOC\")\n    .Arg(\"forget_bias\", \"Bias term to add in while calculating forget gate\")\n    .Arg(\n        \"sequence_lengths\",\n        \"When false, the sequence lengths input is left out, \"\n        \"and all following inputs are shifted left by one.\");\nREGISTER_CPU_OPERATOR(LSTMUnitGradient, LSTMUnitGradientOp<CPUContext>);\nOPERATOR_SCHEMA(LSTMUnitGradient)\n    .NumInputs(8, 9)\n    .NumOutputs(3)\n    .Arg(\n        \"sequence_lengths\",\n        \"When false, the sequence lengths input is left out, \"\n        \"and all following inputs are shifted left by one.\");\n\nclass GetLSTMUnitGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    if (GetFlagArgument(def_, \"sequence_lengths\", true)) {\n      return SingleGradientDef(\n          \"LSTMUnitGradient\",\n          \"\",\n          vector<string>{\n              I(0), I(1), I(2), I(3), I(4), O(0), O(1), GO(0), GO(1)},\n          vector<string>{GI(0), GI(1), GI(2)});\n    } else {\n      return SingleGradientDef(\n          \"LSTMUnitGradient\",\n          \"\",\n          vector<string>{I(0), I(1), I(2), I(3), O(0), O(1), GO(0), GO(1)},\n          vector<string>{GI(0), GI(1), GI(2)});\n    }\n  }\n};\nREGISTER_GRADIENT(LSTMUnit, GetLSTMUnitGradient);\n}\n"
  },
  {
    "path": "caffe2/operators/lstm_unit_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_LSTM_UNIT_OP_H_\n#define CAFFE2_OPERATORS_LSTM_UNIT_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/conversions.h\"\n\nnamespace caffe2 {\nnamespace detail {\ntemplate <typename T>\ninline T sigmoid(T x) {\n  return 1. / (1. + exp(-x));\n}\n\ntemplate <typename T>\ninline T host_tanh(T x) {\n  return 2. * sigmoid(2. * x) - 1.;\n}\n\ntemplate <typename T, typename Context>\nvoid LSTMUnit(\n    int N,\n    int D,\n    int t,\n    const T* H_prev,\n    const T* C_prev,\n    const T* X,\n    const int32_t* seqLengths,\n    bool drop_states,\n    T* C,\n    T* H,\n    const float forget_bias,\n    Context* /*context*/) {\n  for (int n = 0; n < N; ++n) {\n    const bool valid = seqLengths == nullptr || t < seqLengths[n];\n\n    for (int d = 0; d < D; ++d) {\n      if (!valid) {\n        if (drop_states) {\n          H[d] = 0;\n          C[d] = 0;\n        } else {\n          H[d] = H_prev[d];\n          C[d] = C_prev[d];\n        }\n      } else {\n        const T i = sigmoid(X[d]);\n        const T f = sigmoid(X[1 * D + d] + convert::To<float, T>(forget_bias));\n        const T o = sigmoid(X[2 * D + d]);\n        const T g = host_tanh(X[3 * D + d]);\n        const T c_prev = C_prev[d];\n        const T c = f * c_prev + i * g;\n        C[d] = c;\n        const T host_tanh_c = host_tanh(c);\n        H[d] = o * host_tanh_c;\n      }\n    }\n    H_prev += D;\n    C_prev += D;\n    X += 4 * D;\n    C += D;\n    H += D;\n  }\n}\n\ntemplate <typename T, typename Context>\nvoid LSTMUnitGradient(\n    int N,\n    int D,\n    int t,\n    const T* C_prev,\n    const T* X,\n    const int32_t* seqLengths,\n    const T* C,\n    const T* H,\n    const T* C_diff,\n    const T* H_diff,\n    bool drop_states,\n    T* H_prev_diff,\n    T* C_prev_diff,\n    T* X_diff,\n    const float forget_bias,\n    Context* /*context*/) {\n  for (int n = 0; n < N; ++n) {\n    const bool valid = seqLengths == nullptr || t < seqLengths[n];\n\n    for (int d = 0; d < D; ++d) {\n      T* c_prev_diff = C_prev_diff + d;\n      T* h_prev_diff = H_prev_diff + d;\n      T* i_diff = X_diff + d;\n      T* f_diff = X_diff + 1 * D + d;\n      T* o_diff = X_diff + 2 * D + d;\n      T* g_diff = X_diff + 3 * D + d;\n\n      if (!valid) {\n        if (drop_states) {\n          *h_prev_diff = 0;\n          *c_prev_diff = 0;\n        } else {\n          *h_prev_diff = H_diff[d];\n          *c_prev_diff = C_diff[d];\n        }\n        *i_diff = 0;\n        *f_diff = 0;\n        *o_diff = 0;\n        *g_diff = 0;\n      } else {\n        const T i = sigmoid(X[d]);\n        const T f = sigmoid(X[1 * D + d] + convert::To<float, T>(forget_bias));\n        const T o = sigmoid(X[2 * D + d]);\n        const T g = host_tanh(X[3 * D + d]);\n        const T c_prev = C_prev[d];\n        const T c = C[d];\n        const T host_tanh_c = host_tanh(c);\n        const T c_term_diff = C_diff[d] + H_diff[d] * o * (1 - host_tanh_c * host_tanh_c);\n        *c_prev_diff = c_term_diff * f;\n        *h_prev_diff = 0; // not used in 'valid' case\n        *i_diff = c_term_diff * g * i * (1 - i);\n        *f_diff = c_term_diff * c_prev * f * (1 - f);\n        *o_diff = H_diff[d] * host_tanh_c * o * (1 - o);\n        *g_diff = c_term_diff * i * (1 - g * g);\n      }\n    }\n    C_prev += D;\n    X += 4 * D;\n    C += D;\n    H += D;\n    C_diff += D;\n    H_diff += D;\n    X_diff += 4 * D;\n    H_prev_diff += D;\n    C_prev_diff += D;\n  }\n}\n} // namespace detail\n\ntemplate <typename Context>\nclass LSTMUnitOp : public Operator<Context> {\n public:\n  LSTMUnitOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        forget_bias_(\n            static_cast<float>(OperatorBase::template GetSingleArgument<float>(\n                \"forget_bias\",\n                0.0))),\n        sequence_lengths_(OperatorBase::template GetSingleArgument<bool>(\n            \"sequence_lengths\",\n            true)),\n        drop_states_(OperatorBase::template GetSingleArgument<bool>(\n            \"drop_states\",\n            false)) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using Operator<Context>::Operator;\n\n  template <typename T>\n  bool DoRunWithType() {\n    // handle potentially-missing sequence lengths input\n    const size_t TIMESTEP = SEQ_LENGTHS + (sequence_lengths_ ? 1 : 0);\n\n    // Extract N\n    const auto N = Input(CELL_T_M_1).dim(1);\n\n    // Gates: 1xNxG\n    const auto G = Input(GATES).dim(2);\n    const auto D = Input(CELL_T_M_1).dim(2);\n\n    CAFFE_ENFORCE_EQ(4 * D, G);\n    const auto* H_prev = Input(HIDDEN_T_M_1).template data<T>();\n    const auto* C_prev = Input(CELL_T_M_1).template data<T>();\n    const auto* X = Input(GATES).template data<T>();\n\n    const int32_t* seqLengths = nullptr;\n    if (sequence_lengths_) {\n      CAFFE_ENFORCE_EQ(Input(SEQ_LENGTHS).size(), N);\n      seqLengths = Input(SEQ_LENGTHS).template data<int32_t>();\n    }\n\n    const auto t = static_cast<OperatorBase*>(this)\n                       ->Input<Tensor<CPUContext>>(TIMESTEP)\n                       .template data<int32_t>()[0];\n    Output(CELL_T)->ResizeLike(Input(CELL_T_M_1));\n    auto* C = Output(CELL_T)->template mutable_data<T>();\n    Output(HIDDEN_T)->ResizeLike(Input(CELL_T_M_1));\n    auto* H = Output(HIDDEN_T)->template mutable_data<T>();\n    detail::LSTMUnit<T, Context>(\n        N,\n        D,\n        t,\n        H_prev,\n        C_prev,\n        X,\n        seqLengths,\n        drop_states_,\n        C,\n        H,\n        forget_bias_,\n        &context_);\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    return DoRunWithType<float>();\n  }\n\n protected:\n  INPUT_TAGS(HIDDEN_T_M_1, CELL_T_M_1, GATES, SEQ_LENGTHS);\n  // additional input tags are determined dynamically based on whether\n  // sequence_lengths is present.\n  OUTPUT_TAGS(HIDDEN_T, CELL_T);\n\n  float forget_bias_;\n  bool sequence_lengths_;\n\n private:\n  bool drop_states_;\n};\n\ntemplate <typename Context>\nclass LSTMUnitGradientOp : public Operator<Context> {\n public:\n  LSTMUnitGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        forget_bias_(\n            static_cast<float>(OperatorBase::template GetSingleArgument<float>(\n                \"forget_bias\",\n                0.0))),\n        sequence_lengths_(OperatorBase::template GetSingleArgument<bool>(\n            \"sequence_lengths\",\n            true)),\n        drop_states_(OperatorBase::template GetSingleArgument<bool>(\n            \"drop_states\",\n            false)) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  template <typename T>\n  bool DoRunWithType() {\n    // handle potentially-missing sequence lengths input\n    const size_t inputOffset = SEQ_LENGTHS + (sequence_lengths_ ? 1 : 0);\n    const size_t TIMESTEP = inputOffset;\n    const size_t HIDDEN_T = inputOffset + 1;\n    const size_t CELL_T = inputOffset + 2;\n    const size_t HIDDEN_T_GRAD = inputOffset + 3;\n    const size_t CELL_T_GRAD = inputOffset + 4;\n\n    // Extract N\n    const auto N = Input(CELL_T_M_1).dim(1);\n\n    // Gates: 1xNxG\n    const auto G = Input(GATES).dim(2);\n    const auto D = Input(CELL_T_M_1).dim(2);\n\n    CAFFE_ENFORCE_EQ(4 * D, G);\n    const auto* C_prev = Input(CELL_T_M_1).template data<T>();\n    const auto* X = Input(GATES).template data<T>();\n    const auto t = static_cast<OperatorBase*>(this)\n                       ->Input<Tensor<CPUContext>>(TIMESTEP)\n                       .template data<int32_t>()[0];\n    const auto* C = Input(CELL_T).template data<T>();\n    const auto* H = Input(HIDDEN_T).template data<T>();\n    const auto* C_diff = Input(CELL_T_GRAD).template data<T>();\n    const auto* H_diff = Input(HIDDEN_T_GRAD).template data<T>();\n\n    const int32_t* seqLengths = nullptr;\n    if (sequence_lengths_) {\n      CAFFE_ENFORCE_EQ(Input(SEQ_LENGTHS).size(), N);\n      seqLengths = Input(SEQ_LENGTHS).template data<int32_t>();\n    }\n\n    Output(HIDDEN_T_M_1_GRAD)->ResizeLike(Input(HIDDEN_T_M_1));\n    auto* H_prev_diff = Output(HIDDEN_T_M_1_GRAD)->template mutable_data<T>();\n    Output(CELL_T_M_1_GRAD)->ResizeLike(Input(CELL_T_M_1));\n    auto* C_prev_diff = Output(CELL_T_M_1_GRAD)->template mutable_data<T>();\n    Output(GATES_GRAD)->ResizeLike(Input(GATES));\n    auto* X_diff = Output(GATES_GRAD)->template mutable_data<T>();\n\n    detail::LSTMUnitGradient<T, Context>(\n        N,\n        D,\n        t,\n        C_prev,\n        X,\n        seqLengths,\n        C,\n        H,\n        C_diff,\n        H_diff,\n        drop_states_,\n        H_prev_diff,\n        C_prev_diff,\n        X_diff,\n        forget_bias_,\n        &context_);\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    return DoRunWithType<float>();\n  }\n\n protected:\n  INPUT_TAGS(HIDDEN_T_M_1, CELL_T_M_1, GATES, SEQ_LENGTHS);\n  // additional input tags are determined dynamically based on whether\n  // sequence_lengths is present.\n  OUTPUT_TAGS(HIDDEN_T_M_1_GRAD, CELL_T_M_1_GRAD, GATES_GRAD);\n\n  float forget_bias_;\n  bool sequence_lengths_;\n\n private:\n  bool drop_states_;\n};\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_LSTM_UNIT_OP_H_\n"
  },
  {
    "path": "caffe2/operators/lstm_unit_op_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <algorithm>\n#include <cmath>\n#include <vector>\n#include \"caffe2/core/context_gpu.h\"\n#include \"lstm_unit_op.h\"\n\nnamespace caffe2 {\n\nnamespace detail {\n\ntemplate <typename Dtype>\n__device__ Dtype cuda_sigmoid(const Dtype x) {\n  return Dtype(1) / (Dtype(1) + exp(-x));\n}\n\ntemplate <typename T, typename MATH>\n__global__ void LSTMUnitKernel(\n    const int nthreads,\n    const int dim,\n    const int t,\n    const T* H_prev,\n    const T* C_prev,\n    const T* X,\n    const int32_t* seqLengths,\n    bool drop_states,\n    T* C,\n    T* H,\n    const MATH forget_bias) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    const int n = index / dim;\n    const int d = index % dim;\n    const bool valid = seqLengths == nullptr || t < seqLengths[n];\n    if (!valid) {\n      H[index] = convert::To<MATH, T>(convert::To<T, MATH>(H_prev[index]) * !drop_states);\n      C[index] = convert::To<MATH, T>(convert::To<T, MATH>(C_prev[index]) * !drop_states);\n    } else {\n      const T* X_offset = X + 4 * dim * n;\n      const MATH i = cuda_sigmoid(convert::To<T, MATH>(X_offset[d]));\n      const MATH f = cuda_sigmoid(convert::To<T, MATH>(X_offset[1 * dim + d]) + forget_bias);\n      const MATH o = cuda_sigmoid(convert::To<T, MATH>(X_offset[2 * dim + d]));\n      const MATH g = tanh(convert::To<T, MATH>(X_offset[3 * dim + d]));\n      const MATH c_prev = convert::To<T, MATH>(C_prev[index]);\n      const MATH c = f * c_prev + i * g;\n      C[index] = convert::To<MATH, T>(c);\n      const MATH tanh_c = tanh(c);\n      H[index] = convert::To<MATH, T>(o * tanh_c);\n    }\n  }\n}\n\ntemplate <typename T, typename MATH>\n__global__ void LSTMUnitGradientKernel(\n    const int nthreads,\n    const int dim,\n    const int t,\n    const T* C_prev,\n    const T* X,\n    const T* C,\n    const T* H,\n    const int32_t* seqLengths,\n    const T* C_diff,\n    const T* H_diff,\n    bool drop_states,\n    T* H_prev_diff,\n    T* C_prev_diff,\n    T* X_diff,\n    const MATH forget_bias) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    const int n = index / dim;\n    const bool valid = seqLengths == nullptr || t < seqLengths[n];\n    const int d = index % dim;\n    const T* X_offset = X + 4 * dim * n;\n    T* c_prev_diff = C_prev_diff + index;\n    T* h_prev_diff = H_prev_diff + index;\n    T* X_diff_offset = X_diff + 4 * dim * n;\n    T* i_diff = X_diff_offset + d;\n    T* f_diff = X_diff_offset + 1 * dim + d;\n    T* o_diff = X_diff_offset + 2 * dim + d;\n    T* g_diff = X_diff_offset + 3 * dim + d;\n    if (!valid) {\n      *h_prev_diff = convert::To<MATH, T>(convert::To<T, MATH>(H_diff[index]) *\n                                          !drop_states);\n      *c_prev_diff = convert::To<MATH, T>(convert::To<T, MATH>(C_diff[index]) *\n                                          !drop_states);\n      *i_diff = convert::To<MATH, T>(0);\n      *f_diff = convert::To<MATH, T>(0);\n      *o_diff = convert::To<MATH, T>(0);\n      *g_diff = convert::To<MATH, T>(0);\n    } else {\n      const MATH i = cuda_sigmoid(convert::To<T, MATH>(X_offset[d]));\n      const MATH f = cuda_sigmoid(convert::To<T, MATH>(X_offset[1 * dim + d]) + forget_bias);\n      const MATH o = cuda_sigmoid(convert::To<T, MATH>(X_offset[2 * dim + d]));\n      const MATH g = tanh(convert::To<T, MATH>(X_offset[3 * dim + d]));\n      const MATH c_prev = convert::To<T, MATH>(C_prev[index]);\n      const MATH c = convert::To<T, MATH>(C[index]);\n      const MATH tanh_c = tanh(c);\n      const MATH c_term_diff =\n          convert::To<T, MATH>(C_diff[index]) +\n          convert::To<T, MATH>(H_diff[index]) * o * (1 - tanh_c * tanh_c);\n      *c_prev_diff = convert::To<MATH, T>(c_term_diff * f);\n      *h_prev_diff = convert::To<MATH, T>(0);\n      *i_diff = convert::To<MATH, T>(c_term_diff * g * i * (1 - i));\n      *f_diff = convert::To<MATH, T>(c_term_diff * c_prev * f * (1 - f));\n      *o_diff = convert::To<MATH, T>(\n                  convert::To<T, MATH>(H_diff[index]) * tanh_c * o * (1 - o));\n      *g_diff = convert::To<MATH, T>(c_term_diff * i * (1 - g * g));\n    }\n  }\n}\n\ntemplate <>\nvoid LSTMUnit<float, CUDAContext>(\n    int N,\n    int D,\n    int t,\n    const float* H_prev,\n    const float* C_prev,\n    const float* X,\n    const int32_t* seqLengths,\n    bool drop_states,\n    float* C,\n    float* H,\n    const float forget_bias,\n    CUDAContext* context) {\n  LSTMUnitKernel<float, float><<<\n      CAFFE_GET_BLOCKS(N * D),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(\n      N * D,\n      D,\n      t,\n      H_prev,\n      C_prev,\n      X,\n      seqLengths,\n      drop_states,\n      C,\n      H,\n      forget_bias);\n}\n\ntemplate <>\nvoid LSTMUnit<float16, CUDAContext>(\n    int N,\n    int D,\n    int t,\n    const float16* H_prev,\n    const float16* C_prev,\n    const float16* X,\n    const int32_t* seqLengths,\n    bool drop_states,\n    float16* C,\n    float16* H,\n    const float forget_bias,\n    CUDAContext* context) {\n  LSTMUnitKernel<float16, float><<<\n      CAFFE_GET_BLOCKS(N * D),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(\n      N * D,\n      D,\n      t,\n      H_prev,\n      C_prev,\n      X,\n      seqLengths,\n      drop_states,\n      C,\n      H,\n      forget_bias);\n}\n\ntemplate <>\nvoid LSTMUnitGradient<float, CUDAContext>(\n    int N,\n    int D,\n    int t,\n    const float* C_prev,\n    const float* X,\n    const int32_t* seqLengths,\n    const float* C,\n    const float* H,\n    const float* C_diff,\n    const float* H_diff,\n    bool drop_states,\n    float* H_prev_diff,\n    float* C_prev_diff,\n    float* X_diff,\n    const float forget_bias,\n    CUDAContext* context) {\n  LSTMUnitGradientKernel<float, float><<<\n      CAFFE_GET_BLOCKS(N * D),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(\n      N * D,\n      D,\n      t,\n      C_prev,\n      X,\n      C,\n      H,\n      seqLengths,\n      C_diff,\n      H_diff,\n      drop_states,\n      H_prev_diff,\n      C_prev_diff,\n      X_diff,\n      forget_bias);\n}\n\ntemplate <>\nvoid LSTMUnitGradient<float16, CUDAContext>(\n    int N,\n    int D,\n    int t,\n    const float16* C_prev,\n    const float16* X,\n    const int32_t* seqLengths,\n    const float16* C,\n    const float16* H,\n    const float16* C_diff,\n    const float16* H_diff,\n    bool drop_states,\n    float16* H_prev_diff,\n    float16* C_prev_diff,\n    float16* X_diff,\n    const float forget_bias,\n    CUDAContext* context) {\n  LSTMUnitGradientKernel<float16, float><<<\n      CAFFE_GET_BLOCKS(N * D),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(\n      N * D,\n      D,\n      t,\n      C_prev,\n      X,\n      C,\n      H,\n      seqLengths,\n      C_diff,\n      H_diff,\n      drop_states,\n      H_prev_diff,\n      C_prev_diff,\n      X_diff,\n      forget_bias);\n}\n}\n\ntemplate <>\nbool LSTMUnitOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<float, float16>>::call(this, Input(0));\n}\n\ntemplate <>\nbool LSTMUnitGradientOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<float, float16>>::call(this, Input(0));\n}\n\nREGISTER_CUDA_OPERATOR(LSTMUnit, LSTMUnitOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    LSTMUnitGradient,\n    LSTMUnitGradientOp<CUDAContext>);\n}\n"
  },
  {
    "path": "caffe2/operators/map_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/map_ops.h\"\n\nnamespace caffe2 {\n\nusing MapType64To64 = MapTypeTraits<int64_t, int64_t>::MapType;\nCAFFE_KNOWN_TYPE(MapType64To64);\n\nusing MapType64To32 = MapTypeTraits<int64_t, int32_t>::MapType;\nCAFFE_KNOWN_TYPE(MapType64To32);\n\nusing MapType32To32 = MapTypeTraits<int32_t, int32_t>::MapType;\nCAFFE_KNOWN_TYPE(MapType32To32);\n\nusing MapType32To64 = MapTypeTraits<int32_t, int64_t>::MapType;\nCAFFE_KNOWN_TYPE(MapType32To64);\n\nnamespace {\n\nREGISTER_BLOB_SERIALIZER(\n    TypeMeta::Id<MapType64To64>(),\n    MapSerializer<int64_t, int64_t>);\n\nREGISTER_BLOB_SERIALIZER(\n    TypeMeta::Id<MapType64To32>(),\n    MapSerializer<int64_t, int32_t>);\n\nREGISTER_BLOB_SERIALIZER(\n    TypeMeta::Id<MapType32To32>(),\n    MapSerializer<int32_t, int32_t>);\n\nREGISTER_BLOB_SERIALIZER(\n    TypeMeta::Id<MapType32To64>(),\n    MapSerializer<int32_t, int64_t>);\n\nREGISTER_BLOB_DESERIALIZER(\n    (std::unordered_map<int64_t, int64_t>),\n    MapDeserializer<int64_t, int64_t>);\n\nREGISTER_BLOB_DESERIALIZER(\n    (std::unordered_map<int64_t, int32_t>),\n    MapDeserializer<int64_t, int32_t>);\n\nREGISTER_BLOB_DESERIALIZER(\n    (std::unordered_map<int32_t, int32_t>),\n    MapDeserializer<int32_t, int32_t>);\n\nREGISTER_BLOB_DESERIALIZER(\n    (std::unordered_map<int32_t, int64_t>),\n    MapDeserializer<int32_t, int64_t>);\n\nREGISTER_CPU_OPERATOR(CreateMap, CreateMapOp<CPUContext>);\nREGISTER_CPU_OPERATOR(KeyValueToMap, KeyValueToMapOp<CPUContext>);\nREGISTER_CPU_OPERATOR(MapToKeyValue, MapToKeyValueOp<CPUContext>);\n\nOPERATOR_SCHEMA(CreateMap)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(\"Create an empty map blob\")\n    .Arg(\"key_dtype\", \"Key's TensorProto::DataType (default INT32)\")\n    .Arg(\"value_dtype\", \"Value's TensorProto::DataType (default INT32)\")\n    .Output(0, \"map blob\", \"Blob reference to the map\");\n\nOPERATOR_SCHEMA(KeyValueToMap)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(\"Convert key and value blob pairs into a map blob\")\n    .Input(0, \"key blob\", \"Blob reference to the key\")\n    .Input(1, \"value blob\", \"Blob reference to the value\")\n    .Output(0, \"map blob\", \"Blob reference to the map\");\n\nOPERATOR_SCHEMA(MapToKeyValue)\n    .NumInputs(1)\n    .NumOutputs(2)\n    .SetDoc(\"Convert a map blob into key and value blob pairs\")\n    .Input(0, \"map blob\", \"Blob reference to the map\")\n    .Output(0, \"key blob\", \"Blob reference to the key\")\n    .Output(1, \"value blob\", \"Blob reference to the value\");\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/map_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_MAP_OPS_H_\n#define CAFFE2_OPERATORS_MAP_OPS_H_\n\n#include <algorithm>\n#include <iterator>\n#include <string>\n#include <typeinfo>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\nstruct TypeNameTraits {\n  static constexpr const char* name = \"unknown\";\n};\n\ntemplate <>\nstruct TypeNameTraits<int64_t> {\n  static constexpr const char* name = \"int64_t\";\n};\n\ntemplate <>\nstruct TypeNameTraits<int32_t> {\n  static constexpr const char* name = \"int32_t\";\n};\n\ntemplate <typename KEY_T, typename VALUE_T>\nstruct MapTypeTraits {\n  using MapType = std::unordered_map<KEY_T, VALUE_T>;\n  static string MapTypeName() {\n    return string(\"(std::unordered_map<\") + TypeNameTraits<KEY_T>::name + \", \" +\n        TypeNameTraits<VALUE_T>::name + \">)\";\n  }\n};\n\nusing MapType64To64 = MapTypeTraits<int64_t, int64_t>::MapType;\nusing MapType64To32 = MapTypeTraits<int64_t, int32_t>::MapType;\nusing MapType32To32 = MapTypeTraits<int32_t, int32_t>::MapType;\nusing MapType32To64 = MapTypeTraits<int32_t, int64_t>::MapType;\n\ntemplate <class Context>\nclass CreateMapOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  CreateMapOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  ~CreateMapOp() {}\n\n  bool RunOnDevice() override {\n    TensorProto::DataType key_dtype =\n        static_cast<TensorProto::DataType>(OperatorBase::GetSingleArgument<int>(\n            \"key_dtype\", TensorProto_DataType_INT32));\n\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, DataTypeToTypeMeta(key_dtype));\n  }\n\n  template <typename KEY_T>\n  bool DoRunWithType() {\n    TensorProto::DataType value_dtype =\n        static_cast<TensorProto::DataType>(OperatorBase::GetSingleArgument<int>(\n            \"value_dtype\", TensorProto_DataType_INT32));\n\n    return DispatchHelper<\n        TensorTypes2<int32_t, int64_t, GenericTensorImplementation>,\n        KEY_T>::call(this, DataTypeToTypeMeta(value_dtype));\n  }\n\n  template <typename KEY_T, typename VALUE_T>\n  bool DoRunWithType2() {\n    // clear to make sure the map is empty\n    OperatorBase::Output<typename MapTypeTraits<KEY_T, VALUE_T>::MapType>(MAP)\n        ->clear();\n    return true;\n  }\n\n  template <typename KEY_T>\n  bool DoRunWithOtherType2() {\n    TensorProto::DataType value_dtype =\n        static_cast<TensorProto::DataType>(OperatorBase::GetSingleArgument<int>(\n            \"value_dtype\", TensorProto_DataType_INT32));\n\n    CAFFE_THROW(\n        \"CreateMap is not implemented on value tensor of type \",\n        DataTypeToTypeMeta(value_dtype).name(),\n        \"Consider adding it a type in the list DispatchHelper\");\n  }\n\n  OUTPUT_TAGS(MAP);\n};\n\ntemplate <class Context>\nclass KeyValueToMapOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  KeyValueToMapOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  ~KeyValueToMapOp() {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(KEYS));\n  }\n\n  template <typename KEY_T>\n  bool DoRunWithType() {\n    return DispatchHelper<\n        TensorTypes2<int32_t, int64_t, GenericTensorImplementation>,\n        KEY_T>::call(this, Input(VALUES));\n  }\n\n  template <typename KEY_T, typename VALUE_T>\n  bool DoRunWithType2() {\n    using MapType = typename MapTypeTraits<KEY_T, VALUE_T>::MapType;\n    const auto& key_input = Input(KEYS);\n    const auto& value_input = Input(VALUES);\n\n    CAFFE_ENFORCE_EQ(key_input.size(), value_input.size());\n\n    auto* key_data = key_input.template data<KEY_T>();\n    auto* value_data = value_input.template data<VALUE_T>();\n\n    auto* map_data = OperatorBase::Output<MapType>(MAP);\n\n    for (int i = 0; i < key_input.size(); ++i) {\n      map_data->emplace(key_data[i], value_data[i]);\n    }\n\n    return true;\n  }\n\n  template <typename KEY_T>\n  bool DoRunWithOtherType2() {\n    CAFFE_THROW(\n        \"KeyValueToMap is not implemented on value tensor of type \",\n        Input(VALUES).meta().name(),\n        \"Consider adding it a type in the list DispatchHelper\");\n  }\n\n  INPUT_TAGS(KEYS, VALUES);\n  OUTPUT_TAGS(MAP);\n};\n\ntemplate <class Context>\nclass MapToKeyValueOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  MapToKeyValueOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  ~MapToKeyValueOp() {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<\n        MapType64To64,\n        MapType64To32,\n        MapType32To32,\n        MapType32To64>>::call(this, OperatorBase::InputBlob(MAP));\n  }\n\n  template <typename MAP_T>\n  bool DoRunWithType() {\n    using key_type = typename MAP_T::key_type;\n    using mapped_type = typename MAP_T::mapped_type;\n    auto& map_data = OperatorBase::Input<MAP_T>(MAP);\n    auto* key_output = Output(KEYS);\n    auto* value_output = Output(VALUES);\n    key_output->Resize(map_data.size());\n    value_output->Resize(map_data.size());\n    auto* key_data = key_output->template mutable_data<key_type>();\n    auto* value_data = value_output->template mutable_data<mapped_type>();\n\n    for (const auto& it : map_data) {\n      *key_data = it.first;\n      *value_data = it.second;\n      key_data++;\n      value_data++;\n    }\n\n    return true;\n  }\n\n  INPUT_TAGS(MAP);\n  OUTPUT_TAGS(KEYS, VALUES);\n};\n\ntemplate <typename KEY_T, typename VALUE_T>\nclass MapSerializer : public BlobSerializerBase {\n public:\n  using MapType = typename MapTypeTraits<KEY_T, VALUE_T>::MapType;\n\n  void Serialize(\n      const Blob& blob,\n      const string& name,\n      BlobSerializerBase::SerializationAcceptor acceptor) override {\n    CAFFE_ENFORCE(blob.IsType<MapType>());\n    const MapType& map_data = blob.template Get<MapType>();\n    TIndex sz = map_data.size();\n    Tensor<CPUContext> key_tensor;\n    key_tensor.Resize(sz);\n    Tensor<CPUContext> value_tensor;\n    value_tensor.Resize(sz);\n    auto* key_data = key_tensor.mutable_data<KEY_T>();\n    auto* value_data = value_tensor.mutable_data<VALUE_T>();\n    for (const auto& it : map_data) {\n      *key_data = it.first;\n      *value_data = it.second;\n      key_data++;\n      value_data++;\n    }\n\n    TensorProtos tensor_protos;\n    TensorSerializer<CPUContext> ser;\n    ser.Serialize(\n        key_tensor, name, tensor_protos.add_protos(), 0, key_tensor.size());\n    ser.Serialize(\n        value_tensor, name, tensor_protos.add_protos(), 0, value_tensor.size());\n\n    BlobProto blob_proto;\n    blob_proto.set_name(name);\n    blob_proto.set_type(MapTypeTraits<KEY_T, VALUE_T>::MapTypeName());\n    blob_proto.set_content(tensor_protos.SerializeAsString());\n    acceptor(name, blob_proto.SerializeAsString());\n  }\n};\n\ntemplate <typename KEY_T, typename VALUE_T>\nclass MapDeserializer : public BlobDeserializerBase {\n public:\n  using MapType = typename MapTypeTraits<KEY_T, VALUE_T>::MapType;\n\n  void Deserialize(const BlobProto& proto, Blob* blob) override {\n    TensorProtos tensor_protos;\n    CAFFE_ENFORCE(\n        tensor_protos.ParseFromString(proto.content()),\n        \"Fail to parse TensorProtos\");\n    TensorDeserializer<CPUContext> deser;\n    Tensor<CPUContext> key_tensor, value_tensor;\n    deser.Deserialize(tensor_protos.protos(0), &key_tensor);\n    deser.Deserialize(tensor_protos.protos(1), &value_tensor);\n    auto* key_data = key_tensor.data<KEY_T>();\n    auto* value_data = value_tensor.data<VALUE_T>();\n\n    auto* map_ptr = blob->template GetMutable<MapType>();\n    for (int i = 0; i < key_tensor.size(); ++i) {\n      map_ptr->emplace(key_data[i], value_data[i]);\n    }\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_MAP_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/margin_ranking_criterion_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/margin_ranking_criterion_op.h\"\n\n#include <algorithm>\n\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool MarginRankingCriterionOp<CPUContext>::RunOnDevice() {\n  auto& X1 = Input(0);\n  auto& X2 = Input(1);\n  auto& Y = Input(2);\n  auto* loss = Output(0);\n  CAFFE_ENFORCE(\n      X1.size() == X2.size(),\n      \"The two inputs for computing ranking loss should have the same size.\");\n  CAFFE_ENFORCE(\n      X1.size() == Y.size(), \"The input and label should have the same size.\");\n  loss->ResizeLike(X1);\n\n  const float* X1data = X1.data<float>();\n  const float* X2data = X2.data<float>();\n  const int* Ydata = Y.data<int>();\n  float* output = loss->mutable_data<float>();\n  for (int i = 0; i < X1.size(); ++i) {\n    output[i] = std::max(-Ydata[i] * (X1data[i] - X2data[i]) + margin_, 0.f);\n  }\n  return true;\n}\n\ntemplate <>\nbool MarginRankingCriterionGradientOp<CPUContext>::RunOnDevice() {\n  auto& X1 = Input(0);\n  auto& X2 = Input(1);\n  auto& Y = Input(2);\n  auto& dLoss = Input(3);\n  auto* dX1 = Output(0);\n  auto* dX2 = Output(1);\n\n  dX1->ResizeLike(X1);\n  dX2->ResizeLike(X2);\n\n  const float* X1data = X1.data<float>();\n  const float* X2data = X2.data<float>();\n  const int* Ydata = Y.data<int>();\n  const float* dLoss_data = dLoss.data<float>();\n\n  float* dX1_data = dX1->mutable_data<float>();\n  float* dX2_data = dX2->mutable_data<float>();\n  for (int i = 0; i < X1.size(); ++i) {\n    auto dist = -Ydata[i] * (X1data[i] - X2data[i]) + margin_;\n    if (dist < 0.f) {\n      dX1_data[i] = dX2_data[i] = 0.f;\n    } else {\n      dX1_data[i] = -Ydata[i] * dLoss_data[i];\n      dX2_data[i] = Ydata[i] * dLoss_data[i];\n    }\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(\n    MarginRankingCriterion,\n    MarginRankingCriterionOp<CPUContext>);\nREGISTER_CPU_OPERATOR(\n    MarginRankingCriterionGradient,\n    MarginRankingCriterionGradientOp<CPUContext>);\n\nOPERATOR_SCHEMA(MarginRankingCriterion)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nMarginRankingCriterion takes two input data X1 (Tensor<float>),\nX2 (Tensor<float>), and label Y (Tensor<int>) to produce the\nloss (Tensor<float>) where the loss function,\nloss(X1, X2, Y) = max(0, -Y * (X1 - X2) + margin), is applied to\nthe tensor elementwise.\n\nIf y == 1 then it assumed the first input should be ranked higher\n(have a larger value) than the second input, and vice-versa for\ny == -1.\n)DOC\")\n    .Input(0, \"X1\", \"The left input vector as a 1-dim TensorCPU.\")\n    .Input(1, \"X2\", \"The right input vector as a 1-dim TensorCPU.\")\n    .Input(2, \"Y\", \"The label as a 1-dim TensorCPU with int value of 1 or -1.\")\n    .Output(0, \"loss\", \"The output loss with the same dimensionality as X1.\");\n\nOPERATOR_SCHEMA(MarginRankingCriterionGradient)\n    .NumInputs(4)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nMarginRankingCriterionGradient takes both X1, X2, Y and dY and\nuses them to update dX1, and dX2 according to the chain rule\nand derivatives of the loss function.\n)DOC\");\n\nclass GetMarginRankingCriterionGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"MarginRankingCriterionGradient\",\n        \"\",\n        vector<string>{I(0), I(1), I(2), GO(0)},\n        vector<string>{GI(0), GI(1)});\n  }\n};\nREGISTER_GRADIENT(MarginRankingCriterion, GetMarginRankingCriterionGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/margin_ranking_criterion_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/margin_ranking_criterion_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\n\n__global__ void MRCKernel(\n    const int N, const int* Y, const float* X1, const float* X2, const float margin,\n    float* output) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    output[i] = max(0.f, -Y[i] * (X1[i] - X2[i]) + margin);\n  }\n}\n\n__global__ void MRCGradientKernel(\n    const int N, const int* Y, const float* X1, const float* X2, const float* dOutput,\n    const float margin, float* dX1, float* dX2) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    float dist = -Y[i] * (X1[i] - X2[i]) + margin;\n    if (dist < 0.f) {\n      dX1[i] = dX2[i] = 0.f;\n    } else {\n      dX1[i] = -Y[i] * dOutput[i];\n      dX2[i] = Y[i] * dOutput[i];\n    }\n  }\n}\n}  // namespace\n\ntemplate <>\nbool MarginRankingCriterionOp<CUDAContext>::RunOnDevice() {\n  auto& X1 = Input(0);\n  auto& X2 = Input(1);\n  auto& Y = Input(2);\n  auto* loss = Output(0);\n  CAFFE_ENFORCE(\n      X1.size() == X2.size(),\n      \"The two inputs for computing ranking loss should have the same size.\");\n  CAFFE_ENFORCE(\n      X1.size() == Y.size(),\n      \"The input and label should have the same size.\");\n  loss->ResizeLike(X1);\n\n  const float* X1data = X1.data<float>();\n  const float* X2data = X2.data<float>();\n  const int* Ydata = Y.data<int>();\n  float* output_data = loss->mutable_data<float>();\n\n  MRCKernel<<<CAFFE_GET_BLOCKS(X1.size()), CAFFE_CUDA_NUM_THREADS,\n              0, context_.cuda_stream()>>>(\n      X1.size(), Ydata, X1data, X2data, margin_, output_data);\n  return true;\n}\n\ntemplate <>\nbool MarginRankingCriterionGradientOp<CUDAContext>::RunOnDevice() {\n  auto& X1 = Input(0);\n  auto& X2 = Input(1);\n  auto& Y = Input(2);\n  auto& dOutput = Input(3);\n  auto* dX1 = Output(0);\n  auto* dX2 = Output(1);\n\n  dX1->ResizeLike(X1);\n  dX2->ResizeLike(X2);\n\n  const float* X1data = X1.data<float>();\n  const float* X2data = X2.data<float>();\n  const int* Ydata = Y.data<int>();\n  const float* dOutput_data = dOutput.data<float>();\n\n  float* dX1_data = dX1->mutable_data<float>();\n  float* dX2_data = dX2->mutable_data<float>();\n  MRCGradientKernel<<<CAFFE_GET_BLOCKS(X1.size()), CAFFE_CUDA_NUM_THREADS,\n                      0, context_.cuda_stream()>>>(\n      X1.size(), Ydata, X1data, X2data,\n      dOutput_data, margin_, dX1_data, dX2_data);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(\n    MarginRankingCriterion,\n    MarginRankingCriterionOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    MarginRankingCriterionGradient,\n    MarginRankingCriterionGradientOp<CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/margin_ranking_criterion_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_MARGIN_RANKING_CRITERION_OP_H_\n#define CAFFE2_OPERATORS_MARGIN_RANKING_CRITERION_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass MarginRankingCriterionOp final : public Operator<Context> {\n public:\n  MarginRankingCriterionOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        OP_SINGLE_ARG(float, \"margin\", margin_, 1.0) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  float margin_;\n};\n\ntemplate <class Context>\nclass MarginRankingCriterionGradientOp final : public Operator<Context> {\n public:\n  MarginRankingCriterionGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        OP_SINGLE_ARG(float, \"margin\", margin_, 1.0) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  float margin_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_MARGIN_RANKING_CRITERION_OP_H_\n"
  },
  {
    "path": "caffe2/operators/math_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/math_ops.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nstruct SqrCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* device_context) {\n    math::Sqr<T, CPUContext>(n, x, y, device_context);\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    Sqr,\n    UnaryElementwiseOp<TensorTypes<float>, CPUContext, SqrCPUFunctor>);\n\nOPERATOR_SCHEMA(Sqr)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(\"Square (x^2) the elements of the input\")\n    .Input(0, \"input\", \"Input tensor\")\n    .Output(0, \"output\", \"Squared elements of the input\");\n\nclass GetSqrGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    Argument scale_arg;\n    scale_arg.set_name(\"scale\");\n    scale_arg.set_f(2.0);\n    return vector<OperatorDef>{CreateOperatorDef(\n                                   \"Scale\",\n                                   \"\",\n                                   std::vector<string>{GO(0)},\n                                   std::vector<string>{GO(0)},\n                                   std::vector<Argument>{scale_arg}),\n                               CreateOperatorDef(\n                                   \"Mul\",\n                                   \"\",\n                                   std::vector<string>{GO(0), I(0)},\n                                   std::vector<string>{GI(0)})};\n  }\n};\nREGISTER_GRADIENT(Sqr, GetSqrGradient);\n\nstruct SignCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* device_context) {\n    for (int i = 0; i < n; ++i) {\n      y[i] = (-T(1) * (x[i] < 0)) + (x[i] > 0);\n    }\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    Sign,\n    UnaryElementwiseOp<TensorTypes<float>, CPUContext, SignCPUFunctor>);\n\nOPERATOR_SCHEMA(Sign)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(\"Computes sign for each element of the input: -1, 0 or 1.\")\n    .IdenticalTypeAndShape();\nSHOULD_NOT_DO_GRADIENT(Sign);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/math_ops.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/math_ops.h\"\n\nnamespace caffe2 {\n\nstruct SqrCUDAFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CUDAContext* device_context) {\n    math::Sqr<T, CUDAContext>(n, x, y, device_context);\n  }\n};\n\ntemplate <typename T>\n__global__ void SignKernel(int n, const T* x, T* y) {\n  CUDA_1D_KERNEL_LOOP(i, n) {\n    y[i] = (-T(1) * (x[i] < 0)) + (x[i] > 0);\n  }\n}\n\nstruct SignCUDAFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CUDAContext* device_context) {\n    SignKernel<<<\n        CAFFE_GET_BLOCKS(n),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        device_context->cuda_stream()>>>(n, x, y);\n  }\n};\n\nREGISTER_CUDA_OPERATOR(\n    Sqr,\n    UnaryElementwiseOp<TensorTypes<float>, CUDAContext, SqrCUDAFunctor>);\nREGISTER_CUDA_OPERATOR(\n    Sign,\n    UnaryElementwiseOp<TensorTypes<float>, CUDAContext, SignCUDAFunctor>);\n}\n"
  },
  {
    "path": "caffe2/operators/math_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_MATH_OP_H_\n#define CAFFE2_OPERATORS_MATH_OP_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/utils/math.h\"\n\n#endif\n"
  },
  {
    "path": "caffe2/operators/matmul_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/matmul_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(MatMul, MatMulOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(MatMul)\n    .NumInputs(2, 3)\n    .NumOutputs(1)\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      vector<TensorShape> out(1);\n      out[0].set_data_type(in[0].data_type());\n      ArgumentHelper arg_helper(def);\n      int axis_a = arg_helper.GetSingleArgument<int>(\"axis_a\", 1);\n      int axis_b = arg_helper.GetSingleArgument<int>(\"axis_b\", 1);\n      int trans_a = arg_helper.GetSingleArgument<bool>(\"trans_a\", false);\n      int trans_b = arg_helper.GetSingleArgument<bool>(\"trans_b\", false);\n      int canonical_axis_a = canonical_axis_index_(axis_a, in[0].dims().size());\n      int canonical_axis_b = canonical_axis_index_(axis_b, in[0].dims().size());\n\n      int M = size_to_dim_(canonical_axis_a, GetDimsVector(in[0]));\n      int N = size_from_dim_(canonical_axis_b, GetDimsVector(in[1]));\n      if (trans_a) {\n        M = size_from_dim_(canonical_axis_a, GetDimsVector(in[0]));\n      }\n      if (trans_b) {\n        N = size_to_dim_(canonical_axis_b, GetDimsVector(in[1]));\n      }\n\n      out[0].add_dims(M);\n      out[0].add_dims(N);\n\n      return out;\n    })\n    .SetDoc(R\"DOC(\nMatrix multiplication Y = A * B, where A has size (M x K), B has size (K x N),\nand Y will have a size (M x N).\n)DOC\")\n    .Input(0, \"A\", \"2D matrix of size (M x K)\")\n    .Input(1, \"B\", \"2D matrix of size (K x N)\")\n    .Output(0, \"Y\", \"2D matrix of size (M x N)\")\n    .Arg(\n        \"axis_a\",\n        \"Exclusive axis that divides the first and second dimension \\\nof matrix A, default to 1\")\n    .Arg(\n        \"axis_b\",\n        \"Exclusive axis that divides the first and second dimension \\\nof matrix B, default to 1\")\n    .Arg(\n        \"trans_a\",\n        \"Pass 1 to transpose A before multiplication and after the \\\ndimension adjustment using axis_a\")\n    .Arg(\n        \"trans_b\",\n        \"Pass 1 to transpose B before multiplication and after the \\\ndimension adjustment using axis_b\");\n\nclass GetMatMulGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE_EQ(def_.input_size(), 2);\n\n    bool axis_a = 1;\n    bool axis_b = 1;\n    bool trans_a = 0;\n    bool trans_b = 0;\n\n    if (ArgumentHelper::HasArgument(Def(), \"trans_a\")) {\n      trans_a = GetArgument(Def(), \"trans_a\").i();\n    }\n    if (ArgumentHelper::HasArgument(Def(), \"trans_b\")) {\n      trans_b = GetArgument(Def(), \"trans_b\").i();\n    }\n    if (ArgumentHelper::HasArgument(Def(), \"axis_a\")) {\n      axis_a = GetArgument(Def(), \"axis_a\").i();\n    }\n    if (ArgumentHelper::HasArgument(Def(), \"axis_b\")) {\n      axis_b = GetArgument(Def(), \"axis_b\").i();\n    }\n\n    if (trans_a) {\n      if (trans_b) {\n        // A'B':\n        // dA = B'G', dB = G'A'\n        return vector<OperatorDef>{\n            CreateOperatorDef(\n                \"MatMul\",\n                \"\",\n                vector<string>{I(1), GO(0), I(0)},\n                vector<string>{GI(0)},\n                vector<Argument>{MakeArgument<int>(\"trans_a\", 1),\n                                 MakeArgument<int>(\"trans_b\", 1),\n                                 MakeArgument<int>(\"axis_a\", axis_b)}),\n            CreateOperatorDef(\n                \"MatMul\",\n                \"\",\n                vector<string>{GO(0), I(0), I(1)},\n                vector<string>{GI(1)},\n                vector<Argument>{MakeArgument<int>(\"trans_a\", 1),\n                                 MakeArgument<int>(\"trans_b\", 1),\n                                 MakeArgument<int>(\"axis_b\", axis_a)})};\n      } else {\n        // A'B:\n        // dA = BG', dB = AG\n        return vector<OperatorDef>{\n            CreateOperatorDef(\n                \"MatMul\",\n                \"\",\n                vector<string>{I(1), GO(0), I(0)},\n                vector<string>{GI(0)},\n                vector<Argument>{MakeArgument<int>(\"trans_b\", 1),\n                                 MakeArgument<int>(\"axis_a\", axis_b)}),\n            CreateOperatorDef(\n                \"MatMul\",\n                \"\",\n                vector<string>{I(0), GO(0), I(1)},\n                vector<string>{GI(1)},\n                vector<Argument>{MakeArgument<int>(\"axis_a\", axis_a)})};\n      }\n    } else {\n      if (trans_b) {\n        // AB':\n        // dA = GB, dB = G'A\n        return vector<OperatorDef>{\n            CreateOperatorDef(\n                \"MatMul\",\n                \"\",\n                vector<string>{GO(0), I(1), I(0)},\n                vector<string>{GI(0)},\n                vector<Argument>{MakeArgument<int>(\"axis_b\", axis_b)}),\n            CreateOperatorDef(\n                \"MatMul\",\n                \"\",\n                vector<string>{GO(0), I(0), I(1)},\n                vector<string>{GI(1)},\n                vector<Argument>{MakeArgument<int>(\"trans_a\", 1),\n                                 MakeArgument<int>(\"axis_b\", axis_a)})};\n      } else {\n        // AB:\n        // dA = GB', dB = A'G\n        return vector<OperatorDef>{\n            CreateOperatorDef(\n                \"MatMul\",\n                \"\",\n                vector<string>{GO(0), I(1), I(0)},\n                vector<string>{GI(0)},\n                vector<Argument>{MakeArgument<int>(\"trans_b\", 1),\n                                 MakeArgument<int>(\"axis_b\", axis_b)}),\n            CreateOperatorDef(\n                \"MatMul\",\n                \"\",\n                vector<string>{I(0), GO(0), I(1)},\n                vector<string>{GI(1)},\n                vector<Argument>{MakeArgument<int>(\"trans_a\", 1),\n                                 MakeArgument<int>(\"axis_a\", axis_a)})};\n      }\n    }\n  }\n\n  bool CopyArguments() const override {\n    return false;\n  }\n};\n\nREGISTER_GRADIENT(MatMul, GetMatMulGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/matmul_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_MATMUL_OP_H_\n#define CAFFE2_OPERATORS_MATMUL_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nclass MatMulOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  MatMulOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        axis_a_(OperatorBase::GetSingleArgument<int>(\"axis_a\", 1)),\n        axis_b_(OperatorBase::GetSingleArgument<int>(\"axis_b\", 1)),\n        trans_a_(OperatorBase::GetSingleArgument<int>(\"trans_a\", 0)),\n        trans_b_(OperatorBase::GetSingleArgument<int>(\"trans_b\", 0)) {}\n  ~MatMulOp() {}\n\n  bool RunOnDevice() override {\n    const auto& A = Input(0);\n    const auto& B = Input(1);\n    auto* Y = Output(0);\n\n    const auto canonical_axis_a = A.canonical_axis_index(axis_a_);\n    const auto canonical_axis_b = B.canonical_axis_index(axis_b_);\n    int A_dim0 = A.size_to_dim(canonical_axis_a);\n    int A_dim1 = A.size_from_dim(canonical_axis_a);\n    int B_dim0 = B.size_to_dim(canonical_axis_b);\n    int B_dim1 = B.size_from_dim(canonical_axis_b);\n\n    int a_dim0, a_dim1, b_dim0, b_dim1;\n\n    if (trans_a_) {\n      a_dim0 = A_dim1;\n      a_dim1 = A_dim0;\n    } else {\n      a_dim0 = A_dim0;\n      a_dim1 = A_dim1;\n    }\n\n    if (trans_b_) {\n      b_dim0 = B_dim1;\n      b_dim1 = B_dim0;\n    } else {\n      b_dim0 = B_dim0;\n      b_dim1 = B_dim1;\n    }\n\n    auto dimErrorString = [&]() {\n      return MakeString(\n          \"Dimension mismatch: \",\n          trans_a_ ? \"trans(A): \" : \"A: \",\n          a_dim0,\n          \" \",\n          a_dim1,\n          trans_b_ ? \", trans(B): \" : \", B: \",\n          b_dim0,\n          \" \",\n          b_dim1);\n    };\n    // Error checking\n    CAFFE_ENFORCE(a_dim1 == b_dim0, dimErrorString());\n\n    Y_shape_cache_[0] = a_dim0;\n    Y_shape_cache_[1] = b_dim1;\n    Y->Resize(Y_shape_cache_);\n    CAFFE_ENFORCE(a_dim0 * b_dim1 == Y->size(), dimErrorString());\n    // Y = A * B\n    math::Gemm<T, Context, Engine>(\n        trans_a_ ? CblasTrans : CblasNoTrans,\n        trans_b_ ? CblasTrans : CblasNoTrans,\n        a_dim0,\n        b_dim1,\n        a_dim1,\n        1,\n        A.template data<T>(),\n        B.template data<T>(),\n        0,\n        Y->template mutable_data<T>(),\n        &context_);\n\n    if (InputSize() == 3) {\n      // In gradient op, resize to input\n      Y->ResizeLike(Input(2));\n    }\n    return true;\n  }\n\n protected:\n  // A local vector to cache the output shape so we don't need to recreate\n  // a vector object every time we run Run().\n  vector<TIndex> Y_shape_cache_{0, 0};\n  int axis_a_{1};\n  int axis_b_{1};\n  bool trans_a_;\n  bool trans_b_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_MATMUL_OP_H_\n"
  },
  {
    "path": "caffe2/operators/matmul_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/matmul_op.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(MatMul, MatMulOp<float, CUDAContext>);\n\n}\n"
  },
  {
    "path": "caffe2/operators/max_pool_with_index.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/max_pool_with_index.h\"\n#include \"caffe2/utils/conversions.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n/***\n  * Note: CUDA kernels are minor changes from those at:\n  * https://github.com/BVLC/caffe/blob/master/src/caffe/layers/pooling_layer.cu\n  * Originally licensed under BSD\n  **/\ntemplate <typename Dtype>\n__global__ void MaxPoolForward(\n    const int nthreads,\n    const Dtype* const bottom_data,\n    const int num,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_h,\n    const int pad_w,\n    Dtype* const top_data,\n    int* mask) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    const int pw = index % pooled_width;\n    const int ph = (index / pooled_width) % pooled_height;\n    const int c = (index / pooled_width / pooled_height) % channels;\n    const int n = index / pooled_width / pooled_height / channels;\n    int hstart = ph * stride_h - pad_h;\n    int wstart = pw * stride_w - pad_w;\n    const int hend = min(hstart + kernel_h, height);\n    const int wend = min(wstart + kernel_w, width);\n    hstart = max(hstart, 0);\n    wstart = max(wstart, 0);\n    float maxval = -FLT_MAX;\n    int maxidx = -1;\n    const Dtype* const bottom_slice =\n        bottom_data + (n * channels + c) * height * width;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        if (convert::To<Dtype, float>(bottom_slice[h * width + w]) > maxval) {\n          maxidx = h * width + w;\n          maxval = convert::To<Dtype, float>(bottom_slice[maxidx]);\n        }\n      }\n    }\n    top_data[index] = convert::To<float, Dtype>(maxval);\n    mask[index] = maxidx;\n  }\n}\n\ntemplate <typename Dtype>\n__global__ void MaxPoolBackward(\n    const int nthreads,\n    const Dtype* const top_diff,\n    const int* const mask,\n    const int num,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_h,\n    const int pad_w,\n    Dtype* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int w = index % width;\n    const int h = (index / width) % height;\n    const int c = (index / width / height) % channels;\n    const int n = index / width / height / channels;\n    const int phstart =\n        (h + pad_h < kernel_h) ? 0 : (h + pad_h - kernel_h) / stride_h + 1;\n    const int phend = min((h + pad_h) / stride_h + 1, pooled_height);\n    const int pwstart =\n        (w + pad_w < kernel_w) ? 0 : (w + pad_w - kernel_w) / stride_w + 1;\n    const int pwend = min((w + pad_w) / stride_w + 1, pooled_width);\n    float gradient = 0;\n    const int offset = (n * channels + c) * pooled_height * pooled_width;\n    const Dtype* const top_diff_slice = top_diff + offset;\n    const int* const mask_slice = mask + offset;\n\n    for (int ph = phstart; ph < phend; ++ph) {\n      for (int pw = pwstart; pw < pwend; ++pw) {\n        if (mask_slice[ph * pooled_width + pw] == h * width + w) {\n          gradient +=\n              convert::To<Dtype, float>(top_diff_slice[ph * pooled_width + pw]);\n        }\n      }\n    }\n    bottom_diff[index] = convert::To<float, Dtype>(gradient);\n  }\n}\n};\n\ntemplate <typename T>\nbool MaxPoolWithIndexOp::DoRunWithType() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  auto* mask = Output(1);\n\n  ConvPoolOpBase<CUDAContext>::SetOutputSize(X, Y, X.dim32(1));\n  int output_size = Y->size();\n  mask->Resize(output_size);\n\n  MaxPoolForward<T><<<\n      CAFFE_GET_BLOCKS(output_size),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      output_size,\n      X.data<T>(),\n      X.dim32(0),\n      X.dim32(1),\n      X.dim32(2),\n      X.dim32(3),\n      Y->dim32(2),\n      Y->dim32(3),\n      kernel_h(),\n      kernel_w(),\n      stride_h(),\n      stride_w(),\n      pad_t(),\n      pad_l(),\n      Y->mutable_data<T>(),\n      mask->mutable_data<int>());\n  return true;\n}\n\nbool MaxPoolWithIndexOp::RunOnDevice() {\n  auto& X = Input(0);\n\n  CAFFE_ENFORCE(X.ndim() == 4, \"Operator only supports 4D tensors\");\n\n  if (X.IsType<float>()) {\n    return DoRunWithType<float>();\n  } else if (X.IsType<float16>()) {\n    return DoRunWithType<float16>();\n  } else {\n    CAFFE_THROW(\"Unsupported input type\");\n  }\n}\n\ntemplate <typename T>\nbool MaxPoolWithIndexGradientOp::DoRunWithType() {\n  auto& X = Input(0);\n  auto& dY = Input(1);\n  auto& mask = Input(2);\n  auto* dX = Output(0);\n\n  CAFFE_ENFORCE(X.ndim() == 4, \"Operator only supports 4D tensors\");\n\n  dX->ResizeLike(X);\n  ConvPoolOpBase<CUDAContext>::ComputePads(vector<int>{X.dim32(2), X.dim32(3)});\n\n  MaxPoolBackward<T><<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(),\n      dY.data<T>(),\n      mask.data<int>(),\n      X.dim32(0),\n      X.dim32(1),\n      X.dim32(2),\n      X.dim32(3),\n      dY.dim32(2),\n      dY.dim32(3),\n      kernel_h(),\n      kernel_w(),\n      stride_h(),\n      stride_w(),\n      pad_t(),\n      pad_l(),\n      dX->template mutable_data<T>());\n  return true;\n}\n\nbool MaxPoolWithIndexGradientOp::RunOnDevice() {\n  auto& X = Input(0);\n\n  if (X.IsType<float>()) {\n    return DoRunWithType<float>();\n  } else if (X.IsType<float16>()) {\n    return DoRunWithType<float16>();\n  } else {\n    CAFFE_THROW(\"Unsupported input type\");\n  }\n}\n\nnamespace {\n\nREGISTER_CUDA_OPERATOR(MaxPoolWithIndex, MaxPoolWithIndexOp);\nREGISTER_CUDA_OPERATOR(MaxPoolWithIndexGradient, MaxPoolWithIndexGradientOp);\n\nclass GetMaxPoolWithIndexGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"MaxPoolWithIndexGradient\",\n        \"\",\n        vector<string>{I(0), GO(0), O(1)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(MaxPoolWithIndex, GetMaxPoolWithIndexGradient);\n\nOPERATOR_SCHEMA(MaxPoolWithIndexGradient);\n\nOPERATOR_SCHEMA(MaxPoolWithIndex)\n    .NumInputs(1)\n    .NumOutputs(2)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForPool)\n    .SetDoc(R\"DOC(\n    MaxPoolWithIndex consumes an input blob X and applies max pooling across the\n    blob according to kernel sizes, stride sizes and pad lengths defined by the\n    ConvPoolOpBase operator. It also produces an explicit mask that defines the\n    location that all maximum values were found, which is re-used in the\n    gradient pass. This op is deterministic.\n  )DOC\")\n    .Input(\n        0,\n        \"X\",\n        \"Input data tensor from the previous operator; dimensions \"\n        \"depend on whether the NCHW or NHWC operators are being used. For \"\n        \"example, in the former, the input has size (N x C x H x W), where N is\"\n        \" the batch size, C is the number of channels, and H and W are the \"\n        \"height and the width of the data. The corresponding permutation of \"\n        \"dimensions is used in the latter case. \")\n    .Output(\n        0,\n        \"Y\",\n        \"Output data tensor from average pooling across the input \"\n        \"tensor. Dimensions will vary based on various kernel, stride, and pad \"\n        \"sizes.\")\n    .Output(\n        1,\n        \"Index\",\n        \"Mask of location indices of the found maximum values, \"\n        \" used in the gradient operator to accumulate dY values to the \"\n        \"appropriate locations in Y\");\n};\n\n}; // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/max_pool_with_index.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_MAX_POOL_WITH_INDEX_H_\n#define CAFFE2_OPERATORS_MAX_POOL_WITH_INDEX_H_\n\n#include <cfloat>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/operators/pool_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nclass MaxPoolWithIndexOp final : public ConvPoolOpBase<CUDAContext> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(CUDAContext);\n  MaxPoolWithIndexOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CUDAContext>(operator_def, ws) {}\n  ~MaxPoolWithIndexOp() {}\n\n  template <typename T>\n  bool DoRunWithType();\n\n  bool RunOnDevice() override;\n\n  // Input: X\n  // Output: Y, mask\n};\n\nclass MaxPoolWithIndexGradientOp final : public ConvPoolOpBase<CUDAContext> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(CUDAContext);\n  MaxPoolWithIndexGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CUDAContext>(operator_def, ws) {}\n  ~MaxPoolWithIndexGradientOp() {}\n\n  template <typename T>\n  bool DoRunWithType();\n\n  bool RunOnDevice() override;\n\n  // Input: X, dY, mask\n  // Output: dX\n};\n\n}; // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_MAX_POOL_WITH_INDEX_H_\n"
  },
  {
    "path": "caffe2/operators/mean_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/mean_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Mean, MeanOp<CPUContext>);\nREGISTER_CPU_OPERATOR(MeanGradient, MeanGradientOp<CPUContext>);\n\nOPERATOR_SCHEMA(Mean)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInput(0)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nElement-wise mean of each of the input tensors. The first input tensor can be\nused in-place as the output tensor, in which case the mean will be done in\nplace and results will be accumulated in input0. All inputs and outputs must\nhave the same shape and data type.\n)DOC\")\n    .Input(0, \"data_0\", \"First of the input tensors. Can be inplace.\")\n    .Output(0, \"mean\", \"Output tensor. Same dimension as inputs.\");\n\nclass GetMeanGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    auto outputs = std::vector<string>();\n    for (int i = 0; i < def_.input_size(); i++) {\n      outputs.push_back(GI(i));\n    }\n    return SingleGradientDef(\n        \"MeanGradient\", \"\", std::vector<string>{GO(0)}, outputs);\n  }\n};\n\nREGISTER_GRADIENT(Mean, GetMeanGradient);\n\nOPERATOR_SCHEMA(MeanGradient)\n    .NumInputs(1)\n    .NumOutputs(1, INT_MAX)\n    .AllowInplace({{0, 0}});\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/mean_op.cu",
    "content": "/* Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/mean_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(Mean, MeanOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(MeanGradient, MeanGradientOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/mean_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_MEAN_OPS_H_\n#define CAFFE2_OPERATORS_MEAN_OPS_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass MeanOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(MeanOp)\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& input0 = Input(0);\n    auto* output = Output(0);\n\n    output->ResizeLike(input0);\n    output->CopyFrom(input0, &context_);\n\n    if (InputSize() == 1) {\n      return true;\n    }\n\n    // Dimension checking\n    for (int i = 1; i < InputSize(); ++i) {\n      if (output->dims() != Input(i).dims()) {\n        CAFFE_THROW(\n            \"Check failed: output->dims() == Input(i).dims().\",\n            \"Description: Input #\",\n            i,\n            \", input dimension:\",\n            Input(i).dims(),\n            \" should match output dimension: \",\n            output->dims());\n      }\n    }\n\n    T* output_data = output->template mutable_data<T>();\n    for (int i = 1; i < InputSize(); ++i) {\n      math::Add(\n          output->size(),\n          output_data,\n          Input(i).template data<T>(),\n          output_data,\n          &context_);\n    }\n\n    math::Scale(\n        output->size(),\n        1.0f / InputSize(),\n        output_data,\n        output_data,\n        &context_);\n\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    if (Input(0).template IsType<float>()) {\n      return DoRunWithType<float>();\n    } else {\n      CAFFE_THROW(\n          \"Mean operator only supports 32-bit float, but\",\n          \" input was of type \",\n          Input(0).meta().name());\n    }\n  }\n};\n\ntemplate <class Context>\nclass MeanGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  MeanGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& dY = Input(0);\n    const auto* dY_data = dY.template data<T>();\n    int size = dY.size();\n\n    int num_inputs = OutputSize();\n    float scale = 1.0f / num_inputs;\n\n    // dX0 = scale * dY\n    auto* dX0 = Output(0);\n    dX0->ResizeLike(dY);\n    math::Scale(\n        size, scale, dY_data, dX0->template mutable_data<T>(), &context_);\n\n    // Copy the rest dX\n    for (int i = 1; i < num_inputs; i++) {\n      auto* cur_dX = Output(i);\n      cur_dX->ResizeLike(dY);\n      cur_dX->CopyFrom(*dX0, &context_);\n    }\n\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    if (Input(0).template IsType<float>()) {\n      return DoRunWithType<float>();\n    } else {\n      CAFFE_THROW(\n          \"Mean operator only supports 32-bit float, but\",\n          \" input was of type \",\n          Input(0).meta().name());\n    }\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_MEAN_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/mem_query_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\nnamespace {\n\nclass GetGPUMemoryUsageOp final : public Operator<CUDAContext> {\n public:\n  GetGPUMemoryUsageOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws) {}\n  ~GetGPUMemoryUsageOp() {}\n\n  bool RunOnDevice() override {\n    CHECK_EQ(InputSize(), 0);\n    CHECK_EQ(OutputSize(), 1);\n    std::vector<long> total_by_gpu = CUDAContext::TotalMemoryByGpu();\n    std::vector<long> max_by_gpu = CUDAContext::MaxMemoryByGpu();\n    CHECK_EQ(total_by_gpu.size(), max_by_gpu.size());\n\n    auto* stats = Output(0);\n    stats->Resize(2, total_by_gpu.size());\n    context_.Copy<long, CPUContext, CUDAContext>(\n        total_by_gpu.size(), total_by_gpu.data(), stats->mutable_data<long>());\n    context_.Copy<long, CPUContext, CUDAContext>(\n        max_by_gpu.size(),\n        max_by_gpu.data(),\n        stats->mutable_data<long>() + total_by_gpu.size());\n    return true;\n  }\n};\n\nOPERATOR_SCHEMA(GetGPUMemoryUsage)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(Fetches GPU memory stats from CUDAContext. Result is stored\n      in output blob with shape (2, num_gpus). First row contains the total\n      current memory usage, and the second row the maximum usage during\n      this execution.\n\n      NOTE: --caffe2_gpu_memory_tracking flag must be enabled to use this op.\n    )DOC\");\n\nREGISTER_CUDA_OPERATOR(GetGPUMemoryUsage, GetGPUMemoryUsageOp);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/merge_id_lists_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/merge_id_lists_op.h\"\n\nnamespace caffe2 {\nnamespace {\nREGISTER_CPU_OPERATOR(MergeIdLists, MergeIdListsOp<CPUContext>);\n\nOPERATOR_SCHEMA(MergeIdLists)\n    .NumInputs([](int n) { return (n > 0 && n % 2 == 0); })\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nMergeIdLists: Merge multiple ID_LISTs into a single ID_LIST.\n\nAn ID_LIST is a list of IDs (may be ints, often longs) that represents a single\nfeature. As described in https://caffe2.ai/docs/sparse-operations.html, a batch\nof ID_LIST examples is represented as a pair of lengths and values where the\n`lengths` (int32) segment the `values` or ids (int32/int64) into examples.\n\nGiven multiple inputs of the form lengths_0, values_0, lengths_1, values_1, ...\nwhich correspond to lengths and values of ID_LISTs of different features, this\noperator produces a merged ID_LIST that combines the ID_LIST features. The\nfinal merged output is described by a lengths and values vector.\n\nWARNING: The merge makes no guarantee about the relative order of ID_LISTs\nwithin a batch. This can be an issue if ID_LIST are order sensitive.\n)DOC\")\n    .Input(0, \"lengths_0\", \"Lengths of the ID_LISTs batch for first feature\")\n    .Input(1, \"values_0\", \"Values of the ID_LISTs batch for first feature\")\n    .Output(0, \"merged_lengths\", \"Lengths of the merged ID_LISTs batch\")\n    .Output(1, \"merged_values\", \"Values of the merged ID_LISTs batch\");\nNO_GRADIENT(MergeIdLists);\n}\n}\n"
  },
  {
    "path": "caffe2/operators/merge_id_lists_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_MERGE_ID_LISTS_OP_H_\n#define CAFFE2_OPERATORS_MERGE_ID_LISTS_OP_H_\n\n#include <set>\n#include <vector>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass MergeIdListsOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(MergeIdListsOp);\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& first_lengths = Input(0);\n    CAFFE_ENFORCE_EQ(first_lengths.ndim(), 1, \"LENGTHS should be 1-D\");\n    const auto batch_size = first_lengths.size();\n\n    auto* out_lengths = Output(0);\n    out_lengths->ResizeLike(first_lengths);\n\n    auto* out_lengths_data = out_lengths->template mutable_data<int32_t>();\n\n    /**\n     * Loop to figure out how much space to reserve for output\n     * and perform checks.\n     */\n    auto M = 0;\n    for (size_t i = 0; i < InputSize(); i += 2) {\n      auto& lengths = Input(i);\n      CAFFE_ENFORCE_EQ(lengths.ndim(), 1, \"LENGTHS should be 1-D\");\n      CAFFE_ENFORCE_EQ(lengths.size(), batch_size, \"LENGTHS should be equal\");\n      auto& values = Input(i + 1);\n      CAFFE_ENFORCE_EQ(values.ndim(), 1, \"VALUES should be 1-D\");\n      M += values.size();\n    }\n\n    auto* out_values = Output(1);\n    out_values->Resize(M);\n\n    T* out_values_data = out_values->template mutable_data<T>();\n    auto pos = 0;\n\n    // TODO(badri): Use unordered_set if performance is an issue\n    std::set<T> deduped;\n    std::vector<int> offsets(InputSize(), 0);\n    for (auto sample = 0; sample < batch_size; sample++) {\n      for (size_t i = 0; i < InputSize(); i += 2) {\n        auto& lengths = Input(i);\n        const auto* lengths_data = lengths.template data<int32_t>();\n\n        auto& values = Input(i + 1);\n        const T* values_data = values.template data<T>();\n        const auto length = lengths_data[sample];\n\n        for (auto j = offsets[i]; j < offsets[i] + length; j++) {\n          deduped.insert(values_data[j]);\n        }\n        offsets[i] += length;\n      }\n      for (auto val : deduped) {\n        out_values_data[pos++] = val;\n      }\n      out_lengths_data[sample] = deduped.size();\n      deduped.clear();\n    }\n    out_values->Resize(pos);\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(this, Input(1));\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_MERGE_ID_LISTS_OP_H_\n"
  },
  {
    "path": "caffe2/operators/minmax_gradient_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/minmax_ops.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(MaxGradient, MaxGradientOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(MinGradient, MinGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(MaxGradient).NumInputs(3, INT_MAX).NumOutputs(1, INT_MAX);\nOPERATOR_SCHEMA(MinGradient).NumInputs(3, INT_MAX).NumOutputs(1, INT_MAX);\n\ntemplate <typename T, class Context>\nbool SelectGradientOpBase<T, Context>::RunOnDevice() {\n  auto& output = Input(0);\n  auto& grad_output = Input(1);\n  const int kInputStartOffset = 2;\n\n  const T* data = output.template data<T>();\n  ConstEigenArrayMap<T> output_array(\n      output.template data<T>(), 1, output.size());\n  ConstEigenArrayMap<T> grad_out_array(\n      grad_output.template data<T>(), 1, grad_output.size());\n\n  for (int i = 0; i < OutputSize(); i++) {\n    auto& input = Input(i + kInputStartOffset);\n    ConstEigenArrayMap<T> input_array(\n        input.template data<T>(), 1, input.size());\n\n    auto* grad_input = Output(i);\n    grad_input->ResizeLike(input);\n    EigenArrayMap<T> grad_in_array(\n        grad_input->template mutable_data<T>(), 1, grad_input->size());\n    grad_in_array = grad_out_array *\n        input_array.cwiseEqual(output_array).template cast<T>();\n  }\n  return true;\n}\n\nclass GetMaxGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    auto gradInputs = vector<string>();\n    auto inputs = vector<string>{O(0), GO(0)};\n    for (int i = 0; i < def_.input_size(); i++) {\n      gradInputs.push_back(GI(i));\n      inputs.push_back(I(i));\n    }\n    return SingleGradientDef(\"MaxGradient\", \"\", inputs, gradInputs);\n  }\n};\nREGISTER_GRADIENT(Max, GetMaxGradient);\n\nclass GetMinGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    auto gradInputs = vector<string>();\n    auto inputs = vector<string>{O(0), GO(0)};\n    for (int i = 0; i < def_.input_size(); i++) {\n      gradInputs.push_back(GI(i));\n      inputs.push_back(I(i));\n    }\n    return SingleGradientDef(\"MinGradient\", \"\", inputs, gradInputs);\n  }\n};\nREGISTER_GRADIENT(Min, GetMinGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/minmax_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/minmax_ops.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Max, MaxOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(Min, MinOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Max)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInput(0)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nElement-wise max of each of the input tensors. The first input tensor can be\nused in-place as the output tensor, in which case the max will be done in\nplace and results will be accumulated in input0. All inputs and outputs must\nhave the same shape and data type.\n)DOC\")\n    .Input(0, \"data_0\", \"First of the input tensors. Can be inplace.\")\n    .Output(0, \"max\", \"Output tensor. Same dimension as inputs.\");\n\nOPERATOR_SCHEMA(Min)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1)\n    .IdenticalTypeAndShapeOfInput(0)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nElement-wise min of each of the input tensors. The first input tensor can be\nused in-place as the output tensor, in which case the min will be done in\nplace and results will be accumulated in input0. All inputs and outputs must\nhave the same shape and data type.\n)DOC\")\n    .Input(0, \"data_0\", \"First of the input tensors. Can be inplace.\")\n    .Output(0, \"min\", \"Output tensor. Same dimension as inputs.\");\n\ntemplate <typename T, class Context>\nbool MaxOp<T, Context>::Compute() {\n  auto& input0 = Input(0);\n  const int N = input0.size();\n  T* output_data = Output(0)->template mutable_data<T>();\n\n  for (int i = 1; i < InputSize(); i++) {\n    auto input_data = Input(i).template data<T>();\n    EigenVectorMap<T> output_vec(output_data, N);\n    output_vec = output_vec.cwiseMax(ConstEigenVectorMap<T>(input_data, N));\n  }\n\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool MinOp<T, Context>::Compute() {\n  auto& input0 = Input(0);\n  const int N = input0.size();\n  T* output_data = Output(0)->template mutable_data<T>();\n\n  for (int i = 1; i < InputSize(); i++) {\n    auto input_data = Input(i).template data<T>();\n    EigenVectorMap<T> output_vec(output_data, N);\n    output_vec = output_vec.cwiseMin(ConstEigenVectorMap<T>(input_data, N));\n  }\n\n  return true;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/minmax_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_MINMAX_OPS_H_\n#define CAFFE2_OPERATORS_MINMAX_OPS_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass MaxMinOpBase : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(MaxMinOpBase)\n\n  bool RunOnDevice() override {\n    auto& input0 = Input(0);\n    auto* output = Output(0);\n\n    output->ResizeLike(input0);\n    output->CopyFrom(input0, &context_);\n\n    if (InputSize() == 1) {\n      return true;\n    }\n\n    // Dimension checking\n    for (int i = 1; i < InputSize(); ++i) {\n      CAFFE_ENFORCE_EQ(\n          output->dims(),\n          Input(i).dims(),\n          \"Description: Input #\",\n          i,\n          \", input dimension:\",\n          Input(i).dims(),\n          \" should match output dimension: \",\n          output->dims());\n    }\n\n    return this->Compute();\n  }\n\n  virtual bool Compute() = 0;\n};\n\ntemplate <typename T, class Context>\nclass MaxOp : public MaxMinOpBase<T, Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  MaxOp(const OperatorDef& operator_def, Workspace* ws)\n      : MaxMinOpBase<T, Context>(operator_def, ws) {}\n  virtual ~MaxOp() noexcept {}\n  bool Compute() override;\n};\n\ntemplate <typename T, class Context>\nclass SelectGradientOpBase : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(SelectGradientOpBase)\n\n  bool RunOnDevice() override;\n};\n\ntemplate <typename T, class Context>\nclass MaxGradientOp : public SelectGradientOpBase<T, Context> {\n public:\n  MaxGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : SelectGradientOpBase<T, Context>(operator_def, ws) {}\n  virtual ~MaxGradientOp() noexcept {}\n};\n\ntemplate <typename T, class Context>\nclass MinOp : public MaxMinOpBase<T, Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  MinOp(const OperatorDef& operator_def, Workspace* ws)\n      : MaxMinOpBase<T, Context>(operator_def, ws) {}\n  virtual ~MinOp() noexcept {}\n  bool Compute() override;\n};\n\ntemplate <typename T, class Context>\nclass MinGradientOp : public SelectGradientOpBase<T, Context> {\n public:\n  MinGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : SelectGradientOpBase<T, Context>(operator_def, ws) {}\n  virtual ~MinGradientOp() noexcept {}\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_MINMAX_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/mod_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/mod_op.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\ntemplate <>\ntemplate <typename T>\nbool ModOp<CPUContext>::DoRunWithType() {\n  auto& data = Input(DATA);\n  auto N = data.size();\n  const auto* data_ptr = data.template data<T>();\n\n  auto* output = Output(0);\n  output->ResizeLike(Input(DATA));\n  auto* output_ptr = output->template mutable_data<T>();\n\n  for (auto i = 0; i < N; i++) {\n    output_ptr[i] = data_ptr[i] % divisor_;\n    if (output_ptr[i] && sign_follow_divisor_ &&\n        ((output_ptr[i] > 0) != (divisor_ > 0))) {\n      output_ptr[i] += divisor_;\n    }\n  }\n  return true;\n}\n\nnamespace {\n\nREGISTER_CPU_OPERATOR(Mod, ModOp<CPUContext>);\nOPERATOR_SCHEMA(Mod)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .Arg(\"divisor\", \"The divisor of the modulo operation. Must >= 1\")\n    .Arg(\n        \"sign_follow_divisor\",\n        \"The sign of output follows Dividend if set to `false`. \\\n          Otherwise follows Divisor\")\n    .IdenticalTypeAndShape()\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nElementwise modulo operation. Each element in the output is the modulo result\nof the corresponding elment in the input data. The divisor of the modulo is\nprovided by the operator argument `divisor`.\n)DOC\")\n    .Input(0, \"data\", \"input int32 or int64 data\")\n    .Output(0, \"output\", \"output of data with modulo operation applied\");\n\nSHOULD_NOT_DO_GRADIENT(ModOp);\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/mod_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE_OPERATORS_MOD_OP_H_\n#define CAFFE_OPERATORS_MOD_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass ModOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ModOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    divisor_ = OperatorBase::GetSingleArgument<int64_t>(\"divisor\", 0);\n    CAFFE_ENFORCE_NE(divisor_, 0, \"divisor must not be 0\");\n    sign_follow_divisor_ =\n        OperatorBase::GetSingleArgument<bool>(\"sign_follow_divisor\", false);\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int, int64_t>>::call(this, Input(DATA));\n  }\n\n  template <typename T>\n  bool DoRunWithType();\n\n protected:\n  INPUT_TAGS(DATA);\n\n private:\n  int64_t divisor_;\n  bool sign_follow_divisor_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE_OPERATORS_MOD_OP_H_\n"
  },
  {
    "path": "caffe2/operators/multi_class_accuracy_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/multi_class_accuracy_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool MultiClassAccuracyOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(PREDICTION);\n  auto& label = Input(LABEL);\n  auto* Y0 = Output(0);\n  auto* Y1 = Output(1);\n  DCHECK_EQ(X.ndim(), 2);\n  // amount, number of instances\n  int N = X.dim32(0);\n  // dimension, number of classes\n  int D = X.dim32(1);\n  DCHECK_EQ(label.ndim(), 1);\n  DCHECK_EQ(label.dim32(0), N);\n  Y0->Resize(D);\n  Y1->Resize(D);\n\n  const auto* Xdata = X.data<float>();\n  const auto* labeldata = label.data<int>();\n  auto* accuracies = Y0->mutable_data<float>();\n  auto* amounts = Y1->mutable_data<int>();\n  std::fill(accuracies, accuracies + D, 0);\n  std::fill(amounts, amounts + D, 0);\n\n  for (int i = 0; i < N; ++i) {\n    float maxval = std::numeric_limits<float>::lowest();\n    int maxid = 0;\n    for (int j = 0; j < D; ++j) {\n      if (Xdata[i * D + j] > maxval) {\n        maxval = Xdata[i * D + j];\n        maxid = j;\n      }\n    }\n    int labelid = labeldata[i];\n    DCHECK_LT(labelid, D);\n    if (maxid == labelid) {\n      accuracies[labelid]++;\n    }\n    amounts[labelid]++;\n  }\n\n  for (int i = 0; i < D; ++i) {\n    int amount = amounts[i];\n    if (amount) {\n      accuracies[i] /= amount;\n    }\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(\n  MultiClassAccuracy, MultiClassAccuracyOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(MultiClassAccuracy)\n  .NumInputs(2)\n  .NumOutputs(2)\n  .SetDoc(R\"DOC(\nRespectively compute accuracy score for each class given a number of instances\nand predicted scores of each class for each instance.\n)DOC\")\n  .Input(\n    0,\n    \"prediction\",\n    \"2-D float tensor (N,D,) of predicted scores of each class for \"\n    \"each data. N is the number of instances, i.e., batch size. D is number of \"\n    \"possible classes/labels.\")\n  .Input(\n    1,\n    \"labels\",\n    \"1-D int tensor (N,) of labels for each instance.\")\n  .Output(\n    0,\n    \"accuracies\",\n    \"1-D float tensor (D,) of accuracy for each class. If a class has no \"\n    \"instance in the batch, its accuracy score is set to zero.\")\n  .Output(\n    1,\n    \"amounts\",\n    \"1-D int tensor (D,) of number of instances for each class in the batch.\");\n\nSHOULD_NOT_DO_GRADIENT(MultiClassAccuracy);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/multi_class_accuracy_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/multi_class_accuracy_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nnamespace {\n__global__ void MultiClassAccuracyKernel(const int N, const int D, const float* Xdata,\n    const int* labeldata, float* accuracies, int* amounts) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    float maxval = Xdata[i * D];\n    int maxid = 0;\n    for (int j = 1; j < D; ++j) {\n      if (Xdata[i * D + j] > maxval) {\n        maxval = Xdata[i * D + j];\n        maxid = j;\n      }\n    }\n    int labelid = labeldata[i];\n    if (maxid == labelid) {\n      atomicAdd(accuracies + labelid, static_cast<float>(1));\n    }\n    atomicAdd(amounts + labelid, static_cast<int>(1));\n  }\n}\n__global__ void MultiClassAccuracyDivideKernel(\n  const int D, float* accuracies, const int* amounts) {\n  CUDA_1D_KERNEL_LOOP(i, D) {\n    if (amounts[i]) {\n      accuracies[i] /= amounts[i];\n    }\n  }\n}\n}  // namespace\n\ntemplate <>\nbool MultiClassAccuracyOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(PREDICTION);\n  auto& label = Input(LABEL);\n  auto* Y0 = Output(0);\n  auto* Y1 = Output(1);\n  DCHECK_EQ(X.ndim(), 2);\n  // amount, number of instances\n  int N = X.dim32(0);\n  // dimension, number of classes\n  int D = X.dim32(1);\n  DCHECK_EQ(label.ndim(), 1);\n  DCHECK_EQ(label.dim32(0), N);\n  Y0->Resize(D);\n  Y1->Resize(D);\n\n  const float* Xdata = X.data<float>();\n  const int* labeldata = label.data<int>();\n  float* accuracies = Y0->mutable_data<float>();\n  int* amounts = Y1->mutable_data<int>();\n  math::Set<float, CUDAContext>(D, 0.0, accuracies, &context_);\n  math::Set<int, CUDAContext>(D, 0, amounts, &context_);\n\n  MultiClassAccuracyKernel<<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS,\n                              0, context_.cuda_stream()>>>(\n      N, D, Xdata, labeldata, accuracies, amounts);\n  MultiClassAccuracyDivideKernel<<<CAFFE_GET_BLOCKS(D), CAFFE_CUDA_NUM_THREADS,\n                                  0, context_.cuda_stream()>>>(\n    D, accuracies, amounts);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(\n  MultiClassAccuracy, MultiClassAccuracyOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/multi_class_accuracy_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_MULTI_CLASS_ACCURACY_OP_H_\n#define CAFFE2_OPERATORS_MULTI_CLASS_ACCURACY_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass MultiClassAccuracyOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(MultiClassAccuracyOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n protected:\n  INPUT_TAGS(PREDICTION, LABEL);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_MULTI_CLASS_ACCURACY_OP_H_\n"
  },
  {
    "path": "caffe2/operators/negate_gradient_op.cc",
    "content": "#include \"caffe2/operators/negate_gradient_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(NegateGradient, NegateGradientOp<CPUContext>);\nOPERATOR_SCHEMA(NegateGradient)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nNegagteGradient operator in forward pass simply copies input to the\noutput, and in backward pass, flips the sign of the output gradient\n)DOC\");\n\nstruct GetNegateGradientGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  std::vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE_EQ(def_.input_size(), 1);\n    return SingleGradientDef(\n        \"Negative\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(NegateGradient, GetNegateGradientGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/negate_gradient_op.h",
    "content": "// Copyright 2004-present Facebook. All Rights Reserved.\n\n// File: negate_gradient_op.h\n\n#pragma once\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass NegateGradientOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(NegateGradientOp)\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    const auto& in = Input(0);\n    auto* out = Output(0);\n    if (out != &in) {\n      out->CopyFrom(in, &context_);\n    }\n    return true;\n  }\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/negate_gradient_op_gpu.cc",
    "content": "#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/negate_gradient_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(NegateGradient, NegateGradientOp<CUDAContext>)\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/negative_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\nstruct NegativeCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* /*device_context*/) {\n    EigenVectorMap<T>(y, n) = -ConstEigenVectorMap<T>(x, n);\n    // for (int i = 0; i < n; ++i) {\n    //  y[i] = -x[i];\n    //}\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    Negative, UnaryElementwiseOp<\n        TensorTypes<float, double, int, long>, CPUContext, NegativeCPUFunctor>);\n\n// Input: X, output: Y\nOPERATOR_SCHEMA(Negative)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nComputes the element-wise negative of the input.\n)DOC\")\n    .Input(0, \"X\", \"1D input tensor\")\n    .Output(0, \"Y\", \"1D input tensor\");\n\nclass GetNegativeGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"Negative\", \"\",\n        vector<string>{GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Negative, GetNegativeGradient);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/negative_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void NegativeKernel(const int N, const T* x, T* y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    y[i] = -x[i];\n  }\n}\n\nstruct NegativeCUDAFunctor {\n  template <typename T>\n  inline void operator()(const int n, const T* x,\n                         T* y, CUDAContext* device_context) {\n    NegativeKernel<T><<<CAFFE_GET_BLOCKS(n), CAFFE_CUDA_NUM_THREADS,\n                    0, device_context->cuda_stream()>>>(n, x, y);\n    return;\n  }\n};\n\nREGISTER_CUDA_OPERATOR(\n    Negative, UnaryElementwiseOp<\n        TensorTypes<float, double, int, long>, CUDAContext,\n        NegativeCUDAFunctor>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/ngram_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/ngram_ops.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(\n    NGramFromCategorical,\n    NGramFromCategoricalOp<float, int64_t, CPUContext>);\nNO_GRADIENT(NGramFromCategorical);\nOPERATOR_SCHEMA(NGramFromCategorical).NumInputs(1).NumOutputs(1);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/ngram_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <vector>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\ntemplate <typename F, typename T, class Context>\nclass NGramFromCategoricalOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  NGramFromCategoricalOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        col_ids_(OperatorBase::GetRepeatedArgument<int>(\"col_ids\")),\n        categorical_limits_(\n            OperatorBase::GetRepeatedArgument<int>(\"categorical_limits\")),\n        vals_(OperatorBase::GetRepeatedArgument<int>(\"vals\")) {\n    col_num_ = col_ids_.size();\n    max_col_id_ = *std::max_element(col_ids_.begin(), col_ids_.end());\n    CAFFE_ENFORCE_EQ(col_num_, categorical_limits_.size());\n    int expected_vals_size = 0;\n    for (auto& l : categorical_limits_) {\n      CAFFE_ENFORCE_GT(l, 0);\n      expected_vals_size += l;\n    }\n    CAFFE_ENFORCE_EQ(expected_vals_size, vals_.size());\n    // compute ngram maps with small end\n    for (auto& j : col_ids_) {\n      CAFFE_ENFORCE_GE(j, 0);\n      ngram_maps_.push_back(std::map<int, int>());\n    }\n    int base = 1;\n    int idx = 0;\n    for (int k = 0; k < col_num_; k++) {\n      int l = categorical_limits_[k];\n      for (int m = 0; m < l; m++) {\n        int v = vals_[idx++];\n        ngram_maps_[k][v] = m * base;\n      }\n      base *= l;\n    }\n  }\n\n  bool RunOnDevice() override {\n    auto& floats = Input(0);\n    auto N = floats.dim(0);\n    auto D = floats.size_from_dim(1);\n    const F* floats_data = floats.template data<F>();\n    auto* output = Output(0);\n    output->Resize(N);\n    auto* output_data = output->template mutable_data<T>();\n    math::Set<T, Context>(output->size(), 0, output_data, &context_);\n\n    CAFFE_ENFORCE_GT(D, max_col_id_);\n    for (int i = 0; i < N; i++) {\n      for (int k = 0; k < col_num_; k++) {\n        int j = col_ids_[k];\n        int v = round(floats_data[i * D + j]);\n        // for out-of-vocabulary values, we always treat them the same as the\n        // first value specified in vals; if we want to mimic the behavior as\n        // sigrid NGram transform, just push front a random/impossible value at\n        // each segments of vals\n        output_data[i] += ngram_maps_[k].find(v) == ngram_maps_[k].end()\n            ? 0\n            : ngram_maps_[k][v];\n      }\n    }\n    return true;\n  }\n\n private:\n  std::vector<int> col_ids_;\n  std::vector<int> categorical_limits_;\n  std::vector<int> vals_;\n  std::vector<std::map<int, int>> ngram_maps_;\n  int col_num_;\n  int max_col_id_;\n};\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/no_default_engine_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_NO_DEFAULT_ENGINE_OP_H_\n#define CAFFE2_OPERATORS_NO_DEFAULT_ENGINE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\n/**\n * A helper class to denote that an op does not have a default engine.\n *\n * NoDefaultEngineOp is a helper class that one can use to denote that a\n * specific operator is not intended to be called without an explicit engine\n * given. This is the case for e.g. the communication operators where one has\n * to specify a backend (like MPI or ZEROMQ).\n */\ntemplate <class Context>\nclass NoDefaultEngineOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(NoDefaultEngineOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    CAFFE_THROW(\n        \"The operator \",\n        this->debug_def().type(),\n        \" does not have a default engine implementation. Please \"\n        \"specify an engine explicitly for this operator.\");\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_NO_DEFAULT_ENGINE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/norm_planar_yuv_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\nclass NormalizePlanarYUVOp : public Operator<CPUContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CPUContext);\n  using Operator<CPUContext>::Operator;\n\n  bool RunOnDevice() {\n    const auto& X = Input(0);\n    const auto& M = Input(1); // mean\n    const auto& S = Input(2); // standard deviation\n    auto* Z = Output(0);\n    Z->ResizeLike(X);\n\n    CAFFE_ENFORCE(X.dims().size() == 4);\n\n    const auto N = X.dim32(0);\n    auto C = X.dim(1);\n    const auto H = X.dim(2);\n    const auto W = X.dim(3);\n    CAFFE_ENFORCE(C == M.dim(1));\n    CAFFE_ENFORCE(C == S.dim(1));\n    const auto* Xdata = X.data<float>();\n    auto* Zdata = Z->mutable_data<float>();\n\n    int offset = H * W;\n    for (auto n = 0; n < N; n++) { // realistically N will always be 1\n      int batch_offset = n * C * offset;\n      for (auto c = 0; c < C; c++) {\n        ConstEigenVectorMap<float> channel_s(\n            &Xdata[batch_offset + (c * offset)], offset);\n        EigenVectorMap<float> channel_d(\n            &Zdata[batch_offset + (c * offset)], offset);\n        channel_d = channel_s.array() - M.data<float>()[c];\n        channel_d = channel_d.array() / S.data<float>()[c];\n      }\n    }\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(NormalizePlanarYUV, NormalizePlanarYUVOp);\nOPERATOR_SCHEMA(NormalizePlanarYUV)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}});\n;\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/normalize_l1_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/normalize_l1_op.h\"\n\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nvoid NormalizeL1Op<T, Context>::DoNormalize(\n    const T* xData,\n    T* yData,\n    const int m,\n    const int n,\n    const int sf) {\n  using InnerStride = Eigen::InnerStride<Eigen::Dynamic>;\n  using StridedVec =\n      Eigen::Map<Eigen::Matrix<T, 1, Eigen::Dynamic>, 0, InnerStride>;\n  using ConstStridedVec =\n      Eigen::Map<const Eigen::Matrix<T, 1, Eigen::Dynamic>, 0, InnerStride>;\n\n  for (int i = 0; i < n; ++i) {\n    auto base = (i / sf) * sf * m + (i % sf);\n    ConstStridedVec xVec(xData + base, 1, m, InnerStride(sf));\n    auto norm = xVec.template lpNorm<1>();\n    if (norm != 0) {\n      StridedVec yVec(yData + base, 1, m, InnerStride(sf));\n      yVec = xVec / norm;\n    }\n  }\n};\n\nREGISTER_CPU_OPERATOR(NormalizeL1, NormalizeL1Op<float, CPUContext>);\nOPERATOR_SCHEMA(NormalizeL1)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .Arg(\"axis\", \"axis to normalize\")\n    .SetDoc(R\"DOC(\nGiven a matrix, apply L1-normalization along the specified axis.\n)DOC\");\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/normalize_l1_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_NORMALIZE_L1_OP_H_\n#define CAFFE2_OPERATORS_NORMALIZE_L1_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass NormalizeL1Op final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(NormalizeL1Op)\n\n  bool RunOnDevice() override {\n    const auto& x = Input(0);\n    auto* y = Output(0);\n    const auto* xData = x.template data<T>();\n    y->ResizeLike(x);\n    auto* yData = y->template mutable_data<T>();\n\n    const auto canonical_axis = x.canonical_axis_index(\n        OperatorBase::GetSingleArgument<int>(\"axis\", -1));\n    const int m = x.dim32(canonical_axis);\n    const int n = x.size() / m;\n    const int sf = x.size_from_dim(canonical_axis + 1);\n    DoNormalize(xData, yData, m, n, sf);\n    return true;\n  }\n\n private:\n  void\n  DoNormalize(const T* xData, T* yData, const int m, const int n, const int sf);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_NORMALIZE_L1_OP_H_\n"
  },
  {
    "path": "caffe2/operators/normalize_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/normalize_op.h\"\n\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nvoid NormalizeOp<T, Context>::DoNormalize(\n    const T* xData,\n    T* yData,\n    const int m,\n    const int n,\n    const int sf) {\n  using InnerStride = Eigen::InnerStride<Eigen::Dynamic>;\n  using StridedVec =\n      Eigen::Map<Eigen::Matrix<T, 1, Eigen::Dynamic>, 0, InnerStride>;\n  using ConstStridedVec =\n      Eigen::Map<const Eigen::Matrix<T, 1, Eigen::Dynamic>, 0, InnerStride>;\n\n  for (int i = 0; i < n; ++i) {\n    auto base = (i / sf) * sf * m + (i % sf);\n    ConstStridedVec xVec(xData + base, 1, m, InnerStride(sf));\n    auto norm = xVec.template lpNorm<2>();\n    if (norm != 0) {\n      StridedVec yVec(yData + base, 1, m, InnerStride(sf));\n      yVec = xVec / norm;\n    }\n  }\n};\n\ntemplate <typename T, class Context>\nvoid NormalizeGradientOp<T, Context>::DoNormalize(\n    const T* xData,\n    const T* gOutData,\n    T* gInData,\n    const int m,\n    const int n,\n    const int sf) {\n  using InnerStride = Eigen::InnerStride<Eigen::Dynamic>;\n  using StridedVec =\n      Eigen::Map<Eigen::Matrix<T, 1, Eigen::Dynamic>, 0, InnerStride>;\n  using ConstStridedVec =\n      Eigen::Map<const Eigen::Matrix<T, 1, Eigen::Dynamic>, 0, InnerStride>;\n\n  for (int i = 0; i < n; ++i) {\n    auto base = (i / sf) * sf * m + (i % sf);\n    ConstStridedVec xVec(xData + base, 1, m, InnerStride(sf));\n    ConstStridedVec gOutVec(gOutData + base, 1, m, InnerStride(sf));\n\n    auto row_sum = xVec.dot(gOutVec);\n    auto row_norm = xVec.template lpNorm<2>();\n    auto row_norm_3 = pow(row_norm, 3);\n    if (row_norm != 0) {\n      StridedVec gInVec(gInData + base, 1, m, InnerStride(sf));\n      gInVec = (gOutVec / row_norm) - ((xVec / row_norm_3) * row_sum);\n    }\n  }\n};\n\nREGISTER_CPU_OPERATOR(Normalize, NormalizeOp<float, CPUContext>);\nOPERATOR_SCHEMA(Normalize)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .Arg(\"axis\", \"axis to normalize\")\n    .SetDoc(R\"DOC(\nGiven a matrix, apply L2-normalization along the specified dimension.\n)DOC\")\n    .IdenticalTypeAndShape();\n\nREGISTER_CPU_OPERATOR(\n    NormalizeGradient,\n    NormalizeGradientOp<float, CPUContext>);\nOPERATOR_SCHEMA(NormalizeGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .Arg(\"axis\", \"axis to normalize\");\n\nclass GetNormalizeGradient final : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE_EQ(def_.input_size(), 1);\n    return SingleGradientDef(\n        \"NormalizeGradient\",\n        \"\",\n        vector<string>{I(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Normalize, GetNormalizeGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/normalize_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_NORMALIZE_OP_H_\n#define CAFFE2_OPERATORS_NORMALIZE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass NormalizeOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  NormalizeOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n\n  bool RunOnDevice() override {\n    const auto& x = Input(0);\n    auto* y = Output(0);\n    const auto* xData = x.template data<T>();\n    y->ResizeLike(x);\n    auto* yData = y->template mutable_data<T>();\n\n    const auto canonical_axis = x.canonical_axis_index(\n        OperatorBase::GetSingleArgument<int>(\"axis\", -1));\n    const int m = x.dim32(canonical_axis);\n    const int n = x.size() / m;\n    const int sf = x.size_from_dim(canonical_axis + 1);\n    DoNormalize(xData, yData, m, n, sf);\n    return true;\n  }\n\n private:\n  void\n  DoNormalize(const T* xData, T* yData, const int m, const int n, const int sf);\n};\n\ntemplate <typename T, class Context>\nclass NormalizeGradientOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  NormalizeGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n\n  bool RunOnDevice() override {\n    const auto& x = Input(0);\n    const auto& gOut = Input(GRAD_OUT);\n    auto* gIn = Output(GRAD_IN);\n    gIn->ResizeLike(gOut);\n\n    const auto* xData = x.template data<T>();\n    const auto* gOutData = gOut.template data<T>();\n    auto* gInData = gIn->template mutable_data<T>();\n\n    const auto canonical_axis = x.canonical_axis_index(\n        OperatorBase::GetSingleArgument<int>(\"axis\", -1));\n    const int m = x.dim32(canonical_axis);\n    const int n = x.size() / m;\n    const int sf = x.size_from_dim(canonical_axis + 1);\n    DoNormalize(xData, gOutData, gInData, m, n, sf);\n    return true;\n  }\n\n private:\n  void DoNormalize(\n      const T* xData,\n      const T* gOutData,\n      T* gInData,\n      const int m,\n      const int n,\n      const int sf);\n\n  INPUT_TAGS(INPUT, GRAD_OUT);\n  OUTPUT_TAGS(GRAD_IN);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_NORMALIZE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/normalize_ops.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cub/block/block_reduce.cuh>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/normalize_l1_op.h\"\n#include \"caffe2/operators/normalize_op.h\"\n\nnamespace caffe2 {\n\n__global__ void NormalizeKernel(\n    const int m,\n    const int n,\n    const int sf,\n    const float* xData,\n    float* yData) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ BlockReduce::TempStorage temp_storage;\n\n  for (int i = blockIdx.x; i < n; i += gridDim.x) {\n    auto base = (i / sf) * sf * m + (i % sf);\n\n    float sum = 0.0;\n    __shared__ float norm;\n    for (int j = threadIdx.x; j < m; j += blockDim.x) {\n      const auto x_ij = xData[base + j * sf];\n      sum += x_ij * x_ij;\n    }\n    float reduce_result = BlockReduce(temp_storage).Sum(sum);\n\n    if (threadIdx.x == 0) {\n      norm = sqrt(reduce_result);\n    }\n    __syncthreads();\n    if (norm != 0) {\n      for (int j = threadIdx.x; j < m; j += blockDim.x) {\n        const auto index = base + j * sf;\n        yData[index] = xData[index] / norm;\n      }\n    }\n  }\n}\n\n__global__ void NormalizeGradientKernel(\n    const int M,\n    const int N,\n    const int SF,\n    const float* in_mat,\n    const float* grad_out_mat,\n    float* grad_mat) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ BlockReduce::TempStorage temp_storage_sum;\n  __shared__ BlockReduce::TempStorage temp_storage_norm;\n  for (int i = blockIdx.x; i < M; i += gridDim.x) {\n    float sum = 0.0;\n    float norm = 0.0;\n    __shared__ float row_sum;\n    __shared__ float row_norm;\n    __shared__ float row_norm_3;\n    auto base = (i / SF) * SF * N + (i % SF);\n    for (int j = threadIdx.x; j < N; j += blockDim.x) {\n      int index = base + j * SF;\n      sum += in_mat[index] * grad_out_mat[index];\n      norm += in_mat[index] * in_mat[index];\n    }\n    float reduce_result = BlockReduce(temp_storage_sum).Sum(sum);\n    float reduce_norm = BlockReduce(temp_storage_norm).Sum(norm);\n\n    if (threadIdx.x == 0) {\n      row_sum = reduce_result;\n      row_norm = sqrt(reduce_norm);\n      row_norm_3 = pow(row_norm, 3);\n    }\n    __syncthreads();\n    for (int j = threadIdx.x; j < N; j += blockDim.x) {\n      int index = base + j * SF;\n      const float x_ij = in_mat[index];\n      const float dy_ij = grad_out_mat[index];\n      grad_mat[index] = (dy_ij / row_norm) - ((x_ij / row_norm_3) * row_sum);\n    }\n  }\n}\n\ntemplate <>\nvoid NormalizeOp<float, CUDAContext>::DoNormalize(\n    const float* xData,\n    float* yData,\n    const int m,\n    const int n,\n    const int sf) {\n  NormalizeKernel<<<\n      min(n, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(m, n, sf, xData, yData);\n}\n\ntemplate <>\nbool NormalizeGradientOp<float, CUDAContext>::RunOnDevice() {\n  const auto& X = Input(0);\n  const auto& dY = Input(1);\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n\n  const auto canonical_axis =\n      X.canonical_axis_index(OperatorBase::GetSingleArgument<int>(\"axis\", -1));\n  int N = X.dim32(canonical_axis);\n  int M = X.size() / N;\n  const int SF = X.size_from_dim(canonical_axis + 1);\n  NormalizeGradientKernel<<<\n      min(M, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      M, N, SF, X.data<float>(), dY.data<float>(), dX->mutable_data<float>());\n  return true;\n}\n\nnamespace {\n__global__ void NormalizeL1Kernel(\n    const int m,\n    const int n,\n    const int sf,\n    const float* xData,\n    float* yData) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ BlockReduce::TempStorage temp_storage;\n\n  for (int i = blockIdx.x; i < n; i += gridDim.x) {\n    auto base = (i / sf) * sf * m + (i % sf);\n\n    float sum = 0.0;\n    __shared__ float norm;\n    for (int j = threadIdx.x; j < m; j += blockDim.x) {\n      const auto x_ij = xData[base + j * sf];\n      sum += abs(x_ij);\n    }\n    float reduce_result = BlockReduce(temp_storage).Sum(sum);\n\n    if (threadIdx.x == 0) {\n      norm = reduce_result;\n    }\n    __syncthreads();\n    if (norm != 0) {\n      for (int j = threadIdx.x; j < m; j += blockDim.x) {\n        const auto index = base + j * sf;\n        yData[index] = xData[index] / norm;\n      }\n    }\n  }\n}\n} // namespace\n\ntemplate <>\nvoid NormalizeL1Op<float, CUDAContext>::DoNormalize(\n    const float* xData,\n    float* yData,\n    const int m,\n    const int n,\n    const int sf) {\n  NormalizeL1Kernel<<<\n      min(n, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(m, n, sf, xData, yData);\n}\n\nREGISTER_CUDA_OPERATOR(Normalize, NormalizeOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    NormalizeGradient,\n    NormalizeGradientOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(NormalizeL1, NormalizeL1Op<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/one_hot_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/one_hot_ops.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\ntemplate <>\ntemplate <typename T>\nbool BatchOneHotOp<CPUContext>::DoRunWithType() {\n  auto& input = Input(X);\n  auto& lens = Input(LENS);\n  auto& vals = Input(VALS);\n  CAFFE_ENFORCE_GE(input.ndim(), 1);\n  auto N = input.dim(0);\n  auto D = input.size_from_dim(1);\n  CAFFE_ENFORCE_EQ(lens.size(), D);\n\n  const auto* lens_data = lens.template data<int32_t>();\n  TIndex output_dim = 0;\n  valsOffsets_.resize(D + 1);\n  for (TIndex i = 0; i < D; i++) {\n    CAFFE_ENFORCE_GE(lens_data[i], 0);\n    valsOffsets_[i] = output_dim;\n    output_dim += lens_data[i];\n  }\n  valsOffsets_[D] = output_dim;\n\n  CAFFE_ENFORCE_EQ(vals.size(), output_dim);\n  auto* output = Output(ONE_HOT);\n  output->Resize(N, output_dim);\n\n  const auto* input_data = input.template data<T>();\n  const auto* vals_data = vals.template data<T>();\n  auto* output_data = output->template mutable_data<T>();\n\n  for (TIndex i = 0; i < N; ++i) {\n    for (TIndex j = 0; j < D; j++) {\n      const auto input_val = input_data[i * D + j];\n      for (TIndex k = valsOffsets_[j]; k < valsOffsets_[j + 1]; ++k) {\n        output_data[k] = vals_data[k] == input_val;\n      }\n    }\n    output_data += output_dim;\n  }\n\n  return true;\n}\n\ntemplate <>\nvoid OneHotOp<CPUContext>::DoOneHotOp(\n    TIndex batch_size,\n    TIndex index_size,\n    const Tensor<CPUContext>& indices,\n    Tensor<CPUContext>* one_hots) {\n  const TIndex* indices_ptr = indices.template data<TIndex>();\n  float* one_hots_ptr = one_hots->template mutable_data<float>();\n  memset(one_hots_ptr, 0, one_hots->nbytes());\n  for (int i = 0; i < batch_size; ++i) {\n    auto label_idx = indices_ptr[i];\n    DCHECK((0 <= label_idx) && (label_idx < index_size));\n    one_hots_ptr[label_idx] = 1.0;\n    one_hots_ptr += index_size;\n  }\n}\n\ntemplate <>\nbool BatchBucketOneHotOp<CPUContext>::RunOnDevice() {\n  auto& input = Input(X);\n  auto& lens = Input(LENS);\n  auto& boundaries = Input(BOUNDARIES);\n  CAFFE_ENFORCE_GE(input.ndim(), 1);\n  auto N = input.dim(0);\n  auto D = input.size_from_dim(1);\n  CAFFE_ENFORCE_EQ(lens.size(), D);\n\n  const auto* lens_data = lens.template data<int32_t>();\n\n  CAFFE_ENFORCE_EQ(\n      std::accumulate(lens_data, lens_data + lens.size(), 0),\n      boundaries.size(),\n      \"The sum of length should be equal to the length of boundaries\");\n\n  TIndex output_dim = 0;\n  for (TIndex i = 0; i < D; i++) {\n    CAFFE_ENFORCE_GT(lens_data[i], 0);\n    // Number of buckets is number of bucket edges + 1\n    output_dim += (lens_data[i] + 1);\n  }\n  auto* output = Output(ONE_HOT);\n  output->Resize(N, output_dim);\n\n  const auto* input_data = input.template data<float>();\n  const auto* boundaries_data = boundaries.template data<float>();\n  auto* output_data = output->template mutable_data<float>();\n\n  math::Set<float, CPUContext>(output->size(), 0.f, output_data, &context_);\n\n  TIndex pos = 0;\n  for (TIndex i = 0; i < N; i++) {\n    auto* boundaries_offset = boundaries_data;\n    TIndex output_offset = 0;\n\n    for (TIndex j = 0; j < D; j++) {\n      // here we assume the boundary values for each feature are sorted\n      TIndex bucket_idx = std::lower_bound(\n                              boundaries_offset,\n                              boundaries_offset + lens_data[j],\n                              input_data[pos]) -\n          boundaries_offset;\n      output_data[i * output_dim + output_offset + bucket_idx] = 1.0;\n      boundaries_offset += lens_data[j];\n      output_offset += (lens_data[j] + 1);\n      pos++;\n    }\n  }\n\n  return true;\n};\n\nclass SegmentOneHotOp : public Operator<CPUContext> {\n public:\n  SegmentOneHotOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& lengths = Input(0);\n    auto& indices = Input(1);\n    auto& index_size_tensor = Input(2);\n    CAFFE_ENFORCE(lengths.ndim() == 1);\n    CAFFE_ENFORCE(indices.ndim() == 1);\n    CAFFE_ENFORCE(index_size_tensor.size() == 1);\n    auto batch_size = lengths.size();\n    auto index_size = *index_size_tensor.data<int64_t>();\n    CAFFE_ENFORCE(index_size > 0);\n\n    auto* lengths_ptr = lengths.data<int32_t>();\n    auto* indices_ptr = indices.data<int64_t>();\n    auto* one_hots = Output(0);\n    one_hots->Resize(batch_size, index_size);\n    auto* one_hots_ptr = one_hots->mutable_data<float>();\n    if (one_hots->size() == 0) {\n      return true;\n    }\n    memset(one_hots_ptr, 0, one_hots->nbytes());\n    int el_idx = 0;\n    for (int i = 0; i < batch_size; ++i) {\n      for (int j = 0; j < lengths_ptr[i]; ++j) {\n        DCHECK(el_idx < indices.size());\n        auto label_idx = indices_ptr[el_idx++];\n        DCHECK((0 <= label_idx) && (label_idx < index_size));\n        one_hots_ptr[label_idx] = 1.0;\n      }\n      one_hots_ptr += index_size;\n    }\n    return true;\n  }\n};\nREGISTER_CPU_OPERATOR(BatchBucketOneHot, BatchBucketOneHotOp<CPUContext>);\nREGISTER_CPU_OPERATOR(BatchOneHot, BatchOneHotOp<CPUContext>);\nREGISTER_CPU_OPERATOR(OneHot, OneHotOp<CPUContext>);\nREGISTER_CPU_OPERATOR(SegmentOneHot, SegmentOneHotOp);\n\nOPERATOR_SCHEMA(BatchBucketOneHot)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nInput is a matrix tensor. Its first dimension is the batch\nsize. For each column, bucketize it based on the boundary values and then do\none hot encoding. The `lengths` specifies the number of boundary values for each\ncolumn. The final number of buckets is this number plus 1. This would also be\nthe expanded feature size. `boundaries` specifies all the boundary values.\nNote that each bucket is right-inclusive. That is, given boundary values\n[b1, b2, b3], the buckets are defined as (-int, b1], (b1, b2], (b2, b3], (b3, inf).\nFor example\n\n  If data = [[2, 3], [4, 1], [2, 5]], lengths = [2, 3],\n  and boundaries = [0.1, 2.5, 1, 3.1, 4.5], then\n\n  output = [[0, 1, 0, 0, 1, 0, 0], [0, 0, 1, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0, 1]]\n\n)DOC\")\n    .Input(0, \"data\", \"input tensor matrix\")\n    .Input(1, \"lengths\", \"the size is the same as the width of the `data`\")\n    .Input(2, \"boundaries\", \"bucket boundaries\")\n    .Output(\n        0,\n        \"output\",\n        \"output matrix that expands each input column with one hot encoding\"\n        \"based on the bucketization\");\n\nOPERATOR_SCHEMA(BatchOneHot)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nInput is a matrix tensor. Its first dimension is the batch\nsize. Expand each column of it using one hot encoding. The `lengths` specifies\nthe size of each column after encoding, and the `values` is the dictionary value\nof one-hot encoding for each column. For example\n\n  If data = [[2, 3], [4, 1], [2, 5]], lengths = [2, 3],\n  and values = [2, 4, 1, 3, 5], then\n\n  output = [[1, 0, 0, 1, 0], [0, 1, 1, 0, 0], [1, 0, 0, 0, 1]]\n)DOC\")\n    .Input(0, \"data\", \"input tensor matrix\")\n    .Input(1, \"lengths\", \"the size is the same as the width of the `data`\")\n    .Input(2, \"values\", \"one hot encoding dictionary values\")\n    .Output(\n        0,\n        \"output\",\n        \"output matrix that expands each input column with one hot encoding\");\n\nOPERATOR_SCHEMA(OneHot)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven a sequence of indices, one for each example in a batch, returns a matrix\nwhere each inner dimension has the size of the index and has 1.0 in the index\nactive in the given example, and 0.0 everywhere else.\n)DOC\")\n    .Input(0, \"indices\", \"The active index for each example in the batch.\")\n    .Input(\n        1,\n        \"index_size_tensor\",\n        \"Scalar with the size of the index. Must be in CPU context\")\n    .Output(0, \"one_hots\", \"Matrix of size len(indices) x index_size\");\n\nOPERATOR_SCHEMA(SegmentOneHot)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven a sequence of indices, segmented by the lengths tensor, returns a matrix\nthat has the elements in each sequence set to 1.0, and 0.0 everywhere else.\n)DOC\")\n    .Input(0, \"lengths\", \"Size of each segment.\")\n    .Input(1, \"indices\", \"Active indices, of size sum(lengths)\")\n    .Input(2, \"index_size_tensor\", \"Size of the index\")\n    .Output(0, \"one_hots\", \"Matrix of size len(lengths) x index_size\");\n\nNO_GRADIENT(BatchOneHot);\nNO_GRADIENT(OneHot);\nNO_GRADIENT(SegmentOneHot);\nNO_GRADIENT(BucketBatchOneHot);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/one_hot_ops.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cub/block/block_reduce.cuh>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/one_hot_ops.h\"\n\nnamespace caffe2 {\n\n__global__ void OneHotOpKernel(\n    const TIndex batch_size,\n    const TIndex index_size,\n    const TIndex* indices,\n    float* output) {\n  CUDA_1D_KERNEL_LOOP(i, batch_size) {\n    output[i * index_size + indices[i]] = 1.;\n  }\n}\n\ntemplate <>\nvoid OneHotOp<CUDAContext>::DoOneHotOp(\n    TIndex batch_size,\n    TIndex index_size,\n    const Tensor<CUDAContext>& indices,\n    Tensor<CUDAContext>* output) {\n  float* output_ptr = output->mutable_data<float>();\n  math::Set<float, CUDAContext>(output->size(), 0., output_ptr, &context_);\n  OneHotOpKernel<<<\n      CAFFE_GET_BLOCKS(batch_size),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      batch_size, index_size, indices.data<TIndex>(), output_ptr);\n}\n\nREGISTER_CUDA_OPERATOR(OneHot, OneHotOp<CUDAContext>);\n} // namespace\n"
  },
  {
    "path": "caffe2/operators/one_hot_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE_OPERATORS_ONE_HOT_OPS_H_\n#define CAFFE_OPERATORS_ONE_HOT_OPS_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass OneHotOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  OneHotOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& indices = Input(0);\n    CAFFE_ENFORCE_EQ(\n        indices.ndim(),\n        1,\n        \"indices input must be 1D tensor of data type TIndex\");\n\n    // Index size input must be in CPU context\n    auto& index_size_tensor = OperatorBase::Input<Tensor<CPUContext>>(1);\n    CAFFE_ENFORCE_EQ(\n        index_size_tensor.size(),\n        1,\n        \"index_size_tensor input must be scalar of data type TIndex\");\n\n    auto batch_size = indices.size();\n    auto index_size = *index_size_tensor.template data<TIndex>();\n    auto one_hots = Output(0);\n    one_hots->Resize(batch_size, index_size);\n    auto output_size = one_hots->size();\n    if (output_size == 0) {\n      return true;\n    }\n\n    DoOneHotOp(batch_size, index_size, indices, one_hots);\n    return true;\n  }\n\n protected:\n  void DoOneHotOp(\n      TIndex batch_size,\n      TIndex index_size,\n      const Tensor<Context>& indices,\n      Tensor<Context>* output);\n};\n\ntemplate <class Context>\nclass BatchOneHotOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  BatchOneHotOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(this, Input(X));\n  }\n\n  template <typename T>\n  bool DoRunWithType();\n\n protected:\n  INPUT_TAGS(X, LENS, VALS);\n  OUTPUT_TAGS(ONE_HOT);\n\n private:\n  // allows for fast random access to a given dict and is re-used across runs\n  std::vector<TIndex> valsOffsets_;\n};\n\ntemplate <class Context>\nclass BatchBucketOneHotOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  BatchBucketOneHotOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override;\n\n protected:\n  INPUT_TAGS(X, LENS, BOUNDARIES);\n  OUTPUT_TAGS(ONE_HOT);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE_OPERATORS_ONE_HOT_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/onnx_while_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/onnx_while_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(ONNXWhile, ONNXWhileOp<CPUContext>);\n\nOPERATOR_SCHEMA(ONNXWhile)\n    .NumInputs(2, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .SetDoc(R\"DOC(\n*** EXPERIMENTAL. This operator is a work-in-progress. No assumption should be\nmade about the stability or correctness of this op. ***\n\nGeneric Looping construct confirming to the ONNX Loop operator spec. This loop\nhas multiple termination conditions:\n\n1. Trip count. Iteration count specified at runtime. Set by specifying the\n    input M. Optional. Set to empty string to omit. Note that a static trip\n    count (specified at graph construction time) can be specified by passing\n    in a constant node for input M.\n2. Loop termination condition. This is an input to the op that determines\n    whether to run the first interation and also a loop-carried dependency for\n    the body graph. The body graph must yield a value for the condition\n    variable, whether this input is provided or not.\n\nThis table summarizes the operating modes of this operator with equivalent\nC-style code:\n\nOperator inputs defined as (max_trip_count, condition_var). Omitted optional\ninputs are represented as empty string. Concretely, in this caffe2 op an input\nis marked as omitted by setting its 'has_{name}' argument to False.\n\n    input (\"\", \"\"):\n        for (int i=0; ; ++i) {\n          cond = ... // Note this value is ignored, but is required in the body\n        }\n\n    input (\"\", cond) // Note this is analogous to a while loop\n        bool cond = ...;\n        for (int i=0; cond; ++i) {\n          cond = ...;\n        }\n\n    input (\"\", 1) // Note this is analogous to a do-while loop\n        bool cond = true\n        for (int i=0; cond; ++i) {\n          cond = ...;\n        }\n\n    input (trip_count, \"\") // Note this is analogous to a for loop\n        int trip_count = ...\n        for (int i=0; i < trip_count; ++i) {\n          cond = ...; // ignored\n        }\n\n    input (trip_count, cond)\n        int trip_count = ...;\n        bool cond = ...;\n        for (int i=0; i < trip_count && cond; ++i) {\n          cond = ...;\n        }\n    )DOC\")\n    .Arg(\"loop_net\", \"Net executed on each iteration\")\n    .Input(0, \"condition\", \"Scalar boolean condition\")\n    .AllowInplace([](int in, int out) -> bool { return true; });\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/onnx_while_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_ONNX_WHILE_OP_H_\n#define CAFFE2_OPERATORS_ONNX_WHILE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/create_scope_op.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass ONNXWhileOp final : public Operator<Context> {\n public:\n  ONNXWhileOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        parent_ws_(ws),\n        has_trip_count_(\n            OperatorBase::GetSingleArgument<int64_t>(\"has_trip_count\", 0)),\n        has_cond_(OperatorBase::GetSingleArgument<int64_t>(\"has_cond\", 0)),\n        save_scopes_(OperatorBase::GetSingleArgument<int64_t>(\"save_scopes\", 0)) {\n    CAFFE_ENFORCE(\n        this->template HasSingleArgumentOfType<NetDef>(\"body\"),\n        \"body net must be specified in ONNXWhile operator\");\n    body_net_def_ = this->template GetSingleArgument<NetDef>(\"body\", NetDef());\n    if (!body_net_def_.has_name()) {\n      body_net_def_.set_name(\"loop_net\");\n    }\n  }\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  // Operator\n  //  Inputs: max trip count, condition, initial loop-carried dependencies\n  //  Outputs: Final loop-carried dependencies, scan_outputs\n  // Body\n  //  Inputs: iteration number, condition, loop-carried dependencies\n  //  Outputs: condition, loop-carried dependencies, scan_outputs\n  bool RunOnDevice() override {\n    // Clear workspaces from the previous invocations of the loop\n    // and setup a local scope for the first iteration\n    ws_stack_.clear();\n    auto loop_ws = ws_stack_.pushForwardWorkspace(parent_ws_);\n    scope_ = std::make_shared<LocalScope>(loop_ws, body_net_def_);\n\n    constexpr int64_t num_inputs_before_lcds = 2;\n    // First input is the maximumt trip count. Second input is the condition\n    // variable (for the first iteration). The rest of the inputs are\n    // loop-carried dependencies.\n    int num_loop_carried_deps = InputSize() - num_inputs_before_lcds;\n    int64_t max_trip_count = *Input(0).template data<int64_t>();\n    const bool first_iter_condition = *Input(1).template data<bool>();\n\n    // Body graph has 2+N inputs: iteration number, condition value, and N\n    // loop-carried dependencies\n    CAFFE_ENFORCE_EQ(\n        num_loop_carried_deps + 2,\n        scope_->net()->external_input().size(),\n        \"Body graph must have 2+N inputs, where N is the number of \"\n        \"loop carried dependencies.\");\n\n    // Body graph has 1+N+K outputs: recalculated condition variable, N\n    // loop-carried dependencies, and K scan_outputs\n    int num_scan_outputs =\n        scope_->net()->external_output().size() - num_loop_carried_deps - 1;\n\n    CAFFE_ENFORCE_GE(\n        num_scan_outputs,\n        0,\n        \"Body graph must have N+K outputs, where N is the number \"\n        \"of loop-carried dependencies and K is the number of scan \"\n        \"outputs\");\n\n    // Copy initial loop-carried dependencies\n    for (int i = 0; i < num_loop_carried_deps; ++i) {\n      scope_->lcd_tensor(i)->CopyFrom(Input(i + num_inputs_before_lcds));\n    }\n\n    // Initialize iteration variable\n    scope_->set_iteration(0ll);\n\n    // Initialize input condition variable\n    scope_->set_input_condition(first_iter_condition);\n\n    auto valid_iter_num = [this, max_trip_count](int64_t i) {\n      if (has_trip_count_) {\n        return i < max_trip_count;\n      } else {\n        return true;\n      }\n    };\n\n    auto condition_true =\n        [this, first_iter_condition](int64_t i, bool cond_value) {\n          if (has_cond_) {\n            if (i == 0) {\n              return (bool)first_iter_condition;\n            } else {\n              return cond_value;\n            }\n          } else {\n            return true;\n          }\n        };\n\n    // Allocate scan_outputs for zero-iteration case\n    for (int i = 0; i < num_scan_outputs; ++i) {\n      Output(i + num_loop_carried_deps)->Resize(0);\n      Output(i + num_loop_carried_deps)->template mutable_data<int32_t>();\n    }\n\n    // Use this to keep track of the sizes of the scan outputs and validate\n    // they're the same across iterations.\n    std::vector<std::vector<TIndex>> scan_outputs_sizes;\n\n    std::shared_ptr<Workspace> cur_ws = nullptr;\n    bool cur_output_condition = false;\n\n    while (true) {\n      int64_t itr = scope_->iteration();\n      if (valid_iter_num(itr) && condition_true(itr, cur_output_condition)) {\n        if (!scope_->net()->Run()) {\n          return false;\n        }\n\n        cur_ws = scope_->workspace();\n        cur_output_condition = scope_->output_condition();\n        if (save_scopes_) {\n          loop_ws = ws_stack_.pushForwardWorkspace(parent_ws_);\n          scope_ = std::make_shared<LocalScope>(loop_ws, body_net_def_);\n        }\n\n        // Copy forward loop-carried dependencies\n        for (int i = 0; i < num_loop_carried_deps; ++i) {\n          Blob* b = cur_ws->GetBlob(\n              scope_->net()->external_output()[i + 1]);\n          const Tensor<Context>& t = b->template Get<Tensor<Context>>();\n          scope_->lcd_tensor(i)->CopyFrom(t);\n        }\n        // Copy out scan_outputs\n        for (int i = 0; i < num_scan_outputs; ++i) {\n          int net_output_idx = i + 1 + num_loop_carried_deps;\n          const Tensor<Context>& scan_output =\n              cur_ws->GetBlob(\n                  scope_->net()->external_output()[net_output_idx])\n                  ->template Get<Tensor<Context>>();\n          auto* scan_output_target = Output(i + num_loop_carried_deps);\n          if (itr == 0) {\n            auto dims = scan_output.dims();\n            scan_outputs_sizes.push_back(dims);\n            dims.insert(dims.begin(), 1);\n            scan_output_target->Resize(dims);\n            scan_output_target->CopyFrom(scan_output);\n          } else {\n            auto dims = scan_output.dims();\n            CAFFE_ENFORCE_EQ(\n                dims,\n                scan_outputs_sizes[i],\n                \"Size of scan output changed across iterations\");\n            dims.insert(dims.begin(), itr);\n            scan_output_target->Extend(1, 2.0f, &context_);\n\n            TIndex timestep_size = 1;\n            for (const TIndex t : scan_outputs_sizes[i]) {\n              timestep_size *= t;\n            }\n\n            const void* src_data = scan_output.raw_data();\n            auto& sot_meta = scan_output_target->meta();\n            void* dst_data =\n                (char*)scan_output_target->raw_mutable_data(sot_meta) +\n                timestep_size * scan_output.itemsize() * itr;\n            memcpy(dst_data, src_data, timestep_size * scan_output.itemsize());\n          }\n        }\n        scope_->set_iteration(itr + 1ll);\n        scope_->set_input_condition(cur_output_condition);\n      } else {\n        break;\n      }\n    }\n\n    if (scope_->iteration() > 0) {\n      // Copy out final loop-carried dependencies\n      for (int i = 0; i < num_loop_carried_deps; ++i) {\n        Output(i)->CopyFrom(*scope_->lcd_tensor(i));\n      }\n    } else {\n      // Copy out final loop-carried dependencies\n      for (int i = 0; i < num_loop_carried_deps; ++i) {\n        Output(i)->CopyFrom(Input(i + num_inputs_before_lcds));\n      }\n    }\n\n    return true;\n  }\n\n private:\n  class LocalScope {\n   public:\n    LocalScope(\n        const std::shared_ptr<Workspace>& loop_ws,\n        const NetDef& body_net_def) : loop_ws_(loop_ws) {\n      CAFFE_ENFORCE(loop_ws_,\n          \"Failed to initialize local loop workspace\");\n\n      // Create loop-carried deps in Workspace\n      lcd_tensors_.clear();\n      for (int i = 2; i < body_net_def.external_input_size(); ++i) {\n        Blob* b = loop_ws_->CreateBlob(body_net_def.external_input(i));\n        Tensor<Context>* t = b->template GetMutable<Tensor<Context>>();\n        lcd_tensors_.push_back(t);\n      }\n      // First output is the iteration variable\n      auto* iteration_var_blob = loop_ws_->CreateBlob(\n          body_net_def.external_input(0));\n      iteration_var_ =\n          iteration_var_blob->template GetMutable<Tensor<Context>>();\n\n      input_condition_var_ = loop_ws_->CreateBlob(\n          body_net_def.external_input(1))\n          ->template GetMutable<Tensor<Context>>();\n\n      auto* condition_var_blob =\n          loop_ws_->CreateBlob(body_net_def.external_output(0));\n      condition_var_ = condition_var_blob->template GetMutable<Tensor<Context>>();\n      condition_var_->Resize(1);\n      condition_var_->template mutable_data<bool>();\n\n      body_net_ = loop_ws_->GetNet(body_net_def.name());\n      if (!body_net_) {\n        body_net_ = loop_ws_->CreateNet(body_net_def, true);\n      }\n      CAFFE_ENFORCE(body_net_, \"Failed to initialize loop subnet\");\n    }\n\n    NetBase* net() const {\n      return body_net_;\n    }\n\n    std::shared_ptr<Workspace> workspace() const {\n      return loop_ws_;\n    }\n\n    int64_t iteration() const {\n      auto* iteration_var_ptr =\n          iteration_var_->template mutable_data<int64_t>();\n      return *iteration_var_ptr;\n    }\n\n    Tensor<Context>* lcd_tensor(int idx) {\n      return lcd_tensors_[idx];\n    }\n\n    void set_iteration(int64_t itr) {\n      iteration_var_->Resize(1);\n      auto* iteration_var_ptr =\n          iteration_var_->template mutable_data<int64_t>();\n      *iteration_var_ptr = itr;\n    }\n\n    void set_input_condition(bool cond_value) {\n      input_condition_var_->Resize(1);\n      auto* input_condition_var_ptr =\n          input_condition_var_->template mutable_data<bool>();\n      *input_condition_var_ptr = cond_value;\n    }\n\n    bool output_condition() const {\n      auto* condition_var_ptr =\n          condition_var_->template mutable_data<bool>();\n      return *condition_var_ptr;\n    }\n\n   private:\n    std::shared_ptr<Workspace> loop_ws_;\n\n    NetBase* body_net_; // owned by a workspace\n    Tensor<Context>* iteration_var_;\n    Tensor<Context>* input_condition_var_;\n    Tensor<Context>* condition_var_;\n\n    std::vector<Tensor<Context>*> lcd_tensors_;\n  };\n\n  NetDef body_net_def_;\n  Workspace* parent_ws_;\n  detail::WorkspaceStack ws_stack_;\n\n  bool has_trip_count_;\n  bool has_cond_;\n  bool save_scopes_;\n\n  std::shared_ptr<LocalScope> scope_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_ONNX_WHILE_OP_H\n"
  },
  {
    "path": "caffe2/operators/op_utils_cudnn.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CUDNN_OP_UTILS_H_\n#define CAFFE2_OPERATORS_CUDNN_OP_UTILS_H_\n\n#include \"caffe2/core/cudnn_wrappers.h\"\n\nnamespace caffe2 {\n\n// Earlier in the days Caffe sets the default cudnn workspace to 8MB. We bump\n// it up to 64MB in Caffe2, as this enables the use of Winograd in many cases,\n// something very beneficial to more recent CNN models.\nstatic constexpr size_t kCONV_CUDNN_WORKSPACE_LIMIT_BYTES = 64 * 1024 * 1024;\n\n// Manually specified number of algorithms implemented in CuDNN.\n// This does not have any performance implications, as we will always find the\n// fastest algorithm; setting them to the right number of algorithms will enable\n// us to best report the statistics when doing an exhaustive search, though.\n#if CUDNN_VERSION_MIN(7, 0, 0)\n// Note: Double each of these due to potential\n// tensorcore + non-tensorcore versions\n// which are treated as separate returned algos\nstatic constexpr size_t kNUM_CUDNN_FWD_ALGS =\n    2 * CUDNN_CONVOLUTION_FWD_ALGO_COUNT;\nstatic constexpr size_t kNUM_CUDNN_BWD_FILTER_ALGS =\n    2 * CUDNN_CONVOLUTION_BWD_FILTER_ALGO_COUNT;\nstatic constexpr size_t kNUM_CUDNN_BWD_DATA_ALGS =\n    2 * CUDNN_CONVOLUTION_BWD_DATA_ALGO_COUNT;\n#else\nstatic constexpr size_t kNUM_CUDNN_FWD_ALGS = 7;\nstatic constexpr size_t kNUM_CUDNN_BWD_FILTER_ALGS = 4;\nstatic constexpr size_t kNUM_CUDNN_BWD_DATA_ALGS = 5;\n#endif\n\nnamespace {\ntemplate <typename ArrayOfcudnnConvolutionAlgoPerf_t>\ninline void LogCuDNNPerfStats(\n    const ArrayOfcudnnConvolutionAlgoPerf_t& perf_stat,\n    int returned_algo_count) {\n  VLOG(1) << \"Perf result: (algo: stat, time, memory)\";\n  for (int i = 0; i < returned_algo_count; ++i) {\n    const auto& stat = perf_stat[i];\n    VLOG(1) << stat.algo << \": \" << stat.status << \" \" << stat.time << \" \"\n            << stat.memory;\n  }\n}\n} // namespace\n\n// Easier indexing into force_algo_ vector,\n// shared by CudnnConvTransposeOpBase and CudnnConvOpBase to force\n// usage of a particular algortihm instead of searching\nenum { ALGO_FWD = 0, ALGO_WGRAD = 1, ALGO_DGRAD = 2 };\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CUDNN_OP_UTILS_H_\n"
  },
  {
    "path": "caffe2/operators/operator_fallback_gpu.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_OPERATOR_FALLBACK_H_\n#define CAFFE2_OPERATORS_OPERATOR_FALLBACK_H_\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\n/**\n * @brief A templated class to allow one to wrap a CPU operator as a CUDA\n * operator.\n *\n * This class can be used when one does not have the CUDA implementation ready\n * yet for an operator. Essentially, what this op does is to automatically\n * deal with data copy for you. Plausibly, this causes a lot of overhead and\n * is not optimal, so you should use this operator mostly for quick prototyping\n * purpose.\n *\n * All the input and output of the original operator should be TensorCPU.\n *\n * Example usage: if you have a class MyMagicOp that is CPU based, and you use\n * the registration code\n *     REGISTER_CPU_OPERATOR(MyMagic, MyMagicOp);\n * to register the CPU side, you can create its corresponding GPU operator\n * (with performance hits of course) via\n *     REGISTER_CUDA_OPERATOR(MyMagic,\n *                            GPUFallbackOp<MyMagicOp>);\n *\n * Advanced usage: if you want to have some specific outputs never copied, you\n * can use the SkipOutputCopy template argument to do that. For example, if\n * MyMagic produces two outputs and the first output is always going to live on\n * the CPU, you can do\n *     REGISTER_CUDA_OPERATOR(MyMagic,\n *                            GPUFallbackOp<MyMagicOp, SkipIndices<0>>);\n */\ntemplate <class CPUOp, typename SkipOutputCopy = SkipIndices<>>\nclass GPUFallbackOp final : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  GPUFallbackOp(const OperatorDef& def, Workspace* ws)\n      : Operator<CUDAContext>(def, ws) {\n    CAFFE_ENFORCE_EQ(def.device_option().device_type(), CUDA);\n    OperatorDef base_def_(def);\n    // base_def_ runs on CPU, so we will set its device option to CPU.\n    base_def_.clear_device_option();\n    base_def_.mutable_device_option()->set_device_type(CPU);\n    // Set up the symbols for the local workspace.\n    for (const string& name : def.input()) {\n      local_input_blobs_.push_back(local_ws_.CreateBlob(name));\n      CHECK_NOTNULL(local_input_blobs_.back());\n    }\n    base_op_.reset(new CPUOp(base_def_, &local_ws_));\n    for (const string& name : def.output()) {\n      local_output_blobs_.push_back(local_ws_.GetBlob(name));\n      CHECK_NOTNULL(local_output_blobs_.back());\n    }\n  }\n\n  bool RunOnDevice() override {\n    bool need_sync = false;\n    for (int i = 0; i < InputSize(); ++i) {\n      if (OperatorBase::InputIsType<TensorCUDA>(i)) {\n        local_input_blobs_[i]->template GetMutable<TensorCPU>()->CopyFrom(\n            Input(i), &context_);\n        need_sync = true;\n      } else {\n        VLOG(1) << \"Input \" << i << \" is not TensorCUDA. Skipping copy.\";\n        // Note(jiayq): This removes a const but conceptually\n        // local_input_blobs will only be used as const blob input for the\n        // base op so we are still fine.\n        local_input_blobs_[i]->ShareExternal(\n            const_cast<void*>(OperatorBase::Inputs()[i]->GetRaw()),\n            OperatorBase::Inputs()[i]->meta());\n      }\n    }\n\n    // Sync to make sure copies are done.\n    if (need_sync) {\n      context_.FinishDeviceComputation();\n    }\n\n    if (!base_op_->Run()) {\n      LOG(ERROR) << \"Base op run failed in GPUFallbackOp. Def: \"\n                 << ProtoDebugString(this->debug_def());\n      return false;\n    }\n    for (int i = 0; i < OutputSize(); ++i) {\n      if (SkipOutputCopy::Contains(i)) {\n        VLOG(1) << \"Copy output: index \" << i << \" skipped.\";\n        continue;\n      }\n      CAFFE_ENFORCE(\n          local_output_blobs_[i]->template IsType<TensorCPU>(),\n          \"GPU fallback op currently does not support non-TensorCPU \"\n          \"output type who needs copying.\");\n      Output(i)->CopyFrom(\n          local_output_blobs_[i]->template Get<TensorCPU>(), &context_);\n    }\n    return true;\n  }\n\n protected:\n  Workspace local_ws_;\n  vector<Blob*> local_input_blobs_;\n  vector<Blob*> local_output_blobs_;\n  std::unique_ptr<CPUOp> base_op_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_OPERATOR_FALLBACK_H_\n"
  },
  {
    "path": "caffe2/operators/operator_fallback_gpu_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/operator_fallback_gpu.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\n\n\nclass IncrementByOneOp final : public Operator<CPUContext> {\n public:\n  IncrementByOneOp(const OperatorDef& def, Workspace* ws)\n      : Operator<CPUContext>(def, ws) {}\n  bool RunOnDevice() {\n    const auto& in = Input(0);\n    auto* out = Output(0);\n    out->ResizeLike(in);\n    const float* in_data = in.template data<float>();\n    float* out_data = out->template mutable_data<float>();\n    for (int i = 0; i < in.size(); ++i) {\n      out_data[i] = in_data[i] + 1.f;\n    }\n    return true;\n  }\n};\n\n\nOPERATOR_SCHEMA(IncrementByOne)\n    .NumInputs(1).NumOutputs(1).AllowInplace({{0, 0}});\n\nREGISTER_CPU_OPERATOR(IncrementByOne, IncrementByOneOp);\nREGISTER_CUDA_OPERATOR(IncrementByOne,\n                       GPUFallbackOp<IncrementByOneOp>);\n\nTEST(OperatorFallbackTest, IncrementByOneOp) {\n  OperatorDef op_def = CreateOperatorDef(\n      \"IncrementByOne\", \"\", vector<string>{\"X\"},\n      vector<string>{\"X\"});\n  Workspace ws;\n  TensorCPU source_tensor(vector<TIndex>{2, 3});\n  for (int i = 0; i < 6; ++i) {\n    source_tensor.mutable_data<float>()[i] = i;\n  }\n  ws.CreateBlob(\"X\")->GetMutable<TensorCPU>()->CopyFrom(source_tensor);\n  unique_ptr<OperatorBase> op(CreateOperator(op_def, &ws));\n  EXPECT_TRUE(op.get() != nullptr);\n  EXPECT_TRUE(op->Run());\n  const TensorCPU& output = ws.GetBlob(\"X\")->Get<TensorCPU>();\n  EXPECT_EQ(output.ndim(), 2);\n  EXPECT_EQ(output.dim(0), 2);\n  EXPECT_EQ(output.dim(1), 3);\n  for (int i = 0; i < 6; ++i) {\n    EXPECT_EQ(output.data<float>()[i], i + 1);\n  }\n}\n\nTEST(OperatorFallbackTest, GPUIncrementByOneOp) {\n  if (!HasCudaGPU()) return;\n  OperatorDef op_def = CreateOperatorDef(\n      \"IncrementByOne\", \"\", vector<string>{\"X\"},\n      vector<string>{\"X\"});\n  op_def.mutable_device_option()->set_device_type(CUDA);\n  Workspace ws;\n  TensorCPU source_tensor(vector<TIndex>{2, 3});\n  for (int i = 0; i < 6; ++i) {\n    source_tensor.mutable_data<float>()[i] = i;\n  }\n  ws.CreateBlob(\"X\")->GetMutable<TensorCUDA>()->CopyFrom(source_tensor);\n  unique_ptr<OperatorBase> op(CreateOperator(op_def, &ws));\n  EXPECT_TRUE(op.get() != nullptr);\n  EXPECT_TRUE(op->Run());\n  const TensorCUDA& output = ws.GetBlob(\"X\")->Get<TensorCUDA>();\n  TensorCPU output_cpu(output);\n  EXPECT_EQ(output.ndim(), 2);\n  EXPECT_EQ(output.dim(0), 2);\n  EXPECT_EQ(output.dim(1), 3);\n  for (int i = 0; i < 6; ++i) {\n    EXPECT_EQ(output_cpu.data<float>()[i], i + 1);\n  }\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/order_switch_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/order_switch_ops.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool NHWC2NCHWOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE(X.ndim() == 4);\n  const int N = X.dim32(0), H = X.dim32(1), W = X.dim32(2), C = X.dim32(3);\n  Y->Resize(N, C, H, W);\n  const float* Xdata = X.data<float>();\n  float* Ydata = Y->mutable_data<float>();\n  for (int n = 0; n < N; ++n) {\n    for (int h = 0; h < H; ++h) {\n      for (int w = 0; w < W; ++w) {\n        for (int c = 0; c < C; ++c) {\n          Ydata[((n * C + c) * H + h) * W + w] = *(Xdata++);\n        }\n      }\n    }\n  }\n  return true;\n}\n\ntemplate <>\nbool NCHW2NHWCOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE(X.ndim() == 4);\n  const int N = X.dim32(0), C = X.dim32(1), H = X.dim32(2), W = X.dim32(3);\n  Y->Resize(N, H, W, C);\n  const float* Xdata = X.data<float>();\n  float* Ydata = Y->mutable_data<float>();\n  for (int n = 0; n < N; ++n) {\n    for (int c = 0; c < C; ++c) {\n      for (int h = 0; h < H; ++h) {\n        for (int w = 0; w < W; ++w) {\n          Ydata[((n * H + h) * W + w) * C + c] = *(Xdata++);\n        }\n      }\n    }\n  }\n  return true;\n}\n\n\nREGISTER_CPU_OPERATOR(NHWC2NCHW, NHWC2NCHWOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(NCHW2NHWC, NCHW2NHWCOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(NHWC2NCHW)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction([](const OperatorDef& /*unused*/ /*def*/,\n                                const vector<TensorShape>& in) {\n      CAFFE_ENFORCE_EQ(\n          in[0].dims_size(), 4, \"Input for NHWC2NCHW must be 4 dimensional\");\n      vector<TensorShape> out(1);\n      out[0].add_dims(in[0].dims(0));\n      out[0].add_dims(in[0].dims(3));\n      out[0].add_dims(in[0].dims(1));\n      out[0].add_dims(in[0].dims(2));\n      return out;\n    })\n    .SetDoc(R\"DOC(\nThe operator switches the order of data in a tensor from NHWC- sample index N,\nheight H, width H and channels C, to the NCHW order.\n)DOC\")\n    .Input(0, \"data\", \"The input data (Tensor<float>) in the NHWC order.\")\n    .Output(\n        0,\n        \"output\",\n        \"The output tensor (Tensor<float>) in the NCHW order.\");\n\nOPERATOR_SCHEMA(NCHW2NHWC).NumInputs(1).NumOutputs(1)\n  .SetDoc(R\"DOC(\nThe operator switches the order of data in a tensor from NCHW- sample index N,\nchannels C, height H and width W, to the NHWC order.\n)DOC\")\n  .Input(0, \"data\", \"The input data (Tensor<float>) in the NCHW order.\")\n  .Output(0, \"output\", \"The output tensor (Tensor<float>) in the NHWC order.\");\n\n\nclass GetNHWC2NCHWGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"NCHW2NHWC\", \"\",\n        vector<string>{GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(NHWC2NCHW, GetNHWC2NCHWGradient);\n\nclass GetNCHW2NHWCGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"NHWC2NCHW\", \"\",\n        vector<string>{GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(NCHW2NHWC, GetNCHW2NHWCGradient);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/order_switch_ops.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/order_switch_ops.h\"\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\n__global__ void NHWC2NCHWKernel(const int N, const int HW, const int C,\n                                const float* X, float* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N * HW * C) {\n    const int c = i % C;\n    const int hw = i / C % HW;\n    const int n = i / C / HW;\n    Y[(n * C + c) * HW + hw] = X[i];\n  }\n}\n\n__global__ void NCHW2NHWCKernel(const int N, const int C, const int HW,\n                                const float* X, float* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N * C * HW) {\n    const int hw = i % HW;\n    const int c = i / HW % C;\n    const int n = i / C / HW;\n    Y[(n * HW + hw) * C + c] = X[i];\n  }\n}\n\ntemplate <>\nbool NHWC2NCHWOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  DCHECK_EQ(X.ndim(), 4);\n  const int N = X.dim32(0), H = X.dim32(1), W = X.dim32(2), C = X.dim32(3);\n  Y->Resize(N, C, H, W);\n  NHWC2NCHWKernel<<<CAFFE_GET_BLOCKS(X.size()), CAFFE_CUDA_NUM_THREADS,\n                    0, context_.cuda_stream()>>>(\n      N, H * W, C, X.data<float>(), Y->mutable_data<float>());\n  return true;\n}\n\ntemplate <>\nbool NCHW2NHWCOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  DCHECK_EQ(X.ndim(), 4);\n  const int N = X.dim32(0), C = X.dim32(1), H = X.dim32(2), W = X.dim32(3);\n  Y->Resize(N, H, W, C);\n  NCHW2NHWCKernel<<<CAFFE_GET_BLOCKS(X.size()), CAFFE_CUDA_NUM_THREADS,\n                    0, context_.cuda_stream()>>>(\n      N, C, H * W, X.data<float>(), Y->mutable_data<float>());\n  return true;\n}\n\n\nREGISTER_CUDA_OPERATOR(NHWC2NCHW, NHWC2NCHWOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(NCHW2NHWC, NCHW2NHWCOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/order_switch_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_ORDER_SWITCH_OPS_H_\n#define CAFFE2_OPERATORS_ORDER_SWITCH_OPS_H_\n\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\n// Note(Yangqing): I think it is possible to do a more general swapaxes operator\n// but I am a little afraid of going down that general path. Only implementing\n// the two actually needed ones here.\n\ntemplate <typename T, class Context>\nclass NHWC2NCHWOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(NHWC2NCHWOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n protected:\n};\n\ntemplate <typename T, class Context>\nclass NCHW2NHWCOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(NCHW2NHWCOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n protected:\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_ORDER_SWITCH_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/pack_rnn_sequence_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/pack_rnn_sequence_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(PackRNNSequence, PackRNNSequenceOpBase<CPUContext, true>);\nREGISTER_CPU_OPERATOR(\n    UnpackRNNSequence,\n    PackRNNSequenceOpBase<CPUContext, false>);\n\nOPERATOR_SCHEMA(PackRNNSequence)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nPack values based on the length blob. Each number from length blob represents\nthe corresponding values that need to be packed. The dimension for each pack\nis the same as the maximum number from the length blob (padding with zero is\nimplemented for smaller length value). The overall output dimension is:\nT * N * D, where T is the max number of lengths, N is the size of lengths,\nand D is the dimension of each feature value. The following example shows\nthe input and output of this operator:\n\n\nGiven:\n  values = [v1, v2, v3, v4, v5, v6, v7, v8]\n  lengths = [2, 3, 1, 2];\n\n\nOutput:\n  output = [\n    [v1, v3, v6, v7],\n    [v2, v4, 0,  v8],\n    [0,  v5, 0,  0 ],\n  ]\n\n\nOne application for this operator is the transfer data into the format that is\nused for RNN models. Note that the gradient operator of PackRNNSequence is\nUnpackRNNSequence.\n)DOC\")\n    .Input(0, \"values\", \"Data tensor, contains a sequence of features\")\n    .Input(1, \"lengths\", \"lengths with each number representing the pack size.\")\n    .Output(0, \"output\", \"Output tensor after packing\");\n\nOPERATOR_SCHEMA(UnpackRNNSequence)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nThis is the reverse operator for PackRNNSequence. It maps the packed values\nback to sequence values based on the length blob. Each number from length blob\nrepresents the corresponding values that has been grouped. The dimension\nfor each pack is the same as the maximum number from the length blob (padding\nwith zero was implemented for smaller length value). The overall output\ndimension is: M * D, where M is the sum of lengths, and D is the dimension of\neach feature value. The following example shows the input and output of\nthis operator:\n\n\nGiven:\n  values = [\n    [v1, v3, v6, v7],\n    [v2, v4, 0,  v8],\n    [0,  v5, 0,  0 ],\n  ]\n  lengths = [2, 3, 1, 2]\n\n\nOutput:\n  output = [v1, v2, v3, v4, v5, v6, v7, v8];\n\n\nOne application for this operator is the transfer data from the format of RNN\nback to sequence values. Note that the gradient operator of\nUnpackRNNSequence is PackRNNSequence.\n)DOC\")\n    .Input(0, \"values\", \"Data tensor, contains the packed features\")\n    .Input(1, \"lengths\", \"lengths with each number representing the pack size.\")\n    .Output(0, \"output\", \"Output tensor before packing\");\n\nclass GetPackRNNSequenceGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE_EQ(def_.input_size(), 2);\n    return SingleGradientDef(\n        \"UnpackRNNSequence\",\n        \"\",\n        vector<string>{GO(0), I(1)},\n        vector<string>{GI(0)});\n  }\n};\n\nclass GetUnpackRNNSequenceGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE_EQ(def_.input_size(), 2);\n    return SingleGradientDef(\n        \"PackRNNSequence\",\n        \"\",\n        vector<string>{GO(0), I(1)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(PackRNNSequence, GetPackRNNSequenceGradient);\nREGISTER_GRADIENT(UnpackRNNSequence, GetUnpackRNNSequenceGradient);\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/pack_rnn_sequence_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_PACK_RNN_SEQUENCE_OP_H_\n#define CAFFE2_OPERATORS_PACK_RNN_SEQUENCE_OP_H_\n\n#include <algorithm>\n#include <vector>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context, bool Forward>\nclass PackRNNSequenceOpBase : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  PackRNNSequenceOpBase(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t, float, double>>::call(\n        this, Input(0));\n  }\n\n  template <typename ValT>\n  bool DoRunWithType() {\n    // The value is copied from the sequence to the pack\n    // if Forward is true, and vice versa\n    int dim_offset = Forward ? 1 : 2;\n    auto& values = Input(0);\n    CAFFE_ENFORCE_GT(values.ndim(), dim_offset);\n\n    // block_size is the size for each individual feature\n    TIndex block_size = values.size_from_dim(dim_offset);\n    auto values_vec = values.template data<ValT>();\n\n    auto& lengths = Input(LENGTHS);\n    CAFFE_ENFORCE_EQ(lengths.ndim(), 1);\n    const auto cols = lengths.size();\n    const int32_t* lengths_vec = lengths.template data<int32_t>();\n    // the total number of rows is defined as the max number from lengths\n    // if when the lengths is empty, we set rows = 0 to support zero lengths\n    const auto rows =\n        cols ? *std::max_element(lengths_vec, lengths_vec + cols) : 0;\n    CAFFE_ENFORCE_GE(rows, 0);\n    int length_sum = 0;\n    if (cols > 0) {\n      math::Sum<int, Context>(cols, lengths_vec, &length_sum, &context_);\n    }\n\n    vector<TIndex> shape;\n    // the output shape is rows * cols for the pack,\n    // or length_sum for the sequence\n    if (Forward) {\n      shape.push_back(rows);\n      shape.push_back(cols);\n    } else {\n      shape.push_back(length_sum);\n    }\n    // insert the dim for the feature\n    shape.insert(\n        shape.end(), values.dims().begin() + dim_offset, values.dims().end());\n\n    auto* output = Output(OUTPUTVALUE);\n    output->Resize(shape);\n\n    auto output_data = output->template mutable_data<ValT>();\n    // initialize output_data with zero, as it is the default value for padding\n    // when certain length is smaller than rows\n    math::Set<ValT, Context>(output->size(), 0, output_data, &context_);\n\n    int32_t offset = 0;\n    for (int c = 0; c < cols; c++) {\n      for (int r = 0; r < lengths_vec[c]; r++) {\n        auto input_offset = Forward ? (offset + r) : (r * cols + c);\n        auto output_offset = Forward ? (r * cols + c) : (offset + r);\n        context_.template CopyItems<Context, Context>(\n            values.meta(),\n            block_size,\n            values_vec + input_offset * block_size,\n            output_data + output_offset * block_size);\n      }\n      offset += lengths_vec[c];\n    }\n    return true;\n  }\n\n private:\n  INPUT_TAGS(INPUTVALUE, LENGTHS);\n  OUTPUT_TAGS(OUTPUTVALUE);\n};\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_PACK_RNN_SEQUENCE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/pack_segments.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/pack_segments.h\"\n\nnamespace caffe2 {\n\ntemplate <>\ntemplate <typename T>\nbool PackSegmentsOp<CPUContext>::DoRunWithType() {\n  return DispatchHelper<\n      TensorTypes2<char, int32_t, int64_t, float, std::string>,\n      T>::call(this, Input(DATA));\n}\n\ntemplate <>\ntemplate <typename T, typename Data_T>\nbool PackSegmentsOp<CPUContext>::DoRunWithType2() {\n  const auto& data = Input(DATA);\n  const auto& lengths = Input(LENGTHS);\n  auto* output = Output(0);\n  Tensor<CPUContext>* presence_mask = nullptr;\n  if (return_presence_mask_) {\n    presence_mask = Output(1);\n  }\n\n  CAFFE_ENFORCE(data.ndim() >= 1, \"DATA should be at least 1-D\");\n  CAFFE_ENFORCE(lengths.ndim() == 1, \"LENGTH should be 1-D\");\n\n  // Find the length of the longest sequence.\n  const T* l = lengths.template data<T>();\n  T max_length = 0;\n  TIndex total_length = 0;\n  for (T i = 0; i < lengths.dim(0); ++i) {\n    max_length = std::max(max_length, l[i]);\n    total_length += l[i];\n  }\n\n  // Total lengths must be the same as data.dims(0)\n  CAFFE_ENFORCE_EQ(\n      data.dim(0),\n      total_length,\n      \" PackSegments requires that the sum of the lengths \",\n      total_length,\n      \" is equal to the first data dimension \",\n      data.dim(0));\n\n  auto shape = data.dims(); // Shape of output is batch_size x max_len x ...\n  shape[0] = max_length;\n  shape.insert(shape.begin(), lengths.size());\n  output->Resize(shape);\n\n  // create output tensor\n  auto* out = static_cast<char*>(output->raw_mutable_data(data.meta()));\n\n  bool* presence_mask_data = nullptr;\n  if (return_presence_mask_) {\n    // Shape of presence is batch_size x max_len\n    std::vector<caffe2::TIndex> presence_shape{lengths.size(), max_length};\n    presence_mask->Resize(presence_shape);\n    presence_mask_data = presence_mask->template mutable_data<bool>();\n  }\n\n  if (!data.dim(0)) {\n    // Return empty output (with the proper shape)\n    return true;\n  }\n\n  // Do padding\n  if (output->template IsType<float>()) {\n    math::Set<float, CPUContext>(\n        output->size(),\n        padding_,\n        output->template mutable_data<float>(),\n        &context_);\n  }\n  if (return_presence_mask_) {\n    memset(presence_mask_data, (int)false, presence_mask->size());\n  }\n\n  auto block_size = data.size_from_dim(1);\n  auto block_bytesize = data.itemsize() * block_size;\n  const auto* d = static_cast<const char*>(data.raw_data());\n  TIndex start = 0;\n  for (TIndex i = 0; i < lengths.dim(0); ++i) {\n    context_.template CopyItems<CPUContext, CPUContext>(\n        data.meta(),\n        l[i] * block_size,\n        d + block_bytesize * start,\n        out + block_bytesize * max_length * i);\n    if (return_presence_mask_) {\n      memset(presence_mask_data + max_length * i, (int)true, l[i]);\n    }\n    start += l[i];\n  }\n\n  return true;\n}\n\ntemplate <>\ntemplate <typename T>\nbool UnpackSegmentsOp<CPUContext>::DoRunWithType() {\n  return DispatchHelper<\n      TensorTypes2<char, int32_t, int64_t, float, std::string>,\n      T>::call(this, Input(DATA));\n}\n\ntemplate <>\ntemplate <typename T, typename Data_T>\nbool UnpackSegmentsOp<CPUContext>::DoRunWithType2() {\n  const auto& data = Input(DATA);\n  const auto& lengths = Input(LENGTHS);\n  auto* output = Output(0);\n\n  CAFFE_ENFORCE(data.ndim() >= 2, \"DATA should be at least 2-D\");\n  CAFFE_ENFORCE(lengths.ndim() == 1, \"LENGTH should be 1-D\");\n\n  const T* l = lengths.template data<T>();\n\n  TIndex total_l = std::accumulate(l, l + lengths.dim(0), (TIndex)0);\n\n  auto shape = data.dims();\n  CAFFE_ENFORCE(\n      shape[0] == lengths.dim(0), \"LENGTH should match DATA in dimension 0\");\n  shape.erase(shape.begin());\n  shape[0] = total_l;\n  output->Resize(shape);\n  // create output tensor\n  auto* out = static_cast<char*>(output->raw_mutable_data(data.meta()));\n  if (!(data.dim(0) * data.dim(1))) {\n    return true;\n  }\n  auto block_size = data.size_from_dim(2);\n  auto block_bytesize = data.itemsize() * block_size;\n  const auto* d = static_cast<const char*>(data.raw_data());\n  TIndex start = 0;\n  for (TIndex i = 0; i < lengths.dim(0); ++i) {\n    context_.template CopyItems<CPUContext, CPUContext>(\n        data.meta(),\n        l[i] * block_size,\n        d + block_bytesize * data.dim(1) * i,\n        out + block_bytesize * start);\n    start += l[i];\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(PackSegments, PackSegmentsOp<CPUContext>);\nREGISTER_CPU_OPERATOR(UnpackSegments, UnpackSegmentsOp<CPUContext>);\n\nOPERATOR_SCHEMA(PackSegments)\n    .NumInputs(2)\n    .NumOutputs(1, 2)\n    .SetDoc(\n        \"Map N dim tensor to N+1 dim based on length blob. Sequences that \\\n    are shorter than the longest sequence are padded with zeros.\")\n    .Input(\n        0,\n        \"lengths\",\n        \"1-d int/long tensor contains the length in each of the output.\")\n    .Input(1, \"tensor\", \"N dim Tensor.\")\n    .Output(\n        0,\n        \"packed_tensor\",\n        \"N + 1 dim Tensor\"\n        \"where dim(1) is the max length\"\n        \", dim(0) is the batch size.\")\n    .Output(\n        1,\n        \"presence_mask\",\n        \"2 dim boolean tensor\"\n        \", false where packed_tensor is padded, true otherwise.\")\n    .Arg(\n        \"pad_minf\",\n        \"Padding number in the packed segments. Use true to pad \\\n    -infinity, otherwise pad zeros\")\n    .Arg(\n        \"return_presence_mask\",\n        \"bool whether to return presence mask, false by default\");\nOPERATOR_SCHEMA(UnpackSegments)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(\"Map N+1 dim tensor to N dim based on length blob\")\n    .Input(\n        0,\n        \"lengths\",\n        \"1-d int/long tensor contains the length in each of the input.\")\n    .Input(1, \"tensor\", \"N+1 dim Tensor.\")\n    .Output(0, \"packed_tensor\", \"N dim Tensor\");\n\nclass GetPackSegmentsGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"UnpackSegments\",\n        \"\",\n        vector<string>{I(0), GO(0)},\n        vector<string>{GI(1)});\n  }\n};\nREGISTER_GRADIENT(PackSegments, GetPackSegmentsGradient);\n\nclass GetUnpackSegmentsGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"PackSegments\", \"\", vector<string>{I(0), GO(0)}, vector<string>{GI(1)});\n  }\n};\nREGISTER_GRADIENT(UnpackSegments, GetUnpackSegmentsGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/pack_segments.cu",
    "content": "#include <cub/cub.cuh>\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/pack_segments.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <typename T, typename Data_T>\n__global__ void PackSegmentsKernel(\n    const Data_T* data_ptr,\n    const T* lengths_ptr,\n    const T* lengths_cum_sum,\n    const T max_length,\n    const int64_t num_seq,\n    const int64_t cell_size,\n    Data_T padding,\n    Data_T* out_ptr) {\n  CUDA_1D_KERNEL_LOOP(i, num_seq * max_length * cell_size) {\n    int seq = (i / cell_size) / max_length;\n    int cell = (i / cell_size) % max_length;\n    int offset = i % cell_size;\n    if (cell >= lengths_ptr[seq]) {\n      out_ptr[i] = padding;\n    } else {\n      int32_t idx = (lengths_cum_sum[seq] + cell) * cell_size + offset;\n      out_ptr[i] = data_ptr[idx];\n    }\n  }\n}\n\ntemplate <typename T, typename Data_T>\n__global__ void UnpackSegmentsKernel(\n    const Data_T* data_ptr,\n    const T* lengths_ptr,\n    const T* lengths_cum_sum,\n    const T max_length,\n    const int64_t num_seq,\n    const int64_t cell_size,\n    Data_T* out_ptr) {\n  CUDA_1D_KERNEL_LOOP(i, num_seq * max_length * cell_size) {\n    int seq = (i / cell_size) / max_length;\n    int cell = (i / cell_size) % max_length;\n    int offset = i % cell_size;\n    if (cell < lengths_ptr[seq]) {\n      int idx = (lengths_cum_sum[seq] + cell) * cell_size + offset;\n      out_ptr[idx] = data_ptr[i];\n    }\n  }\n}\n\ntemplate <typename T>\nint64_t int_array_sum(\n    const T* dev_array,\n    int64_t num_items,\n    Tensor<CUDAContext>& dev_buffer,\n    Tensor<CUDAContext>& dev_sum,\n    Tensor<CPUContext>& host_sum,\n    CUDAContext& context) {\n  // Retrieve buffer size\n  size_t temp_storage_bytes = 0;\n  cub::DeviceReduce::Sum(\n      nullptr,\n      temp_storage_bytes,\n      dev_array,\n      dev_sum.mutable_data<int64_t>(),\n      num_items,\n      context.cuda_stream());\n\n  // Allocate temporary storage\n  auto buffer_size = (temp_storage_bytes + sizeof(T)) / sizeof(T);\n  dev_buffer.Resize(buffer_size);\n  void* dev_temp_storage = static_cast<void*>(dev_buffer.mutable_data<T>());\n\n  // Find sumimum\n  cub::DeviceReduce::Sum(\n      dev_temp_storage,\n      temp_storage_bytes,\n      dev_array,\n      dev_sum.mutable_data<int64_t>(),\n      num_items,\n      context.cuda_stream());\n\n  // Copy to host\n  host_sum.CopyFrom<CUDAContext>(dev_sum);\n  context.FinishDeviceComputation();\n  return *host_sum.data<int64_t>();\n}\n\ntemplate <typename T>\nT array_max(\n    const T* dev_array,\n    int64_t num_items,\n    Tensor<CUDAContext>& dev_max_buffer,\n    Tensor<CUDAContext>& dev_max,\n    Tensor<CPUContext>& host_max,\n    CUDAContext& context) {\n  // Retrieve buffer size\n  size_t temp_storage_bytes = 0;\n  cub::DeviceReduce::Max(\n      nullptr,\n      temp_storage_bytes,\n      dev_array,\n      dev_max.mutable_data<T>(),\n      num_items,\n      context.cuda_stream());\n\n  // Allocate temporary storage\n  auto buffer_size = (temp_storage_bytes + sizeof(T)) / sizeof(T);\n  dev_max_buffer.Resize(buffer_size);\n  void* dev_temp_storage = static_cast<void*>(dev_max_buffer.mutable_data<T>());\n\n  // Find maximum\n  cub::DeviceReduce::Max(\n      dev_temp_storage,\n      temp_storage_bytes,\n      dev_array,\n      dev_max.mutable_data<T>(),\n      num_items,\n      context.cuda_stream());\n\n  // Copy to host\n  host_max.CopyFrom<CUDAContext>(dev_max);\n  context.FinishDeviceComputation();\n  return *host_max.data<T>();\n}\n\ntemplate <typename T>\nvoid array_prefix_sum_exclusive(\n    const T* dev_array,\n    const int32_t num_items,\n    Tensor<CUDAContext>& prefix_buffer,\n    Tensor<CUDAContext>& prefix_sum,\n    CUDAContext& context) {\n  // Retrieve buffer size\n  size_t temp_storage_bytes = 0;\n  prefix_sum.Resize(num_items);\n  cub::DeviceScan::ExclusiveSum(\n      nullptr,\n      temp_storage_bytes,\n      dev_array,\n      prefix_sum.mutable_data<T>(),\n      num_items,\n      context.cuda_stream());\n\n  // Allocate temporary storage\n  auto buffer_size = (temp_storage_bytes + sizeof(T)) / sizeof(T);\n  prefix_buffer.Resize(buffer_size);\n  void* dev_temp_storage = static_cast<void*>(prefix_buffer.mutable_data<T>());\n\n  // Exclusive sum\n  cub::DeviceScan::ExclusiveSum(\n      dev_temp_storage,\n      temp_storage_bytes,\n      dev_array,\n      prefix_sum.mutable_data<T>(),\n      num_items,\n      context.cuda_stream());\n}\n\n} // namespace\n\ntemplate <>\ntemplate <typename T>\nbool PackSegmentsOp<CUDAContext>::DoRunWithType() {\n  return DispatchHelper<TensorTypes2<char, int32_t, int64_t, float>, T>::call(\n      this, Input(DATA));\n}\n\ntemplate <>\ntemplate <typename T, typename Data_T>\nbool PackSegmentsOp<CUDAContext>::DoRunWithType2() {\n  const auto& data = Input(DATA);\n  const auto& lengths = Input(LENGTHS);\n  int64_t num_seq = lengths.dim(0);\n  const Data_T* data_ptr = data.data<Data_T>();\n  const T* lengths_ptr = lengths.data<T>();\n  auto* out = Output(0);\n\n  if (return_presence_mask_) {\n    CAFFE_THROW(\"CUDA version of PackSegments does not support presence mask.\");\n  }\n  CAFFE_ENFORCE(data.ndim() >= 1, \"DATA should be at least 1-D\");\n  CAFFE_ENFORCE(lengths.ndim() == 1, \"LENGTH should be 1-D\");\n\n  // Find the length of the longest sequence.\n  dev_max_length_.Resize(1);\n  host_max_length_.Resize(1);\n  const T max_length = array_max<T>(\n      lengths_ptr,\n      num_seq,\n      dev_buffer_,\n      dev_max_length_,\n      host_max_length_,\n      context_);\n\n  // Compute prefix sum over the lengths\n  array_prefix_sum_exclusive<T>(\n      lengths_ptr, num_seq, dev_buffer_, dev_lengths_prefix_sum_, context_);\n\n  // create output tensor\n  auto shape = data.dims(); // Shape of out is batch_size x max_len x ...\n  shape[0] = max_length;\n  shape.insert(shape.begin(), lengths.size());\n  out->Resize(shape);\n  Data_T* out_ptr = static_cast<Data_T*>(out->raw_mutable_data(data.meta()));\n\n  // Return empty out (with the proper shape) if first dim is 0.\n  if (!data.dim(0)) {\n    return true;\n  }\n\n  // Do padding\n  Data_T padding = out->IsType<float>() ? padding_ : 0;\n  int64_t cell_size = data.size() / data.dim(0);\n  PackSegmentsKernel<<<\n      CAFFE_GET_BLOCKS(num_seq * max_length * cell_size),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      data_ptr,\n      lengths_ptr,\n      dev_lengths_prefix_sum_.data<T>(),\n      max_length,\n      num_seq,\n      cell_size,\n      padding,\n      out_ptr);\n  return true;\n}\n\ntemplate <>\ntemplate <typename T>\nbool UnpackSegmentsOp<CUDAContext>::DoRunWithType() {\n  return DispatchHelper<TensorTypes2<char, int32_t, int64_t, float>, T>::call(\n      this, Input(DATA));\n}\ntemplate <>\ntemplate <typename T, typename Data_T>\nbool UnpackSegmentsOp<CUDAContext>::DoRunWithType2() {\n  const auto& data = Input(DATA);\n  const auto& lengths = Input(LENGTHS);\n  int64_t num_seq = lengths.dim(0);\n  const Data_T* data_ptr = data.data<Data_T>();\n  const T* lengths_ptr = lengths.data<T>();\n  auto* out = Output(0);\n\n  CAFFE_ENFORCE(data.ndim() >= 1, \"DATA should be at least 1-D\");\n  CAFFE_ENFORCE(lengths.ndim() == 1, \"LENGTH should be 1-D\");\n\n  // Compute prefix sum over the lengths\n  array_prefix_sum_exclusive<T>(\n      lengths_ptr, num_seq, dev_buffer_, dev_lengths_prefix_sum_, context_);\n\n  // compute max of the lengths\n  dev_max_length_.Resize(1);\n  host_max_length_.Resize(1);\n  const T max_length = array_max<T>(\n      lengths_ptr,\n      num_seq,\n      dev_buffer_,\n      dev_max_length_,\n      host_max_length_,\n      context_);\n\n  // compute num of cells: sum of the lengths\n  dev_num_cell_.Resize(1);\n  host_num_cell_.Resize(1);\n  const int64_t num_cell = int_array_sum<T>(\n      lengths_ptr,\n      num_seq,\n      dev_buffer_,\n      dev_num_cell_,\n      host_num_cell_,\n      context_);\n\n  // create output tensor\n  auto shape = data.dims();\n  CAFFE_ENFORCE(\n      shape[0] == lengths.dim(0), \"LENGTH should match DATA in dimension 0\");\n  shape.erase(shape.begin());\n  shape[0] = num_cell;\n  out->Resize(shape);\n  Data_T* out_ptr = static_cast<Data_T*>(out->raw_mutable_data(data.meta()));\n\n  // Return empty out (with the proper shape) if any of the dimensions is 0.\n  if (!(data.dim(0) * data.dim(1))) {\n    return true;\n  }\n\n  // Unpack\n  int64_t cell_size = data.size() / (data.dim(0) * data.dim(1));\n  UnpackSegmentsKernel<<<\n      CAFFE_GET_BLOCKS(num_seq * max_length * cell_size),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      data_ptr,\n      lengths_ptr,\n      dev_lengths_prefix_sum_.data<T>(),\n      max_length,\n      num_seq,\n      cell_size,\n      out_ptr);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(UnpackSegments, UnpackSegmentsOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(PackSegments, PackSegmentsOp<CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/pack_segments.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_PACK_SEGMENTS_H_\n#define CAFFE2_OPERATORS_PACK_SEGMENTS_H_\n\n#include <atomic>\n#include <limits>\n#include <mutex>\n#include <unordered_map>\n#include <vector>\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass PackSegmentsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  // USE_SIMPLE_CTOR_DTOR(PackSegmentsOp)\n  USE_DISPATCH_HELPER;\n\n  PackSegmentsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        pad_minf_(OperatorBase::GetSingleArgument<bool>(\"pad_minf\", false)),\n        return_presence_mask_(OperatorBase::GetSingleArgument<bool>(\n            \"return_presence_mask\",\n            false)) {\n    if (pad_minf_) {\n      padding_ = -1.0 * std::numeric_limits<float>::infinity();\n    } else {\n      padding_ = 0;\n    }\n  }\n\n  bool RunOnDevice() {\n    return DispatchHelper<TensorTypes<int, long>>::call(this, Input(LENGTHS));\n  }\n\n  template <typename T>\n  bool DoRunWithType();\n\n  template <typename T, typename Data_T>\n  bool DoRunWithType2();\n\n  INPUT_TAGS(LENGTHS, DATA);\n\n private:\n  bool pad_minf_;\n  float padding_;\n  bool return_presence_mask_;\n\n  // Scratch space required by the CUDA version\n  Tensor<Context> dev_buffer_;\n  Tensor<Context> dev_lengths_prefix_sum_;\n  Tensor<Context> dev_max_length_;\n  Tensor<CPUContext> host_max_length_;\n};\n\ntemplate <class Context>\nclass UnpackSegmentsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(UnpackSegmentsOp)\n  USE_DISPATCH_HELPER;\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int, long>>::call(this, Input(LENGTHS));\n  }\n\n  template <typename T>\n  bool DoRunWithType();\n\n  template <typename T, typename Data_T>\n  bool DoRunWithType2();\n\n  INPUT_TAGS(LENGTHS, DATA);\n\n private:\n  Tensor<Context> dev_buffer_;\n  Tensor<Context> dev_lengths_prefix_sum_;\n  Tensor<Context> dev_max_length_;\n  Tensor<Context> dev_num_cell_;\n  Tensor<CPUContext> host_max_length_;\n  Tensor<CPUContext> host_num_cell_;\n};\n\n} // namespace caffe2\n#endif // CAFFE2_OPERATORS_PACK_SEGMENTS_H_\n"
  },
  {
    "path": "caffe2/operators/pad_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/pad_op.h\"\n\n#include <algorithm>\n\nnamespace caffe2 {\n\nPadMode StringToPadMode(const string& mode) {\n  if (mode == \"constant\") {\n    return PadMode::CONSTANT;\n  } else if (mode == \"reflect\") {\n    return PadMode::REFLECT;\n  } else if (mode == \"edge\") {\n    return PadMode::EDGE;\n  } else {\n    CAFFE_THROW(\"Unknown padding mode: \" + mode);\n  }\n}\n\nusing std::min;\nusing std::max;\n\ntemplate <>\nbool PadImageOp<float, CPUContext>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  int channels = X.dim32(1);\n  int height = X.dim32(2);\n  int width = X.dim32(3);\n  ConvPoolOpBase::SetOutputSize(X, Y, channels);\n\n  const float* Xdata = X.data<float>();\n  float* Ydata = Y->mutable_data<float>();\n  // The main loop\n  int padded_height = Y->dim32(2);\n  int padded_width = Y->dim32(3);\n\n  switch (mode_) {\n    case PadMode::CONSTANT:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int c = 0; c < channels; ++c) {\n          for (int ph = 0; ph < padded_height; ++ph) {\n            for (int pw = 0; pw < padded_width; ++pw) {\n              int h = ph - pad_t();\n              int w = pw - pad_l();\n              Ydata[ph * padded_width + pw] =\n                  (h < 0 || w < 0 || h >= height || w >= width)\n                  ? value_\n                  : Xdata[h * width + w];\n            }\n          }\n          // Do offset.\n          Xdata += height * width;\n          Ydata += padded_height * padded_width;\n        }\n      }\n      break;\n    case PadMode::REFLECT:\n      if (pad_r() >= 0 && pad_t() >= 0 && pad_l() >= 0 && pad_b() >= 0) {\n        for (int n = 0; n < X.dim32(0); ++n) {\n          for (int c = 0; c < channels; ++c) {\n            // Handle the valid region:\n            // i.e. Y[n][c][pad_t:pad_t+h][pad_l:pad_l+w]\n            auto* Ystart = Ydata + pad_t() * padded_width + pad_l();\n            math::CopyMatrix<CPUContext>(\n                sizeof(float),\n                height,\n                width,\n                Xdata,\n                width,\n                Ystart,\n                padded_width,\n                &context_);\n\n// Fixup areas where we need to reflect\n#define X(ph, pw)                 \\\n  int h = ph - pad_t();           \\\n  int w = pw - pad_l();           \\\n  h = max(h, -h);                 \\\n  h = min(h, 2 * height - h - 2); \\\n  w = max(w, -w);                 \\\n  w = min(w, 2 * width - w - 2);  \\\n  Ydata[ph * padded_width + pw] = Xdata[h * width + w]\n\n            // Top part\n            for (int ph = 0; ph < pad_t(); ++ph) {\n              for (int pw = 0; pw < padded_width; ++pw) {\n                X(ph, pw);\n              }\n            }\n\n            // Bottom part\n            for (int ph = padded_height - pad_b(); ph < padded_height; ++ph) {\n              for (int pw = 0; pw < padded_width; ++pw) {\n                X(ph, pw);\n              }\n            }\n\n            // Interior\n            for (int ph = pad_t(); ph < padded_height - pad_b(); ++ph) {\n              // Left\n              for (int pw = 0; pw < pad_l(); ++pw) {\n                X(ph, pw);\n              }\n              // Right\n              for (int pw = padded_width - pad_r(); pw < padded_width; ++pw) {\n                X(ph, pw);\n              }\n            }\n#undef X\n\n            // Do offset.\n            Xdata += height * width;\n            Ydata += padded_height * padded_width;\n          }\n        }\n      } else {\n        for (int n = 0; n < X.dim32(0); ++n) {\n          for (int c = 0; c < channels; ++c) {\n            for (int ph = 0; ph < padded_height; ++ph) {\n              for (int pw = 0; pw < padded_width; ++pw) {\n                int h = ph - pad_t();\n                int w = pw - pad_l();\n                // max(h, -h) does reflection over 0\n                h = max(h, -h);\n                // min(h, 2 * height - h - 2) does reflection over height.\n                h = min(h, 2 * height - h - 2);\n                w = max(w, -w);\n                w = min(w, 2 * width - w - 2);\n                Ydata[ph * padded_width + pw] = Xdata[h * width + w];\n              }\n            }\n            // Do offset.\n            Xdata += height * width;\n            Ydata += padded_height * padded_width;\n          }\n        }\n      }\n      break;\n    case PadMode::EDGE:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int c = 0; c < channels; ++c) {\n          for (int ph = 0; ph < padded_height; ++ph) {\n            for (int pw = 0; pw < padded_width; ++pw) {\n              // Bounds to the right range.\n              int h = min(height - 1, max(ph - pad_t(), 0));\n              int w = min(width - 1, max(pw - pad_l(), 0));\n              Ydata[ph * padded_width + pw] = Xdata[h * width + w];\n            }\n          }\n          // Do offset.\n          Xdata += height * width;\n          Ydata += padded_height * padded_width;\n        }\n      }\n      break;\n  }\n  return true;\n}\n\ntemplate <>\nbool PadImageOp<float, CPUContext>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  int height = X.dim32(1);\n  int width = X.dim32(2);\n  int channels = X.dim32(3);\n  ConvPoolOpBase::SetOutputSize(X, Y, channels);\n  const float* Xdata = X.data<float>();\n  float* Ydata = Y->mutable_data<float>();\n\n  // The main loop\n  int padded_height = Y->dim32(1);\n  int padded_width = Y->dim32(2);\n\n  switch (mode_) {\n    case PadMode::CONSTANT:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int ph = 0; ph < padded_height; ++ph) {\n          for (int pw = 0; pw < padded_width; ++pw) {\n            int h = ph - pad_t();\n            int w = pw - pad_l();\n            const int pad_index = (ph * padded_width + pw) * channels;\n            if (h < 0 || w < 0 || h >= height || w >= width) {\n              for (int c = 0; c < channels; ++c) {\n                Ydata[pad_index + c] = value_;\n              }\n            } else {\n              const int input_index = (h * width + w) * channels;\n              for (int c = 0; c < channels; ++c) {\n                Ydata[pad_index + c] = Xdata[input_index + c];\n              }\n            }\n          }\n        }\n        // Do offset.\n        Xdata += X.size() / X.dim32(0);\n        Ydata += Y->size() / Y->dim32(0);\n      }\n      break;\n    case PadMode::REFLECT:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int ph = 0; ph < padded_height; ++ph) {\n          for (int pw = 0; pw < padded_width; ++pw) {\n            const int pad_index = (ph * padded_width + pw) * channels;\n            int h = ph - pad_t();\n            int w = pw - pad_l();\n            // max(h, -h) does reflection over 0\n            h = max(h, -h);\n            // min(h, 2 * height - h - 2) does reflection over height.\n            h = min(h, 2 * height - h - 2);\n            w = max(w, -w);\n            w = min(w, 2 * width - w - 2);\n            const int input_index = (h * width + w) * channels;\n            for (int c = 0; c < channels; ++c) {\n              Ydata[pad_index + c] = Xdata[input_index + c];\n            }\n          }\n        }\n        // Do offset.\n        Xdata += X.size() / X.dim32(0);\n        Ydata += Y->size() / Y->dim32(0);\n      }\n      break;\n    case PadMode::EDGE:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int ph = 0; ph < padded_height; ++ph) {\n          for (int pw = 0; pw < padded_width; ++pw) {\n            const int pad_index = (ph * padded_width + pw) * channels;\n            int h = min(height - 1, max(ph - pad_t(), 0));\n            int w = min(width - 1, max(pw - pad_l(), 0));\n            const int input_index = (h * width + w) * channels;\n            for (int c = 0; c < channels; ++c) {\n              Ydata[pad_index + c] = Xdata[input_index + c];\n            }\n          }\n        }\n        // Do offset.\n        Xdata += X.size() / X.dim32(0);\n        Ydata += Y->size() / Y->dim32(0);\n      }\n      break;\n  }\n  return true;\n}\n\ntemplate <>\nbool PadImageGradientOp<float, CPUContext>::RunOnDeviceWithOrderNCHW() {\n  auto& dY = Input(0);\n  auto* dX = Output(0);\n  dX->Resize(\n      dY.dim32(0),\n      dY.dim32(1),\n      dY.dim32(2) - pad_t() - pad_b(),\n      dY.dim32(3) - pad_l() - pad_r());\n  int padded_height = dY.dim32(2);\n  int padded_width = dY.dim32(3);\n  int channels = dX->dim32(1);\n  int height = dX->dim32(2);\n  int width = dX->dim32(3);\n\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  math::Set<float, CPUContext>(dX->size(), 0, dXdata, &context_);\n  // The main loop\n  switch (mode_) {\n    case PadMode::CONSTANT:\n      for (int n = 0; n < dY.dim32(0); ++n) {\n        for (int c = 0; c < channels; ++c) {\n          for (int ph = 0; ph < padded_height; ++ph) {\n            for (int pw = 0; pw < padded_width; ++pw) {\n              int h = ph - pad_t();\n              int w = pw - pad_l();\n              if (!(h < 0 || w < 0 || h >= height || w >= width)) {\n                dXdata[h * width + w] += dYdata[ph * padded_width + pw];\n              }\n            }\n          }\n          // Do offset.\n          dXdata += height * width;\n          dYdata += padded_height * padded_width;\n        }\n      }\n      break;\n    case PadMode::REFLECT:\n      for (int n = 0; n < dY.dim32(0); ++n) {\n        for (int c = 0; c < channels; ++c) {\n          for (int ph = 0; ph < padded_height; ++ph) {\n            for (int pw = 0; pw < padded_width; ++pw) {\n              int h = ph - pad_t();\n              int w = pw - pad_l();\n              // max(h, -h) does reflection over 0\n              h = max(h, -h);\n              // min(h, 2 * height - h - 2) does reflection over height.\n              h = min(h, 2 * height - h - 2);\n              w = max(w, -w);\n              w = min(w, 2 * width - w - 2);\n              dXdata[h * width + w] += dYdata[ph * padded_width + pw];\n            }\n          }\n          // Do offset.\n          dXdata += height * width;\n          dYdata += padded_height * padded_width;\n        }\n      }\n      break;\n    case PadMode::EDGE:\n      for (int n = 0; n < dY.dim32(0); ++n) {\n        for (int c = 0; c < channels; ++c) {\n          for (int ph = 0; ph < padded_height; ++ph) {\n            for (int pw = 0; pw < padded_width; ++pw) {\n              int h = min(height - 1, max(ph - pad_t(), 0));\n              int w = min(width - 1, max(pw - pad_l(), 0));\n              dXdata[h * width + w] += dYdata[ph * padded_width + pw];\n            }\n          }\n          // Do offset.\n          dXdata += height * width;\n          dYdata += padded_height * padded_width;\n        }\n      }\n      break;\n  }\n  return true;\n}\n\ntemplate <>\nbool PadImageGradientOp<float, CPUContext>::RunOnDeviceWithOrderNHWC() {\n  auto& dY = Input(0);\n  auto* dX = Output(0);\n  dX->Resize(\n      dY.dim32(0),\n      dY.dim32(1) - pad_t() - pad_b(),\n      dY.dim32(2) - pad_l() - pad_r(),\n      dY.dim32(3));\n  int padded_height = dY.dim32(1);\n  int padded_width = dY.dim32(2);\n  int channels = dY.dim32(3);\n  int height = dX->dim32(1);\n  int width = dX->dim32(2);\n\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  math::Set<float, CPUContext>(dX->size(), 0, dXdata, &context_);\n\n  switch (mode_) {\n    case PadMode::CONSTANT:\n      for (int n = 0; n < dY.dim32(0); ++n) {\n        for (int ph = 0; ph < padded_height; ++ph) {\n          for (int pw = 0; pw < padded_width; ++pw) {\n            int h = ph - pad_t();\n            int w = pw - pad_l();\n            const int pad_index = (ph * padded_width + pw) * channels;\n            if (!(h < 0 || w < 0 || h >= height || w >= width)) {\n              const int input_index = (h * width + w) * channels;\n              for (int c = 0; c < channels; ++c) {\n                dXdata[input_index + c] += dYdata[pad_index + c];\n              }\n            }\n          }\n        }\n        // Do offset.\n        dXdata += dX->size() / dX->dim32(0);\n        dYdata += dY.size() / dY.dim32(0);\n      }\n      break;\n    case PadMode::REFLECT:\n      for (int n = 0; n < dY.dim32(0); ++n) {\n        for (int ph = 0; ph < padded_height; ++ph) {\n          for (int pw = 0; pw < padded_width; ++pw) {\n            const int pad_index = (ph * padded_width + pw) * channels;\n            int h = ph - pad_t();\n            int w = pw - pad_l();\n            // max(h, -h) does reflection over 0\n            h = max(h, -h);\n            // min(h, 2 * height - h - 2) does reflection over height.\n            h = min(h, 2 * height - h - 2);\n            w = max(w, -w);\n            w = min(w, 2 * width - w - 2);\n            const int input_index = (h * width + w) * channels;\n            for (int c = 0; c < channels; ++c) {\n              dXdata[input_index + c] += dYdata[pad_index + c];\n            }\n          }\n        }\n        // Do offset.\n        dXdata += dX->size() / dX->dim32(0);\n        dYdata += dY.size() / dY.dim32(0);\n      }\n      break;\n    case PadMode::EDGE:\n      for (int n = 0; n < dY.dim32(0); ++n) {\n        for (int ph = 0; ph < padded_height; ++ph) {\n          for (int pw = 0; pw < padded_width; ++pw) {\n            const int pad_index = (ph * padded_width + pw) * channels;\n            // Bounds to the right range.\n            int h = min(height - 1, max(ph - pad_t(), 0));\n            int w = min(width - 1, max(pw - pad_l(), 0));\n            const int input_index = (h * width + w) * channels;\n            for (int c = 0; c < channels; ++c) {\n              dXdata[input_index + c] += dYdata[pad_index + c];\n            }\n          }\n        }\n        // Do offset.\n        dXdata += dX->size() / dX->dim32(0);\n        dYdata += dY.size() / dY.dim32(0);\n      }\n      break;\n  }\n  return true;\n}\n\ntemplate <>\nstd::vector<TensorShape> PadImageOp<float, CPUContext>::PadTensorInference(\n    const OperatorDef& def,\n    const vector<TensorShape>& in) {\n  return ConvPoolOpBase::TensorInferenceForPool(def, in);\n}\n\nREGISTER_CPU_OPERATOR(PadImage, PadImageOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(PadImageGradient, PadImageGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(PadImage)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(PadImageOp<float, CPUContext>::PadTensorInference)\n    .SetDoc(R\"DOC(\nPadImage pads values around the boundary of an image according to the pad\nvalues and stride sizes defined by the ConvPoolOpBase operator.\n  )DOC\")\n    .Input(\n        0,\n        \"X\",\n        \"Input data tensor from the previous operator; dimensions \"\n        \"depend on whether the NCHW or NHWC operators are being used. For example, \"\n        \"in the former, the input has size (N x C x H x W), where N is the batch \"\n        \"size, C is the number of channels, and H and W are the height and the width \"\n        \"of the data. The corresponding permutation of dimensions is used in the \"\n        \"latter case. \")\n    .Output(\n        0,\n        \"Y\",\n        \"Output data tensor from padding the H and W dimensions on \"\n        \"the tensor. Dimensions will vary based on various pad and stride \"\n        \"sizes.\");\n\nOPERATOR_SCHEMA(PadImageGradient).NumInputs(1).NumOutputs(1);\n\nclass GetPadImageGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"PadImageGradient\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(PadImage, GetPadImageGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/pad_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_PAD_OP_H_\n#define CAFFE2_OPERATORS_PAD_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n// Padding mode similar to numpy.\nenum class PadMode {\n  CONSTANT = 0, // pad constant values, with string \"constant\"\n  REFLECT = 1, // pads with reflect values, with string \"reflect\"\n  EDGE = 2, // pads with the edge values, with string \"edge\"\n};\n\nPadMode StringToPadMode(const string&);\n\ntemplate <typename T, class Context>\nclass PadImageOp final : public ConvPoolOpBase<Context> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(Context);\n  PadImageOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<Context>(operator_def, ws),\n        mode_(StringToPadMode(\n            OperatorBase::GetSingleArgument<string>(\"mode\", \"constant\"))),\n        value_(static_cast<T>(\n            OperatorBase::GetSingleArgument<float>(\"value\", 0.0))) {\n    CAFFE_ENFORCE(\n        legacy_pad_ == LegacyPadding::NOTSET,\n        \"Padding layer only supports explicit pad values.\");\n    CAFFE_ENFORCE(\n        dilation_h() == 1 && dilation_w() == 1,\n        \"Pooling op does not support dilation right now.\");\n    CAFFE_ENFORCE(\n        stride_h() == 1 && stride_w() == 1,\n        \"Pooling op does not support stride right now.\");\n    // Pad op does not use kernel sizes, so we set it to 1 for computing the\n    // output size.\n    kernel_.assign(pads_.size() / 2, 1);\n  }\n  ~PadImageOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n  static std::vector<TensorShape> PadTensorInference(\n      const OperatorDef& def,\n      const vector<TensorShape>& in);\n\n private:\n  PadMode mode_;\n  T value_;\n\n  // Input: X\n  // Output: Y\n};\n\ntemplate <typename T, class Context>\nclass PadImageGradientOp final : public ConvPoolOpBase<Context> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(Context);\n  PadImageGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<Context>(operator_def, ws),\n        mode_(StringToPadMode(\n            OperatorBase::GetSingleArgument<string>(\"mode\", \"constant\"))) {\n    CAFFE_ENFORCE(\n        legacy_pad_ == LegacyPadding::NOTSET,\n        \"Padding layer only supports explicit pad values.\");\n    CAFFE_ENFORCE(\n        dilation_h() == 1 && dilation_w() == 1,\n        \"Pooling op does not support dilation right now.\");\n    // Pad op does not use kernel sizes, so we set it to 1 for computing the\n    // output size.\n    kernel_.assign(pads_.size() / 2, 1);\n  }\n  ~PadImageGradientOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n private:\n  PadMode mode_;\n  // Input: dY\n  // Output: dX\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_PAD_OP_H_\n"
  },
  {
    "path": "caffe2/operators/pad_op_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <algorithm>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/pad_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\ntemplate <typename T>\n__global__ void PadImageConstNCHW(\n    const int nthreads, const T* const bottom_data, const int num,\n    const int channels, const int height, const int width,\n    const int padded_height, const int padded_width,\n    const int pad_t, const int pad_l, T value, T* const top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int nc = index / padded_width;\n    const int pw = index % padded_width;\n    const int ph = nc % padded_height;\n    nc /= padded_height;\n    const int h = ph - pad_t;\n    const int w = pw - pad_l;\n    top_data[index] = (h < 0 || w < 0 || h >= height || w >= width)\n        ? value\n        : bottom_data[(nc * height + h) * width + w];\n  }\n}\n\ntemplate <typename T>\n__global__ void PadImageReflectNCHW(\n    const int nthreads, const T* const bottom_data, const int num,\n    const int channels, const int height, const int width,\n    const int padded_height, const int padded_width,\n    const int pad_t, const int pad_l, T* const top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int nc = index / padded_width;\n    const int pw = index % padded_width;\n    const int ph = nc % padded_height;\n    nc /= padded_height;\n    int h = ph - pad_t;\n    int w = pw - pad_l;\n    h = max(h, -h);\n    w = max(w, -w);\n    h = min(h, 2 * height - h - 2);\n    w = min(w, 2 * width - w - 2);\n    top_data[index] = bottom_data[(nc * height + h) * width + w];\n  }\n}\n\ntemplate <typename T>\n__global__ void PadImageEdgeNCHW(\n    const int nthreads, const T* const bottom_data, const int num,\n    const int channels, const int height, const int width,\n    const int padded_height, const int padded_width,\n    const int pad_t, const int pad_l, T* const top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int nc = index / padded_width;\n    const int pw = index % padded_width;\n    const int ph = nc % padded_height;\n    nc /= padded_height;\n    const int h = min(height - 1, max(ph - pad_t, 0));\n    const int w = min(width - 1, max(pw - pad_l, 0));\n    top_data[index] = bottom_data[(nc * height + h) * width + w];\n  }\n}\n\ntemplate <typename T>\n__global__ void PadImageConstNHWC(\n    const int nthreads, const T* const bottom_data, const int num,\n    const int height, const int width, const int channels,\n    const int padded_height, const int padded_width,\n    const int pad_t, const int pad_l, T value, T* const top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index / channels;\n    const int c = index % channels;\n    const int pw = n % padded_width;\n    n /= padded_width;\n    const int ph = n % padded_height;\n    n /= padded_height;\n    const int h = ph - pad_t;\n    const int w = pw - pad_l;\n    top_data[index] = (h < 0 || w < 0 || h >= height || w >= width)\n        ? value\n        : bottom_data[((n * height + h) * width + w) * channels + c];\n  }\n}\n\ntemplate <typename T>\n__global__ void PadImageReflectNHWC(\n    const int nthreads, const T* const bottom_data, const int num,\n    const int height, const int width, const int channels,\n    const int padded_height, const int padded_width,\n    const int pad_t, const int pad_l, T* const top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index / channels;\n    const int c = index % channels;\n    const int pw = n % padded_width;\n    n /= padded_width;\n    const int ph = n % padded_height;\n    n /= padded_height;\n    int h = ph - pad_t;\n    int w = pw - pad_l;\n    h = max(h, -h);\n    w = max(w, -w);\n    h = min(h, 2 * height - h - 2);\n    w = min(w, 2 * width - w - 2);\n    top_data[index] =\n        bottom_data[((n * height + h) * width + w) * channels + c];\n  }\n}\n\ntemplate <typename T>\n__global__ void PadImageEdgeNHWC(\n    const int nthreads, const T* const bottom_data, const int num,\n    const int height, const int width, const int channels,\n    const int padded_height, const int padded_width,\n    const int pad_t, const int pad_l, T* const top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index / channels;\n    const int c = index % channels;\n    const int pw = n % padded_width;\n    n /= padded_width;\n    const int ph = n % padded_height;\n    n /= padded_height;\n    const int h = min(height - 1, max(ph - pad_t, 0));\n    const int w = min(width - 1, max(pw - pad_l, 0));\n    top_data[index] =\n        bottom_data[((n * height + h) * width + w) * channels + c];\n  }\n}\n\ntemplate <typename T>\n__global__ void PadImageGradientConstNCHW(\n    const int nthreads, const T* const top_diff, const int num,\n    const int channels, const int height, const int width,\n    const int padded_height, const int padded_width,\n    const int pad_t, const int pad_l, T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int nc = index / width;\n    const int pw = index % width + pad_l;\n    const int ph = nc % height + pad_t;\n    nc /= height;\n    bottom_diff[index] =\n        top_diff[(nc * padded_height + ph) * padded_width + pw];\n  }\n}\n\ntemplate <typename T>\n__global__ void PadImageGradientReflectNCHW(\n    const int nthreads, const T* const top_diff, const int num,\n    const int channels, const int height, const int width,\n    const int padded_height, const int padded_width,\n    const int pad_t, const int pad_l, T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int nc = index / padded_width;\n    const int pw = index % padded_width;\n    const int ph = nc % padded_height;\n    nc /= padded_height;\n    int h = ph - pad_t;\n    int w = pw - pad_l;\n    h = max(h, -h);\n    w = max(w, -w);\n    h = min(h, 2 * height - h - 2);\n    w = min(w, 2 * width - w - 2);\n    atomicAdd(&bottom_diff[(nc * height + h) * width + w], top_diff[index]);\n  }\n}\n\ntemplate <typename T>\n__global__ void PadImageGradientEdgeNCHW(\n    const int nthreads, const T* const top_diff, const int num,\n    const int channels, const int height, const int width,\n    const int padded_height, const int padded_width,\n    const int pad_t, const int pad_l, T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int nc = index / padded_width;\n    const int pw = index % padded_width;\n    const int ph = nc % padded_height;\n    nc /= padded_height;\n    const int h = min(height - 1, max(ph - pad_t, 0));\n    const int w = min(width - 1, max(pw - pad_l, 0));\n    atomicAdd(&bottom_diff[(nc * height + h) * width + w], top_diff[index]);\n  }\n}\n\ntemplate <typename T>\n__global__ void PadImageGradientConstNHWC(\n    const int nthreads, const T* const top_diff, const int num,\n    const int height, const int width, const int channels,\n    const int padded_height, const int padded_width,\n    const int pad_t, const int pad_l, T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index / channels;\n    const int c = index % channels;\n    const int pw = n % width + pad_l;\n    n /= width;\n    const int ph = n % height + pad_t;\n    n /= height;\n    bottom_diff[index] =\n        top_diff[((n * padded_height + ph) * padded_width + pw) * channels + c];\n  }\n}\n\ntemplate <typename T>\n__global__ void PadImageGradientReflectNHWC(\n    const int nthreads, const T* const top_diff, const int num,\n    const int height, const int width, const int channels,\n    const int padded_height, const int padded_width,\n    const int pad_t, const int pad_l, T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index / channels;\n    const int c = index % channels;\n    const int pw = n % padded_width;\n    n /= padded_width;\n    const int ph = n % padded_height;\n    n /= padded_height;\n    int h = ph - pad_t;\n    int w = pw - pad_l;\n    h = max(h, -h);\n    w = max(w, -w);\n    h = min(h, 2 * height - h - 2);\n    w = min(w, 2 * width - w - 2);\n    atomicAdd(\n        &bottom_diff[((n * height + h) * width + w) * channels + c],\n        top_diff[index]);\n  }\n}\n\ntemplate <typename T>\n__global__ void PadImageGradientEdgeNHWC(\n    const int nthreads, const T* const top_diff, const int num,\n    const int height, const int width, const int channels,\n    const int padded_height, const int padded_width,\n    const int pad_t, const int pad_l, T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index / channels;\n    const int c = index % channels;\n    const int pw = n % padded_width;\n    n /= padded_width;\n    const int ph = n % padded_height;\n    n /= padded_height;\n    const int h = min(height - 1, max(ph - pad_t, 0));\n    const int w = min(width - 1, max(pw - pad_l, 0));\n    atomicAdd(\n        &bottom_diff[((n * height + h) * width + w) * channels + c],\n        top_diff[index]);\n  }\n}\n\n}  // namespace\n\ntemplate <>\nbool PadImageOp<float, CUDAContext>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  const int num = X.dim32(0);\n  const int channels = X.dim32(1);\n  const int height = X.dim32(2);\n  const int width = X.dim32(3);\n  ConvPoolOpBase<CUDAContext>::SetOutputSize(X, Y, channels);\n  const int output_size = Y->size();\n  const int padded_height = Y->dim32(2);\n  const int padded_width = Y->dim32(3);\n  const float* Xdata = X.data<float>();\n  float* Ydata = Y->mutable_data<float>();\n\n  switch (mode_) {\n    case PadMode::CONSTANT:\n      PadImageConstNCHW<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          Xdata,\n          num,\n          channels,\n          height,\n          width,\n          padded_height,\n          padded_width,\n          pad_t(),\n          pad_l(),\n          value_,\n          Ydata);\n      break;\n    case PadMode::REFLECT:\n      PadImageReflectNCHW<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          Xdata,\n          num,\n          channels,\n          height,\n          width,\n          padded_height,\n          padded_width,\n          pad_t(),\n          pad_l(),\n          Ydata);\n      break;\n    case PadMode::EDGE:\n      PadImageEdgeNCHW<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          Xdata,\n          num,\n          channels,\n          height,\n          width,\n          padded_height,\n          padded_width,\n          pad_t(),\n          pad_l(),\n          Ydata);\n      break;\n  }\n\n  return true;\n}\n\ntemplate<>\nbool PadImageOp<float, CUDAContext>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  const int num = X.dim32(0);\n  const int height = X.dim32(1);\n  const int width = X.dim32(2);\n  const int channels = X.dim32(3);\n  ConvPoolOpBase<CUDAContext>::SetOutputSize(X, Y, channels);\n  const int output_size = Y->size();\n  const int padded_height = Y->dim32(1);\n  const int padded_width = Y->dim32(2);\n  const float* Xdata = X.data<float>();\n  float* Ydata = Y->mutable_data<float>();\n\n  switch (mode_) {\n    case PadMode::CONSTANT:\n      PadImageConstNHWC<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          Xdata,\n          num,\n          height,\n          width,\n          channels,\n          padded_height,\n          padded_width,\n          pad_t(),\n          pad_l(),\n          value_,\n          Ydata);\n      break;\n    case PadMode::REFLECT:\n      PadImageReflectNHWC<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          Xdata,\n          num,\n          height,\n          width,\n          channels,\n          padded_height,\n          padded_width,\n          pad_t(),\n          pad_l(),\n          Ydata);\n      break;\n    case PadMode::EDGE:\n      PadImageEdgeNHWC<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          Xdata,\n          num,\n          height,\n          width,\n          channels,\n          padded_height,\n          padded_width,\n          pad_t(),\n          pad_l(),\n          Ydata);\n      break;\n  }\n\n  return true;\n}\n\ntemplate<>\nbool PadImageGradientOp<float, CUDAContext>::RunOnDeviceWithOrderNCHW() {\n  auto& dY = Input(0);\n  auto* dX = Output(0);\n  dX->Resize(\n      dY.dim32(0),\n      dY.dim32(1),\n      dY.dim32(2) - pad_t() - pad_b(),\n      dY.dim32(3) - pad_l() - pad_r());\n  const int input_size = dY.size();\n  const int padded_height = dY.dim32(2);\n  const int padded_width = dY.dim32(3);\n  const int output_size = dX->size();\n  const int num = dX->dim32(0);\n  const int channels = dX->dim32(1);\n  const int height = dX->dim32(2);\n  const int width = dX->dim32(3);\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  math::Set<float, CUDAContext>(output_size, 0, dXdata, &context_);\n\n  switch (mode_) {\n    case PadMode::CONSTANT:\n      PadImageGradientConstNCHW<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          dYdata,\n          num,\n          channels,\n          height,\n          width,\n          padded_height,\n          padded_width,\n          pad_t(),\n          pad_l(),\n          dXdata);\n      break;\n    case PadMode::REFLECT:\n      PadImageGradientReflectNCHW<float><<<\n          CAFFE_GET_BLOCKS(input_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          input_size,\n          dYdata,\n          num,\n          channels,\n          height,\n          width,\n          padded_height,\n          padded_width,\n          pad_t(),\n          pad_l(),\n          dXdata);\n      break;\n    case PadMode::EDGE:\n      PadImageGradientEdgeNCHW<float><<<\n          CAFFE_GET_BLOCKS(input_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          input_size,\n          dYdata,\n          num,\n          channels,\n          height,\n          width,\n          padded_height,\n          padded_width,\n          pad_t(),\n          pad_l(),\n          dXdata);\n      break;\n  }\n\n  return true;\n}\n\ntemplate<>\nbool PadImageGradientOp<float, CUDAContext>::RunOnDeviceWithOrderNHWC() {\n  auto& dY = Input(0);\n  auto* dX = Output(0);\n  dX->Resize(\n      dY.dim32(0),\n      dY.dim32(1) - pad_t() - pad_b(),\n      dY.dim32(2) - pad_l() - pad_r(),\n      dY.dim32(3));\n  const int input_size = dY.size();\n  const int padded_height = dY.dim32(1);\n  const int padded_width = dY.dim32(2);\n  const int output_size = dX->size();\n  const int num = dX->dim32(0);\n  const int height = dX->dim32(1);\n  const int width = dX->dim32(2);\n  const int channels = dX->dim32(3);\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  math::Set<float, CUDAContext>(output_size, 0, dXdata, &context_);\n\n  switch (mode_) {\n    case PadMode::CONSTANT:\n      PadImageGradientConstNHWC<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          dYdata,\n          num,\n          height,\n          width,\n          channels,\n          padded_height,\n          padded_width,\n          pad_t(),\n          pad_l(),\n          dXdata);\n      break;\n    case PadMode::REFLECT:\n      PadImageGradientReflectNHWC<float><<<\n          CAFFE_GET_BLOCKS(input_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          input_size,\n          dYdata,\n          num,\n          height,\n          width,\n          channels,\n          padded_height,\n          padded_width,\n          pad_t(),\n          pad_l(),\n          dXdata);\n      break;\n    case PadMode::EDGE:\n      PadImageGradientEdgeNHWC<float><<<\n          CAFFE_GET_BLOCKS(input_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          input_size,\n          dYdata,\n          num,\n          height,\n          width,\n          channels,\n          padded_height,\n          padded_width,\n          pad_t(),\n          pad_l(),\n          dXdata);\n      break;\n  }\n\n  return true;\n}\n\n\nREGISTER_CUDA_OPERATOR(PadImage, PadImageOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(PadImageGradient,\n                       PadImageGradientOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/partition_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/partition_ops.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(Partition, PartitionOp);\nREGISTER_CPU_OPERATOR(LengthsPartition, LengthsPartitionOp);\nREGISTER_CPU_OPERATOR(GatherByKey, GatherByKeyOp);\n\nOPERATOR_SCHEMA(GatherByKey)\n    .NumInputs(2, INT_MAX)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nInverse operation of Partition.\n\nTakes the original, full 'keys' tensor followed by sharded value tensors,\nand returns the full value tensor, combined using the same hash used in\nPartition.\n)DOC\")\n    .Input(\n        0,\n        \"keys\",\n        \"The first input is the full keys tensor\"\n        \" (same as the first input of Partition).\")\n    .Input(\n        1,\n        \"sharded_values\",\n        \"Subsequented inputs are sharded values tensors.\")\n    .Output(0, \"values\", \"Reconstructed values tensor.\");\n\nOPERATOR_SCHEMA(Partition)\n    .NumInputsOutputs([](int in, int out) {\n      return in > 0 && out > 0 && out % in == 0;\n    })\n    .SetDoc(R\"DOC(\nSplits the input int tensor into multiple ones according to the first tensor.\n\nTakes the first input and partitions it to shards according to the remainder of\nvalues modulo the number of partitions. It requires that the first tensor is of\nintegral type. The number of partitions is derived as (num_output / num_input).\n\nIf additional inputs are present they must have the same shape as the first\ninput, optionally with extra trailing dimensions. They will be partitioned\naccordingly to the first input.\n\nOptional arg 'pack_first_input' transforms the first tensor values as\nX_ij / num_partitions.\n\nOutputs are ordered as\nX_0_part_0, X_1_part_0, ..., X_N-1_part_0, X_0_part_1, ..., X_N-1_part_K-1\n)DOC\")\n    .Arg(\n        \"pack_first_input\",\n        \"(int, default 0) If set, the operator transforms \"\n        \"the first tensor values as floor(X_ij / num_partitions)\")\n    .Input(\n        0,\n        \"input\",\n        \"Input tensor containing data to be partitioned. The \"\n        \"number of input tensors might be greater than 1 but must have the \"\n        \"same shape as the previous tensors.\")\n    .Output(\n        0,\n        \"partitions\",\n        \"Output Partitions. The number of output tensors has to be a \"\n        \"multiple of the number of input tensors.\");\n\nOPERATOR_SCHEMA(LengthsPartition)\n    .NumInputsOutputs([](int in, int out) {\n      return in >= 2 && out > 0 && out % in == 0;\n    })\n    .SetDoc(R\"DOC(\nLengthsPartition splits the input int tensor into multiple ones according to the\nsecond tensor. The first dimension is expected to be the tensor that describes\nlengths of the elements.\n\nTakes the second input and partitions it to shards according to the remainder of\nvalues modulo the number of partitions. It requires the second tensor to be\na 1D-tensor of the integral type. The first tensor should be 1D-tensor of int32\nthat would represent the lengths of the elements in the input. The number of\npartitions is derived as (num_output / num_input).\n\nIf additional inputs are present they must have the same shape as the first\ninput, optionally with extra trailing dimensions. They will be partitioned\naccordingly to the first input.\n\nOptional arg 'pack_first_input' transforms the first tensor values as\nX_ij / num_partitions.\n\nOutputs are ordered as\nX_0_part_0, X_1_part_0, ..., X_N-1_part_0, X_0_part_1, ..., X_N-1_part_K-1\n)DOC\")\n    .Arg(\n        \"pack_first_input\",\n        \"(int, default 0) If set, the operator transforms \"\n        \"the first tensor values as floor(X_ij / num_partitions)\")\n    .Input(\n        0,\n        \"input\",\n        \"Input tensor containing data to be partitioned. The \"\n        \"number of input tensors might be greater than 1 but must have the \"\n        \"same shape as the previous tensors.\")\n    .Output(\n        0,\n        \"partitions\",\n        \"Output Partitions. The number of output tensors has to be a \"\n        \"multiple of the number of input tensors.\");\n\n// This should actually have gradient, but for now nothing uses it.\n// Because gradient computation right now is not input/output aware it can't be\n// GRADIENT_NOT_IMPLEMENTEDYET\nNO_GRADIENT(Partition);\nNO_GRADIENT(LengthsPartition);\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/partition_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_PARTITION_OPS_H_\n#define CAFFE2_OPERATORS_PARTITION_OPS_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename Index>\nstatic inline int moduloPartition(Index key, int numPartitions) {\n  int shard = key % numPartitions;\n  // equivalent to `if (shard < 0) shard += partitions;`\n  shard += numPartitions & (shard >> (sizeof(int) * 8 - 1));\n  return shard;\n}\n\nclass GatherByKeyOp : public Operator<CPUContext> {\n public:\n  USE_DISPATCH_HELPER;\n  USE_OPERATOR_FUNCTIONS(CPUContext);\n  GatherByKeyOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n private:\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(this, Input(0));\n  }\n\n private:\n  template <typename Index>\n  bool DoRunWithType() {\n    const auto numPartitions = InputSize() - 1;\n    CAFFE_ENFORCE_GE(numPartitions, 1);\n    const auto& keysTensor = Input(0);\n    const auto* keysData = keysTensor.template data<Index>();\n    const auto& keysShape = Input(0).dims();\n    CAFFE_ENFORCE_EQ(\n        keysShape.size(), 1, \"Only 1D keys tensor supported currently.\");\n\n    // 1. Shape and type consistency checks\n    const auto& in0Shape = Input(1).dims();\n    CAFFE_ENFORCE_GE(in0Shape.size(), 1);\n\n    vector<TIndex> outShape(keysShape);\n    outShape.insert(outShape.end(), in0Shape.begin() + 1, in0Shape.end());\n\n    CAFFE_ENFORCE_GE(outShape.size(), 1);\n    auto totalSize = in0Shape[0];\n    auto meta = Input(1).meta();\n    for (int i = 2; i < InputSize(); ++i) {\n      const auto& input = Input(i);\n      CAFFE_ENFORCE(meta == input.meta());\n      CAFFE_ENFORCE_GE(input.ndim(), 1);\n      CAFFE_ENFORCE(std::equal(\n          outShape.begin() + keysShape.size(),\n          outShape.end(),\n          input.dims().begin() + 1));\n      totalSize += input.dim(0);\n    }\n    CAFFE_ENFORCE_EQ(keysTensor.size(), totalSize);\n\n    auto* outTensor = Output(0);\n    outTensor->Resize(outShape);\n    auto* outData = static_cast<char*>(outTensor->raw_mutable_data(meta));\n    const auto blockSize = outTensor->size_from_dim(1);\n\n    inputDatas_.resize(numPartitions);\n    for (int i = 0; i < numPartitions; ++i) {\n      inputDatas_[i] = static_cast<const char*>(Input(i + 1).raw_data());\n    }\n    inStartOffsets_.assign(numPartitions, 0);\n    Index outStartOffset = 0;\n    int currentShard = -1;\n\n    // 2. copy from inputs into output based on shard for each input key\n    const auto numEntries = keysTensor.size();\n    for (int64_t i = 0; i <= numEntries; ++i) {\n      auto newShard =\n          i < numEntries ? moduloPartition(keysData[i], numPartitions) : -1;\n      if (newShard != currentShard) {\n        if (currentShard != -1) {\n          auto inStartOffset = inStartOffsets_[currentShard];\n          auto numItems = i - outStartOffset;\n          context_.template CopyItems<CPUContext, CPUContext>(\n              meta,\n              numItems * blockSize,\n              inputDatas_[currentShard] +\n                  inStartOffset * blockSize * meta.itemsize(),\n              outData + outStartOffset * blockSize * meta.itemsize());\n          inStartOffsets_[currentShard] += numItems;\n        }\n        currentShard = newShard;\n        outStartOffset = i;\n      }\n    }\n\n    return true;\n  }\n\n  std::vector<const char*> inputDatas_;\n  std::vector<int64_t> inStartOffsets_;\n};\n\nclass PartitionOpBase : public Operator<CPUContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CPUContext);\n\n  PartitionOpBase(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        OP_SINGLE_ARG(int, \"pack_first_input\", pack_first_input_, 0) {}\n\n protected:\n  template <typename Index>\n  void ApplyPartition(bool skipFirstArgument) {\n    CAFFE_ENFORCE_EQ(\n        OutputSize() % InputSize(),\n        0,\n        \"Output number must be a multiple of input number\");\n    int partitions = OutputSize() / InputSize();\n    int inputSize = InputSize();\n    int mainInputIndex = skipFirstArgument;\n    CAFFE_ENFORCE_GT(partitions, 0, \"Invalid number of partitions\");\n\n    auto& main_input = Input(mainInputIndex);\n    TIndex size = main_input.size();\n    const Index* data = main_input.template data<Index>();\n    counts_.assign(partitions, 0);\n    for (TIndex p = 0; p < size; p++) {\n      int shard = moduloPartition(data[p], partitions);\n      ++counts_[shard];\n    }\n\n    raw_datas_.resize(inputSize);\n    block_sizes_.resize(inputSize);\n    metas_.resize(inputSize);\n    out_datas_.resize(OutputSize());\n    for (int i = mainInputIndex; i < inputSize; ++i) {\n      auto& input = Input(i);\n      if (i > mainInputIndex) {\n        CAFFE_ENFORCE_GE(\n            input.ndim(),\n            main_input.ndim(),\n            \"Prefix of extra input's shape must match main input's shape, \",\n            \"input: \",\n            i);\n        for (int j = 0; j < main_input.ndim(); ++j) {\n          CAFFE_ENFORCE_GE(\n              input.dim(j),\n              main_input.dim(j),\n              \"Prefix of extra input's shape must match main input's shape, \",\n              \"input: \",\n              i,\n              \", dim \",\n              j);\n        }\n      }\n      raw_datas_[i] = input.raw_data();\n      block_sizes_[i] = input.size_from_dim(main_input.ndim());\n      metas_[i] = input.meta();\n      // shape = partition_size + suffix of input dims\n      vector<TIndex> shape(\n          input.dims().begin() + main_input.ndim() - 1, input.dims().end());\n      for (int j = 0; j < partitions; ++j) {\n        int out_idx = i + j * inputSize;\n        auto output = Output(out_idx);\n        shape[0] = counts_[j];\n        output->Resize(shape);\n        out_datas_[out_idx] = output->raw_mutable_data(input.meta());\n      }\n    }\n\n    counts_.assign(partitions, 0);\n    for (TIndex p = 0; p < size; p++) {\n      int shard = moduloPartition(data[p], partitions);\n      TIndex idx = counts_[shard]++;\n\n      // special case first input\n      static_cast<Index*>(out_datas_[shard * inputSize + mainInputIndex])[idx] =\n          pack_first_input_ ? ((data[p] - shard) / partitions) : data[p];\n\n      int baseIndex = shard * inputSize;\n      for (int i = mainInputIndex + 1; i < inputSize; ++i) {\n        auto bs = block_sizes_[i];\n        auto meta = metas_[i];\n        // special case for small bs?\n        context_.template CopyItems<CPUContext, CPUContext>(\n            meta,\n            bs,\n            static_cast<const char*>(raw_datas_[i]) + p * bs * meta.itemsize(),\n            static_cast<char*>(out_datas_[baseIndex + i]) +\n                idx * bs * meta.itemsize());\n      }\n    }\n  }\n\n  bool pack_first_input_;\n\n  // use member fields to reuse memory\n  vector<TIndex> counts_;\n  vector<TIndex> block_sizes_;\n  vector<TypeMeta> metas_;\n  vector<const void*> raw_datas_;\n  vector<void*> out_datas_;\n};\n\nclass PartitionOp : public PartitionOpBase {\n public:\n  USE_DISPATCH_HELPER;\n\n  PartitionOp(const OperatorDef& operator_def, Workspace* ws)\n      : PartitionOpBase(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(this, Input(0));\n  }\n\n private:\n  template <typename Index>\n  bool DoRunWithType() {\n    ApplyPartition<Index>(false /* skipFirstArgument */);\n    return true;\n  }\n\n  DISABLE_COPY_AND_ASSIGN(PartitionOp);\n};\n\nclass LengthsPartitionOp : public PartitionOpBase {\n public:\n  USE_DISPATCH_HELPER;\n\n  LengthsPartitionOp(const OperatorDef& operator_def, Workspace* ws)\n      : PartitionOpBase(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(this, Input(1));\n  }\n\n private:\n  template <typename Index>\n  bool DoRunWithType() {\n    CAFFE_ENFORCE(\n        OutputSize() % InputSize() == 0,\n        \"Output number must be a multiple of input number\");\n    int partitions = OutputSize() / InputSize();\n    CAFFE_ENFORCE_GT(partitions, 0, \"Invalid number of partitions\");\n    CAFFE_ENFORCE_EQ(\n        Input(1).ndim(),\n        1,\n        \"Only 1-D tensors supported as a partitioning tensor for sharding\");\n\n    // Apply sharding to all parameters except lengths\n    ApplyPartition<Index>(true /* skipFirstArgument */);\n\n    // Compute lengths after sharding\n    auto& main_input = Input(1);\n    TIndex size = main_input.size();\n    const Index* data = main_input.template data<Index>();\n\n    auto& length_input = Input(0);\n    TIndex elements = length_input.size();\n    const int32_t* lengths_data = length_input.template data<int32_t>();\n    out_length_.resize(partitions);\n    for (int i = 0; i < partitions; ++i) {\n      auto& output = *Output(i * InputSize());\n      output.Resize(elements);\n      out_length_[i] = output.template mutable_data<int32_t>();\n    }\n\n    int total_length = 0;\n    for (int i = 0; i < elements; ++i) {\n      total_length += lengths_data[i];\n    }\n    CAFFE_ENFORCE(\n        total_length == size,\n        \"Total length is not matching to the number of elements\");\n\n    int index = 0;\n    for (int i = 0; i < elements; ++i) {\n      for (int j = 0; j < partitions; ++j) {\n        out_length_[j][i] = 0;\n      }\n      for (int j = 0; j < lengths_data[i]; ++j, ++index) {\n        int shard = moduloPartition(data[index], partitions);\n        ++out_length_[shard][i];\n      }\n    }\n    return true;\n  }\n\n  DISABLE_COPY_AND_ASSIGN(LengthsPartitionOp);\n\n  vector<int32_t*> out_length_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_PARTITION_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/percentile_op.cc",
    "content": "#include \"caffe2/operators/percentile_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool PercentileOp<CPUContext>::RunOnDevice() {\n  const auto& original_values = Input(X);\n  CAFFE_ENFORCE_EQ(original_values.ndim(), 2);\n  const auto num_examples = original_values.dim(0);\n  const float* original_values_data = original_values.template data<float>();\n  const auto num_features = original_values.dim(1);\n\n  const auto& value_pct_pairs = Input(VAL_PCT_PAIRS);\n  CAFFE_ENFORCE_EQ(value_pct_pairs.ndim(), 2);\n  CAFFE_ENFORCE_EQ(value_pct_pairs.dim(1), 2);\n  const int num_values = value_pct_pairs.dim(0);\n  const float* value_pct_data = value_pct_pairs.template data<float>();\n\n  const auto& lengths = Input(LENS);\n  const int* lengths_data = lengths.template data<int>();\n  CAFFE_ENFORCE_EQ(lengths.size(), num_features);\n\n  CAFFE_ENFORCE_EQ(\n      std::accumulate(lengths_data, lengths_data + lengths.size(), 0),\n      num_values,\n      \"Sum of lengths should be equal to the total number of samples\");\n\n  values_tensor.Resize(num_values);\n  percentiles_tensor.Resize(num_values);\n  float* values_tensor_data = values_tensor.template mutable_data<float>();\n  float* percentiles_tensor_data =\n      percentiles_tensor.template mutable_data<float>();\n  for (int ind = 0; ind < num_values; ind++) {\n    values_tensor_data[ind] = value_pct_data[2 * ind];\n    percentiles_tensor_data[ind] = value_pct_data[2 * ind + 1];\n  }\n\n  auto* percentile_values = Output(PCT);\n  percentile_values->ResizeLike(original_values);\n  float* percentile_values_data =\n      percentile_values->template mutable_data<float>();\n\n  int current_ind = 0;\n  int current_dist_start = 0;\n  int current_length;\n  for (int i = 0; i < num_examples; i++) {\n    current_dist_start = 0;\n\n    for (int j = 0; j < num_features; j++) {\n      current_length = lengths_data[j];\n      const auto lower_bound =\n          std::lower_bound(\n              values_tensor_data + current_dist_start,\n              values_tensor_data + current_dist_start + current_length,\n              original_values_data[current_ind]) -\n          values_tensor_data;\n      if (lower_bound == current_dist_start + current_length) {\n        percentile_values_data[current_ind] = 1.0;\n      } else if (\n          original_values_data[current_ind] ==\n          values_tensor_data[lower_bound]) {\n        percentile_values_data[current_ind] =\n            percentiles_tensor_data[lower_bound];\n      } else if (lower_bound == current_dist_start) {\n        percentile_values_data[current_ind] = 0.0;\n      } else {\n        float lower_pct = percentiles_tensor_data[lower_bound - 1];\n        float upper_pct = percentiles_tensor_data[lower_bound];\n        float interval_length = values_tensor_data[lower_bound] -\n            values_tensor_data[lower_bound - 1];\n        float normalized_dist_to_lower = (original_values_data[current_ind] -\n                                          values_tensor_data[lower_bound - 1]) /\n            interval_length;\n        percentile_values_data[current_ind] =\n            lower_pct + normalized_dist_to_lower * (upper_pct - lower_pct);\n      }\n      current_dist_start += current_length;\n      current_ind++;\n    }\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(Percentile, PercentileOp<CPUContext>);\nOPERATOR_SCHEMA(Percentile)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\n    This operator is used to find percentile representations for raw values, given a sample\n    set of raw values, labeled with their corresponding percentiles from the same distribution.\n    In particular, this operator takes as input a tensor of floats to find the percentile values\n    for, a 2D tensor of floats, where the first column of the tensor represents sampled values,\n    and the second column represents the percentile labels, and a tensor  of integers lengths.\n\n    This lengths tensor is used because the operator works on multiple sets of raw values at the same time. For\n    example, for an input:\n    original_values=[[3, 5, 3],[5, 1, 6]], lengths = [2, 1, 1], value_to_pct = [[3, 0.2], [5, 0.5], [1, 0.3], [3. 0.6]]\n\n    Our operator expects that each column i of the input tensor is sampled from distribution i. Lengths tells\n    us that the first two elements in value_to_pct are sampled from distribution 1, the next is from distribution two,\n    and the last is from distribution 3. We expect the output of our operator to give us [[0.2, 1.0, 0.6], [0.5, 0.3, 1.0]].\n\n    To calculate the percentile of an element, we check to see if its value is already mapped to\n    a percentile in value_to_pct. If so, we return that value. If not, we linearly interpolate between\n    the two closest values in value_to_pct. If the value is larger than all values in value_to_pct, we\n    return 1. If it's smaller than all the values, we return 0.\n\n)DOC\")\n    .Input(\n        0,\n        \"original_values\",\n        \"Input 2D tensor of floats, representing the original, raw data to calculate percentiles for.\")\n    .Input(\n        1,\n        \"value_to_pct\",\n        \"Sorted 2D tensor, with 2 columns. Each element in the first column is a float representing the\"\n        \" raw value of a sample. Its corresponding element in the next column represents the percentile it maps to.\")\n    .Input(\n        2,\n        \"lengths\",\n        \"1D tensor, representing the length of each distribution. We expect that the sum of elements of this tensor\"\n        \" is equal to the total length of value_to_pct.\")\n    .Output(\n        0,\n        \"percentile_values\",\n        \"1D tensor of floats, with the same dimensions as the flattened input tensor. Each element \"\n        \"of this tensor, percentile_values[i], corresponds to the percentile calculated \"\n        \"for original_values[i].\");\n\nNO_GRADIENT(Percentile);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/percentile_op.h",
    "content": "// Operator to calculate percentile values for an input tensor of data,\n// given samples of data from the same distribution, labeled with their\n// percentile values.\n\n#ifndef CAFFE2_OPERATORS_PERCENTILE_OP_H_\n#define CAFFE2_OPERATORS_PERCENTILE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass PercentileOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  PercentileOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override;\n\n protected:\n  INPUT_TAGS(X, VAL_PCT_PAIRS, LENS);\n  OUTPUT_TAGS(PCT);\n  Tensor<Context> values_tensor;\n  Tensor<Context> percentiles_tensor;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_PERCENTILE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/perplexity_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/perplexity_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool PerplexityOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n\n  DCHECK_EQ(X.ndim(), 1);\n  int N = X.dim32(0);\n\n  Y->Resize(vector<TIndex>());\n  const auto* Xdata = X.data<float>();\n\n  float perplexity = 1.0;\n  for (int i = 0; i < N; ++i) {\n    perplexity *= pow(Xdata[i], -1.0/N);\n  }\n  *(Y->mutable_data<float>()) = perplexity;\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(Perplexity, PerplexityOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Perplexity).NumInputs(1).NumOutputs(1)\n.SetDoc(R\"DOC(\nPerplexity calculates how well a probability distribution predicts a sample.\nPerplexity takes a 1-D tensor containing a batch of probabilities. Each value\nin the tensor belongs to a different sample and represents the probability of\nthe model predicting the true label for that sample. The operator returns a\nsingle (float) perplexity value for the batch.\n)DOC\")\n.Input(0, \"probabilities\", \"The input data as Tensor. It contains a batch of\"\n       \"true label or target probabilities\")\n.Output(0, \"output\", \"The output- a single (float) perplexity value for the \"\n        \"batch\");\n\nSHOULD_NOT_DO_GRADIENT(Perplexity);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/perplexity_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/perplexity_op.h\"\n#include \"caffe2/utils/math.h\"\n#include <thrust/device_vector.h>\n#include <thrust/transform_reduce.h>\n#include <thrust/system/cuda/execution_policy.h>\n\nnamespace caffe2 {\n\nstruct perplexity_function\n{\n  perplexity_function(float p) : pow(p) {}\n  __host__ __device__ float operator()(float x) const\n  {\n    return powf(1.0f/x, pow);\n  }\n  float pow;\n};\n\ntemplate <>\nbool PerplexityOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n\n  DCHECK_EQ(X.ndim(), 1);\n  int N = X.dim32(0);\n\n  Y->Resize(vector<TIndex>());\n  float* Ydata = Y->mutable_data<float>();\n  const float* Xdata = X.data<float>();\n\n  float perplexity = thrust::transform_reduce(\n      #if THRUST_VERSION >= 100800\n        thrust::cuda::par.on(context_.cuda_stream()),\n      #endif  // THRUST_VERSION >= 100800\n      Xdata, Xdata + N,\n      perplexity_function(1.0f/N),\n      1.0f,\n      thrust::multiplies<float>());\n\n  math::Set<float, CUDAContext>(1, perplexity, Ydata, &context_);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Perplexity, PerplexityOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/perplexity_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_PERPLEXITY_OP_H_\n#define CAFFE2_OPERATORS_PERPLEXITY_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass PerplexityOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(PerplexityOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_PERPLEXITY_OP_H_\n"
  },
  {
    "path": "caffe2/operators/piecewise_linear_transform_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/piecewise_linear_transform_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(\n    PiecewiseLinearTransform,\n    PiecewiseLinearTransformOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(PiecewiseLinearTransform)\n    .NumInputs(1, 4)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nPiecewiseLinearTransform takes inputs -- predictions, a 2-D or 1-D tensor\n(Tensor<float>) of size (batch_size x prediction_dimensions). The piecewise\nlinear functions are stored in bounds, slopes and intercepts. The output tensor\nhas the same shape of input `predictions` and contains the predictions\ntransformed by the piecewise linear functions. Each column of predictions has\nits own piecewise linear transformation functions. Therefore the size of\npiecewise function parameters are pieces x prediction_dimensions, except for\nbinary predictions where only the positive prediction needs them. Note that in\neach piece, low bound is excluded while high bound is included. Also the\npiecewise linear function must be continuous.\n\nNotes\n- If the input is binary predictions (Nx2 or Nx1 tensor), set the binary arg\nto true so that one group of piecewise linear functions is needed (see\ndetails below).\n- The transform parameters (bounds, slopes, intercepts) can be passed either\nthrough args or through input blobs.\n- If we have multiple groups of piecewise linear functions, each group has the\nsame number of pieces.\n- If a prediction is out of the bounds, it is capped to the smallest or largest\nbound.\n)DOC\")\n    .Arg(\n        \"bounds\",\n        \"1-D vector of size (prediction_dimensions x (pieces+1)) contain the \"\n        \"upper bounds of each piece of linear function. One special case is \"\n        \"the first bound is the lower bound of whole piecewise function and we \"\n        \"treat it the same as the left most functions. (bounds, slopes, \"\n        \"intercepts) can be passed through either arg or input blobs.\")\n    .Arg(\n        \"slopes\",\n        \"1-D vector of size (prediction_dimensions x pieces) containing the \"\n        \"slopes of linear function\")\n    .Arg(\n        \"intercepts\",\n        \"1-D vector of size (prediction_dimensions x pieces) containing the \"\n        \"intercepts of linear function\")\n    .Arg(\n        \"binary\",\n        \"If set true, we assume the input is a Nx1 or Nx2 tensor. If it is Nx1 \"\n        \"tensor, it is positive predictions. If the input is Nx2 tensor, its \"\n        \"first column is negative predictions and second column is positive \"\n        \"and negative + positive = 1. We just need one group of piecewise \"\n        \"linear functions for the positive predictions.\")\n    .Input(\n        0,\n        \"predictions\",\n        \"2-D tensor (Tensor<float>) of size \"\n        \"(num_batches x num_classes) containing scores\")\n    .Input(\n        1,\n        \"bounds (optional)\",\n        \"See bounds in Arg. (bounds, slopes, intercepts) can be passed through \"\n        \"either arg or input blobs.\")\n    .Input(\n        2,\n        \"slopes (optional)\",\n        \"See slopes in Arg. (bounds, slopes, intercepts) can be passed through \"\n        \"either arg or input blobs.\")\n    .Input(\n        3,\n        \"intercepts (optional)\",\n        \"See intercepts in Arg. (bounds, slopes, intercepts) can be passed \"\n        \"through either arg or input blobs.\")\n    .Output(\n        0,\n        \"transforms\",\n        \"2-D tensor (Tensor<float>) of size (num_batches x num_classes) \"\n        \"containing transformed predictions\");\n\nSHOULD_NOT_DO_GRADIENT(PiecewiseLinearTransform);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/piecewise_linear_transform_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/piecewise_linear_transform_op.h\"\n\n#include <thrust/binary_search.h>\n#include <thrust/device_vector.h>\n#include <thrust/execution_policy.h>\n#include <thrust/functional.h>\n\nnamespace caffe2 {\n\nnamespace {\n__global__ void PieceWiseLinearTransformGeneralKernel(\n    const int N,\n    const int M,\n    const int num_grp,\n    const int num_fnc_per_grp,\n    const float* bounds,\n    const float* slopes,\n    const float* intercepts,\n    const float* X,\n    float* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N * M) {\n    int col = i % M;\n    const float* bounds_group = bounds + (col * (num_fnc_per_grp + 1));\n    const float* slopes_group = slopes + (col * num_fnc_per_grp);\n    const float* intercepts_group = intercepts + (col * num_fnc_per_grp);\n\n    if (X[i] <= bounds_group[0]) {\n      Y[i] = slopes_group[0] * bounds_group[0] + intercepts_group[0];\n    } else if (X[i] >= bounds_group[num_fnc_per_grp]) {\n      Y[i] = slopes_group[num_fnc_per_grp - 1] * bounds_group[num_fnc_per_grp] +\n          intercepts_group[num_fnc_per_grp - 1];\n    } else {\n      auto low_bound = thrust::lower_bound(\n          thrust::device,\n          bounds_group,\n          bounds_group + num_fnc_per_grp + 1,\n          X[i]);\n      int bounds_idx = low_bound - bounds_group - 1;\n      Y[i] = slopes_group[bounds_idx] * X[i] + intercepts_group[bounds_idx];\n    }\n  }\n}\n\n} // namespace\n\nnamespace {\n__global__ void PieceWiseLinearTransformBinaryKernel1(\n    const int N,\n    const int M,\n    const int num_grp,\n    const int num_fnc_per_grp,\n    const float* bounds,\n    const float* slopes,\n    const float* intercepts,\n    const float* X,\n    float* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    if (X[i] <= bounds[0]) {\n      Y[i] = slopes[0] * bounds[0] + intercepts[0];\n    } else if (X[i] >= bounds[num_fnc_per_grp]) {\n      Y[i] = slopes[num_fnc_per_grp - 1] * bounds[num_fnc_per_grp] +\n          intercepts[num_fnc_per_grp - 1];\n    } else {\n      auto low_bound = thrust::lower_bound(\n          thrust::device, bounds, bounds + num_fnc_per_grp + 1, X[i]);\n      int bounds_idx = low_bound - bounds - 1;\n      Y[i] = slopes[bounds_idx] * X[i] + intercepts[bounds_idx];\n    }\n  }\n}\n} // namespace\n\nnamespace {\n__global__ void PieceWiseLinearTransformBinaryKernel2(\n    const int N,\n    const int M,\n    const int num_grp,\n    const int num_fnc_per_grp,\n    const float* bounds,\n    const float* slopes,\n    const float* intercepts,\n    const float* X,\n    float* Y) {\n  // N*M/2 = N as M=2\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    int index = i * M;\n    if (X[index + 1] <= bounds[0]) {\n      Y[index + 1] = slopes[0] * bounds[0] + intercepts[0];\n    } else if (X[index + 1] >= bounds[num_fnc_per_grp]) {\n      Y[index + 1] = slopes[num_fnc_per_grp - 1] * bounds[num_fnc_per_grp] +\n          intercepts[num_fnc_per_grp - 1];\n    } else {\n      auto low_bound = thrust::lower_bound(\n          thrust::device, bounds, bounds + num_fnc_per_grp + 1, X[index + 1]);\n      int bounds_idx = low_bound - bounds - 1;\n      Y[index + 1] = slopes[bounds_idx] * X[index + 1] + intercepts[bounds_idx];\n    }\n    Y[index] = 1.0f - Y[index + 1];\n  }\n}\n} // namespace\n\ntemplate <>\nvoid PiecewiseLinearTransformOp<float, CUDAContext>::setUpTensors(\n    TIndex& num_func_per_group,\n    TIndex& num_group,\n    TIndex M) {\n  if (transform_param_from_arg_) {\n    if (!gpu_copied_) {\n      TIndex num_bounds;\n      TIndex num_slopes;\n      TIndex num_intercepts;\n\n      CAFFE_ENFORCE_EQ(InputSize(), 1);\n\n      const float* bounds;\n      const float* slopes;\n      const float* intercepts;\n      bounds = bounds_from_arg_.data();\n      slopes = slopes_from_arg_.data();\n      intercepts = intercepts_from_arg_.data();\n      num_bounds = bounds_from_arg_.size();\n      num_slopes = slopes_from_arg_.size();\n      num_intercepts = intercepts_from_arg_.size();\n      InferNumFunctionsPerGroup(\n          num_bounds,\n          num_slopes,\n          num_intercepts,\n          &num_func_per_group,\n          &num_group);\n\n      if (binary_) {\n        CAFFE_ENFORCE_EQ(num_group, 1);\n      } else {\n        CAFFE_ENFORCE_EQ(num_group, M);\n      }\n\n      int length = num_group * num_func_per_group;\n      TensorCPU bounds_host;\n      bounds_host.Resize(length + num_group);\n      memcpy(\n          bounds_host.mutable_data<float>(),\n          bounds,\n          (length + num_group) * sizeof(float));\n\n      TensorCPU intercepts_host;\n      intercepts_host.Resize(length);\n      memcpy(\n          intercepts_host.mutable_data<float>(),\n          intercepts,\n          (length) * sizeof(float));\n      TensorCPU slopes_host;\n      slopes_host.Resize(length);\n      memcpy(\n          slopes_host.mutable_data<float>(), slopes, (length) * sizeof(float));\n\n      bounds_device_.CopyFrom<CPUContext>(bounds_host);\n      intercepts_device_.CopyFrom<CPUContext>(intercepts_host);\n      slopes_device_.CopyFrom<CPUContext>(slopes_host);\n\n      gpu_copied_ = true;\n    }\n  } else {\n    TIndex num_bounds;\n    TIndex num_slopes;\n    TIndex num_intercepts;\n    CAFFE_ENFORCE_EQ(InputSize(), 4);\n    auto& bounds_input = Input(BOUNDS);\n    auto& slopes_input = Input(SLOPES);\n    auto& intercepts_input = Input(INTERCEPTS);\n    num_bounds = bounds_input.size();\n    num_slopes = slopes_input.size();\n    num_intercepts = intercepts_input.size();\n    InferNumFunctionsPerGroup(\n        num_bounds,\n        num_slopes,\n        num_intercepts,\n        &num_func_per_group,\n        &num_group);\n\n    if (binary_) {\n      CAFFE_ENFORCE_EQ(num_group, 1);\n    } else {\n      CAFFE_ENFORCE_EQ(num_group, M);\n    }\n\n    bounds_device_.CopyFrom<CUDAContext>(bounds_input);\n    slopes_device_.CopyFrom<CUDAContext>(slopes_input);\n    intercepts_device_.CopyFrom<CUDAContext>(intercepts_input);\n  }\n}\n\ntemplate <>\nbool PiecewiseLinearTransformOp<float, CUDAContext>::TransformGeneral() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE_EQ(X.ndim(), 2);\n  TIndex N = X.dim32(0);\n  TIndex M = X.dim32(1);\n  Y->ResizeLike(X);\n\n  TIndex num_func_per_group;\n  TIndex num_group;\n\n  setUpTensors(num_func_per_group, num_group, M);\n\n  PieceWiseLinearTransformGeneralKernel<<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      M,\n      num_group,\n      num_func_per_group,\n      bounds_device_.data<float>(),\n      slopes_device_.data<float>(),\n      intercepts_device_.data<float>(),\n      X.data<float>(),\n      Y->mutable_data<float>());\n\n  return true;\n}\n\ntemplate <>\nbool PiecewiseLinearTransformOp<float, CUDAContext>::TransformBinary() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE(X.ndim() == 1 || X.ndim() == 2);\n  TIndex N = X.dim32(0);\n  TIndex M = X.ndim() == 2 ? X.dim32(1) : 1;\n  CAFFE_ENFORCE(\n      M == 1 || M == 2,\n      \"If binary is set to true, the input must be Nx2 or Nx1 tensor\");\n  Y->ResizeLike(X);\n\n  TIndex num_func_per_group;\n  TIndex num_group;\n\n  setUpTensors(num_func_per_group, num_group, M);\n\n  if (M == 1) {\n    PieceWiseLinearTransformBinaryKernel1<<<\n        CAFFE_GET_BLOCKS(X.size()),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        N,\n        M,\n        num_group,\n        num_func_per_group,\n        bounds_device_.data<float>(),\n        slopes_device_.data<float>(),\n        intercepts_device_.data<float>(),\n        X.data<float>(),\n        Y->mutable_data<float>());\n  } else {\n    PieceWiseLinearTransformBinaryKernel2<<<\n        // don't want N*M threads, only N*M/2\n        CAFFE_GET_BLOCKS(X.size() / 2),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        N,\n        M,\n        num_group,\n        num_func_per_group,\n        bounds_device_.data<float>(),\n        slopes_device_.data<float>(),\n        intercepts_device_.data<float>(),\n        X.data<float>(),\n        Y->mutable_data<float>());\n  }\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(\n    PiecewiseLinearTransform,\n    PiecewiseLinearTransformOp<float, CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/piecewise_linear_transform_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_PIECEWISE_LINEAR_TRANSFORM_OP_H_\n#define CAFFE2_OPERATORS_PIECEWISE_LINEAR_TRANSFORM_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass PiecewiseLinearTransformOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  PiecewiseLinearTransformOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    binary_ = OperatorBase::GetSingleArgument<bool>(\"binary\", false);\n\n    // Retrieve transform params (i.e., the linear functions).\n    bounds_from_arg_ = OperatorBase::GetRepeatedArgument<T>(\"bounds\");\n    slopes_from_arg_ = OperatorBase::GetRepeatedArgument<T>(\"slopes\");\n    intercepts_from_arg_ = OperatorBase::GetRepeatedArgument<T>(\"intercepts\");\n    transform_param_from_arg_ = CheckTransParamFromArg();\n  }\n\n  bool RunOnDevice() override {\n    return binary_ ? TransformBinary() : TransformGeneral();\n  }\n\n private:\n  // num_func_per_group is the number of pieces of linear functions of\n  // each group.\n  // num_group: The number of groups of linear functions. Each group is for\n  // transforming one column of predictions.\n  void InferNumFunctionsPerGroup(\n      const TIndex num_bounds,\n      const TIndex num_slopes,\n      const TIndex num_intercepts,\n      TIndex* num_func_per_group,\n      TIndex* num_group) {\n    CAFFE_ENFORCE_EQ(num_slopes, num_intercepts);\n\n    // This is based on the facts:\n    // 1. in each group, the num of bounds minus the num of slopes is 1;\n    // 2. each group has the same number of pieces.\n    *num_group = num_bounds - num_slopes;\n    CAFFE_ENFORCE_GT(*num_group, 0);\n    if (binary_) {\n      CAFFE_ENFORCE_EQ(*num_group, 1);\n    }\n    *num_func_per_group = num_slopes / *num_group;\n    CAFFE_ENFORCE_GT(*num_func_per_group, 0);\n    CAFFE_ENFORCE_EQ(num_slopes % *num_group, 0);\n  }\n\n  bool CheckBoundsSorted(\n      const T* bounds,\n      const TIndex num_bounds_per_group,\n      const TIndex num_group) {\n    const T* start = bounds;\n    for (TIndex i = 0; i < num_group; i++) {\n      if (!std::is_sorted(start, start + num_bounds_per_group)) {\n        return false;\n      }\n      start += num_bounds_per_group;\n    }\n    return true;\n  }\n\n  // Returns true if the transform params from arg are valid.\n  // Otherwise, we will assume the transform params will pass from Input blobs.\n  bool CheckTransParamFromArg() {\n    int good_param = 0;\n    good_param += bounds_from_arg_.size() > 0;\n    good_param += slopes_from_arg_.size() > 0;\n    good_param += intercepts_from_arg_.size() > 0;\n    CAFFE_ENFORCE(\n        good_param == 0 || good_param == 3,\n        \"bounds, slopes, intercepts must be all set or all not set\");\n    if (good_param == 3) {\n      TIndex num_func_per_group;\n      TIndex num_group;\n      InferNumFunctionsPerGroup(\n          bounds_from_arg_.size(),\n          slopes_from_arg_.size(),\n          intercepts_from_arg_.size(),\n          &num_func_per_group,\n          &num_group);\n      CAFFE_ENFORCE(\n          CheckBoundsSorted(\n              bounds_from_arg_.data(), num_func_per_group + 1, num_group),\n          \"bounds must be sorted for each group\");\n    }\n\n    return good_param == 3;\n  }\n\n  void setUpTensors(TIndex& num_func_per_group, TIndex& num_group, TIndex M);\n\n  void GetTransParamData(\n      const T** bounds,\n      const T** slopes,\n      const T** intercepts,\n      TIndex* num_func_per_group,\n      TIndex* num_group) {\n    TIndex num_bounds;\n    TIndex num_slopes;\n    TIndex num_intercepts;\n\n    if (transform_param_from_arg_) {\n      CAFFE_ENFORCE_EQ(InputSize(), 1);\n      *bounds = bounds_from_arg_.data();\n      *slopes = slopes_from_arg_.data();\n      *intercepts = intercepts_from_arg_.data();\n      num_bounds = bounds_from_arg_.size();\n      num_slopes = slopes_from_arg_.size();\n      num_intercepts = intercepts_from_arg_.size();\n    } else {\n      CAFFE_ENFORCE_EQ(InputSize(), 4);\n      auto& bounds_input = Input(BOUNDS);\n      auto& slopes_input = Input(SLOPES);\n      auto& intercepts_input = Input(INTERCEPTS);\n      *bounds = bounds_input.template data<T>();\n      *slopes = slopes_input.template data<T>();\n      *intercepts = intercepts_input.template data<T>();\n      num_bounds = bounds_input.size();\n      num_slopes = slopes_input.size();\n      num_intercepts = intercepts_input.size();\n    }\n    InferNumFunctionsPerGroup(\n        num_bounds, num_slopes, num_intercepts, num_func_per_group, num_group);\n  }\n\n  bool TransformGeneral() {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n    CAFFE_ENFORCE_EQ(X.ndim(), 2);\n    TIndex N = X.dim32(0);\n    TIndex M = X.dim32(1);\n    Y->ResizeLike(X);\n    const auto* Xdata = X.template data<T>();\n    T* Ydata = Y->template mutable_data<T>();\n\n    const T* bounds;\n    const T* slopes;\n    const T* intercepts;\n    TIndex num_func_per_group;\n    TIndex num_group;\n    GetTransParamData(\n        &bounds, &slopes, &intercepts, &num_func_per_group, &num_group);\n    CAFFE_ENFORCE_EQ(num_group, M);\n\n    for (TIndex j = 0; j < M; ++j) {\n      const T* bounds_group = bounds + j * (num_func_per_group + 1);\n      const T* slopes_group = slopes + j * num_func_per_group;\n      const T* intercepts_group = intercepts + j * num_func_per_group;\n      for (TIndex i = 0; i < N; ++i) {\n        Ydata[i * M + j] = PiecewiseLinearTransform(\n            Xdata[i * M + j],\n            bounds_group,\n            slopes_group,\n            intercepts_group,\n            num_func_per_group);\n      }\n    }\n    return true;\n  }\n\n  bool TransformBinary() {\n    auto& X = Input(PREDICTIONS);\n    auto* Y = Output(0);\n    CAFFE_ENFORCE(X.ndim() == 1 || X.ndim() == 2);\n    TIndex N = X.dim32(0);\n    TIndex M = X.ndim() == 2 ? X.dim32(1) : 1;\n    CAFFE_ENFORCE(\n        M == 1 || M == 2,\n        \"If binary is set to true, the input must be Nx2 or Nx1 tensor\");\n    Y->ResizeLike(X);\n    const auto* Xdata = X.template data<T>();\n    T* Ydata = Y->template mutable_data<T>();\n\n    const T* bounds;\n    const T* slopes;\n    const T* intercepts;\n    TIndex num_func_per_group;\n    TIndex num_group;\n    GetTransParamData(\n        &bounds, &slopes, &intercepts, &num_func_per_group, &num_group);\n    CAFFE_ENFORCE_EQ(num_group, 1);\n\n    if (M == 1) {\n      for (TIndex i = 0; i < N; ++i) {\n        Ydata[i] = PiecewiseLinearTransform(\n            Xdata[i], bounds, slopes, intercepts, num_func_per_group);\n      }\n    } else {\n      for (TIndex i = 0; i < N; ++i) {\n        Ydata[i * M + 1] = PiecewiseLinearTransform(\n            Xdata[i * M + 1], bounds, slopes, intercepts, num_func_per_group);\n        Ydata[i * M] = 1.0f - Ydata[i * M + 1];\n      }\n    }\n\n    return true;\n  }\n\n  T PiecewiseLinearTransform(\n      const T x,\n      const T* bounds,\n      const T* slopes,\n      const T* intercepts,\n      const TIndex num_func_per_group) {\n    T y = 0;\n    // deal with samples out of bounds\n    // make it the same as the upper/lower bound value\n    if (x <= bounds[0]) {\n      y = slopes[0] * bounds[0] + intercepts[0];\n    } else if (x >= bounds[num_func_per_group]) {\n      y = slopes[num_func_per_group - 1] * bounds[num_func_per_group] +\n          intercepts[num_func_per_group - 1];\n    } else {\n      auto low_bound =\n          std::lower_bound(bounds, bounds + num_func_per_group + 1, x);\n      int bounds_idx = low_bound - bounds - 1;\n      // compute the piecewise linear transformation as Y\n      y = slopes[bounds_idx] * x + intercepts[bounds_idx];\n    }\n    return y;\n  }\n\n private:\n  bool binary_;\n  vector<T> bounds_from_arg_;\n  vector<T> slopes_from_arg_;\n  vector<T> intercepts_from_arg_;\n\n  Tensor<Context> bounds_device_;\n  Tensor<Context> intercepts_device_;\n  Tensor<Context> slopes_device_;\n  bool gpu_copied_ = false;\n\n  // If true, the piecewise linear functions are passed through args,\n  // otherwise, they are passed through Input blobs.\n  bool transform_param_from_arg_;\n\n  INPUT_TAGS(PREDICTIONS, BOUNDS, SLOPES, INTERCEPTS);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_PIECEWISE_LINEAR_TRANSFORM_OP_H_\n"
  },
  {
    "path": "caffe2/operators/pool_gradient_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/pool_op.h\"\n\nnamespace caffe2 {\n\nusing std::max;\nusing std::min;\n\nnamespace {\n// These two classe are just used as template arguments passed to the\n// PoolGradientOp\n// template to instantiate the different algorithms.\ntemplate <typename T>\nclass AveragePool {\n public:\n  static void process_grad(\n      const T& /*x_data*/,\n      const T& /*y_data*/,\n      const T& dy_data,\n      const T& scale,\n      T& dx_data) {\n    dx_data += (scale * dy_data);\n  }\n\n  static void process_grad(\n      const int y_col,\n      const int x_col,\n      const float scale,\n      ConstEigenArrayMap<float>& /*x_data*/,\n      ConstEigenArrayMap<float>& /*y_data*/,\n      ConstEigenArrayMap<float>& dy_data,\n      EigenArrayMap<float>& dx_data) {\n    dx_data.col(x_col) += scale * dy_data.col(y_col);\n  }\n};\n\ntemplate <typename T>\nclass MaxPool {\n public:\n  static void process_grad(\n      const T& x_data,\n      const T& y_data,\n      const T& dy_data,\n      const T& /*scale*/,\n      T& dx_data) {\n    if (x_data == y_data) {\n      dx_data += dy_data;\n    }\n  }\n\n  static void process_grad(\n      const int y_col,\n      const int x_col,\n      const float /*scale*/,\n      ConstEigenArrayMap<float>& x_data,\n      ConstEigenArrayMap<float>& y_data,\n      ConstEigenArrayMap<float>& dy_data,\n      EigenArrayMap<float>& dx_data) {\n    dx_data.col(x_col) +=\n        dy_data.col(y_col) * (x_data.col(x_col)\n                                  .cwiseEqual(y_data.col(y_col))\n                                  .template cast<float>());\n  }\n};\n}\n\ntemplate <typename T, class Context, typename PoolType>\nbool PoolGradientOp<T, Context, PoolType>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dY = Input(2);\n  auto* dX = Output(0);\n  // TODO(Yangqing): Add shape checks.\n  dX->ResizeLike(X);\n  math::Set<float, CPUContext>(\n      X.size(), 0, dX->template mutable_data<float>(), &context_);\n  const float* Xdata = X.template data<float>();\n  const float* Ydata = Y.template data<float>();\n  const float* dYdata = dY.template data<float>();\n  float* dXdata = dX->template mutable_data<float>();\n  int channels = X.dim32(1);\n  CAFFE_ENFORCE_EQ(channels, dY.dim32(1));\n  int height = X.dim32(2);\n  int width = kernel_.size() > 1 ? X.dim32(3) : 1;\n  int depth = kernel_.size() > 2 ? X.dim32(4) : 1;\n  vector<int> dims(X.dims().begin() + 2, X.dims().end());\n  ConvPoolOpBase<CPUContext>::ComputePads(dims);\n  int pooled_height = dY.dim32(2);\n  int pooled_width = kernel_.size() > 1 ? dY.dim32(3) : 1;\n  int pooled_depth = kernel_.size() > 2 ? dY.dim32(4) : 1;\n  // The main loop\n  switch (kernel_.size()) {\n    case 1:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int c = 0; c < channels; ++c) {\n          for (int ph = 0; ph < pooled_height; ++ph) {\n            int hstart = ph * stride_h() - pad_t();\n            int hend = min(hstart + kernel_h(), height);\n            hstart = max(hstart, 0);\n            float scale = 1. / (hend - hstart);\n            for (int h = hstart; h < hend; ++h) {\n              PoolType::process_grad(\n                  Xdata[h], Ydata[ph], dYdata[ph], scale, dXdata[h]);\n            }\n          }\n          // offset\n          Xdata += height;\n          dXdata += height;\n          Ydata += pooled_height;\n          dYdata += pooled_height;\n        }\n      }\n      break;\n    case 2:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int c = 0; c < channels; ++c) {\n          for (int ph = 0; ph < pooled_height; ++ph) {\n            int hstart = ph * stride_h() - pad_t();\n            int hend = min(hstart + kernel_h(), height);\n            hstart = max(hstart, 0);\n            for (int pw = 0; pw < pooled_width; ++pw) {\n              int wstart = pw * stride_w() - pad_l();\n              int wend = min(wstart + kernel_w(), width);\n              wstart = max(wstart, 0);\n              float scale = 1. / (hend - hstart) / (wend - wstart);\n              const int pooled_index = ph * pooled_width + pw;\n              for (int h = hstart; h < hend; ++h) {\n                for (int w = wstart; w < wend; ++w) {\n                  const int index = h * width + w;\n                  PoolType::process_grad(\n                      Xdata[index],\n                      Ydata[pooled_index],\n                      dYdata[pooled_index],\n                      scale,\n                      dXdata[index]);\n                }\n              }\n            }\n          }\n          // offset\n          Xdata += height * width;\n          dXdata += height * width;\n          Ydata += pooled_height * pooled_width;\n          dYdata += pooled_height * pooled_width;\n        }\n      }\n      break;\n    case 3:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int c = 0; c < channels; ++c) {\n          for (int ph = 0; ph < pooled_height; ++ph) {\n            int hstart = ph * stride_h() - pad_t();\n            int hend = min(hstart + kernel_h(), height);\n            hstart = max(hstart, 0);\n            for (int pw = 0; pw < pooled_width; ++pw) {\n              int wstart = pw * stride_w() - pad_l();\n              int wend = min(wstart + kernel_w(), width);\n              wstart = max(wstart, 0);\n              for (int pd = 0; pd < pooled_depth; ++pd) {\n                int dstart = pd * stride_[2] - pads_[2];\n                int dend = min(dstart + kernel_[2], depth);\n                dstart = max(dstart, 0);\n                float scale =\n                    1. / (hend - hstart) / (wend - wstart) / (dend - dstart);\n                const int pooled_index =\n                    ph * pooled_width * pooled_depth + pw * pooled_depth + pd;\n                for (int h = hstart; h < hend; ++h) {\n                  for (int w = wstart; w < wend; ++w) {\n                    for (int d = dstart; d < dend; ++d) {\n                      const int index = h * width * depth + w * depth + d;\n                      PoolType::process_grad(\n                          Xdata[index],\n                          Ydata[pooled_index],\n                          dYdata[pooled_index],\n                          scale,\n                          dXdata[index]);\n                    }\n                  }\n                }\n              }\n            }\n          }\n          // offset\n          Xdata += height * width * depth;\n          dXdata += height * width * depth;\n          Ydata += pooled_height * pooled_width * pooled_depth;\n          dYdata += pooled_height * pooled_width * pooled_depth;\n        }\n      }\n      break;\n    default:\n      CAFFE_THROW(\"Unsupported pooling size\");\n      return false;\n  }\n  return true;\n}\n\ntemplate <typename T, class Context, typename PoolType>\nbool PoolGradientOp<T, Context, PoolType>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dY = Input(2);\n  DCHECK_EQ(dY.ndim(), kernel_.size() + 2);\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n\n  int channels = X.dim32(X.ndim() - 1);\n  CAFFE_ENFORCE_EQ(channels, dY.dim32(dY.ndim() - 1));\n  ConstEigenArrayMap<T> Ymat(\n      Y.template data<float>(), channels, Y.size() / channels);\n  ConstEigenArrayMap<float> dYmat(\n      dY.template data<float>(), channels, Y.size() / channels);\n  ConstEigenArrayMap<float> Xmat(\n      X.template data<float>(), channels, X.size() / channels);\n  EigenArrayMap<float> dXmat(\n      dX->template mutable_data<float>(), channels, X.size() / channels);\n  dXmat.setZero();\n  int height = X.dim32(1);\n  int width = kernel_.size() > 1 ? X.dim32(2) : 1;\n  int depth = kernel_.size() > 2 ? X.dim32(3) : 1;\n  vector<int> dims(X.dims().begin() + 1, X.dims().end() - 1);\n  ConvPoolOpBase<CPUContext>::ComputePads(dims);\n  int pooled_height = dY.dim32(1);\n  int pooled_width = kernel_.size() > 1 ? dY.dim32(2) : 1;\n  int pooled_depth = kernel_.size() > 2 ? dY.dim32(3) : 1;\n\n  // The main loop\n  // Do not do openmp here: the following for loops are looping over the pooled\n  // output, so if one parallelizes the outer loops, race conditions could\n  // happen in the inner loops.\n  switch (kernel_.size()) {\n    case 1:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int ph = 0; ph < pooled_height; ++ph) {\n          int hstart = ph * stride_h() - pad_t();\n          int hend = min(hstart + kernel_h(), height);\n          hstart = max(hstart, 0);\n          const int pool_index = n * pooled_height + ph;\n          const float scale = 1. / (hend - hstart);\n          for (int h = hstart; h < hend; ++h) {\n            const int input_index = n * height + h;\n            PoolType::process_grad(\n                pool_index, input_index, scale, Xmat, Ymat, dYmat, dXmat);\n          }\n        }\n      }\n      break;\n    case 2:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int ph = 0; ph < pooled_height; ++ph) {\n          int hstart = ph * stride_h() - pad_t();\n          int hend = min(hstart + kernel_h(), height);\n          hstart = max(hstart, 0);\n          for (int pw = 0; pw < pooled_width; ++pw) {\n            int wstart = pw * stride_w() - pad_l();\n            int wend = min(wstart + kernel_w(), width);\n            wstart = max(wstart, 0);\n            const int pool_index = (n * pooled_height + ph) * pooled_width + pw;\n            const float scale = 1. / (hend - hstart) / (wend - wstart);\n            for (int h = hstart; h < hend; ++h) {\n              for (int w = wstart; w < wend; ++w) {\n                const int input_index = (n * height + h) * width + w;\n                PoolType::process_grad(\n                    pool_index, input_index, scale, Xmat, Ymat, dYmat, dXmat);\n              }\n            }\n          }\n        }\n      }\n      break;\n    case 3:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int ph = 0; ph < pooled_height; ++ph) {\n          int hstart = ph * stride_h() - pad_t();\n          int hend = min(hstart + kernel_h(), height);\n          hstart = max(hstart, 0);\n          for (int pw = 0; pw < pooled_width; ++pw) {\n            int wstart = pw * stride_w() - pad_l();\n            int wend = min(wstart + kernel_w(), width);\n            wstart = max(wstart, 0);\n            for (int pd = 0; pd < pooled_depth; ++pd) {\n              int dstart = pd * stride_[2] - pads_[2];\n              int dend = min(dstart + kernel_[2], depth);\n              dstart = max(dstart, 0);\n              const int pool_index =\n                  ((n * pooled_height + ph) * pooled_width + pw) *\n                      pooled_depth +\n                  pd;\n              const float scale =\n                  1. / (hend - hstart) / (wend - wstart) / (dend - dstart);\n              for (int h = hstart; h < hend; ++h) {\n                for (int w = wstart; w < wend; ++w) {\n                  for (int d = dstart; d < dend; ++d) {\n                    const int input_index =\n                        ((n * height + h) * width + w) * depth + d;\n                    PoolType::process_grad(\n                        pool_index,\n                        input_index,\n                        scale,\n                        Xmat,\n                        Ymat,\n                        dYmat,\n                        dXmat);\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n      break;\n    default:\n      CAFFE_THROW(\"Unsupported pooling size\");\n      return false;\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(\n    AveragePoolGradient,\n    PoolGradientOp<float, CPUContext, AveragePool<float>>);\nOPERATOR_SCHEMA(AveragePoolGradient).NumInputs(3).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(\n    AveragePool1DGradient,\n    PoolGradientOp<float, CPUContext, AveragePool<float>>);\nOPERATOR_SCHEMA(AveragePool1DGradient).NumInputs(3).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(\n    AveragePool2DGradient,\n    PoolGradientOp<float, CPUContext, AveragePool<float>>);\nOPERATOR_SCHEMA(AveragePool2DGradient).NumInputs(3).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(\n    AveragePool3DGradient,\n    PoolGradientOp<float, CPUContext, AveragePool<float>>);\nOPERATOR_SCHEMA(AveragePool3DGradient).NumInputs(3).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(\n    MaxPoolGradient,\n    PoolGradientOp<float, CPUContext, MaxPool<float>>);\nOPERATOR_SCHEMA(MaxPoolGradient).NumInputs(3).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(\n    MaxPool1DGradient,\n    PoolGradientOp<float, CPUContext, MaxPool<float>>);\nOPERATOR_SCHEMA(MaxPool1DGradient).NumInputs(3).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(\n    MaxPool2DGradient,\n    PoolGradientOp<float, CPUContext, MaxPool<float>>);\nOPERATOR_SCHEMA(MaxPool2DGradient).NumInputs(3).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(\n    MaxPool3DGradient,\n    PoolGradientOp<float, CPUContext, MaxPool<float>>);\nOPERATOR_SCHEMA(MaxPool3DGradient).NumInputs(3).NumOutputs(1);\n\nclass GetPoolGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        def_.type() + \"Gradient\",\n        \"\",\n        vector<string>{I(0), O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(AveragePool, GetPoolGradient);\nREGISTER_GRADIENT(AveragePool1D, GetPoolGradient);\nREGISTER_GRADIENT(AveragePool2D, GetPoolGradient);\nREGISTER_GRADIENT(AveragePool3D, GetPoolGradient);\nREGISTER_GRADIENT(MaxPool, GetPoolGradient);\nREGISTER_GRADIENT(MaxPool1D, GetPoolGradient);\nREGISTER_GRADIENT(MaxPool2D, GetPoolGradient);\nREGISTER_GRADIENT(MaxPool3D, GetPoolGradient);\n}\n"
  },
  {
    "path": "caffe2/operators/pool_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// TODO(ataei): reduce the apparent redundancy of all the code below.\n#include \"caffe2/operators/pool_op.h\"\n#include \"caffe2/utils/cpu_neon.h\"\n\nnamespace caffe2 {\n\nusing std::max;\nusing std::min;\n\nnamespace {\n\n#ifdef __ARM_NEON__\n\nbool isNeon4x4p0s0Eligible(\n    int inputH,\n    int inputW,\n    int outputH,\n    int outputW,\n    int kH,\n    int kW,\n    int strideH,\n    int strideW,\n    int padT,\n    int padL,\n    int padB,\n    int padR,\n    int dilationH,\n    int dilationW,\n    const float* input,\n    float* output) {\n  // Use this kernel only if:\n  // Kernel width is 4x4\n  // Kernel stride is 4x4\n  // Padding is 0\n  // Dilation is 1\n  // Output width and height are even divisors of input width\n  // Input width and height are divisible by 4 (should be implied by\n  // all of the above, but just check again)\n  // Input and output pointers are aligned by float32x4_t\n\n  bool kernelOk = (kH == 4) && (kW == 4);\n  bool strideOk = (strideH == 4) && (strideW == 4);\n  bool padOk = (padT == 0) && (padL == 0) && (padB == 0) && (padR == 0);\n  bool dilationOk = (dilationH == 1) && (dilationW == 1);\n\n  bool outputOk = ((inputH % outputH) == 0) && ((inputW % outputW) == 0);\n  bool inputOk = (inputW % 4 == 0) && (inputH % 4 == 0);\n  bool alignOk = isPointerAligned(input, sizeof(float32x4_t)) &&\n      isPointerAligned(output, sizeof(float32x4_t));\n\n  return kernelOk && strideOk && padOk && dilationOk && outputOk && inputOk &&\n      alignOk;\n}\n\n// Vectorizes 4x4p0s0 averge pooling for ARM NEON\nvoid avgPoolNeon4x4p0s0Plane(\n    int inputH,\n    int inputW,\n    const float* input,\n    float* output) {\n  constexpr int kKernelHeight = 4;\n  constexpr int kKernelWidth = 4;\n  constexpr float kDiv = (1.0f / ((float)kKernelHeight * (float)kKernelWidth));\n\n  // Handle portion that can be unrolled by 4\n  constexpr int kUnroll = 4;\n  constexpr int kLoadSizeFloat = (sizeof(float32x4_t) / sizeof(float));\n  constexpr int kLoadCols = kUnroll * kLoadSizeFloat;\n\n  if (inputW % kLoadCols == 0) {\n    //\n    // Manually unroll by 4 (kUnroll)\n    //\n\n    for (int h = 0; h < inputH; h += kKernelHeight) {\n      float* outputRow = output + (h / kKernelHeight) * (inputW / kKernelWidth);\n      const float* curInput = input + h * inputW;\n\n      for (int w = 0; w < inputW; w += kLoadCols) {\n        float32x4_t out = {};\n\n        {\n          float32x4_t v0_0 = vld1q_f32_aligned(curInput + 0 * inputW);\n          float32x4_t v0_1 = vld1q_f32_aligned(curInput + 1 * inputW);\n          float32x4_t v0_2 = vld1q_f32_aligned(curInput + 2 * inputW);\n          float32x4_t v0_3 = vld1q_f32_aligned(curInput + 3 * inputW);\n          float v0 = horizontal_sum_f32(v0_0, v0_1, v0_2, v0_3);\n          out = vsetq_lane_f32(v0, out, 0);\n        }\n        curInput += kLoadSizeFloat;\n\n        {\n          float32x4_t v0_0 = vld1q_f32_aligned(curInput + 0 * inputW);\n          float32x4_t v0_1 = vld1q_f32_aligned(curInput + 1 * inputW);\n          float32x4_t v0_2 = vld1q_f32_aligned(curInput + 2 * inputW);\n          float32x4_t v0_3 = vld1q_f32_aligned(curInput + 3 * inputW);\n          float v0 = horizontal_sum_f32(v0_0, v0_1, v0_2, v0_3);\n          out = vsetq_lane_f32(v0, out, 1);\n        }\n        curInput += kLoadSizeFloat;\n\n        {\n          float32x4_t v0_0 = vld1q_f32_aligned(curInput + 0 * inputW);\n          float32x4_t v0_1 = vld1q_f32_aligned(curInput + 1 * inputW);\n          float32x4_t v0_2 = vld1q_f32_aligned(curInput + 2 * inputW);\n          float32x4_t v0_3 = vld1q_f32_aligned(curInput + 3 * inputW);\n          float v0 = horizontal_sum_f32(v0_0, v0_1, v0_2, v0_3);\n          out = vsetq_lane_f32(v0, out, 2);\n        }\n        curInput += kLoadSizeFloat;\n\n        {\n          float32x4_t v0_0 = vld1q_f32_aligned(curInput + 0 * inputW);\n          float32x4_t v0_1 = vld1q_f32_aligned(curInput + 1 * inputW);\n          float32x4_t v0_2 = vld1q_f32_aligned(curInput + 2 * inputW);\n          float32x4_t v0_3 = vld1q_f32_aligned(curInput + 3 * inputW);\n          float v0 = horizontal_sum_f32(v0_0, v0_1, v0_2, v0_3);\n          out = vsetq_lane_f32(v0, out, 3);\n        }\n        curInput += kLoadSizeFloat;\n\n        out = vmulq_f32(out, vdupq_n_f32(kDiv));\n        vst1q_f32_aligned(&outputRow[w / kKernelWidth], out);\n      }\n    }\n  } else {\n    //\n    // Not unrolled\n    //\n\n    for (int h = 0; h < inputH; h += kKernelHeight) {\n      const float* inputRow = input + h * inputW;\n      float* outputRow = output + (h / kKernelHeight) * (inputW / kKernelWidth);\n\n      for (int w = 0; w < inputW; w += kKernelWidth) {\n        const float* curInput = inputRow + w;\n\n        float32x4_t v0_0 = vld1q_f32_aligned(curInput + 0 * inputW);\n        float32x4_t v0_1 = vld1q_f32_aligned(curInput + 1 * inputW);\n        float32x4_t v0_2 = vld1q_f32_aligned(curInput + 2 * inputW);\n        float32x4_t v0_3 = vld1q_f32_aligned(curInput + 3 * inputW);\n        float v0 = horizontal_sum_f32(v0_0, v0_1, v0_2, v0_3) * kDiv;\n        outputRow[w / kKernelWidth] = v0;\n      }\n    }\n  }\n}\n\nvoid runNeonAveragePool4x4p0s0NCHW(\n    int N,\n    int C,\n    int inputH,\n    int inputW,\n    const float* input,\n    float* output) {\n  // We only have the 4x4p0s0 implementation at present, which is\n  // checked at a higher level\n  int outputH = inputH / 4;\n  int outputW = inputW / 4;\n\n  for (int n = 0; n < N; ++n) {\n    for (int c = 0; c < C; ++c) {\n      const float* curInput = input + (n * C + c) * inputH * inputW;\n      float* curOutput = output + (n * C + c) * outputH * outputW;\n\n      avgPoolNeon4x4p0s0Plane(inputH, inputW, curInput, curOutput);\n    }\n  }\n}\n\nbool isNeon2x2p0s0Eligible(\n    int inputH,\n    int inputW,\n    int outputH,\n    int outputW,\n    int kH,\n    int kW,\n    int strideH,\n    int strideW,\n    int padT,\n    int padL,\n    int padB,\n    int padR,\n    int dilationH,\n    int dilationW,\n    const float* input,\n    float* output) {\n  // Use this kernel only if:\n  // Kernel width is 2x2\n  // Kernel stride is 2x2\n  // Padding is 0\n  // Dilation is 1\n  // Output width and height are even divisors of input width\n  // Input width and height are divisible by 4 (should be implied by\n  // all of the above, but just check again)\n  // Input and output pointers are aligned by float32x4_t\n\n  bool kernelOk = (kH == 2) && (kW == 2);\n  bool strideOk = (strideH == 2) && (strideW == 2);\n  bool padOk = (padT == 0) && (padL == 0) && (padB == 0) && (padR == 0);\n  bool dilationOk = (dilationH == 1) && (dilationW == 1);\n\n  bool outputOk = ((inputH % outputH) == 0) && ((inputW % outputW) == 0);\n  bool inputOk = (inputW % 4 == 0) && (inputH % 4 == 0);\n  bool alignOk = isPointerAligned(input, sizeof(float32x4_t)) &&\n      isPointerAligned(output, sizeof(float32x4_t));\n\n  return kernelOk && strideOk && padOk && dilationOk && outputOk && inputOk &&\n      alignOk;\n}\n\n// Vectorizes 2x2p0s0 averge pooling for ARM NEON\nvoid maxPoolNeon2x2p0s0Plane(\n    int inputH,\n    int inputW,\n    const float* input,\n    float* output) {\n  constexpr int kKernelHeight = 2;\n  constexpr int kKernelWidth = 2;\n\n  // Handle portion that can be unrolled by 4\n  constexpr int kUnroll = 4;\n  constexpr int kLoadSizeFloat = (sizeof(float32x4_t) / sizeof(float));\n  constexpr int kLoadCols = kUnroll * kLoadSizeFloat;\n\n  if (inputW % kLoadCols == 0) {\n    for (int h = 0; h < inputH; h += kKernelHeight) {\n      float* outputRow = output + (h / kKernelHeight) * (inputW / kKernelWidth);\n      const float* curInput = input + h * inputW;\n\n      for (int w = 0; w < inputW; w += kLoadCols) {\n        float32x2_t hmax_0, hmax_1, hmax_2, hmax_3;\n        {\n          float32x4_t v0_0 = vld1q_f32_aligned(curInput + 0 * inputW);\n          float32x4_t v0_1 = vld1q_f32_aligned(curInput + 1 * inputW);\n          float32x4_t vmax = vmaxq_f32(v0_0, v0_1);\n          hmax_0 = vpmax_f32(vget_low_f32(vmax), vget_high_f32(vmax));\n        }\n        curInput += kLoadSizeFloat;\n        {\n          float32x4_t v0_0 = vld1q_f32_aligned(curInput + 0 * inputW);\n          float32x4_t v0_1 = vld1q_f32_aligned(curInput + 1 * inputW);\n          float32x4_t vmax = vmaxq_f32(v0_0, v0_1);\n          hmax_1 = vpmax_f32(vget_low_f32(vmax), vget_high_f32(vmax));\n        }\n        curInput += kLoadSizeFloat;\n        {\n          float32x4_t v0_0 = vld1q_f32_aligned(curInput + 0 * inputW);\n          float32x4_t v0_1 = vld1q_f32_aligned(curInput + 1 * inputW);\n          float32x4_t vmax = vmaxq_f32(v0_0, v0_1);\n          hmax_2 = vpmax_f32(vget_low_f32(vmax), vget_high_f32(vmax));\n        }\n        curInput += kLoadSizeFloat;\n        {\n          float32x4_t v0_0 = vld1q_f32_aligned(curInput + 0 * inputW);\n          float32x4_t v0_1 = vld1q_f32_aligned(curInput + 1 * inputW);\n          float32x4_t vmax = vmaxq_f32(v0_0, v0_1);\n          hmax_3 = vpmax_f32(vget_low_f32(vmax), vget_high_f32(vmax));\n        }\n        curInput += kLoadSizeFloat;\n\n        float32x4_t out_0 = vcombine_f32(hmax_0, hmax_1);\n        float32x4_t out_1 = vcombine_f32(hmax_2, hmax_3);\n        vst1q_f32_aligned(&outputRow[w / kKernelWidth + 0], out_0);\n        vst1q_f32_aligned(&outputRow[w / kKernelWidth + 4], out_1);\n      }\n    }\n  } else {\n    // Not unrolled\n    for (int h = 0; h < inputH; h += kKernelHeight) {\n      const float* inputRow = input + h * inputW;\n      float* outputRow = output + (h / kKernelHeight) * (inputW / kKernelWidth);\n\n      for (int w = 0; w < inputW; w += kKernelWidth * 2) {\n        const float* curInput = inputRow + w;\n        float32x4_t v0_0 = vld1q_f32_aligned(curInput + 0 * inputW);\n        float32x4_t v0_1 = vld1q_f32_aligned(curInput + 1 * inputW);\n        float32x4_t vmax = vmaxq_f32(v0_0, v0_1);\n        float32x2_t hmax = vpmax_f32(vget_low_f32(vmax), vget_high_f32(vmax));\n        vst1_f32(&outputRow[w / kKernelWidth], hmax);\n      }\n    }\n  }\n}\n\nvoid runNeonMaxPool2x2p0s0NCHW(\n    int N,\n    int C,\n    int inputH,\n    int inputW,\n    const float* input,\n    float* output) {\n  // We only have the 2x2p0s0 implementation at present, which is\n  // checked at a higher level\n  int outputH = inputH / 2;\n  int outputW = inputW / 2;\n\n  for (int n = 0; n < N; ++n) {\n    for (int c = 0; c < C; ++c) {\n      const float* curInput = input + (n * C + c) * inputH * inputW;\n      float* curOutput = output + (n * C + c) * outputH * outputW;\n      maxPoolNeon2x2p0s0Plane(inputH, inputW, curInput, curOutput);\n    }\n  }\n}\n#endif // __ARM_NEON__\n\n} // namespace\n\ntemplate <typename T>\nclass AveragePool {\n public:\n  static float initialize() {\n    return 0.0;\n  }\n\n  static void process(\n      const int x_col,\n      const int y_col,\n      ConstEigenMatrixMap<float>& x_mat,\n      EigenMatrixMap<float>& y_mat) {\n    y_mat.col(y_col) += x_mat.col(x_col);\n  }\n\n  static void process(const T& x_data, T& y_data) {\n    y_data += x_data;\n  }\n\n  static void finalize(const int size, T& y_data) {\n    y_data /= size;\n  }\n\n  static void\n  finalize(const int size, const int col, EigenMatrixMap<float>& y_mat) {\n    y_mat.col(col) /= size;\n  }\n\n  static bool runSpecialized(\n      int N,\n      int C,\n      int inputH,\n      int inputW,\n      int outputH,\n      int outputW,\n      int kH,\n      int kW,\n      int strideH,\n      int strideW,\n      int padT,\n      int padL,\n      int padB,\n      int padR,\n      int dilationH,\n      int dilationW,\n      const float* input,\n      float* output) {\n#ifdef __ARM_NEON__\n    if (isNeon4x4p0s0Eligible(\n            inputH,\n            inputW,\n            outputH,\n            outputW,\n            kH,\n            kW,\n            strideH,\n            strideW,\n            padT,\n            padL,\n            padB,\n            padR,\n            dilationH,\n            dilationW,\n            input,\n            output)) {\n      runNeonAveragePool4x4p0s0NCHW(N, C, inputH, inputW, input, output);\n      return true;\n    }\n#else\n    (void)N;\n    (void)C;\n    (void)inputH;\n    (void)inputW;\n    (void)outputH;\n    (void)outputW;\n    (void)kH;\n    (void)kW;\n    (void)strideH;\n    (void)strideW;\n    (void)padT;\n    (void)padL;\n    (void)padB;\n    (void)padR;\n    (void)dilationH;\n    (void)dilationW;\n    (void)input;\n    (void)output;\n#endif\n    return false;\n  }\n};\n\ntemplate <typename T>\nclass MaxPool {\n public:\n  static float initialize() {\n    return std::numeric_limits<float>::lowest();\n  }\n\n  static void process(\n      const int x_col,\n      const int y_col,\n      ConstEigenMatrixMap<float>& x_mat,\n      EigenMatrixMap<float>& y_mat) {\n    y_mat.col(y_col) = y_mat.col(y_col).cwiseMax(x_mat.col(x_col));\n  }\n\n  static void process(const T& x_data, T& y_data) {\n    if (x_data > y_data) {\n      y_data = x_data;\n    }\n  }\n\n  static void finalize(const int /*size*/, T& /*y_data*/) {}\n\n  static void finalize(\n      const int /*size*/,\n      const int /*col*/,\n      EigenMatrixMap<float>& /*y_mat*/) {}\n\n  static bool runSpecialized(\n      int N,\n      int C,\n      int inputH,\n      int inputW,\n      int outputH,\n      int outputW,\n      int kH,\n      int kW,\n      int strideH,\n      int strideW,\n      int padT,\n      int padL,\n      int padB,\n      int padR,\n      int dilationH,\n      int dilationW,\n      const float* input,\n      float* output) {\n#ifdef __ARM_NEON__\n    if (isNeon2x2p0s0Eligible(\n            inputH,\n            inputW,\n            outputH,\n            outputW,\n            kH,\n            kW,\n            strideH,\n            strideW,\n            padT,\n            padL,\n            padB,\n            padR,\n            dilationH,\n            dilationW,\n            input,\n            output)) {\n      runNeonMaxPool2x2p0s0NCHW(N, C, inputH, inputW, input, output);\n      return true;\n    }\n#else\n    (void)N;\n    (void)C;\n    (void)inputH;\n    (void)inputW;\n    (void)outputH;\n    (void)outputW;\n    (void)kH;\n    (void)kW;\n    (void)strideH;\n    (void)strideW;\n    (void)padT;\n    (void)padL;\n    (void)padB;\n    (void)padR;\n    (void)dilationH;\n    (void)dilationW;\n    (void)input;\n    (void)output;\n#endif\n    return false;\n  }\n};\n\ntemplate <typename T, class Context, typename PoolType>\nbool PoolOp<T, Context, PoolType>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  ConvPoolOpBase<Context>::SetOutputSize(X, Y, X.dim32(1));\n\n  const float* Xdata = X.template data<float>();\n  float* Ydata = Y->template mutable_data<float>();\n  // The main loop\n  int channels = X.dim32(1);\n  int height = X.dim32(2);\n  int width = kernel_.size() > 1 ? X.dim32(3) : 1;\n  int depth = kernel_.size() > 2 ? X.dim32(4) : 1;\n  int pooled_height = Y->dim32(2);\n  int pooled_width = kernel_.size() > 1 ? Y->dim32(3) : 1;\n  int pooled_depth = kernel_.size() > 2 ? Y->dim32(4) : 1;\n\n  // We specialize certain variants on ARM for vectorization\n  if (kernel_.size() == 2 &&\n      PoolType::runSpecialized(\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          Y->dim32(2),\n          Y->dim32(3),\n          kernel_h(),\n          kernel_w(),\n          stride_h(),\n          stride_w(),\n          pad_t(),\n          pad_l(),\n          pad_b(),\n          pad_r(),\n          dilation_h(),\n          dilation_w(),\n          Xdata,\n          Ydata)) {\n    return true;\n  }\n\n  switch (kernel_.size()) {\n    case 1:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int c = 0; c < channels; ++c) {\n          for (int ph = 0; ph < pooled_height; ++ph) {\n            int hstart = ph * stride_h() - pad_t();\n            int hend = min(hstart + kernel_h(), height);\n            hstart = max(hstart, 0);\n            T Yh = PoolType::initialize();\n            for (int h = hstart; h < hend; ++h) {\n              PoolType::process(Xdata[h], Yh);\n            }\n            PoolType::finalize(hend - hstart, Yh);\n            Ydata[ph] = Yh;\n          }\n          // Do offset.\n          Xdata += height;\n          Ydata += pooled_height;\n        }\n      }\n      break;\n    case 2:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int c = 0; c < channels; ++c) {\n          for (int ph = 0; ph < pooled_height; ++ph) {\n            int hstart = ph * stride_h() - pad_t();\n            int hend = min(hstart + kernel_h(), height);\n            hstart = max(hstart, 0);\n            for (int pw = 0; pw < pooled_width; ++pw) {\n              int wstart = pw * stride_w() - pad_l();\n              int wend = min(wstart + kernel_w(), width);\n              wstart = max(wstart, 0);\n              const int pool_index = ph * pooled_width + pw;\n              T Yh = PoolType::initialize();\n              for (int h = hstart; h < hend; ++h) {\n                for (int w = wstart; w < wend; ++w) {\n                  const int input_index = h * width + w;\n                  PoolType::process(Xdata[input_index], Yh);\n                }\n              }\n              PoolType::finalize((hend - hstart) * (wend - wstart), Yh);\n              Ydata[pool_index] = Yh;\n            }\n          }\n          // Do offset.\n          Xdata += height * width;\n          Ydata += pooled_height * pooled_width;\n        }\n      }\n      break;\n    case 3:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int c = 0; c < channels; ++c) {\n          for (int ph = 0; ph < pooled_height; ++ph) {\n            int hstart = ph * stride_h() - pad_t();\n            int hend = min(hstart + kernel_h(), height);\n            hstart = max(hstart, 0);\n            for (int pw = 0; pw < pooled_width; ++pw) {\n              int wstart = pw * stride_w() - pad_l();\n              int wend = min(wstart + kernel_w(), width);\n              wstart = max(wstart, 0);\n              for (int pd = 0; pd < pooled_depth; ++pd) {\n                int dstart = pd * stride_[2] - pads_[2];\n                int dend = min(dstart + kernel_[2], depth);\n                dstart = max(dstart, 0);\n                const int pool_index =\n                    ph * pooled_width * pooled_depth + pw * pooled_depth + pd;\n                T Yh = PoolType::initialize();\n                for (int h = hstart; h < hend; ++h) {\n                  for (int w = wstart; w < wend; ++w) {\n                    for (int d = dstart; d < dend; ++d) {\n                      const int input_index = h * width * depth + w * depth + d;\n                      PoolType::process(Xdata[input_index], Yh);\n                    }\n                  }\n                }\n                PoolType::finalize(\n                    (hend - hstart) * (wend - wstart) * (dend - dstart), Yh);\n                Ydata[pool_index] = Yh;\n              }\n            }\n          }\n          // Do offset.\n          Xdata += height * width * depth;\n          Ydata += pooled_height * pooled_width * pooled_depth;\n        }\n      }\n      break;\n    default:\n      CAFFE_THROW(\"Unsupported pooling size : \", kernel_.size());\n      return false;\n  }\n  return true;\n}\n\ntemplate <typename T, class Context, typename PoolType>\nbool PoolOp<T, Context, PoolType>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  int height = X.dim32(1);\n  int width = kernel_.size() > 1 ? X.dim32(2) : 1;\n  int depth = kernel_.size() > 2 ? X.dim32(3) : 1;\n  int channels = X.dim32(X.ndim() - 1);\n  ConvPoolOpBase<Context>::SetOutputSize(X, Y, channels);\n\n  EigenMatrixMap<float> Ymat(\n      Y->template mutable_data<float>(), channels, Y->size() / channels);\n  ConstEigenMatrixMap<float> Xmat(\n      X.template data<float>(), channels, X.size() / channels);\n  int pooled_height = Y->dim32(1);\n  int pooled_width = kernel_.size() > 1 ? Y->dim32(2) : 1;\n  int pooled_depth = kernel_.size() > 2 ? Y->dim32(3) : 1;\n  // The main loop\n  switch (kernel_.size()) {\n    case 1:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int ph = 0; ph < pooled_height; ++ph) {\n          int hstart = ph * stride_h() - pad_t();\n          int hend = min(hstart + kernel_h(), height);\n          hstart = max(hstart, 0);\n          const int y_col = n * pooled_height + ph;\n          Ymat.col(y_col).setConstant(PoolType::initialize());\n          for (int h = hstart; h < hend; ++h) {\n            const int x_col = n * height + h;\n            PoolType::process(x_col, y_col, Xmat, Ymat);\n          }\n          PoolType::finalize((hend - hstart), y_col, Ymat);\n        }\n      }\n      break;\n    case 2:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int ph = 0; ph < pooled_height; ++ph) {\n          int hstart = ph * stride_h() - pad_t();\n          int hend = min(hstart + kernel_h(), height);\n          hstart = max(hstart, 0);\n          for (int pw = 0; pw < pooled_width; ++pw) {\n            int wstart = pw * stride_w() - pad_l();\n            int wend = min(wstart + kernel_w(), width);\n            wstart = max(wstart, 0);\n            const int y_col = (n * pooled_height + ph) * pooled_width + pw;\n            Ymat.col(y_col).setConstant(PoolType::initialize());\n            for (int h = hstart; h < hend; ++h) {\n              for (int w = wstart; w < wend; ++w) {\n                const int x_col = (n * height + h) * width + w;\n                PoolType::process(x_col, y_col, Xmat, Ymat);\n              }\n            }\n            PoolType::finalize((hend - hstart) * (wend - wstart), y_col, Ymat);\n          }\n        }\n      }\n      break;\n    case 3:\n      for (int n = 0; n < X.dim32(0); ++n) {\n        for (int ph = 0; ph < pooled_height; ++ph) {\n          int hstart = ph * stride_h() - pad_t();\n          int hend = min(hstart + kernel_h(), height);\n          hstart = max(hstart, 0);\n          for (int pw = 0; pw < pooled_width; ++pw) {\n            int wstart = pw * stride_w() - pad_l();\n            int wend = min(wstart + kernel_w(), width);\n            wstart = max(wstart, 0);\n            for (int pd = 0; pd < pooled_depth; ++pd) {\n              int dstart = pd * stride_[2] - pads_[2];\n              int dend = min(dstart + kernel_[2], depth);\n              dstart = max(dstart, 0);\n              const int y_col = ((n * pooled_height + ph) * pooled_width + pw) *\n                      pooled_depth +\n                  pd;\n              Ymat.col(y_col).setConstant(PoolType::initialize());\n              for (int h = hstart; h < hend; ++h) {\n                for (int w = wstart; w < wend; ++w) {\n                  for (int d = dstart; d < dend; ++d) {\n                    const int x_col =\n                        ((n * height + h) * width + w) * depth + d;\n                    PoolType::process(x_col, y_col, Xmat, Ymat);\n                  }\n                }\n              }\n              PoolType::finalize(\n                  (hend - hstart) * (wend - wstart) * (dend - dstart),\n                  y_col,\n                  Ymat);\n            }\n          }\n        }\n      }\n      break;\n    default:\n      CAFFE_THROW(\"Unsupported pooling size : \", kernel_.size());\n      return false;\n  }\n  return true;\n}\nconst char* kAveragePoolDoc = R\"DOC(\nconsumes an input blob X and applies average pooling across the\nthe blob according to kernel sizes, stride sizes, and pad lengths defined by the\nConvPoolOpBase operator. Average pooling consisting of averaging all values of a\nsubset of the input tensor according to the kernel size and downsampling the\ndata into the output blob Y for further processing.\n)DOC\";\n\nconst char* kMaxPoolDoc = R\"DOC(\nconsumes an input blob X and applies max pooling across the\nthe blob according to kernel sizes, stride sizes, and pad lengths defined by the\nConvPoolOpBase operator. Max pooling consisting of taking the maximum value of a\nsubset of the input tensor according to the kernel size and downsampling the\ndata into the output blob Y for further processing.\n)DOC\";\n\nstd::function<void(OpSchema&)> AveragePoolDocGenerator(const char* dim) {\n  return [=](OpSchema& schema) {\n    string doc = \"AveragePool{dim} {pool_doc}\";\n    ReplaceAll(doc, \"{dim}\", dim);\n    ReplaceAll(doc, \"{pool_doc}\", kAveragePoolDoc);\n    schema.SetDoc(doc);\n    schema.Input(\n        0,\n        \"X\",\n        \"Input data tensor from the previous operator; dimensions depend on \"\n        \"whether the NCHW or NHWC operators are being used. For example, in \"\n        \"the former, the input has size (N x C x H x W), where N is the batch \"\n        \"size, C is the number of channels, and H and W are the height and the \"\n        \"width of the data. The corresponding permutation of dimensions is \"\n        \"used in the latter case.\");\n    schema.Output(\n        0,\n        \"Y\",\n        \"Output data tensor from average pooling across the input \"\n        \"tensor. Dimensions will vary based on various kernel, stride, and pad \"\n        \"sizes.\");\n  };\n}\n\nstd::function<void(OpSchema&)> MaxPoolDocGenerator(const char* dim) {\n  return [=](OpSchema& schema) {\n    string doc = \"MaxPool{dim} {pool_doc}\";\n    ReplaceAll(doc, \"{dim}\", dim);\n    ReplaceAll(doc, \"{pool_doc}\", kMaxPoolDoc);\n    schema.SetDoc(doc);\n    schema.Input(\n        0,\n        \"X\",\n        \"Input data tensor from the previous operator; dimensions depend on \"\n        \"whether the NCHW or NHWC operators are being used. For example, in \"\n        \"the former, the input has size (N x C x H x W), where N is the batch \"\n        \"size, C is the number of channels, and H and W are the height and the \"\n        \"width of the data. The corresponding permutation of dimensions is \"\n        \"used in the latter case.\");\n    schema.Output(\n        0,\n        \"Y\",\n        \"Output data tensor from max pooling across the input \"\n        \"tensor. Dimensions will vary based on various kernel, stride, and pad \"\n        \"sizes.\");\n  };\n}\nREGISTER_CPU_OPERATOR(\n    AveragePool,\n    PoolOp<float, CPUContext, AveragePool<float>>);\n\nOPERATOR_SCHEMA(AveragePool)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForPool)\n    .FillUsing(AveragePoolDocGenerator(\"\"));\n\nREGISTER_CPU_OPERATOR(\n    AveragePool1D,\n    PoolOp<float, CPUContext, AveragePool<float>>);\n\nOPERATOR_SCHEMA(AveragePool1D)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForPool)\n    .FillUsing(AveragePoolDocGenerator(\"1D\"));\n\nREGISTER_CPU_OPERATOR(\n    AveragePool2D,\n    PoolOp<float, CPUContext, AveragePool<float>>);\n\nOPERATOR_SCHEMA(AveragePool2D)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForPool)\n    .FillUsing(AveragePoolDocGenerator(\"2D\"));\n\nREGISTER_CPU_OPERATOR(\n    AveragePool3D,\n    PoolOp<float, CPUContext, AveragePool<float>>);\n\nOPERATOR_SCHEMA(AveragePool3D)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForPool)\n    .FillUsing(AveragePoolDocGenerator(\"3D\"));\n\nREGISTER_CPU_OPERATOR(MaxPool, PoolOp<float, CPUContext, MaxPool<float>>);\n\nOPERATOR_SCHEMA(MaxPool)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForPool)\n    .FillUsing(MaxPoolDocGenerator(\"\"));\n\nREGISTER_CPU_OPERATOR(MaxPool1D, PoolOp<float, CPUContext, MaxPool<float>>);\n\nOPERATOR_SCHEMA(MaxPool1D)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForPool)\n    .FillUsing(MaxPoolDocGenerator(\"1D\"));\n\nREGISTER_CPU_OPERATOR(MaxPool2D, PoolOp<float, CPUContext, MaxPool<float>>);\n\nOPERATOR_SCHEMA(MaxPool2D)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForPool)\n    .FillUsing(MaxPoolDocGenerator(\"2D\"));\n\nREGISTER_CPU_OPERATOR(MaxPool3D, PoolOp<float, CPUContext, MaxPool<float>>);\n\nOPERATOR_SCHEMA(MaxPool3D)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction(ConvPoolOpBase<CPUContext>::TensorInferenceForPool)\n    .FillUsing(MaxPoolDocGenerator(\"3D\"));\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/pool_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// TODO(ataei): reduce the apparent redundancy of all the code below.\n#include <cfloat>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/pool_op.h\"\n\nnamespace caffe2 {\nnamespace {\nclass AveragePool {};\nclass MaxPool {};\n}  // namespace\n\nnamespace {\ntemplate <typename T>\n__global__ void Average1DPoolForwardNCHW(\n    const int nthreads,\n    const T* bottom_data,\n    const int num,\n    const int channels,\n    const int height,\n    const int pooled_height,\n    const int kernel_h,\n    const int stride_h,\n    const int pad_t,\n    T* top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index;\n    int ph = n % pooled_height;\n    n /= pooled_height;\n    int c = n % channels;\n    n /= channels;\n    int hstart = ph * stride_h - pad_t;\n    int hend = min(hstart + kernel_h, height);\n    hstart = max(hstart, 0);\n    top_data[index] = 0;\n    int bottom_offset = (n * channels + c) * height;\n    for (int h = hstart; h < hend; ++h) {\n      top_data[index] += bottom_data[bottom_offset + h];\n    }\n    top_data[index] /= (hend - hstart);\n  }\n}\n\ntemplate <typename T>\n__global__ void Average2DPoolForwardNCHW(\n    const int nthreads,\n    const T* bottom_data,\n    const int num,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l,\n    T* top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index;\n    int pw = n % pooled_width;\n    n /= pooled_width;\n    int ph = n % pooled_height;\n    n /= pooled_height;\n    int c = n % channels;\n    n /= channels;\n    int hstart = ph * stride_h - pad_t;\n    int wstart = pw * stride_w - pad_l;\n    int hend = min(hstart + kernel_h, height);\n    int wend = min(wstart + kernel_w, width);\n    hstart = max(hstart, 0);\n    wstart = max(wstart, 0);\n    top_data[index] = 0;\n    int bottom_offset = (n * channels + c) * height * width;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        top_data[index] += bottom_data[bottom_offset + h * width + w];\n      }\n    }\n    top_data[index] /= (hend - hstart) * (wend - wstart);\n  }\n}\n\ntemplate <typename T>\n__global__ void Average3DPoolForwardNCHW(\n    const int nthreads,\n    const T* bottom_data,\n    const int num,\n    const int channels,\n    const int height,\n    const int width,\n    const int depth,\n    const int pooled_height,\n    const int pooled_width,\n    const int pooled_depth,\n    const int kernel_h,\n    const int kernel_w,\n    const int kernel_d,\n    const int stride_h,\n    const int stride_w,\n    const int stride_d,\n    const int pad_t,\n    const int pad_l,\n    const int pad_f,\n    T* top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index;\n    int pd = n % pooled_depth;\n    n /= pooled_depth;\n    int pw = n % pooled_width;\n    n /= pooled_width;\n    int ph = n % pooled_height;\n    n /= pooled_height;\n    int c = n % channels;\n    n /= channels;\n    int hstart = ph * stride_h - pad_t;\n    int wstart = pw * stride_w - pad_l;\n    int dstart = pd * stride_d - pad_f;\n    int hend = min(hstart + kernel_h, height);\n    int wend = min(wstart + kernel_w, width);\n    int dend = min(dstart + kernel_d, depth);\n    hstart = max(hstart, 0);\n    wstart = max(wstart, 0);\n    dstart = max(dstart, 0);\n    top_data[index] = 0;\n    int bottom_offset = (n * channels + c) * height * width * depth;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        for (int d = dstart; d < dend; ++d) {\n          const int input_index =\n              bottom_offset + h * width * depth + w * depth + d;\n          top_data[index] += bottom_data[input_index];\n        }\n      }\n    }\n    top_data[index] /= (hend - hstart) * (wend - wstart) * (dend - dstart);\n  }\n}\n\ntemplate <typename T>\n__global__ void Average1DPoolForwardNHWC(\n    const int nthreads,\n    const T* bottom_data,\n    const int num,\n    const int height,\n    const int channels,\n    const int pooled_height,\n    const int kernel_h,\n    const int stride_h,\n    const int pad_t,\n    T* top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int c = index % channels;\n    int ph = (index / channels) % pooled_height;\n    int n = index / channels / pooled_height;\n    int hstart = ph * stride_h - pad_t;\n    int hend = min(hstart + kernel_h, height);\n    hstart = max(hstart, 0);\n    T output = 0;\n    int bottom_offset = n * height * channels + c;\n    for (int h = hstart; h < hend; ++h) {\n      output += bottom_data[bottom_offset + h * channels];\n    }\n    int pool_size = (hend - hstart);\n    top_data[index] = output / pool_size;\n  }\n}\n\ntemplate <typename T>\n__global__ void Average2DPoolForwardNHWC(\n    const int nthreads,\n    const T* bottom_data,\n    const int num,\n    const int height,\n    const int width,\n    const int channels,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l,\n    T* top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int c = index % channels;\n    int pw = (index / channels) % pooled_width;\n    int ph = (index / channels / pooled_width) % pooled_height;\n    int n = index / channels / pooled_width / pooled_height;\n    int hstart = ph * stride_h - pad_t;\n    int wstart = pw * stride_w - pad_l;\n    int hend = min(hstart + kernel_h, height);\n    int wend = min(wstart + kernel_w, width);\n    hstart = max(hstart, 0);\n    wstart = max(wstart, 0);\n    T output = 0;\n    int bottom_offset = n * height * width * channels + c;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        output += bottom_data[bottom_offset + (h * width + w) * channels];\n      }\n    }\n    int pool_size = (hend - hstart) * (wend - wstart);\n    top_data[index] = output / pool_size;\n  }\n}\n\ntemplate <typename T>\n__global__ void Average3DPoolForwardNHWC(\n    const int nthreads,\n    const T* bottom_data,\n    const int num,\n    const int height,\n    const int width,\n    const int depth,\n    const int channels,\n    const int pooled_height,\n    const int pooled_width,\n    const int pooled_depth,\n    const int kernel_h,\n    const int kernel_w,\n    const int kernel_d,\n    const int stride_h,\n    const int stride_w,\n    const int stride_d,\n    const int pad_t,\n    const int pad_l,\n    const int pad_f,\n    T* top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int c = index % channels;\n    int pd = (index / channels) % pooled_depth;\n    int pw = (index / channels / pooled_depth) % pooled_width;\n    int ph = (index / channels / pooled_depth / pooled_width) % pooled_height;\n    int n = index / channels / pooled_depth / pooled_width / pooled_height;\n    int hstart = ph * stride_h - pad_t;\n    int wstart = pw * stride_w - pad_l;\n    int dstart = pd * stride_d - pad_f;\n    int hend = min(hstart + kernel_h, height);\n    int wend = min(wstart + kernel_w, width);\n    int dend = min(dstart + kernel_d, depth);\n    hstart = max(hstart, 0);\n    wstart = max(wstart, 0);\n    dstart = max(dstart, 0);\n    T output = 0;\n    int bottom_offset = n * height * width * depth * channels + c;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        for (int d = dstart; d < dend; ++d) {\n          const int bottom_index =\n              bottom_offset + (h * depth * width + w * depth + d) * channels;\n          output += bottom_data[bottom_index];\n        }\n      }\n    }\n    int pool_size = (hend - hstart) * (wend - wstart) * (dend - dstart);\n    top_data[index] = output / pool_size;\n  }\n}\n\ntemplate <typename T>\n__global__ void Ave1DPoolBackwardNCHW(\n    const int nthreads,\n    const T* const top_diff,\n    const int num,\n    const int channels,\n    const int height,\n    const int pooled_height,\n    const int kernel_h,\n    const int stride_h,\n    const int pad_t,\n    T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int h = index % height + pad_t;\n    const int c = (index / height) % channels;\n    const int n = index / height / channels;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    T gradient = 0;\n    const T* const top_diff_slice =\n        top_diff + (n * channels + c) * pooled_height;\n    for (int ph = phstart; ph < phend; ++ph) {\n      // figure out the pooling size\n      int hstart = ph * stride_h - pad_t;\n      int hend = min(hstart + kernel_h, height);\n      hstart = max(hstart, 0);\n      int pool_size = (hend - hstart);\n      gradient += top_diff_slice[ph] / pool_size;\n    }\n    bottom_diff[index] = gradient;\n  }\n}\n\ntemplate <typename T>\n__global__ void Ave2DPoolBackwardNCHW(\n    const int nthreads,\n    const T* const top_diff,\n    const int num,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l,\n    T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int w = index % width + pad_l;\n    const int h = (index / width) % height + pad_t;\n    const int c = (index / width / height) % channels;\n    const int n = index / width / height / channels;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    const int pwstart = (w < kernel_w) ? 0 : (w - kernel_w) / stride_w + 1;\n    const int pwend = min(w / stride_w + 1, pooled_width);\n    T gradient = 0;\n    const T* const top_diff_slice =\n        top_diff + (n * channels + c) * pooled_height * pooled_width;\n    for (int ph = phstart; ph < phend; ++ph) {\n      for (int pw = pwstart; pw < pwend; ++pw) {\n        // figure out the pooling size\n        int hstart = ph * stride_h - pad_t;\n        int wstart = pw * stride_w - pad_l;\n        int hend = min(hstart + kernel_h, height);\n        int wend = min(wstart + kernel_w, width);\n        hstart = max(hstart, 0);\n        wstart = max(wstart, 0);\n        int pool_size = (hend - hstart) * (wend - wstart);\n        gradient += top_diff_slice[ph * pooled_width + pw] / pool_size;\n      }\n    }\n    bottom_diff[index] = gradient;\n  }\n}\n\ntemplate <typename T>\n__global__ void Ave3DPoolBackwardNCHW(\n    const int nthreads,\n    const T* const top_diff,\n    const int num,\n    const int channels,\n    const int height,\n    const int width,\n    const int depth,\n    const int pooled_height,\n    const int pooled_width,\n    const int pooled_depth,\n    const int kernel_h,\n    const int kernel_w,\n    const int kernel_d,\n    const int stride_h,\n    const int stride_w,\n    const int stride_d,\n    const int pad_t,\n    const int pad_l,\n    const int pad_f,\n    T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int d = index % depth + pad_f;\n    const int w = (index / depth) % width + pad_l;\n    const int h = (index / depth / width) % height + pad_t;\n    const int c = (index / depth / width / height) % channels;\n    const int n = index / depth / width / height / channels;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    const int pwstart = (w < kernel_w) ? 0 : (w - kernel_w) / stride_w + 1;\n    const int pwend = min(w / stride_w + 1, pooled_width);\n    const int pdstart = (d < kernel_d) ? 0 : (d - kernel_d) / stride_d + 1;\n    const int pdend = min(d / stride_d + 1, pooled_depth);\n    T gradient = 0;\n    const T* const top_diff_slice = top_diff +\n        (n * channels + c) * pooled_height * pooled_width * pooled_depth;\n    for (int ph = phstart; ph < phend; ++ph) {\n      for (int pw = pwstart; pw < pwend; ++pw) {\n        for (int pd = pdstart; pd < pdend; ++pd) {\n          // figure out the pooling size\n          int hstart = ph * stride_h - pad_t;\n          int wstart = pw * stride_w - pad_l;\n          int dstart = pd * stride_d - pad_f;\n          int hend = min(hstart + kernel_h, height);\n          int wend = min(wstart + kernel_w, width);\n          int dend = min(dstart + kernel_d, depth);\n          hstart = max(hstart, 0);\n          wstart = max(wstart, 0);\n          dstart = max(dstart, 0);\n          int pool_size = (hend - hstart) * (wend - wstart) * (dend - dstart);\n          const int pooled_index =\n              ph * pooled_depth * pooled_width + pooled_depth * pw + pd;\n          gradient += top_diff_slice[pooled_index] / pool_size;\n        }\n      }\n    }\n    bottom_diff[index] = gradient;\n  }\n}\n\ntemplate <typename T>\n__global__ void Ave1DPoolBackwardNHWC(\n    const int nthreads,\n    const T* const top_diff,\n    const int num,\n    const int height,\n    const int channels,\n    const int pooled_height,\n    const int kernel_h,\n    const int stride_h,\n    const int pad_t,\n    T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int c = index % channels;\n    const int h = (index / channels) % height + pad_t;\n    const int n = index / channels / height;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    T gradient = 0;\n    const T* const top_diff_slice = top_diff + n * pooled_height * channels + c;\n    for (int ph = phstart; ph < phend; ++ph) {\n      // figure out the pooling size\n      int hstart = ph * stride_h - pad_t;\n      int hend = min(hstart + kernel_h, height);\n      hstart = max(hstart, 0);\n      int pool_size = (hend - hstart);\n      gradient += top_diff_slice[ph * channels] / pool_size;\n    }\n    bottom_diff[index] = gradient;\n  }\n}\n\ntemplate <typename T>\n__global__ void Ave2DPoolBackwardNHWC(\n    const int nthreads,\n    const T* const top_diff,\n    const int num,\n    const int height,\n    const int width,\n    const int channels,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l,\n    T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int c = index % channels;\n    const int w = index / channels % width + pad_l;\n    const int h = (index / channels / width) % height + pad_t;\n    const int n = index / channels / width / height;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    const int pwstart = (w < kernel_w) ? 0 : (w - kernel_w) / stride_w + 1;\n    const int pwend = min(w / stride_w + 1, pooled_width);\n    T gradient = 0;\n    const T* const top_diff_slice =\n        top_diff + n * pooled_height * pooled_width * channels + c;\n    for (int ph = phstart; ph < phend; ++ph) {\n      for (int pw = pwstart; pw < pwend; ++pw) {\n        // figure out the pooling size\n        int hstart = ph * stride_h - pad_t;\n        int wstart = pw * stride_w - pad_l;\n        int hend = min(hstart + kernel_h, height);\n        int wend = min(wstart + kernel_w, width);\n        hstart = max(hstart, 0);\n        wstart = max(wstart, 0);\n        int pool_size = (hend - hstart) * (wend - wstart);\n        gradient +=\n            top_diff_slice[(ph * pooled_width + pw) * channels] / pool_size;\n      }\n    }\n    bottom_diff[index] = gradient;\n  }\n}\n\ntemplate <typename T>\n__global__ void Ave3DPoolBackwardNHWC(\n    const int nthreads,\n    const T* const top_diff,\n    const int num,\n    const int height,\n    const int width,\n    const int depth,\n    const int channels,\n    const int pooled_height,\n    const int pooled_width,\n    const int pooled_depth,\n    const int kernel_h,\n    const int kernel_w,\n    const int kernel_d,\n    const int stride_h,\n    const int stride_w,\n    const int stride_d,\n    const int pad_t,\n    const int pad_l,\n    const int pad_f,\n    T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int c = index % channels;\n    const int d = index / channels % depth + pad_f;\n    const int w = (index / channels / depth) % width + pad_l;\n    const int h = (index / channels / depth / width) % height + pad_t;\n    const int n = index / channels / depth / width / height;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    const int pwstart = (w < kernel_w) ? 0 : (w - kernel_w) / stride_w + 1;\n    const int pwend = min(w / stride_w + 1, pooled_width);\n    const int pdstart = (d < kernel_d) ? 0 : (d - kernel_d) / stride_d + 1;\n    const int pdend = min(d / stride_d + 1, pooled_depth);\n    T gradient = 0;\n    const T* const top_diff_slice = top_diff +\n        n * pooled_height * pooled_width * pooled_depth * channels + c;\n    for (int ph = phstart; ph < phend; ++ph) {\n      for (int pw = pwstart; pw < pwend; ++pw) {\n        for (int pd = pdstart; pd < pdend; ++pd) {\n          // figure out the pooling size\n          int hstart = ph * stride_h - pad_t;\n          int wstart = pw * stride_w - pad_l;\n          int dstart = pd * stride_d - pad_f;\n          int hend = min(hstart + kernel_h, height);\n          int wend = min(wstart + kernel_w, width);\n          int dend = min(dstart + kernel_d, depth);\n          hstart = max(hstart, 0);\n          wstart = max(wstart, 0);\n          dstart = max(dstart, 0);\n          int pool_size = (hend - hstart) * (wend - wstart) * (dend - dstart);\n          const int pooled_index =\n              (ph * pooled_depth * pooled_width + pw * pooled_depth + pd) *\n              channels;\n          gradient += top_diff_slice[pooled_index] / pool_size;\n        }\n      }\n    }\n    bottom_diff[index] = gradient;\n  }\n}\n\n}  // namespace\n\ntemplate <>\nbool PoolOp<float, CUDAContext, AveragePool>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  ConvPoolOpBase<CUDAContext>::SetOutputSize(X, Y, X.dim32(1));\n  int output_size = Y->size();\n  switch (kernel_.size()) {\n    case 1:\n      Average1DPoolForwardNCHW<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          Y->dim32(2),\n          kernel_h(),\n          stride_h(),\n          pad_t(),\n          Y->mutable_data<float>());\n      break;\n    case 2:\n      Average2DPoolForwardNCHW<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          Y->dim32(2),\n          Y->dim32(3),\n          kernel_h(),\n          kernel_w(),\n          stride_h(),\n          stride_w(),\n          pad_t(),\n          pad_l(),\n          Y->mutable_data<float>());\n      break;\n    case 3:\n      Average3DPoolForwardNCHW<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          X.dim32(4),\n          Y->dim32(2),\n          Y->dim32(3),\n          Y->dim32(4),\n          kernel_h(),\n          kernel_w(),\n          kernel_[2],\n          stride_h(),\n          stride_w(),\n          stride_[2],\n          pad_t(),\n          pad_l(),\n          pads_[2],\n          Y->mutable_data<float>());\n      break;\n    default:\n      CAFFE_THROW(\"Unsupported pooling size : \", kernel_.size());\n  }\n  return true;\n}\n\ntemplate <>\nbool PoolOp<float, CUDAContext, AveragePool>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  ConvPoolOpBase<CUDAContext>::SetOutputSize(X, Y, X.dim32(X.ndim() - 1));\n  int output_size = Y->size();\n  switch (kernel_.size()) {\n    case 1:\n      Average1DPoolForwardNHWC<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          Y->dim32(1),\n          kernel_h(),\n          stride_h(),\n          pad_t(),\n          Y->mutable_data<float>());\n      break;\n    case 2:\n      Average2DPoolForwardNHWC<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          Y->dim32(1),\n          Y->dim32(2),\n          kernel_h(),\n          kernel_w(),\n          stride_h(),\n          stride_w(),\n          pad_t(),\n          pad_l(),\n          Y->mutable_data<float>());\n      break;\n    case 3:\n      Average3DPoolForwardNHWC<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          X.dim32(4),\n          Y->dim32(1),\n          Y->dim32(2),\n          Y->dim32(3),\n          kernel_h(),\n          kernel_w(),\n          kernel_[2],\n          stride_h(),\n          stride_w(),\n          stride_[2],\n          pad_t(),\n          pad_l(),\n          pads_[2],\n          Y->mutable_data<float>());\n      break;\n    default:\n      CAFFE_THROW(\"Unsupported pooling size : \", kernel_.size());\n  }\n  return true;\n}\n\ntemplate <>\nbool PoolGradientOp<float, CUDAContext, AveragePool>::\n    RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto& dY = Input(2);\n  CAFFE_ENFORCE_EQ(dY.dim32(1), X.dim32(1));\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n  vector<int> dims(X.dims().begin() + 2, X.dims().end());\n  ConvPoolOpBase<CUDAContext>::ComputePads(dims);\n  switch (kernel_.size()) {\n    case 1:\n      Ave1DPoolBackwardNCHW<float><<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          X.size(),\n          dY.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          dY.dim32(2),\n          kernel_h(),\n          stride_h(),\n          pad_t(),\n          dX->mutable_data<float>());\n      break;\n    case 2:\n      Ave2DPoolBackwardNCHW<float><<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          X.size(),\n          dY.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          dY.dim32(2),\n          dY.dim32(3),\n          kernel_h(),\n          kernel_w(),\n          stride_h(),\n          stride_w(),\n          pad_t(),\n          pad_l(),\n          dX->mutable_data<float>());\n      break;\n    case 3:\n      Ave3DPoolBackwardNCHW<float><<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          X.size(),\n          dY.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          X.dim32(4),\n          dY.dim32(2),\n          dY.dim32(3),\n          dY.dim32(4),\n          kernel_h(),\n          kernel_w(),\n          kernel_[2],\n          stride_h(),\n          stride_w(),\n          stride_[2],\n          pad_t(),\n          pad_l(),\n          pads_[2],\n          dX->mutable_data<float>());\n      break;\n    default:\n      CAFFE_THROW(\"Unsupported pooling size : \", kernel_.size());\n  }\n  return true;\n}\n\ntemplate <>\nbool PoolGradientOp<float, CUDAContext, AveragePool>::\n    RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto& dY = Input(2);\n  CAFFE_ENFORCE_EQ(X.ndim(), dY.ndim());\n  CAFFE_ENFORCE_EQ(X.dim32(X.ndim() - 1), dY.dim32(dY.ndim() - 1));\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n  vector<int> dims(X.dims().begin() + 1, X.dims().end() - 1);\n  ConvPoolOpBase<CUDAContext>::ComputePads(dims);\n  switch (kernel_.size()) {\n    case 1:\n      Ave1DPoolBackwardNHWC<float><<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          X.size(),\n          dY.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          dY.dim32(1),\n          kernel_h(),\n          stride_h(),\n          pad_t(),\n          dX->mutable_data<float>());\n      break;\n    case 2:\n      Ave2DPoolBackwardNHWC<float><<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          X.size(),\n          dY.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          dY.dim32(1),\n          dY.dim32(2),\n          kernel_h(),\n          kernel_w(),\n          stride_h(),\n          stride_w(),\n          pad_t(),\n          pad_l(),\n          dX->mutable_data<float>());\n      break;\n    case 3:\n      Ave3DPoolBackwardNHWC<float><<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          X.size(),\n          dY.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          X.dim32(4),\n          dY.dim32(1),\n          dY.dim32(2),\n          dY.dim32(3),\n          kernel_h(),\n          kernel_w(),\n          kernel_[2],\n          stride_h(),\n          stride_w(),\n          stride_[2],\n          pad_t(),\n          pad_l(),\n          pads_[2],\n          dX->mutable_data<float>());\n      break;\n    default:\n      CAFFE_THROW(\"Unsupported pooling size : \", kernel_.size());\n  }\n  return true;\n}\n\n\nnamespace {\n\ntemplate <typename T>\n__global__ void MaxPool1DForwardNCHW(\n    const int nthreads,\n    const T* bottom_data,\n    const int channels,\n    const int height,\n    const int pooled_height,\n    const int kernel_h,\n    const int stride_h,\n    const int pad_t,\n    T* top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int ph = index % pooled_height;\n    int c = (index / pooled_height) % channels;\n    int n = index / pooled_height / channels;\n    int hstart = ph * stride_h - pad_t;\n    int hend = min(hstart + kernel_h, height);\n    hstart = max(hstart, 0);\n    T maxval = -FLT_MAX;\n    const T* bdata_offset = bottom_data + n * channels * height;\n    for (int h = hstart; h < hend; ++h) {\n      int idx = c * height + h;\n      if (bdata_offset[idx] > maxval) {\n        maxval = bdata_offset[idx];\n      }\n    }\n    top_data[index] = maxval;\n  }\n}\n\ntemplate <typename T>\n__global__ void MaxPool2DForwardNCHW(\n    const int nthreads,\n    const T* bottom_data,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l,\n    T* top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int c = (index / pooled_width / pooled_height) % channels;\n    int n = index / pooled_width / pooled_height / channels;\n    int hstart = ph * stride_h - pad_t;\n    int wstart = pw * stride_w - pad_l;\n    int hend = min(hstart + kernel_h, height);\n    int wend = min(wstart + kernel_w, width);\n    hstart = max(hstart, 0);\n    wstart = max(wstart, 0);\n    T maxval = -FLT_MAX;\n    const T* bdata_offset = bottom_data + n * channels * height * width;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        int idx = c * height * width + h * width + w;\n        if (bdata_offset[idx] > maxval) {\n          maxval = bdata_offset[idx];\n        }\n      }\n    }\n    top_data[index] = maxval;\n  }\n}\n\ntemplate <typename T>\n__global__ void MaxPool3DForwardNCHW(\n    const int nthreads,\n    const T* bottom_data,\n    const int channels,\n    const int height,\n    const int width,\n    const int depth,\n    const int pooled_height,\n    const int pooled_width,\n    const int pooled_depth,\n    const int kernel_h,\n    const int kernel_w,\n    const int kernel_d,\n    const int stride_h,\n    const int stride_w,\n    const int stride_d,\n    const int pad_t,\n    const int pad_l,\n    const int pad_f,\n    T* top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int pd = index % pooled_depth;\n    int pw = (index / pooled_depth) % pooled_width;\n    int ph = (index / pooled_depth / pooled_width) % pooled_height;\n    int c = (index / pooled_depth / pooled_width / pooled_height) % channels;\n    int n = index / pooled_depth / pooled_width / pooled_height / channels;\n    int hstart = ph * stride_h - pad_t;\n    int wstart = pw * stride_w - pad_l;\n    int hend = min(hstart + kernel_h, height);\n    int wend = min(wstart + kernel_w, width);\n    int dstart = pd * stride_d - pad_f;\n    int dend = min(dstart + kernel_d, depth);\n    hstart = max(hstart, 0);\n    wstart = max(wstart, 0);\n    dstart = max(dstart, 0);\n    T maxval = -FLT_MAX;\n    const T* bdata_offset = bottom_data + n * channels * height * width * depth;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        for (int d = dstart; d < dend; ++d) {\n          int idx = ((c * height + h) * width + w) * depth + d;\n          if (bdata_offset[idx] > maxval) {\n            maxval = bdata_offset[idx];\n          }\n        }\n      }\n    }\n    top_data[index] = maxval;\n  }\n}\n\ntemplate <typename T>\n__global__ void MaxPool1DForwardNHWC(\n    const int nthreads,\n    const T* bottom_data,\n    const int height,\n    const int channels,\n    const int pooled_height,\n    const int kernel_h,\n    const int stride_h,\n    const int pad_t,\n    T* top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index;\n    int c = n % channels;\n    n /= channels;\n    int hstart = (n % pooled_height) * stride_h - pad_t;\n    n /= pooled_height;\n    int hend = min(hstart + kernel_h, height);\n    hstart = max(hstart, 0);\n    T maxval = -FLT_MAX;\n    const T* bdata_offset = bottom_data + n * height * channels;\n    for (int h = hstart; h < hend; ++h) {\n      int idx = h * channels + c;\n      if (bdata_offset[idx] > maxval) {\n        maxval = bdata_offset[idx];\n      }\n    }\n    top_data[index] = maxval;\n  }\n}\n\ntemplate <typename T>\n__global__ void MaxPool2DForwardNHWC(\n    const int nthreads,\n    const T* bottom_data,\n    const int height,\n    const int width,\n    const int channels,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l,\n    T* top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index;\n    int c = n % channels;\n    n /= channels;\n    int wstart = (n % pooled_width) * stride_w - pad_l;\n    n /= pooled_width;\n    int hstart = (n % pooled_height) * stride_h - pad_t;\n    n /= pooled_height;\n    int hend = min(hstart + kernel_h, height);\n    int wend = min(wstart + kernel_w, width);\n    hstart = max(hstart, 0);\n    wstart = max(wstart, 0);\n    T maxval = -FLT_MAX;\n    const T* bdata_offset = bottom_data + n * height * width * channels;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        int idx = (h * width + w) * channels + c;\n        if (bdata_offset[idx] > maxval) {\n          maxval = bdata_offset[idx];\n        }\n      }\n    }\n    top_data[index] = maxval;\n  }\n}\n\ntemplate <typename T>\n__global__ void MaxPool3DForwardNHWC(\n    const int nthreads,\n    const T* bottom_data,\n    const int height,\n    const int width,\n    const int depth,\n    const int channels,\n    const int pooled_height,\n    const int pooled_width,\n    const int pooled_depth,\n    const int kernel_h,\n    const int kernel_w,\n    const int kernel_d,\n    const int stride_h,\n    const int stride_w,\n    const int stride_d,\n    const int pad_t,\n    const int pad_l,\n    const int pad_f,\n    T* top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index;\n    int c = n % channels;\n    n /= channels;\n    int dstart = (n % pooled_depth) * stride_d - pad_f;\n    n /= pooled_depth;\n    int wstart = (n % pooled_width) * stride_w - pad_l;\n    n /= pooled_width;\n    int hstart = (n % pooled_height) * stride_h - pad_t;\n    n /= pooled_height;\n    int hend = min(hstart + kernel_h, height);\n    int wend = min(wstart + kernel_w, width);\n    int dend = min(dstart + kernel_d, depth);\n    hstart = max(hstart, 0);\n    wstart = max(wstart, 0);\n    dstart = max(dstart, 0);\n    T maxval = -FLT_MAX;\n    const T* bdata_offset = bottom_data + n * height * width * depth * channels;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        for (int d = dstart; d < dend; ++d) {\n          int idx = ((h * width + w) * depth + d) * channels + c;\n          if (bdata_offset[idx] > maxval) {\n            maxval = bdata_offset[idx];\n          }\n        }\n      }\n    }\n    top_data[index] = maxval;\n  }\n}\n\ntemplate <typename T>\n__global__ void MaxPool1DBackwardNCHW(\n    const int nthreads,\n    const T* const bottom_data,\n    const T* const top_data,\n    const T* const top_diff,\n    const int num,\n    const int channels,\n    const int height,\n    const int pooled_height,\n    const int kernel_h,\n    const int stride_h,\n    const int pad_t,\n    T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int h = index % height + pad_t;\n    const int c = (index / height) % channels;\n    const int n = index / height / channels;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    const int top_offset = (n * channels + c) * pooled_height;\n    bottom_diff[index] = 0;\n    for (int ph = phstart; ph < phend; ++ph) {\n      int top_local_offset = top_offset + ph;\n      if (bottom_data[index] == top_data[top_local_offset]) {\n        bottom_diff[index] += top_diff[top_local_offset];\n      }\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void MaxPool2DBackwardNCHW(\n    const int nthreads,\n    const T* const bottom_data,\n    const T* const top_data,\n    const T* const top_diff,\n    const int num,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l,\n    T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int w = index % width + pad_l;\n    const int h = (index / width) % height + pad_t;\n    const int c = (index / width / height) % channels;\n    const int n = index / width / height / channels;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    const int pwstart = (w < kernel_w) ? 0 : (w - kernel_w) / stride_w + 1;\n    const int pwend = min(w / stride_w + 1, pooled_width);\n    const int top_offset = (n * channels + c) * pooled_height * pooled_width;\n    bottom_diff[index] = 0;\n    for (int ph = phstart; ph < phend; ++ph) {\n      for (int pw = pwstart; pw < pwend; ++pw) {\n        int top_local_offset = top_offset + ph * pooled_width + pw;\n        if (bottom_data[index] == top_data[top_local_offset]) {\n          bottom_diff[index] += top_diff[top_local_offset];\n        }\n      }\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void MaxPool3DBackwardNCHW(\n    const int nthreads,\n    const T* const bottom_data,\n    const T* const top_data,\n    const T* const top_diff,\n    const int num,\n    const int channels,\n    const int height,\n    const int width,\n    const int depth,\n    const int pooled_height,\n    const int pooled_width,\n    const int pooled_depth,\n    const int kernel_h,\n    const int kernel_w,\n    const int kernel_d,\n    const int stride_h,\n    const int stride_w,\n    const int stride_d,\n    const int pad_t,\n    const int pad_l,\n    const int pad_f,\n    T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int d = index % depth + pad_f;\n    const int w = (index / depth) % width + pad_l;\n    const int h = (index / depth / width) % height + pad_t;\n    const int c = (index / depth / width / height) % channels;\n    const int n = index / depth / width / height / channels;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    const int pwstart = (w < kernel_w) ? 0 : (w - kernel_w) / stride_w + 1;\n    const int pwend = min(w / stride_w + 1, pooled_width);\n    const int pdstart = (d < kernel_d) ? 0 : (d - kernel_d) / stride_d + 1;\n    const int pdend = min(d / stride_d + 1, pooled_depth);\n    const int top_offset =\n        (n * channels + c) * pooled_height * pooled_width * pooled_depth;\n    bottom_diff[index] = 0;\n    for (int ph = phstart; ph < phend; ++ph) {\n      for (int pw = pwstart; pw < pwend; ++pw) {\n        for (int pd = pdstart; pd < pdend; ++pd) {\n          int top_local_offset =\n              top_offset + (ph * pooled_width + pw) * pooled_depth + pd;\n          if (bottom_data[index] == top_data[top_local_offset]) {\n            bottom_diff[index] += top_diff[top_local_offset];\n          }\n        }\n      }\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void MaxPool1DBackwardNHWC(\n    const int nthreads,\n    const T* const bottom_data,\n    const T* const top_data,\n    const T* const top_diff,\n    const int num,\n    const int height,\n    const int channels,\n    const int pooled_height,\n    const int kernel_h,\n    const int stride_h,\n    const int pad_t,\n    T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int c = index % channels;\n    const int h = (index / channels) % height + pad_t;\n    const int n = index / channels / height;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    const int top_offset = n * pooled_height * channels + c;\n    bottom_diff[index] = 0;\n    for (int ph = phstart; ph < phend; ++ph) {\n      int top_local_offset = top_offset + ph * channels;\n      if (bottom_data[index] == top_data[top_local_offset]) {\n        bottom_diff[index] += top_diff[top_local_offset];\n      }\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void MaxPool2DBackwardNHWC(\n    const int nthreads,\n    const T* const bottom_data,\n    const T* const top_data,\n    const T* const top_diff,\n    const int num,\n    const int height,\n    const int width,\n    const int channels,\n    const int pooled_height,\n    const int pooled_width,\n    const int kernel_h,\n    const int kernel_w,\n    const int stride_h,\n    const int stride_w,\n    const int pad_t,\n    const int pad_l,\n    T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int c = index % channels;\n    const int w = index / channels % width + pad_l;\n    const int h = (index / channels / width) % height + pad_t;\n    const int n = index / channels / width / height;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    const int pwstart = (w < kernel_w) ? 0 : (w - kernel_w) / stride_w + 1;\n    const int pwend = min(w / stride_w + 1, pooled_width);\n    const int top_offset =\n        n * pooled_height * pooled_width * channels + c;\n    bottom_diff[index] = 0;\n    for (int ph = phstart; ph < phend; ++ph) {\n      for (int pw = pwstart; pw < pwend; ++pw) {\n        int top_local_offset = top_offset + (ph * pooled_width + pw) * channels;\n        if (bottom_data[index] == top_data[top_local_offset]) {\n          bottom_diff[index] += top_diff[top_local_offset];\n        }\n      }\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void MaxPool3DBackwardNHWC(\n    const int nthreads,\n    const T* const bottom_data,\n    const T* const top_data,\n    const T* const top_diff,\n    const int num,\n    const int height,\n    const int width,\n    const int depth,\n    const int channels,\n    const int pooled_height,\n    const int pooled_width,\n    const int pooled_depth,\n    const int kernel_h,\n    const int kernel_w,\n    const int kernel_d,\n    const int stride_h,\n    const int stride_w,\n    const int stride_d,\n    const int pad_t,\n    const int pad_l,\n    const int pad_f,\n    T* const bottom_diff) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // find out the local index\n    // find out the local offset\n    const int c = index % channels;\n    const int d = index / channels % depth + pad_f;\n    const int w = (index / depth / channels) % width + pad_l;\n    const int h = (index / channels / depth / width) % height + pad_t;\n    const int n = index / channels / depth / width / height;\n    const int phstart = (h < kernel_h) ? 0 : (h - kernel_h) / stride_h + 1;\n    const int phend = min(h / stride_h + 1, pooled_height);\n    const int pwstart = (w < kernel_w) ? 0 : (w - kernel_w) / stride_w + 1;\n    const int pwend = min(w / stride_w + 1, pooled_width);\n    const int pdstart = (d < kernel_d) ? 0 : (d - kernel_d) / stride_d + 1;\n    const int pdend = min(d / stride_d + 1, pooled_depth);\n    const int top_offset =\n        n * pooled_height * pooled_width * pooled_depth * channels + c;\n    bottom_diff[index] = 0;\n    for (int ph = phstart; ph < phend; ++ph) {\n      for (int pw = pwstart; pw < pwend; ++pw) {\n        for (int pd = pdstart; pd < pdend; ++pd) {\n          int top_local_offset = top_offset +\n              ((ph * pooled_width + pw) * pooled_depth + d) * channels;\n          if (bottom_data[index] == top_data[top_local_offset]) {\n            bottom_diff[index] += top_diff[top_local_offset];\n          }\n        }\n      }\n    }\n  }\n}\n}  // namespace\n\ntemplate <>\nbool PoolOp<float, CUDAContext, MaxPool>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  ConvPoolOpBase<CUDAContext>::SetOutputSize(X, Y, X.dim32(1));\n  int output_size = Y->size();\n  switch (kernel_.size()) {\n    case 1:\n      MaxPool1DForwardNCHW<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          X.dim32(1),\n          X.dim32(2),\n          Y->dim32(2),\n          kernel_h(),\n          stride_h(),\n          pad_t(),\n          Y->mutable_data<float>());\n      break;\n    case 2:\n      MaxPool2DForwardNCHW<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          Y->dim32(2),\n          Y->dim32(3),\n          kernel_h(),\n          kernel_w(),\n          stride_h(),\n          stride_w(),\n          pad_t(),\n          pad_l(),\n          Y->mutable_data<float>());\n      break;\n    case 3:\n      MaxPool3DForwardNCHW<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          X.dim32(4),\n          Y->dim32(2),\n          Y->dim32(3),\n          Y->dim32(4),\n          kernel_h(),\n          kernel_w(),\n          kernel_[2],\n          stride_h(),\n          stride_w(),\n          stride_[2],\n          pad_t(),\n          pad_l(),\n          pads_[2],\n          Y->mutable_data<float>());\n      break;\n    default:\n      CAFFE_THROW(\"Unsupported pooling size : \", kernel_.size());\n  }\n  return true;\n}\n\ntemplate <>\nbool PoolOp<float, CUDAContext, MaxPool>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  ConvPoolOpBase<CUDAContext>::SetOutputSize(X, Y, X.dim32(X.ndim() - 1));\n  int output_size = Y->size();\n  switch (kernel_.size()) {\n    case 1:\n      MaxPool1DForwardNHWC<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          X.dim32(1),\n          X.dim32(2),\n          Y->dim32(1),\n          kernel_h(),\n          stride_h(),\n          pad_t(),\n          Y->mutable_data<float>());\n      break;\n    case 2:\n      MaxPool2DForwardNHWC<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          Y->dim32(1),\n          Y->dim32(2),\n          kernel_h(),\n          kernel_w(),\n          stride_h(),\n          stride_w(),\n          pad_t(),\n          pad_l(),\n          Y->mutable_data<float>());\n      break;\n    case 3:\n      MaxPool3DForwardNHWC<float><<<\n          CAFFE_GET_BLOCKS(output_size),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          X.dim32(4),\n          Y->dim32(1),\n          Y->dim32(2),\n          Y->dim32(3),\n          kernel_h(),\n          kernel_w(),\n          kernel_[2],\n          stride_h(),\n          stride_w(),\n          stride_[2],\n          pad_t(),\n          pad_l(),\n          pads_[2],\n          Y->mutable_data<float>());\n      break;\n    default:\n      CAFFE_THROW(\"Unsupported pooling size : \", kernel_.size());\n  }\n  return true;\n}\n\ntemplate <>\nbool PoolGradientOp<float, CUDAContext, MaxPool>::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dY = Input(2);\n  CAFFE_ENFORCE_EQ(dY.ndim(), X.ndim());\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n  vector<int> dims(X.dims().begin() + 2, X.dims().end());\n  ConvPoolOpBase<CUDAContext>::ComputePads(dims);\n  switch (kernel_.size()) {\n    case 1:\n      MaxPool1DBackwardNCHW<float><<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          X.size(),\n          X.data<float>(),\n          Y.data<float>(),\n          dY.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          dY.dim32(2),\n          kernel_h(),\n          stride_h(),\n          pad_t(),\n          dX->mutable_data<float>());\n      break;\n    case 2:\n      MaxPool2DBackwardNCHW<float><<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          X.size(),\n          X.data<float>(),\n          Y.data<float>(),\n          dY.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          dY.dim32(2),\n          dY.dim32(3),\n          kernel_h(),\n          kernel_w(),\n          stride_h(),\n          stride_w(),\n          pad_t(),\n          pad_l(),\n          dX->mutable_data<float>());\n      break;\n    case 3:\n      MaxPool3DBackwardNCHW<float><<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          X.size(),\n          X.data<float>(),\n          Y.data<float>(),\n          dY.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          X.dim32(4),\n          dY.dim32(2),\n          dY.dim32(3),\n          dY.dim32(4),\n          kernel_h(),\n          kernel_w(),\n          kernel_[2],\n          stride_h(),\n          stride_w(),\n          stride_[2],\n          pad_t(),\n          pad_l(),\n          pads_[2],\n          dX->mutable_data<float>());\n      break;\n    default:\n      CAFFE_THROW(\"Unsupported pooling size : \", kernel_.size());\n  }\n  return true;\n}\n\ntemplate <>\nbool PoolGradientOp<float, CUDAContext, MaxPool>::RunOnDeviceWithOrderNHWC() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dY = Input(2);\n  CAFFE_ENFORCE_EQ(dY.ndim(), X.ndim());\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n  vector<int> dims(X.dims().begin() + 1, X.dims().end() - 1);\n  ConvPoolOpBase<CUDAContext>::ComputePads(dims);\n  switch (kernel_.size()) {\n    case 1:\n      MaxPool1DBackwardNHWC<float><<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          X.size(),\n          X.data<float>(),\n          Y.data<float>(),\n          dY.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          dY.dim32(1),\n          kernel_h(),\n          stride_h(),\n          pad_t(),\n          dX->mutable_data<float>());\n    case 2:\n      MaxPool2DBackwardNHWC<float><<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          X.size(),\n          X.data<float>(),\n          Y.data<float>(),\n          dY.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          dY.dim32(1),\n          dY.dim32(2),\n          kernel_h(),\n          kernel_w(),\n          stride_h(),\n          stride_w(),\n          pad_t(),\n          pad_l(),\n          dX->mutable_data<float>());\n      break;\n    case 3:\n      MaxPool3DBackwardNHWC<float><<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          X.size(),\n          X.data<float>(),\n          Y.data<float>(),\n          dY.data<float>(),\n          X.dim32(0),\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          X.dim32(4),\n          dY.dim32(1),\n          dY.dim32(2),\n          dY.dim32(3),\n          kernel_h(),\n          kernel_w(),\n          kernel_[2],\n          stride_h(),\n          stride_w(),\n          stride_[2],\n          pad_t(),\n          pad_l(),\n          pads_[2],\n          dX->mutable_data<float>());\n      break;\n    default:\n      CAFFE_THROW(\"Unsupported pooling size : \", kernel_.size());\n  }\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(AveragePool, PoolOp<float, CUDAContext, AveragePool>);\nREGISTER_CUDA_OPERATOR(AveragePoolGradient,\n                       PoolGradientOp<float, CUDAContext, AveragePool>);\n\nREGISTER_CUDA_OPERATOR(AveragePool1D, PoolOp<float, CUDAContext, AveragePool>);\nREGISTER_CUDA_OPERATOR(\n    AveragePool1DGradient,\n    PoolGradientOp<float, CUDAContext, AveragePool>);\n\nREGISTER_CUDA_OPERATOR(AveragePool2D, PoolOp<float, CUDAContext, AveragePool>);\nREGISTER_CUDA_OPERATOR(\n    AveragePool2DGradient,\n    PoolGradientOp<float, CUDAContext, AveragePool>);\n\nREGISTER_CUDA_OPERATOR(AveragePool3D, PoolOp<float, CUDAContext, AveragePool>);\nREGISTER_CUDA_OPERATOR(\n    AveragePool3DGradient,\n    PoolGradientOp<float, CUDAContext, AveragePool>);\n\nREGISTER_CUDA_OPERATOR(MaxPool, PoolOp<float, CUDAContext, MaxPool>);\nREGISTER_CUDA_OPERATOR(MaxPoolGradient,\n                       PoolGradientOp<float, CUDAContext, MaxPool>);\n\nREGISTER_CUDA_OPERATOR(MaxPool1D, PoolOp<float, CUDAContext, MaxPool>);\nREGISTER_CUDA_OPERATOR(\n    MaxPool1DGradient,\n    PoolGradientOp<float, CUDAContext, MaxPool>);\n\nREGISTER_CUDA_OPERATOR(MaxPool2D, PoolOp<float, CUDAContext, MaxPool>);\nREGISTER_CUDA_OPERATOR(\n    MaxPool2DGradient,\n    PoolGradientOp<float, CUDAContext, MaxPool>);\n\nREGISTER_CUDA_OPERATOR(MaxPool3D, PoolOp<float, CUDAContext, MaxPool>);\nREGISTER_CUDA_OPERATOR(\n    MaxPool3DGradient,\n    PoolGradientOp<float, CUDAContext, MaxPool>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/pool_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_POOL_OP_H_\n#define CAFFE2_OPERATORS_POOL_OP_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context, typename PoolType>\nclass PoolOp final : public ConvPoolOpBase<Context> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(Context);\n  PoolOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<Context>(operator_def, ws) {\n    for (int i = 0; i < kernel_.size(); ++i) {\n      CAFFE_ENFORCE(\n          dilation_[i] == 1, \"Pooling op does not support dilation right now.\");\n    }\n    if (!global_pooling_) {\n      for (int i = 0; i < kernel_.size(); ++i) {\n        CAFFE_ENFORCE(\n            pads_[i] < kernel_[i] && pads_[i + kernel_.size()] < kernel_[i],\n            \"Pad should be smaller than kernel.\");\n      }\n    }\n  }\n  ~PoolOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n  // Input: X\n  // Output: Y\n};\n\ntemplate <typename T, class Context, class PoolType>\nclass PoolGradientOp final : public ConvPoolOpBase<Context> {\n public:\n  USE_CONV_POOL_BASE_FUNCTIONS(Context);\n  PoolGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<Context>(operator_def, ws) {}\n  ~PoolGradientOp() {}\n\n  bool RunOnDeviceWithOrderNCHW() override;\n  bool RunOnDeviceWithOrderNHWC() override;\n\n  // Input: X, Y, dY\n  // Output: dX\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_POOL_OP_H_\n"
  },
  {
    "path": "caffe2/operators/pool_op_cudnn.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/cudnn_wrappers.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n\n#include <cub/cub.cuh>\n\nnamespace caffe2 {\n\nnamespace {\n\n// Explicit fast paths for avg and max global pooling due to CuDNN global\n// pooling performance bug which makes pooling extremely slow.\ntemplate <typename T>\n__global__ void\nglobal_avgpool_kernel_NCHW(const int NC, const int sz, const T* data, T* out) {\n  typedef cub::BlockReduce<T, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  for (int j = blockIdx.x; j < NC; j += gridDim.x) {\n    T sum(0);\n    for (int k = threadIdx.x; k < sz; k += blockDim.x) {\n      sum += data[j * sz + k];\n    }\n    float totalsum = BlockReduce(temp_storage).Sum(sum);\n    if (threadIdx.x == 0) {\n      out[j] = totalsum / sz;\n    }\n    __syncthreads();\n  }\n}\n\ntemplate <typename T>\n__global__ void\nglobal_avgpool_backward_NCHW(const int NC, const int sz, const T* dx, T* out) {\n  CUDA_1D_KERNEL_LOOP(i, NC * sz) {\n    out[i] = dx[i / sz] / sz;\n  }\n}\n\ntemplate <typename T>\n__global__ void\nglobal_maxpool_kernel_NCHW(const int NC, const int sz, const T* data, T* out) {\n  typedef cub::BlockReduce<T, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  for (int j = blockIdx.x; j < NC; j += gridDim.x) {\n    T max(-FLT_MAX);\n    for (int k = threadIdx.x; k < sz; k += blockDim.x) {\n      max = data[j * sz + k] > max ? data[j * sz + k] : max;\n    }\n    float totalmax = BlockReduce(temp_storage).Reduce(max, cub::Max());\n    if (threadIdx.x == 0) {\n      out[j] = totalmax;\n    }\n    __syncthreads();\n  }\n}\n\ntemplate <typename T>\n__global__ void global_maxpool_backward_NCHW(\n    const int NC,\n    const int sz,\n    const T* dx,\n    T* out,\n    const T* x,\n    const T* in) {\n  CUDA_1D_KERNEL_LOOP(i, NC * sz) {\n    if (in[i] == x[i / sz]) {\n      out[i] = dx[i / sz];\n    } else {\n      out[i] = 0.0;\n    }\n  }\n}\n\ntemplate <typename T>\nvoid setTensorDescriptor(\n    const int size,\n    const StorageOrder order,\n    const int N,\n    const int C,\n    const int H,\n    const int W,\n    const int D,\n    cudnnTensorDescriptor_t& desc) {\n  if (size == 4) {\n    CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n        desc,\n        GetCudnnTensorFormat(order),\n        cudnnTypeWrapper<T>::type,\n        N,\n        C,\n        H,\n        W));\n  } else {\n    vector<int> dims = {N, C, H, W, D};\n    vector<int> strides;\n    order == NCHW\n        ? strides.insert(strides.end(), {C * H * W * D, H * W * D, W * D, D, 1})\n        : strides.insert(\n              strides.end(), {H * W * D * C, 1, W * D * C, D * C, C});\n    CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n        desc,\n        cudnnTypeWrapper<T>::type,\n        size > 3 ? size : 4,\n        dims.data(),\n        strides.data()));\n  }\n}\n\n} // namespace\n\nclass CuDNNPoolOp : public ConvPoolOpBase<CUDAContext> {\n public:\n  CuDNNPoolOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CUDAContext>(operator_def, ws),\n        cudnn_wrapper_(&context_) {\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&bottom_desc_));\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&top_desc_));\n    CUDNN_ENFORCE(cudnnCreatePoolingDescriptor(&pooling_desc_));\n    // Figure out the pooling descriptor.\n    if (operator_def.type().substr(0, 7) == \"MaxPool\") {\n      bool deterministic =\n          OperatorBase::GetSingleArgument<bool>(\"deterministic\", false);\n#if CUDNN_VERSION_MIN(6, 0, 0)\n      mode_ =\n          deterministic ? CUDNN_POOLING_MAX_DETERMINISTIC : CUDNN_POOLING_MAX;\n#else\n      mode_ = CUDNN_POOLING_MAX;\n#endif\n    } else if (operator_def.type().substr(0, 11) == \"AveragePool\") {\n      mode_ = CUDNN_POOLING_AVERAGE_COUNT_EXCLUDE_PADDING;\n    } else {\n      LOG(FATAL) << \"Unsupported pooling method: \" << operator_def.type();\n    }\n  }\n\n  ~CuDNNPoolOp() {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(bottom_desc_));\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(top_desc_));\n    CUDNN_ENFORCE(cudnnDestroyPoolingDescriptor(pooling_desc_));\n  }\n\n  template <typename T, typename M>\n  bool DoRunWithType() {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n    int N = 0, C = 0, H = 0, W = 0, D = 0;\n    int H_out = 0, W_out = 0, D_out = 0;\n\n    // cuDNN pooling support only 2 and 3 spatial dimensions.\n    CAFFE_ENFORCE(X.ndim() >= 4 && X.ndim() <= 5);\n\n    switch (order_) {\n      case StorageOrder::NHWC:\n        N = X.dim32(0);\n        H = X.dim32(1);\n        W = X.ndim() > 3 ? X.dim32(2) : 1;\n        D = X.ndim() > 4 ? X.dim32(3) : 1;\n        C = X.dim32(X.ndim() - 1);\n        ConvPoolOpBase::SetOutputSize(X, Y, C);\n        H_out = Y->dim32(1);\n        W_out = Y->ndim() > 3 ? Y->dim32(2) : 1;\n        D_out = Y->ndim() > 4 ? Y->dim32(3) : 1;\n        break;\n      case StorageOrder::NCHW:\n        N = X.dim32(0);\n        C = X.dim32(1);\n        H = X.dim32(2);\n        W = X.ndim() > 3 ? X.dim32(3) : 1;\n        D = X.ndim() > 4 ? X.dim32(4) : 1;\n        ConvPoolOpBase::SetOutputSize(X, Y, C);\n        H_out = Y->dim32(2);\n        W_out = Y->ndim() > 3 ? Y->dim32(3) : 1;\n        D_out = Y->ndim() > 4 ? Y->dim32(4) : 1;\n        break;\n      default:\n        LOG(FATAL) << \"Unknown storage order: \" << order_;\n    }\n\n    // Fast path for global pooling, as cudnn is slow. But only\n    // on float, because fp16 not supported for CUB.\n    if (std::is_same<T, float>::value) {\n      if (order_ == StorageOrder::NCHW && global_pooling_) {\n        if (mode_ == CUDNN_POOLING_AVERAGE_COUNT_EXCLUDE_PADDING) {\n          global_avgpool_kernel_NCHW<float>\n              <<<std::min(N * C, CAFFE_MAXIMUM_NUM_BLOCKS),\n                 CAFFE_CUDA_NUM_THREADS,\n                 0,\n                 context_.cuda_stream()>>>(\n                  N * C, H * W * D, X.data<float>(), Y->mutable_data<float>());\n          return true;\n        }\n        if (mode_ == CUDNN_POOLING_MAX) {\n          global_maxpool_kernel_NCHW<float>\n              <<<std::min(N * C, CAFFE_MAXIMUM_NUM_BLOCKS),\n                 CAFFE_CUDA_NUM_THREADS,\n                 0,\n                 context_.cuda_stream()>>>(\n                  N * C, H * W * D, X.data<float>(), Y->mutable_data<float>());\n          return true;\n        }\n      }\n    }\n\n    if (cudnn_input_dims_ != X.dims()) {\n      // Dimensions changed; we will need to re-initialize things.\n      VLOG(1) << \"Changing the cudnn descriptor configurations.\";\n      cudnn_input_dims_ = X.dims();\n      setTensorDescriptor<T>(X.ndim(), order_, N, C, H, W, D, bottom_desc_);\n      setTensorDescriptor<T>(\n          Y->ndim(), order_, N, C, H_out, W_out, D_out, top_desc_);\n      for (int i = 0; i < kernel_.size(); ++i) {\n        if (pads_[i] != pads_[kernel_.size() + i]) {\n          CAFFE_ENFORCE(\n              legacy_pad_ == LegacyPadding::CAFFE_LEGACY_POOLING,\n              \"Cudnn pooling only supports even padding on both sides, with \"\n              \"the only exception of the caffe legacy pooling case where we \"\n              \"try to preserve backward compatibility with Caffe.\");\n        }\n      }\n      if (kernel_.size() == 2) {\n        CUDNN_ENFORCE(cudnnSetPooling2dDescriptor(\n            pooling_desc_,\n            mode_,\n            CUDNN_NOT_PROPAGATE_NAN,\n            kernel_h(),\n            kernel_w(),\n            pad_t(),\n            pad_l(),\n            stride_h(),\n            stride_w()));\n      } else {\n        CUDNN_ENFORCE(cudnnSetPoolingNdDescriptor(\n            pooling_desc_,\n            mode_,\n            CUDNN_NOT_PROPAGATE_NAN,\n            kernel_.size(),\n            kernel_.data(),\n            pads_.data(),\n            stride_.data()));\n      }\n    }\n    // Carry out the pooling computation.\n    const T* Xdata = X.template data<T>();\n    T* Ydata = Y->template mutable_data<T>();\n    CUDNN_ENFORCE(cudnnPoolingForward(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        pooling_desc_,\n        cudnnTypeWrapper<T>::kOne(),\n        bottom_desc_,\n        Xdata,\n        cudnnTypeWrapper<T>::kZero(),\n        top_desc_,\n        Ydata));\n    return true;\n  }\n\n  bool RunOnDevice() final {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n\n    if (X.IsType<float>()) {\n      return DoRunWithType<float, float>();\n    } else if (X.IsType<float16>()) {\n      return DoRunWithType<float16, float>();\n    } else {\n      LOG(FATAL) << \"Unsupported input types\";\n    }\n    return true;\n  }\n\n protected:\n  vector<TIndex> cudnn_input_dims_;\n\n  CuDNNWrapper cudnn_wrapper_;\n  cudnnTensorDescriptor_t bottom_desc_;\n  cudnnTensorDescriptor_t top_desc_;\n  cudnnPoolingDescriptor_t pooling_desc_;\n  cudnnPoolingMode_t mode_;\n\n private:\n};\n\nclass CuDNNPoolGradientOp : public ConvPoolOpBase<CUDAContext> {\n public:\n  CuDNNPoolGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CUDAContext>(operator_def, ws),\n        cudnn_wrapper_(&context_) {\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&bottom_desc_));\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&top_desc_));\n    CUDNN_ENFORCE(cudnnCreatePoolingDescriptor(&pooling_desc_));\n    // Figure out the pooling descriptor.\n    if (operator_def.type() == \"MaxPoolGradient\" ||\n        operator_def.type() == \"MaxPool1DGradient\" ||\n        operator_def.type() == \"MaxPool2DGradient\" ||\n        operator_def.type() == \"MaxPool3DGradient\") {\n      bool deterministic =\n          OperatorBase::GetSingleArgument<bool>(\"deterministic\", false);\n#if CUDNN_VERSION_MIN(6, 0, 0)\n      mode_ =\n          deterministic ? CUDNN_POOLING_MAX_DETERMINISTIC : CUDNN_POOLING_MAX;\n#else\n      mode_ = CUDNN_POOLING_MAX;\n#endif\n    } else if (\n        operator_def.type() == \"AveragePoolGradient\" ||\n        operator_def.type() == \"AveragePool1DGradient\" ||\n        operator_def.type() == \"AveragePool2DGradient\" ||\n        operator_def.type() == \"AveragePool3DGradient\") {\n      mode_ = CUDNN_POOLING_AVERAGE_COUNT_EXCLUDE_PADDING;\n    } else {\n      LOG(FATAL) << \"Unsupported pooling method: \" << operator_def.type();\n    }\n  }\n\n  ~CuDNNPoolGradientOp() {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(bottom_desc_));\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(top_desc_));\n    CUDNN_ENFORCE(cudnnDestroyPoolingDescriptor(pooling_desc_));\n  }\n\n  template <typename T, typename M>\n  bool DoRunWithType() {\n    auto& X = Input(0);\n    auto& Y = Input(1);\n    auto& dY = Input(2);\n    auto* dX = Output(0);\n\n    // cuDNN pooling support only 2 and 3 spatial dimensions.\n    CAFFE_ENFORCE(X.ndim() >= 4 && X.ndim() <= 5);\n\n    dX->ResizeLike(X);\n    int N = 0, C = 0, H = 0, W = 0, D = 0;\n    int H_out = 0, W_out = 0, D_out = 0;\n    switch (order_) {\n      case StorageOrder::NHWC:\n        N = X.dim32(0);\n        H = X.dim32(1);\n        W = X.ndim() > 3 ? X.dim32(2) : 1;\n        D = X.ndim() > 4 ? X.dim32(3) : 1;\n        C = X.dim32(X.ndim() - 1);\n        H_out = Y.dim32(1);\n        W_out = Y.ndim() > 3 ? Y.dim32(2) : 1;\n        D_out = Y.ndim() > 4 ? Y.dim32(3) : 1;\n        break;\n      case StorageOrder::NCHW:\n        N = X.dim32(0);\n        C = X.dim32(1);\n        H = X.dim32(2);\n        W = X.ndim() > 3 ? X.dim32(3) : 1;\n        D = X.ndim() > 4 ? X.dim32(4) : 1;\n        H_out = Y.dim32(2);\n        W_out = Y.ndim() > 3 ? Y.dim32(3) : 1;\n        D_out = Y.ndim() > 4 ? Y.dim32(4) : 1;\n        break;\n      default:\n        LOG(FATAL) << \"Unknown storage order: \" << order_;\n    }\n\n    // Fast path for global pooling, as cudnn is slow. But only\n    // on float, because fp16 not supported for CUB.\n    if (std::is_same<T, float>::value) {\n      if (order_ == StorageOrder::NCHW && global_pooling_) {\n        if (mode_ == CUDNN_POOLING_AVERAGE_COUNT_EXCLUDE_PADDING) {\n          global_avgpool_backward_NCHW<float>\n              <<<CAFFE_GET_BLOCKS(dX->size()),\n                 CAFFE_CUDA_NUM_THREADS,\n                 0,\n                 context_.cuda_stream()>>>(\n                  N * C,\n                  H * W * D,\n                  dY.data<float>(),\n                  dX->mutable_data<float>());\n          return true;\n        }\n#if CUDNN_VERSION_MIN(6, 0, 0)\n        if (mode_ == CUDNN_POOLING_MAX ||\n            mode_ == CUDNN_POOLING_MAX_DETERMINISTIC) {\n#else\n        if (mode_ == CUDNN_POOLING_MAX) {\n#endif\n          global_maxpool_backward_NCHW<float>\n              <<<CAFFE_GET_BLOCKS(dX->size()),\n                 CAFFE_CUDA_NUM_THREADS,\n                 0,\n                 context_.cuda_stream()>>>(\n                  N * C,\n                  H * W * D,\n                  dY.data<float>(),\n                  dX->mutable_data<float>(),\n                  Y.data<float>(),\n                  X.data<float>());\n          return true;\n        }\n      }\n    }\n\n    if (kernel_.size() == 1) {\n      ConvPoolOpBase<CUDAContext>::ComputePads({H});\n    } else if (kernel_.size() == 2) {\n      ConvPoolOpBase<CUDAContext>::ComputePads({H, W});\n    } else if (kernel_.size() == 3) {\n      ConvPoolOpBase<CUDAContext>::ComputePads({H, W, D});\n    } else {\n      CAFFE_THROW(\"Unsupported kernel size :\", kernel_.size());\n    }\n\n    if (cudnn_input_dims_ != X.dims()) {\n      // Dimensions changed; we will need to re-initialize things.\n      VLOG(1) << \"Changing the cudnn descriptor configurations.\";\n      cudnn_input_dims_ = X.dims();\n      setTensorDescriptor<T>(X.ndim(), order_, N, C, H, W, D, bottom_desc_);\n      setTensorDescriptor<T>(\n          Y.ndim(), order_, N, C, H_out, W_out, D_out, top_desc_);\n      for (int i = 0; i < kernel_.size(); ++i) {\n        if (pads_[i] != pads_[kernel_.size() + i]) {\n          CAFFE_ENFORCE(\n              legacy_pad_ == LegacyPadding::CAFFE_LEGACY_POOLING,\n              \"Cudnn pooling only supports even padding on both sides, with \"\n              \"the only exception of the caffe legacy pooling case where we \"\n              \"try to preserve backward compatibility with Caffe.\");\n        }\n      }\n      if (kernel_.size() == 2) {\n        CUDNN_ENFORCE(cudnnSetPooling2dDescriptor(\n            pooling_desc_,\n            mode_,\n            CUDNN_NOT_PROPAGATE_NAN,\n            kernel_h(),\n            kernel_w(),\n            pad_t(),\n            pad_l(),\n            stride_h(),\n            stride_w()));\n      } else {\n        CUDNN_ENFORCE(cudnnSetPoolingNdDescriptor(\n            pooling_desc_,\n            mode_,\n            CUDNN_NOT_PROPAGATE_NAN,\n            kernel_.size(),\n            kernel_.data(),\n            pads_.data(),\n            stride_.data()));\n      }\n    }\n    // Carry out the pooling computation.\n    const T* Xdata = X.template data<T>();\n    const T* Ydata = Y.template data<T>();\n    const T* dYdata = dY.template data<T>();\n    T* dXdata = dX->template mutable_data<T>();\n\n    CUDNN_ENFORCE(cudnnPoolingBackward(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        pooling_desc_,\n        cudnnTypeWrapper<T>::kOne(),\n        top_desc_,\n        Ydata,\n        top_desc_,\n        dYdata,\n        bottom_desc_,\n        Xdata,\n        cudnnTypeWrapper<T>::kZero(),\n        bottom_desc_,\n        dXdata));\n    return true;\n  }\n\n  bool RunOnDevice() final {\n    auto& X = Input(0);\n    auto& Y = Input(1);\n    auto& dY = Input(2);\n    auto* dX = Output(0);\n    dX->ResizeLike(X);\n\n    if (X.IsType<float>()) {\n      return DoRunWithType<float, float>();\n    } else if (X.IsType<float16>()) {\n      return DoRunWithType<float16, float>();\n    } else {\n      LOG(FATAL) << \"Unsupported input types\";\n    }\n    return true;\n  }\n\n protected:\n  vector<TIndex> cudnn_input_dims_;\n\n  CuDNNWrapper cudnn_wrapper_;\n  cudnnTensorDescriptor_t bottom_desc_;\n  cudnnTensorDescriptor_t top_desc_;\n  cudnnPoolingDescriptor_t pooling_desc_;\n  cudnnPoolingMode_t mode_;\n};\n\nnamespace {\nREGISTER_CUDNN_OPERATOR(AveragePool, CuDNNPoolOp);\nREGISTER_CUDNN_OPERATOR(AveragePoolGradient, CuDNNPoolGradientOp);\n\nREGISTER_CUDNN_OPERATOR(AveragePool1D, CuDNNPoolOp);\nREGISTER_CUDNN_OPERATOR(AveragePool1DGradient, CuDNNPoolGradientOp);\n\nREGISTER_CUDNN_OPERATOR(AveragePool2D, CuDNNPoolOp);\nREGISTER_CUDNN_OPERATOR(AveragePool2DGradient, CuDNNPoolGradientOp);\n\nREGISTER_CUDNN_OPERATOR(AveragePool3D, CuDNNPoolOp);\nREGISTER_CUDNN_OPERATOR(AveragePool3DGradient, CuDNNPoolGradientOp);\n\nREGISTER_CUDNN_OPERATOR(MaxPool, CuDNNPoolOp);\nREGISTER_CUDNN_OPERATOR(MaxPoolGradient, CuDNNPoolGradientOp);\n\nREGISTER_CUDNN_OPERATOR(MaxPool1D, CuDNNPoolOp);\nREGISTER_CUDNN_OPERATOR(MaxPool1DGradient, CuDNNPoolGradientOp);\n\nREGISTER_CUDNN_OPERATOR(MaxPool2D, CuDNNPoolOp);\nREGISTER_CUDNN_OPERATOR(MaxPool2DGradient, CuDNNPoolGradientOp);\n\nREGISTER_CUDNN_OPERATOR(MaxPool3D, CuDNNPoolOp);\nREGISTER_CUDNN_OPERATOR(MaxPool3DGradient, CuDNNPoolGradientOp);\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/pow_op.cc",
    "content": "/**\n * Copyright (c) 2018-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/pow_op.h\"\n#include \"caffe2/utils/math.h\"\n// definition of NumericTypes and SameTypeAsInput is in below header file\n//#include \"caffe2/operators/elementwise_op.h\"\n#include <Eigen/Core>\n\nnamespace caffe2 {\n\n#define EIGEN_POW(x, y) (x.pow(y))\n\nstruct EigenPowFunctor {\n  template <int b_is_scalar, typename T1, typename T2, typename R>\n  inline void\n  Run(size_t n, const T1* a, const T2* b, T2 e, R* out, CPUContext*) {\n    if (b == NULL) {\n      EigenVectorArrayMap<R>(out, n) =\n          EIGEN_POW((ConstEigenVectorArrayMap<T1>(a, n)), (e));\n    } else {\n      if (b_is_scalar) {\n        EigenVectorArrayMap<R>(out, n) =\n            EIGEN_POW((ConstEigenVectorArrayMap<T1>(a, n)), (b[0]));\n      } else {\n        EigenVectorArrayMap<R>(out, n) = EIGEN_POW(\n            (ConstEigenVectorArrayMap<T1>(a, n)),\n            (ConstEigenVectorArrayMap<T2>(b, n)));\n      }\n    }\n  }\n  template <typename T1, typename T2, typename R>\n  void RunWithBroadcast(\n      const T1* a,\n      const T2* b,\n      R* out,\n      size_t pre,\n      size_t n,\n      CPUContext*) {\n    EigenArrayMap<R>(out, n, pre) = EIGEN_POW(\n        (ConstEigenArrayMap<T1>(a, n, pre)),\n        (ConstEigenVectorArrayMap<T2>(b, n)).rowwise().replicate(pre));\n    /*\n    //below code only allows elementary ops, such as +, -, * and /,\n    //and does not allow operations, such as pow, exp and log\n    EIGEN_POW(\n       (ConstEigenArrayMap<T>(a, n, pre).colwise()),\n       (ConstEigenVectorArrayMap<T>(b, n)));\n     */\n  }\n  template <typename T1, typename T2, typename R>\n  void RunWithBroadcast2(\n      const T1* a,\n      const T2* b,\n      R* out,\n      size_t pre,\n      size_t n,\n      size_t post,\n      CPUContext*) {\n    for (int i = 0; i < pre; ++i) {\n      EigenArrayMap<R>(out + i * n * post, post, n) = EIGEN_POW(\n          (ConstEigenArrayMap<T1>(a + i * n * post, post, n)),\n          (Eigen::Map<const Eigen::Array<T2, 1, Eigen::Dynamic>>(b, n))\n              .colwise()\n              .replicate(post));\n      /*\n      //below code only allows elementary ops, such as +, -, * and /,\n      //and does not allow for operations, such as pow, exp and log\n      EIEGN_POW(\n        (ConstEigenArrayMap<T>(a + i * n * post, post, n).rowwise()),\n        (Eigen::Map<const Eigen::Array<T, 1, Eigen::Dynamic>>(b, n)));\n      */\n    }\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    Pow,\n    PowOp<\n        TensorTypes<float> /*NumericTypes*/,\n        CPUContext,\n        EigenPowFunctor,\n        SameTypeAsInput>)\n\nOPERATOR_SCHEMA(Pow)\n    .NumInputs(1, 2)\n    .NumOutputs(1)\n    .Arg(\"exponent\", \"The exponent of the power function.\")\n    .AllowInplace({{0, 0}, {1, 0}})\n    .IdenticalTypeAndShapeOfInput(0)\n    .SetDoc(R\"DOC(\nPow takes input data (Tensor<T>) and an argument exponent, which can be a\nscalar or another tensor. It produces one output data (Tensor<T>), where\nthe function `f(x) = x^exponent` is applied to the data tensor elementwise.\n)DOC\")\n    .Input(0, \"X\", \"Input tensor of any shape\")\n    .Input(1, \"exponent\", \"The exponent of the power function.\")\n    .Output(0, \"Y\", \"Output tensor (same size as X)\");\n\nclass GetPowGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    ArgumentHelper arg_helper(def_);\n    if (arg_helper.HasArgument(\"exponent\")) { // second input is a scalar\n      // function f(w,a) = w^a\n      // gradient operator with respect to first input tensor\n      // df/dw = a * w^(a-1) (all operations are component-wise)\n      float exponent = arg_helper.GetSingleArgument<float>(\"exponent\", 0.0);\n      Argument scale_arg;\n      scale_arg.set_name(\"scale\");\n      scale_arg.set_f(exponent);\n      Argument pow_arg;\n      pow_arg.set_name(\"exponent\");\n      if (I(0) != O(0)) {\n        pow_arg.set_f(exponent - 1);\n      } else {\n        LOG(WARNING) << \"In-place Pow gradient, possible loss of precision\";\n        constexpr float kEps = 1e-12f;\n        CAFFE_ENFORCE(std::fabs(exponent) > kEps);\n        pow_arg.set_f((exponent - 1) / exponent);\n      }\n      return vector<OperatorDef>{CreateOperatorDef(\n                                     \"Pow\",\n                                     \"\",\n                                     std::vector<string>{I(0)},\n                                     std::vector<string>{GI(0)},\n                                     std::vector<Argument>{pow_arg}),\n                                 CreateOperatorDef(\n                                     \"Mul\",\n                                     \"\",\n                                     std::vector<string>{GI(0), GO(0)},\n                                     std::vector<string>{GI(0)}),\n                                 CreateOperatorDef(\n                                     \"Scale\",\n                                     \"\",\n                                     std::vector<string>{GI(0)},\n                                     std::vector<string>{GI(0)},\n                                     std::vector<Argument>{scale_arg})};\n      /*\n      // Alternative gradient computation\n      return vector<OperatorDef>{CreateOperatorDef(\n                                     \"Div\",\n                                     \"\",\n                                     std::vector<string>{O(0), I(0)},\n                                     std::vector<string>{GI(0)}),\n                                 CreateOperatorDef(\n                                     \"Mul\",\n                                     \"\",\n                                     std::vector<string>{GI(0), GO(0)},\n                                     std::vector<string>{GI(0)}),\n                                 CreateOperatorDef(\n                                     \"Scale\",\n                                     \"\",\n                                     std::vector<string>{GI(0)},\n                                     std::vector<string>{GI(0)},\n                                     std::vector<Argument>{scale_arg})};\n      */\n    } else { // second input is a tensor\n      CAFFE_ENFORCE(\n          Def().input(0) != Def().output(0) &&\n              Def().input(1) != Def().output(0),\n          \"Gradient computation cannot be carried out if Pow uses in-place \"\n          \"computation: \",\n          ProtoDebugString(Def()));\n      vector<OperatorDef> grad_ops;\n      Argument one_arg;\n      one_arg.set_name(\"value\");\n      one_arg.set_f(1);\n      Argument broadcast, axis, axis_str, order;\n      bool bflag = ArgumentHelper::HasArgument(Def(), \"broadcast\");\n\n      if (bflag) {\n        if (ArgumentHelper::HasArgument(Def(), \"broadcast\")) {\n          broadcast = GetArgument(Def(), \"broadcast\");\n        } else {\n          broadcast = MakeArgument<int>(\"broadcast\", 0);\n        }\n        if (ArgumentHelper::HasArgument(Def(), \"axis\")) {\n          axis = GetArgument(Def(), \"axis\");\n        } else {\n          axis = MakeArgument<int>(\"axis\", -1);\n        }\n        if (ArgumentHelper::HasArgument(Def(), \"axis_str\")) {\n          axis_str = GetArgument(Def(), \"axis_str\");\n        } else {\n          axis_str = MakeArgument<string>(\"axis_str\", \"\");\n        }\n        if (ArgumentHelper::HasArgument(Def(), \"order\")) {\n          order = GetArgument(Def(), \"order\");\n        } else {\n          order = MakeArgument<string>(\"order\", \"NCHW\");\n        }\n      }\n\n      // function f(w,a) = w^a\n      // gradient operator with respect to first input tensor\n      // df/dw = a * w^(a-1) (all operations are component-wise)\n      grad_ops.push_back(CreateOperatorDef(\n          \"ConstantFill\",\n          \"\",\n          std::vector<string>{I(1)},\n          std::vector<string>{GI(1)},\n          std::vector<Argument>{one_arg}));\n      grad_ops.push_back(CreateOperatorDef(\n          \"Sub\",\n          \"\",\n          std::vector<string>{I(1), GI(1)},\n          std::vector<string>{GI(1)}));\n      if (bflag) {\n        grad_ops.push_back(CreateOperatorDef(\n            \"Pow\",\n            \"\",\n            std::vector<string>{I(0), GI(1)},\n            std::vector<string>{GI(0)},\n            vector<Argument>{broadcast, axis, axis_str, order}));\n      } else {\n        grad_ops.push_back(CreateOperatorDef(\n            \"Pow\",\n            \"\",\n            std::vector<string>{I(0), GI(1)},\n            std::vector<string>{GI(0)}));\n      }\n\n      grad_ops.push_back(CreateOperatorDef(\n          \"Mul\",\n          \"\",\n          std::vector<string>{GI(0), GO(0)},\n          std::vector<string>{GI(0)}));\n      if (bflag) {\n        grad_ops.push_back(CreateOperatorDef(\n            \"Mul\",\n            \"\",\n            std::vector<string>{GI(0), I(1)},\n            std::vector<string>{GI(0)},\n            vector<Argument>{broadcast, axis, axis_str, order}));\n      } else {\n        grad_ops.push_back(CreateOperatorDef(\n            \"Mul\",\n            \"\",\n            std::vector<string>{GI(0), I(1)},\n            std::vector<string>{GI(0)}));\n      }\n      /*\n      // Alternative gradient computation (no broadcast support)\n      grad_ops.push_back(CreateOperatorDef(\n                           \"Div\",\n                           \"\",\n                           std::vector<string>{O(0), I(0)},\n                           std::vector<string>{GI(0)}));\n      grad_ops.push_back(CreateOperatorDef(\n                           \"Mul\",\n                           \"\",\n                           std::vector<string>{GI(0), GO(0)},\n                           std::vector<string>{GI(0)}));\n      grad_ops.push_back(CreateOperatorDef(\n                           \"Mul\",\n                           \"\",\n                           std::vector<string>{GI(0), I(1)},\n                           std::vector<string>{GI(0)}));\n      */\n      // gradient operator for with respect to second input tensor\n      // df/da =  w^a * ln w (all operations are component-wise)\n      /*\n      // reset GI(1) to zero\n      Argument zero_arg;\n      zero_arg.set_name(\"value\");\n      zero_arg.set_f(0);\n      grad_ops.push_back(CreateOperatorDef(\n          \"ConstantFill\",\n          \"\",\n          std::vector<string>{I(1)},\n          std::vector<string>{GI(1)},\n          std::vector<Argument>{zero_arg}));\n      */\n      grad_ops.push_back(CreateOperatorDef(\n          \"Log\",\n          \"\",\n          std::vector<string>{I(0)},\n          std::vector<string>{GI(1) + \"_autogen_pre_red\"}));\n      grad_ops.push_back(CreateOperatorDef(\n          \"Mul\",\n          \"\",\n          std::vector<string>{GI(1) + \"_autogen_pre_red\", O(0)},\n          std::vector<string>{GI(1) + \"_autogen_pre_red\"}));\n      if (bflag) {\n        grad_ops.push_back(CreateOperatorDef(\n            \"Mul\",\n            \"\",\n            std::vector<string>{GI(1) + \"_autogen_pre_red\", GO(0)},\n            std::vector<string>{GI(1) + \"_autogen_pre_red\"}));\n        grad_ops.push_back(CreateOperatorDef(\n            \"SumReduceLike\",\n            \"\",\n            vector<string>{GI(1) + \"_autogen_pre_red\", I(1)},\n            vector<string>{GI(1)},\n            vector<Argument>{axis, axis_str, order}));\n      } else {\n        grad_ops.push_back(CreateOperatorDef(\n            \"Mul\",\n            \"\",\n            std::vector<string>{GI(1) + \"_autogen_pre_red\", GO(0)},\n            std::vector<string>{GI(1)}));\n      }\n\n      return grad_ops;\n    }\n  }\n\n  // Argument `shape` is no longer needed in backprop.\n  bool CopyArguments() const override {\n    return false;\n  }\n};\n\nREGISTER_GRADIENT(Pow, GetPowGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/pow_op.cu",
    "content": "/**\n * Copyright (c) 2018-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define CUB_STDERR\n#include <cub/block/block_load.cuh>\n#include <cub/block/block_reduce.cuh>\n#include <cub/device/device_reduce.cuh>\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/pow_op.h\"\n#include \"caffe2/utils/conversions.h\"\n\nnamespace caffe2 {\n\n// pow, log and other math functions are defined in\n// CUDA math library in header file math.h\n#define CUDA_POW(x, y) (pow(x, y))\n\ntemplate <int b_is_scalar, typename T1, typename T2, typename R>\n__global__ void PowKernel(const T1* a, const T2* b, T2 e, R* out, int n) {\n  CUDA_1D_KERNEL_LOOP(i, n) {\n    out[i] = CUDA_POW(a[i], ((b == NULL) ? e : b[b_is_scalar ? 0 : i]));\n  }\n}\ntemplate <typename T1, typename T2, typename R>\n__global__ void\nPowBroadcastKernel(const T1* a, const T2* b, R* out, int pre, int n) {\n  CUDA_1D_KERNEL_LOOP(i, pre * n) {\n    out[i] = CUDA_POW(a[i], b[i % n]);\n  }\n}\ntemplate <typename T1, typename T2, typename R>\n__global__ void PowBroadcast2Kernel(\n    const T1* a,\n    const T2* b,\n    R* out,\n    int pre,\n    int n,\n    int post) {\n  CUDA_1D_KERNEL_LOOP(i, pre * n * post) {\n    out[i] = CUDA_POW(a[i], b[(i / post) % n]);\n  }\n}\n\nstruct CudaPowFunctor {\n  template <bool b_is_scalar, typename T1, typename T2, typename R>\n  inline void\n  Run(size_t n, const T1* a, const T2* b, T2 e, R* out, CUDAContext* context) {\n    PowKernel<b_is_scalar, T1, T2, R>\n        <<<CAFFE_GET_BLOCKS(n),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           context->cuda_stream()>>>(a, b, e, out, n);\n  }\n  template <typename T1, typename T2, typename R>\n  void RunWithBroadcast(\n      const T1* a,\n      const T2* b,\n      R* out,\n      size_t pre,\n      size_t n,\n      CUDAContext* context) {\n    PowBroadcastKernel<T1, T2, R>\n        <<<CAFFE_GET_BLOCKS(pre * n),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           context->cuda_stream()>>>(a, b, out, pre, n);\n  }\n  template <typename T1, typename T2, typename R>\n  void RunWithBroadcast2(\n      const T1* a,\n      const T2* b,\n      R* out,\n      size_t pre,\n      size_t n,\n      size_t post,\n      CUDAContext* context) {\n    PowBroadcast2Kernel<T1, T2, R>\n        <<<CAFFE_GET_BLOCKS(pre * n * post),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           context->cuda_stream()>>>(a, b, out, pre, n, post);\n  }\n};\nREGISTER_CUDA_OPERATOR(\n    Pow,\n    PowOp<\n        TensorTypes<float> /*NumericTypes*/,\n        CUDAContext,\n        CudaPowFunctor,\n        SameTypeAsInput>)\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/pow_op.h",
    "content": "/**\n * Copyright (c) 2018-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_POW_OP_H_\n#define CAFFE2_OPERATORS_POW_OP_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n// definition of NumericTypes and SameTypeAsInput is in below header file\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\ntemplate <\n    typename InputTypes,\n    class Context,\n    class Functor,\n    class TypeMap = SameTypeAsInput>\nclass PowOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  PowOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        OP_SINGLE_ARG(bool, \"broadcast\", enable_broadcast_, 0),\n        OP_SINGLE_ARG(int, \"axis\", axis_, -1),\n        OP_SINGLE_ARG(string, \"axis_str\", axis_str_, \"\"),\n        OP_SINGLE_ARG(string, \"order\", order_, \"NCHW\"),\n        functor_() {\n    if ((InputSize() == 1) && HasArgument(\"exponent\")) { // UnaryElementwiseOp\n      exponent_ = this->template GetSingleArgument<float>(\n          \"exponent\", 0); // based on pow_ops.h\n    } else if (InputSize() == 2) { // BinaryElementwiseOp\n      // Figure out the correct axis to use.\n      if (enable_broadcast_) {\n        if (axis_ != -1) {\n          // Get axis from an explicit axis argument.\n          CAFFE_ENFORCE_EQ(\n              axis_str_.size(),\n              0,\n              \"Args axis and axis_str cannot be used simultaneously.\");\n        } else if (axis_str_.size()) {\n          // Get the axis index semantically.\n          CAFFE_ENFORCE_EQ(\n              axis_str_.size(), 1, \"Unsupported axis string\", axis_str_);\n          size_t semantic_axis_ = order_.find(axis_str_);\n          CAFFE_ENFORCE_NE(\n              semantic_axis_,\n              string::npos,\n              \"Unrecognizable axis string \",\n              axis_str_,\n              \" from order string \",\n              order_);\n          axis_ = semantic_axis_;\n        }\n      } else {\n        CAFFE_ENFORCE(\n            axis_ == -1 && axis_str_.size() == 0,\n            \"Do not specify axis or axis_str if broadcast is not enabled.\");\n      }\n    } else {\n      CAFFE_THROW(\n          \"Only a tensor with an argument or two input tensors are supported as input to pow operator.\");\n    }\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<InputTypes>::call(this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    if ((InputSize() == 1) && HasArgument(\"exponent\")) { // UnaryElementwiseOp\n      const auto& A = Input(0);\n      auto* C = Output(0);\n      C->ResizeLike(A);\n      const T* Adata = A.template data<T>();\n      auto* Cdata =\n          C->template mutable_data<typename TypeMap::template type<T>>();\n      functor_.template Run<true, T, float, T>(\n          A.size(), Adata, NULL, exponent_, Cdata, &context_);\n    } else if (InputSize() == 2) { // BinaryElementwiseOp\n      const auto& A = Input(0);\n      const auto& B = Input(1);\n      auto* C = Output(0);\n      CAFFE_ENFORCE(\n          &B != C || !enable_broadcast_,\n          \"In-place is allowed only with the first tensor when broadcasting\");\n      C->ResizeLike(A);\n      const T* Adata = A.template data<T>();\n      const T* Bdata = B.template data<T>();\n      auto* Cdata =\n          C->template mutable_data<typename TypeMap::template type<T>>();\n      if (!enable_broadcast_) {\n        CAFFE_ENFORCE_EQ(\n            A.dims(),\n            B.dims(),\n            \"Dimension mismatch - did you forget to set broadcast=1?\");\n        functor_.template Run<false, T, T, T>(\n            A.size(), Adata, Bdata, 0, Cdata, &context_);\n      } else if (B.size() == 1) {\n        functor_.template Run<true, T, T, T>(\n            A.size(), Adata, Bdata, 0, Cdata, &context_);\n      } else {\n        size_t pre, n, post;\n        std::tie(pre, n, post) = calculate_broadcast_sizes(A, B, axis_);\n        if (post == 1) {\n          functor_.template RunWithBroadcast<T, T, T>(\n              Adata, Bdata, Cdata, pre, n, &context_);\n        } else {\n          functor_.template RunWithBroadcast2<T, T, T>(\n              Adata, Bdata, Cdata, pre, n, post, &context_);\n        }\n      }\n    } else {\n      CAFFE_THROW(\n          \"Only a tensor with an argument or two input tensors are supported as input to pow operator.\");\n    }\n    return true;\n  }\n\n private:\n  bool enable_broadcast_;\n  int axis_;\n  string axis_str_;\n  string order_;\n  float exponent_;\n  Functor functor_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_POW_OP_H_\n"
  },
  {
    "path": "caffe2/operators/prefetch_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_PREFETCH_OP_H_\n#define CAFFE2_OPERATORS_PREFETCH_OP_H_\n\n#include <condition_variable>\n#include <mutex>\n#include <thread> // NOLINT\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\n// PrefetchOperator is an operator that prefetches the next batch. It should\n// almost always be used to read things from disk, so I am setting the input to\n// zero blobs.\n//\n// For any operator that is derived from PrefetchOperator, it should\n// explicitly call the Finalize() function in its destructor, so that the\n// prefetching thread is properly destructed.\n\n// Note: We inherit from OperatorBase since we control the\n// synchronization properties of this operator ourselves (we inform\n// the waiting producer after we synchronize). This is a special-case\n// - you should generally inherit from Operator<Context> directly.\ntemplate <class Context>\nclass PrefetchOperator : public OperatorBase {\n public:\n  PrefetchOperator(const OperatorDef& operator_def, Workspace* ws)\n      : OperatorBase(operator_def, ws),\n        context_(operator_def.device_option()),\n        prefetched_(false),\n        prefetch_success_(true),\n        finalize_(false),\n        no_prefetch_(GetSingleArgument<bool>(\"no_prefetch\", false)) {\n    context_.SwitchToDevice(0);\n  }\n\n  virtual ~PrefetchOperator() noexcept {\n    CHECK(finalize_ || !prefetch_thread_.get()) <<\n        \"YOU MADE A PROGRAMING ERROR: derived class of PrefetchOperator \"\n        \"should call Finalize() in its destructor so the prefetching \"\n        \"thread is joined. \";\n  }\n\n  void Finalize() {\n    if (prefetch_thread_.get()) {\n      {\n        std::unique_lock<std::mutex> lock(prefetch_access_mutex_);\n        while (!prefetched_)\n          consumer_.wait(lock);\n        finalize_ = true;\n        prefetched_ = false;\n      }\n      producer_.notify_one();\n      prefetch_thread_->join();\n      prefetch_thread_.reset();\n    } else {\n      // If we never initialized the prefetch thread, just set\n      // finalize anyway.\n      finalize_ = true;\n    }\n  }\n\n  bool Run(int /* unused */ /*stream_id*/) override {\n    if (no_prefetch_) {\n      context_.SwitchToDevice(0);\n      bool result = Prefetch() && CopyPrefetched();\n      context_.FinishDeviceComputation();\n      return result;\n    }\n    // Note(jiayq): We only start the prefetch_thread at the Run() function\n    // instead of in the constructor, because the prefetch_thread needs to start\n    // after all derived classes' constructors finish.\n    if (!prefetch_thread_) {\n      prefetch_thread_.reset(\n          new std::thread([this] { this->PrefetchWorker(); }));\n    }\n    context_.SwitchToDevice(0);\n    std::unique_lock<std::mutex> lock(prefetch_access_mutex_);\n    while (!prefetched_)\n      consumer_.wait(lock);\n    if (!prefetch_success_) {\n      LOG(ERROR) << \"Prefetching failed.\";\n      return false;\n    }\n    if (!CopyPrefetched()) {\n      LOG(ERROR) << \"Error when copying prefetched data.\";\n      return false;\n    }\n    prefetched_ = false;\n    context_.FinishDeviceComputation();\n    producer_.notify_one();\n    return true;\n  }\n\n  void PrefetchWorker() {\n    context_.SwitchToDevice();\n    std::unique_lock<std::mutex> lock(prefetch_access_mutex_);\n    while (prefetched_)\n      producer_.wait(lock);\n    while (!finalize_) {\n      // We will need to run a FinishDeviceComputation() call because the\n      // prefetcher thread and the main thread are potentially using different\n      // streams (like on GPU).\n      try {\n        prefetch_success_ = Prefetch();\n        context_.FinishDeviceComputation();\n      } catch (const std::exception& e) {\n        // TODO: propagate exception_ptr to the caller side\n        LOG(ERROR) << \"Prefetching error \" << e.what();\n        prefetch_success_ = false;\n      }\n      prefetched_ = true;\n      consumer_.notify_one();\n      while (prefetched_)\n        producer_.wait(lock);\n    }\n  }\n\n  // You will need to implement this instead of the Run function.\n  virtual bool Prefetch() = 0;\n  virtual bool CopyPrefetched() = 0;\n\n protected:\n  Context context_;\n  std::mutex prefetch_access_mutex_;\n  std::condition_variable producer_, consumer_;\n  // prefetched_ is used to tell the operator that it is done.\n  std::atomic<bool> prefetched_;\n  // prefetch_success_ is used to see if prefetching failed or not.\n  std::atomic<bool> prefetch_success_;\n  // finalize_ is used to tell the prefetcher to quit.\n  std::atomic<bool> finalize_;\n  unique_ptr<std::thread> prefetch_thread_;\n\n  // Whether to do prefetching or run this as a normal operator\n  const bool no_prefetch_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_PREFETCH_OP_H_\n"
  },
  {
    "path": "caffe2/operators/prelu_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/prelu_op.h\"\n#include \"caffe2/utils/math.h\"\n\n#include \"caffe2/core/types.h\"\n#include \"caffe2/utils/cpu_neon.h\"\n\nnamespace caffe2 {\n\n#ifdef __ARM_NEON__\nnamespace {\n\nvoid runNeonPrelu(float* out, const float* in, int size, float w) {\n  float32x4_t vZero = vdupq_n_f32(0.0f);\n  float32x4_t vW = vdupq_n_f32(w);\n\n  constexpr int kVecSizeInFloat = sizeof(float32x4_t) / sizeof(float);\n\n  if (size < kVecSizeInFloat) {\n    for (int i = 0; i < size; ++i) {\n      float v = in[i];\n      out[i] = v > 0 ? v : v * w;\n    }\n\n    return;\n  }\n\n  // We want to load aligned from the input, but assume the output is unaligned\n  int prologue =\n    kVecSizeInFloat -\n    // remainder in floats\n    (((uintptr_t) in) % (sizeof(float32x4_t))) / sizeof(float);\n\n  int i = 0;\n\n  // Prologue loop\n  for (; i < prologue; ++i) {\n    float v = in[i];\n    out[i] = v > 0 ? v : v * w;\n  }\n\n  // The loop is manually unrolled by 6; seems to be the limit for\n  // armv7 to avoid register spills\n  constexpr int kUnroll = 6;\n  constexpr int kFloatsPerLoop = kUnroll * kVecSizeInFloat;\n\n  int remainder = size - prologue;\n  int vectorizable = prologue + (remainder / kFloatsPerLoop) * kFloatsPerLoop;\n\n  for (; i < vectorizable; i += kFloatsPerLoop) {\n    float32x4_t v0 = vld1q_f32_aligned(in + i + 0);\n    float32x4_t v1 = vld1q_f32_aligned(in + i + 4);\n    float32x4_t v2 = vld1q_f32_aligned(in + i + 8);\n    float32x4_t v3 = vld1q_f32_aligned(in + i + 12);\n    float32x4_t v4 = vld1q_f32_aligned(in + i + 16);\n    float32x4_t v5 = vld1q_f32_aligned(in + i + 20);\n\n    uint32x4_t gz0 = vcgtq_f32(v0, vZero);\n    uint32x4_t gz1 = vcgtq_f32(v1, vZero);\n    uint32x4_t gz2 = vcgtq_f32(v2, vZero);\n    uint32x4_t gz3 = vcgtq_f32(v3, vZero);\n    uint32x4_t gz4 = vcgtq_f32(v4, vZero);\n    uint32x4_t gz5 = vcgtq_f32(v5, vZero);\n\n    float32x4_t v0neg = vmulq_f32(v0, vW);\n    float32x4_t v1neg = vmulq_f32(v1, vW);\n    float32x4_t v2neg = vmulq_f32(v2, vW);\n    float32x4_t v3neg = vmulq_f32(v3, vW);\n    float32x4_t v4neg = vmulq_f32(v4, vW);\n    float32x4_t v5neg = vmulq_f32(v5, vW);\n\n    // v0 > 0 ? v0 : v0 * w\n    v0 = vbslq_f32(gz0, v0, v0neg);\n    v1 = vbslq_f32(gz1, v1, v1neg);\n    v2 = vbslq_f32(gz2, v2, v2neg);\n    v3 = vbslq_f32(gz3, v3, v3neg);\n    v4 = vbslq_f32(gz4, v4, v4neg);\n    v5 = vbslq_f32(gz5, v5, v5neg);\n\n    vst1q_f32(out + i + 0, v0);\n    vst1q_f32(out + i + 4, v1);\n    vst1q_f32(out + i + 8, v2);\n    vst1q_f32(out + i + 12, v3);\n    vst1q_f32(out + i + 16, v4);\n    vst1q_f32(out + i + 20, v5);\n  }\n\n  for (; i < size; ++i) {\n    float v = in[i];\n    out[i] = v > 0 ? v : v * w;\n  }\n}\n\n}\n#endif // __ARM_NEON__\n\ntemplate <>\nbool PReluOp<float, CPUContext>::RunOnDevice() {\n  const auto& X = Input(0);\n  const auto& W = Input(1);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n  const auto* Xdata = X.template data<float>();\n  const auto* Wdata = W.template data<float>();\n  auto* Ydata = Y->template mutable_data<float>();\n\n  const auto C = order_ == StorageOrder::NCHW ? X.dim(1) : X.dim(X.ndim() - 1);\n  const auto C_shared = (W.size() == 1);\n\n  if (!C_shared) {\n    CAFFE_ENFORCE_EQ(C, W.size());\n  }\n\n  if (C_shared) {\n#ifdef __ARM_NEON__\n    // The function is completely pointwise\n    runNeonPrelu(Ydata, Xdata, X.size(), Wdata[0]);\n#else\n    ConstEigenVectorMap<float> Xvec(Xdata, X.size());\n    EigenVectorMap<float> Yvec(Ydata, Y->size());\n    Yvec = Xvec.cwiseMax(0.f) + Xvec.cwiseMin(0.f) * Wdata[0];\n#endif // __ARM_NEON__\n    return true;\n  }\n\n  // non-shared case.\n  switch (order_) {\n    case StorageOrder::NCHW: {\n      const auto N = X.dim(0);\n      const auto dim = X.size_from_dim(2);\n\n#ifdef __ARM_NEON__\n      // Pointwise for each channel\n      for (int n = 0; n < N; ++n) {\n        for (int c = 0; c < C; ++c) {\n          runNeonPrelu(Ydata + (n * C + c) * dim,\n                       Xdata + (n * C + c) * dim,\n                       dim, Wdata[c]);\n        }\n      }\n#else\n      int nc = 0;\n      for (int n = 0; n < N; ++n) {\n        for (int c = 0; c < C; ++c) {\n          ConstEigenVectorMap<float> Xvec(Xdata + nc * dim, dim);\n          EigenVectorMap<float>(Ydata + nc * dim, dim) =\n              Xvec.cwiseMax(0.f) + Xvec.cwiseMin(0.f) * Wdata[c];\n          nc++;\n        }\n      }\n#endif\n      break;\n    }\n    case StorageOrder::NHWC: {\n      // Lay out matrix as (NHW, C) and multiply by C\n      const auto NHW = X.size() / C;\n      ConstEigenArrayMap<float> Xmat(Xdata, C, NHW);\n      ConstEigenVectorArrayMap<float> Wvec(Wdata, C);\n      EigenArrayMap<float> Ymat(Ydata, C, NHW);\n      Ymat = (Xmat > 0).select(Xmat, Xmat.colwise() * Wvec);\n      break;\n    }\n    default:\n      CAFFE_THROW(\"Unknown storage order: \", order_);\n  }\n  return true;\n}\n\ntemplate <>\nbool PReluGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto& X = Input(2);\n  auto& W = Input(3);\n\n  CAFFE_ENFORCE(&Y != &X, \"Cannot backpropagate through an in-place PReLU\");\n  auto* dX = Output(0);\n  auto* dW = Output(1);\n\n  DCHECK_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n  dW->ResizeLike(W);\n\n  const auto C = order_ == StorageOrder::NCHW ? X.dim(1) : X.dim(X.ndim() - 1);\n  const auto C_shared = (W.size() == 1);\n\n  const float* Ydata = Y.data<float>();\n  const float* dYdata = dY.data<float>();\n  const float* Xdata = X.data<float>();\n  const float* Wdata = W.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  float* dWdata = dW->mutable_data<float>();\n\n  // non-shared case.\n  switch (order_) {\n    case StorageOrder::NCHW: {\n      const auto dim = X.size_from_dim(2);\n      const auto div_factor = C_shared ? C : 1;\n      for (auto c = 0; c < W.size(); ++c) {\n        dWdata[c] = 0;\n      }\n\n      for (int i = 0; i < Y.size(); ++i) {\n        if (Xdata[i] <= 0) {\n          int c = (i / dim) % C / div_factor;\n          dWdata[c] += dYdata[i] * Xdata[i];\n        }\n      }\n\n      for (int i = 0; i < Y.size(); ++i) {\n        if (Xdata[i] > 0) {\n          dXdata[i] = dYdata[i];\n        } else {\n          int c = (i / dim) % C / div_factor;\n          dXdata[i] = Wdata[c] * dYdata[i];\n        }\n      }\n      break;\n    }\n    case StorageOrder::NHWC: {\n      const auto NHW = X.size() / C;\n      ConstEigenVectorArrayMap<float> Wvec(Wdata, W.size());\n      EigenVectorArrayMap<float> dWvec(dWdata, dW->size());\n\n      ConstEigenArrayMap<float> Ymat(Ydata, C, NHW);\n      ConstEigenArrayMap<float> dYmat(dYdata, C, NHW);\n      ConstEigenArrayMap<float> Xmat(Xdata, C, NHW);\n      EigenArrayMap<float> dXmat(dXdata, C, NHW);\n\n      if (C_shared) {\n        dXmat = (Xmat > 0).select(dYmat, dYmat * Wdata[0]);\n        dWdata[0] =\n            (Xmat > 0)\n                .select(\n                    Xmat.cwiseMin(0.0f), // zero gradients on the 'if' path.\n                    dYmat * Xmat)\n                .sum();\n      } else {\n        dXmat = (Xmat > 0).select(dYmat, dYmat.colwise() * Wvec);\n        dWvec = (Xmat > 0)\n                    .select(\n                        Xmat.cwiseMin(0.0f), // zero gradients on the 'if' path.\n                        dYmat * Xmat)\n                    .rowwise()\n                    .sum();\n      }\n      break;\n    }\n    default:\n      CAFFE_THROW(\"Unknown storage order: \", order_);\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(PRelu, PReluOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(PReluGradient, PReluGradientOp<float, CPUContext>);\n\n// Input: X, Slope, output: Y\nOPERATOR_SCHEMA(PRelu)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShapeOfInput(0)\n    .SetDoc(R\"DOC(\n\nPRelu takes input data (Tensor<T>) and slope tensor as input, and produces one\noutput data (Tensor<T>) where the function `f(x) = slope * x for x < 0`,\n`f(x) = x for x >= 0`., is applied to the data tensor elementwise.\n\n)DOC\")\n    .Input(0, \"X\", \"1D input tensor\")\n    .Input(\n        1,\n        \"Slope\",\n        \"1D slope tensor. If `Slope` is of size 1, the value is shared\"\n        \"across different channels\")\n    .Output(0, \"Y\", \"1D input tensor\");\n\n// Input: Y, dY, output: dX\nOPERATOR_SCHEMA(PReluGradient).NumInputs(4).NumOutputs(2).SetDoc(R\"DOC(\n\nPReluGradient takes both Y and dY and uses this to update dX and dW according\nto the chain rule and derivatives of the rectified linear function.\n\n)DOC\");\n\nclass GetPReluGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        def_.type() + \"Gradient\",\n        \"\",\n        vector<string>{O(0), GO(0), I(0), I(1)},\n        vector<string>{GI(0), GI(1)});\n  }\n};\nREGISTER_GRADIENT(PRelu, GetPReluGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/prelu_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/prelu_op.h\"\n\n#include <cub/block/block_reduce.cuh>\n\nnamespace caffe2 {\nnamespace {\ntemplate <typename T>\n__global__ void PReluKernel(const int N, const T* X, const T* W, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = (X[i] > 0) * X[i] + (X[i] < 0) * X[i] * W[0];\n  }\n}\n\ntemplate <typename T>\n__global__ void PReluKernelNCHW(\n    const int N,\n    const int C,\n    const int dim,\n    const T* X,\n    const T* W,\n    T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N * C * dim) {\n    int c = (i / dim) % C;\n    Y[i] = (X[i] > 0) * X[i] + (X[i] < 0) * X[i] * W[c];\n  }\n}\n\ntemplate <typename T>\n__global__ void\nPReluKernelNHWC(const int nitems, const int C, const T* X, const T* W, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, nitems) {\n    int c = i % C;\n    Y[i] = (X[i] > 0) * X[i] + (X[i] < 0) * X[i] * W[c];\n  }\n}\n\ntemplate <typename T>\n__global__ void\nPReluGradientKernel(const int N, const T* X, const T* W, const T* dY, T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dX[i] = (X[i] > 0) * dY[i] + (X[i] <= 0) * dY[i] * W[0];\n  }\n}\n\ntemplate <typename T>\n__global__ void PReluGradientKernelNCHW(\n    const int N,\n    const int C,\n    const int dim,\n    const T* X,\n    const T* W,\n    const T* dY,\n    T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, N * C * dim) {\n    int c = (i / dim) % C;\n    dX[i] = (X[i] > 0) * dY[i] + (X[i] <= 0) * dY[i] * W[c];\n  }\n}\n\ntemplate <typename T>\n__global__ void PReluGradientKernelNHWC(\n    const int nitems,\n    const int C,\n    const T* X,\n    const T* W,\n    const T* dY,\n    T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, nitems) {\n    int c = i % C;\n    dX[i] = (X[i] > 0) * dY[i] + (X[i] <= 0) * dY[i] * W[c];\n  }\n}\n\ntemplate <typename T>\n__global__ void PReluSharedWGradientKernelNCHW(\n    const int num_items,\n    const T* Xdata,\n    const T* dYdata,\n    T* dW) {\n  T wsum = 0.0;\n  for (int i = threadIdx.x; i < num_items; i += blockDim.x) {\n    wsum += (Xdata[i] <= 0) * dYdata[i] * Xdata[i];\n  }\n\n  typedef cub::BlockReduce<T, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  T sum = BlockReduce(temp_storage).Sum(wsum);\n  if (threadIdx.x == 0) {\n    *dW = sum;\n  }\n}\n\ntemplate <typename T>\n__global__ void PReluWGradientKernelNCHW(\n    const int C,\n    const int N,\n    const int num_items,\n    const T* Xdata,\n    const T* dYdata,\n    T* dW) {\n  int c = blockIdx.x;\n\n  T wsum = 0.0;\n  int items_per_channel = num_items / C;\n  int items_per_sample_channel = items_per_channel / N;\n  for (int i = threadIdx.x; i < items_per_channel; i += blockDim.x) {\n    // TODO: simplify\n    int n = i / items_per_sample_channel;\n    int ii = n * items_per_sample_channel * C + c * items_per_sample_channel +\n        i % items_per_sample_channel;\n    wsum += (Xdata[ii] <= 0) * dYdata[ii] * Xdata[ii];\n  }\n\n  typedef cub::BlockReduce<T, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  T sum = BlockReduce(temp_storage).Sum(wsum);\n  if (threadIdx.x == 0) {\n    dW[c] = sum;\n  }\n}\n\ntemplate <typename T>\n__global__ void PReluWGradientKernelNHWC(\n    const int C,\n    const int N,\n    const int num_items,\n    const T* Xdata,\n    const T* dYdata,\n    T* dW) {\n  int c = blockIdx.x;\n  T wsum = 0.0;\n  int items_per_channel = num_items / C;\n  for (int i = threadIdx.x; i < items_per_channel; i += blockDim.x) {\n    int ii = i * C + c;\n    wsum += (Xdata[ii] <= 0) * dYdata[ii] * Xdata[ii];\n  }\n\n  typedef cub::BlockReduce<T, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  T sum = BlockReduce(temp_storage).Sum(wsum);\n  if (threadIdx.x == 0) {\n    dW[c] = sum;\n  }\n}\n\n} // namespace\n\ntemplate <>\nbool PReluOp<float, CUDAContext>::RunOnDevice() {\n  const auto& X = Input(0);\n  const auto& W = Input(1);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n  const auto* Xdata = X.data<float>();\n  const auto* Wdata = W.data<float>();\n  auto* Ydata = Y->mutable_data<float>();\n\n  const auto C = order_ == StorageOrder::NCHW ? X.dim(1) : X.dim(X.ndim() - 1);\n  const auto C_shared = (W.size() == 1);\n\n  if (!C_shared) {\n    CAFFE_ENFORCE_EQ(C, W.size());\n  }\n  if (C_shared) {\n    PReluKernel<<<\n        CAFFE_GET_BLOCKS(X.size()),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(X.size(), Xdata, Wdata, Ydata);\n    return true;\n  }\n  // non-shared case.\n  switch (order_) {\n    case StorageOrder::NCHW: {\n      const auto N = X.dim(0);\n      const auto dim = X.size_from_dim(2);\n      CHECK(N * C * dim == X.size());\n      PReluKernelNCHW<<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(N, C, dim, Xdata, Wdata, Ydata);\n\n      break;\n    }\n    case StorageOrder::NHWC: {\n      PReluKernelNHWC<<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(X.size(), C, Xdata, Wdata, Ydata);\n\n      break;\n    }\n    default:\n      CAFFE_THROW(\"Unknown storage order: \", order_);\n  }\n  return true;\n}\n\ntemplate <>\nbool PReluGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto& X = Input(2);\n  auto& W = Input(3);\n\n  CAFFE_ENFORCE(&Y != &X, \"Cannot backpropagate through an in-place PReLU\");\n  auto* dX = Output(0);\n  auto* dW = Output(1);\n\n  DCHECK_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n  dW->ResizeLike(W);\n\n  const auto C = order_ == StorageOrder::NCHW ? X.dim(1) : X.dim(X.ndim() - 1);\n  const auto C_shared = (W.size() == 1);\n\n  const float* Ydata = Y.data<float>();\n  const float* dYdata = dY.data<float>();\n  const float* Xdata = X.data<float>();\n  const float* Wdata = W.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  float* dWdata = dW->mutable_data<float>();\n  int N = Y.dim(0);\n\n  if (C_shared) {\n    PReluSharedWGradientKernelNCHW<<<\n        1,\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(X.size(), Xdata, dYdata, dWdata);\n    PReluGradientKernel<<<\n        CAFFE_GET_BLOCKS(X.size()),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(X.size(), Xdata, Wdata, dYdata, dXdata);\n\n    return true;\n  }\n  // non-shared case.\n  switch (order_) {\n    case StorageOrder::NCHW: {\n      const auto dim = Y.size_from_dim(2);\n      PReluWGradientKernelNCHW<<<\n          C,\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(C, N, X.size(), Xdata, dYdata, dWdata);\n      PReluGradientKernelNCHW<<<\n          CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(N, C, dim, Xdata, Wdata, dYdata, dXdata);\n\n      break;\n    }\n    case StorageOrder::NHWC: {\n      PReluWGradientKernelNHWC<<<\n          C,\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(C, N, X.size(), Xdata, dYdata, dWdata);\n      PReluGradientKernelNHWC<<<\n          CAFFE_GET_BLOCKS(Y.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(X.size(), C, Xdata, Wdata, dYdata, dXdata);\n\n      break;\n    }\n    default:\n      CAFFE_THROW(\"Unknown storage order: \", order_);\n  }\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(PRelu, PReluOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(PReluGradient, PReluGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/prelu_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass PReluOp final : public Operator<Context> {\n public:\n  PReluOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {}\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  StorageOrder order_;\n};\n\ntemplate <typename T, class Context>\nclass PReluGradientOp final : public Operator<Context> {\n public:\n  PReluGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  StorageOrder order_;\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/prepend_dim_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/prepend_dim_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(PrependDim, PrependDimOp<CPUContext>);\nREGISTER_CPU_OPERATOR(MergeDim, MergeDimOp<CPUContext>);\n\nOPERATOR_SCHEMA(PrependDim)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nReshape the tensor by prepending a dimension of fixed size and dividing the\nsize of the next dimension by that amount.\n)DOC\")\n    .Arg(\"dim_size\", \"Size of the dimension to prepend.\")\n    .Input(0, \"data\", \"An input tensor.\")\n    .Output(0, \"reshaped\", \"Reshaped tensor.\");\n\nOPERATOR_SCHEMA(MergeDim)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nMerge first two dimensions in a single dimension with size dim(0) * dim(1).\n)DOC\")\n    .Input(0, \"data\", \"An input tensor.\")\n    .Output(0, \"reshaped\", \"Reshaped tensor.\");\n\nclass GetPrependDimGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"MergeDim\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n  }\n\n  // Arguments are no longer needed in backprop.\n  bool CopyArguments() const override {\n    return false;\n  }\n};\n\nREGISTER_GRADIENT(PrependDim, GetPrependDimGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/prepend_dim_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#ifndef CAFFE2_OPERATORS_PREPEND_DIM_OP_H_\n#define CAFFE2_OPERATORS_PREPEND_DIM_OP_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass PrependDimOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  PrependDimOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        dim_size_(OperatorBase::GetSingleArgument<int64_t>(\"dim_size\", 0)) {\n    CAFFE_ENFORCE_GT(\n        dim_size_, 0, \"Argument dim_size must be greater than zero.\");\n  }\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE(input.ndim() > 0, \"Input must be at least 1D.\");\n    CAFFE_ENFORCE(\n        input.dim(0) % dim_size_ == 0,\n        \"First dimension must be multiple of prepend_dim.\");\n\n    vector<int64_t> actual_new_shape(input.ndim() + 1);\n    actual_new_shape[0] = dim_size_;\n    actual_new_shape[1] = input.dim(0) / dim_size_;\n    for (int i = 1; i < input.dims().size(); ++i) {\n      actual_new_shape[i + 1] = input.dim(i);\n    }\n    output->Resize(actual_new_shape);\n\n    if (output != &input) {\n      // If we are not doing in-place computation, a copy is needed.\n      context_.template CopyItems<Context, Context>(\n          input.meta(),\n          input.size(),\n          input.raw_data(),\n          output->raw_mutable_data(input.meta()));\n    }\n    return true;\n  }\n\n private:\n  int64_t dim_size_;\n};\n\ntemplate <class Context>\nclass MergeDimOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  MergeDimOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE(input.ndim() > 1, \"Input must be at least 2D.\");\n\n    vector<int64_t> actual_new_shape(input.ndim() - 1);\n    actual_new_shape[0] = input.dim(0) * input.dim(1);\n    for (int i = 1; i < input.dims().size() - 1; ++i) {\n      actual_new_shape[i] = input.dim(i + 1);\n    }\n    output->Resize(actual_new_shape);\n\n    if (output != &input) {\n      // If we are not doing in-place computation, a copy is needed.\n      context_.template CopyItems<Context, Context>(\n          input.meta(),\n          input.size(),\n          input.raw_data(),\n          output->raw_mutable_data(input.meta()));\n    }\n    return true;\n  }\n\n private:\n  int64_t dim_size_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_PREPEND_DIM_OP_H_\n"
  },
  {
    "path": "caffe2/operators/prepend_dim_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/prepend_dim_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(PrependDim, PrependDimOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(MergeDim, MergeDimOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/quant_decode_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"quant_decode_op.h\"\n#include <stdint.h>\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/typeid.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(QuantDecode, QuantDecodeOp<QuantDecodeRunTy::RUN_ALWAYS>);\nREGISTER_CPU_OPERATOR(QuantDecodeGradient, QuantDecodeGradientOp);\n#ifdef CAFFE2_USE_MPSCNN\nREGISTER_CPU_OPERATOR(\n    MPSCNNQuantDecode,\n    QuantDecodeOp<QuantDecodeRunTy::RUN_ONCE>);\n#endif\n\nOPERATOR_SCHEMA(QuantDecode)\n    .NumInputsOutputs([](int in, int out) { return in > 1 && out + 1 == in; })\n    .SetDoc(R\"DOC(\nDecode inputs using codebook. This is a general LUT operator that returns\ntensors with values from codebook (input 0) based on given indices in\ncodes (input 1 ~ n).\n\n\nExample:\n\n\nInput:\n  codebook = [1.5, 2.5, 3.5]\n  codes_0 = [0, 1, 1, 2]\n  codes_1 = [2, 0, 0]\n\n\nOutput:\n  decoded_0 = [1.5, 2.5, 2.5, 3.5]\n  decoded_1 = [3.5, 1.5, 1.5]\n)DOC\")\n    .Input(0, \"codebook\", \"Codebook in 1d tensor (float)\")\n    .Input(1, \"codes_0\", \"Encoded codes 0 (uint8/uint16/int32)\")\n    .Input(2, \"codes_1\", \"Encoded codes 1 if existed (uint8/uint16/int32)\")\n    .Input(3, \"codes_n\", \"Encoded codes n if existed (uint8/uint16/int32)\")\n    .Output(0, \"decoded_0\", \"Decoded tensor for codes_0 (float)\")\n    .Output(1, \"decoded_1\", \"Decoded tensor for codes_1 (float)\")\n    .Output(2, \"decoded_n\", \"Decoded tensor for codes_n (float)\");\n\nOPERATOR_SCHEMA(QuantDecodeGradient)\n    .NumInputs([](int in) { return in >= 3 && in % 2 == 1; })\n    .NumOutputs(1);\n\nclass GetQuantDecodeGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE_EQ(Def().input_size(), Def().output_size() + 1);\n    vector<string> gradient_op_inputs;\n    for (int i = 0; i < Def().input_size(); i++) {\n      gradient_op_inputs.push_back(I(i));\n    }\n    for (int i = 0; i < Def().output_size(); i++) {\n      gradient_op_inputs.push_back(GO(i));\n    }\n    return SingleGradientDef(\n        \"QuantDecodeGradient\", \"\", gradient_op_inputs, vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(QuantDecode, GetQuantDecodeGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/quant_decode_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef QUANT_DECODE_OP_H_\n#define QUANT_DECODE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/typeid.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <class CodebookT, class CodeT>\nvoid Decode(\n    const TensorCPU& codebook,\n    const TensorCPU& codes,\n    /* optional */ const TensorCPU* const decoded_grad,\n    TensorCPU* const output,\n    bool resizeOnly) {\n  CAFFE_ENFORCE(codebook.IsType<CodebookT>());\n\n  auto* cb_ptr = codebook.data<CodebookT>();\n  int cb_size = codebook.size();\n\n  CAFFE_ENFORCE(codes.IsType<CodeT>());\n  auto* code_ptr = codes.data<CodeT>();\n\n  if (decoded_grad == nullptr) {\n    // Forward pass: decode and store codebook values in output.\n    output->ResizeLike(codes);\n    auto* out_ptr = output->mutable_data<CodebookT>();\n    if (resizeOnly) {\n      return;\n    }\n\n    int sz = output->size();\n    for (int i = 0; i < sz; i++) {\n      DCHECK_LE(*code_ptr, cb_size);\n      *out_ptr++ = cb_ptr[*code_ptr++];\n    }\n  } else {\n    // Backward pass: decode and accumulate gradient w.r.t. codebook values.\n    CAFFE_ENFORCE_EQ(codes.size(), decoded_grad->size());\n    auto* gradient_ptr = decoded_grad->data<CodebookT>();\n    auto* const gradient_end = gradient_ptr + decoded_grad->size();\n\n    CAFFE_ENFORCE_EQ(cb_size, output->size());\n    auto* out_ptr = output->mutable_data<CodebookT>();\n    while (gradient_ptr < gradient_end) {\n      DCHECK_LE(*code_ptr, cb_size);\n      out_ptr[*code_ptr++] += *gradient_ptr++;\n    }\n  }\n}\n\n#define REGISTER_DECODER(codebookType, codesType)                      \\\n  {                                                                    \\\n    {TypeMeta::Id<codebookType>(), TypeMeta::Id<codesType>()},         \\\n        [](const TensorCPU& codebook_,                                 \\\n           const TensorCPU& codes_,                                    \\\n           const TensorCPU* gradient_,                                 \\\n           TensorCPU* outDecoded_,                                     \\\n           bool resizeOnly_) {                                         \\\n          Decode<codebookType, codesType>(                             \\\n              codebook_, codes_, gradient_, outDecoded_, resizeOnly_); \\\n        }                                                              \\\n  }\n\ninline void DecodeGeneral(\n    const TensorCPU& codebook,\n    const TensorCPU& codes,\n    const TensorCPU* gradient,\n    TensorCPU* outDecoded,\n    bool resizeOnly) {\n  const static std::map<\n      std::pair<CaffeTypeId, CaffeTypeId>,\n      std::function<void(\n          const TensorCPU& codebook,\n          const TensorCPU& codes,\n          const TensorCPU* gradient,\n          TensorCPU* outDecoded,\n          bool resizeOnly)>>\n      gDecoderMapper = {REGISTER_DECODER(float, uint8_t),\n                        REGISTER_DECODER(float, uint16_t),\n                        REGISTER_DECODER(float, int32_t)};\n\n  gDecoderMapper.at({codebook.meta().id(), codes.meta().id()})(\n      codebook, codes, gradient, outDecoded, resizeOnly);\n}\n\n} // namespace\n\n// Decode tensors based on given codebook,\n// The codebook is generated by model_quantize.py\n\nenum class QuantDecodeRunTy {\n  RUN_ALWAYS,\n  RUN_ONCE,\n};\n\ntemplate <QuantDecodeRunTy QuantDecodeRun>\nclass QuantDecodeOp final : public Operator<CPUContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CPUContext);\n  QuantDecodeOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  ~QuantDecodeOp() {}\n\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE_GT(InputSize(), 1);\n    // first input is the codebook\n    CAFFE_ENFORCE_EQ(InputSize(), OutputSize() + 1);\n\n    const auto& codebook = Input(0);\n    CAFFE_ENFORCE(codebook.template IsType<float>(), codebook.meta().name());\n\n    for (int i = 0; i < OutputSize(); i++) {\n      auto& ci = Input(i + 1);\n      auto* co = Output(i);\n\n      DecodeGeneral(\n          codebook,\n          ci,\n          nullptr,\n          co,\n          /*resizeOnly=*/QuantDecodeRun == QuantDecodeRunTy::RUN_ONCE &&\n              hasRun_);\n    }\n    hasRun_ = true;\n    return true;\n  }\n\n private:\n  bool hasRun_{false};\n};\n\nclass QuantDecodeGradientOp final : public Operator<CPUContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CPUContext);\n  QuantDecodeGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n  ~QuantDecodeGradientOp() {}\n\n  bool RunOnDevice() override {\n    // Inputs: 1 codebook, n tensors of codes, and n corresponding gradients.\n    CAFFE_ENFORCE(InputSize() >= 3 && InputSize() % 2 == 1);\n    const int num_code_tensors = (InputSize() - 1) / 2;\n    CAFFE_ENFORCE_EQ(OutputSize(), 1);\n\n    const auto& codebook = Input(0);\n    CAFFE_ENFORCE(codebook.template IsType<float>(), codebook.meta().name());\n\n    auto* gradient = Output(0);\n    gradient->ResizeLike(codebook);\n    auto* gradient_ptr = gradient->mutable_data<float>();\n    std::fill(gradient_ptr, gradient_ptr + gradient->size(), 0);\n\n    for (int i = 0; i < num_code_tensors; i++) {\n      auto& codes_i = Input(i + 1);\n      auto& output_gradient_i = Input(i + num_code_tensors + 1);\n      DecodeGeneral(codebook, codes_i, &output_gradient_i, gradient, false);\n    }\n    return true;\n  }\n};\n\n} // namespace caffe2\n#endif // QUANT_DECODE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/rank_loss_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/rank_loss_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n// Computes log(1 + exp(y)) in a way that avoids early over-/under-flow\ntemplate <class T>\ninline T logLogit(T x) {\n  static const auto kMinLogDiff = std::log(std::numeric_limits<T>::epsilon());\n\n  if (x < kMinLogDiff) {\n    return 0;\n  }\n  if (x > -kMinLogDiff) {\n    return x;\n  }\n  return std::log(std::exp(x) + 1);\n}\n}\n\ntemplate <typename T, class Context>\nbool PairWiseLossOp<T, Context>::RunOnDevice() {\n  auto& X = Input(XVALUE);\n  auto& label = Input(LABEL);\n  auto* Y = Output(YVALUE);\n\n  int N = X.ndim() > 0 ? X.dim32(0) : 0;\n  if (N == 0) {\n    Y->Resize(0);\n    Y->template mutable_data<T>();\n    return true;\n  }\n\n  const int32_t* lengths_vec;\n  int len_size = 1;\n  if (InputSize() > LENGTHS) {\n    auto& lengths = Input(LENGTHS);\n    CAFFE_ENFORCE_EQ(lengths.ndim(), 1);\n    len_size = lengths.size();\n    lengths_vec = lengths.template data<int32_t>();\n    int len_sum = 0;\n    if (len_size > 0) {\n      math::Sum<int, Context>(len_size, lengths_vec, &len_sum, &context_);\n    }\n    CAFFE_ENFORCE_EQ(len_sum, N);\n  } else {\n    lengths_vec = &N;\n  }\n\n  // a total of len_size sessions\n  Y->Resize(len_size);\n  auto* Ydata = Y->template mutable_data<T>();\n\n  int D = X.size() / N;\n  CAFFE_ENFORCE(\n      (label.ndim() == 1) || (label.ndim() == 2 && label.dim32(1) == 1));\n  CAFFE_ENFORCE_EQ(label.dim32(0), N);\n  CAFFE_ENFORCE_EQ(1, D); // only support one class at the moment\n\n  const auto* Xdata = X.template data<T>();\n  const auto* labelData = label.template data<T>();\n  int offset = 0;\n  for (int idx = 0; idx < len_size; ++idx) {\n    Ydata[idx] = 0;\n    int numPairs = 0;\n    for (int i = offset; i < offset + lengths_vec[idx]; ++i) {\n      for (int j = offset; j < i; ++j) {\n        if (std::abs(labelData[i] - labelData[j]) <\n            std::numeric_limits<T>::epsilon()) {\n          continue;\n        }\n        ++numPairs;\n        // only use sigmoid loss function at the moment\n        auto sign = labelData[i] > labelData[j] ? 1 : -1;\n        Ydata[idx] += logLogit(sign * (Xdata[j] - Xdata[i]));\n      }\n    }\n    if (numPairs > 0) {\n      Ydata[idx] /= numPairs;\n    }\n    offset += lengths_vec[idx];\n  }\n  return true;\n}\n\ntemplate <class T, class Context>\nbool PairWiseLossGradientOp<T, Context>::RunOnDevice() {\n  auto& X = Input(XVALUE);\n  auto& label = Input(LABEL);\n  auto& dY = Input(DYVALUE);\n  auto* dX = Output(DXVALUE);\n  int N = X.ndim() > 0 ? X.dim32(0) : 0;\n  CAFFE_ENFORCE_EQ(N, X.size());\n  CAFFE_ENFORCE(\n      (label.ndim() == 1) || (label.ndim() == 2 && label.dim32(1) == 1));\n  CAFFE_ENFORCE_EQ(label.dim32(0), N);\n  dX->ResizeLike(X);\n  math::Set<T, CPUContext>(\n      dX->size(), 0.f, dX->template mutable_data<T>(), &context_);\n\n  if (N == 0) {\n    return true;\n  }\n\n  const int32_t* lengths_vec;\n  int len_size = 1;\n  if (InputSize() > LENGTHS) {\n    auto& lengths = Input(LENGTHS);\n    CAFFE_ENFORCE_EQ(lengths.ndim(), 1);\n    len_size = lengths.size();\n    lengths_vec = lengths.template data<int32_t>();\n    int len_sum = 0;\n    if (len_size > 0) {\n      math::Sum<int, Context>(len_size, lengths_vec, &len_sum, &context_);\n    }\n    CAFFE_ENFORCE_EQ(len_sum, N);\n  } else {\n    lengths_vec = &N;\n  }\n\n  CAFFE_ENFORCE_EQ(dY.ndim(), 1);\n  CAFFE_ENFORCE_EQ(dY.dim32(0), len_size);\n\n  const T* Xdata = X.template data<T>();\n  const T* dYdata = dY.template data<T>();\n  const T* labelData = label.template data<T>();\n  T* dXdata = dX->template mutable_data<T>();\n  int offset = 0;\n  for (int idx = 0; idx < len_size; ++idx) {\n    int numPairs = 0;\n    for (int i = offset; i < offset + lengths_vec[idx]; ++i) {\n      for (int j = offset; j < i; ++j) {\n        if (std::abs(labelData[i] - labelData[j]) <\n            std::numeric_limits<T>::epsilon()) {\n          continue;\n        }\n        ++numPairs;\n        // only use sigmoid loss function at the moment\n        auto sign = labelData[i] > labelData[j] ? 1 : -1;\n        auto grad =\n            sign * dYdata[idx] / (1 + exp(-sign * (Xdata[j] - Xdata[i])));\n        dXdata[i] -= grad;\n        dXdata[j] += grad;\n      }\n    }\n    if (numPairs > 0) {\n      for (int i = offset; i < offset + lengths_vec[idx]; ++i) {\n        dXdata[i] /= numPairs;\n      }\n    }\n    offset += lengths_vec[idx];\n  }\n  return true;\n}\n\nnamespace {\nREGISTER_CPU_OPERATOR(PairWiseLoss, PairWiseLossOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    PairWiseLossGradient,\n    PairWiseLossGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(PairWiseLoss)\n    .NumInputs(2, 3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nOperator computes the pair wise loss between all pairs within a batch\n using the logit loss function on the difference in scores between pairs\n)DOC\")\n    .Input(\n        0,\n        \"X\",\n        \"Input blob from the previous layer, which is almost always \"\n        \"the result of a softmax operation; X is a 2D array of size N x 1\"\n        \"where N is the batch size. For more info: \"\n        \"D. Sculley, Large Scale Learning to Rank. \"\n        \"https://www.eecs.tufts.edu/~dsculley/papers/large-scale-rank.pdf\")\n    .Input(1, \"label\", \"Blob containing the labels used to compare the input\")\n    .Input(\n        2,\n        \"lengths\",\n        \"Optional input blob that contains the lengths\"\n        \"of multiple sessions. The summation of this blob must be equal\"\n        \"to the size of blob X. If lengths blob is provided, the output\"\n        \"blob has the same size as lengths blob, and the cross entropy\"\n        \"is computed within each session.\")\n    .Output(0, \"Y\", \"Output blob after the cross entropy computation\");\nOPERATOR_SCHEMA(PairWiseLossGradient).NumInputs(3, 4).NumOutputs(1);\n\nclass GetPairWiseLossGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> blob_names{I(0), I(1), GO(0)};\n\n    // Add lengths blob if given\n    if (def_.input_size() == 3) {\n      blob_names.push_back(I(2));\n    }\n    return SingleGradientDef(\n        \"PairWiseLossGradient\", \"\", blob_names, vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(PairWiseLoss, GetPairWiseLossGradient);\n\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/rank_loss_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n// support multiple batches of sessions\ntemplate <typename T, class Context>\nclass PairWiseLossOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(PairWiseLossOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n private:\n  INPUT_TAGS(XVALUE, LABEL, LENGTHS);\n  OUTPUT_TAGS(YVALUE);\n};\n\ntemplate <typename T, class Context>\nclass PairWiseLossGradientOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(PairWiseLossGradientOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n private:\n  INPUT_TAGS(XVALUE, LABEL, DYVALUE, LENGTHS);\n  OUTPUT_TAGS(DXVALUE);\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/recurrent_network_blob_fetcher_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/recurrent_network_blob_fetcher_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(\n    RecurrentNetworkBlobFetcher,\n    RecurrentNetworkBlobFetcherOp<CPUContext>);\n\nOPERATOR_SCHEMA(RecurrentNetworkBlobFetcher)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nRetrieves blobs from scratch workspaces (which contain intermediate recurrent\nnetwork computation for each timestep) and puts them in the global\nworkspace under CPUContext.\n)DOC\")\n    .Arg(\"prefix\", \"Prefix string to prepend extracted blobs.\")\n    .Input(\n        0,\n        \"ScratchWorkspaceBlob\",\n        \"Name of scratch workspace blob returned by recurrent network.\")\n    .Output(\n        0,\n        \"blob_names\",\n        \"1D tensor of strings containing extracted blob names.\");\n\nSHOULD_NOT_DO_GRADIENT(RecurrentNetworkBlobFetcher);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/recurrent_network_blob_fetcher_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_RECURRENT_BLOB_FETCHER_OP_H_\n#define CAFFE2_OPERATORS_RECURRENT_BLOB_FETCHER_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/operators/recurrent_network_op.h\"\n#include \"google/protobuf/text_format.h\"\n\n#include <string>\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass RecurrentNetworkBlobFetcherOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  RecurrentNetworkBlobFetcherOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    prefix_ = OperatorBase::GetSingleArgument<std::string>(\"prefix\", \"rnn\");\n    ws_ = ws;\n  }\n\n  bool RunOnDevice() override {\n    const detail::ScratchWorkspaces& scratch =\n        OperatorBase::Input<detail::ScratchWorkspaces>(0);\n    const std::vector<std::shared_ptr<Workspace>>& stepWorkspaces =\n        scratch.stepWorkspaces;\n\n    std::vector<std::string> blob_names_vector = {};\n\n    for (TIndex i = 0; i < stepWorkspaces.size(); i++) {\n      Workspace* currentStepWorkspace = stepWorkspaces[i].get();\n      std::vector<std::string> blob_names = currentStepWorkspace->LocalBlobs();\n\n      for (auto& blob_name : blob_names) {\n        const Blob* currentBlob = currentStepWorkspace->GetBlob(blob_name);\n        const auto& currentTensor = currentBlob->Get<Tensor<Context>>();\n\n        std::string newBlobName =\n            prefix_ + std::string(\"_\") + blob_name + caffe2::to_string(i);\n        blob_names_vector.push_back(newBlobName);\n\n        ws_->CreateBlob(newBlobName)\n            ->template GetMutable<TensorCPU>()\n            ->ResizeLike(currentTensor);\n\n        auto* newTensor =\n            ws_->GetBlob(newBlobName)->template GetMutable<Tensor<Context>>();\n        newTensor->template CopyFrom<Context>(currentTensor);\n      }\n    }\n\n    auto* output = Output(0);\n    output->Resize(blob_names_vector.size());\n    std::copy(\n        blob_names_vector.begin(),\n        blob_names_vector.end(),\n        output->template mutable_data<std::string>());\n\n    return true;\n  }\n\n private:\n  std::string prefix_;\n  Workspace* ws_;\n};\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_RECURRENT_BLOB_FETCHER_OP_H_\n"
  },
  {
    "path": "caffe2/operators/recurrent_network_blob_fetcher_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/recurrent_network_blob_fetcher_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(\n    RecurrentNetworkBlobFetcher,\n    RecurrentNetworkBlobFetcherOp<CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/recurrent_network_executor.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/recurrent_network_executor.h\"\n\n#include \"caffe2/core/timer.h\"\n\nnamespace caffe2 {\n\n/**\n * Implementation of RecurrentNetworkExecutor that uses thread pool for\n * multithreaded execution of RNNs. Used with CPU.\n */\n\ntemplate <>\nstd::unique_ptr<RecurrentNetworkExecutorBase> createRNNExecutor<CPUContext>(\n    const NetDef& step_net_def,\n    std::map<string, string>& recurrent_input_map,\n    std::string timestep_blob,\n    ArgumentHelper rnn_args) {\n  auto* exec = new ThreadedRecurrentNetworkExecutor(\n      step_net_def, recurrent_input_map, timestep_blob);\n  int num_threads =\n      rnn_args.GetSingleArgument<int>(\"rnn_executor.num_threads\", 0);\n  if (num_threads > 0) {\n    exec->setNumThreads(num_threads);\n    LOG(INFO) << \"Set num threads: \" << num_threads;\n  }\n  exec->debug_ = rnn_args.GetSingleArgument<int>(\"rnn_executor_debug\", 0);\n  return std::unique_ptr<RecurrentNetworkExecutorBase>(exec);\n}\n\n/**\n * Run forwardpass with T timesteps.\n */\nbool ThreadedRecurrentNetworkExecutor::Run(int T) {\n  CAFFE_ENFORCE(timestep_ops_.size() >= T);\n  countdown_ = T * timestep_ops_[0].size();\n  finished_timesteps_ = 0;\n\n  CHECK(task_queue_.size() == 0);\n\n  for (auto& rnn_op : timestep_ops_[0]) {\n    // Launch \"frontier\"-ops first.\n    if (rnn_op.frontier) {\n      task_queue_.Push(OpTask(0, rnn_op.order, T, 1));\n    }\n  }\n\n  _Exec();\n  return true;\n}\n\n/**\n * Run backward pass with T timesteps.\n */\nbool ThreadedRecurrentNetworkExecutor::RunBackwards(int T) {\n  CAFFE_ENFORCE(timestep_ops_.size() >= T);\n  countdown_ = T * timestep_ops_[0].size();\n  finished_timesteps_ = 0;\n\n  // Frontier\n  CHECK(task_queue_.size() == 0);\n\n  for (auto& rnn_op : timestep_ops_[T - 1]) {\n    if (rnn_op.frontier) {\n      task_queue_.Push(OpTask(T - 1, rnn_op.order, T, -1));\n    }\n  }\n\n  _Exec();\n  return true;\n}\n\n/**\n * Runs a single op and updates its dependencies when finished. If\n * dependent ops are ready to run, adds them to the task_queue.\n */\nvoid ThreadedRecurrentNetworkExecutor::RunOp(OpTask job, int thread_id) {\n  bool first_timestep =\n      ((job.forward() && job.timestep == 0) ||\n       (job.backward() && job.timestep == job.T - 1));\n  bool last_timestep =\n      ((job.backward() && job.timestep == 0) ||\n       (job.forward() && job.timestep == job.T - 1));\n  auto& rnn_op = timestep_ops_[job.timestep][job.op_idx];\n  if (rnn_op.num_dynamic_inputs > 0 && !rnn_op.frontier) {\n    CAFFE_ENFORCE_EQ(\n        rnn_op.proc_inputs,\n        rnn_op.num_dynamic_inputs -\n            first_timestep * rnn_op.num_recurrent_inputs,\n        \"Error at operator \",\n        job.op_idx,\n        \" on timestep \",\n        job.timestep,\n        \" T=\",\n        job.T,\n        \" first =\",\n        first_timestep);\n  }\n\n  // Reset input dependency counter\n  rnn_op.proc_inputs = 0;\n\n  // Run the operator\n  rnn_op.op->Run();\n\n  // Knock down dependencies and start next ops, if this\n  // was last dependency fulfilled.\n  for (int depidx : rnn_op.dependencies) {\n    int t = job.timestep;\n    bool for_next_timestep = depidx <= rnn_op.order;\n    if (!last_timestep && for_next_timestep) {\n      t += job.direction;\n    } else if (for_next_timestep) {\n      continue;\n    }\n\n    auto& dep_op = timestep_ops_[t][depidx];\n    int proc_inputs = dep_op.proc_inputs.fetch_add(1) + 1;\n\n    // Schedule next op, if this was the last dependency. Note that on\n    // first timestep we don't have recurrent inputs.\n    int num_req_inputs = dep_op.num_dynamic_inputs;\n    if (first_timestep && !for_next_timestep) {\n      num_req_inputs -= dep_op.num_recurrent_inputs;\n    }\n\n    if (proc_inputs == num_req_inputs || num_req_inputs == 0) {\n      task_queue_.Push(OpTask(t, depidx, job.T, job.direction));\n    }\n  }\n\n  // Decrement countdown: when at zero, we have run all ops and can\n  // notify the caller thread.\n  if (countdown_.fetch_sub(1) == 1) {\n    CAFFE_ENFORCE_EQ(0, task_queue_.size());\n    std::unique_lock<std::mutex> lk(countdown_mtx_);\n    cv_.notify_one();\n  }\n}\n\n/**\n * Run-loop for executor threads: pop tasks from task_queue and execute\n * them with RunOp().\n */\nvoid ThreadedRecurrentNetworkExecutor::WorkerFunction() {\n  size_t num_jobs = 0;\n  static std::atomic<int> seq(0);\n  int id = seq.fetch_add(1);\n\n  while (!failed_) {\n    OpTask job;\n    if (!task_queue_.Pop(&job)) {\n      break;\n    }\n\n    // Check for limited timestep parallelism, and if too many timesteps would\n    // be started concurrently, return the task to task queue.\n    if (max_parallel_timesteps_ > 0) {\n      int t = (job.direction == 1 ? job.timestep : job.T - job.timestep + 1);\n      if (t - finished_timesteps_ >= max_parallel_timesteps_) {\n        // Return to queue\n        task_queue_.Push(job);\n        continue;\n      }\n    }\n\n    try {\n      RunOp(job, id);\n      if (job.op_idx == timestep_ops_template_.size() - 1) {\n        finished_timesteps_.fetch_add(1);\n      }\n      num_jobs++;\n    } catch (::caffe2::EnforceNotMet& enf) {\n      std::unique_lock<std::mutex> lk(countdown_mtx_);\n      LOG(ERROR) << \"Crash at thread \" << id << \" timestep \" << job.timestep\n                 << \" op:\" << ProtoDebugString(step_net_def_.op(job.op_idx))\n                 << enf.what();\n      task_queue_.NoMoreJobs();\n      failed_ = true;\n      cv_.notify_one();\n      return;\n    }\n  }\n  VLOG(1) << \"Worker exiting, did run: \" << num_jobs << \" jobs\";\n}\n\n/**\n * Start worker threads if not started yet, wait until all tasks\n * finished, or a failure. Called by Run() and RunBackwards().\n */\nvoid ThreadedRecurrentNetworkExecutor::_Exec() {\n  CAFFE_ENFORCE_EQ(\n      false, failed_, \"Tried to execute a previously failed RNN executor\");\n\n  // Start threads if not started\n  std::unique_lock<std::mutex> lk(countdown_mtx_);\n  while (workers_.size() < num_threads_) {\n    VLOG(1) << \"Start RNN worker \" << workers_.size() << \" / \" << num_threads_;\n    workers_.push_back(\n        std::thread(&ThreadedRecurrentNetworkExecutor::WorkerFunction, this));\n  }\n\n  // Wait until threads finish.\n  Timer t;\n  while (!failed_ && countdown_ > 0) {\n    cv_.wait_for(lk, std::chrono::seconds(30), [&] {\n      // Log if we are still running, so that we catch deadlocks.. there\n      // should not be any deadlocks, but...\n      if (t.Seconds() > 10) {\n        LOG(INFO) << \"RNN Executor still running, remaining ops: \"\n                  << countdown_;\n      }\n      return failed_ || countdown_ == 0;\n    });\n  }\n\n  CAFFE_ENFORCE_EQ(\n      false,\n      failed_,\n      \"RNN executor encountered failure. See prior error logs for details.\");\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/recurrent_network_executor.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_RECURRENT_NETWORK_EXECUTOR_H_\n#define CAFFE2_OPERATORS_RECURRENT_NETWORK_EXECUTOR_H_\n\n#include <map>\n#include <unordered_set>\n#include <vector>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/operators/recurrent_network_executor_incl.h\"\n\nnamespace caffe2 {\n\n/**\n * RecurrentNetworkExecutor is a specialized runtime for recurrent\n * neural networks (RNNs). It is invoked from the RecurrentNetworkOp\n * and RecurrentNetworkGradientOp.\n *\n * Its main benefit over running each RNN timestep as a separate net\n * is that it can run ops in subsequent timesteps in parallel when possible.\n * For example, multi-layer LSTMs allow for timestep parallelism because\n * next timestep's lower layer can start executing at the same time as\n * the same timestep's upper layer.\n *\n * There are two implementations of the RNN executor: one for CPUs\n * (ThreadedRecurrentNetworkExecutor) and another for GPUs\n * (CUDARecurrentNetworkExecutor).\n */\nclass RecurrentNetworkExecutorBase {\n protected:\n  explicit RecurrentNetworkExecutorBase(\n      const NetDef& step_net_def,\n      std::map<string, string>& recurrent_input_map,\n      std::string timestep_blob)\n      : step_net_def_(step_net_def),\n        recurrent_input_map_(recurrent_input_map),\n        timestep_blob_(timestep_blob) {\n    for (int i = 0; i < step_net_def_.op_size(); i++) {\n      op_deps_.push_back(op_deps(i));\n    }\n  }\n\n public:\n  virtual ~RecurrentNetworkExecutorBase() {\n    if (debug_) {\n      if (timestep_ops_.size() > 0) {\n        PrintInfo(0);\n      }\n    }\n  }\n\n  virtual bool Run(int T) = 0;\n\n  virtual bool RunBackwards(int T) = 0;\n\n  /**\n   * Callers must call EnsureTimestepInitialized before starting execution\n   * for each of the relevant timesteps. If timestep was initialized before,\n   * this is a no-op. First time this is called the dependencies of the\n   * operators in timestep are analyzed, and that incurs higher overhead\n   * than subsequent calls.\n   */\n  void EnsureTimestepInitialized(\n      int t,\n      Workspace* ws,\n      const std::vector<std::unique_ptr<ObserverBase<OperatorBase>>>&\n          observers_list) {\n    if (timestep_ops_template_.size() == 0) {\n      // Firsrt invocation -- compute dependencies\n      CalculateInternalDependencies();\n\n      // Label ops based on whether they contain reference to the timestep\n      // blob. This is an optimization to avoid string comparisons later.\n      for (auto& rnn_op : timestep_ops_template_) {\n        rnn_op.has_timestep_blob = false;\n        const OperatorDef& op = step_net_def_.op(rnn_op.order);\n        for (int i = 0; i < op.input_size(); i++) {\n          if (op.input(i) == timestep_blob_) {\n            rnn_op.has_timestep_blob = true;\n            break;\n          }\n        }\n        CAFFE_ENFORCE(\n            !HasOutput(op, timestep_blob_),\n            \"Timestep cannot be output of an op: \",\n            timestep_blob_,\n            \" op=\" + ProtoDebugString(op));\n      }\n    }\n\n    // Initialize timestep if it is not initialized\n    if (timestep_ops_.size() <= t ||\n        (timestep_ops_.size() > t && timestep_ops_[t].size() == 0)) {\n      // Initialize empty timestep ops vectors for each timestep preceding\n      // this.\n      for (int j = timestep_ops_.size(); j < t + 1; j++) {\n        timestep_ops_.push_back(std::vector<RNNNetOperator>());\n        timestep_ops_.back().reserve(timestep_ops_template_.size());\n      }\n\n      // Keep track of workspaces for optimization in forward-only case\n      if (workspaces_.size() < t + 1) {\n        workspaces_.resize(t + 1);\n      }\n      workspaces_[t] = ws;\n\n      // Create a specific timestep blob for this timestep. This is to\n      // avoid conflicting timestep blobs when reusing workspaces, as with\n      // the forward-only mode.\n      std::string this_timestep_blob =\n          timestep_blob_ + \"_rnnexec_t\" + caffe2::to_string(t);\n      ws->CreateBlob(this_timestep_blob)->GetMutable<TensorCPU>()->Resize(1);\n      auto b = ws->GetBlob(this_timestep_blob);\n      CAFFE_ENFORCE(b);\n      b->GetMutable<TensorCPU>()->mutable_data<int32_t>()[0] = t;\n\n      // Copy the operators from template\n      for (auto& template_rnn_op : timestep_ops_template_) {\n        auto& rnn_op = template_rnn_op;\n\n        // For ops that have the timestep blob as an input we need to\n        // create a new operator definition with the timestep-specific\n        // timestep blob. This is required to avoid race conditions when\n        // multiple timesteps execute in paralle.\n        if (rnn_op.has_timestep_blob) {\n          OperatorDef op_copy = step_net_def_.op(rnn_op.order);\n\n          for (int i = 0; i < op_copy.input_size(); i++) {\n            if (op_copy.input(i) == timestep_blob_) {\n              op_copy.set_input(i, this_timestep_blob);\n            }\n          }\n\n          rnn_op.op = CreateOperator(op_copy, ws);\n          for (const auto& observer : observers_list) {\n            std::unique_ptr<ObserverBase<OperatorBase>> observer_copy =\n                observer->copy(rnn_op.op.get());\n            CAFFE_ENFORCE(\n                observer_copy,\n                \"Observers without copy() implemented cannot be attached \"\n                \"to RNN using RNNExecutor.\");\n            rnn_op.op->AttachObserver(std::move(observer_copy));\n          }\n        } else {\n          // Optimization for forward-only models when we can share workspaces\n          // with timesteps: then we can just copy the op reference.\n          if (t > max_parallel_timesteps_ && max_parallel_timesteps_ > 0 &&\n              workspaces_[t - max_parallel_timesteps_] == ws) {\n            rnn_op.op =\n                timestep_ops_[t - max_parallel_timesteps_][rnn_op.order].op;\n          } else {\n            // Otherwise, we need to create a brand new op with the workspace\n            // owned by this timestep.\n            rnn_op.op = CreateOperator(step_net_def_.op(rnn_op.order), ws);\n            for (const auto& observer : observers_list) {\n              std::unique_ptr<ObserverBase<OperatorBase>> observer_copy =\n                  observer->copy(rnn_op.op.get());\n              CAFFE_ENFORCE(\n                  observer_copy,\n                  \"Observers without copy() implemented cannot be attached \"\n                  \"to RNN using RNNExecutor.\");\n              rnn_op.op->AttachObserver(std::move(observer_copy));\n            }\n          }\n        }\n        rnn_op.op->DisableEvent();\n\n        timestep_ops_[t].emplace_back(rnn_op);\n      }\n    }\n  }\n\n  /**\n   * Set limit for the number of timesteps that run in parallel. Useful\n   * for forward-only execution when we rotate workspaces over timesteps,\n   * i.e when timestep[t] and timestep[t + p] have same workspace.\n   */\n  void SetMaxParallelTimesteps(int p) {\n    max_parallel_timesteps_ = p;\n  }\n\n  size_t NumObserversStepNet() {\n    size_t num = 0;\n    for (auto& ops_at_timestep_t : timestep_ops_) {\n      for (auto& rnn_op : ops_at_timestep_t) {\n        num += rnn_op.op->NumObservers();\n      }\n    }\n    return num;\n  }\n\n private:\n  // Utility method to check if any of the op inputs or control inputs\n  // contain given blob 'input'\n  bool has_input(std::string x, int opidx) {\n    for (auto& inp : step_net_def_.op(opidx).input()) {\n      if (inp == x) {\n        return true;\n      }\n    }\n    for (auto& inp : step_net_def_.op(opidx).control_input()) {\n      if (inp == x) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  // Return all outbound dependencies of an op. Special case for\n  // rnn dependencies, that are set in recurent_network_op.\n  std::vector<string> op_deps(int i) {\n    std::vector<string> outs;\n    auto& opdef = step_net_def_.op(i);\n    for (string o : opdef.output()) {\n      outs.push_back(o);\n    };\n    for (auto& arg : opdef.arg()) {\n      if (arg.name().find(\"rnn_dependency\") == 0) {\n        outs.push_back(arg.s());\n      }\n    }\n    return outs;\n  }\n\n  /**\n   * Calculate dependencies of this op, for the ops following it in this\n   * timestep and also for the next timestep. Removes redundant dependencies.\n   */\n  void infer_dependencies(\n      int start_i,\n      std::unordered_set<string> outputs,\n      std::vector<RNNNetOperator>& rnn_ops,\n      std::unordered_set<int>* dep_ops) {\n    std::unordered_set<int> already_accounted_deps;\n    int num_ops = step_net_def_.op_size();\n    bool ignore_links = this->ignoreLinkDependencies();\n    for (int j = 0; j < num_ops - 1 && !outputs.empty(); j++) {\n      int i = (start_i + j) % num_ops;\n      if (ignore_links && rnn_ops[i].link_op) {\n        continue;\n      }\n      for (auto& outp : outputs) {\n        if (has_input(outp, i)) {\n          if (already_accounted_deps.find(i) == already_accounted_deps.end()) {\n            dep_ops->insert(i);\n          }\n\n          // Now we can take the deps of this ops and not\n          // add them anymore\n          for (int odep : rnn_ops[i].dependencies) {\n            already_accounted_deps.insert(odep);\n          }\n          for (string& dep_out : op_deps_[i]) {\n            auto oit = outputs.find(dep_out);\n            if (oit != outputs.end()) {\n              // This op produces output of the orignal op, so the dependency\n              // passed through that op\n              outputs.erase(oit);\n            }\n          }\n          break;\n        }\n      }\n    }\n  }\n\n  /**\n   * Add dependencies to ops in the next timestep that would write an op\n   * that this op has as an input or output. This is special for RNNs,\n   * since we can have ops running in different timesteps concurrently.\n   * Also, we need to check ops that output a blob that is input of\n   * of the op in question.\n   */\n  void add_race_conflict_dependencies(\n      int opidx,\n      std::vector<RNNNetOperator>& rnn_ops,\n      std::unordered_set<int>* dep_ops) {\n    for (int i = 0; i < rnn_ops.size(); i++) {\n      if (i == opidx) {\n        continue;\n      }\n      if (rnn_ops[i].link_op && this->ignoreLinkDependencies()) {\n        continue;\n      }\n      for (auto& dep_blob : op_deps_[i]) {\n        for (auto& inp : step_net_def_.op(opidx).input()) {\n          if (inp == dep_blob) {\n            dep_ops->insert(i);\n            break;\n          }\n        }\n        if (i < opidx) {\n          for (auto& outp : step_net_def_.op(opidx).output()) {\n            if (outp == dep_blob) {\n              dep_ops->insert(i);\n              break;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  /**\n   * Calculate the dependencies between ops inside timestep and across\n   * timestep. These are store in timestep_ops_ vector that is copied\n   * for each timestep.\n   */\n  void CalculateInternalDependencies() {\n    for (int i = 0; i < step_net_def_.op_size(); i++) {\n      timestep_ops_template_.push_back(RNNNetOperator(step_net_def_.op(i), i));\n    }\n    // Then see which outputs appear as inputs, and those are\n    // the internal blobs.\n    for (auto& rnn_op : timestep_ops_template_) {\n      std::unordered_set<string> dep_outputs;\n      for (auto& outp : op_deps_[rnn_op.order]) {\n        dep_outputs.insert(outp);\n      }\n\n      // Add recurrent dependencies as 'outputs' for this op\n      for (auto& outp : dep_outputs) {\n        auto rit = recurrent_input_map_.find(outp);\n        if (rit != recurrent_input_map_.end()) {\n          dep_outputs.insert(rit->second);\n        } else {\n          dep_outputs.insert(outp);\n        }\n      }\n\n      // Compute dependencies of this op.\n      if (!rnn_op.link_op || !this->ignoreLinkDependencies()) {\n        std::unordered_set<int> dependent_ops;\n        infer_dependencies(\n            rnn_op.order + 1,\n            dep_outputs,\n            timestep_ops_template_,\n            &dependent_ops);\n\n        // Race conditions arise when operator writes a blob that is\n        // being read by another.\n        if (!this->ignoreLinkDependencies()) {\n          add_race_conflict_dependencies(\n            rnn_op.order, timestep_ops_template_, &dependent_ops);\n        }\n\n        for (int i : dependent_ops) {\n          rnn_op.dependencies.push_back(i);\n        }\n\n        // Sort in ascending order of dependency distance. If op\n        // j > i, then distance is j - i. But if j < i, then distance\n        // from i to j passes the timestep boundary and is j + num ops - i.\n        std::sort(\n            rnn_op.dependencies.begin(),\n            rnn_op.dependencies.end(),\n            [&](const int& a, const int& b) {\n              if (a < rnn_op.order && b < rnn_op.order) {\n                return a < b;\n              }\n              if (a >= rnn_op.order && b >= rnn_op.order) {\n                return a < b;\n              }\n              if (a >= rnn_op.order && b < rnn_op.order) {\n                return true;\n              }\n              return false;\n            });\n      }\n    }\n\n    // Update dependency counts\n    for (auto& rnn_op : timestep_ops_template_) {\n      for (int i : rnn_op.dependencies) {\n        timestep_ops_template_[i].num_dynamic_inputs++;\n\n        if (i > rnn_op.order) {\n          timestep_ops_template_[i].frontier = false;\n        } else {\n          timestep_ops_template_[i].num_recurrent_inputs++;\n        }\n      }\n    }\n    // Find ops that have no recurrent inputs, and bind them\n    // to the last op of the timestep. If there is only one op\n    // in the step net, then it will depend on itself. Note that\n    // we do not increase the dynamic input counter.\n    for (auto& rnn_op : timestep_ops_template_) {\n      if (rnn_op.num_dynamic_inputs == 0 && rnn_op.num_recurrent_inputs == 0) {\n        if (rnn_op.link_op && this->ignoreLinkDependencies()) {\n          continue;\n        }\n        timestep_ops_template_.back().dependencies.push_back(rnn_op.order);\n      }\n    }\n\n    // compute parents\n    for (auto& rnn_op : timestep_ops_template_) {\n      for (int dep : rnn_op.dependencies) {\n        timestep_ops_template_[dep].parents.push_back(rnn_op.order);\n      }\n    }\n    AnalyzeOps();\n  }\n\n protected:\n  /**\n   * For debug purposes, print the dependency structure. Set\n   * rnn_executor_debug=1 in the RecurrentNetworkOp to enable.\n   */\n  void PrintInfo(int t) {\n    auto& rnn_ops = timestep_ops_[t];\n\n    LOG(INFO) << \"Timestep: \" << t;\n    for (auto& rnn_op : rnn_ops) {\n      auto& op = rnn_op.op;\n      LOG(INFO) << \"Operator \" << rnn_op.order << \": \" << op->type()\n                << \" dep inputs:\" << rnn_op.num_dynamic_inputs\n                << \" rec inputs:\" << rnn_op.num_recurrent_inputs\n                << \" frontier: \" << rnn_op.frontier;\n      for (auto& inp : rnn_op.op->debug_def().input()) {\n        LOG(INFO) << \" ---- input: \" << inp;\n      }\n      for (auto& outp : rnn_op.op->debug_def().output()) {\n        LOG(INFO) << \" ---- output: \" << outp;\n      }\n      for (auto j : rnn_op.dependencies) {\n        LOG(INFO) << \" dep: \" << j << \": \" << rnn_ops[j].op->type();\n      }\n      for (auto j : rnn_op.parents) {\n        LOG(INFO) << \" parent: \" << j << \": \" << rnn_ops[j].op->type();\n      }\n    }\n\n    LOG(INFO) << \"recurrent_inputs:\" << recurrent_input_map_;\n\n    for (auto& rnn_op : rnn_ops) {\n      LOG(INFO) << \"Operator \" << rnn_op.order;\n      LOG(INFO) << ProtoDebugString(rnn_op.op->debug_def());\n    }\n  }\n\n  virtual void AnalyzeOps() {}\n\n  virtual bool ignoreLinkDependencies() = 0;\n\n  std::vector<std::vector<RNNNetOperator>> timestep_ops_;\n  std::vector<OperatorBase*> op_ptrs_;\n\n  std::vector<RNNNetOperator> timestep_ops_template_;\n\n  NetDef step_net_def_;\n  std::vector<std::vector<string>> op_deps_;\n  std::vector<Workspace*> workspaces_;\n  std::map<string, string> recurrent_input_map_;\n  std::string timestep_blob_;\n\n  int max_parallel_timesteps_ = -1;\n\n public:\n  bool debug_ = false;\n};\n\ntemplate <class Context>\nstd::unique_ptr<RecurrentNetworkExecutorBase> createRNNExecutor(\n    const NetDef& step_net_def,\n    std::map<string, string>& recurrent_input_map,\n    std::string timestep_blob,\n    ArgumentHelper rnn_args);\n\nclass ThreadedRecurrentNetworkExecutor : public RecurrentNetworkExecutorBase {\n public:\n  ThreadedRecurrentNetworkExecutor(\n      const NetDef& step_net_def,\n      std::map<string, string>& recurrent_input_map,\n      std::string timestep_blob)\n      : RecurrentNetworkExecutorBase(step_net_def, recurrent_input_map, timestep_blob),\n        failed_(false) {}\n\n  ~ThreadedRecurrentNetworkExecutor() {\n    task_queue_.NoMoreJobs();\n    VLOG(1) << \"Joining workers.\";\n    for (auto& worker : workers_) {\n      worker.join();\n    }\n  }\n\n  bool Run(int T) override;\n\n  bool RunBackwards(int T) override;\n\n  bool ignoreLinkDependencies() override {\n    return false;\n  }\n\n  void setNumThreads(int n) {\n    num_threads_ = n;\n  }\n\n private:\n  void _ExecRange(int from, int to);\n\n  void _Exec();\n\n  void WorkerFunction();\n\n  void RunOp(OpTask job, int thread_id);\n\n  SimpleQueue<OpTask> task_queue_;\n  std::atomic<int> countdown_;\n  std::atomic<bool> failed_;\n  std::atomic<int> finished_timesteps_;\n  int num_ops_;\n  std::mutex countdown_mtx_;\n  std::condition_variable cv_;\n  std::vector<std::thread> workers_;\n  int num_threads_ = 4;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_RECURRENT_NETWORK_EXECUTOR_H_\n"
  },
  {
    "path": "caffe2/operators/recurrent_network_executor_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/recurrent_network_executor_gpu.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nstd::unique_ptr<RecurrentNetworkExecutorBase> createRNNExecutor<CUDAContext>(\n    const NetDef& step_net_def,\n    std::map<string, string>& recurrent_input_map,\n    std::string timestep_blob,\n    ArgumentHelper arg_helper) {\n  auto* exec = new CUDARecurrentNetworkExecutor(\n      step_net_def, recurrent_input_map, timestep_blob);\n  int max_streams = arg_helper.GetSingleArgument<int>(\"rnn_executor.max_cuda_streams\", 0);\n  if (max_streams > 0) {\n    exec->setMaxStreams(max_streams);\n    LOG(INFO) << \"Set max streams:\" << max_streams;\n  }\n  std::unique_ptr<RecurrentNetworkExecutorBase> ptr(exec);\n  return ptr;\n}\n\nCUDARecurrentNetworkExecutor::~CUDARecurrentNetworkExecutor() {\n  for (cudaEvent_t ev : events_) {\n    if (ev != nullptr) {\n      CUDA_CHECK(cudaEventDestroy(ev));\n    }\n  }\n}\n\n/**\n * Special execution for CUDA. It tries to run ops with as little overhead as\n * possible, but to identify opportunities to run ops with \"frontier execution\"\n * parallelism, i.e by starting kernel from next timestep in parallel with\n * the current timestep. This is done by assigning streams.\n */\nvoid CUDARecurrentNetworkExecutor::_ExecRange(int from, int to) {\n  int direction = to > from ? 1 : -1;\n\n  int max_streams = max_parallel_timesteps_ > 0 ?\n                    std::min(max_parallel_timesteps_, max_cuda_streams_)\n                    : max_cuda_streams_;\n  int stream_seq = 0;\n  int num_ops = timestep_ops_[0].size();\n\n  events_.resize(num_ops * timestep_ops_.size(), nullptr);\n\n  int gpu_id = -1;\n\n  // Loop over timesteps\n  for (int t = from; t != to; t += direction) {\n    bool first_timestep = t == from;\n    bool last_timestep =\n        (direction == -1 && t == 0) || (direction == 1 && t == to - 1);\n    auto& ops = timestep_ops_[t];\n    int stream_id = stream_seq % max_streams;\n\n    for (int i = 0; i < ops.size(); i++) {\n      auto& rnn_op = ops[i];\n\n      // Special handling for link ops -- we just run them directly\n      // they do not execute any kernels.\n      if (rnn_op.link_op) {\n        rnn_op.op->RunAsync(stream_id);\n        CAFFE_ENFORCE(\n            rnn_op.dependencies.empty(),\n            \"GPU executor ignores link dependencies\");\n        continue;\n      }\n\n      if (gpu_id == -1 && rnn_op.op->device_option().device_type() == 1) {\n        gpu_id = rnn_op.op->device_option().cuda_gpu_id();\n      } else {\n        CAFFE_ENFORCE(\n            rnn_op.op->device_option().device_type() == 0 ||\n                rnn_op.op->device_option().cuda_gpu_id() == gpu_id,\n            \"RNN Executor only supports ops on one GPU\");\n      }\n\n      // If have recurrent parents, add for event waits so that those\n      // parents complete their work.\n      if (has_timestep_parallelism_ && !first_timestep) {\n        for (int parent : rnn_op.parents) {\n          if (parent > i) {\n            int parent_ev_idx = (t - direction) * num_ops + parent;\n            CHECK(events_.size() > parent_ev_idx);\n            CAFFE_ENFORCE(events_[parent_ev_idx] != nullptr);\n            CUDA_CHECK(cudaStreamWaitEvent(\n                CUDAContext::cuda_stream(gpu_id, stream_id),\n                events_[parent_ev_idx],\n                0));\n        }\n        }\n      }\n\n      // Run the op in the given stream\n      rnn_op.op->RunAsync(stream_id);\n\n      // Create and record event for this op, if it has at least one\n      // recurrent dependency.\n      if (has_timestep_parallelism_ && !last_timestep) {\n        for (int dep : rnn_op.dependencies) {\n          if (dep < i) {\n            int event_idx = t * num_ops + i;\n            // Create event for recurrent connections\n            if (events_[event_idx] == nullptr) {\n              CUDA_CHECK(cudaEventCreate(&events_[event_idx]));\n            }\n            CUDA_CHECK(cudaEventRecord(\n                events_[event_idx],\n                CUDAContext::cuda_stream(gpu_id, stream_id)));\n            break;\n          }\n        }\n      }\n    } // for over ops\n\n    // Next timestep will run on different stream\n    if (has_timestep_parallelism_) {\n      stream_seq++;\n    }\n  } // for over timesteps\n\n  /**\n   * Wait for all the started streams to complete.\n   */\n  for (int stream_id = 0; stream_id <= std::min(stream_seq, max_streams - 1);\n       stream_id++) {\n    VLOG(1) << \"Wait for stream:\" << stream_id;\n    CUDA_CHECK(\n        cudaStreamSynchronize(CUDAContext::cuda_stream(gpu_id, stream_id)));\n  }\n}\n\nbool CUDARecurrentNetworkExecutor::Run(int T) {\n  _ExecRange(0, T);\n  return true;\n}\n\nbool CUDARecurrentNetworkExecutor::RunBackwards(int T) {\n  _ExecRange(T - 1, -1);\n  return true;\n}\n}\n"
  },
  {
    "path": "caffe2/operators/recurrent_network_executor_gpu.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_RECURRENT_NETWORK_GPU_EXECUTOR_H_\n#define CAFFE2_OPERATORS_RECURRENT_NETWORK_GPU_EXECUTOR_H_\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/recurrent_network_executor.h\"\n\n\n#include <map>\n\nnamespace caffe2 {\n\nclass CUDARecurrentNetworkExecutor : public RecurrentNetworkExecutorBase {\n public:\n  CUDARecurrentNetworkExecutor(\n      const NetDef& step_net_def,\n      std::map<string, string>& recurrent_input_map,\n      std::string timestep_blob)\n  : RecurrentNetworkExecutorBase(step_net_def, recurrent_input_map, timestep_blob) {}\n\n  ~CUDARecurrentNetworkExecutor();\n\n protected:\n  bool Run(int T) override;\n\n  bool RunBackwards(int T) override;\n\n  bool ignoreLinkDependencies() override {\n    return true;\n  }\n\n  void AnalyzeOps() override {\n    /**\n      * Check if there is an op that only depends on ops from previous\n      * timestep, and that ops is not the last op. Then we can start computation\n      * in subsequent timesteps before the whole previous timestep has finished.\n      * If there is no parallelism, we can avoid overhead of event-based\n      * dependency management.\n      */\n    has_timestep_parallelism_ = false;\n    for (auto& rnn_op : timestep_ops_template_) {\n      int i = rnn_op.order;\n      if (rnn_op.parents.size() >= 1 && i < timestep_ops_template_.size() - 1) {\n        bool only_recurrent_deps = std::all_of(\n                  rnn_op.parents.begin(),\n                  rnn_op.parents.end(), [&](const int &parent) {\n                    return parent > i;\n                  }\n        );\n        if (only_recurrent_deps) {\n          VLOG(1) << \"Timestep parallel op: \" << ProtoDebugString(step_net_def_.op(i));\n          has_timestep_parallelism_ = true;\n\n          for (int dep : rnn_op.parents) {\n            if (dep == timestep_ops_template_.size() - 1) {\n              // This op depends on the last op of the previous iteration,\n              // so it will block any parallelism\n              has_timestep_parallelism_ = false;\n              break;\n            }\n          }\n          break;\n        }\n      }\n    }\n    LOG(INFO) << \"Analyzed ops for timestep parallelism: \" << has_timestep_parallelism_;\n }\n\n public:\n\n   void setMaxStreams(int n) {\n     max_cuda_streams_ = n;\n   }\n\n private:\n  void _ExecRange(int from, int to);\n\n  std::vector<cudaEvent_t> events_;\n  bool has_timestep_parallelism_ = false;\n  int max_cuda_streams_ = 2;\n};\n}\n#endif\n"
  },
  {
    "path": "caffe2/operators/recurrent_network_executor_incl.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#ifndef CAFFE2_OPERATORS_RECURRENT_NETWORK_EXECUTOR_INCL_H_\n#define CAFFE2_OPERATORS_RECURRENT_NETWORK_EXECUTOR_INCL_H_\n\n#include <vector>\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\n/**\n * Struct for operator in a timestep and its dependenceis.\n */\nstruct RNNNetOperator {\n  int order; // Position in the step net (i.e nth operator)\n  std::shared_ptr<OperatorBase> op = nullptr;\n  bool link_op; // Special flag for link op, see RNNApplyLinkOp.\n\n  // Bookkeeping, used by ThreadedRecurrentNetworkExecutor\n  int num_dynamic_inputs = 0;\n  int num_recurrent_inputs = 0;\n  std::atomic<int> proc_inputs;\n\n  // Dependencies to other ops. If dependency index < order, it is\n  // a recurrent dependency (i.e to the next timestep)\n  std::vector<int> dependencies;\n  std::vector<int> parents;\n  bool frontier = true; // For ops that are launched first\n  bool has_timestep_blob = false;\n\n  explicit RNNNetOperator(const OperatorDef& def, int order) : order(order) {\n    proc_inputs = 0;\n    link_op = def.type() == \"rnn_internal_apply_link\";\n  }\n\n  RNNNetOperator(const RNNNetOperator& x) {\n    order = x.order;\n    op = x.op;\n    link_op = x.link_op;\n    num_dynamic_inputs = x.num_dynamic_inputs;\n    num_recurrent_inputs = x.num_recurrent_inputs;\n    proc_inputs = 0;\n    dependencies = x.dependencies;\n    parents = x.parents;\n    frontier = x.frontier;\n  }\n};\n\n/**\n * Data structure for a scheduled task in the task queue.\n */\nstruct OpTask {\n  int timestep;\n  int op_idx; // matches RNNNetOperator.order\n  int T; // number of timesteps in this execution\n  int direction; // +1 for forward, -1 for backward pass\n  int stream_id = -1; // only used by gpu version\n  OpTask() {}\n  OpTask(int _timestep, int _op_idx, int _T, int _direction)\n      : timestep(_timestep), op_idx(_op_idx), T(_T), direction(_direction) {\n    CHECK(direction == 1 || direction == -1);\n    CHECK(timestep >= 0 && timestep < _T);\n  }\n\n  inline bool backward() {\n    return direction == -1;\n  }\n  inline bool forward() {\n    return direction == 1;\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_RECURRENT_NETWORK_EXECUTOR_H_\n"
  },
  {
    "path": "caffe2/operators/recurrent_network_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/recurrent_network_op.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\n#ifndef CAFFE2_RNN_NO_TEXT_FORMAT\n#include <google/protobuf/text_format.h>\n#endif\n\nCAFFE2_DEFINE_bool(\n    caffe2_rnn_executor,\n    true,\n    \"If set, uses special RNN executor for executing RecurrentNetworkOp\");\n\nnamespace caffe2 {\nCAFFE_KNOWN_TYPE(detail::ScratchWorkspaces);\n\nREGISTER_CPU_OPERATOR(RecurrentNetwork, RecurrentNetworkOp<CPUContext>);\nOPERATOR_SCHEMA(RecurrentNetwork)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(2, INT_MAX)\n    .SetDoc(R\"DOC(\nRun the input network in a recurrent fashion. This can be used to\nimplement fairly general recurrent neural networks (RNNs).\n\nThe operator proceeds as follows.\n\n- First, initialized the states from the input recurrent states\n- For each timestep T, apply the links (that map offsets from input/output\ntensors into the inputs/outputs for the `step` network)\n- Finally, alias the recurrent states to the specified output blobs.\n\nThis is a fairly special-case meta-operator, and so the implementation\nis somewhat complex. It trades of generality (and frankly usability)\nagainst performance and control (compared to e.g. TF\ndynamic_rnn, Theano scan, etc).\n\nSee the usage examples for a flavor of how to use it.\n)DOC\");\n\nREGISTER_CPU_OPERATOR(\n    RecurrentNetworkGradient,\n    RecurrentNetworkGradientOp<CPUContext>);\nOPERATOR_SCHEMA(RecurrentNetworkGradient);\n\nREGISTER_CPU_OPERATOR(\n    rnn_internal_accumulate_gradient_input,\n    AccumulateInputGradientOp<CPUContext>);\nOPERATOR_SCHEMA(rnn_internal_accumulate_gradient_input)\n    .NumInputs(3)\n    .NumOutputs(1, INT_MAX)\n    .EnforceInplace({{2, 0}})\n    .Private()\n    .SetDoc(R\"DOC(\nInternal RNN operator.\n)DOC\");\n\nREGISTER_CPU_OPERATOR(\n    rnn_internal_apply_link,\n    RNNApplyLinkOp<CPUContext>);\nOPERATOR_SCHEMA(rnn_internal_apply_link)\n    .NumInputs(2)\n    .NumOutputs(2)\n    .EnforceInplace({{1, 1}})\n    .Private()\n    .SetDoc(R\"DOC(\nInternal RNN operator.\n)DOC\");\n\nstruct GetRecurrentNetworkGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  std::vector<OperatorDef> GetGradientDefs() override {\n    ArgumentHelper argsHelper(def_);\n    auto params = argsHelper.GetRepeatedArgument<int32_t>(\"param\");\n    auto recurrentInputs =\n        argsHelper.GetRepeatedArgument<int32_t>(\"initial_recurrent_state_ids\");\n\n    std::vector<std::string> gradientInputs;\n\n    // Argument specifies which outputs have external gradient, (0) by default\n    auto outputs_with_grads =\n        argsHelper.GetRepeatedArgument<int32_t>(\"outputs_with_grads\");\n    CAFFE_ENFORCE(outputs_with_grads.size() > 0);\n    for (auto id : outputs_with_grads) {\n      gradientInputs.push_back(GO(id));\n    }\n\n    // All inputs and outputs are passed back\n    for (int i = 0; i < def_.input_size(); ++i) {\n      gradientInputs.push_back(I(i));\n    }\n    for (int i = 0; i < def_.output_size(); ++i) {\n      gradientInputs.push_back(O(i));\n    }\n\n    // We calculate gradients only for parameters and recurrent inputs\n    std::vector<std::string> gradientOutputs;\n    gradientOutputs.push_back(GI(0));\n    for (auto id : params) {\n      gradientOutputs.push_back(GI(id));\n    }\n    for (auto id : recurrentInputs) {\n      gradientOutputs.push_back(GI(id));\n    }\n\n    VLOG(1) << \"Gradient blobs: \" << Join(\", \", gradientOutputs);\n\n    return SingleGradientDef(\n        \"RecurrentNetworkGradient\", \"\", gradientInputs, gradientOutputs);\n  }\n};\n\nREGISTER_GRADIENT(RecurrentNetwork, GetRecurrentNetworkGradient);\n\nnamespace detail {\n\nstd::map<string, string> GetRecurrentMapping(\n    const std::vector<detail::Link>& links,\n    bool backward) {\n  std::map<string, string> mappings;\n  for (auto it = links.begin(); it != links.end(); ++it) {\n    const auto& l1 = *it;\n\n    // In backward op we expect to see offset 1 before offset 0 and\n    // vice versa.\n    const int offset_l1 = backward ? 1 : 0;\n    const int offset_l2 = 1 - offset_l1;\n    if (l1.offset == offset_l1) {\n      // Find offset = 1 from links. We could probaby rely on order, but\n      // since the number of links is links small, O(n^2) algo is ok\n      for (auto it2 = it + 1; it2 != links.end(); ++it2) {\n        const auto& l2 = *it2;\n        if (l2.offset == offset_l2 && l2.external == l1.external) {\n          mappings[l2.internal] = l1.internal;\n          break;\n        }\n      }\n    }\n  }\n  return mappings;\n}\n\nvoid PrependOps(std::vector<OperatorDef> ops, NetDef* netdef) {\n  for (auto& o : netdef->op()) {\n    ops.push_back(o);\n  }\n  netdef->mutable_op()->Clear();\n  for (auto& o : ops) {\n    auto* ao = netdef->add_op();\n    ao->CopyFrom(o);\n  }\n}\n\nvoid AddApplyLinkOps(\n    const vector<Link>& links,\n    std::string timestep,\n    const DeviceOption& device_option,\n    NetDef* netdef) {\n  std::vector<OperatorDef> ops;\n  for (auto& link : links) {\n    OperatorDef opdef;\n    opdef.set_type(\"rnn_internal_apply_link\");\n    opdef.add_input(timestep);\n    opdef.add_input(link.external);\n    opdef.add_output(link.internal);\n    opdef.add_output(link.external);\n    opdef.mutable_device_option()->CopyFrom(device_option);\n\n    Argument* offset_arg = opdef.add_arg();\n    offset_arg->set_name(\"offset\");\n    offset_arg->set_i(link.offset);\n\n    Argument* window_arg = opdef.add_arg();\n    window_arg->set_name(\"window\");\n    window_arg->set_i(link.window);\n\n    // Find out if the linked blob is used first as an output: then we need\n    // to add control_input to that op\n    for (auto& op : *netdef->mutable_op()) {\n      if (HasInput(op, link.internal)) {\n        // First appears as an input, no need to do antyhing\n        continue;\n      }\n      if (HasOutput(op, link.internal)) {\n        op.add_control_input(link.internal);\n        break;\n      }\n    }\n\n    ops.push_back(opdef);\n\n    netdef->add_external_input(link.internal);\n    netdef->add_external_input(link.external);\n  }\n\n  detail::PrependOps(ops, netdef);\n}\n\nvoid extractLinks(\n    OperatorBase* op,\n    const std::string& internalArg,\n    const std::string& externalArg,\n    const std::string& offsetArg,\n    const std::string& windowArg,\n    std::vector<detail::Link>* links) {\n  const auto& internal = op->GetRepeatedArgument<std::string>(internalArg);\n  const auto& external = op->GetRepeatedArgument<std::string>(externalArg);\n  const auto& offset = op->GetRepeatedArgument<int32_t>(offsetArg);\n  const auto& window = op->GetRepeatedArgument<int32_t>(\n      windowArg, vector<int32_t>(offset.size(), 1));\n  CAFFE_ENFORCE_EQ(\n      internal.size(),\n      offset.size(),\n      \"internal/offset mismatch: \",\n      internalArg,\n      \" \",\n      externalArg);\n  CAFFE_ENFORCE_EQ(\n      external.size(),\n      offset.size(),\n      \"external/offset mismatch: \",\n      externalArg,\n      \" \",\n      offsetArg);\n  CAFFE_ENFORCE_EQ(\n      external.size(),\n      window.size(),\n      \"external/window mismatch: \",\n      externalArg,\n      \" \",\n      windowArg);\n  for (auto i = 0; i < internal.size(); ++i) {\n    detail::Link l;\n    l.internal = internal[i];\n    l.external = external[i];\n    l.offset = offset[i];\n    l.window = window[i];\n    links->push_back(l);\n  }\n}\n\nNetDef extractNetDef(const OperatorDef& op, const std::string& argName) {\n  if (ArgumentHelper::HasSingleArgumentOfType<OperatorDef, NetDef>(\n          op, argName)) {\n    return ArgumentHelper::GetSingleArgument<OperatorDef, NetDef>(\n        op, argName, NetDef());\n  } else {\n#ifndef CAFFE2_RNN_NO_TEXT_FORMAT\n    NetDef result;\n    const auto netString =\n        ArgumentHelper::GetSingleArgument<OperatorDef, string>(op, argName, \"\");\n    CAFFE_ENFORCE(\n        google::protobuf::TextFormat::ParseFromString(netString, &result),\n        \"Invalid NetDef\");\n    return result;\n#else\n    CAFFE_THROW(\"No valid NetDef for argument \", argName);\n#endif\n  }\n}\n} // namespace detail\n}\n"
  },
  {
    "path": "caffe2/operators/recurrent_network_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_RECURRENT_NETWORK_OP_H_\n#define CAFFE2_OPERATORS_RECURRENT_NETWORK_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/operators/recurrent_network_executor.h\"\n#include \"caffe2/utils/conversions.h\"\n#include \"caffe2/utils/math.h\"\n\nCAFFE2_DECLARE_bool(caffe2_rnn_executor);\n\nnamespace caffe2 {\nnamespace detail {\n\nstruct Param {\n  std::string param;\n  std::string grad;\n  std::string cellGradient;\n};\n\nstruct RecurrentInput {\n  std::string state;\n  std::string input;\n};\n\nstruct RecurrentGradient {\n  std::string param;\n  std::string grad;\n  std::string externalGrad;\n  std::string lastExternalGrad;\n  int32_t offset;\n};\n\nstruct OffsetAlias {\n  std::string src;\n  std::string dst;\n  int32_t offset{0};\n};\n\nstruct Link {\n  std::string internal;\n  std::string external;\n  int32_t offset{0};\n  int32_t window{1};\n};\n\nstruct ScratchWorkspaces {\n  std::vector<std::shared_ptr<Workspace>> stepWorkspaces;\n  std::shared_ptr<Workspace> sharedBlobsWs = nullptr;\n};\n\ninline void UpdateTimestepBlob(Workspace* ws, std::string blob_name, int t) {\n  ws->CreateBlob(blob_name)->GetMutable<TensorCPU>()->Resize(1);\n  auto timestepBlob = ws->GetBlob(blob_name);\n  CAFFE_ENFORCE(timestepBlob);\n  timestepBlob->GetMutable<TensorCPU>()->mutable_data<int32_t>()[0] = t;\n}\n\nstd::map<string, string> GetRecurrentMapping(\n  const std::vector<detail::Link>& links, bool backward);\n\ntemplate <typename T, typename Context>\nvoid applyOffsetAlias(\n    const OffsetAlias& oc,\n    Workspace* ws,\n    Context* /*context*/) {\n  VLOG(1) << \"Aliasing: \" << oc.src << \" to: \" << oc.dst\n          << \" at offset: \" << oc.offset;\n  auto srcBlob = ws->GetBlob(oc.src);\n  CAFFE_ENFORCE(srcBlob);\n  auto* src = srcBlob->template GetMutable<Tensor<Context>>();\n  auto* dst = ws->GetBlob(oc.dst)->template GetMutable<Tensor<Context>>();\n  auto timestep = src->size() / src->dim(0);\n  auto dims = src->dims();\n  const int32_t startDstTimestep =\n      oc.offset >= 0 ? oc.offset : src->dim(0) + oc.offset;\n  const int32_t numDstTimesteps = src->dim(0) - startDstTimestep;\n  CAFFE_ENFORCE(\n      numDstTimesteps >= 1, \"Invalid number of timesteps: \", numDstTimesteps);\n  dims[0] = numDstTimesteps;\n  dst->Resize(dims);\n  CAFFE_ENFORCE(timestep == dst->size() / numDstTimesteps, \"Invalid offset\");\n  dst->ShareExternalPointer(\n      src->template mutable_data<T>() + startDstTimestep * timestep,\n      dst->size());\n}\n\ntemplate <typename T, class Context>\nvoid repeatCopy(\n    size_t repeat_n,\n    size_t n,\n    const T* src,\n    T* dst,\n    Context* context) {\n  for (int i = 0; i < repeat_n; ++i) {\n    context->template Copy<T, Context, Context>(n, src, dst + i * n);\n  }\n}\n\n/**\n * Copy external input to the step net into the first item of\n * (T + 1) X batch_size X input_size tensor\n */\ntemplate <typename T, typename Context>\nvoid initializeRecurrentInput(\n    const RecurrentInput& rc,\n    int32_t seqLen,\n    int32_t batchSize,\n    Workspace* ws,\n    Context* context) {\n  auto stateBlob = ws->GetBlob(rc.state);\n  CAFFE_ENFORCE(stateBlob);\n  auto* state = stateBlob->template GetMutable<Tensor<Context>>();\n\n  auto inputBlob = ws->GetBlob(rc.input);\n  CAFFE_ENFORCE(inputBlob);\n  const auto& input = inputBlob->template Get<Tensor<Context>>();\n  CAFFE_ENFORCE_GE(input.ndim(), 1, rc.input);\n  CAFFE_ENFORCE_LE(input.ndim(), 3, rc.input);\n\n  const auto stateSize = input.dim(input.ndim() - 1);\n  // Sometimes we want to provide more than one initial step.\n  // For example, if we do a convolution op in step net\n  // and need a sufficient left padding around the input.\n  // This could be used together with links where window != 1.\n  auto initialStateLength = 1;\n  if (input.ndim() == 3) {\n    initialStateLength = input.dim(0);\n  }\n  // States at [0, ..., (T + initialStateLength - 1)] (inclusive)\n  state->Resize(seqLen + initialStateLength, batchSize, stateSize);\n\n  if (input.ndim() >= 2) {\n    CAFFE_ENFORCE_EQ(input.dim(input.ndim() - 2), batchSize, rc.input);\n    context->template Copy<T, Context, Context>(\n        batchSize * stateSize * initialStateLength,\n        input.template data<T>(),\n        state->template mutable_data<T>());\n  } else {\n    // Usually, the initial state is the same for all inputs in the batch.\n    // So the op conveniently accepts 1-D input and copies it batchSize times.\n    repeatCopy<T, Context>(\n          batchSize,\n          stateSize,\n          input.template data<T>(),\n          state->template mutable_data<T>(),\n          context);\n  }\n}\n\nvoid PrependOps(std::vector<OperatorDef> ops, NetDef* netdef);\n\nvoid AddApplyLinkOps(\n    const vector<Link>& links,\n    std::string timestep,\n    const DeviceOption& device_option,\n    NetDef* netdef);\n\nvoid extractLinks(\n    OperatorBase* op,\n    const std::string& internalArg,\n    const std::string& externalArg,\n    const std::string& offsetArg,\n    const std::string& windowArg,\n    std::vector<detail::Link>* links);\n\nNetDef extractNetDef(const OperatorDef& op, const std::string& argName);\n} // namespace detail\n\ntemplate <class Context>\nclass RecurrentNetworkOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  RecurrentNetworkOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        sharedWs_(ws),\n        enable_rnn_executor_(OperatorBase::template GetSingleArgument<bool>(\n            \"enable_rnn_executor\",\n            false)),\n        timestep_(OperatorBase::template GetSingleArgument<std::string>(\n            \"timestep\",\n            \"timestep\")) {\n    CAFFE_ENFORCE(ws);\n\n    stepNetDef_ = detail::extractNetDef(operator_def, \"step_net\");\n\n    recurrentInputs_ = constructRecurrentInputs(operator_def, sharedWs_);\n    links_ = constructLinks();\n    aliases_ = constructAliases();\n\n    stepNetDef_.add_external_input(timestep_);\n    detail::AddApplyLinkOps(\n        links_, timestep_, operator_def.device_option(), &stepNetDef_);\n\n    if (FLAGS_caffe2_rnn_executor && enable_rnn_executor_) {\n      VLOG(1) << \"Use RecurrentNetworkExecutor\";\n      auto recurrent_map = detail::GetRecurrentMapping(links_, false /* backward */);\n      rnnExecutor_ =\n          createRNNExecutor<Context>(\n              stepNetDef_,\n              recurrent_map,\n              timestep_,\n              ArgumentHelper(operator_def));\n    } else {\n      // Fix for legacy models that pass \"rnn\" type net\n      if (stepNetDef_.type() == \"rnn\") {\n        stepNetDef_.set_type(\"async_simple\");\n      }\n      CAFFE_ENFORCE(stepNetDef_.type() != \"async_dag\");\n    }\n  }\n\n  size_t NumObservers() override {\n    size_t num = this->observers_list_.size();\n    if (rnnExecutor_) {\n      num += rnnExecutor_->NumObserversStepNet();\n    }\n    return num;\n  }\n\n  std::vector<detail::RecurrentInput> constructRecurrentInputs(\n      const OperatorDef& operator_def,\n      Workspace* sharedWs) {\n    const auto states =\n        OperatorBase::GetRepeatedArgument<std::string>(\"recurrent_states\");\n    const auto inputs =\n        OperatorBase::GetRepeatedArgument<int>(\"initial_recurrent_state_ids\");\n    CAFFE_ENFORCE_EQ(states.size(), inputs.size(), \"states/inputs mismatch\");\n    std::vector<detail::RecurrentInput> ris;\n    for (auto i = 0; i < states.size(); ++i) {\n      // States need to be \"global\" (since they are shared between\n      // forward and backward).\n      sharedWs->CreateBlob(states[i]);\n\n      detail::RecurrentInput ri;\n      ri.state = states[i];\n      ri.input = operator_def.input(inputs[i]);\n      ris.push_back(ri);\n    }\n    return ris;\n  }\n\n  std::vector<detail::OffsetAlias> constructAliases() {\n    const auto& src =\n        OperatorBase::GetRepeatedArgument<std::string>(\"alias_src\");\n    const auto& dst =\n        OperatorBase::GetRepeatedArgument<std::string>(\"alias_dst\");\n    const auto& offset =\n        OperatorBase::GetRepeatedArgument<int32_t>(\"alias_offset\");\n    CAFFE_ENFORCE(\n        src.size() == offset.size(), \"alias_src/alias_offset mismatch\");\n    CAFFE_ENFORCE(\n        dst.size() == offset.size(), \"alias_dst/alias_offset mismatch\");\n    std::vector<detail::OffsetAlias> aliases;\n    for (auto i = 0; i < src.size(); ++i) {\n      detail::OffsetAlias oc;\n      oc.src = src[i];\n      oc.dst = dst[i];\n      oc.offset = offset[i];\n      aliases.push_back(oc);\n    }\n    return aliases;\n  }\n\n  /**\n    * Some blobs can be marked as to be recomputed on backward pass.\n    * For those blobs, we do not want to allocate on each step workspace,\n    * but we instead store that blob in the shared workspace so all\n    * steps can use the same buffer on forward pass.\n    */\n  void initializeBlobsToRecomputeOnBackward(Workspace* sharedBlobsWs) {\n    std::vector<std::string> v;\n    const auto& blobs = OperatorBase::GetRepeatedArgument<std::string>(\n        \"recompute_blobs_on_backward\", v);\n    for (const auto& b : blobs) {\n      // Note: if the blob already was created, this is a no-op.\n      sharedBlobsWs->CreateBlob(b);\n    }\n  }\n\n  std::vector<detail::Link> constructLinks() {\n    std::vector<detail::Link> links;\n    detail::extractLinks(\n        this,\n        \"link_internal\",\n        \"link_external\",\n        \"link_offset\",\n        \"link_window\",\n        &links);\n    return links;\n  }\n\n  template<typename T>\n  bool DoRunWithType() {\n    const auto seqLen = Input(0).dim32(0);\n    const auto batchSize = Input(0).dim32(1);\n    for (const auto& ri : recurrentInputs_) {\n      detail::initializeRecurrentInput<T, Context>(\n          ri, seqLen, batchSize, sharedWs_, &context_);\n    }\n\n    // If we don't have a backward step net, this operator is forward_only\n    // and we can avoid creating multiple workspaces.\n    bool has_backward_pass =\n        OperatorBase::HasSingleArgumentOfType<NetDef>(\"backward_step_net\") ||\n        (OperatorBase::HasSingleArgumentOfType<string>(\"backward_step_net\") &&\n         OperatorBase::GetSingleArgument<string>(\"backward_step_net\", \"\") !=\n             \"\");\n\n    // With backward pass: we need to create workspace for each timestep\n    detail::ScratchWorkspaces* scratch =\n        OperatorBase::Output<detail::ScratchWorkspaces>(OutputSize() - 1);\n    std::vector<std::shared_ptr<Workspace>>& stepWorkspaces =\n        scratch->stepWorkspaces;\n    std::shared_ptr<Workspace>& sharedBlobsWs = scratch->sharedBlobsWs;\n    if (!sharedBlobsWs) {\n      sharedBlobsWs = std::make_shared<Workspace>(sharedWs_);\n    }\n\n    // Caller can decide that some of the forward activations\n    // are recomputed on backward pass. Then those activations do not\n    // have to be stored in step workspaces but can be shared.\n    initializeBlobsToRecomputeOnBackward(sharedBlobsWs.get());\n\n    if (has_backward_pass && seqLen > stepWorkspaces.size()) {\n      stepWorkspaces.resize(seqLen);\n    }\n\n    // In forward-only mode, we cycle over workspaces. This limits the amount\n    // of parallelism over timesteps that the RNNExecutor provides. So with\n    // RNN executor we use more workspaces to get better perf.\n    int num_workspaces_on_fwd_only = rnnExecutor_ ? 4 : 2;\n\n    if (!has_backward_pass && stepWorkspaces.size() < num_workspaces_on_fwd_only) {\n      // Use alternating stepWorkspaces when forward_only=True.\n      // Note that the step workspaces can be shared by other ops, thus\n      // we cannot shrink it to 2 if there are more than 2 step workspaces.\n      stepWorkspaces.resize(num_workspaces_on_fwd_only);\n    }\n\n    for (auto t = 0; t < seqLen; ++t) {\n      auto& currentStepWorkspace =\n          (has_backward_pass ? stepWorkspaces[t] :\n              stepWorkspaces[t % num_workspaces_on_fwd_only]);\n      if (!currentStepWorkspace) {\n        currentStepWorkspace = std::make_shared<Workspace>(sharedBlobsWs.get());\n      }\n\n      if (rnnExecutor_) {\n        if (!has_backward_pass) {\n          // Need to limit timestep parallelism because we cycle over workspaces\n          rnnExecutor_->SetMaxParallelTimesteps(num_workspaces_on_fwd_only);\n        }\n        rnnExecutor_->EnsureTimestepInitialized(\n            t, currentStepWorkspace.get(), this->observers_list_);\n      } else {\n        // Use plain Caffe2 nets\n        detail::UpdateTimestepBlob(currentStepWorkspace.get(), timestep_, t);\n        auto* stepNet = currentStepWorkspace->GetNet(stepNetDef_.name());\n        if (stepNet == nullptr) {\n          stepNet = currentStepWorkspace->CreateNet(stepNetDef_);\n        }\n        CAFFE_ENFORCE(stepNet, \"Step Net construction failure\");\n        // Since we have a SimpleNet, there are no races here.\n        stepNet->RunAsync();\n      }\n    }\n\n    if (rnnExecutor_) {\n      rnnExecutor_->Run(seqLen);\n    }\n\n    for (const auto& alias : aliases_) {\n      detail::applyOffsetAlias<T, Context>(alias, sharedWs_, &context_);\n    }\n\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    return DoRunWithType<float>();\n  }\n\n protected:\n  NetDef stepNetDef_;\n  Workspace* sharedWs_;\n  bool enable_rnn_executor_;\n  std::unique_ptr<RecurrentNetworkExecutorBase> rnnExecutor_;\n\n  std::vector<detail::Link> links_;\n  std::vector<detail::OffsetAlias> aliases_;\n  std::vector<detail::RecurrentInput> recurrentInputs_;\n  std::string timestep_;\n};\n\ntemplate <class Context>\nclass RecurrentNetworkGradientOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  RecurrentNetworkGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        sharedWs_(ws),\n        enable_rnn_executor_(OperatorBase::template GetSingleArgument<bool>(\n            \"enable_rnn_executor\",\n            false)),\n        timestep_(OperatorBase::template GetSingleArgument<std::string>(\n            \"timestep\",\n            \"timestep\")),\n        gradInputs_(OperatorBase::template GetRepeatedArgument<int32_t>(\n            \"outputs_with_grads\")) {\n    CAFFE_ENFORCE(ws);\n\n    stepNetDef_ = detail::extractNetDef(operator_def, \"backward_step_net\");\n\n    links_ = constructLinks();\n    params_ = constructParams(operator_def);\n    recurrentGradients_ = constructRecurrentGradients(operator_def);\n    recurrentInputIds_ = OperatorBase::template GetRepeatedArgument<int32_t>(\n        \"initial_recurrent_state_ids\");\n\n    /* Add operators to the backward step net to handle accumulation of\n       gradients over timesteps\n    */\n    stepNetDef_.add_external_input(timestep_);\n\n    AddGradientInputAccumulationOps(operator_def);\n    detail::AddApplyLinkOps(\n        links_, timestep_, operator_def.device_option(), &stepNetDef_);\n    AddParamGradientAccumulationOps(operator_def);\n\n    if (FLAGS_caffe2_rnn_executor && enable_rnn_executor_) {\n      InitializeExecutor(operator_def);\n    }\n  }\n\n  // Renaming maps (generated by memonger.py)\n  std::string remappedName(std::string blob_name) {\n    return OperatorBase::template GetSingleArgument<std::string>(\n        blob_name + \".rename\", blob_name);\n  }\n\n  detail::Link remappedLink(const detail::Link& link) {\n    detail::Link renamed_link = link;\n    renamed_link.internal = remappedName(link.internal);\n    renamed_link.external = remappedName(link.external);\n    return renamed_link;\n  }\n\n  void renameOpInputOutput(std::string from_name, std::string to_name) {\n    for (int j = 0; j < stepNetDef_.op_size(); j++) {\n      auto* op = stepNetDef_.mutable_op(j);\n      for (int i = 0; i < op->input_size(); i++) {\n        if (op->input(i) == from_name) {\n          op->set_input(i, to_name);\n        }\n      }\n      for (int i = 0; i < op->output_size(); i++) {\n        if (op->output(i) == from_name) {\n          op->set_output(i, to_name);\n        }\n      }\n    }\n  }\n\n  std::vector<detail::Param> constructParams(const OperatorDef& operator_def) {\n    std::vector<detail::Param> params;\n    const auto& param = OperatorBase::GetRepeatedArgument<int32_t>(\"param\");\n    const auto& param_grads =\n        OperatorBase::GetRepeatedArgument<string>(\"param_grads\");\n    CAFFE_ENFORCE(\n        param_grads.empty() || param_grads.size() == param.size(),\n        param.size(),\n        \" != \",\n        param_grads.size());\n    for (int i = 0; i < param.size(); ++i) {\n      detail::Param p;\n      // Forward inputs come after [outputs_with_grads] gradient inputs\n      p.param = operator_def.input(param[i] + gradInputs_.size());\n      // See GetRecurrentNetworkGradient to understand offseting here\n      p.grad = operator_def.output(i + numSequences_);\n\n      std::string grad_blob =\n          param_grads.empty() ? p.grad : remappedName(param_grads[i]);\n      p.cellGradient = grad_blob + \"_tmpstep\";\n      params.push_back(p);\n\n      renameOpInputOutput(grad_blob, p.cellGradient);\n    }\n    return params;\n  }\n\n  std::vector<detail::RecurrentGradient> constructRecurrentGradients(\n      const OperatorDef& operator_def) {\n    std::vector<detail::RecurrentGradient> rgs;\n    const auto& recurrent =\n        OperatorBase::GetRepeatedArgument<std::string>(\"recurrent_states\");\n    const auto& alias_src =\n        OperatorBase::GetRepeatedArgument<std::string>(\"alias_src\");\n    const auto& offset =\n        OperatorBase::GetRepeatedArgument<int32_t>(\"alias_offset\");\n\n    for (auto i = 0; i < recurrent.size(); ++i) {\n      detail::RecurrentGradient rg;\n      rg.param = recurrent[i];\n      rg.grad = remappedName(recurrent[i] + \"_grad\");\n\n      for (int j = 0; j < alias_src.size(); ++j) {\n        if (alias_src[j] != recurrent[i]) {\n          continue;\n        }\n        int idx = -1;\n        for (int k = 0; k < gradInputs_.size(); ++k) {\n          if (gradInputs_[k] == j) {\n            idx = k;\n          }\n        }\n        if (idx == -1) {\n          continue;\n        }\n\n        CAFFE_ENFORCE(offset[j] == 1 || offset[j] == -1);\n        if (offset[j] == 1) {\n          rg.externalGrad = operator_def.input(idx);\n        } else if (offset[j] == -1) {\n          rg.lastExternalGrad = operator_def.input(idx);\n        }\n      }\n      rg.offset = 1;\n      rgs.push_back(rg);\n    }\n    return rgs;\n  }\n\n  std::vector<detail::Link> constructLinks() {\n    std::vector<detail::Link> links;\n    detail::extractLinks(\n        this,\n        \"link_internal\",\n        \"link_external\",\n        \"link_offset\",\n        \"link_window\",\n        &links);\n    detail::extractLinks(\n        this,\n        \"backward_link_internal\",\n        \"backward_link_external\",\n        \"backward_link_offset\",\n        \"\",\n        &links);\n    for (int i = 0; i < links.size(); i++) {\n      links[i] = remappedLink(links[i]);\n    }\n    return links;\n  }\n\n  void InitializeExecutor(const OperatorDef& operator_def) {\n    VLOG(1) << \"Use RecurrentNetworkExecutor for backward\";\n    auto recurrent_map = detail::GetRecurrentMapping(links_, true /* backward */);\n    rnnExecutor_ = createRNNExecutor<Context>(\n      stepNetDef_, recurrent_map, timestep_, ArgumentHelper(operator_def));\n  }\n\n  void AddGradientInputAccumulationOps(const OperatorDef& operator_def) {\n    /**\n      * Add ops to the step net to accumulate input gradients.\n      */\n    std::vector<OperatorDef> ops;\n    for (const auto& rg : recurrentGradients_) {\n      if (rg.externalGrad.empty()) {\n        continue;\n      }\n      VLOG(1) << \"Accumulating into: \" << rg.grad << \" from \" << rg.externalGrad\n              << \", offset: \" << rg.offset;\n\n      OperatorDef opdef;\n      opdef.set_type(\"rnn_internal_accumulate_gradient_input\");\n      opdef.add_input(timestep_);\n      opdef.add_input(rg.externalGrad);\n      opdef.add_input(rg.grad);\n      opdef.add_output(rg.grad);\n\n      // Add also the linked blobs to outputs, to ensure correct\n      // chaining.\n      for (auto& l : links_) {\n        if (rg.grad == l.external) {\n          Argument* dep_arg = opdef.add_arg();\n          dep_arg->set_name(\"rnn_dependency.\" + l.internal);\n          dep_arg->set_s(l.internal);\n        }\n      }\n\n      opdef.mutable_device_option()->CopyFrom(operator_def.device_option());\n\n      Argument* offset_arg = opdef.add_arg();\n      offset_arg->set_name(\"offset\");\n      offset_arg->set_i(rg.offset);\n      ops.push_back(opdef);\n\n      stepNetDef_.add_external_input(rg.externalGrad);\n      stepNetDef_.add_external_input(rg.grad);\n    }\n    detail::PrependOps(ops, &stepNetDef_);\n  }\n\n  void AddParamGradientAccumulationOps(const OperatorDef& operator_def) {\n    // If a user passes in param_grads mapping, we can copy dirrectly\n    // form a blob where backward cell net written data to.\n    // This becomes handy in a case where gradient from the cell net\n    // is an internal blob of the backward cell. This happens, for example,\n    // when SumOp is the first op of the cell\n    for (const auto& param : params_) {\n      OperatorDef opdef;\n      opdef.set_type(\"Sum\");\n      opdef.add_input(param.grad);\n      opdef.add_input(param.cellGradient);\n      opdef.add_output(param.grad);\n      opdef.mutable_device_option()->CopyFrom(operator_def.device_option());\n      stepNetDef_.add_op()->CopyFrom(opdef);\n      stepNetDef_.add_external_input(param.grad);\n    }\n  }\n\n  void CreateSharedBlobs(\n      const std::shared_ptr<Workspace>& step0Ws,\n      Workspace* sharedBlobsWs) {\n    /**\n      * Create all output blobs created by ops of the backward step net, they\n      * can be shared.\n      */\n    for (auto& op : stepNetDef_.op()) {\n      for (const string& outp : op.output()) {\n        if (!step0Ws->HasBlob(outp)) {\n          sharedBlobsWs->CreateBlob(outp);\n        }\n      }\n    }\n  }\n\n  template<typename T>\n  bool DoRunWithType() {\n    const auto seqLen = Input(gradInputs_.size()).dim32(0);\n    VLOG(1) << \"seqLen: \" << seqLen;\n\n    const detail::ScratchWorkspaces& scratch =\n        OperatorBase::Input<detail::ScratchWorkspaces>(InputSize() - 1);\n    const std::vector<std::shared_ptr<Workspace>>& stepWorkspaces =\n        scratch.stepWorkspaces;\n    CAFFE_ENFORCE_GE(stepWorkspaces.size(), seqLen);\n    Workspace& sharedBlobsWs = *scratch.sharedBlobsWs.get();\n\n    const auto batchSize = Input(0).dim32(1);\n    for (auto& param : params_) {\n      auto pBlob = sharedWs_->GetBlob(param.param);\n      CAFFE_ENFORCE(pBlob);\n      const auto& p = pBlob->template Get<Tensor<Context>>();\n\n      auto gBlob = sharedWs_->GetBlob(param.grad);\n      CAFFE_ENFORCE(gBlob);\n      auto* g = gBlob->template GetMutable<Tensor<Context>>();\n      g->ResizeLike(p);\n      math::Set<T, Context>(\n          g->size(),\n          convert::To<float,T>(0.0),\n          g->template mutable_data<T>(),\n          &context_);\n    }\n\n    for (auto& rg : recurrentGradients_) {\n      auto pBlob = sharedWs_->GetBlob(rg.param);\n      CAFFE_ENFORCE(pBlob);\n      const auto& p = pBlob->template Get<Tensor<Context>>();\n\n      auto gBlob = sharedWs_->CreateBlob(rg.grad);\n      CAFFE_ENFORCE(gBlob);\n      auto* g = gBlob->template GetMutable<Tensor<Context>>();\n      g->ResizeLike(p);\n      CAFFE_ENFORCE_EQ(g->ndim(), 3);\n      const auto timestep = g->size() / g->dim(0);\n      // Fill the last timestep with zeros for the gradient\n      math::Set<T, Context>(\n          timestep,\n          convert::To<float,T>(0.0),\n          g->template mutable_data<T>() + (g->dim(0) - 1) * timestep,\n          &context_);\n    }\n\n    // This code assumes that there are several inputs\n    // sequences. Actually it is not supported by the rest of the code,\n    // and numSequences_ is a constant, equal to 1.\n    for (int i = 0; i < numSequences_; ++i) {\n      // Offseting as the first gradInputs_.size() inputs of the op\n      // are from GO. Then all I(0..N).\n      const int gradientInputIndex = i + gradInputs_.size();\n      const auto& inputName = this->debug_def().input(gradientInputIndex);\n      auto gradientName = remappedName(inputName + \"_grad\");\n      VLOG(1) << \"Initializing gradient for input \" << gradientInputIndex\n              << \" (\" << inputName << \") \"\n              << \" as blob \" << gradientName\n              << \". Size: \" << Input(gradientInputIndex).size();\n      auto pGradientBlob = sharedWs_->GetBlob(gradientName);\n      CAFFE_ENFORCE(pGradientBlob);\n      auto* g = pGradientBlob->template GetMutable<Tensor<Context>>();\n      g->ResizeLike(Input(gradientInputIndex));\n      g->template mutable_data<T>();\n    }\n\n    auto accumulateFinalInputGradients = [&]() {\n      for (const auto& rg : recurrentGradients_) {\n        if (rg.lastExternalGrad.empty()) {\n          continue;\n        }\n        VLOG(1) << \"Accumulating into: \" << rg.grad << \" from \"\n                << rg.lastExternalGrad << \" for final time step (sep. blob)\";\n        auto gBlob = sharedWs_->GetBlob(rg.grad);\n        CAFFE_ENFORCE(gBlob);\n        auto* g = gBlob->template GetMutable<Tensor<Context>>();\n\n        auto oglastBlob = sharedWs_->GetBlob(rg.lastExternalGrad);\n        CAFFE_ENFORCE(oglastBlob);\n        const auto& oglast = oglastBlob->template Get<Tensor<Context>>();\n        CAFFE_ENFORCE_EQ(g->dim(1), oglast.dim(1));\n        CAFFE_ENFORCE_EQ(g->dim(2), oglast.dim(2));\n\n        const auto t = g->dim(0) - 1;\n        const auto timestep_size = g->size() / g->dim(0);\n        CAFFE_ENFORCE_EQ(timestep_size, oglast.size());\n        T* g_data_with_offset =\n            g->template mutable_data<T>() + t * timestep_size;\n        math::Add<T, Context>(\n            timestep_size,\n            oglast.template data<T>(),\n            g_data_with_offset,\n            g_data_with_offset,\n            &context_);\n      }\n    };\n\n    accumulateFinalInputGradients();\n\n    // Create shared blobs for blobs that can be shared between\n    // all timesteps.\n    if (stepWorkspaces.size() > 0) {\n      CreateSharedBlobs(stepWorkspaces[0], &sharedBlobsWs);\n    }\n    for (int32_t t = seqLen - 1; t >= 0; --t) {\n      if (rnnExecutor_) {\n        rnnExecutor_->EnsureTimestepInitialized(\n            t, stepWorkspaces[t].get(), this->observers_list_);\n      } else {\n        auto* stepNet = stepWorkspaces[t].get()->GetNet(stepNetDef_.name());\n        if (stepNet == nullptr) {\n          stepNet = stepWorkspaces[t].get()->CreateNet(stepNetDef_);\n        }\n        CAFFE_ENFORCE(stepNet);\n        stepNet->RunAsync();\n      }\n    }\n\n    if (rnnExecutor_) {\n      rnnExecutor_->RunBackwards(seqLen);\n    }\n\n    CAFFE_ENFORCE_EQ(recurrentInputIds_.size(), recurrentGradients_.size());\n    for (int i = 0; i < recurrentInputIds_.size(); ++i) {\n      // See GetRecurrentNetworkGradient to understand offseting here\n      // Outputs of the gradient are inputs of the forward pass.\n      // So we need to offset on all inputs that go before recurrent\n      // initial ones\n      auto outputIdx = i + params_.size() + numSequences_;\n      // because first gradInputs_.size() inputs are from GO\n      int inputId = recurrentInputIds_[i] + gradInputs_.size();\n      VLOG(1) << \"Resetting output \" << this->debug_def().output(outputIdx)\n              << \" like input \" << this->debug_def().input(inputId);\n      Output(outputIdx)->ResizeLike(Input(inputId));\n      T* output_data = Output(outputIdx)->template mutable_data<T>();\n      auto pBlob = sharedWs_->GetBlob(recurrentGradients_[i].grad);\n      CAFFE_ENFORCE(pBlob);\n      auto* p = pBlob->template GetMutable<Tensor<Context>>();\n\n      if (Input(inputId).ndim() >= 2) {\n        // Gradient states blob should live. And if it gets changed by the\n        // backward pass, then output should be changed as well. Thus it should\n        // be okay to share data here\n        Output(outputIdx)->template ShareExternalPointer<T>(\n            p->template mutable_data<T>());\n      } else {\n        // We need to do a bunch of Adds any way. So lets not worry about\n        // copy / share data here. One way to speed this up could be a kernel\n        // which sums up several tensors together instead of going 1 by 1\n        const auto recurrentStateSize = Input(inputId).dim32(0);\n\n        math::Set<T, Context>(\n            recurrentStateSize,\n            convert::To<float,T>(0.0),\n            output_data,\n            &context_);\n\n        math::AddStripedBatch<T, Context>(\n            recurrentStateSize,\n            p->template data<T>(),\n            output_data,\n            recurrentStateSize,\n            batchSize,\n            &context_);\n      }\n    }\n\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    return DoRunWithType<float>();\n  }\n\n protected:\n  NetDef stepNetDef_;\n  Workspace* sharedWs_;\n  bool enable_rnn_executor_;\n  std::unique_ptr<RecurrentNetworkExecutorBase> rnnExecutor_;\n  std::vector<detail::Link> links_;\n  std::vector<detail::Param> params_;\n  std::vector<detail::RecurrentGradient> recurrentGradients_;\n  std::string timestep_;\n  // For now we support only one input sequence\n  const int numSequences_{1};\n  std::vector<int32_t> recurrentInputIds_;\n  std::vector<int32_t> gradInputs_;\n};\n\ntemplate <class Context>\nclass AccumulateInputGradientOp : public Operator<Context> {\n public:\n  AccumulateInputGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        offset_(OperatorBase::GetSingleArgument<int>(\"offset\", -1)) {\n    CAFFE_ENFORCE(offset_ >= 0, \"Offset not set\");\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  template<typename T>\n  bool DoRunWithType() {\n    const auto& t0 = OperatorBase::Input<Tensor<CPUContext>>(0);\n    const auto t = t0.template data<int32_t>()[0];\n    auto& og = Input(1);\n    auto* g = Output(0);\n\n    T* g_data = g->template mutable_data<T>();\n    const auto timestep_size = g->size() / g->dim(0);\n\n    CAFFE_ENFORCE(\n        (t + offset_) * timestep_size + timestep_size <= g->size(),\n        \"Accumulation destination address over bounds\");\n    CAFFE_ENFORCE(\n        t * timestep_size + timestep_size <= og.size(),\n        \"Accumulation source address out of bounds\");\n\n    math::Add<T, Context>(\n        timestep_size,\n        og.template data<T>() + t * timestep_size,\n        g_data + (t + offset_) * timestep_size,\n        g_data + (t + offset_) * timestep_size,\n        &context_);\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<float>>::call(this, Input(1));\n  }\n\n private:\n  int offset_;\n};\n\ntemplate <class Context>\nclass RNNApplyLinkOp : public Operator<Context> {\n public:\n  RNNApplyLinkOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        offset_(OperatorBase::GetSingleArgument<int>(\"offset\", -1)),\n        window_(OperatorBase::GetSingleArgument<int>(\"window\", -1)) {\n    CAFFE_ENFORCE(offset_ >= 0, \"offset not set\");\n    CAFFE_ENFORCE(window_ >= 0, \"window not set\");\n  }\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  template <typename T>\n  bool DoRunWithType() {\n    // Both internal and external appear as both input and output to enforce\n    // correct dependency computation.\n    const auto& t0 = OperatorBase::Input<Tensor<CPUContext>>(0);\n    const auto t = t0.template data<int32_t>()[0];\n    auto& external = Input(1);\n\n    auto* internal_out = Output(0);\n    auto* external_out = Output(1);\n\n    CAFFE_ENFORCE_GT(external.size(), 0);\n    const TIndex externalTimestepSize = external.size() / external.dim(0);\n    auto* externalData = external_out->template mutable_data<T>() +\n        (t + offset_) * externalTimestepSize;\n    auto internalDims = external_out->dims();\n    internalDims[0] = window_;\n\n    internal_out->Resize(internalDims);\n    internal_out->ShareExternalPointer(\n        externalData, externalTimestepSize * window_);\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    return DoRunWithType<float>();\n  }\n\n private:\n  int offset_;\n  int window_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_RECURRENT_NETWORK_OP_H_\n"
  },
  {
    "path": "caffe2/operators/recurrent_network_op_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/recurrent_network_op.h\"\n\nnamespace caffe2 {\n\nnamespace detail {\n\ntemplate <typename T, typename Context>\nvoid initializeRecurrentInput(\n    const RecurrentInput& rc,\n    int32_t seqLen,\n    int32_t batchSize,\n    Workspace* ws,\n    Context* context);\n\nnamespace {\n\ntemplate <typename T>\n__global__\nvoid initRecurrentInput_kernel(\n    size_t stateSize,\n    const T* input,\n    T* state) {\n  // index into appropriate target buffer\n  const int block_id = blockIdx.x;\n  T* state_local = state + block_id*stateSize;\n\n  // copy\n  for (int idx=threadIdx.x; idx < stateSize; idx+=blockDim.x) {\n    state_local[idx] = input[idx];\n  }\n}\n\n\n}; // namespace\n\ntemplate <>\nvoid repeatCopy(\n    size_t repeat_n,\n    size_t n,\n    const float* src,\n    float* dst,\n    CUDAContext* context) {\n    initRecurrentInput_kernel<float><<<repeat_n, CAFFE_CUDA_NUM_THREADS, 0, context->cuda_stream()>>>(\n        n, src, dst);\n}\ntemplate <>\nvoid repeatCopy(\n    size_t repeat_n,\n    size_t n,\n    const float16* src,\n    float16* dst,\n    CUDAContext* context) {\n    initRecurrentInput_kernel<float16><<<repeat_n, CAFFE_CUDA_NUM_THREADS, 0, context->cuda_stream()>>>(\n        n, src, dst);\n}\n\n}; // namespace detail\n\ntemplate <>\nbool RecurrentNetworkOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<float, float16>>::call(this, Input(0));\n}\n\ntemplate <>\nbool RecurrentNetworkGradientOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<float, float16>>::call(this, Input(0));\n}\n\ntemplate <>\nbool AccumulateInputGradientOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<float, float16>>::call(this, Input(1));\n}\n\ntemplate <>\nbool RNNApplyLinkOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<float, float16>>::call(this, Input(1));\n}\n\nREGISTER_CUDA_OPERATOR(\n    RecurrentNetwork,\n    RecurrentNetworkOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    RecurrentNetworkGradient,\n    RecurrentNetworkGradientOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    rnn_internal_accumulate_gradient_input,\n    AccumulateInputGradientOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    rnn_internal_apply_link,\n    RNNApplyLinkOp<CUDAContext>);\n\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/recurrent_op_cudnn.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/recurrent_op_cudnn.h\"\n#include \"caffe2/utils/math.h\"\n\n#include <map>\n\nnamespace caffe2 {\n\nnamespace detail {\n\ntemplate <typename T>\nTensorDescriptors<T>::TensorDescriptors(\n    size_t n,\n    const std::vector<int>& dim,\n    const std::vector<int>& stride) {\n  descs_.resize(n);\n  CAFFE_ENFORCE_EQ(dim.size(), stride.size());\n  for (auto i = 0; i < n; ++i) {\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&descs_[i]));\n    CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n        descs_[i],\n        cudnnTypeWrapper<T>::type,\n        dim.size(),\n        dim.data(),\n        stride.data()));\n  }\n}\n\ntemplate <typename T>\nTensorDescriptors<T>::~TensorDescriptors() {\n  for (auto desc : descs_) {\n    cudnnDestroyTensorDescriptor(desc);\n  }\n}\n}\n\ntemplate <typename T>\nRecurrentBaseOp<T>::RecurrentBaseOp(\n    const OperatorDef& operator_def,\n    Workspace* ws)\n    : Operator<CUDAContext>(operator_def, ws), cudnn_wrapper_(&context_) {\n  CUDNN_ENFORCE(cudnnCreateDropoutDescriptor(&dropoutDesc_));\n  CUDNN_ENFORCE(cudnnCreateRNNDescriptor(&rnnDesc_));\n  CUDNN_ENFORCE(cudnnCreateFilterDescriptor(&wDesc_));\n  CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&hxDesc_));\n  CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&cxDesc_));\n  CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&hyDesc_));\n  CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&cyDesc_));\n}\n\ntemplate <typename T>\nRecurrentBaseOp<T>::~RecurrentBaseOp() {\n  CUDNN_ENFORCE(cudnnDestroyDropoutDescriptor(dropoutDesc_));\n  CUDNN_ENFORCE(cudnnDestroyRNNDescriptor(rnnDesc_));\n  CUDNN_ENFORCE(cudnnDestroyFilterDescriptor(wDesc_));\n  CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(hxDesc_));\n  CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(cxDesc_));\n  CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(hyDesc_));\n  CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(cyDesc_));\n}\n\ntemplate <typename T>\nvoid RecurrentBaseOp<T>::initialize(\n    const Tensor<CUDAContext>& input,\n    Tensor<CUDAContext>* dropoutStates,\n    Tensor<CUDAContext>* output,\n    Tensor<CUDAContext>* hiddenOutput,\n    Tensor<CUDAContext>* cellOutput) {\n  static_assert(sizeof(T) == 4, \"\"); // workaround clang bug\n  CAFFE_ENFORCE_GE(input.ndim(), 3);\n  const int seqLength = input.dim(0);\n  const int batchSize = input.dim(1);\n  const int inputDim = input.dim(2);\n  const int hiddenSize = OperatorBase::GetSingleArgument<int>(\"hidden_size\", 0);\n  CAFFE_ENFORCE_GT(hiddenSize, 0);\n  const auto bidirectional =\n      OperatorBase::GetSingleArgument<int>(\"bidirectional\", 0);\n  CAFFE_ENFORCE(bidirectional == 0 || bidirectional == 1);\n  const auto numDirections = bidirectional == 1 ? 2 : 1;\n  const auto outputDim = hiddenSize * numDirections;\n  const auto rnnDirection =\n      bidirectional == 1 ? CUDNN_BIDIRECTIONAL : CUDNN_UNIDIRECTIONAL;\n  const auto numLayers = OperatorBase::GetSingleArgument<int>(\"num_layers\", 0);\n  CAFFE_ENFORCE_GT(numLayers, 0);\n  const auto& rnnModeStr =\n      OperatorBase::GetSingleArgument<string>(\"rnn_mode\", \"\");\n  CAFFE_ENFORCE(rnnModeStr == \"lstm\" || rnnModeStr == \"gru\");\n  const auto rnnMode = rnnModeStr == \"lstm\" ? CUDNN_LSTM : CUDNN_GRU;\n  const auto& rnnInputStr =\n      OperatorBase::GetSingleArgument<string>(\"input_mode\", \"\");\n  CAFFE_ENFORCE(rnnInputStr == \"linear\" || rnnInputStr == \"skip\");\n  const auto rnnInput =\n      rnnInputStr == \"linear\" ? CUDNN_LINEAR_INPUT : CUDNN_SKIP_INPUT;\n\n  // Dropout setup\n  {\n    if (dropoutStates) {\n      size_t stateSize;\n      float dropout_param =\n          OperatorBase::GetSingleArgument<float>(\"dropout\", 1.0);\n      if (dropout_param < 1.0) {\n        CUDNN_ENFORCE(cudnnDropoutGetStatesSize(\n            cudnn_wrapper_.inline_cudnn_handle(), &stateSize));\n        dropoutStates->Resize(std::vector<int>{static_cast<int>(\n            stateSize / 4 /* sizeof(T) - workaround clang bug */)});\n        CUDNN_ENFORCE(cudnnSetDropoutDescriptor(\n            dropoutDesc_,\n            cudnn_wrapper_.inline_cudnn_handle(),\n            dropout_param,\n            dropoutStates->template mutable_data<T>(),\n            stateSize,\n            OperatorBase::GetSingleArgument<int>(\"seed\", 0)));\n      }\n    }\n  }\n\n  // RNN setup\n  {\n#if CUDNN_VERSION_MIN(7, 0, 0)\n    CUDNN_ENFORCE(cudnnSetRNNDescriptor(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        rnnDesc_,\n        hiddenSize,\n        numLayers,\n        dropoutDesc_,\n        rnnInput,\n        rnnDirection,\n        rnnMode,\n        CUDNN_RNN_ALGO_STANDARD, // TODO: verify correctness / efficiency.\n        cudnnTypeWrapper<T>::type));\n#else\n    CUDNN_ENFORCE(cudnnSetRNNDescriptor(\n        rnnDesc_,\n        hiddenSize,\n        numLayers,\n        dropoutDesc_,\n        rnnInput,\n        rnnDirection,\n        rnnMode,\n        cudnnTypeWrapper<T>::type));\n#endif\n  }\n  // X setup\n  {\n    xDesc_.reset(new detail::TensorDescriptors<T>(\n        seqLength,\n        // Third dimension is unused\n        {batchSize, inputDim, 1},\n        // Fully-packed\n        {inputDim, 1, 1}));\n  }\n  // Y setup\n  {\n    yDesc_.reset(new detail::TensorDescriptors<T>(\n        seqLength,\n        // Third dimension is unused\n        {batchSize, hiddenSize * numDirections, 1},\n        // Fully-packed\n        {numDirections * hiddenSize, 1, 1}));\n\n    if (output) {\n      output->Resize(std::vector<int>{seqLength, batchSize, outputDim});\n    }\n  }\n\n  // Hidden/Cell setup\n  {\n    const std::array<int, 3> dim{\n        numLayers * numDirections, batchSize, hiddenSize};\n    const std::array<int, 3> stride{batchSize * hiddenSize, hiddenSize, 1};\n    CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n        hxDesc_, cudnnTypeWrapper<T>::type, 3, dim.data(), stride.data()));\n    CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n        cxDesc_, cudnnTypeWrapper<T>::type, 3, dim.data(), stride.data()));\n    CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n        hyDesc_, cudnnTypeWrapper<T>::type, 3, dim.data(), stride.data()));\n    CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n        cyDesc_, cudnnTypeWrapper<T>::type, 3, dim.data(), stride.data()));\n\n    if (hiddenOutput) {\n      hiddenOutput->Resize(\n          std::vector<int>{numLayers * numDirections, batchSize, hiddenSize});\n    }\n\n    if (cellOutput) {\n      cellOutput->Resize(\n          std::vector<int>{numLayers * numDirections, batchSize, hiddenSize});\n    }\n  }\n\n  // Weights setup\n  {\n    size_t weightsSize;\n    CUDNN_ENFORCE(cudnnGetRNNParamsSize(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        rnnDesc_,\n        xDesc_->descs()[0],\n        &weightsSize,\n        cudnnTypeWrapper<T>::type));\n    const std::array<int, 3> dims{\n        static_cast<int>(\n            weightsSize / 4 /* sizeof(T) - workaround clang bug */),\n        1,\n        1};\n    CUDNN_ENFORCE(cudnnSetFilterNdDescriptor(\n        wDesc_, cudnnTypeWrapper<T>::type, CUDNN_TENSOR_NCHW, 3, dims.data()));\n  }\n\n  // RNN workspace size\n  {\n    CUDNN_ENFORCE(cudnnGetRNNWorkspaceSize(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        rnnDesc_,\n        seqLength,\n        xDesc_->descs(),\n        &cudnnWsNbytes_));\n  }\n}\n\ntemplate <typename T>\nbool RecurrentOp<T>::RunOnDevice() {\n  const int seqLength = Input(INPUT).dim32(0);\n  if (Input(INPUT).dims() != cachedInputDims_) {\n    initialize(\n        Input(INPUT),\n        Output(DROPOUT_STATES),\n        Output(OUTPUT),\n        Output(HIDDEN_OUTPUT),\n        Output(CELL_OUTPUT));\n    cachedInputDims_ = Input(INPUT).dims();\n  }\n\n  // Validation checks\n  size_t weightsSize;\n  CUDNN_ENFORCE(cudnnGetRNNParamsSize(\n      cudnn_wrapper_.inline_cudnn_handle(),\n      rnnDesc_,\n      xDesc_->descs()[0],\n      &weightsSize,\n      cudnnTypeWrapper<T>::type));\n  CAFFE_ENFORCE_EQ(Input(WEIGHT).nbytes(), weightsSize);\n\n  // Training reserve size\n  CUDNN_ENFORCE(cudnnGetRNNTrainingReserveSize(\n      cudnn_wrapper_.inline_cudnn_handle(),\n      rnnDesc_,\n      seqLength,\n      xDesc_->descs(),\n      &reserveNbytes_));\n  Output(RNN_SCRATCH)\n      ->Resize(std::vector<int>{static_cast<int>(\n          reserveNbytes_ / 4)}); // sizeof(T) - workaround clang bug\n  Output(RNN_SCRATCH)->template mutable_data<T>();\n\n  auto InputData = [this](int i) { return this->Input(i).template data<T>(); };\n  auto OutputData = [this](int i) {\n    return this->Output(i)->template mutable_data<T>();\n  };\n\n  if (OperatorBase::GetSingleArgument<int>(OpSchema::Arg_IsTest, 0)) {\n    cudnn_wrapper_.with_cudnn_state(0, [&](CuDNNState* state) {\n      CUDNN_ENFORCE(cudnnRNNForwardInference(\n          state->cudnn_handle(),\n          rnnDesc_,\n          seqLength,\n          xDesc_->descs(),\n          InputData(INPUT), //.template data<T>(),\n          hxDesc_,\n          InputData(HIDDEN_INPUT), //.template data<T>(),\n          cxDesc_,\n          InputData(CELL_INPUT), //.template data<T>(),\n          wDesc_,\n          InputData(WEIGHT), //.template data<T>(),\n          yDesc_->descs(),\n          OutputData(OUTPUT), //->template mutable_data<T>(),\n          hyDesc_,\n          OutputData(HIDDEN_OUTPUT), //->template mutable_data<T>(),\n          cyDesc_,\n          OutputData(CELL_OUTPUT), //->template mutable_data<T>(),\n          state->workspace().get(cudnnWsNbytes_),\n          cudnnWsNbytes_));\n    });\n  } else {\n    cudnn_wrapper_.with_cudnn_state(0, [&](CuDNNState* state) {\n      CUDNN_ENFORCE(cudnnRNNForwardTraining(\n          state->cudnn_handle(),\n          rnnDesc_,\n          seqLength,\n          xDesc_->descs(),\n          InputData(INPUT), //.template data<T>(),\n          hxDesc_,\n          InputData(HIDDEN_INPUT), //.template data<T>(),\n          cxDesc_,\n          InputData(CELL_INPUT), //.template data<T>(),\n          wDesc_,\n          InputData(WEIGHT), //.template data<T>(),\n          yDesc_->descs(),\n          OutputData(OUTPUT), //->template mutable_data<T>(),\n          hyDesc_,\n          OutputData(HIDDEN_OUTPUT), //->template mutable_data<T>(),\n          cyDesc_,\n          OutputData(CELL_OUTPUT), //->template mutable_data<T>(),\n          state->workspace().get(cudnnWsNbytes_),\n          cudnnWsNbytes_,\n          OutputData(RNN_SCRATCH), //->template mutable_data<T>(),\n          reserveNbytes_));\n    });\n  }\n\n  return true;\n}\n\ntemplate <typename T>\nbool RecurrentGradientOp<T>::RunOnDevice() {\n  const int seqLength = Input(INPUT).dim32(0);\n  if (Input(INPUT).dims() != cachedInputDims_) {\n    initialize(Input(INPUT), Output(DROPOUT_STATES));\n    cachedInputDims_ = Input(INPUT).dims();\n  }\n  CUDNN_ENFORCE(cudnnGetRNNTrainingReserveSize(\n      cudnn_wrapper_.inline_cudnn_handle(),\n      rnnDesc_,\n      seqLength,\n      xDesc_->descs(),\n      &reserveNbytes_));\n  CAFFE_ENFORCE_EQ(reserveNbytes_, Input(RNN_SCRATCH).nbytes());\n  Output(GRAD_INPUT)->ResizeLike(Input(INPUT));\n  Output(GRAD_HIDDEN_INPUT)->ResizeLike(Input(HIDDEN_INPUT));\n  Output(GRAD_CELL_INPUT)->ResizeLike(Input(CELL_INPUT));\n\n  Output(GRAD_WEIGHT)->ResizeLike(Input(WEIGHT));\n  math::Set<T, CUDAContext>(\n      Output(GRAD_WEIGHT)->size(),\n      0.0,\n      Output(GRAD_WEIGHT)->template mutable_data<T>(),\n      &context_);\n\n#if CUDNN_VERSION_MIN(6,0,0)\n  auto * reserve = Output(RNN_SCRATCH_OUT)->template mutable_data<T>();\n#else\n  const auto * reserve = Output(RNN_SCRATCH_OUT)->template data<T>();\n#endif\n  auto InputData = [this](int i) { return this->Input(i).template data<T>(); };\n  auto OutputData = [this](int i) {\n    return this->Output(i)->template mutable_data<T>();\n  };\n\n  cudnn_wrapper_.with_cudnn_state(0, [&](CuDNNState* state) {\n    CUDNN_ENFORCE(cudnnRNNBackwardData(\n        state->cudnn_handle(),\n        rnnDesc_,\n        seqLength,\n        yDesc_->descs(),\n        InputData(OUTPUT), // Input(OUTPUT).template data<T>(),\n        yDesc_->descs(),\n        InputData(GRAD_OUTPUT), // Input(GRAD_OUTPUT).template data<T>(),\n        hyDesc_,\n        // Note: like CNTK, ignore these gradient inputs. t16675365 to\n        // reconsider.\n        nullptr,\n        cyDesc_,\n        nullptr,\n        wDesc_,\n        InputData(WEIGHT), // Input(WEIGHT).template data<T>(),\n        hxDesc_,\n        InputData(HIDDEN_INPUT), // Input(HIDDEN_INPUT).template data<T>(),\n        cxDesc_,\n        InputData(CELL_INPUT),\n        xDesc_->descs(),\n        OutputData(GRAD_INPUT),\n        hxDesc_,\n        OutputData(GRAD_HIDDEN_INPUT),\n        cxDesc_,\n        OutputData(GRAD_CELL_INPUT),\n        state->workspace().get(cudnnWsNbytes_),\n        cudnnWsNbytes_,\n        reserve,\n        reserveNbytes_));\n    CUDNN_ENFORCE(cudnnRNNBackwardWeights(\n        state->cudnn_handle(),\n        rnnDesc_,\n        seqLength,\n        xDesc_->descs(),\n        InputData(INPUT), // Input(INPUT).template data<T>(),\n        hxDesc_,\n        InputData(HIDDEN_INPUT), // Input(HIDDEN_INPUT).template data<T>(),\n        yDesc_->descs(),\n        InputData(OUTPUT), // Input(OUTPUT).template data<T>(),\n        state->workspace().get(cudnnWsNbytes_),\n        cudnnWsNbytes_,\n        wDesc_,\n        OutputData(\n            GRAD_WEIGHT), // Output(GRAD_WEIGHT)->template mutable_data<T>(),\n        reserve,\n        reserveNbytes_));\n  });\n\n  return true;\n}\n\ntemplate <typename T, RecurrentParamOpMode mode>\nbool RecurrentParamAccessOp<T, mode>::RunOnDevice() {\n  initialize(Input(0));\n\n  if (mode == SET_PARAM) {\n    size_t paramsSize;\n    CUDNN_ENFORCE(cudnnGetRNNParamsSize(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        rnnDesc_,\n        xDesc_->descs()[0],\n        &paramsSize,\n        cudnnTypeWrapper<T>::type));\n\n    CAFFE_ENFORCE_EQ(\n        paramsSize / 4, Input(1).size(), \"Incorrect weight initialization\");\n  }\n\n  int layer = OperatorBase::GetSingleArgument<int>(\"layer\", 0);\n  std::string param_type =\n      OperatorBase::GetSingleArgument<string>(\"param_type\", \"\");\n  std::string input_type =\n      OperatorBase::GetSingleArgument<string>(\"input_type\", \"\");\n\n  // Mapping to CUDNN constants\n  std::map<string, int> weight_constants = {{\"input_gate_w\", 0},\n                                            {\"forget_gate_w\", 1},\n                                            {\"cell_w\", 2},\n                                            {\"output_gate_w\", 3}};\n  std::map<string, int> bias_constants = {{\"input_gate_b\", 0},\n                                          {\"forget_gate_b\", 1},\n                                          {\"cell_b\", 2},\n                                          {\"output_gate_b\", 3}};\n  if (bias_constants.find(param_type) != bias_constants.end()) {\n    int param_id = bias_constants[param_type] + 4 * (input_type == \"recurrent\");\n\n    cudnnFilterDescriptor_t biasDesc;\n    CUDNN_ENFORCE(cudnnCreateFilterDescriptor(&biasDesc));\n    void* bias;\n\n    CUDNN_ENFORCE(cudnnGetRNNLinLayerBiasParams(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        rnnDesc_,\n        layer,\n        xDesc_->descs()[0],\n        wDesc_,\n        Input(1).template data<T>(),\n        param_id, // Forget gate bias for recurrent input\n        biasDesc,\n        &bias));\n    int numBiasDims;\n    std::vector<int> biasDims(3);\n    cudnnDataType_t dt;\n    cudnnTensorFormat_t tf;\n    // For some reason, the CuDNN Bias tensor is 3 dimensional\n    CUDNN_ENFORCE(cudnnGetFilterNdDescriptor(\n        biasDesc, 3, &dt, &tf, &numBiasDims, biasDims.data()));\n    CAFFE_ENFORCE_EQ(numBiasDims, 3);\n\n    if (mode == SET_PARAM) {\n      CAFFE_ENFORCE_EQ(\n          biasDims[0] * biasDims[1] * biasDims[2], Input(2).size());\n      context_.template Copy<T, CUDAContext, CUDAContext>(\n          biasDims[0] * biasDims[1] * biasDims[2],\n          Input(2).template data<T>(),\n          static_cast<T*>(bias));\n    } else {\n      Output(0)->Resize(biasDims);\n      context_.template Copy<T, CUDAContext, CUDAContext>(\n          biasDims[0] * biasDims[1] * biasDims[2],\n          static_cast<T*>(bias),\n          Output(0)->template mutable_data<T>());\n    }\n  } else if (weight_constants.find(param_type) != weight_constants.end()) {\n    int param_id =\n        weight_constants[param_type] + 4 * (input_type == \"recurrent\");\n    cudnnFilterDescriptor_t matrixParamDesc;\n    CUDNN_ENFORCE(cudnnCreateFilterDescriptor(&matrixParamDesc));\n    void* pmatrix;\n    CUDNN_ENFORCE(cudnnGetRNNLinLayerMatrixParams(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        rnnDesc_,\n        layer,\n        xDesc_->descs()[0],\n        wDesc_,\n        Input(1).template data<T>(),\n        param_id, // Forget gate bias for recurrent input\n        matrixParamDesc,\n        &pmatrix));\n    int numDims;\n    std::vector<int> matDims(3);\n    cudnnDataType_t dt;\n    cudnnTensorFormat_t tf;\n\n    CUDNN_ENFORCE(cudnnGetFilterNdDescriptor(\n        matrixParamDesc, 3, &dt, &tf, &numDims, matDims.data()));\n    CAFFE_ENFORCE_EQ(numDims, 3);\n    if (mode == SET_PARAM) {\n      CAFFE_ENFORCE_EQ(matDims[0] * matDims[1] * matDims[2], Input(2).size());\n      context_.template Copy<T, CUDAContext, CUDAContext>(\n          matDims[0] * matDims[1] * matDims[2],\n          Input(2).template data<T>(),\n          static_cast<T*>(pmatrix));\n    } else {\n      Output(0)->Resize(matDims);\n      context_.template Copy<T, CUDAContext, CUDAContext>(\n          matDims[0] * matDims[1] * matDims[2],\n          static_cast<T*>(pmatrix),\n          Output(0)->template mutable_data<T>());\n    }\n  } else {\n    CAFFE_ENFORCE(false, \"Unknown param type:\", param_type);\n  }\n\n  return true;\n}\n\nREGISTER_CUDNN_OPERATOR(Recurrent, RecurrentOp<float>);\nOPERATOR_SCHEMA(Recurrent).NumInputs(4).NumOutputs(5).SetDoc(R\"DOC(\n\nRecurrent wraps the CuDNN R5 RNN implementation. See the CuDNN R5\ndocumentation for more information.\n\nIn general, the implementation takes an input (TxNxD) tensor, the\nhidden state input (NxD), the cell input (NxD), and a weight tensor\n(effectively an opaque blob, where the size and layout is dictated by\nCuDNN).\n\nThe outputs are the output (again, TxNxD), the final hidden/cell\nstates (NxD). These can be reset (at sequence boundaries across\nminibatches) by multiplying by zero.\n\nThe CuDNN arguments (hidden_size, bidirectional, num_layers, rnn_mode,\ninput_mode) are passed directly through to CuDNN.\n\n)DOC\");\nREGISTER_CUDNN_OPERATOR(RecurrentGradient, RecurrentGradientOp<float>);\nOPERATOR_SCHEMA(RecurrentGradient)\n    .NumInputs(7)\n    .NumOutputs(6)\n    .AllowInplace({{4, 5}});\n\nREGISTER_CUDNN_OPERATOR(\n    RecurrentParamSet,\n    RecurrentParamAccessOp<float, SET_PARAM>);\nOPERATOR_SCHEMA(RecurrentParamSet)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .EnforceInplace({{1, 0}})\n    .SetDoc(\"Set individual parameters of a recurrent net.\")\n    .Arg(\"param_type\", R\"DOC(Type of param to be set:\n                  \"input_gate_w\", \"forget_gate_w\", \"cell_w\", \"output_gate_w\"\n                  \"input_gate_b\", \"forget_gate_b\", \"cell_b\", \"output_gate_b\"\n                  )DOC\")\n    .Arg(\"input_type\", \"'recurrent' or 'input'\")\n    .Arg(\"layer\", \"layer index (starting from 0)\")\n    .Input(0, \"input\", R\"DOC(Input blob. Needed for inferring the shapes.\n                        A dummy tensor matching the input shape is ok.)DOC\")\n    .Input(1, \"all_params\", \"Blob holding all the parameters\")\n    .Input(2, \"param\", \"Values for the specified parameter\")\n    .Output(\n        0,\n        \"all_params\",\n        \"Blob holding all the parameters (same as input(1))\");\n\nREGISTER_CUDNN_OPERATOR(\n    RecurrentParamGet,\n    RecurrentParamAccessOp<float, GET_PARAM>);\nOPERATOR_SCHEMA(RecurrentParamGet)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(\"Retrieve individual parameters of a recurrent net op.\")\n    .Arg(\"param_type\", R\"DOC(Type of param to be set:\n                  \"input_gate_w\", \"forget_gate_w\", \"cell_w\", \"output_gate_w\"\n                  \"input_gate_b\", \"forget_gate_b\", \"cell_b\", \"output_gate_b\"\n                  )DOC\")\n    .Arg(\"input_type\", \"'recurrent' or 'input'\")\n    .Arg(\"layer\", \"layer index (starting from 0)\")\n    .Input(0, \"input\", R\"DOC(Input blob. Needed for inferring the shapes.\n                        A dummy tensor matching the input shape is ok.)DOC\")\n    .Input(1, \"all_params\", \"Blob holding all the parameters\")\n    .Output(0, \"param\", \"Blob holding the requested values\");\n\nstruct GetRecurrentGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"RecurrentGradient\",\n        \"\",\n        vector<string>{I(0), // INPUT\n                       I(1), // HIDDEN_INPUT\n                       I(2), // CELL_INPUT\n                       I(3), // WEIGHT\n                       O(3), // RNN_SCRATCH\n                       O(0), // OUTPUT\n                       GO(0)}, // GRAD_OUTPUT\n        // TODO: not currently using these gradients, investigate t16675365\n        //     GO(1), // GRAD_HIDDEN_OUTPUT\n        //     GO(2)}, // GRAD_CELL_OUTPUT\n        vector<string>{\n            GI(0), // GRAD_INPUT\n            GI(1), // GRAD_HIDDEN_INPUT\n            GI(2), // GRAD_CELL_INPUT\n            GI(3), // GRAD_WEIGHT\n            O(4), // DROPOUT_STATES\n            O(3) // RNN_SCRATCH\n        });\n  }\n};\nREGISTER_GRADIENT(Recurrent, GetRecurrentGradient);\n}\n"
  },
  {
    "path": "caffe2/operators/recurrent_op_cudnn.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_RECURRENT_OP_CUDNN_H_\n#define CAFFE2_OPERATORS_RECURRENT_OP_CUDNN_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/cudnn_wrappers.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\nnamespace detail {\n\ntemplate <typename T>\nclass TensorDescriptors {\n public:\n  TensorDescriptors(\n      size_t n,\n      const std::vector<int>& dim,\n      const std::vector<int>& stride);\n  ~TensorDescriptors();\n  const cudnnTensorDescriptor_t* descs() const {\n    return descs_.data();\n  }\n\n private:\n  std::vector<cudnnTensorDescriptor_t> descs_;\n};\n\n} // namespace detail\n\ntemplate <typename T>\nclass RecurrentBaseOp : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  RecurrentBaseOp(const OperatorDef& operator_def, Workspace* ws);\n  virtual ~RecurrentBaseOp();\n\n protected:\n  void initialize(\n      const Tensor<CUDAContext>& input,\n      Tensor<CUDAContext>* dropoutStates = nullptr,\n      // If passed, reshapes to the appropriate size\n      Tensor<CUDAContext>* output = nullptr,\n      Tensor<CUDAContext>* hiddenOutput = nullptr,\n      Tensor<CUDAContext>* cellOutput = nullptr);\n\n  CuDNNWrapper cudnn_wrapper_;\n  cudnnDropoutDescriptor_t dropoutDesc_;\n  cudnnRNNDescriptor_t rnnDesc_;\n  cudnnFilterDescriptor_t wDesc_;\n  cudnnTensorDescriptor_t hxDesc_;\n  cudnnTensorDescriptor_t cxDesc_;\n  cudnnTensorDescriptor_t hyDesc_;\n  cudnnTensorDescriptor_t cyDesc_;\n\n  std::unique_ptr<detail::TensorDescriptors<T>> xDesc_;\n  std::unique_ptr<detail::TensorDescriptors<T>> yDesc_;\n\n  std::vector<TIndex> cachedInputDims_;\n  size_t reserveNbytes_;\n  size_t cudnnWsNbytes_;\n\n private:\n};\n\n#define USE_RECURRENT_BASE_FUNCTIONS          \\\n  USE_OPERATOR_FUNCTIONS(CUDAContext);        \\\n  using RecurrentBaseOp<T>::cudnn_wrapper_;   \\\n  using RecurrentBaseOp<T>::dropoutDesc_;     \\\n  using RecurrentBaseOp<T>::rnnDesc_;         \\\n  using RecurrentBaseOp<T>::wDesc_;           \\\n  using RecurrentBaseOp<T>::hxDesc_;          \\\n  using RecurrentBaseOp<T>::cxDesc_;          \\\n  using RecurrentBaseOp<T>::hyDesc_;          \\\n  using RecurrentBaseOp<T>::cyDesc_;          \\\n  using RecurrentBaseOp<T>::xDesc_;           \\\n  using RecurrentBaseOp<T>::yDesc_;           \\\n  using RecurrentBaseOp<T>::cachedInputDims_; \\\n  using RecurrentBaseOp<T>::reserveNbytes_;   \\\n  using RecurrentBaseOp<T>::cudnnWsNbytes_;   \\\n  using RecurrentBaseOp<T>::initialize;\n\ntemplate <typename T>\nclass RecurrentOp : public RecurrentBaseOp<T> {\n public:\n  USE_RECURRENT_BASE_FUNCTIONS\n  RecurrentOp(const OperatorDef& operator_def, Workspace* ws)\n      : RecurrentBaseOp<T>(operator_def, ws) {}\n\n  bool RunOnDevice() override;\n\n protected:\n  INPUT_TAGS(INPUT, HIDDEN_INPUT, CELL_INPUT, WEIGHT);\n  OUTPUT_TAGS(OUTPUT, HIDDEN_OUTPUT, CELL_OUTPUT, RNN_SCRATCH, DROPOUT_STATES);\n};\n\nenum RecurrentParamOpMode { SET_PARAM, GET_PARAM };\n\ntemplate <typename T, RecurrentParamOpMode mode>\nclass RecurrentParamAccessOp : public RecurrentBaseOp<T> {\n public:\n  USE_RECURRENT_BASE_FUNCTIONS\n  RecurrentParamAccessOp(const OperatorDef& operator_def, Workspace* ws)\n      : RecurrentBaseOp<T>(operator_def, ws) {}\n\n  bool RunOnDevice() override;\n};\n\ntemplate <typename T>\nclass RecurrentGradientOp : public RecurrentBaseOp<T> {\n public:\n  USE_RECURRENT_BASE_FUNCTIONS\n  RecurrentGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : RecurrentBaseOp<T>(operator_def, ws) {}\n\n  bool RunOnDevice() override;\n\n protected:\n  INPUT_TAGS(\n      INPUT,\n      HIDDEN_INPUT,\n      CELL_INPUT,\n      WEIGHT,\n      RNN_SCRATCH,\n      OUTPUT,\n      GRAD_OUTPUT,\n      GRAD_HIDDEN_OUTPUT,\n      GRAD_CELL_OUTPUT);\n  OUTPUT_TAGS(\n      GRAD_INPUT,\n      GRAD_HIDDEN_INPUT,\n      GRAD_CELL_INPUT,\n      GRAD_WEIGHT,\n      DROPOUT_STATES,\n      RNN_SCRATCH_OUT);\n};\n\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_RECURRENT_OP_CUDNN_H_\n"
  },
  {
    "path": "caffe2/operators/reduce_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/reduce_ops.h\"\n\nnamespace caffe2 {\n\n// For a Tensor X of n dimensions (dims[0], ..., dims[ndim-1]), given index\n// is converted to corresponding n-dimensional index, e.g. for X.shape = (2,\n// 3, 4) the linear index 12 maps to 3-dimensional index (1, 0, 0).\nvector<TIndex> ConvertFromInputIndex(TIndex index, vector<TIndex>& dims) {\n  TIndex ndim = dims.size();\n  vector<TIndex> nd_idx(ndim);\n\n  for (TIndex i = ndim - 1; i >= 0 && index > 0; i--) {\n    nd_idx[i] = index % dims[i];\n    index /= dims[i];\n  }\n  return nd_idx;\n}\n\n// For given n-dimensional index (nd_idx[0], ..., nd_idx[dims.size()-1]) and\n// reduction axes, map the n-dimensional index to the corresponding linear\n// index in the reduced tensor.\nTIndex ConvertToOutputIndex(\n    const vector<int>& axes,\n    const vector<TIndex>& nd_idx,\n    vector<TIndex>& dims) {\n  TIndex index = 0;\n  TIndex multiplier = 1;\n  for (TIndex i = dims.size() - 1, j = axes.size() - 1; i >= 0; i--) {\n    if (j >= 0 && axes[j] == i) {\n      j--;\n    } else {\n      index += nd_idx[i] * multiplier;\n      multiplier *= dims[i];\n    }\n  }\n  return index;\n}\n\ntemplate <typename T>\ninline T Add(T x, T y) {\n  return (x + y);\n}\n\ntemplate <typename T, class Context>\nvoid ComputeOp(\n    const T* X_data,\n    const TIndex X_size,\n    vector<TIndex>& dims,\n    T* Y_data,\n    vector<int>& axes,\n    int keepdims,\n    T (*binary_op)(T, T)) {\n  for (TIndex x_idx = 0; x_idx < X_size; x_idx++) {\n    vector<TIndex> nd_idx = ConvertFromInputIndex(x_idx, dims);\n    TIndex y_idx = ConvertToOutputIndex(axes, nd_idx, dims);\n    Y_data[y_idx] = binary_op(Y_data[y_idx], X_data[x_idx]);\n  }\n}\n\ntemplate <typename T, class Context>\nbool ReduceSumOp<T, Context>::Compute(\n    const T* X_data,\n    const TIndex X_size,\n    vector<TIndex>& dims,\n    T* Y_data,\n    const TIndex Y_size,\n    vector<int>& axes,\n    int keepdims) {\n  math::Set<T, Context>(Y_size, 0.f, Y_data, &context_);\n  ComputeOp<T, Context>(X_data, X_size, dims, Y_data, axes, keepdims, Add);\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool ReduceMeanOp<T, Context>::Compute(\n    const T* X_data,\n    const TIndex X_size,\n    vector<TIndex>& dims,\n    T* Y_data,\n    const TIndex Y_size,\n    vector<int>& axes,\n    int keepdims) {\n  math::Set<T, Context>(Y_size, 0.f, Y_data, &context_);\n  ComputeOp<T, Context>(X_data, X_size, dims, Y_data, axes, keepdims, Add);\n  math::Scale(\n      Y_size, static_cast<float>(Y_size) / X_size, Y_data, Y_data, &context_);\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(ReduceSum, ReduceSumOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(ReduceSum)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\n  Computes the sum of the input tensor's element along the provided axes.\n  The resulted tensor has the same rank as the input if keepdims equal 1.\n  If keepdims equal 0, then the resulted tensor have the reduced dimension pruned.\n)DOC\")\n    .Arg(\"axes\", \"A list of integers, along which to reduce.\")\n    .Arg(\n        \"keepdims\",\n        \"Keep the reduced dimension(s) or not, default 1 keeps the reduced dimension(s).\")\n    .Input(0, \"data\", \"An input tensor.\")\n    .Output(0, \"reduced\", \"Reduced output tensor.\");\n\n// TODO: Write gradient for this when needed\nGRADIENT_NOT_IMPLEMENTED_YET(ReduceSum);\n\nREGISTER_CPU_OPERATOR(ReduceMean, ReduceMeanOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(ReduceMean)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\n      Computes the mean of the input tensor's element along the provided axes.\n      The resulted tensor has the same rank as the input if keepdims equal 1.\n      If keepdims equal 0, then the resulted tensor have the reduced dimension pruned.\n    )DOC\")\n    .Arg(\"axes\", \"A list of integers, along which to reduce.\")\n    .Arg(\n        \"keepdims\",\n        \"Keep the reduced dimension(s) or not, default 1 keeps the reduced dimension(s).\")\n    .Input(0, \"data\", \"An input tensor.\")\n    .Output(0, \"reduced\", \"Reduced output tensor.\");\n\n// TODO: Write gradient for this when needed\nGRADIENT_NOT_IMPLEMENTED_YET(ReduceMean);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/reduce_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_REDUCE_OPS_H_\n#define CAFFE2_OPERATORS_REDUCE_OPS_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass ReduceOpBase : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  ReduceOpBase(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    axes_ = OperatorBase::GetRepeatedArgument<int>(\"axes\");\n    keepdims_ = OperatorBase::GetSingleArgument<int>(\"keepdims\", 1);\n  }\n\n  bool RunOnDevice() override {\n    int ndim = Input(0).ndim();\n\n    if (axes_.empty()) {\n      axes_.resize(ndim);\n      std::iota(axes_.begin(), axes_.end(), 0);\n    } else {\n      std::sort(axes_.begin(), axes_.end());\n      CAFFE_ENFORCE(axes_.front() >= 0, \"Axes ids must be non-negative.\");\n      CAFFE_ENFORCE(\n          axes_.back() < ndim,\n          \"Axes ids must be smaller than the dimensions of input.\");\n    }\n\n    auto& X = Input(0);\n    auto* Y = Output(0);\n\n    vector<TIndex> y_dims = X.dims();\n    TIndex Y_size = X.size();\n    for (TIndex id = axes_.size() - 1; id >= 0; id--) {\n      TIndex reduced_axis = axes_[id];\n      Y_size /= y_dims[reduced_axis];\n      if (keepdims_) {\n        y_dims[reduced_axis] = 1;\n      } else {\n        y_dims.erase(y_dims.begin() + reduced_axis);\n      }\n    }\n    Y->Resize(y_dims);\n\n    return this->Compute(\n        X.template data<T>(),\n        X.size(),\n        const_cast<vector<TIndex>&>(X.dims()),\n        Y->template mutable_data<T>(),\n        Y_size,\n        axes_,\n        keepdims_);\n  }\n\n protected:\n  virtual bool Compute(\n      const T* X_data,\n      const TIndex X_size,\n      vector<TIndex>& dims,\n      T* Y_data,\n      const TIndex Y_size,\n      vector<int>& axes,\n      int keepdims) = 0;\n\n private:\n  std::vector<int> axes_;\n  int keepdims_;\n};\n\ntemplate <typename T, class Context>\nclass ReduceSumOp : public ReduceOpBase<T, Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  ReduceSumOp(const OperatorDef& operator_def, Workspace* ws)\n      : ReduceOpBase<T, Context>(operator_def, ws) {}\n\n protected:\n  bool Compute(\n      const T* X_data,\n      const TIndex X_size,\n      vector<TIndex>& dims,\n      T* Y_data,\n      const TIndex Y_size,\n      vector<int>& axes,\n      int keepdims) override;\n};\n\ntemplate <typename T, class Context>\nclass ReduceMeanOp : public ReduceOpBase<T, Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  ReduceMeanOp(const OperatorDef& operator_def, Workspace* ws)\n      : ReduceOpBase<T, Context>(operator_def, ws) {}\n\n protected:\n  bool Compute(\n      const T* X_data,\n      const TIndex X_size,\n      vector<TIndex>& dims,\n      T* Y_data,\n      const TIndex Y_size,\n      vector<int>& axes,\n      int keepdims) override;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_REDUCE_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/reducer_functors.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#ifndef CAFFE2_OPERATORS_RECUDER_FUNCTORS_H_\n#define CAFFE2_OPERATORS_RECUDER_FUNCTORS_H_\n\n#include <array>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\n////////////////////////////////////////////////////////////////////////////////\n// Range reducers: can leverage that input segment is continuous and provide\n// special implementation\n////////////////////////////////////////////////////////////////////////////////\n\n// Put forward and backward in the same template?\ntemplate <typename T, class Context>\nclass SumRangeReducer;\ntemplate <typename T, class Context>\nclass SumRangeReducerGradient;\n\ntemplate <typename T>\nclass SumRangeReducer<T, CPUContext> {\n public:\n  void operator()(\n      const TIndex block_size,\n      const TIndex blocks,\n      const T* in,\n      T* out,\n      CPUContext* /*context*/) {\n    // do we need to go through wrapper in math.h?\n    EigenVectorMap<T> out_vec(out, block_size);\n    out_vec = ConstEigenMatrixMap<T>(in, block_size, blocks).rowwise().sum();\n  }\n};\n\ntemplate <typename T, class Context>\nclass SumRangeReducerGradient {\n public:\n  void operator()(\n      const TIndex block_size,\n      const TIndex blocks,\n      const T* segment_grad,\n      T* data_grad,\n      const T* /*data_in*/, // unused\n      const T* /*data_out*/, // unused\n      Context* context) {\n    // do we have some op that does it smartly with minimum number of memcpy?\n    for (TIndex i = 0; i < blocks; ++i) {\n      context->template Copy<T, Context, Context>(\n          block_size, segment_grad, data_grad + block_size * i);\n    }\n  }\n};\n\nstruct SumRangeReducerDef {\n  template <typename T, class Context>\n  using Reducer = SumRangeReducer<T, Context>;\n  template <typename T, class Context>\n  using ReducerGradient = SumRangeReducerGradient<T, Context>;\n  static constexpr const char* name = \"Sum\";\n  static constexpr const char* doc =\n      \"Summation is done element-wise across slices of the input tensor and \"\n      \"doesn't change the shape of the individual blocks.\";\n};\n\n// Put forward and backward in the same template?\ntemplate <typename T, class Context>\nclass LogSumExpRangeReducer;\ntemplate <typename T, class Context>\nclass LogSumExpRangeReducerGradient;\n\ntemplate <typename T>\nclass LogSumExpRangeReducer<T, CPUContext> {\n public:\n  void operator()(\n      const TIndex block_size,\n      const TIndex blocks,\n      const T* in,\n      T* out,\n      CPUContext* /*context*/) {\n    for (int j = 0; j < block_size; ++j) {\n      T max_value = std::numeric_limits<T>::lowest();\n      for (int i = 0; i < blocks; ++i) {\n        max_value = std::max(max_value, in[i * block_size + j]);\n      }\n      T scaled_exp_sum = 0;\n      for (int i = 0; i < blocks; ++i) {\n        scaled_exp_sum += std::exp(in[i * block_size + j] - max_value);\n      }\n      *(out++) = std::log(scaled_exp_sum) + max_value;\n    }\n  }\n  T r{1};\n};\n\ntemplate <typename T, class Context>\nclass LogSumExpRangeReducerGradient {\n public:\n  void operator()(\n      const TIndex block_size,\n      const TIndex blocks,\n      const T* segment_grad, // GO\n      T* data_grad, // GI\n      const T* data_in, // I\n      const T* data_out, // O\n      Context* /*context*/) {\n    for (int j = 0; j < block_size; ++j) {\n      const T out_grad = *(segment_grad++);\n      const T offset = *(data_out++);\n      for (int i = 0; i < blocks; ++i) {\n        auto idx = i * block_size + j;\n        data_grad[idx] = out_grad * std::exp(data_in[idx] - offset);\n      }\n    }\n  }\n};\n\nstruct LogSumExpRangeReducerDef {\n  template <typename T, class Context>\n  using Reducer = LogSumExpRangeReducer<T, Context>;\n  template <typename T, class Context>\n  using ReducerGradient = LogSumExpRangeReducerGradient<T, Context>;\n  static constexpr const char* name = \"LogSumExp\";\n  static constexpr const char* doc =\n      \"LogSumExp computes the element-wise log of the sum of exponentials of \"\n      \"input slices. Operation doesn't change the shape of individual blocks.\";\n};\n\ntemplate <typename T, class Context>\nclass LogMeanExpRangeReducer;\ntemplate <typename T, class Context>\nclass LogMeanExpRangeReducerGradient;\n\ntemplate <typename T>\nclass LogMeanExpRangeReducer<T, CPUContext> {\n public:\n  void operator()(\n      const TIndex block_size,\n      const TIndex blocks,\n      const T* in,\n      T* out,\n      CPUContext* /*context*/) {\n    for (int j = 0; j < block_size; ++j) {\n      T max_value = std::numeric_limits<T>::lowest();\n      for (int i = 0; i < blocks; ++i) {\n        max_value = std::max(max_value, in[i * block_size + j]);\n      }\n      T scaled_exp_sum = 0;\n      for (int i = 0; i < blocks; ++i) {\n        scaled_exp_sum += std::exp(in[i * block_size + j] - max_value);\n      }\n      scaled_exp_sum /= blocks;\n      *(out++) = std::log(scaled_exp_sum) + max_value;\n    }\n  }\n};\n\ntemplate <typename T, class Context>\nclass LogMeanExpRangeReducerGradient {\n public:\n  void operator()(\n      const TIndex block_size,\n      const TIndex blocks,\n      const T* segment_grad, // GO\n      T* data_grad, // GI\n      const T* data_in, // I\n      const T* data_out, // O\n      Context* /*context*/) {\n    for (int j = 0; j < block_size; ++j) {\n      const T out_grad = *(segment_grad++);\n      const T offset = *(data_out++);\n      for (int i = 0; i < blocks; ++i) {\n        auto idx = i * block_size + j;\n        data_grad[idx] = out_grad * std::exp(data_in[idx] - offset) / blocks;\n      }\n    }\n  }\n};\n\nstruct LogMeanExpRangeReducerDef {\n  template <typename T, class Context>\n  using Reducer = LogMeanExpRangeReducer<T, Context>;\n  template <typename T, class Context>\n  using ReducerGradient = LogMeanExpRangeReducerGradient<T, Context>;\n  static constexpr const char* name = \"LogMeanExp\";\n  static constexpr const char* doc =\n      \"LogMeanExp computes the element-wise log of the mean of exponentials of \"\n      \"input slices. Operation doesn't change the shape of individual blocks.\";\n};\n\ntemplate <typename T, class Context>\nclass MeanRangeReducer;\ntemplate <typename T, class Context>\nclass MeanRangeReducerGradient;\n\ntemplate <typename T>\nclass MeanRangeReducer<T, CPUContext> {\n public:\n  void operator()(\n      const TIndex block_size,\n      const TIndex blocks,\n      const T* in,\n      T* out,\n      CPUContext* /*context*/) {\n    for (int j = 0; j < block_size; ++j) {\n      T avg_value = 0;\n      for (int i = 0; i < blocks; ++i) {\n        avg_value += in[i * block_size + j] / blocks;\n      }\n      *(out++) = avg_value;\n    }\n  }\n};\n\ntemplate <typename T, class Context>\nclass MeanRangeReducerGradient {\n public:\n  void operator()(\n      const TIndex block_size,\n      const TIndex blocks,\n      const T* segment_grad, // GO\n      T* data_grad, // GI\n      const T* /*data_in*/, // I\n      const T* /*data_out*/, // O\n      Context* /*context*/) {\n    const auto in_grad = 1.0 / blocks;\n    for (int j = 0; j < block_size; ++j) {\n      const T out_grad = *(segment_grad++);\n      for (int i = 0; i < blocks; ++i) {\n        auto idx = i * block_size + j;\n        data_grad[idx] = out_grad * in_grad;\n      }\n    }\n  }\n};\n\nstruct MeanRangeReducerDef {\n  template <typename T, class Context>\n  using Reducer = MeanRangeReducer<T, Context>;\n  template <typename T, class Context>\n  using ReducerGradient = MeanRangeReducerGradient<T, Context>;\n  static constexpr const char* name = \"Mean\";\n  static constexpr const char* doc =\n      \"Mean computation is done element-wise, so that each element of the \"\n      \"output slice corresponds to the average value of the respective \"\n      \"elements in the input slices. Operation doesn't change the shape of \"\n      \"individual blocks.\";\n};\n\ntemplate <typename T, class Context>\nclass MaxRangeReducer;\ntemplate <typename T, class Context>\nclass MaxRangeReducerGradient;\n\ntemplate <typename T>\nclass MaxRangeReducer<T, CPUContext> {\n public:\n  void operator()(\n      const TIndex block_size,\n      const TIndex blocks,\n      const T* in,\n      T* out,\n      CPUContext* /*context*/) {\n    for (int j = 0; j < block_size; ++j) {\n      T max_value = std::numeric_limits<T>::lowest();\n      for (int i = 0; i < blocks; ++i) {\n        max_value = std::max(max_value, in[i * block_size + j]);\n      }\n      *(out++) = max_value;\n    }\n  }\n};\n\ntemplate <typename T, class Context>\nclass MaxRangeReducerGradient {\n public:\n  void operator()(\n      const TIndex block_size,\n      const TIndex blocks,\n      const T* segment_grad, // GO\n      T* data_grad, // GI\n      const T* data_in, // I\n      const T* data_out, // O\n      Context* /*context*/) {\n    std::memset(\n        static_cast<void*>(data_grad), 0, blocks * block_size * sizeof(T));\n    for (int j = 0; j < block_size; ++j) {\n      const T out_grad = *(segment_grad++);\n      const T out = data_out[j];\n      for (int i = 0; i < blocks; ++i) {\n        auto idx = i * block_size + j;\n        if (out == data_in[idx]) {\n          data_grad[idx] = out_grad;\n        }\n      }\n    }\n  }\n};\n\nstruct MaxRangeReducerDef {\n  template <typename T, class Context>\n  using Reducer = MaxRangeReducer<T, Context>;\n  template <typename T, class Context>\n  using ReducerGradient = MaxRangeReducerGradient<T, Context>;\n  static constexpr const char* name = \"Max\";\n  static constexpr const char* doc =\n      \"Max computation is done element-wise, so that each element of the \"\n      \"output slice corresponds to the max value of the respective \"\n      \"elements in the input slices. Operation doesn't change the shape of \"\n      \"individual blocks. This implementation imitates torch nn.Max operator. \"\n      \"If the maximum value occurs more than once, the operator will return \"\n      \"the first occurence of value. When computing the gradient using the \"\n      \"backward propagation, the gradient input corresponding to the first \"\n      \"occurence of the maximum value will be used.\";\n};\n\n////////////////////////////////////////////////////////////////////////////////\n// Incremental reducers: consume elements one by one\n////////////////////////////////////////////////////////////////////////////////\n\n// Base implementation, everything can be overwritten\nclass BaseReducer {\n public:\n  static constexpr int kInputCount = 1;\n\n  struct Meta {\n    TIndex block_size;\n    vector<TIndex> block_shape;\n    bool first_dim;\n\n    explicit Meta(bool first = true) : first_dim(first) {}\n\n    void computeMeta(const std::vector<TIndex>& dims, int skip_dims) {\n      first_dim ? block_shape.assign(dims.begin() + skip_dims, dims.end())\n                : block_shape.assign(dims.begin(), dims.end() - skip_dims);\n      block_size = first_dim ? size_from_dim_(skip_dims, dims)\n                             : size_from_dim_(dims.size() - skip_dims, dims);\n    }\n\n    void\n    observeInput(int input, const Tensor<CPUContext>& value, int skip_dims) {\n      DCHECK_EQ(0, input);\n      auto& dims = value.dims();\n      computeMeta(dims, skip_dims);\n    }\n\n    void appendOutputShape(vector<TIndex>* output_shape) {\n      output_shape->insert(\n          output_shape->end(), block_shape.begin(), block_shape.end());\n    }\n\n    vector<TIndex> getOutputShape(const TensorShape& in, int skip_dims) {\n      vector<TIndex> dims(in.dims().begin(), in.dims().end());\n      computeMeta(dims, skip_dims);\n      return block_shape;\n    }\n  };\n\n  template <int FixedSize>\n  void finish(const Meta& /*meta*/, CPUContext* /*context*/) {}\n};\n\nclass BaseReducerGradient {\n public:\n  // which of the original inputs are required for gradient computation\n  static constexpr std::array<int, 0> originalInputs() {\n    return std::array<int, 0>();\n  }\n\n  static constexpr bool computeLength() {\n    return false;\n  }\n\n  static int numAuxInputsWithGrads(const OperatorDef& /*def*/) {\n    return 0;\n  }\n\n  static bool requiresDataInput(const OperatorDef& /*def*/) {\n    return false;\n  }\n\n  // True if the backward op requires the output of the forward op.\n  static bool requiresForwardOutput() {\n    return false;\n  }\n\n  struct Meta {\n    TIndex block_size;\n    vector<TIndex> block_shape;\n    bool first_dim;\n\n    Meta(\n        const Tensor<CPUContext>& out_grad,\n        int skip_dims,\n        bool first_dim = true)\n        : first_dim(first_dim) {\n      auto& dims = out_grad.dims();\n      first_dim ? block_shape.assign(dims.begin() + skip_dims, dims.end())\n                : block_shape.assign(dims.begin(), dims.end() - skip_dims);\n      block_size = first_dim\n          ? out_grad.size_from_dim(skip_dims)\n          : out_grad.size_from_dim(out_grad.ndim() - skip_dims);\n    }\n\n    void observeOriginalInput(\n        int /*original_input*/,\n        const Tensor<CPUContext>& /*value*/,\n        Tensor<CPUContext>* /*input_grad*/, // optional grad to populate\n        int /*skip_dims*/) {}\n\n    void appendGradShape(vector<TIndex>* output_shape) {\n      output_shape->insert(\n          output_shape->end(), block_shape.begin(), block_shape.end());\n    }\n  };\n};\n\n// Put forward and backward in the same template?\ntemplate <typename T, class Context>\nclass SumReducer;\ntemplate <typename T, class Context>\nclass SumReducerGradient;\n\ntemplate <typename T>\nclass SumReducer<T, CPUContext> : public BaseReducer {\n public:\n  using FixedDispatch = FixedValues<1>;\n\n  SumReducer(const Meta& meta, T* out, CPUContext* /*context*/)\n      : current_size_(0), out_(out) {\n    // add a wrapper in Context for it\n    if (meta.first_dim) {\n      memset(out, 0, sizeof(T) * meta.block_size);\n    }\n  }\n  template <int FixedSize>\n  void process(\n      const Meta& meta,\n      const T* in,\n      TIndex /*offset*/,\n      CPUContext* context) {\n    if (meta.first_dim) {\n      math::AxpyFixedSize<T, CPUContext, FixedSize>(\n          meta.block_size, 1, in, out_, context);\n    } else {\n      math::Sum<T, CPUContext>(\n          meta.block_size, in, out_ + current_size_++, context);\n    }\n  }\n\n private:\n  int current_size_;\n  T* out_;\n};\n\ntemplate <typename T, class Context>\nclass SumReducerGradient : public BaseReducerGradient {\n public:\n  using FixedDispatch = FixedValues<1>;\n\n  SumReducerGradient(\n      const Meta& /*meta*/,\n      const T* s_grad,\n      CPUContext* /*context*/)\n      : s_grad_(s_grad) {}\n\n  template <int FixedSize>\n  void fillGrad(\n      const Meta& meta,\n      T* data_grad,\n      TIndex offset,\n      Context* context,\n      const int length) {\n    if (FixedSize == 1) { // static if\n      *data_grad = *s_grad_;\n    } else if (meta.first_dim) {\n      context->template Copy<T, Context, Context>(\n          meta.block_size, s_grad_, data_grad);\n    } else {\n      math::Set<T, Context>(length, s_grad_[offset], data_grad, context);\n    }\n  }\n\n private:\n  const T* s_grad_;\n};\n\nstruct SumReducerDef {\n  template <typename T, class Context>\n  using Reducer = SumReducer<T, Context>;\n  template <typename T, class Context>\n  using ReducerGradient = SumReducerGradient<T, Context>;\n  static constexpr const char* name = \"Sum\";\n  static constexpr const char* doc =\n      \"Summation is done element-wise across slices of the input tensor and \"\n      \"doesn't change the shape of the individual blocks.\";\n  static void PopulateSchema(OpSchema& /*schema*/) {}\n};\n\n// Put forward and backward in the same template?\ntemplate <typename T, class Context>\nclass WeightedSumReducer;\ntemplate <typename T, class Context>\nclass WeightedSumReducerGradient;\n\ntemplate <typename T>\nclass WeightedSumReducer<T, CPUContext> : public BaseReducer {\n public:\n  static constexpr int kInputCount = 2;\n\n  using FixedDispatch = FixedValues<1>;\n\n  struct Meta : BaseReducer::Meta {\n    const T* scalars;\n\n    bool first_dim;\n\n    explicit Meta(bool first = true) : first_dim(first) {}\n\n    void\n    observeInput(int input, const Tensor<CPUContext>& value, int skip_dims) {\n      if (input == 1) {\n        CAFFE_ENFORCE_EQ(\n            skip_dims, value.ndim(), \"SCALARS mustn't have extra dimensions\");\n        scalars = value.data<T>();\n        return;\n      }\n      BaseReducer::Meta::observeInput(input, value, skip_dims);\n    }\n  };\n\n  WeightedSumReducer(const Meta& meta, T* out, CPUContext* /*context*/)\n      : out_(out) {\n    // do we have a wrapper for it?\n    memset(out, 0, sizeof(T) * meta.block_size);\n  }\n  template <int FixedSize>\n  void\n  process(const Meta& meta, const T* in, TIndex offset, CPUContext* context) {\n    CAFFE_ENFORCE(\n        meta.first_dim,\n        \"WeightedSumReducer implemented only for \"\n        \"front dimensions reduction\");\n    math::AxpyFixedSize<T, CPUContext, FixedSize>(\n        meta.block_size, meta.scalars[offset], in, out_, context);\n  }\n\n private:\n  T* out_;\n};\n\ntemplate <typename T, class Context>\nclass WeightedSumReducerGradient : public BaseReducerGradient {\n public:\n  // which of the original inputs are required for gradient computation\n  static constexpr std::array<int, 1> originalInputs() {\n    return {{1}};\n  }\n\n  static int numAuxInputsWithGrads(const OperatorDef& def) {\n    return GetFlagArgument(def, \"grad_on_weights\");\n  }\n\n  static bool requiresDataInput(const OperatorDef& def) {\n    return numAuxInputsWithGrads(def) > 0;\n  }\n\n  using FixedDispatch = FixedValues<1>;\n\n  struct Meta : public BaseReducerGradient::Meta {\n    const T* scalars;\n    T* scalars_grad;\n\n    using BaseReducerGradient::Meta::Meta;\n\n    void observeOriginalInput(\n        int original_input,\n        const Tensor<CPUContext>& value,\n        Tensor<CPUContext>* input_grad, // optional grad to populate\n        int /*skip_dims*/) {\n      CAFFE_ENFORCE_EQ(1, original_input);\n      scalars = value.data<T>();\n      if (input_grad) {\n        input_grad->ResizeLike(value);\n        scalars_grad = input_grad->mutable_data<T>();\n      }\n    }\n  };\n\n  WeightedSumReducerGradient(\n      const Meta& /*meta*/,\n      const T* s_grad,\n      CPUContext* /*context*/)\n      : s_grad_(s_grad) {}\n\n  template <int FixedSize>\n  void fillGrad(\n      const Meta& meta,\n      T* data_grad,\n      TIndex offset,\n      Context* context,\n      const int /*length*/) {\n    math::ScaleFixedSize<T, CPUContext, FixedSize>(\n        meta.block_size, meta.scalars[offset], s_grad_, data_grad, context);\n  }\n\n  // Special version which is called with the main input too, used only if\n  // additional input grad is requested\n  template <int FixedSize>\n  void fillGradWithMainInput(\n      const Meta& meta,\n      const T* data,\n      T* data_grad,\n      TIndex offset,\n      Context* context,\n      const int /*length*/) {\n    math::ScaleFixedSize<T, CPUContext, FixedSize>(\n        meta.block_size, meta.scalars[offset], s_grad_, data_grad, context);\n    math::Dot(\n        meta.block_size, s_grad_, data, meta.scalars_grad + offset, context);\n  }\n\n private:\n  const T* s_grad_;\n};\n\nstruct WeightedSumReducerDef {\n  template <typename T, class Context>\n  using Reducer = WeightedSumReducer<T, Context>;\n  template <typename T, class Context>\n  using ReducerGradient = WeightedSumReducerGradient<T, Context>;\n  static constexpr const char* name = \"WeightedSum\";\n  static constexpr const char* doc =\n      \"Input slices are first scaled by SCALARS and then summed element-wise. \"\n      \"It doesn't change the shape of the individual blocks.\";\n  static void PopulateSchema(OpSchema& schema) {\n    schema.Input(0, \"DATA\", \"Input tensor for the summation\");\n    schema.Input(\n        1,\n        \"SCALARS\",\n        \"Scalar multipliers for the input slices. Must be a vector with the \"\n        \"length matching the number of slices\");\n    schema.Arg(\n        \"grad_on_weights\",\n        \"Produce also gradient for `weights`. For now it's only supported in \"\n        \"`Lengths`-based operators\");\n  }\n};\n\ntemplate <typename T, class Context>\nclass MeanReducer;\ntemplate <typename T, class Context>\nclass MeanReducerGradient;\n\ntemplate <typename T>\nclass MeanReducer<T, CPUContext> : public BaseReducer {\n public:\n  using FixedDispatch = FixedValues<1>;\n\n  MeanReducer(const Meta& meta, T* out, CPUContext* /*context*/)\n      : out_(out), current_size_(0) {\n    if (meta.first_dim) {\n      memset(out, 0, sizeof(T) * meta.block_size);\n    }\n  }\n\n  template <int FixedSize>\n  void process(\n      const Meta& meta,\n      const T* in,\n      TIndex /*offset*/,\n      CPUContext* context) {\n    if (meta.first_dim) {\n      math::AxpyFixedSize<T, CPUContext, FixedSize>(\n          meta.block_size, 1, in, out_, context);\n    } else {\n      math::Sum<T, CPUContext>(\n          meta.block_size, in, out_ + current_size_, context);\n    }\n    current_size_++;\n  }\n\n  template <int FixedSize>\n  void finish(const Meta& meta, CPUContext* context) {\n    if (meta.first_dim) {\n      if (current_size_ > 0) {\n        math::ScaleFixedSize<T, CPUContext, FixedSize>(\n            meta.block_size, 1.0 / current_size_, out_, out_, context);\n      }\n    } else {\n      math::ScaleFixedSize<T, CPUContext, FixedSize>(\n          current_size_, 1.0 / meta.block_size, out_, out_, context);\n    }\n  }\n\n private:\n  T* out_;\n  int current_size_;\n};\n\ntemplate <typename T, class Context>\nclass MeanReducerGradient : public BaseReducerGradient {\n public:\n  static constexpr bool computeLength() {\n    return true;\n  }\n\n  using FixedDispatch = FixedValues<1>;\n\n  MeanReducerGradient(\n      const Meta& /*meta*/,\n      const T* s_grad,\n      CPUContext* /*context*/)\n      : s_grad_(s_grad) {}\n\n  template <int FixedSize>\n  void fillGrad(\n      const Meta& meta,\n      T* data_grad,\n      TIndex offset,\n      Context* context,\n      const int length) {\n    CAFFE_ENFORCE_GT(length, 0, \"Segment length must be > 0\");\n    if (meta.first_dim) {\n      math::ScaleFixedSize<T, CPUContext, FixedSize>(\n          meta.block_size, 1.0 / length, s_grad_, data_grad, context);\n    } else {\n      math::Set<T, CPUContext>(\n          length, s_grad_[offset] * 1.0f / length, data_grad, context);\n    }\n  }\n\n private:\n  const T* s_grad_;\n};\n\nstruct MeanReducerDef {\n  template <typename T, class Context>\n  using Reducer = MeanReducer<T, Context>;\n  template <typename T, class Context>\n  using ReducerGradient = MeanReducerGradient<T, Context>;\n  static constexpr const char* name = \"Mean\";\n  static constexpr const char* doc =\n      \"Mean computes the element-wise mean of the input slices. \"\n      \"Operation doesn't change the shape of the individual blocks.\";\n  static void PopulateSchema(OpSchema& /*schema*/) {}\n};\n\ntemplate <typename T, class Context>\nclass MaxReducer;\ntemplate <typename T, class Context>\nclass MaxReducerGradient;\n\ntemplate <typename T>\nclass MaxReducer<T, CPUContext> : public BaseReducer {\n public:\n  using FixedDispatch = FixedValues<1>;\n\n  MaxReducer(const Meta& meta, T* out, CPUContext* /*context*/)\n      : out_(out), current_size_(0) {}\n\n  template <int FixedSize>\n  void process(\n      const Meta& meta,\n      const T* in,\n      TIndex /*offset*/,\n      CPUContext* context) {\n    CAFFE_ENFORCE(\n        meta.first_dim,\n        \"MaxReducer implemented only for front dimensions reduction\");\n    if (current_size_ > 0) {\n      EigenVectorMap<T> output_vec(out_, meta.block_size);\n      output_vec =\n          output_vec.cwiseMax(ConstEigenVectorMap<T>(in, meta.block_size));\n    } else {\n      memcpy(out_, in, sizeof(T) * meta.block_size);\n    }\n    ++current_size_;\n  }\n\n private:\n  T* out_;\n  int current_size_;\n};\n\ntemplate <typename T, class Context>\nclass MaxReducerGradient : public BaseReducerGradient {\n public:\n  static bool requiresDataInput(const OperatorDef& /*def*/) {\n    return true;\n  }\n\n  static bool requiresForwardOutput() {\n    return true;\n  }\n\n  using FixedDispatch = FixedValues<1>;\n\n  MaxReducerGradient(\n      const Meta& /*meta*/,\n      const T* s_grad,\n      CPUContext* /*context*/)\n      : s_grad_(s_grad) {}\n\n  template <int FixedSize>\n  void fillGradWithMainInputAndForwardOutput(\n      const Meta& meta,\n      const T* data,\n      T* data_grad,\n      const T* forward_output,\n      TIndex /*offset*/,\n      Context* /*context*/,\n      const int /*length*/) {\n    for (TIndex i = 0; i < meta.block_size; ++i) {\n      data_grad[i] = data[i] == forward_output[i] ? s_grad_[i] : 0;\n    }\n  }\n\n private:\n  const T* s_grad_;\n};\n\nstruct MaxReducerDef {\n  template <typename T, class Context>\n  using Reducer = MaxReducer<T, Context>;\n  template <typename T, class Context>\n  using ReducerGradient = MaxReducerGradient<T, Context>;\n  static constexpr const char* name = \"Max\";\n  static constexpr const char* doc =\n      \"Max computes the element-wise max of the input slices. \"\n      \"Operation doesn't change the shape of the individual blocks.\";\n  static void PopulateSchema(OpSchema& /*schema*/) {}\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_RECUDER_FUNCTORS_H_\n"
  },
  {
    "path": "caffe2/operators/reduction_front_back_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/reduction_front_back_ops.h\"\n#include \"caffe2/core/operator_gradient.h\"\n\nnamespace caffe2 {\n\n/***\n  Sum Ops\n***/\n\n// ReduceFrontSum: columnwise sum\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsOp<CPUContext, true, false>::Compute(\n    int rows,\n    int cols,\n    const T* in_data,\n    const int32_t* lengths_data,\n    T* out_data) {\n  for (int j = 0; j < cols; j++) {\n    T sum = in_data[j];\n    int length = lengths_data == nullptr ? rows : lengths_data[j];\n    for (int i = 1; i < length; i++) {\n      sum += in_data[i * cols + j];\n    }\n    out_data[j] = sum;\n  }\n}\n\n// ReduceBackSum: rowwise sum\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsOp<CPUContext, false, false>::Compute(\n    int rows,\n    int cols,\n    const T* in_data,\n    const int32_t* lengths_data,\n    T* out_data) {\n  for (int i = 0; i < rows; i++) {\n    int offset = i * cols;\n    T sum = in_data[offset];\n    int length = lengths_data == nullptr ? cols : lengths_data[i];\n    for (int j = 1; j < length; j++) {\n      sum += in_data[offset + j];\n    }\n    out_data[i] = sum;\n  }\n}\n\n// ReduceFrontSumGradient\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsGradientOp<CPUContext, true, false>::Compute(\n    int rows,\n    int cols,\n    const T* dYdata,\n    const int* lengths_data,\n    T* dXdata) {\n  for (int i = 0; i < rows * cols; i++) {\n    int row = i / cols;\n    int col = i % cols;\n    if (lengths_data == nullptr || row < lengths_data[col]) {\n      dXdata[i] = dYdata[col];\n    } else {\n      dXdata[i] = 0;\n    }\n  }\n}\n\n// ReduceBackSumGradient\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsGradientOp<CPUContext, false, false>::Compute(\n    int rows,\n    int cols,\n    const T* dYdata,\n    const int* lengths_data,\n    T* dXdata) {\n  for (int i = 0; i < rows * cols; i++) {\n    int row = i / cols;\n    int col = i % cols;\n    if (lengths_data == nullptr || col < lengths_data[row]) {\n      dXdata[i] = dYdata[row];\n    } else {\n      dXdata[i] = 0;\n    }\n  }\n}\n\nREGISTER_CPU_OPERATOR(ReduceFrontSum, SumReduceDimsOp<CPUContext, true, false>);\nREGISTER_CPU_OPERATOR(\n    ReduceFrontSumGradient,\n    SumReduceDimsGradientOp<CPUContext, true, false>);\n\nclass GetReduceFrontSumGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> grad_in = {GO(0), I(0)};\n    if (def_.input_size() == 2) {\n      grad_in.push_back(I(1));\n    }\n    return SingleGradientDef(\n        \"ReduceFrontSumGradient\", \"\", grad_in, vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(ReduceFrontSum, GetReduceFrontSumGradient);\n\nREGISTER_CPU_OPERATOR(ReduceBackSum, SumReduceDimsOp<CPUContext, false, false>);\nREGISTER_CPU_OPERATOR(\n    ReduceBackSumGradient,\n    SumReduceDimsGradientOp<CPUContext, false, false>);\n\nclass GetReduceBackSumGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> grad_in = {GO(0), I(0)};\n    if (def_.input_size() == 2) {\n      grad_in.push_back(I(1));\n    }\n    return SingleGradientDef(\n        \"ReduceBackSumGradient\", \"\", grad_in, vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(ReduceBackSum, GetReduceBackSumGradient);\n\n#define REDUCTION_OP_SHAPE_INFERENCE(is_front_reducer)                      \\\n  CAFFE_ENFORCE_LE(1, in.size());                                           \\\n  CAFFE_ENFORCE_GE(2, in.size());                                           \\\n  ArgumentHelper helper(def);                                               \\\n  int num_reduce_dims = helper.GetSingleArgument<int>(\"num_reduce_dim\", 1); \\\n  int start_index = is_front_reducer ? num_reduce_dims : 0;                 \\\n  int end_index = is_front_reducer ? in[0].dims_size()                      \\\n                                   : in[0].dims_size() - num_reduce_dims;   \\\n  vector<int> output_shape;                                                 \\\n  for (int i = start_index; i < end_index; ++i) {                           \\\n    output_shape.push_back(in[0].dims(i));                                  \\\n  }                                                                         \\\n  return vector<TensorShape>{                                               \\\n      CreateTensorShape(output_shape, in[0].data_type())};\n\nOPERATOR_SCHEMA(ReduceFrontSum)\n    .NumInputs(1, 2)\n    .NumOutputs(1)\n    .Arg(\"num_reduce_dims\", \"Number of dimensions to reduce.\")\n    .SetDoc(R\"DOC(\nReduces the input tensor along the first dimension of the input\ntensor by applying 'Sum'.  When lengths is given, sum is only computed\nwith subsets of elements correspondingly.\n)DOC\")\n    .Input(0, \"data_in\", \"(T<D1..., Dn>) Input data.\")\n    .Input(\n        1,\n        \"lengths\",\n        \"Num of elements in each sample, should have size D2 x D3 x ... x Dn.\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      REDUCTION_OP_SHAPE_INFERENCE(true)\n    });\nOPERATOR_SCHEMA(ReduceFrontSumGradient).NumInputs(2, 3).NumOutputs(1);\n\nOPERATOR_SCHEMA(ReduceBackSum)\n    .NumInputs(1, 2)\n    .NumOutputs(1)\n    .Arg(\"num_reduce_dims\", \"Number of dimensions to reduce.\")\n    .SetDoc(R\"DOC(\nReduces the input tensor along the last dimension of the\ninput tensor by applying 'Sum'.  When lengths is given, sum is only computed\nwith subsets of elements correspondingly.\n)DOC\")\n    .Input(0, \"data_in\", \"(T<D1..., Dn>) Input data.\")\n    .Input(\n        1,\n        \"lengths\",\n        \"Num of elements in each sample, should have size D1 x D2 x ... x D(n-1).\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      REDUCTION_OP_SHAPE_INFERENCE(false)\n    });\nOPERATOR_SCHEMA(ReduceBackSumGradient).NumInputs(2, 3).NumOutputs(1);\n\n/***\n  Mean Ops\n***/\n\n// ReduceFrontMean: columnwise mean\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsOp<CPUContext, true, true>::Compute(\n    int rows,\n    int cols,\n    const T* in_data,\n    const int32_t* lengths_data,\n    T* out_data) {\n  for (int j = 0; j < cols; j++) {\n    T sum = in_data[j];\n    int length = lengths_data == nullptr ? rows : lengths_data[j];\n    for (int i = 1; i < length; i++) {\n      sum += in_data[i * cols + j];\n    }\n    out_data[j] = sum / length;\n  }\n}\n\n// ReduceBackMean: rowwise mean\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsOp<CPUContext, false, true>::Compute(\n    int rows,\n    int cols,\n    const T* in_data,\n    const int32_t* lengths_data,\n    T* out_data) {\n  for (int i = 0; i < rows; i++) {\n    int offset = i * cols;\n    T sum = in_data[offset];\n    int length = lengths_data == nullptr ? cols : lengths_data[i];\n    for (int j = 1; j < length; j++) {\n      sum += in_data[offset + j];\n    }\n    out_data[i] = sum / length;\n  }\n}\n\n// ReduceFrontMeanGradient\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsGradientOp<CPUContext, true, true>::Compute(\n    int rows,\n    int cols,\n    const T* dYdata,\n    const int* lengths_data,\n    T* dXdata) {\n  for (int i = 0; i < rows * cols; i++) {\n    int row = i / cols;\n    int col = i % cols;\n    if (lengths_data == nullptr) {\n      dXdata[i] = dYdata[col] / rows;\n    } else if (row < lengths_data[col]) {\n      dXdata[i] = dYdata[col] / lengths_data[col];\n    } else {\n      dXdata[i] = 0;\n    }\n  }\n}\n\n// ReduceBackMeanGradient\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsGradientOp<CPUContext, false, true>::Compute(\n    int rows,\n    int cols,\n    const T* dYdata,\n    const int* lengths_data,\n    T* dXdata) {\n  for (int i = 0; i < rows * cols; i++) {\n    int row = i / cols;\n    int col = i % cols;\n    if (lengths_data == nullptr) {\n      dXdata[i] = dYdata[row] / cols;\n    } else if (col < lengths_data[row]) {\n      dXdata[i] = dYdata[row] / lengths_data[row];\n    } else {\n      dXdata[i] = 0;\n    }\n  }\n}\n\nREGISTER_CPU_OPERATOR(ReduceFrontMean, SumReduceDimsOp<CPUContext, true, true>);\nREGISTER_CPU_OPERATOR(\n    ReduceFrontMeanGradient,\n    SumReduceDimsGradientOp<CPUContext, true, true>);\n\nclass GetReduceFrontMeanGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> grad_in = {GO(0), I(0)};\n    if (def_.input_size() == 2) {\n      grad_in.push_back(I(1));\n    }\n    return SingleGradientDef(\n        \"ReduceFrontMeanGradient\", \"\", grad_in, vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(ReduceFrontMean, GetReduceFrontMeanGradient);\n\nOPERATOR_SCHEMA(ReduceFrontMean)\n    .NumInputs(1, 2)\n    .NumOutputs(1)\n    .Arg(\"num_reduce_dims\", \"Number of dimensions to reduce.\")\n    .SetDoc(R\"DOC(\nReduces the input tensor along the first dimension of the input\ntensor by applying 'Mean'. When lengths is given, mean is only computed\nwith subsets of elements correspondingly.\n)DOC\")\n    .Input(0, \"data_in\", \"(T<D1..., Dn>) Input data.\")\n    .Input(\n        1,\n        \"lengths\",\n        \"Num of elements in each sample, should have size D2 x D3 x ... x Dn.\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      REDUCTION_OP_SHAPE_INFERENCE(true)\n    });\nOPERATOR_SCHEMA(ReduceFrontMeanGradient).NumInputs(2, 3).NumOutputs(1);\n\nREGISTER_CPU_OPERATOR(ReduceBackMean, SumReduceDimsOp<CPUContext, false, true>);\nREGISTER_CPU_OPERATOR(\n    ReduceBackMeanGradient,\n    SumReduceDimsGradientOp<CPUContext, false, true>);\n\nclass GetReduceBackMeanGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> grad_in = {GO(0), I(0)};\n    if (def_.input_size() == 2) {\n      grad_in.push_back(I(1));\n    }\n    return SingleGradientDef(\n        \"ReduceBackMeanGradient\", \"\", grad_in, vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(ReduceBackMean, GetReduceBackMeanGradient);\n\nOPERATOR_SCHEMA(ReduceBackMean)\n    .NumInputs(1, 2)\n    .NumOutputs(1)\n    .Arg(\"num_reduce_dims\", \"Number of dimensions to reduce.\")\n    .SetDoc(R\"DOC(\nReduces the input tensor along the last dimension of the input\ntensor by applying 'Mean'. When lengths is given, mean is only computed\nwith subsets of elements correspondingly.\n)DOC\")\n    .Input(0, \"data_in\", \"(T<D1..., Dn>) Input data.\")\n    .Input(\n        1,\n        \"lengths\",\n        \"Num of elements in each sample, should have size D1 x D2 x ... x D(n-1).\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      REDUCTION_OP_SHAPE_INFERENCE(false)\n    });\nOPERATOR_SCHEMA(ReduceBackMeanGradient).NumInputs(2, 3).NumOutputs(1);\n\n/***\n  Max Ops\n***/\n\n// ReduceFrontMax\ntemplate <>\nvoid MaxReduceDimsOp<float, CPUContext, true>::Compute(\n    int rows,\n    int cols,\n    const float* data,\n    const int32_t* lengths_data,\n    float* out_data) {\n  for (int i = 0; i < cols; i++) {\n    float mx = data[i];\n    int length = lengths_data == nullptr ? rows : lengths_data[i];\n    for (int j = 1; j < length; j++) {\n      mx = std::max(mx, data[j * cols + i]);\n    }\n    out_data[i] = mx;\n  }\n}\n\n// ReduceBackMax\ntemplate <>\nvoid MaxReduceDimsOp<float, CPUContext, false>::Compute(\n    int rows,\n    int cols,\n    const float* data,\n    const int32_t* lengths_data,\n    float* out_data) {\n  for (int i = 0; i < rows; i++) {\n    float mx = data[i * cols];\n    int length = lengths_data == nullptr ? cols : lengths_data[i];\n    for (int j = 1; j < length; j++) {\n      mx = std::max(mx, data[i * cols + j]);\n    }\n    out_data[i] = mx;\n  }\n}\n\n// ReduceFrontMaxGradient\ntemplate <>\nvoid MaxReduceDimsGradientOp<float, CPUContext, true>::Compute(\n    int rows,\n    int cols,\n    const float* dYdata,\n    const float* Xdata,\n    const float* Ydata,\n    const int32_t* lengths_data,\n    float* dXdata) {\n  int len = cols * rows;\n  for (int i = 0; i < len; i++) {\n    int col = i % cols;\n    int row = i / cols;\n    if (lengths_data != nullptr && row >= lengths_data[col]) {\n      dXdata[i] = 0.0f;\n    } else {\n      dXdata[i] = Xdata[i] == Ydata[col] ? dYdata[col] : 0.0f;\n    }\n  }\n}\n\n// ReduceBackMaxGradient\ntemplate <>\nvoid MaxReduceDimsGradientOp<float, CPUContext, false>::Compute(\n    int rows,\n    int cols,\n    const float* dYdata,\n    const float* Xdata,\n    const float* Ydata,\n    const int32_t* lengths_data,\n    float* dXdata) {\n  int len = cols * rows;\n  for (int i = 0; i < len; i++) {\n    int row = i / cols;\n    int col = i % cols;\n    if (lengths_data == nullptr || col < lengths_data[row]) {\n      dXdata[i] = Xdata[i] == Ydata[row] ? dYdata[row] : 0.0f;\n    } else {\n      dXdata[i] = 0.0f;\n    }\n  }\n}\n\nREGISTER_CPU_OPERATOR(ReduceFrontMax, MaxReduceDimsOp<float, CPUContext, true>);\nREGISTER_CPU_OPERATOR(\n    ReduceFrontMaxGradient,\n    MaxReduceDimsGradientOp<float, CPUContext, true>);\n\nREGISTER_CPU_OPERATOR(ReduceBackMax, MaxReduceDimsOp<float, CPUContext, false>);\nREGISTER_CPU_OPERATOR(\n    ReduceBackMaxGradient,\n    MaxReduceDimsGradientOp<float, CPUContext, false>);\n\nclass GetReduceFrontMaxGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> grad_in = {GO(0), I(0), O(0)};\n    if (def_.input_size() == 2) {\n      grad_in.push_back(I(1));\n    }\n    return SingleGradientDef(\n        \"ReduceFrontMaxGradient\", \"\", grad_in, vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(ReduceFrontMax, GetReduceFrontMaxGradient);\n\nclass GetReduceBackMaxGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> grad_in = {GO(0), I(0), O(0)};\n    if (def_.input_size() == 2) {\n      grad_in.push_back(I(1));\n    }\n    return SingleGradientDef(\n        \"ReduceBackMaxGradient\", \"\", grad_in, vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(ReduceBackMax, GetReduceBackMaxGradient);\n\nOPERATOR_SCHEMA(ReduceFrontMax)\n    .NumInputs(1, 2)\n    .NumOutputs(1)\n    .Arg(\"num_reduce_dims\", \"Number of dimensions to reduce\")\n    .SetDoc(R\"DOC(\nReduces the input tensor along the first dimension of the input\ntensor by applying 'Max'. When lengths is given, max is only computed\nwith subsets of elements correspondingly.\n)DOC\")\n    .Input(0, \"data_in\", \"(T<D1..., Dn>) Input data.\")\n    .Input(\n        1,\n        \"lengths\",\n        \"Num of elements in each sample, should have size D2 x D3 ... x Dn.\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      REDUCTION_OP_SHAPE_INFERENCE(true)\n    });\nOPERATOR_SCHEMA(ReduceFrontMaxGradient).NumInputs(3, 4).NumOutputs(1);\n\nOPERATOR_SCHEMA(ReduceBackMax)\n    .NumInputs(1, 2)\n    .NumOutputs(1)\n    .Arg(\"num_reduce_dims\", \"Number of dimensions to reduce\")\n    .SetDoc(R\"DOC(\nReduces the input tensor along the last dimension of the\ninput tensor by applying 'Max'. When lengths is given, max is only computed\nwith subsets of elements correspondingly.\n)DOC\")\n    .Input(0, \"data_in\", \"(T<D1..., Dn>) Input data.\")\n    .Input(\n        1,\n        \"lengths\",\n        \"Num of elements in each sample, should have size D1 x D2 x ... x D(n-1).\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      REDUCTION_OP_SHAPE_INFERENCE(false)\n    });\nOPERATOR_SCHEMA(ReduceBackMaxGradient).NumInputs(3, 4).NumOutputs(1);\n\n#undef REDUCTION_OP_SHAPE_INFERENCE\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/reduction_front_back_ops.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cub/block/block_reduce.cuh>\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/reduction_front_back_ops.h\"\n\nnamespace caffe2 {\n\nnamespace {\ntemplate <typename T, bool NORMALIZE>\n__global__ void columnwise_fill_kernel(\n    const int rows,\n    const int cols,\n    const T* dY,\n    const int* lengths,\n    T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, rows * cols) {\n    int row = i / cols;\n    int col = i % cols;\n    if (lengths == nullptr) {\n      dX[i] = NORMALIZE ? dY[col] / rows : dY[col];\n    } else if (row < lengths[col]) {\n      dX[i] = NORMALIZE ? dY[col] / lengths[col] : dY[col];\n    } else {\n      dX[i] = 0;\n    }\n  }\n}\n\ntemplate <typename T, bool NORMALIZE>\n__global__ void rowwise_fill_kernel(\n    const int rows,\n    const int cols,\n    const T* dY,\n    const int* lengths,\n    T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, rows * cols) {\n    int row = i / cols;\n    int col = i % cols;\n    if (lengths == nullptr) {\n      dX[i] = NORMALIZE ? dY[row] / cols : dY[row];\n    } else if (col < lengths[row]) {\n      dX[i] = NORMALIZE ? dY[row] / lengths[row] : dY[row];\n    } else {\n      dX[i] = 0;\n    }\n  }\n}\n\ntemplate <typename T, bool NORMALIZE>\n__global__ void rowwise_sum_kernel(\n    const int rows,\n    const int cols,\n    const T* data,\n    const int* lengths,\n    T* out) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  for (int rowIndex = blockIdx.x; rowIndex < rows; rowIndex += gridDim.x) {\n    T sum = 0;\n    const int rowOffset = rowIndex * cols;\n    const int length = lengths == nullptr ? cols : lengths[rowIndex];\n    for (int colIndex = threadIdx.x; colIndex < length;\n         colIndex += blockDim.x) {\n      sum += data[rowOffset + colIndex];\n    }\n    sum = BlockReduce(temp_storage).Reduce(sum, cub::Sum());\n    if (threadIdx.x == 0) {\n      out[rowIndex] = NORMALIZE ? sum / length : sum;\n    }\n    __syncthreads();\n  }\n}\n\ntemplate <typename T, bool NORMALIZE>\n__global__ void columnwise_sum_kernel(\n    const int rows,\n    const int cols,\n    const T* data,\n    const int* lengths,\n    T* out) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  for (int colIndex = blockIdx.x; colIndex < cols; colIndex += gridDim.x) {\n    T sum = 0;\n    const int length = lengths == nullptr ? rows : lengths[colIndex];\n    for (int rowIndex = threadIdx.x; rowIndex < length;\n         rowIndex += blockDim.x) {\n      sum += data[rowIndex * cols + colIndex];\n    }\n    sum = BlockReduce(temp_storage).Reduce(sum, cub::Sum());\n    if (threadIdx.x == 0) {\n      out[colIndex] = NORMALIZE ? sum / length : sum;\n    }\n    __syncthreads();\n  }\n}\n\n} // anonymous namespace\n\n/***\n  Sum Ops\n***/\n\n// ReduceFrontSum: columnwise sum\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsOp<CUDAContext, true, false>::Compute(\n    int rows,\n    int cols,\n    const T* in_data,\n    const int* lengths_data,\n    T* out_data) {\n  columnwise_sum_kernel<T, false>\n      <<<std::min(cols, CAFFE_MAXIMUM_NUM_BLOCKS),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(rows, cols, in_data, lengths_data, out_data);\n}\n\n// ReduceBackSum: rowwise sum\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsOp<CUDAContext, false, false>::Compute(\n    int rows,\n    int cols,\n    const T* in_data,\n    const int* lengths_data,\n    T* out_data) {\n  rowwise_sum_kernel<T, false>\n      <<<std::min(rows, CAFFE_MAXIMUM_NUM_BLOCKS),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(rows, cols, in_data, lengths_data, out_data);\n}\n\n// ReduceFrontSumGradient\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsGradientOp<CUDAContext, true, false>::Compute(\n    int rows,\n    int cols,\n    const T* dYdata,\n    const int* lengths_data,\n    T* dXdata) {\n  columnwise_fill_kernel<T, false>\n      <<<CAFFE_GET_BLOCKS(rows * cols),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(rows, cols, dYdata, lengths_data, dXdata);\n}\n\n// ReduceBackSumGradient\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsGradientOp<CUDAContext, false, false>::Compute(\n    int rows,\n    int cols,\n    const T* dYdata,\n    const int* lengths_data,\n    T* dXdata) {\n  rowwise_fill_kernel<T, false>\n      <<<CAFFE_GET_BLOCKS(rows * cols),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(rows, cols, dYdata, lengths_data, dXdata);\n}\n\nREGISTER_CUDA_OPERATOR(\n    ReduceFrontSum,\n    SumReduceDimsOp<CUDAContext, true, false>);\nREGISTER_CUDA_OPERATOR(\n    ReduceFrontSumGradient,\n    SumReduceDimsGradientOp<CUDAContext, true, false>);\n\nREGISTER_CUDA_OPERATOR(\n    ReduceBackSum,\n    SumReduceDimsOp<CUDAContext, false, false>);\nREGISTER_CUDA_OPERATOR(\n    ReduceBackSumGradient,\n    SumReduceDimsGradientOp<CUDAContext, false, false>);\n\n/***\n  Mean Ops\n***/\n\n// ReduceFrontMean: columnwise mean\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsOp<CUDAContext, true, true>::Compute(\n    int rows,\n    int cols,\n    const T* in_data,\n    const int* lengths_data,\n    T* out_data) {\n  columnwise_sum_kernel<T, true>\n      <<<std::min(cols, CAFFE_MAXIMUM_NUM_BLOCKS),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(rows, cols, in_data, lengths_data, out_data);\n}\n\n// ReduceBackMean: rowwise mean\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsOp<CUDAContext, false, true>::Compute(\n    int rows,\n    int cols,\n    const T* in_data,\n    const int* lengths_data,\n    T* out_data) {\n  rowwise_sum_kernel<T, true>\n      <<<std::min(rows, CAFFE_MAXIMUM_NUM_BLOCKS),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(rows, cols, in_data, lengths_data, out_data);\n}\n\n// ReduceFrontMeanGradient\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsGradientOp<CUDAContext, true, true>::Compute(\n    int rows,\n    int cols,\n    const T* dYdata,\n    const int* lengths_data,\n    T* dXdata) {\n  columnwise_fill_kernel<T, true>\n      <<<CAFFE_GET_BLOCKS(rows * cols),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(rows, cols, dYdata, lengths_data, dXdata);\n}\n\n// ReduceBackMeanGradient\ntemplate <>\ntemplate <typename T>\nvoid SumReduceDimsGradientOp<CUDAContext, false, true>::Compute(\n    int rows,\n    int cols,\n    const T* dYdata,\n    const int* lengths_data,\n    T* dXdata) {\n  rowwise_fill_kernel<T, true>\n      <<<CAFFE_GET_BLOCKS(rows * cols),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(rows, cols, dYdata, lengths_data, dXdata);\n}\n\nREGISTER_CUDA_OPERATOR(\n    ReduceFrontMean,\n    SumReduceDimsOp<CUDAContext, true, true>);\nREGISTER_CUDA_OPERATOR(\n    ReduceFrontMeanGradient,\n    SumReduceDimsGradientOp<CUDAContext, true, true>);\n\nREGISTER_CUDA_OPERATOR(\n    ReduceBackMean,\n    SumReduceDimsOp<CUDAContext, false, true>);\nREGISTER_CUDA_OPERATOR(\n    ReduceBackMeanGradient,\n    SumReduceDimsGradientOp<CUDAContext, false, true>);\n\n/***\n  Max Ops\n***/\n\nnamespace {\n\n__global__ void columnwise_max_kernel(\n    const int rows,\n    const int cols,\n    const float* data,\n    const int* lengths,\n    float* out) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  for (int colIndex = blockIdx.x; colIndex < cols; colIndex += gridDim.x) {\n    float mx = FLT_MIN;\n    const int length = lengths == nullptr ? rows : lengths[colIndex];\n    for (int rowIndex = threadIdx.x; rowIndex < length;\n         rowIndex += blockDim.x) {\n      mx = max(mx, data[rowIndex * cols + colIndex]);\n    }\n    mx = BlockReduce(temp_storage).Reduce(mx, cub::Max());\n    if (threadIdx.x == 0) {\n      out[colIndex] = mx;\n    }\n    __syncthreads();\n  }\n}\n\n__global__ void rowwise_max_kernel(\n    const int rows,\n    const int cols,\n    const float* data,\n    const int* lengths,\n    float* out) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  for (int rowIndex = blockIdx.x; rowIndex < rows; rowIndex += gridDim.x) {\n    float mx = FLT_MIN;\n    const int length = lengths == nullptr ? cols : lengths[rowIndex];\n    for (int colIndex = threadIdx.x; colIndex < length;\n         colIndex += blockDim.x) {\n      mx = max(mx, data[rowIndex * cols + colIndex]);\n    }\n    mx = BlockReduce(temp_storage).Reduce(mx, cub::Max());\n    if (threadIdx.x == 0) {\n      out[rowIndex] = mx;\n    }\n    __syncthreads();\n  }\n}\n\n__global__ void columnwise_max_grad_kernel(\n    const int rows,\n    const int cols,\n    const float* dYdata,\n    const float* Xdata,\n    const float* Ydata,\n    const int* lengths,\n    float* dXdata) {\n  CUDA_1D_KERNEL_LOOP(i, rows * cols) {\n    int col = i % cols;\n    int row = i / cols;\n    if (lengths != nullptr && row >= lengths[col]) {\n      dXdata[i] = 0.0f;\n    } else {\n      dXdata[i] = (Xdata[i] == Ydata[col]) * dYdata[col];\n    }\n  }\n}\n\n__global__ void rowwise_max_grad_kernel(\n    const int rows,\n    const int cols,\n    const float* dYdata,\n    const float* Xdata,\n    const float* Ydata,\n    const int* lengths,\n    float* dXdata) {\n  CUDA_1D_KERNEL_LOOP(i, rows * cols) {\n    int col = i % cols;\n    int row = i / cols;\n    if (lengths != nullptr && col >= lengths[row]) {\n      dXdata[i] = 0.0f;\n    } else {\n      dXdata[i] = (Xdata[i] == Ydata[row]) * dYdata[row];\n    }\n  }\n}\n} // anonymous namespace\n\n// ReduceFrontmax\ntemplate <>\nvoid MaxReduceDimsOp<float, CUDAContext, true>::Compute(\n    int rows,\n    int cols,\n    const float* data,\n    const int32_t* lengths_data,\n    float* out_data) {\n  columnwise_max_kernel<<<\n      std::min(cols, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(rows, cols, data, lengths_data, out_data);\n}\n\n// ReduceBackMax\ntemplate <>\nvoid MaxReduceDimsOp<float, CUDAContext, false>::Compute(\n    int rows,\n    int cols,\n    const float* data,\n    const int32_t* lengths_data,\n    float* out_data) {\n  rowwise_max_kernel<<<\n      std::min(rows, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(rows, cols, data, lengths_data, out_data);\n}\n\n// ReduceFrontMaxGradient\ntemplate <>\nvoid MaxReduceDimsGradientOp<float, CUDAContext, true>::Compute(\n    int rows,\n    int cols,\n    const float* dYdata,\n    const float* Xdata,\n    const float* Ydata,\n    const int32_t* lengths_data,\n    float* dXdata) {\n  columnwise_max_grad_kernel<<<\n      CAFFE_GET_BLOCKS(rows * cols),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      rows, cols, dYdata, Xdata, Ydata, lengths_data, dXdata);\n}\n\n// ReduceBackMaxGradient\ntemplate <>\nvoid MaxReduceDimsGradientOp<float, CUDAContext, false>::Compute(\n    int rows,\n    int cols,\n    const float* dYdata,\n    const float* Xdata,\n    const float* Ydata,\n    const int* lengths_data,\n    float* dXdata) {\n  rowwise_max_grad_kernel<<<\n      CAFFE_GET_BLOCKS(rows * cols),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      rows, cols, dYdata, Xdata, Ydata, lengths_data, dXdata);\n}\n\nREGISTER_CUDA_OPERATOR(\n    ReduceFrontMax,\n    MaxReduceDimsOp<float, CUDAContext, true>);\nREGISTER_CUDA_OPERATOR(\n    ReduceFrontMaxGradient,\n    MaxReduceDimsGradientOp<float, CUDAContext, true>);\n\nREGISTER_CUDA_OPERATOR(\n    ReduceBackMax,\n    MaxReduceDimsOp<float, CUDAContext, false>);\nREGISTER_CUDA_OPERATOR(\n    ReduceBackMaxGradient,\n    MaxReduceDimsGradientOp<float, CUDAContext, false>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/reduction_front_back_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_REDUCTION_FRONT_BACK_OPS_H_\n#define CAFFE2_OPERATORS_REDUCTION_FRONT_BACK_OPS_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context, bool FIRSTDIMS, bool NORMALIZE>\nclass SumReduceDimsOp final : public Operator<Context> {\n public:\n  SumReduceDimsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        num_reduce_dims_(\n            OperatorBase::GetSingleArgument<int32_t>(\"num_reduce_dim\", 1)) {}\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int, long, float, double>>::call(\n        this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n\n    CAFFE_ENFORCE(\n        num_reduce_dims_ >= 0 && num_reduce_dims_ <= X.dims().size(),\n        \"For N-dim input tensor, support num_reduce_dims in range [0, N].\");\n\n    vector<TIndex> output_shape;\n    int start_index = FIRSTDIMS ? num_reduce_dims_ : 0;\n    int end_index =\n        FIRSTDIMS ? X.dims().size() : X.dims().size() - num_reduce_dims_;\n    for (int i = start_index; i < end_index; ++i) {\n      output_shape.push_back(X.dims()[i]);\n    }\n    Y->Resize(output_shape);\n\n    const int rows = FIRSTDIMS ? X.size_to_dim(num_reduce_dims_)\n                               : X.size_to_dim(X.ndim() - num_reduce_dims_);\n    const int cols = FIRSTDIMS ? X.size_from_dim(num_reduce_dims_)\n                               : X.size_from_dim(X.ndim() - num_reduce_dims_);\n\n    if (cols == 0 || rows == 0) {\n      return true;\n    }\n\n    const int32_t* lengths_data = nullptr;\n    if (InputSize() > 1) {\n      const auto& lengths = Input(1);\n      lengths_data = lengths.template data<int32_t>();\n      CAFFE_ENFORCE(\n          num_reduce_dims_ == 1,\n          \"Given lengths input, the number of reduce dimensions should be one.\");\n      const int batch_size = FIRSTDIMS ? cols : rows;\n      CAFFE_ENFORCE(\n          lengths.size() == batch_size,\n          \"The size of lengths vector doesn't match the batch size.\");\n    }\n\n    const T* in_data = X.template data<T>();\n    T* out_data = Y->template mutable_data<T>();\n    Compute(rows, cols, in_data, lengths_data, out_data);\n\n    return true;\n  }\n\n private:\n  template <typename T>\n  void Compute(\n      int rows,\n      int cols,\n      const T* in_data,\n      const int32_t* lengths_data,\n      T* out_data);\n\n  int num_reduce_dims_;\n};\n\ntemplate <class Context, bool FIRSTDIMS, bool NORMALIZE>\nclass SumReduceDimsGradientOp final : public Operator<Context> {\n public:\n  SumReduceDimsGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        num_reduce_dims_(\n            OperatorBase::GetSingleArgument<int32_t>(\"num_reduce_dim\", 1)) {}\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int, long, float, double>>::call(\n        this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& dY = Input(0);\n    auto& input_1 = Input(1);\n    auto* dX = Output(0);\n\n    // In previous diff we changed the semantic: Input(1) was changed from\n    // the shape of the input to the data tensor. This made the backward\n    // computation incompatible with old models. To fix this, we check\n    // the dimension and type of Input(1).\n    if (input_1.ndim() == 1 && input_1.template IsType<TIndex>()) {\n      // Input(1) is the shape of the input\n      shape_.CopyFrom(input_1);\n      // Copy first dims\n      vector<TIndex> output_shape(\n          shape_.template data<TIndex>(),\n          shape_.template data<TIndex>() + shape_.size());\n      dX->Resize(output_shape);\n    } else {\n      // Input(1) is data tensor X\n      dX->ResizeLike(input_1);\n    }\n\n    const int rows = FIRSTDIMS ? dX->size_to_dim(num_reduce_dims_)\n                               : dX->size_to_dim(dX->ndim() - num_reduce_dims_);\n    const int cols = FIRSTDIMS\n        ? dX->size_from_dim(num_reduce_dims_)\n        : dX->size_from_dim(dX->ndim() - num_reduce_dims_);\n\n    const int32_t* lengths_data = nullptr;\n    if (InputSize() > 2) {\n      const auto& lengths = Input(2);\n      lengths_data = lengths.template data<int32_t>();\n      CAFFE_ENFORCE(\n          num_reduce_dims_ == 1,\n          \"Given lengths input, the number of reduce dimensions should be one.\");\n      const int batch_size = FIRSTDIMS ? cols : rows;\n      CAFFE_ENFORCE(\n          lengths.size() == batch_size,\n          \"The size of lengths vector doesn't match the batch size.\");\n    }\n\n    const T* dYdata = dY.template data<T>();\n    T* dXdata = dX->template mutable_data<T>();\n    Compute<T>(rows, cols, dYdata, lengths_data, dXdata);\n    return true;\n  }\n\n private:\n  template <typename T>\n  void Compute(\n      int rows,\n      int cols,\n      const T* dYdata,\n      const int32_t* lengths_data,\n      T* dXdata);\n  int num_reduce_dims_;\n  // scratch space used for former version of this reducer\n  Tensor<CPUContext> shape_;\n};\n\ntemplate <typename T, class Context, bool FIRSTDIMS>\nclass MaxReduceDimsOp final : public Operator<Context> {\n public:\n  MaxReduceDimsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        num_reduce_dims_(\n            OperatorBase::GetSingleArgument<int32_t>(\"num_reduce_dim\", 1)) {}\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n\n    CAFFE_ENFORCE(\n        num_reduce_dims_ >= 0 && num_reduce_dims_ <= X.dims().size(),\n        \"For N-dim input tensor, support num_reduce_dims in range [0, N].\");\n\n    const int rows = FIRSTDIMS ? X.size_to_dim(num_reduce_dims_)\n                               : X.size_to_dim(X.ndim() - num_reduce_dims_);\n    const int cols = FIRSTDIMS ? X.size_from_dim(num_reduce_dims_)\n                               : X.size_from_dim(X.ndim() - num_reduce_dims_);\n\n    vector<TIndex> output_shape;\n    int start_index = FIRSTDIMS ? num_reduce_dims_ : 0;\n    int end_index =\n        FIRSTDIMS ? X.dims().size() : X.dims().size() - num_reduce_dims_;\n\n    for (int i = start_index; i < end_index; ++i) {\n      output_shape.push_back(X.dims()[i]);\n    }\n    Y->Resize(output_shape);\n\n    if (cols == 0 || rows == 0) {\n      return true;\n    }\n\n    const int32_t* lengths_data = nullptr;\n    if (InputSize() > 1) {\n      const auto& lengths = Input(1);\n      lengths_data = lengths.template data<int32_t>();\n      CAFFE_ENFORCE(\n          num_reduce_dims_ == 1,\n          \"Given lengths input, the number of reduce dimensions should be one.\");\n      const int batch_size = FIRSTDIMS ? cols : rows;\n      CAFFE_ENFORCE(\n          lengths.size() == batch_size,\n          \"The size of lengths vector doesn't match the batch size.\");\n    }\n\n    const float* data = X.template data<float>();\n    float* out_data = Y->template mutable_data<float>();\n    Compute(rows, cols, data, lengths_data, out_data);\n    return true;\n  }\n\n protected:\n  void Compute(\n      int rows,\n      int cols,\n      const float* data,\n      const int32_t* lengths_data,\n      float* out_data);\n\n  int num_reduce_dims_;\n};\n\ntemplate <typename T, class Context, bool FIRSTDIMS>\nclass MaxReduceDimsGradientOp final : public Operator<Context> {\n public:\n  MaxReduceDimsGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        num_reduce_dims_(\n            OperatorBase::GetSingleArgument<int32_t>(\"num_reduce_dim\", 1)) {}\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    auto& dY = Input(0);\n    auto& X = Input(1);\n    auto& Y = Input(2);\n    auto* dX = Output(0);\n\n    dX->ResizeLike(X);\n    const int rows = FIRSTDIMS ? X.size_to_dim(num_reduce_dims_)\n                               : X.size_to_dim(X.ndim() - num_reduce_dims_);\n    const int cols = FIRSTDIMS ? X.size_from_dim(num_reduce_dims_)\n                               : X.size_from_dim(X.ndim() - num_reduce_dims_);\n\n    const float* dYdata = dY.template data<float>();\n    const float* Xdata = X.template data<float>();\n    const float* Ydata = Y.template data<float>();\n\n    const int32_t* lengths_data = nullptr;\n    if (InputSize() > 3) {\n      const auto& lengths = Input(3);\n      lengths_data = lengths.template data<int32_t>();\n      CAFFE_ENFORCE(\n          num_reduce_dims_ == 1,\n          \"Given lengths input, the number of reduce dimensions should be one.\");\n      const int batch_size = FIRSTDIMS ? cols : rows;\n      CAFFE_ENFORCE(\n          lengths.size() == batch_size,\n          \"The size of lengths vector doesn't match the batch size.\");\n    }\n\n    float* dXdata = dX->template mutable_data<float>();\n    Compute(rows, cols, dYdata, Xdata, Ydata, lengths_data, dXdata);\n    return true;\n  }\n\n protected:\n  void Compute(\n      int rows,\n      int cols,\n      const float* dYdata,\n      const float* Xdata,\n      const float* Ydata,\n      const int32_t* lengths_data,\n      float* dXdata);\n\n  int num_reduce_dims_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_REDUCTION_FRONT_BACK_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/reduction_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/reduction_ops.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(SumElements, SumElementsOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(SumSqrElements, SumSqrElementsOp<CPUContext>);\n\nREGISTER_CPU_OPERATOR(\n    SumElementsGradient,\n    SumElementsGradientOp<float, CPUContext>);\n\nREGISTER_CPU_OPERATOR(RowwiseMax, MaxReductionOp<float, CPUContext, true>);\nREGISTER_CPU_OPERATOR(\n    RowwiseMaxGradient,\n    MaxReductionGradientOp<float, CPUContext, true>);\nREGISTER_CPU_OPERATOR(\n    ColwiseMaxGradient,\n    MaxReductionGradientOp<float, CPUContext, false>);\nREGISTER_CPU_OPERATOR(ColwiseMax, MaxReductionOp<float, CPUContext, false>);\n\nOPERATOR_SCHEMA(SumElements)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .ScalarType(TensorProto::FLOAT)\n    .SetDoc(\"Sums the elements of the input tensor.\")\n    .Arg(\"average\", \"whether to average or not\")\n    .Input(0, \"X\", \"Tensor to sum up\")\n    .Output(0, \"sum\", \"Scalar sum\");\n\nOPERATOR_SCHEMA(SumSqrElements)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .ScalarType(TensorProto::FLOAT)\n    .SetDoc(\"Sums the squares elements of the input tensor.\")\n    .Arg(\"average\", \"whether to average or not\")\n    .Input(0, \"X\", \"Tensor to sum up\")\n    .Output(0, \"sum\", \"Scalar sum of squares\");\n\nOPERATOR_SCHEMA(SumElementsGradient).NumInputs(2).NumOutputs(1);\n\nclass GetSumElementsGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SumElementsGradient\",\n        \"\",\n        vector<string>{I(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(SumElements, GetSumElementsGradient);\n\nOPERATOR_SCHEMA(RowwiseMax)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(\"Compute row-wise max reduction of the input tensor.\")\n    .Input(\n        0,\n        \"X\",\n        \"A tenosr of dimensions batch_size x M x N to compute rowwise-max.\")\n    .Output(0, \"Y\", \"batch_size x M rowwise-max results matrix.\");\n\nOPERATOR_SCHEMA(RowwiseMaxGradient).NumInputs(3).NumOutputs(1);\nclass GetRowwiseMaxGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"RowwiseMaxGradient\",\n        \"\",\n        vector<string>{I(0), O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(RowwiseMax, GetRowwiseMaxGradient);\n\nOPERATOR_SCHEMA(ColwiseMaxGradient);\n\nOPERATOR_SCHEMA(ColwiseMax)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(\"Compute column-wise max reduction of the input tensor.\")\n    .Input(\n        0,\n        \"X\",\n        \"A tenosr of dimensions batch_size x M x N to compute colwise-max.\")\n    .Output(0, \"Y\", \"batch_size x N column-max results matrix.\");\n\nOPERATOR_SCHEMA(ColumnMaxGradient).NumInputs(3).NumOutputs(1);\nclass GetColwiseMaxGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"ColwiseMaxGradient\",\n        \"\",\n        vector<string>{I(0), O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(ColwiseMax, GetColwiseMaxGradient);\n\ntemplate <typename T, class Context>\nbool SumElementsGradientOp<T, Context>::RunOnDevice()\n// TODO: T21635077 fix float-divide-by-zero undefined behavior\n#if defined(__has_feature)\n#if __has_feature(__address_sanitizer__)\n    __attribute__((__no_sanitize__(\"float-divide-by-zero\")))\n#endif\n#endif\n{\n  auto& X = Input(0);\n  TensorCPU sum_grad = TensorCPU(Input(1));\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n  DCHECK_EQ(sum_grad.size(), 1);\n  math::Set<T, Context>(\n      dX->size(),\n      static_cast<T>(sum_grad.data<T>()[0] * (average_ ? 1.0 / X.size() : 1)),\n      dX->template mutable_data<T>(),\n      &context_);\n  return true;\n}\n\ntemplate <typename T, class Context, bool ROWWISE>\nbool MaxReductionGradientOp<T, Context, ROWWISE>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dY = Input(2);\n\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n\n  CAFFE_ENFORCE_EQ(X.ndim(), 3);\n\n  const int batch_size = X.dim32(0);\n  const int M = X.dim32(1);\n  const int N = X.dim32(2);\n\n  const T* Xdata = X.template data<T>();\n  const T* Ydata = Y.template data<T>();\n  const T* dYdata = dY.template data<T>();\n  T* dXdata = dX->template mutable_data<T>();\n\n  const int input_size = M * N;\n  for (int i = 0; i < batch_size; ++i) {\n    const T* Xdata_i = Xdata + i * input_size;\n    T* dXdata_i = dXdata + i * input_size;\n    if (ROWWISE) {\n      const T* Ydata_i = Ydata + i * M;\n      const T* dYdata_i = dYdata + i * M;\n      for (int m = 0; m < M; ++m) {\n        const T* Xdata_m = Xdata_i + m * N;\n        T* dXdata_m = dXdata_i + m * N;\n        for (int n = 0; n < N; ++n) {\n          if (Xdata_m[n] == Ydata_i[m]) {\n            dXdata_m[n] = dYdata_i[m];\n          } else {\n            dXdata_m[n] = static_cast<T>(0);\n          }\n        }\n      }\n    } else {\n      const T* Ydata_i = Ydata + i * N;\n      const T* dYdata_i = dYdata + i * N;\n      for (int n = 0; n < N; ++n) {\n        for (int m = 0; m < M; ++m) {\n          const T* Xdata_m = Xdata_i + m * N;\n          T* dXdata_m = dXdata_i + m * N;\n          if (Xdata_m[n] == Ydata_i[n]) {\n            dXdata_m[n] = dYdata_i[n];\n          } else {\n            dXdata_m[n] = static_cast<T>(0);\n          }\n        }\n      }\n    }\n  }\n\n  return true;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/reduction_ops.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/reduction_ops.h\"\n#include \"caffe2/utils/conversions.h\"\n\n#include <cub/cub.cuh>\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(SumElements, SumElementsOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(SumSqrElements, SumSqrElementsOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(RowwiseMax, MaxReductionOp<float, CUDAContext, true>);\nREGISTER_CUDA_OPERATOR(ColwiseMax, MaxReductionOp<float, CUDAContext, false>);\nREGISTER_CUDA_OPERATOR(\n    RowwiseMaxGradient,\n    MaxReductionGradientOp<float, CUDAContext, true>)\nREGISTER_CUDA_OPERATOR(\n    ColwiseMaxGradient,\n    MaxReductionGradientOp<float, CUDAContext, false>)\n\nREGISTER_CUDA_OPERATOR(\n    SumElementsGradient,\n    SumElementsGradientOp<float, CUDAContext>);\n\ntemplate <typename T>\n__global__ void\nSumElementsGradientKernel(bool average, const int N, const T* dY, T* dX) {\n  const T value = average ? (*dY) / N : *dY;\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dX[i] = value;\n  }\n}\n\n__global__ void rowwise_max_gradient_kernel(\n    const int batch_size,\n    const int M,\n    const int N,\n    const float* X,\n    const float* Y,\n    const float* dY,\n    float* dX) {\n  const int input_size = M * N;\n  CUDA_1D_KERNEL_LOOP(i, batch_size * M * N) {\n    const int b_i = i / input_size;\n    const int b_n = i / input_size / N;\n    const int y_index = b_i * M + b_n;\n    if (X[i] == Y[y_index]) {\n      dX[i] = dY[y_index];\n    } else {\n      dX[i] = 0.0;\n    }\n  }\n}\n\ntemplate <>\nbool SumSqrElementsOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<float, float16>>::call(this, Input(0));\n}\n\n\n__global__ void colwise_max_gradient_kernel(\n    const int batch_size,\n    const int M,\n    const int N,\n    const float* X,\n    const float* Y,\n    const float* dY,\n    float* dX) {\n  const int input_size = M * N;\n  CUDA_1D_KERNEL_LOOP(i, batch_size * M * N) {\n    const int b_i = i / input_size;\n    const int b_n = i % input_size % N;\n    const int y_index = b_i * N + b_n;\n    if (X[i] == Y[y_index]) {\n      dX[i] = dY[y_index];\n    } else {\n      dX[i] = 0.0;\n    }\n  }\n}\n\ntemplate <>\nbool SumElementsGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& dY = Input(1);\n  DCHECK_EQ(dY.size(), 1);\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n  SumElementsGradientKernel<float><<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      average_, X.size(), dY.data<float>(), dX->mutable_data<float>());\n  return true;\n}\n\ntemplate <typename T, class Context, bool ROWWISE>\nbool MaxReductionGradientOp<T, Context, ROWWISE>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& Y = Input(1);\n  auto& dY = Input(2);\n\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n\n  CAFFE_ENFORCE_EQ(X.ndim(), 3);\n\n  const int batch_size = X.dim32(0);\n  const int M = X.dim32(1);\n  const int N = X.dim32(2);\n\n  const T* Xdata = X.template data<T>();\n  const T* Ydata = Y.template data<T>();\n  const T* dYdata = dY.template data<T>();\n  T* dXdata = dX->template mutable_data<T>();\n\n  const int input_size = M * N;\n  if (ROWWISE) {\n    rowwise_max_gradient_kernel<<<\n        CAFFE_GET_BLOCKS(batch_size * input_size),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        batch_size, M, N, Xdata, Ydata, dYdata, dXdata);\n  } else {\n    colwise_max_gradient_kernel<<<\n        CAFFE_GET_BLOCKS(batch_size * input_size),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        batch_size, M, N, Xdata, Ydata, dYdata, dXdata);\n  }\n  return true;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/reduction_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_REDUCTION_OPS_H_\n#define CAFFE2_OPERATORS_REDUCTION_OPS_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SumElementsOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  SumElementsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        average_(OperatorBase::GetSingleArgument<bool>(\"average\", false)) {}\n  SumElementsOp(const OperatorDef& operator_def, Workspace* ws, bool average)\n      : Operator<Context>(operator_def, ws), average_(average) {}\n  ~SumElementsOp() {}\n\n  bool RunOnDevice() override\n// TODO: T21635002 fix float-divide-by-zero undefined behavior\n#if defined(__has_feature)\n#if __has_feature(__address_sanitizer__)\n      __attribute__((__no_sanitize__(\"float-divide-by-zero\")))\n#endif\n#endif\n  {\n    auto& X = Input(0);\n    auto* sum = Output(0);\n    sum->Resize(vector<TIndex>());\n    T* data = sum->template mutable_data<T>();\n    math::Sum<T, Context>(\n      X.size(), X.template data<T>(), data, &context_, &scratch_);\n    if (average_) {\n      math::Scale<T, Context>(\n          1,\n          static_cast<T>(1.) / X.size(),\n          sum->template data<T>(),\n          data,\n          &context_);\n    }\n    return true;\n  }\n\n private:\n  bool average_;\n  Tensor<Context> scratch_;\n};\n\ntemplate <typename T, class Context>\nclass SumElementsGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  SumElementsGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        average_(OperatorBase::GetSingleArgument<bool>(\"average\", false)) {}\n  SumElementsGradientOp(\n      const OperatorDef& operator_def,\n      Workspace* ws,\n      bool average)\n      : Operator<Context>(operator_def, ws), average_(average) {}\n  ~SumElementsGradientOp() {}\n\n  bool RunOnDevice() override;\n\n private:\n  bool average_;\n};\n\ntemplate <class Context>\nclass SumSqrElementsOp : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(SumSqrElementsOp)\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<float>>::call(this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    bool average = OperatorBase::GetSingleArgument<bool>(\"average\", false);\n    auto& X = Input(0);\n    auto* sum = Output(0);\n    sum->Resize(vector<TIndex>());\n    math::SumSqr<T, Context>(\n        X.size(),\n        X.template data<T>(),\n        sum->template mutable_data<T>(),\n        &context_,\n        &scratch_);\n    if (average) {\n      math::Scale<T, Context>(\n          1,\n          float(1.) / X.size(),\n          sum->template data<T>(),\n          sum->template mutable_data<T>(),\n          &context_);\n    }\n    return true;\n  }\n\n private:\n  Tensor<Context> scratch_;\n};\n\ntemplate <typename T, class Context, bool ROWWISE>\nclass MaxReductionOp : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(MaxReductionOp)\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    auto& X = Input(0);\n    CAFFE_ENFORCE_EQ(X.ndim(), 3);\n\n    const int batch_size = X.dim32(0);\n    const int M = X.dim32(1);\n    const int N = X.dim32(2);\n\n    auto* Y = Output(0);\n    ROWWISE ? Y->Resize(batch_size, M) : Y->Resize(batch_size, N);\n\n    if (ROWWISE) {\n      math::RowwiseMax<T, Context>(\n          batch_size * M,\n          N,\n          X.template data<T>(),\n          Y->template mutable_data<T>(),\n          &context_);\n    } else {\n      const int input_size = N * M;\n      for (int i = 0; i < batch_size; ++i) {\n        math::ColwiseMax<T, Context>(\n            M,\n            N,\n            X.template data<T>() + i * input_size,\n            Y->template mutable_data<T>() + i * N,\n            &context_);\n      }\n    }\n    return true;\n  }\n};\n\ntemplate <typename T, class Context, bool ROWWISE>\nclass MaxReductionGradientOp : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(MaxReductionGradientOp)\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n};\n\n} // namespace caffe2\n\n#endif\n"
  },
  {
    "path": "caffe2/operators/relu_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/relu_op.h\"\n\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool ReluOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n\n#ifdef CAFFE2_USE_ACCELERATE\n  const float zero = 0.0f;\n  vDSP_vthres(X.data<float>(), 1, &zero, Y->mutable_data<float>(), 1, X.size());\n#else\n  EigenVectorMap<float>(Y->mutable_data<float>(), X.size()) =\n      ConstEigenVectorMap<float>(X.data<float>(), X.size()).cwiseMax(0.f);\n#endif\n  /* Naive implementation\n  const float* Xdata = X.data<float>();\n  float* Ydata = Y->mutable_data<float>();\n  for (int i = 0; i < X.size(); ++i) {\n    Ydata[i] = std::max(Xdata[i], 0.f);\n  }\n  */\n  return true;\n}\n\ntemplate <>\nbool ReluGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  CAFFE_ENFORCE_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n\n  const float* Ydata = Y.data<float>();\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  // TODO: proper vectorization with Eigen\n  EigenVectorArrayMap<float> dXvec(dXdata, dX->size());\n  ConstEigenVectorArrayMap<float> Yvec(Ydata, Y.size());\n  ConstEigenVectorArrayMap<float> dYvec(dYdata, dY.size());\n  dXvec = dYvec * Yvec.cwiseSign();\n  /* Previous implementation\n  for (int i = 0; i < Y.size(); ++i) {\n    dXdata[i] = Ydata[i] > 0 ? dYdata[i] : 0;\n  }\n  */\n  return true;\n}\n\nnamespace {\nOpSchema::Cost CostInferenceForRelu(\n    const OperatorDef& def,\n    const vector<TensorShape>& in) {\n  struct OpSchema::Cost cost = PointwiseCostInference<0>(def, in);\n  if (def.input(0) == def.output(0)) {\n    cost.bytes_moved = 0;\n  }\n  cost.params_bytes = 0;\n  return cost;\n}\n} // namespace\n\nREGISTER_CPU_OPERATOR(Relu, ReluOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(ReluGradient, ReluGradientOp<float, CPUContext>);\n\n// Input: X, output: Y\nOPERATOR_SCHEMA(Relu)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .CostInferenceFunction(CostInferenceForRelu)\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nRelu takes one input data (Tensor<T>) and produces one output data\n(Tensor<T>) where the rectified linear function, y = max(0, x), is applied to\nthe tensor elementwise.\n)DOC\")\n    .Input(0, \"X\", \"1D input tensor\")\n    .Output(0, \"Y\", \"1D input tensor\");\n\n// Input: Y, dY, output: dX\nOPERATOR_SCHEMA(ReluGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{1, 0}})\n    .SetDoc(R\"DOC(\nReluGradient takes both Y and dY and uses this to update dX according to the\nchain rule and derivatives of the rectified linear function.\n)DOC\");\n\nclass GetReluGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        def_.type() + \"Gradient\",\n        \"\",\n        vector<string>{O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Relu, GetReluGradient);\nREGISTER_GRADIENT(ReluFp16, GetReluGradient);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/relu_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/relu_op.h\"\n\nnamespace caffe2 {\nnamespace {\ntemplate <typename T>\n__global__ void ReluKernel(const int N, const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = X[i] > 0 ? X[i] : 0;\n  }\n}\n\ntemplate <typename T>\n__global__ void ReluGradientKernel(const int N, const T* Y, const T* dY,\n                              T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dX[i] = Y[i] > 0 ? dY[i] : 0;\n  }\n}\n}  // namespace\n\ntemplate <>\nbool ReluOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE_GT(X.size(), 0);\n  Y->ResizeLike(X);\n  ReluKernel<<<CAFFE_GET_BLOCKS(X.size()), CAFFE_CUDA_NUM_THREADS,\n               0, context_.cuda_stream()>>>(\n      X.size(), X.data<float>(), Y->mutable_data<float>());\n  return true;\n}\n\ntemplate <>\nbool ReluGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  CAFFE_ENFORCE_GT(Y.size(), 0);\n  CAFFE_ENFORCE_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n  ReluGradientKernel<<<CAFFE_GET_BLOCKS(Y.size()), CAFFE_CUDA_NUM_THREADS,\n                       0, context_.cuda_stream()>>>(\n      Y.size(), Y.data<float>(), dY.data<float>(), dX->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Relu, ReluOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(ReluGradient, ReluGradientOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/relu_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_RELU_OP_H_\n#define CAFFE2_OPERATORS_RELU_OP_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass ReluOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(ReluOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n};\n\ntemplate <typename T, class Context>\nclass ReluGradientOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(ReluGradientOp);\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  // Input: Y, dY; Output: dX\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_RELU_OP_H_\n"
  },
  {
    "path": "caffe2/operators/relu_op_cudnn.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/cudnn_wrappers.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/types.h\"\n\nnamespace caffe2 {\n\nclass CuDNNReluOp final : public Operator<CUDAContext> {\n public:\n  CuDNNReluOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws),\n        cudnn_wrapper_(&context_),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&data_desc_));\n    CUDNN_ENFORCE(cudnnCreateActivationDescriptor(&activ_desc_));\n    CUDNN_ENFORCE(cudnnSetActivationDescriptor(\n        activ_desc_, CUDNN_ACTIVATION_RELU, CUDNN_PROPAGATE_NAN, 0.0));\n  }\n\n  ~CuDNNReluOp() {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(data_desc_));\n    CUDNN_ENFORCE(cudnnDestroyActivationDescriptor(activ_desc_));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    const auto& X = Input(0);\n    auto* Y = Output(0);\n\n    // Return if X is empty\n    if (X.size() == 0) {\n      Y->mutable_data<T>();\n      return true;\n    }\n\n    // See if we need to reshape.\n    if (X.dims() != cudnn_input_dims_) {\n      VLOG(1) << \"Setting descriptors.\";\n      cudnn_input_dims_ = X.dims();\n      int C = 1, H = 1, W = 1;\n      if (X.ndim() == 4) {\n        // Normal 4-dimensional tensors for images.\n        C = (order_ == StorageOrder::NCHW ? X.dim32(1) : X.dim32(3));\n        H = (order_ == StorageOrder::NCHW ? X.dim32(2) : X.dim32(1));\n        W = (order_ == StorageOrder::NCHW ? X.dim32(3) : X.dim32(2));\n      } else {\n        // If X is not 4-dimensional, we will simply use H = 1 and W = 1\n        // and wrap everything into C.\n        C = X.size() / X.dim32(0);\n      }\n      CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n          data_desc_,\n          GetCudnnTensorFormat(order_),\n          cudnnTypeWrapper<T>::type,\n          X.dim32(0),\n          C,\n          H,\n          W));\n    }\n    CUDNN_ENFORCE(cudnnActivationForward(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        activ_desc_,\n        cudnnTypeWrapper<T>::kOne(),\n        data_desc_,\n        X.template data<T>(),\n        cudnnTypeWrapper<T>::kZero(),\n        data_desc_,\n        Y->template mutable_data<T>()));\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    // dispatch based on contents of tensor(s)\n    const auto& X = Input(0);\n    auto* Y = Output(0);\n    Y->ResizeLike(X);\n\n    if (X.IsType<float>()) {\n      return DoRunWithType<float>();\n    } else if (X.IsType<float16>()) {\n      return DoRunWithType<float16>();\n    } else {\n      LOG(FATAL) << \"Unsupported input types\";\n    }\n    return true;\n  }\n\n protected:\n  CuDNNWrapper cudnn_wrapper_;\n  cudnnTensorDescriptor_t data_desc_;\n  cudnnActivationDescriptor_t activ_desc_;\n  vector<TIndex> cudnn_input_dims_;\n  StorageOrder order_;\n};\n\n\n// Note: You can see that in CuDNNReluGradientOp, we abused the cudnn interface\n// by passing in the output tensor for both bottom and top. This is dependent on\n// the assumption that the Relu gradient actually does not rely on the bottom\n// data, or it treats input=0 the same way as input<0. This is of course not\n// very safe, but we have been running in this way in Caffe for a while so it\n// *might* be safe to assume so.\nclass CuDNNReluGradientOp final : public Operator<CUDAContext> {\n public:\n  CuDNNReluGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws),\n        cudnn_wrapper_(&context_),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&data_desc_));\n    CUDNN_ENFORCE(cudnnCreateActivationDescriptor(&activ_desc_));\n    CUDNN_ENFORCE(cudnnSetActivationDescriptor(\n        activ_desc_, CUDNN_ACTIVATION_RELU, CUDNN_PROPAGATE_NAN, 0.0));\n  }\n\n  ~CuDNNReluGradientOp() {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(data_desc_));\n    CUDNN_ENFORCE(cudnnDestroyActivationDescriptor(activ_desc_));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    const auto& Y = Input(0);\n    const auto& dY = Input(1);\n    auto* dX = Output(0);\n\n    // Return if Y is empty\n    if (Y.size() == 0) {\n      dX->mutable_data<T>();\n      return true;\n    }\n\n    // See if we need to reshape.\n    if (Y.dims() != cudnn_input_dims_) {\n      VLOG(1) << \"Setting descriptors.\";\n      cudnn_input_dims_ = Y.dims();\n      int C = 1, H = 1, W = 1;\n      if (Y.ndim() == 4) {\n        // Normal 4-dimensional tensors for images.\n        C = (order_ == StorageOrder::NCHW ? Y.dim32(1) : Y.dim32(3));\n        H = (order_ == StorageOrder::NCHW ? Y.dim32(2) : Y.dim32(1));\n        W = (order_ == StorageOrder::NCHW ? Y.dim32(3) : Y.dim32(2));\n      } else {\n        // If Y is not 4-dimensional, we will simply use H = 1 and W = 1\n        // and wrap everything into C.\n        C = Y.size() / Y.dim32(0);\n      }\n      CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n          data_desc_,\n          GetCudnnTensorFormat(order_),\n          cudnnTypeWrapper<T>::type,\n          Y.dim32(0),\n          C,\n          H,\n          W));\n    }\n    CUDNN_ENFORCE(cudnnActivationBackward(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        activ_desc_,\n        cudnnTypeWrapper<T>::kOne(),\n        data_desc_,\n        Y.template data<T>(),\n        data_desc_,\n        dY.template data<T>(),\n        data_desc_,\n        // Note: strictly speaking, we should be using the input data in this\n        // case, but for the ReLU case we rely on the underlying implementation\n        // that only the output is needed to calculate the Relu gradient. This\n        // will enable us to do memory optimization for in-place relu. To\n        // ensure this is correct, a unit test is provided at\n        // caffe2/python/operator_test/relu_op_test.py\n        Y.template data<T>(),\n        cudnnTypeWrapper<T>::kZero(),\n        data_desc_,\n        dX->template mutable_data<T>()));\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    const auto& Y = Input(0);\n    auto* dX = Output(0);\n    dX->ResizeLike(Y);\n\n    if (Y.IsType<float>()) {\n      return DoRunWithType<float>();\n    } else if (Y.IsType<float16>()) {\n      return DoRunWithType<float16>();\n    } else {\n      LOG(FATAL) << \"Unsupported input types\";\n    }\n    return true;\n  }\n\n protected:\n  CuDNNWrapper cudnn_wrapper_;\n  cudnnTensorDescriptor_t data_desc_;\n  cudnnActivationDescriptor_t activ_desc_;\n  vector<TIndex> cudnn_input_dims_;\n  StorageOrder order_;\n  // Input: Y, dY; Output: dX\n};\n\nnamespace {\nREGISTER_CUDNN_OPERATOR(Relu, CuDNNReluOp);\nREGISTER_CUDNN_OPERATOR(ReluGradient, CuDNNReluGradientOp);\n}  // namespace\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/relu_op_fp16.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common_gpu.h\"\n#ifdef CAFFE_HAS_CUDA_FP16\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/relu_op.h\"\n\nnamespace caffe2 {\nnamespace {\n__global__ void ReluKernelHalf(const int N, const half* X, half* Y) {\n  const half kZero = __float2half(0.0);\n  CUDA_1D_KERNEL_LOOP(i, N) {\n#if __CUDA_ARCH__ >= 530\n    Y[i] = __hgt(X[i], kZero) ? X[i] : kZero;\n#else\n    Y[i] = (__half2float(X[i]) > 0) ? X[i] : kZero;\n#endif\n  }\n}\n\n__global__ void ReluKernelHalf2(const int N, const half2* X, half2* Y) {\n  const half2 kZero = __float2half2_rn(0.0);\n  CUDA_1D_KERNEL_LOOP(i, N) {\n#if __CUDA_ARCH__ >= 530\n    Y[i] = __hmul2(__hgt2(X[i], kZero), X[i]);\n#else\n    float2 xx = __half22float2(X[i]);\n    Y[i] = __floats2half2_rn(xx.x > 0 ? xx.x : 0.f,\n                             xx.y > 0 ? xx.y : 0.f);\n#endif\n  }\n}\n\n__global__ void ReluGradientKernelHalf(\n    const int N, const half* Y, const half* dY, half* dX) {\n  const half kZero = __float2half(0.0);\n  CUDA_1D_KERNEL_LOOP(i, N) {\n#if __CUDA_ARCH__ >= 530\n    dX[i] = __hgt(Y[i], kZero) ? dY[i] : kZero;\n#else\n    dX[i] = (__half2float(Y[i]) > 0) ? dY[i] : kZero;\n#endif\n  }\n}\n}  // namespace\n\ntemplate <>\nbool ReluOp<float16, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE_GT(X.size(), 0);\n  Y->ResizeLike(X);\n  if (X.size() % 2 == 0) {\n    ReluKernelHalf2<<<CAFFE_GET_BLOCKS(X.size() / 2), CAFFE_CUDA_NUM_THREADS,\n                      0, context_.cuda_stream()>>>(\n        X.size() / 2, reinterpret_cast<const half2*>(X.data<float16>()),\n        reinterpret_cast<half2*>(Y->mutable_data<float16>()));\n    return true;\n  } else {\n    ReluKernelHalf<<<CAFFE_GET_BLOCKS(X.size()), CAFFE_CUDA_NUM_THREADS,\n                     0, context_.cuda_stream()>>>(\n        X.size(), reinterpret_cast<const half*>(X.data<float16>()),\n        reinterpret_cast<half*>(Y->mutable_data<float16>()));\n    return true;\n  }\n}\n\ntemplate <>\nbool ReluGradientOp<float16, CUDAContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  CAFFE_ENFORCE_GT(Y.size(), 0);\n  CAFFE_ENFORCE_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n  ReluGradientKernelHalf<<<CAFFE_GET_BLOCKS(Y.size()), CAFFE_CUDA_NUM_THREADS,\n                           0, context_.cuda_stream()>>>(\n      Y.size(), reinterpret_cast<const half*>(Y.data<float16>()),\n      reinterpret_cast<const half*>(dY.data<float16>()),\n      reinterpret_cast<half*>(dX->mutable_data<float16>()));\n  return true;\n}\n\nOPERATOR_SCHEMA(ReluFp16);\nOPERATOR_SCHEMA(ReluFp16Gradient);\n\nREGISTER_CUDA_OPERATOR(ReluFp16, ReluOp<float16, CUDAContext>);\nREGISTER_CUDA_OPERATOR(ReluFp16Gradient, ReluGradientOp<float16, CUDAContext>);\n}  // namespace caffe2\n\n#endif  // CAFFE_HAS_CUDA_FP16\n"
  },
  {
    "path": "caffe2/operators/remove_data_blocks_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/remove_data_blocks_op.h\"\n\nnamespace caffe2 {\nnamespace {\nREGISTER_CPU_OPERATOR(RemoveDataBlocks, RemoveDataBlocksOp<CPUContext>);\n\nOPERATOR_SCHEMA(RemoveDataBlocks)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nShrink the data tensor by removing data blocks with given zero-based indices in\nthe outermost dimension of the tensor. Indices are not assumed in any order or\nunique but with the range [0, blocks_size). Indices could be empty.\n  )DOC\")\n    .Input(0, \"data\", \"a N-D data tensor, N >= 1\")\n    .Input(1, \"indices\", \"zero-based indices of blocks to be removed\")\n    .Output(\n        0,\n        \"shrunk data\",\n        \"data after removing data blocks indexed by 'indices'\");\n\nSHOULD_NOT_DO_GRADIENT(RemoveDataBlocks);\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/remove_data_blocks_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_REMOVE_DATA_BLOCKS_OP_H_\n#define CAFFE2_OPERATORS_REMOVE_DATA_BLOCKS_OP_H_\n\n#include <algorithm>\n#include <vector>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass RemoveDataBlocksOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(RemoveDataBlocksOp);\n  USE_DISPATCH_HELPER;\n\n  bool RunOnDevice() override {\n    if (Input(INDICES).dims()[0] == 0) {\n      Output(0)->CopyFrom(Input(0));\n      return true;\n    } else {\n      return DispatchHelper<TensorTypes<int, long>>::call(this, Input(INDICES));\n    }\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    const auto& data = Input(DATA);\n    const auto& indices = Input(INDICES);\n    CAFFE_ENFORCE(data.ndim() > 0, \"DATA should be at leat 1-D.\");\n    CAFFE_ENFORCE(indices.ndim() == 1, \"INDICES should be 1-D.\");\n\n    const auto outer_size = data.dims()[0];\n    const auto block_size = data.size_from_dim(1);\n    const auto block_size_bytes = block_size * data.meta().itemsize();\n    auto indices_size = indices.dims()[0];\n    const char* data_ptr = (char*)data.raw_data();\n    const auto* ind_ptr = indices.template data<T>();\n\n    std::vector<T> ind_vec;\n    for (int64_t i = 0; i < indices_size; i++) {\n      ind_vec.push_back(ind_ptr[i]);\n    }\n    std::sort(ind_vec.begin(), ind_vec.end());\n    CAFFE_ENFORCE(ind_vec[0] >= 0, \"The min index should be larger than zero.\");\n    CAFFE_ENFORCE(\n        ind_vec[indices_size - 1] < outer_size,\n        \"The max index should be smaller than the data outer size.\");\n    // removes duplicate indices\n    ind_vec.erase(std::unique(ind_vec.begin(), ind_vec.end()), ind_vec.end());\n    indices_size = ind_vec.size();\n\n    auto* output = Output(0);\n    auto shape = data.dims();\n    shape[0] -= indices_size;\n    output->Resize(shape);\n    char* out_ptr = (char*)output->raw_mutable_data(data.meta());\n\n    ind_vec.insert(ind_vec.begin(), -1);\n    int64_t ind_vec_size = ind_vec.size();\n    for (auto i = 0; i < ind_vec_size; i++) {\n      int64_t interval_start = ind_vec[i] + 1;\n      int64_t interval_end =\n          (i == ind_vec_size - 1) ? outer_size : ind_vec[i + 1];\n      auto num_items = interval_end - interval_start;\n      context_.template CopyItems<Context, Context>(\n          data.meta(),\n          num_items * block_size,\n          data_ptr + block_size_bytes * interval_start,\n          out_ptr);\n      out_ptr += block_size_bytes * num_items;\n    }\n\n    return true;\n  }\n\n private:\n  INPUT_TAGS(DATA, INDICES);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_REMOVE_DATA_BLOCKS_OP_H_\n"
  },
  {
    "path": "caffe2/operators/replace_nan_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/replace_nan_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\ntemplate <typename T>\nvoid ReplaceNaNOp<CPUContext>::ReplaceNaN(\n    const T& value,\n    const TIndex size,\n    const T* X,\n    T* Y) {\n  for (TIndex i = 0; i < size; i++) {\n    if (std::isnan(X[i])) {\n      Y[i] = value;\n    } else {\n      Y[i] = X[i];\n    }\n  }\n}\n\nREGISTER_CPU_OPERATOR(ReplaceNaN, ReplaceNaNOp<CPUContext>);\n\nOPERATOR_SCHEMA(ReplaceNaN)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nReplace the NaN (not a number) element in the input tensor with argument `value`\n)DOC\")\n    .Arg(\"value (optional)\", \"the value to replace NaN, the default is 0\")\n    .Input(0, \"input\", \"Input tensor\")\n    .Input(1, \"output\", \"Output tensor\");\n\nSHOULD_NOT_DO_GRADIENT(ReplaceNaN);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/replace_nan_op.cu",
    "content": "#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/replace_nan_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\ntemplate <typename T>\n__global__ void\nreplace_nan_kernel(const T value, const TIndex size, const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, size) {\n    if (isnan(X[i])) {\n      Y[i] = value;\n    } else {\n      Y[i] = X[i];\n    }\n  }\n}\n} // namespace\n\ntemplate <>\ntemplate <typename T>\nvoid ReplaceNaNOp<CUDAContext>::ReplaceNaN(\n    const T& value,\n    const TIndex size,\n    const T* X,\n    T* Y) {\n  replace_nan_kernel<<<\n      CAFFE_GET_BLOCKS(size),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(value, size, X, Y);\n}\nREGISTER_CUDA_OPERATOR(ReplaceNaN, ReplaceNaNOp<CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/replace_nan_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE_OPERATORS_REPLACE_NAN_OP_H_\n#define CAFFE_OPERATORS_REPLACE_NAN_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass ReplaceNaNOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ReplaceNaNOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<float, double>>::call(this, Input(0));\n  }\n\n  template <typename T>\n  void ReplaceNaN(const T& value, const TIndex size, const T* X, T* Y);\n\n  template <typename T>\n  bool DoRunWithType() {\n    T value = OperatorBase::GetSingleArgument<T>(\"value\", 0);\n\n    auto& input = Input(0);\n    auto* output = Output(0);\n    output->ResizeLike(input);\n\n    const T* input_data = input.template data<T>();\n    T* output_data = output->template mutable_data<T>();\n\n    ReplaceNaN<T>(value, input.size(), input_data, output_data);\n\n    return true;\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE_OPERATORS_REPLACE_NAN_OP_H_\n"
  },
  {
    "path": "caffe2/operators/reservoir_sampling.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <memory>\n#include <string>\n#include <vector>\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/operators/map_ops.h\"\n\nnamespace caffe2 {\nnamespace {\n\ntemplate <class Context>\nclass ReservoirSamplingOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ReservoirSamplingOp(const OperatorDef operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        numToCollect_(\n            OperatorBase::GetSingleArgument<int>(\"num_to_collect\", -1)) {\n    CAFFE_ENFORCE(numToCollect_ > 0);\n  }\n\n  bool RunOnDevice() override {\n    auto& mutex = OperatorBase::Input<std::unique_ptr<std::mutex>>(MUTEX);\n    std::lock_guard<std::mutex> guard(*mutex);\n\n    auto* output = Output(RESERVOIR);\n    const auto& input = Input(DATA);\n\n    CAFFE_ENFORCE_GE(input.ndim(), 1);\n\n    bool output_initialized = output->size() > 0 &&\n        (static_cast<std::shared_ptr<std::vector<TensorCPU>>*>(\n             output->raw_mutable_data(input.meta()))[0] != nullptr);\n\n    if (output_initialized) {\n      CAFFE_ENFORCE_EQ(output->ndim(), input.ndim());\n      for (size_t i = 1; i < input.ndim(); ++i) {\n        CAFFE_ENFORCE_EQ(output->dim(i), input.dim(i));\n      }\n    }\n\n    auto dims = input.dims();\n    auto num_entries = dims[0];\n\n    dims[0] = numToCollect_;\n    // IMPORTANT: Force the output to have the right type before reserving,\n    // so that the output gets the right capacity\n    output->raw_mutable_data(input.meta());\n    output->Reserve(dims, &context_);\n\n    auto* pos_to_object =\n        OutputSize() > POS_TO_OBJECT ? Output(POS_TO_OBJECT) : nullptr;\n    if (pos_to_object) {\n      pos_to_object->Reserve(std::vector<TIndex>{numToCollect_}, &context_);\n    }\n\n    if (num_entries == 0) {\n      if (!output_initialized) {\n        // Get both shape and meta\n        output->CopyFrom(input, &context_);\n      }\n      return true;\n    }\n\n    const int64_t* object_id_data = nullptr;\n    std::set<int64_t> unique_object_ids;\n    if (InputSize() > OBJECT_ID) {\n      const auto& object_id = Input(OBJECT_ID);\n      CAFFE_ENFORCE_EQ(object_id.ndim(), 1);\n      CAFFE_ENFORCE_EQ(object_id.size(), num_entries);\n      object_id_data = object_id.template data<int64_t>();\n      unique_object_ids.insert(\n          object_id_data, object_id_data + object_id.size());\n    }\n\n    const auto num_new_entries = countNewEntries(unique_object_ids);\n    auto num_to_copy = std::min<int32_t>(num_new_entries, numToCollect_);\n    auto output_batch_size = output_initialized ? output->dim(0) : 0;\n    dims[0] = std::min<size_t>(numToCollect_, output_batch_size + num_to_copy);\n    if (output_batch_size < numToCollect_) {\n      output->Resize(dims);\n      if (pos_to_object) {\n        pos_to_object->Resize(dims[0]);\n      }\n    }\n    auto* output_data =\n        static_cast<char*>(output->raw_mutable_data(input.meta()));\n    auto* pos_to_object_data = pos_to_object\n        ? pos_to_object->template mutable_data<int64_t>()\n        : nullptr;\n\n    auto block_size = input.size_from_dim(1);\n    auto block_bytesize = block_size * input.itemsize();\n    const auto* input_data = static_cast<const char*>(input.raw_data());\n\n    auto* num_visited_tensor = Output(NUM_VISITED);\n    CAFFE_ENFORCE_EQ(1, num_visited_tensor->size());\n    auto* num_visited = num_visited_tensor->template mutable_data<int64_t>();\n    if (!output_initialized) {\n      *num_visited = 0;\n    }\n    CAFFE_ENFORCE_GE(*num_visited, 0);\n\n    const auto start_num_visited = *num_visited;\n\n    auto* object_to_pos_map = OutputSize() > OBJECT_TO_POS_MAP\n        ? OperatorBase::Output<MapType64To32>(OBJECT_TO_POS_MAP)\n        : nullptr;\n\n    std::set<int64_t> eligible_object_ids;\n    if (object_to_pos_map) {\n      for (auto oid : unique_object_ids) {\n        if (!object_to_pos_map->count(oid)) {\n          eligible_object_ids.insert(oid);\n        }\n      }\n    }\n\n    for (int i = 0; i < num_entries; ++i) {\n      if (object_id_data && object_to_pos_map &&\n          !eligible_object_ids.count(object_id_data[i])) {\n        // Already in the pool or processed\n        continue;\n      }\n      if (object_id_data) {\n        eligible_object_ids.erase(object_id_data[i]);\n      }\n      int64_t pos = -1;\n      if (*num_visited < numToCollect_) {\n        // append\n        pos = *num_visited;\n      } else {\n        auto& gen = context_.RandGenerator();\n        // uniform between [0, num_visited]\n        std::uniform_int_distribution<int64_t> uniformDist(0, *num_visited);\n        pos = uniformDist(gen);\n        if (pos >= numToCollect_) {\n          // discard\n          pos = -1;\n        }\n      }\n\n      if (pos < 0) {\n        // discard\n        CAFFE_ENFORCE_GE(*num_visited, numToCollect_);\n      } else {\n        // replace\n        context_.template CopyItems<Context, Context>(\n            input.meta(),\n            block_size,\n            input_data + i * block_bytesize,\n            output_data + pos * block_bytesize);\n\n        if (object_id_data && pos_to_object_data && object_to_pos_map) {\n          auto old_oid = pos_to_object_data[pos];\n          auto new_oid = object_id_data[i];\n          pos_to_object_data[pos] = new_oid;\n          object_to_pos_map->erase(old_oid);\n          object_to_pos_map->emplace(new_oid, pos);\n        }\n      }\n\n      ++(*num_visited);\n    }\n    // Sanity check\n    CAFFE_ENFORCE_EQ(*num_visited, start_num_visited + num_new_entries);\n    return true;\n  }\n\n private:\n  // number of tensors to collect\n  int numToCollect_;\n\n  INPUT_TAGS(\n      RESERVOIR_IN,\n      NUM_VISITED_IN,\n      DATA,\n      MUTEX,\n      OBJECT_ID,\n      OBJECT_TO_POS_MAP_IN,\n      POS_TO_OBJECT_IN);\n  OUTPUT_TAGS(RESERVOIR, NUM_VISITED, OBJECT_TO_POS_MAP, POS_TO_OBJECT);\n\n  int32_t countNewEntries(const std::set<int64_t>& unique_object_ids) {\n    const auto& input = Input(DATA);\n    if (InputSize() <= OBJECT_ID) {\n      return input.dim(0);\n    }\n    const auto& object_to_pos_map =\n        OperatorBase::Input<MapType64To32>(OBJECT_TO_POS_MAP_IN);\n    return std::count_if(\n        unique_object_ids.begin(),\n        unique_object_ids.end(),\n        [&object_to_pos_map](int64_t oid) {\n          return !object_to_pos_map.count(oid);\n        });\n  }\n};\n\nREGISTER_CPU_OPERATOR(ReservoirSampling, ReservoirSamplingOp<CPUContext>);\n\nOPERATOR_SCHEMA(ReservoirSampling)\n    .NumInputs({4, 7})\n    .NumOutputs({2, 4})\n    .NumInputsOutputs([](int in, int out) { return in / 3 == out / 2; })\n    .EnforceInplace({{0, 0}, {1, 1}, {5, 2}, {6, 3}})\n    .SetDoc(R\"DOC(\nCollect `DATA` tensor into `RESERVOIR` of size `num_to_collect`. `DATA` is\nassumed to be a batch.\n\nIn case where 'objects' may be repeated in data and you only want at most one\ninstance of each 'object' in the reservoir, `OBJECT_ID` can be given for\ndeduplication. If `OBJECT_ID` is given, then you also need to supply additional\nbook-keeping tensors. See input blob documentation for details.\n\nThis operator is thread-safe.\n)DOC\")\n    .Arg(\n        \"num_to_collect\",\n        \"The number of random samples to append for each positive samples\")\n    .Input(\n        0,\n        \"RESERVOIR\",\n        \"The reservoir; should be initialized to empty tensor\")\n    .Input(\n        1,\n        \"NUM_VISITED\",\n        \"Number of examples seen so far; should be initialized to 0\")\n    .Input(\n        2,\n        \"DATA\",\n        \"Tensor to collect from. The first dimension is assumed to be batch \"\n        \"size. If the object to be collected is represented by multiple \"\n        \"tensors, use `PackRecords` to pack them into single tensor.\")\n    .Input(3, \"MUTEX\", \"Mutex to prevent data race\")\n    .Input(\n        4,\n        \"OBJECT_ID\",\n        \"(Optional, int64) If provided, used for deduplicating object in the \"\n        \"reservoir\")\n    .Input(\n        5,\n        \"OBJECT_TO_POS_MAP_IN\",\n        \"(Optional) Auxillary bookkeeping map. This should be created from \"\n        \" `CreateMap` with keys of type int64 and values of type int32\")\n    .Input(\n        6,\n        \"POS_TO_OBJECT_IN\",\n        \"(Optional) Tensor of type int64 used for bookkeeping in deduplication\")\n    .Output(0, \"RESERVOIR\", \"Same as the input\")\n    .Output(1, \"NUM_VISITED\", \"Same as the input\")\n    .Output(2, \"OBJECT_TO_POS_MAP\", \"(Optional) Same as the input\")\n    .Output(3, \"POS_TO_OBJECT\", \"(Optional) Same as the input\");\n\nSHOULD_NOT_DO_GRADIENT(ReservoirSampling);\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/reshape_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/reshape_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Reshape, ReshapeOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Reshape)\n    .NumInputs(1, 2)\n    .NumOutputs(2)\n    .TensorInferenceFunction(\n        [](const OperatorDef& def, const vector<TensorShape>& in) {\n          vector<TensorShape> out(2);\n\n          // Do shape inference for old_shape\n          out[1].set_data_type(TensorProto::INT64);\n          out[1].add_dims(in[0].dims_size());\n\n          ArgumentHelper helper(def);\n          if (!helper.HasArgument(\"shape\")) {\n            // Cannot do shape inference for reshaped tensor from runtime data.\n            CAFFE_ENFORCE_EQ(\n                in.size(),\n                2,\n                \"New shape must be specified by either the input blob or the \"\n                \"argument `shape`.\");\n            out[0].set_unknown_shape(true);\n            return out;\n          }\n          CAFFE_ENFORCE_EQ(\n              in.size(),\n              1,\n              \"New shape must not be specified by the input blob and the \"\n              \"argument `shape` at the same time.\");\n\n          // Infer the actual new shape\n          auto actualNewShape = helper.GetRepeatedArgument<int64_t>(\"shape\");\n\n          // Copy over the dimensions for those that are specified zero\n          // and check the eligibility of input\n          for (int i = 0; i < actualNewShape.size(); ++i) {\n            CAFFE_ENFORCE_GE(\n                actualNewShape[i],\n                -1,\n                \"The dimensions in argument `shape` \"\n                \"must not be a negative number.\");\n\n            if (actualNewShape[i] == 0) {\n              CAFFE_ENFORCE_LT(\n                  i,\n                  in[0].dims_size(),\n                  \"Argument `shape` has a dimension set to zero that exceeds \"\n                  \"the original dimension size.\");\n              actualNewShape[i] = in[0].dims(i);\n            }\n          }\n\n          // Check if the new shape is valid and fills in the missing dimension\n          // specified by -1.\n          int64_t totalSize = 1;\n          for (const auto d : in[0].dims()) {\n            totalSize *= d;\n          }\n          int64_t size = 1;\n          int unknownIdx = -1;\n          for (int i = 0; i < actualNewShape.size(); ++i) {\n            const auto dim = actualNewShape[i];\n            if (dim == -1) {\n              CAFFE_ENFORCE(\n                  unknownIdx == -1,\n                  \"Argument `shape` has more than one missing dimension.\");\n              unknownIdx = i;\n            } else {\n              size *= dim;\n            }\n          }\n\n          if (unknownIdx != -1) {\n            CAFFE_ENFORCE(\n                totalSize % size == 0,\n                \"Argument `shape` does not agree with the input data.\",\n                \" (\",\n                totalSize,\n                \" vs \",\n                size,\n                \")\");\n            actualNewShape[unknownIdx] = totalSize / size;\n          } else {\n            CAFFE_ENFORCE_EQ(\n                totalSize,\n                size,\n                \"Argument `shape` does not agree with the input data.\",\n                \" (\",\n                totalSize,\n                \" != \",\n                size,\n                \")\");\n          }\n\n          out[0].set_data_type(in[0].data_type());\n          for (const auto d : actualNewShape) {\n            out[0].add_dims(d);\n          }\n          return out;\n        })\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nReshape the input tensor similar to numpy.reshape.\n\nIt takes a tensor as input and an optional tensor specifying the new shape.\nWhen the second input is absent, an extra argument `shape` must be specified.\nIt outputs the reshaped tensor as well as the original shape.\n\nAt most one dimension of the new shape can be -1. In this case, the value is\ninferred from the size of the tensor and the remaining dimensions. A dimension\ncould also be 0, in which case the actual dimension value is going to be copied\nfrom the input tensor.\n)DOC\")\n    .Arg(\"shape\", \"New shape\")\n    .Input(0, \"data\", \"An input tensor.\")\n    .Input(1, \"new_shape\", \"New shape.\")\n    .Output(0, \"reshaped\", \"Reshaped data.\")\n    .Output(1, \"old_shape\", \"Original shape.\");\n\nclass GetReshapeGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"Reshape\",\n        \"\",\n        vector<string>{GO(0), O(1)},\n        vector<string>{GI(0), \"_\" + GI(0) + \"_dims\"});\n  }\n\n  // Argument `shape` is no longer needed in backprop.\n  bool CopyArguments() const override {\n    return false;\n  }\n};\n\nREGISTER_GRADIENT(Reshape, GetReshapeGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/reshape_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_RESHAPE_OP_H_\n#define CAFFE2_OPERATORS_RESHAPE_OP_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n// Takes a shape and data tensor and reshapes it\ntemplate <typename F, class Context>\nclass ReshapeOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ReshapeOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        new_shape_(OperatorBase::GetRepeatedArgument<int64_t>(\"shape\")) {}\n\n  bool RunOnDevice() override {\n    if (InputSize() == 2) {\n      return DispatchHelper<TensorTypes<int, int64_t>>::call(this, Input(1));\n    }\n    CAFFE_ENFORCE(\n        OperatorBase::HasArgument(\"shape\"), \"Argument `shape` is missing.\");\n    return this->template DoRunWithType<int64_t>();\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    DoRunWithTypeImpl<T>(Input(0), Output(0));\n    return true;\n  }\n\n protected:\n  template <typename T>\n  void DoRunWithTypeImpl(\n      const Tensor<Context>& input,\n      Tensor<Context>* output) {\n    vector<int64_t> actual_new_shape = new_shape_;\n    if (InputSize() == 2) {\n      CAFFE_ENFORCE(\n          !OperatorBase::HasArgument(\"shape\"),\n          \"New shape is specified by the input blob, do not pass in \"\n          \"the argument `shape`.\");\n\n      auto& shape = Input(1);\n      CAFFE_ENFORCE(shape.ndim() == 1, \"Shape should be 1-D\");\n\n      const T* shape_data = shape.template data<T>();\n\n      // Bit awkward, but needed so works on both CPU and CUDA contexts\n      std::vector<T> tmpv(shape.size());\n      context_.template CopyBytes<Context, CPUContext>(\n          shape.size() * sizeof(T), shape_data, &tmpv[0]);\n      actual_new_shape.assign(tmpv.begin(), tmpv.begin() + shape.size());\n    }\n\n    // Copy over the dimensions for those that are specified zero.\n    for (int i = 0; i < actual_new_shape.size(); ++i) {\n      if (actual_new_shape[i] == 0) {\n        actual_new_shape[i] = input.dim(i);\n      }\n    }\n\n    // Checks if the new shape is valid and fills in the missing dimension\n    // specified by -1.\n    // NOTE: At most one dimension can be -1.\n    auto total_size = input.size_from_dim(0);\n    T size = 1;\n    int unknown_idx = -1;\n    for (int i = 0; i < actual_new_shape.size(); ++i) {\n      const auto dim = actual_new_shape[i];\n      if (dim == -1) {\n        CAFFE_ENFORCE(\n            unknown_idx == -1,\n            \"Argument `shape` has more than one missing dimension.\");\n        unknown_idx = i;\n      } else {\n        size *= dim;\n      }\n    }\n\n    if (unknown_idx != -1) {\n      CAFFE_ENFORCE(\n          total_size % size == 0,\n          \"Argument `shape` does not agree with the input data.\",\n          \" (\",\n          total_size,\n          \" vs \",\n          size,\n          \")\");\n      actual_new_shape[unknown_idx] = total_size / size;\n    } else {\n      CAFFE_ENFORCE_EQ(\n          total_size,\n          size,\n          \"Argument `shape` does not agree with the input data.\",\n          \" (\",\n          total_size,\n          \" != \",\n          size,\n          \")\");\n    }\n\n    // Write the original shape to the second output.\n    auto* old_shape = Output(1);\n    old_shape->Resize(input.ndim());\n    T* old_shape_data = old_shape->template mutable_data<T>();\n    for (int i = 0; i < input.ndim(); ++i) {\n      math::Set<T, Context>(1, input.dim(i), old_shape_data + i, &context_);\n    }\n\n    output->Resize(actual_new_shape);\n    if (output != &input) {\n      // If we are not doing in-place computation, a copy is needed.\n      context_.template CopyItems<Context, Context>(\n          input.meta(),\n          input.size(),\n          input.raw_data(),\n          output->raw_mutable_data(input.meta()));\n    }\n  }\n\n private:\n  vector<int64_t> new_shape_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_RESHAPE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/reshape_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/reshape_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(Reshape, ReshapeOp<float, CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/reshape_op_gpu_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n\n#include <gtest/gtest.h>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/operators/reshape_op.h\"\n#include \"caffe2/utils/math.h\"\n\nCAFFE2_DECLARE_string(caffe_test_root);\n\nnamespace caffe2 {\n\nstatic void AddConstInput(\n    const vector<TIndex>& shape,\n    const float value,\n    const string& name,\n    Workspace* ws) {\n  DeviceOption option;\n  option.set_device_type(CUDA);\n  CUDAContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<Tensor<CUDAContext>>();\n  tensor->Resize(shape);\n  math::Set<float, CUDAContext>(\n      tensor->size(), value, tensor->mutable_data<float>(), &context);\n  return;\n}\n\nTEST(ReshapeOpGPUTest, testReshapeWithScalar) {\n  if (!HasCudaGPU())\n    return;\n  Workspace ws;\n  OperatorDef def;\n  def.set_name(\"test_reshape\");\n  def.set_type(\"Reshape\");\n  def.add_input(\"X\");\n  def.add_output(\"XNew\");\n  def.add_output(\"OldShape\");\n  def.add_arg()->CopyFrom(MakeArgument(\"shape\", vector<int64_t>{1}));\n  def.mutable_device_option()->set_device_type(CUDA);\n  AddConstInput(vector<TIndex>(), 3.14, \"X\", &ws);\n  // execute the op\n  unique_ptr<OperatorBase> op(CreateOperator(def, &ws));\n  EXPECT_TRUE(op->Run());\n  Blob* XNew = ws.GetBlob(\"XNew\");\n  const Tensor<CUDAContext>& XNewTensor = XNew->Get<Tensor<CUDAContext>>();\n  EXPECT_EQ(1, XNewTensor.ndim());\n  EXPECT_EQ(1, XNewTensor.size());\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/resize_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/resize_op.h\"\n\n#include \"caffe2/utils/cpu_neon.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nvoid resizeNearest2x(\n    int batch_size,\n    int num_channels,\n    int input_height,\n    int input_width,\n    const float* input,\n    float* output) {\n  const int output_height = input_height * 2;\n  const int output_width = input_width * 2;\n  for (int n = 0; n < batch_size; ++n) {\n    for (int c = 0; c < num_channels; ++c) {\n      for (int y = 0; y < output_height; ++y) {\n        const int in_y = y / 2;\n\n#ifdef __ARM_NEON__\n        int vecW = (input_width / 4) * 4; // round down\n        int x = 0;\n        for (; x < vecW; x += 4) {\n          // load 0 1 2 3\n          float32x4_t v = vld1q_f32(input + in_y * input_width + x);\n          const int oidx = output_width * y + x * 2;\n          float32x4x2_t v2 = {{v, v}};\n          // store 00 11 22 33\n          vst2q_f32(output + oidx + 0, v2);\n        }\n\n        // handle remainder\n        for (; x < input_width; ++x) {\n          const float v = input[in_y * input_width + x];\n          const int oidx = output_width * y + x * 2;\n          output[oidx + 0] = v;\n          output[oidx + 1] = v;\n        }\n#else\n        for (int x = 0; x < input_width; ++x) {\n          const float v = input[in_y * input_width + x];\n          const int oidx = output_width * y + x * 2;\n          output[oidx + 0] = v;\n          output[oidx + 1] = v;\n        }\n#endif\n      }\n      input += input_height * input_width;\n      output += output_height * output_width;\n    }\n  }\n}\n\ntemplate <>\nbool ResizeNearestOp<float, CPUContext>::RunOnDevice() {\n  const auto& X = Input(0);\n  auto* Y = Output(0);\n\n  const int batch_size = X.dim32(0),\n            num_channels = X.dim32(1),\n            input_height = X.dim32(2),\n            input_width = X.dim32(3);\n  int output_width = input_width * width_scale_;\n  int output_height = input_height * height_scale_;\n  Y->Resize(batch_size, num_channels, output_height, output_width);\n\n  const float* Xdata = X.data<float>();\n  float* Ydata = Y->mutable_data<float>();\n\n  // Specialized implementation for fast 2x upsampling\n  if (width_scale_ == 2.0 && height_scale_ == 2.0) {\n    resizeNearest2x(\n        batch_size, num_channels, input_height, input_width, Xdata, Ydata);\n    return true;\n  }\n\n  for (int n = 0; n < batch_size; ++n) {\n    for (int c = 0; c < num_channels; ++c) {\n      for (int y = 0; y < output_height; ++y) {\n        const int in_y = std::min((int)(y / height_scale_), (input_height - 1));\n        for (int x = 0; x < output_width; ++x) {\n          const int in_x = std::min((int)(x / width_scale_), (input_width - 1));\n          Ydata[output_width * y + x] = Xdata[input_width * in_y + in_x];\n        }\n      }\n      Xdata += input_height * input_width;\n      Ydata += output_width * output_height;\n    }\n  }\n\n  return true;\n}\n\ntemplate <>\nbool ResizeNearestGradientOp<float, CPUContext>::RunOnDevice() {\n  const auto& dY = Input(0);\n  const auto& X = Input(1);\n  auto* dX = Output(0);\n\n  const auto& inputDims = dY.dims();\n  CAFFE_ENFORCE_EQ(4, inputDims.size());\n  const int batch_size = dY.dim32(0),\n            num_channels = dY.dim32(1),\n            input_height = dY.dim32(2),\n            input_width = dY.dim32(3);\n  const int output_height = X.dim32(2);\n  const int output_width = X.dim32(3);\n  dX->Resize(batch_size, num_channels, output_height, output_width);\n  math::Set<float, CPUContext>(dX->size(),\n                               0.0f,\n                               dX->mutable_data<float>(),\n                               &context_);\n\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n\n  for (int n = 0; n < batch_size; ++n) {\n    for (int c = 0; c < num_channels; ++c) {\n      for (int y = 0; y < input_height; ++y) {\n        const int out_y = std::min((int)(y / height_scale_),\n                                   (output_height - 1));\n        for (int x = 0; x < input_width; ++x) {\n          const int out_x = std::min((int)(x / width_scale_),\n                                     (output_width - 1));\n          dXdata[output_width * out_y + out_x] += dYdata[input_width * y + x];\n        }\n      }\n      dYdata += input_height * input_width;\n      dXdata += output_height * output_width;\n    }\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(ResizeNearest, ResizeNearestOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(ResizeNearestGradient,\n                      ResizeNearestGradientOp<float, CPUContext>);\n\n// Input: X, output: Y\nOPERATOR_SCHEMA(ResizeNearest)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .Arg(\"width_scale\", \"Scale along width dimension\")\n    .Arg(\"height_scale\", \"Scale along height dimension\")\n    .SetDoc(R\"DOC(\nResizes the spatial dimensions of the input using nearest neighbor\ninterpolation. The `width_scale` and `height_scale` arguments\ncontrol the size of the output, which is given by:\noutput_width = floor(input_width * width_scale)\noutput_height = floor(output_height * height_scale)\n)DOC\")\n    .Input(0, \"X\", \"Input tensor\")\n    .Output(0, \"Y\", \"Output tensor\");\n\n// Input: dY, output: dX\nOPERATOR_SCHEMA(ResizeNearestGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .Arg(\"width_scale\", \"Scale along width dimension\")\n    .Arg(\"height_scale\", \"Scale along height dimension\");\n\nclass GetResizeNearestGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\"ResizeNearestGradient\",\n                             \"\",\n                             vector<string>{GO(0), I(0)},\n                             vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(ResizeNearest, GetResizeNearestGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/resize_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/utils/math.h\"\n#include \"resize_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n__global__ void NearestNeighborKernel(\n    const int size,\n    const int num_channels,\n    const int input_height,\n    const int input_width,\n    const int output_height,\n    const int output_width,\n    const float height_scale,\n    const float width_scale,\n    const float* X,\n    float* Y) {\n  CUDA_1D_KERNEL_LOOP(index, size) {\n\n    int indexTemp = index;\n    const int w = indexTemp % output_width;\n    indexTemp /= output_width;\n    const int h = indexTemp % output_height;\n    indexTemp /= output_height;\n    const int c = indexTemp % num_channels;\n    indexTemp /= num_channels;\n    const int n = indexTemp;\n\n    const int in_y = fminf(h / height_scale, input_height - 1);\n    const int in_x = fminf(w / width_scale, input_width - 1);\n    Y[index] =\n        X[((n * num_channels + c) * input_height + in_y) * input_width + in_x];\n  }\n}\n\n__global__ void NearestNeighborGradientKernel(\n    const int size,\n    const int num_channels,\n    const int input_height,\n    const int input_width,\n    const int output_height,\n    const int output_width,\n    const float height_scale,\n    const float width_scale,\n    const float* dY,\n    float* dX) {\n  CUDA_1D_KERNEL_LOOP(index, size) {\n    int indexTemp = index;\n    const int x = indexTemp % input_width;\n    indexTemp /= input_width;\n    const int y = indexTemp % input_height;\n    indexTemp /= input_height;\n    const int c = indexTemp % num_channels;\n    indexTemp /= num_channels;\n    const int n = indexTemp;\n\n    const int out_y = fminf(y / height_scale, output_height - 1);\n    const int out_x = fminf(x / width_scale, output_width - 1);\n    const int out_index =\n        ((n * num_channels + c) * output_height + out_y) * output_width + out_x;\n#if __CUDA_ARCH__ >= 350\n    atomicAdd(dX + out_index, __ldg(dY + index));\n#else\n    atomicAdd(dX + out_index, *(dY + index));\n#endif\n  }\n}\n\n} // namespace\n\ntemplate <>\nbool ResizeNearestOp<float, CUDAContext>::RunOnDevice() {\n  const auto& X = Input(0);\n  auto* Y = Output(0);\n\n  const auto& inputDims = X.dims();\n  CAFFE_ENFORCE_EQ(4, inputDims.size());\n  const int batch_size = X.dim32(0), num_channels = X.dim32(1),\n            input_height = X.dim32(2), input_width = X.dim32(3);\n  int output_width = input_width * width_scale_;\n  int output_height = input_height * height_scale_;\n  Y->Resize(batch_size, num_channels, output_height, output_width);\n\n  const auto size = Y->size();\n  NearestNeighborKernel<<<\n      CAFFE_GET_BLOCKS(size),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      size,\n      num_channels,\n      input_height,\n      input_width,\n      output_height,\n      output_width,\n      height_scale_,\n      width_scale_,\n      X.data<float>(),\n      Y->mutable_data<float>());\n\n  return true;\n}\n\ntemplate <>\nbool ResizeNearestGradientOp<float, CUDAContext>::RunOnDevice() {\n  const auto& dY = Input(0);\n  const auto& X = Input(1);\n  auto* dX = Output(0);\n\n  const auto& inputDims = dY.dims();\n  CAFFE_ENFORCE_EQ(4, inputDims.size());\n  const int batch_size = dY.dim32(0), num_channels = dY.dim32(1),\n            input_height = dY.dim32(2), input_width = dY.dim32(3);\n  int output_height = X.dim32(2);\n  int output_width = X.dim32(3);\n  dX->Resize(batch_size, num_channels, output_height, output_width);\n  math::Set<float, CUDAContext>(\n      dX->size(), 0.0f, dX->mutable_data<float>(), &context_);\n\n  const auto size = dY.size();\n  NearestNeighborGradientKernel<<<\n      CAFFE_GET_BLOCKS(size),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      size,\n      num_channels,\n      input_height,\n      input_width,\n      output_height,\n      output_width,\n      height_scale_,\n      width_scale_,\n      dY.data<float>(),\n      dX->mutable_data<float>());\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(ResizeNearest, ResizeNearestOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    ResizeNearestGradient,\n    ResizeNearestGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/resize_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass ResizeNearestOp final : public Operator<Context> {\n public:\n  ResizeNearestOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws), width_scale_(1), height_scale_(1) {\n    if (HasArgument(\"width_scale\")) {\n      width_scale_ = static_cast<T>(\n          OperatorBase::GetSingleArgument<float>(\"width_scale\", 1));\n    }\n    if (HasArgument(\"height_scale\")) {\n      height_scale_ = static_cast<T>(\n          OperatorBase::GetSingleArgument<float>(\"height_scale\", 1));\n    }\n    CAFFE_ENFORCE_GT(width_scale_, 0);\n    CAFFE_ENFORCE_GT(height_scale_, 0);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  T width_scale_;\n  T height_scale_;\n};\n\ntemplate <typename T, class Context>\nclass ResizeNearestGradientOp final : public Operator<Context> {\n public:\n  ResizeNearestGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws), width_scale_(1), height_scale_(1) {\n    width_scale_ = static_cast<T>(\n        OperatorBase::GetSingleArgument<float>(\"width_scale\", 1));\n    height_scale_ = static_cast<T>(\n        OperatorBase::GetSingleArgument<float>(\"height_scale\", 1));\n    CAFFE_ENFORCE_GT(width_scale_, 0);\n    CAFFE_ENFORCE_GT(height_scale_, 0);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  T width_scale_;\n  T height_scale_;\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/reverse_packed_segs_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/reverse_packed_segs_op.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(ReversePackedSegs, ReversePackedSegsOp<CPUContext>);\n\nOPERATOR_SCHEMA(ReversePackedSegs)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nReverse segments in a 3-D tensor (lengths, segments, embeddings,), leaving\npaddings unchanged. This operator is used to reverse input of a recurrent neural\nnetwork to make it a BRNN.\n  )DOC\")\n    .Input(0, \"data\", \"a 3-D (lengths, segments, embeddings,) tensor.\")\n    .Input(1, \"lengths\", \"length of each segment.\")\n    .Output(\n        0,\n        \"reversed data\",\n        \"a (lengths, segments, embeddings,) tensor with each segment reversed\"\n        \"and paddings unchanged.\");\n\nclass GetReversePackedSegsGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"ReversePackedSegs\",\n        \"\",\n        vector<string>{GO(0), I(1)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(ReversePackedSegs, GetReversePackedSegsGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/reverse_packed_segs_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"reverse_packed_segs_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <typename T, typename LengthType>\n__global__\nvoid ReversePackedSegments_kernel(\n      size_t max_length,\n      size_t batch_size,\n      size_t block_size,\n      const LengthType* lengths_ptr,\n      const T* data_ptr,\n      T* rev_data_ptr) {\n\n  const int block_id = blockIdx.x;\n\n  // index into [0, batch_size)\n  const int batch = block_id / max_length;\n  // index into [0, segment)\n  const int segment = block_id % max_length;\n\n  if (batch >= batch_size || segment >= max_length) return;\n\n  const int seg_length = lengths_ptr[batch];\n\n  // unique data pointer for this CTA\n  const T* local_data_ptr = data_ptr + (segment * batch_size + batch) * block_size;\n\n  // unique pointer for result\n  T* local_rev_data_ptr;\n  if (segment < seg_length) {\n    local_rev_data_ptr = rev_data_ptr + ((seg_length - 1 - segment) * batch_size + batch) * block_size;\n  } else {\n    local_rev_data_ptr = rev_data_ptr + (segment * batch_size + batch) * block_size;\n  }\n\n  // copy using 1 element / thread for now\n  for (int idx = threadIdx.x; idx < block_size; idx+=blockDim.x) {\n    local_rev_data_ptr[idx] = local_data_ptr[idx];\n  }\n}\n\n} // namespace\n\n// specialization of DoRunWithLengthType\ntemplate <>\ntemplate <typename T, typename LengthType>\nvoid ReversePackedSegsOp<CUDAContext>::DoRunWithLengthType() {\n  const auto& data = Input(DATA);\n  const auto& lengths = Input(LENGTHS);\n\n  CAFFE_ENFORCE(\n      data.ndim() == 3,\n      \"DATA should be 3-D tensor <lengths, \"\n      \"segments, embeddings>\");\n  CAFFE_ENFORCE(lengths.ndim() == 1, \"LENGTH should be 1-D\");\n\n  auto* output = Output(0);\n  const auto& shape = data.dims();\n  output->Resize(shape);\n\n  const auto& max_length = data.dims()[0];\n  const auto& batch_size = data.dims()[1];\n  const auto& block_size = data.dims()[2];\n  CAFFE_ENFORCE(\n      lengths.dims()[0] == batch_size,\n      \"lenths size should be\"\n      \" equal to batch size\");\n\n  const T* data_ptr = data.template data<T>();\n  const LengthType* lengths_ptr = lengths.template data<LengthType>();\n\n  // reversed data\n  T* rev_data_ptr = output->template mutable_data<T>();\n\n  const int grid = max_length * batch_size;\n\n  ReversePackedSegments_kernel<T,LengthType><<<grid, 512, 0, context_.cuda_stream()>>>(\n        max_length,\n        batch_size,\n        block_size,\n        lengths_ptr,\n        data_ptr,\n        rev_data_ptr);\n}\n\nREGISTER_CUDA_OPERATOR(ReversePackedSegs, ReversePackedSegsOp<CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/reverse_packed_segs_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_REVERSE_PACKED_SEGS_OP_H_\n#define CAFFE2_OPERATORS_REVERSE_PACKED_SEGS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass ReversePackedSegsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(ReversePackedSegsOp);\n  USE_DISPATCH_HELPER;\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<float, double, int, long, bool>>::call(\n        this, Input(DATA));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    if (Input(LENGTHS).template IsType<int>()) {\n      DoRunWithLengthType<T, int>();\n    } else {\n      DoRunWithLengthType<T, long>();\n    }\n    return true;\n  }\n\n private:\n  INPUT_TAGS(DATA, LENGTHS);\n\n  template <typename T, typename LengthType>\n  void DoRunWithLengthType() {\n    const auto& data = Input(DATA);\n    const auto& lengths = Input(LENGTHS);\n\n    CAFFE_ENFORCE(\n        data.ndim() == 3,\n        \"DATA should be 3-D tensor <lengths, \"\n        \"segments, embeddings>\");\n    CAFFE_ENFORCE(lengths.ndim() == 1, \"LENGTH should be 1-D\");\n\n    auto* output = Output(0);\n    const auto& shape = data.dims();\n    output->Resize(shape);\n\n    const auto& max_length = data.dims()[0];\n    const auto& batch_size = data.dims()[1];\n    const auto& block_size = data.dims()[2];\n    CAFFE_ENFORCE(\n        lengths.dims()[0] == batch_size,\n        \"lenths size should be\"\n        \" equal to batch size\");\n\n    const T* data_ptr = data.template data<T>();\n    const LengthType* lengths_ptr = lengths.template data<LengthType>();\n\n    vector<LengthType> lengths_host(batch_size);\n    context_.template Copy<LengthType, Context, CPUContext>(\n        batch_size, lengths_ptr, &lengths_host[0]);\n    context_.FinishDeviceComputation();\n\n    T* rev_data_ptr = output->template mutable_data<T>();\n    for (TIndex i = 0; i < batch_size; i++) {\n      const auto& seg_length = lengths_host[i];\n      CAFFE_ENFORCE_LE(seg_length, max_length);\n      TIndex j = 0;\n      for (; j < seg_length; j++) {\n        const T* data_block_ptr = data_ptr + (j * batch_size + i) * block_size;\n        T* rev_data_block_ptr =\n            rev_data_ptr + ((seg_length - 1 - j) * batch_size + i) * block_size;\n        context_.template Copy<T, Context, Context>(\n            block_size, data_block_ptr, rev_data_block_ptr);\n      }\n      for (; j < max_length; j++) {\n        const T* data_block_ptr = data_ptr + (j * batch_size + i) * block_size;\n        T* rev_data_block_ptr =\n            rev_data_ptr + (j * batch_size + i) * block_size;\n        context_.template Copy<T, Context, Context>(\n            block_size, data_block_ptr, rev_data_block_ptr);\n      }\n    }\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_REVERSE_PACKED_SEGS_OP_H_\n"
  },
  {
    "path": "caffe2/operators/rmac_regions_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/rmac_regions_op.h\"\n\n#include <float.h>\n\nnamespace caffe2 {\n\ntemplate <>\nbool RMACRegionsOp<CPUContext>::RunOnDevice() {\n  const auto& X = Input(0); // Input tensor\n  auto* output = Output(0); // RoIs\n  output->Resize(0, 5); // [batch_id x1 y1 x2 y2] format of ROIPoolOp\n\n  if (X.size() == 0) {\n    return true;\n  }\n\n  int batch_size = X.dim32(0);\n  int H = X.dim32(2);\n  int W = X.dim32(3);\n  int minW = std::min(H, W);\n\n  // steps(idx) regions for long dimension\n  int step = 0;\n  if (W != H) {\n    int min_step = 1;\n    int max_step = 6;\n    float cur_min = FLT_MAX;\n    for (int idx = min_step; idx <= max_step; ++idx) {\n      float b = (std::max(H, W) - minW) / (1.0 * idx);\n      float val = std::abs((minW * minW - minW * b) / (minW * minW) - overlap_);\n      if (val < cur_min) {\n        step = idx;\n        cur_min = val;\n      }\n    }\n  }\n\n  // Region overplus per dimension\n  int Wd = (W > H) ? step : 0;\n  int Hd = (H > W) ? step : 0;\n\n  // Regions at each scale\n  for (int l = 1; l <= scales_; ++l) {\n    int region_size = 2 * minW / (l + 1);\n    if (region_size == 0) {\n      // Empty region.\n      // Break early as further scales will also result in empty regions.\n      break;\n    }\n\n    // Region coordinates\n    float bw =\n        (l + Wd - 1 > 0) ? ((W - region_size) / (1.0 * (l + Wd - 1))) : 0;\n    float bh =\n        (l + Hd - 1 > 0) ? ((H - region_size) / (1.0 * (l + Hd - 1))) : 0;\n\n    int cur_rows = output->dim32(0);\n    output->Extend((l + Wd) * (l + Hd), 50, &context_);\n    auto* outputData = output->mutable_data<float>() + cur_rows * 5;\n\n    for (int i = 0; i < l + Wd; ++i) {\n      for (int j = 0; j < l + Hd; ++j) {\n        int x1 = bw * i;\n        int y1 = bh * j;\n        // Careful with the borders\n        if (x1 + region_size > W) {\n          x1 -= (x1 + region_size - W);\n        }\n        if (y1 + region_size > H) {\n          y1 -= (y1 + region_size - H);\n        }\n        int x2 = x1 + region_size - 1;\n        int y2 = y1 + region_size - 1;\n\n        // Write region coordinates for batch 0\n        *outputData++ = 0;\n        *outputData++ = x1;\n        *outputData++ = y1;\n        *outputData++ = x2;\n        *outputData++ = y2;\n      }\n    }\n  }\n\n  // Replicate regions for all items in batch\n  int num_rois = output->dim32(0);\n  output->Extend((batch_size - 1) * num_rois, 50, &context_);\n  auto* outputData = output->mutable_data<float>();\n  for (int b = 1; b < batch_size; ++b) {\n    // Copy all rois\n    std::copy_n(outputData, num_rois * 5, outputData + b * num_rois * 5);\n    // Override batch index\n    for (int r = 0; r < num_rois; ++r) {\n      outputData[(b * num_rois + r) * 5] = b;\n    }\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(RMACRegions, RMACRegionsOp<CPUContext>);\n\nOPERATOR_SCHEMA(RMACRegions)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nComputes a fixed-grid of RMAC region coordinates at various levels\nas described in https://arxiv.org/abs/1511.05879.\n)DOC\")\n    .Arg(\"scales\", \"Number of scales to sample regions at.\")\n    .Arg(\"overlap\", \"Overlap between consecutive regions.\")\n    .Input(0, \"X\", \"The input 4D tensor of shape NCHW.\")\n    .Output(\n        0,\n        \"RMAC_REGIONS\",\n        \"The output RMAC regions for all items in the batch. Tensor of shape \"\n        \"(N x 5) following the ROIPoolOp format - each row is of the format \"\n        \"(batch_index x1 y1 x2 y2) where x1, y1, x2, y2 are the region \"\n        \"co-ordinates. Each region is repeated N times corresponding to each \"\n        \"item in the batch.\");\n\nSHOULD_NOT_DO_GRADIENT(RMACRegions);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/rmac_regions_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cub/block/block_reduce.cuh>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/rmac_regions_op.h\"\n\nnamespace cub {\n\ntemplate <typename KeyT, typename ValueT>\ninline __host__ __device__ bool operator<(\n    const cub::KeyValuePair<KeyT, ValueT>& kv1,\n    const cub::KeyValuePair<KeyT, ValueT>& kv2) {\n  return (kv1.value < kv2.value) ||\n      (kv1.value == kv2.value && kv2.key < kv1.key);\n}\n\n} // namespace cub\n\nnamespace caffe2 {\n\nnamespace {\n\n__global__ void NumRMACRegionsKernel(\n    const int W,\n    const int H,\n    const int min_step,\n    const int max_step,\n    const float overlap,\n    const int scales,\n    int* num_rois_data) {\n  // steps(idx) regions for long dimension\n  typedef cub::KeyValuePair<int, float> KeyValuePair; // <step, value>\n  KeyValuePair kv, min_kv;\n  min_kv.value = FLT_MAX;\n\n  // Local reduction\n  int minW = min(H, W);\n  int diff = max(H, W) - minW;\n  CUDA_1D_KERNEL_LOOP(index, max_step - min_step + 1) {\n    kv.key = min_step + index;\n    float b = diff / (1.0 * kv.key);\n    kv.value = fabsf((minW * minW - minW * b) / (minW * minW) - overlap);\n\n    if (kv < min_kv) {\n      min_kv = kv;\n    }\n  }\n\n  // Block-wise arg-min reduction to find step\n  int step;\n  {\n    typedef cub::BlockReduce<KeyValuePair, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n    __shared__ typename BlockReduce::TempStorage temp_storage;\n    min_kv = BlockReduce(temp_storage).Reduce(min_kv, cub::Min());\n\n    __shared__ int step_shared;\n    if (threadIdx.x == 0) {\n      step_shared = min_kv.key;\n    }\n    __syncthreads();\n    step = step_shared;\n  }\n\n  // Region overplus per dimension\n  int Wd = (W > H) ? step : 0;\n  int Hd = (H > W) ? step : 0;\n\n  // Local reduction to compute the total number of rois at all scales\n  int num_rois = 0;\n  CUDA_1D_KERNEL_LOOP(index, scales) {\n    int l = index + 1;\n    int region_size = 2 * minW / (l + 1);\n    num_rois += (region_size > 0) ? ((l + Wd) * (l + Hd)) : 0;\n  }\n\n  // Block-wise sum reduction to compute num_rois at all scales\n  {\n    typedef cub::BlockReduce<int, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n    __shared__ typename BlockReduce::TempStorage temp_storage;\n    num_rois = BlockReduce(temp_storage).Sum(num_rois);\n  }\n\n  if (threadIdx.x == 0) {\n    num_rois_data[0] = num_rois;\n    num_rois_data[1] = Wd;\n    num_rois_data[2] = Hd;\n  }\n}\n\n__global__ void RMACRegionsKernel(\n    const int W,\n    const int H,\n    const int N,\n    const int* num_rois_data,\n    float* output) {\n  int num_rois = num_rois_data[0];\n  int Wd = num_rois_data[1];\n  int Hd = num_rois_data[2];\n\n  // Block-wide temp shared storage for intermediate ROI results to avoid\n  // uncoalesced writes to global mem\n  __shared__ float output_shared[CAFFE_CUDA_NUM_THREADS * 5];\n\n  CUDA_1D_KERNEL_LOOP(index, N) {\n    int batch_id = index / num_rois;\n    int roi_id = index % num_rois;\n\n    int roi[5];\n    roi[0] = batch_id;\n\n    // Find the scale corresponding to this index and the roi_id relative\n    // to the scale.\n    int l = 0;\n    int num_rois_at_scale = 0;\n    do {\n      roi_id -= num_rois_at_scale;\n      l++;\n      num_rois_at_scale = (l + Wd) * (l + Hd);\n    } while (roi_id - num_rois_at_scale >= 0);\n\n    int region_size = 2 * min(H, W) / (l + 1);\n    float bw =\n        (l + Wd - 1 > 0) ? ((W - region_size) / (1.0 * (l + Wd - 1))) : 0;\n    float bh =\n        (l + Hd - 1 > 0) ? ((H - region_size) / (1.0 * (l + Hd - 1))) : 0;\n\n    int i = roi_id / (l + Hd);\n    int j = roi_id % (l + Hd);\n\n    roi[1] = bw * i;\n    roi[2] = bh * j;\n    // Careful with the borders\n    if (roi[1] + region_size > W) {\n      roi[1] -= (roi[1] + region_size - W);\n    }\n    if (roi[2] + region_size > H) {\n      roi[2] -= (roi[2] + region_size - H);\n    }\n    roi[3] = roi[1] + region_size - 1;\n    roi[4] = roi[2] + region_size - 1;\n\n    // Writing directly to output (global memory) will result in uncoalesced\n    // writes. Write output to shared mem first and then write ROI results to\n    // global output in a coalesced manner.\n    __syncthreads(); // Since output_shared is reused across loop iterations\n    for (int i = 0; i < 5; ++i) {\n      output_shared[threadIdx.x * 5 + i] = roi[i];\n    }\n    __syncthreads();\n    int offset = index - threadIdx.x;\n    float* output_offset = output + offset * 5;\n    int num_threads = min(blockDim.x, N - offset); // Active threads in block\n    for (int i = 0; i < 5; ++i) {\n      output_offset[num_threads * i + threadIdx.x] =\n          output_shared[num_threads * i + threadIdx.x];\n    }\n  }\n}\n\n} // namespace\n\ntemplate <>\nbool RMACRegionsOp<CUDAContext>::RunOnDevice() {\n  const auto& X = Input(0); // Input tensor\n  auto* output = Output(0); // RoIs\n\n  if (X.size() == 0) {\n    return true;\n  }\n\n  int batch_size = X.dim32(0);\n  int H = X.dim32(2);\n  int W = X.dim32(3);\n\n  // Compute number of regions\n  int min_step = 1;\n  int max_step = 6;\n  num_rois_.Resize(3); // num_rois, Wd, Hd\n  NumRMACRegionsKernel<<<\n      1,\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      W,\n      H,\n      min_step,\n      max_step,\n      overlap_,\n      scales_,\n      num_rois_.mutable_data<int>());\n\n  // Bit awkward, but the size of the output tensor depends on the output of\n  // NumRMACRegionsKernel (number of RoIs), so need to copy that to CPU\n  // to Resize() output appropriately.\n  int num_rois = 0;\n  context_.CopyBytes<CUDAContext, CPUContext>(\n      sizeof(int), num_rois_.data<int>(), &num_rois);\n  int N = batch_size * num_rois;\n  output->Resize(N, 5); // [batch_id x1 y1 x2 y2]\n\n  // Compute region coordinates\n  RMACRegionsKernel<<<\n      CAFFE_GET_BLOCKS(N),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      W, H, N, num_rois_.data<int>(), output->mutable_data<float>());\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(RMACRegions, RMACRegionsOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/rmac_regions_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#ifndef CAFFE2_OPERATORS_RMAC_REGIONS_OP_H\n#define CAFFE2_OPERATORS_RMAC_REGIONS_OP_H\n\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass RMACRegionsOp final : public Operator<Context> {\n public:\n  RMACRegionsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        scales_(OperatorBase::GetSingleArgument<int>(\"scales\", 3)),\n        overlap_(OperatorBase::GetSingleArgument<float>(\"overlap\", 0.4f)) {}\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  int scales_;\n  float overlap_;\n  Tensor<Context> num_rois_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_RMAC_REGIONS_OP_H\n"
  },
  {
    "path": "caffe2/operators/roi_align_gradient_op.cc",
    "content": "#include \"roi_align_gradient_op.h\"\n\n#include \"caffe2/utils/eigen_utils.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\nnamespace {\n\ntemplate <typename T>\nvoid bilinear_interpolate_gradient(\n    const int height,\n    const int width,\n    T y,\n    T x,\n    T& w1,\n    T& w2,\n    T& w3,\n    T& w4,\n    int& x_low,\n    int& x_high,\n    int& y_low,\n    int& y_high,\n    const int /*index*/ /* index for debug only*/) {\n  // deal with cases that inverse elements are out of feature map boundary\n  if (y < -1.0 || y > height || x < -1.0 || x > width) {\n    // empty\n    w1 = w2 = w3 = w4 = 0.;\n    x_low = x_high = y_low = y_high = -1;\n    return;\n  }\n\n  if (y <= 0) {\n    y = 0;\n  }\n  if (x <= 0) {\n    x = 0;\n  }\n\n  y_low = (int)y;\n  x_low = (int)x;\n\n  if (y_low >= height - 1) {\n    y_high = y_low = height - 1;\n    y = (T)y_low;\n  } else {\n    y_high = y_low + 1;\n  }\n\n  if (x_low >= width - 1) {\n    x_high = x_low = width - 1;\n    x = (T)x_low;\n  } else {\n    x_high = x_low + 1;\n  }\n\n  T ly = y - y_low;\n  T lx = x - x_low;\n  T hy = 1. - ly, hx = 1. - lx;\n\n  // reference in forward\n  // T v1 = bottom_data[y_low * width + x_low];\n  // T v2 = bottom_data[y_low * width + x_high];\n  // T v3 = bottom_data[y_high * width + x_low];\n  // T v4 = bottom_data[y_high * width + x_high];\n  // T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);\n\n  w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;\n\n  return;\n}\n\ntemplate <class T>\ninline void add(const T& val, T* address) {\n  *address += val;\n}\n\ntemplate <typename T>\nvoid ROIAlignBackwardFeature(\n    const int nthreads,\n    const T* top_diff,\n    const int /*num_rois*/,\n    const T& spatial_scale,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int sampling_ratio,\n    T* bottom_diff,\n    const T* bottom_rois,\n    int rois_cols) {\n  DCHECK(rois_cols == 4 || rois_cols == 5);\n\n  for (int index = 0; index < nthreads; index++) {\n    // (n, c, ph, pw) is an element in the pooled output\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int c = (index / pooled_width / pooled_height) % channels;\n    int n = index / pooled_width / pooled_height / channels;\n\n    const T* offset_bottom_rois = bottom_rois + n * rois_cols;\n    int roi_batch_ind = 0;\n    if (rois_cols == 5) {\n      roi_batch_ind = offset_bottom_rois[0];\n      offset_bottom_rois++;\n    }\n\n    // Do not using rounding; this implementation detail is critical\n    T roi_start_w = offset_bottom_rois[0] * spatial_scale;\n    T roi_start_h = offset_bottom_rois[1] * spatial_scale;\n    T roi_end_w = offset_bottom_rois[2] * spatial_scale;\n    T roi_end_h = offset_bottom_rois[3] * spatial_scale;\n    // T roi_start_w = round(offset_bottom_rois[0] * spatial_scale);\n    // T roi_start_h = round(offset_bottom_rois[1] * spatial_scale);\n    // T roi_end_w = round(offset_bottom_rois[2] * spatial_scale);\n    // T roi_end_h = round(offset_bottom_rois[3] * spatial_scale);\n\n    // Force malformed ROIs to be 1x1\n    T roi_width = std::max(roi_end_w - roi_start_w, (T)1.);\n    T roi_height = std::max(roi_end_h - roi_start_h, (T)1.);\n    T bin_size_h = static_cast<T>(roi_height) / static_cast<T>(pooled_height);\n    T bin_size_w = static_cast<T>(roi_width) / static_cast<T>(pooled_width);\n\n    T* offset_bottom_diff =\n        bottom_diff + (roi_batch_ind * channels + c) * height * width;\n\n    int top_offset = (n * channels + c) * pooled_height * pooled_width;\n    const T* offset_top_diff = top_diff + top_offset;\n    const T top_diff_this_bin = offset_top_diff[ph * pooled_width + pw];\n\n    // We use roi_bin_grid to sample the grid and mimic integral\n    int roi_bin_grid_h = (sampling_ratio > 0)\n        ? sampling_ratio\n        : ceil(roi_height / pooled_height); // e.g., = 2\n    int roi_bin_grid_w =\n        (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);\n\n    // We do average (integral) pooling inside a bin\n    const T count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4\n\n    for (int iy = 0; iy < roi_bin_grid_h; iy++) {\n      const T y = roi_start_h + ph * bin_size_h +\n          static_cast<T>(iy + .5f) * bin_size_h /\n              static_cast<T>(roi_bin_grid_h); // e.g., 0.5, 1.5\n      for (int ix = 0; ix < roi_bin_grid_w; ix++) {\n        const T x = roi_start_w + pw * bin_size_w +\n            static_cast<T>(ix + .5f) * bin_size_w /\n                static_cast<T>(roi_bin_grid_w);\n\n        T w1, w2, w3, w4;\n        int x_low, x_high, y_low, y_high;\n\n        bilinear_interpolate_gradient(\n            height,\n            width,\n            y,\n            x,\n            w1,\n            w2,\n            w3,\n            w4,\n            x_low,\n            x_high,\n            y_low,\n            y_high,\n            index);\n\n        T g1 = top_diff_this_bin * w1 / count;\n        T g2 = top_diff_this_bin * w2 / count;\n        T g3 = top_diff_this_bin * w3 / count;\n        T g4 = top_diff_this_bin * w4 / count;\n\n        if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) {\n          // atomic add is not needed for now since it is single threaded\n          add(static_cast<T>(g1), offset_bottom_diff + y_low * width + x_low);\n          add(static_cast<T>(g2), offset_bottom_diff + y_low * width + x_high);\n          add(static_cast<T>(g3), offset_bottom_diff + y_high * width + x_low);\n          add(static_cast<T>(g4), offset_bottom_diff + y_high * width + x_high);\n        } // if\n      } // ix\n    } // iy\n  } // for\n} // ROIAlignBackward\n\n} // namespace\n\ntemplate <>\nbool RoIAlignGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0); // Input data to pool\n  auto& R = Input(1); // RoIs\n  auto& dY = Input(2); // Gradient of net w.r.t. output of \"forward\" op\n                       // (aka \"gradOutput\")\n  auto* dX = Output(0); // Gradient of net w.r.t. input to \"forward\" op\n                        // (aka \"gradInput\")\n\n  CAFFE_ENFORCE_EQ(R.ndim(), 2);\n  // if R has 5 columns, the first column is the index, otherwise 0\n  CAFFE_ENFORCE(R.dim32(1) == 4 || R.dim32(1) == 5);\n\n  dX->ResizeLike(X);\n\n  // Must zero-out dX before accumulating gradients\n  // (TODO): Kaiming - is this safe?\n  math::Set<float, CPUContext>(\n      dX->size(), 0.f, dX->mutable_data<float>(), &context_);\n\n  if (dY.size() > 0) { // Handle possibly empty gradient if there were no rois\n    ROIAlignBackwardFeature<float>(\n        dY.size(),\n        dY.data<float>(),\n        R.dim32(0),\n        spatial_scale_,\n        X.dim32(1),\n        X.dim32(2),\n        X.dim32(3),\n        pooled_height_,\n        pooled_width_,\n        sampling_ratio_,\n        dX->mutable_data<float>(),\n        R.data<float>(),\n        R.dim32(1));\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(RoIAlignGradient, RoIAlignGradientOp<float, CPUContext>);\n\n// Input: X, rois, dY (aka \"gradOutput\");\n// Output: dX (aka \"gradInput\")\nOPERATOR_SCHEMA(RoIAlignGradient)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .Input(0, \"X\", \"See RoIPoolF.\")\n    .Input(1, \"RoIs\", \"See RoIPoolF.\")\n    .Input(2, \"dY\", \"Gradient of forward output 0 (Y)\")\n    .Output(0, \"dX\", \"Gradient of forward input 0 (X)\");\n\nnamespace {\n\nclass GetRoIAlignGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"RoIAlignGradient\",\n        \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\n} // namespace\n\nREGISTER_GRADIENT(RoIAlign, GetRoIAlignGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/roi_align_gradient_op.cu",
    "content": "#include \"roi_align_gradient_op.h\"\n\n#include <stdio.h>\n#include <cfloat>\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <typename T>\ninline __device__ T gpu_atomic_add(const T val, T* address);\n\ntemplate <>\ninline __device__ float gpu_atomic_add(const float val, float* address) {\n  return atomicAdd(address, val);\n}\n\ntemplate <typename T>\n__device__ void bilinear_interpolate_gradient(\n    const int height,\n    const int width,\n    T y,\n    T x,\n    T& w1,\n    T& w2,\n    T& w3,\n    T& w4,\n    int& x_low,\n    int& x_high,\n    int& y_low,\n    int& y_high,\n    const int index /* index for debug only*/) {\n  // deal with cases that inverse elements are out of feature map boundary\n  if (y < -1.0 || y > height || x < -1.0 || x > width) {\n    // empty\n    w1 = w2 = w3 = w4 = 0.;\n    x_low = x_high = y_low = y_high = -1;\n    return;\n  }\n\n  if (y <= 0) {\n    y = 0;\n  }\n  if (x <= 0) {\n    x = 0;\n  }\n\n  y_low = (int)y;\n  x_low = (int)x;\n\n  if (y_low >= height - 1) {\n    y_high = y_low = height - 1;\n    y = (T)y_low;\n  } else {\n    y_high = y_low + 1;\n  }\n\n  if (x_low >= width - 1) {\n    x_high = x_low = width - 1;\n    x = (T)x_low;\n  } else {\n    x_high = x_low + 1;\n  }\n\n  T ly = y - y_low;\n  T lx = x - x_low;\n  T hy = 1. - ly, hx = 1. - lx;\n\n  // reference in forward\n  // T v1 = bottom_data[y_low * width + x_low];\n  // T v2 = bottom_data[y_low * width + x_high];\n  // T v3 = bottom_data[y_high * width + x_low];\n  // T v4 = bottom_data[y_high * width + x_high];\n  // T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);\n\n  w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;\n\n  return;\n}\n\ntemplate <typename T>\n__global__ void RoIAlignBackwardFeature(\n    const int nthreads,\n    const T* top_diff,\n    const int num_rois,\n    const T spatial_scale,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int sampling_ratio,\n    T* bottom_diff,\n    const T* bottom_rois) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // (n, c, ph, pw) is an element in the pooled output\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int c = (index / pooled_width / pooled_height) % channels;\n    int n = index / pooled_width / pooled_height / channels;\n\n    const T* offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n\n    // Do not using rounding; this implementation detail is critical\n    T roi_start_w = offset_bottom_rois[1] * spatial_scale;\n    T roi_start_h = offset_bottom_rois[2] * spatial_scale;\n    T roi_end_w = offset_bottom_rois[3] * spatial_scale;\n    T roi_end_h = offset_bottom_rois[4] * spatial_scale;\n    // T roi_start_w = round(offset_bottom_rois[1] * spatial_scale);\n    // T roi_start_h = round(offset_bottom_rois[2] * spatial_scale);\n    // T roi_end_w = round(offset_bottom_rois[3] * spatial_scale);\n    // T roi_end_h = round(offset_bottom_rois[4] * spatial_scale);\n\n    // Force malformed ROIs to be 1x1\n    T roi_width = max(roi_end_w - roi_start_w, (T)1.);\n    T roi_height = max(roi_end_h - roi_start_h, (T)1.);\n    T bin_size_h = static_cast<T>(roi_height) / static_cast<T>(pooled_height);\n    T bin_size_w = static_cast<T>(roi_width) / static_cast<T>(pooled_width);\n\n    T* offset_bottom_diff =\n        bottom_diff + (roi_batch_ind * channels + c) * height * width;\n\n    int top_offset = (n * channels + c) * pooled_height * pooled_width;\n    const T* offset_top_diff = top_diff + top_offset;\n    const T top_diff_this_bin = offset_top_diff[ph * pooled_width + pw];\n\n    // We use roi_bin_grid to sample the grid and mimic integral\n    int roi_bin_grid_h = (sampling_ratio > 0)\n        ? sampling_ratio\n        : ceil(roi_height / pooled_height); // e.g., = 2\n    int roi_bin_grid_w =\n        (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);\n\n    // We do average (integral) pooling inside a bin\n    const T count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4\n\n    for (int iy = 0; iy < roi_bin_grid_h; iy++) // e.g., iy = 0, 1\n    {\n      const T y = roi_start_h + ph * bin_size_h +\n          static_cast<T>(iy + .5f) * bin_size_h /\n              static_cast<T>(roi_bin_grid_h); // e.g., 0.5, 1.5\n      for (int ix = 0; ix < roi_bin_grid_w; ix++) {\n        const T x = roi_start_w + pw * bin_size_w +\n            static_cast<T>(ix + .5f) * bin_size_w /\n                static_cast<T>(roi_bin_grid_w);\n\n        T w1, w2, w3, w4;\n        int x_low, x_high, y_low, y_high;\n\n        bilinear_interpolate_gradient(\n            height,\n            width,\n            y,\n            x,\n            w1,\n            w2,\n            w3,\n            w4,\n            x_low,\n            x_high,\n            y_low,\n            y_high,\n            index);\n\n        T g1 = top_diff_this_bin * w1 / count;\n        T g2 = top_diff_this_bin * w2 / count;\n        T g3 = top_diff_this_bin * w3 / count;\n        T g4 = top_diff_this_bin * w4 / count;\n\n        if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) {\n          gpu_atomic_add(\n              static_cast<T>(g1), offset_bottom_diff + y_low * width + x_low);\n          gpu_atomic_add(\n              static_cast<T>(g2), offset_bottom_diff + y_low * width + x_high);\n          gpu_atomic_add(\n              static_cast<T>(g3), offset_bottom_diff + y_high * width + x_low);\n          gpu_atomic_add(\n              static_cast<T>(g4), offset_bottom_diff + y_high * width + x_high);\n        } // if\n      } // ix\n    } // iy\n  } // CUDA_1D_KERNEL_LOOP\n} // RoIAlignBackward\n\n} // namespace\n\ntemplate <>\nbool RoIAlignGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0); // Input data to pool\n  auto& R = Input(1); // RoIs\n  auto& dY = Input(2); // Gradient of net w.r.t. output of \"forward\" op\n                       // (aka \"gradOutput\")\n  auto* dX = Output(0); // Gradient of net w.r.t. input to \"forward\" op\n                        // (aka \"gradInput\")\n\n  dX->ResizeLike(X);\n\n  // Must zero-out dX before accumulating gradients\n  // (TODO): Kaiming - is this safe?\n  math::Set<float, CUDAContext>(\n      dX->size(), 0.f, dX->mutable_data<float>(), &context_);\n\n  if (dY.size() > 0) { // Handle possibly empty gradient if there were no rois\n    RoIAlignBackwardFeature<float>\n        <<<CAFFE_GET_BLOCKS(dY.size()),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           context_.cuda_stream()>>>(\n            dY.size(),\n            dY.data<float>(),\n            R.dim32(0),\n            spatial_scale_,\n            X.dim32(1),\n            X.dim32(2),\n            X.dim32(3),\n            pooled_height_,\n            pooled_width_,\n            sampling_ratio_,\n            dX->mutable_data<float>(),\n            R.data<float>());\n  }\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(\n    RoIAlignGradient,\n    RoIAlignGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/roi_align_gradient_op.h",
    "content": "// Copyright 2004-present Facebook. All Rights Reserved.\n\n#ifndef ROI_ALIGN_OP_H_\n#define ROI_ALIGN_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass RoIAlignGradientOp final : public Operator<Context> {\n public:\n  RoIAlignGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        spatial_scale_(\n            OperatorBase::GetSingleArgument<float>(\"spatial_scale\", 1.)),\n        pooled_height_(OperatorBase::GetSingleArgument<int>(\"pooled_h\", 1)),\n        pooled_width_(OperatorBase::GetSingleArgument<int>(\"pooled_w\", 1)),\n        sampling_ratio_(\n            OperatorBase::GetSingleArgument<int>(\"sampling_ratio\", -1)) {\n    DCHECK_GT(spatial_scale_, 0);\n    DCHECK_GT(pooled_height_, 0);\n    DCHECK_GT(pooled_width_, 0);\n    DCHECK_GE(sampling_ratio_, 0);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float spatial_scale_;\n  int pooled_height_;\n  int pooled_width_;\n  int sampling_ratio_;\n};\n\n} // namespace caffe2\n\n#endif // ROI_ALIGN_OP_H_\n"
  },
  {
    "path": "caffe2/operators/roi_align_op.cc",
    "content": "#include \"roi_align_op.h\"\n\n#include \"caffe2/utils/eigen_utils.h\"\n#include \"caffe2/utils/math.h\"\n\n#ifdef CAFFE2_USE_MKL\n#include \"caffe2/mkl/operators/operator_fallback_mkl.h\"\n#endif // CAFFE2_USE_MKL\n\nnamespace caffe2 {\nnamespace {\n\ntemplate <typename T>\nstruct PreCalc {\n  int pos1;\n  int pos2;\n  int pos3;\n  int pos4;\n  T w1;\n  T w2;\n  T w3;\n  T w4;\n};\n\ntemplate <typename T>\nvoid pre_calc_for_bilinear_interpolate(\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int iy_upper,\n    const int ix_upper,\n    T roi_start_h,\n    T roi_start_w,\n    T bin_size_h,\n    T bin_size_w,\n    int roi_bin_grid_h,\n    int roi_bin_grid_w,\n    std::vector<PreCalc<T>>& pre_calc) {\n  int pre_calc_index = 0;\n  for (int ph = 0; ph < pooled_height; ph++) {\n    for (int pw = 0; pw < pooled_width; pw++) {\n      for (int iy = 0; iy < iy_upper; iy++) {\n        const T yy = roi_start_h + ph * bin_size_h +\n            static_cast<T>(iy + .5f) * bin_size_h /\n                static_cast<T>(roi_bin_grid_h); // e.g., 0.5, 1.5\n        for (int ix = 0; ix < ix_upper; ix++) {\n          const T xx = roi_start_w + pw * bin_size_w +\n              static_cast<T>(ix + .5f) * bin_size_w /\n                  static_cast<T>(roi_bin_grid_w);\n\n          T x = xx;\n          T y = yy;\n          // deal with: inverse elements are out of feature map boundary\n          if (y < -1.0 || y > height || x < -1.0 || x > width) {\n            // empty\n            PreCalc<T> pc;\n            pc.pos1 = 0;\n            pc.pos2 = 0;\n            pc.pos3 = 0;\n            pc.pos4 = 0;\n            pc.w1 = 0;\n            pc.w2 = 0;\n            pc.w3 = 0;\n            pc.w4 = 0;\n            pre_calc[pre_calc_index] = pc;\n            pre_calc_index += 1;\n            continue;\n          }\n\n          if (y <= 0) {\n            y = 0;\n          }\n          if (x <= 0) {\n            x = 0;\n          }\n\n          int y_low = (int)y;\n          int x_low = (int)x;\n          int y_high;\n          int x_high;\n\n          if (y_low >= height - 1) {\n            y_high = y_low = height - 1;\n            y = (T)y_low;\n          } else {\n            y_high = y_low + 1;\n          }\n\n          if (x_low >= width - 1) {\n            x_high = x_low = width - 1;\n            x = (T)x_low;\n          } else {\n            x_high = x_low + 1;\n          }\n\n          T ly = y - y_low;\n          T lx = x - x_low;\n          T hy = 1. - ly, hx = 1. - lx;\n          T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;\n\n          // save weights and indeces\n          PreCalc<T> pc;\n          pc.pos1 = y_low * width + x_low;\n          pc.pos2 = y_low * width + x_high;\n          pc.pos3 = y_high * width + x_low;\n          pc.pos4 = y_high * width + x_high;\n          pc.w1 = w1;\n          pc.w2 = w2;\n          pc.w3 = w3;\n          pc.w4 = w4;\n          pre_calc[pre_calc_index] = pc;\n\n          pre_calc_index += 1;\n        }\n      }\n    }\n  }\n}\n\ntemplate <typename T>\nvoid ROIAlignForward(\n    const int nthreads,\n    const T* bottom_data,\n    const T& spatial_scale,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int sampling_ratio,\n    const T* bottom_rois,\n    int roi_cols,\n    T* top_data,\n    StorageOrder order) {\n  DCHECK(roi_cols == 4 || roi_cols == 5);\n\n  int n_rois = nthreads / channels / pooled_width / pooled_height;\n  // (n, c, ph, pw) is an element in the pooled output\n  // can be parallelized using omp\n  // #pragma omp parallel for num_threads(32)\n  for (int n = 0; n < n_rois; n++) {\n    int index_n = n * channels * pooled_width * pooled_height;\n\n    // roi could have 4 or 5 columns\n    const T* offset_bottom_rois = bottom_rois + n * roi_cols;\n    int roi_batch_ind = 0;\n    if (roi_cols == 5) {\n      roi_batch_ind = offset_bottom_rois[0];\n      offset_bottom_rois++;\n    }\n\n    // Do not using rounding; this implementation detail is critical\n    T roi_start_w = offset_bottom_rois[0] * spatial_scale;\n    T roi_start_h = offset_bottom_rois[1] * spatial_scale;\n    T roi_end_w = offset_bottom_rois[2] * spatial_scale;\n    T roi_end_h = offset_bottom_rois[3] * spatial_scale;\n    // T roi_start_w = round(offset_bottom_rois[0] * spatial_scale);\n    // T roi_start_h = round(offset_bottom_rois[1] * spatial_scale);\n    // T roi_end_w = round(offset_bottom_rois[2] * spatial_scale);\n    // T roi_end_h = round(offset_bottom_rois[3] * spatial_scale);\n\n    // Force malformed ROIs to be 1x1\n    T roi_width = std::max(roi_end_w - roi_start_w, (T)1.);\n    T roi_height = std::max(roi_end_h - roi_start_h, (T)1.);\n    T bin_size_h = static_cast<T>(roi_height) / static_cast<T>(pooled_height);\n    T bin_size_w = static_cast<T>(roi_width) / static_cast<T>(pooled_width);\n\n    // We use roi_bin_grid to sample the grid and mimic integral\n    int roi_bin_grid_h = (sampling_ratio > 0)\n        ? sampling_ratio\n        : ceil(roi_height / pooled_height); // e.g., = 2\n    int roi_bin_grid_w =\n        (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);\n\n    // We do average (integral) pooling inside a bin\n    const T count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4\n\n    // we want to precalculate indeces and weights shared by all chanels,\n    // this is the key point of optimiation\n    std::vector<PreCalc<T>> pre_calc(\n        roi_bin_grid_h * roi_bin_grid_w * pooled_width * pooled_height);\n    pre_calc_for_bilinear_interpolate(\n        height,\n        width,\n        pooled_height,\n        pooled_width,\n        roi_bin_grid_h,\n        roi_bin_grid_w,\n        roi_start_h,\n        roi_start_w,\n        bin_size_h,\n        bin_size_w,\n        roi_bin_grid_h,\n        roi_bin_grid_w,\n        pre_calc);\n\n    if (order == StorageOrder::NCHW) {\n      for (int c = 0; c < channels; c++) {\n        int index_n_c = index_n + c * pooled_width * pooled_height;\n        const T* offset_bottom_data =\n            bottom_data + (roi_batch_ind * channels + c) * height * width;\n        int pre_calc_index = 0;\n\n        for (int ph = 0; ph < pooled_height; ph++) {\n          for (int pw = 0; pw < pooled_width; pw++) {\n            int index = index_n_c + ph * pooled_width + pw;\n\n            T output_val = 0.;\n            for (int iy = 0; iy < roi_bin_grid_h; iy++) {\n              for (int ix = 0; ix < roi_bin_grid_w; ix++) {\n                PreCalc<T> pc = pre_calc[pre_calc_index];\n                output_val += pc.w1 * offset_bottom_data[pc.pos1] +\n                    pc.w2 * offset_bottom_data[pc.pos2] +\n                    pc.w3 * offset_bottom_data[pc.pos3] +\n                    pc.w4 * offset_bottom_data[pc.pos4];\n\n                pre_calc_index += 1;\n              }\n            }\n            output_val /= count;\n\n            top_data[index] = output_val;\n          } // for pw\n        } // for ph\n      } // for c\n    } // if nchw\n\n    if (order == StorageOrder::NHWC) {\n      const T* offset_bottom_data =\n          bottom_data + roi_batch_ind * channels * height * width;\n      int pre_calc_index = 0;\n\n      for (int ph = 0; ph < pooled_height; ph++) {\n        for (int pw = 0; pw < pooled_width; pw++) {\n          EVecXf output_vals = EVecXf::Zero(channels);\n\n          for (int iy = 0; iy < roi_bin_grid_h; iy++) {\n            for (int ix = 0; ix < roi_bin_grid_w; ix++) {\n              PreCalc<T> pc = pre_calc[pre_calc_index];\n\n              ConstEigenVectorMap<T> data_1(\n                  offset_bottom_data + channels * pc.pos1, channels);\n              ConstEigenVectorMap<T> data_2(\n                  offset_bottom_data + channels * pc.pos2, channels);\n              ConstEigenVectorMap<T> data_3(\n                  offset_bottom_data + channels * pc.pos3, channels);\n              ConstEigenVectorMap<T> data_4(\n                  offset_bottom_data + channels * pc.pos4, channels);\n\n              output_vals += pc.w1 * data_1 + pc.w2 * data_2 + pc.w3 * data_3 +\n                  pc.w4 * data_4;\n\n              pre_calc_index += 1;\n            }\n          }\n          output_vals /= count;\n\n          int index_nhw = index_n + (ph * pooled_width + pw) * channels;\n          std::memcpy(\n              top_data + index_nhw, output_vals.data(), channels * sizeof(T));\n        } // for pw\n      } // for ph\n    } // if nhwc\n\n  } // for n\n}\n\n} // namespace\n\ntemplate <>\nbool RoIAlignOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0); // Input data to pool, NCHW\n  auto& R = Input(1); // RoIs\n  auto* Y = Output(0); // RoI pooled data\n\n  if (R.size() == 0) {\n    // Handle empty rois\n    if (order_ == StorageOrder::NCHW) {\n      Y->Resize(0, X.dim32(1), pooled_height_, pooled_width_);\n    } else if (order_ == StorageOrder::NHWC) {\n      Y->Resize(0, pooled_height_, pooled_width_, X.dim32(3));\n    }\n    // The following mutable_data calls are needed to allocate the tensors\n    Y->mutable_data<float>();\n    return true;\n  }\n\n  CAFFE_ENFORCE_EQ(R.ndim(), 2);\n  // if R has 5 columns, the first column is the index, otherwise 0\n  CAFFE_ENFORCE(R.dim32(1) == 4 || R.dim32(1) == 5);\n\n  assert(sampling_ratio_ >= 0);\n\n  if (order_ == StorageOrder::NCHW) {\n    Y->Resize(R.dim32(0), X.dim32(1), pooled_height_, pooled_width_);\n    int output_size = Y->size();\n    ROIAlignForward<float>(\n        output_size,\n        X.data<float>(),\n        spatial_scale_,\n        X.dim32(1),\n        X.dim32(2),\n        X.dim32(3),\n        pooled_height_,\n        pooled_width_,\n        sampling_ratio_,\n        R.data<float>(),\n        R.dim32(1),\n        Y->mutable_data<float>(),\n        order_);\n  } else if (order_ == StorageOrder::NHWC) {\n    Y->Resize(R.dim32(0), pooled_height_, pooled_width_, X.dim32(3));\n    int output_size = Y->size();\n    ROIAlignForward<float>(\n        output_size,\n        X.data<float>(),\n        spatial_scale_,\n        X.dim32(3),\n        X.dim32(1),\n        X.dim32(2),\n        pooled_height_,\n        pooled_width_,\n        sampling_ratio_,\n        R.data<float>(),\n        R.dim32(1),\n        Y->mutable_data<float>(),\n        order_);\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(RoIAlign, RoIAlignOp<float, CPUContext>);\n\n#ifdef CAFFE2_HAS_MKL_DNN\nREGISTER_MKL_OPERATOR(\n    RoIAlign,\n    mkl::MKLFallbackOp<RoIAlignOp<float, CPUContext>>);\n#endif // CAFFE2_HAS_MKL_DNN\n\n// Input: X, rois; Output: Y\nOPERATOR_SCHEMA(RoIAlign)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nRegion of Interest (RoI) align operation as used in Mask R-CNN.\n)DOC\")\n    .Arg(\n        \"spatial_scale\",\n        \"(float) default 1.0; Spatial scale of the input feature map X \"\n        \"relative to the input image. E.g., 0.0625 if X has a stride of 16 \"\n        \"w.r.t. the input image.\")\n    .Arg(\"pooled_h\", \"(int) default 1; Pooled output Y's height.\")\n    .Arg(\"pooled_w\", \"(int) default 1; Pooled output Y's width.\")\n    .Arg(\n        \"sampling_ratio\",\n        \"(int) default -1; number of sampling points in the interpolation grid \"\n        \"used to compute the output value of each pooled output bin. If > 0, \"\n        \"then exactly sampling_ratio x sampling_ratio grid points are used. If \"\n        \"<= 0, then an adaptive number of grid points are used (computed as \"\n        \"ceil(roi_width / pooled_w), and likewise for height).\")\n    .Input(0, \"X\", \"4D feature map input of shape (N, C, H, W).\")\n    .Input(\n        1,\n        \"RoIs\",\n        \"2D input of shape (R, 4 or 5) specifying R RoIs \"\n        \"representing: batch index in [0, N - 1], x1, y1, x2, y2. The RoI \"\n        \"coordinates are in the coordinate system of the input image. For \"\n        \"inputs corresponding to a single image, batch index can be excluded \"\n        \"to have just 4 columns.\")\n    .Output(\n        0,\n        \"Y\",\n        \"4D output of shape (R, C, pooled_h, pooled_w). The r-th batch element \"\n        \"is a pooled feature map cooresponding to the r-th RoI.\");\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/roi_align_op.cu",
    "content": "#include \"roi_align_op.h\"\n\n#include <stdio.h>\n#include <cfloat>\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <typename T>\n__device__ T bilinear_interpolate(\n    const T* bottom_data,\n    const int height,\n    const int width,\n    T y,\n    T x,\n    const int index /* index for debug only*/) {\n  // deal with cases that inverse elements are out of feature map boundary\n  if (y < -1.0 || y > height || x < -1.0 || x > width) {\n    // empty\n    return 0;\n  }\n\n  if (y <= 0) {\n    y = 0;\n  }\n  if (x <= 0) {\n    x = 0;\n  }\n\n  int y_low = (int)y;\n  int x_low = (int)x;\n  int y_high;\n  int x_high;\n\n  if (y_low >= height - 1) {\n    y_high = y_low = height - 1;\n    y = (T)y_low;\n  } else {\n    y_high = y_low + 1;\n  }\n\n  if (x_low >= width - 1) {\n    x_high = x_low = width - 1;\n    x = (T)x_low;\n  } else {\n    x_high = x_low + 1;\n  }\n\n  T ly = y - y_low;\n  T lx = x - x_low;\n  T hy = 1. - ly, hx = 1. - lx;\n  // do bilinear interpolation\n  T v1 = bottom_data[y_low * width + x_low];\n  T v2 = bottom_data[y_low * width + x_high];\n  T v3 = bottom_data[y_high * width + x_low];\n  T v4 = bottom_data[y_high * width + x_high];\n  T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;\n\n  T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);\n\n  return val;\n}\n\ntemplate <typename T>\n__global__ void RoIAlignForward(\n    const int nthreads,\n    const T* bottom_data,\n    const T spatial_scale,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int sampling_ratio,\n    const T* bottom_rois,\n    T* top_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // (n, c, ph, pw) is an element in the pooled output\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int c = (index / pooled_width / pooled_height) % channels;\n    int n = index / pooled_width / pooled_height / channels;\n\n    const T* offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n\n    // Do not using rounding; this implementation detail is critical\n    T roi_start_w = offset_bottom_rois[1] * spatial_scale;\n    T roi_start_h = offset_bottom_rois[2] * spatial_scale;\n    T roi_end_w = offset_bottom_rois[3] * spatial_scale;\n    T roi_end_h = offset_bottom_rois[4] * spatial_scale;\n    // T roi_start_w = round(offset_bottom_rois[1] * spatial_scale);\n    // T roi_start_h = round(offset_bottom_rois[2] * spatial_scale);\n    // T roi_end_w = round(offset_bottom_rois[3] * spatial_scale);\n    // T roi_end_h = round(offset_bottom_rois[4] * spatial_scale);\n\n    // Force malformed ROIs to be 1x1\n    T roi_width = max(roi_end_w - roi_start_w, (T)1.);\n    T roi_height = max(roi_end_h - roi_start_h, (T)1.);\n    T bin_size_h = static_cast<T>(roi_height) / static_cast<T>(pooled_height);\n    T bin_size_w = static_cast<T>(roi_width) / static_cast<T>(pooled_width);\n\n    const T* offset_bottom_data =\n        bottom_data + (roi_batch_ind * channels + c) * height * width;\n\n    // We use roi_bin_grid to sample the grid and mimic integral\n    int roi_bin_grid_h = (sampling_ratio > 0)\n        ? sampling_ratio\n        : ceil(roi_height / pooled_height); // e.g., = 2\n    int roi_bin_grid_w =\n        (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);\n\n    // We do average (integral) pooling inside a bin\n    const T count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4\n\n    T output_val = 0.;\n    for (int iy = 0; iy < roi_bin_grid_h; iy++) // e.g., iy = 0, 1\n    {\n      const T y = roi_start_h + ph * bin_size_h +\n          static_cast<T>(iy + .5f) * bin_size_h /\n              static_cast<T>(roi_bin_grid_h); // e.g., 0.5, 1.5\n      for (int ix = 0; ix < roi_bin_grid_w; ix++) {\n        const T x = roi_start_w + pw * bin_size_w +\n            static_cast<T>(ix + .5f) * bin_size_w /\n                static_cast<T>(roi_bin_grid_w);\n\n        T val = bilinear_interpolate(\n            offset_bottom_data, height, width, y, x, index);\n        output_val += val;\n      }\n    }\n    output_val /= count;\n\n    top_data[index] = output_val;\n  }\n}\n\n} // namespace\n\ntemplate <>\nbool RoIAlignOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0); // Input data to pool\n  auto& R = Input(1); // RoIs\n  auto* Y = Output(0); // RoI pooled data\n\n  if (R.size() == 0) {\n    // Handle empty rois\n    Y->Resize(0, X.dim32(1), pooled_height_, pooled_width_);\n    // The following mutable_data calls are needed to allocate the tensors\n    Y->mutable_data<float>();\n    return true;\n  }\n\n  assert(sampling_ratio_ >= 0);\n\n  Y->Resize(R.dim32(0), X.dim32(1), pooled_height_, pooled_width_);\n  int output_size = Y->size();\n  RoIAlignForward<float>\n      <<<CAFFE_GET_BLOCKS(output_size),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          spatial_scale_,\n          X.dim32(1),\n          X.dim32(2),\n          X.dim32(3),\n          pooled_height_,\n          pooled_width_,\n          sampling_ratio_,\n          R.data<float>(),\n          Y->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(RoIAlign, RoIAlignOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/roi_align_op.h",
    "content": "// Copyright 2004-present Facebook. All Rights Reserved.\n\n#ifndef ROI_ALIGN_OP_H_\n#define ROI_ALIGN_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass RoIAlignOp final : public Operator<Context> {\n public:\n  RoIAlignOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))),\n        spatial_scale_(\n            OperatorBase::GetSingleArgument<float>(\"spatial_scale\", 1.)),\n        pooled_height_(OperatorBase::GetSingleArgument<int>(\"pooled_h\", 1)),\n        pooled_width_(OperatorBase::GetSingleArgument<int>(\"pooled_w\", 1)),\n        sampling_ratio_(\n            OperatorBase::GetSingleArgument<int>(\"sampling_ratio\", -1)) {\n    DCHECK_GT(spatial_scale_, 0);\n    DCHECK_GT(pooled_height_, 0);\n    DCHECK_GT(pooled_width_, 0);\n    DCHECK_GE(sampling_ratio_, 0);\n    DCHECK(order_ == StorageOrder::NCHW || order_ == StorageOrder::NHWC);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  StorageOrder order_;\n  float spatial_scale_;\n  int pooled_height_;\n  int pooled_width_;\n  int sampling_ratio_;\n};\n\n} // namespace caffe2\n\n#endif // ROI_ALIGN_OP_H_\n"
  },
  {
    "path": "caffe2/operators/roi_align_op_gpu_test.cc",
    "content": "#include \"caffe2/utils/eigen_utils.h\"\n#include \"roi_align_op.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/utils/math.h\"\n#include \"gtest/gtest.h\"\n\nnamespace caffe2 {\nnamespace {\n\ntemplate <class Context>\nvoid AddConstInput(\n    const vector<TIndex>& shape,\n    const float value,\n    const string& name,\n    Context* context,\n    Workspace* ws) {\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<Tensor<Context>>();\n  tensor->Resize(shape);\n  math::Set<float, Context>(\n      tensor->size(), value, tensor->template mutable_data<float>(), context);\n  return;\n}\n\ntemplate <class Context>\nvoid AddInput(\n    const vector<TIndex>& shape,\n    const vector<float>& values,\n    const string& name,\n    Workspace* ws);\n\ntemplate <>\nvoid AddInput<CPUContext>(\n    const vector<TIndex>& shape,\n    const vector<float>& values,\n    const string& name,\n    Workspace* ws) {\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(shape);\n  EigenVectorMap<float> tensor_vec(\n      tensor->mutable_data<float>(), tensor->size());\n  tensor_vec.array() = utils::AsEArrXt(values);\n}\n\ntemplate <>\nvoid AddInput<CUDAContext>(\n    const vector<TIndex>& shape,\n    const vector<float>& values,\n    const string& name,\n    Workspace* ws) {\n  TensorCPU tmp(shape);\n  EigenVectorMap<float> tmp_vec(tmp.mutable_data<float>(), tmp.size());\n  tmp_vec.array() = utils::AsEArrXt(values);\n\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->template GetMutable<Tensor<CUDAContext>>();\n  tensor->CopyFrom(tmp);\n}\n\ntemplate <class Context>\nDeviceType GetDeviceType() {\n  return CPU;\n}\ntemplate <>\nDeviceType GetDeviceType<CUDAContext>() {\n  return CUDA;\n}\n\nint randInt(int a, int b) {\n  static std::random_device rd;\n  static std::mt19937 gen(rd());\n  return std::uniform_int_distribution<int>(a, b)(gen);\n}\n\nstruct TestParams {\n  int N;\n  int C;\n  int H;\n  int W;\n  int n_rois;\n  vector<float> rois_array;\n};\n\ntemplate <class Context>\nvoid CreateAndRun(\n    TensorCPU* outResult,\n    const string& order,\n    const TestParams& test_params,\n    bool random_test) {\n  Workspace ws;\n  Context context;\n\n  if (random_test) {\n    const int N = test_params.N;\n    const int C = test_params.C;\n    const int H = test_params.H;\n    const int W = test_params.W;\n    vector<float> features(N * C * H * W);\n    std::iota(features.begin(), features.end(), 0);\n    // utils::AsEArrXt(features) /= features.size();\n    AddInput<Context>(vector<TIndex>{N, C, H, W}, features, \"X\", &ws);\n    const int n_rois = test_params.n_rois;\n    const vector<float>& rois = test_params.rois_array;\n    AddInput<Context>(vector<TIndex>{n_rois, 5}, rois, \"R\", &ws);\n  } else {\n    const int N = 2;\n    const int C = 3;\n    const int H = 100;\n    const int W = 110;\n    vector<float> features(N * C * H * W);\n    std::iota(features.begin(), features.end(), 0);\n    // utils::AsEArrXt(features) /= features.size();\n    AddInput<Context>(vector<TIndex>{N, C, H, W}, features, \"X\", &ws);\n    vector<float> rois{0, 0,           0,           79,          59,\n                       0, 0,           5.0005703,   52.63237,    43.69501495,\n                       0, 24.13628387, 7.51243401,  79,          46.06628418,\n                       0, 0,           7.50924301,  68.47792816, 46.03357315,\n                       0, 0,           23.09477997, 51.61448669, 59,\n                       0, 0,           39.52141571, 52.44710541, 59,\n                       0, 23.57396317, 29.98791885, 79,          59,\n                       0, 0,           41.90219116, 79,          59,\n                       0, 0,           23.30098343, 79,          59};\n    AddInput<Context>(vector<TIndex>{9, 5}, rois, \"R\", &ws);\n  }\n\n  std::vector<unique_ptr<OperatorBase>> ops;\n  EXPECT_TRUE(order == \"NCHW\" || order == \"NHWC\");\n  if (order == \"NCHW\") {\n    OperatorDef def;\n    def.set_name(\"test\");\n    def.set_type(\"RoIAlign\");\n    def.add_input(\"X\");\n    def.add_input(\"R\");\n    def.add_output(\"Y\");\n    def.mutable_device_option()->set_device_type(GetDeviceType<Context>());\n    def.add_arg()->CopyFrom(MakeArgument(\"spatial_scale\", 1.0f / 16.0f));\n    def.add_arg()->CopyFrom(MakeArgument(\"pooled_h\", 6));\n    def.add_arg()->CopyFrom(MakeArgument(\"pooled_w\", 8));\n    def.add_arg()->CopyFrom(MakeArgument(\"sampling_ratio\", 2));\n\n    ops.push_back(CreateOperator(def, &ws));\n  } else if (order == \"NHWC\") {\n    OperatorDef def_roialign;\n    def_roialign.set_name(\"test\");\n    def_roialign.set_type(\"RoIAlign\");\n    def_roialign.add_input(\"X_NHWC\");\n    def_roialign.add_input(\"R\");\n    def_roialign.add_output(\"Y_NHWC\");\n    def_roialign.mutable_device_option()->set_device_type(\n        GetDeviceType<Context>());\n    def_roialign.add_arg()->CopyFrom(\n        MakeArgument(\"spatial_scale\", 1.0f / 16.0f));\n    def_roialign.add_arg()->CopyFrom(MakeArgument(\"pooled_h\", 6));\n    def_roialign.add_arg()->CopyFrom(MakeArgument(\"pooled_w\", 8));\n    def_roialign.add_arg()->CopyFrom(MakeArgument(\"sampling_ratio\", 2));\n    def_roialign.add_arg()->CopyFrom(MakeArgument<string>(\"order\", \"NHWC\"));\n\n    OperatorDef def_x;\n    def_x.set_name(\"test_x\");\n    def_x.set_type(\"NCHW2NHWC\");\n    def_x.add_input(\"X\");\n    def_x.add_output(\"X_NHWC\");\n    def_x.mutable_device_option()->set_device_type(GetDeviceType<Context>());\n\n    OperatorDef def_y;\n    def_y.set_name(\"test_y\");\n    def_y.set_type(\"NHWC2NCHW\");\n    def_y.add_input(\"Y_NHWC\");\n    def_y.add_output(\"Y\");\n    def_y.mutable_device_option()->set_device_type(GetDeviceType<Context>());\n\n    ops.push_back(CreateOperator(def_x, &ws));\n    ops.push_back(CreateOperator(def_roialign, &ws));\n    ops.push_back(CreateOperator(def_y, &ws));\n  }\n\n  for (auto const& op : ops) {\n    EXPECT_NE(nullptr, op.get());\n    EXPECT_TRUE(op->Run());\n  }\n\n  Blob* Y_blob = ws.GetBlob(\"Y\");\n  EXPECT_NE(nullptr, Y_blob);\n\n  auto& Y = Y_blob->Get<Tensor<Context>>();\n  outResult->CopyFrom(Y, &context);\n}\n\n} // namespace\n\nTEST(RoiAlignTest, CheckCPUGPUEqual) {\n  if (!caffe2::HasCudaGPU())\n    return;\n\n  TensorCPU y_cpu;\n  TensorCPU y_gpu;\n  TensorCPU y_cpu_nhwc;\n\n  // tests using FAIR example\n  {\n    TestParams test_params;\n    CreateAndRun<CPUContext>(&y_cpu, \"NCHW\", test_params, false);\n    CreateAndRun<CUDAContext>(&y_gpu, \"NCHW\", test_params, false);\n    CreateAndRun<CPUContext>(&y_cpu_nhwc, \"NHWC\", test_params, false);\n\n    EXPECT_EQ(y_cpu.dims(), y_gpu.dims());\n    EXPECT_EQ(y_cpu.dims(), y_cpu_nhwc.dims());\n    ConstEigenVectorMap<float> y_cpu_vec(y_cpu.data<float>(), y_cpu.size());\n    ConstEigenVectorMap<float> y_gpu_vec(y_gpu.data<float>(), y_gpu.size());\n    ConstEigenVectorMap<float> y_cpu_nhwc_vec(\n        y_cpu_nhwc.data<float>(), y_cpu_nhwc.size());\n    int max_diff_idx = -1;\n    (y_cpu_vec - y_gpu_vec).cwiseAbs().maxCoeff(&max_diff_idx);\n    EXPECT_FLOAT_EQ(y_cpu_vec[max_diff_idx], y_gpu_vec[max_diff_idx]);\n\n    max_diff_idx = -1;\n    (y_cpu_vec - y_cpu_nhwc_vec).cwiseAbs().maxCoeff(&max_diff_idx);\n    EXPECT_FLOAT_EQ(y_cpu_vec[max_diff_idx], y_cpu_nhwc_vec[max_diff_idx]);\n  }\n\n  // random tests\n  const int random_test_numbers = 100;\n  for (int i = 0; i < random_test_numbers; i++) {\n    const int N = randInt(1, 5);\n    const int C = randInt(1, 5);\n    const int H = randInt(1, 50);\n    const int W = randInt(1, 50);\n    const int n_rois = randInt(0, 30);\n    vector<float> rois_array;\n    for (int n = 0; n < n_rois; n++) {\n      rois_array.push_back(randInt(0, N - 1));\n      int w1 = randInt(-20, W + 20);\n      int w2 = randInt(-20, W + 20);\n      int h1 = randInt(-20, H + 20);\n      int h2 = randInt(-20, H + 20);\n      rois_array.push_back(std::min(w1, w2));\n      rois_array.push_back(std::max(h1, h2));\n      rois_array.push_back(std::min(w1, w2));\n      rois_array.push_back(std::max(h1, h2));\n    }\n    TestParams test_params{N, C, H, W, n_rois, rois_array};\n\n    CreateAndRun<CPUContext>(&y_cpu, \"NCHW\", test_params, true);\n    CreateAndRun<CUDAContext>(&y_gpu, \"NCHW\", test_params, true);\n    CreateAndRun<CPUContext>(&y_cpu_nhwc, \"NHWC\", test_params, true);\n\n    EXPECT_EQ(y_cpu.dims(), y_gpu.dims());\n    EXPECT_EQ(y_cpu.dims(), y_cpu_nhwc.dims());\n    ConstEigenVectorMap<float> y_cpu_vec(y_cpu.data<float>(), y_cpu.size());\n    ConstEigenVectorMap<float> y_gpu_vec(y_gpu.data<float>(), y_gpu.size());\n    ConstEigenVectorMap<float> y_cpu_nhwc_vec(\n        y_cpu_nhwc.data<float>(), y_cpu_nhwc.size());\n    int max_diff_idx = -1;\n    (y_cpu_vec - y_gpu_vec).cwiseAbs().maxCoeff(&max_diff_idx);\n    EXPECT_FLOAT_EQ(y_cpu_vec[max_diff_idx], y_gpu_vec[max_diff_idx]);\n\n    max_diff_idx = -1;\n    (y_cpu_vec - y_cpu_nhwc_vec).cwiseAbs().maxCoeff(&max_diff_idx);\n    EXPECT_FLOAT_EQ(y_cpu_vec[max_diff_idx], y_cpu_nhwc_vec[max_diff_idx]);\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/roi_pool_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"roi_pool_op.h\"\n\n#include <cfloat>\n\nnamespace caffe2 {\n\nusing std::max;\nusing std::min;\n\ntemplate <>\nbool RoIPoolOp<float, CPUContext>::RunOnDevice() {\n  const auto& X = Input(0); // Input data to pool\n  const auto& R = Input(1); // RoIs\n  auto* Y = Output(0); // RoI pooled data\n  auto* A = is_test_ ? nullptr : Output(1); // argmaxes\n\n  // Each ROI is of the form [batch_index x1 y1 x2 y2]\n  CAFFE_ENFORCE_EQ(R.dim32(1), 5);\n\n  // TODO: Handle the storage_order properly to get the NCWH.\n  int batch_size = X.dim32(0);\n  int channels = X.dim32(1);\n  int height = X.dim32(2);\n  int width = X.dim32(3);\n  int num_rois = R.dim32(0);\n\n  Y->Resize(num_rois, channels, pooled_height_, pooled_width_);\n  if (!is_test_) {\n    A->Resize(Y->dims());\n  }\n\n  const float* Xdata = X.data<float>();\n  const float* rois = R.data<float>();\n  float* Ydata = Y->mutable_data<float>();\n  int* argmax_data = is_test_ ? nullptr : A->mutable_data<int>();\n\n  // For each ROI R = [batch_index x1 y1 x2 y2]: max pool over R\n  for (int n = 0; n < num_rois; ++n) {\n    int roi_batch_id = rois[0];\n    int roi_start_w = round(rois[1] * spatial_scale_);\n    int roi_start_h = round(rois[2] * spatial_scale_);\n    int roi_end_w = round(rois[3] * spatial_scale_);\n    int roi_end_h = round(rois[4] * spatial_scale_);\n    CAFFE_ENFORCE_GE(roi_batch_id, 0);\n    CAFFE_ENFORCE_LT(roi_batch_id, batch_size);\n\n    // Force malformed ROIs to be 1x1\n    int roi_height = max(roi_end_h - roi_start_h + 1, 1);\n    int roi_width = max(roi_end_w - roi_start_w + 1, 1);\n\n    const float bin_size_h =\n        static_cast<float>(roi_height) / static_cast<float>(pooled_height_);\n    const float bin_size_w =\n        static_cast<float>(roi_width) / static_cast<float>(pooled_width_);\n\n    const float* batch_data = Xdata + roi_batch_id * X.size_from_dim(1);\n\n    for (int c = 0; c < channels; ++c) {\n      for (int ph = 0; ph < pooled_height_; ++ph) {\n        for (int pw = 0; pw < pooled_width_; ++pw) {\n          // Compute pooling region for this output unit:\n          //  start (included) = floor(ph * roi_height / pooled_height_)\n          //  end (excluded) = ceil((ph + 1) * roi_height / pooled_height_)\n          int hstart =\n              static_cast<int>(floor(static_cast<float>(ph) * bin_size_h));\n          int wstart =\n              static_cast<int>(floor(static_cast<float>(pw) * bin_size_w));\n          int hend =\n              static_cast<int>(ceil(static_cast<float>(ph + 1) * bin_size_h));\n          int wend =\n              static_cast<int>(ceil(static_cast<float>(pw + 1) * bin_size_w));\n\n          // Add roi offsets and clip to input boundaries\n          hstart = min(max(hstart + roi_start_h, 0), height);\n          hend = min(max(hend + roi_start_h, 0), height);\n          wstart = min(max(wstart + roi_start_w, 0), width);\n          wend = min(max(wend + roi_start_w, 0), width);\n\n          const int pool_index = ph * pooled_width_ + pw;\n\n          // Define an empty pooling region to be zero\n          bool is_empty = (hend <= hstart) || (wend <= wstart);\n          Ydata[pool_index] = is_empty ? 0 : -FLT_MAX;\n          if (!is_test_) {\n            // If nothing is pooled, argmax = -1 causes nothing to be backprop'd\n            argmax_data[pool_index] = -1;\n          }\n\n          for (int h = hstart; h < hend; ++h) {\n            for (int w = wstart; w < wend; ++w) {\n              const int index = h * width + w;\n              if (batch_data[index] > Ydata[pool_index]) {\n                Ydata[pool_index] = batch_data[index];\n                if (!is_test_) {\n                  argmax_data[pool_index] = index;\n                }\n              }\n            }\n          }\n        }\n      }\n      // Increment all data pointers by one channel\n      batch_data += X.size_from_dim(2);\n      Ydata += Y->size_from_dim(2);\n      if (!is_test_) {\n        argmax_data += A->size_from_dim(2);\n      }\n    }\n    // Increment ROI data pointer\n    rois += R.size_from_dim(1);\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(RoIPool, RoIPoolOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(RoIPoolGradient, RoIPoolGradientOp<float, CPUContext>);\n\n// Input: X, rois\n// Output case #1: Y, argmaxes (train mode)\n// Output case #2: Y           (test mode)\nOPERATOR_SCHEMA(RoIPool)\n    .NumInputs(2)\n    .NumOutputs({1, 2})\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      ArgumentHelper helper(def);\n      const StorageOrder order = StringToStorageOrder(\n          helper.GetSingleArgument<string>(\"order\", \"NCHW\"));\n      const TensorShape& X = in[0];\n      const int num_channels =\n          (order == StorageOrder::NCHW ? X.dims(1) : X.dims(3));\n      const TensorShape& R = in[1];\n      const int num_rois = R.dims(0);\n      const int pooled_height = helper.GetSingleArgument<int>(\"pooled_h\", 1);\n      const int pooled_width = helper.GetSingleArgument<int>(\"pooled_w\", 1);\n      TensorShape Y = CreateTensorShape(\n          vector<int>({num_rois, num_channels, pooled_height, pooled_width}),\n          X.data_type());\n\n      bool is_test = helper.GetSingleArgument<int>(OpSchema::Arg_IsTest, 0);\n      if (!is_test) {\n        TensorShape argmaxes = Y;\n        argmaxes.set_data_type(TensorProto_DataType_INT32);\n        return vector<TensorShape>({Y, argmaxes});\n      } else {\n        return vector<TensorShape>({Y});\n      }\n    })\n    .SetDoc(R\"DOC(\nCarries out ROI Pooling for Faster-RCNN.\nDepending on the mode, there are multiple output cases:\n\n  Output case #1: Y, argmaxes (train mode)\n  Output case #2: Y           (test mode)\n)DOC\")\n    .Arg(\n        \"is_test\",\n        \"If set, run in test mode and skip computation of argmaxes (used for \"\n        \"gradient computation). Only one output tensor is produced. \"\n        \"(Default: false).\")\n    .Arg(\"order\", \"A StorageOrder string (Default: \\\"NCHW\\\").\")\n    .Arg(\"pooled_h\", \"The pooled output height (Default: 1).\")\n    .Arg(\"pooled_w\", \"The pooled output width (Default: 1).\")\n    .Arg(\n        \"spatial_scale\",\n        \"Multiplicative spatial scale factor to translate ROI coords from \"\n        \"their input scale to the scale used when pooling (Default: 1.0).\")\n    .Input(\n        0,\n        \"X\",\n        \"The input 4-D tensor of data. Only NCHW order is currently supported.\")\n    .Input(\n        1,\n        \"rois\",\n        \"RoIs (Regions of Interest) to pool over. Should be a 2-D tensor of \"\n        \"shape (num_rois, 5) given as [[batch_id, x1, y1, x2, y2], ...].\")\n    .Output(\n        0,\n        \"Y\",\n        \"RoI pooled output 4-D tensor of shape \"\n        \"(num_rois, channels, pooled_h, pooled_w).\")\n    .Output(\n        1,\n        \"argmaxes\",\n        \"Argmaxes corresponding to indices in X used for gradient computation. \"\n        \"Only output if arg \\\"is_test\\\" is false.\");\n\n// Input: X, rois, argmaxes, dY (aka \"gradOutput\")\n// Output: dX (aka \"gradInput\")\nOPERATOR_SCHEMA(RoIPoolGradient).NumInputs(4).NumOutputs(1);\n\nclass GetRoIPoolGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"RoIPoolGradient\",\n        \"\",\n        vector<string>{I(0), I(1), O(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(RoIPool, GetRoIPoolGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/roi_pool_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cfloat>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"roi_pool_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <typename T>\ninline __device__ T gpu_atomic_add(const T val, T* address);\n\ntemplate <>\ninline __device__ float gpu_atomic_add(const float val, float* address) {\n  return atomicAdd(address, val);\n}\n\ntemplate <typename T>\n__global__ void ROIPoolForward(\n    const int nthreads,\n    const T* bottom_data,\n    const T spatial_scale,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const T* bottom_rois,\n    T* top_data,\n    int* argmax_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // (n, c, ph, pw) is an element in the pooled output\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int c = (index / pooled_width / pooled_height) % channels;\n    int n = index / pooled_width / pooled_height / channels;\n\n    const T* offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n    int roi_start_w = round(offset_bottom_rois[1] * spatial_scale);\n    int roi_start_h = round(offset_bottom_rois[2] * spatial_scale);\n    int roi_end_w = round(offset_bottom_rois[3] * spatial_scale);\n    int roi_end_h = round(offset_bottom_rois[4] * spatial_scale);\n\n    // Force malformed ROIs to be 1x1\n    int roi_width = max(roi_end_w - roi_start_w + 1, 1);\n    int roi_height = max(roi_end_h - roi_start_h + 1, 1);\n    T bin_size_h = static_cast<T>(roi_height) / static_cast<T>(pooled_height);\n    T bin_size_w = static_cast<T>(roi_width) / static_cast<T>(pooled_width);\n\n    int hstart = static_cast<int>(floor(static_cast<T>(ph) * bin_size_h));\n    int wstart = static_cast<int>(floor(static_cast<T>(pw) * bin_size_w));\n    int hend = static_cast<int>(ceil(static_cast<T>(ph + 1) * bin_size_h));\n    int wend = static_cast<int>(ceil(static_cast<T>(pw + 1) * bin_size_w));\n\n    // Add roi offsets and clip to input boundaries\n    hstart = min(max(hstart + roi_start_h, 0), height);\n    hend = min(max(hend + roi_start_h, 0), height);\n    wstart = min(max(wstart + roi_start_w, 0), width);\n    wend = min(max(wend + roi_start_w, 0), width);\n    bool is_empty = (hend <= hstart) || (wend <= wstart);\n\n    // Define an empty pooling region to be zero\n    T maxval = is_empty ? 0 : -FLT_MAX;\n    // If nothing is pooled, argmax = -1 causes nothing to be backprop'd\n    int maxidx = -1;\n    const T* offset_bottom_data =\n        bottom_data + (roi_batch_ind * channels + c) * height * width;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        int bottom_index = h * width + w;\n        if (offset_bottom_data[bottom_index] > maxval) {\n          maxval = offset_bottom_data[bottom_index];\n          maxidx = bottom_index;\n        }\n      }\n    }\n    top_data[index] = maxval;\n    if (argmax_data) {\n      argmax_data[index] = maxidx;\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void ROIPoolBackward(\n    const int nthreads,\n    const T* top_diff,\n    const int* argmax_data,\n    const int num_rois,\n    const T spatial_scale,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    T* bottom_diff,\n    const T* bottom_rois) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // (n, c, ph, pw) is an element in the pooled output\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int c = (index / pooled_width / pooled_height) % channels;\n    int n = index / pooled_width / pooled_height / channels;\n\n    const T* offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n    int bottom_offset = (roi_batch_ind * channels + c) * height * width;\n    int top_offset = (n * channels + c) * pooled_height * pooled_width;\n    const T* offset_top_diff = top_diff + top_offset;\n    T* offset_bottom_diff = bottom_diff + bottom_offset;\n    const int* offset_argmax_data = argmax_data + top_offset;\n\n    int argmax = offset_argmax_data[ph * pooled_width + pw];\n    if (argmax != -1) {\n      gpu_atomic_add(\n          static_cast<T>(offset_top_diff[ph * pooled_width + pw]),\n          offset_bottom_diff + argmax);\n    }\n  }\n}\n\n} // namespace\n\ntemplate <>\nbool RoIPoolOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0); // Input data to pool\n  auto& R = Input(1); // RoIs\n  auto* Y = Output(0); // RoI pooled data\n  auto* A = is_test_ ? nullptr : Output(1); // argmaxes\n\n  // Handle empty rois\n  if (R.size() == 0) {\n    Y->Resize(0, X.dim32(1), pooled_height_, pooled_width_);\n    // mutable_data calls are needed to allocate the tensors\n    Y->mutable_data<float>();\n    if (!is_test_) {\n      A->Resize(Y->dims());\n      A->mutable_data<int>();\n    }\n    return true;\n  }\n\n  Y->Resize(R.dim32(0), X.dim32(1), pooled_height_, pooled_width_);\n  if (!is_test_) {\n    A->Resize(Y->dims());\n  }\n  int output_size = Y->size();\n  int* argmax_data = is_test_ ? nullptr : A->mutable_data<int>();\n  ROIPoolForward<float><<<\n      CAFFE_GET_BLOCKS(output_size),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      output_size,\n      X.data<float>(),\n      spatial_scale_,\n      X.dim32(1),\n      X.dim32(2),\n      X.dim32(3),\n      pooled_height_,\n      pooled_width_,\n      R.data<float>(),\n      Y->mutable_data<float>(),\n      argmax_data);\n  return true;\n}\n\ntemplate <>\nbool RoIPoolGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0); // Input data to pool\n  auto& R = Input(1); // RoIs\n  auto& A = Input(2); // argmaxes\n  auto& dY = Input(3); // Gradient of net w.r.t. output of \"forward\" op\n  // (aka \"gradOutput\")\n  auto* dX = Output(0); // Gradient of net w.r.t. input to \"forward\" op\n  // (aka \"gradInput\")\n\n  dX->ResizeLike(X);\n  // Must zero-out dX before accumulating gradients\n  math::Set<float, CUDAContext>(\n      dX->size(), 0.f, dX->mutable_data<float>(), &context_);\n  if (dY.size() > 0) { // Handle possibly empty gradient if there were no rois\n    ROIPoolBackward<float><<<\n        CAFFE_GET_BLOCKS(dY.size()),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        dY.size(),\n        dY.data<float>(),\n        A.data<int>(),\n        R.dim32(0),\n        spatial_scale_,\n        X.dim32(1),\n        X.dim32(2),\n        X.dim32(3),\n        pooled_height_,\n        pooled_width_,\n        dX->mutable_data<float>(),\n        R.data<float>());\n  }\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(RoIPool, RoIPoolOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(RoIPoolGradient, RoIPoolGradientOp<float, CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/roi_pool_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef ROI_POOL_OP_H_\n#define ROI_POOL_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass RoIPoolOp final : public Operator<Context> {\n public:\n  RoIPoolOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        is_test_(OperatorBase::GetSingleArgument<int>(OpSchema::Arg_IsTest, 0)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))),\n        pooled_height_(OperatorBase::GetSingleArgument<int>(\"pooled_h\", 1)),\n        pooled_width_(OperatorBase::GetSingleArgument<int>(\"pooled_w\", 1)),\n        spatial_scale_(\n            OperatorBase::GetSingleArgument<float>(\"spatial_scale\", 1.)) {\n    CAFFE_ENFORCE(\n        (is_test_ && OutputSize() == 1) || (!is_test_ && OutputSize() == 2),\n        \"Output size mismatch.\");\n    CAFFE_ENFORCE_GT(spatial_scale_, 0);\n    CAFFE_ENFORCE_GT(pooled_height_, 0);\n    CAFFE_ENFORCE_GT(pooled_width_, 0);\n    CAFFE_ENFORCE_EQ(\n        order_, StorageOrder::NCHW, \"Only NCHW order is supported right now.\");\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  bool is_test_;\n  StorageOrder order_;\n  int pooled_height_;\n  int pooled_width_;\n  float spatial_scale_;\n};\n\ntemplate <typename T, class Context>\nclass RoIPoolGradientOp final : public Operator<Context> {\n public:\n  RoIPoolGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        spatial_scale_(\n            OperatorBase::GetSingleArgument<float>(\"spatial_scale\", 1.)),\n        pooled_height_(OperatorBase::GetSingleArgument<int>(\"pooled_h\", 1)),\n        pooled_width_(OperatorBase::GetSingleArgument<int>(\"pooled_w\", 1)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CAFFE_ENFORCE_GT(spatial_scale_, 0);\n    CAFFE_ENFORCE_GT(pooled_height_, 0);\n    CAFFE_ENFORCE_GT(pooled_width_, 0);\n    CAFFE_ENFORCE_EQ(\n        order_, StorageOrder::NCHW, \"Only NCHW order is supported right now.\");\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float spatial_scale_;\n  int pooled_height_;\n  int pooled_width_;\n  StorageOrder order_;\n};\n\n} // namespace caffe2\n\n#endif // ROI_POOL_OP_H_\n"
  },
  {
    "path": "caffe2/operators/rowmul_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/rowmul_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(ReduceTailSum, ReduceTailSumOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(RowMul, RowMulOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(ReduceTailSum)\n    .NumInputs(1, 1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nReduce the tailing dimensions\n)DOC\")\n    .Input(0, \"mat\", \"The matrix\")\n    .Output(0, \"output\", \"Output\");\n\nOPERATOR_SCHEMA(RowMul)\n    .NumInputs(2, 2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven a matrix A and column vector w, the output is the multiplication of row i\nof A and element i of w, e.g. C[i][j] = A[i][j] * w[i]. This operator should be\ndeprecated when the gradient operator of Mul with broadcast is implemented.\n)DOC\")\n    .Input(0, \"mat\", \"The matrix\")\n    .Input(1, \"w\", \"The column vector\")\n    .Output(0, \"output\", \"Output\");\n\nclass GetRowMulGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return vector<OperatorDef>{\n        CreateOperatorDef(\n            \"RowMul\", \"\", vector<string>{GO(0), I(1)}, vector<string>{GI(0)}),\n        CreateOperatorDef(\n            \"Mul\",\n            \"\",\n            vector<string>{GO(0), I(0)},\n            vector<string>{GI(1) + \"before_aggregate\"}),\n        CreateOperatorDef(\n            \"ReduceTailSum\",\n            \"\",\n            vector<string>{GI(1) + \"before_aggregate\"},\n            vector<string>{GI(1)})};\n  }\n};\nREGISTER_GRADIENT(RowMul, GetRowMulGradient);\n\n} // namespace\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/rowmul_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_ROW_MUL_H_\n#define CAFFE2_OPERATORS_ROW_MUL_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n// A hacky version of Mul with broadcast\n// RowMul([mat, w], [output])\ntemplate <typename T, class Context>\nclass RowMulOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(RowMulOp);\n\n  bool RunOnDevice() override {\n    auto& mat = Input(0);\n    auto& w = Input(1);\n    auto* output = Output(0);\n\n    output->ResizeLike(mat);\n    T* output_data = output->template mutable_data<T>();\n    const T* mat_data = mat.template data<T>();\n    const T* w_data = w.template data<T>();\n\n    // Dimension checking\n    CAFFE_ENFORCE_EQ(\n        w.size(),\n        mat.dim32(0),\n        \"Length of w should be equal to the first dim of mat\");\n\n    auto block_size = mat.size_from_dim(1);\n    for (int i = 0; i < w.size(); i++) {\n      size_t offset = i * block_size;\n      for (int j = 0; j < block_size; j++) {\n        output_data[offset + j] = mat_data[offset + j] * w_data[i];\n      }\n    }\n\n    return true;\n  }\n};\n\n// A hacky version\ntemplate <typename T, class Context>\nclass ReduceTailSumOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(ReduceTailSumOp);\n\n  bool RunOnDevice() override {\n    auto& mat = Input(0);\n    auto* output = Output(0);\n\n    int N = mat.dim32(0);\n    int block_size = mat.size_from_dim(1);\n\n    output->Resize(N);\n    T* output_data = output->template mutable_data<T>();\n    const T* mat_data = mat.template data<T>();\n\n    for (int i = 0; i < N; i++) {\n      output_data[i] = 0;\n      size_t offset = i * block_size;\n      for (int j = 0; j < block_size; j++) {\n        output_data[i] += mat_data[offset + j];\n      }\n    }\n    return true;\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_ROW_MUL_H_\n"
  },
  {
    "path": "caffe2/operators/scale_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/scale_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Scale, ScaleOp<CPUContext>);\nOPERATOR_SCHEMA(Scale)\n  .NumInputs(1)\n  .NumOutputs(1)\n  .AllowInplace({{0, 0}})\n  .IdenticalTypeAndShape()\n  .SetDoc(R\"DOC(\nScale takes one input data (Tensor<float>) and produces one output data\n(Tensor<float>) whose value is the input data tensor scaled element-wise.\n)DOC\")\n  .Arg(\"scale\", \"(float, default 1.0) the scale to apply.\");\n\nclass GetScaleGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    // CopyArguments is true by default so the \"scale\" arg is going to be copied\n    return SingleGradientDef(\n        \"Scale\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Scale, GetScaleGradient);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/scale_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SCALE_OP_H_\n#define CAFFE2_OPERATORS_SCALE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass ScaleOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ScaleOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.0)) {}\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n    Y->ResizeLike(X);\n    math::Scale<T, Context>(\n        X.size(),\n        scale_,\n        X.template data<T>(),\n        Y->template mutable_data<T>(),\n        &context_);\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<float>>::call(this, Input(0));\n  }\n\n protected:\n  float scale_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SCALE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/scale_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/scale_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool ScaleOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<float16, float>>::call(this, Input(0));\n}\n\nREGISTER_CUDA_OPERATOR(Scale, ScaleOp<CUDAContext>);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/segment_reduction_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/segment_reduction_op.h\"\n\nnamespace caffe2 {\n\n// registering 5 input gradient with main output\n// gradient of SparseLengthsWeightedSum\nOPERATOR_SCHEMA(SparseLengthsIndicesInGradientWeightedSumWithMainInputGradient)\n    .NumInputs(5)\n    .NumOutputs(2);\nREGISTER_CPU_OPERATOR(\n    SparseLengthsIndicesInGradientWeightedSumWithMainInputGradient,\n    AbstractLengthsWithMainInputGradientOp<\n        float,\n        int,\n        CPUContext,\n        WeightedSumReducerDef::template ReducerGradient<float, CPUContext>,\n        true /*SparseFused*/,\n        true /*GradientNeedIndices*/>);\n\n// registering 4 input version\nOPERATOR_SCHEMA(SparseLengthsIndicesInGradientWeightedSumGradient)\n    .NumInputs(4)\n    .NumOutputs(1);\nREGISTER_CPU_OPERATOR(\n    SparseLengthsIndicesInGradientWeightedSumGradient,\n    AbstractLengthsGradientOp<\n        float,\n        int,\n        CPUContext,\n        WeightedSumReducerDef::template ReducerGradient<float, CPUContext>,\n        true /*GradientNeedIndices*/>);\n\n// registering 3 input version\n// gradient of SparseLengthsSum\nOPERATOR_SCHEMA(SparseLengthsIndicesInGradientSumGradient)\n    .NumInputs(3)\n    .NumOutputs(1);\nREGISTER_CPU_OPERATOR(\n    SparseLengthsIndicesInGradientSumGradient,\n    AbstractLengthsGradientOp<\n        float,\n        int,\n        CPUContext,\n        SumReducerDef::template ReducerGradient<float, CPUContext>,\n        true /*GradientNeedIndices*/>);\n// gradient of LengthsSum\nOPERATOR_SCHEMA(LengthsIndicesInGradientSumGradient).NumInputs(3).NumOutputs(1);\nREGISTER_CPU_OPERATOR(\n    LengthsIndicesInGradientSumGradient,\n    AbstractLengthsGradientOp<\n        float,\n        int,\n        CPUContext,\n        SumReducerDef::template ReducerGradient<float, CPUContext>,\n        true /*GradientNeedIndices*/>);\n\nnamespace {\n\ntemplate <typename Def>\nstring FormatDoc() {\n  string doc = Def::doc;\n  ReplaceAll(doc, \"{op}\", Def::OpDef::name);\n  ReplaceAll(doc, \"{op_doc}\", Def::OpDef::doc);\n  return doc;\n}\n\n// Helper macro when the main op is defined elsewhere, and we only need to\n// define the schema, and the gradient op.\n#define REGISTER_SEGMENT_DEF_SCHEMA_GRADIENT_ONLY(...)                         \\\n  OPERATOR_SCHEMA_STR(                                                         \\\n      string(__VA_ARGS__::basename) + (__VA_ARGS__::OpDef::name))              \\\n      .NumInputs(__VA_ARGS__::ForwardOp::kNumInputs)                           \\\n      .NumOutputs(1)                                                           \\\n      .SetDoc(FormatDoc<__VA_ARGS__>())                                        \\\n      .Output(0, \"OUTPUT\", \"Aggregated tensor\")                                \\\n      .FillUsing(__VA_ARGS__::PopulateSchema);                                 \\\n  REGISTER_CPU_OPERATOR_STR(                                                   \\\n      string(__VA_ARGS__::basename) + (__VA_ARGS__::OpDef::name) + \"Gradient\", \\\n      __VA_ARGS__::BackwardOp);                                                \\\n  OPERATOR_SCHEMA_STR(                                                         \\\n      string(__VA_ARGS__::basename) + (__VA_ARGS__::OpDef::name) + \"Gradient\") \\\n      .NumInputs(__VA_ARGS__::BackwardOp::kNumInputs)                          \\\n      .NumOutputs(1);                                                          \\\n  REGISTER_GRADIENT_STR(                                                       \\\n      string(__VA_ARGS__::basename) + (__VA_ARGS__::OpDef::name),              \\\n      __VA_ARGS__::GetGradient)\n\n#define REGISTER_SEGMENT_DEF(...)                                 \\\n  REGISTER_CPU_OPERATOR_STR(                                      \\\n      string(__VA_ARGS__::basename) + (__VA_ARGS__::OpDef::name), \\\n      __VA_ARGS__::ForwardOp);                                    \\\n  REGISTER_SEGMENT_DEF_SCHEMA_GRADIENT_ONLY(__VA_ARGS__)\n\nREGISTER_SEGMENT_DEF(\n    AbstractSortedSegmentRangeDef<float, int, CPUContext, SumRangeReducerDef>);\nREGISTER_SEGMENT_DEF(AbstractSortedSegmentRangeDef<\n                     float,\n                     int,\n                     CPUContext,\n                     LogSumExpRangeReducerDef>);\nREGISTER_SEGMENT_DEF(AbstractSortedSegmentRangeDef<\n                     float,\n                     int,\n                     CPUContext,\n                     LogMeanExpRangeReducerDef>);\nREGISTER_SEGMENT_DEF(\n    AbstractSortedSegmentRangeDef<float, int, CPUContext, MeanRangeReducerDef>);\nREGISTER_SEGMENT_DEF(\n    AbstractSortedSegmentRangeDef<float, int, CPUContext, MaxRangeReducerDef>);\n\n#define REGISTER_REDUCER_WITH_OPS(reducer_def)                              \\\n  REGISTER_SEGMENT_DEF(                                                     \\\n      AbstractSortedSegmentDef<float, int, CPUContext, reducer_def>);       \\\n  REGISTER_SEGMENT_DEF(                                                     \\\n      AbstractSparseSortedSegmentDef<float, int, CPUContext, reducer_def>); \\\n  REGISTER_SEGMENT_DEF(                                                     \\\n      AbstractUnsortedSegmentDef<float, int, CPUContext, reducer_def>);     \\\n  REGISTER_SEGMENT_DEF(                                                     \\\n      AbstractSparseUnsortedSegmentDef<float, int, CPUContext, reducer_def>)\n\n#define REGISTER_REDUCER_WITH_LENGTH_OPS(reducer_def, GradientNeedIndices) \\\n  REGISTER_SEGMENT_DEF(AbstractLengthsDef<                                 \\\n                       float,                                              \\\n                       int,                                                \\\n                       CPUContext,                                         \\\n                       reducer_def,                                        \\\n                       GradientNeedIndices>)\n\n#define REGISTER_REDUCER_WITH_ALL_OPS(reducer_def)             \\\n  REGISTER_SEGMENT_DEF(                                        \\\n      AbstractReduceFrontDef<float, CPUContext, reducer_def>); \\\n  REGISTER_REDUCER_WITH_OPS(reducer_def)                       \\\n  REGISTER_REDUCER_WITH_LENGTH_OPS(reducer_def, false)\n\nREGISTER_REDUCER_WITH_OPS(SumReducerDef);\nREGISTER_REDUCER_WITH_LENGTH_OPS(SumReducerDef, true);\n\nREGISTER_REDUCER_WITH_OPS(MeanReducerDef);\nREGISTER_REDUCER_WITH_LENGTH_OPS(MeanReducerDef, false);\n\nREGISTER_REDUCER_WITH_ALL_OPS(WeightedSumReducerDef);\n\n// SparseLengths[Sum,WeightedSum,Mean] are now implemented separately,\n// so we only rely to the historical implementation for the backward + schema.\nREGISTER_SEGMENT_DEF_SCHEMA_GRADIENT_ONLY(AbstractSparseLengthsDef<\n                                          float,\n                                          int,\n                                          CPUContext,\n                                          SumReducerDef,\n                                          true /*GradientNeedIndices*/>)\nREGISTER_SEGMENT_DEF_SCHEMA_GRADIENT_ONLY(AbstractSparseLengthsDef<\n                                          float,\n                                          int,\n                                          CPUContext,\n                                          WeightedSumReducerDef,\n                                          true /*GradientNeedIndices*/>)\n\nREGISTER_SEGMENT_DEF_SCHEMA_GRADIENT_ONLY(\n    AbstractSparseLengthsDef<float, int, CPUContext, MeanReducerDef>)\n\n// Auxiliary output gradients are currently implemented only for Lengths version\n#define REGISTER_GRADIENT_WITH_MAIN_INPUT(...)                     \\\n  REGISTER_CPU_OPERATOR_STR(                                       \\\n      string(__VA_ARGS__::basename) + (__VA_ARGS__::OpDef::name) + \\\n          \"WithMainInputGradient\",                                 \\\n      __VA_ARGS__::WithMainInputBackwardOp);                       \\\n  OPERATOR_SCHEMA_STR(                                             \\\n      string(__VA_ARGS__::basename) + (__VA_ARGS__::OpDef::name) + \\\n      \"WithMainInputGradient\")                                     \\\n      .NumInputs(__VA_ARGS__::WithMainInputBackwardOp::kNumInputs) \\\n      .NumOutputs(1, INT_MAX)\nREGISTER_GRADIENT_WITH_MAIN_INPUT(\n    AbstractLengthsDef<float, int, CPUContext, WeightedSumReducerDef>);\nREGISTER_GRADIENT_WITH_MAIN_INPUT(\n    AbstractSparseLengthsDef<float, int, CPUContext, WeightedSumReducerDef>);\n\n#define REGISTER_GRADIENT_WITH_MAIN_INPUT_AND_FORWARD_OUTPUT(...)           \\\n  REGISTER_CPU_OPERATOR_STR(                                                \\\n      string(__VA_ARGS__::basename) + (__VA_ARGS__::OpDef::name) +          \\\n          \"WithMainInputAndForwardOutputGradient\",                          \\\n      __VA_ARGS__::WithMainInputAndForwardOutputBackwardOp);                \\\n  OPERATOR_SCHEMA_STR(                                                      \\\n      string(__VA_ARGS__::basename) + (__VA_ARGS__::OpDef::name) +          \\\n      \"WithMainInputAndForwardOutputGradient\")                              \\\n      .NumInputs(                                                           \\\n          __VA_ARGS__::WithMainInputAndForwardOutputBackwardOp::kNumInputs) \\\n      .NumOutputs(1, INT_MAX)\n\n#define REGISTER_SEGMENT_DEF_MAIN_INPUT_AND_FORWARD_OUTPUT_GRADIENT(...) \\\n  OPERATOR_SCHEMA_STR(                                                   \\\n      string(__VA_ARGS__::basename) + (__VA_ARGS__::OpDef::name))        \\\n      .NumInputs(__VA_ARGS__::ForwardOp::kNumInputs)                     \\\n      .NumOutputs(1)                                                     \\\n      .SetDoc(FormatDoc<__VA_ARGS__>())                                  \\\n      .Output(0, \"OUTPUT\", \"Aggregated tensor\")                          \\\n      .FillUsing(__VA_ARGS__::PopulateSchema);                           \\\n  REGISTER_GRADIENT_WITH_MAIN_INPUT_AND_FORWARD_OUTPUT(__VA_ARGS__);     \\\n  REGISTER_GRADIENT_STR(                                                 \\\n      string(__VA_ARGS__::basename) + (__VA_ARGS__::OpDef::name),        \\\n      __VA_ARGS__::GetGradient)\n\n// This implements and registers a length op with a gradient which requires\n// the main input as well as the output of the forward output.\n#define REGISTER_LENGTHS_OPS_MAIN_INPUT_AND_FORWARD_OUTPUT_GRADIENT(...) \\\n  REGISTER_CPU_OPERATOR_STR(                                             \\\n      string(__VA_ARGS__::basename) + (__VA_ARGS__::OpDef::name),        \\\n      __VA_ARGS__::ForwardOp);                                           \\\n  REGISTER_SEGMENT_DEF_MAIN_INPUT_AND_FORWARD_OUTPUT_GRADIENT(__VA_ARGS__)\n\nREGISTER_LENGTHS_OPS_MAIN_INPUT_AND_FORWARD_OUTPUT_GRADIENT(\n    AbstractLengthsDef<float, int, CPUContext, MaxReducerDef>);\n}\n}\n"
  },
  {
    "path": "caffe2/operators/segment_reduction_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SEGMENT_REDUCTION_OP_H_\n#define CAFFE2_OPERATORS_SEGMENT_REDUCTION_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/reducer_functors.h\"\n\nnamespace caffe2 {\n\ntemplate <typename TData>\nclass BaseInputAccessor {\n public:\n  BaseInputAccessor() {}\n\n  bool observeInput(const Tensor<CPUContext>& dataInput) {\n    data_ = dataInput.raw_data();\n    return dataInput.template IsType<TData>();\n  }\n\n  inline const TData*\n  getBlockPtr(TIndex in_block_size, TIndex idx, TIndex /* blocks */ = 1) {\n    return static_cast<const TData*>(data_) + in_block_size * idx;\n  }\n\n protected:\n  const void* data_ = nullptr;\n};\n\n////////////////////////////////////////////////////////////////////////////////\n// Range reducer ops: leverage that input segment is continuous and allow\n// reducer functors to do something special\n// Note: for now there are no real use cases for it yet :)\n// Also, doesn't support additional arguments for now\n////////////////////////////////////////////////////////////////////////////////\n\n/**\n * Base implementation for segment reduction op that leverages continuity of the\n * data\n *\n * Assumes that segments are sorted and there are no skip indices\n */\ntemplate <\n    typename T,\n    typename SIndex,\n    class Context,\n    class RangeReducer,\n    class InputAccessor = BaseInputAccessor<T>>\nclass AbstractSortedSegmentRangeOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(AbstractSortedSegmentRangeOp);\n\n  bool RunOnDevice() override {\n    auto& dataInput = Input(DATA);\n    auto& segment_ids = Input(SEGMENT_IDS);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_EQ(1, segment_ids.ndim(), \"SEGMENT_IDS must be a vector\");\n    auto N = segment_ids.dim(0);\n    CAFFE_ENFORCE_EQ(\n        N,\n        dataInput.dim(0),\n        \"SEGMENT_IDS must have the same length as outer dimension of DATA\");\n\n    OPERATOR_NEEDS_FEATURE(\n        inputAccessor_.observeInput(dataInput),\n        \"Unsupported input type: \",\n        dataInput.meta().name(),\n        \".\");\n\n    const SIndex* s_ids = segment_ids.template data<SIndex>();\n\n    const SIndex K = N > 0 ? s_ids[N - 1] + 1 : 0;\n    auto shape = dataInput.dims();\n    shape[0] = K;\n    output->Resize(shape);\n\n    T* out = output->template mutable_data<T>();\n\n    if (N == 0) {\n      return true;\n    }\n\n    TIndex block_size = dataInput.size() / N;\n\n    // Assume the segments are sorted and there are no gaps\n    CAFFE_ENFORCE_EQ(0, s_ids[0], \"Indices must be sorted and not have gaps\");\n    for (TIndex i = 0; i < N;) {\n      TIndex start = i;\n      for (++i; i < N && s_ids[start] == s_ids[i]; ++i)\n        ;\n\n      RangeReducer()(\n          block_size,\n          i - start,\n          inputAccessor_.getBlockPtr(block_size, start, i - start),\n          out + block_size * s_ids[start],\n          &context_);\n\n      // check correctness of the next segment\n      if (i < N) {\n        CAFFE_ENFORCE_EQ(\n            s_ids[start] + 1,\n            s_ids[i],\n            \"Indices must be sorted and not have gaps\");\n      }\n    }\n    return true;\n  }\n\n  static constexpr int kNumInputs = 2;\n  INPUT_TAGS(DATA, SEGMENT_IDS);\n\n private:\n  InputAccessor inputAccessor_;\n};\n\ntemplate <\n    typename T,\n    typename SIndex,\n    class Context,\n    class RangeReducerGradient>\nclass AbstractSortedSegmentRangeGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(AbstractSortedSegmentRangeGradientOp);\n\n  bool RunOnDevice() override {\n    // TODO(azzolini): avoid using input/output if not used by a particular op\n    auto& data_in = Input(DATA_IN);\n    auto& data_out = Input(DATA_OUT);\n    auto& segment_grads = Input(SEGMENT_GRADS);\n    auto& segment_ids = Input(SEGMENT_IDS);\n    auto* data_grads = Output(0);\n\n    CAFFE_ENFORCE_EQ(1, segment_ids.ndim(), \"SEGMENT_IDS must be a vector\");\n    TIndex N = segment_ids.dim(0);\n\n    const SIndex* s_ids = segment_ids.template data<SIndex>();\n    const T* s_grads = segment_grads.template data<T>();\n    const T* d_in = data_in.template data<T>();\n    const T* d_out = data_out.template data<T>();\n\n    auto shape = segment_grads.dims();\n    shape[0] = N;\n    data_grads->Resize(shape);\n\n    const SIndex K = segment_grads.dim(0);\n    T* out = data_grads->template mutable_data<T>();\n\n    if (N == 0) {\n      return true;\n    }\n\n    TIndex block_size = segment_grads.size_from_dim(1);\n\n    // Assume the segments are sorted and there are no gaps\n    CAFFE_ENFORCE_EQ(0, s_ids[0], \"Indices must be sorted and not have gaps\");\n    // repeat the check from forward op\n    CAFFE_ENFORCE_EQ(\n        K - 1, s_ids[N - 1], \"Indices must be sorted and not have gaps\");\n    for (TIndex i = 0; i < N;) {\n      TIndex start = i;\n      for (++i; i < N && s_ids[start] == s_ids[i]; ++i)\n        ;\n\n      auto expanded_idx = block_size * start;\n      auto reduced_idx = block_size * s_ids[start];\n      RangeReducerGradient()(\n          block_size,\n          i - start,\n          s_grads + reduced_idx,\n          out + expanded_idx,\n          d_in + expanded_idx,\n          d_out + reduced_idx,\n          &context_);\n\n      // check correctness of the next segment\n      if (i < N) {\n        CAFFE_ENFORCE_EQ(\n            s_ids[start] + 1,\n            s_ids[i],\n            \"Indices must be sorted and not have gaps\");\n      }\n    }\n    return true;\n  }\n\n  static constexpr int kNumInputs = 4;\n  INPUT_TAGS(DATA_IN, DATA_OUT, SEGMENT_GRADS, SEGMENT_IDS);\n};\n\ntemplate <typename T, typename SIndex, typename Context, typename ReducerDef>\nstruct AbstractSortedSegmentRangeDef {\n  using OpDef = ReducerDef;\n  static constexpr const char* basename = \"SortedSegmentRange\";\n  static constexpr const char* doc = R\"DOC(\nApplies '{op}' to each segment of input tensor. In order to allow for more\nefficient implementation of '{op}', the input segments have to be contiguous\nand non-empty.\n\nSEGMENT_IDS is a vector that maps each of the first dimension slices of the\nDATA to a particular group (segment). Values belonging to the same segment are\naggregated together.\n\nThe first dimension of the output is equal to the number of input segments,\ni.e. `SEGMENT_IDS[-1]+1`. Other dimensions are inherited from the input tensor.\n\n{op_doc}\n  )DOC\";\n  static void PopulateSchema(OpSchema& schema) {\n    schema.Input(0, \"DATA\", \"Input tensor to be aggregated\");\n    schema.Input(\n        1,\n        \"SEGMENT_IDS\",\n        \"Vector with the same length as the first dimension of DATA \"\n        \"and values in the range 0..K-1 and in increasing order that \"\n        \"maps each slice of DATA to one of the segments\");\n    schema.Output(\n        0,\n        \"OUTPUT\",\n        \"Aggregated tensor with the first dimension of K and the \"\n        \"other dimentsions inherited from DATA\");\n  }\n  using ForwardOp = AbstractSortedSegmentRangeOp<\n      T,\n      SIndex,\n      Context,\n      typename ReducerDef::template Reducer<T, Context>>;\n  using BackwardOp = AbstractSortedSegmentRangeGradientOp<\n      T,\n      SIndex,\n      Context,\n      typename ReducerDef::template ReducerGradient<T, Context>>;\n  struct GetGradient : public GradientMakerBase {\n    using GradientMakerBase::GradientMakerBase;\n    vector<OperatorDef> GetGradientDefs() override {\n      return SingleGradientDef(\n          string(basename) + ReducerDef::name + \"Gradient\",\n          \"\",\n          vector<string>{I(0), O(0), GO(0), I(1)},\n          // no gradient on segment_ids!\n          vector<string>{GI(0)});\n    }\n  };\n};\n\n////////////////////////////////////////////////////////////////////////////////\n// Incremental reducer ops: assume that reducer consumes pieces of data one by\n// one. Also, supports additional arguments passed to reducer, e.g. scalers for\n// weighted sum.\n//\n// Note: in current implementation additional inputs are considered auxiliary\n// constants and have limitations:\n// - there is no gradient computation for auxiliary inputs\n// - auxiliary inputs aren't affected by fused embedding lookup in operations\n// like sparse_sorted_segment\n////////////////////////////////////////////////////////////////////////////////\n\n/**\n * @brief Simple non-segmented reduction over the first few dimensions of the\n * tensor\n *\n * Inputs:\n *   0: DATA - input embedding to do lookups in\n *   1..P: AUX_ARG_<I> - optional additional arguments to be passed to the\n *                       reducer\n *\n * Args:\n *   num_reduce_dim (default 1) - the number of dims in front of the tensor to\n *                                reduce\n *\n * Output:\n *   Tensor without the first `num_dim` dimensions of DATA\n */\ntemplate <\n    typename T,\n    class Context,\n    class Reducer,\n    bool FirstDim,\n    class InputAccessor = BaseInputAccessor<T>>\nclass AbstractReduceFrontOrBackOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  AbstractReduceFrontOrBackOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        OP_SINGLE_ARG(int, \"num_reduce_dim\", num_reduce_dims_, 1) {}\n\n  bool RunOnDevice() override {\n    auto& data = Input(0);\n    // If more complicated fixed size logic becomes necessary, it can be moved\n    // to the reducer class\n    TIndex in_block_size = FirstDim\n        ? data.size_from_dim(num_reduce_dims_)\n        : data.size_to_dim(data.ndim() - num_reduce_dims_);\n    return DispatchHelper<typename Reducer::FixedDispatch>::call(\n        this, in_block_size);\n  }\n\n  template <int FixedSize>\n  bool DoRunWithValue() {\n    auto& data = Input(0);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_LE(num_reduce_dims_, data.ndim());\n\n    typename Reducer::Meta ctx(FirstDim);\n    ctx.observeInput(0, data, num_reduce_dims_);\n    for (int i = 1; i < Reducer::kInputCount; ++i) {\n      auto& aux_in = Input(i);\n      ctx.observeInput(i, aux_in, num_reduce_dims_);\n    }\n\n    OPERATOR_NEEDS_FEATURE(\n        inputAccessor_.observeInput(data),\n        \"Unsupported input type: \",\n        data.meta().name(),\n        \".\");\n\n    vector<TIndex> shape;\n    ctx.appendOutputShape(&shape);\n    output->Resize(shape);\n\n    T* out = output->template mutable_data<T>();\n\n    const int block_size = FirstDim\n        ? data.size_from_dim(num_reduce_dims_)\n        : data.size_from_dim(data.ndim() - num_reduce_dims_);\n\n    const int num_blocks = block_size > 0 ? data.size() / block_size : 0;\n\n    Reducer r(ctx, out, &context_);\n    for (TIndex i = 0; i < num_blocks; ++i) {\n      r.template process<FixedSize>(\n          ctx, inputAccessor_.getBlockPtr(block_size, i), i, &context_);\n    }\n    r.template finish<FixedSize>(ctx, &context_);\n    return true;\n  }\n\n  static constexpr int kNumInputs = Reducer::kInputCount;\n\n private:\n  int num_reduce_dims_;\n  InputAccessor inputAccessor_;\n};\n\ntemplate <\n    typename T,\n    class Context,\n    class ReducerGradient,\n    bool FirstDim = true>\nclass AbstractReduceFrontOrBackGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  AbstractReduceFrontOrBackGradientOp(\n      const OperatorDef& operator_def,\n      Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        OP_SINGLE_ARG(int, \"num_reduce_dim\", num_reduce_dims_, 1) {}\n\n  bool RunOnDevice() override {\n    // If more complicated fixed size logic becomes necessary, it can be moved\n    // to the reducer class\n    TIndex grad_block_size = Input(REDUCTION_GRAD).size();\n    return DispatchHelper<typename ReducerGradient::FixedDispatch>::call(\n        this, grad_block_size);\n  }\n\n  template <int FixedSize>\n  bool DoRunWithValue() {\n    auto& reduction_grad = Input(REDUCTION_GRAD);\n    auto& source_shape = OperatorBase::Input<TensorCPU>(SOURCE_SHAPE);\n\n    auto* data_grads = Output(0);\n\n    typename ReducerGradient::Meta ctx(reduction_grad, 0, FirstDim);\n    for (int i = 0; i < ReducerGradient::originalInputs().size(); ++i) {\n      auto& aux_in = Input(i);\n      ctx.observeOriginalInput(\n          ReducerGradient::originalInputs()[i],\n          aux_in,\n          nullptr, /*no grad*/\n          num_reduce_dims_);\n    }\n\n    const T* r_grad = reduction_grad.template data<T>();\n\n    CAFFE_ENFORCE_LE(num_reduce_dims_, source_shape.size());\n\n    vector<TIndex> shape(\n        source_shape.template data<TIndex>(),\n        source_shape.template data<TIndex>() + source_shape.size());\n\n    data_grads->Resize(shape);\n\n    TIndex block_size = FirstDim\n        ? data_grads->size_from_dim(num_reduce_dims_)\n        : data_grads->size_from_dim(data_grads->ndim() - num_reduce_dims_);\n    TIndex block_num = block_size > 0 ? data_grads->size() / block_size : 0;\n\n    T* out = data_grads->template mutable_data<T>();\n\n    ReducerGradient r(ctx, r_grad, &context_);\n    for (TIndex i = 0; i < block_num; ++i) {\n      r.template fillGrad<FixedSize>(\n          ctx,\n          out + block_size * i,\n          i,\n          &context_,\n          FirstDim ? block_num : block_size);\n    }\n    return true;\n  }\n\n  static constexpr int kNumInputs =\n      ReducerGradient::originalInputs().size() + 2;\n  enum _InputTags {\n    REDUCTION_GRAD = ReducerGradient::originalInputs().size(),\n    SOURCE_SHAPE\n  };\n\n private:\n  int num_reduce_dims_;\n};\n\ntemplate <typename T, typename Context, typename ReducerDef>\nstruct AbstractReduceFrontDef {\n  using OpDef = ReducerDef;\n  static constexpr const char* basename = \"ReduceFront\";\n  static constexpr const char* doc = R\"DOC(\nReduces the input tensor along the first dimension of the input tensor by\napplying '{op}'. This op acts in a similar way to SortedSegment{op} and\nUnsortedSegment{op} but as if all input slices belong to a single segment.\n\n{op_doc}\n  )DOC\";\n  static void PopulateSchema(OpSchema& schema) {\n    schema.Input(\n        0, \"DATA\", \"Input tensor to be reduced on the first dimension\");\n    schema.TensorInferenceFunction([](const OperatorDef& def,\n                                      const vector<TensorShape>& in) {\n      CAFFE_ENFORCE_EQ(1, in.size());\n      ArgumentHelper helper(def);\n      int num_reduce_dims = helper.GetSingleArgument<int>(\"num_reduce_dim\", 1);\n      typename ReducerDef::template Reducer<T, Context>::Meta ctx(true);\n      vector<TIndex> out_dims = ctx.getOutputShape(in[0], num_reduce_dims);\n      return vector<TensorShape>{\n          CreateTensorShape(out_dims, in[0].data_type())};\n    });\n    ReducerDef::PopulateSchema(schema);\n  }\n  using ReducerGradient =\n      typename ReducerDef::template ReducerGradient<T, Context>;\n  using ForwardOp = AbstractReduceFrontOrBackOp<\n      T,\n      Context,\n      typename ReducerDef::template Reducer<T, Context>,\n      true>;\n  using BackwardOp =\n      AbstractReduceFrontOrBackGradientOp<T, Context, ReducerGradient, true>;\n  struct GetGradient : public GradientMakerBase {\n    using GradientMakerBase::GradientMakerBase;\n    vector<OperatorDef> GetGradientDefs() override {\n      // Have utility function generating these names?\n      string tmp_dims = \"_\" + O(0) + \"_dims\";\n\n      vector<string> grad_ins;\n      for (const int i : ReducerGradient::originalInputs()) {\n        grad_ins.push_back(I(i));\n      }\n      grad_ins.push_back(GO(0));\n      grad_ins.push_back(tmp_dims);\n\n      vector<Argument> args;\n      if (ArgumentHelper::HasArgument(def_, \"num_reduce_dim\")) {\n        args.push_back(GetArgument(def_, \"num_reduce_dim\"));\n      }\n      // FIXME: pass in num_reduce_dims?!\n      return vector<OperatorDef>{\n          CreateOperatorDef(\n              \"Shape\", \"\", vector<string>{I(0)}, vector<string>{tmp_dims}),\n          CreateOperatorDef(\n              string(basename) + ReducerDef::name + \"Gradient\",\n              \"\",\n              grad_ins,\n              // no gradient on auxiliary inputs for now\n              vector<string>{GI(0)}),\n      };\n    }\n  };\n};\n\ntemplate <typename T, typename Context, typename ReducerDef>\nstruct AbstractReduceBackDef {\n  using OpDef = ReducerDef;\n  static constexpr const char* basename = \"ReduceBack\";\n  static constexpr const char* doc = R\"DOC(\nReduces the input tensor along the last dimension of the input tensor by\napplying '{op}'. This op acts in a similar way to SortedSegment{op} and\nUnsortedSegment{op} but as if all input slices belong to a single segment.\n\n{op_doc}\n  )DOC\";\n  static void PopulateSchema(OpSchema& schema) {\n    schema.Input(\n        0, \"DATA\", \"Input tensor to be reduced on the first dimension\");\n    schema.TensorInferenceFunction([](const OperatorDef& def,\n                                      const vector<TensorShape>& in) {\n      CAFFE_ENFORCE_EQ(1, in.size());\n      ArgumentHelper helper(def);\n      int num_reduce_dims = helper.GetSingleArgument<int>(\"num_reduce_dim\", 1);\n      typename ReducerDef::template Reducer<T, Context>::Meta ctx(false);\n      vector<TIndex> out_dims = ctx.getOutputShape(in[0], num_reduce_dims);\n      return vector<TensorShape>{\n          CreateTensorShape(out_dims, in[0].data_type())};\n    });\n    ReducerDef::PopulateSchema(schema);\n  }\n  using ReducerGradient =\n      typename ReducerDef::template ReducerGradient<T, Context>;\n  using ForwardOp = AbstractReduceFrontOrBackOp<\n      T,\n      Context,\n      typename ReducerDef::template Reducer<T, Context>,\n      false>;\n  using BackwardOp =\n      AbstractReduceFrontOrBackGradientOp<T, Context, ReducerGradient, false>;\n  struct GetGradient : public GradientMakerBase {\n    using GradientMakerBase::GradientMakerBase;\n    vector<OperatorDef> GetGradientDefs() override {\n      // Have utility function generating these names?\n      string tmp_dims = \"_\" + O(0) + \"_dims\";\n\n      vector<string> grad_ins;\n      for (const int i : ReducerGradient::originalInputs()) {\n        grad_ins.push_back(I(i));\n      }\n      grad_ins.push_back(GO(0));\n      grad_ins.push_back(tmp_dims);\n\n      vector<Argument> args;\n      if (ArgumentHelper::HasArgument(def_, \"num_reduce_dim\")) {\n        args.push_back(GetArgument(def_, \"num_reduce_dim\"));\n      }\n      // FIXME: pass in num_reduce_dims?!\n      return vector<OperatorDef>{\n          CreateOperatorDef(\n              \"Shape\", \"\", vector<string>{I(0)}, vector<string>{tmp_dims}),\n          CreateOperatorDef(\n              string(basename) + ReducerDef::name + \"Gradient\",\n              \"\",\n              grad_ins,\n              // no gradient on auxiliary inputs for now\n              vector<string>{GI(0)}),\n      };\n    }\n  };\n};\n\n/**\n * @brief Segment reduction op with optional fused embedding lookup\n *\n * Base implementation for SortedSegmentXXX and SparseSortedSegmentXXX depending\n * on SparseFused static argument.\n *\n * Inputs:\n *   0: DATA - input embedding to do lookups in\n *   1..P: AUX_ARG_<I> - optional additional arguments to be passed to the\n *                       reducer, should have the same first dimension as\n *                       SEGMENT_IDS (e.g. scalars in WeightedSum)\n *   # if SparseFused == true:\n *   P+1: INDICES - 1-D vector with indices to look up in DATA. Should have the\n *                  same dimension as SEGMENT_IDS\n *   # P+1 if SparseFused == false:\n *   P+1 or P+2: SEGMENT_IDS - sorted segment ids 1-D vector\n *\n * Output:\n *   Tensor with first dimension of K, where K is the max segment id + 1. Rest\n *   of dimensions are decided by reducer but usually are the same size as extra\n *   dimensions of DATA\n */\ntemplate <\n    typename T,\n    typename SIndex,\n    class Context,\n    class Reducer,\n    bool SparseFused = true,\n    class InputAccessor = BaseInputAccessor<T>>\nclass AbstractSortedSegmentOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(AbstractSortedSegmentOp);\n\n  bool RunOnDevice() override {\n    if (SparseFused) {\n      return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n          this, Input(INDICES));\n    } else {\n      // type doesn't matter\n      return DoRunWithType<TIndex>();\n    }\n  }\n\n  template <typename IndexType>\n  bool DoRunWithType() {\n    // If more complicated fixed size logic becomes necessary, it can be moved\n    // to the reducer class\n    TIndex in_block_size = Input(0).size_from_dim(1);\n    return DispatchHelper<typename Reducer::FixedDispatch, IndexType>::call(\n        this, in_block_size);\n  }\n\n  template <typename IndexType, int FixedSize>\n  bool DoRunWithValue() {\n    auto& dataInput = Input(0);\n    auto& segment_ids = Input(SEGMENT_IDS);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_EQ(1, segment_ids.ndim(), \"SEGMENT_IDS must be a vector\");\n    TIndex N = segment_ids.dim(0);\n    const TIndex M = dataInput.dim(0);\n\n    const IndexType* idxs;\n    if (SparseFused) { // static if\n      auto& indices = Input(INDICES);\n      CAFFE_ENFORCE_EQ(1, indices.ndim(), \"INDICES must be a vector\");\n      CAFFE_ENFORCE_EQ(\n          N,\n          indices.dim(0),\n          \"SEGMENT_IDS must have the same length as INDICES\");\n      idxs = indices.template data<IndexType>();\n    } else {\n      CAFFE_ENFORCE_EQ(\n          N, M, \"DATA must have the same first dimension as SEGMENT_IDS\");\n    }\n\n    // It would probably look nicer with varargs templates but it's too much\n    // metaprogramming\n    typename Reducer::Meta ctx;\n    ctx.observeInput(0, dataInput, 1);\n    for (int i = 1; i < Reducer::kInputCount; ++i) {\n      auto& aux_in = Input(i);\n      CAFFE_ENFORCE_EQ(\n          N,\n          aux_in.dim(0),\n          \"Input \",\n          i,\n          \" must have the same first dim as SEGMENT_IDS\");\n      ctx.observeInput(i, aux_in, 1);\n    }\n\n    OPERATOR_NEEDS_FEATURE(\n        inputAccessor_.observeInput(dataInput),\n        \"Unsupported input type: \",\n        dataInput.meta().name(),\n        \".\");\n\n    const SIndex* s_ids = segment_ids.template data<SIndex>();\n\n    const SIndex K = N > 0 ? s_ids[N - 1] + 1 : 0;\n    vector<TIndex> shape;\n    shape.push_back(K);\n    ctx.appendOutputShape(&shape);\n    output->Resize(shape);\n\n    T* out = output->template mutable_data<T>();\n    if (N == 0) {\n      return true;\n    }\n    TIndex in_block_size = dataInput.size_from_dim(1);\n    TIndex out_block_size = output->size_from_dim(1);\n\n    // Assume the segments are sorted and there are no gaps\n    CAFFE_ENFORCE_EQ(0, s_ids[0], \"Indices must be sorted and not have gaps\");\n    for (TIndex i = 0; i < N;) {\n      TIndex start = i;\n\n      Reducer r(ctx, out + out_block_size * s_ids[start], &context_);\n      for (; i < N && s_ids[start] == s_ids[i]; ++i) {\n        IndexType idx;\n        if (SparseFused) { // static if\n          CAFFE_ENFORCE(\n              0 <= idxs[i] && idxs[i] < M,\n              \"Index out of bounds: \",\n              idxs[i],\n              \", range 0 to \",\n              M);\n          idx = idxs[i];\n        } else {\n          idx = i;\n        }\n        r.template process<FixedSize>(\n            ctx, inputAccessor_.getBlockPtr(in_block_size, idx), i, &context_);\n      }\n\n      r.template finish<FixedSize>(ctx, &context_);\n      // check correctness of the next segment\n      if (i < N) {\n        CAFFE_ENFORCE_EQ(\n            s_ids[start] + 1,\n            s_ids[i],\n            \"Indices must be sorted and not have gaps\");\n      }\n    }\n    return true;\n  }\n\n  enum {\n    INDICES = Reducer::kInputCount,\n    SEGMENT_IDS = Reducer::kInputCount + (SparseFused ? 1 : 0)\n  };\n  static constexpr int kSelfInputs = SparseFused ? 2 : 1;\n  static constexpr int kNumInputs = Reducer::kInputCount + kSelfInputs;\n\n private:\n  InputAccessor inputAccessor_;\n};\n\n// Gradient actually doesn't depend on whether sparse lookup is fused or not\ntemplate <typename T, typename SIndex, class Context, class ReducerGradient>\nclass AbstractSortedSegmentGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(AbstractSortedSegmentGradientOp);\n\n  bool RunOnDevice() override {\n    // If more complicated fixed size logic becomes necessary, it can be moved\n    // to the reducer class\n    TIndex grad_block_size = Input(SEGMENT_GRADS).size_from_dim(1);\n    return DispatchHelper<typename ReducerGradient::FixedDispatch>::call(\n        this, grad_block_size);\n  }\n\n  template <int FixedSize>\n  bool DoRunWithValue() {\n    auto& segment_grads = Input(SEGMENT_GRADS);\n    auto& segment_ids = Input(SEGMENT_IDS);\n    auto* data_grads = Output(0);\n\n    CAFFE_ENFORCE_EQ(1, segment_ids.ndim(), \"SEGMENT_IDS must be a vector\");\n    TIndex N = segment_ids.dim(0);\n\n    typename ReducerGradient::Meta ctx(segment_grads, 1);\n    for (int i = 0; i < ReducerGradient::originalInputs().size(); ++i) {\n      auto& aux_in = Input(i);\n      CAFFE_ENFORCE_EQ(\n          N,\n          aux_in.dim(0),\n          \"Input \",\n          i,\n          \" must have the same first dim as SEGMENT_IDS\");\n      ctx.observeOriginalInput(\n          ReducerGradient::originalInputs()[i], aux_in, nullptr /*no grad*/, 1);\n    }\n\n    const SIndex* s_ids = segment_ids.template data<SIndex>();\n    const T* s_grads = segment_grads.template data<T>();\n\n    vector<TIndex> shape;\n    shape.push_back(N);\n    ctx.appendGradShape(&shape);\n    data_grads->Resize(shape);\n\n    TIndex d_block_size = data_grads->size_from_dim(1);\n    const SIndex K = segment_grads.dim(0);\n    TIndex s_block_size = segment_grads.size_from_dim(1);\n    T* out = data_grads->template mutable_data<T>();\n\n    if (N == 0) {\n      return true;\n    }\n\n    // Assume the segments are sorted and there are no gaps\n    CAFFE_ENFORCE_EQ(0, s_ids[0], \"Indices must be sorted and not have gaps\");\n    // repeat the check from forward op\n    CAFFE_ENFORCE_EQ(\n        K - 1, s_ids[N - 1], \"Indices must be sorted and not have gaps\");\n    for (TIndex i = 0; i < N;) {\n      TIndex start = i;\n      TIndex end = start;\n\n      if (ReducerGradient::computeLength()) {\n        for (; end < N && s_ids[start] == s_ids[end]; ++end) {\n        }\n      }\n\n      ReducerGradient r(ctx, s_grads + s_block_size * s_ids[start], &context_);\n      for (; i < N && s_ids[start] == s_ids[i]; ++i) {\n        r.template fillGrad<FixedSize>(\n            ctx, out + d_block_size * i, i, &context_, end - start);\n      }\n\n      // check correctness of the next segment\n      if (i < N) {\n        CAFFE_ENFORCE_EQ(\n            s_ids[start] + 1,\n            s_ids[i],\n            \"Indices must be sorted and not have gaps\");\n      }\n    }\n    return true;\n  }\n\n  // Input layout:\n  //   orig_arg1, orig_arg2, ..., orig_argN, SEGMENT_GRADS, SEGMENT_IDS\n  // orig_argXs represent original op's inputs and will be passed to the reducer\n  // directly\n  static constexpr int kNumInputs =\n      ReducerGradient::originalInputs().size() + 2;\n  enum _InputTags {\n    SEGMENT_GRADS = ReducerGradient::originalInputs().size(),\n    SEGMENT_IDS\n  };\n};\n\n// base implementation of sorted/unsorted sparse/non-sparse gradient computation\ntemplate <\n    typename ForwardOp,\n    typename ReducerDef,\n    typename ReducerGradient,\n    bool Sorted,\n    bool SparseFused>\nstruct SegmentOpGetGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE(\n        !ReducerGradient::requiresDataInput(Def()),\n        \"grads on aux inputs are not yet implemented for Segment operators.\");\n    vector<string> grad_ins;\n    for (const int i : ReducerGradient::originalInputs()) {\n      grad_ins.push_back(I(i));\n    }\n    grad_ins.push_back(GO(0));\n    grad_ins.push_back(I(ForwardOp::SEGMENT_IDS));\n    vector<OperatorDef> r{CreateOperatorDef(\n        string(Sorted ? \"SortedSegment\" : \"UnsortedSegment\") +\n            ReducerDef::name + \"Gradient\",\n        \"\",\n        grad_ins,\n        // no gradient on segment_ids or auxiliary inputs for now\n        vector<string>{SparseFused ? GI_V(0) : GI(0)})};\n    if (SparseFused) {\n      SetSparse(0, I(ForwardOp::INDICES), GI_V(0));\n    }\n    return r;\n  }\n};\n\ntemplate <typename T, typename SIndex, typename Context, typename ReducerDef>\nstruct AbstractSortedSegmentDef {\n  using OpDef = ReducerDef;\n  static constexpr const char* basename = \"SortedSegment\";\n  static constexpr const char* doc = R\"DOC(\nApplies '{op}' to each segment of input tensor. Segments need to be sorted and\ncontiguous. See also UnsortedSegment{op} that doesn't have this requirement.\n\nSEGMENT_IDS is a vector that maps each of the first dimension slices of the\nDATA to a particular group (segment). Values belonging to the same segment are\naggregated together.\n\nThe first dimension of the output is equal to the number of input segments,\ni.e. `SEGMENT_IDS[-1]+1`. Other dimensions are inherited from the input tensor.\n\n{op_doc}\n  )DOC\";\n  static void PopulateSchema(OpSchema& schema) {\n    schema.Input(0, \"DATA\", \"Input tensor, slices of which are aggregated.\");\n    schema.Input(\n        Reducer::kInputCount,\n        \"SEGMENT_IDS\",\n        \"Vector with the same length as the first dimension of DATA \"\n        \"and values in the range 0..K-1 and in increasing order that \"\n        \"maps each slice of DATA to one of the segments\");\n    schema.Output(\n        0,\n        \"OUTPUT\",\n        \"Aggregated output tensor. Has the first dimension of K \"\n        \"(the number of segments).\");\n    ReducerDef::PopulateSchema(schema);\n  }\n  using Reducer = typename ReducerDef::template Reducer<T, Context>;\n  using ReducerGradient =\n      typename ReducerDef::template ReducerGradient<T, Context>;\n  using ForwardOp = AbstractSortedSegmentOp<T, SIndex, Context, Reducer, false>;\n  using BackwardOp =\n      AbstractSortedSegmentGradientOp<T, SIndex, Context, ReducerGradient>;\n  using GetGradient = SegmentOpGetGradient<\n      ForwardOp,\n      ReducerDef,\n      ReducerGradient,\n      true /*Sorted*/,\n      false /*SparseFused*/>;\n};\n\ntemplate <typename T, typename SIndex, typename Context, typename ReducerDef>\nstruct AbstractSparseSortedSegmentDef {\n  using OpDef = ReducerDef;\n  static constexpr const char* basename = \"SparseSortedSegment\";\n  static constexpr const char* doc = R\"DOC(\nPulls in slices of the input tensor, groups them into segments and applies\n'{op}' to each segment. Segments need to be sorted and contiguous. See also\nSparseUnsortedSegment{op} that doesn't have this requirement.\n\nThis op is basically Gather and SortedSegment{op} fused together.\n\nINDICES should contain integers in range 0..N-1 where N is the first dimension\nof DATA. INDICES represent which slices of DATA need to be pulled in.\n\nSEGMENT_IDS is a vector that maps each referenced slice of the DATA to a\nparticular group (segment). Values belonging to the same segment are aggregated\ntogether. SEGMENT_IDS should have the same dimension as INDICES.\n\nThe first dimension of the output is equal to the number of input segments,\ni.e. `SEGMENT_IDS[-1]+1`. Other dimensions are inherited from the input tensor.\n\n{op_doc}\n  )DOC\";\n  static void PopulateSchema(OpSchema& schema) {\n    schema.Input(0, \"DATA\", \"Input tensor, slices of which are aggregated.\");\n    schema.Input(\n        Reducer::kInputCount,\n        \"INDICES\",\n        \"Integer vector containing indices of the first dimension of DATA for \"\n        \"the slices that are being aggregated\");\n    schema.Input(\n        Reducer::kInputCount + 1,\n        \"SEGMENT_IDS\",\n        \"Vector with the same length as INDICES and values in the range \"\n        \"0..K-1 and in increasing order that maps each slice of DATA referenced\"\n        \" by INDICES to one of the segments\");\n    schema.Output(\n        0,\n        \"OUTPUT\",\n        \"Aggregated output tensor. Has the first dimension of K \"\n        \"(the number of segments).\");\n    ReducerDef::PopulateSchema(schema);\n  }\n  using Reducer = typename ReducerDef::template Reducer<T, Context>;\n  using ReducerGradient =\n      typename ReducerDef::template ReducerGradient<T, Context>;\n  using ForwardOp = AbstractSortedSegmentOp<T, SIndex, Context, Reducer>;\n  // TODO(dzhulgakov): we're registering the same class twice here,\n  // consider avoiding op duplication here\n  using BackwardOp =\n      AbstractSortedSegmentGradientOp<T, SIndex, Context, ReducerGradient>;\n  using GetGradient = SegmentOpGetGradient<\n      ForwardOp,\n      ReducerDef,\n      ReducerGradient,\n      true /*Sorted*/,\n      true /*SparseFused*/>;\n};\n\n/**\n * @brief Unsorted segment reduction op with optional fused embedding lookup\n *\n * Base implementation for UnsortedSegmentXXX and UnsparseSortedSegmentXXX\n * depending on SparseFused static argument.\n *\n * Unlike the sorted version it allows to have \"gaps\" in segment ids.\n *\n * Inputs:\n *   0: DATA - input embedding to do lookups in\n *   1..P: AUX_ARG_<I> - optional additional arguments to be passed to the\n *                       reducer, should have the same first dimension as\n *                       SEGMENT_IDS (e.g. scalars in WeightedSum)\n *   # if SparseFused == true:\n *   P+1: INDICES - 1-D vector with indices to look up in DATA. Should have the\n *                  same dimension as SEGMENT_IDS\n *   # P+1 if SparseFused == false:\n *   P+1 or P+2: SEGMENT_IDS - unsorted segment ids 1-D vector\n *\n * Args:\n *   num_segments - allows to override the dimension of the output. If not set\n *                  it would be inferred from segment_ids tensor.\n *\n *\n * Output:\n *   Tensor with first dimension of K, where K is the max segment id + 1. Rest\n *   of dimensions are decided by reducer but usually are the same size as extra\n *   dimensions of DATA\n */\ntemplate <\n    typename T,\n    typename SIndex,\n    class Context,\n    class Reducer,\n    bool SparseFused = true,\n    class InputAccessor = BaseInputAccessor<T>>\nclass AbstractUnsortedSegmentOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  AbstractUnsortedSegmentOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        OP_SINGLE_ARG(int, \"num_segments\", num_segments_, -1) {}\n\n  bool RunOnDevice() override {\n    if (SparseFused) {\n      return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n          this, Input(INDICES));\n    } else {\n      // type doesn't matter\n      return DoRunWithType<TIndex>();\n    }\n  }\n\n  template <typename IndexType>\n  bool DoRunWithType() {\n    // If more complicated fixed size logic becomes necessary, it can be moved\n    // to the reducer class\n    TIndex in_block_size = Input(0).size_from_dim(1);\n    return DispatchHelper<typename Reducer::FixedDispatch, IndexType>::call(\n        this, in_block_size);\n  }\n\n  template <typename IndexType, int FixedSize>\n  bool DoRunWithValue() {\n    auto& data = Input(0);\n    auto& segment_ids = Input(SEGMENT_IDS);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_EQ(1, segment_ids.ndim(), \"SEGMENT_IDS must be a vector\");\n    TIndex N = segment_ids.dim(0);\n    const TIndex M = data.dim(0);\n\n    const IndexType* idxs;\n    if (SparseFused) { // static if\n      auto& indices = Input(INDICES);\n      CAFFE_ENFORCE_EQ(1, indices.ndim(), \"INDICES must be a vector\");\n      CAFFE_ENFORCE_EQ(\n          N,\n          indices.dim(0),\n          \"SEGMENT_IDS must have the same length as INDICES\");\n      idxs = indices.template data<IndexType>();\n    } else {\n      CAFFE_ENFORCE_EQ(\n          N, M, \"DATA must have the same first dimension as SEGMENT_IDS\");\n    }\n\n    // It would probably look nicer with varargs templates but it's too much\n    // metaprogramming\n    typename Reducer::Meta ctx;\n    ctx.observeInput(0, data, 1);\n    for (int i = 1; i < Reducer::kInputCount; ++i) {\n      auto& aux_in = Input(i);\n      CAFFE_ENFORCE_EQ(\n          N,\n          aux_in.dim(0),\n          \"Input \",\n          i,\n          \" must have the same first dim as SEGMENT_IDS\");\n      ctx.observeInput(i, aux_in, 1);\n    }\n\n    const SIndex* s_ids = segment_ids.template data<SIndex>();\n    OPERATOR_NEEDS_FEATURE(\n        inputAccessor_.observeInput(data),\n        \"Unsupported input type: \",\n        data.meta().name(),\n        \".\");\n\n    // determine the number of segments\n    SIndex K;\n    if (num_segments_ != -1) {\n      K = num_segments_;\n    } else {\n      K = 0;\n      for (TIndex i = 0; i < N; ++i) {\n        K = std::max(K, s_ids[i] + 1);\n      }\n    }\n\n    vector<TIndex> shape;\n    shape.push_back(K);\n    ctx.appendOutputShape(&shape);\n    output->Resize(shape);\n\n    TIndex in_block_size = data.size_from_dim(1);\n    TIndex out_block_size = output->size_from_dim(1);\n    T* out = output->template mutable_data<T>();\n\n    reducers_.clear();\n    reducers_.reserve(K);\n    for (TIndex i = 0; i < K; ++i) {\n      reducers_.emplace_back(ctx, out + out_block_size * i, &context_);\n    }\n\n    for (TIndex i = 0; i < N; ++i) {\n      auto s_id = s_ids[i];\n      CAFFE_ENFORCE(\n          0 <= s_id && s_id < K,\n          \"Segment id out of range: \",\n          s_id,\n          \", range 0 to \",\n          K);\n      IndexType idx;\n      if (SparseFused) { // static if\n        CAFFE_ENFORCE(\n            0 <= idxs[i] && idxs[i] < M,\n            \"Index out of bounds: \",\n            idxs[i],\n            \", range 0 to \",\n            M);\n        idx = idxs[i];\n      } else {\n        idx = i;\n      }\n      reducers_[s_id].template process<FixedSize>(\n          ctx, inputAccessor_.getBlockPtr(in_block_size, idx), i, &context_);\n    }\n\n    for (TIndex i = 0; i < K; ++i) {\n      reducers_[i].template finish<FixedSize>(ctx, &context_);\n    }\n    // call reducers destructors (if there is any)\n    reducers_.clear();\n    return true;\n  }\n\n  enum {\n    INDICES = Reducer::kInputCount,\n    SEGMENT_IDS = Reducer::kInputCount + (SparseFused ? 1 : 0)\n  };\n  static constexpr int kSelfInputs = SparseFused ? 2 : 1;\n  static constexpr int kNumInputs = Reducer::kInputCount + kSelfInputs;\n\n private:\n  TIndex num_segments_;\n  // member field to reuse memory\n  vector<Reducer> reducers_;\n  InputAccessor inputAccessor_;\n};\n\n// Gradient actually doesn't depend on whether sparse lookup is fused or not\ntemplate <typename T, typename SIndex, class Context, class ReducerGradient>\nclass AbstractUnsortedSegmentGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(AbstractUnsortedSegmentGradientOp);\n\n  bool RunOnDevice() override {\n    // If more complicated fixed size logic becomes necessary, it can be moved\n    // to the reducer class\n    TIndex grad_block_size = Input(SEGMENT_GRADS).size_from_dim(1);\n    return DispatchHelper<typename ReducerGradient::FixedDispatch>::call(\n        this, grad_block_size);\n  }\n\n  template <int FixedSize>\n  bool DoRunWithValue() {\n    auto& segment_grads = Input(SEGMENT_GRADS);\n    auto& segment_ids = Input(SEGMENT_IDS);\n    auto* data_grads = Output(0);\n\n    CAFFE_ENFORCE_EQ(1, segment_ids.ndim(), \"SEGMENT_IDS must be a vector\");\n    TIndex N = segment_ids.dim(0);\n\n    typename ReducerGradient::Meta ctx(segment_grads, 1);\n    for (int i = 0; i < ReducerGradient::originalInputs().size(); ++i) {\n      auto& aux_in = Input(i);\n      CAFFE_ENFORCE_EQ(\n          N,\n          aux_in.dim(0),\n          \"Input \",\n          i,\n          \" must have the same first dim as SEGMENT_IDS\");\n      ctx.observeOriginalInput(\n          ReducerGradient::originalInputs()[i], aux_in, nullptr /*no grad*/, 1);\n    }\n\n    const SIndex* s_ids = segment_ids.template data<SIndex>();\n    const T* s_grads = segment_grads.template data<T>();\n\n    vector<TIndex> shape;\n    shape.push_back(N);\n    ctx.appendGradShape(&shape);\n    data_grads->Resize(shape);\n\n    TIndex d_block_size = data_grads->size_from_dim(1);\n    const SIndex K = segment_grads.dim(0);\n    TIndex s_block_size = segment_grads.size_from_dim(1);\n    T* out = data_grads->template mutable_data<T>();\n\n    if (ReducerGradient::computeLength()) {\n      segment_length_.resize(K, 0);\n      for (int i = 0; i < N; ++i) {\n        auto s_id = s_ids[i];\n        CAFFE_ENFORCE(\n            0 <= s_id && s_id < K,\n            \"Segment id out of range: \",\n            s_id,\n            \", range 0 to \",\n            K);\n        segment_length_[s_ids[i]]++;\n      }\n    }\n\n    reducers_.clear();\n    reducers_.reserve(K);\n    for (SIndex i = 0; i < K; ++i) {\n      reducers_.emplace_back(ctx, s_grads + s_block_size * i, &context_);\n    }\n\n    for (TIndex i = 0; i < N; ++i) {\n      auto s_id = s_ids[i];\n      if (ReducerGradient::computeLength()) {\n        reducers_[s_id].template fillGrad<FixedSize>(\n            ctx, out + d_block_size * i, i, &context_, segment_length_[s_id]);\n      } else {\n        reducers_[s_id].template fillGrad<FixedSize>(\n            ctx, out + d_block_size * i, i, &context_, 0);\n      }\n    }\n    // call reducers destructors (if there is any)\n    reducers_.clear();\n    return true;\n  }\n\n  // Input layout:\n  //   orig_arg1, orig_arg2, ..., orig_argN, SEGMENT_GRADS, SEGMENT_IDS\n  // orig_argXs represent original op's inputs and will be passed to the reducer\n  // directly\n  static constexpr int kNumInputs =\n      ReducerGradient::originalInputs().size() + 2;\n  enum _InputTags {\n    SEGMENT_GRADS = ReducerGradient::originalInputs().size(),\n    SEGMENT_IDS\n  };\n\n private:\n  // member field to reuse memory\n  vector<ReducerGradient> reducers_;\n  vector<int> segment_length_;\n};\n\ntemplate <typename T, typename SIndex, typename Context, typename ReducerDef>\nstruct AbstractUnsortedSegmentDef {\n  using OpDef = ReducerDef;\n  static constexpr const char* basename = \"UnsortedSegment\";\n  static constexpr const char* doc = R\"DOC(\nApplies '{op}' to each segment of input tensor. Segments ids can appear in\narbitrary order (unlike in SortedSegment{op}).\n\nSEGMENT_IDS is a vector that maps each of the first dimension slices of the\nDATA to a particular group (segment). Values belonging to the same segment are\naggregated together.\n\nIf `num_segments` argument is passed it would be used as a first dimension for\nthe output. Otherwise, it'd be dynamically calculated from as the max value of\nSEGMENT_IDS plus one. Other output dimensions are inherited from the input\ntensor.\n\n{op_doc}\n  )DOC\";\n  static void PopulateSchema(OpSchema& schema) {\n    schema.Arg(\n        \"num_segments\",\n        \"Optional int argument specifying the number of output segments and \"\n        \"thus the first dimension of the output\");\n    schema.Input(0, \"DATA\", \"Input tensor, slices of which are aggregated.\");\n    schema.Input(\n        Reducer::kInputCount,\n        \"SEGMENT_IDS\",\n        \"Integer vector with the same length as the first dimension of DATA \"\n        \"that maps each slice of DATA to one of the segments\");\n    schema.Output(\n        0,\n        \"OUTPUT\",\n        \"Aggregated output tensor. Has the first dimension of equal to the \"\n        \"number of segments.\");\n    ReducerDef::PopulateSchema(schema);\n  }\n  using Reducer = typename ReducerDef::template Reducer<T, Context>;\n  using ReducerGradient =\n      typename ReducerDef::template ReducerGradient<T, Context>;\n  using ForwardOp = AbstractUnsortedSegmentOp<\n      T,\n      SIndex,\n      Context,\n      typename ReducerDef::template Reducer<T, Context>,\n      false>;\n  using BackwardOp =\n      AbstractUnsortedSegmentGradientOp<T, SIndex, Context, ReducerGradient>;\n  using GetGradient = SegmentOpGetGradient<\n      ForwardOp,\n      ReducerDef,\n      ReducerGradient,\n      false /*Sorted*/,\n      false /*SparseFused*/>;\n};\n\ntemplate <typename T, typename SIndex, typename Context, typename ReducerDef>\nstruct AbstractSparseUnsortedSegmentDef {\n  using OpDef = ReducerDef;\n  static constexpr const char* basename = \"SparseUnsortedSegment\";\n  static constexpr const char* doc = R\"DOC(\nPulls in slices of the input tensor, groups them into segments and applies\n'{op}' to each segment. Segments ids can appear in arbitrary order (unlike in\nSparseSortedSegment{op}).\n\nThis op is basically Gather and UnsortedSegment{op} fused together.\n\nINDICES should contain integers in range 0..N-1 where N is the first dimension\nof DATA. INDICES represent which slices of DATA need to be pulled in.\n\nSEGMENT_IDS is a vector that maps each referenced slice of the DATA to a\nparticular group (segment). Values belonging to the same segment are aggregated\ntogether. SEGMENT_IDS should have the same dimension as INDICES.\n\nIf `num_segments` argument is passed it would be used as a first dimension for\nthe output. Otherwise, it'd be dynamically calculated from as the max value of\nSEGMENT_IDS plus one. Other output dimensions are inherited from the input\ntensor.\n\n{op_doc}\n  )DOC\";\n  static void PopulateSchema(OpSchema& schema) {\n    schema.Input(0, \"DATA\", \"Input tensor, slices of which are aggregated.\");\n    schema.Input(\n        Reducer::kInputCount,\n        \"INDICES\",\n        \"Integer vector containing indices of the first dimension of DATA for \"\n        \"the slices that are being aggregated\");\n    schema.Input(\n        Reducer::kInputCount + 1,\n        \"SEGMENT_IDS\",\n        \"Integer vector with the same length as INDICES that maps each slice \"\n        \"of DATA referenced by INDICES to one of the segments\");\n    schema.Output(\n        0,\n        \"OUTPUT\",\n        \"Aggregated output tensor. Has the first dimension of equal to the \"\n        \"number of segments.\");\n    ReducerDef::PopulateSchema(schema);\n  }\n  using Reducer = typename ReducerDef::template Reducer<T, Context>;\n  using ReducerGradient =\n      typename ReducerDef::template ReducerGradient<T, Context>;\n  using ForwardOp = AbstractUnsortedSegmentOp<T, SIndex, Context, Reducer>;\n  // TODO(dzhulgakov): we're registering the same class twice here,\n  // consider avoiding op duplication here\n  using BackwardOp =\n      AbstractUnsortedSegmentGradientOp<T, SIndex, Context, ReducerGradient>;\n  using GetGradient = SegmentOpGetGradient<\n      ForwardOp,\n      ReducerDef,\n      ReducerGradient,\n      false /*Sorted*/,\n      true /*SparseFused*/>;\n};\n\n/**\n * @brief Segment reduction op with optional fused embedding lookup\n *\n * Base implementation for LengthsXXX and SparseLengthsXXX depending\n * on SparseFused static argument.\n *\n * Inputs:\n *   0: DATA - input embedding to do lookups in\n *   1..P: AUX_ARG_<I> - optional additional arguments to be passed to the\n *                       reducer, should have the same first dimension as\n *                       LENGTHS (e.g. scalars in WeightedSum)\n *   # if SparseFused == true:\n *   P+1: INDICES - 1-D vector with indices to look up in DATA. Should have the\n *                  same dimension as LENGTHS\n *   # P+1 if SparseFused == false:\n *   P+1 or P+2: LENGTHS - lengths on indecies vector\n *\n * Output:\n *   Tensor with first dimension of K, where K = len(LENGTHS). Rest\n *   of dimensions are decided by reducer but usually are the same size as extra\n *   dimensions of DATA\n */\n// TODO(dzhulgakov): for now it's implemented with incremental reducers because\n// of fused sparse support. But using \"lengths\" representation actually implies\n// continuous segments and thus range reducers can be used for non-sparse\n// version.\n\ntemplate <\n    typename TData,\n    typename TLengths,\n    class Context,\n    class Reducer,\n    bool SparseFused = true,\n    class InputAccessor = BaseInputAccessor<TData>>\nclass AbstractLengthsOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(AbstractLengthsOp);\n\n  bool RunOnDevice() override {\n    if (SparseFused) {\n      return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n          this, Input(INDICES));\n    } else {\n      // type doesn't matter\n      return DoRunWithType<TIndex>();\n    }\n  }\n\n  template <typename IndexType>\n  bool DoRunWithType() {\n    // If more complicated fixed size logic becomes necessary, it can be moved\n    // to the reducer class\n    TIndex in_block_size = Input(0).size_from_dim(1);\n    return DispatchHelper<typename Reducer::FixedDispatch, IndexType>::call(\n        this, in_block_size);\n  }\n\n  template <typename IndexType, int FixedSize>\n  bool DoRunWithValue() {\n    auto& dataInput = Input(0);\n    auto& lengthsInput = Input(LENGTHS);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_EQ(1, lengthsInput.ndim(), \"LENGTHS must be a vector\");\n    const TIndex dataSize = dataInput.dim(0);\n    // Either first dim the data or how much we pull in indexies from it\n    TIndex dataToReduceSize;\n    const TIndex outputSize = lengthsInput.dim(0);\n\n    const IndexType* indices;\n    if (SparseFused) { // static if\n      auto& indicesInput = Input(INDICES);\n      CAFFE_ENFORCE_EQ(1, indicesInput.ndim(), \"INDICES must be a vector\");\n      indices = indicesInput.template data<IndexType>();\n      dataToReduceSize = indicesInput.dim(0);\n    } else {\n      dataToReduceSize = dataSize;\n    }\n\n    typename Reducer::Meta ctx;\n    ctx.observeInput(0, dataInput, 1);\n    for (int i = 1; i < Reducer::kInputCount; ++i) {\n      auto& aux_in = Input(i);\n      CAFFE_ENFORCE(\n          dataToReduceSize == aux_in.dim(0),\n          \"Input \",\n          i,\n          \" must have the same first dim as SEGMENT_IDS\");\n      ctx.observeInput(i, aux_in, 1);\n    }\n\n    const TLengths* lengths = lengthsInput.template data<TLengths>();\n\n    OPERATOR_NEEDS_FEATURE(\n        inputAccessor_.observeInput(dataInput),\n        \"Unsupported input type: \",\n        dataInput.meta().name(),\n        \".\");\n\n    vector<TIndex> shape{outputSize};\n    ctx.appendOutputShape(&shape);\n    output->Resize(shape);\n\n    TIndex in_block_size = dataInput.size_from_dim(1);\n    TIndex out_block_size = output->size_from_dim(1);\n    TData* out = output->template mutable_data<TData>();\n\n    TIndex dataIndex = 0;\n    for (TIndex rangeIndex = 0; rangeIndex < outputSize; ++rangeIndex) {\n      Reducer reducer(ctx, out + out_block_size * rangeIndex, &context_);\n      for (TIndex start = dataIndex; dataIndex < start + lengths[rangeIndex];\n           ++dataIndex) {\n        IndexType idx;\n        if (SparseFused) { // static if\n          idx = indices[dataIndex];\n          CAFFE_ENFORCE(\n              0 <= idx && idx < dataSize,\n              \"Index \",\n              dataIndex,\n              \" is out of bounds: \",\n              idx,\n              \", range 0 to \",\n              dataSize);\n        } else {\n          idx = dataIndex;\n          CAFFE_ENFORCE(\n              idx < dataSize,\n              \"Range \",\n              rangeIndex,\n              \" of length \",\n              lengths[rangeIndex],\n              \" is out of bound \",\n              dataSize);\n        }\n\n        const TData* input = inputAccessor_.getBlockPtr(in_block_size, idx);\n        reducer.template process<FixedSize>(ctx, input, dataIndex, &context_);\n      }\n      reducer.template finish<FixedSize>(ctx, &context_);\n    }\n    CAFFE_ENFORCE(\n        dataIndex == dataToReduceSize, dataIndex, \" != \", dataToReduceSize);\n\n    return true;\n  }\n\n  enum {\n    INDICES = Reducer::kInputCount,\n    LENGTHS = Reducer::kInputCount + (SparseFused ? 1 : 0)\n  };\n  static constexpr int kSelfInputs = SparseFused ? 2 : 1;\n  static constexpr int kNumInputs = Reducer::kInputCount + kSelfInputs;\n\n private:\n  InputAccessor inputAccessor_;\n};\n\n/*\n * Some notice:\n * 1. Gradient actually doesn't depend on whether sparse lookup is fused or not\n * 2. INDICES are not used in CPU version, but they are needed in async CUDA\n *    version. So we register 3 input version for CPU as gradient op for\n *    GPU/CPU convert. We then register 2 input version for CPU for backward\n *    compatibility with older nets.\n */\ntemplate <\n    typename T,\n    typename TLengths,\n    class Context,\n    class ReducerGradient,\n    bool GradientNeedIndices = false>\nclass AbstractLengthsGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(AbstractLengthsGradientOp);\n\n  bool RunOnDevice() override {\n    // If more complicated fixed size logic becomes necessary, it can be moved\n    // to the reducer class\n    TIndex gradBlockSize = Input(SEGMENT_GRADS).size_from_dim(1);\n    return DispatchHelper<typename ReducerGradient::FixedDispatch>::call(\n        this, gradBlockSize);\n  }\n\n  template <int FixedSize>\n  bool DoRunWithValue() {\n    auto& segmentGradsInput = Input(SEGMENT_GRADS);\n    auto& lengthsInput = Input(LENGTHS);\n    auto* dataGradsOutput = Output(0);\n\n    CAFFE_ENFORCE(lengthsInput.ndim() == 1, \"LENGTHS must be a vector\");\n    TIndex reducedDataSize = 0;\n    TIndex numSegments = lengthsInput.dim(0);\n    CAFFE_ENFORCE(segmentGradsInput.ndim() > 0);\n    CAFFE_ENFORCE(numSegments == segmentGradsInput.dim(0));\n    const TLengths* lengths = lengthsInput.template data<TLengths>();\n    for (TIndex i = 0; i < numSegments; ++i) {\n      reducedDataSize += lengths[i];\n    }\n\n    typename ReducerGradient::Meta ctx(segmentGradsInput, 1);\n    for (int i = 0; i < ReducerGradient::originalInputs().size(); ++i) {\n      auto& aux_in = Input(i);\n      CAFFE_ENFORCE_EQ(\n          reducedDataSize,\n          aux_in.dim(0),\n          \"Input \",\n          i,\n          \" must have the same first dim as SEGMENT_IDS\");\n      ctx.observeOriginalInput(\n          ReducerGradient::originalInputs()[i], aux_in, nullptr /*no grad*/, 1);\n    }\n\n    const T* segmentGrads = segmentGradsInput.template data<T>();\n\n    vector<TIndex> shape;\n    shape.push_back(reducedDataSize);\n    ctx.appendGradShape(&shape);\n    dataGradsOutput->Resize(shape);\n\n    TIndex dataGradsBlockSize = dataGradsOutput->size_from_dim(1);\n    TIndex segmentBlockSize = segmentGradsInput.size_from_dim(1);\n    T* dataGrads = dataGradsOutput->template mutable_data<T>();\n\n    TIndex dataIndex = 0;\n    for (TIndex rangeIndex = 0; rangeIndex < numSegments; ++rangeIndex) {\n      ReducerGradient reducer(\n          ctx, segmentGrads + segmentBlockSize * rangeIndex, &context_);\n      for (TIndex start = dataIndex; dataIndex < start + lengths[rangeIndex];\n           ++dataIndex) {\n        reducer.template fillGrad<FixedSize>(\n            ctx,\n            dataGrads + dataGradsBlockSize * dataIndex,\n            dataIndex,\n            &context_,\n            lengths[rangeIndex]);\n      }\n    }\n    CAFFE_ENFORCE(\n        dataIndex == reducedDataSize, dataIndex, \" != \", reducedDataSize);\n    return true;\n  }\n\n  // Input layout:\n  //   orig_arg1, orig_arg2, ..., orig_argN, SEGMENT_GRADS, LENGTHS, INDICES\n  // orig_argXs represent original op's inputs and will be passed to the reducer\n  // directly\n  static constexpr int kNumInputs = ReducerGradient::originalInputs().size() +\n      2 + (GradientNeedIndices ? 1 : 0);\n  enum _InputTags {\n    SEGMENT_GRADS = ReducerGradient::originalInputs().size(),\n    LENGTHS,\n    INDICES\n  };\n};\n\n// Version of gradient that requires the main input and thus needs to receive\n// length, indices and other stuff\ntemplate <\n    typename T,\n    typename TLengths,\n    class Context,\n    class ReducerGradient,\n    bool SparseFused = true,\n    bool GradientNeedIndices = false>\nclass AbstractLengthsWithMainInputGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(AbstractLengthsWithMainInputGradientOp);\n\n  bool RunOnDevice() override {\n    if (SparseFused) {\n      return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n          this, Input(INDICES));\n    } else {\n      // type doesn't matter\n      return DoRunWithType<TIndex>();\n    }\n  }\n\n  template <typename IndexType>\n  bool DoRunWithType() {\n    // If more complicated fixed size logic becomes necessary, it can be moved\n    // to the reducer class\n    TIndex in_block_size = Input(SEGMENT_GRADS).size_from_dim(1);\n    return DispatchHelper<typename ReducerGradient::FixedDispatch, IndexType>::\n        call(this, in_block_size);\n  }\n\n  template <typename IndexType, int FixedSize>\n  bool DoRunWithValue() {\n    auto& dataInput = Input(DATA_INPUT);\n    auto& segmentGradsInput = Input(SEGMENT_GRADS);\n    auto& lengthsInput = Input(LENGTHS);\n    auto* dataGradsOutput = Output(0);\n\n    CAFFE_ENFORCE(lengthsInput.ndim() == 1, \"LENGTHS must be a vector\");\n    TIndex numSegments = lengthsInput.dim(0);\n    CAFFE_ENFORCE(segmentGradsInput.ndim() > 0);\n    CAFFE_ENFORCE(numSegments == segmentGradsInput.dim(0));\n    const TLengths* lengths = lengthsInput.template data<TLengths>();\n\n    typename ReducerGradient::Meta ctx(segmentGradsInput, 1);\n    for (int i = 0; i < ReducerGradient::originalInputs().size(); ++i) {\n      int aux_num = ReducerGradient::originalInputs()[i];\n      auto& aux_in = Input(i);\n      auto* aux_grad = aux_num < OutputSize() ? Output(aux_num) : nullptr;\n      ctx.observeOriginalInput(aux_num, aux_in, aux_grad, 1);\n    }\n\n    // Either first dim the data or how much we pull in indexies from it\n    TIndex dataToReduceSize;\n    const IndexType* indices = nullptr;\n    if (SparseFused) { // static if\n      auto& indicesInput = Input(INDICES);\n      indices = indicesInput.template data<IndexType>();\n      dataToReduceSize = indicesInput.dim(0);\n    } else {\n      dataToReduceSize = dataInput.dim(0);\n    }\n\n    const T* segmentGrads = segmentGradsInput.template data<T>();\n\n    vector<TIndex> shape;\n    shape.push_back(dataToReduceSize);\n    ctx.appendGradShape(&shape);\n    dataGradsOutput->Resize(shape);\n\n    TIndex dataGradsBlockSize = dataGradsOutput->size_from_dim(1);\n    TIndex segmentBlockSize = segmentGradsInput.size_from_dim(1);\n    T* dataGrads = dataGradsOutput->template mutable_data<T>();\n\n    const T* data = dataInput.template data<T>();\n\n    TIndex dataIndex = 0;\n    for (TIndex rangeIndex = 0; rangeIndex < numSegments; ++rangeIndex) {\n      ReducerGradient reducer(\n          ctx, segmentGrads + segmentBlockSize * rangeIndex, &context_);\n      for (TIndex start = dataIndex; dataIndex < start + lengths[rangeIndex];\n           ++dataIndex) {\n        IndexType data_pos;\n        // No range checking, should've been verified in forward pass\n        if (SparseFused) { // static if\n          data_pos = indices[dataIndex];\n        } else {\n          data_pos = dataIndex;\n        }\n        reducer.template fillGradWithMainInput<FixedSize>(\n            ctx,\n            data + dataGradsBlockSize * data_pos,\n            dataGrads + dataGradsBlockSize * dataIndex,\n            dataIndex,\n            &context_,\n            lengths[rangeIndex]);\n      }\n    }\n    return true;\n  }\n\n  // Input layout:\n  //   orig_arg1, orig_arg2, ..., orig_argN, SEGMENT_GRADS, LENGTHS,\n  //      DATA_INPUT, [INDICES]\n  // orig_argXs represent original op's inputs and will be passed to the reducer\n  // directly\n  static constexpr int kNumInputs = ReducerGradient::originalInputs().size() +\n      3 + (SparseFused ? 1 : 0) + (GradientNeedIndices ? 1 : 0);\n  enum _InputTags {\n    SEGMENT_GRADS = ReducerGradient::originalInputs().size(),\n    LENGTHS,\n    DATA_INPUT,\n    INDICES,\n  };\n};\n\n// Version of gradient that requires the main input as well as the output of the\n// forward op.\ntemplate <typename T, typename TLengths, class Context, class ReducerGradient>\nclass AbstractLengthsWithMainInputAndForwardOutputGradientOp\n    : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(AbstractLengthsWithMainInputAndForwardOutputGradientOp);\n\n  bool RunOnDevice() override {\n    // If more complicated fixed size logic becomes necessary, it can be moved\n    // to the reducer class.\n    TIndex in_block_size = Input(SEGMENT_GRADS).size_from_dim(1);\n    return DispatchHelper<typename ReducerGradient::FixedDispatch>::call(\n        this, in_block_size);\n  }\n\n  template <int FixedSize>\n  bool DoRunWithValue() {\n    auto& dataInput = Input(DATA_INPUT);\n    auto& segmentGradsInput = Input(SEGMENT_GRADS);\n    auto& lengthsInput = Input(LENGTHS);\n    auto& forwardOutputInput = Input(FORWARD_OUTPUT);\n    auto* dataGradsOutput = Output(0);\n\n    CAFFE_ENFORCE(lengthsInput.ndim() == 1, \"LENGTHS must be a vector\");\n    TIndex numSegments = lengthsInput.dim(0);\n    CAFFE_ENFORCE(segmentGradsInput.ndim() > 0);\n    CAFFE_ENFORCE(numSegments == segmentGradsInput.dim(0));\n    const TLengths* lengths = lengthsInput.template data<TLengths>();\n\n    typename ReducerGradient::Meta ctx(segmentGradsInput, 1);\n    for (int i = 0; i < ReducerGradient::originalInputs().size(); ++i) {\n      int aux_num = ReducerGradient::originalInputs()[i];\n      auto& aux_in = Input(i);\n      auto* aux_grad = aux_num < OutputSize() ? Output(aux_num) : nullptr;\n      ctx.observeOriginalInput(aux_num, aux_in, aux_grad, 1);\n    }\n\n    CAFFE_ENFORCE(forwardOutputInput.ndim() > 0);\n    CAFFE_ENFORCE(numSegments == forwardOutputInput.dim(0));\n    const T* forwardOutput = forwardOutputInput.template data<T>();\n\n    TIndex dataToReduceSize = dataInput.dim(0);\n\n    const T* segmentGrads = segmentGradsInput.template data<T>();\n\n    vector<TIndex> shape;\n    shape.push_back(dataToReduceSize);\n    ctx.appendGradShape(&shape);\n    dataGradsOutput->Resize(shape);\n\n    TIndex dataGradsBlockSize = dataGradsOutput->size_from_dim(1);\n    TIndex segmentBlockSize = segmentGradsInput.size_from_dim(1);\n    T* dataGrads = dataGradsOutput->template mutable_data<T>();\n\n    const T* data = dataInput.template data<T>();\n\n    TIndex dataIndex = 0;\n    for (TIndex rangeIndex = 0; rangeIndex < numSegments; ++rangeIndex) {\n      ReducerGradient reducer(\n          ctx, segmentGrads + segmentBlockSize * rangeIndex, &context_);\n      for (TIndex start = dataIndex; dataIndex < start + lengths[rangeIndex];\n           ++dataIndex) {\n        // No range checking, should've been verified in forward pass\n        reducer.template fillGradWithMainInputAndForwardOutput<FixedSize>(\n            ctx,\n            data + dataGradsBlockSize * dataIndex,\n            dataGrads + dataGradsBlockSize * dataIndex,\n            forwardOutput + segmentBlockSize * rangeIndex,\n            dataIndex,\n            &context_,\n            lengths[rangeIndex]);\n      }\n    }\n    return true;\n  }\n\n  // Input layout:\n  //   orig_arg1, orig_arg2, ..., orig_argN, FORWARD_OUTPUT, SEGMENT_GRADS,\n  //      LENGTHS, DATA_INPUT\n  // orig_argXs represent original op's inputs and will be passed to the reducer\n  // directly\n  static constexpr int kNumInputs =\n      ReducerGradient::originalInputs().size() + 4;\n  enum _InputTags {\n    FORWARD_OUTPUT = ReducerGradient::originalInputs().size(),\n    SEGMENT_GRADS,\n    LENGTHS,\n    DATA_INPUT,\n  };\n};\n\n// base implementation of sparse/non-sparse gradient computation\ntemplate <\n    typename ForwardOp,\n    typename ReducerDef,\n    typename ReducerGradient,\n    bool SparseFused,\n    bool GradientNeedIndices = false>\nstruct LengthsOpGetGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> grad_ins;\n    string suffix = \"Gradient\";\n    for (const int i : ReducerGradient::originalInputs()) {\n      grad_ins.push_back(I(i));\n    }\n    if (ReducerGradient::requiresForwardOutput()) {\n      grad_ins.push_back(O(0));\n      CAFFE_ENFORCE(\n          !SparseFused,\n          \"Forward pass output not yet supported as input for backward pass \"\n          \"for SparseLengthsXXX operators\");\n      suffix = \"AndForwardOutput\" + suffix;\n    }\n    grad_ins.push_back(GO(0));\n    grad_ins.push_back(I(ForwardOp::LENGTHS));\n    bool indices_pushed = false;\n    if (ReducerGradient::requiresDataInput(Def())) {\n      grad_ins.push_back(I(0));\n      if (SparseFused) {\n        grad_ins.push_back(I(ForwardOp::INDICES));\n        indices_pushed = true;\n      }\n      suffix = \"WithMainInput\" + suffix;\n    }\n    if (GradientNeedIndices && !indices_pushed) {\n      if (SparseFused) {\n        grad_ins.push_back(I(ForwardOp::INDICES));\n      } else {\n        // Hacky: using Input as Indices, remove this after we have specialized\n        // cuda LengthsIndicesInGradientSumGradient\n        grad_ins.push_back(I(0));\n      }\n    }\n    vector<string> grad_outs;\n    grad_outs.push_back({SparseFused ? GI_V(0) : GI(0)});\n    int aux_grads = ReducerGradient::numAuxInputsWithGrads(Def());\n    for (int i = 1; i <= aux_grads; ++i) {\n      grad_outs.push_back(GI(i));\n    }\n    vector<OperatorDef> r{CreateOperatorDef(\n        string(SparseFused ? \"SparseLengths\" : \"Lengths\") +\n            string(GradientNeedIndices ? \"IndicesInGradient\" : \"\") +\n            ReducerDef::name + suffix,\n        \"\",\n        grad_ins,\n        grad_outs)};\n    if (SparseFused) {\n      SetSparse(0, I(ForwardOp::INDICES), GI_V(0));\n    }\n    return r;\n  }\n};\n\ntemplate <\n    typename T,\n    typename SIndex,\n    typename Context,\n    typename ReducerDef,\n    bool GradientNeedIndices = false>\nstruct AbstractLengthsDef {\n  using OpDef = ReducerDef;\n  static constexpr const char* basename = \"Lengths\";\n  static constexpr const char* doc = R\"DOC(\nApplies '{op}' to each segment of the input tensor. Segments are defined\nby their LENGTHS.\n\nLENGTHS is a vector that maps each of the first dimension slices of the\nDATA to a particular group (segment). Values belonging to the same segment are\naggregated together.\n\nFor example LENGTHS = [2, 1] stands for segments DATA[0..1] and DATA[2]\n\nThe first dimension of the output is equal to the number of input segments,\ni.e. `len(LENGTHS)`. Other dimensions are inherited from the input tensor.\n\n{op_doc}\n  )DOC\";\n  static void PopulateSchema(OpSchema& schema) {\n    schema.Input(0, \"DATA\", \"Input tensor, slices of which are aggregated.\");\n    schema.Input(\n        Reducer::kInputCount,\n        \"LENGTHS\",\n        \"Vector with the same sum of elements as the first dimension of DATA\");\n    schema.Output(\n        0,\n        \"OUTPUT\",\n        \"Aggregated output tensor. Has the first dimension of len(LENGTHS) \");\n    schema.TensorInferenceFunction(\n        [](const OperatorDef& def, const vector<TensorShape>& in) {\n          vector<TensorShape> out(0);\n          TensorShape output;\n          for (int d : in[Reducer::kInputCount].dims()) {\n            output.add_dims(d);\n          }\n          for (int j = 1; j < in[0].dims_size(); j++) {\n            output.add_dims(in[0].dims(j));\n          }\n          output.set_data_type(in[0].data_type());\n          out.push_back(output);\n          return out;\n        });\n    ReducerDef::PopulateSchema(schema);\n  }\n  using Reducer = typename ReducerDef::template Reducer<T, Context>;\n  using ReducerGradient =\n      typename ReducerDef::template ReducerGradient<T, Context>;\n  using ForwardOp = AbstractLengthsOp<T, SIndex, Context, Reducer, false>;\n  using BackwardOp =\n      AbstractLengthsGradientOp<T, SIndex, Context, ReducerGradient>;\n  using WithMainInputBackwardOp = AbstractLengthsWithMainInputGradientOp<\n      T,\n      SIndex,\n      Context,\n      ReducerGradient,\n      false>;\n  using WithMainInputAndForwardOutputBackwardOp =\n      AbstractLengthsWithMainInputAndForwardOutputGradientOp<\n          T,\n          SIndex,\n          Context,\n          ReducerGradient>;\n  using GetGradient = LengthsOpGetGradient<\n      ForwardOp,\n      ReducerDef,\n      ReducerGradient,\n      false /*SparseFused*/,\n      GradientNeedIndices>;\n};\n\ntemplate <\n    typename T,\n    typename SIndex,\n    typename Context,\n    typename ReducerDef,\n    bool GradientNeedIndices = false>\nstruct AbstractSparseLengthsDef {\n  using OpDef = ReducerDef;\n  static constexpr const char* basename = \"SparseLengths\";\n  static constexpr const char* doc = R\"DOC(\nPulls in slices of the input tensor, groups them into segments and applies\n'{op}' to each segment. Segments are defined by their LENGTHS.\n\nThis op is basically Gather and Lengths{op} fused together.\n\nINDICES should contain integers in range 0..N-1 where N is the first dimension\nof DATA. INDICES represent which slices of DATA need to be pulled in.\n\nLENGTHS is a vector that defines slice sizes by first dimention of DATA. Values\nbelonging to the same segment are aggregated together. sum(LENGTHS) has\nto match INDICES size.\n\nThe first dimension of the output is equal to the number of input segment,\ni.e. `len(LENGTHS)`. Other dimensions are inherited from the input tensor.\n\n{op_doc}\n  )DOC\";\n  static void PopulateSchema(OpSchema& schema) {\n    schema.Input(0, \"DATA\", \"Input tensor, slices of which are aggregated.\");\n    schema.Input(\n        Reducer::kInputCount,\n        \"INDICES\",\n        \"Integer vector containing indices of the first dimension of DATA for \"\n        \"the slices that are being aggregated\");\n    schema.Input(\n        Reducer::kInputCount + 1,\n        \"LENGTHS\",\n        \"Non negative vector with sum of elements equal to INDICES length\");\n    schema.Output(\n        0,\n        \"OUTPUT\",\n        \"Aggregated output tensor. Has the first dimension of K \"\n        \"(the number of segments).\");\n    ReducerDef::PopulateSchema(schema);\n  }\n  using Reducer = typename ReducerDef::template Reducer<T, Context>;\n  using ReducerGradient =\n      typename ReducerDef::template ReducerGradient<T, Context>;\n  using ForwardOp = AbstractLengthsOp<T, SIndex, Context, Reducer>;\n  // TODO(dzhulgakov): we're registering the same class twice here,\n  // consider avoiding op duplication here\n  // Note: registering 2 input version for now because of naming in the macro,\n  // will register 3 input version alone\n  /* INDICES are not used in CPU version, but they are needed in async CUDA\n   *    version. So we register 3 input version for CPU as gradient op for\n   *    GPU/CPU convert. We then register 2 input version for CPU for backward\n   *    compatibility with older nets.\n   */\n  using BackwardOp = AbstractLengthsGradientOp<\n      T,\n      SIndex,\n      Context,\n      ReducerGradient,\n      false /*GradientNeedIndices*/>;\n  using WithMainInputBackwardOp = AbstractLengthsWithMainInputGradientOp<\n      T,\n      SIndex,\n      Context,\n      ReducerGradient>;\n  // Will return 3 input version. This is aliging new CPU/GPU nets.\n  using GetGradient = LengthsOpGetGradient<\n      ForwardOp,\n      ReducerDef,\n      ReducerGradient,\n      true /*SparseFused*/,\n      GradientNeedIndices>;\n};\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SEGMENT_REDUCTION_OP_H_\n"
  },
  {
    "path": "caffe2/operators/segment_reduction_op_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cub/block/block_reduce.cuh>\n#include <cub/device/device_reduce.cuh>\n#include <cub/device/device_scan.cuh>\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\n\nnamespace caffe2 {\n\nnamespace {\n\nvoid inclusive_scan_wrapper(\n    const int* length_data,\n    int len_length,\n    Tensor<CUDAContext>* temp_buffer,\n    Tensor<CUDAContext>* prefix_sum_out,\n    CUDAContext* context_) {\n  // Retrieve buffer size\n  size_t temp_storage_bytes = 0;\n  cub::DeviceScan::InclusiveSum(\n      NULL,\n      temp_storage_bytes,\n      length_data,\n      prefix_sum_out->mutable_data<int>(),\n      len_length,\n      context_->cuda_stream());\n  // Allocate temporary storage\n  auto buffer_size = (temp_storage_bytes + sizeof(int)) / sizeof(int);\n  temp_buffer->Resize(buffer_size);\n  void* d_temp_storage = static_cast<void*>(temp_buffer->mutable_data<int>());\n  // Run inclusive prefix sum\n  cub::DeviceScan::InclusiveSum(\n      d_temp_storage,\n      temp_storage_bytes,\n      length_data,\n      prefix_sum_out->mutable_data<int>(),\n      len_length,\n      context_->cuda_stream());\n}\n\ntemplate <typename T, bool ExactBlock = false>\n__global__ void length_sum_kernel(\n    const T* __restrict__ in,\n    T* __restrict__ out,\n    const int* __restrict__ prefix_sum_length_data,\n    int N,\n    int post,\n    int len_length) {\n  // len_length blocks\n  int group = blockIdx.x;\n\n  int start = group == 0 ? 0 : prefix_sum_length_data[group - 1];\n  int end = prefix_sum_length_data[group];\n  CUDA_KERNEL_ASSERT(start <= N);\n  CUDA_KERNEL_ASSERT(end <= N);\n\n  if (ExactBlock) {\n    in += threadIdx.x;\n\n    T sum = (T)0;\n    for (int line = start; line < end; ++line) {\n      sum += in[line * post];\n    }\n\n    out[group * post + threadIdx.x] = sum;\n  } else {\n    for (int i = threadIdx.x; i < post; i += blockDim.x) {\n      T sum = (T)0;\n      for (int line = start; line < end; ++line) {\n        sum += in[line * post + i];\n      }\n      out[group * post + i] = sum;\n    }\n  }\n}\n\ntemplate <typename T, bool ExactBlock = false>\n__global__ void length_sum_gradient_kernel(\n    const T* __restrict__ grad_in,\n    T* __restrict__ grad_out,\n    const int* __restrict__ prefix_sum_length_data,\n    int N,\n    int post,\n    int len_length) {\n  // len_length blocks\n  int group = blockIdx.x;\n\n  int start = group == 0 ? 0 : prefix_sum_length_data[group - 1];\n  int end = prefix_sum_length_data[group];\n  CUDA_KERNEL_ASSERT(start <= N);\n  CUDA_KERNEL_ASSERT(end <= N);\n\n  if (ExactBlock) {\n    grad_out += threadIdx.x;\n    grad_in += threadIdx.x;\n\n    for (int line = start + threadIdx.y; line < end; line += blockDim.y) {\n      grad_out[line * post] = grad_in[group * post];\n    }\n  } else {\n    for (int i = threadIdx.x; i < post; i += blockDim.x) {\n      for (int line = start; line < end; ++line) {\n        grad_out[line * post + i] = grad_in[group * post + i];\n      }\n    }\n  }\n}\n\ntemplate <typename T, bool ExactBlock = false>\n__global__ void length_weighted_sum_gradient_kernel(\n    const T* __restrict__ grad_in,\n    const T* __restrict__ weights_in,\n    T* __restrict__ grad_out,\n    const int* __restrict__ prefix_sum_length_data,\n    int N,\n    int post,\n    int len_length) {\n  // len_length blocks\n  int group = blockIdx.x;\n\n  int start = group == 0 ? 0 : prefix_sum_length_data[group - 1];\n  int end = prefix_sum_length_data[group];\n  CUDA_KERNEL_ASSERT(start <= N);\n  CUDA_KERNEL_ASSERT(end <= N);\n\n  if (ExactBlock) {\n    grad_out += threadIdx.x;\n    grad_in += threadIdx.x;\n\n    for (int line = start + threadIdx.y; line < end; line += blockDim.y) {\n      grad_out[line * post] = weights_in[line] * grad_in[group * post];\n    }\n  } else {\n    for (int i = threadIdx.x; i < post; i += blockDim.x) {\n      for (int line = start; line < end; ++line) {\n        grad_out[line * post + i] =\n            weights_in[line] * grad_in[group * post + i];\n      }\n    }\n  }\n}\n\ntemplate <typename T, typename IndexType, int NumThreads>\n__global__ void length_weighted_sum_with_main_input_gradient_kernel(\n    const T* __restrict__ grad_in,\n    const T* __restrict__ weights_in,\n    const T* __restrict__ data_in,\n    const IndexType* __restrict__ indices,\n    T* __restrict__ data_grad_out,\n    T* __restrict__ weights_grad_out,\n    const int* __restrict__ prefix_sum_length_data,\n    int N,\n    int post,\n    int len_length) {\n  // len_length blocks\n  int group = blockIdx.x;\n\n  int start = group == 0 ? 0 : prefix_sum_length_data[group - 1];\n  int end = prefix_sum_length_data[group];\n  CUDA_KERNEL_ASSERT(start <= N);\n  CUDA_KERNEL_ASSERT(end <= N);\n\n  // todo figure this num threads thing\n  typedef cub::BlockReduce<float, NumThreads> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n\n  // TODO(wyiming): parallelize this outter loop\n  for (int line = start; line < end; ++line) {\n    T w_grad = 0;\n    for (int i = threadIdx.x; i < post; i += blockDim.x) {\n      auto g_in = grad_in[group * post + i];\n      data_grad_out[line * post + i] = weights_in[line] * g_in;\n      w_grad += g_in * data_in[indices[line] * post + i];\n    }\n    w_grad = BlockReduce(temp_storage).Reduce(w_grad, cub::Sum());\n    if (threadIdx.x == 0) {\n      weights_grad_out[line] = w_grad;\n    }\n    __syncthreads();\n  }\n}\n\ntemplate <typename T, typename IndexType, bool ExactBlock = false>\n__global__ void sparse_length_sum_kernel(\n    const T* __restrict__ in,\n    T* __restrict__ out,\n    const int* __restrict__ prefix_sum_length_data,\n    const IndexType* __restrict__ indices,\n    int N,\n    int post,\n    int len_length,\n    int len_indices) {\n  // len_length blocks\n  int group = blockIdx.x;\n\n  int start = group == 0 ? 0 : prefix_sum_length_data[group - 1];\n  int end = prefix_sum_length_data[group];\n  CUDA_KERNEL_ASSERT(start <= len_indices);\n  CUDA_KERNEL_ASSERT(end <= len_indices);\n\n  extern __shared__ T reduceVals[];\n\n  if (ExactBlock) {\n    T sum = (T)0;\n\n    in += threadIdx.x;\n    for (int line = start + threadIdx.y; line < end; line += blockDim.y) {\n      sum += in[indices[line] * post];\n    }\n\n    reduceVals[threadIdx.y * blockDim.x + threadIdx.x] = sum;\n    __syncthreads();\n\n    if (threadIdx.y == 0) {\n      sum = (T)0;\n      for (int i = 0; i < blockDim.y; ++i) {\n        sum += reduceVals[i * blockDim.x + threadIdx.x];\n      }\n\n      out[group * post + threadIdx.x] = sum;\n    }\n  } else {\n    for (int i = threadIdx.x; i < post; i += blockDim.x) {\n      T sum = (T)0;\n      for (int line = start; line < end; ++line) {\n        sum += in[indices[line] * post + i];\n      }\n      out[group * post + i] = sum;\n    }\n  }\n}\n\ntemplate <typename T, typename IndexType, bool ExactBlock = false>\n__global__ void sparse_length_weighted_sum_kernel(\n    const T* __restrict__ in,\n    const T* __restrict__ in_weights,\n    T* __restrict__ out,\n    const int* __restrict__ prefix_sum_length_data,\n    const IndexType* __restrict__ indices,\n    int N,\n    int post,\n    int len_length,\n    int len_indices) {\n  // len_length blocks\n  int group = blockIdx.x;\n\n  int start = group == 0 ? 0 : prefix_sum_length_data[group - 1];\n  int end = prefix_sum_length_data[group];\n  CUDA_KERNEL_ASSERT(start <= len_indices);\n  CUDA_KERNEL_ASSERT(end <= len_indices);\n\n  extern __shared__ T reduceVals[];\n\n  if (ExactBlock) {\n    T sum = (T)0;\n\n    in += threadIdx.x;\n    for (int line = start + threadIdx.y; line < end; line += blockDim.y) {\n      sum += in_weights[line] * in[indices[line] * post];\n    }\n\n    reduceVals[threadIdx.y * blockDim.x + threadIdx.x] = sum;\n    __syncthreads();\n\n    if (threadIdx.y == 0) {\n      sum = (T)0;\n      for (int i = 0; i < blockDim.y; ++i) {\n        sum += reduceVals[i * blockDim.x + threadIdx.x];\n      }\n\n      out[group * post + threadIdx.x] = sum;\n    }\n  } else {\n    for (int i = threadIdx.x; i < post; i += blockDim.x) {\n      T sum = (T)0;\n      for (int line = start; line < end; ++line) {\n        sum += in_weights[line] * in[indices[line] * post + i];\n      }\n      out[group * post + i] = sum;\n    }\n  }\n}\n\n} // namespace\n\ntemplate <typename T, class Context = CUDAContext, bool SparseFused = true>\nclass CUDASparseLengthsSumOp : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  CUDASparseLengthsSumOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws) {}\n\n  ~CUDASparseLengthsSumOp() {}\n\n  bool RunOnDevice() override {\n    if (SparseFused) {\n      return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n          this, Input(INDICES));\n    } else {\n      // type doesn't matter\n      return DoRunWithType<int32_t>();\n    }\n  }\n\n  template <typename IndexType>\n  bool DoRunWithType() {\n    auto& dataInput = Input(0);\n    auto& lengthsInput = Input(LENGTHS);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_EQ(1, lengthsInput.ndim(), \"LENGTHS must be a vector\");\n    const TIndex dataSize = dataInput.dim(0);\n    // Either first dim the data or how much we pull in indexies from it\n    TIndex dataToReduceSize;\n    const TIndex outputSize = lengthsInput.dim(0);\n    int len_length = outputSize;\n\n    const IndexType* indices;\n    if (SparseFused) { // static if\n      auto& indicesInput = Input(INDICES);\n      CAFFE_ENFORCE_EQ(1, indicesInput.ndim(), \"INDICES must be a vector\");\n      indices = indicesInput.template data<IndexType>();\n      dataToReduceSize = indicesInput.dim(0);\n    } else {\n      dataToReduceSize = dataSize;\n    }\n\n    auto shape = dataInput.dims();\n    shape[0] = outputSize;\n    output->Resize(shape);\n\n    // only compute this the first time\n    inclusive_scan_length_buffer_.ResizeLike(lengthsInput);\n    inclusive_scan_wrapper(\n        lengthsInput.template data<int>(),\n        len_length,\n        &inclusive_scan_buffer_,\n        &inclusive_scan_length_buffer_,\n        &context_);\n\n    const T* in_data = dataInput.template data<T>();\n    T* out_data = output->template mutable_data<T>();\n    auto* prefix_sum_length_data =\n        inclusive_scan_length_buffer_.template data<int>();\n    int N = dataSize;\n    int post = dataInput.size_from_dim(1);\n\n    auto maxThreads =\n        GetDeviceProperty(CaffeCudaGetDevice()).maxThreadsPerBlock;\n    if (SparseFused) {\n      if (post <= maxThreads) {\n        int multiple = std::min(maxThreads / post, 16);\n        dim3 block(post, multiple);\n        size_t smem = sizeof(T) * post * multiple;\n\n        sparse_length_sum_kernel<T, IndexType, true>\n            <<<len_length, block, smem, context_.cuda_stream()>>>(\n                in_data,\n                out_data,\n                prefix_sum_length_data,\n                indices,\n                N,\n                post,\n                len_length,\n                dataToReduceSize);\n      } else {\n        sparse_length_sum_kernel<T, IndexType, false>\n            <<<len_length, maxThreads, 0, context_.cuda_stream()>>>(\n                in_data,\n                out_data,\n                prefix_sum_length_data,\n                indices,\n                N,\n                post,\n                len_length,\n                dataToReduceSize);\n      }\n    } else {\n      if (post <= maxThreads) {\n        length_sum_kernel<T, true>\n            <<<len_length, post, 0, context_.cuda_stream()>>>(\n                in_data, out_data, prefix_sum_length_data, N, post, len_length);\n      } else {\n        length_sum_kernel<T, true>\n            <<<len_length, maxThreads, 0, context_.cuda_stream()>>>(\n                in_data, out_data, prefix_sum_length_data, N, post, len_length);\n      }\n    }\n    return true;\n  }\n\n  enum { INDICES = 1, LENGTHS = 1 + (SparseFused ? 1 : 0) };\n\n private:\n  // menber field to manage memory\n  Tensor<Context> inclusive_scan_buffer_;\n  Tensor<Context> inclusive_scan_length_buffer_;\n};\n\ntemplate <typename T, class Context = CUDAContext, bool SparseFused = true>\nclass CUDASparseLengthsWeightedSumOp : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  CUDASparseLengthsWeightedSumOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws) {}\n\n  ~CUDASparseLengthsWeightedSumOp() {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename IndexType>\n  bool DoRunWithType() {\n    auto& dataInput = Input(DATA);\n    auto& weightsInput = Input(WEIGHTS);\n    auto& indicesInput = Input(INDICES);\n    auto& lengthsInput = Input(LENGTHS);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_EQ(1, weightsInput.ndim(), \"WEIGHTS must be a vector\");\n    CAFFE_ENFORCE_EQ(1, indicesInput.ndim(), \"INDICES must be a vector\");\n    CAFFE_ENFORCE_EQ(1, lengthsInput.ndim(), \"LENGTHS must be a vector\");\n\n    const TIndex dataSize = dataInput.dim(0);\n    // Either first dim the data or how much we pull in indexies from it\n    const TIndex dataToReduceSize = indicesInput.dim(0);\n    const TIndex outputSize = lengthsInput.dim(0);\n    const int len_length = outputSize;\n\n    auto shape = dataInput.dims();\n    shape[0] = outputSize;\n    output->Resize(shape);\n\n    inclusive_scan_length_buffer_.ResizeLike(lengthsInput);\n    inclusive_scan_wrapper(\n        lengthsInput.template data<int>(),\n        len_length,\n        &inclusive_scan_buffer_,\n        &inclusive_scan_length_buffer_,\n        &context_);\n\n    const IndexType* indices = indicesInput.template data<IndexType>();\n    const T* in_data = dataInput.template data<T>();\n    const T* in_weights = weightsInput.template data<T>();\n    T* out_data = output->template mutable_data<T>();\n    auto* prefix_sum_length_data =\n        inclusive_scan_length_buffer_.template data<int>();\n    int N = dataSize;\n    int post = dataInput.size_from_dim(1);\n\n    auto maxThreads =\n        GetDeviceProperty(CaffeCudaGetDevice()).maxThreadsPerBlock;\n    if (post <= maxThreads) {\n      int multiple = std::min(maxThreads / post, 16);\n      dim3 block(post, multiple);\n      size_t smem = sizeof(T) * post * multiple;\n\n      sparse_length_weighted_sum_kernel<T, IndexType, true>\n          <<<len_length, block, smem, context_.cuda_stream()>>>(\n              in_data,\n              in_weights,\n              out_data,\n              prefix_sum_length_data,\n              indices,\n              N,\n              post,\n              len_length,\n              dataToReduceSize);\n    } else {\n      sparse_length_weighted_sum_kernel<T, IndexType, false>\n          <<<len_length, maxThreads, 0, context_.cuda_stream()>>>(\n              in_data,\n              in_weights,\n              out_data,\n              prefix_sum_length_data,\n              indices,\n              N,\n              post,\n              len_length,\n              dataToReduceSize);\n    }\n    return true;\n  }\n\n  enum { DATA = 0, WEIGHTS = 1, INDICES = 2, LENGTHS = 3 };\n\n private:\n  // menber field to manage memory\n  Tensor<Context> inclusive_scan_buffer_;\n  Tensor<Context> inclusive_scan_length_buffer_;\n};\n\ntemplate <typename SIndex>\n__global__ void\nMaxSegmentKernel(int n, const SIndex* segment_ids, SIndex* max_segment) {\n  typedef cub::BlockReduce<SIndex, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  int mx = 0;\n\n  for (int j = threadIdx.x; j < n; j += blockDim.x) {\n    mx = segment_ids[j] > mx ? segment_ids[j] : mx;\n  }\n  SIndex max_seg = BlockReduce(temp_storage).Reduce(mx, cub::Max());\n  if (threadIdx.x == 0) {\n    *max_segment = max_seg;\n  }\n}\n\ntemplate <typename SIndex, typename T>\n__global__ void UnsortedSegmentSumKernel(\n    int n,\n    int slize_sz,\n    const SIndex* segments,\n    const T* data,\n    T* out,\n    int* scales) {\n  CUDA_1D_KERNEL_LOOP(i, n) {\n    int slice_idx = i / slize_sz;\n    int j = i % slize_sz;\n    SIndex segment = segments[slice_idx];\n    atomicAdd(&out[segment * slize_sz + j], data[i]);\n    if (scales && j == 0) {\n      atomicAdd(&scales[segment], 1);\n    }\n  }\n}\n\ntemplate <typename SIndex, typename T>\n__global__ void\nSegmentScalingKernel(int m, int slize_sz, const int* scales, T* out) {\n  CUDA_1D_KERNEL_LOOP(i, m) {\n    int scale = scales[i / slize_sz];\n    out[i] = scale > 0 ? out[i] / scale : 0.0; // avoid 0/0 division\n  }\n}\n\ntemplate <typename T, typename SIndex, bool mean>\nclass CUDAUnsortedSegmentSumOp : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  CUDAUnsortedSegmentSumOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws) {}\n\n  ~CUDAUnsortedSegmentSumOp() {}\n\n  bool RunOnDevice() override {\n    auto& data = Input(0);\n    auto& segment_ids = Input(1);\n    auto* output = Output(0);\n\n    if (segment_ids.size() == 0 || data.size() == 0) {\n      // Special handling for empty input\n      auto dims = data.dims();\n      if (dims.size() > 0) {\n        dims[0] = 0;\n      }\n      output->Resize(dims);\n      output->template mutable_data<T>();\n      return true;\n    }\n\n    CAFFE_ENFORCE_EQ(1, segment_ids.ndim(), \"SEGMENT_IDS must be a vector\");\n    TIndex slize_sz = data.size_from_dim(1);\n\n    K_tensor_.Resize(1);\n    // Get maximum segment id so we can size the output.\n    // This must be done synchronously with host.\n    if (segment_ids.size() > 4096) {\n      // when the input size is large, device reduce is better.\n      size_t tmp_storage_bytes = 0;\n      // the first call to `Max` do nothing, but set correct tmp_storage_bytes.\n      cub::DeviceReduce::Max(\n          nullptr,\n          tmp_storage_bytes,\n          segment_ids.template data<SIndex>(), // input device data\n          K_tensor_.template mutable_data<SIndex>(), // output device data\n          segment_ids.size(), // number of items\n          context_.cuda_stream());\n\n      // the second call do the real computation.\n      buffer_tensor_.Resize(tmp_storage_bytes);\n      cub::DeviceReduce::Max(\n          static_cast<void*>(buffer_tensor_.mutable_data<char>()),\n          tmp_storage_bytes,\n          segment_ids.template data<SIndex>(), // input device data\n          K_tensor_.template mutable_data<SIndex>(), // output device data\n          segment_ids.size(), // number of items\n          context_.cuda_stream());\n    } else {\n      MaxSegmentKernel<SIndex>\n          <<<1, CAFFE_CUDA_NUM_THREADS, 0, context_.cuda_stream()>>>(\n              segment_ids.size(),\n              segment_ids.template data<SIndex>(),\n              K_tensor_.mutable_data<SIndex>());\n    }\n\n    SIndex K = 0;\n    context_.CopyBytes<CUDAContext, CPUContext>(\n        sizeof(SIndex), K_tensor_.template data<SIndex>(), &K);\n    context_.FinishDeviceComputation();\n\n    auto dims = data.dims();\n    dims[0] = K + 1;\n    output->Resize(dims);\n\n    // Clear the output as we will be accumulating the values\n    math::Set<T, CUDAContext>(\n        output->size(), T(0), output->template mutable_data<T>(), &context_);\n\n    if (!mean) {\n      UnsortedSegmentSumKernel<SIndex, T><<<\n          CAFFE_GET_BLOCKS(data.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          data.size(),\n          slize_sz,\n          segment_ids.template data<SIndex>(),\n          data.template data<T>(),\n          output->template mutable_data<T>(),\n          nullptr);\n    } else {\n      // For mean, we need to compute scaling factors\n      scaling_factors_.Resize(K + 1);\n      math::Set<int, CUDAContext>(\n          scaling_factors_.size(),\n          int(0),\n          scaling_factors_.template mutable_data<int>(),\n          &context_);\n      UnsortedSegmentSumKernel<SIndex, T><<<\n          CAFFE_GET_BLOCKS(data.size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          data.size(),\n          slize_sz,\n          segment_ids.template data<SIndex>(),\n          data.template data<T>(),\n          output->template mutable_data<T>(),\n          scaling_factors_.template mutable_data<int>());\n      // Divide by the scaling factors to get means\n      SegmentScalingKernel<SIndex, T><<<\n          CAFFE_GET_BLOCKS(output->size()),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          output->size(),\n          slize_sz,\n          scaling_factors_.template data<int>(),\n          output->template mutable_data<T>());\n    }\n    return true;\n  }\n\n private:\n  Tensor<CUDAContext> buffer_tensor_;\n  Tensor<CUDAContext> K_tensor_;\n  Tensor<CUDAContext> scaling_factors_; // for mean\n};\n\ntemplate <typename SIndex>\n__global__ void segment_lengths_kernel(int N, const SIndex* X, SIndex* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    atomicAdd(&Y[X[i]], 1);\n  }\n}\n\ntemplate <typename T, typename SIndex, bool LOGEXP = false>\n__global__ void sorted_segment_mean_kernel(\n    const SIndex K,\n    const int N,\n    const SIndex* S,\n    const SIndex* I,\n    const T* X,\n    T* Y) {\n  for (int sId = blockIdx.x; sId < K; sId += gridDim.x) {\n    const int start_index = sId > 0 ? S[sId] * N : 0;\n    const int y_start_index = sId * N;\n    for (int i = threadIdx.x; i < N; i += blockDim.x) {\n      T sum = 0.0;\n      for (int j = 0; j < I[sId]; ++j) {\n        const T x_i_j = X[start_index + j * N + i];\n        sum += LOGEXP ? exp(x_i_j) : x_i_j;\n      }\n      const T norm_sum = sum / I[sId];\n      Y[y_start_index + i] = LOGEXP ? log(norm_sum) : norm_sum;\n    }\n  }\n}\n\ntemplate <typename T, typename SIndex, bool LOGEXP, class Context = CUDAContext>\nclass SortedSegmentRangeMeanOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SortedSegmentRangeMeanOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws) {}\n  ~SortedSegmentRangeMeanOp() {}\n\n  bool RunOnDevice() override {\n    const auto& input = Input(0);\n    const auto& indices = Input(1);\n    int M = input.dim32(0);\n    int N = input.size_from_dim(1);\n    auto* output = Output(0);\n    auto dims = input.dims();\n    SIndex K = 0;\n    context_.template CopyBytes<Context, CPUContext>(\n        sizeof(SIndex),\n        indices.template data<SIndex>() + indices.size() - 1,\n        &K);\n    context_.FinishDeviceComputation();\n    K += 1;\n    dims[0] = K;\n    if (segment_len_.size() != K) {\n      segment_len_.Resize(K);\n      segment_len_prefix_sum_.Resize(K);\n    }\n    output->Resize(dims);\n    math::Set<SIndex, CUDAContext>(\n        segment_len_.size(),\n        0,\n        segment_len_.template mutable_data<SIndex>(),\n        &context_);\n    segment_lengths_kernel<<<\n        CAFFE_GET_BLOCKS(indices.size()),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        indices.size(),\n        indices.template data<SIndex>(),\n        segment_len_.template mutable_data<SIndex>());\n    size_t temp_storage_bytes = 0;\n    cub::DeviceScan::ExclusiveSum(\n        nullptr,\n        temp_storage_bytes,\n        segment_len_.template data<SIndex>(),\n        segment_len_prefix_sum_.template mutable_data<SIndex>(),\n        K,\n        context_.cuda_stream());\n    auto buffer_size = (temp_storage_bytes + sizeof(T)) / sizeof(T);\n    prefix_buffer_.Resize(buffer_size);\n    void* dev_temp_storage =\n        static_cast<void*>(prefix_buffer_.mutable_data<T>());\n    cub::DeviceScan::ExclusiveSum(\n        dev_temp_storage,\n        temp_storage_bytes,\n        segment_len_.template data<SIndex>(),\n        segment_len_prefix_sum_.template mutable_data<SIndex>(),\n        K,\n        context_.cuda_stream());\n    sorted_segment_mean_kernel<T, SIndex, LOGEXP>\n        <<<min(K, CAFFE_MAXIMUM_NUM_BLOCKS),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           context_.cuda_stream()>>>(\n            K,\n            N,\n            segment_len_prefix_sum_.template data<SIndex>(),\n            segment_len_.template data<SIndex>(),\n            input.template data<T>(),\n            output->template mutable_data<T>());\n    return true;\n  }\n\n private:\n  Tensor<CUDAContext> segment_len_; // for mean\n  Tensor<CUDAContext> segment_len_prefix_sum_;\n  Tensor<CUDAContext> prefix_buffer_;\n};\n\ntemplate <typename T, typename SIndex, bool LOGEXP = false>\n__global__ void sorted_segment_mean_gradient_kernel(\n    const int M,\n    const int N,\n    const T* X,\n    const T* Y,\n    const T* dY,\n    const SIndex* I,\n    const SIndex* S,\n    T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, M * N) {\n    const int sId = I[i / N];\n    const int sSize = S[sId];\n    const int yId = N * sId + i % N;\n    dX[i] = LOGEXP ? dY[yId] * exp(X[i] - Y[yId]) / sSize : dY[yId] / sSize;\n  }\n}\n\ntemplate <typename T, typename SIndex, bool LOGEXP, class Context = CUDAContext>\nclass SortedSegmentRangeMeanGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SortedSegmentRangeMeanGradientOp(\n      const OperatorDef& operator_def,\n      Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws) {}\n  ~SortedSegmentRangeMeanGradientOp() {}\n\n  bool RunOnDevice() override {\n    const auto& X = Input(0);\n    const auto& Y = Input(1);\n    const auto& dY = Input(2);\n    const auto& I = Input(3);\n    auto* dX = Output(0);\n    dX->ResizeLike(X);\n\n    const int M = X.dim32(0);\n    const int N = X.size_from_dim(1);\n\n    SIndex K = 0;\n    context_.template CopyBytes<Context, CPUContext>(\n        sizeof(SIndex), I.template data<SIndex>() + I.size() - 1, &K);\n\n    K += 1;\n\n    if (segment_len_.size() != K) {\n      segment_len_.Resize(K);\n    }\n\n    math::Set<SIndex, CUDAContext>(\n        segment_len_.size(),\n        0,\n        segment_len_.template mutable_data<SIndex>(),\n        &context_);\n    segment_lengths_kernel<<<\n        CAFFE_GET_BLOCKS(I.size()),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        I.size(),\n        I.template data<SIndex>(),\n        segment_len_.template mutable_data<SIndex>());\n    sorted_segment_mean_gradient_kernel<T, SIndex, LOGEXP>\n        <<<CAFFE_GET_BLOCKS(dX->size()),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           context_.cuda_stream()>>>(\n            M,\n            N,\n            X.template data<T>(),\n            Y.template data<T>(),\n            dY.template data<T>(),\n            I.template data<SIndex>(),\n            segment_len_.template data<SIndex>(),\n            dX->template mutable_data<T>());\n\n    return true;\n  }\n\n private:\n  Tensor<CUDAContext> segment_len_; // for mean\n};\n\nREGISTER_CUDA_OPERATOR_STR(\n    \"LengthsSum\",\n    CUDASparseLengthsSumOp<float, CUDAContext, false>);\nREGISTER_CUDA_OPERATOR_STR(\n    \"SparseLengthsSum\",\n    CUDASparseLengthsSumOp<float, CUDAContext, true>);\nREGISTER_CUDA_OPERATOR_STR(\n    \"SparseLengthsWeightedSum\",\n    CUDASparseLengthsWeightedSumOp<float, CUDAContext, true>);\nREGISTER_CUDA_OPERATOR_STR(\n    \"UnsortedSegmentSum\",\n    CUDAUnsortedSegmentSumOp<float, int, false>);\nREGISTER_CUDA_OPERATOR_STR(\n    \"UnsortedSegmentMean\",\n    CUDAUnsortedSegmentSumOp<float, int, true>);\nREGISTER_CUDA_OPERATOR_STR(\n    \"SortedSegmentRangeMean\",\n    SortedSegmentRangeMeanOp<float, int, false>);\nREGISTER_CUDA_OPERATOR_STR(\n    \"SortedSegmentRangeLogMeanExp\",\n    SortedSegmentRangeMeanOp<float, int, true>);\nREGISTER_CUDA_OPERATOR_STR(\n    \"SortedSegmentRangeMeanGradient\",\n    SortedSegmentRangeMeanGradientOp<float, int, false>);\nREGISTER_CUDA_OPERATOR_STR(\n    \"SortedSegmentRangeLogMeanExpGradient\",\n    SortedSegmentRangeMeanGradientOp<float, int, true>);\n\ntemplate <typename T, class Context = CUDAContext>\nclass CUDASparseLengthsSumGradientWithIndicesOp : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  CUDASparseLengthsSumGradientWithIndicesOp(\n      const OperatorDef& operator_def,\n      Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws) {}\n\n  ~CUDASparseLengthsSumGradientWithIndicesOp() {}\n\n  bool RunOnDevice() override {\n    auto& segmentGradsInput = Input(0);\n    auto& lengthsInput = Input(1);\n    auto& indicesInput = Input(2);\n    auto* dataGradsOutput = Output(0);\n    CAFFE_ENFORCE_EQ(1, lengthsInput.ndim(), \"LENGTHS must be a vector\");\n    int len_length = lengthsInput.dim(0);\n    CAFFE_ENFORCE(segmentGradsInput.ndim() > 0);\n    CAFFE_ENFORCE(len_length == segmentGradsInput.dim(0));\n\n    inclusive_scan_length_buffer_.ResizeLike(lengthsInput);\n    inclusive_scan_wrapper(\n        lengthsInput.template data<int>(),\n        len_length,\n        &inclusive_scan_buffer_,\n        &inclusive_scan_length_buffer_,\n        &context_);\n\n    // compute output size using length\n    auto* prefix_sum_length_data =\n        inclusive_scan_length_buffer_.template data<int>();\n\n    auto shape = segmentGradsInput.dims();\n    int output_0dim = indicesInput.dim(0);\n    shape[0] = output_0dim;\n    dataGradsOutput->Resize(shape);\n\n    const T* in_data = segmentGradsInput.template data<T>();\n    T* out_data = dataGradsOutput->template mutable_data<T>();\n\n    int N = output_0dim;\n    int post = segmentGradsInput.size_from_dim(1);\n\n    auto maxThreads =\n        GetDeviceProperty(CaffeCudaGetDevice()).maxThreadsPerBlock;\n\n    if (post <= maxThreads) {\n      int multiple = std::min(maxThreads / post, 16);\n      dim3 block(post, multiple);\n\n      length_sum_gradient_kernel<T, true>\n          <<<len_length, block, 0, context_.cuda_stream()>>>(\n\n              in_data, out_data, prefix_sum_length_data, N, post, len_length);\n    } else {\n      length_sum_gradient_kernel<T, false>\n          <<<len_length, maxThreads, 0, context_.cuda_stream()>>>(\n              in_data, out_data, prefix_sum_length_data, N, post, len_length);\n    }\n\n    return true;\n  }\n\n private:\n  // menber field to manage memory\n  Tensor<Context> inclusive_scan_buffer_;\n  Tensor<Context> inclusive_scan_length_buffer_;\n};\n\ntemplate <typename T, class Context = CUDAContext>\nclass CUDASparseLengthsWeightedSumGradientWithIndicesOp\n    : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  CUDASparseLengthsWeightedSumGradientWithIndicesOp(\n      const OperatorDef& operator_def,\n      Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws) {}\n\n  ~CUDASparseLengthsWeightedSumGradientWithIndicesOp() {}\n\n  bool RunOnDevice() override {\n    auto& weightsInput = Input(0);\n    auto& segmentGradsInput = Input(1);\n    auto& lengthsInput = Input(2);\n    auto& indicesInput = Input(3);\n    auto* dataGradsOutput = Output(0);\n    CAFFE_ENFORCE_EQ(1, lengthsInput.ndim(), \"LENGTHS must be a vector\");\n    CAFFE_ENFORCE_EQ(1, weightsInput.ndim(), \"WEIGHTS must be a vector\");\n    int len_length = lengthsInput.dim(0);\n    CAFFE_ENFORCE(segmentGradsInput.ndim() > 0);\n    CAFFE_ENFORCE(len_length == segmentGradsInput.dim(0));\n\n    inclusive_scan_length_buffer_.ResizeLike(lengthsInput);\n    inclusive_scan_wrapper(\n        lengthsInput.template data<int>(),\n        len_length,\n        &inclusive_scan_buffer_,\n        &inclusive_scan_length_buffer_,\n        &context_);\n\n    // compute output size using length\n    auto* prefix_sum_length_data =\n        inclusive_scan_length_buffer_.template data<int>();\n\n    auto shape = segmentGradsInput.dims();\n    int output_0dim = indicesInput.dim(0);\n    shape[0] = output_0dim;\n    dataGradsOutput->Resize(shape);\n\n    const T* in_data = segmentGradsInput.template data<T>();\n    const T* in_weights = weightsInput.template data<T>();\n    T* out_data = dataGradsOutput->template mutable_data<T>();\n\n    int N = output_0dim;\n    int post = segmentGradsInput.size_from_dim(1);\n    auto maxThreads =\n        GetDeviceProperty(CaffeCudaGetDevice()).maxThreadsPerBlock;\n\n    if (post < maxThreads) {\n      int multiple = std::min(maxThreads / post, 16);\n      dim3 block(post, multiple);\n\n      length_weighted_sum_gradient_kernel<T, true>\n          <<<len_length, block, 0, context_.cuda_stream()>>>(\n              in_data,\n              in_weights,\n              out_data,\n              prefix_sum_length_data,\n              N,\n              post,\n              len_length);\n    } else {\n      length_weighted_sum_gradient_kernel<T, false>\n          <<<len_length, maxThreads, 0, context_.cuda_stream()>>>(\n              in_data,\n              in_weights,\n              out_data,\n              prefix_sum_length_data,\n              N,\n              post,\n              len_length);\n    }\n\n    return true;\n  }\n\n private:\n  // menber field to manage memory\n  Tensor<Context> inclusive_scan_buffer_;\n  Tensor<Context> inclusive_scan_length_buffer_;\n};\n\ntemplate <typename T, class Context = CUDAContext>\nclass CUDASparseLengthsIndicesInGradientWeightedSumWithMainInputGradientOp\n    : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  CUDASparseLengthsIndicesInGradientWeightedSumWithMainInputGradientOp(\n      const OperatorDef& operator_def,\n      Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws) {}\n\n  ~CUDASparseLengthsIndicesInGradientWeightedSumWithMainInputGradientOp() {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(this, Input(4));\n  }\n\n  template <typename IndexType>\n  bool DoRunWithType() {\n    auto& weightsInput = Input(0);\n    auto& segmentGradsInput = Input(1);\n    auto& lengthsInput = Input(2);\n    auto& dataInput = Input(3);\n    auto& indicesInput = Input(4);\n    auto* dataGradsOutput = Output(0);\n    auto* weightGradsOutput = Output(1);\n    CAFFE_ENFORCE_EQ(1, lengthsInput.ndim(), \"LENGTHS must be a vector\");\n    CAFFE_ENFORCE_EQ(1, weightsInput.ndim(), \"WEIGHTS must be a vector\");\n    int len_length = lengthsInput.dim(0);\n    CAFFE_ENFORCE(segmentGradsInput.ndim() > 0);\n    CAFFE_ENFORCE(len_length == segmentGradsInput.dim(0));\n\n    inclusive_scan_length_buffer_.ResizeLike(lengthsInput);\n    inclusive_scan_wrapper(\n        lengthsInput.template data<int>(),\n        len_length,\n        &inclusive_scan_buffer_,\n        &inclusive_scan_length_buffer_,\n        &context_);\n\n    // compute output size using length\n    auto* prefix_sum_length_data =\n        inclusive_scan_length_buffer_.template data<int>();\n\n    auto shape = segmentGradsInput.dims();\n    int output_0dim = indicesInput.dim(0);\n    shape[0] = output_0dim;\n    dataGradsOutput->Resize(shape);\n    weightGradsOutput->ResizeLike(indicesInput);\n\n    const T* in_data = dataInput.template data<T>();\n    const T* in_grads = segmentGradsInput.template data<T>();\n    const T* in_weights = weightsInput.template data<T>();\n    const IndexType* indices = indicesInput.template data<IndexType>();\n    T* out_data_grads = dataGradsOutput->template mutable_data<T>();\n    T* out_weight_grads = weightGradsOutput->template mutable_data<T>();\n\n    int N = output_0dim;\n    int post = segmentGradsInput.size_from_dim(1);\n\n    if (post > 128) {\n      length_weighted_sum_with_main_input_gradient_kernel<T, IndexType, 512>\n          <<<len_length, 512, 0, context_.cuda_stream()>>>(\n              in_grads,\n              in_weights,\n              in_data,\n              indices,\n              out_data_grads,\n              out_weight_grads,\n              prefix_sum_length_data,\n              N,\n              post,\n              len_length);\n    } else if (post > 64) {\n      length_weighted_sum_with_main_input_gradient_kernel<T, IndexType, 128>\n          <<<len_length, 128, 0, context_.cuda_stream()>>>(\n              in_grads,\n              in_weights,\n              in_data,\n              indices,\n              out_data_grads,\n              out_weight_grads,\n              prefix_sum_length_data,\n              N,\n              post,\n              len_length);\n    } else if (post > 32) {\n      length_weighted_sum_with_main_input_gradient_kernel<T, IndexType, 64>\n          <<<len_length, 64, 0, context_.cuda_stream()>>>(\n              in_grads,\n              in_weights,\n              in_data,\n              indices,\n              out_data_grads,\n              out_weight_grads,\n              prefix_sum_length_data,\n              N,\n              post,\n              len_length);\n    } else {\n      length_weighted_sum_with_main_input_gradient_kernel<T, IndexType, 32>\n          <<<len_length, 32, 0, context_.cuda_stream()>>>(\n              in_grads,\n              in_weights,\n              in_data,\n              indices,\n              out_data_grads,\n              out_weight_grads,\n              prefix_sum_length_data,\n              N,\n              post,\n              len_length);\n    }\n\n    return true;\n  }\n\n private:\n  // menber field to manage memory\n  Tensor<Context> inclusive_scan_buffer_;\n  Tensor<Context> inclusive_scan_length_buffer_;\n};\n\nREGISTER_CUDA_OPERATOR(\n    SparseLengthsIndicesInGradientWeightedSumGradient,\n    CUDASparseLengthsWeightedSumGradientWithIndicesOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(\n    SparseLengthsIndicesInGradientWeightedSumWithMainInputGradient,\n    CUDASparseLengthsIndicesInGradientWeightedSumWithMainInputGradientOp<\n        float,\n        CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(\n    SparseLengthsIndicesInGradientSumGradient,\n    CUDASparseLengthsSumGradientWithIndicesOp<float, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(\n    LengthsIndicesInGradientSumGradient,\n    CUDASparseLengthsSumGradientWithIndicesOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/selu_op.cc",
    "content": "#include \"caffe2/operators/selu_op.h\"\n\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool SeluOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n\n  ConstEigenVectorArrayMap<float> Xvec(X.data<float>(), X.size());\n  EigenVectorArrayMap<float> Yvec(Y->mutable_data<float>(), Y->size());\n  Yvec = lambda_ * (Xvec > 0).select(Xvec, (alpha_ * Xvec.exp() - alpha_));\n  return true;\n}\n\ntemplate <>\nbool SeluGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  CAFFE_ENFORCE_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n\n  ConstEigenVectorArrayMap<float> Yvec(Y.data<float>(), Y.size());\n  ConstEigenVectorArrayMap<float> dYvec(dY.data<float>(), dY.size());\n  EigenVectorArrayMap<float> dXvec(dX->mutable_data<float>(), dX->size());\n\n  const float la = lambda_ * alpha_;\n  dXvec = (Yvec > 0).select(lambda_ * dYvec, dYvec * (Yvec + la));\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(Selu, SeluOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(SeluGradient, SeluGradientOp<float, CPUContext>);\n\n// Input: X; output: Y\nOPERATOR_SCHEMA(Selu)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nSelu takes one input data (Tensor<T>) and produces one output data\n(Tensor<T>) where the function, y = scale*(alpha_*e^x-alpha_ if x < 0 else x),\nis applied to the tensor elementwise.\n)DOC\")\n    .Arg(\n        \"alpha\",\n        \"(float) default to 1.6732~; affects the activation function itself. \"\n        \"This should go with the weight initialization in the paper. \"\n        \" See https://arxiv.org/abs/1706.02515 \")\n    .Arg(\n        \"scale\",\n        \"(float) default to 1.0507~; affects the activation function itself.\")\n    .Input(0, \"X\", \"input tensor\")\n    .Output(0, \"Y\", \"input tensor\");\n\n// Input: Y, dY; output: dX\nOPERATOR_SCHEMA(SeluGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{1, 0}})\n    .SetDoc(R\"DOC(\nSeluGradient takes both Y and dY and uses this to update dX according to the\nchain rule and derivatives of the selu function.\n)DOC\")\n    .Arg(\n        \"alpha\",\n        \"(float) default to 1.6732~; affects the activation function itself.\"\n        \"This should go with the weight initialization in the paper. \"\n        \" See https://arxiv.org/abs/1706.02515 \")\n    .Arg(\n        \"scale\",\n        \"(float) default to 1.0507~; affects the activation function itself.\")\n    .Input(0, \"Y\", \"input tensor\")\n    .Input(1, \"dY\", \"input tensor\");\n\nclass GetSeluGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        def_.type() + \"Gradient\",\n        \"\",\n        vector<string>{O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Selu, GetSeluGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/selu_op.cu",
    "content": "#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/selu_op.h\"\n\nnamespace caffe2 {\nnamespace {\ntemplate <typename T>\n__global__ void SeluKernel(const int N, const T* X, T* Y, T alpha_, T lambda_) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = lambda_ * (X[i] > 0 ? X[i] : alpha_ * __expf(X[i]) - alpha_);\n  }\n}\n\ntemplate <typename T>\n__global__ void SeluGradientKernel(\n    const int N,\n    const T* Y,\n    const T* dY,\n    T* dX,\n    T alpha_,\n    T lambda_) {\n  const T c = lambda_ * alpha_;\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    // Reuse Y[i] to avoid computing exp(X[i])\n    dX[i] = Y[i] > 0 ? lambda_ * dY[i] : dY[i] * (Y[i] + c);\n  }\n}\n} // namespace\n\ntemplate <>\nbool SeluOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE_GT(X.size(), 0);\n  Y->ResizeLike(X);\n  SeluKernel<<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(), X.data<float>(), Y->mutable_data<float>(), alpha_, lambda_);\n  return true;\n}\n\ntemplate <>\nbool SeluGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  CAFFE_ENFORCE_GT(Y.size(), 0);\n  CAFFE_ENFORCE_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n  SeluGradientKernel<<<\n      CAFFE_GET_BLOCKS(Y.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      Y.size(),\n      Y.data<float>(),\n      dY.data<float>(),\n      dX->mutable_data<float>(),\n      alpha_,\n      lambda_);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Selu, SeluOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(SeluGradient, SeluGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/selu_op.h",
    "content": "#ifndef CAFFE2_OPERATORS_SELU_OP_H_\n#define CAFFE2_OPERATORS_SELU_OP_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SeluOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  SeluOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    alpha_ = OperatorBase::GetSingleArgument<T>(\n        \"alpha\", 1.6732632423543772848170429916717f);\n    lambda_ = OperatorBase::GetSingleArgument<T>(\n        \"scale\", 1.0507009873554804934193349852946f);\n    // In the paper \"scale\" is named \"lambda\", but \"lambda\" is a reserved\n    // keyword in python\n    CAFFE_ENFORCE_GT(lambda_, 1.0);\n  }\n\n  bool RunOnDevice() override;\n\n protected:\n  T alpha_;\n  T lambda_;\n};\n\ntemplate <typename T, class Context>\nclass SeluGradientOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SeluGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    alpha_ = OperatorBase::GetSingleArgument<T>(\n        \"alpha\", 1.6732632423543772848170429916717f);\n    lambda_ = OperatorBase::GetSingleArgument<T>(\n        \"scale\", 1.0507009873554804934193349852946f);\n    CAFFE_ENFORCE_GT(lambda_, 1.0);\n  }\n\n  bool RunOnDevice() override;\n\n protected:\n  T alpha_;\n  T lambda_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SELU_OP_H_\n"
  },
  {
    "path": "caffe2/operators/sequence_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/sequence_ops.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\ntemplate <>\ntemplate <typename T>\nvoid GatherPaddingOp<CPUContext>::GatherPadding(\n    const int outer_size,\n    const int lengths_size,\n    const int block_size,\n    const int pad_width,\n    const T* in_ptr,\n    const int* lengths_ptr,\n    T* padding_start_ptr,\n    T* padding_end_ptr) {\n  CAFFE_ENFORCE(\n      (!std::is_same<bool, T>::value),\n      \"GatherPadding should not be executed on an input of type bool, as \"\n      \"addition is not properly defined with booleans.\");\n  int64_t total_length = 0;\n  for (int i = 0; i < lengths_size; ++i) {\n    // check total length consistency\n    const auto length = lengths_ptr[i];\n    total_length += length;\n    CAFFE_ENFORCE_LE(total_length, outer_size);\n    // accumulate start paddings\n    for (int j = 0; j < startPaddingWidth_; ++j) {\n      for (int k = 0; k < block_size; ++k) {\n        // Note: MSVC warns about unsafe use of type bool in operation.\n        // This is now guarded by a CAFFE_ENFORCE so we can suppress it.\n        #pragma warning(suppress: 4804)\n        padding_start_ptr[k] += in_ptr[k];\n      }\n      in_ptr += block_size;\n    }\n    in_ptr += block_size * (length - pad_width);\n    // accumulate end paddings\n    for (int j = 0; j < endPaddingWidth_; ++j) {\n      for (int k = 0; k < block_size; ++k) {\n        #pragma warning(suppress: 4804)\n        padding_end_ptr[k] += in_ptr[k];\n      }\n      in_ptr += block_size;\n    }\n  }\n}\n\ntemplate <>\ntemplate <typename T>\nbool RemovePaddingOp<CPUContext>::DoRunWithType() {\n  const auto& in = Input(0);\n  CAFFE_ENFORCE_GE(in.ndim(), 1);\n  const int32_t outer_size = in.dims()[0];\n  const auto block_size = std::accumulate(\n      in.dims().begin() + 1, in.dims().end(), 1, std::multiplies<TIndex>());\n  const auto pad_width = startPaddingWidth_ + endPaddingWidth_;\n\n  // if no lengths is provided, assume it is a single full-span entry\n  const int32_t* lengths_ptr = &outer_size;\n  int64_t lengths_size = 1;\n  if (InputSize() > 1) {\n    const auto& lengths = Input(1);\n    lengths_ptr = lengths.data<int32_t>();\n    lengths_size = lengths.size();\n  }\n\n  auto* out = Output(0);\n  {\n    auto out_dims = in.dims();\n    out_dims[0] -= pad_width * lengths_size;\n    out->Resize(std::move(out_dims));\n  }\n  const auto* in_ptr = in.template data<T>();\n  auto* out_ptr = out->template mutable_data<T>();\n  int64_t total_length = 0;\n  for (int i = 0; i < lengths_size; ++i) {\n    // check that total length is consistent\n    const auto length = lengths_ptr[i];\n    total_length += length;\n    CAFFE_ENFORCE_LE(total_length, outer_size);\n    std::copy(\n        in_ptr + block_size * startPaddingWidth_,\n        in_ptr + block_size * (length - endPaddingWidth_),\n        out_ptr);\n    in_ptr += block_size * length;\n    out_ptr += block_size * (length - pad_width);\n  }\n  if (OutputSize() == 1) {\n    return true;\n  }\n  auto* lengths_out = Output(1);\n  lengths_out->Resize(lengths_size);\n  std::transform(\n      lengths_ptr,\n      lengths_ptr + lengths_size,\n      lengths_out->mutable_data<int32_t>(),\n      [pad_width](int32_t x) { return x - pad_width; });\n  return true;\n}\n\ntemplate <>\ntemplate <typename T>\nbool AddPaddingOp<CPUContext>::MakePadding(\n    const T* in_ptr,\n    T* out_ptr,\n    const int32_t* lengths_ptr,\n    int32_t lengths_size,\n    int32_t outer_size,\n    const T* padding_start_ptr,\n    const T* padding_end_ptr,\n    int64_t block_size) {\n  if (!lengths_ptr) {\n    lengths_ptr = &outer_size;\n  }\n\n  int64_t total_length = 0;\n  for (int i = 0; i < lengths_size; ++i) {\n    // check that total length is consistent\n    const auto length = lengths_ptr[i];\n    total_length += length;\n    CAFFE_ENFORCE_LE(total_length, outer_size);\n    // copy padding before\n    if (!padding_start_ptr) {\n      memset(out_ptr, 0, block_size * startPaddingWidth_ * sizeof(T));\n      out_ptr += block_size * startPaddingWidth_;\n    } else {\n      for (int j = 0; j < startPaddingWidth_; ++j) {\n        std::copy(padding_start_ptr, padding_start_ptr + block_size, out_ptr);\n        out_ptr += block_size;\n      }\n    }\n    // copy payload\n    const auto num_elems = block_size * length;\n    std::copy(in_ptr, in_ptr + num_elems, out_ptr);\n    in_ptr += num_elems;\n    out_ptr += num_elems;\n    // copy padding after\n    if (!padding_end_ptr) {\n      memset(out_ptr, 0, block_size * endPaddingWidth_ * sizeof(T));\n      out_ptr += block_size * endPaddingWidth_;\n    } else {\n      for (int j = 0; j < endPaddingWidth_; ++j) {\n        std::copy(padding_end_ptr, padding_end_ptr + block_size, out_ptr);\n        out_ptr += block_size;\n      }\n    }\n  }\n  if (OutputSize() == 1) {\n    return true;\n  }\n  auto* lengths_out = Output(1);\n  lengths_out->Resize(lengths_size);\n  const auto pad_width = startPaddingWidth_ + endPaddingWidth_;\n  std::transform(\n      lengths_ptr,\n      lengths_ptr + lengths_size,\n      lengths_out->mutable_data<int32_t>(),\n      [pad_width](int32_t x) { return x + pad_width; });\n  return true;\n}\n\ntemplate <>\nbool PadEmptySamplesOp<CPUContext>::RunOnDevice() {\n  auto& lengths = Input(0);\n  auto* lengthsPtr = lengths.template data<int32_t>();\n  CAFFE_ENFORCE(lengths.ndim() == 1, \"LENGTH should be 1-D\");\n  CAFFE_ENFORCE(InputSize() >= 1, \"Input size must be no less than 1\");\n\n  auto* out_lengths = Output(0);\n  int needPadding = 0;\n  int sumLen = 0;\n  for (int i = 0; i < lengths.size(); ++i) {\n    if (lengthsPtr[i] == 0) {\n      needPadding++;\n    }\n    sumLen += lengthsPtr[i];\n  }\n\n  out_lengths->Resize(lengths.size());\n  auto* outLengthsPtr = out_lengths->template mutable_data<int32_t>();\n  for (int i = 0; i < lengths.size(); ++i) {\n    if (lengthsPtr[i] == 0) {\n      outLengthsPtr[i] = 1;\n    } else {\n      outLengthsPtr[i] = lengthsPtr[i];\n    }\n  }\n\n  for (int k = 0; k < InputSize() - 1; k++) {\n    auto& features = Input(1 + k);\n    CAFFE_ENFORCE(features.ndim() >= 1, \"FEATURE should at least 1-D\");\n    CAFFE_ENFORCE(\n        features.dim(0) == sumLen, \"FEATURE and LENGTH should be consistent\");\n    const auto block_size = features.size_from_dim(1);\n\n    auto* out_features = Output(1 + k);\n    auto outDim = features.dims();\n    outDim.at(0) += needPadding;\n    out_features->Resize(outDim);\n    auto dst =\n        static_cast<char*>(out_features->raw_mutable_data(features.meta()));\n    auto src_base = static_cast<const char*>(features.raw_data());\n    // copy data and add padding index as zero\n    Tensor<CPUContext> zero;\n    zero.Resize(block_size);\n    auto zeroPtr =\n        static_cast<const char*>(zero.raw_mutable_data(features.meta()));\n    int start_dest = 0;\n    int start_src = 0;\n    for (int i = 0; i < lengths.size(); ++i) {\n      if (lengthsPtr[i] == 0) {\n        context_.template CopyItems<CPUContext, CPUContext>(\n            features.meta(),\n            block_size,\n            zeroPtr,\n            dst + start_dest * features.meta().itemsize());\n        start_dest += block_size;\n      } else {\n        auto src = src_base + start_src * features.meta().itemsize();\n        context_.template CopyItems<CPUContext, CPUContext>(\n            features.meta(),\n            lengthsPtr[i] * block_size,\n            src,\n            dst + start_dest * features.meta().itemsize());\n        start_src += lengthsPtr[i] * block_size;\n        start_dest += lengthsPtr[i] * block_size;\n      }\n    }\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(AddPadding, AddPaddingOp<CPUContext>);\nREGISTER_CPU_OPERATOR(RemovePadding, RemovePaddingOp<CPUContext>);\nREGISTER_CPU_OPERATOR(GatherPadding, GatherPaddingOp<CPUContext>);\nREGISTER_CPU_OPERATOR(PadEmptySamples, PadEmptySamplesOp<CPUContext>);\n\nstruct GetAddPaddingGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    // whether to provide lengths as input to gradient\n    vector<std::string> g_inputs{GO(0)};\n    if (Def().input_size() > 1) {\n      CAFFE_ENFORCE(Def().output_size() > 1);\n      g_inputs.push_back(O(1));\n    }\n\n    vector<OperatorDef> ops;\n    // gradient on the data\n    ops.push_back(CreateOperatorDef(\n        \"RemovePadding\", \"\", g_inputs, vector<string>{GI(0)}));\n    // gradient on the start_padding (and end_padding)\n    if (Def().input_size() >= 3) {\n      std::vector<string> padding_grads{GI(2)};\n      if (Def().input_size() == 4) {\n        padding_grads.push_back(GI(3));\n      }\n      auto g_inputs2 = g_inputs;\n      ops.push_back(\n          CreateOperatorDef(\"GatherPadding\", \"\", g_inputs2, padding_grads));\n    }\n    return ops;\n  }\n};\nREGISTER_GRADIENT(AddPadding, GetAddPaddingGradient);\n\nstruct GetRemovePaddingGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    // whether to provide lengths as input to gradient\n    vector<std::string> g_inputs{GO(0)};\n    if (Def().input_size() > 1) {\n      CAFFE_ENFORCE(Def().output_size() > 1);\n      g_inputs.push_back(O(1));\n    }\n\n    return SingleGradientDef(\"AddPadding\", \"\", g_inputs, vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(RemovePadding, GetRemovePaddingGradient);\n\nOPERATOR_SCHEMA(AddPadding)\n    .NumInputs(1, 4)\n    .NumOutputs(1, 2)\n    .SetDoc(R\"DOC(\nGiven a partitioned tensor T<N, D1..., Dn>, where the partitions are\ndefined as ranges on its outer-most (slowest varying) dimension N,\nwith given range lengths, return a tensor T<N + 2*padding_width, D1 ..., Dn>\nwith paddings added to the start and end of each range.\nOptionally, different paddings can be provided for beginning and end. Paddings\nprovided must be a tensor T<D1..., Dn>.\n\nIf no padding is provided, add zero padding.\nIf no lengths vector is provided, add padding only once,\nat the start and end of data.\n)DOC\")\n    .Arg(\n        \"padding_width\",\n        \"Number of copies of padding to add around each range.\")\n    .Arg(\n        \"end_padding_width\",\n        \"(Optional) Specifies a different end-padding width.\")\n    .Input(0, \"data_in\", \"(T<N, D1..., Dn>) Input data\")\n    .Input(\n        1,\n        \"lengths\",\n        \"(i64) Num of elements in each range. sum(lengths) = N.\")\n    .Input(2, \"start_padding\", \"T<D1..., Dn> Padding data for range start.\")\n    .Input(\n        3,\n        \"end_padding\",\n        \"T<D1..., Dn> (optional) Padding for range end. \"\n        \"If not provided, start_padding is used as end_padding as well.\")\n    .Output(0, \"data_out\", \"(T<N + 2*padding_width, D1..., Dn>) Padded data.\")\n    .Output(1, \"lengths_out\", \"(i64, optional) Lengths for each padded range.\");\n\nOPERATOR_SCHEMA(RemovePadding)\n    .NumInputs(1, 2)\n    .NumOutputs(1, 2)\n    .SetDoc(R\"DOC(\nRemove padding around the edges of each segment of the input data. This is\nthe reverse opration of AddPadding, and uses the same arguments and conventions\nfor input and output data format.\n)DOC\")\n    .Arg(\"padding_width\", \"Outer-size of padding to remove around each range.\")\n    .Arg(\n        \"end_padding_width\",\n        \"(Optional) Specifies a different end-padding width.\")\n    .Input(0, \"data_in\", \"T<N, D1..., Dn> Input data\")\n    .Input(\n        1,\n        \"lengths\",\n        \"(i64) Num of elements in each range. sum(lengths) = N. \"\n        \"If not provided, considers all data as a single segment.\")\n    .Output(0, \"data_out\", \"(T<N - 2*padding_width, D1..., Dn>) Unpadded data.\")\n    .Output(\n        1,\n        \"lengths_out\",\n        \"(i64, optional) Lengths for each unpadded range.\");\n\nOPERATOR_SCHEMA(GatherPadding)\n    .NumInputs(2)\n    .NumOutputs(1, 2)\n    .SetDoc(R\"DOC(\nGather the sum of start and end paddings in a padded input sequence. Used in\norder to compute the gradients of AddPadding w.r.t the padding tensors.\n)DOC\")\n    .Arg(\"padding_width\", \"Outer-size of padding present around each range.\")\n    .Arg(\n        \"end_padding_width\",\n        \"(Optional) Specifies a different end-padding width.\")\n    .Input(0, \"data_in\", \"T<N, D1..., Dn> Padded input data\")\n    .Input(\n        1,\n        \"lengths\",\n        \"(i64) Num of elements in each range. sum(lengths) = N. \"\n        \"If not provided, considers all data as a single segment.\")\n    .Output(\n        0,\n        \"padding_sum\",\n        \"Sum of all start paddings, or of all \"\n        \"paddings if end_padding_sum is not provided.\")\n    .Output(\n        1,\n        \"end_padding_sum\",\n        \"T<D1..., Dn> Sum of all end paddings, if provided.\");\n\nOPERATOR_SCHEMA(PadEmptySamples)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1, INT_MAX)\n    .SetDoc(R\"DOC(\nPad empty field given lengths and index features,\n\nInput(0) is a blob pointing to the lengths of samples in one batch,\n[Input(1),... Input(num_fields)] a list of tensors containing the data for\neach field of the features.\n\nPadEmptySamples is thread safe.\n)DOC\")\n    .Input(0, \"lengths\", \"A blob containing a pointer to the lengths.\")\n    .Output(\n        0,\n        \"out_lengths\",\n        \"Tensor containing lengths with empty sample padded.\");\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/sequence_ops.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cub/cub.cuh>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/sequence_ops.h\"\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\nnamespace {\ntemplate <typename T>\n__global__ void AddPaddingKernel(\n    const T* in,\n    int block_size,\n    int lengths_size,\n    int outer_size,\n    const int32_t* lengths_prefix_sum,\n    const T* padding_start_ptr,\n    int start_padding_width_blocks,\n    const T* padding_end_ptr,\n    int end_padding_width_blocks,\n    T* out,\n    int32_t* lengths_out) {\n  int element_idx = blockIdx.x;\n  int prior_padding =\n      element_idx * (start_padding_width_blocks + end_padding_width_blocks);\n  int out_start_idx = element_idx == 0\n      ? 0\n      : lengths_prefix_sum[element_idx - 1] + prior_padding;\n  int len_blocks;\n  int in_start_idx;\n  if (lengths_prefix_sum) {\n    len_blocks = lengths_prefix_sum[element_idx] -\n        (element_idx == 0 ? 0 : lengths_prefix_sum[element_idx - 1]);\n    in_start_idx = lengths_prefix_sum[element_idx] - len_blocks;\n  } else {\n    // Only one element, use the outer size\n    CUDA_KERNEL_ASSERT(lengths_size == 1);\n    len_blocks = outer_size;\n    in_start_idx = 0;\n  }\n\n  out_start_idx *= block_size;\n  in_start_idx *= block_size;\n\n  int len = len_blocks * block_size;\n  int start_padding_width = start_padding_width_blocks * block_size;\n  int end_padding_width = end_padding_width_blocks * block_size;\n\n  // start pad\n  T* out_ptr = out + out_start_idx;\n  for (int i = threadIdx.x; i < start_padding_width; i += blockDim.x) {\n    T fill = padding_start_ptr ? padding_start_ptr[i % block_size] : T(0);\n    out_ptr[i] = fill;\n  }\n\n  // payload\n  for (int i = threadIdx.x; i < len; i += blockDim.x) {\n    out_ptr[i + start_padding_width] = in[in_start_idx + i];\n  }\n\n  // end pad\n  for (int i = threadIdx.x; i < end_padding_width; i += blockDim.x) {\n    T fill = padding_end_ptr ? padding_end_ptr[i % block_size] : T(0);\n    out_ptr[i + start_padding_width + len] = fill;\n  }\n\n  // update the lengths\n  if (threadIdx.x == 0 && lengths_out != nullptr) {\n    lengths_out[element_idx] =\n        len_blocks + start_padding_width_blocks + end_padding_width_blocks;\n  }\n}\n\ntemplate <typename T>\n__global__ void RemovePaddingKernel(\n    const T* in,\n    int block_size,\n    int lengths_size,\n    int outer_size,\n    const int32_t* lengths_prefix_sum,\n    int start_padding_width_blocks,\n    int end_padding_width_blocks,\n    T* out,\n    int32_t* lengths_out) {\n  int element_idx = blockIdx.x;\n  int prior_padding =\n      element_idx * (start_padding_width_blocks + end_padding_width_blocks);\n  int out_start_idx = element_idx == 0\n      ? 0\n      : lengths_prefix_sum[element_idx - 1] - prior_padding;\n  int len_blocks;\n  int in_start_idx;\n  if (lengths_prefix_sum) {\n    len_blocks = lengths_prefix_sum[element_idx] -\n        (element_idx == 0 ? 0 : lengths_prefix_sum[element_idx - 1]);\n    in_start_idx = lengths_prefix_sum[element_idx] - len_blocks;\n  } else {\n    // Only one element, use the outer size\n    CUDA_KERNEL_ASSERT(lengths_size == 1);\n    len_blocks = outer_size;\n    in_start_idx = 0;\n  }\n\n  out_start_idx *= block_size;\n  in_start_idx *= block_size;\n\n  int len = len_blocks * block_size;\n  int start_padding_width = start_padding_width_blocks * block_size;\n\n  // payload\n  T* out_ptr = out + out_start_idx;\n  for (int i = threadIdx.x; i < len; i += blockDim.x) {\n    out_ptr[in_start_idx + i] = in[i + start_padding_width];\n  }\n\n  // update the lengths\n  if (threadIdx.x == 0 && lengths_out != nullptr) {\n    lengths_out[element_idx] =\n        len_blocks - (start_padding_width_blocks + end_padding_width_blocks);\n  }\n}\n\ntemplate <bool Inclusive = true>\nvoid lengths_prefix_sum(\n    const int32_t* lengths,\n    int32_t num_items,\n    Tensor<CUDAContext>* prefix_buffer,\n    Tensor<CUDAContext>* prefix_sum,\n    CUDAContext* context) {\n  // Retrieve buffer size\n  size_t temp_storage_bytes = 0;\n  prefix_sum->Resize(num_items);\n  if (Inclusive) {\n    cub::DeviceScan::InclusiveSum(\n        NULL,\n        temp_storage_bytes,\n        lengths,\n        prefix_sum->mutable_data<int32_t>(),\n        num_items,\n        context->cuda_stream());\n  } else {\n    cub::DeviceScan::ExclusiveSum(\n        NULL,\n        temp_storage_bytes,\n        lengths,\n        prefix_sum->mutable_data<int32_t>(),\n        num_items,\n        context->cuda_stream());\n  }\n\n  // Allocate temporary storage\n  auto buffer_size = (temp_storage_bytes + sizeof(int32_t)) / sizeof(int32_t);\n  prefix_buffer->Resize(buffer_size);\n  void* d_temp_storage =\n      static_cast<void*>(prefix_buffer->mutable_data<int32_t>());\n\n  if (Inclusive) {\n    cub::DeviceScan::InclusiveSum(\n        d_temp_storage,\n        temp_storage_bytes,\n        lengths,\n        prefix_sum->mutable_data<int32_t>(),\n        num_items,\n        context->cuda_stream());\n  } else {\n    cub::DeviceScan::ExclusiveSum(\n        d_temp_storage,\n        temp_storage_bytes,\n        lengths,\n        prefix_sum->mutable_data<int32_t>(),\n        num_items,\n        context->cuda_stream());\n  }\n}\n} // namespace\n\ntemplate <>\ntemplate <typename T>\nbool AddPaddingOp<CUDAContext>::MakePadding(\n    const T* in_ptr,\n    T* out_ptr,\n    const int32_t* lengths_ptr,\n    int32_t lengths_size,\n    int32_t outer_size,\n    const T* padding_start_ptr,\n    const T* padding_end_ptr,\n    int64_t block_size) {\n  // Step 1: compute prefix sum over the lengths -- unless\n  // there were no lengths given, i.e there is only one segment\n  const int32_t* lengths_prefix_sum_ptr = nullptr;\n  if (lengths_ptr != nullptr) {\n    lengths_prefix_sum(\n        lengths_ptr,\n        lengths_size,\n        &lengths_prefix_sum_buffer_,\n        &lengths_prefix_sum_,\n        &context_);\n    lengths_prefix_sum_ptr = lengths_prefix_sum_.data<int32_t>();\n  }\n\n  int32_t* lengths_out_ptr = nullptr;\n  if (OutputSize() > 1) {\n    auto* lengths_out = Output(1);\n    lengths_out->Resize(lengths_size);\n    lengths_out_ptr = lengths_out->mutable_data<int32_t>();\n  }\n\n  if (lengths_size == 0) {\n    return true;\n  }\n\n  // Compute the padding using the accumulated lengths\n  AddPaddingKernel<T>\n      <<<lengths_size, CAFFE_CUDA_NUM_THREADS, 0, context_.cuda_stream()>>>(\n          in_ptr,\n          block_size,\n          lengths_size,\n          outer_size,\n          lengths_prefix_sum_ptr,\n          padding_start_ptr,\n          startPaddingWidth_,\n          padding_end_ptr,\n          endPaddingWidth_,\n          out_ptr,\n          lengths_out_ptr);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(AddPadding, AddPaddingOp<CUDAContext>);\n\ntemplate <>\ntemplate <typename T>\nbool RemovePaddingOp<CUDAContext>::DoRunWithType() {\n  const auto& in = Input(0);\n  CAFFE_ENFORCE_GE(in.ndim(), 1);\n  const int32_t outer_size = in.dims()[0];\n  const auto block_size = std::accumulate(\n      in.dims().begin() + 1, in.dims().end(), 1, std::multiplies<TIndex>());\n\n  // if no lengths is provided, assume it is a single full-span entry\n  const int32_t* lengths_ptr = nullptr;\n  int32_t lengths_size = 1;\n  if (InputSize() > 1) {\n    const auto& lengths = Input(1);\n    lengths_ptr = lengths.data<int32_t>();\n    lengths_size = lengths.size();\n  }\n\n  auto* out = Output(0);\n  {\n    auto out_dims = in.dims();\n    out_dims[0] -= (startPaddingWidth_ + endPaddingWidth_) * lengths_size;\n    out->Resize(std::move(out_dims));\n  }\n  const auto* in_ptr = in.template data<T>();\n  auto* out_ptr = out->template mutable_data<T>();\n\n  // Step 1: compute prefix sum over the (padded) lengths -- unless\n  // there were no lengths given, i.e there is only one segment\n  const int32_t* lengths_prefix_sum_ptr = nullptr;\n  if (lengths_ptr != nullptr) {\n    lengths_prefix_sum(\n        lengths_ptr,\n        lengths_size,\n        &lengths_prefix_sum_buffer_,\n        &lengths_prefix_sum_,\n        &context_);\n    lengths_prefix_sum_ptr = lengths_prefix_sum_.data<int32_t>();\n  }\n\n  int32_t* lengths_out_ptr = nullptr;\n  if (OutputSize() > 1) {\n    auto* lengths_out = Output(1);\n    lengths_out->Resize(lengths_size);\n    lengths_out_ptr = lengths_out->mutable_data<int32_t>();\n  }\n\n  if (lengths_size == 0) {\n    return true;\n  }\n\n  // Compute the padding using the accumulated lengths\n  RemovePaddingKernel<T>\n      <<<lengths_size, CAFFE_CUDA_NUM_THREADS, 0, context_.cuda_stream()>>>(\n          in_ptr,\n          block_size,\n          lengths_size,\n          outer_size,\n          lengths_prefix_sum_ptr,\n          startPaddingWidth_,\n          endPaddingWidth_,\n          out_ptr,\n          lengths_out_ptr);\n  return true;\n}\n\ntemplate <typename T>\n__global__ void gather_padding_kernel(\n    const int K,\n    const int N,\n    const int Y0Width,\n    const int Y1Width,\n    const T* X,\n    const int* I,\n    const int* L,\n    T* Y0,\n    T* Y1) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage y0_tmp;\n  __shared__ typename BlockReduce::TempStorage y1_tmp;\n  for (int i = blockIdx.x; i < N; i += gridDim.x) {\n    T sum_1 = T(0);\n    T sum_2 = T(0);\n    for (int j = threadIdx.x; j < K * Y0Width; j += blockDim.x) {\n      const int j1 = j / Y0Width;\n      const int j2 = j % Y0Width;\n      const int idx1 = N * (L[j1] + j2);\n      sum_1 += X[idx1 + i];\n    }\n    for (int j = threadIdx.x; j < K * Y1Width; j += blockDim.x) {\n      const int j1 = j / Y1Width;\n      const int j2 = j % Y1Width;\n      const int idx1 = N * L[j1];\n      const int idx2 = idx1 + N * (I[j1] - Y1Width + j2);\n      sum_2 += X[idx2 + i];\n    }\n    sum_1 = BlockReduce(y0_tmp).Reduce(sum_1, cub::Sum());\n    sum_2 = BlockReduce(y1_tmp).Reduce(sum_2, cub::Sum());\n    if (threadIdx.x == 0) {\n      Y0[i] = sum_1;\n      Y0 != Y1 ? Y1[i] = sum_2 : Y0[i] = sum_1 + sum_2;\n    }\n    __syncthreads();\n  }\n}\n\ntemplate <>\ntemplate <typename T>\nvoid GatherPaddingOp<CUDAContext>::GatherPadding(\n    const int outer_size,\n    const int lengths_size,\n    const int block_size,\n    const int pad_width,\n    const T* in_ptr,\n    const int* lengths_ptr,\n    T* padding_start_ptr,\n    T* padding_end_ptr) {\n  if (lengths_size > 0) {\n    lengths_prefix_sum<false>(\n        lengths_ptr,\n        lengths_size,\n        &lengths_prefix_sum_buffer_,\n        &lengths_prefix_sum_,\n        &context_);\n    gather_padding_kernel<T>\n        <<<min(block_size, CAFFE_MAXIMUM_NUM_BLOCKS),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           context_.cuda_stream()>>>(\n            lengths_size,\n            block_size,\n            startPaddingWidth_,\n            endPaddingWidth_,\n            in_ptr,\n            lengths_ptr,\n            lengths_prefix_sum_.template data<int>(),\n            padding_start_ptr,\n            padding_end_ptr);\n  }\n}\nREGISTER_CUDA_OPERATOR(RemovePadding, RemovePaddingOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(GatherPadding, GatherPaddingOp<CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/sequence_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SEQUENCE_OPS_H_\n#define CAFFE2_OPERATORS_SEQUENCE_OPS_H_\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass GatherPaddingOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  GatherPaddingOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        startPaddingWidth_(\n            OperatorBase::GetSingleArgument<int>(\"padding_width\", 1)),\n        endPaddingWidth_(\n            OperatorBase::GetSingleArgument<int>(\"end_padding_width\", -1)) {\n    CAFFE_ENFORCE_GE(startPaddingWidth_, 0);\n    if (endPaddingWidth_ < 0) {\n      endPaddingWidth_ = startPaddingWidth_;\n    }\n  }\n\n  bool RunOnDevice() override {\n    if (startPaddingWidth_ == 0 && endPaddingWidth_ == 0) {\n      Output(0)->Resize(std::vector<TIndex>(0));\n      Output(0)->template mutable_data<TIndex>();\n      if (OutputSize() == 2) {\n        Output(1)->Resize(std::vector<TIndex>(0));\n        Output(1)->template mutable_data<TIndex>();\n      }\n      return true;\n    }\n    return DispatchHelper<TensorTypes<float, double, int, int64_t, bool>>::call(\n        this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    const auto& in = Input(0);\n    CAFFE_ENFORCE_GE(in.ndim(), 1);\n    const int32_t outer_size = in.dims()[0];\n    const auto block_size = in.size_from_dim(1);\n    const auto pad_width = startPaddingWidth_ + endPaddingWidth_;\n\n    // if no lengths is provided, assume it is a single full-span entry\n    const int32_t* lengths_ptr = &outer_size;\n    int64_t lengths_size = 1;\n    if (InputSize() > 1) {\n      const auto& lengths = Input(1);\n      lengths_ptr = lengths.template data<int32_t>();\n      lengths_size = lengths.size();\n    }\n    std::vector<TIndex> padShape(in.dims().begin() + 1, in.dims().end());\n    // output will contain accumulator over paddings\n    Output(0)->Resize(padShape);\n    T* padding_start_ptr = Output(0)->template mutable_data<T>();\n    math::Set<T, Context>(block_size, 0.0, padding_start_ptr, &context_);\n\n    // if no end_padding is provided, assume it's the same as start_padding\n    T* padding_end_ptr = padding_start_ptr;\n    if (OutputSize() == 2) {\n      Output(1)->Resize(padShape);\n      padding_end_ptr = Output(1)->template mutable_data<T>();\n      math::Set<T, Context>(block_size, 0.0, padding_end_ptr, &context_);\n    }\n    GatherPadding<T>(\n        outer_size,\n        lengths_size,\n        block_size,\n        pad_width,\n        in.template data<T>(),\n        lengths_ptr,\n        padding_start_ptr,\n        padding_end_ptr);\n    return true;\n  }\n\n private:\n  template <typename T>\n  void GatherPadding(\n      const int outer_size,\n      const int lengths_size,\n      const int block_size,\n      const int pad_width,\n      const T* in_ptr,\n      const int* lengths_ptr,\n      T* padding_start_ptr,\n      T* padding_end_ptr);\n\n  int startPaddingWidth_;\n  int endPaddingWidth_;\n  // Scratch space required by the CUDA version\n  Tensor<Context> lengths_prefix_sum_buffer_;\n  Tensor<Context> lengths_prefix_sum_;\n};\n\ntemplate <class Context>\nclass RemovePaddingOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  RemovePaddingOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        startPaddingWidth_(\n            OperatorBase::GetSingleArgument<int>(\"padding_width\", 1)),\n        endPaddingWidth_(\n            OperatorBase::GetSingleArgument<int>(\"end_padding_width\", -1)) {\n    CAFFE_ENFORCE_GE(startPaddingWidth_, 0);\n    if (endPaddingWidth_ < 0) {\n      endPaddingWidth_ = startPaddingWidth_;\n    }\n  }\n\n  bool RunOnDevice() override {\n    if (startPaddingWidth_ == 0 && endPaddingWidth_ == 0) {\n      Output(0)->CopyFrom(Input(0), &context_);\n      if (OutputSize() == 2) {\n        Output(1)->CopyFrom(Input(1), &context_);\n      }\n      return true;\n    }\n    return DispatchHelper<TensorTypes<float, double, int, int64_t, bool>>::call(\n        this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType();\n\n private:\n  int startPaddingWidth_;\n  int endPaddingWidth_;\n\n  // Scratch space required by the CUDA version\n  Tensor<Context> lengths_prefix_sum_buffer_;\n  Tensor<Context> lengths_prefix_sum_;\n};\n\ntemplate <class Context>\nclass AddPaddingOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  AddPaddingOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        startPaddingWidth_(\n            OperatorBase::GetSingleArgument<int>(\"padding_width\", 1)),\n        endPaddingWidth_(\n            OperatorBase::GetSingleArgument<int>(\"end_padding_width\", -1)) {\n    CAFFE_ENFORCE_GE(startPaddingWidth_, 0);\n    if (endPaddingWidth_ < 0) {\n      endPaddingWidth_ = startPaddingWidth_;\n    }\n  }\n\n  bool RunOnDevice() override {\n    if (startPaddingWidth_ == 0 && endPaddingWidth_ == 0) {\n      Output(0)->CopyFrom(Input(0), &context_);\n      if (OutputSize() == 2) {\n        Output(1)->CopyFrom(Input(1), &context_);\n      }\n      return true;\n    }\n    return DispatchHelper<TensorTypes<float, double, int, int64_t, bool>>::call(\n        this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    const auto& in = Input(0);\n    CAFFE_ENFORCE_GE(in.ndim(), 1);\n    const int32_t outer_size = in.dims()[0];\n    const auto block_size = in.size_from_dim(1);\n\n    // if no lengths is provided, assume it is a single full-span entry\n    const int32_t* lengths_ptr = nullptr;\n    int32_t lengths_size = 1;\n    if (InputSize() > 1) {\n      const auto& lengths = Input(1);\n      lengths_ptr = lengths.template data<int32_t>();\n      lengths_size = lengths.size();\n    }\n\n    // fetch paddings\n    // input_size == 2 : pad with zeros\n    // input_size == 3 : start and end paddings are the same\n    // input_size == 4 : different start and end paddings\n    const T* padding_start_ptr = nullptr;\n    const T* padding_end_ptr = nullptr;\n    if (InputSize() >= 3) {\n      auto& padding_start = Input(2);\n      CAFFE_ENFORCE_EQ(block_size, padding_start.size());\n      padding_start_ptr = padding_start.template data<T>();\n    }\n    if (InputSize() == 4) {\n      auto& padding_end = Input(3);\n      CAFFE_ENFORCE_EQ(block_size, padding_end.size());\n      padding_end_ptr = padding_end.template data<T>();\n    } else {\n      padding_end_ptr = padding_start_ptr;\n    }\n\n    auto* out = Output(0);\n    {\n      auto out_dims = in.dims();\n      out_dims[0] += (startPaddingWidth_ + endPaddingWidth_) * lengths_size;\n      out->Resize(std::move(out_dims));\n    }\n    const auto* in_ptr = in.template data<T>();\n    auto* out_ptr = out->template mutable_data<T>();\n\n    return MakePadding<T>(\n        in_ptr,\n        out_ptr,\n        lengths_ptr,\n        lengths_size,\n        outer_size,\n        padding_start_ptr,\n        padding_end_ptr,\n        block_size);\n  }\n\n private:\n  template <typename T>\n  bool MakePadding(\n      const T* in_ptr,\n      T* out_ptr,\n      const int32_t* lengths_ptr,\n      int32_t lengths_size,\n      int32_t outer_size,\n      const T* padding_start_ptr,\n      const T* padding_end_ptr,\n      int64_t block_size);\n\n  int startPaddingWidth_;\n  int endPaddingWidth_;\n\n  // Scratch space required by the CUDA version\n  Tensor<Context> lengths_prefix_sum_buffer_;\n  Tensor<Context> lengths_prefix_sum_;\n};\n\ntemplate <class Context>\nclass PadEmptySamplesOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  PadEmptySamplesOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SEQUENCE_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/shape_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/shape_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Shape, ShapeOp<CPUContext>);\n\nOPERATOR_SCHEMA(Shape)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction([](const OperatorDef& /*def*/,\n                                const vector<TensorShape>& in) {\n      vector<TensorShape> out(1);\n      out[0].add_dims(in[0].dims().size());\n      out[0].set_data_type(TensorProto::INT32);\n      return out;\n    })\n    .SetDoc(\"Produce a 1D int64 tensor with the shape of the input tensor.\");\n\nSHOULD_NOT_DO_GRADIENT(Shape);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/shape_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\n// RecordShapeOp records the shape of the input tensor to a vector of int. You\n// mostly don't need this operator explicitly, and it is mostly used in the\n// autodiff process.\ntemplate <class Context>\nclass ShapeOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(ShapeOp);\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = OperatorBase::Output<Tensor<Context>>(0);\n    output->Resize(input.ndim());\n    TIndex* output_data = output->template mutable_data<TIndex>();\n    context_.template CopyBytes<Context, Context>(\n        input.ndim() * sizeof(TIndex), input.dims().data(), output_data);\n    return true;\n  }\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/shape_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/shape_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(Shape, ShapeOp<CUDAContext>);\n}\n"
  },
  {
    "path": "caffe2/operators/sigmoid_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nstruct SigmoidCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* /*device_context*/) {\n    ConstEigenVectorArrayMap<T> xM(x, n);\n    EigenVectorArrayMap<T>(y, n) = 1. / (1. + (-xM).exp());\n  }\n};\n\nstruct SigmoidGradientCPUFunctor {\n  template <typename T>\n  inline void Run(\n      const int n,\n      const T* y,\n      const T* dy,\n      T* dx,\n      CPUContext* /*device_context*/) {\n    ConstEigenVectorArrayMap<T> yM(y, n), dyM(dy, n);\n    EigenVectorArrayMap<T>(dx, n) = dyM * yM * (1. - yM);\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    Sigmoid, UnaryElementwiseOp<\n        TensorTypes<float>, CPUContext, SigmoidCPUFunctor>);\nREGISTER_CPU_OPERATOR(\n    SigmoidGradient,\n    BinaryElementwiseOp<\n        TensorTypes<float>,\n        CPUContext,\n        WithoutBroadcast<SigmoidGradientCPUFunctor>>);\n\n// Input: X, output: Y\nOPERATOR_SCHEMA(Sigmoid)\n  .NumInputs(1)\n  .NumOutputs(1)\n  .AllowInplace({{0, 0}})\n  .IdenticalTypeAndShape()\n  .SetDoc(R\"DOC(\nSigmoid takes one input data (Tensor<T>) and produces one output data\n(Tensor<T>) where the sigmoid function, y = 1 / (1 + exp(-x)), is applied to the\ntensor elementwise.\n)DOC\")\n  .Input(0, \"X\", \"1D input tensor\")\n  .Output(0, \"Y\", \"1D output tensor\");\n// Input: Y, dY, output: dX\nOPERATOR_SCHEMA(SigmoidGradient)\n  .NumInputs(2)\n  .NumOutputs(1)\n  .AllowInplace({{1, 0}})\n  .SetDoc(R\"DOC(\nSigmoidGradient takes both Y and dY and uses this to update dX according to the\nchain rule and derivatives of the sigmoid function.\n)DOC\");\n\nclass GetSigmoidGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SigmoidGradient\", \"\",\n        vector<string>{O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Sigmoid, GetSigmoidGradient);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/sigmoid_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cmath>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void SigmoidKernel(const int N, const T* x, T* y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    y[i] = 1. / (1. + exp(-x[i]));\n  }\n}\n\ntemplate <typename T>\n__global__ void SigmoidGradientKernel(const int N, const T* y, const T* dy,\n                              T* dx) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dx[i] = dy[i] * y[i] * (1. - y[i]);\n  }\n}\n\nstruct SigmoidCUDAFunctor {\n  template <typename T>\n  inline void operator()(const int n, const T* x,\n                         T* y, CUDAContext* device_context) {\n    SigmoidKernel<T><<<CAFFE_GET_BLOCKS(n), CAFFE_CUDA_NUM_THREADS,\n                    0, device_context->cuda_stream()>>>(n, x, y);\n    return;\n  }\n};\n\nstruct SigmoidGradientCUDAFunctor {\n  template <typename T>\n  inline void Run(const int n, const T* y, const T* dy,\n                  T* dx, CUDAContext* device_context) {\n    SigmoidGradientKernel<T><<<CAFFE_GET_BLOCKS(n), CAFFE_CUDA_NUM_THREADS,\n                            0, device_context->cuda_stream()>>>(n, y, dy, dx);\n    return;\n  }\n};\n\nREGISTER_CUDA_OPERATOR(\n    Sigmoid,\n    UnaryElementwiseOp<TensorTypes<float>, CUDAContext, SigmoidCUDAFunctor>);\nREGISTER_CUDA_OPERATOR(\n    SigmoidGradient, BinaryElementwiseOp<\n        TensorTypes<float>, CUDAContext,\n        WithoutBroadcast<SigmoidGradientCUDAFunctor>>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/sin_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nstruct SinCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* device_context) {\n    math::Sin<T, CPUContext>(n, x, y, device_context);\n  }\n};\n\nstruct SinGradientCPUFunctor {\n  template <typename T>\n  inline void\n  Run(const int n, const T* x, const T* dy, T* dx, CPUContext* /* unused */) {\n    ConstEigenVectorArrayMap<T> dyM(dy, n);\n    ConstEigenVectorArrayMap<T> xM(x, n);\n    EigenVectorMap<T>(dx, n) = dyM * cos(xM);\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    Sin,\n    UnaryElementwiseOp<TensorTypes<float>, CPUContext, SinCPUFunctor>);\nREGISTER_CPU_OPERATOR(\n    SinGradient,\n    BinaryElementwiseOp<\n        TensorTypes<float>,\n        CPUContext,\n        WithoutBroadcast<SinGradientCPUFunctor>>);\n\nOPERATOR_SCHEMA(Sin)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nCalculates the sine of the given input tensor, element-wise.\n)DOC\")\n    .Input(0, \"input\", \"Input tensor\")\n    .Output(0, \"output\", \"The sine of the input tensor computed element-wise\");\n\nOPERATOR_SCHEMA(SinGradient).NumInputs(2).NumOutputs(1).IdenticalTypeAndShape();\n\nclass GetSinGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SinGradient\",\n        \"\",\n        std::vector<string>{I(0), GO(0)},\n        std::vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Sin, GetSinGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/sin_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cmath>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void SinKernel(const int N, const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = sin(X[i]);\n  }\n}\n\ntemplate <typename T>\n__global__ void SinGradientKernel(const int N, const T* X, const T* dY, T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dX[i] = dY[i] * cos(X[i]);\n  }\n}\n\nstruct SinCUDAFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CUDAContext* device_context) {\n    SinKernel<T>\n        <<<CAFFE_GET_BLOCKS(n),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           device_context->cuda_stream()>>>(n, x, y);\n    return;\n  }\n};\n\nstruct SinGradientCUDAFunctor {\n  template <typename T>\n  inline void Run(\n      const int n,\n      const T* x,\n      const T* dy,\n      T* dx,\n      CUDAContext* device_context) {\n    SinGradientKernel<T>\n        <<<CAFFE_GET_BLOCKS(n),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           device_context->cuda_stream()>>>(n, x, dy, dx);\n    return;\n  }\n};\n\nREGISTER_CUDA_OPERATOR(\n    Sin,\n    UnaryElementwiseOp<TensorTypes<float>, CUDAContext, SinCUDAFunctor>);\nREGISTER_CUDA_OPERATOR(\n    SinGradient,\n    BinaryElementwiseOp<\n        TensorTypes<float>,\n        CUDAContext,\n        WithoutBroadcast<SinGradientCUDAFunctor>>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/sinusoid_position_encoding_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/sinusoid_position_encoding_op.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(\n    SinusoidPositionEncoding,\n    SinusoidPositionEncodingOp<CPUContext>);\n\nOPERATOR_SCHEMA(SinusoidPositionEncoding)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nCalculates a sinusoid position encoding tensor as described\nin https://arxiv.org/abs/1706.03762. Takes a 2-D tensor\n(of size M x K) of positions as input, the embedding size\nas an argument, and outputs a position encoding tensor of\nsize (M x K x embedding_size). Here M is typically the max\nsequence length and K is typically the batch size.\nThe input tensor must satisfy input[m, 0] == input[m, k] for all k.\n\nEncoded as amplitude * SIN(pos/alpha^(i/embedding_size)) if i is even,\nelse amplitude * COS(pos/alpha^(i/embedding_size)). Here, pos is the position,\nalpha and amplitude are tuning parameters, i is the current dimension for\nthe embedding, and embedding_size is the number of total dimensions in\nthe embedding.\n)DOC\")\n    .Arg(\n        \"embedding_size\",\n        \"Desired embedding size/number of dimensions -- defaults to 100\")\n    .Arg(\"alpha\", \"Sinusoid tuning parameter -- defaults to 10000\")\n    .Arg(\"amplitude\", \"Amplitude of Sin/Cos output\")\n    .Input(0, \"positions\", \"2-D tensor of positions to be encoded\")\n    .Output(0, \"output\", \"3-D tensor representing the positional encoding\");\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/sinusoid_position_encoding_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SINUSOID_POSITION_ENCODING_OP_H_\n#define CAFFE2_OPERATORS_SINUSOID_POSITION_ENCODING_OP_H_\n\n#ifdef _MSC_VER\n#define _USE_MATH_DEFINES\n#endif // _MSC_VER\n#include <cmath>\n\n#include \"caffe2/core/operator.h\"\n\n#include \"Eigen/Core\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass SinusoidPositionEncodingOp : public Operator<Context> {\n public:\n  SinusoidPositionEncodingOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        embedding_size_(OperatorBase::template GetSingleArgument<int>(\n            \"embedding_size\",\n            100)),\n        alpha_(OperatorBase::template GetSingleArgument<float>(\"alpha\", 10000)),\n        amplitude_(\n            OperatorBase::template GetSingleArgument<float>(\"amplitude\", 1)) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, OperatorBase::Input<TensorCPU>(0));\n  }\n\n  template <typename Index>\n  bool DoRunWithType() {\n    auto& positions = Input(0);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_EQ(positions.ndim(), 2, \"POSITIONS should be a 2-D tensor\");\n\n    auto shape = positions.dims();\n    shape.push_back(embedding_size_);\n    output->Resize(shape);\n\n    int M = shape[0];\n    int K = shape[1];\n    const Index* idxs = positions.template data<Index>();\n    float* out = output->template mutable_data<float>();\n\n    float log_alpha = std::log(alpha_);\n    float max_alpha_pow =\n        ((float)embedding_size_ - 1.0f) / (float)embedding_size_;\n\n    for (int i = 0; i < M; ++i) {\n      float pos = (float)idxs[i * K];\n\n      // Compute the embedding for position i, example 0 first\n      float* row = &out[i * K * embedding_size_];\n      Eigen::Map<Eigen::VectorXf> row_map(row, embedding_size_, 1);\n      auto row_array = row_map.array();\n\n      float log_pos = std::log(pos);\n      row_array.setLinSpaced(\n          embedding_size_, log_pos, log_pos - log_alpha * max_alpha_pow);\n      row_array = row_array.exp().eval();\n      // row_array[k] == pos / alpha^(k / embedding_size)\n\n      // Phase shift so that alternating elements are cosines\n      for (int k = 1; k < embedding_size_; k += 2) {\n        row[k] += (float)M_PI_2;\n      }\n      row_array = amplitude_ * row_array.sin().eval();\n\n      // Copy the embedding to position i in the other examples\n      for (int j = 1; j < K; ++j) {\n        int base = i * K * embedding_size_;\n        std::copy(\n            &out[base],\n            &out[base + embedding_size_],\n            &out[base + j * embedding_size_]);\n      }\n    }\n    return true;\n  }\n\n protected:\n  int embedding_size_;\n  float alpha_;\n  float amplitude_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SINUSOID_POSITION_ENCODING_OP_H_\n"
  },
  {
    "path": "caffe2/operators/slice_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/slice_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Slice, SliceOp<int, CPUContext>);\nREGISTER_CPU_OPERATOR(SliceGradient, SliceGradientOp<int, CPUContext>);\n\nOPERATOR_SCHEMA(Slice)\n    .NumInputs(1, 3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nProduces a slice of the input tensor. Currently, only slicing in a single\ndimension is supported.\nSlices are passed as 2 1D vectors or as two keyword argument lists with starting\nand end indices for each dimension of the input `data` tensor. If a negative\nvalue is passed for any of the start or end indices, it represents the number of\nelements before the end of that dimension. End indices are non-inclusive unless\nnegative (end index -1 means up to and including the last element).\n\n\nExample:\n\n  data = [\n      [1, 2, 3, 4],\n      [5, 6, 7, 8],\n  ]\n  starts = [0, 1]\n  ends = [-1, 3]\n\n  result = [\n      [2, 3],\n      [6, 7],\n  ]\n)DOC\")\n    .Input(0, \"data\", \"Tensor of data to extract slices from.\")\n    .Input(1, \"starts\", \"1D tensor: start-indices for each dimension of data.\")\n    .Input(2, \"ends\", \"1D tensor: end-indices for each dimension of data.\")\n    .Arg(\"starts\", \"List of starting indices\")\n    .Arg(\"ends\", \"List of ending indices\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      if (in.size() > 1) {\n        // Cannot compute shape inference when the splits are defined\n        // in data.\n        return vector<TensorShape>();\n      }\n      auto const& data = in[0];\n\n      ArgumentHelper helper(def);\n      auto starts = helper.GetRepeatedArgument<int>(\"starts\", vector<int>());\n      auto ends = helper.GetRepeatedArgument<int>(\"ends\", vector<int>());\n      vector<int> dst_sizes(data.dims_size());\n\n      for (int i = 0; i < data.dims_size(); ++i) {\n        if (i >= starts.size()) {\n          continue;\n        }\n        if (data.dims_size() > 0) {\n          auto start = starts[i];\n          auto end = ends[i];\n          if (start < 0) {\n            start = data.dims(i) + 1 + start;\n          }\n          if (end < 0) {\n            end = data.dims(i) + 1 + end;\n          }\n          dst_sizes[i] = end - start;\n        } else {\n          dst_sizes[i] = 0;\n        }\n      }\n      return vector<TensorShape>{\n          CreateTensorShape(dst_sizes, data.data_type())};\n    })\n    .Output(0, \"output\", \"Sliced data tensor.\");\n\nOPERATOR_SCHEMA(SliceGradient);\n\nnamespace {\nstruct GetSliceGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    if (def_.input_size() > 1) {\n      return vector<OperatorDef>{CreateOperatorDef(\n          \"SliceGradient\",\n          \"\",\n          std::vector<string>{I(0), I(1), I(2), GO(0)},\n          std::vector<string>{GI(0)})};\n    } else {\n      return vector<OperatorDef>{CreateOperatorDef(\n          \"SliceGradient\",\n          \"\",\n          std::vector<string>{I(0), GO(0)},\n          std::vector<string>{GI(0)})};\n    }\n  }\n};\n}\nREGISTER_GRADIENT(Slice, GetSliceGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/slice_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/slice_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\nnamespace {\n__global__ void SliceCopyKernel(\n    char* src_offset_bytes,\n    int src_block_size_bytes,\n    char* dst_offset_bytes,\n    int dst_block_size_bytes,\n    int copy_size,\n    int itemsize,\n    int num_blocks) {\n  if ((copy_size % sizeof(int) == 0) &&\n      (src_block_size_bytes % sizeof(int) == 0) &&\n      (dst_block_size_bytes % sizeof(int) == 0)) {\n    int* src = (int*)src_offset_bytes;\n    int* dst = (int*)dst_offset_bytes;\n\n    int src_block_size = src_block_size_bytes / sizeof(int);\n    int dst_block_size = dst_block_size_bytes / sizeof(int);\n\n    int copyChunks = copy_size / sizeof(int);\n\n    CUDA_1D_KERNEL_LOOP(index, num_blocks * copyChunks) {\n      int chunk = index % copyChunks;\n      int block = index / copyChunks;\n\n      dst[block * dst_block_size + chunk] = src[block * src_block_size + chunk];\n    }\n  } else {\n    char* src = (char*)src_offset_bytes;\n    char* dst = (char*)dst_offset_bytes;\n\n    int src_block_size = src_block_size_bytes / sizeof(char);\n    int dst_block_size = dst_block_size_bytes / sizeof(char);\n\n    int copyChunks = copy_size / sizeof(char);\n\n    CUDA_1D_KERNEL_LOOP(index, num_blocks * copyChunks) {\n      int chunk = index % copyChunks;\n      int block = index / copyChunks;\n\n      dst[block * dst_block_size + chunk] = src[block * src_block_size + chunk];\n    }\n  }\n}\n\ntemplate <class SIndex, class Context>\nbool SliceImplGpu(\n    Tensor<Context>* output,\n    const Tensor<Context>& data,\n    const TensorCPU& starts,\n    const TensorCPU& ends,\n    Context* context,\n    Tensor<Context>* gdata = nullptr,\n    const Tensor<Context>* go = nullptr) {\n  bool backward = output == nullptr;\n\n  auto* starts_data = starts.template data<SIndex>();\n  auto* ends_data = ends.template data<SIndex>();\n\n  CAFFE_ENFORCE_EQ(starts.ndim(), 1);\n  CAFFE_ENFORCE_EQ(ends.ndim(), 1);\n  CAFFE_ENFORCE_GE(data.ndim(), starts.size());\n  CAFFE_ENFORCE_EQ(starts.size(), ends.size());\n\n  std::vector<int> starts_idx(data.ndim());\n  std::vector<int> ends_idx(data.ndim());\n  std::vector<int> dst_sizes(data.ndim());\n\n  for (int i = 0; i < data.ndim(); ++i) {\n    if (i >= starts.size()) {\n      starts_idx[i] = 0;\n      ends_idx[i] = data.dims()[i];\n      continue;\n    }\n    if (data.dims()[i] > 0) {\n      auto start = starts_data[i];\n      auto end = ends_data[i];\n      if (start < 0) {\n        start = data.dims()[i] + 1 + start;\n      }\n      if (end < 0) {\n        end = data.dims()[i] + 1 + end;\n      }\n      if (start > data.dims()[i]) {\n        start = data.dims()[i];\n      }\n      if (end > data.dims()[i]) {\n        end = data.dims()[i];\n      }\n      CAFFE_ENFORCE_GE(start, 0);\n      CAFFE_ENFORCE_GE(end, 0);\n      CAFFE_ENFORCE_GE(end, start);\n      starts_idx[i] = start;\n      ends_idx[i] = end;\n      dst_sizes[i] = end - start;\n    } else {\n      starts_idx[i] = 0;\n      ends_idx[i] = 0;\n      dst_sizes[i] = 0;\n    }\n  }\n\n  if (data.size() <= 0) {\n    // When the input is empty, we do not need to do copy.\n    if (!backward) {\n      output->Resize(dst_sizes);\n      output->raw_mutable_data(data.meta());\n    }\n    return true;\n  }\n  // for now only supports slicing in 1 dimension\n  int dim = -1;\n  for (int i = 0; i < data.ndim(); ++i) {\n    if (starts_idx[i] > 0 || ends_idx[i] < data.dims()[i]) {\n      CAFFE_ENFORCE_EQ(\n          dim, -1, \"Currently only possible to slice in 1 dimension.\");\n      dim = i;\n    }\n  }\n  if (dim == -1) {\n    if (!backward) {\n      output->CopyFrom(data, context);\n    } else {\n      gdata->CopyFrom(*go, context);\n    }\n    return true;\n  }\n  int unit = std::accumulate(\n      data.dims().begin() + dim + 1,\n      data.dims().end(),\n      1,\n      std::multiplies<int>());\n  int num_blocks = std::accumulate(\n      data.dims().begin(),\n      data.dims().begin() + dim,\n      1,\n      std::multiplies<int>());\n  if (!backward) {\n    output->Resize(dst_sizes);\n  } else {\n    gdata->ResizeLike(data);\n  }\n\n  auto itemsize = data.meta().itemsize();\n\n  if (!backward) {\n    char* src_bytes = (char*)data.raw_data();\n    char* dst_bytes = (char*)output->raw_mutable_data(data.meta());\n\n    size_t src_nbytes = data.nbytes();\n    size_t dst_nbytes = output->nbytes();\n\n    size_t src_block_size = unit * data.dims()[dim];\n    size_t dst_block_size = unit * (ends_idx[dim] - starts_idx[dim]);\n    size_t src_offset = unit * starts_idx[dim];\n\n    if (num_blocks == 0 || dst_block_size == 0) {\n      return true;\n    }\n\n    size_t src_block_size_bytes = itemsize * src_block_size;\n    size_t dst_block_size_bytes = itemsize * dst_block_size;\n    char* src_offset_bytes = src_bytes + itemsize * src_offset;\n    char* dst_offset_bytes = dst_bytes;\n\n    SliceCopyKernel<<<\n        std::min(num_blocks, CAFFE_MAXIMUM_NUM_BLOCKS),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context->cuda_stream()>>>(\n        src_offset_bytes,\n        src_block_size_bytes,\n        dst_offset_bytes,\n        dst_block_size_bytes,\n        dst_block_size_bytes,\n        itemsize,\n        num_blocks);\n  } else {\n    char* src_bytes = (char*)go->raw_data();\n    char* dst_bytes = (char*)gdata->raw_mutable_data(go->meta());\n\n    size_t src_nbytes = go->nbytes();\n    size_t dst_nbytes = gdata->nbytes();\n\n    size_t src_block_size = unit * (ends_idx[dim] - starts_idx[dim]);\n    size_t dst_block_size = unit * data.dims()[dim];\n    size_t dst_offset = unit * starts_idx[dim];\n\n    if (num_blocks == 0 || dst_block_size == 0) {\n      return true;\n    }\n\n    size_t src_block_size_bytes = itemsize * src_block_size;\n    size_t dst_block_size_bytes = itemsize * dst_block_size;\n\n    char* src_offset_bytes = src_bytes;\n    char* dst_offset_bytes = dst_bytes + itemsize * dst_offset;\n    // Zero out gradient blob before copy since we copy in fewer items than\n    // there is space for\n    math::Set<float, CUDAContext>(\n        gdata->size(),\n        0.0f,\n        (float*)gdata->raw_mutable_data(go->meta()),\n        context);\n\n    // If output tensor is empty, just return zeroed gradient tensor\n    if (!src_bytes) {\n      return true;\n    }\n\n    SliceCopyKernel<<<\n        std::min(num_blocks, CAFFE_MAXIMUM_NUM_BLOCKS),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context->cuda_stream()>>>(\n        src_offset_bytes,\n        src_block_size_bytes,\n        dst_offset_bytes,\n        dst_block_size_bytes,\n        src_block_size_bytes,\n        itemsize,\n        num_blocks);\n  }\n\n  return true;\n}\n\n} // namespace\n\ntemplate <>\nbool SliceOp<int, CUDAContext>::RunOnDevice() {\n  auto* output = Output(0);\n  auto& data = Input(0);\n\n  if (InputSize() > 1) {\n    starts_host_.CopyFrom<CUDAContext>(Input(1));\n    ends_host_.CopyFrom<CUDAContext>(Input(2));\n  } else {\n    if (!statically_inited_) {\n      CAFFE_ENFORCE(HasArgument(\"starts\"));\n      CAFFE_ENFORCE(HasArgument(\"ends\"));\n      CAFFE_ENFORCE_EQ(starts_.size(), ends_.size());\n\n      starts_host_.Resize(starts_.size());\n      ends_host_.Resize(ends_.size());\n\n      memcpy(\n          starts_host_.mutable_data<int>(),\n          starts_.data(),\n          sizeof(int) * starts_.size());\n      memcpy(\n          ends_host_.mutable_data<int>(),\n          ends_.data(),\n          sizeof(int) * ends_.size());\n      statically_inited_ = true;\n    }\n  }\n\n  return SliceImplGpu<int, CUDAContext>(\n      output, data, starts_host_, ends_host_, &context_);\n}\n\nREGISTER_CUDA_OPERATOR(Slice, SliceOp<int, CUDAContext>);\n\ntemplate <>\nbool SliceGradientOp<int, CUDAContext>::RunOnDevice() {\n  auto* gdata = Output(0);\n  auto& data = Input(0);\n\n  if (InputSize() == 4) {\n    starts_host_.CopyFrom<CUDAContext>(Input(1));\n    ends_host_.CopyFrom<CUDAContext>(Input(2));\n\n    auto& go = Input(3);\n\n    return SliceImplGpu<int, CUDAContext>(\n        nullptr, data, starts_host_, ends_host_, &context_, gdata, &go);\n  } else {\n    if (!statically_inited_) {\n      CAFFE_ENFORCE(HasArgument(\"starts\"));\n      CAFFE_ENFORCE(HasArgument(\"ends\"));\n      CAFFE_ENFORCE_EQ(starts_.size(), ends_.size());\n\n      starts_host_.Resize(starts_.size());\n      ends_host_.Resize(ends_.size());\n\n      memcpy(\n          starts_host_.mutable_data<int>(),\n          starts_.data(),\n          sizeof(int) * starts_.size());\n      memcpy(\n          ends_host_.mutable_data<int>(),\n          ends_.data(),\n          sizeof(int) * ends_.size());\n\n      statically_inited_ = true;\n    }\n    auto& go = Input(1);\n\n    return SliceImplGpu<int, CUDAContext>(\n        nullptr, data, starts_host_, ends_host_, &context_, gdata, &go);\n  }\n}\nREGISTER_CUDA_OPERATOR(SliceGradient, SliceGradientOp<int, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/slice_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <class SIndex, class Context>\nbool SliceImpl(\n    Tensor<Context>* output,\n    const Tensor<Context>& data,\n    const Tensor<Context>& starts,\n    const Tensor<Context>& ends,\n    Context* context,\n    Tensor<Context>* gdata = nullptr,\n    const Tensor<Context>* go = nullptr) {\n  bool backward = output == nullptr;\n\n  auto* starts_data = starts.template data<SIndex>();\n  auto* ends_data = ends.template data<SIndex>();\n\n  CAFFE_ENFORCE_EQ(starts.ndim(), 1);\n  CAFFE_ENFORCE_EQ(ends.ndim(), 1);\n  CAFFE_ENFORCE_GE(data.ndim(), starts.size());\n  CAFFE_ENFORCE_EQ(starts.size(), ends.size());\n\n  std::vector<SIndex> starts_idx(data.ndim());\n  std::vector<SIndex> ends_idx(data.ndim());\n  std::vector<SIndex> dst_sizes(data.ndim());\n\n  for (int i = 0; i < data.ndim(); ++i) {\n    if (i >= starts.size()) {\n      starts_idx[i] = 0;\n      ends_idx[i] = data.dims()[i];\n      continue;\n    }\n    if (data.dims()[i] > 0) {\n      auto start = starts_data[i];\n      auto end = ends_data[i];\n      if (start < 0) {\n        start = data.dims()[i] + 1 + start;\n      }\n      if (end < 0) {\n        end = data.dims()[i] + 1 + end;\n      }\n      if (start > data.dims()[i]) {\n        start = data.dims()[i];\n      }\n      if (end > data.dims()[i]) {\n        end = data.dims()[i];\n      }\n      CAFFE_ENFORCE_GE(start, 0);\n      CAFFE_ENFORCE_GE(end, 0);\n      CAFFE_ENFORCE_GE(end, start);\n      starts_idx[i] = start;\n      ends_idx[i] = end;\n      dst_sizes[i] = end - start;\n    } else {\n      starts_idx[i] = 0;\n      ends_idx[i] = 0;\n      dst_sizes[i] = 0;\n    }\n  }\n\n  if (data.size() <= 0) {\n    // When the input is empty, we do not need to do copy.\n    if (!backward) {\n      output->Resize(dst_sizes);\n      output->raw_mutable_data(data.meta());\n    }\n    return true;\n  }\n  // for now only supports slicing in 1 dimension\n  int dim = -1;\n  for (int i = 0; i < data.ndim(); ++i) {\n    if (starts_idx[i] > 0 || ends_idx[i] < data.dims()[i]) {\n      CAFFE_ENFORCE_EQ(\n          dim, -1, \"Currently only possible to slice in 1 dimension.\");\n      dim = i;\n    }\n  }\n  if (dim == -1) {\n    if (!backward) {\n      output->CopyFrom(data, context);\n    } else {\n      gdata->CopyFrom(*go, context);\n    }\n    return true;\n  }\n  size_t unit = std::accumulate(\n      data.dims().begin() + dim + 1,\n      data.dims().end(),\n      1,\n      std::multiplies<SIndex>());\n  size_t num_blocks = std::accumulate(\n      data.dims().begin(),\n      data.dims().begin() + dim,\n      1,\n      std::multiplies<SIndex>());\n  if (!backward) {\n    output->Resize(dst_sizes);\n  } else {\n    gdata->ResizeLike(data);\n  }\n\n  size_t itemsize = data.meta().itemsize();\n\n  if (!backward) {\n    char* src_bytes = (char*)data.raw_data();\n    char* dst_bytes = (char*)output->raw_mutable_data(data.meta());\n\n    size_t src_nbytes = data.nbytes();\n    size_t dst_nbytes = output->nbytes();\n\n    size_t src_block_size = unit * data.dims()[dim];\n    size_t dst_block_size = unit * (ends_idx[dim] - starts_idx[dim]);\n    size_t src_offset = unit * starts_idx[dim];\n\n    if (num_blocks == 0 || dst_block_size == 0) {\n      return true;\n    }\n\n    size_t src_block_size_bytes = itemsize * src_block_size;\n    size_t dst_block_size_bytes = itemsize * dst_block_size;\n\n    char* src_offset_bytes = src_bytes + itemsize * src_offset;\n    char* dst_offset_bytes = dst_bytes;\n    for (int i = 0; i < num_blocks; ++i) {\n      char* local_src_offset_bytes =\n          src_offset_bytes + i * src_block_size_bytes;\n      char* local_dst_offset_bytes =\n          dst_offset_bytes + i * dst_block_size_bytes;\n      DCHECK_LE(\n          static_cast<void*>(local_src_offset_bytes + dst_block_size_bytes),\n          static_cast<void*>(src_bytes + src_nbytes));\n      DCHECK_LE(\n          static_cast<void*>(local_dst_offset_bytes + dst_block_size_bytes),\n          static_cast<void*>(dst_bytes + dst_nbytes));\n      context->template CopyItems<Context, Context>(\n          data.meta(),\n          dst_block_size,\n          (void*)local_src_offset_bytes,\n          (void*)local_dst_offset_bytes);\n    }\n  } else {\n    char* src_bytes = (char*)go->raw_data();\n    char* dst_bytes = (char*)gdata->raw_mutable_data(go->meta());\n\n    size_t src_nbytes = go->nbytes();\n    size_t dst_nbytes = gdata->nbytes();\n\n    size_t src_block_size = unit * (ends_idx[dim] - starts_idx[dim]);\n    size_t dst_block_size = unit * data.dims()[dim];\n    size_t dst_offset = unit * starts_idx[dim];\n\n    if (num_blocks == 0 || dst_block_size == 0) {\n      return true;\n    }\n\n    size_t src_block_size_bytes = itemsize * src_block_size;\n    size_t dst_block_size_bytes = itemsize * dst_block_size;\n\n    char* src_offset_bytes = src_bytes;\n    char* dst_offset_bytes = dst_bytes + itemsize * dst_offset;\n    // Zero out gradient blob before copy since we copy in fewer items than\n    // there is space for\n    math::Set<char, Context>(dst_nbytes, 0, dst_bytes, context);\n\n    // If output tensor is empty, just return zeroed gradient tensor\n    if (!src_bytes) {\n      return true;\n    }\n\n    for (int i = 0; i < num_blocks; ++i) {\n      char* local_src_offset_bytes =\n          src_offset_bytes + i * src_block_size_bytes;\n      char* local_dst_offset_bytes =\n          dst_offset_bytes + i * dst_block_size_bytes;\n      DCHECK_LE(\n          local_src_offset_bytes + src_block_size_bytes,\n          src_bytes + src_nbytes);\n      DCHECK_LE(\n          local_dst_offset_bytes + src_block_size_bytes,\n          dst_bytes + dst_nbytes);\n      context->template CopyItems<Context, Context>(\n          go->meta(),\n          src_block_size,\n          (void*)local_src_offset_bytes,\n          (void*)local_dst_offset_bytes);\n    }\n  }\n  return true;\n}\n\n} // namespace\n\ntemplate <class SIndex, class Context>\nclass SliceOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SliceOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        starts_(OperatorBase::GetRepeatedArgument<SIndex>(\"starts\")),\n        ends_(OperatorBase::GetRepeatedArgument<SIndex>(\"ends\")),\n        statically_inited_(false) {}\n\n  bool RunOnDevice() override {\n    auto* output = Output(0);\n    auto& data = Input(0);\n\n    if (InputSize() > 1) {\n      starts_host_.template CopyFrom<Context>(Input(1));\n      ends_host_.template CopyFrom<Context>(Input(2));\n    } else {\n      if (!statically_inited_) {\n        CAFFE_ENFORCE(HasArgument(\"starts\"));\n        CAFFE_ENFORCE(HasArgument(\"ends\"));\n        CAFFE_ENFORCE_EQ(starts_.size(), ends_.size());\n\n        starts_host_.Resize(starts_.size());\n        ends_host_.Resize(ends_.size());\n\n        memcpy(\n            starts_host_.template mutable_data<SIndex>(),\n            starts_.data(),\n            sizeof(SIndex) * starts_.size());\n        memcpy(\n            ends_host_.template mutable_data<SIndex>(),\n            ends_.data(),\n            sizeof(SIndex) * ends_.size());\n        statically_inited_ = true;\n      }\n    }\n\n    return SliceImpl<SIndex, Context>(\n        output, data, starts_host_, ends_host_, &context_);\n  }\n\n  DISABLE_COPY_AND_ASSIGN(SliceOp);\n\n private:\n  std::vector<SIndex> starts_;\n  std::vector<SIndex> ends_;\n  bool statically_inited_;\n  TensorCPU starts_host_;\n  TensorCPU ends_host_;\n};\n\ntemplate <class SIndex, class Context>\nclass SliceGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SliceGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        starts_(OperatorBase::GetRepeatedArgument<SIndex>(\"starts\")),\n        ends_(OperatorBase::GetRepeatedArgument<SIndex>(\"ends\")),\n        statically_inited_(false) {}\n\n  bool RunOnDevice() override {\n    auto* gdata = Output(0);\n    auto& data = Input(0);\n\n    if (InputSize() == 4) {\n      starts_host_.template CopyFrom<Context>(Input(1));\n      ends_host_.template CopyFrom<Context>(Input(2));\n\n      auto& go = Input(3);\n\n      return SliceImpl<SIndex, Context>(\n          nullptr, data, starts_host_, ends_host_, &context_, gdata, &go);\n    } else {\n      if (!statically_inited_) {\n        CAFFE_ENFORCE(HasArgument(\"starts\"));\n        CAFFE_ENFORCE(HasArgument(\"ends\"));\n        CAFFE_ENFORCE_EQ(starts_.size(), ends_.size());\n\n        starts_host_.Resize(starts_.size());\n        ends_host_.Resize(ends_.size());\n\n        memcpy(\n            starts_host_.template mutable_data<SIndex>(),\n            starts_.data(),\n            sizeof(SIndex) * starts_.size());\n        memcpy(\n            ends_host_.template mutable_data<SIndex>(),\n            ends_.data(),\n            sizeof(SIndex) * ends_.size());\n\n        statically_inited_ = true;\n      }\n      auto& go = Input(1);\n\n      return SliceImpl<SIndex, Context>(\n          nullptr, data, starts_host_, ends_host_, &context_, gdata, &go);\n    }\n  }\n\n  DISABLE_COPY_AND_ASSIGN(SliceGradientOp);\n\n private:\n  std::vector<SIndex> starts_;\n  std::vector<SIndex> ends_;\n  bool statically_inited_;\n  TensorCPU starts_host_;\n  TensorCPU ends_host_;\n};\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/softmax_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/softmax_op.h\"\n#include \"caffe2/operators/softmax_shared.h\"\n\nnamespace caffe2 {\n\n// Implementation for the CPU context.\ntemplate <>\nbool SoftmaxOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  const auto canonical_axis = X.canonical_axis_index(axis_);\n  const int N = X.size_to_dim(canonical_axis);\n  const int D = X.size_from_dim(canonical_axis);\n  Y->ResizeLike(X);\n  float* Ydata = Y->mutable_data<float>();\n  // First, get scales\n  if (scale_.size() != N) {\n    scale_.Resize(N);\n  }\n  if (rowmax_.size() != N) {\n    rowmax_.Resize(N);\n  }\n  if (sum_multiplier_.size() != D) {\n    sum_multiplier_.Resize(D);\n    math::Set<float, CPUContext>(D, 1.f, sum_multiplier_.mutable_data<float>(),\n                                 &context_);\n  }\n\n  SoftmaxCPU(\n      context_,\n      N,\n      D,\n      X.data<float>(),\n      Ydata,\n      scale_.mutable_data<float>(),\n      sum_multiplier_.data<float>(),\n      false,\n      rowmax_.mutable_data<float>());\n  return true;\n}\n\n// Implementation for the CPU context.\ntemplate <>\nbool SoftmaxGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  const auto canonical_axis = Y.canonical_axis_index(axis_);\n  const int N = Y.size_to_dim(canonical_axis);\n  const int D = Y.size_from_dim(canonical_axis);\n  // First, get scales\n  if (scale_.size() != N) {\n    scale_.Resize(N);\n  }\n  if (sum_multiplier_.size() != D) {\n    sum_multiplier_.Resize(D);\n    math::Set<float, CPUContext>(D, 1.f, sum_multiplier_.mutable_data<float>(),\n                                 &context_);\n  }\n  dX->ResizeLike(Y);\n  const float* Ydata = Y.data<float>();\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  context_.Copy<float, CPUContext, CPUContext>(Y.size(), dYdata, dXdata);\n  float* scaledata = scale_.mutable_data<float>();\n  for (int i = 0; i < N; ++i) {\n    math::Dot<float, CPUContext>(D, Ydata + i * D, dYdata + i * D,\n                                 scaledata + i, &context_);\n  }\n  math::Gemm<float, CPUContext>(CblasNoTrans, CblasNoTrans, N, D, 1, -1,\n                                scaledata, sum_multiplier_.data<float>(), 1,\n                                dXdata, &context_);\n  math::Mul<float, CPUContext>(Y.size(), dXdata, Ydata, dXdata,\n                               &context_);\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(Softmax, SoftmaxOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(SoftmaxGradient, SoftmaxGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Softmax)\n  .NumInputs(1)\n  .NumOutputs(1)\n  .IdenticalTypeAndShape()\n  .SetDoc(R\"DOC(\nThe operator computes the softmax normalized values for each layer in the batch\n of the given input. The input is a 2-D tensor (Tensor<float>) of size\n(batch_size x input_feature_dimensions). The output tensor has the same shape\nand contains the softmax normalized values of the corresponding input.\n\nX does not need to explicitly be a 2D vector; rather, it will be\ncoerced into one. For an arbitrary n-dimensional tensor\nX \\in [a_0, a_1, ..., a_{k-1}, a_k, ..., a_{n-1}] and k is\nthe axis provided, then X will be coerced into a 2-dimensional tensor with\ndimensions [a_0 * ... * a_{k-1}, a_k * ... * a_{n-1}]. For the default\ncase where axis=1, this means the X tensor will be coerced into a 2D tensor\nof dimensions [a_0, a_1 * ... * a_{n-1}], where a_0 is often the batch size.\nIn this situation, we must have a_0 = N and a_1 * ... * a_{n-1} = D.\nEach of these dimensions must be matched correctly, or else the operator\nwill throw errors.\n)DOC\")\n  .Arg(\"axis\",\n       \"(int) default to 1; describes the axis of the inputs when coerced \"\n       \"to 2D; defaults to one because the 0th axis most likely describes \"\n       \"the batch_size\")\n  .Input(0, \"input\",\n         \"The input tensor that's coerced into a 2D matrix of size (NxD) \"\n         \"as described above.\")\n  .Output(0, \"output\", \"The softmax normalized output values with the same \"\n          \"shape as input tensor.\");\n\n// Input: Y, dY. Output: dX\nOPERATOR_SCHEMA(SoftmaxGradient).NumInputs(2).NumOutputs(1);\n\nclass GetSoftmaxGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        def_.type() + \"Gradient\", \"\",\n        vector<string>{O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Softmax, GetSoftmaxGradient);\nREGISTER_GRADIENT(SoftmaxFp16, GetSoftmaxGradient);\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/softmax_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SOFTMAX_OP_H_\n#define CAFFE2_OPERATORS_SOFTMAX_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SoftmaxOp final : public Operator<Context> {\n public:\n  SoftmaxOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n      axis_(OperatorBase::GetSingleArgument<int>(\"axis\", 1)) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n protected:\n  int axis_;\n  Tensor<Context> scale_;\n  Tensor<Context> rowmax_;\n  Tensor<Context> sum_multiplier_;\n};\n\ntemplate <typename T, class Context>\nclass SoftmaxGradientOp final : public Operator<Context> {\n public:\n  SoftmaxGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        axis_(OperatorBase::GetSingleArgument<int>(\"axis\", 1)) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n protected:\n  int axis_;\n  Tensor<Context> scale_;\n  Tensor<Context> sum_multiplier_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SOFTMAX_OP_H_\n"
  },
  {
    "path": "caffe2/operators/softmax_op_cudnn.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/cudnn_wrappers.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/operators/softmax_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\nconstexpr int NUM_DESCRIPTORS = 2;\nconstexpr int GRADIENT_NUM_DESCRIPTORS = 3;\nconstexpr int BOTTOM_DESC_ID = 0;\nconstexpr int TOP_DESC_ID = 1;\nconstexpr int TOP_GRADIENT_DESC_ID = 2;\n}  // namespace\n\nclass CuDNNSoftmaxOp final : public Operator<CUDAContext> {\n public:\n  explicit CuDNNSoftmaxOp(const OperatorDef& def, Workspace* ws)\n      : Operator<CUDAContext>(def, ws),\n        cudnn_wrapper_(&context_),\n        axis_(OperatorBase::GetSingleArgument<int>(\"axis\", 1)) {\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&desc_));\n  }\n\n  ~CuDNNSoftmaxOp() {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(desc_));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& X = Input(0);\n    auto* Y = Output(0);\n    const auto canonical_axis = X.canonical_axis_index(axis_);\n    const int N = X.size_to_dim(canonical_axis);\n    const int D = X.size_from_dim(canonical_axis);\n\n    Y->ResizeLike(X);\n    if (dims_ != X.dims()) {\n      CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n          desc_,\n          GetCudnnTensorFormat(StorageOrder::NCHW),\n          cudnnTypeWrapper<T>::type,\n          N,\n          D,\n          1,\n          1));\n      dims_ = X.dims();\n    }\n    CUDNN_ENFORCE(cudnnSoftmaxForward(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        CUDNN_SOFTMAX_ACCURATE,\n        CUDNN_SOFTMAX_MODE_INSTANCE,\n        cudnnTypeWrapper<T>::kOne(),\n        desc_,\n        X.template data<T>(),\n        cudnnTypeWrapper<T>::kZero(),\n        desc_,\n        Y->template mutable_data<T>()));\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<float, float16>>::call(this, Input(0));\n  }\n\n protected:\n  CuDNNWrapper cudnn_wrapper_;\n  int axis_;\n  cudnnTensorDescriptor_t desc_;\n  vector<TIndex> dims_;\n};\n\n\nclass CuDNNSoftmaxGradientOp final : public Operator<CUDAContext> {\n public:\n  explicit CuDNNSoftmaxGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<CUDAContext>(def, ws),\n        cudnn_wrapper_(&context_),\n        axis_(OperatorBase::GetSingleArgument<int>(\"axis\", 1)) {\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&desc_));\n  }\n\n  ~CuDNNSoftmaxGradientOp() {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(desc_));\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    auto& Y = Input(0);\n    auto& dY = Input(1);\n    auto* dX = Output(0);\n    const auto canonical_axis = Y.canonical_axis_index(axis_);\n    const int N = Y.size_to_dim(canonical_axis);\n    const int D = Y.size_from_dim(canonical_axis);\n\n    CHECK_EQ(Y.dims(), dY.dims());\n    dX->ResizeLike(Y);\n    if (dims_ != Y.dims()) {\n      CUDNN_ENFORCE(cudnnSetTensor4dDescriptor(\n          desc_,\n          GetCudnnTensorFormat(StorageOrder::NCHW),\n          cudnnTypeWrapper<T>::type,\n          N,\n          D,\n          1,\n          1));\n      dims_ = Y.dims();\n    }\n    CUDNN_ENFORCE(cudnnSoftmaxBackward(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        CUDNN_SOFTMAX_ACCURATE,\n        CUDNN_SOFTMAX_MODE_INSTANCE,\n        cudnnTypeWrapper<T>::kOne(),\n        desc_,\n        Y.template data<T>(),\n        desc_,\n        dY.template data<T>(),\n        cudnnTypeWrapper<T>::kZero(),\n        desc_,\n        dX->template mutable_data<T>()));\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<float, float16>>::call(this, Input(0));\n  }\n\n protected:\n  CuDNNWrapper cudnn_wrapper_;\n  int axis_;\n  cudnnTensorDescriptor_t desc_;\n  vector<TIndex> dims_;\n};\n\nnamespace {\nREGISTER_CUDNN_OPERATOR(Softmax, CuDNNSoftmaxOp);\nREGISTER_CUDNN_OPERATOR(SoftmaxGradient, CuDNNSoftmaxGradientOp);\n}  // namespace\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/softmax_ops.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cfloat>\n#include <cub/block/block_reduce.cuh>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"softmax_op.h\"\n#include \"softmax_with_loss_op.h\"\n#include \"spatial_softmax_with_loss_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n__global__ void LabelCrossEntropyKernel(\n    const int N,\n    const int D,\n    const float* logPdata,\n    const int* labeldata,\n    const float* weights,\n    float* Ydata) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    CUDA_KERNEL_ASSERT(labeldata[i] >= 0 && labeldata[i] < D);\n    float weight = weights ? weights[i] : 1.0;\n    Ydata[i] = -logPdata[i * D + labeldata[i]] * weight;\n  }\n}\n\n__global__ void LabelCrossEntropyGradientKernel(\n    const int N,\n    const int D,\n    const float* Pdata,\n    const int* labeldata,\n    float* dXdata) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    int idx = i * D + labeldata[i];\n    dXdata[idx] = Pdata[idx] - 1.;\n  }\n}\n\n__global__ void LabelCrossEntropyGradientKernelWeighted(\n    const int N,\n    const int D,\n    const float* Pdata,\n    const int* labeldata,\n    float* dXdata,\n    const float* weights) {\n  CUDA_1D_KERNEL_LOOP(i, N * D) {\n    int row = i / D;\n    int d = i % D;\n    float val = Pdata[i] - 1.0 * (d == labeldata[row]);\n    float weight = weights[row];\n    dXdata[i] = val * weight;\n  }\n}\n\n__global__ void ProbCrossEntropyKernel(\n    const int N,\n    const int D,\n    const float* Pdata,\n    const float* labeldata,\n    const float* weights,\n    float* Ydata) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n\n  for (int i = blockIdx.x; i < N; i += gridDim.x) {\n    float weight = weights ? weights[i] : 1.0;\n    float sum = 0.0;\n    float total_prob = 0.0;\n    for (int j = threadIdx.x; j < D; j += blockDim.x) {\n      int idx = i * D + j;\n      CUDA_KERNEL_ASSERT(labeldata[idx] >= 0);\n      total_prob += labeldata[idx];\n      sum += -logf(max(Pdata[idx], FLT_MIN)) * labeldata[idx] * weight;\n    }\n    float tot = BlockReduce(temp_storage).Sum(sum);\n    __syncthreads();\n    float total_prob_sum = BlockReduce(temp_storage).Sum(total_prob);\n    if (threadIdx.x == 0) {\n      Ydata[i] = tot;\n      // Sanity check\n      CUDA_KERNEL_ASSERT(abs(1.0 - total_prob_sum) < 1e-5f);\n    }\n    __syncthreads();\n  }\n}\n\n__global__ void ProbCrossEntropyGradientKernel(\n    const int N,\n    const int D,\n    const float* Pdata,\n    const float* labeldata,\n    float* dXdata,\n    const float* weights) {\n  if (weights == NULL) {\n    CUDA_1D_KERNEL_LOOP(idx, N * D) {\n      dXdata[idx] = Pdata[idx] - labeldata[idx];\n    }\n  } else {\n    CUDA_1D_KERNEL_LOOP(idx, N * D) {\n      dXdata[idx] = (Pdata[idx] - labeldata[idx]) * weights[idx / D];\n    }\n  }\n}\n\n__global__ void SpatialSoftmaxKernel(\n    const int num,\n    const int D,\n    const int W,\n    const int H,\n    const float* Xdata,\n    float* Pdata) {\n  CUDA_1D_KERNEL_LOOP(index, num * W * H) {\n    int x = index % W;\n    int y = (index / W) % H;\n    int i = index / W / H;\n\n    // Subtract max on each cell for numerical reasons\n    float max_val = -FLT_MAX;\n    for(int c = 0; c < D; ++c) {\n      int idx = i * (H * W * D) + c * (H * W) + y * W + x;\n      max_val = max(max_val, Xdata[idx]);\n    }\n\n    // Exponentiate\n    float expsum = 0.0f;\n    for(int c = 0; c < D; ++c) {\n      int idx = i * (H * W * D) + c * (H * W) + y * W + x;\n      float expx = exp(Xdata[idx] - max_val);\n      Pdata[idx] = expx;\n      expsum += expx;\n    }\n\n    // Normalize\n    for(int c=0; c<D; ++c) {\n      int idx = i * (H * W * D) + c * (H * W) + y * W + x;\n      Pdata[idx] /= expsum;\n    }\n  }\n}\n\n\n#define DONTCARE (-1)\n\n__global__ void SpatialCrossEntropyLossKernel(\n    const int N,\n    const int D,\n    const int W,\n    const int H,\n    const float* Pdata,\n    const int* label_data,\n    const float* weights,\n    float* loss_data,\n    float* weight_data) {\n  CUDA_1D_KERNEL_LOOP(index, N * W * H) {\n    int x = index % W;\n    int y = (index / W) % H;\n    int i = index / W / H;\n    const int label = static_cast<int>(label_data[index]);\n\n    if (label != DONTCARE) {\n      CUDA_KERNEL_ASSERT(label >= 0 && label < D);\n      float weight = (weights == NULL ? 1.0 : weights[index]);\n      loss_data[index] = -log(max(\n        Pdata[i * W * H * D + label * W * H + y * W + x], 1e-20f)) * weight;\n      weight_data[index] = weight;\n    } else {\n      loss_data[index] = 0;\n      weight_data[index] = 0;\n    }\n  }\n}\n\n__global__ void SpatialSoftmaxLossGradientKernel(const int N, const int D,\n    const int W, const int H, const int* label_data, const float* weights,\n         float* dX_data, float* weights_) {\n CUDA_1D_KERNEL_LOOP(index, N * W * H) {\n   int x = index % W;\n   int y = (index / W) % H;\n   int i = index / W / H;\n   const int label = static_cast<int>(label_data[index]);\n\n   if (label != DONTCARE) {\n     int data_idx = i * (H * W * D) + label * (H * W) + y * W + x;\n     dX_data[data_idx] -= 1.0;\n     if (weights != NULL) {\n       float weight = weights[index];\n       for (int c = 0; c < D; ++c) {\n         int data_idx = i * (H * W * D) + c * (H * W) + y * W + x;\n         dX_data[data_idx] *= weight;\n       }\n       weights_[index] = weight;\n     } else {\n       weights_[index] = 1.0;\n     }\n   } else {\n     // Ignore-label, so set all gradients for this positions\n     // tp zero\n     for (int c = 0; c < D; ++c) {\n       int data_idx = i * (H * W * D) + c * (H * W) + y * W + x;\n       dX_data[data_idx] = 0.0;\n     }\n     weights_[index] = 0.0;\n   }\n }\n}\n\n__global__ void SoftmaxNormalizeLogsKernel(\n    const int nthreads,\n    const int D,\n    const float* logits,\n    const float* rowmax,\n    const float* scales,\n    float* out_log) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index / D;\n    out_log[index] = logits[index] - rowmax[n] - logf(max(scales[n], FLT_MIN));\n  }\n}\n\n__global__ void SoftmaxNormalizeKernel(\n    const int nthreads,\n    const int D,\n    const float* probs,\n    const float* scales,\n    float* out) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    int n = index / D;\n    out[index] = probs[index] / scales[n];\n  }\n}\n\nvoid Softmax(\n    const int N,\n    const int D,\n    const float* logits,\n    const float* sum_multiplier,\n    float* scales,\n    float* rowmax,\n    float* probs,\n    bool log_softmax,\n    CUDAContext* context) {\n  const int size = N * D;\n\n  math::RowwiseMax<float, CUDAContext>(N, D, logits, rowmax, context);\n  // Put the intermediate result X - max(X) into Y\n  context->Copy<float, CUDAContext, CUDAContext>(size, logits, probs);\n  // Subtract the scale\n  math::Gemm<float, CUDAContext>(\n      CblasNoTrans,\n      CblasNoTrans,\n      N,\n      D,\n      1,\n      -1,\n      rowmax,\n      sum_multiplier,\n      1,\n      probs,\n      context);\n  // Exponentiation\n  math::Exp<float, CUDAContext>(size, probs, probs, context);\n  // Sum exponentiated values\n  math::Gemv<float, CUDAContext>(CblasNoTrans, N, D, 1, probs, sum_multiplier,\n                                 0, scales, context);\n  // Normalize\n  if (!log_softmax) {\n    SoftmaxNormalizeKernel<<<\n        CAFFE_GET_BLOCKS(size),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context->cuda_stream()>>>(size, D, probs, scales, probs);\n  } else {\n    SoftmaxNormalizeLogsKernel<<<\n        CAFFE_GET_BLOCKS(size),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context->cuda_stream()>>>(size, D, logits, rowmax, scales, probs);\n  }\n}\n\n} // namespace\n\ntemplate<>\nbool SoftmaxWithLossOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);  // Logits\n  auto& T = Input(1);  // Labels / targets\n  auto* P = Output(0); // Probabilities from softmax\n  auto* avg_loss = Output(1); // Average loss\n  const float* weights = (InputSize() > 2 ? Input(2).data<float>() : NULL);\n\n  const auto canonical_axis = X.canonical_axis_index(axis_);\n  int N, D;\n  N = X.size_to_dim(canonical_axis); // batch size\n  D = X.size_from_dim(canonical_axis);\n  P->ResizeLike(X);\n  total_weight_ptr_.Resize(1);\n\n  if (label_prob_mode_) {\n    CAFFE_ENFORCE_GE(T.ndim(), 2);\n    CAFFE_ENFORCE_EQ(T.size_to_dim(canonical_axis), N);\n    CAFFE_ENFORCE_EQ(T.size_from_dim(canonical_axis), D);\n  } else {\n    if (T.ndim() == canonical_axis) {\n      CAFFE_ENFORCE_EQ(T.size(), N);\n    } else {\n      CAFFE_ENFORCE_EQ(T.size_to_dim(canonical_axis), N);\n      CAFFE_ENFORCE_EQ(T.size_from_dim(canonical_axis), 1);\n    }\n  }\n\n  avg_loss->Resize(vector<TIndex>());\n  if (losses_.size() != N) {\n    losses_.Resize(N);\n  }\n  if (rowmax_.size() != N) {\n    rowmax_.Resize(N);\n  }\n  if (sum_multiplier_.size() != D) {\n    sum_multiplier_.Resize(D);\n    math::Set<float, CUDAContext>(\n        D, 1.f, sum_multiplier_.mutable_data<float>(), &context_);\n  }\n  Softmax(\n      N,\n      D,\n      X.data<float>(),\n      sum_multiplier_.data<float>(),\n      losses_.mutable_data<float>(),\n      rowmax_.mutable_data<float>(),\n      P->mutable_data<float>(),\n      !label_prob_mode_, // logarithmic output\n      &context_);\n  // Compute label xent loss per example\n  if (!label_prob_mode_) {\n    LabelCrossEntropyKernel<<<\n        CAFFE_GET_BLOCKS(N),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        N,\n        D,\n        P->data<float>(),\n        T.data<int>(),\n        weights,\n        losses_.mutable_data<float>());\n    // Since we had logarithmic output, we need to exponentiate\n    // them again.\n    math::Exp<float, CUDAContext>(\n        N * D, P->data<float>(), P->mutable_data<float>(), &context_);\n  } else {\n    ProbCrossEntropyKernel<<<\n        std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        N,\n        D,\n        P->data<float>(),\n        T.data<float>(),\n        weights,\n        losses_.mutable_data<float>());\n  }\n\n  float total_weight = N;\n  if (weights) {\n    // Sum weights\n    math::Sum<float, CUDAContext>(\n        N, weights, total_weight_ptr_.mutable_data<float>(), &context_, &scratch_);\n    cudaMemcpyAsync(\n        &total_weight,\n        total_weight_ptr_.data<float>(),\n        sizeof(float),\n        cudaMemcpyDeviceToHost,\n        context_.cuda_stream());\n  }\n\n  // Sum of all losses\n  float* avg_loss_data = avg_loss->mutable_data<float>();\n  math::Sum<float, CUDAContext>(\n      losses_.size(), losses_.data<float>(), avg_loss_data, &context_, &scratch_);\n  // Average of input batch size\n  if (total_weight > 0) {\n    math::Scale<float, CUDAContext>(\n        1, scale_ / total_weight, avg_loss_data, avg_loss_data, &context_);\n  }\n\n  return true;\n}\n\ntemplate <>\nbool SpatialSoftmaxWithLossOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0); // Logits\n  auto& T = Input(1); // Labels / targets\n  auto* P = Output(0); // Probabilities from softmax\n  auto* avg_loss = Output(1); // Average loss\n  const float* weights = (InputSize() > 2 ? Input(2).data<float>() : NULL);\n  int N, D;\n  N = X.dim32(0);\n  D = X.dim32(1);\n  P->ResizeLike(X);\n  total_weight_ptr_.Resize(1);\n  CAFFE_ENFORCE_EQ(X.ndim(), 4);\n  CAFFE_ENFORCE_EQ(T.ndim(), 3);\n  CAFFE_ENFORCE_EQ(T.dim32(0), N);\n\n  int H = X.dim32(2);\n  int W = X.dim32(3);\n  if (losses_.size() != N * W * H) {\n    losses_.Resize(N * W * H);\n  }\n  if (weights_.size() != N * W * H) {\n    weights_.Resize(N * W * H);\n  }\n\n  const float* Xdata = X.data<float>();\n  float* Pdata = P->mutable_data<float>();\n\n  // Softmax for each x,y location\n  SpatialSoftmaxKernel<<<\n      CAFFE_GET_BLOCKS(N),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, D, W, H, Xdata, Pdata);\n\n  // Cross entropy\n  avg_loss->Resize(vector<TIndex>());\n  float* avg_loss_data = avg_loss->mutable_data<float>();\n  math::Set<float, CUDAContext>(1, 0.0f, avg_loss_data, &context_);\n\n  const int* label_data = T.data<int>();\n  math::Set<float, CUDAContext>(\n      1, 0.0f, total_weight_ptr_.mutable_data<float>(), &context_);\n\n  SpatialCrossEntropyLossKernel<<<\n      CAFFE_GET_BLOCKS(N * W * H),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N,\n      D,\n      W,\n      H,\n      P->data<float>(),\n      label_data,\n      weights,\n      losses_.mutable_data<float>(),\n      weights_.mutable_data<float>());\n\n  // Somewhat awkward scalar passing from device to host\n  float h_total_weight;\n  math::Sum<float, CUDAContext>(\n      weights_.size(),\n      weights_.data<float>(),\n      total_weight_ptr_.mutable_data<float>(),\n      &context_,\n      &scratch_);\n  cudaMemcpyAsync(\n      &h_total_weight,\n      total_weight_ptr_.data<float>(),\n      sizeof(float),\n      cudaMemcpyDeviceToHost,\n      context_.cuda_stream());\n\n  math::Sum<float, CUDAContext>(\n      losses_.size(), losses_.data<float>(), avg_loss_data, &context_, &scratch_);\n\n  // Final scaling\n  if (h_total_weight > 0) {\n    math::Scale<float, CUDAContext>(\n        1, scale_ / h_total_weight, avg_loss_data, avg_loss_data, &context_);\n  }\n\n  return true;\n}\n\ntemplate <>\nbool SoftmaxWithLossGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);  // Logits\n  auto& T = Input(1);  // Labels / targets\n  // Input(2) is weights, if given\n  auto& P = Input(InputSize() - 2);  // Probabilities from softmax\n  auto& d_avg_loss = Input(InputSize() - 1); // Gradient w.r.t. avg loss\n  const float* weights = (InputSize() > 4 ? Input(2).data<float>() : NULL);\n\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n\n  const auto canonical_axis = X.canonical_axis_index(axis_);\n  int N, D;\n  N = X.size_to_dim(canonical_axis); // batch size\n  D = X.size_from_dim(canonical_axis);\n\n  if (only_loss_) {\n    // Memory saving trick to share the buffer with the softmax output.\n    // Softmax output is thus overwritten.\n    dX->ShareData(P);\n  }\n\n  total_weight_ptr_.Resize(1);\n\n  if (label_prob_mode_) {\n    CAFFE_ENFORCE_GE(T.ndim(), 2);\n    CAFFE_ENFORCE_EQ(T.size_to_dim(canonical_axis), N);\n    CAFFE_ENFORCE_EQ(T.size_from_dim(canonical_axis), D);\n  } else {\n    if (T.ndim() == canonical_axis) {\n      CAFFE_ENFORCE_EQ(T.size(), N);\n    } else {\n      CAFFE_ENFORCE_EQ(T.size_to_dim(canonical_axis), N);\n      CAFFE_ENFORCE_EQ(T.size_from_dim(canonical_axis), 1);\n    }\n  }\n\n  // Subtract 1 from labeled positions\n  if (!label_prob_mode_) {\n    if (weights == nullptr) {\n      // Copy softmax probabilities into dX\n      if (!only_loss_) {\n        context_.Copy<float, CUDAContext, CUDAContext>(\n            P.size(), P.data<float>(), dX->mutable_data<float>());\n      }\n      LabelCrossEntropyGradientKernel<<<\n          CAFFE_GET_BLOCKS(N),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          N, D, P.data<float>(), T.data<int>(), dX->mutable_data<float>());\n    } else {\n      // Weighted version gets the Pdata values internally\n      LabelCrossEntropyGradientKernelWeighted<<<\n          CAFFE_GET_BLOCKS(N * D),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context_.cuda_stream()>>>(\n          N,\n          D,\n          P.data<float>(),\n          T.data<int>(),\n          dX->mutable_data<float>(),\n          weights);\n    }\n  } else {\n    ProbCrossEntropyGradientKernel<<<\n        CAFFE_GET_BLOCKS(N * D),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        N,\n        D,\n        P.data<float>(),\n        T.data<float>(),\n        dX->mutable_data<float>(),\n        weights);\n  }\n  float total_weight = N;\n  if (weights) {\n    // Sum weights\n    math::Sum<float, CUDAContext>(\n        N, weights, total_weight_ptr_.mutable_data<float>(), &context_, &scratch_);\n    cudaMemcpyAsync(\n        &total_weight,\n        total_weight_ptr_.data<float>(),\n        sizeof(float),\n        cudaMemcpyDeviceToHost,\n        context_.cuda_stream());\n  }\n\n  // Scale by d_avg_loss / N\n  if (total_weight > 0) {\n    math::Scale<float, CUDAContext>(\n        dX->size(),\n        scale_ / total_weight,\n        dX->data<float>(),\n        dX->mutable_data<float>(),\n        &context_);\n  }\n  math::Scale<float, CUDAContext>(\n      dX->size(),\n      d_avg_loss.data<float>(),\n      dX->data<float>(),\n      dX->mutable_data<float>(),\n      &context_);\n\n  return true;\n}\n\ntemplate <>\nbool SpatialSoftmaxWithLossGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0); // Logits\n  auto& T = Input(1); // Labels / targets\n  // Input(2) is weights, if given\n  auto& P = Input(InputSize() - 2); // Probabilities from softmax\n  auto& d_avg_loss = Input(InputSize() - 1); // Gradient w.r.t. avg loss\n  const float* weights = (InputSize() > 4 ? Input(2).data<float>() : NULL);\n\n  auto* dX = Output(0);\n  dX->ResizeLike(X);\n\n  const auto canonical_axis = X.canonical_axis_index(1);\n  int N, D;\n  N = X.dim32(0);\n  D = X.dim32(1);\n\n  if (only_loss_) {\n    // Memory saving trick to share the buffer with the softmax output.\n    // Softmax output is thus overwritten.\n    dX->ShareData(P);\n  }\n\n  total_weight_ptr_.Resize(1);\n  // Spatial mode, compute softmax for each x, y location\n  CAFFE_ENFORCE_EQ(X.ndim(), 4);\n  CAFFE_ENFORCE_EQ(T.ndim(), 3);\n\n  int H = X.dim32(2);\n  int W = X.dim32(3);\n  dX->ResizeLike(X);\n  if (weights_.size() != N * W * H) {\n    weights_.Resize(N * W * H);\n  }\n\n  const float* Pdata = P.data<float>();\n  float* dX_data = dX->mutable_data<float>();\n  const int* label_data = T.data<int>();\n  const float* d_avg_loss_data = d_avg_loss.data<float>();\n\n  // Copy softmax probabilities into dX. All but the neuron\n  // corresponding to the correct label has gradient equaling e(x_j)\n  // which is the probability under softmax.\n  context_.Copy<float, CUDAContext, CUDAContext>(P.size(), Pdata, dX_data);\n\n  math::Set<float, CUDAContext>(\n      1, 0.0f, total_weight_ptr_.mutable_data<float>(), &context_);\n\n  SpatialSoftmaxLossGradientKernel<<<\n      CAFFE_GET_BLOCKS(N * W * H),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N, D, W, H, label_data, weights, dX_data, weights_.mutable_data<float>());\n\n  math::Sum<float, CUDAContext>(\n      weights_.size(),\n      weights_.data<float>(),\n      total_weight_ptr_.mutable_data<float>(),\n      &context_,\n      &scratch_);\n\n  // Somewhat awkward scalar passing from device to host\n  float h_total_weight;\n  cudaMemcpyAsync(\n      &h_total_weight,\n      total_weight_ptr_.data<float>(),\n      sizeof(float),\n      cudaMemcpyDeviceToHost,\n      context_.cuda_stream());\n\n  // Final scaling\n  if (h_total_weight > 0) {\n    math::Scale<float, CUDAContext>(\n        dX->size(),\n        scale_ / h_total_weight,\n        dX->data<float>(),\n        dX->mutable_data<float>(),\n        &context_);\n  }\n  math::Scale<float, CUDAContext>(\n      dX->size(),\n      d_avg_loss.data<float>(),\n      dX->data<float>(),\n      dX->mutable_data<float>(),\n      &context_);\n\n  return true;\n}\n\n// Implementation for the CUDA context.\ntemplate <>\nbool SoftmaxOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* P = Output(0);\n  const auto canonical_axis = X.canonical_axis_index(axis_);\n  const int N = X.size_to_dim(canonical_axis);\n  const int D = X.size_from_dim(canonical_axis);\n  P->ResizeLike(X);\n  if (sum_multiplier_.size() != D) {\n    sum_multiplier_.Resize(D);\n    math::Set<float, CUDAContext>(\n        D, 1.f, sum_multiplier_.mutable_data<float>(), &context_);\n  }\n  if (scale_.size() != N) {\n    scale_.Resize(N);\n  }\n  if (rowmax_.size() != N) {\n    rowmax_.Resize(N);\n  }\n  Softmax(\n      N,\n      D,\n      X.data<float>(),\n      sum_multiplier_.data<float>(),\n      scale_.mutable_data<float>(),\n      rowmax_.mutable_data<float>(),\n      P->mutable_data<float>(),\n      false,\n      &context_);\n  return true;\n}\n#define SOFTMAX_NUM_THREADS 128\n\n// The softmax gradient kernel. This kernel has to be called with the number of\n// threads per block being no more than SOFTMAX_NUM_THREADS.\nnamespace {\n__global__ void softmax_gradient_kernel(\n    const int dim,\n    const float* Y,\n    const float* dY,\n    float* dX) {\n  Y += blockIdx.x * dim;\n  dY += blockIdx.x * dim;\n  dX += blockIdx.x * dim;\n  const int idx = threadIdx.x;\n  __shared__ float reduction_buffer[SOFTMAX_NUM_THREADS];\n  float tmp;\n\n  // A two-level reduction to compute the inner products.\n  tmp = 0;\n  for (int i = idx; i < dim; i += blockDim.x) {\n    tmp += dY[i] * Y[i];\n  }\n  reduction_buffer[idx] = tmp;\n  __syncthreads();\n  if (idx == 0) {\n    tmp = reduction_buffer[0];\n    for (int i = 1; i < blockDim.x; ++i)\n      tmp += reduction_buffer[i];\n    reduction_buffer[0] = tmp;\n  }\n  __syncthreads();\n  // Compute gradient.\n  tmp = reduction_buffer[0];\n  for (int i = idx; i < dim; i += blockDim.x) {\n    dX[i] = Y[i] * (dY[i] - tmp);\n  }\n}\n} // namespace\n\ntemplate <>\nbool SoftmaxGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  const auto canonical_axis = Y.canonical_axis_index(axis_);\n  const int N = Y.size_to_dim(canonical_axis);\n  const int D = Y.size_from_dim(canonical_axis);\n  dX->ResizeLike(Y);\n  softmax_gradient_kernel<<<\n      N,\n      SOFTMAX_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      D, Y.data<float>(), dY.data<float>(), dX->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(SoftmaxWithLoss,\n                       SoftmaxWithLossOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(SoftmaxWithLossGradient,\n                       SoftmaxWithLossGradientOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    SpatialSoftmaxWithLoss,\n    SpatialSoftmaxWithLossOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    SpatialSoftmaxWithLossGradient,\n    SpatialSoftmaxWithLossGradientOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(Softmax, SoftmaxOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(SoftmaxGradient, SoftmaxGradientOp<float, CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/softmax_shared.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nvoid SoftmaxCPU(\n    CPUContext& context,\n    const int N,\n    const int D,\n    const float* Xdata,\n    float* Ydata,\n    float* scale,\n    const float* sum_multiplier,\n    bool logarithmic,\n    float* rowmax) {\n  math::RowwiseMax<float, CPUContext>(N, D, Xdata, rowmax, &context);\n  // Put the intermediate result X - max(X) into Y\n  context.template Copy<float, CPUContext, CPUContext>(N * D, Xdata, Ydata);\n  // Subtract the max (for numerical reasons)\n  math::Gemm<float, CPUContext>(\n      CblasNoTrans,\n      CblasNoTrans,\n      N,\n      D,\n      1,\n      -1,\n      rowmax,\n      sum_multiplier,\n      1,\n      Ydata,\n      &context);\n  // Exponentiation\n  math::Exp<float, CPUContext>(N * D, Ydata, Ydata, &context);\n  math::Gemv<float, CPUContext>(\n      CblasNoTrans, N, D, 1, Ydata, sum_multiplier, 0, scale, &context);\n  // Do division\n  // TODO(Yangqing): maybe implement it more beautifully?\n  if (!logarithmic) {\n    for (int i = 0; i < N; ++i) {\n      for (int j = 0; j < D; ++j) {\n        Ydata[i * D + j] /= scale[i];\n      }\n    }\n  } else {\n    for (int i = 0; i < N; ++i) {\n      for (int j = 0; j < D; ++j) {\n        Ydata[i * D + j] =\n            Xdata[i * D + j] - rowmax[i] - log(fmaxf(scale[i], 1e-20f));\n      }\n    }\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/softmax_shared.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SOFTMAX_SHARED_H_\n#define CAFFE2_OPERATORS_SOFTMAX_SHARED_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\nvoid SoftmaxCPU(\n    CPUContext& context,\n    const int N,\n    const int D,\n    const float* Xdata,\n    float* Ydata,\n    float* scale,\n    const float* sum_multiplier,\n    bool logarithmic,\n    float* rowmax);\n} // namespace caffe2\n\n#endif // #define CAFFE2_OPERATORS_SOFTMAX_SHARED_H_\n"
  },
  {
    "path": "caffe2/operators/softmax_with_loss_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"softmax_with_loss_op.h\"\n#include \"softmax_shared.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(SoftmaxWithLoss, SoftmaxWithLossOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    SoftmaxWithLossGradient,\n    SoftmaxWithLossGradientOp<float, CPUContext>);\n\n// Input: X (logits), T (labels); Output: P (probs), Y\nOPERATOR_SCHEMA(SoftmaxWithLoss)\n    .NumInputs(2, 3)\n    .NumOutputs(2)\n    .TensorInferenceFunction(\n        [](const OperatorDef& def, const vector<TensorShape>& in) {\n          ArgumentHelper helper(def);\n          auto axis = helper.GetSingleArgument<int32_t>(\"axis\", 1);\n\n          vector<TensorShape> out(2);\n\n          auto logits = in[0]; // Tensor with Shape [batch_size, num_classes]\n          auto labels = in[1]; // Tensor with shape [batch_size, ]\n          const auto canonical_axis =\n              canonical_axis_index_(axis, logits.dims().size());\n          const int batch_size =\n              size_to_dim_(canonical_axis, GetDimsVector(logits));\n          const int num_classes =\n              size_from_dim_(canonical_axis, GetDimsVector(logits));\n\n          out[0].set_data_type(logits.data_type());\n          out[0].add_dims(batch_size);\n          out[0].add_dims(num_classes);\n\n          return out;\n        })\n    .SetDoc(R\"DOC(\nCombined Softmax and Cross-Entropy loss operator.\nThe operator computes the softmax normalized values for each layer in the batch\nof the given input, after which cross-entropy loss is computed. This operator is\nnumerically more stable than separate Softmax and CrossEntropy ops.\nThe inputs are a 2-D tensor (Tensor<float>) of size\n(batch_size x input_feature_dimensions) and tensor of labels (ground truth).\nOutput is tensor with the probability for each label for each example (N x D)\nand averaged loss (scalar).\nUse parameter label_prob=1 to enable inputting labels as a probability\ndistribution.\nOptional third input blob can be used to weight the samples for the loss.\n)DOC\")\n    .Input(0, \"logits\", \"Unscaled log probabilities\")\n    .Input(1, \"labels\", \"Ground truth\")\n    .Input(\n        2,\n        \"weight_tensor\",\n        \"Optional blob to be used to weight the samples for the loss.\")\n    .Output(0, \"softmax\", \"Tensor with softmax cross entropy loss\")\n    .Output(1, \"loss\", \"Average loss\");\n\n// Input: X, T, P, dY; Output: dX\nOPERATOR_SCHEMA(SoftmaxWithLossGradient).NumOutputs(1);\n\n#define DONT_CARE (-1)\n\ntemplate <>\nbool SoftmaxWithLossOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0); // Logits\n  auto& T = Input(1); // Labels / targets\n  auto* P = Output(0); // Probabilities from softmax\n  auto* avg_loss = Output(1); // Average loss\n\n  const auto canonical_axis = X.canonical_axis_index(axis_);\n  int N, D;\n  N = X.size_to_dim(canonical_axis); // batch size\n  D = X.size_from_dim(canonical_axis);\n  P->ResizeLike(X);\n\n  if (sum_multiplier_.size() != D) {\n    sum_multiplier_.Resize(D);\n    math::Set<float, CPUContext>(\n        D, 1.f, sum_multiplier_.mutable_data<float>(), &context_);\n  }\n\n  float* Pdata = P->mutable_data<float>();\n  const float* weights = (InputSize() > 2 ? Input(2).data<float>() : nullptr);\n\n  if (label_prob_mode_) {\n    CAFFE_ENFORCE_GE(T.ndim(), 2);\n    CAFFE_ENFORCE_EQ(T.size_to_dim(canonical_axis), N);\n    CAFFE_ENFORCE_EQ(T.size_from_dim(canonical_axis), D);\n  } else {\n    if (T.ndim() == canonical_axis) {\n      CAFFE_ENFORCE_EQ(T.size(), N);\n    } else {\n      CAFFE_ENFORCE_EQ(T.size_to_dim(canonical_axis), N);\n      CAFFE_ENFORCE_EQ(T.size_from_dim(canonical_axis), 1);\n    }\n  }\n\n  if (sum_multiplier_.size() != D) {\n    sum_multiplier_.Resize(D);\n    math::Set<float, CPUContext>(\n        D, 1.f, sum_multiplier_.mutable_data<float>(), &context_);\n  }\n\n  rowmax_.Resize(N);\n  losses_.Resize(N);\n\n  SoftmaxCPU(\n      context_,\n      N,\n      D,\n      X.data<float>(),\n      Pdata,\n      losses_.mutable_data<float>(),\n      sum_multiplier_.data<float>(),\n      !label_prob_mode_,\n      rowmax_.mutable_data<float>());\n\n  // Then compute cross entropy\n  float loss_sum = 0.0;\n  float weight_sum = 0.0;\n  if (!label_prob_mode_) {\n    const int* label_data = T.data<int>();\n    const float* Xdata = X.data<float>();\n\n    for (int i = 0; i < N; ++i) {\n      CAFFE_ENFORCE(\n          label_data[i] < D && label_data[i] >= 0,\n          \"Label seems incorrect: label value larger than number of classes: \",\n          label_data[i],\n          \" vs \",\n          D);\n      float weight = weights ? weights[i] : 1.0;\n      float l = -Pdata[i * D + label_data[i]] * weight;\n      loss_sum += l;\n      weight_sum += weight;\n    }\n    math::Exp(N * D, Pdata, Pdata, &context_);\n  } else {\n    const float* label_data = T.data<float>();\n\n    for (int i = 0; i < N; ++i) {\n      float l = 0.0;\n      float total_prob = 0.0;\n      float weight = weights ? weights[i] : 1.0;\n      for (int j = 0; j < D; ++j) {\n        CAFFE_ENFORCE(\n            label_data[i * D + j] >= 0,\n            \"Label prob seems incorrect: label prob value must be nonnegative:\",\n            \" \",\n            label_data[i * D + j]);\n        l += -log(std::max(Pdata[i * D + j], 1e-20f)) * label_data[i * D + j] *\n            weight;\n        total_prob += label_data[i * D + j];\n      }\n      loss_sum += l;\n      CAFFE_ENFORCE(\n          std::abs(total_prob - 1.) < 1e-5f,\n          \"Label prob seems incorrect: label prob values do not sum to 1.0: \",\n          total_prob,\n          \" vs 1.0 (+/- 1e-5)\");\n      weight_sum += weight;\n    }\n  }\n\n  avg_loss->Resize(vector<TIndex>());\n  float* avg_loss_data = avg_loss->mutable_data<float>();\n  if (weight_sum != 0.0) {\n    avg_loss_data[0] = loss_sum * scale_ / weight_sum;\n  } else {\n    avg_loss_data[0] = 0.0;\n  }\n  return true;\n}\n\ntemplate <>\nbool SoftmaxWithLossGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0); // Logits\n  auto& T = Input(1); // Labels / targets\n  // Input(2) is weights if given\n  auto& P = Input(InputSize() - 2); // Probabilities from softmax\n  auto& d_avg_loss = Input(InputSize() - 1); // Gradient w.r.t. avg loss\n  auto* dX = Output(0);\n  const float* weights = (InputSize() > 4 ? Input(2).data<float>() : nullptr);\n\n  const auto canonical_axis = X.canonical_axis_index(axis_);\n  int N, D;\n  N = X.size_to_dim(canonical_axis); // batch size\n  D = X.size_from_dim(canonical_axis);\n  dX->ResizeLike(X);\n\n  if (label_prob_mode_) {\n    CAFFE_ENFORCE_GE(T.ndim(), 2);\n    CAFFE_ENFORCE_EQ(T.size_to_dim(canonical_axis), N);\n    CAFFE_ENFORCE_EQ(T.size_from_dim(canonical_axis), D);\n  } else {\n    if (T.ndim() == canonical_axis) {\n      CAFFE_ENFORCE_EQ(T.size(), N);\n    } else {\n      CAFFE_ENFORCE_EQ(T.size_to_dim(canonical_axis), N);\n      CAFFE_ENFORCE_EQ(T.size_from_dim(canonical_axis), 1);\n    }\n  }\n\n  const float* Pdata = P.data<float>();\n  float* dX_data = dX->mutable_data<float>();\n\n  // Copy softmax probabilities into dX. All but the neuron\n  // corresponding to the correct label has gradient equaling e(x_j)\n  // which is the probability under softmax.\n  context_.Copy<float, CPUContext, CPUContext>(P.size(), Pdata, dX_data);\n\n  // Compute gradient for the matching labels.\n  float total_weight = 0.0f;\n  if (!label_prob_mode_) {\n    const int* label_data = T.data<int>();\n\n    if (weights) {\n      for (int i = 0; i < N; ++i) {\n        int idx = i * D + label_data[i];\n        float weight = weights[i];\n        dX_data[idx] = Pdata[idx] - 1.0;\n        for (int d = 0; d < D; d++) {\n          int k = i * D + d;\n          dX_data[k] *= weight;\n        }\n\n        total_weight += weight;\n      }\n    } else {\n      for (int i = 0; i < N; ++i) {\n        int idx = i * D + label_data[i];\n        dX_data[idx] = Pdata[idx] - 1.0f;\n      }\n      total_weight = N;\n    }\n  } else {\n    const float* label_data = T.data<float>();\n\n    if (weights) {\n      for (int i = 0; i < N; ++i) {\n        float weight = weights[i];\n        for (int j = 0; j < D; ++j) {\n          int idx = i * D + j;\n          dX_data[idx] = (Pdata[idx] - label_data[idx]) * weight;\n        }\n        total_weight += weight;\n      }\n    } else {\n      for (int i = 0; i < N; ++i) {\n        for (int j = 0; j < D; ++j) {\n          int idx = i * D + j;\n          dX_data[idx] = Pdata[idx] - label_data[idx];\n        }\n      }\n      total_weight = N;\n    }\n  }\n\n  // Scale by d_avg_loss / N\n  if (total_weight > 0) {\n    math::Scale<float, CPUContext>(\n        dX->size(),\n        scale_ / total_weight * d_avg_loss.data<float>()[0],\n        dX->data<float>(),\n        dX_data,\n        &context_);\n  }\n  return true;\n}\n\nnamespace {\nclass GetSoftmaxWithLossGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> blob_names{\n        {I(0), I(1), O(0), GO(1)},\n    };\n\n    // Add weight blob, if given\n    if (def_.input_size() == 3) {\n      blob_names.emplace(blob_names.begin() + 2, I(2));\n    }\n    return SingleGradientDef(\n        \"SoftmaxWithLossGradient\", \"\", blob_names, vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(SoftmaxWithLoss, GetSoftmaxWithLossGradient);\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/softmax_with_loss_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef SOFTMAX_WITH_LOSS_OP_H_\n#define SOFTMAX_WITH_LOSS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SoftmaxWithLossOp final : public Operator<Context> {\n public:\n  SoftmaxWithLossOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)),\n        label_prob_mode_(OperatorBase::GetSingleArgument<int>(\"label_prob\", 0)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))),\n        axis_(OperatorBase::GetSingleArgument<int>(\"axis\", 1)) {\n    CAFFE_ENFORCE(scale_ >= 0);\n    CAFFE_ENFORCE_EQ(\n        order_, StorageOrder::NCHW, \"Only NCHW order is supported right now.\");\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  float scale_;\n  int label_prob_mode_;\n  StorageOrder order_;\n  int axis_;\n\n  Tensor<Context> losses_; // Per example loss\n  Tensor<Context> rowmax_; // per example row max\n  Tensor<Context> weights_; // unignored weights\n  Tensor<Context> sum_multiplier_; // Vector of ones for summing via dot prod\n  Tensor<Context> total_weight_ptr_;\n  Tensor<Context> scratch_;\n};\n\ntemplate <typename T, class Context>\nclass SoftmaxWithLossGradientOp final : public Operator<Context> {\n public:\n  SoftmaxWithLossGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)),\n        label_prob_mode_(OperatorBase::GetSingleArgument<int>(\"label_prob\", 0)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))),\n        only_loss_(OperatorBase::GetSingleArgument<bool>(\"only_loss\", false)),\n        axis_(OperatorBase::GetSingleArgument<int>(\"axis\", 1)) {\n    CAFFE_ENFORCE(scale_ >= 0);\n    CAFFE_ENFORCE_EQ(\n        order_, StorageOrder::NCHW, \"Only NCHW order is supported right now.\");\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  float scale_;\n  int label_prob_mode_;\n  Tensor<Context> sum_multiplier_;\n  Tensor<Context> weights_; // unignored weights\n  Tensor<Context> total_weight_ptr_;\n  StorageOrder order_;\n  bool only_loss_;\n  int axis_;\n  Tensor<Context> scratch_;\n};\n\n} // namespace caffe2\n\n#endif // SOFTMAX_WITH_LOSS_OP_H_\n"
  },
  {
    "path": "caffe2/operators/softplus_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/softplus_op.h\"\n\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool SoftplusOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n\n  EigenVectorMap<float>(Y->mutable_data<float>(), X.size()) =\n      (ConstEigenVectorMap<float>(X.data<float>(), X.size()).array().exp() +\n       1.0f)\n          .log();\n  return true;\n}\n\ntemplate <>\nbool SoftplusGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  DCHECK_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n\n  const float* Ydata = Y.data<float>();\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  EigenVectorArrayMap<float> dXvec(dXdata, dX->size());\n  ConstEigenVectorArrayMap<float> Yvec(Ydata, Y.size());\n  ConstEigenVectorArrayMap<float> dYvec(dYdata, dY.size());\n  dXvec = dYvec * (1.0 - (-Yvec).exp());\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(Softplus, SoftplusOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(SoftplusGradient, SoftplusGradientOp<float, CPUContext>);\n\n// Input: X, output: Y\nOPERATOR_SCHEMA(Softplus)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nSoftplus takes one input data (Tensor<T>) and produces one output data\n(Tensor<T>) where the softplus function, y = ln(exp(x) + 1), is applied to\nthe tensor elementwise.\n)DOC\")\n    .Input(0, \"X\", \"1D input tensor\")\n    .Output(0, \"Y\", \"1D input tensor\");\n\n// Input: Y, dY, output: dX\nOPERATOR_SCHEMA(SoftplusGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{1, 0}});\n\nclass GetSoftplusGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SoftplusGradient\",\n        \"\",\n        vector<string>{O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Softplus, GetSoftplusGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/softplus_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/softplus_op.h\"\n\nnamespace caffe2 {\nnamespace {\ntemplate <typename T>\n__global__ void SoftplusKernel(const int N, const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = log(exp(X[i]) + 1.0f);\n  }\n}\n\ntemplate <typename T>\n__global__ void\nSoftplusGradientKernel(const int N, const T* Y, const T* dY, T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    const float nexpY = exp(-Y[i]);\n    dX[i] = dY[i] * (1 - nexpY);\n  }\n}\n} // namespace\n\ntemplate <>\nbool SoftplusOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  DCHECK_GT(X.size(), 0);\n  Y->ResizeLike(X);\n  SoftplusKernel<<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(), X.data<float>(), Y->mutable_data<float>());\n  return true;\n}\n\ntemplate <>\nbool SoftplusGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  DCHECK_GT(Y.size(), 0);\n  DCHECK_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n  SoftplusGradientKernel<<<\n      CAFFE_GET_BLOCKS(Y.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      Y.size(), Y.data<float>(), dY.data<float>(), dX->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Softplus, SoftplusOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    SoftplusGradient,\n    SoftplusGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/softplus_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SOFTPLUS_OP_H_\n#define CAFFE2_OPERATORS_SOFTPLUS_OP_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SoftplusOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(SoftplusOp)\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n};\n\ntemplate <typename T, class Context>\nclass SoftplusGradientOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(SoftplusGradientOp)\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  // Input: Y, dY; Output: dX\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SOFTPLUS_OP_H_\n"
  },
  {
    "path": "caffe2/operators/softsign_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nstruct SoftsignCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* /*device_context*/) {\n    ConstEigenVectorArrayMap<T> x_arr(x, n);\n    EigenVectorMap<T>(y, n) = (1 + x_arr.abs()).inverse() * x_arr;\n  }\n};\n\nstruct SoftsignGradientCPUFunctor {\n  template <typename T>\n  inline void Run(\n      const int n,\n      const T* x,\n      const T* dy,\n      T* dx,\n      CPUContext* /*device_context*/) {\n    ConstEigenVectorArrayMap<T> dy_arr(dy, n);\n    ConstEigenVectorArrayMap<T> x_arr(x, n);\n    EigenVectorMap<T>(dx, n) = dy_arr * (1 + x_arr.abs()).pow(2).inverse();\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    Softsign,\n    UnaryElementwiseOp<TensorTypes<float>, CPUContext, SoftsignCPUFunctor>);\nREGISTER_CPU_OPERATOR(\n    SoftsignGradient,\n    BinaryElementwiseOp<\n        TensorTypes<float>,\n        CPUContext,\n        WithoutBroadcast<SoftsignGradientCPUFunctor>>);\n\nOPERATOR_SCHEMA(Softsign)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nCalculates the softsign (x/1+|x|) of the given input tensor element-wise. This\noperation can be done in an in-place fashion too, by providing the same input\nand output blobs.\n)DOC\")\n    .Input(0, \"input\", \"1-D input tensor\")\n    .Output(\n        0,\n        \"output\",\n        \"The softsign (x/1+|x|) values of the input tensor \"\n        \"computed element-wise\");\n\nOPERATOR_SCHEMA(SoftsignGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{1, 0}})\n    .SetDoc(R\"DOC(\nCalculates the softsign gradient (sgn(x)/(1+|x|)^2) of the given input tensor\nelement-wise.\n)DOC\")\n    .Input(0, \"input\", \"1-D input tensor\")\n    .Input(1, \"input\", \"1-D input tensor\")\n    .Output(\n        0,\n        \"output\",\n        \"The softsign gradient (sgn(x)/(1+|x|)^2) values of the input tensor \"\n        \"computed element-wise\");\n\nclass GetSoftsignGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE(\n        I(0) != O(0),\n        \"Cannot compute softsign gradient \"\n        \"if you choose to do an in-place calculation.\");\n\n    return SingleGradientDef(\n        \"SoftsignGradient\",\n        \"\",\n        vector<string>{I(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(Softsign, GetSoftsignGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/softsign_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cmath>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void SoftsignKernel(const int N, const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = X[i] / (1 + abs(X[i]));\n  }\n}\n\ntemplate <typename T>\n__global__ void SoftsignGradientKernel(const int N, const T* x, const T* dy,\n                              T* dx) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dx[i] = dy[i] / pow(1 + abs(x[i]), 2);\n  }\n}\n\nstruct SoftsignCUDAFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CUDAContext* device_context) {\n    SoftsignKernel<T><<<\n        CAFFE_GET_BLOCKS(n),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        device_context->cuda_stream()>>>(n, x, y);\n    return;\n  }\n};\n\nstruct SoftsignGradientCUDAFunctor {\n  template <typename T>\n  inline void\n  Run(const int n, const T* x, const T* dy, T* dx, CUDAContext* device_context) {\n    SoftsignGradientKernel<T><<<\n        CAFFE_GET_BLOCKS(n),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        device_context->cuda_stream()>>>(n, x, dy, dx);\n    return;\n  }\n};\n\nREGISTER_CUDA_OPERATOR(\n    Softsign,\n    UnaryElementwiseOp<TensorTypes<float>, CUDAContext, SoftsignCUDAFunctor>);\nREGISTER_CUDA_OPERATOR(\n    SoftsignGradient,\n    BinaryElementwiseOp<TensorTypes<float>, CUDAContext, WithoutBroadcast<SoftsignGradientCUDAFunctor>>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/space_batch_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/space_batch_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(SpaceToBatch, SpaceToBatchOp<CPUContext>);\nOPERATOR_SCHEMA(SpaceToBatch).NumInputs(1).NumOutputs(1).SetDoc(R\"DOC(\n\nSpaceToBatch for 4-D tensors of type T.\n\nZero-pads and then rearranges (permutes) blocks of spatial data into\nbatch. More specifically, this op outputs a copy of the input tensor\nwhere values from the height and width dimensions are moved to the\nbatch dimension. After the zero-padding, both height and width of the\ninput must be divisible by the block size.\n\n)DOC\");\n\nREGISTER_CPU_OPERATOR(BatchToSpace, BatchToSpaceOp<CPUContext>);\nOPERATOR_SCHEMA(BatchToSpace).NumInputs(1).NumOutputs(1).SetDoc(R\"DOC(\n\nBatchToSpace for 4-D tensors of type T.\n\nRearranges (permutes) data from batch into blocks of spatial data,\nfollowed by cropping. This is the reverse transformation of\nSpaceToBatch. More specifically, this op outputs a copy of the input\ntensor where values from the batch dimension are moved in spatial\nblocks to the height and width dimensions, followed by cropping along\nthe height and width dimensions.\n\n)DOC\");\n\nclass GetSpaceToBatchGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"BatchToSpace\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n  }\n};\n\nclass GetBatchToSpaceGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SpaceToBatch\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(SpaceToBatch, GetSpaceToBatchGradient);\nREGISTER_GRADIENT(BatchToSpace, GetBatchToSpaceGradient);\n}\n"
  },
  {
    "path": "caffe2/operators/space_batch_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SPACE_BATCH_OP_H_\n#define CAFFE2_OPERATORS_SPACE_BATCH_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename Context>\nvoid spaceToBatch(\n    const Tensor<Context>& input,\n    int pad_t,\n    int pad_l,\n    int block_size,\n    Tensor<Context>* output,\n    Context* /*context*/) {\n  CAFFE_ENFORCE(input.ndim() == 4);\n  CAFFE_ENFORCE(output->ndim() == 4);\n\n  const int output_batch = output->dim32(0);\n  const int output_depth = output->dim32(1);\n  const int output_height = output->dim32(2);\n  const int output_width = output->dim32(3);\n\n  const int input_batch = input.dim32(0);\n  const int input_depth = input.dim32(1);\n  const int input_height = input.dim32(2);\n  const int input_width = input.dim32(3);\n\n  for (int out_b = 0; out_b < output_batch; ++out_b) {\n    const int in_b = out_b % input_batch;\n    const int offset_w = (out_b / input_batch) % block_size;\n    const int offset_h = (out_b / input_batch) / block_size;\n    for (int d = 0; d < input_depth; ++d) {\n      for (int out_h = 0; out_h < output_height; ++out_h) {\n        const int in_h = out_h * block_size + offset_h - pad_t;\n        for (int out_w = 0; out_w < output_width; ++out_w) {\n          const int in_w = out_w * block_size + offset_w - pad_l;\n          const auto output_offset =\n              ((out_b * output_depth + d) * output_height + out_h) *\n                  output_width +\n              out_w;\n          const auto input_offset =\n              ((in_b * input_depth + d) * input_height + in_h) * input_width +\n              in_w;\n          if (in_h >= 0 && in_w >= 0 && in_h < input_height &&\n              in_w < input_width) {\n            output->template mutable_data<float>()[output_offset] =\n                input.template data<float>()[input_offset];\n          } else {\n            output->template mutable_data<float>()[output_offset] = 0.0;\n          }\n        }\n      }\n    }\n  }\n}\n\ntemplate <typename Context>\nvoid batchToSpace(\n    const Tensor<Context>& input,\n    int pad_t,\n    int pad_l,\n    int block_size,\n    Tensor<Context>* output,\n    Context* /*context*/) {\n  CAFFE_ENFORCE(input.ndim() == 4);\n  CAFFE_ENFORCE(output->ndim() == 4);\n\n  const int output_batch = output->dim32(0);\n  const int output_depth = output->dim32(1);\n  const int output_height = output->dim32(2);\n  const int output_width = output->dim32(3);\n\n  const int input_batch = input.dim32(0);\n  const int input_depth = input.dim32(1);\n  const int input_height = input.dim32(2);\n  const int input_width = input.dim32(3);\n\n  CAFFE_ENFORCE(input_depth == output_depth);\n  for (int in_b = 0; in_b < input_batch; ++in_b) {\n    const int out_b = in_b % output_batch;\n    const int offset_w = (in_b / output_batch) % block_size;\n    const int offset_h = (in_b / output_batch) / block_size;\n    for (int d = 0; d < input_depth; ++d) {\n      for (int in_h = 0; in_h < input_height; ++in_h) {\n        const int out_h = in_h * block_size + offset_h - pad_t;\n        for (int in_w = 0; in_w < input_width; ++in_w) {\n          const int out_w = in_w * block_size + offset_w - pad_l;\n          if (out_h >= 0 && out_w >= 0 && out_h < output_height &&\n              out_w < output_width) {\n            const auto output_offset =\n                ((out_b * output_depth + d) * output_height + out_h) *\n                    output_width +\n                out_w;\n            const auto input_offset =\n                ((in_b * input_depth + d) * input_height + in_h) * input_width +\n                in_w;\n            output->template mutable_data<float>()[output_offset] =\n                input.template data<float>()[input_offset];\n          }\n        }\n      }\n    }\n  }\n}\n\ntemplate <typename Context>\nclass SpaceBatchOpBase : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SpaceBatchOpBase(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        pad_(OperatorBase::GetSingleArgument<int>(\"pad\", 0)),\n        pad_t_(OperatorBase::GetSingleArgument<int>(\"pad_t\", pad_)),\n        pad_l_(OperatorBase::GetSingleArgument<int>(\"pad\", pad_)),\n        pad_b_(OperatorBase::GetSingleArgument<int>(\"pad\", pad_)),\n        pad_r_(OperatorBase::GetSingleArgument<int>(\"pad\", pad_)),\n        block_size_(OperatorBase::GetSingleArgument<int>(\"block_size\", 2)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CAFFE_ENFORCE(order_ == StorageOrder::NCHW);\n  }\n\n protected:\n  int pad_;\n  int pad_t_;\n  int pad_l_;\n  int pad_b_;\n  int pad_r_;\n  int block_size_;\n  StorageOrder order_;\n};\n\ntemplate <typename Context>\nclass SpaceToBatchOp final : public SpaceBatchOpBase<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using SpaceBatchOpBase<Context>::SpaceBatchOpBase;\n\n  bool RunOnDevice() override {\n    const auto& input = Input(0);\n    auto* output = Output(0);\n    const int batch = input.dim32(0);\n    const int depth = input.dim32(1);\n    const int height = this->pad_b_ + this->pad_t_ + input.dim32(2);\n    const int width = this->pad_l_ + this->pad_r_ + input.dim32(3);\n    CAFFE_ENFORCE(\n        height % this->block_size_ == 0,\n        \"Height: \",\n        height,\n        \", block size: \",\n        this->block_size_);\n    CAFFE_ENFORCE(width % this->block_size_ == 0);\n\n    const int output_batch = batch * this->block_size_ * this->block_size_;\n    const int output_height = height / this->block_size_;\n    const int output_width = width / this->block_size_;\n    Output(0)->Resize(output_batch, depth, output_height, output_width);\n\n    spaceToBatch<Context>(\n        input,\n        this->pad_t_,\n        this->pad_l_,\n        this->block_size_,\n        output,\n        &context_);\n\n    return true;\n  }\n};\n\ntemplate <typename Context>\nclass BatchToSpaceOp final : public SpaceBatchOpBase<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using SpaceBatchOpBase<Context>::SpaceBatchOpBase;\n\n  bool RunOnDevice() override {\n    const auto& input = Input(0);\n    auto* output = Output(0);\n    const int batch = input.dim32(0);\n    const int depth = input.dim32(1);\n    const int height = input.dim32(2);\n    const int width = input.dim32(3);\n\n    const int output_batch = batch / this->block_size_ / this->block_size_;\n    const int output_height =\n        height * this->block_size_ - this->pad_b_ - this->pad_t_;\n    const int output_width =\n        width * this->block_size_ - this->pad_l_ - this->pad_r_;\n    Output(0)->Resize(output_batch, depth, output_height, output_width);\n    batchToSpace<Context>(\n        input,\n        this->pad_t_,\n        this->pad_l_,\n        this->block_size_,\n        output,\n        &context_);\n    return true;\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SPACE_BATCH_OP_H_\n"
  },
  {
    "path": "caffe2/operators/space_batch_op_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/space_batch_op.h\"\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\n__global__ void SpaceToBatch(\n    int N,\n    int output_batch,\n    int output_depth,\n    int output_height,\n    int output_width,\n    int input_batch,\n    int input_depth,\n    int input_height,\n    int input_width,\n    const int pad_l,\n    const int pad_t,\n    int block_size,\n    const float* input,\n    float* output) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    // Recall:\n    // const auto output_offset =\n    //     ((out_b * output_depth + d) * output_height + out_h) * output_width +\n    //     out_w;\n    const int out_w = i % output_width;\n    const int i_2 = i / output_width;\n    const int out_h = i_2 % output_height;\n    const int i_3 = i_2 / output_height;\n    const int d = i_3 % output_depth;\n    const int out_b = i_3 / output_depth;\n\n    const int in_b = out_b % input_batch;\n    const int offset_w = (out_b / input_batch) % block_size;\n    const int offset_h = (out_b / input_batch) / block_size;\n    const int in_h = out_h * block_size + offset_h - pad_t;\n    const int in_w = out_w * block_size + offset_w - pad_l;\n\n    if (in_h >= 0 && in_w >= 0 && in_h < input_height && in_w < input_width) {\n      const auto input_offset =\n          ((in_b * input_depth + d) * input_height + in_h) * input_width +\n          in_w;\n      output[i] = input[input_offset];\n    } else {\n      output[i] = 0.0;\n    }\n  }\n}\n\ntemplate<>\nvoid spaceToBatch<CUDAContext>(\n    const Tensor<CUDAContext>& input,\n    int pad_t,\n    int pad_l,\n    int block_size,\n    Tensor<CUDAContext>* output,\n    CUDAContext* context) {\n  const int output_batch = output->dim32(0);\n  const int output_depth = output->dim32(1);\n  const int output_height = output->dim32(2);\n  const int output_width = output->dim32(3);\n\n  const int input_batch = input.dim32(0);\n  const int input_depth = input.dim32(1);\n  const int input_height = input.dim32(2);\n  const int input_width = input.dim32(3);\n  const int N = output->size();\n  SpaceToBatch<<<\n      CAFFE_GET_BLOCKS(N),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(\n      N,\n      output_batch,\n      output_depth,\n      output_height,\n      output_width,\n      input_batch,\n      input_depth,\n      input_height,\n      input_width,\n      pad_l,\n      pad_t,\n      block_size,\n      input.data<float>(),\n      output->mutable_data<float>());\n}\n\n\n__global__ void BatchToSpace(\n    int N,\n    int output_batch,\n    int output_depth,\n    int output_height,\n    int output_width,\n    int input_batch,\n    int input_depth,\n    int input_height,\n    int input_width,\n    const int pad_l,\n    const int pad_t,\n    int block_size,\n    const float* input,\n    float* output) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    // Recall:\n    // const auto input_offset = ((in_b * input_depth + d) *\n    //   input_height + in_h) * input_width + in_w;\n    const int in_w = i  % input_width;\n    const int i_2 = i / input_width;\n    const int in_h = i_2 % input_height;\n    const int i_3 = i_2 / input_height;\n    const int d = i_3 % input_depth;\n    const int in_b = i_3 / input_depth;\n\n    const int out_b = in_b % output_batch;\n    const int offset_w = (in_b / output_batch) % block_size;\n    const int offset_h = (in_b / output_batch) / block_size;\n    const int out_h = in_h * block_size + offset_h - pad_t;\n    const int out_w = in_w * block_size + offset_w - pad_l;\n\n    if (out_h >= 0 && out_w >= 0 && out_h < output_height &&\n        out_w < output_width) {\n      const auto output_offset =\n          ((out_b * output_depth + d) * output_height + out_h) *\n          output_width +\n          out_w;\n      output[output_offset] = input[i];\n    }\n  }\n}\n\ntemplate <>\nvoid batchToSpace(\n    const Tensor<CUDAContext>& input,\n    int pad_t,\n    int pad_l,\n    int block_size,\n    Tensor<CUDAContext>* output,\n    CUDAContext* context) {\n  CAFFE_ENFORCE(input.ndim() == 4);\n  CAFFE_ENFORCE(output->ndim() == 4);\n\n  const int output_batch = output->dim32(0);\n  const int output_depth = output->dim32(1);\n  const int output_height = output->dim32(2);\n  const int output_width = output->dim32(3);\n\n  const int input_batch = input.dim32(0);\n  const int input_depth = input.dim32(1);\n  const int input_height = input.dim32(2);\n  const int input_width = input.dim32(3);\n  const int N = input.size();\n  BatchToSpace<<<\n      CAFFE_GET_BLOCKS(N),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(\n      N,\n      output_batch,\n      output_depth,\n      output_height,\n      output_width,\n      input_batch,\n      input_depth,\n      input_height,\n      input_width,\n      pad_l,\n      pad_t,\n      block_size,\n      input.data<float>(),\n      output->mutable_data<float>());\n}\n\nREGISTER_CUDA_OPERATOR(SpaceToBatch, SpaceToBatchOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(BatchToSpace, BatchToSpaceOp<CUDAContext>);\n\n}\n"
  },
  {
    "path": "caffe2/operators/sparse_normalize_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/sparse_normalize_op.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\ntemplate <>\ntemplate <typename SIndex>\nbool SparseNormalizeOp<float, CPUContext>::DoRunWithType() {\n  const auto* indices = Input(INDICES).template data<SIndex>();\n  const auto* paramIn = Input(PARAM).template data<float>();\n  auto* paramOut = Output(OUTPUT_PARAM)->template mutable_data<float>();\n  const float kEps = 1e-12f;\n\n  // n: number of sparse embeddings to be normalized\n  auto n = Input(INDICES).size();\n  if (n == 0) {\n    return true;\n  }\n\n  // embedding length, e.g. 32, 64, 128\n  auto block_size = Input(GRAD).size() / n;\n  for (int i = 0; i < n; ++i) {\n    auto idx = indices[i];\n    auto offsetIdx = idx * block_size;\n    ConstEigenVectorMap<float> xVec(paramIn + offsetIdx, block_size);\n    auto norm = xVec.template lpNorm<2>() + kEps;\n\n    if (use_max_norm_ && norm < norm_) {\n      continue;\n    }\n\n    for (int j = 0; j < block_size; j++) {\n      paramOut[offsetIdx + j] = paramOut[offsetIdx + j] * (norm_ / norm);\n    }\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(SparseNormalize, SparseNormalizeOp<float, CPUContext>);\nOPERATOR_SCHEMA(SparseNormalize)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .Input(0, \"param\", \"Parameters to be normalized\")\n    .Input(1, \"indices\", \"Sparse indices\")\n    .Input(2, \"grad\", \"Gradient computed\")\n    .Output(0, \"output_param\", \"Normalized parameters\")\n    .EnforceOneToOneInplace()\n    .Arg(\n        \"use_max_norm\",\n        \"A bool variable to control whether to use max norm \\\n    or constant norm. When use_max_norm = false, constant norm is used so that \\\n    all the embedding vectors are scaled to have a L2 norm equals to A \\\n    (see blow arugment norm=A). If use_max_norm = true, \\\n    max norm is used so that embedding is scaled so that its l2 norm is no larger \\\n    than A. If an embedding's norm is less than A originally, \\\n    the embedding is left unchanged.\\\n    The default is True.\")\n    .Arg(\"norm\", \"L2 norm of the embedding. The default is 1.0.\")\n    .SetDoc(R\"DOC(\nGiven a sparse matrix, apply max_norm or constant_norm sparse regularization.\n)DOC\");\n\nSHOULD_NOT_DO_GRADIENT(SparseNormalize);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/sparse_normalize_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SparseNormalizeOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SparseNormalizeOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        use_max_norm_(\n            OperatorBase::GetSingleArgument<bool>(\"use_max_norm\", true)),\n        norm_(OperatorBase::GetSingleArgument<float>(\"norm\", 1.0)) {\n    CAFFE_ENFORCE_GE(norm_, 0, \"norm should be bigger than 0\");\n  }\n\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE_EQ(\n        Input(PARAM).size_from_dim(1),\n        Input(GRAD).size_from_dim(Input(INDICES).ndim()));\n\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename SIndex>\n  bool DoRunWithType();\n\n protected:\n  bool use_max_norm_;\n  float norm_;\n  INPUT_TAGS(PARAM, INDICES, GRAD);\n  OUTPUT_TAGS(OUTPUT_PARAM);\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/sparse_to_dense_mask_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/sparse_to_dense_mask_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(SparseToDenseMask, SparseToDenseMaskOp<CPUContext>);\nREGISTER_CPU_OPERATOR(\n    SparseToDenseMaskGradient,\n    SparseToDenseMaskGradientOp<CPUContext>);\n\nOPERATOR_SCHEMA(SparseToDenseMask)\n    .NumInputs(3, 4)\n    .NumOutputs(1, 2)\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      ArgumentHelper helper(def);\n      auto mask = helper.template GetRepeatedArgument<int64_t>(\"mask\");\n      bool return_presence_mask = helper.template GetSingleArgument<bool>(\n          \"return_presence_mask\", false);\n      vector<TensorShape> out(1);\n\n      if (in.size() == 4) {\n        out[0].add_dims(in[3].dims(0));\n      }\n      out[0].add_dims(mask.size());\n      for (const auto dim : in[2].dims()) {\n        out[0].add_dims(dim);\n      }\n      out[0].set_data_type(in[2].data_type());\n\n      if (return_presence_mask) {\n        out.emplace_back();\n        if (in.size() == 4) {\n          out[1].add_dims(in[3].dims(0));\n        }\n        out[1].add_dims(mask.size());\n        out[1].set_data_type(TensorProto::BOOL);\n      }\n\n      return out;\n    })\n    .SetDoc(R\"DOC(\nConvert sparse representations to dense with given indices.\n\nTransforms a sparse representation of map<id, value> represented as `indices`\nvector and `values` tensor into a compacted tensor where the first dimension\ncorresponds to each id provided in mask argument. Missing values are filled with\nthe value of `default_value`. After running this op:\n\n  output[j, :] = values[i] # where mask[j] == indices[i]\n  output[j, ...] = default_value # when mask[j] doesn't appear in indices\n\nIf `lengths` is provided and not empty, and extra \"batch\" dimension is prepended\nto the output.\n\n`values` and `default_value` can have additional matching dimensions, operation\nis performed on the entire subtensor in thise case.\n\nFor example, if `lengths` is supplied and `values` is 1-D vector of floats and\n`default_value` is a float scalar, the output is going to be a float matrix\nof size `len(lengths) X len(mask)`\n)DOC\")\n    .Arg(\n        \"mask\",\n        \"list(int) argument with desired ids on the 'dense' output dimension\")\n    .Arg(\n        \"return_presence_mask\",\n        \"bool whether to return presence mask, false by default\")\n    .Input(0, \"indices\", \"1-D int32/int64 tensor of concatenated ids of data\")\n    .Input(1, \"values\", \"Data tensor, first dimension has to match `indices`\")\n    .Input(\n        2,\n        \"default_value\",\n        \"Default value for the output if the id is not present in `indices`. \"\n        \"Must have the same type as `values` and the same shape, but without \"\n        \"the first dimension\")\n    .Input(\n        3,\n        \"lengths\",\n        \"Optional lengths to represent a batch of `indices` and `values`.\")\n    .Output(\n        0,\n        \"output\",\n        \"Output tensor of the same type as `values` of shape `[len(lengths), \"\n        \"len(mask)] + shape(default_value)` (if `lengths` is not provided the \"\n        \"first dimension is omitted)\")\n    .Output(\n        1,\n        \"presence_mask\",\n        \"Bool tensor of shape `[len(lengths), len(mask)]` (if `lengths` is not \"\n        \"provided the first dimension is omitted). True when a value for given \"\n        \"id was present, false otherwise.\");\n\nOPERATOR_SCHEMA(SparseToDenseMaskGradient)\n    .NumInputs(2, 3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nThe output is the gradient of the input value from SparseToDenseMask. The\ngradient for default_value has not been implemented.\n)DOC\");\n\nclass GetSparseToDenseMaskGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> blob_names{I(0), GO(0)};\n\n    // Add lengths blob if given\n    if (def_.input_size() == 4) {\n      blob_names.push_back(I(3));\n    }\n    return SingleGradientDef(\n        \"SparseToDenseMaskGradient\", \"\", blob_names, vector<string>{GI(1)});\n  }\n};\nREGISTER_GRADIENT(SparseToDenseMask, GetSparseToDenseMaskGradient);\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/sparse_to_dense_mask_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SPARSE_TO_DENSE_MASK_OP_H_\n#define CAFFE2_OPERATORS_SPARSE_TO_DENSE_MASK_OP_H_\n\n#include <algorithm>\n#include <unordered_map>\n#include <vector>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass SparseToDenseMaskBase : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SparseToDenseMaskBase(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    std::vector<int64_t> mask =\n        OperatorBase::template GetRepeatedArgument<int64_t>(\"mask\");\n    featuresCount_ = mask.size();\n\n    CAFFE_ENFORCE(!mask.empty(), \"mask can't be empty\");\n    auto biggest = *std::max_element(mask.begin(), mask.end());\n    dense_.assign(std::min(kMaxDenseSize, biggest + 1), -1);\n    for (int i = 0; i < mask.size(); i++) {\n      int64_t id = mask[i];\n      CAFFE_ENFORCE_GE(id, 0, \"Only positive IDs are allowed.\");\n      if (id >= kMaxDenseSize) {\n        CAFFE_ENFORCE(sparse_.count(id) == 0, \"Duplicated id: \", id);\n        sparse_[id] = i;\n      } else {\n        CAFFE_ENFORCE(dense_[id] == -1, \"Duplicated id: \", id);\n        dense_[id] = i;\n      }\n    }\n  }\n\n protected:\n  const int64_t kMaxDenseSize = 1024 * 128;\n\n  std::unordered_map<int64_t, int> sparse_;\n  std::vector<int> dense_;\n  int featuresCount_;\n\n  inline int getFeatureIdx(int64_t id) const {\n    if (id >= kMaxDenseSize) {\n      const auto& iter = sparse_.find(id);\n      if (iter == sparse_.end()) {\n        return -1;\n      } else {\n        return iter->second;\n      }\n    } else {\n      return (id >= dense_.size()) ? -1 : dense_[id];\n    }\n  }\n};\n\ntemplate <class Context>\nclass SparseToDenseMaskOp : public SparseToDenseMaskBase<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SparseToDenseMaskOp(const OperatorDef& operator_def, Workspace* ws)\n      : SparseToDenseMaskBase<Context>(operator_def, ws) {\n    returnPresenceMask_ = OperatorBase::template GetSingleArgument<bool>(\n        \"return_presence_mask\", false);\n    maxSkippedSparseIndices_ =\n        OperatorBase::template GetSingleArgument<int32_t>(\n            \"max_skipped_indices\", kMaxSkippedSparseIndices);\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename TInd>\n  bool DoRunWithType() {\n    auto& sparse_indices = Input(INDICES);\n    CAFFE_ENFORCE_EQ(sparse_indices.ndim(), 1);\n    auto& sparse_values = Input(VALUES);\n    CAFFE_ENFORCE_GE(sparse_values.ndim(), 1);\n    CAFFE_ENFORCE_EQ(sparse_indices.size(), sparse_values.dim(0));\n    auto& default_value = Input(DEFAULT);\n    CAFFE_ENFORCE_EQ(default_value.ndim() + 1, sparse_values.ndim());\n    CAFFE_ENFORCE_EQ(default_value.size(), sparse_values.size_from_dim(1));\n    CAFFE_ENFORCE(sparse_values.meta() == default_value.meta());\n\n    const TInd* sparse_indices_vec = sparse_indices.template data<TInd>();\n    const char* sparse_values_vec =\n        static_cast<const char*>(sparse_values.raw_data());\n    const void* default_val = default_value.raw_data();\n\n    TIndex block_size = default_value.size();\n    size_t block_nbytes = default_value.nbytes();\n\n    const int cols = this->featuresCount_;\n    int rows = -1;\n    int32_t sparse_indices_length = sparse_indices.dim32(0);\n    const int32_t* lengths_vec = nullptr;\n    auto* output = Output(OUTPUTVALUE);\n    Tensor<Context>* presence_mask = nullptr;\n    if (returnPresenceMask_) {\n      presence_mask = Output(PRESENCEMASK);\n    }\n    vector<TIndex> shape;\n    if (InputSize() == 4) {\n      auto& lengths = Input(LENGTHS);\n      CAFFE_ENFORCE_EQ(lengths.ndim(), 1);\n      lengths_vec = lengths.template data<int32_t>();\n      rows = lengths.dim32(0);\n    }\n    if (rows == -1) {\n      // if the LENGTHS is not set, the output will be a vector\n      rows = 1;\n      lengths_vec = &sparse_indices_length;\n    } else {\n      shape.push_back(rows);\n    }\n    shape.push_back(cols);\n    if (returnPresenceMask_) {\n      presence_mask->Resize(shape);\n    }\n    shape.insert(\n        shape.end(), default_value.dims().begin(), default_value.dims().end());\n    output->Resize(shape);\n\n    // init\n    // TODO: consider unrolling CopyItems to make elemental types copy faster\n    char* output_data =\n        static_cast<char*>(output->raw_mutable_data(sparse_values.meta()));\n    for (int i = 0; i < cols * rows; i++) {\n      context_.template CopyItems<Context, Context>(\n          default_value.meta(),\n          block_size,\n          default_val,\n          output_data + i * block_nbytes);\n    }\n    bool* presence_mask_data = nullptr;\n    if (returnPresenceMask_) {\n      presence_mask_data = presence_mask->template mutable_data<bool>();\n      math::Set<bool, Context>(\n          rows * cols, false, presence_mask_data, &context_);\n    }\n\n    int64_t offset = 0;\n    for (int r = 0; r < rows; r++) {\n      for (int c = 0; c < lengths_vec[r]; c++) {\n        const auto sparse_index = sparse_indices_vec[offset + c];\n        if (sparse_index < 0 ||\n            sparse_index >= std::numeric_limits<TInd>::max()) {\n          CAFFE_ENFORCE_LT(\n              ++skippedSparseIndices_,\n              maxSkippedSparseIndices_,\n              \"Too many sparse indices skipped\");\n          continue;\n        }\n        int idx = this->getFeatureIdx(sparse_index);\n        if (idx != -1) {\n          context_.template CopyItems<Context, Context>(\n              sparse_values.meta(),\n              block_size,\n              sparse_values_vec + (offset + c) * block_nbytes,\n              output_data + (r * cols + idx) * block_nbytes);\n          if (returnPresenceMask_) {\n            presence_mask_data[r * cols + idx] = true;\n          }\n        }\n      }\n      offset += lengths_vec[r];\n    }\n\n    return true;\n  }\n\n private:\n  static const uint32_t kMaxSkippedSparseIndices = 5;\n\n  bool returnPresenceMask_;\n  uint32_t maxSkippedSparseIndices_ = 0;\n  uint32_t skippedSparseIndices_ = 0;\n\n  INPUT_TAGS(INDICES, VALUES, DEFAULT, LENGTHS);\n  OUTPUT_TAGS(OUTPUTVALUE, PRESENCEMASK);\n};\n\ntemplate <class Context>\nclass SparseToDenseMaskGradientOp : public SparseToDenseMaskBase<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SparseToDenseMaskGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : SparseToDenseMaskBase<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename TInd>\n  bool DoRunWithType() {\n    auto& sparse_indices = Input(INDICES);\n    CAFFE_ENFORCE_EQ(sparse_indices.ndim(), 1);\n    auto& gradient_output = Input(GOUTPUT);\n\n    TIndex block_size = gradient_output.size_from_dim(1);\n    size_t block_nbytes = gradient_output.itemsize() * block_size;\n\n    const int cols = this->featuresCount_;\n    int rows = -1;\n    int iter_offset = 1;\n    int32_t default_length = sparse_indices.dim32(0);\n    const int32_t* lengths_vec = nullptr;\n    auto* output = Output(GVALUES);\n    vector<TIndex> shape;\n    if (InputSize() > LENGTHS) {\n      // if the LENGTHS is set, the gradient_output has dim:\n      // lengths * mask.size() * feature_dim\n      auto& lengths = Input(LENGTHS);\n      lengths_vec = lengths.template data<int32_t>();\n      rows = lengths.dim32(0);\n      CAFFE_ENFORCE_EQ(lengths.ndim(), 1);\n      CAFFE_ENFORCE_GE(gradient_output.ndim(), 2);\n      CAFFE_ENFORCE_EQ(gradient_output.dim(0), rows);\n      CAFFE_ENFORCE_EQ(gradient_output.dim(1), cols);\n      block_nbytes /= gradient_output.dim(1);\n      block_size /= gradient_output.dim(1);\n      iter_offset += 1;\n    }\n    if (rows == -1) {\n      // if the LENGTHS is not set, the gradient_output has dim:\n      // mask.size() * feature_dim\n      rows = 1;\n      lengths_vec = &default_length;\n      CAFFE_ENFORCE_GE(gradient_output.ndim(), 1);\n      CAFFE_ENFORCE_EQ(gradient_output.dim(0), cols);\n    }\n    shape.push_back(default_length);\n    // insert feature_dim\n    shape.insert(\n        shape.end(),\n        gradient_output.dims().begin() + iter_offset,\n        gradient_output.dims().end());\n    output->Resize(shape);\n\n    const TInd* sparse_indices_vec = sparse_indices.template data<TInd>();\n    const char* gradient_output_vec =\n        static_cast<const char*>(gradient_output.raw_data());\n\n    char* output_data =\n        static_cast<char*>(output->raw_mutable_data(gradient_output.meta()));\n    math::Set<char, Context>(\n        default_length * gradient_output.itemsize(), 0, output_data, &context_);\n\n    int32_t offset = 0;\n    // SparseToDenseMask is not injective; gradient_used records\n    // if the gradient is used for other input value from the same row\n    vector<bool> gradient_used(cols, false);\n    for (int r = 0; r < rows; r++) {\n      std::fill(gradient_used.begin(), gradient_used.end(), false);\n      for (int c = lengths_vec[r] - 1; c >= 0; c--) {\n        int idx = this->getFeatureIdx(sparse_indices_vec[offset + c]);\n        if (idx != -1 && !gradient_used[idx]) {\n          gradient_used[idx] = true;\n          context_.template CopyItems<Context, Context>(\n              gradient_output.meta(),\n              block_size,\n              gradient_output_vec + (r * cols + idx) * block_nbytes,\n              output_data + (offset + c) * block_nbytes);\n        }\n      }\n      offset += lengths_vec[r];\n    }\n    return true;\n  }\n\n private:\n  INPUT_TAGS(INDICES, GOUTPUT, LENGTHS);\n  OUTPUT_TAGS(GVALUES);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SPARSE_TO_DENSE_MASK_OP_H_\n"
  },
  {
    "path": "caffe2/operators/sparse_to_dense_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"sparse_to_dense_op.h\"\n\n#include \"caffe2/core/context.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(SparseToDense, SparseToDenseOp<CPUContext>);\n\nOPERATOR_SCHEMA(SparseToDense)\n    .NumInputs(2, 3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nConvert sparse representations to dense with given indices.\n\nTransforms a sparse representation of map<id, value> represented as `indices`\nvector and `values` tensor into a compacted tensor where the first dimension\nis determined by the first dimension of the 3rd input if it is given or the\nmax index. Missing values are filled with zeros.\n\nThe op supports duplicated indices and performs summation over corresponding\nvalues. This behavior is useful for converting GradientSlices into dense\nrepresentation.\n\nAfter running this op:\n\n  output[indices[i], :] += values[i]  # sum over all indices[i] equal to the index\n  output[j, ...] = 0 if j not in indices\n)DOC\")\n    .Input(0, \"indices\", \"1-D int32/int64 tensor of concatenated ids of data\")\n    .Input(\n        1,\n        \"values\",\n        \"Data tensor, first dimension has to match `indices`, \"\n        \"basic numeric types are supported\")\n    .Input(\n        2,\n        \"data_to_infer_dim\",\n        \"Optional: if provided, the first dimension of output is the first \"\n        \"dimension of this tensor.\")\n    .Output(\n        0,\n        \"output\",\n        \"Output tensor of the same type as `values` of shape `[len(lengths), \"\n        \"len(mask)] + shape(default_value)` (if `lengths` is not provided the \"\n        \"first dimension is omitted)\");\n\n\nnamespace {\nclass GetSparseToDenseGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"Gather\", \"\", vector<string>{GO(0), I(0)}, vector<string>{GI(1)});\n  }\n};\n\nREGISTER_GRADIENT(SparseToDense, GetSparseToDenseGradient);\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/sparse_to_dense_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"sparse_to_dense_op.h\"\n\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\n  template <typename TInd, typename TData>\n  __global__ void SparseToDenseKernel(\n    size_t N, TIndex block_nitems, const TInd* indices, const TData* vals, TData* dst) {\n    CUDA_1D_KERNEL_LOOP(i, N) {\n      int idx = indices[i / block_nitems];\n      int dst_idx = block_nitems * idx + i % block_nitems;\n      atomicAdd(&dst[dst_idx], vals[i]);\n    }\n  }\n\n  template <>\n  bool SparseToDenseOp<CUDAContext>::RunOnDevice() {\n    return DispatchHelper<TensorTypes<int32_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <>\n  template <typename TInd>\n  bool SparseToDenseOp<CUDAContext>::DoRunWithType() {\n    return DispatchHelper<\n        TensorTypes2<\n            float,\n            int32_t>,\n        TInd>::call(this, Input(VALUES));\n  }\n\n  template <>\n  template <typename TInd, typename TData>\n  bool SparseToDenseOp<CUDAContext>::DoRunWithType2() {\n    auto& sparse_indices = Input(INDICES);\n    CAFFE_ENFORCE_EQ(sparse_indices.ndim(), 1);\n    auto& sparse_values = Input(VALUES);\n    CAFFE_ENFORCE_GE(sparse_values.ndim(), 1);\n    CAFFE_ENFORCE_EQ(sparse_indices.size(), sparse_values.dim(0));\n\n    const TInd* sparse_indices_vec = sparse_indices.template data<TInd>();\n    const int32_t sparse_indices_len = sparse_indices.dim32(0);\n    const int output_first_dim =\n        GetOutputFirstDim(sparse_indices_vec, sparse_indices_len);\n\n    auto shape = sparse_values.dims();\n    shape[0] = output_first_dim;\n    auto* output = Output(0);\n    output->Resize(shape);\n\n    TData* output_data = output->template mutable_data<TData>();\n    math::Set<TData>(output->size(), TData(0), output_data, &context_);\n\n    const auto block_nitems = sparse_values.size_from_dim(1);\n    const TData* sparse_values_vec = sparse_values.template data<TData>();\n\n    size_t N = block_nitems * sparse_indices_len;\n    CAFFE_ENFORCE_EQ(output->size(), output_first_dim * block_nitems);\n    SparseToDenseKernel<TInd, TData><<<\n      CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS, 0,\n      context_.cuda_stream()>>>(\n        N,\n        block_nitems,\n        sparse_indices_vec,\n        sparse_values_vec,\n        output_data\n    );\n\n    return true;\n  }\n\n\nREGISTER_CUDA_OPERATOR(SparseToDense, SparseToDenseOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/sparse_to_dense_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SPARSE_TO_DENSE_OP_H_\n#define CAFFE2_OPERATORS_SPARSE_TO_DENSE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass SparseToDenseOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_DISPATCH_HELPER;\n\n  SparseToDenseOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        output_first_dim_(\n            OperatorBase::GetSingleArgument<int>(\"output_first_dim\", 0)) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n private:\n  template <typename TInd>\n  int GetOutputFirstDim(\n      const TInd* sparse_indices_vec,\n      const int32_t sparse_indices_len) {\n    if (output_first_dim_ > 0) {\n      CAFFE_ENFORCE_EQ(InputSize(), 2);\n      return output_first_dim_;\n    }\n    if (InputSize() == 3) {\n      auto& data_to_infer_dim = Input(DATA_TO_INFER_DIM);\n      CAFFE_ENFORCE_GE(data_to_infer_dim.ndim(), 1);\n      return data_to_infer_dim.dim32(0);\n    }\n    if (sparse_indices_len <= 0) {\n      return 0;\n    }\n\n    // Awkward way to get the max element to make it work with both CUDA\n    // and CPU.\n    max_element_.Resize(1);\n    TInd* max_element_ptr = max_element_.template mutable_data<TInd>();\n    math::ReduceMax<TInd>(sparse_indices_len, sparse_indices_vec, max_element_ptr,\n          &scratch_, &context_);\n    max_element_host_.CopyFrom(max_element_);\n    return 1 + max_element_host_.template data<TInd>()[0];\n  }\n\n  template <typename TInd>\n  bool DoRunWithType() {\n    return DispatchHelper<\n        TensorTypes2<\n            float,\n            int32_t,\n            int64_t,\n            GenericTensorImplementation>,\n        TInd>::call(this, Input(VALUES));\n  }\n\n  template <typename TInd, typename TData>\n  bool DoRunWithType2() {\n    auto& sparse_indices = Input(INDICES);\n    CAFFE_ENFORCE_EQ(sparse_indices.ndim(), 1);\n    auto& sparse_values = Input(VALUES);\n    CAFFE_ENFORCE_GE(sparse_values.ndim(), 1);\n    CAFFE_ENFORCE_EQ(sparse_indices.size(), sparse_values.dim(0));\n\n    const TInd* sparse_indices_vec = sparse_indices.template data<TInd>();\n    const int32_t sparse_indices_len = sparse_indices.dim32(0);\n    const int output_first_dim =\n        GetOutputFirstDim(sparse_indices_vec, sparse_indices_len);\n\n    auto shape = sparse_values.dims();\n    shape[0] = output_first_dim;\n    auto* output = Output(0);\n    output->Resize(shape);\n\n    TData* output_data = output->template mutable_data<TData>();\n    memset(output_data, 0, output->nbytes());\n    const auto block_nitems = sparse_values.size_from_dim(1);\n    const TData* sparse_values_vec = sparse_values.template data<TData>();\n\n    for (int32_t i = 0; i < sparse_indices_len; i++) {\n      const TInd idx = sparse_indices_vec[i];\n      CAFFE_ENFORCE_GE(idx, 0);\n      CAFFE_ENFORCE_LT(idx, output_first_dim);\n      math::Add(\n          block_nitems,\n          output_data + idx * block_nitems,\n          sparse_values_vec + i * block_nitems,\n          output_data + idx * block_nitems,\n          &context_);\n    }\n    return true;\n  }\n\n  template <typename TInd>\n  bool DoRunWithOtherType2() {\n    CAFFE_THROW(\n        \"SparseToDense is not implemented on tensor of type \",\n        Input(VALUES).meta().name(),\n        \"Consider adding it a type in the list DispatchHelper or implementing \"\n        \"a generic version (which won't work for duplicated indices though)\");\n  }\n\n private:\n  int output_first_dim_;\n  Tensor<Context> scratch_;\n  Tensor<CPUContext> max_element_host_;\n  Tensor<Context> max_element_;\n\n  INPUT_TAGS(INDICES, VALUES, DATA_TO_INFER_DIM);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SPARSE_TO_DENSE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/spatial_batch_norm_gradient_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/spatial_batch_norm_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool SpatialBNGradientOp<CPUContext>::RunOnDevice() {\n  const auto& X = Input(INPUT);\n  const auto& dY = Input(OUTPUT_GRAD);\n  const auto& scale = Input(SCALE);\n\n  CAFFE_ENFORCE(X.ndim() >= 3 && X.ndim() <= 5);\n  const int N = X.dim32(0);\n  const int C =\n      (order_ == StorageOrder::NCHW ? X.dim32(1) : X.dim32(X.ndim() - 1));\n  const int H = (order_ == StorageOrder::NCHW ? X.dim32(2) : X.dim32(1));\n  const int W = X.ndim() > 3\n      ? (order_ == StorageOrder::NCHW ? X.dim32(3) : X.dim32(2))\n      : 1;\n  const int D = X.ndim() > 4\n      ? (order_ == StorageOrder::NCHW ? X.dim32(4) : X.dim32(3))\n      : 1;\n\n  const int sample_size = H * W * D;\n\n  CAFFE_ENFORCE_EQ(scale.ndim(), 1);\n  CAFFE_ENFORCE_EQ(scale.dim32(0), C);\n\n  ConstEigenVectorArrayMap<float> scale_arr(scale.data<float>(), C);\n  ConstEigenVectorArrayMap<float> mean_arr(Input(SAVED_MEAN).data<float>(), C);\n  ConstEigenVectorArrayMap<float> inv_var_arr(\n      Input(SAVED_INV_VAR).data<float>(), C);\n\n  auto* dX = Output(INPUT_GRAD);\n  dX->ResizeLike(X);\n\n  auto* dScale = Output(SCALE_GRAD);\n  auto* dBias = Output(BIAS_GRAD);\n\n  if (num_batches_ == 1) {\n    dScale->ResizeLike(scale);\n    dBias->ResizeLike(scale);\n  }\n\n  // dBias = np.sum(dY, axis=0)\n  // dScale = np.sum((X - mean) / inv_std * dy, axis=0)\n  // dX = (1. / N) * scale * inv_var * (N * dY - np.sum(dY, axis=0) - (X - mean)\n  //   * inv_var * inv_var * np.sum(dY * (X - mean), axis=0))\n\n  EigenVectorArrayMap<float> dBias_arr(dBias->mutable_data<float>(), C);\n  EigenVectorArrayMap<float> dScale_arr(dScale->mutable_data<float>(), C);\n\n  if (num_batches_ == 1) {\n    dBias_arr.setZero();\n    dScale_arr.setZero();\n  }\n\n  const auto scaleInvVarNHW = scale_arr * inv_var_arr / (N * sample_size);\n\n  switch (order_) {\n    case StorageOrder::NCHW: {\n      ConstEigenArrayMap<float> X_arr(X.data<float>(), sample_size, N * C);\n      ConstEigenArrayMap<float> dY_arr(dY.data<float>(), sample_size, N * C);\n      EigenArrayMap<float> dX_arr(\n          dX->mutable_data<float>(), sample_size, N * C);\n      dX_arr.setZero();\n\n      if (num_batches_ == 1) {\n        for (int nc = 0; nc < N * C; ++nc) {\n          int c = nc % C;\n          dBias_arr(c) += dY_arr.col(nc).sum();\n          dScale_arr(c) +=\n              ((X_arr.col(nc) - mean_arr(c)) * inv_var_arr(c) * dY_arr.col(nc))\n                  .sum();\n        }\n      } else {\n        for (int c = 0; c < C; ++c) {\n          dBias_arr(c) /= num_batches_;\n          dScale_arr(c) /= num_batches_;\n        }\n      }\n      for (int nc = 0; nc < N * C; ++nc) {\n        int c = nc % C;\n        dX_arr.col(nc) += scaleInvVarNHW(c) *\n            (dY_arr.col(nc) * N * sample_size - dBias_arr(c) -\n             (X_arr.col(nc) - mean_arr[c]) * dScale_arr(c) * inv_var_arr(c));\n      }\n      break;\n    }\n    case StorageOrder::NHWC: {\n      ConstEigenArrayMap<float> X_arr(X.data<float>(), C, N * sample_size);\n      ConstEigenArrayMap<float> dY_arr(dY.data<float>(), C, N * sample_size);\n      EigenArrayMap<float> dX_arr(\n          dX->mutable_data<float>(), C, N * sample_size);\n      dX_arr.setZero();\n\n      const auto dYRowSum = dY_arr.rowwise().sum();\n      const auto XMinusMean = X_arr.colwise() - mean_arr;\n      const auto dYMulXMinusMeanRowSum = (dY_arr * XMinusMean).rowwise().sum();\n      const auto invVarSqr = inv_var_arr * inv_var_arr;\n      for (int nhw = 0; nhw < N * sample_size; ++nhw) {\n        dBias_arr += dY_arr.col(nhw);\n        dScale_arr +=\n            (X_arr.col(nhw) - mean_arr) * inv_var_arr * dY_arr.col(nhw);\n        dX_arr.col(nhw) += scaleInvVarNHW *\n            (dY_arr.col(nhw) * N * sample_size - dYRowSum -\n             XMinusMean.col(nhw) * invVarSqr * dYMulXMinusMeanRowSum);\n      }\n      break;\n    }\n    default:\n      CAFFE_THROW(\"Unknown storage order: \", order_);\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(SpatialBNGradient, SpatialBNGradientOp<CPUContext>);\n\n// Input: X, scale, dY, mean, variance, dscale, dbias\n// Output: dX, dscale, dbias\nOPERATOR_SCHEMA(SpatialBNGradient)\n    .NumInputs({5, 7})\n    .NumOutputs(3)\n    .AllowInplace({{5, 1}, {6, 2}});\n\n// Spatial batch normalization's gradient, depending on the various input sizes,\n// is a bit more complex than usual gradient operators.\nclass GetSpatialBNGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    // Check if we are in training or testing mode.\n    bool is_test =\n        ArgumentHelper::GetSingleArgument(def_, OpSchema::Arg_IsTest, 0);\n    int num_batches = ArgumentHelper::GetSingleArgument(def_, \"num_batches\", 1);\n    vector<string> grad_outputs{GI(0), GI(1), GI(2)};\n    vector<string> grad_inputs;\n    if (is_test) {\n      // This is in testing mode. The operator should have five inputs:\n      //     X, scale, bias, estimated_mean, estimated_variance\n      // The gradient inputs are:\n      //     X, scale, dY, estimated_mean, estimated_variance\n      CAFFE_ENFORCE_EQ(def_.input_size(), 5);\n      CAFFE_ENFORCE_EQ(def_.output_size(), 1);\n      grad_inputs = vector<string>{I(0), I(1), GO(0), I(3), I(4)};\n    } else if (num_batches > 1) {\n      CAFFE_ENFORCE_EQ(def_.input_size(), 7);\n      CAFFE_ENFORCE_EQ(def_.output_size(), 5);\n      grad_inputs = vector<string>{I(0), I(1), GO(0), O(3), O(4), GI(1), GI(2)};\n    } else {\n      CAFFE_ENFORCE_EQ(def_.input_size(), 5);\n      CAFFE_ENFORCE_EQ(def_.output_size(), 5);\n      grad_inputs = vector<string>{I(0), I(1), GO(0), O(3), O(4)};\n    }\n    return SingleGradientDef(\n        \"SpatialBNGradient\", \"\", grad_inputs, grad_outputs);\n  }\n};\nREGISTER_GRADIENT(SpatialBN, GetSpatialBNGradient);\n}\n"
  },
  {
    "path": "caffe2/operators/spatial_batch_norm_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/spatial_batch_norm_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool SpatialBNOp<CPUContext>::RunOnDevice() {\n  const auto& X = Input(INPUT);\n  const auto& scale = Input(SCALE);\n  const auto& bias = Input(BIAS);\n\n  CAFFE_ENFORCE(X.ndim() >= 3 && X.ndim() <= 5);\n  const int N = X.dim32(0);\n  const int C =\n      (order_ == StorageOrder::NCHW ? X.dim32(1) : X.dim32(X.ndim() - 1));\n  const int H = (order_ == StorageOrder::NCHW ? X.dim32(2) : X.dim32(1));\n  const int W = X.ndim() > 3\n      ? (order_ == StorageOrder::NCHW ? X.dim32(3) : X.dim32(2))\n      : 1;\n  const int D = X.ndim() > 4\n      ? (order_ == StorageOrder::NCHW ? X.dim32(4) : X.dim32(3))\n      : 1;\n\n  const int sample_size = H * W * D;\n  CAFFE_ENFORCE_EQ(scale.ndim(), 1);\n  CAFFE_ENFORCE_EQ(bias.ndim(), 1);\n  CAFFE_ENFORCE_EQ(scale.dim32(0), C);\n  CAFFE_ENFORCE_EQ(bias.dim32(0), C);\n\n  ConstEigenVectorArrayMap<float> scale_arr(scale.data<float>(), C);\n  ConstEigenVectorArrayMap<float> bias_arr(bias.data<float>(), C);\n\n  auto* Y = Output(OUTPUT);\n  Y->ResizeLike(X);\n\n  if (!is_test_) {\n    // training mode\n    // Get the mean and variance.\n    // Note that, to be consistent with cudnn, we will output saved inverse\n    // std as output 5, but we will still use the same storage place to\n    // compute var as well. The inverse is going to be carried out at the end\n    // of the op.\n    Output(SAVED_MEAN)->Resize(C);\n    Output(SAVED_INV_VAR)->Resize(C);\n    EigenVectorArrayMap<float> mean(\n        Output(SAVED_MEAN)->mutable_data<float>(), C);\n    EigenVectorArrayMap<float> var(\n        Output(SAVED_INV_VAR)->mutable_data<float>(), C);\n\n    if (num_batches_ > 1) {\n      ConstEigenVectorArrayMap<float> sums(Input(SUMS).data<float>(), C);\n      ConstEigenVectorArrayMap<float> sumsq(Input(SUMSQ).data<float>(), C);\n      const auto multi_batch_size = N * num_batches_ * sample_size;\n      mean = sums / multi_batch_size;\n      var = (sumsq - (sums * sums) / multi_batch_size) / multi_batch_size;\n    } else {\n      mean.setZero();\n      var.setZero();\n      switch (order_) {\n        case StorageOrder::NCHW: {\n          ConstEigenArrayMap<float> X_arr(X.data<float>(), sample_size, N * C);\n          for (int nc = 0; nc < N * C; ++nc) {\n            mean(nc % C) += X_arr.col(nc).sum();\n          }\n          mean /= N * sample_size;\n          for (int nc = 0; nc < N * C; ++nc) {\n            var(nc % C) +=\n                (X_arr.col(nc) - mean(nc % C)).matrix().squaredNorm();\n          }\n          var /= N * sample_size;\n          break;\n        }\n        case StorageOrder::NHWC: {\n          ConstEigenArrayMap<float> X_arr(X.data<float>(), C, N * sample_size);\n          for (int i = 0; i < N * sample_size; ++i) {\n            mean += X_arr.col(i);\n          }\n          mean /= N * sample_size;\n          for (int i = 0; i < N * sample_size; ++i) {\n            var += (X_arr.col(i) - mean) * (X_arr.col(i) - mean);\n          }\n          var /= N * sample_size;\n          break;\n        }\n        default:\n          CAFFE_THROW(\"Unknown storage order: \", order_);\n      }\n    }\n\n    // Compute the running mean and running inv variance.\n    auto* running_mean = Output(RUNNING_MEAN);\n    auto* running_var = Output(RUNNING_VAR);\n    // Check if they are initialized\n    if (!running_mean->size()) {\n      running_mean->Resize(C);\n      EigenVectorArrayMap<float> running_mean_map(\n          running_mean->mutable_data<float>(), C);\n      running_mean_map.setZero();\n    }\n    if (!running_var->size()) {\n      running_var->Resize(C);\n      EigenVectorArrayMap<float> running_var_map(\n          running_var->mutable_data<float>(), C);\n      running_var_map.setZero();\n    }\n    EigenVectorArrayMap<float> running_mean_arr(\n        running_mean->mutable_data<float>(), C);\n    EigenVectorArrayMap<float> running_var_arr(\n        running_var->mutable_data<float>(), C);\n    running_mean_arr = running_mean_arr * momentum_ + mean * (1. - momentum_);\n    running_var_arr = running_var_arr * momentum_ + var * (1. - momentum_);\n  }\n\n  // Regardless of training or testing, we will apply the estimated mean\n  // and standard deviation to the input. For testing, they are\n  // specified directly by the input, and for training, they are computed\n  // by the op.\n  Eigen::Array<float, Eigen::Dynamic, 1> inv_std(C);\n  if (is_test_) {\n    ConstEigenVectorArrayMap<float> var_arr(Input(EST_VAR).data<float>(), C);\n    inv_std = (var_arr + epsilon_).sqrt().inverse();\n  } else {\n    EigenVectorArrayMap<float> saved_inv_std(\n        Output(SAVED_INV_VAR)->mutable_data<float>(), C);\n    saved_inv_std = (saved_inv_std + epsilon_).inverse().sqrt();\n    inv_std = saved_inv_std;\n  }\n  ConstEigenVectorArrayMap<float> mean_arr(\n      is_test_ ? Input(EST_MEAN).data<float>()\n               : Output(SAVED_MEAN)->data<float>(),\n      C);\n  // We can fuse the output computation as follows:\n  //   ((x - est_mean) * (inv_var) * scale + bias\n  // to\n  //   (x * inv_var * scale) + (bias - est_mean * inv_var * scale)\n  Eigen::Array<float, Eigen::Dynamic, 1> new_scale = inv_std * scale_arr;\n  Eigen::Array<float, Eigen::Dynamic, 1> new_bias =\n      bias_arr - mean_arr * inv_std * scale_arr;\n  switch (order_) {\n    case StorageOrder::NHWC: {\n      EigenArrayMap<float>(Y->mutable_data<float>(), C, N * sample_size) =\n          (ConstEigenArrayMap<float>(X.data<float>(), C, N * sample_size)\n               .colwise() *\n           new_scale)\n              .colwise() +\n          new_bias;\n      break;\n    }\n    case StorageOrder::NCHW: {\n      EigenArrayMap<float> Y_arr(Y->mutable_data<float>(), sample_size, N * C);\n      ConstEigenArrayMap<float> X_arr(X.data<float>(), sample_size, N * C);\n      for (int nc = 0; nc < N * C; ++nc) {\n        Y_arr.col(nc) = X_arr.col(nc) * new_scale(nc % C) + new_bias(nc % C);\n      }\n      break;\n    }\n    default:\n      CAFFE_THROW(\"Unknown storage order: \", order_);\n  }\n  return true;\n}\n\nnamespace {\nOpSchema::Cost CostInferenceForSpatialBN(\n    const OperatorDef& def,\n    const vector<TensorShape>& in) {\n  struct OpSchema::Cost cost = PointwiseCostInference<4>(def, in);\n  ArgumentHelper helper(def);\n  auto order =\n      StringToStorageOrder(helper.GetSingleArgument<string>(\"order\", \"NCHW\"));\n  const TensorShape X = in[0];\n  const int C =\n      (order == StorageOrder::NCHW ? X.dims(1) : X.dims(X.dims_size() - 1));\n  cost.params_bytes = 2 * C * sizeof(float);\n  return cost;\n}\n} // namespace\n\nREGISTER_CPU_OPERATOR(SpatialBN, SpatialBNOp<CPUContext>);\n\nOPERATOR_SCHEMA(SpatialBN)\n    .NumInputs({5, 7})\n    .NumOutputs({1, 5})\n    .AllowInplace({{0, 0}})\n    .CostInferenceFunction(CostInferenceForSpatialBN)\n    .EnforceInplace({{3, 1}, {4, 2}})\n    .TensorInferenceFunction(\n        [](const OperatorDef& def, const vector<TensorShape>& in) {\n          ArgumentHelper helper(def);\n          bool is_test = helper.GetSingleArgument<int>(OpSchema::Arg_IsTest, 0);\n\n          if (!is_test) {\n            vector<TensorShape> out;\n            StorageOrder order = StringToStorageOrder(\n                helper.GetSingleArgument<string>(\"order\", \"NCHW\"));\n            const TensorShape& X = in[0];\n            const int C =\n                (order == StorageOrder::NCHW ? X.dims(1)\n                                             : X.dims(X.dims_size() - 1));\n\n            out.push_back(in[0]);\n            TensorShape meanvar_tp =\n                CreateTensorShape(vector<int>{C}, TensorProto::FLOAT);\n            out.push_back(meanvar_tp); // RUNNING_MEAN\n            out.push_back(meanvar_tp); // RUNNING_MEAN\n            out.push_back(meanvar_tp); // SAVED_MEAN\n            out.push_back(meanvar_tp); // SAVED_VAR\n            return out;\n          } else {\n            return vector<TensorShape>{in[0]};\n          }\n        })\n    .SetDoc(R\"DOC(\nCarries out spatial batch normalization as described in the paper\nhttps://arxiv.org/abs/1502.03167 . Depending on the mode it is being run,\nthere are multiple cases for the number of outputs, which we list below:\n\n\nOutput case #1:\n  Y, mean, var, saved_mean, saved_var (training mode)\n\n\nOutput case #2:\n  Y (test mode)\n)DOC\")\n    .ArgIsTest(\n        \"If set to nonzero, run spatial batch normalization in test mode.\")\n    .Arg(\"epsilon\", \"The epsilon value to use to avoid division by zero.\")\n    .Arg(\"order\", \"A StorageOrder string.\")\n    .Arg(\n        \"momentum\",\n        \"Factor used in computing the running mean and variance.\"\n        \"e.g., running_mean = running_mean * momentum + mean * (1 - momentum)\")\n    .Arg(\n        \"num_batches\",\n        \"(Optional) Specifies the number of batches to apply normalization on. \"\n        \"Requires specifying the optional sums and sumsq inputs that provide \"\n        \"statistics across multiple batches from which mean and variance can \"\n        \"be determined.\")\n    .Input(\n        0,\n        \"X\",\n        \"The input 4-dimensional tensor of shape NCHW or NHWC depending \"\n        \"on the order parameter.\")\n    .Input(\n        1,\n        \"scale\",\n        \"The scale as a 1-dimensional tensor of size C to be applied to the \"\n        \"output.\")\n    .Input(\n        2,\n        \"bias\",\n        \"The bias as a 1-dimensional tensor of size C to be applied to the \"\n        \"output.\")\n    .Input(\n        3,\n        \"mean\",\n        \"The running mean (training) or the estimated mean (testing) \"\n        \"as a 1-dimensional tensor of size C.\")\n    .Input(\n        4,\n        \"var\",\n        \"The running variance (training) or the estimated \"\n        \"variance (testing) as a 1-dimensional tensor of size C.\")\n    .Input(\n        5,\n        \"sums\",\n        \"(optional) Per-channel sums of elements to be used to determine the \"\n        \"mean and variance for this batch\")\n    .Input(\n        6,\n        \"sumsq\",\n        \"(optional) Per-channel sum of elements squared per channel to be used \"\n        \"to determine the variance for this batch\")\n\n    .Output(0, \"Y\", \"The output 4-dimensional tensor of the same shape as X.\")\n    .Output(\n        1,\n        \"mean\",\n        \"The running mean after the spatial BN operator. Must be in-place \"\n        \"with the input mean. Should not be used for testing.\")\n    .Output(\n        2,\n        \"var\",\n        \"The running variance after the spatial BN operator. Must be \"\n        \"in-place with the input var. Should not be used for testing.\")\n    .Output(\n        3,\n        \"saved_mean\",\n        \"Saved mean used during training to speed up gradient \"\n        \"computation. Should not be used for testing.\")\n    .Output(\n        4,\n        \"saved_var\",\n        \"Saved variance used during training to speed up \"\n        \"gradient computation. Should not be used for testing.\");\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/spatial_batch_norm_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SPATIAL_BATCH_NORM_OP_H_\n#define CAFFE2_OPERATORS_SPATIAL_BATCH_NORM_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass SpatialBNOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SpatialBNOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        is_test_(OperatorBase::GetSingleArgument<int>(OpSchema::Arg_IsTest, 0)),\n        epsilon_(OperatorBase::GetSingleArgument<float>(\"epsilon\", 1e-5f)),\n        momentum_(OperatorBase::GetSingleArgument<float>(\"momentum\", 0.9f)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))),\n        num_batches_(OperatorBase::GetSingleArgument<int>(\"num_batches\", 1)) {\n    // TODO(jiayq): update the input and output size checks.\n    CAFFE_ENFORCE(\n        (is_test_ && OutputSize() == 1) || (!is_test_ && OutputSize() == 5));\n    CAFFE_ENFORCE_GT(epsilon_, 0);\n    CAFFE_ENFORCE_GE(momentum_, 0);\n    CAFFE_ENFORCE_LE(momentum_, 1);\n  }\n  ~SpatialBNOp() {}\n\n  bool RunOnDevice() override {\n    return true;\n  }\n\n protected:\n  bool is_test_;\n  double epsilon_;\n  double momentum_;\n  StorageOrder order_;\n  int num_batches_;\n  INPUT_TAGS(INPUT, SCALE, BIAS, EST_MEAN, EST_VAR, SUMS, SUMSQ);\n  OUTPUT_TAGS(OUTPUT, RUNNING_MEAN, RUNNING_VAR, SAVED_MEAN, SAVED_INV_VAR);\n};\n\ntemplate <class Context>\nclass SpatialBNGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SpatialBNGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        is_test_(OperatorBase::GetSingleArgument<int>(OpSchema::Arg_IsTest, 0)),\n        epsilon_(OperatorBase::GetSingleArgument<float>(\"epsilon\", 1e-5f)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))),\n        num_batches_(OperatorBase::GetSingleArgument<int>(\"num_batches\", 1)) {\n    CAFFE_ENFORCE(InputSize() == 5 || InputSize() == 7);\n    CAFFE_ENFORCE(OutputSize() == 3);\n  }\n  ~SpatialBNGradientOp() {}\n\n  bool RunOnDevice() override {\n    return true;\n  }\n\n protected:\n  bool is_test_;\n  double epsilon_;\n  StorageOrder order_;\n  int num_batches_;\n\n  INPUT_TAGS(\n      INPUT,\n      SCALE,\n      OUTPUT_GRAD,\n      SAVED_MEAN,\n      SAVED_INV_VAR,\n      AGGREGATE_SCALE_GRAD,\n      AGGREGATE_BIAS_GRAD);\n  OUTPUT_TAGS(INPUT_GRAD, SCALE_GRAD, BIAS_GRAD);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SPATIAL_BATCH_NORM_OP_H_\n"
  },
  {
    "path": "caffe2/operators/spatial_batch_norm_op_cudnn.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cfloat>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/cudnn_wrappers.h\"\n#include \"caffe2/operators/spatial_batch_norm_op.h\"\n#include \"caffe2/utils/math.h\"\n\n// Note: Instead of directly failing, we will choose to not build this operator\n// if cudnn version is not high enough.\nstatic_assert(CUDNN_VERSION >= 5000,\n             \"CudnnSpatialBN requires cudnn version 5.0 or above.\");\n\nnamespace caffe2 {\n\nclass CudnnSpatialBNOp final : public SpatialBNOp<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  CudnnSpatialBNOp(const OperatorDef& operator_def, Workspace* ws)\n      : SpatialBNOp<CUDAContext>(operator_def, ws), cudnn_wrapper_(&context_) {\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&data_desc_));\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&bn_param_desc_));\n    if (epsilon_ <= CUDNN_BN_MIN_EPSILON - FLT_EPSILON) {\n      LOG(ERROR) << \"Provided epsilon is smaller than \"\n                 << \"CUDNN_BN_MIN_EPSILON. Setting it to \"\n                 << \"CUDNN_BN_MIN_EPSILON instead.\";\n    }\n    epsilon_ = std::max(epsilon_, CUDNN_BN_MIN_EPSILON);\n#if CUDNN_VERSION_MIN(7,0,0)\n    mode_ = CUDNN_BATCHNORM_SPATIAL_PERSISTENT;\n#else\n    mode_ = CUDNN_BATCHNORM_SPATIAL;\n#endif\n  }\n\n  ~CudnnSpatialBNOp() {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(data_desc_));\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(bn_param_desc_));\n  }\n\n  template <typename T, typename M>\n  bool DoRunWithType();\n  bool RunOnDevice() override;\n\n protected:\n  CuDNNWrapper cudnn_wrapper_;\n  cudnnTensorDescriptor_t data_desc_;\n  cudnnTensorDescriptor_t bn_param_desc_;\n  vector<TIndex> cudnn_input_dims_;\n\n  cudnnBatchNormMode_t mode_;\n};\n\nclass CudnnSpatialBNGradientOp final : public SpatialBNGradientOp<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  CudnnSpatialBNGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : SpatialBNGradientOp<CUDAContext>(operator_def, ws),\n        cudnn_wrapper_(&context_) {\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&data_desc_));\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&bn_param_desc_));\n    if (epsilon_ <= CUDNN_BN_MIN_EPSILON - FLT_EPSILON) {\n      LOG(ERROR) << \"Provided epsilon is smaller than \"\n                 << \"CUDNN_BN_MIN_EPSILON. Setting it to \"\n                 << \"CUDNN_BN_MIN_EPSILON instead.\";\n    }\n    epsilon_ = std::max(epsilon_, CUDNN_BN_MIN_EPSILON);\n#if CUDNN_VERSION_MIN(7,0,0)\n    mode_ = CUDNN_BATCHNORM_SPATIAL_PERSISTENT;\n#else\n    mode_ = CUDNN_BATCHNORM_SPATIAL;\n#endif\n  }\n\n  ~CudnnSpatialBNGradientOp() {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(data_desc_));\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(bn_param_desc_));\n  }\n\n  template <typename T, typename M>\n  bool DoRunWithType();\n\n  bool RunOnDevice() override;\n\n protected:\n  CuDNNWrapper cudnn_wrapper_;\n  cudnnTensorDescriptor_t data_desc_;\n  cudnnTensorDescriptor_t bn_param_desc_;\n  vector<TIndex> cudnn_input_dims_;\n\n  cudnnBatchNormMode_t mode_;\n};\n\n\n////////////////////////////////////////////////////////////////////////////////\n// Implementations\n////////////////////////////////////////////////////////////////////////////////\n\ntemplate <typename T, typename M>\nbool CudnnSpatialBNOp::DoRunWithType() {\n\n  // QoL\n  typedef typename cudnnTypeWrapper<T>::BNParamType BNParamType;\n\n  const auto& X = Input(INPUT);\n  const auto& scale = Input(SCALE);\n  const auto& bias = Input(BIAS);\n\n  CAFFE_ENFORCE_GE(X.ndim(), 3);\n  const int N = X.dim32(0);\n  const int C = X.ndim() > 3\n      ? (order_ == StorageOrder::NCHW ? X.dim32(1) : X.dim32(X.ndim() - 1))\n      : (order_ == StorageOrder::NCHW ? X.dim32(1) : X.dim32(2));\n  const int H = (order_ == StorageOrder::NCHW ? X.dim32(2) : X.dim32(1));\n  const int W = X.ndim() > 3\n      ? (order_ == StorageOrder::NCHW ? X.dim32(3) : X.dim32(2))\n      : 1;\n  const int D = X.ndim() > 4\n      ? (order_ == StorageOrder::NCHW ? X.dim32(4) : X.dim32(3))\n      : 1;\n  CAFFE_ENFORCE_EQ(scale.ndim(), 1);\n  CAFFE_ENFORCE_EQ(bias.ndim(), 1);\n  CAFFE_ENFORCE_EQ(scale.dim32(0), C);\n  CAFFE_ENFORCE_EQ(bias.dim32(0), C);\n  // See if we need to reshape.\n  if (X.dims() != cudnn_input_dims_) {\n    VLOG(1) << \"Setting descriptors.\";\n    cudnn_input_dims_ = X.dims();\n    if (order_ == StorageOrder::NCHW) {\n      vector<int> dims = {N, C, H, W, D};\n      vector<int> strides = {C * H * W * D, H * W * D, W * D, D, 1};\n      CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n          data_desc_,\n          cudnnTypeWrapper<T>::type,\n          X.ndim() > 3 ? X.ndim() : 4,\n          dims.data(),\n          strides.data()));\n    } else {\n      vector<int> dims = {N, C, H, W, D};\n      vector<int> strides = {H * W * D * C, 1, W * D * C, D * C, C};\n      CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n          data_desc_,\n          cudnnTypeWrapper<T>::type,\n          X.ndim() > 3 ? X.ndim() : 4,\n          dims.data(),\n          strides.data()));\n    }\n    CUDNN_ENFORCE(cudnnDeriveBNTensorDescriptor(\n        bn_param_desc_, data_desc_, mode_));\n  }\n\n  // Now, depending on whether we are running test or not, we have two paths.\n  if (is_test_) {\n    // Run inference mode.\n    const auto& est_mean = Input(EST_MEAN);\n    const auto& est_var = Input(EST_VAR);\n    CAFFE_ENFORCE_EQ(est_mean.ndim(), 1);\n    CAFFE_ENFORCE_EQ(est_var.ndim(), 1);\n    CAFFE_ENFORCE_EQ(est_mean.dim32(0), C);\n    CAFFE_ENFORCE_EQ(est_var.dim32(0), C);\n\n    auto* Y = Output(OUTPUT);\n    Y->ResizeLike(X);\n    CUDNN_ENFORCE(cudnnBatchNormalizationForwardInference(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        // Note: PERSISTENT not implemented for inference\n        CUDNN_BATCHNORM_SPATIAL,\n        cudnnTypeWrapper<T>::kOne(),\n        cudnnTypeWrapper<T>::kZero(),\n        data_desc_,\n        X.template data<T>(),\n        data_desc_,\n        Y->template mutable_data<T>(),\n        bn_param_desc_,\n        scale.template data<BNParamType>(),\n        bias.template data<BNParamType>(),\n        est_mean.template data<BNParamType>(),\n        est_var.template data<BNParamType>(),\n        epsilon_));\n  } else {\n    // Run training mode.\n    auto* Y = Output(OUTPUT);\n    Y->ResizeLike(X);\n    // obtain running mean and running inv var, and see if we need to\n    // initialize them.\n    auto* running_mean = Output(RUNNING_MEAN);\n    auto* running_var = Output(RUNNING_VAR);\n    double this_factor = 1. - momentum_;\n    BNParamType* running_mean_data = nullptr;\n    BNParamType* running_var_data = nullptr;\n    if (!running_mean->size()) {\n      // If the input mean and var are not initialized yet, this is the first\n      // run and we will initialize the storage.\n      VLOG(1) << \"Initializing running mean and var.\";\n      // Need to do initialization\n      running_mean->Resize(C);\n      running_var->Resize(C);\n      running_mean_data = running_mean->template mutable_data<BNParamType>();\n      running_var_data = running_var->template mutable_data<BNParamType>();\n      // In principle, setting this_momentum to 1 will wipe existing data.\n      // This has a caveat that if cudnn does not deal with 0*NaN cases we\n      // will be having an issue. Thus we choose a safe path by explicitly\n      // setting zero.\n      math::Set<BNParamType, CUDAContext>(C, 0, running_mean_data, &context_);\n      math::Set<BNParamType, CUDAContext>(C, 0, running_var_data, &context_);\n    } else {\n      // Does not need to do initialization.\n      CAFFE_ENFORCE_EQ(running_mean->ndim(), 1);\n      CAFFE_ENFORCE_EQ(running_var->ndim(), 1);\n      CAFFE_ENFORCE_EQ(running_mean->dim32(0), C);\n      CAFFE_ENFORCE_EQ(running_var->dim32(0), C);\n      running_mean_data = running_mean->template mutable_data<BNParamType>();\n      running_var_data = running_var->template mutable_data<BNParamType>();\n    }\n    // Save the mean and inv var results.\n    auto* save_mean = Output(SAVED_MEAN);\n    auto* save_var = Output(SAVED_INV_VAR);\n    save_mean->Resize(C);\n    save_var->Resize(C);\n    void* save_mean_data = save_mean->template mutable_data<BNParamType>();\n    void* save_var_data = save_var->template mutable_data<BNParamType>();\n\n    CUDNN_ENFORCE(cudnnBatchNormalizationForwardTraining(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        mode_,\n        cudnnTypeWrapper<T>::kOne(),\n        cudnnTypeWrapper<T>::kZero(),\n        data_desc_,\n        X.template data<T>(),\n        data_desc_,\n        Y->template mutable_data<T>(),\n        bn_param_desc_,\n        scale.template data<BNParamType>(),\n        bias.template data<BNParamType>(),\n        this_factor,\n        running_mean_data,\n        running_var_data,\n        epsilon_,\n        save_mean_data,\n        save_var_data));\n  }\n  return true;\n}\n\nbool CudnnSpatialBNOp::RunOnDevice() {\n  if (Input(0).IsType<float>()) {\n    return DoRunWithType<float,float>();\n  } else if (Input(0).IsType<float16>()) {\n    return DoRunWithType<float16,float>();\n  } else {\n    LOG(FATAL) << \"Unsupported input types\";\n  }\n  return true;\n}\n\ntemplate <typename T, typename M>\nbool CudnnSpatialBNGradientOp::DoRunWithType() {\n  // QoL\n  typedef typename cudnnTypeWrapper<T>::BNParamType BNParamType;\n\n  const auto& X = Input(INPUT);\n  const auto& scale = Input(SCALE);\n  const auto& dY = Input(OUTPUT_GRAD);\n\n  CAFFE_ENFORCE_GE(X.ndim(), 3);\n  const int N = X.dim32(0);\n  const int C = X.ndim() > 3\n      ? (order_ == StorageOrder::NCHW ? X.dim32(1) : X.dim32(X.ndim() - 1))\n      : (order_ == StorageOrder::NCHW ? X.dim32(1) : X.dim32(2));\n  const int H = (order_ == StorageOrder::NCHW ? X.dim32(2) : X.dim32(1));\n  const int W = X.ndim() > 3\n      ? (order_ == StorageOrder::NCHW ? X.dim32(3) : X.dim32(2))\n      : 1;\n  const int D = X.ndim() > 4\n      ? (order_ == StorageOrder::NCHW ? X.dim32(4) : X.dim32(3))\n      : 1;\n  CAFFE_ENFORCE_EQ(scale.ndim(), 1);\n  CAFFE_ENFORCE_EQ(scale.dim32(0), C);\n  // See if we need to reshape.\n  if (X.dims() != cudnn_input_dims_) {\n    if (order_ == StorageOrder::NCHW) {\n      vector<int> dims = {N, C, H, W, D};\n      vector<int> strides = {C * H * W * D, H * W * D, W * D, D, 1};\n      CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n          data_desc_,\n          cudnnTypeWrapper<T>::type,\n          X.ndim() > 3 ? X.ndim() : 4,\n          dims.data(),\n          strides.data()));\n    } else {\n      vector<int> dims = {N, C, H, W, D};\n      vector<int> strides = {H * W * C * D, 1, W * D * C, D * C, C};\n      CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n          data_desc_,\n          cudnnTypeWrapper<T>::type,\n          X.ndim() > 3 ? X.ndim() : 4,\n          dims.data(),\n          strides.data()));\n    }\n    CUDNN_ENFORCE(cudnnDeriveBNTensorDescriptor(\n        bn_param_desc_, data_desc_, mode_));\n  }\n\n  auto* dX = Output(INPUT_GRAD);\n  auto* dScale = Output(SCALE_GRAD);\n  auto* dBias = Output(BIAS_GRAD);\n  dX->ResizeLike(X);\n  dScale->ResizeLike(scale);\n  dBias->ResizeLike(scale);\n\n  const auto& saved_mean = Input(SAVED_MEAN);\n  const auto& saved_var = Input(SAVED_INV_VAR);\n  const void* saved_mean_data = saved_mean.template data<BNParamType>();\n  const void* saved_var_data = saved_var.template data<BNParamType>();\n\n  CUDNN_ENFORCE(cudnnBatchNormalizationBackward(\n      cudnn_wrapper_.inline_cudnn_handle(),\n      mode_,\n      cudnnTypeWrapper<T>::kOne(),\n      cudnnTypeWrapper<T>::kZero(),\n      cudnnTypeWrapper<T>::kOne(),\n      cudnnTypeWrapper<T>::kZero(),\n      data_desc_,\n      X.template data<T>(),\n      data_desc_,\n      dY.template data<T>(),\n      data_desc_,\n      dX->template mutable_data<T>(),\n      bn_param_desc_,\n      scale.template data<BNParamType>(),\n      dScale->template mutable_data<BNParamType>(),\n      dBias->template mutable_data<BNParamType>(),\n      epsilon_,\n      saved_mean_data,\n      saved_var_data));\n  return true;\n}\n\nbool CudnnSpatialBNGradientOp::RunOnDevice() {\n  if (Input(0).IsType<float>()) {\n    return DoRunWithType<float,float>();\n  } else if (Input(0).IsType<float16>()) {\n    return DoRunWithType<float16,float>();\n  } else {\n    LOG(FATAL) << \"Unsupported input types\";\n  }\n  return true;\n}\n\n// Since there is no default implementation for spatial batch normalization,\n// we will register the cudnn version as the default as well.\nREGISTER_CUDA_OPERATOR(SpatialBN, CudnnSpatialBNOp);\nREGISTER_CUDA_OPERATOR(SpatialBNGradient, CudnnSpatialBNGradientOp);\n\nREGISTER_CUDNN_OPERATOR(SpatialBN, CudnnSpatialBNOp);\nREGISTER_CUDNN_OPERATOR(SpatialBNGradient, CudnnSpatialBNGradientOp);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/spatial_softmax_with_loss_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"spatial_softmax_with_loss_op.h\"\n#include \"softmax_shared.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(\n    SpatialSoftmaxWithLoss,\n    SpatialSoftmaxWithLossOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    SpatialSoftmaxWithLossGradient,\n    SpatialSoftmaxWithLossGradientOp<float, CPUContext>);\n\n// Input: X (logits), T (labels); Output: P (probs), Y\nOPERATOR_SCHEMA(SpatialSoftmaxWithLoss)\n    .NumInputs(2, 3)\n    .NumOutputs(2)\n    .TensorInferenceFunction(\n        [](const OperatorDef& def, const vector<TensorShape>& in) {\n          ArgumentHelper helper(def);\n          vector<TensorShape> out(2);\n\n          auto logits = in[0]; // Tensor with Shape [batch_size, num_classes]\n          auto labels = in[1]; // Tensor with shape [batch_size, ]\n          auto batch_size = logits.dims().Get(0);\n          auto num_classes = logits.dims().Get(1);\n\n          CAFFE_ENFORCE_EQ(logits.dims_size(), 4);\n          CAFFE_ENFORCE_EQ(labels.dims_size(), 3);\n          out[0].set_data_type(logits.data_type());\n          out[0].add_dims(batch_size);\n          out[0].add_dims(num_classes);\n          out[0].add_dims(in[0].dims(2));\n          out[0].add_dims(in[0].dims(3));\n          // Output 2 is scalar shape, so no dims added\n          return out;\n        })\n    .SetDoc(R\"DOC(\nCombined Spatial Softmax and Cross-Entropy loss operator.\nSimilar to SoftmaxWithLoss, this operator computes the spatial softmax\nnormalized values for each layer in the batch of the given input, after which\ncross-entropy loss is computed. This operator is numerically more stable than\nseparate Softmax and CrossEntropy ops. The inputs are a 2-D tensor\n(Tensor<float>) of size (batch_size x input_feature_dimensions) and tensor of\nlabels (ground truth).\nOutput is tensor with the probability for each label in a pixel for each example\n(N x D x W x H) and averaged loss (scalar).\nFor spatial softmax, weighting is by x,y position of the input.\n)DOC\")\n    .Input(0, \"logits\", \"Unscaled log probabilities\")\n    .Input(1, \"labels\", \"Ground truth\")\n    .Input(\n        2,\n        \"weight_tensor\",\n        \"Optional blob to be used to weight the samples for the loss. With\\\n        spatial set, weighting is by x,y of the input\")\n    .Output(0, \"softmax\", \"Tensor with softmax cross entropy loss\")\n    .Output(1, \"loss\", \"Average loss\");\n\n// Input: X, T, P, dY; Output: dX\nOPERATOR_SCHEMA(SpatialSoftmaxWithLossGradient).NumOutputs(1);\n\n#define DONT_CARE (-1)\n\ntemplate <>\nbool SpatialSoftmaxWithLossOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0); // Logits\n  auto& T = Input(1); // Labels / targets\n  auto* P = Output(0); // Probabilities from softmax\n  auto* avg_loss = Output(1); // Average loss\n  int N, D;\n  N = X.dim32(0);\n  D = X.dim32(1);\n  P->ResizeLike(X);\n\n  if (sum_multiplier_.size() != D) {\n    sum_multiplier_.Resize(D);\n    math::Set<float, CPUContext>(\n        D, 1.f, sum_multiplier_.mutable_data<float>(), &context_);\n  }\n\n  float* Pdata = P->mutable_data<float>();\n  const float* weights = (InputSize() > 2 ? Input(2).data<float>() : nullptr);\n  CAFFE_ENFORCE_EQ(X.ndim(), 4);\n  CAFFE_ENFORCE_EQ(T.ndim(), 3);\n  CAFFE_ENFORCE_EQ(T.dim32(0), N);\n\n  int H = X.dim32(2);\n  int W = X.dim32(3);\n\n  const float* Xdata = X.data<float>();\n\n  for (int i = 0; i < N; ++i) {\n    for (int y = 0; y < H; ++y) {\n      for (int x = 0; x < W; ++x) {\n        // Subtract max on each cell for numerical reasons\n        float max_val = (-1e20f);\n        for (int c = 0; c < D; ++c) {\n          // TODO optimize\n          int idx = i * (H * W * D) + c * (H * W) + y * W + x;\n          max_val = std::max(max_val, Xdata[idx]);\n        }\n\n        // Exponentiate\n        float expsum = 0.0f;\n        for (int c = 0; c < D; ++c) {\n          int idx = i * (H * W * D) + c * (H * W) + y * W + x;\n          float expx = exp(Xdata[idx] - max_val);\n          Pdata[idx] = expx;\n          expsum += expx;\n        }\n\n        // Normalize\n        for (int c = 0; c < D; ++c) {\n          int idx = i * (H * W * D) + c * (H * W) + y * W + x;\n          Pdata[idx] /= expsum;\n        }\n      }\n    }\n  }\n\n  // Compute the avg cross-entropy loss\n  avg_loss->Resize(vector<TIndex>());\n  float* avg_loss_data = avg_loss->mutable_data<float>();\n  const int* label_data = T.data<int>();\n\n  float sum_label_xent = 0.0f;\n  float total_weight = 0.0;\n\n  for (int y = 0; y < H; y++) {\n    for (int x = 0; x < W; x++) {\n      for (int i = 0; i < N; i++) {\n        int label_idx = i * H * W + y * W + x;\n        int label = label_data[label_idx];\n        if (label != DONT_CARE) {\n          CAFFE_ENFORCE(\n              label < D && label >= 0,\n              \"Label seems incorrect:label value larger than number of classes\",\n              label_data[i],\n              \" vs \",\n              D);\n          int idx = i * (H * W * D) + label * (H * W) + y * W + x;\n          float w = weights ? weights[label_idx] : 1.0;\n          total_weight += w;\n          sum_label_xent += -log(std::max(Pdata[idx], 1e-20f)) * w;\n        }\n      }\n    }\n  }\n  if (total_weight != 0.0) {\n    *avg_loss_data = sum_label_xent / total_weight;\n  } else {\n    *avg_loss_data = 0.0;\n  }\n  return true;\n}\n\ntemplate <>\nbool SpatialSoftmaxWithLossGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0); // Logits\n  auto& T = Input(1); // Labels / targets\n  // Input(2) is weights if given\n  auto& P = Input(InputSize() - 2); // Probabilities from softmax\n  auto& d_avg_loss = Input(InputSize() - 1); // Gradient w.r.t. avg loss\n  auto* dX = Output(0);\n  const float* weights = (InputSize() > 4 ? Input(2).data<float>() : nullptr);\n  int N, D;\n  N = X.dim32(0);\n  D = X.dim32(1);\n  dX->ResizeLike(X);\n  CAFFE_ENFORCE_EQ(T.dim32(0), N);\n  CAFFE_ENFORCE_EQ(X.ndim(), 4);\n  CAFFE_ENFORCE_EQ(T.ndim(), 3);\n\n  int H = X.dim32(2);\n  int W = X.dim32(3);\n\n  const float* Pdata = P.data<float>();\n  float* dX_data = dX->mutable_data<float>();\n  const int* label_data = T.data<int>();\n\n  // Copy softmax probabilities into dX. All but the neuron\n  // corresponding to the correct label has gradient equaling e(x_j)\n  // which is the probability under softmax.\n  context_.Copy<float, CPUContext, CPUContext>(P.size(), Pdata, dX_data);\n\n  float total_weight = 0.0f;\n  for (int y = 0; y < H; ++y) {\n    for (int x = 0; x < W; ++x) {\n      for (int i = 0; i < N; ++i) {\n        int label_idx = i * H * W + y * W + x;\n        int label = label_data[label_idx];\n\n        if (label != DONT_CARE) {\n          int idx = i * (H * W * D) + label * (H * W) + y * W + x;\n\n          dX_data[idx] = (dX_data[idx] - 1.0);\n\n          if (weights != nullptr) {\n            float weight = weights[label_idx];\n            for (int c = 0; c < D; ++c) {\n              int k = i * (H * W * D) + c * (H * W) + y * W + x;\n              dX_data[k] *= weight;\n            }\n            total_weight += weight;\n          } else {\n            total_weight += 1.0;\n          }\n        } else {\n          // Set gradient to zero for coordinates where we have dont care\n          for (int c = 0; c < D; ++c) {\n            int idx = i * (H * W * D) + c * (H * W) + y * W + x;\n            dX_data[idx] = 0;\n          }\n        }\n      }\n    }\n  }\n\n  if (total_weight > 0) {\n    math::Scale<float, CPUContext>(\n        dX->size(),\n        scale_ / total_weight,\n        dX->data<float>(),\n        dX_data,\n        &context_);\n  }\n  math::Scale<float, CPUContext>(\n      dX->size(),\n      d_avg_loss.data<float>(),\n      dX->data<float>(),\n      dX->mutable_data<float>(),\n      &context_);\n  return true;\n}\n\nnamespace {\nclass GetSoftmaxWithLossGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> blob_names{\n        {I(0), I(1), O(0), GO(1)},\n    };\n\n    // Add weight blob, if given\n    if (def_.input_size() == 3) {\n      blob_names.emplace(blob_names.begin() + 2, I(2));\n    }\n    return SingleGradientDef(\n        \"SpatialSoftmaxWithLossGradient\",\n        \"\",\n        blob_names,\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(SpatialSoftmaxWithLoss, GetSoftmaxWithLossGradient);\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/spatial_softmax_with_loss_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef SPATIAL_SOFTMAX_WITH_LOSS_OP_H_\n#define SPATIAL_SOFTMAX_WITH_LOSS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SpatialSoftmaxWithLossOp final : public Operator<Context> {\n public:\n  SpatialSoftmaxWithLossOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CAFFE_ENFORCE(scale_ >= 0);\n    CAFFE_ENFORCE_EQ(\n        order_, StorageOrder::NCHW, \"Only NCHW order is supported right now.\");\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  float scale_;\n  StorageOrder order_;\n\n  Tensor<Context> losses_; // Per example loss\n  Tensor<Context> rowmax_; // per example row max\n  Tensor<Context> weights_; // unignored weights\n  Tensor<Context> sum_multiplier_; // Vector of ones for summing via dot prod\n  Tensor<Context> total_weight_ptr_;\n  Tensor<Context> scratch_;\n};\n\ntemplate <typename T, class Context>\nclass SpatialSoftmaxWithLossGradientOp final : public Operator<Context> {\n public:\n  SpatialSoftmaxWithLossGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))),\n        only_loss_(OperatorBase::GetSingleArgument<bool>(\"only_loss\", false)) {\n    CAFFE_ENFORCE(scale_ >= 0);\n    CAFFE_ENFORCE_EQ(\n        order_, StorageOrder::NCHW, \"Only NCHW order is supported right now.\");\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n protected:\n  float scale_;\n  Tensor<Context> sum_multiplier_;\n  Tensor<Context> weights_; // unignored weights\n  Tensor<Context> total_weight_ptr_;\n  StorageOrder order_;\n  bool only_loss_;\n  Tensor<Context> scratch_;\n};\n\n} // namespace caffe2\n\n#endif // SOFTMAX_WITH_LOSS_OP_H_\n"
  },
  {
    "path": "caffe2/operators/sqrt_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <Eigen/Core>\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\nstruct SqrtCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* /*device_context*/) {\n    EigenVectorArrayMap<T>(y, n) = ConstEigenVectorArrayMap<T>(x, n).sqrt();\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    Sqrt,\n    UnaryElementwiseOp<TensorTypes<float>, CPUContext, SqrtCPUFunctor>);\n// Input: X, output: Y\nOPERATOR_SCHEMA(Sqrt)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nComputes the element-wise sqrt of the input.\n)DOC\")\n    .Input(0, \"X\", \"ND input tensor\")\n    .Output(0, \"Y\", \"ND input tensor\");\n\nclass GetSqrtGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    Argument scale_arg;\n    scale_arg.set_name(\"scale\");\n    scale_arg.set_f(0.5);\n    return vector<OperatorDef>{CreateOperatorDef(\n                                   \"Scale\",\n                                   \"\",\n                                   std::vector<string>{GO(0)},\n                                   std::vector<string>{GI(0)},\n                                   std::vector<Argument>{scale_arg}),\n                               CreateOperatorDef(\n                                   \"Div\",\n                                   \"\",\n                                   std::vector<string>{GI(0), O(0)},\n                                   std::vector<string>{GI(0)})};\n  }\n};\nREGISTER_GRADIENT(Sqrt, GetSqrtGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/sqrt_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/operators/math_ops.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void SqrtKernel(const int N, const T* x, T* y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    y[i] = sqrt(x[i]);\n  }\n}\n\nstruct SqrtCUDAFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CUDAContext* device_context) {\n    SqrtKernel<T>\n        <<<CAFFE_GET_BLOCKS(n),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           device_context->cuda_stream()>>>(n, x, y);\n    return;\n  }\n};\n\nREGISTER_CUDA_OPERATOR(\n    Sqrt,\n    UnaryElementwiseOp<TensorTypes<float>, CUDAContext, SqrtCUDAFunctor>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/square_root_divide_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/square_root_divide_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(SquareRootDivide, SquareRootDivideOp<CPUContext>);\nOPERATOR_SCHEMA(SquareRootDivide)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nGiven DATA tensor with first dimension N and SCALE vector of the same size N\nproduces an output tensor with same dimensions as DATA. Which consists of DATA\nslices. i-th slice is divided by sqrt(SCALE[i]) elementwise. If SCALE[i] == 0\noutput slice is identical to the input one (no scaling)\n\nExample:\n\n  Data = [\n    [2.0, 4.0],\n    [9.0, 12.0]\n  ]\n\n  SCALE = [4, 9]\n\n  OUTPUT = [\n    [1.0, 2.0],\n    [3.0, 4.0]\n  ]\n\n)DOC\");\n\nclass GetSquareRootDivideGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SquareRootDivide\",\n        \"\",\n        vector<string>{GO(0), I(1)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(SquareRootDivide, GetSquareRootDivideGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/square_root_divide_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SQUARE_ROOT_DIVIDE_OP_H_\n#define CAFFE2_OPERATORS_SQUARE_ROOT_DIVIDE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass SquareRootDivideOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_DISPATCH_HELPER;\n\n  SquareRootDivideOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<float>>::call(this, Input(DATA));\n  }\n\n private:\n  template <typename TData>\n  bool DoRunWithType() {\n    return DispatchHelper<TensorTypes2<float, int32_t, int64_t>, TData>::call(\n        this, Input(SCALE));\n  }\n\n  template <typename TData, typename TScale>\n  bool DoRunWithType2() {\n    auto& data = Input(DATA);\n    auto& scale = Input(SCALE);\n    auto* Y = Output(0);\n    Y->ResizeLike(data);\n    size_t batchSize = data.dim(0);\n    size_t exampleSize = data.size_from_dim(1);\n    CAFFE_ENFORCE(batchSize == scale.dim(0), batchSize, \" != \", scale.dim(0));\n    auto* scalePtr = scale.template data<TScale>();\n    auto* dataPtr = data.template data<TData>();\n    auto* yPtr = Y->template mutable_data<TData>();\n    for (auto i = 0; i < batchSize; ++i) {\n      auto scale = scalePtr[i];\n      CAFFE_ENFORCE(scale >= 0, scale, \" < 0\");\n      auto multiplier = scale == 0 ? 1.0 : 1 / std::sqrt(scale);\n      math::Scale<TData, Context>(\n          exampleSize,\n          multiplier,\n          dataPtr + i * exampleSize,\n          yPtr + i * exampleSize,\n          &context_);\n    }\n    return true;\n  }\n\n  INPUT_TAGS(DATA, SCALE);\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SQUARE_ROOT_DIVIDE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/stats_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <chrono>\n#include <vector>\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/stats.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\nclass StatRegistryCreateOp : public Operator<CPUContext> {\n public:\n  StatRegistryCreateOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    *OperatorBase::Output<std::unique_ptr<StatRegistry>>(0) =\n        std::unique_ptr<StatRegistry>(new StatRegistry);\n    return true;\n  }\n};\n\nclass StatRegistryExportOp : public Operator<CPUContext> {\n public:\n  StatRegistryExportOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        reset_(GetSingleArgument<bool>(\"reset\", true)) {}\n\n  bool RunOnDevice() override {\n    auto registry = InputSize() > 0\n        ? OperatorBase::Input<std::unique_ptr<StatRegistry>>(0).get()\n        : &StatRegistry::get();\n    auto* keys = Output(0);\n    auto* values = Output(1);\n    auto* timestamps = Output(2);\n    auto data = registry->publish(reset_);\n    keys->Resize(data.size());\n    values->Resize(data.size());\n    timestamps->Resize(data.size());\n    auto* pkeys = keys->mutable_data<std::string>();\n    auto* pvals = values->mutable_data<int64_t>();\n    auto* ptimestamps = timestamps->mutable_data<int64_t>();\n    int i = 0;\n    for (const auto& stat : data) {\n      pkeys[i] = std::move(stat.key);\n      pvals[i] = stat.value;\n      ptimestamps[i] =\n          std::chrono::nanoseconds(stat.ts.time_since_epoch()).count();\n      ++i;\n    }\n    return true;\n  }\n\n private:\n  bool reset_;\n};\n\nclass StatRegistryUpdateOp : public Operator<CPUContext> {\n public:\n  StatRegistryUpdateOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    const auto& keys = Input(0);\n    const auto& values = Input(1);\n    auto registry = InputSize() == 3\n        ? OperatorBase::Input<std::unique_ptr<StatRegistry>>(2).get()\n        : &StatRegistry::get();\n    CAFFE_ENFORCE_EQ(keys.size(), values.size());\n    ExportedStatList data(keys.size());\n    auto* pkeys = keys.data<std::string>();\n    auto* pvals = values.data<int64_t>();\n    int i = 0;\n    for (auto& stat : data) {\n      stat.key = pkeys[i];\n      stat.value = pvals[i];\n      ++i;\n    }\n    registry->update(data);\n    return true;\n  }\n};\n\nclass TimerInstance {\n public:\n  explicit TimerInstance(const std::string& name)\n      : running_(false), stat_(name) {}\n\n  void begin() {\n    CAFFE_ENFORCE(!running_, \"Called TimerBegin on an already running timer.\");\n    running_ = true;\n    start_ = std::chrono::high_resolution_clock::now();\n  }\n\n  void end() {\n    CAFFE_ENFORCE(running_, \"Called TimerEnd on a stopped timer.\");\n    using namespace std::chrono;\n    auto duration = high_resolution_clock::now() - start_;\n    auto nanos = duration_cast<nanoseconds>(duration).count();\n    CAFFE_EVENT(stat_, time_ns, nanos);\n    running_ = false;\n  }\n\n  int64_t get_ns() {\n    CAFFE_ENFORCE(running_, \"Called TimerGet on a stopped timer.\");\n    using namespace std::chrono;\n    auto duration = high_resolution_clock::now() - start_;\n    auto nanos = duration_cast<nanoseconds>(duration).count();\n    return nanos;\n  }\n\n private:\n  bool running_;\n  std::chrono::high_resolution_clock::time_point start_;\n\n  struct TimerStat {\n    CAFFE_STAT_CTOR(TimerStat);\n    CAFFE_AVG_EXPORTED_STAT(time_ns);\n  } stat_;\n};\n\nstruct TimerBeginOp : public Operator<CPUContext> {\n  TimerBeginOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        given_name_(GetSingleArgument<std::string>(\n            \"counter_name\",\n            operator_def.output().Get(0))),\n        timer_([this]() { return given_name_; }()) {}\n\n  bool RunOnDevice() override {\n    *OperatorBase::Output<TimerInstance*>(0) = &timer_;\n    timer_.begin();\n    return true;\n  }\n\n private:\n  const std::string given_name_;\n  TimerInstance timer_;\n};\n\nstruct TimerEndOp : public Operator<CPUContext> {\n  TimerEndOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    OperatorBase::Input<TimerInstance*>(0)->end();\n    return true;\n  }\n};\n\nstruct TimerGetAndEndOp : public Operator<CPUContext> {\n  TimerGetAndEndOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    int64_t nanos = OperatorBase::Input<TimerInstance*>(0)->get_ns();\n    OperatorBase::Input<TimerInstance*>(0)->end();\n    auto* res = OperatorBase::Output<TensorCPU>(0);\n    res->Resize(1);\n    res->template mutable_data<int64_t>()[0] = nanos;\n    return true;\n  }\n};\n\nstruct TimerGetOp : public Operator<CPUContext> {\n  TimerGetOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    int64_t nanos = OperatorBase::Input<TimerInstance*>(0)->get_ns();\n    auto* res = OperatorBase::Output<TensorCPU>(0);\n    res->Resize();\n    res->template mutable_data<int64_t>()[0] = nanos;\n    return true;\n  }\n};\n\nstruct CpuUtilizationReportOp : public Operator<CPUContext> {\n  CpuUtilizationReportOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        statsName_(GetSingleArgument<std::string>(\"stats_name\", \"utilization\")),\n        stat_([this]() { return statsName_; }()) {}\n\n  bool RunOnDevice() override {\n    float utilization = Input(0).template data<float>()[0];\n    // Utilization is a float value, but CAFFE_EVENT only keeps int64_t values.\n    // We will keep 100x of the received utilization to maintain accuracy.\n    CAFFE_EVENT(stat_, cpu_utilization, (int)(utilization * 100));\n    return true;\n  }\n\n private:\n  std::string statsName_;\n  struct CpuStats {\n    CAFFE_STAT_CTOR(CpuStats);\n    CAFFE_EXPORTED_STAT(cpu_utilization);\n  } stat_;\n};\n\nREGISTER_CPU_OPERATOR(StatRegistryCreate, StatRegistryCreateOp);\nREGISTER_CPU_OPERATOR(StatRegistryUpdate, StatRegistryUpdateOp);\nREGISTER_CPU_OPERATOR(StatRegistryExport, StatRegistryExportOp);\n\nREGISTER_CPU_OPERATOR(TimerBegin, TimerBeginOp);\nREGISTER_CPU_OPERATOR(TimerEnd, TimerEndOp);\nREGISTER_CPU_OPERATOR(TimerGetAndEnd, TimerGetAndEndOp);\nREGISTER_CPU_OPERATOR(TimerGet, TimerGetOp);\nREGISTER_CPU_OPERATOR(CpuUtilizationReport, CpuUtilizationReportOp);\n\nOPERATOR_SCHEMA(StatRegistryCreate)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nCreate a StatRegistry object that will contain a map of performance counters\nkeyed by name. A StatRegistry is used to gather and retrieve performance\ncounts throughout the caffe2 codebase.\n)DOC\")\n    .Output(0, \"handle\", \"A Blob pointing to the newly created StatRegistry.\");\n\nOPERATOR_SCHEMA(StatRegistryUpdate)\n    .NumInputs(2, 3)\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(\nUpdate the given StatRegistry, or the global StatRegistry,\nwith the values of counters for the given keys.\n)DOC\")\n    .Input(0, \"keys\", \"1D string tensor with the key names to update.\")\n    .Input(1, \"values\", \"1D int64 tensor with the values to update.\")\n    .Input(\n        2,\n        \"handle\",\n        \"If provided, update the given StatRegistry. \"\n        \"Otherwise, update the global singleton.\");\n\nOPERATOR_SCHEMA(StatRegistryExport)\n    .NumInputs(0, 1)\n    .NumOutputs(3)\n    .Input(\n        0,\n        \"handle\",\n        \"If provided, export values from given StatRegistry.\"\n        \"Otherwise, export values from the global singleton StatRegistry.\")\n    .Output(0, \"keys\", \"1D string tensor with exported key names\")\n    .Output(1, \"values\", \"1D int64 tensor with exported values\")\n    .Output(2, \"timestamps\", \"The unix timestamp at counter retrieval.\")\n    .Arg(\n        \"reset\",\n        \"(default true) Whether to atomically reset the counters afterwards.\");\n\nOPERATOR_SCHEMA(TimerBegin)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nStart a wallclock timer, returning a pointer to it.\nThe timer is stopped by calling TimerEnd)DOC\")\n    .Arg(\"counter_name\", \"Name of the timer. If not provided, use output name.\")\n    .Output(0, \"timer\", \"Pointer to timer, to be passed to TimerEnd.\");\n\nOPERATOR_SCHEMA(TimerEnd)\n    .NumInputs(1)\n    .NumOutputs(0)\n    .SetDoc(\"Stop a timer started with TimerBegin, publishing a CAFFE_EVENT\")\n    .Input(0, \"timer\", \"Pointer to timer, obtained from TimerBegin.\");\n\nOPERATOR_SCHEMA(TimerGetAndEnd)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(Queries the current time of a timer in nanos, stops the timer\n            publishing a CAFFE_EVENT)DOC\")\n    .Input(0, \"timer\", \"Pointer to timer, obtained from TimerBegin.\")\n    .Output(0, \"nanos\", \"nanoseconds in int64\");\n\nOPERATOR_SCHEMA(TimerGet)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(Queries the current time of a timer in nanos)DOC\")\n    .Input(0, \"timer\", \"Pointer to timer, obtained from TimerBegin.\")\n    .Output(0, \"nanos\", \"nanoseconds in int64\");\n\nOPERATOR_SCHEMA(CpuUtilizationReport)\n    .NumInputs(1)\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(Report the delta in max CPU utilization observed so far in the\n            plan)DOC\")\n    .Input(\n        0,\n        \"utilization\",\n        \"Delta in max CPU utilization observed, in percentage as a float value\")\n    .Arg(\"stats_name\", \"String name of the stat entry holding CPU utilization\");\n\nCAFFE_KNOWN_TYPE(TimerInstance*);\nCAFFE_KNOWN_TYPE(std::unique_ptr<caffe2::StatRegistry>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/stop_gradient.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/stop_gradient.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(StopGradient, StopGradientOp<CPUContext>);\n\n// TODO(jiayq): Add example to the doc string.\nOPERATOR_SCHEMA(StopGradient)\n    .NumInputs(1, 1)\n    .NumOutputs(1, 1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nStopGradient is a helper operator that does no actual numerical computation,\nand in the gradient computation phase stops the gradient from being computed\nthrough it.\n)DOC\");\n\nNO_GRADIENT(StopGradient);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/stop_gradient.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_STOP_GRADIENT_H_\n#define CAFFE2_OPERATORS_STOP_GRADIENT_H_\n\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass StopGradientOp : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(StopGradientOp)\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override {\n    const auto& in = Input(0);\n    auto* out = Output(0);\n    if (out != &in) {\n      out->CopyFrom(in, &context_);\n    }\n    return true;\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_STOP_GRADIENT_H_\n"
  },
  {
    "path": "caffe2/operators/stop_gradient_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/stop_gradient.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(StopGradient, StopGradientOp<CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/string_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/string_ops.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <>\ntemplate <typename T>\nbool StringJoinOp<CPUContext>::DoRunWithType() {\n  const auto& input = Input(0);\n  auto* output = Output(0);\n  CAFFE_ENFORCE_GT(input.size(), 0);\n  CAFFE_ENFORCE_LE(input.ndim(), 2, \"Only 1-D and 2-D tensors are supported\");\n\n  const auto* inputData = input.data<T>();\n  int rowSize = (input.ndim() == 2) ? input.dim(1) : 1;\n  if (this->axis_ == 0) {\n    output->Resize(input.dim(0));\n    auto* outputData = output->mutable_data<std::string>();\n\n    int offset = 0;\n    for (int i = 0; i < input.dim(0); ++i) {\n      std::stringstream stream;\n      std::copy(\n          inputData + offset,\n          inputData + offset + rowSize,\n          std::ostream_iterator<T>(stream, delimiter_.c_str()));\n      outputData[i] = stream.str();\n      offset += rowSize;\n    }\n  } else if (this->axis_ == 1) {\n    output->Resize(input.dim(1));\n    auto* outputData = output->mutable_data<std::string>();\n\n    for (int j = 0; j < input.dim(1); ++j) {\n      std::stringstream stream;\n      for (int i = 0; i < input.dim(0); ++i) {\n        stream << inputData[i * rowSize + j] << delimiter_;\n      }\n      outputData[j] = stream.str();\n    }\n  } else {\n    CAFFE_ENFORCE(false, \"Not supported\");\n  }\n\n  return true;\n}\n\nnamespace {\n\nstruct StartsWith {\n  explicit StartsWith(OperatorBase& op)\n      : prefix_(op.GetSingleArgument<std::string>(\"prefix\", \"\")) {}\n  bool operator()(const std::string& str) {\n    return std::mismatch(prefix_.begin(), prefix_.end(), str.begin()).first ==\n        prefix_.end();\n  }\n\n private:\n  std::string prefix_;\n};\n\nstruct EndsWith {\n  explicit EndsWith(OperatorBase& op)\n      : suffix_(op.GetSingleArgument<std::string>(\"suffix\", \"\")) {}\n  bool operator()(const std::string& str) {\n    return std::mismatch(suffix_.rbegin(), suffix_.rend(), str.rbegin())\n               .first == suffix_.rend();\n  }\n\n private:\n  std::string suffix_;\n};\n\nstruct Prefix {\n  explicit Prefix(OperatorBase& op)\n      : length_(op.GetSingleArgument<int>(\"length\", 3)) {}\n  std::string operator()(const std::string& str) {\n    return std::string(str.begin(), std::min(str.end(), str.begin() + length_));\n  }\n\n private:\n  int length_;\n};\n\nstruct Suffix {\n  explicit Suffix(OperatorBase& op)\n      : length_(op.GetSingleArgument<int>(\"length\", 3)) {}\n  std::string operator()(const std::string& str) {\n    return std::string(std::max(str.begin(), str.end() - length_), str.end());\n  }\n\n private:\n  int length_;\n};\n\ntemplate <typename ScalarFunctor, typename TypeMap = FixedType<std::string>>\nusing StringElementwiseOp = UnaryElementwiseWithArgsOp<\n    TensorTypes<std::string>,\n    CPUContext,\n    ForEach<ScalarFunctor>,\n    TypeMap>;\n\nREGISTER_CPU_OPERATOR(StringPrefix, StringElementwiseOp<Prefix>);\nREGISTER_CPU_OPERATOR(StringSuffix, StringElementwiseOp<Suffix>);\nREGISTER_CPU_OPERATOR(\n    StringStartsWith,\n    StringElementwiseOp<StartsWith, FixedType<bool>>);\nREGISTER_CPU_OPERATOR(\n    StringEndsWith,\n    StringElementwiseOp<EndsWith, FixedType<bool>>);\nREGISTER_CPU_OPERATOR(StringJoin, StringJoinOp<CPUContext>);\n\nOPERATOR_SCHEMA(StringPrefix)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nComputes the element-wise string prefix of the string tensor.\nInput strings that are shorter than prefix length will be returned unchanged.\nNOTE: Prefix is computed on number of bytes, which may lead to wrong behavior\nand potentially invalid strings for variable-length encodings such as utf-8.\n)DOC\")\n    .Arg(\"length\", \"Maximum size of the prefix, in bytes.\")\n    .Input(0, \"strings\", \"Tensor of std::string.\")\n    .Output(\n        0,\n        \"prefixes\",\n        \"Tensor of std::string containing prefixes for each input.\");\n\nOPERATOR_SCHEMA(StringSuffix)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nComputes the element-wise string suffix of the string tensor.\nInput strings that are shorter than suffix length will be returned unchanged.\nNOTE: Prefix is computed on number of bytes, which may lead to wrong behavior\nand potentially invalid strings for variable-length encodings such as utf-8.\n)DOC\")\n    .Input(0, \"strings\", \"Tensor of std::string.\")\n    .Output(\n        0,\n        \"suffixes\",\n        \"Tensor of std::string containing suffixes for each output.\")\n    .Arg(\"length\", \"Maximum size of the suffix, in bytes.\");\n\nOPERATOR_SCHEMA(StringStartsWith)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nPerforms the starts-with check on each string in the input tensor.\nReturns tensor of boolean of the same dimension of input.\n)DOC\")\n    .Arg(\"prefix\", \"The prefix to check input strings against.\")\n    .Input(0, \"strings\", \"Tensor of std::string.\")\n    .Output(0, \"bools\", \"Tensor of bools of same shape as input.\");\n\nOPERATOR_SCHEMA(StringEndsWith)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nPerforms the ends-with check on each string in the input tensor.\nReturns tensor of boolean of the same dimension of input.\n)DOC\")\n    .Arg(\"suffix\", \"The suffix to check input strings against.\")\n    .Input(0, \"strings\", \"Tensor of std::string.\")\n    .Output(0, \"bools\", \"Tensor of bools of same shape as input.\");\n\nOPERATOR_SCHEMA(StringJoin)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nTakes a 1-D or a 2-D tensor as input and joins elements in each row with the\nprovided delimiter. Output is a 1-D tensor of size equal to the first dimension\nof the input. Each element in the output tensor is a string of concatenated\nelements corresponding to each row in the input tensor. For 1-D input, each\nelement is treated as a row.\n)DOC\")\n    .Arg(\"delimiter\", \"Delimiter for join (Default: \\\",\\\").\")\n    .Arg(\"axis\", \"Axis for the join (either 0 or 1)\")\n    .Input(0, \"input\", \"1-D or 2-D tensor\")\n    .Output(\n        0,\n        \"strings\",\n        \"1-D tensor of strings created by joining row elements from the \"\n        \"input tensor.\");\n\nSHOULD_NOT_DO_GRADIENT(StringPrefix);\nSHOULD_NOT_DO_GRADIENT(StringSuffix);\nSHOULD_NOT_DO_GRADIENT(StringStartsWith);\nSHOULD_NOT_DO_GRADIENT(StringEndsWith);\nSHOULD_NOT_DO_GRADIENT(StringJoin);\n}\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/string_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_STRING_OPS_H_\n#define CAFFE2_OPERATORS_STRING_OPS_H_\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\n/**\n * ForEach is a unary functor that forwards each element of the input array\n * into the elementwise Functor provided, and gathers the results of each\n * call into the resulting array. Use it as an adaptor if you want to create\n * a UnaryElementwiseOp that acts on each element of the tensor per function\n * call -- this is resonable for complex types where vectorization wouldn't\n * be much of a gain, performance-wise.\n */\ntemplate <typename Functor>\nstruct ForEach {\n  explicit ForEach(OperatorBase& op) : functor(op) {}\n\n  template <typename In, typename Out, typename Context>\n  void operator()(int n, const In* in, Out* out, Context* /*c*/) {\n    for (int i = 0; i < n; ++i) {\n      out[i] = functor(in[i]);\n    }\n  }\n  Functor functor;\n};\n\ntemplate <typename ScalarFunctor, typename TypeMap = FixedType<std::string>>\nusing StringElementwiseOp = UnaryElementwiseWithArgsOp<\n    TensorTypes<std::string>,\n    CPUContext,\n    ForEach<ScalarFunctor>,\n    TypeMap>;\n\ntemplate <class Context>\nclass StringJoinOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  StringJoinOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        delimiter_(\n            OperatorBase::GetSingleArgument<std::string>(\"delimiter\", \",\")),\n        axis_(OperatorBase::GetSingleArgument<int>(\"axis\", 0)) {\n    CAFFE_ENFORCE(axis_ == 0 || axis_ == 1);\n  }\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<\n        float,\n        double,\n        int8_t,\n        uint8_t,\n        int16_t,\n        uint16_t,\n        int32_t,\n        int64_t,\n        std::string,\n        bool>>::call(this, Input(0));\n  }\n\n  template <typename T>\n  bool DoRunWithType();\n\n protected:\n  std::string delimiter_;\n  int axis_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_STRING_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/string_ops_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n#include <vector>\n\n#include \"caffe2/operators/string_ops.h\"\n\nnamespace caffe2 {\n\nclass StringJoinOpTest : public testing::Test {\n public:\n  bool runOp(const TensorCPU& input) {\n    auto* blob = ws_.CreateBlob(\"X\");\n    auto* tensor = blob->GetMutable<TensorCPU>();\n    tensor->ResizeLike(input);\n    tensor->ShareData(input);\n\n    OperatorDef def;\n    def.set_name(\"test\");\n    def.set_type(\"StringJoin\");\n    def.add_input(\"X\");\n    def.add_output(\"Y\");\n\n    auto op = CreateOperator(def, &ws_);\n    return op->Run();\n  }\n\n  const std::string* checkAndGetOutput(int outputSize) {\n    const auto* output = ws_.GetBlob(\"Y\");\n    EXPECT_NE(output, nullptr);\n    EXPECT_TRUE(output->IsType<TensorCPU>());\n    const auto& outputTensor = output->Get<TensorCPU>();\n    EXPECT_EQ(outputTensor.ndim(), 1);\n    EXPECT_EQ(outputTensor.dim(0), outputSize);\n    EXPECT_EQ(outputTensor.size(), outputSize);\n    return outputTensor.data<std::string>();\n  }\n\n protected:\n  Workspace ws_;\n};\n\nTEST_F(StringJoinOpTest, testString1DJoin) {\n  std::vector<std::string> input = {\"a\", \"xx\", \"c\"};\n\n  auto blob = caffe2::make_unique<Blob>();\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(input.size());\n  auto* data = tensor->mutable_data<std::string>();\n  for (int i = 0; i < input.size(); ++i) {\n    *data++ = input[i];\n  }\n\n  EXPECT_TRUE(runOp(*tensor));\n\n  const auto* outputData = checkAndGetOutput(input.size());\n  EXPECT_EQ(outputData[0], \"a,\");\n  EXPECT_EQ(outputData[1], \"xx,\");\n  EXPECT_EQ(outputData[2], \"c,\");\n}\n\nTEST_F(StringJoinOpTest, testString2DJoin) {\n  std::vector<std::vector<std::string>> input = {{\"aa\", \"bb\", \"cc\"},\n                                                 {\"dd\", \"ee\", \"ff\"}};\n\n  auto blob = caffe2::make_unique<Blob>();\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(input.size(), input[0].size());\n  auto* data = tensor->mutable_data<std::string>();\n  for (int i = 0; i < input.size(); ++i) {\n    for (int j = 0; j < input[0].size(); ++j) {\n      *data++ = input[i][j];\n    }\n  }\n\n  EXPECT_TRUE(runOp(*tensor));\n\n  const auto* outputData = checkAndGetOutput(input.size());\n  EXPECT_EQ(outputData[0], \"aa,bb,cc,\");\n  EXPECT_EQ(outputData[1], \"dd,ee,ff,\");\n}\n\nTEST_F(StringJoinOpTest, testFloat1DJoin) {\n  std::vector<float> input = {3.90f, 5.234f, 8.12f};\n\n  auto blob = caffe2::make_unique<Blob>();\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(input.size());\n  auto* data = tensor->mutable_data<float>();\n  for (int i = 0; i < input.size(); ++i) {\n    *data++ = input[i];\n  }\n\n  EXPECT_TRUE(runOp(*tensor));\n\n  const auto* outputData = checkAndGetOutput(input.size());\n  EXPECT_EQ(outputData[0], \"3.9,\");\n  EXPECT_EQ(outputData[1], \"5.234,\");\n  EXPECT_EQ(outputData[2], \"8.12,\");\n}\n\nTEST_F(StringJoinOpTest, testFloat2DJoin) {\n  std::vector<std::vector<float>> input = {{1.23f, 2.45f, 3.56f},\n                                           {4.67f, 5.90f, 6.32f}};\n\n  auto blob = caffe2::make_unique<Blob>();\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(input.size(), input[0].size());\n  auto* data = tensor->mutable_data<float>();\n  for (int i = 0; i < input.size(); ++i) {\n    for (int j = 0; j < input[0].size(); ++j) {\n      *data++ = input[i][j];\n    }\n  }\n\n  EXPECT_TRUE(runOp(*tensor));\n\n  const auto* outputData = checkAndGetOutput(input.size());\n  EXPECT_EQ(outputData[0], \"1.23,2.45,3.56,\");\n  EXPECT_EQ(outputData[1], \"4.67,5.9,6.32,\");\n}\n\nTEST_F(StringJoinOpTest, testLong2DJoin) {\n  std::vector<std::vector<int64_t>> input = {{100, 200}, {1000, 2000}};\n\n  auto blob = caffe2::make_unique<Blob>();\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(input.size(), input[0].size());\n  auto* data = tensor->mutable_data<int64_t>();\n  for (int i = 0; i < input.size(); ++i) {\n    for (int j = 0; j < input[0].size(); ++j) {\n      *data++ = input[i][j];\n    }\n  }\n\n  EXPECT_TRUE(runOp(*tensor));\n\n  const auto* outputData = checkAndGetOutput(input.size());\n  EXPECT_EQ(outputData[0], \"100,200,\");\n  EXPECT_EQ(outputData[1], \"1000,2000,\");\n}\n}\n"
  },
  {
    "path": "caffe2/operators/stylizer_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/cpu_neon.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n#ifdef __ARM_NEON__\nnamespace {\n\n//\n// ARM Neon code utilities\n//\n\ninline float32x4_t to_v4_f32(uint16x4_t v) {\n  return vcvtq_f32_u32(vmovl_u16(v));\n}\n\ninline float32x4x4_t to_f32_v4_x4(uint8x16_t v) {\n  float32x4x4_t out;\n\n  uint16x8_t lo_u16 = vmovl_u8(vget_low_u8(v));\n\n  out.val[0] = to_v4_f32(vget_low_u16(lo_u16));\n  out.val[1] = to_v4_f32(vget_high_u16(lo_u16));\n\n  uint16x8_t hi_u16 = vmovl_u8(vget_high_u8(v));\n\n  out.val[2] = to_v4_f32(vget_low_u16(hi_u16));\n  out.val[3] = to_v4_f32(vget_high_u16(hi_u16));\n\n  return out;\n}\n\ninline void clamp(float32x4_t& v) {\n  v = vmaxq_f32(v, vdupq_n_f32(0));\n  v = vminq_f32(v, vdupq_n_f32((float)std::numeric_limits<uint8_t>::max()));\n}\n\ninline void addMeanAndClamp(float32x4_t& v, float mean) {\n  v = vaddq_f32(v, vdupq_n_f32(mean));\n  clamp(v);\n}\n\ninline uint8x8_t convertNarrowAndPack(float32x4_t v0, float32x4_t v1) {\n  uint16x4_t u16_0 = vmovn_u32(vcvtq_u32_f32(v0));\n  uint16x4_t u16_1 = vmovn_u32(vcvtq_u32_f32(v1));\n  uint16x8_t u16_01 = vcombine_u16(u16_0, u16_1);\n  return vmovn_u16(u16_01);\n}\n\n} // unnamed namespace\n#endif // __ARM_NEON__\n\nclass PackedInt8BGRANHWCToNCHWCStylizerPreprocessOp\n    : public Operator<CPUContext> {\n public:\n  // Expect this many channels as input\n  static constexpr int kInputChannels = 4;\n\n  // Expect this many channels as output\n  static constexpr int kOutputChannels = 3;\n\n  // We read this much noise per vectorized cycle\n  static constexpr int kNeonNoiseReadSize = kOutputChannels * 16;\n\n  USE_OPERATOR_FUNCTIONS(CPUContext);\n  PackedInt8BGRANHWCToNCHWCStylizerPreprocessOp(\n      const OperatorDef& operator_def,\n      Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws), ws_(ws) {}\n\n  bool RunOnDevice() {\n    const auto& X = Input(0);\n    const auto& mean = Input(1);\n    auto* Y = Output(0);\n    auto* noiseBlob = ws_->CreateBlob(\"__CAFFE2_STYLIZER_NOISE__\");\n    auto defaultNoiseSize = OperatorBase::GetSingleArgument<int>(\n        \"noise_size\", 491 /* prime to avoid artifacts */);\n\n    if (!noiseBlob->IsType<TensorCPU>()) {\n      // Initialize random noise on first use.\n      // Cache it to maintain temporal consistency.\n      auto* t = noiseBlob->template GetMutable<TensorCPU>();\n\n#ifdef __ARM_NEON__\n      // Noise space is larger for vectorized code due to the\n      // vectorized load\n      initNoiseCPUNeon(t, defaultNoiseSize);\n#else\n      initNoiseCPU(t, defaultNoiseSize);\n#endif\n    }\n    const auto& noise = noiseBlob->template Get<TensorCPU>();\n    CAFFE_ENFORCE(noise.size() >= defaultNoiseSize);\n\n    CAFFE_ENFORCE(X.ndim() == 4);\n    const int N = X.dim32(0), H = X.dim32(1), W = X.dim32(2), C = X.dim32(3);\n    // Assume BGR or BGRA\n    CAFFE_ENFORCE(mean.size() == kOutputChannels);\n\n    CAFFE_ENFORCE(C == kInputChannels);\n    Y->Resize(N, kOutputChannels, H, W);\n\n    runBatch(\n        N,\n        C,\n        H,\n        W,\n        defaultNoiseSize,\n        X.data<uint8_t>(),\n        mean.data<float>(),\n        noise.data<float>(),\n        Y->mutable_data<float>());\n\n    return true;\n  }\n\n#ifndef __ARM_NEON__\n  void initNoiseCPU(Tensor<CPUContext>* noise, int size) {\n    noise->Resize(size);\n\n    math::RandGaussian<float, CPUContext>(\n        size,\n        0.0,\n        OperatorBase::GetSingleArgument<float>(\"noise_std\", 10.0),\n        noise->template mutable_data<float>(),\n        &context_);\n  }\n#endif // !__ARM_NEON__\n\n#ifdef __ARM_NEON__\n  void initNoiseCPUNeon(Tensor<CPUContext>* noise, int size) {\n    // For ARM NEON, we read in multiples of kNeonNoiseReadSize since\n    // the inner loop is vectorized. Round up to the next highest\n    // multiple of kNeonNoiseReadSize\n    size = math::roundUp(size, kNeonNoiseReadSize) + size;\n    noise->Resize(size);\n\n    math::RandGaussian<float, CPUContext>(\n        size,\n        0.0,\n        OperatorBase::GetSingleArgument<float>(\"noise_std\", 10.0),\n        noise->template mutable_data<float>(),\n        &context_);\n  }\n#endif // __ARM_NEON\n\n  void runBatch(\n      int N,\n      int /*C*/,\n      int H,\n      int W,\n      int noiseCycle,\n      const uint8_t* input,\n      const float* meanChannel,\n      const float* noise,\n      float* output) {\n    int planeSize = H * W;\n\n    for (int n = 0; n < N; ++n) {\n      auto curInput = input + n * kInputChannels * planeSize;\n      auto curOutput = output + n * kOutputChannels * planeSize;\n\n#ifdef __ARM_NEON__\n      runCPUNeon(H, W, noiseCycle, curInput, meanChannel, noise, curOutput);\n#else\n      runCPU(H, W, noiseCycle, curInput, meanChannel, noise, curOutput);\n#endif // __ARM_NEON__\n    }\n  }\n\n#ifndef __ARM_NEON__\n  void runCPU(\n      int H,\n      int W,\n      int noiseCycle,\n      const uint8_t* input,\n      const float* meanChannel,\n      const float* noise,\n      float* output) {\n    int planeSize = H * W;\n    int noiseOffset = 0;\n\n    for (int point = 0; point < planeSize; ++point) {\n      for (int c = 0; c < kOutputChannels; ++c) {\n        float v = (float)input[point * kInputChannels + c];\n        output[c * planeSize + point] = v - meanChannel[c] + noise[noiseOffset];\n\n        if (++noiseOffset >= noiseCycle) {\n          noiseOffset = 0;\n        }\n      }\n    }\n  }\n#endif // !__ARM_NEON__\n\n#ifdef __ARM_NEON__\n  void runCPUNeon(\n      int H,\n      int W,\n      int noiseCycle,\n      const uint8_t* input,\n      const float* meanChannel,\n      const float* noise,\n      float* output) {\n    // Vectorized load parameters:\n\n    // Loop unroll factor\n    // FIXME: this doesn't actually unroll; clang has per-loop unroll\n    // pragmas but GCC does not\n    constexpr int kUnroll = 1;\n\n    // How much data we load for each inner loop\n    constexpr int kInnerLoadSize = sizeof(uint8x16x4_t);\n\n    // What we write out\n    constexpr int kInnerStoreSize = sizeof(float32x4_t);\n\n    // We load 16 pixels at a time, with 4 channels each\n    constexpr int kLoadPixels = kInnerLoadSize / kInputChannels;\n    static_assert(kLoadPixels == 16, \"unexpected\");\n\n    // How many pixels we load per loop\n    constexpr int kLoadPixelsPerLoop = kLoadPixels * kUnroll;\n\n    // We need at least this much noise each loop through\n    CAFFE_ENFORCE_GE(noiseCycle, kOutputChannels * kLoadPixelsPerLoop);\n\n    int noiseUsed = 0;\n    const float* curNoise = noise;\n\n    float mean[kOutputChannels] = {\n        meanChannel[0], meanChannel[1], meanChannel[2]};\n    int planeSize = H * W;\n\n    // Vectorized portion\n    int point = 0;\n\n    // If the slice is not aligned, then we have to use the\n    // un-vectorized version\n    bool isAligned = isPointerAligned(input, kInnerLoadSize) &&\n        isPointerAligned(output, kInnerStoreSize) &&\n        // Because we are writing to output at offsets of planeSize,\n        // planeSize has to be an even multiple of kInnerStoreSize\n        (planeSize % kInnerStoreSize == 0);\n\n    // What portion the vectorized loop will handle\n    int limit =\n        isAligned ? (planeSize / kLoadPixelsPerLoop) * kLoadPixelsPerLoop : 0;\n\n    for (; point < limit; point += kLoadPixelsPerLoop) {\n      // Unroll load/update/store by kUnroll\n      for (int j = 0; j < kUnroll; ++j) {\n        // We load 16 pixels x 4 channels at a time\n        const uint8_t* inputAligned = (const uint8_t*)__builtin_assume_aligned(\n            input + (point + j * kLoadPixels) * kInputChannels,\n            sizeof(uint8x16x4_t));\n        uint8x16x4_t loadV = vld4q_u8(inputAligned);\n\n        // The compiler doesn't want to unroll this when we put it in a\n        // loop, and in GCC there's no per-loop unroll pragma, so we do\n        // it manually.\n        // This seems to involve no register spillage, crossing fingers\n        // that it remains that way.\n        {\n          constexpr int kChannel = 0;\n          float32x4_t noise0 = vld1q_f32(curNoise + j * 48 + 0);\n          float32x4_t noise1 = vld1q_f32(curNoise + j * 48 + 4);\n          float32x4_t noise2 = vld1q_f32(curNoise + j * 48 + 8);\n          float32x4_t noise3 = vld1q_f32(curNoise + j * 48 + 12);\n\n          float32x4x4_t outV = to_f32_v4_x4(loadV.val[kChannel]);\n          float32x4_t meanV = vdupq_n_f32(mean[kChannel]);\n          outV.val[0] = vsubq_f32(outV.val[0], meanV);\n          outV.val[1] = vsubq_f32(outV.val[1], meanV);\n          outV.val[2] = vsubq_f32(outV.val[2], meanV);\n          outV.val[3] = vsubq_f32(outV.val[3], meanV);\n\n          outV.val[0] = vaddq_f32(outV.val[0], noise0);\n          outV.val[1] = vaddq_f32(outV.val[1], noise1);\n          outV.val[2] = vaddq_f32(outV.val[2], noise2);\n          outV.val[3] = vaddq_f32(outV.val[3], noise3);\n\n          float* outputAligned = (float*)__builtin_assume_aligned(\n              &output[kChannel * planeSize + (point + j * kLoadPixels)],\n              sizeof(float32x4_t));\n\n          vst1q_f32(outputAligned + 0, outV.val[0]);\n          vst1q_f32(outputAligned + 4, outV.val[1]);\n          vst1q_f32(outputAligned + 8, outV.val[2]);\n          vst1q_f32(outputAligned + 12, outV.val[3]);\n        }\n\n        {\n          constexpr int kChannel = 1;\n          float32x4_t noise0 = vld1q_f32(curNoise + j * 48 + 16);\n          float32x4_t noise1 = vld1q_f32(curNoise + j * 48 + 20);\n          float32x4_t noise2 = vld1q_f32(curNoise + j * 48 + 24);\n          float32x4_t noise3 = vld1q_f32(curNoise + j * 48 + 28);\n\n          float32x4x4_t outV = to_f32_v4_x4(loadV.val[kChannel]);\n          float32x4_t meanV = vdupq_n_f32(mean[kChannel]);\n          outV.val[0] = vsubq_f32(outV.val[0], meanV);\n          outV.val[1] = vsubq_f32(outV.val[1], meanV);\n          outV.val[2] = vsubq_f32(outV.val[2], meanV);\n          outV.val[3] = vsubq_f32(outV.val[3], meanV);\n\n          outV.val[0] = vaddq_f32(outV.val[0], noise0);\n          outV.val[1] = vaddq_f32(outV.val[1], noise1);\n          outV.val[2] = vaddq_f32(outV.val[2], noise2);\n          outV.val[3] = vaddq_f32(outV.val[3], noise3);\n\n          float* outputAligned = (float*)__builtin_assume_aligned(\n              &output[kChannel * planeSize + (point + j * kLoadPixels)],\n              sizeof(float32x4_t));\n\n          vst1q_f32(outputAligned + 0, outV.val[0]);\n          vst1q_f32(outputAligned + 4, outV.val[1]);\n          vst1q_f32(outputAligned + 8, outV.val[2]);\n          vst1q_f32(outputAligned + 12, outV.val[3]);\n        }\n\n        {\n          constexpr int kChannel = 2;\n          float32x4_t noise0 = vld1q_f32(curNoise + j * 48 + 32);\n          float32x4_t noise1 = vld1q_f32(curNoise + j * 48 + 36);\n          float32x4_t noise2 = vld1q_f32(curNoise + j * 48 + 40);\n          float32x4_t noise3 = vld1q_f32(curNoise + j * 48 + 44);\n\n          float32x4x4_t outV = to_f32_v4_x4(loadV.val[kChannel]);\n          float32x4_t meanV = vdupq_n_f32(mean[kChannel]);\n          outV.val[0] = vsubq_f32(outV.val[0], meanV);\n          outV.val[1] = vsubq_f32(outV.val[1], meanV);\n          outV.val[2] = vsubq_f32(outV.val[2], meanV);\n          outV.val[3] = vsubq_f32(outV.val[3], meanV);\n\n          outV.val[0] = vaddq_f32(outV.val[0], noise0);\n          outV.val[1] = vaddq_f32(outV.val[1], noise1);\n          outV.val[2] = vaddq_f32(outV.val[2], noise2);\n          outV.val[3] = vaddq_f32(outV.val[3], noise3);\n\n          float* outputAligned = (float*)__builtin_assume_aligned(\n              &output[kChannel * planeSize + (point + j * kLoadPixels)],\n              sizeof(float32x4_t));\n\n          vst1q_f32(outputAligned + 0, outV.val[0]);\n          vst1q_f32(outputAligned + 4, outV.val[1]);\n          vst1q_f32(outputAligned + 8, outV.val[2]);\n          vst1q_f32(outputAligned + 12, outV.val[3]);\n        }\n      }\n\n      curNoise += (kLoadPixels * kOutputChannels) * kUnroll;\n      noiseUsed += (kLoadPixels * kOutputChannels) * kUnroll;\n\n      if (noiseUsed >= noiseCycle) {\n        noiseUsed = 0;\n        curNoise = noise + ((curNoise - noise) % noiseCycle);\n      }\n    }\n\n    // Epilogue: non-vectorized remainder\n    for (; point < planeSize; ++point) {\n      for (int c = 0; c < kOutputChannels; ++c) {\n        float v = (float)input[point * kInputChannels + c];\n        output[c * planeSize + point] = v - mean[c] + *curNoise++;\n        ++noiseUsed;\n      }\n\n      if (noiseUsed >= noiseCycle) {\n        noiseUsed = 0;\n        curNoise = noise + ((curNoise - noise) % noiseCycle);\n      }\n    }\n  }\n#endif // __ARM_NEON__\n\n private:\n  Workspace* ws_;\n};\n\nnamespace {\n\ntemplate <typename T>\nstatic inline T clamped_cast(float f) {\n  if (f >= std::numeric_limits<T>::max()) {\n    return std::numeric_limits<T>::max();\n  }\n  if (f <= std::numeric_limits<T>::min()) {\n    return std::numeric_limits<T>::min();\n  }\n  return static_cast<T>(f);\n}\n\n} // unnamed namespace\n\nclass BRGNCHWCToPackedInt8BGRAStylizerDeprocessOp\n    : public Operator<CPUContext> {\n public:\n  using Operator<CPUContext>::Operator;\n\n  // Expect this many channels as input\n  static constexpr int kInputChannels = 3;\n\n  // Expect this many channels as output\n  static constexpr int kOutputChannels = 4;\n\n  bool RunOnDevice() {\n    const auto& X = Input(0);\n    const auto& mean = Input(1);\n    auto* Y = Output(0);\n    CAFFE_ENFORCE(X.ndim() == 4);\n    const int N = X.dim32(0), C = X.dim32(1), H = X.dim32(2), W = X.dim32(3);\n    // Assume BGR or BGRA\n    CAFFE_ENFORCE(mean.size() == kInputChannels);\n    CAFFE_ENFORCE(C == kInputChannels);\n    // RGB\n    Y->Resize(N, H, W, kOutputChannels);\n\n    runBatch(\n        N,\n        C,\n        H,\n        W,\n        X.data<float>(),\n        mean.data<float>(),\n        Y->mutable_data<uint8_t>());\n\n    return true;\n  }\n\n  void runBatch(\n      int N,\n      int /*C*/,\n      int H,\n      int W,\n      const float* input,\n      const float* meanChannel,\n      uint8_t* output) {\n    int planeSize = H * W;\n\n    for (int n = 0; n < N; ++n) {\n      auto curInput = input + n * kInputChannels * planeSize;\n      auto curOutput = output + n * kOutputChannels * planeSize;\n\n#ifdef __ARM_NEON__\n      runCPUNeon(H, W, curInput, meanChannel, curOutput);\n#else\n      runCPU(H, W, curInput, meanChannel, curOutput);\n#endif // __ARM_NEON__\n    }\n  }\n\n#ifndef __ARM_NEON__\n  void runCPU(\n      int H,\n      int W,\n      const float* input,\n      const float* meanChannel,\n      uint8_t* output) {\n    int planeSize = H * W;\n\n    for (int point = 0; point < planeSize; ++point) {\n      for (int c = 0; c < kInputChannels; ++c) {\n        uint8_t v = clamped_cast<uint8_t>(\n            input[c * planeSize + point] + meanChannel[c]);\n        output[point * kOutputChannels + c] = v;\n      }\n\n      // alpha\n      output[point * kOutputChannels + (kOutputChannels - 1)] =\n          std::numeric_limits<uint8_t>::max();\n    }\n  }\n#endif // !__ARM_NEON__\n\n#ifdef __ARM_NEON__\n  void runCPUNeon(\n      int H,\n      int W,\n      const float* input,\n      const float* meanChannel,\n      uint8_t* output) {\n    // Vectorized load parameters:\n\n    // We load in chunks of this size\n    constexpr int kLoadUnit = sizeof(float32x4_t);\n    constexpr int kLoadFloats = (sizeof(float32x4_t) / sizeof(float));\n\n    // We store in chunks of this size\n    constexpr int kStoreUnit = sizeof(uint8x8x4_t);\n\n    // The vector portion loads this many f32 pixels at a time (8)\n    constexpr int kLoadPixels = 2 * kLoadFloats;\n\n    float mean[kInputChannels] = {\n        meanChannel[0], meanChannel[1], meanChannel[2]};\n    int planeSize = H * W;\n\n    // Vectorized portion\n    int point = 0;\n\n    // If the slice is not aligned, then we have to use the\n    // un-vectorized version\n    bool isAligned = isPointerAligned(input, kLoadUnit) &&\n        isPointerAligned(output, kStoreUnit) &&\n        // Because we are reading from input at offsets of planeSize,\n        // planeSize has to be an even multiple of kLoadUnit\n        (planeSize % kLoadUnit == 0);\n\n    // What portion the vectorized loop will handle\n    int limit = isAligned ? (planeSize / kLoadPixels) * kLoadPixels : 0;\n\n    for (; point < limit; point += kLoadPixels) {\n      // Load 8 f32 pixels from each channel; loading 16 involves\n      // register spills it seems\n      float32x4_t inputc0_0 =\n          vld1q_f32_aligned(input + 0 * planeSize + point + 0 * kLoadFloats);\n      float32x4_t inputc0_1 =\n          vld1q_f32_aligned(input + 0 * planeSize + point + 1 * kLoadFloats);\n\n      float32x4_t inputc1_0 =\n          vld1q_f32_aligned(input + 1 * planeSize + point + 0 * kLoadFloats);\n      float32x4_t inputc1_1 =\n          vld1q_f32_aligned(input + 1 * planeSize + point + 1 * kLoadFloats);\n\n      float32x4_t inputc2_0 =\n          vld1q_f32_aligned(input + 2 * planeSize + point + 0 * kLoadFloats);\n      float32x4_t inputc2_1 =\n          vld1q_f32_aligned(input + 2 * planeSize + point + 1 * kLoadFloats);\n\n      addMeanAndClamp(inputc0_0, mean[0]);\n      addMeanAndClamp(inputc0_1, mean[0]);\n      uint8x8_t u8_c0 = convertNarrowAndPack(inputc0_0, inputc0_1);\n\n      addMeanAndClamp(inputc1_0, mean[1]);\n      addMeanAndClamp(inputc1_1, mean[1]);\n      uint8x8_t u8_c1 = convertNarrowAndPack(inputc1_0, inputc1_1);\n\n      addMeanAndClamp(inputc2_0, mean[2]);\n      addMeanAndClamp(inputc2_1, mean[2]);\n      uint8x8_t u8_c2 = convertNarrowAndPack(inputc2_0, inputc2_1);\n\n      // This is the alpha channel\n      uint8x8_t u8_c3 = vdup_n_u8(std::numeric_limits<uint8_t>::max());\n\n      // We now have 8 bytes of each channel in a separate vector\n      // Write BGRA interleaved to output\n      uint8x8x4_t u8_out = {{ u8_c0, u8_c1, u8_c2, u8_c3 }};\n      vst4_u8_aligned(output + kOutputChannels * point, u8_out);\n    }\n\n    // Epilogue: non-vectorized remainder\n    for (; point < planeSize; ++point) {\n      for (int c = 0; c < kInputChannels; ++c) {\n        uint8_t v =\n            clamped_cast<uint8_t>(input[c * planeSize + point] + mean[c]);\n        output[point * kOutputChannels + c] = v;\n      }\n\n      // alpha\n      output[point * kOutputChannels + (kOutputChannels - 1)] =\n          std::numeric_limits<uint8_t>::max();\n    }\n  }\n#endif // __ARM_NEON__\n};\n\nnamespace {\n\nREGISTER_CPU_OPERATOR(\n    PackedInt8BGRANHWCToNCHWCStylizerPreprocess,\n    PackedInt8BGRANHWCToNCHWCStylizerPreprocessOp);\nOPERATOR_SCHEMA(PackedInt8BGRANHWCToNCHWCStylizerPreprocess)\n    .NumInputs(2)\n    .NumOutputs(1);\nREGISTER_CPU_OPERATOR(\n    BRGNCHWCToPackedInt8BGRAStylizerDeprocess,\n    BRGNCHWCToPackedInt8BGRAStylizerDeprocessOp);\nOPERATOR_SCHEMA(BRGNCHWCToPackedInt8BGRAStylizerDeprocess)\n    .NumInputs(2)\n    .NumOutputs(1);\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/summarize_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/summarize_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool SummarizeOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  const auto N = X.size();\n  CAFFE_ENFORCE_GT(N, 0);\n\n  const float* Xdata = X.data<float>();\n  double mean = 0;\n  float max = Xdata[0];\n  float min = Xdata[0];\n  for (auto i = 0; i < N; ++i) {\n    mean += static_cast<double>(Xdata[i]) / N;\n    max = std::max(max, Xdata[i]);\n    min = std::min(min, Xdata[i]);\n  }\n  // We will simply do a two-pass. More efficient solutions can be written but\n  // I'll keep code simple for now.\n  double standard_deviation = 0;\n  for (auto i = 0; i < N; ++i) {\n    double diff = Xdata[i] - mean;\n    standard_deviation += diff * diff;\n  }\n  // Unbiased or biased? Let's do unbiased now.\n  standard_deviation = N == 1 ? 0 : std::sqrt(standard_deviation / (N - 1));\n  if (to_file_) {\n    (*log_file_) << min << \" \" << max << \" \" << mean << \" \"\n                 << standard_deviation << std::endl;\n  }\n  if (OutputSize()) {\n    auto* Y = Output(0);\n    Y->Resize(NUM_STATS);\n    float* Ydata = Y->mutable_data<float>();\n    Ydata[MIN_IDX] = min;\n    Ydata[MAX_IDX] = max;\n    Ydata[MEAN_IDX] = static_cast<float>(mean);\n    Ydata[STD_IDX] = static_cast<float>(standard_deviation);\n  }\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(Summarize, SummarizeOp<float, CPUContext>);\n\n// Input: X; output: if set, a summarized Tensor of shape 4, with the values\n// being min, max, mean and std respectively.\nOPERATOR_SCHEMA(Summarize)\n    .NumInputs(1)\n    .NumOutputs(0, 1)\n    .SetDoc(R\"DOC(\nSummarize computes four statistics of the input tensor (Tensor<float>)- min,\nmax, mean and standard deviation. The output will be written to a 1-D tensor of\nsize 4 if an output tensor is provided. Else, if the argument 'to_file' is\ngreater than 0, the values are written to a log file in the root folder.\n)DOC\")\n    .Arg(\n        \"to_file\",\n        \"(int, default 0) flag to indicate if the summarized \"\n        \"statistics have to be written to a log file.\")\n    .Input(0, \"data\", \"The input data as Tensor<float>.\")\n    .Output(\n        0,\n        \"output\",\n        \"1-D tensor (Tensor<float>) of size 4 containing min, \"\n        \"max, mean and standard deviation\");\n\nSHOULD_NOT_DO_GRADIENT(Summarize);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/summarize_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cuda.h>\n#include <thrust/device_vector.h>\n#include <thrust/transform_reduce.h>\n#include <thrust/system/cuda/execution_policy.h>\n\n#include \"caffe2/operators/summarize_op.h\"\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n// structure used to accumulate the moments and other statistical properties\n// encountered so far.\ntemplate <typename T>\nstruct SummaryStatsData {\n  T n;\n  T min;\n  T max;\n  T mean;\n  T M2;\n\n  // initialize to the identity element\n  void initialize() {\n    n = mean = M2 = 0;\n    min = std::numeric_limits<T>::max();\n    max = std::numeric_limits<T>::min();\n  }\n\n  T variance() { return (n == 1 ? 0 : M2 / (n - 1)); }\n};\n\n// stats_unary_op is a functor that takes in a value x and\n// returns a variace_data whose mean value is initialized to x.\ntemplate <typename T>\nstruct summary_stats_unary_op {\n  __host__ __device__ SummaryStatsData<T> operator()(const T& x) const {\n     SummaryStatsData<T> result;\n     result.n    = 1;\n     result.min  = x;\n     result.max  = x;\n     result.mean = x;\n     result.M2   = 0;\n     return result;\n  }\n};\n\n// summary_stats_binary_op is a functor that accepts two SummaryStatsData\n// structs and returns a new SummaryStatsData which are an\n// approximation to the summary_stats for\n// all values that have been agregated so far\ntemplate <typename T>\nstruct summary_stats_binary_op\n    : public thrust::binary_function<const SummaryStatsData<T>&,\n                                     const SummaryStatsData<T>&,\n                                           SummaryStatsData<T> > {\n  __host__ __device__ SummaryStatsData<T> operator()(\n      const SummaryStatsData<T>& x, const SummaryStatsData <T>& y) const {\n    SummaryStatsData<T> result;\n    T n  = x.n + y.n;\n    T delta  = y.mean - x.mean;\n    T delta2 = delta  * delta;\n    result.n   = n;\n    result.min = thrust::min(x.min, y.min);\n    result.max = thrust::max(x.max, y.max);\n    result.mean = x.mean + delta * y.n / n;\n    result.M2  = x.M2 + y.M2;\n    result.M2 += delta2 * x.n * y.n / n;\n    return result;\n  }\n};\n\n}  // namespace\n\ntemplate<>\nbool SummarizeOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  const int N = X.size();\n  DCHECK_GT(N, 0);\n\n  // TODO(Yangqing): Any better way to avoid having to const cast?\n  thrust::device_ptr<float> Xdata(const_cast<float*>(X.data<float>()));\n  summary_stats_unary_op<float> unary_op;\n  summary_stats_binary_op<float> binary_op;\n  SummaryStatsData<float> init;\n  init.initialize();\n  // compute summary statistics\n  SummaryStatsData<float> result = thrust::transform_reduce(\n#if THRUST_VERSION >= 100800\n      thrust::cuda::par.on(context_.cuda_stream()),\n#endif  // THRUST_VERSION >= 100800\n      Xdata, Xdata + N, unary_op, init, binary_op);\n  float standard_deviation = std::sqrt(result.variance());\n  if (to_file_) {\n    (*log_file_) << result.min << \" \" << result.max << \" \" << result.mean << \" \"\n                 << standard_deviation << std::endl;\n  }\n  if (OutputSize()) {\n    auto* Y = OperatorBase::Output<TensorCUDA>(0);\n    Y->Resize(4);\n    float output_buffer[NUM_STATS] = {result.min, result.max, result.mean,\n                               standard_deviation};\n    context_.Copy<float, CPUContext, CUDAContext>(\n        NUM_STATS, output_buffer, Y->mutable_data<float>());\n  }\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Summarize, SummarizeOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/summarize_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_SUMMARIZE_OP_H_\n#define CAFFE2_OPERATORS_SUMMARIZE_OP_H_\n\n#include <fstream>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nconstexpr char kSummaryzeOpExtension[] = \".summary\";\n\ntemplate <typename T, class Context>\nclass SummarizeOp final : public Operator<Context> {\n public:\n  SummarizeOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        to_file_(OperatorBase::GetSingleArgument<int>(\"to_file\", 0)) {\n    if (to_file_) {\n      // We will output to file instead of printing on screen.\n      const string& target_folder = ws->RootFolder();\n      // We will write each individual tensor to its individual file.\n      // Also, since the namescope is currently represented by \"/\", we will\n      // need to replace it with a symbol that does not conflict with the\n      // folder separator in Linux.\n      string proper_name = def.input(0);\n      std::replace(proper_name.begin(), proper_name.end(), '/', '#');\n      log_file_.reset(new std::ofstream(\n          target_folder + \"/\" + proper_name + kSummaryzeOpExtension,\n          std::ofstream::out | std::ofstream::trunc));\n      CAFFE_ENFORCE(\n          log_file_->good(),\n          \"Failed to open summarize file for tensor \",\n          def.input(0),\n          \". rdstate() = \",\n          log_file_->rdstate());\n    }\n  }\n  ~SummarizeOp() {\n    if (to_file_)\n      log_file_->close();\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  bool RunOnDevice() override;\n\n  static constexpr int MIN_IDX = 0;\n  static constexpr int MAX_IDX = 1;\n  static constexpr int MEAN_IDX = 2;\n  static constexpr int STD_IDX = 3;\n\n  static constexpr int NUM_STATS = 4;\n\n protected:\n  bool to_file_;\n  std::unique_ptr<std::ofstream> log_file_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_SUMMARIZE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/swish_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"swish_op.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\nstruct SwishCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* /*device_context*/) {\n    ConstEigenVectorArrayMap<T> xM(x, n);\n    EigenVectorArrayMap<T>(y, n) = xM / (1. + (-xM).exp());\n  }\n};\n\ntemplate <>\ntemplate <typename T>\nbool SwishGradientOp<CPUContext>::DoRunWithType() {\n  auto& Xin = Input(X);\n  auto& Yin = Input(Y);\n  auto& DYin = Input(DY);\n  auto* DXout = Output(DX);\n  CAFFE_ENFORCE_EQ(Xin.size(), Yin.size());\n  CAFFE_ENFORCE_EQ(DYin.size(), Yin.size());\n  DXout->ResizeLike(Yin);\n\n  const float* Xdata = Xin.template data<float>();\n  const float* Ydata = Yin.template data<float>();\n  const float* dYdata = DYin.template data<float>();\n  float* dXdata = DXout->template mutable_data<float>();\n\n  EigenVectorArrayMap<float> dXvec(dXdata, DXout->size());\n  ConstEigenVectorArrayMap<float> Xvec(Xdata, Xin.size());\n  ConstEigenVectorArrayMap<float> Yvec(Ydata, Yin.size());\n  ConstEigenVectorArrayMap<float> dYvec(dYdata, DYin.size());\n\n  // dx = dy * (y + sigmoid(x)*(1-y))\n  dXvec = dYvec * (Yvec + (1. / (1. + (-Xvec).exp())) * (1. - Yvec));\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(\n    Swish,\n    UnaryElementwiseOp<\n        TensorTypes<float, double>,\n        CPUContext,\n        SwishCPUFunctor>);\nREGISTER_CPU_OPERATOR(SwishGradient, SwishGradientOp<CPUContext>);\n\n// Input: X, output: Y\nOPERATOR_SCHEMA(Swish)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nSwish takes one input data (Tensor<T>) and produces one output data\n(Tensor<T>) where the swish function, y = x / (1 + exp(-x)), is applied to the\ntensor elementwise.\n)DOC\")\n    .Input(0, \"X\", \"1D input tensor\")\n    .Output(0, \"Y\", \"1D output tensor\");\n// Input: X, Y, dY, output: dX\nOPERATOR_SCHEMA(SwishGradient)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .AllowInplace({{2, 0}})\n    .SetDoc(R\"DOC(\nSwishGradient takes X, Y and dY and uses this to update dX according to the\nchain rule and derivatives of the swish function.\n)DOC\");\n\nREGISTER_GRADIENT(Swish, GetSwishGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/swish_op.cu",
    "content": "/**\n * Copyright (c) 2018-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cmath>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/operators/swish_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void SwishKernel(const int N, const T* x, T* y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    y[i] = x[i] / (1. + exp(-x[i]));\n  }\n}\n\ntemplate <typename T>\n__global__ void\nSwishGradientKernel(const int N, const T* x, const T* y, const T* dy, T* dx) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dx[i] = dy[i] * (y[i] + (1. - y[i]) / (1. + exp(-x[i])));\n  }\n}\n\nstruct SwishCUDAFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CUDAContext* device_context) {\n    SwishKernel<T>\n        <<<CAFFE_GET_BLOCKS(n),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           device_context->cuda_stream()>>>(n, x, y);\n    return;\n  }\n};\n\ntemplate <>\ntemplate <typename T>\nbool SwishGradientOp<CUDAContext>::DoRunWithType() {\n  auto& Xin = Input(X);\n  auto& Yin = Input(Y);\n  auto& DYin = Input(DY);\n  auto* DXout = Output(DX);\n  CAFFE_ENFORCE_EQ(Xin.size(), Yin.size());\n  CAFFE_ENFORCE_EQ(DYin.size(), Yin.size());\n  DXout->ResizeLike(Yin);\n\n  const int n = Xin.size();\n  const T* x = Xin.template data<T>();\n  const T* y = Yin.template data<T>();\n  const T* dy = DYin.template data<T>();\n  T* dx = DXout->template mutable_data<T>();\n  SwishGradientKernel<T>\n      <<<CAFFE_GET_BLOCKS(n),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(n, x, y, dy, dx);\n  return true;\n}\n\ntemplate <>\nbool SwishGradientOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<float, double>>::call(this, Input(X));\n}\n\nREGISTER_CUDA_OPERATOR(\n    Swish,\n    UnaryElementwiseOp<\n        TensorTypes<float, double>,\n        CUDAContext,\n        SwishCUDAFunctor>);\nREGISTER_CUDA_OPERATOR(SwishGradient, SwishGradientOp<CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/swish_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\ntemplate <class Context>\nclass SwishGradientOp final : public Operator<Context> {\n public:\n  USE_SIMPLE_CTOR_DTOR(SwishGradientOp)\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  template <typename T>\n  bool DoRunWithType();\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<float, double>>::call(this, Input(X));\n  }\n\n protected:\n  INPUT_TAGS(X, Y, DY);\n  OUTPUT_TAGS(DX);\n};\n\nclass GetSwishGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SwishGradient\",\n        \"\",\n        vector<string>{I(0), O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/tanh_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cmath>\n\n#include \"caffe2/operators/elementwise_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nstruct TanhCPUFunctor {\n  template <typename T>\n  inline void\n  operator()(const int n, const T* x, T* y, CPUContext* /*device_context*/) {\n#ifdef CAFFE2_USE_ACCELERATE\n    vvtanhf(y, x, &n);\n#else\n    ConstEigenVectorArrayMap<T> x_arr(x, n);\n    EigenVectorMap<T>(y, n) = 1 - 2 * ((x_arr * 2).exp() + 1).inverse();\n#endif\n  }\n};\n\nstruct TanhGradientCPUFunctor {\n  template <typename T>\n  inline void Run(\n      const int n,\n      const T* y,\n      const T* dy,\n      T* dx,\n      CPUContext* /*device_context*/) {\n    ConstEigenVectorArrayMap<T> dy_arr(dy, n);\n    ConstEigenVectorArrayMap<T> y_arr(y, n);\n    EigenVectorMap<T>(dx, n) = dy_arr * (1 - y_arr * y_arr);\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n    Tanh, UnaryElementwiseOp<TensorTypes<float>, CPUContext, TanhCPUFunctor>);\nREGISTER_CPU_OPERATOR(\n    TanhGradient,\n    BinaryElementwiseOp<\n        TensorTypes<float>,\n        CPUContext,\n        WithoutBroadcast<TanhGradientCPUFunctor>>);\n\nOPERATOR_SCHEMA(Tanh)\n  .NumInputs(1)\n  .NumOutputs(1)\n  .AllowInplace({{0, 0}})\n  .IdenticalTypeAndShape()\n  .SetDoc(R\"DOC(\nCalculates the hyperbolic tangent of the given input tensor element-wise. This\noperation can be done in an in-place fashion too, by providing the same input\nand output blobs.\n)DOC\")\n  .Input(0, \"input\", \"1-D input tensor\")\n  .Output(0, \"output\", \"The hyperbolic tangent values of the input tensor \"\n          \"computed element-wise\");\n\nOPERATOR_SCHEMA(TanhGradient).NumInputs(2).NumOutputs(1).AllowInplace({{1, 0}});\n\nclass GetTanhGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"TanhGradient\", \"\",\n        std::vector<string>{O(0), GO(0)},\n        std::vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Tanh, GetTanhGradient);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/tanh_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cmath>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/elementwise_op.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\n__global__ void TanhKernel(const int N, const T* X, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = tanh(X[i]);\n  }\n}\n\ntemplate <typename T>\n__global__ void TanhGradientKernel(const int N, const T* Y, const T* dY,\n                              T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dX[i] = dY[i]*(1 - Y[i]*Y[i]);\n  }\n}\n\nstruct TanhCUDAFunctor {\n  template <typename T>\n  inline void operator()(const int n, const T* x,\n                         T* y, CUDAContext* device_context) {\n    TanhKernel<T><<<CAFFE_GET_BLOCKS(n), CAFFE_CUDA_NUM_THREADS,\n                    0, device_context->cuda_stream()>>>(n, x, y);\n    return;\n  }\n  inline bool InplaceAllowed() {\n    return true;\n  }\n};\n\nstruct TanhGradientCUDAFunctor {\n  template <typename T>\n  inline void Run(const int n, const T* y, const T* dy,\n                  T* dx, CUDAContext* device_context) {\n    TanhGradientKernel<T><<<CAFFE_GET_BLOCKS(n), CAFFE_CUDA_NUM_THREADS,\n                            0, device_context->cuda_stream()>>>(n, y, dy, dx);\n    return;\n  }\n};\n\nREGISTER_CUDA_OPERATOR(\n    Tanh, UnaryElementwiseOp<TensorTypes<float>, CUDAContext, TanhCUDAFunctor>);\nREGISTER_CUDA_OPERATOR(\n    TanhGradient, BinaryElementwiseOp<\n        TensorTypes<float>, CUDAContext,\n        WithoutBroadcast<TanhGradientCUDAFunctor>>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/tensor_protos_db_input.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/tensor_protos_db_input.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(TensorProtosDBInput, TensorProtosDBInput<CPUContext>);\n\nOPERATOR_SCHEMA(TensorProtosDBInput)\n  .NumInputs(1)\n  .NumOutputs(1, INT_MAX)\n  .SetDoc(R\"DOC(\nTensorProtosDBInput is a simple input operator that basically reads things\nfrom a db where each key-value pair stores an index as key, and a TensorProtos\nobject as value. These TensorProtos objects should have the same size, and they\nwill be grouped into batches of the given size. The DB Reader is provided as\ninput to the operator and it returns as many output tensors as the size of the\nTensorProtos object. Each output will simply be a tensor containing a batch of\ndata with size specified by the 'batch_size' argument containing data from the\ncorresponding index in the TensorProtos objects in the DB.\n)DOC\")\n  .Arg(\"batch_size\", \"(int, default 0) the number of samples in a batch. The \"\n       \"default value of 0 means that the operator will attempt to insert the \"\n       \"entire data in a single output blob.\")\n  .Input(0, \"data\", \"A pre-initialized DB reader. Typically, this is obtained \"\n         \"by calling CreateDB operator with a db_name and a db_type. The \"\n         \"resulting output blob is a DB Reader tensor\")\n  .Output(0, \"output\", \"The output tensor in which the batches of data are \"\n          \"returned. The number of output tensors is equal to the size of \"\n          \"(number of TensorProto's in) the TensorProtos objects stored in the \"\n          \"DB as values. Each output tensor will be of size specified by the \"\n          \"'batch_size' argument of the operator\");\n\nNO_GRADIENT(TensorProtosDBInput);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/tensor_protos_db_input.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_TENSOR_PROTOS_DB_INPUT_H_\n#define CAFFE2_OPERATORS_TENSOR_PROTOS_DB_INPUT_H_\n\n#include <iostream>\n#include <mutex>\n\n#include \"caffe2/core/db.h\"\n#include \"caffe2/operators/prefetch_op.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass TensorProtosDBInput final : public PrefetchOperator<Context> {\n public:\n  using OperatorBase::OutputSize;\n  using PrefetchOperator<Context>::prefetch_thread_;\n  explicit TensorProtosDBInput(const OperatorDef& operator_def, Workspace* ws);\n  ~TensorProtosDBInput() {\n    PrefetchOperator<Context>::Finalize();\n  }\n\n  bool Prefetch() override;\n  bool CopyPrefetched() override;\n\n private:\n  // Prefetch will always just happen on the CPU side.\n  vector<Blob> prefetched_blobs_;\n  int batch_size_;\n  bool shape_inferred_ = false;\n  string key_;\n  string value_;\n};\n\ntemplate <class Context>\nTensorProtosDBInput<Context>::TensorProtosDBInput(\n    const OperatorDef& operator_def,\n    Workspace* ws)\n    : PrefetchOperator<Context>(operator_def, ws),\n      prefetched_blobs_(operator_def.output_size()),\n      batch_size_(\n          OperatorBase::template GetSingleArgument<int>(\"batch_size\", 0)) {}\n\ntemplate <class Context>\nbool TensorProtosDBInput<Context>::Prefetch() {\n  const db::DBReader& reader = OperatorBase::Input<db::DBReader>(0);\n  TensorDeserializer<CPUContext> deserializer;\n  if (batch_size_ == 0) {\n    // We do not need to construct a batch. As a result, we will simply\n    // deserialize everything into the target prefetched blob.\n    reader.Read(&key_, &value_);\n    TensorProtos protos;\n    CAFFE_ENFORCE(protos.ParseFromString(value_));\n    CAFFE_ENFORCE(protos.protos_size() == OutputSize());\n    for (int i = 0; i < protos.protos_size(); ++i) {\n      if (protos.protos(i).has_device_detail()) {\n        protos.mutable_protos(i)->clear_device_detail();\n      }\n      deserializer.Deserialize(\n          protos.protos(i),\n          prefetched_blobs_[i].template GetMutable<TensorCPU>());\n    }\n  } else {\n    vector<TensorCPU> temp_tensors(OutputSize());\n    for (int item_id = 0; item_id < batch_size_; ++item_id) {\n      reader.Read(&key_, &value_);\n      TensorProtos protos;\n      CAFFE_ENFORCE(protos.ParseFromString(value_));\n      CAFFE_ENFORCE(protos.protos_size() == OutputSize());\n      if (!shape_inferred_) {\n        // First, set the shape of all the blobs.\n        for (int i = 0; i < protos.protos_size(); ++i) {\n          vector<int> dims(\n              protos.protos(i).dims().begin(), protos.protos(i).dims().end());\n          dims.insert(dims.begin(), batch_size_);\n          prefetched_blobs_[i].template GetMutable<TensorCPU>()->Resize(dims);\n        }\n      }\n      for (int i = 0; i < protos.protos_size(); ++i) {\n        TensorCPU* dst = prefetched_blobs_[i].template GetMutable<TensorCPU>();\n        TensorCPU& src = temp_tensors[i];\n        if (protos.protos(i).has_device_detail()) {\n          protos.mutable_protos(i)->clear_device_detail();\n        }\n        deserializer.Deserialize(protos.protos(i), &src);\n        DCHECK_EQ(src.size() * batch_size_, dst->size());\n        this->context_.template CopyItems<CPUContext, CPUContext>(\n            src.meta(),\n            src.size(),\n            src.raw_data(),\n            static_cast<char*>(dst->raw_mutable_data(src.meta())) +\n                src.nbytes() * item_id);\n      }\n    }\n  }\n  return true;\n}\n\ntemplate <class Context>\nbool TensorProtosDBInput<Context>::CopyPrefetched() {\n  for (int i = 0; i < OutputSize(); ++i) {\n    OperatorBase::Output<Tensor<Context>>(i)->CopyFrom(\n        prefetched_blobs_[i].template Get<TensorCPU>(), &this->context_);\n  }\n  return true;\n}\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_TENSOR_PROTOS_DB_INPUT_H_\n"
  },
  {
    "path": "caffe2/operators/tensor_protos_db_input_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/tensor_protos_db_input.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(TensorProtosDBInput, TensorProtosDBInput<CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/text_file_reader.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/operators/text_file_reader_utils.h\"\n#include \"caffe2/utils/string_utils.h\"\n\nnamespace caffe2 {\n\nstruct TextFileReaderInstance {\n  TextFileReaderInstance(\n      const std::vector<char>& delims,\n      char escape,\n      const std::string& filename,\n      int numPasses,\n      const std::vector<int>& types)\n      : fileReader(filename),\n        tokenizer(Tokenizer(delims, escape), &fileReader, numPasses),\n        fieldTypes(types) {\n    for (const auto dt : fieldTypes) {\n      fieldMetas.push_back(\n          DataTypeToTypeMeta(static_cast<TensorProto_DataType>(dt)));\n      fieldByteSizes.push_back(fieldMetas.back().itemsize());\n    }\n  }\n\n  FileReader fileReader;\n  BufferedTokenizer tokenizer;\n  std::vector<int> fieldTypes;\n  std::vector<TypeMeta> fieldMetas;\n  std::vector<size_t> fieldByteSizes;\n  size_t rowsRead{0};\n\n  // hack to guarantee thread-safeness of the read op\n  // TODO(azzolini): support multi-threaded reading.\n  std::mutex globalMutex_;\n};\n\nclass CreateTextFileReaderOp : public Operator<CPUContext> {\n public:\n  CreateTextFileReaderOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        filename_(GetSingleArgument<string>(\"filename\", \"\")),\n        numPasses_(GetSingleArgument<int>(\"num_passes\", 1)),\n        fieldTypes_(GetRepeatedArgument<int>(\"field_types\")) {\n    CAFFE_ENFORCE(fieldTypes_.size() > 0, \"field_types arg must be non-empty\");\n  }\n\n  bool RunOnDevice() override {\n    *OperatorBase::Output<std::unique_ptr<TextFileReaderInstance>>(0) =\n        std::unique_ptr<TextFileReaderInstance>(new TextFileReaderInstance(\n            {'\\n', '\\t'}, '\\0', filename_, numPasses_, fieldTypes_));\n    return true;\n  }\n\n private:\n  std::string filename_;\n  int numPasses_;\n  std::vector<int> fieldTypes_;\n};\n\ninline void convert(\n    TensorProto_DataType dst_type,\n    const char* src_start,\n    const char* src_end,\n    void* dst) {\n  switch (dst_type) {\n    case TensorProto_DataType_STRING: {\n      static_cast<std::string*>(dst)->assign(src_start, src_end);\n    } break;\n    case TensorProto_DataType_FLOAT: {\n      // TODO(azzolini): avoid copy, use faster convertion\n      std::string str_copy(src_start, src_end);\n      const char* src_copy = str_copy.c_str();\n      char* src_copy_end;\n      float val = strtof(src_copy, &src_copy_end);\n      if (src_copy == src_copy_end) {\n        throw std::runtime_error(\"Invalid float: \" + str_copy);\n      }\n      *static_cast<float*>(dst) = val;\n    } break;\n    default:\n      throw std::runtime_error(\"Unsupported type.\");\n  }\n}\n\nclass TextFileReaderReadOp : public Operator<CPUContext> {\n public:\n  TextFileReaderReadOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        batchSize_(GetSingleArgument<int>(\"batch_size\", 1)) {}\n\n  bool RunOnDevice() override {\n    const int numFields = OutputSize();\n    CAFFE_ENFORCE(numFields > 0, \"Expected at least one output.\");\n\n    auto instance =\n        OperatorBase::Input<std::unique_ptr<TextFileReaderInstance>>(0).get();\n\n    CAFFE_ENFORCE(\n        instance->fieldTypes.size() == numFields,\n        \"Invalid number of outputs. Expected \" +\n            to_string(instance->fieldTypes.size()) + \" got \" +\n            to_string(numFields));\n\n    // char* datas[numFields];\n    // MSVC does not allow using const int, so we will need to dynamically allocate\n    // it.\n    std::vector<char*> datas(numFields);\n    for (int i = 0; i < numFields; ++i) {\n      Output(i)->Resize(batchSize_);\n      datas[i] = (char*)Output(i)->raw_mutable_data(instance->fieldMetas[i]);\n    }\n\n    int rowsRead = 0;\n    {\n      // TODO(azzolini): support multi-threaded reading\n      std::lock_guard<std::mutex> guard(instance->globalMutex_);\n\n      bool finished = false;\n      Token token;\n      while (!finished && (rowsRead < batchSize_)) {\n        int field;\n        for (field = 0; field < numFields; ++field) {\n          finished = !instance->tokenizer.next(token);\n          if (finished) {\n            CAFFE_ENFORCE(\n                field == 0, \"Invalid number of fields at end of file.\");\n            break;\n          }\n          CAFFE_ENFORCE(\n              (field == 0 && token.startDelimId == 0) ||\n                  (field > 0 && token.startDelimId == 1),\n              \"Invalid number of columns at row \",\n              instance->rowsRead + rowsRead + 1);\n          const auto& meta = instance->fieldMetas[field];\n          char*& data = datas[field];\n          convert(\n              (TensorProto_DataType)instance->fieldTypes[field],\n              token.start,\n              token.end,\n              data);\n          data += instance->fieldByteSizes[field];\n        }\n        if (!finished) {\n          ++rowsRead;\n        }\n      }\n      instance->rowsRead += rowsRead;\n    }\n\n    for (int i = 0; i < numFields; ++i) {\n      Output(i)->Shrink(rowsRead);\n    }\n    return true;\n  }\n\n private:\n  TIndex batchSize_;\n};\n\nCAFFE_KNOWN_TYPE(std::unique_ptr<TextFileReaderInstance>);\n\nREGISTER_CPU_OPERATOR(CreateTextFileReader, CreateTextFileReaderOp);\nREGISTER_CPU_OPERATOR(TextFileReaderRead, TextFileReaderReadOp);\n\nOPERATOR_SCHEMA(CreateTextFileReader)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(\"Create a text file reader. Fields are delimited by <TAB>.\")\n    .Arg(\"filename\", \"Path to the file.\")\n    .Arg(\"num_passes\", \"Number of passes over the file.\")\n    .Arg(\n        \"field_types\",\n        \"List with type of each field. Type enum is found at core.DataType.\")\n    .Output(0, \"handler\", \"Pointer to the created TextFileReaderInstance.\");\n\nOPERATOR_SCHEMA(TextFileReaderRead)\n    .NumInputs(1)\n    .NumOutputs(1, INT_MAX)\n    .SetDoc(\n        \"Read a batch of rows from the given text file reader instance. \"\n        \"Expects the number of fields to be equal to the number of outputs. \"\n        \"Each output is a 1D tensor containing the values for the given field \"\n        \"for each row. When end of file is reached, returns empty tensors.\")\n    .Input(0, \"handler\", \"Pointer to an existing TextFileReaderInstance.\")\n    .Arg(\"batch_size\", \"Maximum number of rows to read.\");\n\nNO_GRADIENT(CreateTextFileReader);\nNO_GRADIENT(TextFileReaderRead);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/text_file_reader_utils.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/text_file_reader_utils.h\"\n\n#include <fcntl.h>\n#include <cerrno>\n#include <cstring>\n#include <sstream>\n\nnamespace caffe2 {\n\nTokenizer::Tokenizer(const std::vector<char>& delims, char escape)\n    : escape_(escape) {\n  reset();\n  std::memset(delimTable_, 0, sizeof(delimTable_));\n  for (int i = 0; i < delims.size(); ++i) {\n    delimTable_[(unsigned char)delims.at(i)] = i + 1;\n  }\n}\n\nvoid Tokenizer::reset() {\n  toBeSkipped_ = 0;\n  startDelimId_ = 0;\n  leftover_.clear();\n}\n\nvoid Tokenizer::next(char* start, char* end, TokenizedString& tokenized) {\n  tokenized.modifiedStrings_.clear();\n  tokenized.tokens_.clear();\n\n  char* currentStart = start;\n  std::string* copied = nullptr;\n  if (!leftover_.empty()) {\n    tokenized.modifiedStrings_.emplace_back(new std::string());\n    copied = tokenized.modifiedStrings_.back().get();\n    *copied = std::move(leftover_);\n  }\n\n  char* ch;\n  for (ch = start + toBeSkipped_; ch < end; ++ch) {\n    if (*ch == escape_) {\n      if (!copied) {\n        tokenized.modifiedStrings_.emplace_back(new std::string());\n        copied = tokenized.modifiedStrings_.back().get();\n      }\n      copied->append(currentStart, ch);\n      currentStart = ch + 1;\n      // skip next character, since it's escaped\n      ++ch;\n      continue;\n    }\n    int newDelimId = delimTable_[(unsigned char)*ch];\n    if (newDelimId > 0) {\n      // found delimiter\n      tokenized.tokens_.emplace_back();\n      auto& token = tokenized.tokens_.back();\n      token.startDelimId = startDelimId_;\n      if (copied) {\n        copied->append(currentStart, ch);\n        const char* c_str = copied->data();\n        token.start = c_str;\n        token.end = c_str + copied->size();\n      } else {\n        token.start = currentStart;\n        token.end = ch;\n      }\n      currentStart = ch + 1;\n      copied = nullptr;\n      startDelimId_ = newDelimId - 1;\n    }\n  }\n  tokenized.lastDelim_ = startDelimId_;\n\n  toBeSkipped_ = ch - end;\n  if (copied) {\n    copied->append(currentStart, end);\n    leftover_ = std::move(*copied);\n  } else {\n    leftover_.assign(currentStart, end);\n  }\n}\n\nFileReader::FileReader(const std::string& path, size_t bufferSize)\n    : bufferSize_(bufferSize), buffer_(new char[bufferSize]) {\n  fd_ = open(path.c_str(), O_RDONLY, 0777);\n  if (fd_ < 0) {\n    throw std::runtime_error(\n        \"Error opening file for reading: \" + std::string(std::strerror(errno)) +\n        \" Path=\" + path);\n  }\n}\n\nvoid FileReader::reset() {\n  if (lseek(fd_, 0, SEEK_SET) == -1) {\n    throw std::runtime_error(\n        \"Error reseting file cursor: \" + std::string(std::strerror(errno)));\n  }\n}\n\nFileReader::~FileReader() {\n  if (fd_ >= 0) {\n    close(fd_);\n  }\n}\n\nvoid FileReader::operator()(CharRange& range) {\n  char* buffer = buffer_.get();\n  auto numRead = read(fd_, buffer, bufferSize_);\n  if (numRead == -1) {\n    throw std::runtime_error(\n        \"Error reading file: \" + std::string(std::strerror(errno)));\n  }\n  if (numRead == 0) {\n    range.start = nullptr;\n    range.end = nullptr;\n    return;\n  }\n  range.start = buffer;\n  range.end = buffer + numRead;\n}\n}\n"
  },
  {
    "path": "caffe2/operators/text_file_reader_utils.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_TEXT_FILE_READER_UTILS_H\n#define CAFFE2_OPERATORS_TEXT_FILE_READER_UTILS_H\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"caffe2/core/common.h\"\n\nnamespace caffe2 {\n\nstruct Token {\n  int startDelimId;\n  const char* start;\n  const char* end;\n};\n\nclass TokenizedString {\n  // holder for strings that have been modified\n  std::vector<std::unique_ptr<std::string>> modifiedStrings_;\n  std::vector<Token> tokens_;\n  int lastDelim_;\n\n public:\n  const std::vector<Token>& tokens() const {\n    return tokens_;\n  }\n  int lastDelim() const {\n    return lastDelim_;\n  }\n  friend class Tokenizer;\n};\n\nclass Tokenizer {\n private:\n  int startDelimId_;\n  // state of the tokenizer\n  std::string leftover_;\n  // if we need to skip the first characters of the next batch because\n  // e.g. an escape char that was the last character of the last batch.\n  int toBeSkipped_;\n  int delimTable_[256];\n  const char escape_;\n\n public:\n  Tokenizer(const std::vector<char>& delimiters, char escape);\n  void reset();\n  void next(char* start, char* end, TokenizedString& tokenized);\n};\n\nstruct CharRange {\n  char* start;\n  char* end;\n};\n\nstruct StringProvider {\n  virtual void operator()(CharRange&) = 0;\n  virtual void reset() = 0;\n  virtual ~StringProvider() {}\n};\n\nclass BufferedTokenizer {\n public:\n  BufferedTokenizer(const Tokenizer& t, StringProvider* p, int numPasses = 1)\n      : provider_(p), tokenizer_(t), tokenIndex_(0), numPasses_(numPasses) {}\n\n  bool next(Token& token) {\n    CharRange range;\n    while (tokenIndex_ >= tokenized_.tokens().size()) {\n      range.start = nullptr;\n      while (range.start == nullptr && pass_ < numPasses_) {\n        (*provider_)(range);\n        if (range.start == nullptr) {\n          ++pass_;\n          if (pass_ < numPasses_) {\n            provider_->reset();\n            tokenizer_.reset();\n          }\n        }\n      }\n      if (range.start == nullptr) {\n        return false;\n      }\n      tokenizer_.next(range.start, range.end, tokenized_);\n      tokenIndex_ = 0;\n    }\n    token = tokenized_.tokens()[tokenIndex_++];\n    return true;\n  };\n\n  int endDelim() const {\n    if (tokenIndex_ + 1 < tokenized_.tokens().size()) {\n      return tokenized_.tokens()[tokenIndex_ + 1].startDelimId;\n    }\n    return tokenized_.lastDelim();\n  }\n\n private:\n  StringProvider* provider_;\n  Tokenizer tokenizer_;\n  TokenizedString tokenized_;\n  int tokenIndex_;\n  int numPasses_;\n  int pass_{0};\n};\n\nclass FileReader : public StringProvider {\n public:\n  explicit FileReader(const std::string& path, size_t bufferSize = 65536);\n  ~FileReader();\n  void operator()(CharRange& range) override;\n  void reset() override;\n\n private:\n  const size_t bufferSize_;\n  int fd_;\n  std::unique_ptr<char[]> buffer_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_TEXT_FILE_READER_UTILS_H\n"
  },
  {
    "path": "caffe2/operators/text_file_reader_utils_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <fstream>\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/math.h\"\n#include <gtest/gtest.h>\n\n#include \"caffe2/operators/text_file_reader_utils.h\"\n#include \"caffe2/utils/string_utils.h\"\n\n#include <cstdio>\n#include <cstdlib>\n\nnamespace caffe2 {\n\nTEST(TextFileReaderUtilsTest, TokenizeTest) {\n  TokenizedString tokenized;\n  std::string ch =\n      \"label\\1text\\xc3\\xbf\\nlabel2\\\\\\nTest\\1tex\\\\\\\\t2\\n\"\n      \"Two\\\\\\\\Escapes\\\\\\1\\1Second\\n\";\n  std::vector<char> seps = {'\\n', '\\1'};\n  Tokenizer tokenizer(seps, '\\\\');\n  tokenizer.next(&ch.front(), &ch.back() + 1, tokenized);\n\n  std::vector<std::pair<int, std::string>> expected = {{0, \"label\"},\n                                                       {1, \"text\\xc3\\xbf\"},\n                                                       {0, \"label2\\nTest\"},\n                                                       {1, \"tex\\\\t2\"},\n                                                       {0, \"Two\\\\Escapes\\1\"},\n                                                       {1, \"Second\"}};\n\n  EXPECT_EQ(expected.size(), tokenized.tokens().size());\n  for (int i = 0; i < expected.size(); ++i) {\n    const auto& token = tokenized.tokens().at(i);\n    EXPECT_EQ(expected.at(i).first, token.startDelimId);\n    EXPECT_EQ(expected.at(i).second, std::string(token.start, token.end));\n  }\n\n  // try each of the subsplits\n  for (int i = 0; i < ch.size() - 1; ++i) {\n    tokenizer.reset();\n    char* mid = &ch.front() + i;\n\n    tokenizer.next(&ch.front(), mid, tokenized);\n    EXPECT_GE(expected.size(), tokenized.tokens().size());\n    for (int j = 0; j < tokenized.tokens().size(); ++j) {\n      const auto& token = tokenized.tokens().at(j);\n      EXPECT_EQ(expected.at(j).first, token.startDelimId);\n      EXPECT_EQ(expected.at(j).second, std::string(token.start, token.end));\n    }\n    int s1 = tokenized.tokens().size();\n\n    tokenizer.next(mid, &ch.back() + 1, tokenized);\n    EXPECT_EQ(expected.size(), s1 + tokenized.tokens().size());\n    for (int j = 0; j < tokenized.tokens().size(); ++j) {\n      const auto& token = tokenized.tokens().at(j);\n      EXPECT_EQ(expected.at(j + s1).first, token.startDelimId);\n      EXPECT_EQ(\n          expected.at(j + s1).second, std::string(token.start, token.end));\n    }\n    EXPECT_EQ(0, tokenized.lastDelim());\n  }\n\n  struct ChunkProvider : public StringProvider {\n    ChunkProvider(const std::string& str) : ch(str) {}\n    std::string ch;\n    size_t charIdx{0};\n    void operator()(CharRange& range) {\n      if (charIdx >= ch.size()) {\n        range.start = nullptr;\n        range.end = nullptr;\n      } else {\n        size_t endIdx = std::min(charIdx + 10, ch.size());\n        range.start = &ch.front() + charIdx;\n        range.end = &ch.front() + endIdx;\n        charIdx = endIdx;\n      }\n    };\n    void reset() {\n      charIdx = 0;\n    }\n  };\n\n  for (int numPasses = 1; numPasses <= 2; ++numPasses) {\n    ChunkProvider chunkProvider(ch);\n    BufferedTokenizer bt(tokenizer, &chunkProvider, numPasses);\n    Token token;\n    int i = 0;\n    for (i = 0; bt.next(token); ++i) {\n      EXPECT_GT(expected.size() * numPasses, i);\n      const auto& expectedToken = expected.at(i % expected.size());\n      EXPECT_EQ(expectedToken.first, token.startDelimId);\n      EXPECT_EQ(expectedToken.second, std::string(token.start, token.end));\n    }\n    EXPECT_EQ(expected.size() * numPasses, i);\n    EXPECT_EQ(0, bt.endDelim());\n  }\n\n  char* tmpname = std::tmpnam(nullptr);\n  std::ofstream outFile;\n  outFile.open(tmpname);\n  outFile << ch;\n  outFile.close();\n  for (int numPasses = 1; numPasses <= 2; ++numPasses) {\n    FileReader fr(tmpname, 5);\n    BufferedTokenizer fileTokenizer(tokenizer, &fr, numPasses);\n    Token token;\n    int i;\n    for (i = 0; fileTokenizer.next(token); ++i) {\n      EXPECT_GT(expected.size() * numPasses, i);\n      const auto& expectedToken = expected.at(i % expected.size());\n      EXPECT_EQ(expectedToken.first, token.startDelimId);\n      EXPECT_EQ(expectedToken.second, std::string(token.start, token.end));\n    }\n    EXPECT_EQ(expected.size() * numPasses, i);\n    EXPECT_EQ(0, fileTokenizer.endDelim());\n  }\n  std::remove(tmpname);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/thresholded_relu_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/thresholded_relu_op.h\"\n\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool ThresholdedReluOp<float, CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  Y->ResizeLike(X);\n\n  ConstEigenVectorArrayMap<float> Xvec(X.data<float>(), X.size());\n  EigenVectorArrayMap<float> Yvec(Y->mutable_data<float>(), Y->size());\n  Yvec = (Xvec > alpha_).select(Xvec, 0.f);\n  /* Naive implementation\n  const float* Xdata = X.data<float>();\n  float* Ydata = Y->mutable_data<float>();\n  for (int i = 0; i < X.size(); ++i) {\n    Xdata[i] -= alpha_;\n    Ydata[i] = std::max(Xdata[i], 0.0f);\n  }\n  */\n  return true;\n}\n\ntemplate <>\nbool ThresholdedReluGradientOp<float, CPUContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  CAFFE_ENFORCE_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n\n  const float* Ydata = Y.data<float>();\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n  EigenVectorArrayMap<float> dXvec(dXdata, dX->size());\n  ConstEigenVectorArrayMap<float> Yvec(Ydata, Y.size());\n  ConstEigenVectorArrayMap<float> dYvec(dYdata, dY.size());\n  dXvec = dYvec * Yvec.cwiseSign();\n  /* Non vectorized implementation\n  for (int i = 0; i < Y.size(); ++i) {\n    dXdata[i] = Ydata[i] > 0 ? dYdata[i] : 0;\n  }\n  */\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(ThresholdedRelu, ThresholdedReluOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    ThresholdedReluGradient,\n    ThresholdedReluGradientOp<float, CPUContext>);\n\n// Input: X, output: Y\nOPERATOR_SCHEMA(ThresholdedRelu)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .CostInferenceFunction(PointwiseCostInference<2>)\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nThresholdedRelu takes one input data (Tensor) and produces one output data\n(Tensor) where the rectified linear function, y = x for x > alpha, y = 0\notherwise, is applied to the tensor elementwise.\n)DOC\")\n    .Arg(\"alpha\", \"(float) defaults to 1.0.\")\n    .Input(0, \"X\", \"1D input tensor\")\n    .Output(0, \"Y\", \"1D input tensor\");\n\n// Input: Y, dY, output: dX\nOPERATOR_SCHEMA(ThresholdedReluGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{1, 0}})\n    .SetDoc(R\"DOC(\nThresholdedReluGradient takes both Y and dY and uses this to update dX\naccording to the chain rule and derivatives of the rectified linear function.\n)DOC\");\n\nclass GetThresholdedReluGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        def_.type() + \"Gradient\",\n        \"\",\n        vector<string>{O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(ThresholdedRelu, GetThresholdedReluGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/thresholded_relu_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/thresholded_relu_op.h\"\n\nnamespace caffe2 {\nnamespace {\ntemplate <typename T>\n__global__ void ThresholdedReluKernel(const int N, const T* X, T* Y, T alpha_) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = X[i] > alpha_ ? X[i] : 0;\n  }\n}\n\ntemplate <typename T>\n__global__ void\nThresholdedReluGradientKernel(const int N, const T* Y, const T* dY, T* dX) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    dX[i] = Y[i] > 0 ? dY[i] : 0;\n  }\n}\n} // namespace\n\ntemplate <>\nbool ThresholdedReluOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE_GT(X.size(), 0);\n  Y->ResizeLike(X);\n  ThresholdedReluKernel<<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(), X.data<float>(), Y->mutable_data<float>(), alpha_);\n  return true;\n}\n\ntemplate <>\nbool ThresholdedReluGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& Y = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  CAFFE_ENFORCE_GT(Y.size(), 0);\n  CAFFE_ENFORCE_EQ(dY.size(), Y.size());\n  dX->ResizeLike(Y);\n  ThresholdedReluGradientKernel<<<\n      CAFFE_GET_BLOCKS(Y.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      Y.size(), Y.data<float>(), dY.data<float>(), dX->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(ThresholdedRelu, ThresholdedReluOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    ThresholdedReluGradient,\n    ThresholdedReluGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/thresholded_relu_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_THRESHOLDED_RELU_OP_H_\n#define CAFFE2_OPERATORS_THRESHOLDED_RELU_OP_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass ThresholdedReluOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ThresholdedReluOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    alpha_ = OperatorBase::GetSingleArgument<T>(\"alpha\", 1.0);\n  }\n\n  bool RunOnDevice() override;\n\n protected:\n  T alpha_;\n};\n\ntemplate <typename T, class Context>\nclass ThresholdedReluGradientOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  ThresholdedReluGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    alpha_ = OperatorBase::GetSingleArgument<T>(\"alpha\", 1.0);\n  }\n\n  bool RunOnDevice() override;\n\n protected:\n  T alpha_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_THRESHOLDED_RELU_OP_H_\n"
  },
  {
    "path": "caffe2/operators/tile_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/tile_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Tile, TileOp<CPUContext>);\nREGISTER_CPU_OPERATOR(TileGradient, TileGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Tile)\n    .NumInputs(1, 3)\n    .NumOutputs(1)\n    .TensorInferenceFunction(\n        [](const OperatorDef& def, const vector<TensorShape>& in) {\n          vector<TensorShape> out(1);\n          out[0] = TensorShape(in[0]);\n          ArgumentHelper helper(def);\n\n          auto tiles = helper.GetSingleArgument<int32_t>(\"tiles\", 1);\n          auto axis = helper.GetSingleArgument<int32_t>(\"axis\", 0);\n          if (in.size() > 1) {\n            // Tile or axis is specified as input; we can't determine\n            // the size\n            out[0].set_unknown_shape(true);\n          } else {\n            const auto canonical_axis =\n                canonical_axis_index_(axis, out[0].dims().size());\n            out[0].set_dims(\n                canonical_axis, out[0].dims().Get(canonical_axis) * tiles);\n          }\n          return out;\n        })\n    .SetDoc(R\"DOC(\nConstructs a tensor by tiling a given tensor along a specified axis.\n\nThis operation creates a new tensor by replicating the input tensor 'tiles'\ntimes along dimension 'axis'. The output tensor's 'axis'th dimension has\ninput.dims(axis) * tiles elements, and the values of input are replicated\n'tiles' times along the 'axis'th dimension.\nFor example, tiling [[a b c d]] by tile=2, axis=0 produces\n[[a b c d], [a b c d]].\n)DOC\")\n    .Arg(\"tiles\", \"Number of replicas\")\n    .Arg(\"axis\", \"Axis to replicate along\")\n    .Input(0, \"data\", \"The input tensor.\")\n    .Input(1, \"tiles\", \"(optional) Number of replicas (overrides argument)\")\n    .Input(2, \"axis\", \"(optional) Axis to replicate along (overrides argument)\")\n    .Output(\n        0,\n        \"tiled_data\",\n        \"Tensor that will contain input replicated along the given axis.\");\n\nOPERATOR_SCHEMA(TileGradient).NumInputs(1, 3).NumOutputs(1);\n\nclass GetTileGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    // Check whether the tiles/axis information was\n    // passed through input arguments\n    vector<std::string> g_inputs({GO(0)});\n    if (Def().input_size() > 1) {\n      g_inputs.push_back(I(1));\n    }\n    if (Def().input_size() > 2) {\n      g_inputs.push_back(I(2));\n    }\n    return SingleGradientDef(\n        \"TileGradient\", \"\", g_inputs, vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(Tile, GetTileGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/tile_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cub/block/block_reduce.cuh>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/tile_op.h\"\n\nnamespace caffe2 {\nnamespace {\n__global__ void TileCopyKernel(\n    int item_size,\n    int outer_dim,\n    int inner_dim,\n    int tiles,\n    const char* input_data,\n    char* output_data) {\n  CUDA_1D_KERNEL_LOOP(index, outer_dim * tiles) {\n    int i = index / tiles;\n    int t = index % tiles;\n    const char* input_ptr = input_data + inner_dim * item_size * i;\n    char* output_ptr = output_data + (i * tiles + t) * inner_dim * item_size;\n    memcpy(output_ptr, input_ptr, inner_dim * item_size);\n  }\n}\n\ntemplate <typename T>\n__global__ void TileGradientAxpyKernel(\n    int outer_dim,\n    int inner_dim,\n    int tiles,\n    const T* input_data,\n    T* output_data) {\n  typedef cub::BlockReduce<T, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n\n  for (int idx = blockIdx.x; idx < outer_dim * inner_dim; idx += gridDim.x) {\n    int i = idx / inner_dim;\n    int j = idx % inner_dim;\n    T* output_ptr = output_data + inner_dim * i;\n\n    T x = 0.0;\n    for (int t = threadIdx.x; t < tiles; t += blockDim.x) {\n      const T* input_ptr = input_data + (i * tiles + t) * inner_dim;\n      x += input_ptr[j];\n    }\n    __shared__ typename BlockReduce::TempStorage temp_storage;\n    T totx = BlockReduce(temp_storage).Sum(x);\n    if (threadIdx.x == 0) {\n      output_ptr[j] = totx;\n    }\n    __syncthreads();\n  }\n}\n} // namespace\n\ntemplate <>\nvoid TileOp<CUDAContext>::DoTile(\n    const TypeMeta& meta,\n    int item_size,\n    int outer_dim,\n    int inner_dim,\n    const char* input_data,\n    char* output_data) {\n  TileCopyKernel<<<\n      std::min(outer_dim * tiles_, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      item_size, outer_dim, inner_dim, tiles_, input_data, output_data);\n}\n\ntemplate <>\nvoid TileGradientOp<float, CUDAContext>::DoTileGradient(\n    const TypeMeta& meta,\n    int item_size,\n    int outer_dim,\n    int inner_dim,\n    const char* input_data,\n    char* output_data) {\n  TileGradientAxpyKernel<float><<<\n      std::min(outer_dim * inner_dim, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      outer_dim,\n      inner_dim,\n      tiles_,\n      reinterpret_cast<const float*>(input_data),\n      reinterpret_cast<float*>(output_data));\n}\n\nREGISTER_CUDA_OPERATOR(Tile, TileOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(TileGradient, TileGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/tile_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_TILE_OP_H_\n#define CAFFE2_OPERATORS_TILE_OP_H_\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n// Copy a Blob n times along a specified axis.\ntemplate <class Context>\nclass TileOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  TileOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        tiles_(OperatorBase::GetSingleArgument<int32_t>(\"tiles\", 1)),\n        axis_(OperatorBase::GetSingleArgument<int32_t>(\"axis\", 0)) {}\n  ~TileOp() {}\n\n  bool RunOnDevice() override {\n    const auto& input = Input(0);\n    std::array<int32_t, 2> temp_params = {{tiles_, axis_}};\n    if (InputSize() > 1) {\n      // We potentially have tiles and/or axis specified as inputs\n      // as well. We will check for them in that order. In other words:\n      // InputSize() == 2: tiles is specified\n      // InputSize() == 3: tiles is specified and axis.\n      // Anything specified as input will override the arguments\n      CAFFE_ENFORCE(\n          Input(1).ndim() == 1 && Input(1).size() == 1,\n          \"Input `tiles` should be a vector of size 1.\");\n\n      const auto& input1 = Input(1);\n      context_.template CopyItems<Context, CPUContext>(\n          input1.meta(),\n          1,\n          static_cast<const char*>(input1.raw_data()),\n          &(temp_params[0]));\n\n      if (InputSize() > 2) {\n        CAFFE_ENFORCE(\n            Input(2).ndim() == 1 && Input(2).size() == 1,\n            \"Input `axis` should be a vector of size 1.\");\n\n        const auto& input2 = Input(2);\n        context_.template CopyItems<Context, CPUContext>(\n            input2.meta(),\n            1,\n            static_cast<const char*>(input2.raw_data()),\n            &(temp_params[1]));\n      } else {\n        CAFFE_ENFORCE(\n            OperatorBase::HasArgument(\"axis\"),\n            \"Argument `axis` is missing and was not specified as input.\");\n      }\n    } else {\n      CAFFE_ENFORCE(\n          OperatorBase::HasArgument(\"tiles\"),\n          \"Argument `tiles` is missing and was not specified as input.\");\n      CAFFE_ENFORCE(\n          OperatorBase::HasArgument(\"axis\"),\n          \"Argument `axis` is missing and was not specified as input.\");\n    }\n\n    tiles_ = temp_params[0];\n    axis_ = temp_params[1];\n\n    auto* output = Output(0);\n    const auto axis = input.canonical_axis_index(axis_);\n\n    // reshape output to be input tiled along the axis\n    vector<TIndex> output_dims(input.dims());\n    output_dims[axis_] = output_dims[axis_] * tiles_;\n    output->Resize(output_dims);\n\n    // size up to (and not including) axis\n    const auto outer_dim = input.size_to_dim(axis);\n    // size from axis up\n    const auto inner_dim = input.size_from_dim(axis);\n\n    /**\n     * How this works:\n     * Imagine a 2D tensor (matrix) of size 3x10, tiled 2 times.\n     * - Tiling along axis 0 (row) means copying the entire 3x10 Matrix 2\n     * times. outer_dim = 0, inner_dim = 30.\n     * - Tiling along axis 1 (column) means copying each row 2 times, then\n     * proceed to the next row, until the end. outer_dim = 3, inner_dim = 10.\n     */\n    const char* input_data = static_cast<const char*>(input.raw_data());\n    char* output_data =\n        static_cast<char*>(output->raw_mutable_data(input.meta()));\n\n    DoTile(\n        input.meta(),\n        input.itemsize(),\n        outer_dim,\n        inner_dim,\n        input_data,\n        output_data);\n\n    return true;\n  }\n\n private:\n  void DoTile(\n      const TypeMeta& meta,\n      int item_size,\n      int outer_dim,\n      int inner_dim,\n      const char* input_data,\n      char* output_data) {\n    for (auto i = 0; i < outer_dim; ++i) {\n      for (auto t = 0; t < tiles_; ++t) {\n        context_.template CopyItems<Context, Context>(\n            meta, inner_dim, input_data, output_data);\n        output_data += inner_dim * item_size;\n      }\n      input_data += inner_dim * item_size;\n    }\n  }\n\n  int32_t tiles_;\n  int32_t axis_;\n};\n\ntemplate <typename T, class Context>\nclass TileGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  TileGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        tiles_(OperatorBase::GetSingleArgument<int32_t>(\"tiles\", 1)),\n        axis_(OperatorBase::GetSingleArgument<int32_t>(\"axis\", 0)) {}\n  ~TileGradientOp() {}\n\n  bool RunOnDevice() override {\n    std::array<int32_t, 2> temp_params = {{tiles_, axis_}};\n    if (InputSize() > 1) {\n      // We potentially have tiles and/or axis specified as inputs\n      // as well. We will check for them in that order. In other words:\n      // InputSize() == 2: tiles is specified\n      // InputSize() == 3: tiles is specified and axis.\n      // Anything specified as input will override the arguments\n      CAFFE_ENFORCE(\n          Input(1).ndim() == 1 && Input(1).size() == 1,\n          \"Input `tiles` should be a vector of size 1.\");\n\n      const auto& input1 = Input(1);\n      context_.template CopyItems<Context, CPUContext>(\n          input1.meta(),\n          1,\n          static_cast<const char*>(input1.raw_data()),\n          &(temp_params[0]));\n\n      if (InputSize() > 2) {\n        CAFFE_ENFORCE(\n            Input(2).ndim() == 1 && Input(2).size() == 1,\n            \"Input `axis` should be a vector of size 1.\");\n\n        const auto& input2 = Input(2);\n        context_.template CopyItems<Context, CPUContext>(\n            input2.meta(),\n            1,\n            static_cast<const char*>(input2.raw_data()),\n            &(temp_params[1]));\n      } else {\n        CAFFE_ENFORCE(\n            OperatorBase::HasArgument(\"axis\"),\n            \"Argument `axis` is missing and was not specified as input.\");\n      }\n    } else {\n      CAFFE_ENFORCE(\n          OperatorBase::HasArgument(\"tiles\"),\n          \"Argument `tiles` is missing and was not specified as input.\");\n      CAFFE_ENFORCE(\n          OperatorBase::HasArgument(\"axis\"),\n          \"Argument `axis` is missing and was not specified as input.\");\n    }\n\n    tiles_ = temp_params[0];\n    axis_ = temp_params[1];\n\n    const auto& input = Input(0);\n    auto* output = Output(0);\n    const auto axis = input.canonical_axis_index(axis_);\n\n    // reshape output to be input \"untiled\" along the axis\n    vector<TIndex> output_dims(input.dims());\n    output_dims[axis_] = output_dims[axis_] / tiles_;\n    output->Resize(output_dims);\n\n    // size up to (and not including) axis\n    const auto outer_dim = output->size_to_dim(axis);\n    // size from axis up\n    const auto inner_dim = output->size_from_dim(axis);\n\n    /**\n     * How this works:\n     * Imagine a 2D tensor (matrix) of size 3x10, tiled 2 times along axis 1\n     * (column).\n     * This is equivalent to multiplying by a vector of 1s transposed.\n     * The gradient of this is all 1s in the shape of the input matrix\n     * (call it X).\n     * So the output gradient should be the matrix multipication result\n     * of input gradient (gradient of tiled tensor output) and X.\n     */\n    const char* input_data = static_cast<const char*>(input.raw_data());\n    char* output_data =\n        static_cast<char*>(output->raw_mutable_data(input.meta()));\n\n    DoTileGradient(\n        input.meta(),\n        input.itemsize(),\n        outer_dim,\n        inner_dim,\n        input_data,\n        output_data);\n\n    return true;\n  }\n\n private:\n  void DoTileGradient(\n      const TypeMeta& meta,\n      int item_size,\n      int outer_dim,\n      int inner_dim,\n      const char* input_data,\n      char* output_data) {\n    for (auto i = 0; i < outer_dim; ++i) {\n      context_.template CopyItems<Context, Context>(\n          meta, inner_dim, input_data, output_data);\n      input_data += inner_dim * item_size;\n      for (auto t = 1; t < tiles_; ++t) {\n        math::Axpy<T, Context>(\n            inner_dim,\n            T(1),\n            reinterpret_cast<const T*>(input_data),\n            reinterpret_cast<T*>(output_data),\n            &context_);\n        input_data += inner_dim * item_size;\n      }\n      output_data += inner_dim * item_size;\n    }\n  }\n\n  int32_t tiles_;\n  int32_t axis_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_TILE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/top_k.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/top_k.h\"\n\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <typename T>\nstruct ValueCmp {\n  bool operator()(\n      const std::pair<T, TIndex>& lhs,\n      const std::pair<T, TIndex>& rhs) {\n    return (\n        lhs.first > rhs.first ||\n        (lhs.first == rhs.first && lhs.second < rhs.second));\n  }\n};\n\n// Define these two names to allow lookup into the 2d tensors like\n// mytensor(i, j)\ntemplate <typename T>\nusing EigenMatrixMapRowMajor = Eigen::Map<\n    Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>;\n\ntemplate <typename T>\nusing ConstEigenMatrixMapRowMajor = Eigen::Map<\n    const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>;\n\n} // namespace\n\ntemplate <typename T, class Context>\nbool TopKOp<T, Context>::RunOnDevice() {\n  auto& input = Input(0);\n  auto* values = Output(0);\n  auto* indices = Output(1);\n  auto* flatten_indices = OutputSize() > 2 ? Output(2) : nullptr;\n\n  vector<TIndex> in_dims = input.dims();\n  // Linearize input tensor except for last dimension\n  // e.g. [3, 4, 5] -> [12, 5]\n  // [5] -> [5]\n  CAFFE_ENFORCE(\n      in_dims.back() >= k_, \"k argment should not be greater than last dim\");\n  vector<TIndex> linear_shape = {size_to_dim_(in_dims.size() - 1, in_dims),\n                                 in_dims[in_dims.size() - 1]};\n  auto input_map = ConstEigenMatrixMapRowMajor<T>(\n      static_cast<const T*>(input.raw_data()),\n      linear_shape[0],\n      linear_shape[1]);\n\n  // Resize output tensors to be the same shape as the linearized input except\n  // for the last dimension, which will be of size k. E.x. for an input tensor\n  // of shape [3, 4, 5] and k=2, both of these will be shape [3, 4, 2]\n  vector<TIndex> output_linear_shape = {linear_shape[0], k_};\n  values->Resize(output_linear_shape);\n  indices->Resize(output_linear_shape);\n  if (flatten_indices) {\n    flatten_indices->Resize(linear_shape[0] * k_);\n  }\n\n  // Use Eigen maps to allow indexing into the 2d tensors like values_map(i,j)\n  auto values_map = EigenMatrixMapRowMajor<T>(\n      values->template mutable_data<T>(), linear_shape[0], k_);\n  auto indices_map = EigenMatrixMapRowMajor<TIndex>(\n      indices->template mutable_data<TIndex>(), linear_shape[0], k_);\n  auto* flatten_indices_data = flatten_indices\n      ? flatten_indices->template mutable_data<TIndex>()\n      : nullptr;\n\n  TIndex flatten_offset = 0;\n  // Sort preserving indices\n  for (TIndex i = 0; i < linear_shape[0]; ++i) {\n    // Build a min-heap, the heap element is pair of (value, idx)\n    // the top of the heap is the smallest value\n    std::priority_queue<\n        std::pair<T, TIndex>,\n        std::vector<std::pair<T, TIndex>>,\n        ValueCmp<T>>\n        PQ;\n\n    // Maintain the size of heap to be less or equal to k_, so the\n    // heap will hold the k_ largest values\n    for (TIndex j = 0; j < linear_shape[1]; ++j) {\n      const auto value = input_map(i, j);\n      if (PQ.size() < k_ || value > PQ.top().first) {\n        PQ.push(std::make_pair(value, j));\n      }\n      if (PQ.size() > k_) {\n        PQ.pop();\n      }\n    }\n    for (TIndex j = 0; j < k_; ++j) {\n      auto& pqElem = PQ.top();\n      values_map(i, k_ - j - 1) = pqElem.first;\n      indices_map(i, k_ - j - 1) = pqElem.second;\n      if (flatten_indices_data) {\n        flatten_indices_data[k_ - j - 1] = pqElem.second + flatten_offset;\n      }\n      PQ.pop();\n    }\n    if (flatten_indices_data) {\n      flatten_indices_data += k_;\n    }\n    flatten_offset += linear_shape[1];\n  }\n\n  // Reshape output tensors to [a_1, a_2, ..., a_n, k]\n  auto out_dims = in_dims;\n  out_dims[out_dims.size() - 1] = k_;\n  values->Reshape(out_dims);\n  indices->Reshape(out_dims);\n  return true;\n}\n\ntemplate <typename T, class Context>\nbool TopKGradientOp<T, Context>::RunOnDevice() {\n  auto& values = Input(0);\n  auto& indices = Input(1);\n  auto& original_input = Input(2);\n  auto* output = Output(0);\n\n  vector<TIndex> in_dims = values.dims();\n  // Linearize input tensor except for last dimension\n  // e.g. [3, 4, 5] -> [12, 5]\n  // [5] -> [5]\n  vector<TIndex> linear_shape = {size_to_dim_(in_dims.size() - 1, in_dims),\n                                 in_dims[in_dims.size() - 1]};\n  auto values_map = ConstEigenMatrixMapRowMajor<T>(\n      static_cast<const T*>(values.raw_data()),\n      linear_shape[0],\n      linear_shape[1]);\n  auto indices_map = ConstEigenMatrixMapRowMajor<TIndex>(\n      static_cast<const TIndex*>(indices.raw_data()),\n      linear_shape[0],\n      linear_shape[1]);\n\n  // Resize output tensors to be as orignial_input size and initialized with 0\n  vector<TIndex> original_dims = original_input.dims();\n  output->Resize(original_dims);\n  T* output_data = output->template mutable_data<T>();\n  memset(output_data, 0, output->nbytes());\n\n  // Use Eigen maps to allow indexing into the 2d tensors\n  auto output_map = EigenMatrixMapRowMajor<T>(\n      output_data, linear_shape[0], original_dims.back());\n\n  for (TIndex i = 0; i < linear_shape[0]; ++i) {\n    for (TIndex j = 0; j < linear_shape[1]; ++j) {\n      output_map(i, indices_map(i, j)) = values_map(i, j);\n    }\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(TopK, TopKOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(TopKGradient, TopKGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(TopK)\n    .NumInputs(1)\n    .NumOutputs(2, 3)\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      vector<TensorShape> out = {in[0], in[0]};\n      ArgumentHelper helper(def);\n      auto k = helper.GetSingleArgument(\"k\", -1);\n      auto dims_size = in[0].dims_size();\n      out[0].set_dims(dims_size - 1, k);\n      out[1].set_dims(dims_size - 1, k);\n      out[1].set_data_type(TensorProto_DataType_INT32);\n      if (def.output_size() > 2) {\n        TensorShape flatten_indices_shape;\n        flatten_indices_shape.set_data_type(TensorProto_DataType_INT32);\n        flatten_indices_shape.add_dims(\n            std::accumulate(\n                in[0].dims().begin(),\n                in[0].dims().end() - 1,\n                1,\n                std::multiplies<long>()) *\n            k);\n        out.push_back(flatten_indices_shape);\n      }\n      return out;\n    })\n    .SetDoc(R\"DOC(\nRetrieve the top-K elements for the last dimension. Given an input tensor of\nshape [a_1, a_2, ..., a_n, r] and integer argument k, return two outputs:\n-Value tensor of shape [a_1, a_2, ..., a_n, k] which contains the values of\n the top k elements along the last dimension\n-Index tensor of shape [a_1, a_2, ..., a_n, k] which contains the indices\n of the top k elements (original indices from the input tensor).\n\nGiven two equivalent values, this operator uses the indices along the last dim-\nension as a tiebreaker. That is, the element with the lower index will appear\nfirst.\n    )DOC\")\n    .Input(0, \"X\", \"Tensor of shape [a_1, a_2, ..., a_n, r]\")\n    .Output(\n        0,\n        \"Values\",\n        \"Tensor of shape [a_1, a_2, ..., a_n, k] containing\"\n        \" top K values from the input tensor\")\n    .Output(\n        1,\n        \"Indices\",\n        \"Tensor of shape [a_1, a_2, ..., a_n, k] containing\"\n        \" the corresponding input tensor indices for the top K values.\")\n    .Output(\n        2,\n        \"Flatten indices\",\n        \"Tensor of shape [a_1 * a_2 * ... * a_n * k] containing the indices \"\n        \"into the flatten input\")\n    .Arg(\"k\", \"Number of top elements to retrieve\");\n\nOPERATOR_SCHEMA(TopKGradient).NumInputs(3).NumOutputs(1);\n\nclass GetTopKGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"TopKGradient\",\n        \"\",\n        vector<string>{GO(0), O(1), I(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(TopK, GetTopKGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/top_k.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/top_k.h\"\n\n#include <thrust/sort.h>\n#include <thrust/system/cuda/execution_policy.h>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/top_k_heap_selection.cuh\"\n#include \"caffe2/operators/top_k_radix_selection.cuh\"\n\nnamespace caffe2 {\n\n// Converts a matrix of size [outerSize, k] containing\n// row-wise indices into global (linearized) indices from an original\n// matrix of [outerSize, innerSize]\ntemplate <typename Index>\n__global__ void linearizeRowIndices(\n    Index* in,\n    Index* out,\n    int outerSize,\n    int innerSize,\n    int k) {\n  if (blockIdx.x < outerSize) {\n    in += (Index)blockIdx.x * k;\n    out += (Index)blockIdx.x * k;\n\n    auto indexOffset = (Index)blockIdx.x * innerSize;\n    for (int i = threadIdx.x; i < k; i += blockDim.x) {\n      out[i] = in[i] + indexOffset;\n    }\n  }\n}\n\ntemplate <>\nclass TopKOp<float, CUDAContext> : public Operator<CUDAContext> {\n public:\n  TopKOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws),\n        OP_SINGLE_ARG(int, \"k\", k_, -1) {}\n\n  bool RunOnDevice() override;\n\n private:\n  int k_;\n};\n\nbool TopKOp<float, CUDAContext>::RunOnDevice() {\n  auto& input = Input(0);\n  auto* values = Output(0);\n  auto* indices = Output(1);\n  auto* flatten_indices = OutputSize() > 2 ? Output(2) : nullptr;\n\n  vector<TIndex> in_dims = input.dims();\n  CAFFE_ENFORCE(\n      in_dims.back() >= k_, \"k argment should not be greater than last dim\");\n\n  vector<TIndex> out_dims = in_dims;\n  out_dims.back() = k_;\n\n  // Get the batch size\n  size_t outerSize = 1;\n  for (int i = 0; i < in_dims.size() - 1; ++i) {\n    outerSize *= in_dims[i];\n  }\n\n  values->Resize(out_dims);\n  indices->Resize(out_dims);\n  if (flatten_indices) {\n    flatten_indices->Resize(outerSize * k_);\n  }\n\n  // Right now, the top-k operator only supports max-k\n  constexpr bool kDir = true;\n\n  if (k_ <= 512) {\n    // heap selection is possible\n    constexpr int kBlockSize = 256;\n    int numWarps = kBlockSize / kWarpSize;\n\n    auto grid = outerSize;\n    auto block = kBlockSize;\n\n#define RUN_HEAP(HEAP_SIZE)                                               \\\n  do {                                                                    \\\n    int smem = numWarps * HEAP_SIZE * (sizeof(float) + sizeof(TIndex));   \\\n                                                                          \\\n    selectRowsViaHeap<float, TIndex, TIndex, kBlockSize, HEAP_SIZE, kDir> \\\n        <<<grid, block, smem, context_.cuda_stream()>>>(                  \\\n            input.data<float>(),                                          \\\n            values->mutable_data<float>(),                                \\\n            indices->mutable_data<TIndex>(),                              \\\n            kDir ? -std::numeric_limits<float>::infinity()                \\\n                 : std::numeric_limits<float>::infinity(),                \\\n            kDir ? -std::numeric_limits<TIndex>::max()                    \\\n                 : std::numeric_limits<float>::max(),                     \\\n            outerSize,                                                    \\\n            in_dims.back(),                                               \\\n            k_);                                                          \\\n  } while (false)\n\n    if (k_ <= 32) {\n      RUN_HEAP(32);\n    } else if (k_ <= 128) {\n      RUN_HEAP(128);\n    } else {\n      RUN_HEAP(512);\n    }\n\n#undef RUN_HEAP\n\n  } else {\n    // k is too large, use radix selection instead\n    auto grid = outerSize;\n    auto block = std::min(\n        math::roundUp((int)in_dims.back(), kWarpSize), CAFFE_CUDA_NUM_THREADS);\n\n    // Radix selection required\n    gatherTopK<float, kDir, TIndex><<<grid, block, 0, context_.cuda_stream()>>>(\n        input.data<float>(),\n        in_dims.back(),\n        k_,\n        outerSize,\n        values->mutable_data<float>(),\n        indices->mutable_data<TIndex>());\n\n    // Unfortunately the output is not currently sorted, and there is\n    // no batch sorting utility available. Iterate over all of the\n    // slices and sort them in-place using Thrust.\n    for (int slice = 0; slice < outerSize; ++slice) {\n      thrust::sort_by_key(\n          thrust::cuda::par.on(context_.cuda_stream()),\n          values->mutable_data<float>() + slice * k_,\n          values->mutable_data<float>() + slice * k_ + k_,\n          indices->mutable_data<TIndex>() + slice * k_,\n          thrust::greater<float>());\n    }\n  }\n\n  // Now that we've completed writing the indices, linearize the\n  // indices if we need it\n  if (flatten_indices) {\n    linearizeRowIndices<TIndex>\n        <<<outerSize, CAFFE_CUDA_NUM_THREADS, 0, context_.cuda_stream()>>>(\n            indices->mutable_data<TIndex>(),\n            flatten_indices->mutable_data<TIndex>(),\n            outerSize,\n            in_dims.back(),\n            k_);\n  }\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(TopK, TopKOp<float, CUDAContext>);\n\n__global__ void fillValuesWithIndicesKernel(\n    const float* values,\n    const TIndex* indices,\n    const TIndex k,\n    const TIndex orignal_last_dim,\n    const TIndex length,\n    float* output) {\n  CUDA_1D_KERNEL_LOOP(i, length) {\n    int first_dim = i / k;\n    int idx = orignal_last_dim * first_dim + indices[i];\n    output[idx] = values[i];\n  }\n}\n\ntemplate <>\nbool TopKGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& values = Input(0);\n  auto& indices = Input(1);\n  auto& original_input = Input(2);\n\n  vector<TIndex> in_dims = values.dims();\n\n  // Linearize input tensor except for last dimension\n  // e.g. [3, 4, 5] -> [12, 5]\n  // [5] -> [5]\n  TIndex flatten_shape[] = {size_to_dim_(in_dims.size() - 1, in_dims),\n                            in_dims[in_dims.size() - 1]};\n\n  vector<TIndex> original_dims = original_input.dims();\n  auto* output = Output(0);\n  output->Resize(original_dims);\n\n  float* output_data = output->mutable_data<float>();\n  math::Set<float, CUDAContext>(\n      output->size(), float(0), output_data, &context_);\n\n  int length = flatten_shape[0] * flatten_shape[1];\n  if (length == 0) { // for empty batch\n    return true;\n  }\n\n  int num_threads = std::min(CAFFE_CUDA_NUM_THREADS, length);\n  int blocks = math::divUp(length, num_threads);\n\n  fillValuesWithIndicesKernel<<<\n      blocks,\n      num_threads,\n      0,\n      context_.cuda_stream()>>>(\n      values.data<float>(),\n      indices.data<TIndex>(),\n      flatten_shape[1],\n      original_dims.back(),\n      length,\n      output_data);\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(TopKGradient, TopKGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/top_k.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_TOP_K_H_\n#define CAFFE2_OPERATORS_TOP_K_H_\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass TopKOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  TopKOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws), OP_SINGLE_ARG(int, \"k\", k_, -1) {\n    CAFFE_ENFORCE(k_ >= 1, \"k argument must be >= 1\");\n  }\n\n  bool RunOnDevice() override;\n\n private:\n  int k_;\n};\n\ntemplate <typename T, class Context>\nclass TopKGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  TopKGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_TOP_K_H_\n"
  },
  {
    "path": "caffe2/operators/top_k_heap_selection.cuh",
    "content": "#ifndef CAFFE2_OPERATORS_TOP_K_HEAP_SELECTION_H_\n#define CAFFE2_OPERATORS_TOP_K_HEAP_SELECTION_H_\n\n#include \"caffe2/utils/GpuBitonicSort.cuh\"\n#include \"caffe2/utils/GpuDefs.cuh\"\n#include \"caffe2/utils/math.h\"\n#include <cuda_runtime.h>\n\nnamespace caffe2 {\n\ntemplate <typename K, typename V>\nstruct LTComp {\n  __device__ inline bool\n  operator()(const K& kA, const V& vA, const K& kB, const V& vB) const {\n    // FIXME: adding value comparison is slow\n    return (kA < kB) || ((kA == kB) && (vA < vB));\n  }\n};\n\ntemplate <typename K, typename V>\nstruct GTComp {\n  __device__ inline bool\n  operator()(const K& kA, const V& vA, const K& kB, const V& vB) const {\n    // FIXME: adding value comparison is slow\n    // FIXME: it's vA < vB because the sorting order for V (aka\n    // indices) is different in our use case\n    return (kA > kB) || ((kA == kB) && (vA < vB));\n  }\n};\n\nconstexpr size_t getHeapSmemSize(\n    size_t keySize,\n    size_t valueSize,\n    int numThreads,\n    int heapSize) {\n  return (numThreads / kWarpSize) * heapSize * (keySize + valueSize);\n}\n\n// Per-warp heap structure in shared memory:\n// [key_0, ..., key_(HeapSize-2)], [empty element] (warp 0)\n// ...\n// [key_0, ..., key_(HeapSize-2)], [empty element] (warp n-1)\n// [value_0, ..., value_(HeapSize-2)], [empty element] (warp 0)\n// ...\n// [value_0, ..., value_(HeapSize-2)], [empty element] (warp n-1)\n\n// Dir == true means we are selecting the largest values, thus\n// the heap is a min-heap\ntemplate <typename K, typename V, int HeapSize, bool Dir>\n__device__ inline void warpHeapInsert(K k, V v, K* keyHeap, V* valueHeap) {\n  // Replace head if we are < head\n  bool valid = Dir ? (k > keyHeap[0]) : (k < keyHeap[0]);\n\n  // Even though this is the single-thread case, another preceding\n  // thread in the warp may have inserted in a new element that\n  // supersedes our element and thus our attempt at an insert would do\n  // nothing.\n  if (!valid) {\n    return;\n  }\n\n  // Swap with head if valid\n  K currentKey = k;\n  V currentValue = v;\n\n  keyHeap[0] = currentKey;\n  valueHeap[0] = currentValue;\n\n  // The number of interior nodes in the heap is log2(HeapSize / 2):\n  // heap size 8 means there are 7 elements in the heap, indices 0-6\n  // (0 12 3456)\n  // log2(8 / 2) = 2 levels of interior nodes for heap size 8 (0 and 12)\n  int i = 0;\n#pragma unroll\n  for (int levels = 0; levels < math::integerLog2(HeapSize / 2); ++levels) {\n    int leftChild = i * 2 + 1;\n    int rightChild = leftChild + 1;\n    K leftKey = keyHeap[leftChild];\n    K rightKey = keyHeap[rightChild];\n\n    // What child might we want to swap with (max heap = larger child;\n    // min heap = smaller child)\n    bool swap = Dir ? (leftKey < rightKey) : (leftKey > rightKey);\n    int childToSwap = swap ? leftChild : rightChild;\n    K keyChildToSwap = swap ? leftKey : rightKey;\n\n    // If we're bigger than both children (max heap), or smaller than\n    // both children (min heap), then we do nothing for the rest of\n    // the iterations\n    valid =\n        Dir ? !(currentKey < keyChildToSwap) : !(currentKey > keyChildToSwap);\n\n    // Swap with childToSwap if still valid\n    keyHeap[i] = valid ? keyChildToSwap : currentKey;\n    valueHeap[i] = valid ? valueHeap[childToSwap] : currentValue;\n\n    keyHeap[childToSwap] = valid ? currentKey : keyChildToSwap;\n    valueHeap[childToSwap] = valid ? currentValue : valueHeap[childToSwap];\n\n    i = childToSwap;\n\n    // This is our new element to potentially downheap\n    currentKey = keyHeap[i];\n    currentValue = valueHeap[i];\n  }\n}\n\ntemplate <typename K, typename V, int HeapSize, bool Dir>\n__device__ inline void\nwarpHeap(K k, V v, K& keyHeapHead, K* keyHeap, V* valueHeap) {\n  // All threads in the warp have elements\n  bool wantInsert = Dir ? (k > keyHeapHead) : (k < keyHeapHead);\n\n  // Find out all the lanes that have elements to add to the heap\n#if CUDA_VERSION >= 9000\n  unsigned int vote = __ballot_sync(__activemask(), wantInsert);\n#else\n  unsigned int vote = __ballot(wantInsert);\n#endif\n\n  if (!vote) {\n    // Everything the warp has is smaller than our heap\n    return;\n  }\n\n  // Otherwise, we want to serialize execution of the threads\n  // that have elements\n  int index = __popc(getLaneMaskLt() & vote);\n  int total = __popc(vote);\n\n  // FIXME: try switch statement and explicitly handle cases\n  // FIXME: how do cases work?\n  for (int i = 0; i < total; ++i) {\n    if (index == i && wantInsert) {\n      // Insert into our heap\n      warpHeapInsert<K, V, HeapSize, Dir>(k, v, keyHeap, valueHeap);\n\n      // Make sure all smem writes are visible\n      __threadfence_block();\n    }\n  }\n\n  // Re-broadcast the new heap head\n  // FIXME: consider each updater above will broadcast its value with\n  // a shuffle instead?\n  keyHeapHead = keyHeap[0];\n}\n\ntemplate <typename K, typename V, int ThreadsPerBlock, int HeapSize, bool Dir>\nclass Heap {\n public:\n  static constexpr size_t getSmemSize() {\n    return getHeapSmemSize(sizeof(K), sizeof(V), ThreadsPerBlock, HeapSize);\n  }\n\n  __device__ Heap(void* smem, K initKey, V initVal) {\n    heapBase = smem;\n\n    int warpId = threadIdx.x / kWarpSize;\n    int laneId = getLaneId();\n\n    auto kStart = getKeyStart();\n    heapK = &kStart[warpId * HeapSize];\n    auto vStart = getValueStart();\n    heapV = &vStart[warpId * HeapSize];\n\n    heapHead = initKey;\n\n    if (HeapSize < kWarpSize) {\n      if (laneId < HeapSize) {\n        heapK[laneId] = initKey;\n        heapV[laneId] = initVal;\n      }\n    } else {\n#pragma unroll\n      for (int i = 0; i < HeapSize / kWarpSize; ++i) {\n        heapK[laneId + i * kWarpSize] = initKey;\n        heapV[laneId + i * kWarpSize] = initVal;\n      }\n    }\n  }\n\n  // Returns a pointer to the start of our block-wide key storage\n  inline __device__ K* getKeyStart() {\n    return (K*)heapBase;\n  }\n\n  // Returns a pointer to the start of our block-wide value storage\n  inline __device__ V* getValueStart() {\n    constexpr int warpsPerBlock = ThreadsPerBlock / kWarpSize;\n    return (V*)&getKeyStart()[warpsPerBlock * HeapSize];\n  }\n\n  // Returns a pointer past the end of our block-wide heap storage\n  inline __device__ void* getStorageEnd() {\n    constexpr int warpsPerBlock = ThreadsPerBlock / kWarpSize;\n    return getValueStart() + (warpsPerBlock * HeapSize);\n  }\n\n  inline __device__ void add(K k, V v) {\n    warpHeap<K, V, HeapSize, Dir>(k, v, heapHead, heapK, heapV);\n  }\n\n  // Reduce all per-warp heaps to a unified, sorted list\n  inline __device__ void reduceHeaps() {\n    constexpr int allHeapSize = (ThreadsPerBlock / kWarpSize) * HeapSize;\n\n    if (Dir) {\n      bitonicSort<GTComp<K, V>, K, V, allHeapSize, ThreadsPerBlock>(\n          getKeyStart(), getValueStart(), GTComp<K, V>());\n    } else {\n      bitonicSort<LTComp<K, V>, K, V, allHeapSize, ThreadsPerBlock>(\n          getKeyStart(), getValueStart(), LTComp<K, V>());\n    }\n  }\n\n private:\n  void* heapBase;\n  K heapHead;\n  K* heapK;\n  V* heapV;\n};\n\ntemplate <\n    typename V,\n    typename IndexType,\n    typename OutIndexType,\n    int ThreadsPerBlock,\n    int HeapSize,\n    bool Dir>\n__global__ void selectRowsViaHeap(\n    const V* input, // m x n\n    V* outKeys, // m x k\n    OutIndexType* outIndices, // m x k\n    V initVal,\n    IndexType initIndex,\n    int m,\n    int n,\n    int k) {\n  extern __shared__ float smem[];\n\n  Heap<V, IndexType, ThreadsPerBlock, HeapSize, Dir> heap(\n      smem, initVal, initIndex);\n\n  auto inputStart = &input[blockIdx.x * n];\n\n  // FIXME choose desired unroll level\n  constexpr int Unroll = 1;\n  V vals[Unroll];\n\n  for (int i = threadIdx.x; i < n; i += blockDim.x * Unroll) {\n#pragma unroll\n    for (int j = 0; j < Unroll; ++j) {\n      vals[j] = inputStart[i + j * blockDim.x];\n    }\n\n#pragma unroll\n    for (int j = 0; j < Unroll; ++j) {\n      heap.add(vals[j], (IndexType)i + j * blockDim.x);\n    }\n  }\n\n  // When finished, we restructure the heaps in shared memory\n  // The heaps are actually of size HeapSize - 1 (e.g., 32 -> 31); the\n  // extra element should have remained untouched, so we can still\n  // sort things in-place as a power of 2.\n  __syncthreads();\n\n  heap.reduceHeaps();\n\n  auto outKeysStart = &outKeys[blockIdx.x * k];\n  auto outIndicesStart = &outIndices[blockIdx.x * k];\n\n  auto heapK = heap.getKeyStart();\n  auto heapV = heap.getValueStart();\n\n  // Write out the final k-selected values; they should be all\n  // together\n  for (int i = threadIdx.x; i < k; i += blockDim.x) {\n    outKeysStart[i] = heapK[i];\n    outIndicesStart[i] = (OutIndexType)heapV[i];\n  }\n}\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_TOP_K_HEAP_SELECTION_H_\n"
  },
  {
    "path": "caffe2/operators/top_k_radix_selection.cuh",
    "content": "#ifndef CAFFE2_OPERATORS_TOP_K_RADIX_SELECTION_H_\n#define CAFFE2_OPERATORS_TOP_K_RADIX_SELECTION_H_\n\n#include \"caffe2/utils/GpuDefs.cuh\"\n#include \"caffe2/utils/GpuScanUtils.cuh\"\n#include \"caffe2/utils/math.h\"\n#include <cuda_runtime.h>\n\nnamespace caffe2 {\n\n// From the cutorch library\n\ntemplate <typename T>\nstruct AddOp {\n  __device__ __forceinline__ T operator()(T &lhs, T &rhs) {\n    return lhs + rhs;\n  }\n};\n\ntemplate <typename T>\nstruct TopKTypeConfig {};\n\ntemplate <>\nstruct TopKTypeConfig<float> {\n  typedef unsigned int RadixType;\n\n  // Converts a float to an integer representation with the same\n  // sorting; i.e., for floats f1, f2:\n  // if f1 < f2 then convert(f1) < convert(f2)\n  // We use this to enable radix selection of floating-point values.\n  // This also gives a relative order for NaNs, but that's ok, as they\n  // will all be adjacent\n  static inline __device__ RadixType convert(float v) {\n    RadixType x = __float_as_int(v);\n    RadixType mask = (x & 0x80000000) ? 0xffffffff : 0x80000000;\n\n    return (x ^ mask);\n  }\n\n  static inline __device__ float deconvert(RadixType v) {\n    RadixType mask = (v & 0x80000000) ? 0x80000000 : 0xffffffff;\n\n    return __int_as_float(v ^ mask);\n  }\n};\n\ntemplate <>\nstruct TopKTypeConfig<unsigned char> {\n  typedef unsigned int RadixType;\n\n  static inline __device__ RadixType convert(unsigned char v) {\n    return v;\n  }\n\n  static inline __device__ unsigned char deconvert(RadixType v) {\n    return v;\n  }\n};\n\ntemplate <>\nstruct TopKTypeConfig<char> {\n  typedef unsigned int RadixType;\n\n  static inline __device__ RadixType convert(char v) {\n    return 128u + v;\n  }\n\n  static inline __device__ char deconvert(RadixType v) {\n    return v - 128;\n  }\n};\n\ntemplate <>\nstruct TopKTypeConfig<short> {\n  typedef unsigned int RadixType;\n\n  static inline __device__ RadixType convert(short v) {\n    assert(sizeof(short) == 2);\n    return 32768u + v;\n  }\n\n  static inline __device__ short deconvert(RadixType v) {\n    return v - 32768;\n  }\n};\n\ntemplate <>\nstruct TopKTypeConfig<int> {\n  typedef unsigned int RadixType;\n\n  static inline __device__ RadixType convert(int v) {\n    assert(sizeof(int) == 4);\n    return 2147483648u + v;\n  }\n\n  static inline __device__ int deconvert(RadixType v) {\n    return v - 2147483648u;\n  }\n};\n\ntemplate <>\nstruct TopKTypeConfig<long> {\n  typedef unsigned long long int RadixType;\n\n  static inline __device__ RadixType convert(long v) {\n    assert(sizeof(long) == 8);\n    return 9223372036854775808ull + v;\n  }\n\n  static inline __device__ long deconvert(RadixType v) {\n    return v - 9223372036854775808ull;\n  }\n};\n\ntemplate <>\nstruct TopKTypeConfig<double> {\n  typedef unsigned long long int RadixType;\n\n  static inline __device__ RadixType convert(double v) {\n    RadixType x = __double_as_longlong(v);\n    RadixType mask = -((x >> 63)) | 0x8000000000000000;\n    return (x ^ mask);\n  }\n\n  static inline __device__ double deconvert(RadixType v) {\n    RadixType mask = ((v >> 63) - 1) | 0x8000000000000000;\n    return __longlong_as_double(v ^ mask);\n  }\n};\n\n// This function counts the distribution of all input values in a\n// slice we are selecting by radix digit at `radixDigitPos`, but only\n// those that pass the filter `((v & desiredMask) == desired)`.\n// This produces and broadcasts the seen counts for a single block only.\n// `smem` must have at least `RadixSize` elements.\ntemplate <typename DataType,\n          typename BitDataType,\n          typename CountType,\n          int RadixSize,\n          int RadixBits>\n__device__ void countRadixUsingMask(CountType counts[RadixSize],\n                                    CountType* smem,\n                                    BitDataType desired,\n                                    BitDataType desiredMask,\n                                    int radixDigitPos,\n                                    int sliceSize,\n                                    const DataType* data) {\n  // Clear out per-thread counts from a previous round\n#pragma unroll\n  for (int i = 0; i < RadixSize; ++i) {\n    counts[i] = 0;\n  }\n\n  if (threadIdx.x < RadixSize) {\n    smem[threadIdx.x] = 0;\n  }\n  __syncthreads();\n\n  // Scan over all the data. Upon a read, the warp will accumulate\n  // counts per each digit in the radix using warp voting.\n  for (int i = threadIdx.x; i < sliceSize; i += blockDim.x) {\n    BitDataType val = TopKTypeConfig<DataType>::convert(data[i]);\n\n    bool hasVal = ((val & desiredMask) == desired);\n    BitDataType digitInRadix = Bitfield<BitDataType>::getBitfield(val, radixDigitPos, RadixBits);\n\n#pragma unroll\n    for (unsigned int j = 0; j < RadixSize; ++j) {\n      bool vote = hasVal && (digitInRadix == j);\n#if CUDA_VERSION >= 9000\n      counts[j] += __popc(__ballot_sync(__activemask(), vote));\n#else\n      counts[j] += __popc(__ballot(vote));\n#endif\n    }\n  }\n\n  // Now, for each warp, sum values\n  if (getLaneId() == 0) {\n#pragma unroll\n    for (unsigned int i = 0; i < RadixSize; ++i) {\n      atomicAdd(&smem[i], counts[i]);\n    }\n  }\n\n  __syncthreads();\n\n  // For each thread, read in the total counts\n#pragma unroll\n  for (unsigned int i = 0; i < RadixSize; ++i) {\n    counts[i] = smem[i];\n  }\n\n  __syncthreads();\n}\n\n// Over what radix we are selecting values\n#define RADIX_BITS 2 // digits are base-(2 ^ RADIX_BITS)\n#define RADIX_SIZE 4 // 2 ^ RADIX_BITS\n#define RADIX_MASK (RADIX_SIZE - 1)\n\n// This finds the unique value `v` that matches the pattern\n// ((v & desired) == desiredMask) in our sorted int format\ntemplate <typename DataType, typename BitDataType>\n__device__ DataType findPattern(DataType* smem,\n                                const DataType* data,\n                                int sliceSize,\n                                BitDataType desired,\n                                BitDataType desiredMask) {\n  if (threadIdx.x < 32) {\n    smem[threadIdx.x] = (DataType) 0;\n  }\n  __syncthreads();\n\n  // All threads participate in the loop, in order to sync on the flag\n  int numIterations = math::roundUp(sliceSize, (int) blockDim.x);\n  for (int i = threadIdx.x; i < numIterations; i += blockDim.x) {\n    bool inRange = (i < sliceSize);\n    DataType v = inRange ? data[i] : (DataType)0;\n\n    if (inRange && ((TopKTypeConfig<DataType>::convert(v) & desiredMask) == desired)) {\n      // There should not be conflicts if we are using findPattern,\n      // since the result is unique\n      smem[0] = (DataType)1;\n      smem[1] = v; // can't use val as the flag, since it could be 0\n    }\n\n    __syncthreads();\n\n    DataType found = smem[0];\n    DataType val = smem[1];\n\n    __syncthreads();\n\n    // Check to see if a thread found the value\n    if (found != (DataType)0) {\n      // all threads return this value\n      return val;\n    }\n  }\n\n  // should not get here\n  assert(false);\n  return (DataType)0;\n}\n\n// Returns the top-Kth element found in the data using radix selection\ntemplate <typename DataType, typename BitDataType, bool Order>\n__device__ void radixSelect(const DataType* data,\n                            int k,\n                            int sliceSize,\n                            int* smem,\n                            DataType* topK) {\n  // Per-thread buckets into which we accumulate digit counts in our\n  // radix\n  int counts[RADIX_SIZE];\n\n  // We only consider elements x such that (x & desiredMask) == desired\n  // Initially, we consider all elements of the array, so the above\n  // statement is true regardless of input.\n  BitDataType desired = 0;\n  BitDataType desiredMask = 0;\n\n  // We are looking for the top kToFind-th element when iterating over\n  // digits; this count gets reduced by elimination when counting\n  // successive digits\n  int kToFind = k;\n\n  // We start at the most significant digit in our radix, scanning\n  // through to the least significant digit\n#pragma unroll\n  for (int digitPos = sizeof(DataType) * 8 - RADIX_BITS;\n       digitPos >= 0;\n       digitPos -= RADIX_BITS) {\n\n    // Count radix distribution for the current position and reduce\n    // across all threads\n    countRadixUsingMask<DataType, BitDataType,\n                        int,\n                        RADIX_SIZE, RADIX_BITS>(\n                          counts, smem,\n                          desired, desiredMask, digitPos,\n                          sliceSize, data);\n\n    // All threads participate in the comparisons below to know the\n    // final result\n\n\n#define CHECK_RADIX(i)                                                  \\\n    int count = counts[i];                                              \\\n                                                                        \\\n    /* All threads have the same value in counts here, so all */        \\\n    /* threads will return from the function. */                        \\\n    if (count == 1 && kToFind == 1) {                                   \\\n      /* There is a unique answer. */                                   \\\n      desired = Bitfield<BitDataType>::setBitfield(desired, i, digitPos, RADIX_BITS);          \\\n      desiredMask =                                                     \\\n        Bitfield<BitDataType>::setBitfield(desiredMask, RADIX_MASK, digitPos, RADIX_BITS);     \\\n                                                                        \\\n      /* The answer is now the unique element v such that: */           \\\n      /* (v & desiredMask) == desired */                                \\\n      /* However, we do not yet know what the actual element is. We */  \\\n      /* need to perform a search through the data to find the */       \\\n      /* element that matches this pattern. */                          \\\n      *topK = findPattern<DataType, BitDataType>(                       \\\n        (DataType*) smem, data, sliceSize,                              \\\n        desired, desiredMask);                                          \\\n      return;                                                           \\\n    }                                                                   \\\n                                                                        \\\n    if (count >= kToFind) {                                             \\\n      desired = Bitfield<BitDataType>::setBitfield(desired, i, digitPos, RADIX_BITS);          \\\n      desiredMask =                                                     \\\n        Bitfield<BitDataType>::setBitfield(desiredMask, RADIX_MASK, digitPos, RADIX_BITS);     \\\n                                                                        \\\n      /* The top-Kth element v must now be one such that: */            \\\n      /* (v & desiredMask == desired) */                                \\\n      /* but we haven't narrowed it down; we must check the next */     \\\n      /* least-significant digit */                                     \\\n      break;                                                            \\\n    }                                                                   \\\n                                                                        \\\n    kToFind -= count                                                    \\\n\n    if (Order) {\n      // Process in descending order\n#pragma unroll\n      for (int i = RADIX_SIZE - 1; i >= 0; --i) {\n        CHECK_RADIX(i);\n      }\n    } else {\n      // Process in ascending order\n#pragma unroll\n      for (int i = 0; i < RADIX_SIZE; ++i) {\n        CHECK_RADIX(i);\n      }\n    }\n#undef CHECK_RADIX\n  } // end digitPos for\n\n  // There is no unique result, but there is a non-unique result\n  // matching `desired` exactly\n  *topK = TopKTypeConfig<DataType>::deconvert(desired);\n}\n\ntemplate <typename T, bool Order, typename IndicesType>\n__global__ void gatherTopK(const T* inputPtr,\n                           int inputSliceSize,\n                           int outputSliceSize, // aka `k`\n                           int numInputSlices,\n                           T* topKPtr,\n                           IndicesType* indicesPtr) {\n  __shared__ int smem[32]; // one per each warp, up to warp limit\n\n  int slice = blockIdx.x;\n  if (slice >= numInputSlices) {\n    return;\n  }\n\n  // Find the start offset for our slice\n  const T* inputSliceStart = &inputPtr[slice * inputSliceSize];\n  T* topKSliceStart = &topKPtr[slice * outputSliceSize];\n  caffe2::TIndex* indicesSliceStart = &indicesPtr[slice * outputSliceSize];\n\n  // Find the k-th highest element in our input\n  T topKValue = (T)0;\n  radixSelect<T, typename TopKTypeConfig<T>::RadixType, Order>(\n    inputSliceStart, outputSliceSize,\n    inputSliceSize,\n    smem, &topKValue);\n\n  // Every value that is strictly less/greater than `pattern`\n  // (depending on sort dir) in sorted int format is in the top-K.\n  // The top-K value itself might not be unique.\n  //\n  // Since there are a variable number of elements that we see that\n  // are within the top-k, we don't know at what index to write out\n  // the resulting values.\n  // In order to get this, we perform an exclusive prefix sum of\n  // `hasTopK`. This will return the resulting index into which we\n  // need to write the result, if a thread has a result.\n\n  // All threads need to participate in the loop and the prefix sum,\n  // but not necessarily in the load; hence loop bounds being rounded\n  // up to a multiple of the block dim.\n  int numIterations = math::roundUp(inputSliceSize, (int) blockDim.x);\n  int writeIndexStart = 0;\n\n  for (int i = threadIdx.x; i < numIterations; i += blockDim.x) {\n    bool inRange = (i < inputSliceSize);\n    T v = inRange ? inputSliceStart[i] : (T)0;\n    bool hasTopK;\n    if (Order) {\n      hasTopK = inRange && (v > topKValue);\n    } else {\n      hasTopK = inRange && (v < topKValue);\n    }\n\n    int index;\n    int carry;\n    exclusiveBinaryPrefixScan<int, true>(smem, hasTopK, &index, &carry, AddOp<int>());\n\n    if (hasTopK) {\n      int writeIndex = writeIndexStart + index;\n      assert(writeIndex < outputSliceSize);\n\n      int topKOffset = writeIndex;\n      int indexOffset = writeIndex;\n\n      topKSliceStart[topKOffset] = v;\n      indicesSliceStart[indexOffset] = i;\n    }\n\n    writeIndexStart += carry;\n  }\n\n  // We need to fill in the rest with actual == top-K values.\n  // The number that we need is outputSliceSize -\n  // writeIndexStart. There might be more than that number available,\n  // in which case we have to choose the first seen set. We do this\n  // via a prefix sum to calculate indices for writing results.\n  assert(outputSliceSize >= writeIndexStart);\n  int topKRemaining = (outputSliceSize - writeIndexStart);\n\n  for (int i = threadIdx.x; i < numIterations; i += blockDim.x) {\n    bool inRange = (i < inputSliceSize);\n    T v = inRange ? inputSliceStart[i] : (T)0;\n    bool hasTopK = inRange && (v == topKValue);\n\n    int index;\n    int carry;\n    exclusiveBinaryPrefixScan<int, true>(smem, hasTopK, &index, &carry, AddOp<int>());\n\n    if (hasTopK && index < topKRemaining) {\n      int writeIndex = writeIndexStart + index;\n      assert(writeIndex < outputSliceSize);\n\n      int topKOffset = writeIndex;\n      int indexOffset = writeIndex;\n\n      topKSliceStart[topKOffset] = v;\n      indicesSliceStart[indexOffset] = i;\n    }\n\n    if (carry >= topKRemaining) {\n      break;\n    }\n\n    topKRemaining -= carry;\n    writeIndexStart += carry;\n  }\n}\n\n#undef RADIX_BITS\n#undef RADIX_SIZE\n#undef RADIX_MASK\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_TOP_K_RADIX_SELECTION_H_\n"
  },
  {
    "path": "caffe2/operators/transpose_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/transpose_op.h\"\n\n#ifdef CAFFE2_USE_MKL\n#include \"caffe2/mkl/operators/operator_fallback_mkl.h\"\n#endif // CAFFE2_USE_MKL\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Transpose, TransposeOp<CPUContext>);\n\n#ifdef CAFFE2_HAS_MKL_DNN\n// Registering in operator_fallback_mkl.cc results in a linker error in\n// in opt build related to DoRunWithType().\nREGISTER_MKL_OPERATOR(Transpose, mkl::MKLFallbackOp<TransposeOp<CPUContext>>);\n#endif // CAFFE2_HAS_MKL_DNN\n\nOPERATOR_SCHEMA(Transpose)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction([](\n        const OperatorDef& def,\n        const vector<TensorShape>& in) {\n      ArgumentHelper helper(def);\n      vector<int> axes = helper.GetRepeatedArgument<int>(\"axes\");\n      vector<TensorShape> out(1);\n      out[0].set_data_type(in[0].data_type());\n\n      if (axes.empty()) {\n        for (auto axis = in [0].dims().rbegin(); axis != in[0].dims().rend();\n             ++axis) {\n          out[0].add_dims(*axis);\n        }\n      } else {\n        auto tensor_size = in[0].dims().size();\n        auto valid_axes =\n            std::all_of(axes.begin(), axes.end(), [&tensor_size](int& axis) {\n              return axis >= 0 && axis < tensor_size;\n            });\n\n        CAFFE_ENFORCE(valid_axes, \"Axes argument passed in had invalid values\");\n        CAFFE_ENFORCE(\n            axes.size() == tensor_size,\n            \"Axes argument passed in had the incorrect size\");\n\n        for (auto axis = axes.begin(); axis != axes.end(); ++axis) {\n          out[0].add_dims(in[0].dims().Get(*axis));\n        }\n      }\n\n      return out;\n    })\n    .SetDoc(R\"DOC(\nTranspose the input tensor similar to numpy.transpose. For example, when\naxes=(1, 0, 2), given an input tensor of shape (1, 2, 3), the output shape\nwill be (2, 1, 3).\n)DOC\")\n    .Arg(\n        \"axes\",\n        \"A list of integers. By default, reverse the dimensions, \"\n        \"otherwise permute the axes according to the values given.\")\n    .Input(0, \"data\", \"An input tensor.\")\n    .Output(0, \"transposed\", \"Transposed output.\");\n\nclass GetTransposeGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  // We will create our own arguments.\n  bool CopyArguments() const override {\n    return false;\n  }\n  vector<OperatorDef> GetGradientDefs() override {\n    auto ops = SingleGradientDef(\n        \"Transpose\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n    ops[0].mutable_arg()->CopyFrom(Def().arg());\n    if (ArgumentHelper::HasArgument(Def(), \"axes\")) {\n      // If axes is specified, we will need to figure out the inverse index.\n      const Argument& old_axes = GetArgument(Def(), \"axes\");\n      const int axes_size = old_axes.ints_size();\n      Argument* new_arg = GetMutableArgument(\"axes\", false, &ops[0]);\n      for (int i = 0; i < axes_size; ++i) {\n        new_arg->set_ints(old_axes.ints(i), i);\n      }\n    }\n    return ops;\n  }\n};\n\nREGISTER_GRADIENT(Transpose, GetTransposeGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/transpose_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/transpose_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(Transpose, TransposeOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/transpose_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_TRANSPOSE_H_\n#define CAFFE2_OPERATORS_TRANSPOSE_H_\n#define MAX_BLOB_NUM 1024\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass TransposeOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_DISPATCH_HELPER;\n  TransposeOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        axes_(OperatorBase::GetRepeatedArgument<int>(\"axes\")) {\n    // We will check the legality of axes_: it should be from 0 to axes_.size().\n    std::vector<int> axes_sorted(axes_);\n    std::sort(axes_sorted.begin(), axes_sorted.end());\n    for (int i = 0; i < axes_sorted.size(); ++i) {\n      if (axes_sorted[i] != i) {\n        CAFFE_THROW(\"Axes should be a permutation of 0 to ndim.\");\n      }\n    }\n  }\n  ~TransposeOp() {}\n\n  bool RunOnDevice() override {\n    const auto& X = Input(0);\n    auto* Y = Output(0);\n    const int num_axes = X.ndim();\n    const std::vector<int> x_dims(X.dims().cbegin(), X.dims().cend());\n    std::vector<int> y_dims(num_axes);\n    if (axes_.empty()) {\n      axes_.resize(num_axes);\n      for (int i = 0; i < num_axes; ++i) {\n        axes_[i] = num_axes - 1 - i;\n      }\n      y_dims.assign(X.dims().rbegin(), X.dims().rend());\n    } else {\n      CAFFE_ENFORCE_EQ(X.ndim(), axes_.size());\n      for (int i = 0; i < num_axes; ++i) {\n        y_dims[i] = X.dim32(axes_[i]);\n      }\n    }\n    Y->Resize(y_dims);\n    SetDeviceTensor(x_dims, &x_dims_device_);\n    SetDeviceTensor(y_dims, &y_dims_device_);\n    SetDeviceTensor(axes_, &axes_device_);\n\n    // Do the actual transpose, which is implemented in DoRunWithType().\n    return DispatchHelper<TensorTypes<float, double, int, long>>::call(\n        this, Input(0));\n  }\n\n protected:\n  void SetDeviceTensor(const std::vector<int>& data, Tensor<Context>* tensor) {\n    tensor->Resize(data.size());\n    context_.template Copy<int, CPUContext, Context>(\n        data.size(), data.data(), tensor->template mutable_data<int>());\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    const auto& X = Input(0);\n    auto* Y = Output(0);\n    math::Transpose<T, Context>(\n        axes_.size(),\n        x_dims_device_.template data<int>(),\n        y_dims_device_.template data<int>(),\n        axes_device_.template data<int>(),\n        X.size(),\n        X.template data<T>(),\n        Y->template mutable_data<T>(),\n        &context_);\n    return true;\n  }\n\n  std::vector<int> axes_;\n\n  Tensor<Context> x_dims_device_;\n  Tensor<Context> y_dims_device_;\n  Tensor<Context> axes_device_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_TRANSPOSE_H_\n"
  },
  {
    "path": "caffe2/operators/transpose_op_cudnn.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/cudnn_wrappers.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/operators/transpose_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n#define MAX_DIMS 8\n\nclass CuDNNTransposeOp final : public Operator<CUDAContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n  USE_DISPATCH_HELPER;\n\n  CuDNNTransposeOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws),\n        cudnn_wrapper_(&context_),\n        axes_(OperatorBase::GetRepeatedArgument<int>(\"axes\")) {\n    // We will check the legality of axes_: it should be from 0 to axes_.size().\n    std::vector<int> axes_sorted(axes_);\n    std::sort(axes_sorted.begin(), axes_sorted.end());\n    for (int i = 0; i < axes_sorted.size(); ++i) {\n      if (axes_sorted[i] != i) {\n        CAFFE_THROW(\"Axes should be a permutation of 0 to ndim.\");\n      }\n    }\n\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&xDesc_));\n    CUDNN_ENFORCE(cudnnCreateTensorDescriptor(&yDesc_));\n  }\n\n  ~CuDNNTransposeOp() {\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(xDesc_));\n    CUDNN_ENFORCE(cudnnDestroyTensorDescriptor(yDesc_));\n  }\n\n  bool RunOnDevice() override {\n    const auto& X = Input(0);\n    auto* Y = Output(0);\n    const int num_axes = X.ndim();\n    const std::vector<int> x_dims(X.dims().cbegin(), X.dims().cend());\n    std::vector<int> y_dims(num_axes);\n    if (axes_.empty()) {\n      axes_.resize(num_axes);\n      for (int i = 0; i < num_axes; ++i) {\n        axes_[i] = num_axes - 1 - i;\n      }\n      y_dims.assign(X.dims().rbegin(), X.dims().rend());\n    } else {\n      CAFFE_ENFORCE_EQ(X.ndim(), axes_.size());\n      for (int i = 0; i < num_axes; ++i) {\n        y_dims[i] = X.dim32(axes_[i]);\n      }\n    }\n    Y->Resize(y_dims);\n    SetDeviceTensor(x_dims, &x_dims_device_);\n    SetDeviceTensor(y_dims, &y_dims_device_);\n    SetDeviceTensor(axes_, &axes_device_);\n    // Do the actual transpose, which is implemented in DoRunWithType().\n#if CUDNN_VERSION_MIN(6, 0, 0)\n    return DispatchHelper<TensorTypes<float, int>>::call(this, Input(0));\n#else\n    // CUDNN 5.1 does not have int support yet.\n    return DispatchHelper<TensorTypes<float>>::call(this, Input(0));\n#endif\n  }\n\n protected:\n  void SetDeviceTensor(\n      const std::vector<int>& data,\n      Tensor<CUDAContext>* tensor) {\n    tensor->Resize(data.size());\n    context_.template Copy<int, CPUContext, CUDAContext>(\n        data.size(), data.data(), tensor->template mutable_data<int>());\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    const auto& input = Input(0);\n    auto* output = Output(0);\n    int ndim = input.ndim();\n\n    if (ndim == 0) {\n      return true;\n    }\n    if (ndim == 1) {\n      output->CopyFrom(input);\n      return true;\n    }\n\n    cudnnDataType_t typedesc = cudnnTypeWrapper<T>::type;\n#if CUDNN_VERSION_MIN(6, 0, 0)\n    if (typedesc == CUDNN_DATA_INT32) {\n      // CUDNN Transpose only support float for now\n      math::Transpose<int, CUDAContext>(\n          axes_.size(),\n          x_dims_device_.template data<int>(),\n          y_dims_device_.template data<int>(),\n          axes_device_.template data<int>(),\n          input.size(),\n          input.template data<int>(),\n          output->template mutable_data<int>(),\n          &context_);\n      return true;\n    }\n#endif\n\n    CAFFE_ENFORCE(ndim < MAX_DIMS, \"Input ndim exceeds compile time max.\");\n\n    stride_y[ndim - 1] = 1;\n    for (int i = ndim - 2; i >= 0; i--) {\n      stride_y[i] = stride_y[i + 1] * output->dim32(i + 1);\n    }\n\n    CHECK(axes_.size() >= ndim);\n\n    stride_x[ndim] = 1;\n    for (int i = 0; i < ndim; i++) {\n      stride_x[i] = 1;\n      for (int j = axes_[i] + 1; j < ndim; j++) {\n        stride_x[i] *= input.dim32(j);\n      }\n      dim_y_int[i] = output->dim32(i);\n    }\n\n    // CuDNN requires at least 3-dim tensors\n    for (int i = ndim; i < MAX_DIMS; i++) {\n      stride_x[i] = 1;\n      stride_y[i] = 1;\n      dim_y_int[i] = 1;\n    }\n\n    CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n        xDesc_, typedesc, ndim < 4 ? 4 : ndim, dim_y_int, stride_x));\n\n    CUDNN_ENFORCE(cudnnSetTensorNdDescriptor(\n        yDesc_, typedesc, ndim < 4 ? 4 : ndim, dim_y_int, stride_y));\n\n    CUDNN_ENFORCE(cudnnTransformTensor(\n        cudnn_wrapper_.inline_cudnn_handle(),\n        cudnnTypeWrapper<T>::kOne(),\n        xDesc_,\n        static_cast<const void*>(input.template data<T>()),\n        cudnnTypeWrapper<T>::kZero(),\n        yDesc_,\n        static_cast<void*>(output->template mutable_data<T>())));\n    return true;\n  }\n\n  int stride_x[MAX_DIMS];\n  int stride_y[MAX_DIMS];\n  int dim_y_int[MAX_DIMS];\n\n  cudnnTensorDescriptor_t xDesc_;\n  cudnnTensorDescriptor_t yDesc_;\n  CuDNNWrapper cudnn_wrapper_;\n\n  std::vector<int> axes_;\n\n  Tensor<CUDAContext> x_dims_device_;\n  Tensor<CUDAContext> y_dims_device_;\n  Tensor<CUDAContext> axes_device_;\n};\n\nREGISTER_CUDNN_OPERATOR(Transpose, CuDNNTransposeOp);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/tt_linear_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/tt_linear_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\nREGISTER_CPU_OPERATOR(TT, TTLinearOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(TTLinearGradient, TTLinearGradientOp<float, CPUContext>);\n\n// The TT-layer serves as a low-rank decomposition of a fully connected layer.\n// The inputs are the same as to an FC layer, but the number of the parameters\n// are greatly reduced.\nOPERATOR_SCHEMA(TT)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nThe TT-layer serves as a low-rank decomposition of a fully connected layer. The\ninputs are the same as to a fully connected layer, but the number of parameters\nare greatly reduced and forward computation time can be drastically reduced\nespecially for layers with large weight matrices. The multiplication is computed\nas a product of the input vector with each of the cores that make up the TT\nlayer. Given the input sizes (inp_sizes), output sizes(out_sizes), and the ranks\nof each of the cores (tt_ranks), the ith core will have size:\n\n    inp_sizes[i] * tt_ranks[i] * tt_ranks[i + 1] * out_sizes[i].\n\nThe complexity of the computation is dictated by the sizes of inp_sizes,\nout_sizes, and tt_ranks, where there is the trade off between accuracy of the\nlow-rank decomposition and the speed of the computation.\n)DOC\")\n    .Arg(\n        \"inp_sizes\",\n        \"(int[]) Input sizes of cores. Indicates the input size of \"\n        \"the individual cores; the size of the input vector X must match the \"\n        \"product of the inp_sizes array.\")\n    .Arg(\n        \"out_sizes\",\n        \"(int[]) Output sizes of cores. Indicates the output size \"\n        \"of the individual cores; the size of the output vector Y must match \"\n        \"the product of the out_sizes array.\")\n    .Arg(\n        \"tt_ranks\",\n        \"(int[]) Ranks of cores. Indicates the ranks of the \"\n        \"individual cores; lower rank means larger compression, faster \"\n        \"computation but reduce accuracy.\")\n    .Input(\n        0,\n        \"X\",\n        \"Input tensor from previous layer with size (M x K), where \"\n        \"M is the batch size and K is the input size.\")\n    .Input(1, \"b\", \"1D blob containing the bias vector\")\n    .Input(\n        2,\n        \"cores\",\n        \"1D blob containing each individual cores with sizes \"\n        \"specified above.\")\n    .Output(\n        0,\n        \"Y\",\n        \"Output tensor from previous layer with size (M x N), \"\n        \"where M is the batch size and N is the output size.\");\n\nOPERATOR_SCHEMA(TTLinearGradient);\n\nGRADIENT_NOT_IMPLEMENTED_YET(TT);\n} // namespace\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/tt_linear_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_TT_LINEAR_OP_H_\n#define CAFFE2_OPERATORS_TT_LINEAR_OP_H_\n\n#ifdef CAFFE2_USE_MKL\n#include <mkl.h>\n#endif // CAFFE2_USE_MKL\n\n#include \"Eigen/Core\"\n#include \"Eigen/Dense\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nclass TTLinearOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  TTLinearOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        inp_sizes_(OperatorBase::GetRepeatedArgument<int>(\"inp_sizes\")),\n        out_sizes_(OperatorBase::GetRepeatedArgument<int>(\"out_sizes\")),\n        tt_ranks_(OperatorBase::GetRepeatedArgument<int>(\"tt_ranks\")),\n        Y_temp_(unique_ptr<Blob>(new Blob())) {}\n  ~TTLinearOp() {}\n\n  bool RunOnDevice() override {\n    const auto& X = Input(0); // Input array\n    const auto& b = Input(1); // Bias array\n    const auto& cores = Input(2); // 1D array containing the TT-cores\n    auto* Y = Output(0);\n\n    CAFFE_ENFORCE(X.ndim() > 1, \"Number of dimensions in X: \", X.ndim());\n    CAFFE_ENFORCE(b.ndim() == 1, \"Number of dimensions in b: \", b.ndim());\n    CAFFE_ENFORCE(\n        inp_sizes_.size() == out_sizes_.size(),\n        \"inp_sizes has size: \",\n        inp_sizes_.size(),\n        \", out_sizes has size: \",\n        out_sizes_.size());\n    CAFFE_ENFORCE(\n        cores.ndim() == 1, \"Number of dimensions in cores: \", cores.ndim());\n    // batch size\n    const int batch_size = X.ndim() > 1 ? X.dim32(0) : 1;\n\n    // dimension d of tensors\n    const int d = inp_sizes_.size();\n\n    // Keep track of index of current core in multiplication\n    int cores_idx = 0;\n\n    // Temporary buffer to facilitate multiplication of TT-cores with input\n    auto Y_buf = Y_temp_->GetMutable<Tensor<Context>>();\n    Y_buf->ResizeLike(X);\n    Y_buf->CopyFrom(X);\n\n    // The overall forward pass involves multiplication with each core, where\n    // each core has sizes dictated by inp_sizes_ and out_sizes_. Each core thus\n    // has size inp_sizes_[i] * tt_ranks_[i] * tt_ranks_[i + 1] * out_sizes_[i].\n    for (int i = (d - 1); i >= 0; --i) {\n      int curr_rows = inp_sizes_[i] * tt_ranks_[i + 1];\n      int curr_cols = tt_ranks_[i] * out_sizes_[i];\n\n      // TODO Replace by Reshape(), once wrappers are written\n      Y_buf->Resize(Y_buf->size() / curr_rows, curr_rows);\n      Y->Resize(Y_buf->size() / curr_rows, curr_cols);\n\n      // Defensive checks\n      CAFFE_ENFORCE(Y_buf->size() % curr_rows == 0, Y_buf->size(), curr_rows);\n      CAFFE_ENFORCE(\n          cores_idx + curr_rows * curr_cols <= cores.size(),\n          cores_idx + curr_rows * curr_cols,\n          cores.size());\n\n      // Multiply ith core with the intermediate output\n      math::Gemm<float, Context, Engine>(\n          CblasNoTrans,\n          CblasNoTrans,\n          Y_buf->size() / curr_rows,\n          curr_cols,\n          curr_rows,\n          1,\n          Y_buf->template data<float>(),\n          cores.template data<float>() + cores_idx,\n          0,\n          Y->template mutable_data<float>(),\n          &context_);\n\n      CAFFE_ENFORCE(Y->size() % out_sizes_[i] == 0, Y->size(), out_sizes_[i]);\n\n      // TODO Add GPU support by writing a generic wrapper.\n      auto Y_mat = EigenMatrixMap<float>(\n          Y->template mutable_data<float>(),\n          Y->size() / out_sizes_[i],\n          out_sizes_[i]);\n      Y_mat = ConstEigenMatrixMap<float>(\n                  Y->template data<float>(),\n                  out_sizes_[i],\n                  Y->size() / out_sizes_[i])\n                  .transpose()\n                  .eval();\n\n      // Resize operation\n      Y_buf->Resize(Y->dim32(0), Y->dim32(1));\n      context_.template Copy<float, CPUContext, CPUContext>(\n          Y->size(),\n          Y->template data<float>(),\n          Y_buf->template mutable_data<float>());\n\n      cores_idx += curr_rows * curr_cols;\n    }\n\n    // TODO Add GPU support by writing a generic wrapper.\n    auto Y_mat = EigenMatrixMap<float>(\n        Y->template mutable_data<float>(), batch_size, Y->size() / batch_size);\n    Y_mat = ConstEigenMatrixMap<float>(\n                Y->template data<float>(), Y->size() / batch_size, batch_size)\n                .transpose()\n                .eval();\n    // TODO Replace by Reshape(), once wrappers are written\n    Y->Resize(batch_size, Y->size() / batch_size);\n\n    // Check that output size of Y is the element-wise product of out_sizes\n    int prod_out_sizes = 1;\n    for (int i = 0; i < out_sizes_.size(); i++) {\n      prod_out_sizes *= out_sizes_[i];\n    }\n    CAFFE_ENFORCE(\n        Y->dim32(1) == prod_out_sizes,\n        \"Output dimension of Y: \",\n        Y->dim32(1),\n        \", product of out_sizes: \",\n        prod_out_sizes);\n\n    // Add bias term\n    if (bias_multiplier_.size() != batch_size) {\n      // If the helper bias multiplier is not M, reshape and fill it with one.\n      bias_multiplier_.Resize(batch_size);\n      math::Set<T, Context>(\n          batch_size,\n          static_cast<T>(1),\n          bias_multiplier_.template mutable_data<T>(),\n          &context_);\n    }\n    math::Gemm<T, Context, Engine>(\n        CblasNoTrans,\n        CblasNoTrans,\n        Y->dim32(0),\n        Y->dim32(1),\n        1,\n        1,\n        bias_multiplier_.template data<T>(),\n        b.template data<T>(),\n        1,\n        Y->template mutable_data<T>(),\n        &context_);\n    return true;\n  }\n\n protected:\n  Tensor<Context> bias_multiplier_;\n  std::vector<int> inp_sizes_;\n  std::vector<int> out_sizes_;\n  std::vector<int> tt_ranks_;\n  std::unique_ptr<Blob> Y_temp_;\n};\n\n// TODO: Complete after verifying utility of TT-layer's forward pass.\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nclass TTLinearGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  TTLinearGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  ~TTLinearGradientOp() {}\n\n  bool RunOnDevice() override {\n    return false;\n  }\n\n protected:\n  Tensor<Context> bias_multiplier_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_TT_LINEAR_OP_H_\n"
  },
  {
    "path": "caffe2/operators/utility_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/utility_ops.h\"\n\n#include <cmath>\n\nnamespace caffe2 {\n\ntemplate <>\nbool WeightedSumOp<CPUContext>::RunOnDevice() {\n  return DoRunWithType<float>();\n}\n\ntemplate <>\nbool WeightedSumGradientOp<CPUContext>::RunOnDevice() {\n  return DoRunWithType<float>();\n}\n\ntemplate <>\ntemplate <typename T>\nvoid UniqueOp<CPUContext>::DoRun() {\n  auto& inputTensor = Input(0);\n  // use dim32 to enforce that it's fine to have remapping of type int\n  int N = inputTensor.dim32(0);\n  CAFFE_ENFORCE_EQ(inputTensor.ndim(), 1, \"Input should be a vector\");\n  auto* uniqueTensor = Output(UNIQUE);\n\n  int* remapping = nullptr;\n  if (REMAPPING < OutputSize()) {\n    auto* remappingTensor = Output(REMAPPING);\n    remappingTensor->ResizeLike(inputTensor);\n    remapping = remappingTensor->template mutable_data<int>();\n  }\n\n  const T* input = inputTensor.template data<T>();\n  // TODO(dzhulgakov): if perf becomes an issue consider doing hash table\n  // instead of sorting\n  order_.resize(N);\n  std::iota(order_.begin(), order_.end(), 0);\n  std::sort(order_.begin(), order_.end(), [input](const int x, const int y) {\n    return input[x] < input[y];\n  });\n  int K = N;\n  for (int i = 1; i < N; ++i) {\n    K -= input[order_[i]] == input[order_[i - 1]];\n  }\n  uniqueTensor->Resize(K);\n  T* unique = uniqueTensor->template mutable_data<T>();\n  K = 0;\n  T prev = -1;\n  for (int i = 0; i < N; ++i) {\n    if (i == 0 || prev != input[order_[i]]) {\n      prev = unique[K++] = input[order_[i]];\n    }\n    if (remapping) {\n      remapping[order_[i]] = K - 1;\n    }\n  }\n}\n\nREGISTER_CPU_OPERATOR(WallClockTime, WallClockTimeOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Print, PrintOp<CPUContext>);\nREGISTER_CPU_OPERATOR(FlattenToVec, FlattenToVecOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Alias, AliasOp<CPUContext>);\nREGISTER_CPU_OPERATOR(ResizeLike, ResizeLikeOp<CPUContext>);\nREGISTER_CPU_OPERATOR(SumInt, SumOp<CPUContext>);\nREGISTER_CPU_OPERATOR(WeightedSum, WeightedSumOp<CPUContext>);\nREGISTER_CPU_OPERATOR(WeightedSumGradient, WeightedSumGradientOp<CPUContext>);\nREGISTER_CPU_OPERATOR(\n    ScatterWeightedSum,\n    ScatterWeightedSumOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(ScatterAssign, ScatterAssignOp<CPUContext>);\n// From whatever the current context, ensure the output is TensorCPU\nREGISTER_CPU_OPERATOR(\n    EnsureCPUOutput,\n    CopyOp<CPUContext, CPUContext, CPUContext>);\n// From CPU, copy it to whatever the current context\nREGISTER_CPU_OPERATOR(\n    CopyFromCPUInput,\n    CopyOp<CPUContext, CPUContext, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    CopyOnDeviceLike,\n    CopyOnDeviceLikeOp<CPUContext, CPUContext, CPUContext>);\nREGISTER_CPU_OPERATOR(Copy, CopyOp<CPUContext, CPUContext, CPUContext>);\nREGISTER_CPU_OPERATOR(LengthsToShape, LengthsToShapeOp<CPUContext>);\nREGISTER_CPU_OPERATOR(HasElements, HasElementsOp<CPUContext>);\nREGISTER_CPU_OPERATOR(IsEmpty, IsEmptyOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Gather, GatherOp<CPUContext>);\nREGISTER_CPU_OPERATOR(GatherRanges, GatherRangesOp<CPUContext>);\nREGISTER_CPU_OPERATOR(LengthsGather, LengthsGatherOp<CPUContext>);\nREGISTER_CPU_OPERATOR(Unique, UniqueOp<CPUContext>);\nREGISTER_CPU_OPERATOR(LengthsToSegmentIds, LengthsToSegmentIdsOp<CPUContext>);\nREGISTER_CPU_OPERATOR(LengthsToRanges, LengthsToRangesOp<CPUContext>);\nREGISTER_CPU_OPERATOR(SegmentIdsToLengths, SegmentIdsToLengthsOp<CPUContext>);\nREGISTER_CPU_OPERATOR(SegmentIdsToRanges, SegmentIdsToRangesOp<CPUContext>);\nREGISTER_CPU_OPERATOR(LengthsToWeights, LengthsToWeightsOp<CPUContext>);\nREGISTER_CPU_OPERATOR(EnsureDense, EnsureDenseOp<CPUContext>);\nREGISTER_CPU_OPERATOR(\n    AccumulateHistogram,\n    AccumulateHistogramOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(WallClockTime)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(\"Time since epoch in nanoseconds.\")\n    .Output(0, \"time\", \"The time in nanoseconds.\");\n\nREGISTER_CPU_OPERATOR(UnsafeCoalesce, UnsafeCoalesceOp<CPUContext>);\n\nOPERATOR_SCHEMA(Print)\n    .NumInputs(1)\n    .NumOutputs(0)\n    .SetDoc(\"Logs shape and contents of input tensor to stderr or to a file.\")\n    .Arg(\n        \"to_file\",\n        \"(bool) if 1, saves contents to the root folder of the current \"\n        \"workspace, appending the tensor contents to a file named after \"\n        \"the blob name. Otherwise, logs to stderr.\")\n    .Input(0, \"tensor\", \"The tensor to print.\");\n\nOPERATOR_SCHEMA(LengthsToShape).NumInputs(1).NumOutputs(1);\n\nOPERATOR_SCHEMA(FlattenToVec)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .TensorInferenceFunction([](const OperatorDef& /*def*/,\n                                const vector<TensorShape>& in) {\n      vector<TensorShape> out(1);\n      int total = 1;\n      for (auto d : in[0].dims()) {\n        total *= d;\n      }\n      out[0].set_data_type(in[0].data_type());\n      out[0].add_dims(total);\n      return out;\n    })\n    .SetDoc(R\"DOC(\nFlattens the input tensor into a 1D vector.\n)DOC\")\n    .Input(0, \"input\", \"A tensor of rank >= 1.\")\n    .Output(\n        0,\n        \"output\",\n        \"A tensor of rank 1 with the contents of the input tensor\");\n\nOPERATOR_SCHEMA(Alias)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nMakes the output and the input share the same underlying storage.\n\nWARNING: in general, in caffe2's operator interface different tensors should\nhave different underlying storage, which is the assumption made by\ncomponents such as the dependency engine and memory optimization. Thus, in\nnormal situations you should not use the AliasOp, especially in a normal\nforward-backward pass.\n\nThe Alias op is provided so one can achieve true asynchrony, such as\nHogwild, in a graph. But make sure you understand all the implications\nsimilar to multi-thread computation before you use it explicitly.\n)DOC\")\n    .Input(0, \"input\", \"Input tensor whose storage will be shared.\")\n    .Output(0, \"output\", \"Tensor of same shape as input, sharing its storage.\");\n\nOPERATOR_SCHEMA(ResizeLike)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .TensorInferenceFunction([](const OperatorDef& /*def*/,\n                                const vector<TensorShape>& in) {\n      vector<TensorShape> out(1);\n      out.push_back(in[1]);\n      out[0].set_data_type(in[0].data_type());\n      return out;\n    })\n    .SetDoc(R\"DOC(\nProduces tensor containing data of first input and shape of second input.\n)DOC\")\n    .Input(0, \"data\", \"Tensor whose data will be copied into the output.\")\n    .Input(1, \"shape_tensor\", \"Tensor whose shape will be applied to output.\")\n    .Output(0, \"output\", \"Tensor with data of input 0 and shape of input 1.\");\n\nOPERATOR_SCHEMA(SumInt)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1)\n    .InputsCanCrossDevices()\n    .TensorInferenceFunction([](const OperatorDef& /*def*/,\n                                const vector<TensorShape>& in) {\n      vector<TensorShape> out(1);\n      out.push_back(in[0]);\n      out[0].set_data_type(TensorProto::INT32);\n      return out;\n    })\n    .AllowInplace({{0, 0}});\n\nOPERATOR_SCHEMA(WeightedSum)\n    .NumInputs([](int n) { return (n > 0 && n % 2 == 0); })\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShapeOfInput(0)\n    .SetDoc(R\"DOC(\nElement-wise weighted sum of several data, weight tensor pairs.\nInput should be in the form X_0, weight_0, X_1, weight_1, ... where X_i all\nhave the same shape, and weight_i are size 1 tensors that specifies the weight\nof each vector. Note that if one wants to do in-place computation, it could\nonly be done with X_0 also as the output, but not other X_i.\n)DOC\")\n    .Input(0, \"data_0\", \"First of the input tensors.\")\n    .Input(0, \"weight_0\", \"Weight of the first input in the sum.\")\n    .Output(0, \"output\", \"Result containing weighted elem-wise sum of inputs.\");\n\nOPERATOR_SCHEMA(WeightedSumGradient)\n    .NumInputs([](int n) { return (n > 0 && n % 2 == 1); })\n    .NumOutputs(1, INT_MAX);\n\nOPERATOR_SCHEMA(ScatterWeightedSum)\n    .NumInputs([](int n) { return (n > 3 && (n - 3) % 2 == 0); })\n    .NumOutputs(1)\n    .EnforceInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nSimilar to WeightedSum, computes the weighted sum of several tensors, with\nthe difference that inputs are sliced tensors. The first tensor has to be\nin-place and only slices of it on the first dimension as indexed by INDICES\nwill be updated.\n\nNote: The op pretty much ignores the exact shapes of the input arguments and\ncares only about sizes. It's done for performance consideration to avoid\nunnecessary reshapes. Only first dimension of X_0 is important, let's call it\nN. If M is the total size of X_0 and K is the size of INDICES then X_i is\nassumed to be of shape K x (M / N) regardless of the real shape.\n\nNote: Each update in INDICES is applied independently which means that if\nduplicated elements are present in INDICES the corresponding slice of X_0\nwill be scaled multiple times. Manual collapsing of INDICES is required\nbeforehand if necessary.\n\nNote: Updates are applied sequentially by inputs which might have undesired\nconsequences if the input tensor is accessed concurrently by different op\n(e.g. when doing Hogwild). Other threads might see intermediate results even\non individual slice level, e.g. X_0 scaled by weight_0 but without any\nupdates applied.\n\nCurrently only works on CPU because of access to INDICES.\n)DOC\")\n    .Input(0, \"X_0\", \"Tensor to be updated.\")\n    .Input(\n        1,\n        \"Weight_0\",\n        \"Scalar weight for X_0, applied only to slices affected.\")\n    .Input(\n        2,\n        \"INDICES\",\n        \"1-D list of indices on the first dimension of X_0 \"\n        \"that need to be updated\")\n    .Input(3, \"X_1\", \"Update slices, with shape len(INDICES) + shape(X_0)[1:]\")\n    .Input(4, \"Weight_1\", \"Scalar weight for X_1 update\")\n    .Output(0, \"X_0\", \"Has to be exactly the same tensor as the input 0\")\n    .EnforceInplace({{0, 0}});\n\nOPERATOR_SCHEMA(ScatterAssign)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .EnforceInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nUpdate slices of the tensor in-place by overriding current value.\n\nNote: The op pretty much ignores the exact shapes of the input arguments and\ncares only about sizes. It's done for performance consideration to avoid\nunnecessary reshapes. Only first dimension of X_0 is important, let's call it\nN. If M is the total size of X_0 and K is the size of INDICES then X_i is\nassumed to be of shape K x (M / N) regardless of the real shape.\n\nNote: Each update in INDICES is applied independently which means that if\nduplicated elements are present in INDICES arbitrary one will win.\n\nCurrently only works on CPU because of access to INDICES.\n)DOC\")\n    .Input(0, \"DATA\", \"Tensor to be updated.\")\n    .Input(\n        1,\n        \"INDICES\",\n        \"1-D list of indices on the first dimension\"\n        \"of X_0 that need to be updated\")\n    .Input(\n        2,\n        \"SLICES\",\n        \"Update slices, with shape len(INDICES) + shape(X_0)[1:]\")\n    .Output(0, \"DATA\", \"Has to be exactly the same tensor as the input 0\");\n\nOPERATOR_SCHEMA(Copy)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .IdenticalTypeAndShape()\n    .InputsCanCrossDevices()\n    .SetDoc(\"Copy input tensor into output, potentially across devices.\")\n    .Input(0, \"input\", \"The input tensor.\")\n    .Output(0, \"output\", \"Tensor that will contain a copy of the input.\");\n\nOPERATOR_SCHEMA(CopyGPUToCPU)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .IdenticalTypeAndShape()\n    .InputsCanCrossDevices()\n    .DeviceInferenceFunction([](const OperatorDef& def) {\n      CAFFE_ENFORCE(\n          def.has_device_option(),\n          \"CopyGPUToCPU op should have cuda device option.\");\n      auto& cuda_option = def.device_option();\n      auto cpu_option = DeviceOption();\n      vector<DeviceOption> in_dev(def.input_size(), cuda_option);\n      vector<DeviceOption> out_dev(def.output_size(), cpu_option);\n      return std::make_pair(in_dev, out_dev);\n    })\n    .SetDoc(R\"DOC(\nCopy tensor for GPU to CPU context. Must be run under GPU device option.\n)DOC\")\n    .Input(0, \"input\", \"The input tensor.\")\n    .Output(0, \"output\", \"Tensor that will contain a copy of the input.\");\n\nOPERATOR_SCHEMA(CopyCPUToGPU)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .IdenticalTypeAndShape()\n    .InputsCanCrossDevices()\n    .DeviceInferenceFunction([](const OperatorDef& def) {\n      CAFFE_ENFORCE(\n          def.has_device_option(),\n          \"CopyCPUToGPU op should have cuda device option.\");\n      auto& cuda_option = def.device_option();\n      auto cpu_option = DeviceOption();\n      vector<DeviceOption> in_dev(def.input_size(), cpu_option);\n      vector<DeviceOption> out_dev(def.output_size(), cuda_option);\n      return std::make_pair(in_dev, out_dev);\n    })\n    .SetDoc(R\"DOC(\nCopy tensor for CPU to GPU context. Must be run under GPU device option.\n)DOC\")\n    .Input(0, \"input\", \"The input tensor.\")\n    .Output(0, \"output\", \"Tensor that will contain a copy of the input.\");\n\nOPERATOR_SCHEMA(EnsureCPUOutput)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .IdenticalTypeAndShape()\n    .InputsCanCrossDevices()\n    .DeviceInferenceFunction([](const OperatorDef& def) {\n      auto op_device =\n          def.has_device_option() ? def.device_option() : DeviceOption();\n      auto cpu_option = DeviceOption();\n      vector<DeviceOption> in_dev(def.input_size(), op_device);\n      vector<DeviceOption> out_dev(def.output_size(), cpu_option);\n      return std::make_pair(in_dev, out_dev);\n    })\n    .SetDoc(R\"DOC(\nTake an input tensor in the current Context (GPU or CPU) and create an output\nwhich is always a TensorCPU. This may involves cross-device MemCpy.\n)DOC\")\n    .Input(0, \"input\", \"The input CUDA or CPU tensor.\")\n    .Output(0, \"output\", \"TensorCPU that is a copy of the input.\");\n\nOPERATOR_SCHEMA(CopyFromCPUInput)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .IdenticalTypeAndShape()\n    .InputsCanCrossDevices()\n    .DeviceInferenceFunction([](const OperatorDef& def) {\n      auto op_device =\n          def.has_device_option() ? def.device_option() : DeviceOption();\n      auto cpu_option = DeviceOption();\n      vector<DeviceOption> in_dev(def.input_size(), cpu_option);\n      vector<DeviceOption> out_dev(def.output_size(), op_device);\n      return std::make_pair(in_dev, out_dev);\n    })\n    .SetDoc(R\"DOC(\nTake a CPU input tensor and copy it to an output in the current\nContext (GPU or CPU). This may involves cross-device MemCpy.\n)DOC\")\n    .Input(0, \"input\", \"The input CPU tensor.\")\n    .Output(0, \"output\", \"either a TensorCUDA or a TensorCPU\");\n\nOPERATOR_SCHEMA(CopyOnDeviceLike)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(\"Copy input tensor into output to the specific device.\")\n    .Input(0, \"input\", \"The input tensor.\")\n    .Input(1, \"dst\", \"Tensor, on which device the copy will be performed.\")\n    .Output(0, \"output\", \"Tensor that will contain a copy of the input.\");\n\nOPERATOR_SCHEMA(HasElements)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(\"Returns true iff the input tensor has size > 0\")\n    .Input(0, \"tensor\", \"Tensor of any type.\")\n    .Output(\n        0,\n        \"has_elements\",\n        \"Scalar bool tensor. True if input is not empty.\");\n\nOPERATOR_SCHEMA(IsEmpty)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(\"Returns true iff the input tensor has size == 0\")\n    .ScalarType(::caffe2::TensorProto_DataType::TensorProto_DataType_BOOL)\n    .Input(0, \"tensor\", \"Tensor of any type.\")\n    .Output(0, \"is_empty\", \"Scalar bool tensor. True if input is empty.\");\n\nOPERATOR_SCHEMA(Gather)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven DATA tensor of rank r >= 1, and INDICES tensor of rank q, gather\nentries of the outer-most dimension of DATA indexed by INDICES, and concatenate\nthem in an output tensor of rank q + (r - 1).\n\nExample:\n  DATA  = [\n      [1.0, 1.2],\n      [2.3, 3.4],\n      [4.5, 5.7],\n  ]\n  INDICES = [\n      [0, 1],\n      [1, 2],\n  ]\n  OUTPUT = [\n      [\n          [1.0, 1.2],\n          [2.3, 3.4],\n      ],\n      [\n          [2.3, 3.4],\n          [4.5, 5.7],\n      ],\n  ]\n)DOC\")\n    .Input(0, \"DATA\", \"Tensor of rank r >= 1.\")\n    .Input(1, \"INDICES\", \"Tensor of int32/int64 indices, of any rank q.\")\n    .Output(0, \"OUTPUT\", \"Tensor of rank q + (r - 1).\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      vector<TensorShape> out(1);\n      for (auto d : in[1].dims()) {\n        out[0].add_dims(d);\n      }\n      for (int i = 1; i < in[0].dims_size(); ++i) {\n        out[0].add_dims(in[0].dims(i));\n      }\n      out[0].set_data_type(in[0].data_type());\n      return out;\n    });\n\nOPERATOR_SCHEMA(GatherRanges)\n    .NumInputs(2)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nGiven DATA tensor of rank 1, and RANGES tensor of rank 3, gather\ncorresponding ranges into a 1-D tensor OUTPUT.\n\nRANGES dimentions description:\n1: represents list of examples within a batch\n2: represents list features\n3: two values which are start and length or a range (to be applied on DATA)\n\nAnother output LENGTHS represents each example length within OUTPUT\n\nExample:\n  DATA  = [1, 2, 3, 4, 5, 6]\n  RANGES = [\n    [\n      [0, 1],\n      [2, 2],\n    ],\n    [\n      [4, 1],\n      [5, 1],\n    ]\n  ]\n  OUTPUT = [1, 3, 4, 5, 6]\n  LENGTHS = [3, 2]\n)DOC\")\n    .Input(0, \"DATA\", \"Tensor of rank 1.\")\n    .Input(\n        1,\n        \"RANGES\",\n        \"Tensor of int32/int64 ranges, of dims (N, M, 2). \"\n        \"Where N is number of examples and M is a size of each example. \"\n        \"Last dimension represents a range in the format (start, lengths)\")\n    .Output(0, \"OUTPUT\", \"1-D tensor of size sum of range lengths\")\n    .Output(\n        1,\n        \"LENGTHS\",\n        \"1-D tensor of size N with lengths over gathered data\"\n        \" for each row in a batch. sum(LENGTHS) == OUTPUT.size()\")\n    .TensorInferenceFunction([](const OperatorDef& /* unused */,\n                                const vector<TensorShape>& in) {\n      std::vector<TensorShape> out(2);\n\n      int total = 1;\n      for (auto d : in[0].dims()) {\n        total *= d;\n      }\n      out[0].add_dims(total);\n      out[0].set_data_type(in[0].data_type());\n      out[1].add_dims(in[1].dims(0));\n      out[1].set_data_type(in[1].data_type());\n      return out;\n    });\n\nOPERATOR_SCHEMA(LengthsGather)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGather items from sparse tensor. Sparse tensor is described by items and\nlengths. This operator gathers items corresponding to lengths at the given\nindices. This deliberately doesn't return lengths of OUTPUTS so that both lists\nand maps can be supported without special cases. If you need lengths tensor for\n OUTPUT, use `Gather`.\n\nExample:\n  ITEMS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n  LENGTHS = [0, 2, 3, 1, 4]\n  INDICES = [0, 2, 4]\n\n  OUTPUT = [2, 3, 4, 6, 7, 8, 9]\n)DOC\")\n    .Input(0, \"ITEMS\", \"items tensor\")\n    .Input(1, \"LENGTHS\", \"lengths tensor\")\n    .Input(2, \"INDICES\", \"indices into LENGTHS where items should be gathered\")\n    .Output(0, \"OUTPUT\", \"1-D tensor containing gathered items\");\n\nOPERATOR_SCHEMA(Unique)\n    .NumInputs(1)\n    .NumOutputs(1, 2)\n    .SetDoc(R\"DOC(\nDeduplicates input indices vector and optionally produces reverse remapping.\nThere's no guarantees on the ordering of the output indices.\n)DOC\")\n    .Input(0, \"indices\", \"1D tensor of int32 or int64 indices.\")\n    .Output(0, \"unique_indices\", \"1D tensor of deduped entries.\")\n    .Output(\n        1,\n        \"remapping\",\n        \"(optional) mapping from `indices` to `unique_indices`. This has the \"\n        \"same shape as `indices`. Its elements are the indices into \"\n        \"`unique_indices` such that `Gather(['unique_indices', 'remapping'])` \"\n        \"yields `indices`.\")\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      std::vector<TensorShape> out(1);\n      out[0].set_data_type(in[0].data_type());\n      CAFFE_ENFORCE_EQ(in[0].dims_size(), 1);\n      if (in[0].dims(0) <= 1) {\n        // This special case is useful in some situation, e.g., when feeding\n        // tensor inference with empty tensor (where the first dim is the batch\n        // size)\n        out[0].add_dims(in[0].dims(0));\n      } else {\n        out[0].set_unknown_shape(true);\n      }\n      if (def.output_size() > 1) {\n        // Remapping has the same shape as the input tensor\n        out.push_back(in[0]);\n        out.back().set_data_type(TensorProto::INT32);\n      }\n      return out;\n    });\n\nOPERATOR_SCHEMA(LengthsToSegmentIds)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven a vector of segment lengths, returns a zero-based, consecutive vector\nof segment_ids. For example, [1, 3, 0, 2] will produce [0, 1, 1, 1, 3, 3].\nIn general, the inverse operation is SegmentIdsToLengths. Notice though that\ntrailing empty sequence lengths can't be properly recovered from segment ids.\n)DOC\")\n    .Input(0, \"lengths\", \"1D tensor of int32 or int64 segment lengths.\")\n    .Output(0, \"segment_ids\", \"1D tensor of length `sum(lengths)`\");\n\nOPERATOR_SCHEMA(LengthsToRanges)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nGiven a vector of segment lengths, calculates offsets of each segment and packs\nthem next to the lengths. For the input vector of length N the output is a Nx2\nmatrix with (offset, lengths) packaged for each segment.\n\nFor example, `[1, 3, 0, 2]` transforms into `[[0, 1], [1, 3], [4, 0], [4, 2]]`.\n)DOC\")\n    .Input(0, \"lengths\", \"1D tensor of int32 segment lengths.\")\n    .Output(\n        0,\n        \"ranges\",\n        \"2D tensor of shape len(lengths) X 2 and the same type as `lengths`\");\n\nOPERATOR_SCHEMA(SegmentIdsToLengths)\n    .NumInputs(1, 2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nTransfers a vector of segment ids to a vector of segment lengths. This operation\nsupports non-consecutive segment ids. Segments not appearing in the input vector\nwill have length 0. If the second input is provided, the number of segments =\nthe size of its first dimension. Otherwise, the number of segments = the last\nindex in the first input vector + 1.\n\nIn general, for consecutive, zero-based segment IDs, this is the inverse\noperation of LengthsToSegmentIds, except that a vector of segment IDs\ncannot represent empty segments at the end (if the second input is absent).\n)DOC\")\n    .Input(0, \"segment_ids\", \"1-D int32_t or int64_t tensor of segment ids\")\n    .Input(\n        1,\n        \"data (optional)\",\n        \"if provided, number of segments = the size of its first dimension\")\n    .Output(0, \"lengths\", \"1-D int64_t tensor of segment lengths\");\n\nOPERATOR_SCHEMA(SegmentIdsToRanges)\n    .NumInputs(1, 2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nTransfers a vector of segment ids to a vector of segment ranges. This operation\nsupports non-consecutive segment ids. Segments not appearing in the input vector\nwill have length 0. If the second input is provided, the number of segments =\nthe size of its first dimension. Otherwise, the number of segments = the last\nindex in the first input vector + 1.\n)DOC\")\n    .Input(0, \"segment_ids\", \"1-D int32_t or int64_t tensor of segment ids\")\n    .Input(\n        1,\n        \"data (optional)\",\n        \"if provided, number of segments = the size of its first dimension\")\n    .Output(0, \"lengths\", \"1-D int64_t tensor of segment lengths\");\n\nOPERATOR_SCHEMA(LengthsToWeights)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .Arg(\"power\", \"n of 1/pow(length,n) for normalization\")\n    .SetDoc(R\"DOC(\nSimilar as LengthsToSegmentIds but output vector of segment\nweights derived by lengths. i.e 1/pow(length, power)\n)DOC\")\n    .Input(0, \"lengths\", \"1-D int32_t or int64_t tensor of lengths\")\n    .Output(0, \"a vector of weights\", \"1-D float tensor of weights by length\");\n\n\n\nSHOULD_NOT_DO_GRADIENT(WallClockTime);\n\nOPERATOR_SCHEMA(UnsafeCoalesce)\n    .NumInputsOutputs([](int inputs, int outputs) {\n      return inputs + 1 == outputs;\n    })\n    .AllowInplace([](int input, int output) { return input == output; })\n    .SetDoc(R\"DOC(\nCoalesce the N inputs into N outputs and a single coalesced output blob.\n\nThis allows operations that operate over multiple small kernels (e.g.\nbiases in a deep CNN) to be coalesced into a single larger operation,\namortizing the kernel launch overhead, synchronization costs for\ndistributed computation, etc.\n\nThe operator:\n\n- computes the total size of the coalesced blob by summing the input sizes\n- allocates the coalesced output blob as the total size\n- copies the input vectors into the coalesced blob, at the correct offset.\n- aliases each Output(i) to- point into the coalesced blob, at the corresponding offset for Input(i).\n\nThis is 'unsafe' as the output vectors are aliased, so use with\ncaution.\n\n)DOC\");\n\nOPERATOR_SCHEMA(EnsureDense)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShape()\n    .SetDoc(R\"DOC(\nThis operator converts dense or sparse gradients to dense ones.\nTherefore, sparse gradient can be back propagated to Operators that consume\ndense gradients only (e.g., FCGradient).\n\nThe operator's behaviors:\n\n- In forward, simply pass in place or copy input to the output.\n- In backward, if the gradient passed-in is sparse gradient, change it to dense gradient in linear time; otherwise, simply pass the dense gradient.\n)DOC\")\n    .Input(0, \"input\", \"Input tensors.\")\n    .Output(0, \"output\", \"Output tensor. Same dimension as inputs.\");\n\nOPERATOR_SCHEMA(AccumulateHistogram)\n    .NumInputs(1)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nThis operator calculate thes histogram of values in input tensor.\nThere're 2 outputs, one for histogram of current input tensor, and another\nfor histogram of the all input tensors accumulated through history.\nThe output would contain num_buckets + 2 values. index[1 ... num_buckets]\nfor values in [lower_bound, upper_bound) interval. And the rest 2 for values\nsmaller than lower_bound or greater than upper_bound respectively.\n)DOC\")\n    .Input(0, \"X\", \"Input tensor.\")\n    .Output(0, \"CurHist\", \"Output histogram of the current tensor.\")\n    .Output(1, \"AccHist\", \"Accumulated histogram of the history tensor.\")\n    .Arg(\"lower_bound\", \"the lower bound value\")\n    .Arg(\"upper_bound\", \"the upper bound value\")\n    .Arg(\n        \"num_buckets\",\n        \"number of buckets to use in [lower_bound, upper_bound)\");\n\nclass GetEnsureDenseGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE(\n        GradOut(0).IsSparse() || GradOut(0).IsDense(),\n        \"Input gradient \",\n        O(0),\n        \" should be either sparse or dense.\");\n\n    if (GradOut(0).IsDense()) {\n      SetDense(0, GO(0));\n      return vector<OperatorDef>();\n    } else {\n      return SingleGradientDef(\n          \"SparseToDense\",\n          \"\",\n          vector<string>{GO_I(0), GO_V(0), I(0)},\n          vector<string>{GI(0)});\n    }\n  }\n};\nREGISTER_GRADIENT(EnsureDense, GetEnsureDenseGradient);\n\nSHOULD_NOT_DO_GRADIENT(Print);\nSHOULD_NOT_DO_GRADIENT(HasElements);\nSHOULD_NOT_DO_GRADIENT(IsEmpty);\nSHOULD_NOT_DO_GRADIENT(LengthsToShape);\nSHOULD_NOT_DO_GRADIENT(UnsafeCoalesce);\n\nclass GetAliasGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    // We will simply pass-along the gradient. Nothing needs to\n    // be calculated.\n    SetDense(0, GO(0));\n    return vector<OperatorDef>();\n  }\n};\nREGISTER_GRADIENT(Alias, GetAliasGradient);\n\nSHOULD_NOT_DO_GRADIENT(ResizeLike);\n\nclass GetSumGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    for (auto i = 0; i < def_.input_size(); ++i) {\n      SetDense(i, GO(0));\n    }\n    return vector<OperatorDef>();\n  }\n};\nREGISTER_GRADIENT(Sum, GetSumGradient);\n\nSHOULD_NOT_DO_GRADIENT(ScatterWeightedSum);\nSHOULD_NOT_DO_GRADIENT(ScatterAssign);\n\nclass GetWeightedSumGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    ArgumentHelper argsHelper(def_);\n    const bool grad_on_w = argsHelper.GetSingleArgument<bool>(\"grad_on_w\", 0);\n\n    auto inputs = vector<string>{GO(0)};\n    auto outputs = vector<string>();\n    for (int i = 0; i < def_.input_size(); i += 2) {\n      inputs.push_back(I(i));\n      inputs.push_back(I(i + 1));\n      outputs.push_back(GI(i));\n    }\n\n    if (grad_on_w) {\n      for (int i = 0; i < def_.input_size(); i += 2) {\n        outputs.push_back(GI(i + 1));\n      }\n    }\n\n    return SingleGradientDef(\"WeightedSumGradient\", \"\", inputs, outputs);\n  }\n};\nREGISTER_GRADIENT(WeightedSum, GetWeightedSumGradient);\n\nclass GetGatherGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    ArgumentHelper argsHelper(def_);\n    const bool dense_gradient =\n        argsHelper.GetSingleArgument<bool>(\"dense_gradient\", false);\n\n    using Op = GatherOp<CPUContext>;\n\n    if (dense_gradient) {\n      return vector<OperatorDef>{CreateOperatorDef(\n          \"SparseToDense\",\n          \"\",\n          vector<string>{I(Op::INDICES), GO(0), I(Op::DATA)},\n          vector<string>{GI(Op::DATA)})};\n    } else {\n      // For now we don't do any reshaping as the consumer of this op would\n      // probably be ScatterUpdate which is intenionally ignores shapes. We\n      // might need to revisit it in the future for correctness purposes. The\n      // right shape for the output woild be to flatten INDICES and collapse\n      // first X dims of GRAD\n      SetSparse(Op::DATA, I(Op::INDICES), GO(0));\n      return vector<OperatorDef>();\n    }\n  }\n};\nREGISTER_GRADIENT(Gather, GetGatherGradient);\n\nstruct GetFlattenToVecGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"ResizeLike\", \"\", vector<string>{GO(0), I(0)}, vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(FlattenToVec, GetFlattenToVecGradient);\n\nstruct GetCopyGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"CopyOnDeviceLike\",\n        \"\",\n        vector<string>{GO(0), I(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(Copy, GetCopyGradient);\n\nstruct GetGPUToCPUGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    if (g_output_[0].IsDense()) {\n      return SingleGradientDef(\n          \"CopyCPUToGPU\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n    } else {\n      return vector<OperatorDef>{CreateOperatorDef(\n                                     \"CopyCPUToGPU\",\n                                     \"\",\n                                     std::vector<string>{GO_I(0)},\n                                     std::vector<string>{GI_I(0)}),\n                                 CreateOperatorDef(\n                                     \"CopyCPUToGPU\",\n                                     \"\",\n                                     std::vector<string>{GO_V(0)},\n                                     std::vector<string>{GI_V(0)})};\n    }\n  }\n};\nREGISTER_GRADIENT(CopyGPUToCPU, GetGPUToCPUGradient);\n\nstruct GetCPUToGPUGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    if (g_output_[0].IsDense()) {\n      return SingleGradientDef(\n          \"CopyGPUToCPU\", \"\", vector<string>{GO(0)}, vector<string>{GI(0)});\n    } else {\n      return vector<OperatorDef>{CreateOperatorDef(\n                                     \"CopyGPUToCPU\",\n                                     \"\",\n                                     std::vector<string>{GO_I(0)},\n                                     std::vector<string>{GI_I(0)}),\n                                 CreateOperatorDef(\n                                     \"CopyGPUToCPU\",\n                                     \"\",\n                                     std::vector<string>{GO_V(0)},\n                                     std::vector<string>{GI_V(0)})};\n    }\n  }\n};\nREGISTER_GRADIENT(CopyCPUToGPU, GetCPUToGPUGradient);\n\nSHOULD_NOT_DO_GRADIENT(Unique);\nSHOULD_NOT_DO_GRADIENT(LengthsToSegmentIds);\nSHOULD_NOT_DO_GRADIENT(SegmentIdsToLengths);\nSHOULD_NOT_DO_GRADIENT(SegmentIdsToRanges);\nSHOULD_NOT_DO_GRADIENT(SegmentIdsToLengthWeights);\nSHOULD_NOT_DO_GRADIENT(GatherRangesOp);\nSHOULD_NOT_DO_GRADIENT(LengthsGather);\nSHOULD_NOT_DO_GRADIENT(AccumulateHistogram);\n\ntemplate <>\nbool NanCheckOp<CPUContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  const int D = X.size();\n  const float* data = X.data<float>();\n  ConstEigenVectorMap<float> input_data(data, D);\n\n  bool all_finite = input_data.allFinite();\n\n  if (!all_finite) {\n    std::cerr << \"Tensor contained NaN or inf: [\" << this->debug_def().input(0)\n              << \"]\" << std::endl;\n\n    for (int j = 0; j < InputSize(); j++) {\n      std::cerr << \"Tensor name: \" << this->debug_def().input(j) << std::endl;\n      std::cerr << \"Input tensor:\" << std::endl;\n      tensorPrinter_.Print<float>(Input(j));\n      std::cerr << \"NaN idxs:\" << std::endl;\n      const float* x = Input(j).data<float>();\n      for (size_t i = 0; i < Input(j).size(); ++i) {\n        if (std::isnan(x[i]) || std::isinf(x[i])) {\n          std::cerr << i << \" \";\n        }\n      }\n      std::cerr << std::endl;\n    }\n    return false;\n  }\n\n  if (&X != Y) {\n    Y->CopyFrom(X, &context_);\n  }\n  return true;\n}\nREGISTER_CPU_OPERATOR(NanCheck, NanCheckOp<CPUContext>);\nREGISTER_GRADIENT(NanCheck, GetNanCheckGradient);\n\nOPERATOR_SCHEMA(NanCheck)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .IdenticalTypeAndShapeOfInput(0)\n    .SetDoc(\"Identity operator, but checks all values for nan or inf\")\n    .Input(0, \"tensor\", \"Tensor to check for nan/inf\")\n    .Output(\n        0,\n        \"output\",\n        \"Tensor to copy input into if no NaNs or inf.\"\n        \" Can be in-place\");\n\nOPERATOR_SCHEMA(Size)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(\n        \"Return a 1D tensor of type int64 that contains the number \"\n        \"of elements of the input tensor\")\n    .Input(0, \"tensor\", \"Tensor to calculate number of elements\")\n    .Output(\n        0,\n        \"output\",\n        \"1D tensor of type int64 that contains the number of \"\n        \"elements in the input tensor.\");\n\nREGISTER_CPU_OPERATOR(Size, SizeOp<CPUContext>);\nNO_GRADIENT(Size);\n\ntemplate <>\ntemplate <typename T>\nbool RangeOp<CPUContext>::DoRunOnDevice(\n    const T& start,\n    const T& step,\n    Tensor<CPUContext>* output) {\n  auto* output_data = output->template mutable_data<T>();\n  for (int i = 0; i < output->size(); ++i) {\n    output_data[i] = i * step + start;\n  }\n  return true;\n}\n\nOPERATOR_SCHEMA(Range)\n    .NumInputs(1, 3)\n    .NumOutputs(1)\n    .SetDoc(\n        \"Values are generated within the half-open interval [start, stop) \"\n        \"(in other words, the interval including start but excluding stop). \"\n        \"When called with a single value, this will return `[0, v]` with the \"\n        \"result type inferred from the input types.\")\n    .Input(\n        0,\n        \"start\",\n        \"Optional scalar Tensor with the start of the interval (inclusive).\")\n    .Input(1, \"stop\", \"scalar Tensor with the end of the interval (exclusive)\")\n    .Input(2, \"step\", \"Optional scalar Tensor with spacing between values.\")\n    .Output(\n        0,\n        \"output\",\n        \"1D tensor of same type as inputs that contains the sequence.\");\n\nREGISTER_CPU_OPERATOR(Range, RangeOp<CPUContext>);\nNO_GRADIENT(Range);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/utility_ops.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <math.h>\n#include <cfloat>\n// TODO(jamesreed): I would use <cmath> here but std::isnan\n// and std::isinf are declared constexpr there and the nvidia\n// compiler throws an error because of it\n\n#include <thrust/device_vector.h>\n#include <thrust/sequence.h>\n#include <thrust/sort.h>\n#include <thrust/system/cuda/execution_policy.h>\n#include <thrust/unique.h>\n#include \"caffe2/core/context_gpu.h\"\n#include \"flatten_op.h\"\n#include \"minmax_ops.h\"\n#include \"utility_ops.h\"\n\nnamespace caffe2 {\nCAFFE_KNOWN_TYPE(const float*);\n\nREGISTER_CUDA_OPERATOR(EnsureDense, EnsureDenseOp<CUDAContext>);\n\n__global__ void NanCheckKernel(int N, const float* X, bool* result) {\n  bool has_nan = false;\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    // Note: we have no need to do early return, since only if this fails\n    // will we not need to inspect all elements. No need to optimize the\n    // case that will fail.\n    has_nan = has_nan || isnan(X[i]) || isinf(X[i]);\n  }\n  __syncthreads();\n  if (has_nan) {\n    result[0] = true;\n  }\n}\n\ntemplate <>\nbool NanCheckOp<CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n  const size_t N = X.size();\n  const float* data_ptr = X.data<float>();\n\n  scratch_.Resize(1);\n  math::Set<bool, CUDAContext>(\n      1, false, scratch_.mutable_data<bool>(), &context_);\n  NanCheckKernel<<<\n      CAFFE_GET_BLOCKS(N),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      N, X.data<float>(), scratch_.mutable_data<bool>());\n\n  bool result = false;\n  {\n    std::lock_guard<std::mutex> lock(CUDAContext::mutex());\n    CUDA_ENFORCE(cudaMemcpyAsync(\n        &result,\n        scratch_.raw_data(),\n        1,\n        cudaMemcpyDefault,\n        context_.cuda_stream()));\n  }\n  // Note: we must synchronize here so we can inspect the result\n  context_.FinishDeviceComputation();\n\n  // Print out diagnostic info if we have a NaN or inf\n  if (result) {\n    std::cerr << \"Tensor contained NaN or inf: \" << this->debug_def().input(0)\n              << std::endl;\n\n    for (int j = 0; j < InputSize(); j++) {\n      TensorCPU cpu_X;\n      cpu_X.ResizeLike(Input(j));\n      // Hack to cause allocaiton happen here, so it won't happen\n      // when we do CopyFrom. We need the mutex then because host->gpu\n      // copies seem to possibly lock with NCCL.\n      cpu_X.mutable_data<float>();\n\n      {\n        std::lock_guard<std::mutex> lock(CUDAContext::mutex());\n        cpu_X.CopyFrom(Input(j), &context_);\n      }\n      context_.FinishDeviceComputation();\n      std::cerr << \"Input tensor: \" << j << \": [\" << this->debug_def().input(j)\n                << \"]\" << std::endl;\n      tensorPrinter_.Print<float>(cpu_X);\n\n      if (j == 0) {\n        std::cerr << \"NaN idxs:\" << std::endl;\n        auto* cpu_X_data = cpu_X.data<float>();\n        for (size_t i = 0; i < cpu_X.size(); ++i) {\n          if (isnan(cpu_X_data[i]) || isinf(cpu_X_data[i])) {\n            std::cerr << i << \" \";\n          }\n        }\n      }\n      std::cerr << std::endl;\n    }\n    return false;\n  }\n\n  // This op should act as an identity matrix if we don't find any NaNs/infs.\n  // Copy over the data if we are not doing this in-place.\n  if (&X != Y) {\n    Y->CopyFrom(X, &context_);\n  }\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(NanCheck, NanCheckOp<CUDAContext>);\n\n__global__ void\nElwiseMaxKernel(const float* X, const float* Y, float* maxout, const int N) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    maxout[i] = max(X[i], Y[i]);\n  }\n}\n\ntemplate <>\nbool MaxOp<float, CUDAContext>::Compute() {\n  float* output_data = Output(0)->mutable_data<float>();\n  const int N = Input(0).size();\n\n  // Run pairwise-maxes\n  for (int i = 1; i < InputSize(); ++i) {\n    ElwiseMaxKernel<<<\n        CAFFE_GET_BLOCKS(N),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        (i == 0 ? Input(0).data<float>() : Output(0)->data<float>()),\n        Input(i).data<float>(),\n        output_data,\n        N);\n  }\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Max, MaxOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(MaxGradient, MaxGradientOp<float, CUDAContext>);\n\n__global__ void\nElwiseMinKernel(const float* X, const float* Y, float* minout, const int N) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    minout[i] = min(X[i], Y[i]);\n  }\n}\n\ntemplate <>\nbool MinOp<float, CUDAContext>::Compute() {\n  float* output_data = Output(0)->mutable_data<float>();\n  const int N = Input(0).size();\n\n  // Run pairwise-mines\n  for (int i = 1; i < InputSize(); ++i) {\n    ElwiseMinKernel<<<\n        CAFFE_GET_BLOCKS(N),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        (i == 0 ? Input(0).data<float>() : Output(0)->data<float>()),\n        Input(i).data<float>(),\n        output_data,\n        N);\n  }\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Min, MinOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(MinGradient, MinGradientOp<float, CUDAContext>);\n\ntemplate <typename T>\n__global__ void\nMaxMinGradKernel(int N, const T* mx, const T* x, const T* go, T* gi) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    gi[i] = go[i] * (mx[i] == x[i]);\n  }\n}\n\ntemplate <>\nbool SelectGradientOpBase<float, CUDAContext>::RunOnDevice() {\n  auto& output = Input(0);\n  auto& grad_output = Input(1);\n  const int kInputStartOffset = 2;\n\n  const float* data = output.data<float>();\n\n  for (int i = 0; i < OutputSize(); i++) {\n    auto& input = Input(i + kInputStartOffset);\n    auto* grad_input = Output(i);\n    grad_input->ResizeLike(input);\n    MaxMinGradKernel<<<\n        CAFFE_GET_BLOCKS(input.size()),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        input.size(),\n        output.data<float>(),\n        input.data<float>(),\n        grad_output.data<float>(),\n        grad_input->mutable_data<float>());\n  }\n  return true;\n}\n\ntemplate <typename T_INDEX>\n__global__ void GatherKernel(\n    const float* X,\n    float* Y,\n    const T_INDEX* indices,\n    const int N,\n    const int block_size) {\n  for (int i = blockIdx.x; i < N; i += gridDim.x) {\n    T_INDEX idx = indices[i];\n    const float* src_offset = X + idx * block_size;\n    float* dst_offset = Y + i * block_size;\n    for (int j = threadIdx.x; j < block_size; j += blockDim.x) {\n      dst_offset[j] = src_offset[j];\n    }\n  }\n}\n\ntemplate <>\nbool GatherOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n      this, OperatorBase::Input<TensorCUDA>(INDICES));\n}\n\ntemplate <>\ntemplate <typename Index>\nbool GatherOp<CUDAContext>::DoRunWithType() {\n  auto& data = Input(DATA);\n  auto& indices = Input(INDICES);\n  auto* output = Output(0);\n\n  CAFFE_ENFORCE_GE(data.ndim(), 1, \"DATA should be at least 1-D\");\n  auto shape = indices.dims();\n  shape.insert(shape.end(), data.dims().begin() + 1, data.dims().end());\n  output->Resize(shape);\n\n  int block_size = data.size() / data.dim(0);\n  auto block_bytesize = data.size_from_dim(1) * data.meta().itemsize();\n  CAFFE_ENFORCE(\n      block_bytesize == data.nbytes() / data.dim(0),\n      \"block_bytesize should be consistent with data dim\");\n  int N = indices.size();\n\n  auto src_base = static_cast<const float*>(data.raw_data());\n  const Index* idxs = indices.template data<Index>();\n  auto out = static_cast<float*>(output->raw_mutable_data(data.meta()));\n\n  // return early when the input is empty, since CUDA kernel will fail for\n  // empty input.\n  if (N <= 0) {\n    return true;\n  }\n\n  GatherKernel<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(src_base, out, idxs, N, block_size);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Gather, GatherOp<CUDAContext>);\n\n/**\n * @brief Update slices of Y in-place with a batch of weighted X's.\n * Y[idx] = alpha[b] * X[b][i] + Y[idx]\n * i=0,...,N-1\n * b=0,...,B-1\n * idx=Indices[i]\n */\ntemplate <typename T_INDEX>\n__global__ void AxpySliceKernel(\n    const float* weight0,\n    const TIndex N,\n    const TIndex B,\n    const TIndex slice_size,\n    const float** alpha,\n    const float** X,\n    const T_INDEX* Indices,\n    float* Y,\n    const TIndex M) {\n  // This implementation requires that the first weight is 1.0\n  CUDA_KERNEL_ASSERT(weight0[0] == 1.0);\n  for (int i = blockIdx.x; i < N; i += gridDim.x) {\n    T_INDEX idx = Indices[i];\n    float* y_offset = Y + (idx * slice_size);\n    for (int b = 0; b < B; b++) {\n      float a = *alpha[b];\n      const float* x_offset = X[b] + (i * slice_size);\n      for (int j = threadIdx.x; j < slice_size; j += blockDim.x) {\n        atomicAdd(&y_offset[j], a * x_offset[j]);\n      }\n    }\n  }\n}\n\ntemplate <>\nbool ScatterWeightedSumOp<float, CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(this, Input(2));\n}\n\ntemplate <>\ntemplate <typename Index>\nbool ScatterWeightedSumOp<float, CUDAContext>::DoRunWithType() {\n  CAFFE_ENFORCE_EQ(InputSize() % 2, 1);\n  auto& X0 = Input(0);\n  auto& weight0 = Input(1);\n  auto& indices = Input(2);\n  auto* output = Output(0);\n\n  CAFFE_ENFORCE_EQ(&X0, output, \"In place operation is required\");\n  CAFFE_ENFORCE_GT(X0.size(), 0);\n  CAFFE_ENFORCE_GT(X0.ndim(), 0, \"X0 has to be at least the vector\");\n  CAFFE_ENFORCE_EQ(weight0.size(), 1);\n\n  TIndex M = X0.size();\n  TIndex N = X0.dim(0);\n  TIndex K = indices.size();\n  TIndex block_size = M / N;\n\n  T* data = output->template mutable_data<T>();\n\n  // In order to have all device pointers of x_i (and weight_i similarly)\n  // consecutively in device memory, copy pointers to a host vector and then\n  // copy back into a device array.\n  const TIndex B = (InputSize() - 3) / 2;\n  x_data_host_.Resize(B);\n  weights_host_.Resize(B);\n  x_data_device_.Resize(B);\n  weights_device_.Resize(B);\n\n  const float** x_data_host = x_data_host_.mutable_data<const float*>();\n  const float** weights_host = weights_host_.mutable_data<const float*>();\n  const float** x_data_device = x_data_device_.mutable_data<const float*>();\n  const float** weights_device = weights_device_.mutable_data<const float*>();\n\n  for (int inp = 3; inp < InputSize(); inp += 2) {\n    int idx = (inp - 3) / 2;\n    x_data_host[idx] = static_cast<const float*>(Input(inp).raw_data());\n    weights_host[idx] = static_cast<const float*>(Input(inp + 1).raw_data());\n  }\n  context_.Copy<const float*, CPUContext, CUDAContext>(\n      B, x_data_host, x_data_device);\n  context_.Copy<const float*, CPUContext, CUDAContext>(\n      B, weights_host, weights_device);\n\n  AxpySliceKernel<<<\n      std::min<TIndex>(K, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      weight0.template data<float>(),\n      K,\n      B,\n      block_size,\n      weights_device,\n      x_data_device,\n      indices.template data<Index>(),\n      data,\n      M);\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(\n    ScatterWeightedSum,\n    ScatterWeightedSumOp<float, CUDAContext>);\n\nnamespace {\n\ntemplate <typename Index, typename T>\n__global__ void scatter_assign_kernel(\n    T* data,\n    const Index* idxs,\n    const T* slicesData,\n    TIndex N,\n    TIndex K,\n    TIndex block_size) {\n  for (TIndex i = blockIdx.x; i < K; i += gridDim.x) {\n    Index idx = idxs[i];\n    CUDA_KERNEL_ASSERT(0 <= idx && idx < N);\n    const T* src = slicesData + block_size * i;\n    T* dest = data + block_size * idx;\n    for (TIndex j = threadIdx.x; j < block_size; j += blockDim.x) {\n      dest[j] = src[j];\n    }\n  }\n}\n\n} // namespace\n\ntemplate <>\ntemplate <typename Index, typename T>\nvoid ScatterAssignOp<CUDAContext>::DoScatterAssign(\n    T* data,\n    const Index* idxs,\n    const T* slicesData,\n    TIndex N,\n    TIndex K,\n    TIndex block_size) {\n  scatter_assign_kernel<<<\n      std::min(K, static_cast<TIndex>(CAFFE_MAXIMUM_NUM_BLOCKS)),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(data, idxs, slicesData, N, K, block_size);\n}\n\nREGISTER_CUDA_OPERATOR(ScatterAssign, ScatterAssignOp<CUDAContext>);\n\n#if THRUST_VERSION >= 100800\n__global__ void remap_kernel(\n    thrust::device_ptr<int> second_order,\n    thrust::device_ptr<int> order,\n    int* output,\n    int N,\n    int K) {\n  int i = blockDim.x * blockIdx.x + threadIdx.x;\n  if (i >= K)\n    return;\n  int idx = second_order[i];\n  output[order[idx]] = i;\n  // Maybe cuda 1D kernel?\n  for (idx++; idx < N && (i == K - 1 || idx != second_order[i + 1]); idx++) {\n    output[order[idx]] = i;\n  }\n  return;\n}\n\ntemplate <>\ntemplate <typename T>\nvoid UniqueOp<CUDAContext>::DoRun() {\n  auto& inputTensor = Input(0);\n  // use dim32 to enforce that it's fine to have remapping of type int\n  int N = inputTensor.dim32(0);\n  CAFFE_ENFORCE_EQ(inputTensor.ndim(), 1, \"Input should be a vector\");\n  auto* uniqueTensor = Output(UNIQUE);\n\n  int* remapping = nullptr;\n  if (REMAPPING < OutputSize()) {\n    auto* remappingTensor = Output(REMAPPING);\n    remappingTensor->ResizeLike(inputTensor);\n    remapping = remappingTensor->template mutable_data<int>();\n  }\n\n  const T* input = inputTensor.template data<T>();\n  thrust_unique_buffer_.Resize(N);\n  auto* buffer = thrust_unique_buffer_.template mutable_data<T>();\n  context_.template CopyItems<CUDAContext, CUDAContext>(\n      inputTensor.meta(), N, input, buffer);\n\n  // Create two vectors of {0, 1, ..., N-1} on CUDA device\n  thrust::device_vector<int> order1(N), order2(N);\n  thrust::sequence(\n      thrust::cuda::par.on(context_.cuda_stream()),\n      order1.begin(),\n      order1.end());\n  thrust::sequence(\n      thrust::cuda::par.on(context_.cuda_stream()),\n      order2.begin(),\n      order2.end());\n\n  // Sort the input along with order vector. So now we know where each element\n  // is permutated to. For example:\n  //    input1 = 1,3,5,1,5,7,9\n  //    order1 = 0,1,2,3,4,5,6\n  // Now we have:\n  //    output = 1,1,3,5,5,7,9\n  //    order1 = 0,3,1,2,4,5,6\n  thrust::sort_by_key(\n      thrust::cuda::par.on(context_.cuda_stream()),\n      buffer,\n      buffer + N,\n      order1.begin());\n\n  // Use consequent unique op to get another order_buffer\n  //    input2 = 1,1,3,5,5,7,9\n  //    order2 = 0,1,2,3,4,5,6\n  // Now we have:\n  //    output = 1,3,5,7,9\n  //    order2 = 0,2,3,5,6\n  auto new_last = thrust::unique_by_key(\n      thrust::cuda::par.on(context_.cuda_stream()),\n      buffer,\n      buffer + N,\n      order2.begin());\n  int K = new_last.first - buffer;\n\n  uniqueTensor->Resize(K);\n  T* unique = uniqueTensor->template mutable_data<T>();\n  context_.template CopyItems<CUDAContext, CUDAContext>(\n      thrust_unique_buffer_.meta(), K, buffer, unique);\n\n  // Compute the remapping. For example, for the number 1, if we look at\n  // order2[0] and order2[1], we know that input2[0:2) are all 1. They are all\n  // remapped to 0 in final input. And from order1, we know where they come\n  // from. The rest is easy.\n  if (remapping != nullptr) {\n    // record remap\n    remap_kernel<<<\n        CAFFE_GET_BLOCKS(K),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        order2.data(), order1.data(), remapping, N, K);\n  }\n}\nREGISTER_CUDA_OPERATOR(Unique, UniqueOp<CUDAContext>);\n#endif // THRUST_VERSION >= 100800\n\nREGISTER_CUDA_OPERATOR(Size, SizeOp<CUDAContext>);\n\ntemplate <typename T>\n__global__ void RangeKernel(const int n, T* Y, T offset, T step) {\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    Y[index] = index * step + offset;\n  }\n}\n\ntemplate <>\ntemplate <typename T>\nbool RangeOp<CUDAContext>::DoRunOnDevice(\n    const T& start,\n    const T& step,\n    Tensor<CUDAContext>* output) {\n  int N = output->size();\n  RangeKernel<<<\n      CAFFE_GET_BLOCKS(N),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(N, output->mutable_data<T>(), start, step);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Range, RangeOp<CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/utility_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_UTILITY_OPS_H_\n#define CAFFE2_OPERATORS_UTILITY_OPS_H_\n\n#include <math.h>\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/utils/math.h\"\n\n#include <map>\n#include <utility>\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass NanCheckOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  NanCheckOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override;\n\n private:\n  TensorPrinter tensorPrinter_;\n  Tensor<Context> scratch_;\n};\n\nstruct GetNanCheckGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  std::vector<OperatorDef> GetGradientDefs() override {\n    return {CreateOperatorDef(\n        \"NanCheck\",\n        \"\",\n        std::vector<string>{GO(0)},\n        std::vector<string>{GI(0)})};\n  }\n};\n\ntemplate <class Context>\nclass WallClockTimeOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  WallClockTimeOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    int64_t nanoseconds = static_cast<long int>(\n        std::chrono::duration_cast<std::chrono::nanoseconds>(\n            std::chrono::high_resolution_clock::now().time_since_epoch())\n            .count());\n\n    TensorCPU* output = OperatorBase::Output<TensorCPU>(0);\n    output->Resize();\n    *output->template mutable_data<int64_t>() = nanoseconds;\n\n    return true;\n  }\n};\n\nconst char kPrintFileExtension[] = \".log\";\n\ntemplate <class Context>\nclass PrintOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_DISPATCH_HELPER;\n  PrintOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        tensor_printer_(\n            operator_def.input(0),\n            OperatorBase::GetSingleArgument<int>(\"to_file\", 0)\n                ? ws->RootFolder() + \"/\" + operator_def.input(0) +\n                    kPrintFileExtension\n                : \"\",\n            OperatorBase::GetSingleArgument<int>(\"limit\", 0)),\n        every_n_(OperatorBase::GetSingleArgument<int>(\"every_n\", 1)) {\n    CAFFE_ENFORCE_GE(every_n_, 1);\n  }\n\n  bool RunOnDevice() override {\n    if (++occurrences_mod_n_ > every_n_) {\n      occurrences_mod_n_ -= every_n_;\n    }\n    if (occurrences_mod_n_ != 1) {\n      return true;\n    }\n\n    if (!OperatorBase::InputIsType<Tensor<Context>>(0) &&\n        !OperatorBase::InputIsType<TensorCPU>(0)) {\n      LOG(INFO) << \"Blob of type: \"\n                << OperatorBase::Inputs().at(0)->meta().name();\n      return true;\n    }\n    // special-case empty tensors since they may have no meta()\n    if (Input(0).size() == 0) {\n      tensor_printer_.PrintMeta(Input(0));\n      return true;\n    }\n\n    using Types = TensorTypes<\n        float,\n        double,\n        int,\n        long,\n        bool,\n        char,\n        unsigned char,\n        std::string>;\n\n    if (OperatorBase::InputIsType<TensorCPU>(0)) {\n      return DispatchHelper<Types>::call(\n          this, OperatorBase::Input<TensorCPU>(0));\n    } else {\n      return DispatchHelper<Types>::call(this, Input(0));\n    }\n  }\n\n private:\n  template <typename T>\n  bool DoRunWithType() {\n    // A simple strategy to copy tensor if needed, and have the tensor pointer\n    // pointing to the right instantiation. Note that tensor_copy_if_needed\n    // will handle memory deallocation itself so no smart pointer is needed.\n    const TensorCPU* tensor;\n    TensorCPU tensor_copy_if_needed;\n    if (OperatorBase::InputIsType<TensorCPU>(0)) {\n      tensor = &OperatorBase::Input<TensorCPU>(0);\n    } else {\n      tensor_copy_if_needed.CopyFrom(Input(0), &context_);\n      // Make sure that the copy is finished.\n      context_.FinishDeviceComputation();\n      tensor = &tensor_copy_if_needed;\n    }\n    tensor_printer_.Print<T>(*tensor);\n    return true;\n  }\n\n private:\n  TensorPrinter tensor_printer_;\n  int every_n_;\n  int occurrences_mod_n_{0};\n};\n\n/**\n * @brief Alias op makes the output and the input share the same underlying\n * storage.\n *\n * WARNING: in general, in caffe2's operator interface different tensors should\n * have different underlying storage, which is the assumption made by\n * components such as the dependency engine and memory optimization. Thus, in\n * normal situations you should not use the AliasOp, especially in a normal\n * forward-backward pass.\n *\n * The Alias op is provided so one can achieve true asynchrony, such as\n * Hogwild, in a graph. But make sure you understand all the implications\n * similar to multi-thread computation before you use it explicitly.\n */\ntemplate <class Context>\nclass AliasOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(AliasOp);\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    CAFFE_ENFORCE_GE(input.size(), 0, \"Tensor is not initialized\");\n    Output(0)->ResizeLike(input);\n    Output(0)->ShareData(input);\n    return true;\n  }\n};\n\n/**\n * @brief Pass inputs to outputs.\n * Input:\n *   DATA - dense tensor.\n * Output:\n *   DATA - same tensor as input.\n */\ntemplate <class Context>\nclass EnsureDenseOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(EnsureDenseOp)\n\n  bool RunOnDevice() override {\n    const auto& input = Input(0);\n    auto* output = Output(0);\n    CAFFE_ENFORCE_GT(input.ndim(), 0, \"Input has to be at least a vector.\");\n    // it is allowed to have the output inplace overwrite the input but also\n    // allow the output to be copied from the input\n    if (&input != output) {\n      output->ResizeLike(input);\n      output->CopyFrom(input, &context_);\n    }\n    return true;\n  }\n};\n\ntemplate <class Context>\nclass FlattenToVecOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(FlattenToVecOp);\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = Output(0);\n    CAFFE_ENFORCE_GE(\n        input.dims().size(), 1, \"The rank of the tensor must be >= 1.\");\n    output->Resize(input.size());\n\n    context_.template CopyItems<Context, Context>(\n        input.meta(),\n        input.size(),\n        input.raw_data(),\n        output->raw_mutable_data(input.meta()));\n    return true;\n  }\n};\n\n// Output gets the data of input(0), but reshapes it like input(1).\ntemplate <class Context>\nclass ResizeLikeOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(ResizeLikeOp);\n\n  bool RunOnDevice() override {\n    auto& input0 = Input(0);\n    auto& input1 = Input(1);\n    auto* output = Output(0);\n    CAFFE_ENFORCE_EQ(input0.size(), input1.size());\n    output->ResizeLike(Input(1));\n    context_.template CopyItems<Context, Context>(\n        input0.meta(),\n        input0.size(),\n        input0.raw_data(),\n        output->raw_mutable_data(input0.meta()));\n    return true;\n  }\n};\n\ntemplate <class Context>\nclass SumOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(SumOp);\n\n  template <typename T, typename M>\n  bool DoRunWithType() {\n    auto& input0 = Input(0);\n    auto* output = Output(0);\n    if (InputSize() == 1) {\n      output->CopyFrom(input0, &context_);\n      return true;\n    }\n    output->ResizeLike(input0);\n    T* output_data = output->template mutable_data<T>();\n    // Dimension checking\n    for (int i = 1; i < InputSize(); ++i) {\n      if (output->dims() != Input(i).dims()) {\n        CAFFE_THROW(\n            \"Check failed: output->dims() == Input(i).dims().\",\n            \"Description: Input #\",\n            i,\n            \", input dimension:\",\n            Input(i).dims(),\n            \" should match output dimension: \",\n            output->dims());\n      }\n    }\n\n    // Add the first two - works if in-place or not.\n    math::Add(\n        output->size(),\n        input0.template data<T>(),\n        Input(1).template data<T>(),\n        output_data,\n        &context_);\n    // Add remaining.\n    for (int i = 2; i < InputSize(); ++i) {\n      math::Add(\n          output->size(),\n          output_data,\n          Input(i).template data<T>(),\n          output_data,\n          &context_);\n    }\n    return true;\n  }\n\n  bool RunOnDevice() override {\n    if (Input(0).template IsType<float>()) {\n      return DoRunWithType<float, float>();\n    } else if (Input(0).template IsType<int>()) {\n      return DoRunWithType<int, int>();\n    } else {\n      CAFFE_THROW(\n          \"Sum operator only supports 32-bit float and ints, but\",\n          \" input was of type \",\n          Input(0).meta().name());\n    }\n  }\n};\n\n// WeightedSumOp computes the weighted sum of several tensors. The input should\n// be in the form X_0, weight_0, X_1, weight_1, ... where X_i all have the same\n// shape, and weight_i are size 1 tensors that specifies the weight of each\n// vector. Note that if one wants to do in-place computation, it could only be\n// done with X_0 also as the output, but not other X_i.\ntemplate <class Context>\nclass WeightedSumOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(WeightedSumOp);\n\n  template <typename DstType>\n  bool DoRunWithType() {\n    CAFFE_ENFORCE_EQ(InputSize() % 2, 0);\n    auto& X0 = Input(0);\n    auto& weight0 = Input(1);\n    CAFFE_ENFORCE_GT(X0.size(), 0);\n    CAFFE_ENFORCE_EQ(weight0.size(), 1);\n    int size = X0.size();\n    auto* output = Output(0);\n    output->ResizeLike(X0);\n    math::Scale<DstType, Context>(\n        size,\n        weight0.template data<float>(),\n        X0.template data<DstType>(),\n        output->template mutable_data<DstType>(),\n        &context_);\n    for (int i = 2; i < InputSize(); i += 2) {\n      auto& X = Input(i);\n      // Do a check: if the input is the same as output, we have a problem -\n      // in-place update should always only happen with the zeroth input.\n      if (&X == output) {\n        LOG(ERROR) << \"Input #\" << i << \" is the same as output. \"\n                   << \"If you want to do in-place updates, put the output as \"\n                   << \"input #0.\";\n        return false;\n      }\n      auto& weight = Input(i + 1);\n      CAFFE_ENFORCE_EQ(X.size(), size);\n      CAFFE_ENFORCE_EQ(weight.size(), 1);\n      math::Axpy<DstType, Context>(\n          size,\n          weight.template data<float>(),\n          X.template data<DstType>(),\n          output->template mutable_data<DstType>(),\n          &context_);\n    }\n    return true;\n  }\n  bool RunOnDevice() override;\n};\n\ntemplate <class Context>\nclass WeightedSumGradientOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  WeightedSumGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        grad_on_w_(OperatorBase::GetSingleArgument<bool>(\"grad_on_w\", false)) {}\n\n  template <typename DstType>\n  bool DoRunWithType() {\n    CAFFE_ENFORCE_EQ(InputSize() % 2, 1);\n    auto output_size = grad_on_w_ ? InputSize() - 1 : InputSize() / 2;\n    CAFFE_ENFORCE_EQ(OutputSize(), output_size);\n\n    auto& dY = Input(0);\n    const auto* dY_data = dY.template data<DstType>();\n    int size = dY.size();\n\n    // The input size should be the input size of the forward op plus 1\n    for (int i = 0; i < InputSize() / 2; i++) {\n      auto& cur_w = Input(2 * i + 2);\n      CAFFE_ENFORCE_EQ(cur_w.size(), 1);\n      auto* cur_dX = Output(i);\n      cur_dX->ResizeLike(dY);\n\n      math::Scale<DstType, Context>(\n          size,\n          cur_w.template data<float>(),\n          dY_data,\n          cur_dX->template mutable_data<DstType>(),\n          &context_);\n\n      if (grad_on_w_) {\n        auto& cur_X = Input(2 * i + 1);\n        CAFFE_ENFORCE_EQ(cur_X.size(), size);\n        auto* cur_dw = Output(i + output_size / 2);\n        cur_dw->Resize(1);\n        math::Dot<DstType, Context>(\n            size,\n            dY_data,\n            cur_X.template data<DstType>(),\n            cur_dw->template mutable_data<float>(),\n            &context_);\n      }\n    }\n\n    return true;\n  }\n\n  bool RunOnDevice() override;\n\n private:\n  bool grad_on_w_;\n};\n\n/**\n * @brief Update slices of the tensor in-place with weighted sum.\n *\n * ScatterWeightedSumOp is similar to WeightedSum and computes the weighted sum\n * of several tensors. The first tensor has to be in-place and only slices of it\n * on the first dimension as indexed by INDICES will be updated.\n *\n * Input:\n *   X_0 - tensor to be updated\n *   weight_0 - scalar weight for X_0, applied only to slices affected,\n *   INDICES - 1-D list of indices on the first dimension of X_0 that need to be\n * updated\n *   X_1 - update slices, has to have shape of len(INDICES) + shape(X_0)[1:]\n *   weight_1 - scalar weight for X_1 update\n *   X_2, weight_2, ...\n *\n * Output:\n *   X_0 - has to be exactly the same tensor as the input 0\n *\n * Note: The op pretty much ignores the exact shapes of the input arguments and\n * cares only about sizes. It's done for performance consideration to avoid\n * unnecessary reshapes. Only first dimension of X_0 is important, let's call it\n * N. If M is the total size of X_0 and K is the size of INDICES then X_i is\n * assumed to be of shape K x (M / N) regardless of the real shape.\n *\n * Note: Each update in INDICES is applied independently which means that if\n * duplicated elements are present in INDICES the corresponding slice of X_0\n * will be scaled multiple times. Manual collapsing of INDICES is required\n * beforehand if necessary.\n *\n * Note: Updates are applied sequentially by inputs which might have undesired\n * consequences if the input tensor is accessed concurrently by different op\n * (e.g. when doing Hogwild). Other threads might see intermediate results even\n * on individual slice level, e.g. X_0 scaled by weight_0 but without any\n * updates applied.\n *\n * For now really works only on CPU because of INDICES access\n */\ntemplate <typename T, class Context>\nclass ScatterWeightedSumOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(ScatterWeightedSumOp);\n  USE_DISPATCH_HELPER;\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(this, Input(2));\n  }\n\n private:\n  template <typename Index>\n  bool DoRunWithType() {\n    TIndex block_size = Input(0).size_from_dim(1);\n    return DispatchHelper<FixedValues<1>, Index>::call(this, block_size);\n  }\n\n  template <typename Index, int FixedSize>\n  bool DoRunWithValue() {\n    CAFFE_ENFORCE_EQ(InputSize() % 2, 1);\n    auto& X0 = Input(0);\n    auto& weight0 = Input(1);\n    auto& indices = Input(2);\n    auto* output = Output(0);\n    CAFFE_ENFORCE_EQ(&X0, output, \"In place operation is required\");\n\n    CAFFE_ENFORCE_GT(X0.size(), 0);\n    CAFFE_ENFORCE_GT(X0.ndim(), 0, \"X0 has to be at least the vector\");\n    CAFFE_ENFORCE_EQ(weight0.size(), 1);\n    TIndex M = X0.size();\n    TIndex N = X0.dim(0);\n    TIndex K = indices.size();\n    TIndex block_size = M / N;\n    T* data = output->template mutable_data<T>();\n    const Index* idxs = indices.template data<Index>();\n    T w0 = *weight0.template data<T>();\n    // It's most likely a constant so exact comparison is fine\n    if (w0 != 1.0) {\n      for (int i = 0; i < K; ++i) {\n        Index idx = idxs[i];\n        CAFFE_ENFORCE(\n            0 <= idx && idx < N,\n            \"Index out of bounds: \",\n            idx,\n            \", range 0 to \",\n            N);\n        math::ScaleFixedSize<T, Context, FixedSize>(\n            block_size,\n            w0,\n            data + block_size * idx,\n            data + block_size * idx,\n            &context_);\n      }\n    }\n    for (int inp = 3; inp < InputSize(); inp += 2) {\n      auto& X = Input(inp);\n      auto& weight = Input(inp + 1);\n      CAFFE_ENFORCE_EQ(X.size(), block_size * K);\n      CAFFE_ENFORCE_EQ(weight.size(), 1);\n      const T* x_data = X.template data<T>();\n      T w = *weight.template data<T>();\n      for (int i = 0; i < K; ++i) {\n        Index idx = idxs[i];\n        // double-checking the indices, but it's fine as it's DCHECK only\n        DCHECK(0 <= idx && idx < N) << \"Index out of bounds: \" << idx\n                                    << \", range 0 to \" << N;\n        math::AxpyFixedSize<T, Context, FixedSize>(\n            block_size,\n            w,\n            x_data + block_size * i,\n            data + block_size * idx,\n            &context_);\n      }\n    }\n    return true;\n  }\n  Tensor<CPUContext> x_data_host_;\n  Tensor<CPUContext> weights_host_;\n  Tensor<Context> x_data_device_;\n  Tensor<Context> weights_device_;\n};\n\n/**\n * @brief Update slices of the tensor in-place by overriding.\n *\n * Input:\n *   DATA - tensor to be updated\n *   INDICES - 1-D list of indices on the first dimension of X_0 that need to be\n *             updated\n *   SLICES - update slices, has to have shape of len(INDICES) + shape(X_0)[1:]\n *\n * Output:\n *   DATA - has to be exactly the same tensor as the input 0\n *\n * Note: The op pretty much ignores the exact shapes of the input arguments and\n * cares only about sizes. It's done for performance consideration to avoid\n * unnecessary reshapes. Only first dimension of X_0 is important, let's call it\n * N. If M is the total size of X_0 and K is the size of INDICES then X_i is\n * assumed to be of shape K x (M / N) regardless of the real shape.\n *\n * Note: Each update in INDICES is applied independently which means that if\n * duplicated elements are present in INDICES arbitrary one will win.\n *\n * For now really works only on CPU because of INDICES access\n */\ntemplate <class Context>\nclass ScatterAssignOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  virtual ~ScatterAssignOp() {}\n\n  ScatterAssignOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        runners_({{{TensorProto_DataType_INT32, TensorProto_DataType_FLOAT},\n                   &ScatterAssignOp::DoRun<int32_t, float>},\n                  {{TensorProto_DataType_INT32, TensorProto_DataType_FLOAT16},\n                   &ScatterAssignOp::DoRun<int32_t, float16>},\n                  {{TensorProto_DataType_INT32, TensorProto_DataType_INT32},\n                   &ScatterAssignOp::DoRun<int32_t, int32_t>},\n                  {{TensorProto_DataType_INT32, TensorProto_DataType_INT64},\n                   &ScatterAssignOp::DoRun<int32_t, int64_t>},\n                  {{TensorProto_DataType_INT64, TensorProto_DataType_FLOAT},\n                   &ScatterAssignOp::DoRun<int64_t, float>},\n                  {{TensorProto_DataType_INT64, TensorProto_DataType_FLOAT16},\n                   &ScatterAssignOp::DoRun<int64_t, float16>},\n                  {{TensorProto_DataType_INT64, TensorProto_DataType_INT32},\n                   &ScatterAssignOp::DoRun<int64_t, int32_t>},\n                  {{TensorProto_DataType_INT64, TensorProto_DataType_INT64},\n                   &ScatterAssignOp::DoRun<int64_t, int64_t>}}) {}\n\n  bool RunOnDevice() override {\n    const auto& data = Input(DATA);\n    const auto& slices = Input(SLICES);\n    auto& indices = Input(INDICES);\n\n    const auto dataType = TypeMetaToDataType(data.meta());\n    const auto slicesType = TypeMetaToDataType(slices.meta());\n    const auto indicesType = TypeMetaToDataType(indices.meta());\n    auto* output = Output(0);\n\n    auto runner = GetRunner(dataType, slicesType, indicesType);\n    (this->*runner)();\n    return true;\n  }\n\n private:\n  typedef void (ScatterAssignOp::*RunnerType)();\n  typedef std::\n      map<std::pair<TensorProto_DataType, TensorProto_DataType>, RunnerType>\n          RunnerMap;\n\n  RunnerMap runners_;\n\n  RunnerType GetRunner(\n      const TensorProto_DataType dataType,\n      const TensorProto_DataType slicesType,\n      const TensorProto_DataType indicesType) {\n    CAFFE_ENFORCE_EQ(dataType, slicesType, \"Data and slice types must match\");\n    auto it = runners_.find({indicesType, dataType});\n    CAFFE_ENFORCE(\n        it != runners_.end(),\n        \"Could not find the runner corresponding to indicesType, dataType = \",\n        indicesType,\n        \" \",\n        dataType);\n    return it->second;\n  }\n\n  template <typename Index, typename T>\n  void DoRun() {\n    auto& input = Input(DATA);\n    auto& indices = Input(INDICES);\n    auto& slices = Input(SLICES);\n    auto* output = Output(0);\n    CAFFE_ENFORCE_EQ(&input, output, \"In place operation is required\");\n\n    CAFFE_ENFORCE_GT(input.ndim(), 0, \"X0 has to be at least the vector\");\n    TIndex M = input.size();\n    TIndex N = input.dim(0);\n    TIndex K = indices.size();\n    TIndex block_size = M / N;\n    CAFFE_ENFORCE_EQ(slices.size(), block_size * K);\n    // TODO(dzhulgakov): it can be made to work with arbitrary data type by\n    // using raw_mutable_data\n    T* data = output->template mutable_data<T>();\n    const Index* idxs = indices.template data<Index>();\n    const T* slicesData = slices.template data<T>();\n    DoScatterAssign(data, idxs, slicesData, N, K, block_size);\n  }\n\n  template <typename Index, typename T>\n  void DoScatterAssign(\n      T* data,\n      const Index* idxs,\n      const T* slicesData,\n      TIndex N,\n      TIndex K,\n      TIndex block_size) {\n    for (int i = 0; i < K; ++i) {\n      Index idx = idxs[i];\n      // double-checking the indices, but it's fine as it's DCHECK only\n      DCHECK(0 <= idx && idx < N) << \"Index out of bounds: \" << idx\n                                  << \", range 0 to \" << N;\n      context_.template Copy<T, Context, Context>(\n          block_size, slicesData + block_size * i, data + block_size * idx);\n    }\n  }\n\n  INPUT_TAGS(DATA, INDICES, SLICES);\n};\n\ntemplate <class Context, class DstContext, class SrcContext>\nclass CopyOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(CopyOp);\n\n  bool RunOnDevice() override {\n    auto& input = OperatorBase::Input<Tensor<SrcContext>>(0);\n    auto* output = OperatorBase::Output<Tensor<DstContext>>(0);\n    output->ResizeLike(input);\n    this->context_.template CopyItems<SrcContext, DstContext>(\n        input.meta(),\n        input.size(),\n        input.raw_data(),\n        output->raw_mutable_data(input.meta()));\n    return true;\n  }\n};\n\ntemplate <class Context, class DstContext, class SrcContext>\nclass CopyOnDeviceLikeOp : public CopyOp<Context, DstContext, SrcContext> {\n public:\n  CopyOnDeviceLikeOp(const OperatorDef& operator_def, Workspace* ws)\n      : CopyOp<Context, DstContext, SrcContext>(operator_def, ws) {}\n};\n\ntemplate <class Context>\nclass LengthsToSegmentIdsOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(LengthsToSegmentIdsOp);\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = Output(0);\n    auto* input_data = input.template data<int32_t>();\n\n    CAFFE_ENFORCE(input.dims().size() == 1, \"Input must be a vector.\");\n    auto total_length =\n        std::accumulate(input_data, input_data + input.size(), 0);\n\n    output->Resize(total_length);\n    auto* output_data = output->template mutable_data<int32_t>();\n\n    for (int i = 0; i < input.size(); ++i) {\n      auto len = input_data[i];\n      std::fill(output_data, output_data + len, i);\n      output_data += len;\n    }\n    return true;\n  }\n};\n\ntemplate <class Context>\nclass LengthsToRangesOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(LengthsToRangesOp);\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = Output(0);\n    auto* input_data = input.template data<int32_t>();\n\n    CAFFE_ENFORCE(input.dims().size() == 1, \"Input must be a vector.\");\n    auto size = input.size();\n\n    output->Resize(size, 2);\n    auto* output_data = output->template mutable_data<int32_t>();\n\n    int32_t offset = 0;\n    for (int i = 0; i < size; ++i) {\n      auto len = input_data[i];\n      output_data[i * 2] = offset;\n      output_data[i * 2 + 1] = len;\n      offset += len;\n    }\n    return true;\n  }\n};\n\ntemplate <class Context>\nclass SegmentIdsToLengthsOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(SegmentIdsToLengthsOp);\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(this, Input(0));\n  }\n\n  template <typename Index>\n  bool DoRunWithType() {\n    auto& input = Input(0);\n    if (input.ndim() == 2) {\n      CAFFE_ENFORCE(\n          input.dim32(0) == 1 || input.dim32(1) == 1,\n          \"Input must be a vector.\");\n    } else {\n      CAFFE_ENFORCE_EQ(input.ndim(), 1, \"Input must be a vector.\");\n    }\n    auto* input_data = input.template data<Index>();\n    auto input_size = input.size();\n    auto* output = Output(0);\n    // segment id starts from 0\n    auto num_segments = input_size ? input_data[input_size - 1] + 1 : 0;\n    if (InputSize() > 1) {\n      CAFFE_ENFORCE_GE(Input(1).ndim(), 1);\n      CAFFE_ENFORCE_LE(\n          num_segments,\n          Input(1).dim(0),\n          \"The number of segments inferred should *NOT* be larger \"\n          \"than the size of Input(1)'s first dimension\");\n      num_segments = Input(1).dim(0);\n    }\n    CAFFE_ENFORCE(0 <= num_segments, \"Indices must be in 0..K-1 range\");\n    output->Resize(num_segments);\n    auto* output_data = output->template mutable_data<int32_t>();\n    if (num_segments == 0) {\n      return true;\n    }\n    std::fill(output_data, output_data + num_segments, 0);\n    Index prev = 0; // Assume that segment_id >= 0.\n    for (int64_t i = 0; i < input_size; i++) {\n      CAFFE_ENFORCE(\n          prev <= input_data[i],\n          \"Segment ids must be sorted: \",\n          prev,\n          \" vs \",\n          input_data[i]);\n      prev = input_data[i];\n      output_data[input_data[i]] += 1;\n    }\n\n    return true;\n  }\n};\n\ntemplate <class Context>\nclass SegmentIdsToRangesOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(SegmentIdsToRangesOp);\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(this, Input(0));\n  }\n\n  template <typename Index>\n  bool DoRunWithType() {\n    auto& input = Input(0);\n    CAFFE_ENFORCE(input.dims().size() == 1, \"Input must be a vector.\");\n    auto* input_data = input.template data<Index>();\n    auto input_size = input.size();\n    auto* output = Output(0);\n    // segment id starts from 0\n    auto num_segments = input_size ? input_data[input_size - 1] + 1 : 0;\n    if (InputSize() > 1) {\n      CAFFE_ENFORCE_GE(Input(1).ndim(), 1);\n      CAFFE_ENFORCE_LE(\n          num_segments,\n          Input(1).dim(0),\n          \"The number of segments inferred should *NOT* be larger \"\n          \"than the size of Input(1)'s first dimension\");\n      num_segments = Input(1).dim(0);\n    }\n    CAFFE_ENFORCE(0 <= num_segments, \"Indices must be in 0..K-1 range\");\n    output->Resize(num_segments, 2);\n    auto* output_data = output->template mutable_data<int32_t>();\n    if (num_segments == 0) {\n      return true;\n    }\n    std::fill(output_data, output_data + num_segments * 2, 0);\n    Index prev = input_data[0];\n    for (int64_t i = 0; i < input_size; i++) {\n      CAFFE_ENFORCE(\n          prev <= input_data[i],\n          \"Segment ids must be sorted: \",\n          prev,\n          \" vs \",\n          input_data[i]);\n      while (prev != input_data[i]) {\n        ++prev;\n        output_data[prev * 2] = i;\n      }\n      output_data[input_data[i] * 2 + 1] += 1;\n    }\n\n    return true;\n  }\n};\n\ntemplate <class Context>\nclass LengthsToWeightsOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  LengthsToWeightsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        power_(OperatorBase::GetSingleArgument<float>(\"power\", 0.5)) {}\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(this, Input(0));\n  }\n\n  template <typename Index>\n  bool DoRunWithType() {\n    auto& input = Input(0);\n    CAFFE_ENFORCE(input.dims().size() == 1, \"Input must be a vector.\");\n    auto* input_data = input.template data<Index>();\n    auto input_size = input.size();\n    auto* output = Output(0);\n\n    int64_t output_size = 0;\n    for (auto i = 0; i < input_size; i++) {\n      CAFFE_ENFORCE_GE(input_data[i], 0, \"unexpected negative length value\");\n      output_size += input_data[i];\n    }\n\n    std::function<float(const int64_t& length, const float& power)> getWeight;\n    if (power_ == 0.5) {\n      getWeight = [](const int64_t& length, const float& /*power*/) {\n        return 1.0 / std::sqrt(length);\n      };\n    } else if (power_ == 1) {\n      getWeight = [](const int64_t& length, const float& /*power*/) {\n        return 1.0 / length;\n      };\n    } else {\n      getWeight = [](const int64_t& length, const float& power) {\n        return 1.0 / std::pow(length, power);\n      };\n    }\n\n    output->Resize(output_size);\n    auto* output_data = output->template mutable_data<float>();\n    int64_t cnt = 0;\n    for (auto i = 0; i < input_size; i++) {\n      auto len = input_data[i];\n      if (len == 0) {\n        continue;\n      }\n      CAFFE_ENFORCE_LE(cnt + len, output_size, \"unexpected lengths value\");\n\n      float weight_value = getWeight(len, power_);\n      std::fill(output_data + cnt, output_data + cnt + len, weight_value);\n      cnt += len;\n    }\n\n    return true;\n  }\n\n private:\n  float power_;\n};\n\ntemplate <class Context>\nclass HasElementsOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(HasElementsOp);\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = OperatorBase::Output<TensorCPU>(0);\n    output->Resize(std::vector<TIndex>{});\n    *output->template mutable_data<bool>() = input.size() > 0;\n    return true;\n  }\n};\n\ntemplate <class Context>\nclass IsEmptyOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(IsEmptyOp);\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = OperatorBase::Output<TensorCPU>(0);\n    output->Resize(std::vector<TIndex>{});\n    *output->template mutable_data<bool>() = (input.size() == 0);\n    return true;\n  }\n};\n\n// Return the size of a tensor\ntemplate <class Context>\nclass SizeOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(SizeOp);\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = Output(0);\n\n    output->Resize(vector<TIndex>());\n    auto* output_data = output->template mutable_data<int64_t>();\n\n    auto size = input.size();\n    math::Set<int64_t, Context>(\n        1, static_cast<int64_t>(size), output_data, &context_);\n\n    return true;\n  }\n};\n\n// returns a shape to be passed to Reshape\ntemplate <class Context>\nclass LengthsToShapeOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(LengthsToShapeOp);\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n\n    CAFFE_ENFORCE(input.dims().size() == 1, \"Input must be a vector.\");\n    auto* output = Output(0);\n    auto* input_data = input.template data<int32_t>();\n\n    auto size = input.size();\n    auto first = input_data[0];\n\n    for (int i = 1; i < size; i++) {\n      CAFFE_ENFORCE(\n          input_data[i] == first, \"All elements of input must be same \");\n    }\n\n    output->Resize(2);\n    auto* output_data = output->template mutable_data<int32_t>();\n    output_data[0] = size;\n    output_data[1] = first;\n\n    return true;\n  }\n};\n\ntemplate <class Context>\nclass GatherOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(GatherOp);\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, OperatorBase::Input<TensorCPU>(INDICES));\n  }\n\n  template <typename Index>\n  bool DoRunWithType() {\n    // If we endup using it on GPU doing O(N) memcpy is probably not best :)\n    // TODO: implement prefetching if it starts mattering (TF does it)\n    auto& data = Input(DATA);\n    auto& indices = Input(INDICES);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_GE(data.ndim(), 1, \"DATA should be at least 1-D\");\n    auto shape = indices.dims();\n    shape.insert(shape.end(), data.dims().begin() + 1, data.dims().end());\n    output->Resize(shape);\n\n    int block_size = data.size_from_dim(1);\n    auto block_bytesize = data.size_from_dim(1) * data.meta().itemsize();\n    int N = indices.size();\n\n    auto src_base = static_cast<const char*>(data.raw_data());\n    const Index* idxs = indices.template data<Index>();\n    auto out = static_cast<char*>(output->raw_mutable_data(data.meta()));\n\n    for (int i = 0; i < N; ++i) {\n      auto idx = idxs[i];\n      CAFFE_ENFORCE(\n          0 <= idx && idx < data.dim(0),\n          \"INDICES element is out of DATA bounds, id=\",\n          idx,\n          \" data_dim=\",\n          data.dim(0));\n      auto src = src_base + idx * block_bytesize;\n      context_.template CopyItems<Context, Context>(\n          data.meta(), block_size, src, out + block_bytesize * i);\n    }\n    return true;\n  }\n\n  INPUT_TAGS(DATA, INDICES);\n};\n\ntemplate <class Context>\nclass GatherRangesOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(GatherRangesOp);\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, OperatorBase::Input<TensorCPU>(RANGES));\n  }\n\n  template <typename Index>\n  bool DoRunWithType() {\n    auto& data = Input(DATA);\n    auto& ranges = Input(RANGES);\n    auto* outputData = Output(0);\n    auto* outputLengths = Output(1);\n\n    auto batchSize = ranges.dim(0);\n    CAFFE_ENFORCE(data.ndim() == 1, \"Data has to be 1-D\");\n    CAFFE_ENFORCE(ranges.ndim() == 3, \"Ranges must be 3-D\");\n    CAFFE_ENFORCE(ranges.dim(1) > 0, \"There has to be at least one range\");\n    CAFFE_ENFORCE_EQ(\n        ranges.dim(2), 2, \"Ranges last dimention should be of size 2\");\n\n    auto* rawData = static_cast<const char*>(data.raw_data());\n    auto* rangesData = ranges.template data<Index>();\n\n    outputLengths->Resize(batchSize);\n    auto* outputLengthsPtr = outputLengths->template mutable_data<int32_t>();\n    size_t start = 0;\n    size_t blockSize = ranges.size_from_dim(1);\n    for (size_t i = 0; i < batchSize; ++i) {\n      auto end = start + blockSize;\n      outputLengthsPtr[i] = accumulate(rangesData, start, end);\n      start = end;\n    }\n\n    size_t outputSize = accumulate(rangesData, 0, ranges.size());\n    outputData->Resize(outputSize);\n\n    auto outputRawData =\n        static_cast<char*>(outputData->raw_mutable_data(data.meta()));\n    VLOG(1) << \"Copying data\";\n    size_t outputOffsetBytes = 0;\n    auto itemsize = data.meta().itemsize();\n    for (int i = 0; i < ranges.size(); i += 2) {\n      auto rangeStart = rangesData[i];\n      auto rangeLength = rangesData[i + 1];\n      if (!rangeLength) {\n        continue;\n      }\n      auto rangeSizeBytes = rangeLength * itemsize;\n      CAFFE_ENFORCE(outputOffsetBytes < outputSize * itemsize);\n      CAFFE_ENFORCE(rangeStart + rangeLength <= data.size());\n      context_.template CopyItems<Context, Context>(\n          data.meta(),\n          rangeLength,\n          rawData + rangeStart * itemsize,\n          outputRawData + outputOffsetBytes);\n      outputOffsetBytes += rangeSizeBytes;\n    }\n    CAFFE_ENFORCE(outputOffsetBytes == outputSize * itemsize);\n    return true;\n  }\n\n  INPUT_TAGS(DATA, RANGES, LENGTHS);\n\n private:\n  template <typename Index>\n  size_t accumulate(Index* ranges, size_t start, size_t end) {\n    size_t result = 0;\n    for (int i = start + 1; i < end; i += 2) {\n      result += ranges[i];\n    }\n    return result;\n  }\n};\n\ntemplate <class Context>\nclass LengthsGatherOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(LengthsGatherOp);\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, OperatorBase::Input<TensorCPU>(INDICES));\n  }\n\n  template <typename Index>\n  bool DoRunWithType() {\n    auto& items = Input(ITEMS);\n    auto& lengths = Input(LENGTHS);\n    auto& indices = Input(INDICES);\n    auto* output = Output(0);\n\n    CAFFE_ENFORCE_GE(items.ndim(), 1, \"ITEMS should be at least 1-D\");\n    CAFFE_ENFORCE_EQ(lengths.ndim(), 1, \"LENGTHS should be 1-D\");\n    CAFFE_ENFORCE_EQ(indices.ndim(), 1, \"INDICES should be 1-D\");\n\n    const auto* lengths_data = lengths.template data<int32_t>();\n    const auto* indices_data = indices.template data<Index>();\n\n    TIndex total_length = 0;\n    for (size_t i = 0; i < indices.size(); ++i) {\n      auto idx = indices_data[i];\n      CAFFE_ENFORCE_LT(idx, lengths.size());\n      total_length += lengths_data[idx];\n    }\n    auto shape = items.dims();\n    shape[0] = total_length;\n    output->Resize(shape);\n\n    offsets_.clear();\n    TIndex running_offset = 0;\n    offsets_.reserve(lengths.size());\n    for (size_t i = 0; i < lengths.size(); ++i) {\n      offsets_.push_back(running_offset);\n      running_offset += lengths_data[i];\n    }\n    CAFFE_ENFORCE_EQ(\n        items.dim(0),\n        running_offset,\n        \"LENGTHS must match the first dimension of ITEMS\");\n\n    auto src_base = static_cast<const char*>(items.raw_data());\n    auto block_size = items.size_from_dim(1);\n    auto block_bytesize = block_size * items.itemsize();\n    auto out = static_cast<char*>(output->raw_mutable_data(items.meta()));\n\n    for (size_t i = 0; i < indices.size(); ++i) {\n      auto idx = indices_data[i];\n      auto length = lengths_data[idx];\n      context_.template CopyItems<Context, Context>(\n          items.meta(),\n          length * block_size,\n          src_base + offsets_[idx] * block_bytesize,\n          out);\n      out += length * block_bytesize;\n    }\n    return true;\n  }\n\n  std::vector<TIndex> offsets_;\n\n  INPUT_TAGS(ITEMS, LENGTHS, INDICES);\n};\n\n// Since we just do copying, consider untemplating it on T and using raw_data()\n/**\n * Deduplicates input indices vector and optionally produces reverse remapping.\n * Current implementation produces a sorted list but it's not guaranteed in\n * general.\n */\ntemplate <class Context>\nclass UniqueOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(UniqueOp);\n\n  bool RunOnDevice() override {\n    // Use run-time polymorphism\n    auto& input = Input(0);\n    if (input.template IsType<int32_t>()) {\n      DoRun<int32_t>();\n    } else if (input.template IsType<int64_t>()) {\n      DoRun<int64_t>();\n    } else {\n      LOG(FATAL) << \"Unsupported type of input in Unique: \"\n                 << input.meta().name();\n    }\n    return true;\n  }\n\n private:\n  vector<int> order_;\n  Tensor<Context> thrust_unique_buffer_;\n  Tensor<Context> cuda_order_buffer_;\n  Tensor<Context> second_order_buffer_;\n\n  template <typename T>\n  void DoRun();\n\n public:\n  OUTPUT_TAGS(UNIQUE, REMAPPING);\n};\n\ntemplate <class Context>\nclass UnsafeCoalesceOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using Operator<Context>::Operator;\n\n  bool RunOnDevice() override {\n    size_t coalesced_size = 0;\n    for (int i = 0; i < InputSize(); ++i) {\n      CAFFE_ENFORCE(\n          !Input(i).meta().ctor(),\n          \"Must only coalesce fundamental types, error at input: \",\n          i);\n    }\n\n    auto roundToAlignment = [](size_t bytes) -> size_t {\n      return ((bytes + gCaffe2Alignment - 1) / gCaffe2Alignment) *\n          gCaffe2Alignment;\n    };\n\n    for (int i = 0; i < InputSize(); ++i) {\n      coalesced_size += roundToAlignment(Input(i).nbytes());\n    }\n\n    auto* coalesced = Output(OutputSize() - 1);\n    coalesced->Resize(coalesced_size);\n    math::Set<uint8_t, Context>(\n        coalesced_size,\n        0.0,\n        coalesced->template mutable_data<uint8_t>(),\n        &context_);\n\n    size_t coalesced_offset = 0;\n    for (auto i = 0; i < InputSize(); ++i) {\n      const auto input_nbytes = Input(i).nbytes();\n      context_.template CopyBytes<Context, Context>(\n          input_nbytes,\n          (const uint8_t*)Input(i).raw_data(),\n          coalesced->template mutable_data<uint8_t>() + coalesced_offset);\n\n      // Note: this could cause Input(i) to free it's data if\n      // Output(i) and Input(i) alias each other. This is safe on a\n      // GPU (as the copy will happen-before the free), but it's\n      // worth mentioning.\n\n      Output(i)->ResizeLike(Input(i));\n      Output(i)->ShareExternalPointer(\n          static_cast<void*>(\n              coalesced->template mutable_data<uint8_t>() + coalesced_offset),\n          Input(i).meta(),\n          input_nbytes);\n      coalesced_offset += roundToAlignment(input_nbytes);\n    }\n    return true;\n  }\n};\n\ntemplate <typename T, class Context>\nclass AccumulateHistogramOp : public Operator<Context> {\n public:\n  AccumulateHistogramOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        lower_bound_(\n            OperatorBase::GetSingleArgument<float>(\"lower_bound\", 0.0)),\n        upper_bound_(\n            OperatorBase::GetSingleArgument<float>(\"upper_bound\", 1.0)),\n        num_buckets_(OperatorBase::GetSingleArgument<int>(\"num_buckets\", 1)) {\n    CAFFE_ENFORCE_GT(num_buckets_, 0);\n    // 2 more for histograms < lower_bound, >= upper_bound respectively\n    num_output_buckets_ = num_buckets_ + 2;\n    accumulate_hist_ = std::vector<int64_t>(num_output_buckets_, 0);\n  }\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    auto& X = Input(X_IN);\n    auto* X_data = X.template data<T>();\n    int N = X.size();\n    auto* cur_hist = Output(CUR_HIST);\n    auto* acc_hist = Output(ACC_HIST);\n    cur_hist->Resize(num_output_buckets_);\n    acc_hist->Resize(num_output_buckets_);\n    auto* cur_hist_data = cur_hist->template mutable_data<int64_t>();\n    auto* acc_hist_data = acc_hist->template mutable_data<int64_t>();\n    auto segment = (upper_bound_ - lower_bound_) / num_buckets_;\n    math::Set<int64_t, Context>(\n        num_output_buckets_, 0, cur_hist_data, &context_);\n\n    for (int i = 0; i < N; i++) {\n      int bucket_index = -1;\n      if (X_data[i] < lower_bound_) {\n        bucket_index = 0;\n      } else if (X_data[i] >= upper_bound_) {\n        bucket_index = num_buckets_ + 1;\n      } else {\n        bucket_index = (int)((X_data[i] - lower_bound_) / segment) + 1;\n      }\n      cur_hist_data[bucket_index] += 1;\n      accumulate_hist_[bucket_index] += 1;\n    }\n\n    for (int i = 0; i < num_output_buckets_; i++) {\n      acc_hist_data[i] = accumulate_hist_[i];\n    }\n\n    return true;\n  }\n\n private:\n  float lower_bound_;\n  float upper_bound_;\n  int num_buckets_;\n  int num_output_buckets_;\n  std::vector<int64_t> accumulate_hist_;\n\n  INPUT_TAGS(X_IN);\n  OUTPUT_TAGS(CUR_HIST, ACC_HIST);\n};\n\ntemplate <class Context>\nclass RangeOp : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(RangeOp)\n\n  bool RunOnDevice() override {\n    return DispatchHelper<TensorTypes<int32_t, int64_t, float, double>>::call(\n        this, Input(0));\n  }\n\n  template <typename T>\n  T readScalarInput(const int index) {\n    if (std::is_same<Context, TensorCPU>::value) {\n      return Input(index).template data<T>()[0];\n    } else {\n      local_.template CopyFrom<Context>(Input(index));\n      return local_.template data<T>()[0];\n    }\n  }\n\n  template <typename T>\n  bool DoRunWithType() {\n    T stop = 0;\n    T start = 0;\n    T step = 1;\n\n    for (int i = 0; i < InputSize(); ++i) {\n      CAFFE_ENFORCE_EQ(Input(0).ndim(), 0, \"All inputs must be scalar.\");\n    }\n\n    switch (InputSize()) {\n      case 1:\n        stop = readScalarInput<T>(0);\n        break;\n      case 2:\n        start = readScalarInput<T>(0);\n        stop = readScalarInput<T>(1);\n        break;\n      case 3:\n        step = readScalarInput<T>(2);\n        start = readScalarInput<T>(0);\n        stop = readScalarInput<T>(1);\n        break;\n    }\n    CAFFE_ENFORCE_NE(step, 0, \"Step size cannot be 0.\");\n    int length;\n    auto diff = stop - start;\n    if (std::is_integral<T>::value) {\n      // Avoid casting to and from floats in case it introduces rounding and\n      // avoid mod because the compiler doesn't strip unused code until later.\n      length = diff / step;\n      if (length * step < diff) {\n        length += 1;\n      }\n    } else {\n      length = static_cast<int>(ceil(diff / step));\n    }\n    auto* output = Output(0);\n    // Match numpy's behavior here.\n    if (length <= 0) {\n      output->Resize(0);\n      // Called for the side effect of setting the data.\n      output->template mutable_data<T>();\n      return true;\n    } else {\n      output->Resize(length);\n      return DoRunOnDevice<T>(start, step, output);\n    }\n  }\n\n  template <typename T>\n  bool DoRunOnDevice(const T& start, const T& step, Tensor<Context>* output);\n\n private:\n  // local CPU tensor for copying constants.\n  TensorCPU local_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_UTILITY_OPS_H_\n"
  },
  {
    "path": "caffe2/operators/utility_ops_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/flatten_op.h\"\n#include \"caffe2/operators/utility_ops.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool WeightedSumOp<CUDAContext>::RunOnDevice() {\n  if (Input(0).IsType<float>()) {\n    return DoRunWithType<float>();\n  } else if (Input(0).IsType<float16>()) {\n    return DoRunWithType<float16>();\n  } else {\n    CAFFE_THROW(\"Unsupported inputs\");\n  }\n  return false;\n}\n\ntemplate <>\nbool SumOp<CUDAContext>::RunOnDevice() {\n  if (Input(0).IsType<float>()) {\n    return DoRunWithType<float, float>();\n  } else if (Input(0).IsType<float16>()) {\n    return DoRunWithType<float16, float16>();\n  } else {\n    CAFFE_THROW(\"Unsupported inputs\");\n  }\n  return false;\n}\n\ntemplate <>\nclass CopyOnDeviceLikeOp<CUDAContext, CUDAContext, CUDAContext>\n    : public Operator<CUDAContext> {\n public:\n  CopyOnDeviceLikeOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CUDAContext>(operator_def, ws) {}\n  USE_OPERATOR_FUNCTIONS(CUDAContext);\n\n  bool RunOnDevice() override {\n    auto& input = Input(0);\n    auto* output = OperatorBase::Output<Tensor<CUDAContext>>(0);\n    CUDAContext context(GetGPUIDForPointer(Input(1).raw_data()));\n    output->ResizeLike(input);\n    context.template CopyItems<CUDAContext, CUDAContext>(\n        input.meta(),\n        input.size(),\n        input.raw_data(),\n        output->raw_mutable_data(input.meta()));\n    return true;\n  }\n};\n\nREGISTER_CUDA_OPERATOR(Print, PrintOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(Flatten, FlattenOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(FlattenToVec, FlattenToVecOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(Alias, AliasOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(ResizeLike, ResizeLikeOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(Sum, SumOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(WeightedSum, WeightedSumOp<CUDAContext>);\n// From whatever the current context, ensure the output is TensorCPU\nREGISTER_CUDA_OPERATOR(\n    EnsureCPUOutput,\n    CopyOp<CUDAContext, CPUContext, CUDAContext>);\n// From CPU, copy it to whatever the current context\nREGISTER_CUDA_OPERATOR(\n    CopyFromCPUInput,\n    CopyOp<CUDAContext, CUDAContext, CPUContext>);\n\n// CopyGPUToCPU and CopyCPUToGPU should both be carried out in a cuda context,\n// since gpu code will be involved.\nREGISTER_CUDA_OPERATOR(\n    CopyGPUToCPU,\n    CopyOp<CUDAContext, CPUContext, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    CopyCPUToGPU,\n    CopyOp<CUDAContext, CUDAContext, CPUContext>);\n// If we only specify Copy, we assume that it is a gpu to gpu copy - maybe\n// involving different GPUs.\nREGISTER_CUDA_OPERATOR(Copy, CopyOp<CUDAContext, CUDAContext, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(\n    CopyOnDeviceLike,\n    CopyOnDeviceLikeOp<CUDAContext, CUDAContext, CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(UnsafeCoalesce, UnsafeCoalesceOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/utility_ops_gpu_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/operators/utility_ops.h\"\n#include <gtest/gtest.h>\n\nCAFFE2_DECLARE_string(caffe_test_root);\n\nnamespace caffe2 {\n\nstatic void AddConstInput(\n    const vector<TIndex>& shape,\n    const float value,\n    const string& name,\n    Workspace* ws) {\n  DeviceOption option;\n  option.set_device_type(CUDA);\n  CUDAContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<Tensor<CUDAContext>>();\n  tensor->Resize(shape);\n  math::Set<float, CUDAContext>(\n      tensor->size(), value, tensor->mutable_data<float>(), &context);\n  return;\n}\n\nTEST(UtilityOpGPUTest, testEnsureCPUOutput) {\n  if (!HasCudaGPU())\n    return;\n  Workspace ws;\n  OperatorDef def;\n  def.set_name(\"test\");\n  def.set_type(\"EnsureCPUOutput\");\n  def.add_input(\"X\");\n  def.add_output(\"Y\");\n  def.mutable_device_option()->set_device_type(CUDA);\n  AddConstInput(vector<TIndex>{5, 10}, 3.14, \"X\", &ws);\n  Blob* Xblob = ws.GetBlob(\"X\");\n  EXPECT_NE(nullptr, Xblob);\n  // input X should start as a CUDATensor\n  EXPECT_TRUE(Xblob->IsType<Tensor<CUDAContext>>());\n  // now execute the op to get Y\n  unique_ptr<OperatorBase> op(CreateOperator(def, &ws));\n  EXPECT_NE(nullptr, op.get());\n  EXPECT_TRUE(op->Run());\n  Blob* Yblob = ws.GetBlob(\"Y\");\n  EXPECT_NE(nullptr, Yblob);\n  // output Y should be a CPUTensor\n  EXPECT_TRUE(Yblob->IsType<Tensor<CPUContext>>());\n  const TensorCPU& Y_cpu = Yblob->Get<Tensor<CPUContext>>();\n  EXPECT_EQ(Y_cpu.size(), 5 * 10);\n  for (int i = 0; i < Y_cpu.size(); ++i) {\n    EXPECT_LT(Y_cpu.data<float>()[i], 3.15);\n    EXPECT_GT(Y_cpu.data<float>()[i], 3.13);\n  }\n}\n\nTEST(UtilityOpGPUTest, testReshapeWithScalar) {\n  if (!HasCudaGPU())\n    return;\n  Workspace ws;\n  OperatorDef def;\n  def.set_name(\"test_reshape\");\n  def.set_type(\"Reshape\");\n  def.add_input(\"X\");\n  def.add_output(\"XNew\");\n  def.add_output(\"OldShape\");\n  def.add_arg()->CopyFrom(MakeArgument(\"shape\", vector<int64_t>{1}));\n  def.mutable_device_option()->set_device_type(CUDA);\n  AddConstInput(vector<TIndex>(), 3.14, \"X\", &ws);\n  // execute the op\n  unique_ptr<OperatorBase> op(CreateOperator(def, &ws));\n  EXPECT_TRUE(op->Run());\n  Blob* XNew = ws.GetBlob(\"XNew\");\n  const Tensor<CUDAContext>& XNewTensor = XNew->Get<Tensor<CUDAContext>>();\n  EXPECT_EQ(1, XNewTensor.ndim());\n  EXPECT_EQ(1, XNewTensor.size());\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/utility_ops_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/operators/utility_ops.h\"\n#include <gtest/gtest.h>\n\nCAFFE2_DECLARE_string(caffe_test_root);\n\nnamespace caffe2 {\n\nstatic void AddConstInput(\n    const vector<TIndex>& shape,\n    const float value,\n    const string& name,\n    Workspace* ws) {\n  DeviceOption option;\n  CPUContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(shape);\n  math::Set<float, CPUContext>(\n      tensor->size(), value, tensor->mutable_data<float>(), &context);\n  return;\n}\n\nTEST(UtilityOpTest, testEnsureCPUOutput) {\n  Workspace ws;\n  OperatorDef def;\n  def.set_name(\"test\");\n  def.set_type(\"EnsureCPUOutput\");\n  def.add_input(\"X\");\n  def.add_output(\"Y\");\n  AddConstInput(vector<TIndex>{5, 10}, 3.14, \"X\", &ws);\n  Blob* Xblob = ws.GetBlob(\"X\");\n  EXPECT_NE(nullptr, Xblob);\n  // input X should be a CPUTensor\n  EXPECT_TRUE(Xblob->IsType<Tensor<CPUContext>>());\n  // now execute the op to get Y\n  unique_ptr<OperatorBase> op(CreateOperator(def, &ws));\n  EXPECT_NE(nullptr, op.get());\n  EXPECT_TRUE(op->Run());\n  Blob* Yblob = ws.GetBlob(\"Y\");\n  EXPECT_NE(nullptr, Yblob);\n  // output Y should be a CPUTensor\n  EXPECT_TRUE(Yblob->IsType<Tensor<CPUContext>>());\n  const TensorCPU& Y_cpu = Yblob->Get<Tensor<CPUContext>>();\n  EXPECT_EQ(Y_cpu.size(), 5 * 10);\n  for (int i = 0; i < Y_cpu.size(); ++i) {\n    EXPECT_LT(Y_cpu.data<float>()[i], 3.15);\n    EXPECT_GT(Y_cpu.data<float>()[i], 3.13);\n  }\n}\n\nTEST(UtilityOpTest, testReshapeWithScalar) {\n  Workspace ws;\n  OperatorDef def;\n  def.set_name(\"test_reshape\");\n  def.set_type(\"Reshape\");\n  def.add_input(\"X\");\n  def.add_output(\"XNew\");\n  def.add_output(\"OldShape\");\n  def.add_arg()->CopyFrom(MakeArgument(\"shape\", vector<int64_t>{1}));\n  AddConstInput(vector<TIndex>(), 3.14, \"X\", &ws);\n  // execute the op\n  unique_ptr<OperatorBase> op(CreateOperator(def, &ws));\n  EXPECT_TRUE(op->Run());\n  Blob* XNew = ws.GetBlob(\"XNew\");\n  const TensorCPU& XNewTensor = XNew->Get<Tensor<CPUContext>>();\n  EXPECT_EQ(1, XNewTensor.ndim());\n  EXPECT_EQ(1, XNewTensor.size());\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/variable_length_sequence_padding.cc",
    "content": "/**\n * Copyright (c) 2018-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"variable_length_sequence_padding.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(\n    VariableLengthSequencePadding,\n    VariableLengthSequencePaddingOp<float, CPUContext>);\nOPERATOR_SCHEMA(VariableLengthSequencePadding)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nSuper special-case operator. Used to pad a tensor to mimic pytorch's\npad_packed_sequence.\n\nGiven an input tensor INPUT of size NxBxM and an input tensor LENS\nof size B, where\n\nN = maximum sequence length\nB = batch size\nM = hidden size\n\nset each element of INPUT to zero if it is is past the end of the\ncorresponding sequence (i.e. if LENS[j] > i for an index (i,j,k)).\n\n)DOC\");\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/variable_length_sequence_padding.h",
    "content": "/**\n * Copyright (c) 2018-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\nnamespace detail {\n\ntemplate <typename T, typename Context>\nvoid VariableLengthSequencePadding(\n    int N,\n    int B,\n    int M,\n    T* X,\n    const int32_t* seqLengths,\n    const T padValue,\n    Context* /*context*/) {\n  for (int j = 0; j < B; j++) {\n    for (int i = seqLengths[j]; i < N; i++) {\n      EigenVectorArrayMap<T>(X + B * M * i + M * j, M).setConstant(padValue);\n    }\n  }\n}\n\n} // namespace detail\n\ntemplate <typename T, typename Context>\nclass VariableLengthSequencePaddingOp : public Operator<Context> {\n public:\n  VariableLengthSequencePaddingOp(\n      const OperatorDef& operator_def,\n      Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    const auto N = Input(INPUT).dim(0);\n    const auto B = Input(INPUT).dim(1);\n    const auto M = Input(INPUT).dim(2);\n\n    auto X = Output(OUTPUT)->template mutable_data<T>();\n\n    auto seqLengths = Input(SEQ_LENGTHS).template data<int32_t>();\n\n    detail::VariableLengthSequencePadding<T, Context>(\n        N, B, M, X, seqLengths, 0, &context_);\n    return true;\n  }\n\n protected:\n  INPUT_TAGS(INPUT, SEQ_LENGTHS);\n  OUTPUT_TAGS(OUTPUT);\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/weighted_sample_op.cc",
    "content": "#include \"caffe2/operators/weighted_sample_op.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nbool WeightedSampleOp<float, CPUContext>::RunOnDevice() {\n  CAFFE_ENFORCE_EQ(\n      InputSize(),\n      OutputSize(),\n      \"The number of tensors of the input and the output must be the same.\");\n  auto& weights = Input(0);\n  int batch_size = weights.dim(0);\n  int weights_dim = weights.dim(1);\n  auto* out_idx = Output(0);\n\n  if (batch_size > 0 && weights_dim > 0) {\n    cum_mass_.resize(weights_dim);\n    const float* mat_weights = weights.template data<float>();\n    const float* mat_values = nullptr;\n    out_idx->Resize(batch_size, 1);\n    int* output_indices = out_idx->template mutable_data<int>();\n    float* output_values = nullptr;\n\n    if (InputSize() == 2) {\n      auto& values = Input(1);\n      CAFFE_ENFORCE_EQ(\n          weights.dims(),\n          values.dims(),\n          \"The sampling weights tensor and the sampling values tensor must have the same dimensions.\");\n      mat_values = values.template data<float>();\n      auto* out_value = Output(1);\n      out_value->Resize(batch_size, 1);\n      output_values = out_value->template mutable_data<float>();\n    }\n\n    for (int i = 0; i < batch_size; i++) {\n      float r;\n      int offset = i * weights_dim;\n\n      cum_mass_[0] = mat_weights[offset];\n      for (int j = 1; j < weights_dim; j++) {\n        cum_mass_[j] = cum_mass_[j - 1] + mat_weights[offset + j];\n      }\n\n      math::RandUniform<float, CPUContext>(\n          1, 0.0f, cum_mass_[cum_mass_.size() - 1], &r, &context_);\n      // Makes the element in cum_mass_ slightly bigger\n      // to compensate inaccuracy introduced due to rounding,\n      cum_mass_[cum_mass_.size() - 1] += 0.01f;\n      auto lb = lower_bound(cum_mass_.begin(), cum_mass_.end(), r);\n      CAFFE_ENFORCE(lb != cum_mass_.end(), \"Cannot find \", r, \" in cum_mass_.\");\n      output_indices[i] = static_cast<int>(lb - cum_mass_.begin());\n\n      if (output_values) {\n        output_values[i] =\n            static_cast<float>(mat_values[offset + (lb - cum_mass_.begin())]);\n      }\n    }\n  } else {\n    out_idx->Resize(0);\n    out_idx->template mutable_data<int>();\n    if (OutputSize() == 2) {\n      auto* out_value = Output(1);\n      out_value->Resize(0);\n      out_value->template mutable_data<float>();\n    }\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(WeightedSample, WeightedSampleOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(WeightedSample)\n    .NumInputs(1, 2)\n    .NumOutputs(1, 2)\n    .TensorInferenceFunction([](const OperatorDef& def,\n                                const vector<TensorShape>& in) {\n      vector<TensorShape> out(2);\n      int batch_size = in[0].dims(0);\n      out[0] = CreateTensorShape(vector<int>{batch_size}, TensorProto::INT32);\n      out[1] = CreateTensorShape(vector<int>{batch_size}, TensorProto::FLOAT);\n      return out;\n    })\n    .SetDoc(R\"DOC(\nThe operator performs sampling based on the input sampling weights for\neach batch. All weights must be non-negative numbers.\nThe input is a 2-D tensor (Tensor<float>) of size (batch_size x weights_dim).\nFor each batch, an index is randomly sampled from the distribution given by\nthe weights of the corresponding batch.\nThe output is a 1-D tensor (Tensor<int>) of size (batch_size x 1) and\ncontains the index(es) of the sampled output.\n)DOC\")\n    .Input(\n        0,\n        \"sampling_weights\",\n        \"A 2-D Tensor<float> of size (batch_size x weights_dim).\"\n        \"All weights must be non-negative numbers.\")\n    .Input(\n        1,\n        \"sampling_values\",\n        \"An optional 2-D Tensor<float> of size (batch_size x weights_dim).\"\n        \"Its values correspond to the sampling weights.\")\n    .Output(\n        0,\n        \"sampled_indexes\",\n        \"The output tensor contains index(es) sampled from distribution given\"\n        \"by the weight vector(s) in the input tensor\"\n        \"The output is a 1-D Tensor<int> of size (batch_size x 1)\")\n    .Output(\n        1,\n        \"sampled_values\",\n        \"The output tensor contains value(s) selected by the sampled index(es)\"\n        \"It is a 1-D Tensor<float> of size (batch_size x 1)\");\n\nSHOULD_NOT_DO_GRADIENT(WeightedSample);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/weighted_sample_op.cu",
    "content": "#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/weighted_sample_op.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\nnamespace {\n\n__global__ void WeightedSampleKernel(\n    const int batch_size,\n    const int weights_dim,\n    const float* in_weights_data,\n    const float* in_val_data,\n    float* samples,\n    int* out_idx_data,\n    float* out_val_data) {\n  CUDA_1D_KERNEL_LOOP(i, batch_size) {\n    int offset = i * weights_dim;\n\n    float sum = 0.0;\n    for (int j = 0; j < weights_dim; j++) {\n      sum += in_weights_data[offset + j];\n    }\n    samples[i] *= sum;\n\n    float cum_sum = 0.0;\n    int j = 0;\n    for (; j < weights_dim; j++) {\n      cum_sum += in_weights_data[offset + j];\n      if (cum_sum >= samples[i]) {\n        break;\n      }\n    }\n    out_idx_data[i] = min(j, weights_dim - 1);\n\n    if (out_val_data) {\n      out_val_data[i] = in_val_data[offset + out_idx_data[i]];\n    }\n  }\n}\n\n} // namespace\n\ntemplate <>\nbool WeightedSampleOp<float, CUDAContext>::RunOnDevice() {\n  CAFFE_ENFORCE_EQ(\n      InputSize(),\n      OutputSize(),\n      \"The number of tensors of the input and the output must be the same.\");\n\n  auto& in_weights = Input(0);\n  auto* out_idx = Output(0);\n  int batch_size = in_weights.dim(0);\n  int weights_dim = in_weights.dim(1);\n\n  if (batch_size > 0 && weights_dim > 0) {\n    out_idx->Resize(batch_size, 1);\n    unif_samples_.Resize(batch_size);\n\n    const float* in_weights_data = in_weights.data<float>();\n    const float* in_val_data = nullptr;\n    int* out_idx_data = out_idx->mutable_data<int>();\n    float* out_val_data = nullptr;\n\n    if (OutputSize() == 2) {\n      auto& in_val = Input(1);\n      CAFFE_ENFORCE_EQ(\n          in_weights.dims(),\n          in_val.dims(),\n          \"The sampling weights tensor and the sampling values tensor must have the same dimensions.\");\n      in_val_data = in_val.data<float>();\n\n      auto* out_val = Output(1);\n      out_val->Resize(batch_size, 1);\n      out_val_data = out_val->mutable_data<float>();\n    }\n\n    float* unif_samples_data = unif_samples_.mutable_data<float>();\n    CURAND_ENFORCE(curandGenerateUniform(\n        context_.curand_generator(), unif_samples_data, batch_size));\n\n    WeightedSampleKernel<<<\n        CAFFE_GET_BLOCKS(batch_size),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(\n        batch_size,\n        weights_dim,\n        in_weights_data,\n        in_val_data,\n        unif_samples_data,\n        out_idx_data,\n        out_val_data);\n  } else {\n    out_idx->Resize(0);\n    out_idx->mutable_data<int>();\n    if (OutputSize() == 2) {\n      auto* out_val = Output(1);\n      out_val->Resize(0);\n      out_val->mutable_data<float>();\n    }\n  }\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(WeightedSample, WeightedSampleOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/weighted_sample_op.h",
    "content": "// Copyright 2004-present Facebook. All Rights Reserved.\n\n#ifndef CAFFE2_OPERATORS_WEIGHTEDSAMPLE_OP_H_\n#define CAFFE2_OPERATORS_WEIGHTEDSAMPLE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass WeightedSampleOp final : public Operator<Context> {\n public:\n  WeightedSampleOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override;\n\n private:\n  vector<float> cum_mass_;\n  Tensor<Context> unif_samples_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_WEIGHTEDSAMPLE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/while_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/while_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(While, WhileOp<CPUContext>);\n\nOPERATOR_SCHEMA(While)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .SetDoc(R\"DOC(\n'While' control operator, first input is a scalar boolean blob that stores loop's\ncondition value. Accepts 'loop_net' (required) and 'cond_net' (optional) arguments for\nloop's body and condition subnets respectively. If condition subnet is specified,\nit is executed before the first and after each iteration. Subnets are executed in\nthe same workspace as 'While'.\n    )DOC\")\n    .Arg(\"loop_net\", \"Net executed on each iteration\")\n    .Arg(\"cond_net\", \"Net to (re)compute condition value\")\n    .Input(0, \"condition\", \"Scalar boolean condition\")\n    .AllowInplace([](int in, int out) -> bool { return true; });\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/while_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_WHILE_OP_H_\n#define CAFFE2_OPERATORS_WHILE_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass WhileOp final : public Operator<Context> {\n public:\n  WhileOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    CAFFE_ENFORCE(\n        this->template HasSingleArgumentOfType<NetDef>(\"loop_net\"),\n        \"loop_net must be specified in While operator\");\n    loop_net_def_ =\n        this->template GetSingleArgument<NetDef>(\"loop_net\", NetDef());\n    loop_net_ = CreateNet(loop_net_def_, ws);\n    CAFFE_ENFORCE(loop_net_, \"Failed to initialize loop subnet\");\n\n    cond_net_ = nullptr;\n    bool has_cond_net =\n        this->template HasSingleArgumentOfType<NetDef>(\"cond_net\");\n    if (has_cond_net) {\n      cond_net_def_ =\n          this->template GetSingleArgument<NetDef>(\"cond_net\", NetDef());\n      cond_net_ = CreateNet(cond_net_def_, ws);\n      CAFFE_ENFORCE(cond_net_, \"Failed to initialize condition subnet\");\n    }\n  }\n\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE(\n        this->template InputIsType<Tensor<Context>>(0),\n        \"Invalid condition in While operator: tensor expected\");\n\n    const auto& condition = Input(0);\n    CAFFE_ENFORCE_EQ(\n        condition.size(),\n        1,\n        \"Invalid condition tensor in While operator: single value expected\");\n\n    while (true) {\n      if (cond_net_ && !cond_net_->Run()) {\n        return false;\n      }\n      if (!*condition.template data<bool>()) {\n        return true;\n      }\n      if (!loop_net_->Run()) {\n        return false;\n      }\n    }\n\n    return true;\n  }\n\n private:\n  NetDef loop_net_def_;\n  std::unique_ptr<NetBase> loop_net_;\n\n  NetDef cond_net_def_;\n  std::unique_ptr<NetBase> cond_net_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_WHILE_OP_H_\n"
  },
  {
    "path": "caffe2/operators/while_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/while_op.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(While, WhileOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/workspace_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\nnamespace {\n\nclass GetAllBlobNamesOp final : public Operator<CPUContext> {\n public:\n  GetAllBlobNamesOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws),\n        include_shared_(GetSingleArgument<int>(\"include_shared\", true)),\n        ws_(ws) {}\n\n  bool RunOnDevice() override {\n    auto* out = Output(0);\n    const auto& blobs = include_shared_ ? ws_->Blobs() : ws_->LocalBlobs();\n    out->Resize(blobs.size());\n    std::copy(blobs.begin(), blobs.end(), out->mutable_data<std::string>());\n    return true;\n  }\n\n private:\n  bool include_shared_;\n  Workspace* ws_;\n};\n\nREGISTER_CPU_OPERATOR(GetAllBlobNames, GetAllBlobNamesOp);\nOPERATOR_SCHEMA(GetAllBlobNames)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nReturn a 1D tensor of strings containing the names\nof each blob in the active workspace.\n)DOC\")\n    .Arg(\n        \"include_shared\",\n        \"(bool, default true) Whether to include blobs \"\n        \"inherited from parent workspaces.\")\n    .Output(0, \"blob_names\", \"1D tensor of strings containing blob names.\");\nSHOULD_NOT_DO_GRADIENT(GetAllBlobNamesOp);\n}\n}\n"
  },
  {
    "path": "caffe2/operators/zero_gradient_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/operators/zero_gradient_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(ZeroGradient, ZeroGradientOp<CPUContext>);\nOPERATOR_SCHEMA(ZeroGradient)\n    .NumInputs(1)\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(\nZeroGradient operators doesn't produce any output blobs. One can use\nthis operator to produce 0 gradient for the input blob.\n)DOC\");\n\nstruct GetZeroGradientOpGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  std::vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"ConstantFill\",\n        \"\",\n        vector<string>{I(0)},\n        vector<string>{GI(0)},\n        vector<Argument>{MakeArgument<float>(\"value\", 0.0)});\n  }\n};\n\nREGISTER_GRADIENT(ZeroGradient, GetZeroGradientOpGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/zero_gradient_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass ZeroGradientOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_SIMPLE_CTOR_DTOR(ZeroGradientOp);\n\n  bool RunOnDevice() override {\n    return true;\n  }\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/operators/zero_gradient_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/zero_gradient_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(ZeroGradient, ZeroGradientOp<CUDAContext>);\n}\n"
  },
  {
    "path": "caffe2/perfkernels/CMakeLists.txt",
    "content": "# ---[ CPU files.\nfile(GLOB common_srcs *.cc)\nfile(GLOB avx_srcs *_avx.cc)\nfile(GLOB avx2_srcs *_avx2.cc)\n# exclude avx and avx2 srcs from common_srcs\nexclude(common_srcs \"${common_srcs}\" ${avx_srcs})\nexclude(common_srcs \"${common_srcs}\" ${avx2_srcs})\n\n# We will always build common srcs.\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${common_srcs})\n\n# We will only build the perf kernel files if the compiler supports avx2\n# extensions.\n# Currently MSVC seems to have a symbol not found error while linking (related\n# to source file order?). As a result we will currently disable the perfkernel\n# in msvc.\nif (NOT MSVC AND CAFFE2_COMPILER_SUPPORTS_AVX2_EXTENSIONS)\n  add_library(Caffe2_perfkernels_avx OBJECT ${avx_srcs})\n  add_library(Caffe2_perfkernels_avx2 OBJECT ${avx2_srcs})\n  add_dependencies(Caffe2_perfkernels_avx Caffe_PROTO Caffe2_PROTO)\n  add_dependencies(Caffe2_perfkernels_avx2 Caffe_PROTO Caffe2_PROTO)\n  if (MSVC)\n    set_target_properties(\n        Caffe2_perfkernels_avx PROPERTIES COMPILE_FLAGS \"/arch:AVX\")\n    set_target_properties(\n        Caffe2_perfkernels_avx2 PROPERTIES COMPILE_FLAGS \"/arch:AVX2\")\n  else()\n    set_target_properties(\n        Caffe2_perfkernels_avx PROPERTIES COMPILE_FLAGS \"-mavx -mf16c\")\n    set_target_properties(\n        Caffe2_perfkernels_avx2 PROPERTIES COMPILE_FLAGS \"-mavx2 -mfma -mavx -mf16c\")\n  endif()\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS}\n      $<TARGET_OBJECTS:Caffe2_perfkernels_avx>\n      $<TARGET_OBJECTS:Caffe2_perfkernels_avx2>)\nendif()\n\n# TODO(jiayq): currently, we only implement the very base files for the\n# perfkernels. This is because to implement avx and avx2 files, we actually\n# need to set up different compilation units and this is a bit more involving\n# in terms of CMakefile changes. This is a stop-gap solution until we get a\n# more proper implementation.\n\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/perfkernels/common.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Common utilities for writing performance kernels and easy dispatching of\n// different backends.\n/*\nThe general workflow shall be as follows, say we want to\nimplement a functionality called void foo(int a, float b).\n\nIn foo.h, do:\n   void foo(int a, float b);\n\nIn foo_avx2.cc, do:\n   void foo__avx2(int a, float b) {\n     [actual avx2 implementation]\n   }\n\nIn foo_avx.cc, do:\n   void foo__avx(int a, float b) {\n     [actual avx implementation]\n   }\n\nIn foo.cc, do:\n   // The base implementation should *always* be provided.\n   void foo__base(int a, float b) {\n     [base, possibly slow implementation]\n   }\n   void foo(int a, float b) {\n     // You should always order things by their preference, faster\n     // implementations earlier in the function.\n     AVX2_DO(foo, a, b);\n     AVX_DO(foo, a, b);\n     BASE_DO(foo, a, b);\n   }\n\n*/\n// Details: this functionality basically covers the cases for both build time\n// and run time architecture support.\n//\n// During build time:\n//    The build system should provide flags CAFFE2_PERF_WITH_AVX2 and\n//    CAFFE2_PERF_WITH_AVX that corresponds to the __AVX__ and __AVX2__ flags\n//    the compiler provides. Note that we do not use the compiler flags but\n//    rely on the build system flags, because the common files (like foo.cc\n//    above) will always be built without __AVX__ and __AVX2__.\n// During run time:\n//    we use cpuid to identify cpu support and run the proper functions.\n\n#pragma once\n\n// DO macros: these should be used in your entry function, similar to foo()\n// above, that routes implementations based on CPU capability.\n\n#define BASE_DO(funcname, ...) return funcname##__base(__VA_ARGS__);\n\n#ifdef CAFFE2_PERF_WITH_AVX2\n#define AVX2_DO(funcname, ...)                 \\\n  decltype(funcname##__base) funcname##__avx2; \\\n  if (GetCpuId().avx2()) {                     \\\n    return funcname##__avx2(__VA_ARGS__);      \\\n  }\n#define AVX2_FMA_DO(funcname, ...)                 \\\n  decltype(funcname##__base) funcname##__avx2_fma; \\\n  if (GetCpuId().avx2() && GetCpuId().fma()) {     \\\n    return funcname##__avx2_fma(__VA_ARGS__);      \\\n  }\n#else // CAFFE2_PERF_WITH_AVX2\n#define AVX2_DO(funcname, ...)\n#define AVX2_FMA_DO(funcname, ...)\n#endif // CAFFE2_PERF_WITH_AVX2\n\n#ifdef CAFFE2_PERF_WITH_AVX\n#define AVX_DO(funcname, ...)                 \\\n  decltype(funcname##__base) funcname##__avx; \\\n  if (GetCpuId().avx()) {                     \\\n    return funcname##__avx(__VA_ARGS__);      \\\n  }\n#define AVX_F16C_DO(funcname, ...)                 \\\n  decltype(funcname##__base) funcname##__avx_f16c; \\\n  if (GetCpuId().avx() && GetCpuId().f16c()) {     \\\n    return funcname##__avx_f16c(__VA_ARGS__);      \\\n  }\n#else // CAFFE2_PERF_WITH_AVX\n#define AVX_DO(funcname, ...)\n#define AVX_F16C_DO(funcname, ...)\n#endif // CAFFE2_PERF_WITH_AVX\n"
  },
  {
    "path": "caffe2/perfkernels/common_avx.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// This file is here merely to check that the flags are not mixed up: for\n// example, if your compiler did not specify -mavx, you should not provide\n// the CAFFE2_PERF_WITH_AVX macro.\n\n#include \"caffe2/core/common.h\"\n\n#ifdef CAFFE2_PERF_WITH_AVX\n#ifndef __AVX__\n#error( \\\n    \"You found a build system error: CAFFE2_PERF_WITH_AVX is defined\" \\\n    \"but __AVX__ is not defined (via e.g. -mavx).\");\n#endif // __AVX__\n#endif // CAFFE2_PERF_WITH_AVX\n\n#ifdef __AVX__\n#ifndef CAFFE2_PERF_WITH_AVX\n#error( \\\n    \"You found a build system error: __AVX__ is defined (via e.g. -mavx) \" \\\n    \"but CAFFE2_PERF_WITH_AVX is not defined.\");\n#endif // CAFFE2_PERF_WITH_AVX\n#endif\n"
  },
  {
    "path": "caffe2/perfkernels/common_avx2.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// This file is here merely to check that the flags are not mixed up: for\n// example, if your compiler did not specify -mavx2, you should not provide\n// the CAFFE2_PERF_WITH_AVX2 macro.\n\n#include \"caffe2/core/common.h\"\n\n#ifdef CAFFE2_PERF_WITH_AVX2\n#ifndef __AVX2__\n#error( \\\n    \"You found a build system error: CAFFE2_PERF_WITH_AVX2 is defined\" \\\n    \"but __AVX2__ is not defined (via e.g. -mavx2).\");\n#endif // __AVX2__\n#endif // CAFFE2_PERF_WITH_AVX2\n\n#ifdef __AVX2__\n#ifndef CAFFE2_PERF_WITH_AVX2\n#error( \\\n    \"You found a build system error: __AVX2__ is defined (via e.g. -mavx2) \" \\\n    \"but CAFFE2_PERF_WITH_AVX2 is not defined.\");\n#endif // CAFFE2_PERF_WITH_AVX2\n#endif\n"
  },
  {
    "path": "caffe2/perfkernels/cvtsh_ss_bugfix.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n// Apple clang was fixed in 8.1\n#if defined(__apple_build_version__) && ((__clang_major__ < 8) || ((__clang_major__ == 8) && (__clang_minor__ < 1)))\n#define __APPLE_NEED_FIX 1\n#endif\n\n// Regular clang was fixed in 3.9\n#if defined(__clang__) && (__clang_major__ < 4) && (__clang_minor__ < 9)\n#define __CLANG_NEED_FIX 1\n#endif\n\n#if __APPLE_NEED_FIX || __CLANG_NEED_FIX\n\n#include <emmintrin.h>\n\n// This version of clang has a bug that _cvtsh_ss is not defined, see\n// https://reviews.llvm.org/D16177\nstatic __inline float\n    __attribute__((__always_inline__, __nodebug__, __target__(\"f16c\")))\n_cvtsh_ss(unsigned short a)\n{\n  __v8hi v = {(short)a, 0, 0, 0, 0, 0, 0, 0};\n  __v4sf r = __builtin_ia32_vcvtph2ps(v);\n  return r[0];\n}\n\n#endif // __APPLE_NEED_FIX || __CLANG_NEED_FIX\n\n#undef __APPLE_NEED_FIX\n#undef __CLANG_NEED_FIX\n\n#ifdef _MSC_VER\n\n// It seems that microsoft msvc does not have a _cvtsh_ss implementation so\n// we will add a dummy version to it.\n\nstatic inline float\n_cvtsh_ss(unsigned short x) {\n  union {\n    uint32_t intval;\n    float floatval;\n  } t1;\n  uint32_t t2, t3;\n  t1.intval = x & 0x7fff; // Non-sign bits\n  t2 = x & 0x8000; // Sign bit\n  t3 = x & 0x7c00; // Exponent\n  t1.intval <<= 13; // Align mantissa on MSB\n  t2 <<= 16; // Shift sign bit into position\n  t1.intval += 0x38000000; // Adjust bias\n  t1.intval = (t3 == 0 ? 0 : t1.intval); // Denormals-as-zero\n  t1.intval |= t2; // Re-insert sign bit\n  return t1.floatval;\n}\n\n#endif // _MSC_VER\n"
  },
  {
    "path": "caffe2/perfkernels/embedding_lookup.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/perfkernels/embedding_lookup.h\"\n\n#include \"caffe2/core/types.h\"\n#include \"caffe2/perfkernels/common.h\"\n#include \"caffe2/perfkernels/typed_axpy.h\"\n#include \"caffe2/utils/cpuid.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n// Base implementation does runtime dispatch for each segment of reduction\ntemplate <typename IndexType, typename InType, typename OutType>\nstatic void EmbeddingLookupGenericSlow(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const InType* input,\n    const IndexType* indices,\n    const int* lengths,\n    const float* weights, // optional, can be null for sum reducer\n    const float* scale_bias, // optional scale & bias params for uint8 input\n    bool normalize_by_lengths,\n    OutType* out) {\n  TIndex current = 0;\n  for (int m = 0; m < output_size; ++m) {\n    memset(out, 0, sizeof(OutType) * block_size);\n    EigenVectorArrayMap<OutType> out_vector(out, block_size);\n    for (int i = 0; i < lengths[m]; ++i) {\n      CAFFE_ENFORCE_LT(current, index_size);\n      TIndex idx = indices[current];\n      CAFFE_ENFORCE(\n          0 <= idx && idx < data_size,\n          \"Index \",\n          current,\n          \" is out of bounds: \",\n          idx,\n          \", range 0 to \",\n          data_size);\n      CAFFE_ENFORCE_LT(idx, data_size);\n#ifdef __GNUC__\n      if (current + 1 < index_size) {\n        __builtin_prefetch(input + block_size * indices[current + 1], 0, 1);\n      }\n#endif // __GNUC__\n\n      float w = 1.f, b = 0.f;\n      if (weights) {\n        w = weights[current];\n      }\n      if (scale_bias) {\n        b = w * scale_bias[2 * indices[current] + 1];\n        w = w * scale_bias[2 * indices[current]];\n      }\n\n      TypedAxpy<InType, OutType>(\n          block_size, w, input + block_size * indices[current], out);\n\n      if (scale_bias) {\n        out_vector = out_vector + b;\n      }\n\n      ++current;\n    }\n    if (normalize_by_lengths && lengths[m]) {\n      // hack: context is not really used\n      math::Scale<OutType, CPUContext>(\n          block_size, 1.f / lengths[m], out, out, nullptr);\n    }\n    out += block_size;\n  }\n  CAFFE_ENFORCE_EQ(\n      current,\n      index_size,\n      \"Your input seems to be incorrect: the sum of lengths values should be \"\n      \"the size of the indices tensor, but it appears not.\");\n}\n\n// Proxy back to generic implementation\n#define EMBEDDING_SPECIALIZATION(IndexType, InType, OutType)       \\\n  void EmbeddingLookup_##IndexType##_##InType##_##OutType##__base( \\\n      const TIndex block_size,                                     \\\n      const TIndex output_size,                                    \\\n      const TIndex index_size,                                     \\\n      const TIndex data_size,                                      \\\n      const InType* input,                                         \\\n      const IndexType* indices,                                    \\\n      const int* lengths,                                          \\\n      const float* weights,                                        \\\n      const float* scale_bias,                                     \\\n      bool normalize_by_lengths,                                   \\\n      OutType* out) {                                              \\\n    EmbeddingLookupGenericSlow<IndexType, InType, OutType>(        \\\n        block_size,                                                \\\n        output_size,                                               \\\n        index_size,                                                \\\n        data_size,                                                 \\\n        input,                                                     \\\n        indices,                                                   \\\n        lengths,                                                   \\\n        weights,                                                   \\\n        scale_bias,                                                \\\n        normalize_by_lengths,                                      \\\n        out);                                                      \\\n  }                                                                \\\n  template <>                                                      \\\n  void EmbeddingLookup(                                            \\\n      const TIndex block_size,                                     \\\n      const TIndex output_size,                                    \\\n      const TIndex index_size,                                     \\\n      const TIndex data_size,                                      \\\n      const InType* input,                                         \\\n      const IndexType* indices,                                    \\\n      const int* lengths,                                          \\\n      const float* weights,                                        \\\n      const float* scale_bias,                                     \\\n      bool normalize_by_lengths,                                   \\\n      OutType* out) {                                              \\\n    AVX2_FMA_DO(                                                   \\\n        EmbeddingLookup_##IndexType##_##InType##_##OutType,        \\\n        block_size,                                                \\\n        output_size,                                               \\\n        index_size,                                                \\\n        data_size,                                                 \\\n        input,                                                     \\\n        indices,                                                   \\\n        lengths,                                                   \\\n        weights,                                                   \\\n        scale_bias,                                                \\\n        normalize_by_lengths,                                      \\\n        out);                                                      \\\n    BASE_DO(                                                       \\\n        EmbeddingLookup_##IndexType##_##InType##_##OutType,        \\\n        block_size,                                                \\\n        output_size,                                               \\\n        index_size,                                                \\\n        data_size,                                                 \\\n        input,                                                     \\\n        indices,                                                   \\\n        lengths,                                                   \\\n        weights,                                                   \\\n        scale_bias,                                                \\\n        normalize_by_lengths,                                      \\\n        out);                                                      \\\n  }\n\nEMBEDDING_SPECIALIZATION(int32_t, float, float);\nEMBEDDING_SPECIALIZATION(int64_t, float, float);\nEMBEDDING_SPECIALIZATION(int32_t, float16, float);\nEMBEDDING_SPECIALIZATION(int64_t, float16, float);\nEMBEDDING_SPECIALIZATION(int32_t, uint8_t, float);\nEMBEDDING_SPECIALIZATION(int64_t, uint8_t, float);\n\n#undef EMBEDDING_SPECIALIZATION\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/perfkernels/embedding_lookup.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/common.h\"\n\nnamespace caffe2 {\n\n/**\n * Embedding lookup with reduction.\n *\n * `input` of size data_size * block_size\n * `indices` of size index_size\n * `lengths` of size output_size\n * `weights` nullptr or array of size index_size\n * `out` of size output_size * block_size\n * sum(lengths[i]) == index_size\n *\n * Behavior is roughly equivalent to pseudocode:\n *\n * pos = 0\n * for (i = 0..index_size-1)\n *   for (k = 0..block_size-1)\n *     out[i*block_size + k] = 0\n *   for (j = 0..lengths[i]-1)\n *     for (k = 0..block_size-1)\n *       out[i*block_size + k] += input[indices[pos]*block_size + k] *\n *                                (weights ? weights[pos] : 1.0)\n *     pos += 1\n *   if (normalize_weights && lengths[i] > 0)\n *     for (k = 0..block_size-1)\n *       out[i*block_size + k] /= lengths[i]\n *\n */\ntemplate <typename IndexType, typename InType, typename OutType>\nvoid EmbeddingLookup(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const InType* input,\n    const IndexType* indices,\n    const int* lengths,\n    const float* weights, // optional, can be null for non-weighted sum\n    const float* scale_bias, // optional scale & bias params for uint8 input\n    bool normalize_by_lengths,\n    OutType* out);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/perfkernels/embedding_lookup_avx2.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n//// --------------------------\n//// ATTENTION:\n//// THIS CODE IS AUTOGENERATED\n//// BY hp_emblookup_codegen.py\n//// DO NOT MODIFY!!!\n//// --------------------------\n\n#include <immintrin.h>\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/types.h\"\n\nnamespace caffe2 {\n\nvoid EmbeddingLookup_int32_t_float_float__avx2_fma(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const float* input,\n    const int32_t* indices,\n    const int* lengths,\n    const float* weights,\n    const float* scale_bias,\n    bool normalize_by_lengths,\n    float* out) {\n  const int32_t prefdist_T0 = 16;\n  CAFFE_ENFORCE(scale_bias == nullptr, \"scale_bias must be nullptr\");\n  if (block_size == 128) {\n    // unrolling 16 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      __m256 vop64 = _mm256_setzero_ps();\n      __m256 vop72 = _mm256_setzero_ps();\n      __m256 vop80 = _mm256_setzero_ps();\n      __m256 vop88 = _mm256_setzero_ps();\n      __m256 vop96 = _mm256_setzero_ps();\n      __m256 vop104 = _mm256_setzero_ps();\n      __m256 vop112 = _mm256_setzero_ps();\n      __m256 vop120 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (16)), vop16);\n        _mm_prefetch((&ip_next_T0[16]), _MM_HINT_T0);\n        vop24 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (24)), vop24);\n        // skip unecassery prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (32)), vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (40)), vop40);\n        // skip unecassery prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (48)), vop48);\n        _mm_prefetch((&ip_next_T0[48]), _MM_HINT_T0);\n        vop56 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (56)), vop56);\n        // skip unecassery prefetch of (&ip_next_T0[56])\n        vop64 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (64)), vop64);\n        _mm_prefetch((&ip_next_T0[64]), _MM_HINT_T0);\n        vop72 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (72)), vop72);\n        // skip unecassery prefetch of (&ip_next_T0[72])\n        vop80 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (80)), vop80);\n        _mm_prefetch((&ip_next_T0[80]), _MM_HINT_T0);\n        vop88 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (88)), vop88);\n        // skip unecassery prefetch of (&ip_next_T0[88])\n        vop96 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (96)), vop96);\n        _mm_prefetch((&ip_next_T0[96]), _MM_HINT_T0);\n        vop104 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (104)), vop104);\n        // skip unecassery prefetch of (&ip_next_T0[104])\n        vop112 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (112)), vop112);\n        _mm_prefetch((&ip_next_T0[112]), _MM_HINT_T0);\n        vop120 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (120)), vop120);\n        // skip unecassery prefetch of (&ip_next_T0[120])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n        _mm256_storeu_ps(&op[64], vop64);\n        _mm256_storeu_ps(&op[72], vop72);\n        _mm256_storeu_ps(&op[80], vop80);\n        _mm256_storeu_ps(&op[88], vop88);\n        _mm256_storeu_ps(&op[96], vop96);\n        _mm256_storeu_ps(&op[104], vop104);\n        _mm256_storeu_ps(&op[112], vop112);\n        _mm256_storeu_ps(&op[120], vop120);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n        _mm256_storeu_ps(&op[64], _mm256_mul_ps(vop64, vlen_inv));\n        _mm256_storeu_ps(&op[72], _mm256_mul_ps(vop72, vlen_inv));\n        _mm256_storeu_ps(&op[80], _mm256_mul_ps(vop80, vlen_inv));\n        _mm256_storeu_ps(&op[88], _mm256_mul_ps(vop88, vlen_inv));\n        _mm256_storeu_ps(&op[96], _mm256_mul_ps(vop96, vlen_inv));\n        _mm256_storeu_ps(&op[104], _mm256_mul_ps(vop104, vlen_inv));\n        _mm256_storeu_ps(&op[112], _mm256_mul_ps(vop112, vlen_inv));\n        _mm256_storeu_ps(&op[120], _mm256_mul_ps(vop120, vlen_inv));\n      }\n    }\n  } else if (block_size == 64) {\n    // unrolling 8 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (16)), vop16);\n        _mm_prefetch((&ip_next_T0[16]), _MM_HINT_T0);\n        vop24 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (24)), vop24);\n        // skip unecassery prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (32)), vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (40)), vop40);\n        // skip unecassery prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (48)), vop48);\n        _mm_prefetch((&ip_next_T0[48]), _MM_HINT_T0);\n        vop56 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (56)), vop56);\n        // skip unecassery prefetch of (&ip_next_T0[56])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n      }\n    }\n  } else if (block_size == 32) {\n    // unrolling 4 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (16)), vop16);\n        _mm_prefetch((&ip_next_T0[16]), _MM_HINT_T0);\n        vop24 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (24)), vop24);\n        // skip unecassery prefetch of (&ip_next_T0[24])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n      }\n    }\n  } else if (block_size == 16) {\n    // unrolling 2 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n      }\n    }\n  } else {\n    // generic code\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      TIndex j = 0;\n      for (; j + 8 <= block_size; j += 8) {\n        _mm256_storeu_ps(op + j, _mm256_setzero_ps());\n      }\n      for (; j < block_size; j++) {\n        op[j] = 0.0f;\n      }\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j],\n              _mm256_fmadd_ps(\n                  vwgt, _mm256_loadu_ps(&ip[j]), _mm256_loadu_ps(&op[j])));\n          _mm_prefetch((&ip_next_T0[j]), _MM_HINT_T0);\n        }\n        for (; j < block_size; j++) {\n          op[j] += wgt * ip[j];\n        }\n      }\n      if (normalize_by_lengths && lengths[rangeIndex]) {\n        float len_inv = 1.0f / lengths[rangeIndex];\n        __m256 vlen_inv = _mm256_set1_ps(len_inv);\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j], _mm256_mul_ps(_mm256_loadu_ps(&op[j]), vlen_inv));\n        }\n        for (; j < block_size; j++) {\n          op[j] = len_inv * op[j];\n        }\n      }\n    }\n  }\n}\n\nvoid EmbeddingLookup_int64_t_float_float__avx2_fma(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const float* input,\n    const int64_t* indices,\n    const int* lengths,\n    const float* weights,\n    const float* scale_bias,\n    bool normalize_by_lengths,\n    float* out) {\n  const int64_t prefdist_T0 = 16;\n  CAFFE_ENFORCE(scale_bias == nullptr, \"scale_bias must be nullptr\");\n  if (block_size == 128) {\n    // unrolling 16 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      __m256 vop64 = _mm256_setzero_ps();\n      __m256 vop72 = _mm256_setzero_ps();\n      __m256 vop80 = _mm256_setzero_ps();\n      __m256 vop88 = _mm256_setzero_ps();\n      __m256 vop96 = _mm256_setzero_ps();\n      __m256 vop104 = _mm256_setzero_ps();\n      __m256 vop112 = _mm256_setzero_ps();\n      __m256 vop120 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (16)), vop16);\n        _mm_prefetch((&ip_next_T0[16]), _MM_HINT_T0);\n        vop24 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (24)), vop24);\n        // skip unecassery prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (32)), vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (40)), vop40);\n        // skip unecassery prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (48)), vop48);\n        _mm_prefetch((&ip_next_T0[48]), _MM_HINT_T0);\n        vop56 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (56)), vop56);\n        // skip unecassery prefetch of (&ip_next_T0[56])\n        vop64 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (64)), vop64);\n        _mm_prefetch((&ip_next_T0[64]), _MM_HINT_T0);\n        vop72 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (72)), vop72);\n        // skip unecassery prefetch of (&ip_next_T0[72])\n        vop80 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (80)), vop80);\n        _mm_prefetch((&ip_next_T0[80]), _MM_HINT_T0);\n        vop88 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (88)), vop88);\n        // skip unecassery prefetch of (&ip_next_T0[88])\n        vop96 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (96)), vop96);\n        _mm_prefetch((&ip_next_T0[96]), _MM_HINT_T0);\n        vop104 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (104)), vop104);\n        // skip unecassery prefetch of (&ip_next_T0[104])\n        vop112 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (112)), vop112);\n        _mm_prefetch((&ip_next_T0[112]), _MM_HINT_T0);\n        vop120 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (120)), vop120);\n        // skip unecassery prefetch of (&ip_next_T0[120])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n        _mm256_storeu_ps(&op[64], vop64);\n        _mm256_storeu_ps(&op[72], vop72);\n        _mm256_storeu_ps(&op[80], vop80);\n        _mm256_storeu_ps(&op[88], vop88);\n        _mm256_storeu_ps(&op[96], vop96);\n        _mm256_storeu_ps(&op[104], vop104);\n        _mm256_storeu_ps(&op[112], vop112);\n        _mm256_storeu_ps(&op[120], vop120);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n        _mm256_storeu_ps(&op[64], _mm256_mul_ps(vop64, vlen_inv));\n        _mm256_storeu_ps(&op[72], _mm256_mul_ps(vop72, vlen_inv));\n        _mm256_storeu_ps(&op[80], _mm256_mul_ps(vop80, vlen_inv));\n        _mm256_storeu_ps(&op[88], _mm256_mul_ps(vop88, vlen_inv));\n        _mm256_storeu_ps(&op[96], _mm256_mul_ps(vop96, vlen_inv));\n        _mm256_storeu_ps(&op[104], _mm256_mul_ps(vop104, vlen_inv));\n        _mm256_storeu_ps(&op[112], _mm256_mul_ps(vop112, vlen_inv));\n        _mm256_storeu_ps(&op[120], _mm256_mul_ps(vop120, vlen_inv));\n      }\n    }\n  } else if (block_size == 64) {\n    // unrolling 8 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (16)), vop16);\n        _mm_prefetch((&ip_next_T0[16]), _MM_HINT_T0);\n        vop24 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (24)), vop24);\n        // skip unecassery prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (32)), vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (40)), vop40);\n        // skip unecassery prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (48)), vop48);\n        _mm_prefetch((&ip_next_T0[48]), _MM_HINT_T0);\n        vop56 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (56)), vop56);\n        // skip unecassery prefetch of (&ip_next_T0[56])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n      }\n    }\n  } else if (block_size == 32) {\n    // unrolling 4 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (16)), vop16);\n        _mm_prefetch((&ip_next_T0[16]), _MM_HINT_T0);\n        vop24 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (24)), vop24);\n        // skip unecassery prefetch of (&ip_next_T0[24])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n      }\n    }\n  } else if (block_size == 16) {\n    // unrolling 2 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n      }\n    }\n  } else {\n    // generic code\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      TIndex j = 0;\n      for (; j + 8 <= block_size; j += 8) {\n        _mm256_storeu_ps(op + j, _mm256_setzero_ps());\n      }\n      for (; j < block_size; j++) {\n        op[j] = 0.0f;\n      }\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j],\n              _mm256_fmadd_ps(\n                  vwgt, _mm256_loadu_ps(&ip[j]), _mm256_loadu_ps(&op[j])));\n          _mm_prefetch((&ip_next_T0[j]), _MM_HINT_T0);\n        }\n        for (; j < block_size; j++) {\n          op[j] += wgt * ip[j];\n        }\n      }\n      if (normalize_by_lengths && lengths[rangeIndex]) {\n        float len_inv = 1.0f / lengths[rangeIndex];\n        __m256 vlen_inv = _mm256_set1_ps(len_inv);\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j], _mm256_mul_ps(_mm256_loadu_ps(&op[j]), vlen_inv));\n        }\n        for (; j < block_size; j++) {\n          op[j] = len_inv * op[j];\n        }\n      }\n    }\n  }\n}\n\nvoid EmbeddingLookup_int32_t_float16_float__avx2_fma(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const float16* input,\n    const int32_t* indices,\n    const int* lengths,\n    const float* weights,\n    const float* scale_bias,\n    bool normalize_by_lengths,\n    float* out) {\n  const int32_t prefdist_T0 = 16;\n  CAFFE_ENFORCE(scale_bias == nullptr, \"scale_bias must be nullptr\");\n  if (block_size == 128) {\n    // unrolling 16 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      __m256 vop64 = _mm256_setzero_ps();\n      __m256 vop72 = _mm256_setzero_ps();\n      __m256 vop80 = _mm256_setzero_ps();\n      __m256 vop88 = _mm256_setzero_ps();\n      __m256 vop96 = _mm256_setzero_ps();\n      __m256 vop104 = _mm256_setzero_ps();\n      __m256 vop112 = _mm256_setzero_ps();\n      __m256 vop120 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (16)))),\n            vop16);\n        // skip unecassery prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (24)))),\n            vop24);\n        // skip unecassery prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (32)))),\n            vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (40)))),\n            vop40);\n        // skip unecassery prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (48)))),\n            vop48);\n        // skip unecassery prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (56)))),\n            vop56);\n        // skip unecassery prefetch of (&ip_next_T0[56])\n        vop64 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (64)))),\n            vop64);\n        _mm_prefetch((&ip_next_T0[64]), _MM_HINT_T0);\n        vop72 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (72)))),\n            vop72);\n        // skip unecassery prefetch of (&ip_next_T0[72])\n        vop80 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (80)))),\n            vop80);\n        // skip unecassery prefetch of (&ip_next_T0[80])\n        vop88 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (88)))),\n            vop88);\n        // skip unecassery prefetch of (&ip_next_T0[88])\n        vop96 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (96)))),\n            vop96);\n        _mm_prefetch((&ip_next_T0[96]), _MM_HINT_T0);\n        vop104 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (104)))),\n            vop104);\n        // skip unecassery prefetch of (&ip_next_T0[104])\n        vop112 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (112)))),\n            vop112);\n        // skip unecassery prefetch of (&ip_next_T0[112])\n        vop120 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (120)))),\n            vop120);\n        // skip unecassery prefetch of (&ip_next_T0[120])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n        _mm256_storeu_ps(&op[64], vop64);\n        _mm256_storeu_ps(&op[72], vop72);\n        _mm256_storeu_ps(&op[80], vop80);\n        _mm256_storeu_ps(&op[88], vop88);\n        _mm256_storeu_ps(&op[96], vop96);\n        _mm256_storeu_ps(&op[104], vop104);\n        _mm256_storeu_ps(&op[112], vop112);\n        _mm256_storeu_ps(&op[120], vop120);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n        _mm256_storeu_ps(&op[64], _mm256_mul_ps(vop64, vlen_inv));\n        _mm256_storeu_ps(&op[72], _mm256_mul_ps(vop72, vlen_inv));\n        _mm256_storeu_ps(&op[80], _mm256_mul_ps(vop80, vlen_inv));\n        _mm256_storeu_ps(&op[88], _mm256_mul_ps(vop88, vlen_inv));\n        _mm256_storeu_ps(&op[96], _mm256_mul_ps(vop96, vlen_inv));\n        _mm256_storeu_ps(&op[104], _mm256_mul_ps(vop104, vlen_inv));\n        _mm256_storeu_ps(&op[112], _mm256_mul_ps(vop112, vlen_inv));\n        _mm256_storeu_ps(&op[120], _mm256_mul_ps(vop120, vlen_inv));\n      }\n    }\n  } else if (block_size == 64) {\n    // unrolling 8 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (16)))),\n            vop16);\n        // skip unecassery prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (24)))),\n            vop24);\n        // skip unecassery prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (32)))),\n            vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (40)))),\n            vop40);\n        // skip unecassery prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (48)))),\n            vop48);\n        // skip unecassery prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (56)))),\n            vop56);\n        // skip unecassery prefetch of (&ip_next_T0[56])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n      }\n    }\n  } else if (block_size == 32) {\n    // unrolling 4 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (16)))),\n            vop16);\n        // skip unecassery prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (24)))),\n            vop24);\n        // skip unecassery prefetch of (&ip_next_T0[24])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n      }\n    }\n  } else if (block_size == 16) {\n    // unrolling 2 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n      }\n    }\n  } else {\n    // generic code\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      TIndex j = 0;\n      for (; j + 8 <= block_size; j += 8) {\n        _mm256_storeu_ps(op + j, _mm256_setzero_ps());\n      }\n      for (; j < block_size; j++) {\n        op[j] = 0.0f;\n      }\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j],\n              _mm256_fmadd_ps(\n                  vwgt,\n                  _mm256_cvtph_ps(_mm_loadu_si128(\n                      reinterpret_cast<const __m128i*>(&ip[j]))),\n                  _mm256_loadu_ps(&op[j])));\n          _mm_prefetch((&ip_next_T0[j]), _MM_HINT_T0);\n        }\n        float16 vtmp1[8] CAFFE2_ALIGNED(64);\n        for (; j < block_size; j++) {\n          vtmp1[0] = ip[j];\n          __m256 vtmp2 = _mm256_cvtph_ps(*((__m128i*)vtmp1));\n          op[j] += wgt * ((float*)(&vtmp2))[0];\n        }\n      }\n      if (normalize_by_lengths && lengths[rangeIndex]) {\n        float len_inv = 1.0f / lengths[rangeIndex];\n        __m256 vlen_inv = _mm256_set1_ps(len_inv);\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j], _mm256_mul_ps(_mm256_loadu_ps(&op[j]), vlen_inv));\n        }\n        for (; j < block_size; j++) {\n          op[j] = len_inv * op[j];\n        }\n      }\n    }\n  }\n}\n\nvoid EmbeddingLookup_int64_t_float16_float__avx2_fma(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const float16* input,\n    const int64_t* indices,\n    const int* lengths,\n    const float* weights,\n    const float* scale_bias,\n    bool normalize_by_lengths,\n    float* out) {\n  const int64_t prefdist_T0 = 16;\n  CAFFE_ENFORCE(scale_bias == nullptr, \"scale_bias must be nullptr\");\n  if (block_size == 128) {\n    // unrolling 16 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      __m256 vop64 = _mm256_setzero_ps();\n      __m256 vop72 = _mm256_setzero_ps();\n      __m256 vop80 = _mm256_setzero_ps();\n      __m256 vop88 = _mm256_setzero_ps();\n      __m256 vop96 = _mm256_setzero_ps();\n      __m256 vop104 = _mm256_setzero_ps();\n      __m256 vop112 = _mm256_setzero_ps();\n      __m256 vop120 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (16)))),\n            vop16);\n        // skip unecassery prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (24)))),\n            vop24);\n        // skip unecassery prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (32)))),\n            vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (40)))),\n            vop40);\n        // skip unecassery prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (48)))),\n            vop48);\n        // skip unecassery prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (56)))),\n            vop56);\n        // skip unecassery prefetch of (&ip_next_T0[56])\n        vop64 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (64)))),\n            vop64);\n        _mm_prefetch((&ip_next_T0[64]), _MM_HINT_T0);\n        vop72 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (72)))),\n            vop72);\n        // skip unecassery prefetch of (&ip_next_T0[72])\n        vop80 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (80)))),\n            vop80);\n        // skip unecassery prefetch of (&ip_next_T0[80])\n        vop88 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (88)))),\n            vop88);\n        // skip unecassery prefetch of (&ip_next_T0[88])\n        vop96 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (96)))),\n            vop96);\n        _mm_prefetch((&ip_next_T0[96]), _MM_HINT_T0);\n        vop104 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (104)))),\n            vop104);\n        // skip unecassery prefetch of (&ip_next_T0[104])\n        vop112 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (112)))),\n            vop112);\n        // skip unecassery prefetch of (&ip_next_T0[112])\n        vop120 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (120)))),\n            vop120);\n        // skip unecassery prefetch of (&ip_next_T0[120])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n        _mm256_storeu_ps(&op[64], vop64);\n        _mm256_storeu_ps(&op[72], vop72);\n        _mm256_storeu_ps(&op[80], vop80);\n        _mm256_storeu_ps(&op[88], vop88);\n        _mm256_storeu_ps(&op[96], vop96);\n        _mm256_storeu_ps(&op[104], vop104);\n        _mm256_storeu_ps(&op[112], vop112);\n        _mm256_storeu_ps(&op[120], vop120);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n        _mm256_storeu_ps(&op[64], _mm256_mul_ps(vop64, vlen_inv));\n        _mm256_storeu_ps(&op[72], _mm256_mul_ps(vop72, vlen_inv));\n        _mm256_storeu_ps(&op[80], _mm256_mul_ps(vop80, vlen_inv));\n        _mm256_storeu_ps(&op[88], _mm256_mul_ps(vop88, vlen_inv));\n        _mm256_storeu_ps(&op[96], _mm256_mul_ps(vop96, vlen_inv));\n        _mm256_storeu_ps(&op[104], _mm256_mul_ps(vop104, vlen_inv));\n        _mm256_storeu_ps(&op[112], _mm256_mul_ps(vop112, vlen_inv));\n        _mm256_storeu_ps(&op[120], _mm256_mul_ps(vop120, vlen_inv));\n      }\n    }\n  } else if (block_size == 64) {\n    // unrolling 8 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (16)))),\n            vop16);\n        // skip unecassery prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (24)))),\n            vop24);\n        // skip unecassery prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (32)))),\n            vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (40)))),\n            vop40);\n        // skip unecassery prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (48)))),\n            vop48);\n        // skip unecassery prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (56)))),\n            vop56);\n        // skip unecassery prefetch of (&ip_next_T0[56])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n      }\n    }\n  } else if (block_size == 32) {\n    // unrolling 4 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (16)))),\n            vop16);\n        // skip unecassery prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (24)))),\n            vop24);\n        // skip unecassery prefetch of (&ip_next_T0[24])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n      }\n    }\n  } else if (block_size == 16) {\n    // unrolling 2 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unecassery prefetch of (&ip_next_T0[8])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n      }\n    }\n  } else {\n    // generic code\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      TIndex j = 0;\n      for (; j + 8 <= block_size; j += 8) {\n        _mm256_storeu_ps(op + j, _mm256_setzero_ps());\n      }\n      for (; j < block_size; j++) {\n        op[j] = 0.0f;\n      }\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j],\n              _mm256_fmadd_ps(\n                  vwgt,\n                  _mm256_cvtph_ps(_mm_loadu_si128(\n                      reinterpret_cast<const __m128i*>(&ip[j]))),\n                  _mm256_loadu_ps(&op[j])));\n          _mm_prefetch((&ip_next_T0[j]), _MM_HINT_T0);\n        }\n        float16 vtmp1[8] CAFFE2_ALIGNED(64);\n        for (; j < block_size; j++) {\n          vtmp1[0] = ip[j];\n          __m256 vtmp2 = _mm256_cvtph_ps(*((__m128i*)vtmp1));\n          op[j] += wgt * ((float*)(&vtmp2))[0];\n        }\n      }\n      if (normalize_by_lengths && lengths[rangeIndex]) {\n        float len_inv = 1.0f / lengths[rangeIndex];\n        __m256 vlen_inv = _mm256_set1_ps(len_inv);\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j], _mm256_mul_ps(_mm256_loadu_ps(&op[j]), vlen_inv));\n        }\n        for (; j < block_size; j++) {\n          op[j] = len_inv * op[j];\n        }\n      }\n    }\n  }\n}\n\nvoid EmbeddingLookup_int32_t_uint8_t_float__avx2_fma(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const uint8_t* input,\n    const int32_t* indices,\n    const int* lengths,\n    const float* weights,\n    const float* scale_bias,\n    bool normalize_by_lengths,\n    float* out) {\n  const int32_t prefdist_T0 = 16;\n  CAFFE_ENFORCE(scale_bias != nullptr, \"scale_bias must not be nullptr\");\n  if (block_size == 128) {\n    // unrolling 16 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      __m256 vop64 = _mm256_setzero_ps();\n      __m256 vop72 = _mm256_setzero_ps();\n      __m256 vop80 = _mm256_setzero_ps();\n      __m256 vop88 = _mm256_setzero_ps();\n      __m256 vop96 = _mm256_setzero_ps();\n      __m256 vop104 = _mm256_setzero_ps();\n      __m256 vop112 = _mm256_setzero_ps();\n      __m256 vop120 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        bio = wgt * scale_bias[2 * idx + 1];\n        wgt = wgt * scale_bias[2 * idx];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (16))))),\n            _mm256_add_ps(vop16, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (24))))),\n            _mm256_add_ps(vop24, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (32))))),\n            _mm256_add_ps(vop32, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[32])\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (40))))),\n            _mm256_add_ps(vop40, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (48))))),\n            _mm256_add_ps(vop48, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (56))))),\n            _mm256_add_ps(vop56, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[56])\n        vop64 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (64))))),\n            _mm256_add_ps(vop64, vbio));\n        _mm_prefetch((&ip_next_T0[64]), _MM_HINT_T0);\n        vop72 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (72))))),\n            _mm256_add_ps(vop72, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[72])\n        vop80 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (80))))),\n            _mm256_add_ps(vop80, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[80])\n        vop88 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (88))))),\n            _mm256_add_ps(vop88, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[88])\n        vop96 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (96))))),\n            _mm256_add_ps(vop96, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[96])\n        vop104 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (104))))),\n            _mm256_add_ps(vop104, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[104])\n        vop112 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (112))))),\n            _mm256_add_ps(vop112, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[112])\n        vop120 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (120))))),\n            _mm256_add_ps(vop120, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[120])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n        _mm256_storeu_ps(&op[64], vop64);\n        _mm256_storeu_ps(&op[72], vop72);\n        _mm256_storeu_ps(&op[80], vop80);\n        _mm256_storeu_ps(&op[88], vop88);\n        _mm256_storeu_ps(&op[96], vop96);\n        _mm256_storeu_ps(&op[104], vop104);\n        _mm256_storeu_ps(&op[112], vop112);\n        _mm256_storeu_ps(&op[120], vop120);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n        _mm256_storeu_ps(&op[64], _mm256_mul_ps(vop64, vlen_inv));\n        _mm256_storeu_ps(&op[72], _mm256_mul_ps(vop72, vlen_inv));\n        _mm256_storeu_ps(&op[80], _mm256_mul_ps(vop80, vlen_inv));\n        _mm256_storeu_ps(&op[88], _mm256_mul_ps(vop88, vlen_inv));\n        _mm256_storeu_ps(&op[96], _mm256_mul_ps(vop96, vlen_inv));\n        _mm256_storeu_ps(&op[104], _mm256_mul_ps(vop104, vlen_inv));\n        _mm256_storeu_ps(&op[112], _mm256_mul_ps(vop112, vlen_inv));\n        _mm256_storeu_ps(&op[120], _mm256_mul_ps(vop120, vlen_inv));\n      }\n    }\n  } else if (block_size == 64) {\n    // unrolling 8 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        bio = wgt * scale_bias[2 * idx + 1];\n        wgt = wgt * scale_bias[2 * idx];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (16))))),\n            _mm256_add_ps(vop16, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (24))))),\n            _mm256_add_ps(vop24, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (32))))),\n            _mm256_add_ps(vop32, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[32])\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (40))))),\n            _mm256_add_ps(vop40, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (48))))),\n            _mm256_add_ps(vop48, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (56))))),\n            _mm256_add_ps(vop56, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[56])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n      }\n    }\n  } else if (block_size == 32) {\n    // unrolling 4 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        bio = wgt * scale_bias[2 * idx + 1];\n        wgt = wgt * scale_bias[2 * idx];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (16))))),\n            _mm256_add_ps(vop16, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (24))))),\n            _mm256_add_ps(vop24, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[24])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n      }\n    }\n  } else if (block_size == 16) {\n    // unrolling 2 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        bio = wgt * scale_bias[2 * idx + 1];\n        wgt = wgt * scale_bias[2 * idx];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[8])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n      }\n    }\n  } else {\n    // generic code\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      TIndex j = 0;\n      for (; j + 8 <= block_size; j += 8) {\n        _mm256_storeu_ps(op + j, _mm256_setzero_ps());\n      }\n      for (; j < block_size; j++) {\n        op[j] = 0.0f;\n      }\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        assert(scale_bias);\n        bio = wgt * scale_bias[2 * idx + 1];\n        wgt = wgt * scale_bias[2 * idx];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j],\n              _mm256_fmadd_ps(\n                  vwgt,\n                  _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(_mm_loadl_epi64(\n                      reinterpret_cast<const __m128i*>(&ip[j])))),\n                  _mm256_add_ps(_mm256_loadu_ps(&op[j]), vbio)));\n          _mm_prefetch((&ip_next_T0[j]), _MM_HINT_T0);\n        }\n        for (; j < block_size; j++) {\n          op[j] += wgt * ((float)ip[j]) + bio;\n        }\n      }\n      if (normalize_by_lengths && lengths[rangeIndex]) {\n        float len_inv = 1.0f / lengths[rangeIndex];\n        __m256 vlen_inv = _mm256_set1_ps(len_inv);\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j], _mm256_mul_ps(_mm256_loadu_ps(&op[j]), vlen_inv));\n        }\n        for (; j < block_size; j++) {\n          op[j] = len_inv * op[j];\n        }\n      }\n    }\n  }\n}\n\nvoid EmbeddingLookup_int64_t_uint8_t_float__avx2_fma(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const uint8_t* input,\n    const int64_t* indices,\n    const int* lengths,\n    const float* weights,\n    const float* scale_bias,\n    bool normalize_by_lengths,\n    float* out) {\n  const int64_t prefdist_T0 = 16;\n  CAFFE_ENFORCE(scale_bias != nullptr, \"scale_bias must not be nullptr\");\n  if (block_size == 128) {\n    // unrolling 16 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      __m256 vop64 = _mm256_setzero_ps();\n      __m256 vop72 = _mm256_setzero_ps();\n      __m256 vop80 = _mm256_setzero_ps();\n      __m256 vop88 = _mm256_setzero_ps();\n      __m256 vop96 = _mm256_setzero_ps();\n      __m256 vop104 = _mm256_setzero_ps();\n      __m256 vop112 = _mm256_setzero_ps();\n      __m256 vop120 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        bio = wgt * scale_bias[2 * idx + 1];\n        wgt = wgt * scale_bias[2 * idx];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (16))))),\n            _mm256_add_ps(vop16, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (24))))),\n            _mm256_add_ps(vop24, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (32))))),\n            _mm256_add_ps(vop32, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[32])\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (40))))),\n            _mm256_add_ps(vop40, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (48))))),\n            _mm256_add_ps(vop48, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (56))))),\n            _mm256_add_ps(vop56, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[56])\n        vop64 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (64))))),\n            _mm256_add_ps(vop64, vbio));\n        _mm_prefetch((&ip_next_T0[64]), _MM_HINT_T0);\n        vop72 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (72))))),\n            _mm256_add_ps(vop72, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[72])\n        vop80 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (80))))),\n            _mm256_add_ps(vop80, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[80])\n        vop88 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (88))))),\n            _mm256_add_ps(vop88, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[88])\n        vop96 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (96))))),\n            _mm256_add_ps(vop96, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[96])\n        vop104 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (104))))),\n            _mm256_add_ps(vop104, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[104])\n        vop112 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (112))))),\n            _mm256_add_ps(vop112, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[112])\n        vop120 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (120))))),\n            _mm256_add_ps(vop120, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[120])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n        _mm256_storeu_ps(&op[64], vop64);\n        _mm256_storeu_ps(&op[72], vop72);\n        _mm256_storeu_ps(&op[80], vop80);\n        _mm256_storeu_ps(&op[88], vop88);\n        _mm256_storeu_ps(&op[96], vop96);\n        _mm256_storeu_ps(&op[104], vop104);\n        _mm256_storeu_ps(&op[112], vop112);\n        _mm256_storeu_ps(&op[120], vop120);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n        _mm256_storeu_ps(&op[64], _mm256_mul_ps(vop64, vlen_inv));\n        _mm256_storeu_ps(&op[72], _mm256_mul_ps(vop72, vlen_inv));\n        _mm256_storeu_ps(&op[80], _mm256_mul_ps(vop80, vlen_inv));\n        _mm256_storeu_ps(&op[88], _mm256_mul_ps(vop88, vlen_inv));\n        _mm256_storeu_ps(&op[96], _mm256_mul_ps(vop96, vlen_inv));\n        _mm256_storeu_ps(&op[104], _mm256_mul_ps(vop104, vlen_inv));\n        _mm256_storeu_ps(&op[112], _mm256_mul_ps(vop112, vlen_inv));\n        _mm256_storeu_ps(&op[120], _mm256_mul_ps(vop120, vlen_inv));\n      }\n    }\n  } else if (block_size == 64) {\n    // unrolling 8 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        bio = wgt * scale_bias[2 * idx + 1];\n        wgt = wgt * scale_bias[2 * idx];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (16))))),\n            _mm256_add_ps(vop16, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (24))))),\n            _mm256_add_ps(vop24, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (32))))),\n            _mm256_add_ps(vop32, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[32])\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (40))))),\n            _mm256_add_ps(vop40, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (48))))),\n            _mm256_add_ps(vop48, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (56))))),\n            _mm256_add_ps(vop56, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[56])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n      }\n    }\n  } else if (block_size == 32) {\n    // unrolling 4 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        bio = wgt * scale_bias[2 * idx + 1];\n        wgt = wgt * scale_bias[2 * idx];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (16))))),\n            _mm256_add_ps(vop16, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (24))))),\n            _mm256_add_ps(vop24, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[24])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n      }\n    }\n  } else if (block_size == 16) {\n    // unrolling 2 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        bio = wgt * scale_bias[2 * idx + 1];\n        wgt = wgt * scale_bias[2 * idx];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unecassery prefetch of (&ip_next_T0[8])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n      }\n    }\n  } else {\n    // generic code\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      TIndex j = 0;\n      for (; j + 8 <= block_size; j += 8) {\n        _mm256_storeu_ps(op + j, _mm256_setzero_ps());\n      }\n      for (; j < block_size; j++) {\n        op[j] = 0.0f;\n      }\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        assert(scale_bias);\n        bio = wgt * scale_bias[2 * idx + 1];\n        wgt = wgt * scale_bias[2 * idx];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * block_size];\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j],\n              _mm256_fmadd_ps(\n                  vwgt,\n                  _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(_mm_loadl_epi64(\n                      reinterpret_cast<const __m128i*>(&ip[j])))),\n                  _mm256_add_ps(_mm256_loadu_ps(&op[j]), vbio)));\n          _mm_prefetch((&ip_next_T0[j]), _MM_HINT_T0);\n        }\n        for (; j < block_size; j++) {\n          op[j] += wgt * ((float)ip[j]) + bio;\n        }\n      }\n      if (normalize_by_lengths && lengths[rangeIndex]) {\n        float len_inv = 1.0f / lengths[rangeIndex];\n        __m256 vlen_inv = _mm256_set1_ps(len_inv);\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j], _mm256_mul_ps(_mm256_loadu_ps(&op[j]), vlen_inv));\n        }\n        for (; j < block_size; j++) {\n          op[j] = len_inv * op[j];\n        }\n      }\n    }\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/perfkernels/embedding_lookup_fused_8bit_rowwise_avx2.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n//// --------------------------\n//// ATTENTION:\n//// THIS CODE IS AUTOGENERATED\n//// BY caffe2/caffe2/perfkernels/hp_emblookup_codegen.py\n//// DO NOT MODIFY!!!\n//// --------------------------\n\n#include <caffe2/core/common.h>\n#include <caffe2/core/types.h>\n#include <immintrin.h>\n\nnamespace caffe2 {\n\nvoid Fused8BitRowwiseEmbeddingLookup_int32_t_float_float__avx2_fma(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const float* input,\n    const int32_t* indices,\n    const int* lengths,\n    const float* weights,\n    bool normalize_by_lengths,\n    float* out) {\n  const int32_t prefdist_T0 = 16;\n  const int32_t fused_block_size = block_size + 2;\n  if (block_size == 128) {\n    // unrolling 16 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      __m256 vop64 = _mm256_setzero_ps();\n      __m256 vop72 = _mm256_setzero_ps();\n      __m256 vop80 = _mm256_setzero_ps();\n      __m256 vop88 = _mm256_setzero_ps();\n      __m256 vop96 = _mm256_setzero_ps();\n      __m256 vop104 = _mm256_setzero_ps();\n      __m256 vop112 = _mm256_setzero_ps();\n      __m256 vop120 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (16)), vop16);\n        _mm_prefetch((&ip_next_T0[16]), _MM_HINT_T0);\n        vop24 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (24)), vop24);\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (32)), vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (40)), vop40);\n        // skip unnecessary prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (48)), vop48);\n        _mm_prefetch((&ip_next_T0[48]), _MM_HINT_T0);\n        vop56 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (56)), vop56);\n        // skip unnecessary prefetch of (&ip_next_T0[56])\n        vop64 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (64)), vop64);\n        _mm_prefetch((&ip_next_T0[64]), _MM_HINT_T0);\n        vop72 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (72)), vop72);\n        // skip unnecessary prefetch of (&ip_next_T0[72])\n        vop80 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (80)), vop80);\n        _mm_prefetch((&ip_next_T0[80]), _MM_HINT_T0);\n        vop88 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (88)), vop88);\n        // skip unnecessary prefetch of (&ip_next_T0[88])\n        vop96 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (96)), vop96);\n        _mm_prefetch((&ip_next_T0[96]), _MM_HINT_T0);\n        vop104 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (104)), vop104);\n        // skip unnecessary prefetch of (&ip_next_T0[104])\n        vop112 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (112)), vop112);\n        _mm_prefetch((&ip_next_T0[112]), _MM_HINT_T0);\n        vop120 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (120)), vop120);\n        // skip unnecessary prefetch of (&ip_next_T0[120])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n        _mm256_storeu_ps(&op[64], vop64);\n        _mm256_storeu_ps(&op[72], vop72);\n        _mm256_storeu_ps(&op[80], vop80);\n        _mm256_storeu_ps(&op[88], vop88);\n        _mm256_storeu_ps(&op[96], vop96);\n        _mm256_storeu_ps(&op[104], vop104);\n        _mm256_storeu_ps(&op[112], vop112);\n        _mm256_storeu_ps(&op[120], vop120);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n        _mm256_storeu_ps(&op[64], _mm256_mul_ps(vop64, vlen_inv));\n        _mm256_storeu_ps(&op[72], _mm256_mul_ps(vop72, vlen_inv));\n        _mm256_storeu_ps(&op[80], _mm256_mul_ps(vop80, vlen_inv));\n        _mm256_storeu_ps(&op[88], _mm256_mul_ps(vop88, vlen_inv));\n        _mm256_storeu_ps(&op[96], _mm256_mul_ps(vop96, vlen_inv));\n        _mm256_storeu_ps(&op[104], _mm256_mul_ps(vop104, vlen_inv));\n        _mm256_storeu_ps(&op[112], _mm256_mul_ps(vop112, vlen_inv));\n        _mm256_storeu_ps(&op[120], _mm256_mul_ps(vop120, vlen_inv));\n      }\n    }\n  } else if (block_size == 64) {\n    // unrolling 8 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (16)), vop16);\n        _mm_prefetch((&ip_next_T0[16]), _MM_HINT_T0);\n        vop24 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (24)), vop24);\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (32)), vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (40)), vop40);\n        // skip unnecessary prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (48)), vop48);\n        _mm_prefetch((&ip_next_T0[48]), _MM_HINT_T0);\n        vop56 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (56)), vop56);\n        // skip unnecessary prefetch of (&ip_next_T0[56])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n      }\n    }\n  } else if (block_size == 32) {\n    // unrolling 4 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (16)), vop16);\n        _mm_prefetch((&ip_next_T0[16]), _MM_HINT_T0);\n        vop24 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (24)), vop24);\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n      }\n    }\n  } else if (block_size == 16) {\n    // unrolling 2 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n      }\n    }\n  } else {\n    // generic code\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      TIndex j = 0;\n      for (; j + 8 <= block_size; j += 8) {\n        _mm256_storeu_ps(op + j, _mm256_setzero_ps());\n      }\n      for (; j < block_size; j++) {\n        op[j] = 0.0f;\n      }\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j],\n              _mm256_fmadd_ps(\n                  vwgt, _mm256_loadu_ps(&ip[j]), _mm256_loadu_ps(&op[j])));\n          _mm_prefetch((&ip_next_T0[j]), _MM_HINT_T0);\n        }\n        for (; j < block_size; j++) {\n          op[j] += wgt * ip[j];\n        }\n      }\n      if (normalize_by_lengths && lengths[rangeIndex]) {\n        float len_inv = 1.0f / lengths[rangeIndex];\n        __m256 vlen_inv = _mm256_set1_ps(len_inv);\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j], _mm256_mul_ps(_mm256_loadu_ps(&op[j]), vlen_inv));\n        }\n        for (; j < block_size; j++) {\n          op[j] = len_inv * op[j];\n        }\n      }\n    }\n  }\n}\n\nvoid Fused8BitRowwiseEmbeddingLookup_int64_t_float_float__avx2_fma(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const float* input,\n    const int64_t* indices,\n    const int* lengths,\n    const float* weights,\n    bool normalize_by_lengths,\n    float* out) {\n  const int64_t prefdist_T0 = 16;\n  const int64_t fused_block_size = block_size + 2;\n  if (block_size == 128) {\n    // unrolling 16 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      __m256 vop64 = _mm256_setzero_ps();\n      __m256 vop72 = _mm256_setzero_ps();\n      __m256 vop80 = _mm256_setzero_ps();\n      __m256 vop88 = _mm256_setzero_ps();\n      __m256 vop96 = _mm256_setzero_ps();\n      __m256 vop104 = _mm256_setzero_ps();\n      __m256 vop112 = _mm256_setzero_ps();\n      __m256 vop120 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (16)), vop16);\n        _mm_prefetch((&ip_next_T0[16]), _MM_HINT_T0);\n        vop24 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (24)), vop24);\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (32)), vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (40)), vop40);\n        // skip unnecessary prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (48)), vop48);\n        _mm_prefetch((&ip_next_T0[48]), _MM_HINT_T0);\n        vop56 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (56)), vop56);\n        // skip unnecessary prefetch of (&ip_next_T0[56])\n        vop64 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (64)), vop64);\n        _mm_prefetch((&ip_next_T0[64]), _MM_HINT_T0);\n        vop72 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (72)), vop72);\n        // skip unnecessary prefetch of (&ip_next_T0[72])\n        vop80 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (80)), vop80);\n        _mm_prefetch((&ip_next_T0[80]), _MM_HINT_T0);\n        vop88 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (88)), vop88);\n        // skip unnecessary prefetch of (&ip_next_T0[88])\n        vop96 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (96)), vop96);\n        _mm_prefetch((&ip_next_T0[96]), _MM_HINT_T0);\n        vop104 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (104)), vop104);\n        // skip unnecessary prefetch of (&ip_next_T0[104])\n        vop112 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (112)), vop112);\n        _mm_prefetch((&ip_next_T0[112]), _MM_HINT_T0);\n        vop120 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (120)), vop120);\n        // skip unnecessary prefetch of (&ip_next_T0[120])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n        _mm256_storeu_ps(&op[64], vop64);\n        _mm256_storeu_ps(&op[72], vop72);\n        _mm256_storeu_ps(&op[80], vop80);\n        _mm256_storeu_ps(&op[88], vop88);\n        _mm256_storeu_ps(&op[96], vop96);\n        _mm256_storeu_ps(&op[104], vop104);\n        _mm256_storeu_ps(&op[112], vop112);\n        _mm256_storeu_ps(&op[120], vop120);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n        _mm256_storeu_ps(&op[64], _mm256_mul_ps(vop64, vlen_inv));\n        _mm256_storeu_ps(&op[72], _mm256_mul_ps(vop72, vlen_inv));\n        _mm256_storeu_ps(&op[80], _mm256_mul_ps(vop80, vlen_inv));\n        _mm256_storeu_ps(&op[88], _mm256_mul_ps(vop88, vlen_inv));\n        _mm256_storeu_ps(&op[96], _mm256_mul_ps(vop96, vlen_inv));\n        _mm256_storeu_ps(&op[104], _mm256_mul_ps(vop104, vlen_inv));\n        _mm256_storeu_ps(&op[112], _mm256_mul_ps(vop112, vlen_inv));\n        _mm256_storeu_ps(&op[120], _mm256_mul_ps(vop120, vlen_inv));\n      }\n    }\n  } else if (block_size == 64) {\n    // unrolling 8 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (16)), vop16);\n        _mm_prefetch((&ip_next_T0[16]), _MM_HINT_T0);\n        vop24 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (24)), vop24);\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (32)), vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (40)), vop40);\n        // skip unnecessary prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (48)), vop48);\n        _mm_prefetch((&ip_next_T0[48]), _MM_HINT_T0);\n        vop56 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (56)), vop56);\n        // skip unnecessary prefetch of (&ip_next_T0[56])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n      }\n    }\n  } else if (block_size == 32) {\n    // unrolling 4 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (16)), vop16);\n        _mm_prefetch((&ip_next_T0[16]), _MM_HINT_T0);\n        vop24 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (24)), vop24);\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n      }\n    }\n  } else if (block_size == 16) {\n    // unrolling 2 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (0)), vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(vwgt, _mm256_loadu_ps(ip + (8)), vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n      }\n    }\n  } else {\n    // generic code\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      TIndex j = 0;\n      for (; j + 8 <= block_size; j += 8) {\n        _mm256_storeu_ps(op + j, _mm256_setzero_ps());\n      }\n      for (; j < block_size; j++) {\n        op[j] = 0.0f;\n      }\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j],\n              _mm256_fmadd_ps(\n                  vwgt, _mm256_loadu_ps(&ip[j]), _mm256_loadu_ps(&op[j])));\n          _mm_prefetch((&ip_next_T0[j]), _MM_HINT_T0);\n        }\n        for (; j < block_size; j++) {\n          op[j] += wgt * ip[j];\n        }\n      }\n      if (normalize_by_lengths && lengths[rangeIndex]) {\n        float len_inv = 1.0f / lengths[rangeIndex];\n        __m256 vlen_inv = _mm256_set1_ps(len_inv);\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j], _mm256_mul_ps(_mm256_loadu_ps(&op[j]), vlen_inv));\n        }\n        for (; j < block_size; j++) {\n          op[j] = len_inv * op[j];\n        }\n      }\n    }\n  }\n}\n\nvoid Fused8BitRowwiseEmbeddingLookup_int32_t_float16_float__avx2_fma(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const float16* input,\n    const int32_t* indices,\n    const int* lengths,\n    const float* weights,\n    bool normalize_by_lengths,\n    float* out) {\n  const int32_t prefdist_T0 = 16;\n  const int32_t fused_block_size = block_size + 4;\n  if (block_size == 128) {\n    // unrolling 16 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      __m256 vop64 = _mm256_setzero_ps();\n      __m256 vop72 = _mm256_setzero_ps();\n      __m256 vop80 = _mm256_setzero_ps();\n      __m256 vop88 = _mm256_setzero_ps();\n      __m256 vop96 = _mm256_setzero_ps();\n      __m256 vop104 = _mm256_setzero_ps();\n      __m256 vop112 = _mm256_setzero_ps();\n      __m256 vop120 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (16)))),\n            vop16);\n        // skip unnecessary prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (24)))),\n            vop24);\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (32)))),\n            vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (40)))),\n            vop40);\n        // skip unnecessary prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (48)))),\n            vop48);\n        // skip unnecessary prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (56)))),\n            vop56);\n        // skip unnecessary prefetch of (&ip_next_T0[56])\n        vop64 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (64)))),\n            vop64);\n        _mm_prefetch((&ip_next_T0[64]), _MM_HINT_T0);\n        vop72 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (72)))),\n            vop72);\n        // skip unnecessary prefetch of (&ip_next_T0[72])\n        vop80 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (80)))),\n            vop80);\n        // skip unnecessary prefetch of (&ip_next_T0[80])\n        vop88 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (88)))),\n            vop88);\n        // skip unnecessary prefetch of (&ip_next_T0[88])\n        vop96 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (96)))),\n            vop96);\n        _mm_prefetch((&ip_next_T0[96]), _MM_HINT_T0);\n        vop104 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (104)))),\n            vop104);\n        // skip unnecessary prefetch of (&ip_next_T0[104])\n        vop112 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (112)))),\n            vop112);\n        // skip unnecessary prefetch of (&ip_next_T0[112])\n        vop120 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (120)))),\n            vop120);\n        // skip unnecessary prefetch of (&ip_next_T0[120])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n        _mm256_storeu_ps(&op[64], vop64);\n        _mm256_storeu_ps(&op[72], vop72);\n        _mm256_storeu_ps(&op[80], vop80);\n        _mm256_storeu_ps(&op[88], vop88);\n        _mm256_storeu_ps(&op[96], vop96);\n        _mm256_storeu_ps(&op[104], vop104);\n        _mm256_storeu_ps(&op[112], vop112);\n        _mm256_storeu_ps(&op[120], vop120);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n        _mm256_storeu_ps(&op[64], _mm256_mul_ps(vop64, vlen_inv));\n        _mm256_storeu_ps(&op[72], _mm256_mul_ps(vop72, vlen_inv));\n        _mm256_storeu_ps(&op[80], _mm256_mul_ps(vop80, vlen_inv));\n        _mm256_storeu_ps(&op[88], _mm256_mul_ps(vop88, vlen_inv));\n        _mm256_storeu_ps(&op[96], _mm256_mul_ps(vop96, vlen_inv));\n        _mm256_storeu_ps(&op[104], _mm256_mul_ps(vop104, vlen_inv));\n        _mm256_storeu_ps(&op[112], _mm256_mul_ps(vop112, vlen_inv));\n        _mm256_storeu_ps(&op[120], _mm256_mul_ps(vop120, vlen_inv));\n      }\n    }\n  } else if (block_size == 64) {\n    // unrolling 8 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (16)))),\n            vop16);\n        // skip unnecessary prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (24)))),\n            vop24);\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (32)))),\n            vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (40)))),\n            vop40);\n        // skip unnecessary prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (48)))),\n            vop48);\n        // skip unnecessary prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (56)))),\n            vop56);\n        // skip unnecessary prefetch of (&ip_next_T0[56])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n      }\n    }\n  } else if (block_size == 32) {\n    // unrolling 4 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (16)))),\n            vop16);\n        // skip unnecessary prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (24)))),\n            vop24);\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n      }\n    }\n  } else if (block_size == 16) {\n    // unrolling 2 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n      }\n    }\n  } else {\n    // generic code\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      TIndex j = 0;\n      for (; j + 8 <= block_size; j += 8) {\n        _mm256_storeu_ps(op + j, _mm256_setzero_ps());\n      }\n      for (; j < block_size; j++) {\n        op[j] = 0.0f;\n      }\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j],\n              _mm256_fmadd_ps(\n                  vwgt,\n                  _mm256_cvtph_ps(_mm_loadu_si128(\n                      reinterpret_cast<const __m128i*>(&ip[j]))),\n                  _mm256_loadu_ps(&op[j])));\n          _mm_prefetch((&ip_next_T0[j]), _MM_HINT_T0);\n        }\n        float16 vtmp1[8] CAFFE2_ALIGNED(64);\n        for (; j < block_size; j++) {\n          vtmp1[0] = ip[j];\n          __m256 vtmp2 = _mm256_cvtph_ps(*((__m128i*)vtmp1));\n          op[j] += wgt * ((float*)(&vtmp2))[0];\n        }\n      }\n      if (normalize_by_lengths && lengths[rangeIndex]) {\n        float len_inv = 1.0f / lengths[rangeIndex];\n        __m256 vlen_inv = _mm256_set1_ps(len_inv);\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j], _mm256_mul_ps(_mm256_loadu_ps(&op[j]), vlen_inv));\n        }\n        for (; j < block_size; j++) {\n          op[j] = len_inv * op[j];\n        }\n      }\n    }\n  }\n}\n\nvoid Fused8BitRowwiseEmbeddingLookup_int64_t_float16_float__avx2_fma(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const float16* input,\n    const int64_t* indices,\n    const int* lengths,\n    const float* weights,\n    bool normalize_by_lengths,\n    float* out) {\n  const int64_t prefdist_T0 = 16;\n  const int64_t fused_block_size = block_size + 4;\n  if (block_size == 128) {\n    // unrolling 16 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      __m256 vop64 = _mm256_setzero_ps();\n      __m256 vop72 = _mm256_setzero_ps();\n      __m256 vop80 = _mm256_setzero_ps();\n      __m256 vop88 = _mm256_setzero_ps();\n      __m256 vop96 = _mm256_setzero_ps();\n      __m256 vop104 = _mm256_setzero_ps();\n      __m256 vop112 = _mm256_setzero_ps();\n      __m256 vop120 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (16)))),\n            vop16);\n        // skip unnecessary prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (24)))),\n            vop24);\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (32)))),\n            vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (40)))),\n            vop40);\n        // skip unnecessary prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (48)))),\n            vop48);\n        // skip unnecessary prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (56)))),\n            vop56);\n        // skip unnecessary prefetch of (&ip_next_T0[56])\n        vop64 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (64)))),\n            vop64);\n        _mm_prefetch((&ip_next_T0[64]), _MM_HINT_T0);\n        vop72 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (72)))),\n            vop72);\n        // skip unnecessary prefetch of (&ip_next_T0[72])\n        vop80 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (80)))),\n            vop80);\n        // skip unnecessary prefetch of (&ip_next_T0[80])\n        vop88 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (88)))),\n            vop88);\n        // skip unnecessary prefetch of (&ip_next_T0[88])\n        vop96 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (96)))),\n            vop96);\n        _mm_prefetch((&ip_next_T0[96]), _MM_HINT_T0);\n        vop104 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (104)))),\n            vop104);\n        // skip unnecessary prefetch of (&ip_next_T0[104])\n        vop112 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (112)))),\n            vop112);\n        // skip unnecessary prefetch of (&ip_next_T0[112])\n        vop120 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (120)))),\n            vop120);\n        // skip unnecessary prefetch of (&ip_next_T0[120])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n        _mm256_storeu_ps(&op[64], vop64);\n        _mm256_storeu_ps(&op[72], vop72);\n        _mm256_storeu_ps(&op[80], vop80);\n        _mm256_storeu_ps(&op[88], vop88);\n        _mm256_storeu_ps(&op[96], vop96);\n        _mm256_storeu_ps(&op[104], vop104);\n        _mm256_storeu_ps(&op[112], vop112);\n        _mm256_storeu_ps(&op[120], vop120);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n        _mm256_storeu_ps(&op[64], _mm256_mul_ps(vop64, vlen_inv));\n        _mm256_storeu_ps(&op[72], _mm256_mul_ps(vop72, vlen_inv));\n        _mm256_storeu_ps(&op[80], _mm256_mul_ps(vop80, vlen_inv));\n        _mm256_storeu_ps(&op[88], _mm256_mul_ps(vop88, vlen_inv));\n        _mm256_storeu_ps(&op[96], _mm256_mul_ps(vop96, vlen_inv));\n        _mm256_storeu_ps(&op[104], _mm256_mul_ps(vop104, vlen_inv));\n        _mm256_storeu_ps(&op[112], _mm256_mul_ps(vop112, vlen_inv));\n        _mm256_storeu_ps(&op[120], _mm256_mul_ps(vop120, vlen_inv));\n      }\n    }\n  } else if (block_size == 64) {\n    // unrolling 8 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (16)))),\n            vop16);\n        // skip unnecessary prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (24)))),\n            vop24);\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (32)))),\n            vop32);\n        _mm_prefetch((&ip_next_T0[32]), _MM_HINT_T0);\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (40)))),\n            vop40);\n        // skip unnecessary prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (48)))),\n            vop48);\n        // skip unnecessary prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (56)))),\n            vop56);\n        // skip unnecessary prefetch of (&ip_next_T0[56])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n      }\n    }\n  } else if (block_size == 32) {\n    // unrolling 4 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (16)))),\n            vop16);\n        // skip unnecessary prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (24)))),\n            vop24);\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n      }\n    }\n  } else if (block_size == 16) {\n    // unrolling 2 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (0)))),\n            vop0);\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtph_ps(\n                _mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (8)))),\n            vop8);\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n      }\n    }\n  } else {\n    // generic code\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      TIndex j = 0;\n      for (; j + 8 <= block_size; j += 8) {\n        _mm256_storeu_ps(op + j, _mm256_setzero_ps());\n      }\n      for (; j < block_size; j++) {\n        op[j] = 0.0f;\n      }\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const float16* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const float16* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j],\n              _mm256_fmadd_ps(\n                  vwgt,\n                  _mm256_cvtph_ps(_mm_loadu_si128(\n                      reinterpret_cast<const __m128i*>(&ip[j]))),\n                  _mm256_loadu_ps(&op[j])));\n          _mm_prefetch((&ip_next_T0[j]), _MM_HINT_T0);\n        }\n        float16 vtmp1[8] CAFFE2_ALIGNED(64);\n        for (; j < block_size; j++) {\n          vtmp1[0] = ip[j];\n          __m256 vtmp2 = _mm256_cvtph_ps(*((__m128i*)vtmp1));\n          op[j] += wgt * ((float*)(&vtmp2))[0];\n        }\n      }\n      if (normalize_by_lengths && lengths[rangeIndex]) {\n        float len_inv = 1.0f / lengths[rangeIndex];\n        __m256 vlen_inv = _mm256_set1_ps(len_inv);\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j], _mm256_mul_ps(_mm256_loadu_ps(&op[j]), vlen_inv));\n        }\n        for (; j < block_size; j++) {\n          op[j] = len_inv * op[j];\n        }\n      }\n    }\n  }\n}\n\nvoid Fused8BitRowwiseEmbeddingLookup_int32_t_uint8_t_float__avx2_fma(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const uint8_t* input,\n    const int32_t* indices,\n    const int* lengths,\n    const float* weights,\n    bool normalize_by_lengths,\n    float* out) {\n  const int32_t prefdist_T0 = 16;\n  const int32_t fused_block_size = block_size + 8;\n  if (block_size == 128) {\n    // unrolling 16 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      __m256 vop64 = _mm256_setzero_ps();\n      __m256 vop72 = _mm256_setzero_ps();\n      __m256 vop80 = _mm256_setzero_ps();\n      __m256 vop88 = _mm256_setzero_ps();\n      __m256 vop96 = _mm256_setzero_ps();\n      __m256 vop104 = _mm256_setzero_ps();\n      __m256 vop112 = _mm256_setzero_ps();\n      __m256 vop120 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        const float* scale_bias = reinterpret_cast<const float*>(\n            &input[idx * fused_block_size + block_size]);\n        bio = wgt * scale_bias[1];\n        wgt = wgt * scale_bias[0];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (16))))),\n            _mm256_add_ps(vop16, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (24))))),\n            _mm256_add_ps(vop24, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (32))))),\n            _mm256_add_ps(vop32, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[32])\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (40))))),\n            _mm256_add_ps(vop40, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (48))))),\n            _mm256_add_ps(vop48, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (56))))),\n            _mm256_add_ps(vop56, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[56])\n        vop64 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (64))))),\n            _mm256_add_ps(vop64, vbio));\n        _mm_prefetch((&ip_next_T0[64]), _MM_HINT_T0);\n        vop72 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (72))))),\n            _mm256_add_ps(vop72, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[72])\n        vop80 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (80))))),\n            _mm256_add_ps(vop80, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[80])\n        vop88 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (88))))),\n            _mm256_add_ps(vop88, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[88])\n        vop96 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (96))))),\n            _mm256_add_ps(vop96, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[96])\n        vop104 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (104))))),\n            _mm256_add_ps(vop104, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[104])\n        vop112 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (112))))),\n            _mm256_add_ps(vop112, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[112])\n        vop120 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (120))))),\n            _mm256_add_ps(vop120, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[120])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n        _mm256_storeu_ps(&op[64], vop64);\n        _mm256_storeu_ps(&op[72], vop72);\n        _mm256_storeu_ps(&op[80], vop80);\n        _mm256_storeu_ps(&op[88], vop88);\n        _mm256_storeu_ps(&op[96], vop96);\n        _mm256_storeu_ps(&op[104], vop104);\n        _mm256_storeu_ps(&op[112], vop112);\n        _mm256_storeu_ps(&op[120], vop120);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n        _mm256_storeu_ps(&op[64], _mm256_mul_ps(vop64, vlen_inv));\n        _mm256_storeu_ps(&op[72], _mm256_mul_ps(vop72, vlen_inv));\n        _mm256_storeu_ps(&op[80], _mm256_mul_ps(vop80, vlen_inv));\n        _mm256_storeu_ps(&op[88], _mm256_mul_ps(vop88, vlen_inv));\n        _mm256_storeu_ps(&op[96], _mm256_mul_ps(vop96, vlen_inv));\n        _mm256_storeu_ps(&op[104], _mm256_mul_ps(vop104, vlen_inv));\n        _mm256_storeu_ps(&op[112], _mm256_mul_ps(vop112, vlen_inv));\n        _mm256_storeu_ps(&op[120], _mm256_mul_ps(vop120, vlen_inv));\n      }\n    }\n  } else if (block_size == 64) {\n    // unrolling 8 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        const float* scale_bias = reinterpret_cast<const float*>(\n            &input[idx * fused_block_size + block_size]);\n        bio = wgt * scale_bias[1];\n        wgt = wgt * scale_bias[0];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (16))))),\n            _mm256_add_ps(vop16, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (24))))),\n            _mm256_add_ps(vop24, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (32))))),\n            _mm256_add_ps(vop32, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[32])\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (40))))),\n            _mm256_add_ps(vop40, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (48))))),\n            _mm256_add_ps(vop48, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (56))))),\n            _mm256_add_ps(vop56, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[56])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n      }\n    }\n  } else if (block_size == 32) {\n    // unrolling 4 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        const float* scale_bias = reinterpret_cast<const float*>(\n            &input[idx * fused_block_size + block_size]);\n        bio = wgt * scale_bias[1];\n        wgt = wgt * scale_bias[0];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (16))))),\n            _mm256_add_ps(vop16, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (24))))),\n            _mm256_add_ps(vop24, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n      }\n    }\n  } else if (block_size == 16) {\n    // unrolling 2 times\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        const float* scale_bias = reinterpret_cast<const float*>(\n            &input[idx * fused_block_size + block_size]);\n        bio = wgt * scale_bias[1];\n        wgt = wgt * scale_bias[0];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n      }\n    }\n  } else {\n    // generic code\n    int32_t dataInd = 0;\n    for (int32_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      TIndex j = 0;\n      for (; j + 8 <= block_size; j += 8) {\n        _mm256_storeu_ps(op + j, _mm256_setzero_ps());\n      }\n      for (; j < block_size; j++) {\n        op[j] = 0.0f;\n      }\n      for (int32_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int32_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        const float* scale_bias = reinterpret_cast<const float*>(\n            &input[idx * fused_block_size + block_size]);\n        bio = wgt * scale_bias[1];\n        wgt = wgt * scale_bias[0];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * fused_block_size];\n        const int32_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int32_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j],\n              _mm256_fmadd_ps(\n                  vwgt,\n                  _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(_mm_loadl_epi64(\n                      reinterpret_cast<const __m128i*>(&ip[j])))),\n                  _mm256_add_ps(_mm256_loadu_ps(&op[j]), vbio)));\n          _mm_prefetch((&ip_next_T0[j]), _MM_HINT_T0);\n        }\n        for (; j < block_size; j++) {\n          op[j] += wgt * ((float)ip[j]) + bio;\n        }\n      }\n      if (normalize_by_lengths && lengths[rangeIndex]) {\n        float len_inv = 1.0f / lengths[rangeIndex];\n        __m256 vlen_inv = _mm256_set1_ps(len_inv);\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j], _mm256_mul_ps(_mm256_loadu_ps(&op[j]), vlen_inv));\n        }\n        for (; j < block_size; j++) {\n          op[j] = len_inv * op[j];\n        }\n      }\n    }\n  }\n}\n\nvoid Fused8BitRowwiseEmbeddingLookup_int64_t_uint8_t_float__avx2_fma(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const uint8_t* input,\n    const int64_t* indices,\n    const int* lengths,\n    const float* weights,\n    bool normalize_by_lengths,\n    float* out) {\n  const int64_t prefdist_T0 = 16;\n  const int64_t fused_block_size = block_size + 8;\n  if (block_size == 128) {\n    // unrolling 16 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      __m256 vop64 = _mm256_setzero_ps();\n      __m256 vop72 = _mm256_setzero_ps();\n      __m256 vop80 = _mm256_setzero_ps();\n      __m256 vop88 = _mm256_setzero_ps();\n      __m256 vop96 = _mm256_setzero_ps();\n      __m256 vop104 = _mm256_setzero_ps();\n      __m256 vop112 = _mm256_setzero_ps();\n      __m256 vop120 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        const float* scale_bias = reinterpret_cast<const float*>(\n            &input[idx * fused_block_size + block_size]);\n        bio = wgt * scale_bias[1];\n        wgt = wgt * scale_bias[0];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (16))))),\n            _mm256_add_ps(vop16, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (24))))),\n            _mm256_add_ps(vop24, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (32))))),\n            _mm256_add_ps(vop32, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[32])\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (40))))),\n            _mm256_add_ps(vop40, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (48))))),\n            _mm256_add_ps(vop48, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (56))))),\n            _mm256_add_ps(vop56, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[56])\n        vop64 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (64))))),\n            _mm256_add_ps(vop64, vbio));\n        _mm_prefetch((&ip_next_T0[64]), _MM_HINT_T0);\n        vop72 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (72))))),\n            _mm256_add_ps(vop72, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[72])\n        vop80 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (80))))),\n            _mm256_add_ps(vop80, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[80])\n        vop88 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (88))))),\n            _mm256_add_ps(vop88, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[88])\n        vop96 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (96))))),\n            _mm256_add_ps(vop96, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[96])\n        vop104 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (104))))),\n            _mm256_add_ps(vop104, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[104])\n        vop112 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (112))))),\n            _mm256_add_ps(vop112, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[112])\n        vop120 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (120))))),\n            _mm256_add_ps(vop120, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[120])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n        _mm256_storeu_ps(&op[64], vop64);\n        _mm256_storeu_ps(&op[72], vop72);\n        _mm256_storeu_ps(&op[80], vop80);\n        _mm256_storeu_ps(&op[88], vop88);\n        _mm256_storeu_ps(&op[96], vop96);\n        _mm256_storeu_ps(&op[104], vop104);\n        _mm256_storeu_ps(&op[112], vop112);\n        _mm256_storeu_ps(&op[120], vop120);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n        _mm256_storeu_ps(&op[64], _mm256_mul_ps(vop64, vlen_inv));\n        _mm256_storeu_ps(&op[72], _mm256_mul_ps(vop72, vlen_inv));\n        _mm256_storeu_ps(&op[80], _mm256_mul_ps(vop80, vlen_inv));\n        _mm256_storeu_ps(&op[88], _mm256_mul_ps(vop88, vlen_inv));\n        _mm256_storeu_ps(&op[96], _mm256_mul_ps(vop96, vlen_inv));\n        _mm256_storeu_ps(&op[104], _mm256_mul_ps(vop104, vlen_inv));\n        _mm256_storeu_ps(&op[112], _mm256_mul_ps(vop112, vlen_inv));\n        _mm256_storeu_ps(&op[120], _mm256_mul_ps(vop120, vlen_inv));\n      }\n    }\n  } else if (block_size == 64) {\n    // unrolling 8 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      __m256 vop32 = _mm256_setzero_ps();\n      __m256 vop40 = _mm256_setzero_ps();\n      __m256 vop48 = _mm256_setzero_ps();\n      __m256 vop56 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        const float* scale_bias = reinterpret_cast<const float*>(\n            &input[idx * fused_block_size + block_size]);\n        bio = wgt * scale_bias[1];\n        wgt = wgt * scale_bias[0];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (16))))),\n            _mm256_add_ps(vop16, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (24))))),\n            _mm256_add_ps(vop24, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n        vop32 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (32))))),\n            _mm256_add_ps(vop32, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[32])\n        vop40 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (40))))),\n            _mm256_add_ps(vop40, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[40])\n        vop48 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (48))))),\n            _mm256_add_ps(vop48, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[48])\n        vop56 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (56))))),\n            _mm256_add_ps(vop56, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[56])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n        _mm256_storeu_ps(&op[32], vop32);\n        _mm256_storeu_ps(&op[40], vop40);\n        _mm256_storeu_ps(&op[48], vop48);\n        _mm256_storeu_ps(&op[56], vop56);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n        _mm256_storeu_ps(&op[32], _mm256_mul_ps(vop32, vlen_inv));\n        _mm256_storeu_ps(&op[40], _mm256_mul_ps(vop40, vlen_inv));\n        _mm256_storeu_ps(&op[48], _mm256_mul_ps(vop48, vlen_inv));\n        _mm256_storeu_ps(&op[56], _mm256_mul_ps(vop56, vlen_inv));\n      }\n    }\n  } else if (block_size == 32) {\n    // unrolling 4 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      __m256 vop16 = _mm256_setzero_ps();\n      __m256 vop24 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        const float* scale_bias = reinterpret_cast<const float*>(\n            &input[idx * fused_block_size + block_size]);\n        bio = wgt * scale_bias[1];\n        wgt = wgt * scale_bias[0];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n        vop16 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (16))))),\n            _mm256_add_ps(vop16, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[16])\n        vop24 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (24))))),\n            _mm256_add_ps(vop24, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[24])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n        _mm256_storeu_ps(&op[16], vop16);\n        _mm256_storeu_ps(&op[24], vop24);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n        _mm256_storeu_ps(&op[16], _mm256_mul_ps(vop16, vlen_inv));\n        _mm256_storeu_ps(&op[24], _mm256_mul_ps(vop24, vlen_inv));\n      }\n    }\n  } else if (block_size == 16) {\n    // unrolling 2 times\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      __m256 vop0 = _mm256_setzero_ps();\n      __m256 vop8 = _mm256_setzero_ps();\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        const float* scale_bias = reinterpret_cast<const float*>(\n            &input[idx * fused_block_size + block_size]);\n        bio = wgt * scale_bias[1];\n        wgt = wgt * scale_bias[0];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        vop0 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (0))))),\n            _mm256_add_ps(vop0, vbio));\n        _mm_prefetch((&ip_next_T0[0]), _MM_HINT_T0);\n        vop8 = _mm256_fmadd_ps(\n            vwgt,\n            _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(\n                _mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (8))))),\n            _mm256_add_ps(vop8, vbio));\n        // skip unnecessary prefetch of (&ip_next_T0[8])\n      }\n      if (normalize_by_lengths == false) {\n        _mm256_storeu_ps(&op[0], vop0);\n        _mm256_storeu_ps(&op[8], vop8);\n      } else if (lengths[rangeIndex]) {\n        __m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\n        _mm256_storeu_ps(&op[0], _mm256_mul_ps(vop0, vlen_inv));\n        _mm256_storeu_ps(&op[8], _mm256_mul_ps(vop8, vlen_inv));\n      }\n    }\n  } else {\n    // generic code\n    int64_t dataInd = 0;\n    for (int64_t rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\n      float* op = &out[rangeIndex * block_size];\n      TIndex j = 0;\n      for (; j + 8 <= block_size; j += 8) {\n        _mm256_storeu_ps(op + j, _mm256_setzero_ps());\n      }\n      for (; j < block_size; j++) {\n        op[j] = 0.0f;\n      }\n      for (int64_t start = dataInd; dataInd < start + lengths[rangeIndex];\n           ++dataInd) {\n        const int64_t idx = indices[dataInd];\n        CAFFE_ENFORCE(\n            idx >= 0 && idx < data_size,\n            \"Index \",\n            dataInd,\n            \" is out of bounds: \",\n            idx,\n            \", range 0 to \",\n            data_size);\n        float wgt = 1.f;\n        float bio;\n        if (weights) {\n          wgt = weights[dataInd];\n        }\n        const float* scale_bias = reinterpret_cast<const float*>(\n            &input[idx * fused_block_size + block_size]);\n        bio = wgt * scale_bias[1];\n        wgt = wgt * scale_bias[0];\n        __m256 vbio = _mm256_set1_ps(bio);\n        __m256 vwgt = _mm256_set1_ps(wgt);\n        const uint8_t* ip = &input[idx * fused_block_size];\n        const int64_t next_T0 = (dataInd < index_size - prefdist_T0)\n            ? (dataInd + prefdist_T0)\n            : dataInd;\n        const int64_t idx_pref_T0 = indices[next_T0];\n        CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\n        const uint8_t* ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j],\n              _mm256_fmadd_ps(\n                  vwgt,\n                  _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(_mm_loadl_epi64(\n                      reinterpret_cast<const __m128i*>(&ip[j])))),\n                  _mm256_add_ps(_mm256_loadu_ps(&op[j]), vbio)));\n          _mm_prefetch((&ip_next_T0[j]), _MM_HINT_T0);\n        }\n        for (; j < block_size; j++) {\n          op[j] += wgt * ((float)ip[j]) + bio;\n        }\n      }\n      if (normalize_by_lengths && lengths[rangeIndex]) {\n        float len_inv = 1.0f / lengths[rangeIndex];\n        __m256 vlen_inv = _mm256_set1_ps(len_inv);\n        j = 0;\n        for (; j + 8 <= block_size; j += 8) {\n          _mm256_storeu_ps(\n              &op[j], _mm256_mul_ps(_mm256_loadu_ps(&op[j]), vlen_inv));\n        }\n        for (; j < block_size; j++) {\n          op[j] = len_inv * op[j];\n        }\n      }\n    }\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/perfkernels/fused_8bit_rowwise_embedding_lookup.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/perfkernels/fused_8bit_rowwise_embedding_lookup.h\"\n\n#include \"caffe2/core/types.h\"\n#include \"caffe2/perfkernels/common.h\"\n#include \"caffe2/perfkernels/typed_axpy.h\"\n#include \"caffe2/utils/cpuid.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\n// Base implementation does runtime dispatch for each segment of reduction\ntemplate <typename IndexType, typename InType, typename OutType>\nstatic void Fused8BitRowwiseEmbeddingLookupGenericSlow(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const InType* input,\n    const IndexType* indices,\n    const int* lengths,\n    const float* weights, // optional, can be null for sum reducer\n    bool normalize_by_lengths,\n    OutType* out) {\n  // block_size is the number of elements and fused_block_size is the size of\n  // an entire row, including scale and bias.\n  const auto scale_bias_offset = 8 / sizeof(InType);\n  const TIndex fused_block_size = block_size + scale_bias_offset;\n  TIndex current = 0;\n  for (int m = 0; m < output_size; ++m) {\n    memset(out, 0, sizeof(OutType) * block_size);\n    EigenVectorArrayMap<OutType> out_vector(out, block_size);\n    for (int i = 0; i < lengths[m]; ++i) {\n      CAFFE_ENFORCE_LT(current, index_size);\n      TIndex idx = indices[current];\n      CAFFE_ENFORCE(\n          0 <= idx && idx < data_size,\n          \"Index \",\n          current,\n          \" is out of bounds: \",\n          idx,\n          \", range 0 to \",\n          data_size);\n      CAFFE_ENFORCE_LT(idx, data_size);\n#ifdef __GNUC__\n      if (current + 1 < index_size) {\n        __builtin_prefetch(\n            input + fused_block_size * indices[current + 1], 0, 1);\n      }\n#endif // __GNUC__\n\n      const float* scale_bias = reinterpret_cast<const float*>(\n          input + fused_block_size * indices[current] + block_size);\n\n      const float weight = weights ? weights[current] : 1.0f;\n      const float scale = weight * scale_bias[0];\n      const float bias = weight * scale_bias[1];\n\n      TypedAxpy<InType, OutType>(\n          block_size, scale, input + fused_block_size * indices[current], out);\n\n      out_vector += bias;\n\n      ++current;\n    }\n    if (normalize_by_lengths && lengths[m]) {\n      // hack: context is not really used\n      math::Scale<OutType, CPUContext>(\n          block_size, 1.f / lengths[m], out, out, nullptr);\n    }\n    out += block_size;\n  }\n  CAFFE_ENFORCE_EQ(\n      current,\n      index_size,\n      \"Your input seems to be incorrect: the sum of lengths values should be \"\n      \"the size of the indices tensor, but it appears not.\");\n}\n\n// Proxy back to generic implementation\n#define FUSED_8BIT_ROWWISE_EMBEDDING_SPECIALIZATION(                              \\\n    IndexType, InType, OutType)                                                   \\\n  void                                                                            \\\n      Fused8BitRowwiseEmbeddingLookup_##IndexType##_##InType##_##OutType##__base( \\\n          const TIndex block_size,                                                \\\n          const TIndex output_size,                                               \\\n          const TIndex index_size,                                                \\\n          const TIndex data_size,                                                 \\\n          const InType* input,                                                    \\\n          const IndexType* indices,                                               \\\n          const int* lengths,                                                     \\\n          const float* weights,                                                   \\\n          bool normalize_by_lengths,                                              \\\n          OutType* out) {                                                         \\\n    Fused8BitRowwiseEmbeddingLookupGenericSlow<IndexType, InType, OutType>(       \\\n        block_size,                                                               \\\n        output_size,                                                              \\\n        index_size,                                                               \\\n        data_size,                                                                \\\n        input,                                                                    \\\n        indices,                                                                  \\\n        lengths,                                                                  \\\n        weights,                                                                  \\\n        normalize_by_lengths,                                                     \\\n        out);                                                                     \\\n  }                                                                               \\\n  template <>                                                                     \\\n  void Fused8BitRowwiseEmbeddingLookup(                                           \\\n      const TIndex block_size,                                                    \\\n      const TIndex output_size,                                                   \\\n      const TIndex index_size,                                                    \\\n      const TIndex data_size,                                                     \\\n      const InType* input,                                                        \\\n      const IndexType* indices,                                                   \\\n      const int* lengths,                                                         \\\n      const float* weights,                                                       \\\n      bool normalize_by_lengths,                                                  \\\n      OutType* out) {                                                             \\\n    const int32_t one = 1;                                                        \\\n    CAFFE_ENFORCE_EQ(                                                             \\\n        reinterpret_cast<const uint8_t*>(&one)[0],                                \\\n        1,                                                                        \\\n        \"Fused8BitRowwiseEmbeddingLookup is not supported on this platform\");     \\\n    AVX2_FMA_DO(                                                                  \\\n        Fused8BitRowwiseEmbeddingLookup_##IndexType##_##InType##_##OutType,       \\\n        block_size,                                                               \\\n        output_size,                                                              \\\n        index_size,                                                               \\\n        data_size,                                                                \\\n        input,                                                                    \\\n        indices,                                                                  \\\n        lengths,                                                                  \\\n        weights,                                                                  \\\n        normalize_by_lengths,                                                     \\\n        out);                                                                     \\\n    BASE_DO(                                                                      \\\n        Fused8BitRowwiseEmbeddingLookup_##IndexType##_##InType##_##OutType,       \\\n        block_size,                                                               \\\n        output_size,                                                              \\\n        index_size,                                                               \\\n        data_size,                                                                \\\n        input,                                                                    \\\n        indices,                                                                  \\\n        lengths,                                                                  \\\n        weights,                                                                  \\\n        normalize_by_lengths,                                                     \\\n        out);                                                                     \\\n  }\n\nFUSED_8BIT_ROWWISE_EMBEDDING_SPECIALIZATION(int32_t, uint8_t, float);\nFUSED_8BIT_ROWWISE_EMBEDDING_SPECIALIZATION(int64_t, uint8_t, float);\n\n#undef FUSED_8BIT_ROWWISE_EMBEDDING_SPECIALIZATION\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/perfkernels/fused_8bit_rowwise_embedding_lookup.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/common.h\"\n\nnamespace caffe2 {\n\n/**\n * Embedding lookup with reduction.\n *\n * `input` of size data_size * (block_size + 8B)\n * `indices` of size index_size\n * `lengths` of size output_size\n * `weights` nullptr or array of size index_size\n * `out` of size output_size * block_size\n * sum(lengths[i]) == index_size\n *\n * Note that block_size should be the number of quantized values per row in the\n * data, i.e. excluding the scale and bias. The total (fused) block size is\n * assumed to be this block_size, plus 4 bytes for scale and 4 bytes for bias.\n *\n * Behavior is roughly equivalent to pseudocode:\n *\n * pos = 0\n * fused_block_size = block_size + 8B // quantized values and scale and bias\n * for (i = 0..index_size-1)\n *   for (k = 0..block_size-1)\n *     out[i*block_size + k] = 0\n *   for (j = 0..lengths[i]-1)\n *     for (k = 0..block_size-1)\n *       out[i*block_size + k] += input[indices[pos]*(fused_block_size) + k] *\n *                                (weights ? weights[pos] : 1.0)\n *     pos += 1\n *   if (normalize_weights && lengths[i] > 0)\n *     for (k = 0..block_size-1)\n *       out[i*block_size + k] /= lengths[i]\n *\n */\n\ntemplate <typename IndexType, typename InType, typename OutType>\nvoid Fused8BitRowwiseEmbeddingLookup(\n    const TIndex block_size,\n    const TIndex output_size,\n    const TIndex index_size,\n    const TIndex data_size,\n    const InType* input,\n    const IndexType* indices,\n    const int* lengths,\n    const float* weights, // optional, can be null for non-weighted sum\n    bool normalize_by_lengths,\n    OutType* out);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/perfkernels/hp_emblookup_codegen.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport argparse\nimport sys\n\nsizeof = {'float': 4, 'float16': 2, 'uint8_t': 1}\n\n\ndef unroll(uf, IndexType, InType, OutType, use_weights, isa, fused):\n    def compute(regid, InType, use_weights, isa, prefetch):\n        code = []\n\n        if InType == \"float\":\n            code.append(\n                \"vop%d = _mm256_fmadd_ps(vwgt,  \\\n                  _mm256_loadu_ps(ip + (%d)), vop%d);\"\n                                                       % (regid, regid, regid)\n            )\n        elif InType == \"float16\":\n            code.append(\n                \"vop%d = _mm256_fmadd_ps(vwgt,  \\\n                   _mm256_cvtph_ps(_mm_loadu_si128(reinterpret_cast<const __m128i*>(ip + (%d)))), \\\n                   vop%d);\"\n                            % (regid, regid, regid)\n            )\n        elif InType == \"uint8_t\":\n            code.append(\n                \"vop%d = _mm256_fmadd_ps(vwgt,  \\\n                   _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(_mm_loadl_epi64(reinterpret_cast<const __m128i*>(ip + (%d))))), \\\n                   _mm256_add_ps(vop%d, vbio));\"\n                                                 % (regid, regid, regid)\n            )\n        else:\n            assert False\n\n        if prefetch:\n            code.append(\"_mm_prefetch((&ip_next_T0[%d]), _MM_HINT_T0);\" % (regid))\n        else:\n            code.append(\"// skip unnecessary prefetch of (&ip_next_T0[%d])\" % (regid))\n\n        return code\n\n    code = []\n    code.append(\"// unrolling \" + str(uf) + \" times\")\n    code.append(IndexType + \" dataInd = 0;\")\n    code.append(\"for (\" + IndexType +\n                \" rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\")\n    code.append(OutType + \" *op = &out[rangeIndex * block_size];\")\n    for i in range(0, uf):\n        j = 8 * i\n        code.append(\"__m256 vop\" + str(j) + \" = _mm256_setzero_ps();\")\n\n    # inner loop\n    code.append(\"for (\" + IndexType +\n                \" start = dataInd; dataInd < start + lengths[rangeIndex]; ++dataInd) {\")\n    code.append(\"const  \" + IndexType + \" idx = indices[dataInd];\")\n    code.append(\n        'CAFFE_ENFORCE(idx >=0 && idx < data_size, \"Index \", dataInd, \" is out of bounds: \", idx, \", range 0 to \", data_size);')\n\n    if InType == \"uint8_t\":\n        code.append(OutType + \" wgt = 1.f;\")\n        code.append(OutType + \" bio;\")\n        code.append(\"if (weights) {\")\n        code.append(\"wgt = weights[dataInd];\")\n        code.append(\"}\")\n        if fused:\n            code.append(\n                'const float* scale_bias = reinterpret_cast<'\n                'const float*>(&input[idx * fused_block_size + block_size]);'\n            )\n            code.append(\"bio = wgt * scale_bias[1];\")\n            code.append(\"wgt = wgt * scale_bias[0];\")\n        else:\n            code.append(\"bio = wgt * scale_bias[2 * idx + 1];\")\n            code.append(\"wgt = wgt * scale_bias[2 * idx];\")\n        code.append(\"__m256 vbio = _mm256_set1_ps(bio);\")\n    else:\n        code.append(OutType + \" wgt = 1.f;\")\n        code.append(\"if (weights) {\")\n        code.append(\"wgt = weights[dataInd];\")\n        code.append(\"}\")\n    code.append(\"__m256 vwgt = _mm256_set1_ps(wgt);\")\n\n    code.append(\"const {} *ip = &input[idx * fused_block_size];\".format(InType))\n    code.append(\n        'const {} next_T0 = (dataInd < index_size - prefdist_T0)'\n        ' ? (dataInd + prefdist_T0) : dataInd;'.format(IndexType)\n    )\n    code.append(\"const  \" + IndexType + \" idx_pref_T0 = indices[next_T0];\")\n    code.append(\n        \"CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\")\n\n    code.append(\n        'const {} *ip_next_T0 = &input[idx_pref_T0'\n        ' * fused_block_size];'.format(InType)\n    )\n\n    for i in range(0, uf):\n        j = 8 * i\n        cachelinesize = 64\n        byteoffset = sizeof[InType] * j\n        prefetch = (byteoffset % cachelinesize) == 0\n        code.extend(compute(j, InType, use_weights, isa, prefetch))\n    code.append(\"}\")\n\n    code.append(\"if (normalize_by_lengths == false) {\")\n    for i in range(0, uf):\n        j = 8 * i\n        code.append(\n            \"_mm256_storeu_ps(&op[\" + str(j) + \"], vop\" + str(j) + \");\")\n    code.append(\"} else if (lengths[rangeIndex]) {\")\n    # inv of length\n    code.append(\n        \"__m256 vlen_inv = _mm256_set1_ps(1.0f / lengths[rangeIndex]);\")\n    for i in range(0, uf):\n        j = 8 * i\n        code.append(\n            \"_mm256_storeu_ps(&op[\" + str(j) + \"], _mm256_mul_ps(\" + \"vop\" + str(j) + \", vlen_inv));\")\n    code.append(\"}\")\n\n    code.append(\"}\")\n    return code\n\n\ndef generic(IndexType, InType, OutType, use_weights, isa, fused):\n\n    def compute(InType, use_weights, isa):\n        code = []\n        if InType == \"float\":\n            code.append(\n                \"_mm256_storeu_ps(&op[j], \\\n                                 _mm256_fmadd_ps(vwgt,_mm256_loadu_ps(&ip[j]), _mm256_loadu_ps(&op[j])) \\\n                                   );\"\n            )\n        elif InType == \"float16\":\n            code.append(\n                \"_mm256_storeu_ps(&op[j], \\\n                   _mm256_fmadd_ps(vwgt, \\\n                     _mm256_cvtph_ps(_mm_loadu_si128(reinterpret_cast<const __m128i*>(&ip[j]))), _mm256_loadu_ps(&op[j])) \\\n                                   );\"\n            )\n        elif InType == \"uint8_t\":\n            code.append(\n                \"_mm256_storeu_ps(&op[j], \\\n                   _mm256_fmadd_ps(vwgt, \\\n                     _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(_mm_loadl_epi64(reinterpret_cast<const __m128i*>(&ip[j])))), \\\n                     _mm256_add_ps(_mm256_loadu_ps(&op[j]), vbio) ) \\\n                                   );\"\n            )\n        else:\n            assert False\n\n\n        code.append(\"_mm_prefetch((&ip_next_T0[j]), _MM_HINT_T0);\")\n\n        return code\n\n    code = []\n    code.append(IndexType + \" dataInd = 0;\")\n    code.append(\"for (\" + IndexType +\n                \" rangeIndex = 0; rangeIndex < output_size; ++rangeIndex) {\")\n    code.append(OutType + \" *op = &out[rangeIndex * block_size];\")\n\n    # initialize to 0\n    code.append(\"TIndex j = 0;\")\n    code.append(\"for(; j + 8 <= block_size; j += 8) {\")\n    code.append(\"_mm256_storeu_ps(op + j, _mm256_setzero_ps());\")\n    code.append(\"}\")\n    code.append(\"for(; j < block_size; j++) {\")\n    code.append(\"op[j] = 0.0f;\")\n    code.append(\"}\")\n\n    # inner loop\n    code.append(\"for (\" + IndexType +\n                \" start = dataInd; dataInd < start + lengths[rangeIndex]; ++dataInd) {\")\n    code.append(\"const  \" + IndexType + \" idx = indices[dataInd];\")\n    code.append(\n        'CAFFE_ENFORCE(idx >=0 && idx < data_size, \"Index \", dataInd, \" is out of bounds: \", idx, \", range 0 to \", data_size);')\n\n    if InType == \"uint8_t\":\n        code.append(OutType + \" wgt = 1.f;\")\n        code.append(OutType + \" bio;\")\n        code.append(\"if (weights) {\")\n        code.append(\"wgt = weights[dataInd];\")\n        code.append(\"}\")\n        if fused:\n            code.append(\n                'const float* scale_bias = reinterpret_cast<'\n                'const float*>(&input[idx * fused_block_size + block_size]);'\n            )\n            code.append(\"bio = wgt * scale_bias[1];\")\n            code.append(\"wgt = wgt * scale_bias[0];\")\n        else:\n            code.append(\"assert (scale_bias);\")\n            code.append(\"bio = wgt * scale_bias[2 * idx + 1];\")\n            code.append(\"wgt = wgt * scale_bias[2 * idx];\")\n        code.append(\"__m256 vbio = _mm256_set1_ps(bio);\")\n    else:\n        code.append(OutType + \" wgt = 1.f;\")\n        code.append(\"if (weights) {\")\n        code.append(\"wgt = weights[dataInd];\")\n        code.append(\"}\")\n    code.append(\"__m256 vwgt = _mm256_set1_ps(wgt);\")\n\n    code.append(\"const {} *ip = &input[idx * fused_block_size];\".format(InType))\n    code.append(\n        'const {} next_T0 = (dataInd < index_size - prefdist_T0)'\n        ' ? (dataInd + prefdist_T0) : dataInd;'.format(IndexType)\n    )\n    code.append(\"const  \" + IndexType + \" idx_pref_T0 = indices[next_T0];\")\n    code.append(\n        \"CAFFE_ENFORCE(idx_pref_T0 >= 0 && idx_pref_T0 < data_size);\")\n    code.append(\n        \"const {} *ip_next_T0 = &input[idx_pref_T0 * fused_block_size];\".\n        format(InType)\n    )\n\n    # compute and store main loop\n    code.append(\"j = 0;\")\n    code.append(\"for(; j + 8 <= block_size; j += 8) {\")\n    code.extend(compute(InType, use_weights, isa))\n    code.append(\"}\")\n    # leftover\n    if InType == \"float16\":\n        code.append(\"float16 vtmp1[8] CAFFE2_ALIGNED(64);\")\n    code.append(\"for(; j < block_size; j++) {\")\n    if InType == \"float\":\n        code.append(\"op[j] += wgt * ip[j];\")\n    elif InType == \"float16\":\n        code.append(\"vtmp1[0] = ip[j];\")\n        code.append(\"__m256 vtmp2 = _mm256_cvtph_ps(*((__m128i*)vtmp1));\")\n        code.append(\"op[j] += wgt * ((float*)(&vtmp2))[0];\")\n    elif InType == \"uint8_t\":\n        code.append(\"op[j] += wgt * ((float)ip[j]) + bio;\")\n    else:\n        assert False\n\n    code.append(\"}\")\n\n    code.append(\"}\")\n\n    code.append(\"if (normalize_by_lengths && lengths[rangeIndex]) {\")\n    code.append(\"float len_inv = 1.0f / lengths[rangeIndex];\")\n    code.append(\"__m256 vlen_inv = _mm256_set1_ps(len_inv);\")\n    code.append(\"j = 0;\")\n    code.append(\"for(; j + 8 <= block_size; j += 8) {\")\n    code.append(\n        \"_mm256_storeu_ps(&op[j], _mm256_mul_ps(_mm256_loadu_ps(&op[j]), vlen_inv));\")\n    code.append(\"}\")\n    code.append(\"for(; j < block_size; j++) {\")\n    code.append(\"op[j] = len_inv * op[j];\")\n    code.append(\"}\")\n\n    code.append(\"}\")\n\n    code.append(\"}\")\n    return code\n\n\n# start main code\nparser = argparse.ArgumentParser()\nparser.add_argument('-f', '--filename', help=\"file name\")\nparser.add_argument('--fused', action='store_true')\nopts = parser.parse_args()\nif opts.filename:\n    filename = opts.filename\nelif opts.fused:\n    filename = \"embedding_lookup_fused_8bit_rowwise_avx2.cc\"\nelse:\n    filename = \"embedding_lookup_avx2.cc\"\nfout = open(filename, 'w')\n\noptions = [[\"int32_t\", \"float\",   \"float\"],\n           [\"int64_t\", \"float\",   \"float\"],\n           [\"int32_t\", \"float16\", \"float\"],\n           [\"int64_t\", \"float16\", \"float\"],\n           [\"int32_t\", \"uint8_t\",  \"float\"],\n           [\"int64_t\", \"uint8_t\",  \"float\"],\n          ]\n\ncode = []\n# includes\ncode.append(\n\"\"\"/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\"\"\")\ncode.append(\"//// --------------------------\")\ncode.append(\"//// ATTENTION:\")\ncode.append(\"//// THIS CODE IS AUTOGENERATED\")\ncode.append(\"//// BY {}\".format(sys.argv[0]))\ncode.append(\"//// DO NOT MODIFY!!!\")\ncode.append(\"//// --------------------------\\n\\n\")\n\ncode.append(\"#include <caffe2/core/types.h>\")\ncode.append(\"#include <caffe2/core/common.h>\")\ncode.append(\"#include <immintrin.h>\")\ncode.append(\"\\n\")\n\ncode.append(\"namespace caffe2 {\\n\")\nfor o in options:\n    [IndexType, InType, OutType] = o\n\n    prefix = 'Fused8BitRowwise' if opts.fused else ''\n    fn = 'void {}EmbeddingLookup_{}_{}_{}__avx2_fma('.format(\n        prefix, IndexType, InType, OutType\n    )\n    code.append(fn)\n    code.append(\"const TIndex block_size,\")\n    code.append(\"const TIndex output_size,\")\n    code.append(\"const TIndex index_size,\")\n    code.append(\"const TIndex data_size,\")\n    code.append(\"const \" + InType + \"* input,\")\n    code.append(\"const \" + IndexType + \"* indices,\")\n    code.append(\"const int* lengths,\")\n    code.append(\"const float* weights,\")\n    if not opts.fused:\n        code.append(\"const float* scale_bias,\")\n    code.append(\"bool normalize_by_lengths,\")\n    code.append(OutType + \"* out)\")\n\n    code.append(\"{\")\n    code.append(\"const \" + IndexType + \" prefdist_T0 = 16;\")\n    # block_size is the number of elements and fused_block_size is the size of\n    # an entire row, including scale and bias.\n    offset = (8 // sizeof[InType]) if opts.fused else 0\n    code.append(\n        \"const {} fused_block_size = block_size + {};\".\n        format(IndexType, offset)\n    )\n\n    #code.append(\"printf(\\\"calling \" + fn + \"\\\\n\\\");\");\n    if not opts.fused:\n        if InType != \"uint8_t\":\n            code.append(\n                'CAFFE_ENFORCE(scale_bias == nullptr,'\n                ' \"scale_bias must be nullptr\");'\n            )\n        else:\n            code.append(\n                'CAFFE_ENFORCE(scale_bias != nullptr,'\n                ' \"scale_bias must not be nullptr\");'\n            )\n\n    code.append(\"if (block_size == 128) {\")\n    code += unroll(16, IndexType, InType, OutType, True, \"AVX2\", opts.fused)\n    code.append(\"} else if (block_size == 64) {\")\n    code += unroll(8, IndexType, InType, OutType, True, \"AVX2\", opts.fused)\n    code.append(\"} else if (block_size == 32) {\")\n    code += unroll(4, IndexType, InType, OutType, True, \"AVX2\", opts.fused)\n    code.append(\"} else if (block_size == 16) {\")\n    code += unroll(2, IndexType, InType, OutType, True, \"AVX2\", opts.fused)\n    code.append(\"} else {\")\n    code.append(\"// generic code\")\n    code += generic(IndexType, InType, OutType, True, \"AVX2\", opts.fused)\n    code.append(\"}\")\n\n\n    code.append(\"}\")\n\n    code.append(\"\\n\")\ncode.append(\"} // namespace caffe2\")\n\nfor c in code:\n    #print(c, file = fout)\n    fout.write(c + \"\\n\")\nfout.close()\n\n\nprint(\"Created \" + filename)\n"
  },
  {
    "path": "caffe2/perfkernels/typed_axpy.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/perfkernels/typed_axpy.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/perfkernels/common.h\"\n#include \"caffe2/utils/cpuid.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nvoid TypedAxpy<float, float>(int N, const float a, const float* x, float* y) {\n  // This uses a hack that axpy implementation actually does not use the\n  // CPUContext, so passing in a nullpointer works.\n  math::Axpy<float, CPUContext>(N, a, x, y, nullptr);\n}\n\nvoid TypedAxpy_float16_float__base(\n    int N,\n    const float a,\n    const float16* x,\n    float* y) {\n  for (int i = 0; i < N; ++i) {\n    union {\n      uint32_t intval;\n      float floatval;\n    } t1;\n    uint32_t t2, t3;\n    t1.intval = x[i].x & 0x7fff; // Non-sign bits\n    t2 = x[i].x & 0x8000; // Sign bit\n    t3 = x[i].x & 0x7c00; // Exponent\n    t1.intval <<= 13; // Align mantissa on MSB\n    t2 <<= 16; // Shift sign bit into position\n    t1.intval += 0x38000000; // Adjust bias\n    t1.intval = (t3 == 0 ? 0 : t1.intval); // Denormals-as-zero\n    t1.intval |= t2; // Re-insert sign bit\n    y[i] += t1.floatval * a;\n  }\n}\n\ntemplate <>\nvoid TypedAxpy<float16, float>(\n    int N,\n    const float a,\n    const float16* x,\n    float* y) {\n  AVX2_FMA_DO(TypedAxpy_float16_float, N, a, x, y);\n  AVX_F16C_DO(TypedAxpy_float16_float, N, a, x, y);\n  BASE_DO(TypedAxpy_float16_float, N, a, x, y);\n}\n\nvoid TypedAxpy_uint8_float__base(\n    int N,\n    const float a,\n    const std::uint8_t* x,\n    float* y) {\n  for (int i = 0; i < N; ++i) {\n    y[i] += (float)(x[i]) * a;\n  }\n}\n\ntemplate <>\nvoid TypedAxpy<std::uint8_t, float>(\n    int N,\n    const float a,\n    const std::uint8_t* x,\n    float* y) {\n  AVX2_FMA_DO(TypedAxpy_uint8_float, N, a, x, y);\n  BASE_DO(TypedAxpy_uint8_float, N, a, x, y);\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/perfkernels/typed_axpy.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\nnamespace caffe2 {\n\n// Similar to Axpy that calculate y = a * x + y, but allowing x and y to be\n// of different data types.\n// It also provides a performance optimization hint (use_a) to see if a is going\n// to be 1 or not.\ntemplate <typename IN, typename OUT>\nvoid TypedAxpy(int N, const OUT a, const IN* x, OUT* y);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/perfkernels/typed_axpy_avx.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/types.h\"\n#include \"caffe2/perfkernels/cvtsh_ss_bugfix.h\"\n#include \"caffe2/perfkernels/typed_axpy.h\"\n#include \"caffe2/utils/math.h\"\n\n#include <emmintrin.h>\n#include <immintrin.h>\n\nnamespace caffe2 {\n\nvoid TypedAxpy_float16_float__avx_f16c(\n    int N,\n    const float a,\n    const float16* x,\n    float* y) {\n  // if x does not start at the 16 byte boundary, we will process the first few.\n  // before we get to a real one.\n  while (N && (unsigned long)x % 16) {\n    *(y++) += _cvtsh_ss((*(x++)).x) * a;\n    --N;\n  }\n\n  // From now on we can do vectorized additions using __m256, which is 8 floats,\n  // so we will vectorize every 8 element and then resort to cvtsh_ss.\n  __m256 mma = _mm256_set1_ps(a);\n  int current = 0;\n  const int bound = (N % 8) ? N - 8 : N;\n\n  for (; current < bound; current += 8) {\n    __m128i mmx_16 =\n        _mm_loadu_si128(reinterpret_cast<const __m128i*>(x + current));\n    __m256 mmx_32 = _mm256_cvtph_ps(mmx_16);\n    __m256 mmy_in = _mm256_loadu_ps(y + current);\n    __m256 mmmul = _mm256_mul_ps(mmx_32, mma);\n    __m256 mmy_out = _mm256_add_ps(mmmul, mmy_in);\n    _mm256_storeu_ps(y + current, mmy_out);\n  }\n\n  if (bound != N) {\n    while (current < N) {\n      y[current] += _cvtsh_ss(x[current].x) * a;\n      ++current;\n    }\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/perfkernels/typed_axpy_avx2.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/types.h\"\n#include \"caffe2/perfkernels/cvtsh_ss_bugfix.h\"\n#include \"caffe2/perfkernels/typed_axpy.h\"\n#include \"caffe2/utils/math.h\"\n\n#include <emmintrin.h>\n#include <immintrin.h>\n\nnamespace caffe2 {\n\nvoid TypedAxpy_float16_float__avx2_fma(\n    int N,\n    const float a,\n    const float16* x,\n    float* y) {\n  // if x does not start at the 16 byte boundary, we will process the first few.\n  // before we get to a real one.\n  while (((unsigned long)x % 16) && N) {\n    *(y++) += _cvtsh_ss((*(x++)).x) * a;\n    --N;\n  }\n\n  // From now on we can do vectorized additions using __m256, which is 8 floats,\n  // so we will vectorize every 8 element and then resort to cvtsh_ss.\n  __m256 mma = _mm256_set1_ps(a);\n  int current = 0;\n  const int bound = (N % 8) ? N - 8 : N;\n\n  for (; current < bound; current += 8) {\n    __m128i mmx_16 =\n        _mm_loadu_si128(reinterpret_cast<const __m128i*>(x + current));\n    __m256 mmx_32 = _mm256_cvtph_ps(mmx_16);\n    __m256 mmy = _mm256_loadu_ps(y + current);\n    mmy = _mm256_fmadd_ps(mmx_32, mma, mmy);\n    _mm256_storeu_ps(y + current, mmy);\n  }\n\n  if (bound != N) {\n    while (current < N) {\n      y[current] += _cvtsh_ss(x[current].x) * a;\n      ++current;\n    }\n  }\n}\n\nvoid TypedAxpy_uint8_float__avx2_fma(\n    int N,\n    const float a,\n    const std::uint8_t* x,\n    float* y) {\n  // if x does not start at the 16 byte boundary, we will process the first few.\n  // before we get to a real one.\n  while (((unsigned long)x % 16) && N) {\n    *(y++) += (float)(*(x++)) * a;\n    --N;\n  }\n\n  // From now on we can do vectorized additions using __m256, which is 8 floats,\n  // so we will vectorize every 8 element and then resort to cvtsh_ss.\n  __m256 mma = _mm256_set1_ps(a);\n  int current = 0;\n  const int bound = (N % 8) ? N - 8 : N;\n\n  for (; current < bound; current += 8) {\n    __m256i mmx_int32 = _mm256_cvtepi8_epi32(\n        _mm_loadu_si128(reinterpret_cast<const __m128i*>(x + current)));\n    __m256 mmx_fp32 = _mm256_cvtepi32_ps(mmx_int32);\n\n    __m256 mmy = _mm256_loadu_ps(y + current);\n    mmy = _mm256_fmadd_ps(mmx_fp32, mma, mmy);\n    _mm256_storeu_ps(y + current, mmy);\n  }\n\n  if (bound != N) {\n    while (current < N) {\n      y[current] += (float)(x[current]) * a;\n      ++current;\n    }\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/proto/CMakeLists.txt",
    "content": "file(GLOB Caffe2_PROTOBUF_FILES \"${CMAKE_CURRENT_SOURCE_DIR}/*.proto\")\n\ncaffe2_protobuf_generate_cpp_py(Caffe2_PROTO_SRCS Caffe2_PROTO_HEADERS Caffe2_PROTO_PY ${Caffe2_PROTOBUF_FILES})\n\nadd_library(Caffe2_PROTO OBJECT ${Caffe2_PROTO_HEADERS} ${Caffe2_PROTO_SRCS})\n\nif (MSVC)\n  if(BUILD_SHARED_LIBS)\n    set(Caffe2_API_DEFINE \"-DCAFFE2_API=__declspec(dllexport)\")\n  else()\n    set(Caffe2_API_DEFINE \"-DCAFFE2_API=\")\n  endif()\n  target_compile_definitions(\n      Caffe2_PROTO PRIVATE ${Caffe2_API_DEFINE})\nendif()\n\ninstall(FILES ${Caffe2_PROTO_HEADERS} DESTINATION include/caffe2/proto)\n"
  },
  {
    "path": "caffe2/proto/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/proto/caffe2.proto",
    "content": "syntax = \"proto2\";\n\npackage caffe2;\n\n// A few notes about the Caffe2's protobuffer convention:\n// (1) Most objects are registered by their types, such as operators and nets.\n//     For these, we have a string-type field \"type\" for registration purposes.\n// (2) We do not use extension because that used to create quite some conflicts\n//     in Caffe's protobuf design.\n// (3) We have not used any proto3 specific features, such as Any or Map. This\n//     is mainly for backward compability purposes but we may consider using\n//     those in the future.\n\n// TensorProto stores serialized Tensor objects.\nmessage TensorProto {\n  // The dimensions in the tensor.\n  repeated int64 dims = 1;\n  enum DataType {\n    UNDEFINED = 0;\n    FLOAT = 1;  // float\n    INT32 = 2;  // int\n    BYTE = 3;  // BYTE, when deserialized, is going to be restored as uint8.\n    STRING = 4;  // string\n    // Less-commonly used data types.\n    BOOL = 5;  // bool\n    UINT8 = 6;  // uint8_t\n    INT8 = 7;  // int8_t\n    UINT16 = 8;  // uint16_t\n    INT16 = 9;  // int16_t\n    INT64 = 10;  // int64_t\n    FLOAT16 = 12;  // caffe2::__f16, caffe2::float16\n    DOUBLE = 13;  // double\n  }\n  optional DataType data_type = 2 [default = FLOAT];\n  // For float\n  repeated float float_data = 3 [packed = true];\n  // For int32, uint8, int8, uint16, int16, bool, and float16\n  // Note about float16: in storage we will basically convert float16 byte-wise\n  // to unsigned short and then store them in the int32_data field.\n  repeated int32 int32_data = 4 [packed = true];\n  // For bytes\n  optional bytes byte_data = 5;\n  // For strings\n  repeated bytes string_data = 6;\n  // For double\n  repeated double double_data = 9 [packed = true];\n  // For int64\n  repeated int64 int64_data = 10 [packed = true];\n  // Optionally, a name for the tensor.\n  optional string name = 7;\n\n  // Optionally, a TensorProto can contain the details about the device that\n  // it was serialized from. This is useful in cases like snapshotting a whole\n  // workspace in a multi-GPU environment.\n  optional DeviceOption device_detail = 8;\n  // When loading from chunks this is going to indicate where to put data in the\n  // full array. When not used full data have to be present\n  message Segment {\n    required int64 begin = 1;\n    required int64 end = 2;\n  }\n  optional Segment segment = 11;\n}\n\nmessage QTensorProto {\n  repeated int64 dims = 1;\n  required int32 precision = 2;\n  required double scale = 3;\n  required double bias = 4;\n  required bool is_signed = 5;\n  repeated int32 data = 6 [packed = true];\n  optional string name = 7;\n}\n\n// TensorProtos stores multiple TensorProto objects in one single proto. This\n// is useful for small tensors; For anything big, consider using a DB for\n// storage.\nmessage TensorProtos {\n  repeated TensorProto protos = 1;\n}\n\nmessage TensorShape {\n  repeated int64 dims = 1;\n  optional TensorProto.DataType data_type = 2 [default = FLOAT];\n  repeated int32 unknown_dims = 3;\n  optional bool unknown_shape = 4 [default = false];\n  optional string name = 5;\n\n}\n\nmessage TensorShapes {\n  repeated TensorShape shapes = 1;\n}\n\n// A named argument containing either singular float, integer and string\n// values, or repeated float, int and string arrays.\nmessage Argument {\n  optional string name = 1;\n  optional float f = 2;\n  optional int64 i = 3;\n  optional bytes s = 4;\n  optional NetDef n = 8;\n  repeated float floats = 5;\n  repeated int64 ints = 6;\n  repeated bytes strings = 7;\n  repeated NetDef nets = 9;\n}\n\n// DeviceType that Caffe2 currently supports.\n// Note: if you add a device type, make sure you add the corresponding device\n// line in core/blob_serialization.cc.\nenum DeviceType {\n  CPU = 0;                    // In default, we will use CPU.\n  CUDA = 1;                   // CUDA.\n  MKLDNN = 2;                 // Reserved for explicit MKLDNN\n  OPENGL = 3;                 // OpenGL\n  // Change the following number if you add more devices in the code.\n  COMPILE_TIME_MAX_DEVICE_TYPES = 4;\n  ONLY_FOR_TEST = 20901701;   // This device type is only for test.\n}\n\n// Device-specific options. We do not distinguish DeviceOption protos for\n// different DeviceTypes, so currently all devices share the same DeviceOption\n// proto. Fields that are specific to a device type is ignored if the type does\n// not match.\n// Note: if you add fields to the DeviceOption, make sure you add the\n// corresponding changes to IsSameDevice() function in utils/proto_utils.{h,cc}.\nmessage DeviceOption {\n  // [general] Options that need to be carried out before running the execution.\n  // optional DeviceType device_type = 1 [ default = CPU ];\n  optional int32 device_type = 1 [ default = 0 ]; // 0 is CPU.\n  // [CUDA specific] the cuda gpu id.\n  optional int32 cuda_gpu_id = 2;\n  // [general] The random seed to start the device random number generator with.\n  optional uint32 random_seed = 3;\n  // [general] What node this op should execute on.\n  // Used for net transformation purposes. Must be empty at execution time.\n  optional string node_name = 4;\n  // [CPU and Linux specific] NUMA node id\n  optional int32 numa_node_id = 5 [default = -1];\n}\n\n// Operator Definition.\nmessage OperatorDef {\n  repeated string input = 1; // the name of the input blobs\n  repeated string output = 2; // the name of output top blobs\n  optional string name = 3; // the operator name. This is optional.\n  // the operator type. This is needed to create the object from the operator\n  // registry.\n  optional string type = 4;\n  repeated Argument arg = 5;\n\n  // The device option that the operator should run under.\n  optional DeviceOption device_option = 6;\n\n  // Optionally, one can specify an engine when there are multiple\n  // implementations available simultaneously for one device type.\n  // If one specifies an engine but that engine does not exist in the compiled\n  // Caffe2 binary, Caffe2 will fall back to the default engine of that device\n  // type.\n  optional string engine = 7;\n\n\n  // Additional 'fake' inputs used for expressing control dependencies\n  // in the operator graph. This can be used to ensure that an\n  // operator does not run until another operator is ready, for e.g.\n  // scheduling control. These are not passed as actual inputs to the\n  // Operator implementation, and are only used by the Net class for\n  // scheduling purposes.\n  repeated string control_input = 8;\n\n  // is_gradient_op argument is only used as a hint in shape inference\n  // and has no runtime significance\n  optional bool is_gradient_op = 9 [default = false];\n\n  // debug information associated with the construction of the operator.\n  // This is an optional string with no assumed characteristics as\n  // operators can be constructed in any language.\n  optional string debug_info = 10;\n}\n\n// Network definition.\nmessage NetDef {\n  optional string name = 1; // the network's name\n  // Operators that the network contains.\n  // Note: this is not named \"operator\" because that is a reserved word in C++.\n  repeated OperatorDef op = 2;\n\n  // The type of network that the net should be run with. This routes the\n  // network instantiation to different execution modes. The default mode,\n  // \"simple\", runs the operators in a sequential way as the original Caffe\n  // implementation does.\n  optional string type = 3;\n\n  // the number of workers, if the operators in the network is to be carried out\n  // in parallel.\n  // Note: This is to be deprecated. Using the arg field with \"num_workers\" as\n  // key.\n  optional int32 num_workers = 4 [deprecated=true];\n\n  // The device option for the network. If a network has a specific device\n  // option and one of its operators does not have it set, we will copy over the\n  // device option to the operator. This allows us to basically avoid putting\n  // device options at every operator.\n  optional DeviceOption device_option = 5;\n\n  repeated Argument arg = 6;\n\n  // Two optional fields to declare external input and output of a net.\n  // If these two are set, when a net is created, we will sanity check for\n  // every op whether its input is declared (either as an external input,\n  // or as an intermediate blob created by one of the ops), and sanity check\n  // if all blobs in external_output are produced.\n  //\n  // In cases of memory optimization, declaring external_input and\n  // external_output also ensures that storage of these blobs are persistent:\n  // for any blob in external_input and external_output, after a network run\n  // finishes, their content are actually the right content. Any intermediate\n  // blobs' contents may be overwritten.\n  repeated string external_input = 7;\n  repeated string external_output = 8;\n}\n\n// ExecutionStep is actually a sort-of-hacky way we simulate iteration right\n// now.\nmessage ExecutionStep {\n  // ExecutionStep should either contain a set of substeps, or a set of\n  // network names to run in this execution step. They should NOT both be set\n  // at the same time.\n  optional string name = 1;\n  // An execution step could be recursive, in which it involves a set of\n  // substeps.\n  repeated ExecutionStep substep = 2;\n  // Alternatively, an execution step could involve one or more networks.\n  // Note that you cannot have both substeps and networks. Choose one.\n  // Note that an execution step refers networks by their name. The actual\n  // network definition of the same name should be included in the network field\n  // of the plan. The reason is that a network object might hold internal states\n  // (think of a data layer), so we want to have the same network object that\n  // multiple steps could ask to run.\n  repeated string network = 3;\n  // Number of iterations to run this step. The substeps or the networks\n  // specified will be run sequentially, and one sequential run is considered\n  // one iteration. If this is not set, the number of iterations is assumed to\n  // be 1.\n  optional int64 num_iter = 4;\n\n  // Criteria network specifies a single output (TensorCPU<bool>) of\n  // size (1), is run on every iteration by the executor, and\n  // execution terminates when the output[0] is `false`.\n  optional string criteria_network = 5 [deprecated=true];\n\n  // DEPRECATED. Use `run_every_ms`.\n  optional string report_net = 7;\n  optional int32 report_interval = 8;\n\n  // If provided, execute this step at every time interval (in millisecs)\n  // while its sibiling execution steps execute in parallel. This step is\n  // guaranteed to run at least once after all non-interval siblings finished.\n  optional int64 run_every_ms = 11;\n\n  // If false or not set, execute sub-steps serially.\n  // If true, execute all substeps concurrently, each one in a separte thread.\n  optional bool concurrent_substeps = 6;\n\n  // Name of a scalar boolean tensor.\n  // ES checks this blob AFTER every substeps/subnets.\n  // If specified, and the value is true, then ES will skip the rest and return\n  // immediately.\n  // This means that the report_net and the first step will always be called.\n  // Use cases:\n  // 1) the first substep stops the rest if data condition not met\n  // 2) the first substep decide which of the rest of the steps should be run.\n  // 3) external control\n  //\n  // ** It is the user's responsibility to not to put this blob in race conditions.\n  // ** For example when setting this blob in concurrent substeps\n  optional string should_stop_blob = 9;\n\n  // if only_once is true, this step will only be executed once. this ONLY takes\n  // effect when using should_stop_blob\n  optional bool only_once = 10;\n\n  // Whether to create a child workspace for this step.\n  // If yes, the workflow and nets are re-created every time this step is run.\n  optional bool create_workspace = 12;\n\n  // How many copies of the children execution steps to run concurrently.\n  optional int32 num_concurrent_instances = 13;\n}\n\nmessage PlanDef {\n  // All the networks that are used in this execution. Note that networks should\n  // be ordered in the way they are executed, i.e. for a layer in a network, all\n  // its input blobs should already have been initialized by the layers or\n  // networks defined before it.\n  optional string name = 1;\n  // The networks that are going to be used in this plan.\n  repeated NetDef network = 2;\n  repeated ExecutionStep execution_step = 3;\n}\n\n// Protobuf format for blobs that are not Tensors. We use a key to store the\n// type of the blob. For example for a serialized DBProto, the type should\n// be \"DBReader\" and the content should be a serialized DBProto object.\nmessage BlobProto {\n  optional string name = 1;\n  optional string type = 2;\n  optional TensorProto tensor = 3;\n  optional bytes content = 4;\n  optional QTensorProto qtensor = 5;\n  // If blob is not Tensor and is divided into chunks, content_num_chunks\n  // contains number of chunks, into which blob was divided.\n  optional int32 content_num_chunks = 6;\n  optional int32 content_chunk_id = 7;\n}\n\n// Protobuf format to serialize DBReader.\nmessage DBReaderProto {\n  // The name for the DB object in the workspace.\n  optional string name = 1;\n  // The source of the DB\n  optional string source = 2;\n  // The type of the DB\n  optional string db_type = 3;\n  // The current key of the DB if the DB supports seeking.\n  optional string key = 4;\n}\n"
  },
  {
    "path": "caffe2/proto/caffe2_legacy.proto",
    "content": "syntax = \"proto2\";\n\npackage caffe2;\n\nenum LegacyPadding {\n  NOTSET = 0;  // Do not use old-stype padding strategies.\n\n  // VALID and SAME are two strategies adopted in Google DistBelief: it forces\n  // the input shape as follows. For SAME, the output is:\n  //   R_out = ceil(float(R) / float(S))\n  //   C_out = ceil(float(C) / float(S))\n  // where R and C are row and column, S is the stride, and K is the kernel.\n  // The number of padded pixels is then computed as\n  //   Pr = ((R_out - 1) * S + K - R)\n  //   Pc = ((C_out - 1) * S + K - C)\n  // When Pr and Pc are even numbers, both sides (left and right, or top and\n  // bottom) get half each. When Pr and Pc are odd numbers, the right and the\n  // bottom gets the one additional padding pixel.\n  // For VALID, padding values of 0 are always used.\n  VALID = 1;\n  SAME = 2;\n\n  // CAFFE_LEGACY_POOLING is a flag that notifies the code to use the old Caffe\n  // padding strategy.\n  // Basically, in caffe2, after padding the convolution and pooling use the\n  // same computation strategy: half-windows at the right and bottom are\n  // discarded. In Caffe, convolution follows this strategy but if there are\n  // some pixels in the half-windows, the pooling layer will actually put one\n  // additional output. If you set LegacyPadding to this, we will compute the\n  // equivalent padding strategy in caffe2 so that the output size is\n  // backward compatible with Caffe.\n  // THIS IS NOW DEPRECATED. ANY non-conventional use has to be manually\n  // converted.\n  CAFFE_LEGACY_POOLING = 3;\n}\n"
  },
  {
    "path": "caffe2/proto/hsm.proto",
    "content": "syntax = \"proto2\";\n\npackage caffe2;\n\n// Hierarchical Softmax protobuffer convention:\n// The HSM operator requires a hierarchy of vocabulary words in the form of a\n// tree from the user. This tree is expressed using the proto format.\n// TreeProto points to the root NodeProto which can recursively contain children\n// NodeProtos (internal nodes) or word_ids (leaf nodes).\n\n// The aforementioned TreeProto is internally translated into a list of word_ids\n// tagged with a list of NodeProtos that lie in the path from the root to that\n// word_id using hsm_util.create_hierarchy(tree_proto).\n// Specifically, HierarchyProto contains a list of PathProtos. Each PathProto\n// belongs to a word_id and contains a list of PathNodeProtos. Each\n// PathNodeProto contains information about the number of children the node has\n// (length), the index of the child node that lies in the path from root to\n// word_id (target) and a cumulative sum of children nodes (index; this acts as\n// the weight parameter matrix offset).\n\n// Each node in the hierarchy contains links to either leaf nodes or more\n// non-terminal nodes\nmessage NodeProto {\n  // Links to non-terminal children nodes\n  repeated NodeProto children = 1;\n  // Links to terminal (leaf) nodes\n  repeated int32 word_ids = 2;\n  optional int32 offset = 3;\n  optional string name = 4;\n  repeated float scores = 5;\n}\n\n// Protobuf format to accept hierarchy for hierarchical softmax operator.\n// TreeProto points to the root node.\nmessage TreeProto {\n  optional NodeProto root_node = 1;\n}\n\n// Internal Protobuf format which represents the path in the tree hierarchy for\n// each word in the vocabulary.\nmessage HierarchyProto {\n  optional int32 size = 1;\n  repeated PathProto paths = 2;\n}\n\n// Each PathProto belongs to a word and is an array of nodes in the\n// path from the root to the leaf (which is the word itself) in the tree.\nmessage PathProto {\n  optional int32 word_id = 1;\n  repeated PathNodeProto path_nodes = 2;\n}\n\n// Represents a node in the path from the root node all the way down to the\n// word (leaf).\nmessage PathNodeProto {\n  // Parameter matrix offset for this node\n  optional int32 index = 1;\n  // Number of children\n  optional int32 length = 2;\n  // Index of the next node in the path\n  optional int32 target = 3;\n}\n"
  },
  {
    "path": "caffe2/proto/metanet.proto",
    "content": "syntax = \"proto2\";\n\nimport \"caffe2/proto/caffe2.proto\";\n\npackage caffe2;\n\nmessage ModelInfo {\n  optional string project = 1;\n  optional string modelClass = 2;\n  optional string version = 3;\n  optional string predictorType = 4 [ default = \"SINGLE_PREDICTOR\" ];\n  optional string modelId = 5;\n}\n\nmessage BlobsMap {\n  required string key = 1;\n  repeated string value = 2;\n}\n\nmessage NetsMap {\n  required string key = 1;\n  required NetDef value = 2;\n}\n\nmessage PlansMap {\n  required string key = 1;\n  required PlanDef value = 2;\n}\n\nmessage StringMap {\n  required string key = 1;\n  required string value = 2;\n}\n\nmessage MetaNetDef {\n  repeated BlobsMap blobs = 1;\n  // Text-format serialized NetDefs.\n  repeated NetsMap nets = 2;\n  // Info about where the model comes from. Possible use cases:\n  // 1) sanity check or diagnose\n  // 2) provide info for evaluation.\n  optional ModelInfo modelInfo = 3;\n  repeated PlansMap plans = 4;\n  repeated StringMap applicationSpecificInfo = 5;\n}\n"
  },
  {
    "path": "caffe2/proto/predictor_consts.proto",
    "content": "syntax = \"proto2\";\n\npackage caffe2;\n\nmessage PredictorConsts {\n  // Important - to ensure ordered traversal of the DB, these must be\n  // set in the given (lexicographic) order in the input DBReader.\n  optional string META_NET_DEF = 1 [ default = \"!!META_NET_DEF\" ];\n\n  // The key the Predictor sets in the global workspace for DBReader\n  // consumed by the LoadOp in GLOBAL_INIT_NET.\n\n  optional string PREDICTOR_DBREADER = 2 [ default = \"!!PREDICTOR_DBREADER\" ];\n\n  // Blob types used in MetaNetDef blobs\n  optional string PARAMETERS_BLOB_TYPE = 3 [ default = \"PARAMETERS_BLOB_TYPE\" ];\n  optional string INPUTS_BLOB_TYPE = 4 [ default = \"INPUTS_BLOB_TYPE\" ];\n  optional string OUTPUTS_BLOB_TYPE = 5 [ default = \"OUTPUTS_BLOB_TYPE\" ];\n\n  // Net types used in MetaNetDef nets\n  optional string GLOBAL_INIT_NET_TYPE = 6 [ default = \"GLOBAL_INIT_NET_TYPE\" ];\n  optional string PREDICT_INIT_NET_TYPE = 7\n      [ default = \"PREDICT_INIT_NET_TYPE\" ];\n  optional string PREDICT_NET_TYPE = 8 [ default = \"PREDICT_NET_TYPE\" ];\n  optional string SINGLE_PREDICTOR = 9 [ default = \"SINGLE_PREDICTOR\" ];\n  optional string MULTI_PREDICTOR = 10 [ default = \"MULTI_PREDICTOR\" ];\n  optional string TRAIN_INIT_PLAN_TYPE = 11\n      [ default = \"TRAIN_INIT_PLAN_TYPE\" ];\n  optional string TRAIN_PLAN_TYPE = 12 [ default = \"TRAIN_PLAN_TYPE\" ];\n}\n"
  },
  {
    "path": "caffe2/proto/prof_dag.proto",
    "content": "syntax = \"proto2\";\n\npackage caffe2;\n\n// A few notes about the Caffe2's protobuffer convention:\n// (1) Most objects are registered by their types, such as operators and nets.\n//     For these, we have a string-type field \"type\" for registration purposes.\n// (2) We do not use extension because that used to create quite some conflicts\n//     in Caffe's protobuf design.\n// (3) We have not used any proto3 specific features, such as Any or Map. This\n//     is mainly for backward compability purposes but we may consider using\n//     those in the future.\n\n// Protobuf format to serialize profiler data\nmessage ProfDAGProto {\n  // The name for the operator\n  required string name = 1;\n  // The mean execution time\n  required float mean = 2;\n  // The standard deviation\n  required float stddev = 3;\n}\n\nmessage ProfDAGProtos {\n  repeated ProfDAGProto stats = 1;\n}\n"
  },
  {
    "path": "caffe2/python/CMakeLists.txt",
    "content": "# ---[ CPU files.\nset(Caffe2_CPU_PYTHON_SRCS\n    \"/pybind_state.cc\"\n    \"/pybind_state_dlpack.cc\"\n    \"/pybind_state_mkl.cc\"\n)\n# ---[ GPU files\nset(Caffe2_GPU_PYTHON_SRCS\n    ${Caffe2_CPU_PYTHON_SRCS}\n    \"/pybind_state_gpu.cc\"\n)\n\nprepend(Caffe2_CPU_PYTHON_SRCS ${CMAKE_CURRENT_SOURCE_DIR} ${Caffe2_CPU_PYTHON_SRCS})\nprepend(Caffe2_GPU_PYTHON_SRCS ${CMAKE_CURRENT_SOURCE_DIR} ${Caffe2_GPU_PYTHON_SRCS})\n\nset(Caffe2_CPU_PYTHON_SRCS ${Caffe2_CPU_PYTHON_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_PYTHON_SRCS ${Caffe2_GPU_PYTHON_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/python/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/python/_import_c_extension.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package _import_c_extension\n# Module caffe2.python._import_c_extension\nimport atexit\nimport logging\nimport sys\nfrom caffe2.python import extension_loader\n\n# We will first try to load the gpu-enabled caffe2. If it fails, we will then\n# attempt to load the cpu version. The cpu backend is the minimum required, so\n# if that still fails, we will exit loud.\nwith extension_loader.DlopenGuard():\n    try:\n        from caffe2.python.caffe2_pybind11_state_gpu import *  # noqa\n        if num_cuda_devices():  # noqa\n            has_gpu_support = True\n        else:\n            has_gpu_support = False\n    except ImportError as e:\n        logging.warning(\n            'This caffe2 python run does not have GPU support. '\n            'Will run in CPU only mode.')\n        logging.warning('Debug message: {0}'.format(str(e)))\n        has_gpu_support = False\n        try:\n            from caffe2.python.caffe2_pybind11_state import *  # noqa\n        except ImportError as e:\n            logging.critical(\n                'Cannot load caffe2.python. Error: {0}'.format(str(e)))\n            sys.exit(1)\n\n# libcaffe2_python contains a global Workspace that we need to properly delete\n# when exiting. Otherwise, cudart will cause segfaults sometimes.\natexit.register(on_module_exit)  # noqa\n\n\n# Add functionalities for the TensorCPU interface.\ndef _TensorCPU_shape(self):\n    return tuple(self._shape)\n\n\ndef _TensorCPU_reshape(self, shape):\n    return self._reshape(list(shape))\n\nTensorCPU.shape = property(_TensorCPU_shape)  # noqa\nTensorCPU.reshape = _TensorCPU_reshape  # noqa\n"
  },
  {
    "path": "caffe2/python/allcompare_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n#!/usr/bin/env python\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\nfrom multiprocessing import Process\n\nimport numpy as np\nimport tempfile\nimport shutil\n\nimport caffe2.python.hypothesis_test_util as hu\n\nop_engine = 'GLOO'\n\n\nclass TemporaryDirectory:\n    def __enter__(self):\n        self.tmpdir = tempfile.mkdtemp()\n        return self.tmpdir\n\n    def __exit__(self, type, value, traceback):\n        shutil.rmtree(self.tmpdir)\n\n\ndef allcompare_process(filestore_dir, process_id, data, num_procs):\n    from caffe2.python import core, data_parallel_model, workspace, dyndep\n    from caffe2.python.model_helper import ModelHelper\n    from caffe2.proto import caffe2_pb2\n    dyndep.InitOpsLibrary(\"@/caffe2/caffe2/distributed:file_store_handler_ops\")\n\n    workspace.RunOperatorOnce(\n        core.CreateOperator(\n            \"FileStoreHandlerCreate\", [], [\"store_handler\"], path=filestore_dir\n        )\n    )\n    rendezvous = dict(\n        kv_handler=\"store_handler\",\n        shard_id=process_id,\n        num_shards=num_procs,\n        engine=op_engine,\n        exit_nets=None\n    )\n\n    model = ModelHelper()\n    model._rendezvous = rendezvous\n\n    workspace.FeedBlob(\"test_data\", data)\n\n    data_parallel_model._RunComparison(\n        model, \"test_data\", core.DeviceOption(caffe2_pb2.CPU, 0)\n    )\n\n\nclass TestAllCompare(hu.HypothesisTestCase):\n    @given(\n        d=st.integers(1, 5), n=st.integers(2, 11), num_procs=st.integers(1, 8)\n    )\n    def test_allcompare(self, d, n, num_procs):\n        dims = []\n        for _ in range(d):\n            dims.append(np.random.randint(1, high=n))\n        test_data = np.random.ranf(size=tuple(dims)).astype(np.float32)\n\n        with TemporaryDirectory() as tempdir:\n            processes = []\n            for idx in range(num_procs):\n                process = Process(\n                    target=allcompare_process,\n                    args=(tempdir, idx, test_data, num_procs)\n                )\n                processes.append(process)\n                process.start()\n\n            while len(processes) > 0:\n                process = processes.pop()\n                process.join()\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/attention.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package attention\n# Module caffe2.python.attention\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import brew\n\n\nclass AttentionType:\n    Regular, Recurrent, Dot, SoftCoverage = tuple(range(4))\n\n\ndef s(scope, name):\n    # We have to manually scope due to our internal/external blob\n    # relationships.\n    return \"{}/{}\".format(str(scope), str(name))\n\n\n# c_i = \\sum_j w_{ij}\\textbf{s}_j\ndef _calc_weighted_context(\n    model,\n    encoder_outputs_transposed,\n    encoder_output_dim,\n    attention_weights_3d,\n    scope,\n):\n    # [batch_size, encoder_output_dim, 1]\n    attention_weighted_encoder_context = brew.batch_mat_mul(\n        model,\n        [encoder_outputs_transposed, attention_weights_3d],\n        s(scope, 'attention_weighted_encoder_context'),\n    )\n    # [batch_size, encoder_output_dim]\n    attention_weighted_encoder_context, _ = model.net.Reshape(\n        attention_weighted_encoder_context,\n        [\n            attention_weighted_encoder_context,\n            s(scope, 'attention_weighted_encoder_context_old_shape'),\n        ],\n        shape=[1, -1, encoder_output_dim],\n    )\n    return attention_weighted_encoder_context\n\n\n# Calculate a softmax over the passed in attention energy logits\ndef _calc_attention_weights(\n    model,\n    attention_logits_transposed,\n    scope,\n    encoder_lengths=None,\n):\n    if encoder_lengths is not None:\n        attention_logits_transposed = model.net.SequenceMask(\n            [attention_logits_transposed, encoder_lengths],\n            ['masked_attention_logits'],\n            mode='sequence',\n        )\n\n    # [batch_size, encoder_length, 1]\n    attention_weights_3d = brew.softmax(\n        model,\n        attention_logits_transposed,\n        s(scope, 'attention_weights_3d'),\n        engine='CUDNN',\n        axis=1,\n    )\n    return attention_weights_3d\n\n\n# e_{ij} = \\textbf{v}^T tanh \\alpha(\\textbf{h}_{i-1}, \\textbf{s}_j)\ndef _calc_attention_logits_from_sum_match(\n    model,\n    decoder_hidden_encoder_outputs_sum,\n    encoder_output_dim,\n    scope,\n):\n    # [encoder_length, batch_size, encoder_output_dim]\n    decoder_hidden_encoder_outputs_sum = model.net.Tanh(\n        decoder_hidden_encoder_outputs_sum,\n        decoder_hidden_encoder_outputs_sum,\n    )\n\n    # [encoder_length, batch_size, 1]\n    attention_logits = brew.fc(\n        model,\n        decoder_hidden_encoder_outputs_sum,\n        s(scope, 'attention_logits'),\n        dim_in=encoder_output_dim,\n        dim_out=1,\n        axis=2,\n        freeze_bias=True,\n    )\n\n    # [batch_size, encoder_length, 1]\n    attention_logits_transposed = brew.transpose(\n        model,\n        attention_logits,\n        s(scope, 'attention_logits_transposed'),\n        axes=[1, 0, 2],\n    )\n    return attention_logits_transposed\n\n\n# \\textbf{W}^\\alpha used in the context of \\alpha_{sum}(a,b)\ndef _apply_fc_weight_for_sum_match(\n    model,\n    input,\n    dim_in,\n    dim_out,\n    scope,\n    name,\n):\n    output = brew.fc(\n        model,\n        input,\n        s(scope, name),\n        dim_in=dim_in,\n        dim_out=dim_out,\n        axis=2,\n    )\n    output = model.net.Squeeze(\n        output,\n        output,\n        dims=[0],\n    )\n    return output\n\n\n# Implement RecAtt due to section 4.1 in http://arxiv.org/abs/1601.03317\ndef apply_recurrent_attention(\n    model,\n    encoder_output_dim,\n    encoder_outputs_transposed,\n    weighted_encoder_outputs,\n    decoder_hidden_state_t,\n    decoder_hidden_state_dim,\n    attention_weighted_encoder_context_t_prev,\n    scope,\n    encoder_lengths=None,\n):\n    weighted_prev_attention_context = _apply_fc_weight_for_sum_match(\n        model=model,\n        input=attention_weighted_encoder_context_t_prev,\n        dim_in=encoder_output_dim,\n        dim_out=encoder_output_dim,\n        scope=scope,\n        name='weighted_prev_attention_context',\n    )\n\n    weighted_decoder_hidden_state = _apply_fc_weight_for_sum_match(\n        model=model,\n        input=decoder_hidden_state_t,\n        dim_in=decoder_hidden_state_dim,\n        dim_out=encoder_output_dim,\n        scope=scope,\n        name='weighted_decoder_hidden_state',\n    )\n    # [1, batch_size, encoder_output_dim]\n    decoder_hidden_encoder_outputs_sum_tmp = model.net.Add(\n        [\n            weighted_prev_attention_context,\n            weighted_decoder_hidden_state,\n        ],\n        s(scope, 'decoder_hidden_encoder_outputs_sum_tmp'),\n    )\n    # [encoder_length, batch_size, encoder_output_dim]\n    decoder_hidden_encoder_outputs_sum = model.net.Add(\n        [\n            weighted_encoder_outputs,\n            decoder_hidden_encoder_outputs_sum_tmp,\n        ],\n        s(scope, 'decoder_hidden_encoder_outputs_sum'),\n        broadcast=1,\n    )\n    attention_logits_transposed = _calc_attention_logits_from_sum_match(\n        model=model,\n        decoder_hidden_encoder_outputs_sum=decoder_hidden_encoder_outputs_sum,\n        encoder_output_dim=encoder_output_dim,\n        scope=scope,\n    )\n\n    # [batch_size, encoder_length, 1]\n    attention_weights_3d = _calc_attention_weights(\n        model=model,\n        attention_logits_transposed=attention_logits_transposed,\n        scope=scope,\n        encoder_lengths=encoder_lengths,\n    )\n\n    # [batch_size, encoder_output_dim, 1]\n    attention_weighted_encoder_context = _calc_weighted_context(\n        model=model,\n        encoder_outputs_transposed=encoder_outputs_transposed,\n        encoder_output_dim=encoder_output_dim,\n        attention_weights_3d=attention_weights_3d,\n        scope=scope,\n    )\n    return attention_weighted_encoder_context, attention_weights_3d, [\n        decoder_hidden_encoder_outputs_sum,\n    ]\n\n\ndef apply_regular_attention(\n    model,\n    encoder_output_dim,\n    encoder_outputs_transposed,\n    weighted_encoder_outputs,\n    decoder_hidden_state_t,\n    decoder_hidden_state_dim,\n    scope,\n    encoder_lengths=None,\n):\n    weighted_decoder_hidden_state = _apply_fc_weight_for_sum_match(\n        model=model,\n        input=decoder_hidden_state_t,\n        dim_in=decoder_hidden_state_dim,\n        dim_out=encoder_output_dim,\n        scope=scope,\n        name='weighted_decoder_hidden_state',\n    )\n\n    # [encoder_length, batch_size, encoder_output_dim]\n    decoder_hidden_encoder_outputs_sum = model.net.Add(\n        [weighted_encoder_outputs, weighted_decoder_hidden_state],\n        s(scope, 'decoder_hidden_encoder_outputs_sum'),\n        broadcast=1,\n        use_grad_hack=1,\n    )\n\n    attention_logits_transposed = _calc_attention_logits_from_sum_match(\n        model=model,\n        decoder_hidden_encoder_outputs_sum=decoder_hidden_encoder_outputs_sum,\n        encoder_output_dim=encoder_output_dim,\n        scope=scope,\n    )\n\n    # [batch_size, encoder_length, 1]\n    attention_weights_3d = _calc_attention_weights(\n        model=model,\n        attention_logits_transposed=attention_logits_transposed,\n        scope=scope,\n        encoder_lengths=encoder_lengths,\n    )\n\n    # [batch_size, encoder_output_dim, 1]\n    attention_weighted_encoder_context = _calc_weighted_context(\n        model=model,\n        encoder_outputs_transposed=encoder_outputs_transposed,\n        encoder_output_dim=encoder_output_dim,\n        attention_weights_3d=attention_weights_3d,\n        scope=scope,\n    )\n    return attention_weighted_encoder_context, attention_weights_3d, [\n        decoder_hidden_encoder_outputs_sum,\n    ]\n\n\ndef apply_dot_attention(\n    model,\n    encoder_output_dim,\n    # [batch_size, encoder_output_dim, encoder_length]\n    encoder_outputs_transposed,\n    # [1, batch_size, decoder_state_dim]\n    decoder_hidden_state_t,\n    decoder_hidden_state_dim,\n    scope,\n    encoder_lengths=None,\n):\n    if decoder_hidden_state_dim != encoder_output_dim:\n        weighted_decoder_hidden_state = brew.fc(\n            model,\n            decoder_hidden_state_t,\n            s(scope, 'weighted_decoder_hidden_state'),\n            dim_in=decoder_hidden_state_dim,\n            dim_out=encoder_output_dim,\n            axis=2,\n        )\n    else:\n        weighted_decoder_hidden_state = decoder_hidden_state_t\n\n    # [batch_size, decoder_state_dim]\n    squeezed_weighted_decoder_hidden_state = model.net.Squeeze(\n        weighted_decoder_hidden_state,\n        s(scope, 'squeezed_weighted_decoder_hidden_state'),\n        dims=[0],\n    )\n\n    # [batch_size, decoder_state_dim, 1]\n    expanddims_squeezed_weighted_decoder_hidden_state = model.net.ExpandDims(\n        squeezed_weighted_decoder_hidden_state,\n        squeezed_weighted_decoder_hidden_state,\n        dims=[2],\n    )\n\n    # [batch_size, encoder_output_dim, 1]\n    attention_logits_transposed = model.net.BatchMatMul(\n        [\n            encoder_outputs_transposed,\n            expanddims_squeezed_weighted_decoder_hidden_state,\n        ],\n        s(scope, 'attention_logits'),\n        trans_a=1,\n    )\n\n    # [batch_size, encoder_length, 1]\n    attention_weights_3d = _calc_attention_weights(\n        model=model,\n        attention_logits_transposed=attention_logits_transposed,\n        scope=scope,\n        encoder_lengths=encoder_lengths,\n    )\n\n    # [batch_size, encoder_output_dim, 1]\n    attention_weighted_encoder_context = _calc_weighted_context(\n        model=model,\n        encoder_outputs_transposed=encoder_outputs_transposed,\n        encoder_output_dim=encoder_output_dim,\n        attention_weights_3d=attention_weights_3d,\n        scope=scope,\n    )\n    return attention_weighted_encoder_context, attention_weights_3d, []\n\n\ndef apply_soft_coverage_attention(\n    model,\n    encoder_output_dim,\n    encoder_outputs_transposed,\n    weighted_encoder_outputs,\n    decoder_hidden_state_t,\n    decoder_hidden_state_dim,\n    scope,\n    encoder_lengths,\n    coverage_t_prev,\n    coverage_weights,\n):\n\n    weighted_decoder_hidden_state = _apply_fc_weight_for_sum_match(\n        model=model,\n        input=decoder_hidden_state_t,\n        dim_in=decoder_hidden_state_dim,\n        dim_out=encoder_output_dim,\n        scope=scope,\n        name='weighted_decoder_hidden_state',\n    )\n\n    # [encoder_length, batch_size, encoder_output_dim]\n    decoder_hidden_encoder_outputs_sum = model.net.Add(\n        [weighted_encoder_outputs, weighted_decoder_hidden_state],\n        s(scope, 'decoder_hidden_encoder_outputs_sum'),\n        broadcast=1,\n    )\n    # [batch_size, encoder_length]\n    coverage_t_prev_2d = model.net.Squeeze(\n        coverage_t_prev,\n        s(scope, 'coverage_t_prev_2d'),\n        dims=[0],\n    )\n    # [encoder_length, batch_size]\n    coverage_t_prev_transposed = brew.transpose(\n        model,\n        coverage_t_prev_2d,\n        s(scope, 'coverage_t_prev_transposed'),\n    )\n\n    # [encoder_length, batch_size, encoder_output_dim]\n    scaled_coverage_weights = model.net.Mul(\n        [coverage_weights, coverage_t_prev_transposed],\n        s(scope, 'scaled_coverage_weights'),\n        broadcast=1,\n        axis=0,\n    )\n\n    # [encoder_length, batch_size, encoder_output_dim]\n    decoder_hidden_encoder_outputs_sum = model.net.Add(\n        [decoder_hidden_encoder_outputs_sum, scaled_coverage_weights],\n        decoder_hidden_encoder_outputs_sum,\n    )\n\n    # [batch_size, encoder_length, 1]\n    attention_logits_transposed = _calc_attention_logits_from_sum_match(\n        model=model,\n        decoder_hidden_encoder_outputs_sum=decoder_hidden_encoder_outputs_sum,\n        encoder_output_dim=encoder_output_dim,\n        scope=scope,\n    )\n\n    # [batch_size, encoder_length, 1]\n    attention_weights_3d = _calc_attention_weights(\n        model=model,\n        attention_logits_transposed=attention_logits_transposed,\n        scope=scope,\n        encoder_lengths=encoder_lengths,\n    )\n\n    # [batch_size, encoder_output_dim, 1]\n    attention_weighted_encoder_context = _calc_weighted_context(\n        model=model,\n        encoder_outputs_transposed=encoder_outputs_transposed,\n        encoder_output_dim=encoder_output_dim,\n        attention_weights_3d=attention_weights_3d,\n        scope=scope,\n    )\n\n    # [batch_size, encoder_length]\n    attention_weights_2d = model.net.Squeeze(\n        attention_weights_3d,\n        s(scope, 'attention_weights_2d'),\n        dims=[2],\n    )\n\n    coverage_t = model.net.Add(\n        [coverage_t_prev, attention_weights_2d],\n        s(scope, 'coverage_t'),\n        broadcast=1,\n    )\n\n    return (\n        attention_weighted_encoder_context,\n        attention_weights_3d,\n        [decoder_hidden_encoder_outputs_sum],\n        coverage_t,\n    )\n"
  },
  {
    "path": "caffe2/python/benchmark_generator.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n#!/usr/bin/env python\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport string\n\nimport argparse\n\nimport numpy as np\n\nfrom caffe2.python.model_helper import ModelHelper\nfrom caffe2.python.predictor import mobile_exporter\nfrom caffe2.python import core, workspace, brew, utils\n\n\ndef parse_kwarg(kwarg_str):\n    key, value = map(string.strip, kwarg_str.split(\"=\", 1))\n    try:\n        value = int(value)\n    except ValueError:\n        try:\n            value = float(value)\n        except ValueError:\n            pass\n    return key, value\n\n\ndef main(args):\n    # User defined keyword arguments\n    kwargs = {\"order\": \"NCHW\"}\n    kwargs.update(dict(args.kwargs))\n\n    model = ModelHelper(name=args.benchmark_name)\n\n    op_type = args.operator  # assumes a brew type op name\n    input_name = args.input_name\n    output_name = args.output_name\n\n    iters = int(args.iters)\n    for i in range(iters):\n        input_blob_name = input_name + (str(i) if i > 0 and args.chain else '')\n        output_blob_name = output_name + str(i + 1)\n        add_op = getattr(brew, op_type)\n        add_op(model, input_blob_name, output_blob_name, **kwargs)\n        if args.chain:\n            input_name, output_name = output_name, input_name\n\n    workspace.RunNetOnce(model.param_init_net)\n    extra_init_net_ops = []\n\n    def make_blob_on_context(blob_name, blob_data, context):\n        if context.upper() != \"CPU\":\n            blob_name_modified = \"{}_CPU\".format(blob_name)\n        else:  # CPU case is simple\n            blob_name_modified = blob_name\n\n        fill_op = core.CreateOperator(\n            \"GivenTensorFill\", [], [blob_name_modified],\n            arg=[\n                utils.MakeArgument(\"shape\", blob_data.shape),\n                utils.MakeArgument(\"values\", blob_data)\n            ]\n        )\n        extra_init_net_ops.append(fill_op)\n\n        # We need to create CPU blobs and add some copy operations in\n        # the init_net\n        if context.upper() == \"OPENGL\":\n            copy_op = core.CreateOperator(\"CopyToOpenGL\", [blob_name_modified],\n                                          [blob_name])\n            extra_init_net_ops.append(copy_op)\n\n    for unparsed_blob in args.blob:\n        name, unparsed_dims = unparsed_blob.split('=')\n        dims = [int(d) for d in unparsed_dims.split(',')]\n        np_input = np.random.rand(*dims).astype(np.float32)\n        make_blob_on_context(name, np_input, args.context)\n\n    init_net, predict_net = mobile_exporter.Export(\n        workspace, model.net, model.params\n    )\n    init_net.op.extend(extra_init_net_ops)\n\n    # Handle manual rewrite\n    if args.context.upper() == \"OPENGL\":\n        old_ops = [op for op in predict_net.op]\n        del predict_net.op[:]\n        for op in old_ops:\n            op.type = 'OpenGL{}'.format(op.type)\n        predict_net.op.extend(old_ops)\n\n    if args.debug:\n        print(\"init_net:\")\n        for op in init_net.op:\n            print(\" \", op.type, op.input, \"-->\", op.output)\n        print(\"predict_net:\")\n        for op in predict_net.op:\n            print(\" \", op.type, op.input, \"-->\", op.output)\n\n    with open(args.predict_net, 'wb') as f:\n        f.write(predict_net.SerializeToString())\n    with open(args.init_net, 'wb') as f:\n        f.write(init_net.SerializeToString())\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(\n        description=\"Utilitity to generate Caffe2 benchmark models.\")\n    parser.add_argument(\"operator\", help=\"Caffe2 operator to benchmark.\")\n    parser.add_argument(\"-b\", \"--blob\",\n                        help=\"Instantiate a blob --blob name=dim1,dim2,dim3\",\n                        action='append')\n    parser.add_argument(\"--context\", help=\"Context to run on.\", default=\"CPU\")\n    parser.add_argument(\"--kwargs\", help=\"kwargs to pass to operator.\",\n                        nargs=\"*\", type=parse_kwarg, default=[])\n    parser.add_argument(\"--init_net\", help=\"Output initialization net.\",\n                        default=\"init_net.pb\")\n    parser.add_argument(\"--predict_net\", help=\"Output prediction net.\",\n                        default=\"predict_net.pb\")\n    parser.add_argument(\"--benchmark_name\",\n                        help=\"Name of the benchmark network\",\n                        default=\"benchmark\")\n    parser.add_argument(\"--input_name\", help=\"Name of the input blob.\",\n                        default=\"data\")\n    parser.add_argument(\"--output_name\", help=\"Name of the output blob.\",\n                        default=\"output\")\n    parser.add_argument(\"--iters\",\n                        help=\"Number of iterations to run the operator.\",\n                        default=\"1\")\n    parser.add_argument(\"-d\", \"--debug\", help=\"Print debug information.\",\n                        action='store_true')\n    parser.add_argument(\"-c\", \"--chain\",\n                        help=\"Chain ops together (create data dependencies)\",\n                        action='store_true')\n    args = parser.parse_args()\n    main(args)\n"
  },
  {
    "path": "caffe2/python/binarysize.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n\"\"\"A tool to inspect the binary size of a built binary file.\n\nThis script prints out a tree of symbols and their corresponding sizes, using\nLinux's nm functionality.\n\nUsage:\n\n    python binary_size.py -- \\\n            --target=/path/to/your/target/binary \\\n            [--nm_command=/path/to/your/custom/nm] \\\n            [--max_depth=10] [--min_size=1024] \\\n            [--color] \\\n\nTo assist visualization, pass in '--color' to make the symbols color coded to\ngreen, assuming that you have a xterm connection that supports color.\n\"\"\"\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport argparse\nimport subprocess\nimport sys\n\n\nclass Trie(object):\n    \"\"\"A simple class that represents a Trie.\"\"\"\n\n    def __init__(self, name):\n        \"\"\"Initializes a Trie object.\"\"\"\n        self.name = name\n        self.size = 0\n        self.dictionary = {}\n\n\ndef GetSymbolTrie(target, nm_command, max_depth):\n    \"\"\"Gets a symbol trie with the passed in target.\n\n    Args:\n            target: the target binary to inspect.\n            nm_command: the command to run nm.\n            max_depth: the maximum depth to create the trie.\n    \"\"\"\n    # Run nm to get a dump on the strings.\n    proc = subprocess.Popen(\n        [nm_command, '--radix=d', '--size-sort', '--print-size', target],\n        stdout=subprocess.PIPE, stderr=subprocess.STDOUT)\n    nm_out, _ = proc.communicate()\n    if proc.returncode != 0:\n        print('NM command failed. Output is as follows:')\n        print(nm_out)\n        sys.exit(1)\n    # Run c++filt to get proper symbols.\n    proc = subprocess.Popen(['c++filt'],\n                            stdin=subprocess.PIPE, stdout=subprocess.PIPE,\n                            stderr=subprocess.STDOUT)\n    out, _ = proc.communicate(input=nm_out)\n    if proc.returncode != 0:\n        print('c++filt failed. Output is as follows:')\n        print(out)\n        sys.exit(1)\n    # Splits the output to size and function name.\n    data = []\n    for line in out.split('\\n'):\n        if line:\n            content = line.split(' ')\n            if len(content) < 4:\n                # This is a line not representing symbol sizes. skip.\n                continue\n            data.append([int(content[1]), ' '.join(content[3:])])\n    symbol_trie = Trie('')\n    for size, name in data:\n        curr = symbol_trie\n        for c in name:\n            if c not in curr.dictionary:\n                curr.dictionary[c] = Trie(curr.name + c)\n            curr = curr.dictionary[c]\n            curr.size += size\n            if len(curr.name) > max_depth:\n                break\n    symbol_trie.size = sum(t.size for t in symbol_trie.dictionary.values())\n    return symbol_trie\n\n\ndef MaybeAddColor(s, color):\n    \"\"\"Wrap the input string to the xterm green color, if color is set.\n    \"\"\"\n    if color:\n        return '\\033[92m{0}\\033[0m'.format(s)\n    else:\n        return s\n\n\ndef ReadableSize(num):\n    \"\"\"Get a human-readable size.\"\"\"\n    for unit in ['B', 'KB', 'MB', 'GB']:\n        if abs(num) <= 1024.0:\n            return '%3.2f%s' % (num, unit)\n        num /= 1024.0\n    return '%.1f TB' % (num,)\n\n\n# Note(jiayq): I know, I know, this is a recursive function, but it is\n# convenient to write.\ndef PrintTrie(trie, prefix, max_depth, min_size, color):\n    \"\"\"Prints the symbol trie in a readable manner.\n    \"\"\"\n    if len(trie.name) == max_depth or not trie.dictionary.keys():\n        # If we are reaching a leaf node or the maximum depth, we will print the\n        # result.\n        if trie.size > min_size:\n            print('{0}{1} {2}'.format(\n                  prefix,\n                  MaybeAddColor(trie.name, color),\n                  ReadableSize(trie.size)))\n    elif len(trie.dictionary.keys()) == 1:\n        # There is only one child in this dictionary, so we will just delegate\n        # to the downstream trie to print stuff.\n        PrintTrie(\n            trie.dictionary.values()[0], prefix, max_depth, min_size, color)\n    elif trie.size > min_size:\n        print('{0}{1} {2}'.format(\n              prefix,\n              MaybeAddColor(trie.name, color),\n              ReadableSize(trie.size)))\n        keys_with_sizes = [\n            (k, trie.dictionary[k].size) for k in trie.dictionary.keys()]\n        keys_with_sizes.sort(key=lambda x: x[1])\n        for k, _ in keys_with_sizes[::-1]:\n            PrintTrie(\n                trie.dictionary[k], prefix + ' |', max_depth, min_size, color)\n\n\ndef main(argv):\n    if not sys.platform.startswith('linux'):\n        raise RuntimeError('Currently this tool only supports Linux.')\n    parser = argparse.ArgumentParser(\n        description=\"Tool to inspect binary size.\")\n    parser.add_argument(\n        '--max_depth', type=int, default=10,\n        help='The maximum depth to print the symbol tree.')\n    parser.add_argument(\n        '--min_size', type=int, default=1024,\n        help='The mininum symbol size to print.')\n    parser.add_argument(\n        '--nm_command', type=str, default='nm',\n        help='The path to the nm command that the tool needs.')\n    parser.add_argument(\n        '--color', action='store_true',\n        help='If set, use ascii color for output.')\n    parser.add_argument(\n        '--target', type=str,\n        help='The binary target to inspect.')\n    args = parser.parse_args(argv)\n    if not args.target:\n        raise RuntimeError('You must specify a target to inspect.')\n    symbol_trie = GetSymbolTrie(\n        args.target, args.nm_command, args.max_depth)\n    PrintTrie(symbol_trie, '', args.max_depth, args.min_size, args.color)\n\n\nif __name__ == '__main__':\n    main(sys.argv[1:])\n"
  },
  {
    "path": "caffe2/python/brew.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package model_helper_api\n# Module caffe2.python.model_helper_api\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport sys\nimport copy\nimport inspect\nfrom past.builtins import basestring\nfrom caffe2.python.model_helper import ModelHelper\n\n# flake8: noqa\nfrom caffe2.python.helpers.algebra import *\nfrom caffe2.python.helpers.arg_scope import *\nfrom caffe2.python.helpers.array_helpers import *\nfrom caffe2.python.helpers.control_ops import *\nfrom caffe2.python.helpers.conv import *\nfrom caffe2.python.helpers.db_input import *\nfrom caffe2.python.helpers.dropout import *\nfrom caffe2.python.helpers.elementwise_linear import *\nfrom caffe2.python.helpers.fc import *\nfrom caffe2.python.helpers.nonlinearity import *\nfrom caffe2.python.helpers.normalization import *\nfrom caffe2.python.helpers.pooling import *\nfrom caffe2.python.helpers.tools import *\nfrom caffe2.python.helpers.train import *\n\n\nclass HelperWrapper(object):\n    _registry = {\n        'arg_scope': arg_scope,\n        'fc': fc,\n        'packed_fc': packed_fc,\n        'fc_decomp': fc_decomp,\n        'fc_sparse': fc_sparse,\n        'fc_prune': fc_prune,\n        'dropout': dropout,\n        'max_pool': max_pool,\n        'average_pool': average_pool,\n        'max_pool_with_index' : max_pool_with_index,\n        'lrn': lrn,\n        'softmax': softmax,\n        'instance_norm': instance_norm,\n        'spatial_bn': spatial_bn,\n        'relu': relu,\n        'prelu': prelu,\n        'tanh': tanh,\n        'concat': concat,\n        'depth_concat': depth_concat,\n        'sum': sum,\n        'transpose': transpose,\n        'iter': iter,\n        'accuracy': accuracy,\n        'conv': conv,\n        'conv_nd': conv_nd,\n        'conv_transpose': conv_transpose,\n        'group_conv': group_conv,\n        'group_conv_deprecated': group_conv_deprecated,\n        'image_input': image_input,\n        'video_input': video_input,\n        'add_weight_decay': add_weight_decay,\n        'elementwise_linear': elementwise_linear,\n        'layer_norm': layer_norm,\n        'batch_mat_mul' : batch_mat_mul,\n        'cond' : cond,\n        'loop' : loop,\n        'db_input' : db_input,\n    }\n\n    def __init__(self, wrapped):\n        self.wrapped = wrapped\n\n    def __getattr__(self, helper_name):\n        if helper_name not in self._registry:\n            raise AttributeError(\n                \"Helper function {} not \"\n                \"registered.\".format(helper_name)\n            )\n\n        def scope_wrapper(*args, **kwargs):\n            new_kwargs = {}\n            if helper_name != 'arg_scope':\n                if len(args) > 0 and isinstance(args[0], ModelHelper):\n                    model = args[0]\n                elif 'model' in kwargs:\n                    model = kwargs['model']\n                else:\n                    raise RuntimeError(\n                \"The first input of helper function should be model. \" \\\n                \"Or you can provide it in kwargs as model=<your_model>.\")\n                new_kwargs = copy.deepcopy(model.arg_scope)\n            func = self._registry[helper_name]\n            var_names, _, varkw, _= inspect.getargspec(func)\n            if varkw is None:\n                # this helper function does not take in random **kwargs\n                new_kwargs = {\n                    var_name: new_kwargs[var_name]\n                    for var_name in var_names if var_name in new_kwargs\n                }\n\n            cur_scope = get_current_scope()\n            new_kwargs.update(cur_scope.get(helper_name, {}))\n            new_kwargs.update(kwargs)\n            return func(*args, **new_kwargs)\n\n        scope_wrapper.__name__ = helper_name\n        return scope_wrapper\n\n    def Register(self, helper):\n        name = helper.__name__\n        if name in self._registry:\n            raise AttributeError(\n                \"Helper {} already exists. Please change your \"\n                \"helper name.\".format(name)\n            )\n        self._registry[name] = helper\n\n    def has_helper(self, helper_or_helper_name):\n        helper_name = (\n            helper_or_helper_name\n            if isinstance(helper_or_helper_name, basestring) else\n            helper_or_helper_name.__name__\n        )\n        return helper_name in self._registry\n\n\nsys.modules[__name__] = HelperWrapper(sys.modules[__name__])\n"
  },
  {
    "path": "caffe2/python/brew_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import brew, core, scope, workspace\nfrom caffe2.python.modeling.parameter_info import ParameterTags\nfrom caffe2.python.model_helper import ModelHelper\nfrom caffe2.python.cnn import CNNModelHelper\n\nimport unittest\nimport numpy as np\n\n\nclass BrewTest(unittest.TestCase):\n    def setUp(self):\n\n        def myhelper(model, val=-1):\n            return val\n\n        if not brew.has_helper(myhelper):\n            brew.Register(myhelper)\n        self.myhelper = myhelper\n\n        def myhelper2(model, val=-1):\n            return val\n\n        if not brew.has_helper(myhelper2):\n            brew.Register(myhelper2)\n        self.myhelper2 = myhelper2\n        self.model = ModelHelper(name=\"test_model\")\n\n    def test_dropout(self):\n        p = 0.2\n        X = np.ones((100, 100)).astype(np.float32) - p\n        workspace.FeedBlob(\"x\", X)\n        model = ModelHelper(name=\"test_model\")\n        brew.dropout(model, \"x\", \"out\", is_test=False)\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        out = workspace.FetchBlob(\"out\")\n        self.assertLess(abs(out.mean() - (1 - p)), 0.05)\n\n    def test_fc(self):\n        m, n, k = (15, 15, 15)\n        X = np.random.rand(m, k).astype(np.float32) - 0.5\n\n        workspace.FeedBlob(\"x\", X)\n        model = ModelHelper(name=\"test_model\")\n        brew.fc(model, \"x\", \"out_1\", k, n)\n        model.Validate()\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n\n    def test_relu(self):\n        Xpos = np.ones((5, 5)).astype(np.float32) - 0.5\n        Xneg = np.ones((5, 5)).astype(np.float32) - 1.5\n\n        workspace.FeedBlob(\"xpos\", Xpos)\n        workspace.FeedBlob(\"xneg\", Xneg)\n        model = ModelHelper(name=\"test_model\")\n        brew.relu(model, \"xpos\", \"out_xpos\")\n        brew.relu(model, \"xneg\", \"out_xneg\")\n        model.Validate()\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n\n        pos = workspace.FetchBlob(\"out_xpos\")\n        self.assertAlmostEqual(pos.mean(), 0.5)\n        neg = workspace.FetchBlob(\"out_xneg\")\n        self.assertAlmostEqual(neg.mean(), 0)\n\n    def test_tanh(self):\n        X = np.ones((5, 5)).astype(np.float32) - 0.5\n\n        workspace.FeedBlob(\"x\", X)\n        model = ModelHelper(name=\"test_model\")\n        brew.tanh(model, \"x\", \"out_tanh\")\n        model.Validate()\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n\n        out = workspace.FetchBlob(\"out_tanh\")\n        self.assertAlmostEqual(out.mean(), 0.46211711)\n\n    def test_validate(self):\n        model = ModelHelper(name=\"test_model\")\n        model.params.append(\"aaa\")\n        model.params.append(\"bbb\")\n        self.assertEqual(model._Validate(), [])\n\n        model.params.append(\"xxx\")\n        model.params.append(\"bbb\")\n        self.assertEqual(model._Validate(), [\"bbb\"])\n\n    def test_arg_scope(self):\n        myhelper = self.myhelper\n        myhelper2 = self.myhelper2\n        n = 15\n        with brew.arg_scope([myhelper], val=n):\n            res = brew.myhelper(self.model)\n        self.assertEqual(n, res)\n\n        with brew.arg_scope([myhelper, myhelper2], val=n):\n            res1 = brew.myhelper(self.model)\n            res2 = brew.myhelper2(self.model)\n        self.assertEqual([n, n], [res1, res2])\n\n    def test_arg_scope_single(self):\n        X = np.random.rand(64, 3, 32, 32).astype(np.float32) - 0.5\n\n        workspace.FeedBlob(\"x\", X)\n        model = ModelHelper(name=\"test_model\")\n        with brew.arg_scope(\n            brew.conv,\n            stride=2,\n            pad=2,\n            weight_init=('XavierFill', {}),\n            bias_init=('ConstantFill', {})\n        ):\n            brew.conv(\n                model=model,\n                blob_in=\"x\",\n                blob_out=\"out\",\n                dim_in=3,\n                dim_out=64,\n                kernel=3,\n            )\n        model.Validate()\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        out = workspace.FetchBlob(\"out\")\n        self.assertEqual(out.shape, (64, 64, 17, 17))\n\n    def test_arg_scope_nested(self):\n        myhelper = self.myhelper\n        n = 16\n        with brew.arg_scope([myhelper], val=-3), \\\n                brew.arg_scope([myhelper], val=-2):\n            with brew.arg_scope([myhelper], val=n):\n                res = brew.myhelper(self.model)\n                self.assertEqual(n, res)\n            res = brew.myhelper(self.model)\n            self.assertEqual(res, -2)\n\n        res = brew.myhelper(self.model, val=15)\n        self.model.Validate()\n        self.assertEqual(res, 15)\n\n    def test_double_register(self):\n        myhelper = self.myhelper\n        with self.assertRaises(AttributeError):\n            brew.Register(myhelper)\n\n    def test_has_helper(self):\n        self.assertTrue(brew.has_helper(brew.conv))\n        self.assertTrue(brew.has_helper(\"conv\"))\n\n        def myhelper3():\n            pass\n\n        self.assertFalse(brew.has_helper(myhelper3))\n\n    def test_model_helper(self):\n        X = np.random.rand(64, 32, 32, 3).astype(np.float32) - 0.5\n\n        workspace.FeedBlob(\"x\", X)\n        my_arg_scope = {'order': 'NHWC'}\n        model = ModelHelper(name=\"test_model\", arg_scope=my_arg_scope)\n        with brew.arg_scope(\n            brew.conv,\n            stride=2,\n            pad=2,\n            weight_init=('XavierFill', {}),\n            bias_init=('ConstantFill', {})\n        ):\n            brew.conv(\n                model=model,\n                blob_in=\"x\",\n                blob_out=\"out\",\n                dim_in=3,\n                dim_out=64,\n                kernel=[8, 3]\n            )\n        model.Validate()\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        out = workspace.FetchBlob(\"out\")\n        self.assertEqual(out.shape, (64, 15, 17, 64))\n\n    def test_cnn_model_helper_deprecated(self):\n        X = np.random.rand(64, 32, 32, 3).astype(np.float32) - 0.5\n\n        workspace.FeedBlob(\"x\", X)\n        # CNNModelHelper is going to be deprecated soon. This test is only\n        # covering some CNNModelHelper logic\n        model = CNNModelHelper(name=\"test_model\", order='NHWC')\n        self.assertEqual(model.arg_scope['order'], 'NHWC')\n\n    def test_get_params(self):\n        def param(x):\n            return core.ScopedBlobReference(x)\n\n        def to_str_list(x):\n            return sorted([str(p) for p in x])\n\n        model = ModelHelper(name=\"test_model\")\n        model.AddParameter(param(\"a\"))\n        model.AddParameter(param(\"b\"), tags=ParameterTags.COMPUTED_PARAM)\n        with scope.NameScope(\"c\"):\n            model.AddParameter(param(\"a\"))\n            model.AddParameter(param(\"d\"), tags=ParameterTags.COMPUTED_PARAM)\n            self.assertEqual(to_str_list(model.GetParams()), ['c/a'])\n            self.assertEqual(to_str_list(model.GetComputedParams()), ['c/d'])\n            self.assertEqual(to_str_list(model.GetAllParams()), ['c/a', 'c/d'])\n            # Get AllParams from the global Scope\n            self.assertEqual(to_str_list(model.GetAllParams('')), [\n                             'a', 'b', 'c/a', 'c/d'])\n        self.assertEqual(to_str_list(model.GetParams()), ['a', 'c/a'])\n        self.assertEqual(to_str_list(model.GetComputedParams()), ['b', 'c/d'])\n        self.assertEqual(to_str_list(model.GetAllParams()),\n                         ['a', 'b', 'c/a', 'c/d'])\n        self.assertEqual(to_str_list(model.GetAllParams('')),\n                         ['a', 'b', 'c/a', 'c/d'])\n        # Get AllParams from the scope 'c'\n        self.assertEqual(to_str_list(model.GetAllParams('c')), ['c/a', 'c/d'])\n        self.assertEqual(to_str_list(model.GetAllParams('c/')), ['c/a', 'c/d'])\n\n    def test_param_consistence(self):\n        model = ModelHelper(name='test_mode')\n        cnv = brew.conv(model, 'data', 'cnv', 32, 32, 4)\n        step_model = ModelHelper(name='step_model', param_model=model)\n        a = brew.fc(step_model, cnv, 'a', 100, 200)\n        brew.fc(model, a, 'b', 200, 5)\n        # test the _parameters_info is shared between model and step_model\n        self.assertEqual(model._parameters_info, step_model._parameters_info)\n\n    def test_cond(self):\n        workspace.FeedBlob(\"cond\", np.array(True))\n        workspace.FeedBlob(\"then_value\", np.array(1))\n        workspace.FeedBlob(\"else_value\", np.array(2))\n\n        then_model = ModelHelper(name=\"then_test_model\")\n        then_model.net.Copy(\"then_value\", \"output_blob\")\n\n        else_model = ModelHelper(name=\"else_test_model\")\n        else_model.net.Copy(\"else_value\", \"output_blob\")\n\n        model = ModelHelper(name=\"test_model\")\n        brew.cond(\n            model=model,\n            cond_blob=\"cond\",\n            external_blobs=[\"then_value\", \"else_value\", \"output_blob\"],\n            then_model=then_model,\n            else_model=else_model)\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        output_value = workspace.FetchBlob(\"output_blob\")\n        self.assertEqual(output_value, 1)\n        workspace.FeedBlob(\"cond\", np.array(False))\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        output_value = workspace.FetchBlob(\"output_blob\")\n        self.assertEqual(output_value, 2)\n\n    def test_loop(self):\n        workspace.FeedBlob(\"cond\", np.array(True))\n        workspace.FeedBlob(\"ONE\", np.array(1))\n        workspace.FeedBlob(\"TWO\", np.array(2))\n        workspace.FeedBlob(\"TEN\", np.array(10))\n        workspace.FeedBlob(\"counter\", np.array(0))\n        workspace.FeedBlob(\"output_blob\", np.array(0))\n\n        loop_model = ModelHelper(name=\"loop_test_model\")\n        loop_model.net.Add([\"output_blob\", \"TWO\"], \"output_blob\")\n\n        cond_model = ModelHelper(name=\"cond_test_model\")\n        cond_model.net.Add([\"counter\", \"ONE\"], \"counter\")\n        comp_res = cond_model.net.LT([\"counter\", \"TEN\"])\n        cond_model.net.Copy(comp_res, \"cond\")\n\n        model = ModelHelper(name=\"test_model\")\n        brew.loop(\n            model=model,\n            cond_blob=\"cond\",\n            external_blobs=[\"cond\", \"ONE\", \"TWO\", \"TEN\", \"counter\", \"output_blob\"],\n            loop_model=loop_model,\n            cond_model=cond_model)\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        output_value = workspace.FetchBlob(\"output_blob\")\n        self.assertEqual(output_value, 18)\n\n\n@unittest.skipIf(not workspace.has_gpu_support, \"No gpu support.\")\nclass BrewGPUTest(unittest.TestCase):\n    def test_relu(self):\n        Xpos = np.ones((5, 5)).astype(np.float32) - 0.5\n        Xneg = np.ones((5, 5)).astype(np.float32) - 1.5\n\n        workspace.FeedBlob(\"xpos\", Xpos)\n        workspace.FeedBlob(\"xneg\", Xneg)\n        model = ModelHelper(name=\"test_model\")\n        brew.relu(model, \"xpos\", \"out_xpos\", use_cudnn=True)\n        brew.relu(model, \"xneg\", \"out_xneg\", use_cudnn=True)\n        model.Validate()\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n\n        pos = workspace.FetchBlob(\"out_xpos\")\n        self.assertAlmostEqual(pos.mean(), 0.5)\n        neg = workspace.FetchBlob(\"out_xneg\")\n        self.assertAlmostEqual(neg.mean(), 0)\n\n    def test_tanh(self):\n        X = np.ones((5, 5)).astype(np.float32) - 0.5\n\n        workspace.FeedBlob(\"x\", X)\n        model = ModelHelper(name=\"test_model\")\n        brew.tanh(model, \"x\", \"out_tanh\", use_cudnn=True)\n        model.Validate()\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n\n        out = workspace.FetchBlob(\"out_tanh\")\n        self.assertAlmostEqual(out.mean(), 0.46211711)\n"
  },
  {
    "path": "caffe2/python/build.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport caffe2.python._import_c_extension as C\n\nCAFFE2_NO_OPERATOR_SCHEMA = C.define_caffe2_no_operator_schema\nbuild_options = C.get_build_options()\n"
  },
  {
    "path": "caffe2/python/cached_reader.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package cached_reader\n# Module caffe2.python.cached_reader\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport os\n\nfrom caffe2.python import core\nfrom caffe2.python.dataio import Reader\nfrom caffe2.python.dataset import Dataset\nfrom caffe2.python.pipeline import pipe\nfrom caffe2.python.task import Cluster, TaskGroup\n\n\nclass CachedReader(Reader):\n    \"\"\"\n    Reader with persistent in-file cache.\n\n    Example usage:\n    cached_reader = CachedReader(reader)\n    build_cache_step = cached_reader.build_cache('/tmp/cache.db')\n    with LocalSession() as session:\n        session.run(build_cache_step)\n\n    Every time new reader is created, it's expected that build_cache will be\n    called before setup_ex and usage of the reader. build_cache will check\n    existence of provided file path and in case it's missing will initialize it\n    by reading data from original reader. All consequent attempts to read will\n    ignore original reader (i.e. no additional data will be read from it).\n    \"\"\"\n\n    def __init__(self, reader, db_type='leveldb', name='cached_reader'):\n        super(CachedReader, self).__init__(reader.schema())\n        self.original_reader = reader\n        self.cache_path = None\n        self.ds_reader = None\n        self.ds = Dataset(self._schema, name)\n        self.db_type = db_type\n        self.name = name\n        self.field_names = self._schema.field_names()\n\n    def setup_ex(self, init_net, finish_net):\n        assert self.cache_path, 'build_cache must be called first'\n        self._init_dataset(init_net)\n        self._load_from_file(init_net)\n        self.ds_reader = self.ds.reader(init_net, batch_size=100)\n\n    def read(self, read_net):\n        assert self.ds_reader, 'setup must be called first'\n        return self.ds_reader.read(read_net)\n\n    def has_cache(self):\n        return self.cache_path and os.path.exists(self.cache_path)\n\n    def build_cache(self, cache_path, overwrite=False):\n        if not self.has_cache() or overwrite:\n            self.cache_path = cache_path\n        if self.has_cache() and not overwrite:\n            # cache already exists, no need to rebuild it\n            return core.execution_step('build_step', [])\n\n        init_net = core.Net('init')\n        self._init_dataset(init_net)\n        with Cluster(), core.NameScope(self.name), TaskGroup() as copy_tg:\n            pipe(self.original_reader, self.ds.writer(), num_threads=16)\n            copy_step = copy_tg.to_task().get_step()\n        save_net = core.Net('save')\n        self._save_to_file(save_net)\n\n        return core.execution_step('build_cache', [init_net, copy_step, save_net])\n\n    def _init_dataset(self, init_net):\n        with core.NameScope(self.name):\n            self.ds.init_empty(init_net)\n\n    def _save_to_file(self, net):\n        net.Save(\n            self.ds.content().field_blobs(),\n            [],\n            db=self.cache_path,\n            db_type=self.db_type,\n            blob_name_overrides=self.field_names,\n            absolute_path=True,\n        )\n\n    def _load_from_file(self, net):\n        net.Load(\n            [],\n            self.ds.content().field_blobs(),\n            db=self.cache_path,\n            db_type=self.db_type,\n            absolute_path=True,\n            source_blob_names=self.field_names,\n        )\n"
  },
  {
    "path": "caffe2/python/caffe_translator.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package caffe_translator\n# Module caffe2.python.caffe_translator\n#!/usr/bin/env python2\n\nimport argparse\nimport copy\nimport logging\nimport re\nimport numpy as np  # noqa\n\nfrom caffe2.proto import caffe2_pb2, caffe2_legacy_pb2\nfrom caffe.proto import caffe_pb2\nfrom caffe2.python import core, utils, workspace\nfrom google.protobuf import text_format\n\nlogging.basicConfig()\nlog = logging.getLogger(\"caffe_translator\")\nlog.setLevel(logging.INFO)\n\n\ndef _StateMeetsRule(state, rule):\n    \"\"\"A function that reproduces Caffe's StateMeetsRule functionality.\"\"\"\n    if rule.HasField('phase') and rule.phase != state.phase:\n        return False\n    if rule.HasField('min_level') and state.level < rule.min_level:\n        return False\n    if rule.HasField('max_level') and state.level > rule.max_level:\n        return False\n    curr_stages = set(list(state.stage))\n    # all stages in rule.stages should be in, otherwise it's not a match.\n    if len(rule.stage) and any([s not in curr_stages for s in rule.stage]):\n        return False\n    # none of the stage in rule.stages should be in, otherwise it's not a match.\n    if len(rule.not_stage) and any([s in curr_stages for s in rule.not_stage]):\n        return False\n    # If none of the nonmatch happens, return True.\n    return True\n\n\ndef _ShouldInclude(net_state, layer):\n    \"\"\"A function that reproduces Caffe's inclusion and exclusion rule.\"\"\"\n    ret = (len(layer.include) == 0)\n    # check exclude rules: if any exclusion is met, we shouldn't include.\n    ret &= not any([_StateMeetsRule(net_state, rule) for rule in layer.exclude])\n    if len(layer.include):\n        # check include rules: if any inclusion is met, we should include.\n        ret |= any([_StateMeetsRule(net_state, rule) for rule in layer.include])\n    return ret\n\n\ndef _GetLegacyDims(net, net_params, dummy_input, legacy_pad_ops):\n    dim_map = {}\n    ws = workspace.C.Workspace()\n    for param in net_params.protos:\n        ws.create_blob(param.name) \\\n            .feed(utils.Caffe2TensorToNumpyArray(param))\n    external_input = net.op[0].input[0]\n    ws.create_blob(external_input).feed(dummy_input)\n    # Get dimensions with legacy pad\n    for i in range(len(net.op)):\n        op_def = net.op[i]\n        ws._run_operator(op_def.SerializeToString())\n        if i in legacy_pad_ops:\n            output = op_def.output[0]\n            blob_legacy = ws.fetch_blob(output)\n            dim_map[i] = blob_legacy.shape\n    return dim_map\n\n\ndef _GetLegacyPadArgs(op_def, arg_map):\n    pads = {}\n    keys = ['pad_l', 'pad_t', 'pad_r', 'pad_b']\n    is_pad = 'pad' in arg_map\n    if is_pad:\n        for k in keys:\n            pads[k] = arg_map['pad'].i\n    else:\n        pads = {x: arg_map[x].i for x in keys}\n    return pads\n\n\ndef _AdjustDims(op_def, arg_map, pads, dim1, dim2):\n    n1, c1, h1, w1 = dim1\n    n2, c2, h2, w2 = dim2\n    assert(n1 == n2)\n    assert(c1 == c2)\n    is_pad = 'pad' in arg_map\n    if h1 != h2 or w1 != w2:\n        if h1 == h2 + 1:\n            pads['pad_b'] += 1\n        elif h1 != h2:\n            raise Exception(\"Unexpected dimensions for height:\", h1, h2)\n        if w1 == w2 + 1:\n            pads['pad_r'] += 1\n        elif w1 != w2:\n            raise Exception(\"Unexpected dimensions for width:\", w1, w2)\n        if is_pad:\n            op_def.arg.remove(arg_map['pad'])\n            args = []\n            for name in pads.keys():\n                arg = caffe2_pb2.Argument()\n                arg.name = name\n                arg.i = pads[name]\n                args.append(arg)\n            op_def.arg.extend(args)\n        else:\n            for name in pads.keys():\n                arg_map[name].i = pads[name]\n\n\ndef _RemoveLegacyPad(net, net_params, input_dims):\n    legacy_pad_ops = []\n    for i in range(len(net.op)):\n        op_def = net.op[i]\n        if re.match(r'^(Conv|ConvTranspose|MaxPool|AveragePool)(\\dD)?$',\n                    op_def.type):\n            for arg in op_def.arg:\n                if arg.name == 'legacy_pad':\n                    legacy_pad_ops.append(i)\n                    break\n    if legacy_pad_ops:\n        n, c, h, w = input_dims\n        dummy_input = np.random.randn(n, c, h, w).astype(np.float32)\n        dim_map = _GetLegacyDims(net, net_params, dummy_input, legacy_pad_ops)\n\n        # Running with the legacy pad argument removed\n        # compare the dimensions and adjust pad argument when necessary\n        ws = workspace.C.Workspace()\n\n        external_input = net.op[0].input[0]\n        ws.create_blob(external_input).feed_blob(dummy_input)\n        for param in net_params.protos:\n            ws.create_blob(param.name) \\\n              .feed_blob(utils.Caffe2TensorToNumpyArray(param))\n\n        for i in range(len(net.op)):\n            op_def = net.op[i]\n            if i in legacy_pad_ops:\n                arg_map = {}\n                for arg in op_def.arg:\n                    arg_map[arg.name] = arg\n                pads = _GetLegacyPadArgs(op_def, arg_map)\n                # remove legacy pad arg\n                for j in range(len(op_def.arg)):\n                    arg = op_def.arg[j]\n                    if arg.name == 'legacy_pad':\n                        del op_def.arg[j]\n                        break\n                output = op_def.output[0]\n                # use a new name to avoid the interference with inplace\n                nonlegacy_output = output + '_nonlegacy'\n                op_def.output[0] = nonlegacy_output\n                ws._run_operator(op_def.SerializeToString())\n                blob_nonlegacy = ws.fetch_blob(nonlegacy_output)\n                # reset output name\n                op_def.output[0] = output\n\n                dim1 = dim_map[i]\n                dim2 = blob_nonlegacy.shape\n                _AdjustDims(op_def, arg_map, pads, dim1, dim2)\n\n            ws._run_operator(op_def.SerializeToString())\n    return net\n\n\ndef _GetBlobDimMap(net, net_params, dummy_input):\n    dim_map = {}\n    ws = workspace.C.Workspace()\n    for param in net_params.protos:\n        ws.create_blob(param.name) \\\n          .feed(utils.Caffe2TensorToNumpyArray(param))\n    external_input = net.op[0].input[0]\n    ws.create_blob(external_input).feed(dummy_input)\n    # Get dimensions with legacy pad\n    for i in range(len(net.op)):\n        op_def = net.op[i]\n        ws._run_operator(op_def.SerializeToString())\n        for output in op_def.output:\n            blob = ws.fetch_blob(output)\n            dim_map[output] = blob.shape\n    return dim_map\n\n\ndef _GetInputDims(caffe_net):\n    input_dims = []\n    if caffe_net.input_dim:\n        input_dims = caffe_net.input_dim\n    elif caffe_net.input_shape:\n        input_dims = caffe_net.input_shape[0].dim\n    elif caffe_net.layer[0].input_param.shape:\n        # getting input dimension from first layer\n        input_dims = caffe_net.layer[0].input_param.shape[0].dim\n    return input_dims\n\n\nclass TranslatorRegistry(object):\n    registry_ = {}\n\n    @classmethod\n    def Register(cls, op_name):\n        \"\"\"A decorator for registering gradient mappings.\"\"\"\n\n        def Wrapper(func):\n            cls.registry_[op_name] = func\n            return func\n\n        return Wrapper\n\n    @classmethod\n    def TranslateLayer(cls, layer, pretrained_blobs, is_test, **kwargs):\n        try:\n            caffe_ops, params = cls.registry_[layer.type](\n                layer, pretrained_blobs, is_test, **kwargs)\n        except KeyError:\n            raise KeyError('No translator registered for layer: %s yet.' %\n                           str(layer))\n        if caffe_ops is None:\n            caffe_ops = []\n        if type(caffe_ops) is not list:\n            caffe_ops = [caffe_ops]\n        return caffe_ops, params\n\n    @classmethod\n    def TranslateModel(\n        cls,\n        caffe_net,\n        pretrained_net,\n        is_test=False,\n        net_state=None,\n        remove_legacy_pad=False,\n        input_dims=None\n    ):\n        net_state = caffe_pb2.NetState() if net_state is None else net_state\n        net = caffe2_pb2.NetDef()\n        net.name = caffe_net.name\n        net_params = caffe2_pb2.TensorProtos()\n        if len(caffe_net.layers) > 0:\n            raise ValueError(\n                'I think something is wrong. This translation script '\n                'only accepts new style layers that are stored in the '\n                'layer field.'\n            )\n        if not input_dims:\n            input_dims = _GetInputDims(caffe_net)\n        for layer in caffe_net.layer:\n            if not _ShouldInclude(net_state, layer):\n                log.info('Current net state does not need layer {}'\n                            .format(layer.name))\n                continue\n            log.info('Translate layer {}'.format(layer.name))\n            # Get pretrained one\n            pretrained_layers = (\n                [l for l in pretrained_net.layer\n                 if l.name == layer.name] + [l\n                                             for l in pretrained_net.layers\n                                             if l.name == layer.name]\n            )\n            if len(pretrained_layers) > 1:\n                raise ValueError(\n                    'huh? more than one pretrained layer of one name?')\n            elif len(pretrained_layers) == 1:\n                pretrained_blobs = [\n                    utils.CaffeBlobToNumpyArray(blob)\n                    for blob in pretrained_layers[0].blobs\n                ]\n            else:\n                # No pretrained layer for the given layer name. We'll just pass\n                # no parameter blobs.\n                # print 'No pretrained layer for layer', layer.name\n                pretrained_blobs = []\n            operators, params = cls.TranslateLayer(\n                layer, pretrained_blobs, is_test, net=net,\n                net_params=net_params, input_dims=input_dims)\n            net.op.extend(operators)\n            net_params.protos.extend(params)\n        if remove_legacy_pad:\n            assert input_dims, \\\n                   'Please specify input_dims to remove legacy_pad'\n            net = _RemoveLegacyPad(net, net_params, input_dims)\n        return net, net_params\n\n\ndef TranslateModel(*args, **kwargs):\n    return TranslatorRegistry.TranslateModel(*args, **kwargs)\n\n\ndef ConvertTensorProtosToInitNet(net_params, input_name):\n    \"\"\"Takes the net_params returned from TranslateModel, and wrap it as an\n    init net that contain GivenTensorFill.\n\n    This is a very simple feature that only works with float tensors, and is\n    only intended to be used in an environment where you want a single\n    initialization file - for more complex cases, use a db to store the\n    parameters.\n    \"\"\"\n    init_net = caffe2_pb2.NetDef()\n    for tensor in net_params.protos:\n        if len(tensor.float_data) == 0:\n            raise RuntimeError(\n                \"Only float tensors are supported in this util.\")\n        op = core.CreateOperator(\n            \"GivenTensorFill\", [], [tensor.name],\n            arg=[\n                utils.MakeArgument(\"shape\", list(tensor.dims)),\n                utils.MakeArgument(\"values\", tensor.float_data)])\n        init_net.op.extend([op])\n    init_net.op.extend([core.CreateOperator(\"ConstantFill\", [], [input_name], shape=[1])])\n    return init_net\n\n\ndef BaseTranslate(layer, caffe2_type):\n    \"\"\"A simple translate interface that maps the layer input and output.\"\"\"\n    caffe2_op = caffe2_pb2.OperatorDef()\n    caffe2_op.type = caffe2_type\n    caffe2_op.input.extend(layer.bottom)\n    caffe2_op.output.extend(layer.top)\n    return caffe2_op\n\n\ndef AddArgument(op, key, value):\n    \"\"\"Makes an argument based on the value type.\"\"\"\n    op.arg.extend([utils.MakeArgument(key, value)])\n\n################################################################################\n# Common translators for layers.\n################################################################################\n\n\n@TranslatorRegistry.Register(\"Input\")\ndef TranslateInput(layer, pretrained_blobs, is_test, **kwargs):\n    return [], []\n\n\n@TranslatorRegistry.Register(\"VideoData\")\ndef TranslateVideoData(layer, pretrained_blobs, is_test, **kwargs):\n    return [], []\n\n\n@TranslatorRegistry.Register(\"Data\")\ndef TranslateData(layer, pretrained_blobs, is_test, **kwargs):\n    return [], []\n\n\n# A function used in convolution, pooling and deconvolution to deal with\n# conv pool specific parameters.\ndef _TranslateStridePadKernelHelper(param, caffe_op):\n    try:\n        if (len(param.stride) > 1 or len(param.kernel_size) > 1 or\n                len(param.pad) > 1):\n            raise NotImplementedError(\n                \"Translator currently does not support non-conventional \"\n                \"pad/kernel/stride settings.\"\n            )\n        stride = param.stride[0] if len(param.stride) else 1\n        pad = param.pad[0] if len(param.pad) else 0\n        kernel = param.kernel_size[0] if len(param.kernel_size) else 0\n    except TypeError:\n        # This catches the case of a PoolingParameter, in which case we are\n        # having non-repeating pad, stride and kernel.\n        stride = param.stride\n        pad = param.pad\n        kernel = param.kernel_size\n    # Get stride\n    if param.HasField(\"stride_h\") or param.HasField(\"stride_w\"):\n        AddArgument(caffe_op, \"stride_h\", param.stride_h)\n        AddArgument(caffe_op, \"stride_w\", param.stride_w)\n    else:\n        AddArgument(caffe_op, \"stride\", stride)\n    # Get pad\n    if param.HasField(\"pad_h\") or param.HasField(\"pad_w\"):\n        if param.pad_h == param.pad_w:\n            AddArgument(caffe_op, \"pad\", param.pad_h)\n        else:\n            AddArgument(caffe_op, \"pad_t\", param.pad_h)\n            AddArgument(caffe_op, \"pad_b\", param.pad_h)\n            AddArgument(caffe_op, \"pad_l\", param.pad_w)\n            AddArgument(caffe_op, \"pad_r\", param.pad_w)\n    else:\n        AddArgument(caffe_op, \"pad\", pad)\n    # Get kernel\n    if param.HasField(\"kernel_h\") or param.HasField(\"kernel_w\"):\n        AddArgument(caffe_op, \"kernel_h\", param.kernel_h)\n        AddArgument(caffe_op, \"kernel_w\", param.kernel_w)\n    else:\n        AddArgument(caffe_op, \"kernel\", kernel)\n\n\n@TranslatorRegistry.Register(\"Convolution3D\")\ndef TranslateConvNd(layer, pretrained_blobs, is_test, **kwargs):\n    param = layer.convolution3d_param\n    caffe_op = BaseTranslate(layer, \"Conv\")\n    output = caffe_op.output[0]\n    caffe_op.input.append(output + '_w')\n\n    AddArgument(\n        caffe_op,\n        \"kernels\",\n        [param.kernel_depth, param.kernel_size, param.kernel_size])\n    AddArgument(\n        caffe_op,\n        \"strides\",\n        [param.temporal_stride, param.stride, param.stride])\n    temporal_pad = 0\n    spatial_pad = 0\n    if hasattr(param, 'temporal_pad'):\n        temporal_pad = param.temporal_pad\n    if hasattr(param, 'pad'):\n        spatial_pad = param.pad\n    AddArgument(caffe_op, \"pads\", [temporal_pad, spatial_pad, spatial_pad] * 2)\n\n    # weight\n    params = [\n        utils.NumpyArrayToCaffe2Tensor(pretrained_blobs[0], output + '_w')]\n    # bias\n    if len(pretrained_blobs) == 2:\n        caffe_op.input.append(output + '_b')\n        params.append(\n            utils.NumpyArrayToCaffe2Tensor(\n                pretrained_blobs[1].flatten(), output + '_b'))\n    return caffe_op, params\n\n\n@TranslatorRegistry.Register(\"Convolution\")\ndef TranslateConv(layer, pretrained_blobs, is_test, **kwargs):\n    param = layer.convolution_param\n    caffe_op = BaseTranslate(layer, \"Conv\")\n    output = caffe_op.output[0]\n    caffe_op.input.append(output + '_w')\n    _TranslateStridePadKernelHelper(param, caffe_op)\n    # weight\n    params = [\n        utils.NumpyArrayToCaffe2Tensor(pretrained_blobs[0], output + '_w')]\n    # bias\n    if len(pretrained_blobs) == 2:\n        caffe_op.input.append(output + '_b')\n        params.append(\n            utils.NumpyArrayToCaffe2Tensor(\n                pretrained_blobs[1].flatten(), output + '_b'))\n    # Group convolution option\n    if param.group != 1:\n        AddArgument(caffe_op, \"group\", param.group)\n    # Get dilation - not tested. If you have a model and this checks out,\n    # please provide a test and uncomment this.\n    if len(param.dilation) > 0:\n        if len(param.dilation) == 1:\n            AddArgument(caffe_op, \"dilation\", param.dilation[0])\n        elif len(param.dilation) == 2:\n            AddArgument(caffe_op, \"dilation_h\", param.dilation[0])\n            AddArgument(caffe_op, \"dilation_w\", param.dilation[1])\n    return caffe_op, params\n\n\n@TranslatorRegistry.Register(\"Deconvolution\")\ndef TranslateDeconv(layer, pretrained_blobs, is_test, **kwargs):\n    param = layer.convolution_param\n    if param.group > 1:\n        raise NotImplementedError(\n            \"Translator currently does not support group deconvolution.\"\n        )\n    caffe_op = BaseTranslate(layer, \"ConvTranspose\")\n    output = caffe_op.output[0]\n    _TranslateStridePadKernelHelper(param, caffe_op)\n    caffe_op.input.extend([output + '_w'])\n    AddArgument(caffe_op, \"order\", \"NCHW\")\n    weight = utils.NumpyArrayToCaffe2Tensor(pretrained_blobs[0], output + '_w')\n    if param.bias_term:\n        bias = utils.NumpyArrayToCaffe2Tensor(\n            pretrained_blobs[1].flatten(), output + '_b'\n        )\n        caffe_op.input.extend([output + '_b'])\n        return caffe_op, [weight, bias]\n    else:\n        return caffe_op, [weight]\n\n\n@TranslatorRegistry.Register(\"Crop\")\ndef TranslateCrop(layer, pretrained_blobs, is_test, **kwargs):\n    net, net_params, input_dims = kwargs['net'], kwargs['net_params'], kwargs['input_dims']\n    n, c, h, w = input_dims\n    dummy_input = np.random.randn(n, c, h, w).astype(np.float32)\n    dim_map = _GetBlobDimMap(net, net_params, dummy_input)\n    param = layer.crop_param\n    axis, offsets = param.axis, param.offset\n    caffe_op = BaseTranslate(layer, \"Slice\")\n    input_1 = caffe_op.input[1]\n    input_1_dim = dim_map[input_1]\n    starts, ends = [], []\n    dims = len(dim_map[input_1])\n    assert len(offsets) == 1, 'Caffe Translator for Crop only works for offset \\\n    of 1 for now'\n    for _ in range(axis):\n        starts.append(0)\n        ends.append(-1)\n    end_offset = [int(offsets[0] + input_1_dim[i]) for i in range(axis, dims)]\n    ends.extend(end_offset)\n    starts.extend([offsets[0]] * len(end_offset))\n    op = caffe2_pb2.OperatorDef()\n    op.input.extend([caffe_op.input[0]])\n    op.output.extend(caffe_op.output)\n    op.arg.extend(caffe_op.arg)\n    op.type = caffe_op.type\n    AddArgument(op, \"starts\", starts)\n    AddArgument(op, \"ends\", ends)\n    return op, []\n\n@TranslatorRegistry.Register(\"ReLU\")\ndef TranslateRelu(layer, pretrained_blobs, is_test, **kwargs):\n    return BaseTranslate(layer, \"Relu\"), []\n\n\n@TranslatorRegistry.Register(\"Pooling\")\ndef TranslatePool(layer, pretrained_blobs, is_test, **kwargs):\n    param = layer.pooling_param\n    if param.pool == caffe_pb2.PoolingParameter.MAX:\n        caffe_op = BaseTranslate(layer, \"MaxPool\")\n    elif param.pool == caffe_pb2.PoolingParameter.AVE:\n        caffe_op = BaseTranslate(layer, \"AveragePool\")\n    _TranslateStridePadKernelHelper(param, caffe_op)\n    AddArgument(caffe_op, \"order\", \"NCHW\")\n    try:\n        # In the Facebook port of Caffe, a torch_pooling field was added to\n        # map the pooling computation of Torch. Essentially, it uses\n        #   floor((height + 2 * padding - kernel) / stride) + 1\n        # instead of\n        #   ceil((height + 2 * padding - kernel) / stride) + 1\n        # which is Caffe's version.\n        # Torch pooling is actually the same as Caffe2 pooling, so we don't\n        # need to do anything.\n        is_torch_pooling = param.torch_pooling\n    except AttributeError:\n        is_torch_pooling = False\n    if not is_torch_pooling:\n        AddArgument(caffe_op, \"legacy_pad\",\n                    caffe2_legacy_pb2.CAFFE_LEGACY_POOLING)\n    if param.global_pooling:\n        AddArgument(caffe_op, \"global_pooling\", 1)\n    return caffe_op, []\n\n\n@TranslatorRegistry.Register(\"Pooling3D\")\ndef TranslatePool3D(layer, pretrained_blobs, is_test, **kwargs):\n    param = layer.pooling3d_param\n    if param.pool == caffe_pb2.Pooling3DParameter.MAX:\n        caffe_op = BaseTranslate(layer, \"MaxPool\")\n\n    elif param.pool == caffe_pb2.Pooling3DParameter.AVE:\n        caffe_op = BaseTranslate(layer, \"AveragePool\")\n    AddArgument(caffe_op, \"order\", \"NCHW\")\n    AddArgument(\n        caffe_op,\n        \"kernels\",\n        [param.kernel_depth, param.kernel_size, param.kernel_size])\n\n    AddArgument(\n        caffe_op,\n        \"strides\",\n        [param.temporal_stride, param.stride, param.stride])\n    temporal_pad = 0\n    spatial_pad = 0\n    if hasattr(param, 'temporal_pad'):\n        temporal_pad = param.temporal_pad\n    if hasattr(param, 'pad'):\n        spatial_pad = param.pad\n    AddArgument(caffe_op, \"pads\", [temporal_pad, spatial_pad, spatial_pad] * 2)\n    return caffe_op, []\n\n\n@TranslatorRegistry.Register(\"LRN\")\ndef TranslateLRN(layer, pretrained_blobs, is_test, **kwargs):\n    caffe_op = BaseTranslate(layer, \"LRN\")\n    caffe_op.output.extend(['_' + caffe_op.output[0] + '_scale'])\n    param = layer.lrn_param\n    if param.norm_region != caffe_pb2.LRNParameter.ACROSS_CHANNELS:\n        raise ValueError(\n            \"Does not support norm region other than across channels.\")\n    AddArgument(caffe_op, \"size\", int(param.local_size))\n    AddArgument(caffe_op, \"alpha\", float(param.alpha))\n    AddArgument(caffe_op, \"beta\", float(param.beta))\n    AddArgument(caffe_op, \"bias\", float(param.k))\n    AddArgument(caffe_op, \"order\", \"NCHW\")\n    return caffe_op, []\n\n\n@TranslatorRegistry.Register(\"InnerProduct\")\ndef TranslateInnerProduct(layer, pretrained_blobs, is_test, **kwargs):\n    param = layer.inner_product_param\n    try:\n        if param.axis != 1 or param.transpose:\n            raise ValueError(\n                \"We don't have testing case for non-default axis and transpose \"\n                \"cases yet so we are disabling it for now. If you have a model \"\n                \"with this, please do send us your model for us to update this \"\n                \"support, and you are more than welcome to send a PR for this.\")\n    except AttributeError:\n        # We might be using an historic Caffe protobuf that does not have axis\n        # and transpose arguments, so we will silently pass.\n        pass\n    caffe_op = BaseTranslate(layer, \"FC\")\n    output = caffe_op.output[0]\n    caffe_op.input.extend([output + '_w', output + '_b'])\n    # To provide the old-style 4-dimensional blob (1, 1, dim_output, dim_input)\n    # case, we always explicitly reshape the pretrained blob.\n    if pretrained_blobs[0].ndim not in [2, 4]:\n        raise ValueError(\"Unexpected weight ndim.\")\n    if (pretrained_blobs[0].ndim == 4 and\n            list(pretrained_blobs[0].shape[:2]) != [1, 1]):\n        raise ValueError(\n            \"If pretrained blob has 4 dims (old-style Caffe), the first two \"\n            \"should be of value 1, but I got \" + str(pretrained_blobs[0].shape))\n    weight = utils.NumpyArrayToCaffe2Tensor(\n        pretrained_blobs[0].reshape(-1, pretrained_blobs[0].shape[-1]),\n        output + '_w'\n    )\n    bias = utils.NumpyArrayToCaffe2Tensor(\n        pretrained_blobs[1].flatten(), output + '_b'\n    )\n    return caffe_op, [weight, bias]\n\n\n@TranslatorRegistry.Register(\"Dropout\")\ndef TranslateDropout(layer, pretrained_blobs, is_test, **kwargs):\n    caffe_op = BaseTranslate(layer, \"Dropout\")\n    caffe_op.output.extend(['_' + caffe_op.output[0] + '_mask'])\n    param = layer.dropout_param\n    AddArgument(caffe_op, \"ratio\", param.dropout_ratio)\n    if (is_test):\n        AddArgument(caffe_op, \"is_test\", 1)\n    return caffe_op, []\n\n\n@TranslatorRegistry.Register(\"Softmax\")\ndef TranslateSoftmax(layer, pretrained_blobs, is_test, **kwargs):\n    caffe_op = BaseTranslate(layer, \"Softmax\")\n    return caffe_op, []\n\n\n@TranslatorRegistry.Register(\"SoftmaxWithLoss\")\ndef TranslateSoftmaxWithLoss(layer, pretrained_blobs, is_test, **kwargs):\n    softmax_op = core.CreateOperator(\n        \"Softmax\", [layer.bottom[0]],\n        layer.bottom[0] + \"_translator_autogen_softmax\")\n    xent_op = core.CreateOperator(\n        \"LabelCrossEntropy\",\n        [softmax_op.output[0], layer.bottom[1]],\n        layer.bottom[0] + \"_translator_autogen_xent\")\n    loss_op = core.CreateOperator(\n        \"AveragedLoss\",\n        xent_op.output[0],\n        layer.top[0])\n    return [softmax_op, xent_op, loss_op], []\n\n\n@TranslatorRegistry.Register(\"Accuracy\")\ndef TranslateAccuracy(layer, pretrained_blobs, is_test, **kwargs):\n    caffe_op = BaseTranslate(layer, \"Accuracy\")\n    if layer.accuracy_param.top_k != 1:\n        AddArgument(caffe_op, \"top_k\", layer.accuracy_param.top_k)\n    return caffe_op, []\n\n\n@TranslatorRegistry.Register(\"Concat\")\ndef TranslateConcat(layer, pretrained_blobs, is_test, **kwargs):\n    caffe_op = BaseTranslate(layer, \"Concat\")\n    caffe_op.output.extend(['_' + caffe_op.output[0] + '_dims'])\n    AddArgument(caffe_op, \"order\", \"NCHW\")\n    return caffe_op, []\n\n\n@TranslatorRegistry.Register(\"TanH\")\ndef TranslateTanH(layer, pretrained_blobs, is_test, **kwargs):\n    caffe_op = BaseTranslate(layer, \"Tanh\")\n    return caffe_op, []\n\n\n@TranslatorRegistry.Register(\"InstanceNorm\")\ndef TranslateInstanceNorm(layer, pretrained_blobs, is_test, **kwargs):\n    caffe_op = BaseTranslate(layer, \"InstanceNorm\")\n    output = caffe_op.output[0]\n    weight = utils.NumpyArrayToCaffe2Tensor(\n        pretrained_blobs[0].flatten(), output + '_w')\n    bias = utils.NumpyArrayToCaffe2Tensor(\n        pretrained_blobs[1].flatten(), output + '_b')\n    caffe_op.input.extend([output + '_w', output + '_b'])\n    AddArgument(caffe_op, \"order\", \"NCHW\")\n    return caffe_op, [weight, bias]\n\n\n@TranslatorRegistry.Register(\"BatchNorm\")\ndef TranslateBatchNorm(layer, pretrained_blobs, is_test, **kwargs):\n    caffe_op = BaseTranslate(layer, \"SpatialBN\")\n    output = caffe_op.output[0]\n    param = layer.batch_norm_param\n    AddArgument(caffe_op, \"is_test\", is_test)\n    AddArgument(caffe_op, \"epsilon\", param.eps)\n    AddArgument(caffe_op, \"order\", \"NCHW\")\n\n    caffe_op.input.extend(\n        [output + \"_scale\",\n         output + \"_bias\",\n         output + \"_mean\",\n         output + \"_var\"])\n    if not is_test:\n        caffe_op.output.extend(\n            [output + \"_mean\",\n             output + \"_var\",\n             output + \"_saved_mean\",\n             output + \"_saved_var\"])\n\n    n_channels = pretrained_blobs[0].shape[0]\n    if pretrained_blobs[2][0] != 0:\n        mean = utils.NumpyArrayToCaffe2Tensor(\n            (1. / pretrained_blobs[2][0]) * pretrained_blobs[0],\n            output + '_mean')\n        var = utils.NumpyArrayToCaffe2Tensor(\n            (1. / pretrained_blobs[2][0]) * pretrained_blobs[1],\n            output + '_var')\n    else:\n        raise RuntimeError(\"scalar is zero.\")\n    pretrained_blobs[2][0] = 1\n    pretrained_blobs[2] = np.tile(pretrained_blobs[2], (n_channels, ))\n    scale = utils.NumpyArrayToCaffe2Tensor(\n        pretrained_blobs[2],\n        output + '_scale')\n    bias = utils.NumpyArrayToCaffe2Tensor(\n        np.zeros_like(pretrained_blobs[2]),\n        output + '_bias')\n\n    return caffe_op, [scale, bias, mean, var]\n\n\n@TranslatorRegistry.Register(\"Eltwise\")\ndef TranslateElementWise(layer, pretrained_blobs, is_test, **kwargs):\n    param = layer.eltwise_param\n    # TODO(jiayq): if we have a protobuf that uses this, lift this constraint\n    # and verify that we can correctly translate.\n    if len(param.coeff) or param.operation != 1:\n        raise RuntimeError(\"This eltwise layer is not yet supported.\")\n    caffe_op = BaseTranslate(layer, \"Sum\")\n    return caffe_op, []\n\n\n@TranslatorRegistry.Register(\"Scale\")\ndef TranslateScale(layer, pretrained_blobs, is_test, **kwargs):\n    mul_op = BaseTranslate(layer, \"Mul\")\n    scale_param = layer.scale_param\n    AddArgument(mul_op, \"axis\", scale_param.axis)\n    AddArgument(mul_op, \"broadcast\", True)\n    if len(mul_op.input) == 1:\n        # the scale parameter is in pretrained blobs\n        if scale_param.num_axes != 1:\n            raise RuntimeError(\"This path has not been verified yet.\")\n\n        output = mul_op.output[0]\n        mul_op_param = output + '_w'\n        mul_op.input.append(mul_op_param)\n        weights = []\n        weights.append(utils.NumpyArrayToCaffe2Tensor(\n            pretrained_blobs[0].flatten(), mul_op_param))\n\n        add_op = None\n        if len(pretrained_blobs) == 1:\n            # No bias-term in Scale layer\n            pass\n        elif len(pretrained_blobs) == 2:\n            # Caffe Scale layer supports a bias term such that it computes\n            # (scale_param * X + bias), whereas Caffe2 Mul op doesn't.\n            # Include a separate Add op for the bias followed by Mul.\n            add_op = copy.deepcopy(mul_op)\n            add_op.type = \"Add\"\n            add_op_param = output + '_b'\n            internal_blob = output + \"_internal\"\n            del mul_op.output[:]\n            mul_op.output.append(internal_blob)\n            del add_op.input[:]\n            add_op.input.append(internal_blob)\n            add_op.input.append(add_op_param)\n            weights.append(utils.NumpyArrayToCaffe2Tensor(\n                pretrained_blobs[1].flatten(), add_op_param))\n        else:\n            raise RuntimeError(\"Unexpected number of pretrained blobs in Scale\")\n\n        caffe_ops = [mul_op]\n        if add_op:\n            caffe_ops.append(add_op)\n        assert len(caffe_ops) == len(weights)\n        return caffe_ops, weights\n    elif len(mul_op.input) == 2:\n        # TODO(jiayq): find a protobuf that uses this and verify.\n        raise RuntimeError(\"This path has not been verified yet.\")\n    else:\n        raise RuntimeError(\"Unexpected number of inputs.\")\n\n\n@TranslatorRegistry.Register(\"Reshape\")\ndef TranslateReshape(layer, pretrained_blobs, is_test, **kwargs):\n    caffe_op = BaseTranslate(layer, \"Reshape\")\n    caffe_op.output.append(\"_\" + caffe_op.input[0] + \"_dims\")\n    reshape_param = layer.reshape_param\n    AddArgument(caffe_op, 'shape', reshape_param.shape.dim)\n    return caffe_op, []\n\n\n@TranslatorRegistry.Register(\"Flatten\")\ndef TranslateFlatten(layer, pretrained_blobs, is_test, **kwargs):\n    param = layer.flatten_param\n    if param.end_axis != -1:\n        raise NotImplementedError(\"flatten_param.end_axis not supported yet.\")\n\n    if param.axis == 0:\n        caffe_op = BaseTranslate(layer, \"FlattenToVec\")\n    elif param.axis == 1:\n        caffe_op = BaseTranslate(layer, \"Flatten\")\n    else:\n        # This could be a Reshape op, but dim size is not known here.\n        raise NotImplementedError(\n            \"Not supported yet for flatten_param.axis {}.\".format(param.axis))\n\n    return caffe_op, []\n\n\n@TranslatorRegistry.Register(\"Sigmoid\")\ndef TranslateSigmoid(layer, pretrained_blobs, is_test, **kwargs):\n    caffe_op = BaseTranslate(layer, \"Sigmoid\")\n    return caffe_op, []\n\n\n@TranslatorRegistry.Register(\"ROIPooling\")\ndef TranslateROIPooling(layer, pretrained_blobs, is_test, **kwargs):\n    caffe_op = BaseTranslate(layer, \"RoIPool\")\n    AddArgument(caffe_op, \"order\", \"NCHW\")\n\n    if is_test:\n        AddArgument(caffe_op, \"is_test\", is_test)\n    else:\n        # Only used for gradient computation\n        caffe_op.output.append(caffe_op.output[0] + '_argmaxes')\n\n    param = layer.roi_pooling_param\n    if param.HasField('pooled_h'):\n        AddArgument(caffe_op, 'pooled_h', param.pooled_h)\n    if param.HasField('pooled_w'):\n        AddArgument(caffe_op, 'pooled_w', param.pooled_w)\n    if param.HasField('spatial_scale'):\n        AddArgument(caffe_op, 'spatial_scale', param.spatial_scale)\n\n    return caffe_op, []\n\n\n@TranslatorRegistry.Register(\"PReLU\")\ndef TranslatePRelu(layer, pretrained_blobs, is_test, **kwargs):\n    caffe_op = BaseTranslate(layer, \"PRelu\")\n    output = caffe_op.output[0]\n    caffe_op.input.extend([output + '_Slope'])\n    slope = utils.NumpyArrayToCaffe2Tensor(pretrained_blobs[0], output + '_Slope')\n\n    return caffe_op, [slope]\n\n\n@TranslatorRegistry.Register(\"Reduction\")\ndef TranslateReduction(layer, pretrained_blobs, is_test, **kwargs):\n    param = layer.reduction_param\n    if param.operation == caffe_pb2.ReductionParameter.SUM:\n        caffe_op = BaseTranslate(layer, \"ReduceBackSum\")\n    elif param.operation == caffe_pb2.ReductionParameter.MEAN:\n        caffe_op = BaseTranslate(layer, \"ReduceBackMean\")\n    else:\n        raise NotImplementedError(\"Not yet supported\")\n\n    if param.axis > 0:\n        # We can't figure out the number of dims to reduce from positive axis\n        # for back reduction since the shape info is not known here.\n        raise NotImplementedError(\"Not yet supported\")\n    num_reduce_dim = -param.axis\n    AddArgument(caffe_op, \"num_reduce_dim\", num_reduce_dim)\n\n    return caffe_op, []\n\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser(\n        description=\"Utilitity to convert pretrained caffe models to Caffe2 models.\")\n    parser.add_argument(\"prototext\", help=\"Caffe prototext.\")\n    parser.add_argument(\"caffemodel\", help=\"Caffe trained model.\")\n    parser.add_argument(\"--init_net\", help=\"Caffe2 initialization net.\",\n                        default=\"init_net.pb\")\n    parser.add_argument(\"--predict_net\", help=\"Caffe2 prediction net.\",\n                        default=\"predict_net.pb\")\n    parser.add_argument(\"--remove_legacy_pad\", help=\"Remove legacy pad \\\n                        (Only works for nets with one input blob)\",\n                        action=\"store_true\",\n                        default=False)\n    parser.add_argument(\"--input_dims\", help=\"Dimension of input blob\", nargs='+',\n                        type=int, default=[])\n    args = parser.parse_args()\n\n    caffenet = caffe_pb2.NetParameter()\n    caffenet_pretrained = caffe_pb2.NetParameter()\n    input_proto = args.prototext\n    input_caffemodel = args.caffemodel\n    output_init_net = args.init_net\n    output_predict_net = args.predict_net\n\n    text_format.Merge(\n        open(input_proto, 'r').read(), caffenet\n    )\n    caffenet_pretrained.ParseFromString(\n        open(input_caffemodel, 'rb').read()\n    )\n    net, pretrained_params = TranslateModel(\n        caffenet, caffenet_pretrained, is_test=True,\n        remove_legacy_pad=args.remove_legacy_pad,\n        input_dims=args.input_dims\n    )\n\n    # Assume there is one input and one output\n    external_input = net.op[0].input[0]\n    external_output = net.op[-1].output[0]\n\n    net.external_input.extend([external_input])\n    net.external_input.extend([param.name for param in pretrained_params.protos])\n    net.external_output.extend([external_output])\n    init_net = ConvertTensorProtosToInitNet(pretrained_params, external_input)\n\n    with open(output_predict_net, 'wb') as f:\n        f.write(net.SerializeToString())\n    with open(output_predict_net + 'txt', 'w') as f:\n        f.write(str(net))\n    with open(output_init_net, 'wb') as f:\n        f.write(init_net.SerializeToString())\n"
  },
  {
    "path": "caffe2/python/caffe_translator_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n# This a large test that goes through the translation of the bvlc caffenet\n# model, runs an example through the whole model, and verifies numerically\n# that all the results look right. In default, it is disabled unless you\n# explicitly want to run it.\n\nfrom caffe.proto import caffe_pb2\nfrom google.protobuf import text_format\nimport numpy as np\nimport os\nfrom caffe2.python import caffe_translator, utils, workspace, test_util\nimport sys\nimport unittest\n\n\n@unittest.skipIf(not os.path.exists('data/testdata/caffe_translator'),\n                 'No testdata existing for the caffe translator test. Exiting.')\ndef setUpModule():\n    # We will do all the computation stuff in the global space.\n    caffenet = caffe_pb2.NetParameter()\n    caffenet_pretrained = caffe_pb2.NetParameter()\n    text_format.Merge(\n        open('data/testdata/caffe_translator/deploy.prototxt').read(), caffenet\n    )\n    caffenet_pretrained.ParseFromString(\n        open(\n            'data/testdata/caffe_translator/bvlc_reference_caffenet.caffemodel')\n        .read()\n    )\n    for remove_legacy_pad in [True, False]:\n        net, pretrained_params = caffe_translator.TranslateModel(\n            caffenet, caffenet_pretrained, is_test=True,\n            remove_legacy_pad=remove_legacy_pad\n        )\n        with open('data/testdata/caffe_translator/'\n                  'bvlc_reference_caffenet.translatedmodel',\n                  'w') as fid:\n            fid.write(str(net))\n        for param in pretrained_params.protos:\n            workspace.FeedBlob(param.name, utils.Caffe2TensorToNumpyArray(param))\n        # Let's also feed in the data from the Caffe test code.\n        data = np.load('data/testdata/caffe_translator/data_dump.npy').astype(\n            np.float32)\n        workspace.FeedBlob('data', data)\n        # Actually running the test.\n        workspace.RunNetOnce(net.SerializeToString())\n\n\nclass TestNumericalEquivalence(test_util.TestCase):\n    def testBlobs(self):\n        names = [\n            \"conv1\", \"pool1\", \"norm1\", \"conv2\", \"pool2\", \"norm2\", \"conv3\",\n            \"conv4\", \"conv5\", \"pool5\", \"fc6\", \"fc7\", \"fc8\", \"prob\"\n        ]\n        for name in names:\n            print('Verifying {}'.format(name))\n            caffe2_result = workspace.FetchBlob(name)\n            reference = np.load(\n                'data/testdata/caffe_translator/' + name + '_dump.npy'\n            )\n            self.assertEqual(caffe2_result.shape, reference.shape)\n            scale = np.max(caffe2_result)\n            np.testing.assert_almost_equal(\n                caffe2_result / scale,\n                reference / scale,\n                decimal=5\n            )\n\n\nif __name__ == '__main__':\n    if len(sys.argv) == 1:\n        print(\n            'If you do not explicitly ask to run this test, I will not run it. '\n            'Pass in any argument to have the test run for you.'\n        )\n        sys.exit(0)\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/checkpoint.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package checkpoint\n# Module caffe2.python.checkpoint\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport os\nimport logging\nfrom caffe2.python import core, context\nfrom caffe2.python.net_builder import ops\nfrom caffe2.python.task import Node, Task, TaskGroup, TaskOutput, WorkspaceType\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO)\n\n\n@context.define_context()\nclass Job(object):\n    \"\"\"\n    A Job defines three TaskGroups: the `init_group`, the `epoch_group` and the\n    `exit_group` which will be run by a JobRunner.\n\n    The `init_group` will be run only once at startup. Its role is to\n    initialize globally persistent blobs such as model weights, accumulators\n    and data file lists.\n\n    The `epoch_group` will be run in a loop after init_group. The loop will\n    exit when any of the stop signals added with `add_stop_signal` is True\n    at the end of an epoch.\n\n    The download_group will be run only once, after all the executions of\n    epoch_group finish. Its role is to collect the distribute scattered\n    parameters back after training.\n\n    The `exit_group` will be run only once at the very end of the job, the\n    role of this group is to save the results of training in the end of the job.\n\n    Jobs are context-driven, so that Tasks can be added to the active Job\n    without having to explicitly pass the job object around.\n\n    Example of usage:\n\n    def build_reader(partitions):\n        with Job.current().init_group:\n            reader = HiveReader(init_reader, ..., partitions)\n            Task(step=init_reader)\n        with Job.current().epoch_group:\n            limited_reader = ReaderWithLimit(reader, num_iter=10000)\n            data_queue = pipe(limited_reader, num_threads=8)\n            Job.current().add_stop_signal(limited_reader.data_finished())\n        return data_queue\n\n    def build_hogwild_trainer(reader, model):\n        with Job.current().init_group:\n            Task(step=model.param_init_net)\n        with Job.current().epoch_group:\n            pipe(reader, processor=model, num_threads=8)\n        with Job.current().exit_group:\n            Task(step=model.save_model_net)\n\n    with Job() as job:\n        reader = build_reader(partitions)\n        model = build_model(params)\n        build_hogwild_trainer(reader, model)\n    \"\"\"\n    def __init__(self,\n                 init_group=None, epoch_group=None,\n                 download_group=None, exit_group=None,\n                 stop_signals=None, nodes_to_checkpoint=None):\n        self.init_group = init_group or TaskGroup(\n            workspace_type=WorkspaceType.GLOBAL)\n        self.epoch_group = epoch_group or TaskGroup()\n        self.download_group = download_group or TaskGroup()\n        self.exit_group = exit_group or TaskGroup()\n        self.stop_signals = stop_signals or []\n        self._nodes_to_checkpoint = nodes_to_checkpoint\n\n    def nodes_to_checkpoint(self):\n        if self._nodes_to_checkpoint:\n            return self._nodes_to_checkpoint\n        else:\n            return self.init_group.used_nodes()\n\n    def compile(self, session_class):\n        return Job(\n            init_group=session_class.compile(self.init_group),\n            epoch_group=session_class.compile(self.epoch_group),\n            download_group=session_class.compile(self.download_group),\n            exit_group=session_class.compile(self.exit_group),\n            stop_signals=self.stop_signals,\n            nodes_to_checkpoint=self.nodes_to_checkpoint())\n\n    def __enter__(self):\n        self.epoch_group.__enter__()\n        return self\n\n    def __exit__(self, *args):\n        self.epoch_group.__exit__()\n\n    def add_stop_signal(self, output):\n        if isinstance(output, core.BlobReference):\n            t = Task(outputs=[output], group=self.epoch_group)\n            output = t.outputs()[0]\n        assert isinstance(output, TaskOutput)\n        self.stop_signals.append(output)\n\n\ndef get_ckpt_filename(node_name, epoch):\n    \"\"\"Returns the checkpoint filename.\n\n    Args:\n        node_name: A string. The name of the node.\n        epoch: An integer. The checkpoint epoch.\n\n    Returns:\n        ckpt_filename: A string. The filename of the checkpoint.\n    \"\"\"\n    return node_name + '.' + str(epoch)\n\n\ndef db_name(epoch, node_name, db_prefix, path_prefix=None):\n    \"\"\"Returns the full db name where checkpoint files are saved.\n\n    Args:\n        epoch: An integer. The checkpoint epoch.\n        node_name: A string. The name of the node.\n        db_prefix: A string. The prefix used to construct full db name.\n        path_prefix: A string. Optional param used to construct db name or path\n            where checkpoint files are are stored.\n    Returns:\n        db_name: A string. The absolute path of full_db_name where checkpoint\n            files are saved\n    \"\"\"\n    if path_prefix:\n        db_name = path_prefix + get_ckpt_filename(node_name, epoch)\n    else:\n        ckpt_filename = get_ckpt_filename(node_name, epoch)\n        db_name = os.path.join(db_prefix, ckpt_filename)\n    return db_name\n\n\nclass CheckpointManager(object):\n    \"\"\"\n    Controls saving and loading of workspaces on every epoch boundary of a job.\n    If a CheckpointManager instance is passed to JobRunner, then JobRunner will\n    call `init`, `read` and `save` at different moments in between epoch runs.\n\n    Args:\n        db_prefix: The prefix used to construct full db name. Since `absolute_path`\n            is set to True, this will be used as db_name in SaveOp.\n        node_name: Name of the node where this checkpoint_manager is used.\n        db_type: Type of database to use for storing checkpoint.\n        metadata_handler: An optional object capable of reading/writing\n            checkpoint info in storage of choice.\n    \"\"\"\n    def __init__(self, db_prefix, node_name, db_type, metadata_handler=None):\n        self._db_prefix = db_prefix\n        self._node_name = node_name\n        self._db_type = db_type\n        self._metadata_handler = metadata_handler\n        # make sure these blobs are the first in the checkpoint file.\n        self._net = core.Net('!!checkpoint_mngr')\n        self._blob_names = self._net.AddExternalInput('blob_names')\n        self._names_output = None\n        self._path_prefix = None\n        self._path_type = None\n\n    \"\"\"\n    Initialize the checkpoint manager. Determines all blobs that need to be saved\n    or loads from a checkpoint.\n\n    Args:\n        nodes: An array of nodes where this checkpoint manager is running. Should\n            only contain a single node.\n        retrieve_from_epoch: Set to a number to load blobs from this epoch.\n        path_prefix: Used to construct db name or path where checkpoint files are\n            stored.\n        path_type: Indicate the type of path where checkpoint files are stored.\n    \"\"\"\n    def init(\n        self,\n        nodes=None,\n        retrieve_from_epoch=None,\n        path_prefix=None,\n        path_type=None\n    ):\n        \"\"\"\n        Build a Task that will be run once after the job's `init_group` is run.\n        This task will determine which blobs need to be checkpointed.\n        If retrieve_from_epoch is not None, then the checkpoint metadata is\n        retrieved from a previously saved checkpoint.\n        \"\"\"\n        assert nodes is None or len(nodes) == 1, (\n            'CheckpointManager only supports single node.')\n\n        with Task(outputs=[self._blob_names]) as task:\n            if retrieve_from_epoch is None:\n                ops.GetAllBlobNames(\n                    [],\n                    self._blob_names,\n                    include_shared=False)\n            else:\n                full_db_name = db_name(retrieve_from_epoch,\n                                        self._node_name, self._db_prefix, path_prefix)\n                db_type = path_type or self._db_type\n                logger.info(\"Initializing checkpoints from = %s\"\n                            % full_db_name)\n                ops.Load(\n                    [], self._blob_names,\n                    db=full_db_name,\n                    db_type=db_type,\n                    absolute_path=True)\n        self._names_output = task.outputs()[0]\n        return task\n\n    def blob_list(self):\n        assert self._names_output\n        return self._names_output.fetch().tolist()\n\n    def load(self, epoch, path_prefix=None, path_type=None):\n        \"\"\"\n        Build a Task that will be run by JobRunner when the job is to be\n        resumed from a given epoch. This task will run a Load op that will\n        load and deserialize all relevant blobs from a persistent storage.\n        \"\"\"\n        full_db_name = db_name(epoch, self._node_name, self._db_prefix, path_prefix)\n        db_type = path_type or self._db_type\n        logger.info(\"Loading checkpoints from = %s\" % full_db_name)\n        with Task() as task:\n            ops.Load(\n                [],\n                self.blob_list(),\n                db=full_db_name,\n                db_type=db_type,\n                absolute_path=True)\n        return task\n\n    def load_blobs_from_checkpoint(self, blob_names, epoch):\n        \"\"\"\n        Builds a Task that loads only the necessary blobs from a checkpoint of\n        the given epoch. The necessary blobs are given in the blob_names\n        argument.\n\n        Args:\n            blob_names: A list of strings. Each string is the name of a\n                blob.\n            epoch: The checkpoint epoch to load from.\n\n        Returns:\n            A Task which loads the specified blobs from the checkpoint of the\n            given epoch.\n        \"\"\"\n        logger.info('Load from %s' % db_name(epoch, self._node_name, self._db_prefix))\n        with Task() as task:\n            ops.Load(\n                [],\n                blob_names,\n                db=db_name(epoch, self._node_name, self._db_prefix),\n                db_type=self._db_type,\n                absolute_path=True,\n                allow_incomplete=True)\n        return task\n\n    def check_db_exists(self, epoch):\n        logger.info('Check existence of %s' %\n                    db_name(epoch, self._node_name, self._db_prefix))\n        with Task() as task:\n            existence = ops.Const(False)\n            ops.DBExists(\n                [],\n                [existence],\n                db_name=db_name(epoch, self._node_name, self._db_prefix),\n                db_type=self._db_type,\n                absolute_path=True)\n            task.add_output(existence)\n        return task\n\n    def save(self, epoch):\n        \"\"\"\n        Build a Task that is run once after `init_group` and after each\n        epoch is run. This will execute a Save ops to serialize and persist\n        blobs present in the global workspace.\n        \"\"\"\n        logger.info('Saving to %s' % db_name(epoch, self._node_name, self._db_prefix))\n        with Task() as task:\n            ops.Save(\n                self.blob_list(), [],\n                db=db_name(epoch, self._node_name, self._db_prefix),\n                db_type=self._db_type, absolute_path=True)\n        return task\n\n    def write_checkpoint_metadata(self, epoch):\n        \"\"\"\n        Write metadata for checkpoint\n\n        Args:\n            epoch: An integer. The epoch-id for which checkpoint metadata is\n                written\n        \"\"\"\n        if self._metadata_handler is not None:\n            self._metadata_handler.write(epoch=epoch)\n\n    def get_resume_from_epoch_id(self, user_epoch=None):\n        \"\"\"\n        Identify the epoch-id from which Job must resume\n\n        Args:\n            user_epoch: An integer. Optional parameter for user to explicitly\n                identify the epoch-id to load checkpoint from\n        Retruns:\n            epoch: the epoch-id to load checkpoints from\n                or None if no checkpoints were written\n        \"\"\"\n        last_epoch = user_epoch\n        if self._metadata_handler is not None:\n            last_epoch = self._metadata_handler.last_epoch(user_epoch=user_epoch)\n        return last_epoch\n\n    def set_params(self, nodes, path_prefix=None, path_type=None):\n        \"\"\"Set parameters associated with CP manager\n\n        Args:\n            nodes: An array of nodes where this checkpoint manager is running.\n            path_prefix: Used to construct db name or path where checkpoint files are\n                stored.\n            path_type: Indicate the type of path where checkpoint files are stored.\n        \"\"\"\n        if path_prefix:\n            self._path_prefix = path_prefix\n        if path_type:\n            self._path_type = path_type\n        if self._metadata_handler:\n            self._metadata_handler.set_params(\n                db_prefix=self._db_prefix,\n                db_type=self._db_type,\n                node_names=[str(self._node_name)],\n                path_prefix=self._path_prefix,\n                path_type=self._path_type)\n\n    def cp_accessible(self, epoch=None):\n        \"\"\"Returns True if Checkpoint data is accessible\n\n        Args:\n            epoch: An integer. The epoch of the checkpoint. If None,\n                it implies we need to check if checkpoint directory is accessible\n\n        Returns:\n            is_cp_accessible: A boolean. Returns True if Checkpoint data is accessible\n        \"\"\"\n        if self._metadata_handler is not None:\n            return self._metadata_handler.cp_accessible(epoch)\n        else:\n            return True\n\n\nclass MultiNodeCheckpointManager(object):\n    \"\"\"\n    Coordinates checkpointing and checkpointing across multiple nodes.\n    Each of `init`, `load` and `save` will build TaskGroups which will\n    trigger checkpointing on each of the nodes involved in a distributed job.\n\n    Args:\n        db_prefix: The prefix used to construct full db name. Since `absolute_path`\n            is set to True, this will be used as db_name in SaveOp.\n        db_type: Type of database to use for storing checkpoint.\n        metadata_handler: An optional object capable of reading/writing\n            checkpoint info in storage of choice.\n    \"\"\"\n    def __init__(self, db_prefix, db_type, metadata_handler=None):\n        self._node_managers = None\n        self._db_prefix = db_prefix\n        self._db_type = db_type\n        self._metadata_handler = metadata_handler\n        self._path_prefix = None\n        self._path_type = None\n\n    def _task_group(self, func, *args, **kw):\n        assert self._node_managers is not None, 'init must be called first.'\n        with TaskGroup(WorkspaceType.GLOBAL) as task_group:\n            for node, manager in self._node_managers:\n                with Node(node):\n                    func(manager, *args, **kw)\n            return task_group\n\n    \"\"\"\n    Args:\n        nodes: An array of nodes where this checkpoint manager is running.\n        retrieve_from_epoch: Set to a number to load blobs from this epoch.\n        path_prefix: Used to construct db name or path where checkpoint files are\n            stored.\n        path_type: Indicate the type of path where checkpoint files are stored.\n    \"\"\"\n    def init(\n        self, nodes, retrieve_from_epoch=None, path_prefix=None, path_type=None\n    ):\n        if self._node_managers is not None:\n            assert [node for node, _ in self._node_managers] == nodes\n            return TaskGroup(WorkspaceType.GLOBAL)\n        self._node_managers = []\n        for node in nodes:\n            with Node(node):\n                manager = CheckpointManager(\n                    db_prefix=self._db_prefix,\n                    node_name=str(node),\n                    db_type=self._db_type)\n                self._node_managers.append((node, manager))\n        return self._task_group(\n            CheckpointManager.init,\n            nodes=[node],\n            retrieve_from_epoch=retrieve_from_epoch,\n            path_prefix=path_prefix,\n            path_type=path_type)\n\n    def load(self, epoch, path_prefix=None, path_type=None):\n        return self._task_group(\n            CheckpointManager.load,\n            epoch,\n            path_prefix=path_prefix,\n            path_type=path_type)\n\n    def load_blobs_locally(self, nodes, blob_names, epoch, session):\n        \"\"\"Loads the necessary blobs from the checkpoints to the current node.\n\n        Args:\n            blob_names: A list of strings. Each string is the name of a\n                blob.\n            epoch: An integer. The checkpoint epoch to load from.\n            session: A Session object to execute the Load ops.\n        \"\"\"\n        if self._node_managers is not None:\n            assert [node for node, _ in self._node_managers] == nodes\n        else:\n            self._node_managers = []\n            for node in nodes:\n                with Node(node):\n                    manager = CheckpointManager(\n                        db_prefix=self._db_prefix,\n                        node_name=str(node),\n                        db_type=self._db_type)\n                    self._node_managers.append((node, manager))\n        assert self._node_managers is not None, 'must initialize node managers'\n        for _, manager in self._node_managers:\n            existence_task = manager.check_db_exists(epoch)\n            session.run(existence_task)\n            existence = existence_task.outputs()[0].fetch()\n            if not existence:\n                logger.info('DB %s does not exist!' %\n                            db_name(epoch, manager._node_name, manager._db_prefix))\n                return False\n            load_task = manager.load_blobs_from_checkpoint(blob_names, epoch)\n            session.run(load_task)\n        logger.info('Successfully loaded from checkpoints.')\n        return True\n\n    def get_ckpt_db_name(self, node_name, epoch):\n        \"\"\"Returns the DB name of the given node and the given epoch.\n\n        The DB name is effectively the checkpoint path of the given node and\n        the given epoch.\n\n        Args:\n            node_name: A string. The node name of interest.\n            epoch: An integer. The epoch of the checkpoint.\n\n        Returns:\n            checkpoint_db_name: A string. The checkpoint path of the given\n                node and the given epoch.\n        \"\"\"\n        for node, manager in self._node_managers:\n            if str(node) == node_name:\n                return db_name(epoch, manager._node_name, manager._db_prefix)\n\n    def save(self, epoch):\n        \"\"\"\n        Build a Task that will execute a Save ops to serialize and persist\n        blobs present in the global workspace.\n        \"\"\"\n        return self._task_group(CheckpointManager.save, epoch)\n\n    def write_checkpoint_metadata(self, epoch):\n        \"\"\"\n        Write metadata for checkpoint\n\n        Args:\n            epoch: An integer. The epoch-id for which checkpoint metadata is\n                written\n        \"\"\"\n        if self._metadata_handler is not None:\n            self._metadata_handler.write(epoch=epoch)\n\n    def get_resume_from_epoch_id(self, user_epoch=None):\n        \"\"\"\n        Identify the epoch-id from which Job must resume\n\n        Args:\n            user_epoch: An integer. Optional parameter for user to explicitly\n                identify the epoch-id to load checkpoint from\n        Retruns:\n            epoch: the epoch-id to load checkpoints from\n                or None if no checkpoints were written\n        \"\"\"\n        last_epoch = user_epoch\n        if self._metadata_handler is not None:\n            last_epoch = self._metadata_handler.last_epoch(user_epoch=user_epoch)\n        return last_epoch\n\n    def set_params(self, nodes, path_prefix=None, path_type=None):\n        \"\"\"Set parameters associated with CP manager\n\n        Args:\n            nodes: An array of nodes where this checkpoint manager is running.\n            path_prefix: Used to construct db name or path where checkpoint files are\n                stored.\n            path_type: Indicate the type of path where checkpoint files are stored.\n        \"\"\"\n        self._node_names = [str(node) for node in nodes]\n        if path_prefix:\n            self._path_prefix = path_prefix\n        if path_type:\n            self._path_type = path_type\n        if self._metadata_handler:\n            self._metadata_handler.set_params(\n                db_prefix=self._db_prefix,\n                db_type=self._db_type,\n                node_names=self._node_names,\n                path_prefix=self._path_prefix,\n                path_type=self._path_type)\n\n    def cp_accessible(self, epoch=None):\n        \"\"\"Returns True if Checkpoint data is accessible\n\n        Args:\n            epoch: An integer. The epoch of the checkpoint. If None,\n                it implies we need to check if checkpoint directory is accessible\n\n        Returns:\n            is_cp_accessible: A boolean. Returns True if Checkpoint data is accessible\n        \"\"\"\n        if self._metadata_handler is not None:\n            return self._metadata_handler.cp_accessible(epoch)\n        else:\n            return True\n\n\nclass UploadTaskGroupBuilder(object):\n    \"\"\"A simple class to upload checkpoints.\"\"\"\n    def build(self, epoch, checkpoint_manager):\n        \"\"\"Builds the task group to upload checkpoints.\n\n        Args:\n            epoch: An integer. The checkpoint epoch to be uploaded.\n            checkpoint_manager: Can be a CheckpointManager for single machine\n                or a MultiNodeCheckpointManager for multi-machine. The manager\n                that initializes/saves/loads checkpoints.\n\n        Raises:\n            NotImplementedError: This base class only has the interface,\n                the implementation will be in the subclasses.\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass JobRunner(object):\n    \"\"\"\n    Implement the runtime logic for jobs with checkpointing at the level of\n    epoch. Can be used to run either single-host or distributed jobs. Job\n    runner is a callable to be called once from the master, passing a session\n    as an argument. This call will block until the Job execution is complete.\n\n    If a checkpoint_manager is passed, checkpoints will be taken after\n    initialization and after each epoch execution. If, in addition,\n    `resume_from_epoch` is an epoch number, the corresponding checkpoint will\n    be loaded and job execution will continue from the given epoch. In\n    this case, the job's init_group will not be run.\n\n    Refer to checkpoint_test.py for an example.\n    \"\"\"\n    def __init__(self, job, checkpoint_manager=None, resume_from_epoch=None,\n                 upload_task_group_builder=None):\n        \"\"\"Initializes the JobRunner.\n\n        Args:\n            job: A Job object. The job to be executed.\n            checkpoint_manager: Can be a CheckpointManager for single machine\n                or a MultiNodeCheckpointManager for multi-machine. The manager\n                that initializes/saves/loads checkpoints.\n            resume_from_epoch: An integer. The epoch to resume from.\n            upload_task_group_builder: A subclass of the\n                UploadTaskGroupBuilder. Creates a task group to upload\n                checkpoints.\n        \"\"\"\n        self.resume_from_epoch = resume_from_epoch\n        self.checkpoint_manager = checkpoint_manager\n        self.job = job\n        self.upload_task_group_builder = upload_task_group_builder\n\n    def __call__(self, session):\n        \"\"\"Runs the training flow.\n\n        Args:\n            session: A Session object. Valid choises are: LocalSession,\n                LocalHostScheduler, and DistributedSession. It is used to\n                execute one TaskGroup a time.\n        \"\"\"\n        # identify the epoch we must resume from\n        if self.checkpoint_manager:\n            self.checkpoint_manager.set_params(nodes=self.job.nodes_to_checkpoint())\n            self.resume_from_epoch = self.checkpoint_manager.\\\n                get_resume_from_epoch_id(self.resume_from_epoch)\n            if self.resume_from_epoch is not None:\n                logger.info('Resuming from epoch {}'.format(self.resume_from_epoch))\n\n        # Initialize all the nodes.\n        from_scratch = self.resume_from_epoch is None\n        if from_scratch:\n            session.run(self.job.init_group)\n\n        if self.checkpoint_manager:\n            logger.info('Preparing checkpoints ...')\n            session.run(self.checkpoint_manager.init(\n                self.job.nodes_to_checkpoint(),\n                retrieve_from_epoch=self.resume_from_epoch))\n            # Save the first checkpoint before training starts, or resume from\n            # a previously saved checkpoint.\n            if from_scratch:\n                self.save_checkpoints(0, session)\n            else:\n                logger.info('Loading checkpoints for epoch {} ...'.format(\n                    self.resume_from_epoch))\n                session.run(\n                    self.checkpoint_manager.load(self.resume_from_epoch))\n                logger.info('Checkpoint loaded')\n\n        logger.info(\"Finished initializing\")\n\n        # Start training.\n        epoch = 1 if from_scratch else self.resume_from_epoch + 1\n        while True:\n            logger.info('Starting epoch %d' % epoch)\n            session.run(self.job.epoch_group)\n            logger.info('Finished epoch %d' % epoch)\n            stop_signals = [o.fetch() for o in self.job.stop_signals]\n\n            if self.checkpoint_manager:\n                self.save_checkpoints(epoch, session)\n\n            if any(stop_signals):\n                logger.info('Stopping')\n                break\n            epoch += 1\n        logger.info('Finished training')\n        # Upload the checkpoints.\n        if (self.upload_task_group_builder):\n            upload_task_group = self.upload_task_group_builder.build(\n                epoch, self.checkpoint_manager)\n            session.run(upload_task_group)\n            logger.info('Finished uploading the checkpoints')\n\n        # Download the parameters to save\n        session.run(self.job.download_group)\n        logger.info('Finished downloading the parameters')\n\n        # Finally run the exit step to save nets\n        session.run(self.job.exit_group)\n        logger.info('Finished running the exit group')\n        return epoch\n\n    def load_blobs_from_checkpoints(self, blob_names, epoch, session):\n        \"\"\"Loads the necessary blobs from the checkpoints.\n\n        Checkpoints store the snapshots of the workspace in each node.\n        Sometimes we only need to load a subset of the blobs from the\n        checkpoints. One common scenario is to load only the model blobs from\n        the checkpoints for evaluation purpose. Given the names of the\n        necessary blobs, this function goes over all the checkpoints of all the\n        nodes, but only loads the blobs specified in the blob_names to the\n        current workspace.\n\n        Args:\n            blob_names: A list of strings. Each string is the name of a\n                blob.\n            epoch: An integer. The checkpoint epoch to load from.\n            session: A Session object to execute the load ops.\n\n        Raises:\n            ValueError: When the checkpoint manager is invalid.\n        \"\"\"\n        if not self.checkpoint_manager:\n            raise ValueError('Checkpoint manager is None')\n        logger.info('Loading checkpoint for epoch {} ...'.format(epoch))\n        return self.checkpoint_manager.load_blobs_locally(\n            self.job.nodes_to_checkpoint(), blob_names, epoch, session)\n\n    def save_checkpoints(self, epoch, session):\n        \"\"\"Triggers operation to save checkpoints\n\n        This method will trigger the Save ops to serialize and persist the\n        blobs present in the global workspaace.\n\n        Args:\n            epoch: An integer. The checkpoint epoch-id that we are saving.\n            session: A Session object to execute the save ops.\n\n        Raises:\n            ValueError: When the checkpoint manager is invalid.\n        \"\"\"\n        if not self.checkpoint_manager:\n            raise ValueError('Checkpoint manager is None')\n        try:\n            is_accessible = self.checkpoint_manager.cp_accessible(epoch=None)\n            if is_accessible:\n                logger.info('Saving checkpoints for epoch {}'.format(epoch))\n                session.run(self.checkpoint_manager.save(epoch))\n                self.checkpoint_manager.write_checkpoint_metadata(epoch)\n                logger.info('Checkpoints saved')\n            else:\n                logger.warning(\"Checkpoint files cannot be accessed!\")\n        except Exception as ex:\n            logger.warning(\"Unable to write checkpoint for epoch {}. Error={}\".\n                            format(epoch, ex))\n\n\ndef epoch_limiter(num_epochs):\n    \"\"\"\n    Creates a task that will output True when a given\n    number of epochs has finished.\n    \"\"\"\n    with Job.current().init_group:\n        init_net = core.Net('epoch_counter_init')\n        counter = init_net.CreateCounter([], init_count=num_epochs - 1)\n        Task(step=init_net)\n\n    with Job.current().epoch_group:\n        epoch_net = core.Net('epoch_countdown')\n        finished = epoch_net.CountDown(counter)\n        output = Task(step=epoch_net, outputs=finished).outputs()[0]\n    Job.current().add_stop_signal(output)\n"
  },
  {
    "path": "caffe2/python/checkpoint_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python.schema import Struct, ConstRecord\nfrom caffe2.python import core, workspace, model_helper\nfrom caffe2.python.session import LocalSession\nfrom caffe2.python.dataset import Dataset\nfrom caffe2.python.pipeline import pipe\nfrom caffe2.python.checkpoint import (\n    CheckpointManager, MultiNodeCheckpointManager, Job, JobRunner, epoch_limiter,\n    UploadTaskGroupBuilder, db_name)\nfrom caffe2.python.net_builder import ops\nfrom caffe2.python.task import Node, Task, TaskGroup, WorkspaceType, Cluster\nfrom caffe2.python.test_util import TestCase\nfrom caffe2.python.dataio import ReaderWithLimit\n\nimport numpy as np\nimport os\nimport shutil\nimport tempfile\n\ndef build_pipeline(node_id):\n    with Node('trainer_%d' % node_id):\n        with Job.current().init_group, Task():\n            data_arr = Struct(('val', np.array(list(range(10)))))\n            data = ConstRecord(ops, data_arr)\n            ds = Dataset(data, name='dataset:%d' % node_id)\n            full_reader = ds.reader(ops)\n            total = ops.Const([100])\n\n        def inc_total(rec):\n            ops.Add([total, rec.val()], [total])\n\n        epoch_reader = ReaderWithLimit(full_reader, num_iter=3)\n        pipe(epoch_reader, processor=inc_total)\n        Job.current().add_stop_signal(epoch_reader.data_finished())\n    return [total]\n\n\nEXPECTED_TOTALS = [103, 115, 136, 145]\n\n\ndef local_copy_op(src, dest):\n    def copy_op(inputs, outputs):\n        shutil.copyfile(src, dest)\n    return copy_op\n\n\nclass UploadToLocalFile(UploadTaskGroupBuilder):\n    def __init__(self, dest_dir):\n        self.dest_dir = dest_dir\n\n    def build(self, epoch, checkpoint_manager):\n        with TaskGroup(WorkspaceType.GLOBAL) as upload_task_group:\n            for node, manager in checkpoint_manager._node_managers:\n                with Node(str(node)), Task():\n                    src_path = db_name(epoch, manager._node_name, manager._db_prefix)\n                    dest_path = os.path.join(self.dest_dir, str(node))\n                    ops.Python((local_copy_op,\n                                [src_path, dest_path], {}))([], [])\n        return upload_task_group\n\nclass TestCheckpoint(TestCase):\n    def run_with(self, builder):\n        with Cluster():\n            with Job() as job:\n                outputs = build_pipeline(node_id=0)\n            output_fetcher = Task(step=core.Net('empty'), outputs=outputs)\n\n            def fetch_total(session):\n                session.run(output_fetcher)\n                return output_fetcher.outputs()[0].fetch()\n\n            session, checkpoint = builder()\n            compiled_job = job.compile(LocalSession)\n            num_epochs = JobRunner(compiled_job, checkpoint)(session)\n            self.assertEquals(num_epochs, len(EXPECTED_TOTALS))\n            self.assertEquals(fetch_total(session), EXPECTED_TOTALS[-1])\n\n            for initial_epoch in range(1, num_epochs + 1):\n                session, checkpoint = builder()\n                JobRunner(\n                    compiled_job,\n                    checkpoint, resume_from_epoch=initial_epoch)(session)\n                self.assertEquals(fetch_total(session), EXPECTED_TOTALS[-1])\n\n            for epoch in range(1, num_epochs + 1):\n                session.run(checkpoint.load(epoch))\n                self.assertEquals(fetch_total(session),\n                                  EXPECTED_TOTALS[epoch - 1])\n\n    def test_single_checkpoint(self):\n        # test single node\n        try:\n            tmpdir = tempfile.mkdtemp()\n\n            def builder():\n                ws = workspace.C.Workspace()\n                session = LocalSession(ws)\n                checkpoint = CheckpointManager(tmpdir, 'temp_node', 'minidb')\n                return session, checkpoint\n\n            self.run_with(builder)\n        finally:\n            shutil.rmtree(tmpdir)\n\n        # test multi-node\n        try:\n            tmpdir = tempfile.mkdtemp()\n\n            def builder():\n                ws = workspace.C.Workspace()\n                session = LocalSession(ws)\n                checkpoint = MultiNodeCheckpointManager(tmpdir, 'minidb')\n                return session, checkpoint\n\n            self.run_with(builder)\n        finally:\n            shutil.rmtree(tmpdir)\n\n    def test_ckpt_name_and_load_model_from_ckpts(self):\n        try:\n            num_nodes = 3\n            tmpdir = tempfile.mkdtemp()\n            # First, check if the checkpoint name generation mechanism is\n            # correct.\n            checkpoint = MultiNodeCheckpointManager(tmpdir, 'minidb')\n            with Cluster():\n                with Job() as job:\n                    for node_id in range(num_nodes):\n                        build_pipeline(node_id)\n                compiled_job = job.compile(LocalSession)\n                checkpoint.init(compiled_job.nodes_to_checkpoint())\n\n                for node_id in range(num_nodes):\n                    epoch = 5\n                    node_name = 'trainer_%d' % node_id\n                    expected_db_name = tmpdir + '/' + node_name + '.5'\n                    self.assertEquals(\n                        checkpoint.get_ckpt_db_name(node_name, epoch),\n                        expected_db_name)\n            shutil.rmtree(tmpdir)\n\n            # Next, check mechanism to load model from checkpoints.\n            tmpdir = tempfile.mkdtemp()\n            workspace.ResetWorkspace()\n            for node_id in range(num_nodes):\n                ws = workspace.C.Workspace()\n                session = LocalSession(ws)\n                checkpoint = MultiNodeCheckpointManager(tmpdir, 'minidb')\n                with Cluster():\n                    with Job() as job:\n                        build_pipeline(node_id)\n                    compiled_job = job.compile(LocalSession)\n                    job_runner = JobRunner(compiled_job, checkpoint)\n                    num_epochs = job_runner(session)\n                self.assertEquals(num_epochs, len(EXPECTED_TOTALS))\n\n                # There are 12 global blobs after finishing up the job runner.\n                # (only blobs on init_group are checkpointed)\n                self.assertEquals(len(ws.blobs), 12)\n\n            ws = workspace.C.Workspace()\n            session = LocalSession(ws)\n            self.assertEquals(len(ws.blobs), 0)\n            model_blob_names = ['trainer_1/task_2/GivenTensorInt64Fill:0',\n                                'trainer_2/task_2/GivenTensorInt64Fill:0']\n            checkpoint = MultiNodeCheckpointManager(tmpdir, 'minidb')\n            with Cluster():\n                with Job() as job:\n                    for node_id in range(num_nodes):\n                        build_pipeline(node_id)\n                compiled_job = job.compile(LocalSession)\n                job_runner = JobRunner(compiled_job, checkpoint)\n                job_runner.load_blobs_from_checkpoints(\n                    blob_names=model_blob_names, epoch=1, session=session)\n\n                # Check that we can successfully load from checkpoints of epochs\n                # 1 to 4, but not epoch 5.\n                for epoch in range(1, 5):\n                    self.assertTrue(\n                        job_runner.load_blobs_from_checkpoints(\n                            blob_names=model_blob_names, epoch=epoch,\n                            session=session))\n                    # Check that all the model blobs are loaded.\n                    for blob_name in model_blob_names:\n                        self.assertTrue(ws.has_blob(blob_name))\n                        self.assertEquals(\n                            ws.fetch_blob(blob_name),\n                            np.array([EXPECTED_TOTALS[epoch - 1]]))\n                self.assertFalse(\n                    job_runner.load_blobs_from_checkpoints(\n                        blob_names=model_blob_names, epoch=5, session=session))\n\n        finally:\n            shutil.rmtree(tmpdir)\n\n    def test_upload_checkpoint(self):\n        try:\n            tmpdir = tempfile.mkdtemp()\n            upload_dir = os.path.join(tmpdir, \"upload\")\n            os.mkdir(upload_dir)\n            num_nodes = 3\n\n            # The uploaded files do not exist yet.\n            for node_id in range(num_nodes):\n                node_name = 'trainer_%d' % node_id\n                upload_path = os.path.join(upload_dir, node_name)\n                self.assertFalse(os.path.exists(upload_path))\n\n            # Create and run the job runner.\n            for node_id in range(3):\n                ws = workspace.C.Workspace()\n                session = LocalSession(ws)\n                checkpoint = MultiNodeCheckpointManager(tmpdir, 'minidb')\n                with Cluster():\n                    with Job() as job:\n                        build_pipeline(node_id)\n                    compiled_job = job.compile(LocalSession)\n                    local_upload_builder = UploadToLocalFile(upload_dir)\n                    job_runner = JobRunner(\n                        compiled_job, checkpoint,\n                        upload_task_group_builder=local_upload_builder)\n                    num_epochs = job_runner(session)\n                    self.assertEquals(num_epochs, len(EXPECTED_TOTALS))\n\n            # The uploaded files should exist now.\n            for node_id in range(num_nodes):\n                node_name = 'trainer_%d' % node_id\n                upload_path = os.path.join(upload_dir, node_name)\n                self.assertTrue(os.path.exists(upload_path))\n\n        finally:\n            shutil.rmtree(tmpdir)\n\n    def test_ckpt_save_failure(self):\n        num_nodes = 3\n        # The goal of this test is to ensure that the job runs\n        # successfully even if saving a checkpoint fails.\n        # Hence tmpdir is a non existent directory to emulate a failure\n        # while saving checkpoints\n        tmpdir = \"/tmp/path_does_not_exist/\"\n\n        # Check the saving checkpoint failure does not cause job failure\n        workspace.ResetWorkspace()\n        for node_id in range(num_nodes):\n            ws = workspace.C.Workspace()\n            session = LocalSession(ws)\n            checkpoint = MultiNodeCheckpointManager(tmpdir, 'minidb')\n            with Cluster():\n                with Job() as job:\n                    build_pipeline(node_id)\n                compiled_job = job.compile(LocalSession)\n                job_runner = JobRunner(compiled_job, checkpoint)\n                num_epochs = job_runner(session)\n            # make sure all epochs are executed even though saving the checkpoint failed\n            # Saving checkpoint failure should not cause job failure\n            self.assertEquals(num_epochs, len(EXPECTED_TOTALS))\n\n    def test_download_group_simple(self):\n        \"\"\"\n        A simple test that ensures we have download task group\n        executed between epoch_group and exit_group.\n        \"\"\"\n        model = model_helper.ModelHelper(name=\"test_model\")\n        download_net = core.Net(\"download_net\")\n\n        for name in [\"input1\", \"input2\", \"output\", \"download_result\"]:\n            model.param_init_net.ConstantFill([],\n                                              [name],\n                                              shape=[8, ],\n                                              value=1.0,\n                                              run_once=0)\n        model.net.Add([\"input1\", \"input2\"], [\"output\"])\n        download_net.Copy([\"output\"], [\"download_result\"])\n\n        # All blob values are initialized as 1.0, after download_net executed\n        # we expect to see download result is the same as training result.\n        with Job() as job:\n            with Node(\"trainer:0\"):\n                epoch_limiter(1)\n                with job.init_group:\n                    Task(step=model.param_init_net)\n                with job.epoch_group:\n                    with Task():\n                        with ops.loop(1):\n                            ops.net(model.net)\n                with job.download_group:\n                    Task(step=download_net)\n\n        ws = workspace.C.Workspace()\n        session = LocalSession(ws)\n        job_runner = JobRunner(job)\n        job_runner(session)\n\n        expected_result = np.full(8, 2.0).astype(np.float32)\n        self.assertTrue(np.array_equal(expected_result,\n                                       ws.fetch_blob(\"output\")))\n        self.assertTrue(np.array_equal(expected_result,\n                                       ws.fetch_blob(\"download_result\")))\n\n    def test_reuse_checkpoint_manager(self):\n        \"\"\"\n        A simple test that ensures we can reuse a MultiNodeCheckpointManager\n        object.\n        \"\"\"\n        try:\n            tmpdir = tempfile.mkdtemp()\n            ws = workspace.C.Workspace()\n            session = LocalSession(ws)\n            checkpoint = MultiNodeCheckpointManager(tmpdir, 'minidb')\n\n            with Job() as job:\n                outputs = build_pipeline(node_id=0)\n            output_fetcher = Task(step=core.Net('empty'), outputs=outputs)\n            compiled_job = job.compile(LocalSession)\n\n            def fetch_total(session):\n                session.run(output_fetcher)\n                return output_fetcher.outputs()[0].fetch()\n\n            num_epochs = JobRunner(compiled_job, checkpoint)(session)\n            for initial_epoch in range(1, num_epochs + 1):\n                JobRunner(\n                    compiled_job,\n                    checkpoint, resume_from_epoch=initial_epoch)(session)\n                self.assertEquals(fetch_total(session), EXPECTED_TOTALS[-1])\n\n        finally:\n            shutil.rmtree(tmpdir)\n"
  },
  {
    "path": "caffe2/python/cnn.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package cnn\n# Module caffe2.python.cnn\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import brew\nfrom caffe2.python.model_helper import ModelHelper\nfrom caffe2.proto import caffe2_pb2\nimport logging\n\n\nclass CNNModelHelper(ModelHelper):\n    \"\"\"A helper model so we can write CNN models more easily, without having to\n    manually define parameter initializations and operators separately.\n    \"\"\"\n\n    def __init__(self, order=\"NCHW\", name=None,\n                 use_cudnn=True, cudnn_exhaustive_search=False,\n                 ws_nbytes_limit=None, init_params=True,\n                 skip_sparse_optim=False,\n                 param_model=None):\n        logging.warning(\n            \"[====DEPRECATE WARNING====]: you are creating an \"\n            \"object from CNNModelHelper class which will be deprecated soon. \"\n            \"Please use ModelHelper object with brew module. For more \"\n            \"information, please refer to caffe2.ai and python/brew.py, \"\n            \"python/brew_test.py for more information.\"\n        )\n\n        cnn_arg_scope = {\n            'order': order,\n            'use_cudnn': use_cudnn,\n            'cudnn_exhaustive_search': cudnn_exhaustive_search,\n        }\n        if ws_nbytes_limit:\n            cnn_arg_scope['ws_nbytes_limit'] = ws_nbytes_limit\n        super(CNNModelHelper, self).__init__(\n            skip_sparse_optim=skip_sparse_optim,\n            name=\"CNN\" if name is None else name,\n            init_params=init_params,\n            param_model=param_model,\n            arg_scope=cnn_arg_scope,\n        )\n\n        self.order = order\n        self.use_cudnn = use_cudnn\n        self.cudnn_exhaustive_search = cudnn_exhaustive_search\n        self.ws_nbytes_limit = ws_nbytes_limit\n        if self.order != \"NHWC\" and self.order != \"NCHW\":\n            raise ValueError(\n                \"Cannot understand the CNN storage order %s.\" % self.order\n            )\n\n    def ImageInput(self, blob_in, blob_out, use_gpu_transform=False, **kwargs):\n        return brew.image_input(\n            self,\n            blob_in,\n            blob_out,\n            order=self.order,\n            use_gpu_transform=use_gpu_transform,\n            **kwargs\n        )\n\n    def VideoInput(self, blob_in, blob_out, **kwargs):\n        return brew.video_input(\n            self,\n            blob_in,\n            blob_out,\n            **kwargs\n        )\n\n    def PadImage(self, blob_in, blob_out, **kwargs):\n        # TODO(wyiming): remove this dummy helper later\n        self.net.PadImage(blob_in, blob_out, **kwargs)\n\n    def ConvNd(self, *args, **kwargs):\n        return brew.conv_nd(\n            self,\n            *args,\n            use_cudnn=self.use_cudnn,\n            order=self.order,\n            cudnn_exhaustive_search=self.cudnn_exhaustive_search,\n            ws_nbytes_limit=self.ws_nbytes_limit,\n            **kwargs\n        )\n\n    def Conv(self, *args, **kwargs):\n        return brew.conv(\n            self,\n            *args,\n            use_cudnn=self.use_cudnn,\n            order=self.order,\n            cudnn_exhaustive_search=self.cudnn_exhaustive_search,\n            ws_nbytes_limit=self.ws_nbytes_limit,\n            **kwargs\n        )\n\n    def ConvTranspose(self, *args, **kwargs):\n        return brew.conv_transpose(\n            self,\n            *args,\n            use_cudnn=self.use_cudnn,\n            order=self.order,\n            cudnn_exhaustive_search=self.cudnn_exhaustive_search,\n            ws_nbytes_limit=self.ws_nbytes_limit,\n            **kwargs\n        )\n\n    def GroupConv(self, *args, **kwargs):\n        return brew.group_conv(\n            self,\n            *args,\n            use_cudnn=self.use_cudnn,\n            order=self.order,\n            cudnn_exhaustive_search=self.cudnn_exhaustive_search,\n            ws_nbytes_limit=self.ws_nbytes_limit,\n            **kwargs\n        )\n\n    def GroupConv_Deprecated(self, *args, **kwargs):\n        return brew.group_conv_deprecated(\n            self,\n            *args,\n            use_cudnn=self.use_cudnn,\n            order=self.order,\n            cudnn_exhaustive_search=self.cudnn_exhaustive_search,\n            ws_nbytes_limit=self.ws_nbytes_limit,\n            **kwargs\n        )\n\n    def FC(self, *args, **kwargs):\n        return brew.fc(self, *args, **kwargs)\n\n    def PackedFC(self, *args, **kwargs):\n        return brew.packed_fc(self, *args, **kwargs)\n\n    def FC_Prune(self, *args, **kwargs):\n        return brew.fc_prune(self, *args, **kwargs)\n\n    def FC_Decomp(self, *args, **kwargs):\n        return brew.fc_decomp(self, *args, **kwargs)\n\n    def FC_Sparse(self, *args, **kwargs):\n        return brew.fc_sparse(self, *args, **kwargs)\n\n    def Dropout(self, *args, **kwargs):\n        return brew.dropout(\n            self, *args, order=self.order, use_cudnn=self.use_cudnn, **kwargs\n        )\n\n    def LRN(self, *args, **kwargs):\n        return brew.lrn(\n            self, *args, order=self.order, use_cudnn=self.use_cudnn, **kwargs\n        )\n\n    def Softmax(self, *args, **kwargs):\n        return brew.softmax(self, *args, use_cudnn=self.use_cudnn, **kwargs)\n\n    def SpatialBN(self, *args, **kwargs):\n        return brew.spatial_bn(self, *args, order=self.order, **kwargs)\n\n    def InstanceNorm(self, *args, **kwargs):\n        return brew.instance_norm(self, *args, order=self.order, **kwargs)\n\n    def Relu(self, *args, **kwargs):\n        return brew.relu(\n            self, *args, order=self.order, use_cudnn=self.use_cudnn, **kwargs\n        )\n\n    def PRelu(self, *args, **kwargs):\n        return brew.prelu(self, *args, **kwargs)\n\n    def Concat(self, *args, **kwargs):\n        return brew.concat(self, *args, order=self.order, **kwargs)\n\n    def DepthConcat(self, *args, **kwargs):\n        \"\"\"The old depth concat function - we should move to use concat.\"\"\"\n        print(\"DepthConcat is deprecated. use Concat instead.\")\n        return self.Concat(*args, **kwargs)\n\n    def Sum(self, *args, **kwargs):\n        return brew.sum(self, *args, **kwargs)\n\n    def Transpose(self, *args, **kwargs):\n        return brew.transpose(self, *args, use_cudnn=self.use_cudnn, **kwargs)\n\n    def Iter(self, *args, **kwargs):\n        return brew.iter(self, *args, **kwargs)\n\n    def Accuracy(self, *args, **kwargs):\n        return brew.accuracy(self, *args, **kwargs)\n\n    def MaxPool(self, *args, **kwargs):\n        return brew.max_pool(\n            self, *args, use_cudnn=self.use_cudnn, order=self.order, **kwargs\n        )\n\n    def MaxPoolWithIndex(self, *args, **kwargs):\n        return brew.max_pool_with_index(self, *args, order=self.order, **kwargs)\n\n    def AveragePool(self, *args, **kwargs):\n        return brew.average_pool(\n            self, *args, use_cudnn=self.use_cudnn, order=self.order, **kwargs\n        )\n\n    @property\n    def XavierInit(self):\n        return ('XavierFill', {})\n\n    def ConstantInit(self, value):\n        return ('ConstantFill', dict(value=value))\n\n    @property\n    def MSRAInit(self):\n        return ('MSRAFill', {})\n\n    @property\n    def ZeroInit(self):\n        return ('ConstantFill', {})\n\n    def AddWeightDecay(self, weight_decay):\n        return brew.add_weight_decay(self, weight_decay)\n\n    @property\n    def CPU(self):\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.device_type = caffe2_pb2.CPU\n        return device_option\n\n    @property\n    def GPU(self, gpu_id=0):\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.device_type = caffe2_pb2.CUDA\n        device_option.cuda_gpu_id = gpu_id\n        return device_option\n"
  },
  {
    "path": "caffe2/python/context.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package context\n# Module caffe2.python.context\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport threading\nimport six\n\n\nclass ContextInfo(object):\n    def __init__(self, cls, allow_default, arg_name):\n        self.cls = cls\n        self.allow_default = allow_default\n        self.arg_name = arg_name\n        self._local_stack = threading.local()\n\n    @property\n    def _stack(self):\n        if not hasattr(self._local_stack, 'obj'):\n            self._local_stack.obj = []\n        return self._local_stack.obj\n\n    def enter(self, value):\n        self._stack.append(value)\n\n    def exit(self, value):\n        assert len(self._stack) > 0, 'Context %s is empty.' % self.cls\n        assert self._stack.pop() == value\n\n    def get_active(self, required=True):\n        if len(self._stack) == 0:\n            if not required:\n                return None\n            assert self.allow_default, (\n                'Context %s is required but none is active.' % self.cls)\n            self.enter(self.cls())\n        return self._stack[-1]\n\n\nclass ContextManager(object):\n    def __init__(self):\n        self._ctxs = {}\n\n    def register(self, ctx_info):\n        assert isinstance(ctx_info, ContextInfo)\n        assert (ctx_info.cls not in self._ctxs), (\n            'Context %s already registered' % ctx_info.cls)\n        self._ctxs[ctx_info.cls] = ctx_info\n\n    def get(self, cls):\n        assert cls in self._ctxs, 'Context %s not registered.' % cls\n        return self._ctxs[cls]\n\n\n_CONTEXT_MANAGER = ContextManager()\n\n\ndef context_manager():\n    global _CONTEXT_MANAGER\n    return _CONTEXT_MANAGER\n\n\ndef __enter__(self):\n    if self._prev_enter is not None:\n        self._prev_enter()\n    context_manager().get(self._ctx_class).enter(self)\n    return self\n\n\ndef __exit__(self, *args):\n    context_manager().get(self._ctx_class).exit(self)\n    if self._prev_exit is not None:\n        self._prev_exit(*args)\n\n\ndef __call__(self, func):\n    @six.wraps(func)\n    def wrapper(*args, **kwargs):\n        with self:\n            return func(*args, **kwargs)\n    return wrapper\n\n\n@classmethod\ndef current(cls, value=None, required=True):\n    return get_active_context(cls, value, required)\n\n\nclass define_context(object):\n    def __init__(self, arg_name=None, allow_default=False):\n        self.arg_name = arg_name\n        self.allow_default = allow_default\n\n    def __call__(self, cls):\n        assert not hasattr(cls, '_ctx_class'), (\n            '%s parent class (%s) already defines context.' % (\n                cls, cls._ctx_class))\n        context_manager().register(\n            ContextInfo(cls, self.allow_default, self.arg_name))\n        cls._prev_enter = cls.__enter__ if hasattr(cls, '__enter__') else None\n        cls._prev_exit = cls.__exit__ if hasattr(cls, '__exit__') else None\n        cls._ctx_class = cls\n        cls.__enter__ = __enter__\n        cls.__exit__ = __exit__\n        cls.__call__ = __call__\n        cls.current = current\n        return cls\n\n\ndef get_active_context(cls, val=None, required=True):\n    ctx_info = context_manager().get(cls)\n    if val is not None:\n        assert isinstance(val, cls), (\n            'Wrong context type. Expected: %s, got %s.' % (cls, type(val)))\n        return val\n    return ctx_info.get_active(required=required)\n"
  },
  {
    "path": "caffe2/python/context_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import context, test_util\nfrom threading import Thread\n\n\n@context.define_context()\nclass MyContext(object):\n    pass\n\n\nclass TestContext(test_util.TestCase):\n    def use_my_context(self):\n        try:\n            for _ in range(100):\n                with MyContext() as a:\n                    for _ in range(100):\n                        self.assertTrue(MyContext.current() == a)\n        except Exception as e:\n            self._exceptions.append(e)\n\n    def testMultiThreaded(self):\n        threads = []\n        self._exceptions = []\n        for _ in range(8):\n            thread = Thread(target=self.use_my_context)\n            thread.start()\n            threads.append(thread)\n        for t in threads:\n            t.join()\n        for e in self._exceptions:\n            raise e\n\n    @MyContext()\n    def testDecorator(self):\n        self.assertIsNotNone(MyContext.current())\n"
  },
  {
    "path": "caffe2/python/control.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package control\n# Module caffe2.python.control\n\"\"\"\nImplement functions for controlling execution of nets and steps, including\n  Do\n  DoParallel\n  For-loop\n  While-loop\n  Do-While-loop\n  Switch\n  If\n\"\"\"\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom future.utils import viewitems\n\n\n# Used to generate names of the steps created by the control functions.\n# It is actually the internal index of these steps.\n_current_idx = 1\n_used_step_names = set()\n\n\ndef _get_next_step_name(control_name, base_name):\n    global _current_idx, _used_step_names\n    concat_name = '%s/%s' % (base_name, control_name)\n    next_name = concat_name\n    while next_name in _used_step_names:\n        next_name = '%s_%d' % (concat_name, _current_idx)\n        _current_idx += 1\n    _used_step_names.add(next_name)\n    return next_name\n\n\ndef _MakeList(input):\n    \"\"\" input is a tuple.\n    Example:\n    (a, b, c)   --> [a, b, c]\n    (a)         --> [a]\n    ([a, b, c]) --> [a, b, c]\n    \"\"\"\n    if len(input) == 0:\n        raise ValueError(\n            'input cannot be empty.')\n    elif len(input) == 1:\n        output = input[0]\n        if not isinstance(output, list):\n            output = [output]\n    else:\n        output = list(input)\n    return output\n\n\ndef _IsNets(nets_or_steps):\n    if isinstance(nets_or_steps, list):\n        return all(isinstance(n, core.Net) for n in nets_or_steps)\n    else:\n        return isinstance(nets_or_steps, core.Net)\n\n\ndef _PrependNets(nets_or_steps, *nets):\n    nets_or_steps = _MakeList((nets_or_steps,))\n    nets = _MakeList(nets)\n    if _IsNets(nets_or_steps):\n        return nets + nets_or_steps\n    else:\n        return [Do('prepend', nets)] + nets_or_steps\n\n\ndef _AppendNets(nets_or_steps, *nets):\n    nets_or_steps = _MakeList((nets_or_steps,))\n    nets = _MakeList(nets)\n    if _IsNets(nets_or_steps):\n        return nets_or_steps + nets\n    else:\n        return nets_or_steps + [Do('append', nets)]\n\n\ndef GetConditionBlobFromNet(condition_net):\n    \"\"\"\n    The condition blob is the last external_output that must\n    be a single bool\n    \"\"\"\n    assert len(condition_net.Proto().external_output) > 0, (\n        \"Condition net %s must has at least one external output\" %\n        condition_net.Proto.name)\n    # we need to use a blob reference here instead of a string\n    # otherwise, it will add another name_scope to the input later\n    # when we create new ops (such as OR of two inputs)\n    return core.BlobReference(condition_net.Proto().external_output[-1])\n\n\ndef BoolNet(*blobs_with_bool_value):\n    \"\"\"A net assigning constant bool values to blobs. It is mainly used for\n    initializing condition blobs, for example, in multi-task learning, we\n    need to access reader_done blobs before reader_net run. In that case,\n    the reader_done blobs must be initialized.\n\n    Args:\n    blobs_with_bool_value: one or more (blob, bool_value) pairs. The net will\n    assign each bool_value to the corresponding blob.\n\n    returns\n    bool_net: A net assigning constant bool values to blobs.\n\n    Examples:\n    - BoolNet((blob_1, bool_value_1), ..., (blob_n, bool_value_n))\n    - BoolNet([(blob_1, net1), ..., (blob_n, bool_value_n)])\n    - BoolNet((cond_1, bool_value_1))\n    \"\"\"\n    blobs_with_bool_value = _MakeList(blobs_with_bool_value)\n    bool_net = core.Net('bool_net')\n    for blob, bool_value in blobs_with_bool_value:\n        out_blob = bool_net.ConstantFill(\n            [],\n            [blob],\n            shape=[],\n            value=bool_value,\n            dtype=core.DataType.BOOL)\n        bool_net.AddExternalOutput(out_blob)\n\n    return bool_net\n\n\ndef NotNet(condition_blob_or_net):\n    \"\"\"Not of a condition blob or net\n\n    Args:\n    condition_blob_or_net can be either blob or net. If condition_blob_or_net\n    is Net, the condition is its last external_output\n    that must be a single bool.\n\n    returns\n    not_net: the net NOT the input\n    out_blob: the output blob of the not_net\n    \"\"\"\n    if isinstance(condition_blob_or_net, core.Net):\n        condition_blob = GetConditionBlobFromNet(condition_blob_or_net)\n    else:\n        condition_blob = condition_blob_or_net\n\n    not_net = core.Net('not_net')\n    out_blob = not_net.Not(condition_blob)\n    not_net.AddExternalOutput(out_blob)\n\n    return not_net, out_blob\n\n\ndef _CopyConditionBlobNet(condition_blob):\n    \"\"\"Make a condition net that copies the condition_blob\n\n    Args:\n    condition_blob is a single bool.\n\n    returns\n    not_net: the net NOT the input\n    out_blob: the output blob of the not_net\n    \"\"\"\n    condition_net = core.Net('copy_condition_blob_net')\n    out_blob = condition_net.Copy(condition_blob)\n    condition_net.AddExternalOutput(out_blob)\n\n    return condition_net, out_blob\n\n\ndef MergeConditionNets(name, condition_nets, relation):\n    \"\"\"\n    Merge multi condition nets into a single condition nets.\n\n    Args:\n        name: name of the new condition net.\n        condition_nets: a list of condition nets. The last external_output\n                        of each condition net must be single bool value.\n        relation: can be 'And' or 'Or'.\n\n    Returns:\n        - A new condition net. Its last external output is relation of all\n          condition_nets.\n    \"\"\"\n    if not isinstance(condition_nets, list):\n        return condition_nets\n    if len(condition_nets) <= 1:\n        return condition_nets[0] if condition_nets else None\n\n    merged_net = core.Net(name)\n    for i in range(len(condition_nets)):\n        net_proto = condition_nets[i].Proto()\n        assert net_proto.device_option == merged_net.Proto().device_option\n        assert net_proto.type == merged_net.Proto().type\n        merged_net.Proto().op.extend(net_proto.op)\n        merged_net.Proto().external_input.extend(net_proto.external_input)\n        # discard external outputs as we're combining them together\n        curr_cond = GetConditionBlobFromNet(condition_nets[i])\n        if i == 0:\n            last_cond = curr_cond\n        else:\n            last_cond = merged_net.__getattr__(relation)([last_cond, curr_cond])\n        # merge attributes\n        for k, v in viewitems(condition_nets[i]._attr_dict):\n            merged_net._attr_dict[k] += v\n\n    merged_net.AddExternalOutput(last_cond)\n\n    return merged_net\n\n\ndef CombineConditions(name, condition_nets, relation):\n    \"\"\"\n    Combine conditions of multi nets into a single condition nets. Unlike\n    MergeConditionNets, the actual body of condition_nets is not copied into\n    the combine condition net.\n\n    One example is about multi readers. Each reader net has a reader_done\n    condition. When we want to check whether all readers are done, we can\n    use this function to build a new net.\n\n    Args:\n        name: name of the new condition net.\n        condition_nets: a list of condition nets. The last external_output\n                        of each condition net must be single bool value.\n        relation: can be 'And' or 'Or'.\n\n    Returns:\n        - A new condition net. Its last external output is relation of all\n          condition_nets.\n    \"\"\"\n    if not condition_nets:\n        return None\n    if not isinstance(condition_nets, list):\n        raise ValueError('condition_nets must be a list of nets.')\n\n    if len(condition_nets) == 1:\n        condition_blob = GetConditionBlobFromNet(condition_nets[0])\n        condition_net, _ = _CopyConditionBlobNet(condition_blob)\n        return condition_net\n\n    combined_net = core.Net(name)\n    for i in range(len(condition_nets)):\n        curr_cond = GetConditionBlobFromNet(condition_nets[i])\n        if i == 0:\n            last_cond = curr_cond\n        else:\n            last_cond = combined_net.__getattr__(relation)(\n                [last_cond, curr_cond])\n\n    combined_net.AddExternalOutput(last_cond)\n\n    return combined_net\n\n\ndef Do(name, *nets_or_steps):\n    \"\"\"\n    Execute the sequence of nets or steps once.\n\n    Examples:\n    - Do('myDo', net1, net2, ..., net_n)\n    - Do('myDo', list_of_nets)\n    - Do('myDo', step1, step2, ..., step_n)\n    - Do('myDo', list_of_steps)\n    \"\"\"\n    nets_or_steps = _MakeList(nets_or_steps)\n    if (len(nets_or_steps) == 1 and isinstance(\n            nets_or_steps[0], core.ExecutionStep)):\n        return nets_or_steps[0]\n    else:\n        return core.scoped_execution_step(\n            _get_next_step_name('Do', name), nets_or_steps)\n\n\ndef DoParallel(name, *nets_or_steps):\n    \"\"\"\n    Execute the nets or steps in parallel, waiting for all of them to finish\n\n    Examples:\n    - DoParallel('pDo', net1, net2, ..., net_n)\n    - DoParallel('pDo', list_of_nets)\n    - DoParallel('pDo', step1, step2, ..., step_n)\n    - DoParallel('pDo', list_of_steps)\n    \"\"\"\n    nets_or_steps = _MakeList(nets_or_steps)\n    if (len(nets_or_steps) == 1 and isinstance(\n            nets_or_steps[0], core.ExecutionStep)):\n        return nets_or_steps[0]\n    else:\n        return core.scoped_execution_step(\n            _get_next_step_name('DoParallel', name),\n            nets_or_steps,\n            concurrent_substeps=True)\n\n\ndef _RunOnceIf(name, condition_blob_or_net, nets_or_steps):\n    \"\"\"\n    Execute nets_or_steps once if condition_blob_or_net evaluates as true.\n\n    If condition_blob_or_net is Net, the condition is its last external_output\n    that must be a single bool. And this net will be executed before\n    nets_or_steps so as to get the condition.\n    \"\"\"\n    condition_not_net, stop_blob = NotNet(condition_blob_or_net)\n    if isinstance(condition_blob_or_net, core.Net):\n        nets_or_steps = _PrependNets(\n            nets_or_steps, condition_blob_or_net, condition_not_net)\n    else:\n        nets_or_steps = _PrependNets(nets_or_steps, condition_not_net)\n\n    def if_step(control_name):\n        return core.scoped_execution_step(\n            _get_next_step_name(control_name, name),\n            nets_or_steps,\n            should_stop_blob=stop_blob,\n            only_once=True,\n        )\n\n    if _IsNets(nets_or_steps):\n        bool_net = BoolNet((stop_blob, False))\n        return Do(name + '/_RunOnceIf',\n                  bool_net, if_step('_RunOnceIf-inner'))\n    else:\n        return if_step('_RunOnceIf')\n\n\ndef _RunOnceIfNot(name, condition_blob_or_net, nets_or_steps):\n    \"\"\"\n    Similar to _RunOnceIf() but Execute nets_or_steps once if\n    condition_blob_or_net evaluates as false.\n    \"\"\"\n    if isinstance(condition_blob_or_net, core.Net):\n        condition_blob = GetConditionBlobFromNet(condition_blob_or_net)\n        nets_or_steps = _PrependNets(nets_or_steps, condition_blob_or_net)\n    else:\n        copy_net, condition_blob = _CopyConditionBlobNet(condition_blob_or_net)\n        nets_or_steps = _PrependNets(nets_or_steps, copy_net)\n\n    return core.scoped_execution_step(\n        _get_next_step_name('_RunOnceIfNot', name),\n        nets_or_steps,\n        should_stop_blob=condition_blob,\n        only_once=True,\n    )\n\n\ndef For(name, nets_or_steps, iter_num):\n    \"\"\"\n    Execute nets_or_steps iter_num times.\n\n    Args:\n    nets_or_steps: a ExecutionStep or a Net or a list of ExecutionSteps or\n                   a list nets.\n    iter_num:    the number times to execute the nets_or_steps.\n\n    Returns:\n    A ExecutionStep instance.\n    \"\"\"\n    init_net = core.Net('init-net')\n    iter_cnt = init_net.CreateCounter([], init_count=iter_num)\n    iter_net = core.Net('For-iter')\n    iter_done = iter_net.CountDown([iter_cnt])\n\n    for_step = core.scoped_execution_step(\n        _get_next_step_name('For-inner', name),\n        _PrependNets(nets_or_steps, iter_net),\n        should_stop_blob=iter_done)\n    return Do(name + '/For',\n              Do(name + '/For-init-net', init_net),\n              for_step)\n\n\ndef While(name, condition_blob_or_net, nets_or_steps):\n    \"\"\"\n    Execute nets_or_steps when condition_blob_or_net returns true.\n\n    Args:\n    condition_blob_or_net: If it is an instance of Net, its last\n      external_output must be a single bool.\n    nets_or_steps: a ExecutionStep or a Net or a list of ExecutionSteps or\n                   a list nets.\n\n    Returns:\n    A ExecutionStep instance.\n    \"\"\"\n    condition_not_net, stop_blob = NotNet(condition_blob_or_net)\n    if isinstance(condition_blob_or_net, core.Net):\n        nets_or_steps = _PrependNets(\n            nets_or_steps, condition_blob_or_net, condition_not_net)\n    else:\n        nets_or_steps = _PrependNets(nets_or_steps, condition_not_net)\n\n    def while_step(control_name):\n        return core.scoped_execution_step(\n            _get_next_step_name(control_name, name),\n            nets_or_steps,\n            should_stop_blob=stop_blob,\n        )\n\n    if _IsNets(nets_or_steps):\n        # In this case, while_step has sub-nets:\n        # [condition_blob_or_net, condition_not_net, nets_or_steps]\n        # If stop_blob is pre-set to True (this may happen when While() is\n        # called twice), the loop will exit after executing\n        # condition_blob_or_net. So we use BootNet to set stop_blob to\n        # False.\n        bool_net = BoolNet((stop_blob, False))\n        return Do(name + '/While', bool_net, while_step('While-inner'))\n    else:\n        return while_step('While')\n\n\ndef Until(name, condition_blob_or_net, nets_or_steps):\n    \"\"\"\n    Similar to While() but execute nets_or_steps when\n    condition_blob_or_net returns false\n    \"\"\"\n    if isinstance(condition_blob_or_net, core.Net):\n        stop_blob = GetConditionBlobFromNet(condition_blob_or_net)\n        nets_or_steps = _PrependNets(nets_or_steps, condition_blob_or_net)\n    else:\n        stop_blob = core.BlobReference(str(condition_blob_or_net))\n\n    return core.scoped_execution_step(\n        _get_next_step_name('Until', name),\n        nets_or_steps,\n        should_stop_blob=stop_blob)\n\n\ndef DoWhile(name, condition_blob_or_net, nets_or_steps):\n    \"\"\"\n    Execute nets_or_steps when condition_blob_or_net returns true. It will\n    execute nets_or_steps before evaluating condition_blob_or_net.\n\n    Args:\n    condition_blob_or_net: if it is an instance of Net, tts last external_output\n      must be a single bool.\n    nets_or_steps: a ExecutionStep or a Net or a list of ExecutionSteps or\n                   a list nets.\n\n    Returns:\n    A ExecutionStep instance.\n    \"\"\"\n    condition_not_net, stop_blob = NotNet(condition_blob_or_net)\n    if isinstance(condition_blob_or_net, core.Net):\n        nets_or_steps = _AppendNets(\n            nets_or_steps, condition_blob_or_net, condition_not_net)\n    else:\n        nets_or_steps = _AppendNets(nets_or_steps, condition_not_net)\n\n    # If stop_blob is pre-set to True (this may happen when DoWhile() is\n    # called twice), the loop will exit after executing the first net/step\n    # in nets_or_steps. This is not what we want. So we use BootNet to\n    # set stop_blob to False.\n    bool_net = BoolNet((stop_blob, False))\n    return Do(name + '/DoWhile', bool_net, core.scoped_execution_step(\n        _get_next_step_name('DoWhile-inner', name),\n        nets_or_steps,\n        should_stop_blob=stop_blob,\n    ))\n\n\ndef DoUntil(name, condition_blob_or_net, nets_or_steps):\n    \"\"\"\n    Similar to DoWhile() but execute nets_or_steps when\n    condition_blob_or_net returns false. It will execute\n    nets_or_steps before evaluating condition_blob_or_net.\n\n    Special case: if condition_blob_or_net is a blob and is pre-set to\n    true, then only the first net/step of nets_or_steps will be executed and\n    loop is exited. So you need to be careful about the initial value the\n    condition blob when using DoUntil(), esp when DoUntil() is called twice.\n    \"\"\"\n    if not isinstance(condition_blob_or_net, core.Net):\n        stop_blob = core.BlobReference(condition_blob_or_net)\n        return core.scoped_execution_step(\n            _get_next_step_name('DoUntil', name),\n            nets_or_steps,\n            should_stop_blob=stop_blob)\n\n    nets_or_steps = _AppendNets(nets_or_steps, condition_blob_or_net)\n    stop_blob = GetConditionBlobFromNet(condition_blob_or_net)\n\n    # If stop_blob is pre-set to True (this may happen when DoWhile() is\n    # called twice), the loop will exit after executing the first net/step\n    # in nets_or_steps. This is not what we want. So we use BootNet to\n    # set stop_blob to False.\n    bool_net = BoolNet((stop_blob, False))\n    return Do(name + '/DoUntil', bool_net, core.scoped_execution_step(\n        _get_next_step_name('DoUntil-inner', name),\n        nets_or_steps,\n        should_stop_blob=stop_blob,\n    ))\n\n\ndef Switch(name, *conditions):\n    \"\"\"\n    Execute the steps for which the condition is true.\n    Each condition is a tuple (condition_blob_or_net, nets_or_steps).\n    Note:\n      1. Multi steps can be executed if their conditions are true.\n      2. The conditions_blob_or_net (if it is Net) of all steps will be\n         executed once.\n\n    Examples:\n    - Switch('name', (cond_1, net_1), (cond_2, net_2), ..., (cond_n, net_n))\n    - Switch('name', [(cond_1, net1), (cond_2, net_2), ..., (cond_n, net_n)])\n    - Switch('name', (cond_1, net_1))\n    \"\"\"\n    conditions = _MakeList(conditions)\n    return core.scoped_execution_step(\n        _get_next_step_name('Switch', name),\n        [_RunOnceIf(name + '/Switch', cond, step) for cond, step in conditions])\n\n\ndef SwitchNot(name, *conditions):\n    \"\"\"\n    Similar to Switch() but execute the steps for which the condition is False.\n    \"\"\"\n    conditions = _MakeList(conditions)\n    return core.scoped_execution_step(\n        _get_next_step_name('SwitchNot', name),\n        [_RunOnceIfNot(name + '/SwitchNot', cond, step)\n         for cond, step in conditions])\n\n\ndef If(name, condition_blob_or_net,\n       true_nets_or_steps, false_nets_or_steps=None):\n    \"\"\"\n    condition_blob_or_net is first evaluated or executed. If the condition is\n    true, true_nets_or_steps is then executed, otherwise, false_nets_or_steps\n    is executed.\n\n    If condition_blob_or_net is Net, the condition is its last external_output\n    that must be a single bool. And this Net will be executred before both\n    true/false_nets_or_steps so as to get the condition.\n    \"\"\"\n    if not false_nets_or_steps:\n        return _RunOnceIf(name + '/If',\n                          condition_blob_or_net, true_nets_or_steps)\n\n    if isinstance(condition_blob_or_net, core.Net):\n        condition_blob = GetConditionBlobFromNet(condition_blob_or_net)\n    else:\n        condition_blob = condition_blob_or_net\n\n    return Do(\n        name + '/If',\n        _RunOnceIf(name + '/If-true',\n                   condition_blob_or_net, true_nets_or_steps),\n        _RunOnceIfNot(name + '/If-false', condition_blob, false_nets_or_steps)\n    )\n\n\ndef IfNot(name, condition_blob_or_net,\n          true_nets_or_steps, false_nets_or_steps=None):\n    \"\"\"\n    If condition_blob_or_net returns false, executes true_nets_or_steps,\n    otherwise executes false_nets_or_steps\n    \"\"\"\n    if not false_nets_or_steps:\n        return _RunOnceIfNot(name + '/IfNot',\n                             condition_blob_or_net, true_nets_or_steps)\n\n    if isinstance(condition_blob_or_net, core.Net):\n        condition_blob = GetConditionBlobFromNet(condition_blob_or_net)\n    else:\n        condition_blob = condition_blob_or_net\n\n    return Do(\n        name + '/IfNot',\n        _RunOnceIfNot(name + '/IfNot-true',\n                      condition_blob_or_net, true_nets_or_steps),\n        _RunOnceIf(name + '/IfNot-false', condition_blob, false_nets_or_steps)\n    )\n"
  },
  {
    "path": "caffe2/python/control_ops_grad.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package control_ops_grad\n# Module caffe2.python.control_ops_grad\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.proto import caffe2_pb2\n\n\ndef gen_do_gradient(op, g_output):\n    \"\"\"\n    Generates gradient Do operator, given forward Do op and a list\n    of gradient blobs corresponding to forward op's outputs\n    Returns a gradient op and a list of blobs corresponding to input gradients\n    \"\"\"\n    from caffe2.python.core import BlobReference\n    subnet, outer_to_inner_map, inner_to_outer_map, workspace_blob_name = \\\n        _do_op_sanity_check_and_process(op)\n\n    assert len(g_output) == len(op.output), \\\n        \"Different number of gradient blobs and Do op outputs\"\n\n    grad_ops, deduped_g_output = dedupe_g_output(op, g_output)\n    g_output = deduped_g_output\n\n    # From the outer net point of view:\n    #  Do is an operator that has some number of inputs and outputs;\n    #  we have to generate a gradient operator that writes into\n    #  corresponding input gradient blobs and has access to inputs, outputs\n    #  and gradient output blobs\n    # From the inner net point of view:\n    #  Do is an operator with a subnet and blob bindings,\n    #  we need to forward Do's output blob gradients into inner workspace,\n    #  use them to run backward pass generation and forward Do's input blob\n    #  gradients back into outer workspace\n\n    op_output = [str(o) for o in op.output]\n    op_output = op_output[:-1]  # remove workspace pointer blob\n    op_input = [str(i) for i in op.input]\n    op_input = op_input[:-1]  # remove workspace pointer blob\n\n    ordered_inner_output_blob_names = [outer_to_inner_map[o] for o in op_output]\n\n    backward_pass_initial_grad_map = {}\n    initial_grad_map = {}\n    for inner_output_name, outer_grad_output_name in \\\n            zip(ordered_inner_output_blob_names, g_output):\n        # link inner_output_name to corresponding inner_grad_output_name for\n        # backward pass generation;\n        if outer_grad_output_name:\n            inner_grad_output_name = inner_output_name + \"/_DO_OPERATOR_INNER_GRAD_\"\n            backward_pass_initial_grad_map[BlobReference(inner_output_name)] = \\\n                BlobReference(inner_grad_output_name)\n            initial_grad_map[inner_grad_output_name] = str(outer_grad_output_name)\n    assert len(initial_grad_map) > 0, \"Empty initial gradient map for Do op\"\n\n    inner_grad_ops, inner_grad_names_map = _gen_subgradient_pass(\n        subnet, backward_pass_initial_grad_map)\n\n    if len(inner_grad_ops) == 0:\n        return [], []\n\n    grad_copy_ops = []\n    g_input = []\n    new_op_outputs = []\n    new_blob_bindings = {}\n    for outer_input_name in op_input:\n        inner_input_name = outer_to_inner_map[outer_input_name]\n        if inner_input_name in inner_grad_names_map:\n            inner_grad_input_name = inner_grad_names_map[inner_input_name]\n            outer_grad_input_name = outer_input_name + \"_grad\"\n\n            # It is possible that inner_grad_input_name will need to be\n            # linked to another outer blob. For example:\n            #\n            #    // y - param initialized in init_net\n            #    x = ...\n            #    z = ...\n            #    with ops.IfNet(...):\n            #        ops.Add([z, x], y) # inner Do block\n            #    loss = f(..., y, ...)\n            #\n            # In this case x, y and z are external for the inner Do block,\n            # the inputs of the Do block are z and x and the output is y.\n            # When computing the gradient of input x given the gradient\n            # of output y it's easy to see that they are equal.\n            # During the generation of gradient Do operator, we link\n            # external gradient y (y_grad) to the internal name\n            # (y/_DO_OPERATOR_INNER_GRAD_) and generate the backward pass\n            # for the internal Do net. As a result we get gradient operators\n            # for the gradient Do and gradient map that maps internal Do\n            # blobs to their computed gradients.\n            # In this example, gradient map may have blob x linked to\n            # gradient blob y/_DO_OPERATOR_INNER_GRAD_.\n            # We should export gradient for x outside of Do, so\n            # we add a blob mapping from inner gradient blob\n            # (y/_DO_OPERATOR_INNER_GRAD_) to a new outer name (x_grad).\n            #\n            # (Note: since we use transparent blob mapping between outer and\n            # inner (Do's) workspace, these operations do not involve copying\n            # but are merely using blobs in outer workspace in the Do's operator\n            # workspace under (possibly) different names)\n            #\n            # At the same time, we need to add a blob mapping from inner name\n            # y/_DO_OPERATOR_INNER_GRAD_ to the outer blob y_grad\n            # Hence in this case, we cannot use existing blob mapping scheme\n            # that requires a bijection between subset of inner blob names and\n            # a set of all (Do's input and output) outer blob names\n\n            # TODO(iliacher): Remove unnecessary blob copying\n\n            new_inner_grad_input_name = \\\n                inner_input_name + \"/_DO_OPERATOR_INNER_GRAD_COPY_\"\n            grad_copy_ops.append(_prepare_blob_copy_op(\n                inner_grad_input_name, new_inner_grad_input_name))\n\n            new_blob_bindings[new_inner_grad_input_name] = outer_grad_input_name\n            new_op_outputs.append(outer_grad_input_name)\n            g_input.append(outer_grad_input_name)\n        else:\n            g_input.append(None)\n\n    new_op_inputs = []\n    overwritten_names = set()\n    saved_local_blob_names = set()\n    for grad_op in inner_grad_ops:\n        grad_op_input = [str(i) for i in grad_op.input]\n        grad_op_output = [str(o) for o in grad_op.output]\n        for grad_op_input_name in grad_op_input:\n            if grad_op_input_name in overwritten_names:\n                continue\n            # check if this is an external blob\n            outer_name = inner_to_outer_map.get(grad_op_input_name, None)\n            if not outer_name:\n                # check if this is an external gradient blob\n                outer_name = initial_grad_map.get(grad_op_input_name, None)\n            if outer_name:\n                outer_name = str(outer_name)\n                if outer_name not in new_op_inputs:\n                    new_op_inputs.append(outer_name)\n\n                new_blob_bindings[grad_op_input_name] = outer_name\n            else:\n                # this is a local blob, we'll get it's value from\n                # a saved forward op workspace\n                saved_local_blob_names.add(grad_op_input_name)\n        overwritten_names.update(grad_op_output)\n\n    # add inner gradient copy ops\n    inner_grad_ops += grad_copy_ops\n\n    gradient_do_def = _prepare_gradient_do_op(\n        fwd_op=op,\n        fwd_net=subnet,\n        grad_ops=inner_grad_ops,\n        inputs=new_op_inputs,\n        outputs=new_op_outputs,\n        blob_bindings=new_blob_bindings,\n        saved_fwd_blobs=saved_local_blob_names,\n        workspace_blob_name=workspace_blob_name)\n    grad_ops.append(gradient_do_def)\n\n    _do_op_sanity_check_and_process(gradient_do_def)\n\n    return grad_ops, g_input\n\n\ndef dedupe_g_output(op, g_output):\n    # When generation a gradient op it's possible to receive the same gradient\n    # blob corresponding to different forward op output blobs, Do operator\n    # requires a bijection between inner and outer names, make sure we do\n    # deduplication\n    grad_ops = []\n    deduped_g_output = []\n    init_grad_map = {}\n    for output_name, grad_name in zip(op.output, g_output):\n        if not grad_name:\n            deduped_g_output.append(grad_name)\n            continue\n\n        if output_name in init_grad_map:\n            deduped_g_output.append(init_grad_map[output_name])\n        else:\n            if grad_name not in init_grad_map.values():\n                init_grad_map[output_name] = grad_name\n                deduped_g_output.append(grad_name)\n            else:\n                deduped_grad_name = output_name + \"_\" + grad_name + \"_DEDUP\"\n                assert deduped_grad_name not in init_grad_map.values()\n                grad_copy_op = caffe2_pb2.OperatorDef()\n                grad_copy_op.type = \"Copy\"\n                grad_copy_op.input.extend([grad_name])\n                grad_copy_op.output.extend([deduped_grad_name])\n                grad_ops.append(grad_copy_op)\n                deduped_g_output.append(deduped_grad_name)\n                init_grad_map[output_name] = deduped_grad_name\n    return grad_ops, deduped_g_output\n\n\ndef gen_while_gradient(op, g_output):\n    \"\"\"\n    Generates gradient While operator\n    \"\"\"\n    from caffe2.python.core import BlobReference\n    assert op.type == \"While\", \"Expected While op\"\n    assert len(op.input) > 0, \"Expected at least one input in While op\"\n\n    assert len(op.output) == len(g_output), \\\n        \"Different number of gradient blobs and While op outputs\"\n\n    grad_ops, deduped_g_output = dedupe_g_output(op, g_output)\n    g_output = deduped_g_output\n\n    init_grad_map = {}\n    op_output = [str(o) for o in op.output]\n    for output_name, grad_output_name in zip(op_output, g_output):\n        if grad_output_name:\n            init_grad_map[BlobReference(output_name)] = \\\n                BlobReference(grad_output_name)\n    assert len(init_grad_map) > 0, \"Empty initial gradient map for While op\"\n\n    loop_net = _get_net_argument(op, \"loop_net\")\n    assert loop_net, \"Expected loop subnet in While op\"\n    assert len(loop_net.op) == 1 and loop_net.op[0].type == \"Do\", \\\n        \"Gradient While op requires single Do op as a loop body\"\n    do_op = loop_net.op[0]\n    do_args = _get_do_arguments(do_op)\n    assert \"reuse_workspace\" not in do_args or not do_args[\"reuse_workspace\"], \\\n        \"Gradient While op requires Do loop body op without reuse_workspace set\"\n\n    assert len(do_op.output) > 0, \"Expected Do op with at least one output\"\n    workspace_blob = do_op.output[-1]\n\n    loop_grad_net, loop_grad_map, loop_input_names, loop_output_names = \\\n        _gen_subnet_gradient(loop_net, init_grad_map)\n    assert loop_grad_net, \"Failed to get gradient net for loop body in While op\"\n\n    grad_ops += _prepare_gradient_while_ops(\n        fwd_op=op,\n        input_names=loop_input_names,\n        output_names=loop_output_names,\n        loop_grad_net=loop_grad_net,\n        workspace_blob=workspace_blob,\n        init_grad_map=init_grad_map,\n        loop_grad_map=loop_grad_map)\n\n    op_input = [str(i) for i in op.input]\n    g_input = [loop_grad_map.get(i, None) for i in op_input]\n    return grad_ops, g_input\n\n\n# Constructs gradient While op, arguments:\n#  fwd_op - forward While op\n#  input_names - input blob names for a gradient op\n#  output_names - output blob names for a gradient op\n#  loop_grad_net - gradient loop body net\n#  workspace_blob - blob that holds forward workspaces stack\n#  init_grad_map - initial gradient to forward blob map\n#  loop_grad_map - gradient blob map for loop's body\ndef _prepare_gradient_while_ops(\n        fwd_op, input_names, output_names, loop_grad_net, workspace_blob,\n        init_grad_map, loop_grad_map):\n    gradient_while_def = caffe2_pb2.OperatorDef()\n    gradient_while_def.CopyFrom(fwd_op)\n    if gradient_while_def.name:\n        gradient_while_def.name += \"_grad\"\n\n    loop_net_arg = caffe2_pb2.Argument()\n    loop_net_arg.name = \"loop_net\"\n    loop_net_arg.n.CopyFrom(loop_grad_net)\n\n    cond_net_arg = caffe2_pb2.Argument()\n    cond_net_arg.name = \"cond_net\"\n    from caffe2.python.core import Net, BlobReference\n    # Construct condition net - check that there're still forward workspaces\n    # left using HasScope op\n    cond_net = Net('gradient_loop_cond_net')\n    cond_init_net = Net('gradient_loop_cond_net_init')\n    cond_blob = cond_net.NextScopedBlob(cond_net.Name() + '/cond')\n    cond_init_net.HasScope(workspace_blob, cond_blob)\n    cond_net.HasScope(workspace_blob, cond_blob)\n    for blob, init_grad_blob in init_grad_map.items():\n        blob_name = str(blob)\n        init_grad_blob_name = str(init_grad_blob)\n        if blob_name in loop_grad_map and \\\n                loop_grad_map[blob_name] != init_grad_blob_name:\n            cond_net.Copy(\n                BlobReference(loop_grad_map[blob_name]), init_grad_blob)\n            cond_init_net.Copy(\n                init_grad_blob, BlobReference(loop_grad_map[blob_name]))\n    cond_net_arg.n.CopyFrom(cond_net.Proto())\n\n    del gradient_while_def.arg[:]\n    gradient_while_def.arg.extend([loop_net_arg, cond_net_arg])\n\n    del gradient_while_def.control_input[:]\n    del gradient_while_def.input[:]\n    gradient_while_def.input.extend(\n        [str(cond_blob).encode('utf-8')] + list(input_names))\n    del gradient_while_def.output[:]\n    gradient_while_def.output.extend(output_names)\n    gradient_while_def.is_gradient_op = True\n    return [o for o in cond_init_net.Proto().op] + [gradient_while_def]\n\n\ndef _get_do_arguments(do_op):\n    assert do_op.type == \"Do\", \"Expected Do op\"\n    args = {}\n    for arg in do_op.arg:\n        if not arg.name:\n            continue\n        if arg.name == \"net\":\n            assert arg.n, \"Expected non empty net argument\"\n            args[\"net\"] = arg.n\n        elif arg.name == \"reuse_workspace\":\n            assert arg.i, \"Expected non empty reuse_workspace argument\"\n            args[\"reuse_workspace\"] = bool(arg.i)\n        elif arg.name == \"inner_blobs\":\n            assert arg.strings, \"Expected non empty inner_blobs argument\"\n            args[\"inner_blobs\"] = arg.strings\n        elif arg.name == \"outer_blobs_idx\":\n            assert arg.ints, \"Expected non empty outer_blobs_idx argument\"\n            args[\"outer_blobs_idx\"] = arg.ints\n    return args\n\n\ndef gen_if_gradient(op, g_output):\n    \"\"\"\n    Generates gradient If operator, given forward If op and a list\n    of gradient blobs corresponding to forward op's outputs\n    Returns a gradient op and a list of blobs corresponding to input gradients\n    \"\"\"\n    from caffe2.python.core import BlobReference\n    assert op.type == \"If\", \"Expected If op\"\n    # first input is the condition blob\n    assert len(op.input) > 0, \"Expected at least one input in If op\"\n\n    assert len(op.output) == len(g_output), \\\n        \"Different number of gradient blobs and If op outputs\"\n\n    grad_ops, deduped_g_output = dedupe_g_output(op, g_output)\n    g_output = deduped_g_output\n\n    init_grad_map = {}  # map from if's output blob to output gradient blob\n    op_input = [str(i) for i in op.input]\n    op_output = [str(o) for o in op.output]\n    for output_name, grad_output_name in zip(op_output, g_output):\n        if grad_output_name:\n            init_grad_map[BlobReference(output_name)] = \\\n                BlobReference(grad_output_name)\n    # shouldn't call without at least one output gradient available\n    assert len(init_grad_map) > 0, \"Empty initial gradient map for If op\"\n\n    grad_map = {}  # map from blob to gradient blob\n    then_net = _get_net_argument(op, \"then_net\")\n    assert then_net, \"Expected then subnet in If op\"\n    then_grad_net, then_grad_map, then_input_names, then_output_names = \\\n        _gen_subnet_gradient(then_net, init_grad_map)\n    assert then_grad_net, \"Failed to get gradient net for then in If op\"\n    grad_map.update(then_grad_map)\n\n    else_input_names = set()\n    else_output_names = set()\n    else_grad_map = {}\n    else_grad_net = None\n    else_net = _get_net_argument(op, \"else_net\")\n    if else_net:\n        else_grad_net, else_grad_map, else_input_names, else_output_names = \\\n            _gen_subnet_gradient(else_net, init_grad_map)\n        assert else_grad_net, \"Failed to get gradient net for else in If op\"\n        # consider case: else doesn't update blob's gradient and keeps original\n        # from init_grad_map, but then updates the gradient\n        for else_blob, else_grad_blob in else_grad_map.items():\n            if else_blob in then_grad_map:\n                then_grad_blob = then_grad_map[else_blob]\n                # if both then and else branches have grad blob name for the same\n                # blob and grad names are different, then one of the branches\n                # doesn't use blob and has original grad blob name in it's grad map,\n                # and another branch uses blob and has <blob_name>_grad name\n                # in it's grad map (might be different from original grad blob)\n                if then_grad_blob != else_grad_blob:\n                    init_grad_name = init_grad_map[else_blob] \\\n                        if else_blob in init_grad_map else None\n\n                    if then_grad_blob == init_grad_name:\n                        grad_map[else_blob] = else_grad_blob\n                    elif else_grad_blob == init_grad_name:\n                        grad_map[else_blob] = then_grad_blob\n                    else:\n                        raise \"Unexpected grad blob name \" + else_blob + \", \" + \\\n                            else_grad_blob + \", \" + then_grad_blob\n            else:\n                grad_map[else_blob] = else_grad_blob\n\n    # make sure gradients of blobs that were not computed\n    # by the selected if's branch are initialized with zeros\n    then_other_output_names = \\\n        then_output_names - (then_output_names & else_output_names)\n    then_other_grad_output_names = set(\n        [o for o in then_other_output_names if o in then_grad_map.values()])\n    zero_then = _gen_grad_zero_init_ops(\n        init_grad_map, then_grad_map, then_other_grad_output_names)\n    if else_grad_net:\n        else_grad_net.op.extend(zero_then)\n    elif len(zero_then) > 0:\n        else_grad_net = caffe2_pb2.NetDef()\n        else_grad_net.CopyFrom(then_grad_net)\n        if else_grad_net.name:\n            else_grad_net.name += \"_auto_else_zero_blobs_\"\n        del else_grad_net.op[:]\n        else_grad_net.op.extend(zero_then)\n        del else_grad_net.external_input[:]\n        del else_grad_net.external_output[:]\n\n    else_other_output_names = \\\n        else_output_names - (then_output_names & else_output_names)\n    else_other_grad_output_names = set(\n        [o for o in else_other_output_names if o in else_grad_map.values()])\n    zero_else = _gen_grad_zero_init_ops(\n        init_grad_map, else_grad_map, else_other_grad_output_names)\n    then_grad_net.op.extend(zero_else)\n\n    output_names = list(then_output_names | else_output_names)\n    input_names = then_input_names | else_input_names\n    # make sure condition blob is the first in the list\n    input_names = [op_input[0]] + list(input_names - set(op_input[0]))\n    gradient_if_def = _prepare_gradient_if_op(\n        fwd_op=op,\n        input_names=input_names,\n        output_names=output_names,\n        then_grad_net=then_grad_net,\n        else_grad_net=else_grad_net)\n    g_input = [grad_map.get(i, None) for i in op_input]\n    return grad_ops + [gradient_if_def], g_input\n\n\ndef _gen_subnet_gradient(subnet, init_grad):\n    grad_ops, grad_names_map = _gen_subgradient_pass(\n        subnet, init_grad)\n\n    output_names = set()\n    input_names = set()\n    for grad_op in grad_ops:\n        for grad_op_input in grad_op.input:\n            if str(grad_op_input) not in output_names:\n                input_names.add(str(grad_op_input))\n        for grad_op_output in grad_op.output:\n            output_names.add(str(grad_op_output))\n\n    gradient_net_def = caffe2_pb2.NetDef()\n    gradient_net_def.CopyFrom(subnet)\n    if gradient_net_def.name:\n        gradient_net_def.name += \"_grad\"\n    del gradient_net_def.op[:]\n    gradient_net_def.op.extend(grad_ops)\n    del gradient_net_def.external_input[:]\n    del gradient_net_def.external_output[:]\n\n    return gradient_net_def, grad_names_map, input_names, output_names\n\n\ndef _get_net_argument(op, net_name):\n    for arg in op.arg:\n        if arg.name and arg.name == net_name:\n            assert arg.n, \"Expected non empty net argument \" + net_name\n            return arg.n\n    return None\n\n\ndef _gen_subgradient_pass(subnet, init_grad):\n    from caffe2.python.core import IR\n    subnet_ir = IR(subnet.op)\n    grad_ops, grad_blob_map = \\\n        subnet_ir.GetBackwardPass(init_grad)\n    grad_names_map = {}\n    for b, g in grad_blob_map.items():\n        grad_names_map[str(b)] = str(g)\n    return grad_ops, grad_names_map\n\n\ndef _do_op_sanity_check_and_process(op):\n    assert op.type == \"Do\", \"Expected Do op\"\n\n    subnet = _get_net_argument(op, \"net\")\n    assert subnet, \"No net argument found in Do op\"\n\n    inner_blobs = None\n    outer_blobs_idx = None\n    for arg in op.arg:\n        if arg.name and arg.name == \"inner_blobs\":\n            assert not inner_blobs, \"inner_blobs redefinition\"\n            assert arg.strings and len(arg.strings) > 0, \\\n                \"Empty inner_blobs argument in Do op\"\n            inner_blobs = [s.decode('utf-8') for s in arg.strings]\n        if arg.name and arg.name == \"outer_blobs_idx\":\n            assert not outer_blobs_idx, \"outer_blobs_idx redefinition\"\n            assert arg.ints and len(arg.ints) > 0, \\\n                \"Empty outer_blobs_idx argument in Do op\"\n            outer_blobs_idx = arg.ints\n        if inner_blobs and outer_blobs_idx:\n            break\n\n    assert inner_blobs, \"No inner_blobs argument found in Do op\"\n    assert outer_blobs_idx, \"No outer_blobs_idx argument found in Do op\"\n\n    assert len(inner_blobs) == len(outer_blobs_idx), \\\n        \"Arguments inner_blobs and outer_blobs_idx of different length in Do op\"\n\n    all_inner_blobs = set(inner_blobs)\n    assert len(all_inner_blobs) == len(inner_blobs), \\\n        \"Found duplicates in inner_blobs in Do op\"\n\n    op_input = [str(i) for i in op.input]\n    assert len(op_input) > 0, \"Expected at least one input blob\"\n    # remove last input blob that holds pointer to workspace\n    input_workspace_blob_name = op_input[-1]\n    op_input = op_input[:-1]\n\n    op_output = [str(o) for o in op.output]\n    assert len(op_output) > 0, \"Expected at least one output blob\"\n    # remove last output blob that holds pointer to workspace\n    workspace_blob_name = op_output[-1]\n    assert input_workspace_blob_name == workspace_blob_name, \\\n        \"Expected same input/output workspace blob\"\n    op_output = op_output[:-1]\n\n    all_op_input_blob_names = set(op_input)\n    assert len(all_op_input_blob_names) == len(op_input), \\\n        \"Found duplicates in Do op inputs\"\n    all_op_output_blob_names = set(op_output)\n    assert len(all_op_output_blob_names) == len(op_output), \\\n        \"Found duplicates in Do op outputs\"\n\n    ordered_outer_blob_names = op_input + op_output\n    all_outer_blob_names = set(ordered_outer_blob_names)\n    used_outer_blob_names = set()\n    outer_to_inner_map = {}\n    inner_to_outer_map = {}\n    for inner_name, outer_blob_idx in zip(inner_blobs, outer_blobs_idx):\n        assert outer_blob_idx >= 0 and \\\n            outer_blob_idx < len(ordered_outer_blob_names), \\\n            \"Outer blob index is out of bounds in Do op\"\n        outer_name = ordered_outer_blob_names[outer_blob_idx]\n        assert outer_name not in used_outer_blob_names, \\\n            \"Reusage of outer blob name \" + outer_name + \" in Do op\"\n        used_outer_blob_names.add(outer_name)\n        outer_to_inner_map[outer_name] = inner_name\n        inner_to_outer_map[inner_name] = outer_name\n\n    assert len(used_outer_blob_names) == len(all_outer_blob_names), \\\n        \"Not all outer blob names are used in blob bindings in Do op\"\n\n    return subnet, outer_to_inner_map, inner_to_outer_map, workspace_blob_name\n\n\ndef _prepare_blob_copy_op(from_name, to_name):\n    copy_op_def = caffe2_pb2.OperatorDef()\n    copy_op_def.type = \"Copy\"\n    copy_op_def.input.extend([from_name])\n    copy_op_def.output.extend([to_name])\n    return copy_op_def\n\n\ndef _prepare_gradient_do_op(\n        fwd_op, fwd_net, grad_ops, inputs, outputs, blob_bindings, saved_fwd_blobs,\n        workspace_blob_name):\n    gradient_net_def = caffe2_pb2.NetDef()\n    gradient_net_def.CopyFrom(fwd_net)\n    if gradient_net_def.name:\n        gradient_net_def.name += \"_grad\"\n    del gradient_net_def.op[:]\n    gradient_net_def.op.extend(grad_ops)\n    del gradient_net_def.external_input[:]\n    del gradient_net_def.external_output[:]\n\n    gradient_do_def = caffe2_pb2.OperatorDef()\n    gradient_do_def.CopyFrom(fwd_op)\n    if gradient_do_def.name and len(gradient_do_def.name) > 0:\n        gradient_do_def.name += \"_grad\"\n\n    del gradient_do_def.input[:]\n    gradient_do_def.input.extend(inputs)\n    # workspace pointer blob\n    gradient_do_def.input.append(workspace_blob_name)\n    del gradient_do_def.output[:]\n    gradient_do_def.output.extend(outputs)\n    # workspace pointer blob\n    gradient_do_def.output.append(workspace_blob_name)\n\n    net_arg = caffe2_pb2.Argument()\n    net_arg.name = \"net\"\n    net_arg.n.CopyFrom(gradient_net_def)\n\n    ordered_new_outer_names = inputs + outputs\n    inner_blobs = blob_bindings.keys()\n    new_outer_blobs_idx = [ordered_new_outer_names.index(blob_bindings[b])\n                            for b in inner_blobs]\n\n    inner_blobs_arg = caffe2_pb2.Argument()\n    inner_blobs_arg.name = \"inner_blobs\"\n    inner_blobs_arg.strings.extend([b.encode('utf-8') for b in inner_blobs])\n\n    outer_blobs_idx_arg = caffe2_pb2.Argument()\n    outer_blobs_idx_arg.name = \"outer_blobs_idx\"\n    outer_blobs_idx_arg.ints.extend(new_outer_blobs_idx)\n\n    saved_blobs_arg = caffe2_pb2.Argument()\n    saved_blobs_arg.name = \"saved_fwd_blobs\"\n    saved_blobs_arg.strings.extend(\n        [b.encode('utf-8') for b in saved_fwd_blobs])\n\n    del gradient_do_def.arg[:]\n    gradient_do_def.arg.extend([\n        net_arg, inner_blobs_arg, outer_blobs_idx_arg, saved_blobs_arg])\n    del gradient_do_def.control_input[:]\n\n    gradient_do_def.is_gradient_op = True\n\n    return gradient_do_def\n\n\ndef _gen_grad_zero_init_ops(init_grad_map, grad_map, grad_output_names):\n    grad_init_ops = []\n    for grad_output in grad_output_names:\n        # get the corresponding output name blob and use it in ConstantFill\n        # so that grad_output has the same shape\n        output_name = None\n        for o, g in grad_map.items():\n            if g == grad_output:\n                output_name = o\n                break\n        assert output_name, \"Unknown gradient output \" + grad_output\n\n        grad_init_op = None\n        # make sure that we do not overwrite existing gradients with zeros\n        if output_name in init_grad_map:\n            init_grad_name = init_grad_map[output_name]\n            # in case we use a different gradient blob name, copy gradient\n            if init_grad_name != grad_output:\n                grad_init_op = caffe2_pb2.OperatorDef()\n                grad_init_op.type = \"Copy\"\n                grad_init_op.input.extend([str(init_grad_name)])\n                grad_init_op.output.extend([str(grad_output)])\n        else:\n            grad_init_op = caffe2_pb2.OperatorDef()\n            grad_init_op.type = \"ConstantFill\"\n            grad_init_op.input.extend([output_name])\n            grad_init_op.output.extend([grad_output])\n            value_arg = caffe2_pb2.Argument()\n            value_arg.name = \"value\"\n            value_arg.f = 0.0\n            grad_init_op.arg.extend([value_arg])\n\n        if grad_init_op:\n            grad_init_ops.append(grad_init_op)\n    return grad_init_ops\n\n\ndef _prepare_gradient_if_op(\n        fwd_op, input_names, output_names, then_grad_net, else_grad_net):\n    gradient_if_def = caffe2_pb2.OperatorDef()\n    gradient_if_def.CopyFrom(fwd_op)\n    del gradient_if_def.input[:]\n    gradient_if_def.input.extend(input_names)\n    del gradient_if_def.output[:]\n    gradient_if_def.output.extend(output_names)\n\n    then_net_arg = caffe2_pb2.Argument()\n    then_net_arg.name = \"then_net\"\n    then_net_arg.n.CopyFrom(then_grad_net)\n    gradient_args = [then_net_arg]\n    if else_grad_net:\n        else_net_arg = caffe2_pb2.Argument()\n        else_net_arg.name = \"else_net\"\n        else_net_arg.n.CopyFrom(else_grad_net)\n        gradient_args.append(else_net_arg)\n\n    del gradient_if_def.arg[:]\n    gradient_if_def.arg.extend(gradient_args)\n    if gradient_if_def.name:\n        gradient_if_def.name += \"_grad\"\n    del gradient_if_def.control_input[:]\n    gradient_if_def.is_gradient_op = True\n    return gradient_if_def\n"
  },
  {
    "path": "caffe2/python/control_ops_util.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package control_ops_util\n# Module caffe2.python.control_ops_util\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\n\n\ndef get_external_blob_names(net, lexical_scope):\n    \"\"\"\n    Returns a set of blobs a given net depends on and a set of\n    output blobs that are written by the net\n    Inputs:\n        net - net to return input/output blobs for;\n        lexical_scope - all external blob names visible to the net\n    \"\"\"\n    # Use the blobs that are actually read/written to as external inputs/outputs\n    net_proto = net.Proto()\n    net_ssa, _ = core.get_ssa(net_proto)\n    input_names = core.get_undefined_blobs(net_ssa)\n    for input_name in input_names:\n        assert str(input_name) in lexical_scope, \\\n            \"Input blob \" + input_name + \" is undefined\"\n\n    output_names = set()\n    for op in net_proto.op:\n        for output in op.output:\n            if output in lexical_scope:\n                output_names.add(output)\n\n    return input_names, output_names\n\n\ndef add_if_op(if_net, cond_blob, lexical_scope, then_net, else_net=None):\n    \"\"\"\n    A helper function to add an If op to the net.\n    Automatically determines whether blobs in the then/else subnets are external\n    (from the outer workspace) or local (visible only inside subnet's workspace)\n    based on lexical scope - set of all outer blob names visible to the 'If'\n    operator. All the blobs in then/else subnets with names matching a name in lexical\n    scope and all the blobs that are first used as the operators' inputs are\n    considered outer blobs - these blobs must exist in the outer workspace,\n    then/else subnets can read their values and new values written into these blobs\n    will be visible outside of the 'If' operator. All other blobs are local - exist\n    only within inner workspaces for then/else.\n    Inputs:\n        if_net - net to add an If op to;\n        cond_blob - scalar bool blob reference, used as If condition;\n        lexical_scope - a set of outer blob names visible to then/else branches;\n        then_net/else_net - nets (core.Net) for then/else branches\n    \"\"\"\n    then_input_blob_names, then_output_blob_names = get_external_blob_names(\n        then_net, lexical_scope)\n\n    else_input_blob_names = set()\n    else_output_blob_names = set()\n    if else_net:\n        else_input_blob_names, else_output_blob_names = get_external_blob_names(\n            else_net, lexical_scope)\n\n    input_blob_names = then_input_blob_names | else_input_blob_names\n    output_blob_names = then_output_blob_names | else_output_blob_names\n\n    if_inputs = [cond_blob]\n    if_inputs += [core.BlobReference(name=b, net=None) for b in input_blob_names]\n    if_outputs = [core.BlobReference(name=b, net=None) for b in output_blob_names]\n\n    do_then_net = core.Net('do_then_net')\n\n    then_input_blobs = \\\n        [core.BlobReference(name=b, net=None) for b in then_input_blob_names]\n    then_output_blobs = \\\n        [core.BlobReference(name=b, net=None) for b in then_output_blob_names]\n    then_input_output_names_ordered = [\n        str(b) for b in (then_input_blobs + then_output_blobs)]\n\n    then_outer_blob_names = list(then_input_blob_names | then_output_blob_names)\n    then_outer_blob_names_idx = [\n        then_input_output_names_ordered.index(b) for b in then_outer_blob_names]\n\n    # make sure to use net's name to have unique blob name across multiple subnets\n    do_then_workspace_blob = if_net.NextScopedBlob(if_net.Name() + '/workspace_if_then')\n    then_input_blobs.append(do_then_workspace_blob)\n    then_output_blobs.append(do_then_workspace_blob)\n    # make sure that added workspace pointer blobs are in if inputs/outputs\n    if_inputs.append(do_then_workspace_blob)\n    if_outputs.append(do_then_workspace_blob)\n\n    do_then_net.Do(\n        then_input_blobs,\n        then_output_blobs,\n        net=then_net.Proto(),\n        inner_blobs=then_outer_blob_names,\n        outer_blobs_idx=then_outer_blob_names_idx)\n    do_then_net.AddExternalOutput(*then_output_blobs)\n\n    if_args = {}\n    if_args['then_net'] = do_then_net.Proto()\n\n    do_else_workspace_blob = None\n    if else_net:\n        do_else_net = core.Net('do_else_net')\n\n        else_input_blobs = \\\n            [core.BlobReference(name=b, net=None) for b in else_input_blob_names]\n        else_output_blobs = \\\n            [core.BlobReference(name=b, net=None) for b in else_output_blob_names]\n        else_input_output_names_ordered = [\n            str(b) for b in (else_input_blobs + else_output_blobs)]\n\n        else_outer_blob_names = list(else_input_blob_names | else_output_blob_names)\n        else_outer_blob_names_idx = [\n            else_input_output_names_ordered.index(b) for b in else_outer_blob_names]\n\n        do_else_workspace_blob = \\\n            if_net.NextScopedBlob(if_net.Name() + '/workspace_if_else')\n        else_input_blobs.append(do_else_workspace_blob)\n        else_output_blobs.append(do_else_workspace_blob)\n        # make sure that added workspace pointer blobs are in if inputs/outputs\n        if_inputs.append(do_else_workspace_blob)\n        if_outputs.append(do_else_workspace_blob)\n\n        do_else_net.Do(\n            else_input_blobs,\n            else_output_blobs,\n            net=else_net.Proto(),\n            inner_blobs=else_outer_blob_names,\n            outer_blobs_idx=else_outer_blob_names_idx)\n        do_else_net.AddExternalOutput(*else_output_blobs)\n        if_args['else_net'] = do_else_net.Proto()\n\n    if_net.CreateScope([], [do_then_workspace_blob])\n    if do_else_workspace_blob:\n        if_net.CreateScope([], [do_else_workspace_blob])\n    if_net.If(if_inputs, if_outputs, **if_args)\n    if_net.AddExternalOutput(*if_outputs)\n\n\ndef add_while_op(\n        while_net, cond_blob, lexical_scope, loop_body_net, condition_body_net=None):\n    \"\"\"\n    A helper function to add a While op to the net. Same rules for determining\n    outer and inner blobs as for the 'If' operator apply for the 'While' operator\n    loop and condition subnets. If specified, condition net is executed in a separate\n    workspace before the first and after each iteration, the last operator must have\n    a single scalar boolean output that is written into the condition blob.\n    Inputs:\n        while_net - net to add a While op to;\n        cond_blob - scalar bool blob reference, used as a stop condition;\n        lexical_scope - a set of outer blob names visible to the loop's body;\n        loop_body_net - net to execute on each iteration;\n        condition_body_net - net to compute condition value\n    \"\"\"\n    input_blob_names, output_blob_names = get_external_blob_names(\n        loop_body_net, lexical_scope)\n\n    # Since it's possible that loop is not going to run even once\n    # we have to add loop's external outputs into inputs\n    input_blob_names |= output_blob_names\n\n    loop_inputs = [core.BlobReference(name=b, net=None) for b in input_blob_names]\n    loop_outputs = [core.BlobReference(name=b, net=None) for b in output_blob_names]\n\n    while_inputs = [cond_blob] + loop_inputs\n    while_outputs = [] + loop_outputs\n\n    do_loop_body_net = core.Net('do_loop_body_net')\n\n    loop_input_output_names_ordered = [\n        str(b) for b in (loop_inputs + loop_outputs)]\n    loop_body_outer_blob_names = list(input_blob_names | output_blob_names)\n    loop_body_outer_blob_names_idx = [\n        loop_input_output_names_ordered.index(b) for b in loop_body_outer_blob_names]\n\n    do_loop_body_workspace_blob = \\\n        while_net.NextScopedBlob(while_net.Name() + '/workspace_loop_body')\n\n    loop_inputs.append(do_loop_body_workspace_blob)\n    loop_outputs.append(do_loop_body_workspace_blob)\n    # make sure that added workspace pointer blobs are in While inputs/outputs\n    while_inputs.append(do_loop_body_workspace_blob)\n    while_outputs.append(do_loop_body_workspace_blob)\n\n    do_loop_body_net.Do(\n        loop_inputs,\n        loop_outputs,\n        net=loop_body_net.Proto(),\n        inner_blobs=loop_body_outer_blob_names,\n        outer_blobs_idx=loop_body_outer_blob_names_idx,\n        copy_external_blobs=True)\n    do_loop_body_net.AddExternalOutput(*loop_outputs)\n\n    while_args = {}\n    while_args['loop_net'] = do_loop_body_net.Proto()\n\n    cond_workspace_blob = None\n    if condition_body_net:\n        cond_input_blob_names, cond_output_blob_names = get_external_blob_names(\n            condition_body_net, lexical_scope)\n\n        # make sure condition blob is written by condition net and is\n        # visible outside of it\n        found_condition_output = False\n        for op in condition_body_net.Proto().op:\n            if str(cond_blob) in op.output:\n                found_condition_output = True\n                break\n        assert found_condition_output, \\\n            \"Condition net does not write into condition blob\"\n        if str(cond_blob) not in cond_output_blob_names:\n            cond_output_blob_names.add(str(cond_blob))\n\n        cond_inputs = [core.BlobReference(name=b, net=None)\n                        for b in cond_input_blob_names]\n        assert str(cond_blob) in cond_output_blob_names, \\\n            'Condition blob expected in condition net output'\n        cond_outputs = [core.BlobReference(name=b, net=None)\n                        for b in cond_output_blob_names]\n\n        condition_net = core.Net('do_loop_condition_net')\n\n        cond_input_output_names_ordered = [\n            str(b) for b in (cond_inputs + cond_outputs)]\n        cond_body_outer_blob_names = \\\n            list(cond_input_blob_names | cond_output_blob_names)\n        cond_body_outer_blob_names_idx = [\n            cond_input_output_names_ordered.index(b)\n            for b in cond_body_outer_blob_names]\n\n        cond_workspace_blob = \\\n            while_net.NextScopedBlob(while_net.Name() + '/workspace_loop_cond')\n        cond_inputs.append(cond_workspace_blob)\n        cond_outputs.append(cond_workspace_blob)\n\n        condition_net.Do(\n            cond_inputs,\n            cond_outputs,\n            net=condition_body_net.Proto(),\n            inner_blobs=cond_body_outer_blob_names,\n            outer_blobs_idx=cond_body_outer_blob_names_idx)\n        condition_net.AddExternalOutput(*cond_outputs)\n\n        while_args['cond_net'] = condition_net.Proto()\n\n        while_inputs += [b for b in cond_inputs\n                            if str(b) not in input_blob_names]\n        while_outputs += [b for b in cond_outputs\n                            if str(b) not in output_blob_names]\n\n        if str(cond_blob) not in lexical_scope:\n            while_net.ConstantFill(\n                [],\n                cond_blob,\n                dtype=core.DataType.BOOL,\n                value=False)\n\n    while_net.CreateScope([], [do_loop_body_workspace_blob])\n    if cond_workspace_blob:\n        while_net.CreateScope([], [cond_workspace_blob])\n    while_net.While(while_inputs, while_outputs, **while_args)\n    while_net.AddExternalOutput(*while_outputs)\n"
  },
  {
    "path": "caffe2/python/control_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import control, core, test_util, workspace\n\nimport logging\nlogger = logging.getLogger(__name__)\n\n\nclass TestControl(test_util.TestCase):\n    def setUp(self):\n        super(TestControl, self).setUp()\n        self.N_ = 10\n\n        self.init_net_ = core.Net(\"init-net\")\n        cnt = self.init_net_.CreateCounter([], init_count=0)\n        const_n = self.init_net_.ConstantFill(\n            [], shape=[], value=self.N_, dtype=core.DataType.INT64)\n        const_0 = self.init_net_.ConstantFill(\n            [], shape=[], value=0, dtype=core.DataType.INT64)\n\n        self.cnt_net_ = core.Net(\"cnt-net\")\n        self.cnt_net_.CountUp([cnt])\n        curr_cnt = self.cnt_net_.RetrieveCount([cnt])\n        self.init_net_.ConstantFill(\n            [], [curr_cnt], shape=[], value=0, dtype=core.DataType.INT64)\n        self.cnt_net_.AddExternalOutput(curr_cnt)\n\n        self.cnt_2_net_ = core.Net(\"cnt-2-net\")\n        self.cnt_2_net_.CountUp([cnt])\n        self.cnt_2_net_.CountUp([cnt])\n        curr_cnt_2 = self.cnt_2_net_.RetrieveCount([cnt])\n        self.init_net_.ConstantFill(\n            [], [curr_cnt_2], shape=[], value=0, dtype=core.DataType.INT64)\n        self.cnt_2_net_.AddExternalOutput(curr_cnt_2)\n\n        self.cond_net_ = core.Net(\"cond-net\")\n        cond_blob = self.cond_net_.LT([curr_cnt, const_n])\n        self.cond_net_.AddExternalOutput(cond_blob)\n\n        self.not_cond_net_ = core.Net(\"not-cond-net\")\n        cond_blob = self.not_cond_net_.GE([curr_cnt, const_n])\n        self.not_cond_net_.AddExternalOutput(cond_blob)\n\n        self.true_cond_net_ = core.Net(\"true-cond-net\")\n        true_blob = self.true_cond_net_.LT([const_0, const_n])\n        self.true_cond_net_.AddExternalOutput(true_blob)\n\n        self.false_cond_net_ = core.Net(\"false-cond-net\")\n        false_blob = self.false_cond_net_.GT([const_0, const_n])\n        self.false_cond_net_.AddExternalOutput(false_blob)\n\n        self.idle_net_ = core.Net(\"idle-net\")\n        self.idle_net_.ConstantFill(\n            [], shape=[], value=0, dtype=core.DataType.INT64)\n\n    def CheckNetOutput(self, nets_and_expects):\n        \"\"\"\n        Check the net output is expected\n        nets_and_expects is a list of tuples (net, expect)\n        \"\"\"\n        for net, expect in nets_and_expects:\n            output = workspace.FetchBlob(\n                net.Proto().external_output[-1])\n            self.assertEqual(output, expect)\n\n    def CheckNetAllOutput(self, net, expects):\n        \"\"\"\n        Check the net output is expected\n        expects is a list of bools.\n        \"\"\"\n        self.assertEqual(len(net.Proto().external_output), len(expects))\n        for i in range(len(expects)):\n            output = workspace.FetchBlob(\n                net.Proto().external_output[i])\n            self.assertEqual(output, expects[i])\n\n    def BuildAndRunPlan(self, step):\n        plan = core.Plan(\"test\")\n        plan.AddStep(control.Do('init', self.init_net_))\n        plan.AddStep(step)\n        self.assertEqual(workspace.RunPlan(plan), True)\n\n    def ForLoopTest(self, nets_or_steps):\n        step = control.For('myFor', nets_or_steps, self.N_)\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(self.cnt_net_, self.N_)])\n\n    def testForLoopWithNets(self):\n        self.ForLoopTest(self.cnt_net_)\n        self.ForLoopTest([self.cnt_net_, self.idle_net_])\n\n    def testForLoopWithStep(self):\n        step = control.Do('count', self.cnt_net_)\n        self.ForLoopTest(step)\n        self.ForLoopTest([step, self.idle_net_])\n\n    def WhileLoopTest(self, nets_or_steps):\n        step = control.While('myWhile', self.cond_net_, nets_or_steps)\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(self.cnt_net_, self.N_)])\n\n    def testWhileLoopWithNet(self):\n        self.WhileLoopTest(self.cnt_net_)\n        self.WhileLoopTest([self.cnt_net_, self.idle_net_])\n\n    def testWhileLoopWithStep(self):\n        step = control.Do('count', self.cnt_net_)\n        self.WhileLoopTest(step)\n        self.WhileLoopTest([step, self.idle_net_])\n\n    def UntilLoopTest(self, nets_or_steps):\n        step = control.Until('myUntil', self.not_cond_net_, nets_or_steps)\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(self.cnt_net_, self.N_)])\n\n    def testUntilLoopWithNet(self):\n        self.UntilLoopTest(self.cnt_net_)\n        self.UntilLoopTest([self.cnt_net_, self.idle_net_])\n\n    def testUntilLoopWithStep(self):\n        step = control.Do('count', self.cnt_net_)\n        self.UntilLoopTest(step)\n        self.UntilLoopTest([step, self.idle_net_])\n\n    def DoWhileLoopTest(self, nets_or_steps):\n        step = control.DoWhile('myDoWhile', self.cond_net_, nets_or_steps)\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(self.cnt_net_, self.N_)])\n\n    def testDoWhileLoopWithNet(self):\n        self.DoWhileLoopTest(self.cnt_net_)\n        self.DoWhileLoopTest([self.idle_net_, self.cnt_net_])\n\n    def testDoWhileLoopWithStep(self):\n        step = control.Do('count', self.cnt_net_)\n        self.DoWhileLoopTest(step)\n        self.DoWhileLoopTest([self.idle_net_, step])\n\n    def DoUntilLoopTest(self, nets_or_steps):\n        step = control.DoUntil('myDoUntil', self.not_cond_net_, nets_or_steps)\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(self.cnt_net_, self.N_)])\n\n    def testDoUntilLoopWithNet(self):\n        self.DoUntilLoopTest(self.cnt_net_)\n        self.DoUntilLoopTest([self.cnt_net_, self.idle_net_])\n\n    def testDoUntilLoopWithStep(self):\n        step = control.Do('count', self.cnt_net_)\n        self.DoUntilLoopTest(step)\n        self.DoUntilLoopTest([self.idle_net_, step])\n\n    def IfCondTest(self, cond_net, expect, cond_on_blob):\n        if cond_on_blob:\n            step = control.Do(\n                'if-all',\n                control.Do('count', cond_net),\n                control.If('myIf', cond_net.Proto().external_output[-1],\n                           self.cnt_net_))\n        else:\n            step = control.If('myIf', cond_net, self.cnt_net_)\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(self.cnt_net_, expect)])\n\n    def testIfCondTrueOnNet(self):\n        self.IfCondTest(self.true_cond_net_, 1, False)\n\n    def testIfCondTrueOnBlob(self):\n        self.IfCondTest(self.true_cond_net_, 1, True)\n\n    def testIfCondFalseOnNet(self):\n        self.IfCondTest(self.false_cond_net_, 0, False)\n\n    def testIfCondFalseOnBlob(self):\n        self.IfCondTest(self.false_cond_net_, 0, True)\n\n    def IfElseCondTest(self, cond_net, cond_value, expect, cond_on_blob):\n        if cond_value:\n            run_net = self.cnt_net_\n        else:\n            run_net = self.cnt_2_net_\n        if cond_on_blob:\n            step = control.Do(\n                'if-else-all',\n                control.Do('count', cond_net),\n                control.If('myIfElse', cond_net.Proto().external_output[-1],\n                           self.cnt_net_, self.cnt_2_net_))\n        else:\n            step = control.If('myIfElse', cond_net,\n                              self.cnt_net_, self.cnt_2_net_)\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(run_net, expect)])\n\n    def testIfElseCondTrueOnNet(self):\n        self.IfElseCondTest(self.true_cond_net_, True, 1, False)\n\n    def testIfElseCondTrueOnBlob(self):\n        self.IfElseCondTest(self.true_cond_net_, True, 1, True)\n\n    def testIfElseCondFalseOnNet(self):\n        self.IfElseCondTest(self.false_cond_net_, False, 2, False)\n\n    def testIfElseCondFalseOnBlob(self):\n        self.IfElseCondTest(self.false_cond_net_, False, 2, True)\n\n    def IfNotCondTest(self, cond_net, expect, cond_on_blob):\n        if cond_on_blob:\n            step = control.Do(\n                'if-not',\n                control.Do('count', cond_net),\n                control.IfNot('myIfNot', cond_net.Proto().external_output[-1],\n                              self.cnt_net_))\n        else:\n            step = control.IfNot('myIfNot', cond_net, self.cnt_net_)\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(self.cnt_net_, expect)])\n\n    def testIfNotCondTrueOnNet(self):\n        self.IfNotCondTest(self.true_cond_net_, 0, False)\n\n    def testIfNotCondTrueOnBlob(self):\n        self.IfNotCondTest(self.true_cond_net_, 0, True)\n\n    def testIfNotCondFalseOnNet(self):\n        self.IfNotCondTest(self.false_cond_net_, 1, False)\n\n    def testIfNotCondFalseOnBlob(self):\n        self.IfNotCondTest(self.false_cond_net_, 1, True)\n\n    def IfNotElseCondTest(self, cond_net, cond_value, expect, cond_on_blob):\n        if cond_value:\n            run_net = self.cnt_2_net_\n        else:\n            run_net = self.cnt_net_\n        if cond_on_blob:\n            step = control.Do(\n                'if-not-else',\n                control.Do('count', cond_net),\n                control.IfNot('myIfNotElse',\n                              cond_net.Proto().external_output[-1],\n                              self.cnt_net_, self.cnt_2_net_))\n        else:\n            step = control.IfNot('myIfNotElse', cond_net,\n                                 self.cnt_net_, self.cnt_2_net_)\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(run_net, expect)])\n\n    def testIfNotElseCondTrueOnNet(self):\n        self.IfNotElseCondTest(self.true_cond_net_, True, 2, False)\n\n    def testIfNotElseCondTrueOnBlob(self):\n        self.IfNotElseCondTest(self.true_cond_net_, True, 2, True)\n\n    def testIfNotElseCondFalseOnNet(self):\n        self.IfNotElseCondTest(self.false_cond_net_, False, 1, False)\n\n    def testIfNotElseCondFalseOnBlob(self):\n        self.IfNotElseCondTest(self.false_cond_net_, False, 1, True)\n\n    def testSwitch(self):\n        step = control.Switch(\n            'mySwitch',\n            (self.false_cond_net_, self.cnt_net_),\n            (self.true_cond_net_, self.cnt_2_net_)\n        )\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(self.cnt_net_, 0), (self.cnt_2_net_, 2)])\n\n    def testSwitchNot(self):\n        step = control.SwitchNot(\n            'mySwitchNot',\n            (self.false_cond_net_, self.cnt_net_),\n            (self.true_cond_net_, self.cnt_2_net_)\n        )\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(self.cnt_net_, 1), (self.cnt_2_net_, 0)])\n\n    def testBoolNet(self):\n        bool_net = control.BoolNet(('a', True))\n        step = control.Do('bool', bool_net)\n        self.BuildAndRunPlan(step)\n        self.CheckNetAllOutput(bool_net, [True])\n\n        bool_net = control.BoolNet(('a', True), ('b', False))\n        step = control.Do('bool', bool_net)\n        self.BuildAndRunPlan(step)\n        self.CheckNetAllOutput(bool_net, [True, False])\n\n        bool_net = control.BoolNet([('a', True), ('b', False)])\n        step = control.Do('bool', bool_net)\n        self.BuildAndRunPlan(step)\n        self.CheckNetAllOutput(bool_net, [True, False])\n\n    def testCombineConditions(self):\n        # combined by 'Or'\n        combine_net = control.CombineConditions(\n            'test', [self.true_cond_net_, self.false_cond_net_], 'Or')\n        step = control.Do('combine',\n                          self.true_cond_net_,\n                          self.false_cond_net_,\n                          combine_net)\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(combine_net, True)])\n\n        # combined by 'And'\n        combine_net = control.CombineConditions(\n            'test', [self.true_cond_net_, self.false_cond_net_], 'And')\n        step = control.Do('combine',\n                          self.true_cond_net_,\n                          self.false_cond_net_,\n                          combine_net)\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(combine_net, False)])\n\n    def testMergeConditionNets(self):\n        # merged by 'Or'\n        merge_net = control.MergeConditionNets(\n            'test', [self.true_cond_net_, self.false_cond_net_], 'Or')\n        step = control.Do('merge', merge_net)\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(merge_net, True)])\n\n        # merged by 'And'\n        merge_net = control.MergeConditionNets(\n            'test', [self.true_cond_net_, self.false_cond_net_], 'And')\n        step = control.Do('merge', merge_net)\n        self.BuildAndRunPlan(step)\n        self.CheckNetOutput([(merge_net, False)])\n"
  },
  {
    "path": "caffe2/python/convnet_benchmarks.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package convnet_benchmarks\n# Module caffe2.python.convnet_benchmarks\n\"\"\"\nBenchmark for common convnets.\n\nSpeed on Titan X, with 10 warmup steps and 10 main steps and with different\nversions of cudnn, are as follows (time reported below is per-batch time,\nforward / forward+backward):\n\n                    CuDNN V3        CuDNN v4\nAlexNet         32.5 / 108.0    27.4 /  90.1\nOverFeat       113.0 / 342.3    91.7 / 276.5\nInception      134.5 / 485.8   125.7 / 450.6\nVGG (batch 64) 200.8 / 650.0   164.1 / 551.7\n\nSpeed on Inception with varied batch sizes and CuDNN v4 is as follows:\n\nBatch Size   Speed per batch     Speed per image\n 16             22.8 /  72.7         1.43 / 4.54\n 32             38.0 / 127.5         1.19 / 3.98\n 64             67.2 / 233.6         1.05 / 3.65\n128            125.7 / 450.6         0.98 / 3.52\n\nSpeed on Tesla M40, which 10 warmup steps and 10 main steps and with cudnn\nv4, is as follows:\n\nAlexNet         68.4 / 218.1\nOverFeat       210.5 / 630.3\nInception      300.2 / 1122.2\nVGG (batch 64) 405.8 / 1327.7\n\n(Note that these numbers involve a \"full\" backprop, i.e. the gradient\nwith respect to the input image is also computed.)\n\nTo get the numbers, simply run:\n\nfor MODEL in AlexNet OverFeat Inception; do\n  PYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size 128 --model $MODEL --forward_only True\ndone\nfor MODEL in AlexNet OverFeat Inception; do\n  PYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size 128 --model $MODEL\ndone\nPYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n  --batch_size 64 --model VGGA --forward_only True\nPYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n  --batch_size 64 --model VGGA\n\nfor BS in 16 32 64 128; do\n  PYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size $BS --model Inception --forward_only True\n  PYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size $BS --model Inception\ndone\n\nNote that VGG needs to be run at batch 64 due to memory limit on the backward\npass.\n\"\"\"\n\nimport argparse\n\nfrom caffe2.python import workspace, brew, model_helper\n\n\ndef MLP(order, cudnn_ws):\n    model = model_helper.ModelHelper(name=\"MLP\")\n    d = 256\n    depth = 20\n    width = 3\n    for i in range(depth):\n        for j in range(width):\n            current = \"fc_{}_{}\".format(i, j) if i > 0 else \"data\"\n            next_ = \"fc_{}_{}\".format(i + 1, j)\n            brew.fc(\n                model,\n                current,\n                next_,\n                dim_in=d,\n                dim_out=d,\n                weight_init=('XavierFill', {}),\n                bias_init=('XavierFill', {}),\n            )\n    brew.sum(\n        model, [\"fc_{}_{}\".format(depth, j) for j in range(width)], [\"sum\"]\n    )\n    brew.fc(\n        model,\n        \"sum\",\n        \"last\",\n        dim_in=d,\n        dim_out=1000,\n        weight_init=('XavierFill', {}),\n        bias_init=('XavierFill', {}),\n    )\n    xent = model.net.LabelCrossEntropy([\"last\", \"label\"], \"xent\")\n    model.net.AveragedLoss(xent, \"loss\")\n    return model, d\n\n\ndef AlexNet(order, cudnn_ws):\n    my_arg_scope = {\n        'order': order,\n        'use_cudnn': True,\n        'cudnn_exhaustive_search': True,\n    }\n    if cudnn_ws:\n        my_arg_scope['ws_nbytes_limit'] = cudnn_ws\n    model = model_helper.ModelHelper(\n        name=\"alexnet\",\n        arg_scope=my_arg_scope,\n    )\n    conv1 = brew.conv(\n        model,\n        \"data\",\n        \"conv1\",\n        3,\n        64,\n        11, ('XavierFill', {}), ('ConstantFill', {}),\n        stride=4,\n        pad=2\n    )\n    relu1 = brew.relu(model, conv1, \"conv1\")\n    pool1 = brew.max_pool(model, relu1, \"pool1\", kernel=3, stride=2)\n    conv2 = brew.conv(\n        model,\n        pool1,\n        \"conv2\",\n        64,\n        192,\n        5,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=2\n    )\n    relu2 = brew.relu(model, conv2, \"conv2\")\n    pool2 = brew.max_pool(model, relu2, \"pool2\", kernel=3, stride=2)\n    conv3 = brew.conv(\n        model,\n        pool2,\n        \"conv3\",\n        192,\n        384,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu3 = brew.relu(model, conv3, \"conv3\")\n    conv4 = brew.conv(\n        model,\n        relu3,\n        \"conv4\",\n        384,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu4 = brew.relu(model, conv4, \"conv4\")\n    conv5 = brew.conv(\n        model,\n        relu4,\n        \"conv5\",\n        256,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu5 = brew.relu(model, conv5, \"conv5\")\n    pool5 = brew.max_pool(model, relu5, \"pool5\", kernel=3, stride=2)\n    fc6 = brew.fc(\n        model,\n        pool5, \"fc6\", 256 * 6 * 6, 4096, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    relu6 = brew.relu(model, fc6, \"fc6\")\n    fc7 = brew.fc(\n        model, relu6, \"fc7\", 4096, 4096, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    relu7 = brew.relu(model, fc7, \"fc7\")\n    fc8 = brew.fc(\n        model, relu7, \"fc8\", 4096, 1000, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    pred = brew.softmax(model, fc8, \"pred\")\n    xent = model.net.LabelCrossEntropy([pred, \"label\"], \"xent\")\n    model.net.AveragedLoss(xent, \"loss\")\n    return model, 224\n\n\ndef OverFeat(order, cudnn_ws):\n    my_arg_scope = {\n        'order': order,\n        'use_cudnn': True,\n        'cudnn_exhaustive_search': True,\n    }\n    if cudnn_ws:\n        my_arg_scope['ws_nbytes_limit'] = cudnn_ws\n    model = model_helper.ModelHelper(\n        name=\"overfeat\",\n        arg_scope=my_arg_scope,\n    )\n    conv1 = brew.conv(\n        model,\n        \"data\",\n        \"conv1\",\n        3,\n        96,\n        11,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        stride=4,\n    )\n    relu1 = brew.relu(model, conv1, \"conv1\")\n    pool1 = brew.max_pool(model, relu1, \"pool1\", kernel=2, stride=2)\n    conv2 = brew.conv(\n        model, pool1, \"conv2\", 96, 256, 5, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    relu2 = brew.relu(model, conv2, \"conv2\")\n    pool2 = brew.max_pool(model, relu2, \"pool2\", kernel=2, stride=2)\n    conv3 = brew.conv(\n        model,\n        pool2,\n        \"conv3\",\n        256,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1,\n    )\n    relu3 = brew.relu(model, conv3, \"conv3\")\n    conv4 = brew.conv(\n        model,\n        relu3,\n        \"conv4\",\n        512,\n        1024,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1,\n    )\n    relu4 = brew.relu(model, conv4, \"conv4\")\n    conv5 = brew.conv(\n        model,\n        relu4,\n        \"conv5\",\n        1024,\n        1024,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1,\n    )\n    relu5 = brew.relu(model, conv5, \"conv5\")\n    pool5 = brew.max_pool(model, relu5, \"pool5\", kernel=2, stride=2)\n    fc6 = brew.fc(\n        model, pool5, \"fc6\", 1024 * 6 * 6, 3072, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    relu6 = brew.relu(model, fc6, \"fc6\")\n    fc7 = brew.fc(\n        model, relu6, \"fc7\", 3072, 4096, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    relu7 = brew.relu(model, fc7, \"fc7\")\n    fc8 = brew.fc(\n        model, relu7, \"fc8\", 4096, 1000, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    pred = brew.softmax(model, fc8, \"pred\")\n    xent = model.net.LabelCrossEntropy([pred, \"label\"], \"xent\")\n    model.net.AveragedLoss(xent, \"loss\")\n    return model, 231\n\n\ndef VGGA(order, cudnn_ws):\n    my_arg_scope = {\n        'order': order,\n        'use_cudnn': True,\n        'cudnn_exhaustive_search': True,\n    }\n    if cudnn_ws:\n        my_arg_scope['ws_nbytes_limit'] = cudnn_ws\n    model = model_helper.ModelHelper(\n        name=\"vgga\",\n        arg_scope=my_arg_scope,\n    )\n    conv1 = brew.conv(\n        model,\n        \"data\",\n        \"conv1\",\n        3,\n        64,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1,\n    )\n    relu1 = brew.relu(model, conv1, \"conv1\")\n    pool1 = brew.max_pool(model, relu1, \"pool1\", kernel=2, stride=2)\n    conv2 = brew.conv(\n        model,\n        pool1,\n        \"conv2\",\n        64,\n        128,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1,\n    )\n    relu2 = brew.relu(model, conv2, \"conv2\")\n    pool2 = brew.max_pool(model, relu2, \"pool2\", kernel=2, stride=2)\n    conv3 = brew.conv(\n        model,\n        pool2,\n        \"conv3\",\n        128,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1,\n    )\n    relu3 = brew.relu(model, conv3, \"conv3\")\n    conv4 = brew.conv(\n        model,\n        relu3,\n        \"conv4\",\n        256,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1,\n    )\n    relu4 = brew.relu(model, conv4, \"conv4\")\n    pool4 = brew.max_pool(model, relu4, \"pool4\", kernel=2, stride=2)\n    conv5 = brew.conv(\n        model,\n        pool4,\n        \"conv5\",\n        256,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1,\n    )\n    relu5 = brew.relu(model, conv5, \"conv5\")\n    conv6 = brew.conv(\n        model,\n        relu5,\n        \"conv6\",\n        512,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1,\n    )\n    relu6 = brew.relu(model, conv6, \"conv6\")\n    pool6 = brew.max_pool(model, relu6, \"pool6\", kernel=2, stride=2)\n    conv7 = brew.conv(\n        model,\n        pool6,\n        \"conv7\",\n        512,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1,\n    )\n    relu7 = brew.relu(model, conv7, \"conv7\")\n    conv8 = brew.conv(\n        model,\n        relu7,\n        \"conv8\",\n        512,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1,\n    )\n    relu8 = brew.relu(model, conv8, \"conv8\")\n    pool8 = brew.max_pool(model, relu8, \"pool8\", kernel=2, stride=2)\n\n    fcix = brew.fc(\n        model, pool8, \"fcix\", 512 * 7 * 7, 4096, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    reluix = brew.relu(model, fcix, \"fcix\")\n    fcx = brew.fc(\n        model, reluix, \"fcx\", 4096, 4096, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    relux = brew.relu(model, fcx, \"fcx\")\n    fcxi = brew.fc(\n        model, relux, \"fcxi\", 4096, 1000, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    pred = brew.softmax(model, fcxi, \"pred\")\n    xent = model.net.LabelCrossEntropy([pred, \"label\"], \"xent\")\n    model.net.AveragedLoss(xent, \"loss\")\n    return model, 231\n\n\ndef _InceptionModule(\n    model, input_blob, input_depth, output_name, conv1_depth, conv3_depths,\n    conv5_depths, pool_depth\n):\n    # path 1: 1x1 conv\n    conv1 = brew.conv(\n        model, input_blob, output_name + \":conv1\", input_depth, conv1_depth, 1,\n        ('XavierFill', {}), ('ConstantFill', {})\n    )\n    conv1 = brew.relu(model, conv1, conv1)\n    # path 2: 1x1 conv + 3x3 conv\n    conv3_reduce = brew.conv(\n        model, input_blob, output_name + \":conv3_reduce\", input_depth,\n        conv3_depths[0], 1, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    conv3_reduce = brew.relu(model, conv3_reduce, conv3_reduce)\n    conv3 = brew.conv(\n        model,\n        conv3_reduce,\n        output_name + \":conv3\",\n        conv3_depths[0],\n        conv3_depths[1],\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1,\n    )\n    conv3 = brew.relu(model, conv3, conv3)\n    # path 3: 1x1 conv + 5x5 conv\n    conv5_reduce = brew.conv(\n        model, input_blob, output_name + \":conv5_reduce\", input_depth,\n        conv5_depths[0], 1, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    conv5_reduce = brew.relu(model, conv5_reduce, conv5_reduce)\n    conv5 = brew.conv(\n        model,\n        conv5_reduce,\n        output_name + \":conv5\",\n        conv5_depths[0],\n        conv5_depths[1],\n        5,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=2,\n    )\n    conv5 = brew.relu(model, conv5, conv5)\n    # path 4: pool + 1x1 conv\n    pool = brew.max_pool(\n        model,\n        input_blob,\n        output_name + \":pool\",\n        kernel=3,\n        stride=1,\n        pad=1,\n    )\n    pool_proj = brew.conv(\n        model, pool, output_name + \":pool_proj\", input_depth, pool_depth, 1,\n        ('XavierFill', {}), ('ConstantFill', {})\n    )\n    pool_proj = brew.relu(model, pool_proj, pool_proj)\n    output = brew.concat(model, [conv1, conv3, conv5, pool_proj], output_name)\n    return output\n\n\ndef Inception(order, cudnn_ws):\n    my_arg_scope = {\n        'order': order,\n        'use_cudnn': True,\n        'cudnn_exhaustive_search': True,\n    }\n    if cudnn_ws:\n        my_arg_scope['ws_nbytes_limit'] = cudnn_ws\n    model = model_helper.ModelHelper(\n        name=\"inception\",\n        arg_scope=my_arg_scope,\n    )\n    conv1 = brew.conv(\n        model,\n        \"data\",\n        \"conv1\",\n        3,\n        64,\n        7,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        stride=2,\n        pad=3,\n    )\n    relu1 = brew.relu(model, conv1, \"conv1\")\n    pool1 = brew.max_pool(model, relu1, \"pool1\", kernel=3, stride=2, pad=1)\n    conv2a = brew.conv(\n        model, pool1, \"conv2a\", 64, 64, 1, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    conv2a = brew.relu(model, conv2a, conv2a)\n    conv2 = brew.conv(\n        model,\n        conv2a,\n        \"conv2\",\n        64,\n        192,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1,\n    )\n    relu2 = brew.relu(model, conv2, \"conv2\")\n    pool2 = brew.max_pool(model, relu2, \"pool2\", kernel=3, stride=2, pad=1)\n    # Inception modules\n    inc3 = _InceptionModule(\n        model, pool2, 192, \"inc3\", 64, [96, 128], [16, 32], 32\n    )\n    inc4 = _InceptionModule(\n        model, inc3, 256, \"inc4\", 128, [128, 192], [32, 96], 64\n    )\n    pool5 = brew.max_pool(model, inc4, \"pool5\", kernel=3, stride=2, pad=1)\n    inc5 = _InceptionModule(\n        model, pool5, 480, \"inc5\", 192, [96, 208], [16, 48], 64\n    )\n    inc6 = _InceptionModule(\n        model, inc5, 512, \"inc6\", 160, [112, 224], [24, 64], 64\n    )\n    inc7 = _InceptionModule(\n        model, inc6, 512, \"inc7\", 128, [128, 256], [24, 64], 64\n    )\n    inc8 = _InceptionModule(\n        model, inc7, 512, \"inc8\", 112, [144, 288], [32, 64], 64\n    )\n    inc9 = _InceptionModule(\n        model, inc8, 528, \"inc9\", 256, [160, 320], [32, 128], 128\n    )\n    pool9 = brew.max_pool(model, inc9, \"pool9\", kernel=3, stride=2, pad=1)\n    inc10 = _InceptionModule(\n        model, pool9, 832, \"inc10\", 256, [160, 320], [32, 128], 128\n    )\n    inc11 = _InceptionModule(\n        model, inc10, 832, \"inc11\", 384, [192, 384], [48, 128], 128\n    )\n    pool11 = brew.average_pool(model, inc11, \"pool11\", kernel=7, stride=1)\n    fc = brew.fc(\n        model, pool11, \"fc\", 1024, 1000, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    # It seems that Soumith's benchmark does not have softmax on top\n    # for Inception. We will add it anyway so we can have a proper\n    # backward pass.\n    pred = brew.softmax(model, fc, \"pred\")\n    xent = model.net.LabelCrossEntropy([pred, \"label\"], \"xent\")\n    model.net.AveragedLoss(xent, \"loss\")\n    return model, 224\n\n\ndef AddParameterUpdate(model):\n    \"\"\" Simple plain SGD update -- not tuned to actually train the models \"\"\"\n    ITER = brew.iter(model, \"iter\")\n    LR = model.net.LearningRate(\n        ITER, \"LR\", base_lr=-1e-8, policy=\"step\", stepsize=10000, gamma=0.999)\n    ONE = model.param_init_net.ConstantFill([], \"ONE\", shape=[1], value=1.0)\n    for param in model.params:\n        param_grad = model.param_to_grad[param]\n        model.net.WeightedSum([param, ONE, param_grad, LR], param)\n\n\ndef Benchmark(model_gen, arg):\n    model, input_size = model_gen(arg.order, arg.cudnn_ws)\n    model.Proto().type = arg.net_type\n    model.Proto().num_workers = arg.num_workers\n\n    # In order to be able to run everything without feeding more stuff, let's\n    # add the data and label blobs to the parameter initialization net as well.\n    if arg.order == \"NCHW\":\n        input_shape = [arg.batch_size, 3, input_size, input_size]\n    else:\n        input_shape = [arg.batch_size, input_size, input_size, 3]\n    if arg.model == \"MLP\":\n        input_shape = [arg.batch_size, input_size]\n\n    model.param_init_net.GaussianFill(\n        [],\n        \"data\",\n        shape=input_shape,\n        mean=0.0,\n        std=1.0\n    )\n    model.param_init_net.UniformIntFill(\n        [],\n        \"label\",\n        shape=[arg.batch_size, ],\n        min=0,\n        max=999\n    )\n\n    if arg.forward_only:\n        print('{}: running forward only.'.format(arg.model))\n    else:\n        print('{}: running forward-backward.'.format(arg.model))\n        model.AddGradientOperators([\"loss\"])\n        AddParameterUpdate(model)\n        if arg.order == 'NHWC':\n            print(\n                '==WARNING==\\n'\n                'NHWC order with CuDNN may not be supported yet, so I might\\n'\n                'exit suddenly.'\n            )\n\n    if not arg.cpu:\n        model.param_init_net.RunAllOnGPU()\n        model.net.RunAllOnGPU()\n\n    if arg.engine:\n        for op in model.net.Proto().op:\n            op.engine = arg.engine\n\n    if arg.dump_model:\n        # Writes out the pbtxt for benchmarks on e.g. Android\n        with open(\n            \"{0}_init_batch_{1}.pbtxt\".format(arg.model, arg.batch_size), \"w\"\n        ) as fid:\n            fid.write(str(model.param_init_net.Proto()))\n        with open(\"{0}.pbtxt\".format(arg.model, arg.batch_size), \"w\") as fid:\n            fid.write(str(model.net.Proto()))\n\n    workspace.RunNetOnce(model.param_init_net)\n    workspace.CreateNet(model.net)\n    workspace.BenchmarkNet(\n        model.net.Proto().name, arg.warmup_iterations, arg.iterations,\n        arg.layer_wise_benchmark)\n\n\ndef GetArgumentParser():\n    parser = argparse.ArgumentParser(description=\"Caffe2 benchmark.\")\n    parser.add_argument(\n        \"--batch_size\",\n        type=int,\n        default=128,\n        help=\"The batch size.\"\n    )\n    parser.add_argument(\"--model\", type=str, help=\"The model to benchmark.\")\n    parser.add_argument(\n        \"--order\",\n        type=str,\n        default=\"NCHW\",\n        help=\"The order to evaluate.\"\n    )\n    parser.add_argument(\n        \"--cudnn_ws\",\n        type=int,\n        help=\"The cudnn workspace size.\"\n    )\n    parser.add_argument(\n        \"--iterations\",\n        type=int,\n        default=10,\n        help=\"Number of iterations to run the network.\"\n    )\n    parser.add_argument(\n        \"--warmup_iterations\",\n        type=int,\n        default=10,\n        help=\"Number of warm-up iterations before benchmarking.\"\n    )\n    parser.add_argument(\n        \"--forward_only\",\n        action='store_true',\n        help=\"If set, only run the forward pass.\"\n    )\n    parser.add_argument(\n        \"--layer_wise_benchmark\",\n        action='store_true',\n        help=\"If True, run the layer-wise benchmark as well.\"\n    )\n    parser.add_argument(\n        \"--cpu\",\n        action='store_true',\n        help=\"If True, run testing on CPU instead of GPU.\"\n    )\n    parser.add_argument(\n        \"--engine\",\n        type=str,\n        default=\"\",\n        help=\"If set, blindly prefer the given engine(s) for every op.\")\n    parser.add_argument(\n        \"--dump_model\",\n        action='store_true',\n        help=\"If True, dump the model prototxts to disk.\"\n    )\n    parser.add_argument(\"--net_type\", type=str, default=\"dag\")\n    parser.add_argument(\"--num_workers\", type=int, default=2)\n    parser.add_argument(\"--use-nvtx\", default=False, action='store_true')\n    parser.add_argument(\"--htrace_span_log_path\", type=str)\n    return parser\n\n\nif __name__ == '__main__':\n    args, extra_args = GetArgumentParser().parse_known_args()\n    if (\n        not args.batch_size or not args.model or not args.order\n    ):\n        GetArgumentParser().print_help()\n    else:\n        workspace.GlobalInit(\n            ['caffe2', '--caffe2_log_level=0'] + extra_args +\n            (['--caffe2_use_nvtx'] if args.use_nvtx else []) +\n            (['--caffe2_htrace_span_log_path=' + args.htrace_span_log_path]\n                if args.htrace_span_log_path else []))\n\n        model_map = {\n            'AlexNet': AlexNet,\n            'OverFeat': OverFeat,\n            'VGGA': VGGA,\n            'Inception': Inception,\n            'MLP': MLP,\n        }\n        Benchmark(model_map[args.model], args)\n"
  },
  {
    "path": "caffe2/python/convnet_benchmarks_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nimport unittest\nfrom caffe2.python import convnet_benchmarks as cb\nfrom caffe2.python import test_util, workspace\n\n\n@unittest.skipIf(not workspace.has_gpu_support, \"no gpu\")\nclass TestConvnetBenchmarks(test_util.TestCase):\n    def testConvnetBenchmarks(self):\n        all_args = [\n            '--batch_size 16 --order NCHW --iterations 1 '\n            '--warmup_iterations 1',\n            '--batch_size 16 --order NCHW --iterations 1 '\n            '--warmup_iterations 1 --forward_only',\n        ]\n        for model in [cb.AlexNet, cb.OverFeat, cb.VGGA, cb.Inception]:\n            for arg_str in all_args:\n                args = cb.GetArgumentParser().parse_args(arg_str.split(' '))\n                cb.Benchmark(model, args)\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/core.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package core\n# Module caffe2.python.core\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom collections import namedtuple, OrderedDict, defaultdict\nfrom past.builtins import basestring\nfrom future.utils import viewitems, viewkeys, viewvalues\nfrom itertools import chain\nfrom six import binary_type, string_types, text_type\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import scope, utils, workspace\nfrom caffe2.python.control_ops_grad import \\\n    gen_do_gradient, gen_if_gradient, gen_while_gradient\n\nimport caffe2.python._import_c_extension as C\nimport pickle\nimport numpy as np\nimport sys\nimport traceback\n\n# Mac os specific message\nif (sys.platform == 'darwin' and 'leveldb' in C.registered_dbs()):\n    print('If you are using homebrew leveldb on a Mac OS, you might see an '\n          'error warning you that malloc_zone_unregister() failed. This is '\n          'not a caffe2 issue but is due to the homebrew leveldb having an '\n          'incompatible memory allocator. It does not affect usage.')\n\n# Convenience redirections to functions inside scope.\nDeviceScope = scope.DeviceScope\nNameScope = scope.NameScope\n\n\n# Bring datatype enums to the main namespace\nclass DataType:\n    pass\n\n\ndef _InitDataType():\n    for name, value in caffe2_pb2.TensorProto.DataType.items():\n        setattr(DataType, name, value)\n\n\n_InitDataType()\n\n\ndef _GetRegisteredOperators():\n    return set(workspace.RegisteredOperators())\n\n\n_REGISTERED_OPERATORS = _GetRegisteredOperators()\n\n\ndef RefreshRegisteredOperators():\n    global _REGISTERED_OPERATORS\n    _REGISTERED_OPERATORS = _GetRegisteredOperators()\n\n\n_GLOBAL_INIT_ARGS = []\n\n\ndef GlobalInit(args):\n    _GLOBAL_INIT_ARGS.extend(args[1:])\n    C.global_init(args)\n\n\ndef GetGlobalInitArgs():\n    return _GLOBAL_INIT_ARGS[:]\n\n\ndef IsOperator(op_type):\n    return IsOperatorWithEngine(op_type, engine='DEFAULT')\n\n\ndef IsOperatorWithEngine(op_type, engine):\n    return C.op_registry_key(op_type, engine) in _REGISTERED_OPERATORS\n\n\ndef DeviceOption(device_type, cuda_gpu_id=0, random_seed=None, node_name=None):\n    option = caffe2_pb2.DeviceOption()\n    option.device_type = device_type\n    option.cuda_gpu_id = cuda_gpu_id\n    if node_name is not None:\n        option.node_name = node_name\n    if random_seed is not None:\n        option.random_seed = random_seed\n    return option\n\n\ndef device_option_equal(opt1, opt2, ignore_node_name=True, ignore_random_seed=True):\n    if not opt1 or not opt2:\n        return opt1 == opt2\n    if not ignore_node_name and opt1.node_name != opt2.node_name:\n        return False\n    if not ignore_random_seed and opt1.random_seed != opt2.random_seed:\n        return False\n    if not opt1.device_type or not opt2.device_type:\n        # At least one option is for CPU, check if both are for CPU.\n        return not opt1.device_type and not opt2.device_type\n    return opt1.cuda_gpu_id == opt2.cuda_gpu_id\n\n\ndef InferBlobDevices(net):\n    '''\n    Compute mapping from parameters to devices by looking at the\n    device option of the op that creates the blob has\n    '''\n    mapping = {}\n    for op in net.Proto().op:\n        op_device = op.device_option\n        if op_device is None:\n            op_device = caffe2_pb2.DeviceOption(caffe2_pb2.CPU)\n        # TODO: T18892922, use device annotations\n        for b in op.output:\n            mapping[b] = op_device\n    return mapping\n\n\ndef InferOpBlobDevices(op):\n    device_info = C.infer_op_input_output_device(op.SerializeToString())\n    input_info = []\n    output_info = []\n    for dev_str in device_info[0]:\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.ParseFromString(dev_str)\n        input_info.append(device_option)\n    for dev_str in device_info[1]:\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.ParseFromString(dev_str)\n        output_info.append(device_option)\n    return input_info, output_info\n\n\nGradientSlice = namedtuple('GradientSlice', ['indices', 'values'])\n\n\nclass BlobReference(object):\n    \"\"\"A wrapper around a blob in a net.\n\n    BlobReference gives us a way to refer to the network that the blob is\n    generated from. Note that blobs are, essentially, just strings in the\n    current workspace.\n    \"\"\"\n\n    def __init__(self, name, net=None):\n        \"\"\"Initializes a blob reference.\n\n        Note that this does not prepends the namescope. If needed, use\n        ScopedBlobReference() to prepend the existing namespace.\n        \"\"\"\n        if isinstance(name, string_types):\n            self._name = name\n        elif isinstance(name, binary_type):\n            self._name = name.decode('utf-8')\n        else:\n            self._name = str(name)\n        self._from_net = net\n        # meta allows helper functions to put whatever metainformation needed\n        # there.\n        self.meta = {}\n\n    def __hash__(self):\n        return hash(self._name)\n\n    def __eq__(self, other):\n        if isinstance(other, string_types):\n            return self._name == other\n        elif isinstance(other, binary_type):\n            return self._name == other.decode('utf-8')\n        elif isinstance(other, BlobReference):\n            return self._name == other._name\n        else:\n            return False\n\n    def __ne__(self, other):\n        return not(self == other)\n\n    def __str__(self):\n        return self._name\n\n    def __repr__(self):\n        return 'BlobReference(\"{}\")'.format(self._name)\n\n    def __add__(self, other):\n        if not isinstance(other, string_types):\n            raise RuntimeError('Cannot add BlobReference to a non-string.')\n        return BlobReference(self._name + other, self._from_net)\n\n    def __radd__(self, other):\n        if not isinstance(other, string_types):\n            raise RuntimeError('Cannot add a non-string to BlobReference.')\n        return BlobReference(other + self._name, self._from_net)\n\n    def Net(self):\n        return self._from_net\n\n    def GetNameScope(self):\n        return self._name[:self._name.rfind(scope._NAMESCOPE_SEPARATOR) + 1]\n\n    def _CreateAndAddToNet(self, op_type, inputs=None, *args, **kwargs):\n        \"\"\"Internal function that routes the operator generation to the\n        network's __getattr__ function.\n        \"\"\"\n        inputs = [] if inputs is None else inputs\n        if isinstance(inputs, BlobReference) or isinstance(inputs, string_types):\n            inputs = [inputs]\n        # add self to the input list.\n        inputs.insert(0, self)\n        return self._from_net.__getattr__(op_type)(inputs, *args, **kwargs)\n\n    def __getattr__(self, op_type):\n        \"\"\"A wrapper allowing one to initiate operators from a blob reference.\n\n        Example: for a blob reference b that comes from network n, doing\n            b.Relu(...)\n        is equivalent to doing\n            net.Relu([b], ...)\n        \"\"\"\n        if op_type.startswith('__'):\n            raise AttributeError('Attribute {} not found.'.format(op_type))\n        if self._from_net is None:\n            raise RuntimeError(\n                'You cannot use a blob reference that does not have a net '\n                'source to create operators. Create the operator from an '\n                'explicit net object.')\n        if not IsOperator(op_type):\n            raise RuntimeError(\n                'Method ' + op_type + ' is not a registered operator.' +\n                ' Did you mean: [' +\n                \",\".join(workspace.C.nearby_opnames(op_type)) + ']'\n            )\n        return lambda *args, **kwargs: self._CreateAndAddToNet(\n            op_type, *args, **kwargs)\n\n    def __dir__(self):\n        additional_methods = [\n            op\n            for op in _REGISTERED_OPERATORS\n            if '_ENGINE_' not in op or '_ENGINE_CUDNN' in op]\n        return sorted(set(chain(\n            dir(type(self)),\n            viewkeys(self.__dict__),\n            additional_methods\n        )))\n\n\ndef ScopedName(name):\n    \"\"\"prefix the name with the current scope.\"\"\"\n    if isinstance(name, binary_type):\n        name = name.decode('ascii')\n    return scope.CurrentNameScope() + name\n\n\ndef ScopedBlobReference(name, *args, **kwargs):\n    \"\"\"Returns a blob reference with scope prefixed.\"\"\"\n    return BlobReference(ScopedName(name), *args, **kwargs)\n\n\ndef _RectifyInputOutput(blobs, net=None):\n    \"\"\"A helper function to rectify the input or output of the CreateOperator\n    interface.\n    \"\"\"\n    if isinstance(blobs, string_types) or isinstance(blobs, binary_type):\n        # If blobs is a single string, prepend scope.CurrentNameScope()\n        # and put it as a list.\n        # TODO(jiayq): enforce using BlobReference instead of raw strings.\n        return [ScopedBlobReference(blobs, net=net)]\n    elif type(blobs) is BlobReference:\n        # If blob is a BlobReference, simply put it as a list.\n        return [blobs]\n    elif type(blobs) in (list, tuple):\n        # If blob is a list, we go through it and type check.\n        rectified = []\n        for blob in blobs:\n            if isinstance(blob, string_types) or isinstance(blob, binary_type):\n                rectified.append(ScopedBlobReference(blob, net=net))\n            elif type(blob) is BlobReference:\n                rectified.append(blob)\n            else:\n                raise TypeError(\n                    \"I/O blob #{} of unsupported type: {} of type {}\"\n                    .format(len(rectified), str(blob), type(blob)))\n        return rectified\n    else:\n        raise TypeError(\n            \"Unknown input/output type: %s of type %s.\" %\n            (str(blobs), type(blobs))\n        )\n\n\ndef CreateOperator(\n    operator_type,\n    inputs,\n    outputs,\n    name='',\n    control_input=None,\n    device_option=None,\n    arg=None,\n    engine=None,\n    **kwargs\n):\n    \"\"\"A function wrapper that allows one to create operators based on the\n    operator type. The type should be a string corresponding to an operator\n    registered with Caffe2.\n    \"\"\"\n    operator = caffe2_pb2.OperatorDef()\n    stack = traceback.format_stack()\n    operator.debug_info = \"\".join(stack[:-1])\n\n    operator.type = operator_type\n    operator.name = name\n    # Add rectified inputs and outputs\n    inputs = _RectifyInputOutput(inputs)\n    outputs = _RectifyInputOutput(outputs)\n    operator.input.extend([text_type(i) for i in inputs])\n    operator.output.extend([text_type(o) for o in outputs])\n    if control_input:\n        control_input = _RectifyInputOutput(control_input)\n        operator.control_input.extend([text_type(i) for i in control_input])\n    # Set device option:\n    # (1) If device_option is explicitly set, use device_option.\n    # (2) If not, but scope.CurrentDeviceScope() is set,\n    #     then we use scope.CurrentDeviceScope().\n    # (3) Otherwise, do not set device option.\n    if device_option is not None:\n        operator.device_option.CopyFrom(device_option)\n    elif scope.CurrentDeviceScope() is not None:\n        operator.device_option.CopyFrom(scope.CurrentDeviceScope())\n    if engine is not None:\n        operator.engine = engine\n    # random seed is defined in the device option, so we need to do special\n    # care.\n\n    if 'random_seed' in kwargs:\n        operator.device_option.random_seed = kwargs['random_seed']\n        del kwargs['random_seed']\n    # Add given arguments that do not need parsing\n    if arg is not None:\n        operator.arg.extend(arg)\n    # Add all other arguments\n    for key, value in viewitems(kwargs):\n        operator.arg.add().CopyFrom(utils.MakeArgument(key, value))\n\n    if workspace.IsImmediate():\n        workspace.RunOperatorImmediate(operator)\n    return operator\n\n\ndef _RegisterPythonImpl(\n    f, grad_f=None, python_func_type=None, pass_workspace=False\n):\n    if python_func_type:\n        func = python_func_type(f)\n        f = func.forward\n        grad_f = func.backward\n    else:\n        if isinstance(f, tuple):\n            f = f[0](*f[1], **f[2])\n        if isinstance(grad_f, tuple):\n            grad_f = grad_f[0](*grad_f[1], **grad_f[2])\n\n    token = C.register_python_op(f, pass_workspace, '')\n    if grad_f:\n        C.register_python_gradient_op(token, grad_f)\n    return token\n\n\ndef CreatePythonOperator(\n    f, inputs,\n    outputs,\n    grad_f=None,\n    pass_workspace=False,\n    python_func_type=None,\n    *args,\n    **kwargs\n):\n    \"\"\"\n    `f` should have a signature (inputs, outputs)\n\n    If `pass_workspace` is True, the signature is changed to\n    (inputs, outputs, workspace) where `workspace` is the workspace the op\n    is going to run on. This is potentially dangerous (as the op can manipulate\n    the workspace directly), use on your own risk.\n    \"\"\"\n    kwargs[\"token\"] = _RegisterPythonImpl(\n        f, grad_f, python_func_type, pass_workspace=pass_workspace\n    )\n    return CreateOperator(\"Python\", inputs, outputs, *args, **kwargs)\n\n\ndef GetIndexFromGradientList(g_list, name):\n    \"\"\"A helper function to get the index from a gradient list, None if not\n    matching.\"\"\"\n    for i, g in enumerate(g_list):\n        if g == name:\n            return i\n        elif type(g) is GradientSlice:\n            if (g.indices == name or g.values == name):\n                return i\n    return None\n\n\nOpSSA = namedtuple('OpSSA', ['op', 'in_versions', 'out_versions'])\nGradGenMeta = namedtuple('GradGenMeta', ['grad_op', 'idx', 'gradient'])\nSparseGradGenMeta = namedtuple('SparseGradGenMeta', [\n    'grad_op_indices', 'idx_indices',\n    'grad_op_values', 'idx_values',\n    'gradient',\n])\n\n\nclass IR(object):\n    \"\"\"A simple IR class to keep track of all intermediate representations used\n    in the gradient computation.\n    \"\"\"\n\n    def __init__(self, operators):\n        # The IR class holds multiple metadata from the forward pass:\n        # a) ssa: a list of [op, in_versions, out_versions] recording the\n        #    input and the output version of each operator, similar\n        #    to a normal SSA form.\n        # b) input_count: a dictionary specifying for each blob and\n        #    each of its version, how many times it is used as input for another\n        #    op.\n        # c) frontier: maintaining the current versions of the blobs\n        #    we are having in the workspace, after the execution of all the ops\n        #    added to the IR so far. This is useful because if a gradient is\n        #    trying to access an earlier version of a blob, we can sanity check\n        #    that it is no longer there, and thus throw an error.\n        # d) gradient_frontier: maps the names of blobs to its version that the\n        #    gradient corresponds to.\n        # e) gradient_generators: for each blob and each of its version, maps to\n        #    a list of operators that generates its gradient together with the\n        #    gradient name.\n        self.ssa = []\n        self.input_usages = defaultdict(lambda: defaultdict(list))\n        self.frontier = defaultdict(int)\n        self.gradient_frontier = {}\n        self.gradient_generators = defaultdict(lambda: defaultdict(list))\n        self.out_version_history = defaultdict(list)\n        self.in_version_history = defaultdict(list)\n\n        for op in operators:\n            self.Play(op)\n\n        self.SanityCheck(operators)\n\n    def SanityCheck(self, operators):\n        # Validate StopGradient usage by checking that StopGradient's output\n        # is actually passed forward\n        for op in operators:\n            if op.type == 'StopGradient':\n                if op.output[0] not in self.input_usages:\n                    raise ValueError(\"\"\"StopGradient's output '{}' is orphan.\nYou typically want to specify same input and output for\nStopGradient. Op:\\n\\n{}\"\"\".format(op.output[0], str(op)))\n\n    def Play(self, op):\n        \"\"\"\"Adds an op to the current IR, and update the internal states to\n        reflect the blobs and versions after the execution of the op.\n        \"\"\"\n        # For input, they are the current version in the dict.\n        in_versions = {}\n        for s in op.input:\n            in_versions[s] = self.frontier[s]\n            self.input_usages[s][self.frontier[s]].append(len(self.ssa))\n            self.in_version_history[s].append((op, self.frontier[s]))\n        # For output, they are the current version plus one. If this is a\n        # newly created blob, its version starts with zero.\n        out_versions = {}\n        for s in op.output:\n            if s in self.frontier:\n                self.frontier[s] += 1\n            out_versions[s] = self.frontier[s]\n            self.out_version_history[s].append((op, self.frontier[s]))\n        # Add to SSA for bookkeeping.\n        self.ssa.append(OpSSA(op, in_versions, out_versions))\n\n    def CheckGradientOperatorInput(\n            self, grad_op_input, g_output, fwd_op_idx, locally_generated_blobs):\n        \"\"\"Checks if the gradient operators can be correctly carried out.\"\"\"\n        forward_op, in_versions, out_versions = self.ssa[fwd_op_idx]\n        original_index = GetIndexFromGradientList(g_output, grad_op_input)\n\n        # Functions to generate debug help for version-mismatches\n        def versionMismatchInfoOut(name):\n            s = \"DEBUG HELP:\\n\"\n            s += \"Maybe you use same output blob twice for different ops?\\n\"\n            s += \"== Version history of blob [{}]\\n\".format(name)\n            for (op, vers) in self.out_version_history[name]:\n                s += \"Version (out) {} <-- {}\".format(vers, op)\n                s += \"\\n\"\n            return s\n\n        def versionMismatchInfoIn(name):\n            s = \"DEBUG HELP:\\n\"\n            s += \"Maybe the blob was overwritten by another op?\\n\"\n            s += \"== Version history of blob [{}]\\n\".format(name)\n            for (op, vers) in self.in_version_history[name]:\n                s += \"version (in) {} <-- {}\".format(vers, op)\n                s += \"\\n\"\n            return s\n\n        # If it is a dense or sparse gradient name, it should match the\n        # version of the corresponding output.\n        if original_index is not None:\n            original_name = forward_op.output[original_index]\n            if (out_versions[original_name] !=\n                    self.gradient_frontier[original_name]):\n                raise RuntimeError(\n                    'Gradient name \"%s\" is expected to correspond '\n                    'to version %d of \"%s\", but currently we have '\n                    'version %d.\\n\\n' % (\n                        grad_op_input, out_versions[original_name],\n                        original_name,\n                        self.gradient_frontier[original_name]) +\n                    versionMismatchInfoOut(original_name))\n        # If it is an output name, the current version should match the\n        # version when the operator was run.\n        elif grad_op_input in out_versions:\n            if self.frontier[grad_op_input] != out_versions[grad_op_input]:\n                raise RuntimeError(\n                    'Gradient operator needs output \"%s\" at version'\n                    ' %d, but currently we have version %d.\\n\\n' % (\n                        grad_op_input, out_versions[grad_op_input],\n                        self.frontier[grad_op_input]\n                    ) + versionMismatchInfoOut(grad_op_input)\n                )\n        # If it is an input name, the current version should match the\n        # version when the operator was run.\n        elif grad_op_input in in_versions:\n            if (self.frontier[grad_op_input] != in_versions[grad_op_input]):\n                raise RuntimeError(\n                    'Gradient operator needs input \"%s\" at version '\n                    '%d, but currently we have version %d.\\n\\n' % (\n                        grad_op_input, in_versions[grad_op_input],\n                        self.frontier[grad_op_input]\n                    ) + versionMismatchInfoIn(grad_op_input)\n                )\n        # If it is none of the above, it should be a blob that is\n        # generated locally by one of the previous gradient operators.\n        else:\n            if grad_op_input not in locally_generated_blobs:\n                raise RuntimeError(\n                    'Blob name \"%s\" not in the scope of operator: '\n                    '%s\\nand is not generated by any of the local '\n                    'gradient operators.' % (grad_op_input, str(forward_op))\n                )\n\n    def AppendSparseGenerators(self, sparse_generators):\n        # merge indices and values generators for sparse gradients\n        for name, input_generators in viewitems(sparse_generators):\n            for version, generators in viewitems(input_generators):\n                if len(generators) == 1:\n                    # either indices or values are generated (but not both)\n                    generator = generators[0]\n                else:\n                    # both indices and values are generated\n                    assert(len(generators) == 2)\n                    op1_i, idx1_i, op1_v, idx1_v, g1 = generators[0]\n                    op2_i, idx2_i, op2_v, idx2_v, g2 = generators[1]\n                    assert(g1 == g2)\n                    assert(op1_i is None or op2_i is None)\n                    assert(op1_v is None or op2_v is None)\n                    assert(idx1_i == 0 or idx2_i == 0)\n                    assert(idx1_v == 0 or idx2_v == 0)\n                    generator = SparseGradGenMeta(\n                        op1_i or op2_i, idx1_i + idx2_i,\n                        op1_v or op2_v, idx1_v + idx2_v,\n                        g1)\n                self.gradient_generators[name][version].append(generator)\n\n    def BuildGradientGenerators(  # NOQA\n            self, fwd_op_idx, gradient_ops, g_output, g_input):\n        \"\"\"Updates gradient_generators and gradient_frontier\"\"\"\n        forward_op, in_versions, out_versions = self.ssa[fwd_op_idx]\n        locally_generated_blobs = []\n        sparse_generators = defaultdict(lambda: defaultdict(list))\n\n        for grad_op in gradient_ops:\n            # (1) check that inputs are valid\n            for s in grad_op.input:\n                self.CheckGradientOperatorInput(\n                    s, g_output, fwd_op_idx, locally_generated_blobs)\n\n            # (2) add outputs to the locally generated blobs\n            # If an output corresponds to the gradient of an input, we also\n            # record it to gradient_generators\n            locally_generated_blobs.extend([str(s) for s in grad_op.output])\n            for i, output in enumerate(grad_op.output):\n                input_index = GetIndexFromGradientList(g_input, output)\n                if input_index is not None:\n                    input_name = forward_op.input[input_index]\n                    input_version = in_versions[input_name]\n                    g = g_input[input_index]\n                    if type(g) is GradientSlice:\n                        # the output corresponds either to the indices or the\n                        # values of the sparse gradient. In either case we\n                        # create a (partial) SparseGradGenMeta. If necessary,\n                        # we'll merge indices and values generators\n                        # corresponding to the same gradient in step (3)\n                        if g.indices == output:\n                            m = SparseGradGenMeta(grad_op, i, None, 0, g)\n                        else:\n                            assert(g.values == output)\n                            m = SparseGradGenMeta(None, 0, grad_op, i, g)\n                        sparse_generators[input_name][input_version].append(m)\n                    else:\n                        self.gradient_generators[input_name][input_version] \\\n                            .append(GradGenMeta(\n                                grad_op, i, g))\n\n        # (3) merge indices and values generators for sparse gradients, and\n        # add them to gradient_generators\n        self.AppendSparseGenerators(sparse_generators)\n\n        # (4) for ops (e.g., Add, Sum, Sub) which have gradient outputs directly\n        # passed from inputs (not computed from gradient ops), we create an\n        # GradGenMeta with None grad_op and idx so that the gradient_generators\n        # knows where the gradients are coming from. This is needed for creating\n        # Sum op to accumulate the gradients from multiple parents.\n        for input_index, g in enumerate(g_input):\n            input_name = forward_op.input[input_index]\n            input_version = in_versions[input_name]\n            if not g:\n                continue\n            if type(g) is GradientSlice:\n                if str(g.indices) not in locally_generated_blobs and \\\n                        str(g.values) not in locally_generated_blobs:\n                    self.gradient_generators[input_name][input_version].append(\n                        SparseGradGenMeta(None, 0, None, 0, g))\n            else:\n                if str(g) not in locally_generated_blobs:\n                    self.gradient_generators[input_name][input_version].append(\n                        GradGenMeta(None, 0, g))\n\n        # Finally, for the gradients specified in g_input, we update the\n        # gradient frontier to reflect the input versions that the gradients\n        # correspond to.\n        for i, g in enumerate(g_input):\n            if g is not None:\n                input_name = forward_op.input[i]\n                input_version = in_versions[input_name]\n                self.gradient_frontier[input_name] = input_version\n\n    def _GetSumOpOutputName(self, generator, input_name):\n        def remove_suffix(s, suffix):\n            if s.endswith(suffix):\n                return s[:-len(suffix)]\n            return s\n\n        for g in generator:\n            if type(g) is GradGenMeta:\n                grad_op, idx, _ = g\n                if grad_op:\n                    return grad_op.output[idx]\n            else:\n                assert(type(g) is SparseGradGenMeta)\n                op_i, idx_i, op_v, idx_v, _ = g\n                if op_i:\n                    return remove_suffix(op_i.output[idx_i], '_indices')\n                if op_v:\n                    return remove_suffix(op_v.output[idx_v], '_values')\n\n        return input_name + '_grad'\n\n    def _SetSumOpsDeviceOption(self, sum_ops, generators):\n        # we already checked that device options are consistent so we can just\n        # use the first one we find\n        for generator in generators:\n            grad_op = generator.grad_op if type(generator) is GradGenMeta \\\n                else generator.grad_op_values or generator.grad_op_indices\n            if grad_op:\n                if grad_op.HasField('device_option'):\n                    for op in sum_ops:\n                        op.device_option.CopyFrom(grad_op.device_option)\n                break\n\n    def _DisambiguateGradOpOutput(self, grad_op, idx, cnt):\n        grad_op.output[idx] = (\n            '_' + grad_op.output[idx] + '_autosplit_{}'.format(cnt))\n        return grad_op.output[idx], cnt + 1\n\n    def _CheckSumOpsConflict(self, out_base_name, g):\n        if str(out_base_name) == str(g):\n            # TODO not sure what this message really means\n            raise RuntimeError(\n                'The gradient output of empty gradient op can not '\n                'be the same as the normal name of the current '\n                'input gradient.')\n\n    def _MakeDenseSumOps(self, generators, out_base_name):\n        sum_op_input = []\n        cnt = 0\n\n        assert len(generators) > 1\n\n        first_grad_op = True\n        for generator in generators:\n            grad_op, idx, g = generator\n            assert(type(g) is not GradientSlice)\n            if grad_op:\n                if first_grad_op:\n                    first_grad_op = False\n                    out = grad_op.output[idx]\n                else:\n                    out, cnt = self._DisambiguateGradOpOutput(grad_op, idx, cnt)\n                sum_op_input.append(out)\n            else:\n                self._CheckSumOpsConflict(out_base_name, g)\n                sum_op_input.append(str(g))\n\n        if out_base_name in sum_op_input:\n            # Sum inplace mode works only for the first input\n            # So we do a swap\n            idx = sum_op_input.index(out_base_name)\n            sum_op_input[0], sum_op_input[idx] = (\n                sum_op_input[idx], sum_op_input[0]\n            )\n        sum_ops = [CreateOperator(\n            \"Sum\",\n            [BlobReference(x) for x in sum_op_input],\n            BlobReference(out_base_name))]\n        return sum_ops, out_base_name\n\n    def _MakeSparseSumOps(self, generators, out_base_name):\n        indices_concat_input = []\n        values_concat_input = []\n        cnt_i = 0\n        cnt_v = 0\n\n        for generator in generators:\n            assert(type(generator) is SparseGradGenMeta)\n            op_i, idx_i, op_v, idx_v, g = generator\n            if op_i:\n                out, cnt_i = self._DisambiguateGradOpOutput(op_i, idx_i, cnt_i)\n                indices_concat_input.append(out)\n            else:\n                self._CheckSumOpsConflict(out_base_name, g.indices)\n                indices_concat_input.append(g.indices)\n            if op_v:\n                out, cnt_v = self._DisambiguateGradOpOutput(op_v, idx_v, cnt_v)\n                values_concat_input.append(out)\n            else:\n                self._CheckSumOpsConflict(out_base_name, g.values)\n                values_concat_input.append(g.values)\n\n        indices_concat_output = out_base_name + '_indices_concat'\n        indices_concat_split = out_base_name + '_indices_concat_split'\n        values_concat_output = out_base_name + '_values_concat'\n        values_concat_split = out_base_name + '_values_concat_split'\n        # Sum the given sparse representations by simply concatenating the\n        # indices (resp. values) tensors together. We don't do any deduplication\n        # of indices at this point. This will be done as needed before the\n        # optimizer is called\n        sum_ops = [\n            CreateOperator(\n                \"Concat\",\n                [BlobReference(x) for x in indices_concat_input],\n                [BlobReference(x) for x in\n                    [indices_concat_output, indices_concat_split]],\n                axis=0\n            ),\n            CreateOperator(\n                \"Concat\",\n                [BlobReference(x) for x in values_concat_input],\n                [BlobReference(x) for x in\n                    [values_concat_output, values_concat_split]],\n                axis=0\n            ),\n        ]\n        sum_op_output = GradientSlice(\n            indices=indices_concat_output,\n            values=values_concat_output,\n        )\n        return sum_ops, sum_op_output\n\n    def _MakeSumOps(self, input_name, input_version):\n        generators = self.gradient_generators[input_name][input_version]\n        out_base_name = self._GetSumOpOutputName(generators, input_name)\n        types = list(set(type(x) for x in generators))\n        assert(len(types) == 1)\n        if types[0] is GradGenMeta:\n            sum_ops, g = self._MakeDenseSumOps(generators, out_base_name)\n        else:\n            assert(types[0] is SparseGradGenMeta)\n            sum_ops, g = self._MakeSparseSumOps(generators, out_base_name)\n        self._SetSumOpsDeviceOption(sum_ops, generators)\n        return sum_ops, g\n\n    def _VerifyGradientGenerators(self, generator):\n        # (1) check if all gradients are of the same type. Aggregating a mix of\n        # sparse and dense gradients is not supported yet\n        if len({type(g) for g in generator}) > 1:\n            raise RuntimeError(\n                'Automatic aggregation of a mix of sparse and dense gradients '\n                'is not supported yet')\n\n        # If for all the operators that used the operator, none or only one\n        # produced the gradient, then no additional sum needs to be carried\n        # out.\n        if len(generator) < 2:\n            return False\n\n        all_gradient_names = []\n        all_device_options = []\n        for g in generator:\n            if type(g) is GradGenMeta:\n                if g.grad_op:\n                    all_gradient_names.append(g.gradient)\n                    all_device_options.append(g.grad_op.device_option)\n            else:\n                assert(type(g) is SparseGradGenMeta)\n                if g.grad_op_indices:\n                    all_device_options.append(g.grad_op_indices.device_option)\n                if g.grad_op_values:\n                    all_device_options.append(g.grad_op_values.device_option)\n                    all_gradient_names.append(g.gradient.values)\n\n        # Check if all grad op device options are the same.\n        if len(all_device_options) >= 2 and not all(\n                device_option_equal(d, all_device_options[0])\n                for d in all_device_options[1:]):\n            raise RuntimeError('Unexpected behavior: not all grad ops '\n                               'have the same device option.')\n        return True\n\n    def DoGradientAccumulation(self, fwd_op_idx):\n        \"\"\"For each input name in the forward op, check if we will need to\n        add gradient accumulation. If so, do gradient accumulation and return\n        the list of gradient operators.\n\n        The criteria for doing gradient accumulation is:\n        (1) the specific input version has been used by multiple operators.\n        (2) the current fwd_op_idx is the first to use that input, i.e. in the\n            backward pass, is the last to optionally generate the gradient for\n            the op.\n        (3) For the operators that used the input, their gradient operators\n            have generated more than 1 gradient.\n\n        When accumulating operators, our current solution is to rename all the\n        created gradients with an internal intermediate name, and then add a\n        Sum() operator that adds up all the gradients. This may use more memory\n        due to intermediate storage, but is usually the fastest approach as one\n        can do one single sum for multiple intermediate gradients.\n        \"\"\"\n        forward_op, in_versions, out_versions = self.ssa[fwd_op_idx]\n        additional_sum_ops = []\n        grad_map = {}\n        for _i, input_name in enumerate(set(forward_op.input)):\n            input_version = in_versions[input_name]\n            input_usage = self.input_usages[input_name][input_version]\n            if (len(input_usage) <= 1 or fwd_op_idx != input_usage[0]):\n                # We do not need to do gradient accumulation yet.\n                continue\n            generator = self.gradient_generators[input_name][input_version]\n            try:\n                if not self._VerifyGradientGenerators(generator):\n                    continue\n            except RuntimeError as err:\n                raise RuntimeError(\n                    \"Gradients for param ''{}'' failed to verify: {}\".format(\n                        input_name,\n                        err\n                    )\n                )\n\n            # Finally, let's create the sum operator.\n            sum_ops, g = self._MakeSumOps(input_name, input_version)\n            additional_sum_ops.extend(sum_ops)\n            grad_map[input_name] = g\n        return additional_sum_ops, grad_map\n\n    def _AppendAutoGradGenerator(self, y, grad, autograd_op):\n        # Gradient here is not sparse  as it was generated by\n        # a ConstantFill operator. Autogeneration for sparse gradients is\n        # not supported\n        generator = GradGenMeta(\n            autograd_op, 0 if autograd_op else None, str(grad))\n\n        self.gradient_generators[str(y)][self.frontier[str(y)]].append(\n            generator)\n\n\n    def _GetInitGradients(self, ys):\n        input_to_grad = {}\n        gradient_ops = []\n\n        for y, g in viewitems(ys):\n            autograd_op = None\n            if g is None:\n                autograd_op = CreateOperator(\n                    \"ConstantFill\", [y], [str(y) + \"_autogen_grad\"],\n                    value=1.0)\n                gradient_ops.append(autograd_op)\n                g = autograd_op.output[0]\n            # Since the C++ gradient registry does not have notion of\n            # NameScopes, we will convert all references to strings.\n            input_to_grad[str(y)] = (\n                GradientSlice(str(g[0]), str(g[1]))\n                if isinstance(g, GradientSlice) else str(g))\n            # Autogenerated gradients are assumed to be provided for the last\n            # input version\n            if autograd_op is not None:\n                self._AppendAutoGradGenerator(y, g, autograd_op)\n\n        return input_to_grad, gradient_ops\n\n    def _GenerateGradientsForForwardOp(\n            self, forward_op_idx, input_to_grad):\n        new_input_to_grad = {}\n        gradient_ops = []\n        forward_op, in_versions, out_versions = self.ssa[forward_op_idx]\n        g_output = list(\n            input_to_grad.get(name, None) for name in forward_op.output)\n\n        if not all(g is None for g in g_output) or (\n                forward_op.type == \"ZeroGradient\"):\n            gradient_ops, g_input = GradientRegistry.GetGradientForOp(\n                forward_op, g_output)\n            # Check if the gradient operators are legal, and update\n            # gradient_generators and gradient_frontier\n            self.BuildGradientGenerators(\n                forward_op_idx, gradient_ops, g_output, g_input)\n            # Record the gradient map to all_input_to_grad.\n            for name, grad in zip(forward_op.input, g_input):\n                # Do not overwrite an existing gradient with a None\n                # unless the input is also an output of the op, since\n                # we update the blob version when blob is output of an\n                # operator.\n                if grad is not None or \\\n                    name not in input_to_grad or \\\n                        name in list(forward_op.output):\n                    new_input_to_grad[name] = grad\n\n        return new_input_to_grad, gradient_ops\n\n    def GetBackwardPass(self, ys):\n        \"\"\"Gets the backward pass that computes the derivatives of given blobs.\n\n        Inputs:\n          ys: a list or a dictionary specifying what blobs we want to compute\n              derivatives of. If the input is a list, we will automatically\n              generate their gradients with all-one values; if the input is a\n              dictionary, for any dictionary entries that are not None, we will\n              take the corresponding blobs as their gradients; for all those\n              that are None, we will auto-fill them with 1.\n        \"\"\"\n        if isinstance(ys, list):\n            ys = dict((y, None) for y in ys)\n        elif not isinstance(ys, dict):\n            raise TypeError(\"ys should either be a list or a dict.\")\n\n        # Set the gradient frontier with the initialized external\n        # gradients.\n        for y in viewkeys(ys):\n            self.gradient_frontier[y] = self.frontier[y]\n            self.input_usages[str(y)][self.frontier[str(y)]].append(\n                len(self.ssa))\n\n        all_input_to_grad, all_gradient_ops = self._GetInitGradients(ys)\n\n        # (2) Now, after having the virtual play above, we now play the ops\n        # backwards, creating the gradients along the path. Note that although\n        # we are playing it backwards, we cannot refer to variables that are\n        # at a version older than current_versions because it is already been\n        # overwritten.\n        for forward_op_idx in reversed(range(len(self.ssa))):\n            input_to_grad, gradient_ops = self._GenerateGradientsForForwardOp(\n                forward_op_idx, all_input_to_grad)\n            all_input_to_grad.update(input_to_grad)\n            all_gradient_ops += gradient_ops\n\n            # If there are multiple use blobs, do gradient accumulation.\n            additional_sum_ops, grad_map = self.DoGradientAccumulation(\n                forward_op_idx)\n            # This line is so that if in an accumulation some of the operators\n            # have not produced gradients, they still do not overwrite the\n            # general all_input_to_grad map.\n            all_input_to_grad.update(grad_map)\n            all_gradient_ops += additional_sum_ops\n\n        # (3) Post-processing.\n        # After we have done computation for each op, we now have the gradient\n        # operators ready. For the output map, we will convert everything to\n        # BlobReferences for easier handling in python.\n        all_input_to_grad_out = {}\n        for key, val in viewitems(all_input_to_grad):\n            if val is not None:\n                if (isinstance(val, string_types) or\n                        isinstance(val, binary_type)):\n                    grad_out = BlobReference(val)\n                else:\n                    grad_out = GradientSlice(BlobReference(val[0]),\n                                             BlobReference(val[1]))\n                all_input_to_grad_out[BlobReference(key)] = grad_out\n        return all_gradient_ops, all_input_to_grad_out\n\n\nclass GradientRegistry(object):\n    \"\"\"GradientRegistry holds the mapping from operators to their gradients.\"\"\"\n    gradient_registry_ = {}\n\n    @classmethod\n    def RegisterGradient(cls, op_type):\n        \"\"\"A decorator for registering gradient mappings.\"\"\"\n\n        def Wrapper(func):\n            cls.gradient_registry_[op_type] = func\n            return func\n\n        return Wrapper\n\n    @classmethod\n    def _GetGradientForOpCC(cls, op_def, g_output):\n        # TODO(tulloch) - Propagate GradientWrapper up through the stack.\n        def from_untyped(grad):\n            if grad is None:\n                w = C.GradientWrapper()\n                assert w.is_empty()\n                return w\n            try:\n                (indices, values) = grad\n                w = C.GradientWrapper()\n                w.indices = indices\n                w.values = values\n                assert w.is_sparse()\n                return w\n            except ValueError:\n                w = C.GradientWrapper()\n                w.dense = grad\n                assert w.is_dense()\n                return w\n\n        g_output = [from_untyped(grad) for grad in g_output]\n        grad_defs_str, g_input = C.get_gradient_defs(\n            op_def.SerializeToString(), g_output)\n\n        def to_untyped(grad_wrapper):\n            if grad_wrapper.is_empty():\n                return None\n            if grad_wrapper.is_sparse():\n                return GradientSlice(grad_wrapper.indices, grad_wrapper.values)\n            assert grad_wrapper.is_dense()\n            return grad_wrapper.dense\n\n        g_input = [to_untyped(grad_wrapper) for grad_wrapper in g_input]\n        grad_defs = []\n        for grad_def_str in grad_defs_str:\n            grad_def = caffe2_pb2.OperatorDef()\n            grad_def.ParseFromString(grad_def_str)\n            grad_defs.append(grad_def)\n        return grad_defs, g_input\n\n    @classmethod\n    def GetGradientForOp(cls, op, g_output):\n        try:\n            gradient_ops, g_input = cls._GetGradientForOpCC(op, g_output)\n        except Exception as e:\n            # Not supported in C++; will try python registration next.\n            if op.type in cls.gradient_registry_:\n                gradient_ops, g_input = cls.gradient_registry_[op.type](\n                    op, g_output\n                )\n            else:\n                raise Exception(\n                    \"Exception when creating gradient for [{}]:{}.\\nOp: \\n{}\".\n                    format(op.type, e, str(op))\n                )\n\n        if gradient_ops is None:\n            return [], g_input\n        if type(gradient_ops) is not list:\n            gradient_ops = [gradient_ops]\n        return gradient_ops, g_input\n\n    @classmethod\n    def GetBackwardPass(cls, operators, ys, ys_generate_gradient=False):\n        \"\"\"Gets the backward pass for the list of operators.\n\n        Args:\n            operators: a list of operators constituting the forward pass.\n            ys: a list or a dictionary specifying what blobs we want to compute\n                derivatives of. If the input is a list, we will automatically\n                generate their gradients with all-one values; if the input is a\n                dictionary, for any dictionary entries that are not None, we'll\n                take the corresponding blobs as their gradients; for all those\n                that are None, we will auto-fill them with 1.\n        Returns:\n            gradient_ops: a list of gradient operators to run.\n            all_input_to_grads: a map from input to their corresponding\n                gradients.\n        \"\"\"\n        ir = IR(operators)\n        return ir.GetBackwardPass(ys)\n\n\nGradientRegistry.RegisterGradient('Do')(gen_do_gradient)\nGradientRegistry.RegisterGradient('If')(gen_if_gradient)\nGradientRegistry.RegisterGradient('While')(gen_while_gradient)\n\n\ndef get_ssa(net, blob_versions=None):\n    \"\"\"\n    Given a net, return a structure containing the version of each input and\n    output blob used by each operator.\n\n    Args:\n        net:            either a Net or a NetDef\n        blob_versions:  (optional) map with current version number for given\n                        blob names. If not provided or blob not found, start\n                        from version 0.\n    Returns:\n        Tuple (ssa, blob_versions)\n        ssa:            list of tuples (versioned_inputs, versioned_outputs)\n                        for each op in the net. A versioned input is a tuple\n                        (blob_name, version).\n        blob_versions:  updated map with latest version of each blob found in\n                        the net.\n    \"\"\"\n    proto = net.Proto() if isinstance(net, Net) else net\n    assert isinstance(proto, caffe2_pb2.NetDef)\n    if blob_versions is None:\n        blob_versions = {}\n    if isinstance(net, list):\n        return [get_ssa(n, blob_versions) for n in net], blob_versions\n    for i in proto.external_input:\n        if i not in blob_versions:\n            blob_versions[str(i)] = 0\n    ssa = []\n    for op in proto.op:\n        if not proto.external_input:\n            for i in op.input:\n                if i not in blob_versions:\n                    blob_versions[i] = 0\n        inputs = [(str(i), blob_versions.get(str(i), 0)) for i in op.input]\n        for o in op.output:\n            blob_versions[str(o)] = blob_versions.get(str(o), 0) + 1\n        outputs = [(str(o), blob_versions[str(o)]) for o in op.output]\n        ssa.append((inputs, outputs))\n    return ssa, blob_versions\n\n\ndef get_undefined_blobs(ssa):\n    \"\"\"\n    Given a ssa in the format produced by get_ssa(), return a set of blobs that\n    are used before they are defined, which corresponds to inputs at version 0.\n    \"\"\"\n    undef_blobs = set()\n    for inputs, _outputs in ssa:\n        undef_blobs |= set(name for (name, ver) in inputs if ver == 0)\n    return undef_blobs\n\n\ndef get_output_producers(ssa):\n    \"\"\"\n    Given a ssa in the format produced by get_ssa(), returns a map from\n    versioned blob into the operator index that produces that version of\n    the blob. A versioned blob is a tuple (blob_name, version).\n    \"\"\"\n    producers = {}\n    for i, (_inputs, outputs) in enumerate(ssa):\n        for o in outputs:\n            producers[o] = i\n    return producers\n\n\ndef get_op_ids_in_path(ssa, blob_versions, inputs, outputs):\n    \"\"\"\n    Given a ssa and blob_versions as produced by get_ssa(), returns the list\n    of op indices that are necessary in order to generate the blobs in\n    `outputs`, given blobs in `inputs`.\n    Consider that the `inputs` are given in their latest version.\n    \"\"\"\n    inputs_set = set((str(i), blob_versions[str(i)]) for i in inputs)\n    producers = get_output_producers(ssa)\n    queue = [(str(o), blob_versions[str(o)]) for o in outputs]\n    used_op_ids = set()\n    while len(queue) > 0:\n        o = queue.pop()\n        if (o not in inputs_set) and (o in producers):\n            op_id = producers[o]\n            if op_id not in used_op_ids:\n                used_op_ids |= {op_id}\n                inputs, _ = ssa[op_id]\n                queue.extend(inputs)\n    return sorted(used_op_ids)\n\n\ndef recurrent_network_op_remap(op, prefix, blob_remap):\n    \"\"\"\n    Parameters\n    ----------\n    op : Caffe2 operator (RecurrentNetworkOp or RecurrentNetworkGradientOp).\n    prefix: this argument is not used in this function, just for legacy support.\n    blob_remap : Dictionary that represents the map from old blob name to new.\n\n    Updates blob names in arguments of RecurrentNetworkOp and\n    RecurrentNetworkGradientOp to conform to cloned input and output of both\n    operators and also makes sure names of locally generated blobs in arguments\n    have the same prefix as the input and output of the operators.\n    \"\"\"\n\n    def get_remapped_str(blob_str):\n        if isinstance(blob_str, binary_type):\n            blob_str = blob_str.decode('utf-8')\n        return blob_remap.get(blob_str, blob_str).encode('utf-8')\n\n    for argument in op.arg:\n        if len(argument.strings) > 0:\n            for i in range(len(argument.strings)):\n                argument.strings[i] = get_remapped_str(argument.strings[i])\n        elif argument.name == 'timestep':\n            argument.s = get_remapped_str(argument.s)\n        elif argument.name.endswith('step_net'):\n            # argument is a proto\n            remap_proto(argument, blob_remap)\n\n\ndef control_op_remap(op, prefix, blob_remap):\n    net_arg_names = []\n    if op.type == \"If\":\n        net_arg_names = ['then_net', 'else_net']\n    else:\n        net_arg_names = ['loop_net', 'cond_net']\n    for argument in op.arg:\n        if argument.name in net_arg_names:\n            assert argument.n, \\\n                \"Expected non empty net in \" + op.type + \"'s \" + argument.name + \" argument\"\n            subnet = Net(argument.n)\n            remapped_subnet = subnet.Clone(\n                name=(subnet._net.name if subnet._net.name else '') + '_remapped',\n                blob_remap=blob_remap)\n            argument.n.CopyFrom(remapped_subnet.Proto())\n\n\nDEFAULT_REMAP_FUNCS = {\n    'RecurrentNetwork': recurrent_network_op_remap,\n    'RecurrentNetworkGradient': recurrent_network_op_remap,\n    'If': control_op_remap,\n    'While': control_op_remap,\n}\n\n\ndef remap_proto(argument, blob_remap):\n    subnet = Net(argument.n)\n\n    cloned_sub_net = subnet.Clone(\n        'cloned_sub_net',\n        blob_remap,\n    )\n\n    argument.n.CopyFrom(cloned_sub_net.Proto())\n\n\ndef clone_and_bind_net(net, name, prefix, blob_remap=None, inputs=None,\n                       keep_schema=True):\n    \"\"\"\n    Clone the given Net, binding its input schema to the given `inputs` record.\n    Blob names defined by the net are prepended with the given `prefix`.\n\n    Args:\n        net:        the net to clone\n        name:       the name of the new net\n        prefix:     the prefix to append to local blobs\n        blob_remap: (optional) dict with additional blob name remapping.\n        inputs:     (optional) input record that will provide actual input\n                    values for the cloned net. Must be compatible with the\n                    net's input schema or be a strict superset of it\n        keep_schema: by default (True), the original schema will be kept and\n                     remapped accordingly. otherwise, the schema will be set as\n                     inputs or left empty if inputs is not given.\n    Returns:\n        Tuple (cloned_net, blob_remap)\n        clone_net:  the cloned Net\n        blob_remap: a map from original blob names into remapped blob names\n    \"\"\"\n    from caffe2.python import schema\n    assert isinstance(net, Net)\n    if blob_remap is None:\n        blob_remap = {}\n    if inputs is not None:\n        assert isinstance(inputs, schema.Field)\n        original = net.input_record()\n        assert original is not None\n        # TODO(azzolini): improve schema type checking\n        diff = set(original.field_names()) - set(inputs.field_names())\n        assert len(diff) == 0, (\n            \"Schemas don't match, extra fields {diff} found in the net {name}. \"\n            \"original: {original}; inputs: {inputs}\"\n            .format(\n                diff=diff, name=net.Name(), original=original.field_names(),\n                inputs=inputs.field_names()\n            )\n        )\n        original_mapping = dict(zip(original.field_names(),\n                                    original.field_blobs()))\n        for fn, fb in zip(inputs.field_names(), inputs.field_blobs()):\n            if fn in original_mapping:\n                blob_remap[str(original_mapping[fn])] = str(fb)\n    proto = net.Proto()\n    ssa, blob_versions = get_ssa(proto)\n    undef_blobs = get_undefined_blobs(ssa)\n\n    for blob in viewkeys(blob_versions):\n        if blob in blob_remap:\n            continue\n        elif blob in undef_blobs:\n            blob_remap[blob] = blob\n        else:\n            blob_remap[blob] = prefix + blob\n    cloned_net = net.Clone(name, blob_remap, keep_schema=keep_schema)\n    if not keep_schema and inputs:\n        cloned_net.set_input_record(inputs)\n    return cloned_net, blob_remap\n\n\ndef _get_blob_ref(blob_name_or_ref):\n    return (\n        blob_name_or_ref if isinstance(input, BlobReference)\n        else BlobReference(blob_name_or_ref)\n    )\n\n\ndef _recover_record_by_prefix(names, prefix=''):\n    \"\"\"\n    Tries to recover record by taking a subset of blob names with\n    a given prefix name and interpreting them as schema column names\n    \"\"\"\n    from caffe2.python import schema\n    column_names = [name[len(prefix):] for name in names\n                    if name.startswith(prefix)]\n    if not column_names:\n        return None\n    return schema.from_column_list(\n        column_names,\n        col_blobs=[_get_blob_ref(prefix + name) for name in column_names])\n\n\nclass Net(object):\n    _net_names_used = set()\n    operator_registry_ = {}\n\n    @staticmethod\n    def current_prefix():\n        from caffe2.python.net_builder import NetBuilder\n        builder = NetBuilder.current(required=False)\n        return builder.name if builder else ''\n\n    @staticmethod\n    def _get_next_net_name(basename):\n        name = basename = '/'.join(\n            x for x in [Net.current_prefix(), basename] if x\n        )\n        next_idx = 1\n        while name in Net._net_names_used:\n            name = basename + '_' + str(next_idx)\n            next_idx += 1\n        Net._net_names_used |= set([name])\n        return name\n\n    def __init__(self, name_or_proto):\n        \"\"\"\n        Create a Net.\n        Args:\n            name_or_proto:  If a NetDef is provided, clone it. Otherwise,\n                            create an empty net with the given name.\n        \"\"\"\n        self._input_record = None\n        self._output_record = None\n        # Register blobs so that it's guaranteed that different calls to\n        # NextBlob/NextScopedBlob always return blobs with different names\n        self._registered_blob_names = set()\n        self._recreate_lookup_tables = False\n        self._op_outputs = set()\n        self._external_input_map = set()\n        self._attr_dict = defaultdict(list)\n        if type(name_or_proto) is caffe2_pb2.NetDef:\n            proto = name_or_proto\n            # We rae initializing a network by a NetDef. In this case, we will\n            # initialize our network with the given netdef.\n            self._net = caffe2_pb2.NetDef()\n            self._net.CopyFrom(proto)\n\n            existing_outputs = [list(op.output) for op in self._net.op]\n\n            self._external_input_map.update(list(self._net.external_input))\n\n            # Set the next name index properly.\n            existing_names = set(\n                sum(\n                    [list(op.input) for op in self._net.op], []\n                ) + sum(\n                    existing_outputs, []\n                )\n            )\n            for outs in existing_outputs:\n                self._op_outputs.update(outs)\n\n            prefix_len = len(self._net.name + '_blob_')\n            autogen_indices = []\n            for s in existing_names:\n                if s.startswith(self._net.name + '_blob_'):\n                    try:\n                        autogen_indices.append(int(s[prefix_len]))\n                    except ValueError:\n                        pass\n            if len(autogen_indices):\n                self._next_name_index = max(autogen_indices) + 1\n            else:\n                self._next_name_index = 0\n            name = self._net.name\n        else:\n            name = name_or_proto\n            self._net = caffe2_pb2.NetDef()\n            self._next_name_index = 0\n\n        # make sure that this net name hasn't been used before\n        self._net.name = Net._get_next_net_name(name)\n\n    def AppendNet(self, net):\n        assert isinstance(net, Net)\n        for i in net.Proto().external_input:\n            if (\n                i not in self.Proto().external_input and\n                i not in self._op_outputs\n            ):\n                self.Proto().external_input.append(i)\n\n        self.Proto().external_output.extend(\n            [\n                o for o in net.Proto().external_output\n                if o not in self.Proto().external_output\n            ]\n        )\n        self._ExtendOps(net.Proto().op)\n        return self\n\n    def LogInfo(self, *msg_or_blobs):\n        for msg_or_blob in msg_or_blobs:\n            if not isinstance(msg_or_blob, BlobReference):\n                blob = self.GivenTensorStringFill(\n                    [], self.NextName('log'),\n                    shape=[], values=[msg_or_blob])\n            else:\n                blob = msg_or_blob\n            self.Print(blob, [])\n\n    def add_attribute(self, name, obj):\n        \"\"\"\n        Add `obj` to the list of attributes in this net under the given `name`.\n        Attributes are user-defined objects and have no pre-defined semantics.\n        \"\"\"\n        self._attr_dict[name].append(obj)\n\n    def get_attributes(self, name):\n        \"\"\"\n        Returns the list of attributes in this net for a given `name`.\n        Attributes are user-defined objects added with `add_attribute'.\n        \"\"\"\n        return self._attr_dict.get(name, [])\n\n    def set_rand_seed(self, seed=100, sequence_seed=True, seed_on_op_def=False):\n        \"\"\"\n        Adds a random seed to each op in the net.\n        If sequence_seed is set, the i-th op has rand_seed=`seed + i`\n        If seed_on_op_def is set, the op rand_seed=hash(str(op))\n        sequence_seed and seed_on_op_def cannot be both set to True.\n        \"\"\"\n        assert not (sequence_seed and seed_on_op_def), (\n            'sequence_seed and seed_on_op_def cannot be both set to True.')\n        for i, op in enumerate(self.Proto().op):\n            if sequence_seed:\n                curr_seed = seed + i\n            elif seed_on_op_def:\n                curr_seed = hash(str(op) + str(seed)) % np.iinfo(np.uint32).max\n            else:\n                curr_seed = seed\n            op.device_option.random_seed = curr_seed\n\n    def Name(self):\n        return self._net.name\n\n    def __str__(self):\n        return self.Name()\n\n    def Const(self, array, blob_out=None, dtype=None):\n        if isinstance(array, bool):\n            return self.ConstantFill(\n                [],\n                blob_out or 1,\n                dtype=DataType.BOOL,\n                value=array)\n\n        if dtype is None:\n            array = np.array(array)\n        else:\n            array = np.array(array, dtype=dtype)\n\n        def do_set(operator):\n            return operator(\n                [],\n                blob_out or 1,\n                shape=array.shape,\n                values=array.flatten().tolist())\n\n        if array.dtype == np.int32:\n            return do_set(self.GivenTensorIntFill)\n        elif array.dtype == np.int64:\n            return do_set(self.GivenTensorInt64Fill)\n        elif array.dtype == np.str:\n            return do_set(self.GivenTensorStringFill)\n        elif array.dtype == np.bool:\n            return do_set(self.GivenTensorBoolFill)\n        else:\n            return do_set(self.GivenTensorFill)\n\n    def BlobIsDefined(self, blob):\n        \"\"\"\n        Returns true if the given BlobReference is produced as output of\n        an operator in this net, or if it is provided as an external input.\n        \"\"\"\n        if self._recreate_lookup_tables:\n            self._RecreateLookupTables()\n        name = str(blob)\n        return (name in self._op_outputs) or (name in self._external_input_map)\n\n    def UsesBlob(self, blob):\n        \"\"\"\n        Returns true iff the given BlobReference is used by any operator\n        or this net, or if it is one of the external inputs of the net.\n        \"\"\"\n        blob_name = str(blob)\n        for op in self._net.op:\n            for input in op.input:\n                if input == blob_name:\n                    return True\n        return blob_name in self._external_input_map\n\n    def UsedBlobNames(self):\n        \"\"\"\n        Returns a set of blob names used in the net\n        \"\"\"\n        blob_names = set()\n        for op in self._net.op:\n            blob_names |= set(op.input)\n            blob_names |= set(op.output)\n        if self._net.external_input:\n            blob_names |= set(self._net.external_input)\n        if self._net.external_output:\n            blob_names |= set(self._net.external_output)\n        return blob_names\n\n    def GetBlobRef(self, blob_name):\n        \"\"\"\n        Given the name of a blob produced by this net, return a BlobReference\n        to it. If the blob is not produced by any op in this net,\n        raises KeyError.\n        \"\"\"\n        blob_name = str(blob_name)\n        if not self.BlobIsDefined(blob_name):\n            raise KeyError('Net does not define blob %s' % blob_name)\n        return BlobReference(blob_name, self)\n\n    def Clone(\n        self,\n        name,\n        blob_remap=None,\n        op_id_mask=None,\n        remap_funcs=None,\n        keep_schema=True\n    ):\n        \"\"\"\n        Clone this net.\n        Args:\n            name:        name of the cloned net\n            blob_remap:  optional map with list of blob names to replace\n            op_id_mask:  optional list of operator indices to include in\n                         the cloned net. If not provided, all ops are included.\n        \"\"\"\n        orig_remap_funcs = {} if remap_funcs is None else remap_funcs\n        # by default we want to put RecurrentNetworkOp and\n        # RecurrentNetworkGradientOp into remap_funcs, as these two operators\n        # also take blobs and proto into the arguments.\n        remap_funcs = DEFAULT_REMAP_FUNCS.copy()\n        remap_funcs.update(orig_remap_funcs)\n        proto = self._net\n        new_proto = caffe2_pb2.NetDef()\n        new_proto.CopyFrom(proto)\n        new_proto.name = name\n\n        if blob_remap is None:\n            blob_remap = {}\n        if op_id_mask is None:\n            op_id_mask = list(range(0, len(proto.op)))\n\n        def get_remapped_str(blob):\n            blob_str = str(blob)\n            return str(blob_remap.get(blob_str, blob_str))\n\n        def remap_list(proto_list):\n            new_list = [get_remapped_str(b) for b in proto_list]\n            del proto_list[:]\n            proto_list.extend(new_list)\n\n        def remap_op(op):\n            new_op = caffe2_pb2.OperatorDef()\n            new_op.CopyFrom(op)\n            remap_list(new_op.input)\n            remap_list(new_op.output)\n            if new_op.type in remap_funcs:\n                remap_funcs[new_op.type](\n                    new_op,\n                    (name + '/') if name else '',\n                    blob_remap,\n                )\n            return new_op\n\n        del new_proto.op[:]\n        new_proto.op.extend([remap_op(proto.op[op_id]) for op_id in op_id_mask])\n        remap_list(new_proto.external_input)\n        remap_list(new_proto.external_output)\n        new_net = Net(new_proto)\n\n        if keep_schema:\n            from caffe2.python import schema\n            if self._input_record:\n                new_net._input_record = schema.from_blob_list(\n                    self._input_record,\n                    [\n                        BlobReference(get_remapped_str(blob), net=new_net)\n                        for blob in self._input_record.field_blobs()\n                    ],\n                )\n            if self._output_record:\n                new_net._output_record = schema.from_blob_list(\n                    self._output_record,\n                    [\n                        BlobReference(get_remapped_str(blob), net=new_net)\n                        for blob in self._output_record.field_blobs()\n                    ],\n                )\n\n        new_net._attr_dict.update(self._attr_dict)\n        return new_net\n\n    def ClonePartial(self, name, inputs, outputs, remap_funcs=None):\n        \"\"\"\n        Clone this net, including only ops that are necessary in order to\n        compute `outputs` given `inputs`. Return references to the cloned\n        outputs. Internal blobs (blobs that are produced and consumed inside\n        the net but not used as outputs) will be remapped to avoid name\n        conflict.\n\n        Args:\n            name:    the name of the cloned net\n            inputs:  map where the keys correspond to BlobReferences in the\n                     original net, and the values correspond to external inputs\n                     in the partially cloned net. If `inputs` is a list, don't\n                     remap input names.\n            outputs: outputs to be produced by the cloned net.\n\n        Returns:\n            Tuple (new_net, new_outputs)\n                new_net:       a new Net object.\n                new_outputs:   list of BlobReferences corresponding to the\n                               outputs produced by new_net.\n        \"\"\"\n        input_is_pair_list = isinstance(inputs, list) and all(\n            isinstance(i, tuple) and len(i) == 2 for i in inputs)\n        inputs = (\n            inputs if isinstance(inputs, (dict, OrderedDict)) else\n            OrderedDict(inputs) if input_is_pair_list else\n            OrderedDict(zip(inputs, inputs)))\n        for output in outputs:\n            assert self.BlobIsDefined(output)\n        input_names = {str(k): str(v) for k, v in viewitems(inputs)}\n        output_names = [str(o) for o in outputs]\n        proto = self._net\n        blob_versions = {str(i): 0 for i in inputs}\n        ssa, blob_versions = get_ssa(proto, blob_versions)\n        used_op_ids = get_op_ids_in_path(ssa, blob_versions, inputs, outputs)\n        disallowed_op_ids = get_op_ids_in_path(ssa, blob_versions, [], inputs)\n        assert len(set(used_op_ids) & set(disallowed_op_ids)) == 0, (\n            'Cannot partially clone net: some of the ops required would ' +\n            'generate the given input.')\n\n        sub_ssa = [op for i, op in enumerate(ssa) if i in used_op_ids]\n        undef_blobs = get_undefined_blobs(sub_ssa) - set(viewkeys(input_names))\n        prefix = (name + '/') if name else ''\n\n        def remap(blob_name):\n            if blob_name in input_names:\n                return input_names[blob_name]\n            elif blob_name in undef_blobs:\n                return blob_name\n            else:\n                return prefix + blob_name\n\n        blob_mapping = {b: remap(b) for b in viewkeys(blob_versions)}\n        new_net = self.Clone(name, blob_mapping, used_op_ids, remap_funcs)\n        new_in = [\n            blob_mapping[i] for i in viewkeys(input_names)] + list(undef_blobs)\n        new_out = [blob_mapping[o] for o in output_names]\n        del new_net.Proto().external_input[:]\n        new_net.Proto().external_input.extend(new_in)\n        new_net._external_input_map = set(list(new_in))\n        del new_net.Proto().external_output[:]\n        new_net.Proto().external_output.extend(new_out)\n        return new_net, [new_net.GetBlobRef(o) for o in new_out]\n\n    def Proto(self):\n        self._InvalidateLookupTables()\n        return self._net\n\n    def PopulateProtoWithFileName(self):\n        net_tb = workspace.operator_tracebacks.get(self.Name(), None)\n        if net_tb is not None:\n            for idx, op in enumerate(self.Proto().op):\n                if idx in net_tb:\n                    op.name = ':'.join(map(str, net_tb[idx][0]))\n\n    def NextScopedBlob(self, prefix='unnamed'):\n        \"\"\"Return the blob that has not been defined or registered in the\n        current net. It returns `ScopedBlobReference(prefix)`, if it's valid,\n        otherwise `ScopedBlobReference(prefix) + '_auto_' + ?`. Different calls\n        is guaranteed to return blob with different names.\n        \"\"\"\n        output_blob_base = ScopedName(prefix)\n        return self.NextBlob(output_blob_base)\n\n    def NextBlob(self, prefix='unnamed'):\n        \"\"\"Return the blob that has not been defined or registered in the\n        current net. It returns `BlobReference(prefix)`, if it's valid,\n        otherwise `BlobReference(prefix) + '_auto_' + ?`. Different calls\n        is guaranteed to return blob with different names.\"\"\"\n        output_blob_base = BlobReference(prefix)\n        output_blob = output_blob_base\n        index = 0\n        while str(output_blob) in self._registered_blob_names or (\n                self.BlobIsDefined(output_blob)):\n            output_blob = output_blob_base + '_auto_' + str(index)\n            index += 1\n\n        self._registered_blob_names.add(str(output_blob))\n        return output_blob\n\n    def NextName(self, prefix=None, output_id=None):\n        \"\"\"Returns the next name to be used, if you do not want to explicitly\n        name your blob. [Deprecated, use NextBlob, NextScopedBlob instead]\"\"\"\n        if prefix:\n            output_name_base = self._net.name + '/' + prefix\n            output_name = output_name_base\n            if output_id is not None:\n                output_name += ':' + str(output_id)\n            index = 2\n            while self.BlobIsDefined(str(ScopedBlobReference(output_name))):\n                output_name = output_name_base + '_' + str(index)\n                if output_id is not None:\n                    output_name += ':' + str(output_id)\n                index += 1\n        else:\n            output_name = self._net.name + '_blob_' + str(self._next_name_index)\n            self._next_name_index += 1\n        return str(output_name)\n\n    def _ExtendOps(self, new_ops):\n        self._net.op.extend(new_ops)\n        for op in new_ops:\n            self._op_outputs.update([text_type(o) for o in op.output])\n\n    def _CheckLookupTables(self):\n        '''\n        Called from unit tests to validate the internal lookup tables\n        match the protobuf contents.\n        '''\n        test_op_outputs = set()\n        for op in self._net.op:\n            for o in op.output:\n                test_op_outputs.add(o)\n\n        test_external_inp = set()\n        for inp in self._net.external_input:\n            test_external_inp.add(inp)\n\n        assert test_op_outputs.difference(self._op_outputs) == set()\n        assert test_external_inp.difference(self._external_input_map) == set()\n\n    def _InvalidateLookupTables(self):\n        self._recreate_lookup_tables = True\n\n    def _RecreateLookupTables(self):\n        self._op_outputs = set()\n        for op in self._net.op:\n            for o in op.output:\n                self._op_outputs.add(o)\n\n        self._external_input_map = set()\n        for inp in self._net.external_input:\n            self._external_input_map.add(inp)\n\n        self._recreate_lookup_tables = False\n\n    def AddGradientOperators(self, ys, skip=0):\n        \"\"\"Add the gradient for operators in the net.\n\n        Inputs:\n          ys: a list or a dictionary specifying what blobs we want to compute\n              derivatives of. If the input is a list, we will automatically\n              generate their gradients with all-one values; if the input is a\n              dictionary, for any dictionary entries that are not None, we will\n              take the corresponding blobs as their gradients; for all those\n              that are None, we will auto-fill them with 1.\n          skip: skips the first n operators. This is provided mainly because a\n              lot of nets may use the first few operators for data generation\n              like stuff which really do not need to have gradients.\n\n        Outputs:\n          returns a map from the blob name in the input network to a blob\n          containing gradient or a GradientSlice in case of sparse gradient\n\n        Currently, this is hard-coded for float operators if there are branches\n        (i.e. a blob is used as input to multiple operators). This is because\n        the gradient accumulation (Sum) is float only right now.\n        \"\"\"\n\n        grad_ops, input_to_grad = GradientRegistry.GetBackwardPass(\n            self._net.op[skip:], ys)\n        # Check if in immediate mode: the grad_ops are actually being produced\n        # by C++ and bypasses the CreateOperator() call, so in immediate mode\n        # we will have to explicitly run them.\n        if workspace.IsImmediate():\n            for op in grad_ops:\n                workspace.RunOperatorImmediate(op)\n        self._ExtendOps(grad_ops)\n        return input_to_grad\n\n    def AddExternalInput(self, *inputs):\n        assert len(inputs) > 0\n        refs = []\n        for input in inputs:\n            input_name = str(input)\n            assert str(input) not in self._external_input_map, (\n                'Net already contains an input named %s' % input_name)\n        for input in inputs:\n            input_name = str(input)\n            self._net.external_input.extend([input_name])\n            self._external_input_map.update([input_name])\n            refs.append(_get_blob_ref(input_name))\n\n        return refs[0] if len(refs) == 1 else refs\n\n    def AddExternalOutput(self, *outputs):\n        for output in outputs:\n            assert isinstance(output, BlobReference)\n            assert self.BlobIsDefined(output)\n        for output in outputs:\n            self.Proto().external_output.extend([str(output)])\n\n    def AddScopedExternalInputs(self, *inputs):\n        res = self.AddExternalInput(\n            * [ScopedBlobReference(b) for b in inputs]\n        )\n        if not isinstance(res, list):\n            res = [res]\n        return res\n\n    def AddScopedExternalOutputs(self, *outputs):\n        return self.AddExternalOutput(\n            * [ScopedBlobReference(b) for b in outputs]\n        )\n\n    # This returns a reference to the observer\n    def AddObserver(self, observer_type):\n        return C.add_observer_to_net(self._net.name, observer_type)\n\n    def RemoveObserver(self, observer):\n        C.remove_observer_from_net(self._net.name, observer)\n\n    def NumObservers(self):\n        return C.num_observers_on_net(self._net.name)\n\n    @property\n    def external_inputs(self):\n        return [_get_blob_ref(x) for x in self._net.external_input]\n\n    @property\n    def external_outputs(self):\n        return [_get_blob_ref(x) for x in self._net.external_output]\n\n    def set_input_record(self, input_record):\n        from caffe2.python import schema\n        assert self._input_record is None or (input_record.has_blobs() and\n            set(input_record.field_blobs()) ==\n            set(self._input_record.field_blobs())), (\n            'Input schema cannot be reset')\n        if not input_record.has_blobs():\n            with NameScope(self.Name()):\n                self._input_record = schema.NewRecord(self, input_record)\n        else:\n            self._input_record = input_record\n            for blob in input_record.field_blobs():\n                if blob not in self.external_inputs:\n                    self.AddExternalInput(blob)\n        return self._input_record\n\n    def recover_input_record_by_prefix(self, prefix):\n        \"\"\"\n        Tries to recover input record by taking a subset of external_inputs with\n        a given prefix name and interpreting them as schema column names\n        \"\"\"\n        record = _recover_record_by_prefix(self._net.external_input, prefix)\n        if record:\n            self.set_input_record(record)\n\n    def set_output_record(self, record):\n        assert self._output_record is None or (record.has_blobs() and\n            set(record.field_blobs()) ==\n            set(self._output_record.field_blobs())), (\n            'Output schema cannot be reset')\n        for blob in record.field_blobs():\n            assert self.BlobIsDefined(blob), \"{} is not defined\".format(blob)\n        for blob in record.field_blobs():\n            self.AddExternalOutput(blob)\n        self._output_record = record\n\n    def recover_output_record_by_prefix(self, prefix):\n        \"\"\"\n        Tries to recover out record by taking a subset of external_outputs with\n        a given prefix name and interpreting them as schema column names\n        \"\"\"\n        record = _recover_record_by_prefix(self._net.external_output, prefix)\n        if record:\n            self.set_output_record(record)\n\n    def AppendOutputRecordField(self, field_name, record):\n        from caffe2.python import schema\n        assert self._output_record is not None, (\n            'Tried to append to missing output record'\n        )\n        for blob in record.field_blobs():\n            assert self.BlobIsDefined(blob)\n        for blob in record.field_blobs():\n            self.AddExternalOutput(blob)\n        self._output_record = self._output_record + schema.Struct(\n            (field_name, record)\n        )\n\n    def input_record(self):\n        return self._input_record\n\n    def output_record(self):\n        return self._output_record\n\n    def AddExternalInputs(self, *inputs):\n        return self.AddExternalInput(*inputs)\n\n    def AddExternalOutputs(self, *outputs):\n        self.AddExternalOutput(*outputs)\n\n    def DeduplicateGradientSlices(self, g, aggregator='sum'):\n        assert isinstance(g, GradientSlice)\n        unique, remapping = self.Unique([g.indices], 2, engine='SparseHash')\n        if aggregator.lower() == 'sum':\n            new_g = self.UnsortedSegmentSum([g.values, remapping], 1)\n        elif aggregator.lower() == 'mean':\n            new_g = self.UnsortedSegmentMean([g.values, remapping], 1)\n        else:\n            raise ValueError('{} is not supported'.format(aggregator))\n        return GradientSlice(indices=unique, values=new_g)\n\n    def RunAllOnGPU(self, gpu_id=0, use_cudnn=False):\n        \"\"\"A convenient function to run everything on the GPU.\"\"\"\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.device_type = caffe2_pb2.CUDA\n        device_option.cuda_gpu_id = gpu_id\n        self._net.device_option.CopyFrom(device_option)\n        if use_cudnn:\n            for op in self._net.op:\n                op.engine = \"CUDNN\"\n    def RunAllOnMKL(self):\n        \"\"\"A convenient function to run everything using MKLDNN.\"\"\"\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.device_type = caffe2_pb2.MKLDNN\n        self._net.device_option.CopyFrom(device_option)\n\n    def _CreateAndAddToSelf(self, op_type, inputs, outputs=None, **kwargs):\n        \"\"\"A helper function to create an operator and add it to self.\n        \"\"\"\n        inputs = _RectifyInputOutput(inputs)\n        for input in inputs:\n            if not self.BlobIsDefined(input):\n                assert input.Net() != self\n                self.AddExternalInput(input)\n        if outputs is None:\n            # If we do not specify an output, we will assume that this op\n            # produces one output in this case.\n            outputs = self.NextName(prefix=op_type)\n        elif type(outputs) is int:\n            # In this case, we will auto-fill the given number of outputs\n            # with auto-generated names.\n            outputs = [\n                self.NextName(prefix=op_type, output_id=i)\n                for i in range(outputs)]\n        outputs = _RectifyInputOutput(outputs, net=self)\n        op = CreateOperator(op_type, inputs, outputs, **kwargs)\n        self._ExtendOps([op])\n\n        workspace.operator_tracebacks[self.Name()][\n            len(self._net.op) - 1] = _extract_stacktrace()\n\n        if len(op.output) == 0:\n            return\n        elif len(op.output) == 1:\n            return BlobReference(op.output[0], self)\n        else:\n            return tuple(BlobReference(o, self) for o in op.output)\n\n    def __getattr__(self, op_type):\n        if op_type.startswith('__'):\n            raise AttributeError('Attribute {} not found.'.format(op_type))\n        if not IsOperator(op_type) and not IsOperatorWithEngine(op_type, \"CUDNN\"):\n            raise AttributeError(\n                'Method ' + op_type + ' is not a registered operator.' +\n                ' Did you mean: [' +\n                \",\".join(workspace.C.nearby_opnames(op_type)) + ']'\n            )\n        return lambda *args, **kwargs: self._CreateAndAddToSelf(\n            op_type, *args, **kwargs)\n\n    def __dir__(self):\n        additional_methods = [\n            op\n            for op in _REGISTERED_OPERATORS\n            if '_ENGINE_' not in op]\n        return sorted(set(chain(\n            dir(type(self)),\n            viewkeys(self.__dict__),\n            additional_methods\n        )))\n\n    def Python(\n        self,\n        f,\n        grad_f=None,\n        python_func_type=None,\n        pass_workspace=False,\n        grad_output_indices=None,\n        grad_input_indices=None\n    ):\n        \"\"\"\n        Registers and returns a python operator.\n\n        `f` and `grad_f` can be one of the following:\n            - a function with signature (inputs, outputs), where inputs and\n              outputs are a list of CPUTensor objects. This function will be\n              called from C++ everytime the operator is executed.\n            - a tuple (func, args, kwargs), here `func` is a callable, args is\n              an argument list, and kwargs is a dict list. The call:\n                  f = func(*args, kwargs)\n              will be performed locally at node initialization time, on all of\n              the nodes of the job, returning `f`, a callable that will be used\n              as the python operator function to be called during Net execution.\n              This is to be used when using python operator in a distributed\n              context, and allows to create and keep local python state across\n              calls to the operator.\n\n        `python_func_type` is a type of an object that constructed as\n        python_func_type(f) and provides an implementation to forward and\n        backward functions. Its useful in such a case where users needs\n        a statefull PythonOp (ex: use autograd for computing grad_f).\n\n        If `pass_workspace` is True, the signature is changed to\n        (inputs, outputs, workspace) where `workspace` is the workspace the op\n        is going to run on. This is potentially dangerous (as the op can\n        manipulate the workspace directly), use on your own risk.\n\n        If a gradient function is specified (`grad_f`), by default its inputs\n        will be: (1) all inputs to `f`, (2) followed by all outputs of `f`, (3)\n        and then all gradient outputs of `f`. The outputs of `grad_f` will be\n        (by default) all gradient inputs to `f`. If a subset of the gradient\n        outputs or gradient inputs is desired instead, then the subsets can be\n        specified by providing `grad_output_indices` and/or `grad_input_indices`\n        which identify the indices of `f`'s inputs and outputs which have\n        gradients.\n        \"\"\"\n        assert(IsOperator('Python'))\n\n        def make_builder(t):\n            if not isinstance(t, tuple):\n                return ''\n            assert len(t) == 3, 'Expected builder tuple (func, args, kwargs)'\n            func, args, kwargs = t\n            normalized = (func, tuple(args), dict(kwargs))\n            return pickle.dumps(normalized)\n\n        f_builder = make_builder(f)\n        grad_f_builder = make_builder(grad_f)\n\n        assert (not grad_f) or ((not f_builder) == (not grad_f_builder)), (\n            'A tuple has to be passed to both f and grad_f or neither.')\n\n        core_kwargs = {}\n        if f_builder:\n            core_kwargs['pickled_builder'] = f_builder\n            core_kwargs['pickled_grad_builder'] = grad_f_builder\n            core_kwargs['pass_workspace'] = pass_workspace\n        else:\n            core_kwargs['token'] = _RegisterPythonImpl(\n                f, grad_f, python_func_type, pass_workspace=pass_workspace)\n\n        grad_output_indices = grad_output_indices or []\n        grad_input_indices = grad_input_indices or []\n        return lambda *args, **kwargs: self._CreateAndAddToSelf(\n            'Python',\n            grad_output_indices=grad_output_indices,\n            grad_input_indices=grad_input_indices,\n            *args,\n            **dict(chain(viewitems(kwargs), viewitems(core_kwargs)))\n        )\n\n    def is_external_input(self, blob):\n        name = str(blob)\n        return name in self._external_input_map\n\n    def extend_ops(self, new_ops):\n        return self._ExtendOps(new_ops)\n\n\ndef copy_func_between_devices(src, dst):\n    CPU = caffe2_pb2.CPU\n    CUDA = caffe2_pb2.CUDA\n\n    if src.device_type == CPU and dst.device_type == CPU:\n        return None\n\n    if src.device_type == CUDA and dst.device_type == CUDA:\n        if src.cuda_gpu_id == dst.cuda_gpu_id:\n            return None\n        else:\n            def fun(net, *args, **kw):\n                with DeviceScope(dst):\n                    return net.Copy(*args, **kw)\n            return fun\n\n    if src.device_type == CUDA and dst.device_type == CPU:\n        def fun(net, *args, **kw):\n            with DeviceScope(src):\n                return net.CopyGPUToCPU(*args, **kw)\n        return fun\n\n    if src.device_type == CPU and dst.device_type == CUDA:\n        def fun(net, *args, **kw):\n            with DeviceScope(dst):\n                return net.CopyCPUToGPU(*args, **kw)\n        return fun\n\n    raise ValueError('Non-supported devices: %s and %s' % (src, dst))\n\n\ndef device_equal(src, dst):\n    '''\n    We are using this fucntion instead of == operator because optional-value\n    comparison between empty device_options and {device_type:0, cuda_gpu_id:0}\n    returns not equal in some cases.\n    '''\n    return src.device_type == dst.device_type and src.cuda_gpu_id == dst.cuda_gpu_id\n\n\nclass RemapEntry:\n    def __init__(self, blob, device):\n        self.blob = blob\n        self.device = device\n\n    def __eq__(self, other):\n        return self.blob == other.blob and self.device == other.device\n\n    def __hash__(self):\n        return hash(self.blob + str(self.device))\n\n\ndef InjectCrossDeviceCopies(net, blob_to_device=None, blob_remap=None):\n    '''\n    Injecting Copy functions between device within a net. Users can provide\n    a net with part of operators using different device_options. This method\n    will automatically create a new net with Copy ops inserted in it.\n\n    Inputs:\n      blob_to_device: If not None, it is a map of blobs and their device locations.\n      blob_remap: If not None, it is a map from a pair (blob, device) to\n                  the name of the blob in the given device. Blobs found in this\n                  map are assumed to be cached and don't need to be copied.\n    Outputs:\n      new_net: A new net with CopyCPUToGPU inserted with correct device option\n\n      required_external_to_device:\n               A mapping between unresolved external inputs and their\n               required device options.\n    Assumptions:\n      1. every external inputs of this net is already in blob_to_device!\n      2. if not, this function will use net device option\n    '''\n    new_net = net.Clone(net._net.name + '_cross_device', keep_schema=True)\n    del new_net._net.op[:]\n    if blob_to_device is None:\n        blob_to_device = {}\n    # remapping of input blobs for each op.\n    if blob_remap is None:\n        blob_remap = {}\n    temp_remap = {}\n    net_option = net._net.device_option or caffe2_pb2.DeviceOption()\n\n    # if external_inputs have device remappings generated by previous nets,\n    # then add those remappings as external inputs as well.\n    all_remaps = defaultdict(list)\n    for entry, mapped_blob in blob_remap.items():\n        all_remaps[entry.blob].append(mapped_blob)\n    mapped_external_inputs = []\n    for input in new_net._net.external_input:\n        mapped_external_inputs.extend(all_remaps.get(input) or [])\n    new_net._net.external_input.extend(mapped_external_inputs)\n\n    for op in net._net.op:\n        temp_remap.clear()\n        # Get where inputs and outputs should be\n        input_dev, output_dev = InferOpBlobDevices(op)\n\n        for dev, input in zip(input_dev, op.input):\n            assert net.BlobIsDefined(input), \\\n                \"input {} should be defined in the net.\".format(input)\n            if input not in blob_to_device:\n                if net.is_external_input(input):\n                    blob_to_device[input] = net_option\n                else:\n                    raise AttributeError(\n                        \"No device information found for blob {}.\".\n                        format(input)\n                    )\n\n            if not device_equal(blob_to_device[input], dev):\n                # reuse already moved input\n                if (RemapEntry(input, dev) in blob_remap and\n                        blob_to_device[blob_remap[RemapEntry(input, dev)]] == dev):\n                    temp_remap[input] = blob_remap[RemapEntry(input, dev)]\n                else:\n                    # need to make input on correct device.\n                    copy_func = copy_func_between_devices(\n                        blob_to_device[input], dev\n                    )\n\n                    def _gen_new_name(blob, device_option):\n                        CPU = caffe2_pb2.CPU\n                        CUDA = caffe2_pb2.CUDA\n                        if device_option.device_type == CPU:\n                            suffix = '_cpu'\n                        elif device_option.device_type == CUDA:\n                            suffix = '_cuda_' + str(device_option.cuda_gpu_id)\n                        else:\n                            raise RuntimeError(\n                                \"Unknown device type: {}\".\n                                format(device_option.device_type)\n                            )\n                        return blob + suffix\n\n                    new_name = _gen_new_name(input, dev)\n                    copy_func(new_net, input, new_name)\n                    blob_remap[RemapEntry(input, dev)] = new_name\n                    temp_remap[input] = new_name\n                    blob_to_device[new_name] = dev\n\n        # Enforcing no reuse blob between operators. In-place blob usage in an\n        # op is allowed. This is based on the assumption that in-place op has\n        # same device info\n        for out_blob, device in zip(op.output, output_dev):\n            if out_blob in blob_to_device and (\n                out_blob not in op.input and\n                not device_equal(blob_to_device[out_blob], device)\n            ):\n                raise RuntimeError(\n                    \"In-place blob: {} is not supported between operators \"\n                    \"with different device option previous:{} now: {}. \"\n                    \"Failed op:\\n {}\".format(\n                        out_blob, blob_to_device[out_blob], device, op\n                    )\n                )\n        new_op = caffe2_pb2.OperatorDef()\n        new_op.CopyFrom(op)\n\n        new_list = [temp_remap.get(b, b) for b in new_op.input]\n        del new_op.input[:]\n        new_op.input.extend(new_list)\n\n        # keep inplace blobs inplace\n        original_inputs = list(op.input)\n        for i, out in enumerate(new_op.output):\n            try:\n                input_idx = original_inputs.index(out)\n                new_op.output[i] = new_op.input[input_idx]\n            except ValueError:\n                pass\n\n        blob_to_device.update(\n            {o: d for d, o in zip(output_dev, new_op.output)})\n        new_net.extend_ops([new_op])\n\n    return new_net, blob_to_device\n\n\ndef InjectDeviceCopiesAmongNets(nets, blob_to_device_init=None):\n    \"\"\"\n    Takes in a list of nets. They usually represent your whole execution graph.\n    This function will insert cross device copy functions to all nets, and resolve\n    inter-net external inputs dependencies. This method will insert Copy funcitons if\n    external inputs of a net is produced on different device than it is required.\n    Inputs:\n      nets: a list of nets\n    Outputs:\n      new_nets: a list of new nets with device difference solved.\n\n    Some notes from wyiming:\n      1. You MUST pass nets in execution order. e.g. [train_init, train]\n    \"\"\"\n    assert isinstance(nets, list), \\\n        \"nets {} should be a list of nets.\".format(str(nets))\n    assert all(isinstance(net, Net) for net in nets), \\\n        \"nets {} should be a list of nets.\".format(str(nets))\n    # A holistic blob to device mapping.\n    blob_to_device = blob_to_device_init or {}\n    blob_remap = {}\n    new_nets = []\n\n    for net in nets:\n        new_net, blob_to_device = InjectCrossDeviceCopies(\n            net,\n            blob_to_device=blob_to_device,\n            blob_remap=blob_remap,\n        )\n        new_nets.append(new_net)\n\n    return new_nets, blob_to_device\n\n\ndef InjectDeviceCopiesAmongNetsWithoutB2D(nets, blob_to_device_init=None):\n    new_nets, _ = InjectDeviceCopiesAmongNets(nets, blob_to_device_init)\n    return new_nets\n\n\ndef get_net_name(netlike):\n    if isinstance(netlike, Net):\n        return netlike.Proto().name\n    elif isinstance(netlike, caffe2_pb2.NetDef):\n        return netlike.name\n    else:\n        return netlike\n\n\ndef output_to_list(op_output):\n    \"\"\"\n    Ensures that the output of an operator is a list.\n    Use when an operator has a variable number of outputs, but a list of\n    outputs is desired even when number of outputs is 1.\n\n    Args:\n        op_output: Either a BlobReferenece or an iterable of BlobReferences.\n\n    Returns:\n        A list of BlobReferences.\n    \"\"\"\n    assert type(op_output) in (list, tuple, BlobReference)\n    return (\n        [op_output]\n        if isinstance(op_output, BlobReference) else list(op_output))\n\n\ndef _add_net_to_dict(net_dict, net):\n    name = get_net_name(net)\n    if name in net_dict:\n        assert net_dict[name] is None or net == net_dict[name], (\n            'Different nets with same name: ' + name)\n        return False\n    else:\n        net_dict[name] = net if isinstance(net, Net) else None\n        return True\n\n\nclass ExecutionStep(object):\n    _step_names_used = set()\n\n    @staticmethod\n    def _get_next_step_name(basename):\n        name = basename\n        next_idx = 1\n        while name in ExecutionStep._step_names_used:\n            name = basename + '_' + str(next_idx)\n            next_idx += 1\n        ExecutionStep._step_names_used |= set([name])\n        return name\n\n    def __init__(self, name, nets=None, num_iter=None):\n        self._step = caffe2_pb2.ExecutionStep()\n        self._step.name = name or ExecutionStep._get_next_step_name('step')\n        self._net_dict = OrderedDict()\n        self._is_used = False\n        self._substeps = []\n        if nets is not None:\n            if type(nets) is Net:\n                nets = [nets]\n            for net in nets:\n                if _add_net_to_dict(self._net_dict, net):\n                    self._step.network.extend([get_net_name(net)])\n        if num_iter is not None:\n            self._step.num_iter = num_iter\n\n    def get_net(self, name):\n        return self._net_dict[name]\n\n    def Name(self):\n        return self._step.name\n\n    def __str__(self):\n        return self._step.name\n\n    def _assert_can_mutate(self):\n        assert not self._is_used, (\n            'Cannot mutate a step that has already been added to a plan/step.')\n\n    def _notify_is_used(self):\n        self._is_used = True\n\n    def Proto(self):\n        return self._step\n\n    def HasNets(self):\n        return self._step.network is not None and (\n            len(self._step.network) > 0)\n\n    def HasSubsteps(self):\n        return self._step.substep is not None and (\n            len(self._step.substep) > 0)\n\n    def Nets(self):\n        return list(viewvalues(self._net_dict))\n\n    def Substeps(self):\n        return self._substeps\n\n    def SetIter(self, num_iter):\n        self._assert_can_mutate()\n        self._step.num_iter = num_iter\n\n    def SetCreateWorkspace(self, create_workspace):\n        self._assert_can_mutate()\n        self._step.create_workspace = create_workspace\n\n    def SetNumConcurrentInstances(self, num_concurrent_instances):\n        self._assert_can_mutate()\n        self._step.num_concurrent_instances = num_concurrent_instances\n\n    def SetOnlyOnce(self, only_once):\n        self._assert_can_mutate()\n        self._step.only_once = only_once\n\n    def SetShouldStopBlob(self, should_stop_blob):\n        assert isinstance(should_stop_blob, BlobReference), (\n            \"expects BlobReference here, got {}\".format(type(should_stop_blob)))\n        self._assert_can_mutate()\n        self._step.should_stop_blob = str(should_stop_blob)\n\n    def RunEveryMillis(self, interval):\n        \"\"\"\n        Run this step every interval millisecods, as long as its\n        siblings are still running. It is guaranteed that, after all\n        siblings finish, this step will run at least one.\n\n        This property is ignored for top-level ExecutionSteps.\n        \"\"\"\n        self._step.run_every_ms = interval\n\n    def SetReportNet(self, report_net, report_interval):\n        \"\"\" DEPRECATED. Use RunEveryMillis instead. \"\"\"\n        self._assert_can_mutate()\n        _add_net_to_dict(self._net_dict, report_net)\n        self._step.report_net = get_net_name(report_net)\n        self._step.report_interval = report_interval\n\n    def AddSubstep(self, substep):\n        self._assert_can_mutate()\n        assert not self.HasNets(), 'Cannot have both network and substeps.'\n        if isinstance(substep, ExecutionStep):\n            substep._notify_is_used()\n            if not substep.HasNets() and not substep.HasSubsteps():\n                return self\n            for net in substep.Nets():\n                _add_net_to_dict(self._net_dict, net)\n            self._substeps.append(substep)\n            proto = substep.Proto()\n        else:\n            proto = substep\n        self._step.substep.add().CopyFrom(proto)\n        return self\n\n    def SetConcurrentSubsteps(self, concurrent_substeps):\n        self._assert_can_mutate()\n        assert not self.HasNets(), 'Cannot have both network and substeps.'\n        self._step.concurrent_substeps = concurrent_substeps\n\n    def AddNet(self, net):\n        self._assert_can_mutate()\n        assert not self.HasSubsteps(), 'Cannot have both network and substeps.'\n        assert isinstance(net, Net)\n        _add_net_to_dict(self._net_dict, net)\n        self._step.network.extend([get_net_name(net)])\n        return self\n\n    def get_all_attributes(self, name):\n        \"\"\"\n        Return the list of all attributes under the given `name`, present in\n        all of the nets used in this execution step and its children.\n        \"\"\"\n        return [\n            attr\n            for net in viewvalues(self._net_dict)\n            for attr in net.get_attributes(name)\n        ]\n\n    @classmethod\n    def create_from_proto(cls, step_proto, net_obj_dict, net_proto_dict):\n        \"\"\"\n        Create ExecutionStep from ExecutionStep protobuf recursively\n        \"\"\"\n        assert isinstance(step_proto, caffe2_pb2.ExecutionStep)\n        assert (len(step_proto.network) > 0 and len(step_proto.substep) == 0) or \\\n            (len(step_proto.network) == 0 and len(step_proto.substep) > 0)\n\n        steps_or_nets = []\n        if len(step_proto.substep) > 0:\n            for substep_proto in step_proto.substep:\n                steps_or_nets.append(ExecutionStep.create_from_proto(\n                    substep_proto, net_obj_dict, net_proto_dict))\n        else:\n            for net_name in step_proto.network:\n                if net_name not in net_obj_dict:\n                    assert net_name in net_proto_dict\n                    net = Net(net_proto_dict[net_name])\n                    net_obj_dict[net_name] = net\n                net = net_obj_dict[net_name]\n                assert isinstance(net, Net)\n                steps_or_nets.append(net)\n\n        num_iter = step_proto.num_iter if step_proto.HasField('num_iter') else None\n        concurrent_substeps = step_proto.concurrent_substeps if\\\n            step_proto.HasField('concurrent_substeps') else None\n        should_stop_blob = BlobReference(step_proto.should_stop_blob) if\\\n            step_proto.HasField('should_stop_blob') else None\n        only_once = step_proto.only_once if\\\n            step_proto.HasField('only_once') else None\n        num_concurrent_instances = step_proto.num_concurrent_instances if\\\n            step_proto.HasField('num_concurrent_instances') else None\n        create_workspace = step_proto.create_workspace if\\\n            step_proto.HasField('create_workspace') else None\n        run_every_ms = step_proto.run_every_ms if\\\n            step_proto.HasField('run_every_ms') else None\n\n        return execution_step(\n            step_proto.name,\n            steps_or_nets,\n            num_iter=num_iter,\n            report_net=None,        # DEPRECATED\n            report_interval=None,   # DEPRECATED\n            concurrent_substeps=concurrent_substeps,\n            should_stop_blob=should_stop_blob,\n            only_once=only_once,\n            num_concurrent_instances=num_concurrent_instances,\n            create_workspace=create_workspace,\n            run_every_ms=run_every_ms)\n\n\ndef add_nets_in_order(step, net_list):\n    proto = step.Proto()\n    for substep in step.Substeps():\n        add_nets_in_order(substep, net_list)\n    for net in proto.network:\n        if net not in net_list:\n            net_list.append(net)\n    # FIXME(azzolini): This is actually wrong. Report nets should be\n    # instantiated first since they may run before any substep is run.\n    # However, curerntly, Reporter depends on this behavior.\n    if proto.report_net and proto.report_net not in net_list:\n        net_list.append(proto.report_net)\n\n\nclass Plan(object):\n\n    def __init__(self, name_or_step):\n        self._plan = caffe2_pb2.PlanDef()\n        self._net_dict = OrderedDict()\n        self._steps = []    # A list of ExecutionStep\n        if isinstance(name_or_step, ExecutionStep):\n            self._plan.name = name_or_step.Name()\n            self.AddStep(name_or_step)\n        elif isinstance(name_or_step, basestring):\n            self._plan.name = name_or_step\n        else:\n            raise ValueError('name_or_step must be a string or ExecutionStep')\n\n    def __str__(self):\n        return self._plan.name\n\n    def Proto(self):\n        return self._plan\n\n    def AddNets(self, nets):\n        for net in nets:\n            if _add_net_to_dict(self._net_dict, net):\n                assert isinstance(net, Net)\n                self._plan.network.add().CopyFrom(net.Proto())\n\n    def Nets(self):\n        return list(viewvalues(self._net_dict))\n\n    def AddStep(self, step):\n        assert isinstance(step, ExecutionStep)\n        step._notify_is_used()\n        if not step.HasNets() and not step.HasSubsteps():\n            return\n        self._plan.execution_step.add().CopyFrom(step.Proto())\n        self._steps.append(step)\n        # nets need to be added to the plan in order of usage\n        net_list = []\n        add_nets_in_order(step, net_list)\n        self.AddNets([step.get_net(n) for n in net_list])\n\n    def Steps(self):\n        return self._steps\n\n    def get_all_attributes(self, name):\n        \"\"\"\n        Return the list of all attributes under the given `name`, present in\n        all of the nets used in this plan.\n        \"\"\"\n        return [\n            attr\n            for net in viewvalues(self._net_dict)\n            for attr in net.get_attributes(name)\n        ]\n\n    @classmethod\n    def create_from_proto(cls, plan_proto):\n        assert isinstance(plan_proto, caffe2_pb2.PlanDef)\n        plan = Plan(plan_proto.name)\n        plan._plan.CopyFrom(plan_proto)\n\n        net_obj_dict = {}\n        net_proto_dict = {}\n        for net_proto in plan_proto.network:\n            assert net_proto.name not in net_proto_dict\n            net_proto_dict[net_proto.name] = net_proto\n\n        for step_proto in plan_proto.execution_step:\n            step = ExecutionStep.create_from_proto(\n                step_proto, net_obj_dict, net_proto_dict)\n            plan.AddStep(step)\n\n        return plan\n\n\ndef to_execution_step(step_or_nets, default_name=None):\n    from caffe2.python.net_builder import NetBuilder\n    if isinstance(step_or_nets, ExecutionStep):\n        return step_or_nets\n\n    stop_blob = None\n    if not default_name and hasattr(step_or_nets, 'name'):\n        default_name = step_or_nets.name\n    if isinstance(step_or_nets, NetBuilder):\n        stop_blob = step_or_nets._stop_blob\n        step_or_nets = step_or_nets.get()\n    return execution_step(\n        default_name, step_or_nets, should_stop_blob=stop_blob)\n\n\ndef execution_step(default_name,\n                   steps_or_nets,\n                   num_iter=None,\n                   report_net=None,\n                   report_interval=None,\n                   concurrent_substeps=None,\n                   should_stop_blob=None,\n                   only_once=None,\n                   num_concurrent_instances=None,\n                   create_workspace=False,\n                   run_every_ms=None):\n    \"\"\"\n    Helper for creating an ExecutionStep.\n    - steps_or_nets can be:\n      - None\n      - Net\n      - ExecutionStep\n      - list<Net>\n      - list<ExecutionStep>\n    - should_stop_blob is either None or a scalar boolean blob.\n      - This blob is checked AFTER every substeps/subnets.\n      - If specified and true, then this step will return immediately.\n      - Be sure to handle race conditions if setting from concurrent threads.\n    - if no should_stop_blob or num_iter is provided, defaults to num_iter=1\n    \"\"\"\n    assert should_stop_blob is None or num_iter is None, (\n        'Cannot set both should_stop_blob and num_iter.')\n    if should_stop_blob is None and num_iter is None:\n        num_iter = 1\n\n    step = ExecutionStep(default_name)\n    if should_stop_blob is not None:\n        step.SetShouldStopBlob(should_stop_blob)\n    if num_iter is not None:\n        step.SetIter(num_iter)\n    if only_once is not None:\n        step.SetOnlyOnce(only_once)\n    if concurrent_substeps is not None:\n        step.SetConcurrentSubsteps(concurrent_substeps)\n    if report_net is not None:\n        assert report_interval is not None\n        step.SetReportNet(report_net, report_interval)\n    if num_concurrent_instances is not None:\n        step.SetNumConcurrentInstances(num_concurrent_instances)\n    if create_workspace:\n        step.SetCreateWorkspace(True)\n    if run_every_ms:\n        step.RunEveryMillis(run_every_ms)\n\n    if isinstance(steps_or_nets, ExecutionStep):\n        step.AddSubstep(steps_or_nets)\n    elif isinstance(steps_or_nets, Net):\n        step.AddNet(steps_or_nets)\n    elif isinstance(steps_or_nets, list):\n        if all(isinstance(x, Net) for x in steps_or_nets):\n            for x in steps_or_nets:\n                step.AddNet(x)\n        else:\n            for x in steps_or_nets:\n                step.AddSubstep(to_execution_step(x))\n    elif steps_or_nets:\n        raise ValueError(\n            'steps_or_nets must be a step, a net, or a list of nets or steps.')\n    return step\n\n\ndef scoped_execution_step(name, *args, **kwargs):\n    \"\"\"Same as execution_step() except that the step name is scoped.\"\"\"\n    default_name = ScopedName(name) if name else name\n    return execution_step(default_name, *args, **kwargs)\n\n\ndef _extract_stacktrace():\n    '''\n    This function extracts stacktrace without file system access\n    by purely using sys._getframe() and removes part that belongs to\n    this file (core.py). We are not using inspect module because\n    its just a wrapper on top of sys._getframe() whos\n    logis is based on accessing source files on disk - exactly what\n    we are trying to avoid here. Same stands for traceback module\n\n    The reason for file system access avoidance is that\n    if code is located on an NFS, file access might be slow\n\n    Function returns a list of tuples (file_name, line_number, function)\n    '''\n\n    result = []\n    # Ignore top 3 layers of stack: this function, _CreateAndAddToSelf, and\n    # whatever calls _CreateAndAddToSelf (either __getattr__ or Python)\n    frame = sys._getframe(3)\n    # We just go down the frame stack in a loop\n    while frame:\n        # Its important to extract information from the frame here\n        # as frame's current line most probably will change later.\n        result.append((frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name))\n        frame = frame.f_back\n    return result\n\n\nSetPerOpEnginePref = C.set_per_op_engine_pref\nSetGlobalEnginePref = C.set_global_engine_pref\nSetEnginePref = C.set_engine_pref\nSetOpEnginePref = C.set_op_engine_pref\n"
  },
  {
    "path": "caffe2/python/core_gradients_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom future.utils import bytes_to_native_str\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport unittest\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core, test_util\nfrom caffe2.python.core import CreateOperator, GradientRegistry\nfrom caffe2.python import workspace\n\nimport numpy as np\n\n\n# First, we will set up a few gradient registry entries so that we can manually\n# construct some test cases.\n\n\ndef NeedAll(op, g_output):\n    \"\"\"A sanity check to make sure that all the gradient are given.\"\"\"\n    for name, g in zip(op.output, g_output):\n        if g is None:\n            raise RuntimeError(\n                'Need gradient for \"%s\" but it is not provided.' % name)\n    return g_output\n\n\ndef GIS(op):\n    \"\"\"A test util function to generate the gradient name for input.\"\"\"\n    return [s + '_grad' for s in op.input]\n\n\ndef CopyDeviceOption(op, src_op):\n    if src_op.HasField('device_option'):\n        op.device_option.CopyFrom(src_op.device_option)\n    return op\n\n\n# First gradient: (in -> out) leading to (out_grad -> in_grad)\n@GradientRegistry.RegisterGradient('Direct')\ndef AddDirectGradient(op, g_output):\n    return (\n        CopyDeviceOption(\n            CreateOperator('DirectGradient', NeedAll(op, g_output), GIS(op)),\n            op),\n        GIS(op)\n    )\n\n\n# Second gradient: (in -> out) leading to (out, out_grad -> in_grad)\n@GradientRegistry.RegisterGradient('UseOutput')\ndef AddUseOutputGradient(op, g_output):\n    return (\n        CopyDeviceOption(\n            CreateOperator(\n                'UseOutputGradient',\n                list(op.output) + NeedAll(op, g_output), GIS(op)),\n            op),\n        GIS(op)\n    )\n\n\n@GradientRegistry.RegisterGradient('UseInput')\ndef AddUseInputGradient(op, g_output):\n    return (\n        CopyDeviceOption(\n            CreateOperator(\n                'UseInputGradient',\n                list(op.input) + NeedAll(op, g_output), GIS(op)),\n            op),\n        GIS(op)\n    )\n\n\n@GradientRegistry.RegisterGradient('Nogradient')\ndef AddNogradient(op, g_output):\n    return (\n        [],\n        [None for s in op.input]\n    )\n\n\nclass TestGradientCalculation(test_util.TestCase):\n    def assertOperatorListEqual(self, operatorDefList1, operatorDefList2):\n        for op in operatorDefList1:\n            op.debug_info = \"\"\n        for op in operatorDefList2:\n            op.debug_info = \"\"\n        self.assertEqual(operatorDefList1, operatorDefList2)\n\n    @given(device_option=st.sampled_from([\n        None,\n        core.DeviceOption(caffe2_pb2.CUDA, 1)]))\n    def testDirect(self, device_option):\n        operators = [\n            CreateOperator('Direct', 'in', 'hidden'),\n            CreateOperator('Direct', 'hidden', 'out'),\n        ]\n        if device_option:\n            for op in operators:\n                op.device_option.CopyFrom(device_option)\n        desired_grad_operators = [\n            CreateOperator('DirectGradient', 'out_grad', 'hidden_grad'),\n            CreateOperator('DirectGradient', 'hidden_grad', 'in_grad'),\n        ]\n        if device_option:\n            for op in desired_grad_operators:\n                op.device_option.CopyFrom(device_option)\n        gradients, _ = GradientRegistry.GetBackwardPass(\n            operators, {'out': 'out_grad'})\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n\n    def testDirectImplicitGradientSource(self):\n        operators = [\n            CreateOperator('Direct', 'in', 'hidden'),\n            CreateOperator('Direct', 'hidden', 'out'),\n        ]\n        desired_grad_operators = [\n            CreateOperator(\n                \"ConstantFill\", 'out', \"out_autogen_grad\", value=1.0),\n            CreateOperator(\n                'DirectGradient', 'out_autogen_grad', 'hidden_grad'),\n            CreateOperator('DirectGradient', 'hidden_grad', 'in_grad'),\n        ]\n        for op in desired_grad_operators:\n            op.debug_info = \"\"\n        gradients, _ = GradientRegistry.GetBackwardPass(\n            operators, ['out'])\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n\n    def testDoesNotGenerateUnnecessaryGradients(self):\n        operators = [\n            CreateOperator('Direct', 'in', 'hidden'),\n            CreateOperator('Direct', 'hidden', 'out'),\n        ]\n        desired_grad_operators = [\n            CreateOperator('DirectGradient', 'hidden_grad', 'in_grad'),\n        ]\n        for op in desired_grad_operators:\n            op.debug_info = \"\"\n        gradients, _ = GradientRegistry.GetBackwardPass(\n            operators, {'hidden': 'hidden_grad'})\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n\n    def testDirectButNoOutputGradientGiven(self):\n        operators = [\n            CreateOperator('Direct', 'in', 'hidden'),\n            CreateOperator('Direct', 'hidden', 'out'),\n        ]\n        gradients, _ = GradientRegistry.GetBackwardPass(\n            operators, {})\n        self.assertOperatorListEqual(gradients, [])\n\n    def testDirectInPlace(self):\n        operators = [\n            CreateOperator('Direct', 'in', 'in'),\n            CreateOperator('Direct', 'in', 'out'),\n        ]\n        desired_grad_operators = [\n            CreateOperator('DirectGradient', 'out_grad', 'in_grad'),\n            CreateOperator('DirectGradient', 'in_grad', 'in_grad'),\n        ]\n        gradients, _ = GradientRegistry.GetBackwardPass(\n            operators, {'out': 'out_grad'})\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n\n    def testVersionMismatch(self):\n        operators = [\n            CreateOperator('Direct', 'x', 'x'),\n            CreateOperator('Direct', 'y', 'x'),\n            CreateOperator('Direct', 'x', 'y'),\n        ]\n        try:\n            gradients, _ = GradientRegistry.GetBackwardPass(\n                operators, {'y': 'y_grad'})\n            self.assertFalse(True, \"Should raise exception of incorrect version\")\n        except RuntimeError as e:\n            print(e)\n            self.assertTrue(\"version\" in str(e))\n            pass\n\n    def testUseOutput(self):\n        operators = [\n            CreateOperator('UseOutput', 'in', 'hidden'),\n            CreateOperator('UseOutput', 'hidden', 'out'),\n            CreateOperator('Direct', 'out', 'sink'),\n        ]\n        desired_grad_operators = [\n            CreateOperator('DirectGradient', 'sink_grad', 'out_grad'),\n            CreateOperator(\n                'UseOutputGradient',\n                ['out', 'out_grad'], 'hidden_grad'\n            ),\n            CreateOperator(\n                'UseOutputGradient',\n                ['hidden', 'hidden_grad'], 'in_grad'\n            ),\n        ]\n        gradients, _ = GradientRegistry.GetBackwardPass(\n            operators, {'sink': 'sink_grad'})\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n\n    def testUseOutputInPlace(self):\n        operators = [\n            CreateOperator('UseOutput', 'in', 'in'),\n            CreateOperator('UseOutput', 'in', 'out'),\n            CreateOperator('Direct', 'out', 'sink'),\n        ]\n        desired_grad_operators = [\n            CreateOperator('DirectGradient', 'sink_grad', 'out_grad'),\n            CreateOperator(\n                'UseOutputGradient',\n                ['out', 'out_grad'], 'in_grad'\n            ),\n            CreateOperator(\n                'UseOutputGradient',\n                ['in', 'in_grad'], 'in_grad'\n            ),\n        ]\n        gradients, _ = GradientRegistry.GetBackwardPass(\n            operators, {'sink': 'sink_grad'})\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n\n    def testUseOutputButOutputHasBeenChanged(self):\n        operators = [\n            CreateOperator('UseOutput', 'in', 'hidden'),\n            # Note here: we overwrite hidden, but hidden will be needed by the\n            # gradient calculation of the first operator, so the gradient\n            # registry should return an error.\n            CreateOperator('Direct', 'hidden', 'hidden'),\n            CreateOperator('UseOutput', 'hidden', 'out'),\n            CreateOperator('Direct', 'out', 'sink'),\n        ]\n        with self.assertRaises(RuntimeError):\n            gradients, _ = GradientRegistry.GetBackwardPass(\n                operators, {'sink': 'sink_grad'})\n\n    def testUseInput(self):\n        operators = [\n            CreateOperator('Direct', 'in', 'hidden'),\n            CreateOperator('UseInput', 'hidden', 'out'),\n            CreateOperator('Direct', 'out', 'sink'),\n        ]\n        desired_grad_operators = [\n            CreateOperator('DirectGradient', 'sink_grad', 'out_grad'),\n            CreateOperator(\n                'UseInputGradient',\n                ['hidden', 'out_grad'], 'hidden_grad'\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'hidden_grad', 'in_grad'\n            ),\n        ]\n        gradients, _ = GradientRegistry.GetBackwardPass(\n            operators, {'sink': 'sink_grad'})\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n\n    def testUseInputButInputHasBeenChanged(self):\n        \"\"\"Test gradient for the following case:\n\n        in -> out, with UseInput\n        in -> in\n\n        Since we overwrite in in op#1, but in will be needed by the gradient\n        calculation of op#0, the gradient registry should raise an error.\n        \"\"\"\n        operators = [\n            CreateOperator('UseInput', 'in', 'out'),\n            CreateOperator('Direct', 'in', 'in'),\n        ]\n        with self.assertRaises(RuntimeError):\n            gradients, _ = GradientRegistry.GetBackwardPass(\n                operators, {'out': 'out_grad'})\n\n    @given(device_option=st.sampled_from([\n        None,\n        core.DeviceOption(caffe2_pb2.CUDA, 1)]))\n    def testMultiUseInput(self, device_option):\n        \"\"\"Test gradient for the following case:\n\n        in -> hidden1\n        in -> hidden2\n        hidden1, hidden2 -> out\n        \"\"\"\n        operators = [\n            CreateOperator('Direct', 'in', 'hidden1'),\n            CreateOperator('Direct', 'in', 'hidden2'),\n            CreateOperator('Direct', ['hidden1', 'hidden2'], 'out'),\n        ]\n        if device_option:\n            for op in operators:\n                op.device_option.CopyFrom(device_option)\n        desired_grad_operators = [\n            CreateOperator(\n                'DirectGradient',\n                'out_grad', ['hidden1_grad', 'hidden2_grad']\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'hidden2_grad', 'in_grad'\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'hidden1_grad', '_in_grad_autosplit_0'\n            ),\n            CreateOperator(\n                'Sum',\n                ['in_grad', '_in_grad_autosplit_0'], 'in_grad'\n            ),\n        ]\n        if device_option:\n            for op in desired_grad_operators:\n                op.device_option.CopyFrom(device_option)\n        gradients, _ = GradientRegistry.GetBackwardPass(\n            operators, {\"out\": \"out_grad\"})\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n\n    def testMultiUseInputButWithNoGradient(self):\n        \"\"\"Test gradient for the following case:\n\n        in -> hidden1\n        in -(no gradient)-> hidden2\n        hidden1, hidden2 -> out\n        \"\"\"\n        operators = [\n            CreateOperator('Direct', 'in', 'hidden1'),\n            CreateOperator('Nogradient', 'in', 'hidden2'),\n            CreateOperator('Direct', ['hidden1', 'hidden2'], 'out'),\n        ]\n        desired_grad_operators = [\n            CreateOperator(\n                'DirectGradient',\n                'out_grad', ['hidden1_grad', 'hidden2_grad']\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'hidden1_grad', 'in_grad'\n            ),\n        ]\n        gradients, _ = GradientRegistry.GetBackwardPass(\n            operators, {'out': 'out_grad'})\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n\n    def testMultiUseInputAndMultipleVersions(self):\n        \"\"\"Test gradient for the following case:\n\n        in -> in\n        in -> hidden1, hidden2\n        hidden1, hidden2 -> out\n        \"\"\"\n        operators = [\n            CreateOperator('Direct', 'in', 'in'),\n            CreateOperator('Direct', 'in', 'hidden1'),\n            CreateOperator('Direct', 'in', 'hidden2'),\n            CreateOperator('Direct', ['hidden1', 'hidden2'], 'out'),\n        ]\n        desired_grad_operators = [\n            CreateOperator(\n                'DirectGradient',\n                'out_grad', ['hidden1_grad', 'hidden2_grad']\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'hidden2_grad', 'in_grad'\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'hidden1_grad', '_in_grad_autosplit_0'\n            ),\n            CreateOperator(\n                'Sum',\n                ['in_grad', '_in_grad_autosplit_0'], 'in_grad'\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'in_grad', 'in_grad'\n            ),\n        ]\n        gradients, _ = GradientRegistry.GetBackwardPass(\n            operators, {'out': 'out_grad'})\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n\n    def testMultiUseInputAndMultipleVersionsBig(self):\n        \"\"\"Test gradient for the following case:\n\n        in -> in\n        in -> hidden1, hidden2\n        hidden1, hidden2 -> in\n        in -> hidden3, hidden4, hidden5\n        hidden3, hidden4, hidden5 -> out\n        \"\"\"\n        operators = [\n            CreateOperator('Direct', 'in', 'in'),\n            CreateOperator('Direct', 'in', 'hidden1'),\n            CreateOperator('Direct', 'in', 'hidden2'),\n            CreateOperator('Direct', ['hidden1', 'hidden2'], 'in'),\n            CreateOperator('Direct', 'in', 'hidden3'),\n            CreateOperator('Direct', 'in', 'hidden4'),\n            CreateOperator('Direct', 'in', 'hidden5'),\n            CreateOperator('Direct', ['hidden3', 'hidden4', 'hidden5'], 'out'),\n        ]\n        desired_grad_operators = [\n            CreateOperator(\n                'DirectGradient',\n                'out_grad', ['hidden3_grad', 'hidden4_grad', 'hidden5_grad']\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'hidden5_grad', 'in_grad'\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'hidden4_grad', '_in_grad_autosplit_0'\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'hidden3_grad', '_in_grad_autosplit_1'\n            ),\n            CreateOperator(\n                'Sum',\n                ['in_grad', '_in_grad_autosplit_0',\n                 '_in_grad_autosplit_1'],\n                'in_grad'\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'in_grad', ['hidden1_grad', 'hidden2_grad']\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'hidden2_grad', 'in_grad'\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'hidden1_grad', '_in_grad_autosplit_0'\n            ),\n            CreateOperator(\n                'Sum',\n                ['in_grad', '_in_grad_autosplit_0'],\n                'in_grad'\n            ),\n            CreateOperator(\n                'DirectGradient',\n                'in_grad', 'in_grad'\n            ),\n        ]\n        gradients, _ = GradientRegistry.GetBackwardPass(\n            operators, {'out': 'out_grad'})\n        for s in gradients:\n            print(str(s))\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n\n    def testGradientMappingUsingSumOp(self):\n        \"\"\"Since Sum is used in accumulating gradients, we will test if\n        it is OK to also explicitly use it in the graph.\"\"\"\n        operators = [\n            CreateOperator('FC', ['in', 'w', 'b'], 'fc'),\n            CreateOperator('Sum', 'fc', 'agg'),\n            CreateOperator('AveragedLoss', 'agg', 'loss'),\n        ]\n        # This should run correctly.\n        gradient_ops, _ = GradientRegistry.GetBackwardPass(\n            operators, {'loss': 'loss_grad'})\n        for s in gradient_ops:\n            print(str(s))\n\n    def testGradientCalculationWithPrint(self):\n        \"\"\"Test a common use case where we have Print in the forward pass.\"\"\"\n        operators = [\n            CreateOperator('FC', ['in', 'w', 'b'], 'fc'),\n            CreateOperator('Print', 'fc', []),\n            CreateOperator('AveragedLoss', 'fc', 'loss'),\n        ]\n        desired_grad_operators = [\n            CreateOperator('AveragedLossGradient',\n                           ['fc', 'loss_grad'], 'fc_grad'),\n            CreateOperator('FCGradient', ['in', 'w', 'fc_grad'],\n                           ['w_grad', 'b_grad', 'in_grad']),\n        ]\n        for g in desired_grad_operators:\n            g.is_gradient_op = 1\n        # This should run correctly.\n        gradient_ops, _ = GradientRegistry.GetBackwardPass(\n            operators, {'loss': 'loss_grad'})\n        for s in gradient_ops:\n            print(str(s))\n        self.assertOperatorListEqual(gradient_ops, desired_grad_operators)\n\n    def testStopGradient(self):\n        operators = [\n            CreateOperator('Direct', 'in', 'hidden'),\n            CreateOperator('StopGradient', 'hidden', 'hidden2'),\n            CreateOperator('Direct', 'hidden2', 'out'),\n        ]\n        desired_grad_operators = [\n            CreateOperator('DirectGradient', 'out_grad', 'hidden2_grad'),\n        ]\n        gradients, _ = GradientRegistry.GetBackwardPass(\n            operators, {'out': 'out_grad'})\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n\n    def testStopGradientOrphan(self):\n        operators = [\n            CreateOperator('Direct', 'in', 'hidden'),\n            CreateOperator('StopGradient', 'hidden', 'auto_blobx'),\n            CreateOperator('Direct', 'hidden', 'out'),\n        ]\n        with self.assertRaises(ValueError):\n            # This should complain about incorrect use of StopGradient\n            gradients, _ = GradientRegistry.GetBackwardPass(\n                operators, {'out': 'out_grad'})\n\n    def testStopGradientInplace(self):\n        operators = [\n            CreateOperator('Direct', 'in', 'hidden'),\n            CreateOperator('StopGradient', 'hidden', 'hidden'),\n            CreateOperator('Direct', 'hidden', 'out'),\n        ]\n        desired_grad_operators = [\n            CreateOperator('DirectGradient', 'out_grad', 'hidden_grad'),\n        ]\n        gradients, grad_map = GradientRegistry.GetBackwardPass(\n            operators, {'out': 'out_grad'})\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n        self.assertEqual(grad_map, {'out': 'out_grad'})\n\n    def testStopGradientWithMultiUseOperators(self):\n        operators = [\n            CreateOperator('Direct', 'in', 'hidden'),\n            CreateOperator('Direct', 'hidden', 'hidden2'),\n            CreateOperator('StopGradient', 'hidden', 'hidden3'),\n            CreateOperator('Direct', ['hidden2', 'hidden3'], 'out'),\n        ]\n        desired_grad_operators = [\n            CreateOperator('DirectGradient', 'out_grad',\n                           ['hidden2_grad', 'hidden3_grad']),\n            CreateOperator('DirectGradient', 'hidden2_grad', 'hidden_grad'),\n            CreateOperator('DirectGradient', 'hidden_grad', 'in_grad'),\n        ]\n        gradients, grad_map = GradientRegistry.GetBackwardPass(\n            operators, {'out': 'out_grad'})\n        self.assertOperatorListEqual(gradients, desired_grad_operators)\n        self.assertEqual(\n            grad_map, {'out': 'out_grad', 'hidden2': 'hidden2_grad',\n                       'hidden3': 'hidden3_grad', 'hidden': 'hidden_grad',\n                       'in': 'in_grad'})\n\n    def test_zero_gradient(self):\n        net = core.Net(\"zero_grad_test\")\n\n        hidden_prev, cell, gates, seq_lengths, timestep =\\\n            net.AddExternalInput(\"h\", \"c\", \"g\", \"s\", \"t\")\n        hidden, cell = net.LSTMUnit(\n            [hidden_prev, cell, gates, seq_lengths, timestep],\n            [\"hidden_t\", \"cell_t\"])\n        with self.assertRaises(Exception):\n            net.AddGradientOperators([hidden])\n        net.ZeroGradient(cell, [])\n        net.AddGradientOperators([hidden])\n\n    def test_two_grads(self):\n        net = core.Net(\"test_two_grads\")\n        input, two, three = net.AddExternalInput(\"input\", \"two\", \"three\")\n\n        m1 = net.Mul([input, two], \"mul_1\")\n        m2 = net.Mul([m1, three], \"mul_2\")\n        grad_map = net.AddGradientOperators([m2, m1])\n        workspace.ResetWorkspace()\n        workspace.blobs[input] = np.array([1]).astype(np.float32)\n        workspace.blobs[two] = np.array([2]).astype(np.float32)\n        workspace.blobs[three] = np.array([3]).astype(np.float32)\n        workspace.RunNetOnce(net)\n        print(net.Proto())\n        for blob in workspace.blobs:\n            print(blob, workspace.blobs[blob])\n        print(\"Input grad: \", workspace.blobs[grad_map[str(input)]])\n        assert workspace.blobs[grad_map[str(input)]] == 8.0\n\n\n# Skip if sparse operators are not available\n@unittest.skipIf(not core.IsOperator('SparseFunHash'),\n                 'Sparse operators not available')\nclass TestSparseGradientsAccumulation(test_util.TestCase):\n    def testSparseAccumulationWithValues(self):\n        # The gradient for \"Gather\" only computes values. indices are directly\n        # passed from the input\n        #\n        # x1-->Gather-->x4-->\n        #        |          |\n        # x2-----+     DotProduct-->x6\n        #        |          |\n        # x3-->Gather-->x5-->\n        net = core.Net(\"test_net\")\n        net.Gather([\"x2\", \"x1\"], \"x4\")\n        net.Gather([\"x2\", \"x3\"], \"x5\")\n        net.DotProduct([\"x4\", \"x5\"], \"x6\")\n        net.AddGradientOperators([\"x6\"])\n        sum_op_i = net.Proto().op[-2]\n        sum_op_v = net.Proto().op[-1]\n        self.assertEqual(sum_op_i.input[0], \"x3\")\n        self.assertEqual(sum_op_i.input[1], \"x1\")\n        self.assertEqual(sum_op_i.output[0], \"x2_grad_indices_concat\")\n        self.assertEqual(sum_op_v.input[0], \"x5_grad\")\n        self.assertEqual(sum_op_v.input[1], \"x4_grad\")\n        self.assertEqual(sum_op_v.output[0], \"x2_grad_values_concat\")\n\n    def testSparseGradientToDense(self):\n        #\n        #                                        x1-->Gather-->x4-->\n        #                                                 |        |\n        # x0, w, b-->FC-->x2-->EnsureDenseGradient-->x2---+  DotProduct-->x6\n        #                                                 |        |\n        #                                        x3-->Gather-->x5-->\n        net = core.Net(\"test_net\")\n        net.FC([\"x0\", \"w\", \"b\"], \"x2\")\n        net.EnsureDense([\"x2\"], \"x2\")\n        net.Gather([\"x2\", \"x1\"], \"x4\")\n        net.Gather([\"x2\", \"x3\"], \"x5\")\n        net.DotProduct([\"x4\", \"x5\"], \"x6\")\n        net.AddGradientOperators([\"x6\"])\n        ensure_dense_op = net.Proto().op[-2]\n        self.assertEqual(ensure_dense_op.input[0], \"x2_grad_indices_concat\")\n        self.assertEqual(ensure_dense_op.input[1], \"x2_grad_values_concat\")\n        self.assertEqual(ensure_dense_op.output[0], \"x2_grad\")\n\n    def testSparseAccumulationWithIndicesAndValues(self):\n        # The gradient for \"SparseFunHash\" computes both indices and values\n        #\n        # x1-------->\n        #           |\n        # x2---->   |\n        #       |   |\n        # x3---SparseFunHash-->x8\n        #       /               \\\n        # x4---+            DotProduct-->x10\n        #       \\               /\n        # x5---SparseFunHash-->x9\n        #       |   |\n        # x6---->   |\n        #           |\n        # x7-------->\n        net = core.Net(\"test_net\")\n        net.SparseFunHash([\"x1\", \"x2\", \"x3\", \"x4\"], \"x8\")\n        net.SparseFunHash([\"x5\", \"x6\", \"x7\", \"x4\"], \"x9\")\n        net.DotProduct([\"x8\", \"x9\"], \"x10\")\n        net.AddGradientOperators([\"x10\"])\n        sum_op_i = net.Proto().op[-2]\n        sum_op_v = net.Proto().op[-1]\n        self.assertEqual(sum_op_i.input[0], \"_x4_grad_indices_autosplit_0\")\n        self.assertEqual(sum_op_i.input[1], \"_x4_grad_indices_autosplit_1\")\n        self.assertEqual(sum_op_i.output[0], \"x4_grad_indices_concat\")\n        self.assertEqual(sum_op_v.input[0], \"_x4_grad_values_autosplit_0\")\n        self.assertEqual(sum_op_v.input[1], \"_x4_grad_values_autosplit_1\")\n        self.assertEqual(sum_op_v.output[0], \"x4_grad_values_concat\")\n\n\nclass TestGradientsAccumulationWithNoGradientOps(test_util.TestCase):\n    def testNormalAccumulation(self):\n        #  x1-->Relu--x2----------------->DotProduct-->x4\n        #                |                 |\n        #                 -->Softmax-->x3-->\n        net = core.Net(\"test_net\")\n        net.Relu(\"x1\", \"x2\")\n        net.Softmax(\"x2\", \"x3\")\n        net.DotProduct([\"x2\", \"x3\"], \"x4\")\n        net.AddGradientOperators([\"x4\"])\n        sum_op = net.Proto().op[-2]\n        self.assertEqual(sum_op.input[0], \"x2_grad\")\n        self.assertEqual(sum_op.input[1], \"_x2_grad_autosplit_0\")\n        self.assertEqual(sum_op.output[0], \"x2_grad\")\n\n    def testAccumulationWithNoGradientBranch(self):\n        #                 -->PRINT\n        #                |\n        #  x1-->Relu--x2----------------->DotProduct-->x4\n        #                |                 |\n        #                 -->Softmax-->x3-->\n        net = core.Net(\"test_net\")\n        net.Relu(\"x1\", \"x2\")\n        net.Print(\"x2\", [])\n        net.Softmax(\"x2\", \"x3\")\n        net.DotProduct([\"x2\", \"x3\"], \"x4\")\n        net.AddGradientOperators([\"x4\"])\n        sum_op = net.Proto().op[-2]\n        self.assertEqual(sum_op.input[0], \"x2_grad\")\n        self.assertEqual(sum_op.input[1], \"_x2_grad_autosplit_0\")\n        self.assertEqual(sum_op.output[0], \"x2_grad\")\n\n\nclass TestGradientsAccumulationWithPassThroughGradients(test_util.TestCase):\n    def testAddOpInMiddle(self):\n        #  x1-->Relu--x2----------------->Add-->x4\n        #                |                 |\n        #                 -->Softmax-->x3-->\n        #\n        # Expected gradient graph:\n        #\n        #  x1_g<--ReluG<--x2_g<--Sum<------------<---------x4_g\n        #                          |                       |\n        #                           <--_x2_g_split_0<--SoftmaxG\n        net = core.Net(\"test_net\")\n        net.Relu(\"x1\", \"x2\")\n        net.Softmax(\"x2\", \"x3\")\n        net.Add([\"x2\", \"x3\"], \"x4\")\n        input_to_grad = net.AddGradientOperators({\"x4\": \"x4_grad\"})\n        sum_op = net.Proto().op[-2]\n        self.assertEqual(sum_op.input[0], \"x2_grad\")\n        self.assertEqual(sum_op.input[1], \"x4_grad\")\n        self.assertEqual(sum_op.output[0], \"x2_grad\")\n        self.assertEqual(input_to_grad[\"x1\"], \"x1_grad\")\n\n    def testAddAndDynamicConstant(self):\n        net = core.Net(\"test_net\")\n        net.FC([\"x1\", \"x1_w\", \"x1_b\"], [\"x2\"])\n        net.Relu(\"x2\", \"x2\")\n        net.ConstantFill([\"x2\"], [\"x3\"])\n        net.Add([\"x2\", \"x3\"], \"x4\")\n        net.FC([\"x4\", \"x4_w\", \"x4_b\"], [\"x5\"])\n        net.SoftmaxWithLoss([\"x5\", \"labels\"], [\"softmax\", \"loss\"])\n        input_to_grad = net.AddGradientOperators([\"loss\"])\n        for op in net.Proto().op:\n            self.assertFalse(op.type == 'Sum')\n\n        self.assertTrue(\"x4\" in input_to_grad)\n        self.assertTrue(\"x1\" in input_to_grad)\n        self.assertEqual(input_to_grad[\"x1\"], \"x1_grad\")\n\n    def testAddAndStaticConstant(self):\n        net = core.Net(\"test_net\")\n        net.FC([\"x1\", \"x1_w\", \"x1_b\"], [\"x2\"])\n        net.Relu(\"x2\", \"x2\")\n        net.ConstantFill([], [\"x3\"], shape=[1])\n        net.Add([\"x2\", \"x3\"], \"x4\", broadcast=1)\n        net.FC([\"x4\", \"x4_w\", \"x4_b\"], [\"x5\"])\n        net.SoftmaxWithLoss([\"x5\", \"labels\"], [\"softmax\", \"loss\"])\n        input_to_grad = net.AddGradientOperators([\"loss\"])\n        print(input_to_grad)\n\n        self.assertTrue(\"x1\" in input_to_grad)\n        self.assertEqual(input_to_grad[\"x1\"], \"x1_grad\")\n\n    def testSubOpInMiddle(self):\n        #  x1-->Relu--x2----------------->Sub-->x4\n        #                |                 |\n        #                 -->Softmax-->x3-->\n        #\n        # Expected gradient graph:\n        #\n        #  x1_g<--ReluG<--x2_g<--Sum<------------<-----------------------x4_g\n        #                          |                                      |\n        #                           <--_x2_g_split_0<--SoftmaxG<--x3_g<--neg\n        net = core.Net(\"test_net\")\n        net.Relu(\"x1\", \"x2\")\n        net.Softmax(\"x2\", \"x3\")\n        net.Sub([\"x2\", \"x3\"], \"x4\")\n        input_to_grad = net.AddGradientOperators({\"x4\": \"x4_grad\"})\n        print(str(net.Proto()))\n        sum_op = net.Proto().op[-2]\n        self.assertEqual(sum_op.input[0], \"x2_grad\")\n        self.assertEqual(sum_op.input[1], \"x4_grad\")\n        self.assertEqual(sum_op.output[0], \"x2_grad\")\n        self.assertEqual(input_to_grad[\"x1\"], \"x1_grad\")\n\n    def testAddOpAtLeaf(self):\n        # x1\n        #   \\\n        #    -->Add-->x4\n        #   /           \\\n        # x2             -->DotProduct-->x6\n        #   \\           /\n        #    -->Add-->x5\n        #   /\n        # x3\n        #\n        # Expected gradient graph:\n        #\n        #  x2_g<--Sum<--x4_g<--DotProductG<--x6_g\n        #          |                |                       |\n        #           <---x5_g<-------\n        net = core.Net(\"test_net\")\n        net.Add([\"x1\", \"x2\"], \"x4\")\n        net.Add([\"x2\", \"x3\"], \"x5\")\n        net.DotProduct([\"x4\", \"x5\"], \"x6\")\n        input_to_grad = net.AddGradientOperators({\"x6\": \"x6_grad\"})\n        sum_op = net.Proto().op[-1]\n        self.assertEqual(sum_op.input[0], \"x5_grad\")\n        self.assertEqual(sum_op.input[1], \"x4_grad\")\n        self.assertEqual(sum_op.output[0], \"x2_grad\")\n        self.assertEqual(input_to_grad[\"x1\"], \"x4_grad\")\n        self.assertEqual(input_to_grad[\"x2\"], \"x2_grad\")\n        self.assertEqual(input_to_grad[\"x3\"], \"x5_grad\")\n\n    def testSubOpAtLeaf(self):\n        # x1\n        #   \\\n        #    -->Sub-->x4\n        #   /           \\\n        # x2             -->DotProduct-->x6\n        #   \\           /\n        #    -->Sub-->x5\n        #   /\n        # x3\n        #\n        # Expected gradient graph:\n        #\n        #  x2_g<-------Sum<--x2_g_split_0<--neg<--x4_g<--DotProductG<--x6_g\n        #               |                                       |\n        #  x3_g<--neg<--<--x5_g<--------------------------------\n        net = core.Net(\"test_net\")\n        net.Sub([\"x1\", \"x2\"], \"x4\")\n        net.Sub([\"x2\", \"x3\"], \"x5\")\n        net.DotProduct([\"x4\", \"x5\"], \"x6\")\n        input_to_grad = net.AddGradientOperators({\"x6\": \"x6_grad\"})\n        sum_op = net.Proto().op[-1]\n        self.assertEqual(sum_op.input[0], \"x2_grad\")\n        self.assertEqual(sum_op.input[1], \"x5_grad\")\n        self.assertEqual(sum_op.output[0], \"x2_grad\")\n        self.assertEqual(input_to_grad[\"x1\"], \"x4_grad\")\n        self.assertEqual(input_to_grad[\"x2\"], \"x2_grad\")\n        self.assertEqual(input_to_grad[\"x3\"], \"x3_grad\")\n\n    def testMultiLayerAddOps(self):\n        # x1\n        #   \\\n        #    -->Add-->x4\n        #   /           \\\n        # x2             -->Add-->x6\n        #   \\           /\n        #    -->Add-->x5\n        #   /\n        # x3\n        #\n        # Expected gradient graph:\n        #\n        #  x2_g<--Sum<-----x6_g\n        #          |         |\n        #           <--------\n        net = core.Net(\"test_net\")\n        net.Add([\"x1\", \"x2\"], \"x4\")\n        net.Add([\"x2\", \"x3\"], \"x5\")\n        net.Add([\"x4\", \"x5\"], \"x6\")\n        input_to_grad = net.AddGradientOperators({\"x6\": \"x6_grad\"})\n        sum_op = net.Proto().op[-1]\n        self.assertEqual(sum_op.input[0], \"x6_grad\")\n        self.assertEqual(sum_op.input[1], \"x6_grad\")\n        self.assertEqual(sum_op.output[0], \"x2_grad\")\n        self.assertEqual(input_to_grad[\"x1\"], \"x6_grad\")\n        self.assertEqual(input_to_grad[\"x2\"], \"x2_grad\")\n        self.assertEqual(input_to_grad[\"x3\"], \"x6_grad\")\n\n    def testMultiLayerSubOps(self):\n        # x1\n        #   \\\n        #    -->Sub-->x4\n        #   /           \\\n        # x2             -->Sub-->x6\n        #   \\           /\n        #    -->Sub-->x5\n        #   /\n        # x3\n        #\n        # Expected gradient graph:\n        #\n        #  x2_g<--Sum<-----x6_g\n        #          |         |\n        #           <--------\n        net = core.Net(\"test_net\")\n        net.Sub([\"x1\", \"x2\"], \"x4\")\n        net.Sub([\"x2\", \"x3\"], \"x5\")\n        net.Sub([\"x4\", \"x5\"], \"x6\")\n        input_to_grad = net.AddGradientOperators({\"x6\": \"x6_grad\"})\n        sum_op = net.Proto().op[-1]\n        self.assertEqual(sum_op.input[0], \"x2_grad\")\n        self.assertEqual(sum_op.input[1], \"x5_grad\")\n        self.assertEqual(sum_op.output[0], \"x2_grad\")\n        self.assertEqual(input_to_grad[\"x1\"], \"x6_grad\")\n        self.assertEqual(input_to_grad[\"x2\"], \"x2_grad\")\n        self.assertEqual(input_to_grad[\"x3\"], \"x3_grad\")\n\n    def testAccumulationRuns(self):\n        net = core.Net(\"test_net\")\n        input, one, two, three = net.AddExternalInput(\n            \"input\", \"one\", \"two\", \"three\")\n\n        m1 = net.Mul([input, two], \"mul_1\")\n        m2 = net.Mul([input, three], \"mul_2\")\n        sub = net.Sub([m1, one])\n        grad_map = net.AddGradientOperators([m2, sub])\n\n        workspace.ResetWorkspace()\n        workspace.blobs[one] = np.array([1]).astype(np.float32)\n        workspace.blobs[input] = np.array([1]).astype(np.float32)\n        workspace.blobs[two] = np.array([2]).astype(np.float32)\n        workspace.blobs[three] = np.array([3]).astype(np.float32)\n        workspace.RunNetOnce(net)\n        print(\"Input grad: \", workspace.blobs[grad_map[str(input)]])\n        assert workspace.blobs[grad_map[str(input)]] == 5.0\n\n    def testIncorrectOperator(self):\n        net = core.Net(\"test_net\")\n        a, b, one = net.AddExternalInput(\"a\", \"b\", \"one\")\n        m1 = net.Mul(a, b)  # does not have second output\n        sub = net.Sub([m1, one])\n        try:\n            net.AddGradientOperators([sub])\n            self.assertFalse(True, \"Did not throw exception\")\n        except Exception as e:\n            self.assertTrue(\"schema\" in str(e))\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/core_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom inspect import currentframe, getframeinfo\nimport unittest\n\nimport numpy as np\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core, workspace, test_util\nfrom caffe2.python.task import Node, Task\n\n\nclass TestScopes(test_util.TestCase):\n    def testBlobReferenceIsIndependentFromNameScope(self):\n        blob_v = core.BlobReference(\"v\")\n        with core.NameScope(\"foo\"):\n            blob_w = core.BlobReference(\"w\")\n            with core.NameScope(\"bar\"):\n                blob_x = core.BlobReference(\"x\")\n        self.assertEqual(str(blob_v), \"v\")\n        self.assertEqual(str(blob_w), \"w\")\n        self.assertEqual(str(blob_x), \"x\")\n\n    def testNameScopeWithOp(self):\n        global_x = core.BlobReference(\"x\")\n        global_y = core.BlobReference(\"y\")\n        with core.NameScope(\"foo\"):\n            # Raw strings should have namescope prepended.\n            op = core.CreateOperator(\"Relu\", \"x\", \"y\")\n            self.assertEqual(len(op.input), 1)\n            self.assertEqual(op.input[0], \"foo/x\")\n            self.assertEqual(len(op.output), 1)\n            self.assertEqual(op.output[0], \"foo/y\")\n            # BlobReferences should not.\n            op = core.CreateOperator(\"Relu\", global_x, global_y)\n            self.assertEqual(len(op.input), 1)\n            self.assertEqual(op.input[0], \"x\")\n            self.assertEqual(len(op.output), 1)\n            self.assertEqual(op.output[0], \"y\")\n\n    def testNameScopeWithReset(self):\n        with core.NameScope(\"foo\"):\n            # foo/\n            op = core.CreateOperator(\"Relu\", \"x\", \"y\")\n            self.assertEqual(len(op.input), 1)\n            self.assertEqual(op.input[0], \"foo/x\")\n            self.assertEqual(len(op.output), 1)\n            self.assertEqual(op.output[0], \"foo/y\")\n            with core.NameScope(\"bar\"):\n                # foo/bar/\n                op = core.CreateOperator(\"Relu\", \"x\", \"y\")\n                self.assertEqual(len(op.input), 1)\n                self.assertEqual(op.input[0], \"foo/bar/x\")\n                self.assertEqual(len(op.output), 1)\n                self.assertEqual(op.output[0], \"foo/bar/y\")\n            # Back to foo/\n            op = core.CreateOperator(\"Relu\", \"x\", \"y\")\n            self.assertEqual(len(op.input), 1)\n            self.assertEqual(op.input[0], \"foo/x\")\n            self.assertEqual(len(op.output), 1)\n            self.assertEqual(op.output[0], \"foo/y\")\n            with core.NameScope(\"bar\", reset=True):\n                # bar/\n                op = core.CreateOperator(\"Relu\", \"x\", \"y\")\n                self.assertEqual(len(op.input), 1)\n                self.assertEqual(op.input[0], \"bar/x\")\n                self.assertEqual(len(op.output), 1)\n                self.assertEqual(op.output[0], \"bar/y\")\n            # Back to foo/\n            op = core.CreateOperator(\"Relu\", \"x\", \"y\")\n            self.assertEqual(len(op.input), 1)\n            self.assertEqual(op.input[0], \"foo/x\")\n            self.assertEqual(len(op.output), 1)\n            self.assertEqual(op.output[0], \"foo/y\")\n\n    def testDeviceScope(self):\n        # No device\n        op = core.CreateOperator(\"Relu\", \"x\", \"y\")\n        self.assertFalse(op.HasField('device_option'))\n        # explicitly setting a device\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.device_type = caffe2_pb2.CUDA\n        device_option.cuda_gpu_id = 1\n        op = core.CreateOperator(\"Relu\", \"x\", \"y\", device_option=device_option)\n        self.assertTrue(op.HasField('device_option'))\n        self.assertEqual(op.device_option.device_type, caffe2_pb2.CUDA)\n        self.assertEqual(op.device_option.cuda_gpu_id, 1)\n        with core.DeviceScope(device_option):\n            # from device scope\n            op = core.CreateOperator(\"Relu\", \"x\", \"y\")\n            self.assertTrue(op.HasField('device_option'))\n            self.assertEqual(op.device_option.device_type, caffe2_pb2.CUDA)\n            self.assertEqual(op.device_option.cuda_gpu_id, 1)\n            # from an overridden device option\n            override_device = caffe2_pb2.DeviceOption()\n            override_device.device_type = caffe2_pb2.CPU\n            op = core.CreateOperator(\n                \"Relu\", \"x\", \"y\", device_option=override_device)\n            self.assertTrue(op.HasField('device_option'))\n            self.assertEqual(op.device_option.device_type, caffe2_pb2.CPU)\n        # back from normal: no device\n        op = core.CreateOperator(\"Relu\", \"x\", \"y\")\n        self.assertFalse(op.HasField('device_option'))\n        device_option = caffe2_pb2.DeviceOption()\n\n    def testNameAndDeviceScopeTogether(self):\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.device_type = caffe2_pb2.CUDA\n        device_option.cuda_gpu_id = 1\n        with core.DeviceScope(device_option):\n            with core.NameScope(\"foo\"):\n                op = core.CreateOperator(\"Relu\", \"x\", \"y\")\n                self.assertTrue(op.HasField('device_option'))\n                self.assertEqual(op.device_option.device_type, caffe2_pb2.CUDA)\n                self.assertEqual(op.device_option.cuda_gpu_id, 1)\n                self.assertEqual(len(op.input), 1)\n                self.assertEqual(op.input[0], \"foo/x\")\n                self.assertEqual(len(op.output), 1)\n                self.assertEqual(op.output[0], \"foo/y\")\n\n\nclass TestCloneNet(test_util.TestCase):\n    def testPartialClone(self):\n        params = core.Net('params')\n        p1 = params.ConstantFill([], ['p1'])\n        workspace.CreateNet(params)\n        workspace.RunNetOnce(params)\n\n        n = core.Net('original')\n        a1 = n.AddExternalInput('a1')\n        a2 = n.AddExternalInput('a2')\n        b1, b2 = n.Concat([a1, a2], ['b1', 'b2'], axis=0)\n        c1 = n.Sum([b1, p1], ['c1'])\n        c2 = n.Sum([b2], ['c2'])\n        d = n.Sum([c1, c2], ['d'])\n\n        # test that gradient ops are ignored when partial-cloning\n        n.AddGradientOperators([d])\n\n        # test some in-place ops\n        k = n.Sum([p1], ['k'])\n        e = n.Sum([d], ['e'])\n        e = n.Sum([e, k], [e])\n        e = n.Sum([e], [e])\n        f = n.Sum(e, ['f'])\n\n        def net_assert(net, num_ops, inputs, outputs, internals):\n            self.assertEqual(len(net.Proto().op), num_ops)\n            self.assertEqual(set(net.Proto().external_input), inputs)\n            self.assertEqual(set(net.Proto().external_output), outputs)\n            all_blobs = set(net.Proto().external_input)\n            all_blobs |= set(net.Proto().external_output)\n            for op in net.Proto().op:\n                all_blobs |= set(op.input) | set(op.output)\n            self.assertEqual(all_blobs, inputs | outputs | internals)\n            # create net to make sure its valid\n            for input in inputs:\n                workspace.FeedBlob(input, np.array([]))\n            workspace.CreateNet(net)\n\n        n2, (d22, ) = n.ClonePartial('f1', {a1: 'a11', a2: 'a22'}, [d])\n        net_assert(\n            n2, 4, {'p1', 'a11', 'a22'}, {'f1/d'},\n            {'f1/b1', 'f1/b2', 'f1/c1', 'f1/c2', 'p1'})\n        self.assertTrue(isinstance(d22, core.BlobReference))\n        self.assertEqual(d22.Net(), n2)\n        self.assertEqual(str(d22), 'f1/d')\n\n        n3, (d22, ) = n.ClonePartial('f2', [b1, b2], [d])\n        net_assert(\n            n3, 3, {'p1', 'b1', 'b2'}, {'f2/d'}, {'f2/c1', 'f2/c2', 'p1'})\n        self.assertEqual(str(d22), 'f2/d')\n\n        n4, (c22, ) = n.ClonePartial('f3', [b1], [c1])\n        net_assert(n4, 1, {'p1', 'b1'}, {'f3/c1'}, {'p1'})\n        self.assertEqual(str(c22), 'f3/c1')\n\n        n5, (c11, c22) = n.ClonePartial('f4', [b1, b2], [c1, c2])\n        net_assert(n5, 2, {'p1', 'b1', 'b2'}, {'f4/c1', 'f4/c2'}, {'p1'})\n        self.assertEqual(str(c11), 'f4/c1')\n        self.assertEqual(str(c22), 'f4/c2')\n\n        with self.assertRaises(AssertionError):\n            n.ClonePartial('f4', [a1, a2, c2], [d])\n\n        n6, (e22, ) = n.ClonePartial('f5', [d], [e])\n        net_assert(n6, 4, {'p1', 'd'}, {'f5/e'}, {'f5/k', 'p1'})\n        self.assertEqual(str(e22), 'f5/e')\n\n        n8, (e22, f22) = n.ClonePartial('f7', [d], [e, f])\n        net_assert(n8, 5, {'p1', 'd'}, {'f7/e', 'f7/f'}, {'p1', 'f7/k'})\n        self.assertEqual(str(e22), 'f7/e')\n        self.assertEqual(str(f22), 'f7/f')\n\n        params._CheckLookupTables()\n        n._CheckLookupTables()\n\n\nclass TestCreateOperator(test_util.TestCase):\n    def testCreate(self):\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.device_type = caffe2_pb2.CUDA\n        device_option.cuda_gpu_id = 1\n        op = core.CreateOperator(\n            \"Ludicrous\", \"x\", \"y\", name=\"ludicrous\",\n            control_input=\"z\", device_option=device_option,\n            engine=\"WARP\", arg1=1, arg2=\"2\", arg3=[1, 2, 3])\n        self.assertEqual(op.type, \"Ludicrous\")\n        self.assertEqual(op.name, \"ludicrous\")\n        self.assertEqual(op.engine, \"WARP\")\n        self.assertEqual(len(op.input), 1)\n        self.assertEqual(op.input[0], \"x\")\n        self.assertEqual(len(op.output), 1)\n        self.assertEqual(op.output[0], \"y\")\n        self.assertEqual(len(op.control_input), 1)\n        self.assertEqual(op.control_input[0], \"z\")\n        self.assertTrue(op.HasField('device_option'))\n        self.assertEqual(op.device_option.device_type, caffe2_pb2.CUDA)\n        self.assertEqual(op.device_option.cuda_gpu_id, 1)\n        self.assertTrue(len(op.arg), 3)\n\n        # can't guarantee ordering of kwargs, so generate a set of args\n        # to test with\n        arg_map = {}\n        for arg in op.arg:\n            arg_map[arg.name] = arg\n\n        # Check all elements exist that should\n        self.assertEqual(\"arg1\" in arg_map, True)\n        self.assertEqual(\"arg2\" in arg_map, True)\n        self.assertEqual(\"arg3\" in arg_map, True)\n\n        # Now test that all args were initialized correctly\n        self.assertEqual(arg_map[\"arg1\"].i, 1)\n        self.assertEqual(arg_map[\"arg2\"].s, b\"2\")\n        self.assertEqual(list(arg_map[\"arg3\"].ints), [1, 2, 3])\n\n    def testCreateWithNoneKwarg(self):\n        with self.assertRaises(ValueError):\n            core.CreateOperator(\"Ludicrous\", \"x\", \"y\", arg1=None)\n\n\nclass TestAutoNaming(test_util.TestCase):\n    def assertOperatorListEqual(self, operatorDefList1, operatorDefList2):\n        for op in operatorDefList1:\n            op.debug_info = \"\"\n        for op in operatorDefList2:\n            op.debug_info = \"\"\n        self.assertEqual(operatorDefList1, operatorDefList2)\n    \"\"\"\n    Test that operators are named with different names, and that automatically\n    named blob names don't clash intra or inter networks.\n    \"\"\"\n    def test_next_blob(self):\n        def create_net():\n            net = core.Net('net')\n            with core.NameScope('foo'):\n                net.Add(['a', 'b'], net.NextScopedBlob('ab'))\n\n            net.Add(['c', 'd'], net.NextBlob('cd'))\n            return net\n\n        net_a = create_net()\n        net_b = create_net()\n        # created net proto is predicatable.\n        self.assertOperatorListEqual(net_a.Proto().op,\n                         net_b.Proto().op)\n        self.assertEqual(net_a.Proto().op[0].output[0], 'foo/ab')\n        self.assertEqual(net_a.Proto().op[1].output[0], 'cd')\n\n        net_c = core.Net('net')\n        # different calls return different blob names\n        self.assertNotEqual(str(net_c.NextBlob('b')), str(net_c.NextBlob('b')))\n\n    def test_auto_naming(self):\n        a = core.Net('net')\n        b = core.Net('net')\n        self.assertNotEqual(a.Proto().name, b.Proto().name)\n        a_in1 = a.AddExternalInput('a')\n        b_in1 = b.AddExternalInput('b')\n        all_outputs_single = []\n        all_outputs_list = []\n\n        def add_ops():\n            all_outputs_single.append(a.Sum([a_in1, a_in1]))\n            all_outputs_single.append(a.Sum([a_in1, a_in1]))\n            all_outputs_single.append(b.Sum([b_in1, b_in1]))\n            all_outputs_single.append(b.Sum([b_in1, b_in1]))\n            all_outputs_list.append(a.Sum([a_in1, a_in1], outputs=2))\n            all_outputs_list.append(a.Sum([a_in1, a_in1], outputs=2))\n            all_outputs_list.append(b.Sum([b_in1, b_in1], outputs=2))\n            all_outputs_list.append(b.Sum([b_in1, b_in1], outputs=2))\n\n        add_ops()\n        with core.NameScope('n1'):\n            add_ops()\n\n        # Force reset of lookup tables\n        a.Proto().name\n\n        with core.NameScope('n2'):\n            add_ops()\n\n        all_outputs = []\n        for s in all_outputs_single:\n            all_outputs.append(str(s))\n        for l in all_outputs_list:\n            for o in l:\n                all_outputs.append(str(o))\n\n        for i, o1 in enumerate(all_outputs):\n            for j, o2 in enumerate(all_outputs):\n                if i != j:\n                    self.assertNotEqual(str(o1), str(o2))\n\n        a._CheckLookupTables()\n        b._CheckLookupTables()\n\n\nclass TestAppendNet(test_util.TestCase):\n\n    def test_external_inputs_merged_correctly(self):\n        netA = core.Net(\"A\")\n        netA.Sum([\"in1\", \"in2\"], [\"sum1\"])\n        self.assertTrue(\"in1\" in netA.external_inputs)\n\n        netB = core.Net(\"B\")\n        netB.Sum([\"in3\", \"in4\"], [\"in1\"])\n        netB.AppendNet(netA)\n        self.assertFalse(\"in1\" in netB.external_inputs)\n\n    def test_external_inputs_merged_correctlyB(self):\n        netA = core.Net(\"A\")\n        netA.Sum([\"in1\", \"in2\"], [\"sum1\"])\n        self.assertTrue(\"in1\" in netA.external_inputs)\n\n        netB = core.Net(\"B\")\n        netB.Sum([\"in3\", \"in4\"], [\"in1\"])\n        netA.AppendNet(netB)  # note different order than in prev test\n        self.assertTrue(\"in1\" in netA.external_inputs)\n\n\nclass TestExtractPredictorNet(test_util.TestCase):\n\n    def test_extract_simple(self):\n        from caffe2.python import brew\n        from caffe2.python.model_helper import ModelHelper, ExtractPredictorNet\n\n        model = ModelHelper(name=\"test\", arg_scope={'order': 'NCHW'})\n        [data, label] = brew.image_input(\n            model,\n            \"reader\", [\"xx/data\", \"label\"],\n            is_test=1,\n        )\n        cnv = brew.conv(model, data, 'cnv', 32, 32, 4)\n        a = brew.fc(model, cnv, 'a', 100, 200)\n        pred = brew.fc(model, a, 'pred', 200, 5)\n        brew.softmax(model, [pred, label], \"softmax\")\n\n        (predict_net, export_blobs) = ExtractPredictorNet(\n            net_proto=model.net.Proto(),\n            input_blobs=[\"xx/data\"],\n            output_blobs=[\"pred\"],\n            renames={\"xx/data\": \"image\"},\n        )\n        export_blobs = set(export_blobs)\n\n        ops = list(predict_net.Proto().op)\n        for op in ops:\n            self.assertFalse(op.type == \"Softmax\")\n            self.assertFalse(\"xx/data\" in op.input)\n\n        # Note: image input should not be included\n        self.assertEquals(ops[0].type, \"Conv\")\n        self.assertEquals(ops[1].type, \"FC\")\n        self.assertEquals(ops[2].type, \"FC\")\n        self.assertEquals(len(ops), 3)\n\n        # test rename happened\n        self.assertEquals(ops[0].input[0], \"image\")\n\n        # Check export blobs\n        self.assertTrue(\"image\" not in export_blobs)\n        self.assertTrue(\"xx/data\" not in export_blobs)\n        self.assertEqual(set([str(p) for p in model.params]), export_blobs)\n\n        # Check external inputs/outputs\n        self.assertTrue(\"image\" in predict_net.Proto().external_input)\n        self.assertEquals(set([\"pred\"]), set(predict_net.Proto().external_output))\n        self.assertEqual(\n            set(predict_net.Proto().external_input) -\n            set([str(p) for p in model.params]), set([\"image\"])\n        )\n\n\nclass TestOperatorTraceback(test_util.TestCase):\n    def op_name_check(self, net, cf, line, func):\n        net.PopulateProtoWithFileName()\n        filename = getframeinfo(cf).filename\n        self.assertEqual(net.Proto().op[0].name, '{}:{}:{}'.format(\n            filename, line, func))\n\n    def test_operator_constructor_traceback(self):\n        net = core.Net(\"test\")\n        a, b = net.AddExternalInput(\"a\", \"b\")\n        net.Mul([a, b], \"c\"); cf = currentframe(); line = cf.f_lineno\n        func = cf.f_code.co_name\n        with self.assertRaises(Exception):\n            workspace.RunNetOnce(net)\n        with self.assertRaises(Exception):\n            workspace.CreateNet(net)\n        self.op_name_check(net, cf, line, func)\n\n    def test_operator_runtime_traceback(self):\n        net = core.Net(\"test\")\n        a = net.AddExternalInput(\"a\")\n        workspace.blobs[a] = np.array([1, 2, 3], dtype=np.float32)\n        net.Split(a, [\"b\", \"c\"], axis=0); cf = currentframe(); line = cf.f_lineno\n        func = cf.f_code.co_name\n        with self.assertRaises(Exception):\n            workspace.RunNetOnce(net)\n        workspace.CreateNet(net)\n        with self.assertRaises(Exception):\n            workspace.RunNet(net)\n        self.op_name_check(net, cf, line, func)\n\n    def test_c_workspace_constructor(self):\n        net = core.Net(\"test\")\n        a, b = net.AddExternalInput(\"a\", \"b\")\n        net.Mul([a, b], \"c\"); cf = currentframe(); line = cf.f_lineno\n        func = cf.f_code.co_name\n        ws = workspace.C.Workspace()\n        with self.assertRaises(Exception):\n            ws.run(net)\n        with self.assertRaises(Exception):\n            ws.create_net(net)\n        self.op_name_check(net, cf, line, func)\n\n    def test_c_workspace_runtime(self):\n        net = core.Net(\"test\")\n        a = net.AddExternalInput(\"a\")\n        net.Split(a, [\"b\", \"c\"], axis=0); cf = currentframe(); line = cf.f_lineno\n        func = cf.f_code.co_name\n        ws = workspace.C.Workspace()\n        ws.create_blob(str(a)).feed(np.array([1, 2, 3], dtype=np.float32))\n        ws.create_net(net)\n        with self.assertRaises(Exception):\n            ws.run(net)\n        self.op_name_check(net, cf, line, func)\n\n    def test_async_exception_handling(self):\n        net = core.Net(\"test\")\n        net.Proto().type = 'dag'  # this runs operators on background threads\n        a = net.AddExternalInput(\"a\")\n        net.Split(a, [\"b\", \"c\"], axis=0); cf = currentframe(); line = cf.f_lineno\n        func = cf.f_code.co_name\n        workspace.FeedBlob(a, np.array([1, 2, 3], dtype=np.float32))\n        with self.assertRaises(Exception) as enforceNotMet:\n            workspace.RunNetOnce(net)\n        self.assertIn('enforce fail', str(enforceNotMet.exception))\n        self.op_name_check(net, cf, line, func)\n\n\nclass TestCreatePlan(test_util.TestCase):\n\n    def test_create_plan_from_proto_correctly(self):\n        from caffe2.python.net_builder import ops\n        with Node('trainer'), Task(name='my_task', num_instances=2) as task:\n            with ops.task_init():\n                globl = ops.Const(0)\n            with ops.task_instance_init():\n                local = ops.Const(0)\n            with ops.loop(100):\n                ops.Copy(globl, local)\n            with ops.task_instance_exit():\n                ops.Add([globl, local], [globl])\n            with ops.task_exit():\n                ops.Mul([globl, globl], [globl])\n\n        plan = core.Plan(task.get_step())\n        test_plan = core.Plan.create_from_proto(plan.Proto())\n\n        self.assertEqual(len(plan.Steps()), 1)\n        self.assertEqual(len(test_plan.Steps()), 1)\n        self.assertEqual(plan.Steps()[0].Name(), test_plan.Steps()[0].Name())\n\n        self.assertEqual(len(plan.Nets()), len(test_plan.Nets()))\n        for idx in range(0, len(plan.Nets())):\n            # When we create Net for test_plan, we will end up with new Net\n            # name with postfix.\n            net_1 = plan.Nets()[idx]\n            net_2 = test_plan.Nets()[idx]\n            trim_size = len(net_1.Name())\n            self.assertEqual(net_1.Name(), net_2.Name()[:trim_size])\n\n\nclass TestOpRegistryKey(test_util.TestCase):\n    def test_is_operator(self):\n        self.assertTrue(core.IsOperator('Relu'))\n        self.assertFalse(core.IsOperator('NOEXIST'))\n\n    def test_is_operator_with_engine(self):\n        self.assertTrue(core.IsOperatorWithEngine('Relu', 'DEFAULT'))\n        self.assertFalse(core.IsOperatorWithEngine('Relu', 'NOEXIST'))\n\n\nclass TestDeviceOption(test_util.TestCase):\n    def test_check_equal_node_name(self):\n        opt1 = core.DeviceOption(0)\n        opt2 = core.DeviceOption(0)\n        self.assertTrue(core.device_option_equal(opt1, opt2))\n        opt2.node_name = 'test'\n        self.assertTrue(core.device_option_equal(opt1, opt2))\n        self.assertFalse(core.device_option_equal(opt1, opt2, ignore_node_name=False))\n        opt1.node_name = 'test'\n        self.assertTrue(core.device_option_equal(opt1, opt2, ignore_node_name=False))\n\n    def test_check_equal_default_value(self):\n        opt1 = caffe2_pb2.DeviceOption()\n        opt2 = caffe2_pb2.DeviceOption()\n        opt1.device_type = 0\n        self.assertTrue(core.device_option_equal(opt1, opt2))\n        opt1.cuda_gpu_id = 5\n        # opt1 still is on CPU, so the options should be equal\n        self.assertTrue(core.device_option_equal(opt1, opt2))\n        opt2.device_type = 0\n        self.assertTrue(core.device_option_equal(opt1, opt2))\n        opt1.device_type = 1\n        self.assertFalse(core.device_option_equal(opt1, opt2))\n\n\n@unittest.skipIf(not workspace.has_gpu_support, 'No GPU support')\nclass TestInferDevice(test_util.TestCase):\n\n    def setUp(self):\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.device_type = caffe2_pb2.CUDA\n        device_option.cuda_gpu_id = 1\n        self.cuda_option = device_option\n        self.cpu_option = caffe2_pb2.DeviceOption()\n\n    def _test_op(\n        self,\n        op_name,\n        in_option,\n        out_option,\n        op_option=None,\n        inputs=None,\n        outputs=None\n    ):\n        op_option = self.cuda_option if not op_option else op_option\n        inputs = [\"blob_1\"] if not inputs else inputs\n        outputs = [\"blob_2\"] if not outputs else outputs\n        with core.DeviceScope(op_option):\n            op = core.CreateOperator(op_name, inputs, outputs)\n        input_dev, output_dev = core.InferOpBlobDevices(op)\n        for in_dev in input_dev:\n            self.assertEqual(in_dev, in_option)\n        for out_dev in output_dev:\n            self.assertEqual(out_dev, out_option)\n\n    def test_infer_device(self):\n        self._test_op(\n            \"FC\",\n            self.cuda_option,\n            self.cuda_option,\n            op_option=self.cuda_option,\n            inputs=[\"data\", \"fc_w\", \"fc_b\"],\n            outputs=[\"fc_1\"]\n        )\n\n    def test_infer_device_cross_device(self):\n        self._test_op(\"CopyGPUToCPU\", self.cuda_option, self.cpu_option)\n        self._test_op(\"CopyCPUToGPU\", self.cpu_option, self.cuda_option)\n        self._test_op(\"EnsureCPUOutput\", self.cuda_option, self.cpu_option)\n        self._test_op(\"CopyFromCPUInput\", self.cpu_option, self.cuda_option)\n        self._test_op(\n            \"EnsureCPUOutput\",\n            self.cpu_option,\n            self.cpu_option,\n            op_option=self.cpu_option\n        )\n        self._test_op(\n            \"CopyFromCPUInput\",\n            self.cpu_option,\n            self.cpu_option,\n            op_option=self.cpu_option\n        )\n\n    def test_inject_copy(self):\n        net = core.Net(\"test\")\n        init_net = core.Net(\"init\")\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.device_type = caffe2_pb2.CUDA\n        device_option.cuda_gpu_id = 1\n        weight = init_net.XavierFill([], 'fc_w', shape=[10, 100])\n        bias = init_net.ConstantFill([], 'fc_b', shape=[10, ])\n\n        with core.DeviceScope(device_option):\n            net.FC([\"data\", weight, bias], \"fc1\")\n\n        _, blob_to_device = core.InjectCrossDeviceCopies(init_net)\n        new_net, blob_to_device = core.InjectCrossDeviceCopies(\n            net, blob_to_device\n        )\n        op = new_net._net.op[-1]\n        self.assertEqual(op.type, \"FC\")\n        self.assertEqual(op.input[0], \"data_cuda_1\")\n        self.assertEqual(op.input[1], \"fc_w_cuda_1\")\n        self.assertEqual(op.input[2], \"fc_b_cuda_1\")\n        self.assertEqual(op.device_option.device_type, 1)\n        self.assertEqual(op.device_option.cuda_gpu_id, 1)\n        self.assertEqual(new_net._net.op[-2].type, \"CopyCPUToGPU\")\n        self.assertEqual(new_net._net.op[0].type, \"CopyCPUToGPU\")\n        self.assertNotEqual(blob_to_device[\"fc_w\"], device_option)\n\n    def test_cross_nets(self):\n        net = core.Net(\"test\")\n        init_net = core.Net(\"init\")\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.device_type = caffe2_pb2.CUDA\n        device_option.cuda_gpu_id = 1\n        weight = init_net.XavierFill([], 'fc_w', shape=[10, 100])\n        bias = init_net.ConstantFill([], 'fc_b', shape=[10, ])\n        const = init_net.ConstantFill([], 'const', shape=[], value=1.)\n        with core.DeviceScope(device_option):\n            const = init_net.Add([const, const], [const])\n            fc_out = net.FC([\"data\", weight, bias], \"fc1\")\n            net.Add([fc_out, const], [fc_out])\n\n        data_remap = {'data': device_option}\n        nets, _ = core.InjectDeviceCopiesAmongNets(\n            [init_net, net], blob_to_device_init=data_remap\n        )\n        op = nets[1]._net.op[0]\n        self.assertEqual(op.type, \"CopyCPUToGPU\")\n        self.assertEqual(op.device_option.device_type, 1)\n        self.assertEqual(op.device_option.cuda_gpu_id, 1)\n        self.assertEqual(op.output[0], \"fc_w_cuda_1\")\n        op = nets[1]._net.op[1]\n        self.assertEqual(op.type, \"CopyCPUToGPU\")\n        self.assertEqual(op.device_option.device_type, 1)\n        self.assertEqual(op.device_option.cuda_gpu_id, 1)\n        self.assertEqual(op.output[0], \"fc_b_cuda_1\")\n        op = nets[1]._net.op[2]\n        self.assertEqual(op.type, \"FC\")\n        self.assertEqual(op.input[0], \"data\")\n        self.assertEqual(op.input[1], \"fc_w_cuda_1\")\n        self.assertEqual(op.input[2], \"fc_b_cuda_1\")\n        self.assertEqual(op.device_option.device_type, 1)\n        self.assertEqual(op.device_option.cuda_gpu_id, 1)\n        op = nets[1]._net.op[3]\n        self.assertEqual(op.type, \"Add\")\n        self.assertEqual(op.input[0], \"fc1\")\n        self.assertEqual(op.input[1], \"const_cuda_1\")\n        # check that moved blob is in input to the new net\n        for c in [\"data\", \"fc_w\", \"fc_b\", \"const_cuda_1\"]:\n            self.assertTrue(c in nets[1]._net.external_input)\n        \"\"\"\nFor reference, net.Proto() should be like:\nname: \"\"\nop {\n  input: \"fc_w\"\n  output: \"fc_w_cuda_1\"\n  name: \"\"\n  type: \"CopyCPUToGPU\"\n  device_option {\n    device_type: 1\n    cuda_gpu_id: 1\n  }\n}\nop {\n  input: \"fc_b\"\n  output: \"fc_b_cuda_1\"\n  name: \"\"\n  type: \"CopyCPUToGPU\"\n  device_option {\n    device_type: 1\n    cuda_gpu_id: 1\n  }\n}\nop {\n  input: \"data\"\n  input: \"fc_w_cuda_1\"\n  input: \"fc_b_cuda_1\"\n  output: \"fc1\"\n  name: \"\"\n  type: \"FC\"\n  device_option {\n    device_type: 1\n    cuda_gpu_id: 1\n  }\n}\nop {\n  input: \"fc1\"\n  input: \"const_cuda_1\"\n  output: \"fc1\"\n  name: \"\"\n  type: \"Add\"\n  device_option {\n    device_type: 1\n    cuda_gpu_id: 1\n  }\n}\nexternal_input: \"data\"\nexternal_input: \"fc_w\"\nexternal_input: \"fc_b\"\nexternal_input: \"const\"\nexternal_input: \"const_cuda_1\"\n\"\"\"\n\n    def test_cross_nets_no_change(self):\n        net = core.Net(\"test\")\n        init_net = core.Net(\"init\")\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.device_type = caffe2_pb2.CUDA\n        device_option.cuda_gpu_id = 1\n\n        with core.DeviceScope(device_option):\n            weight = init_net.XavierFill([], 'fc_w', shape=[10, 100])\n            bias = init_net.ConstantFill([], 'fc_b', shape=[10, ])\n            net.FC([\"data\", weight, bias], \"fc1\")\n\n        data_remap = {'data': device_option}\n        nets = core.InjectDeviceCopiesAmongNetsWithoutB2D(\n            [init_net, net], blob_to_device_init=data_remap\n        )\n        op = nets[1]._net.op[0]\n        self.assertEqual(op.type, \"FC\")\n        self.assertEqual(op.input[0], \"data\")\n        self.assertEqual(op.input[1], \"fc_w\")\n        self.assertEqual(op.input[2], \"fc_b\")\n        self.assertEqual(op.device_option.device_type, 1)\n        self.assertEqual(op.device_option.cuda_gpu_id, 1)\n        \"\"\"\nFor reference, net.Proto() should be like:\nname: \"\"\nop {\n  input: \"data\"\n  input: \"fc_w\"\n  input: \"fc_b\"\n  output: \"fc1\"\n  name: \"\"\n  type: \"FC\"\n  device_option {\n    device_type: 1\n    cuda_gpu_id: 1\n  }\n}\nexternal_input: \"data\"\nexternal_input: \"fc_w\"\nexternal_input: \"fc_b\"\n\"\"\"\n\n    def test_inject_copy_multi_use(self):\n        net = core.Net(\"test\")\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.device_type = caffe2_pb2.CUDA\n        device_option.cuda_gpu_id = 1\n\n        with core.DeviceScope(device_option):\n            net.Relu(\"data\", \"relu1\")\n        net.Relu(\"data\", \"relu2\")\n        with core.DeviceScope(device_option):\n            net.Relu(\"data\", \"relu3\")\n        net.Relu(\"data\", \"relu4\")\n        device_option.cuda_gpu_id = 0\n        with core.DeviceScope(device_option):\n            net.Relu(\"data\", \"relu5\")\n        device_option.cuda_gpu_id = 1\n        with core.DeviceScope(device_option):\n            net.Relu(\"data\", \"relu6\")\n\n        new_net, _ = core.InjectCrossDeviceCopies(net)\n        op = new_net._net.op[0]\n        self.assertEqual(op.type, \"CopyCPUToGPU\")\n        self.assertEqual(op.device_option.device_type, 1)\n        self.assertEqual(op.device_option.cuda_gpu_id, 1)\n        self.assertEqual(op.output[0], \"data_cuda_1\")\n        op = new_net._net.op[1]\n        self.assertEqual(op.type, \"Relu\")\n        self.assertEqual(op.device_option.device_type, 1)\n        self.assertEqual(op.device_option.cuda_gpu_id, 1)\n        self.assertEqual(op.output[0], \"relu1\")\n        op = new_net._net.op[2]\n        self.assertEqual(op.type, \"Relu\")\n        self.assertEqual(op.device_option.device_type, 0)\n        self.assertEqual(op.output[0], \"relu2\")\n        op = new_net._net.op[3]\n        self.assertEqual(op.type, \"Relu\")\n        self.assertEqual(op.device_option.device_type, 1)\n        self.assertEqual(op.device_option.cuda_gpu_id, 1)\n        self.assertEqual(op.input[0], \"data_cuda_1\")\n        self.assertEqual(op.output[0], \"relu3\")\n        op = new_net._net.op[4]\n        self.assertEqual(op.type, \"Relu\")\n        self.assertEqual(op.device_option.device_type, 0)\n        self.assertEqual(op.output[0], \"relu4\")\n        op = new_net._net.op[5]\n        self.assertEqual(op.type, \"CopyCPUToGPU\")\n        self.assertEqual(op.device_option.device_type, 1)\n        self.assertEqual(op.device_option.cuda_gpu_id, 0)\n        self.assertEqual(op.output[0], \"data_cuda_0\")\n        op = new_net._net.op[6]\n        self.assertEqual(op.type, \"Relu\")\n        self.assertEqual(op.device_option.device_type, 1)\n        self.assertEqual(op.device_option.cuda_gpu_id, 0)\n        self.assertEqual(op.input[0], \"data_cuda_0\")\n        self.assertEqual(op.output[0], \"relu5\")\n        op = new_net._net.op[7]\n        self.assertEqual(op.type, \"Relu\")\n        self.assertEqual(op.device_option.device_type, 1)\n        self.assertEqual(op.device_option.cuda_gpu_id, 1)\n        self.assertEqual(op.input[0], \"data_cuda_1\")\n        self.assertEqual(op.output[0], \"relu6\")\n        \"\"\"\nFor reference, net.Proto() should be like:\nname: \"\"\nop {\n  input: \"data\"\n  output: \"data_cuda_1\"\n  name: \"\"\n  type: \"CopyCPUToGPU\"\n  device_option {\n    device_type: 1\n    cuda_gpu_id: 1\n  }\n}\nop {\n  input: \"data_cuda_1\"\n  output: \"relu1\"\n  name: \"\"\n  type: \"Relu\"\n  device_option {\n    device_type: 1\n    cuda_gpu_id: 1\n  }\n}\nop {\n  input: \"data\"\n  output: \"relu2\"\n  name: \"\"\n  type: \"Relu\"\n}\nop {\n  input: \"data_cuda_1\"\n  output: \"relu3\"\n  name: \"\"\n  type: \"Relu\"\n  device_option {\n    device_type: 1\n    cuda_gpu_id: 1\n  }\n}\nop {\n  input: \"data\"\n  output: \"relu4\"\n  name: \"\"\n  type: \"Relu\"\n}\nop {\n  input: \"data\"\n  output: \"data_cuda_0\"\n  name: \"\"\n  type: \"CopyCPUToGPU\"\n  device_option {\n    device_type: 1\n    cuda_gpu_id: 0\n  }\n}\nop {\n  input: \"data_cuda_0\"\n  output: \"relu5\"\n  name: \"\"\n  type: \"Relu\"\n  device_option {\n    device_type: 1\n    cuda_gpu_id: 0\n  }\n}\nop {\n  input: \"data_cuda_1\"\n  output: \"relu6\"\n  name: \"\"\n  type: \"Relu\"\n  device_option {\n    device_type: 1\n    cuda_gpu_id: 1\n  }\n}\nexternal_input: \"data\"\n\"\"\"\n\n    def test_blob_inplace(self):\n        net = core.Net(\"test\")\n        device_option = caffe2_pb2.DeviceOption()\n        device_option.device_type = caffe2_pb2.CUDA\n        device_option.cuda_gpu_id = 1\n\n        net.Adagrad(['param', 'moment', 'grad', 'lr'], ['param', 'moment'])\n        with core.DeviceScope(device_option):\n            net.Relu(\"param\", \"param_relu_no_sense\")\n        net, _ = core.InjectCrossDeviceCopies(net)\n        op = net._net.op[1]\n        self.assertEqual(op.type, 'CopyCPUToGPU')\n        self.assertEqual(op.input[0], 'param')\n        self.assertEqual(op.output[0], 'param_cuda_1')\n        op = net._net.op[2]\n        self.assertEqual(op.input[0], 'param_cuda_1')\n\n        net.Relu('nonsense_input', 'moment')\n        # should not raise inplace error\n        core.InjectCrossDeviceCopies(net)\n        with core.DeviceScope(device_option):\n            net.Relu('nonsense_input_gpu', 'moment')\n        with self.assertRaises(RuntimeError):\n            core.InjectCrossDeviceCopies(net)\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/crf.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package crf\n# Module caffe2.python.crf\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, recurrent, model_helper, brew\nimport numpy as np\n\n'''\nDue to a limitation in ReccurentNetworkOp, this layer only supports batch_size=1\nIn order to support batch_size > 1, we will have to implement the CRFUnit\nand its gradient in C++ and handle the different batches there.\n'''\n\n\nclass CRFWithLoss(object):\n    def __init__(self, model, num_classes, transitions_blob=None):\n        self.model = model\n        self.num_classes = num_classes\n        self.num_classes_padded = num_classes + 2  # After adding BOS and EOS\n        if not transitions_blob:\n            transitions_blob = self.model.param_init_net.UniformFill(\n                [],\n                [core.ScopedBlobReference('crf_transitions')],\n                shape=[self.num_classes_padded, self.num_classes_padded],\n                min=-1.0,\n                max=1.0\n            )\n        self.transitions = transitions_blob\n        self.model.params.append(self.transitions)\n\n    def crf_loss(self, predictions, labels, seq_lengths=None):\n        # Since the transitions matrix is a shared parameter, need to\n        # take a snapshot of it at the beginning since it can be updated\n        # in between the operators that uses it when doing parallel updates\n        transitions_snapshot = self.model.net.Copy(\n            self.transitions, core.ScopedBlobReference('transitions_snapshot')\n        )\n        # Compute best path unary score from the logits\n        path_unary_score = self._gather_entries_sum(\n            predictions, labels, self.num_classes\n        )\n        # Append BOS and EOS entries to the predictions and labels\n        predictions = self._pad_predictions(predictions)\n        labels = self._pad_labels(labels)\n        # Compute best path binary scores from the transitions matrix\n        path_binary_score = self._path_binary_scores(\n            labels, transitions_snapshot, seq_lengths\n        )\n        path_total_score = self.model.net.Add(\n            [path_binary_score, path_unary_score],\n            core.ScopedBlobReference('path_total')\n        )\n        # Compute all paths score\n        zero_index = self.model.param_init_net.ConstantFill(\n            [], shape=[1], value=0\n        )\n        initial_state = self.model.net.Gather(\n            [predictions, zero_index],\n            core.ScopedBlobReference('rnn_initial'),\n            dense_gradient=True\n        )\n        input_data, _ = self.model.net.RemovePadding(\n            [predictions],\n            padding_width=1,\n            end_padding_width=0,\n            outputs=2,\n        )\n        input_data = self.model.net.ExpandDims(\n            [input_data],\n            core.ScopedBlobReference('rnn_input_data'),\n            dims=[1]\n        )\n        # Due to a bug in RecurrentNetworkGradientOp, we need to copy the\n        # transitions blob before sending it to the recurrent network\n        transitions_copy = self.model.net.Copy(\n            transitions_snapshot, core.ScopedBlobReference('transitions_copy')\n        )\n        all_paths_scores = self._crf_forward(\n            input_data, initial_state, transitions_copy\n        )\n        loss = self.model.net.Sub(\n            [all_paths_scores, path_total_score],\n            core.ScopedBlobReference('crf_loss')\n        )\n        return loss\n\n    def _pad_predictions(self, predictions):\n        # This function will introduce two labels for beginning of sequence\n        # And end of sequence, it will make the necessary udpates to the\n        # the predictions blob\n\n        low_score = -1000.0  # An arbitray very low number\n        b_scores = np.array(\n            [[low_score] * self.num_classes + [0, low_score]]\n        ).astype(np.float32)\n\n        e_scores = np.array(\n            [[low_score] * self.num_classes + [low_score, 0]]\n        ).astype(np.float32)\n\n        b_scores = self.model.param_init_net.GivenTensorFill(\n            [], \"b_scores\", shape=[1, self.num_classes_padded], values=b_scores\n        )\n        e_scores = self.model.param_init_net.GivenTensorFill(\n            [], \"e_scores\", shape=[1, self.num_classes_padded], values=e_scores\n        )\n\n        zero_index = self.model.net.ConstantFill(\n            [], shape=[1, ], value=0\n        )\n        length = self.model.net.Gather(\n            [self.model.net.Shape([predictions]), zero_index],\n        )\n        length = self.model.net.Cast(length, to='int32')\n        t_range = self.model.net.LengthsRangeFill(length)\n        padding = self.model.net.ConstantFill([t_range], value=low_score)\n        padding = self.model.net.ExpandDims(padding, dims=[1])\n        padded_predictions, _ = self.model.net.Concat(\n            [predictions, padding, padding],\n            outputs=2,\n            axis=1\n        )\n        padded_predictions_concat, _ = self.model.net.Concat(\n            [b_scores, padded_predictions, e_scores],\n            outputs=2,\n            axis=0\n        )\n        return padded_predictions_concat\n\n    def _pad_labels(self, labels):\n        bos_i = self.num_classes\n        eos_i = self.num_classes + 1\n        bos_i_b = self.model.param_init_net.ConstantFill(\n            [], shape=[1], value=bos_i\n        )\n        eos_i_b = self.model.param_init_net.ConstantFill(\n            [], shape=[1], value=eos_i\n        )\n        labels = self.model.net.Cast([labels], to='int64')\n        padded_labels, _ = self.model.net.Concat(\n            [bos_i_b, labels, eos_i_b],\n            axis=0,\n            outputs=2\n        )\n        return padded_labels\n\n    def _path_binary_scores(self, labels, transitions, seq_lengths=None):\n        column_ids, _ = self.model.net.RemovePadding(\n            [labels],\n            outputs=2,\n            padding_width=1,\n            end_padding_width=0\n        )\n        row_ids, _ = self.model.net.RemovePadding(\n            [labels],\n            outputs=2,\n            padding_width=0,\n            end_padding_width=1\n        )\n        # Since there is no multi-dimensional gather, I flatten the matrix to\n        # a 1-d vector and transform the ids to (row_ids * num_columns +\n        # column_ids) and do gather in 1-d\n        num_columns_blob = self.model.net.ConstantFill(\n            [row_ids],\n            value=self.num_classes_padded,\n        )\n        flattened_ids = self.model.net.Mul([row_ids, num_columns_blob])\n        flattened_ids = self.model.net.Add([flattened_ids, column_ids])\n        flattened_transitions = self.model.net.FlattenToVec([transitions])\n        entries = self.model.net.Gather(\n            [flattened_transitions, flattened_ids],\n            dense_gradient=True\n        )\n        return self.model.ReduceFrontSum(entries)\n\n    def _gather_entries_sum(self, in_data, indices, index_size):\n        indices = self.model.net.Cast([indices], to='int64')\n        index_size_blob = self.model.param_init_net.ConstantFill(\n            [],\n            shape=[1],\n            value=index_size,\n        )\n        query_one_hot = self.model.net.OneHot(\n            [indices, index_size_blob]\n        )\n        flattend_query = self.model.net.FlattenToVec(query_one_hot)\n        flattend_data = self.model.net.FlattenToVec(in_data)\n        query_scores = self.model.net.DotProduct(\n            [flattend_query, flattend_data]\n        )\n        final_sum = self.model.net.ReduceFrontSum([query_scores])\n        return final_sum\n\n    def _crf_forward(\n        self,\n        input_blob,\n        initial_state,\n        transitions_copy,\n        seq_lengths=None\n    ):\n        # Build the RNN net and get the last timestep output\n        out_last = self.build_crf_net(\n            input_blob, initial_state, transitions_copy\n        )\n        out_last, _ = self.model.net.Reshape(\n            [out_last],\n            outputs=2,\n            shape=(self.num_classes_padded,)\n        )\n        zero_segment_id = self.model.param_init_net.ConstantFill(\n            [],\n            value=0,\n            shape=[self.num_classes_padded],\n            dtype=core.DataType.INT32,\n        )\n\n        # Compute the accumlated total score of all the paths\n        accum_score = self.model.net.SortedSegmentRangeLogSumExp(\n            [out_last, zero_segment_id]\n        )\n        accum_score, _ = self.model.net.Reshape(\n            accum_score,\n            outputs=2,\n            shape=()\n        )\n        return accum_score\n\n    def build_crf_net(self, input_blob, initial_state, transitions):\n            '''\n            Adds the crf_net recurrent operator to the model.\n\n            model: model_helper.ModelHelper object new operators would be added\n            to\n\n            input_blob: the input sequence in a format T x N x D\n            where T is sequence size, N - batch size and D - input dimention\n            ##Only supports batch-size 1##\n\n            seq_lengths: blob containing sequence lengths (unused)\n            '''\n\n            scope = 'crf_net'\n\n            def s(name):\n                ''\n                # We have to manually scope due to our internal/external blob\n                # relationships.\n                return \"{}/{}\".format(str(scope), str(name))\n\n            step_model = model_helper.ModelHelper(name='crf_step',\n                                                  param_model=self.model)\n            input_t, cell_t_prev, _ = (\n                step_model.net.AddExternalInputs(\n                    core.ScopedBlobReference('input_t'),\n                    core.ScopedBlobReference('cell_t_prev'),\n                    transitions\n                )\n            )\n            zero_segment_id = step_model.param_init_net.ConstantFill(\n                [],\n                [s('zero_segment_id')],\n                value=0,\n                shape=[self.num_classes_padded],\n                dtype=core.DataType.INT32,\n            )\n\n            # A hack to bypass model cloning for test\n            step_model.param_init_net.AddExternalOutput(zero_segment_id)\n            \"\"\" the CRF step \"\"\"\n            # Do tile\n            prev_transpose = brew.transpose(\n                step_model,\n                cell_t_prev,\n                [s('prev_transpose')],\n                axes=(0, 2, 1),\n            )\n            prev_tiled = step_model.net.Tile(\n                prev_transpose,\n                [s('prev_tiled')],\n                tiles=self.num_classes_padded,\n                axis=2,\n            )\n            input_t_tiled = step_model.net.Tile(\n                input_t,\n                [s('input_t_tiled')],\n                tiles=self.num_classes_padded,\n                axis=1,\n            )\n            input_with_prev = step_model.net.Add(\n                [prev_tiled, input_t_tiled],\n                [s('input_with_prev')]\n            )\n            all_with_transitions = step_model.net.Add(\n                [input_with_prev, transitions],\n                [s('prev_with_transitions')],\n                broadcast=1,\n                use_grad_hack=1,\n            )\n            all_with_transitions_reshaped, _ = step_model.net.Reshape(\n                all_with_transitions,\n                [s('all_with_transitions_reshaped'), s('all_with_transitions_orig')],\n                shape=(self.num_classes_padded, self.num_classes_padded)\n            )\n            cell_t = step_model.net.SortedSegmentRangeLogSumExp(\n                [all_with_transitions_reshaped, zero_segment_id],\n                [s('cell_t')],\n            )\n            step_model.net.AddExternalOutputs(cell_t)\n            \"\"\" recurrent network \"\"\"\n            cell_input_blob = initial_state\n            out_all, out_last = recurrent.recurrent_net(\n                net=self.model.net,\n                cell_net=step_model.net,\n                inputs=[(input_t, input_blob)],\n                initial_cell_inputs=[\n                    (cell_t_prev, cell_input_blob),\n                ],\n                links={\n                    cell_t_prev: cell_t,\n                },\n                scope=scope,\n                outputs_with_grads=(1,)\n            )\n            return out_last\n\n    def update_predictions(self, classes):\n\n        def crf_update_predictions_op(inputs, outputs):\n            # This operator will compute the best path of classes by performing\n            # Viterbi decoding and then updates the predictions to make the tag\n            # On the best path has the highest score among the others\n            predictions = inputs[0].data\n            transitions = inputs[1].data\n            predictions = inputs[0].data\n            predictions_shape = inputs[0].shape\n            outputs[0].reshape(predictions_shape)\n\n            trellis = np.zeros(predictions_shape)\n            backpointers = np.zeros(predictions_shape, dtype=np.int32)\n            trellis[0] = predictions[0]\n\n            for t in range(1, predictions_shape[0]):\n                v = np.expand_dims(trellis[t - 1], 1) + transitions\n                trellis[t] = predictions[t] + np.max(v, 0)\n                backpointers[t] = np.argmax(v, 0)\n\n            viterbi = [np.argmax(trellis[-1])]\n            for bp in reversed(backpointers[1:]):\n                viterbi.append(bp[viterbi[-1]])\n            viterbi.reverse()\n\n            new_predictions = np.zeros(predictions_shape)\n            old_bests = []\n            for i, w_predictions in enumerate(predictions):\n                # Get the current tag with the maximum score\n                new_predictions[i] = predictions[i]\n                old_best = np.argmax(w_predictions)\n                old_bests.append(old_best)\n                # Swap the scores of the current best tag and the tag on the\n                # Viterbi path\n                w_predictions[viterbi[i]], w_predictions[old_best] = \\\n                    w_predictions[old_best], w_predictions[viterbi[i]]\n                new_predictions[i] = w_predictions\n            # Remove the BOS and EOS entries from the predictions matrix\n            orig_predictions = new_predictions[1:-1, 0:-2]\n            outputs[0].reshape(orig_predictions.shape)\n            outputs[0].data[...] = orig_predictions\n        padded_classes = self._pad_predictions(classes)\n        new_classes = self.model.net.Python(crf_update_predictions_op)(\n            [padded_classes, self.transitions],\n            core.ScopedBlobReference('post_crf_classes')\n        )\n        return new_classes\n"
  },
  {
    "path": "caffe2/python/data_parallel_model.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package data_parallel_model\n# Module caffe2.python.data_parallel_model\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nfrom collections import OrderedDict\nfrom future.utils import viewitems, viewkeys, viewvalues\nimport logging\nimport copy\n\nfrom caffe2.python import \\\n    model_helper, dyndep, scope, workspace, core, memonger, utils\nfrom caffe2.proto import caffe2_pb2\n\nimport numpy as np\n\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/contrib/nccl:nccl_ops\")\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/contrib/gloo:gloo_ops\")\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/contrib/gloo:gloo_ops_gpu\")\n\nlog = logging.getLogger(\"data_parallel_model\")\nlog.setLevel(logging.INFO)\n\n_DEFAULT_TIMEOUT_SEC = 30\n\n\ndef Parallelize_GPU(*args, **kwargs):\n    kwargs['cpu_device'] = False\n    Parallelize(*args, **kwargs)\n\n\ndef Parallelize_CPU(*args, **kwargs):\n    kwargs['cpu_device'] = True\n    Parallelize(*args, **kwargs)\n\n\ndef Parallelize(\n    model_helper_obj,\n    input_builder_fun,\n    forward_pass_builder_fun,\n    param_update_builder_fun=None,\n    optimizer_builder_fun=None,\n    post_sync_builder_fun=None,\n    devices=None,\n    rendezvous=None,\n    net_type='dag',\n    broadcast_computed_params=True,\n    optimize_gradient_memory=False,\n    dynamic_memory_management=False,\n    blobs_to_keep=None,\n    use_nccl=False,\n    max_concurrent_distributed_ops=16,\n    cpu_device=False,\n    num_threads_per_device=4,\n    shared_model=False,\n    combine_spatial_bn=False,\n):\n    '''\n    Function to create a model that can run on many GPUs or CPUs.\n      model_helper_obj: an object of ModelHelper\n      input_builder_fun:\n                         Function that adds the input operators\n                         Note: Remember to instantiate reader outside of this\n                         function so all devices share same reader object.\n                         Signature:  input_builder_fun(model)\n      forward_pass_builder_fun:\n                        Function to add the operators to the model.\n                        Must return list of loss-blob references that\n                        are used to build the gradient. Loss scale parameter\n                        is passed, as you should scale the loss of your model\n                        by 1.0 / the total number of devices.\n                        Signature: forward_pass_builder_fun(model, loss_scale)\n      param_update_builder_fun:\n                        Function that adds operators that are run after\n                        gradient update, such as updating the weights and\n                        weight decaying. This is called for each GPU separately.\n                        Signature: param_update_builder_fun(model)\n      optimizer_builder_fun:\n                        Alternative to param_update_builder_fun, allows one\n                        to add an optimizer for the whole model. Called only\n                        once, without name or devicescope.\n      post_sync_builder_fun:\n                        Function applied after initial parameter sync has been\n                        completed, such as keeping multi-precision parameters\n                        in sync.\n                        Signature: post_sync_builder_fun(model)\n      devices:          List of GPU ids, such as [0, 1, 2, 3],\n      rendezvous:       used for rendezvous in distributed computation, if None\n                        then only one node is used. To create rendezvous,\n                        use <TBD>.\n      net_type:         Network type\n      optimize_gradient_memory: whether to apply 'memonger' to share blobs\n      shared_model      (only for CPU) use same parameters on each device\n                        in gradient computation to reduce memory footprint.\n      dynamic_memory_management: Whether to apply dynamic memory optimization\n                        by freeing unused blobs. The underlying (de)allocation\n                        uses cached allocator. For GPU training PLEASE MAKE SURE\n                        caffe2_cuda_memory_pool is set.\n      blobs_to_keep :   A list of blob names to keep and don't free during\n                        dynamic memory optimization (for example loss blob).\n      cpu_device        Use CPU instead of GPU.\n      combine_spatial_bn:\n                        When set to True, applies batch normalization across\n                        all devices within the node. If False, batch\n                        normalization will be done separately for each device.\n                        This option is currently only supported on the CPU.\n    '''\n    assert scope.CurrentDeviceScope() is None \\\n        or scope.CurrentDeviceScope().device_type == caffe2_pb2.CPU, \\\n        \"Parallelize must be called without device-scope, \\\n        device scope was: {}\".format(scope.CurrentDeviceScope())\n\n    if devices is None:\n        devices = list(range(0, workspace.NumCudaDevices())),\n\n    if not cpu_device:\n        for gpu in devices:\n            if gpu >= workspace.NumCudaDevices():\n                log.warning(\"** Only {} GPUs available, GPUs {} requested\".format(\n                    workspace.NumCudaDevices(), devices))\n                break\n        model_helper_obj._device_type = caffe2_pb2.CUDA\n        model_helper_obj._device_prefix = \"gpu\"\n        model_helper_obj._shared_model = False\n        device_name = \"GPU\"\n        assert shared_model is False, \"Shared model only supported on CPU\"\n    else:\n        model_helper_obj._device_type = caffe2_pb2.CPU\n        model_helper_obj._device_prefix = \"cpu\"\n        device_name = \"CPU\"\n        model_helper_obj._shared_model = shared_model\n        if shared_model and rendezvous is not None:\n            assert \"Shared model only supported on single-node currently\"\n\n    log.info(\"Parallelizing model for devices: {}\".format(devices))\n    extra_workers = 8 if rendezvous is not None else 0  # best-guess\n    num_workers = len(devices) * num_threads_per_device + extra_workers\n    max_concurrent_distributed_ops =\\\n        min(max_concurrent_distributed_ops, num_workers - 1)\n    model_helper_obj.net.Proto().num_workers = num_workers\n    model_helper_obj.net.Proto().type = net_type\n\n    # Store some information in the model -- a bit ugly\n    model_helper_obj._devices = devices\n    model_helper_obj._rendezvous = rendezvous\n    model_helper_obj._broadcast_context = None\n    model_helper_obj._grad_names = []\n\n    assert isinstance(model_helper_obj, model_helper.ModelHelper)\n\n    # Keep track of params that were in the model before: they are not\n    # data parallel, so we need to handle them separately\n    non_datapar_params = copy.copy(model_helper_obj.params)\n\n    # Add input and model\n    log.info(\"Create input and model training operators\")\n\n    losses_by_gpu = {}\n    num_shards = 1 if rendezvous is None else rendezvous['num_shards']\n    loss_scale = 1.0 / (len(devices) * num_shards)\n\n    has_parameter_updates = param_update_builder_fun is not None or \\\n        optimizer_builder_fun is not None\n    assert not (\n        param_update_builder_fun is not None and\n        optimizer_builder_fun is not None\n    ), 'Can only specify one of param_update_builder_fun, optimizer_builder_fun'\n\n    # Check that a model that is used for validation/testing has\n    # init_params False, otherwise running the param init net will overwrite\n    # synchronized values by the training net\n    if not has_parameter_updates and model_helper_obj.init_params:\n        log.warning('')\n        log.warning(\"############# WARNING #############\")\n        log.warning(\"Model {}/{} is used for testing/validation but\".format(\n            model_helper_obj.name, model_helper_obj))\n        log.warning(\"has init_params=True!\")\n        log.warning(\"This can conflict with model training.\")\n        log.warning(\"Please ensure model = ModelHelper(init_params=False)\")\n        log.warning('####################################')\n        log.warning('')\n        # TODO: make into assert\n\n    for device in devices:\n        device_opt = core.DeviceOption(model_helper_obj._device_type, device)\n        with core.DeviceScope(device_opt):\n            with core.NameScope(\"{}_{}\".format(model_helper_obj._device_prefix,\n                                               device)):\n                log.info(\"Model for {} : {}\".format(device_name, device))\n                input_builder_fun(model_helper_obj)\n                losses = forward_pass_builder_fun(model_helper_obj, loss_scale)\n                # Losses are not needed for test net\n                if has_parameter_updates:\n                    assert isinstance(losses, list), \\\n                        'Model builder function must return list of loss blobs'\n                    for loss in losses:\n                        assert isinstance(loss, core.BlobReference), \\\n                            'Model builder func must return list of loss blobs'\n\n                losses_by_gpu[device] = losses\n    _ValidateParams(model_helper_obj.params)\n\n    # Create parameter map\n    model_helper_obj._device_grouped_blobs =\\\n        _GroupByDevice(model_helper_obj, devices,\n                       model_helper_obj.params, non_datapar_params)\n\n    # computed params\n    computed_params_grouped =\\\n        _GroupByDevice(model_helper_obj, devices,\n                       model_helper_obj.GetComputedParams(''), [])\n    model_helper_obj._device_grouped_blobs.update(computed_params_grouped)\n\n    model_helper_obj._param_names =\\\n        list(viewkeys(model_helper_obj._device_grouped_blobs))\n    model_helper_obj._computed_param_names =\\\n        list(viewkeys(computed_params_grouped))\n\n    if not has_parameter_updates:\n        log.info(\"Parameter update function not defined --> only forward\")\n        _InferBlobDevice(model_helper_obj)\n        return\n\n    log.info(\"Adding gradient operators\")\n    _AddGradientOperators(devices, model_helper_obj, losses_by_gpu)\n\n    if combine_spatial_bn:\n        assert(cpu_device), \\\n            'combine_spatial_bn is currently only supported on the CPU'\n        assert(has_parameter_updates), \\\n            'combine_spatial_bn should only be used for train model'\n        _InterleaveOps(model_helper_obj)\n        _InterDeviceBatchNormalization(model_helper_obj)\n\n    _ValidateParams(model_helper_obj.params)\n\n    # Group gradients by device and register to blob lookup\n    param_to_grad = model_helper_obj.param_to_grad\n    grads_ordered = [param_to_grad[p] for p in\n                     model_helper_obj.params if p in param_to_grad]\n    non_datapar_grads = [param_to_grad[p] for p in non_datapar_params]\n\n    gradients_grouped = _GroupByDevice(\n        model_helper_obj,\n        devices,\n        grads_ordered,\n        non_datapar_grads\n    )\n    model_helper_obj._device_grouped_blobs.update(gradients_grouped)\n    model_helper_obj._grad_names = list(viewkeys(gradients_grouped))\n    model_helper_obj._losses_by_gpu = losses_by_gpu\n\n    _InferBlobDevice(model_helper_obj)\n\n    log.info(\"Add gradient all-reduces for SyncSGD\")\n    if broadcast_computed_params:\n        _BroadcastComputedParams(devices, model_helper_obj, rendezvous, use_nccl)\n\n    if len(model_helper_obj._grad_names) > 0:\n        # Gradients in reverse order\n        reverse_ordered_grads = _GetReverseOrderedGrads(model_helper_obj)\n        assert(len(reverse_ordered_grads) > 0)\n        _AllReduceBlobs(\n            reverse_ordered_grads,\n            devices,\n            model_helper_obj,\n            model_helper_obj.net,\n            rendezvous,\n            use_nccl,\n            max_concurrent_distributed_ops,\n        )\n    else:\n        log.info(\"NOTE: Param builder function did not create any parameters.\")\n\n    log.info(\"Post-iteration operators for updating params\")\n    num_shards = 1 if rendezvous is None else rendezvous['num_shards']\n\n    all_params = set(model_helper_obj.GetParams(''))\n    if shared_model:\n        _PruneParametersForSharing(model_helper_obj)\n\n    if param_update_builder_fun is not None:\n        for device in devices:\n            device_opt = core.DeviceOption(model_helper_obj._device_type, device)\n            with core.DeviceScope(device_opt):\n                with core.NameScope(\n                    \"{}_{}\".format(model_helper_obj._device_prefix, device)\n                ):\n                    param_update_builder_fun(model_helper_obj)\n    else:\n        log.info(\"Calling optimizer builder function\")\n        optimizer = optimizer_builder_fun(model_helper_obj)\n        model_helper_obj._optimizer = optimizer\n\n    (sync_blobs, sync_names) = _ComputeBlobsToSync(model_helper_obj)\n    sync_blobs_grouped = _GroupByDevice(\n        model_helper_obj,\n        devices,\n        sync_blobs,\n        [],\n    )\n    model_helper_obj._device_grouped_blobs.update(sync_blobs_grouped)\n\n    _InferBlobDevice(model_helper_obj)\n    _AnalyzeOperators(model_helper_obj)\n\n    # Configure dagnet to run with only one worker on the first iteration,\n    # to prevent concurrency problems with allocs and nccl.\n    arg = model_helper_obj.Proto().arg.add()\n    arg.name = \"first_iter_only_one_worker\"\n    arg.i = 1\n\n    # Add initial parameter syncs\n    log.info(\"Add initial parameter sync\")\n    _SyncAllParams(\n        devices,\n        model_helper_obj,\n        model_helper_obj.param_init_net,\n        model_helper_obj.param_init_net,\n        rendezvous,\n        sync_names,\n        max_concurrent_distributed_ops=1\n    )\n\n    # Handle any operations that need to be done after parameter sync\n    # i.e. making sure multi-precision copies of parameters are up-to-date\n    if post_sync_builder_fun is not None:\n        for device in devices:\n            device_opt = core.DeviceOption(model_helper_obj._device_type, device)\n            with core.DeviceScope(device_opt):\n                with core.NameScope(\n                    \"{}_{}\".format(model_helper_obj._device_prefix, device)\n                ):\n                    post_sync_builder_fun(model_helper_obj)\n\n    assert not (optimize_gradient_memory and dynamic_memory_management), \\\n        \"\"\"It is not advised to use gradient optimization ('memonger')\n        with dynamic memory management.\"\"\"\n\n    if optimize_gradient_memory:\n        _OptimizeGradientMemorySimple(model_helper_obj, losses_by_gpu, devices)\n\n    if dynamic_memory_management:\n        _AddDynamicMemoryOptimization(model_helper_obj, blobs_to_keep, devices)\n\n    model_helper_obj._data_parallel_model_init_nets = [\n        model_helper_obj.param_init_net,\n    ]\n    model_helper_obj._data_parallel_model_nets = [model_helper_obj.net]\n\n    if shared_model:\n        _RemapParameterBlobsForSharedModel(model_helper_obj, all_params)\n\n\ndef Parallelize_GPU_BMUF(*args, **kwargs):\n    kwargs['cpu_device'] = False\n    Parallelize_BMUF(*args, **kwargs)\n\n\ndef Parallelize_CPU_BMUF(*args, **kwargs):\n    kwargs['cpu_device'] = True\n    Parallelize_BMUF(*args, **kwargs)\n\n\ndef Parallelize_BMUF(\n    model_helper_obj,\n    input_builder_fun,\n    forward_pass_builder_fun,\n    param_update_builder_fun,\n    block_learning_rate=1.0,\n    block_momentum=None,\n    devices=None,\n    rendezvous=None,\n    net_type='dag',\n    master_device=None,\n    use_nccl=False,\n    nesterov=False,\n    optimize_gradient_memory=False,\n    reset_momentum_sgd=False,\n    warmup_iterations=None,\n    max_concurrent_distributed_ops=4,\n    add_blobs_to_sync=None,\n    num_threads_per_device=4,\n    cpu_device=False\n):\n    '''\n    Function to create model that run on many GPUs and creates a net for\n    parameter_updates that can be run independently for number of iterations\n    then followed by another net that runs once to compute the final parameter\n    updates according to block wise model update filtering rule described\n    in : Scalable Training of Deep Learning Machines by Incremental Block\n    Training with Intra-block Parallel Optimization and Blockwise Model-Update\n    Filtering (ICASSP 2016).\n    '''\n    assert scope.CurrentDeviceScope() is None \\\n        or scope.CurrentDeviceScope().device_type == caffe2_pb2.CPU, \\\n        \"Parallelize must be called without device-scope, \\\n        device scope was: {}\".format(scope.CurrentDeviceScope())\n\n    assert isinstance(model_helper_obj, model_helper.ModelHelper)\n\n    if devices is None:\n        devices = list(range(0, workspace.NumCudaDevices()))\n    if master_device is None:\n        master_device = devices[0]\n\n    if not cpu_device:\n        for gpu in devices:\n            if gpu >= workspace.NumCudaDevices():\n                log.warning(\"** Only {} GPUs available, GPUs {} requested\".format(\n                    workspace.NumCudaDevices(), devices))\n                break\n        model_helper_obj._device_type = caffe2_pb2.CUDA\n        model_helper_obj._device_prefix = \"gpu\"\n    else:\n        model_helper_obj._device_type = caffe2_pb2.CPU\n        model_helper_obj._device_prefix = \"cpu\"\n\n    model_helper_obj._devices = devices\n    model_helper_obj._rendezvous = rendezvous\n    model_helper_obj._broadcast_context = None\n    model_helper_obj._shared_model = False\n    master_dev_opt = core.DeviceOption(model_helper_obj._device_type, master_device)\n\n    # question: rendezvous structure\n    num_shards = rendezvous['num_shards'] if rendezvous else 1\n    # num_devices is #devices across all machines\n    num_devices = len(devices) * num_shards\n    # num_workers is #threads to execute the DAG per shard\n    num_workers = num_threads_per_device * len(devices)\n    if rendezvous:\n        num_workers += 8\n\n    loss_scale = 1.0 / num_devices\n    if block_momentum is None:\n        block_momentum = 1.0 - 1.0 / num_devices\n\n    max_concurrent_distributed_ops = min(\n        max_concurrent_distributed_ops,\n        num_workers - 1\n    )\n\n    model_helper_obj.net.Proto().num_workers = num_workers\n    model_helper_obj.net.Proto().type = net_type\n\n    # A net for initializing global model parameters. Its called once in the\n    # same step as net parameters initialization.\n    model_helper_obj._global_model_init_net = core.Net('global_model_init')\n    model_helper_obj._global_model_init_net.Proto().type = net_type\n    model_helper_obj._global_model_init_net.Proto().num_workers = \\\n        num_workers\n\n    # A net for computing final parameter updates. Its will run once after\n    # running net (local models updates) for `num_local_iterations` times.\n    model_helper_obj._global_model_param_updates_net = core.Net('global_model')\n    model_helper_obj._global_model_param_updates_net.Proto().type = net_type\n    model_helper_obj._global_model_param_updates_net.Proto().num_workers = \\\n        num_workers\n\n    def _v(param):\n        return \"{}_v\".format(param)\n\n    def _g(param):\n        return \"{}_g\".format(param)\n\n    def _v_prev(param):\n        return \"{}_prev\".format(param)\n\n    # Keep track of params that were in the model before: they are not\n    # data parallel, so we need to handle them separately\n    non_datapar_params = copy.copy(model_helper_obj.params)\n    model_helper_obj._losses_by_gpu = {}\n\n    def _InitializeModels(gpu_id):\n        input_builder_fun(model_helper_obj)\n        loss = forward_pass_builder_fun(model_helper_obj, loss_scale)\n        model_helper_obj._losses_by_gpu[gpu_id] = loss\n    _ForEachDevice(\n        devices,\n        _InitializeModels,\n        device_type=model_helper_obj._device_type,\n        device_prefix=model_helper_obj._device_prefix,\n        scoped=True\n    )\n    _ValidateParams(model_helper_obj.params)\n\n    model_helper_obj._device_grouped_blobs =\\\n        _GroupByDevice(model_helper_obj, devices,\n                       model_helper_obj.params, non_datapar_params)\n\n    model_helper_obj._param_names =\\\n        list(viewkeys(model_helper_obj._device_grouped_blobs))\n\n    _AddGradientOperators(\n        devices, model_helper_obj, model_helper_obj._losses_by_gpu\n    )\n    _ValidateParams(model_helper_obj.params)\n\n    _InferBlobDevice(model_helper_obj)\n\n    def _InitializeParamUpdate(gpu_id):\n        param_update_builder_fun(model_helper_obj)\n    _ForEachDevice(\n        devices,\n        _InitializeParamUpdate,\n        device_type=model_helper_obj._device_type,\n        device_prefix=model_helper_obj._device_prefix,\n        scoped=True\n    )\n\n    model_parameter_names = list(\n        viewkeys(model_helper_obj._device_grouped_blobs)\n    )\n    if warmup_iterations is not None:\n        model_helper_obj._warmup_iterations = warmup_iterations\n        # A net for broadcasting gpu-0 (master shard) parameters after\n        # running net for `warmup_iterartions`.\n        model_helper_obj._warmup_broadcast = core.Net('warmup-broadcast')\n        model_helper_obj._warmup_broadcast.Proto().type = net_type\n        model_helper_obj._warmup_broadcast.Proto().num_workers = \\\n           num_workers\n\n        _SyncAllParams(\n            devices,\n            model_helper_obj,\n            model_helper_obj.param_init_net,\n            model_helper_obj._warmup_broadcast,\n            rendezvous,\n            model_parameter_names,\n            max_concurrent_distributed_ops\n        )\n        for param_name in viewkeys(model_helper_obj._device_grouped_blobs):\n            param = model_helper_obj._device_grouped_blobs[param_name][master_device]\n            with core.DeviceScope(master_dev_opt):\n                model_helper_obj._warmup_broadcast.Copy(param, _g(param))\n\n    # (Step-0) Initialize momentum parameters on master device.\n    for param_name in viewkeys(model_helper_obj._device_grouped_blobs):\n        param = model_helper_obj._device_grouped_blobs[param_name][master_device]\n        with core.DeviceScope(master_dev_opt):\n            model_helper_obj._global_model_init_net.ConstantFill(\n                param, _v(param), value=0.0\n            )\n            model_helper_obj._global_model_init_net.Copy(param, _g(param))\n            if nesterov:\n                model_helper_obj._global_model_init_net.ConstantFill(\n                    param, _v_prev(param), value=0.0\n                )\n\n    # (Step-1) Update models for num_local_iterations.\n\n    # (Step-2) Compute post-local-updates average of the params.\n    # Sum model params across GPUs and store resutls in param_avg blob.\n    _AllReduceBlobs(\n        model_parameter_names,\n        devices,\n        model_helper_obj,\n        model_helper_obj._global_model_param_updates_net,\n        rendezvous,\n        use_nccl,\n        max_concurrent_distributed_ops\n    )\n\n    # (Step-3) Update momentum params :\n    # param_v = block_momentum * param_v\n    # + block_learning_Rate * (param_avg - param)\n    # if nesterov momentum:\n    # param = param + param_v\n    # - block_momentum * (param_v - param_v_prev)\n    # param_v_prev = param_v\n    # else:\n    # param = param + param_v\n    for param_name in model_parameter_names:\n        param = model_helper_obj._device_grouped_blobs[param_name][master_device]\n        with core.DeviceScope(master_dev_opt):\n            # TODO(ataei) : Stop building the graph here to get model average ?\n            model_helper_obj._global_model_param_updates_net.Scale(\n                param, param, scale=1.0 / num_devices\n            )\n            model_helper_obj._global_model_param_updates_net.Sub(\n                [param, _g(param)], param\n            )\n            model_helper_obj._global_model_param_updates_net.Scale(\n                param, param, scale=block_learning_rate\n            )\n            model_helper_obj._global_model_param_updates_net.Scale(\n                _v(param), _v(param), scale=block_momentum\n            )\n            model_helper_obj._global_model_param_updates_net.Add(\n                [_v(param), param], _v(param)\n            )\n            model_helper_obj._global_model_param_updates_net.Add(\n                [_g(param), _v(param)], _g(param)\n            )\n            if nesterov:\n                model_helper_obj._global_model_param_updates_net.Sub(\n                    [_v(param), _v_prev(param)], _v_prev(param)\n                )\n                model_helper_obj._global_model_param_updates_net.Scale(\n                    _v_prev(param), _v_prev(param), scale=block_momentum\n                )\n                model_helper_obj._global_model_param_updates_net.Sub(\n                    [_g(param), _v_prev(param)], _g(param)\n                )\n                model_helper_obj._global_model_param_updates_net.Copy(\n                    _v(param), _v_prev(param)\n                )\n            model_helper_obj._global_model_param_updates_net.Copy(\n                _g(param), param\n            )\n\n\n    _SyncAllParams(\n        devices,\n        model_helper_obj,\n        model_helper_obj.param_init_net,\n        model_helper_obj._global_model_param_updates_net,\n        rendezvous,\n        model_parameter_names,\n        max_concurrent_distributed_ops\n    )\n\n    # Add additional syncs\n    if add_blobs_to_sync is not None:\n        AddBlobSync(\n            model_helper_obj,\n            add_blobs_to_sync,\n            net=model_helper_obj._global_model_param_updates_net)\n\n    # Reset momentum-SGD parameters\n    if reset_momentum_sgd:\n        momentum_ops = [op for op in model_helper_obj.net.Proto().op\n                        if op.type == 'MomentumSGDUpdate']\n        for op in momentum_ops:\n            momentum_blob = op.input[1]\n            with core.DeviceScope(op.device_option):\n                model_helper_obj._global_model_param_updates_net.ConstantFill(\n                    [momentum_blob], momentum_blob, value=0.0\n                )\n\n    if optimize_gradient_memory:\n        _OptimizeGradientMemorySimple(\n            model_helper_obj, model_helper_obj._losses_by_gpu, devices\n        )\n\n    model_helper_obj._data_parallel_model_init_nets = [\n        model_helper_obj.param_init_net,\n        model_helper_obj._global_model_init_net\n    ]\n\n    model_helper_obj._data_parallel_model_nets = [\n        model_helper_obj.net,\n        (model_helper_obj._global_model_param_updates_net, 1)\n    ]\n\n\ndef RunInitNet(model):\n    for init_net in model._data_parallel_model_init_nets:\n        workspace.RunNetOnce(init_net)\n    for net_iters in model._data_parallel_model_nets:\n        if isinstance(net_iters, tuple):\n            workspace.CreateNet(net_iters[0])\n        else:\n            workspace.CreateNet(net_iters)\n\n\ndef RunWarmup(model):\n    workspace.RunNet(model.net, model._warmup_iterations)\n    workspace.RunNetOnce(model._warmup_broadcast)\n\n\ndef RunNet(model, num_iterations):\n    for net_iter in model._data_parallel_model_nets:\n        if isinstance(net_iter, tuple):\n            workspace.RunNet(net_iter[0].Proto().name, net_iter[1])\n        else:\n            workspace.RunNet(net_iter, num_iterations)\n\n\nbarrier_instance = 0\n\n\ndef Synchronize(model, timeout_sec=_DEFAULT_TIMEOUT_SEC):\n    log.info(\"Creating synchronization barrier net\")\n    assert model._rendezvous is not None, \"Missing rendezvous\"\n    assert model._rendezvous['engine'] == 'GLOO', \"Engine does not support barrier\"\n    assert model._rendezvous['num_shards'] > 1, \\\n        \"synchronization barrier requires multiple shards\"\n    global barrier_instance\n    instance = barrier_instance\n    barrier_instance += 1\n    barrier_net = core.Net(\"sync_barrier_net_\" + str(instance))\n    comm_world = _CreateOrCloneCommonWorld(\n        barrier_net,\n        \"sync_barrier_cw_\" + str(instance),\n        rendezvous=model._rendezvous,\n        status_blob=\"sync_barrier_cw_status_\" + str(instance),\n        timeout_sec=timeout_sec,\n    )\n    barrier_net.Barrier(\n        inputs=[comm_world],\n        outputs=[],\n        engine=model._rendezvous['engine'],\n        status_blob=\"sync_barrier_status_\" + str(instance),\n    )\n    workspace.RunNetOnce(barrier_net)\n\n\ndef ConvertNetForDevice(net, device=None):\n    '''\n    Converts all blobs in the net to have namescope gpu_X, and correct\n    device scope. You can use this to enable AppendNet with a\n    forward_pass_builder_fun:\n\n       def builder_fun(model):\n          ...\n          model.net.AppendNet(\n             data_parallel_model.ConvertNetForDevice(othermodel.net))\n          model.param_init_net.AppendNet(\n             data_parallel_model.ConvertNetForDevice(othermodel.param_init_net))\n    '''\n    mnet = copy.deepcopy(net)\n\n    if device is None:\n        device = scope.CurrentDeviceScope()\n\n    device_prefix = \"gpu\" if device.device_type == caffe2_pb2.CUDA else \"cpu\"\n\n    namescope = \"{}_{}/\".format(device_prefix, device.cuda_gpu_id)\n    for op in mnet.Proto().op:\n        if \"RecurrentNetwork\" in op.type:\n            raise(\"RecurrentNetwork conversion not yet supported\")\n        for i, inputb in enumerate(op.input):\n            op.input[i] = namescope + inputb\n        for i, outputb in enumerate(op.output):\n            op.output[i] = namescope + outputb\n        for i, blob in enumerate(op.control_input):\n            op.control_input[i] = namescope + blob\n        op.device_option.CopyFrom(device)\n    for i, einp in enumerate(mnet.Proto().external_input):\n        mnet.Proto().external_input[i] = namescope + einp\n    for i, eoutp in enumerate(mnet.Proto().external_output):\n        mnet.Proto().external_output[i] = namescope + eoutp\n    return mnet\n\n\ndef _ForEachDevice(devices, f, device_type, device_prefix, scoped=False,\n                   *args, **kwargs):\n    for device in devices:\n        device_opt = core.DeviceOption(device_type, device)\n        with core.DeviceScope(device_opt):\n            if scoped:\n                with core.NameScope(\"{}_{}\".format(device_prefix, device)):\n                    f(device, *args, **kwargs)\n            else:\n                f(device, *args, **kwargs)\n\n\ndef _AddGradientOperators(devices, model, losses_by_gpu):\n    def create_grad(lossp):\n        return model.ConstantFill(lossp, str(lossp) + \"_grad\", value=1.0)\n\n    loss_grad = {}\n    # Explicitly need to create gradients on each GPU\n    for gpu_id in devices:\n        device = core.DeviceOption(model._device_type, gpu_id)\n        with core.DeviceScope(device):\n            for l in losses_by_gpu[gpu_id]:\n                lg = create_grad(l)\n                loss_grad[str(l)] = str(lg)\n\n    model.AddGradientOperators(loss_grad)\n\n\ndef ExtractPredictorNet(model, inputs, outputs, device):\n    '''\n    Returns (net, params) that can be exported to be used as a prediction\n    net.\n    '''\n    master_device = model._devices[0]\n    prefix = \"{}_{}/\".format(model._device_prefix, master_device)\n    prefix_inputs = [prefix + str(b) for b in inputs]\n    prefix_outputs = [prefix + str(b) for b in outputs]\n    (predictor_net, export_blobs) = model_helper.ExtractPredictorNet(\n        net_proto=model.net.Proto(),\n        input_blobs=prefix_inputs,\n        output_blobs=prefix_outputs,\n        device=device,\n        renames={\n            a: b\n            for (a, b) in zip(prefix_inputs + prefix_outputs, inputs + outputs)\n        },\n    )\n\n    return (predictor_net, export_blobs)\n\n\ndef GetCheckpointParams(model):\n    '''\n    Returns a set of blobs that are needed for a complete check point.\n    They are blobs for the first gpu and iteration blobs.\n    '''\n    (all_blobs, _) = _ComputeBlobsToSync(model)\n    first_gpu_blobs = {\n        b\n        for b in all_blobs\n        if str(b)\n        .startswith(\"{}_{}/\".format(model._device_prefix, model._devices[0]))\n    }\n\n    # Add iteration blobs that do not have namescope separately, since\n    # it is important to checkpoint iteration counter\n    iteration_blobs = set()\n    for op in model.net.Proto().op:\n        if op.type == 'Iter' or op.type == 'AtomicIter':\n            if not op.output[0].startswith(\"{}_\".format(model._device_prefix)):\n                iteration_blobs.add(op.output[0])\n\n    return first_gpu_blobs.union(iteration_blobs)\n\n\ndef FinalizeAfterCheckpoint(model, blobs=None):\n    '''\n    This function should be called after loading parameters from a\n    checkpoint / initial parameters file.\n    '''\n\n    if not hasattr(model, \"_checkpoint_net\"):\n        if blobs is None:\n            (_, uniq_blob_names) = _ComputeBlobsToSync(model)\n        else:\n            uniq_blob_names = [stripBlobName(p) for p in blobs]\n\n        # Synchronize to the blob lookup map, as the provided\n        # blobs might have non-parameters, such as momemtum blobs.\n        log.info(\"Creating checkpoint synchronization net\")\n        devices = model.GetDevices()\n        for name in uniq_blob_names:\n            if name not in model._device_grouped_blobs:\n                grouped = {\n                    d:\n                    core.BlobReference(\"{}_{}{}{}\".format(\n                        model._device_prefix,\n                        d,\n                        scope._NAMESCOPE_SEPARATOR,\n                        name)\n                    ) for d in devices}\n                model._device_grouped_blobs[name] = grouped\n\n        model._checkpoint_net = core.Net(\"checkpoint_sync_net\")\n        model._checkpoint_net.RunAllOnGPU()\n\n        checkpoint_init_net = None\n        if (model._rendezvous is not None and model._rendezvous['num_shards'] > 1):\n            checkpoint_init_net = core.Net(\"checkpoint_init_net\")\n            checkpoint_init_net.RunAllOnGPU()\n\n        _SyncAllParams(\n            devices,\n            model,\n            checkpoint_init_net,\n            model._checkpoint_net,\n            model._rendezvous,\n            uniq_blob_names,\n            max_concurrent_distributed_ops=1\n        )\n        if (checkpoint_init_net):\n            workspace.RunNetOnce(checkpoint_init_net)\n\n        workspace.CreateNet(model._checkpoint_net)\n\n    # Run the sync\n    log.info(\"Run checkpoint net\")\n    workspace.RunNet(model._checkpoint_net.Proto().name)\n\n\ndef GetLearningRateBlobNames(model):\n    '''\n    Returns a list of learning rates blob names used in the optimizer.\n    '''\n    if model._optimizer is not None:\n        if model._device_type == caffe2_pb2.CPU:\n            return [model._optimizer.get_cpu_blob_name('lr')]\n        elif model._device_type == caffe2_pb2.CUDA:\n            return [model._optimizer.get_gpu_blob_name('lr', gpu, '')\n                    for gpu in model._devices]\n        else:\n            raise Exception(\n                \"Unsupported device type : {}\".format(model._device_type)\n            )\n    else:\n        lr_blob_names = []\n        for op in model.net.Proto().op:\n            if op.type == \"LearningRate\":\n                lr_blob_names.append(op.output(0))\n        return lr_blob_names\n\n\ndef _Broadcast(devices, model, net, param, use_nccl=False):\n    # Copy params from gpu_0 to other\n    master_dev = devices[0]\n\n    if use_nccl:\n        if _IsGPUBlob(model, param):\n            master_device_opt = core.DeviceOption(model._device_type, master_dev)\n            with core.DeviceScope(master_device_opt):\n                # Note that the root is the root _rank_ and not the root\n                # _device_. Thus we always use root=0, regardless of the\n                # devices used.\n                net.NCCLBroadcast(\n                    list(viewvalues(model._device_grouped_blobs[param])),\n                    list(viewvalues(model._device_grouped_blobs[param])),\n                    root=0,\n                )\n                return\n\n    for dev_idx in devices[1:]:\n        if _IsGPUBlob(model, param):\n            device_opt = core.DeviceOption(caffe2_pb2.CUDA, dev_idx)\n        else:\n            device_opt = core.DeviceOption(caffe2_pb2.CPU, 0)\n        with core.DeviceScope(device_opt):\n            net.Copy(\n                model._device_grouped_blobs[param][master_dev],\n                model._device_grouped_blobs[param][dev_idx]\n            )\n\n\ndef _AllReduce(devices, model, net, param, use_nccl=False, control_input=None):\n    blobs_group = list(viewvalues(model._device_grouped_blobs[param]))\n    if model._device_type == caffe2_pb2.CUDA and use_nccl:\n        # TODO: for _shared_model, do only NCCLReduce\n        model.NCCLAllreduce(\n            blobs_group, blobs_group, control_input=control_input\n        )\n        return\n\n    if model._device_type == caffe2_pb2.CUDA:\n        p2p_access_pattern = workspace.GetCudaPeerAccessPattern()\n    else:\n        p2p_access_pattern = None\n\n    def sumN(*dev_indices):\n        \"\"\"Create a Sum op for 2 or more blobs on different devices.\n        Saves the result on the first device.\n\n        Arguments:\n        dev_indices -- a list of device indices, which can be translated into\n                       CUDA identifiers with model._devices\n        \"\"\"\n        devices = [model._devices[idx] for idx in dev_indices]\n        blobs = [blobs_group[idx] for idx in dev_indices]\n        for i, peer in enumerate(devices):\n            if i == 0:\n                continue  # Skip the first device\n            if p2p_access_pattern is not None and not p2p_access_pattern[\n                devices[0], peer\n            ]:\n                # Copy from peer to d0\n                blobs[i] = model.Copy(\n                    blobs[i],\n                    'gpu_{}/{}_gpu{}_copy'.format(devices[0], param, peer)\n                )\n        device_opt = core.DeviceOption(model._device_type, devices[0])\n        with core.DeviceScope(device_opt):\n            net.Sum(blobs, [blobs[0]], name='dpm')\n\n    if len(devices) == 16:\n        # Special tree reduction for 16 gpus, TODO generalize like in muji.py\n        for j in range(8):\n            sumN(j * 2, j * 2 + 1)\n        for j in range(4):\n            sumN(j * 4, j * 4 + 2)\n        for j in range(2):\n            sumN(j * 8, j * 8 + 4)\n        sumN(0, 8)\n    elif len(devices) == 8:\n        for j in range(4):\n            sumN(j * 2, j * 2 + 1)\n        for j in range(2):\n            sumN(j * 4, j * 4 + 2)\n        sumN(0, 4)\n    elif len(devices) == 4:\n        sumN(0, 1)\n        sumN(2, 3)\n        sumN(0, 2)\n    else:\n        sumN(*range(len(devices)))\n    # TODO: for _shared_model, no need to broadcast\n    _Broadcast(devices, model, net, param)\n\n\ndef _SyncAllParams(\n    devices,\n    model,\n    init_net,\n    net,\n    rendezvous,\n    unique_param_names,\n    max_concurrent_distributed_ops=4\n):\n    if rendezvous is None or rendezvous['num_shards'] <= 1:\n        _SyncAllParamsSingleHost(devices, model, net, unique_param_names)\n    else:\n        _SyncAllParamsDistributed(\n            devices,\n            model,\n            init_net,\n            net,\n            rendezvous,\n            unique_param_names,\n            max_concurrent_distributed_ops\n        )\n\n\ndef AddBlobSync(model, blobs, net=None):\n    '''\n    Sync a blob across devices and hosts\n    '''\n    if len(blobs) == 0:\n        return\n    net = model.net if net is None else net\n    for b in blobs:\n        assert not b.startswith(model._device_prefix), \\\n            \"Provide unprefixed blob name: {}\".format(b)\n        model._device_grouped_blobs[b] = {\n            d: core.BlobReference(\"{}_{}/{}\".format(model._device_prefix, d, b))\n            for d in model._devices\n        }\n\n    _SyncAllParams(\n        model._devices,\n        model,\n        model.param_init_net,\n        net,\n        model._rendezvous,\n        set(blobs))\n\n\ndef AddDistributedBlobSync(model, blobs):\n    '''\n    Sync blobs across machines (but not across devices)\n    '''\n    if model._rendezvous is None:\n        return\n    synth_name = \"_\".join([str(b) for b in blobs])\n    comm_world = _CreateOrCloneCommonWorld(\n        model.param_init_net,\n        \"blob_sync_cw_\" + synth_name,\n        rendezvous=model._rendezvous,\n        status_blob=\"create_blob_sync_cw_{}_cw_status\".format(\n            synth_name,\n        ),\n    )\n\n    model.net.Allreduce(\n        inputs=[comm_world] + blobs,\n        outputs=blobs,\n        engine=model._rendezvous['engine'],\n        status_blob=\"blob_sync_allred_{}_status\".format(synth_name),\n    )\n\n\ndef _SyncAllParamsDistributed(\n    devices,\n    model,\n    init_net,\n    net,\n    rendezvous,\n    unique_param_names,\n    max_concurrent_distributed_ops\n):\n    assert rendezvous['num_shards'] > 1\n\n    gpu_device_opt = core.DeviceOption(model._device_type, devices[0])\n    cpu_device_opt = core.DeviceOption(caffe2_pb2.CPU)\n\n    if model._broadcast_context is None:\n        model._broadcast_context = CollectivesConcurrencyControl(\n            \"broadcast\",\n            max_concurrent_distributed_ops,\n            init_net,\n            rendezvous\n        )\n    context = model._broadcast_context\n\n    for param_name in sorted(unique_param_names):\n        master_param = model._device_grouped_blobs[param_name][devices[0]]\n        params_group = list(viewvalues(model._device_grouped_blobs[param_name]))\n\n        def broadcast(params):\n            comm_world, control_input = context.get_control_and_context(params)\n            net.Broadcast(\n                inputs=[comm_world] + params,\n                outputs=params,\n                name=param_name,\n                engine=rendezvous['engine'],\n                status_blob=\"broadcast_{}_status\".format(str(param_name)),\n                control_input=control_input\n            )\n\n        device_opt = gpu_device_opt if _IsGPUBlob(\n            model, param_name\n        ) else cpu_device_opt\n\n        if rendezvous['engine'] == 'GLOO':\n            with core.DeviceScope(device_opt):\n                broadcast(params_group)\n        else:\n            # Copy between GPU and CPU\n            with core.DeviceScope(device_opt):\n                param_cpu = net.CopyGPUToCPU(\n                    master_param,\n                    str(master_param) + \"cpu\"\n                )\n            with core.DeviceScope(cpu_device_opt):\n                broadcast([param_cpu])\n            with core.DeviceScope(device_opt):\n                net.CopyCPUToGPU(param_cpu, master_param)\n\n            # Broadcast locally\n            _Broadcast(devices, model, net, param_name)\n\n\ndef _SyncAllParamsSingleHost(devices, model, net, unique_param_names):\n    for param in unique_param_names:\n        _Broadcast(devices, model, net, param)\n\n\ndef _AllReduceBlobs(blob_names, devices, model, net, rendezvous, use_nccl,\n                    max_concurrent_distributed_ops):\n    if rendezvous is None or rendezvous['num_shards'] <= 1:\n        _AllReduceBlobsSingleHost(\n            blob_names,\n            devices,\n            model,\n            net,\n            use_nccl\n        )\n    else:\n        _AllReduceBlobsDistributed(\n            blob_names,\n            devices,\n            model,\n            net,\n            rendezvous,\n            max_concurrent_distributed_ops,\n        )\n\n\ndef _PruneParametersForSharing(model):\n    assert model._shared_model\n    master_prefix = \"{}_{}/\".format(model._device_prefix, model._devices[0])\n\n    # Remove non-master parameters so that they will not receive parameter\n    # update operators.\n    model.params = model.GetParams(master_prefix)\n    paramset = set(model.params)\n\n    model.param_to_grad = {\n        p: model.param_to_grad[p]\n        for p in model.param_to_grad if p in paramset\n    }\n    model.weights = [w for w in model.weights if w in paramset]\n    model.biases = [w for w in model.biases if w in paramset]\n\n\ndef _RemapParameterBlobsForSharedModel(model, all_params):\n    assert model._shared_model\n    master_prefix = \"{}_{}/\".format(\n        model._device_prefix, model._devices[0])\n    log.info(\"Remapping param blobs to master -> {}\".format(master_prefix))\n    master_params = set(model.GetParams())\n\n    # Remove all but master params\n    def modify_ops(net):\n        ops = []\n        for op in net.Proto().op:\n            delete_op = False\n            # Delete ops that output non-master version of parameter\n            for outp in op.output:\n                if outp in all_params and outp not in master_params:\n                    delete_op = True\n                    log.debug(\"Delete b/c {}:  {}\".format(outp, str(op)))\n                    break\n            if delete_op:\n                continue\n            # Remap inputs to point to the master param\n            for j, inp in enumerate(op.input):\n                if inp in all_params and inp not in master_params:\n                    op.input[j] = master_prefix + stripBlobName(inp)\n            ops.append(op)\n        del net.Proto().op[:]\n        net.Proto().op.extend(ops)\n\n    modify_ops(model.param_init_net)\n    modify_ops(model.net)\n\n\nclass CollectivesConcurrencyControl(object):\n    \"\"\"\n    Creates common worlds (up to max_concurrent_context) and manage the\n    sequential execution of collectives that shares the same context with\n    cyclic control inputs.\n    \"\"\"\n    def __init__(\n        self,\n        name,\n        max_concurrent_context,\n        param_init_net,\n        rendezvous\n    ):\n        self.name = name\n        self.param_init_net = param_init_net\n        self.max_concurrent_context = max_concurrent_context\n        self.counter = 0\n        self.common_worlds = []\n        self.control_inputs = []\n        self.rendezvous = rendezvous\n\n    def get_control_and_context(self, control_output_blob):\n        common_world, control_input = [None, None]\n        current_slot = self.counter % self.max_concurrent_context\n        if len(self.common_worlds) < self.max_concurrent_context:\n            common_world = _CreateOrCloneCommonWorld(\n                self.param_init_net,\n                \"{}_{}_cw\".format(self.name, current_slot),\n                rendezvous=self.rendezvous,\n                status_blob=\"create_{}_cw_{}_status\".format(\n                    self.name,\n                    current_slot\n                ),\n            )\n            self.common_worlds.append(common_world)\n            self.control_inputs.append(control_output_blob)\n        else:\n            common_world = self.common_worlds[current_slot]\n            control_input = self.control_inputs[current_slot]\n            self.control_inputs[current_slot] = control_output_blob\n        self.counter += 1\n        return common_world, control_input\n\n\ndef _AllReduceBlobsDistributed(\n    blob_names,\n    devices,\n    model,\n    net,\n    rendezvous,\n    max_concurrent_distributed_ops,\n):\n    num_workers = model.net.Proto().num_workers\n    assert num_workers > 1, \"Please specify more than 1 worker\"\n    all_reduce_engine = rendezvous['engine']\n\n    master_device_opt = core.DeviceOption(model._device_type, devices[0])\n\n    reducing_device_opt = master_device_opt\n\n    context = CollectivesConcurrencyControl(\n        \"allreduce\",\n        max_concurrent_distributed_ops,\n        model.param_init_net,\n        rendezvous\n    )\n\n    nccl_control_blob = None\n\n    for blob_name in blob_names:\n        master_blob = model._device_grouped_blobs[blob_name][devices[0]]\n        blobs_group = list(viewvalues(model._device_grouped_blobs[blob_name]))\n\n        assert master_blob in blobs_group\n\n        # Remark: NCCLReduce does not support in-place modifications\n        # so we need a temporary blob\n        reduced_blob = str(master_blob) + \"_red\"\n\n        def allreduce(blobs, **kwargs):\n            with core.DeviceScope(reducing_device_opt):\n                comm_world, control_input = \\\n                    context.get_control_and_context(blobs[0])\n                net.Allreduce(\n                    inputs=[comm_world] + blobs,\n                    outputs=blobs,\n                    name=blob_name,\n                    engine=all_reduce_engine,\n                    control_input=control_input,\n                    status_blob=\"allreduce_{}_status\".format(blob_name),\n                    **kwargs\n                )\n\n        if rendezvous['engine'] == 'GLOO':\n            # With Gloo cross GPU and cross machine allreduce\n            # can be executed in a single operation.\n            # Try to use GPUDirect if transport == ibverbs.\n            allreduce(\n                blobs_group,\n                gpu_direct=(rendezvous.get(\"transport\", None) == \"ibverbs\"),\n            )\n        else:\n            # Step 1: sum blobs from local GPUs to master GPU\n            with core.DeviceScope(master_device_opt):\n                model.ConstantFill(master_blob, reduced_blob, value=0.0)\n\n                # Temp fix since NCCLReduce does not work\n                net.NCCLAllreduce(\n                    blobs_group,\n                    blobs_group,\n                    control_input=nccl_control_blob,\n                )\n                nccl_control_blob = blobs_group[0]\n                net.Copy(master_blob, reduced_blob)\n\n            # Step 2: allreduce between all hosts, between master GPUs\n            allreduce([reduced_blob])\n\n            with core.DeviceScope(master_device_opt):\n                net.Copy(reduced_blob, master_blob)\n\n            # Step 3: broadcast locally\n            _Broadcast(devices, model, net, blob_name)\n\n\ndef _AllReduceBlobsSingleHost(blob_names, devices, model, net, use_nccl):\n    \"\"\"Performs NCCL AllReduce to distribute blobs to all the GPUs.\"\"\"\n\n    if len(devices) == 1:\n        return\n\n    # Now we need to Allreduce blobs on all the GPUs.\n    # Pick GPU #0 as a master GPU.\n    master_device_opt = core.DeviceOption(model._device_type, devices[0])\n    last_out = None\n    concatenated_idx = set()\n\n    for blob_name in blob_names:\n        # Group by blob_name for reduce.\n        blobs_group = list(viewvalues(model._device_grouped_blobs[blob_name]))\n        if len(blobs_group) == 1:\n            # Non-reducible\n            continue\n        assert len(blobs_group) == len(devices), \\\n            \"Each GPU from {}, should have a copy of {}.\".format(\n                devices, blob_name)\n\n        if _IsGPUBlob(model, blob_name):\n            with core.DeviceScope(master_device_opt):\n                if not isinstance(blobs_group[0], core.GradientSlice):\n                    _AllReduce(\n                        devices, model, net, blob_name, use_nccl, last_out\n                    )\n                    # last_out is used to serialize the execution of nccls\n                    last_out = blobs_group[0]\n\n                else:\n                    # Sparse gradients: all-gather for indices and values\n                    master_ns = \"{}_{}\".format(model._device_prefix, devices[0])\n                    '''\n                    Skip if we have already copied concatenated indices\n                    to the indices of GradientSlice. This happens when two\n                    or more grad blobs are gathered with the same indices\n                    blob\n                    '''\n                    skip_idx_concat = False\n                    for g in blobs_group:\n                        if g.indices in concatenated_idx:\n                            skip_idx_concat = True\n\n                    if not skip_idx_concat:\n                        grad_idx_concat, _ = net.Concat(\n                            [g.indices for g in blobs_group],\n                            [\"{}/{}_index_concat\".format(master_ns, blob_name),\n                             \"{}/{}_index_splitinfo\".format(master_ns, blob_name)],\n                            axis=0,\n                            name=\"note:data_parallel_model\")\n\n                        for gpu, g in viewitems(model._device_grouped_blobs[blob_name]):\n                            device_opt = core.DeviceOption(model._device_type, gpu)\n                            with core.DeviceScope(device_opt):\n                                model.Copy(grad_idx_concat, g.indices)\n                                concatenated_idx.add(g.indices)\n\n                    grad_val_concat, _ = net.Concat(\n                        [g.values for g in blobs_group],\n                        [\"{}/{}_val_concat\".format(master_ns, blob_name),\n                         \"{}/{}_val_splitinfo\".format(master_ns, blob_name)],\n                        axis=0, name=\"note:data_parallel_model\")\n\n                    for gpu, g in viewitems(model._device_grouped_blobs[blob_name]):\n                        device_opt = core.DeviceOption(model._device_type, gpu)\n                        with core.DeviceScope(device_opt):\n                            model.Copy(grad_val_concat, g.values)\n\n        else:\n            assert not isinstance(blobs_group[0], core.GradientSlice), \\\n                \"Synchronizing gradient slices not supported\"\n            with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n                # Poor man's allreduce\n                net.Sum(blobs_group, [blobs_group[0]])\n                if not model._shared_model:\n                    _Broadcast(devices, model, net, blob_name)\n\n\ndef _BroadcastComputedParams(devices, model, rendezvous, use_nccl=False):\n    if rendezvous is None:\n        _BroadcastComputedParamsSingleHost(devices, model, use_nccl)\n    else:\n        _BroadcastComputedParamsDistributed(devices, model, rendezvous, use_nccl)\n\n\ndef _BroadcastComputedParamsDistributed(\n    devices,\n    model,\n    rendezvous,\n    use_nccl=False\n):\n    _BroadcastComputedParamsSingleHost(devices, model, use_nccl)\n    log.warn(\"Distributed broadcast of computed params is not implemented yet\")\n\n\ndef _BroadcastComputedParamsSingleHost(devices, model, use_nccl=False):\n    '''\n    Average computed params over all devices\n    '''\n    if len(devices) == 1:\n        return\n\n    for param_name in model._computed_param_names:\n        # Copy from master to others -- averaging would be perhaps better,\n        # but currently NCCLAllReduce is too prone to deadlock\n        _Broadcast(devices, model, model.net, param_name, use_nccl)\n\n\ndef _GetReverseOrderedGrads(model):\n    '''\n    Returns the gradients in reverse order (namespace stripped),\n    for the optimal synchronization order.\n    '''\n    return list(reversed(model._grad_names))\n\n\n# A helper function to extract a parameter's name\ndef stripBlobName(param):\n    # Format is \"a/b/c/d\" -> \"b/c/d\"\n    if isinstance(param, core.GradientSlice):\n        return stripBlobName(param.indices) + \":\" + stripBlobName(param.values)\n    else:\n        name = str(param)\n    return name[name.index(scope._NAMESCOPE_SEPARATOR) + 1:]\n\n\ndef _AnalyzeOperators(model):\n    '''\n    Look at all the operators and check that they do not cross device scopes\n    '''\n    for op in model.Proto().op:\n        if \"NCCL\" in op.type or \"Copy\" in op.type or \"Concat\" in op.type:\n            continue\n        if \"Sum\" == op.type and op.name == \"dpm\":\n            continue\n        if \"Allreduce\" in op.type and \"GLOO\" in op.engine:\n            continue\n\n        op_dev = op.device_option\n        op_gpu = op_dev.cuda_gpu_id\n\n        # This avoids failing on operators that are only for CPU\n        if op_dev.device_type != caffe2_pb2.CUDA:\n            continue\n\n        namescope = \"{}_{}/\".format(model._device_prefix, op_gpu)\n        for inp in list(op.input) + list(op.output):\n            if inp.startswith(\"{}_\".format(model._device_prefix)\n                             ) and not inp.startswith(namescope):\n                raise Exception(\n                    \"Blob {} of op {}, should have namescope {}. Op: {}\".format(\n                        inp,\n                        op.type,\n                        \"{}_{}/\".format(model._device_prefix, op_gpu),\n                        str(op),\n                    )\n                )\n\n\ndef _InferBlobDevice(model):\n    '''\n    Assign blob to device option based on the operator outputing it\n    '''\n    mapping = {}\n\n    def map_ops(proto):\n        for op in proto.op:\n            device_option = op.device_option\n            if op.type == \"Iter\":\n                # Hack for Iters which have blob in CPU context\n                device_option = caffe2_pb2.DeviceOption()\n                device_option.device_type = caffe2_pb2.CPU\n            for b in list(op.input) + list(op.output):\n                if b not in mapping:\n                    mapping[b] = device_option\n            if op.type.startswith('RecurrentNetwork'):\n                step_args = [a for a in op.arg if a.name.endswith(\"step_net\")]\n                for step_arg in step_args:\n                    map_ops(step_arg.n)\n    map_ops(model.param_init_net.Proto())\n    map_ops(model.net.Proto())\n    model._blob_to_device = mapping\n\ndef _IsGPUBlob(model, blob_name):\n    if blob_name in model._blob_to_device:\n        return model._blob_to_device[blob_name].device_type == caffe2_pb2.CUDA\n    else:\n        blob_name = \"{}_{}/{}\".format(\n            model._device_prefix, model._devices[0], blob_name\n        )\n        if blob_name not in model._blob_to_device:\n            return model._device_type == caffe2_pb2.CUDA\n        return model._blob_to_device[blob_name].device_type == caffe2_pb2.CUDA\n\n\ndef _GroupByDevice(model, devices, params, non_data_params):\n    '''\n    Groups blobs by device, returning a map of [blobname] = {0: BlobRef, 1: ..}.\n    Returns ordered dictionary, ensuring the original order.\n    '''\n    grouped = OrderedDict()\n    # Only consider params that were created to be  \"data parallel\"\n    params = params[len(non_data_params):]\n\n    for _i, p in enumerate(params):\n        assert isinstance(p, core.BlobReference) or \\\n            isinstance(p, core.GradientSlice), \\\n            \"Param {} is not BlobReference or GradientSlice\".format(p)\n\n        name = stripBlobName(p)\n        gpuid = None\n\n        if isinstance(p, core.BlobReference):\n            gpuid = int(p.GetNameScope().split(\"_\")[1].split(\"/\")[0])\n            assert \"{}_{}/\".format(model._device_prefix, gpuid) in p.GetNameScope(),\\\n                \"Param {} expected to have namescope '{}_{}'\".format(str(p), model._device_prefix, gpuid)\n        else:\n            gpuid = int(p.indices.GetNameScope().split(\"_\")[1].split(\"/\")[0])\n            assert \"{}_{}/\".format(model._device_prefix, gpuid) in p.indices.GetNameScope(),\\\n                \"Indices {} expected to have namescope '{}_{}'\".format(str(p), model._device_prefix, gpuid)\n            assert \"{}_{}/\".format(model._device_prefix, gpuid) in p.values.GetNameScope(),\\\n                \"Values {} expected to have namescope '{}_{}'\".format(str(p), model._device_prefix, gpuid)\n\n        if name not in grouped:\n            grouped[name] = {}\n        grouped[name][gpuid] = p\n\n    return grouped\n\n\ndef _ValidateParams(params):\n    set_params = set(params)\n    if len(params) > len(set_params):\n        dupes = []\n        sp = sorted(params)\n        for j, p in enumerate(sp):\n            if j > 0 and sp[j - 1] == p:\n                dupes.append(p)\n\n        assert len(params) == len(set_params), \\\n            \"Duplicate entries in params: {}\".format(dupes)\n\n\ndef _ComputeBlobsToSync(model):\n    '''\n    We sync all blobs that are generated by param init net and\n    are 'data parallel', i.e assigned to a device\n    '''\n    sync_names = set()\n\n    # We don't sync params if the model is shared\n    if model._shared_model:\n        blobs_to_sync = [str(p) for p in model.GetComputedParams('')]\n        sync_names = [stripBlobName(p) for p in blobs_to_sync]\n    else:\n        blobs_to_sync = []\n\n        for op in model.param_init_net.Proto().op:\n            dp_outputs = [\n                o for o in op.output\n                if o.startswith(\"{}_\".format(model._device_prefix))\n            ]\n            sync_names.update([stripBlobName(o) for o in dp_outputs])\n            blobs_to_sync.extend(dp_outputs)\n\n        # Sanity check\n        diff = set(model._param_names) - sync_names\n        assert diff == set(), \\\n           \"Some params not instantiated in param init net: {}\".format(diff)\n\n    # Remove duplicates and sort\n    prefixlen = len(model._device_prefix) + 1\n\n    def extract_sort_key(b):\n        # Sort first based on device id, and then by whole string\n        deviceid = int(b[prefixlen:b.index(scope._NAMESCOPE_SEPARATOR)])\n        return (deviceid, b)\n\n    blobs_to_sync = sorted(\n        list(set(blobs_to_sync)),\n        key=extract_sort_key)\n\n    blobs_to_sync = [core.BlobReference(b) for b in blobs_to_sync]\n    return (blobs_to_sync, sync_names)\n\n\ndef _OptimizeGradientMemorySimple(model, losses_by_gpu, devices):\n    log.warning(\"------- DEPRECATED API, please use \" +\n                   \"data_parallel_model.OptimizeGradientMemory() ----- \")\n    for device in devices:\n        namescope = \"{}_{}/\".format(model._device_prefix, device)\n        model.net._net = memonger.share_grad_blobs(\n            model.net,\n            losses_by_gpu[device],\n            set(viewvalues(model.param_to_grad)),\n            namescope,\n            share_activations=False,\n        )\n\n\ndef _AddDynamicMemoryOptimization(model, blobs_to_keep, devices):\n    blobs_to_keep_all_devices = set()\n    if blobs_to_keep is not None:\n        for device in devices:\n            for blob_name in blobs_to_keep:\n                blobs_to_keep_all_devices.add(\n                    \"{}_{}/{}\".format(model._device_prefix, device, blob_name)\n                )\n\n    if model._rendezvous is not None:\n        # GLOO operators expect the tensor addresses to remain same over\n        # iterations so we need to remove param grads from the dynamic memory\n        # management.\n        blobs_to_keep_all_devices.update(\n            [str(b) for b in viewvalues(model.param_to_grad)]\n        )\n\n    model.net._net = memonger.release_blobs_when_used(\n        model.net.Proto(),\n        blobs_to_keep_all_devices\n    )\n\n\ndef OptimizeGradientMemory(model,\n                           input_shapes,\n                           excluded_blobs,\n                           recycle_activations):\n    \"\"\"\n    Optimize memory usage of the backward pass by recycling blobs for gradient\n    inputs that have been 'used'.\n    input_shapes:  dict of blob name to shape for the inputs of the model.\n                   Pass empty dictionary if not known.\n    excluded_blobs: list of blobs that cannot be recycled. These are blobs\n                   that you will access externally.\n    recycle_activations: whether to also recycle forward pass activations\n    \"\"\"\n    if input_shapes is not None:\n        input_shapes_all_devices = {}\n        for b, shp in viewitems(input_shapes):\n            for d in model._devices:\n                input_shapes_all_devices[\"{}_{}/{}\".\n                                         format(model._device_prefix, d, b)] = shp\n\n        (shapes, types) = workspace.InferShapesAndTypes(\n            [model.param_init_net, model.net],\n            input_shapes_all_devices,\n        )\n    else:\n        shapes = None\n\n    for device in model._devices:\n        namescope = \"{}_{}/\".format(model._device_prefix, device)\n        excluded_blobs_by_device = set(namescope + b for b in excluded_blobs)\n        model.net._net = memonger.share_grad_blobs(\n            model.net,\n            model._losses_by_gpu[device],\n            set(viewvalues(model.param_to_grad)),\n            namescope,\n            dont_share_blobs=excluded_blobs_by_device,\n            share_activations=recycle_activations,\n            blob_shapes=shapes,\n        )\n\n\ndef _CreateOrCloneCommonWorld(\n        net,\n        common_world_blob,\n        rendezvous,\n        name=None,\n        status_blob=None,\n        timeout_sec=None):\n\n    if timeout_sec is None:\n        timeout_sec = _DEFAULT_TIMEOUT_SEC\n\n    timeout_ms = timeout_sec * 1000\n\n    # Check if there is an existing CreateCommonWorld\n    # with the same timeout we're looking for. If so,\n    # we can clone it instead of creating a new one.\n    existing = None\n    for op in net.Proto().op:\n        if op.type != \"CreateCommonWorld\":\n            continue\n\n        # Find common world timeout\n        op_timeout_ms = -1\n        for arg in op.arg:\n            if arg.name == 'timeout_ms':\n                op_timeout_ms = arg.i\n                break\n        if op_timeout_ms != timeout_ms:\n            continue\n\n        # This common world was created with the same timeout we're\n        # looking for, so we can clone it\n        existing = op.output[0]\n        break\n\n    if name is None:\n        name = \"{}_op\".format(common_world_blob)\n\n    if existing is not None:\n        comm_world = net.CloneCommonWorld(\n            [existing],\n            common_world_blob,\n            name=name,\n            engine=rendezvous['engine'],\n            status_blob=status_blob,\n        )\n    else:\n        kwargs=dict()\n        if 'transport' in rendezvous:\n            kwargs['transport'] = rendezvous['transport']\n        if 'interface' in rendezvous:\n            kwargs['interface'] = rendezvous['interface']\n        if 'mpi_rendezvous' in rendezvous:\n            kwargs['mpi_rendezvous'] = rendezvous['mpi_rendezvous']\n        comm_world = net.CreateCommonWorld(\n            rendezvous['kv_handler'] or [],\n            common_world_blob,\n            name=name,\n            size=rendezvous['num_shards'],\n            rank=rendezvous['shard_id'],\n            engine=rendezvous['engine'],\n            status_blob=status_blob,\n            timeout_ms=timeout_ms,\n            **kwargs\n        )\n\n    return comm_world\n\n\ndef _RunComparison(model, blob_name, device=None):\n    if device is None:\n        device = model._blob_to_device[blob_name]\n    with core.DeviceScope(device):\n        rendezvous = model._rendezvous\n        if rendezvous is None or rendezvous['num_shards'] == 1:\n            return True\n\n        test_data_arr = np.zeros(rendezvous['num_shards']).astype(np.float32)\n        test_data_arr[rendezvous['shard_id']] = 1\n        workspace.FeedBlob(\"compare_arr\", test_data_arr)\n\n        comparison_net = core.Net(\"allcompare_net\")\n\n        kwargs=dict()\n        if 'mpi_rendezvous' in rendezvous:\n            kwargs['mpi_rendezvous'] = rendezvous['mpi_rendezvous']\n        comm_world = comparison_net.CreateCommonWorld(\n            rendezvous['kv_handler'] or [],\n            \"initial_sync\",\n            name=model.net.Proto().name + \".cw_master_select\",\n            size=rendezvous['num_shards'],\n            rank=rendezvous['shard_id'],\n            engine=rendezvous['engine'],\n            status_blob=\"cw_master_select\",\n            **kwargs\n        )\n\n        blob_name_checksum = blob_name + \"_checksum\"\n        comparison_net.SumSqrElements(\n            [blob_name], [blob_name_checksum], average=False\n        )\n\n        blob_name_gather = blob_name + \"_gather\"\n        comparison_net.Mul(\n            inputs=[\"compare_arr\", blob_name_checksum],\n            outputs=blob_name_gather,\n            broadcast=1\n        )\n\n        comparison_net.Allreduce(\n            inputs=[comm_world, blob_name_gather],\n            outputs=[blob_name_gather],\n            engine=rendezvous['engine'],\n            status_blob=\"all_reduce_master_select_status\",\n        )\n\n        workspace.RunNetOnce(comparison_net)\n        gather_arr = workspace.FetchBlob(blob_name_gather)\n\n        baseline = gather_arr[0]\n        for i in range(rendezvous['num_shards']):\n            assert gather_arr[i] == baseline, \\\n                \"allcompare failed on shard {}.\".format(rendezvous['shard_id'])\n\n        return True\n\n\ndef _InterleaveOps(model):\n    '''\n    Data Parallel Model creates a net with ops in one device grouped together.\n    This will interleave the ops so that each op for each device is next\n    to each other in the net. Kind of like combining decks of cards. This\n    ensures that progress is made along the critical path roughly concurrently\n    for each device, which is important due to the extra intra-node\n    synchronization required for multi-device batch normalization.\n    '''\n    orig_ops = list(model.net.Proto().op)\n    num_devices = len(model._devices)\n    num_ops_per_dev = len(orig_ops) // num_devices\n    assert num_devices * num_ops_per_dev == len(orig_ops), \\\n           'Number of ops per device in original net is not uniform'\n    new_ops = []\n    ops = {d: [] for d in range(num_devices)}\n    for op in orig_ops:\n        ops[op.device_option.cuda_gpu_id].append(op)\n\n    for j in range(num_ops_per_dev):\n        tp = None\n        for d in model._devices:\n            if tp is None:\n                tp = ops[d][j].type\n            new_ops.append(ops[d][j])\n            # Sanity\n            assert ops[d][j].type == tp, \\\n                \"Type mismatch {} / {}\".format(tp, ops[d][j].type)\n\n    del model.net.Proto().op[:]\n    model.net.Proto().op.extend(new_ops)\n\n\ndef _InterDeviceBatchNormalization(model):\n    orig_ops = list(model.net.Proto().op)\n    new_ops = []\n    num_devices = len(model._devices)\n    batch_norm_ops = []\n    injected_ops = []\n\n    spatial_bn_phase = False\n    sums_blobs = []\n    sumsq_blobs = []\n    name = []\n    input_blob_name = None\n\n    spatial_bn_gradient_phase = False\n    scale_grad_blobs = []\n    bias_grad_blobs = []\n\n    for op in orig_ops:\n        if op.type != 'SpatialBN' and op.type != 'SpatialBNGradient':\n            if spatial_bn_phase:\n                new_ops.extend(injected_ops)\n                new_ops.append(\n                    core.CreateOperator(\"Sum\",\n                                        sums_blobs,\n                                        input_blob_name + \"_sums_combined\"))\n                new_ops.append(\n                    core.CreateOperator(\"Sum\",\n                                        sumsq_blobs,\n                                        input_blob_name + \"_sumsq_combined\"))\n                new_ops.extend(batch_norm_ops)\n                injected_ops = []\n                batch_norm_ops = []\n                sums_blobs = []\n                sumsq_blobs = []\n                spatial_bn_phase = False\n                input_blob_name = None\n            elif spatial_bn_gradient_phase:\n                new_ops.extend(injected_ops)\n                scale_blob = \\\n                    \"cpu_0/\" + stripBlobName(scale_grad_blobs[0]) + \"_combined\"\n                bias_blob = \\\n                    \"cpu_0/\" + stripBlobName(bias_grad_blobs[0]) + \"_combined\"\n                new_ops.append(\n                    core.CreateOperator(\"Sum\", scale_grad_blobs, scale_blob))\n                new_ops.append(\n                    core.CreateOperator(\"Sum\", bias_grad_blobs, bias_blob))\n                for blob in scale_grad_blobs:\n                    new_ops.append(\n                        core.CreateOperator(\"Copy\", scale_blob, blob))\n                for blob in bias_grad_blobs:\n                    new_ops.append(core.CreateOperator(\"Copy\", bias_blob, blob))\n                new_ops.extend(batch_norm_ops)\n                injected_ops = []\n                batch_norm_ops = []\n                scale_grad_blobs = []\n                bias_grad_blobs = []\n                spatial_bn_gradient_phase = False\n            new_ops.append(op)\n        elif op.type == 'SpatialBN':\n            spatial_bn_phase = True\n            if input_blob_name is None:\n                input_blob_name = op.input[0]\n            name = op.input[0]\n            injected_ops.append(\n                core.CreateOperator(\n                    \"ChannelStats\",\n                    name,\n                    [name + \"_sums\", name + \"_sumsq\"]))\n            sums_blobs.append(name + \"_sums\")\n            sumsq_blobs.append(name + \"_sumsq\")\n            op.input.append(input_blob_name + \"_sums_combined\")\n            op.input.append(input_blob_name + \"_sumsq_combined\")\n            op.arg.extend([utils.MakeArgument(\"num_batches\", num_devices)])\n            batch_norm_ops.append(op)\n        elif op.type == 'SpatialBNGradient':\n            spatial_bn_gradient_phase = True\n            injected_ops.append(\n                core.CreateOperator(\"ChannelBackpropStats\",\n                                    [op.input[0], op.input[3], op.input[4],\n                                     op.input[2]],\n                                    [op.output[1], op.output[2]]))\n            scale_grad_blobs.append(op.output[1])\n            bias_grad_blobs.append(op.output[2])\n            op.arg.extend([utils.MakeArgument(\"num_batches\", num_devices)])\n            op.input.extend([op.output[1], op.output[2]])\n            batch_norm_ops.append(op)\n\n    assert not spatial_bn_phase, \\\n        \"Net modification for inter-device batch normalization failed\"\n    del model.net.Proto().op[:]\n    model.net.Proto().op.extend(new_ops)\n"
  },
  {
    "path": "caffe2/python/data_parallel_model_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nfrom future.utils import viewkeys\nfrom multiprocessing import Process, Queue\nimport numpy as np\nimport os\nimport shutil\nimport tempfile\nimport unittest\nfrom hypothesis import assume, given\nimport hypothesis.strategies as st\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import brew, core, cnn, data_parallel_model, dyndep, \\\n    model_helper, optimizer, rnn_cell, workspace, data_parallel_model_utils\nfrom caffe2.python.test_util import TestCase\n\n\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/distributed:file_store_handler_ops\")\n\n\nclass TemporaryDirectory:\n    def __enter__(self):\n        self.tmpdir = tempfile.mkdtemp()\n        return self.tmpdir\n\n    def __exit__(self, type, value, traceback):\n        shutil.rmtree(self.tmpdir)\n\n# Note(jiayq): we are yet to find out why Travis gives out an error in gloo\n# like:\n# RuntimeError: [enforce fail at /home/travis/build/caffe2/caffe2/third_party/gloo/gloo/transport/tcp/device.cc:113] ifa != nullptr. Unable to find interface for: [127.0.1.1]\n# See for example https://travis-ci.org/caffe2/caffe2/jobs/262433866\n# As a result, we will check if this is travis, and if yes, disable it.\n@unittest.skipIf(os.environ.get(\"TRAVIS\"), \"DPMTest has a known issue with Travis.\")\nclass DataParallelModelTest(TestCase):\n\n    def run_model(self, devices, gpu):\n        '''\n        Helper function for test_equiv\n        '''\n        def input_builder_fun(model):\n            return None\n\n        def model_build_fun(model, loss_scale):\n            fc = model.FC(\"data\", \"fc\", 16, 1,\n                          (\"ConstantFill\", {}), (\"ConstantFill\", {}))\n            fc_fl = model.FlattenToVec(fc, \"fc_fl\")\n            sigm = model.Sigmoid(fc_fl, \"sigm\")\n            sq = model.SquaredL2Distance([sigm, \"label\"], \"sq\")\n            loss = model.AveragedLoss(sq, \"loss\")\n            loss = model.Scale(loss, scale=loss_scale)\n\n            # For testing explicit sync\n            model.param_init_net.UniformFill([], [\"sync_num\"], shape=[1])\n            return [loss]\n\n        def add_optimizer(model):\n            return optimizer.build_sgd(\n                model,\n                0.1,\n                policy=\"fixed\",\n                max_gradient_norm=5.0,\n                allow_lr_injection=True,\n            )\n\n        workspace.ResetWorkspace()\n        model = cnn.CNNModelHelper(\n            order=\"NHWC\",\n            name=\"test{}\".format(devices),\n        )\n        data_parallel_model.Parallelize(\n            model,\n            input_builder_fun=input_builder_fun,\n            forward_pass_builder_fun=model_build_fun,\n            optimizer_builder_fun=add_optimizer,\n            devices=devices,\n            cpu_device=not gpu,\n            shared_model=not gpu,\n            combine_spatial_bn=not gpu,\n        )\n        data_parallel_model.AddBlobSync(model, [\"sync_num\"])\n\n        # Light test for LR names\n        lr_names = data_parallel_model.GetLearningRateBlobNames(model)\n        self.assertGreater(len(lr_names), 0)\n\n        np.random.seed(2603)\n\n        # Each run has same input, independent of number of gpus\n        batch_size = 64\n        for i in range(0, 10):\n            full_data = np.random.rand(batch_size, 16)\n            full_labels = np.round(full_data[:, 0])\n            batch_per_device = batch_size // len(devices)\n\n            for (j, g) in enumerate(devices):\n                st = j * batch_per_device\n                en = st + batch_per_device\n                data = full_data[st:en, :].astype(np.float32)\n                labels = full_labels[st:en].astype(np.float32)\n                with core.DeviceScope(core.DeviceOption(model._device_type, g)):\n                    workspace.FeedBlob(\n                        \"{}_{}/data\".format(model._device_prefix, g), data\n                    )\n                    workspace.FeedBlob(\n                        \"{}_{}/label\".format(model._device_prefix, g), labels\n                    )\n\n            if i == 0:\n                workspace.RunNetOnce(model.param_init_net)\n                workspace.CreateNet(model.net)\n\n            workspace.FeedBlob(\n                model._device_prefix + \"_0/sync_num\",\n                np.array([i * 2]).astype(np.float32),\n                device_option=core.DeviceOption(model._device_type, 0))\n            workspace.RunNet(model.net.Proto().name)\n\n            # Test AddBlobSync\n            for j in model._devices:\n                sync = workspace.FetchBlob(\n                    model._device_prefix + \"_{}/sync_num\".format(j))[0]\n                self.assertTrue(abs(sync - i * 2) < 0.01)\n\n        return workspace.FetchBlob(\"{}_0/fc_w\".format(model._device_prefix))\n\n    def run_test_locally(self, fn, device_option=None, **kwargs):\n        # Queue for assertion errors on subprocesses\n        queue = Queue()\n\n        # Capture any exception thrown by the subprocess\n        def run_fn(*args, **kwargs):\n            try:\n                if device_option is None:\n                    fn(*args, **kwargs)\n                    workspace.ResetWorkspace()\n                else:\n                    with core.DeviceScope(device_option):\n                        fn(*args, **kwargs)\n                        workspace.ResetWorkspace()\n            except Exception as ex:\n                queue.put(ex)\n\n        # Start N processes in the background\n        procs = []\n        for i in range(kwargs['comm_size']):\n            kwargs['comm_rank'] = i\n            proc = Process(\n                target=run_fn,\n                kwargs=kwargs)\n            proc.start()\n            procs.append(proc)\n\n        # Test complete, join background processes\n        while len(procs) > 0:\n            proc = procs.pop(0)\n            while proc.is_alive():\n                proc.join(1)\n\n                # Raise exception if we find any.\n                # Note that the following is executed ALSO after\n                # the last process was joined, so if ANY exception\n                # was raised, it will be re-raised here.\n                if not queue.empty():\n                    raise queue.get()\n\n    def test_equiv(self):\n        '''\n        Test that the model produces exactly same results given\n        total batchsize, independent of number of GPUs.\n        '''\n        for gpu in [True, False]:\n            if gpu and (not workspace.has_gpu_support or\n                        workspace.NumCudaDevices() < 2):\n                continue\n            result_2gpus = self.run_model([0, 1], gpu=gpu)\n            result_1gpus = self.run_model([0], gpu=gpu)\n\n            self.assertTrue(np.allclose(result_1gpus, result_2gpus))\n\n            if not gpu or workspace.NumCudaDevices() >= 4:\n                result_4gpus = self.run_model(list(range(4)), gpu=gpu)\n                self.assertTrue(np.allclose(result_1gpus, result_4gpus))\n\n            if not gpu or workspace.NumCudaDevices() >= 8:\n                result_8gpus = self.run_model(list(range(8)), gpu=gpu)\n                self.assertTrue(np.allclose(result_1gpus, result_8gpus))\n\n            if not gpu or workspace.NumCudaDevices() >= 16:\n                result_16gpus = self.run_model(list(range(16)), gpu=gpu)\n                self.assertTrue(np.allclose(result_1gpus, result_16gpus))\n\n    def test_checkpoint_params(self):\n        def add_input_ops(model):\n            pass\n\n        def add_model_ops(model, loss_scale):\n            model.NHWC2NCHW(\"data\", \"data_nchw\")\n            model.Conv(\"data_nchw\", 'conv1', 3, 64,\n                       weight_init=(\"MSRAFill\", {}), kernel=7,\n                       stride=2, pad=3, no_bias=0)\n            model.SpatialBN('conv1', 'conv1_spatbn_relu', 64, epsilon=1e-3, is_test=False)\n            model.Relu('conv1_spatbn_relu', 'conv1_spatbn_relu')\n            model.MaxPool('conv1_spatbn_relu', 'pool1', kernel=3, stride=2)\n            model.FC('pool1', 'fc', dim_in=(64 * 56 * 56), dim_out=100)\n            model.Sigmoid('fc', 'fc_sigm')\n            model.Softmax('fc_sigm', 'softmax')\n            model.LabelCrossEntropy(['softmax', 'label'], 'xent')\n            loss = model.AveragedLoss('xent', 'loss')\n\n            # Add a duplicate param init to ensure it does not cause issues\n            model.param_init_net.ConstantFill(\n                [], [\"fc_w\"], shape=((64 * 56 * 56), 1000)\n            )\n            return [loss]\n\n        def add_optimizer(model):\n            optimizer.build_sgd(model, 0.1, policy=\"fixed\", momentum=0.9)\n\n        model = cnn.CNNModelHelper(\n            order=\"NHWC\",\n            name=\"test\",\n        )\n        data_parallel_model.Parallelize_CPU(\n            model,\n            input_builder_fun=add_input_ops,\n            forward_pass_builder_fun=add_model_ops,\n            optimizer_builder_fun=add_optimizer,\n            devices=[1, 2, 3],\n        )\n\n        # Only gpu_1 params should be returned (gpu_1 is the first gpu)\n        checkpoint_params = data_parallel_model.GetCheckpointParams(model)\n        for p in model.GetParams(\"cpu_1/\"):\n            self.assertTrue(p in checkpoint_params)\n            self.assertTrue(p + \"_momentum\" in checkpoint_params)\n        for p in model.GetParams(\"cpu_2/\"):\n            self.assertFalse(p in checkpoint_params)\n        self.assertTrue(\n            core.BlobReference(\"cpu_1/fc_w_momentum\") in checkpoint_params)\n        for c in model.GetComputedParams(\"cpu_1/\"):\n            self.assertTrue(c in checkpoint_params)\n        for c in model.GetComputedParams(\"cpu_2/\"):\n            self.assertFalse(c in checkpoint_params)\n        self.assertFalse(core.BlobReference(\"cpu_1/data\") in checkpoint_params)\n        self.assertTrue(core.BlobReference(\"optimizer_iteration\") in checkpoint_params)\n\n    def test_net_conversion_and_append_net(self):\n        other = model_helper.ModelHelper()\n        fc1 = brew.fc(other, \"data\", \"other_fc1\", dim_in=3*227*227, dim_out=10)\n        fc2 = brew.fc(other, fc1, \"other_fc2\", dim_in=10, dim_out=10)\n        brew.fc(other, fc2, \"other_fc3\", dim_in=10, dim_out=10)\n\n        def add_input_ops(model):\n            model.net.UniformFill([], [\"data\"], shape=[4, 227, 227, 3])\n            model.net.UniformFill([], [\"label\"], shape=[4])\n\n        def add_model_ops(model, loss_scale):\n            model.NHWC2NCHW(\"data\", \"data_nchw\")\n            model.Conv(\"data_nchw\", 'conv1', 3, 64,\n                       weight_init=(\"MSRAFill\", {}), kernel=7,\n                       stride=2, pad=3, no_bias=0)\n            model.SpatialBN('conv1', 'conv1_spatbn_relu', 64, epsilon=1e-3, is_test=False)\n            model.Relu('conv1_spatbn_relu', 'conv1_spatbn_relu')\n            model.MaxPool('conv1_spatbn_relu', 'pool1', kernel=3, stride=2)\n            model.FC('pool1', 'fc', dim_in=(64 * 56 * 56), dim_out=10)\n\n            # Append the net and param_init_net of the other model\n            appendnet = data_parallel_model.ConvertNetForDevice(other.net)\n            model.net.AppendNet(appendnet)\n\n            model.param_init_net.AppendNet(\n                data_parallel_model.ConvertNetForDevice(other.param_init_net))\n\n            model.Sigmoid('fc', 'fc_sigm')\n            model.Softmax('fc_sigm', 'softmax')\n            loss = model.AveragedLoss('softmax', 'loss')\n            return [loss]\n\n        def add_optimizer(model):\n            optimizer.build_sgd(model, 0.1, policy=\"fixed\", momentum=0.9)\n\n        model = cnn.CNNModelHelper(\n            order=\"NCHW\",\n            name=\"test\",\n        )\n        data_parallel_model.Parallelize_CPU(\n            model,\n            input_builder_fun=add_input_ops,\n            forward_pass_builder_fun=add_model_ops,\n            optimizer_builder_fun=add_optimizer,\n            devices=range(4)\n        )\n\n        # Just create and run net and confirm no exception is thrown\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.CreateNet(model.net)\n        workspace.RunNet(model.net)\n\n\n    def test_synchronization_barrier(self):\n\n        def run(comm_rank, comm_size, tmpdir):\n            def add_input_ops(model):\n                pass\n\n            def add_model_ops(model, loss_scale):\n                return []\n\n            def add_optimizer(model):\n                pass\n\n            store_handler = \"store_handler\"\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"FileStoreHandlerCreate\",\n                    [],\n                    [store_handler],\n                    path=tmpdir))\n            rendezvous = dict(\n                kv_handler=store_handler,\n                shard_id=comm_rank,\n                num_shards=comm_size,\n                engine='GLOO',\n            )\n\n            model = cnn.CNNModelHelper(\n                order=\"NHWC\",\n                name=\"test\",\n            )\n            data_parallel_model.Parallelize_CPU(\n                model,\n                input_builder_fun=add_input_ops,\n                forward_pass_builder_fun=add_model_ops,\n                optimizer_builder_fun=add_optimizer,\n                devices=[1, 2, 3],\n                rendezvous=rendezvous\n            )\n            data_parallel_model.RunInitNet(model)\n\n            for _ in range(2):\n                data_parallel_model.Synchronize(model)\n\n        with TemporaryDirectory() as tmpdir:\n            self.run_test_locally(\n                run,\n                comm_size=2,\n                device_option=None,\n                tmpdir=tmpdir)\n\n    def test_device_scope_check(self):\n        with self.assertRaises(AssertionError):\n            with core.DeviceScope(core.DeviceOption(caffe2_pb2.CUDA, 0)):\n                data_parallel_model.Parallelize_GPU(None, None, None)\n\n\nclass RecurrentNetworkParallelTest(TestCase):\n\n    def run_model(self, devices, gpu):\n\n        '''\n        Helper function for test_equiv\n        '''\n        def input_builder_fun(model):\n            return None\n\n        def model_build_fun(model, loss_scale):\n            workspace.FeedBlob(\n                core.ScopedBlobReference(\"seq_lengths\"),\n                np.array([self.T] * self.batch_per_device, dtype=np.int32)\n            )\n            model.param_init_net.ConstantFill(\n                [],\n                \"hidden_init\",\n                value=0.0,\n                shape=[1, self.batch_per_device, self.hidden_dim]\n            )\n            model.param_init_net.ConstantFill(\n                [],\n                \"cell_init\",\n                value=0.0,\n                shape=[1, self.batch_per_device, self.hidden_dim]\n            )\n\n            output, _last_hidden, _, _last_state, = rnn_cell.LSTM(\n                model=model,\n                input_blob=\"data\",\n                seq_lengths=\"seq_lengths\",\n                initial_states=(\"hidden_init\", \"cell_init\"),\n                dim_in=self.input_dim,\n                dim_out=self.hidden_dim,\n                scope=\"partest\",\n            )\n\n            # A silly loss function\n            loss = model.AveragedLoss(\n                model.Sub([output, \"target\"], \"dist\"),\n                \"loss\",\n            )\n            loss = model.Scale(loss, \"loss_scaled\", scale=loss_scale)\n            return [loss]\n\n        def param_update_fun(model):\n            ITER = model.Iter(\"ITER\")\n            LR = model.net.LearningRate(\n                [ITER],\n                \"LR\",\n                base_lr=(-0.1),\n                policy=\"fixed\",\n            )\n            ONE = model.param_init_net.ConstantFill(\n                [], \"ONE\", shape=[1], value=1.0,\n            )\n            for param in model.GetParams():\n                param_grad = model.param_to_grad[param]\n                model.WeightedSum([param, ONE, param_grad, LR], param)\n\n            assert len(model.GetParams()) == len(model.params) // len(model._devices)\n\n        workspace.ResetWorkspace()\n        model = cnn.CNNModelHelper(\n            name=\"recurrent_test{}\".format(devices),\n        )\n\n        self.T = 8\n        self.batch_size = 64\n        self.input_dim = 8\n        self.hidden_dim = 31\n        self.batch_per_device = self.batch_size // len(devices)\n\n        data_parallel_model.Parallelize(\n            model,\n            input_builder_fun=input_builder_fun,\n            forward_pass_builder_fun=model_build_fun,\n            param_update_builder_fun=param_update_fun,\n            devices=devices,\n            optimize_gradient_memory=True,\n            cpu_device=not gpu,\n        )\n\n        # Change all initialization to be ConstantFills so that\n        # the everything is deterministic\n        for op in model.param_init_net.Proto().op:\n            if op.type.endswith('Fill'):\n                op.type = 'ConstantFill'\n\n        # Each run has same input, independent of number of gpus\n        np.random.seed(20150210)\n        for i in range(0, 10):\n            full_data = np.random.rand(self.T, self.batch_size, self.input_dim)\n            full_target = np.random.rand(\n                self.T, self.batch_size, self.hidden_dim\n            )\n\n            for (j, g) in enumerate(devices):\n                st = j * self.batch_per_device\n                en = st + self.batch_per_device\n                data = full_data[:, st:en, :].astype(np.float32)\n                targets = full_target[:, st:en, :].astype(np.float32)\n                with core.DeviceScope(core.DeviceOption(model._device_type, g)):\n                    workspace.FeedBlob(\n                        \"{}_{}/data\".format(model._device_prefix, g), data\n                    )\n                    workspace.FeedBlob(\n                        \"{}_{}/target\".format(model._device_prefix, g), targets\n                    )\n\n            if i == 0:\n                workspace.RunNetOnce(model.param_init_net)\n                workspace.CreateNet(model.net)\n\n            workspace.RunNet(model.net.Proto().name)\n\n        return workspace.FetchBlob(\"{}_0/partest/i2h_w\".format(model._device_prefix))\n\n    def test_equiv_recurrent(self):\n        '''\n        Test that the model produces exactly same results given\n        total batchsize, independent of number of GPUs/CPUs.\n        '''\n        for gpu in [True, False]:\n            if gpu and not workspace.has_gpu_support:\n                continue\n            result_2gpus = self.run_model([0, 1], gpu)\n            result_1gpus = self.run_model([0], gpu)\n\n            self.assertTrue(np.allclose(result_1gpus, result_2gpus))\n\n            if not gpu or workspace.NumCudaDevices() >= 4:\n                result_4gpus = self.run_model(list(range(4)), gpu)\n                self.assertTrue(np.allclose(result_1gpus, result_4gpus))\n\n            if not gpu or workspace.NumCudaDevices() >= 8:\n                result_8gpus = self.run_model(list(range(8)), gpu)\n                self.assertTrue(np.allclose(result_1gpus, result_8gpus))\n\n\n@unittest.skipIf(not workspace.has_gpu_support, \"No gpu support.\")\n@unittest.skipIf(workspace.NumCudaDevices() < 2, \"Need at least 2 GPUs.\")\nclass SparseDataParallelModelTest(TestCase):\n\n    '''\n    Create and run the model. We try with both storing indices for gather\n    on CPU and on GPU\n    '''\n    def run_model(self, V, gpu_devices, cpu_indices):\n\n        def input_builder_fun(model):\n            return None\n\n        def model_build_fun(model, loss_scale):\n            if cpu_indices:\n                with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n                    gathered_cpu = model.net.Gather(\n                        [self.vecs, 'indices'], 'gathered_cpu')\n\n                gathered = model.CopyCPUToGPU(gathered_cpu, \"gathered\")\n            else:\n                gpu_vecs = model.param_init_net.CopyCPUToGPU(\n                    self.vecs, \"gpuvecs\",\n                )\n                model.params.append(gpu_vecs)\n                gathered = model.net.Gather([gpu_vecs, 'indices'], 'gathered')\n            flattened = model.Flatten(gathered, \"flattened\")\n            fc = model.FC(flattened, \"fc\", 16 * 16, 1,\n                          (\"ConstantFill\", {}), (\"ConstantFill\", {}))\n            fc_fl = model.FlattenToVec(fc, \"fc_fl\")\n            sigm = model.Sigmoid(fc_fl, \"sigm\")\n            sq = model.SquaredL2Distance([sigm, \"label\"], \"sq\")\n            loss = model.AveragedLoss(sq, \"loss\")\n            loss = model.Scale(loss, scale=loss_scale)\n            return [loss]\n\n        def param_update_fun(model):\n            ONE = model.param_init_net.ConstantFill(\n                [], \"ONE\", shape=[1], value=1.0,\n            )\n            LR = model.CopyCPUToGPU(self.LR, \"LR\")\n            for param in model.GetParams():\n                param_grad = model.param_to_grad[param]\n                if not isinstance(param_grad, core.GradientSlice):\n                    model.WeightedSum([param, ONE, param_grad, LR], param)\n                else:\n                    param_momentum = model.param_init_net.ConstantFill(\n                        [param],\n                        param + '_momentum',\n                        value=0.0,\n                    )\n                    model.net.SparseMomentumSGDUpdate(\n                        [\n                            param_grad.values,\n                            param_momentum,\n                            LR,\n                            param,\n                            param_grad.indices,\n                        ],\n                        [\n                            param_grad.values, param_momentum, param\n                        ],\n                        momentum=0.1,\n                        nesterov=0,\n                    )\n\n        workspace.ResetWorkspace()\n        model = cnn.CNNModelHelper(\n            order=\"NHWC\",\n            name=\"sparse_test{}\".format(gpu_devices),\n        )\n\n        with core.NameScope(\"cpu\"):\n            with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n                self.ITER = model.Iter(\"ITER\")\n                self.LR = model.net.LearningRate(\n                    [self.ITER],\n                    \"LR\",\n                    base_lr=(-0.1),\n                    policy=\"fixed\",\n                )\n                self.vecs = model.param_init_net.UniformFill(\n                    [], \"vecs\", shape=[V, 16])\n                if cpu_indices:\n                    model.params.append(self.vecs)\n                self.ONE_CPU = model.param_init_net.ConstantFill(\n                    [], \"ONE_CPU\", shape=[1], value=1.0,\n                )\n\n        data_parallel_model.Parallelize_GPU(\n            model,\n            input_builder_fun=input_builder_fun,\n            forward_pass_builder_fun=model_build_fun,\n            param_update_builder_fun=param_update_fun,\n            devices=gpu_devices,\n        )\n\n        # Update the vecs\n        if cpu_indices:\n            with core.NameScope(\"cpu\"):\n                with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n                    for param in model.GetParams():\n                        param_grad = model.param_to_grad[param]\n                        model.ScatterWeightedSum([param, self.ONE_CPU,\n                                                  param_grad.indices,\n                                                  param_grad.values,\n                                                  self.LR],\n                                                  self.vecs)\n        else:\n            with core.DeviceScope(core.DeviceOption(caffe2_pb2.CUDA, 0)):\n                model.CopyGPUToCPU(\"gpu_0/gpuvecs\", self.vecs)\n\n        np.random.seed(2603)\n\n        # Each run has same input, independent of number of gpus\n        batch_size = 64\n        for i in range(0, 10):\n            full_indices = np.random.permutation(V)[:batch_size * 16].reshape(\n                batch_size, 16\n            )\n            full_labels = full_indices[:, 0] % 2\n            batch_per_device = batch_size // len(gpu_devices)\n\n            for (j, g) in enumerate(gpu_devices):\n                st = j * batch_per_device\n                en = st + batch_per_device\n                indices = full_indices[st:en, :].astype(np.int32)\n                labels = full_labels[st:en].astype(np.float32)\n\n                device_for_indices = core.DeviceOption(caffe2_pb2.CPU)\n                if not cpu_indices:\n                    device_for_indices = core.DeviceOption(caffe2_pb2.CUDA, g)\n\n                with core.DeviceScope(device_for_indices):\n                    workspace.FeedBlob(\"gpu_{}/indices\".format(g), indices)\n\n                with core.DeviceScope(core.DeviceOption(caffe2_pb2.CUDA, g)):\n                    workspace.FeedBlob(\"gpu_{}/label\".format(g), labels)\n\n            if i == 0:\n                workspace.RunNetOnce(model.param_init_net)\n                # Force vecs to be same on all runs\n                orig_vecs = np.random.rand(V, 16).astype(np.float32)\n                workspace.FeedBlob(\n                    self.vecs,\n                    orig_vecs\n                )\n                if not cpu_indices:\n                    for g in gpu_devices:\n                        workspace.FeedBlob(\n                            \"gpu_{}/gpuvecs\".format(g),\n                            orig_vecs,\n                            device_option=core.DeviceOption(caffe2_pb2.CUDA, g),\n                        )\n                workspace.CreateNet(model.net)\n\n            workspace.RunNet(model.net.Proto().name)\n            if len(gpu_devices) == 2:\n                if not cpu_indices:\n                    idx = workspace.FetchBlob(\"gpu_0/indices\")\n                    idx = list(idx.flatten())\n                    n = len(idx)\n                    nu = len(set(idx))\n                    assert n == nu, \"We cannot have duplicate indices\"\n\n        # Sanity check to see the vecs were updated\n        self.assertFalse(\n            np.allclose(workspace.FetchBlob(self.vecs), orig_vecs))\n        return [workspace.FetchBlob(self.vecs if cpu_indices else \"gpu_0/gpuvecs\"),\n                workspace.FetchBlob(\"gpu_0/fc_w\")]\n\n    def _test_equiv_sparse(self, cpu_indices):\n        '''\n            Test that the model produces exactly same results given\n            total batchsize, independent of number of GPUs.\n        '''\n        V = 10000\n        result_2gpus = self.run_model(V, [0, 1], cpu_indices)\n        result_1gpus = self.run_model(V, [0], cpu_indices)\n\n        self.assertTrue(np.allclose(result_1gpus[0], result_2gpus[0]))\n        self.assertTrue(np.allclose(result_1gpus[1], result_2gpus[1]))\n\n        if workspace.NumCudaDevices() >= 4:\n            result_4gpus = self.run_model(V, list(range(4)), cpu_indices)\n            self.assertTrue(np.allclose(result_1gpus[0], result_4gpus[0]))\n            self.assertTrue(np.allclose(result_1gpus[1], result_4gpus[1]))\n\n        if workspace.NumCudaDevices() >= 8:\n            result_8gpus = self.run_model(V, list(range(8)), cpu_indices)\n            self.assertTrue(np.allclose(result_1gpus[0], result_8gpus[0]))\n            self.assertTrue(np.allclose(result_1gpus[1], result_8gpus[1]))\n\n    def test_equiv_sparse(self):\n        self._test_equiv_sparse(True)\n        self._test_equiv_sparse(False)\n\n\n@unittest.skipIf(workspace.NumCudaDevices() < 2, \"Need at least 2 GPUs.\")\nclass ParallelizeBMUFTest(TestCase):\n\n    def _run_model(self, gpu_devices):\n        '''\n        Helper function for test_equiv\n        '''\n        def input_builder_fun(model):\n            return None\n\n    def _model_build_fun(self, model, loss_scale):\n        fc = model.FC(\n            \"data\", \"fc\", 16, 1, (\"ConstantFill\", {}), (\"ConstantFill\", {})\n        )\n        fc_fl = model.FlattenToVec(fc, \"fc_fl\")\n        sigm = model.Sigmoid(fc_fl, \"sigm\")\n        sq = model.SquaredL2Distance([sigm, \"label\"], \"sq\")\n        loss = model.AveragedLoss(sq, \"loss\")\n        loss = model.Scale(loss, scale=loss_scale)\n\n        return [loss]\n\n    def _param_update_fun(self, model):\n        ITER = model.Iter(\"ITER\")\n        LR = model.net.LearningRate(\n            [ITER],\n            \"LR\",\n            base_lr=(-0.1),\n            policy=\"fixed\",\n        )\n        ONE = model.param_init_net.ConstantFill(\n            [], \"ONE\", shape=[1], value=1.0,\n        )\n        for param in model.GetParams():\n            grad = model.param_to_grad[param]\n            model.WeightedSum([param, ONE, grad, LR], param)\n\n    def _generate_data(self, devices, device_type, device_prefix):\n        np.random.seed(26)\n        # Each run has same input, independent of number of gpus\n        batch_size = 64\n        for _ in range(0, 10):\n            full_data = np.random.rand(batch_size, 16)\n            full_labels = np.round(full_data[:, 0])\n            batch_per_device = batch_size // len(devices)\n\n            for (j, g) in enumerate(devices):\n                st = j * batch_per_device\n                en = st + batch_per_device\n                data = full_data[st:en, :].astype(np.float32)\n                labels = full_labels[st:en].astype(np.float32)\n                with core.DeviceScope(core.DeviceOption(device_type, g)):\n                    workspace.FeedBlob(\"{}_{}/data\".format(device_prefix, g), data)\n                    workspace.FeedBlob(\"{}_{}/label\".format(device_prefix, g), labels)\n\n    @given(\n        cpu_device=st.booleans()\n    )\n    def test_parallelize_bmuf(self, cpu_device):\n        assume(cpu_device or workspace.has_gpu_support)\n\n        workspace.ResetWorkspace()\n\n        model = cnn.CNNModelHelper(\n            order=\"NHWC\",\n            name=\"test\"\n        )\n        devices = [0, 1]\n\n        def input_builder_fun(model):\n            return None\n\n        if not cpu_device:\n            device_type = caffe2_pb2.CUDA\n            device_prefix = \"gpu\"\n        else:\n            device_type = caffe2_pb2.CPU\n            device_prefix = \"cpu\"\n        self._generate_data(devices, device_type, device_prefix)\n\n        data_parallel_model.Parallelize_BMUF(\n            model,\n            input_builder_fun,\n            self._model_build_fun,\n            self._param_update_fun,\n            devices=devices,\n            cpu_device=cpu_device\n        )\n\n        data_parallel_model.RunInitNet(model)\n\n        # Check initial momentum params are zeros\n        self.assertEqual(\n            list(viewkeys(model._device_grouped_blobs)), ['fc_w', 'fc_b']\n        )\n        self.assertEqual(workspace.FetchBlob('{}_0/fc_b_v'.format(device_prefix)), 0)\n        np.testing.assert_equal(\n            workspace.FetchBlob('{}_0/fc_w_v'.format(device_prefix)),\n            np.zeros(16).astype(np.float32).reshape(1, 16)\n        )\n\n        # Run the algorithm for one iteration to have non-zero params.\n        data_parallel_model.RunNet(model, 1)\n\n        # Save iteration momentum and post local update params\n        v_b_ = workspace.FetchBlob('{}_0/fc_b_v'.format(device_prefix))\n        v_w_ = workspace.FetchBlob('{}_0/fc_w_v'.format(device_prefix))\n\n        workspace.RunNetOnce(model.net)\n\n        b_0_ = workspace.FetchBlob('{}_0/fc_b'.format(device_prefix))\n        w_0_ = workspace.FetchBlob('{}_0/fc_w'.format(device_prefix))\n        b_1_ = workspace.FetchBlob('{}_1/fc_b'.format(device_prefix))\n        w_1_ = workspace.FetchBlob('{}_1/fc_w'.format(device_prefix))\n\n        # Compute block gradients.\n        b_g_ = workspace.FetchBlob('{}_0/fc_b_g'.format(device_prefix))\n        w_g_ = workspace.FetchBlob('{}_0/fc_w_g'.format(device_prefix))\n        workspace.RunNetOnce(model._global_model_param_updates_net)\n\n        g_b = (b_0_ + b_1_) / 2 - b_g_\n        g_w = (w_0_ + w_1_) / 2 - w_g_\n        v_b = workspace.FetchBlob('{}_0/fc_b_v'.format(device_prefix))\n        v_w = workspace.FetchBlob('{}_0/fc_w_v'.format(device_prefix))\n\n        w_g = workspace.FetchBlob('{}_0/fc_w_g'.format(device_prefix))\n        b_g = workspace.FetchBlob('{}_0/fc_b_g'.format(device_prefix))\n        w_0 = workspace.FetchBlob('{}_0/fc_w'.format(device_prefix))\n        b_0 = workspace.FetchBlob('{}_0/fc_b'.format(device_prefix))\n        w_1 = workspace.FetchBlob('{}_1/fc_w'.format(device_prefix))\n        b_1 = workspace.FetchBlob('{}_1/fc_b'.format(device_prefix))\n\n        # Check momentum update step\n        np.testing.assert_equal(v_b, 0.5 * v_b_ + g_b)\n        np.testing.assert_equal(v_w, 0.5 * v_w_ + g_w)\n\n        np.testing.assert_equal(w_g, w_0)\n        np.testing.assert_equal(w_g, w_1)\n        np.testing.assert_equal(b_g, b_0)\n        np.testing.assert_equal(b_g, b_1)\n\n        # Check params update step\n        np.testing.assert_equal(w_0, w_g_ + v_w)\n        np.testing.assert_equal(b_0, b_g_ + v_b)\n\n\n@unittest.skipIf(not workspace.has_gpu_support, \"No gpu support.\")\n@unittest.skipIf(workspace.NumCudaDevices() < 2, \"Need at least 2 GPUs.\")\nclass SparseDataParallelModelTestWithSharedIndices(TestCase):\n\n    '''\n    Create and run the model. We try with both storing indices for gather\n    on CPU and on GPU\n    '''\n    def run_model(self, V, gpu_devices):\n\n        def input_builder_fun(model):\n            return None\n\n        def model_build_fun(model, loss_scale):\n            gpu_vecs_gathered = []\n            gpu_vecs = []\n            for num, vec in enumerate(self.vecs):\n                gpu_vec = model.param_init_net.CopyCPUToGPU(\n                    vec, 'gpuvec_{}'.format(num),\n                )\n                if num != 2:\n                    model.params.append(gpu_vec)\n                gpu_vecs.append(gpu_vec)\n            for num, gpu_vec in enumerate(gpu_vecs):\n                gpu_vec_gathered = model.net.Gather(\n                    [gpu_vec, 'indices'],\n                    ['gpu_vec_gathered_{}'.format(num)]\n                )\n                gpu_vecs_gathered.append(gpu_vec_gathered)\n\n            assert len(gpu_vecs_gathered) == 3\n\n            fc = model.net.FC(\n                [\n                    gpu_vecs_gathered[2],\n                    gpu_vecs_gathered[0],\n                    gpu_vecs_gathered[1],\n                ],\n                ['fc'],\n            )\n            _, loss = model.net.SoftmaxWithLoss(\n                [fc, 'label'],\n                ['ce_loss', 'avg_loss'],\n                only_loss=True,\n            )\n            loss = model.Scale(loss, scale=loss_scale)\n            model.net.Print(loss, [], limit=10)\n            return [loss]\n\n        def param_update_fun(model):\n            ONE = model.param_init_net.ConstantFill(\n                [], \"ONE\", shape=[1], value=1.0,\n            )\n            LR = model.CopyCPUToGPU(self.LR, \"LR\")\n            for param in model.GetParams():\n                param_grad = model.param_to_grad[param]\n                if not isinstance(param_grad, core.GradientSlice):\n                    model.WeightedSum([param, ONE, param_grad, LR], param)\n                else:\n                    model.net.ScatterWeightedSum(\n                        [\n                            param,\n                            ONE,\n                            param_grad.indices,\n                            param_grad.values,\n                            ONE,\n                        ],\n                        param,\n                    )\n\n        workspace.ResetWorkspace()\n        model = cnn.CNNModelHelper(\n            order=\"NHWC\",\n            name=\"sparse_test{}\".format(gpu_devices),\n        )\n        batch_size = 32\n        batch_per_device = batch_size // len(gpu_devices)\n\n        with core.NameScope(\"cpu\"):\n            with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n                self.ITER = model.Iter(\"ITER\")\n                self.LR = model.net.LearningRate(\n                    [self.ITER],\n                    \"LR\",\n                    base_lr=(-0.1),\n                    policy=\"fixed\",\n                )\n                '''\n                self.vecs consists of 3 big blobs on which we call Gather:\n                1) FC weights, shape=(V, 16)\n                2) FC bias, shape=(V)\n                3) FC input, shape=(batch_per_device, 16)\n                '''\n                self.vecs = [\n                    model.param_init_net.UniformFill(\n                        [], \"vec_{}\".format(num), shape=[V, 16])\n                    for num in range(2)\n                ]\n                self.vecs.append(\n                    model.param_init_net.UniformFill(\n                        [],\n                        \"vec_2\", shape=[batch_per_device, 16]\n                    )\n                )\n                self.ONE_CPU = model.param_init_net.ConstantFill(\n                    [], \"ONE_CPU\", shape=[1], value=1.0,\n                )\n\n        data_parallel_model.Parallelize_GPU(\n            model,\n            input_builder_fun=input_builder_fun,\n            forward_pass_builder_fun=model_build_fun,\n            param_update_builder_fun=param_update_fun,\n            devices=gpu_devices,\n        )\n\n        # Update the vecs\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CUDA, 0)):\n            for num, vec in enumerate(self.vecs[:-1]):\n                model.CopyGPUToCPU(\"gpu_0/gpuvec_{}\".format(num), vec)\n\n        # Each run has same input, independent of number of gpus\n        for i in range(0, 10):\n            np.random.seed(2603)\n            full_indices = np.random.permutation(V)[:batch_size].reshape(\n                batch_size\n            )\n            full_labels = full_indices[:] % batch_per_device\n\n            for (j, g) in enumerate(gpu_devices):\n                st = j * batch_per_device\n                en = st + batch_per_device\n                indices = full_indices[st:en].astype(np.int32)\n                labels = full_labels[st:en].astype(np.int32)\n\n                with core.DeviceScope(core.DeviceOption(caffe2_pb2.CUDA, g)):\n                    workspace.FeedBlob(\"gpu_{}/indices\".format(g), indices)\n                    workspace.FeedBlob(\"gpu_{}/label\".format(g), labels)\n\n            if i == 0:\n                workspace.RunNetOnce(model.param_init_net)\n                # Force vecs to be same on all runs\n                orig_vecs = [\n                    np.random.rand(V, 16).astype(np.float32),\n                    np.random.rand(V).astype(np.float32),\n                    np.random.rand(V, 16).astype(np.float32),\n                ]\n                for vec, orig_vec in zip(self.vecs, orig_vecs):\n                    workspace.FeedBlob(\n                        vec,\n                        orig_vec\n                    )\n                for g in gpu_devices:\n                    for num, orig_vec in enumerate(orig_vecs):\n                        workspace.FeedBlob(\n                            \"gpu_{}/gpuvec_{}\".format(g, num),\n                            orig_vec,\n                            device_option=core.DeviceOption(\n                                caffe2_pb2.CUDA, g),\n                        )\n                workspace.CreateNet(model.net)\n\n            workspace.RunNet(model.net.Proto().name)\n\n            idx = workspace.FetchBlob('gpu_0/indices')\n            grad_slices = [\n                workspace.FetchBlob(\n                    'gpu_{}/gpu_vec_gathered_{}_grad'.format(g, num))\n                for g in gpu_devices for num in range(2)\n            ]\n            for grad_slice in grad_slices:\n                # print (len(idx), len(grad_slice))\n                assert len(idx) == len(grad_slice), (\n                    'Number of indices {} is not same as number of gradient '\n                    'slices {}. This might lead to illegal memory access'.format(\n                        len(idx), len(grad_slice)\n                    )\n                )\n\n    def test_sparse_shared_indices_gpu(self):\n        '''\n            Test that the model has same number of indices and gradient rows\n            given total batchsize, independent of number of GPUs.\n        '''\n        V = 10000\n        self.run_model(V, [0, 1])\n        self.run_model(V, [0])\n\n        if workspace.NumCudaDevices() >= 4:\n            self.run_model(V, list(range(4)))\n\n        if workspace.NumCudaDevices() >= 8:\n            self.run_model(V, list(range(8)))\n\n\n@unittest.skipIf(workspace.has_gpu_support, \"No GPU support\")\n@unittest.skipIf(workspace.NumCudaDevices() < 4, \"Test requires at least 4 GPUs\")\nclass DeviceShiftTest(TestCase):\n    def create_model(self):\n        def input_builder_fun(model):\n            model.param_init_net.UniformFill([], [\"data\"], shape=[32, 8])\n\n        def model_build_fun(model, loss_scale):\n            fc1 = brew.fc(model, \"data\", \"fc1\", dim_in=8, dim_out=8)\n            fc2 = brew.fc(model, fc1, \"fc2\", dim_in=8, dim_out=8)\n            fc3 = brew.fc(model, fc2, \"fc3\", dim_in=8, dim_out=8)\n            fc4 = brew.fc(model, fc3, \"fc4\", dim_in=8, dim_out=8)\n            fc5 = brew.fc(model, fc4, \"fc5\", dim_in=8, dim_out=8)\n            loss = model.net.SumElements([fc5], [\"loss\"])\n            return [loss]\n\n        def add_optimizer(model):\n            return optimizer.build_sgd(model, 0.1, policy=\"fixed\")\n\n        model = model_helper.ModelHelper()\n        data_parallel_model.Parallelize(\n            model,\n            input_builder_fun=input_builder_fun,\n            forward_pass_builder_fun=model_build_fun,\n            optimizer_builder_fun=add_optimizer,\n            devices=[0, 1, 2, 3],\n        )\n        return model\n\n    def test_activation_blobs(self):\n        model = self.create_model()\n        activations = data_parallel_model_utils.GetActivationBlobs(model)\n        self.assertEqual(activations, [\"fc1\", \"fc2\", \"fc3\", \"fc4\", \"fc5\", \"loss\"])\n\n    def test_shift_gpu(self):\n        model = self.create_model()\n        data_parallel_model_utils.ShiftActivationDevices(\n            model,\n            activations=[\"fc4\", \"fc5\"],\n            shifts={0: 4, 1: 4, 2: 5, 3: 5},\n        )\n        for op in model.param_init_net.Proto().op:\n            for outp in op.output:\n                prefix = outp.split(\"/\")[0]\n                if outp.split(\"/\")[-1] in set(['fc4_w', 'fc5_w', 'fc4_b', 'fc5_b']):\n                    if prefix == 'gpu_0' or prefix == 'gpu_1':\n                        self.assertEqual(op.device_option.cuda_gpu_id, 4)\n                    else:\n                        self.assertEqual(op.device_option.cuda_gpu_id, 5)\n                if outp.split(\"/\")[-1] in set(['fc1_w', 'fc2_w', 'fc3_b', 'fc3_w']):\n                    gpu_id = int(prefix.split(\"_\")[-1])\n                    self.assertEqual(gpu_id, op.device_option.cuda_gpu_id)\n\n        # Test that we can run the net\n        if workspace.NumCudaDevices() >= 6:\n            workspace.RunNetOnce(model.param_init_net)\n            workspace.CreateNet(model.net)\n            workspace.RunNet(model.net.Proto().name)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/data_parallel_model_utils.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package data_parallel_model_utils\n# Module caffe2.python.data_parallel_model_utils\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nfrom future.utils import viewitems, viewkeys, viewvalues\n\nimport logging\n\nfrom caffe2.python import core\nfrom caffe2.python.data_parallel_model import stripBlobName\n\nlog = logging.getLogger(\"data_parallel_model_utils\")\nlog.setLevel(logging.INFO)\n\n\ndef GetActivationBlobs(model):\n    # Hacky way to get activations, think of a better way\n    activations = []\n    first_gpu_prefix = \"{}_{}/\".format(model._device_prefix, model._devices[0])\n\n    all_inputs = set()\n    for op in model.net.Proto().op:\n        for inp in op.input:\n            all_inputs.add(inp)\n\n    params = set(model.GetParams(''))\n\n    for op in model.net.Proto().op:\n        for b in op.output:\n            if b.startswith(first_gpu_prefix) and not b.endswith(\"_grad\"):\n                if b in all_inputs and b not in params and b + \"_grad\" in all_inputs:\n                    activations.append(stripBlobName(b))\n    return activations\n\n\ndef _ShiftActivationDevices(model, activations, from_device, to_device):\n    prefix = \"{}_{}/\".format(model._device_prefix, from_device)\n    activations = set([prefix + a for a in activations])\n    all_activations = set([prefix + a for a in GetActivationBlobs(model)])\n    ops = list(op for op in model.net.Proto().op if\n               op.device_option.cuda_gpu_id == from_device)\n    device_mapping = {a: to_device for a in activations}\n    device_mapping.update({b: from_device for b in all_activations if\n                           b not in activations})\n\n    # Assign each blob to a device in a label propagation manner. activations\n    # override, and if multiple activations in same op, the output activations\n    # determine.\n    for op in ops:\n        op_device = None\n        for b in list(op.input) + list(op.output):\n            if b in device_mapping:\n                if b in all_activations or op_device is None:\n                    op_device = device_mapping[b]\n        if op_device is None:\n            op_device = op.device_option.cuda_gpu_id\n        for b in list(op.input) + list(op.output):\n            if b not in device_mapping and b.startswith(prefix):\n                device_mapping[b] = op_device\n        op.device_option.cuda_gpu_id = op_device\n\n    # Change param_init_net accordingly\n    for op in model.param_init_net.Proto().op:\n        if op.output[0] in device_mapping:\n            op.device_option.cuda_gpu_id = device_mapping[op.output[0]]\n\n\ndef ShiftActivationDevices(model, activations, shifts):\n    '''\n    Function to enable simple model-parallellism for data_parallel_model\n    models. 'shifts' is a dictionary from_gpu -> to_gpu, and activations is\n    a list of activation blobs (wout gpu_x/ prefix -- use GetActivationBlobs()).\n\n    Operators handling these activations are shifted to the gpu declared in\n    'shifts'. Also related operators such as gradient operators will be moved.\n    Appropriate copy-ops are inserted.\n\n    This allows shifting memory usage from one gpu to another, enabling bigger\n    models to be trained.\n    '''\n    assert set(viewvalues(shifts)).intersection(set(viewkeys(shifts))) == set()\n    for from_device, to_device in viewitems(shifts):\n        log.info(\n            \"Shifting {} activations from {} --> {}\".\n            format(len(activations), from_device, to_device)\n        )\n        _ShiftActivationDevices(model, activations, from_device, to_device)\n\n    param_init_net, blob_to_device = core.InjectCrossDeviceCopies(model.param_init_net)\n    net, _blob_to_device = core.InjectCrossDeviceCopies(model.net, blob_to_device)\n    model.param_init_net = param_init_net\n    model.net = net\n"
  },
  {
    "path": "caffe2/python/data_workers.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package data_workers\n# Module caffe2.python.data_workers\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n\n'''\nThis module provides a python-land multithreaded data input mechanism\nfor Caffe2 nets.\n\nBasic usage is as follows:\n   coordinator = data_workers.init_data_input_workers(\n      net,\n      [\"data\", \"label\"],\n      my_fetch_fun,\n      batch_size=32,\n      input_source_name=\"train\",\n      dont_rebatch=False\n   )\n   ...\n   coordinator.start()\n\nFirst argument is the Caffe2 net (or model helper), and second argument\nis list of input blobs that are to be fed.\n\nArgument 'input_source_name' is used to distinguish different sources of data,\nsuch as train or test data. This is to ensure the data does not get mixed up,\nalthough two nets would share blobs.\n\nTo do the actual data loading, one defines a \"fetcher function\"\nthat has call signature\n   my_fetch_fun(worker_id, batch_size)\n\nOptionally, one can define a \"init function\" that is called once before\nthreads start, and has call signature:\n   my_init_fun(data_coordinator, global_coordinator)\n\nIf dont_rebatch is set to True, the data input is not batched into equal sized\nchunks but data directly provided by fetchers is used.\n\n'batch_columns' can be used to specify which dimension is the batch dimension,\nfor each of the inputs. Default is 0 for all iputs.\n\n'timeout' is the timeout in seconds after which if no data is available, the\nnet will fail (default 600s = 10 mins).\n\nThis function returns a list of numpy arrays corresponding to the different\ninput blobs. In the example above, it would return two arrays, one for the\ndata blob and another for the labels. These arrays can have arbitrary number\nof elements (i.e they do not need to match the batch size). The batch size\nis provided for the function as a hint only.\n\nFor example, fetcher function could download images from a remote service or\nload random images from a directory on a file system.\n\nFor a dummy example, see the data_workers_test unit test.\n\nNote that for data_parallel_models, init_data_input_workers will be called\nfor each GPU. Note that the 'coordinator' returned by the function is same\neach time.\n'''\n\ntry:\n    import Queue\nexcept ImportError:\n    # Py3\n    import queue as Queue\nfrom itertools import chain\nimport logging\nimport threading\nimport numpy as np\nimport time\n\nfrom caffe2.python import workspace, core, scope, utils\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python.parallel_workers import Metrics, State, \\\n    WorkerCoordinator, GlobalWorkerCoordinator, Worker, run_worker\n\nlog = logging.getLogger(\"data_workers\")\nlog.setLevel(logging.INFO)\nLOG_INT_SECS = 60\n\n\ndef get_worker_ids(num_workers):\n    return list(range(0, num_workers))\n\n\ndef init_data_input_workers(\n    net,\n    input_blob_names,\n    fetch_fun,\n    batch_size,\n    num_worker_threads=2,\n    input_source_name=\"train\",\n    max_buffered_batches=800,\n    init_fun=None,\n    external_loggers=None,\n    dont_rebatch=False,\n    batch_columns=None,\n    timeout=600\n):\n    global global_coordinator\n    device_option = scope.CurrentDeviceScope()\n    if (device_option is None):\n        device_option = caffe2_pb2.DeviceOption(device_type=caffe2_pb2.CPU)\n\n    metrics = Metrics(external_loggers)\n    batch_feeder = BatchFeeder(\n        net,\n        input_blob_names,\n        batch_size,\n        device_option,\n        scope.CurrentNameScope(),\n        input_source_name,\n        global_coordinator.get_queue(input_source_name, max_buffered_batches),\n        metrics,\n        dont_rebatch,\n        batch_columns,\n        timeout=timeout\n    )\n\n    # Create coordinator object\n    coordinator = WorkerCoordinator(\n        input_source_name, init_fun, batch_feeder)\n\n    # Launch fetch worker threads\n    worker_ids = [\n        global_coordinator.get_new_worker_id()\n        for i in range(num_worker_threads)\n    ]\n    workers = [\n        threading.Thread(\n            target=run_worker,\n            name=\"data_workers fetcher id {}\".format(worker_id),\n            args=[coordinator,\n                  DataWorker(coordinator, worker_id, fetch_fun, metrics,\n                             batch_size, batch_feeder)],\n        ) for worker_id in worker_ids\n    ]\n\n    workers.append(threading.Thread(\n        target=enqueuer,\n        name=\"Enqueuer {} {}\".format(input_source_name, scope.CurrentNameScope()),\n        args=[coordinator, batch_feeder]))\n    coordinator._workers = workers\n    global_coordinator.add(coordinator)\n\n    return global_coordinator\n\n\nclass BatchFeeder(State):\n    def __init__(self, net, input_blob_names, batch_size,\n                 device_option, namescope, input_source_name, queue,\n                 metrics, dont_rebatch, batch_columns, timeout=600):\n        self._counter = 0\n        self._input_blob_names = input_blob_names\n        self._batch_size = batch_size\n        self._internal_queue = queue\n        self._queues = []\n        self._device_option = device_option\n        self._namescope = namescope\n        self._timeout = timeout\n        self._input_source_name = input_source_name\n        self._c2_queue_capacity = 4\n        self._create_caffe2_queues(net)\n        self._create_caffe2_ops(net)\n        self._inputs = 0\n        self._prev_seconds = 0\n        self._last_warning = time.time()\n        self._dont_rebatch = dont_rebatch\n        self._init_scratch()\n        self._metrics = metrics\n\n        if batch_columns is None:\n            batch_columns = [0 for _ in input_blob_names]\n        self._batch_columns = batch_columns\n\n    def start(self):\n        self._inputs = 0\n        self._prev_seconds = time.time()\n\n    def stop(self):\n        try:\n            for q in self._queues:\n                workspace.RunOperatorOnce(\n                    core.CreateOperator(\"CloseBlobsQueue\", [q], [])\n                )\n        finally:\n            self._log_inputs_per_interval(0, force=True)\n\n    def cleanup(self):\n        utils.ResetBlobs(self._scratch_blob.values())\n        utils.ResetBlobs(self._scratch_status.values())\n\n    def _get(self, data_input_coordinator):\n        start_time = time.time()\n        last_warning = time.time()\n        while data_input_coordinator.is_active():\n            try:\n                return self._internal_queue.get(block=True, timeout=0.5)\n            except Queue.Empty:\n                if time.time() - last_warning > 10.0:\n                    log.warning(\"** Data input is slow: (still) no data in {} secs.\".format(\n                        time.time() - start_time))\n                    last_warning = time.time()\n                continue\n        return None\n\n    def _validate_chunk(self, chunk):\n        if chunk is None:\n            log.warning(\"Fetcher function returned None\")\n            return False\n\n        assert len(chunk) == len(self._input_blob_names), \\\n            \"Expecting data blob for each input\"\n        for d in chunk:\n            assert isinstance(d, np.ndarray), \\\n                \"Fetcher function must return a numpy array\"\n        if not self._dont_rebatch:\n            j = 1\n            for d in chunk[1:]:\n                assert d.shape[self._batch_columns[j]] == \\\n                    chunk[0].shape[self._batch_columns[0]], \\\n                    \"Each returned input must have equal number of samples\"\n                j += 1\n\n        if len(chunk) == 0:\n            log.warning(\"Worker provided zero length input\")\n            return False\n\n        return True\n\n    def put(self, chunk, data_input_coordinator):\n        if not self._validate_chunk(chunk):\n            return\n\n        while data_input_coordinator.is_active():\n            try:\n                qsize = self._internal_queue.qsize()\n                if qsize < 2 and (time.time() - self._last_warning) > LOG_INT_SECS:\n                    log.warning(\"Warning, data loading lagging behind: \" +\n                                \"name={}\".format(qsize, self._input_source_name))\n                    self._last_warning = time.time()\n                self._counter += 1\n                self._internal_queue.put(chunk, block=True, timeout=0.5)\n                self._log_inputs_per_interval(chunk[0].shape[0])\n                return\n            except Queue.Full:\n                log.debug(\"Queue full: stalling fetchers...\")\n                continue\n\n    def _enqueue_batch_direct(self, data_input_coordinator):\n        data = self._get(data_input_coordinator)\n        if data is None:\n            return\n        if data_input_coordinator.is_active():\n            for b, q, c in zip(self._input_blob_names, self._queues, data):\n                self._enqueue(b, q, c)\n\n    def _enqueue_batch(self, data_input_coordinator):\n        '''\n        This pulls data from the python-side queue and collects them\n        into batch-sized pieces, unless dont_rebatch is set to true.\n        '''\n        if self._dont_rebatch:\n            self._enqueue_batch_direct(data_input_coordinator)\n            return\n\n        cur_batch = [np.array([]) for d in self._input_blob_names]\n        first_batch_col = self._batch_columns[0]\n\n        # Collect data until we have a full batch size\n        while (\n            cur_batch[0].shape[0] == 0 or\n            cur_batch[0].shape[first_batch_col] < self._batch_size\n        ) and data_input_coordinator.is_active():\n            chunk = self._get(data_input_coordinator)\n            if chunk is None:\n                continue\n\n            for j, chunk_elem in enumerate(chunk):\n                if cur_batch[j].shape[0] == 0:\n                    cur_batch[j] = chunk_elem.copy()\n                else:\n                    cur_batch[j] = np.append(\n                        cur_batch[j], chunk_elem, axis=self._batch_columns[j]\n                    )\n\n        start_time = time.time()\n        try:\n            # Return data over the batch size back to queue\n            if cur_batch[0].shape[0] > 0 and cur_batch[0].shape[\n                first_batch_col\n            ] > self._batch_size:\n                leftover = []\n                trimmed_batch = []\n                for j, b in enumerate(cur_batch):\n                    [c, l] = np.split(\n                        b, [self._batch_size], axis=self._batch_columns[j]\n                    )\n                    leftover.append(l)\n                    trimmed_batch.append(c)\n                cur_batch = trimmed_batch\n                try:\n                    self._internal_queue.put(leftover, block=False)\n                except Queue.Full:\n                    pass\n\n                assert cur_batch[0].shape[first_batch_col] == self._batch_size\n\n            if data_input_coordinator.is_active():\n                for b, q, c in zip(\n                    self._input_blob_names, self._queues, cur_batch\n                ):\n                    self._enqueue(b, q, c)\n        finally:\n            self._metrics.put_metric('enqueue_time', time.time() - start_time)\n\n    def _init_scratch(self):\n        self._scratch_blob = {}\n        self._scratch_status = {}\n        for blob_name in self._input_blob_names:\n            scratch_name = self._namescope + blob_name + \\\n                \"_scratch_\" + self._input_source_name\n            self._scratch_blob[blob_name] = core.BlobReference(scratch_name)\n            self._scratch_status[blob_name] = core.BlobReference(\n                scratch_name + \"_status\"\n            )\n\n        # Feed empty arrays to the scratch blobs here, so that there won't be\n        # race conditions when calling FeedBlob (which calls wworkspace\n        # CreateBlob()) from enqueue threads\n        for b in chain(\n            self._scratch_blob.values(), self._scratch_status.values()\n        ):\n            workspace.FeedBlob(\n                b,\n                np.array([]).astype(np.float32),\n                device_option=self._device_option,\n            )\n\n    def _enqueue(self, blob_name, queue, data_arr):\n        '''\n        Enqueue the correctly sized batch arrays to Caffe2's queue.\n        '''\n        workspace.FeedBlob(\n            self._scratch_blob[blob_name],\n            data_arr,\n            device_option=self._device_option\n        )\n\n        op = core.CreateOperator(\n            \"SafeEnqueueBlobs\",\n            [queue, self._scratch_blob[blob_name]],\n            [self._scratch_blob[blob_name], self._scratch_status[blob_name]],\n            device_option=self._device_option\n        )\n        workspace.RunOperatorOnce(op)\n\n    def _create_caffe2_queues(self, net):\n        '''\n        Creates queues on caffe2 side\n        '''\n        def create_queue(queue_name, num_blobs, capacity):\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"CreateBlobsQueue\",\n                    [], [queue_name],\n                    num_blobs=1,\n                    capacity=capacity))\n            return core.ScopedBlobReference(queue_name)\n\n        for blob_name in self._input_blob_names:\n            qname = blob_name + \"_c2queue\" + \"_\" + self._input_source_name\n            q = create_queue(\n                qname, num_blobs=1, capacity=self._c2_queue_capacity\n            )\n            self._queues.append(q)\n\n    def _create_caffe2_ops(self, net):\n        '''\n        Creates dequeue-ops on caffe2 side\n        '''\n        for q, blob_name in zip(self._queues, self._input_blob_names):\n            # Add operator to the Caffe2 network to dequeue\n            net.DequeueBlobs(q, blob_name, timeout_secs=float(self._timeout))\n\n    def _log_inputs_per_interval(self, inputs, force=False):\n        self._inputs += inputs\n        current_seconds = time.time()\n        delta_seconds = current_seconds - self._prev_seconds\n        if delta_seconds >= LOG_INT_SECS or force:\n            inputs_per_sec = int(self._inputs / delta_seconds)\n            qsize = self._internal_queue.qsize()\n            log.info(\"{}/{}: {} inputs/sec\".format(\n                self._input_source_name,\n                self._namescope,\n                inputs_per_sec,\n            ))\n            log.info(\"-- queue: {} batches\".format(qsize))\n            # log and reset perf metrics\n            self._metrics.put_metric(\n                'inputs_per_sec', inputs_per_sec, False)\n            self._metrics.put_metric('queue_size', qsize, False)\n            self._metrics.put_metric(\n                'time_elapsed', delta_seconds, False)\n            self._metrics.log_metrics()\n            self._metrics.reset_metrics()\n            self._inputs = 0\n            self._prev_seconds = current_seconds\n\n\nclass GlobalCoordinator(GlobalWorkerCoordinator):\n    def __init__(self):\n        GlobalWorkerCoordinator.__init__(self)\n        self._queues = {}\n\n    def get_queue(self, queue_name, max_buffered_batches):\n        assert isinstance(max_buffered_batches, int)\n        if queue_name not in self._queues:\n            self._queues[queue_name] = Queue.Queue(maxsize=max_buffered_batches)\n        return self._queues[queue_name]\n\n    def reset_data_input(self, namescope, name, net, batch_size):\n        log.info(\"Reset data input {}, batch size {}: \".format(name, batch_size))\n        for c in self._coordinators:\n            if c._worker_name == name and c._state._namescope == namescope:\n                c._state._batch_size = batch_size\n                c._state._create_caffe2_ops(net)\n\n\nclass DataWorker(Worker):\n    def __init__(\n        self,\n        coordinator,\n        worker_id,\n        worker_fun,\n        metrics,\n        batch_size,\n        batch_feeder\n    ):\n        Worker.__init__(self, coordinator, worker_id, worker_fun=worker_fun,\n                        metrics=metrics)\n        self._batch_size = batch_size\n        self._batch_feeder = batch_feeder\n\n    def run(self):\n        input_data = self._worker_fun(self._worker_id, self._batch_size)\n\n        self._batch_feeder.put(input_data, self._coordinator)\n\n    def finish(self):\n        self._metrics.put_metric(\n            'fetcher_time', time.time() - self._start_time)\n\n\nglobal_coordinator = GlobalCoordinator()\n\n\ndef enqueuer(coordinator, batch_feeder):\n    while coordinator.is_active():\n        batch_feeder._enqueue_batch(coordinator)\n"
  },
  {
    "path": "caffe2/python/data_workers_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nimport unittest\nimport time\n\nfrom caffe2.python import workspace, model_helper\nfrom caffe2.python import timeout_guard\nimport caffe2.python.data_workers as data_workers\n\n\ndef dummy_fetcher(fetcher_id, batch_size):\n    # Create random amount of values\n    n = np.random.randint(64) + 1\n    data = np.zeros((n, 3))\n    labels = []\n    for j in range(n):\n        data[j, :] *= (j + fetcher_id)\n        labels.append(data[j, 0])\n\n    return [np.array(data), np.array(labels)]\n\n\ndef dummy_fetcher_rnn(fetcher_id, batch_size):\n    # Hardcoding some input blobs\n    T = 20\n    N = batch_size\n    D = 33\n    data = np.random.rand(T, N, D)\n    label = np.random.randint(N, size=(T, N))\n    seq_lengths = np.random.randint(N, size=(N))\n    return [data, label, seq_lengths]\n\n\nclass DataWorkersTest(unittest.TestCase):\n\n    def testNonParallelModel(self):\n        workspace.ResetWorkspace()\n\n        model = model_helper.ModelHelper(name=\"test\")\n        old_seq_id = data_workers.global_coordinator._fetcher_id_seq\n        coordinator = data_workers.init_data_input_workers(\n            model,\n            [\"data\", \"label\"],\n            dummy_fetcher,\n            32,\n            2,\n            input_source_name=\"unittest\"\n        )\n        new_seq_id = data_workers.global_coordinator._fetcher_id_seq\n        self.assertEqual(new_seq_id, old_seq_id + 2)\n\n        coordinator.start()\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.CreateNet(model.net)\n\n        for _i in range(500):\n            with timeout_guard.CompleteInTimeOrDie(5):\n                workspace.RunNet(model.net.Proto().name)\n\n            data = workspace.FetchBlob(\"data\")\n            labels = workspace.FetchBlob(\"label\")\n\n            self.assertEqual(data.shape[0], labels.shape[0])\n            self.assertEqual(data.shape[0], 32)\n\n            for j in range(32):\n                self.assertEqual(labels[j], data[j, 0])\n                self.assertEqual(labels[j], data[j, 1])\n                self.assertEqual(labels[j], data[j, 2])\n\n        coordinator.stop_coordinator(\"unittest\")\n        self.assertEqual(coordinator._coordinators, [])\n\n    def testRNNInput(self):\n        workspace.ResetWorkspace()\n        model = model_helper.ModelHelper(name=\"rnn_test\")\n        old_seq_id = data_workers.global_coordinator._fetcher_id_seq\n        coordinator = data_workers.init_data_input_workers(\n            model,\n            [\"data1\", \"label1\", \"seq_lengths1\"],\n            dummy_fetcher_rnn,\n            32,\n            2,\n            dont_rebatch=False,\n            batch_columns=[1, 1, 0],\n        )\n        new_seq_id = data_workers.global_coordinator._fetcher_id_seq\n        self.assertEqual(new_seq_id, old_seq_id + 2)\n\n        coordinator.start()\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.CreateNet(model.net)\n\n        while coordinator._coordinators[0]._state._inputs < 100:\n            time.sleep(0.01)\n\n        # Run a couple of rounds\n        workspace.RunNet(model.net.Proto().name)\n        workspace.RunNet(model.net.Proto().name)\n\n        # Wait for the enqueue thread to get blocked\n        time.sleep(0.2)\n\n        # We don't dequeue on caffe2 side (as we don't run the net)\n        # so the enqueue thread should be blocked.\n        # Let's now shutdown and see it succeeds.\n        self.assertTrue(coordinator.stop())\n\n    def testInputOrder(self):\n        #\n        # Create two models (train and validation) with same input blobs\n        # names and ensure that both will get the data in correct order\n        #\n        workspace.ResetWorkspace()\n        self.counters = {0: 0, 1: 1}\n\n        def dummy_fetcher_rnn_ordered1(fetcher_id, batch_size):\n            # Hardcoding some input blobs\n            T = 20\n            N = batch_size\n            D = 33\n            data = np.zeros((T, N, D))\n            data[0][0][0] = self.counters[fetcher_id]\n            label = np.random.randint(N, size=(T, N))\n            label[0][0] = self.counters[fetcher_id]\n            seq_lengths = np.random.randint(N, size=(N))\n            seq_lengths[0] = self.counters[fetcher_id]\n            self.counters[fetcher_id] += 1\n            return [data, label, seq_lengths]\n\n        workspace.ResetWorkspace()\n        model = model_helper.ModelHelper(name=\"rnn_test_order\")\n\n        coordinator = data_workers.init_data_input_workers(\n            model,\n            input_blob_names=[\"data2\", \"label2\", \"seq_lengths2\"],\n            fetch_fun=dummy_fetcher_rnn_ordered1,\n            batch_size=32,\n            max_buffered_batches=1000,\n            num_worker_threads=1,\n            dont_rebatch=True,\n            input_source_name='train'\n        )\n        coordinator.start()\n\n        val_model = model_helper.ModelHelper(name=\"rnn_test_order_val\")\n        coordinator1 = data_workers.init_data_input_workers(\n            val_model,\n            input_blob_names=[\"data2\", \"label2\", \"seq_lengths2\"],\n            fetch_fun=dummy_fetcher_rnn_ordered1,\n            batch_size=32,\n            max_buffered_batches=1000,\n            num_worker_threads=1,\n            dont_rebatch=True,\n            input_source_name='val'\n        )\n        coordinator1.start()\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.CreateNet(model.net)\n        workspace.CreateNet(val_model.net)\n\n        while coordinator._coordinators[0]._state._inputs < 900:\n            time.sleep(0.01)\n\n        with timeout_guard.CompleteInTimeOrDie(5):\n            for m in (model, val_model):\n                print(m.net.Proto().name)\n                workspace.RunNet(m.net.Proto().name)\n                last_data = workspace.FetchBlob('data2')[0][0][0]\n                last_lab = workspace.FetchBlob('label2')[0][0]\n                last_seq = workspace.FetchBlob('seq_lengths2')[0]\n\n                # Run few rounds\n                for _i in range(10):\n                    workspace.RunNet(m.net.Proto().name)\n                    data = workspace.FetchBlob('data2')[0][0][0]\n                    lab = workspace.FetchBlob('label2')[0][0]\n                    seq = workspace.FetchBlob('seq_lengths2')[0]\n                    self.assertEqual(data, last_data + 1)\n                    self.assertEqual(lab, last_lab + 1)\n                    self.assertEqual(seq, last_seq + 1)\n                    last_data = data\n                    last_lab = lab\n                    last_seq = seq\n\n            time.sleep(0.2)\n\n            self.assertTrue(coordinator.stop())\n"
  },
  {
    "path": "caffe2/python/dataio.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package dataio\n# Module caffe2.python.dataio\n\"\"\"\nDefines the base interface for reading and writing operations.\n\nReaders/Writers are objects that produce operations that read/write sequences\nof data. Each operation reads or writes a list of BlobReferences.\n\nReaders and Writers must be implemented such that read and write operations\nare atomic and thread safe.\n\nExamples of possible Readers and Writers:\n    QueueReader, QueueWriter,\n    DatasetReader, DatasetWriter,\n\nSee `dataset.py` for an example of implementation.\n\"\"\"\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom caffe2.python.schema import Field, Struct, from_blob_list\nimport numpy as np\n\n\nclass Reader(object):\n    \"\"\"\n    Reader is an abstract class to be implemented in order to provide\n    operations capable of iterating through a dataset or stream of data.\n\n    A Reader must implement at least one operation, `read`, which\n    adds operations to a net that read the next batch of data. Readers can\n    optionally support the `reset` operation, which is useful when multiple\n    passes over the data are required.\n    \"\"\"\n    def __init__(self, schema=None):\n        if schema is not None:\n            assert isinstance(schema, Field)\n        self._schema = schema\n\n    def schema(self):\n        assert self._schema is not None, 'Schema not provided for this reader.'\n        return self._schema\n\n    def _set_schema(self, schema):\n        self._schema = schema\n\n    def setup_ex(self, init_net, finish_net):\n        \"\"\"Setup nets to run at task initialization and cleanup time.\n\n        Args:\n            global_init_net: A net invoked at task init time.\n            global_finish_net: A net invoked at task cleanup time.\n        \"\"\"\n        pass\n\n    def read_ex(self, local_init_net, local_finish_net):\n        read_net = core.Net('reader_body')\n        return ([read_net], ) + self.read(read_net)\n\n    def read_record_ex(self, local_init_net, local_finish_net):\n        nets, should_stop, fields = self.read_ex(\n            local_init_net, local_finish_net)\n        if self._schema:\n            fields = from_blob_list(self._schema, fields)\n        return nets, should_stop, fields\n\n    def read(self, read_net):\n        \"\"\"Append operations to read_net that will read a batch from the\n        underlying data soruce.\n\n        Operations added to `read_net` must be thread safe and atomic, that is,\n        it should be possible to clone `read_net` and run multiple instances of\n        it in parallel.\n\n        Args:\n            read_net: the net that will be appended with read operations\n\n        Returns:\n            A tuple (should_stop, fields), with:\n                should_stop: BlobReference pointing to a boolean scalar\n                    blob that indicates whether the read operation\n                    was succesfull or whether the end of data has\n                    been reached.\n                fields: A tuple of BlobReference containing the latest batch\n                    of data that was read.\n        \"\"\"\n        raise NotImplementedError('Readers must implement `read`.')\n\n    def reset(self, net):\n        \"\"\"Append operations to `net` that will reset the reader.\n\n        This can be used to read the data multiple times.\n        Not all readers support this operation.\n        \"\"\"\n        raise NotImplementedError('This reader cannot be resetted.')\n\n    def read_record(self, read_net):\n        should_stop, fields = self.read(read_net)\n        if self._schema:\n            fields = from_blob_list(self._schema, fields)\n        return should_stop, fields\n\n    def execution_step(self, reader_net_name=None, external_should_stop=None):\n        \"\"\"Create an execution step with a net containing read operators.\n\n        The execution step will contain a `stop_blob` that knows how to stop\n        the execution loop when end of data was reached.\n\n        E.g.:\n\n            read_step, fields = reader.execution_step()\n            consume_net = core.Net('consume')\n            consume_net.Print(fields[0], [])\n            p = core.Plan('reader')\n            p.AddStep(read_step.AddNet(consume_net))\n            core.RunPlan(p)\n\n        Args:\n            reader_net_name: (optional) the name of the reader_net to be\n                             created. The execution step will\n                             be named accordingly.\n\n        Returns:\n            A tuple (read_step, fields), with:\n                read_step: A newly created execution step containing a net with\n                           read operations. The step will have `stop_blob` set,\n                           in order to stop the loop on end of data.\n                fields: A tuple of BlobReference containing the latest batch\n                        of data that was read.\n        \"\"\"\n        reader_net = core.Net(reader_net_name or 'reader')\n        should_stop, fields = self.read_record(reader_net)\n        if external_should_stop is not None:\n            should_stop = reader_net.Or([external_should_stop, should_stop])\n        read_step = core.execution_step(\n            '{}_step'.format(reader_net_name),\n            reader_net,\n            should_stop_blob=should_stop)\n        return (read_step, fields)\n\n\nclass Writer(object):\n    \"\"\"\n    Writer is an abstract class to be implemented in order to provide\n    operations capable of feeding a data stream or a dataset.\n\n    A Writer must implement 2 operations:\n    `write`, which adds operations to a net that write the write batch of\n    data, and `commit`, which adds operations to a net in order to indicate\n    that no more data will be written.\n    \"\"\"\n    _schema = None\n\n    def schema(self):\n        return self._schema\n\n    def write(self, writer_net, fields):\n        \"\"\"Add operations to `writer_net` that write the next batch of data.\n\n        Operations added to the net must be thread-safe and unique, that is:\n        multiple writers must be able to write to the dataset in parallel.\n\n        Args:\n            fields: a tuple of BlobReference containing the batch of data to\n                    write.\n        \"\"\"\n        raise NotImplementedError('Writers must implement write.')\n\n    def write_record(self, writer_net, fields):\n        if isinstance(fields, Field):\n            self._schema = fields\n            fields = fields.field_blobs()\n        self.write(writer_net, fields)\n\n    def setup_ex(self, init_net, finish_net):\n        \"\"\"Experimental, don't use yet\"\"\"\n        self.commit(finish_net)\n\n    def write_ex(self, fields, local_init_net, local_finish_net, stop_blob):\n        \"\"\"Experimental extension to the interface. Don't use yet\"\"\"\n        write_net = core.Net('write_net')\n        self.write(write_net, fields)\n        return [write_net]\n\n    def write_record_ex(\n            self, fields, local_init_net, local_finish_net, stop_blob=None):\n        \"\"\"Experimental extension to the interface. Don't use yet.\"\"\"\n        if isinstance(fields, Field):\n            self._schema = fields\n            fields = fields.field_blobs()\n        if stop_blob is None:\n            stop_blob = local_init_net.NextName(\"dequeue_status\")\n        write_nets = self.write_ex(\n            fields, local_init_net, local_finish_net, stop_blob)\n        return (write_nets, stop_blob)\n\n    def commit(self, finish_net):\n        \"\"\"Add operations to `finish_net` that signal end of data.\n\n        This must be implemented by all Writers, but may be no-op for some\n        of them.\n        \"\"\"\n        pass\n\n\nclass ReaderBuilder(object):\n    \"\"\" Allow usage of a reader in distributed fashion. \"\"\"\n    def schema(self):\n        raise NotImplementedError()\n\n    def splits(self, net):\n        raise NotImplementedError()\n\n    def new_reader(self, split_reader=None, **kwargs):\n        raise NotImplementedError()\n\n\nclass PipedReaderBuilder(ReaderBuilder):\n    \"\"\"ReaderBuilder that modifies underlying builder by calling `piper`\n    function on each new reader produced, and return the result of\n    the function. This way, it is possible to append data processing\n    pipelines that will be replicated for each reader that gets created.\n\n    E.g.:\n\n    PipedReaderBuilder(\n        ReaderBuilder(...),\n        lambda reader: pipe(reader, processor=my_proc))\n    \"\"\"\n\n    def __init__(self, builder, piper):\n        self._builder = builder\n        self._piper = piper\n\n    def schema(self):\n        return self._builder.schema()\n\n    def splits(self, net):\n        return self._builder.splits(net)\n\n    def new_reader(self, split_reader=None, init_group=None, pipe_group=None,\n                   **kwargs):\n        output = self._piper(\n            self._builder.new_reader(\n                split_reader,\n                init_group=init_group,\n                pipe_group=pipe_group\n            ),\n            **kwargs\n        )\n        return output if isinstance(output, Reader) else output.reader()\n\n\nclass Pipe(object):\n    def __init__(self, schema=None, obj_key=None):\n        self._num_writers = 0\n        self._num_readers = 0\n        self._schema = schema\n        self._obj_key = obj_key\n\n    def schema(self):\n        return self._schema\n\n    def setup(self, global_init_net):\n        pass\n\n    def reader(self):\n        raise NotImplementedError()\n\n    def writer(self):\n        raise NotImplementedError()\n\n    def num_readers(self):\n        return self._num_readers\n\n    def num_writers(self):\n        return self._num_writers\n\n    def _new_writer(self, writer_schema, writer_init_net):\n        if writer_schema is not None and self._schema is None:\n            self._schema = writer_schema\n        self._num_writers += 1\n        if self._obj_key is not None:\n            writer_init_net.add_attribute(self._obj_key, self)\n\n    def _new_reader(self, reader_init_net):\n        self._num_readers += 1\n        if self._obj_key is not None:\n            reader_init_net.add_attribute(self._obj_key, self)\n\n\nclass CounterReader(Reader):\n    \"\"\" Reader that produces increasing integers. \"\"\"\n    def __init__(self):\n        Reader.__init__(self, schema=Struct(('iter', np.int64)))\n        self.counter = None\n        self.should_stop = None\n\n    def setup_ex(self, global_init_net, global_finish_net):\n        if self.counter is None:\n            self.counter = global_init_net.CreateCounter([], init_count=0)\n            self.should_stop = global_init_net.ConstantFill(\n                [], shape=[], dtype=core.DataType.BOOL, value=False)\n\n    def read_ex(self, local_init_net, local_finish_net):\n        count_net = core.Net('limited_reader_counter')\n        value = count_net.CountUp([self.counter], 1)\n        return [count_net], self.should_stop, [value]\n\n\nclass ReaderWithLimitBase(Reader):\n    \"\"\"Abstract Reader constrained by certain conditions.\n\n    Base class for Reader classes which check for certain conditions to stop\n    further processing (e.g. max number of iterations or time limit).\n    Also produces a boolean blob (data_finished) that can be used to see if\n    the reader exausted all input data (true) or stopped for another reason\n    (false).\n    \"\"\"\n\n    def __init__(self, reader):\n        Reader.__init__(self, schema=reader._schema)\n        self.reader = reader\n        self.net = core.Net('reader_with_limit')\n        self._data_finished = self.net.AddExternalInput(\n            self.net.NextName('data_finished'))\n        self.should_stop = None\n\n    def setup_ex(self, global_init_net, global_finish_net):\n        global_init_net.ConstantFill(\n            [], [self._data_finished],\n            shape=[], value=False, dtype=core.DataType.BOOL)\n        self.reader.setup_ex(global_init_net, global_finish_net)\n        self.setup_limiter(global_init_net, global_finish_net)\n\n    def read_ex(self, local_init_net, local_finish_net):\n        \"\"\"Reads from an underlying Reader class, but may stop due to additional\n        constraints.\n\n        Build and return network(s) to read data from a Reader with\n        additional constraints, depending on which derived class is used.\n        Derived classes implement setup_limited and check_limiter_condition\n        which determine the nature of the constraint imposed on the reader,\n        e.g. iteration limits or time limit.\n\n        Args:\n            local_init_net: A net invoked at task instance init time (Once per\n                parallel thread).\n            local_finish_net: A net invoked at task instance cleanup time (Once\n                per parallel thread).\n        \"\"\"\n\n        # Check if limiting constraint is met.\n        stop_condition_net = core.Net('limited_reader_condition')\n        should_stop = self.check_limiter_condition(stop_condition_net)\n\n        # Call original reader.\n        nets, local_data_finished, fields = self.reader.read_ex(\n            local_init_net, local_finish_net)\n        self._set_schema(self.reader._schema)\n\n        # Check if original reader is done.\n        check_done_net = core.Net('limited_reader_post')\n        # Copy to the same blob as the counter output to trigger reader\n        # stopping - this is ok because execution will check should_stop_blob\n        # after every single operation, so it has already been checked on this\n        # iteration by this point.\n        check_done_net.Copy(local_data_finished, should_stop)\n        # Update externally-accessible flag indicating if reader is done\n        check_done_net.Or([self._data_finished, local_data_finished],\n                          [self._data_finished])\n\n        return [stop_condition_net] + nets + [check_done_net], should_stop, fields\n\n    def setup_limiter(self, global_init_net, global_finish_net):\n        \"\"\"Configure task level init/cleanup nets required to implement limit\n        condition. Must be implemented by subclass.\n\n        Args:\n            global_init_net: A net invoked at task init time.\n            global_finish_net: A net invoked at task cleanup time.\n        \"\"\"\n        raise NotImplementedError(\"Subclass must implement `setup_limiter`\")\n\n    def check_limiter_condition(self, stop_condition_net):\n        \"\"\"Configure a net that is invoked between reading batches to see if\n        limit condition is met. Must be implemented by subclass.\n\n        Args:\n            stop_condition_net: A net invoked to evaluate an early termination\n                condition.\n        \"\"\"\n        raise NotImplementedError(\"Subclass must implement `check_limiter_condition\")\n\n    def data_finished(self):\n        \"\"\"\n        Return a blob that can be checked after the end of the reading task,\n        which will contain a scalar float indicating whether the underlying\n        reader has been exhausted (True) or whether we stopped because reached\n        the limit of iterations (False).\n        \"\"\"\n        return self._data_finished\n\n\nclass ReaderWithLimit(ReaderWithLimitBase):\n    \"\"\"Reader that stops after `num_iter` batches.\n\n    If `num_iter` <= 0 or is None, reverts to an unconstrained reader that\n    exports a boolean blob indicating that the reader has exhausted\n    the data steam.\n    \"\"\"\n    def __init__(self, reader, num_iter=1):\n        \"\"\"Class initializer.\n\n        Args:\n            reader: The underlying reader object doing the actual read.\n            num_iter: Number of batches to read. If `None`,\n                the class reverts to a normal reader except that it also\n                produces a data_finished blob as a side effect to indicate\n                whether the input stream is exhausted.\n        \"\"\"\n        super(ReaderWithLimit, self).__init__(reader)\n        self.counter = None\n        self.num_iter = num_iter\n        if self.num_iter is not None:\n            self.counter = self.net.AddExternalInput(\n                self.net.NextName('counter'))\n\n    def setup_limiter(self, global_init_net, global_finish_net):\n        if self.counter:\n            global_init_net.CreateCounter(\n                [], [self.counter], init_count=int(self.num_iter))\n\n    def check_limiter_condition(self, stop_condition_net):\n        if self.counter:\n            return stop_condition_net.CountDown([self.counter], 1)\n        else:\n            return stop_condition_net.ConstantFill(\n                [], 1,\n                shape=[], value=False, dtype=core.DataType.BOOL)\n\n\ndef CountUntil(num_iter):\n    return ReaderWithLimit(CounterReader(), num_iter)\n\n\nclass ReaderWithTimeLimit(ReaderWithLimitBase):\n    \"\"\"Reader that stops after `duration` seconds.\n\n    If `duration` <= 0 or is None, reverts to an unconstrained reader that\n    exports a boolean blob indicating that the reader has exhausted\n    the data steam.\n    \"\"\"\n    def __init__(self, reader, duration=0):\n        \"\"\"Class initializer.\n\n        Args:\n            reader: The underlying reader object doing the actual read.\n            duration: Number of seconds to read. If un-specified, None, or <= 0,\n                the class reverts to a normal reader except that it also\n                produces a data_finished blob as a side effect to indicate\n                whether the input stream is exhausted.\n        \"\"\"\n        super(ReaderWithTimeLimit, self).__init__(reader)\n\n        self.timer = None\n        self.duration = duration\n        self.duration_ns_blob = None\n\n    def setup_limiter(self, global_init_net, global_finish_net):\n        if self.duration is not None and self.duration > 0:\n            duration_ns = int(self.duration * (10**9))\n\n            self.timer = global_init_net.TimerBegin(\n                [], counter_name='epoch_timer')\n            start_time = global_init_net.TimerGet(self.timer)\n            self.duration_ns_blob = global_init_net.ConstantFill(\n                [start_time], value=duration_ns)\n\n            global_finish_net.TimerEnd([self.timer], [])\n\n    def check_limiter_condition(self, stop_condition_net):\n        if self.duration:\n            time_elapsed = stop_condition_net.TimerGet(self.timer)\n            return stop_condition_net.GE(\n                [time_elapsed, self.duration_ns_blob], str(self.should_stop))\n        else:\n            return stop_condition_net.ConstantFill(\n                [], 1, shape=[], value=False, dtype=core.DataType.BOOL\n            )\n"
  },
  {
    "path": "caffe2/python/dataio_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python.dataio import Reader, ReaderWithLimit, ReaderWithTimeLimit\nfrom caffe2.python.dataset import Dataset\nfrom caffe2.python.pipeline import pipe\nfrom caffe2.python.schema import Struct, NewRecord, FeedRecord\nfrom caffe2.python.session import LocalSession\nfrom caffe2.python.task import TaskGroup, final_output, WorkspaceType\nfrom caffe2.python.test_util import TestCase\nfrom caffe2.python.cached_reader import CachedReader\nfrom caffe2.python import core, workspace\nfrom caffe2.python.net_builder import ops\n\nimport numpy as np\nimport os\nimport shutil\nimport tempfile\nimport time\n\n\ndef init_dataset(ws, size=100):\n    src_init = core.Net('src_init')\n    with core.NameScope('src'):\n        src_values = Struct(('label', np.array(range(size))))\n        src_blobs = NewRecord(src_init, src_values)\n        src_ds = Dataset(src_blobs)\n        FeedRecord(src_blobs, src_values, ws)\n    ws.run(src_init)\n    return src_ds\n\n\ndef read_all_data(ws, reader, session):\n    dst_init = core.Net('dst_init')\n    with core.NameScope('dst'):\n        dst_ds = Dataset(reader.schema().clone_schema())\n        dst_ds.init_empty(dst_init)\n    session.run(dst_init)\n\n    with TaskGroup(workspace_type=WorkspaceType.GLOBAL) as tg:\n        pipe(reader, dst_ds.writer(), num_runtime_threads=8)\n    session.run(tg)\n\n    return ws.blobs[str(dst_ds.content().label())].fetch()\n\n\nclass ReaderWithDelay(Reader):\n    \"\"\"Test reader class that inserts a delay between reading batches.\"\"\"\n    def __init__(self, reader, delay):\n        Reader.__init__(self, schema=reader._schema)\n        self.reader = reader\n        self.delay = delay\n\n    def setup_ex(self, global_init_net, global_finish_net):\n        self.reader.setup_ex(global_init_net, global_finish_net)\n\n    def read_ex(self, local_init_net, local_finish_net):\n        read_net = core.Net('reader_body')\n\n        def sleep_op(*args, **argd):\n            time.sleep(self.delay)\n\n        read_net.Python(sleep_op)([], [])\n        return ([read_net], ) + self.reader.read(read_net)\n\n\nclass TestReaderWithLimit(TestCase):\n    def test_runtime_threads(self):\n        ws = workspace.C.Workspace()\n        session = LocalSession(ws)\n        src_ds = init_dataset(ws)\n        totals = [None] * 3\n\n        def proc(rec):\n            # executed once\n            with ops.task_init():\n                counter1 = ops.CreateCounter([], ['global_counter'])\n                counter2 = ops.CreateCounter([], ['global_counter2'])\n                counter3 = ops.CreateCounter([], ['global_counter3'])\n            # executed once per thread\n            with ops.task_instance_init():\n                task_counter = ops.CreateCounter([], ['task_counter'])\n            # executed on each iteration\n            ops.CountUp(counter1)\n            ops.CountUp(task_counter)\n            # executed once per thread\n            with ops.task_instance_exit():\n                with ops.loop(ops.RetrieveCount(task_counter)):\n                    ops.CountUp(counter2)\n                ops.CountUp(counter3)\n            # executed once\n            with ops.task_exit():\n                totals[0] = final_output(ops.RetrieveCount(counter1))\n                totals[1] = final_output(ops.RetrieveCount(counter2))\n                totals[2] = final_output(ops.RetrieveCount(counter3))\n            return rec\n\n        # Read full data set from original reader\n        with TaskGroup() as tg:\n            pipe(src_ds.reader(), num_runtime_threads=8, processor=proc)\n        session.run(tg)\n        self.assertEqual(totals[0].fetch(), 100)\n        self.assertEqual(totals[1].fetch(), 100)\n        self.assertEqual(totals[2].fetch(), 8)\n\n        # Read with a count-limited reader\n        with TaskGroup() as tg:\n            q1 = pipe(src_ds.reader(), num_runtime_threads=2)\n            q2 = pipe(\n                ReaderWithLimit(q1.reader(), num_iter=25),\n                num_runtime_threads=3)\n            pipe(q2, processor=proc, num_runtime_threads=6)\n        session.run(tg)\n        self.assertEqual(totals[0].fetch(), 25)\n        self.assertEqual(totals[1].fetch(), 25)\n        self.assertEqual(totals[2].fetch(), 6)\n\n    def _test_limit_reader_init_shared(self, size):\n        ws = workspace.C.Workspace()\n        session = LocalSession(ws)\n\n        # Build test dataset\n        src_ds = init_dataset(ws, size=size)\n\n        # Create an identically sized empty destnation dataset\n        dst_init = core.Net('dst_init')\n        with core.NameScope('dst'):\n            dst_ds = Dataset(src_ds.content().clone_schema())\n            dst_ds.init_empty(dst_init)\n        ws.run(dst_init)\n\n        return ws, session, src_ds, dst_init, dst_ds\n\n    def _test_limit_reader_shared(self, reader_class, size, expected_read_len,\n                                  expected_finish, num_threads, read_delay,\n                                  **limiter_args):\n        ws, session, src_ds, dst_init, dst_ds = \\\n            self._test_limit_reader_init_shared(size)\n\n        # Read without limiter\n        # WorkspaceType.GLOBAL is required because we are fetching\n        # reader.data_finished() after the TaskGroup finishes.\n        with TaskGroup(workspace_type=WorkspaceType.GLOBAL) as tg:\n            if read_delay > 0:\n                reader = reader_class(ReaderWithDelay(src_ds.reader(),\n                                                      read_delay),\n                                      **limiter_args)\n            else:\n                reader = reader_class(src_ds.reader(), **limiter_args)\n            pipe(reader, dst_ds.writer(), num_runtime_threads=num_threads)\n        session.run(tg)\n        read_len = len(sorted(ws.blobs[str(dst_ds.content().label())].fetch()))\n        self.assertEqual(read_len, expected_read_len)\n        self.assertEqual(\n            sorted(ws.blobs[str(dst_ds.content().label())].fetch()),\n            list(range(expected_read_len))\n        )\n        self.assertEqual(ws.blobs[str(reader.data_finished())].fetch(),\n                         expected_finish)\n\n    def test_count_limit_reader_without_limit(self):\n        # No iter count specified, should read all records.\n        self._test_limit_reader_shared(ReaderWithLimit,\n                                       size=100,\n                                       expected_read_len=100,\n                                       expected_finish=True,\n                                       num_threads=8,\n                                       read_delay=0,\n                                       num_iter=None)\n\n    def test_count_limit_reader_with_zero_limit(self):\n        # Zero iter count specified, should read 0 records.\n        self._test_limit_reader_shared(ReaderWithLimit,\n                                       size=100,\n                                       expected_read_len=0,\n                                       expected_finish=False,\n                                       num_threads=8,\n                                       read_delay=0,\n                                       num_iter=0)\n\n    def test_count_limit_reader_with_low_limit(self):\n        # Read with limit smaller than size of dataset\n        self._test_limit_reader_shared(ReaderWithLimit,\n                                       size=100,\n                                       expected_read_len=10,\n                                       expected_finish=False,\n                                       num_threads=8,\n                                       read_delay=0,\n                                       num_iter=10)\n\n    def test_count_limit_reader_with_high_limit(self):\n        # Read with limit larger than size of dataset\n        self._test_limit_reader_shared(ReaderWithLimit,\n                                       size=100,\n                                       expected_read_len=100,\n                                       expected_finish=True,\n                                       num_threads=8,\n                                       read_delay=0,\n                                       num_iter=110)\n\n    def test_time_limit_reader_without_limit(self):\n        # No duration specified, should read all records.\n        self._test_limit_reader_shared(ReaderWithTimeLimit,\n                                       size=100,\n                                       expected_read_len=100,\n                                       expected_finish=True,\n                                       num_threads=8,\n                                       read_delay=0.1,\n                                       duration=0)\n\n    def test_time_limit_reader_with_short_limit(self):\n        # Read with insufficient time limit\n        size = 50\n        num_threads = 4\n        sleep_duration = 0.25\n        duration = 1\n        expected_read_len = int(round(num_threads * duration / sleep_duration))\n        # Because the time limit check happens before the delay + read op,\n        # subtract a little bit of time to ensure we don't get in an extra read\n        duration = duration - 0.25 * sleep_duration\n        self._test_limit_reader_shared(ReaderWithTimeLimit,\n                                       size=size,\n                                       expected_read_len=expected_read_len,\n                                       expected_finish=False,\n                                       num_threads=num_threads,\n                                       read_delay=sleep_duration,\n                                       duration=duration)\n\n    def test_time_limit_reader_with_long_limit(self):\n        # Read with ample time limit\n        self._test_limit_reader_shared(ReaderWithTimeLimit,\n                                       size=50,\n                                       expected_read_len=50,\n                                       expected_finish=True,\n                                       num_threads=4,\n                                       read_delay=0.25,\n                                       duration=6)\n\n    def test_cached_reader(self):\n        ws = workspace.C.Workspace()\n        session = LocalSession(ws)\n\n        def build_source_reader(size):\n            src_ds = init_dataset(ws, size)\n            return src_ds.reader()\n\n        with tempfile.NamedTemporaryFile(delete=False) as f:\n            path = f.name\n            f.close()\n            os.remove(path)\n\n            # Read data for the first time.\n            cached_reader1 = CachedReader(build_source_reader(100))\n            init_step = cached_reader1.build_cache(path)\n            session.run(init_step)\n\n            data = read_all_data(ws, cached_reader1, session)\n            self.assertEqual(sorted(data), list(range(100)))\n\n            # Read data from cache.\n            workspace.ResetWorkspace()\n            cached_reader2 = CachedReader(build_source_reader(200))\n            init_step = cached_reader2.build_cache(path)\n            session.run(init_step)\n\n            data = read_all_data(ws, cached_reader2, session)\n            self.assertEqual(sorted(data), list(range(100)))\n\n            shutil.rmtree(path)\n\n            # We removed cache so we expect to receive data from original reader\n            workspace.ResetWorkspace()\n            cached_reader3 = CachedReader(build_source_reader(300))\n            init_step = cached_reader3.build_cache(path)\n            session.run(init_step)\n\n            data = read_all_data(ws, cached_reader3, session)\n            self.assertEqual(sorted(data), list(range(300)))\n\n            shutil.rmtree(path)\n"
  },
  {
    "path": "caffe2/python/dataset.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package dataset\n# Module caffe2.python.dataset\n\"\"\"\nImplementation of an in-memory dataset with structured schema.\n\nUse this to store and iterate through datasets with complex schema that\nfit in memory.\n\nIterating through entries of this dataset is very fast since the dataset\nis stored as a set of native Caffe2 tensors, thus no type conversion or\ndeserialization is necessary.\n\"\"\"\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nfrom caffe2.python.dataio import Reader, Writer\nfrom caffe2.python.schema import (\n    Struct, from_blob_list, from_column_list, InitEmptyRecord)\nimport numpy as np\n\n\nclass _DatasetReader(Reader):\n    def __init__(self, dataset, name, batch_size=1, enforce_batch_size=False):\n        \"\"\"Don't call this directly. Instead, use dataset.reader()\"\"\"\n        Reader.__init__(self, dataset.content())\n        self.dataset = dataset\n        self.name = name or (dataset.name + '_cursor')\n        self.batch_size = batch_size\n        self.enforce_batch_size = enforce_batch_size\n        self.cursor = None\n\n    def setup_ex(self, init_net, exit_net):\n        if self.cursor is None:\n            self.cursor = init_net.CreateTreeCursor(\n                [],\n                init_net.NextScopedBlob(self.name),\n                fields=self.dataset.fields)\n\n    def read(self, read_net):\n        assert self.cursor, 'setup not called.'\n        content = self.dataset.content()\n        with core.NameScope(read_net.NextName(self.name)):\n            fields = read_net.ReadNextBatch(\n                [self.cursor] + content.field_blobs(),\n                content.field_names(),\n                batch_size=self.batch_size,\n                enforce_batch_size=self.enforce_batch_size)\n            if type(fields) is core.BlobReference:\n                fields = [fields]\n            return (read_net.IsEmpty([fields[0]]), fields)\n\n    def reset(self, net):\n        net.ResetCursor([self.cursor], [])\n\n\nclass _DatasetRandomReader(Reader):\n    def __init__(self, dataset, name, indices, batch_size=1, loop_over=False,\n                 enforce_batch_size=False):\n        \"\"\"Don't call this directly. Instead, use dataset.random_reader()\"\"\"\n        Reader.__init__(self, dataset.content())\n        self.dataset = dataset\n        self.cursor = None\n        self.name = name or (dataset.name + '_cursor')\n        self.indices = indices\n        self.batch_size = batch_size\n        self.loop_over = loop_over\n        self.enforce_batch_size = enforce_batch_size\n\n    def setup_ex(self, init_net, exit_net):\n        if self.cursor is None:\n            self.cursor = init_net.CreateTreeCursor(\n                [],\n                [self.name],\n                fields=self.dataset.fields)\n\n    def reset(self, net):\n        net.ResetCursor([self.cursor], [])\n\n    def computeoffset(self, net):\n        self.reset(net)\n        offsets = net.ComputeOffset(\n            [self.cursor] + self.dataset.content().field_blobs(),\n            'offsets')\n        self.offsets = offsets\n\n    def sort_and_shuffle(self, net, sort_by_field=None,\n                         shuffle_size=1, batch_size=1):\n        # no sorting by default\n        content = self.dataset.content()\n        sort_by_field_idx = -1\n        if sort_by_field:\n            assert sort_by_field in content.field_names(), (\n                'Must be valid field.')\n            sort_by_field_idx = content.field_names().index(sort_by_field)\n        self.reset(net)\n\n        indices = net.SortAndShuffle(\n            [self.cursor] + content.field_blobs(),\n            'indices',\n            sort_by_field_idx=sort_by_field_idx,\n            shuffle_size=shuffle_size,\n            batch_size=batch_size)\n        self.indices = indices\n\n    def read(self, read_net):\n        with core.NameScope(read_net.NextName(self.name)):\n            fields = read_net.ReadRandomBatch(\n                [self.cursor, self.indices, self.offsets] + (\n                    self.dataset.content().field_blobs()),\n                self.dataset.content().field_names(),\n                batch_size=self.batch_size,\n                enforce_batch_size=self.enforce_batch_size,\n                loop_over=self.loop_over)\n            return (read_net.IsEmpty([fields[0]]), fields)\n\n\nclass _DatasetWriter(Writer):\n    def __init__(self, content):\n        \"\"\"Don't call this directly. Use dataset.writer() instead.\"\"\"\n        self._content = content\n        self.mutex = None\n\n    def setup_ex(self, init_net, exit_net):\n        if self.mutex is None:\n            self.mutex = init_net.CreateMutex([])\n\n    def write(self, writer_net, fields):\n        \"\"\"\n        Add operations to `net` that append the blobs in `fields` to the end\n        of the dataset. An additional operator will also be added that checks\n        the consistency of the data in `fields` against the dataset schema.\n\n        Args:\n            writer_net: The net that will contain the Append operators.\n            fields: A list of BlobReference to be appeneded to this dataset.\n        \"\"\"\n        assert self.mutex is not None, 'setup not called.'\n        field_blobs = self._content.field_blobs()\n        assert len(fields) == len(field_blobs), (\n            'Expected %s fields, got %s.' % (len(field_blobs), len(fields)))\n        writer_net.CheckDatasetConsistency(\n            fields, [], fields=self._content.field_names())\n        writer_net.AtomicAppend(\n            [self.mutex] + field_blobs + list(fields),\n            field_blobs)\n\n    def commit(self, finish_net):\n        \"\"\"Commit is a no-op for an in-memory dataset.\"\"\"\n        pass\n\n\ndef Const(net, value, dtype=None, name=None):\n    \"\"\"\n    Create a 'constant' by first creating an external input in the given\n    net, and then feeding the corresponding blob with its provided value\n    in the current workspace. The name is automatically generated in order\n    to avoid clashes with existing blob names.\n    \"\"\"\n    assert isinstance(net, core.Net), 'net must be a core.Net instance.'\n    value = np.array(value, dtype=dtype)\n    blob = net.AddExternalInput(net.NextName(prefix=name))\n    workspace.FeedBlob(str(blob), value)\n    return blob\n\n\ndef execution_step_with_progress(name, init_net, substeps, rows_read):\n    # progress reporter\n    report_net = core.Net('report_net')\n    report_net.Print([rows_read], [])\n    return core.execution_step(\n        name,\n        substeps,\n        report_net=report_net,\n        concurrent_substeps=True,\n        report_interval=5)\n\n\nclass Dataset(object):\n    \"\"\"Represents an in-memory dataset with fixed schema.\n\n    Use this to store and iterate through datasets with complex schema that\n    fit in memory.\n\n    Iterating through entries of this dataset is very fast since the dataset\n    is stored as a set of native Caffe2 tensors, thus no type conversion or\n    deserialization is necessary.\n    \"\"\"\n\n    def __init__(self, fields, name=None):\n        \"\"\"Create an un-initialized dataset with schema provided by `fields`.\n\n        Before this dataset can be used, it must be initialized, either by\n        `init_empty` or `init_from_dataframe`.\n\n        Args:\n            fields: either a schema.Struct or a list of field names in a format\n                    compatible with the one described in schema.py.\n            name: optional name to prepend to blobs that will store the data.\n        \"\"\"\n        assert isinstance(fields, list) or isinstance(fields, Struct), (\n            'fields must be either a Struct or a list of raw field names.')\n        if isinstance(fields, list):\n            fields = from_column_list(fields)\n        self.schema = fields\n        self.fields = fields.field_names()\n        self.field_types = fields.field_types()\n        self.name = name or 'dataset'\n        self.field_blobs = fields.field_blobs() if fields.has_blobs() else None\n\n    def trim(self, net, multiple_of):\n        \"\"\"\n        Trims the contents of this dataset so that the number of records is\n        multiple of the given argument.\n        \"\"\"\n        net.TrimDataset(\n            self.field_blobs,\n            self.field_blobs,\n            fields=self.fields,\n            multiple_of=multiple_of)\n\n    def init_empty(self, init_net):\n        \"\"\"Initialize the blobs for this dataset with empty values.\n\n        Empty arrays will be immediately fed into the current workspace,\n        and `init_net` will take those blobs as external inputs.\n        \"\"\"\n        self.field_blobs = InitEmptyRecord(\n            init_net, self.schema.clone_schema()).field_blobs()\n\n    def init_from_dataframe(self, net, dataframe):\n        \"\"\"Initialize the blobs for this dataset from a Pandas dataframe.\n\n        Each column of the dataframe will be immediately fed into the current\n        workspace, and the `net` will take this blobs as external inputs.\n        \"\"\"\n        assert len(self.fields) == len(dataframe.columns)\n        self.field_blobs = [\n            Const(net, dataframe.as_matrix([col]).flatten(), name=field)\n            for col, field in enumerate(self.fields)]\n\n    def get_blobs(self):\n        \"\"\"\n        Return the list of BlobReference pointing to the blobs that contain\n        the data for this dataset.\n        \"\"\"\n        assert self\n        return self.field_blobs\n\n    def content(self):\n        \"\"\"\n        Return a Record of BlobReferences pointing to the full content of\n        this dataset.\n        \"\"\"\n        return from_blob_list(self.schema, self.field_blobs)\n\n    def field_names(self):\n        \"\"\"Return the list of field names for this dataset.\"\"\"\n        return self.fields\n\n    def field_types(self):\n        \"\"\"\n        Return the list of field dtypes for this dataset.\n\n        If a list of strings, not a schema.Struct, was passed to the\n        constructor, this will return a list of dtype(np.void).\n        \"\"\"\n        return self.field_types\n\n    def reader(self, init_net=None, cursor_name=None, batch_size=1,\n               enforce_batch_size=False):\n        \"\"\"Create a Reader object that is used to iterate through the dataset.\n\n        This will append operations to `init_net` that create a TreeCursor,\n        used to iterate through the data.\n\n        NOTE: Currently, it is not safe to append to a dataset while reading.\n\n        Args:\n            init_net: net that will be run once to create the cursor.\n            cursor_name: optional name for the blob containing a pointer\n                         to the cursor.\n            batch_size: how many samples to read per iteration.\n\n        Returns:\n            A _DatasetReader that can be used to create operators that will\n            iterate through the dataset.\n        \"\"\"\n        assert self.field_blobs, 'Dataset not initialized.'\n        reader = _DatasetReader(self, cursor_name, batch_size,\n                                enforce_batch_size)\n        if init_net is not None:\n            reader.setup_ex(init_net, None)\n        return reader\n\n    def random_reader(self, init_net=None, indices=None, cursor_name=None,\n                      batch_size=1, loop_over=False, enforce_batch_size=False):\n        \"\"\"Create a Reader object that is used to iterate through the dataset.\n\n        NOTE: The reader order depends on the order in indices.\n\n        Args:\n            init_net: net that will be run once to create the cursor.\n            indices: blob of reading order\n            cursor_name: optional name for the blob containing a pointer\n                         to the cursor.\n            batch_size: how many samples to read per iteration.\n            loop_over: repeat the dataset indefinitely (in the same order)\n\n        Returns:\n            A DatasetReader that can be used to create operators that will\n            iterate through the dataset according to indices.\n        \"\"\"\n        assert self.field_blobs, 'Dataset not initialized.'\n        reader = _DatasetRandomReader(\n            self, cursor_name, indices, batch_size, loop_over,\n            enforce_batch_size)\n        if init_net is not None:\n            reader.setup_ex(init_net, None)\n        return reader\n\n    def writer(self, init_net=None):\n        \"\"\"Create a Writer that can be used to append entries into the dataset.\n\n        NOTE: Currently, it is not safe to append to a dataset\n              while reading from it.\n        NOTE: Currently implementation of writer is not thread safe.\n              TODO: fixme\n\n        Args:\n            init_net: net that will be run once in order to create the writer.\n                      (currently not used)\n        \"\"\"\n        assert self.field_blobs, 'Dataset not initialized.'\n        writer = _DatasetWriter(self.content())\n        if init_net is not None:\n            writer.setup_ex(init_net, None)\n        return writer\n"
  },
  {
    "path": "caffe2/python/db_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import workspace\n\nimport os\nimport tempfile\nimport unittest\n\n\nclass TestDB(unittest.TestCase):\n    def setUp(self):\n        handle, self.file_name = tempfile.mkstemp()\n        os.close(handle)\n        self.data = [\n            (\n                \"key{}\".format(i).encode(\"ascii\"),\n                \"value{}\".format(i).encode(\"ascii\")\n            )\n            for i in range(1, 10)\n        ]\n\n    def testSimple(self):\n        db = workspace.C.create_db(\n            \"minidb\", self.file_name, workspace.C.Mode.write)\n\n        for key, value in self.data:\n            transaction = db.new_transaction()\n            transaction.put(key, value)\n            del transaction\n\n        del db  # should close DB\n\n        db = workspace.C.create_db(\n            \"minidb\", self.file_name, workspace.C.Mode.read)\n        cursor = db.new_cursor()\n        data = []\n        while cursor.valid():\n            data.append((cursor.key(), cursor.value()))\n            cursor.next()  # noqa: B305\n        del cursor\n\n        db.close()  # test explicit db closer\n        self.assertEqual(data, self.data)\n"
  },
  {
    "path": "caffe2/python/device_checker.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package device_checker\n# Module caffe2.python.device_checker\nimport numpy as np\nimport copy\nfrom caffe2.python import workspace\nfrom future.utils import viewitems\n\n\nclass DeviceChecker(object):\n    \"\"\"A device checker in Python to check consistency across multiple devices.\n\n    This is not the most efficient way to check devices, as the Python interface\n    will involve a lot of copies back and forth operations. Use at your own risk.\n    \"\"\"\n\n    def __init__(self, threshold, device_options):\n        self._threshold = threshold\n        self._device_options = device_options\n\n    def CheckSimple(self, op, inputs, outputs_to_check,\n                    input_device_options=None):\n        \"\"\"Checks the operator with different device implementations.\n\n        Inputs:\n          op: the operator to be checked.\n          inputs: the input data in numpy arrays.\n          outputs_to_check: the outputs to check between devices.\n          input_device_options: a mapping from input name to a device to use\n            (instead of self._device_options)\n        Outputs:\n          boolean: True if it passes, False if it does not pass.\n        \"\"\"\n        op = copy.deepcopy(op)\n        input_device_options = input_device_options or {}\n        # Entering the checker workspace\n        old_ws_name = workspace.CurrentWorkspace()\n        results = []\n        workspace.SwitchWorkspace(\"_device_check_\", True)\n        for i, device_option in enumerate(self._device_options):\n            for i, arr in enumerate(inputs):\n                workspace.FeedBlob(\n                    op.input[i], np.array(arr),\n                    input_device_options.get(op.input[i], device_option))\n            op.device_option.CopyFrom(device_option)\n            workspace.RunOperatorOnce(op)\n            results.append(\n                [workspace.FetchBlob(op.output[idx])\n                 for idx in outputs_to_check])\n            # Everything is done, reset the workspace.\n            workspace.ResetWorkspace()\n        # After running on all devices, check correctness\n        success = True\n        for i in range(1, len(self._device_options)):\n            for j in range(len(outputs_to_check)):\n                x = results[i][j]\n                y = results[0][j]\n                if not np.allclose(x, y,\n                                   atol=self._threshold, rtol=self._threshold):\n                    print('Failure in checking device option {}'\n                          ' and output {}. The outputs are:'\n                          .format(i, op.output[outputs_to_check[j]]))\n                    print(x.flatten())\n                    print(y.flatten())\n                    print(np.max(np.abs(x - y)))\n                    success = False\n                # else:\n                #     print ('Passed device pair (0, %d), %s %s' %\n                #            (i, outputs_to_check[j], y.shape))\n        workspace.SwitchWorkspace(old_ws_name)\n        return success\n\n    def CheckNet(self, net, inputs=None, blobs_to_check=None, ignore=None):\n        \"\"\"Checks a network by inspecting all of its intermediate results, and\n        see if things match.\n        \"\"\"\n        if inputs is None:\n            inputs = {}\n        if ignore is None:\n            ignore = set()\n        old_ws_name = workspace.CurrentWorkspace()\n        results = []\n        if blobs_to_check is None:\n            blobs_to_check = sum([list(op.output) for op in net.op], [])\n        blobs_to_check = [b for b in blobs_to_check if b not in ignore]\n        workspace.SwitchWorkspace(\"_device_check_\", True)\n        for device_option in self._device_options:\n            for name, arr in viewitems(inputs):\n                # print 'feeding', name\n                workspace.FeedBlob(name, arr, device_option)\n            for op in net.op:\n                op.device_option.CopyFrom(device_option)\n            workspace.RunNetOnce(net)\n            results.append(\n                [workspace.FetchBlob(name) for name in blobs_to_check]\n            )\n        # After running on all devices, check correctness\n        success = True\n        for i in range(1, len(results)):\n            for j in range(len(blobs_to_check)):\n                x = results[i][j]\n                y = results[0][j]\n                if not np.allclose(x, y,\n                                   atol=self._threshold, rtol=self._threshold):\n                    print('Failure in checking device option {}'\n                          ' and output {}. The outputs are:'\n                          .format(i, blobs_to_check[j]))\n                    print(x.flatten())\n                    print(y.flatten())\n                    print(np.max(np.abs(x - y)))\n                    success = False\n                # else:\n                #     print ('Passed device pair (%d, %d), %s %s: %s' %\n                #            (i, j, blobs_to_check[j], y.shape,\n                #             str(y.flatten())))\n        workspace.SwitchWorkspace(old_ws_name)\n        return success\n"
  },
  {
    "path": "caffe2/python/dlpack.h",
    "content": "/*!\n *  Copyright (c) 2017 by Contributors\n * \\file dlpack.h\n * \\brief The common header of DLPack.\n */\n\n// Copied from pytorch/torch/lib/ATen/dlpack.h\n\n#ifndef DLPACK_DLPACK_H_\n#define DLPACK_DLPACK_H_\n\n#ifdef __cplusplus\n#define DLPACK_EXTERN_C extern \"C\"\n#else\n#define DLPACK_EXTERN_C\n#endif\n\n/*! \\brief The current version of dlpack */\n#define DLPACK_VERSION 010\n\n/*! \\brief DLPACK_DLL prefix for windows */\n#ifdef _WIN32\n#ifdef DLPACK_EXPORTS\n#define DLPACK_DLL __declspec(dllexport)\n#else\n#define DLPACK_DLL __declspec(dllimport)\n#endif\n#else\n#define DLPACK_DLL\n#endif\n\n#include <stdint.h>\n#include <stddef.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n/*!\n * \\brief The device type in DLContext.\n */\ntypedef enum {\n  kCPU = 1,\n  kGPU = 2,\n  // kCPUPinned = kCPU | kGPU\n  kCPUPinned = 3,\n  kOpenCL = 4,\n  kMetal = 8,\n  kVPI = 9,\n  kROCM = 10,\n} DLDeviceType;\n\n/*!\n * \\brief A Device context for Tensor and operator.\n */\ntypedef struct {\n  /*! \\brief The device type used in the device. */\n  DLDeviceType device_type;\n  /*! \\brief The device index */\n  int device_id;\n} DLContext;\n\n/*!\n * \\brief The type code options DLDataType.\n */\ntypedef enum {\n  kInt = 0U,\n  kUInt = 1U,\n  kFloat = 2U,\n} DLDataTypeCode;\n\n/*!\n * \\brief The data type the tensor can hold.\n *\n *  Examples\n *   - float: type_code = 2, bits = 32, lanes=1\n *   - float4(vectorized 4 float): type_code = 2, bits = 32, lanes=4\n *   - int8: type_code = 0, bits = 8, lanes=1\n */\ntypedef struct {\n  /*!\n   * \\brief Type code of base types.\n   * We keep it uint8_t instead of DLDataTypeCode for minimal memory\n   * footprint, but the value should be one of DLDataTypeCode enum values.\n   * */\n  uint8_t code;\n  /*!\n   * \\brief Number of bits, common choices are 8, 16, 32.\n   */\n  uint8_t bits;\n  /*! \\brief Number of lanes in the type, used for vector types. */\n  uint16_t lanes;\n} DLDataType;\n\n/*!\n * \\brief Plain C Tensor object, does not manage memory.\n */\ntypedef struct {\n  /*!\n   * \\brief The opaque data pointer points to the allocated data.\n   *  This will be CUDA device pointer or cl_mem handle in OpenCL.\n   *  This pointer is always aligns to 256 bytes as in CUDA.\n   */\n  void* data;\n  /*! \\brief The device context of the tensor */\n  DLContext ctx;\n  /*! \\brief Number of dimensions */\n  int ndim;\n  /*! \\brief The data type of the pointer*/\n  DLDataType dtype;\n  /*! \\brief The shape of the tensor */\n  int64_t* shape;\n  /*!\n   * \\brief strides of the tensor,\n   *  can be NULL, indicating tensor is compact.\n   */\n  int64_t* strides;\n  /*! \\brief The offset in bytes to the beginning pointer to data */\n  uint64_t byte_offset;\n} DLTensor;\n\n/*!\n * \\brief C Tensor object, manage memory of DLTensor.\n */\ntypedef struct DLManagedTensor {\n  /*! \\DLTensor which is being memory managed */\n  DLTensor dlTensor;\n  /*! \\brief context in which DLManagedTensor is used in a framework. It can\n   *   also be NULL\n   */\n  void * ctx;\n  /*! \\brief Destructor signature void (*)(void*) - this should be called\n   *   to destruct ctx which holds the DLManagedTensor. It can be NULL if there\n   *   is no way for the caller to provide a reasonable destructor.\n   */\n  void (*destructor)(DLManagedTensor * self);\n} DLManagedTensor;\n\n#ifdef __cplusplus\n}  // DLPACK_EXTERN_C\n#endif\n#endif  // DLPACK_DLPACK_H_\n"
  },
  {
    "path": "caffe2/python/docs/formatter.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package formatter\n# Module caffe2.python.docs.formatter\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python.docs.parser import Parser\n\n\nclass Formatter(object):\n    def __init__(self):\n        self.content = \"\"\n\n    def clone(self):\n        return self.__class__()\n\n    def dump(self):\n        return self.content\n\n    def parseAndAdd(self, text):\n        text = Parser(text, self).parse()\n        self.addRaw(text)\n\n    def addRaw(self, text):\n        raise Exception('Not yet implemented.')\n\n    def addLine(self, text):\n        raise Exception('Not yet implemented.')\n\n    def addLinebreak(self):\n        raise Exception('Not yet implemented.')\n\n    def addHeader(self, text):\n        raise Exception('Not yet implemented.')\n\n    def addEmphasis(self, text):\n        raise Exception('Not yet implemented.')\n\n    def addList(self, textList):\n        raise Exception('Not yet implemented.')\n\n    def addLink(self, text, url):\n        raise Exception('Not yet implemented.')\n\n    def addCode(self, text):\n        raise Exception('Not yet implemented.')\n\n    def addCodeLink(self, text):\n        raise Exception('Not yet implemented.')\n\n    def addTable(self, table):\n        raise Exception('Not yet implemented.')\n\n    def addBreak(self):\n        raise Exception('Not yet implemented.')\n\n\nclass Markdown(Formatter):\n    def addRaw(self, text):\n        self.content += \"{text}\".format(text=text)\n\n    def addLine(self, text, new_line=False):\n        self.content += \"{line}{text}\\n\".format(line=('\\n' if new_line else ''),\n                                                text=text)\n\n    def addLinebreak(self):\n        self.content += \"\\n\"\n\n    def addHeader(self, text, h=1):\n        self.addLine(\"{header} {text}\".format(header=h * '#', text=text), True)\n\n    def addEmphasis(self, text, s=1):\n        self.addRaw(\"{stars}{text}{stars}\".format(stars=s * '*', text=text))\n\n    def addList(self, textList):\n        for text in textList:\n            self.addLine(\"- {text}\".format(text=text), True)\n        self.addLinebreak()\n\n    def addLink(self, text, url):\n        self.addRaw(\"[{text}]({url})\".format(text=text, url=url))\n\n    def addCodeLink(self, path, options=None):\n        self.addRaw(\"({path})\".format(path=path))\n\n    def addCode(self, text, inline=False):\n        if (inline):\n            self.content += \"`{text}`\".format(text=text)\n        else:\n            self.addRaw(\"\\n\\n```\\n{text}```\\n\\n\".format(text=text))\n\n    def addTable(self, table, noTitle=False):\n        self.addLinebreak()\n        assert(len(table) > 1)\n        if noTitle:\n            table.insert(0, [' ' for i in range(len(table[0]))])\n        self.addLine(' | '.join(table[0]))\n        self.addLine(' | '.join(['----' for i in range(len(table[0]))]))\n        for row in table[1:]:\n            self.addLine(' | '.join(row))\n        self.addLinebreak()\n\n    def addBreak(self):\n        self.addLine('\\n---\\n', True)\n"
  },
  {
    "path": "caffe2/python/docs/generator.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package generator\n# Module caffe2.python.docs.generator\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport argparse\nimport os\nfrom caffe2.python import core, workspace\nfrom caffe2.python.docs.formatter import Markdown\nfrom future.utils import viewitems, viewvalues\n\nOpSchema = workspace.C.OpSchema\n\n\nclass DocUploader(object):\n    def __init__(self):\n        pass\n\n    def upload(self, text):\n        pass\n\n\nclass DocGenerator(object):\n    def __init__(self, formatter, uploader):\n        self.formatter = formatter\n        self.uploader = uploader\n        self.content_body = \"\"\n\n    def create_body(self):\n        pass\n\n    def update(self):\n        self.uploader.upload(self.content_body)\n\n\nclass OpDocGenerator(DocGenerator):\n    def getOperatorDoc(self, name, schema, priority):\n        return OperatorDoc(name, schema, priority)\n\n    def getOperatorEngine(self, name):\n        return OperatorEngine(name)\n\n    def getOperators(self):\n        # map: op_name -> operator\n        self.operators = {}\n        # map: op_name -> [engine, engine]\n        self.engines = {}\n\n        def filePriority(x):\n            if x == \"caffe2/caffe2/operators\":\n                return 0\n            if 'contrib' in x.split('/'):\n                return 2\n            if 'experiments' in x.split('/'):\n                return 3\n            return 1\n\n        for name in core._GetRegisteredOperators():\n            schema = OpSchema.get(name)\n            if schema:\n                priority = filePriority(os.path.dirname(schema.file))\n                operator = self.getOperatorDoc(name, schema, priority)\n                self.operators[name] = operator\n\n            # Engine\n            elif name.find(\"_ENGINE_\") != -1:\n                engine = self.getOperatorEngine(name)\n                if engine.base_op_name in self.engines:\n                    self.engines[engine.base_op_name].append(engine)\n                else:\n                    self.engines[engine.base_op_name] = [engine]\n\n            # No schema\n            else:\n                priority = 4\n                self.operators[name] = self.getOperatorDoc(name, schema, priority)\n\n        for name, engines in viewitems(self.engines):\n            if name in self.operators:\n                self.operators[name].addEngines(engines)\n\n        # Generate a sorted list of operators\n        return sorted(\n            viewvalues(self.operators),\n            key=lambda op: (op.priority, op.name)\n        )\n\n    def createBody(self):\n        operators = self.getOperators()\n\n        for operator in operators:\n            operator.generateSchema(self.formatter)\n\n        self.content_body += self.formatter.dump()\n\n\nclass OperatorEngine(object):\n    def __init__(self, name):\n        self.op_name = name\n        self.base_op_name, self.engine = name.split(\"_ENGINE_\", 1)\n\n    def getDeviceImpl(self):\n        deviceImplList = []\n        for device, impl in [('CPU', OpSchema.get_cpu_impl(self.op_name)),\n                             ('CUDA', OpSchema.get_cuda_impl(self.op_name))]:\n            if not impl:\n                continue\n            deviceImplList.append((device, impl))\n        return deviceImplList\n\n    def generateDoc(self, formatter):\n        for device, impl in self.getDeviceImpl():\n            formatter.addLine(\n                '{engine} on {device}: {impl}'.format(engine=self.engine,\n                                                      device=device,\n                                                      impl=impl))\n\n\nclass OperatorDoc(object):\n    def __init__(self, name, schema, priority):\n        self.name = name\n        self.schema = schema\n        self.priority = priority\n        print(\"Gathering docs for {}...\".format(self.name))\n        self.engines = []\n\n    def addEngines(self, engines):\n        self.engines = engines\n\n    def generateDoc(self, formatter):\n        if self.schema.doc:\n            formatter.parseAndAdd(self.schema.doc)\n            formatter.addLinebreak()\n        else:\n            formatter.addLine(\"No documentation yet.\")\n\n    def generateTable(self, formatter, tuples, title_row, title):\n        if tuples:\n            if title:\n                formatter.addHeader(title, 3)\n            table = []\n            if title_row:\n                table = [title_row]\n            for name, doc in tuples:\n                table.append([name, doc or ''])\n            formatter.addTable(table, (table == []))\n\n    def generateInterface(self, formatter):\n        def makeDesc(title, args):\n            f = formatter.clone()\n            f.addEmphasis(title, 1)\n            out = [(f.dump(), '')]\n            for arg in args:\n                f = formatter.clone()\n                if isinstance(arg, tuple):\n                    name = arg[0]\n                    if len(arg) > 1:\n                        description = arg[1] or ''\n                    else:\n                        description = ''\n                else:\n                    name = arg.name\n                    description = arg.description or ''\n                f.addCode(name, inline=True)\n                out.append((f.dump(), description or ''))\n            return out\n\n        tuples = []\n\n        if self.schema.args:\n            tuples += makeDesc('Arguments', self.schema.args)\n\n        if self.schema.input_desc:\n            tuples += makeDesc('Inputs', self.schema.input_desc)\n\n        if self.schema.output_desc:\n            tuples += makeDesc('Outputs', self.schema.output_desc)\n\n        self.generateTable(formatter, tuples, None, 'Interface')\n        print(\"Generated interface for {}\".format(self.name))\n\n    def generateCodeLink(self, formatter):\n        formatter.addHeader(\"Code\", 3)\n        formatter.addLinebreak()\n        formatter.addCodeLink(self.schema.file)\n\n    def getInfo(self, formatter, name, impl):\n        pass\n\n    def generateDevices(self, formatter):\n        formatter.addHeader(\"Devices\", 3)\n        devices = [\n            self.getInfo(formatter,\n                         'CPU', OpSchema.get_cpu_impl(self.name)),\n            self.getInfo(formatter,\n                         'GPU', OpSchema.get_cuda_impl(self.name)),\n        ]\n        formatter.addList([i for i in devices if i])\n\n    def generateEngines(self, formatter):\n        if not len(self.engines):\n            return\n        formatter.addHeader(\"Engines\", 3)\n        for engine in self.engines:\n            engine.generateDoc(formatter)\n\n    def generateSchema(self, formatter):\n        formatter.addHeader(self.name, 2)\n        if self.schema:\n            self.generateDoc(formatter)\n            self.generateInterface(formatter)\n            self.generateCodeLink(formatter)\n            self.generateDevices(formatter)\n            self.generateEngines(formatter)\n            formatter.addBreak()\n        else:\n            formatter.addLine(\"No schema documented yet.\")\n            self.generateDevices(formatter)\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description=\"Operators catalog generator.\")\n    parser.add_argument('catalog_path', type=str,\n                        help='operators-catalogue.md to write out to')\n    args = parser.parse_args()\n\n    with open(args.catalog_path, 'w') as fp:\n        ops = OpDocGenerator(Markdown(), DocUploader())\n        ops.createBody()\n        fp.write(ops.content_body)\n"
  },
  {
    "path": "caffe2/python/docs/github.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package github\n# Module caffe2.python.docs.github\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport argparse\nimport os\nfrom caffe2.python.docs.formatter import Markdown\nfrom caffe2.python.docs.generator import OpDocGenerator, DocUploader\nfrom caffe2.python.docs.generator import OperatorDoc, OperatorEngine\n\n\nclass GHOpDocUploader(DocUploader):\n    def __init__(self):\n        pass\n\n    def upload(self, content_body):\n        print(content_body)\n\n\nclass GHMarkdown(Markdown):\n    def addHeader(self, text, h=1):\n        self.addLine(\"\\n{header} {text}\\n\".format(header=h * '#', text=text), True)\n\n    def addDocHeader(self):\n        self.addLine(\"---\")\n        self.addLine(\"docid: operators-catalog\")\n        self.addLine(\"title: Operators Catalog\")\n        self.addLine(\"layout: operators\")\n        self.addLine(\"permalink: /docs/operators-catalogue.html\")\n        self.addLine(\"---\")\n        self.addLine(\"* TOC\")\n        self.addLine(\"{:toc}\")\n\n    def addTable(self, table, noTitle=False):\n        self.addLinebreak()\n        assert(len(table) > 1)\n        self.addLine(' | '.join(['----------' for i in range(len(table[0]))]))\n        self.addLine(' | '.join(table[0]))\n        for row in table[1:]:\n            self.addLine(' | '.join(row))\n\n    def addTableHTML(self, table, noTitle=False):\n        self.addRaw(\"<table>\")\n        for row in table:\n            self.addRaw(\"<tr>\")\n            for cell in row:\n                self.addRaw(\"<td>\")\n                self.addLine(\"{cell}\".format(cell=cell))\n                self.addRaw(\"</td>\")\n            self.addRaw(\"</tr>\")\n        self.addRaw(\"</table>\")\n\ndef getCodeLink(formatter, schema):\n    formatter = formatter.clone()\n    path = os.path.join(\"caffe2\", os.path.relpath(schema.file, \"caffe2\"))\n    schemaLink = ('https://github.com/caffe2/caffe2/blob/master/{path}'\n                  .format(path=path))\n    formatter.addLink('{path}'.format(path=path), schemaLink)\n    return formatter.dump()\n\n\nclass GHOperatorEngine(OperatorEngine):\n    def generateDoc(self, formatter):\n        for device, _ in self.getDeviceImpl():\n            formatter.addCode('{engine}'.format(engine=self.engine), True)\n            if device:\n                formatter.addRaw(' on ')\n                formatter.addEmphasis(\"{device}\".format(device=device), 1)\n\n\nclass GHOperatorDoc(OperatorDoc):\n    def generateCodeLink(self, formatter):\n        formatter.addHeader(\"Code\", 3)\n        formatter.addLinebreak()\n        formatter.addRaw(getCodeLink(formatter, self.schema))\n\n    def getInfo(self, formatter, name, impl):\n        formatter = formatter.clone()\n        if impl:\n            formatter.addEmphasis('{name}'.format(name=name), 1)\n            formatter.addRaw(' ')\n            formatter.addCode('{impl}'.format(impl=impl), True)\n        return formatter.dump()\n\n    def generateSchema(self, formatter):\n        formatter.addHeader(self.name, 2)\n        if self.schema:\n            self.generateDoc(formatter)\n            self.generateInterface(formatter)\n            self.generateCodeLink(formatter)\n            formatter.addBreak()\n        else:\n            formatter.addLine(\"No schema documented yet.\")\n\n\nclass GHOpDocGenerator(OpDocGenerator):\n    def getOperatorDoc(self, name, schema, priority):\n        return GHOperatorDoc(name, schema, priority)\n\n    def getOperatorEngine(self, name):\n        return GHOperatorEngine(name)\n\n    def createBody(self):\n        self.formatter.addDocHeader()\n        operators = self.getOperators()\n\n        for operator in operators:\n            operator.generateSchema(self.formatter)\n\n        self.content_body += self.formatter.dump()\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description=\"Operators catalog generator.\")\n    parser.add_argument('catalog_path', type=str,\n                        help='operators-catalogue.md to write out to')\n    args = parser.parse_args()\n\n    with open(args.catalog_path, 'w') as fp:\n        ops = GHOpDocGenerator(GHMarkdown(), GHOpDocUploader)\n        ops.createBody()\n        fp.write(ops.content_body)\n        print(\"Updated {}!\".format(args.catalog_path))\n"
  },
  {
    "path": "caffe2/python/docs/parser.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package parser\n# Module caffe2.python.docs.parser\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport re\n\n\nclass Parser(object):\n    # List of tuples (regex_str, lambda(regex_match, formatter))\n    # If a lambda returns True it will be called repeatedly with replacement\n    # otherwise it will only be called on text that hasn't been parsed yet.\n    regexes = [\n        # Code blocks of various formats\n        ('````(.+?)````',\n         lambda m, f: f.addCode(m.group(1))\n         ),\n        ('```(.+?)```',\n         lambda m, f: f.addCode(m.group(1))\n         ),\n        ('((( {2})+)(\\S.*)(\\n\\s*\\n|\\n))+',\n         lambda m, f: f.addCode(m.group(0))\n         ),\n        ('([^\\.])\\n',\n         lambda m, f: f.addRaw('{c} '.format(c=m.group(1))) or True\n         ),\n        ('`(.+?)`',\n         lambda m, f: f.addCode(m.group(1), True)\n         ),\n        # Make links clickable\n        ('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'\n         '|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+',\n         lambda m, f: f.addLink(m.group(0), m.group(0))\n         ),\n        ('\\*\\*(.+?)\\*\\*',\n         lambda m, f: f.addEmphasis(m.group(1), 2)\n         ),\n        ('\\*(.+?)\\*',\n         lambda m, f: f.addEmphasis(m.group(1), 1)\n         ),\n    ]\n\n    def __init__(self, text, formatter):\n        self.text = text\n        self.lines = []\n        self.formatter = formatter\n\n    def parseText(self):\n        UNPARSED = 0\n        PARSED = 1\n        parsed_block = [(UNPARSED, self.text)]\n        for regex, func in self.regexes:\n            index = 0\n            while index < len(parsed_block):\n                label, text = parsed_block[index]\n\n                # Already been parsed\n                if (label == PARSED):\n                    index += 1\n                    continue\n\n                match = re.search(regex, text)\n                if match:\n                    parsed_block.pop(index)\n                    start = match.start(0)\n                    end = match.end(0)\n\n                    f = self.formatter.clone()\n                    merge = func(match, f)\n\n                    if merge:\n                        merged = text[:start] + f.dump() + text[end:]\n                        parsed_block.insert(index, (UNPARSED, merged))\n                    else:\n                        if text[:start]:\n                            parsed_block.insert(index,\n                                                (UNPARSED, text[:start]))\n\n                        index += 1\n                        parsed_block.insert(index, (PARSED, f.dump()))\n\n                        index += 1\n                        if text[end:]:\n                            parsed_block.insert(index,\n                                                (UNPARSED, text[end:]))\n\n                else:\n                    index += 1\n\n        self.lines += [i for _, i in parsed_block]\n        self.text = ' '.join(self.lines)\n\n    def parse(self):\n        self.parseText()\n        return self.text\n"
  },
  {
    "path": "caffe2/python/dyndep.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package dyndep\n# Module caffe2.python.dyndep\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport ctypes\nimport os\n\nfrom caffe2.python import core, extension_loader\n\n\ndef InitOpsLibrary(name):\n    \"\"\"Loads a dynamic library that contains custom operators into Caffe2.\n\n    Since Caffe2 uses static variable registration, you can optionally load a\n    separate .so file that contains custom operators and registers that into\n    the caffe2 core binary. In C++, this is usually done by either declaring\n    dependency during compilation time, or via dynload. This allows us to do\n    registration similarly on the Python side.\n\n    Args:\n        name: a name that ends in .so, such as \"my_custom_op.so\". Otherwise,\n            the command will simply be ignored.\n    Returns:\n        None\n    \"\"\"\n    if not os.path.exists(name):\n        # Note(jiayq): if the name does not exist, instead of immediately\n        # failing we will simply print a warning, deferring failure to the\n        # time when an actual call is made.\n        print('Ignoring {} as it is not a valid file.'.format(name))\n        return\n    _init_impl(name)\n\n\n_IMPORTED_DYNDEPS = set()\n\n\ndef GetImportedOpsLibraries():\n    return _IMPORTED_DYNDEPS\n\n\ndef _init_impl(path):\n    _IMPORTED_DYNDEPS.add(path)\n    with extension_loader.DlopenGuard():\n        ctypes.CDLL(path)\n    # reinitialize available ops\n    core.RefreshRegisteredOperators()\n"
  },
  {
    "path": "caffe2/python/embedding_generation_benchmark.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package embedding_generation_benchmark\n# Module caffe2.python.embedding_generation_benchmark\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import workspace, core, utils, model_helper\n\nimport argparse\nimport numpy as np\nimport time\n\nimport logging\n\nlogging.basicConfig()\nlog = logging.getLogger(\"embedding_generation_benchmark\")\nlog.setLevel(logging.DEBUG)\n\n\ndef generate_data(T, batch_size, max_seq_length):\n    '''\n    Fill a queue with input data\n    '''\n    log.info(\"Generating T={} batches\".format(T))\n\n    generate_input_init_net = core.Net('generate_input_init')\n    queue = generate_input_init_net.CreateBlobsQueue(\n        [], \"inputqueue\", num_blobs=1, capacity=T,\n    )\n    workspace.RunNetOnce(generate_input_init_net)\n\n    generate_input_net = core.Net('generate_input')\n    generate_input_net.EnqueueBlobs([queue, \"scratch\"], [\"scratch\"])\n    np.random.seed(2603)\n\n    for t in range(T):\n        if (t % (max(10, T // 10)) == 0):\n            log.info(\"Generating data {}/{}\".format(t, T))\n        X = np.tile(np.arange(max_seq_length), [batch_size, 1]).transpose()\n        workspace.FeedBlob(\"scratch\", X)\n        workspace.RunNetOnce(generate_input_net.Proto())\n\n    log.info(\"Finished data generation\")\n    return queue\n\n\ndef generate_embedding_table(vocab_size, embedding_size):\n    log.info(\"Generating embedding table with dimensions {}\"\n             .format([vocab_size, embedding_size]))\n\n    generate_table_net = core.Net('generate_table')\n    table = generate_table_net.GaussianFill(\n        [],\n        ['embedding_table'],\n        shape=[vocab_size, embedding_size],\n    )\n\n    workspace.RunNetOnce(generate_table_net)\n    return table\n\n\ndef create_model(args, queue, embedding_table, embedding_size):\n    model = model_helper.ModelHelper(name='embedding_generation_bench')\n    input_blob = model.net.DequeueBlobs(queue, 'input_data')\n\n    if args.implementation == 'sinusoid':\n        model.net.SinusoidPositionEncoding(\n            [input_blob],\n            ['output'],\n            embedding_size=embedding_size\n        )\n    else:\n        model.net.Gather(\n            [embedding_table, input_blob],\n            ['output'],\n        )\n\n    return model\n\n\ndef Caffe2EmbeddingGeneration(args):\n    T = args.data_size // args.batch_size\n\n    queue = generate_data(T, args.batch_size, args.seq_length)\n\n    embedding_table = None\n    if args.implementation == 'table':\n        embedding_table = generate_embedding_table(\n            args.seq_length,\n            args.embedding_size,\n        )\n\n    model = create_model(args, queue, embedding_table, args.embedding_size)\n\n    workspace.RunNetOnce(model.param_init_net)\n    workspace.CreateNet(model.net)\n\n    start_time = time.time()\n    num_iters = T\n    total_iters = 0\n\n    # Run the Benchmark\n    log.info(\"------ Warming up ------\")\n    workspace.RunNet(model.net.Proto().name)\n\n    log.info(\"------ Starting benchmark ------\")\n    start_time = time.time()\n    last_time = time.time()\n    for iteration in range(1, num_iters, args.iters_to_report):\n        iters_once = min(args.iters_to_report, num_iters - iteration)\n        total_iters += iters_once\n        workspace.RunNet(model.net.Proto().name, iters_once)\n\n        new_time = time.time()\n        log.info(\n            \"Iter: {} / {}. Embeddings Generated Per Second: {}k.\".format(\n                iteration,\n                num_iters,\n                (iters_once * args.batch_size * args.seq_length) /\n                (new_time - last_time) // 100 / 10,\n            )\n        )\n        last_time = new_time\n\n    total_per_sec = (num_iters - 1) * args.batch_size * args.seq_length\n    total_per_sec = total_per_sec / (time.time() - start_time) // 100 / 10\n\n    log.info(\"Done. Total embeddings generated per second \" +\n             \"excluding 1st iteration: {}k\".format(total_per_sec))\n\n    return time.time() - start_time\n\n\n@utils.debug\ndef Benchmark(args):\n    return Caffe2EmbeddingGeneration(args)\n\n\ndef GetArgumentParser():\n    parser = argparse.ArgumentParser(\n        description=\"Embedding generation benchmark.\"\n    )\n\n    parser.add_argument(\n        \"--embedding_size\",\n        type=int,\n        default=512,\n        help=\"Embedding size\",\n    )\n    parser.add_argument(\n        \"--batch_size\",\n        type=int,\n        default=16,\n        help=\"The batch size.\"\n    )\n    parser.add_argument(\n        \"--data_size\",\n        type=int,\n        default=10000,\n        help=\"Number of sequences to generate\"\n    )\n    parser.add_argument(\n        \"--seq_length\",\n        type=int,\n        default=128,\n        help=\"Max sequence length\"\n    )\n    parser.add_argument(\n        \"--iters_to_report\",\n        type=int,\n        default=20,\n        help=\"Number of iterations to report progress\"\n    )\n    parser.add_argument(\n        \"--implementation\",\n        type=str,\n        default=\"sinusoid\",\n        help=\"'table' or 'sinusoid'\",\n    )\n    return parser\n\n\nif __name__ == '__main__':\n    args, extra_args = GetArgumentParser().parse_known_args()\n\n    workspace.GlobalInit([\n        'caffe2',\n        '--caffe2_log_level=0',\n        '--caffe2_print_blob_sizes_at_exit=0'] + extra_args)\n\n    device = core.DeviceOption(caffe2_pb2.CPU)\n\n    with core.DeviceScope(device):\n        Benchmark(args)\n"
  },
  {
    "path": "caffe2/python/examples/char_rnn.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package char_rnn\n# Module caffe2.python.examples.char_rnn\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace, model_helper, utils, brew\nfrom caffe2.python.rnn_cell import LSTM\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python.optimizer import build_sgd\n\n\nimport argparse\nimport logging\nimport numpy as np\nfrom datetime import datetime\n\n'''\nThis script takes a text file as input and uses a recurrent neural network\nto learn to predict next character in a sequence.\n'''\n\nlogging.basicConfig()\nlog = logging.getLogger(\"char_rnn\")\nlog.setLevel(logging.DEBUG)\n\n\n# Default set() here is intentional as it would accumulate values like a global\n# variable\ndef CreateNetOnce(net, created_names=set()): # noqa\n    name = net.Name()\n    if name not in created_names:\n        created_names.add(name)\n        workspace.CreateNet(net)\n\n\nclass CharRNN(object):\n    def __init__(self, args):\n        self.seq_length = args.seq_length\n        self.batch_size = args.batch_size\n        self.iters_to_report = args.iters_to_report\n        self.hidden_size = args.hidden_size\n\n        with open(args.train_data) as f:\n            self.text = f.read()\n\n        self.vocab = list(set(self.text))\n        self.char_to_idx = {ch: idx for idx, ch in enumerate(self.vocab)}\n        self.idx_to_char = {idx: ch for idx, ch in enumerate(self.vocab)}\n        self.D = len(self.char_to_idx)\n\n        print(\"Input has {} characters. Total input size: {}\".format(\n            len(self.vocab), len(self.text)))\n\n    def CreateModel(self):\n        log.debug(\"Start training\")\n        model = model_helper.ModelHelper(name=\"char_rnn\")\n\n        input_blob, seq_lengths, hidden_init, cell_init, target = \\\n            model.net.AddExternalInputs(\n                'input_blob',\n                'seq_lengths',\n                'hidden_init',\n                'cell_init',\n                'target',\n            )\n\n        hidden_output_all, self.hidden_output, _, self.cell_state = LSTM(\n            model, input_blob, seq_lengths, (hidden_init, cell_init),\n            self.D, self.hidden_size, scope=\"LSTM\")\n        output = brew.fc(\n            model,\n            hidden_output_all,\n            None,\n            dim_in=self.hidden_size,\n            dim_out=self.D,\n            axis=2\n        )\n\n        # axis is 2 as first two are T (time) and N (batch size).\n        # We treat them as one big batch of size T * N\n        softmax = model.net.Softmax(output, 'softmax', axis=2)\n\n        softmax_reshaped, _ = model.net.Reshape(\n            softmax, ['softmax_reshaped', '_'], shape=[-1, self.D])\n\n        # Create a copy of the current net. We will use it on the forward\n        # pass where we don't need loss and backward operators\n        self.forward_net = core.Net(model.net.Proto())\n\n        xent = model.net.LabelCrossEntropy([softmax_reshaped, target], 'xent')\n        # Loss is average both across batch and through time\n        # Thats why the learning rate below is multiplied by self.seq_length\n        loss = model.net.AveragedLoss(xent, 'loss')\n        model.AddGradientOperators([loss])\n\n        # use build_sdg function to build an optimizer\n        build_sgd(\n            model,\n            base_learning_rate=0.1 * self.seq_length,\n            policy=\"step\",\n            stepsize=1,\n            gamma=0.9999\n        )\n\n        self.model = model\n        self.predictions = softmax\n        self.loss = loss\n\n        self.prepare_state = core.Net(\"prepare_state\")\n        self.prepare_state.Copy(self.hidden_output, hidden_init)\n        self.prepare_state.Copy(self.cell_state, cell_init)\n\n    def _idx_at_pos(self, pos):\n        return self.char_to_idx[self.text[pos]]\n\n    def TrainModel(self):\n        log.debug(\"Training model\")\n\n        workspace.RunNetOnce(self.model.param_init_net)\n\n        # As though we predict the same probability for each character\n        smooth_loss = -np.log(1.0 / self.D) * self.seq_length\n        last_n_iter = 0\n        last_n_loss = 0.0\n        num_iter = 0\n        N = len(self.text)\n\n        # We split text into batch_size pieces. Each piece will be used only\n        # by a corresponding batch during the training process\n        text_block_positions = np.zeros(self.batch_size, dtype=np.int32)\n        text_block_size = N // self.batch_size\n        text_block_starts = list(range(0, N, text_block_size))\n        text_block_sizes = [text_block_size] * self.batch_size\n        text_block_sizes[self.batch_size - 1] += N % self.batch_size\n        assert sum(text_block_sizes) == N\n\n        # Writing to output states which will be copied to input\n        # states within the loop below\n        workspace.FeedBlob(self.hidden_output, np.zeros(\n            [1, self.batch_size, self.hidden_size], dtype=np.float32\n        ))\n        workspace.FeedBlob(self.cell_state, np.zeros(\n            [1, self.batch_size, self.hidden_size], dtype=np.float32\n        ))\n        workspace.CreateNet(self.prepare_state)\n\n        # We iterate over text in a loop many times. Each time we peak\n        # seq_length segment and feed it to LSTM as a sequence\n        last_time = datetime.now()\n        progress = 0\n        while True:\n            workspace.FeedBlob(\n                \"seq_lengths\",\n                np.array([self.seq_length] * self.batch_size,\n                         dtype=np.int32)\n            )\n            workspace.RunNet(self.prepare_state.Name())\n\n            input = np.zeros(\n                [self.seq_length, self.batch_size, self.D]\n            ).astype(np.float32)\n            target = np.zeros(\n                [self.seq_length * self.batch_size]\n            ).astype(np.int32)\n\n            for e in range(self.batch_size):\n                for i in range(self.seq_length):\n                    pos = text_block_starts[e] + text_block_positions[e]\n                    input[i][e][self._idx_at_pos(pos)] = 1\n                    target[i * self.batch_size + e] =\\\n                        self._idx_at_pos((pos + 1) % N)\n                    text_block_positions[e] = (\n                        text_block_positions[e] + 1) % text_block_sizes[e]\n                    progress += 1\n\n            workspace.FeedBlob('input_blob', input)\n            workspace.FeedBlob('target', target)\n\n            CreateNetOnce(self.model.net)\n            workspace.RunNet(self.model.net.Name())\n\n            num_iter += 1\n            last_n_iter += 1\n\n            if num_iter % self.iters_to_report == 0:\n                new_time = datetime.now()\n                print(\"Characters Per Second: {}\". format(\n                    int(progress / (new_time - last_time).total_seconds())\n                ))\n                print(\"Iterations Per Second: {}\". format(\n                    int(self.iters_to_report /\n                        (new_time - last_time).total_seconds())\n                ))\n\n                last_time = new_time\n                progress = 0\n\n                print(\"{} Iteration {} {}\".\n                      format('-' * 10, num_iter, '-' * 10))\n\n            loss = workspace.FetchBlob(self.loss) * self.seq_length\n            smooth_loss = 0.999 * smooth_loss + 0.001 * loss\n            last_n_loss += loss\n\n            if num_iter % self.iters_to_report == 0:\n                self.GenerateText(500, np.random.choice(self.vocab))\n\n                log.debug(\"Loss since last report: {}\"\n                          .format(last_n_loss / last_n_iter))\n                log.debug(\"Smooth loss: {}\".format(smooth_loss))\n\n                last_n_loss = 0.0\n                last_n_iter = 0\n\n    def GenerateText(self, num_characters, ch):\n        # Given a starting symbol we feed a fake sequence of size 1 to\n        # our RNN num_character times. After each time we use output\n        # probabilities to pick a next character to feed to the network.\n        # Same character becomes part of the output\n        CreateNetOnce(self.forward_net)\n\n        text = '' + ch\n        for _i in range(num_characters):\n            workspace.FeedBlob(\n                \"seq_lengths\", np.array([1] * self.batch_size, dtype=np.int32))\n            workspace.RunNet(self.prepare_state.Name())\n\n            input = np.zeros([1, self.batch_size, self.D]).astype(np.float32)\n            input[0][0][self.char_to_idx[ch]] = 1\n\n            workspace.FeedBlob(\"input_blob\", input)\n            workspace.RunNet(self.forward_net.Name())\n\n            p = workspace.FetchBlob(self.predictions)\n            next = np.random.choice(self.D, p=p[0][0])\n\n            ch = self.idx_to_char[next]\n            text += ch\n\n        print(text)\n\n\n@utils.debug\ndef main():\n    parser = argparse.ArgumentParser(\n        description=\"Caffe2: Char RNN Training\"\n    )\n    parser.add_argument(\"--train_data\", type=str, default=None,\n                        help=\"Path to training data in a text file format\",\n                        required=True)\n    parser.add_argument(\"--seq_length\", type=int, default=25,\n                        help=\"One training example sequence length\")\n    parser.add_argument(\"--batch_size\", type=int, default=1,\n                        help=\"Training batch size\")\n    parser.add_argument(\"--iters_to_report\", type=int, default=500,\n                        help=\"How often to report loss and generate text\")\n    parser.add_argument(\"--hidden_size\", type=int, default=100,\n                        help=\"Dimension of the hidden representation\")\n    parser.add_argument(\"--gpu\", action=\"store_true\",\n                        help=\"If set, training is going to use GPU 0\")\n\n    args = parser.parse_args()\n\n    device = core.DeviceOption(\n        caffe2_pb2.CUDA if args.gpu else caffe2_pb2.CPU, 0)\n    with core.DeviceScope(device):\n        model = CharRNN(args)\n        model.CreateModel()\n        model.TrainModel()\n\n\nif __name__ == '__main__':\n    workspace.GlobalInit(['caffe2', '--caffe2_log_level=2'])\n    main()\n"
  },
  {
    "path": "caffe2/python/examples/lmdb_create_example.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package lmdb_create_example\n# Module caffe2.python.examples.lmdb_create_example\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport argparse\nimport numpy as np\n\nimport lmdb\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import workspace, model_helper\n\n'''\nSimple example to create an lmdb database of random image data and labels.\nThis can be used a skeleton to write your own data import.\n\nIt also runs a dummy-model with Caffe2 that reads the data and\nvalidates the checksum is same.\n'''\n\n\ndef create_db(output_file):\n    print(\">>> Write database...\")\n    LMDB_MAP_SIZE = 1 << 40   # MODIFY\n    env = lmdb.open(output_file, map_size=LMDB_MAP_SIZE)\n\n    checksum = 0\n    with env.begin(write=True) as txn:\n        for j in range(0, 128):\n            # MODIFY: add your own data reader / creator\n            label = j % 10\n            width = 64\n            height = 32\n\n            img_data = np.random.rand(3, width, height)\n            # ...\n\n            # Create TensorProtos\n            tensor_protos = caffe2_pb2.TensorProtos()\n            img_tensor = tensor_protos.protos.add()\n            img_tensor.dims.extend(img_data.shape)\n            img_tensor.data_type = 1\n\n            flatten_img = img_data.reshape(np.prod(img_data.shape))\n            img_tensor.float_data.extend(flatten_img)\n\n            label_tensor = tensor_protos.protos.add()\n            label_tensor.data_type = 2\n            label_tensor.int32_data.append(label)\n            txn.put(\n                '{}'.format(j).encode('ascii'),\n                tensor_protos.SerializeToString()\n            )\n\n            checksum += np.sum(img_data) * label\n            if (j % 16 == 0):\n                print(\"Inserted {} rows\".format(j))\n\n    print(\"Checksum/write: {}\".format(int(checksum)))\n    return checksum\n\n\ndef read_db_with_caffe2(db_file, expected_checksum):\n    print(\">>> Read database...\")\n    model = model_helper.ModelHelper(name=\"lmdbtest\")\n    batch_size = 32\n    data, label = model.TensorProtosDBInput(\n        [], [\"data\", \"label\"], batch_size=batch_size,\n        db=db_file, db_type=\"lmdb\")\n\n    checksum = 0\n\n    workspace.RunNetOnce(model.param_init_net)\n    workspace.CreateNet(model.net)\n\n    for _ in range(0, 4):\n        workspace.RunNet(model.net.Proto().name)\n\n        img_datas = workspace.FetchBlob(\"data\")\n        labels = workspace.FetchBlob(\"label\")\n        for j in range(batch_size):\n            checksum += np.sum(img_datas[j, :]) * labels[j]\n\n    print(\"Checksum/read: {}\".format(int(checksum)))\n    assert np.abs(expected_checksum - checksum < 0.1), \\\n        \"Read/write checksums dont match\"\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        description=\"Example LMDB creation\"\n    )\n    parser.add_argument(\"--output_file\", type=str, default=None,\n                        help=\"Path to write the database to\",\n                        required=True)\n\n    args = parser.parse_args()\n    checksum = create_db(args.output_file)\n\n    # For testing reading:\n    read_db_with_caffe2(args.output_file, checksum)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "caffe2/python/examples/resnet50_trainer.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n# Module caffe2.python.examples.resnet50_trainer\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport argparse\nimport logging\nimport numpy as np\nimport time\nimport os\n\nfrom caffe2.python import core, workspace, experiment_util, data_parallel_model\nfrom caffe2.python import data_parallel_model_utils, dyndep, optimizer\nfrom caffe2.python import timeout_guard, model_helper, brew\nfrom caffe2.proto import caffe2_pb2\n\nimport caffe2.python.models.resnet as resnet\nfrom caffe2.python.modeling.initializers import Initializer, PseudoFP16Initializer\nimport caffe2.python.predictor.predictor_exporter as pred_exp\nimport caffe2.python.predictor.predictor_py_utils as pred_utils\nfrom caffe2.python.predictor_constants import predictor_constants as predictor_constants\n\n'''\nParallelized multi-GPU distributed trainer for Resnet 50. Can be used to train\non imagenet data, for example.\n\nTo run the trainer in single-machine multi-gpu mode by setting num_shards = 1.\n\nTo run the trainer in multi-machine multi-gpu mode with M machines,\nrun the same program on all machines, specifying num_shards = M, and\nshard_id = a unique integer in the set [0, M-1].\n\nFor rendezvous (the trainer processes have to know about each other),\nyou can either use a directory path that is visible to all processes\n(e.g. NFS directory), or use a Redis instance. Use the former by\npassing the `file_store_path` argument. Use the latter by passing the\n`redis_host` and `redis_port` arguments.\n'''\n\nlogging.basicConfig()\nlog = logging.getLogger(\"resnet50_trainer\")\nlog.setLevel(logging.DEBUG)\n\ndyndep.InitOpsLibrary('@/caffe2/caffe2/distributed:file_store_handler_ops')\ndyndep.InitOpsLibrary('@/caffe2/caffe2/distributed:redis_store_handler_ops')\n\n\ndef AddImageInput(model, reader, batch_size, img_size, dtype, is_test):\n    '''\n    The image input operator loads image and label data from the reader and\n    applies transformations to the images (random cropping, mirroring, ...).\n    '''\n    data, label = brew.image_input(\n        model,\n        reader, [\"data\", \"label\"],\n        batch_size=batch_size,\n        output_type=dtype,\n        use_gpu_transform=True if model._device_type == 1 else False,\n        use_caffe_datum=True,\n        mean=128.,\n        std=128.,\n        scale=256,\n        crop=img_size,\n        mirror=1,\n        is_test=is_test,\n    )\n\n    data = model.StopGradient(data, data)\n\n\ndef AddNullInput(model, reader, batch_size, img_size, dtype):\n    '''\n    The null input function uses a gaussian fill operator to emulate real image\n    input. A label blob is hardcoded to a single value. This is useful if you\n    want to test compute throughput or don't have a dataset available.\n    '''\n    suffix = \"_fp16\" if dtype == \"float16\" else \"\"\n    model.param_init_net.GaussianFill(\n        [],\n        [\"data\" + suffix],\n        shape=[batch_size, 3, img_size, img_size],\n    )\n    if dtype == \"float16\":\n        model.param_init_net.FloatToHalf(\"data\" + suffix, \"data\")\n\n    model.param_init_net.ConstantFill(\n        [],\n        [\"label\"],\n        shape=[batch_size],\n        value=1,\n        dtype=core.DataType.INT32,\n    )\n\n\ndef SaveModel(args, train_model, epoch):\n    prefix = \"[]_{}\".format(train_model._device_prefix, train_model._devices[0])\n    predictor_export_meta = pred_exp.PredictorExportMeta(\n        predict_net=train_model.net.Proto(),\n        parameters=data_parallel_model.GetCheckpointParams(train_model),\n        inputs=[prefix + \"/data\"],\n        outputs=[prefix + \"/softmax\"],\n        shapes={\n            prefix + \"/softmax\": (1, args.num_labels),\n            prefix + \"/data\": (args.num_channels, args.image_size, args.image_size)\n        }\n    )\n\n    # save the train_model for the current epoch\n    model_path = \"%s/%s_%d.mdl\" % (\n        args.file_store_path,\n        args.save_model_name,\n        epoch,\n    )\n\n    # set db_type to be \"minidb\" instead of \"log_file_db\", which breaks\n    # the serialization in save_to_db. Need to switch back to log_file_db\n    # after migration\n    pred_exp.save_to_db(\n        db_type=\"minidb\",\n        db_destination=model_path,\n        predictor_export_meta=predictor_export_meta,\n    )\n\n\ndef LoadModel(path, model):\n    '''\n    Load pretrained model from file\n    '''\n    log.info(\"Loading path: {}\".format(path))\n    meta_net_def = pred_exp.load_from_db(path, 'minidb')\n    init_net = core.Net(pred_utils.GetNet(\n        meta_net_def, predictor_constants.GLOBAL_INIT_NET_TYPE))\n    predict_init_net = core.Net(pred_utils.GetNet(\n        meta_net_def, predictor_constants.PREDICT_INIT_NET_TYPE))\n\n    predict_init_net.RunAllOnGPU()\n    init_net.RunAllOnGPU()\n\n    assert workspace.RunNetOnce(predict_init_net)\n    assert workspace.RunNetOnce(init_net)\n\n    # Hack: fix iteration counter which is in CUDA context after load model\n    itercnt = workspace.FetchBlob(\"optimizer_iteration\")\n    workspace.FeedBlob(\n        \"optimizer_iteration\",\n        itercnt,\n        device_option=core.DeviceOption(caffe2_pb2.CPU, 0)\n    )\n\n\ndef RunEpoch(\n    args,\n    epoch,\n    train_model,\n    test_model,\n    total_batch_size,\n    num_shards,\n    expname,\n    explog,\n):\n    '''\n    Run one epoch of the trainer.\n    TODO: add checkpointing here.\n    '''\n    # TODO: add loading from checkpoint\n    log.info(\"Starting epoch {}/{}\".format(epoch, args.num_epochs))\n    epoch_iters = int(args.epoch_size / total_batch_size / num_shards)\n    for i in range(epoch_iters):\n        # This timeout is required (temporarily) since CUDA-NCCL\n        # operators might deadlock when synchronizing between GPUs.\n        timeout = 600.0 if i == 0 else 60.0\n        with timeout_guard.CompleteInTimeOrDie(timeout):\n            t1 = time.time()\n            workspace.RunNet(train_model.net.Proto().name)\n            t2 = time.time()\n            dt = t2 - t1\n\n        fmt = \"Finished iteration {}/{} of epoch {} ({:.2f} images/sec)\"\n        log.info(fmt.format(i + 1, epoch_iters, epoch, total_batch_size / dt))\n        prefix = \"{}_{}\".format(\n            train_model._device_prefix,\n            train_model._devices[0])\n        accuracy = workspace.FetchBlob(prefix + '/accuracy')\n        loss = workspace.FetchBlob(prefix + '/loss')\n        train_fmt = \"Training loss: {}, accuracy: {}\"\n        log.info(train_fmt.format(loss, accuracy))\n\n    num_images = epoch * epoch_iters * total_batch_size\n    prefix = \"{}_{}\".format(train_model._device_prefix, train_model._devices[0])\n    accuracy = workspace.FetchBlob(prefix + '/accuracy')\n    loss = workspace.FetchBlob(prefix + '/loss')\n    learning_rate = workspace.FetchBlob(\n        data_parallel_model.GetLearningRateBlobNames(train_model)[0]\n    )\n    test_accuracy = 0\n    if (test_model is not None):\n        # Run 100 iters of testing\n        ntests = 0\n        for _ in range(0, 100):\n            workspace.RunNet(test_model.net.Proto().name)\n            for g in test_model._devices:\n                test_accuracy += np.asscalar(workspace.FetchBlob(\n                    \"{}_{}\".format(test_model._device_prefix, g) + '/accuracy'\n                ))\n                ntests += 1\n        test_accuracy /= ntests\n    else:\n        test_accuracy = (-1)\n\n    explog.log(\n        input_count=num_images,\n        batch_count=(i + epoch * epoch_iters),\n        additional_values={\n            'accuracy': accuracy,\n            'loss': loss,\n            'learning_rate': learning_rate,\n            'epoch': epoch,\n            'test_accuracy': test_accuracy,\n        }\n    )\n    assert loss < 40, \"Exploded gradients :(\"\n\n    # TODO: add checkpointing\n    return epoch + 1\n\n\ndef Train(args):\n    # Either use specified device list or generate one\n    if args.gpus is not None:\n        gpus = [int(x) for x in args.gpus.split(',')]\n        num_gpus = len(gpus)\n    else:\n        gpus = list(range(args.num_gpus))\n        num_gpus = args.num_gpus\n\n    log.info(\"Running on GPUs: {}\".format(gpus))\n\n    # Verify valid batch size\n    total_batch_size = args.batch_size\n    batch_per_device = total_batch_size // num_gpus\n    assert \\\n        total_batch_size % num_gpus == 0, \\\n        \"Number of GPUs must divide batch size\"\n\n    # Round down epoch size to closest multiple of batch size across machines\n    global_batch_size = total_batch_size * args.num_shards\n    epoch_iters = int(args.epoch_size / global_batch_size)\n\n    assert \\\n        epoch_iters > 0, \\\n        \"Epoch size must be larger than batch size times shard count\"\n\n    args.epoch_size = epoch_iters * global_batch_size\n    log.info(\"Using epoch size: {}\".format(args.epoch_size))\n\n    # Create ModelHelper object\n    train_arg_scope = {\n        'order': 'NCHW',\n        'use_cudnn': True,\n        'cudnn_exhaustive_search': True,\n        'ws_nbytes_limit': (args.cudnn_workspace_limit_mb * 1024 * 1024),\n    }\n    train_model = model_helper.ModelHelper(\n        name=\"resnet50\", arg_scope=train_arg_scope\n    )\n\n    num_shards = args.num_shards\n    shard_id = args.shard_id\n\n    # Expect interfaces to be comma separated.\n    # Use of multiple network interfaces is not yet complete,\n    # so simply use the first one in the list.\n    interfaces = args.distributed_interfaces.split(\",\")\n\n    # Rendezvous using MPI when run with mpirun\n    if os.getenv(\"OMPI_COMM_WORLD_SIZE\") is not None:\n        num_shards = int(os.getenv(\"OMPI_COMM_WORLD_SIZE\", 1))\n        shard_id = int(os.getenv(\"OMPI_COMM_WORLD_RANK\", 0))\n        if num_shards > 1:\n            rendezvous = dict(\n                kv_handler=None,\n                num_shards=num_shards,\n                shard_id=shard_id,\n                engine=\"GLOO\",\n                transport=args.distributed_transport,\n                interface=interfaces[0],\n                mpi_rendezvous=True,\n                exit_nets=None)\n\n    elif num_shards > 1:\n        # Create rendezvous for distributed computation\n        store_handler = \"store_handler\"\n        if args.redis_host is not None:\n            # Use Redis for rendezvous if Redis host is specified\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"RedisStoreHandlerCreate\", [], [store_handler],\n                    host=args.redis_host,\n                    port=args.redis_port,\n                    prefix=args.run_id,\n                )\n            )\n        else:\n            # Use filesystem for rendezvous otherwise\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"FileStoreHandlerCreate\", [], [store_handler],\n                    path=args.file_store_path,\n                    prefix=args.run_id,\n                )\n            )\n\n        rendezvous = dict(\n            kv_handler=store_handler,\n            shard_id=shard_id,\n            num_shards=num_shards,\n            engine=\"GLOO\",\n            transport=args.distributed_transport,\n            interface=interfaces[0],\n            exit_nets=None)\n\n    else:\n        rendezvous = None\n\n    # Model building functions\n    def create_resnet50_model_ops(model, loss_scale):\n        initializer = (PseudoFP16Initializer if args.dtype == 'float16'\n                       else Initializer)\n\n        with brew.arg_scope([brew.conv, brew.fc],\n                            WeightInitializer=initializer,\n                            BiasInitializer=initializer,\n                            enable_tensor_core=args.enable_tensor_core,\n                            float16_compute=args.float16_compute):\n            pred = resnet.create_resnet50(\n                model,\n                \"data\",\n                num_input_channels=args.num_channels,\n                num_labels=args.num_labels,\n                no_bias=True,\n                no_loss=True,\n            )\n\n        if args.dtype == 'float16':\n            pred = model.net.HalfToFloat(pred, pred + '_fp32')\n\n        softmax, loss = model.SoftmaxWithLoss([pred, 'label'],\n                                              ['softmax', 'loss'])\n        loss = model.Scale(loss, scale=loss_scale)\n        brew.accuracy(model, [softmax, \"label\"], \"accuracy\")\n        return [loss]\n\n    def add_optimizer(model):\n        stepsz = int(30 * args.epoch_size / total_batch_size / num_shards)\n\n        if args.float16_compute:\n            # TODO: merge with multi-prceision optimizer\n            opt = optimizer.build_fp16_sgd(\n                model,\n                args.base_learning_rate,\n                momentum=0.9,\n                nesterov=1,\n                weight_decay=args.weight_decay,   # weight decay included\n                policy=\"step\",\n                stepsize=stepsz,\n                gamma=0.1\n            )\n        else:\n            optimizer.add_weight_decay(model, args.weight_decay)\n            opt = optimizer.build_multi_precision_sgd(\n                model,\n                args.base_learning_rate,\n                momentum=0.9,\n                nesterov=1,\n                policy=\"step\",\n                stepsize=stepsz,\n                gamma=0.1\n            )\n        return opt\n\n    # Define add_image_input function.\n    # Depends on the \"train_data\" argument.\n    # Note that the reader will be shared with between all GPUS.\n    if args.train_data == \"null\":\n        def add_image_input(model):\n            AddNullInput(\n                model,\n                None,\n                batch_size=batch_per_device,\n                img_size=args.image_size,\n                dtype=args.dtype,\n            )\n    else:\n        reader = train_model.CreateDB(\n            \"reader\",\n            db=args.train_data,\n            db_type=args.db_type,\n            num_shards=num_shards,\n            shard_id=shard_id,\n        )\n\n        def add_image_input(model):\n            AddImageInput(\n                model,\n                reader,\n                batch_size=batch_per_device,\n                img_size=args.image_size,\n                dtype=args.dtype,\n                is_test=False,\n            )\n\n    def add_post_sync_ops(model):\n        \"\"\"Add ops applied after initial parameter sync.\"\"\"\n        for param_info in model.GetOptimizationParamInfo(model.GetParams()):\n            if param_info.blob_copy is not None:\n                model.param_init_net.HalfToFloat(\n                    param_info.blob,\n                    param_info.blob_copy[core.DataType.FLOAT]\n                )\n\n    # Create parallelized model\n    data_parallel_model.Parallelize(\n        train_model,\n        input_builder_fun=add_image_input,\n        forward_pass_builder_fun=create_resnet50_model_ops,\n        optimizer_builder_fun=add_optimizer,\n        post_sync_builder_fun=add_post_sync_ops,\n        devices=gpus,\n        rendezvous=rendezvous,\n        optimize_gradient_memory=False,\n        cpu_device=args.use_cpu,\n        shared_model=args.use_cpu,\n        combine_spatial_bn=args.use_cpu,\n    )\n\n    if args.model_parallel:\n        # Shift half of the activations to another GPU\n        assert workspace.NumCudaDevices() >= 2 * args.num_gpus\n        activations = data_parallel_model_utils.GetActivationBlobs(train_model)\n        data_parallel_model_utils.ShiftActivationDevices(\n            train_model,\n            activations=activations[len(activations) // 2:],\n            shifts={g: args.num_gpus + g for g in range(args.num_gpus)},\n        )\n\n    data_parallel_model.OptimizeGradientMemory(train_model, {}, set(), False)\n\n    workspace.RunNetOnce(train_model.param_init_net)\n    workspace.CreateNet(train_model.net)\n\n    # Add test model, if specified\n    test_model = None\n    if (args.test_data is not None):\n        log.info(\"----- Create test net ----\")\n        test_arg_scope = {\n            'order': \"NCHW\",\n            'use_cudnn': True,\n            'cudnn_exhaustive_search': True,\n        }\n        test_model = model_helper.ModelHelper(\n            name=\"resnet50_test\", arg_scope=test_arg_scope, init_params=False\n        )\n\n        test_reader = test_model.CreateDB(\n            \"test_reader\",\n            db=args.test_data,\n            db_type=args.db_type,\n        )\n\n        def test_input_fn(model):\n            AddImageInput(\n                model,\n                test_reader,\n                batch_size=batch_per_device,\n                img_size=args.image_size,\n                dtype=args.dtype,\n                is_test=True,\n            )\n\n        data_parallel_model.Parallelize(\n            test_model,\n            input_builder_fun=test_input_fn,\n            forward_pass_builder_fun=create_resnet50_model_ops,\n            post_sync_builder_fun=add_post_sync_ops,\n            param_update_builder_fun=None,\n            devices=gpus,\n            cpu_device=args.use_cpu,\n        )\n        workspace.RunNetOnce(test_model.param_init_net)\n        workspace.CreateNet(test_model.net)\n\n    epoch = 0\n    # load the pre-trained model and reset epoch\n    if args.load_model_path is not None:\n        LoadModel(args.load_model_path, train_model)\n\n        # Sync the model params\n        data_parallel_model.FinalizeAfterCheckpoint(train_model)\n\n        # reset epoch. load_model_path should end with *_X.mdl,\n        # where X is the epoch number\n        last_str = args.load_model_path.split('_')[-1]\n        if last_str.endswith('.mdl'):\n            epoch = int(last_str[:-4])\n            log.info(\"Reset epoch to {}\".format(epoch))\n        else:\n            log.warning(\"The format of load_model_path doesn't match!\")\n\n    expname = \"resnet50_gpu%d_b%d_L%d_lr%.2f_v2\" % (\n        args.num_gpus,\n        total_batch_size,\n        args.num_labels,\n        args.base_learning_rate,\n    )\n\n    explog = experiment_util.ModelTrainerLog(expname, args)\n\n    # Run the training one epoch a time\n    while epoch < args.num_epochs:\n        epoch = RunEpoch(\n            args,\n            epoch,\n            train_model,\n            test_model,\n            total_batch_size,\n            num_shards,\n            expname,\n            explog\n        )\n\n        # Save the model for each epoch\n        SaveModel(args, train_model, epoch)\n\n        model_path = \"%s/%s_\" % (\n            args.file_store_path,\n            args.save_model_name\n        )\n        # remove the saved model from the previous epoch if it exists\n        if os.path.isfile(model_path + str(epoch - 1) + \".mdl\"):\n            os.remove(model_path + str(epoch - 1) + \".mdl\")\n\n\ndef main():\n    # TODO: use argv\n    parser = argparse.ArgumentParser(\n        description=\"Caffe2: Resnet-50 training\"\n    )\n    parser.add_argument(\"--train_data\", type=str, default=None, required=True,\n                        help=\"Path to training data (or 'null' to simulate)\")\n    parser.add_argument(\"--test_data\", type=str, default=None,\n                        help=\"Path to test data\")\n    parser.add_argument(\"--db_type\", type=str, default=\"lmdb\",\n                        help=\"Database type (such as lmdb or leveldb)\")\n    parser.add_argument(\"--gpus\", type=str,\n                        help=\"Comma separated list of GPU devices to use\")\n    parser.add_argument(\"--num_gpus\", type=int, default=1,\n                        help=\"Number of GPU devices (instead of --gpus)\")\n    parser.add_argument(\"--model_parallel\", type=bool, default=False,\n                        help=\"Split model over 2 x num_gpus\")\n    parser.add_argument(\"--num_channels\", type=int, default=3,\n                        help=\"Number of color channels\")\n    parser.add_argument(\"--image_size\", type=int, default=227,\n                        help=\"Input image size (to crop to)\")\n    parser.add_argument(\"--num_labels\", type=int, default=1000,\n                        help=\"Number of labels\")\n    parser.add_argument(\"--batch_size\", type=int, default=32,\n                        help=\"Batch size, total over all GPUs\")\n    parser.add_argument(\"--epoch_size\", type=int, default=1500000,\n                        help=\"Number of images/epoch, total over all machines\")\n    parser.add_argument(\"--num_epochs\", type=int, default=1000,\n                        help=\"Num epochs.\")\n    parser.add_argument(\"--base_learning_rate\", type=float, default=0.1,\n                        help=\"Initial learning rate.\")\n    parser.add_argument(\"--weight_decay\", type=float, default=1e-4,\n                        help=\"Weight decay (L2 regularization)\")\n    parser.add_argument(\"--cudnn_workspace_limit_mb\", type=int, default=64,\n                        help=\"CuDNN workspace limit in MBs\")\n    parser.add_argument(\"--num_shards\", type=int, default=1,\n                        help=\"Number of machines in distributed run\")\n    parser.add_argument(\"--shard_id\", type=int, default=0,\n                        help=\"Shard id.\")\n    parser.add_argument(\"--run_id\", type=str,\n                        help=\"Unique run identifier (e.g. uuid)\")\n    parser.add_argument(\"--redis_host\", type=str,\n                        help=\"Host of Redis server (for rendezvous)\")\n    parser.add_argument(\"--redis_port\", type=int, default=6379,\n                        help=\"Port of Redis server (for rendezvous)\")\n    parser.add_argument(\"--file_store_path\", type=str, default=\"/tmp\",\n                        help=\"Path to directory to use for rendezvous\")\n    parser.add_argument(\"--save_model_name\", type=str, default=\"resnet50_model\",\n                        help=\"Save the trained model to a given name\")\n    parser.add_argument(\"--load_model_path\", type=str, default=None,\n                        help=\"Load previously saved model to continue training\")\n    parser.add_argument(\"--use_cpu\", type=bool, default=False,\n                        help=\"Use CPU instead of GPU\")\n    parser.add_argument('--dtype', default='float',\n                        choices=['float', 'float16'],\n                        help='Data type used for training')\n    parser.add_argument('--float16_compute', action='store_true',\n                        help=\"Use float 16 compute, if available\")\n    parser.add_argument('--enable_tensor_core', action='store_true',\n                        help='Enable Tensor Core math for Conv and FC ops')\n    parser.add_argument(\"--distributed_transport\", type=str, default=\"tcp\",\n                        help=\"Transport to use for distributed run [tcp|ibverbs]\")\n    parser.add_argument(\"--distributed_interfaces\", type=str, default=\"\",\n                        help=\"Network interfaces to use for distributed run\")\n\n    args = parser.parse_args()\n\n    Train(args)\n\nif __name__ == '__main__':\n    workspace.GlobalInit(['caffe2', '--caffe2_log_level=2'])\n    main()\n"
  },
  {
    "path": "caffe2/python/experiment_util.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package experiment_util\n# Module caffe2.python.experiment_util\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport datetime\nimport time\nimport logging\nimport socket\nimport abc\nimport six\n\nfrom collections import OrderedDict\nfrom future.utils import viewkeys, viewvalues\n\n'''\nUtilities for logging experiment run stats, such as accuracy\nand loss over time for different runs. Runtime arguments are stored\nin the log.\n\nOptionally, ModelTrainerLog calls out to a logger to log to\nan external log destination.\n'''\n\n\nclass ExternalLogger(object):\n    six.add_metaclass(abc.ABCMeta)\n\n    @abc.abstractmethod\n    def set_runtime_args(self, runtime_args):\n        \"\"\"\n            Set runtime arguments for the logger.\n            runtime_args: dict of runtime arguments.\n        \"\"\"\n        raise NotImplementedError(\n            'Must define set_runtime_args function to use this base class'\n        )\n\n    @abc.abstractmethod\n    def log(self, log_dict):\n        \"\"\"\n            log a dict of key/values to an external destination\n            log_dict: input dict\n        \"\"\"\n        raise NotImplementedError(\n            'Must define log function to use this base class'\n        )\n\n\nclass ModelTrainerLog():\n\n    def __init__(self, expname, runtime_args, external_loggers=None):\n        now = datetime.datetime.fromtimestamp(time.time())\n        self.experiment_id = \\\n            \"{}_{}\".format(expname, now.strftime('%Y%m%d_%H%M%S'))\n        self.filename = \"{}.log\".format(self.experiment_id)\n        self.logstr(\"# %s\" % str(runtime_args))\n        self.headers = None\n        self.start_time = time.time()\n        self.last_time = self.start_time\n        self.last_input_count = 0\n        self.external_loggers = None\n\n        if external_loggers is not None:\n            self.external_loggers = external_loggers\n            if not isinstance(runtime_args, dict):\n                runtime_args = dict(vars(runtime_args))\n            runtime_args['experiment_id'] = self.experiment_id\n            runtime_args['hostname'] = socket.gethostname()\n            for logger in self.external_loggers:\n                logger.set_runtime_args(runtime_args)\n        else:\n            self.external_loggers = []\n\n    def logstr(self, str):\n        with open(self.filename, \"a\") as f:\n            f.write(str + \"\\n\")\n            f.close()\n        logging.getLogger(\"experiment_logger\").info(str)\n\n    def log(self, input_count, batch_count, additional_values):\n        logdict = OrderedDict()\n        delta_t = time.time() - self.last_time\n        delta_count = input_count - self.last_input_count\n        self.last_time = time.time()\n        self.last_input_count = input_count\n\n        logdict['time_spent'] = delta_t\n        logdict['cumulative_time_spent'] = time.time() - self.start_time\n        logdict['input_count'] = delta_count\n        logdict['cumulative_input_count'] = input_count\n        logdict['cumulative_batch_count'] = batch_count\n        if delta_t > 0:\n            logdict['inputs_per_sec'] = delta_count / delta_t\n        else:\n            logdict['inputs_per_sec'] = 0.0\n\n        for k in sorted(viewkeys(additional_values)):\n            logdict[k] = additional_values[k]\n\n        # Write the headers if they are not written yet\n        if self.headers is None:\n            self.headers = list(viewkeys(logdict))\n            self.logstr(\",\".join(self.headers))\n\n        self.logstr(\",\".join(str(v) for v in viewvalues(logdict)))\n\n        for logger in self.external_loggers:\n            try:\n                logger.log(logdict)\n            except Exception as e:\n                logging.warn(\n                    \"Failed to call ExternalLogger: {}\".format(e), e)\n"
  },
  {
    "path": "caffe2/python/extension_loader.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package extension_loader\n# Module caffe2.python.extension_loader\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport contextlib\nimport ctypes\nimport sys\n\n\n_set_global_flags = (\n    hasattr(sys, 'getdlopenflags') and hasattr(sys, 'setdlopenflags'))\n\n\n@contextlib.contextmanager\ndef DlopenGuard():\n    if _set_global_flags:\n        old_flags = sys.getdlopenflags()\n        sys.setdlopenflags(old_flags | ctypes.RTLD_GLOBAL)\n    yield\n    if _set_global_flags:\n        sys.setdlopenflags(old_flags)\n"
  },
  {
    "path": "caffe2/python/functional.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nfrom collections import namedtuple\nfrom six import string_types\n\nOpSchema = workspace.C.OpSchema\n\n\ndef namedtupledict(typename, field_names, *args, **kwargs):\n    field_names_map = {n: i for i, n in enumerate(field_names)}\n    # Some output names are invalid python identifier, e.g. \"0\"\n    kwargs.setdefault('rename', True)\n    data = namedtuple(typename, field_names, *args, **kwargs)\n\n    def getitem(self, key):\n        if isinstance(key, string_types):\n            key = field_names_map[key]\n        return super(type(self), self).__getitem__(key)\n\n    data.__getitem__ = getitem\n    return data\n\n\nclass _Functional(object):\n    def __getattribute__(self, op_type):\n        def op_func(*inputs, **args):\n            ws = workspace.C.Workspace()\n            schema = OpSchema.get(op_type)\n            input_prefix = 'input_'\n            output_prefix = 'output_'\n\n            def get_name_list(prefix, num, max_num):\n                return [prefix + str(x) for x in range(min(num, max_num))]\n\n            input_names, output_names = [], []\n            input_names = get_name_list(\n                input_prefix, len(inputs), schema.max_input\n            )\n            # verify the length of input name is in range\n            # of schema\n            num_input = len(input_names)\n            if num_input > schema.max_input or num_input < \\\n               schema.min_input or not schema.num_inputs_allowed(num_input):\n                raise ValueError(\n                    \"Functional C2: Number of inputs not in \\\n                range: {} - {} or not allowed.\"\n                    .format(schema.min_input, schema.max_input)\n                )\n\n            if 'num_output' in args:\n                num_output = args['num_output']\n                if num_output > schema.max_output or \\\n                   num_output < schema.min_output or \\\n                   not schema.num_outputs_allowed(num_output) or \\\n                   not schema.num_inputs_outputs_allowed(num_input,\n                                                         num_output):\n                    raise ValueError(\n                        \"Functional C2: Number of output \\\n                    not in range: {} - {} or not allowed\"\n                        .format(schema.min_output, schema.max_output)\n                    )\n                output_names = get_name_list(\n                    output_prefix, num_output, schema.max_output\n                )\n                args.pop('num_output')\n            calculated = schema.CalculateOutput(num_input)\n            if not output_names and calculated != -1:\n                output_names = get_name_list(\n                    output_prefix, calculated, schema.max_output\n                )\n\n            if not output_names:\n                max_output = schema.max_output\n                # For an op with max_output == inf\n                # and no Output defined in schema\n                # user should pass output_size explicitly\n                if schema.inf == max_output:\n                    raise ValueError(\n                        \"For operators with max_output == inf,\\\n                        user should pass num_output explicity.\"\n                    )\n                output_names = get_name_list(\n                    output_prefix, max_output, max_output\n                )\n            for i, input_blob in enumerate(inputs):\n                ws.create_blob(input_names[i]).feed(input_blob)\n\n            op = core.CreateOperator(\n                op_type, input_names, output_names, **args\n            )\n            ws._run_operator(op.SerializeToString())\n            # RunOperator\n            output_values = [ws.fetch_blob(x) for x in output_names]\n            return namedtupledict('output', output_names)(*output_values)\n\n        return op_func\n\n\nFunctional = _Functional()\n"
  },
  {
    "path": "caffe2/python/functional_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport caffe2.python.hypothesis_test_util as hu\nfrom caffe2.python import workspace\nfrom caffe2.python.functional import Functional\nimport numpy as np\n\n\n@st.composite\ndef _tensor_splits(draw, add_axis=False):\n    \"\"\"Generates (axis, split_info, tensor_splits) tuples.\"\"\"\n    tensor = draw(hu.tensor(min_value=4))  # Each dim has at least 4 elements.\n    axis = draw(st.integers(0, len(tensor.shape) - 1))\n    if add_axis:\n        # Simple case: get individual slices along one axis, where each of them\n        # is (N-1)-dimensional. The axis will be added back upon concatenation.\n        return (\n            axis, np.ones(tensor.shape[axis], dtype=np.int32), [\n                np.array(tensor.take(i, axis=axis))\n                for i in range(tensor.shape[axis])\n            ]\n        )\n    else:\n        # General case: pick some (possibly consecutive, even non-unique)\n        # indices at which we will split the tensor, along the given axis.\n        splits = sorted(\n            draw(\n                st.\n                lists(elements=st.integers(0, tensor.shape[axis]), max_size=4)\n            ) + [0, tensor.shape[axis]]\n        )\n        return (\n            axis, np.array(np.diff(splits), dtype=np.int32), [\n                tensor.take(range(splits[i], splits[i + 1]), axis=axis)\n                for i in range(len(splits) - 1)\n            ],\n        )\n\n\nclass TestFunctional(hu.HypothesisTestCase):\n    @given(X=hu.tensor(), engine=st.sampled_from([\"\", \"CUDNN\"]))\n    def test_relu(self, X, engine):\n        X += 0.02 * np.sign(X)\n        X[X == 0.0] += 0.02\n        output = Functional.Relu(X)\n        Y_l = output[0]\n        Y_d = output[\"output_0\"]\n\n        with workspace.WorkspaceGuard(\"tmp_workspace\"):\n            op = core.CreateOperator(\"Relu\", [\"X\"], [\"Y\"], engine=engine)\n            workspace.FeedBlob(\"X\", X)\n            workspace.RunOperatorOnce(op)\n            Y_ref = workspace.FetchBlob(\"Y\")\n\n        np.testing.assert_array_equal(\n            Y_l, Y_ref, err_msg='Functional Relu result mismatch'\n        )\n\n        np.testing.assert_array_equal(\n            Y_d, Y_ref, err_msg='Functional Relu result mismatch'\n        )\n\n    @given(tensor_splits=_tensor_splits())\n    def test_concat(self, tensor_splits):\n        # Input Size: 1 -> inf\n        axis, _, splits = tensor_splits\n        concat_result, split_info = Functional.Concat(*splits, axis=axis)\n\n        concat_result_ref = np.concatenate(splits, axis=axis)\n        split_info_ref = np.array([a.shape[axis] for a in splits])\n\n        np.testing.assert_array_equal(\n            concat_result,\n            concat_result_ref,\n            err_msg='Functional Concat result mismatch'\n        )\n\n        np.testing.assert_array_equal(\n            split_info,\n            split_info_ref,\n            err_msg='Functional Concat split info mismatch'\n        )\n\n    @given(tensor_splits=_tensor_splits(), split_as_arg=st.booleans())\n    def test_split(self, tensor_splits, split_as_arg):\n        # Output Size: 1 - inf\n        axis, split_info, splits = tensor_splits\n\n        split_as_arg = True\n\n        if split_as_arg:\n            input_tensors = [np.concatenate(splits, axis=axis)]\n            kwargs = dict(axis=axis, split=split_info, num_output=len(splits))\n        else:\n            input_tensors = [np.concatenate(splits, axis=axis), split_info]\n            kwargs = dict(axis=axis, num_output=len(splits))\n        result = Functional.Split(*input_tensors, **kwargs)\n\n        def split_ref(input, split=split_info):\n            s = np.cumsum([0] + list(split))\n            return [\n                np.array(input.take(np.arange(s[i], s[i + 1]), axis=axis))\n                for i in range(len(split))\n            ]\n\n        result_ref = split_ref(*input_tensors)\n        for i, ref in enumerate(result_ref):\n            np.testing.assert_array_equal(\n                result[i], ref, err_msg='Functional Relu result mismatch'\n            )\n"
  },
  {
    "path": "caffe2/python/fused_8bit_rowwise_conversion_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\n\nimport numpy as np\nimport struct\nfrom hypothesis import given\n\n# Eigen/Python round 0.5 away from 0, Numpy rounds to even\nround_to_nearest = np.vectorize(round)\n\n\ndef bytes_to_floats(byte_matrix):\n    floats = np.empty([np.shape(byte_matrix)[0], 1], dtype=np.float32)\n    for i, byte_values in enumerate(byte_matrix):\n        floats[i], = struct.unpack('f', bytearray(byte_values))\n    return floats\n\n\ndef floats_to_bytes(floats):\n    byte_matrix = np.empty([np.shape(floats)[0], 4], dtype=np.uint8)\n    for i, value in enumerate(floats):\n        assert isinstance(value, np.float32), (value, floats)\n        as_bytes = struct.pack('f', value)\n        # In Python3 bytes will be a list of int, in Python2 a list of string\n        if isinstance(as_bytes[0], int):\n            byte_matrix[i] = list(as_bytes)\n        else:\n            byte_matrix[i] = list(map(ord, as_bytes))\n    return byte_matrix\n\n\ndef fused_rowwise_8bit_quantize_reference(data):\n    minimum = np.min(data, axis=1, keepdims=True)\n    maximum = np.max(data, axis=1, keepdims=True)\n    span = maximum - minimum\n    bias = minimum\n    scale = span / 255.0\n    inverse_scale = 255.0 / (span + 1e-8)\n    quantized_data = round_to_nearest((data - bias) * inverse_scale)\n    scale_bytes = floats_to_bytes(scale.reshape(-1))\n    bias_bytes = floats_to_bytes(bias.reshape(-1))\n    return np.concatenate([quantized_data, scale_bytes, bias_bytes], axis=1)\n\n\ndef fused_rowwise_8bit_quantize_dequantize_reference(data):\n    fused_quantized = fused_rowwise_8bit_quantize_reference(data)\n    scale = bytes_to_floats(fused_quantized[:, -8:-4].astype(np.uint8))\n    bias = bytes_to_floats(fused_quantized[:, -4:].astype(np.uint8))\n    quantized_data = fused_quantized[:, :-8]\n    return quantized_data * scale + bias\n\n\nclass TestFused8BitRowwiseQuantizationConversion(hu.HypothesisTestCase):\n    @given(input_data=hu.tensor(min_dim=2, max_dim=2))\n    def test_quantize_op(self, input_data):\n        quantize = core.CreateOperator(\n            'FloatToFused8BitRowwiseQuantized',\n            ['input_data'],\n            ['quantized_data'],\n        )\n        workspace.FeedBlob('input_data', input_data)\n        workspace.RunOperatorOnce(quantize)\n\n        quantized_data = workspace.FetchBlob('quantized_data')\n\n        reference = fused_rowwise_8bit_quantize_reference(\n            input_data.astype(np.float32)\n        )\n        np.testing.assert_array_almost_equal(quantized_data, reference)\n\n    @given(input_data=hu.tensor(min_dim=2, max_dim=2))\n    def test_quantize_and_dequantize_op(self, input_data):\n        quantize = core.CreateOperator(\n            'FloatToFused8BitRowwiseQuantized',\n            ['input_data'],\n            ['quantized_data'],\n        )\n        workspace.FeedBlob('input_data', input_data)\n        workspace.RunOperatorOnce(quantize)\n\n        quantized_data = workspace.FetchBlob('quantized_data')\n\n        dequantize = core.CreateOperator(\n            'Fused8BitRowwiseQuantizedToFloat',\n            ['quantized_data'],\n            ['dequantized_data'],\n        )\n        workspace.FeedBlob('quantized_data', quantized_data)\n        workspace.RunOperatorOnce(dequantize)\n\n        dequantized_data = workspace.FetchBlob('dequantized_data')\n\n        reference = fused_rowwise_8bit_quantize_dequantize_reference(input_data)\n        np.testing.assert_array_almost_equal(dequantized_data, reference)\n"
  },
  {
    "path": "caffe2/python/gradient_check_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n# TODO(jiayq): as more and more tests are moving to hypothesis test, we\n# can gradually remove this test script. DO NOT ADD MORE TESTS TO THIS\n# FILE.\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport numpy as np\nfrom caffe2.python import (\n    brew,\n    core,\n    device_checker,\n    gradient_checker,\n    model_helper,\n    test_util,\n    workspace,\n)\nfrom caffe2.python.gradient_checker import NetGradientChecker\nfrom caffe2.python.net_builder import ops, NetBuilder\nfrom caffe2.proto import caffe2_pb2\n\nimport unittest\n\n\nif workspace.has_gpu_support and workspace.NumCudaDevices() > 0:\n    gpu_device_option = caffe2_pb2.DeviceOption()\n    gpu_device_option.device_type = caffe2_pb2.CUDA\n    cpu_device_option = caffe2_pb2.DeviceOption()\n    gpu_device_checker = device_checker.DeviceChecker(\n        0.01, [gpu_device_option]\n    )\n    device_checker = device_checker.DeviceChecker(\n        0.01, [gpu_device_option, cpu_device_option]\n    )\n    gpu_gradient_checkers = [\n        gradient_checker.GradientChecker(\n            0.005, 0.05, gpu_device_option, \"gpu_checker_ws\"\n        ),\n    ]\n    gradient_checkers = [\n        gradient_checker.GradientChecker(\n            0.005, 0.05, gpu_device_option, \"gpu_checker_ws\"\n        ),\n        gradient_checker.GradientChecker(\n            0.01, 0.05, cpu_device_option, \"cpu_checker_ws\"\n        ),\n    ]\nelse:\n    cpu_device_option = caffe2_pb2.DeviceOption()\n    gpu_device_option = None\n    gpu_device_checker = device_checker.DeviceChecker(\n        0.01, []\n    )\n    device_checker = device_checker.DeviceChecker(0.01, [cpu_device_option])\n\n    gradient_checkers = [\n        gradient_checker.GradientChecker(\n            0.01, 0.05, cpu_device_option, \"cpu_checker_ws\"\n        )\n    ]\n    gpu_gradient_checkers = []\n\n\nclass TestLRN(test_util.TestCase):\n\n    def setUp(self):\n        self.test_configs = [(6, 10), (3, 13), ]\n\n    def testLRN(self):\n        for input_size, depth in self.test_configs:\n            op = core.CreateOperator(\"LRN\",\n                                     [\"X\"],\n                                     [\"Y\", \"Y_scale\"],\n                                     size=11,\n                                     alpha=0.001,\n                                     beta=0.5,\n                                     bias=2.0,\n                                     order=\"NHWC\"\n                                     )\n            X = np.random.rand(2, input_size, input_size,\n                               depth).astype(np.float32)\n            res = device_checker.CheckSimple(op, [X], [0])\n            self.assertTrue(res)\n            for checker in gradient_checkers:\n                res, grad, grad_estimated = checker.CheckSimple(op, [X], 0, [0])\n                self.assertTrue(res)\n\n\nclass TestFlatten(test_util.TestCase):\n\n    def testFlatten(self):\n        op = core.CreateOperator(\"Flatten\", [\"X\"], [\"Y\"])\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        res = device_checker.CheckSimple(op, [X], [0])\n        self.assertTrue(res)\n        for checker in gradient_checkers:\n            res, grad, grad_estimated = checker.CheckSimple(op, [X], 0, [0])\n            self.assertTrue(res)\n\n\nclass TestConcat(test_util.TestCase):\n\n    def setUp(self):\n        self.test_configs = [\n            # input_size, depth1, depth2, depth3, depth4\n            (3, 2, 3, 4, 5),\n            (4, 5, 4, 3, 2),\n        ]\n\n    def testConcatNHWC(self):\n        for input_size, d1, d2, d3, d4 in self.test_configs:\n            op = core.CreateOperator(\"Concat\",\n                                     [\"X1\", \"X2\", \"X3\", \"X4\"],\n                                     [\"Y\", \"Y_dims\"],\n                                     order=\"NHWC\"\n                                     )\n            Xs = [\n                np.random.rand(2, input_size, input_size,\n                               d1).astype(np.float32),\n                np.random.rand(2, input_size, input_size,\n                               d2).astype(np.float32),\n                np.random.rand(2, input_size, input_size,\n                               d3).astype(np.float32),\n                np.random.rand(2, input_size, input_size, d4).astype(np.float32)\n            ]\n            for i in range(4):\n                res = device_checker.CheckSimple(op, Xs, [0])\n                self.assertTrue(res)\n                for checker in gradient_checkers:\n                    res, grad, grad_estimated = checker.CheckSimple(op, Xs, i,\n                                                                    [0])\n                    self.assertTrue(res)\n\n    def testConcatNCHW(self):\n        for input_size, d1, d2, d3, d4 in self.test_configs:\n            op = core.CreateOperator(\"Concat\",\n                                     [\"X1\", \"X2\", \"X3\", \"X4\"],\n                                     [\"Y\", \"Y_dims\"],\n                                     order=\"NCHW\"\n                                     )\n            Xs = [\n                np.random.rand(2, d1, input_size,\n                               input_size).astype(np.float32),\n                np.random.rand(2, d2, input_size,\n                               input_size).astype(np.float32),\n                np.random.rand(2, d3, input_size,\n                               input_size).astype(np.float32),\n                np.random.rand(2, d4, input_size, input_size).astype(np.float32)\n            ]\n            for i in range(4):\n                res = device_checker.CheckSimple(op, Xs, [0])\n                self.assertTrue(res)\n                for checker in gradient_checkers:\n                    res, grad, grad_estimated = checker.CheckSimple(op, Xs, i,\n                                                                    [0])\n                    self.assertTrue(res)\n\n\nclass TestRelu(test_util.TestCase):\n\n    def setUp(self):\n        self.test_configs = [\n            # input size\n            # (0, 1),\n            (1, 1),\n            (2, 1),\n            (1, 3, 3, 1),\n            (2, 3, 3, 1),\n            (1, 5, 5, 3),\n            (2, 5, 5, 3),\n        ]\n\n    def testRelu(self):\n        for input_size in self.test_configs:\n            op = core.CreateOperator(\"Relu\", [\"X\"], [\"Y\"])\n            X = np.random.rand(*input_size).astype(np.float32)\n            # go away from the origin point to avoid kink problems\n            X += 0.01 * np.sign(X)\n            X[X == 0] = 0.01\n            res = device_checker.CheckSimple(op, [X], [0])\n            self.assertTrue(res)\n            for checker in gradient_checkers:\n                res, grad, grad_estimated = checker.CheckSimple(op, [X], 0, [0])\n                self.assertTrue(res)\n\n\nclass TestTanh(test_util.TestCase):\n\n    def setUp(self):\n        self.test_configs = [\n            # (0, 1),\n            (1, 1),\n            (2, 1),\n            (1, 2, 3, 4),\n        ]\n\n    def testTanh(self):\n        for input_size in self.test_configs:\n            op = core.CreateOperator(\"Tanh\", [\"X\"], [\"Y\"])\n            X = np.random.rand(*input_size).astype(np.float32) - 0.5\n            res = device_checker.CheckSimple(op, [X], [0])\n            self.assertTrue(res)\n            for checker in gradient_checkers:\n                res, grad, grad_estimated = checker.CheckSimple(op, [X], 0, [0])\n                self.assertTrue(res)\n\n\nclass TestAbs(test_util.TestCase):\n\n    def setUp(self):\n        self.test_configs = [\n            (1, 1),\n            (2, 3),\n            (2, 3, 4),\n            (2, 3, 4, 5),\n        ]\n\n    def testAbs(self):\n        for input_size in self.test_configs:\n            op = core.CreateOperator(\"Abs\", [\"X\"], [\"Y\"])\n            X = np.random.rand(*input_size).astype(np.float32)\n            # go away from the origin point to avoid kink problems\n            X += 0.01 * np.sign(X)\n            X[X == 0] = 0.01\n            res = device_checker.CheckSimple(op, [X], [0])\n            self.assertTrue(res)\n            for checker in gradient_checkers:\n                res, grad, grad_estimated = checker.CheckSimple(op, [X], 0, [0])\n                self.assertTrue(res)\n\nclass TestExp(test_util.TestCase):\n\n    def setUp(self):\n        self.test_configs = [\n            # (0, 1),\n            (1, 1),\n            (2, 1),\n            (1, 2, 3, 4),\n        ]\n\n    def testExp(self):\n        for input_size in self.test_configs:\n            op = core.CreateOperator(\"Exp\", [\"X\"], [\"Y\"])\n            X = np.random.rand(*input_size).astype(np.float32) - 0.5\n            res = device_checker.CheckSimple(op, [X], [0])\n            self.assertTrue(res)\n            for checker in gradient_checkers:\n                res, grad, grad_estimated = checker.CheckSimple(op, [X], 0, [0])\n                self.assertTrue(res)\n\nclass TestCos(test_util.TestCase):\n\n    def setUp(self):\n        self.test_configs = [\n            (1, 1),\n            (2, 3),\n            (2, 3, 4),\n            (2, 3, 4, 5),\n        ]\n\n    def testCos(self):\n        for input_size in self.test_configs:\n            op = core.CreateOperator(\"Cos\", [\"X\"], [\"Y\"])\n            X = np.random.rand(*input_size).astype(np.float32) - 0.5\n            res = device_checker.CheckSimple(op, [X], [0])\n            self.assertTrue(res)\n            for checker in gradient_checkers:\n                res, grad, grad_estimated = checker.CheckSimple(op, [X], 0, [0])\n                self.assertTrue(res)\n\nclass TestSin(test_util.TestCase):\n\n    def setUp(self):\n        self.test_configs = [\n            (1, 1),\n            (2, 3),\n            (2, 3, 4),\n            (2, 3, 4, 5),\n        ]\n\n    def testSin(self):\n        for input_size in self.test_configs:\n            op = core.CreateOperator(\"Sin\", [\"X\"], [\"Y\"])\n            X = np.random.rand(*input_size).astype(np.float32) - 0.5\n            res = device_checker.CheckSimple(op, [X], [0])\n            self.assertTrue(res)\n            for checker in gradient_checkers:\n                res, grad, grad_estimated = checker.CheckSimple(op, [X], 0, [0])\n                self.assertTrue(res)\n\nclass TestSigmoid(test_util.TestCase):\n\n    def setUp(self):\n        self.test_configs = [\n            # (0, 1),\n            (1, 1),\n            (2, 1),\n            (1, 2, 3, 4),\n        ]\n\n    def testSigmoid(self):\n        for input_size in self.test_configs:\n            op = core.CreateOperator(\"Sigmoid\", [\"X\"], [\"Y\"])\n            X = np.random.rand(*input_size).astype(np.float32) - 0.5\n            res = device_checker.CheckSimple(op, [X], [0])\n            self.assertTrue(res)\n            for checker in gradient_checkers:\n                res, grad, grad_estimated = checker.CheckSimple(op, [X], 0, [0])\n                self.assertTrue(res)\n\n\nclass TestSum(test_util.TestCase):\n\n    def setUp(self):\n        self.test_configs = [\n            # ((0, 1), False),\n            ((1, 2, 3, 4), True),\n            ((1, 2, 3, 4), False)]\n\n    def testSum(self):\n        for (input_size, in_place) in self.test_configs:\n            op = core.CreateOperator(\"Sum\", [\"X1\", \"X2\"],\n                                     [\"Y\" if not in_place else \"X1\"])\n            X1 = np.random.rand(*input_size).astype(np.float32) - 0.5\n            X2 = np.random.rand(*input_size).astype(np.float32) - 0.5\n            res = device_checker.CheckSimple(op, [X1, X2], [0])\n            self.assertTrue(res)\n            for checker in gradient_checkers:\n                res, grad, grad_estimated = checker.CheckSimple(\n                    op, [X1, X2], 0, [0])\n                self.assertTrue(res)\n\n\nclass TestMakeTwoClass(test_util.TestCase):\n\n    def setUp(self):\n        self.test_configs = [\n            # input size\n            # (0, 1),\n            (1,),\n            (7,),\n            (1, 3),\n            (2, 5),\n        ]\n\n    def testMakeTwoClass(self):\n        for input_size in self.test_configs:\n            op = core.CreateOperator(\"MakeTwoClass\", [\"X\"], [\"Y\"])\n            X = np.random.rand(*input_size).astype(np.float32)\n            # step a little to avoid gradient problems\n            X[X < 0.01] += 0.01\n            X[X > 0.99] -= 0.01\n            res = device_checker.CheckSimple(op, [X], [0])\n            self.assertTrue(res)\n            for checker in gradient_checkers:\n                res, grad, grad_estimated = checker.CheckSimple(op, [X], 0, [0])\n                self.assertTrue(res)\n\n\nclass TestNetGradientChecker(test_util.TestCase):\n    def test_net_gradient_checker(self):\n        model = model_helper.ModelHelper(name=\"test\")\n        const = model.net.AddExternalInputs(\"const1\", \"const2\")\n        fc = brew.fc(model, dim_in=3, dim_out=4, blob_in=\"X\", blob_out=\"Y\", axis=0)\n        dist = [model.net.SquaredL2Distance([fc, c]) for c in const]\n        losses = [model.net.AveragedLoss(d) for d in dist]  # using two losses here\n\n        workspace.RunNetOnce(model.param_init_net)\n        NetGradientChecker.Check(\n            model.net,\n            outputs_with_grad=losses,\n            input_values={\"X\": np.array([1, 2, 3], dtype=\"float32\"),\n                          const[0]: np.array([1, 1, 1, 1], dtype=\"float32\"),\n                          const[1]: np.array([2, 2, 2, 2], dtype=\"float32\")},\n            input_to_check=\"X\",\n        )\n\n    def test_net_comparison(self):\n        # (a + b) * (c + d) == a * c + a * d + b * c + b * d\n        net1 = core.Net(\"net1\")\n        a, b, c, d = net1.AddExternalInputs(\"a\", \"b\", \"c\", \"d\")\n        a_b = net1.Sum([a, b], \"a+b\")\n        c_d = net1.Sum([c, d], \"c+d\")\n        x = net1.Mul([a_b, c_d], \"x\")\n\n        net2 = core.Net(\"net2\")\n        ac = net2.Mul([a, c], \"ac\")\n        ad = net2.Mul([a, d], \"ad\")\n        bc = net2.Mul([b, c], \"bc\")\n        bd = net2.Mul([b, d], \"bd\")\n        y = net2.Sum([ac, ad, bc, bd], \"y\")\n\n        input_values = {blob: np.array([i], dtype=np.float32)\n                        for i, blob in enumerate([a, b, c, d])}\n\n        NetGradientChecker.CompareNets(\n            [net1, net2], [[x], [y]], [0],\n            inputs_with_grads=[a, b, c, d],\n            input_values=input_values,\n        )\n\n\nclass TestIf(test_util.TestCase):\n    def testIf(self):\n        W_a_values = [2.0, 1.5]\n        B_a_values = [0.5]\n        W_b_values = [7.0, 3.5]\n        B_b_values = [1.5]\n\n        with NetBuilder(_use_control_ops=True) as init_nb:\n            W_a = ops.UniformFill([], \"W_a\", shape=[1, 2], min=-1., max=1.)\n            B_a = ops.ConstantFill([], \"B_a\", shape=[1], value=0.0)\n            W_b = ops.UniformFill([], \"W_b\", shape=[1, 2], min=-1., max=1.)\n            B_b = ops.ConstantFill([], \"B_b\", shape=[1], value=0.0)\n\n            W_gt_a = ops.GivenTensorFill(\n                [], \"W_gt_a\", shape=[1, 2], values=W_a_values)\n            B_gt_a = ops.GivenTensorFill([], \"B_gt_a\", shape=[1], values=B_a_values)\n            W_gt_b = ops.GivenTensorFill(\n                [], \"W_gt_b\", shape=[1, 2], values=W_b_values)\n            B_gt_b = ops.GivenTensorFill([], \"B_gt_b\", shape=[1], values=B_b_values)\n\n        params = [W_gt_a, B_gt_a, W_a, B_a, W_gt_b, B_gt_b, W_b, B_b]\n\n        with NetBuilder(_use_control_ops=True, initial_scope=params) as train_nb:\n            Y_pred = ops.ConstantFill([], \"Y_pred\", shape=[1], value=0.0)\n            Y_noise = ops.ConstantFill([], \"Y_noise\", shape=[1], value=0.0)\n\n            switch = ops.UniformFill(\n                [], \"switch\", shape=[1], min=-1., max=1., run_once=0)\n            zero = ops.ConstantFill([], \"zero\", shape=[1], value=0.0)\n            X = ops.GaussianFill(\n                [], \"X\", shape=[4096, 2], mean=0.0, std=1.0, run_once=0)\n            noise = ops.GaussianFill(\n                [], \"noise\", shape=[4096, 1], mean=0.0, std=1.0, run_once=0)\n\n            with ops.IfNet(ops.LT([switch, zero])):\n                Y_gt = ops.FC([X, W_gt_a, B_gt_a], \"Y_gt\")\n                ops.Add([Y_gt, noise], Y_noise)\n                ops.FC([X, W_a, B_a], Y_pred)\n            with ops.Else():\n                Y_gt = ops.FC([X, W_gt_b, B_gt_b], \"Y_gt\")\n                ops.Add([Y_gt, noise], Y_noise)\n                ops.FC([X, W_b, B_b], Y_pred)\n\n            dist = ops.SquaredL2Distance([Y_noise, Y_pred], \"dist\")\n            loss = dist.AveragedLoss([], [\"loss\"])\n\n        assert len(init_nb.get()) == 1, \"Expected a single init net produced\"\n        assert len(train_nb.get()) == 1, \"Expected a single train net produced\"\n\n        train_net = train_nb.get()[0]\n        gradient_map = train_net.AddGradientOperators([loss])\n\n        init_net = init_nb.get()[0]\n        ITER = init_net.ConstantFill(\n            [], \"ITER\", shape=[1], value=0, dtype=core.DataType.INT32)\n        train_net.Iter(ITER, ITER)\n        LR = train_net.LearningRate(ITER, \"LR\", base_lr=-0.1,\n                                        policy=\"step\", stepsize=20, gamma=0.9)\n        ONE = init_net.ConstantFill([], \"ONE\", shape=[1], value=1.)\n        train_net.WeightedSum([W_a, ONE, gradient_map[W_a], LR], W_a)\n        train_net.WeightedSum([B_a, ONE, gradient_map[B_a], LR], B_a)\n        train_net.WeightedSum([W_b, ONE, gradient_map[W_b], LR], W_b)\n        train_net.WeightedSum([B_b, ONE, gradient_map[B_b], LR], B_b)\n\n        workspace.RunNetOnce(init_net)\n        workspace.CreateNet(train_net)\n        # print(\"Before training, W_a is: {}\".format(workspace.FetchBlob(\"W_a\")))\n        # print(\"Before training, B_a is: {}\".format(workspace.FetchBlob(\"B_a\")))\n        # print(\"Before training, W_b is: {}\".format(workspace.FetchBlob(\"W_b\")))\n        # print(\"Before training, B_b is: {}\".format(workspace.FetchBlob(\"B_b\")))\n\n        for _epoch in range(1000):\n            workspace.RunNet(train_net.Proto().name)\n\n        # print(\"After training, W_a is: {}\".format(workspace.FetchBlob(\"W_a\")))\n        # print(\"After training, B_a is: {}\".format(workspace.FetchBlob(\"B_a\")))\n        # print(\"After training, W_b is: {}\".format(workspace.FetchBlob(\"W_b\")))\n        # print(\"After training, B_b is: {}\".format(workspace.FetchBlob(\"B_b\")))\n        # print(\"Ground truth W_a is: {}\".format(workspace.FetchBlob(\"W_gt_a\")))\n        # print(\"Ground truth B_a is: {}\".format(workspace.FetchBlob(\"B_gt_a\")))\n        # print(\"Ground truth W_b is: {}\".format(workspace.FetchBlob(\"W_gt_b\")))\n        # print(\"Ground truth B_b is: {}\".format(workspace.FetchBlob(\"B_gt_b\")))\n\n        values_map = {\n            \"W_a\": W_a_values,\n            \"B_a\": B_a_values,\n            \"W_b\": W_b_values,\n            \"B_b\": B_b_values,\n        }\n\n        train_eps = 0.01\n\n        for blob_name, values in values_map.items():\n            trained_values = workspace.FetchBlob(blob_name)\n            if trained_values.ndim == 2:\n                self.assertEqual(trained_values.shape[0], 1)\n                trained_values = trained_values[0][:]\n            else:\n                self.assertEqual(trained_values.ndim, 1)\n\n            self.assertEqual(trained_values.size, len(values))\n            for idx in range(len(trained_values)):\n                self.assertTrue(abs(trained_values[idx] - values[idx]) < train_eps)\n\n\nclass TestWhile(test_util.TestCase):\n    def testWhile(self):\n        with NetBuilder(_use_control_ops=True) as nb:\n            ops.Copy(ops.Const(0), \"i\")\n            ops.Copy(ops.Const(1), \"one\")\n            ops.Copy(ops.Const(2), \"two\")\n            ops.Copy(ops.Const(2.0), \"x\")\n            ops.Copy(ops.Const(3.0), \"y\")\n            ops.Copy(ops.Const(2.0), \"z\")\n            # raises x to the power of 4 and y to the power of 2\n            # and z to the power of 3\n            with ops.WhileNet():\n                with ops.Condition():\n                    ops.Add([\"i\", \"one\"], \"i\")\n                    ops.LE([\"i\", \"two\"])\n                ops.Pow(\"x\", \"x\", exponent=2.0)\n                with ops.IfNet(ops.LT([\"i\", \"two\"])):\n                    ops.Pow(\"y\", \"y\", exponent=2.0)\n                with ops.Else():\n                    ops.Pow(\"z\", \"z\", exponent=3.0)\n\n            ops.Add([\"x\", \"y\"], \"x_plus_y\")\n            ops.Add([\"x_plus_y\", \"z\"], \"s\")\n\n        assert len(nb.get()) == 1, \"Expected a single net produced\"\n        net = nb.get()[0]\n\n        net.AddGradientOperators([\"s\"])\n        workspace.RunNetOnce(net)\n        # (x^4)' = 4x^3\n        self.assertAlmostEqual(workspace.FetchBlob(\"x_grad\"), 32)\n        self.assertAlmostEqual(workspace.FetchBlob(\"x\"), 16)\n        # (y^2)' = 2y\n        self.assertAlmostEqual(workspace.FetchBlob(\"y_grad\"), 6)\n        self.assertAlmostEqual(workspace.FetchBlob(\"y\"), 9)\n        # (z^3)' = 3z^2\n        self.assertAlmostEqual(workspace.FetchBlob(\"z_grad\"), 12)\n        self.assertAlmostEqual(workspace.FetchBlob(\"z\"), 8)\n\n\nif __name__ == '__main__':\n    workspace.GlobalInit([\"python\"])\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/gradient_checker.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package gradient_checker\n# Module caffe2.python.gradient_checker\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nfrom caffe2.python import core, workspace, net_drawer\nfrom caffe2.proto import caffe2_pb2\n\n\ndef _get_grad_blob(grad_map, input_to_check):\n    grad_blob = grad_map[input_to_check]\n\n    if isinstance(grad_blob, core.BlobReference):\n        return workspace.blobs[grad_blob]\n\n    # If grad_blob is not a single blob, it should be a gradient slice.\n    # To make it comparable with the estimiated gradient which is dense,\n    # we need to first convert grad_blob to dense gradient.\n    assert isinstance(grad_blob, core.GradientSlice)\n    dense_grad = 'tmp_dense_grad'\n    sparse_to_dense_op = core.CreateOperator(\n        'SparseToDense',\n        [grad_blob.indices, grad_blob.values, input_to_check],\n        dense_grad,\n    )\n    workspace.RunOperatorOnce(sparse_to_dense_op)\n    return workspace.blobs[dense_grad]\n\n\ndef _get_grad(net, outputs, outputs_with_grad, input_values, inputs_with_grads):\n    grad_net = net.Clone(net.Name() + \"_copy\")\n    grad_map = grad_net.AddGradientOperators(outputs_with_grad)\n\n    for name, value in (input_values or {}).items():\n        workspace.blobs[name] = value\n\n    for input_to_check in inputs_with_grads:\n        assert input_to_check in grad_map, (\n            '{} has no gradient, cannot check net gradient.'.format(\n                input_to_check))\n        assert str(input_to_check) in workspace.blobs\n\n    workspace.RunNetOnce(grad_net)\n    forward_results = [(output, workspace.blobs[output]) for output in outputs]\n    grads = {input_to_check: _get_grad_blob(grad_map, input_to_check)\n             for input_to_check in inputs_with_grads}\n\n    return forward_results, grads, grad_net\n\n\ndef _assert_close(value1, value2, threshold, err_msg=''):\n    np.testing.assert_allclose(\n        value1, value2,\n        atol=threshold, rtol=threshold,\n        err_msg=err_msg,\n    )\n\n    delta = np.abs(value1 - value2).flatten()\n    return np.mean(delta), max(delta)\n\n\nclass NetGradientChecker(object):\n    @staticmethod\n    def CompareNets(nets, outputs, outputs_with_grad_ids,\n                    inputs_with_grads, input_values=None,\n                    threshold=0.0000001, print_net_images=False):\n        def _get_output_with_grad_names(net_outputs):\n            return [net_outputs[i] for i in outputs_with_grad_ids]\n\n        if print_net_images:\n            for i, net in enumerate(nets):\n                png = net_drawer.GetPydotGraph(net).create_png()\n                with open(\"caffe2_net_forward_\" + str(i) + net.Name() + \".png\",\n                          'wb') \\\n                     as f:\n                    f.write(png)\n\n        results = [\n            _get_grad(net, net_outputs,\n                      _get_output_with_grad_names(net_outputs),\n                      input_values, inputs_with_grads)\n            for net, net_outputs in zip(nets, outputs)\n        ]\n\n        if print_net_images:\n            _, _, backward_nets = zip(*results)\n            for i, net in enumerate(backward_nets):\n                png = net_drawer.GetPydotGraph(net).create_png()\n                with open(\"caffe2_net_\" + str(i) + net.Name() + \".png\", 'wb') \\\n                     as f:\n                    f.write(png)\n\n        first_net_results, first_net_grads, _ = results[0]\n        for net_results, net_grads, _ in results[1:]:\n            assert len(net_results) == len(first_net_results)\n            for idx, ((blob1, blob_value1), (blob2, blob_value2)) in enumerate(\n                    zip(first_net_results, net_results)):\n                _assert_close(\n                    blob_value1, blob_value2, threshold,\n                    err_msg=\"Different forward pass results for output id {}. \"\n                    \"Corresponding output blobs: {} and {}\".format(\n                        idx, blob1, blob2))\n\n            assert net_grads.keys() == first_net_grads.keys()\n            for blob, blob_grad_value in net_grads.items():\n                _assert_close(\n                    first_net_grads[blob], blob_grad_value, threshold,\n                    err_msg=\"Different gradients for input {}\".format(blob))\n\n    @staticmethod\n    def Check(net, outputs_with_grad, input_values,\n              input_to_check, step_size=0.0001,\n              threshold=0.05, print_net=True):\n\n        net_results, net_grads, full_net = _get_grad(\n            net, [], outputs_with_grad, input_values, [input_to_check])\n        analytic_grad = net_grads[input_to_check]\n\n        def GetLoss(new_value):\n            workspace.blobs[input_to_check] = new_value\n            workspace.RunNetOnce(full_net)\n            return sum([\n                workspace.blobs[output]\n                for output in outputs_with_grad\n            ]).sum()\n\n        def GetValue(dim, delta):\n            input_value = input_values[input_to_check].copy()\n            input_value.flat[dim] += delta\n            return input_value\n\n        grad_estimate = np.zeros_like(input_values[input_to_check])\n        for dim in range(input_values[input_to_check].size):\n            pos_loss = GetLoss(GetValue(dim, step_size))\n            neg_loss = GetLoss(GetValue(dim, -step_size))\n            grad_estimate.flat[dim] = (pos_loss - neg_loss) / step_size / 2\n\n        err_msg = \"Error in gradient check for net_copy {}\".format(\n            net.Name())\n        if print_net:\n            err_msg += \": {}\".format(net.Proto())\n\n        return _assert_close(analytic_grad, grad_estimate, threshold, err_msg)\n\nclass GradientChecker:\n    \"\"\"A gradient checker in Python.\n\n    This is not the most efficient way to check gradients, as the Python\n    interface will involve a lot of copies back and forth operations. Use at your\n    own risk.\n    \"\"\"\n\n    def __init__(\n        self,\n        stepsize,\n        threshold,\n        device_option=caffe2_pb2.DeviceOption(),\n        workspace_name=\"gradient_check\"\n    ):\n        self._stepsize = stepsize\n        self._threshold = threshold\n        self._device_option = device_option\n        self._workspace_name = workspace_name\n\n    def GetLossAndGrad(\n        self, op, grad_ops, x, input_name, grad_name, outputs_with_grads\n    ):\n        # First, feed in the current input. Note that we are not changing\n        # anything else, so we don't need to feed in others.\n        workspace.FeedBlob(input_name, x, self._device_option)\n        # Run.\n        workspace.RunOperatorOnce(op)\n        loss = 0.\n        # Get Loss and feed in the gradients, run gradient ops.\n        for idx in outputs_with_grads:\n            name = op.output[idx]\n            arr = workspace.FetchBlob(name)\n            loss += (arr**2).sum()\n            workspace.FeedBlob(name + '_grad', arr, self._device_option)\n        loss /= 2.\n        # Run gradient ops\n        workspace.RunOperatorsOnce(grad_ops)\n        # Get gradients\n        if isinstance(grad_name, core.GradientSlice):\n            workspace.FeedBlob('zeros', np.zeros_like(x, dtype=np.float32))\n            workspace.FeedBlob('ones', np.ones(1, dtype=np.float32))\n            gv_cpu_op = core.CreateOperator(\n                'EnsureCPUOutput', grad_name.values, grad_name.values + '_cpu',\n                device_option=self._device_option\n            )\n            gi_cpu_op = core.CreateOperator(\n                'EnsureCPUOutput', grad_name.indices, grad_name.indices + '_cpu',\n                device_option=self._device_option\n            )\n            sparse_to_dense_op = core.CreateOperator(\n                'ScatterWeightedSum',\n                [\n                    'zeros', 'ones', grad_name.indices + '_cpu',\n                    grad_name.values + '_cpu', 'ones'\n                ],\n                'zeros',\n            )\n            workspace.RunOperatorOnce(gv_cpu_op)\n            workspace.RunOperatorOnce(gi_cpu_op)\n            workspace.RunOperatorOnce(sparse_to_dense_op)\n            grad = workspace.FetchBlob('zeros')\n        else:\n            grad = workspace.FetchBlob(grad_name)\n        return loss, grad\n\n    def CheckSimple(\n        self,\n        op,\n        inputs,\n        input_to_check,\n        outputs_with_grads,\n        grad_ops=None,\n        input_device_options=None\n    ):\n        \"\"\"Checks the operator in a very simple fashion by stacking a sum of\n        squares on the top.\n\n        Inputs:\n          op: the operator to be checked.\n          inputs: the input data in numpy arrays.\n          input_to_check: an index specifying which input blob we should\n              check.\n          outputs_with_grads: indices specifying which output blobs will we\n              need to check gradients with. For these outputs, we will collect a\n              squared sum and also feed in their gradients.\n          grad_operator: the gradient operator. If not given, we will get the\n              gradient operator from the gradient registry.\n          input_device_options: an optional mapping from input names to\n              DeviceOptions (to override the default DeviceOption)\n        Outputs:\n          boolean: True if it passes, False if it does not pass.\n        \"\"\"\n        if input_device_options is None:\n            input_device_options = {}\n        # Entering the checker workspace\n        old_ws_name = workspace.CurrentWorkspace()\n        if self._workspace_name != old_ws_name:\n            workspace.SwitchWorkspace(self._workspace_name, True)\n\n        op.device_option.CopyFrom(self._device_option)\n        if grad_ops is None:\n            # TODO(jiayq): use the gradient registration instead of the old\n            # hack.\n            grad_ops, g_input = core.GradientRegistry.GetGradientForOp(\n                op, [s + '_grad' for s in op.output])\n\n        dims_to_check = inputs[input_to_check].size\n        # First, feed in the input.\n        for i, arr in enumerate(inputs):\n            workspace.FeedBlob(\n                op.input[i], arr,\n                input_device_options.get(\n                    op.input[i], self._device_option))\n\n        # Get the loss and gradient for the original.\n        input_name = op.input[input_to_check]\n        grad_name = g_input[input_to_check]\n        loss, grad = self.GetLossAndGrad(\n            op, grad_ops, inputs[input_to_check], input_name, grad_name,\n            outputs_with_grads\n        )\n        grad_estimate = np.zeros_like(inputs[input_to_check])\n        if grad_estimate.shape != grad.shape:\n            raise Exception(\n                \"Mismatched gradient shapes: estimated ({}), grad ({})\".format(\n                    grad_estimate.shape, grad.shape))\n\n        for current_dim in range(dims_to_check):\n            # Positive gradient\n            inputs[input_to_check].flat[current_dim] += self._stepsize\n            pos_loss, _ = self.GetLossAndGrad(\n                op, grad_ops, inputs[input_to_check], input_name,\n                grad_name, outputs_with_grads\n            )\n            # Negative gradient\n            inputs[input_to_check].flat[current_dim] -= self._stepsize * 2\n            neg_loss, _ = self.GetLossAndGrad(\n                op, grad_ops, inputs[input_to_check], input_name,\n                grad_name, outputs_with_grads\n            )\n            # Recover the value\n            inputs[input_to_check].flat[current_dim] += self._stepsize\n            grad_estimate.flat[current_dim] = (\n                pos_loss - neg_loss) / self._stepsize / 2\n        # Now, check correctness\n        fail_mat = ~np.isclose(\n            grad, grad_estimate, atol=self._threshold, rtol=self._threshold)\n        if np.any(fail_mat):\n            idx = np.flatnonzero(fail_mat)\n            print('Failed. [idx, grad, grad_estimate] are:')\n            print(np.vstack([idx, grad.flat[idx], grad_estimate.flat[idx]]).T)\n            ret = False\n        else:\n            ret = True\n        # After finishing, cleaning up things.\n        if self._workspace_name != old_ws_name:\n            # We reset the workspace to make sure everything intermediate is\n            # cleaned up. Note that there is no need to delete a workspace -\n            # when empty it takes a very limited amount of memory.\n            workspace.ResetWorkspace()\n            workspace.SwitchWorkspace(old_ws_name)\n        return ret, grad, grad_estimate\n"
  },
  {
    "path": "caffe2/python/gru_cell.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport functools\nfrom caffe2.python import brew, rnn_cell\n\n\nclass GRUCell(rnn_cell.RNNCell):\n\n    def __init__(\n        self,\n        input_size,\n        hidden_size,\n        forget_bias,  # Currently unused!  Values here will be ignored.\n        memory_optimization,\n        drop_states=False,\n        linear_before_reset=False,\n        **kwargs\n    ):\n        super(GRUCell, self).__init__(**kwargs)\n        self.input_size = input_size\n        self.hidden_size = hidden_size\n        self.forget_bias = float(forget_bias)\n        self.memory_optimization = memory_optimization\n        self.drop_states = drop_states\n        self.linear_before_reset = linear_before_reset\n\n    # Unlike LSTMCell, GRUCell needs the output of one gate to feed into another.\n    # (reset gate -> output_gate)\n    # So, much of the logic to calculate the reset gate output and modified\n    # output gate input is set here, in the graph definition.\n    # The remaining logic lives in in gru_unit_op.{h,cc}.\n    def _apply(\n        self,\n        model,\n        input_t,\n        seq_lengths,\n        states,\n        timestep,\n        extra_inputs=None,\n    ):\n        hidden_t_prev = states[0]\n\n        # Split input tensors to get inputs for each gate.\n        input_t_reset, input_t_update, input_t_output = model.net.Split(\n            [\n                input_t,\n            ],\n            [\n                self.scope('input_t_reset'),\n                self.scope('input_t_update'),\n                self.scope('input_t_output'),\n            ],\n            axis=2,\n        )\n\n        # Fully connected layers for reset and update gates.\n        reset_gate_t = brew.fc(\n            model,\n            hidden_t_prev,\n            self.scope('reset_gate_t'),\n            dim_in=self.hidden_size,\n            dim_out=self.hidden_size,\n            axis=2,\n        )\n        update_gate_t = brew.fc(\n            model,\n            hidden_t_prev,\n            self.scope('update_gate_t'),\n            dim_in=self.hidden_size,\n            dim_out=self.hidden_size,\n            axis=2,\n        )\n\n        # Calculating the modified hidden state going into output gate.\n        reset_gate_t = model.net.Sum(\n            [reset_gate_t, input_t_reset],\n            self.scope('reset_gate_t')\n        )\n        reset_gate_t_sigmoid = model.net.Sigmoid(\n            reset_gate_t,\n            self.scope('reset_gate_t_sigmoid')\n        )\n\n        # `self.linear_before_reset = True` matches cudnn semantics\n        if self.linear_before_reset:\n            output_gate_fc = brew.fc(\n                model,\n                hidden_t_prev,\n                self.scope('output_gate_t'),\n                dim_in=self.hidden_size,\n                dim_out=self.hidden_size,\n                axis=2,\n            )\n            output_gate_t = model.net.Mul(\n                [reset_gate_t_sigmoid, output_gate_fc],\n                self.scope('output_gate_t_mul')\n            )\n        else:\n            modified_hidden_t_prev = model.net.Mul(\n                [reset_gate_t_sigmoid, hidden_t_prev],\n                self.scope('modified_hidden_t_prev')\n            )\n            output_gate_t = brew.fc(\n                model,\n                modified_hidden_t_prev,\n                self.scope('output_gate_t'),\n                dim_in=self.hidden_size,\n                dim_out=self.hidden_size,\n                axis=2,\n            )\n\n        # Add input contributions to update and output gate.\n        # We already (in-place) added input contributions to the reset gate.\n        update_gate_t = model.net.Sum(\n            [update_gate_t, input_t_update],\n            self.scope('update_gate_t'),\n        )\n        output_gate_t = model.net.Sum(\n            [output_gate_t, input_t_output],\n            self.scope('output_gate_t_summed'),\n        )\n\n        # Join gate outputs and add input contributions\n        gates_t, _gates_t_concat_dims = model.net.Concat(\n            [\n                reset_gate_t,\n                update_gate_t,\n                output_gate_t,\n            ],\n            [\n                self.scope('gates_t'),\n                self.scope('_gates_t_concat_dims'),\n            ],\n            axis=2,\n        )\n\n        if seq_lengths is not None:\n            inputs = [hidden_t_prev, gates_t, seq_lengths, timestep]\n        else:\n            inputs = [hidden_t_prev, gates_t, timestep]\n\n        hidden_t = model.net.GRUUnit(\n            inputs,\n            list(self.get_state_names()),\n            forget_bias=self.forget_bias,\n            drop_states=self.drop_states,\n            sequence_lengths=(seq_lengths is not None),\n        )\n        model.net.AddExternalOutputs(hidden_t)\n        return (hidden_t,)\n\n    def prepare_input(self, model, input_blob):\n        return brew.fc(\n            model,\n            input_blob,\n            self.scope('i2h'),\n            dim_in=self.input_size,\n            dim_out=3 * self.hidden_size,\n            axis=2,\n        )\n\n    def get_state_names(self):\n        return (self.scope('hidden_t'),)\n\n    def get_output_dim(self):\n        return self.hidden_size\n\n\nGRU = functools.partial(rnn_cell._LSTM, GRUCell)\n"
  },
  {
    "path": "caffe2/python/helpers/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/python/helpers/algebra.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package algebra\n# Module caffe2.python.helpers.algebra\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n\ndef transpose(model, blob_in, blob_out, use_cudnn=False, **kwargs):\n    \"\"\"Transpose.\"\"\"\n    if use_cudnn:\n        kwargs['engine'] = 'CUDNN'\n    return model.net.Transpose(blob_in, blob_out, **kwargs)\n\n\ndef sum(model, blob_in, blob_out, **kwargs):\n    \"\"\"Sum\"\"\"\n    return model.net.Sum(blob_in, blob_out, **kwargs)\n\n\ndef batch_mat_mul(model, blob_in, blob_out,\n                  enable_tensor_core=False, **kwargs):\n    if enable_tensor_core:\n        kwargs['engine'] = 'TENSORCORE'\n\n    return model.net.BatchMatMul(blob_in, blob_out, **kwargs)\n"
  },
  {
    "path": "caffe2/python/helpers/arg_scope.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nimport contextlib\nimport copy\nimport threading\n\n_threadlocal_scope = threading.local()\n\n\n@contextlib.contextmanager\ndef arg_scope(single_helper_or_list, **kwargs):\n    global _threadlocal_scope\n    if not isinstance(single_helper_or_list, list):\n        assert callable(single_helper_or_list), \\\n            \"arg_scope is only supporting single or a list of helper functions.\"\n        single_helper_or_list = [single_helper_or_list]\n    old_scope = copy.deepcopy(get_current_scope())\n    for helper in single_helper_or_list:\n        assert callable(helper), \\\n            \"arg_scope is only supporting a list of callable helper functions.\"\n        helper_key = helper.__name__\n        if helper_key not in old_scope:\n            _threadlocal_scope.current_scope[helper_key] = {}\n        _threadlocal_scope.current_scope[helper_key].update(kwargs)\n\n    yield\n    _threadlocal_scope.current_scope = old_scope\n\n\ndef get_current_scope():\n    global _threadlocal_scope\n    if not hasattr(_threadlocal_scope, \"current_scope\"):\n        _threadlocal_scope.current_scope = {}\n    return _threadlocal_scope.current_scope\n"
  },
  {
    "path": "caffe2/python/helpers/array_helpers.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package arra_helpers\n# Module caffe2.python.helpers.array_helpers\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n\ndef concat(model, blobs_in, blob_out, **kwargs):\n    \"\"\"Depth Concat.\"\"\"\n    if kwargs.get('order') and kwargs.get('axis'):\n        # The backend throws an error if both are given\n        kwargs.pop('order')\n\n    return model.net.Concat(\n        blobs_in,\n        [blob_out, \"_\" + blob_out + \"_concat_dims\"],\n        **kwargs\n    )[0]\n\n\ndef depth_concat(model, blobs_in, blob_out, **kwargs):\n    \"\"\"The old depth concat function - we should move to use concat.\"\"\"\n    print(\"DepthConcat is deprecated. use Concat instead.\")\n    return concat(blobs_in, blob_out, **kwargs)\n"
  },
  {
    "path": "caffe2/python/helpers/control_ops.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package control_ops\n# Module caffe2.python.helpers.control_ops\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python.control_ops_util import add_if_op, add_while_op\n\n\ndef cond(model, cond_blob, external_blobs, then_model, else_model=None):\n    \"\"\"Condition\"\"\"\n    add_if_op(\n        model.net,\n        cond_blob,\n        external_blobs,\n        then_model.net,\n        else_model.net if else_model else None)\n\n\ndef loop(model, cond_blob, external_blobs, loop_model, cond_model=None):\n    \"\"\"Loop\"\"\"\n    add_while_op(\n        model.net,\n        cond_blob,\n        external_blobs,\n        loop_model.net,\n        cond_model.net if cond_model else None)\n"
  },
  {
    "path": "caffe2/python/helpers/conv.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package conv\n# Module caffe2.python.helpers.conv\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom caffe2.python.modeling import initializers\nfrom caffe2.python.modeling.parameter_info import ParameterTags\n\ndef _ConvBase(\n    model,\n    is_nd,\n    blob_in,\n    blob_out,\n    dim_in,\n    dim_out,\n    kernel,\n    weight_init=None,\n    bias_init=None,\n    WeightInitializer=None,\n    BiasInitializer=None,\n    group=1,\n    transform_inputs=None,\n    use_cudnn=False,\n    order=\"NCHW\",\n    cudnn_exhaustive_search=False,\n    ws_nbytes_limit=None,\n    float16_compute=False,\n    **kwargs\n):\n    kernels = []\n    if is_nd:\n        if not isinstance(kernel, list):\n            kernels = [kernel]\n        else:\n            kernels = kernel\n    else:\n        if isinstance(kernel, list):\n            assert len(kernel) == 2, \"Conv support only a 2D kernel.\"\n            kernels = kernel\n        else:\n            kernels = [kernel] * 2\n\n    requested_engine = kwargs.get('engine')\n    if requested_engine is not None:\n        if use_cudnn and requested_engine != 'CUDNN':\n            raise ValueError(\n                'When use_cudnn=True, the only engine you can specify is '\n                '\"CUDNN\"')\n        elif not use_cudnn and requested_engine == 'CUDNN':\n            raise ValueError(\n                'When use_cudnn=False, the only engine you can specify is '\n                '\"\"')\n\n    if use_cudnn:\n        kwargs['engine'] = 'CUDNN'\n        kwargs['exhaustive_search'] = cudnn_exhaustive_search\n        if ws_nbytes_limit:\n            kwargs['ws_nbytes_limit'] = ws_nbytes_limit\n\n    use_bias =\\\n            False if (\"no_bias\" in kwargs and kwargs[\"no_bias\"]) else True\n    blob_out = blob_out or model.net.NextName()\n    weight_shape = [dim_out]\n    if order == \"NCHW\":\n        weight_shape.append(int(dim_in / group))\n        weight_shape.extend(kernels)\n    else:\n        weight_shape.extend(kernels)\n        weight_shape.append(int(dim_in / group))\n\n    WeightInitializer = initializers.update_initializer(\n        WeightInitializer, weight_init, (\"XavierFill\", {})\n    )\n    BiasInitializer = initializers.update_initializer(\n        BiasInitializer, bias_init, (\"ConstantFill\", {})\n    )\n    if not model.init_params:\n        WeightInitializer = initializers.ExternalInitializer()\n        BiasInitializer = initializers.ExternalInitializer()\n\n    weight = model.create_param(\n        param_name=blob_out + '_w',\n        shape=weight_shape,\n        initializer=WeightInitializer,\n        tags=ParameterTags.WEIGHT\n    )\n    if use_bias:\n        bias = model.create_param(\n            param_name=blob_out + '_b',\n            shape=[dim_out, ],\n            initializer=BiasInitializer,\n            tags=ParameterTags.BIAS\n        )\n\n    if use_bias:\n        inputs = [blob_in, weight, bias]\n    else:\n        inputs = [blob_in, weight]\n\n    if transform_inputs is not None:\n        transform_inputs(model, blob_out, inputs)\n\n    # Enable float 16 compute kernel (relevant for CUDA)\n    if float16_compute:\n        kwargs['float16_compute'] = True\n\n    # For the operator, we no longer need to provide the no_bias field\n    # because it can automatically figure this out from the number of\n    # inputs.\n    if 'no_bias' in kwargs:\n        del kwargs['no_bias']\n    if group != 1:\n        kwargs['group'] = group\n    if is_nd:\n        return model.net.Conv(\n            inputs,\n            blob_out,\n            kernels=kernels,\n            order=order,\n            **kwargs)\n    else:\n        if isinstance(kernel, list):\n            return model.net.Conv(\n                inputs,\n                blob_out,\n                kernel_h=kernel[0],\n                kernel_w=kernel[1],\n                order=order,\n                **kwargs)\n        else:\n            return model.net.Conv(\n                inputs,\n                blob_out,\n                kernel=kernel,\n                order=order,\n                **kwargs)\n\n\n\ndef conv_nd(\n    model,\n    blob_in,\n    blob_out,\n    dim_in,\n    dim_out,\n    kernel,\n    weight_init=None,\n    bias_init=None,\n    WeightInitializer=None,\n    BiasInitializer=None,\n    group=1,\n    transform_inputs=None,\n    order=\"NCHW\",\n    **kwargs\n):\n    \"\"\"N-dimensional convolution for inputs with NCHW storage order.\n    \"\"\"\n    assert order == \"NCHW\", \"ConvNd only supported for NCHW storage.\"\n    return _ConvBase(model, True, blob_in, blob_out, dim_in, dim_out, kernel,\n                     weight_init, bias_init, WeightInitializer, BiasInitializer,\n                     group, transform_inputs, order=order, **kwargs)\n\n\ndef conv(\n    model,\n    blob_in,\n    blob_out,\n    dim_in,\n    dim_out,\n    kernel,\n    weight_init=None,\n    bias_init=None,\n    WeightInitializer=None,\n    BiasInitializer=None,\n    group=1,\n    transform_inputs=None,\n    **kwargs\n):\n    \"\"\"2-dimensional convolution.\n    \"\"\"\n    return _ConvBase(model, False, blob_in, blob_out, dim_in, dim_out, kernel,\n                     weight_init, bias_init, WeightInitializer, BiasInitializer,\n                     group, transform_inputs, **kwargs)\n\n\ndef conv_transpose(\n    model,\n    blob_in,\n    blob_out,\n    dim_in,\n    dim_out,\n    kernel,\n    weight_init=None,\n    bias_init=None,\n    use_cudnn=False,\n    order=\"NCHW\",\n    cudnn_exhaustive_search=False,\n    ws_nbytes_limit=None,\n    **kwargs\n):\n    \"\"\"ConvTranspose.\n    \"\"\"\n    weight_init = weight_init if weight_init else ('XavierFill', {})\n    bias_init = bias_init if bias_init else ('ConstantFill', {})\n    blob_out = blob_out or model.net.NextName()\n    weight_shape = (\n        [dim_in, dim_out, kernel, kernel]\n        if order == \"NCHW\" else [dim_in, kernel, kernel, dim_out]\n    )\n    if model.init_params:\n        weight = model.param_init_net.__getattr__(weight_init[0])(\n            [],\n            blob_out + '_w',\n            shape=weight_shape,\n            **weight_init[1]\n        )\n        bias = model.param_init_net.__getattr__(bias_init[0])(\n            [],\n            blob_out + '_b',\n            shape=[dim_out, ],\n            **bias_init[1]\n        )\n    else:\n        weight = core.ScopedBlobReference(\n            blob_out + '_w', model.param_init_net)\n        bias = core.ScopedBlobReference(\n            blob_out + '_b', model.param_init_net)\n    model.AddParameter(weight, ParameterTags.WEIGHT)\n    model.AddParameter(bias, ParameterTags.BIAS)\n    if use_cudnn:\n        kwargs['engine'] = 'CUDNN'\n        kwargs['exhaustive_search'] = cudnn_exhaustive_search\n        if ws_nbytes_limit:\n            kwargs['ws_nbytes_limit'] = ws_nbytes_limit\n    return model.net.ConvTranspose(\n        [blob_in, weight, bias],\n        blob_out,\n        kernel=kernel,\n        order=order,\n        **kwargs\n    )\n\n\ndef group_conv(\n    model,\n    blob_in,\n    blob_out,\n    dim_in,\n    dim_out,\n    kernel,\n    weight_init=None,\n    bias_init=None,\n    group=1,\n    **kwargs\n):\n    \"\"\"Group Convolution.\n\n    This is essentially the same as Conv with a group argument passed in.\n    We specialize this for backward interface compatibility.\n    \"\"\"\n    return conv(model, blob_in, blob_out, dim_in, dim_out, kernel,\n                weight_init=weight_init, bias_init=bias_init,\n                group=group, **kwargs)\n\n\ndef group_conv_deprecated(\n    model,\n    blob_in,\n    blob_out,\n    dim_in,\n    dim_out,\n    kernel,\n    weight_init=None,\n    bias_init=None,\n    group=1,\n    use_cudnn=False,\n    order=\"NCHW\",\n    cudnn_exhaustive_search=False,\n    ws_nbytes_limit=None,\n    **kwargs\n):\n    \"\"\"GroupConvolution's deprecated interface.\n\n    This is used to simulate a group convolution via split and concat. You\n    should always use the new group convolution in your new code.\n    \"\"\"\n    weight_init = weight_init if weight_init else ('XavierFill', {})\n    bias_init = bias_init if bias_init else ('ConstantFill', {})\n    use_bias = False if (\"no_bias\" in kwargs and kwargs[\"no_bias\"]) else True\n    if use_cudnn:\n        kwargs['engine'] = 'CUDNN'\n        kwargs['exhaustive_search'] = cudnn_exhaustive_search\n        if ws_nbytes_limit:\n            kwargs['ws_nbytes_limit'] = ws_nbytes_limit\n            if dim_in % group:\n                raise ValueError(\"dim_in should be divisible by group.\")\n    if dim_out % group:\n        raise ValueError(\"dim_out should be divisible by group.\")\n    splitted_blobs = model.net.DepthSplit(\n        blob_in,\n        ['_' + blob_out + '_gconv_split_' + str(i) for i in range(group)],\n        dimensions=[int(dim_in / group) for i in range(group)],\n        order=order\n    )\n    weight_shape = (\n        [dim_out / group, dim_in / group, kernel, kernel]\n        if order == \"NCHW\" else\n        [dim_out / group, kernel, kernel, dim_in / group]\n    )\n    # Make sure that the shapes are of int format. Especially for py3 where\n    # int division gives float output.\n    weight_shape = [int(v) for v in weight_shape]\n    conv_blobs = []\n    for i in range(group):\n        if model.init_params:\n            weight = model.param_init_net.__getattr__(weight_init[0])(\n                [],\n                blob_out + '_gconv_%d_w' % i,\n                shape=weight_shape,\n                **weight_init[1]\n            )\n            if use_bias:\n                bias = model.param_init_net.__getattr__(bias_init[0])(\n                    [],\n                    blob_out + '_gconv_%d_b' % i,\n                    shape=[int(dim_out / group)],\n                    **bias_init[1]\n                )\n        else:\n            weight = core.ScopedBlobReference(\n                blob_out + '_gconv_%d_w' % i, model.param_init_net)\n            if use_bias:\n                bias = core.ScopedBlobReference(\n                    blob_out + '_gconv_%d_b' % i, model.param_init_net)\n        model.AddParameter(weight, ParameterTags.WEIGHT)\n        if use_bias:\n            model.AddParameter(bias, ParameterTags.BIAS)\n        if use_bias:\n            inputs = [weight, bias]\n        else:\n            inputs = [weight]\n        if 'no_bias' in kwargs:\n            del kwargs['no_bias']\n        conv_blobs.append(\n            splitted_blobs[i].Conv(\n                inputs,\n                blob_out + '_gconv_%d' % i,\n                kernel=kernel,\n                order=order,\n                **kwargs\n            )\n        )\n    concat, concat_dims = model.net.Concat(\n        conv_blobs,\n        [blob_out,\n         \"_\" + blob_out + \"_concat_dims\"],\n        order=order\n    )\n    return concat\n"
  },
  {
    "path": "caffe2/python/helpers/db_input.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package db_input\n# Module caffe2.python.helpers.db_input\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\ndef db_input(model, blobs_out, batch_size, db, db_type):\n    dbreader_name = \"dbreader_\" + db\n    dbreader = model.param_init_net.CreateDB(\n        [],\n        dbreader_name,\n        db=db,\n        db_type=db_type,\n    )\n    return model.net.TensorProtosDBInput(\n        dbreader, blobs_out, batch_size=batch_size)\n"
  },
  {
    "path": "caffe2/python/helpers/dropout.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package dropout\n# Module caffe2.python.helpers.dropout\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n\ndef dropout(model, blob_in, blob_out, use_cudnn=False, **kwargs):\n    \"\"\"dropout\"\"\"\n    if use_cudnn:\n        kwargs['engine'] = 'CUDNN'\n    else:\n        kwargs['engine'] = 'DEFAULT'\n    assert 'is_test' in kwargs, \"Argument 'is_test' is required\"\n    return model.net.Dropout(\n        blob_in, [blob_out, \"_\" + blob_out + \"_mask\"], **kwargs)[0]\n"
  },
  {
    "path": "caffe2/python/helpers/elementwise_linear.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package elementwise_linear\n# Module caffe2.python.helpers.elementwise_linear\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom caffe2.python.modeling.parameter_info import ParameterTags\n\n\ndef _elementwise_linear(\n    model, op_call, blob_in, blob_out, dim,\n    weight_init=None, bias_init=None, **kwargs\n):\n    \"\"\"Elementwise_Linear\"\"\"\n    weight_init = weight_init or ('ConstantFill', {'value': 1.0})\n    bias_init = bias_init or ('ConstantFill', {'value': 0.0})\n    blob_out = blob_out or model.net.NextName()\n    if model.init_params:\n        weight = model.param_init_net.__getattr__(weight_init[0])(\n            [],\n            blob_out + '_w',\n            shape=[dim],\n            **weight_init[1]\n        )\n        bias = model.param_init_net.__getattr__(bias_init[0])(\n            [],\n            blob_out + '_b',\n            shape=[dim],\n            **bias_init[1]\n        )\n    else:\n        weight = core.ScopedBlobReference(\n            blob_out + '_w', model.param_init_net)\n        bias = core.ScopedBlobReference(\n            blob_out + '_b', model.param_init_net)\n\n    model.AddParameter(weight, ParameterTags.WEIGHT)\n    model.AddParameter(bias, ParameterTags.BIAS)\n    return op_call([blob_in, weight, bias], blob_out, **kwargs)\n\n\ndef elementwise_linear(model, *args, **kwargs):\n    return _elementwise_linear(\n        model, model.net.ElementwiseLinear, *args, **kwargs)\n"
  },
  {
    "path": "caffe2/python/helpers/fc.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package fc\n# Module caffe2.python.helpers.fc\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom caffe2.python.modeling import initializers\nfrom caffe2.python.modeling.parameter_info import ParameterTags\n\n\ndef _FC_or_packed_FC(\n    model, op_call, blob_in, blob_out, dim_in, dim_out, weight_init=None,\n        bias_init=None, WeightInitializer=None, BiasInitializer=None,\n        enable_tensor_core=False, float16_compute=False, **kwargs\n):\n    WeightInitializer = initializers.update_initializer(\n        WeightInitializer, weight_init, (\"XavierFill\", {})\n    )\n    BiasInitializer = initializers.update_initializer(\n        BiasInitializer, bias_init, (\"ConstantFill\", {})\n    )\n    if not model.init_params:\n        WeightInitializer = initializers.ExternalInitializer()\n        BiasInitializer = initializers.ExternalInitializer()\n\n    blob_out = blob_out or model.net.NextName()\n    bias_tags = [ParameterTags.BIAS]\n    if 'freeze_bias' in kwargs:\n        bias_tags.append(ParameterTags.COMPUTED_PARAM)\n\n    weight = model.create_param(\n        param_name=blob_out + '_w',\n        shape=[dim_out, dim_in],\n        initializer=WeightInitializer,\n        tags=ParameterTags.WEIGHT\n    )\n    bias = model.create_param(\n        param_name=blob_out + '_b',\n        shape=[dim_out, ],\n        initializer=BiasInitializer,\n        tags=bias_tags\n    )\n\n    # enable TensorCore by setting appropriate engine\n    if enable_tensor_core:\n        kwargs['engine'] = 'TENSORCORE'\n\n    # Enable float 16 compute kernel (relevant for CUDA)\n    if float16_compute:\n        kwargs['float16_compute'] = True\n\n    return op_call([blob_in, weight, bias], blob_out, **kwargs)\n\n\ndef fc(model, *args, **kwargs):\n    return _FC_or_packed_FC(model, model.net.FC, *args, **kwargs)\n\n\ndef packed_fc(model, *args, **kwargs):\n    return _FC_or_packed_FC(model, model.net.PackedFC, *args, **kwargs)\n\n\ndef fc_decomp(\n    model, blob_in, blob_out, dim_in, dim_out,\n    rank_approx=5, weight_init=None, bias_init=None,\n    WeightInitializer=None, BiasInitializer=None, **kwargs\n):\n    \"\"\"FC_Decomp version\n    Here we assume that the rank of original input is bigger than 5.\n    \"\"\"\n    WeightInitializer = initializers.update_initializer(\n        WeightInitializer, weight_init, (\"XavierFill\", {})\n    )\n    BiasInitializer = initializers.update_initializer(\n        BiasInitializer, bias_init, (\"ConstantFill\", {})\n    )\n    blob_out = blob_out or model.net.NextName()\n    u = model.create_param(\n        param_name=blob_out + '_u',\n        shape=[dim_out, rank_approx],\n        initializer=WeightInitializer,\n    )\n    v = model.create_param(\n        param_name=blob_out + '_v',\n        shape=[dim_in, rank_approx],\n        initializer=WeightInitializer,\n    )\n    bias = model.create_param(\n        param_name=blob_out + '_b',\n        shape=[dim_out, ],\n        initializer=BiasInitializer,\n    )\n    return model.net.FC_Decomp([blob_in, u, v, bias], blob_out, **kwargs)\n\n\ndef fc_prune(\n    model, blob_in, blob_out, dim_in, dim_out,\n    weight_init=None, bias_init=None, mask_init=None,\n    threshold=0.00001, need_compress_rate=False,\n    comp_lb=0.05,\n    **kwargs\n):\n    \"\"\"FC_Prune version\n    Runnable so far. Great!:)\n    \"\"\"\n    weight_init = weight_init if weight_init else ('XavierFill', {})\n    bias_init = bias_init if bias_init else ('ConstantFill', {})\n    mask_init = mask_init if mask_init else ('ConstantFill', {})\n    blob_out = blob_out or model.net.NextName()\n    compress_rate = blob_out + '_compress_rate'\n    if model.init_params:\n        compress_lb = model.param_init_net.ConstantFill(\n            [],\n            blob_out + '_lb',\n            shape=[1],\n            value=comp_lb\n        )\n        weight = model.param_init_net.__getattr__(weight_init[0])(\n            [],\n            blob_out + '_w',\n            shape=[dim_out, dim_in],\n            **weight_init[1]\n        )\n        mask = model.param_init_net.ConstantFill(\n            [],\n            blob_out + '_m',\n            shape=[dim_out, dim_in],\n            value=1.0\n        )\n        ag_dw = model.param_init_net.__getattr__(mask_init[0])(\n            [],\n            blob_out + '_ag_dw',\n            shape=[dim_out, dim_in],\n            **mask_init[1]\n        )\n        bias = model.param_init_net.__getattr__(bias_init[0])(\n            [],\n            blob_out + '_b',\n            shape=[dim_out, ],\n            **bias_init[1]\n        )\n        mask_seq = model.param_init_net.__getattr__(mask_init[0])(\n            [],\n            blob_out + '_mask_seq',\n            shape=[dim_out, dim_in],\n            **mask_init[1]\n        )\n        thres = model.param_init_net.ConstantFill(\n            [],\n            blob_out + '_thres',\n            shape=[1],\n            value=threshold\n        )\n    else:\n        compress_lb = core.ScopedBlobReference(\n            blob_out + '_lb', model.param_init_net)\n        weight = core.ScopedBlobReference(\n            blob_out + '_w', model.param_init_net)\n        bias = core.ScopedBlobReference(\n            blob_out + '_b', model.param_init_net)\n        mask = core.ScopedBlobReference(\n            blob_out + '_m', model.param_init_net)\n        ag_dw = core.ScopedBlobReference(\n            blob_out + '_ag_dw', model.param_init_net)\n        mask_seq = core.ScopedBlobReference(\n            blob_out + '_mask_seq', model.param_init_net)\n        thres = core.ScopedBlobReference(\n            blob_out + '_thres', model.param_init_net)\n\n    model.AddParameter(weight)\n    model.AddParameter(bias)\n    if need_compress_rate:\n        return model.net.FC_Prune([blob_in, weight, mask, bias, ag_dw, mask_seq,\n                                   thres, compress_lb],\n                                  [blob_out, compress_rate], **kwargs)\n    else:\n        return model.net.FC_Prune([blob_in, weight, mask,\n                                   bias, ag_dw, mask_seq,\n                                   thres, compress_lb],\n                                  blob_out, **kwargs)\n\n\ndef fc_sparse(\n    model, blob_in, blob_out, w_csr, iw, jw, bias,\n    **kwargs\n):\n    \"\"\"FC_Sparse: Only takes in alocated weights\"\"\"\n    if not (w_csr and iw and jw and bias):\n        print(\"Warning...\")\n    model.AddParameter(w_csr)\n    model.AddParameter(iw)\n    model.AddParameter(jw)\n    model.AddParameter(bias)\n    return model.net.FC_Sparse([blob_in, w_csr, iw, jw, bias],\n                               blob_out, **kwargs)\n"
  },
  {
    "path": "caffe2/python/helpers/nonlinearity.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package nonlinearity\n# Module caffe2.python.helpers.nonlinearity\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\n\n\ndef prelu(model, blob_in, blob_out, num_channels=1, slope_init=None,\n          **kwargs):\n    \"\"\"PRelu\"\"\"\n    slope_init = (\n        slope_init if slope_init else ('ConstantFill', {'value': 0.25}))\n    if model.init_params:\n        slope = model.param_init_net.__getattr__(slope_init[0])(\n            [],\n            blob_out + '_slope',\n            shape=[num_channels],\n            **slope_init[1]\n        )\n    else:\n        slope = core.ScopedBlobReference(\n            blob_out + '_slope', model.param_init_net)\n\n    model.AddParameter(slope)\n\n    return model.net.PRelu([blob_in, slope], [blob_out])\n\n\ndef relu(model, blob_in, blob_out, use_cudnn=False, order=\"NCHW\", **kwargs):\n    \"\"\"Relu.\"\"\"\n    if use_cudnn:\n        kwargs['engine'] = 'CUDNN'\n    return model.net.Relu(blob_in, blob_out, order=order, **kwargs)\n\n\ndef tanh(model, blob_in, blob_out, use_cudnn=False, order=\"NCHW\", **kwargs):\n    \"\"\"Tanh.\"\"\"\n    if use_cudnn:\n        kwargs['engine'] = 'CUDNN'\n    return model.net.Tanh(blob_in, blob_out, order=order, **kwargs)\n"
  },
  {
    "path": "caffe2/python/helpers/normalization.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package normalization\n# Module caffe2.python.helpers.normalization\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import scope\nfrom caffe2.python.modeling.parameter_info import ParameterTags\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python.modeling import initializers\n\n\ndef lrn(model, blob_in, blob_out, order=\"NCHW\", use_cudnn=False, **kwargs):\n    \"\"\"LRN\"\"\"\n    dev = kwargs['device_option'] if 'device_option' in kwargs \\\n        else scope.CurrentDeviceScope()\n    is_cpu = dev is None or dev.device_type == caffe2_pb2.CPU\n    if use_cudnn and (not is_cpu):\n        kwargs['engine'] = 'CUDNN'\n        blobs_out = blob_out\n    else:\n        blobs_out = [blob_out, \"_\" + blob_out + \"_scale\"]\n    lrn = model.net.LRN(\n        blob_in,\n        blobs_out,\n        order=order,\n        **kwargs\n    )\n\n    if use_cudnn and (not is_cpu):\n        return lrn\n    else:\n        return lrn[0]\n\n\ndef softmax(model, blob_in, blob_out=None, use_cudnn=False, **kwargs):\n    \"\"\"Softmax.\"\"\"\n    if use_cudnn:\n        kwargs['engine'] = 'CUDNN'\n    if blob_out is not None:\n        return model.net.Softmax(blob_in, blob_out, **kwargs)\n    else:\n        return model.net.Softmax(blob_in, **kwargs)\n\n\ndef instance_norm(model, blob_in, blob_out, dim_in, order=\"NCHW\", **kwargs):\n    blob_out = blob_out or model.net.NextName()\n    # Input: input, scale, bias\n    # Output: output, saved_mean, saved_inv_std\n    # scale: initialize with ones\n    # bias: initialize with zeros\n\n    def init_blob(value, suffix):\n        return model.param_init_net.ConstantFill(\n            [], blob_out + \"_\" + suffix, shape=[dim_in], value=value)\n    scale, bias = init_blob(1.0, \"s\"), init_blob(0.0, \"b\")\n\n    model.AddParameter(scale, ParameterTags.WEIGHT)\n    model.AddParameter(bias, ParameterTags.BIAS)\n    blob_outs = [blob_out, blob_out + \"_sm\", blob_out + \"_siv\"]\n    if 'is_test' in kwargs and kwargs['is_test']:\n        blob_outputs = model.net.InstanceNorm(\n            [blob_in, scale, bias], [blob_out],\n            order=order, **kwargs)\n        return blob_outputs\n    else:\n        blob_outputs = model.net.InstanceNorm(\n            [blob_in, scale, bias], blob_outs,\n            order=order, **kwargs)\n        # Return the output\n        return blob_outputs[0]\n\n\ndef spatial_bn(model, blob_in, blob_out, dim_in,\n               init_scale=1., init_bias=0.,\n               ScaleInitializer=None, BiasInitializer=None,\n               RunningMeanInitializer=None, RunningVarianceInitializer=None,\n               order=\"NCHW\", **kwargs):\n    blob_out = blob_out or model.net.NextName()\n    # Input: input, scale, bias, est_mean, est_inv_var\n    # Output: output, running_mean, running_inv_var, saved_mean,\n    #         saved_inv_var\n    # scale: initialize with init_scale (default 1.)\n    # bias: initialize with init_bias (default 0.)\n    # est mean: zero\n    # est var: ones\n\n    if model.init_params:\n        scale_init = (\"ConstantFill\", {'value': init_scale})\n        bias_init = (\"ConstantFill\", {'value': init_bias})\n        rm_init = (\"ConstantFill\", {'value': 0.0})\n        riv_init = (\"ConstantFill\", {'value': 1.0})\n\n        ScaleInitializer = initializers.update_initializer(\n            ScaleInitializer, scale_init, (\"ConstantFill\", {})\n        )\n        BiasInitializer = initializers.update_initializer(\n            BiasInitializer, bias_init, (\"ConstantFill\", {})\n        )\n        RunningMeanInitializer = initializers.update_initializer(\n            RunningMeanInitializer, rm_init, (\"ConstantFill\", {})\n        )\n        RunningVarianceInitializer = initializers.update_initializer(\n            RunningVarianceInitializer, riv_init, (\"ConstantFill\", {})\n        )\n    else:\n        ScaleInitializer = initializers.ExternalInitializer()\n        BiasInitializer = initializers.ExternalInitializer()\n        RunningMeanInitializer = initializers.ExternalInitializer()\n        RunningVarianceInitializer = initializers.ExternalInitializer()\n\n    scale = model.create_param(\n        param_name=blob_out + '_s',\n        shape=[dim_in],\n        initializer=ScaleInitializer,\n        tags=ParameterTags.WEIGHT\n    )\n\n    bias = model.create_param(\n        param_name=blob_out + '_b',\n        shape=[dim_in],\n        initializer=BiasInitializer,\n        tags=ParameterTags.BIAS\n    )\n\n    running_mean = model.create_param(\n        param_name=blob_out + '_rm',\n        shape=[dim_in],\n        initializer=RunningMeanInitializer,\n        tags=ParameterTags.COMPUTED_PARAM\n    )\n\n    running_inv_var = model.create_param(\n        param_name=blob_out + '_riv',\n        shape=[dim_in],\n        initializer=RunningVarianceInitializer,\n        tags=ParameterTags.COMPUTED_PARAM\n    )\n\n    blob_outs = [blob_out, running_mean, running_inv_var,\n                 blob_out + \"_sm\", blob_out + \"_siv\"]\n    if 'is_test' in kwargs and kwargs['is_test']:\n        blob_outputs = model.net.SpatialBN(\n            [blob_in, scale, bias, blob_outs[1], blob_outs[2]], [blob_out],\n            order=order, **kwargs)\n        return blob_outputs\n    else:\n        blob_outputs = model.net.SpatialBN(\n            [blob_in, scale, bias, blob_outs[1], blob_outs[2]], blob_outs,\n            order=order, **kwargs)\n        # Return the output\n        return blob_outputs[0]\n\n\ndef layer_norm(\n    model,\n    blob_in,\n    blob_out,\n    dim_in,\n    axis=1,\n    epsilon=1e-4,\n    initial_scale=1.0,\n    initial_bias=0.0,\n):\n    '''\n    Layer normalizes the input, cf. https://arxiv.org/pdf/1607.06450.pdf.\n\n    Args:\n        blob_in: The input blob to layer normalize.\n        blob_out: The layer normalized output blob.\n        dim_in: The dimension of the scale and bias. For example, if blob_in is\n            a 2D design matrix and axis is 1, this would be the number of\n            columns.\n        axis: (optional) The axis to normalize. Typically the feature axis.\n            Defaults to 1.\n        epsilon: (optional) A small value used for numerical stability in\n            calculation. Defaults to 1e-4.\n        initial_scale: (optional) The initial value for the learned scale\n            parameter. Defaults to 1.0\n        initial_bias: (optional) The initial value for the learned bias\n            parameter of the layerwise standard deviation. Defaults to 0.0.\n\n    Returns:\n        A 3-tuple consisting of:\n            - The layer normalized input blob.\n            - The mean of the input blob across the given axis.\n            - The standard deviation of the input blob acress the given axis.\n    '''\n\n    # The LayerNorm operator only performs the layerwise z-shift, without\n    # scaling and shifting by the learned scale and bias parameters. We have\n    # to do that separately below.\n    normalized, mean, stdev = model.net.LayerNorm(\n        [blob_in],\n        [blob_out, blob_out + \"_mean\", blob_out + \"_stdev\"],\n        axis=axis,\n        epsilon=epsilon,\n    )\n\n    # The learned multiplicative scale or \"gain\".\n    scale = model.create_param(\n        param_name='{}_scale'.format(blob_out),\n        shape=[dim_in],\n        initializer=initializers.Initializer(\n            'ConstantFill',\n            value=initial_scale,\n        ),\n        tags=ParameterTags.WEIGHT,\n    )\n\n    # The learned additive bias or \"shift\".\n    bias = model.create_param(\n        param_name='{}_bias'.format(blob_out),\n        shape=[dim_in],\n        initializer=initializers.Initializer(\n            'ConstantFill',\n            value=initial_bias,\n        ),\n        tags=ParameterTags.BIAS,\n    )\n\n    scaled = model.net.Mul(\n        [normalized, scale],\n        ['{}_scaled'.format(blob_out)],\n        broadcast=1,\n        axis=axis,\n    )\n\n    biased = model.net.Add(\n        [scaled, bias],\n        ['{}_biased'.format(blob_out)],\n        broadcast=1,\n        axis=axis,\n    )\n\n    return biased, mean, stdev\n"
  },
  {
    "path": "caffe2/python/helpers/pooling.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package pooling\n# Module caffe2.python.helpers.pooling\n## @package fc\n# Module caffe2.python.helpers.pooling\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n\ndef max_pool(model, blob_in, blob_out, use_cudnn=False, order=\"NCHW\", **kwargs):\n    \"\"\"Max pooling\"\"\"\n    if use_cudnn:\n        kwargs['engine'] = 'CUDNN'\n    return model.net.MaxPool(blob_in, blob_out, order=order, **kwargs)\n\n\ndef average_pool(model, blob_in, blob_out, use_cudnn=False, order=\"NCHW\",\n                 **kwargs):\n    \"\"\"Average pooling\"\"\"\n    if use_cudnn:\n        kwargs['engine'] = 'CUDNN'\n    return model.net.AveragePool(\n        blob_in,\n        blob_out,\n        order=order,\n        **kwargs\n    )\n\n\ndef max_pool_with_index(model, blob_in, blob_out, order=\"NCHW\", **kwargs):\n    \"\"\"Max pooling with an explicit index of max position\"\"\"\n    return model.net.MaxPoolWithIndex(\n        blob_in,\n        [blob_out, blob_out + \"_index\"],\n        order=order,\n        **kwargs\n    )[0]\n"
  },
  {
    "path": "caffe2/python/helpers/tools.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package tools\n# Module caffe2.python.helpers.tools\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n\ndef image_input(\n    model, blob_in, blob_out, order=\"NCHW\", use_gpu_transform=False, **kwargs\n):\n    assert 'is_test' in kwargs, \"Argument 'is_test' is required\"\n    if order == \"NCHW\":\n        if (use_gpu_transform):\n            kwargs['use_gpu_transform'] = 1 if use_gpu_transform else 0\n            # GPU transform will handle NHWC -> NCHW\n            outputs = model.net.ImageInput(blob_in, blob_out, **kwargs)\n            pass\n        else:\n            outputs = model.net.ImageInput(\n                blob_in, [blob_out[0] + '_nhwc'] + blob_out[1:], **kwargs\n            )\n            outputs_list = list(outputs)\n            outputs_list[0] = model.net.NHWC2NCHW(outputs_list[0], blob_out[0])\n            outputs = tuple(outputs_list)\n    else:\n        outputs = model.net.ImageInput(blob_in, blob_out, **kwargs)\n    return outputs\n\n\ndef video_input(model, blob_in, blob_out, **kwargs):\n    # size of outputs can vary depending on kwargs\n    outputs = model.net.VideoInput(blob_in, blob_out, **kwargs)\n    return outputs\n"
  },
  {
    "path": "caffe2/python/helpers/train.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package train\n# Module caffe2.python.helpers.train\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, scope\nfrom caffe2.proto import caffe2_pb2\n\n\ndef _get_weights(model, namescope=None):\n    if namescope is None:\n        namescope = scope.CurrentNameScope()\n\n    if namescope == '':\n        return model.weights[:]\n    else:\n        return [w for w in model.weights if w.GetNameScope() == namescope]\n\n\ndef iter(model, blob_out, **kwargs):\n    if 'device_option' in kwargs:\n        del kwargs['device_option']\n    model.param_init_net.ConstantFill(\n        [],\n        blob_out,\n        shape=[1],\n        value=0,\n        dtype=core.DataType.INT64,\n        device_option=core.DeviceOption(caffe2_pb2.CPU, 0),\n        **kwargs\n    )\n    return model.net.Iter(blob_out, blob_out, **kwargs)\n\n\ndef accuracy(model, blob_in, blob_out, **kwargs):\n    dev = kwargs['device_option'] if 'device_option' in kwargs \\\n        else scope.CurrentDeviceScope()\n    is_cpu = dev is None or dev.device_type == caffe2_pb2.CPU\n\n    # We support top_k > 1 only on CPU\n    if not is_cpu and 'top_k' in kwargs and kwargs['top_k'] > 1:\n        pred_host = model.net.CopyGPUToCPU(blob_in[0], blob_in[0] + \"_host\")\n        label_host = model.net.CopyGPUToCPU(blob_in[1], blob_in[1] + \"_host\")\n\n        # Now use the Host version of the accuracy op\n        model.net.Accuracy(\n            [pred_host, label_host],\n            blob_out,\n            device_option=core.DeviceOption(caffe2_pb2.CPU, 0),\n            **kwargs\n        )\n    else:\n        model.net.Accuracy(blob_in, blob_out)\n\n\ndef add_weight_decay(model, weight_decay):\n    \"\"\"Adds a decay to weights in the model.\n\n    This is a form of L2 regularization.\n\n    Args:\n        weight_decay: strength of the regularization\n    \"\"\"\n    if weight_decay <= 0.0:\n        return\n    wd = model.param_init_net.ConstantFill(\n        [], 'wd', shape=[1], value=weight_decay\n    )\n    ONE = model.param_init_net.ConstantFill([], \"ONE\", shape=[1], value=1.0)\n    for param in _get_weights(model):\n        #  Equivalent to: grad += wd * param\n        grad = model.param_to_grad[param]\n        model.net.WeightedSum(\n            [grad, ONE, param, wd],\n            grad,\n        )\n"
  },
  {
    "path": "caffe2/python/hsm_util.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package hsm_util\n# Module caffe2.python.hsm_util\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.proto import hsm_pb2\n\n'''\n    Hierarchical softmax utility methods that can be used to:\n    1) create TreeProto structure given list of word_ids or NodeProtos\n    2) create HierarchyProto structure using the user-inputted TreeProto\n'''\n\n\ndef create_node_with_words(words, name='node'):\n    node = hsm_pb2.NodeProto()\n    node.name = name\n    for word in words:\n        node.word_ids.append(word)\n    return node\n\n\ndef create_node_with_nodes(nodes, name='node'):\n    node = hsm_pb2.NodeProto()\n    node.name = name\n    for child_node in nodes:\n        new_child_node = node.children.add()\n        new_child_node.MergeFrom(child_node)\n    return node\n\n\ndef create_hierarchy(tree_proto):\n    max_index = 0\n\n    def create_path(path, word):\n        path_proto = hsm_pb2.PathProto()\n        path_proto.word_id = word\n        for entry in path:\n            new_path_node = path_proto.path_nodes.add()\n            new_path_node.index = entry[0]\n            new_path_node.length = entry[1]\n            new_path_node.target = entry[2]\n        return path_proto\n\n    def recursive_path_builder(node_proto, path, hierarchy_proto, max_index):\n        node_proto.offset = max_index\n        path.append([max_index,\n                    len(node_proto.word_ids) + len(node_proto.children), 0])\n        max_index += len(node_proto.word_ids) + len(node_proto.children)\n        if hierarchy_proto.size < max_index:\n            hierarchy_proto.size = max_index\n        for target, node in enumerate(node_proto.children):\n            path[-1][2] = target\n            max_index = recursive_path_builder(node, path, hierarchy_proto,\n                                               max_index)\n        for target, word in enumerate(node_proto.word_ids):\n            path[-1][2] = target + len(node_proto.children)\n            path_entry = create_path(path, word)\n            new_path_entry = hierarchy_proto.paths.add()\n            new_path_entry.MergeFrom(path_entry)\n        del path[-1]\n        return max_index\n\n    node = tree_proto.root_node\n    hierarchy_proto = hsm_pb2.HierarchyProto()\n    path = []\n    max_index = recursive_path_builder(node, path, hierarchy_proto, max_index)\n    return hierarchy_proto\n"
  },
  {
    "path": "caffe2/python/hypothesis_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nimport copy\nimport time\nfrom functools import partial, reduce\nfrom future.utils import viewitems, viewkeys\nfrom hypothesis import assume, given, settings, HealthCheck\nimport hypothesis.strategies as st\nimport unittest\n\nfrom caffe2.python import core, workspace, tt_core, dyndep\nimport caffe2.python.hypothesis_test_util as hu\nfrom caffe2.proto.caffe2_pb2 import TensorProto\n\ndyndep.InitOpsLibrary('@/caffe2/caffe2/fb/optimizers:sgd_simd_ops')\n\n\ndef sigmoid(x):\n    return 1.0 / (1.0 + np.exp(-x))\n\n\n@st.composite\ndef _tensor_and_prefix(draw, dtype, elements, min_dim=1, max_dim=4, **kwargs):\n    dims_ = draw(\n        st.lists(hu.dims(**kwargs), min_size=min_dim, max_size=max_dim))\n    extra_ = draw(\n        st.lists(hu.dims(**kwargs), min_size=min_dim, max_size=max_dim))\n    assume(len(dims_) + len(extra_) < max_dim)\n    return (draw(hu.arrays(dims_ + extra_, dtype, elements)),\n            draw(hu.arrays(extra_, dtype, elements)))\n\n\ndef _tensor_and_indices(min_dim=1, max_dim=4, dtype=np.float32,\n                        elements=None, **kwargs):\n    \"\"\" generates a tensor and a list of indices of larger tensor of same dim\"\"\"\n    data_dims_ = st.lists(hu.dims(**kwargs), min_size=min_dim, max_size=max_dim)\n    original_dim = st.integers(min_value=2, max_value=10)\n    return st.tuples(data_dims_, original_dim).flatmap(lambda pair: st.tuples(\n        st.just(pair[1]),  # original dimension\n        hu.arrays(pair[0], dtype, elements),  # data tensor\n        hu.arrays(pair[0][0], dtype=np.int64, elements=st.integers(\n            min_value=0, max_value=pair[1] - 1)),\n    ))\n\n\n_NUMPY_TYPE_TO_ENUM = {\n    np.float32: core.DataType.FLOAT,\n    np.int32: core.DataType.INT32,\n    np.bool: core.DataType.BOOL,\n    np.uint8: core.DataType.UINT8,\n    np.int8: core.DataType.INT8,\n    np.uint16: core.DataType.UINT16,\n    np.int16: core.DataType.INT16,\n    np.int64: core.DataType.INT64,\n    np.float64: core.DataType.DOUBLE,\n}\n\n\ndef _dtypes(dtypes=None):\n    dtypes = dtypes if dtypes else [np.int32, np.int64, np.float32]\n    return st.sampled_from(dtypes)\n\n\ndef _test_binary(name, ref, filter_=None, gcs=hu.gcs,\n                 test_gradient=False, allow_inplace=False, dtypes=_dtypes):\n    @given(\n        inputs=dtypes().flatmap(\n            lambda dtype: hu.tensors(\n                n=2, dtype=dtype,\n                elements=hu.elements_of_type(dtype, filter_=filter_))),\n        out=st.sampled_from(('Y', 'X1', 'X2') if allow_inplace else ('Y',)),\n        **gcs)\n    @settings(max_examples=3, timeout=100)\n    def test_binary(self, inputs, out, gc, dc):\n        op = core.CreateOperator(name, [\"X1\", \"X2\"], [out])\n        X1, X2 = inputs\n        self.assertDeviceChecks(dc, op, [X1, X2], [0])\n        # We only do gradient check with float32 types.\n        if test_gradient and X1.dtype == np.float32:\n            self.assertGradientChecks(gc, op, [X1, X2], 0, [0])\n        self.assertReferenceChecks(gc, op, [X1, X2], ref)\n\n    return test_binary\n\n\ndef _test_binary_broadcast(name, ref, filter_=None,\n                           gcs=hu.gcs, allow_inplace=False, dtypes=_dtypes):\n    @given(\n        inputs=dtypes().flatmap(lambda dtype: _tensor_and_prefix(\n            dtype=dtype,\n            elements=hu.elements_of_type(dtype, filter_=filter_))),\n        in_place=(st.booleans() if allow_inplace else st.just(False)),\n        **gcs)\n    @settings(max_examples=3, timeout=100)\n    def test_binary_broadcast(self, inputs, in_place, gc, dc):\n        op = core.CreateOperator(\n            name, [\"X1\", \"X2\"], [\"X1\" if in_place else \"Y\"], broadcast=1)\n        X1, X2 = inputs\n        self.assertDeviceChecks(dc, op, [X1, X2], [0])\n\n        def cast_ref(x, y):\n            return (np.array(ref(x, y)[0], dtype=x.dtype), )\n\n        # gradient not implemented yet\n        # self.assertGradientChecks(gc, op, [X1, X2], 0, [0])\n        self.assertReferenceChecks(gc, op, [X1, X2], cast_ref)\n\n    return test_binary_broadcast\n\n\nclass TestOperators(hu.HypothesisTestCase):\n\n    def test_comparison_ops(self):\n        ops = {\"LT\": lambda x1, x2: [x1 < x2],\n               \"LE\": lambda x1, x2: [x1 <= x2],\n               \"GT\": lambda x1, x2: [x1 > x2],\n               \"GE\": lambda x1, x2: [x1 >= x2]}\n        for name, ref in viewitems(ops):\n            _test_binary(name, ref, gcs=hu.gcs_cpu_only)(self)\n            _test_binary_broadcast(name, ref, gcs=hu.gcs_cpu_only)(self)\n\n    @given(inputs=hu.tensors(n=2), in_place=st.booleans(), **hu.gcs)\n    def test_sum(self, inputs, in_place, gc, dc):\n        op = core.CreateOperator(\"Sum\", [\"X1\", \"X2\"],\n                                        [\"Y\" if not in_place else \"X1\"])\n        X1, X2 = inputs\n        self.assertDeviceChecks(dc, op, [X1, X2], [0])\n        self.assertGradientChecks(gc, op, [X1, X2], 0, [0])\n\n    @given(inputs=hu.tensors(n=2, min_dim=2, max_dim=2), **hu.gcs_cpu_only)\n    def test_row_mul(self, inputs, gc, dc):\n        op = core.CreateOperator(\"RowMul\", [\"X1\", \"X2\"], [\"Y\"])\n        X1, Xtmp = inputs\n        X2 = Xtmp[:, 0]\n\n        def ref(x, y):\n            ret = np.zeros(shape=x.shape, dtype=x.dtype)\n            for i in range(y.size):\n                ret[i, ] = x[i, ] * y[i]\n            return [ret]\n\n        self.assertDeviceChecks(dc, op, [X1, X2], [0])\n        for i in range(2):\n            self.assertGradientChecks(gc, op, [X1, X2], i, [0])\n        self.assertReferenceChecks(gc, op, [X1, X2], ref)\n\n    @given(inputs=hu.tensors(n=2), **hu.gcs_cpu_only)\n    def test_max(self, inputs, gc, dc):\n        op = core.CreateOperator(\"Max\", [\"X1\", \"X2\"], [\"Y\"])\n\n        X1, X2 = inputs\n        # Make X1 and X2 far from each other, since X1=X2 is not differentiable\n        # and the step size of gradient checker is 0.05\n        X1[np.logical_and(X1 >= X2 - 0.05, X1 <= X2)] -= 0.05\n        X1[np.logical_and(X1 <= X2 + 0.05, X1 >= X2)] += 0.05\n        self.assertDeviceChecks(dc, op, [X1, X2], [0])\n        for i in range(2):\n            self.assertGradientChecks(gc, op, [X1, X2], i, [0])\n\n        def elementwise_max(X, Y):\n            return [np.maximum(X, Y)]\n        self.assertReferenceChecks(gc, op, [X1, X2], elementwise_max)\n\n    def test_add(self):\n        def not_overflow(x):\n            if not isinstance(x, float):\n                return abs(x) < (1 << 30) - 1\n            return True\n\n        def ref(x, y):\n            return (x + y, )\n        _test_binary(\"Add\", ref, filter_=not_overflow, test_gradient=True)(self)\n        _test_binary_broadcast(\"Add\", ref, filter_=not_overflow)(self)\n\n    def test_sub(self):\n        def ref(x, y):\n            return (x - y, )\n        # TODO(jiayq): enable gradient test when implemented.\n        _test_binary(\"Sub\", ref, test_gradient=True)(self)\n        _test_binary_broadcast(\"Sub\", ref)(self)\n\n    def test_mul(self):\n        def not_overflow(x):\n            if not isinstance(x, float):\n                return abs(x) < (1 << 15) - 1\n            return True\n\n        def ref(x, y):\n            return (x * y, )\n        _test_binary(\"Mul\", ref, filter_=not_overflow, test_gradient=True)(self)\n        _test_binary_broadcast(\"Mul\", ref, filter_=not_overflow)(self)\n\n    def test_div(self):\n        def ref(x, y):\n            return (x / y, )\n\n        def non_zero(x):\n            return abs(x) > 1e-2\n\n        def div_dtypes():\n            return st.sampled_from([np.float32, np.float64])\n\n        _test_binary(\n            \"Div\", ref, filter_=non_zero, test_gradient=True,\n            dtypes=div_dtypes, gcs=hu.gcs_cpu_only\n        )(self)\n        _test_binary(\n            \"Div\", ref, filter_=non_zero, test_gradient=False,\n            dtypes=div_dtypes\n        )(self)\n        _test_binary_broadcast(\n            \"Div\", ref, filter_=non_zero, dtypes=div_dtypes)(self)\n\n    @given(X=hu.tensor(), in_place=st.booleans(), **hu.gcs)\n    def test_negative(self, X, in_place, gc, dc):\n        op = core.CreateOperator(\"Negative\", [\"X\"],\n                                 [\"Y\" if not in_place else \"X\"])\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @given(X=hu.tensor(), **hu.gcs)\n    def test_tanh(self, X, gc, dc):\n        op = core.CreateOperator(\"Tanh\", \"X\", \"Y\")\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @given(X=hu.tensor(), **hu.gcs)\n    def test_averaged_loss(self, X, gc, dc):\n        op = core.CreateOperator(\"AveragedLoss\", [\"X\"], [\"loss\"])\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @given(X=hu.tensor(), inplace=st.booleans(), **hu.gcs)\n    def test_softsign(self, X, inplace, gc, dc):\n        op = core.CreateOperator(\"Softsign\", [\"X\"], [\"X\" if inplace else \"Y\"])\n\n        def softsign(X):\n            return (X / (1 + np.abs(X)),)\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertReferenceChecks(gc, op, [X], softsign)\n        if inplace:\n            with self.assertRaises(Exception):\n                self.assertGradientChecks(gc, op, [X], 0, [0])\n        else:\n            self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @given(\n        device_options=st.lists(\n            min_size=2,\n            max_size=4,\n            elements=st.sampled_from(hu.expanded_device_options)),\n        set_seed=st.booleans())\n    def test_random_seed_behaviour(self, device_options, set_seed):\n        # Assume we are always operating on CUDA or CPU, since RNG is\n        # inconsistent between CPU and GPU.\n        device_options = copy.deepcopy(device_options)\n        assume(len({do.device_type for do in device_options}) == 1)\n        if set_seed:\n            for do in device_options:\n                do.random_seed = 1000\n\n        def run(do):\n            # Reset each time because 'Y' may already exist in the workspace\n            #   on a different device\n            workspace.ResetWorkspace()\n            ws = workspace.C.Workspace()\n            op = core.CreateOperator(\n                \"XavierFill\", [], [\"Y\"],\n                device_option=do,\n                shape=[2])\n            ws.run(op)\n            return ws.blobs[\"Y\"].fetch()\n\n        ys = [run(do) for do in device_options]\n        for y in ys[1:]:\n            if set_seed:\n                np.testing.assert_array_equal(ys[0], y)\n            else:\n                with self.assertRaises(AssertionError):\n                    np.testing.assert_array_equal(ys[0], y)\n\n    @given(axis=st.integers(min_value=1, max_value=4),\n           num_output=st.integers(min_value=4, max_value=8),\n           engine=st.sampled_from([\"\", \"PACKED\"]),\n           **hu.gcs)\n    def test_fully_connected_axis(self, axis, num_output, engine, gc, dc):\n        np.random.seed(1)\n        X = np.random.randn(1, 2, 3, 2, 1).astype(np.float32)\n\n        def prod(xs):\n            p = 1\n            for x in xs:\n                p *= x\n            return p\n\n        K = prod(list(X.shape)[axis:])\n        N = num_output\n        W = np.random.randn(N, K).astype(np.float32)\n        b = np.random.randn(N).astype(np.float32)\n\n        op = core.CreateOperator(\n            \"FC\",\n            [\"X\", \"W\", \"b\"],\n            [\"Y\"],\n            engine=engine,\n            axis=axis)\n        for name, param in [(\"X\", X), (\"W\", W), (\"b\", b)]:\n            self.ws.create_blob(name).feed(param)\n        self.ws.run(op)\n        Y = self.ws.blobs[\"Y\"].fetch()\n        self.assertEqual(list(Y.shape), list(X.shape)[:axis] + [N])\n\n        inputs = [X, W, b]\n        self.assertDeviceChecks(dc, op, inputs, [0])\n        for param, _ in enumerate(inputs):\n            self.assertGradientChecks(gc, op, inputs, param, [0])\n\n    @unittest.skipIf(not workspace.has_gpu_support,\n                     \"Skipping test due to no gpu present.\")\n    @given(hidden_size=st.integers(min_value=1, max_value=3),\n           num_layers=st.integers(min_value=1, max_value=3),\n           bidirectional=st.just(False),  # TODO\n           rnn_mode=st.sampled_from([\"lstm\"]),   # TODO: \"gru\"\n           input_mode=st.sampled_from([\"linear\"]),\n           dropout=st.floats(min_value=1.0, max_value=1.0),\n           T=st.integers(min_value=2, max_value=6),\n           N=st.integers(min_value=1, max_value=4),\n           D=st.integers(min_value=1, max_value=4))\n    def test_recurrent(self, hidden_size, num_layers, bidirectional, rnn_mode,\n                       input_mode, dropout, T, N, D):\n\n        # Random seed, this one happens to pass\n        seed = 1234\n        np.random.seed(seed)\n\n        input_weight_size = hidden_size * D\n        upper_layer_input_weight_size = hidden_size * hidden_size\n        recurrent_weight_size = hidden_size * hidden_size\n        input_bias_size = hidden_size\n        recurrent_bias_size = hidden_size\n        num_directions = 2 if bidirectional else 1\n        first_layer_sz = input_weight_size + recurrent_weight_size + \\\n                         input_bias_size + recurrent_bias_size\n        upper_layer_sz = upper_layer_input_weight_size + \\\n                         recurrent_weight_size + input_bias_size + \\\n                         recurrent_bias_size\n        total_sz = 4 * (first_layer_sz + (num_layers - 1) * upper_layer_sz)\n        total_sz *= num_directions\n\n        W = np.random.rand(total_sz).astype(np.float32)\n        self.ws.create_blob(\"WEIGHT\").feed(W, device_option=hu.gpu_do)\n\n        op = core.CreateOperator(\n            \"Recurrent\",\n            [\"INPUT\", \"HIDDEN_INPUT\", \"CELL_INPUT\", \"WEIGHT\"],\n            [\"OUTPUT\", \"HIDDEN_OUTPUT\", \"CELL_OUTPUT\",\n             \"RNN_SCRATCH\", \"DROPOUT_STATES\"],\n            hidden_size=hidden_size,\n            bidirectional=bidirectional,\n            rnn_mode=rnn_mode,\n            dropout=dropout,\n            input_mode=input_mode,\n            num_layers=num_layers,\n            seed=seed,\n            engine=\"CUDNN\")\n        X = np.random.randn(T, N, D).astype(np.float32)\n        self.ws.create_blob(\"INPUT\").feed(X, device_option=hu.gpu_do)\n        W = self.ws.blobs[\"WEIGHT\"].fetch()\n        H = np.random.randn(\n            num_layers, N, hidden_size * num_directions).astype(\n                np.float32)\n        C = np.random.randn(\n            num_layers, N, hidden_size * num_directions).astype(\n                np.float32) if rnn_mode == \"lstm\" else \\\n            np.empty((1,)).astype(np.float32)  # unused in GRU\n        inputs = [X, H, C, W]\n        input_idxs = [i for (i, _) in enumerate(inputs)] \\\n            if rnn_mode == \"lstm\" else [0, 1, 3]  # ignore C\n\n        for input_idx in input_idxs:\n            self.assertGradientChecks(\n                hu.gpu_do, op, inputs, input_idx, [0],\n                stepsize=0.01, threshold=0.01)\n\n    @given(ndim=st.integers(1, 4),\n           axis=st.integers(0, 3),\n           add_axis=st.integers(0, 1),\n           num_inputs=st.integers(2, 4), **hu.gcs)\n    def test_depth_concat(self, ndim, axis, add_axis, num_inputs, gc, dc):\n        assume(axis < ndim)\n        input_names = ['X0', 'X1', 'X2', 'X3'][:num_inputs]\n        shape = [2, 3, 5, 7][:ndim]\n        individual_dims = [1, 2, 3, 4, 5][:num_inputs]\n        inputs = []\n        for i in range(num_inputs):\n            if add_axis == 0:\n                # Sets a unique dim and create the input.\n                shape[axis] = individual_dims[i]\n            inputs.append(np.random.randn(*shape).astype(np.float32))\n        op = core.CreateOperator(\"Concat\", input_names, [\"Y\", \"Y_dims\"],\n                                 axis=axis, add_axis=add_axis)\n        self.assertDeviceChecks(dc, op, inputs, [0])\n        for i in range(num_inputs):\n            self.assertGradientChecks(gc, op, inputs, i, [0])\n\n        # Reference\n        def depth_concat(*inputs):\n            inputs = list(inputs)\n            if add_axis:\n                for i in range(len(inputs)):\n                    inputs[i] = np.expand_dims(inputs[i], axis)\n            input_dims = np.array([np.shape(x)[axis] for x in inputs])\n            return [np.concatenate(inputs, axis=axis), input_dims]\n\n        self.assertReferenceChecks(gc, op, inputs, depth_concat)\n\n    @given(num_inputs=st.integers(2, 4),\n           order=st.sampled_from([(\"NCHW\", 1), (\"NHWC\", 3)]),\n           **hu.gcs)\n    def test_depth_concat_with_order(self, num_inputs, order, gc, dc):\n        input_names = ['X0', 'X1', 'X2', 'X3'][:num_inputs]\n        shape = [2, 3, 5, 7]\n        individual_dims = [1, 2, 3, 4][:num_inputs]\n        inputs = []\n        for i in range(num_inputs):\n            # Sets a unique dim and create the input.\n            shape[order[1]] = individual_dims[i]\n            inputs.append(np.random.rand(*shape).astype(np.float32))\n        op = core.CreateOperator(\"Concat\", input_names, [\"Y\", \"Y_dims\"],\n                                 order=order[0])\n        self.assertDeviceChecks(dc, op, inputs, [0])\n        for i in range(num_inputs):\n            self.assertGradientChecks(gc, op, inputs, i, [0])\n\n        # Reference\n        def depth_concat_with_order(*inputs):\n            inputs = list(inputs)\n            axis = order[1]\n            input_dims = np.array([np.shape(x)[axis] for x in inputs])\n            return [np.concatenate(inputs, axis=axis), input_dims]\n\n        self.assertReferenceChecks(gc, op, inputs, depth_concat_with_order)\n\n    @given(X=hu.arrays(dims=[5, 2],\n                       elements=st.floats(min_value=1.0, max_value=10.0)),\n           **hu.gcs_cpu_only)\n    def test_last_n_windows(self, X, gc, dc):\n        workspace.FeedBlob('input', X)\n        workspace.FeedBlob('next', np.array(0, dtype=np.int32))\n        workspace.CreateBlob('output')\n        collect_net = core.Net('collect_net')\n        collect_net.LastNWindowCollector(\n            ['output', 'next', 'input'],\n            ['output', 'next'],\n            num_to_collect=7,\n        )\n        plan = core.Plan('collect_data')\n        plan.AddStep(core.execution_step('collect_data',\n                                         [collect_net], num_iter=2))\n        workspace.RunPlan(plan)\n        output = workspace.FetchBlob('output')\n        inputs = workspace.FetchBlob('input')\n        new_output = np.zeros([7, inputs.shape[1]])\n        for i in range(inputs.shape[0] * 2):\n            new_output[i % 7] = inputs[i % inputs.shape[0]]\n        import numpy.testing as npt\n        npt.assert_almost_equal(output, new_output, decimal=5)\n\n    @given(dtype=st.sampled_from([np.float32, np.float64, np.int32, np.bool]))\n    def test_print(self, dtype):\n        data = np.random.permutation(6).astype(dtype)\n        self.ws.create_blob(\"data\").feed(data)\n        op = core.CreateOperator(\"Print\", \"data\", [])\n        self.ws.run(op)\n\n    @given(inputs=hu.tensors(n=2),\n           in_place=st.booleans(),\n           momentum=st.floats(min_value=0.1, max_value=0.9),\n           nesterov=st.booleans(),\n           lr=st.floats(min_value=0.1, max_value=0.9),\n           **hu.gcs)\n    def test_momentum_sgd(\n            self, inputs, in_place, momentum, nesterov, lr, gc, dc):\n        grad, m = inputs\n        lr = np.asarray([lr], dtype=np.float32)\n        op = core.CreateOperator(\n            \"MomentumSGD\",\n            [\"grad\", \"m\", \"lr\"],\n            [\"grad\" if in_place else \"grad_o\",\n             \"m\" if in_place else \"m_o\"],\n            momentum=momentum,\n            nesterov=int(nesterov),\n            device_option=gc)\n        self.assertDeviceChecks(\n            dc, op, [grad, m, lr], [0])\n\n        # Reference\n        def momentum_sgd(grad, m, lr):\n            lr = lr[0]\n            if not nesterov:\n                adjusted_gradient = lr * grad + momentum * m\n                return (adjusted_gradient, adjusted_gradient)\n            else:\n                m_new = momentum * m + lr * grad\n                return ((1 + momentum) * m_new - momentum * m, m_new)\n\n        self.assertReferenceChecks(gc, op, [grad, m, lr], momentum_sgd)\n\n    @given(inputs=hu.tensors(n=3),\n           in_place=st.booleans(),\n           decay=st.floats(min_value=0.1, max_value=0.9),\n           momentum=st.floats(min_value=0.1, max_value=0.9),\n           lr=st.floats(min_value=0.1, max_value=0.9),\n           epsilon=st.floats(min_value=1e-5, max_value=1e-2),\n           **hu.gcs)\n    def test_rmsprop_sgd(self, inputs, in_place, decay, momentum, lr, epsilon,\n                         gc, dc):\n        grad, ms, mom = inputs\n        ms = np.abs(ms) + 0.01\n        lr = np.asarray([lr], dtype=np.float32)\n        op = core.CreateOperator(\n            \"RmsProp\",\n            [\"grad\", \"ms\", \"mom\", \"lr\"],\n            [\"grad\" if in_place else \"grad_o\",\n             \"ms\" if in_place else \"ms_o\",\n             \"mom\" if in_place else \"mom_o\"],\n            momentum=momentum, decay=decay, epsilon=epsilon, device_option=gc)\n        self.assertDeviceChecks(dc, op, [grad, ms, mom, lr], [0])\n\n        def rmsprop(grad, ms, mom, lr):\n            lr = lr[0]\n            ms_o = ms + (1. - decay) * (np.square(grad) - ms)\n            mom_o = momentum * mom + lr * grad / np.sqrt(epsilon + ms_o)\n            grad_o = mom_o\n            return (grad_o, ms_o, mom_o)\n        self.assertReferenceChecks(gc, op, [grad, ms, mom, lr], rmsprop)\n\n    # Reference\n    @staticmethod\n    def _dense_ftrl(alpha, beta, lambda1, lambda2, w, nz, g):\n        if isinstance(alpha, np.ndarray):\n            alpha = np.asscalar(alpha)\n        n = np.take(nz, 0, axis=-1)\n        z = np.take(nz, 1, axis=-1)\n        # python port of Sigrid's implementation\n        g2 = g * g\n        sigma = (np.sqrt(n + g2) - np.sqrt(n)) / alpha\n        z += g - sigma * w\n        n += g2\n        w = (np.sign(z) * lambda1 - z) / (\n            (beta + np.sqrt(n)) / alpha + lambda2)\n        w[np.abs(z) <= lambda1] = 0\n        return (w, np.stack([n, z], axis=-1))\n\n    @given(inputs=hu.tensors(n=4),\n           in_place=st.booleans(),\n           alpha=st.floats(min_value=0.01, max_value=0.1),\n           beta=st.floats(min_value=0.1, max_value=0.9),\n           lambda1=st.floats(min_value=0.001, max_value=0.1),\n           lambda2=st.floats(min_value=0.001, max_value=0.1),\n           engine=st.sampled_from([None, \"SIMD\"]),\n           **hu.gcs_cpu_only)\n    def test_ftrl_sgd(self, inputs, in_place, alpha, beta, lambda1, lambda2,\n                      engine, gc, dc):\n        var, n, z, grad = inputs\n        n = np.abs(n)\n        nz = np.stack([n, z], axis=-1)\n        op = core.CreateOperator(\n            \"Ftrl\",\n            [\"var\", \"nz\", \"grad\"],\n            [\"var\" if in_place else \"var_o\",\n             \"nz\" if in_place else \"nz_o\"],\n            alpha=alpha, beta=beta, lambda1=lambda1, lambda2=lambda2,\n            engine=engine,\n            device_option=gc)\n        self.assertDeviceChecks(\n            dc, op, [var, nz, grad], [0])\n\n        self.assertReferenceChecks(\n            gc, op, [var, nz, grad],\n            partial(self._dense_ftrl, alpha, beta, lambda1, lambda2))\n\n    @given(inputs=hu.tensors(n=4),\n           alpha=st.floats(min_value=0.01, max_value=0.1),\n           beta=st.floats(min_value=0.1, max_value=0.9),\n           lambda1=st.floats(min_value=0.001, max_value=0.1),\n           lambda2=st.floats(min_value=0.001, max_value=0.1),\n           engine=st.sampled_from([None, \"SIMD\"]),\n           **hu.gcs_cpu_only)\n    def test_sparse_ftrl_sgd(self, inputs, alpha, beta, lambda1, lambda2,\n                             engine, gc, dc):\n        var, n, z, grad = inputs\n        # generate fake subset manually because hypothesis is too complicated :)\n        indices = np.arange(var.shape[0])\n        indices = indices[indices % 2 == 0]\n        grad = grad[indices]\n        n = np.abs(n)\n        nz = np.stack([n, z], axis=-1)\n        op = core.CreateOperator(\n            \"SparseFtrl\",\n            [\"var\", \"nz\", \"indices\", \"grad\"],\n            [\"var\", \"nz\"],\n            alpha=alpha, beta=beta, lambda1=lambda1, lambda2=lambda2,\n            engine=engine,\n            device_option=gc)\n        self.assertDeviceChecks(\n            dc, op, [var, nz, indices, grad], [0])\n\n        # Reference\n        def ftrl(w, nz, i, g):\n            sw, snz = self._dense_ftrl(alpha, beta, lambda1, lambda2,\n                                       w[i], nz[i], g)\n            w[i] = sw\n            nz[i] = snz\n            return (w, nz)\n\n        self.assertReferenceChecks(gc, op, [var, nz, indices, grad], ftrl)\n\n    # Reference\n    @staticmethod\n    def _dense_ftrl_send_alpha_by_input(beta, lambda1, lambda2, w, nz, g, alpha):\n        return TestOperators._dense_ftrl(alpha, beta, lambda1, lambda2, w, nz,\n                                         g)\n\n    @given(inputs=hu.tensors(n=4),\n           in_place=st.booleans(),\n           alpha=st.floats(min_value=0.01, max_value=0.1),\n           beta=st.floats(min_value=0.1, max_value=0.9),\n           lambda1=st.floats(min_value=0.001, max_value=0.1),\n           lambda2=st.floats(min_value=0.001, max_value=0.1),\n           engine=st.sampled_from([None, \"SIMD\"]),\n           **hu.gcs_cpu_only)\n    def test_ftrl_sgd_send_alpha_by_input(self, inputs, in_place, alpha, beta,\n                                          lambda1, lambda2, engine, gc, dc):\n        var, n, z, grad = inputs\n        n = np.abs(n)\n        nz = np.stack([n, z], axis=-1)\n        alpha = np.array(alpha).astype(np.float32)\n        op = core.CreateOperator(\n            \"Ftrl\",\n            [\"var\", \"nz\", \"grad\", \"alpha\"],\n            [\"var\" if in_place else \"var_o\",\n             \"nz\" if in_place else \"nz_o\"],\n            beta=beta, lambda1=lambda1, lambda2=lambda2,\n            engine=engine,\n            device_option=gc)\n        self.assertDeviceChecks(\n            dc, op, [var, nz, grad, alpha], [0])\n\n        self.assertReferenceChecks(\n            gc, op, [var, nz, grad, alpha],\n            partial(self._dense_ftrl_send_alpha_by_input, beta, lambda1, lambda2))\n\n    @given(inputs=hu.tensors(n=4),\n           alpha=st.floats(min_value=0.01, max_value=0.1),\n           beta=st.floats(min_value=0.1, max_value=0.9),\n           lambda1=st.floats(min_value=0.001, max_value=0.1),\n           lambda2=st.floats(min_value=0.001, max_value=0.1),\n           engine=st.sampled_from([None, \"SIMD\"]),\n           **hu.gcs_cpu_only)\n    def test_sparse_ftrl_sgd_send_alpha_by_input(self, inputs, alpha, beta,\n                                                 lambda1, lambda2, engine, gc,\n                                                 dc):\n        var, n, z, grad = inputs\n        # generate fake subset manually because hypothesis is too complicated :)\n        indices = np.arange(var.shape[0])\n        indices = indices[indices % 2 == 0]\n        grad = grad[indices]\n        n = np.abs(n)\n        nz = np.stack([n, z], axis=-1)\n        alpha = np.array(alpha).astype(np.float32)\n        op = core.CreateOperator(\n            \"SparseFtrl\",\n            [\"var\", \"nz\", \"indices\", \"grad\", \"alpha\"],\n            [\"var\", \"nz\"],\n            beta=beta, lambda1=lambda1, lambda2=lambda2,\n            engine=engine,\n            device_option=gc)\n        self.assertDeviceChecks(\n            dc, op, [var, nz, indices, grad, alpha], [0])\n\n        # Reference\n        def ftrl(w, nz, i, g, alpha):\n            sw, snz = self._dense_ftrl_send_alpha_by_input(beta, lambda1,\n                                                           lambda2, w[i], nz[i],\n                                                           g, alpha)\n            w[i] = sw\n            nz[i] = snz\n            return (w, nz)\n\n        self.assertReferenceChecks(gc, op, [var, nz, indices, grad, alpha],\n                                   ftrl)\n\n    @given(input=hu.tensor(max_value=20,\n                           max_dim=1,\n                           dtype=np.int32,\n                           elements=st.integers(min_value=0, max_value=10)),\n           with_remapping=st.booleans(),\n           **hu.gcs)\n    def test_unique(self, input, with_remapping, gc, dc):\n        op = core.CreateOperator(\n            \"Unique\",\n            [\"input\"],\n            [\"unique\"] + ([\"remapping\"] if with_remapping else []),\n            device_option=gc)\n        self.assertDeviceChecks(dc, op, [input], [0])\n\n        # Validator\n        def unique_valid(input, unique, remapping=None):\n            self.assertEqual(unique.size, len(set(input)))\n            self.assertEqual(sorted(unique), sorted(set(input)))\n            if with_remapping:\n                self.assertEqual(remapping.shape, input.shape)\n                remapped = [unique[remapping[i]] for i in range(len(input))]\n                np.testing.assert_array_equal(remapped, input)\n\n        self.assertValidationChecks(gc, op, [input], unique_valid)\n\n    @given(prediction=hu.arrays(dims=[10, 3],\n                                elements=st.floats(allow_nan=False,\n                                                   allow_infinity=False,\n                                                   min_value=0,\n                                                   max_value=1)),\n           labels=hu.arrays(dims=[10],\n                            dtype=np.int32,\n                            elements=st.integers(min_value=0,\n                                                 max_value=3 - 1)),\n           top_k=st.integers(min_value=1, max_value=3),\n           **hu.gcs)\n    def test_accuracy(self, prediction, labels, top_k, gc, dc):\n        if(top_k > 1):\n            gc = hu.cpu_do\n\n        op = core.CreateOperator(\n            \"Accuracy\",\n            [\"prediction\", \"labels\"],\n            [\"accuracy\"],\n            top_k=top_k,\n            device_option=gc\n        )\n\n        def op_ref(prediction, labels, top_k):\n            N = prediction.shape[0]\n            correct = 0\n            for i in range(0, len(prediction)):\n                pred_sorted = sorted(\n                    ([item, j] for j, item in enumerate(prediction[i])),\n                    key=lambda x: x[0],\n                    reverse=True\n                )\n                max_ids = [x[1] for x in pred_sorted[0:top_k]]\n                for m in max_ids:\n                    if m == labels[i]:\n                        correct += 1\n            accuracy = correct / N\n            return (accuracy,)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[prediction, labels, top_k],\n            reference=op_ref)\n\n    @given(target_probabilities=hu.arrays(\n        dims=[10], elements=st.floats(allow_nan=False,\n                                      allow_infinity=False,\n                                      min_value=0.01,\n                                      max_value=1)),\n           **hu.gcs)\n    def test_perplexity(self, target_probabilities, gc, dc):\n        op = core.CreateOperator(\n            \"Perplexity\",\n            [\"target_probabilities\"],\n            [\"perplexity\"]\n        )\n\n        def op_ref(target_probabilities):\n            N = target_probabilities.shape[0]\n            perplexities = np.power(target_probabilities, -1.0 / N)\n            perplexity = reduce(lambda x, y: x * y, perplexities)\n            return (perplexity,)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[target_probabilities],\n            reference=op_ref)\n\n    @given(lengths=st.lists(st.integers(min_value=0, max_value=10),\n                            min_size=0,\n                            max_size=10),\n           **hu.gcs_cpu_only)\n    def test_lengths_to_segment_ids(self, lengths, gc, dc):\n        op = core.CreateOperator(\n            \"LengthsToSegmentIds\",\n            [\"lengths\"],\n            [\"segment_ids\"])\n\n        def op_ref(lengths):\n            sids = []\n            for i, l in enumerate(lengths):\n                sids.extend(l * [i])\n            return (np.array(sids, dtype=np.int32), )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[np.array(lengths, dtype=np.int32)],\n            reference=op_ref)\n\n    @given(lengths=st.lists(st.integers(min_value=0, max_value=10),\n                            min_size=0,\n                            max_size=10),\n           **hu.gcs_cpu_only)\n    def test_lengths_range_fill(self, lengths, gc, dc):\n        op = core.CreateOperator(\n            \"LengthsRangeFill\",\n            [\"lengths\"],\n            [\"increasing_seq\"])\n\n        def op_ref(lengths):\n            sids = []\n            for _, l in enumerate(lengths):\n                sids.extend(list(range(l)))\n            return (np.array(sids, dtype=np.int32), )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[np.array(lengths, dtype=np.int32)],\n            reference=op_ref)\n\n    @given(**hu.gcs_cpu_only)\n    def test_segment_ids_to_ranges(self, gc, dc):\n        lengths = [4, 6, 3, 2, 0, 4]\n        op = core.CreateOperator(\n            \"SegmentIdsToRanges\",\n            [\"segment_ids\"],\n            [\"ranges\"])\n\n        def op_ref(segment_ids):\n            ranges = [np.array([0, 0], dtype=np.int32)]\n            prev = 0\n            for i, sid in enumerate(segment_ids):\n                while sid != prev:\n                    prev += 1\n                    ranges.append(np.array([i, 0], dtype=np.int32))\n                ranges[-1][1] += 1\n            return (np.array(ranges, dtype=np.int32), )\n\n        def lengths_to_segment_ids(lengths):\n            sids = []\n            for i, l in enumerate(lengths):\n                sids.extend(l * [i])\n            return (np.array(sids, dtype=np.int32), )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=np.array(lengths_to_segment_ids(lengths), dtype=np.int32),\n            reference=op_ref)\n\n    @given(lengths=st.lists(st.integers(min_value=0, max_value=10),\n                            min_size=0,\n                            max_size=10),\n           **hu.gcs_cpu_only)\n    def test_lengths_to_ranges(self, lengths, gc, dc):\n        op = core.CreateOperator(\n            \"LengthsToRanges\",\n            [\"lengths\"],\n            [\"ranges\"])\n\n        def op_ref(x):\n            if not x.size:\n                return (x.reshape((0, 2)), )\n            return (np.column_stack((np.concatenate(([0], np.cumsum(x)[:-1])),\n                                     x)), )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[np.array(lengths, dtype=np.int32)],\n            reference=op_ref)\n\n    @given(prediction=hu.arrays(dims=[10, 3],\n                                elements=st.floats(allow_nan=False,\n                                                   allow_infinity=False,\n                                                   min_value=0,\n                                                   max_value=1)),\n           labels=hu.arrays(dims=[10],\n                            dtype=np.int32,\n                            elements=st.integers(min_value=0,\n                                                 max_value=3 - 1)),\n            **hu.gcs)\n    def test_multi_class_accuracy(self, prediction, labels, gc, dc):\n        op = core.CreateOperator(\n            \"MultiClassAccuracy\",\n            [\"prediction\", \"labels\"],\n            [\"accuracies\", \"amounts\"]\n        )\n\n        def op_ref(prediction, labels):\n            N = prediction.shape[0]\n            D = prediction.shape[1]\n            accuracies = np.empty(D, dtype=float)\n            accuracies.fill(0)\n            amounts = np.empty(D, dtype=int)\n            amounts.fill(0)\n            max_ids = np.argmax(prediction, axis=1)\n            for i in range(0, N):\n                max_id = max_ids[i]\n                label_id = labels[i]\n                if max_id == label_id:\n                    accuracies[label_id] += 1\n                amounts[label_id] += 1\n            for i in range(0, D):\n                amount = amounts[i]\n                if amount:\n                    accuracies[i] /= amount\n            return (accuracies, amounts,)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[prediction, labels],\n            reference=op_ref)\n\n    @given(lengths=st.lists(st.integers(min_value=0, max_value=10),\n                            min_size=0,\n                            max_size=10),\n           **hu.gcs_cpu_only)\n    def test_segment_ids_to_lengths(self, lengths, gc, dc):\n        op = core.CreateOperator(\n            \"SegmentIdsToLengths\",\n            [\"segment_ids\"],\n            [\"lengths\"])\n\n        def lengths_to_ids(lengths):\n            sids = []\n            for i, l in enumerate(lengths):\n                sids.extend(l * [i])\n            return sids\n\n        segment_ids = lengths_to_ids(lengths)\n\n        def ids_to_lengths(ids):\n            ids_length = len(ids)\n            if ids_length == 0:\n                return (np.array([], dtype=np.int32),)\n\n            lengths = []\n            # segment id starts with 0\n            prev_id = -1\n            tmp_length = 0\n            for idx in range(ids_length):\n                cur_id = ids[idx]\n                if cur_id != prev_id:\n                    if idx != 0:\n                        lengths.append(tmp_length)\n                    while prev_id + 1 != cur_id:\n                        lengths.append(0)\n                        prev_id += 1\n                    prev_id = cur_id\n                    tmp_length = 0\n                tmp_length += 1\n            lengths.append(tmp_length)\n            return (np.array(lengths, dtype=np.int32),)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[np.array(segment_ids, dtype=np.int32)],\n            reference=ids_to_lengths)\n\n    @given(lengths=st.lists(st.integers(min_value=1, max_value=10),\n                            min_size=0,\n                            max_size=10),\n            power=st.sampled_from([0.5, 1.0, 1.5, 2.0]),\n           **hu.gcs_cpu_only)\n    def test_lengths_to_weights(self, lengths, power, gc, dc):\n        op = core.CreateOperator(\n            \"LengthsToWeights\",\n            [\"lengths\"],\n            [\"weights\"],\n            power=power)\n\n        def lengths_to_weights(lengths):\n            weighted_length = []\n            for l in lengths:\n                weighted_length.extend(l * [1 / pow(l, power)])\n\n            return (np.array(weighted_length, dtype=float),)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[np.array(lengths, dtype=np.int32)],\n            reference=lengths_to_weights)\n\n    @given(input_tensor=hu.arrays(\n        dims=[10], elements=st.floats(allow_nan=False,\n                                      allow_infinity=False)),\n           **hu.gcs)\n    def test_abs(self, input_tensor, gc, dc):\n        op = core.CreateOperator(\n            \"Abs\",\n            [\"input\"],\n            [\"output\"]\n        )\n\n        def abs_ref(input_tensor):\n            return (np.abs(input_tensor),)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[input_tensor],\n            reference=abs_ref)\n\n    @given(input_tensor=hu.arrays(\n        dims=[10], elements=st.floats(min_value=-10,\n                                      max_value=10)),\n           **hu.gcs)\n    def test_cos(self, input_tensor, gc, dc):\n        op = core.CreateOperator(\n            \"Cos\",\n            [\"input\"],\n            [\"output\"]\n        )\n\n        def cos_ref(input_tensor):\n            return (np.cos(input_tensor),)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[input_tensor],\n            reference=cos_ref)\n\n    @given(input_tensor=hu.arrays(\n        dims=[10], elements=st.floats(min_value=-10,\n                                      max_value=10)),\n           **hu.gcs)\n    def test_sin(self, input_tensor, gc, dc):\n        op = core.CreateOperator(\n            \"Sin\",\n            [\"input\"],\n            [\"output\"]\n        )\n\n        def sin_ref(input_tensor):\n            return (np.sin(input_tensor),)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[input_tensor],\n            reference=sin_ref)\n\n    @given(input_tensor=hu.arrays(\n        dims=[10], elements=st.floats(allow_nan=False,\n                                      allow_infinity=False)),\n           **hu.gcs)\n    def test_exp(self, input_tensor, gc, dc):\n        op = core.CreateOperator(\n            \"Exp\",\n            [\"input\"],\n            [\"output\"]\n        )\n\n        def exp_ref(input_tensor):\n            return (np.exp(input_tensor),)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[input_tensor],\n            reference=exp_ref)\n\n    @given(input_tensor=hu.arrays(\n        dims=[10], elements=st.floats(min_value=1,\n                                      max_value=10000)),\n           **hu.gcs_cpu_only)\n    def test_log(self, input_tensor, gc, dc):\n        op = core.CreateOperator(\n            \"Log\",\n            [\"input\"],\n            [\"output\"]\n        )\n\n        def log_ref(input_tensor):\n            return (np.log(input_tensor),)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[input_tensor],\n            reference=log_ref)\n        self.assertGradientChecks(gc, op, [input_tensor], 0, [0])\n\n    def test_blobs_dequeue_timeout(self):\n        op = core.CreateOperator(\n            \"CreateBlobsQueue\",\n            [],\n            [\"queue\"],\n            capacity=5,\n            num_blobs=1)\n        self.ws.run(op)\n        t = time.time()\n        op = core.CreateOperator(\n            \"DequeueBlobs\",\n            [\"queue\"],\n            [\"out\"],\n            timeout_secs=0.2)\n        self.assertRaises(RuntimeError, lambda: self.ws.run(op))\n        t = time.time() - t\n        self.assertGreater(t, 0.19)\n\n    @given(num_threads=st.integers(1, 10),  # noqa\n           num_elements=st.integers(1, 100),\n           capacity=st.integers(1, 5),\n           num_blobs=st.integers(1, 3),\n           do=st.sampled_from(hu.device_options))\n    def test_blobs_queue_threading(self, num_threads, num_elements,\n                                   capacity, num_blobs, do):\n        \"\"\"\n        - Construct matrices of size N x D\n        - Start K threads\n        - Push all N rows into the queue of capacity C\n        - Pull all N rows out of the queue.\n        - Verify that the output matrices are permutation of the rows of the\n          original matrices.\n        \"\"\"\n        import threading\n        try:\n            import queue\n        except ImportError:\n            # Py3\n            import Queue as queue\n        op = core.CreateOperator(\n            \"CreateBlobsQueue\",\n            [],\n            [\"queue\"],\n            capacity=capacity,\n            num_blobs=num_blobs,\n            device_option=do)\n        self.ws.run(op)\n\n        xs = [np.random.randn(num_elements, 5).astype(np.float32)\n              for _ in range(num_blobs)]\n        q = queue.Queue()\n        for i in range(num_elements):\n            q.put([x[i] for x in xs])\n\n        def enqueue(t):\n            while True:\n                feed_blobs = [\"x_{}_{}\".format(i, t) for i in range(num_blobs)]\n                op = core.CreateOperator(\n                    \"EnqueueBlobs\",\n                    [\"queue\"] + feed_blobs,\n                    feed_blobs,\n                    device_option=do)\n                try:\n                    elems = q.get_nowait()\n                    for elem, feed_blob in zip(elems, feed_blobs):\n                        self.ws.create_blob(feed_blob).feed(\n                            elem, device_option=do)\n                    self.ws.run(op)\n                except queue.Empty:\n                    return\n\n        # Create all blobs before racing on multiple threads\n        # (blob creation is not threadsafe)\n        for t in range(num_threads):\n            for i in range(num_blobs):\n                self.ws.create_blob(\"x_{}_{}\".format(i, t))\n\n        threads = [threading.Thread(target=enqueue, args=(t,))\n                   for t in range(num_threads)]\n        for thread in threads:\n            thread.start()\n\n        for n in range(num_elements):\n            dequeue_blobs = [\"y_{}_{}\".format(i, n) for i in range(num_blobs)]\n            op = core.CreateOperator(\n                \"DequeueBlobs\",\n                [\"queue\"],\n                dequeue_blobs,\n                device_option=do)\n            self.ws.run(op)\n        for thread in threads:\n            thread.join()\n        op = core.CreateOperator(\"CloseBlobsQueue\", [\"queue\"], [])\n        self.ws.run(op)\n        ys = [np.vstack([self.ws.blobs[\"y_{}_{}\".format(i, n)].fetch()\n                         for n in range(num_elements)])\n              for i in range(num_blobs)]\n        for i in range(num_blobs):\n            self.assertEqual(ys[i].shape, xs[i].shape)\n            for j in range(num_elements):\n                # Verify that the rows of the returned blob are a\n                # permutation. The order may be different due to\n                # different threads racing.\n                self.assertTrue(\n                    any(np.array_equal(xs[i][j], ys[i][k])\n                        for k in range(num_elements)))\n\n    @given(num_producers=st.integers(1, 10),\n           num_consumers=st.integers(1, 10),\n           capacity=st.integers(1, 5),\n           num_blobs=st.integers(1, 3),\n           do=st.sampled_from(hu.device_options))\n    def test_safe_blobs_queue(self, num_producers, num_consumers,\n                              capacity, num_blobs, do):\n        init_net = core.Net('init_net')\n        queue = init_net.CreateBlobsQueue(\n            [], 1, capacity=capacity, num_blobs=num_blobs)\n        producer_steps = []\n        truth = 0\n        for i in range(num_producers):\n            name = 'producer_%d' % i\n            net = core.Net(name)\n            blobs = [net.ConstantFill([], 1, value=1.0, run_once=False)\n                     for times in range(num_blobs)]\n            status = net.NextName()\n            net.SafeEnqueueBlobs([queue] + blobs, blobs + [status])\n            count = (i + 1) * 10\n            step = core.execution_step(name, net, num_iter=count)\n            truth += count\n            producer_steps.append(step)\n        producer_exit_net = core.Net('producer_exit_net')\n        producer_exit_net.CloseBlobsQueue([queue], 0)\n        producer_step = core.execution_step('producer', [\n            core.execution_step(\n                'producers', producer_steps, concurrent_substeps=True),\n            core.execution_step('producer_exit', producer_exit_net)]\n        )\n\n        consumer_steps = []\n        counters = []\n        const_1 = init_net.ConstantFill([], 1, value=1.0)\n        for i in range(num_consumers):\n            name = 'consumer_%d' % i\n            net1 = core.Net(name)\n            blobs = net1.SafeDequeueBlobs([queue], num_blobs + 1)\n            status = blobs[-1]\n\n            net2 = core.Net(name + '_counter')\n            counter = init_net.ConstantFill([], 1, value=0.0)\n            counters.append(counter)\n            net2.Add([counter, const_1], counter)\n            consumer_steps.append(core.execution_step(\n                name, [net1, net2], should_stop_blob=status))\n        consumer_step = core.execution_step(\n            'consumer', consumer_steps, concurrent_substeps=True)\n\n        init_step = core.execution_step('init', init_net)\n        worker_step = core.execution_step(\n            'worker', [consumer_step, producer_step], concurrent_substeps=True)\n\n        plan = core.Plan('test')\n        plan.AddStep(init_step)\n        plan.AddStep(worker_step)\n\n        self.ws.run(plan)\n        v = 0\n        for counter in counters:\n            v += self.ws.blobs[str(counter)].fetch().tolist()\n        self.assertEqual(v, truth)\n\n    @given(num_queues=st.integers(1, 5),\n           num_iter=st.integers(5, 10),\n           capacity=st.integers(1, 5),\n           num_blobs=st.integers(1, 3))\n    def test_weighted_sample_blobs_queue(\n        self, num_queues, num_iter, capacity, num_blobs\n    ):\n        # Create BlobsQueue for each input queue\n        print(\"num_queues\", num_queues)\n        init_net = core.Net('init_net')\n        queues = [\n            init_net.CreateBlobsQueue(\n                [], 1, capacity=capacity, num_blobs=num_blobs\n            ) for _ in range(num_queues)\n        ]\n\n        # Create multiple producer nets and one producer exist net\n        producer_steps = []\n        producer_exit_nets = []\n        for i in range(num_queues):\n            name = 'producer_%d' % i\n            net = core.Net(name)\n            blobs = [net.ConstantFill([], 1, value=1.0, run_once=False)\n                     for _ in range(num_blobs)]\n            status = net.NextName()\n            net.SafeEnqueueBlobs([queues[i]] + blobs, blobs + [status])\n\n            exit_net = core.Net('producer_exit_%d' % i)\n            exit_net.CloseBlobsQueue(queues[i], 0)\n            producer_exit_nets.append(exit_net)\n\n            step = core.execution_step(\n                name, [\n                    core.execution_step(\n                        'producer_%d' % i, [net], num_iter=num_iter\n                    ),\n                    core.execution_step('producer_exit_%d' % i, [exit_net]),\n                ]\n            )\n            producer_steps.append(step)\n\n        producer_step = core.execution_step(\n            'producer', [\n                core.execution_step(\n                    'producers',\n                    producer_steps,\n                    concurrent_substeps=True,\n                ),\n            ]\n        )\n\n        status_lst = []\n\n        def append(ins, outs):\n            status_lst.append(ins)\n\n        # Create one consumer dequeue net and one consumer exist net\n        consumer_net = core.Net('weight_sample_dequeue_net')\n        table_idx_blob = np.random.randint(low=-1, high=num_blobs, size=1)\n        blobs = consumer_net.WeightedSampleDequeueBlobs(\n            queues,\n            num_blobs + 1,\n            weights=np.random.uniform(low=0.0, high=1.0, size=(num_queues,)),\n            table_idx_blob=table_idx_blob[0],\n        )\n        status = blobs[-1]\n        consumer_net.Python(append)(status)\n\n        consumer_step = core.execution_step(\n            'consumer',\n            [\n                core.execution_step(\n                    'consumer', [consumer_net], should_stop_blob=status\n                ),\n                core.execution_step('producer_exit', producer_exit_nets)\n            ]\n        )\n\n        init_step = core.execution_step('init', init_net)\n        worker_step = core.execution_step(\n            'worker', [producer_step, consumer_step], concurrent_substeps=True)\n\n        plan = core.Plan('test')\n        plan.AddStep(init_step)\n        plan.AddStep(worker_step)\n\n        self.ws.run(plan)\n        assert len(status_lst) >= num_iter + 1\n        assert len(status_lst) <= num_iter * num_queues + 1\n\n    @given(\n        data=hu.tensor(),\n        **hu.gcs_cpu_only)\n    def test_squeeze_expand_dims(self, data, gc, dc):\n        dims = [0, 0]\n        if len(data.shape) > 2:\n            dims.append(2)\n        op = core.CreateOperator(\n            \"ExpandDims\",\n            [\"data\"],\n            [\"expanded\"],\n            dims=dims)\n\n        def expand_dims_ref(data, *args, **kw):\n            inc_dims = list(set(dims))\n            inc_dims.sort()\n            r = data\n            for dim in inc_dims:\n                r = np.expand_dims(r, axis=dim)\n            return (r, )\n\n        def squeeze_ref(data, *args, **kw):\n            dec_dims = list(set(dims))\n            dec_dims.sort(reverse=True)\n            r = data\n            for dim in dec_dims:\n                r = np.squeeze(r, axis=dim)\n            return (r, )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[data],\n            reference=expand_dims_ref,\n            output_to_grad='expanded',\n            grad_reference=squeeze_ref)\n\n    @given(**hu.gcs_cpu_only)\n    def test_tt_layer(self, gc, dc):\n        seed = 1234\n        np.random.seed(seed)\n\n        inp_sizes = [2, 2, 2, 2]\n        out_sizes = [2, 2, 2, 2]\n        tt_ranks = [1, 3, 3, 3, 1]\n\n        op = core.CreateOperator(\n            \"TT\",\n            [\"X\", \"b\", \"cores\"],\n            [\"Y\"],\n            inp_sizes=inp_sizes,\n            out_sizes=out_sizes,\n            tt_ranks=tt_ranks,\n        )\n\n        X = np.expand_dims(\n            np.random.rand(16).astype(np.float32), axis=0)\n        b = np.array([0] * 16).astype(np.float32)\n        cores = tt_core.init_tt_cores(inp_sizes, out_sizes, tt_ranks)\n\n        self.ws.create_blob(\"X\").feed(X)\n        self.ws.create_blob(\"b\").feed(b)\n        self.ws.create_blob(\"cores\").feed(cores)\n        self.ws.run(op)\n\n        Y = self.ws.blobs[(\"Y\")].fetch()\n        Y = Y.reshape([16])\n\n        golden = np.array([-9.51763490e-07, -1.28442286e-06,\n                           -2.86281141e-07, 2.28865644e-07,\n                           -1.96180017e-06, -1.78920531e-06,\n                           9.31094666e-07, -2.04273989e-07,\n                           1.70017107e-06, 1.64845711e-06,\n                           -1.06099132e-06, -4.69111137e-07,\n                           6.57552358e-08, -1.28942040e-08,\n                           -2.29114004e-07, -1.04262714e-06])\n\n        # This golden array is dependent on the specified inp_sizes, out_sizes,\n        # tt_ranks, and seed. Changing these will cause the test to fail.\n        self.assertAlmostEqual(np.linalg.norm(golden - Y), 0, delta=1e-10)\n\n    @given(num_workers=st.integers(1, 10),\n           net_type=st.sampled_from(\n               [\"simple\", \"dag\"] +\n               ([\"async_dag\"] if workspace.has_gpu_support else [])),\n           do=st.sampled_from(hu.device_options))\n    def test_dag_net_forking(self, net_type, num_workers, do):\n        from caffe2.python.model_helper import ModelHelper\n        from caffe2.python import brew\n        m = ModelHelper(name=\"test_model\")\n        n = 10\n        d = 2\n        depth = 2\n        iters = 5\n        np.random.seed(1701)\n        # Build a binary tree of FC layers, summing at each node.\n        for i in reversed(range(depth)):\n            for j in range(2 ** i):\n                bottom_1 = \"{}_{}\".format(i + 1, 2 * j)\n                bottom_2 = \"{}_{}\".format(i + 1, 2 * j + 1)\n                mid_1 = \"{}_{}_m\".format(i + 1, 2 * j)\n                mid_2 = \"{}_{}_m\".format(i + 1, 2 * j + 1)\n                top = \"{}_{}\".format(i, j)\n                brew.fc(\n                    m,\n                    bottom_1, mid_1,\n                    dim_in=d, dim_out=d,\n                    weight_init=('ConstantFill', dict(value=np.random.randn())),\n                    bias_init=('ConstantFill', dict(value=np.random.randn())))\n                brew.fc(\n                    m,\n                    bottom_2, mid_2,\n                    dim_in=d, dim_out=d,\n                    weight_init=('ConstantFill', dict(value=np.random.randn())),\n                    bias_init=('ConstantFill', dict(value=np.random.randn())))\n                m.net.Sum([mid_1, mid_2], top)\n        m.net.SquaredL2Distance([\"0_0\", \"label\"], \"xent\")\n        m.net.AveragedLoss(\"xent\", \"loss\")\n        input_to_grad = m.AddGradientOperators([\"loss\"])\n        m.Proto().device_option.CopyFrom(do)\n        m.param_init_net.Proto().device_option.CopyFrom(do)\n\n        m.Proto().type = net_type\n        m.Proto().num_workers = num_workers\n\n        self.ws.run(m.param_init_net)\n\n        print(str(m.Proto()))\n\n        def run():\n            import numpy as np\n            np.random.seed(1701)\n            input_blobs = [\"{}_{}\".format(depth, j) for j in range(2 ** depth)]\n            for input_blob in input_blobs:\n                self.ws.create_blob(input_blob).feed(\n                    np.random.randn(n, d).astype(np.float32),\n                    device_option=do)\n                self.ws.create_blob(\"label\").feed(\n                    np.random.randn(n, d).astype(np.float32),\n                    device_option=do)\n            self.ws.run(m.net)\n            gradients = [\n                self.ws.blobs[str(input_to_grad[input_blob])].fetch()\n                for input_blob in input_blobs]\n            return gradients\n\n        outputs = [run() for _ in range(iters)]\n        for output in outputs[1:]:\n            np.testing.assert_array_equal(outputs[0], output)\n            self.assertAlmostEqual(np.sum(np.square(output)), 91.81752,\n                                   delta=1e-2)\n\n    @given(input=hu.tensor(min_dim=2, max_dim=6, dtype=np.int32,\n                           elements=st.integers(min_value=0,\n                                                max_value=2**32 - 1)),\n           slice_dim=st.integers(),\n           a=st.integers(),\n           b=st.integers(),\n           is_empty=st.booleans(),\n           **hu.gcs_cpu_only)\n    def test_slice(self, input, slice_dim, a, b, is_empty, gc, dc):\n        slice_dim = slice_dim % len(input.shape)\n        if (is_empty):\n            input = np.random.rand(*([0] + list(input.shape))).astype(np.int32)\n            slice_dim += 1\n\n        a = a % input.shape[slice_dim]\n        b = b % input.shape[slice_dim] + 1\n        start_vec = np.zeros(len(input.shape), dtype=np.int32)\n        end_vec = np.ones(len(input.shape), dtype=np.int32) * -1\n        start_vec[slice_dim] = min(a, b)\n        end_vec[slice_dim] = max(a, b)\n        op = core.CreateOperator(\n            \"Slice\",\n            [\"input\", \"start\", \"end\"],\n            [\"output\"])\n\n        def slice_ref(x, s, e):\n            if len(s.shape) == 0:\n                return x\n            slc = [slice(si, None if ei == -1 else ei) for si, ei in zip(s, e)]\n            return (x[slc], )\n\n        self.assertReferenceChecks(gc, op, [input, start_vec, end_vec],\n                                   slice_ref)\n\n    @given(data=hu.tensor(), **hu.gcs_cpu_only)\n    def test_shape(self, data, gc, dc):\n        op = core.CreateOperator(\"Shape\", [\"data\"], [\"shape\"])\n        self.assertReferenceChecks(gc, op, [data], lambda x: (x.shape, ))\n\n    @given(data=hu.tensor(), **hu.gcs_cpu_only)\n    def test_has_elements(self, data, gc, dc):\n        op = core.CreateOperator(\"HasElements\", [\"data\"], [\"has_elements\"])\n        self.assertReferenceChecks(gc, op, [data], lambda x: (len(x) > 0, ))\n\n        op = core.CreateOperator(\"IsEmpty\", [\"data\"], [\"is_empty\"])\n        self.assertReferenceChecks(gc, op, [data], lambda x: (len(x) == 0, ))\n\n    @given(initial_iters=st.integers(0, 100),\n           max_iters=st.integers(0, 100))\n    def test_should_stop_as_criteria_net_execution_step(\n            self, initial_iters, max_iters):\n        net = core.Net(\"net\")\n        net.Iter([\"iter\"], [\"iter\"])\n        self.ws.create_blob(\"iter\").feed(\n            np.asarray([initial_iters]).astype(np.int64))\n        self.ws.create_blob(\"num_iters\").feed(\n            np.asarray([max_iters]).astype(np.int64))\n        criteria_net = core.Net(\"criteria\")\n        criteria_net.GE([\"iter\", \"num_iters\"], [\"stop\"])\n        criteria_net.Proto().external_output.extend([\"stop\"])\n\n        plan = core.Plan('plan')\n        plan.AddStep(core.execution_step(\n            'step', [criteria_net, net],\n            should_stop_blob=core.BlobReference(\"stop\")))\n        self.ws.run(plan)\n        iters = self.ws.blobs[(\"iter\")].fetch()\n        self.assertEqual(iters.dtype, np.int64)\n        self.assertEqual(iters[0], max(initial_iters, max_iters))\n\n    def test_disabled_execution_step(self):\n        def createNets(i, disabled):\n            should_stop = 'should_stop_{}'.format(i)\n            output = 'output_{}'.format(i)\n\n            # init content and stop signal\n            init = core.Net(\"init_{}\".format(i))\n            init.ConstantFill(\n                [],\n                [output],\n                shape=[1],\n                value=0.0\n            )\n            init.Cast([output], [should_stop], to='bool')\n\n            # decide if disabled or not\n            criterion = core.Net(\"criterion_{}\".format(i))\n            tmp = criterion.ConstantFill(\n                [],\n                shape=[1],\n                value=1.0 if disabled else 0.0\n            )\n            criterion.Cast([tmp], [should_stop], to='bool')\n            criterion.Proto().external_output.extend([should_stop])\n\n            # the body net is just to turn a 0 blob to 1\n            net = core.Net(\"net_{}\".format(i))\n            net.ConstantFill(\n                [],\n                [output],\n                shape=[1],\n                value=1.0\n            )\n\n            # always end the loop\n            ender = core.Net(\"ender_{}\".format(i))\n            tmp = ender.ConstantFill(\n                [],\n                shape=[1],\n                value=1.0\n            )\n            ender.Cast([tmp], [should_stop], to='bool')\n            ender.Proto().external_output.extend([should_stop])\n\n            return [init, criterion, net, ender]\n\n        nets = [createNets(1, False),\n                createNets(2, True),\n                createNets(3, False)]\n        steps = [\n            core.execution_step(\n                'step_1', nets[0],\n                should_stop_blob=core.BlobReference('should_stop_1')),\n            core.execution_step(\n                'step_2', nets[1],\n                should_stop_blob=core.BlobReference('should_stop_2')),\n            core.execution_step('step_3', nets[2])\n        ]\n        expected = [1.0, 0.0, 1.0]\n\n        plan = core.Plan('plan')\n        plan.AddStep(core.execution_step('all_steps', steps, num_iter=3))\n        self.ws.run(plan)\n\n        for i, _ in enumerate(nets):\n            self.assertEqual(\n                self.ws.blobs['output_{}'.format(i + 1)].fetch()[0],\n                expected[i])\n\n    @given(initial_iters=st.integers(0, 100),\n           num_iters=st.integers(0, 100))\n    def test_iter_count_with_execution_step(self, initial_iters, num_iters):\n        net = core.Net(\"net\")\n        net.Iter([\"iter\"], [\"iter\"])\n        self.ws.create_blob(\"iter\").feed(\n            np.asarray([initial_iters]).astype(np.int64))\n\n        step = core.ExecutionStep(\"step\", [net])\n        step.SetIter(num_iters)\n\n        plan = core.Plan(\"plan\")\n        plan.AddStep(step)\n        self.ws.run(plan)\n        iters = self.ws.blobs[(\"iter\")].fetch()\n        self.assertEqual(iters.dtype, np.int64)\n        self.assertEqual(iters[0], initial_iters + num_iters)\n\n    @given(initial_iters=st.integers(0, 100),\n           num_iters=st.integers(0, 100),\n           num_nets=st.integers(0, 5))\n    def test_atomic_iter_with_concurrent_steps(self, initial_iters, num_iters,\n                                               num_nets):\n        init_net = core.Net(\"init_net\")\n        iter_mutex = init_net.CreateMutex([], [\"iter_mutex\"])\n        self.ws.create_blob(\"iter\").feed(\n            np.asarray([initial_iters]).astype(np.int64))\n        concurrent_steps = core.ExecutionStep(\"concurrent_steps\",\n                                              num_iter=num_iters)\n        for i in range(num_nets):\n            net = core.Net(\"net_{}\".format(i))\n            net.AtomicIter([iter_mutex, \"iter\"], [\"iter\"])\n            step = core.ExecutionStep(\"step\", [net])\n            concurrent_steps.AddSubstep(step)\n\n        concurrent_steps.SetConcurrentSubsteps(True)\n        plan = core.Plan(\"plan\")\n        plan.AddStep(concurrent_steps)\n\n        self.ws.run(init_net)\n        self.ws.run(plan)\n        iters = self.ws.blobs[(\"iter\")].fetch()\n        self.assertEqual(iters.dtype, np.int64)\n        self.assertEqual(iters[0], initial_iters + num_iters * num_nets)\n\n    @given(a=hu.tensor(),\n           src=st.sampled_from(list(viewkeys(_NUMPY_TYPE_TO_ENUM))),\n           dst=st.sampled_from(list(viewkeys(_NUMPY_TYPE_TO_ENUM))),\n           use_name=st.booleans(),\n           **hu.gcs)\n    def test_cast(self, a, src, dst, use_name, gc, dc):\n        a = a.astype(src)\n\n        # Casting from a float type outside the range of the integral\n        # type is UB.\n        ftypes = [np.float32, np.float64]\n        if src in ftypes and dst not in ftypes and dst is not np.bool:\n            info = np.iinfo(dst)\n            a = np.clip(a, info.min, info.max)\n\n        def ref(data):\n            return [data.astype(dst)]\n\n        to = _NUMPY_TYPE_TO_ENUM[dst]\n        if use_name:\n            to = TensorProto.DataType.Name(to).lower()\n        op = core.CreateOperator('Cast', [\"X\"], [\"Y\"], to=to)\n        self.assertDeviceChecks(dc, op, [a], [0])\n        out, = self.assertReferenceChecks(gc, op, [a], ref)\n        self.assertEqual(dst, out.dtype)\n\n    @given(a=hu.tensor(),\n           eps=st.floats(min_value=1e-4, max_value=1e-2),\n           a_grad=hu.tensor(elements=st.floats(min_value=0.01, max_value=0.99)),\n           eps_grad=st.floats(min_value=1e-4, max_value=1e-3),\n           **hu.gcs)\n    def test_logit(self, a, eps, a_grad, eps_grad, gc, dc):\n        def ref(data):\n            data = np.clip(data, eps, 1.0 - eps)\n            return (np.log(data / (1 - data)), )\n        # forward testing carried out in the full range of input\n        # to ensure original test coverage.\n        # gradient test carried out with reduced input range\n        # because the sharp increase of the logit curve at 0 and 1\n        # error increases dramtically when input is close to 0 or 1\n        # and it will fail the test.\n        # So we only run gradient test in the range of (0.01, 0.99)\n        # very occationally, test may fail due to random accumulated error\n        # reduce test range to (0.02, 0.98) will improve test stability\n        op = core.CreateOperator('Logit', [\"X\"], [\"Y\"], eps=eps)\n        self.assertDeviceChecks(dc, op, [a], [0])\n        self.assertReferenceChecks(gc, op, [a], ref)\n        op_grad = core.CreateOperator('Logit', [\"X\"], [\"Y\"], eps=eps_grad)\n        self.assertGradientChecks(gc, op_grad, [a_grad], 0, [0],\n                                  threshold=0.04, stepsize=2e-3)\n\n    @given(a=hu.tensor(elements=st.floats(allow_nan=True)),\n           value=st.floats(min_value=-10, max_value=10),\n           **hu.gcs)\n    def test_replace_nan(self, a, value, gc, dc):\n        def ref(data):\n            out = np.copy(data)\n            out[np.isnan(data)] = value\n            return (out, )\n\n        op = core.CreateOperator('ReplaceNaN', [\"X\"], [\"Y\"], value=value)\n        self.assertDeviceChecks(dc, op, [a], [0])\n        self.assertReferenceChecks(gc, op, [a], ref)\n\n    @given(data=_dtypes(dtypes=[np.int32, np.int64, np.float32, np.bool]).\n           flatmap(lambda dtype: hu.tensor(\n               min_dim=1, dtype=dtype, elements=hu.elements_of_type(dtype))),\n           has_input=st.booleans(),\n           has_extra_shape=st.booleans(),\n           extra_shape=st.lists(\n           min_size=1, max_size=5, elements=st.integers(1, 5)),\n           **hu.gcs)\n    def test_constant_fill(self, data, has_input, has_extra_shape, extra_shape,\n                           gc, dc):\n        dtype = data.dtype.type\n        # in opt mode, np.bool is converted into np.bool_\n        if data.dtype == np.dtype(np.bool):\n            dtype = np.bool\n\n        value = data.item(0)\n        gt_shape = data.shape\n        inputs = [data]\n        enum_type = _NUMPY_TYPE_TO_ENUM[dtype]\n\n        if has_input:\n            if has_extra_shape:\n                op = core.CreateOperator('ConstantFill', [\"X\"], [\"Y\"],\n                                         dtype=enum_type,\n                                         extra_shape=extra_shape,\n                                         value=value)\n                gt_shape += tuple(extra_shape)\n            else:\n                op = core.CreateOperator('ConstantFill', [\"X\"], [\"Y\"],\n                                         dtype=enum_type,\n                                         value=value)\n        else:\n            op = core.CreateOperator('ConstantFill', [], [\"Y\"],\n                                     dtype=enum_type,\n                                     value=value,\n                                     shape=list(gt_shape))\n            inputs = []\n\n        def ref(inputs=None):\n            outputs = np.full(shape=gt_shape, fill_value=value, dtype=dtype)\n            return [outputs]\n\n        self.assertDeviceChecks(dc, op, inputs, [0])\n        out, = self.assertReferenceChecks(gc, op, inputs, ref)\n        self.assertEqual(dtype, out.dtype)\n\n    @given(t=st.integers(1, 5),\n           n=st.integers(1, 5),\n           d=st.integers(1, 5))\n    def test_elman_recurrent_network(self, t, n, d):\n        from caffe2.python import model_helper, brew\n        np.random.seed(1701)\n        step_net = model_helper.ModelHelper(name=\"Elman\")\n        # TODO: name scope external inputs and outputs\n        step_net.Proto().external_input.extend(\n            [\"input_t\", \"seq_lengths\", \"timestep\",\n             \"hidden_t_prev\", \"gates_t_w\", \"gates_t_b\"])\n        step_net.Proto().type = \"simple\"\n        step_net.Proto().external_output.extend([\"hidden_t\", \"gates_t\"])\n        brew.fc(step_net,\n                \"hidden_t_prev\", \"gates_t\", dim_in=d, dim_out=d, axis=2)\n        step_net.net.Sum([\"gates_t\", \"input_t\"], [\"gates_t\"])\n        step_net.net.Sigmoid([\"gates_t\"], [\"hidden_t\"])\n\n        # Initialize params for step net in the parent net\n        for op in step_net.param_init_net.Proto().op:\n            workspace.RunOperatorOnce(op)\n\n        backward_ops, backward_mapping = core.GradientRegistry.GetBackwardPass(\n            step_net.Proto().op, {\"hidden_t\": \"hidden_t_grad\"})\n        backward_mapping = {\n            str(k): str(v) for k, v in viewitems(backward_mapping)\n        }\n        backward_step_net = core.Net(\"ElmanBackward\")\n        del backward_step_net.Proto().op[:]\n        backward_step_net.Proto().op.extend(backward_ops)\n        assert backward_mapping[\"input_t\"] == \"gates_t_grad\"\n        links = [\n            (\"hidden_t_prev\", \"hidden\", 0),\n            (\"hidden_t\", \"hidden\", 1),\n            (\"input_t\", \"input\", 0),\n        ]\n        link_internal, link_external, link_offset = zip(*links)\n        backward_links = [\n            (\"hidden_t_prev_grad\", \"hidden_grad\", 0),\n            (\"hidden_t_grad\", \"hidden_grad\", 1),\n            (\"gates_t_grad\", \"input_grad\", 0),\n        ]\n        backward_link_internal, backward_link_external, backward_link_offset = \\\n            zip(*backward_links)\n        backward_step_net.Proto().external_input.extend([\"hidden_t_grad\"])\n        backward_step_net.Proto().external_input.extend(\n            step_net.Proto().external_input)\n        backward_step_net.Proto().external_input.extend(\n            step_net.Proto().external_output)\n        inputs = [\"input\", \"seq_lengths\", \"gates_t_w\", \"gates_t_b\", \"hidden_input\"]\n        recurrent_inputs = [\"hidden_input\"]\n        op = core.CreateOperator(\n            \"RecurrentNetwork\",\n            inputs,\n            [\"output\", \"hidden\", \"hidden_output\", \"step_workspaces\"],\n            alias_src=[\"hidden\", \"hidden\"],\n            alias_dst=[\"output\", \"hidden_output\"],\n            alias_offset=[1, -1],\n            recurrent_states=[\"hidden\"],\n            initial_recurrent_state_ids=[\n                inputs.index(i) for i in recurrent_inputs\n            ],\n            link_internal=link_internal,\n            link_external=link_external,\n            link_offset=link_offset,\n            backward_link_internal=backward_link_internal,\n            backward_link_external=backward_link_external,\n            backward_link_offset=backward_link_offset,\n            param=[inputs.index(p) for p in step_net.params],\n            step_net=step_net.Proto(),\n            backward_step_net=backward_step_net.Proto(),\n            outputs_with_grads=[0],\n        )\n        workspace.FeedBlob(\n            \"input\", np.random.randn(t, n, d).astype(np.float32))\n        workspace.FeedBlob(\n            \"hidden_input\", np.random.randn(1, n, d).astype(np.float32))\n        workspace.FeedBlob(\n            \"seq_lengths\", np.random.randint(0, t, size=(n,)).astype(np.int32))\n\n        def reference(input, seq_lengths, gates_w, gates_b, hidden_input):\n            T = input.shape[0]\n            N = input.shape[1]\n            D = input.shape[2]\n            hidden = np.zeros(shape=(T + 1, N, D))\n            assert hidden.shape[0] == T + 1\n            assert hidden.shape[1] == N\n            assert hidden.shape[2] == D\n\n            hidden[0, :, :] = hidden_input\n            for t in range(T):\n                input_t = input[t].reshape(1, N, D)\n                hidden_t_prev = hidden[t].reshape(1, N, D)\n                gates = np.dot(hidden_t_prev, gates_w.T)\n                gates = gates.reshape(1, N, D) + input_t.reshape(1, N, D)\n                hidden[t + 1] = sigmoid(gates)\n            return hidden[1:], hidden, hidden[-1].reshape(1, N, D)\n\n        self.assertReferenceChecks(\n            hu.cpu_do,\n            op,\n            [workspace.FetchBlob(name)\n             for name in [\"input\", \"seq_lengths\", \"gates_t_w\", \"gates_t_b\",\n                          \"hidden_input\"]],\n            reference,\n            outputs_to_check=[0, 1, 2])\n\n        for param in [0, 2, 3]:\n            self.assertGradientChecks(\n                hu.cpu_do,\n                op,\n                [workspace.FetchBlob(name)\n                 for name in [\"input\", \"seq_lengths\", \"gates_t_w\", \"gates_t_b\",\n                              \"hidden_input\"]],\n                param,\n                [0])\n\n    @settings(suppress_health_check=[HealthCheck.filter_too_much])\n    @given(n=st.integers(1, 5),\n           c=st.integers(1, 5),\n           h=st.integers(1, 5),\n           w=st.integers(1, 5),\n           pad=st.integers(0, 2),\n           block_size=st.integers(2, 3),\n           **hu.gcs)\n    def test_space_to_batch(self, n, c, h, w, pad, block_size, gc, dc):\n        assume((h + 2 * pad) % block_size == 0)\n        assume((w + 2 * pad) % block_size == 0)\n        X = np.random.randn(n, c, h, w).astype(np.float32)\n        op = core.CreateOperator(\"SpaceToBatch\", [\"X\"], [\"Y\"],\n                                 pad=pad, block_size=block_size)\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @settings(suppress_health_check=[HealthCheck.filter_too_much])\n    @given(n=st.integers(1, 5),\n           c=st.integers(1, 5),\n           h=st.integers(1, 5),\n           w=st.integers(1, 5),\n           pad=st.integers(0, 2),\n           block_size=st.integers(2, 3),\n           **hu.gcs)\n    def test_batch_to_space(self, n, c, h, w, pad, block_size, gc, dc):\n        assume((h + 2 * pad) % block_size == 0)\n        assume((w + 2 * pad) % block_size == 0)\n        X = np.random.randn(\n            n * block_size * block_size,\n            c,\n            (h + 2 * pad) // block_size,\n            (w + 2 * pad) // block_size).astype(np.float32)\n        op = core.CreateOperator(\"BatchToSpace\", [\"X\"], [\"Y\"],\n                                 pad=pad, block_size=block_size)\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @given(X=hu.tensor(),\n           in_place=st.booleans(),\n           scale=st.floats(min_value=-2.0, max_value=2.0),\n           **hu.gcs)\n    def test_scale(self, X, in_place, scale, gc, dc):\n        op = core.CreateOperator(\n            \"Scale\", [\"X\"], [\"Y\" if not in_place else \"X\"],\n            scale=scale)\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @given(s=st.text())\n    def test_string_serde(self, s):\n        s = s.encode('ascii', 'ignore')\n        self.ws.create_blob(\"a\").feed(s)\n        serialized = self.ws.blobs[\"a\"].serialize(\"a\")\n        self.ws.create_blob(\"b\").deserialize(serialized)\n        self.assertEqual(s, self.ws.blobs[(\"a\")].fetch())\n        self.assertEqual(s, self.ws.blobs[(\"b\")].fetch())\n\n    @given(pad=st.integers(0, 3),\n           size=st.integers(1, 10),\n           input_channels=st.integers(1, 5),\n           batch_size=st.integers(1, 5),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           mode=st.sampled_from([\"constant\", \"reflect\", \"edge\"]),\n           **hu.gcs)\n    def test_same_pad_image(self, pad, size, input_channels, batch_size, order,\n                            mode, gc, dc):\n        assume(size > pad)\n\n        op = core.CreateOperator(\n            \"PadImage\",\n            [\"X\"],\n            [\"Y\"],\n            pad=pad,\n            mode=mode,\n            order=order,\n        )\n        if order == \"NHWC\":\n            X = np.random.rand(\n                batch_size, size, size, input_channels).astype(np.float32) - 0.5\n\n            def numpy_pad_ref(x):\n                return (np.pad(\n                    x, ((0, 0), (pad, pad), (pad, pad), (0, 0)), mode),)\n\n        else:\n            X = np.random.rand(\n                batch_size, input_channels, size, size).astype(np.float32) - 0.5\n\n            def numpy_pad_ref(x):\n                return (np.pad(\n                    x, ((0, 0), (0, 0), (pad, pad), (pad, pad)), mode),)\n\n        self.assertReferenceChecks(gc, op, [X], numpy_pad_ref)\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @given(pad_t=st.integers(0, 3),\n           pad_l=st.integers(0, 3),\n           pad_b=st.integers(0, 3),\n           pad_r=st.integers(0, 3),\n           size=st.integers(1, 10),\n           input_channels=st.integers(1, 5),\n           batch_size=st.integers(1, 5),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           mode=st.sampled_from([\"constant\", \"reflect\", \"edge\"]),\n           **hu.gcs)\n    def test_pad_image(self, pad_t, pad_l, pad_b, pad_r, size, input_channels,\n                       batch_size, order, mode, gc, dc):\n        assume(size > max(pad_b, pad_r, pad_t, pad_l))\n\n        op = core.CreateOperator(\n            \"PadImage\",\n            [\"X\"],\n            [\"Y\"],\n            pad_t=pad_t,\n            pad_l=pad_l,\n            pad_b=pad_b,\n            pad_r=pad_r,\n            mode=mode,\n            order=order,\n        )\n        if order == \"NHWC\":\n            X = np.random.rand(\n                batch_size, size, size, input_channels).astype(np.float32) - 0.5\n\n            def numpy_pad_ref(x):\n                return (np.pad(\n                    x, ((0, 0), (pad_t, pad_b), (pad_l, pad_r), (0, 0)),\n                    mode),)\n\n        else:\n            X = np.random.rand(\n                batch_size, input_channels, size, size).astype(np.float32) - 0.5\n\n            def numpy_pad_ref(x):\n                return (np.pad(\n                    x, ((0, 0), (0, 0), (pad_t, pad_b), (pad_l, pad_r)),\n                    mode),)\n\n        self.assertReferenceChecks(gc, op, [X], numpy_pad_ref)\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @given(size=st.integers(7, 10),\n           input_channels=st.integers(1, 10),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           epsilon=st.floats(min_value=1e-4, max_value=1e-2),\n           **hu.gcs_cpu_only)\n    def test_instance_norm(self, size, input_channels, batch_size, order,\n                           epsilon, gc, dc):\n        op = core.CreateOperator(\n            \"InstanceNorm\",\n            [\"X\", \"scale\", \"bias\"],\n            [\"Y\"],\n            order=order,\n            epsilon=epsilon,\n        )\n        np.random.seed(1701)\n        scale = np.random.rand(input_channels).astype(np.float32) + 0.5\n        bias = np.random.rand(input_channels).astype(np.float32) - 0.5\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n        if order == \"NHWC\":\n            X = X.swapaxes(1, 2).swapaxes(2, 3)\n\n        def ref_nchw(x, scale, bias):\n            x = x.reshape(batch_size * input_channels, size * size)\n            y = (x - x.mean(1)[:, np.newaxis])\n            y /= np.sqrt(x.var(1) + epsilon)[:, np.newaxis]\n            y = y.reshape(batch_size, input_channels, size, size)\n            y = y * scale.reshape(1, input_channels, 1, 1)\n            y = y + bias.reshape(1, input_channels, 1, 1)\n            return (y, )\n\n        def ref_nhwc(x, scale, bias):\n            x = x.swapaxes(2, 3).swapaxes(1, 2)\n            y = ref_nchw(x, scale, bias)[0]\n            return (y.swapaxes(1, 2).swapaxes(2, 3), )\n\n        self.assertReferenceChecks(\n            gc, op, [X, scale, bias],\n            ref_nchw if order == \"NCHW\" else ref_nhwc)\n        # TODO(jiayq): when there are backward and GPU implementations, enable\n        # these two.\n        # self.assertDeviceChecks(dc, op, [X, scale, bias], [0])\n        # self.assertGradientChecks(gc, op, [X, scale, bias], 0, [0])\n\n        ws = workspace.C.Workspace()\n        feeds = [(\"X\", X), (\"scale\", scale), (\"bias\", bias)]\n        for blob, arr in feeds:\n            ws.create_blob(blob).feed(arr)\n        for _ in range(100):\n            ws.run(op)\n        for blob, arr in feeds:\n            np.testing.assert_array_equal(ws.blobs[blob].fetch(), arr)\n\n    @given(sizes=st.lists(st.integers(1, 100), min_size=1),\n           in_place=st.booleans(),\n           **hu.gcs)\n    def test_unsafe_coalesce(self, sizes, in_place, gc, dc):\n        gAlignment = 32\n        Xs = [np.random.randn(size)\n              .astype(np.random.choice([np.float32, np.float64, np.uint8]))\n              for size in sizes]\n        op = core.CreateOperator(\n            \"UnsafeCoalesce\",\n            [\"X_{}\".format(i) for i, _ in enumerate(sizes)],\n            [(\"X_{}\" if in_place else \"Y_{}\").format(i)\n             for i, _ in enumerate(sizes)] + [\"coalesced\"])\n        self.assertDeviceChecks(dc, op, Xs, list(range(len(sizes) + 1)))\n\n        def unsafe_coalesce(*xs):\n            def to_uint8(x):\n                x_aligned_bytes = ((x.nbytes + gAlignment - 1) // gAlignment) \\\n                    * gAlignment\n                x_aligned = np.zeros(\n                    shape=(x_aligned_bytes // x.dtype.itemsize, ),\n                    dtype=x.dtype)\n                x_aligned[:x.size] = x\n                x_cast = np.fromstring(x_aligned.tobytes(), dtype='<u1')\n                return x_cast\n            flat = [to_uint8(x) for x in xs]\n            coalesced = np.concatenate(flat)\n            return list(xs) + [coalesced]\n        self.assertReferenceChecks(gc, op, Xs, unsafe_coalesce)\n\n    @given(inp=_dtypes().flatmap(lambda dt: _tensor_and_indices(\n        elements=st.floats(min_value=0, max_value=1), dtype=dt)),\n        **hu.gcs)\n    def test_sparse_to_dense(self, inp, gc, dc):\n        first_dim, X, I = inp\n        if X.dtype != np.dtype('float32') and gc.device_type == 1:\n            # Cuda only support 32 bit float\n            print(\"Bailout {}\".format(X.dtype))\n            return\n        if gc.device_type == 1:\n            # Cuda version only support int32\n            I = I.astype(np.int32)\n\n        # values don't matter\n        D = np.zeros((first_dim,) + X.shape[1:]).astype(X.dtype)\n\n        op = core.CreateOperator(\"SparseToDense\", [\"I\", \"X\", \"D\"], [\"Y\"])\n\n        def sparse_to_dense(I, X, D):\n            O = np.zeros(D.shape)\n            for i, p in enumerate(I):\n                O[p] += X[i]\n            return [O]\n\n        self.assertReferenceChecks(gc, op, [I, X, D], sparse_to_dense)\n        X = X.astype(np.float32)\n        self.assertGradientChecks(gc, op, [I, X, D], 1, [0])\n\n    @given(inputs=hu.tensors(n=2, min_dim=2, max_dim=2), **hu.gcs_cpu_only)\n    def test_dot_product(self, inputs, gc, dc):\n        X, Y = inputs\n        op = core.CreateOperator(\"DotProduct\", [\"X\", \"Y\"], 'out')\n\n        def dotproduct(X, Y):\n            return (np.sum(X * Y, axis=1), )\n\n        self.assertReferenceChecks(gc, op, [X, Y], dotproduct)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        self.assertGradientChecks(gc, op, [X, Y], 0, [0])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n    @given(N=st.integers(min_value=2, max_value=10),\n           M=st.integers(min_value=2, max_value=10),\n           K=st.integers(min_value=2, max_value=10),\n           pad_value=st.floats(min_value=0.1, max_value=1.0),\n           **hu.gcs_cpu_only)\n    def test_dot_product_with_padding(self, N, M, K, pad_value, gc, dc):\n        X = np.random.rand(N, M).astype(np.float32) - 0.5\n        Y = np.random.rand(N, K).astype(np.float32) - 0.5\n        op = core.CreateOperator(\"DotProductWithPadding\", [\"X\", \"Y\"], 'out',\n                                 pad_value=pad_value)\n\n        def dotproduct(X, Y):\n            Z = np.ones((N, max(M, K))).astype(np.float32) * pad_value\n            if M < K:\n                Z[:, :M] = X\n                return (np.sum(Z * Y, axis=1), )\n            else:\n                Z[:, :K] = Y\n                return (np.sum(Z * X, axis=1), )\n\n        self.assertReferenceChecks(gc, op, [X, Y], dotproduct)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        self.assertGradientChecks(gc, op, [X, Y], 0, [0])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n    @given(N=st.integers(min_value=2, max_value=10),\n           M=st.integers(min_value=2, max_value=10),\n           pad_value=st.floats(min_value=0.1, max_value=1.0),\n           **hu.gcs_cpu_only)\n    def test_dot_product_with_rep_padding(self, N, M, pad_value, gc, dc):\n        K = 2 * M\n        X = np.random.rand(N, M).astype(np.float32) - 0.5\n        Y = np.random.rand(N, K).astype(np.float32) - 0.5\n        op = core.CreateOperator(\"DotProductWithPadding\", [\"X\", \"Y\"], 'out',\n                                 replicate=True,\n                                 pad_value=pad_value)\n\n        def dotproduct(X, Y):\n            import numpy.matlib as npm\n            if M < K:\n                Z = npm.repmat(X, 1, K // M)\n                return (np.sum(Z * Y, axis=1), )\n            else:\n                Z = npm.repmat(Y, 1, M // K)\n                return (np.sum(Z * X, axis=1), )\n\n        self.assertReferenceChecks(gc, op, [X, Y], dotproduct)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        self.assertGradientChecks(gc, op, [X, Y], 0, [0])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n    @given(N=st.integers(min_value=2, max_value=10),\n           M=st.integers(min_value=2, max_value=10), **hu.gcs_cpu_only)\n    def test_ensure_dense(self, N, M, gc, dc):\n        # in place\n        X = np.random.rand(N, M).astype(np.float32) - 0.5\n        op = core.CreateOperator(\"EnsureDense\", [\"X\"], \"X\")\n        self.assertReferenceChecks(gc, op, [X], lambda x: [x])\n        self.assertDeviceChecks(dc, op, [X], [0])\n        # or not\n        X = np.random.rand(N, M).astype(np.float32) - 0.5\n        op = core.CreateOperator(\"EnsureDense\", [\"X\"], \"out\")\n        self.assertReferenceChecks(gc, op, [X], lambda x: [x])\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n    @given(N=st.integers(min_value=10, max_value=100),\n           M=st.integers(min_value=2, max_value=10),\n           num_buckets=st.integers(min_value=1, max_value=5),\n           **hu.gcs_cpu_only)\n    def test_accumulate_histogram_op(self, N, M, num_buckets, gc, dc):\n        X = np.random.rand(N, M).astype(np.float32)\n        lower_bound, upper_bound = 0.1, 0.9\n        op = core.CreateOperator(\"AccumulateHistogram\", [\"X\"],\n                                 ['cur_hist', 'acc_hist'],\n                                 lower_bound=lower_bound,\n                                 upper_bound=upper_bound,\n                                 num_buckets=num_buckets)\n\n        def histogram(X):\n            hist = np.zeros((num_buckets + 2, ), dtype=np.int32)\n            segment = (upper_bound - lower_bound) / num_buckets\n            Y = np.zeros((N, M), dtype=np.int32)\n            Y[X < lower_bound] = 0\n            Y[X >= upper_bound] = num_buckets + 1\n            Y[(X >= lower_bound) & (X < upper_bound)] = \\\n                ((X[(X >= lower_bound) & (X < upper_bound)] - lower_bound) /\n                        segment + 1).astype(np.int32)\n\n            for i in range(Y.shape[0]):\n                for j in range(Y.shape[1]):\n                    hist[Y[i][j]] += 1\n            cur_hist, acc_hist = hist, hist\n\n            return [cur_hist, acc_hist]\n\n        self.assertDeviceChecks(dc, op, [X], [0, 1])\n        self.assertReferenceChecks(gc, op, [X], histogram)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/hypothesis_test_util.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package hypothesis_test_util\n# Module caffe2.python.hypothesis_test_util\n\"\"\"\nThe Hypothesis library uses *property-based testing* to check\ninvariants about the code under test under a variety of random inputs.\n\n The key idea here is to express properties of the code under test\n(e.g. that it passes a gradient check, that it implements a reference\nfunction, etc), and then generate random instances and verify they\nsatisfy these properties.\n\nThe main functions of interest are exposed on `HypothesisTestCase`.\nYou can usually just add a short function in this to generate an\narbitrary number of test cases for your operator.\n\nThe key functions are:\n\n- `assertDeviceChecks(devices, op, inputs, outputs)`. This asserts that the\n  operator computes the same outputs, regardless of which device it is executed\n  on.\n- `assertGradientChecks(device, op, inputs, output_,\n  outputs_with_grads)`. This implements a standard numerical gradient checker\n  for the operator in question.\n- `assertReferenceChecks(device, op, inputs, reference)`. This runs the\n  reference function (effectively calling `reference(*inputs)`, and comparing\n  that to the output of output.\n\n`hypothesis_test_util.py` exposes some useful pre-built samplers.\n\n- `hu.gcs` - a gradient checker device (`gc`) and device checker devices (`dc`)\n\n- `hu.gcs_cpu_only` - a CPU-only gradient checker device (`gc`) and\n  device checker devices (`dc`). Used for when your operator is only\n  implemented on the CPU.\n\"\"\"\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import (\n    workspace, device_checker, gradient_checker, test_util, core)\nimport contextlib\nimport copy\nimport functools\nimport hypothesis\nimport hypothesis.extra.numpy\nimport hypothesis.strategies as st\nimport logging\nimport numpy as np\nimport os\n\n\ndef is_sandcastle():\n    if os.getenv('SANDCASTLE') == '1':\n        return True\n    elif os.getenv('TW_JOB_USER') == 'sandcastle':\n        return True\n    return False\n\n\ndef is_travis():\n    return 'TRAVIS' in os.environ\n\n\nhypothesis.settings.register_profile(\n    \"sandcastle\",\n    hypothesis.settings(\n        derandomize=True,\n        suppress_health_check=[hypothesis.HealthCheck.too_slow],\n        database=None,\n        min_satisfying_examples=1,\n        max_examples=100,\n        verbosity=hypothesis.Verbosity.verbose))\n\nhypothesis.settings.register_profile(\n    \"dev\",\n    hypothesis.settings(\n        suppress_health_check=[hypothesis.HealthCheck.too_slow],\n        database=None,\n        max_examples=10,\n        min_satisfying_examples=1,\n        verbosity=hypothesis.Verbosity.verbose))\nhypothesis.settings.register_profile(\n    \"debug\",\n    hypothesis.settings(\n        suppress_health_check=[hypothesis.HealthCheck.too_slow],\n        database=None,\n        max_examples=1000,\n        min_satisfying_examples=1,\n        verbosity=hypothesis.Verbosity.verbose))\nhypothesis.settings.load_profile(\n    'sandcastle' if is_sandcastle() else os.getenv('CAFFE2_HYPOTHESIS_PROFILE',\n                                                   'dev')\n)\n\n\ndef dims(min_value=1, max_value=5):\n    return st.integers(min_value=min_value, max_value=max_value)\n\n\ndef elements_of_type(dtype=np.float32, filter_=None):\n    elems = None\n    if dtype in (np.float16, np.float32, np.float64):\n        elems = st.floats(min_value=-1.0, max_value=1.0)\n    elif dtype is np.int32:\n        elems = st.integers(min_value=0, max_value=2 ** 31 - 1)\n    elif dtype is np.int64:\n        elems = st.integers(min_value=0, max_value=2 ** 63 - 1)\n    elif dtype is np.bool:\n        elems = st.booleans()\n    else:\n        raise ValueError(\"Unexpected dtype without elements provided\")\n    return elems if filter_ is None else elems.filter(filter_)\n\n\ndef arrays(dims, dtype=np.float32, elements=None):\n    if elements is None:\n        elements = elements_of_type(dtype)\n    return hypothesis.extra.numpy.arrays(\n        dtype,\n        dims,\n        elements=elements,\n    )\n\n\ndef tensor(min_dim=1,\n           max_dim=4,\n           dtype=np.float32,\n           elements=None,\n           **kwargs):\n    dims_ = st.lists(dims(**kwargs), min_size=min_dim, max_size=max_dim)\n    return dims_.flatmap(\n        lambda dims: arrays(dims, dtype, elements))\n\n\ndef tensor1d(min_len=1, max_len=64, dtype=np.float32, elements=None):\n    return tensor(1, 1, dtype, elements, min_value=min_len, max_value=max_len)\n\n\ndef segment_ids(size, is_sorted):\n    if size == 0:\n        return st.just(np.empty(shape=[0], dtype=np.int32))\n    if is_sorted:\n        return arrays(\n            [size],\n            dtype=np.int32,\n            elements=st.booleans()).map(\n                lambda x: np.cumsum(x, dtype=np.int32) - x[0])\n    else:\n        return arrays(\n            [size],\n            dtype=np.int32,\n            elements=st.integers(min_value=0, max_value=2 * size))\n\n\ndef lengths(size, min_segments=None, max_segments=None, **kwargs):\n    # First generate number of boarders between segments\n    # Then create boarder values and add 0 and size\n    # By sorting and computing diff we convert them to lengths of\n    # possible 0 value\n    if min_segments is None:\n        min_segments = 0\n    if max_segments is None:\n        max_segments = size\n    assert min_segments >= 0\n    assert min_segments <= max_segments\n    if size == 0 and max_segments == 0:\n        return st.just(np.empty(shape=[0], dtype=np.int32))\n    assert max_segments > 0, \"size is not 0, need at least one segment\"\n    return st.integers(\n        min_value=max(min_segments - 1, 0), max_value=max_segments - 1\n    ).flatmap(\n        lambda num_borders:\n        hypothesis.extra.numpy.arrays(\n            np.int32, num_borders, elements=st.integers(\n                min_value=0, max_value=size\n            )\n        )\n    ).map(\n        lambda x: np.append(x, np.array([0, size], dtype=np.int32))\n    ).map(sorted).map(np.diff)\n\n\ndef segmented_tensor(\n    min_dim=1,\n    max_dim=4,\n    dtype=np.float32,\n    is_sorted=True,\n    elements=None,\n    segment_generator=segment_ids,\n    allow_empty=False,\n    **kwargs\n):\n    gen_empty = st.booleans() if allow_empty else st.just(False)\n    data_dims_ = st.lists(dims(**kwargs), min_size=min_dim, max_size=max_dim)\n    data_dims_ = st.tuples(\n        gen_empty, data_dims_\n    ).map(lambda pair: ([0] if pair[0] else []) + pair[1])\n    return data_dims_.flatmap(lambda data_dims: st.tuples(\n        arrays(data_dims, dtype, elements),\n        segment_generator(data_dims[0], is_sorted=is_sorted),\n    ))\n\n\ndef lengths_tensor(min_segments=None, max_segments=None, *args, **kwargs):\n    gen = functools.partial(\n        lengths, min_segments=min_segments, max_segments=max_segments)\n    return segmented_tensor(*args, segment_generator=gen, **kwargs)\n\n\ndef sparse_segmented_tensor(min_dim=1, max_dim=4, dtype=np.float32,\n                            is_sorted=True, elements=None, allow_empty=False,\n                            segment_generator=segment_ids, itype=np.int64,\n                            **kwargs):\n    gen_empty = st.booleans() if allow_empty else st.just(False)\n    data_dims_ = st.lists(dims(**kwargs), min_size=min_dim, max_size=max_dim)\n    all_dims_ = st.tuples(gen_empty, data_dims_).flatmap(\n        lambda pair: st.tuples(\n            st.just(pair[1]),\n            (st.integers(min_value=1, max_value=pair[1][0]) if not pair[0]\n             else st.just(0)),\n        ))\n    return all_dims_.flatmap(lambda dims: st.tuples(\n        arrays(dims[0], dtype, elements),\n        arrays(dims[1], dtype=itype, elements=st.integers(\n            min_value=0, max_value=dims[0][0] - 1)),\n        segment_generator(dims[1], is_sorted=is_sorted),\n    ))\n\n\ndef sparse_lengths_tensor(**kwargs):\n    return sparse_segmented_tensor(segment_generator=lengths, **kwargs)\n\n\ndef tensors(n, min_dim=1, max_dim=4, dtype=np.float32, elements=None, **kwargs):\n    dims_ = st.lists(dims(**kwargs), min_size=min_dim, max_size=max_dim)\n    return dims_.flatmap(\n        lambda dims: st.lists(\n            arrays(dims, dtype, elements),\n            min_size=n,\n            max_size=n))\n\n\ndef tensors1d(n, min_len=1, max_len=64, dtype=np.float32, elements=None):\n    return tensors(\n        n, 1, 1, dtype, elements, min_value=min_len, max_value=max_len\n    )\n\n\ncpu_do = caffe2_pb2.DeviceOption()\ngpu_do = caffe2_pb2.DeviceOption(device_type=caffe2_pb2.CUDA)\ndevice_options = [cpu_do] + ([gpu_do] if workspace.has_gpu_support else [])\n# Include device option for each GPU\nexpanded_device_options = [cpu_do] + (\n    [caffe2_pb2.DeviceOption(device_type=caffe2_pb2.CUDA, cuda_gpu_id=i)\n     for i in range(workspace.NumCudaDevices())]\n    if workspace.has_gpu_support else [])\n\n\ndef device_checker_device_options():\n    return st.just(device_options)\n\n\ndef gradient_checker_device_option():\n    return st.sampled_from(device_options)\n\n\ngcs = dict(\n    gc=gradient_checker_device_option(),\n    dc=device_checker_device_options()\n)\n\ngcs_cpu_only = dict(gc=st.sampled_from([cpu_do]), dc=st.just([cpu_do]))\ngcs_gpu_only = dict(gc=st.sampled_from([gpu_do]), dc=st.just([gpu_do]))\n\n\n@contextlib.contextmanager\ndef temp_workspace(name=b\"temp_ws\"):\n    old_ws_name = workspace.CurrentWorkspace()\n    workspace.SwitchWorkspace(name, True)\n    yield\n    workspace.ResetWorkspace()\n    workspace.SwitchWorkspace(old_ws_name)\n\n\ndef runOpBenchmark(\n    device_option,\n    op,\n    inputs,\n    input_device_options=None,\n    iterations=10,\n):\n    if input_device_options is None:\n        input_device_options = {}\n    op = copy.deepcopy(op)\n    op.device_option.CopyFrom(device_option)\n    net = caffe2_pb2.NetDef()\n    net.op.extend([op])\n    net.name = op.name if op.name else \"test\"\n\n    with temp_workspace():\n        for (n, b) in zip(op.input, inputs):\n            workspace.FeedBlob(\n                n,\n                b,\n                device_option=input_device_options.get(n, device_option)\n            )\n        workspace.CreateNet(net)\n        ret = workspace.BenchmarkNet(net.name, 1, iterations, True)\n    return ret\n\n\nclass HypothesisTestCase(test_util.TestCase):\n    \"\"\"\n    A unittest.TestCase subclass with some helper functions for\n    utilizing the `hypothesis` (hypothesis.readthedocs.io) library.\n    \"\"\"\n    def assertDeviceChecks(\n        self,\n        device_options,\n        op,\n        inputs,\n        outputs_to_check,\n        input_device_options=None,\n        threshold=0.01\n    ):\n        \"\"\"\n        Asserts that the operator computes the same outputs, regardless of\n        which device it is executed on.\n\n        Useful for checking the consistency of GPU and CPU\n        implementations of operators.\n\n        Usage example:\n\n            @given(inputs=hu.tensors(n=2), in_place=st.booleans(), **hu.gcs)\n            def test_sum(self, inputs, in_place, gc, dc):\n                op = core.CreateOperator(\"Sum\", [\"X1\", \"X2\"],\n                                                [\"Y\" if not in_place else \"X1\"])\n                X1, X2 = inputs\n                self.assertDeviceChecks(dc, op, [X1, X2], [0])\n        \"\"\"\n        dc = device_checker.DeviceChecker(\n            threshold,\n            device_options=device_options\n        )\n        self.assertTrue(\n            dc.CheckSimple(op, inputs, outputs_to_check, input_device_options)\n        )\n\n    def assertGradientChecks(\n        self,\n        device_option,\n        op,\n        inputs,\n        outputs_to_check,\n        outputs_with_grads,\n        grad_ops=None,\n        threshold=0.005,\n        stepsize=0.05,\n        input_device_options=None,\n    ):\n        \"\"\"\n        Implements a standard numerical gradient checker for the operator\n        in question.\n\n        Useful for checking the consistency of the forward and\n        backward implementations of operators.\n\n        Usage example:\n\n            @given(inputs=hu.tensors(n=2), in_place=st.booleans(), **hu.gcs)\n            def test_sum(self, inputs, in_place, gc, dc):\n                op = core.CreateOperator(\"Sum\", [\"X1\", \"X2\"],\n                                                [\"Y\" if not in_place else \"X1\"])\n                X1, X2 = inputs\n                self.assertGradientChecks(gc, op, [X1, X2], 0, [0])\n        \"\"\"\n        gc = gradient_checker.GradientChecker(\n            stepsize=stepsize,\n            threshold=threshold,\n            device_option=device_option,\n            workspace_name=str(device_option),\n        )\n        res, grad, grad_estimated = gc.CheckSimple(\n            op, inputs, outputs_to_check, outputs_with_grads,\n            grad_ops=grad_ops,\n            input_device_options=input_device_options\n        )\n        self.assertEqual(grad.shape, grad_estimated.shape)\n        self.assertTrue(\n            res,\n            \"Gradient check failed for input \" + str(op.input[outputs_to_check])\n        )\n\n    def _assertGradReferenceChecks(\n        self,\n        op,\n        inputs,\n        ref_outputs,\n        output_to_grad,\n        grad_reference,\n        threshold=1e-4,\n    ):\n        grad_blob_name = output_to_grad + '_grad'\n        grad_ops, grad_map = core.GradientRegistry.GetBackwardPass(\n            [op], {output_to_grad: grad_blob_name})\n        output_grad = workspace.FetchBlob(output_to_grad)\n        grad_ref_outputs = grad_reference(output_grad, ref_outputs, inputs)\n        workspace.FeedBlob(grad_blob_name, workspace.FetchBlob(output_to_grad))\n        workspace.RunOperatorsOnce(grad_ops)\n\n        self.assertEqual(len(grad_ref_outputs), len(inputs))\n        for (n, ref) in zip(op.input, grad_ref_outputs):\n            grad_names = grad_map.get(n)\n            if not grad_names:\n                # no grad for this input\n                self.assertIsNone(ref)\n            else:\n                if isinstance(grad_names, core.BlobReference):\n                    # dense gradient\n                    ref_vals = ref\n                    ref_indices = None\n                    val_name = grad_names\n                else:\n                    # sparse gradient\n                    ref_vals, ref_indices = ref\n                    val_name = grad_names.values\n                vals = workspace.FetchBlob(str(val_name))\n                np.testing.assert_allclose(\n                    vals,\n                    ref_vals,\n                    atol=threshold,\n                    rtol=threshold,\n                    err_msg='Gradient {0} (x) is not matching the reference (y)'\n                    .format(val_name),\n                )\n                if ref_indices is not None:\n                    indices = workspace.FetchBlob(str(grad_names.indices))\n                    np.testing.assert_allclose(indices, ref_indices,\n                                               atol=1e-4, rtol=1e-4)\n\n    def _assertInferTensorChecks(self, name, shapes, types, output):\n        if name not in shapes:\n            # No inferred shape or type available\n            return\n        output = workspace.FetchBlob(name)\n        if type(output) is np.ndarray:\n            if output.dtype == np.dtype('float64'):\n                correct_type = caffe2_pb2.TensorProto.DOUBLE\n            elif output.dtype == np.dtype('float32'):\n                correct_type = caffe2_pb2.TensorProto.FLOAT\n            elif output.dtype == np.dtype('int32'):\n                correct_type = caffe2_pb2.TensorProto.INT32\n            elif output.dtype == np.dtype('int64'):\n                correct_type = caffe2_pb2.TensorProto.INT64\n            else:\n                correct_type = \"unknown {}\".format(np.dtype)\n        else:\n            correct_type = str(type(output))\n        try:\n            np.testing.assert_array_equal(\n                np.array(shapes[name]).astype(np.int32),\n                np.array(output.shape).astype(np.int32),\n                err_msg='Shape {} mismatch: {} vs. {}'.format(\n                    name,\n                    shapes[name],\n                    output.shape))\n            # BUG: Workspace blob type not being set correctly T16121392\n            if correct_type != caffe2_pb2.TensorProto.INT32:\n                return\n            np.testing.assert_equal(\n                types[name],\n                correct_type,\n                err_msg='Type {} mismatch: {} vs. {}'.format(\n                    name, types[name], correct_type,\n                )\n            )\n        except AssertionError as e:\n            # Temporarily catch these assertion errors when validating\n            # inferred shape and type info\n            logging.warning(str(e))\n            if os.getenv('CAFFE2_ASSERT_SHAPEINFERENCE') == '1':\n                raise e\n\n    def assertReferenceChecks(\n        self,\n        device_option,\n        op,\n        inputs,\n        reference,\n        input_device_options=None,\n        threshold=1e-4,\n        output_to_grad=None,\n        grad_reference=None,\n        atol=None,\n        outputs_to_check=None,\n    ):\n        \"\"\"\n        This runs the reference Python function implementation\n        (effectively calling `reference(*inputs)`, and compares that\n        to the output of output, with an absolute/relative tolerance\n        given by the `threshold` parameter.\n\n        Useful for checking the implementation matches the Python\n        (typically NumPy) implementation of the same functionality.\n\n        Usage example:\n\n            @given(X=hu.tensor(), inplace=st.booleans(), **hu.gcs)\n            def test_softsign(self, X, inplace, gc, dc):\n                op = core.CreateOperator(\n                    \"Softsign\", [\"X\"], [\"X\" if inplace else \"Y\"])\n\n                def softsign(X):\n                    return (X / (1 + np.abs(X)),)\n\n                self.assertReferenceChecks(gc, op, [X], softsign)\n        \"\"\"\n        if input_device_options is None:\n            input_device_options = {}\n\n        op = copy.deepcopy(op)\n        op.device_option.CopyFrom(device_option)\n\n        with temp_workspace():\n            if (len(op.input) > len(inputs)):\n                raise ValueError(\n                    'must supply an input for each input on the op: %s vs %s' %\n                    (op.input, inputs))\n            for (n, b) in zip(op.input, inputs):\n                workspace.FeedBlob(\n                    n,\n                    b,\n                    device_option=input_device_options.get(n, device_option)\n                )\n            net = core.Net(\"opnet\")\n            net.Proto().op.extend([op])\n            test_shape_inference = False\n            try:\n                (shapes, types) = workspace.InferShapesAndTypes([net])\n                test_shape_inference = True\n            except RuntimeError as e:\n                # Temporarily catch runtime errors when inferring shape\n                # and type info\n                logging.warning(str(e))\n                if os.getenv('CAFFE2_ASSERT_SHAPEINFERENCE') == '1':\n                    raise e\n            workspace.RunNetOnce(net)\n            reference_outputs = reference(*inputs)\n            if not (isinstance(reference_outputs, tuple) or\n                    isinstance(reference_outputs, list)):\n                raise RuntimeError(\n                    \"You are providing a wrong reference implementation. A \"\n                    \"proper one should return a tuple/list of numpy arrays.\")\n            if not outputs_to_check:\n                self.assertEqual(len(reference_outputs), len(op.output))\n                outputs_to_check = list(range(len(op.output)))\n            outs = []\n            for (output_index, ref) in zip(outputs_to_check, reference_outputs):\n                output_blob_name = op.output[output_index]\n                output = workspace.FetchBlob(output_blob_name)\n                if output.dtype.kind in ('S', 'O'):\n                    np.testing.assert_array_equal(output, ref)\n                else:\n                    if atol is None:\n                        atol = threshold\n                    np.testing.assert_allclose(\n                        output, ref, atol=atol, rtol=threshold,\n                        err_msg=(\n                            'Output {0} is not matching the reference'.format(\n                                output_blob_name,\n                            )),\n                    )\n                if test_shape_inference:\n                    self._assertInferTensorChecks(\n                        output_blob_name, shapes, types, output)\n                outs.append(output)\n            if grad_reference is not None:\n                assert output_to_grad is not None, \\\n                    \"If grad_reference is set,\" \\\n                    \"output_to_grad has to be set as well\"\n\n                with core.DeviceScope(device_option):\n                    self._assertGradReferenceChecks(\n                        op, inputs, reference_outputs,\n                        output_to_grad, grad_reference,\n                        threshold=threshold)\n            return outs\n\n    def assertValidationChecks(\n            self,\n            device_option,\n            op,\n            inputs,\n            validator,\n            input_device_options=None,\n            as_kwargs=True,\n            init_net=None,\n    ):\n        if input_device_options is None:\n            input_device_options = {}\n        if as_kwargs:\n            assert len(set(list(op.input) + list(op.output))) == \\\n                len(op.input) + len(op.output), \\\n                \"in-place ops are not supported in as_kwargs mode\"\n        op = copy.deepcopy(op)\n        op.device_option.CopyFrom(device_option)\n\n        with temp_workspace():\n            for (n, b) in zip(op.input, inputs):\n                workspace.FeedBlob(\n                    n,\n                    b,\n                    device_option=input_device_options.get(n, device_option)\n                )\n            if init_net:\n                workspace.RunNetOnce(init_net)\n            workspace.RunOperatorOnce(op)\n            outputs = [workspace.FetchBlob(n) for n in op.output]\n            if as_kwargs:\n                validator(**dict(zip(\n                    list(op.input) + list(op.output), inputs + outputs)))\n            else:\n                validator(inputs=inputs, outputs=outputs)\n\n    def assertRunOpRaises(\n        self,\n        device_option,\n        op,\n        inputs,\n        input_device_options=None,\n        exception=(Exception,),\n        regexp=None,\n    ):\n        if input_device_options is None:\n            input_device_options = {}\n\n        op = copy.deepcopy(op)\n        op.device_option.CopyFrom(device_option)\n\n        with temp_workspace():\n            for (n, b) in zip(op.input, inputs):\n                workspace.FeedBlob(\n                    n,\n                    b,\n                    device_option=input_device_options.get(n, device_option)\n                )\n            if regexp is None:\n                self.assertRaises(exception, workspace.RunOperatorOnce, op)\n            else:\n                self.assertRaisesRegexp(\n                    exception, regexp, workspace.RunOperatorOnce, op)\n"
  },
  {
    "path": "caffe2/python/layer_model_helper.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package layer_model_helper\n# Module caffe2.python.layer_model_helper\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, model_helper, schema, scope\nfrom caffe2.python.modeling.parameter_info import (\n    ParameterInfo,\n)\nfrom caffe2.python.modeling.parameter_sharing import (\n    parameter_sharing_context,\n)\nfrom caffe2.python.modeling.net_modifier import NetModifier\n\nfrom caffe2.python.optimizer import get_param_device\nfrom caffe2.python.regularizer import Regularizer\nfrom caffe2.python.layers import layers\nfrom caffe2.proto import caffe2_pb2\nfrom future.utils import viewitems, viewvalues\n\nimport logging\nimport numpy as np\nimport six\nimport copy\nlogger = logging.getLogger(__name__)\n\n\nclass LayerModelHelper(model_helper.ModelHelper):\n    \"\"\"\n    Model helper for building models on top of layers abstractions.\n\n    Each layer is the abstraction that is higher level than Operator. Layer\n    is responsible for ownership of it's own parameters and can easily be\n    instantiated in multiple nets possible with different sets of ops.\n    As an example: one can easily instantiate predict and train nets from\n    the same set of layers, where predict net will have subset of the\n    operators from train net.\n    \"\"\"\n\n    def __init__(self, name, input_feature_schema, trainer_extra_schema,\n                 keep_blobs=False):\n        ''' TODO(amalevich): more documnetation on input args\n        '''\n\n        super(LayerModelHelper, self).__init__(name=name)\n        self._layer_names = set()\n        self._layers = []\n        self._param_to_shape = {}\n\n        # seed default\n        self._seed = None\n        self._sequence_seed = True\n\n        # optimizer bookkeeping\n        self.param_to_optim = {}\n        self.param_to_reg = {}\n\n        self._default_optimizer = None\n        self._loss = None\n        self._output_schema = None\n\n        self._post_grad_net_modifiers = []\n        self._final_net_modifiers = []\n\n        # breakdown map; breakdown features are categorical (like dense) but not\n        # necessarily used to represent data for training\n        self._breakdown_map = None\n\n        # Connect Schema to self.net. That particular instance of schmea will be\n        # use for generation of the Layers accross the network and would be used\n        # for connection with Readers.\n        self._input_feature_schema = schema.NewRecord(\n            self.net,\n            input_feature_schema\n        ) if not keep_blobs else input_feature_schema.clone()\n        self._trainer_extra_schema = schema.NewRecord(\n            self.net,\n            trainer_extra_schema\n        ) if not keep_blobs else trainer_extra_schema.clone()\n        self._metrics_schema = schema.Struct()\n\n        self._init_global_constants()\n        self.param_init_net = self.create_init_net('param_init_net')\n        self._initialize_params = True\n\n    def clear_output_schema(self):\n        self._output_schema = None\n\n    def set_initialize_params(self, initialize_params):\n        self._initialize_params = initialize_params\n\n    def add_metric_field(self, name, value):\n        assert name not in self._metrics_schema.fields, (\n            \"Try to add metric field twice: {}\".format(name))\n        self._metrics_schema = self._metrics_schema + schema.Struct(\n            (name, value)\n        )\n\n    @staticmethod\n    def _get_global_constant_initializer_op(\n        blob_name, array=None, dtype=None, initializer=None\n    ):\n        # to add a global constant to model, one first need to get the\n        # initializer\n        if array is not None:\n            assert initializer is None,\\\n                \"Only one from array and initializer should be specified\"\n            if dtype is None:\n                array = np.array(array)\n            else:\n                array = np.array(array, dtype=dtype)\n\n            # TODO: make GivenTensor generic\n            op_name = None\n            if array.dtype == np.int32:\n                op_name = 'GivenTensorIntFill'\n            elif array.dtype == np.int64:\n                op_name = 'GivenTensorInt64Fill'\n            elif array.dtype == np.str:\n                op_name = 'GivenTensorStringFill'\n            elif array.dtype == np.bool:\n                op_name = 'GivenTensorBoolFill'\n            else:\n                op_name = 'GivenTensorFill'\n\n            def initializer(blob_name):\n                return core.CreateOperator(\n                    op_name, [],\n                    blob_name,\n                    shape=array.shape,\n                    values=array.flatten().tolist()\n                )\n        else:\n            assert initializer is not None\n        initializer_op = initializer(blob_name)\n        return initializer_op\n\n    def add_global_constant(\n        self, name, array=None, dtype=None, initializer=None\n    ):\n        assert isinstance(name, six.string_types), (\n            'name should be a string as we are using it as map key')\n        # This is global namescope for constants. They will be created in all\n        # init_nets and there should be very few of them.\n        assert name not in self.global_constants, \\\n            \"%s already added in global_constants\" % name\n        blob_name = self.net.NextBlob(name)\n        self.global_constants[name] = blob_name\n        initializer_op = LayerModelHelper._get_global_constant_initializer_op(\n            blob_name, array, dtype, initializer\n        )\n        assert blob_name not in self.global_constant_initializers, \\\n            \"there is already a initializer op associated with blob %s\" % \\\n            blob_name\n        self.global_constant_initializers[blob_name] = initializer_op\n        return blob_name\n\n    def maybe_add_global_constant(self, name, *args, **kwargs):\n        # To ad hoc add new global constants without duplication\n        # if the name was already registered in global_constants, it will not be\n        # added even if the intended value is different from its original value\n\n        def op_equal(operator1, operator2):\n            o1 = copy.deepcopy(operator1)\n            o2 = copy.deepcopy(operator2)\n            # debug_info is supposed to be different, and we don't need to\n            # compare debug_info\n            if hasattr(o1, 'debug_info'):\n                o1.debug_info = ''\n            if hasattr(o2, 'debug_info'):\n                o2.debug_info = ''\n            return o1 == o2\n\n        if name in self.global_constants:\n            blob_name = self.global_constants[name]\n            initializer_op = \\\n                LayerModelHelper._get_global_constant_initializer_op(\n                    blob_name, *args, **kwargs\n                )\n            # check if the original initializer is the same as the one intended\n            # now\n            assert op_equal(initializer_op,\n                            self.global_constant_initializers[blob_name]), \\\n                \"conflict initializers for global constant %s, \" \\\n                \"previous %s, now %s\" % (\n                    blob_name, str(initializer_op),\n                    str(self.global_constant_initializers[blob_name]))\n            return blob_name\n        return self.add_global_constant(name, *args, **kwargs)\n\n    def _init_global_constants(self):\n        self.global_constants = {}\n        self.global_constant_initializers = {}\n        self.add_global_constant('ONE', 1.0)\n        self.add_global_constant('ZERO', 0.0)\n        self.add_global_constant('ZERO_RANGE', [0, 0], dtype='int32')\n\n    def _add_global_constants(self, init_net):\n        for initializer_op in viewvalues(self.global_constant_initializers):\n            init_net._net.op.extend([initializer_op])\n\n    def create_init_net(self, name):\n        init_net = core.Net(name)\n        self._add_global_constants(init_net)\n        return init_net\n\n    def _validate_param_shape(self, param_name, shape):\n        if param_name not in self._param_to_shape:\n            return\n\n        ref_shape = self._param_to_shape[param_name]\n\n        if shape != ref_shape:\n            raise ValueError(\n                \"Got inconsistent shapes between shared parameters \"\n                \"when trying to map a blob in scope {0} to {1}. ref_shape : \"\n                \" {2}, shape : {3}\".format(\n                    scope.CurrentNameScope(), param_name, ref_shape, shape)\n            )\n\n    def create_param(self, param_name, shape, initializer, optimizer=None,\n                     ps_param=None, regularizer=None):\n        if isinstance(param_name, core.BlobReference):\n            param_name = str(param_name)\n        elif isinstance(param_name, six.string_types):\n            # Parameter name will be equal to current Namescope that got\n            # resolved with the respect of parameter sharing of the scopes.\n            param_name = parameter_sharing_context.get_parameter_name(\n                param_name)\n        else:\n            raise \"Unsupported type for param_name\"\n\n        param_blob = core.BlobReference(param_name)\n\n        if len(initializer) == 1:\n            init_op_args = {}\n        else:\n            assert len(initializer) == 2\n            init_op_args = copy.deepcopy(initializer[1])\n        if shape is not None:\n            assert 'shape' not in init_op_args\n            init_op_args.update({'shape': shape})\n\n        initializer_op = None\n        if self._initialize_params:\n            initializer_op = core.CreateOperator(\n                initializer[0],\n                [],\n                param_blob,\n                **init_op_args\n            )\n\n        param = layers.LayerParameter(\n            parameter=param_blob,\n            initializer=initializer_op,\n            optimizer=optimizer,\n            ps_param=ps_param,\n            regularizer=regularizer\n        )\n\n        self._validate_param_shape(param_name, shape)\n\n        self._param_to_shape[param_name] = shape\n\n        return param\n\n    def next_layer_name(self, prefix):\n        base_name = core.ScopedName(prefix)\n        name = base_name\n        index = 0\n        while name in self._layer_names:\n            name = base_name + '_auto_' + str(index)\n            index += 1\n\n        self._layer_names.add(name)\n        return name\n\n    def add_layer(self, layer):\n        self._layers.append(layer)\n        for param in layer.get_parameters():\n            assert isinstance(param.parameter, core.BlobReference)\n\n            self.param_to_optim[str(param.parameter)] = \\\n                param.optimizer or self.default_optimizer\n\n            self.params.append(param.parameter)\n            if isinstance(param, layers.LayerParameter):\n                self.param_to_reg[param.parameter] = param.regularizer\n            elif isinstance(param, ParameterInfo):\n                # TODO:\n                # Currently, LSTM and RNNcells, which use ModelHelper instead of\n                # LayerModelHelper as super class, are called in pooling_methods\n                # In ModelHelper, regularization is not supported in create_param\n                # We will unify the way of create_param of ModelHelper and\n                # LayerModelHelper in the future.\n                logger.info('regularization is unsupported for ParameterInfo object')\n            else:\n                raise ValueError(\n                    'unknown object type besides ParameterInfo and LayerParameter: {}'\n                    .format(param)\n                )\n\n        # The primary value of adding everything to self.net - generation of the\n        # operators right away, i.e. if error happens it'll be detected\n        # immediately. Other than this - create_x_net should be called.\n        layer.add_operators(self.net, self.param_init_net)\n        return layer.output_schema\n\n    def get_parameter_blobs(self):\n        param_blobs = []\n        for layer in self._layers:\n            for param in layer.get_parameters():\n                param_blobs.append(param.parameter)\n\n        return param_blobs\n\n    def add_post_grad_net_modifiers(self, modifier):\n        assert modifier not in self._post_grad_net_modifiers,\\\n            \"{0} is already in {1}\".format(modifier, self._post_grad_net_modifiers)\n        assert isinstance(modifier, NetModifier),\\\n            \"{} has to be a NetModifier instance\".format(modifier)\n        self._post_grad_net_modifiers.append(modifier)\n\n    def add_final_net_modifiers(self, modifier):\n        assert modifier not in self._final_net_modifiers,\\\n            \"{0} is already in {1}\".format(modifier, self._final_net_modifiers)\n        assert isinstance(modifier, NetModifier),\\\n            \"{} has to be a NetModifier instance\".format(modifier)\n        self._final_net_modifiers.append(modifier)\n\n    @property\n    def seed(self):\n        return self._seed\n\n    @property\n    def sequence_seed(self):\n        return self._sequence_seed\n\n    def store_seed(self, seed, sequence_seed=True):\n        # Store seed config that will be applied to each op in the net.\n        self._seed = seed\n        # If sequence_seed is True, the i-th op has rand_seed=`seed + i`\n        self._sequence_seed = sequence_seed\n\n    def apply_seed(self, net):\n        if self._seed:\n            net.set_rand_seed(self._seed, self._sequence_seed)\n\n    @property\n    def default_optimizer(self):\n        return self._default_optimizer\n\n    @default_optimizer.setter\n    def default_optimizer(self, optimizer):\n        self._default_optimizer = optimizer\n\n    @property\n    def input_feature_schema(self):\n        return self._input_feature_schema\n\n    @property\n    def trainer_extra_schema(self):\n        return self._trainer_extra_schema\n\n    @property\n    def metrics_schema(self):\n        \"\"\"\n        Returns the schema that represents model output that should be used for\n        metric reporting.\n\n        During the training/evaluation this schema will be appended to the\n        schema that represents model output.\n        \"\"\"\n        return self._metrics_schema\n\n    @property\n    def output_schema(self):\n        assert self._output_schema is not None\n        return self._output_schema\n\n    @output_schema.setter\n    def output_schema(self, schema):\n        assert self._output_schema is None\n        self._output_schema = schema\n\n    @property\n    def loss(self):\n        assert self._loss is not None\n        return self._loss\n\n    @loss.setter\n    def loss(self, loss):\n        assert self._loss is None\n        self._loss = loss\n\n    def has_loss(self):\n        return self._loss is not None\n\n    def add_loss(self, loss, name='unnamed'):\n        assert loss is not None, \"Added loss should not be None\"\n        assert isinstance(loss, schema.Scalar) or isinstance(\n            loss, schema.Struct\n        ), \"Added loss should be a scalar or a struct\"\n        if self._loss is None:\n            self._loss = schema.Struct((name, loss))\n        else:\n            prefix_base = name + '_auto_'\n            index = 0\n            prefix = name\n            while prefix in self._loss:\n                prefix = prefix_base + str(index)\n                index += 1\n            loss_struct = schema.Struct((prefix, loss))\n            self._loss = self._loss + loss_struct\n\n    def add_output_schema(self, name, value):\n        assert value is not None, \\\n            'Added output schema {} should not be None'.format(name)\n        assert isinstance(value, schema.Scalar) or \\\n            isinstance(value, schema.Struct), \\\n            'Added output schema {} should be a scalar or a struct.\\n\\\n            Now it is {}.'.format(name, type(value))\n        if self._output_schema is None:  # be the first field\n            self._output_schema = schema.Struct((name, value))\n        else:  # merge with other fields\n            assert name not in self._output_schema.fields, \\\n                'Output Schema Field {} already exists'.format(name)\n            self._output_schema = \\\n                self._output_schema + schema.Struct((name, value))\n\n    def add_trainer_extra_schema(self, trainer_extra_schema):\n        trainer_extra_record = schema.NewRecord(self.net, trainer_extra_schema)\n        self._trainer_extra_schema += trainer_extra_record\n\n    def __getattr__(self, layer):\n        if layer.startswith('__'):\n            raise AttributeError(layer)\n\n        # TODO(amalevich): Add add support for ifbpy inline documentation\n        if layers.layer_exists(layer):\n            def wrapper(*args, **kwargs):\n                new_layer = layers.create_layer(layer, self, *args, **kwargs)\n                if kwargs.get(\"output_to_metrics\", False):\n                    new_layer.export_output_for_metrics()\n                if kwargs.get(\"params_to_metrics\", False):\n                    new_layer.export_params_for_metrics()\n                return self.add_layer(new_layer)\n            return wrapper\n        elif core.IsOperator(layer):\n            def wrapper(*args, **kwargs):\n                def apply_operator(net, in_record, out_record, **kwargs):\n                    # TODO(amalevich): Switch to net.operator as soon as it gets\n                    # landed\n                    net.__getattr__(layer)(in_record.field_blobs(),\n                                           out_record.field_blobs(),\n                                           **kwargs)\n\n                if 'name' not in kwargs:\n                    kwargs['name'] = layer\n\n                new_layer = layers.create_layer(\n                    'Functional',\n                    self, *args, function=apply_operator,\n                    **kwargs\n                )\n\n                if kwargs.get(\"output_to_metrics\", False):\n                    new_layer.export_output_for_metrics()\n                if kwargs.get(\"params_to_metrics\", False):\n                    new_layer.export_params_for_metrics()\n\n                return self.add_layer(new_layer)\n            return wrapper\n        else:\n            raise ValueError(\n                \"Trying to create non-registered layer: {}\".format(layer))\n\n    @property\n    def layers(self):\n        return self._layers\n\n    def apply_regularizers_on_loss(\n        self,\n        train_net,\n        train_init_net,\n        blob_to_device=None,\n    ):\n        for param, regularizer in viewitems(self.param_to_reg):\n            if regularizer is None or regularizer.apply_after_optimizer:\n                continue\n            assert isinstance(regularizer, Regularizer)\n            added_loss_blob = regularizer(train_net, train_init_net, param)\n            self.add_loss(\n                schema.Scalar(blob=added_loss_blob),\n                str(added_loss_blob)\n            )\n\n    def apply_regularizers_after_optimizer(\n        self,\n        train_net,\n        train_init_net,\n        grad_map,\n        blob_to_device=None,\n    ):\n        for param, regularizer in viewitems(self.param_to_reg):\n            if regularizer is None or not regularizer.apply_after_optimizer:\n                continue\n            assert isinstance(regularizer, Regularizer)\n            regularizer(\n                train_net, train_init_net, param, grad_map.get(str(param)))\n\n    def apply_post_grad_net_modifiers(\n        self,\n        trainer_net,\n        trainer_init_net,\n        grad_map,\n        blob_to_device=None,\n    ):\n        for modifier in self._post_grad_net_modifiers:\n            modifier(trainer_net, trainer_init_net, grad_map,\n                     blob_to_device=blob_to_device)\n\n    def apply_final_net_modifiers(\n        self,\n        trainer_net,\n        trainer_init_net,\n        grad_map,\n        blob_to_device=None,\n    ):\n        for modifier in self._final_net_modifiers:\n            modifier(trainer_net, trainer_init_net, grad_map,\n                     blob_to_device=blob_to_device)\n\n    def apply_optimizers(\n        self,\n        train_net,\n        train_init_net,\n        grad_map,\n        blob_to_device=None,\n    ):\n        CPU = core.DeviceOption(caffe2_pb2.CPU)\n        # if given, blob_to_device is a map from blob to device_option\n        blob_to_device = blob_to_device or {}\n        for param, optimizer in viewitems(self.param_to_optim):\n            assert optimizer is not None, \\\n                \"default optimizer must have been set in add_layer\"\n            # note that not all params has gradient and thus we sent None if\n            # gradient does not exists\n            device = get_param_device(\n                param,\n                grad_map.get(str(param)),\n                param_to_device=blob_to_device,\n                default_device=CPU,\n            )\n            with core.DeviceScope(device):\n                optimizer(\n                    train_net, train_init_net, param, grad_map.get(str(param)))\n\n    def _GetOne(self):\n        return self.global_constants['ONE']\n\n    # An optimizer which allows us to do NO optimization\n    def NoOptim(self, *args, **kwargs):\n        pass\n\n    @property\n    def breakdown_map(self):\n        return self._breakdown_map\n\n    @breakdown_map.setter\n    def breakdown_map(self, breakdown_map):\n        # TODO(xlwang): provide more rich feature information in breakdown_map;\n        # and change the assertion accordingly\n        assert isinstance(breakdown_map, dict)\n        assert all(isinstance(k, six.string_types) for k in breakdown_map)\n        assert sorted(list(breakdown_map.values())) == range(len(breakdown_map))\n        self._breakdown_map = breakdown_map\n"
  },
  {
    "path": "caffe2/python/layer_model_instantiator.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package layer_model_instantiator\n# Module caffe2.python.layer_model_instantiator\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, schema\nfrom caffe2.python.layers.layers import InstantiationContext\nfrom caffe2.python.layers.tags import Tags\n\n\ndef _filter_layers(layers, include_tags):\n    if include_tags is None:\n        return layers\n    include_tags = set(include_tags)\n    return [l for l in layers if not include_tags.isdisjoint(l.tags)]\n\n\ndef shrink_output_schema(net, out_schema):\n    if len(out_schema.field_names()) <= 1:\n        return out_schema\n    exists = [net.BlobIsDefined(blob) for blob in out_schema.field_blobs()]\n    return schema.from_column_list(\n        [\n            col_name for ok, col_name in\n            zip(exists, out_schema.field_names()) if ok\n        ],\n        [\n            col_type for ok, col_type in\n            zip(exists, out_schema.field_types()) if ok\n        ],\n        [\n            col_blob for ok, col_blob in\n            zip(exists, out_schema.field_blobs()) if ok\n        ],\n        [\n            col_meta for ok, col_meta in\n            zip(exists, out_schema.field_metadata()) if ok\n        ]\n    )\n\n\ndef generate_predict_net(model, include_tags=None):\n    predict_net = core.Net('predict_net')\n\n    for layer in _filter_layers(model.layers, include_tags):\n        if Tags.EXCLUDE_FROM_PREDICTION not in layer.tags:\n            layer.add_operators(\n                predict_net, context=InstantiationContext.PREDICTION)\n\n    predict_net.set_input_record(model.input_feature_schema.clone())\n    output_schema = shrink_output_schema(\n        predict_net, model.output_schema.clone()\n    )\n    predict_net.set_output_record(output_schema)\n    return predict_net\n\n\ndef generate_eval_net(model, include_tags=None):\n    eval_net = core.Net('eval_net')\n\n    for layer in _filter_layers(model.layers, include_tags):\n        if Tags.EXCLUDE_FROM_EVAL not in layer.tags:\n            layer.add_operators(eval_net, context=InstantiationContext.EVAL)\n\n    input_schema = model.input_feature_schema + model.trainer_extra_schema\n    eval_net.set_input_record(input_schema)\n    output_schema = shrink_output_schema(\n        eval_net, model.output_schema + model.metrics_schema\n    )\n    eval_net.set_output_record(output_schema)\n    return eval_net\n\n\ndef _generate_training_net_only(model, include_tags=None):\n    train_net = core.Net('train_net')\n    train_init_net = model.create_init_net('train_init_net')\n\n    for layer in _filter_layers(model.layers, include_tags):\n        if Tags.EXCLUDE_FROM_TRAIN not in layer.tags:\n            layer.add_operators(train_net, train_init_net)\n\n    input_schema = model.input_feature_schema + model.trainer_extra_schema\n    train_net.set_input_record(input_schema)\n    output_schema = shrink_output_schema(\n        train_net, model.output_schema + model.metrics_schema\n    )\n    train_net.set_output_record(output_schema)\n    return train_init_net, train_net\n\n\ndef generate_training_nets_forward_only(model, include_tags=None):\n    train_init_net, train_net = _generate_training_net_only(model, include_tags)\n    return train_init_net, train_net\n\n\ndef generate_training_nets(model, include_tags=None):\n    train_init_net, train_net = _generate_training_net_only(model, include_tags)\n\n    model.apply_regularizers_on_loss(train_net, train_init_net)\n    loss = model.loss\n    grad_map = train_net.AddGradientOperators(loss.field_blobs())\n    model.apply_post_grad_net_modifiers(train_net, train_init_net, grad_map)\n    model.apply_optimizers(train_net, train_init_net, grad_map)\n    model.apply_regularizers_after_optimizer(train_net, train_init_net, grad_map)\n    model.apply_final_net_modifiers(train_net, train_init_net, grad_map)\n\n    return train_init_net, train_net\n"
  },
  {
    "path": "caffe2/python/layer_parameter_sharing_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import scope\nfrom caffe2.python.modeling.parameter_sharing import (\n    ParameterSharing,\n)\nfrom caffe2.python.layer_test_util import LayersTestCase\n\n\nclass ParameterSharingTest(LayersTestCase):\n\n    def test_layer_parameter_name(self):\n        output_dims = 2\n        with scope.NameScope('global_scope'):\n            fc1_output = self.model.FC(\n                self.model.input_feature_schema.float_features,\n                output_dims\n            )\n            self.assertEquals(self.model.layers[-1].w, 'global_scope/fc/w')\n            self.assertEquals(fc1_output(), 'global_scope/fc/output')\n\n            with scope.NameScope('nested_scope'):\n                fc2_output = self.model.FC(\n                    fc1_output,\n                    output_dims\n                )\n                self.assertEquals(self.model.layers[-1].w,\n                                  'global_scope/nested_scope/fc/w')\n                self.assertEquals(fc2_output(),\n                                  'global_scope/nested_scope/fc/output')\n\n                fc3_output = self.model.FC(\n                    fc1_output,\n                    output_dims\n                )\n                self.assertEquals(self.model.layers[-1].w,\n                                  'global_scope/nested_scope/fc_auto_0/w')\n                self.assertEquals(fc3_output(),\n                                  'global_scope/nested_scope/fc_auto_0/output')\n\n    def test_layer_shared_parameter_name_different_namescopes(self):\n        output_dims = 2\n        with scope.NameScope('global_scope'):\n            with ParameterSharing({'scope_1': 'scope_0'}):\n                with scope.NameScope('scope_0'):\n                    fc1_output = self.model.FC(\n                        self.model.input_feature_schema.float_features,\n                        output_dims\n                    )\n                    self.assertEquals(self.model.layers[-1].w,\n                                      'global_scope/scope_0/fc/w')\n                    self.assertEquals(fc1_output(),\n                                      'global_scope/scope_0/fc/output')\n\n                with scope.NameScope('scope_1'):\n                    fc2_output = self.model.FC(\n                        self.model.input_feature_schema.float_features,\n                        output_dims\n                    )\n                    self.assertEquals(self.model.layers[-1].w,\n                                      'global_scope/scope_0/fc/w')\n                    self.assertEquals(fc2_output(),\n                                      'global_scope/scope_1/fc/output')\n\n    def test_layer_shared_parameter_name_within_same_namescope(self):\n        output_dims = 2\n        with scope.NameScope('global_scope'):\n            with ParameterSharing({'fc_auto_0': 'fc'}):\n                self.model.FC(\n                    self.model.input_feature_schema.float_features,\n                    output_dims\n                )\n                self.assertEquals(self.model.layers[-1].w,\n                                  'global_scope/fc/w')\n\n                self.model.FC(\n                    self.model.input_feature_schema.float_features,\n                    output_dims\n                )\n                self.assertEquals(self.model.layers[-1].w,\n                                  'global_scope/fc/w')\n\n    def test_layer_shared_parameter_name_within_same_namescope_customized_name(self):\n        output_dims = 2\n        with scope.NameScope('global_scope'):\n            with ParameterSharing({'new_fc': 'shared_fc'}):\n                self.model.FC(\n                    self.model.input_feature_schema.float_features,\n                    output_dims,\n                    name='shared_fc'\n                )\n                self.assertEquals(self.model.layers[-1].w,\n                                  'global_scope/shared_fc/w')\n\n                self.model.FC(\n                    self.model.input_feature_schema.float_features,\n                    output_dims,\n                    name='new_fc'\n                )\n                self.assertEquals(self.model.layers[-1].w,\n                                  'global_scope/shared_fc/w')\n\n    def test_layer_shared_parameter_name_different_shapes(self):\n        output_dims = 2\n        with scope.NameScope('global_scope'):\n            with ParameterSharing({'fc_auto_0': 'fc'}):\n                self.model.FC(\n                    self.model.input_feature_schema.float_features,\n                    output_dims\n                )\n                self.assertEquals(self.model.layers[-1].w,\n                                  'global_scope/fc/w')\n\n                with self.assertRaisesRegexp(ValueError, 'Got inconsistent shapes .*'):\n                    self.model.FC(\n                        self.model.input_feature_schema.float_features,\n                        output_dims + 1\n                    )\n"
  },
  {
    "path": "caffe2/python/layer_test_util.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package layer_test_util\n# Module caffe2.python.layer_test_util\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom collections import namedtuple\n\nfrom caffe2.python import (\n    core,\n    layer_model_instantiator,\n    layer_model_helper,\n    schema,\n    test_util,\n    workspace,\n    utils,\n)\nfrom caffe2.proto import caffe2_pb2\nimport numpy as np\n\n\nclass OpSpec(namedtuple(\"OpSpec\", \"type input output arg\")):\n\n    def __new__(cls, op_type, op_input, op_output, op_arg=None):\n        return super(OpSpec, cls).__new__(cls, op_type, op_input,\n                                          op_output, op_arg)\n\n\nclass LayersTestCase(test_util.TestCase):\n\n    def setUp(self):\n        super(LayersTestCase, self).setUp()\n        self.setup_example()\n\n    def setup_example(self):\n        \"\"\"\n        This is undocumented feature in hypothesis,\n        https://github.com/HypothesisWorks/hypothesis-python/issues/59\n        \"\"\"\n        workspace.ResetWorkspace()\n        self.reset_model()\n\n    def reset_model(self, input_feature_schema=None, trainer_extra_schema=None):\n        input_feature_schema = input_feature_schema or schema.Struct(\n            ('float_features', schema.Scalar((np.float32, (32,)))),\n        )\n        trainer_extra_schema = trainer_extra_schema or schema.Struct()\n        self.model = layer_model_helper.LayerModelHelper(\n            'test_model',\n            input_feature_schema=input_feature_schema,\n            trainer_extra_schema=trainer_extra_schema)\n\n    def new_record(self, schema_obj):\n        return schema.NewRecord(self.model.net, schema_obj)\n\n    def get_training_nets(self):\n        \"\"\"\n        We don't use\n        layer_model_instantiator.generate_training_nets_forward_only()\n        here because it includes initialization of global constants, which make\n        testing tricky\n        \"\"\"\n        train_net = core.Net('train_net')\n        train_init_net = core.Net('train_init_net')\n        for layer in self.model.layers:\n            layer.add_operators(train_net, train_init_net)\n        return train_init_net, train_net\n\n    def get_eval_net(self):\n        return layer_model_instantiator.generate_eval_net(self.model)\n\n    def get_predict_net(self):\n        return layer_model_instantiator.generate_predict_net(self.model)\n\n    def run_train_net(self):\n        self.model.output_schema = schema.Struct()\n        train_init_net, train_net = \\\n            layer_model_instantiator.generate_training_nets(self.model)\n        workspace.RunNetOnce(train_init_net)\n        workspace.RunNetOnce(train_net)\n\n    def run_train_net_forward_only(self, num_iter=1):\n        self.model.output_schema = schema.Struct()\n        train_init_net, train_net = \\\n            layer_model_instantiator.generate_training_nets_forward_only(\n                self.model)\n        workspace.RunNetOnce(train_init_net)\n        assert num_iter > 0, 'num_iter must be larger than 0'\n        workspace.CreateNet(train_net)\n        workspace.RunNet(train_net.Proto().name, num_iter=num_iter)\n\n    def assertBlobsEqual(self, spec_blobs, op_blobs):\n        \"\"\"\n        spec_blobs can either be None or a list of blob names. If it's None,\n        then no assertion is performed. The elements of the list can be None,\n        in that case, it means that position will not be checked.\n        \"\"\"\n        if spec_blobs is None:\n            return\n        self.assertEqual(len(spec_blobs), len(op_blobs))\n        for spec_blob, op_blob in zip(spec_blobs, op_blobs):\n            if spec_blob is None:\n                continue\n            self.assertEqual(spec_blob, op_blob)\n\n    def assertArgsEqual(self, spec_args, op_args):\n        self.assertEqual(len(spec_args), len(op_args))\n        keys = [a.name for a in op_args]\n\n        def parse_args(args):\n            operator = caffe2_pb2.OperatorDef()\n            # Generate the expected value in the same order\n            for k in keys:\n                v = args[k]\n                arg = utils.MakeArgument(k, v)\n                operator.arg.add().CopyFrom(arg)\n            return operator.arg\n\n        self.assertEqual(parse_args(spec_args), op_args)\n\n    def assertNetContainOps(self, net, op_specs):\n        \"\"\"\n        Given a net and a list of OpSpec's, check that the net match the spec\n        \"\"\"\n        ops = net.Proto().op\n        self.assertEqual(len(op_specs), len(ops))\n        for op, op_spec in zip(ops, op_specs):\n            self.assertEqual(op_spec.type, op.type)\n            self.assertBlobsEqual(op_spec.input, op.input)\n            self.assertBlobsEqual(op_spec.output, op.output)\n            if op_spec.arg is not None:\n                self.assertArgsEqual(op_spec.arg, op.arg)\n        return ops\n"
  },
  {
    "path": "caffe2/python/layers/__init__.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom importlib import import_module\nimport pkgutil\nimport sys\nfrom . import layers\n\n\ndef import_recursive(package):\n    \"\"\"\n    Takes a package and imports all modules underneath it\n    \"\"\"\n\n    pkg_dir = package.__path__\n    module_location = package.__name__\n    for (_module_loader, name, ispkg) in pkgutil.iter_modules(pkg_dir):\n        module_name = \"{}.{}\".format(module_location, name)  # Module/package\n        module = import_module(module_name)\n        if ispkg:\n            import_recursive(module)\n\n\ndef find_subclasses_recursively(base_cls, sub_cls):\n    cur_sub_cls = base_cls.__subclasses__()\n    sub_cls.update(cur_sub_cls)\n    for cls in cur_sub_cls:\n        find_subclasses_recursively(cls, sub_cls)\n\n\nimport_recursive(sys.modules[__name__])\n\nmodel_layer_subcls = set()\nfind_subclasses_recursively(layers.ModelLayer, model_layer_subcls)\n\nfor cls in list(model_layer_subcls):\n    layers.register_layer(cls.__name__, cls)\n"
  },
  {
    "path": "caffe2/python/layers/add_bias.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package add_bias\n# Module caffe2.python.layers.add_bias\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import ModelLayer\nimport math\n\n\nclass AddBias(ModelLayer):\n\n    def __init__(self, model, input_record, bias_init=None,\n                 bias_optim=None, name='add_bias'):\n        super(AddBias, self).__init__(model, name, input_record)\n        assert isinstance(input_record, schema.Scalar), \"Incorrect input type\"\n        assert len(input_record.field_type().shape) > 0, (\n            \"AddBias expects limited dimensions of the input tensor\")\n\n        input_dims = input_record.field_type().shape[0]\n        assert input_dims > 0, (\n            \"AddBias expects input dimensions > 0, got {}\".format(input_dims))\n\n        scale = math.sqrt(1.0 / input_dims)\n        bias_init = bias_init if bias_init else (\n            'UniformFill', {'min': -scale, 'max': scale})\n\n        self.b = self.create_param(\n            param_name='b',\n            shape=[input_dims, ],\n            initializer=bias_init,\n            optimizer=bias_optim,\n        )\n\n        self.output_schema = schema.Scalar(\n            (input_record.field_type().base, (input_dims, )),\n            self.get_next_blob_reference('output')\n        )\n\n    def add_ops(self, net):\n        net.Add(self.input_record.field_blobs() + [self.b],\n                self.output_schema.field_blobs(), broadcast=1)\n"
  },
  {
    "path": "caffe2/python/layers/arc_cosine_feature_map.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import ModelLayer\nimport numpy as np\n\n\nclass ArcCosineFeatureMap(ModelLayer):\n    \"\"\"\n    A general version of the arc-cosine kernel feature map (s = 1 restores\n    the original arc-cosine kernel feature map).\n\n    Applies H(x) * x^s, where H is the Heaviside step function and x is the\n    input after applying FC (such that x = w * x_orig + b).\n\n    For more information, see the original paper:\n        http://cseweb.ucsd.edu/~saul/papers/nips09_kernel.pdf\n\n    Inputs :\n        output_dims -- dimensions of the output vector\n        s -- degree to raise transformed features\n        scale -- amount to scale the standard deviation\n        weight_init -- initialization distribution for weight parameter\n        bias_init -- initialization distribution for bias pararmeter\n        weight_optim -- optimizer for weight params; None for random features\n        bias_optim -- optimizer for bias param; None for random features\n        set_weight_as_global_constant -- if True, initialized random parameters\n                                         will be constant across all distributed\n                                         instances of the layer\n        initialize_output_schema -- if True, initialize output schema as Scalar\n                                    from Arc Cosine; else output schema is None\n    \"\"\"\n    def __init__(\n            self,\n            model,\n            input_record,\n            output_dims,\n            s=1,\n            scale=1.0,\n            weight_init=None,\n            bias_init=None,\n            weight_optim=None,\n            bias_optim=None,\n            set_weight_as_global_constant=False,\n            initialize_output_schema=True,\n            name='arc_cosine_feature_map',\n            **kwargs):\n\n        super(ArcCosineFeatureMap, self).__init__(model, name, input_record,\n                                                  **kwargs)\n        assert isinstance(input_record, schema.Scalar), \"Incorrect input type\"\n        self.params = []\n        self.model = model\n        self.set_weight_as_global_constant = set_weight_as_global_constant\n\n        self.input_dims = input_record.field_type().shape[0]\n        assert self.input_dims >= 1, \"Expected input dimensions >= 1, got %s\" \\\n                                     % self.input_dims\n\n        if initialize_output_schema:\n            self.output_schema = schema.Scalar(\n                (np.float32, (output_dims, )),\n                model.net.NextScopedBlob(name + '_output')\n            )\n\n        self.output_dims = output_dims\n        assert self.output_dims >= 1, \"Expected output dimensions >= 1, got %s\" \\\n                                      % self.output_dims\n        self.s = s\n        assert (self.s >= 0), \"Expected s >= 0, got %s\" % self.s\n        assert isinstance(self.s, int), \"Expected s to be type int, got type %s\" \\\n                                        % type(self.s)\n\n        assert (scale > 0.0), \"Expected scale > 0, got %s\" % scale\n        self.stddev = scale * np.sqrt(1.0 / self.input_dims)\n\n        # Initialize train_init_net parameters\n        # Random Parameters\n        if set_weight_as_global_constant:\n            w_init = np.random.normal(scale=self.stddev,\n                                      size=(self.output_dims, self.input_dims))\n            b_init = np.random.uniform(low=-0.5 * self.stddev,\n                                       high=0.5 * self.stddev,\n                                       size=self.output_dims)\n            self.random_w = self.model.add_global_constant(\n                name=self.name + \"_fixed_rand_W\",\n                array=w_init\n            )\n            self.random_b = self.model.add_global_constant(\n                name=self.name + \"_fixed_rand_b\",\n                array=b_init\n            )\n        else:\n            (self.random_w, self.random_b) = self._initialize_params(\n                'random_w',\n                'random_b',\n                w_init=weight_init,\n                b_init=bias_init,\n                w_optim=weight_optim,\n                b_optim=bias_optim\n            )\n\n    def _initialize_params(self, w_name, b_name, w_init=None, b_init=None,\n                           w_optim=None, b_optim=None):\n        \"\"\"\n        Initializes the Layer Parameters for weight and bias terms for features\n\n        Inputs :\n            w_blob -- blob to contain w values\n            b_blob -- blob to contain b values\n            w_init -- initialization distribution for weight parameter\n            b_init -- initialization distribution for bias parameter\n            w_optim -- optimizer to use for w; if None, then will use no optimizer\n            b_optim -- optimizer to user for b; if None, then will use no optimizer\n        \"\"\"\n\n        w_init = w_init if w_init else (\n            'GaussianFill', {'mean': 0.0, 'std': self.stddev}\n        )\n        w_optim = w_optim if w_optim else self.model.NoOptim\n\n        b_init = b_init if b_init else (\n            'UniformFill', {'min': -0.5 * self.stddev, 'max': 0.5 * self.stddev}\n        )\n        b_optim = b_optim if b_optim else self.model.NoOptim\n\n        w_param = self.create_param(param_name=w_name,\n                                    shape=(self.output_dims, self.input_dims),\n                                    initializer=w_init,\n                                    optimizer=w_optim)\n\n        b_param = self.create_param(param_name=b_name,\n                                    shape=[self.output_dims],\n                                    initializer=b_init,\n                                    optimizer=b_optim)\n\n        return [w_param, b_param]\n\n    def _heaviside_with_power(self, net, input_features, output_blob, s):\n        \"\"\"\n        Applies Heaviside step function and Relu / exponentiation to features\n        depending on the value of s.\n\n        Inputs:\n            net -- net with operators\n            input_features -- features to processes\n            output_blob -- output blob reference\n            s -- degree to raise the transformed features\n        \"\"\"\n        if s == 0:\n            softsign_features = net.Softsign([input_features],\n                                             net.NextScopedBlob('softsign'))\n            return net.Relu(softsign_features, output_blob)\n        elif s == 1:\n            return net.Relu([input_features],\n                            output_blob)\n        else:\n            relu_features = net.Relu([input_features],\n                                     net.NextScopedBlob('relu_rand'))\n            pow_features = net.Pow([input_features],\n                                   net.NextScopedBlob('pow_rand'),\n                                   exponent=float(s - 1))\n            return net.Mul([relu_features, pow_features],\n                           output_blob)\n\n    def add_ops(self, net):\n        input_blob = self.input_record.field_blobs()\n\n        # Random features: wx + b\n        random_features = net.FC(input_blob + [self.random_w, self.random_b],\n                                 net.NextScopedBlob('random_features'))\n        # Process random features\n        self._heaviside_with_power(net,\n                                   random_features,\n                                   self.output_schema.field_blobs(),\n                                   self.s)\n"
  },
  {
    "path": "caffe2/python/layers/batch_distill_lr_loss.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package batch_distill_lr_loss\n# Module caffe2.python.layers.batch_distill_lr_loss\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, schema\nfrom caffe2.python.layers.layers import (\n    ModelLayer,\n)\nfrom caffe2.python.layers.tags import (\n    Tags\n)\nimport numpy as np\n\n\nclass BatchDistillLRLoss(ModelLayer):\n\n    def __init__(\n            self, model, input_record,\n            name='batch_distill_lr_loss', teacherWeight=0.0, **kwargs):\n\n        super(BatchDistillLRLoss, self).__init__(model, name, input_record, **kwargs)\n\n        assert teacherWeight >= 0 and teacherWeight <= 1, (\n            'teacherWeight=%0.2f should be in [0, 1]' % teacherWeight\n        )\n        self._teacherWeight = teacherWeight\n\n        assert schema.is_schema_subset(\n            schema.Struct(\n                ('teacher_label', schema.Scalar()),\n                ('label', schema.Scalar()),\n                ('logit', schema.Scalar()),\n            ),\n            input_record\n        )\n        self.tags.update([Tags.EXCLUDE_FROM_PREDICTION])\n\n        self.output_schema = schema.Scalar(\n            np.float32,\n            self.get_next_blob_reference('output')\n        )\n\n    def add_ops(self, net):\n        label = self.input_record.label()\n        if self.input_record.label.field_type() != np.float32:\n            label = net.Cast(\n                label,\n                net.NextScopedBlob('float_label'),\n                to=core.DataType.FLOAT,\n            )\n\n        # Assuming 1-D input\n        label = net.ExpandDims(label, net.NextScopedBlob('expanded_label'),\n                               dims=[1])\n\n        teacher_label = self.input_record.teacher_label()\n        if self.input_record.teacher_label.field_type() != np.float32:\n            teacher_label = net.Cast(\n                teacher_label,\n                net.NextScopedBlob('float_teacher_label'),\n                to=core.DataType.FLOAT,\n            )\n        teacher_label = net.ExpandDims(\n            teacher_label, net.NextScopedBlob('expanded_teacher_label'),\n            dims=[1])\n\n        true_xent = net.SigmoidCrossEntropyWithLogits(\n            [self.input_record.logit(), label],\n            net.NextScopedBlob('cross_entropy')\n        )\n\n        teacher_xent = net.SigmoidCrossEntropyWithLogits(\n            [self.input_record.logit(), teacher_label],\n            net.NextScopedBlob('teacher_cross_entropy')\n        )\n\n        scaled_true_xent = net.Scale(\n            true_xent,\n            net.NextScopedBlob('scaled_cross_entropy'),\n            scale=1.0 - self._teacherWeight,\n        )\n        scaled_teacher_xent = net.Scale(\n            teacher_xent,\n            net.NextScopedBlob('scaled_teacher_cross_entropy'),\n            scale=self._teacherWeight,\n        )\n\n        true_loss = net.AveragedLoss(\n            scaled_true_xent,\n            net.NextScopedBlob('true_loss')\n        )\n        teacher_loss = net.AveragedLoss(\n            scaled_teacher_xent,\n            net.NextScopedBlob('teacher_loss')\n        )\n\n        net.Add(\n            [true_loss, teacher_loss],\n            self.output_schema.field_blobs()\n        )\n"
  },
  {
    "path": "caffe2/python/layers/batch_lr_loss.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package batch_lr_loss\n# Module caffe2.python.layers.batch_lr_loss\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, schema\nfrom caffe2.python.layers.layers import (\n    ModelLayer,\n)\nfrom caffe2.python.layers.tags import (\n    Tags\n)\nimport numpy as np\n\n\nclass BatchLRLoss(ModelLayer):\n\n    def __init__(\n        self,\n        model,\n        input_record,\n        name='batch_lr_loss',\n        average_loss=True,\n        jsd_weight=0.0,\n        pos_label_target=1.0,\n        neg_label_target=0.0,\n        **kwargs\n    ):\n        super(BatchLRLoss, self).__init__(model, name, input_record, **kwargs)\n\n        self.average_loss = average_loss\n\n        assert (schema.is_schema_subset(\n            schema.Struct(\n                ('label', schema.Scalar()),\n                ('logit', schema.Scalar())\n            ),\n            input_record\n        ))\n\n        assert jsd_weight >= 0 and jsd_weight <= 1\n        self.jsd_weight = jsd_weight\n        if self.jsd_weight > 0:\n            assert 'prediction' in input_record\n            self.jsd_weight_const = model.add_global_constant(\n                'jsd_weight', self.jsd_weight\n            )\n            self.xent_weight_const = model.add_global_constant(\n                'xent_weight', 1 - self.jsd_weight\n            )\n\n        assert pos_label_target <= 1 and pos_label_target >= 0\n        assert neg_label_target <= 1 and neg_label_target >= 0\n        assert pos_label_target >= neg_label_target\n        self.pos_label_target = pos_label_target\n        self.neg_label_target = neg_label_target\n\n        self.tags.update([Tags.EXCLUDE_FROM_PREDICTION])\n\n        self.output_schema = schema.Scalar(\n            np.float32,\n            self.get_next_blob_reference('output')\n        )\n\n    def add_ops(self, net):\n        # numerically stable log-softmax with crossentropy\n        label = self.input_record.label()\n        # mandatory cast to float32\n        # self.input_record.label.field_type().base is np.float32 but\n        # label type is actually int\n        label = net.Cast(\n            label,\n            net.NextScopedBlob('label_float32'),\n            to=core.DataType.FLOAT)\n        label = net.ExpandDims(label, net.NextScopedBlob('expanded_label'),\n                                dims=[1])\n        if self.pos_label_target != 1.0 or self.neg_label_target != 0.0:\n            label = net.StumpFunc(\n                label,\n                net.NextScopedBlob('smoothed_label'),\n                threshold=0.5,\n                low_value=self.neg_label_target,\n                high_value=self.pos_label_target,\n            )\n        xent = net.SigmoidCrossEntropyWithLogits(\n            [self.input_record.logit(), label],\n            net.NextScopedBlob('cross_entropy'),\n        )\n        # fuse with JSD\n        if self.jsd_weight > 0:\n            jsd = net.BernoulliJSD(\n                [self.input_record.prediction(), label],\n                net.NextScopedBlob('jsd'),\n            )\n            loss = net.WeightedSum(\n                [xent, self.xent_weight_const, jsd, self.jsd_weight_const],\n                net.NextScopedBlob('loss'),\n            )\n        else:\n            loss = xent\n\n\n        if 'weight' in self.input_record.fields:\n            weight_blob = self.input_record.weight()\n            if self.input_record.weight.field_type().base != np.float32:\n                weight_blob = net.Cast(\n                    weight_blob,\n                    weight_blob + '_float32',\n                    to=core.DataType.FLOAT\n                )\n            weight_blob = net.StopGradient(\n                [weight_blob],\n                [net.NextScopedBlob('weight_stop_gradient')],\n            )\n            loss = net.Mul(\n                [loss, weight_blob],\n                net.NextScopedBlob('weighted_cross_entropy'),\n            )\n\n        if self.average_loss:\n            net.AveragedLoss(loss, self.output_schema.field_blobs())\n        else:\n            net.ReduceFrontSum(loss, self.output_schema.field_blobs())\n"
  },
  {
    "path": "caffe2/python/layers/batch_mse_loss.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package batch_mse_loss\n# Module caffe2.python.layers.batch_mse_loss\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, schema\nfrom caffe2.python.layers.layers import (\n    ModelLayer,\n)\nfrom caffe2.python.layers.tags import (\n    Tags\n)\nimport numpy as np\n\n\nclass BatchMSELoss(ModelLayer):\n\n    def __init__(self, model, input_record, name='batch_mse_loss', **kwargs):\n        super(BatchMSELoss, self).__init__(model, name, input_record, **kwargs)\n\n        assert schema.is_schema_subset(\n            schema.Struct(\n                ('label', schema.Scalar()),\n                ('prediction', schema.Scalar())\n            ),\n            input_record\n        )\n        self.tags.update([Tags.EXCLUDE_FROM_PREDICTION])\n\n        self.output_schema = schema.Scalar(\n            np.float32,\n            self.get_next_blob_reference('output'))\n\n    def add_ops(self, net):\n        prediction = net.Squeeze(\n            self.input_record.prediction(),\n            net.NextScopedBlob('squeezed_prediction'),\n            dims=[1]\n        )\n\n        label = self.input_record.label.field_blobs()\n        if self.input_record.label.field_type().base != (\n                self.input_record.prediction.field_type().base):\n\n            label = net.Cast(\n                label,\n                net.NextScopedBlob('cast_label'),\n                to=schema.data_type_for_dtype(\n                    self.input_record.prediction.field_type()\n                )\n            )\n\n        label = net.StopGradient(\n            label,\n            net.NextScopedBlob('stopped_label')\n        )\n\n        l2dist = net.SquaredL2Distance(\n            [label, prediction],\n            net.NextScopedBlob('l2')\n        )\n\n        if 'weight' in self.input_record.fields:\n            weight_blob = self.input_record.weight()\n            if self.input_record.weight.field_type().base != np.float32:\n                weight_blob = net.Cast(\n                    weight_blob,\n                    weight_blob + '_float32',\n                    to=core.DataType.FLOAT\n                )\n            weight_blob = net.StopGradient(\n                [weight_blob],\n                [net.NextScopedBlob('weight_stop_gradient')],\n            )\n            l2dist = net.Mul(\n                [l2dist, weight_blob],\n                net.NextScopedBlob('weighted_l2_distance'),\n            )\n\n        net.AveragedLoss(l2dist, self.output_schema.field_blobs())\n"
  },
  {
    "path": "caffe2/python/layers/batch_normalization.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import ModelLayer\n\nimport numpy as np\n\n\nclass BatchNormalization(ModelLayer):\n    def __init__(\n        self,\n        model,\n        input_record,\n        name='batch_normalization',\n        scale_optim=None,\n        bias_optim=None,\n        momentum=0.9,\n        order='NCHW',\n        **kwargs\n    ):\n        super(BatchNormalization, self).__init__(\n            model, name, input_record, **kwargs)\n\n        assert isinstance(input_record, schema.Scalar), \"Incorrect input type\"\n\n        self.input_shape = input_record.field_type().shape\n\n        if len(self.input_shape) == 3:\n            if order == \"NCHW\":\n                input_dims = self.input_shape[0]\n            elif order == \"NHWC\":\n                input_dims = self.input_shape[2]\n            else:\n                raise ValueError(\"Please specify a correct order\")\n        else:\n            assert len(self.input_shape) == 1, (\n                \"This layer supports only 4D or 2D tesnors\")\n            input_dims = self.input_shape[0]\n\n        self.output_schema = schema.Scalar(\n            (np.float32, self.input_shape),\n            self.get_next_blob_reference('output')\n        )\n\n        self.momentum = momentum\n        self.order = order\n\n        self.scale = self.create_param(param_name='scale',\n                                       shape=[input_dims],\n                                       initializer=('ConstantFill', {'value': 1.0}),\n                                       optimizer=scale_optim)\n        self.bias = self.create_param(param_name='bias',\n                                       shape=[input_dims],\n                                       initializer=('ConstantFill', {'value': 0.0}),\n                                       optimizer=bias_optim)\n        self.rm = self.create_param(param_name='running_mean',\n                                       shape=[input_dims],\n                                       initializer=('ConstantFill', {'value': 0.0}),\n                                       optimizer=model.NoOptim)\n        self.riv = self.create_param(param_name='running_inv_var',\n                                       shape=[input_dims],\n                                       initializer=('ConstantFill', {'value': 1.0}),\n                                       optimizer=model.NoOptim)\n\n    def _add_ops(self, net, is_test, out_blob=None):\n        original_input_blob = self.input_record.field_blobs()\n        input_blob = net.NextScopedBlob('expand_input')\n        if len(self.input_shape) == 1:\n            input_blob = net.ExpandDims(original_input_blob,\n                                        dims=[2, 3])\n        else:\n            input_blob = original_input_blob[0]\n\n        if out_blob is None:\n            bn_output = self.output_schema.field_blobs()\n        else:\n            bn_output = out_blob\n        if is_test:\n            output_blobs = bn_output\n        else:\n            output_blobs = bn_output + [self.rm, self.riv,\n                                        net.NextScopedBlob('bn_saved_mean'),\n                                        net.NextScopedBlob('bn_saved_iv')]\n\n        net.SpatialBN([input_blob, self.scale,\n                       self.bias, self.rm, self.riv],\n                      output_blobs,\n                      momentum=self.momentum,\n                      is_test=is_test,\n                      order=self.order)\n\n        if len(self.input_shape) == 1:\n            net.Squeeze(bn_output,\n                        bn_output,\n                        dims=[2, 3])\n\n    def add_train_ops(self, net):\n        self._add_ops(net, is_test=False)\n\n    def add_eval_ops(self, net):\n        self._add_ops(net, is_test=True)\n\n    def add_ops(self, net):\n        self.add_eval_ops(net)\n"
  },
  {
    "path": "caffe2/python/layers/batch_sigmoid_cross_entropy_loss.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package batch_sigmoid_cross_entropy_loss\n# Module caffe2.python.layers.batch_sigmoid_cross_entropy_loss\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import ModelLayer\nfrom caffe2.python.layers.tags import Tags\nimport numpy as np\n\n\nclass BatchSigmoidCrossEntropyLoss(ModelLayer):\n    def __init__(\n        self,\n        model,\n        input_record,\n        name='batch_sigmoid_cross_entropy_loss',\n        **kwargs\n    ):\n        super(BatchSigmoidCrossEntropyLoss, self).__init__(\n            model, name, input_record, **kwargs)\n\n        assert schema.is_schema_subset(\n            schema.Struct(\n                ('label', schema.Scalar(np.float32)),\n                ('prediction', schema.Scalar(np.float32)),\n            ),\n            input_record\n        )\n        assert input_record.prediction.field_type().shape == \\\n            input_record.label.field_type().shape, \\\n            \"prediction and label must have the same shape\"\n\n        self.tags.update([Tags.EXCLUDE_FROM_PREDICTION])\n\n        self.output_schema = schema.Scalar(\n            (np.float32, tuple()), self.get_next_blob_reference('loss')\n        )\n\n    def add_ops(self, net):\n        sigmoid_cross_entropy = net.SigmoidCrossEntropyWithLogits(\n            [self.input_record.prediction(), self.input_record.label()],\n            net.NextScopedBlob('sigmoid_cross_entropy')\n        )\n\n        net.AveragedLoss(\n            sigmoid_cross_entropy, self.output_schema.field_blobs())\n"
  },
  {
    "path": "caffe2/python/layers/batch_softmax_loss.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package batch_softmax_loss\n# Module caffe2.python.layers.batch_softmax_loss\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, schema\nfrom caffe2.python.layers.layers import ModelLayer\nimport numpy as np\n\n\nclass BatchSoftmaxLoss(ModelLayer):\n    def __init__(\n        self,\n        model,\n        input_record,\n        name='batch_softmax_loss',\n        label_smoothing_matrix=None,\n        **kwargs\n    ):\n        super(BatchSoftmaxLoss, self).__init__(\n            model, name, input_record, **kwargs)\n\n        assert schema.is_schema_subset(\n            schema.Struct(\n                ('label', schema.Scalar()),\n                ('prediction', schema.Scalar()),\n            ),\n            input_record\n        )\n        # default case: label is given NOT as target distribution\n        self.label_prob = False\n\n        # label smoothing matrix: a K * K matrix where K is the label\n        # cardinality; (i, j) element is the value of for label i\n        # treated/smoothed as label j\n        self.label_smoothing_matrix = label_smoothing_matrix\n        if self.label_smoothing_matrix is not None:\n            self.initialize_label_smoothing_constants()\n\n        self.output_schema = schema.Struct(\n            (\n                'softmax', schema.Scalar(\n                    input_record.prediction.field_type(),\n                    self.get_next_blob_reference('softmax')\n                )\n            ),\n            (\n                'loss', schema.Scalar(\n                    np.float32, self.get_next_blob_reference('loss')\n                )\n            ),\n        )\n\n    def initialize_label_smoothing_constants(self):\n        assert self.label_smoothing_matrix is not None\n        self.label_smoothing_matrix = np.array(\n            self.label_smoothing_matrix).astype(np.float32)\n        assert len(self.label_smoothing_matrix.shape) == 2\n        label_dim = self.label_smoothing_matrix.shape[0]\n        assert label_dim == self.label_smoothing_matrix.shape[1]\n\n        self.label_smoothing_matrix = self.model.add_global_constant(\n            '%s_label_smoothing_matrix' % self.name,\n            array=self.label_smoothing_matrix,\n            dtype=np.dtype(np.float32),\n        )\n        self.label_dim = self.model.add_global_constant(\n            '%s_label_dim' % self.name,\n            array=label_dim,\n            dtype=np.dtype(np.int64),\n        )\n        self.label_prob = True\n\n    def compute_smoothed_label(self, net):\n        assert self.label_smoothing_matrix is not None\n        label = self.input_record.label()\n        original_label_type = self.input_record.label.field_type()\n        if original_label_type.base != np.int64:\n            int64_label = net.NextScopedBlob('int64_label')\n            net.Cast([label], [int64_label], to=core.DataType.INT64)\n        else:\n            int64_label = label\n        one_hot_label = net.NextScopedBlob('one_hot_label')\n        smoothed_label = net.NextScopedBlob('smoothed_label')\n        net.OneHot([int64_label, self.label_dim], [one_hot_label])\n        net.MatMul([one_hot_label, self.label_smoothing_matrix], smoothed_label)\n        return smoothed_label\n\n    def add_ops(self, net):\n        label = self.input_record.label.field_blobs()\n        if self.label_smoothing_matrix is None:\n            if self.input_record.label.field_types()[0].base != np.int32:\n                label = [\n                    net.Cast(label,\n                             net.NextScopedBlob('int32_label'),\n                             to=core.DataType.INT32)\n                ]\n        else:\n            label = [self.compute_smoothed_label(net)]\n\n        softmax_input = self.input_record.prediction.field_blobs() + label\n\n        if 'weight' in self.input_record:\n            weight_blob = self.input_record.weight()\n            if self.input_record.weight.field_type().base != np.float32:\n                weight_blob = net.Cast(\n                    weight_blob,\n                    weight_blob + '_float32',\n                    to=core.DataType.FLOAT\n                )\n\n            softmax_input += [weight_blob]\n\n        net.SoftmaxWithLoss(\n            softmax_input,\n            self.output_schema.field_blobs(),\n            label_prob=self.label_prob,\n        )\n"
  },
  {
    "path": "caffe2/python/layers/concat.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package concat\n# Module caffe2.python.layers.concat\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import (\n    ModelLayer,\n)\nfrom future.utils import viewitems\nimport numpy as np\n\nimport logging\nlogger = logging.getLogger(__name__)\n\nclass Concat(ModelLayer):\n    \"\"\"\n    Construct Concat layer\n    Assume that first dimension is batch,\n\n    Example:\n\n        embedding_dim = 64\n        input_record = self.new_record(schema.Struct(\n            ('input1', schema.Scalar((np.float32, (embedding_dim, )))),\n            ('input2', schema.Scalar((np.float32, (embedding_dim, )))),\n            ('input3', schema.Scalar((np.float32, (embedding_dim, )))),\n        ))\n\n        output = self.model.Concat(input_record)\n        self.assertEqual(\n            schema.Scalar((np.float32, ((len(input_record.fields) * embedding_dim, )))),\n            output\n        )\n\n        # Note that in Concat layer we assume first dimension is batch.\n        # so input is B * embedding_dim\n        # add_axis=1 make it B * 1 * embedding_dim\n        # Concat on axis=1 make it B * N * embedding_dim\n\n        output = self.model.Concat(input_record, axis=1, add_axis=1)\n        self.assertEqual(\n            schema.Scalar((np.float32, ((len(input_record.fields), embedding_dim)))),\n            output\n        )\n    \"\"\"\n\n    def __init__(self, model, input_record, axis=1, add_axis=0,\n                 name='concat', **kwargs):\n        super(Concat, self).__init__(model, name, input_record, **kwargs)\n        self.axis = axis\n        self.add_axis = add_axis\n        assert not (axis == 0 and add_axis == 1), \\\n            \"It's not allowed to add axis=0\"\n        assert isinstance(input_record, schema.Struct),\\\n            \"Incorrect input type. Excpected Struct, but received: {0}\".\\\n            format(input_record)\n\n        shapes = []\n        for field_name, field_type in viewitems(input_record.fields):\n            assert isinstance(field_type, schema.Scalar),\\\n                \"Incorrect input type for {}. Excpected Scalar, but got: {}\".\\\n                format(field_name, field_type)\n            # Assume that first dimension is batch, so actual axis in shape is\n            # axis - 1\n            shape = list(field_type.field_type().shape)\n            if add_axis:\n                shape.insert(axis - 1, 1)\n            assert len(shape) >= axis,\\\n                \"Concat expects that limited dimensions of the input tensor\"\n            shapes.append(shape)\n        logger.info('Concat Layer input shapes: ' + str(shapes))\n\n        if axis == 0:\n            self.output_schema = schema.from_blob_list(\n                input_record[0],\n                [self.get_next_blob_reference('output')]\n            )\n            return\n\n        concat_dim = 0\n        for shape in shapes:\n            concat_dim += shape[axis - 1]\n            shape[axis - 1] = 0\n            assert shape == shapes[0],\\\n                \"Shapes {0} and {1} are not compatible for Concat\".\\\n                format(shape, shapes[0])\n        output_dims = shapes[0]\n        output_dims[axis - 1] = concat_dim\n\n        logger.info('Concat Layer output_dims: ' + str(output_dims))\n        self.output_schema = schema.Scalar(\n            (np.float32, output_dims),\n            self.get_next_blob_reference('output'))\n\n    def add_ops(self, net):\n        net.Concat(\n            self.input_record.field_blobs(),\n            [\n                self.output_schema.field_blobs()[0],\n                self.output_schema.field_blobs()[0] + \"_concat_dims\"\n            ],\n            axis=self.axis,\n            add_axis=self.add_axis,\n        )\n"
  },
  {
    "path": "caffe2/python/layers/conv.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package conv\n# Module caffe2.python.layers.conv\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import (\n    ModelLayer,\n)\nimport numpy as np\n\n\nclass Conv(ModelLayer):\n    \"\"\"\n        Convolutional layer\n        Input:\n        - input_record: at least has the shape info of C (num_channels)\n        - output_dim: number of convolutional filters\n        - kernel_h, kernel_w: kernel size for h and w\n        - stride_h, stride_w: stride for h and w\n        - pad_b, pad_l, pad_r, pad_t: padding sizes, if stride == 1,\n                                      'None' value will do auto padding\n        - order: either 'NHWC' or 'NCHW'\n    \"\"\"\n\n    def __init__(self, model, input_record, output_dim, kernel_h, kernel_w,\n                 stride_h, stride_w, pad_b=None, pad_l=None, pad_r=None,\n                 pad_t=None, order='NHWC', kernel_init=None, bias_init=None,\n                 kernel_optim=None, bias_optim=None,\n                 name='conv', **kwargs):\n\n        super(Conv, self).__init__(model, name, input_record, **kwargs)\n        assert isinstance(input_record, schema.Scalar), \"Incorrect input type\"\n        # input num_channels (C) is needed\n        input_dims = input_record.field_type().shape\n\n        assert (kernel_h > 0 and isinstance(kernel_h, int)), (\n            \"kernel_h should be positive integer\")\n        assert (kernel_w > 0 and isinstance(kernel_w, int)), (\n            \"kernel_w should be positive integer\")\n        self.kernel_h = kernel_h\n        self.kernel_w = kernel_w\n\n        assert (stride_h > 0 and isinstance(stride_h, int)), (\n            \"stride_h should be positive integer\")\n        assert (stride_w > 0 and isinstance(stride_w, int)), (\n            \"stride_w should be positive integer\")\n        self.stride_h = stride_h\n        self.stride_w = stride_w\n\n        # output_dim calculation (http://cs231n.github.io/convolutional-networks/)\n        # output_dim_w = (input_dim_w - kernel_w + pad_r + pad_l) / stride_w + 1\n        # so, do auto_padding requires\n        # pad_r, pad_l = [(input_dim_w - 1) * stride_w - input_dim_w + kernel_w] / 2\n        # similair for pad_t and pad_b to auto pad kernel_h\n        # here we only do auto padding for stride = 1 case\n        if stride_h == 1:\n            pad_t = int((kernel_h - 1) / 2) if pad_t is None else pad_t\n            pad_b = int((kernel_h - 1) / 2) if pad_b is None else pad_b\n        else:\n            pad_t = 0 if pad_t is None else pad_t\n            pad_b = 0 if pad_b is None else pad_b\n\n        if stride_w == 1:\n            pad_r = int((kernel_w - 1) / 2) if pad_r is None else pad_r\n            pad_l = int((kernel_w - 1) / 2) if pad_l is None else pad_l\n        else:\n            pad_r = 0 if pad_r is None else pad_r\n            pad_l = 0 if pad_l is None else pad_l\n\n        assert (pad_t >= 0 and isinstance(pad_t, int)), \"pad_t should be int >= 0\"\n        assert (pad_b >= 0 and isinstance(pad_b, int)), \"pad_b should be int >= 0\"\n        assert (pad_r >= 0 and isinstance(pad_r, int)), \"pad_r should be int >= 0\"\n        assert (pad_l >= 0 and isinstance(pad_l, int)), \"pad_l should be int >= 0\"\n        self.pad_t = pad_t\n        self.pad_b = pad_b\n        self.pad_r = pad_r\n        self.pad_l = pad_l\n\n        assert order in ['NHWC', 'NCHW'], \"order should either 'NHWC' or 'NCHW'\"\n        self.order = order\n\n        if order == 'NHWC':\n            input_c = input_dims[-1]\n            kernel_shape = [output_dim, kernel_h, kernel_w, input_c]\n        elif order == 'NCHW':\n            input_c = input_dims[0]\n            kernel_shape = [output_dim, input_c, kernel_h, kernel_w]\n        assert input_c > 0, (\n            \"Number of input channels in conv parameters should be positive\")\n\n        kernel_init = kernel_init if kernel_init else (\n            'XavierFill', {}\n        )\n        bias_init = bias_init if bias_init else (\n            'ConstantFill', {'value': 0.0}\n        )\n\n        self.kernel = self.create_param(\n            param_name='conv_kernel',\n            shape=kernel_shape,\n            initializer=kernel_init,\n            optimizer=kernel_optim,\n        )\n\n        self.bias = self.create_param(\n            param_name='conv_bias',\n            shape=[output_dim],\n            initializer=bias_init,\n            optimizer=bias_optim,\n        )\n\n        # the output_schema only has the num of output channels\n        # output_h and output_w would be inferred internally\n        self.output_schema = schema.Scalar(\n            (np.float32, (output_dim,)),\n            self.get_next_blob_reference('output')\n        )\n\n    def add_ops(self, net):\n        net.Conv(\n            self.input_record.field_blobs() + [self.kernel, self.bias],\n            self.output_schema.field_blobs(),\n            kernel_h=self.kernel_h,\n            kernel_w=self.kernel_w,\n            stride_h=self.stride_h,\n            stride_w=self.stride_w,\n            pad_t=self.pad_t,\n            pad_l=self.pad_l,\n            pad_b=self.pad_b,\n            pad_r=self.pad_r,\n            order=self.order\n        )\n"
  },
  {
    "path": "caffe2/python/layers/dropout.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n# Module caffe2.python.layers.dropout\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import ModelLayer\n\n\nclass Dropout(ModelLayer):\n\n    def __init__(\n            self,\n            model,\n            input_record,\n            name='dropout',\n            ratio=0.5,\n            **kwargs):\n\n        super(Dropout, self).__init__(model, name, input_record, **kwargs)\n        assert isinstance(input_record, schema.Scalar), \"Incorrect input type\"\n        assert (ratio >= 0 and ratio < 1.0), \\\n            \"Expected 0 <= ratio < 1, but got ratio of %s\" % ratio\n\n        self.output_schema = input_record.clone_schema()\n        self.output_schema.set_value(self.get_next_blob_reference('output'))\n\n        self.ratio = ratio\n\n    def _add_ops(self, net, is_test):\n        input_blob = self.input_record.field_blobs()\n        output_blobs = self.output_schema.field_blobs() \\\n                     + [net.NextScopedBlob('d_mask')]\n\n        net.Dropout(input_blob,\n                    output_blobs,\n                    ratio=self.ratio,\n                    is_test=is_test)\n\n    def add_train_ops(self, net):\n        self._add_ops(net, is_test=False)\n\n    def add_eval_ops(self, net):\n        self._add_ops(net, is_test=True)\n\n    def add_ops(self, net):\n        self.add_eval_ops(net)\n"
  },
  {
    "path": "caffe2/python/layers/fc.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package fc\n# Module caffe2.python.layers.fc\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import ModelLayer\nfrom caffe2.python.layers.sampling_trainable_mixin import SamplingTrainableMixin\nimport math\nimport numpy as np\n\n\nclass FC(SamplingTrainableMixin, ModelLayer):\n\n    def __init__(self, model, input_record, output_dims, weight_init=None,\n                 bias_init=None, weight_optim=None, bias_optim=None, name='fc',\n                 weight_reg=None, bias_reg=None, **kwargs):\n        super(FC, self).__init__(model, name, input_record, **kwargs)\n        assert isinstance(input_record, schema.Scalar), (\n            \"Incorrect input type {}\".format(input_record))\n        assert len(input_record.field_types()[0].shape) > 0, (\n            \"FC expects limited dimensions of the input tensor\")\n\n        input_dims = input_record.field_types()[0].shape[0]\n        assert input_dims > 0, (\n            \"FC expects input dimensions > 0, got {}\".format(input_dims))\n\n        scale = math.sqrt(1.0 / input_dims)\n        weight_init = weight_init if weight_init else (\n            'UniformFill', {'min': -scale, 'max': scale})\n        bias_init = bias_init if bias_init else (\n            'UniformFill', {'min': -scale, 'max': scale})\n\n        self.w = self.create_param(param_name='w',\n                                   shape=[output_dims, input_dims],\n                                   initializer=weight_init,\n                                   optimizer=weight_optim,\n                                   regularizer=weight_reg)\n\n        self.b = self.create_param(param_name='b',\n                                   shape=[output_dims, ],\n                                   initializer=bias_init,\n                                   optimizer=bias_optim,\n                                   regularizer=bias_reg)\n\n        self.output_schema = schema.Scalar(\n            (np.float32, (output_dims, )),\n            self.get_next_blob_reference('output')\n        )\n\n    def _add_ops(self, net, params):\n        net.FC(self.input_record.field_blobs() + params,\n               self.output_schema.field_blobs(), **self.kwargs)\n\n    @property\n    def param_blobs(self):\n        return [self.w, self.b]\n"
  },
  {
    "path": "caffe2/python/layers/fc_without_bias.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package fc_without_bias\n# Module caffe2.python.layers.fc_without_bias\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import ModelLayer\nfrom caffe2.python.layers.sampling_trainable_mixin import SamplingTrainableMixin\n\nimport math\nimport numpy as np\n\n\nclass FCWithoutBias(SamplingTrainableMixin, ModelLayer):\n    def __init__(\n        self,\n        model,\n        input_record,\n        output_dims,\n        weight_init=None,\n        weight_optim=None,\n        name='fc_without_bias',\n        **kwargs\n    ):\n        super(FCWithoutBias, self).__init__(model, name, input_record, **kwargs)\n        assert isinstance(input_record, schema.Scalar), \"Incorrect input type\"\n        assert len(input_record.field_types()[0].shape) > 0, (\n            \"FCWithoutBias expects limited dimensions of the input tensor\"\n        )\n\n        input_dims = input_record.field_types()[0].shape[0]\n        assert input_dims > 0, (\n            \"FCWithoutBias expects input dimensions > 0, got {}\".format(input_dims)\n        )\n\n        self.output_schema = schema.Scalar(\n            (np.float32, (output_dims, )),\n            self.get_next_blob_reference('output')\n        )\n\n        scale = math.sqrt(1.0 / input_dims)\n        weight_init = weight_init if weight_init else (\n            'UniformFill', {'min': -scale,\n                            'max': scale}\n        )\n\n        self.w = self.create_param(param_name='w',\n                                   shape=[output_dims, input_dims],\n                                   initializer=weight_init,\n                                   optimizer=weight_optim)\n\n    def _add_ops(self, net, params):\n        net.MatMul(\n            self.input_record.field_blobs() + params,\n            self.output_schema.field_blobs(), trans_b=1, **self.kwargs\n        )\n\n    @property\n    def param_blobs(self):\n        return [self.w]\n"
  },
  {
    "path": "caffe2/python/layers/feature_sparse_to_dense.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n# @package sparse_to_dense\n# Module caffe2.python.layers.sparse_to_dense\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import (\n    ModelLayer,\n)\nimport numpy as np\n\n\nclass FeatureSparseToDense(ModelLayer):\n\n    def __init__(self, model, input_record, input_specs,\n                 name='feature_sparse_to_dense', **kwargs):\n        \"\"\"\n        `input_specs` follows the format of FeatureSpec from schema. To be more\n        precise it's a namedtuple that should have:\n            'feature_type', 'feature_names', 'feature_ids'\n        \"\"\"\n        super(FeatureSparseToDense, self).__init__(model, name,\n                                            input_record, **kwargs)\n\n        self.input_specs = input_specs\n\n        outputs = []\n        for field, feature_specs in self.input_specs:\n            assert len(feature_specs.feature_names) ==\\\n                len(feature_specs.feature_ids)\n            if feature_specs.feature_type == 'FLOAT':\n                outputs.append((\n                    field,\n                    schema.Scalar(\n                        (np.float32, (len(feature_specs.feature_ids), )),\n                        self.get_next_blob_reference(field + '_output')\n                    )\n                ))\n            elif feature_specs.feature_type == 'ID_LIST':\n                outputs.append((\n                    field,\n                    schema.Struct(\n                        ('ranges',\n                            schema.Scalar(\n                                (\n                                    np.int32,\n                                    (len(feature_specs.feature_ids), 2)\n                                ),\n                                self.get_next_blob_reference(\n                                    field + '_ranges')\n                            ),\n                         ),\n                        ('values',\n                         schema.Scalar(np.int64,\n                                       self.get_next_blob_reference(\n                                           field + '_values')\n                                       ),\n                         )\n                    )\n                ))\n            elif feature_specs.feature_type == 'ID_SCORE_LIST':\n                outputs.append((\n                    field,\n                    schema.Struct(\n                        ('ranges',\n                            schema.Scalar(\n                                (\n                                    np.int32,\n                                    (len(feature_specs.feature_ids), 2)\n                                ),\n                                self.get_next_blob_reference(\n                                    field + '_ranges')\n                            ),\n                         ),\n                        ('ids',\n                         schema.Scalar(np.int64,\n                                       self.get_next_blob_reference(\n                                           field + '_ids')\n                                       ),\n                         ),\n                        ('scores',\n                         schema.Scalar(np.float32,\n                                       self.get_next_blob_reference(\n                                           field + '_scores')\n                                       ),\n                         )\n                    )\n                ))\n            elif feature_specs.feature_type == 'EMBEDDING':\n                # We don't know dimensions of embeddings in input data.\n                # Even though they should match dimensions from feature config,\n                # we keep ranges blob to check input data later.\n                outputs.append((\n                    field,\n                    schema.Struct(\n                        ('ranges',\n                            schema.Scalar(\n                                (\n                                    np.int32,\n                                    (len(feature_specs.feature_ids), 2)\n                                ),\n                                self.get_next_blob_reference(\n                                    field + '_ranges')\n                            ),\n                         ),\n                        ('values',\n                         schema.Scalar(np.float32,\n                                       self.get_next_blob_reference(\n                                           field + '_values')\n                                       ),\n                         )\n                    )\n                ))\n            else:\n                raise TypeError(\n                    \"Unsupported input type: {0}\".\n                    format(feature_specs.feature_type))\n\n        # TODO(amalevich): This schema is producing ranges. And thus if there is\n        # something using it it should support ranges as well. It might be\n        # confusing, if we don't add better support for ranges/have it as a\n        # first layer\n        self.output_schema = schema.Struct(\n            *outputs\n        )\n\n        # TODO(amalevich): Consider moving this data to schema, instead\n        # Structs doens't support attaching metadata to them and clonning\n        # will break things badly, but this is the most elegant way to pass\n        # this info around. Should we change it or it'll be too much work and\n        # not worse it?\n        for field, feature_specs in input_specs:\n            schema.attach_metadata_to_scalars(\n                self.output_schema[field],\n                schema.Metadata(\n                    feature_specs=feature_specs)\n            )\n        self.zero = model.global_constants['ZERO']\n        self.zero_range = model.global_constants['ZERO_RANGE']\n\n    # Add operators to all types that need to be densified\n    def add_ops(self, net):\n        record = self.input_record\n        for field, feature_specs in self.input_specs:\n            if feature_specs.feature_type == 'FLOAT':\n                net.SparseToDenseMask(\n                    [\n                        record[field].keys(),\n                        record[field].values(),\n                        self.zero,\n                        record[field].lengths(),\n                    ],\n                    [\n                        self.output_schema[field](),\n                    ],\n                    mask=feature_specs.feature_ids,\n                )\n            elif feature_specs.feature_type == 'ID_LIST':\n                id_list_ranges = net.LengthsToRanges(\n                    record[field].values.lengths(),\n                    net.NextScopedBlob('id_list_ranges')\n                )\n                net.SparseToDenseMask(\n                    [\n                        record[field].keys(), id_list_ranges, self.zero_range,\n                        record[field].lengths()\n                    ],\n                    self.output_schema[field].ranges(),\n                    mask=feature_specs.feature_ids,\n                )\n                # Alias helps to enforce the fact that all SparseToDense calls\n                # produce new blobs.\n                # Reusing blob names might result in some weird consequences\n                # during the delivery time, when content of the blobs is\n                # generated based on the inputSpecs.\n                net.Alias(record[field].values.items(),\n                          self.output_schema[field].values())\n            elif feature_specs.feature_type == 'ID_SCORE_LIST':\n                # TODO: merge this to the case above?\n                id_list_ranges = net.LengthsToRanges(\n                    record[field].values.lengths(),\n                    net.NextScopedBlob('id_score_list_ranges')\n                )\n                net.SparseToDenseMask(\n                    [\n                        record[field].keys(), id_list_ranges, self.zero_range,\n                        record[field].lengths()\n                    ],\n                    self.output_schema[field].ranges(),\n                    mask=feature_specs.feature_ids,\n                )\n                # Alias helps to enforce the fact that all SparseToDense calls\n                # produce new blobs.\n                # Reusing blob names might result in some weird consequences\n                # during the delivery time, when content of the blobs is\n                # generated based on the inputSpecs.\n                net.Alias(record[field].values.keys(),\n                          self.output_schema[field].ids())\n                net.Alias(record[field].values.values(),\n                          self.output_schema[field].scores())\n            elif feature_specs.feature_type == 'EMBEDDING':\n                ranges = net.LengthsToRanges(\n                    record[field].values.lengths(),\n                    net.NextScopedBlob('embeddings_ranges')\n                )\n                net.SparseToDenseMask(\n                    [\n                        record[field].keys(),\n                        ranges,\n                        self.zero_range,\n                        record[field].lengths()\n                    ],\n                    self.output_schema[field].ranges(),\n                    mask=feature_specs.feature_ids,\n                )\n                # Alias helps to enforce the fact that all SparseToDense calls\n                # produce new blobs.\n                # Reusing blob names might result in some weird consequences\n                # during the delivery time, when content of the blobs is\n                # generated based on the inputSpecs.\n                net.Alias(record[field].values.items(),\n                          self.output_schema[field].values())\n\n    def get_metadata(self):\n        metadata = []\n        for field, feature_specs in self.input_specs:\n            metadata.append(\n                (\n                    {\n                        'type': feature_specs.feature_type,\n                        'names': feature_specs.feature_names,\n                        'ids': feature_specs.feature_ids,\n                    },\n                    self.output_schema[field].field_blobs(),\n                    self.output_schema[field].field_types()\n                )\n            )\n            if feature_specs.feature_type == 'FLOAT':\n                metadata[-1][0]['cardinality'] = 1\n        return metadata\n"
  },
  {
    "path": "caffe2/python/layers/functional.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n# @package functional\n# Module caffe2.python.layers.functional\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, schema, scope, workspace\nfrom caffe2.python.layers.layers import (\n    ModelLayer,\n)\nimport caffe2.proto.caffe2_pb2 as caffe2_pb2\nimport numpy as np\nimport six\nimport logging\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO)\n\n\nclass Functional(ModelLayer):\n\n    def __init__(self, model, input_record, output_names_or_num, function,\n                 name='functional', output_dtypes=None, **kwargs):\n\n        # allow coercion\n        input_record = schema.as_record(input_record)\n\n        super(Functional, self).__init__(model, name, input_record, **kwargs)\n        self._function = function\n        self._kwargs = kwargs\n        return_struct = (\n            isinstance(output_names_or_num, list) or\n            (isinstance(output_names_or_num, six.integer_types) and\n             output_names_or_num != 1)\n        )\n\n        with scope.NameScope(self.name, reset=True):\n            if isinstance(output_names_or_num, int):\n                struct_output_schema = schema.NewRecord(\n                    model.net, schema.RawTuple(output_names_or_num))\n            elif isinstance(output_names_or_num, schema.Field):\n                self.output_schema = output_names_or_num.clone(keep_blobs=True)\n                return\n            else:\n                if not isinstance(output_names_or_num, list):\n                    output_names_or_num = [output_names_or_num]\n                out_tuple = [(out, np.void) for out in output_names_or_num]\n                struct_output_schema = schema.NewRecord(\n                    model.net, schema.Struct(*out_tuple))\n\n        num_outputs = len(struct_output_schema.field_blobs())\n\n        # functional layer returns Struct if more than one outputs or output is\n        # a list, otherwise Scalar\n        if return_struct:\n            self.output_schema = struct_output_schema\n        else:\n            self.output_schema = struct_output_schema[0]\n\n        # If output_dtypes is provided, use it for output schema. Otherwise\n        # the shape and type will be inferred.\n        if output_dtypes is not None:\n            if not isinstance(output_dtypes, list):\n                output_dtypes = [output_dtypes] * num_outputs\n            assert len(output_dtypes) == num_outputs\n            for dtype, scalar in zip(output_dtypes,\n                                     self.output_schema.all_scalars()):\n                scalar.set_type(dtype)\n            return\n\n        # Fake execution of the function to infer shapes and types automatically\n        had_issues = False\n        try:\n            type_net = core.Net('_temp_type_and_shape_inference_net')\n            schema.InitEmptyRecord(type_net, input_record, enforce_types=True)\n\n            function(type_net, self.input_record, self.output_schema, **kwargs)\n            (shapes, types) = workspace.InferShapesAndTypes([type_net], {})\n            for i in range(num_outputs):\n                scalar_schema = (self.output_schema[i] if return_struct\n                                 else self.output_schema)\n                blob = scalar_schema()\n                if blob not in types or blob not in shapes:\n                    had_issues = True\n                    continue\n                if shapes[blob] == []:\n                    # Scalar type\n                    shape = tuple()\n                elif shapes[blob][0] == 0:\n                    shape = tuple(shapes[blob][1:])\n                else:\n                    logger.warning(\"unexpeced shape: {}\".format(shapes[blob]))\n                    # If batch dimension is not first - give up on shape\n                    # inference for that blob\n                    had_issues = True\n                    continue\n\n                # TODO(amalevich): Move it to some shared library\n                dtype = None\n                if types[blob] == caffe2_pb2.TensorProto.DOUBLE:\n                    dtype = (np.float64, shape)\n                elif types[blob] == caffe2_pb2.TensorProto.FLOAT:\n                    dtype = (np.float32, shape)\n                elif types[blob] == caffe2_pb2.TensorProto.INT32:\n                    dtype = (np.int32, shape)\n                elif types[blob] == caffe2_pb2.TensorProto.INT64:\n                    dtype = (np.int64, shape)\n                elif types[blob] == caffe2_pb2.TensorProto.FLOAT16:\n                    dtype = (np.float16, shape)\n\n                if dtype is not None:\n                    scalar_schema.set_type(dtype)\n        except TypeError as ex:\n            had_issues = True\n            logger.warning(str(ex))\n\n        if had_issues:\n            logger.warning(\n                \"Type inference had problems for layer: {}\".format(self.name))\n\n    def add_ops(self, net):\n        self._function(\n            net, self.input_record, self.output_schema, **(self._kwargs))\n"
  },
  {
    "path": "caffe2/python/layers/gather_record.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package gather_record\n# Module caffe2.python.layers.gather_record\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, schema\nfrom caffe2.python.layers.layers import ModelLayer\n\n\nclass GatherRecord(ModelLayer):\n    \"\"\"\n    Given 1-D `indices` tensor, gather elements at `i` in `indices` from all the\n    blobs in `record`. If a blob is a values blob of a list, all the elements\n    included by the list's lengths blob are gathered. For example,\n\n    Input:\n        indices = [0, 2]\n        record:a = [[0, 1], [2, 3], [4, 5], [6, 7]]\n        record:b:lengths = [0, 1, 2, 3]\n        record:b:items = [0, 1, 2, 3, 4, 5]\n\n    Output:\n        a = [[0, 1], [4, 5]]\n        b:lengths = [0, 2]\n        b:items = [1, 2]\n\n    This supports nested list.\n    \"\"\"\n\n    def __init__(self, model, input_record, name='gather_record', **kwargs):\n        super(GatherRecord, self).__init__(model, name, input_record, **kwargs)\n\n        assert 'indices' in input_record\n        assert 'record' in input_record\n\n        self.output_schema = schema.NewRecord(\n            model.net, input_record.record.clone_schema())\n\n        self._indices = self.input_record.indices()\n\n    def _gather_scalar(self, net, record, lengths_blob, output_record):\n        if lengths_blob is None:\n            net.Gather([record(), self._indices], output_record())\n        else:\n            net.LengthsGather([record(), lengths_blob, self._indices],\n                              output_record())\n\n    def _gather_struct(self, net, record, lengths_blob, output_record):\n        for name, field in record.get_children():\n            self._dispatch(net, field, lengths_blob, output_record[name])\n\n    def _gather_list(self, net, record, lengths_blob, output_record):\n        self._gather_scalar(\n            net, record.lengths, lengths_blob, output_record.lengths)\n        if lengths_blob is None:\n            lengths_blob = record.lengths()\n        else:\n            # TODO(kittipat): This is a hacky solution until LengthsSum for int\n            # is implemented\n            lengths_float = net.Cast(\n                record.lengths(),\n                net.NextScopedBlob(str(record.lengths()) + '_float'),\n                to=core.DataType.FLOAT,\n            )\n            lengths_blob_float = net.LengthsSum(\n                [lengths_float, lengths_blob],\n                net.NextScopedBlob(str(record.lengths()) + \"_nested_float\")\n            )\n            lengths_blob = net.Cast(\n                lengths_blob_float,\n                net.NextScopedBlob(str(record.lengths()) + \"_nested\"),\n                to=core.DataType.INT32,\n            )\n        self._dispatch(net, record._items, lengths_blob, output_record._items)\n\n    def _dispatch(self, net, record, lengths_blob, output_record):\n        if isinstance(record, schema.Scalar):\n            self._gather_scalar(net, record, lengths_blob, output_record)\n        elif isinstance(record, schema.Struct):\n            self._gather_struct(net, record, lengths_blob, output_record)\n        elif isinstance(record, schema.List):\n            self._gather_list(net, record, lengths_blob, output_record)\n        else:\n            raise NotImplementedError\n\n    def add_ops(self, net):\n        self._dispatch(net, self.input_record.record, None, self.output_schema)\n"
  },
  {
    "path": "caffe2/python/layers/last_n_window_collector.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package last_n_window_collector\n# Module caffe2.python.layers.last_n_window_collector\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, schema\nfrom caffe2.python.layers.layers import ModelLayer\n\n\nclass LastNWindowCollector(ModelLayer):\n    \"\"\"\n    Collect last-N samples from input record. If you have complex data,\n    use PackRecords to pack it before using this layer.\n\n    This layer is not thread safe.\n    \"\"\"\n\n    def __init__(self, model, input_record, num_to_collect,\n                 name='last_n_window_collector', **kwargs):\n        super(LastNWindowCollector, self).__init__(\n            model, name, input_record, **kwargs)\n        assert num_to_collect > 0\n        self.num_to_collect = num_to_collect\n        assert isinstance(input_record, schema.Scalar), \\\n            \"Got {!r}\".format(input_record)\n\n        self.last_n = self.create_param(param_name='last_n',\n                                        shape=[0],\n                                        initializer=('ConstantFill', {}),\n                                        optimizer=model.NoOptim)\n\n        self.next_blob = self.create_param(\n            param_name='next',\n            shape=[],\n            initializer=('ConstantFill',\n                         {'value': 0, 'dtype': core.DataType.INT32}),\n            optimizer=model.NoOptim\n        )\n\n        self.mutex = self.create_param(\n            param_name='mutex',\n            shape=None,\n            initializer=('CreateMutex',),\n            optimizer=model.NoOptim,\n        )\n\n        self.num_visited_blob = self.create_param(\n            param_name='num_visited',\n            shape=[],\n            initializer=('ConstantFill', {\n                'value': 0,\n                'dtype': core.DataType.INT64,\n            }),\n            optimizer=model.NoOptim,\n        )\n\n        self.output_schema = schema.Struct(\n            (\n                'last_n',\n                schema.from_blob_list(input_record, [self.last_n])\n            ),\n            ('num_visited', schema.Scalar(blob=self.num_visited_blob)),\n            ('mutex', schema.Scalar(blob=self.mutex)),\n        )\n\n    def add_ops(self, net):\n        net.LastNWindowCollector(\n            [self.last_n, self.next_blob, self.input_record(), self.mutex,\n             self.num_visited_blob],\n            [self.last_n, self.next_blob, self.num_visited_blob],\n            num_to_collect=self.num_to_collect,\n        )\n"
  },
  {
    "path": "caffe2/python/layers/layers.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package layers\n# Module caffe2.python.layers.layers\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport logging\nfrom caffe2.python import core, schema, scope, workspace\nfrom caffe2.python.layers.tags import TagContext\nfrom caffe2.proto import caffe2_pb2\n\nfrom collections import namedtuple\nimport numpy as np\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO)\n\n# Some types to simplify descriptions of things traveling between ops\nIdList = schema.List(np.int64)\nIdScoreList = schema.Map(np.int64, np.float32)\n\n\ndef get_key(record):\n    if schema.equal_schemas(record, IdList):\n        key = 'values'\n    elif schema.equal_schemas(record, IdScoreList, check_field_types=False):\n        key = 'values:keys'\n    else:\n        raise NotImplementedError('Not implemented for {}'.format(record))\n    assert record[key].metadata is not None, (\n        \"Blob {} doesn't have metadata\".format(str(record[key]())))\n    return record[key]\n\n\ndef get_categorical_limit(record):\n    key = get_key(record)\n    return key.metadata.categorical_limit\n\n\ndef get_avg_length(record):\n    return record['lengths'].metadata.expected_value\n\n\ndef set_request_only(field):\n    for f in field.all_scalars():\n        categorical_limit, expected_value = None, None\n        if not f.metadata:\n            feature_specs = schema.FeatureSpec(\n                feature_is_request_only=True,\n            )\n        elif not f.metadata.feature_specs:\n            categorical_limit = f.metadata.categorical_limit\n            expected_value = f.metadata.expected_value\n            feature_specs = schema.FeatureSpec(\n                feature_is_request_only=True,\n            )\n        else:\n            categorical_limit = f.metadata.categorical_limit\n            expected_value = f.metadata.expected_value\n            feature_specs = schema.FeatureSpec(\n                feature_type=f.metadata.feature_specs.feature_type,\n                feature_names=f.metadata.feature_specs.feature_names,\n                feature_ids=f.metadata.feature_specs.feature_ids,\n                feature_is_request_only=True,\n                desired_hash_size=f.metadata.feature_specs.desired_hash_size,\n            )\n\n        # make sure not to set categorical_limit for a non-integer field\n        if not np.issubdtype(f.field_type(), np.integer):\n            assert categorical_limit is None, \\\n                \"categorical_limit shouldn't be set for no-integer field\"\n\n        f.set_metadata(\n            schema.Metadata(\n                categorical_limit=categorical_limit,\n                expected_value=expected_value,\n                feature_specs=feature_specs,\n            )\n        )\n\n\nclass InstantiationContext(object):\n    \"\"\"\n    List of contexts where layer could be instantitated\n    \"\"\"\n    # The layers support this context will accumulate predictions, labels,\n    # weights. The accumulated data can later be used to compute\n    # calibration or for other\n    # purpose.\n    ACCUMULATE_PRED = 'accumulate_pred'\n    EVAL = 'eval'\n    PREDICTION = 'prediction'\n    TRAINING = 'training'\n\n\n_LAYER_REGISTRY = {}\n\n\ndef register_layer(name, layer):\n    assert name not in _LAYER_REGISTRY, \"{0} already exists\".format(name)\n    _LAYER_REGISTRY[name] = layer\n\n\ndef layer_exists(name):\n    return name in _LAYER_REGISTRY\n\n\ndef get_layer_class(name):\n    return _LAYER_REGISTRY[name]\n\n\ndef create_layer(layer_name, *args, **kwargs):\n    return _LAYER_REGISTRY[layer_name](*args, **kwargs)\n\n\nLayerPsParam = namedtuple('LayerPsParam', ['sparse_key', 'average_length'])\n\n\nclass LayerParameter(object):\n\n    def __init__(self, parameter=None, optimizer=None, initializer=None,\n                 ps_param=None, regularizer=None):\n        assert isinstance(parameter, core.BlobReference), \\\n            \"expect {0} to be a blob reference\".format(str(parameter))\n        # need to put the following line (shape) before initialier\n        # shape will be updated once initializer is (re)set\n        self._shape = None\n        self.parameter = parameter\n        self.optimizer = optimizer\n        self.initializer = initializer\n        self.ps_param = ps_param\n        self.regularizer = regularizer\n\n    @property\n    def initializer(self):\n        return self._initializer\n\n    @initializer.setter\n    def initializer(self, op):\n        assert op is None or core.IsOperator(getattr(op, 'type', None)), \\\n            \"initializer expects an operator, got type: {}\".format(type(op))\n        self._initializer = op\n        if op is not None:\n            self.shape = self._infer_shape_from_initializer()\n\n    @property\n    def shape(self):\n        return self._shape\n\n    @shape.setter\n    def shape(self, shape):\n        assert self.shape is None or self.shape == shape, \\\n            \"inconsistent shape for layer parameter:\"\\\n            \" {}, expect: {}, but got {}\".format(self, self.shape, shape)\n        self._shape = shape\n\n    def _infer_shape_from_initializer(self):\n        for arg in self.initializer.arg:\n            if arg.name == 'shape':\n                return list(arg.ints)\n        with workspace.WorkspaceGuard(\"model_init_by_loading_params\"):\n            try:\n                net = core.Net(\"shape_checker\")\n                net._net.op.extend([self.initializer])\n                shape_blob = net.NextScopedBlob(self.parameter + \"_shape\")\n                net.Shape([self.parameter], shape_blob)\n                workspace.RunNetOnce(net)\n                shape = workspace.FetchBlob(shape_blob).tolist()\n                # ResetWorkspace to save memory\n                workspace.ResetWorkspace()\n                return shape\n            except RuntimeError as exp:\n                logger.warning(\n                    \"Cannot infer the shape of blob {} from operator {}: {}\".format(\n                        self.parameter, self.initializer.type, exp)\n                )\n                workspace.ResetWorkspace()\n                return None\n\n    def __str__(self):\n        return str(self.parameter)\n\n\ndef is_request_only_scalar(scalar):\n    if len(scalar.field_metadata()) == 0:\n        return False\n    for metadata in scalar.field_metadata():\n        if not (metadata and metadata.feature_specs and getattr(\n                metadata.feature_specs, 'feature_is_request_only', False)):\n            return False\n    return True\n\n\nclass ModelLayer(object):\n\n    def __init__(self, model, prefix, input_record,\n                 predict_input_record_fields=None, tags=None, **kwargs):\n        \"\"\"\n        Base class for model layers. Layer is an abstraction that allows to\n        provide model description in terms of meta-operators, where each of the\n        meta-operators can have different implementations for training,\n        evaluation and prediction, that are instantiated later. As an example\n        SampledSoftmax can do something related to sampling depending on\n        supervision during the training and just apply softmax if it's used for\n        prediction/evaluation.\n\n        All inputs/outputs from layers are represented as a record (instance of\n        schema bounded to blobs) and are accessible through input_record and\n        output_schema. If Layer needs to have only a subset of inputs/provides\n        subset of outputs during the inference - it should provide\n        predict_input_record and predict_output_schema correspondingly (those\n        records are expected to be a subset of input_record/output_schema).\n\n        Each layer has a list of Tags associated with it, that depends on\n        current context and arguments. It's possible to use those tags during\n        the instantiation time.\n\n        \"\"\"\n        self.name = model.next_layer_name(prefix)\n        self.model = model\n        self.kwargs = kwargs\n        self._input_record = input_record\n        if predict_input_record_fields:\n            if not isinstance(predict_input_record_fields, list):\n                predict_input_record_fields = [predict_input_record_fields]\n            self._predict_input_record = self._input_record[\n                predict_input_record_fields]\n        else:\n            self._predict_input_record = None\n\n        self.request_only = True\n        if len(input_record.all_scalars()) == 0:\n            self.request_only = False\n        for scalar in input_record.all_scalars():\n            if not is_request_only_scalar(scalar):\n                self.request_only = False\n                break\n\n        self._output_schema = None\n        self._predict_output_schema = None\n        self.eval_output_schema = None\n        self.tags = set(tags or [])\n        self.tags.update(TagContext.current().tags)\n        self.params = []\n        self._export_output_for_metrics = False\n        self._export_params_for_metrics = False\n\n    def get_type(self):\n        return self.__class__.__name__\n\n    def _check_output_schema(self):\n        assert self._output_schema is not None, \"Schema is not initialized\"\n        assert (self._predict_output_schema is None or\n                schema.is_schema_subset(self._predict_output_schema,\n                                        self._output_schema)), (\n            \"predict_output_schema is not a subset of the output_schema\")\n\n    @property\n    def predict_input_record(self):\n        return self._predict_input_record or self._input_record\n\n    @property\n    def input_record(self):\n        return self._input_record\n\n    @property\n    def predict_output_schema(self):\n        self._check_output_schema()\n        return self._predict_output_schema or self._output_schema\n\n    @predict_output_schema.setter\n    def predict_output_schema(self, output_schema):\n        assert self._predict_output_schema is None\n        self._predict_output_schema = output_schema\n\n    @property\n    def output_schema(self):\n        if self.request_only:\n            set_request_only(self._output_schema)\n        self._check_output_schema()\n        return self._output_schema\n\n    @output_schema.setter\n    def output_schema(self, output_schema):\n        assert self._output_schema is None\n        self._output_schema = output_schema\n\n    def get_parameters(self):\n        return self.params\n\n    def get_fp16_compatible_parameters(self):\n        \"\"\"Return a subset of parameters which can be converted to fp16\"\"\"\n        return []\n\n    def get_memory_usage(self):\n        return 0\n\n    def add_init_params(self, init_net):\n        '''\n        Adds layer initialization operators to passed net.\n        '''\n        for param in self.params:\n            # TODO(amalevich): Either return back to lambdas, that add\n            # all params (looks a bit safer and breaking less\n            # abstractions) or extend Net interface to this type of\n            # operations better\n            # TODO(xlwang) init_net._net.op has type google.protobuf.\\\n            # internal.containers.RepeatedCompositeFieldContainer, but\n            # the version of protobuf in fbcode does not support append\n            # so extend is used\n            init_op = param.initializer\n            current_device_scope = scope.CurrentDeviceScope()\n            if init_op:\n                if not init_op.HasField('device_option') and\\\n                        current_device_scope:\n                    init_op = caffe2_pb2.OperatorDef()\n                    init_op.CopyFrom(param.initializer)\n                    init_op.device_option.CopyFrom(current_device_scope)\n                init_net._net.op.extend([init_op])\n\n    def create_param(self, param_name, shape, initializer, optimizer,\n                     ps_param=None, regularizer=None):\n        with scope.NameScope(self.name, reset=True):\n            param = self.model.create_param(param_name=param_name,\n                                            shape=shape,\n                                            initializer=initializer,\n                                            optimizer=optimizer,\n                                            ps_param=ps_param,\n                                            regularizer=regularizer)\n\n            # make sure we don't share parameters in the same layer\n            assert all(param.parameter != p.parameter for p in self.params)\n\n            self.params.append(param)\n            return param.parameter\n\n    def get_next_blob_reference(self, name):\n        with scope.NameScope(self.name, reset=True):\n            return self.model.net.NextScopedBlob(name)\n\n    def add_operators(self, net, init_net=None,\n                      context=InstantiationContext.TRAINING):\n        '''\n        Adds layer trainig or initialization operators to the passed in net.\n        init_net can be None and can be called independently from add_init_params\n        '''\n        # Namescope below should warranty that all intermediate blobs will be\n        # assiciated with the layer that produces them\n        with scope.NameScope(self.name):\n            if context not in {InstantiationContext.PREDICTION,\n                               InstantiationContext.EVAL,\n                               InstantiationContext.ACCUMULATE_PRED}:\n                assert init_net, (\n                    \"Only prediction and eval context don't need init_net\")\n            if init_net:\n                self.add_init_params(init_net)\n            if context == InstantiationContext.TRAINING:\n                self.add_train_ops(net)\n            elif context == InstantiationContext.EVAL:\n                self.add_eval_ops(net)\n            elif context == InstantiationContext.ACCUMULATE_PRED:\n                self.add_ops_to_accumulate_pred(net)\n            else:\n                self.add_ops(net)\n\n            if context in {InstantiationContext.TRAINING,\n                           InstantiationContext.EVAL} \\\n               and self._export_params_for_metrics:\n                self.add_param_copy_operators(net)\n\n    def add_ops(self, net):\n        # Predict layer implementation.\n        raise NotImplementedError\n\n    def add_eval_ops(self, net):\n        # Default eval layer implementation is completely matching\n        # predict layer implementation.\n        self.add_ops(net)\n\n    def add_train_ops(self, net):\n        # Default train layer implementation is completely matching\n        # eval layer implementation.\n        self.add_eval_ops(net)\n\n    def add_ops_to_accumulate_pred(self, net):\n        # This adds operators to accumulate predictions/labels/weights. The\n        # accumulated data can later be used to compute calibration or for other\n        # purpose. Default layer implementation is completely matching eval\n        # layer implementation.\n        self.add_eval_ops(net)\n\n    def add_param_copy_operators(self, net):\n        for param in self.params:\n            param_copy_ref = self.model.metrics_schema[str(param.parameter)]\n            net.Copy([param.parameter], param_copy_ref.field_blobs())\n\n    def export_output_for_metrics(self):\n        self._export_output_for_metrics = True\n\n        # Export output of the layer directly\n        export_name = self.name + \"/output\"\n        self.model.add_metric_field(export_name, self.output_schema)\n\n    def export_params_for_metrics(self):\n        self._export_params_for_metrics = True\n\n        # Export copies of parameters\n        for param in self.params:\n            param_copy_ref = self.get_next_blob_reference(\n                str(param).split(\"/\")[-1] + \"_copy\")\n            self.model.add_metric_field(str(param.parameter), param_copy_ref)\n"
  },
  {
    "path": "caffe2/python/layers/margin_rank_loss.py",
    "content": "## @package random_neg_rank_loss\n# Module caffe2.python.layers.random_neg_rank_loss\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema, core\nfrom caffe2.python.layers.layers import (\n    ModelLayer,\n)\nfrom caffe2.python.layers.tags import (\n    Tags\n)\nimport numpy as np\n\n\nclass MarginRankLoss(ModelLayer):\n\n    def __init__(self, model, input_record, name='margin_rank_loss',\n                 margin=0.1, **kwargs):\n        super(MarginRankLoss, self).__init__(model, name, input_record, **kwargs)\n        assert margin >= 0, ('For hinge loss, margin should be no less than 0')\n        self._margin = margin\n        assert schema.is_schema_subset(\n            schema.Struct(\n                ('pos_prediction', schema.Scalar()),\n                ('neg_prediction', schema.List(np.float32)),\n            ),\n            input_record\n        )\n        self.tags.update([Tags.EXCLUDE_FROM_PREDICTION])\n        self.output_schema = schema.Scalar(\n            np.float32,\n            self.get_next_blob_reference('output'))\n\n    def add_ops(self, net):\n        neg_score = self.input_record.neg_prediction['values']()\n\n        pos_score = net.LengthsTile(\n            [\n                self.input_record.pos_prediction(),\n                self.input_record.neg_prediction['lengths']()\n            ],\n            net.NextScopedBlob('pos_score_repeated')\n        )\n        const_1 = net.ConstantFill(\n            neg_score,\n            net.NextScopedBlob('const_1'),\n            value=1,\n            dtype=core.DataType.INT32\n        )\n        rank_loss = net.MarginRankingCriterion(\n            [pos_score, neg_score, const_1],\n            net.NextScopedBlob('rank_loss'),\n            margin=self._margin,\n        )\n        net.SumElements(rank_loss, self.output_schema.field_blobs())\n"
  },
  {
    "path": "caffe2/python/layers/merge_id_lists.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import (\n    get_categorical_limit,\n    ModelLayer,\n    IdList\n)\n\nimport numpy as np\n\n\nclass MergeIdLists(ModelLayer):\n    \"\"\"Merge multiple ID_LISTs into a single ID_LIST\n\n    Arguments:\n        model: A layer model instance\n        input_record: Tuple (Struct) of ID_LIST features to be\n        merged\n\n    Returns:\n        the merged ID_LIST feature\n    \"\"\"\n    def __init__(self, model, input_record, name='merged'):\n        super(MergeIdLists, self).__init__(model, name, input_record)\n        assert all(schema.equal_schemas(x, IdList) for x in input_record), \\\n            \"Inputs to MergeIdLists should all be IdLists.\"\n\n        assert all(record.items.metadata is not None\n                   for record in self.input_record), \\\n            \"Features without metadata are not supported\"\n\n        merge_dim = max(get_categorical_limit(record)\n                        for record in self.input_record)\n        assert merge_dim is not None, \"Unbounded features are not supported\"\n\n        self.output_schema = schema.NewRecord(\n            model.net, schema.List(\n                schema.Scalar(\n                    np.int64,\n                    blob=model.net.NextBlob(name),\n                    metadata=schema.Metadata(categorical_limit=merge_dim)\n                )))\n\n    def add_ops(self, net):\n        return net.MergeIdLists(self.input_record.field_blobs(),\n                                self.output_schema.field_blobs())\n"
  },
  {
    "path": "caffe2/python/layers/pairwise_dot_product.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package dot_product\n# Module caffe2.python.layers.dot_product\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import (\n    ModelLayer,\n)\n\n\nclass PairwiseDotProduct(ModelLayer):\n\n    def __init__(self, model, input_record, output_dim,\n                 name='pairwise_dot_product', **kwargs):\n        super(PairwiseDotProduct, self).__init__(model, name, input_record, **kwargs)\n        assert isinstance(input_record, schema.Struct), (\n            \"Incorrect input type. Excpected Struct, but received: {0}\".\n            format(input_record))\n        assert (\n            ('all_embeddings' in input_record) ^\n            ('x_embeddings' in input_record and 'y_embeddings' in input_record)\n        ), (\n            \"either (all_embeddings) xor (x_embeddings and y_embeddings) \" +\n            \"should be given.\"\n        )\n        if 'all_embeddings' in input_record:\n            x_embeddings = input_record['all_embeddings']\n            y_embeddings = input_record['all_embeddings']\n        else:\n            x_embeddings = input_record['x_embeddings']\n            y_embeddings = input_record['y_embeddings']\n\n        assert isinstance(x_embeddings, schema.Scalar), (\n            \"Incorrect input type for x. Expected Scalar, \" +\n            \"but received: {0}\".format(x_embeddings))\n        assert isinstance(y_embeddings, schema.Scalar), (\n            \"Incorrect input type for y. Expected Scalar, \" +\n            \"but received: {0}\".format(y_embeddings)\n        )\n\n        if 'indices_to_gather' in input_record:\n            indices_to_gather = input_record['indices_to_gather']\n            assert isinstance(indices_to_gather, schema.Scalar), (\n                \"Incorrect type of indices_to_gather. \"\n                \"Expected Scalar, but received: {0}\".format(indices_to_gather)\n            )\n            self.indices_to_gather = indices_to_gather\n        else:\n            self.indices_to_gather = None\n\n        self.x_embeddings = x_embeddings\n        self.y_embeddings = y_embeddings\n\n        dtype = x_embeddings.field_types()[0].base\n\n        self.output_schema = schema.Scalar(\n            (dtype, (output_dim,)),\n            self.get_next_blob_reference('output')\n        )\n\n    def add_ops(self, net):\n        Y = net.BatchMatMul(\n            [self.x_embeddings(), self.y_embeddings()],\n            [self.x_embeddings() + '_matmul'],\n            trans_b=1,\n        )\n\n        if self.indices_to_gather:\n            flattened = net.Flatten(\n                Y, Y + '_flatten',\n            )\n            net.BatchGather(\n                [flattened, self.indices_to_gather()],\n                self.output_schema(),\n            )\n        else:\n            net.Flatten(Y, self.output_schema())\n"
  },
  {
    "path": "caffe2/python/layers/position_weighted.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package position_weighted\n# Module caffe2.python.layers.position_weighted\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport logging\nimport numpy as np\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import (\n    get_categorical_limit,\n    ModelLayer,\n)\n\nfrom caffe2.python.layers.tags import Tags\n\nlogger = logging.getLogger(__name__)\n\n\nclass PositionWeighted(ModelLayer):\n    def __init__(self, model, input_record, weight_optim=None,\n                 name=\"position_weights\"):\n        super(PositionWeighted, self).__init__(model, name, input_record)\n\n        assert isinstance(input_record, schema.List), \"Incorrect input type\"\n        length_metadata = input_record.lengths.metadata\n        max_length = (length_metadata.categorical_limit if length_metadata is\n                      not None else None)\n        if max_length is not None:\n            self.shape = max_length\n        else:\n            self.shape = get_categorical_limit(input_record)\n            logger.warning(\n                '{}: categorical_limit of lengths is not available, using '\n                'categorical_limit of the keys: {}'.format(\n                    str(input_record.lengths()), self.shape))\n\n        self.pos_w = self.create_param(param_name='pos_w',\n                                       shape=[self.shape, ],\n                                       initializer=('ConstantFill', {'value': 1.0}),\n                                       optimizer=weight_optim)\n\n        self.output_schema = schema.Struct(\n            ('position_weights',\n                schema.Scalar((np.float32, self.shape),\n                              self.get_next_blob_reference(\"pos_w_gather\")))\n        )\n\n        self.tags.update({Tags.HANDLE_AS_SPARSE_LAYER})\n        self.tags.update({Tags.GRADIENT_FROM_PS})\n\n    def get_memory_usage(self):\n        return self.shape\n\n    def add_ops(self, net):\n        inc_seq = net.LengthsRangeFill(\n            [self.input_record.lengths()],\n            self.input_record.lengths() + '_pos_w_seq'\n        )\n\n        net.Gather(\n            [self.pos_w, inc_seq],\n            self.output_schema.position_weights.field_blobs())\n"
  },
  {
    "path": "caffe2/python/layers/random_fourier_features.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import ModelLayer\n\nimport numpy as np\n\n\nclass RandomFourierFeatures(ModelLayer):\n    \"\"\"\n    Implementation of random fourier feature map for feature processing.\n\n    Applies sqrt(2 / output_dims) * cos(wx+b), where:\n        output_dims is the output feature dimensions, and\n        wx + b applies FC using randomized, fixed weight and bias parameters\n\n    For more information, see the original paper:\n        https://people.eecs.berkeley.edu/~brecht/papers/07.rah.rec.nips.pdf\n\n    Inputs:\n        output_dims -- output feature dimensions\n        sigma -- bandwidth for the Gaussian kernel estimator\n        w_init -- initalization options for weight parameter\n        b_init -- initalization options for bias parameter\n\n    \"\"\"\n    def __init__(\n            self,\n            model,\n            input_record,\n            output_dims,\n            sigma,  # bandwidth\n            w_init=None,\n            b_init=None,\n            name='random_fourier_features',\n            **kwargs):\n\n        super(RandomFourierFeatures, self).__init__(model, name, input_record,\n                                                    **kwargs)\n        assert isinstance(input_record, schema.Scalar), \"Incorrect input type\"\n\n        input_dims = input_record.field_type().shape[0]\n        assert input_dims >= 1, \"Expected input dimensions >= 1, got %s\" \\\n                                % input_dims\n        self.output_dims = output_dims\n        assert self.output_dims >= 1, \"Expected output dimensions >= 1, got %s\" \\\n                                      % self.output_dims\n\n        self.output_schema = schema.Scalar(\n            (np.float32, (self.output_dims, )),\n            self.get_next_blob_reference('output')\n        )\n\n        assert sigma > 0.0, \"Expected bandwidth > 0, got %s\" % sigma\n\n        # Initialize train_init_net parameters\n        w_init = w_init if w_init else (\n            'GaussianFill', {'mean': 0.0, 'std': 1.0 / sigma}\n        )\n\n        b_init = b_init if b_init else (\n            'UniformFill', {'min': 0.0, 'max': 2 * np.pi}\n        )\n\n        self.w = self.create_param(param_name='w',\n                                   shape=[self.output_dims, input_dims],\n                                   initializer=w_init,\n                                   optimizer=model.NoOptim)\n\n        self.b = self.create_param(param_name='b',\n                                   shape=[self.output_dims],\n                                   initializer=b_init,\n                                   optimizer=model.NoOptim)\n\n    def add_ops(self, net):\n        # Random features: wx + b\n        cosine_arg = net.FC(self.input_record.field_blobs() + [self.w, self.b],\n                            net.NextScopedBlob(\"cosine_arg\"))\n\n        # Apply cosine to new vectors\n        new_feature_vec = net.Cos([cosine_arg],\n                                  net.NextScopedBlob('new_feature_vec'))\n\n        # Multiply each element in vector by sqrt(2/D)\n        scale = np.sqrt(2.0 / self.output_dims)\n        net.Scale([new_feature_vec],\n                  self.output_schema.field_blobs(),\n                  scale=scale)\n"
  },
  {
    "path": "caffe2/python/layers/reservoir_sampling.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package reservoir_sampling\n# Module caffe2.python.layers.reservoir_sampling\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, schema\nfrom caffe2.python.layers.layers import ModelLayer\n\n\nclass ReservoirSampling(ModelLayer):\n    \"\"\"\n    Collect samples from input record w/ reservoir sampling. If you have complex\n    data, use PackRecords to pack it before using this layer.\n\n    This layer is not thread safe.\n    \"\"\"\n\n    def __init__(self, model, input_record, num_to_collect,\n                 name='reservoir_sampling', **kwargs):\n        super(ReservoirSampling, self).__init__(\n            model, name, input_record, **kwargs)\n        assert num_to_collect > 0\n        self.num_to_collect = num_to_collect\n\n        self.reservoir = self.create_param(\n            param_name='reservoir',\n            shape=[0],\n            initializer=('ConstantFill',),\n            optimizer=model.NoOptim,\n        )\n        self.num_visited_blob = self.create_param(\n            param_name='num_visited',\n            shape=[],\n            initializer=('ConstantFill', {\n                'value': 0,\n                'dtype': core.DataType.INT64,\n            }),\n            optimizer=model.NoOptim,\n        )\n        self.mutex = self.create_param(\n            param_name='mutex',\n            shape=None,\n            initializer=('CreateMutex',),\n            optimizer=model.NoOptim,\n        )\n\n        self.extra_input_blobs = []\n        self.extra_output_blobs = []\n        if 'object_id' in input_record:\n            object_to_pos = self.create_param(\n                param_name='object_to_pos',\n                initializer=('CreateMap', {\n                    'key_dtype': core.DataType.INT64,\n                    'valued_dtype': core.DataType.INT32,\n                }),\n                optimizer=model.NoOptim,\n            )\n            pos_to_object = self.create_param(\n                param_name='pos_to_object',\n                shape=[0],\n                initializer=('ConstantFill', {\n                    'value': 0,\n                    'dtype': core.DataType.INT64,\n                }),\n                optimizer=model.NoOptim,\n            )\n            self.extra_input_blobs.append(input_record.object_id())\n            self.extra_input_blobs.extend([object_to_pos, pos_to_object])\n            self.extra_output_blobs.extend([object_to_pos, pos_to_object])\n\n        self.output_schema = schema.Struct(\n            (\n                'reservoir',\n                schema.from_blob_list(input_record.data, [self.reservoir])\n            ),\n            ('num_visited', schema.Scalar(blob=self.num_visited_blob)),\n            ('mutex', schema.Scalar(blob=self.mutex)),\n        )\n\n    def add_ops(self, net):\n        net.ReservoirSampling(\n            [self.reservoir, self.num_visited_blob, self.input_record.data(),\n             self.mutex] + self.extra_input_blobs,\n            [self.reservoir, self.num_visited_blob] + self.extra_output_blobs,\n            num_to_collect=self.num_to_collect,\n        )\n"
  },
  {
    "path": "caffe2/python/layers/sampling_train.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package sampling_train\n# Module caffe2.python.layers.sampling_train\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import ModelLayer, get_layer_class\nfrom caffe2.python.layers.sampling_trainable_mixin import SamplingTrainableMixin\n\n\nclass SamplingTrain(ModelLayer):\n    def __init__(\n        self,\n        model,\n        input_record,\n        prediction_layer,\n        output_dims,\n        subtract_log_odd=True,\n        name='sampling_train',\n        **kwargs\n    ):\n        super(SamplingTrain, self).__init__(\n            model, name, input_record, **kwargs\n        )\n\n        layer_class = get_layer_class(prediction_layer)\n        assert issubclass(layer_class, SamplingTrainableMixin)\n\n        assert 'indices' in input_record\n        assert isinstance(input_record.indices, schema.Scalar),\\\n            \"input_record.indices is expected to be a schema.Scalar\"\n        assert 'input' in input_record\n\n        self.subtract_log_odd = subtract_log_odd\n        if self.subtract_log_odd:\n            assert 'sampling_prob' in input_record\n\n        self._prediction_layer = layer_class(\n            model,\n            input_record.input,\n            output_dims=output_dims,\n            **kwargs\n        )\n\n        self._prediction_layer.train_param_blobs = [\n            model.net.NextBlob(str(blob) + '_sampled')\n            for blob in self._prediction_layer.param_blobs\n        ]\n\n        self.params = self._prediction_layer.params\n\n        self.output_schema = self._prediction_layer.output_schema\n\n    def add_ops(self, net):\n        self._prediction_layer.add_ops(net)\n\n    def add_train_ops(self, net):\n        for full_blob, sampled_blob in zip(\n            self._prediction_layer.param_blobs,\n            self._prediction_layer.train_param_blobs\n        ):\n            net.Gather([full_blob, self.input_record.indices()], sampled_blob)\n        self._prediction_layer.add_train_ops(net)\n        if not self.subtract_log_odd:\n            return\n        log_q = net.Log(self.input_record.sampling_prob(),\n                        net.NextScopedBlob(\"log_q\"))\n        net.Sub([self.output_schema(), log_q], self.output_schema(),\n                broadcast=1, use_grad_hack=1)\n"
  },
  {
    "path": "caffe2/python/layers/sampling_trainable_mixin.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package sampling_trainable_mixin\n# Module caffe2.python.layers.sampling_trainable_mixin\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport abc\nimport six\n\n\nclass SamplingTrainableMixin(six.with_metaclass(abc.ABCMeta, object)):\n\n    def __init__(self, *args, **kwargs):\n        super(SamplingTrainableMixin, self).__init__(*args, **kwargs)\n        self._train_param_blobs = None\n        self._train_param_blobs_frozen = False\n\n    @property\n    @abc.abstractmethod\n    def param_blobs(self):\n        \"\"\"\n        List of parameter blobs for prediction net\n        \"\"\"\n        pass\n\n    @property\n    def train_param_blobs(self):\n        \"\"\"\n        If train_param_blobs is not set before used, default to param_blobs\n        \"\"\"\n        if self._train_param_blobs is None:\n            self.train_param_blobs = self.param_blobs\n        return self._train_param_blobs\n\n    @train_param_blobs.setter\n    def train_param_blobs(self, blobs):\n        assert not self._train_param_blobs_frozen\n        assert blobs is not None\n        self._train_param_blobs_frozen = True\n        self._train_param_blobs = blobs\n\n    @abc.abstractmethod\n    def _add_ops(self, net, param_blobs):\n        \"\"\"\n        Add ops to the given net, using the given param_blobs\n        \"\"\"\n        pass\n\n    def add_ops(self, net):\n        self._add_ops(net, self.param_blobs)\n\n    def add_train_ops(self, net):\n        self._add_ops(net, self.train_param_blobs)\n"
  },
  {
    "path": "caffe2/python/layers/select_record_by_context.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport logging\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import (\n    InstantiationContext,\n    ModelLayer,\n)\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass SelectRecordByContext(ModelLayer):\n    \"\"\"\n    Allowing model to follow different paths for each instatiation context and\n    join later at some point. The implementation use `Alias` because schema\n    sometimes clone fields internally so we need static blob name for output\n    \"\"\"\n\n    def __init__(\n        self,\n        model,\n        input_record,\n        name='select_record_by_context',\n        check_field_metas=True,\n        use_copy=False,\n        default_output_record_field=None,\n        **kwargs\n    ):\n        super(SelectRecordByContext, self).__init__(model, name, input_record,\n                                                    **kwargs)\n\n        assert isinstance(input_record, schema.Struct)\n        assert len(input_record) > 1\n\n        self.use_copy = use_copy\n        self.default_output_record = (\n            input_record[default_output_record_field]\n            if (default_output_record_field is not None) else None\n        )\n        ref_record = input_record[0]\n        for record in input_record:\n            assert schema.equal_schemas(record, ref_record,\n                                        check_field_metas=check_field_metas)\n\n        self.output_schema = schema.NewRecord(model.net, ref_record)\n\n    def _set_output_blobs(self, net, context):\n        record = self.input_record.get(context, self.default_output_record)\n        assert record is not None, (\n            \"{} context is not in input record without providing default\"\n            \" output\".format(context)\n        )\n        for in_blob, out_blob in zip(\n                record.field_blobs(), self.output_schema.field_blobs()\n        ):\n            if self.use_copy:\n                net.Copy(in_blob, out_blob)\n            else:\n                net.Alias(in_blob, out_blob)\n\n    def add_ops(self, net):\n        self._set_output_blobs(net, InstantiationContext.PREDICTION)\n\n    def add_eval_ops(self, net):\n        self._set_output_blobs(net, InstantiationContext.EVAL)\n\n    def add_train_ops(self, net):\n        self._set_output_blobs(net, InstantiationContext.TRAINING)\n\n    def add_ops_to_accumulate_pred(self, net):\n        self._set_output_blobs(net, InstantiationContext.ACCUMULATE_PRED)\n"
  },
  {
    "path": "caffe2/python/layers/semi_random_features.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.arc_cosine_feature_map import ArcCosineFeatureMap\nimport numpy as np\n\n\nclass SemiRandomFeatures(ArcCosineFeatureMap):\n    \"\"\"\n    Implementation of the semi-random kernel feature map.\n\n    Applies H(x_rand) * x_rand^s * x_learned, where\n        H is the Heaviside step function,\n        x_rand is the input after applying FC with randomized parameters,\n        and x_learned is the input after applying FC with learnable parameters.\n\n    If using multilayer model with semi-random layers, then input and output records\n    should have a 'full' and 'random' Scalar. The random Scalar will be passed as\n    input to process the random features.\n\n    For more information, see the original paper:\n        https://arxiv.org/pdf/1702.08882.pdf\n\n    Inputs :\n        output_dims -- dimensions of the output vector\n        s -- if s == 0, will obtain linear semi-random features;\n             else if s == 1, will obtain squared semi-random features;\n             else s >= 2, will obtain higher order semi-random features\n        scale_random -- amount to scale the standard deviation\n                        (for random parameter initialization when weight_init or\n                        bias_init hasn't been specified)\n        scale_learned -- amount to scale the standard deviation\n                        (for learned parameter initialization when weight_init or\n                        bias_init hasn't been specified)\n\n        weight_init_random -- initialization distribution for random weight parameter\n                              (if None, will use Gaussian distribution)\n        bias_init_random -- initialization distribution for random bias pararmeter\n                            (if None, will use Uniform distribution)\n        weight_init_learned -- initialization distribution for learned weight parameter\n                               (if None, will use Gaussian distribution)\n        bias_init_learned -- initialization distribution for learned bias pararmeter\n                             (if None, will use Uniform distribution)\n        weight_optim -- optimizer for weight params for learned features\n        bias_optim -- optimizer for bias param for learned features\n\n        set_weight_as_global_constant -- if True, initialized random parameters\n                                         will be constant across all distributed\n                                         instances of the layer\n    \"\"\"\n    def __init__(\n            self,\n            model,\n            input_record,\n            output_dims,\n            s=1,\n            scale_random=1.0,\n            scale_learned=1.0,\n            weight_init_random=None,\n            bias_init_random=None,\n            weight_init_learned=None,\n            bias_init_learned=None,\n            weight_optim=None,\n            bias_optim=None,\n            set_weight_as_global_constant=False,\n            name='semi_random_features',\n            **kwargs):\n\n        if isinstance(input_record, schema.Struct):\n            schema.is_schema_subset(\n                schema.Struct(\n                    ('full', schema.Scalar()),\n                    ('random', schema.Scalar()),\n                ),\n                input_record\n            )\n            self.input_record_full = input_record.full\n            self.input_record_random = input_record.random\n\n        elif isinstance(input_record, schema.Scalar):\n            self.input_record_full = input_record\n            self.input_record_random = input_record\n\n        super(SemiRandomFeatures, self).__init__(\n            model,\n            self.input_record_full,\n            output_dims,\n            s=s,\n            scale=scale_random,       # To initialize the random parameters\n            weight_init=weight_init_random,\n            bias_init=bias_init_random,\n            weight_optim=None,\n            bias_optim=None,\n            set_weight_as_global_constant=set_weight_as_global_constant,\n            initialize_output_schema=False,\n            name=name,\n            **kwargs)\n\n        self.output_schema = schema.Struct(\n            ('full', schema.Scalar(\n                (np.float32, output_dims),\n                model.net.NextScopedBlob(name + '_full_output')\n            ),),\n            ('random', schema.Scalar(\n                (np.float32, output_dims),\n                model.net.NextScopedBlob(name + '_random_output')\n            ),),\n        )\n\n        # To initialize the learnable parameters\n        assert (scale_learned > 0.0), \\\n            \"Expected scale (learned) > 0, got %s\" % scale_learned\n        self.stddev = scale_learned * np.sqrt(1.0 / self.input_dims)\n\n        # Learned Parameters\n        (self.learned_w, self.learned_b) = self._initialize_params(\n            'learned_w',\n            'learned_b',\n            w_init=weight_init_learned,\n            b_init=bias_init_learned,\n            w_optim=weight_optim,\n            b_optim=bias_optim\n        )\n\n    def add_ops(self, net):\n        # Learned features: wx + b\n        learned_features = net.FC(self.input_record_full.field_blobs() +\n                                  [self.learned_w, self.learned_b],\n                                  net.NextScopedBlob('learned_features'))\n        # Random features: wx + b\n        random_features = net.FC(self.input_record_random.field_blobs() +\n                                 [self.random_w, self.random_b],\n                                 net.NextScopedBlob('random_features'))\n        processed_random_features = self._heaviside_with_power(\n            net,\n            random_features,\n            self.output_schema.random.field_blobs(),\n            self.s\n        )\n        net.Mul([processed_random_features, learned_features],\n                self.output_schema.full.field_blobs())\n"
  },
  {
    "path": "caffe2/python/layers/sparse_feature_hash.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package sparse_feature_hash\n# Module caffe2.python.layers.sparse_feature_hash\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema, core\nfrom caffe2.python.layers.layers import (\n    ModelLayer,\n    IdList,\n    IdScoreList,\n)\nfrom caffe2.python.layers.tags import (\n    Tags\n)\n\nimport numpy as np\n\n\nclass SparseFeatureHash(ModelLayer):\n\n    def __init__(self, model, input_record, seed=0, modulo=None,\n                 use_hashing=True, name='sparse_feature_hash', **kwargs):\n        super(SparseFeatureHash, self).__init__(model, name, input_record, **kwargs)\n\n        self.seed = seed\n        self.use_hashing = use_hashing\n        if schema.equal_schemas(input_record, IdList):\n            self.modulo = modulo or self.extract_hash_size(input_record.items.metadata)\n            metadata = schema.Metadata(\n                categorical_limit=self.modulo,\n                feature_specs=input_record.items.metadata.feature_specs,\n                expected_value=input_record.items.metadata.expected_value\n            )\n            with core.NameScope(name):\n                self.output_schema = schema.NewRecord(model.net, IdList)\n            self.output_schema.items.set_metadata(metadata)\n\n        elif schema.equal_schemas(input_record, IdScoreList):\n            self.modulo = modulo or self.extract_hash_size(input_record.keys.metadata)\n            metadata = schema.Metadata(\n                categorical_limit=self.modulo,\n                feature_specs=input_record.keys.metadata.feature_specs,\n                expected_value=input_record.keys.metadata.expected_value\n            )\n            with core.NameScope(name):\n                self.output_schema = schema.NewRecord(model.net, IdScoreList)\n            self.output_schema.keys.set_metadata(metadata)\n\n        else:\n            assert False, \"Input type must be one of (IdList, IdScoreList)\"\n\n        assert self.modulo >= 1, 'Unexpected modulo: {}'.format(self.modulo)\n\n        # operators in this layer do not have CUDA implementation yet.\n        # In addition, since the sparse feature keys that we are hashing are\n        # typically on CPU originally, it makes sense to have this layer on CPU.\n        self.tags.update([Tags.CPU_ONLY])\n\n    def extract_hash_size(self, metadata):\n        if metadata.feature_specs and metadata.feature_specs.desired_hash_size:\n            return metadata.feature_specs.desired_hash_size\n        elif metadata.categorical_limit is not None:\n            return metadata.categorical_limit\n        else:\n            assert False, \"desired_hash_size or categorical_limit must be set\"\n\n    def add_ops(self, net):\n        net.Copy(\n            self.input_record.lengths(),\n            self.output_schema.lengths()\n        )\n        if schema.equal_schemas(self.output_schema, IdList):\n            input_blob = self.input_record.items()\n            output_blob = self.output_schema.items()\n        elif schema.equal_schemas(self.output_schema, IdScoreList):\n            input_blob = self.input_record.keys()\n            output_blob = self.output_schema.keys()\n            net.Copy(\n                self.input_record.values(),\n                self.output_schema.values()\n            )\n        else:\n            raise NotImplementedError()\n\n        if self.use_hashing:\n            net.IndexHash(\n                input_blob, output_blob, seed=self.seed, modulo=self.modulo\n            )\n        else:\n            net.Mod(\n                input_blob, output_blob, divisor=self.modulo, sign_follow_divisor=True\n            )\n"
  },
  {
    "path": "caffe2/python/layers/sparse_lookup.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package sparse_lookup\n# Module caffe2.python.layers.sparse_lookup\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python.helpers.arg_scope import get_current_scope\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import (\n    get_categorical_limit,\n    get_key,\n    IdList,\n    IdScoreList,\n    LayerPsParam,\n    ModelLayer,\n)\nimport collections\nimport functools\nimport math\nimport numpy as np\nimport operator\n\n\ndef get_sparse_lookup_predictor_version(version):\n    assert version in {'fp32', 'fp16', 'uint8rowwise', 'fused_uint8rowwise'},\\\n        \"Unexpected version of sparse_lookup layer {0}\".format(version)\n    return version\n\n\ndef _is_id_list(input_record):\n    return schema.equal_schemas(input_record, IdList)\n\n\ndef _is_id_score_list(input_record):\n    return schema.equal_schemas(input_record,\n                                IdScoreList,\n                                check_field_types=False)\n\n\nclass SparseLookup(ModelLayer):\n    _id_list_supported_reducers = [\n        'LogMeanExp', 'LogSumExp', 'Max', 'Mean', 'Sum',\n        'WeightedSum', 'WeightedMean', 'Sqrt', 'None']\n\n    _id_score_list_supported_reducers = [\n        'PositionWeighted', 'Mean', 'Sum', 'WeightedSum', 'WeightedMean', 'None']\n\n    def __init__(self, model, input_record, inner_shape, reducer,\n                 weight_init=None, weight_optim=None,\n                 name='sparse_lookup', regularizer=None, **kwargs):\n\n        super(SparseLookup, self).__init__(model, name, input_record, **kwargs)\n\n        # TODO Add some asserts about input type\n        if isinstance(inner_shape, int):\n            inner_shape = [inner_shape]\n        assert isinstance(inner_shape, list) or isinstance(inner_shape, tuple),\\\n            \"Unexpected type for inner_shape, expected list or tuple, got {0}\".\\\n            format(type(inner_shape))\n\n        if reducer == \"PositionWeighted\":\n            assert _is_id_score_list(self.input_record), (\n                \"PositionWeighted only support IdScoreList, but got {} \" +\n                \"please use PositionWeighted layer to convert IdList \" +\n                \"to IdScoreList\").format(repr(self.input_record))\n            self.external_weights = input_record.values()\n        self.reducer = reducer\n\n        input_dim = get_categorical_limit(input_record)\n        assert input_dim > 0, (\n            \"{} should have categorical limit > 0, but got {}\".format(\n                get_key(input_record)(), input_dim))\n\n        scale = math.sqrt(1.0 / input_dim)\n        self.shape = [input_dim] + inner_shape\n        self.weight_init = weight_init if weight_init else (\n            'UniformFill', {'min': -scale, 'max': scale})\n\n        if _is_id_list(self.input_record):\n            sparse_key = self.input_record.items()\n        elif _is_id_score_list(self.input_record):\n            sparse_key = self.input_record.keys()\n        else:\n            raise NotImplementedError()\n\n        if self.input_record.lengths.metadata:\n            avg_length = self.input_record.lengths.metadata.expected_value\n        else:\n            avg_length = None\n\n        self.w = self.create_param(\n            param_name='w',\n            shape=self.shape,\n            initializer=self.weight_init,\n            optimizer=weight_optim,\n            ps_param=LayerPsParam(\n                sparse_key=sparse_key,\n                average_length=avg_length),\n            regularizer=regularizer\n        )\n\n        self.scale_bias_init = ('ConstantFill', {'value': 0.0})\n\n        self.scale_bias = self.create_param(\n            param_name='scale_bias',\n            shape=[],\n            initializer=self.scale_bias_init,\n            optimizer=model.NoOptim,\n        )\n\n        self.output_schema = schema.Scalar(\n            (np.float32, inner_shape),\n            self.get_next_blob_reference('output'),\n        )\n\n    def get_memory_usage(self):\n        return functools.reduce(operator.mul, self.shape) * 4\n\n    def get_fp16_compatible_parameters(self):\n        return [self.w]\n\n    def support_8bit(self):\n        # Rowwise quantization makes sense only if shape it's 2D matrix with\n        # second dimension >= 8\n        if len(self.shape) != 2 or self.shape[1] < 8:\n            return False\n        return True\n\n    def get_8bits_compatible_parameters(self, fused=True):\n        if not self.support_8bit():\n            return []\n        if fused:\n            RowwiseQuantized8BitsWeight = collections.namedtuple(\n                'RowwiseQuantized8BitsWeight', 'w'\n            )\n            return [RowwiseQuantized8BitsWeight(self.w)]\n        else:\n            RowwiseQuantized8BitsWeight = collections.namedtuple(\n                'RowwiseQuantized8BitsWeight', 'w, scale_bias'\n            )\n            return [RowwiseQuantized8BitsWeight(self.w, self.scale_bias)]\n\n    def _gather_wrapper(self, net, version, in_indices, out):\n        # Gather can work on all kinds of input data types, and output\n        # data with the same type. Convert the output of Gather to float,\n        # because the follow-up Ops expect fp32.\n        if version == 'fp32':\n            return net.Gather([self.w, in_indices], out)\n        elif version == 'fp16':\n            gathered_w = net.Gather([self.w, in_indices], 'gathered_w')\n\n            return net.HalfToFloat(gathered_w, out)\n        elif version == 'uint8rowwise':\n            gathered_w = net.Gather([self.w, in_indices], 'gathered_w')\n            gathered_scale_bias = net.Gather(\n                [self.scale_bias, in_indices],\n                'gathered_scale_bias'\n            )\n\n            return net.Rowwise8BitQuantizedToFloat(\n                [gathered_w, gathered_scale_bias], out)\n        elif version == 'fused_uint8rowwise':\n            gathered_w = net.Gather([self.w, in_indices], 'gathered_w')\n            return net.Fused8BitRowwiseQuantizedToFloat(gathered_w, out)\n        else:\n            raise \"Unsupported version of operators in SparseLookup \" +\\\n                \"layer: {0}\".format(version)\n\n    def _sparse_lengths_weighted_reducer(\n            self, in_indices, weights, reducer,\n            net, version, grad_on_weights=0):\n        op_input = [\n            self.w,\n            weights,\n            in_indices,\n            self.input_record.lengths()\n        ]\n        layer_name = 'SparseLengths' + reducer\n\n        if version in ['fp32', 'fp16']:\n            # SparseLengths* Ops will accept either fp16 or fp32 embedding\n            # matrix and output fp32 pooled embedding\n            net.__getattr__(layer_name)(\n                op_input,\n                self.output_schema.field_blobs(),\n                grad_on_weights=grad_on_weights,\n            )\n        elif version == 'uint8rowwise':\n            op_input.insert(len(op_input), self.scale_bias)\n            net.__getattr__(layer_name + '8BitsRowwise')(\n                op_input, self.output_schema.field_blobs())\n        elif version == 'fused_uint8rowwise':\n            net.__getattr__(layer_name + 'Fused8BitRowwise')(\n                op_input, self.output_schema.field_blobs())\n        else:\n            raise \"Unsupported version of operator in SparseLookUp \" +\\\n                \"layer: {0}\".format(version)\n\n    # deal with sparse features of id_list type\n    def _add_ops_id_list(self, net, version):\n        assert self.reducer in self._id_list_supported_reducers, (\n            \"Unsupported reducer: {} for ID_LIST\".format(self.reducer)\n        )\n        if self.reducer in ['Sum', 'Mean', 'WeightedSum', 'WeightedMean']:\n            op_input = [self.w,\n                        self.input_record.items(),\n                        self.input_record.lengths()]\n\n            # For id list features, the behaviors of 'Sum' and\n            # 'WeightedSum' are identical, since we can regard the weight on each\n            # id as 1. Similarly, for 'Mean' and 'WeightedMean'.\n            if self.reducer == 'WeightedSum':\n                self.reducer = 'Sum'\n            elif self.reducer == 'WeightedMean':\n                self.reducer = 'Mean'\n\n            layer_name = 'SparseLengths' + self.reducer\n            if version in ['fp32', 'fp16']:\n                # SparseLengths* Ops will accept either fp16 or fp32 embedding\n                # matrix and output fp32 pooled embedding\n                net.__getattr__(layer_name)(\n                    op_input,\n                    self.output_schema.field_blobs(),\n                )\n            elif version == 'uint8rowwise':\n                op_input.insert(len(op_input), self.scale_bias)\n                net.__getattr__(layer_name + '8BitsRowwise')(\n                    op_input, self.output_schema.field_blobs())\n            elif version == 'fused_uint8rowwise':\n                net.__getattr__(layer_name + 'Fused8BitRowwise')(\n                    op_input, self.output_schema.field_blobs())\n            else:\n                raise \"Unsupported version of operator in SparseLookUp \" +\\\n                    \"layer: {0}\".format(version)\n\n        elif self.reducer == 'Sqrt':\n            sqrt_weight = net.LengthsToWeights(\n                [self.input_record.lengths()],\n                [net.NextScopedBlob('lengths_sqrt')],\n                power=0.5,\n            )\n            self._sparse_lengths_weighted_reducer(\n                self.input_record.items(),\n                sqrt_weight,\n                'WeightedSum', net, version)\n\n        elif self.reducer == 'None':\n            # Gather operator will gather the embedding for each id of\n            # each IdList.\n            self._gather_wrapper(net, version, self.input_record.items(),\n                                 self.output_schema.field_blobs())\n\n        else:\n            table_rows = self._gather_wrapper(\n                net, version, self.input_record.items(), 'table_rows')\n\n            segment_ids = net.LengthsToSegmentIds(\n                self.input_record.lengths(),\n                self.input_record.lengths() + '_sid')\n            net.__getattr__('SortedSegmentRange' + self.reducer)(\n                [table_rows, segment_ids],\n                self.output_schema.field_blobs(),\n            )\n\n    # deal with sparse features of id_score_list type\n    def _add_ops_id_score_list(self, net, version):\n        assert self.reducer in self._id_score_list_supported_reducers, (\n            \"Unsupported reducer: {} for ID_SCORE_LIST\".format(self.reducer)\n        )\n        if self.reducer in ['WeightedSum', 'WeightedMean']:\n            self._sparse_lengths_weighted_reducer(\n                self.input_record.keys(),\n                self.input_record.values(),\n                self.reducer, net, version)\n\n        elif self.reducer in ['Sum', 'Mean']:\n            op_input = [self.w,\n                        self.input_record.keys(),\n                        self.input_record.lengths()]\n\n            layer_name = 'SparseLengths' + self.reducer\n\n            if version in ['fp32', 'fp16']:\n                net.__getattr__(layer_name)(\n                    op_input,\n                    self.output_schema.field_blobs(),\n                )\n            elif version == 'uint8rowwise':\n                net.__getattr__(layer_name + '8BitsRowwise')(\n                    op_input, self.output_schema.field_blobs())\n            elif version == 'fused_uint8rowwise':\n                net.__getattr__(layer_name + 'Fused8BitRowwise')(\n                    op_input, self.output_schema.field_blobs())\n            else:\n                raise \"Unsupported version of operator in SparseLookUp \" +\\\n                    \"layer: {0}\".format(version)\n\n        elif self.reducer == 'PositionWeighted':\n            self._sparse_lengths_weighted_reducer(\n                self.input_record.keys(),\n                self.external_weights,\n                'WeightedSum', net, version, grad_on_weights=1)\n\n        elif self.reducer == 'None':\n            # Gather operator will gather the embedding for each id of\n            # each IdList.\n            self._gather_wrapper(net, version, self.input_record.keys(),\n                                 self.output_schema.field_blobs())\n        else:\n            raise \"Only Sum, Mean, None are supported for IdScoreList input.\" +\\\n                \"Trying to create with {}\".format(self.reducer)\n\n    def add_ops(self, net):\n        cur_scope = get_current_scope()\n        version = get_sparse_lookup_predictor_version(\n            **cur_scope.get(get_sparse_lookup_predictor_version.__name__,\n                            {'version': 'fp32'}))\n\n        # TODO(amalevich): Layer should not be responsible for decision about\n        # quantization.\n        if not self.support_8bit() and version in {'uint8rowwise',\n                                                   'fused_uint8rowwise'}:\n            version = 'fp32'\n\n        if _is_id_list(self.input_record):\n            self._add_ops_id_list(net, version=version)\n        elif _is_id_score_list(self.input_record):\n            self._add_ops_id_score_list(net, version=version)\n        else:\n            raise \"Unsupported input type {0}\".format(self.input_record)\n"
  },
  {
    "path": "caffe2/python/layers/split.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package split\n# Module caffe2.python.layers.split\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import schema\nfrom caffe2.python.layers.layers import (\n    ModelLayer,\n)\n\n\nclass Split(ModelLayer):\n\n    def __init__(self, model, input_record, num_splits, axis=1,\n                 name='split', **kwargs):\n        super(Split, self).__init__(model, name, input_record, **kwargs)\n        self.axis = axis\n        # Assume that first dimension is batch, so actual axis in shape is\n        # axis - 1\n        axis -= 1\n        assert axis >= 0\n\n        assert isinstance(input_record, schema.Scalar),\\\n            \"Incorrect input type. Excpected Scalar, but received: {0}\".\\\n            format(input_record)\n\n        input_shape = input_record.field_type().shape\n        assert len(input_shape) >= axis\n        assert input_shape[axis] % num_splits == 0\n\n        output_shape = list(input_shape)\n        output_shape[axis] = int(output_shape[axis] / num_splits)\n\n        data_type = input_record.field_type().base\n\n        output_scalars = [\n            schema.Scalar(\n                (data_type, output_shape),\n                self.get_next_blob_reference('output_{}'.format(i)),\n            )\n            for i in range(num_splits)\n        ]\n        self.output_schema = schema.Tuple(*output_scalars)\n\n    def add_ops(self, net):\n        net.Split(\n            self.input_record.field_blobs(),\n            self.output_schema.field_blobs(),\n            axis=self.axis,\n        )\n"
  },
  {
    "path": "caffe2/python/layers/tags.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package tags\n# Module caffe2.python.layers.tags\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport six\n\nfrom caffe2.python import context\n\n\n@context.define_context(allow_default=True)\nclass TagContext(object):\n    \"\"\"\n    Scope driven way to provide tags to the layers.\n    \"\"\"\n\n    def __init__(self, tags=None):\n        # Tags is expected to be list to keep order of adding/removing things\n        self.tags = tags or []\n\n    def add_tags(self, tags):\n        self.tags.extend(tags)\n\n    def remove_tags(self, tags):\n        assert self.tags[-len(tags):] == tags\n        self.tags = self.tags[:-len(tags)]\n\n\nclass Tags(object):\n    # TODO(amalevich): Tags might need to live in their own contexts, add this\n    # split later\n    EXCLUDE_FROM_TRAIN = 'exclude_from_train'\n    EXCLUDE_FROM_EVAL = 'exclude_from_eval'\n    EXCLUDE_FROM_PREDICTION = 'exclude_from_prediction'\n    EXCLUDE_FROM_ACCUMULATE_PRED = 'exclude_from_accumulate_pred'\n    PREPROCESSING = 'preprocessing'\n    HANDLE_AS_SPARSE_LAYER = 'handle_as_sparse_layer'\n    GRADIENT_FROM_PS = 'gradient_from_ps'\n    PREFER_GPU = 'prefer_gpu'\n    CPU_ONLY = 'cpu_only'\n\n    # The following three tags are hints to **distributed training framework**.\n    \"\"\"\n    Indicates a layer contains a sparse shardable parameter.  The parameter\n    should be sharded nd operators on those parameters should be done on\n    distributed parameter servers.\n    \"\"\"\n    SPARSE_SHARDED = 'sparse_sharded'\n    \"\"\"\n    Indicates a layer contains a sparse parameters among others, and that the\n    parameters should not be sharded (i.e. should be placed together on a node).\n    \"\"\"\n    SPARSE_DONT_SHARD = 'sparse_dont_shard'\n    \"\"\"\n    Used to manually indicate a component for an operator.  Parameters for\n    all operators with the same component should be colocated on the same\n    parameter server.\n    \"\"\"\n    COMPONENT = 'component:'\n    \"\"\"\n    Valid tag prefixes for distributed training framework.\n    \"\"\"\n    DT_TAGS = (SPARSE_SHARDED, SPARSE_DONT_SHARD, COMPONENT)\n\n    # In certain cases we want to have different schema for training and\n    # prediction, as an example in prediction we might need to have only\n    # subset of ids present in the orignal schema. This tag is one of the ways\n    # to mark operators that will be removed from prediction and should\n    # override schema for predictors.\n    PREDICTION_SCHEMA = 'prediction_schema'\n\n    def __init__(self, tags):\n        if not isinstance(tags, list):\n            tags = [tags]\n        self.tags = tags\n\n    def __enter__(self):\n        TagContext.current().add_tags(self.tags)\n        return self\n\n    def __exit__(self, type, value, traceback):\n        TagContext.current().remove_tags(self.tags)\n\n    def __call__(self, func):\n        @six.wraps(func)\n        def wrapper(*args, **kwargs):\n            with self:\n                return func(*args, **kwargs)\n        return wrapper\n\n\nTags.TRAIN_ONLY = [Tags.EXCLUDE_FROM_PREDICTION, Tags.EXCLUDE_FROM_EVAL,\n                   Tags.EXCLUDE_FROM_ACCUMULATE_PRED]\nTags.EVAL_ONLY = [Tags.EXCLUDE_FROM_PREDICTION, Tags.EXCLUDE_FROM_TRAIN,\n                  Tags.EXCLUDE_FROM_ACCUMULATE_PRED]\nTags.PREDICTION_ONLY = [Tags.EXCLUDE_FROM_TRAIN, Tags.EXCLUDE_FROM_EVAL,\n                        Tags.EXCLUDE_FROM_ACCUMULATE_PRED]\n"
  },
  {
    "path": "caffe2/python/layers/uniform_sampling.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package uniform_sampling\n# Module caffe2.python.layers.uniform_sampling\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nfrom caffe2.python import core, schema\nfrom caffe2.python.layers.layers import ModelLayer\n\n\nclass UniformSampling(ModelLayer):\n    \"\"\"\n    Uniform sampling `num_samples - len(input_record)` unique elements from the\n    range [0, num_elements). `samples` is the concatenation of input_record and\n    the samples. input_record is expected to be unique.\n    \"\"\"\n\n    def __init__(\n        self,\n        model,\n        input_record,\n        num_samples,\n        num_elements,\n        name='uniform_sampling',\n        **kwargs\n    ):\n        super(UniformSampling, self).__init__(\n            model, name, input_record, **kwargs\n        )\n\n        assert num_elements > num_samples > 0\n        assert isinstance(input_record, schema.Scalar)\n\n        self.num_elements = num_elements\n\n        num_examples_init = ('GivenTensorInt64Fill',\n                             {'values': [num_samples]})\n        self.num_samples = self.create_param(param_name='num_examples',\n                                              shape=(1,),\n                                              initializer=num_examples_init,\n                                              optimizer=model.NoOptim)\n\n        sampling_blob_init = ('ConstantFill',\n                              {'value': float(num_samples) / num_elements,\n                               'dtype': core.DataType.FLOAT})\n        self.sampling_prob = self.create_param(param_name='prob',\n                                               shape=(num_samples,),\n                                               initializer=sampling_blob_init,\n                                               optimizer=model.NoOptim)\n\n        self.output_schema = schema.Struct(\n            (\n                'samples', schema.Scalar(\n                    np.int32, self.get_next_blob_reference(\"samples\")\n                )\n            ),\n            ('sampling_prob', schema.Scalar(np.float32, self.sampling_prob)),\n        )\n\n    def add_ops(self, net):\n        net.StopGradient(self.sampling_prob, self.sampling_prob)\n\n        shape = net.Shape([self.input_record()], net.NextScopedBlob(\"shape\"))\n        shape = net.Sub([self.num_samples, shape], shape)\n        samples = net.UniqueUniformFill(\n            [shape, self.input_record()],\n            net.NextScopedBlob(\"samples_before_concat\"),\n            min=0,\n            max=self.num_elements - 1,\n            input_as_shape=True\n        )\n\n        net.Concat(\n            [self.input_record(), samples],\n            [self.output_schema.samples(), net.NextScopedBlob(\"split_info\")],\n            axis=0\n        )\n        net.StopGradient(\n            self.output_schema.samples(), self.output_schema.samples()\n        )\n"
  },
  {
    "path": "caffe2/python/layers_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport hypothesis.strategies as st\nimport numpy as np\nimport numpy.testing as npt\nimport unittest\nfrom hypothesis import given\n\nimport caffe2.python.hypothesis_test_util as hu\n\nfrom caffe2.python import (\n    layer_model_instantiator,\n    core,\n    schema,\n    workspace,\n)\nfrom caffe2.python.layers.layers import (\n    InstantiationContext,\n)\nfrom caffe2.python.layers.tags import Tags\nfrom caffe2.python.layer_test_util import (\n    LayersTestCase,\n    OpSpec,\n)\nfrom caffe2.python.layers.layers import (\n    IdList,\n    set_request_only,\n    is_request_only_scalar,\n    get_key,\n)\n\nimport logging\nlogger = logging.getLogger(__name__)\n\n\nclass TestLayers(LayersTestCase):\n    def testAddLoss(self):\n        input_record_LR = self.new_record(\n            schema.Struct(\n                ('label', schema.Scalar((np.float64, (1, )))),\n                ('logit', schema.Scalar((np.float32, (2, )))),\n                ('weight', schema.Scalar((np.float64, (1, ))))\n            )\n        )\n        loss_LR = self.model.BatchLRLoss(input_record_LR)\n\n        self.model.add_loss(loss_LR)\n        assert 'unnamed' in self.model.loss\n        self.assertEqual(\n            schema.Scalar((np.float32, tuple())), self.model.loss.unnamed\n        )\n        self.assertEqual(loss_LR, self.model.loss.unnamed)\n\n        self.model.add_loss(loss_LR, 'addLoss')\n        assert 'addLoss' in self.model.loss\n        self.assertEqual(\n            schema.Scalar((np.float32, tuple())), self.model.loss.addLoss\n        )\n        self.assertEqual(loss_LR, self.model.loss.addLoss)\n\n        self.model.add_loss(\n            schema.Scalar(\n                dtype=np.float32, blob=core.BlobReference('loss_blob_1')\n            ), 'addLoss'\n        )\n        assert 'addLoss_auto_0' in self.model.loss\n        self.assertEqual(\n            schema.Scalar((np.float32, tuple())), self.model.loss.addLoss_auto_0\n        )\n        assert core.BlobReference('loss_blob_1') in self.model.loss.field_blobs()\n\n        self.model.add_loss(\n            schema.Struct(\n                (\n                    'structName', schema.Scalar(\n                        dtype=np.float32,\n                        blob=core.BlobReference('loss_blob_2')\n                    )\n                )\n            ), 'addLoss'\n        )\n        assert 'addLoss_auto_1' in self.model.loss\n        self.assertEqual(\n            schema.Struct(('structName', schema.Scalar((np.float32, tuple())))),\n            self.model.loss.addLoss_auto_1\n        )\n        assert core.BlobReference('loss_blob_2') in self.model.loss.field_blobs()\n\n        loss_in_tuple_0 = schema.Scalar(\n            dtype=np.float32, blob=core.BlobReference('loss_blob_in_tuple_0')\n        )\n\n        loss_in_tuple_1 = schema.Scalar(\n            dtype=np.float32, blob=core.BlobReference('loss_blob_in_tuple_1')\n        )\n\n        loss_tuple = schema.NamedTuple(\n            'loss_in_tuple', * [loss_in_tuple_0, loss_in_tuple_1]\n        )\n        self.model.add_loss(loss_tuple, 'addLoss')\n        assert 'addLoss_auto_2' in self.model.loss\n        self.assertEqual(\n            schema.Struct(\n                ('loss_in_tuple_0', schema.Scalar((np.float32, tuple()))),\n                ('loss_in_tuple_1', schema.Scalar((np.float32, tuple())))\n            ), self.model.loss.addLoss_auto_2\n        )\n        assert core.BlobReference('loss_blob_in_tuple_0')\\\n         in self.model.loss.field_blobs()\n        assert core.BlobReference('loss_blob_in_tuple_1')\\\n         in self.model.loss.field_blobs()\n\n    def testAddOutputSchema(self):\n        # add the first field\n        self.model.add_output_schema('struct', schema.Struct())\n        expected_output_schema = schema.Struct(('struct', schema.Struct()))\n        self.assertEqual(\n            self.model.output_schema,\n            expected_output_schema,\n        )\n\n        # add the second field\n        self.model.add_output_schema('scalar', schema.Scalar(np.float64))\n        expected_output_schema = schema.Struct(\n            ('struct', schema.Struct()),\n            ('scalar', schema.Scalar(np.float64)),\n        )\n        self.assertEqual(\n            self.model.output_schema,\n            expected_output_schema,\n        )\n\n        # overwrite a field should raise\n        with self.assertRaises(AssertionError):\n            self.model.add_output_schema('scalar', schema.Struct())\n\n    def _test_net(self, net, ops_list):\n        \"\"\"\n        Helper function to assert the net contains some set of operations and\n        then to run the net.\n\n        Inputs:\n            net -- the network to test and run\n            ops_list -- the list of operation specifications to check for\n                        in the net\n        \"\"\"\n        ops_output = self.assertNetContainOps(net, ops_list)\n        workspace.RunNetOnce(net)\n        return ops_output\n\n    def testFCWithoutBias(self):\n        output_dims = 2\n        fc_without_bias = self.model.FCWithoutBias(\n            self.model.input_feature_schema.float_features, output_dims)\n        self.model.output_schema = fc_without_bias\n\n        self.assertEqual(\n            schema.Scalar((np.float32, (output_dims, ))),\n            fc_without_bias\n        )\n\n        train_init_net, train_net = self.get_training_nets()\n\n        init_ops = self.assertNetContainOps(\n            train_init_net,\n            [\n                OpSpec(\"UniformFill\", None, None),\n            ]\n        )\n\n        mat_mul_spec = OpSpec(\n            \"MatMul\",\n            [\n                self.model.input_feature_schema.float_features(),\n                init_ops[0].output[0],\n            ],\n            fc_without_bias.field_blobs()\n        )\n\n        self.assertNetContainOps(train_net, [mat_mul_spec])\n\n        predict_net = self.get_predict_net()\n        self.assertNetContainOps(predict_net, [mat_mul_spec])\n\n    def testSparseLookupSumPooling(self):\n        record = schema.NewRecord(self.model.net, schema.Struct(\n            ('sparse', schema.Struct(\n                ('sparse_feature_0', schema.List(\n                    schema.Scalar(np.int64,\n                                  metadata=schema.Metadata(categorical_limit=1000)))),\n            )),\n        ))\n        embedding_dim = 64\n        embedding_after_pooling = self.model.SparseLookup(\n            record.sparse.sparse_feature_0, [embedding_dim], 'Sum')\n        self.model.output_schema = schema.Struct()\n        self.assertEqual(\n            schema.Scalar((np.float32, (embedding_dim, ))),\n            embedding_after_pooling\n        )\n\n        train_init_net, train_net = self.get_training_nets()\n\n        init_ops = self.assertNetContainOps(\n            train_init_net,\n            [\n                OpSpec(\"UniformFill\", None, None),\n                OpSpec(\"ConstantFill\", None, None),\n            ]\n        )\n        sparse_lookup_op_spec = OpSpec(\n            'SparseLengthsSum',\n            [\n                init_ops[0].output[0],\n                record.sparse.sparse_feature_0.items(),\n                record.sparse.sparse_feature_0.lengths(),\n            ],\n            [embedding_after_pooling()]\n        )\n        self.assertNetContainOps(train_net, [sparse_lookup_op_spec])\n\n        predict_net = self.get_predict_net()\n        self.assertNetContainOps(predict_net, [sparse_lookup_op_spec])\n\n    @given(\n        use_hashing=st.booleans(),\n        modulo=st.integers(min_value=100, max_value=200),\n    )\n    def testSparseFeatureHashIdList(self, use_hashing, modulo):\n        record = schema.NewRecord(\n            self.model.net,\n            schema.List(schema.Scalar(\n                np.int64,\n                metadata=schema.Metadata(categorical_limit=60000)\n            ))\n        )\n        output_schema = self.model.SparseFeatureHash(\n            record,\n            modulo=modulo,\n            use_hashing=use_hashing)\n\n        self.model.output_schema = output_schema\n\n        self.assertEqual(len(self.model.layers), 1)\n        self.assertEqual(output_schema._items.metadata.categorical_limit,\n                modulo)\n        train_init_net, train_net = self.get_training_nets()\n\n    @given(\n        use_hashing=st.booleans(),\n        modulo=st.integers(min_value=100, max_value=200),\n    )\n    def testSparseFeatureHashIdScoreList(self, use_hashing, modulo):\n        record = schema.NewRecord(self.model.net,\n                schema.Map(schema.Scalar(np.int64,\n                    metadata=schema.Metadata(\n                        categorical_limit=60000)),\n                    np.float32))\n\n        output_schema = self.model.SparseFeatureHash(\n            record,\n            modulo=modulo,\n            use_hashing=use_hashing)\n\n        self.model.output_schema = output_schema\n\n        self.assertEqual(len(self.model.layers), 1)\n        self.assertEqual(output_schema._items.keys.metadata.categorical_limit,\n                modulo)\n        train_init_net, train_net = self.get_training_nets()\n\n    def testSparseLookupIncorrectPositionWeightedOnIdList(self):\n        '''\n        Currently the implementation of SparseLookup assumed input is id_score_list\n        when use PositionWeighted.\n        '''\n        record = schema.NewRecord(self.model.net, schema.Struct(\n            ('sparse', schema.Struct(\n                ('sparse_feature_0', schema.List(\n                    schema.Scalar(np.int64,\n                                  metadata=schema.Metadata(categorical_limit=1000)))),\n            )),\n        ))\n\n        embedding_dim = 64\n        with self.assertRaises(AssertionError):\n            self.model.SparseLookup(\n                record.sparse.sparse_feature_0, [embedding_dim], 'PositionWeighted')\n\n    def testSparseLookupPositionWeightedOnIdList(self):\n        record = schema.NewRecord(self.model.net, schema.Struct(\n            ('sparse', schema.Struct(\n                ('sparse_feature_0', schema.List(\n                    schema.Scalar(np.int64,\n                                  metadata=schema.Metadata(categorical_limit=1000)))),\n            )),\n        ))\n\n        # convert id_list to id_score_list with PositionWeighted layer\n        sparse_segment = record.sparse.sparse_feature_0\n        pos_w_layer = self.model.PositionWeighted(sparse_segment)\n\n        sparse_segment = schema.Map(\n            keys=get_key(sparse_segment),\n            values=pos_w_layer.position_weights,\n            lengths_blob=sparse_segment.lengths\n        )\n\n        embedding_dim = 64\n        embedding_after_pooling = self.model.SparseLookup(\n            sparse_segment, [embedding_dim], 'PositionWeighted')\n        self.model.output_schema = schema.Struct()\n        self.assertEqual(\n            schema.Scalar((np.float32, (embedding_dim, ))),\n            embedding_after_pooling\n        )\n\n        train_init_net, train_net = self.get_training_nets()\n\n        self.assertNetContainOps(\n            train_init_net,\n            [\n                OpSpec(\"ConstantFill\", None, None),  # position_weights/pos_w\n                OpSpec(\"UniformFill\", None, None),\n                OpSpec(\"ConstantFill\", None, None),\n            ]\n        )\n        self.assertNetContainOps(train_net, [\n            OpSpec(\"LengthsRangeFill\", None, None),\n            OpSpec(\"Gather\", None, None),\n            OpSpec(\"SparseLengthsWeightedSum\", None, None),\n        ])\n\n        predict_net = self.get_predict_net()\n        self.assertNetContainOps(predict_net, [\n            OpSpec(\"LengthsRangeFill\", None, None),\n            OpSpec(\"Gather\", None, None),\n            OpSpec(\"SparseLengthsWeightedSum\", None, None),\n        ])\n\n    def testSparseLookupPositionWeightedOnIdScoreList(self):\n        record = schema.NewRecord(self.model.net, schema.Struct(\n            ('sparse', schema.Struct(\n                ('id_score_list_0', schema.Map(\n                    schema.Scalar(\n                        np.int64,\n                        metadata=schema.Metadata(\n                            categorical_limit=1000\n                        ),\n                    ),\n                    np.float32\n                )),\n            )),\n        ))\n\n        embedding_dim = 64\n        embedding_after_pooling = self.model.SparseLookup(\n            record.sparse.id_score_list_0, [embedding_dim], 'PositionWeighted')\n        self.model.output_schema = schema.Struct()\n        self.assertEqual(\n            schema.Scalar((np.float32, (embedding_dim, ))),\n            embedding_after_pooling\n        )\n\n        train_init_net, train_net = self.get_training_nets()\n\n        init_ops = self.assertNetContainOps(\n            train_init_net,\n            [\n                OpSpec(\"UniformFill\", None, None),\n                OpSpec(\"ConstantFill\", None, None),\n            ]\n        )\n        sparse_lookup_op_spec = OpSpec(\n            'SparseLengthsWeightedSum',\n            [\n                init_ops[0].output[0],\n                record.sparse.id_score_list_0.values(),\n                record.sparse.id_score_list_0.keys(),\n                record.sparse.id_score_list_0.lengths(),\n            ],\n            [embedding_after_pooling()]\n        )\n        self.assertNetContainOps(train_net, [sparse_lookup_op_spec])\n\n        predict_net = self.get_predict_net()\n        self.assertNetContainOps(predict_net, [sparse_lookup_op_spec])\n\n    def testPairwiseDotProductWithAllEmbeddings(self):\n        embedding_dim = 64\n        N = 5\n        record = schema.NewRecord(self.model.net, schema.Struct(\n            ('all_embeddings', schema.Scalar(\n                ((np.float32, (N, embedding_dim)))\n            )),\n        ))\n        current = self.model.PairwiseDotProduct(\n            record, N * N)\n\n        self.assertEqual(\n            schema.Scalar((np.float32, (N * N, ))),\n            current\n        )\n\n        train_init_net, train_net = self.get_training_nets()\n        self.assertNetContainOps(train_init_net, [])\n        self.assertNetContainOps(train_net, [\n            OpSpec(\"BatchMatMul\", None, None),\n            OpSpec(\"Flatten\", None, None),\n        ])\n\n    def testPairwiseDotProductWithXandYEmbeddings(self):\n        embedding_dim = 64\n        record = schema.NewRecord(self.model.net, schema.Struct(\n            ('x_embeddings', schema.Scalar(\n                ((np.float32, (5, embedding_dim)))\n            )),\n            ('y_embeddings', schema.Scalar(\n                ((np.float32, (6, embedding_dim)))\n            )),\n        ))\n        current = self.model.PairwiseDotProduct(\n            record, 5 * 6)\n\n        self.assertEqual(\n            schema.Scalar((np.float32, (5 * 6, ))),\n            current\n        )\n\n        train_init_net, train_net = self.get_training_nets()\n        self.assertNetContainOps(train_init_net, [])\n        self.assertNetContainOps(train_net, [\n            OpSpec(\"BatchMatMul\", None, None),\n            OpSpec(\"Flatten\", None, None),\n        ])\n\n    def testPairwiseDotProductWithXandYEmbeddingsAndGather(self):\n        embedding_dim = 64\n\n        output_idx = [1, 3, 5]\n        output_idx_blob = self.model.add_global_constant(\n            str(self.model.net.NextScopedBlob('pairwise_dot_product_gather')),\n            output_idx,\n            dtype=np.int32,\n        )\n        indices_to_gather = schema.Scalar(\n            (np.int32, len(output_idx)),\n            output_idx_blob,\n        )\n\n        record = schema.NewRecord(self.model.net, schema.Struct(\n            ('x_embeddings', schema.Scalar(\n                ((np.float32, (5, embedding_dim)))\n            )),\n            ('y_embeddings', schema.Scalar(\n                ((np.float32, (6, embedding_dim)))\n            )),\n            ('indices_to_gather', indices_to_gather),\n        ))\n        current = self.model.PairwiseDotProduct(\n            record, len(output_idx))\n\n        # This assert is not necessary,\n        # output size is passed into PairwiseDotProduct\n        self.assertEqual(\n            schema.Scalar((np.float32, (len(output_idx), ))),\n            current\n        )\n\n        train_init_net, train_net = self.get_training_nets()\n        self.assertNetContainOps(train_init_net, [])\n        self.assertNetContainOps(train_net, [\n            OpSpec(\"BatchMatMul\", None, None),\n            OpSpec(\"Flatten\", None, None),\n            OpSpec(\"BatchGather\", None, None),\n        ])\n\n    def testPairwiseDotProductIncorrectInput(self):\n        embedding_dim = 64\n        record = schema.NewRecord(self.model.net, schema.Struct(\n            ('x_embeddings', schema.Scalar(\n                ((np.float32, (5, embedding_dim)))\n            )),\n        ))\n        with self.assertRaises(AssertionError):\n            self.model.PairwiseDotProduct(\n                record, 25)\n\n        record = schema.NewRecord(self.model.net, schema.Struct(\n            ('all_embeddings', schema.List(np.float32))\n        ))\n        with self.assertRaises(AssertionError):\n            self.model.PairwiseDotProduct(\n                record, 25)\n\n    def testConcat(self):\n        embedding_dim = 64\n        input_record = self.new_record(schema.Struct(\n            ('input1', schema.Scalar((np.float32, (embedding_dim, )))),\n            ('input2', schema.Scalar((np.float32, (embedding_dim, )))),\n            ('input3', schema.Scalar((np.float32, (embedding_dim, )))),\n        ))\n\n        output = self.model.Concat(input_record)\n        self.assertEqual(\n            schema.Scalar((np.float32, ((len(input_record.fields) * embedding_dim, )))),\n            output\n        )\n\n        # Note that in Concat layer we assume first dimension is batch.\n        # so input is B * embedding_dim\n        # add_axis=1 make it B * 1 * embedding_dim\n        # concat on axis=1 make it B * N * embedding_dim\n        output = self.model.Concat(input_record, axis=1, add_axis=1)\n        self.assertEqual(\n            schema.Scalar((np.float32, ((len(input_record.fields), embedding_dim)))),\n            output\n        )\n\n    def testSamplingTrain(self):\n        output_dims = 1000\n\n        indices = self.new_record(schema.Scalar((np.int32, (10,))))\n        sampling_prob = self.new_record(schema.Scalar((np.float32, (10, ))))\n\n        sampled_fc = self.model.SamplingTrain(\n            schema.Struct(\n                ('input', self.model.input_feature_schema.float_features),\n                ('indices', indices),\n                ('sampling_prob', sampling_prob),\n            ),\n            \"FC\",\n            output_dims,\n        )\n        self.model.output_schema = sampled_fc\n\n        # Check that we don't add prediction layer into the model\n        self.assertEqual(1, len(self.model.layers))\n\n        self.assertEqual(\n            schema.Scalar((np.float32, (output_dims, ))),\n            sampled_fc\n        )\n\n        train_init_net, train_net = self.get_training_nets()\n\n        init_ops = self.assertNetContainOps(\n            train_init_net,\n            [\n                OpSpec(\"UniformFill\", None, None),\n                OpSpec(\"UniformFill\", None, None),\n            ]\n        )\n\n        sampled_fc_layer = self.model.layers[0]\n\n        gather_w_spec = OpSpec(\n            \"Gather\",\n            [\n                init_ops[0].output[0],\n                indices(),\n            ],\n            [\n                sampled_fc_layer._prediction_layer.train_param_blobs[0]\n            ]\n        )\n        gather_b_spec = OpSpec(\n            \"Gather\",\n            [\n                init_ops[1].output[0],\n                indices(),\n            ],\n            [\n                sampled_fc_layer._prediction_layer.train_param_blobs[1]\n            ]\n        )\n        train_fc_spec = OpSpec(\n            \"FC\",\n            [\n                self.model.input_feature_schema.float_features(),\n            ] + sampled_fc_layer._prediction_layer.train_param_blobs,\n            sampled_fc.field_blobs()\n        )\n        log_spec = OpSpec(\"Log\", [sampling_prob()], [None])\n        sub_spec = OpSpec(\n            \"Sub\",\n            [sampled_fc.field_blobs()[0], None],\n            sampled_fc.field_blobs()\n        )\n\n        train_ops = self.assertNetContainOps(\n            train_net,\n            [gather_w_spec, gather_b_spec, train_fc_spec, log_spec, sub_spec])\n\n        self.assertEqual(train_ops[3].output[0], train_ops[4].input[1])\n\n        predict_net = self.get_predict_net()\n        self.assertNetContainOps(\n            predict_net,\n            [\n                OpSpec(\n                    \"FC\",\n                    [\n                        self.model.input_feature_schema.float_features(),\n                        init_ops[0].output[0],\n                        init_ops[1].output[0],\n                    ],\n                    sampled_fc.field_blobs()\n                )\n            ]\n        )\n\n    def testBatchLRLoss(self):\n        input_record = self.new_record(schema.Struct(\n            ('label', schema.Scalar((np.float64, (1,)))),\n            ('logit', schema.Scalar((np.float32, (2,)))),\n            ('weight', schema.Scalar((np.float64, (1,))))\n        ))\n        loss = self.model.BatchLRLoss(input_record)\n        self.assertEqual(schema.Scalar((np.float32, tuple())), loss)\n\n    def testMarginRankLoss(self):\n        input_record = self.new_record(schema.Struct(\n            ('pos_prediction', schema.Scalar((np.float32, (1,)))),\n            ('neg_prediction', schema.List(np.float32)),\n        ))\n        pos_items = np.array([0.1, 0.2, 0.3], dtype=np.float32)\n        neg_lengths = np.array([1, 2, 3], dtype=np.int32)\n        neg_items = np.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6], dtype=np.float32)\n        schema.FeedRecord(\n            input_record,\n            [pos_items, neg_lengths, neg_items]\n        )\n        loss = self.model.MarginRankLoss(input_record)\n        self.run_train_net_forward_only()\n        self.assertEqual(schema.Scalar((np.float32, tuple())), loss)\n\n    def testBatchMSELoss(self):\n        input_record = self.new_record(schema.Struct(\n            ('label', schema.Scalar((np.float64, (1,)))),\n            ('prediction', schema.Scalar((np.float32, (2,)))),\n        ))\n        loss = self.model.BatchMSELoss(input_record)\n        self.assertEqual(schema.Scalar((np.float32, tuple())), loss)\n\n    def testBatchSigmoidCrossEntropyLoss(self):\n        input_record = self.new_record(schema.Struct(\n            ('label', schema.Scalar((np.float32, (32,)))),\n            ('prediction', schema.Scalar((np.float32, (32,))))\n        ))\n        loss = self.model.BatchSigmoidCrossEntropyLoss(input_record)\n        self.assertEqual(schema.Scalar((np.float32, tuple())), loss)\n\n    def testBatchSoftmaxLoss(self):\n        input_record = self.new_record(schema.Struct(\n            ('label', schema.Scalar((np.float32, tuple()))),\n            ('prediction', schema.Scalar((np.float32, (32,))))\n        ))\n        loss = self.model.BatchSoftmaxLoss(input_record)\n        self.assertEqual(schema.Struct(\n            ('softmax', schema.Scalar((np.float32, (32,)))),\n            ('loss', schema.Scalar(np.float32)),\n        ), loss)\n\n    def testBatchSoftmaxLossWeight(self):\n        input_record = self.new_record(schema.Struct(\n            ('label', schema.Scalar((np.float32, tuple()))),\n            ('prediction', schema.Scalar((np.float32, (32,)))),\n            ('weight', schema.Scalar((np.float64, (1,))))\n        ))\n        loss = self.model.BatchSoftmaxLoss(input_record)\n        self.assertEqual(schema.Struct(\n            ('softmax', schema.Scalar((np.float32, (32,)))),\n            ('loss', schema.Scalar(np.float32)),\n        ), loss)\n\n    @given(\n        X=hu.arrays(dims=[2, 5]),\n    )\n    def testBatchNormalization(self, X):\n        input_record = self.new_record(schema.Scalar((np.float32, (5,))))\n        schema.FeedRecord(input_record, [X])\n        bn_output = self.model.BatchNormalization(input_record)\n        self.assertEqual(schema.Scalar((np.float32, (5,))), bn_output)\n        self.model.output_schema = schema.Struct()\n\n        train_init_net, train_net = self.get_training_nets()\n\n        init_ops = self.assertNetContainOps(\n            train_init_net,\n            [\n                OpSpec(\"ConstantFill\", None, None),\n                OpSpec(\"ConstantFill\", None, None),\n                OpSpec(\"ConstantFill\", None, None),\n                OpSpec(\"ConstantFill\", None, None),\n            ]\n        )\n\n        input_blob = input_record.field_blobs()[0]\n        output_blob = bn_output.field_blobs()[0]\n\n        expand_dims_spec = OpSpec(\n            \"ExpandDims\",\n            [input_blob],\n            None,\n        )\n\n        train_bn_spec = OpSpec(\n            \"SpatialBN\",\n            [None, init_ops[0].output[0], init_ops[1].output[0],\n                init_ops[2].output[0], init_ops[3].output[0]],\n            [output_blob, init_ops[2].output[0], init_ops[3].output[0], None, None],\n            {'is_test': 0, 'order': 'NCHW', 'momentum': 0.9},\n        )\n\n        test_bn_spec = OpSpec(\n            \"SpatialBN\",\n            [None, init_ops[0].output[0], init_ops[1].output[0],\n                init_ops[2].output[0], init_ops[3].output[0]],\n            [output_blob],\n            {'is_test': 1, 'order': 'NCHW', 'momentum': 0.9},\n        )\n\n        squeeze_spec = OpSpec(\n            \"Squeeze\",\n            [output_blob],\n            [output_blob],\n        )\n\n        self.assertNetContainOps(\n            train_net,\n            [expand_dims_spec, train_bn_spec, squeeze_spec]\n        )\n\n        eval_net = self.get_eval_net()\n\n        self.assertNetContainOps(\n            eval_net,\n            [expand_dims_spec, test_bn_spec, squeeze_spec]\n        )\n\n        predict_net = self.get_predict_net()\n\n        self.assertNetContainOps(\n            predict_net,\n            [expand_dims_spec, test_bn_spec, squeeze_spec]\n        )\n\n        workspace.RunNetOnce(train_init_net)\n        workspace.RunNetOnce(train_net)\n\n        schema.FeedRecord(input_record, [X])\n        workspace.RunNetOnce(eval_net)\n\n        schema.FeedRecord(input_record, [X])\n        workspace.RunNetOnce(predict_net)\n\n    @given(\n        X=hu.arrays(dims=[5, 2]),\n        num_to_collect=st.integers(min_value=1, max_value=10),\n    )\n    def testLastNWindowCollector(self, X, num_to_collect):\n        input_record = self.new_record(schema.Scalar(np.float32))\n        schema.FeedRecord(input_record, [X])\n        last_n = self.model.LastNWindowCollector(input_record, num_to_collect)\n        self.run_train_net_forward_only()\n        output_record = schema.FetchRecord(last_n.last_n)\n        start = max(0, 5 - num_to_collect)\n        npt.assert_array_equal(X[start:], output_record())\n        num_visited = schema.FetchRecord(last_n.num_visited)\n        npt.assert_array_equal([5], num_visited())\n\n    def testUniformSampling(self):\n        input_record = self.new_record(schema.Scalar(np.int32))\n        input_array = np.array([3, 10, 11, 15, 20, 99], dtype=np.int32)\n        schema.FeedRecord(input_record, [input_array])\n        num_samples = 20\n        num_elements = 100\n        uniform_sampling_output = self.model.UniformSampling(\n            input_record, num_samples, num_elements)\n        self.model.loss = uniform_sampling_output\n        self.run_train_net()\n        samples = workspace.FetchBlob(uniform_sampling_output.samples())\n        sampling_prob = workspace.FetchBlob(\n            uniform_sampling_output.sampling_prob())\n        self.assertEqual(num_samples, len(samples))\n        np.testing.assert_array_equal(input_array, samples[:len(input_array)])\n        np.testing.assert_almost_equal(\n            np.array([float(num_samples) / num_elements] * num_samples,\n                     dtype=np.float32),\n            sampling_prob\n        )\n\n    def testUniformSamplingWithIncorrectSampleSize(self):\n        input_record = self.new_record(schema.Scalar(np.int32))\n        num_samples = 200\n        num_elements = 100\n        with self.assertRaises(AssertionError):\n            self.model.UniformSampling(input_record, num_samples, num_elements)\n\n    def testGatherRecord(self):\n        indices = np.array([1, 3, 4], dtype=np.int32)\n        dense = np.array(list(range(20)), dtype=np.float32).reshape(10, 2)\n        lengths = np.array(list(range(10)), dtype=np.int32)\n        items = np.array(list(range(lengths.sum())), dtype=np.int64)\n        items_lengths = np.array(list(range(lengths.sum())), dtype=np.int32)\n        items_items = np.array(list(range(items_lengths.sum())), dtype=np.int64)\n        record = self.new_record(schema.Struct(\n            ('dense', schema.Scalar(np.float32)),\n            ('sparse', schema.Struct(\n                ('list', schema.List(np.int64)),\n                ('list_of_list', schema.List(schema.List(np.int64))),\n            )),\n            ('empty_struct', schema.Struct())\n        ))\n        indices_record = self.new_record(schema.Scalar(np.int32))\n        input_record = schema.Struct(\n            ('indices', indices_record),\n            ('record', record),\n        )\n        schema.FeedRecord(\n            input_record,\n            [indices, dense, lengths, items, lengths, items_lengths,\n             items_items])\n        gathered_record = self.model.GatherRecord(input_record)\n        self.assertTrue(schema.equal_schemas(gathered_record, record))\n\n        self.run_train_net_forward_only()\n        gathered_dense = workspace.FetchBlob(gathered_record.dense())\n        np.testing.assert_array_equal(\n            np.concatenate([dense[i:i + 1] for i in indices]), gathered_dense)\n        gathered_lengths = workspace.FetchBlob(\n            gathered_record.sparse.list.lengths())\n        np.testing.assert_array_equal(\n            np.concatenate([lengths[i:i + 1] for i in indices]),\n            gathered_lengths)\n        gathered_items = workspace.FetchBlob(\n            gathered_record.sparse.list.items())\n        offsets = lengths.cumsum() - lengths\n        np.testing.assert_array_equal(\n            np.concatenate([\n                items[offsets[i]: offsets[i] + lengths[i]]\n                for i in indices\n            ]), gathered_items)\n\n        gathered_items_lengths = workspace.FetchBlob(\n            gathered_record.sparse.list_of_list.items.lengths())\n        np.testing.assert_array_equal(\n            np.concatenate([\n                items_lengths[offsets[i]: offsets[i] + lengths[i]]\n                for i in indices\n            ]),\n            gathered_items_lengths\n        )\n\n        nested_offsets = []\n        nested_lengths = []\n        nested_offset = 0\n        j = 0\n        for l in lengths:\n            nested_offsets.append(nested_offset)\n            nested_length = 0\n            for _i in range(l):\n                nested_offset += items_lengths[j]\n                nested_length += items_lengths[j]\n                j += 1\n            nested_lengths.append(nested_length)\n\n        gathered_items_items = workspace.FetchBlob(\n            gathered_record.sparse.list_of_list.items.items())\n        np.testing.assert_array_equal(\n            np.concatenate([\n                items_items[nested_offsets[i]:\n                            nested_offsets[i] + nested_lengths[i]]\n                for i in indices\n            ]),\n            gathered_items_items\n        )\n\n    def testMapToRange(self):\n        input_record = self.new_record(schema.Scalar(np.int32))\n        indices_blob = self.model.MapToRange(input_record,\n                                             max_index=100).indices\n        self.model.output_schema = schema.Struct()\n\n        train_init_net, train_net = self.get_training_nets()\n\n        schema.FeedRecord(\n            input_record,\n            [np.array([10, 3, 20, 99, 15, 11, 3, 11], dtype=np.int32)]\n        )\n        workspace.RunNetOnce(train_init_net)\n        workspace.RunNetOnce(train_net)\n        indices = workspace.FetchBlob(indices_blob())\n        np.testing.assert_array_equal(\n            np.array([1, 2, 3, 4, 5, 6, 2, 6], dtype=np.int32),\n            indices\n        )\n\n        schema.FeedRecord(\n            input_record,\n            [np.array([10, 3, 23, 35, 60, 15, 10, 15], dtype=np.int32)]\n        )\n        workspace.RunNetOnce(train_net)\n        indices = workspace.FetchBlob(indices_blob())\n        np.testing.assert_array_equal(\n            np.array([1, 2, 7, 8, 9, 5, 1, 5], dtype=np.int32),\n            indices\n        )\n\n        eval_net = self.get_eval_net()\n\n        schema.FeedRecord(\n            input_record,\n            [np.array([10, 3, 23, 35, 60, 15, 200], dtype=np.int32)]\n        )\n        workspace.RunNetOnce(eval_net)\n        indices = workspace.FetchBlob(indices_blob())\n        np.testing.assert_array_equal(\n            np.array([1, 2, 7, 8, 9, 5, 0], dtype=np.int32),\n            indices\n        )\n\n        schema.FeedRecord(\n            input_record,\n            [np.array([10, 3, 23, 15, 101, 115], dtype=np.int32)]\n        )\n        workspace.RunNetOnce(eval_net)\n        indices = workspace.FetchBlob(indices_blob())\n        np.testing.assert_array_equal(\n            np.array([1, 2, 7, 5, 0, 0], dtype=np.int32),\n            indices\n        )\n\n        predict_net = self.get_predict_net()\n\n        schema.FeedRecord(\n            input_record,\n            [np.array([3, 3, 20, 23, 151, 35, 60, 15, 200], dtype=np.int32)]\n        )\n        workspace.RunNetOnce(predict_net)\n        indices = workspace.FetchBlob(indices_blob())\n        np.testing.assert_array_equal(\n            np.array([2, 2, 3, 7, 0, 8, 9, 5, 0], dtype=np.int32),\n            indices\n        )\n\n    def testSelectRecordByContext(self):\n        float_features = self.model.input_feature_schema.float_features\n\n        float_array = np.array([1.0, 2.0], dtype=np.float32)\n\n        schema.FeedRecord(float_features, [float_array])\n\n        with Tags(Tags.EXCLUDE_FROM_PREDICTION):\n            log_float_features = self.model.Log(float_features, 1)\n        joined = self.model.SelectRecordByContext(\n            schema.Struct(\n                (InstantiationContext.PREDICTION, float_features),\n                (InstantiationContext.TRAINING, log_float_features),\n                # TODO: TRAIN_ONLY layers are also generated in eval\n                (InstantiationContext.EVAL, log_float_features),\n            )\n        )\n\n        # model.output_schema has to a struct\n        self.model.output_schema = schema.Struct((\n            'joined', joined\n        ))\n        predict_net = layer_model_instantiator.generate_predict_net(self.model)\n        workspace.RunNetOnce(predict_net)\n        predict_output = schema.FetchRecord(predict_net.output_record())\n        npt.assert_array_equal(float_array,\n                               predict_output['joined']())\n        eval_net = layer_model_instantiator.generate_eval_net(self.model)\n        workspace.RunNetOnce(eval_net)\n        eval_output = schema.FetchRecord(eval_net.output_record())\n        npt.assert_array_equal(np.log(float_array),\n                               eval_output['joined']())\n        _, train_net = (\n            layer_model_instantiator.generate_training_nets_forward_only(\n                self.model\n            )\n        )\n        workspace.RunNetOnce(train_net)\n        train_output = schema.FetchRecord(train_net.output_record())\n        npt.assert_array_equal(np.log(float_array),\n                               train_output['joined']())\n\n    def testFunctionalLayer(self):\n        def normalize(net, in_record, out_record):\n            mean = net.ReduceFrontMean(in_record(), 1)\n            net.Sub(\n                [in_record(), mean],\n                out_record(),\n                broadcast=1)\n        normalized = self.model.Functional(\n            self.model.input_feature_schema.float_features, 1,\n            normalize, name=\"normalizer\")\n\n        # Attach metadata to one of the outputs and use it in FC\n        normalized.set_type((np.float32, 32))\n        self.model.output_schema = self.model.FC(normalized, 2)\n\n        predict_net = layer_model_instantiator.generate_predict_net(\n            self.model)\n        ops = predict_net.Proto().op\n        assert len(ops) == 3\n        assert ops[0].type == \"ReduceFrontMean\"\n        assert ops[1].type == \"Sub\"\n        assert ops[2].type == \"FC\"\n        assert len(ops[0].input) == 1\n        assert ops[0].input[0] ==\\\n            self.model.input_feature_schema.float_features()\n        assert len(ops[1].output) == 1\n        assert ops[1].output[0] in ops[2].input\n\n    def testFunctionalLayerHelper(self):\n        mean = self.model.ReduceFrontMean(\n            self.model.input_feature_schema.float_features, 1)\n        normalized = self.model.Sub(\n            schema.Tuple(\n                self.model.input_feature_schema.float_features, mean),\n            1, broadcast=1)\n        # Attach metadata to one of the outputs and use it in FC\n        normalized.set_type((np.float32, (32,)))\n        self.model.output_schema = self.model.FC(normalized, 2)\n\n        predict_net = layer_model_instantiator.generate_predict_net(\n            self.model)\n        ops = predict_net.Proto().op\n        assert len(ops) == 3\n        assert ops[0].type == \"ReduceFrontMean\"\n        assert ops[1].type == \"Sub\"\n        assert ops[2].type == \"FC\"\n        assert len(ops[0].input) == 1\n        assert ops[0].input[0] ==\\\n            self.model.input_feature_schema.float_features()\n        assert len(ops[1].output) == 1\n        assert ops[1].output[0] in ops[2].input\n\n    def testFunctionalLayerHelperAutoInference(self):\n        softsign = self.model.Softsign(\n            schema.Tuple(self.model.input_feature_schema.float_features),\n            1)\n        assert softsign.field_type().base == np.float32\n        assert softsign.field_type().shape == (32,)\n        self.model.output_schema = self.model.FC(softsign, 2)\n\n        predict_net = layer_model_instantiator.generate_predict_net(\n            self.model)\n        ops = predict_net.Proto().op\n        assert len(ops) == 2\n        assert ops[0].type == \"Softsign\"\n        assert ops[1].type == \"FC\"\n        assert len(ops[0].input) == 1\n        assert ops[0].input[0] ==\\\n            self.model.input_feature_schema.float_features()\n        assert len(ops[0].output) == 1\n        assert ops[0].output[0] in ops[1].input\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support.\")\n    def testHalfToFloatTypeInference(self):\n        input = self.new_record(schema.Scalar((np.float32, (32,))))\n\n        output = self.model.FloatToHalf(input, 1)\n        assert output.field_type().base == np.float16\n        assert output.field_type().shape == (32, )\n\n        output = self.model.HalfToFloat(output, 1)\n        assert output.field_type().base == np.float32\n        assert output.field_type().shape == (32, )\n\n    def testFunctionalLayerHelperAutoInferenceScalar(self):\n        loss = self.model.AveragedLoss(self.model.input_feature_schema, 1)\n        self.assertEqual(1, len(loss.field_types()))\n        self.assertEqual(np.float32, loss.field_types()[0].base)\n        self.assertEqual(tuple(), loss.field_types()[0].shape)\n\n    def testFunctionalLayerInputCoercion(self):\n        one = self.model.global_constants['ONE']\n        two = self.model.Add([one, one], 1)\n        self.model.loss = two\n        self.run_train_net()\n        data = workspace.FetchBlob(two.field_blobs()[0])\n        np.testing.assert_array_equal([2.0], data)\n\n    def testFunctionalLayerWithOutputNames(self):\n        k = 3\n        topk = self.model.TopK(\n            self.model.input_feature_schema,\n            output_names_or_num=['values', 'indices'],\n            k=k,\n        )\n        self.assertEqual(2, len(topk.field_types()))\n        self.assertEqual(np.float32, topk.field_types()[0].base)\n        self.assertEqual((k,), topk.field_types()[0].shape)\n        self.assertEqual(np.int32, topk.field_types()[1].base)\n        self.assertEqual((k,), topk.field_types()[1].shape)\n        self.assertEqual(['TopK/values', 'TopK/indices'], topk.field_blobs())\n\n    def testFunctionalLayerSameOperatorOutputNames(self):\n        Con1 = self.model.ConstantFill([], 1, value=1)\n        Con2 = self.model.ConstantFill([], 1, value=2)\n        self.assertNotEqual(str(Con1), str(Con2))\n\n    def testFunctionalLayerWithOutputDtypes(self):\n        loss = self.model.AveragedLoss(\n            self.model.input_feature_schema,\n            1,\n            output_dtypes=(np.float32, (1,)),\n        )\n        self.assertEqual(1, len(loss.field_types()))\n        self.assertEqual(np.float32, loss.field_types()[0].base)\n        self.assertEqual((1,), loss.field_types()[0].shape)\n\n    def testPropagateRequestOnly(self):\n        # test case when output is request only\n        input_record = self.new_record(schema.Struct(\n            ('input1', schema.Scalar((np.float32, (32, )))),\n            ('input2', schema.Scalar((np.float32, (64, )))),\n            ('input3', schema.Scalar((np.float32, (16, )))),\n        ))\n\n        set_request_only(input_record)\n        concat_output = self.model.Concat(input_record)\n        self.assertEqual(is_request_only_scalar(concat_output), True)\n\n        # test case when output is not request only\n        input_record2 = self.new_record(schema.Struct(\n            ('input4', schema.Scalar((np.float32, (100, ))))\n        )) + input_record\n\n        concat_output2 = self.model.Concat(input_record2)\n        self.assertEqual(is_request_only_scalar(concat_output2), False)\n\n    def testSetRequestOnly(self):\n        input_record = schema.Scalar(np.int64)\n        schema.attach_metadata_to_scalars(\n            input_record,\n            schema.Metadata(\n                categorical_limit=100000000,\n                expected_value=99,\n                feature_specs=schema.FeatureSpec(\n                    feature_ids=[1, 100, 1001]\n                )\n            )\n        )\n\n        set_request_only(input_record)\n        self.assertEqual(input_record.metadata.categorical_limit, 100000000)\n        self.assertEqual(input_record.metadata.expected_value, 99)\n        self.assertEqual(\n            input_record.metadata.feature_specs.feature_ids,\n            [1, 100, 1001]\n        )\n\n    @given(\n        X=hu.arrays(dims=[5, 5]),  # Shape of X is irrelevant\n    )\n    def testDropout(self, X):\n        input_record = self.new_record(schema.Scalar((np.float32, (1,))))\n        schema.FeedRecord(input_record, [X])\n        d_output = self.model.Dropout(input_record)\n        self.assertEqual(schema.Scalar((np.float32, (1,))), d_output)\n        self.model.output_schema = schema.Struct()\n\n        train_init_net, train_net = self.get_training_nets()\n\n        input_blob = input_record.field_blobs()[0]\n        output_blob = d_output.field_blobs()[0]\n\n        train_d_spec = OpSpec(\n            \"Dropout\",\n            [input_blob],\n            [output_blob, None],\n            {'is_test': 0, 'ratio': 0.5}\n        )\n\n        test_d_spec = OpSpec(\n            \"Dropout\",\n            [input_blob],\n            [output_blob, None],\n            {'is_test': 1, 'ratio': 0.5}\n        )\n\n        self.assertNetContainOps(\n            train_net,\n            [train_d_spec]\n        )\n\n        eval_net = self.get_eval_net()\n\n        self.assertNetContainOps(\n            eval_net,\n            [test_d_spec]\n        )\n\n        predict_net = self.get_predict_net()\n\n        self.assertNetContainOps(\n            predict_net,\n            [test_d_spec]\n        )\n\n        workspace.RunNetOnce(train_init_net)\n        workspace.RunNetOnce(train_net)\n\n        schema.FeedRecord(input_record, [X])\n        workspace.RunNetOnce(eval_net)\n\n        schema.FeedRecord(input_record, [X])\n        workspace.RunNetOnce(predict_net)\n\n    @given(\n        num_inputs=st.integers(1, 3),\n        batch_size=st.integers(5, 10)\n    )\n    def testMergeIdListsLayer(self, num_inputs, batch_size):\n        inputs = []\n        for _ in range(num_inputs):\n            lengths = np.random.randint(5, size=batch_size).astype(np.int32)\n            size = lengths.sum()\n            values = np.random.randint(1, 10, size=size).astype(np.int64)\n            inputs.append(lengths)\n            inputs.append(values)\n        input_schema = schema.Tuple(\n            *[schema.List(\n                schema.Scalar(dtype=np.int64, metadata=schema.Metadata(\n                    categorical_limit=20\n                ))) for _ in range(num_inputs)]\n        )\n\n        input_record = schema.NewRecord(self.model.net, input_schema)\n        schema.FeedRecord(input_record, inputs)\n        output_schema = self.model.MergeIdLists(input_record)\n        assert schema.equal_schemas(\n            output_schema, IdList,\n            check_field_names=False)\n\n    @given(\n        batch_size=st.integers(min_value=2, max_value=10),\n        input_dims=st.integers(min_value=5, max_value=10),\n        output_dims=st.integers(min_value=5, max_value=10),\n        bandwidth=st.floats(min_value=0.1, max_value=5),\n    )\n    def testRandomFourierFeatures(self, batch_size, input_dims, output_dims, bandwidth):\n\n        def _rff_hypothesis_test(rff_output, X, W, b, scale):\n            \"\"\"\n            Runs hypothesis test for Semi Random Features layer.\n\n            Inputs:\n                rff_output -- output of net after running random fourier features layer\n                X -- input data\n                W -- weight parameter from train_init_net\n                b -- bias parameter from train_init_net\n                scale -- value by which to scale the output vector\n            \"\"\"\n            output = workspace.FetchBlob(rff_output)\n            output_ref = scale * np.cos(np.dot(X, np.transpose(W)) + b)\n            npt.assert_allclose(output, output_ref, rtol=1e-3, atol=1e-3)\n\n        X = np.random.random((batch_size, input_dims)).astype(np.float32)\n        scale = np.sqrt(2.0 / output_dims)\n        input_record = self.new_record(schema.Scalar((np.float32, (input_dims,))))\n        schema.FeedRecord(input_record, [X])\n        input_blob = input_record.field_blobs()[0]\n        rff_output = self.model.RandomFourierFeatures(input_record,\n                                                      output_dims,\n                                                      bandwidth)\n        self.model.output_schema = schema.Struct()\n\n        self.assertEqual(\n            schema.Scalar((np.float32, (output_dims, ))),\n            rff_output\n        )\n\n        train_init_net, train_net = self.get_training_nets()\n\n        # Init net assertions\n        init_ops_list = [\n            OpSpec(\"GaussianFill\", None, None),\n            OpSpec(\"UniformFill\", None, None),\n        ]\n        init_ops = self._test_net(train_init_net, init_ops_list)\n        W = workspace.FetchBlob(self.model.layers[0].w)\n        b = workspace.FetchBlob(self.model.layers[0].b)\n\n        # Operation specifications\n        fc_spec = OpSpec(\"FC\", [input_blob, init_ops[0].output[0],\n                         init_ops[1].output[0]], None)\n        cosine_spec = OpSpec(\"Cos\", None, None)\n        scale_spec = OpSpec(\"Scale\", None, rff_output.field_blobs(),\n                            {'scale': scale})\n        ops_list = [\n            fc_spec,\n            cosine_spec,\n            scale_spec\n        ]\n\n        # Train net assertions\n        self._test_net(train_net, ops_list)\n        _rff_hypothesis_test(rff_output(), X, W, b, scale)\n\n        # Eval net assertions\n        eval_net = self.get_eval_net()\n        self._test_net(eval_net, ops_list)\n        _rff_hypothesis_test(rff_output(), X, W, b, scale)\n\n        # Predict net assertions\n        predict_net = self.get_predict_net()\n        self._test_net(predict_net, ops_list)\n        _rff_hypothesis_test(rff_output(), X, W, b, scale)\n\n    @given(\n        batch_size=st.integers(min_value=2, max_value=10),\n        input_dims=st.integers(min_value=5, max_value=10),\n        output_dims=st.integers(min_value=5, max_value=10),\n        s=st.integers(min_value=0, max_value=3),\n        scale=st.floats(min_value=0.1, max_value=5),\n        set_weight_as_global_constant=st.booleans()\n    )\n    def testArcCosineFeatureMap(self, batch_size, input_dims, output_dims, s, scale,\n                                set_weight_as_global_constant):\n\n        def _arc_cosine_hypothesis_test(ac_output, X, W, b, s):\n            \"\"\"\n            Runs hypothesis test for Arc Cosine layer.\n\n            Inputs:\n                ac_output -- output of net after running arc cosine layer\n                X -- input data\n                W -- weight parameter from train_init_net\n                b -- bias parameter from train_init_net\n                s -- degree parameter\n            \"\"\"\n            # Get output from net\n            net_output = workspace.FetchBlob(ac_output)\n\n            # Computing output directly\n            x_rand = np.matmul(X, np.transpose(W)) + b\n            x_pow = np.power(x_rand, s)\n            if s > 0:\n                h_rand_features = np.piecewise(x_rand,\n                                               [x_rand <= 0, x_rand > 0],\n                                               [0, 1])\n            else:\n                h_rand_features = np.piecewise(x_rand,\n                                               [x_rand <= 0, x_rand > 0],\n                                               [0, lambda x: x / (1 + x)])\n            output_ref = np.multiply(x_pow, h_rand_features)\n\n            # Comparing net output and computed output\n            npt.assert_allclose(net_output, output_ref, rtol=1e-3, atol=1e-3)\n\n        X = np.random.normal(size=(batch_size, input_dims)).astype(np.float32)\n        input_record = self.new_record(schema.Scalar((np.float32, (input_dims,))))\n        schema.FeedRecord(input_record, [X])\n        input_blob = input_record.field_blobs()[0]\n\n        ac_output = self.model.ArcCosineFeatureMap(\n            input_record,\n            output_dims,\n            s=s,\n            scale=scale,\n            set_weight_as_global_constant=set_weight_as_global_constant\n        )\n        self.model.output_schema = schema.Struct()\n        self.assertEqual(\n            schema.Scalar((np.float32, (output_dims, ))),\n            ac_output\n        )\n\n        train_init_net, train_net = self.get_training_nets()\n\n        # Run create_init_net to initialize the global constants, and W and b\n        workspace.RunNetOnce(train_init_net)\n        workspace.RunNetOnce(self.model.create_init_net(name='init_net'))\n\n        if set_weight_as_global_constant:\n            W = workspace.FetchBlob(\n                self.model.global_constants['arc_cosine_feature_map_fixed_rand_W']\n            )\n            b = workspace.FetchBlob(\n                self.model.global_constants['arc_cosine_feature_map_fixed_rand_b']\n            )\n        else:\n            W = workspace.FetchBlob(self.model.layers[0].random_w)\n            b = workspace.FetchBlob(self.model.layers[0].random_b)\n\n        # Operation specifications\n        fc_spec = OpSpec(\"FC\", [input_blob, None, None], None)\n        softsign_spec = OpSpec(\"Softsign\", None, None)\n        relu_spec = OpSpec(\"Relu\", None, None)\n        relu_spec_output = OpSpec(\"Relu\", None, ac_output.field_blobs())\n        pow_spec = OpSpec(\"Pow\", None, None, {'exponent': float(s - 1)})\n        mul_spec = OpSpec(\"Mul\", None, ac_output.field_blobs())\n\n        if s == 0:\n            ops_list = [\n                fc_spec,\n                softsign_spec,\n                relu_spec_output,\n            ]\n        elif s == 1:\n            ops_list = [\n                fc_spec,\n                relu_spec_output,\n            ]\n        else:\n            ops_list = [\n                fc_spec,\n                relu_spec,\n                pow_spec,\n                mul_spec,\n            ]\n\n        # Train net assertions\n        self._test_net(train_net, ops_list)\n        _arc_cosine_hypothesis_test(ac_output(), X, W, b, s)\n\n        # Eval net assertions\n        eval_net = self.get_eval_net()\n        self._test_net(eval_net, ops_list)\n        _arc_cosine_hypothesis_test(ac_output(), X, W, b, s)\n\n        # Predict net assertions\n        predict_net = self.get_predict_net()\n        self._test_net(predict_net, ops_list)\n        _arc_cosine_hypothesis_test(ac_output(), X, W, b, s)\n\n    @given(\n        batch_size=st.integers(min_value=2, max_value=10),\n        input_dims=st.integers(min_value=5, max_value=10),\n        output_dims=st.integers(min_value=5, max_value=10),\n        s=st.integers(min_value=0, max_value=3),\n        scale=st.floats(min_value=0.1, max_value=5),\n        set_weight_as_global_constant=st.booleans(),\n        use_struct_input=st.booleans(),\n    )\n    def testSemiRandomFeatures(self, batch_size, input_dims, output_dims, s, scale,\n                               set_weight_as_global_constant, use_struct_input):\n\n        def _semi_random_hypothesis_test(srf_output, X_full, X_random, rand_w,\n                                         rand_b, s):\n            \"\"\"\n            Runs hypothesis test for Semi Random Features layer.\n\n            Inputs:\n                srf_output -- output of net after running semi random features layer\n                X_full -- full input data\n                X_random -- random-output input data\n                rand_w -- random-initialized weight parameter from train_init_net\n                rand_b -- random-initialized bias parameter from train_init_net\n                s -- degree parameter\n\n            \"\"\"\n            # Get output from net\n            net_output = workspace.FetchBlob(srf_output)\n\n            # Fetch learned parameter blobs\n            learned_w = workspace.FetchBlob(self.model.layers[0].learned_w)\n            learned_b = workspace.FetchBlob(self.model.layers[0].learned_b)\n\n            # Computing output directly\n            x_rand = np.matmul(X_random, np.transpose(rand_w)) + rand_b\n            x_learn = np.matmul(X_full, np.transpose(learned_w)) + learned_b\n            x_pow = np.power(x_rand, s)\n            if s > 0:\n                h_rand_features = np.piecewise(x_rand,\n                                               [x_rand <= 0, x_rand > 0],\n                                               [0, 1])\n            else:\n                h_rand_features = np.piecewise(x_rand,\n                                               [x_rand <= 0, x_rand > 0],\n                                               [0, lambda x: x / (1 + x)])\n            output_ref = np.multiply(np.multiply(x_pow, h_rand_features), x_learn)\n\n            # Comparing net output and computed output\n            npt.assert_allclose(net_output, output_ref, rtol=1e-3, atol=1e-3)\n\n        X_full = np.random.normal(size=(batch_size, input_dims)).astype(np.float32)\n        if use_struct_input:\n            X_random = np.random.normal(size=(batch_size, input_dims)).\\\n                astype(np.float32)\n            input_data = [X_full, X_random]\n            input_record = self.new_record(schema.Struct(\n                ('full', schema.Scalar(\n                    (np.float32, (input_dims,))\n                )),\n                ('random', schema.Scalar(\n                    (np.float32, (input_dims,))\n                ))\n            ))\n        else:\n            X_random = X_full\n            input_data = [X_full]\n            input_record = self.new_record(schema.Scalar(\n                (np.float32, (input_dims,))\n            ))\n\n        schema.FeedRecord(input_record, input_data)\n        srf_output = self.model.SemiRandomFeatures(\n            input_record,\n            output_dims,\n            s=s,\n            scale_random=scale,\n            scale_learned=scale,\n            set_weight_as_global_constant=set_weight_as_global_constant\n        )\n\n        self.model.output_schema = schema.Struct()\n\n        self.assertEqual(\n            schema.Struct(\n                ('full', schema.Scalar(\n                    (np.float32, (output_dims,))\n                )),\n                ('random', schema.Scalar(\n                    (np.float32, (output_dims,))\n                ))\n            ),\n            srf_output\n        )\n\n        init_ops_list = [\n            OpSpec(\"GaussianFill\", None, None),\n            OpSpec(\"UniformFill\", None, None),\n            OpSpec(\"GaussianFill\", None, None),\n            OpSpec(\"UniformFill\", None, None),\n        ]\n        train_init_net, train_net = self.get_training_nets()\n\n        # Need to run to initialize the global constants for layer\n        workspace.RunNetOnce(self.model.create_init_net(name='init_net'))\n\n        if set_weight_as_global_constant:\n            # If weight params are global constants, they won't be in train_init_net\n            init_ops = self._test_net(train_init_net, init_ops_list[:2])\n            rand_w = workspace.FetchBlob(\n                self.model.global_constants['semi_random_features_fixed_rand_W']\n            )\n            rand_b = workspace.FetchBlob(\n                self.model.global_constants['semi_random_features_fixed_rand_b']\n            )\n\n            # Operation specifications\n            fc_random_spec = OpSpec(\"FC\", [None, None, None], None)\n            fc_learned_spec = OpSpec(\"FC\", [None, init_ops[0].output[0],\n                                     init_ops[1].output[0]], None)\n        else:\n            init_ops = self._test_net(train_init_net, init_ops_list)\n            rand_w = workspace.FetchBlob(self.model.layers[0].random_w)\n            rand_b = workspace.FetchBlob(self.model.layers[0].random_b)\n\n            # Operation specifications\n            fc_random_spec = OpSpec(\"FC\", [None, init_ops[0].output[0],\n                                    init_ops[1].output[0]], None)\n            fc_learned_spec = OpSpec(\"FC\", [None, init_ops[2].output[0],\n                                     init_ops[3].output[0]], None)\n\n        softsign_spec = OpSpec(\"Softsign\", None, None)\n        relu_spec = OpSpec(\"Relu\", None, None)\n        relu_output_spec = OpSpec(\"Relu\", None, srf_output.random.field_blobs())\n        pow_spec = OpSpec(\"Pow\", None, None, {'exponent': float(s - 1)})\n        mul_interim_spec = OpSpec(\"Mul\", None, srf_output.random.field_blobs())\n        mul_spec = OpSpec(\"Mul\", None, srf_output.full.field_blobs())\n\n        if s == 0:\n            ops_list = [\n                fc_learned_spec,\n                fc_random_spec,\n                softsign_spec,\n                relu_output_spec,\n                mul_spec,\n            ]\n        elif s == 1:\n            ops_list = [\n                fc_learned_spec,\n                fc_random_spec,\n                relu_output_spec,\n                mul_spec,\n            ]\n        else:\n            ops_list = [\n                fc_learned_spec,\n                fc_random_spec,\n                relu_spec,\n                pow_spec,\n                mul_interim_spec,\n                mul_spec,\n            ]\n\n        # Train net assertions\n        self._test_net(train_net, ops_list)\n        _semi_random_hypothesis_test(srf_output.full(), X_full, X_random,\n                                     rand_w, rand_b, s)\n\n        # Eval net assertions\n        eval_net = self.get_eval_net()\n        self._test_net(eval_net, ops_list)\n        _semi_random_hypothesis_test(srf_output.full(), X_full, X_random,\n                                     rand_w, rand_b, s)\n\n        # Predict net assertions\n        predict_net = self.get_predict_net()\n        self._test_net(predict_net, ops_list)\n        _semi_random_hypothesis_test(srf_output.full(), X_full, X_random,\n                                     rand_w, rand_b, s)\n\n    def testConv(self):\n        batch_size = 50\n        H = 1\n        W = 10\n        C = 50\n        output_dims = 32\n        kernel_h = 1\n        kernel_w = 3\n        stride_h = 1\n        stride_w = 1\n        pad_t = 0\n        pad_b = 0\n        pad_r = None\n        pad_l = None\n\n        input_record = self.new_record(schema.Scalar((np.float32, (H, W, C))))\n        X = np.random.random((batch_size, H, W, C)).astype(np.float32)\n        schema.FeedRecord(input_record, [X])\n        conv = self.model.Conv(\n            input_record,\n            output_dims,\n            kernel_h=kernel_h,\n            kernel_w=kernel_w,\n            stride_h=stride_h,\n            stride_w=stride_w,\n            pad_t=pad_t,\n            pad_b=pad_b,\n            pad_r=pad_r,\n            pad_l=pad_l,\n            order='NHWC'\n        )\n\n        self.assertEqual(\n            schema.Scalar((np.float32, (output_dims,))),\n            conv\n        )\n\n        self.run_train_net_forward_only()\n        output_record = schema.FetchRecord(conv)\n        # check the number of output channels is the same as input in this example\n        assert output_record.field_types()[0].shape == (H, W, output_dims)\n        assert output_record().shape == (batch_size, H, W, output_dims)\n\n        train_init_net, train_net = self.get_training_nets()\n        # Init net assertions\n        init_ops = self.assertNetContainOps(\n            train_init_net,\n            [\n                OpSpec(\"XavierFill\", None, None),\n                OpSpec(\"ConstantFill\", None, None),\n            ]\n        )\n        conv_spec = OpSpec(\n            \"Conv\",\n            [\n                input_record.field_blobs()[0],\n                init_ops[0].output[0],\n                init_ops[1].output[0],\n            ],\n            conv.field_blobs()\n        )\n\n        # Train net assertions\n        self.assertNetContainOps(train_net, [conv_spec])\n\n        # Predict net assertions\n        predict_net = self.get_predict_net()\n        self.assertNetContainOps(predict_net, [conv_spec])\n\n        # Eval net assertions\n        eval_net = self.get_eval_net()\n        self.assertNetContainOps(eval_net, [conv_spec])\n"
  },
  {
    "path": "caffe2/python/lengths_reducer_fused_8bit_rowwise_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\n\nimport numpy as np\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\n\nclass TestLengthsReducerOpsFused8BitRowwise(hu.HypothesisTestCase):\n    @given(\n        input_data=hu.tensor(min_dim=2, max_dim=2),\n        weighted=st.booleans(),\n        seed=st.integers(0, 2**32 - 1),\n    )\n    def test_sparse_lengths_sum(self, input_data, weighted, seed):\n        net = core.Net(\"bench\")\n\n        np.random.seed(seed)\n\n        input_data = input_data.astype(np.float32)\n        indices = np.random.randint(\n            low=0,\n            high=len(input_data),\n            size=[np.random.randint(len(input_data))],\n            dtype=np.int32\n        )\n        weights = np.random.uniform(size=[len(indices)]).astype(np.float32)\n        lengths_split = np.clip(1, len(indices) // 2, 10)\n        lengths = np.ones(\n            [len(indices) // lengths_split], dtype=np.int32\n        ) * lengths_split\n        print(indices, weights, lengths)\n\n        quantized_data = net.FloatToFused8BitRowwiseQuantized(\n            'input_data', 'quantized_data'\n        )\n        dequantized_data = net.Fused8BitRowwiseQuantizedToFloat(\n            quantized_data, 'dequantized_data'\n        )\n\n        if weighted:\n            net.SparseLengthsWeightedSum(\n                [dequantized_data, 'weights', 'indices', 'lengths'],\n                'sum_reference',\n                engine='fp16'\n            )\n            net.SparseLengthsWeightedSumFused8BitRowwise(\n                [quantized_data, 'weights', 'indices', 'lengths'],\n                'sum_quantized'\n            )\n        else:\n            net.SparseLengthsSum(\n                [dequantized_data, 'indices', 'lengths'],\n                'sum_reference',\n                engine='fp16'\n            )\n            net.SparseLengthsSumFused8BitRowwise(\n                [quantized_data, 'indices', 'lengths'], 'sum_quantized'\n            )\n\n        workspace.FeedBlob('input_data', input_data)\n        workspace.FeedBlob('weights', weights)\n        workspace.FeedBlob('indices', indices)\n        workspace.FeedBlob('lengths', lengths)\n\n        workspace.GlobalInit(['caffe2', '--caffe2_log_level=0'])\n        workspace.CreateNet(net)\n        workspace.RunNetOnce(net)\n\n        sum_reference = workspace.FetchBlob('sum_reference')\n        sum_quantized = workspace.FetchBlob('sum_quantized')\n        np.testing.assert_array_almost_equal(sum_reference, sum_quantized)\n\n    @given(\n        input_data=hu.tensor(min_dim=2, max_dim=2),\n        seed=st.integers(0, 2**32 - 1),\n    )\n    def test_sparse_lengths_mean(self, input_data, seed):\n        net = core.Net(\"bench\")\n\n        np.random.seed(seed)\n\n        input_data = input_data.astype(np.float32)\n        indices = np.random.randint(\n            low=0,\n            high=len(input_data),\n            size=[np.random.randint(len(input_data))],\n            dtype=np.int32\n        )\n        lengths_split = np.clip(1, len(indices) // 2, 10)\n        lengths = np.ones(\n            [len(indices) // lengths_split], dtype=np.int32\n        ) * lengths_split\n        print(indices, lengths)\n\n        quantized_data = net.FloatToFused8BitRowwiseQuantized(\n            'input_data', 'quantized_data'\n        )\n        dequantized_data = net.Fused8BitRowwiseQuantizedToFloat(\n            quantized_data, 'dequantized_data'\n        )\n\n        net.SparseLengthsMean(\n            [dequantized_data, 'indices', 'lengths'],\n            'mean_reference',\n            engine='fp16'\n        )\n        net.SparseLengthsMeanFused8BitRowwise(\n            [quantized_data, 'indices', 'lengths'], 'mean_quantized'\n        )\n\n        workspace.FeedBlob('input_data', input_data)\n        workspace.FeedBlob('indices', indices)\n        workspace.FeedBlob('lengths', lengths)\n\n        workspace.GlobalInit(['caffe2', '--caffe2_log_level=0'])\n        workspace.CreateNet(net)\n        workspace.RunNetOnce(net)\n\n        mean_reference = workspace.FetchBlob('mean_reference')\n        mean_quantized = workspace.FetchBlob('mean_quantized')\n        np.testing.assert_array_almost_equal(mean_reference, mean_quantized)\n"
  },
  {
    "path": "caffe2/python/lengths_reducer_rowwise_8bit_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\n\nimport numpy as np\n\n\ndef FakeQuantization8BitsRowwise(data):\n    min_el = np.min(data, axis=1)\n    max_el = np.max(data, axis=1)\n    scale = (max_el - min_el) / 255.\n    bias = min_el\n    inv_scale = 1. / scale\n    data = data.T\n    data = np.round((data - bias) * inv_scale) * scale + bias\n    return data.T\n\n\nclass TestQuantize8bits(hu.HypothesisTestCase):\n\n    def test_quantize_op(self):\n        op = core.CreateOperator(\n            'FloatToRowwiseQuantized8Bits',\n            ['input_data'],\n            ['quantized_input', 'scale_bias'])\n        input_data = np.float32(np.asarray([[801., 786, 235.2, 2353.3434],\n                                            [5., 11., 9., -2.]]))\n        workspace.FeedBlob('input_data', input_data)\n        workspace.RunOperatorOnce(op)\n        op1 = core.CreateOperator(\n            'Rowwise8BitQuantizedToFloat',\n            ['quantized_input', 'scale_bias'],\n            ['dequantized_input'])\n        workspace.RunOperatorOnce(op1)\n        result = workspace.FetchBlob('dequantized_input')\n        ground_truth = FakeQuantization8BitsRowwise(input_data)\n        np.testing.assert_array_almost_equal(\n            result, ground_truth)\n\n    def test_quantize_tensor_with_const_row_op(self):\n        op = core.CreateOperator(\n            'FloatToRowwiseQuantized8Bits',\n            ['input_data'],\n            ['quantized_input', 'scale_bias'])\n        input_data = np.float32(np.asarray([[801., 786, 235.2, 2353.3434],\n                                            [9., 9., 9., 9.]]))\n        workspace.FeedBlob('input_data', input_data)\n        workspace.RunOperatorOnce(op)\n        op1 = core.CreateOperator(\n            'Rowwise8BitQuantizedToFloat',\n            ['quantized_input', 'scale_bias'],\n            ['dequantized_input'])\n        workspace.RunOperatorOnce(op1)\n        result = workspace.FetchBlob('dequantized_input')\n        ground_truth = FakeQuantization8BitsRowwise(input_data)\n        ground_truth[1, :] = 9.\n        np.testing.assert_array_almost_equal(\n            result, ground_truth)\n\n    def test_SparseSegmentUint8(self):\n\n        init_net = core.Net(\"init\")\n        net = core.Net(\"bench\")\n        size = 10**3\n        isize = 10**2\n\n        # input preparation\n        d = init_net.UniformFill([], shape=[size, 32])\n        w = init_net.UniformFill([], shape=[isize, ])\n        i = init_net.UniformIntFill([], shape=[isize], max=size - 1)\n        i = init_net.Cast([i], to=core.DataType.INT64)\n        l = init_net.ConstantFill(\n            [],\n            ['l'],\n            shape=[isize // 10],\n            value=10,\n            dtype=core.DataType.INT32,\n        )\n        net.FloatToRowwiseQuantized8Bits([d],\n                                         ['quantized_data', 'scale_bias'])\n        net.Rowwise8BitQuantizedToFloat(['quantized_data', 'scale_bias'],\n                                        ['dequantized_data'])\n\n        # SparseLengthsWeightedSum\n        net.SparseLengthsWeightedSum(['dequantized_data', w, i, l],\n                                     ['PositionWeighted_0'], engine='fp16')\n        net.SparseLengthsWeightedSum8BitsRowwise(\n            ['quantized_data', w, i, l, 'scale_bias'],\n            ['PositionWeighted_1'])\n\n        # SparseLengthsSum\n        net.SparseLengthsSum(['dequantized_data', i, l],\n                             ['Sum_0'], engine='fp16')\n\n        net.SparseLengthsSum8BitsRowwise(\n            ['quantized_data', i, l, 'scale_bias'],\n            ['Sum_1'])\n\n        # SparseLengthsWeightedMean\n        # net.SparseLengthsWeightedMean(['dequantized_data', w, i, l],\n        #                              ['WeightedMean_0'])\n        # net.SparseLengthsWeightedMean8BitsRowwise(\n        #     ['quantized_data', w, i, l, 'scale_bias'],\n        #     ['WeightedMean_1'])\n\n        # SparseLengthsMean\n        net.SparseLengthsMean(['dequantized_data', i, l],\n                              ['Mean_0'], engine='fp16')\n\n        net.SparseLengthsMean8BitsRowwise(\n            ['quantized_data', i, l, 'scale_bias'],\n            ['Mean_1'])\n\n        gathered_w = net.Gather(['quantized_data', i],\n                                engine='fp16')\n\n        gathered_scale_bias = net.Gather(['scale_bias', i],\n                                         engine='fp16')\n        net.Rowwise8BitQuantizedToFloat(\n            [gathered_w, gathered_scale_bias],\n            'Gathered_1')\n\n        net.Gather(['dequantized_data', i], 'Gathered_0')\n\n        workspace.GlobalInit(['caffe2', '--caffe2_log_level=0'])\n        workspace.RunNetOnce(init_net)\n        workspace.CreateNet(net)\n        workspace.RunNetOnce(net)\n\n        PositionWeighted_1 = workspace.FetchBlob('PositionWeighted_1')\n        ground_truth_posw = workspace.FetchBlob('PositionWeighted_0')\n        np.testing.assert_array_almost_equal(PositionWeighted_1,\n                                             ground_truth_posw, decimal=5)\n        Sum_1 = workspace.FetchBlob('Sum_1')\n        ground_truth_sum = workspace.FetchBlob('Sum_0')\n        np.testing.assert_array_almost_equal(Sum_1,\n                                             ground_truth_sum, decimal=5)\n\n        Mean_1 = workspace.FetchBlob('Mean_1')\n        ground_truth_mean = workspace.FetchBlob('Mean_0')\n        np.testing.assert_array_almost_equal(Mean_1,\n                                             ground_truth_mean, decimal=5)\n\n        Gathered_1 = workspace.FetchBlob('Gathered_1')\n        ground_truth_gathered = workspace.FetchBlob('Gathered_0')\n        np.testing.assert_array_almost_equal(Gathered_1,\n                                             ground_truth_gathered, decimal=5)\n"
  },
  {
    "path": "caffe2/python/lstm_benchmark.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package lstm_benchmark\n# Module caffe2.python.lstm_benchmark\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import workspace, core, utils, rnn_cell, model_helper\nfrom caffe2.python import recurrent\n\nimport argparse\nimport numpy as np\nimport time\n\nimport logging\n\nlogging.basicConfig()\nlog = logging.getLogger(\"lstm_bench\")\nlog.setLevel(logging.DEBUG)\n\n\ndef generate_data(T, shape, num_labels, fixed_shape):\n    '''\n    Fill a queue with input data\n    '''\n    log.info(\"Generating T={} sequence batches\".format(T))\n\n    generate_input_init_net = core.Net('generate_input_init')\n    queue = generate_input_init_net.CreateBlobsQueue(\n        [], \"inputqueue\", num_blobs=1, capacity=T,\n    )\n    label_queue = generate_input_init_net.CreateBlobsQueue(\n        [], \"labelqueue\", num_blobs=1, capacity=T,\n    )\n\n    workspace.RunNetOnce(generate_input_init_net)\n    generate_input_net = core.Net('generate_input')\n\n    generate_input_net.EnqueueBlobs([queue, \"scratch\"], [\"scratch\"])\n    generate_input_net.EnqueueBlobs([label_queue, \"label_scr\"], [\"label_scr\"])\n    np.random.seed(2603)\n\n    entry_counts = []\n    for t in range(T):\n        if (t % (max(10, T // 10)) == 0):\n            print(\"Generating data {}/{}\".format(t, T))\n        # Randomize the seqlength\n        random_shape = (\n            [np.random.randint(1, shape[0])] + shape[1:]\n            if t > 0 and not fixed_shape else shape\n        )\n        X = np.random.rand(*random_shape).astype(np.float32)\n        batch_size = random_shape[1]\n        L = num_labels * batch_size\n        labels = (np.random.rand(random_shape[0]) * L).astype(np.int32)\n        workspace.FeedBlob(\"scratch\", X)\n        workspace.FeedBlob(\"label_scr\", labels)\n        workspace.RunNetOnce(generate_input_net.Proto())\n        entry_counts.append(random_shape[0] * random_shape[1])\n\n    log.info(\"Finished data generation\")\n\n    return queue, label_queue, entry_counts\n\n\ndef create_model(args, queue, label_queue, input_shape):\n    model = model_helper.ModelHelper(name=\"LSTM_bench\")\n    seq_lengths, target = \\\n        model.net.AddExternalInputs(\n            'seq_lengths',\n            'target',\n        )\n\n    input_blob = model.net.DequeueBlobs(queue, \"input_data\")\n    labels = model.net.DequeueBlobs(label_queue, \"label\")\n\n    init_blobs = []\n    if args.implementation in [\"own\", \"static\", \"static_dag\"]:\n        T = None\n        if \"static\" in args.implementation:\n            assert args.fixed_shape, \\\n                \"Random input length is not static RNN compatible\"\n            T = args.seq_length\n            print(\"Using static RNN of size {}\".format(T))\n\n        for i in range(args.num_layers):\n            hidden_init, cell_init = model.net.AddExternalInputs(\n                \"hidden_init_{}\".format(i),\n                \"cell_init_{}\".format(i)\n            )\n            init_blobs.extend([hidden_init, cell_init])\n\n        output, last_hidden, _, last_state = rnn_cell.LSTM(\n            model=model,\n            input_blob=input_blob,\n            seq_lengths=seq_lengths,\n            initial_states=init_blobs,\n            dim_in=args.input_dim,\n            dim_out=[args.hidden_dim] * args.num_layers,\n            scope=\"lstm1\",\n            memory_optimization=args.memory_optimization,\n            forward_only=args.forward_only,\n            drop_states=True,\n            return_last_layer_only=True,\n            static_rnn_unroll_size=T,\n        )\n\n        if \"dag\" in args.implementation:\n            print(\"Using DAG net type\")\n            model.net.Proto().type = 'dag'\n            model.net.Proto().num_workers = 4\n\n    elif args.implementation == \"cudnn\":\n        # We need to feed a placeholder input so that RecurrentInitOp\n        # can infer the dimensions.\n        init_blobs = model.net.AddExternalInputs(\"hidden_init\", \"cell_init\")\n        model.param_init_net.ConstantFill([], input_blob, shape=input_shape)\n        output, last_hidden, _ = rnn_cell.cudnn_LSTM(\n            model=model,\n            input_blob=input_blob,\n            initial_states=init_blobs,\n            dim_in=args.input_dim,\n            dim_out=args.hidden_dim,\n            scope=\"cudnnlstm\",\n            num_layers=args.num_layers,\n        )\n\n    else:\n        assert False, \"Unknown implementation\"\n\n    weights = model.net.UniformFill(labels, \"weights\")\n    softmax, loss = model.net.SoftmaxWithLoss(\n        [model.Flatten(output), labels, weights],\n        ['softmax', 'loss'],\n    )\n\n    if not args.forward_only:\n        model.AddGradientOperators([loss])\n\n    # carry states over\n    for init_blob in init_blobs:\n        model.net.Copy(last_hidden, init_blob)\n\n        sz = args.hidden_dim\n        if args.implementation == \"cudnn\":\n            sz *= args.num_layers\n        workspace.FeedBlob(init_blob, np.zeros(\n            [1, args.batch_size, sz], dtype=np.float32\n        ))\n\n    if args.rnn_executor:\n        for op in model.net.Proto().op:\n            if op.type.startswith('RecurrentNetwork'):\n                recurrent.set_rnn_executor_config(\n                    op,\n                    num_threads=args.rnn_executor_num_threads,\n                    max_cuda_streams=args.rnn_executor_max_cuda_streams,\n                )\n    return model, output\n\n\ndef Caffe2LSTM(args):\n    T = args.data_size // args.batch_size\n\n    input_blob_shape = [args.seq_length, args.batch_size, args.input_dim]\n    queue, label_queue, entry_counts = generate_data(T // args.seq_length,\n                                       input_blob_shape,\n                                       args.hidden_dim,\n                                       args.fixed_shape)\n\n    workspace.FeedBlob(\n        \"seq_lengths\",\n        np.array([args.seq_length] * args.batch_size, dtype=np.int32)\n    )\n\n    model, output = create_model(args, queue, label_queue, input_blob_shape)\n\n    workspace.RunNetOnce(model.param_init_net)\n    workspace.CreateNet(model.net)\n\n    start_time = time.time()\n    num_iters = T // args.seq_length\n    total_iters = 0\n\n    # Run the Benchmark\n    log.info(\"------ Warming up ------\")\n    workspace.RunNet(model.net.Proto().name)\n\n    if (args.gpu):\n        log.info(\"Memory stats:\")\n        stats = utils.GetGPUMemoryUsageStats()\n        log.info(\"GPU memory:\\t{} MB\".format(stats['max_total'] / 1024 / 1024))\n\n    log.info(\"------ Starting benchmark ------\")\n    start_time = time.time()\n    last_time = time.time()\n    for iteration in range(1, num_iters, args.iters_to_report):\n        iters_once = min(args.iters_to_report, num_iters - iteration)\n        total_iters += iters_once\n        workspace.RunNet(model.net.Proto().name, iters_once)\n\n        new_time = time.time()\n        log.info(\n            \"Iter: {} / {}. Entries Per Second: {}k.\".format(\n                iteration,\n                num_iters,\n                np.sum(entry_counts[iteration:iteration + iters_once]) /\n                (new_time - last_time) // 100 / 10,\n            )\n        )\n        last_time = new_time\n\n    log.info(\"Done. Total EPS excluding 1st iteration: {}k {}\".format(\n         np.sum(entry_counts[1:]) / (time.time() - start_time) // 100 / 10,\n         \" (with RNN executor)\" if args.rnn_executor else \"\",\n    ))\n\n    if (args.gpu):\n        log.info(\"Memory stats:\")\n        stats = utils.GetGPUMemoryUsageStats()\n        log.info(\"GPU memory:\\t{} MB\".format(stats['max_total'] / 1024 / 1024))\n        if (stats['max_total'] != stats['total']):\n            log.warning(\n                \"Max usage differs from current total usage: {} > {}\".\n                format(stats['max_total'], stats['total'])\n            )\n            log.warning(\"This means that costly deallocations occured.\")\n\n    return time.time() - start_time\n\n\n@utils.debug\ndef Benchmark(args):\n    return Caffe2LSTM(args)\n\n\ndef GetArgumentParser():\n    parser = argparse.ArgumentParser(description=\"LSTM benchmark.\")\n\n    parser.add_argument(\n        \"--hidden_dim\",\n        type=int,\n        default=800,\n        help=\"Hidden dimension\",\n    )\n    parser.add_argument(\n        \"--input_dim\",\n        type=int,\n        default=40,\n        help=\"Input dimension\",\n    )\n    parser.add_argument(\n        \"--batch_size\",\n        type=int,\n        default=128,\n        help=\"The batch size.\"\n    )\n    parser.add_argument(\n        \"--seq_length\",\n        type=int,\n        default=20,\n        help=\"Max sequence length\"\n    )\n    parser.add_argument(\n        \"--data_size\",\n        type=int,\n        default=1000000,\n        help=\"Number of data points to generate\"\n    )\n    parser.add_argument(\n        \"--iters_to_report\",\n        type=int,\n        default=20,\n        help=\"Number of iteration to report progress\"\n    )\n    parser.add_argument(\n        \"--gpu\",\n        action=\"store_true\",\n        help=\"Run all on GPU\",\n    )\n    parser.add_argument(\n        \"--implementation\",\n        type=str,\n        default=\"own\",\n        help=\"'cudnn', 'own', 'static' or 'static_dag'\",\n    )\n    parser.add_argument(\n        \"--fixed_shape\",\n        action=\"store_true\",\n        help=(\"Whether to randomize shape of input batches. \"\n              \"Static RNN requires fixed shape\"),\n    )\n    parser.add_argument(\n        \"--memory_optimization\",\n        action=\"store_true\",\n        help=\"Whether to use memory optimized LSTM or not\",\n    )\n    parser.add_argument(\n        \"--forward_only\",\n        action=\"store_true\",\n        help=\"Whether to run only forward pass\"\n    )\n    parser.add_argument(\n        \"--num_layers\",\n        type=int,\n        default=1,\n        help=\"Number of LSTM layers. All output dimensions are going to be\"\n             \"of hidden_dim size\",\n    )\n    parser.add_argument(\n        \"--rnn_executor\",\n        action=\"store_true\",\n        help=\"Whether to use RNN executor\"\n    )\n    parser.add_argument(\n        \"--rnn_executor_num_threads\",\n        type=int,\n        default=None,\n        help=\"Number of threads used by CPU RNN Executor\"\n    )\n    parser.add_argument(\n        \"--rnn_executor_max_cuda_streams\",\n        type=int,\n        default=None,\n        help=\"Maximum number of CUDA streams used by RNN executor on GPU\"\n    )\n    return parser\n\n\nif __name__ == '__main__':\n    args, extra_args = GetArgumentParser().parse_known_args()\n\n    rnn_executor_opt = 1 if args.rnn_executor else 0\n\n    workspace.GlobalInit([\n        'caffe2',\n        '--caffe2_log_level=0',\n        '--caffe2_print_blob_sizes_at_exit=0',\n        '--caffe2_rnn_executor={}'.format(rnn_executor_opt),\n        '--caffe2_gpu_memory_tracking=1'] + extra_args)\n\n    device = core.DeviceOption(\n        caffe2_pb2.CUDA if args.gpu else caffe2_pb2.CPU, 4)\n\n    with core.DeviceScope(device):\n        Benchmark(args)\n"
  },
  {
    "path": "caffe2/python/memonger.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package memonger\n# Module caffe2.python.memonger\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport networkx as nx\nimport collections\nimport time\nimport copy\nfrom caffe2.python import workspace, core\nfrom caffe2.proto import caffe2_pb2\nimport enum\nimport logging\nfrom future.utils import viewitems, viewvalues\nimport caffe2.python._import_c_extension as C\n\nlog = logging.getLogger(\"memonger\")\nlog.setLevel(logging.INFO)\nLiveRange = collections.namedtuple('LiveRange', [\"defined\", \"used\", \"size\"])\n\n\ndef share_grad_blobs(\n    net,\n    losses,\n    param_grads,\n    namescope,\n    dont_share_blobs=None,\n    share_activations=False,\n    blob_shapes=None,\n):\n    '''\n    Implements similar optimization as Torch's shareGradInput():\n    for the gradients that are passed between layers, share blobs between\n    operators when possible. This yields significant memory savings with\n    deep networks.\n\n    Returns an optimized protobuf (assign to net._net)\n    '''\n    def is_grad_blob(b):\n        name = str(b)\n        # Note: need to look at _{namescope} pattern as it matches\n        # to handle the auto-split gradients\n        return name.endswith(\"_grad\") and (name.startswith(namescope) or\n            name.startswith(\"_\" + namescope)) and name not in param_grads\n\n    def is_grad_op(op):\n        # TODO: something smarter\n        for b in list(op.input) + list(op.output):\n            if is_grad_blob(b):\n                return True\n        return False\n\n    log.warn(\"NOTE: Executing memonger to optimize gradient memory\")\n\n    # Collect ops that have something to do with gradients\n    if namescope != \"\" and not namescope.endswith(\"/\"):\n        namescope += \"/\"\n\n    netproto = copy.deepcopy(net.Proto())\n    activations = []\n    external_output = set(net.Proto().external_output)\n\n    # Hacky way to get activations, think of a better way\n    for op in net.Proto().op:\n        for b in op.output:\n            if b + \"_w\" in op.input and b not in external_output:\n                activations.append(b)\n\n    # Remove last activations, as they are usually accessed externally\n    activations = set(activations[:-2])\n\n    # Gradient ops\n    grad_op_indices = []\n    for idx, op in enumerate(netproto.op):\n        if (is_grad_op(op)):\n            grad_op_indices.append(idx)\n\n    shared_blobs = set()\n    for op in net.Proto().op:\n        for b in list(op.input) + list(op.output):\n            if is_grad_blob(b) or (share_activations and b in activations):\n                shared_blobs.add(b)\n    start_time = time.time()\n    optim_str = C.memonger_compute_blob_recycling_for_dag(\n        netproto.SerializeToString(),\n        [str(s).encode('utf-8') for s in losses],\n        grad_op_indices,\n        set(str(s).encode('utf-8') for s in shared_blobs),\n        namescope.encode('utf-8'),\n        set() if dont_share_blobs is None else dont_share_blobs,\n        {} if blob_shapes is None else blob_shapes\n    )\n\n    log.info(\"Memonger memory optimization took {} secs\".format(\n        time.time() - start_time),\n    )\n\n    optim = caffe2_pb2.NetDef()\n    optim.ParseFromString(optim_str)\n    assert verify_graph_equality(net.Proto(), optim), \\\n        \"Memonger graph is not equal to original.\"\n    assert verify_inplace_blobs(net.Proto(), optim), \\\n        \"Inplace assignments differ in memonger net.\"\n    return optim\n\n\ndef optimize_inference_for_dag(net, input_blobs, namescope=\"\"):\n    netproto = copy.deepcopy(net.Proto())\n    external_input = set(net.Proto().external_input)\n    external_output = set(net.Proto().external_output)\n\n    def is_activation_blob(b):\n        return b not in external_input and b not in external_output\n\n    activation_blobs = set()\n    seen_as_output = set()\n    ops = list(net.Proto().op)\n    op_indices = [index for index, op in enumerate(net.Proto().op)]\n\n    # Sanity check: check that all external inputs are properlyh accounted\n    # and that no gradient ops are included in 'net'\n    for op in ops:\n        for b in op.input:\n            if is_activation_blob(b):\n                activation_blobs.add(b)\n                if b not in seen_as_output:\n                    assert False, \"{} not in external input\".format(b)\n        for b in op.output:\n            if is_activation_blob(b):\n                activation_blobs.add(b)\n        seen_as_output = seen_as_output.union(set(op.output))\n        assert not op.is_gradient_op, \\\n            \"You can only pass inference-only nets to optimize_inference_for_dag\"\n    start_time = time.time()\n    optim_str = C.memonger_compute_blob_recycling_for_dag(\n        netproto.SerializeToString(),\n        [str(s).encode('utf-8') for s in input_blobs],\n        op_indices,\n        set(str(s).encode('utf-8') for s in activation_blobs),\n        namescope.encode('utf-8'),\n        set(),\n        {}\n    )\n\n    log.info(\"Memonger memory optimization took {} secs\".format(\n        time.time() - start_time),\n    )\n\n    optim = caffe2_pb2.NetDef()\n    optim.ParseFromString(optim_str)\n\n    assert verify_graph_equality(net.Proto(), optim), \\\n        \"Memonger graph is not equal to original.\"\n    assert verify_inplace_blobs(net.Proto(), optim), \\\n        \"Inplace assignments differ in memonger net.\"\n    return optim\n\n\ndef estimate_memory_usage(protos, shapes, types, devicescope):\n    import numpy as np\n    '''\n    Estimate memory usage of a model. This is an estimate because\n    we assume a single threaded execution and miss some internal\n    memory usage of operators. Only estimates the memory for a given\n    device scope.\n\n    Also, currently it does not handle correctly if blob sizes vary\n    during execution, as it uses only the final blob size.\n\n    Returns (total, highwater, by op type) memory allocation in bytes.\n    '''\n    sizeofs = {\n        caffe2_pb2.TensorProto.DOUBLE: 8,\n        caffe2_pb2.TensorProto.FLOAT: 4,\n        caffe2_pb2.TensorProto.FLOAT16: 2,\n        caffe2_pb2.TensorProto.INT32: 4,\n        caffe2_pb2.TensorProto.INT8: 1,\n        caffe2_pb2.TensorProto.UINT8: 1,\n        caffe2_pb2.TensorProto.UINT16: 2,\n        caffe2_pb2.TensorProto.INT16: 2,\n        caffe2_pb2.TensorProto.BOOL: 1,\n        caffe2_pb2.TensorProto.INT64: 8,\n    }\n\n    def split_net(proto):\n        ops = [op for op in proto.op if\n               op.device_option == devicescope or op.type in {\"Free\", \"Alias\"}]\n        del proto.op[:]\n        proto.op.extend(ops)\n        return proto\n\n    def num_bytes(blob):\n        if blob not in shapes or blob not in types:\n            log.warning(\"Unknown blob encountered: {}\".format(blob))\n            return 0\n        sizeof = sizeofs[types[blob]]\n        return sizeof * np.prod(shapes[blob])\n\n    protos = [split_net(proto) for proto in protos]\n    allocs_by_ops = collections.defaultdict(lambda: 0)\n\n    # Evaluate\n    current_allocated = 0\n    max_allocated = 0\n    total_allocated = 0\n    allocated = set()\n    for proto in protos:\n        for op in proto.op:\n            if op.type == \"Free\" or op.type == \"Alias\":\n                for o in op.output:\n                    if o in allocated:\n                        current_allocated -= num_bytes(o)\n                        allocated.remove(o)\n            else:\n                for output in op.output:\n                    if output not in allocated:\n                        nbytes = num_bytes(output)\n                        total_allocated += nbytes\n                        current_allocated += nbytes\n                        max_allocated = max(max_allocated, current_allocated)\n                        allocated.add(output)\n                        allocs_by_ops[op.type] += nbytes\n\n    return (total_allocated, max_allocated, allocs_by_ops)\n\n\ndef release_blobs_when_used(netproto, dont_free_blobs, selector_fun=None):\n    '''\n    Insert Free-ops after a blob has been used the last time, so that its\n    memory can be reclaimed. Use this only with efficient caching memory\n    managers (such as CUB, --caffe2_cuda_memory_pool=cub).\n\n    Blobs used with Alias op won't be freed.\n\n    @dont_free_blobs:  is a set of blobs that should not be freed\n    @selector_fun:     optional lambda that return True if blob name\n                       can be released. Use for easy special filtering, like\n                       excluding blobs with \"loss\" in the name.\n\n    Returns a new protobuffer. To use with a model, use:\n        model.net._net = memonger.release_blobs_when_used(..)\n    '''\n    input_blobs = set()\n    can_release = set()\n    alias_blobs = set()\n    netproto = copy.deepcopy(netproto)\n\n    for op in netproto.op:\n        if op.type == 'Alias':\n            alias_blobs.add(op.input[0])\n            continue\n        for inp in op.input:\n            input_blobs.add(inp)\n        for outp in op.output:\n            if outp not in input_blobs:\n                if selector_fun is None or selector_fun(outp):\n                    can_release.add(outp)\n\n    # Remove such blobs that are not input at all and external outputs\n    can_release = can_release - set(netproto.external_output)\n    can_release = can_release.intersection(input_blobs)\n    can_release = can_release - dont_free_blobs\n    can_release = can_release - alias_blobs\n\n    ops = list(netproto.op)\n\n    # .. then find last use of each can-release blob, and insert a Free op\n    for j in reversed(range(0, len(netproto.op))):\n        op = netproto.op[j]\n        for inp in op.input:\n            if inp in can_release:\n                can_release.remove(inp)\n                ops.insert(j + 1, core.CreateOperator(\"Free\", [inp], [inp]))\n\n    del netproto.op[:]\n    netproto.op.extend(ops)\n    return netproto\n\n\ndef _find_source_nodes(g):\n    ''' Return nodes without predecessors '''\n    ret = []\n    for cn in g:\n        cur_pred = list(g.predecessors(cn))\n        if not cur_pred:\n            ret.append(cn)\n    return ret\n\n\ndef _find_target_nodes(g):\n    ''' Return nodes without successors '''\n    ret = []\n    for cn in g:\n        cur_succ = list(g.successors(cn))\n        if not cur_succ:\n            ret.append(cn)\n    return ret\n\n\ndef _add_single_target_ifneeded(g):\n    targets = _find_target_nodes(g)\n    assert len(targets) >= 1\n    if len(targets) == 1:\n        return g\n    ret = copy.deepcopy(g)\n\n    def _next_available_idx(g):\n        ret = -1\n        for cn in g:\n            if cn > ret:\n                ret = cn\n        ret += 1\n        return ret\n\n    target_node_idx = _next_available_idx(g)\n    ret.add_node(target_node_idx)\n    for cn in targets:\n        ret.add_edge(cn, target_node_idx)\n\n    return ret\n\n\ndef _get_path(pred_list, dist_list):\n    ''' Get the path from nx.bellman_ford()'s output '''\n\n    # distances are negative\n    assert all(dist_list[x] <= 0 for x in dist_list)\n    # node with longest distance to source is the target\n    target = min(dist_list, key=lambda x: dist_list[x])\n\n    ret = []\n    cur = target\n\n\n    while cur is not None:\n        ret.append(cur)\n        # Hack to get networkx 2.0 happy: it uses list in pred.\n        # TODO(tulloch): are there cases with multiple predecessors?\n        try:\n            cur = pred_list[cur][0]\n        except TypeError:\n            cur = pred_list[cur]\n\n    return list(reversed(ret))\n\n\ndef _get_longest_paths(g, source_nodes):\n    ''' Get the longest path for nodes in 'source_nodes'\n        Find with bellman_ford() by setting weight = -1\n    '''\n\n    ng = copy.deepcopy(g)\n    for u, v in ng.edges():\n        ng[u][v][\"weight\"] = -1\n\n    ret = {}\n    for cn in source_nodes:\n        pred, dist = nx.bellman_ford(ng, cn, weight=\"weight\")\n        path = _get_path(pred, dist)\n        assert path[0] == cn\n        assert len(path) - 1 == -dist[path[-1]]\n        ret[cn] = path\n\n    return ret\n\n\ndef _build_tree(paths):\n    ''' Build a tree for given paths based on common elements.\n        Last elements of all paths are the same, which is the root of the tree.\n    '''\n    assert all(cp[-1] == paths[0][-1] for cp in paths)\n    g = nx.DiGraph()\n    node_set = {y for x in paths for y in x}\n    g.add_nodes_from(node_set)\n    for cp in paths:\n        for ce in zip(cp[0:-1], cp[1:]):\n            g.add_edge(ce[1], ce[0])\n\n    root = paths[0][-1]\n    _compute_tree_height(g, root)\n\n    return (g, root)\n\n\ndef _compute_tree_height(g, root):\n    ''' Compute the heights of the tree for all nodes\n        Height of leaves are 0\n    '''\n    def _get_height(root):\n        children = list(g.successors(root))\n        height = 0\n        if children:\n            child_heights = [_get_height(x) for x in children]\n            height = max(child_heights) + 1\n        g.node[root][\"height\"] = height\n        return height\n\n    _get_height(root)\n\n\ndef _sort_tree_leaves(g, root):\n    ''' For each node, sort its child nodes based on the height of the nodes.\n        Return the leaf nodes of the tree after sorting.\n    '''\n    def _get_height(root):\n        return g.node[root][\"height\"]\n\n    def _get_sorted_leaves(root):\n        children = list(g.successors(root))\n        if not children:\n            return [root]\n        child_heights = [_get_height(x) for x in children]\n        order = sorted(range(len(children)), key=lambda x: child_heights[x])\n        ret = []\n        for co in order:\n            cr = children[co]\n            ret += _get_sorted_leaves(cr)\n\n        return ret\n\n    return _get_sorted_leaves(root)\n\n\ndef topological_sort_traversal_longest_path(g):\n    ''' The graph 'g' may contain several source nodes (nodes without incoming\n        edge), which could be in any order and still be a valid\n        topological sorting result. We would like to arrange these source nodes\n        so that the average live spans of the computed blobs are shorter.\n        The idea is to sort the source nodes based on the length of their path to\n        the target node so that the one with longer path is used first.\n        This is done by:\n        - Add a single target node if there are multiple target nodes in 'g'.\n        - Find the longest path between each source and the target node.\n        - Convert the longest paths to a tree with the target node being the root\n          and source nodes being the leaves.\n        - Sort the nodes of the tree based on the height of the tree.\n    '''\n    gt = _add_single_target_ifneeded(g)\n    source_nodes = _find_source_nodes(gt)\n    lpaths = _get_longest_paths(gt, source_nodes)\n    tree, root = _build_tree(list(viewvalues(lpaths)))\n    sorted_sources = _sort_tree_leaves(tree, root)\n    assert(sorted(sorted_sources) == sorted(source_nodes))\n\n    if nx.__version__ < '2.0':\n        ret = nx.topological_sort(g, sorted_sources)\n    else:\n        # Manually making a sorted descendent list\n        dependency_order = list(sorted_sources)\n        seen_nodes = set(sorted_sources)\n        for s in sorted_sources:\n            desc = nx.descendants(g, s)\n            for d in desc:\n                if d not in seen_nodes:\n                    seen_nodes.add(d)\n                    dependency_order.append(d)\n        sort_key = dict((v, len(dependency_order) - i) for i, v in enumerate(dependency_order))\n        ret = nx.algorithms.dag.lexicographical_topological_sort(\n            g, key=lambda x: sort_key[x])\n        ret = list(ret)\n    assert(len(ret) == len(g.node))\n    return ret\n\n\ndef topological_sort_traversal(g):\n    return list(nx.topological_sort(g))\n\n\ndef compute_ranges(linearized_ops, blob_sizes=None):\n    if not blob_sizes:\n        log.warning('Provide blob sizes to get more accurate assignments.')\n\n    blobs = collections.defaultdict(\n        lambda: LiveRange(defined=None, used=None, size=None))\n    for i, op in enumerate(linearized_ops):\n        for blob in op.input:\n            used = blobs[blob].used\n            if used is None:\n                used = i\n            else:\n                used = max(used, i)\n            blobs[blob] = blobs[blob]._replace(used=used)\n            blob_size = blob_sizes[blob] if blob_sizes else None\n            assert not blob_sizes or blob_size is not None\n            blobs[blob] = blobs[blob]._replace(size=blob_size)\n        for blob in op.output:\n            defined = blobs[blob].defined\n            if defined is None:\n                defined = i\n            else:\n                defined = min(defined, i)\n            blobs[blob] = blobs[blob]._replace(defined=defined)\n            blob_size = blob_sizes[blob] if blob_sizes else None\n            assert not blob_sizes or blob_size is not None\n            blobs[blob] = blobs[blob]._replace(size=blob_size)\n\n    return blobs\n\n\ndef is_compatible(candidate_range, assignment, static_blobs):\n    (name, range_) = assignment[-1]\n    if name in static_blobs:\n        return False\n    if candidate_range.defined is None or range_.defined is None \\\n      or range_.used is None:\n        return False\n    return candidate_range.defined > range_.used\n\n\ndef compute_blob_assignments(assignments):\n    blob_assignments = {}\n    for assignment in assignments:\n        if len(assignment) == 1:\n            continue\n        last_blob, _ = assignment[-1]\n        for (blob, _) in assignment:\n            blob_assignments[blob] = last_blob\n    return blob_assignments\n\n\ndef _get_max_size(assignment):\n    if not assignment:\n        return 0\n    ret = max([x[1].size for x in assignment])\n    ret = 0 if ret is None else ret\n    return ret\n\n\ndef get_memory_usage(assignments):\n    ret = 0\n    for cur in assignments:\n        ret += _get_max_size(cur)\n    return ret\n\n\ndef compute_assignments_greedy(ranges_sorted, init_assignments=None):\n    assignments = init_assignments or []\n    visited = {y[0] for x in assignments for y in x}\n\n    for (name, range_) in ranges_sorted:\n        if name in visited:\n            continue\n        assigned = False\n        best_assignment = 0\n        min_dist = float(\"inf\")\n        candidate_size = range_.size or 0\n        for idx, assignment in enumerate(assignments):\n            if is_compatible(range_, assignment, []):\n                assigned = True\n                dist = abs(_get_max_size(assignment) - candidate_size)\n                if dist < min_dist:\n                    min_dist = dist\n                    best_assignment = idx\n        if assigned:\n            assignment = assignments[best_assignment]\n            assignment.append((name, range_))\n        else:\n            assignments.append([(name, range_)])\n    return assignments\n\n\ndef _get_count(assignments):\n    ''' Return number of blobs in assignments '''\n    if assignments:\n        return sum([len(x) for x in assignments])\n    return 0\n\n\ndef compute_assignments_dp(ranges_sorted, init_assignment, counter=None):\n    ''' Compute assignment for blobs in 'ranges_sorted' on top of 'init_assignment'\n        using dynamic programming + recursion.\n\n        ranges_sorted: blobs sorted by 'used'\n        init_assignment: assignment to start with, blobs in 'ranges_sorted' should\n                         not be used in 'init_assignment'\n\n        Using f(b, k, init) to represent the best assignment for blobs b[0:k]\n        given initial assignment 'init', we have\n            f(b, k, init) = f(b, j, init) +\n                            find_best(b[j:k], f(b, j, init))\n        where j is the index of the last best assignment that is independent of\n        blob b[k - 1] (b[k - 1] is compatible with all assignments in\n        f(b, j, init)), and find_best(b1, init1) gives the best assignment\n        for blobs in 'b1' based on the initial assignment 'init1', and blobs\n        b1[0:-1] should be incompatible with b1[-1]. f(b, len(b), []) gives\n        the best assignment for blobs 'b'.\n\n        For find_best(b, init), since b[0:-1] are not compatible with b[-1], we\n        could reduce it to a smaller problem to find best assignment for b[0:-1]\n        as\n            find_best(b, init) = min {\n                f(b[0:-1], len(b) - 1, init - x) + [x, b[-1]] for x in init, or\n                f(b[0:-1], len(b) - 1, init) + [b[-1]]\n            }\n        where min{} gives the assignment with minimum memory usage.\n    '''\n\n    def _get_compatible_prev(candidate_range, best_assignments, cur_idx):\n        ''' Find closest position k of best_assignments that is independent of\n            candidate_range that candiate_range is compatible with all assignments\n            in best_assignments[k].\n            Return -1 if not found.\n        '''\n        def is_compatible_all(candidate_range, assignments):\n            ''' return true if compatiable for all assignments in assignments '''\n            return all([is_compatible(candidate_range[1], x, []) for x in assignments])\n\n        ii = cur_idx - 1\n        while ii >= 0:\n            cba = best_assignments[ii]\n            if is_compatible_all(candidate_range, cba):\n                return ii\n            ii -= 1\n        return -1\n\n    def _find_best(ranges, init_assignment, prev_best_assignment, counter):\n        ''' Find the best assignment for blobs 'ranges' given an initialized\n            assignment 'init_assignment'.\n\n            Blobs in ranges[0:-1] should be incompatible with blob range[-1].\n            'prev_best_assignment': best assignment for blobs in ranges[:-1]\n\n            By assigning ranges[-1] to each assignment k in 'init_assignment' or\n            in a new assignment, the problem becomes a smaller problem to find\n            the best assignment for ranges[0:-1] given the initial assignment\n            init_assigment[0:k, (k+1):-1].\n        '''\n        # Blob to check\n        find_range = ranges[-1]\n        # Blobs in ranges[0:-1] are incompatible with ranges[-1] so that we can\n        # reduce it to a smaller problem.\n        assert all(not is_compatible(x[1], [find_range], []) for x in ranges[0:-1])\n\n        sz = len(init_assignment)\n        best_candidates = []\n        # Try to assign 'find_range' to each assignment in init_assignment\n        for ii in range(sz):\n            if not is_compatible(find_range[1], init_assignment[ii], []):\n                continue\n            cur_best = copy.deepcopy(init_assignment)\n            cur_best[ii].append(find_range)\n            if len(ranges) > 1:\n                cur_best_tmp = [x for i, x in enumerate(cur_best) if i != ii]\n                # reduce to a smaller dp problem\n                cur_best_tmp = compute_assignments_dp(\n                    ranges[:-1], cur_best_tmp, counter)\n                cur_best = cur_best_tmp + [cur_best[ii]]\n            best_candidates.append(cur_best)\n        # Try to put 'find_range' in a new assignment\n        best_candidates.append(prev_best_assignment + [[find_range]])\n\n        ret = min(best_candidates, key=lambda x: get_memory_usage(x))\n        return ret\n\n    if not counter:\n        counter = [0]\n    counter[0] += 1\n\n    if counter and counter[0] % 5000 == 0:\n        rs = [ranges_sorted[0][1].defined, ranges_sorted[-1][1].used]\n        log.info('Finding assignments {} ({} -> {})...'.format(\n            counter[0], rs[0], rs[1]))\n\n    init_assignment = init_assignment or []\n    # best_assignments[k]: best assignments for first k blobs ranges_sorted[0:(k+1)]\n    best_assignments = []\n    # Find best assignment for blobs ranges_sorted[0:ii]\n    for ii, cur_range in enumerate(ranges_sorted):\n        # closest best_assignment that is independent of ranges_sorted[ii]\n        prev_idx = _get_compatible_prev(cur_range, best_assignments, ii)\n        prev_best = copy.deepcopy(init_assignment) if prev_idx < 0 else \\\n                    copy.deepcopy(best_assignments[prev_idx])\n        # Need to find best assignment for blobs in 'ranges_part'\n        ranges_part = ranges_sorted[(prev_idx + 1):(ii + 1)]\n        cur_best = _find_best(\n            ranges_part, prev_best,\n            best_assignments[-1] if best_assignments else init_assignment,\n            counter)\n        assert _get_count(cur_best) == _get_count(prev_best) + len(ranges_part)\n        best_assignments.append(copy.deepcopy(cur_best))\n\n    assert len(best_assignments) == len(ranges_sorted)\n\n    best = best_assignments[-1]\n\n    return best\n\n\ndef get_updated_ranges(ranges, max_live=None):\n    ''' Set LiveRange.defined = -1 if it is None\n        Set LiveRange.used = max_live if it is None\n        Set LiveRanee.size = 1 if it is None\n    '''\n\n    def _get_max_live(ranges):\n        max_live = max(x[1].used for x in ranges if x[1].used) + 1\n        return max_live\n\n    def _update_range(x, max_live, size):\n        cx = x\n        if x[1].defined is None:\n            cx = (cx[0], cx[1]._replace(defined=-1))\n        if x[1].used is None:\n            cx = (cx[0], cx[1]._replace(used=max_live))\n        if x[1].size is None:\n            cx = (cx[0], cx[1]._replace(size=size))\n        return cx\n\n    if max_live is None:\n        max_live = _get_max_live(ranges)\n    ranges = [_update_range(x, max_live, 1) for x in ranges]\n\n    return ranges\n\n\ndef compute_assignments(ranges, static_blobs, algo):\n    '''\n    algo: Method used to find assignments (AssignmentAlgorithm.GREEDY or\n          AssignmentAlgorithm.DYNAMIC_PROGRAMMING).\n          AssignmentAlgorithm.DYNAMIC_PROGRAMMING gives optimal solution at the\n          cost of more computation.\n          AssignmentAlgorithm.GREEDY may be better in the case 'blob_sizes' is\n          not provided.\n    '''\n\n    # Sort the ranges based on when they are last used.\n    # If LiveRange.used is None, then the blob is never used and could\n    # be consumed externally. Sort these to the end of the list as opposed\n    # to the beginning so that they can be shared as well.\n    ranges = sorted(\n        viewitems(ranges),\n        key=lambda p: (p[1].used is None, p[1].used),\n    )\n    # Update None values\n    ranges = get_updated_ranges(ranges)\n\n    # Sharable blobs\n    ranges_sharable = [x for x in ranges if x[0] not in static_blobs]\n    # Static blobs, not sharable\n    ranges_static = [x for x in ranges if x[0] in static_blobs]\n\n    log.info(\"Total sharable blobs {}\".format(len(ranges_sharable)))\n\n    best_assignment = []\n    if algo == AssignmentAlgorithm.DYNAMIC_PROGRAMMING:\n        best_assignment = compute_assignments_dp(ranges_sharable, [])\n    elif algo == AssignmentAlgorithm.GREEDY:\n        best_assignment = compute_assignments_greedy(ranges_sharable, [])\n    else:\n        assert \"Invalid algo name {}\".format(algo)\n    best_assignment += [[x] for x in ranges_static]\n\n    # verify_assignments(best_assignment)\n\n    return best_assignment\n\n\ndef verify_assignments(assignments):\n    for cur in assignments:\n        for x, y in zip(cur[0:-1], cur[1:]):\n            assert x[1].used < y[1].defined\n\n\ndef compute_interference_graph(ops):\n    g = nx.DiGraph()\n    for i, op in enumerate(ops):\n        g.add_node(i, op=op)\n    for i, parent_op in enumerate(ops):\n        for j, child_op in enumerate(ops):\n            if i >= j:\n                continue\n            if any(output in child_op.input for output in parent_op.output):\n                deps = set(child_op.input).intersection(parent_op.output)\n                g.add_edge(i, j, deps=deps)\n                assert nx.is_directed_acyclic_graph(g), child_op\n    return g\n\n\nOptimization = collections.namedtuple(\n    'Optimization', ['net', 'assignments', 'blob_assignments'])\n\n\ndef apply_assignments(net, blob_assignments):\n    def canonical_name(blob):\n        if blob not in blob_assignments:\n            return blob\n        return blob_assignments[blob]\n\n    for op in net.op:\n        # Descend into subnets of the recurrent network\n        if op.type.startswith('RecurrentNetwork'):\n            apply_recurrent_blob_assignments(op, blob_assignments, canonical_name)\n\n        for i, input_ in enumerate(op.input):\n            op.input[i] = canonical_name(input_)\n        for i, output in enumerate(op.output):\n            op.output[i] = canonical_name(output)\n\n\n\ndef apply_recurrent_blob_assignments(op, blob_assignments, canonical_name):\n    log.debug(\"Applying assignments to recurrent op: {}\".format(op.type))\n    step_args = [a for a in op.arg if a.name.endswith(\"step_net\")]\n    for step_arg in step_args:\n        apply_assignments(step_arg.n, blob_assignments)\n        for i, einp in enumerate(step_arg.n.external_input):\n            if einp in blob_assignments:\n                step_arg.n.external_input[i] = canonical_name(einp)\n    # Store renamings\n    for blob, renamed in viewitems(blob_assignments):\n        if blob in list(op.input) + list(op.output):\n            a = caffe2_pb2.Argument()\n            a.name = blob + \".rename\"\n            a.s = str(renamed).encode(\"ascii\")\n            op.arg.extend([a])\n\n\nclass AssignmentAlgorithm(enum.Enum):\n    GREEDY = 0\n    DYNAMIC_PROGRAMMING = 1\n\n\ndef optimize_inference_fast(net, static_blobs):\n    optim = caffe2_pb2.NetDef()\n    optim_str = C.memonger_optimize_inference_net(\n        net.SerializeToString(),\n        [str(s).encode('utf-8') for s in static_blobs]\n    )\n    optim.ParseFromString(optim_str)\n    return optim\n\n\ndef optimize_interference(net, static_blobs,\n                          ordering_function=topological_sort_traversal,\n                          blob_sizes=None,\n                          algo=AssignmentAlgorithm.GREEDY):\n    \"\"\"\n    ordering_function: topological_sort_traversal or\n                       topological_sort_traversal_longest_path.\n                       topological_sort_traversal_longest_path gives better\n                       results but needs a bit more computation.\n    algo: Method used to find assignments (AssignmentAlgorithm.GREEDY or\n          AssignmentAlgorithm.DYNAMIC_PROGRAMMING).\n          AssignmentAlgorithm.DYNAMIC_PROGRAMMING gives optimal solution at the\n          cost of more computation.\n          AssignmentAlgorithm.GREEDY may be better in the case 'blob_sizes' is\n          not provided.\n    \"\"\"\n\n    \"\"\"\n    1) Use a BFS traversal of the execution graph to generate an\n       ordering of the node executions.\n    2) Generate use-def ranges for each `blob` in the BFS traversal\n       order.\n    3) Assign blobs to `canonical blobs`\n    4) Rename blobs to canonical blobs\n    \"\"\"\n\n    net = copy.deepcopy(net)\n    g = compute_interference_graph(net.op)\n    ordering = ordering_function(g)\n    linearized_ops = [net.op[i] for i in ordering]\n\n    # Reorder ops in net based on the computed linearlized order.\n    # If the graph has multiple topological orderings and if the NetDef's\n    # ordering differs from the order used to compute ranges, then the\n    # runtime might end up overwriting blobs before they are used.\n    del net.op[:]\n    net.op.extend(linearized_ops)\n\n    ranges = compute_ranges(linearized_ops, blob_sizes)\n    assignments = compute_assignments(ranges, static_blobs, algo)\n    blob_assignments = compute_blob_assignments(assignments)\n    apply_assignments(net, blob_assignments)\n    return Optimization(\n        net=net,\n        blob_assignments=blob_assignments,\n        assignments=assignments)\n\n\ndef verify_inplace_blobs(net_a, net_b):\n    \"\"\"\n    Verifies that net_a and net_b have the same in-place blob assignments.\n    Particularly, that memonger did not add an in-place assignment when that\n    did not exist before.\n    \"\"\"\n    def get_inplaces(op):\n        out = list(op.output)\n        inplaces = []\n        for j, inp in enumerate(op.input):\n            if inp in out:\n                inplaces.append([j, out.index(inp)])\n        return inplaces\n\n    for op_a, op_b in zip(net_a.op, net_b.op):\n        if op_a.type != op_b.type:\n            return False\n        if get_inplaces(op_a) != get_inplaces(op_b):\n            return False\n    return True\n\n\ndef verify_graph_equality(net_a, net_b):\n    \"\"\"\n    Determines if the execution of two graphs are identical.\n    That is, all inputs blobs are mapped to the same output blobs\n    for each operator in their respective positions.\n\n    This is meant to check the output of memonger with the original graph.\n    It assumes that the nets have same external input and output.\n\n    O(E) runtime + O(1) amortized cost to hash for python dict\n    \"\"\"\n\n    def parent_list(ops):\n        parent_list = [[] for _ in ops]\n        edge_owner = {}\n        for i, op in enumerate(ops):\n            for blob in op.input:\n                parent_id = edge_owner.get(blob)\n                if parent_id is not None:\n                    parent_list[i].append(parent_id)\n            for blob in op.output:\n                edge_owner[blob] = i\n\n        return parent_list\n\n    # Operator wise equality checks\n    if (len(net_a.op) != len(net_b.op)):\n        return False\n    for op_a, op_b in zip(net_a.op, net_b.op):\n        if (op_a.type != op_b.type or\n                op_a.device_option != op_b.device_option or\n                op_a.engine != op_b.engine):\n            return False\n\n    # Print debug info\n    parent_list_a = parent_list(net_a.op)\n    parent_list_b = parent_list(net_b.op)\n    if parent_list_a != parent_list_b:\n        j = 0\n        for a, b in zip(parent_list_a, parent_list_b):\n            if a != b:\n                print(\"Difference {} vs {} \\n {}\".format(\n                    j, net_a.op[j], net_b.op[j]))\n                print(\"Parents: {} vs {}\".format(a, b))\n\n            j += 1\n\n    # Net wise equality check\n    return parent_list_a == parent_list_b\n\n\nStatistics = collections.namedtuple(\n    'Statistics', ['baseline_nbytes', 'optimized_nbytes'])\n\n\ndef blob_nbytes(blob):\n    sz = 0\n    try:\n        sz = workspace.FetchBlob(blob).nbytes\n    except Exception:\n        log.warning('Error when fetching blob {}'.format(blob))\n    return sz\n\n\ndef compute_statistics(assignments):\n    blob_bytes = {\n        blob: blob_nbytes(blob) for assignment in assignments\n        for (blob, _) in assignment}\n    baseline_nbytes = sum(viewvalues(blob_bytes))\n    optimized_nbytes = sum(\n        max(blob_bytes[blob] for (blob, _) in assignment)\n        for assignment in assignments)\n    return Statistics(\n        baseline_nbytes=baseline_nbytes,\n        optimized_nbytes=optimized_nbytes)\n\n\ndef collect_blob_sizes(net):\n    blobs = {}\n    for op in net.op:\n        for blob in op.input:\n            blobs[blob] = blob_nbytes(blob)\n        for blob in op.output:\n            blobs[blob] = blob_nbytes(blob)\n\n    return blobs\n"
  },
  {
    "path": "caffe2/python/memonger_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nfrom caffe2.python import workspace, memonger, core, model_helper, brew\nfrom caffe2.proto import caffe2_pb2\nimport caffe2.python.hypothesis_test_util as hu\nfrom future.utils import viewvalues\nimport hypothesis.strategies as st\nfrom hypothesis import given, settings\nimport unittest\n\n\ndef has_blob(proto, needle):\n    for op in proto.op:\n        for inp in op.input:\n            if inp == needle:\n                return True\n        for outp in op.output:\n            if outp == needle:\n                return True\n    return False\n\n\ndef count_blobs(proto):\n    blobs = set()\n    for op in proto.op:\n        blobs = blobs.union(set(op.input)).union(set(op.output))\n    return len(blobs)\n\n\nclass MemongerTest(hu.HypothesisTestCase):\n    @given(input_dim=st.integers(min_value=1, max_value=10),\n           output_dim=st.integers(min_value=1, max_value=10),\n           batch_size=st.integers(min_value=1, max_value=10),\n           do=st.sampled_from(hu.device_options),\n           algo=st.sampled_from(memonger.AssignmentAlgorithm))\n    @settings(max_examples=5, timeout=120)\n    def test_simple_memonger(self, input_dim, output_dim, batch_size, do, algo):\n        m = model_helper.ModelHelper()\n        fc1 = brew.fc(m, \"data\", \"fc1\", dim_in=input_dim, dim_out=output_dim)\n        fc2 = brew.fc(m, fc1, \"fc2\", dim_in=output_dim, dim_out=output_dim)\n        fc3 = brew.fc(m, fc2, \"fc3\", dim_in=output_dim, dim_out=output_dim)\n\n        fc3.Relu([], fc3)\\\n           .Softmax([], \"pred\") \\\n           .LabelCrossEntropy([\"label\"], [\"xent\"]) \\\n           .AveragedLoss([], \"loss\")\n        input_to_grad = m.AddGradientOperators([\"loss\"])\n        m.net.Proto().device_option.CopyFrom(do)\n        m.param_init_net.Proto().device_option.CopyFrom(do)\n        static_blobs = \\\n            [o for op in m.param_init_net.Proto().op for o in op.output] + \\\n            [\"data\", \"label\", \"loss\", input_to_grad[\"fc1_w\"]]\n\n        optimization = memonger.optimize_interference(\n            m.Proto(), static_blobs, algo=algo)\n        data = np.random.randn(batch_size, input_dim).astype(np.float32)\n        label = np.random.randint(\n            low=0, high=output_dim, size=(batch_size,)).astype(np.int32)\n        workspace.RunNetOnce(m.param_init_net)\n        workspace.FeedBlob(\"data\", data, device_option=do)\n        workspace.FeedBlob(\"label\", label, device_option=do)\n        workspace.RunNetOnce(m.net)\n        loss = workspace.FetchBlob(\"loss\")\n        grad = workspace.FetchBlob(str(input_to_grad[\"fc1_w\"]))\n        workspace.RunNetOnce(optimization.net)\n        optimized_loss = workspace.FetchBlob(\"loss\")\n        optimized_grad = workspace.FetchBlob(str(input_to_grad[\"fc1_w\"]))\n        np.testing.assert_almost_equal(loss, optimized_loss)\n        np.testing.assert_almost_equal(grad, optimized_grad)\n        stats = memonger.compute_statistics(optimization.assignments)\n        self.assertLess(stats.optimized_nbytes, stats.baseline_nbytes)\n\n        # run with blob sizes\n        blob_sizes = memonger.collect_blob_sizes(m.Proto())\n        optimization1 = memonger.optimize_interference(\n            m.Proto(), static_blobs, blob_sizes=blob_sizes, algo=algo)\n        workspace.RunNetOnce(optimization1.net)\n        optimized_loss = workspace.FetchBlob(\"loss\")\n        optimized_grad = workspace.FetchBlob(str(input_to_grad[\"fc1_w\"]))\n        np.testing.assert_almost_equal(loss, optimized_loss)\n        np.testing.assert_almost_equal(grad, optimized_grad)\n        stats = memonger.compute_statistics(optimization1.assignments)\n        self.assertLessEqual(stats.optimized_nbytes, stats.baseline_nbytes)\n\n    @given(input_dim=st.integers(min_value=1, max_value=10),\n           output_dim=st.integers(min_value=1, max_value=10),\n           batch_size=st.integers(min_value=1, max_value=10),\n           do=st.sampled_from(hu.device_options))\n    @settings(max_examples=5, timeout=120)\n    def test_fast_memonger(self, input_dim, output_dim, batch_size, do):\n        m = model_helper.ModelHelper()\n        fc1 = brew.fc(m, \"data\", \"fc1\", dim_in=input_dim, dim_out=output_dim)\n        fc2 = brew.fc(m, fc1, \"fc2\", dim_in=output_dim, dim_out=output_dim)\n        fc3 = brew.fc(m, fc2, \"fc3\", dim_in=output_dim, dim_out=output_dim)\n\n        fc3.Relu([], fc3)\\\n           .Softmax([], \"pred\") \\\n           .LabelCrossEntropy([\"label\"], [\"xent\"]) \\\n           .AveragedLoss([], \"loss\")\n        input_to_grad = m.AddGradientOperators([\"loss\"])\n        m.net.Proto().device_option.CopyFrom(do)\n        m.param_init_net.Proto().device_option.CopyFrom(do)\n        static_blobs = \\\n            [o for op in m.param_init_net.Proto().op for o in op.output] + \\\n            [\"data\", \"label\", \"loss\", input_to_grad[\"fc1_w\"]]\n\n        optimized_net = memonger.optimize_inference_fast(\n            m.Proto(), static_blobs)\n        data = np.random.randn(batch_size, input_dim).astype(np.float32)\n        label = np.random.randint(\n            low=0, high=output_dim, size=(batch_size,)).astype(np.int32)\n        workspace.RunNetOnce(m.param_init_net)\n        workspace.FeedBlob(\"data\", data, device_option=do)\n        workspace.FeedBlob(\"label\", label, device_option=do)\n        workspace.RunNetOnce(m.net)\n        loss = workspace.FetchBlob(\"loss\")\n        grad = workspace.FetchBlob(str(input_to_grad[\"fc1_w\"]))\n        workspace.RunNetOnce(optimized_net)\n        optimized_loss = workspace.FetchBlob(\"loss\")\n        optimized_grad = workspace.FetchBlob(str(input_to_grad[\"fc1_w\"]))\n        np.testing.assert_almost_equal(loss, optimized_loss)\n        np.testing.assert_almost_equal(grad, optimized_grad)\n\n        self.assertLess(count_blobs(optimized_net), count_blobs(m.Proto()))\n\n    def test_fast_memonger_unique_outputs(self):\n        m = model_helper.ModelHelper()\n        fc = []\n        for i in range(2):\n            z = brew.fc(\n                m, \"data{}\".format(i), \"fc\".format(i), dim_in=2, dim_out=2)\n            fc.append(z)\n        r = []\n        # Trick is here to have same input appear twice in a same Sum\n        for x in fc:\n            for y in fc:\n                r.append(brew.sum(m, [x, y], 1))\n        concated = brew.concat(m, r, \"concated\")\n        brew.relu(m, concated, \"merged\")\n\n        static_blobs = \\\n            [o for op in m.param_init_net.Proto().op for o in op.output] + \\\n            [\"merged\"] + [\"data{}\".format(i) for i in range(len(fc))]\n\n        optimized_net = memonger.optimize_inference_fast(\n            m.Proto(), static_blobs)\n        for op in optimized_net.op:\n            self.assertEqual(len(op.output), len(set(op.output)), str(op))\n\n    @given(input_dim=st.integers(min_value=1, max_value=4),\n           output_dim=st.integers(min_value=1, max_value=4),\n           batch_size=st.integers(min_value=1, max_value=4))\n    def test_gradient_optim(self, input_dim, output_dim, batch_size):\n        m = model_helper.ModelHelper()\n        with core.NameScope(\"name_x\"):\n            fc1 = brew.fc(m, \"data\", \"fc1\", dim_in=input_dim, dim_out=output_dim)\n            fc2 = brew.fc(m, fc1, \"fc2\", dim_in=output_dim, dim_out=output_dim)\n            fc3 = brew.fc(m, fc2, \"fc3\", dim_in=output_dim, dim_out=output_dim)\n            fc4 = brew.fc(m, fc3, \"fc4\", dim_in=output_dim, dim_out=output_dim)\n            fc5 = brew.fc(m, fc4, \"fc5\", dim_in=output_dim, dim_out=output_dim)\n            fc5.Relu([], fc5)\\\n               .Softmax([], \"pred\") \\\n               .LabelCrossEntropy([\"label\"], [\"xent\"]) \\\n               .AveragedLoss([], \"loss\")\n        input_to_grad = m.AddGradientOperators([\"name_x/loss\"])\n\n        blobs_before = count_blobs(m.net.Proto())\n        optim_proto = memonger.share_grad_blobs(\n            m.net,\n            [\"name_x/loss\"],\n            set(viewvalues(m.param_to_grad)),\n            \"name_x/\",\n            share_activations=False,\n        )\n        blobs_after = count_blobs(optim_proto)\n        self.assertLess(blobs_after, blobs_before)\n\n        optim_proto_wacts = memonger.share_grad_blobs(\n            m.net,\n            [\"name_x/loss\"],\n            set(viewvalues(m.param_to_grad)),\n            \"name_x/\",\n            share_activations=True,\n            dont_share_blobs=set([str(input_to_grad[\"name_x/fc1_w\"])]),\n        )\n        blobs_wact_optim = count_blobs(optim_proto_wacts)\n        self.assertLessEqual(blobs_wact_optim, blobs_after)\n\n        # Check that the last activations are not shared\n        self.assertTrue(has_blob(optim_proto, \"name_x/fc5\"))\n        self.assertTrue(\n            has_blob(optim_proto_wacts, \"name_x/fc5\"),\n            \"Dont remap final activation\",\n        )\n\n        # Test networks produce exactly same gradients\n        data = np.random.randn(batch_size, input_dim).astype(np.float32)\n        label = np.random.randint(\n            low=0, high=output_dim, size=(batch_size,)).astype(np.int32)\n        workspace.RunNetOnce(m.param_init_net)\n        workspace.FeedBlob(\"name_x/data\", data)\n        workspace.FeedBlob(\"name_x/label\", label)\n        workspace.RunNetOnce(m.net)\n        loss = workspace.FetchBlob(\"name_x/loss\")\n        grad = workspace.FetchBlob(str(input_to_grad[\"name_x/fc1_w\"]))\n        workspace.RunNetOnce(optim_proto)\n        optimized_loss = workspace.FetchBlob(\"name_x/loss\")\n        optimized_grad = workspace.FetchBlob(str(input_to_grad[\"name_x/fc1_w\"]))\n        np.testing.assert_almost_equal(loss, optimized_loss)\n        np.testing.assert_almost_equal(grad, optimized_grad)\n\n        workspace.FeedBlob(str(input_to_grad[\"name_x/fc1_w\"]), np.array([0.0]))\n\n        # Run with the forward optimization\n        workspace.RunNetOnce(optim_proto_wacts)\n        optimized_loss = workspace.FetchBlob(\"name_x/loss\")\n        optimized_grad = workspace.FetchBlob(str(input_to_grad[\"name_x/fc1_w\"]))\n        np.testing.assert_almost_equal(loss, optimized_loss)\n        np.testing.assert_almost_equal(grad, optimized_grad)\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support.\")\n    def test_memonger_mix_cpu_gpu(self):\n        '''\n        Check that memonger does not make blobs cross CPU/GPU boundary\n        '''\n        m = model_helper.ModelHelper()\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CUDA, 0)):\n            fc1 = brew.fc(m, \"data\", \"fc1\", dim_in=2, dim_out=2)\n            fc2 = brew.fc(m, fc1, \"fc2\", dim_in=2, dim_out=2)\n            fc3 = brew.fc(m, fc2, \"fc3\", dim_in=2, dim_out=2)\n            fc4 = brew.fc(m, fc3, \"fc4\", dim_in=2, dim_out=2)\n            fc4_cpu = m.net.CopyGPUToCPU(fc4, \"fc4_cpu\")\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU, 0)):\n            fc5_cpu = brew.fc(m, fc4_cpu, \"fc5_cpu\", dim_in=2, dim_out=2)\n            fc6_cpu = brew.fc(m, fc5_cpu, \"fc6_cpu\", dim_in=2, dim_out=2)\n            fc7_cpu = brew.fc(m, fc6_cpu, \"fc7_cpu\", dim_in=2, dim_out=2)\n            fc7_cpu.Relu([], fc7_cpu) \\\n               .Softmax([], \"pred\") \\\n               .LabelCrossEntropy([\"label\"], [\"xent\"]) \\\n               .AveragedLoss([], \"loss\")\n        m.AddGradientOperators([\"loss\"])\n\n        blobs_before = count_blobs(m.net.Proto())\n        optim_proto = memonger.share_grad_blobs(\n            m.net,\n            [\"loss\"],\n            set(viewvalues(m.param_to_grad)),\n            \"\",\n            share_activations=True,\n            dont_share_blobs=set(),\n        )\n        blobs_after = count_blobs(optim_proto)\n        self.assertLess(blobs_after, blobs_before)\n\n        # Create set of blobs on CPU side and GPU side and check they don't\n        # overlap\n        device_blobs = {caffe2_pb2.CPU: set(), caffe2_pb2.CUDA: set()}\n        for op in optim_proto.op:\n            if op.type not in ['CopyCPUToGPU', \"CopyGPUToCPU\"]:\n                dev = op.device_option.device_type\n                for b in list(op.input) + list(op.output):\n                    device_blobs[dev].add(b)\n\n        device_crossers = device_blobs[caffe2_pb2.CPU].intersection(\n            device_blobs[caffe2_pb2.CUDA]\n        )\n        self.assertEquals(device_crossers, set())\n\n    @given(input_dim=st.integers(min_value=4, max_value=4),\n           output_dim=st.integers(min_value=4, max_value=4),\n           batch_size=st.integers(min_value=4, max_value=4))\n    def test_gradient_optim_tree(self, input_dim, output_dim, batch_size):\n        m = model_helper.ModelHelper()\n        with core.NameScope(\"name_x\"):\n            fc1 = brew.fc(m, \"data\", \"fc1\", dim_in=input_dim, dim_out=output_dim)\n            fc2 = brew.fc(m, fc1, \"fc2\", dim_in=output_dim, dim_out=output_dim)\n            fc3 = brew.fc(m, fc2, \"fc3\", dim_in=output_dim, dim_out=output_dim)\n            fc4 = brew.fc(m, fc3, \"fc4\", dim_in=output_dim, dim_out=output_dim)\n            fc5 = brew.fc(m, fc4, \"fc5\", dim_in=output_dim, dim_out=output_dim)\n            fc5.Relu([], fc5) \\\n               .Softmax([], \"pred1\") \\\n               .LabelCrossEntropy([\"label\"], [\"xent1\"]) \\\n               .AveragedLoss([], \"loss1\")\n            fc6 = brew.fc(m, fc5, \"fc6\", dim_in=output_dim, dim_out=output_dim)\n            fc6.Relu([], fc6) \\\n               .Softmax([], \"pred2\") \\\n               .LabelCrossEntropy([\"label\"], [\"xent2\"]) \\\n               .AveragedLoss([], \"loss2\")\n        input_to_grad = m.AddGradientOperators([\"name_x/loss1\", \"name_x/loss2\"])\n\n        blobs_before = count_blobs(m.net.Proto())\n        optim_proto = memonger.share_grad_blobs(\n            m.net,\n            [\"name_x/loss1\", \"name_x/loss2\"],\n            set(viewvalues(m.param_to_grad)),\n            \"name_x\",  # \"name_x//shared_gradinp_0_shared\" if using \"name_x/\"\n            share_activations=True,\n            dont_share_blobs=set(['name_x/fc6', 'name_x/fc5',\n                                   str(input_to_grad[\"name_x/fc1_w\"])]),\n        )\n        blobs_after = count_blobs(optim_proto)\n        self.assertLess(blobs_after, blobs_before)\n        self.assertTrue(has_blob(optim_proto, \"name_x/fc6\"))\n\n        # Test networks produce exactly same gradients\n        data = np.random.randn(batch_size, input_dim).astype(np.float32)\n        label = np.random.randint(\n            low=0, high=output_dim, size=(batch_size,)).astype(np.int32)\n        workspace.RunNetOnce(m.param_init_net)\n        workspace.FeedBlob(\"name_x/data\", data)\n        workspace.FeedBlob(\"name_x/label\", label)\n        workspace.RunNetOnce(m.net)\n        loss1 = workspace.FetchBlob(\"name_x/loss1\")\n        loss2 = workspace.FetchBlob(\"name_x/loss2\")\n        grad = workspace.FetchBlob(str(input_to_grad[\"name_x/fc1_w\"]))\n        workspace.FeedBlob(str(input_to_grad[\"name_x/fc1_w\"]), np.array([0.0]))\n\n        workspace.RunNetOnce(optim_proto)\n        optimized_loss1 = workspace.FetchBlob(\"name_x/loss1\")\n        optimized_loss2 = workspace.FetchBlob(\"name_x/loss2\")\n        optimized_grad = workspace.FetchBlob(str(input_to_grad[\"name_x/fc1_w\"]))\n        np.testing.assert_almost_equal(loss1, optimized_loss1)\n        np.testing.assert_almost_equal(loss2, optimized_loss2)\n        np.testing.assert_almost_equal(grad, optimized_grad)\n\n    @given(input_dim=st.integers(min_value=4, max_value=4),\n           output_dim=st.integers(min_value=4, max_value=4),\n           batch_size=st.integers(min_value=4, max_value=4))\n    def test_forward_optim_tree_daggy(self, input_dim, output_dim, batch_size):\n        m = model_helper.ModelHelper()\n        m.Proto().type = \"dag\"\n        m.Proto().num_workers = 4\n\n        with core.NameScope(\"name_x\"):\n            fc1 = brew.fc(m, \"data\", \"fc1\", dim_in=input_dim, dim_out=output_dim)\n            fc2 = brew.fc(m, fc1, \"fc2\", dim_in=output_dim, dim_out=output_dim)\n\n            fc3 = brew.fc(m, fc2, \"fc3\", dim_in=output_dim, dim_out=output_dim)\n            fc4 = brew.fc(m, fc3, \"fc4\", dim_in=output_dim, dim_out=output_dim)\n            fc5 = brew.fc(m, fc4, \"fc5\", dim_in=output_dim, dim_out=output_dim)\n\n            # Branch\n            fc3b = brew.fc(m, fc2, \"fc3b\", dim_in=output_dim, dim_out=output_dim)\n            fc4b = brew.fc(m, fc3b, \"fc4b\", dim_in=output_dim, dim_out=output_dim)\n            fc5b = brew.fc(m, fc4b, \"fc5b\", dim_in=output_dim, dim_out=output_dim)\n\n            fc5sum = brew.sum(m, [fc5, fc5b], \"fc5sum\")\n\n            fc5.Relu([], fc5sum) \\\n               .Softmax([], \"pred1\") \\\n               .LabelCrossEntropy([\"label\"], [\"xent1\"]) \\\n               .AveragedLoss([], \"loss1\")\n            fc6 = brew.fc(m, fc5, \"fc6\", dim_in=output_dim, dim_out=output_dim)\n            fc6.Relu([], fc6) \\\n               .Softmax([], \"pred2\") \\\n               .LabelCrossEntropy([\"label\"], [\"xent2\"]) \\\n               .AveragedLoss([], \"loss2\")\n\n        blobs_before = count_blobs(m.net.Proto())\n        optim_proto = memonger.optimize_inference_for_dag(\n            m.net, [\"name_x/data\"], \"name_x\"\n        )\n        blobs_after = count_blobs(optim_proto)\n        self.assertLess(blobs_after, blobs_before)\n\n        # Test networks produce exactly same results\n        data = np.random.randn(batch_size, input_dim).astype(np.float32)\n        label = np.random.randint(\n            low=0, high=output_dim, size=(batch_size,)).astype(np.int32)\n        workspace.RunNetOnce(m.param_init_net)\n        workspace.FeedBlob(\"name_x/data\", data)\n        workspace.FeedBlob(\"name_x/label\", label)\n        workspace.RunNetOnce(m.net)\n        loss1 = workspace.FetchBlob(\"name_x/loss1\")\n        loss2 = workspace.FetchBlob(\"name_x/loss2\")\n        workspace.RunNetOnce(optim_proto)\n        optimized_loss1 = workspace.FetchBlob(\"name_x/loss1\")\n        optimized_loss2 = workspace.FetchBlob(\"name_x/loss2\")\n        np.testing.assert_almost_equal(loss1, optimized_loss1)\n        np.testing.assert_almost_equal(loss2, optimized_loss2)\n\n    @given(input_dim=st.integers(min_value=4, max_value=4),\n           output_dim=st.integers(min_value=4, max_value=4),\n           batch_size=st.integers(min_value=4, max_value=4))\n    def test_forward_optim_tree_harder(self, input_dim, output_dim, batch_size):\n        m = model_helper.ModelHelper()\n        m.net.Proto().type = \"dag\"\n        m.net.Proto().num_workers = 4\n        m.net.AddExternalInput(\"label\")\n        m.net.AddExternalInput(\"data\")\n\n        with core.NameScope(\"name_x\"):\n            fc1 = brew.fc(m, \"data\", \"fc1\", dim_in=input_dim, dim_out=output_dim)\n            fc2 = brew.fc(m, fc1, \"fc2\", dim_in=output_dim, dim_out=output_dim)\n\n            fc3 = brew.fc(m, fc2, \"fc3\", dim_in=output_dim, dim_out=output_dim)\n            fc4 = brew.fc(m, fc3, \"fc4\", dim_in=output_dim, dim_out=output_dim)\n            fc5 = brew.fc(m, fc4, \"fc5\", dim_in=output_dim, dim_out=output_dim)\n\n            # Branch\n            fc3b = brew.fc(m, fc2, \"fc3b\", dim_in=output_dim, dim_out=output_dim)\n            fc4b = brew.fc(m, fc3b, \"fc4b\", dim_in=output_dim, dim_out=output_dim)\n            fc5b = brew.fc(m, fc4b, \"fc5b\", dim_in=output_dim, dim_out=output_dim)\n\n            fc5sum = brew.sum(m, [fc5, fc5b], \"fc5sum\")\n            fc5sum.Relu([], \"relu1\") \\\n               .Softmax([], \"pred1\") \\\n               .LabelCrossEntropy([\"label\"], [\"xent1\"]) \\\n               .AveragedLoss([], \"loss1\")\n            fc6 = brew.fc(m, fc5, \"fc6\", dim_in=output_dim, dim_out=output_dim)\n            fc6.Relu([], fc6) \\\n               .Softmax([], \"pred2\") \\\n               .LabelCrossEntropy([\"label\"], [\"xent2\"]) \\\n               .AveragedLoss([], \"loss2\")\n\n        blobs_before = count_blobs(m.net.Proto())\n        optim_proto = memonger.optimize_inference_for_dag(\n            m.net, [\"name_x/data\"], \"name_x/\"\n        )\n\n        blobs_after = count_blobs(optim_proto)\n\n        # Extra test with when one of the parameters is also an input.\n        # This caused a bug before.\n        optim_proto_extra_input = memonger.optimize_inference_for_dag(\n            m.net, [\"name_x/data\", \"name_x/fc1_w\"], \"name_x/\"\n        )\n        blobs_after_extra_input = count_blobs(optim_proto_extra_input)\n        self.assertEqual(blobs_after, blobs_after_extra_input)\n        ###\n\n        print(str(optim_proto))\n        self.assertLess(blobs_after, blobs_before)\n\n        # Test networks produce exactly same results\n        data = np.random.randn(batch_size, input_dim).astype(np.float32)\n        label = np.random.randint(\n            low=0, high=output_dim, size=(batch_size,)).astype(np.int32)\n        workspace.RunNetOnce(m.param_init_net)\n        workspace.FeedBlob(\"name_x/data\", data)\n        workspace.FeedBlob(\"name_x/label\", label)\n        workspace.RunNetOnce(m.net)\n        loss1 = workspace.FetchBlob(\"name_x/loss1\")\n        loss2 = workspace.FetchBlob(\"name_x/loss2\")\n        workspace.RunNetOnce(optim_proto)\n        optimized_loss1 = workspace.FetchBlob(\"name_x/loss1\")\n        optimized_loss2 = workspace.FetchBlob(\"name_x/loss2\")\n        np.testing.assert_almost_equal(loss1, optimized_loss1)\n        np.testing.assert_almost_equal(loss2, optimized_loss2)\n\n    def test_rnn(self):\n        from caffe2.python import rnn_cell\n        T = 5\n        model = model_helper.ModelHelper()\n        seq_lengths, labels = \\\n            model.net.AddExternalInputs(\n                'seq_lengths', 'labels',\n            )\n        init_blobs = []\n        for i in range(2):\n            hidden_init, cell_init = model.net.AddExternalInputs(\n                \"hidden_init_{}\".format(i),\n                \"cell_init_{}\".format(i)\n            )\n            init_blobs.extend([hidden_init, cell_init])\n        model.param_init_net.ConstantFill([], [\"input\"], shape=[T, 4, 10])\n        output, last_hidden, _, last_state = rnn_cell.LSTM(\n            model=model,\n            input_blob=\"input\",\n            seq_lengths=seq_lengths,\n            initial_states=init_blobs,\n            dim_in=10,\n            dim_out=[10, 10],\n            scope=\"lstm1\",\n            forward_only=False,\n            drop_states=True,\n            return_last_layer_only=True,\n        )\n        softmax, loss = model.net.SoftmaxWithLoss(\n            [model.Flatten(output), \"labels\"],\n            ['softmax', 'loss'],\n        )\n\n        model.AddGradientOperators([loss])\n        blobs_before = count_blobs(model.net.Proto())\n        optim_proto = memonger.share_grad_blobs(\n            model.net,\n            [\"loss\"],\n            set(viewvalues(model.param_to_grad)),\n            \"\",\n            share_activations=True,\n            dont_share_blobs=set(),\n        )\n        blobs_after = count_blobs(optim_proto)\n        self.assertLess(blobs_after, blobs_before)\n\n        # Run once to see all blobs are set up correctly\n        for init_blob in init_blobs:\n            workspace.FeedBlob(init_blob, np.zeros(\n                [1, 4, 10], dtype=np.float32\n            ))\n        workspace.FeedBlob(\"seq_lengths\", np.array([T] * 4, dtype=np.int32))\n        workspace.FeedBlob(\"labels\", np.random.rand(T).astype(np.int32))\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n\n    def test_compute_interference_graph_inplace_ops(self):\n        m = model_helper.ModelHelper()\n        m.Copy(\"b1\", \"b1\")\n        m.Copy(\"b1\", \"b1\")\n        m.Copy(\"b1\", \"b1\")\n        g = memonger.compute_interference_graph(m.net.Proto().op)\n        self.assertEqual(list(g.edges()), [(0, 1), (0, 2), (1, 2)])\n\n    def test_topological_sort_longest_path(self):\n        m = model_helper.ModelHelper()\n        # 0\n        m.Copy(\"conv0_w_comp\", \"conv0_w\")\n        # 1\n        conv0 = brew.conv(m, \"data\", \"conv0\", 32, 32, 4)\n        # 2\n        m.Copy(\"conv2_w\", \"conv2_w\")\n        # 3\n        brew.conv(m, conv0, \"conv2\", 16, 32, 4)\n\n        g = memonger.compute_interference_graph(m.net.Proto().op)\n\n        orders_org = memonger.topological_sort_traversal(g)\n        orders_gt_org = [2, 0, 1, 3]\n        self.assertEqual(orders_gt_org, list(orders_org))\n\n        orders = memonger.topological_sort_traversal_longest_path(g)\n        # longer path is in front of the shorter one\n        orders_gt = [0, 1, 2, 3]\n        self.assertEqual(orders_gt, list(orders))\n\n    def test_topological_sort_longest_path_multi_target(self):\n        # two outputs: conv2 and data4\n        m = model_helper.ModelHelper()\n        # 0\n        m.Copy(\"conv0_w_comp\", \"conv0_w\")\n        # 1\n        conv0 = brew.conv(m, \"data\", \"conv0\", 32, 32, 4)\n        # 2\n        m.Copy(\"conv2_w\", \"conv2_w\")\n        # 3\n        brew.conv(m, conv0, \"conv2\", 16, 32, 4)\n        # 4\n        m.Copy(\"data1\", \"data2\")\n        # 5\n        m.Copy(\"data2\", \"data3\")\n\n        g = memonger.compute_interference_graph(m.net.Proto().op)\n\n        orders_org = memonger.topological_sort_traversal(g)\n        orders_gt_org = [4, 5, 2, 0, 1, 3]\n        self.assertEqual(orders_gt_org, list(orders_org))\n\n        orders = memonger.topological_sort_traversal_longest_path(g)\n        # longer path is in front of the shorter one\n        orders_gt = [0, 1, 2, 3, 4, 5]\n        self.assertEqual(orders_gt, list(orders))\n\n    def test_topological_sort_longest_path_single_node(self):\n        # single node\n        m = model_helper.ModelHelper()\n        # 0\n        m.Copy(\"conv0_w_comp\", \"conv0_w\")\n\n        g = memonger.compute_interference_graph(m.net.Proto().op)\n\n        orders_org = memonger.topological_sort_traversal(g)\n        orders_gt_org = [0]\n        self.assertEqual(orders_gt_org, list(orders_org))\n\n        orders = memonger.topological_sort_traversal_longest_path(g)\n        # longer path is in front of the shorter one\n        orders_gt = [0]\n        self.assertEqual(orders_gt, list(orders))\n\n    def test_compute_assignments_greedy(self):\n        LiveRange = memonger.LiveRange\n        ranges_sorted = [\n            ('b1', LiveRange(1, 3, 10)),\n            ('b2', LiveRange(3, 4, 1)),\n            ('b3', LiveRange(5, 6, 1)),\n            ('b4', LiveRange(5, 7, 10)),\n        ]\n        assignment_gt = [\n            [ranges_sorted[0], ranges_sorted[3]],\n            [ranges_sorted[1], ranges_sorted[2]],\n        ]\n\n        best = memonger.compute_assignments_greedy(ranges_sorted, None)\n        self.assertEqual(memonger.get_memory_usage(best), 11)\n        self.assertEqual(best, assignment_gt)\n\n    def test_compute_assignments_dp(self):\n        LiveRange = memonger.LiveRange\n        ranges_sorted = [\n            ('b1', LiveRange(1, 3, 10)),\n            ('b2', LiveRange(3, 4, 1)),\n            ('b3', LiveRange(5, 6, 1)),\n            ('b4', LiveRange(5, 7, 10)),\n        ]\n\n        best = memonger.compute_assignments_dp(ranges_sorted, None)\n        self.assertEqual(memonger.get_memory_usage(best), 11)\n\n    def test_compute_assignments_dp1(self):\n        LiveRange = memonger.LiveRange\n        ranges_sorted = [\n            ('b1', LiveRange(1, 2, 10)),\n            ('b2', LiveRange(4, 6, 1)),\n            ('b3', LiveRange(5, 6, 10)),\n        ]\n\n        best = memonger.compute_assignments_dp(ranges_sorted, [])\n        self.assertEqual(memonger.get_memory_usage(best), 11)\n\n    @given(input_dim=st.integers(min_value=4, max_value=4),\n           output_dim=st.integers(min_value=4, max_value=4),\n           batch_size=st.integers(min_value=4, max_value=4))\n    def test_verify_graph_equality(self, input_dim, output_dim, batch_size):\n        m = model_helper.ModelHelper()\n        m.Proto().type = \"dag\"\n        m.Proto().num_workers = 4\n        with core.NameScope(\"name_x\"):\n            fc1 = brew.fc(m, \"data\", \"x\", dim_in=input_dim, dim_out=output_dim)\n            fc2 = brew.fc(m, fc1, \"y\", dim_in=output_dim, dim_out=output_dim)\n            fc3 = brew.fc(m, fc1, \"z\", dim_in=output_dim, dim_out=output_dim)\n            brew.sum(m, [fc2, fc3], \"out\")\n\n        m2 = model_helper.ModelHelper()\n        m2.Proto().type = \"dag\"\n        m2.Proto().num_workers = 4\n        with core.NameScope(\"name_x\"):\n            fc1 = brew.fc(m2, \"data\", \"other_x\", dim_in=input_dim, dim_out=output_dim)\n            fc2 = brew.fc(m2, fc1, \"other_y\", dim_in=output_dim, dim_out=output_dim)\n            fc3 = brew.fc(m2, fc1, \"other_z\", dim_in=output_dim, dim_out=output_dim)\n            brew.sum(m2, [fc2, fc3], \"out\")\n\n        self.assertTrue(memonger.verify_graph_equality(m.net.Proto(), m2.net.Proto()))\n\n    @given(input_dim=st.integers(min_value=4, max_value=4),\n           output_dim=st.integers(min_value=4, max_value=4),\n           batch_size=st.integers(min_value=4, max_value=4))\n    def test_verify_graph_equality_harder(self, input_dim, output_dim, batch_size):\n        m = model_helper.ModelHelper()\n        m.Proto().type = \"dag\"\n        m.Proto().num_workers = 4\n        with core.NameScope(\"name_x\"):\n            fc1 = brew.fc(m, \"data\", \"x\", dim_in=input_dim, dim_out=output_dim)\n            fc2a = brew.fc(m, fc1, \"y\", dim_in=output_dim, dim_out=output_dim)\n            fc2b = brew.fc(m, fc1, \"z\", dim_in=output_dim, dim_out=output_dim)\n            fc3a = brew.fc(m, fc2a, \"u\", dim_in=output_dim, dim_out=output_dim)\n            fc3b = brew.fc(m, fc2b, \"v\", dim_in=output_dim, dim_out=output_dim)\n            brew.sum(m, [fc3a, fc3b], \"out\")\n\n        m2 = model_helper.ModelHelper()\n        m2.Proto().type = \"dag\"\n        m2.Proto().num_workers = 4\n        with core.NameScope(\"name_x\"):\n            fc1 = brew.fc(m2, \"data\", \"x\", dim_in=input_dim, dim_out=output_dim)\n            fc2a = brew.fc(m2, fc1, \"y\", dim_in=output_dim, dim_out=output_dim)\n            fc2b = brew.fc(m2, fc1, \"z\", dim_in=output_dim, dim_out=output_dim)\n            fc3a = brew.fc(m2, fc2a, \"y\", dim_in=output_dim, dim_out=output_dim)\n            fc3b = brew.fc(m2, fc2b, \"z\", dim_in=output_dim, dim_out=output_dim)\n            brew.sum(m2, [fc3a, fc3b], \"out\")\n\n        self.assertTrue(memonger.verify_graph_equality(m.net.Proto(), m2.net.Proto()))\n\n    @given(input_dim=st.integers(min_value=4, max_value=4),\n           output_dim=st.integers(min_value=4, max_value=4),\n           batch_size=st.integers(min_value=4, max_value=4))\n    def test_verify_graph_inequality(self, input_dim, output_dim, batch_size):\n        m = model_helper.ModelHelper()\n        m.Proto().type = \"dag\"\n        m.Proto().num_workers = 4\n        with core.NameScope(\"name_x\"):\n            fc1 = brew.fc(m, \"data\", \"x\", dim_in=input_dim, dim_out=output_dim)\n            fc2 = brew.fc(m, fc1, \"y\", dim_in=output_dim, dim_out=output_dim)\n            fc3 = brew.fc(m, fc1, \"z\", dim_in=output_dim, dim_out=output_dim)\n            brew.sum(m, [fc2, fc3], \"out\")\n\n        m2 = model_helper.ModelHelper()\n        m2.Proto().type = \"dag\"\n        m2.Proto().num_workers = 4\n        with core.NameScope(\"name_x\"):\n            fc1 = brew.fc(m2, \"data\", \"x\", dim_in=input_dim, dim_out=output_dim)\n            fc2 = brew.fc(m2, fc1, \"y\", dim_in=output_dim, dim_out=output_dim)\n            fc3 = brew.fc(m2, fc1, \"y\", dim_in=output_dim, dim_out=output_dim)\n            brew.sum(m2, [fc2, fc3], \"out\")\n\n        self.assertFalse(memonger.verify_graph_equality(m.net.Proto(), m2.net.Proto()))\n\n    @given(input_dim=st.integers(min_value=4, max_value=4),\n           output_dim=st.integers(min_value=4, max_value=4),\n           batch_size=st.integers(min_value=4, max_value=4))\n    def test_verify_graph_inequality_harder(self, input_dim, output_dim, batch_size):\n        m = model_helper.ModelHelper()\n        m.Proto().type = \"dag\"\n        m.Proto().num_workers = 4\n        with core.NameScope(\"name_x\"):\n            fc1 = brew.fc(m, \"data\", \"x\", dim_in=input_dim, dim_out=output_dim)\n            fc2a = brew.fc(m, fc1, \"y\", dim_in=output_dim, dim_out=output_dim)\n            fc2b = brew.fc(m, fc1, \"z\", dim_in=output_dim, dim_out=output_dim)\n            fc3a = brew.fc(m, fc2a, \"u\", dim_in=output_dim, dim_out=output_dim)\n            fc3b = brew.fc(m, fc2b, \"v\", dim_in=output_dim, dim_out=output_dim)\n            brew.sum(m, [fc3a, fc3b], \"out\")\n\n        m2 = model_helper.ModelHelper()\n        m2.Proto().type = \"dag\"\n        m2.Proto().num_workers = 4\n        with core.NameScope(\"name_x\"):\n            fc1 = brew.fc(m2, \"data\", \"x\", dim_in=input_dim, dim_out=output_dim)\n            fc2a = brew.fc(m2, fc1, \"y\", dim_in=output_dim, dim_out=output_dim)\n            fc2b = brew.fc(m2, fc1, \"y\", dim_in=output_dim, dim_out=output_dim)\n            fc3a = brew.fc(m2, fc2a, \"u\", dim_in=output_dim, dim_out=output_dim)\n            fc3b = brew.fc(m2, fc2b, \"v\", dim_in=output_dim, dim_out=output_dim)\n            brew.sum(m2, [fc3a, fc3b], \"out\")\n\n        self.assertFalse(memonger.verify_graph_equality(m.net.Proto(), m2.net.Proto()))\n\n    def test_release_blobs_when_used(self):\n        m = model_helper.ModelHelper()\n        fc1 = brew.fc(m, \"data\", \"x\", dim_in=2, dim_out=2)\n        fc2 = brew.fc(m, fc1, \"y\", dim_in=2, dim_out=2)\n        fc3 = brew.fc(m, fc1, \"z\", dim_in=2, dim_out=2)\n        fc4 = brew.fc(m, fc2, \"u\", dim_in=2, dim_out=2)\n        m.net.Alias([\"u\"], [\"u_alias\"])\n\n        brew.sum(m, [fc3, fc4], \"out\")\n\n        with_frees = memonger.release_blobs_when_used(m.net.Proto(), set(\"data\"))\n\n        expect_frees = {\"x\", \"y\", \"z\"}  # out is external output\n                                        # and u is aliased so cannot be freed\n        found_frees = set()\n        for op in with_frees.op:\n            if op.type == \"Free\":\n                self.assertFalse(op.input[0] in found_frees)  # no double frees\n                found_frees.add(op.input[0])\n            else:\n                # Check a freed blob is not used anymore\n                for inp in op.input:\n                    self.assertFalse(inp in found_frees)\n                for outp in op.output:\n                    self.assertFalse(outp in found_frees)\n\n        self.assertEqual(expect_frees, found_frees)\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mint/app.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package app\n# Module caffe2.python.mint.app\nimport argparse\nimport flask\nimport glob\nimport numpy as np\nimport nvd3\nimport os\nimport sys\nimport tornado.httpserver\nimport tornado.wsgi\n\n__folder__ = os.path.abspath(os.path.dirname(__file__))\n\napp = flask.Flask(\n    __name__,\n    template_folder=os.path.join(__folder__, \"templates\"),\n    static_folder=os.path.join(__folder__, \"static\")\n)\nargs = None\n\n\ndef jsonify_nvd3(chart):\n    chart.buildcontent()\n    # Note(Yangqing): python-nvd3 does not seem to separate the built HTML part\n    # and the script part. Luckily, it seems to be the case that the HTML part is\n    # only a <div>, which can be accessed by chart.container; the script part,\n    # while the script part occupies the rest of the html content, which we can\n    # then find by chart.htmlcontent.find['<script>'].\n    script_start = chart.htmlcontent.find('<script>') + 8\n    script_end = chart.htmlcontent.find('</script>')\n    return flask.jsonify(\n        result=chart.container,\n        script=chart.htmlcontent[script_start:script_end].strip()\n    )\n\n\ndef visualize_summary(filename):\n    try:\n        data = np.loadtxt(filename)\n    except Exception as e:\n        return 'Cannot load file {}: {}'.format(filename, str(e))\n    chart_name = os.path.splitext(os.path.basename(filename))[0]\n    chart = nvd3.lineChart(\n        name=chart_name + '_summary_chart',\n        height=args.chart_height,\n        y_axis_format='.03g'\n    )\n    if args.sample < 0:\n        step = max(data.shape[0] / -args.sample, 1)\n    else:\n        step = args.sample\n    xdata = np.arange(0, data.shape[0], step)\n    # data should have 4 dimensions.\n    chart.add_serie(x=xdata, y=data[xdata, 0], name='min')\n    chart.add_serie(x=xdata, y=data[xdata, 1], name='max')\n    chart.add_serie(x=xdata, y=data[xdata, 2], name='mean')\n    chart.add_serie(x=xdata, y=data[xdata, 2] + data[xdata, 3], name='m+std')\n    chart.add_serie(x=xdata, y=data[xdata, 2] - data[xdata, 3], name='m-std')\n    return jsonify_nvd3(chart)\n\n\ndef visualize_print_log(filename):\n    try:\n        data = np.loadtxt(filename)\n        if data.ndim == 1:\n            data = data[:, np.newaxis]\n    except Exception as e:\n        return 'Cannot load file {}: {}'.format(filename, str(e))\n    chart_name = os.path.splitext(os.path.basename(filename))[0]\n    chart = nvd3.lineChart(\n        name=chart_name + '_log_chart',\n        height=args.chart_height,\n        y_axis_format='.03g'\n    )\n    if args.sample < 0:\n        step = max(data.shape[0] / -args.sample, 1)\n    else:\n        step = args.sample\n    xdata = np.arange(0, data.shape[0], step)\n    # if there is only one curve, we also show the running min and max\n    if data.shape[1] == 1:\n        # We also print the running min and max for the steps.\n        trunc_size = data.shape[0] / step\n        running_mat = data[:trunc_size * step].reshape((trunc_size, step))\n        chart.add_serie(\n            x=xdata[:trunc_size],\n            y=running_mat.min(axis=1),\n            name='running_min'\n        )\n        chart.add_serie(\n            x=xdata[:trunc_size],\n            y=running_mat.max(axis=1),\n            name='running_max'\n        )\n        chart.add_serie(x=xdata, y=data[xdata, 0], name=chart_name)\n    else:\n        for i in range(0, min(data.shape[1], args.max_curves)):\n            # data should have 4 dimensions.\n            chart.add_serie(\n                x=xdata,\n                y=data[xdata, i],\n                name='{}[{}]'.format(chart_name, i)\n            )\n\n    return jsonify_nvd3(chart)\n\n\ndef visualize_file(filename):\n    fullname = os.path.join(args.root, filename)\n    if filename.endswith('summary'):\n        return visualize_summary(fullname)\n    elif filename.endswith('log'):\n        return visualize_print_log(fullname)\n    else:\n        return flask.jsonify(\n            result='Unsupport file: {}'.format(filename),\n            script=''\n        )\n\n\n@app.route('/')\ndef index():\n    files = glob.glob(os.path.join(args.root, \"*.*\"))\n    files.sort()\n    names = [os.path.basename(f) for f in files]\n    return flask.render_template(\n        'index.html',\n        root=args.root,\n        names=names,\n        debug_messages=names\n    )\n\n\n@app.route('/visualization/<string:name>')\ndef visualization(name):\n    ret = visualize_file(name)\n    return ret\n\n\ndef main(argv):\n    parser = argparse.ArgumentParser(\"The mint visualizer.\")\n    parser.add_argument(\n        '-p',\n        '--port',\n        type=int,\n        default=5000,\n        help=\"The flask port to use.\"\n    )\n    parser.add_argument(\n        '-r',\n        '--root',\n        type=str,\n        default='.',\n        help=\"The root folder to read files for visualization.\"\n    )\n    parser.add_argument(\n        '--max_curves',\n        type=int,\n        default=5,\n        help=\"The max number of curves to show in a dump tensor.\"\n    )\n    parser.add_argument(\n        '--chart_height',\n        type=int,\n        default=300,\n        help=\"The chart height for nvd3.\"\n    )\n    parser.add_argument(\n        '-s',\n        '--sample',\n        type=int,\n        default=-200,\n        help=\"Sample every given number of data points. A negative \"\n        \"number means the total points we will sample on the \"\n        \"whole curve. Default 100 points.\"\n    )\n    global args\n    args = parser.parse_args(argv)\n    server = tornado.httpserver.HTTPServer(tornado.wsgi.WSGIContainer(app))\n    server.listen(args.port)\n    print(\"Tornado server starting on port {}.\".format(args.port))\n    tornado.ioloop.IOLoop.instance().start()\n\n\nif __name__ == '__main__':\n    main(sys.argv[1:])\n"
  },
  {
    "path": "caffe2/python/mint/static/css/simple-sidebar.css",
    "content": "/*!\n * Start Bootstrap - Simple Sidebar HTML Template (http://startbootstrap.com)\n * Code licensed under the Apache License v2.0.\n * For details, see http://www.apache.org/licenses/LICENSE-2.0.\n */\n\n/* Toggle Styles */\n\n#wrapper {\n    padding-left: 0;\n    -webkit-transition: all 0.5s ease;\n    -moz-transition: all 0.5s ease;\n    -o-transition: all 0.5s ease;\n    transition: all 0.5s ease;\n}\n\n#wrapper.toggled {\n    padding-left: 250px;\n}\n\n#sidebar-wrapper {\n    z-index: 1000;\n    position: fixed;\n    left: 250px;\n    width: 0;\n    height: 100%;\n    margin-left: -250px;\n    overflow-y: auto;\n    background: rgb(193,237,201);\n    -webkit-transition: all 0.5s ease;\n    -moz-transition: all 0.5s ease;\n    -o-transition: all 0.5s ease;\n    transition: all 0.5s ease;\n}\n\n#wrapper.toggled #sidebar-wrapper {\n    width: 250px;\n}\n\n#page-content-wrapper {\n    width: 100%;\n    position: absolute;\n    padding: 15px;\n}\n\n#wrapper.toggled #page-content-wrapper {\n    position: absolute;\n    margin-right: -250px;\n}\n\n/* Sidebar Styles */\n\n.sidebar-nav {\n    position: absolute;\n    top: 0;\n    width: 250px;\n    margin-bottom: 40px;\n    padding: 0;\n    list-style: none;\n}\n\n.sidebar-nav li {\n    text-indent: 20px;\n    line-height: 30px;\n}\n\n.sidebar-nav li a {\n    display: block;\n    text-decoration: none;\n    color: #999999;\n}\n\n.sidebar-nav li a:hover {\n    text-decoration: none;\n    color: #000;\n    background: rgba(255,255,255,0.8);\n}\n\n.sidebar-nav li a:active,\n.sidebar-nav li a:focus {\n    text-decoration: none;\n}\n\n.sidebar-nav > .sidebar-brand {\n    height: 65px;\n    font-size: 18px;\n    line-height: 60px;\n}\n\n.sidebar-nav > .sidebar-brand a {\n    color: #999999;\n}\n\n.sidebar-nav > .sidebar-brand a:hover {\n    color: #fff;\n    background: none;\n}\n\n@media(min-width:768px) {\n    #wrapper {\n        padding-left: 250px;\n    }\n\n    #wrapper.toggled {\n        padding-left: 0;\n    }\n\n    #sidebar-wrapper {\n        width: 250px;\n    }\n\n    #wrapper.toggled #sidebar-wrapper {\n        width: 0;\n    }\n\n    #page-content-wrapper {\n        padding: 20px;\n        position: relative;\n    }\n\n    #wrapper.toggled #page-content-wrapper {\n        position: relative;\n        margin-right: 0;\n    }\n}"
  },
  {
    "path": "caffe2/python/mint/templates/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <title>Mint</title>\n    <!-- Latest compiled and minified CSS -->\n    <link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css\">\n    <!--  NVD3 css -->\n    <link href=\"http://nvd3.org/assets/css/nv.d3.css\" type=\"text/css\" rel=\"stylesheet\"/>\n    <!-- static sidebar -->\n    <link href=\"{{ url_for('static', filename='css/simple-sidebar.css') }}\" type=\"text/css\" rel=\"stylesheet\"/>\n  </head>\n  <body>\n    <script src=\"http://nvd3.org/assets/lib/d3.v3.js\"></script>\n    <script src=\"http://nvd3.org/assets/js/nv.d3.js\"></script>\n    <!-- Simple Ajax content -->\n    <script>\n    function getCurrentTimeString() {\n      var now = new Date();\n      return now.toLocaleString();\n    }\n    function loadVisualization(content) {\n      var xmlhttp = new XMLHttpRequest();\n      xmlhttp.onreadystatechange=function() {\n        if (xmlhttp.readyState==4 && xmlhttp.status==200) {\n          var response = JSON.parse(xmlhttp.responseText);\n          document.getElementById(\"visualization_\" + content).innerHTML =\n              response['result'];\n          document.getElementById(\"visualization_\" + content + \"_time\").innerHTML =\n              getCurrentTimeString();\n          eval(response['script']);\n        }\n      }\n      document.getElementById(\"visualization_\" + content).innerHTML =\n          \"Loading...\";\n      xmlhttp.open(\"GET\", \"visualization/\" + content, true);\n      xmlhttp.send();\n    }\n    </script>\n\n    <div id=\"wrapper\">\n      <!-- Sidebar -->\n      <div id=\"sidebar-wrapper\">\n        <ul class=\"sidebar-nav\">\n          <li class=\"sidebar-brand\">\n            <h3><span class=\"glyphicon glyphicon-leaf\" aria-hidden=\"true\"></span>&nbspCaffe-Mint</h3>\n          </li>\n          <li><a href=\"#page-content-wrapper\"><strong>Top</strong></a></li>\n          {% for name in names %}\n            <li><a href=\"#visualization_{{name}}_panel\"> {{name}} </a></li>\n          {% endfor %}\n        </ul>\n      </div> <!-- /#sidebar-wrapper -->\n\n      <div id=\"page-content-wrapper\">\n        <p>\n          Visualizing folder: {{ root }}.<br/>\n          <a class=\"btn btn-default\" id=\"menu-toggle\">Toggle sidebar</a>\n          <a class=\"btn btn-default\" id=\"menu-toggle\" onclick=\"refreshAll()\">Refresh all</a>\n        </p>\n        <div role=\"tabpanel\">\n          <!-- Nav tabs -->\n          <ul class=\"nav nav-tabs\" role=\"tablist\">\n            <li role=\"presentation\" class=\"active\"><a href=\"#visualizations\" aria-controls=\"visualizations\" role=\"tab\" data-toggle=\"tab\">Visualizations</a></li>\n            <li role=\"presentation\"><a href=\"#debug\" aria-controls=\"debug\" role=\"tab\" data-toggle=\"tab\">Debug</a></li>\n          </ul>\n\n          <!-- Tab panes -->\n          <div class=\"tab-content\">\n            <p></p>\n            <div role=\"tabpanel\" class=\"tab-pane active\" id=\"visualizations\">\n              {% for name in names %}\n                <div class=\"panel panel-default\" id=\"visualization_{{name}}_panel\">\n                  <div class=\"panel-heading\">\n                    {{ name }}\n                    <a onclick=\"loadVisualization('{{name}}')\">\n                      <span class=\"glyphicon glyphicon-refresh\" aria-hidden=\"true\"></span>\n                    </a>\n                    <a href=\"#page-content-wrapper\" class=\"pull-right\"> Top </a>\n                  </div>\n                  <div class=\"panel-body\">\n                    <div id=\"visualization_{{name}}\"> Loading... </div>\n                    <p> Last updated: <span id=\"visualization_{{name}}_time\">NA</span></p>\n                  </div>\n                </div>\n              {% endfor %}\n            </div>\n            <div role=\"tabpanel\" class=\"tab-pane\" id=\"debug\">\n              <ul>\n                {% for message in debug_messages %}\n                  <li>{{ message }}</li>\n                {% endfor %}\n              </ul>\n            </div>\n          </div>\n        </div>\n\n\n        <hr>\n        <div id=\"footer\">\n          <div class=\"container\">\n            <p>\n              Mint is a minimal Caffe2 visualization tool by\n              <a href=\"http://daggerfs.com/\" target=\"_blank\">Yangqing</a>.\n            </p>\n          </div>\n        </div>\n      </div> <!-- /#page-content-wrapper -->\n    </div><!-- /#wrapper -->\n\n    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->\n    <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js\"></script>\n    <!-- Latest compiled and minified JavaScript -->\n    <script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js\"></script>\n    <!-- Menu Toggle Script -->\n    <script>\n    $(\"#menu-toggle\").click(function(e) {\n        e.preventDefault();\n        $(\"#wrapper\").toggleClass(\"toggled\");\n    });\n    </script>\n    <!-- load all contents for the first time when this page loads. -->\n    <script>\n      function refreshAll() {\n        {% for name in names %}\n          loadVisualization(\"{{name}}\");\n        {% endfor %}\n      }\n      refreshAll();\n    </script>\n  </body>\n</html>"
  },
  {
    "path": "caffe2/python/mkl/convnet_benchmarks.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package convnet_benchmarks\n# Module caffe2.python.convnet_benchmarks\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\"\"\"\nBenchmark for common convnets.\n\nSpeed on Titan X, with 10 warmup steps and 10 main steps and with different\nversions of cudnn, are as follows (time reported below is per-batch time,\nforward / forward+backward):\n\n                    CuDNN V3        CuDNN v4\nAlexNet         32.5 / 108.0    27.4 /  90.1\nOverFeat       113.0 / 342.3    91.7 / 276.5\nInception      134.5 / 485.8   125.7 / 450.6\nVGG (batch 64) 200.8 / 650.0   164.1 / 551.7\n\nSpeed on Inception with varied batch sizes and CuDNN v4 is as follows:\n\nBatch Size   Speed per batch     Speed per image\n 16             22.8 /  72.7         1.43 / 4.54\n 32             38.0 / 127.5         1.19 / 3.98\n 64             67.2 / 233.6         1.05 / 3.65\n128            125.7 / 450.6         0.98 / 3.52\n\nSpeed on Tesla M40, which 10 warmup steps and 10 main steps and with cudnn\nv4, is as follows:\n\nAlexNet         68.4 / 218.1\nOverFeat       210.5 / 630.3\nInception      300.2 / 1122.2\nVGG (batch 64) 405.8 / 1327.7\n\n(Note that these numbers involve a \"full\" backprop, i.e. the gradient\nwith respect to the input image is also computed.)\n\nTo get the numbers, simply run:\n\nfor MODEL in AlexNet OverFeat Inception; do\n  PYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size 128 --model $MODEL --forward_only True\ndone\nfor MODEL in AlexNet OverFeat Inception; do\n  PYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size 128 --model $MODEL\ndone\nPYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n  --batch_size 64 --model VGGA --forward_only True\nPYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n  --batch_size 64 --model VGGA\n\nfor BS in 16 32 64 128; do\n  PYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size $BS --model Inception --forward_only True\n  PYTHONPATH=../gen:$PYTHONPATH python convnet_benchmarks.py \\\n    --batch_size $BS --model Inception\ndone\n\nNote that VGG needs to be run at batch 64 due to memory limit on the backward\npass.\n\"\"\"\n\nimport argparse\n\nfrom caffe2.python import brew, cnn, workspace\nfrom caffe2.python.model_helper import ModelHelper\n\nfrom caffe2.python.models import resnet\nimport numpy as np\n\ndef MLP(order, cudnn_ws, mkl):\n    model = ModelHelper(name=\"benchmark\")\n    d = 256\n    depth = 20\n    width = 3\n    for i in range(depth):\n        for j in range(width):\n            current = \"fc_{}_{}\".format(i, j) if i > 0 else \"data\"\n            next_ = \"fc_{}_{}\".format(i + 1, j)\n            brew.fc(\n                model,\n                current, next_,\n                dim_in=d, dim_out=d,\n                weight_init=('XavierFill', {}),\n                bias_init=('XavierFill', {}))\n\n    brew.sum(model, [\"fc_{}_{}\".format(depth, j) for j in range(width)], [\"sum\"])\n    brew.fc(model, \"sum\", \"last\",\n             dim_in=d, dim_out=1000,\n             weight_init=('XavierFill', {}),\n             bias_init=('XavierFill', {}))\n    xent = model.LabelCrossEntropy([\"last\", \"label\"], \"xent\")\n    if not mkl:\n        model.AveragedLoss(xent, \"loss\")\n    return model, d\n\n\ndef ResNet50(order, cudnn_ws, mkl):\n    my_arg_scope = {'order': order, 'use_cudnn': True,\n                    'cudnn_exhaustive_search': True,\n                    'ws_nbytes_limit': str(cudnn_ws)}\n    model = ModelHelper(name=\"alexnet\", arg_scope=my_arg_scope)\n    resnet.create_resnet50(model, \"data\", 3, 1000, is_test=True,\n                           final_avg_kernel=14)\n    return model, 448\n\ndef AlexNet(order, cudnn_ws, mkl):\n    my_arg_scope = {'order': order, 'use_cudnn': True,\n                    'cudnn_exhaustive_search': True,\n                    'ws_nbytes_limit': str(cudnn_ws)}\n    model = ModelHelper(name=\"alexnet\", arg_scope=my_arg_scope)\n    conv1 = brew.conv(\n        model,\n        \"data\",\n        \"conv1\",\n        3,\n        64,\n        11,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        stride=4,\n        pad=2\n    )\n    relu1 = brew.relu(model, conv1, \"conv1\")\n    pool1 = brew.max_pool(model, relu1, \"pool1\", kernel=3, stride=2)\n    conv2 = brew.conv(\n        model,\n        pool1,\n        \"conv2\",\n        64,\n        192,\n        5,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=2\n    )\n    relu2 = brew.relu(model, conv2, \"conv2\")\n    pool2 = brew.max_pool(model, relu2, \"pool2\", kernel=3, stride=2)\n    conv3 = brew.conv(\n        model,\n        pool2,\n        \"conv3\",\n        192,\n        384,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu3 = brew.relu(model, conv3, \"conv3\")\n    conv4 = brew.conv(\n        model,\n        relu3,\n        \"conv4\",\n        384,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu4 = brew.relu(model, conv4, \"conv4\")\n    conv5 = brew.conv(\n        model,\n        relu4,\n        \"conv5\",\n        256,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu5 = brew.relu(model, conv5, \"conv5\")\n    pool5 = brew.max_pool(model, relu5, \"pool5\", kernel=3, stride=2)\n    fc6 = brew.fc(\n        model, pool5, \"fc6\", 256 * 6 * 6, 4096, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    relu6 = brew.relu(model, fc6, \"fc6\")\n    fc7 = brew.fc(\n        model, relu6, \"fc7\", 4096, 4096, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    relu7 = brew.relu(model, fc7, \"fc7\")\n    fc8 = brew.fc(\n        model, relu7, \"fc8\", 4096, 1000, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    pred = brew.softmax(model, fc8, \"pred\")\n    xent = model.LabelCrossEntropy([pred, \"label\"], \"xent\")\n    if not mkl:\n        loss = model.AveragedLoss(xent, \"loss\")\n    return model, 224\n\n\ndef OverFeat(order, cudnn_ws, mkl):\n    my_arg_scope = {'order': order, 'use_cudnn': True,\n                    'cudnn_exhaustive_search': True,\n                    'ws_nbytes_limit': str(cudnn_ws)}\n    model = ModelHelper(name='overfeat', arg_scope=my_arg_scope)\n    conv1 = brew.conv(\n        model,\n        \"data\",\n        \"conv1\",\n        3,\n        96,\n        11,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        stride=4\n    )\n    relu1 = brew.relu(model, conv1, \"conv1\")\n    pool1 = brew.max_pool(model, relu1, \"pool1\", kernel=2, stride=2)\n    conv2 = brew.conv(\n        model, pool1, \"conv2\", 96, 256, 5, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    relu2 = brew.relu(model, conv2, \"conv2\")\n    pool2 = brew.max_pool(model, relu2, \"pool2\", kernel=2, stride=2)\n    conv3 = brew.conv(\n        model,\n        pool2,\n        \"conv3\",\n        256,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu3 = brew.relu(model, conv3, \"conv3\")\n    conv4 = brew.conv(\n        model,\n        relu3,\n        \"conv4\",\n        512,\n        1024,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu4 = brew.relu(model, conv4, \"conv4\")\n    conv5 = brew.conv(\n        model,\n        relu4,\n        \"conv5\",\n        1024,\n        1024,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu5 = brew.relu(model, conv5, \"conv5\")\n    pool5 = brew.max_pool(model, relu5, \"pool5\", kernel=2, stride=2)\n    fc6 = brew.fc(\n        model, pool5, \"fc6\", 1024 * 6 * 6, 3072, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    relu6 = brew.relu(model, fc6, \"fc6\")\n    fc7 = brew.fc(\n        model, relu6, \"fc7\", 3072, 4096, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    relu7 = brew.relu(model, fc7, \"fc7\")\n    fc8 = brew.fc(\n        model, relu7, \"fc8\", 4096, 1000, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    pred = brew.softmax(model, fc8, \"pred\")\n    xent = model.LabelCrossEntropy([pred, \"label\"], \"xent\")\n    if not mkl:\n        loss = model.AveragedLoss(xent, \"loss\")\n    return model, 231\n\n\ndef VGGA(order, cudnn_ws, mkl):\n    my_arg_scope = {'order': order, 'use_cudnn': True,\n                    'cudnn_exhaustive_search': True,\n                    'ws_nbytes_limit': str(cudnn_ws)}\n    model = ModelHelper(name='vgg-a', arg_scope=my_arg_scope)\n    conv1 = brew.conv(\n        model,\n        \"data\",\n        \"conv1\",\n        3,\n        64,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu1 = brew.relu(model, conv1, \"conv1\")\n    pool1 = brew.max_pool(model, relu1, \"pool1\", kernel=2, stride=2)\n    conv2 = brew.conv(\n        model,\n        pool1,\n        \"conv2\",\n        64,\n        128,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu2 = brew.relu(model, conv2, \"conv2\")\n    pool2 = brew.max_pool(model, relu2, \"pool2\", kernel=2, stride=2)\n    conv3 = brew.conv(\n        model,\n        pool2,\n        \"conv3\",\n        128,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu3 = brew.relu(model, conv3, \"conv3\")\n    conv4 = brew.conv(\n        model,\n        relu3,\n        \"conv4\",\n        256,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu4 = brew.relu(model, conv4, \"conv4\")\n    pool4 = brew.max_pool(model, relu4, \"pool4\", kernel=2, stride=2)\n    conv5 = brew.conv(\n        model,\n        pool4,\n        \"conv5\",\n        256,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu5 = brew.relu(model, conv5, \"conv5\")\n    conv6 = brew.conv(\n        model,\n        relu5,\n        \"conv6\",\n        512,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu6 = brew.relu(model, conv6, \"conv6\")\n    pool6 = brew.max_pool(model, relu6, \"pool6\", kernel=2, stride=2)\n    conv7 = brew.conv(\n        model,\n        pool6,\n        \"conv7\",\n        512,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu7 = brew.relu(model, conv7, \"conv7\")\n    conv8 = brew.conv(\n        model,\n        relu7,\n        \"conv8\",\n        512,\n        512,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu8 = brew.relu(model, conv8, \"conv8\")\n    pool8 = brew.max_pool(model, relu8, \"pool8\", kernel=2, stride=2)\n\n    fcix = brew.fc(\n        model, pool8, \"fcix\", 512 * 7 * 7, 4096, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    reluix = brew.relu(model, fcix, \"fcix\")\n    fcx = brew.fc(\n        model, reluix, \"fcx\", 4096, 4096, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    relux = brew.relu(model, fcx, \"fcx\")\n    fcxi = brew.fc(\n        model, relux, \"fcxi\", 4096, 1000, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    pred = brew.softmax(model, fcxi, \"pred\")\n    xent = model.LabelCrossEntropy([pred, \"label\"], \"xent\")\n    if not mkl:\n        loss = model.AveragedLoss(xent, \"loss\")\n    return model, 231\n\n\ndef _InceptionModule(\n    model, input_blob, input_depth, output_name, conv1_depth, conv3_depths,\n    conv5_depths, pool_depth\n):\n    # path 1: 1x1 conv\n    conv1 = brew.conv(\n        model, input_blob, output_name + \":conv1\", input_depth, conv1_depth, 1,\n        ('XavierFill', {}), ('ConstantFill', {})\n    )\n    conv1 = brew.relu(model, conv1, conv1)\n    # path 2: 1x1 conv + 3x3 conv\n    conv3_reduce = brew.conv(\n        model, input_blob, output_name + \":conv3_reduce\", input_depth,\n        conv3_depths[0], 1, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    conv3_reduce = brew.relu(model, conv3_reduce, conv3_reduce)\n    conv3 = brew.conv(\n        model,\n        conv3_reduce,\n        output_name + \":conv3\",\n        conv3_depths[0],\n        conv3_depths[1],\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    conv3 = brew.relu(model, conv3, conv3)\n    # path 3: 1x1 conv + 5x5 conv\n    conv5_reduce = brew.conv(\n        model, input_blob, output_name + \":conv5_reduce\", input_depth,\n        conv5_depths[0], 1, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    conv5_reduce = brew.relu(model, conv5_reduce, conv5_reduce)\n    conv5 = brew.conv(\n        model,\n        conv5_reduce,\n        output_name + \":conv5\",\n        conv5_depths[0],\n        conv5_depths[1],\n        5,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=2\n    )\n    conv5 = brew.relu(model, conv5, conv5)\n    # path 4: pool + 1x1 conv\n    pool = brew.max_pool(\n        model,\n        input_blob,\n        output_name + \":pool\",\n        kernel=3,\n        stride=1,\n        pad=1\n    )\n    pool_proj = brew.conv(\n        model, pool, output_name + \":pool_proj\", input_depth, pool_depth, 1,\n        ('XavierFill', {}), ('ConstantFill', {})\n    )\n    pool_proj = brew.relu(model, pool_proj, pool_proj)\n    output = brew.concat(model, [conv1, conv3, conv5, pool_proj], output_name)\n    return output\n\n\ndef Inception(order, cudnn_ws, mkl):\n    my_arg_scope = {'order': order, 'use_cudnn': True,\n                    'cudnn_exhaustive_search': True,\n                    'ws_nbytes_limit': str(cudnn_ws)}\n    model = ModelHelper(name=\"inception\", arg_scope=my_arg_scope)\n    conv1 = brew.conv(\n        model,\n        \"data\",\n        \"conv1\",\n        3,\n        64,\n        7,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        stride=2,\n        pad=3\n    )\n    relu1 = brew.relu(model, conv1, \"conv1\")\n    pool1 = brew.max_pool(model, relu1, \"pool1\", kernel=3, stride=2, pad=1)\n    conv2a = brew.conv(\n        model, pool1, \"conv2a\", 64, 64, 1,\n        ('XavierFill', {}), ('ConstantFill', {})\n    )\n    conv2a = brew.relu(model, conv2a, conv2a)\n    conv2 = brew.conv(\n        model,\n        conv2a,\n        \"conv2\",\n        64,\n        192,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu2 = brew.relu(model, conv2, \"conv2\")\n    pool2 = brew.max_pool(model, relu2, \"pool2\", kernel=3, stride=2, pad=1)\n    # Inception modules\n    inc3 = _InceptionModule(\n        model, pool2, 192, \"inc3\", 64, [96, 128], [16, 32], 32\n    )\n    inc4 = _InceptionModule(\n        model, inc3, 256, \"inc4\", 128, [128, 192], [32, 96], 64\n    )\n    pool5 = brew.max_pool(model, inc4, \"pool5\", kernel=3, stride=2, pad=1)\n    inc5 = _InceptionModule(\n        model, pool5, 480, \"inc5\", 192, [96, 208], [16, 48], 64\n    )\n    inc6 = _InceptionModule(\n        model, inc5, 512, \"inc6\", 160, [112, 224], [24, 64], 64\n    )\n    inc7 = _InceptionModule(\n        model, inc6, 512, \"inc7\", 128, [128, 256], [24, 64], 64\n    )\n    inc8 = _InceptionModule(\n        model, inc7, 512, \"inc8\", 112, [144, 288], [32, 64], 64\n    )\n    inc9 = _InceptionModule(\n        model, inc8, 528, \"inc9\", 256, [160, 320], [32, 128], 128\n    )\n    pool9 = brew.max_pool(model, inc9, \"pool9\", kernel=3, stride=2, pad=1)\n    inc10 = _InceptionModule(\n        model, pool9, 832, \"inc10\", 256, [160, 320], [32, 128], 128\n    )\n    inc11 = _InceptionModule(\n        model, inc10, 832, \"inc11\", 384, [192, 384], [48, 128], 128\n    )\n    pool11 = brew.average_pool(model, inc11, \"pool11\", kernel=7, stride=1)\n    fc = brew.fc(\n        model, pool11, \"fc\", 1024, 1000,\n        ('XavierFill', {}), ('ConstantFill', {})\n    )\n    # It seems that Soumith's benchmark does not have softmax on top\n    # for Inception. We will add it anyway so we can have a proper\n    # backward pass.\n    pred = brew.softmax(model, fc, \"pred\")\n    xent = model.LabelCrossEntropy([pred, \"label\"], \"xent\")\n    if not mkl:\n        loss = model.AveragedLoss(xent, \"loss\")\n    return model, 224\n\n\ndef AddParameterUpdate(model):\n    \"\"\" Simple plain SGD update -- not tuned to actually train the models \"\"\"\n    ITER = brew.iter(model, \"iter\")\n    LR = model.LearningRate(\n        ITER, \"LR\", base_lr=-1e-8, policy=\"step\", stepsize=10000, gamma=0.999)\n    ONE = model.param_init_net.ConstantFill([], \"ONE\", shape=[1], value=1.0)\n    for param in model.params:\n        param_grad = model.param_to_grad[param]\n        model.WeightedSum([param, ONE, param_grad, LR], param)\n\n\ndef Benchmark(model_gen, arg):\n    model, input_size = model_gen(arg.order, arg.cudnn_ws, arg.mkl)\n    model.Proto().type = arg.net_type\n    model.Proto().num_workers = arg.num_workers\n\n    # In order to be able to run everything without feeding more stuff, let's\n    # add the data and label blobs to the parameter initialization net as well.\n    if arg.order == \"NCHW\":\n        input_shape = [arg.batch_size, 3, input_size, input_size]\n    else:\n        input_shape = [arg.batch_size, input_size, input_size, 3]\n    if arg.model == \"MLP\":\n        input_shape = [arg.batch_size, input_size]\n\n    model.param_init_net.GaussianFill(\n        [],\n        \"data\",\n        shape=input_shape,\n        mean=0.0,\n        std=1.0\n    )\n    #MKL doesn't support int, so have to use numpy\n    if arg.mkl:\n        label = np.random.randint(low=0, high=1000, size=(arg.batch_size,)).astype(np.int32)\n        workspace.FeedBlob(\"label\", label)\n    else:\n        model.param_init_net.UniformIntFill(\n            [],\n            \"label\",\n            shape=[arg.batch_size, ],\n            min=0,\n            max=999\n        )\n\n    if arg.forward_only:\n        print('{}: running forward only.'.format(arg.model))\n    else:\n        if arg.mkl:\n            print(\n                '==WARNING==\\n'\n                'forward-backward not supported yet in MKL, so exiting'\n            )\n        print('{}: running forward-backward.'.format(arg.model))\n        model.AddGradientOperators([\"loss\"])\n        AddParameterUpdate(model)\n        if arg.order == 'NHWC':\n            print(\n                '==WARNING==\\n'\n                'NHWC order with CuDNN may not be supported yet, so I might\\n'\n                'exit suddenly.'\n            )\n\n    if not arg.cpu:\n        if arg.mkl:\n            model.param_init_net.RunAllOnMKL()\n            model.net.RunAllOnMKL()\n        else:\n            model.param_init_net.RunAllOnGPU()\n            model.net.RunAllOnGPU()\n\n    if arg.engine:\n        for op in model.net.Proto().op:\n            op.engine = arg.engine\n\n    if arg.dump_model:\n        # Writes out the pbtxt for benchmarks on e.g. Android\n        with open(\n            \"{0}_init_batch_{1}.pbtxt\".format(arg.model, arg.batch_size), \"w\"\n        ) as fid:\n            fid.write(str(model.param_init_net.Proto()))\n        with open(\"{0}.pbtxt\".format(arg.model, arg.batch_size), \"w\") as fid:\n            fid.write(str(model.net.Proto()))\n\n    workspace.RunNetOnce(model.param_init_net)\n    workspace.CreateNet(model.net)\n    workspace.BenchmarkNet(\n        model.net.Proto().name, arg.warmup_iterations, arg.iterations,\n        arg.layer_wise_benchmark)\n\n\ndef GetArgumentParser():\n    parser = argparse.ArgumentParser(description=\"Caffe2 benchmark.\")\n    parser.add_argument(\n        \"--batch_size\",\n        type=int,\n        default=128,\n        help=\"The batch size.\"\n    )\n    parser.add_argument(\"--model\", type=str, help=\"The model to benchmark.\")\n    parser.add_argument(\n        \"--order\",\n        type=str,\n        default=\"NCHW\",\n        help=\"The order to evaluate.\"\n    )\n    parser.add_argument(\n        \"--cudnn_ws\",\n        type=int,\n        help=\"The cudnn workspace size.\"\n    )\n    parser.add_argument(\n        \"--iterations\",\n        type=int,\n        default=10,\n        help=\"Number of iterations to run the network.\"\n    )\n    parser.add_argument(\n        \"--warmup_iterations\",\n        type=int,\n        default=10,\n        help=\"Number of warm-up iterations before benchmarking.\"\n    )\n    parser.add_argument(\n        \"--forward_only\",\n        action='store_true',\n        help=\"If set, only run the forward pass.\"\n    )\n    parser.add_argument(\n        \"--layer_wise_benchmark\",\n        action='store_true',\n        help=\"If True, run the layer-wise benchmark as well.\"\n    )\n    parser.add_argument(\n        \"--cpu\",\n        action='store_true',\n        help=\"If True, run testing on CPU instead of GPU.\"\n    )\n    parser.add_argument(\n        \"--mkl\",\n        action='store_true',\n        help=\"If True, run testing on CPU-MKL instead of GPU.\"\n    )\n    parser.add_argument(\n        \"--engine\",\n        type=str,\n        default=\"\",\n        help=\"If set, blindly prefer the given engine(s) for every op.\")\n    parser.add_argument(\n        \"--dump_model\",\n        action='store_true',\n        help=\"If True, dump the model prototxts to disk.\"\n    )\n    parser.add_argument(\"--net_type\", type=str, default=\"simple\")\n    parser.add_argument(\"--num_workers\", type=int, default=2)\n    parser.add_argument(\"--use-nvtx\", default=False, action='store_true')\n    parser.add_argument(\"--htrace_span_log_path\", type=str)\n    return parser\n\n\nif __name__ == '__main__':\n    args, extra_args = GetArgumentParser().parse_known_args()\n    if (\n        not args.batch_size or not args.model or not args.order\n    ):\n        GetArgumentParser().print_help()\n    else:\n        workspace.GlobalInit(\n            ['caffe2', '--caffe2_log_level=0'] + extra_args +\n            (['--caffe2_use_nvtx'] if args.use_nvtx else []) +\n            (['--caffe2_htrace_span_log_path=' + args.htrace_span_log_path]\n                if args.htrace_span_log_path else []))\n\n        model_map = {\n            'AlexNet': AlexNet,\n            'OverFeat': OverFeat,\n            'VGGA': VGGA,\n            'Inception': Inception,\n            'ResNet50': ResNet50,\n            'MLP': MLP,\n        }\n        Benchmark(model_map[args.model], args)\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_LRN_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given, settings\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn,\n                 \"Skipping as we do not have mkldnn.\")\n\n\nclass MKLLRNTest(hu.HypothesisTestCase):\n    @given(input_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           im_size=st.integers(1, 10),\n           order=st.sampled_from([\"NCHW\"]),\n           **mu.gcs)\n\n    def test_mkl_LRN(self, input_channels,\n                            batch_size, im_size, order,\n                             gc, dc):\n        op = core.CreateOperator(\n            \"LRN\",\n            [\"X\"],\n            [\"Y\", \"Y_scale\"],\n            size=5,\n            alpha=0.001,\n            beta=0.75,\n            bias=2.0,\n            order=order,\n        )\n        X = np.random.rand(\n            batch_size, input_channels, im_size, im_size).astype(np.float32)\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_LRN_speed_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport unittest\n\nimport numpy as np\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import cnn, core, workspace, test_util\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn, \"Skipping as we do not have mkldnn.\")\nclass TestMKLBasic(test_util.TestCase):\n    def testLRNSpeed(self):\n        # We randomly select a shape to test the speed. Intentionally we\n        # test a batch size of 1 since this may be the most frequent use\n        # case for MKL during deployment time.\n        X = np.random.rand(1, 2, 224, 224).astype(np.float32)\n        mkl_do = core.DeviceOption(caffe2_pb2.MKLDNN)\n        # Makes sure that feed works.\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"X_mkl\", X, device_option=mkl_do)\n        net = core.Net(\"test\")\n        # Makes sure that we can run relu.\n        net.LRN(\"X\", [\"Y\", \"Y_Scale\"], size=5, alpha=0.001, beta=0.75, bias=2.0, order=\"NCHW\")\n        net.LRN(\"X_mkl\", [\"Y_mkl\", \"Y_Scale_mkl\"], size=5, alpha=0.001, beta=0.75, bias=2.0, order=\"NCHW\", device_option=mkl_do)\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n\n        # makes sure that the results are good.\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"Y\"),\n            workspace.FetchBlob(\"Y_mkl\"),\n            atol=1e-2,\n            rtol=1e-2)\n        runtime = workspace.BenchmarkNet(net.Proto().name, 1, 100, True)\n\n        print(\"LRN CPU runtime {}, MKL runtime {}.\".format(runtime[1], runtime[2]))\n\n    def testConvReluLRNSpeed(self):\n        # We randomly select a shape to test the speed. Intentionally we\n        # test a batch size of 1 since this may be the most frequent use\n        # case for MKL during deployment time.\n        X = np.random.rand(1, 3, 224, 224).astype(np.float32) - 0.5\n        W = np.random.rand(64, 3, 11, 11).astype(np.float32) - 0.5\n        b = np.random.rand(64).astype(np.float32) - 0.5\n\n        mkl_do = core.DeviceOption(caffe2_pb2.MKLDNN)\n        # Makes sure that feed works.\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"W\", W)\n        workspace.FeedBlob(\"b\", b)\n        workspace.FeedBlob(\"X_mkl\", X, device_option=mkl_do)\n        workspace.FeedBlob(\"W_mkl\", W, device_option=mkl_do)\n        workspace.FeedBlob(\"b_mkl\", b, device_option=mkl_do)\n\n        net = core.Net(\"test\")\n\n        net.Conv([\"X\", \"W\", \"b\"], \"C\", pad=1, stride=1, kernel=11)\n        net.Conv([\"X_mkl\", \"W_mkl\", \"b_mkl\"], \"C_mkl\",\n                 pad=1, stride=1, kernel=11, device_option=mkl_do)\n        net.Relu(\"C\", \"R\")\n        net.Relu(\"C_mkl\", \"R_mkl\", device_option=mkl_do)\n        net.LRN(\"R\", [\"Y\", \"Y_Scale\"], size=5, alpha=0.001, beta=0.75, bias=2.0, order=\"NCHW\")\n        net.LRN(\"R_mkl\", [\"Y_mkl\", \"Y_Scale_mkl\"],size=5, alpha=0.001, beta=0.75, bias=2.0, order=\"NCHW\", device_option=mkl_do)\n\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n        # makes sure that the results are good.\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"Y\"),\n            workspace.FetchBlob(\"Y_mkl\"),\n            atol=1e-2,\n            rtol=1e-2)\n        runtime = workspace.BenchmarkNet(net.Proto().name, 1, 100, True)\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_concat_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\n\n\n@unittest.skipIf(\n    not workspace.C.has_mkldnn, \"Skipping as we do not have mkldnn.\"\n)\nclass MKLConcatTest(hu.HypothesisTestCase):\n    @given(\n        batch_size=st.integers(1, 10),\n        channel_splits=st.lists(st.integers(1, 10), min_size=1, max_size=3),\n        height=st.integers(1, 10),\n        width=st.integers(1, 10),\n        **mu.gcs\n    )\n    def test_mkl_concat(\n        self, batch_size, channel_splits, height, width, gc, dc\n    ):\n        Xs = [\n            np.random.rand(batch_size, channel,\n                           height, width).astype(np.float32)\n            for channel in channel_splits\n        ]\n        op = core.CreateOperator(\n            \"Concat\",\n            [\"X_{}\".format(i) for i in range(len(Xs))],\n            [\"concat_result\", \"split_info\"],\n            order=\"NCHW\",\n        )\n        self.assertDeviceChecks(dc, op, Xs, [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_conv_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given, settings\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn,\n                 \"Skipping as we do not have mkldnn.\")\nclass MKLConvTest(hu.HypothesisTestCase):\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(3, 5),\n           size=st.integers(8, 20),\n           input_channels=st.integers(1, 16),\n           output_channels=st.integers(1, 16),\n           batch_size=st.integers(1, 3),\n           use_bias=st.booleans(),\n           group=st.integers(1, 8),\n           **mu.gcs)\n    def test_mkl_convolution(self, stride, pad, kernel, size,\n                             input_channels, output_channels,\n                             batch_size, use_bias, group, gc, dc):\n        op = core.CreateOperator(\n            \"Conv\",\n            [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n            [\"Y\"],\n            stride=stride,\n            pad=pad,\n            kernel=kernel,\n            group=group\n        )\n        X = np.random.rand(\n            batch_size, input_channels * group, size, size).astype(np.float32) - 0.5\n        w = np.random.rand(\n                output_channels * group, input_channels, kernel, kernel) \\\n            .astype(np.float32) - 0.5\n        b = np.random.rand(output_channels * group).astype(np.float32) - 0.5\n\n        inputs = [X, w, b] if use_bias else [X, w]\n        self.assertDeviceChecks(dc, op, inputs, [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_copy_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\nimport caffe2.proto.caffe2_pb2 as pb2\n\n@unittest.skipIf(not workspace.C.has_mkldnn,\n                 \"Skipping as we do not have mkldnn.\")\nclass MKCopyTest(hu.HypothesisTestCase):\n    @given(width=st.integers(7, 9),\n           height=st.integers(7, 9),\n           input_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           **mu.gcs)\n    def test_mkl_copy(self,\n                      width,\n                      height,\n                      input_channels,\n                      batch_size,\n                      gc, dc):\n        X = np.random.rand(\n            batch_size, input_channels, width, height).astype(np.float32)\n        self.ws.create_blob(\"X\").feed(X, pb2.DeviceOption())\n        self.ws.run(core.CreateOperator(\n            \"CopyCPUToMKL\",\n            [\"X\"],\n            [\"X_MKL\"],\n            device_option=pb2.DeviceOption(device_type=pb2.MKLDNN)\n        ))\n        self.ws.run(core.CreateOperator(\n            \"CopyMKLToCPU\",\n            [\"X_MKL\"],\n            [\"X_copy\"],\n            device_option=pb2.DeviceOption(device_type=pb2.MKLDNN)\n        ))\n        np.testing.assert_array_equal(X, self.ws.blobs[\"X_copy\"].fetch())\n\n    @given(n=st.sampled_from([0, 10]))\n    def test_mkl_zero_copy(self, n):\n        shape = (0, n)\n        X = np.zeros(shape=shape).astype(np.float32)\n        self.ws.create_blob(\"X\").feed(X, pb2.DeviceOption())\n        self.ws.run(core.CreateOperator(\n            \"CopyCPUToMKL\",\n            [\"X\"],\n            [\"X_MKL\"],\n            device_option=pb2.DeviceOption(device_type=pb2.MKLDNN)\n        ))\n        self.ws.run(core.CreateOperator(\n            \"CopyMKLToCPU\",\n            [\"X_MKL\"],\n            [\"X_copy\"],\n            device_option=pb2.DeviceOption(device_type=pb2.MKLDNN)\n        ))\n        np.testing.assert_equal(shape, self.ws.blobs[\"X_copy\"].fetch().shape)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_elementwise_add_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn,\n                 \"Skipping as we do not have mkldnn.\")\nclass MKLElementwiseAddTest(hu.HypothesisTestCase):\n    @given(size=st.integers(7, 9),\n           input_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           inplace=st.booleans(),\n           **mu.gcs)\n    def test_mkl_elementwise_add(self,\n                                 size,\n                                 input_channels,\n                                 batch_size,\n                                 inplace,\n                                 gc,\n                                 dc):\n        op = core.CreateOperator(\n            \"Add\",\n            [\"X0\", \"X1\"],\n            [\"X0\" if inplace else \"Y\"],\n        )\n        Xs = [np.random.rand(batch_size, input_channels, size, size).astype(\n            np.float32) for _ in range(2)]\n        self.assertDeviceChecks(dc, op, Xs, [0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_elementwise_sum_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn,\n                 \"Skipping as we do not have mkldnn.\")\nclass MKLElementwiseSumTest(hu.HypothesisTestCase):\n    @given(size=st.integers(7, 9),\n           input_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           inputs=st.integers(1, 3),\n           inplace=st.booleans(),\n           **mu.gcs)\n    def test_mkl_elementwise_sum(self,\n                                 size,\n                                 input_channels,\n                                 batch_size,\n                                 inputs,\n                                 inplace,\n                                 gc,\n                                 dc):\n        op = core.CreateOperator(\n            \"Sum\",\n            [\"X_{}\".format(i) for i in range(inputs)],\n            [\"X_0\" if inplace else \"Y\"],\n        )\n        Xs = [np.random.rand(batch_size, input_channels, size, size).astype(\n            np.float32) for _ in range(inputs)]\n        self.assertDeviceChecks(dc, op, Xs, [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_fc_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given, settings\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn,\n                 \"Skipping as we do not have mkldnn.\")\nclass MKLFcTest(hu.HypothesisTestCase):\n    @given(n=st.integers(1, 5), m=st.integers(1, 5),\n           k=st.integers(1, 5), **mu.gcs)\n\n    def test_mkl_fc(self,n, m, k, gc, dc):\n        X = np.random.rand(m, k).astype(np.float32) - 0.5\n        W = np.random.rand(n, k).astype(np.float32) - 0.5\n        b = np.random.rand(n).astype(np.float32) - 0.5\n\n        op = core.CreateOperator(\n            'FC',\n            ['X', 'W', 'b'],\n            [\"Y\"]\n            )\n\n        self.assertDeviceChecks(dc, op, [X, W, b], [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_fc_speed_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport unittest\n\nimport numpy as np\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import cnn, core, workspace, test_util\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn, \"Skipping as we do not have mkldnn.\")\nclass TestMKLBasic(test_util.TestCase):\n    def testFCSpeed(self):\n        # We randomly select a shape to test the speed. Intentionally we\n        # test a batch size of 1 since this may be the most frequent use\n        # case for MKL during deployment time.\n        X = np.random.rand(1, 256, 6, 6).astype(np.float32) - 0.5\n        #X = np.random.rand(32, 256*6*6).astype(np.float32) - 0.5\n        W = np.random.rand(4096, 9216).astype(np.float32) - 0.5\n        b = np.random.rand(4096).astype(np.float32) - 0.5\n        mkl_do = core.DeviceOption(caffe2_pb2.MKLDNN)\n        # Makes sure that feed works.\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"W\", W)\n        workspace.FeedBlob(\"b\", b)\n        workspace.FeedBlob(\"X_mkl\", X, device_option=mkl_do)\n        workspace.FeedBlob(\"W_mkl\", W, device_option=mkl_do)\n        workspace.FeedBlob(\"b_mkl\", b, device_option=mkl_do)\n        net = core.Net(\"test\")\n        # Makes sure that we can run relu.\n        net.FC([\"X\", \"W\", \"b\"], \"Y\")\n        net.FC([\"X_mkl\", \"W_mkl\", \"b_mkl\"], \"Y_mkl\", device_option=mkl_do)\n\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n        # makes sure that the results are good.\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"Y\"),\n            workspace.FetchBlob(\"Y_mkl\"),\n            atol=1e-2,\n            rtol=1e-2)\n        runtime = workspace.BenchmarkNet(net.Proto().name, 1, 100, True)\n\n        print(\"FC CPU runtime {}, MKL runtime {}.\".format(runtime[1], runtime[2]))\n\n    def testConvReluMaxPoolFcSpeed(self):\n        # We randomly select a shape to test the speed. Intentionally we\n        # test a batch size of 1 since this may be the most frequent use\n        # case for MKL during deployment time.\n        X = np.random.rand(1, 256, 13, 13).astype(np.float32) - 0.5\n        W = np.random.rand(256, 256, 3, 3).astype(np.float32) - 0.5\n        b = np.random.rand(256).astype(np.float32) - 0.5\n\n        w_fc = np.random.rand(4096, 9216).astype(np.float32) - 0.5\n        b_fc = np.random.rand(4096).astype(np.float32) - 0.5\n        mkl_do = core.DeviceOption(caffe2_pb2.MKLDNN)\n        # Makes sure that feed works.\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"W\", W)\n        workspace.FeedBlob(\"b\", b)\n        workspace.FeedBlob(\"w_fc\", w_fc)\n        workspace.FeedBlob(\"b_fc\", b_fc)\n        workspace.FeedBlob(\"X_mkl\", X, device_option=mkl_do)\n        workspace.FeedBlob(\"W_mkl\", W, device_option=mkl_do)\n        workspace.FeedBlob(\"b_mkl\", b, device_option=mkl_do)\n        workspace.FeedBlob(\"w_fc_mkl\", w_fc, device_option=mkl_do)\n        workspace.FeedBlob(\"b_fc_mkl\", b_fc, device_option=mkl_do)\n\n        net = core.Net(\"test\")\n\n        net.Conv([\"X\", \"W\", \"b\"], \"C\", pad=1, stride=1, kernel=3)\n        net.Relu(\"C\", \"R\")\n        net.MaxPool(\"R\", \"P\", stride=2, kernel=3)\n        net.FC([\"P\",\"w_fc\", \"b_fc\"], \"Y\")\n\n        net.Conv([\"X_mkl\", \"W_mkl\", \"b_mkl\"], \"C_mkl\",\n                 pad=1, stride=1, kernel=3, device_option=mkl_do)\n        net.Relu(\"C_mkl\", \"R_mkl\", device_option=mkl_do)\n        net.MaxPool(\"R_mkl\", \"P_mkl\",\n                 stride=2, kernel=3, device_option=mkl_do)\n        net.FC([\"P_mkl\",\"w_fc_mkl\", \"b_fc_mkl\"], \"Y_mkl\", device_option=mkl_do)\n\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n        # makes sure that the results are good.\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"Y\"),\n            workspace.FetchBlob(\"Y_mkl\"),\n            atol=1e-2,\n            rtol=1e-2)\n        runtime = workspace.BenchmarkNet(net.Proto().name, 1, 100, True)\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_fill_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given, settings\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn,\n                 \"Skipping as we do not have mkldnn.\")\nclass MKLFillTest(hu.HypothesisTestCase):\n    @given(n=st.integers(1, 4), c=st.integers(1, 4),\n           h=st.integers(1, 4), w=st.integers(1, 4),\n           filler=st.sampled_from(\n               [\"XavierFill\", \"ConstantFill\", \"GaussianFill\", \"MSRAFill\"]\n           ),\n           seed=st.integers(5, 10),\n           **mu.gcs_cpu_mkl)\n    def test_mkl_fill(self, n, c, h, w, filler, seed, gc, dc):\n        op = core.CreateOperator(\n            filler,\n            [],\n            [\"Y\"],\n            shape=[n, c, h, w],\n        )\n        for d in dc:\n            d.random_seed = seed\n        self.assertDeviceChecks(dc, op, [], [0])\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_pool_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given, settings, assume\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn,\n                 \"Skipping as we do not have mkldnn.\")\nclass MKLPoolTest(hu.HypothesisTestCase):\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(3, 5),\n           size=st.integers(7, 9),\n           input_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           method=st.sampled_from([\"MaxPool\", \"AveragePool\"]),\n           **mu.gcs)\n    @settings(max_examples=2, timeout=100)\n    def test_mkl_pooling(self, stride, pad, kernel, size,\n                         input_channels, batch_size,\n                         method, gc, dc):\n        assume(pad < kernel)\n        op = core.CreateOperator(\n            method,\n            [\"X\"],\n            [\"Y\"],\n            stride=stride,\n            pad=pad,\n            kernel=kernel,\n        )\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32)\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_pool_speed_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport unittest\n\nimport numpy as np\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import cnn, core, workspace, test_util\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn, \"Skipping as we do not have mkldnn.\")\nclass TestMKLBasic(test_util.TestCase):\n    def testMaxPoolingSpeed(self):\n        # We randomly select a shape to test the speed. Intentionally we\n        # test a batch size of 1 since this may be the most frequent use\n        # case for MKL during deployment time.\n        X = np.random.rand(1, 64, 224, 224).astype(np.float32)\n        mkl_do = core.DeviceOption(caffe2_pb2.MKLDNN)\n        # Makes sure that feed works.\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"X_mkl\", X, device_option=mkl_do)\n        net = core.Net(\"test\")\n        # Makes sure that we can run relu.\n        net.MaxPool(\"X\", \"Y\", stride=2, kernel=3)\n        net.MaxPool(\"X_mkl\", \"Y_mkl\",\n                 stride=2, kernel=3, device_option=mkl_do)\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n        # makes sure that the results are good.\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"Y\"),\n            workspace.FetchBlob(\"Y_mkl\"),\n            atol=1e-2,\n            rtol=1e-2)\n        runtime = workspace.BenchmarkNet(net.Proto().name, 1, 100, True)\n\n        print(\"Maxpooling CPU runtime {}, MKL runtime {}.\".format(runtime[1], runtime[2]))\n\n    def testAveragePoolingSpeed(self):\n        # We randomly select a shape to test the speed. Intentionally we\n        # test a batch size of 1 since this may be the most frequent use\n        # case for MKL during deployment time.\n        X = np.random.rand(1, 64, 224, 224).astype(np.float32)\n        mkl_do = core.DeviceOption(caffe2_pb2.MKLDNN)\n        # Makes sure that feed works.\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"X_mkl\", X, device_option=mkl_do)\n        net = core.Net(\"test\")\n        # Makes sure that we can run relu.\n        net.AveragePool(\"X\", \"Y\", stride=2, kernel=3)\n        net.AveragePool(\"X_mkl\", \"Y_mkl\",\n                 stride=2, kernel=3, device_option=mkl_do)\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n        # makes sure that the results are good.\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"Y\"),\n            workspace.FetchBlob(\"Y_mkl\"),\n            atol=1e-2,\n            rtol=1e-2)\n        runtime = workspace.BenchmarkNet(net.Proto().name, 1, 100, True)\n\n        print(\"Averagepooling CPU runtime {}, MKL runtime {}.\".format(runtime[1], runtime[2]))\n\n    def testConvReluMaxPoolSpeed(self):\n        # We randomly select a shape to test the speed. Intentionally we\n        # test a batch size of 1 since this may be the most frequent use\n        # case for MKL during deployment time.\n        X = np.random.rand(1, 3, 224, 224).astype(np.float32) - 0.5\n        W = np.random.rand(64, 3, 11, 11).astype(np.float32) - 0.5\n        b = np.random.rand(64).astype(np.float32) - 0.5\n\n        mkl_do = core.DeviceOption(caffe2_pb2.MKLDNN)\n        # Makes sure that feed works.\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"W\", W)\n        workspace.FeedBlob(\"b\", b)\n        workspace.FeedBlob(\"X_mkl\", X, device_option=mkl_do)\n        workspace.FeedBlob(\"W_mkl\", W, device_option=mkl_do)\n        workspace.FeedBlob(\"b_mkl\", b, device_option=mkl_do)\n\n        net = core.Net(\"test\")\n\n        net.Conv([\"X\", \"W\", \"b\"], \"C\", pad=1, stride=1, kernel=11)\n        net.Conv([\"X_mkl\", \"W_mkl\", \"b_mkl\"], \"C_mkl\",\n                 pad=1, stride=1, kernel=11, device_option=mkl_do)\n        net.Relu(\"C\", \"R\")\n        net.Relu(\"C_mkl\", \"R_mkl\", device_option=mkl_do)\n        net.AveragePool(\"R\", \"Y\", stride=2, kernel=3)\n        net.AveragePool(\"R_mkl\", \"Y_mkl\",\n                 stride=2, kernel=3, device_option=mkl_do)\n\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n        # makes sure that the results are good.\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"Y\"),\n            workspace.FetchBlob(\"Y_mkl\"),\n            atol=1e-2,\n            rtol=1e-2)\n        runtime = workspace.BenchmarkNet(net.Proto().name, 1, 100, True)\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_relu_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn,\n                 \"Skipping as we do not have mkldnn.\")\nclass MKLReluTest(hu.HypothesisTestCase):\n    @given(size=st.integers(8, 20),\n           input_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           inplace=st.booleans(),\n           **mu.gcs)\n    def test_mkl_relu(self, size, input_channels, batch_size, inplace, gc, dc):\n        op = core.CreateOperator(\n            \"Relu\",\n            [\"X\"],\n            [\"Y\"] if not inplace else [\"X\"],\n        )\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_sbn_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given, settings\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn,\n                 \"Skipping as we do not have mkldnn.\")\nclass MKLSpatialBNTest(hu.HypothesisTestCase):\n    @given(size=st.integers(7, 10),\n           input_channels=st.integers(1, 10),\n           batch_size=st.integers(1, 3),\n           seed=st.integers(0, 65535),\n           #order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           order=st.sampled_from([\"NCHW\"]),\n           epsilon=st.floats(1e-5, 1e-2),\n           **mu.gcs)\n    def test_spatialbn_test_mode(self, size, input_channels,\n                                 batch_size, seed, order, epsilon, gc, dc):\n        np.random.seed(seed)\n        scale = np.random.rand(input_channels).astype(np.float32) + 0.5\n        bias = np.random.rand(input_channels).astype(np.float32) - 0.5\n        mean = np.random.randn(input_channels).astype(np.float32)\n        var = np.random.rand(input_channels).astype(np.float32) + 0.5\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n\n        op = core.CreateOperator(\n            \"SpatialBN\",\n            [\"X\", \"scale\", \"bias\", \"mean\", \"var\"],\n            [\"Y\"],\n            order=order,\n            is_test=True,\n            epsilon=epsilon,\n        )\n\n        self.assertDeviceChecks(dc, op, [X, scale, bias, mean, var], [0])\n\n    @given(size=st.integers(7, 10),\n           input_channels=st.integers(1, 10),\n           batch_size=st.integers(1, 3),\n           seed=st.integers(0, 65535),\n           #order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           order=st.sampled_from([\"NCHW\"]),\n           epsilon=st.floats(1e-5, 1e-2),\n           **mu.gcs)\n    def test_spatialbn_train_mode(\n            self, size, input_channels, batch_size, seed, order, epsilon,\n            gc, dc):\n        op = core.CreateOperator(\n            \"SpatialBN\",\n            [\"X\", \"scale\", \"bias\", \"running_mean\", \"running_var\"],\n            [\"Y\", \"running_mean\", \"running_var\", \"saved_mean\", \"saved_var\"],\n            order=order,\n            is_test=False,\n            epsilon=epsilon,\n        )\n        np.random.seed(seed)\n        scale = np.random.rand(input_channels).astype(np.float32) + 0.5\n        bias = np.random.rand(input_channels).astype(np.float32) - 0.5\n        mean = np.random.randn(input_channels).astype(np.float32)\n        var = np.random.rand(input_channels).astype(np.float32) + 0.5\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n        # Note: it seems that the running mean and var do not pass the device\n        # test, suggesting that the semantics are a bit different. Only\n        # checking the output and saved mean and var at this stage.\n        self.assertDeviceChecks(dc, op, [X, scale, bias, mean, var],\n                                [0, 3, 4])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_sbn_speed_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport unittest\n\nimport numpy as np\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import cnn, core, workspace, test_util\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn, \"Skipping as we do not have mkldnn.\")\nclass TestMKLBasic(test_util.TestCase):\n    def testSpatialBNTestingSpeed(self):\n\n        input_channel = 10\n        X = np.random.rand(1, input_channel, 100, 100).astype(np.float32) - 0.5\n        scale = np.random.rand(input_channel).astype(np.float32) + 0.5\n        bias = np.random.rand(input_channel).astype(np.float32) - 0.5\n        mean = np.random.randn(input_channel).astype(np.float32)\n        var = np.random.rand(input_channel).astype(np.float32) + 0.5\n\n        mkl_do = core.DeviceOption(caffe2_pb2.MKLDNN)\n        # Makes sure that feed works.\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"scale\", scale)\n        workspace.FeedBlob(\"bias\", bias)\n        workspace.FeedBlob(\"mean\", mean)\n        workspace.FeedBlob(\"var\", var)\n        workspace.FeedBlob(\"X_mkl\", X, device_option=mkl_do)\n        workspace.FeedBlob(\"scale_mkl\", scale, device_option=mkl_do)\n        workspace.FeedBlob(\"bias_mkl\", bias, device_option=mkl_do)\n        workspace.FeedBlob(\"mean_mkl\", mean, device_option=mkl_do)\n        workspace.FeedBlob(\"var_mkl\", var, device_option=mkl_do)\n        net = core.Net(\"test\")\n        # Makes sure that we can run relu.\n        net.SpatialBN([\"X\", \"scale\", \"bias\",\"mean\",\"var\"], \"Y\", order=\"NCHW\",\n            is_test=True,\n            epsilon=1e-5)\n        net.SpatialBN([\"X_mkl\", \"scale_mkl\", \"bias_mkl\",\"mean_mkl\",\"var_mkl\"], \"Y_mkl\", order=\"NCHW\",\n            is_test=True,\n            epsilon=1e-5, device_option=mkl_do)\n\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n        # makes sure that the results are good.\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"Y\"),\n            workspace.FetchBlob(\"Y_mkl\"),\n            atol=1e-2,\n            rtol=1e-2)\n        runtime = workspace.BenchmarkNet(net.Proto().name, 1, 100, True)\n\n        print(\"FC CPU runtime {}, MKL runtime {}.\".format(runtime[1], runtime[2]))\n\n    def testSpatialBNTrainingSpeed(self):\n        input_channel = 10\n        X = np.random.rand(1, input_channel, 100, 100).astype(np.float32) - 0.5\n        scale = np.random.rand(input_channel).astype(np.float32) + 0.5\n        bias = np.random.rand(input_channel).astype(np.float32) - 0.5\n        mean = np.random.randn(input_channel).astype(np.float32)\n        var = np.random.rand(input_channel).astype(np.float32) + 0.5\n\n        #mean = np.zeros(input_channel)\n        #var = np.zeros(input_channel)\n\n        mkl_do = core.DeviceOption(caffe2_pb2.MKLDNN)\n        # Makes sure that feed works.\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"scale\", scale)\n        workspace.FeedBlob(\"bias\", bias)\n        workspace.FeedBlob(\"mean\", mean)\n        workspace.FeedBlob(\"var\", var)\n        workspace.FeedBlob(\"X_mkl\", X, device_option=mkl_do)\n        workspace.FeedBlob(\"scale_mkl\", scale, device_option=mkl_do)\n        workspace.FeedBlob(\"bias_mkl\", bias, device_option=mkl_do)\n        workspace.FeedBlob(\"mean_mkl\", mean, device_option=mkl_do)\n        workspace.FeedBlob(\"var_mkl\", var, device_option=mkl_do)\n        net = core.Net(\"test\")\n        # Makes sure that we can run relu.\n        net.SpatialBN([\"X\", \"scale\", \"bias\",\"mean\", \"var\"],\n            [\"Y\", \"mean\", \"var\", \"saved_mean\", \"saved_var\"],\n            order=\"NCHW\",\n            is_test=False,\n            epsilon=1e-5)\n        net.SpatialBN([\"X_mkl\", \"scale_mkl\", \"bias_mkl\",\"mean_mkl\",\"var_mkl\"],\n            [\"Y_mkl\", \"mean_mkl\", \"var_mkl\", \"saved_mean_mkl\", \"saved_var_mkl\"],\n            order=\"NCHW\",\n            is_test=False,\n            epsilon=1e-5,\n            device_option=mkl_do)\n\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n\n        # makes sure that the results are good.\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"Y\"),\n            workspace.FetchBlob(\"Y_mkl\"),\n            atol=1e-2,\n            rtol=1e-2)\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"mean\"),\n            workspace.FetchBlob(\"mean_mkl\"),\n            atol=1e-2,\n            rtol=1e-2)\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"var\"),\n            workspace.FetchBlob(\"var_mkl\"),\n            atol=1e-2,\n            rtol=1e-2)\n\n        runtime = workspace.BenchmarkNet(net.Proto().name, 1, 100, True)\n\n        print(\"FC CPU runtime {}, MKL runtime {}.\".format(runtime[1], runtime[2]))\n\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_sigmoid_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn,\n                 \"Skipping as we do not have mkldnn.\")\nclass MKLSigmoidTest(hu.HypothesisTestCase):\n    @given(n=st.integers(1, 5), m=st.integers(1, 5), inplace=st.booleans(),\n           **mu.gcs)\n    def test_mkl_sigmoid(self, n, m, inplace, gc, dc):\n        X = np.random.rand(m, n).astype(np.float32)\n        op = core.CreateOperator(\n            \"Sigmoid\",\n            [\"X\"],\n            [\"Y\" if not inplace else \"X\"]\n        )\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_speed_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport unittest\n\nimport numpy as np\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import cnn, core, workspace, test_util\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn, \"Skipping as we do not have mkldnn.\")\nclass TestMKLBasic(test_util.TestCase):\n    def testReLUSpeed(self):\n        X = np.random.randn(128, 4096).astype(np.float32)\n        mkl_do = core.DeviceOption(caffe2_pb2.MKLDNN)\n        # Makes sure that feed works.\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"X_mkl\", X, device_option=mkl_do)\n        net = core.Net(\"test\")\n        # Makes sure that we can run relu.\n        net.Relu(\"X\", \"Y\")\n        net.Relu(\"X_mkl\", \"Y_mkl\", device_option=mkl_do)\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n        # makes sure that the results are good.\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"Y\"),\n            workspace.FetchBlob(\"Y_mkl\"),\n            atol=1e-10,\n            rtol=1e-10)\n        runtime = workspace.BenchmarkNet(net.Proto().name, 1, 100, True)\n\n        # The returned runtime is the time of\n        # [whole_net, cpu_op, mkl_op]\n        # so we will assume that the MKL one runs faster than the CPU one.\n\n        # Note(Yangqing): in fact, it seems that in optimized mode, this is\n        # not always guaranteed - MKL runs slower than the Eigen vectorized\n        # version, so I am turning this assertion off.\n        #self.assertTrue(runtime[1] >= runtime[2])\n\n        print(\"Relu CPU runtime {}, MKL runtime {}.\".format(runtime[1], runtime[2]))\n\n\n    def testConvSpeed(self):\n        # We randomly select a shape to test the speed. Intentionally we\n        # test a batch size of 1 since this may be the most frequent use\n        # case for MKL during deployment time.\n        X = np.random.rand(1, 256, 27, 27).astype(np.float32) - 0.5\n        W = np.random.rand(192, 256, 3, 3).astype(np.float32) - 0.5\n        b = np.random.rand(192).astype(np.float32) - 0.5\n        mkl_do = core.DeviceOption(caffe2_pb2.MKLDNN)\n        # Makes sure that feed works.\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"W\", W)\n        workspace.FeedBlob(\"b\", b)\n        workspace.FeedBlob(\"X_mkl\", X, device_option=mkl_do)\n        workspace.FeedBlob(\"W_mkl\", W, device_option=mkl_do)\n        workspace.FeedBlob(\"b_mkl\", b, device_option=mkl_do)\n        net = core.Net(\"test\")\n        # Makes sure that we can run relu.\n        net.Conv([\"X\", \"W\", \"b\"], \"Y\", pad=1, stride=1, kernel=3)\n        net.Conv([\"X_mkl\", \"W_mkl\", \"b_mkl\"], \"Y_mkl\",\n                 pad=1, stride=1, kernel=3, device_option=mkl_do)\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n        # makes sure that the results are good.\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"Y\"),\n            workspace.FetchBlob(\"Y_mkl\"),\n            atol=1e-2,\n            rtol=1e-2)\n        runtime = workspace.BenchmarkNet(net.Proto().name, 1, 100, True)\n\n        print(\"Conv CPU runtime {}, MKL runtime {}.\".format(runtime[1], runtime[2]))\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/mkl_squeeze_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\n\n\n@unittest.skipIf(\n    not workspace.C.has_mkldnn, \"Skipping as we do not have mkldnn.\"\n)\nclass MKLSqueezeTest(hu.HypothesisTestCase):\n    @given(\n        squeeze_dims=st.lists(st.integers(0, 3), min_size=1, max_size=3),\n        inplace=st.booleans(),\n        **mu.gcs\n    )\n    def test_mkl_squeeze(self, squeeze_dims, inplace, gc, dc):\n        shape = [\n            1 if dim in squeeze_dims else np.random.randint(1, 5)\n            for dim in range(4)\n        ]\n        X = np.random.rand(*shape).astype(np.float32)\n        op = core.CreateOperator(\n            \"Squeeze\", \"X\", \"X\" if inplace else \"Y\", dims=squeeze_dims\n        )\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl/rewrite_graph.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport copy\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core\n\n\ndef rewrite_init_net_simple(net):\n    for op in net.op:\n        op.device_option.device_type = caffe2_pb2.MKLDNN\n\ndef last_producer(ops, blob):\n    for (i, op) in reversed(list(enumerate(ops))):\n        if blob in op.output:\n            return i\n    raise ValueError(\"Failed to find last producer of blob, %s\", blob)\n\n\ndef rewrite_run_net_simple(net):\n    # Simple rewrite for now - assume entire graph can be executed\n    # with MKL, so just insert copy ops for external_input[0] and\n    # external_output[0]\n    def mkl_tmp(name):\n        return \"{}__MKL__\".format(name)\n\n    input_blob = net.external_input[0]\n    if input_blob != net.op[0].input[0]:\n        raise Exception(\n            \"Input blob: {} is not consumed by first op: {}\".format(\n                input_blob, net.op[0]))\n    # Modify input/outputs to point to copied MKL blobs.\n    copy_input_op = core.CreateOperator(\n        \"CopyCPUToMKL\", input_blob, mkl_tmp(input_blob))\n    net.op[0].input[0] = mkl_tmp(input_blob)\n\n    copy_output_ops = [\n        core.CreateOperator(\"CopyMKLToCPU\", mkl_tmp(output_blob), output_blob)\n        for output_blob in net.external_output]\n\n    for output_blob in net.external_output:\n        last_producer_idx = last_producer(net.op, output_blob)\n        renamed_outputs = [blob if blob != output_blob else mkl_tmp(blob)\n                           for blob in net.op[last_producer_idx].output]\n        net.op[last_producer_idx].output[:] = renamed_outputs\n        # Rename any subsequent consumers of an output blob.\n        for op in net.op[last_producer_idx + 1:]:\n            renamed_input = [blob if blob != output_blob else mkl_tmp(blob)\n                             for blob in op.input]\n            op.input[:] = renamed_input\n\n    ops = [copy_input_op] + net.op[:] + copy_output_ops\n    del net.op[:]\n    net.op.extend(ops)\n    for op in net.op:\n        op.device_option.MergeFrom(\n            core.DeviceOption(device_type=caffe2_pb2.MKLDNN))\n        op.engine = \"\"\n\n\ndef rewrite_model_helper_simple(model):\n    model = copy.deepcopy(model)\n    # All parameter initialization should run on MKL\n    rewrite_init_net_simple(model.param_init_net.Proto())\n    rewrite_run_net_simple(model.net.Proto())\n    return model\n"
  },
  {
    "path": "caffe2/python/mkl/rewrite_graph_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport numpy as np\nimport copy\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom caffe2.python.model_helper import ModelHelper\nfrom caffe2.python.models import resnet\nfrom caffe2.python import workspace, brew\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl.rewrite_graph as rewrite_graph\n\n\ndef deterministic_io(model):\n    model = copy.deepcopy(model)\n    for i, op in enumerate(model.InitProto().op):\n        op.device_option.random_seed = i + 1\n    if not model.Proto().external_output:\n        model.Proto().external_output.extend([model.Proto().op[-1].output[0]])\n    return model\n\ndef simple_fc():\n    model = ModelHelper(name=\"r\")\n    brew.fc(model, \"data\", \"fc\", 10, 10)\n    return model, [(1, 10)]\n\ndef double_matmul():\n    model = ModelHelper(name=\"r\")\n    fc0 = brew.fc(model, \"data\", \"fc0\", 10, 10)\n    fc1 = brew.fc(model, fc0, \"fc1\", 10, 10)\n    model.Proto().external_output[:] = [str(fc0), str(fc1)]\n    return model, [(1, 10)]\n\ndef simple_relu():\n    model = ModelHelper(name=\"r\")\n    brew.relu(model, \"data\", \"fc\")\n    return model, [(1, 10)]\n\n\ndef simple_mlp():\n    model = ModelHelper(name=\"r\")\n    brew.relu(\n        model,\n        brew.fc(\n            model,\n            brew.relu(\n                model,\n                brew.fc(\n                    model,\n                    \"data\",\n                    \"fc1\",\n                    10,\n                    10),\n                \"rl1\"),\n            \"fc2\",\n            10,\n            10),\n        \"rl2\")\n    return model, [(1, 10)]\n\n\ndef simple_cnn():\n    model = ModelHelper(name=\"r\", arg_scope={\"order\": \"NCHW\", \"is_test\": True})\n    brew.conv(\n        model, \"data\", 'conv1', 3, 16, kernel=3, stride=1\n    )\n    brew.spatial_bn(\n        model, 'conv1', 'conv1_spatbn', 16, epsilon=1e-3\n    )\n    brew.relu(model, 'conv1_spatbn', 'relu1')\n    return model, [(1, 3, 32, 32)]\n\n\ndef alexnet():\n    model = ModelHelper(name=\"r\", arg_scope={\"order\": \"NCHW\", \"is_test\": True})\n    conv1 = brew.conv(\n        model,\n        \"data\",\n        \"conv1\",\n        3,\n        64,\n        11, ('XavierFill', {}), ('ConstantFill', {}),\n        stride=4,\n        pad=2\n    )\n    relu1 = brew.relu(model, conv1, \"conv1\")\n    pool1 = brew.max_pool(model, relu1, \"pool1\", kernel=3, stride=2, pad=0,\n                          legacy_pad=3)\n    lrn1 = brew.lrn(\n        model, pool1, \"pool1_lrn\", size=5, alpha=1.0e-4, beta=0.75, bias=1.0)\n    conv2 = brew.conv(\n        model,\n        lrn1,\n        \"conv2\",\n        64,\n        192,\n        5,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=2\n    )\n    relu2 = brew.relu(model, conv2, \"conv2\")\n    pool2 = brew.max_pool(model, relu2, \"pool2\", kernel=3, stride=2)\n    lrn2 = brew.lrn(\n        model, pool2, \"pool2_lrn\", size=5, alpha=1.0e-4, beta=0.75, bias=1.0)\n    conv3 = brew.conv(\n        model,\n        lrn2,\n        \"conv3\",\n        192,\n        384,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu3 = brew.relu(model, conv3, \"conv3\")\n    conv4 = brew.conv(\n        model,\n        relu3,\n        \"conv4\",\n        384,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu4 = brew.relu(model, conv4, \"conv4\")\n    conv5 = brew.conv(\n        model,\n        relu4,\n        \"conv5\",\n        256,\n        256,\n        3,\n        ('XavierFill', {}),\n        ('ConstantFill', {}),\n        pad=1\n    )\n    relu5 = brew.relu(model, conv5, \"conv5\")\n    pool5 = brew.max_pool(model, relu5, \"pool5\", kernel=3, stride=2)\n    fc6 = brew.fc(\n        model,\n        pool5, \"fc6\", 256 * 6 * 6, 4096, ('XavierFill', {}),\n        ('ConstantFill', {})\n    )\n    relu6 = brew.relu(model, fc6, \"fc6\")\n    fc7 = brew.fc(\n        model, relu6, \"fc7\", 4096, 4096, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    relu7 = brew.relu(model, fc7, \"fc7\")\n    drop7 = brew.dropout(model, relu7, \"fc7_dropout\", is_test=1, ratio=0.5)\n    fc8 = brew.fc(\n        model, drop7, \"fc8\", 4096, 1000, ('XavierFill', {}), ('ConstantFill', {})\n    )\n    relu8 = brew.relu(model, fc8, \"fc8\")\n    _ = brew.dropout(model, relu8, \"fc8_dropout\", is_test=1, ratio=0.5)\n    return model, [(1, 3, 224, 224)]\n\n\ndef simple_resnet():\n    model = ModelHelper(name=\"r\", arg_scope={\"order\": \"NCHW\", \"is_test\": True})\n    resnet.create_resnet_32x32(\n        model, \"data\", num_input_channels=1, num_groups=1, num_labels=5,\n        is_test=True)\n    return model, [(1, 1, 32, 32)]\n\n\ndef complex_resnet():\n    model = ModelHelper(name=\"r\", arg_scope={\"order\": \"NCHW\", \"is_test\": True})\n    resnet.create_resnet50(\n        model, \"data\", num_input_channels=1, num_labels=5, is_test=True,\n        no_loss=True)\n    return model, [(1, 1, 224, 224)]\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn,\n                 \"Skipping as we do not have mkldnn.\")\nclass MKLRewriteTest(hu.HypothesisTestCase):\n    @given(gen=st.sampled_from([simple_relu, simple_fc,\n                                simple_mlp, simple_cnn]))\n    def test_mkl_simple_rewrite(self, gen):\n        cpu_model, (shape,) = gen()\n        cpu_model = deterministic_io(cpu_model)\n        mkl_model = rewrite_graph.rewrite_model_helper_simple(cpu_model)\n        X = np.random.randn(*shape).astype(np.float32)\n\n        def run(model):\n            self.ws.run(model.InitProto())\n            self.ws.create_blob(model.Proto().external_input[0]).feed(X)\n            self.ws.run(model.Proto())\n            return self.ws.blobs[model.Proto().external_output[0]].fetch()\n\n        np.testing.assert_allclose(run(cpu_model), run(mkl_model),\n                                   atol=1e-4, rtol=1e-4)\n\n    def test_mkl_resnet_rewrite(self):\n        cpu_model, (shape,) = complex_resnet()\n        cpu_model = deterministic_io(cpu_model)\n        mkl_model = rewrite_graph.rewrite_model_helper_simple(cpu_model)\n        np.random.seed(1701)\n        X = np.random.randn(*shape).astype(np.float32)\n\n        def run(model):\n            self.ws.run(model.InitProto())\n            self.ws.create_blob(model.Proto().external_input[0]).feed(X)\n            self.ws.run(model.Proto())\n            return self.ws.blobs[model.Proto().external_output[0]].fetch()\n        np.testing.assert_allclose(run(cpu_model), run(mkl_model),\n                                   atol=1e-4, rtol=1e-4)\n\n    def test_mkl_multi_output_rewrite(self):\n        cpu_model, shapes = double_matmul()\n        cpu_model = deterministic_io(cpu_model)\n        mkl_model = rewrite_graph.rewrite_model_helper_simple(cpu_model)\n        np.random.seed(1701)\n        Xs = [np.random.randn(*shape).astype(np.float32) for shape in shapes]\n\n        def run(model):\n            self.ws.run(model.InitProto())\n            for (name, X) in zip(model.Proto().external_input, Xs):\n                self.ws.create_blob(name).feed(X)\n            print(model.Proto())\n            self.ws.run(model.Proto())\n            return [self.ws.blobs[name].fetch()\n                    for name in model.Proto().external_output]\n\n        run(mkl_model)\n\n        np.testing.assert_allclose(run(cpu_model), run(mkl_model),\n                                   atol=1e-4, rtol=1e-4)\n\n    def test_mkl_alexnet_rewrite(self):\n        cpu_model, (shape,) = alexnet()\n        cpu_model = deterministic_io(cpu_model)\n        mkl_model = rewrite_graph.rewrite_model_helper_simple(cpu_model)\n        np.random.seed(1701)\n        X = np.random.randn(*shape).astype(np.float32)\n\n        def run(model):\n            self.ws.run(model.InitProto())\n            self.ws.create_blob(model.Proto().external_input[0]).feed(X)\n            self.ws.run(model.Proto())\n            return self.ws.blobs[model.Proto().external_output[0]].fetch()\n        np.testing.assert_allclose(run(cpu_model), run(mkl_model),\n                                   atol=1e-4, rtol=1e-4)\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/mkl_test_util.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package mkl_test_util\n# Module caffe2.python.mkl_test_util\n\"\"\"\nThe MKL test utils is a small addition on top of the hypothesis test utils\nunder caffe2/python, which allows one to more easily test MKL related\noperators.\n\"\"\"\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport hypothesis.strategies as st\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import workspace\nfrom caffe2.python import hypothesis_test_util as hu\n\ncpu_do = hu.cpu_do\ngpu_do = hu.gpu_do\nmkl_do = caffe2_pb2.DeviceOption(device_type=caffe2_pb2.MKLDNN)\ndevice_options = hu.device_options + (\n    [mkl_do] if workspace.C.has_mkldnn else [])\n\n\ndef device_checker_device_options():\n    return st.just(device_options)\n\n\ndef gradient_checker_device_option():\n    return st.sampled_from(device_options)\n\n\ngcs = dict(\n    gc=gradient_checker_device_option(),\n    dc=device_checker_device_options()\n)\n\ngcs_cpu_only = dict(gc=st.sampled_from([cpu_do]), dc=st.just([cpu_do]))\ngcs_gpu_only = dict(gc=st.sampled_from([gpu_do]), dc=st.just([gpu_do]))\ngcs_mkl_only = dict(gc=st.sampled_from([mkl_do]), dc=st.just([mkl_do]))\n\ngcs_cpu_mkl = dict(gc=st.sampled_from([cpu_do, mkl_do]), dc=st.just([cpu_do, mkl_do]))\n"
  },
  {
    "path": "caffe2/python/model_device_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nimport numpy as np\nimport unittest\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import (\n    workspace,\n    device_checker,\n    test_util,\n    model_helper,\n    brew,\n)\n\n\nclass TestMiniAlexNet(test_util.TestCase):\n\n    def _MiniAlexNetNoDropout(self, order):\n        # First, AlexNet using the cnn wrapper.\n        model = model_helper.ModelHelper(name=\"alexnet\")\n        conv1 = brew.conv(\n            model,\n            \"data\",\n            \"conv1\",\n            3,\n            16,\n            11,\n            (\"XavierFill\", {}),\n            (\"ConstantFill\", {}),\n            stride=4,\n            pad=0\n        )\n        relu1 = brew.relu(model, conv1, \"relu1\")\n        norm1 = brew.lrn(model, relu1, \"norm1\", size=5, alpha=0.0001, beta=0.75)\n        pool1 = brew.max_pool(model, norm1, \"pool1\", kernel=3, stride=2)\n        conv2 = brew.group_conv(\n            model,\n            pool1,\n            \"conv2\",\n            16,\n            32,\n            5,\n            (\"XavierFill\", {}),\n            (\"ConstantFill\", {\"value\": 0.1}),\n            group=2,\n            stride=1,\n            pad=2\n        )\n        relu2 = brew.relu(model, conv2, \"relu2\")\n        norm2 = brew.lrn(model, relu2, \"norm2\", size=5, alpha=0.0001, beta=0.75)\n        pool2 = brew.max_pool(model, norm2, \"pool2\", kernel=3, stride=2)\n        conv3 = brew.conv(\n            model,\n            pool2,\n            \"conv3\",\n            32,\n            64,\n            3,\n            (\"XavierFill\", {'std': 0.01}),\n            (\"ConstantFill\", {}),\n            pad=1\n        )\n        relu3 = brew.relu(model, conv3, \"relu3\")\n        conv4 = brew.group_conv(\n            model,\n            relu3,\n            \"conv4\",\n            64,\n            64,\n            3,\n            (\"XavierFill\", {}),\n            (\"ConstantFill\", {\"value\": 0.1}),\n            group=2,\n            pad=1\n        )\n        relu4 = brew.relu(model, conv4, \"relu4\")\n        conv5 = brew.group_conv(\n            model,\n            relu4,\n            \"conv5\",\n            64,\n            32,\n            3,\n            (\"XavierFill\", {}),\n            (\"ConstantFill\", {\"value\": 0.1}),\n            group=2,\n            pad=1\n        )\n        relu5 = brew.relu(model, conv5, \"relu5\")\n        pool5 = brew.max_pool(model, relu5, \"pool5\", kernel=3, stride=2)\n        fc6 = brew.fc(\n            model, pool5, \"fc6\", 1152, 1024, (\"XavierFill\", {}),\n            (\"ConstantFill\", {\"value\": 0.1})\n        )\n        relu6 = brew.relu(model, fc6, \"relu6\")\n        fc7 = brew.fc(\n            model, relu6, \"fc7\", 1024, 1024, (\"XavierFill\", {}),\n            (\"ConstantFill\", {\"value\": 0.1})\n        )\n        relu7 = brew.relu(model, fc7, \"relu7\")\n        fc8 = brew.fc(\n            model, relu7, \"fc8\", 1024, 5, (\"XavierFill\", {}),\n            (\"ConstantFill\", {\"value\": 0.0})\n        )\n        pred = brew.softmax(model, fc8, \"pred\")\n        xent = model.LabelCrossEntropy([pred, \"label\"], \"xent\")\n        loss = model.AveragedLoss([xent], [\"loss\"])\n        model.AddGradientOperators([loss])\n        return model\n\n    def _testMiniAlexNet(self, order):\n        # First, we get all the random initialization of parameters.\n        model = self._MiniAlexNetNoDropout(order)\n        workspace.ResetWorkspace()\n        workspace.RunNetOnce(model.param_init_net)\n        inputs = dict(\n            [(str(name), workspace.FetchBlob(str(name))) for name in\n             model.params]\n        )\n        if order == \"NCHW\":\n            inputs[\"data\"] = np.random.rand(4, 3, 227, 227).astype(np.float32)\n        else:\n            inputs[\"data\"] = np.random.rand(4, 227, 227, 3).astype(np.float32)\n        inputs[\"label\"] = np.array([1, 2, 3, 4]).astype(np.int32)\n\n        cpu_device = caffe2_pb2.DeviceOption()\n        cpu_device.device_type = caffe2_pb2.CPU\n        gpu_device = caffe2_pb2.DeviceOption()\n        gpu_device.device_type = caffe2_pb2.CUDA\n\n        checker = device_checker.DeviceChecker(0.05, [cpu_device, gpu_device])\n        ret = checker.CheckNet(\n            model.net.Proto(),\n            inputs,\n            # The indices sometimes may be sensitive to small numerical\n            # differences in the input, so we ignore checking them.\n            ignore=['_pool1_idx', '_pool2_idx', '_pool5_idx']\n        )\n        self.assertEqual(ret, True)\n\n    @unittest.skipIf(not workspace.has_gpu_support,\n                     \"No GPU support. Skipping test.\")\n    def testMiniAlexNetNCHW(self):\n        self._testMiniAlexNet(\"NCHW\")\n\n    # No Group convolution support for NHWC right now\n    #@unittest.skipIf(not workspace.has_gpu_support,\n    #                 \"No GPU support. Skipping test.\")\n    #def testMiniAlexNetNHWC(self):\n    #    self._testMiniAlexNet(\"NHWC\")\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/model_helper.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package model_helper\n# Module caffe2.python.model_helper\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, scope, workspace, helpers\nfrom caffe2.python.modeling import parameter_info\nfrom caffe2.python.modeling.parameter_sharing import (\n    parameter_sharing_context,\n)\nfrom caffe2.python.optimizer_context import (\n    OptimizerContext,\n    DEFAULT_OPTIM,\n)\nfrom caffe2.python.regularizer_context import RegularizerContext\n\nfrom future.utils import viewitems, viewkeys\nfrom itertools import chain\n\nimport logging\nimport six\n\n\n# _known_working_ops are operators that do not need special care.\n_known_working_ops = [\n    \"Accuracy\",\n    \"Adam\",\n    \"Add\",\n    \"Adagrad\",\n    \"SparseAdagrad\",\n    \"AveragedLoss\",\n    \"Cast\",\n    \"Checkpoint\",\n    \"ConstantFill\",\n    \"Copy\",\n    \"CopyGPUToCPU\",\n    \"CopyCPUToGPU\",\n    \"DequeueBlobs\",\n    \"EnsureCPUOutput\",\n    \"ExpandDims\",\n    \"Flatten\",\n    \"FlattenToVec\",\n    \"LabelCrossEntropy\",\n    \"LearningRate\",\n    \"MakeTwoClass\",\n    \"MatMul\",\n    \"NCCLAllreduce\",\n    \"NHWC2NCHW\",\n    \"PackSegments\",\n    \"Print\",\n    \"PRelu\",\n    \"ReduceFrontSum\",\n    \"Scale\",\n    \"ScatterWeightedSum\",\n    \"Sigmoid\",\n    \"SortedSegmentSum\",\n    \"Snapshot\",  # Note: snapshot is deprecated, use Checkpoint\n    \"Softmax\",\n    \"SoftmaxWithLoss\",\n    \"SquaredL2Distance\",\n    \"Squeeze\",\n    \"StopGradient\",\n    \"Summarize\",\n    \"Tanh\",\n    \"Transpose\",\n    \"UnpackSegments\",\n    \"WeightedSum\",\n    \"YellowFin\"\n]\n\n\nclass ModelHelper(object):\n    \"\"\"A helper model so we can manange models more easily. It contains net def\n    and parameter storages. You can add an Operator yourself, e.g.\n\n        model = model_helper.ModelHelper(name=\"train_net\")\n        # init your weight and bias as w and b\n        w = model.param_init_net.XavierFill(...)\n        b = model.param_init_net.ConstantFill(...)\n        fc1 = model.FC([input, w, b], output, **kwargs)\n\n    or you can use helper functions in brew module without manually\n    defining parameter initializations and operators.\n\n        model = model_helper.ModelHelper(name=\"train_net\")\n        fc1 = brew.fc(model, input, output, dim_in, dim_out, **kwargs)\n\n    \"\"\"\n\n    def __init__(self, name=None, init_params=True, allow_not_known_ops=True,\n                 skip_sparse_optim=False, param_model=None, arg_scope=None):\n        self.name = name or \"model\"\n        self.net = core.Net(self.name)\n\n        if param_model is not None:\n            self.param_init_net = param_model.param_init_net\n            self.param_to_grad = param_model.param_to_grad\n            self.params = param_model.params\n            self._parameters_info = param_model._parameters_info\n            self._computed_params = param_model._computed_params\n        else:\n            self.param_init_net = core.Net(self.name + '_init')\n            self.param_to_grad = {}\n            self.params = []\n            self._parameters_info = {}\n            self._computed_params = []\n\n        self._param_info_deprecated = []\n        self._devices = []\n        self.gradient_ops_added = False\n        self.init_params = init_params\n        self.allow_not_known_ops = allow_not_known_ops\n        self.skip_sparse_optim = skip_sparse_optim\n        self.weights = []\n        self.biases = []\n        self._arg_scope = {\n            'order': \"NCHW\",\n            'use_cudnn': True,\n            'cudnn_exhaustive_search': False,\n        }\n        if arg_scope is not None:\n            # Please notice value as None is not acceptable. We are not checking it\n            # here because we already have check in MakeArgument.\n            self._arg_scope.update(arg_scope)\n\n    @property\n    def arg_scope(self):\n        return self._arg_scope\n\n    def get_name(self):\n        return self.name\n\n    def _infer_param_shape(self, param):\n        for op in self.param_init_net.Proto().op:\n            if str(param) in op.output:\n                for arg in op.arg:\n                    if arg.name == \"shape\":\n                        return list(arg.ints)\n        return None\n\n    def _update_param_info_deprecated(self):\n        assert len(self._param_info_deprecated) <= len(self.params)\n        for param in self.params[len(self._param_info_deprecated):]:\n            if not isinstance(param, core.BlobReference):\n                raise ValueError(\n                    \"Param %s must be a BlobReference!\" % str(param))\n            self._param_info_deprecated.append(parameter_info.ParameterInfo(\n                param_id=len(self._param_info_deprecated),\n                param=param,\n                shape=self._infer_param_shape(param)))\n        for info in self._param_info_deprecated:\n            info.grad = self.param_to_grad.get(info.name)\n\n    def _normalize_tags(self, tags):\n        tags = tags or []\n        return set(tags) if isinstance(tags, list) else set([tags])\n\n    def create_param(self, param_name, shape, initializer, tags=None):\n        \"\"\"\n        Creates parameter with a given name and initializer.\n\n        If param_name is instance of BlobRefernce - then this blob will be used\n        to store parameter (no any logic will affect it's location).\n\n        If param_name is instance of a string type, then the final blob will\n        be created in the CurrentNameScope with the respect of all parameter\n        sharing logic, i.e. 'resolved_name_scope/param_name'.\n\n        Parameter sharing logic is going to override CurrentNameScope accoring\n        to the rules that are specified through ParameterSharing contexts,\n        all ParameterSharing contexts are applied recursively until there are no\n        extra overrides present, where on each step the best match will be\n        applied first.\n\n        The following examples should clarify the way ParameterSharing logic\n        works:\n\n        As an example if this function is called with parameter 'w':\n        a. Call from some scope 'global_scope' with no Parameter sharing:\n          'global_scope/w'\n        b. Call from scope 'scope_b', with override {'scope_b': 'scope_a'}:\n          'scope_a/w'\n        c. Call from scope 'scope_a', with override {'scope_a': ''}:\n          'scope_a/w'\n        d. Call from scope 'scope_b/shared', with overrides\n          {'scope_b/shared': 'scope_b', 'scope_b': 'scope_a'}:\n          'scope_a/w'\n        d. Call from scope 'scope_b/unshared', with overrides\n          {'scope_b/shared': 'scope_b', 'scope_b': 'scope_a'}:\n          'scope_a/unshared/w'\n        \"\"\"\n        # ParameterSharing works only for case when param_name is instance of\n        # a string type. If param_name is a BlobReference - no attempt for\n        # ParameterSharing will be applied.\n        if isinstance(param_name, core.BlobReference):\n            param_name = str(param_name)\n        elif isinstance(param_name, six.string_types):\n            # Parameter name will be equal to current Namescope that got\n            # resolved with the respect of parameter sharing of the scopes.\n            param_name = parameter_sharing_context.get_parameter_name(\n                param_name)\n        else:\n            raise \"Unsupported type for param_name\"\n\n        if param_name in self._parameters_info:\n            assert self._parameters_info[param_name].shape == shape\n            return self._parameters_info[param_name].blob\n\n        param_info = initializer.create_param(\n            param_name=core.BlobReference(param_name),\n            init_net=self.param_init_net,\n            shape=shape,\n        )\n        optim_context = OptimizerContext.current()\n        for tag in self._normalize_tags(tags):\n            if optim_context.has_optimizer(tag):\n                # param_info will check optimizer has not been set\n                param_info.optimizer = optim_context.get_optimizer(tag)\n        if not param_info.optimizer and optim_context.has_optimizer(DEFAULT_OPTIM):\n            param_info.optimizer = optim_context.get_optimizer(DEFAULT_OPTIM)\n\n        reg_context = RegularizerContext.current()\n        param_info.regularizer = reg_context\n\n        self._parameters_info[param_name] = param_info\n        # Add param to legacy structs as well, so all other functions for\n        # parameters are still working.\n        self.AddParameter(param_info.blob, tags)\n        return param_info.blob\n\n    def get_param_info(self, param):\n        assert isinstance(param, core.BlobReference), \\\n            \"Param {} is not a BlobReference\".format(param)\n        return self._parameters_info.get(param, None)\n\n    # This method is deprecated, use create_param method which\n    # also does parameter initialization when needed\n    def add_param_DEPRECATED(self, param, key=None, shape=None, length=None):\n        logging.warning(\"add_param method is DEPRECATED\")\n        self._update_param_info_deprecated()\n        self.AddParameter(param)\n        if key is not None and self.net.input_record() is not None:\n            idx = self.net.input_record().field_blobs().index(key)\n            key = self.net.input_record().field_names()[idx]\n        shape = shape if shape is not None else self._infer_param_shape(param)\n        if not isinstance(param, core.BlobReference):\n            raise ValueError(\"Param %s must be a BlobReference!\" % str(param))\n        self._param_info_deprecated.append(parameter_info.ParameterInfo(\n            param_id=len(self._param_info_deprecated),\n            param=param,\n            shape=shape,\n            key=key,\n            length=length,\n        ))\n        return self._param_info_deprecated[-1]\n\n    # This method is deprecated, use get_param_info method\n    def param_info(self, grad_type=None, id=None):\n        logging.info(\"param_info method is DEPRECATED\")\n        self._update_param_info_deprecated()\n        if id is not None:\n            assert grad_type is None\n            info = self._param_info_deprecated[id]\n            assert info.param_id == id\n            return info\n        elif grad_type is not None:\n            return [\n                info for info in self._param_info_deprecated\n                if info.grad_type() == grad_type]\n        else:\n            return self._param_info_deprecated\n\n    def AddParameter(self, param, tags=None):\n        assert isinstance(param, core.BlobReference)\n        tags = self._normalize_tags(tags)\n        if parameter_info.ParameterTags.COMPUTED_PARAM in tags:\n            self._computed_params.append(param)\n        else:\n            self.params.append(param)\n\n        if parameter_info.ParameterTags.WEIGHT in tags:\n            self.weights.append(param)\n        if parameter_info.ParameterTags.BIAS in tags:\n            self.biases.append(param)\n\n    @staticmethod\n    def _NormalizeNamescope(namescope):\n        if namescope is None:\n            return scope.CurrentNameScope()\n        elif namescope == '' or namescope.endswith(scope._NAMESCOPE_SEPARATOR):\n            return namescope\n        else:\n            return namescope + scope._NAMESCOPE_SEPARATOR\n\n    def GetParams(self, namescope=None, top_scope=False):\n        '''\n        Returns the params in current namescope\n        '''\n        namescope = ModelHelper._NormalizeNamescope(namescope)\n\n        if namescope == '':\n            return self.params[:]\n        elif top_scope:\n            return [\n                p for p in self.params\n                if p.GetNameScope().startswith(namescope)\n            ]\n        else:\n            return [p for p in self.params if\n                    p.GetNameScope().startswith(namescope)]\n\n    def Proto(self):\n        return self.net.Proto()\n\n    def InitProto(self):\n        return self.param_init_net.Proto()\n\n    def RunAllOnGPU(self, *args, **kwargs):\n        self.param_init_net.RunAllOnGPU(*args, **kwargs)\n        self.net.RunAllOnGPU(*args, **kwargs)\n\n    def CreateDB(self, blob_out, db, db_type, **kwargs):\n        dbreader = self.param_init_net.CreateDB(\n            [], blob_out, db=db, db_type=db_type, **kwargs)\n        return dbreader\n\n    def AddGradientOperators(self, *args, **kwargs):\n        if self.gradient_ops_added:\n            raise RuntimeError(\"You cannot run AddGradientOperators twice.\")\n        self.Validate()\n\n        self.gradient_ops_added = True\n        self.grad_map = self.net.AddGradientOperators(*args, **kwargs)\n        self.param_to_grad = self.get_param_to_grad(self.params)\n\n        # Populate ParameterInfo for all parameters if missing\n        # and add gradient blob information. So optimizers can use it\n        for param, grad in self.param_to_grad.items():\n            param_info = self.get_param_info(param)\n            if param_info:\n                param_info.grad = grad\n            else:\n                self._parameters_info[param] = parameter_info.ParameterInfo(\n                    param_id=None,\n                    param=param,\n                    grad=grad,\n                )\n\n        return self.grad_map\n\n    def get_param_to_grad(self, params):\n        '''\n        Given a list of parameters returns a dict from a parameter\n        to a corresponding gradient\n        '''\n\n        param_to_grad = {}\n        if not self.gradient_ops_added:\n            raise RuntimeError(\"You need to run AddGradientOperators first.\")\n        # We need to use empty namescope when creating the gradients\n        # to prevent duplicating the namescope prefix for gradient blobs.\n        for p in params:\n            if str(p) in self.grad_map:\n                param_to_grad[p] = self.grad_map[str(p)]\n        return param_to_grad\n\n    def GetOptimizationParamInfo(self, params=None):\n        '''\n        Returns a map for param => grad.\n        If params is not specified, all parameters will be considered.\n        '''\n        if not self.gradient_ops_added:\n            raise RuntimeError(\"Need to call AddGradientOperators first\")\n\n        param_to_grad = self.param_to_grad\n        if params:\n            param_to_grad = self.get_param_to_grad(params)\n\n        return [\n            self.get_param_info(param) for param, grad in viewitems(param_to_grad)\n            if (\n                not self.skip_sparse_optim or\n                not isinstance(grad, core.GradientSlice)\n            )\n        ]\n\n    def _Validate(self):\n        '''\n        Check for duplicate params\n        '''\n        params_list = [str(p) for p in self.params]\n        params_set = set(params_list)\n\n        dupes = []\n        if len(params_set) != len(params_list):\n            params_list = sorted(params_list)\n            for j, p in enumerate(params_list):\n                if j > 0 and params_list[j - 1] == p:\n                    if p not in dupes:\n                        dupes.append(p)\n\n        return dupes\n\n    def Validate(self):\n        dupes = self._Validate()\n        assert dupes == [], \"Duplicate params: {}\".format(dupes)\n\n    def GetComputedParams(self, namescope=None):\n        '''\n        Returns the computed params in current namescope. 'Computed params'\n        are such parameters that are not optimized via gradient descent but are\n        directly computed from data, such as the running mean and variance\n        of Spatial Batch Normalization.\n        '''\n        namescope = ModelHelper._NormalizeNamescope(namescope)\n\n        if namescope == '':\n            return self._computed_params[:]\n        else:\n            return [p for p in self._computed_params\n                    if p.GetNameScope().startswith(namescope)]\n\n    def GetAllParams(self, namescope=None):\n        return self.GetParams(namescope) + self.GetComputedParams(namescope)\n\n    def TensorProtosDBInput(\n        self, unused_blob_in, blob_out, batch_size, db, db_type, **kwargs\n    ):\n        \"\"\"TensorProtosDBInput.\"\"\"\n        assert len(unused_blob_in) == 0, \\\n            \"\"\"You cannot pass reader to model_helper.TensorProtosDBInput.\n               Use model.net.TensorProtosDBInput instead to create the op.\"\"\"\n\n        return helpers.db_input.db_input(\n            self, blob_out, batch_size, db, db_type, **kwargs)\n\n    def GetDevices(self):\n        assert len(self._devices) > 0, \\\n            \"Use data_parallel_model to run model on multiple GPUs.\"\n        return self._devices\n\n    def __getattr__(self, op_type):\n        \"\"\"Catch-all for all other operators, mostly those without params.\"\"\"\n        if op_type.startswith('__'):\n            raise AttributeError(op_type)\n\n        if not core.IsOperator(op_type):\n            raise AttributeError(\n                'Method ' + op_type + ' is not a registered operator.' +\n                ' Did you mean: [' +\n                ','.join(workspace.C.nearby_opnames(op_type)) + ']'\n            )\n        if op_type not in _known_working_ops:\n            if not self.allow_not_known_ops:\n                raise AttributeError(\n                    \"Operator {} is not known to be safe\".format(op_type))\n\n            logging.warning(\"You are creating an op that the ModelHelper \"\n                            \"does not recognize: {}.\".format(op_type))\n        return self.net.__getattr__(op_type)\n\n    def __dir__(self):\n        return sorted(set(chain(\n            dir(type(self)),\n            viewkeys(self.__dict__),\n            _known_working_ops\n        )))\n\n\ndef ExtractPredictorNet(\n    net_proto,\n    input_blobs,\n    output_blobs,\n    device=None,\n    renames=None,\n    disabled_inputs=None,\n):\n    '''\n    Takes a model net for training and returns a net which can be\n    used for prediction. For example, all gradient operators and\n    input operators are removed.\n    @param net_proto protobuf of the net you want to process (net.Proto())\n    @param input_blobs list/set of blob names that are the inputs of predictor\n    @param output_blobs list/set of blob names that are outputs of predictor\n    @param device optional device option that is assigned\n    @param renames dictionary of blob name to a new name (optional)\n    @param disabled_inputs optional set of blobs that are 'switched off'. This\n                will cause branches with those blobs as inputs to be removed\n    '''\n    predict_net = core.Net(net_proto.name + \"_predict\")\n    predict_proto = predict_net.Proto()\n\n    orig_external_inputs = set(net_proto.external_input)\n    orig_external_outputs = set(net_proto.external_output)\n    input_blobs = {str(b) for b in input_blobs}\n    known_blobs = set(orig_external_inputs).union(input_blobs)\n    output_blobs = {str(b) for b in output_blobs}\n    external_inputs = set(input_blobs)\n    external_outputs = set(output_blobs)\n\n    if renames is None:\n        renames = {}\n\n    if disabled_inputs is not None:\n        known_blobs = known_blobs - set(disabled_inputs)\n\n    ops = list(net_proto.op)\n\n    # Find the range of ops that we should include\n    try:\n        first_op_with_input = min(\n            [\n                j for j in range(len(ops))\n                if input_blobs.intersection(ops[j].input) and ops[j].type !=\n                'StopGradient'\n            ]\n        )\n    except ValueError:\n        raise Exception(\"No ops with input={}\".format(input_blobs))\n    try:\n        last_op_with_output = max(\n            [\n                j for j in range(len(ops))\n                if output_blobs.intersection(ops[j].output)\n            ]\n        )\n    except ValueError:\n        raise Exception(\"No ops with output={}\".format(output_blobs))\n\n    def validate_op(op):\n        # Check that the op does not have is_test = 0 set. This is a common\n        # pitfall with SpatialBN op, at lest.\n        for arg in op.arg:\n            if arg.name == \"is_test\" and arg.i == 0:\n                raise Exception(\n                    \"An operator had is_test=0, did you try to extract a \" +\n                    \"predictor from a train model (instead of test model)?\" +\n                    \" Op was: {}\".format(str(op))\n                )\n\n    def rename_list(proto_list):\n        # proto lists don't support assignments\n        new_list = proto_list[:]\n        for j, b in enumerate(new_list):\n            if b in renames:\n                new_list[j] = renames[b]\n\n        del proto_list[:]\n        proto_list.extend(new_list)\n\n    # Iterate through the ops and only include those whose inputs\n    # we can satisfy.\n    for op in ops[first_op_with_input:(last_op_with_output + 1)]:\n        if known_blobs.issuperset(op.input):\n\n            # Special handling for recurrent nets\n            # TODO: when standard argument type for \"nets\" is introduced,\n            # this can be more general\n            if op.type == 'RecurrentNetwork':\n                for arg in op.arg:\n                    if arg.name == 'backward_step_net':\n                        arg.ClearField(str('n'))\n                    elif arg.name == 'step_net':\n                        for step_op in arg.n.op:\n                            rename_list(step_op.input)\n                            rename_list(step_op.output)\n                            if device is not None:\n                                step_op.device_option.device_type = device.device_type\n                                step_op.device_option.cuda_gpu_id = device.cuda_gpu_id\n\n                        rename_list(arg.n.external_input)\n                        rename_list(arg.n.external_output)\n\n                        # Add additional external inputs\n                        external_inputs.update(\n                            set(arg.n.external_input).intersection(\n                                orig_external_inputs\n                            )\n                        )\n\n            if device is not None:\n                op.device_option.device_type = device.device_type\n                op.device_option.cuda_gpu_id = device.cuda_gpu_id\n            validate_op(op)\n            predict_proto.op.extend([op])\n            known_blobs.update(op.output)\n            external_inputs.update(\n                set(op.input).intersection(orig_external_inputs)\n            )\n            external_outputs.update(\n                set(op.output).intersection(orig_external_outputs)\n            )\n\n        else:\n            logging.debug(\n                \"Op {} had unknown inputs: {}\".format(\n                    op.type, set(op.input).difference(known_blobs)\n                )\n            )\n\n    # Predictor net's external inputs and outputs include only those\n    # that are part of this net.\n    predict_proto.external_input.extend(external_inputs)\n    predict_proto.external_output.extend(external_outputs)\n\n    rename_list(predict_proto.external_input)\n    rename_list(predict_proto.external_output)\n\n    renamed_input_blobs = []\n    for b in input_blobs:\n        if b in renames:\n            renamed_input_blobs.append(renames[b])\n        else:\n            renamed_input_blobs.append(b)\n\n    for op in predict_proto.op:\n        rename_list(op.input)\n        rename_list(op.output)\n\n    return predict_net, list(\n        set(predict_proto.external_input) - set(renamed_input_blobs)\n    )\n"
  },
  {
    "path": "caffe2/python/modeling/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/python/modeling/compute_norm_for_blobs.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, schema\nfrom caffe2.python.modeling.net_modifier import NetModifier\n\nimport numpy as np\n\n\nclass ComputeNormForBlobs(NetModifier):\n    \"\"\"\n    This class modifies the net passed in by adding ops to compute norms for\n    certain blobs.\n\n    Args:\n        blobs: list of blobs to compute norm for\n        logging_frequency: frequency for printing norms to logs\n        p: type of norm. Currently it supports p=1 or p=2\n    \"\"\"\n\n    def __init__(self, blobs, logging_frequency, p=2):\n        self._blobs = blobs\n        self._logging_frequency = logging_frequency\n        self._p = p\n        self._field_name_suffix = '_l{}_norm'.format(p)\n\n    def modify_net(self, net, init_net=None, grad_map=None, blob_to_device=None):\n\n        p = self._p\n\n        for blob_name in self._blobs:\n            blob = core.BlobReference(blob_name)\n            if not net.BlobIsDefined(blob):\n                raise Exception('blob {0} is not defined in net {1}'.format(\n                    blob, net.Name()))\n\n            norm_name = net.NextScopedBlob(prefix=blob + self._field_name_suffix)\n            norm = net.LpNorm(blob, norm_name, p=p)\n\n            if self._logging_frequency >= 1:\n                net.Print(norm, [], every_n=self._logging_frequency)\n\n            output_field_name = str(blob) + self._field_name_suffix\n            output_scalar = schema.Scalar((np.float, (1,)), norm)\n\n            if net.output_record() is None:\n                net.set_output_record(\n                    schema.Struct((output_field_name, output_scalar))\n                )\n            else:\n                net.AppendOutputRecordField(\n                    output_field_name,\n                    output_scalar)\n\n    def field_name_suffix(self):\n        return self._field_name_suffix\n"
  },
  {
    "path": "caffe2/python/modeling/compute_norm_for_blobs_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nfrom caffe2.python import workspace, brew, model_helper\nfrom caffe2.python.modeling.compute_norm_for_blobs import ComputeNormForBlobs\n\nimport numpy as np\n\n\nclass ComputeNormForBlobsTest(unittest.TestCase):\n    def test_compute_norm_for_blobs(self):\n        model = model_helper.ModelHelper(name=\"test\")\n        data = model.net.AddExternalInput(\"data\")\n        fc1 = brew.fc(model, data, \"fc1\", dim_in=4, dim_out=2)\n\n        # no operator name set, will use default\n        brew.fc(model, fc1, \"fc2\", dim_in=2, dim_out=1)\n\n        net_modifier = ComputeNormForBlobs(\n            blobs=['fc1_w', 'fc2_w'],\n            logging_frequency=10,\n        )\n\n        net_modifier(model.net)\n\n        workspace.FeedBlob('data', np.random.rand(10, 4).astype(np.float32))\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n\n        fc1_w = workspace.FetchBlob('fc1_w')\n        fc1_w_l2_norm = workspace.FetchBlob('fc1_w_l2_norm')\n\n        self.assertEqual(fc1_w_l2_norm.size, 1)\n        self.assertAlmostEqual(fc1_w_l2_norm[0],\n                               np.linalg.norm(fc1_w)**2,\n                               delta=1e-5)\n\n        self.assertEqual(len(model.net.Proto().op), 6)\n\n        assert 'fc1_w' + net_modifier.field_name_suffix() in\\\n            model.net.output_record().field_blobs(),\\\n            model.net.output_record().field_blobs()\n        assert 'fc2_w' + net_modifier.field_name_suffix() in\\\n            model.net.output_record().field_blobs(),\\\n            model.net.output_record().field_blobs()\n\n    def test_compute_norm_for_blobs_no_print(self):\n        model = model_helper.ModelHelper(name=\"test\")\n        data = model.net.AddExternalInput(\"data\")\n        fc1 = brew.fc(model, data, \"fc1\", dim_in=4, dim_out=2)\n\n        # no operator name set, will use default\n        brew.fc(model, fc1, \"fc2\", dim_in=2, dim_out=1)\n\n        net_modifier = ComputeNormForBlobs(\n            blobs=['fc1_w', 'fc2_w'],\n            logging_frequency=-1,\n        )\n\n        net_modifier(model.net)\n\n        workspace.FeedBlob('data', np.random.rand(10, 4).astype(np.float32))\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n\n        fc1_w = workspace.FetchBlob('fc1_w')\n        fc1_w_l2_norm = workspace.FetchBlob('fc1_w_l2_norm')\n\n        self.assertEqual(fc1_w_l2_norm.size, 1)\n        self.assertAlmostEqual(fc1_w_l2_norm[0],\n                               np.linalg.norm(fc1_w)**2,\n                               delta=1e-5)\n\n        self.assertEqual(len(model.net.Proto().op), 4)\n\n        assert 'fc1_w' + net_modifier.field_name_suffix() in\\\n            model.net.output_record().field_blobs(),\\\n            model.net.output_record().field_blobs()\n        assert 'fc2_w' + net_modifier.field_name_suffix() in\\\n            model.net.output_record().field_blobs(),\\\n            model.net.output_record().field_blobs()\n\n    def test_compute_l1_norm_for_blobs(self):\n        model = model_helper.ModelHelper(name=\"test\")\n        data = model.net.AddExternalInput(\"data\")\n        fc1 = brew.fc(model, data, \"fc1\", dim_in=4, dim_out=2)\n\n        # no operator name set, will use default\n        brew.fc(model, fc1, \"fc2\", dim_in=2, dim_out=1)\n\n        net_modifier = ComputeNormForBlobs(\n            blobs=['fc1_w', 'fc2_w'],\n            logging_frequency=10,\n            p=1,\n        )\n\n        net_modifier(model.net)\n\n        workspace.FeedBlob('data', np.random.rand(10, 4).astype(np.float32))\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n\n        fc1_w = workspace.FetchBlob('fc1_w')\n        fc1_w_l1_norm = workspace.FetchBlob('fc1_w_l1_norm')\n\n        self.assertEqual(fc1_w_l1_norm.size, 1)\n        self.assertAlmostEqual(fc1_w_l1_norm[0],\n                               np.sum(np.abs(fc1_w)),\n                               delta=1e-5)\n\n        self.assertEqual(len(model.net.Proto().op), 6)\n\n        assert 'fc1_w' + net_modifier.field_name_suffix() in\\\n            model.net.output_record().field_blobs(),\\\n            model.net.output_record().field_blobs()\n        assert 'fc2_w' + net_modifier.field_name_suffix() in\\\n            model.net.output_record().field_blobs(),\\\n            model.net.output_record().field_blobs()\n"
  },
  {
    "path": "caffe2/python/modeling/compute_statistics_for_blobs.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, schema\nfrom caffe2.python.modeling.net_modifier import NetModifier\n\nimport numpy as np\n\n\nclass ComputeStatisticsForBlobs(NetModifier):\n    \"\"\"\n    This class modifies the net passed in by adding ops to compute statistics\n    for certain blobs. For each blob in the list, its min, max, mean and standard\n    deviation will be computed.\n\n    Args:\n        blobs: list of blobs to compute norm for\n        logging_frequency: frequency for printing norms to logs\n    \"\"\"\n\n    def __init__(self, blobs, logging_frequency):\n        self._blobs = blobs\n        self._logging_frequency = logging_frequency\n        self._field_name_suffix = '_summary'\n\n    def modify_net(self, net, init_net=None, grad_map=None, blob_to_device=None):\n\n        for blob_name in self._blobs:\n            blob = core.BlobReference(blob_name)\n            if not net.BlobIsDefined(blob):\n                raise Exception('blob {0} is not defined in net {1}'.format(\n                    blob, net.Name()))\n\n            cast_blob = net.Cast(blob, to=core.DataType.FLOAT)\n            stats_name = net.NextScopedBlob(prefix=blob + self._field_name_suffix)\n            stats = net.Summarize(cast_blob, stats_name, to_file=0)\n            net.Print(stats, [], every_n=self._logging_frequency)\n\n            output_field_name = str(blob) + self._field_name_suffix\n            output_scalar = schema.Scalar((np.float, (1,)), stats)\n\n            if net.output_record() is None:\n                net.set_output_record(\n                    schema.Struct((output_field_name, output_scalar))\n                )\n            else:\n                net.AppendOutputRecordField(\n                    output_field_name,\n                    output_scalar)\n\n    def field_name_suffix(self):\n        return self._field_name_suffix\n"
  },
  {
    "path": "caffe2/python/modeling/compute_statistics_for_blobs_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nfrom caffe2.python import workspace, brew, model_helper\nfrom caffe2.python.modeling.compute_statistics_for_blobs import (\n    ComputeStatisticsForBlobs\n)\n\nimport numpy as np\n\n\nclass ComputeStatisticsForBlobsTest(unittest.TestCase):\n    def test_compute_statistics_for_blobs(self):\n        model = model_helper.ModelHelper(name=\"test\")\n        data = model.net.AddExternalInput(\"data\")\n        fc1 = brew.fc(model, data, \"fc1\", dim_in=4, dim_out=2)\n\n        # no operator name set, will use default\n        brew.fc(model, fc1, \"fc2\", dim_in=2, dim_out=1)\n\n        net_modifier = ComputeStatisticsForBlobs(\n            blobs=['fc1_w', 'fc2_w'],\n            logging_frequency=10,\n        )\n\n        net_modifier(model.net)\n\n        workspace.FeedBlob('data', np.random.rand(10, 4).astype(np.float32))\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n\n        fc1_w = workspace.FetchBlob('fc1_w')\n        fc1_w_summary = workspace.FetchBlob('fc1_w_summary')\n\n        # std is unbiased here\n        stats_ref = np.array([fc1_w.flatten().min(), fc1_w.flatten().max(),\n                     fc1_w.flatten().mean(), fc1_w.flatten().std(ddof=1)])\n\n        self.assertAlmostEqual(np.linalg.norm(stats_ref - fc1_w_summary), 0,\n                               delta=1e-5)\n        self.assertEqual(fc1_w_summary.size, 4)\n\n        self.assertEqual(len(model.net.Proto().op), 8)\n        assert 'fc1_w' + net_modifier.field_name_suffix() in\\\n            model.net.output_record().field_blobs()\n        assert 'fc2_w' + net_modifier.field_name_suffix() in\\\n            model.net.output_record().field_blobs()\n"
  },
  {
    "path": "caffe2/python/modeling/initializers.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python.core import DataType, BlobReference, ScopedBlobReference\nfrom caffe2.python.modeling.parameter_info import ParameterInfo\n\nimport six\n\n\nclass Initializer(object):\n    '''\n    This class abstracts out parameter creation. One cancome up with a new\n    Initializer in order to implement more complex parameter initializaion logic\n    '''\n\n    def __init__(self, operator_name=None, **kwargs):\n        self.operator_name = operator_name\n        self.operator_kwargs = kwargs\n\n    def update(self, operator_name, kwargs):\n        if self.operator_name is not None:\n            raise Exception(\"Operator name overwrites are not allowed\")\n        self.operator_name = operator_name\n        self.operator_kwargs = kwargs\n\n    def create_param(self, param_name, init_net, shape):\n        param = init_net.__getattr__(self.operator_name)(\n            [], param_name, shape=shape, **self.operator_kwargs)\n        return ParameterInfo(\n            param_id=None,\n            param=param,\n            shape=shape,\n        )\n\n\nclass ExternalInitializer(object):\n    '''\n    This class is used in cases when the parameter should not be initialized by\n    the initializer, but rather provided in the workspace when param_init_net is\n    executed.\n\n    Current version is not doing any real sanity checks to the parameter.\n    '''\n\n    def create_param(self, param_name, init_net, shape):\n        if isinstance(param_name, BlobReference):\n            param = BlobReference(str(param_name), init_net)\n        elif isinstance(param_name, six.string_types):\n            param = ScopedBlobReference(param_name, init_net)\n        else:\n            raise \"Unsupported type for param_name\"\n        # TODO(amalevich): Add operator that will check param in the workspace\n        return ParameterInfo(\n            param_id=None,\n            param=param,\n            shape=shape,\n        )\n\n\nclass PseudoFP16Initializer(Initializer):\n    '''\n    Used in cases when the parameter should be used at half (16-bit) precision\n    for compute purposes (i.e. on the forward and backward pass) but\n    needs to be stored and optimized at single (32-bit) precision so tiny\n    gradients with small learning rates don't underflow FP16 precision.\n    A 32-bit copy of the 16-bit blob is stored in the ParameterInfo.\n    This is helpful for mixed-precision training, see\n    https://arxiv.org/abs/1710.03740 for details.\n    '''\n    def update(self, operator_name, kwargs):\n        if self.operator_name is not None:\n            raise Exception(\"Operator name overwrites are not allowed\")\n        self.operator_name = operator_name\n        self.operator_kwargs = kwargs\n\n    def create_param(self, param_name, init_net, shape):\n        # create master fp32 copy\n        param_fp32 = init_net.__getattr__(self.operator_name)(\n            [], param_name + \"_fp32\", shape=shape,\n            **self.operator_kwargs)\n        # cast to fp16 copy\n        param = init_net.FloatToHalf(\n            param_fp32, param_name)\n\n        return ParameterInfo(\n            param_id=None,\n            param=param,\n            shape=shape,\n            blob_copy={DataType.FLOAT: param_fp32}\n        )\n\n\nclass ReversePseudoFP16Initializer(Initializer):\n    '''\n    Like PseudoFP16Initializer above, except the primary blob is taken to\n    be the 32-bit precision parameter, and the 16-bit version of the blob\n    is stored in blob_copy instead.\n    '''\n    def update(self, operator_name, kwargs):\n        if self.operator_name is not None:\n            raise Exception(\"Operator name overwrites are not allowed\")\n        self.operator_name = operator_name\n        self.operator_kwargs = kwargs\n\n    def create_param(self, param_name, init_net, shape):\n        # create master fp32 copy\n        param_fp32 = init_net.__getattr__(self.operator_name)(\n            [], param_name, shape=shape,\n            **self.operator_kwargs)\n        # cast to fp16 copy\n        param_fp16 = init_net.FloatToHalf(\n            param_fp32, param_name + \"_fp16\")\n\n        return ParameterInfo(\n            param_id=None,\n            param=param_fp32,\n            shape=shape,\n            blob_copy={DataType.FLOAT16: param_fp16}\n        )\n\ndef update_initializer(initializer_class,\n                       operator_name_and_kwargs,\n                       default_operator_name_and_kwargs):\n    '''\n    A helper function to convert from operator_name_and_kwargs to new\n    object of type initializer_class. This function serves two purposes:\n\n    1. Support for custom initialization operators being passed in\n    2. Allow user to specify a custom Initializer without overwriting\n       default operators used for initialization\n\n    If initializer_class is None, creates a default initializer using\n    the Initializer class and operator_name_and_kwargs provided\n\n    If operator_name_and_kwargs is None, uses default_operator_name_and_kwargs\n\n    returns an instantiated Initializer object\n    '''\n    def get_initializer_args():\n        return (\n            operator_name_and_kwargs or\n            default_operator_name_and_kwargs\n        )\n\n    if initializer_class is not None:\n        init = initializer_class(get_initializer_args()[0],\n                                 **get_initializer_args()[1])\n    else:\n        init = Initializer(\n            get_initializer_args()[0],\n            **get_initializer_args()[1]\n        )\n    return init\n"
  },
  {
    "path": "caffe2/python/modeling/initializers_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nfrom caffe2.python import brew, model_helper, workspace\nfrom caffe2.python.modeling.initializers import (\n        Initializer, PseudoFP16Initializer)\n\n\nclass InitializerTest(unittest.TestCase):\n    def test_fc_initializer(self):\n        model = model_helper.ModelHelper(name=\"test\")\n        data = model.net.AddExternalInput(\"data\")\n        fc1 = brew.fc(model, data, \"fc1\", dim_in=1, dim_out=1)\n\n        # no operator name set, will use default\n        fc2 = brew.fc(model, fc1, \"fc2\", dim_in=1, dim_out=1,\n                      WeightInitializer=Initializer)\n\n        # no operator name set, will use custom\n        fc3 = brew.fc(model, fc2, \"fc3\", dim_in=1, dim_out=1,\n                      WeightInitializer=Initializer,\n                      weight_init=(\"ConstantFill\", {}),\n        )\n\n        # operator name set, no initializer class set\n        fc4 = brew.fc(model, fc3, \"fc4\", dim_in=1, dim_out=1,\n                      WeightInitializer=None,\n                      weight_init=(\"ConstantFill\", {})\n        )\n\n    @unittest.skipIf(not workspace.has_gpu_support, 'No GPU support')\n    def test_fc_fp16_initializer(self):\n        model = model_helper.ModelHelper(name=\"test\")\n        data = model.net.AddExternalInput(\"data\")\n        fc1 = brew.fc(model, data, \"fc1\", dim_in=1, dim_out=1)\n\n        # default operator, PseudoFP16Initializer\n        fc2 = brew.fc(model, fc1, \"fc2\", dim_in=1, dim_out=1,\n                      WeightInitializer=PseudoFP16Initializer\n        )\n\n        # specified operator, PseudoFP16Initializer\n        fc3 = brew.fc(model, fc2, \"fc3\", dim_in=1, dim_out=1,\n                      weight_init=(\"ConstantFill\", {}),\n                      WeightInitializer=PseudoFP16Initializer\n        )\n\n    def test_fc_external_initializer(self):\n        model = model_helper.ModelHelper(name=\"test\", init_params=False)\n        data = model.net.AddExternalInput(\"data\")\n        fc1 = brew.fc(model, data, \"fc1\", dim_in=1, dim_out=1)  # noqa\n        self.assertEqual(len(model.net.Proto().op), 1)\n        self.assertEqual(len(model.param_init_net.Proto().op), 0)\n"
  },
  {
    "path": "caffe2/python/modeling/net_modifier.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport abc\nimport six\n\n\nclass NetModifier(six.with_metaclass(abc.ABCMeta, object)):\n    \"\"\"\n    An abstraction class for supporting modifying a generated net.\n    Inherited classes should implement the modify_net method where\n    related operators are added to the net.\n\n    Example usage:\n        modifier = SomeNetModifier(opts)\n        modifier(net)\n    \"\"\"\n\n    def __init__(self):\n        pass\n\n    @abc.abstractmethod\n    def modify_net(self, net, init_net=None, grad_map=None, blob_to_device=None):\n        pass\n\n    def __call__(self, net, init_net=None, grad_map=None, blob_to_device=None):\n        self.modify_net(\n            net,\n            init_net=init_net,\n            grad_map=grad_map,\n            blob_to_device=blob_to_device)\n"
  },
  {
    "path": "caffe2/python/modeling/parameter_info.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\n\nimport numpy as np\n\n\nclass ParameterTags(object):\n    BIAS = 'BIAS'\n    WEIGHT = 'WEIGHT'\n    COMPUTED_PARAM = 'COMPUTED_PARAM'\n\n\nclass ParameterType(object):\n    DENSE = 'dense'\n    SPARSE = 'sparse'\n\n\nclass ParameterInfo(object):\n\n    def __init__(\n            self, param_id, param, key=None, shape=None, length=None,\n            grad=None, blob_copy=None):\n        assert isinstance(param, core.BlobReference)\n        self.param_id = param_id\n        self.name = str(param)\n        self.blob = param\n        self.key = key\n        self.shape = shape\n        self.size = None if shape is None else np.prod(shape)\n        self.length = max(1, length if length is not None else 1)\n        self.grad = grad\n        self._cloned_init_net = None\n        # Optionally store equivalent copies of the blob\n        # in different precisions (i.e. half and float copies)\n        # stored as a dict of TensorProto.DataType -> BlobReference\n        self.blob_copy = blob_copy\n        # each param_info can have its own optimizer. It can be set within\n        # OptimizerContext (caffe2/python/optimizer.py)\n        self._optimizer = None\n\n    def grad_type(self):\n        # self.grad could be None for model parallelism with parameter server\n        if self.grad is None:\n            return\n        return (\n            ParameterType.SPARSE if isinstance(self.grad, core.GradientSlice)\n            else ParameterType.DENSE)\n\n    @property\n    def parameter(self):\n        return self.blob\n\n    @property\n    def optimizer(self):\n        return self._optimizer\n\n    @optimizer.setter\n    def optimizer(self, value):\n        assert self._optimizer is None, \"optimizer has already been set\"\n        self._optimizer = value\n\n    def __str__(self):\n        return self.name\n"
  },
  {
    "path": "caffe2/python/modeling/parameter_sharing.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import scope\n\nimport contextlib\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass ParameterSharingContext(object):\n    \"\"\"\n    This class manages scope driven way of parameter sharing across different\n    NameScopes.\n    \"\"\"\n\n    def __init__(self):\n        self._scope_overrides = {}\n        self._contexts = []\n\n    def _resolve_scope_overrides(self, candidate_scope):\n        \"\"\"\n        Recursively resolves all scope overrides, i.e multiple steps of\n        override can be used.\n\n        For example, if one provides following scope overrides:\n        {'scope_b': 'scope_a'} and within 'scope_b' - {'shared_child': ''},\n        then name 'w' will get resolved to the following blobs depending on the\n        namescope:\n          a. 'scope_a' -> 'scope_a/w'\n          b. 'scope_b' -> 'scope_a/w'\n          c. 'scope_c' -> 'scope_c/w'\n          d. 'scope_b/shared_child' -> 'scope_a/w'\n          d. 'scope_b/unshared_child' -> 'scope_a/unshared_child/w'\n        \"\"\"\n        best_scope = candidate_scope\n        best_scope_idx = 0\n        sub_scopes = candidate_scope.split(scope._NAMESCOPE_SEPARATOR)\n\n        cur_scope = ''\n        for idx, sub_scope in enumerate(sub_scopes):\n            cur_scope = cur_scope + sub_scope + scope._NAMESCOPE_SEPARATOR\n            if cur_scope in self._scope_overrides:\n                best_scope = self._scope_overrides[cur_scope]\n                best_scope_idx = idx\n        if best_scope == candidate_scope:\n            return candidate_scope\n        else:\n            return (self._resolve_scope_overrides(best_scope) +\n                    scope._NAMESCOPE_SEPARATOR.join(\n                        sub_scopes[best_scope_idx + 1:]))\n\n    def get_parameter_name(self, name):\n        candidate_scope = scope.CurrentNameScope()\n        best_scope = self._resolve_scope_overrides(candidate_scope)\n        if best_scope != candidate_scope:\n            logger.info(\"Overwiting scope {0} with scope {1}\".format(\n                candidate_scope, best_scope))\n\n        return best_scope + name\n\n    def add_scope_overrides(self, shared_scopes):\n        self._contexts.append(shared_scopes)\n        self._scope_overrides.update(shared_scopes)\n\n    def pop(self):\n        assert len(self._contexts) > 0\n        self._contexts.pop()\n        self._scope_overrides = {}\n        for x in self._contexts:\n            self._scope_overrides.update(x)\n\n\nparameter_sharing_context = ParameterSharingContext()\n\n\ndef _normalize_namescope(namescope):\n    if namescope and namescope[-1] != scope._NAMESCOPE_SEPARATOR:\n        return namescope + scope._NAMESCOPE_SEPARATOR\n    else:\n        return namescope\n\n\n@contextlib.contextmanager\ndef ParameterSharing(shared_scopes):\n    \"\"\"\n    Helper function for sharing scopes.\n    All the parameters within the shared_scopes, will be remapped with the\n    respect of CurrentNamescope()\n\n    I.e. if one calls ParameterSharing with {'scope_b': 'scope_'a'}, from the\n    scope 'some_global_scope', it'll effectively mean, that all parameters from\n    'some_global_scope/scope_b' will shared with the parameters from\n    'some_global_scope/scope_a'\n    \"\"\"\n    assert isinstance(shared_scopes, dict)\n\n    shared_scope_overrides = {}\n    current_scope = scope.CurrentNameScope()\n    for k, v in shared_scopes.items():\n        assert not v.startswith(k), (\n            \"Illegal override for parameter sharing. {} is prefix of {}\".\n            format(k, v))\n        k = current_scope + k\n        v = current_scope + v\n        # Normalize all the scopes, so scope_a and scope_a/ are equivalent\n        k = _normalize_namescope(k)\n        v = _normalize_namescope(v)\n        shared_scope_overrides[k] = v\n\n    try:\n        parameter_sharing_context.add_scope_overrides(shared_scope_overrides)\n        yield\n    finally:\n        parameter_sharing_context.pop()\n"
  },
  {
    "path": "caffe2/python/modeling/parameter_sharing_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import brew, model_helper, scope\nfrom caffe2.python.modeling.parameter_sharing import (\n    ParameterSharing,\n    parameter_sharing_context,\n)\nfrom caffe2.python.modeling.initializers import (\n    Initializer\n)\nimport unittest\n\n\nclass ParameterSharingTest(unittest.TestCase):\n\n    def test_parameter_sharing_default_scopes(self):\n        # Test no sharing default scopes\n        param_1 = parameter_sharing_context.get_parameter_name('w')\n        self.assertEquals(param_1, 'w')\n        with scope.NameScope('scope'):\n            param_2 = parameter_sharing_context.get_parameter_name('w')\n            self.assertEquals(param_2, 'scope/w')\n            with scope.NameScope('scope_2'):\n                param_3 = parameter_sharing_context.get_parameter_name('w')\n                self.assertEquals(param_3, 'scope/scope_2/w')\n\n    def test_parameter_sharing_nested_scopes(self):\n        # Test parameter sharing\n        with scope.NameScope('global_scope'):\n            with ParameterSharing({'model_b': 'model_a'}):\n                param_global = parameter_sharing_context.get_parameter_name('w')\n                self.assertEquals(param_global, 'global_scope/w')\n                # This scope is overridden to match 'model_a'\n                with scope.NameScope('model_b'):\n                    with ParameterSharing({'shared_scope': ''}):\n                        param_4 = parameter_sharing_context.get_parameter_name(\n                            'w')\n                        self.assertEquals(param_4, 'global_scope/model_a/w')\n                        with scope.NameScope('shared_scope'):\n                            param_5 = parameter_sharing_context.\\\n                                get_parameter_name('w')\n                            self.assertEquals(param_5, 'global_scope/model_a/w')\n                # This scope is supposed to have not sharing\n                with scope.NameScope('model_c'):\n                    with ParameterSharing({'shared_scope': ''}):\n                        param_4 = parameter_sharing_context.get_parameter_name(\n                            'w')\n                        self.assertEquals(param_4, 'global_scope/model_c/w')\n                        with scope.NameScope('shared_scope'):\n                            param_5 = parameter_sharing_context.\\\n                                get_parameter_name('w')\n                            self.assertEquals(param_5, 'global_scope/model_c/w')\n\n    def test_parameter_sharing_subscopes(self):\n        # Sharing only one of the subscopes\n        with ParameterSharing({'global_scope/b': 'global_scope/a'}):\n            with scope.NameScope('global_scope'):\n                param_6 = parameter_sharing_context.get_parameter_name('w')\n                self.assertEquals(param_6, 'global_scope/w')\n                with scope.NameScope('a'):\n                    param_7 = parameter_sharing_context.get_parameter_name('w')\n                    self.assertEquals(param_7, 'global_scope/a/w')\n                with scope.NameScope('b'):\n                    param_8 = parameter_sharing_context.get_parameter_name('w')\n                    self.assertEquals(param_8, 'global_scope/a/w')\n                with scope.NameScope('c'):\n                    param_9 = parameter_sharing_context.get_parameter_name('w')\n                    self.assertEquals(param_9, 'global_scope/c/w')\n\n    def test_create_param(self):\n        model = model_helper.ModelHelper(name=\"test\")\n        # Test no sharing default scopes\n        p1 = model.create_param(\n            'w',\n            shape=[2],\n            initializer=Initializer(\"ConstantFill\")\n        )\n        with scope.NameScope('some_global_scope'):\n            p2 = model.create_param(\n                'w',\n                shape=[2],\n                initializer=Initializer(\"ConstantFill\")\n            )\n        self.assertNotEqual(model.get_param_info(p1), None)\n        self.assertNotEqual(model.get_param_info(p2), None)\n        self.assertNotEqual(model.get_param_info(p1), model.get_param_info(p2))\n        model.Validate()\n\n    def test_deep_hierarchy(self):\n        model = model_helper.ModelHelper(name=\"test\")\n        with ParameterSharing({'a': 'b'}):\n            with scope.NameScope('a'):\n                with ParameterSharing({'c': 'd'}):\n                    with scope.NameScope('c'):\n                        with ParameterSharing({'e': 'f'}):\n                            with scope.NameScope('e'):\n                                p = model.create_param(\n                                    'w',\n                                    shape=[2],\n                                    initializer=Initializer(\"ConstantFill\")\n                                )\n        self.assertNotEqual(model.get_param_info(p), None)\n\n\n    def test_parameter_sharing_brew(self):\n        # Test no sharing default scopes\n        model = model_helper.ModelHelper(name=\"test\")\n        data = model.net.AddExternalInput(\"data\")\n        fc1 = brew.fc(model, data, \"fc1\", dim_in=16, dim_out=16)\n        # Shared params are expected to share the same shape and fail if it's\n        # not true\n        with self.assertRaises(AssertionError):\n            _ = brew.fc(model, data, \"fc1\", dim_in=2, dim_out=2)  # noqa\n\n        output_blobs = set()\n        with scope.NameScope('some_global_scope'):\n            with scope.NameScope('model_a'):\n                output_blobs.add(str(brew.fc(model, fc1, 'output', 16, 16)))\n            with ParameterSharing({'model_b': 'model_a'}),\\\n                    scope.NameScope('model_b'):\n                with ParameterSharing({'shared_1': '', 'shared_2': ''}):\n                    # All params in DenseLayers from shared_1, shared_2 and\n                    # model_a are shared and will be pointing to:\n                    # [some_global_scope/model_a/output_W,\n                    #  some_global_scope/model_a/output_b]\n                    with scope.NameScope('shared_1'):\n                        output_blobs.add(\n                            str(brew.fc(model, fc1, 'output', 16, 16)))\n                    with scope.NameScope('shared_2'):\n                        output_blobs.add(\n                            str(brew.fc(model, fc1, 'output', 16, 16)))\n                    # Params of this layer are not shared with anyone unless\n                    # there is some explicit sharing with model_a/unshared (not\n                    # in this example).\n                    # Names of the blobs are\n                    # [some_global_scope/model_a/unshared/output_W,\n                    #  some_global_scope/model_a/unshared/output_b]\n                    with scope.NameScope('unshared'):\n                        output_blobs.add(\n                            str(brew.fc(model, fc1, 'output', 16, 16)))\n\n        self.assertEqual(len(model._parameters_info), 6)\n        self.assertEqual(len(output_blobs), 4)\n        self.assertEqual(sorted(model._parameters_info.keys()), [\n            'fc1_b',\n            'fc1_w',\n            'some_global_scope/model_a/output_b',\n            'some_global_scope/model_a/output_w',\n            'some_global_scope/model_a/unshared/output_b',\n            'some_global_scope/model_a/unshared/output_w',\n        ])\n        model.Validate()\n"
  },
  {
    "path": "caffe2/python/models/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/python/models/__sym_init__.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport os\nfrom caffe2.proto import caffe2_pb2\n\n\ndef _parseFile(filename):\n    out_net = caffe2_pb2.NetDef()\n    # TODO(bwasti): A more robust handler for pathnames.\n    dir_path = os.path.dirname(__file__)\n    with open('{dir_path}/{filename}'.format(dir_path=dir_path,\n                                             filename=filename), 'rb') as f:\n        out_net.ParseFromString(f.read())\n    return out_net\n\n\ninit_net = _parseFile('init_net.pb')\npredict_net = _parseFile('predict_net.pb')\n"
  },
  {
    "path": "caffe2/python/models/download.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package download\n# Module caffe2.python.models.download\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport argparse\nimport os\nimport sys\nimport signal\nimport re\n\n# Import urllib\ntry:\n    import urllib.error as urlliberror\n    import urllib.request as urllib\n    HTTPError = urlliberror.HTTPError\n    URLError = urlliberror.URLError\nexcept ImportError:\n    import urllib2 as urllib\n    HTTPError = urllib.HTTPError\n    URLError = urllib.URLError\n\n# urllib requires more work to deal with a redirect, so not using vanity url\nDOWNLOAD_BASE_URL = \"https://s3.amazonaws.com/download.caffe2.ai/models/\"\nDOWNLOAD_COLUMNS = 70\n\n\n# Don't let urllib hang up on big downloads\ndef signalHandler(signal, frame):\n    print(\"Killing download...\")\n    exit(0)\n\n\nsignal.signal(signal.SIGINT, signalHandler)\n\n\ndef deleteDirectory(top_dir):\n    for root, dirs, files in os.walk(top_dir, topdown=False):\n        for name in files:\n            os.remove(os.path.join(root, name))\n        for name in dirs:\n            os.rmdir(os.path.join(root, name))\n    os.rmdir(top_dir)\n\n\ndef progressBar(percentage):\n    full = int(DOWNLOAD_COLUMNS * percentage / 100)\n    bar = full * \"#\" + (DOWNLOAD_COLUMNS - full) * \" \"\n    sys.stdout.write(u\"\\u001b[1000D[\" + bar + \"] \" + str(percentage) + \"%\")\n    sys.stdout.flush()\n\n\ndef downloadFromURLToFile(url, filename, show_progress=True):\n    try:\n        print(\"Downloading from {url}\".format(url=url))\n        response = urllib.urlopen(url)\n        size = int(response.info().get('Content-Length').strip())\n        chunk = min(size, 8192)\n        print(\"Writing to {filename}\".format(filename=filename))\n        if show_progress:\n            downloaded_size = 0\n            progressBar(0)\n        with open(filename, \"wb\") as local_file:\n            while True:\n                data_chunk = response.read(chunk)\n                if not data_chunk:\n                    break\n                local_file.write(data_chunk)\n                if show_progress:\n                    downloaded_size += len(data_chunk)\n                    progressBar(int(100 * downloaded_size / size))\n        print(\"\")  # New line to fix for progress bar\n    except HTTPError as e:\n        raise Exception(\"Could not download model. [HTTP Error] {code}: {reason}.\"\n                        .format(code=e.code, reason=e.reason))\n    except URLError as e:\n        raise Exception(\"Could not download model. [URL Error] {reason}.\"\n                        .format(reason=e.reason))\n    except Exception as e:\n        raise e\n\n\ndef getURLFromName(name, filename):\n    return \"{base_url}{name}/{filename}\".format(base_url=DOWNLOAD_BASE_URL,\n                                                name=name, filename=filename)\n\n\ndef downloadModel(model, args):\n    # Figure out where to store the model\n    model_folder = '{folder}'.format(folder=model)\n    dir_path = os.path.dirname(os.path.realpath(__file__))\n    if args.install:\n        model_folder = '{dir_path}/{folder}'.format(dir_path=dir_path,\n                                                    folder=model)\n\n    # Check if that folder is already there\n    if os.path.exists(model_folder) and not os.path.isdir(model_folder):\n        if not args.force:\n            raise Exception(\"Cannot create folder for storing the model,\\\n                            there exists a file of the same name.\")\n        else:\n            print(\"Overwriting existing file! ({filename})\"\n                  .format(filename=model_folder))\n            os.remove(model_folder)\n    if os.path.isdir(model_folder):\n        if not args.force:\n            response = \"\"\n            query = \"Model already exists, continue? [y/N] \"\n            try:\n                response = raw_input(query)\n            except NameError:\n                response = input(query)\n            if response.upper() == 'N' or not response:\n                print(\"Cancelling download...\")\n                exit(0)\n        print(\"Overwriting existing folder! ({filename})\".format(filename=model_folder))\n        deleteDirectory(model_folder)\n\n    # Now we can safely create the folder and download the model\n    os.makedirs(model_folder)\n    for f in ['predict_net.pb', 'init_net.pb']:\n        try:\n            downloadFromURLToFile(getURLFromName(model, f),\n                                  '{folder}/{f}'.format(folder=model_folder,\n                                                        f=f))\n        except Exception as e:\n            print(\"Abort: {reason}\".format(reason=str(e)))\n            print(\"Cleaning up...\")\n            deleteDirectory(model_folder)\n            exit(0)\n\n    if args.install:\n        os.symlink(\"{folder}/__sym_init__.py\".format(folder=dir_path),\n                   \"{folder}/__init__.py\".format(folder=model_folder))\n\n\ndef validModelName(name):\n    invalid_names = ['__init__']\n    if name in invalid_names:\n        return False\n    if not re.match(\"^[/0-9a-zA-Z_]+$\", name):\n        return False\n    return True\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(\n        description='Download or install pretrained models.')\n    parser.add_argument('model', nargs='+',\n                        help='Model to download/install.')\n    parser.add_argument('-i', '--install', action='store_true',\n                        help='Install the model.')\n    parser.add_argument('-f', '--force', action='store_true',\n                        help='Force a download/installation.')\n    args = parser.parse_args()\n    for model in args.model:\n        if validModelName(model):\n            downloadModel(model, args)\n        else:\n            print(\"'{}' is not a valid model name.\".format(model))\n"
  },
  {
    "path": "caffe2/python/models/resnet.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package resnet\n# Module caffe2.python.models.resnet\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nfrom caffe2.python import brew\n'''\nUtility for creating ResNets\nSee \"Deep Residual Learning for Image Recognition\" by He, Zhang et. al. 2015\n'''\n\n\nclass ResNetBuilder():\n    '''\n    Helper class for constructing residual blocks.\n    '''\n\n    def __init__(self, model, prev_blob, no_bias, is_test, spatial_bn_mom=0.9):\n        self.model = model\n        self.comp_count = 0\n        self.comp_idx = 0\n        self.prev_blob = prev_blob\n        self.is_test = is_test\n        self.spatial_bn_mom = spatial_bn_mom\n        self.no_bias = 1 if no_bias else 0\n\n    def add_conv(self, in_filters, out_filters, kernel, stride=1, pad=0):\n        self.comp_idx += 1\n        self.prev_blob = brew.conv(\n            self.model,\n            self.prev_blob,\n            'comp_%d_conv_%d' % (self.comp_count, self.comp_idx),\n            in_filters,\n            out_filters,\n            weight_init=(\"MSRAFill\", {}),\n            kernel=kernel,\n            stride=stride,\n            pad=pad,\n            no_bias=self.no_bias,\n        )\n        return self.prev_blob\n\n    def add_relu(self):\n        self.prev_blob = brew.relu(\n            self.model,\n            self.prev_blob,\n            self.prev_blob,  # in-place\n        )\n        return self.prev_blob\n\n    def add_spatial_bn(self, num_filters):\n        self.prev_blob = brew.spatial_bn(\n            self.model,\n            self.prev_blob,\n            'comp_%d_spatbn_%d' % (self.comp_count, self.comp_idx),\n            num_filters,\n            epsilon=1e-3,\n            momentum=self.spatial_bn_mom,\n            is_test=self.is_test,\n        )\n        return self.prev_blob\n\n    '''\n    Add a \"bottleneck\" component as decribed in He et. al. Figure 3 (right)\n    '''\n\n    def add_bottleneck(\n        self,\n        input_filters,   # num of feature maps from preceding layer\n        base_filters,    # num of filters internally in the component\n        output_filters,  # num of feature maps to output\n        down_sampling=False,\n        spatial_batch_norm=True,\n    ):\n        self.comp_idx = 0\n        shortcut_blob = self.prev_blob\n\n        # 1x1\n        self.add_conv(\n            input_filters,\n            base_filters,\n            kernel=1,\n            stride=1\n        )\n\n        if spatial_batch_norm:\n            self.add_spatial_bn(base_filters)\n\n        self.add_relu()\n\n        # 3x3 (note the pad, required for keeping dimensions)\n        self.add_conv(\n            base_filters,\n            base_filters,\n            kernel=3,\n            stride=(1 if down_sampling is False else 2),\n            pad=1\n        )\n\n        if spatial_batch_norm:\n            self.add_spatial_bn(base_filters)\n        self.add_relu()\n\n        # 1x1\n        last_conv = self.add_conv(base_filters, output_filters, kernel=1)\n        if spatial_batch_norm:\n            last_conv = self.add_spatial_bn(output_filters)\n\n        # Summation with input signal (shortcut)\n        # If we need to increase dimensions (feature maps), need to\n        # do a projection for the short cut\n        if (output_filters > input_filters):\n            shortcut_blob = brew.conv(\n                self.model,\n                shortcut_blob,\n                'shortcut_projection_%d' % self.comp_count,\n                input_filters,\n                output_filters,\n                weight_init=(\"MSRAFill\", {}),\n                kernel=1,\n                stride=(1 if down_sampling is False else 2),\n                no_bias=self.no_bias,\n            )\n            if spatial_batch_norm:\n                shortcut_blob = brew.spatial_bn(\n                    self.model,\n                    shortcut_blob,\n                    'shortcut_projection_%d_spatbn' % self.comp_count,\n                    output_filters,\n                    epsilon=1e-3,\n                    momentum=self.spatial_bn_mom,\n                    is_test=self.is_test,\n                )\n\n        self.prev_blob = brew.sum(\n            self.model, [shortcut_blob, last_conv],\n            'comp_%d_sum_%d' % (self.comp_count, self.comp_idx)\n        )\n        self.comp_idx += 1\n        self.add_relu()\n\n        # Keep track of number of high level components if this ResNetBuilder\n        self.comp_count += 1\n\n    def add_simple_block(\n        self,\n        input_filters,\n        num_filters,\n        down_sampling=False,\n        spatial_batch_norm=True\n    ):\n        self.comp_idx = 0\n        shortcut_blob = self.prev_blob\n\n        # 3x3\n        self.add_conv(\n            input_filters,\n            num_filters,\n            kernel=3,\n            stride=(1 if down_sampling is False else 2),\n            pad=1\n        )\n\n        if spatial_batch_norm:\n            self.add_spatial_bn(num_filters)\n        self.add_relu()\n\n        last_conv = self.add_conv(num_filters, num_filters, kernel=3, pad=1)\n        if spatial_batch_norm:\n            last_conv = self.add_spatial_bn(num_filters)\n\n        # Increase of dimensions, need a projection for the shortcut\n        if (num_filters != input_filters):\n            shortcut_blob = brew.conv(\n                self.model,\n                shortcut_blob,\n                'shortcut_projection_%d' % self.comp_count,\n                input_filters,\n                num_filters,\n                weight_init=(\"MSRAFill\", {}),\n                kernel=1,\n                stride=(1 if down_sampling is False else 2),\n                no_bias=self.no_bias,\n            )\n            if spatial_batch_norm:\n                shortcut_blob = brew.spatial_bn(\n                    self.model,\n                    shortcut_blob,\n                    'shortcut_projection_%d_spatbn' % self.comp_count,\n                    num_filters,\n                    epsilon=1e-3,\n                    is_test=self.is_test,\n                )\n\n        self.prev_blob = brew.sum(\n            self.model, [shortcut_blob, last_conv],\n            'comp_%d_sum_%d' % (self.comp_count, self.comp_idx)\n        )\n        self.comp_idx += 1\n        self.add_relu()\n\n        # Keep track of number of high level components if this ResNetBuilder\n        self.comp_count += 1\n\n\n# The conv1 and final_avg kernel/stride args provide a basic mechanism for\n# adapting resnet50 for different sizes of input images.\ndef create_resnet50(\n    model,\n    data,\n    num_input_channels,\n    num_labels,\n    label=None,\n    is_test=False,\n    no_loss=False,\n    no_bias=0,\n    conv1_kernel=7,\n    conv1_stride=2,\n    final_avg_kernel=7,\n):\n    # conv1 + maxpool\n    brew.conv(\n        model,\n        data,\n        'conv1',\n        num_input_channels,\n        64,\n        weight_init=(\"MSRAFill\", {}),\n        kernel=conv1_kernel,\n        stride=conv1_stride,\n        pad=3,\n        no_bias=no_bias\n    )\n\n    brew.spatial_bn(\n        model,\n        'conv1',\n        'conv1_spatbn_relu',\n        64,\n        epsilon=1e-3,\n        momentum=0.1,\n        is_test=is_test\n    )\n    brew.relu(model, 'conv1_spatbn_relu', 'conv1_spatbn_relu')\n    brew.max_pool(model, 'conv1_spatbn_relu', 'pool1', kernel=3, stride=2)\n\n    # Residual blocks...\n    builder = ResNetBuilder(model, 'pool1', no_bias=no_bias,\n                            is_test=is_test, spatial_bn_mom=0.1)\n\n    # conv2_x (ref Table 1 in He et al. (2015))\n    builder.add_bottleneck(64, 64, 256)\n    builder.add_bottleneck(256, 64, 256)\n    builder.add_bottleneck(256, 64, 256)\n\n    # conv3_x\n    builder.add_bottleneck(256, 128, 512, down_sampling=True)\n    for _ in range(1, 4):\n        builder.add_bottleneck(512, 128, 512)\n\n    # conv4_x\n    builder.add_bottleneck(512, 256, 1024, down_sampling=True)\n    for _ in range(1, 6):\n        builder.add_bottleneck(1024, 256, 1024)\n\n    # conv5_x\n    builder.add_bottleneck(1024, 512, 2048, down_sampling=True)\n    builder.add_bottleneck(2048, 512, 2048)\n    builder.add_bottleneck(2048, 512, 2048)\n\n    # Final layers\n    final_avg = brew.average_pool(\n        model,\n        builder.prev_blob,\n        'final_avg',\n        kernel=final_avg_kernel,\n        stride=1,\n        global_pooling=True,\n    )\n\n    # Final dimension of the \"image\" is reduced to 7x7\n    last_out = brew.fc(\n        model, final_avg, 'last_out_L{}'.format(num_labels), 2048, num_labels\n    )\n\n    if no_loss:\n        return last_out\n\n    # If we create model for training, use softmax-with-loss\n    if (label is not None):\n        (softmax, loss) = model.SoftmaxWithLoss(\n            [last_out, label],\n            [\"softmax\", \"loss\"],\n        )\n\n        return (softmax, loss)\n    else:\n        # For inference, we just return softmax\n        return brew.softmax(model, last_out, \"softmax\")\n\n\ndef create_resnet_32x32(\n    model, data, num_input_channels, num_groups, num_labels, is_test=False\n):\n    '''\n    Create residual net for smaller images (sec 4.2 of He et. al (2015))\n    num_groups = 'n' in the paper\n    '''\n    # conv1 + maxpool\n    brew.conv(\n        model, data, 'conv1', num_input_channels, 16, kernel=3, stride=1\n    )\n    brew.spatial_bn(\n        model, 'conv1', 'conv1_spatbn', 16, epsilon=1e-3, is_test=is_test\n    )\n    brew.relu(model, 'conv1_spatbn', 'relu1')\n\n    # Number of blocks as described in sec 4.2\n    filters = [16, 32, 64]\n\n    builder = ResNetBuilder(model, 'relu1', no_bias=0, is_test=is_test)\n    prev_filters = 16\n    for groupidx in range(0, 3):\n        for blockidx in range(0, 2 * num_groups):\n            builder.add_simple_block(\n                prev_filters if blockidx == 0 else filters[groupidx],\n                filters[groupidx],\n                down_sampling=(True if blockidx == 0 and\n                               groupidx > 0 else False))\n        prev_filters = filters[groupidx]\n\n    # Final layers\n    brew.average_pool(\n        model, builder.prev_blob, 'final_avg', kernel=8, stride=1\n    )\n    brew.fc(model, 'final_avg', 'last_out', 64, num_labels)\n    softmax = brew.softmax(model, 'last_out', 'softmax')\n    return softmax\n"
  },
  {
    "path": "caffe2/python/models/resnet_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nimport time\n\nfrom caffe2.python import workspace, cnn, memonger, core\nimport caffe2.python.models.resnet as resnet\nimport hypothesis.strategies as st\nfrom hypothesis import given, settings\nimport caffe2.python.hypothesis_test_util as hu\n\n\ndef has_blob(proto, needle):\n    for op in proto.op:\n        for inp in op.input:\n            if inp == needle:\n                return True\n        for outp in op.output:\n            if outp == needle:\n                return True\n    return False\n\n\ndef count_blobs(proto):\n    blobs = set()\n    for op in proto.op:\n        blobs = blobs.union(set(op.input)).union(set(op.output))\n    return len(blobs)\n\n\ndef count_shared_blobs(proto):\n    blobs = set()\n    for op in proto.op:\n        blobs = blobs.union(set(op.input)).union(set(op.output))\n    return len([b for b in blobs if \"_shared\" in b])\n\n\nclass ResnetMemongerTest(hu.HypothesisTestCase):\n\n    @given(with_shapes=st.booleans(), **hu.gcs_cpu_only)\n    @settings(max_examples=2, timeout=120)\n    def test_resnet_shared_grads(self, with_shapes, gc, dc):\n        model = cnn.CNNModelHelper(\n            order=\"NCHW\",\n            name=\"test\",\n            cudnn_exhaustive_search=True,\n        )\n        with core.NameScope(\"gpu_0\"):\n            data = model.net.AddExternalInput(\"gpu_0/data\")\n            label = model.net.AddExternalInput(\"gpu_0/label\")\n            (_softmax, loss) = resnet.create_resnet50(\n                model,\n                data,\n                num_input_channels=3,\n                num_labels=1000,\n                label=label,\n                is_test=False,\n            )\n\n        param_to_grad = model.AddGradientOperators([loss])\n\n        (shapes, types) = workspace.InferShapesAndTypes(\n            [model.param_init_net, model.net],\n            {'gpu_0/data': [4, 3, 227, 227],\n                         'gpu_0/label': [4]},\n        )\n\n        count_before = count_blobs(model.net.Proto())\n        optim_proto = memonger.share_grad_blobs(\n            model.net,\n            [\"gpu_0/loss\"],\n            set(model.param_to_grad.values()),\n            \"gpu_0/\",\n            share_activations=True,\n            dont_share_blobs=set([str(param_to_grad[\"gpu_0/conv1_w\"])]),\n            blob_shapes=shapes if with_shapes else None,\n        )\n        count_after = count_blobs(optim_proto)\n        self.assertTrue(count_after < count_before)\n\n        # Run model and compare results. We check that the loss is same\n        # and also that the final gradient (conv1_w_grad is same)\n        workspace.RunNetOnce(model.param_init_net)\n        data = np.random.rand(4, 3, 227, 227).astype(np.float32)\n        label = (np.random.rand(4) * 1000).astype(np.int32)\n\n        workspace.FeedBlob(\"gpu_0/data\", data)\n        workspace.FeedBlob(\"gpu_0/label\", label)\n\n        workspace.RunNetOnce(model.net)\n        model.net.Proto().type = 'dag'\n        model.net.Proto().num_workers = 4\n        loss1 = workspace.FetchBlob(\"gpu_0/last_out_L1000\")\n        conv1_w_grad = workspace.FetchBlob(param_to_grad[\"gpu_0/conv1_w\"])\n        workspace.FeedBlob(param_to_grad[\"gpu_0/conv1_w\"], np.array([0.0]))\n\n        workspace.RunNetOnce(optim_proto)\n        optimized_loss1 = workspace.FetchBlob(\"gpu_0/last_out_L1000\")\n        optim_conv1_w_grad = workspace.FetchBlob(param_to_grad[\"gpu_0/conv1_w\"])\n\n        print(\"before: {} after: {}\".format(count_before, count_after))\n\n        np.testing.assert_almost_equal(loss1, optimized_loss1)\n        np.testing.assert_almost_equal(conv1_w_grad, optim_conv1_w_grad)\n\n    def test_resnet_forward_only(self):\n        model = cnn.CNNModelHelper(\n            order=\"NCHW\",\n            name=\"test\",\n            cudnn_exhaustive_search=True,\n        )\n        with core.NameScope(\"gpu_0\"):\n                data = model.net.AddExternalInput(\"gpu_0/data\")\n                resnet.create_resnet50(\n                    model,\n                    data,\n                    num_input_channels=3,\n                    num_labels=1000,\n                    is_test=True\n                )\n\n        count_before = count_blobs(model.net.Proto())\n        optim_proto = memonger.optimize_inference_for_dag(\n            model.net, [\"gpu_0/data\"], \"gpu_0/\"\n        )\n        count_after = count_blobs(optim_proto)\n        num_shared_blobs = count_shared_blobs(optim_proto)\n\n        # Run model and compare results\n        workspace.RunNetOnce(model.param_init_net)\n        data = np.random.rand(4, 3, 227, 227).astype(np.float32)\n\n        workspace.FeedBlob(\"gpu_0/data\", data)\n        workspace.RunNetOnce(model.net)\n        model.net.Proto().type = 'dag'\n        model.net.Proto().num_workers = 4\n        loss1 = workspace.FetchBlob(\"gpu_0/last_out_L1000\")\n\n        workspace.RunNetOnce(optim_proto)\n        optimized_loss1 = workspace.FetchBlob(\"gpu_0/last_out_L1000\")\n        self.assertTrue(count_after < count_before)\n        self.assertTrue(num_shared_blobs < 7 and num_shared_blobs > 0)\n        np.testing.assert_almost_equal(loss1, optimized_loss1)\n\n    def test_resnet_forward_only_fast_simplenet(self):\n        '''\n        Test C++ memonger that is only for simple nets\n        '''\n        model = cnn.CNNModelHelper(\n            order=\"NCHW\",\n            name=\"test\",\n            cudnn_exhaustive_search=True,\n        )\n        with core.NameScope(\"gpu_0\"):\n                data = model.net.AddExternalInput(\"gpu_0/data\")\n                resnet.create_resnet50(\n                    model,\n                    data,\n                    num_input_channels=3,\n                    num_labels=1000,\n                    is_test=True\n                )\n\n        count_before = count_blobs(model.net.Proto())\n        t = time.time()\n        optim_proto = memonger.optimize_inference_fast(\n            model.net.Proto(),\n            set([\"gpu_0/data\", \"gpu_0/last_out_L1000\"]).union(\n                set(model.net.Proto().external_input))\n        )\n        print(\"Optimization took {} secs\".format(time.time() - t))\n        count_after = count_blobs(optim_proto)\n        num_shared_blobs = count_shared_blobs(optim_proto)\n\n        self.assertTrue(count_after < count_before)\n        print(count_after, count_before, num_shared_blobs)\n        self.assertTrue(num_shared_blobs < 7 and num_shared_blobs > 0)\n\n        # Run model and compare results\n        workspace.RunNetOnce(model.param_init_net)\n        data = np.random.rand(4, 3, 227, 227).astype(np.float32)\n\n        workspace.FeedBlob(\"gpu_0/data\", data)\n        model.net.Proto().type = 'simple'\n\n        workspace.RunNetOnce(model.net)\n        loss1 = workspace.FetchBlob(\"gpu_0/last_out_L1000\")\n\n        workspace.RunNetOnce(optim_proto)\n        optimized_loss1 = workspace.FetchBlob(\"gpu_0/last_out_L1000\")\n        np.testing.assert_almost_equal(loss1, optimized_loss1)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    import random\n    random.seed(2603)\n    workspace.GlobalInit([\n        'caffe2',\n        '--caffe2_log_level=0',\n        '--caffe2_print_blob_sizes_at_exit=0',\n        '--caffe2_gpu_memory_tracking=1'])\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/models/seq2seq/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/python/models/seq2seq/beam_search.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package beam_search\n# Module caffe2.python.models.seq2seq.beam_search\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom collections import namedtuple\nfrom caffe2.python import core\nimport caffe2.python.models.seq2seq.seq2seq_util as seq2seq_util\nfrom caffe2.python.models.seq2seq.seq2seq_model_helper import Seq2SeqModelHelper\n\n\nclass BeamSearchForwardOnly(object):\n    \"\"\"\n    Class generalizing forward beam search for seq2seq models.\n\n    Also provides types to specify the recurrent structure of decoding:\n\n    StateConfig:\n        initial_value: blob providing value of state at first step_model\n        state_prev_link: LinkConfig describing how recurrent step receives\n            input from global state blob in each step\n        state_link: LinkConfig describing how step writes (produces new state)\n            to global state blob in each step\n\n    LinkConfig:\n        blob: blob connecting global state blob to step application\n        offset: offset from beginning of global blob for link in time dimension\n        window: width of global blob to read/write in time dimension\n    \"\"\"\n\n    LinkConfig = namedtuple('LinkConfig', ['blob', 'offset', 'window'])\n\n    StateConfig = namedtuple(\n        'StateConfig',\n        ['initial_value', 'state_prev_link', 'state_link'],\n    )\n\n    def __init__(\n        self,\n        beam_size,\n        model,\n        eos_token_id,\n        go_token_id=seq2seq_util.GO_ID,\n        post_eos_penalty=None,\n    ):\n        self.beam_size = beam_size\n        self.model = model\n        self.step_model = Seq2SeqModelHelper(\n            name='step_model',\n            param_model=self.model,\n        )\n        self.go_token_id = go_token_id\n        self.eos_token_id = eos_token_id\n        self.post_eos_penalty = post_eos_penalty\n\n        (\n            self.timestep,\n            self.scores_t_prev,\n            self.tokens_t_prev,\n            self.hypo_t_prev,\n            self.attention_t_prev,\n        ) = self.step_model.net.AddExternalInputs(\n            'timestep',\n            'scores_t_prev',\n            'tokens_t_prev',\n            'hypo_t_prev',\n            'attention_t_prev',\n        )\n        tokens_t_prev_int32 = self.step_model.net.Cast(\n            self.tokens_t_prev,\n            'tokens_t_prev_int32',\n            to=core.DataType.INT32,\n        )\n        self.tokens_t_prev_int32_flattened, _ = self.step_model.net.Reshape(\n            [tokens_t_prev_int32],\n            [tokens_t_prev_int32, 'input_t_int32_old_shape'],\n            shape=[1, -1],\n        )\n\n    def get_step_model(self):\n        return self.step_model\n\n    def get_previous_tokens(self):\n        return self.tokens_t_prev_int32_flattened\n\n    def get_timestep(self):\n        return self.timestep\n\n    # TODO: make attentions a generic state\n    # data_dependencies is a list of blobs that the operator should wait for\n    # before beginning execution. This ensures that ops are run in the correct\n    # order when the RecurrentNetwork op is embedded in a DAGNet, for ex.\n    def apply(\n        self,\n        inputs,\n        length,\n        log_probs,\n        attentions,\n        state_configs,\n        data_dependencies,\n        word_rewards=None,\n        possible_translation_tokens=None,\n        go_token_id=None,\n    ):\n        ZERO = self.model.param_init_net.ConstantFill(\n            [],\n            'ZERO',\n            shape=[1],\n            value=0,\n            dtype=core.DataType.INT32,\n        )\n        on_initial_step = self.step_model.net.EQ(\n            [ZERO, self.timestep],\n            'on_initial_step',\n        )\n\n        if self.post_eos_penalty is not None:\n            eos_token = self.model.param_init_net.ConstantFill(\n                [],\n                'eos_token',\n                shape=[self.beam_size],\n                value=self.eos_token_id,\n                dtype=core.DataType.INT32,\n            )\n            finished_penalty = self.model.param_init_net.ConstantFill(\n                [],\n                'finished_penalty',\n                shape=[1],\n                value=float(self.post_eos_penalty),\n                dtype=core.DataType.FLOAT,\n            )\n            ZERO_FLOAT = self.model.param_init_net.ConstantFill(\n                [],\n                'ZERO_FLOAT',\n                shape=[1],\n                value=0.0,\n                dtype=core.DataType.FLOAT,\n            )\n            finished_penalty = self.step_model.net.Conditional(\n                [on_initial_step, ZERO_FLOAT, finished_penalty],\n                'possible_finished_penalty',\n            )\n\n            tokens_t_flat = self.step_model.net.FlattenToVec(\n                self.tokens_t_prev,\n                'tokens_t_flat',\n            )\n            tokens_t_flat_int = self.step_model.net.Cast(\n                tokens_t_flat,\n                'tokens_t_flat_int',\n                to=core.DataType.INT32,\n            )\n\n            predecessor_is_eos = self.step_model.net.EQ(\n                [tokens_t_flat_int, eos_token],\n                'predecessor_is_eos',\n            )\n            predecessor_is_eos_float = self.step_model.net.Cast(\n                predecessor_is_eos,\n                'predecessor_is_eos_float',\n                to=core.DataType.FLOAT,\n            )\n            predecessor_is_eos_penalty = self.step_model.net.Mul(\n                [predecessor_is_eos_float, finished_penalty],\n                'predecessor_is_eos_penalty',\n                broadcast=1,\n            )\n\n            log_probs = self.step_model.net.Add(\n                [log_probs, predecessor_is_eos_penalty],\n                'log_probs_penalized',\n                broadcast=1,\n                axis=0,\n            )\n\n        # [beam_size, beam_size]\n        best_scores_per_hypo, best_tokens_per_hypo = self.step_model.net.TopK(\n            log_probs,\n            ['best_scores_per_hypo', 'best_tokens_per_hypo_indices'],\n            k=self.beam_size,\n        )\n        if possible_translation_tokens:\n            # [beam_size, beam_size]\n            best_tokens_per_hypo = self.step_model.net.Gather(\n                [possible_translation_tokens, best_tokens_per_hypo],\n                ['best_tokens_per_hypo']\n            )\n\n        # [beam_size]\n        scores_t_prev_squeezed, _ = self.step_model.net.Reshape(\n            self.scores_t_prev,\n            ['scores_t_prev_squeezed', 'scores_t_prev_old_shape'],\n            shape=[self.beam_size],\n        )\n        # [beam_size, beam_size]\n        output_scores = self.step_model.net.Add(\n            [best_scores_per_hypo, scores_t_prev_squeezed],\n            'output_scores',\n            broadcast=1,\n            axis=0,\n        )\n        if word_rewards is not None:\n            # [beam_size, beam_size]\n            word_rewards_for_best_tokens_per_hypo = self.step_model.net.Gather(\n                [word_rewards, best_tokens_per_hypo],\n                'word_rewards_for_best_tokens_per_hypo',\n            )\n            # [beam_size, beam_size]\n            output_scores = self.step_model.net.Add(\n                [output_scores, word_rewards_for_best_tokens_per_hypo],\n                'output_scores',\n            )\n        # [beam_size * beam_size]\n        output_scores_flattened, _ = self.step_model.net.Reshape(\n            [output_scores],\n            [output_scores, 'output_scores_old_shape'],\n            shape=[-1],\n        )\n        MINUS_ONE_INT32 = self.model.param_init_net.ConstantFill(\n            [],\n            'MINUS_ONE_INT32',\n            value=-1,\n            shape=[1],\n            dtype=core.DataType.INT32,\n        )\n        BEAM_SIZE = self.model.param_init_net.ConstantFill(\n            [],\n            'beam_size',\n            shape=[1],\n            value=self.beam_size,\n            dtype=core.DataType.INT32,\n        )\n\n        # current_beam_size (predecessor states from previous step)\n        # is 1 on first step (so we just need beam_size scores),\n        # and beam_size subsequently (so we need all beam_size * beam_size\n        # scores)\n        slice_end = self.step_model.net.Conditional(\n            [on_initial_step, BEAM_SIZE, MINUS_ONE_INT32],\n            ['slice_end'],\n        )\n\n        # [current_beam_size * beam_size]\n        output_scores_flattened_slice = self.step_model.net.Slice(\n            [output_scores_flattened, ZERO, slice_end],\n            'output_scores_flattened_slice',\n        )\n        # [1, current_beam_size * beam_size]\n        output_scores_flattened_slice, _ = self.step_model.net.Reshape(\n            output_scores_flattened_slice,\n            [\n                output_scores_flattened_slice,\n                'output_scores_flattened_slice_old_shape',\n            ],\n            shape=[1, -1],\n        )\n        # [1, beam_size]\n        scores_t, best_indices = self.step_model.net.TopK(\n            output_scores_flattened_slice,\n            ['scores_t', 'best_indices'],\n            k=self.beam_size,\n        )\n        BEAM_SIZE_64 = self.model.param_init_net.Cast(\n            BEAM_SIZE,\n            'BEAM_SIZE_64',\n            to=core.DataType.INT64,\n        )\n        # [1, beam_size]\n        hypo_t_int32 = self.step_model.net.Div(\n            [best_indices, BEAM_SIZE_64],\n            'hypo_t_int32',\n            broadcast=1,\n        )\n        hypo_t = self.step_model.net.Cast(\n            hypo_t_int32,\n            'hypo_t',\n            to=core.DataType.FLOAT,\n        )\n\n        # [beam_size, encoder_length, 1]\n        attention_t = self.step_model.net.Gather(\n            [attentions, hypo_t_int32],\n            'attention_t',\n        )\n        # [1, beam_size, encoder_length]\n        attention_t, _ = self.step_model.net.Reshape(\n            attention_t,\n            [attention_t, 'attention_t_old_shape'],\n            shape=[1, self.beam_size, -1],\n        )\n        # [beam_size * beam_size]\n        best_tokens_per_hypo_flatten, _ = self.step_model.net.Reshape(\n            best_tokens_per_hypo,\n            [\n                'best_tokens_per_hypo_flatten',\n                'best_tokens_per_hypo_old_shape',\n            ],\n            shape=[-1],\n        )\n        tokens_t_int32 = self.step_model.net.Gather(\n            [best_tokens_per_hypo_flatten, best_indices],\n            'tokens_t_int32',\n        )\n        tokens_t = self.step_model.net.Cast(\n            tokens_t_int32,\n            'tokens_t',\n            to=core.DataType.FLOAT,\n        )\n\n        def choose_state_per_hypo(state_config):\n            state_flattened, _ = self.step_model.net.Reshape(\n                state_config.state_link.blob,\n                [\n                    state_config.state_link.blob,\n                    state_config.state_link.blob + '_old_shape',\n                ],\n                shape=[self.beam_size, -1],\n            )\n            state_chosen_per_hypo = self.step_model.net.Gather(\n                [state_flattened, hypo_t_int32],\n                str(state_config.state_link.blob) + '_chosen_per_hypo',\n            )\n            return self.StateConfig(\n                initial_value=state_config.initial_value,\n                state_prev_link=state_config.state_prev_link,\n                state_link=self.LinkConfig(\n                    blob=state_chosen_per_hypo,\n                    offset=state_config.state_link.offset,\n                    window=state_config.state_link.window,\n                )\n            )\n        state_configs = [choose_state_per_hypo(c) for c in state_configs]\n        initial_scores = self.model.param_init_net.ConstantFill(\n            [],\n            'initial_scores',\n            shape=[1],\n            value=0.0,\n            dtype=core.DataType.FLOAT,\n        )\n        if go_token_id:\n            initial_tokens = self.model.net.Copy(\n                [go_token_id],\n                'initial_tokens',\n            )\n        else:\n            initial_tokens = self.model.param_init_net.ConstantFill(\n                [],\n                'initial_tokens',\n                shape=[1],\n                value=float(self.go_token_id),\n                dtype=core.DataType.FLOAT,\n            )\n\n        initial_hypo = self.model.param_init_net.ConstantFill(\n            [],\n            'initial_hypo',\n            shape=[1],\n            value=0.0,\n            dtype=core.DataType.FLOAT,\n        )\n        encoder_inputs_flattened, _ = self.model.net.Reshape(\n            inputs,\n            ['encoder_inputs_flattened', 'encoder_inputs_old_shape'],\n            shape=[-1],\n        )\n        init_attention = self.model.net.ConstantFill(\n            encoder_inputs_flattened,\n            'init_attention',\n            value=0.0,\n            dtype=core.DataType.FLOAT,\n        )\n        state_configs = state_configs + [\n            self.StateConfig(\n                initial_value=initial_scores,\n                state_prev_link=self.LinkConfig(self.scores_t_prev, 0, 1),\n                state_link=self.LinkConfig(scores_t, 1, 1),\n            ),\n            self.StateConfig(\n                initial_value=initial_tokens,\n                state_prev_link=self.LinkConfig(self.tokens_t_prev, 0, 1),\n                state_link=self.LinkConfig(tokens_t, 1, 1),\n            ),\n            self.StateConfig(\n                initial_value=initial_hypo,\n                state_prev_link=self.LinkConfig(self.hypo_t_prev, 0, 1),\n                state_link=self.LinkConfig(hypo_t, 1, 1),\n            ),\n            self.StateConfig(\n                initial_value=init_attention,\n                state_prev_link=self.LinkConfig(self.attention_t_prev, 0, 1),\n                state_link=self.LinkConfig(attention_t, 1, 1),\n            ),\n        ]\n        fake_input = self.model.net.ConstantFill(\n            length,\n            'beam_search_fake_input',\n            input_as_shape=True,\n            extra_shape=[self.beam_size, 1],\n            value=0.0,\n            dtype=core.DataType.FLOAT,\n        )\n        all_inputs = (\n            [fake_input] +\n            self.step_model.params +\n            [state_config.initial_value for state_config in state_configs] +\n            data_dependencies\n        )\n        forward_links = []\n        recurrent_states = []\n        for state_config in state_configs:\n            state_name = str(state_config.state_prev_link.blob) + '_states'\n            recurrent_states.append(state_name)\n            forward_links.append((\n                state_config.state_prev_link.blob,\n                state_name,\n                state_config.state_prev_link.offset,\n                state_config.state_prev_link.window,\n            ))\n            forward_links.append((\n                state_config.state_link.blob,\n                state_name,\n                state_config.state_link.offset,\n                state_config.state_link.window,\n            ))\n        link_internal, link_external, link_offset, link_window = (\n            zip(*forward_links)\n        )\n        all_outputs = [\n            str(s) + '_all'\n            for s in [scores_t, tokens_t, hypo_t, attention_t]\n        ]\n        results = self.model.net.RecurrentNetwork(\n            all_inputs,\n            all_outputs + ['step_workspaces'],\n            param=[all_inputs.index(p) for p in self.step_model.params],\n            alias_src=[\n                str(s) + '_states'\n                for s in [\n                    self.scores_t_prev,\n                    self.tokens_t_prev,\n                    self.hypo_t_prev,\n                    self.attention_t_prev,\n                ]\n            ],\n            alias_dst=all_outputs,\n            alias_offset=[0] * 4,\n            recurrent_states=recurrent_states,\n            initial_recurrent_state_ids=[\n                all_inputs.index(state_config.initial_value)\n                for state_config in state_configs\n            ],\n            link_internal=[str(l) for l in link_internal],\n            link_external=[str(l) for l in link_external],\n            link_offset=link_offset,\n            link_window=link_window,\n            backward_link_internal=[],\n            backward_link_external=[],\n            backward_link_offset=[],\n            step_net=self.step_model.net.Proto(),\n            timestep=str(self.timestep),\n            outputs_with_grads=[],\n            enable_rnn_executor=1,\n            rnn_executor_debug=0\n        )\n        score_t_all, tokens_t_all, hypo_t_all, attention_t_all = results[:4]\n\n        output_token_beam_list = self.model.net.Cast(\n            tokens_t_all,\n            'output_token_beam_list',\n            to=core.DataType.INT32,\n        )\n        output_prev_index_beam_list = self.model.net.Cast(\n            hypo_t_all,\n            'output_prev_index_beam_list',\n            to=core.DataType.INT32,\n        )\n        output_score_beam_list = self.model.net.Alias(\n            score_t_all,\n            'output_score_beam_list',\n        )\n        output_attention_weights_beam_list = self.model.net.Alias(\n            attention_t_all,\n            'output_attention_weights_beam_list',\n        )\n\n        return (\n            output_token_beam_list,\n            output_prev_index_beam_list,\n            output_score_beam_list,\n            output_attention_weights_beam_list,\n        )\n"
  },
  {
    "path": "caffe2/python/models/seq2seq/seq2seq_beam_search_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nimport os\nimport tempfile\n\nfrom caffe2.python import test_util, workspace\nimport caffe2.python.models.seq2seq.seq2seq_util as seq2seq_util\nfrom caffe2.python.models.seq2seq.train import Seq2SeqModelCaffe2\nfrom caffe2.python.models.seq2seq.translate import (\n    Seq2SeqModelCaffe2EnsembleDecoder,\n)\n\n\nclass Seq2SeqBeamSearchTest(test_util.TestCase):\n\n    def _build_seq2seq_model(\n        self,\n        model_params,\n        tmp_dir,\n        source_vocab_size=20,\n        target_vocab_size=20,\n        num_gpus=0,\n        batch_size=2,\n    ):\n        training_params = dict(\n            model_params,\n            batch_size=batch_size,\n            optimizer_params=dict(\n                learning_rate=0.1,\n            ),\n            max_gradient_norm=1.0,\n        )\n\n        model_obj = Seq2SeqModelCaffe2(\n            training_params,\n            source_vocab_size,\n            target_vocab_size,\n            num_gpus,\n        )\n        model_obj.initialize_from_scratch()\n\n        checkpoint_path_prefix = os.path.join(tmp_dir, 'checkpoint')\n        checkpoint_path = model_obj.save(\n            checkpoint_path_prefix=checkpoint_path_prefix,\n            current_step=0,\n        )\n\n        return model_obj, checkpoint_path\n\n    def _run_compare_train_inference(self, model_params):\n        tmp_dir = tempfile.mkdtemp()\n\n        model_obj, checkpoint_path = self._build_seq2seq_model(\n            model_params,\n            tmp_dir=tmp_dir,\n            source_vocab_size=20,\n            target_vocab_size=20,\n            num_gpus=0,\n            batch_size=2,\n        )\n        assert model_obj is not None\n\n        translate_params = dict(\n            ensemble_models=[dict(\n                source_vocab={i: str(i) for i in range(20)},\n                target_vocab={i: str(i) for i in range(20)},\n                model_params=model_params,\n                model_file=checkpoint_path,\n            )],\n            decoding_params=dict(\n                beam_size=3,\n                word_reward=0,\n                unk_reward=0,\n            ),\n        )\n\n        beam_decoder_model = Seq2SeqModelCaffe2EnsembleDecoder(translate_params)\n        beam_decoder_model.load_models()\n\n        encoder_lengths = 5\n        decoder_lengths = 7\n\n        for _ in range(3):\n            encoder_inputs = np.random.random_integers(\n                low=3,  # after GO_ID (1) and EOS_ID (2)\n                high=19,\n                size=encoder_lengths,\n            )\n            targets, _, beam_model_score = beam_decoder_model.decode(\n                encoder_inputs,\n                decoder_lengths,\n            )\n            targets_2, _, beam_model_score = beam_decoder_model.decode(\n                encoder_inputs,\n                decoder_lengths,\n            )\n            self.assertEqual(targets, targets_2)\n\n            workspace.FeedBlob(\n                'encoder_inputs',\n                np.array(\n                    [list(reversed(encoder_inputs))]\n                ).transpose().astype(dtype=np.int32))\n            workspace.FeedBlob(\n                'encoder_lengths',\n                np.array([len(encoder_inputs)]).astype(dtype=np.int32),\n            )\n            decoder_inputs = [seq2seq_util.GO_ID] + targets[:-1]\n            workspace.FeedBlob(\n                'decoder_inputs',\n                np.array([decoder_inputs]).transpose().astype(dtype=np.int32),\n            )\n            workspace.FeedBlob(\n                'decoder_lengths',\n                np.array([len(decoder_inputs)]).astype(dtype=np.int32),\n            )\n            workspace.FeedBlob(\n                'targets',\n                np.array([targets]).transpose().astype(dtype=np.int32),\n            )\n            workspace.FeedBlob(\n                'target_weights',\n                np.array([[1.0] * len(targets)]).astype(dtype=np.float32),\n            )\n\n            workspace.RunNet(model_obj.forward_net)\n            train_model_score = workspace.FetchBlob('total_loss_scalar')\n\n            np.testing.assert_almost_equal(\n                beam_model_score,\n                train_model_score,\n                decimal=4,\n            )\n\n    def test_attention(self):\n        model_params = dict(\n            attention='regular',\n            decoder_layer_configs=[\n                dict(\n                    num_units=32,\n                ),\n            ],\n            encoder_type=dict(\n                encoder_layer_configs=[\n                    dict(\n                        num_units=16,\n                    ),\n                ],\n                use_bidirectional_encoder=True,\n            ),\n            encoder_embedding_size=8,\n            decoder_embedding_size=8,\n            decoder_softmax_size=None,\n        )\n        self._run_compare_train_inference(model_params)\n\n    def test_2layer_attention(self):\n        model_params = dict(\n            attention='regular',\n            decoder_layer_configs=[\n                dict(\n                    num_units=32,\n                ),\n                dict(\n                    num_units=32,\n                ),\n            ],\n            encoder_type=dict(\n                encoder_layer_configs=[\n                    dict(\n                        num_units=16,\n                    ),\n                    dict(\n                        num_units=32,\n                    ),\n                ],\n                use_bidirectional_encoder=True,\n            ),\n            encoder_embedding_size=8,\n            decoder_embedding_size=8,\n            decoder_softmax_size=None,\n        )\n        self._run_compare_train_inference(model_params)\n\n    def test_multi_decoder(self):\n        model_params = dict(\n            attention='regular',\n            decoder_layer_configs=[\n                dict(\n                    num_units=32,\n                ),\n                dict(\n                    num_units=32,\n                ),\n                dict(\n                    num_units=32,\n                ),\n            ],\n            encoder_type=dict(\n                encoder_layer_configs=[\n                    dict(\n                        num_units=32,\n                    ),\n                ],\n                use_bidirectional_encoder=False,\n            ),\n            encoder_embedding_size=8,\n            decoder_embedding_size=8,\n            decoder_softmax_size=None,\n        )\n        self._run_compare_train_inference(model_params)\n"
  },
  {
    "path": "caffe2/python/models/seq2seq/seq2seq_model_helper.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package seq2seq_model_helper\n# Module caffe2.python.models.seq2seq.seq2seq_model_helper\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import scope\nfrom caffe2.python.model_helper import ModelHelper\n\n\nclass Seq2SeqModelHelper(ModelHelper):\n\n    def __init__(self, init_params=True, **kwargs):\n        arg_scope = {\n            'use_cudnn': kwargs.pop('use_cudnn', True),\n            'cudnn_exhaustive_search': kwargs.pop('cudnn_exhaustive_search', False),\n            'order': 'NHWC',\n        }\n        if kwargs.get('ws_nbytes_limit', None):\n            arg_scope['ws_nbytes_limit'] = kwargs.pop('ws_nbytes_limit')\n\n        super(Seq2SeqModelHelper, self).__init__(\n            init_params=init_params,\n            arg_scope=arg_scope,\n            **kwargs\n        )\n        self.non_trainable_params = []\n\n    def AddParam(self, name, init=None, init_value=None, trainable=True):\n        \"\"\"Adds a parameter to the model's net and it's initializer if needed\n\n        Args:\n            init: a tuple (<initialization_op_name>, <initialization_op_kwargs>)\n            init_value: int, float or str. Can be used instead of `init` as a\n                simple constant initializer\n            trainable: bool, whether to compute gradient for this param or not\n        \"\"\"\n        if init_value is not None:\n            assert init is None\n            assert type(init_value) in [int, float, str]\n            init = ('ConstantFill', dict(\n                shape=[1],\n                value=init_value,\n            ))\n\n        if self.init_params:\n            param = self.param_init_net.__getattr__(init[0])(\n                [],\n                name,\n                **init[1]\n            )\n        else:\n            param = self.net.AddExternalInput(name)\n\n        if trainable:\n            self.params.append(param)\n        else:\n            self.non_trainable_params.append(param)\n\n        return param\n\n    def GetNonTrainableParams(self, namescope=None):\n        '''\n        Returns the params in current namescope\n        '''\n        if namescope is None:\n            namescope = scope.CurrentNameScope()\n        else:\n            if not namescope.endswith(scope._NAMESCOPE_SEPARATOR):\n                namescope += scope._NAMESCOPE_SEPARATOR\n\n        if namescope == '':\n            return self.non_trainable_params[:]\n        else:\n            return [\n                p for p in self.non_trainable_params\n                if p.GetNameScope() == namescope\n            ]\n\n    def GetAllParams(self, namescope=None):\n        return (\n            self.GetParams(namescope) +\n            self.GetComputedParams(namescope) +\n            self.GetNonTrainableParams(namescope)\n        )\n"
  },
  {
    "path": "caffe2/python/models/seq2seq/seq2seq_model_helper_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python.models.seq2seq import seq2seq_model_helper\nfrom caffe2.python import scope, test_util\n\n\nclass Seq2SeqModelHelperTest(test_util.TestCase):\n    def testConstuctor(self):\n        model_name = 'TestModel'\n        m = seq2seq_model_helper.Seq2SeqModelHelper(name=model_name)\n\n        self.assertEqual(m.name, model_name)\n        self.assertEqual(m.init_params, True)\n\n        self.assertEqual(m.arg_scope, {\n            'use_cudnn': True,\n            'cudnn_exhaustive_search': False,\n            'order': 'NHWC'\n        })\n\n    def testAddParam(self):\n        m = seq2seq_model_helper.Seq2SeqModelHelper()\n\n        param_name = 'test_param'\n        param = m.AddParam(param_name, init_value=1)\n        self.assertEqual(str(param), param_name)\n\n    def testGetNonTrainableParams(self):\n        m = seq2seq_model_helper.Seq2SeqModelHelper()\n\n        m.AddParam('test_param1', init_value=1, trainable=True)\n        p2 = m.AddParam('test_param2', init_value=2, trainable=False)\n\n        self.assertEqual(\n            m.GetNonTrainableParams(),\n            [p2]\n        )\n\n        with scope.NameScope('A', reset=True):\n            p3 = m.AddParam('test_param3', init_value=3, trainable=False)\n            self.assertEqual(\n                m.GetNonTrainableParams(),\n                [p3]\n            )\n\n        self.assertEqual(\n            m.GetNonTrainableParams(),\n            [p2, p3]\n        )\n\n    def testGetAllParams(self):\n        m = seq2seq_model_helper.Seq2SeqModelHelper()\n\n        p1 = m.AddParam('test_param1', init_value=1, trainable=True)\n        p2 = m.AddParam('test_param2', init_value=2, trainable=False)\n\n        self.assertEqual(\n            m.GetAllParams(),\n            [p1, p2]\n        )\n\n\nif __name__ == \"__main__\":\n    import unittest\n    import random\n    random.seed(2221)\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/models/seq2seq/seq2seq_util.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package seq2seq_util\n# Module caffe2.python.examples.seq2seq_util\n\"\"\" A bunch of util functions to build Seq2Seq models with Caffe2.\"\"\"\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport collections\nfrom future.utils import viewitems\n\nimport caffe2.proto.caffe2_pb2 as caffe2_pb2\nfrom caffe2.python import attention, core, rnn_cell, brew\n\n\nPAD_ID = 0\nPAD = '<PAD>'\nGO_ID = 1\nGO = '<GO>'\nEOS_ID = 2\nEOS = '<EOS>'\nUNK_ID = 3\nUNK = '<UNK>'\n\n\ndef gen_vocab(corpus, unk_threshold):\n    vocab = collections.defaultdict(lambda: len(vocab))\n    freqs = collections.defaultdict(lambda: 0)\n    # Adding padding tokens to the vocabulary to maintain consistency with IDs\n    vocab[PAD]\n    vocab[GO]\n    vocab[EOS]\n    vocab[UNK]\n\n    with open(corpus) as f:\n        for sentence in f:\n            tokens = sentence.strip().split()\n            for token in tokens:\n                freqs[token] += 1\n    for token, freq in viewitems(freqs):\n        if freq > unk_threshold:\n            vocab[token]\n\n    return vocab\n\n\ndef get_numberized_sentence(sentence, vocab):\n    numerized_sentence = []\n    for token in sentence.strip().split():\n        if token in vocab:\n            numerized_sentence.append(vocab[token])\n        else:\n            numerized_sentence.append(vocab[UNK])\n    return numerized_sentence\n\n\ndef rnn_unidirectional_layer(\n    model,\n    inputs,\n    input_lengths,\n    input_size,\n    num_units,\n    dropout_keep_prob,\n    forward_only,\n    return_sequence_output,\n    return_final_state,\n    scope=None,\n):\n    \"\"\" Unidirectional LSTM encoder.\"\"\"\n    with core.NameScope(scope):\n        initial_cell_state = model.param_init_net.ConstantFill(\n            [],\n            'initial_cell_state',\n            shape=[num_units],\n            value=0.0,\n        )\n        initial_hidden_state = model.param_init_net.ConstantFill(\n            [],\n            'initial_hidden_state',\n            shape=[num_units],\n            value=0.0,\n        )\n\n    cell = rnn_cell.LSTMCell(\n        input_size=input_size,\n        hidden_size=num_units,\n        forget_bias=0.0,\n        memory_optimization=False,\n        name=(scope + '/' if scope else '') + 'lstm',\n        forward_only=forward_only,\n    )\n\n    dropout_ratio = (\n        None if dropout_keep_prob is None else (1.0 - dropout_keep_prob)\n    )\n    if dropout_ratio is not None:\n        cell = rnn_cell.DropoutCell(\n            internal_cell=cell,\n            dropout_ratio=dropout_ratio,\n            name=(scope + '/' if scope else '') + 'dropout',\n            forward_only=forward_only,\n            is_test=False,\n        )\n\n    outputs_with_grads = []\n    if return_sequence_output:\n        outputs_with_grads.append(0)\n    if return_final_state:\n        outputs_with_grads.extend([1, 3])\n\n    outputs, (_, final_hidden_state, _, final_cell_state) = (\n        cell.apply_over_sequence(\n            model=model,\n            inputs=inputs,\n            seq_lengths=input_lengths,\n            initial_states=(initial_hidden_state, initial_cell_state),\n            outputs_with_grads=outputs_with_grads,\n        )\n    )\n    return outputs, final_hidden_state, final_cell_state\n\n\ndef rnn_bidirectional_layer(\n    model,\n    inputs,\n    input_lengths,\n    input_size,\n    num_units,\n    dropout_keep_prob,\n    forward_only,\n    return_sequence_output,\n    return_final_state,\n    scope=None,\n):\n    outputs_fw, final_hidden_fw, final_cell_fw = rnn_unidirectional_layer(\n        model,\n        inputs,\n        input_lengths,\n        input_size,\n        num_units,\n        dropout_keep_prob,\n        forward_only,\n        return_sequence_output,\n        return_final_state,\n        scope=(scope + '/' if scope else '') + 'fw',\n    )\n    with core.NameScope(scope):\n        reversed_inputs = model.net.ReversePackedSegs(\n            [inputs, input_lengths],\n            ['reversed_inputs'],\n        )\n    outputs_bw, final_hidden_bw, final_cell_bw = rnn_unidirectional_layer(\n        model,\n        reversed_inputs,\n        input_lengths,\n        input_size,\n        num_units,\n        dropout_keep_prob,\n        forward_only,\n        return_sequence_output,\n        return_final_state,\n        scope=(scope + '/' if scope else '') + 'bw',\n    )\n    with core.NameScope(scope):\n        outputs_bw = model.net.ReversePackedSegs(\n            [outputs_bw, input_lengths],\n            ['outputs_bw'],\n        )\n\n    # Concatenate forward and backward results\n    if return_sequence_output:\n        with core.NameScope(scope):\n            outputs, _ = model.net.Concat(\n                [outputs_fw, outputs_bw],\n                ['outputs', 'outputs_dim'],\n                axis=2,\n            )\n    else:\n        outputs = None\n\n    if return_final_state:\n        with core.NameScope(scope):\n            final_hidden_state, _ = model.net.Concat(\n                [final_hidden_fw, final_hidden_bw],\n                ['final_hidden_state', 'final_hidden_state_dim'],\n                axis=2,\n            )\n            final_cell_state, _ = model.net.Concat(\n                [final_cell_fw, final_cell_bw],\n                ['final_cell_state', 'final_cell_state_dim'],\n                axis=2,\n            )\n    else:\n        final_hidden_state = None\n        final_cell_state = None\n\n    return outputs, final_hidden_state, final_cell_state\n\n\ndef build_embeddings(\n    model,\n    vocab_size,\n    embedding_size,\n    name,\n    freeze_embeddings,\n):\n    embeddings = model.param_init_net.GaussianFill(\n        [],\n        name,\n        shape=[vocab_size, embedding_size],\n        std=0.1,\n    )\n    if not freeze_embeddings:\n        model.params.append(embeddings)\n    return embeddings\n\n\ndef get_layer_scope(scope, layer_type, i):\n    prefix = (scope + '/' if scope else '') + layer_type\n    return '{}/layer{}'.format(prefix, i)\n\n\ndef build_embedding_encoder(\n    model,\n    encoder_params,\n    num_decoder_layers,\n    inputs,\n    input_lengths,\n    vocab_size,\n    embeddings,\n    embedding_size,\n    use_attention,\n    num_gpus=0,\n    forward_only=False,\n    scope=None,\n):\n    with core.NameScope(scope or ''):\n        if num_gpus == 0:\n            embedded_encoder_inputs = model.net.Gather(\n                [embeddings, inputs],\n                ['embedded_encoder_inputs'],\n            )\n        else:\n            with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n                embedded_encoder_inputs_cpu = model.net.Gather(\n                    [embeddings, inputs],\n                    ['embedded_encoder_inputs_cpu'],\n                )\n            embedded_encoder_inputs = model.CopyCPUToGPU(\n                embedded_encoder_inputs_cpu,\n                'embedded_encoder_inputs',\n            )\n\n    layer_inputs = embedded_encoder_inputs\n    layer_input_size = embedding_size\n    encoder_units_per_layer = []\n    final_encoder_hidden_states = []\n    final_encoder_cell_states = []\n\n    num_encoder_layers = len(encoder_params['encoder_layer_configs'])\n    use_bidirectional_encoder = encoder_params.get(\n        'use_bidirectional_encoder',\n        False,\n    )\n\n    for i, layer_config in enumerate(encoder_params['encoder_layer_configs']):\n\n        if use_bidirectional_encoder and i == 0:\n            layer_func = rnn_bidirectional_layer\n            output_dims = 2 * layer_config['num_units']\n        else:\n            layer_func = rnn_unidirectional_layer\n            output_dims = layer_config['num_units']\n        encoder_units_per_layer.append(output_dims)\n\n        is_final_layer = (i == num_encoder_layers - 1)\n\n        dropout_keep_prob = layer_config.get(\n            'dropout_keep_prob',\n            None,\n        )\n\n        return_final_state = i >= (num_encoder_layers - num_decoder_layers)\n        (\n            layer_outputs,\n            final_layer_hidden_state,\n            final_layer_cell_state,\n        ) = layer_func(\n            model=model,\n            inputs=layer_inputs,\n            input_lengths=input_lengths,\n            input_size=layer_input_size,\n            num_units=layer_config['num_units'],\n            dropout_keep_prob=dropout_keep_prob,\n            forward_only=forward_only,\n            return_sequence_output=(not is_final_layer) or use_attention,\n            return_final_state=return_final_state,\n            scope=get_layer_scope(scope, 'encoder', i),\n        )\n\n        if not is_final_layer:\n            layer_inputs = layer_outputs\n            layer_input_size = output_dims\n        final_encoder_hidden_states.append(final_layer_hidden_state)\n        final_encoder_cell_states.append(final_layer_cell_state)\n\n    encoder_outputs = layer_outputs\n    weighted_encoder_outputs = None\n\n    return (\n        encoder_outputs,\n        weighted_encoder_outputs,\n        final_encoder_hidden_states,\n        final_encoder_cell_states,\n        encoder_units_per_layer,\n    )\n\n\nclass LSTMWithAttentionDecoder(object):\n\n    def scope(self, name):\n        return self.name + '/' + name if self.name is not None else name\n\n    def _get_attention_type(self, attention_type_as_string):\n        if attention_type_as_string == 'regular':\n            return attention.AttentionType.Regular\n        elif attention_type_as_string == 'recurrent':\n            return attention.AttentionType.Recurrent\n        else:\n            assert False, 'Unknown type ' + attention_type_as_string\n\n    def __init__(\n        self,\n        encoder_outputs,\n        encoder_output_dim,\n        encoder_lengths,\n        vocab_size,\n        attention_type,\n        embedding_size,\n        decoder_num_units,\n        decoder_cells,\n        residual_output_layers=None,\n        name=None,\n        weighted_encoder_outputs=None,\n    ):\n        self.name = name\n        self.num_layers = len(decoder_cells)\n        if attention_type == 'none':\n            self.cell = rnn_cell.MultiRNNCell(\n                decoder_cells,\n                name=self.scope('decoder'),\n                residual_output_layers=residual_output_layers,\n            )\n            self.use_attention = False\n            self.decoder_output_dim = decoder_num_units\n            self.output_indices = self.cell.output_indices\n        else:\n            decoder_cell = rnn_cell.MultiRNNCell(\n                decoder_cells,\n                name=self.scope('decoder'),\n                residual_output_layers=residual_output_layers,\n            )\n            self.cell = rnn_cell.AttentionCell(\n                encoder_output_dim=encoder_output_dim,\n                encoder_outputs=encoder_outputs,\n                encoder_lengths=encoder_lengths,\n                decoder_cell=decoder_cell,\n                decoder_state_dim=decoder_num_units,\n                name=self.scope('attention_decoder'),\n                attention_type=self._get_attention_type(attention_type),\n                weighted_encoder_outputs=weighted_encoder_outputs,\n                attention_memory_optimization=True,\n            )\n            self.use_attention = True\n            self.decoder_output_dim = decoder_num_units + encoder_output_dim\n\n            self.output_indices = decoder_cell.output_indices\n            self.output_indices.append(2 * self.num_layers)\n\n    def get_state_names(self):\n        return self.cell.get_state_names()\n\n    def get_outputs_with_grads(self):\n        # sequence (all) output locations are at twice their state index\n        return [2 * i for i in self.output_indices]\n\n    def get_output_dim(self):\n        return self.decoder_output_dim\n\n    def get_attention_weights(self):\n        assert self.use_attention\n        # [batch_size, encoder_length, 1]\n        return self.cell.get_attention_weights()\n\n    def apply(\n        self,\n        model,\n        input_t,\n        seq_lengths,\n        states,\n        timestep,\n    ):\n        return self.cell.apply(\n            model=model,\n            input_t=input_t,\n            seq_lengths=seq_lengths,\n            states=states,\n            timestep=timestep,\n        )\n\n    def apply_over_sequence(\n        self,\n        model,\n        inputs,\n        seq_lengths,\n        initial_states,\n    ):\n        return self.cell.apply_over_sequence(\n            model=model,\n            inputs=inputs,\n            seq_lengths=seq_lengths,\n            initial_states=initial_states,\n            outputs_with_grads=self.get_outputs_with_grads(),\n        )\n\n\ndef build_initial_rnn_decoder_states(\n    model,\n    encoder_units_per_layer,\n    decoder_units_per_layer,\n    final_encoder_hidden_states,\n    final_encoder_cell_states,\n    use_attention,\n):\n    num_encoder_layers = len(encoder_units_per_layer)\n    num_decoder_layers = len(decoder_units_per_layer)\n    if num_encoder_layers > num_decoder_layers:\n        offset = num_encoder_layers - num_decoder_layers\n    else:\n        offset = 0\n\n    initial_states = []\n    for i, decoder_num_units in enumerate(decoder_units_per_layer):\n\n        if (\n            final_encoder_hidden_states and\n            len(final_encoder_hidden_states) > (i + offset)\n        ):\n            final_encoder_hidden_state = final_encoder_hidden_states[i + offset]\n        else:\n            final_encoder_hidden_state = None\n\n        if final_encoder_hidden_state is None:\n            decoder_initial_hidden_state = model.param_init_net.ConstantFill(\n                [],\n                'decoder_initial_hidden_state_{}'.format(i),\n                shape=[decoder_num_units],\n                value=0.0,\n            )\n            model.params.append(decoder_initial_hidden_state)\n        elif decoder_num_units != encoder_units_per_layer[i + offset]:\n            decoder_initial_hidden_state = brew.fc(\n                model,\n                final_encoder_hidden_state,\n                'decoder_initial_hidden_state_{}'.format(i),\n                encoder_units_per_layer[i + offset],\n                decoder_num_units,\n                axis=2,\n            )\n        else:\n            decoder_initial_hidden_state = final_encoder_hidden_state\n        initial_states.append(decoder_initial_hidden_state)\n\n        if (\n            final_encoder_cell_states and\n            len(final_encoder_cell_states) > (i + offset)\n        ):\n            final_encoder_cell_state = final_encoder_cell_states[i + offset]\n        else:\n            final_encoder_cell_state = None\n\n        if final_encoder_cell_state is None:\n            decoder_initial_cell_state = model.param_init_net.ConstantFill(\n                [],\n                'decoder_initial_cell_state_{}'.format(i),\n                shape=[decoder_num_units],\n                value=0.0,\n            )\n            model.params.append(decoder_initial_cell_state)\n        elif decoder_num_units != encoder_units_per_layer[i + offset]:\n            decoder_initial_cell_state = brew.fc(\n                model,\n                final_encoder_cell_state,\n                'decoder_initial_cell_state_{}'.format(i),\n                encoder_units_per_layer[i + offset],\n                decoder_num_units,\n                axis=2,\n            )\n        else:\n            decoder_initial_cell_state = final_encoder_cell_state\n        initial_states.append(decoder_initial_cell_state)\n\n    if use_attention:\n        initial_attention_weighted_encoder_context = (\n            model.param_init_net.ConstantFill(\n                [],\n                'initial_attention_weighted_encoder_context',\n                shape=[encoder_units_per_layer[-1]],\n                value=0.0,\n            )\n        )\n        model.params.append(initial_attention_weighted_encoder_context)\n        initial_states.append(initial_attention_weighted_encoder_context)\n\n    return initial_states\n\n\ndef build_embedding_decoder(\n    model,\n    decoder_layer_configs,\n    inputs,\n    input_lengths,\n    encoder_lengths,\n    encoder_outputs,\n    weighted_encoder_outputs,\n    final_encoder_hidden_states,\n    final_encoder_cell_states,\n    encoder_units_per_layer,\n    vocab_size,\n    embeddings,\n    embedding_size,\n    attention_type,\n    forward_only,\n    num_gpus=0,\n    scope=None,\n):\n    with core.NameScope(scope or ''):\n        if num_gpus == 0:\n            embedded_decoder_inputs = model.net.Gather(\n                [embeddings, inputs],\n                ['embedded_decoder_inputs'],\n            )\n        else:\n            with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n                embedded_decoder_inputs_cpu = model.net.Gather(\n                    [embeddings, inputs],\n                    ['embedded_decoder_inputs_cpu'],\n                )\n            embedded_decoder_inputs = model.CopyCPUToGPU(\n                embedded_decoder_inputs_cpu,\n                'embedded_decoder_inputs',\n            )\n\n    decoder_cells = []\n    decoder_units_per_layer = []\n    for i, layer_config in enumerate(decoder_layer_configs):\n        num_units = layer_config['num_units']\n        decoder_units_per_layer.append(num_units)\n\n        if i == 0:\n            input_size = embedding_size\n        else:\n            input_size = decoder_cells[-1].get_output_dim()\n\n        cell = rnn_cell.LSTMCell(\n            forward_only=forward_only,\n            input_size=input_size,\n            hidden_size=num_units,\n            forget_bias=0.0,\n            memory_optimization=False,\n        )\n\n        dropout_keep_prob = layer_config.get('dropout_keep_prob', None)\n        if dropout_keep_prob is not None:\n            dropout_ratio = 1.0 - layer_config.dropout_keep_prob\n            cell = rnn_cell.DropoutCell(\n                internal_cell=cell,\n                dropout_ratio=dropout_ratio,\n                forward_only=forward_only,\n                is_test=False,\n                name=get_layer_scope(scope, 'decoder_dropout', i),\n            )\n\n        decoder_cells.append(cell)\n\n    states = build_initial_rnn_decoder_states(\n        model=model,\n        encoder_units_per_layer=encoder_units_per_layer,\n        decoder_units_per_layer=decoder_units_per_layer,\n        final_encoder_hidden_states=final_encoder_hidden_states,\n        final_encoder_cell_states=final_encoder_cell_states,\n        use_attention=(attention_type != 'none'),\n    )\n    attention_decoder = LSTMWithAttentionDecoder(\n        encoder_outputs=encoder_outputs,\n        encoder_output_dim=encoder_units_per_layer[-1],\n        encoder_lengths=encoder_lengths,\n        vocab_size=vocab_size,\n        attention_type=attention_type,\n        embedding_size=embedding_size,\n        decoder_num_units=decoder_units_per_layer[-1],\n        decoder_cells=decoder_cells,\n        weighted_encoder_outputs=weighted_encoder_outputs,\n        name=scope,\n    )\n    decoder_outputs, _ = attention_decoder.apply_over_sequence(\n        model=model,\n        inputs=embedded_decoder_inputs,\n        seq_lengths=input_lengths,\n        initial_states=states,\n    )\n\n    # we do softmax over the whole sequence\n    # (max_length in the batch * batch_size) x decoder embedding size\n    # -1 because we don't know max_length yet\n    decoder_outputs_flattened, _ = model.net.Reshape(\n        [decoder_outputs],\n        [\n            'decoder_outputs_flattened',\n            'decoder_outputs_and_contexts_combination_old_shape',\n        ],\n        shape=[-1, attention_decoder.get_output_dim()],\n    )\n\n    decoder_outputs = decoder_outputs_flattened\n    decoder_output_dim = attention_decoder.get_output_dim()\n\n    return (decoder_outputs, decoder_output_dim)\n\n\ndef output_projection(\n    model,\n    decoder_outputs,\n    decoder_output_size,\n    target_vocab_size,\n    decoder_softmax_size,\n):\n    if decoder_softmax_size is not None:\n        decoder_outputs = brew.fc(\n            model,\n            decoder_outputs,\n            'decoder_outputs_scaled',\n            dim_in=decoder_output_size,\n            dim_out=decoder_softmax_size,\n        )\n        decoder_output_size = decoder_softmax_size\n\n    output_projection_w = model.param_init_net.XavierFill(\n        [],\n        'output_projection_w',\n        shape=[target_vocab_size, decoder_output_size],\n    )\n\n    output_projection_b = model.param_init_net.XavierFill(\n        [],\n        'output_projection_b',\n        shape=[target_vocab_size],\n    )\n    model.params.extend([\n        output_projection_w,\n        output_projection_b,\n    ])\n    output_logits = model.net.FC(\n        [\n            decoder_outputs,\n            output_projection_w,\n            output_projection_b,\n        ],\n        ['output_logits'],\n    )\n    return output_logits\n"
  },
  {
    "path": "caffe2/python/models/seq2seq/train.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package train\n# Module caffe2.python.models.seq2seq.train\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport argparse\nimport collections\nimport logging\nimport math\nimport numpy as np\nimport random\nimport time\nimport sys\nimport os\n\nimport caffe2.proto.caffe2_pb2 as caffe2_pb2\nfrom caffe2.python import core, workspace, data_parallel_model\nimport caffe2.python.models.seq2seq.seq2seq_util as seq2seq_util\nfrom caffe2.python.models.seq2seq.seq2seq_model_helper import Seq2SeqModelHelper\n\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO)\nlogger.addHandler(logging.StreamHandler(sys.stderr))\n\nBatch = collections.namedtuple('Batch', [\n    'encoder_inputs',\n    'encoder_lengths',\n    'decoder_inputs',\n    'decoder_lengths',\n    'targets',\n    'target_weights',\n])\n\n\ndef prepare_batch(batch):\n    encoder_lengths = [len(entry[0]) for entry in batch]\n    max_encoder_length = max(encoder_lengths)\n    decoder_lengths = []\n    max_decoder_length = max([len(entry[1]) for entry in batch])\n\n    batch_encoder_inputs = []\n    batch_decoder_inputs = []\n    batch_targets = []\n    batch_target_weights = []\n\n    for source_seq, target_seq in batch:\n        encoder_pads = (\n            [seq2seq_util.PAD_ID] * (max_encoder_length - len(source_seq))\n        )\n        batch_encoder_inputs.append(\n            list(reversed(source_seq)) + encoder_pads\n        )\n\n        decoder_pads = (\n            [seq2seq_util.PAD_ID] * (max_decoder_length - len(target_seq))\n        )\n        target_seq_with_go_token = [seq2seq_util.GO_ID] + target_seq\n        decoder_lengths.append(len(target_seq_with_go_token))\n        batch_decoder_inputs.append(target_seq_with_go_token + decoder_pads)\n\n        target_seq_with_eos = target_seq + [seq2seq_util.EOS_ID]\n        targets = target_seq_with_eos + decoder_pads\n        batch_targets.append(targets)\n\n        if len(source_seq) + len(target_seq) == 0:\n            target_weights = [0] * len(targets)\n        else:\n            target_weights = [\n                1 if target != seq2seq_util.PAD_ID else 0\n                for target in targets\n            ]\n        batch_target_weights.append(target_weights)\n\n    return Batch(\n        encoder_inputs=np.array(\n            batch_encoder_inputs,\n            dtype=np.int32,\n        ).transpose(),\n        encoder_lengths=np.array(encoder_lengths, dtype=np.int32),\n        decoder_inputs=np.array(\n            batch_decoder_inputs,\n            dtype=np.int32,\n        ).transpose(),\n        decoder_lengths=np.array(decoder_lengths, dtype=np.int32),\n        targets=np.array(\n            batch_targets,\n            dtype=np.int32,\n        ).transpose(),\n        target_weights=np.array(\n            batch_target_weights,\n            dtype=np.float32,\n        ).transpose(),\n    )\n\n\nclass Seq2SeqModelCaffe2(object):\n\n    def _build_model(\n        self,\n        init_params,\n    ):\n        model = Seq2SeqModelHelper(init_params=init_params)\n        self._build_shared(model)\n        self._build_embeddings(model)\n\n        forward_model = Seq2SeqModelHelper(init_params=init_params)\n        self._build_shared(forward_model)\n        self._build_embeddings(forward_model)\n\n        if self.num_gpus == 0:\n            loss_blobs = self.model_build_fun(model)\n            model.AddGradientOperators(loss_blobs)\n            self.norm_clipped_grad_update(\n                model,\n                scope='norm_clipped_grad_update'\n            )\n            self.forward_model_build_fun(forward_model)\n\n        else:\n            assert (self.batch_size % self.num_gpus) == 0\n\n            data_parallel_model.Parallelize_GPU(\n                forward_model,\n                input_builder_fun=lambda m: None,\n                forward_pass_builder_fun=self.forward_model_build_fun,\n                param_update_builder_fun=None,\n                devices=list(range(self.num_gpus)),\n            )\n\n            def clipped_grad_update_bound(model):\n                self.norm_clipped_grad_update(\n                    model,\n                    scope='norm_clipped_grad_update',\n                )\n\n            data_parallel_model.Parallelize_GPU(\n                model,\n                input_builder_fun=lambda m: None,\n                forward_pass_builder_fun=self.model_build_fun,\n                param_update_builder_fun=clipped_grad_update_bound,\n                devices=list(range(self.num_gpus)),\n            )\n        self.norm_clipped_sparse_grad_update(\n            model,\n            scope='norm_clipped_sparse_grad_update',\n        )\n        self.model = model\n        self.forward_net = forward_model.net\n\n    def _build_shared(self, model):\n        optimizer_params = self.model_params['optimizer_params']\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n            self.learning_rate = model.AddParam(\n                name='learning_rate',\n                init_value=float(optimizer_params['learning_rate']),\n                trainable=False,\n            )\n            self.global_step = model.AddParam(\n                name='global_step',\n                init_value=0,\n                trainable=False,\n            )\n            self.start_time = model.AddParam(\n                name='start_time',\n                init_value=time.time(),\n                trainable=False,\n            )\n\n    def _build_embeddings(self, model):\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n            sqrt3 = math.sqrt(3)\n            self.encoder_embeddings = model.param_init_net.UniformFill(\n                [],\n                'encoder_embeddings',\n                shape=[\n                    self.source_vocab_size,\n                    self.model_params['encoder_embedding_size'],\n                ],\n                min=-sqrt3,\n                max=sqrt3,\n            )\n            model.params.append(self.encoder_embeddings)\n            self.decoder_embeddings = model.param_init_net.UniformFill(\n                [],\n                'decoder_embeddings',\n                shape=[\n                    self.target_vocab_size,\n                    self.model_params['decoder_embedding_size'],\n                ],\n                min=-sqrt3,\n                max=sqrt3,\n            )\n            model.params.append(self.decoder_embeddings)\n\n    def model_build_fun(self, model, forward_only=False, loss_scale=None):\n        encoder_inputs = model.net.AddExternalInput(\n            workspace.GetNameScope() + 'encoder_inputs',\n        )\n        encoder_lengths = model.net.AddExternalInput(\n            workspace.GetNameScope() + 'encoder_lengths',\n        )\n        decoder_inputs = model.net.AddExternalInput(\n            workspace.GetNameScope() + 'decoder_inputs',\n        )\n        decoder_lengths = model.net.AddExternalInput(\n            workspace.GetNameScope() + 'decoder_lengths',\n        )\n        targets = model.net.AddExternalInput(\n            workspace.GetNameScope() + 'targets',\n        )\n        target_weights = model.net.AddExternalInput(\n            workspace.GetNameScope() + 'target_weights',\n        )\n        attention_type = self.model_params['attention']\n        assert attention_type in ['none', 'regular', 'dot']\n\n        (\n            encoder_outputs,\n            weighted_encoder_outputs,\n            final_encoder_hidden_states,\n            final_encoder_cell_states,\n            encoder_units_per_layer,\n        ) = seq2seq_util.build_embedding_encoder(\n            model=model,\n            encoder_params=self.encoder_params,\n            num_decoder_layers=len(self.model_params['decoder_layer_configs']),\n            inputs=encoder_inputs,\n            input_lengths=encoder_lengths,\n            vocab_size=self.source_vocab_size,\n            embeddings=self.encoder_embeddings,\n            embedding_size=self.model_params['encoder_embedding_size'],\n            use_attention=(attention_type != 'none'),\n            num_gpus=self.num_gpus,\n        )\n\n        (\n            decoder_outputs,\n            decoder_output_size,\n        ) = seq2seq_util.build_embedding_decoder(\n            model,\n            decoder_layer_configs=self.model_params['decoder_layer_configs'],\n            inputs=decoder_inputs,\n            input_lengths=decoder_lengths,\n            encoder_lengths=encoder_lengths,\n            encoder_outputs=encoder_outputs,\n            weighted_encoder_outputs=weighted_encoder_outputs,\n            final_encoder_hidden_states=final_encoder_hidden_states,\n            final_encoder_cell_states=final_encoder_cell_states,\n            encoder_units_per_layer=encoder_units_per_layer,\n            vocab_size=self.target_vocab_size,\n            embeddings=self.decoder_embeddings,\n            embedding_size=self.model_params['decoder_embedding_size'],\n            attention_type=attention_type,\n            forward_only=False,\n            num_gpus=self.num_gpus,\n        )\n\n        output_logits = seq2seq_util.output_projection(\n            model=model,\n            decoder_outputs=decoder_outputs,\n            decoder_output_size=decoder_output_size,\n            target_vocab_size=self.target_vocab_size,\n            decoder_softmax_size=self.model_params['decoder_softmax_size'],\n        )\n        targets, _ = model.net.Reshape(\n            [targets],\n            ['targets', 'targets_old_shape'],\n            shape=[-1],\n        )\n        target_weights, _ = model.net.Reshape(\n            [target_weights],\n            ['target_weights', 'target_weights_old_shape'],\n            shape=[-1],\n        )\n        _, loss_per_word = model.net.SoftmaxWithLoss(\n            [output_logits, targets, target_weights],\n            ['OutputProbs_INVALID', 'loss_per_word'],\n            only_loss=True,\n        )\n\n        num_words = model.net.SumElements(\n            [target_weights],\n            'num_words',\n        )\n        total_loss_scalar = model.net.Mul(\n            [loss_per_word, num_words],\n            'total_loss_scalar',\n        )\n        total_loss_scalar_weighted = model.net.Scale(\n            [total_loss_scalar],\n            'total_loss_scalar_weighted',\n            scale=1.0 / self.batch_size,\n        )\n        return [total_loss_scalar_weighted]\n\n    def forward_model_build_fun(self, model, loss_scale=None):\n        return self.model_build_fun(\n            model=model,\n            forward_only=True,\n            loss_scale=loss_scale\n        )\n\n    def _calc_norm_ratio(self, model, params, scope, ONE):\n        with core.NameScope(scope):\n            grad_squared_sums = []\n            for i, param in enumerate(params):\n                logger.info(param)\n                grad = (\n                    model.param_to_grad[param]\n                    if not isinstance(\n                        model.param_to_grad[param],\n                        core.GradientSlice,\n                    ) else model.param_to_grad[param].values\n                )\n                grad_squared = model.net.Sqr(\n                    [grad],\n                    'grad_{}_squared'.format(i),\n                )\n                grad_squared_sum = model.net.SumElements(\n                    grad_squared,\n                    'grad_{}_squared_sum'.format(i),\n                )\n                grad_squared_sums.append(grad_squared_sum)\n\n            grad_squared_full_sum = model.net.Sum(\n                grad_squared_sums,\n                'grad_squared_full_sum',\n            )\n            global_norm = model.net.Pow(\n                grad_squared_full_sum,\n                'global_norm',\n                exponent=0.5,\n            )\n            clip_norm = model.param_init_net.ConstantFill(\n                [],\n                'clip_norm',\n                shape=[],\n                value=float(self.model_params['max_gradient_norm']),\n            )\n            max_norm = model.net.Max(\n                [global_norm, clip_norm],\n                'max_norm',\n            )\n            norm_ratio = model.net.Div(\n                [clip_norm, max_norm],\n                'norm_ratio',\n            )\n            return norm_ratio\n\n    def _apply_norm_ratio(\n        self, norm_ratio, model, params, learning_rate, scope, ONE\n    ):\n        for param in params:\n            param_grad = model.param_to_grad[param]\n            nlr = model.net.Negative(\n                [learning_rate],\n                'negative_learning_rate',\n            )\n            with core.NameScope(scope):\n                update_coeff = model.net.Mul(\n                    [nlr, norm_ratio],\n                    'update_coeff',\n                    broadcast=1,\n                )\n            if isinstance(param_grad, core.GradientSlice):\n                param_grad_values = param_grad.values\n\n                model.net.ScatterWeightedSum(\n                    [\n                        param,\n                        ONE,\n                        param_grad.indices,\n                        param_grad_values,\n                        update_coeff,\n                    ],\n                    param,\n                )\n            else:\n                model.net.WeightedSum(\n                    [\n                        param,\n                        ONE,\n                        param_grad,\n                        update_coeff,\n                    ],\n                    param,\n                )\n\n    def norm_clipped_grad_update(self, model, scope):\n\n        if self.num_gpus == 0:\n            learning_rate = self.learning_rate\n        else:\n            learning_rate = model.CopyCPUToGPU(self.learning_rate, 'LR')\n\n        params = []\n        for param in model.GetParams(top_scope=True):\n            if param in model.param_to_grad:\n                if not isinstance(\n                    model.param_to_grad[param],\n                    core.GradientSlice,\n                ):\n                    params.append(param)\n\n        ONE = model.param_init_net.ConstantFill(\n            [],\n            'ONE',\n            shape=[1],\n            value=1.0,\n        )\n        logger.info('Dense trainable variables: ')\n        norm_ratio = self._calc_norm_ratio(model, params, scope, ONE)\n        self._apply_norm_ratio(\n            norm_ratio, model, params, learning_rate, scope, ONE\n        )\n\n    def norm_clipped_sparse_grad_update(self, model, scope):\n        learning_rate = self.learning_rate\n\n        params = []\n        for param in model.GetParams(top_scope=True):\n            if param in model.param_to_grad:\n                if isinstance(\n                    model.param_to_grad[param],\n                    core.GradientSlice,\n                ):\n                    params.append(param)\n\n        ONE = model.param_init_net.ConstantFill(\n            [],\n            'ONE',\n            shape=[1],\n            value=1.0,\n        )\n        logger.info('Sparse trainable variables: ')\n        norm_ratio = self._calc_norm_ratio(model, params, scope, ONE)\n        self._apply_norm_ratio(\n            norm_ratio, model, params, learning_rate, scope, ONE\n        )\n\n    def total_loss_scalar(self):\n        if self.num_gpus == 0:\n            return workspace.FetchBlob('total_loss_scalar')\n        else:\n            total_loss = 0\n            for i in range(self.num_gpus):\n                name = 'gpu_{}/total_loss_scalar'.format(i)\n                gpu_loss = workspace.FetchBlob(name)\n                total_loss += gpu_loss\n            return total_loss\n\n    def _init_model(self):\n        workspace.RunNetOnce(self.model.param_init_net)\n\n        def create_net(net):\n            workspace.CreateNet(\n                net,\n                input_blobs=[str(i) for i in net.external_inputs],\n            )\n\n        create_net(self.model.net)\n        create_net(self.forward_net)\n\n    def __init__(\n        self,\n        model_params,\n        source_vocab_size,\n        target_vocab_size,\n        num_gpus=1,\n        num_cpus=1,\n    ):\n        self.model_params = model_params\n        self.encoder_type = 'rnn'\n        self.encoder_params = model_params['encoder_type']\n        self.source_vocab_size = source_vocab_size\n        self.target_vocab_size = target_vocab_size\n        self.num_gpus = num_gpus\n        self.num_cpus = num_cpus\n        self.batch_size = model_params['batch_size']\n\n        workspace.GlobalInit([\n            'caffe2',\n            # NOTE: modify log level for debugging purposes\n            '--caffe2_log_level=0',\n            # NOTE: modify log level for debugging purposes\n            '--v=0',\n            # Fail gracefully if one of the threads fails\n            '--caffe2_handle_executor_threads_exceptions=1',\n            '--caffe2_mkl_num_threads=' + str(self.num_cpus),\n        ])\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        workspace.ResetWorkspace()\n\n    def initialize_from_scratch(self):\n        logger.info('Initializing Seq2SeqModelCaffe2 from scratch: Start')\n        self._build_model(init_params=True)\n        self._init_model()\n        logger.info('Initializing Seq2SeqModelCaffe2 from scratch: Finish')\n\n    def get_current_step(self):\n        return workspace.FetchBlob(self.global_step)[0]\n\n    def inc_current_step(self):\n        workspace.FeedBlob(\n            self.global_step,\n            np.array([self.get_current_step() + 1]),\n        )\n\n    def step(\n        self,\n        batch,\n        forward_only\n    ):\n        if self.num_gpus < 1:\n            batch_obj = prepare_batch(batch)\n            for batch_obj_name, batch_obj_value in zip(\n                Batch._fields,\n                batch_obj,\n            ):\n                workspace.FeedBlob(batch_obj_name, batch_obj_value)\n        else:\n            for i in range(self.num_gpus):\n                gpu_batch = batch[i::self.num_gpus]\n                batch_obj = prepare_batch(gpu_batch)\n                for batch_obj_name, batch_obj_value in zip(\n                    Batch._fields,\n                    batch_obj,\n                ):\n                    name = 'gpu_{}/{}'.format(i, batch_obj_name)\n                    if batch_obj_name in ['encoder_inputs', 'decoder_inputs']:\n                        dev = core.DeviceOption(caffe2_pb2.CPU)\n                    else:\n                        dev = core.DeviceOption(caffe2_pb2.CUDA, i)\n                    workspace.FeedBlob(name, batch_obj_value, device_option=dev)\n\n        if forward_only:\n            workspace.RunNet(self.forward_net)\n        else:\n            workspace.RunNet(self.model.net)\n            self.inc_current_step()\n\n        return self.total_loss_scalar()\n\n    def save(self, checkpoint_path_prefix, current_step):\n        checkpoint_path = '{0}-{1}'.format(\n            checkpoint_path_prefix,\n            current_step,\n        )\n\n        assert workspace.RunOperatorOnce(core.CreateOperator(\n            'Save',\n            self.model.GetAllParams(),\n            [],\n            absolute_path=True,\n            db=checkpoint_path,\n            db_type='minidb',\n        ))\n\n        checkpoint_config_path = os.path.join(\n            os.path.dirname(checkpoint_path_prefix),\n            'checkpoint',\n        )\n        with open(checkpoint_config_path, 'w') as checkpoint_config_file:\n            checkpoint_config_file.write(\n                'model_checkpoint_path: \"' + checkpoint_path + '\"\\n'\n                'all_model_checkpoint_paths: \"' + checkpoint_path + '\"\\n'\n            )\n            logger.info('Saved checkpoint file to ' + checkpoint_path)\n\n        return checkpoint_path\n\ndef gen_batches(source_corpus, target_corpus, source_vocab, target_vocab,\n                batch_size, max_length):\n    with open(source_corpus) as source, open(target_corpus) as target:\n        parallel_sentences = []\n        for source_sentence, target_sentence in zip(source, target):\n            numerized_source_sentence = seq2seq_util.get_numberized_sentence(\n                source_sentence,\n                source_vocab,\n            )\n            numerized_target_sentence = seq2seq_util.get_numberized_sentence(\n                target_sentence,\n                target_vocab,\n            )\n            if (\n                len(numerized_source_sentence) > 0 and\n                len(numerized_target_sentence) > 0 and\n                (\n                    max_length is None or (\n                        len(numerized_source_sentence) <= max_length and\n                        len(numerized_target_sentence) <= max_length\n                    )\n                )\n            ):\n                parallel_sentences.append((\n                    numerized_source_sentence,\n                    numerized_target_sentence,\n                ))\n    parallel_sentences.sort(key=lambda s_t: (len(s_t[0]), len(s_t[1])))\n\n    batches, batch = [], []\n    for sentence_pair in parallel_sentences:\n        batch.append(sentence_pair)\n        if len(batch) >= batch_size:\n            batches.append(batch)\n            batch = []\n    if len(batch) > 0:\n        while len(batch) < batch_size:\n            batch.append(batch[-1])\n        assert len(batch) == batch_size\n        batches.append(batch)\n    random.shuffle(batches)\n    return batches\n\n\ndef run_seq2seq_model(args, model_params=None):\n    source_vocab = seq2seq_util.gen_vocab(\n        args.source_corpus,\n        args.unk_threshold,\n    )\n    target_vocab = seq2seq_util.gen_vocab(\n        args.target_corpus,\n        args.unk_threshold,\n    )\n    logger.info('Source vocab size {}'.format(len(source_vocab)))\n    logger.info('Target vocab size {}'.format(len(target_vocab)))\n\n    batches = gen_batches(args.source_corpus, args.target_corpus, source_vocab,\n                          target_vocab, model_params['batch_size'],\n                          args.max_length)\n    logger.info('Number of training batches {}'.format(len(batches)))\n\n    batches_eval = gen_batches(args.source_corpus_eval, args.target_corpus_eval,\n                               source_vocab, target_vocab,\n                               model_params['batch_size'], args.max_length)\n    logger.info('Number of eval batches {}'.format(len(batches_eval)))\n\n    with Seq2SeqModelCaffe2(\n        model_params=model_params,\n        source_vocab_size=len(source_vocab),\n        target_vocab_size=len(target_vocab),\n        num_gpus=args.num_gpus,\n        num_cpus=20,\n    ) as model_obj:\n        model_obj.initialize_from_scratch()\n        for i in range(args.epochs):\n            logger.info('Epoch {}'.format(i))\n            total_loss = 0\n            for batch in batches:\n                total_loss += model_obj.step(\n                    batch=batch,\n                    forward_only=False,\n                )\n            logger.info('\\ttraining loss {}'.format(total_loss))\n            total_loss = 0\n            for batch in batches_eval:\n                total_loss += model_obj.step(\n                    batch=batch,\n                    forward_only=True,\n                )\n            logger.info('\\teval loss {}'.format(total_loss))\n            if args.checkpoint is not None:\n                model_obj.save(args.checkpoint, i)\n\n\ndef main():\n    random.seed(31415)\n    parser = argparse.ArgumentParser(\n        description='Caffe2: Seq2Seq Training'\n    )\n    parser.add_argument('--source-corpus', type=str, default=None,\n                        help='Path to source corpus in a text file format. Each '\n                        'line in the file should contain a single sentence',\n                        required=True)\n    parser.add_argument('--target-corpus', type=str, default=None,\n                        help='Path to target corpus in a text file format',\n                        required=True)\n    parser.add_argument('--max-length', type=int, default=None,\n                        help='Maximal lengths of train and eval sentences')\n    parser.add_argument('--unk-threshold', type=int, default=50,\n                        help='Threshold frequency under which token becomes '\n                        'labeled unknown token')\n\n    parser.add_argument('--batch-size', type=int, default=32,\n                        help='Training batch size')\n    parser.add_argument('--epochs', type=int, default=10,\n                        help='Number of iterations over training data')\n    parser.add_argument('--learning-rate', type=float, default=0.5,\n                        help='Learning rate')\n    parser.add_argument('--max-gradient-norm', type=float, default=1.0,\n                        help='Max global norm of gradients at the end of each '\n                        'backward pass. We do clipping to match the number.')\n    parser.add_argument('--num-gpus', type=int, default=0,\n                        help='Number of GPUs for data parallel model')\n\n    parser.add_argument('--use-bidirectional-encoder', action='store_true',\n                        help='Set flag to use bidirectional recurrent network '\n                        'for first layer of encoder')\n    parser.add_argument('--use-attention', action='store_true',\n                        help='Set flag to use seq2seq with attention model')\n    parser.add_argument('--source-corpus-eval', type=str, default=None,\n                        help='Path to source corpus for evaluation in a text '\n                        'file format', required=True)\n    parser.add_argument('--target-corpus-eval', type=str, default=None,\n                        help='Path to target corpus for evaluation in a text '\n                        'file format', required=True)\n    parser.add_argument('--encoder-cell-num-units', type=int, default=512,\n                        help='Number of cell units per encoder layer')\n    parser.add_argument('--encoder-num-layers', type=int, default=2,\n                        help='Number encoder layers')\n    parser.add_argument('--decoder-cell-num-units', type=int, default=512,\n                        help='Number of cell units in the decoder layer')\n    parser.add_argument('--decoder-num-layers', type=int, default=2,\n                        help='Number decoder layers')\n    parser.add_argument('--encoder-embedding-size', type=int, default=256,\n                        help='Size of embedding in the encoder layer')\n    parser.add_argument('--decoder-embedding-size', type=int, default=512,\n                        help='Size of embedding in the decoder layer')\n    parser.add_argument('--decoder-softmax-size', type=int, default=None,\n                        help='Size of softmax layer in the decoder')\n\n    parser.add_argument('--checkpoint', type=str, default=None,\n                        help='Path to checkpoint')\n\n    args = parser.parse_args()\n\n    encoder_layer_configs = [\n        dict(\n            num_units=args.encoder_cell_num_units,\n        ),\n    ] * args.encoder_num_layers\n\n    if args.use_bidirectional_encoder:\n        assert args.encoder_cell_num_units % 2 == 0\n        encoder_layer_configs[0]['num_units'] /= 2\n\n    decoder_layer_configs = [\n        dict(\n            num_units=args.decoder_cell_num_units,\n        ),\n    ] * args.decoder_num_layers\n\n    run_seq2seq_model(args, model_params=dict(\n        attention=('regular' if args.use_attention else 'none'),\n        decoder_layer_configs=decoder_layer_configs,\n        encoder_type=dict(\n            encoder_layer_configs=encoder_layer_configs,\n            use_bidirectional_encoder=args.use_bidirectional_encoder,\n        ),\n        batch_size=args.batch_size,\n        optimizer_params=dict(\n            learning_rate=args.learning_rate,\n        ),\n        encoder_embedding_size=args.encoder_embedding_size,\n        decoder_embedding_size=args.decoder_embedding_size,\n        decoder_softmax_size=args.decoder_softmax_size,\n        max_gradient_norm=args.max_gradient_norm,\n    ))\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "caffe2/python/models/seq2seq/translate.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package translate\n# Module caffe2.python.models.seq2seq.translate\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport argparse\nfrom future.utils import viewitems\nimport logging\nimport numpy as np\nimport sys\n\nfrom caffe2.python import core, rnn_cell, workspace\nfrom caffe2.python.models.seq2seq.beam_search import BeamSearchForwardOnly\nfrom caffe2.python.models.seq2seq.seq2seq_model_helper import Seq2SeqModelHelper\nimport caffe2.python.models.seq2seq.seq2seq_util as seq2seq_util\n\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO)\nlogger.addHandler(logging.StreamHandler(sys.stderr))\n\n\ndef _weighted_sum(model, values, weight, output_name):\n    values_weights = zip(values, [weight] * len(values))\n    values_weights_flattened = [x for v_w in values_weights for x in v_w]\n    return model.net.WeightedSum(\n        values_weights_flattened,\n        output_name,\n    )\n\n\nclass Seq2SeqModelCaffe2EnsembleDecoder(object):\n\n    def scope(self, scope_name, blob_name):\n        return (\n            scope_name + '/' + blob_name\n            if scope_name is not None\n            else blob_name\n        )\n\n    def _build_decoder(\n        self,\n        model,\n        step_model,\n        model_params,\n        scope,\n        previous_tokens,\n        timestep,\n        fake_seq_lengths,\n    ):\n        attention_type = model_params['attention']\n        assert attention_type in ['none', 'regular']\n        use_attention = (attention_type != 'none')\n\n        with core.NameScope(scope):\n            encoder_embeddings = seq2seq_util.build_embeddings(\n                model=model,\n                vocab_size=self.source_vocab_size,\n                embedding_size=model_params['encoder_embedding_size'],\n                name='encoder_embeddings',\n                freeze_embeddings=False,\n            )\n\n        (\n            encoder_outputs,\n            weighted_encoder_outputs,\n            final_encoder_hidden_states,\n            final_encoder_cell_states,\n            encoder_units_per_layer,\n        ) = seq2seq_util.build_embedding_encoder(\n            model=model,\n            encoder_params=model_params['encoder_type'],\n            num_decoder_layers=len(model_params['decoder_layer_configs']),\n            inputs=self.encoder_inputs,\n            input_lengths=self.encoder_lengths,\n            vocab_size=self.source_vocab_size,\n            embeddings=encoder_embeddings,\n            embedding_size=model_params['encoder_embedding_size'],\n            use_attention=use_attention,\n            num_gpus=0,\n            forward_only=True,\n            scope=scope,\n        )\n        with core.NameScope(scope):\n            if use_attention:\n                # [max_source_length, beam_size, encoder_output_dim]\n                encoder_outputs = model.net.Tile(\n                    encoder_outputs,\n                    'encoder_outputs_tiled',\n                    tiles=self.beam_size,\n                    axis=1,\n                )\n\n            if weighted_encoder_outputs is not None:\n                weighted_encoder_outputs = model.net.Tile(\n                    weighted_encoder_outputs,\n                    'weighted_encoder_outputs_tiled',\n                    tiles=self.beam_size,\n                    axis=1,\n                )\n\n            decoder_embeddings = seq2seq_util.build_embeddings(\n                model=model,\n                vocab_size=self.target_vocab_size,\n                embedding_size=model_params['decoder_embedding_size'],\n                name='decoder_embeddings',\n                freeze_embeddings=False,\n            )\n            embedded_tokens_t_prev = step_model.net.Gather(\n                [decoder_embeddings, previous_tokens],\n                'embedded_tokens_t_prev',\n            )\n\n        decoder_cells = []\n        decoder_units_per_layer = []\n        for i, layer_config in enumerate(model_params['decoder_layer_configs']):\n            num_units = layer_config['num_units']\n            decoder_units_per_layer.append(num_units)\n            if i == 0:\n                input_size = model_params['decoder_embedding_size']\n            else:\n                input_size = (\n                    model_params['decoder_layer_configs'][i - 1]['num_units']\n                )\n\n            cell = rnn_cell.LSTMCell(\n                forward_only=True,\n                input_size=input_size,\n                hidden_size=num_units,\n                forget_bias=0.0,\n                memory_optimization=False,\n            )\n            decoder_cells.append(cell)\n\n        with core.NameScope(scope):\n            if final_encoder_hidden_states is not None:\n                for i in range(len(final_encoder_hidden_states)):\n                    if final_encoder_hidden_states[i] is not None:\n                        final_encoder_hidden_states[i] = model.net.Tile(\n                            final_encoder_hidden_states[i],\n                            'final_encoder_hidden_tiled_{}'.format(i),\n                            tiles=self.beam_size,\n                            axis=1,\n                        )\n            if final_encoder_cell_states is not None:\n                for i in range(len(final_encoder_cell_states)):\n                    if final_encoder_cell_states[i] is not None:\n                        final_encoder_cell_states[i] = model.net.Tile(\n                            final_encoder_cell_states[i],\n                            'final_encoder_cell_tiled_{}'.format(i),\n                            tiles=self.beam_size,\n                            axis=1,\n                        )\n            initial_states = \\\n                seq2seq_util.build_initial_rnn_decoder_states(\n                    model=model,\n                    encoder_units_per_layer=encoder_units_per_layer,\n                    decoder_units_per_layer=decoder_units_per_layer,\n                    final_encoder_hidden_states=final_encoder_hidden_states,\n                    final_encoder_cell_states=final_encoder_cell_states,\n                    use_attention=use_attention,\n                )\n\n        attention_decoder = seq2seq_util.LSTMWithAttentionDecoder(\n            encoder_outputs=encoder_outputs,\n            encoder_output_dim=encoder_units_per_layer[-1],\n            encoder_lengths=None,\n            vocab_size=self.target_vocab_size,\n            attention_type=attention_type,\n            embedding_size=model_params['decoder_embedding_size'],\n            decoder_num_units=decoder_units_per_layer[-1],\n            decoder_cells=decoder_cells,\n            weighted_encoder_outputs=weighted_encoder_outputs,\n            name=scope,\n        )\n        states_prev = step_model.net.AddExternalInputs(*[\n            '{}/{}_prev'.format(scope, s)\n            for s in attention_decoder.get_state_names()\n        ])\n        decoder_outputs, states = attention_decoder.apply(\n            model=step_model,\n            input_t=embedded_tokens_t_prev,\n            seq_lengths=fake_seq_lengths,\n            states=states_prev,\n            timestep=timestep,\n        )\n\n        state_configs = [\n            BeamSearchForwardOnly.StateConfig(\n                initial_value=initial_state,\n                state_prev_link=BeamSearchForwardOnly.LinkConfig(\n                    blob=state_prev,\n                    offset=0,\n                    window=1,\n                ),\n                state_link=BeamSearchForwardOnly.LinkConfig(\n                    blob=state,\n                    offset=1,\n                    window=1,\n                ),\n            )\n            for initial_state, state_prev, state in zip(\n                initial_states,\n                states_prev,\n                states,\n            )\n        ]\n\n        with core.NameScope(scope):\n            decoder_outputs_flattened, _ = step_model.net.Reshape(\n                [decoder_outputs],\n                [\n                    'decoder_outputs_flattened',\n                    'decoder_outputs_and_contexts_combination_old_shape',\n                ],\n                shape=[-1, attention_decoder.get_output_dim()],\n            )\n            output_logits = seq2seq_util.output_projection(\n                model=step_model,\n                decoder_outputs=decoder_outputs_flattened,\n                decoder_output_size=attention_decoder.get_output_dim(),\n                target_vocab_size=self.target_vocab_size,\n                decoder_softmax_size=model_params['decoder_softmax_size'],\n            )\n            # [1, beam_size, target_vocab_size]\n            output_probs = step_model.net.Softmax(\n                output_logits,\n                'output_probs',\n            )\n            output_log_probs = step_model.net.Log(\n                output_probs,\n                'output_log_probs',\n            )\n            if use_attention:\n                attention_weights = attention_decoder.get_attention_weights()\n            else:\n                attention_weights = step_model.net.ConstantFill(\n                    [self.encoder_inputs],\n                    'zero_attention_weights_tmp_1',\n                    value=0.0,\n                )\n                attention_weights = step_model.net.Transpose(\n                    attention_weights,\n                    'zero_attention_weights_tmp_2',\n                )\n                attention_weights = step_model.net.Tile(\n                    attention_weights,\n                    'zero_attention_weights_tmp',\n                    tiles=self.beam_size,\n                    axis=0,\n                )\n\n        return (\n            state_configs,\n            output_log_probs,\n            attention_weights,\n        )\n\n    def build_word_rewards(self, vocab_size, word_reward, unk_reward):\n        word_rewards = np.full([vocab_size], word_reward, dtype=np.float32)\n        word_rewards[seq2seq_util.PAD_ID] = 0\n        word_rewards[seq2seq_util.GO_ID] = 0\n        word_rewards[seq2seq_util.EOS_ID] = 0\n        word_rewards[seq2seq_util.UNK_ID] = word_reward + unk_reward\n        return word_rewards\n\n    def __init__(\n        self,\n        translate_params,\n    ):\n        self.models = translate_params['ensemble_models']\n        decoding_params = translate_params['decoding_params']\n        self.beam_size = decoding_params['beam_size']\n\n        assert len(self.models) > 0\n        source_vocab = self.models[0]['source_vocab']\n        target_vocab = self.models[0]['target_vocab']\n        for model in self.models:\n            assert model['source_vocab'] == source_vocab\n            assert model['target_vocab'] == target_vocab\n\n        self.source_vocab_size = len(source_vocab)\n        self.target_vocab_size = len(target_vocab)\n\n        self.decoder_scope_names = [\n            'model{}'.format(i) for i in range(len(self.models))\n        ]\n\n        self.model = Seq2SeqModelHelper(init_params=True)\n\n        self.encoder_inputs = self.model.net.AddExternalInput('encoder_inputs')\n        self.encoder_lengths = self.model.net.AddExternalInput(\n            'encoder_lengths'\n        )\n        self.max_output_seq_len = self.model.net.AddExternalInput(\n            'max_output_seq_len'\n        )\n\n        fake_seq_lengths = self.model.param_init_net.ConstantFill(\n            [],\n            'fake_seq_lengths',\n            shape=[self.beam_size],\n            value=100000,\n            dtype=core.DataType.INT32,\n        )\n\n        beam_decoder = BeamSearchForwardOnly(\n            beam_size=self.beam_size,\n            model=self.model,\n            go_token_id=seq2seq_util.GO_ID,\n            eos_token_id=seq2seq_util.EOS_ID,\n        )\n        step_model = beam_decoder.get_step_model()\n\n        state_configs = []\n        output_log_probs = []\n        attention_weights = []\n        for model, scope_name in zip(\n            self.models,\n            self.decoder_scope_names,\n        ):\n            (\n                state_configs_per_decoder,\n                output_log_probs_per_decoder,\n                attention_weights_per_decoder,\n            ) = self._build_decoder(\n                model=self.model,\n                step_model=step_model,\n                model_params=model['model_params'],\n                scope=scope_name,\n                previous_tokens=beam_decoder.get_previous_tokens(),\n                timestep=beam_decoder.get_timestep(),\n                fake_seq_lengths=fake_seq_lengths,\n            )\n            state_configs.extend(state_configs_per_decoder)\n            output_log_probs.append(output_log_probs_per_decoder)\n            if attention_weights_per_decoder is not None:\n                attention_weights.append(attention_weights_per_decoder)\n\n        assert len(attention_weights) > 0\n        num_decoders_with_attention_blob = (\n            self.model.param_init_net.ConstantFill(\n                [],\n                'num_decoders_with_attention_blob',\n                value=1 / float(len(attention_weights)),\n                shape=[1],\n            )\n        )\n        # [beam_size, encoder_length, 1]\n        attention_weights_average = _weighted_sum(\n            model=step_model,\n            values=attention_weights,\n            weight=num_decoders_with_attention_blob,\n            output_name='attention_weights_average',\n        )\n\n        num_decoders_blob = self.model.param_init_net.ConstantFill(\n            [],\n            'num_decoders_blob',\n            value=1 / float(len(output_log_probs)),\n            shape=[1],\n        )\n        # [beam_size, target_vocab_size]\n        output_log_probs_average = _weighted_sum(\n            model=step_model,\n            values=output_log_probs,\n            weight=num_decoders_blob,\n            output_name='output_log_probs_average',\n        )\n        word_rewards = self.model.param_init_net.ConstantFill(\n            [],\n            'word_rewards',\n            shape=[self.target_vocab_size],\n            value=0.0,\n            dtype=core.DataType.FLOAT,\n        )\n        (\n            self.output_token_beam_list,\n            self.output_prev_index_beam_list,\n            self.output_score_beam_list,\n            self.output_attention_weights_beam_list,\n        ) = beam_decoder.apply(\n            inputs=self.encoder_inputs,\n            length=self.max_output_seq_len,\n            log_probs=output_log_probs_average,\n            attentions=attention_weights_average,\n            state_configs=state_configs,\n            data_dependencies=[],\n            word_rewards=word_rewards,\n        )\n\n        workspace.RunNetOnce(self.model.param_init_net)\n        workspace.FeedBlob(\n            'word_rewards',\n            self.build_word_rewards(\n                vocab_size=self.target_vocab_size,\n                word_reward=translate_params['decoding_params']['word_reward'],\n                unk_reward=translate_params['decoding_params']['unk_reward'],\n            )\n        )\n\n        workspace.CreateNet(\n            self.model.net,\n            input_blobs=[\n                str(self.encoder_inputs),\n                str(self.encoder_lengths),\n                str(self.max_output_seq_len),\n            ],\n        )\n\n        logger.info('Params created: ')\n        for param in self.model.params:\n            logger.info(param)\n\n    def load_models(self):\n        db_reader = 'reader'\n        for model, scope_name in zip(\n            self.models,\n            self.decoder_scope_names,\n        ):\n            params_for_current_model = [\n                param\n                for param in self.model.GetAllParams()\n                if str(param).startswith(scope_name)\n            ]\n            assert workspace.RunOperatorOnce(core.CreateOperator(\n                'CreateDB',\n                [], [db_reader],\n                db=model['model_file'],\n                db_type='minidb')\n            ), 'Failed to create db {}'.format(model['model_file'])\n            assert workspace.RunOperatorOnce(core.CreateOperator(\n                'Load',\n                [db_reader],\n                params_for_current_model,\n                load_all=1,\n                add_prefix=scope_name + '/',\n                strip_prefix='gpu_0/',\n            ))\n            logger.info('Model {} is loaded from a checkpoint {}'.format(\n                scope_name,\n                model['model_file'],\n            ))\n\n    def decode(self, numberized_input, max_output_seq_len):\n        workspace.FeedBlob(\n            self.encoder_inputs,\n            np.array([\n                [token_id] for token_id in reversed(numberized_input)\n            ]).astype(dtype=np.int32),\n        )\n        workspace.FeedBlob(\n            self.encoder_lengths,\n            np.array([len(numberized_input)]).astype(dtype=np.int32),\n        )\n        workspace.FeedBlob(\n            self.max_output_seq_len,\n            np.array([max_output_seq_len]).astype(dtype=np.int64),\n        )\n\n        workspace.RunNet(self.model.net)\n\n        num_steps = max_output_seq_len\n        score_beam_list = workspace.FetchBlob(self.output_score_beam_list)\n        token_beam_list = (\n            workspace.FetchBlob(self.output_token_beam_list)\n        )\n        prev_index_beam_list = (\n            workspace.FetchBlob(self.output_prev_index_beam_list)\n        )\n\n        attention_weights_beam_list = (\n            workspace.FetchBlob(self.output_attention_weights_beam_list)\n        )\n        best_indices = (num_steps, 0)\n        for i in range(num_steps + 1):\n            for hyp_index in range(self.beam_size):\n                if (\n                    (\n                        token_beam_list[i][hyp_index][0] ==\n                        seq2seq_util.EOS_ID or\n                        i == num_steps\n                    ) and\n                    (\n                        score_beam_list[i][hyp_index][0] >\n                        score_beam_list[best_indices[0]][best_indices[1]][0]\n                    )\n                ):\n                    best_indices = (i, hyp_index)\n\n        i, hyp_index = best_indices\n        output = []\n        attention_weights_per_token = []\n        best_score = -score_beam_list[i][hyp_index][0]\n        while i > 0:\n            output.append(token_beam_list[i][hyp_index][0])\n            attention_weights_per_token.append(\n                attention_weights_beam_list[i][hyp_index]\n            )\n            hyp_index = prev_index_beam_list[i][hyp_index][0]\n            i -= 1\n\n        attention_weights_per_token = reversed(attention_weights_per_token)\n        # encoder_inputs are reversed, see get_batch func\n        attention_weights_per_token = [\n            list(reversed(attention_weights))[:len(numberized_input)]\n            for attention_weights in attention_weights_per_token\n        ]\n        output = list(reversed(output))\n        return output, attention_weights_per_token, best_score\n\n\ndef run_seq2seq_beam_decoder(args, model_params, decoding_params):\n    source_vocab = seq2seq_util.gen_vocab(\n        args.source_corpus,\n        args.unk_threshold,\n    )\n    logger.info('Source vocab size {}'.format(len(source_vocab)))\n    target_vocab = seq2seq_util.gen_vocab(\n        args.target_corpus,\n        args.unk_threshold,\n    )\n    inversed_target_vocab = {v: k for (k, v) in viewitems(target_vocab)}\n    logger.info('Target vocab size {}'.format(len(target_vocab)))\n\n    decoder = Seq2SeqModelCaffe2EnsembleDecoder(\n        translate_params=dict(\n            ensemble_models=[dict(\n                source_vocab=source_vocab,\n                target_vocab=target_vocab,\n                model_params=model_params,\n                model_file=args.checkpoint,\n            )],\n            decoding_params=decoding_params,\n        ),\n    )\n    decoder.load_models()\n\n    for line in sys.stdin:\n        numerized_source_sentence = seq2seq_util.get_numberized_sentence(\n            line,\n            source_vocab,\n        )\n        translation, alignment, _ = decoder.decode(\n            numerized_source_sentence,\n            2 * len(numerized_source_sentence) + 5,\n        )\n        print(' '.join([inversed_target_vocab[tid] for tid in translation]))\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        description='Caffe2: Seq2Seq Translation',\n    )\n    parser.add_argument('--source-corpus', type=str, default=None,\n                        help='Path to source corpus in a text file format. Each '\n                        'line in the file should contain a single sentence',\n                        required=True)\n    parser.add_argument('--target-corpus', type=str, default=None,\n                        help='Path to target corpus in a text file format',\n                        required=True)\n    parser.add_argument('--unk-threshold', type=int, default=50,\n                        help='Threshold frequency under which token becomes '\n                        'labeled unknown token')\n\n    parser.add_argument('--use-bidirectional-encoder', action='store_true',\n                        help='Set flag to use bidirectional recurrent network '\n                        'in encoder')\n    parser.add_argument('--use-attention', action='store_true',\n                        help='Set flag to use seq2seq with attention model')\n    parser.add_argument('--encoder-cell-num-units', type=int, default=512,\n                        help='Number of cell units per encoder layer')\n    parser.add_argument('--encoder-num-layers', type=int, default=2,\n                        help='Number encoder layers')\n    parser.add_argument('--decoder-cell-num-units', type=int, default=512,\n                        help='Number of cell units in the decoder layer')\n    parser.add_argument('--decoder-num-layers', type=int, default=2,\n                        help='Number decoder layers')\n    parser.add_argument('--encoder-embedding-size', type=int, default=256,\n                        help='Size of embedding in the encoder layer')\n    parser.add_argument('--decoder-embedding-size', type=int, default=512,\n                        help='Size of embedding in the decoder layer')\n    parser.add_argument('--decoder-softmax-size', type=int, default=None,\n                        help='Size of softmax layer in the decoder')\n\n    parser.add_argument('--beam-size', type=int, default=6,\n                        help='Size of beam for the decoder')\n    parser.add_argument('--word-reward', type=float, default=0.0,\n                        help='Reward per each word generated.')\n    parser.add_argument('--unk-reward', type=float, default=0.0,\n                        help='Reward per each UNK token generated. '\n                        'Typically should be negative.')\n\n    parser.add_argument('--checkpoint', type=str, default=None,\n                        help='Path to checkpoint', required=True)\n\n    args = parser.parse_args()\n\n    encoder_layer_configs = [\n        dict(\n            num_units=args.encoder_cell_num_units,\n        ),\n    ] * args.encoder_num_layers\n\n    if args.use_bidirectional_encoder:\n        assert args.encoder_cell_num_units % 2 == 0\n        encoder_layer_configs[0]['num_units'] /= 2\n\n    decoder_layer_configs = [\n        dict(\n            num_units=args.decoder_cell_num_units,\n        ),\n    ] * args.decoder_num_layers\n\n    run_seq2seq_beam_decoder(\n        args,\n        model_params=dict(\n            attention=('regular' if args.use_attention else 'none'),\n            decoder_layer_configs=decoder_layer_configs,\n            encoder_type=dict(\n                encoder_layer_configs=encoder_layer_configs,\n                use_bidirectional_encoder=args.use_bidirectional_encoder,\n            ),\n            encoder_embedding_size=args.encoder_embedding_size,\n            decoder_embedding_size=args.decoder_embedding_size,\n            decoder_softmax_size=args.decoder_softmax_size,\n        ),\n        decoding_params=dict(\n            beam_size=args.beam_size,\n            word_reward=args.word_reward,\n            unk_reward=args.unk_reward,\n        ),\n    )\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "caffe2/python/modifier_context.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n# @package modifier_context\n# Module caffe2.python.modifier_context\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n\nDEFAULT_MODIFIER = 'DEFAULT'\n\n\nclass ModifierContext(object):\n    \"\"\"\n    provide context to allow param_info to have different modifiers\n    \"\"\"\n\n    def __init__(self):\n        self._modifiers = {}\n        self._modifiers_list = []\n\n    def _rebuild_modifiers(self):\n        self._modifiers = {}\n        for m in self._modifiers_list:\n            self._modifiers.update(m)\n\n    def _has_modifier(self, name):\n        return name in self._modifiers\n\n    def _get_modifier(self, name):\n        return self._modifiers.get(name)\n\n    def push_modifiers(self, modifiers):\n        # modifier override is allowed\n        self._modifiers_list.append(modifiers)\n        self._modifiers.update(modifiers)\n\n    def pop_modifiers(self):\n        assert len(self._modifiers_list) > 0\n        self._modifiers_list.pop()\n        self._rebuild_modifiers()\n\n\nclass UseModifierBase(object):\n    '''\n    context class to allow setting the current context.\n    Example useage with layer:\n        modifiers = {'modifier1': modifier1, 'modifier2': modifier2}\n        with Modifiers(modifiers):\n            modifier = ModifierContext.current().get_modifier('modifier1')\n            layer(modifier=modifier)\n    '''\n\n    def __init__(self, modifier_or_dict):\n        if isinstance(modifier_or_dict, dict):\n            self._modifiers = modifier_or_dict\n        else:\n            self._modifiers = {DEFAULT_MODIFIER: modifier_or_dict}\n\n    def _context_class(self):\n        raise NotImplementedError\n\n    def __enter__(self):\n        self._context_class().current().push_modifiers(self._modifiers)\n        return self\n\n    def __exit__(self, type, value, traceback):\n        self._context_class().current().pop_modifiers()\n"
  },
  {
    "path": "caffe2/python/mpi_python.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <pybind11/pybind11.h>\n#include <pybind11/stl.h>\n\n#include \"caffe2/mpi/mpi_common.h\"\n\nnamespace caffe2 {\n\nnamespace py = pybind11;\n\nPYBIND11_MODULE(mpi_utils, m) {\n  m.doc() = \"MPI helper functions\";\n  m.def(\n      \"SetupPeers\",\n      &MPISetupPeers,\n      py::arg(\"replicas\"),\n      py::arg(\"role\"),\n      py::arg(\"job_path\"));\n  m.def(\"CommSize\", [] {\n    auto comm = GlobalMPIComm();\n    return MPICommSize(comm);\n  });\n  m.def(\"CommRank\", [] {\n    auto comm = GlobalMPIComm();\n    return MPICommRank(comm);\n  });\n  m.def(\"Finalize\", [] {\n    // NOTE(pietern): Doesn't seem to work when calling it\n    // from Python. It ends up calling pthread_join on a\n    // thread that doesn't exit. For now, running mpirun\n    // with `-quiet` and skipping the finalize call.\n    MPI_Finalize();\n  });\n  m.def(\"Broadcast\", [](py::bytes in) -> py::bytes {\n    std::string str = in;\n    auto comm = GlobalMPIComm();\n    auto length = str.length();\n    MPI_Bcast(&length, sizeof(length), MPI_CHAR, 0, comm);\n    auto ptr = caffe2::make_unique<char[]>(length);\n    if (MPICommRank(comm) == 0) {\n      memcpy(ptr.get(), str.data(), str.length());\n    }\n    MPI_Bcast(ptr.get(), length, MPI_CHAR, 0, comm);\n    return std::string(ptr.get(), length);\n  });\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/python/muji.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package muji\n# Module caffe2.python.muji\n\"\"\"muji.py does multi-gpu training for caffe2 with no need to change the c++\nside code. Everything is defined on the computation graph level.\n\nCurrently, here are the assumptions: we only support the following use cases:\n  - 2 gpus, where peer access is enabled between them.\n  - 4 gpus, where peer access are enabled between all of them.\n  - 8 gpus, where peer access are enabled in two groups,\n    between {1, 2, 3, 4} and {5, 6, 7, 8}.\n\"\"\"\n\nfrom caffe2.proto import caffe2_pb2\n\n\ndef OnGPU(gpu_id):\n    \"\"\"A utility function that returns a device option protobuf of the\n  specified gpu id.\n  \"\"\"\n    device_option = caffe2_pb2.DeviceOption()\n    device_option.device_type = caffe2_pb2.CUDA\n    device_option.cuda_gpu_id = gpu_id\n    return device_option\n\n\ndef OnCPU():\n    device_option = caffe2_pb2.DeviceOption()\n    device_option.device_type = caffe2_pb2.CPU\n    return device_option\n\n\ndef Allreduce(net, blobs, reduced_affix=\"_reduced\", gpu_indices=None):\n    \"\"\"The general Allreduce interface that reroutes the function calls.\n  \"\"\"\n    if gpu_indices is None:\n        gpu_indices = list(range(len(blobs)))\n    if len(gpu_indices) != len(blobs):\n        raise RuntimeError(\n            \"gpu_indices length and blobs length mismatch: %d vs %d\" %\n            (len(gpu_indices), len(blobs))\n        )\n    if len(blobs) == 2:\n        return Allreduce2(net, blobs, reduced_affix, gpu_indices)\n    elif len(blobs) == 4:\n        return Allreduce4(net, blobs, reduced_affix, gpu_indices)\n    elif len(blobs) == 8:\n        return Allreduce8(net, blobs, reduced_affix, gpu_indices)\n    else:\n        return AllreduceFallback(net, blobs, reduced_affix, gpu_indices)\n\n\ndef Allreduce2(net, blobs, reduced_affix, gpu_indices):\n    \"\"\"Allreduce for 2 gpus.\n\n  Algorithm: 0r <- 0 + 1, 1r <- 0r, where r means \"reduced\"\n  \"\"\"\n    a, b = blobs\n    gpu_a, gpu_b = gpu_indices\n    a_reduced = net.Add([a, b], a + reduced_affix, device_option=OnGPU(gpu_a))\n    b_reduced = a_reduced.Copy(\n        [],\n        b + reduced_affix,\n        device_option=OnGPU(gpu_b)\n    )\n    return a_reduced, b_reduced\n\n\ndef Allreduce4(net, blobs, reduced_affix, gpu_indices):\n    \"\"\"Allreduce for 4 gpus.\n\n  Algorithm: 2 level reduction.\n      0r <- 0 + 1, 2r <- 2 + 3\n      0r <- 0r + 2r\n      2r <- 0r,\n      1r <- 0r, 3r <- 2r\n  \"\"\"\n    a, b, c, d = blobs\n    gpu_a, gpu_b, gpu_c, gpu_d = gpu_indices\n    # a_reduced <- a+b, c_reduced <- c + d\n    a_reduced = net.Add(\n        [a, b],\n        str(a) + reduced_affix,\n        device_option=OnGPU(gpu_a)\n    )\n    c_reduced = net.Add(\n        [c, d],\n        str(c) + reduced_affix,\n        device_option=OnGPU(gpu_c)\n    )\n    # a_reduced <- a_reduced + c_reduced\n    a_reduced = a_reduced.Add(c_reduced, a_reduced, device_option=OnGPU(gpu_a))\n    # broadcast a_reduced to c_reduced\n    c_reduced = a_reduced.Copy([], c_reduced, device_option=OnGPU(gpu_c))\n    # broadcast to b and d\n    b_reduced = a_reduced.Copy(\n        [],\n        str(b) + reduced_affix,\n        device_option=OnGPU(gpu_b)\n    )\n    d_reduced = c_reduced.Copy(\n        [],\n        str(d) + reduced_affix,\n        device_option=OnGPU(gpu_d)\n    )\n    return a_reduced, b_reduced, c_reduced, d_reduced\n\n\ndef Allreduce8(net, blobs, reduced_affix, gpu_indices):\n    \"\"\"Allreduce for 8 gpus.\n\n  Algorithm: 3 level reduction.\n      0r <- 0 + 1, 2r <- 2 + 3, 4r <- 4 + 5, 6r <- 6 + 7\n      0r <- 0r + 2r, 4r <- 4r + 6r\n      0r <- 0r + 4r\n      4r <- 0r\n      2r <- 0r, 6r <- 4r\n      1r <- 0r, 3r <- 2r, 5r <- 4r, 7r <- 6r\n  \"\"\"\n    reduced = [None] * 8\n    # Reduction level 1\n    for i in [0, 2, 4, 6]:\n        reduced[i] = net.Add(\n            [blobs[i], blobs[i + 1]],\n            blobs[i] + reduced_affix,\n            device_option=OnGPU(gpu_indices[i])\n        )\n    # Reduction level 2\n    for i in [0, 4]:\n        reduced[i] = net.Add(\n            [reduced[i], reduced[i + 2]],\n            str(blobs[i]) + reduced_affix,\n            device_option=OnGPU(gpu_indices[i])\n        )\n    # Reduction level 3: this involves a copy.\n    reduced_4_copy = reduced[4].Copy(\n        [],\n        str(reduced[4]) + '_copy',\n        device_option=OnGPU(gpu_indices[0])\n    )\n    reduced[0] = reduced[0].Add(\n        reduced_4_copy,\n        reduced[0],\n        device_option=OnGPU(gpu_indices[0])\n    )\n    # Broadcast level 1\n    reduced[4] = reduced[0].Copy(\n        [],\n        reduced[4],\n        device_option=OnGPU(gpu_indices[4])\n    )\n    # Broadcast level 2\n    for i in [2, 6]:\n        reduced[i] = reduced[i - 2].Copy(\n            [],\n            reduced[i],\n            device_option=OnGPU(gpu_indices[i])\n        )\n    # Broadcast level 3\n    for i in [1, 3, 5, 7]:\n        reduced[i] = reduced[i - 1].Copy(\n            [],\n            blobs[i] + reduced_affix,\n            device_option=OnGPU(gpu_indices[i])\n        )\n    return reduced\n\n\ndef AllreduceFallback(net, blobs, reduced_affix, gpu_indices):\n    \"\"\"A fallback option for Allreduce with no assumption on p2p.\n\n  Algorithm: a flat operation on gpu 0\n      0r <- 0\n      0r <- 0r + i for i in gpu_indices[1:]\n      ir <- 0r for i in gpu_indices[1:]\n  \"\"\"\n    reduced = [None] * len(gpu_indices)\n    # copy first\n    reduced[0] = net.Copy(\n        blobs[0],\n        blobs[0] + reduced_affix,\n        device_option=OnGPU(gpu_indices[0])\n    )\n    # do temp copy and add\n    temp_name = reduced[0] + '_temp_copy'\n    for i in range(1, len(gpu_indices)):\n        temp = net.Copy(\n            blobs[i],\n            temp_name,\n            device_option=OnGPU(gpu_indices[0])\n        )\n        reduced[0] = reduced[0].Add(\n            temp,\n            reduced[0],\n            device_option=OnGPU(gpu_indices[0])\n        )\n    # Broadcast to everyone else\n    for i in range(1, len(gpu_indices)):\n        reduced[i] = net.Copy(\n            reduced[0],\n            blobs[i] + reduced_affix,\n            device_option=OnGPU(gpu_indices[i])\n        )\n    return reduced\n"
  },
  {
    "path": "caffe2/python/muji_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nimport numpy as np\nimport unittest\n\nfrom caffe2.python import core, workspace, muji, test_util\n\n\n@unittest.skipIf(not workspace.has_gpu_support, \"no gpu\")\nclass TestMuji(test_util.TestCase):\n    def RunningAllreduceWithGPUs(self, gpu_ids, allreduce_function):\n        \"\"\"A base function to test different scenarios.\"\"\"\n        net = core.Net(\"mujitest\")\n        for id in gpu_ids:\n            net.ConstantFill(\n                [],\n                \"testblob_gpu_\" + str(id),\n                shape=[1, 2, 3, 4],\n                value=float(id + 1),\n                device_option=muji.OnGPU(id)\n            )\n        allreduce_function(\n            net, [\"testblob_gpu_\" + str(i)\n                  for i in gpu_ids], \"_reduced\", gpu_ids\n        )\n        workspace.RunNetOnce(net)\n        target_value = sum(gpu_ids) + len(gpu_ids)\n        all_blobs = workspace.Blobs()\n        all_blobs.sort()\n        for blob in all_blobs:\n            print('{} {}'.format(blob, workspace.FetchBlob(blob)))\n\n        for idx in gpu_ids:\n            blob = workspace.FetchBlob(\"testblob_gpu_\" + str(idx) + \"_reduced\")\n            np.testing.assert_array_equal(\n                blob,\n                target_value,\n                err_msg=\"gpu id %d of %s\" % (idx, str(gpu_ids))\n            )\n\n    def testAllreduceFallback(self):\n        self.RunningAllreduceWithGPUs(\n            list(range(workspace.NumCudaDevices())), muji.AllreduceFallback\n        )\n\n    def testAllreduceSingleGPU(self):\n        for i in range(workspace.NumCudaDevices()):\n            self.RunningAllreduceWithGPUs([i], muji.Allreduce)\n\n    def testAllreduceWithTwoGPUs(self):\n        pattern = workspace.GetCudaPeerAccessPattern()\n        if pattern.shape[0] >= 2 and np.all(pattern[:2, :2]):\n            self.RunningAllreduceWithGPUs([0, 1], muji.Allreduce2)\n        else:\n            print('Skipping allreduce with 2 gpus. Not peer access ready.')\n\n    def testAllreduceWithFourGPUs(self):\n        pattern = workspace.GetCudaPeerAccessPattern()\n        if pattern.shape[0] >= 4 and np.all(pattern[:4, :4]):\n            self.RunningAllreduceWithGPUs([0, 1, 2, 3], muji.Allreduce4)\n        else:\n            print('Skipping allreduce with 4 gpus. Not peer access ready.')\n\n    def testAllreduceWithEightGPUs(self):\n        pattern = workspace.GetCudaPeerAccessPattern()\n        if (\n            pattern.shape[0] >= 8 and np.all(pattern[:4, :4]) and\n            np.all(pattern[4:, 4:])\n        ):\n            self.RunningAllreduceWithGPUs(\n                list(range(8)), muji.Allreduce8)\n        else:\n            print('Skipping allreduce with 8 gpus. Not peer access ready.')\n"
  },
  {
    "path": "caffe2/python/net_builder.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package net_builder\n# Module caffe2.python.net_builder\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, context\nfrom caffe2.python.task import Task, TaskGroup\nfrom caffe2.python.control_ops_util import add_if_op, add_while_op\n\n\n@context.define_context()\nclass NetBuilder(object):\n    \"\"\"\n    Scope-driven mechanism for building nets, loops and conditional blocks.\n    Arguments:\n      name: NetBuilder's name\n      initial_scope: list of blobs that are available for reading/writing\n    Example:\n        from caffe2.python.net_builder import NetBuilder, ops\n        with NetBuilder() as nb:\n            c = ops.Const(5)\n            d = ops.Const(0)\n            with ops.loop():\n                ops.stop_if(ops.LE([c, ops.Const(0)]))\n                ops.Add([c, ops.Const(-1)], [c])\n                with ops.If(ops.GE([c, ops.Const(3)])):\n                    ops.Add([d, ops.Const(10)], [d])\n            ops.Print(c, [])\n            ops.Print(d, [])\n        step = core.to_execution_step(nb)\n    \"\"\"\n    def __init__(self, name=None, initial_scope=None, _stop_blob_required=False,\n                 _stop_blob=None, _fullname=None, _use_control_ops=False):\n        parent = NetBuilder.current(required=False)\n        assert not _fullname or not name, 'Cannot set both _fullname and name'\n        assert not _use_control_ops or \\\n            (not _stop_blob_required and not _stop_blob), \\\n            'Stop blobs are not used with control operators'\n        self.name = _fullname or '/'.join(\n            n for n in (parent.name if parent else None, name) if n\n        )\n        self._frozen = False\n        self._current_net = None\n        self._children = []\n        if parent:\n            # make sure parent has an up to date lexical scope computed\n            parent._update_lexical_scope()\n        self._init_lexical_scope = set(parent._lexical_scope) if parent else set()\n        if initial_scope:\n            self._init_lexical_scope |= set([str(b) for b in initial_scope])\n        self._lexical_scope = set(self._init_lexical_scope)\n        self._stop_blob = _stop_blob\n        self._stop_blob_required = _stop_blob_required\n        self._use_control_ops = _use_control_ops\n\n    def stop_blob(self):\n        \"\"\"\n        Returns the BlobReference to the stop_blob of this NetBuilder.\n        If one is not yet available, creates one.\n        This function assumes that the stop_blob() will be used immediatelly\n        in the current net, so it doesn't initialize it if the current net is\n        the first of the builder.\n        \"\"\"\n        assert not self._use_control_ops, \\\n            'Stop blobs are not used with control operators'\n        if self._stop_blob is None:\n            net = self.current_net()\n            self._stop_blob = core.BlobReference(\n                net.NextName('stop_blob'), net=net)\n            if self._current_net != self._children[0]:\n                self._children.insert(0, core.Net('stop_blob_init'))\n                self._children[0].Const(False, blob_out=self._stop_blob)\n        return self._stop_blob\n\n    def stop_if(self, blob):\n        assert not self._use_control_ops, \\\n            'Stop blobs are not used with control operators'\n        ops.Copy(blob, self.stop_blob())\n        self._current_net = None\n\n    def _assert_mutable(self):\n        assert not self._frozen, (\n            'This NetBuilder (%s) has been built already.' % self.name)\n\n    def _update_lexical_scope(self):\n        \"\"\"\n        Updates lexical scope based on the current list of children.\n        Lexical scope contains names of blobs that are currently available\n        and were introduced in the net builder\n        \"\"\"\n        self._lexical_scope = set(self._init_lexical_scope)\n        for child in self._children:\n            if isinstance(child, core.Net):\n                self._lexical_scope |= child.UsedBlobNames()\n            elif isinstance(child, NetBuilder) and child._use_control_ops:\n                self._lexical_scope |= child._lexical_scope\n\n    def _reset_children(self):\n        self._current_net = None\n        self._children = []\n        self._lexical_scope = set(self._init_lexical_scope)\n\n    def add(self, child):\n        self._assert_mutable()\n\n        if self._use_control_ops:\n            assert isinstance(child, core.Net) or (\n                isinstance(child, NetBuilder) and child._use_control_ops), \\\n                \"Expected Net or NetBuilder with control ops\"\n\n        self._current_net = None\n        self._children.append(child)\n        # to-do : check it's not a dag net\n        if isinstance(child, core.Net):\n            self._current_net = child\n        self._update_lexical_scope()\n        return child\n\n    def current_net(self, name=None):\n        self._assert_mutable()\n        if self._current_net is None or name is not None:\n            self.add(core.Net(name))\n        return self._current_net\n\n    def freeze(self):\n        for child in self._children:\n            if hasattr(child, 'freeze'):\n                child.freeze()\n        self._current_net = None\n        self._frozen = True\n\n    def get(self):\n        self.freeze()\n        return self._children\n\n    def __exit__(self, etype, *args):\n        if self._use_control_ops and len(self._children) > 0:\n            _children = self._children\n            self._reset_children()\n            merged_net = NetBuilder.merge_nets(\n                _children, self._lexical_scope)\n            assert merged_net, \"Expected a non-empty merge of children\"\n            self._children = [merged_net]\n\n        self.freeze()\n        if etype is not None:\n            return\n        assert (not self._stop_blob_required) or self._stop_blob is not None, (\n            'This NetBuilder (%s) requires a stop condition ' % self.name +\n            'to be set with `stop` or `stop_if`')\n\n    @staticmethod\n    def merge_nets(nets_or_builders, outer_blob_names):\n        # Only nets or builders with control ops are allowed.\n        # Need to pay attention to external outputs, e.g.\n        #   ...\n        #   IfNet1 (cond_blob):\n        #       (Net1)\n        #           X = 1\n        #       IfNet2 (...):\n        #           X = X + 1\n        #   ...\n        # In this example there're two children in then branch of IfNet1:\n        # a subnet Net1 that creates blob X and sets its value to one, and\n        # a net builder IfNet2 that (conditionally) increments X.\n        # From IfNet2's point of view X is an external input\n        # and output blob, it will be put into IfNet2 net's external_output.\n        # At the same time, from the point of view of IfNet1 X is purely local.\n        # Net.AppendNet just merges external outputs of the networks, so\n        # without checking this the result of Net1.AppendNet(IfNet2's net)\n        # would have blob X in external_output\n\n        net = None\n        for n in nets_or_builders:\n            cur = None\n            if isinstance(n, NetBuilder):\n                assert n._use_control_ops, \\\n                    \"Merging of NetBuilder supported only for control ops\"\n                nets = n.get()\n                assert len(nets) == 1 and isinstance(nets[0], core.Net), \\\n                    \"Invalid control op net builder\"\n                cur = nets[0]\n            else:\n                assert isinstance(n, core.Net)\n                cur = n\n            if net:\n                net.AppendNet(cur)\n            else:\n                net = cur\n        if net:\n            # correct external output\n            external_outputs = [o for o in net.Proto().external_output\n                                    if o in outer_blob_names]\n            net.Proto().external_output[:] = external_outputs\n        return net\n\n    def __str__(self):\n        return self.name or 'Un-named NetBuilder'\n\n\nclass Operations(object):\n    \"\"\"\n    Operations to be used in the context of a NetBuilder.\n    \"\"\"\n    def net(self, net=None, name=None):\n        \"\"\"\n        Retrieves the current net, or add a new net to the builder.\n        Args:\n            net:   If provided, add the given net to the active builder.\n                   Else, returns the current Net or creates a new one as needed.\n            name:  if provided, creates a new Net with given name and makes\n                   it the new current net of the active builder. Cannot\n                   be provided if net is provided.\n        \"\"\"\n        assert name is None or net is None, (\n            'Cannot provide both `net` and `name`.')\n        if net is not None:\n            NetBuilder.current().add(net)\n            return net\n        return NetBuilder.current().current_net(name=name)\n\n    def __getattr__(self, op_type):\n        \"\"\"\n        Adds an operator call to the currently active Net.\n        \"\"\"\n        if op_type.startswith('__'):\n            raise AttributeError()\n        # We want hasattr to work properly even if no context is active.\n        if NetBuilder.current(required=False) is None:\n            raise AttributeError('No active NetBuilder.')\n        return getattr(self.net(), op_type)\n\n    def task_group(self):\n        \"\"\"\n        Creates a local task group which will execute as the next step of\n        the current NetBuilder.\n        \"\"\"\n        from caffe2.python import task\n        group = NetBuilder.current()\n        with task.Cluster():\n            with task.Node('local'):\n                tg = task.TaskGroup()\n                group.add(tg)\n                return tg\n\n    def stop(self):\n        \"\"\"\n        Stop execution of the current execution step.\n            Example:\n                ops.Print(a, 0)\n                ops.stop()\n                ops.Print(b, 0)\n            In the example, 'b' will never be printed.\n        \"\"\"\n        return self.stop_if(ops.Const(True))\n\n    def stop_if(self, blob):\n        \"\"\"\n        Stop execution of the current execution step if the\n        condition `blob` is met.\n            Example:\n                ops.Print(a, 0)\n                ops.stop_if(ops.LE([x, ops.Const(0)]))\n                ops.Print(b, 0)\n            In the example, 'b' will only be printed if the value of scalar\n            tensor 'x' lower or equal to 0.\n        \"\"\"\n        return NetBuilder.current().stop_if(blob)\n\n    def loop(self, iters=None, name=None):\n        \"\"\"\n        Creates a NetBuilder that will execute in a loop as the next step of\n        the current NetBuilder. If `iters` is provided, the loop will execute\n        for `iters` iterations and then stop. `iters` can be a constant or a\n        BlobReference. If `iters` is not provided, the loop will execute\n        until `ops.stop` or `ops.stop_if` is called.\n            Examples:\n                a = ops.Const(5)\n                with ops.loop():\n                    ops.stop_if(ops.LE([a, ops.Const(0)]))\n                    ops.Print(a, 0)\n                    ops.Add([a, ops.Const(-1)], [a])\n            Above, 'a' will be printed 5 times, with values 5 to 1.\n\n                with ops.loop(10) as loop:\n                    ops.LogInfo(loop.iter())\n            This will print the numbers from 0 to 9.\n\n                x = ops.Add([ops.Const(10), ops.Const(10)])\n                with ops.loop(x) as loop:\n                    ops.LogInfo(loop.iter())\n            This will print the numbers from 0 to 19.\n        \"\"\"\n        return NetBuilder.current().add(_Loop(iters, name=name))\n\n    def stop_guard(self, has_stopped_blob=None, name=None):\n        \"\"\"\n        Creates a NetBuilder that will execute once as the next step of the\n        current NetBuilder. After execution, a bool tensor will indicate\n        whether the inner execution was halted with `stop` or `stop_if`.\n            Example:\n                a = ops.Const(True)\n                with ops.stop_guard() as sg1:\n                    ops.stop_if(a)\n                    ops.Print(ops.Const('did not stop'))\n                b = ops.Const(False)\n                with ops.stop_guard() as sg2:\n                    ops.stop_if(b)\n                    ops.Print(ops.Const('did not stop'))\n                ops.Print(sg1.has_stopped(), [])\n                ops.Print(sg2.has_stopped(), [])\n            In the example, 'did not stop' will be printed once,\n            followed by True and False.\n        \"\"\"\n        return NetBuilder.current().add(\n            _StopGuard(has_stopped_blob=has_stopped_blob, name=name))\n\n    def If(self, cond, name=None):\n        \"\"\"\n        Creates a NetBuilder that will execute once as the next step of the\n        current NetBuilder if the blob `cond` is True.\n            Example:\n                with ops.If(ops.Const(True)):\n                    ops.Print(ops.Const('Will print'))\n                with ops.If(ops.Const(False)):\n                    ops.Print(ops.Const('Wont print'))\n            The example will print 'Will print' once.\n        \"\"\"\n        return NetBuilder.current().add(_RunIf(cond, name=name))\n\n    def IfNet(self, cond, name=None):\n        \"\"\"\n        Same as If, but uses 'If' operator instead of execution step logic\n        \"\"\"\n        return NetBuilder.current().add(_RunIfNet(cond, name=name))\n\n    def Else(self, name=None):\n        \"\"\"\n        Else branch of IfNet, has to be specified immediately after IfNet.\n            Example:\n                with ops.IfNet(ops.LT([x, y])):\n                    ...\n                with ops.Else():\n                    ...\n        \"\"\"\n        return _RunElseNet(name=name)\n\n    def WhileNet(self, name=None):\n        \"\"\"\n        NetBuilder for 'While' control operator\n        \"\"\"\n        return NetBuilder.current().add(_RunWhileNet(name=name))\n\n    def Condition(self, name=None):\n        \"\"\"\n        Loop's condition, executed within WhileNet context\n        \"\"\"\n        assert isinstance(NetBuilder.current(), _RunWhileNet), \\\n            \"Use of Condition outside of WhileNet\"\n        return _RunWhileCondition(name=name)\n\n    def task_init(self):\n        \"\"\"\n        Defines operations that will be executed once at task startup.\n        Useful when implementing processors, that don't have access to the Task\n        top-level structure.\n\n        This setup will be run only once, even if multiple instances of the task\n        will run in parallel. For instance-local initialization, use\n        `task_instance_init` instead.\n\n            Example:\n                def my_processor(rec):\n                    with ops.task_init():\n                        one = ops.Const(1)\n                        two = ops.Const(1)\n                    return Tuple(\n                        ops.Add(rec[0](), zero), ops.Add(rec[1](), two))\n        \"\"\"\n        setup = _SetupBuilder(_SetupBuilder.INIT)\n        self.net().add_attribute(Task.TASK_SETUP, setup)\n        return setup\n\n    def task_exit(self):\n        \"\"\"\n        Define operations to be executed once at task shutdown.\n        Useful when implementing processors, that don't have access to the Task\n        top-level structure.\n\n        This shutdown will be run only once, after all concurrent instances of\n        the task have already finished. For instance-local shutdown,\n        use `task_instance_exit` instead.\n\n            Example:\n                def read_queue(queue):\n                    with ops.task_exit():\n                        queue.close(ops.net())\n                    return queue.read(ops.net())\n        \"\"\"\n        setup = _SetupBuilder(_SetupBuilder.EXIT)\n        self.net().add_attribute(Task.TASK_SETUP, setup)\n        return setup\n\n    def task_instance_init(self):\n        \"\"\"\n        Defines operations that will be executed once at startup of each\n        instance of a task. This can be seen as \"thread_local\" initialization.\n        It is guaranteed to run only after all `task_init` logic finishes.\n\n        This setup will be run concurrently for each instance of a task.\n        For global task initialization, use `task_init` instead.\n        \"\"\"\n        setup = _SetupBuilder(_SetupBuilder.INIT)\n        self.net().add_attribute(Task.TASK_INSTANCE_SETUP, setup)\n        return setup\n\n    def task_instance_exit(self):\n        \"\"\"\n        Defines operations that will be executed once at shutdown of each\n        instance of a task. This can be seen as \"thread_local\" finalization.\n\n        This shutdown will be run concurrently for each instance of a task.\n        For global task shutdown, use `task_exit` instead.\n        \"\"\"\n        setup = _SetupBuilder(_SetupBuilder.EXIT)\n        self.net().add_attribute(Task.TASK_INSTANCE_SETUP, setup)\n        return setup\n\n    def local_init(self):\n        \"\"\"\n        Similar to `task_init`, but executes at TaskGroup's startup instead,\n        before any task of the group starts executing. This will run only\n        once on each node, before initialization of any task, so it can be\n        used e.g. to initialize blobs shared across tasks.\n        \"\"\"\n        setup = _SetupBuilder(_SetupBuilder.INIT)\n        self.net().add_attribute(TaskGroup.LOCAL_SETUP, setup)\n        return setup\n\n    def local_exit(self, name=None):\n        \"\"\"\n        Similar to `task_exit`, but executes at TaskGroup's exit instead,\n        after all tasks of the group finished execution.\n        This will run only once on each node.\n        \"\"\"\n        setup = _SetupBuilder(_SetupBuilder.EXIT, name)\n        self.net().add_attribute(TaskGroup.LOCAL_SETUP, setup)\n        return setup\n\n    def task_reporter(self, interval_ms=1000, name=None):\n        \"\"\"\n        Define operations to be executed at every time interval from\n        task start-up to finish. These operations are guaranteed to\n        execute at least once after all other operations of the task are\n        finished.\n\n            Example:\n                with ops.task_reporter(interval_ms=10000):\n                    ops.LogInfo('10s elapsed')\n        \"\"\"\n        return _ReporterBuilder(interval_ms, net=self.net(), name=name)\n\n    def local_reporter(self, interval_ms=1000, name=None):\n        \"\"\"\n        Similar to task_report, but operations defined within this block\n        will run repeatedly for as long as any of the tasks in the current\n        TaskGroup have not finished.\n        \"\"\"\n        return _ReporterBuilder(interval_ms, name=name)\n\n\nops = Operations()\n\n\nclass _ReporterBuilder(NetBuilder):\n    def __init__(self, interval_ms, net=None, name=None):\n        NetBuilder.__init__(self, name)\n        self._net = net\n        self.interval_ms = interval_ms\n\n    def __exit__(self, etype, *args):\n        if etype is None:\n            step = core.to_execution_step(self)\n            step.RunEveryMillis(self.interval_ms)\n            if self._net:\n                self._net.add_attribute(Task.REPORT_STEP, step)\n            else:\n                TaskGroup.current().report_step(\n                    step, interval_ms=self.interval_ms)\n        NetBuilder.__exit__(self, etype, *args)\n\n\nclass _SetupBuilder(NetBuilder):\n    INIT = 'init'\n    EXIT = 'exit'\n\n    def __init__(self, type, name=None):\n        NetBuilder.__init__(self, name)\n        self.type = type\n\n    def setup(self, net):\n        if self.type == _SetupBuilder.INIT:\n            return core.to_execution_step(self)\n\n    def exit(self, net):\n        if self.type == _SetupBuilder.EXIT:\n            return core.to_execution_step(self)\n\n\nclass _RunOnce(NetBuilder):\n    def __init__(self, name=None):\n        NetBuilder.__init__(self, name)\n\n    def __exit__(self, etype, *args):\n        if etype is None and self._stop_blob is not None:\n            ops.stop()\n        NetBuilder.__exit__(self, etype, *args)\n\n\nclass _StopGuard(_RunOnce):\n    def __init__(self, has_stopped_blob=None, name=None):\n        _RunOnce.__init__(self, name)\n        self._stopped = has_stopped_blob\n        self._ran = False\n\n    def __enter__(self):\n        r = _RunOnce.__enter__(self)\n        self._stopped = ops.Const(True, blob_out=self._stopped)\n        return r\n\n    def __exit__(self, etype, *args):\n        if etype is None:\n            self._ran = True\n            ops.Const(False, blob_out=self._stopped)\n        _RunOnce.__exit__(self, etype, *args)\n\n    def has_stopped(self):\n        \"\"\"\n        Return a blob that will be set to scalar bool `True` after\n        this net builder ran, iff it was halted early.\n        \"\"\"\n        assert self._ran, 'Context not used yet.'\n        return self._stopped\n\n\nclass _Loop(NetBuilder):\n    def __init__(self, iters=None, name=None):\n        NetBuilder.__init__(self, name, _stop_blob_required=True)\n        if iters is not None:\n            self._inc = ops.Const(1)\n            self._iter = ops.Const(0)\n            self._num_iters = (\n                iters if isinstance(iters, core.BlobReference)\n                else ops.Const(iters))\n        else:\n            self._num_iters = None\n\n    def iter(self):\n        assert self._num_iters is not None, (\n            'This loop does not have a number of iterations.')\n        assert self._iter is not None, (\n            'iter() must be called from inside the loop context')\n        return self._iter\n\n    def __enter__(self):\n        builder = NetBuilder.__enter__(self)\n        if self._num_iters is not None:\n            ops.stop_if(ops.GE([self._iter, self._num_iters]))\n        return builder\n\n    def __exit__(self, type, *args):\n        if type is None and self._num_iters is not None:\n            self.current_net().Add([self._iter, self._inc], [self._iter])\n        NetBuilder.__exit__(self, type, *args)\n\n\nclass _RunIf(_RunOnce):\n    def __init__(self, cond_blob=None, name=None, _already_ran=None):\n        _RunOnce.__init__(self, name)\n        assert cond_blob or _already_ran\n        self._is_else = cond_blob is None\n        if _already_ran is None:\n            self._else_blob = ops.Not(cond_blob)\n            self._already_ran = ops.Const(False)\n        else:\n            self._already_ran = _already_ran\n            self._else_blob = _already_ran if cond_blob is None else (\n                ops.Or([_already_ran, ops.Not(cond_blob)]))\n\n    def __enter__(self):\n        r = _RunOnce.__enter__(self)\n        ops.stop_if(self._else_blob)\n        ops.Const(True, blob_out=self._already_ran)\n        return r\n\n    def Elif(self, cond, name=None):\n        assert not self._is_else, 'Else not allowed for an Else.'\n        return NetBuilder.current().add(_RunIf(\n            cond, name=name or self.name, _already_ran=self._already_ran))\n\n    def Else(self, name=None):\n        assert not self._is_else, 'Elif not allowed for an Else.'\n        return NetBuilder.current().add(\n            _RunIf(name=name or self.name, _already_ran=self._already_ran))\n\n\nclass _RunIfNet(NetBuilder):\n    \"\"\"\n    Generates a single net that uses If operator\n    \"\"\"\n    def __init__(self, cond_blob, name=None):\n        NetBuilder.__init__(self, name=name, _use_control_ops=True)\n        assert cond_blob, 'Conditional blob is not specified for an If net'\n        self._cond_blob = cond_blob\n        self._then_net = None\n        self._else_net = None\n\n    def add(self, child):\n        return NetBuilder.add(self, child)\n\n    def __exit__(self, type, *args):\n        if type is None:\n            _then_nets = self._children\n            self._reset_children()\n\n            self._then_net = NetBuilder.merge_nets(\n                _then_nets, self._lexical_scope)\n            if not self._then_net:\n                self._then_net = core.Net('empty_then_net')\n\n            if_net = core.Net(self.name + '/if_net')\n            add_if_op(if_net, self._cond_blob, self._lexical_scope,\n                        self._then_net, self._else_net)\n\n            self._current_net = if_net\n            self._children = [if_net]\n        NetBuilder.__exit__(self, type, *args)\n\n\nclass _RunElseNet(NetBuilder):\n    \"\"\"\n    Else branch for _RunIfNet builder\n    \"\"\"\n    def __init__(self, name=None):\n        NetBuilder.__init__(self, name=name, _use_control_ops=True)\n        parent = NetBuilder.current(required=False)\n        assert parent and len(parent._children) > 0 and \\\n            isinstance(parent._children[-1], _RunIfNet), \\\n            'Invalid use of Else builder'\n        self._if_builder = parent._children[-1]\n\n    def __exit__(self, type, *args):\n        if type is None:\n            _else_nets = self._children\n            self._reset_children()\n\n            self._if_builder._else_net = NetBuilder.merge_nets(\n                _else_nets, self._lexical_scope)\n            if self._if_builder._else_net:\n                if_else_net = core.Net(self.name + '/if_else_net')\n                add_if_op(\n                    if_else_net,\n                    self._if_builder._cond_blob,\n                    self._lexical_scope,\n                    self._if_builder._then_net,\n                    self._if_builder._else_net)\n                self._if_builder._current_net = if_else_net\n                self._if_builder._children = [if_else_net]\n        NetBuilder.__exit__(self, type, *args)\n\n\nclass _RunWhileNet(NetBuilder):\n    \"\"\"\n    Generates a single net that uses While operator\n    \"\"\"\n    def __init__(self, name=None):\n        NetBuilder.__init__(self, name=name, _use_control_ops=True)\n        self._cond_builder = None\n\n    def __exit__(self, type, *args):\n        if type is None:\n            assert self._cond_builder, \\\n                'Condition builder must be specified in While op'\n\n            _cond_blob = self._cond_builder._cond_blob\n            _cond_net = self._cond_builder._cond_net\n\n            loop_body = self._children\n            self._reset_children()\n            loop_body_net = NetBuilder.merge_nets(\n                loop_body, self._lexical_scope)\n            if not loop_body_net:\n                loop_body_net = core.Net('empty_loop_body_net')\n\n            while_net = core.Net(self.name + '/while_net')\n            add_while_op(while_net, _cond_blob, self._lexical_scope,\n                            loop_body_net, _cond_net)\n\n            self._current_net = while_net\n            self._children = [while_net]\n        NetBuilder.__exit__(self, type, *args)\n\n\nclass _RunWhileCondition(NetBuilder):\n    \"\"\"\n    Computes loop's condition, used in the context of WhileNet.\n    Last operator must have a single scalar boolean output that will be used\n    as a condition value, no other blobs created in the condition net are\n    visible outside of it\n    \"\"\"\n    def __init__(self, name=None):\n        NetBuilder.__init__(self, name=name, _use_control_ops=True)\n        parent = NetBuilder.current(required=False)\n        assert parent and isinstance(parent, _RunWhileNet), \\\n            'Invalid use of loop condition builder'\n        assert not parent._cond_builder, \\\n            'Multiple loop condition builders specified'\n        assert len(parent._children) == 0, \\\n            'Condition definition must be specified before the loop\\'s body'\n        parent._cond_builder = self\n        self._cond_blob = None\n        self._cond_net = None\n\n    def __exit__(self, type, *args):\n        if type is None:\n            condition_body = self._children\n            self._reset_children()\n            self._cond_net = NetBuilder.merge_nets(\n                condition_body, self._lexical_scope)\n            assert self._cond_net, 'Invalid loop condition specified'\n            assert len(self._cond_net.Proto().op) > 0, 'Invalid condition net'\n            last_op = self._cond_net.Proto().op[-1]\n            assert len(last_op.output) == 1, 'Invalid condition net'\n            self._cond_blob = core.BlobReference(name=last_op.output[0], net=None)\n\n            self._current_net = self._cond_net\n            self._children = [self._cond_net]\n        NetBuilder.__exit__(self, type, *args)\n"
  },
  {
    "path": "caffe2/python/net_builder_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import workspace\nfrom caffe2.python.core import Plan, to_execution_step, Net\nfrom caffe2.python.task import Task, TaskGroup, final_output\nfrom caffe2.python.net_builder import ops, NetBuilder\nfrom caffe2.python.session import LocalSession\nimport unittest\nimport threading\n\n\nclass PythonOpStats(object):\n    lock = threading.Lock()\n    num_instances = 0\n    num_calls = 0\n\n\ndef python_op_builder():\n    PythonOpStats.lock.acquire()\n    PythonOpStats.num_instances += 1\n    PythonOpStats.lock.release()\n\n    def my_op(inputs, outputs):\n        PythonOpStats.lock.acquire()\n        PythonOpStats.num_calls += 1\n        PythonOpStats.lock.release()\n\n    return my_op\n\n\ndef _test_loop():\n    x = ops.Const(5)\n    y = ops.Const(0)\n    with ops.loop():\n        ops.stop_if(ops.EQ([x, ops.Const(0)]))\n        ops.Add([x, ops.Const(-1)], [x])\n        ops.Add([y, ops.Const(1)], [y])\n    return y\n\n\ndef _test_inner_stop(x):\n    ops.stop_if(ops.LT([x, ops.Const(5)]))\n\n\ndef _test_outer():\n    x = ops.Const(10)\n    # test stop_if(False)\n    with ops.stop_guard() as g1:\n        _test_inner_stop(x)\n\n    # test stop_if(True)\n    y = ops.Const(3)\n    with ops.stop_guard() as g2:\n        _test_inner_stop(y)\n\n    # test no stop\n    with ops.stop_guard() as g4:\n        ops.Const(0)\n\n    # test empty clause\n    with ops.stop_guard() as g3:\n        pass\n\n    return (\n        g1.has_stopped(), g2.has_stopped(), g3.has_stopped(), g4.has_stopped())\n\n\ndef _test_if(x):\n    y = ops.Const(1)\n    with ops.If(ops.GT([x, ops.Const(50)])):\n        ops.Const(2, blob_out=y)\n    with ops.If(ops.LT([x, ops.Const(50)])):\n        ops.Const(3, blob_out=y)\n        ops.stop()\n        ops.Const(4, blob_out=y)\n    return y\n\n\nclass TestNetBuilder(unittest.TestCase):\n    def test_ops(self):\n        with NetBuilder() as nb:\n            y = _test_loop()\n            z, w, a, b = _test_outer()\n            p = _test_if(ops.Const(75))\n            q = _test_if(ops.Const(25))\n        plan = Plan('name')\n        plan.AddStep(to_execution_step(nb))\n        ws = workspace.C.Workspace()\n        ws.run(plan)\n        expected = [\n            (y, 5),\n            (z, False),\n            (w, True),\n            (a, False),\n            (b, False),\n            (p, 2),\n            (q, 3),\n        ]\n        for b, expected in expected:\n            actual = ws.blobs[str(b)].fetch()\n            self.assertEquals(actual, expected)\n\n    def _expected_loop(self):\n        total = 0\n        total_large = 0\n        total_small = 0\n        total_tiny = 0\n        for loop_iter in range(10):\n            outer = loop_iter * 10\n            for inner_iter in range(loop_iter):\n                val = outer + inner_iter\n                if val >= 80:\n                    total_large += val\n                elif val >= 50:\n                    total_small += val\n                else:\n                    total_tiny += val\n                total += val\n        return total, total_large, total_small, total_tiny\n\n    def _actual_loop(self):\n        total = ops.Const(0)\n        total_large = ops.Const(0)\n        total_small = ops.Const(0)\n        total_tiny = ops.Const(0)\n        with ops.loop(10) as loop:\n            outer = ops.Mul([loop.iter(), ops.Const(10)])\n            with ops.loop(loop.iter()) as inner:\n                val = ops.Add([outer, inner.iter()])\n                with ops.If(ops.GE([val, ops.Const(80)])) as c:\n                    ops.Add([total_large, val], [total_large])\n                with c.Elif(ops.GE([val, ops.Const(50)])) as c:\n                    ops.Add([total_small, val], [total_small])\n                with c.Else():\n                    ops.Add([total_tiny, val], [total_tiny])\n                ops.Add([total, val], total)\n        return [\n            final_output(x)\n            for x in [total, total_large, total_small, total_tiny]\n        ]\n\n    def test_net_multi_use(self):\n        with Task() as task:\n            total = ops.Const(0)\n            net = Net('my_net')\n            net.Add([total, net.Const(1)], [total])\n            ops.net(net)\n            ops.net(net)\n            result = final_output(total)\n        with LocalSession() as session:\n            session.run(task)\n            self.assertEquals(2, result.fetch())\n\n    def test_loops(self):\n        with Task() as task:\n            out_actual = self._actual_loop()\n        with LocalSession() as session:\n            session.run(task)\n            expected = self._expected_loop()\n            actual = [o.fetch() for o in out_actual]\n            for e, a in zip(expected, actual):\n                self.assertEquals(e, a)\n\n    def test_setup(self):\n        with Task() as task:\n            with ops.task_init():\n                one = ops.Const(1)\n            two = ops.Add([one, one])\n            with ops.task_init():\n                three = ops.Const(3)\n            accum = ops.Add([two, three])\n            # here, accum should be 5\n            with ops.task_exit():\n                # here, accum should be 6, since this executes after lines below\n                seven_1 = ops.Add([accum, one])\n            six = ops.Add([accum, one])\n            ops.Add([accum, one], [accum])\n            seven_2 = ops.Add([accum, one])\n            o6 = final_output(six)\n            o7_1 = final_output(seven_1)\n            o7_2 = final_output(seven_2)\n        with LocalSession() as session:\n            session.run(task)\n            self.assertEquals(o6.fetch(), 6)\n            self.assertEquals(o7_1.fetch(), 7)\n            self.assertEquals(o7_2.fetch(), 7)\n\n    def test_multi_instance_python_op(self):\n        \"\"\"\n        When task instances are created at runtime, C++ concurrently creates\n        multiple instances of operators in C++, and concurrently destroys them\n        once the task is finished. This means that the destructor of PythonOp\n        will be called concurrently, so the GIL must be acquired. This\n        test exercises this condition.\n        \"\"\"\n        with Task(num_instances=64) as task:\n            with ops.loop(4):\n                ops.Python((python_op_builder, [], {}))([], [])\n        with LocalSession() as session:\n            PythonOpStats.num_instances = 0\n            PythonOpStats.num_calls = 0\n            session.run(task)\n            self.assertEquals(PythonOpStats.num_instances, 64)\n            self.assertEquals(PythonOpStats.num_calls, 256)\n\n    def test_multi_instance(self):\n        NUM_INSTANCES = 10\n        NUM_ITERS = 15\n        with TaskGroup() as tg:\n            with Task(num_instances=NUM_INSTANCES):\n                with ops.task_init():\n                    counter1 = ops.CreateCounter([], ['global_counter'])\n                    counter2 = ops.CreateCounter([], ['global_counter2'])\n                    counter3 = ops.CreateCounter([], ['global_counter3'])\n                # both task_counter and local_counter should be thread local\n                with ops.task_instance_init():\n                    task_counter = ops.CreateCounter([], ['task_counter'])\n                local_counter = ops.CreateCounter([], ['local_counter'])\n                with ops.loop(NUM_ITERS):\n                    ops.CountUp(counter1)\n                    ops.CountUp(task_counter)\n                    ops.CountUp(local_counter)\n                # gather sum of squares of local counters to make sure that\n                # each local counter counted exactly up to NUM_ITERS, and\n                # that there was no false sharing of counter instances.\n                with ops.task_instance_exit():\n                    count2 = ops.RetrieveCount(task_counter)\n                    with ops.loop(ops.Mul([count2, count2])):\n                        ops.CountUp(counter2)\n                # This should have the same effect as the above\n                count3 = ops.RetrieveCount(local_counter)\n                with ops.loop(ops.Mul([count3, count3])):\n                    ops.CountUp(counter3)\n                # The code below will only run once\n                with ops.task_exit():\n                    total1 = final_output(ops.RetrieveCount(counter1))\n                    total2 = final_output(ops.RetrieveCount(counter2))\n                    total3 = final_output(ops.RetrieveCount(counter3))\n\n        with LocalSession() as session:\n            session.run(tg)\n            self.assertEquals(total1.fetch(), NUM_INSTANCES * NUM_ITERS)\n            self.assertEquals(total2.fetch(), NUM_INSTANCES * (NUM_ITERS ** 2))\n            self.assertEquals(total3.fetch(), NUM_INSTANCES * (NUM_ITERS ** 2))\n\n    def test_if_net(self):\n        with NetBuilder() as nb:\n            x0 = ops.Const(0)\n            x1 = ops.Const(1)\n            x2 = ops.Const(2)\n            y0 = ops.Const(0)\n            y1 = ops.Const(1)\n            y2 = ops.Const(2)\n\n            # basic logic\n            first_res = ops.Const(0)\n            with ops.IfNet(ops.Const(True)):\n                ops.Const(1, blob_out=first_res)\n            with ops.Else():\n                ops.Const(2, blob_out=first_res)\n\n            second_res = ops.Const(0)\n            with ops.IfNet(ops.Const(False)):\n                ops.Const(1, blob_out=second_res)\n            with ops.Else():\n                ops.Const(2, blob_out=second_res)\n\n            # nested and sequential ifs,\n            # empty then/else,\n            # passing outer blobs into branches,\n            # writing into outer blobs, incl. into input blob\n            # using local blobs\n            with ops.IfNet(ops.LT([x0, x1])):\n                local_blob = ops.Const(900)\n                ops.Add([ops.Const(100), local_blob], [y0])\n\n                gt = ops.GT([x1, x2])\n                with ops.IfNet(gt):\n                    # empty then\n                    pass\n                with ops.Else():\n                    ops.Add([y1, local_blob], [local_blob])\n                    ops.Add([ops.Const(100), y1], [y1])\n\n                with ops.IfNet(ops.EQ([local_blob, ops.Const(901)])):\n                    ops.Const(7, blob_out=y2)\n                    ops.Add([y1, y2], [y2])\n            with ops.Else():\n                # empty else\n                pass\n\n        plan = Plan('if_net_test')\n        plan.AddStep(to_execution_step(nb))\n        ws = workspace.C.Workspace()\n        ws.run(plan)\n\n        first_res_value = ws.blobs[str(first_res)].fetch()\n        second_res_value = ws.blobs[str(second_res)].fetch()\n        y0_value = ws.blobs[str(y0)].fetch()\n        y1_value = ws.blobs[str(y1)].fetch()\n        y2_value = ws.blobs[str(y2)].fetch()\n\n        self.assertEquals(first_res_value, 1)\n        self.assertEquals(second_res_value, 2)\n        self.assertEquals(y0_value, 1000)\n        self.assertEquals(y1_value, 101)\n        self.assertEquals(y2_value, 108)\n        self.assertTrue(str(local_blob) not in ws.blobs)\n\n    def test_while_net(self):\n        with NetBuilder() as nb:\n            x = ops.Const(0)\n            y = ops.Const(0)\n            with ops.WhileNet():\n                with ops.Condition():\n                    ops.Add([x, ops.Const(1)], [x])\n                    ops.LT([x, ops.Const(7)])\n                ops.Add([x, y], [y])\n\n        plan = Plan('while_net_test')\n        plan.AddStep(to_execution_step(nb))\n        ws = workspace.C.Workspace()\n        ws.run(plan)\n\n        x_value = ws.blobs[str(x)].fetch()\n        y_value = ws.blobs[str(y)].fetch()\n\n        self.assertEqual(x_value, 7)\n        self.assertEqual(y_value, 21)\n"
  },
  {
    "path": "caffe2/python/net_drawer.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package net_drawer\n# Module caffe2.python.net_drawer\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport argparse\nimport json\nimport logging\nfrom collections import defaultdict\nfrom caffe2.python import utils\nfrom future.utils import viewitems\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO)\n\ntry:\n    import pydot\nexcept ImportError:\n    logger.info(\n        'Cannot import pydot, which is required for drawing a network. This '\n        'can usually be installed in python with \"pip install pydot\". Also, '\n        'pydot requires graphviz to convert dot files to pdf: in ubuntu, this '\n        'can usually be installed with \"sudo apt-get install graphviz\".'\n    )\n    print(\n        'net_drawer will not run correctly. Please install the correct '\n        'dependencies.'\n    )\n    pydot = None\n\nfrom caffe2.proto import caffe2_pb2\n\nOP_STYLE = {\n    'shape': 'box',\n    'color': '#0F9D58',\n    'style': 'filled',\n    'fontcolor': '#FFFFFF'\n}\nBLOB_STYLE = {'shape': 'octagon'}\n\n\ndef _rectify_operator_and_name(operators_or_net, name):\n    \"\"\"Gets the operators and name for the pydot graph.\"\"\"\n    if isinstance(operators_or_net, caffe2_pb2.NetDef):\n        operators = operators_or_net.op\n        if name is None:\n            name = operators_or_net.name\n    elif hasattr(operators_or_net, 'Proto'):\n        net = operators_or_net.Proto()\n        if not isinstance(net, caffe2_pb2.NetDef):\n            raise RuntimeError(\n                \"Expecting NetDef, but got {}\".format(type(net)))\n        operators = net.op\n        if name is None:\n            name = net.name\n    else:\n        operators = operators_or_net\n        if name is None:\n            name = \"unnamed\"\n    return operators, name\n\n\ndef _escape_label(name):\n    # json.dumps is poor man's escaping\n    return json.dumps(name)\n\n\ndef GetOpNodeProducer(append_output, **kwargs):\n    def ReallyGetOpNode(op, op_id):\n        if op.name:\n            node_name = '%s/%s (op#%d)' % (op.name, op.type, op_id)\n        else:\n            node_name = '%s (op#%d)' % (op.type, op_id)\n        if append_output:\n            for output_name in op.output:\n                node_name += '\\n' + output_name\n        return pydot.Node(node_name, **kwargs)\n    return ReallyGetOpNode\n\n\ndef GetPydotGraph(\n    operators_or_net,\n    name=None,\n    rankdir='LR',\n    node_producer=None\n):\n    if node_producer is None:\n        node_producer = GetOpNodeProducer(False, **OP_STYLE)\n    operators, name = _rectify_operator_and_name(operators_or_net, name)\n    graph = pydot.Dot(name, rankdir=rankdir)\n    pydot_nodes = {}\n    pydot_node_counts = defaultdict(int)\n    for op_id, op in enumerate(operators):\n        op_node = node_producer(op, op_id)\n        graph.add_node(op_node)\n        # print 'Op: %s' % op.name\n        # print 'inputs: %s' % str(op.input)\n        # print 'outputs: %s' % str(op.output)\n        for input_name in op.input:\n            if input_name not in pydot_nodes:\n                input_node = pydot.Node(\n                    _escape_label(\n                        input_name + str(pydot_node_counts[input_name])),\n                    label=_escape_label(input_name),\n                    **BLOB_STYLE\n                )\n                pydot_nodes[input_name] = input_node\n            else:\n                input_node = pydot_nodes[input_name]\n            graph.add_node(input_node)\n            graph.add_edge(pydot.Edge(input_node, op_node))\n        for output_name in op.output:\n            if output_name in pydot_nodes:\n                # we are overwriting an existing blob. need to updat the count.\n                pydot_node_counts[output_name] += 1\n            output_node = pydot.Node(\n                _escape_label(\n                    output_name + str(pydot_node_counts[output_name])),\n                label=_escape_label(output_name),\n                **BLOB_STYLE\n            )\n            pydot_nodes[output_name] = output_node\n            graph.add_node(output_node)\n            graph.add_edge(pydot.Edge(op_node, output_node))\n    return graph\n\n\ndef GetPydotGraphMinimal(\n    operators_or_net,\n    name=None,\n    rankdir='LR',\n    minimal_dependency=False,\n    node_producer=None,\n):\n    \"\"\"Different from GetPydotGraph, hide all blob nodes and only show op nodes.\n\n    If minimal_dependency is set as well, for each op, we will only draw the\n    edges to the minimal necessary ancestors. For example, if op c depends on\n    op a and b, and op b depends on a, then only the edge b->c will be drawn\n    because a->c will be implied.\n    \"\"\"\n    if node_producer is None:\n        node_producer = GetOpNodeProducer(False, **OP_STYLE)\n    operators, name = _rectify_operator_and_name(operators_or_net, name)\n    graph = pydot.Dot(name, rankdir=rankdir)\n    # blob_parents maps each blob name to its generating op.\n    blob_parents = {}\n    # op_ancestry records the ancestors of each op.\n    op_ancestry = defaultdict(set)\n    for op_id, op in enumerate(operators):\n        op_node = node_producer(op, op_id)\n        graph.add_node(op_node)\n        # Get parents, and set up op ancestry.\n        parents = [\n            blob_parents[input_name] for input_name in op.input\n            if input_name in blob_parents\n        ]\n        op_ancestry[op_node].update(parents)\n        for node in parents:\n            op_ancestry[op_node].update(op_ancestry[node])\n        if minimal_dependency:\n            # only add nodes that do not have transitive ancestry\n            for node in parents:\n                if all(\n                    [node not in op_ancestry[other_node]\n                     for other_node in parents]\n                ):\n                    graph.add_edge(pydot.Edge(node, op_node))\n        else:\n            # Add all parents to the graph.\n            for node in parents:\n                graph.add_edge(pydot.Edge(node, op_node))\n        # Update blob_parents to reflect that this op created the blobs.\n        for output_name in op.output:\n            blob_parents[output_name] = op_node\n    return graph\n\n\ndef GetOperatorMapForPlan(plan_def):\n    operator_map = {}\n    for net_id, net in enumerate(plan_def.network):\n        if net.HasField('name'):\n            operator_map[plan_def.name + \"_\" + net.name] = net.op\n        else:\n            operator_map[plan_def.name + \"_network_%d\" % net_id] = net.op\n    return operator_map\n\n\ndef _draw_nets(nets, g):\n    nodes = []\n    for i, net in enumerate(nets):\n        nodes.append(pydot.Node(_escape_label(net)))\n        g.add_node(nodes[-1])\n        if i > 0:\n            g.add_edge(pydot.Edge(nodes[-2], nodes[-1]))\n    return nodes\n\n\ndef _draw_steps(steps, g, skip_step_edges=False):  # noqa\n    kMaxParallelSteps = 3\n\n    def get_label():\n        label = [step.name + '\\n']\n        if step.report_net:\n            label.append('Reporter: {}'.format(step.report_net))\n        if step.should_stop_blob:\n            label.append('Stopper: {}'.format(step.should_stop_blob))\n        if step.concurrent_substeps:\n            label.append('Concurrent')\n        if step.only_once:\n            label.append('Once')\n        return '\\n'.join(label)\n\n    def substep_edge(start, end):\n        return pydot.Edge(start, end, arrowhead='dot', style='dashed')\n\n    nodes = []\n    for i, step in enumerate(steps):\n        parallel = step.concurrent_substeps\n\n        nodes.append(pydot.Node(_escape_label(get_label()), **OP_STYLE))\n        g.add_node(nodes[-1])\n\n        if i > 0 and not skip_step_edges:\n            g.add_edge(pydot.Edge(nodes[-2], nodes[-1]))\n\n        if step.network:\n            sub_nodes = _draw_nets(step.network, g)\n        elif step.substep:\n            if parallel:\n                sub_nodes = _draw_steps(\n                    step.substep[:kMaxParallelSteps], g, skip_step_edges=True)\n            else:\n                sub_nodes = _draw_steps(step.substep, g)\n        else:\n            raise ValueError('invalid step')\n\n        if parallel:\n            for sn in sub_nodes:\n                g.add_edge(substep_edge(nodes[-1], sn))\n            if len(step.substep) > kMaxParallelSteps:\n                ellipsis = pydot.Node('{} more steps'.format(\n                    len(step.substep) - kMaxParallelSteps), **OP_STYLE)\n                g.add_node(ellipsis)\n                g.add_edge(substep_edge(nodes[-1], ellipsis))\n        else:\n            g.add_edge(substep_edge(nodes[-1], sub_nodes[0]))\n\n    return nodes\n\n\ndef GetPlanGraph(plan_def, name=None, rankdir='TB'):\n    graph = pydot.Dot(name, rankdir=rankdir)\n    _draw_steps(plan_def.execution_step, graph)\n    return graph\n\n\ndef GetGraphInJson(operators_or_net, output_filepath):\n    operators, _ = _rectify_operator_and_name(operators_or_net, None)\n    blob_strid_to_node_id = {}\n    node_name_counts = defaultdict(int)\n    nodes = []\n    edges = []\n    for op_id, op in enumerate(operators):\n        op_label = op.name + '/' + op.type if op.name else op.type\n        op_node_id = len(nodes)\n        nodes.append({\n            'id': op_node_id,\n            'label': op_label,\n            'op_id': op_id,\n            'type': 'op'\n        })\n        for input_name in op.input:\n            strid = _escape_label(\n                input_name + str(node_name_counts[input_name]))\n            if strid not in blob_strid_to_node_id:\n                input_node = {\n                    'id': len(nodes),\n                    'label': input_name,\n                    'type': 'blob'\n                }\n                blob_strid_to_node_id[strid] = len(nodes)\n                nodes.append(input_node)\n            else:\n                input_node = nodes[blob_strid_to_node_id[strid]]\n            edges.append({\n                'source': blob_strid_to_node_id[strid],\n                'target': op_node_id\n            })\n        for output_name in op.output:\n            strid = _escape_label(\n                output_name + str(node_name_counts[output_name]))\n            if strid in blob_strid_to_node_id:\n                # we are overwriting an existing blob. need to update the count.\n                node_name_counts[output_name] += 1\n                strid = _escape_label(\n                    output_name + str(node_name_counts[output_name]))\n\n            if strid not in blob_strid_to_node_id:\n                output_node = {\n                    'id': len(nodes),\n                    'label': output_name,\n                    'type': 'blob'\n                }\n                blob_strid_to_node_id[strid] = len(nodes)\n                nodes.append(output_node)\n            edges.append({\n                'source': op_node_id,\n                'target': blob_strid_to_node_id[strid]\n            })\n\n    with open(output_filepath, 'w') as f:\n        json.dump({'nodes': nodes, 'edges': edges}, f)\n\n\n# A dummy minimal PNG image used by GetGraphPngSafe as a\n# placeholder when rendering fail to run.\n_DummyPngImage = (\n    b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00\\x00\\x01\\x00\\x00\\x00'\n    b'\\x01\\x01\\x00\\x00\\x00\\x007n\\xf9$\\x00\\x00\\x00\\nIDATx\\x9cc`\\x00\\x00'\n    b'\\x00\\x02\\x00\\x01H\\xaf\\xa4q\\x00\\x00\\x00\\x00IEND\\xaeB`\\x82')\n\n\ndef GetGraphPngSafe(func, *args, **kwargs):\n    \"\"\"\n    Invokes `func` (e.g. GetPydotGraph) with args. If anything fails - returns\n    and empty image instead of throwing Exception\n    \"\"\"\n    try:\n        graph = func(*args, **kwargs)\n        if not isinstance(graph, pydot.Dot):\n            raise ValueError(\"func is expected to return pydot.Dot\")\n        return graph.create_png()\n    except Exception as e:\n        logger.error(\"Failed to draw graph: {}\".format(e))\n        return _DummyPngImage\n\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Caffe2 net drawer.\")\n    parser.add_argument(\n        \"--input\",\n        type=str, required=True,\n        help=\"The input protobuf file.\"\n    )\n    parser.add_argument(\n        \"--output_prefix\",\n        type=str, default=\"\",\n        help=\"The prefix to be added to the output filename.\"\n    )\n    parser.add_argument(\n        \"--minimal\", action=\"store_true\",\n        help=\"If set, produce a minimal visualization.\"\n    )\n    parser.add_argument(\n        \"--minimal_dependency\", action=\"store_true\",\n        help=\"If set, only draw minimal dependency.\"\n    )\n    parser.add_argument(\n        \"--append_output\", action=\"store_true\",\n        help=\"If set, append the output blobs to the operator names.\")\n    parser.add_argument(\n        \"--rankdir\", type=str, default=\"LR\",\n        help=\"The rank direction of the pydot graph.\"\n    )\n    args = parser.parse_args()\n    with open(args.input, 'r') as fid:\n        content = fid.read()\n        graphs = utils.GetContentFromProtoString(\n            content, {\n                caffe2_pb2.PlanDef: lambda x: GetOperatorMapForPlan(x),\n                caffe2_pb2.NetDef: lambda x: {x.name: x.op},\n            }\n        )\n    for key, operators in viewitems(graphs):\n        if args.minimal:\n            graph = GetPydotGraphMinimal(\n                operators,\n                name=key,\n                rankdir=args.rankdir,\n                node_producer=GetOpNodeProducer(args.append_output, **OP_STYLE),\n                minimal_dependency=args.minimal_dependency)\n        else:\n            graph = GetPydotGraph(\n                operators,\n                name=key,\n                rankdir=args.rankdir,\n                node_producer=GetOpNodeProducer(args.append_output, **OP_STYLE))\n        filename = args.output_prefix + graph.get_name() + '.dot'\n        graph.write(filename, format='raw')\n        pdf_filename = filename[:-3] + 'pdf'\n        try:\n            graph.write_pdf(pdf_filename)\n        except Exception:\n            print(\n                'Error when writing out the pdf file. Pydot requires graphviz '\n                'to convert dot files to pdf, and you may not have installed '\n                'graphviz. On ubuntu this can usually be installed with \"sudo '\n                'apt-get install graphviz\". We have generated the .dot file '\n                'but will not be able to generate pdf file for now.'\n            )\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "caffe2/python/net_printer.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package net_printer\n# Module caffe2.python.net_printer\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.proto.caffe2_pb2 import OperatorDef, NetDef\nfrom caffe2.python.checkpoint import Job\nfrom caffe2.python.core import Net, ExecutionStep, Plan\nfrom caffe2.python.task import Task, TaskGroup, WorkspaceType, TaskOutput\nfrom collections import defaultdict\nfrom contextlib import contextmanager\nfrom copy import copy\nfrom future.utils import viewkeys\nfrom itertools import chain\nfrom six import binary_type, text_type\n\n\nclass Visitor(object):\n    @classmethod\n    def register(cls, Type):\n        if not(hasattr(cls, 'visitors')):\n            cls.visitors = []\n\n        def _register(func):\n            cls.visitors.append((Type, func))\n            return func\n\n        return _register\n\n    def __call__(self, obj, *args, **kwargs):\n        if obj is None:\n            return\n        for Type, func in self.__class__.visitors:\n            if isinstance(obj, Type):\n                return func(self, obj, *args, **kwargs)\n        raise TypeError('%s: unsupported object type: %s' % (\n            self.__class__.__name__, type(obj)))\n\n\nclass Analyzer(Visitor):\n    PREFIXES_TO_IGNORE = {'distributed_ctx_init'}\n\n    def __init__(self):\n        self.workspaces = defaultdict(lambda: defaultdict(lambda: 0))\n        self.workspace_ctx = []\n\n    @property\n    def workspace(self):\n        return self.workspace_ctx[-1]\n\n    @contextmanager\n    def set_workspace(self, node=None, ws=None, do_copy=False):\n        if ws is not None:\n            ws = ws\n        elif node is not None:\n            ws = self.workspaces[str(node)]\n        else:\n            ws = self.workspace\n        if do_copy:\n            ws = copy(ws)\n        self.workspace_ctx.append(ws)\n        yield ws\n        del self.workspace_ctx[-1]\n\n    def define_blob(self, blob):\n        self.workspace[blob] += 1\n\n    def need_blob(self, blob):\n        if any(blob.startswith(p) for p in Analyzer.PREFIXES_TO_IGNORE):\n            return\n        assert blob in self.workspace, 'Blob undefined: %s' % blob\n\n\n@Analyzer.register(OperatorDef)\ndef analyze_op(analyzer, op):\n    for x in op.input:\n        analyzer.need_blob(x)\n    for x in op.output:\n        analyzer.define_blob(x)\n\n\n@Analyzer.register(Net)\ndef analyze_net(analyzer, net):\n    for x in net.Proto().op:\n        analyzer(x)\n\n\n@Analyzer.register(ExecutionStep)\ndef analyze_step(analyzer, step):\n    proto = step.Proto()\n    with analyzer.set_workspace(do_copy=proto.create_workspace):\n        if proto.report_net:\n            with analyzer.set_workspace(do_copy=True):\n                analyzer(step.get_net(proto.report_net))\n        all_new_blobs = set()\n        substeps = step.Substeps() + [step.get_net(n) for n in proto.network]\n        for substep in substeps:\n            with analyzer.set_workspace(\n                    do_copy=proto.concurrent_substeps) as ws_in:\n                analyzer(substep)\n                if proto.should_stop_blob:\n                    analyzer.need_blob(proto.should_stop_blob)\n            if proto.concurrent_substeps:\n                new_blobs = set(viewkeys(ws_in)) - set(viewkeys(analyzer.workspace))\n                assert len(all_new_blobs & new_blobs) == 0, (\n                    'Error: Blobs created by multiple parallel steps: %s' % (\n                        ', '.join(all_new_blobs & new_blobs)))\n                all_new_blobs |= new_blobs\n    for x in all_new_blobs:\n        analyzer.define_blob(x)\n\n\n@Analyzer.register(Task)\ndef analyze_task(analyzer, task):\n    # check that our plan protobuf is not too large (limit of 64Mb)\n    step = task.get_step()\n    plan = Plan(task.node)\n    plan.AddStep(step)\n    proto_len = len(plan.Proto().SerializeToString())\n    assert proto_len < 2 ** 26, (\n        'Due to a protobuf limitation, serialized tasks must be smaller '\n        'than 64Mb, but this task has {} bytes.' % proto_len)\n\n    is_private = task.workspace_type() != WorkspaceType.GLOBAL\n    with analyzer.set_workspace(do_copy=is_private):\n        analyzer(step)\n\n\n@Analyzer.register(TaskGroup)\ndef analyze_task_group(analyzer, tg):\n    for task in tg.tasks_by_node().tasks():\n        with analyzer.set_workspace(node=task.node):\n            analyzer(task)\n\n\n@Analyzer.register(Job)\ndef analyze_job(analyzer, job):\n    analyzer(job.init_group)\n    analyzer(job.epoch_group)\n\n\ndef analyze(obj):\n    \"\"\"\n    Given a Job, visits all the execution steps making sure that:\n      - no undefined blobs will be found during excution\n      - no blob with same name is defined in concurrent steps\n    \"\"\"\n    Analyzer()(obj)\n\n\nclass Text(object):\n    def __init__(self):\n        self._indent = 0\n        self._lines_in_context = [0]\n        self.lines = []\n\n    @contextmanager\n    def context(self, text):\n        if text is not None:\n            self.add('with %s:' % text)\n            self._indent += 4\n            self._lines_in_context.append(0)\n        yield\n        if text is not None:\n            if self._lines_in_context[-1] == 0:\n                self.add('pass')\n            self._indent -= 4\n            del self._lines_in_context[-1]\n\n    def add(self, text):\n        self._lines_in_context[-1] += 1\n        self.lines.append((' ' * self._indent) + text)\n\n    def __str__(self):\n        return '\\n'.join(self.lines)\n\n\nclass Printer(Visitor, Text):\n    def __init__(self, factor_prefixes=False, c2_syntax=True):\n        super(Visitor, self).__init__()\n        super(Text, self).__init__()\n        self.factor_prefixes = factor_prefixes\n        self.c2_syntax = c2_syntax\n        self.c2_net_name = None\n\n\ndef _sanitize_str(s):\n    if isinstance(s, text_type):\n        sanitized = s\n    elif isinstance(s, binary_type):\n        sanitized = s.decode('ascii', errors='ignore')\n    else:\n        sanitized = str(s)\n    if len(sanitized) < 64:\n        return \"'%s'\" % sanitized\n    else:\n        return \"'%s'\" % sanitized[:64] + '...<+len=%d>' % (len(sanitized) - 64)\n\n\ndef _arg_val(arg):\n    if arg.HasField('f'):\n        return str(arg.f)\n    if arg.HasField('i'):\n        return str(arg.i)\n    if arg.HasField('s'):\n        return _sanitize_str(arg.s)\n    if arg.floats:\n        return str(list(arg.floats))\n    if arg.ints:\n        return str(list(arg.ints))\n    if arg.strings:\n        return str([_sanitize_str(s) for s in arg.strings])\n    return '[]'\n\n\ndef commonprefix(m):\n    \"Given a list of strings, returns the longest common prefix\"\n    if not m:\n        return ''\n    s1 = min(m)\n    s2 = max(m)\n    for i, c in enumerate(s1):\n        if c != s2[i]:\n            return s1[:i]\n    return s1\n\n\ndef format_value(val):\n    if isinstance(val, list):\n        return '[%s]' % ', '.join(\"'%s'\" % str(v) for v in val)\n    else:\n        return str(val)\n\n\ndef factor_prefix(vals, do_it):\n    vals = [format_value(v) for v in vals]\n    prefix = commonprefix(vals) if len(vals) > 1 and do_it else ''\n    joined = ', '.join(v[len(prefix):] for v in vals)\n    return '%s[%s]' % (prefix, joined) if prefix else joined\n\n\ndef call(op, inputs=None, outputs=None, factor_prefixes=False):\n    if not inputs:\n        inputs = ''\n    else:\n        inputs_v = [a for a in inputs if not isinstance(a, tuple)]\n        inputs_kv = [a for a in inputs if isinstance(a, tuple)]\n        inputs = ', '.join(\n            x\n            for x in chain(\n                [factor_prefix(inputs_v, factor_prefixes)],\n                ('%s=%s' % kv for kv in inputs_kv),\n            )\n            if x\n        )\n    call = '%s(%s)' % (op, inputs)\n    return call if not outputs else '%s = %s' % (\n        factor_prefix(outputs, factor_prefixes), call)\n\n\ndef format_device_option(dev_opt):\n    if not dev_opt or not (\n            dev_opt.device_type or dev_opt.cuda_gpu_id or dev_opt.node_name):\n        return None\n    return call(\n        'DeviceOption',\n        [dev_opt.device_type, dev_opt.cuda_gpu_id, \"'%s'\" % dev_opt.node_name])\n\n\n@Printer.register(OperatorDef)\ndef print_op(text, op):\n    args = [(a.name, _arg_val(a)) for a in op.arg]\n    dev_opt_txt = format_device_option(op.device_option)\n    if dev_opt_txt:\n        args.append(('device_option', dev_opt_txt))\n\n    if text.c2_net_name:\n        text.add(call(\n            text.c2_net_name + '.' + op.type,\n            [list(op.input), list(op.output)] + args))\n    else:\n        text.add(call(\n            op.type,\n            list(op.input) + args,\n            op.output,\n            factor_prefixes=text.factor_prefixes))\n    for arg in op.arg:\n        if arg.HasField('n'):\n            with text.context('arg: %s' % arg.name):\n                text(arg.n)\n\n@Printer.register(NetDef)\ndef print_net_def(text, net_def):\n    if text.c2_syntax:\n        text.add(call('core.Net', [\"'%s'\" % net_def.name], [net_def.name]))\n        text.c2_net_name = net_def.name\n    else:\n        text.add('# net: %s' % net_def.name)\n    for op in net_def.op:\n        text(op)\n    if text.c2_syntax:\n        text.c2_net_name = None\n\n\n@Printer.register(Net)\ndef print_net(text, net):\n    text(net.Proto())\n\n\ndef _get_step_context(step):\n    proto = step.Proto()\n    if proto.should_stop_blob:\n        return call('loop'), False\n    if proto.num_iter and proto.num_iter != 1:\n        return call('loop', [proto.num_iter]), False\n    if proto.num_concurrent_instances > 1:\n        return (\n            call('parallel',\n                 [('num_instances', proto.num_concurrent_instances)]),\n            len(step.Substeps()) > 1)\n    concurrent = proto.concurrent_substeps and len(step.Substeps()) > 1\n    if concurrent:\n        return call('parallel'), True\n    if proto.report_net:\n        return call('run_once'), False\n    return None, False\n\n\n@Printer.register(ExecutionStep)\ndef print_step(text, step):\n    proto = step.Proto()\n    step_ctx, do_substep = _get_step_context(step)\n    with text.context(step_ctx):\n        if proto.report_net:\n            with text.context(call('report_net', [proto.report_interval])):\n                text(step.get_net(proto.report_net))\n        substeps = step.Substeps() + [step.get_net(n) for n in proto.network]\n        for substep in substeps:\n            sub_proto = (\n                substep.Proto() if isinstance(substep, ExecutionStep) else None)\n            if sub_proto is not None and sub_proto.run_every_ms:\n                substep_ctx = call(\n                    'reporter',\n                    [str(substep), ('interval_ms', sub_proto.run_every_ms)])\n            elif do_substep:\n                title = (\n                    'workspace'\n                    if sub_proto is not None and sub_proto.create_workspace else\n                    'step')\n                substep_ctx = call(title, [str(substep)])\n            else:\n                substep_ctx = None\n            with text.context(substep_ctx):\n                text(substep)\n                if proto.should_stop_blob:\n                    text.add(call('yield stop_if', [proto.should_stop_blob]))\n\n\ndef _print_task_output(x):\n    assert isinstance(x, TaskOutput)\n    return 'Output[' + ', '.join(str(x) for x in x.names) + ']'\n\n\n@Printer.register(Task)\ndef print_task(text, task):\n    outs = ', '.join(_print_task_output(o) for o in task.outputs())\n    context = [('node', task.node), ('name', task.name), ('outputs', outs)]\n    with text.context(call('Task', context)):\n        text(task.get_step())\n\n\n@Printer.register(TaskGroup)\ndef print_task_group(text, tg, header=None):\n    with text.context(header or call('TaskGroup')):\n        for task in tg.tasks_by_node().tasks():\n            text(task)\n\n\n@Printer.register(Job)\ndef print_job(text, job):\n    text(job.init_group, 'Job.current().init_group')\n    text(job.epoch_group, 'Job.current().epoch_group')\n    with text.context('Job.current().stop_signals'):\n        for out in job.stop_signals:\n            text.add(_print_task_output(out))\n    text(job.download_group, 'Job.current().download_group')\n    text(job.exit_group, 'Job.current().exit_group')\n\n\ndef to_string(obj, **kwargs):\n    \"\"\"\n    Given a Net, ExecutionStep, Task, TaskGroup or Job, produces a string\n    with detailed description of the execution steps.\n    \"\"\"\n    printer = Printer(**kwargs)\n    printer(obj)\n    return str(printer)\n\n\ndef debug_net(net):\n    \"\"\"\n    Given a Net, produce another net that logs info about the operator call\n    before each operator execution. Use for debugging purposes.\n    \"\"\"\n    assert isinstance(net, Net)\n    debug_net = Net(str(net))\n    assert isinstance(net, Net)\n    for op in net.Proto().op:\n        text = Text()\n        print_op(op, text)\n        debug_net.LogInfo(str(text))\n        debug_net.Proto().op.extend([op])\n    return debug_net\n"
  },
  {
    "path": "caffe2/python/net_printer_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import net_printer\nfrom caffe2.python.checkpoint import Job\nfrom caffe2.python.net_builder import ops\nfrom caffe2.python.task import Task, final_output, WorkspaceType\nimport unittest\n\n\ndef example_loop():\n    with Task():\n        total = ops.Const(0)\n        total_large = ops.Const(0)\n        total_small = ops.Const(0)\n        total_tiny = ops.Const(0)\n        with ops.loop(10) as loop:\n            outer = ops.Mul([loop.iter(), ops.Const(10)])\n            with ops.loop(loop.iter()) as inner:\n                val = ops.Add([outer, inner.iter()])\n                with ops.If(ops.GE([val, ops.Const(80)])) as c:\n                    ops.Add([total_large, val], [total_large])\n                with c.Elif(ops.GE([val, ops.Const(50)])) as c:\n                    ops.Add([total_small, val], [total_small])\n                with c.Else():\n                    ops.Add([total_tiny, val], [total_tiny])\n                ops.Add([total, val], total)\n\n\ndef example_task():\n    with Task():\n        with ops.task_init():\n            one = ops.Const(1)\n        two = ops.Add([one, one])\n        with ops.task_init():\n            three = ops.Const(3)\n        accum = ops.Add([two, three])\n        # here, accum should be 5\n        with ops.task_exit():\n            # here, accum should be 6, since this executes after lines below\n            seven_1 = ops.Add([accum, one])\n        six = ops.Add([accum, one])\n        ops.Add([accum, one], [accum])\n        seven_2 = ops.Add([accum, one])\n        o6 = final_output(six)\n        o7_1 = final_output(seven_1)\n        o7_2 = final_output(seven_2)\n\n    with Task(num_instances=2):\n        with ops.task_init():\n            one = ops.Const(1)\n        with ops.task_instance_init():\n            local = ops.Const(2)\n        ops.Add([one, local], [one])\n        ops.LogInfo('ble')\n\n    return o6, o7_1, o7_2\n\ndef example_job():\n    with Job() as job:\n        with job.init_group:\n            example_loop()\n        example_task()\n    return job\n\n\nclass TestNetPrinter(unittest.TestCase):\n    def test_print(self):\n        self.assertTrue(len(net_printer.to_string(example_job())) > 0)\n\n    def test_valid_job(self):\n        job = example_job()\n        with job:\n            with Task():\n                # distributed_ctx_init_* ignored by analyzer\n                ops.Add(['distributed_ctx_init_a', 'distributed_ctx_init_b'])\n        # net_printer.analyze(example_job())\n        print(net_printer.to_string(example_job()))\n\n    def test_undefined_blob(self):\n        job = example_job()\n        with job:\n            with Task():\n                ops.Add(['a', 'b'])\n        with self.assertRaises(AssertionError) as e:\n            net_printer.analyze(job)\n        self.assertEqual(\"Blob undefined: a\", str(e.exception))\n\n    def test_multiple_definition(self):\n        job = example_job()\n        with job:\n            with Task(workspace_type=WorkspaceType.GLOBAL):\n                ops.Add([ops.Const(0), ops.Const(1)], 'out1')\n            with Task(workspace_type=WorkspaceType.GLOBAL):\n                ops.Add([ops.Const(2), ops.Const(3)], 'out1')\n        with self.assertRaises(AssertionError):\n            net_printer.analyze(job)\n"
  },
  {
    "path": "caffe2/python/numa_benchmark.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nfrom caffe2.python import core, workspace\nfrom caffe2.proto import caffe2_pb2\nimport time\n\n\ndef build_net(net_name, cross_socket):\n    net = core.Net(net_name)\n    net.Proto().type = \"async_scheduling\"\n    numa_device_option = caffe2_pb2.DeviceOption()\n    numa_device_option.device_type = caffe2_pb2.CPU\n    numa_device_option.numa_node_id = 0\n    net.XavierFill([], net_name + \"/input_blob\", shape=[1024, 1024],\n                    device_option=numa_device_option)\n    if cross_socket:\n        numa_device_option.numa_node_id = 1\n    net.Copy(net_name + \"/input_blob\", net_name + \"/output_blob\",\n                device_option=numa_device_option)\n    return net\n\n\ndef main():\n    assert workspace.IsNUMAEnabled() and workspace.GetNumNUMANodes() >= 2\n\n    single_net = build_net(\"single_net\", False)\n    cross_net = build_net(\"cross_net\", True)\n    workspace.CreateNet(single_net)\n    workspace.CreateNet(cross_net)\n\n    for _ in range(4):\n        t = time.time()\n        workspace.RunNet(single_net.Name(), 5000)\n        print(\"Single socket time:\", time.time() - t)\n\n        t = time.time()\n        workspace.RunNet(cross_net.Name(), 5000)\n        print(\"Cross socket time:\", time.time() - t)\n\n\nif __name__ == '__main__':\n    core.GlobalInit([\"caffe2\", \"--caffe2_cpu_numa_enabled=1\"])\n    main()\n"
  },
  {
    "path": "caffe2/python/numa_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nfrom caffe2.python import core, workspace\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python.test_util import TestCase\nimport unittest\n\ncore.GlobalInit([\"caffe2\", \"--caffe2_cpu_numa_enabled=1\"])\n\n\ndef build_test_net(net_name):\n    net = core.Net(net_name)\n    net.Proto().type = \"async_scheduling\"\n\n    numa_device_option = caffe2_pb2.DeviceOption()\n    numa_device_option.device_type = caffe2_pb2.CPU\n    numa_device_option.numa_node_id = 0\n\n    net.ConstantFill([], \"output_blob_0\", shape=[1], value=3.14,\n                         device_option=numa_device_option)\n\n    numa_device_option.numa_node_id = 1\n    net.ConstantFill([], \"output_blob_1\", shape=[1], value=3.14,\n                         device_option=numa_device_option)\n\n    gpu_device_option = caffe2_pb2.DeviceOption()\n    gpu_device_option.device_type = caffe2_pb2.CUDA\n    gpu_device_option.cuda_gpu_id = 0\n\n    net.CopyCPUToGPU(\"output_blob_0\", \"output_blob_0_gpu\",\n                        device_option=gpu_device_option)\n    net.CopyCPUToGPU(\"output_blob_1\", \"output_blob_1_gpu\",\n                        device_option=gpu_device_option)\n\n    return net\n\n\n@unittest.skipIf(not workspace.IsNUMAEnabled(), \"NUMA is not enabled\")\n@unittest.skipIf(workspace.GetNumNUMANodes() < 2, \"Not enough NUMA nodes\")\n@unittest.skipIf(not workspace.has_gpu_support, \"No GPU support\")\nclass NUMATest(TestCase):\n    def test_numa(self):\n        net = build_test_net(\"test_numa\")\n\n        workspace.RunNetOnce(net)\n\n        self.assertEqual(workspace.GetBlobNUMANode(\"output_blob_0\"), 0)\n        self.assertEqual(workspace.GetBlobNUMANode(\"output_blob_1\"), 1)\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/observer_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nimport unittest\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom caffe2.python import brew, core, model_helper, rnn_cell\nimport caffe2.python.workspace as ws\n\n\nclass TestObservers(unittest.TestCase):\n    def setUp(self):\n        core.GlobalInit([\"python\", \"caffe2\"])\n        ws.ResetWorkspace()\n        self.model = model_helper.ModelHelper()\n        brew.fc(self.model, \"data\", \"y\",\n                    dim_in=4, dim_out=2,\n                    weight_init=('ConstantFill', dict(value=1.0)),\n                    bias_init=('ConstantFill', dict(value=0.0)),\n                    axis=0)\n        ws.FeedBlob(\"data\", np.zeros([4], dtype='float32'))\n\n        ws.RunNetOnce(self.model.param_init_net)\n        ws.CreateNet(self.model.net)\n\n    def testObserver(self):\n        ob = self.model.net.AddObserver(\"TimeObserver\")\n        ws.RunNet(self.model.net)\n        print(ob.average_time())\n        num = self.model.net.NumObservers()\n        self.model.net.RemoveObserver(ob)\n        assert(self.model.net.NumObservers() + 1 == num)\n\n    @given(\n        num_layers=st.integers(1, 4),\n        forward_only=st.booleans()\n    )\n    def test_observer_rnn_executor(self, num_layers, forward_only):\n        '''\n        Test that the RNN executor produces same results as\n        the non-executor (i.e running step nets as sequence of simple nets).\n        '''\n\n        Tseq = [2, 3, 4]\n        batch_size = 10\n        input_dim = 3\n        hidden_dim = 3\n\n        run_cnt = [0] * len(Tseq)\n        avg_time = [0] * len(Tseq)\n        for j in range(len(Tseq)):\n            T = Tseq[j]\n\n            ws.ResetWorkspace()\n            ws.FeedBlob(\n                \"seq_lengths\",\n                np.array([T] * batch_size, dtype=np.int32)\n            )\n            ws.FeedBlob(\"target\", np.random.rand(\n                T, batch_size, hidden_dim).astype(np.float32))\n            ws.FeedBlob(\"hidden_init\", np.zeros(\n                [1, batch_size, hidden_dim], dtype=np.float32\n            ))\n            ws.FeedBlob(\"cell_init\", np.zeros(\n                [1, batch_size, hidden_dim], dtype=np.float32\n            ))\n\n            model = model_helper.ModelHelper(name=\"lstm\")\n            model.net.AddExternalInputs([\"input\"])\n\n            init_blobs = []\n            for i in range(num_layers):\n                hidden_init, cell_init = model.net.AddExternalInputs(\n                    \"hidden_init_{}\".format(i),\n                    \"cell_init_{}\".format(i)\n                )\n                init_blobs.extend([hidden_init, cell_init])\n\n            output, last_hidden, _, last_state = rnn_cell.LSTM(\n                model=model,\n                input_blob=\"input\",\n                seq_lengths=\"seq_lengths\",\n                initial_states=init_blobs,\n                dim_in=input_dim,\n                dim_out=[hidden_dim] * num_layers,\n                drop_states=True,\n                forward_only=forward_only,\n                return_last_layer_only=True,\n            )\n\n            loss = model.AveragedLoss(\n                model.SquaredL2Distance([output, \"target\"], \"dist\"),\n                \"loss\"\n            )\n            # Add gradient ops\n            if not forward_only:\n                model.AddGradientOperators([loss])\n\n            # init\n            for init_blob in init_blobs:\n                ws.FeedBlob(init_blob, np.zeros(\n                    [1, batch_size, hidden_dim], dtype=np.float32\n                ))\n            ws.RunNetOnce(model.param_init_net)\n\n            # Run with executor\n            self.enable_rnn_executor(model.net, 1, forward_only)\n\n            np.random.seed(10022015)\n            input_shape = [T, batch_size, input_dim]\n            ws.FeedBlob(\n                \"input\",\n                np.random.rand(*input_shape).astype(np.float32)\n            )\n            ws.FeedBlob(\n                \"target\",\n                np.random.rand(\n                    T,\n                    batch_size,\n                    hidden_dim\n                ).astype(np.float32)\n            )\n            ws.CreateNet(model.net, overwrite=True)\n\n            time_ob = model.net.AddObserver(\"TimeObserver\")\n            run_cnt_ob = model.net.AddObserver(\"RunCountObserver\")\n            ws.RunNet(model.net)\n            avg_time[j] = time_ob.average_time()\n            run_cnt[j] = int(''.join(x for x in run_cnt_ob.debug_info() if x.isdigit()))\n            model.net.RemoveObserver(time_ob)\n            model.net.RemoveObserver(run_cnt_ob)\n\n        print(avg_time)\n        print(run_cnt)\n        self.assertTrue(run_cnt[1] > run_cnt[0] and run_cnt[2] > run_cnt[1])\n        self.assertEqual(run_cnt[1] - run_cnt[0], run_cnt[2] - run_cnt[1])\n\n    def enable_rnn_executor(self, net, value, forward_only):\n        num_found = 0\n        for op in net.Proto().op:\n            if op.type.startswith(\"RecurrentNetwork\"):\n                for arg in op.arg:\n                    if arg.name == 'enable_rnn_executor':\n                        arg.i = value\n                        num_found += 1\n        # This sanity check is so that if someone changes the\n        # enable_rnn_executor parameter name, the test will\n        # start failing as this function will become defective.\n        self.assertEqual(1 if forward_only else 2, num_found)\n"
  },
  {
    "path": "caffe2/python/onnx/ONNXOpCoverage.md",
    "content": "# Tracking why operators are not covered\n[ONNX backend test script](https://github.com/onnx/onnx-caffe2/blob/master/tests/onnx_backend_test.py)\nreports the coverage on the operators and attributes. But we have various of reasons for the missing test coverage on operators.\nThis doc keeps tracking why operators are not covered by the testcases.\n\n- &#x1F49A; The ONNX operator can map to a Caffe2 operator.\n- &#x1F49B; The solution is not perfect/finished, for example, the operator can map to a combination of Caffe2 operators.\n- &#x1F494; Hard to find a solution with existing Caffe2 operators.\n\n| Operator | Test Coverage | PyTorch | Caffe2 |\n|---|:--:|:---:|:---:|\n|Abs|Yes|OK|&#x1F49A;OK|\n|Add|Yes|OK|&#x1F49A;OK|\n|And|Yes|Support int tensor, but no bool tensor|&#x1F49A;OK|\n|ArgMax|||&#x1F494;No op|\n|ArgMin|||&#x1F494;No op|\n|AveragePool||OK|&#x1F49A;OK|\n|BatchNormalization||OK|&#x1F49A;OK|\n|Cast|Yes||&#x1F494;Need extendtion|\n|Ceil|Yes||&#x1F49A;OK|\n|Clip|Yes|OK|&#x1F49A;OK|\n|Concat|Yes|OK|&#x1F49A;OK|\n|Constant|Yes|OK|&#x1F49B;Special handling|\n|Conv|Yes|OK|&#x1F49A;OK|\n|ConvTranspose|Yes||&#x1F49A;OK|\n|DepthToSpace|||&#x1F49B;Should be BatchToSpace, no tests|\n|Div|Yes|OK|&#x1F49A;OK|\n|Dropout|Yes|OK|&#x1F49A;OK|\n|Elu|Yes|OK|&#x1F49A;OK|\n|Equal|Yes|OK|&#x1F49A;OK|\n|Exp|Yes|OK|&#x1F49A;OK|\n|Flatten|Yes|OK|&#x1F49A;OK|\n|Floor|Yes||&#x1F49A;OK|\n|GRU|||&#x1F49B;Under development|\n|Gather|Yes|OK|&#x1F49B;C2 only support axis=0 or 1, under development|\n|Gemm|Yes|OK|&#x1F49B;C2 use FC or MatMul + Add|\n|GlobalAveragePool|Yes|No direct mapping|&#x1F49A;OK|\n|GlobalLpPool|||&#x1F494;No op|\n|GlobalMaxPool|||&#x1F49A;OK|\n|Greater|Yes||&#x1F49A;OK|\n|HardSigmoid|Yes||&#x1F494;No op|\n|Hardmax|Yes||&#x1F494;No op|\n|InstanceNormalization|||&#x1F49A;OK|\n|LRN||OK|&#x1F49A;OK|\n|LSTM|||&#x1F49B;Under development|\n|LeakyRelu|Yes|OK|&#x1F49A;OK|\n|Less|Yes||&#x1F49A;|\n|Log|Yes|OK|&#x1F49A;OK|\n|LogSoftmax||OK|&#x1F49B;No op, translated in onnx-caffe2|\n|LpNormalization|||&#x1F49A;Should be LpNorm, no tests|\n|LpPool|||&#x1F49A;Should be LpPool, no tests|\n|MatMul|Yes|OK|&#x1F49A;OK|\n|Max|Yes|OK|&#x1F49A;OK|\n|MaxPool||OK|&#x1F49A;OK|\n|MaxRoiPool|||&#x1F494;No op|\n|Mean|||&#x1F494;No op|\n|Min|Yes|OK|&#x1F49A;OK|\n|Mul|Yes|OK|&#x1F49A;OK|\n|Neg|Yes|OK|&#x1F49A;OK|\n|Not|Yes||&#x1F49A;OK|\n|Or|Yes||&#x1F49A;OK|\n|PRelu|Yes|OK|&#x1F49A;OK|\n|Pad|Yes|OK|&#x1F49A;OK|\n|Pow|Yes|OK|&#x1F49A;OK|\n|RNN|||&#x1F49B;Under development|\n|RandomNormal|||&#x1F494;No op|\n|RandomNormalLike|||&#x1F494;No op|\n|RandomUniform|||&#x1F494;No op|\n|RandomUniformLike|||&#x1F494;No op|\n|Reciprocal|Yes||&#x1F49B;Use Pow to implement|\n|ReduceL1|||&#x1F494;No op|\n|ReduceL2|||&#x1F494;No op|\n|ReduceLogSum|||&#x1F494;No op|\n|ReduceLogSumExp|||&#x1F494;No op|\n|ReduceMax|||&#x1F494;No op|\n|ReduceMean|||&#x1F494;No op|\n|ReduceMin|||&#x1F494;No op|\n|ReduceProd|||&#x1F494;No op|\n|ReduceSum|||&#x1F494;No op|\n|ReduceSumSquare|||&#x1F494;No op|\n|Relu|Yes|OK|&#x1F49A;OK|\n|Reshape|Yes|OK|&#x1F49A;OK|\n|Selu|Yes|OK|&#x1F49A;OK|\n|Sigmoid|Yes|OK|&#x1F49A;OK|\n|Slice|Yes|OK|&#x1F494;ScatterAssign + Cast, very hacky implementaion, Slice in C2 only supports one dimension|\n|Softmax|Yes|OK|&#x1F494;Axis and dim has different semantics|\n|Softplus|Yes|OK|&#x1F49A;OK|\n|Softsign|Yes||&#x1F49A;OK|\n|SpaceToDepth|||&#x1F49B;Should be SpaceToBatch, no tests|\n|Split|Yes|OK|&#x1F49A;OK|\n|Sqrt|Yes||&#x1F49A;OK|\n|Squeeze|Yes||&#x1F49A;OK|\n|Sub|Yes|OK|&#x1F49A;OK|\n|Sum|Yes|OK|&#x1F49A;OK|\n|Tanh|Yes|OK|&#x1F49A;OK|\n|Tile|||&#x1F49B;OK, no tests|\n|Transpose|Yes|OK|&#x1F49A;OK|\n|Xor|Yes||&#x1F49A;OK|\n|experimental ATen|||&#x1F49A;OK|\n|experimental Affine|||&#x1F494;No op|\n|experimental ConstantFill|||&#x1F49A;OK|\n|experimental Crop|||&#x1F494;No op|\n|experimental FC|||&#x1F49A;OK|\n|experimental GRUUnit|||&#x1F49A;OK, no tests|\n|experimental GivenTensorFill|||&#x1F49A;OK|\n|experimental Identity|||&#x1F49A;OK|\n|experimental ImageScaler|||&#x1F494;No op|\n|experimental MeanVarianceNormalization|||&#x1F494;No op|\n|experimental ParametricSoftplus|||&#x1F494;No op|\n|experimental Scale|||&#x1F49A;OK|\n|experimental ScaledTanh|||&#x1F494;No op|\n|experimental ThresholdedRelu|Yes||&#x1F49A;OK|\n|experimental Upsample|||&#x1F494;No bilinear|\n"
  },
  {
    "path": "caffe2/python/onnx/README.md",
    "content": "Caffe2 implementation of Open Neural Network Exchange (ONNX)\n========\n\n# Usage\n\n* [ONNX to Caffe2](https://github.com/onnx/tutorials/blob/master/tutorials/OnnxCaffe2Import.ipynb)\n* [Caffe2 to ONNX](https://github.com/onnx/tutorials/blob/master/tutorials/Caffe2OnnxExport.ipynb)\n* [other end-to-end tutorials](https://github.com/onnx/tutorials)\n\n# Installation\n\nonnx-caffe2 is installed as a part of Caffe2.\nPlease follow the [instructions](https://caffe2.ai/docs/getting-started.html) to install Caffe2.\n\n\n# Folder Structure\n\n- ./: the main folder that all code lies under\n  - frontend.py: translate from caffe2 model to onnx model\n  - backend.py: execution engine that runs onnx on caffe2\n- tests/: test files\n\n# Testing\n\nonnx-caffe2 uses [pytest](https://docs.pytest.org) as test driver. In order to run tests, first you need to install pytest:\n\n\n```\npip install pytest-cov\n```\n\nAfter installing pytest, do\n\n```\npytest\n```\n\nto run tests.\n\nTesting coverage issues/status: https://github.com/caffe2/caffe2/blob/master/caffe2/python/onnx/ONNXOpCoverage.md\n\n# Development\n\nDuring development it's convenient to install caffe2 in development mode:\n\n```\ncd /path/to/caffe2\npip install -e caffe2/\n```\n\n# License\n\n[MIT License](LICENSE)\n\n"
  },
  {
    "path": "caffe2/python/onnx/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/python/onnx/backend.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.backend\n\n\"\"\"Backend for running ONNX on Caffe2\n\nTo run this, you will need to have Caffe2 installed as well.\n\"\"\"\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport os\nimport collections\nfrom subprocess import Popen, PIPE\n\nimport caffe2\nfrom caffe2.python import core, workspace, rnn_cell, gru_cell\nfrom caffe2.python.model_helper import ModelHelper\nfrom caffe2.proto import caffe2_pb2\nimport caffe2.python.utils\nimport numpy as np\nimport onnx\nfrom onnx import checker, GraphProto, TensorProto, AttributeProto, ModelProto\nimport onnx.numpy_helper\nimport onnx.defs\nimport onnx.optimizer\nfrom onnx.backend.base import Backend, Device, DeviceType, namedtupledict\n\nfrom caffe2.python.onnx.workspace import Workspace\nfrom caffe2.python.onnx.backend_rep import Caffe2Rep\nfrom caffe2.python.onnx.backend_cpp_rep import Caffe2CppRep\nfrom caffe2.python.onnx.helper import dummy_name\n\nimport caffe2.python._import_c_extension as C\n\nimport warnings\n\ndef force_unicode(s):\n    try:\n        return s.decode('utf-8')\n    except AttributeError:\n        return s\n\ndef get_device_option(device):\n    m = {DeviceType.CPU: caffe2_pb2.CPU,\n         DeviceType.CUDA: caffe2_pb2.CUDA}\n    return core.DeviceOption(m[device.type], device.device_id)\n\n\nclass OnnxAttributes(dict):\n    \"\"\"\n    This is a more convenient way to work with ONNX/Caffe2 attributes\n    that is not the protobuf representation.\n    \"\"\"\n    @staticmethod\n    def from_onnx(args):\n        d = OnnxAttributes()\n        for arg in args:\n            d[arg.name] = convertAttributeProto(arg)\n        return d\n\n    def caffe2(self, kmap=lambda k: k):\n        for k, v in self.items():\n            if kmap(k) != '':\n                yield caffe2.python.utils.MakeArgument(kmap(k), v)\n\n# TODO: Move this into ONNX main library\ndef convertAttributeProto(onnx_arg):\n    \"\"\"\n    Convert an ONNX AttributeProto into an appropriate Python object\n    for the type.\n\n    NB: Tensor attribute gets returned as the straight proto.\n    \"\"\"\n    if onnx_arg.HasField('f'):\n        return onnx_arg.f\n    elif onnx_arg.HasField('i'):\n        return onnx_arg.i\n    elif onnx_arg.HasField('s'):\n        return onnx_arg.s\n    elif onnx_arg.HasField('t'):\n        return onnx_arg.t  # this is a proto!\n    elif len(onnx_arg.floats):\n        return list(onnx_arg.floats)\n    elif len(onnx_arg.ints):\n        return list(onnx_arg.ints)\n    elif len(onnx_arg.strings):\n        return list(onnx_arg.strings)\n    else:\n        raise ValueError(\"Unsupported ONNX attribute: {}\".format(onnx_arg))\n\n\n# TODO: Move this into ONNX main library\nclass OnnxNode(object):\n    \"\"\"\n    Reimplementation of NodeProto from ONNX, but in a form\n    more convenient to work with from Python.\n\n    We may temporarily edit these nodes to get them into Caffe2 form,\n    before actually translating into the Caffe2 protobuf, since this\n    is easier than decomposing everything, and putting it back together\n    when we're ready.\n    \"\"\"\n    def __init__(self, node):\n        self.name = str(node.name)\n        self.op_type = str(node.op_type)\n        self.attrs = OnnxAttributes.from_onnx(node.attribute)\n        self.consumed_inputs = self.attrs.pop(\"consumed_inputs\", None)\n        self.inputs = list(node.input)\n        self.outputs = list(node.output)\n\n\nCaffe2Ops = collections.namedtuple('Caffe2Ops', ['ops', 'init_ops', 'interface_blobs'])\n\n\nclass Caffe2Backend(Backend):\n\n    # The greatest version of the ONNX operator set which we are aware of.\n    # Models whose version is larger than this will cause us to emit a warning\n    # that we are attempting to translate on a \"best effort\" basis.\n    #\n    # If you increase this, make SURE you cross-reference all BC-breaking\n    # changes from one version to the next, and any that you did not\n    # implement, mark as broken in _broken_operators\n    _known_opset_version = 5\n\n    # This dictionary will record operators which are KNOWN to be\n    # broken, so we give a good error message rather than do something\n    # bogus and then fail.\n    _broken_operators = {\n        # 'BrokenOp': version_it_was_broken_in\n    }\n\n    # Operators that are different between Caffe2 and\n    # ONNX but only in their name.\n    # In most cases, this should be empty - as the effort of ONNX is\n    # to unify the operator definitions.\n    _renamed_operators = {\n        'Caffe2ConvTranspose':   'ConvTranspose',\n        'GlobalMaxPool':         'MaxPool',\n        'GlobalAveragePool':     'AveragePool',\n        'Pad':                   'PadImage',\n        'Neg':                   'Negative',\n        'BatchNormalization':    'SpatialBN',\n        'InstanceNormalization': 'InstanceNorm',\n        'MatMul':                'BatchMatMul',\n        'Upsample':              'ResizeNearest',\n        'Identity':              'Copy',\n        'InstanceNormalization': 'InstanceNorm',\n        'Equal':                 'EQ',\n        'Less':                  'LT',\n        'Greater':               'GT',\n        'Unsqueeze':             'ExpandDims',\n    }\n\n    _global_renamed_attrs = {'kernel_shape': 'kernels'}\n    _per_op_renamed_attrs = {\n        'Squeeze':              {'axes': 'dims'},\n        'Unsqueeze':            {'axes': 'dims'},\n        'Transpose':            {'perm': 'axes'},\n        'Upsample':             {'mode': ''},\n        'ConvTranspose':        {'output_padding': 'adjs'},\n        'Selu':                 {'gamma': 'scale'},\n    }\n\n    # operators whose behavior is different beyond renaming\n    # the value is an attribute of this class that is a\n    # function from ToffeIR node_def to caffe2 op_def\n    _special_operators = {\n        'Constant': '_create_constant',\n        'Conv': '_create_conv_pool_op_base',\n        'AveragePool': '_create_conv_pool_op_base',\n        'GlobalAveragePool': '_create_conv_pool_op_base',\n        'GlobalMaxPool': '_create_conv_pool_op_base',\n        'MaxPool': '_create_conv_pool_op_base',\n        'Reshape': '_create_reshape',\n        'Gather': '_create_gather',\n        'Gemm': '_create_gemm',\n        'Pad': '_create_pad',\n        'Concat': '_create_concat',\n        'LogSoftmax': '_create_logsoftmax',\n        'Slice': '_create_slice',\n        'LSTM': '_create_lstm',\n        'GRU': '_create_gru',\n        'RNN': '_create_rnn',\n        'Sqrt': '_create_sqrt',\n        'Reciprocal': '_create_reciprocal',\n        'MatMul': '_create_matmul',\n    }\n\n    # NB: By default, you will use the LATEST definition of the operator,\n    # so this interface MAY make BC-breaking changes.  Specify an\n    # opset_version if you don't want this to version.\n    @classmethod\n    def run_node(cls, node, inputs, device='CPU', opset_version=_known_opset_version, outputs_info=None):\n        super(Caffe2Backend, cls).run_node(node, inputs, device=device, outputs_info=outputs_info)\n\n        device_option = get_device_option(Device(device))\n        with Workspace(), core.DeviceScope(device_option):  # temporary!\n            if isinstance(inputs, dict):\n                for key, value in inputs.items():\n                    workspace.FeedBlob(key, value)\n            else:\n                assert len(node.input) == len(inputs), \"{}: expected {} but got {}\".format(\n                    node.op_type, len(node.input), len(inputs))\n                for key, value in zip(node.input, inputs):\n                    workspace.FeedBlob(key, value)\n\n            ops = []\n            cbackend = C.Caffe2Backend()\n            ops_str = cbackend.convert_node(node.SerializeToString(), opset_version)\n            for s in ops_str:\n                op = caffe2_pb2.OperatorDef()\n                op.ParseFromString(s)\n                op.device_option.CopyFrom(device_option)\n                ops.append(op)\n            cls._inplace_rewrite([node])\n            # For testing\n            if \"ONNX_CAFFE2_DEBUG\" in os.environ:\n                init_ops, ops2, _ = cls._onnx_node_to_caffe2_op(\n                    None, None, node, opset_version or cls._known_opset_version)\n                ops2 = init_ops + ops2\n                for op in ops2:\n                    op.device_option.CopyFrom(device_option)\n                print(\"\\nC++:\\n{}\\nPython:\\n{}\".format(ops, ops2))\n            workspace.RunOperatorsOnce(ops)\n            output_values = [workspace.FetchBlob(name) for name in node.output]\n            return namedtupledict('Outputs', node.output)(*output_values)\n\n    @classmethod\n    def _create_tensor_filling_op(cls, onnx_tensor, name=None):\n        \"\"\"\n        Given an Onnx TensorProto, translate it into a Caffe2 operator\n        which produces the given tensor filling op.\n        \"\"\"\n        assert name or onnx_tensor.name\n        name = name or onnx_tensor.name\n\n        c2_op = caffe2_pb2.OperatorDef()\n\n        c2_values = c2_op.arg.add()\n        c2_values.name = \"values\"\n\n        def tensor2list(onnx_tensor):\n            # Use the onnx.numpy_helper because the data may be raw\n            return onnx.numpy_helper.to_array(onnx_tensor).flatten().tolist()\n\n        if onnx_tensor.data_type in [TensorProto.FLOAT]:\n            c2_op.type = 'GivenTensorFill'\n            c2_values.floats.extend(tensor2list(onnx_tensor))\n        elif onnx_tensor.data_type in [TensorProto.DOUBLE]:\n            c2_op.type = 'GivenTensorDoubleFill'\n            c2_values.floats.extend(tensor2list(onnx_tensor))\n        elif onnx_tensor.data_type in [TensorProto.INT64,\n                                       TensorProto.UINT32]:\n            c2_op.type = 'GivenTensorInt64Fill'\n            c2_values.ints.extend(tensor2list(onnx_tensor))\n        elif onnx_tensor.data_type in [TensorProto.UINT8,\n                                       TensorProto.INT8,\n                                       TensorProto.UINT16,\n                                       TensorProto.INT16,\n                                       TensorProto.INT32]:\n            c2_op.type = 'GivenTensorIntFill'\n            c2_values.ints.extend(tensor2list(onnx_tensor))\n        elif onnx_tensor.data_type == TensorProto.BOOL:\n            c2_op.type = 'GivenTensorBoolFill'\n            c2_values.ints.extend(tensor2list(onnx_tensor))\n        elif onnx_tensor.data_type == TensorProto.STRING:\n            c2_op.type = 'GivenTensorStringFill'\n            c2_values.strings.extend(onnx_tensor.string_data)\n        else:\n            raise RuntimeError(\n                \"unrecognized tensor type {}\".format(onnx_tensor.data_type))\n\n        c2_shape = c2_op.arg.add()\n        c2_shape.name = \"shape\"\n        c2_shape.ints.extend(onnx_tensor.dims)\n\n        c2_op.output.append(name)\n\n        return c2_op\n\n    @classmethod\n    def _create_constant(cls, init_model, pred_model, n, opset_version):\n        assert len(n.outputs) == 1\n        return cls._create_tensor_filling_op(n.attrs[\"value\"], n.outputs[0])\n\n    @classmethod\n    def _create_gather(cls, init_model, pred_model, n, opset_version):\n        (A, B) = n.inputs\n        (Y, ) = n.outputs\n        axis = n.attrs.get('axis', 0)\n\n        if axis == 0:\n            return core.CreateOperator(\"Gather\", [A, B], [Y])\n        elif axis == 1:\n            return core.CreateOperator(\"BatchGather\", [A, B], [Y])\n        raise ValueError(\n            'Caffe2 only supports Gather with axis being 0 or 1,' +\n            'whereas axis is ' + str(axis))\n\n    @classmethod\n    def _create_logsoftmax(cls, init_model, pred_model, n, opset_version):\n        # NB: this implementation is not backward stable.\n        (A,) = n.inputs\n        (Y,) = n.outputs\n        axis = n.attrs.get('axis', 1)\n        ops = []\n        softmax_A = dummy_name()\n        ops.append(core.CreateOperator('Softmax', [A], [softmax_A], axis=axis))\n        ops.append(core.CreateOperator('Log', [softmax_A], [Y]))\n        return ops\n\n    @classmethod\n    def _create_gemm(cls, init_model, pred_model, n, opset_version):\n        (A, B, C) = n.inputs\n        (Y,) = n.outputs\n        alpha = n.attrs.get('alpha', 1.)\n        beta = n.attrs.get('beta', 1.)\n\n        ops = []\n        if alpha != 1:\n            scaled_A = dummy_name()\n            ops.append(core.CreateOperator('Scale', [A], [scaled_A], scale=alpha))\n            A = scaled_A\n        if beta != 1:\n            scaled_C = dummy_name()\n            ops.append(core.CreateOperator('Scale', [C], [scaled_C], scale=beta))\n            C = scaled_C\n\n        trans_a = n.attrs.get('transA', 0)\n        trans_b = n.attrs.get('transB', 0)\n        broadcast = n.attrs.get('broadcast', 0)\n        if not trans_a and trans_b and broadcast:\n            ops.append(core.CreateOperator('FC',\n                                           [A, B, C],\n                                           [Y]))\n        else:\n            AB = dummy_name()\n            ops.append(core.CreateOperator('MatMul',\n                                           [A, B],\n                                           [AB],\n                                           trans_a=trans_a,\n                                           trans_b=trans_b))\n            ops.append(core.CreateOperator('Add',\n                                           [AB, C],\n                                           [Y],\n                                           broadcast=broadcast))\n\n        return ops\n\n    @classmethod\n    def _rnn_shape_inference(cls, init_model, pred_model, n, input_blob, W):\n        # ad-hoc, informally-specified, bug-ridden, slow\n        # implementation of shape inference\n\n        # if the weight matrices are directly provided as\n        # initializers, their dimensions should be available in the\n        # init net model.\n        for x in init_model.graph.input:\n            if x.name == W:\n                return x.type.tensor_type.shape.dim[1].dim_value\n\n        # otherwise, assume that the input_blob is either a direct\n        # graph input, or another rnn op of the same type. This\n        # matches the pattern produced by exporting from pytorch\n        # (where the weight matrices are unusable for this purpose due\n        # to reshaping operations that lose shape information).\n        for x in pred_model.graph.input:\n            if x.name == input_blob:\n                return x.type.tensor_type.shape.dim[2].dim_value\n\n        curr = n\n        while True:\n            for x in pred_model.graph.input:\n                if x.name == curr.inputs[0] and curr.op_type == 'Gather':\n                    return x.type.tensor_type.shape.dim[1].dim_value\n            prev = [x for x in map(OnnxNode, pred_model.graph.node) if x.outputs[0] == curr.inputs[0]]\n            if len(prev) != 1:\n                return\n            prev = prev[0]\n            if prev.op_type == n.op_type:\n                return prev.attrs['hidden_size']\n            if prev.op_type == 'Transpose':\n                for x in pred_model.graph.input:\n                    if x.name == prev.inputs[0]:\n                        return x.type.tensor_type.shape.dim[2].dim_value\n            curr = prev\n\n    @classmethod\n    def _create_rnn(cls, init_model, pred_model, n, opset_version):\n        assert init_model is not None, \"cannot convert RNNs without access to the full model\"\n        assert pred_model is not None, \"cannot convert RNNs without access to the full model\"\n\n        attrs = dict(n.attrs) # make a copy, which is safe to mutate\n        hidden_size = attrs.pop('hidden_size')\n        activation = force_unicode(attrs.pop('activations', ('tanh',))[0])\n        direction = force_unicode(attrs.pop('direction', 'forward'))\n        assert not attrs, \"unsupported RNN attributes: \" + str(attrs.keys())\n        assert direction in ['forward', 'bidirectional'], \"unsupported backwards RNN\"\n\n        input_blob, W, R, B, sequence_lens, initial_h = n.inputs\n\n        if sequence_lens == \"\":\n            sequence_lens = None\n\n        input_size = cls._rnn_shape_inference(init_model, pred_model, n, input_blob, W)\n        if input_size is None:\n            raise RuntimeError(\"best-effort shape inference for RNN input failed\")\n\n        init_net = core.Net(\"init-net\")\n        pred_mh = ModelHelper()\n\n        def make_rnn(direction_offset):\n            name = dummy_name()\n\n            # input and recurrence biases are squashed together in\n            # onnx but not in caffe2\n\n            bias_offset = 2 * direction_offset * hidden_size\n            init_net.Slice(B, name + \"/i2h_b\",\n                           starts=[bias_offset + 0 * hidden_size],\n                           ends  =[bias_offset + 1 * hidden_size])\n            init_net.Slice(B, name + \"/gates_t_b\",\n                           starts=[bias_offset + 1 * hidden_size],\n                           ends  =[bias_offset + 2 * hidden_size])\n\n            weight_offset = direction_offset * hidden_size\n            init_net.Slice(W, name + '/i2h_w',\n                           starts=[weight_offset + 0 * hidden_size, 0],\n                           ends  =[weight_offset + 1 * hidden_size,-1])\n            init_net.Slice(R, name + '/gates_t_w',\n                           starts=[weight_offset + 0 * hidden_size, 0],\n                           ends  =[weight_offset + 1 * hidden_size,-1])\n\n            initial_h_sliced = name + '/initial_h'\n            init_net.Slice(initial_h, initial_h_sliced,\n                           starts=[direction_offset + 0, 0, 0],\n                           ends  =[direction_offset + 1,-1,-1])\n\n            if direction_offset == 1:\n                input = pred_mh.net.ReversePackedSegs(\n                    [input_blob, sequence_lens], name + \"/input-reversed\")\n            else:\n                input = input_blob\n\n            hidden_t_all, hidden_t_last = rnn_cell.BasicRNN(\n                pred_mh,\n                input,\n                sequence_lens,\n                [initial_h_sliced],\n                input_size,\n                hidden_size,\n                name,\n                drop_states=False,\n                forward_only=True,\n                activation=activation\n            )\n\n            if direction_offset == 1:\n                hidden_t_all = pred_mh.net.ReversePackedSegs(\n                    [hidden_t_all, sequence_lens], name + \"/output-reversed\")\n\n            return hidden_t_all, hidden_t_last\n\n        if direction == 'forward':\n            hidden_t_all, hidden_t_last = make_rnn(0)\n\n            # in the forward case, storage is shared between the two\n            # outputs. We need to decouple them so that the\n            # VariableLengthSequencePadding only mutates n.outputs[0]\n            pred_mh.net.Copy(hidden_t_last, n.outputs[1])\n\n            pred_mh.net = pred_mh.net.Clone(\n                \"dummy-clone-net\",\n                blob_remap={ hidden_t_all: n.outputs[0] }\n            )\n        elif direction == 'bidirectional':\n            hidden_t_all_f, hidden_t_last_f = make_rnn(0)\n            hidden_t_all_b, hidden_t_last_b = make_rnn(1)\n            pred_mh.net.Concat([hidden_t_all_f, hidden_t_all_b],\n                               [n.outputs[0], dummy_name()], axis=2)\n            pred_mh.net.Concat([hidden_t_last_f, hidden_t_last_b],\n                               [n.outputs[1], dummy_name()], axis=0)\n\n        if sequence_lens is not None:\n            pred_mh.net.VariableLengthSequencePadding(\n                [n.outputs[0], sequence_lens], [n.outputs[0]])\n\n        return Caffe2Ops(list(pred_mh.Proto().op),\n                         list(init_net.Proto().op),\n                         list(pred_mh.Proto().external_input))\n\n    @classmethod\n    def _create_lstm(cls, init_model, pred_model, n, opset_version):\n        assert init_model is not None, \"cannot convert LSTMs without access to the full model\"\n        assert pred_model is not None, \"cannot convert LSTMs without access to the full model\"\n\n        attrs = dict(n.attrs) # make a copy, which is safe to mutate\n        hidden_size = attrs.pop('hidden_size')\n        direction = force_unicode(attrs.pop('direction', 'forward'))\n        assert not attrs, \"unsupported LSTM attributes: \" + str(attrs.keys())\n        assert direction in ['forward', 'bidirectional'], \"unsupported backwards LSTM\"\n\n        input_blob, W, R, B, sequence_lens, initial_h, initial_c = n.inputs\n\n        if sequence_lens == \"\":\n            sequence_lens = None\n\n        input_size = cls._rnn_shape_inference(init_model, pred_model, n, input_blob, W)\n        if input_size is None:\n            raise RuntimeError(\"best-effort shape inference for LSTM input failed\")\n\n        init_net = core.Net(\"init-net\")\n        pred_mh = ModelHelper()\n\n        def make_lstm(direction_offset):\n            name = dummy_name()\n\n            # input and recurrence biases are squashed together in\n            # onnx but not in caffe2\n\n            bias_offset = 8 * direction_offset * hidden_size\n            Bi = init_net.Slice(B, name + \"_bias_i2h\",\n                                starts=[bias_offset + 0 * hidden_size],\n                                ends  =[bias_offset + 4 * hidden_size])\n            Br = init_net.Slice(B, name + \"_bias_gates\",\n                                starts=[bias_offset + 4 * hidden_size],\n                                ends  =[bias_offset + 8 * hidden_size])\n\n            weight_offset = 4 * direction_offset * hidden_size\n            W_ = init_net.Slice(W, name + '/i2h_w_pre',\n                                starts=[weight_offset + 0 * hidden_size, 0],\n                                ends  =[weight_offset + 4 * hidden_size,-1])\n            R_ = init_net.Slice(R, name + '/gates_t_w_pre',\n                                starts=[weight_offset + 0 * hidden_size, 0],\n                                ends  =[weight_offset + 4 * hidden_size,-1])\n\n            # caffe2 has a different order from onnx. We need to rearrange\n            #   i o f c -> i f o c\n            reforms = ((W_, 'i2h_w',     [(0, -1)]),\n                       (R_, 'gates_t_w', [(0, -1)]),\n                       (Bi, 'i2h_b'    , []),\n                       (Br, 'gates_t_b', []))\n            for name_from, name_to, extra_dims in reforms:\n                xi, xo, xf, xc = [name_from + suffix for suffix in (\"_i\", \"_o\", \"_f\", \"_c\")]\n                for i, x in enumerate([xi, xo, xf, xc]):\n                    dim0 = i * hidden_size, (i+1) * hidden_size\n                    starts, ends = zip(dim0, *extra_dims)\n                    init_net.Slice(name_from, x, starts=starts, ends=ends)\n                init_net.Concat([xi, xf, xo, xc], ['%s/%s' % (name, name_to), dummy_name()], axis=0)\n\n            initial_h_sliced = name + '/initial_h'\n            init_net.Slice(initial_h, initial_h_sliced,\n                           starts=[direction_offset + 0, 0, 0],\n                           ends  =[direction_offset + 1,-1,-1])\n            initial_c_sliced = name + '/initial_c'\n            init_net.Slice(initial_c, initial_c_sliced,\n                           starts=[direction_offset + 0, 0, 0],\n                           ends  =[direction_offset + 1,-1,-1])\n\n            if direction_offset == 1:\n                input = pred_mh.net.ReversePackedSegs(\n                    [input_blob, sequence_lens], name + \"/input-reversed\")\n            else:\n                input = input_blob\n\n            hidden_t_all, hidden_t_last, _, cell_last, params = rnn_cell.LSTM(\n                pred_mh,\n                input,\n                sequence_lens,\n                [initial_h_sliced, initial_c_sliced],\n                input_size,\n                hidden_size,\n                name,\n                drop_states=False,\n                forward_only=True,\n                return_params=True\n            )\n\n            if direction_offset == 1:\n                hidden_t_all = pred_mh.net.ReversePackedSegs(\n                    [hidden_t_all, sequence_lens], name + \"/output-reversed\")\n\n            return hidden_t_all, hidden_t_last, cell_last\n\n        if direction == 'forward':\n            hidden_t_all, hidden_t_last, cell_last = make_lstm(0)\n\n            # in the forward case, storage is shared between the three\n            # outputs. We need to decouple them so that the\n            # VariableLengthSequencePadding only mutates n.outputs[0]\n            pred_mh.net.Copy(hidden_t_last, n.outputs[1])\n            pred_mh.net.Copy(cell_last, n.outputs[2])\n\n            pred_mh.net = pred_mh.net.Clone(\n                \"dummy-clone-net\",\n                blob_remap={ hidden_t_all: n.outputs[0] }\n            )\n        elif direction == 'bidirectional':\n            hidden_t_all_f, hidden_t_last_f, cell_last_f = make_lstm(0)\n            hidden_t_all_b, hidden_t_last_b, cell_last_b = make_lstm(1)\n            pred_mh.net.Concat([hidden_t_all_f, hidden_t_all_b],\n                               [n.outputs[0], dummy_name()], axis=2)\n            pred_mh.net.Concat([hidden_t_last_f, hidden_t_last_b],\n                               [n.outputs[1], dummy_name()], axis=0)\n            pred_mh.net.Concat([cell_last_f, cell_last_b],\n                               [n.outputs[2], dummy_name()], axis=0)\n\n        if sequence_lens is not None:\n            pred_mh.net.VariableLengthSequencePadding(\n                [n.outputs[0], sequence_lens], [n.outputs[0]])\n\n        return Caffe2Ops(list(pred_mh.Proto().op),\n                         list(init_net.Proto().op),\n                         list(pred_mh.Proto().external_input))\n\n    @classmethod\n    def _create_gru(cls, init_model, pred_model, n, opset_version):\n        assert init_model is not None, \"cannot convert GRUs without access to the full model\"\n        assert pred_model is not None, \"cannot convert GRUs without access to the full model\"\n\n        attrs = dict(n.attrs) # make a copy, which is safe to mutate\n        hidden_size = attrs.pop('hidden_size')\n        linear_before_reset = attrs.pop('linear_before_reset', 0)\n        direction = force_unicode(attrs.pop('direction', 'forward'))\n        assert not attrs, \"unsupported GRU attributes: \" + str(attrs.keys())\n        assert direction in ['forward', 'bidirectional'], \"unsupported backwards GRU\"\n\n        input_blob, W, R, B, sequence_lens, initial_h = n.inputs\n\n        if sequence_lens == \"\":\n            sequence_lens = None\n\n        input_size = cls._rnn_shape_inference(init_model, pred_model, n, input_blob, W)\n        if input_size is None:\n            raise RuntimeError(\"best-effort shape inference for GRU input failed\")\n\n        init_net = core.Net(\"init-net\")\n        pred_mh = ModelHelper()\n\n        def make_gru(direction_offset):\n            name = dummy_name()\n\n            # input and recurrence biases are squashed together in\n            # onnx but not in caffe2\n\n            bias_offset = 6 * direction_offset * hidden_size\n            Bi = init_net.Slice(B, name + \"_bias_i2h\",\n                                starts=[bias_offset + 0 * hidden_size],\n                                ends  =[bias_offset + 3 * hidden_size])\n            Br = init_net.Slice(B, name + \"_bias_gates\",\n                                starts=[bias_offset + 3 * hidden_size],\n                                ends  =[bias_offset + 6 * hidden_size])\n\n            weight_offset = 3 * direction_offset * hidden_size\n            W_ = init_net.Slice(W, name + '/i2h_w_pre',\n                                starts=[weight_offset + 0 * hidden_size, 0],\n                                ends  =[weight_offset + 3 * hidden_size,-1])\n            R_ = init_net.Slice(R, name + '/gates_t_w_pre',\n                                starts=[weight_offset + 0 * hidden_size, 0],\n                                ends  =[weight_offset + 3 * hidden_size,-1])\n\n            # caffe2 has a different order from onnx. We need to rearrange\n            #  z r h  -> r z h\n            reforms = ((W_, 'i2h_w',    True,  [(0,-1)]),\n                       (R_, 'gate_t_w', False, [(0,-1)]),\n                       (Bi, 'i2h_b',    True,  []),\n                       (Br, 'gate_t_b', False, []))\n            for name_from, name_to, do_concat, extra_dims in reforms:\n                xz, xr, xh = ['%s/%s_%s' % (name, prefix, name_to) for prefix in ('update', 'reset', 'output')]\n                for i, x in enumerate([xz, xr, xh]):\n                    dim0 = i * hidden_size, (i+1) * hidden_size\n                    starts, ends = zip(dim0, *extra_dims)\n                    init_net.Slice(name_from, x, starts=starts, ends=ends)\n                if do_concat:\n                    init_net.Concat([xr, xz, xh], ['%s/%s' % (name, name_to), dummy_name()], axis=0)\n\n            initial_h_sliced = name + '/initial_h'\n            init_net.Slice(initial_h, initial_h_sliced,\n                           starts=[direction_offset + 0, 0, 0],\n                           ends  =[direction_offset + 1,-1,-1])\n\n            if direction_offset == 1:\n                input = pred_mh.net.ReversePackedSegs(\n                    [input_blob, sequence_lens], name + \"/input-reversed\")\n            else:\n                input = input_blob\n\n            hidden_t_all, hidden_t_last = gru_cell.GRU(\n                pred_mh,\n                input,\n                sequence_lens,\n                [initial_h_sliced],\n                input_size,\n                hidden_size,\n                name,\n                drop_states=False,\n                forward_only=True,\n                linear_before_reset=linear_before_reset\n            )\n\n            if direction_offset == 1:\n                hidden_t_all = pred_mh.net.ReversePackedSegs(\n                    [hidden_t_all, sequence_lens], name + \"/output-reversed\")\n\n            return hidden_t_all, hidden_t_last\n\n        if direction == 'forward':\n            hidden_t_all, hidden_t_last = make_gru(0)\n\n            # in the forward case, storage is shared between the two\n            # outputs. We need to decouple them so that the\n            # VariableLengthSequencePadding only mutates n.outputs[0]\n            pred_mh.net.Copy(hidden_t_last, n.outputs[1])\n\n            pred_mh.net = pred_mh.net.Clone(\n                \"dummy-clone-net\",\n                blob_remap={ hidden_t_all: n.outputs[0] }\n            )\n        elif direction == 'bidirectional':\n            hidden_t_all_f, hidden_t_last_f = make_gru(0)\n            hidden_t_all_b, hidden_t_last_b = make_gru(1)\n            pred_mh.net.Concat([hidden_t_all_f, hidden_t_all_b],\n                               [n.outputs[0], dummy_name()], axis=2)\n            pred_mh.net.Concat([hidden_t_last_f, hidden_t_last_b],\n                               [n.outputs[1], dummy_name()], axis=0)\n\n        if sequence_lens is not None:\n            pred_mh.net.VariableLengthSequencePadding(\n                [n.outputs[0], sequence_lens], [n.outputs[0]])\n\n        return Caffe2Ops(list(pred_mh.Proto().op),\n                         list(init_net.Proto().op),\n                         list(pred_mh.Proto().external_input))\n\n    @classmethod\n    def _create_pad(cls, init_model, pred_model, n, opset_version):\n        if opset_version < 2:\n            pads = n.attrs['paddings']\n        else:\n            pads = n.attrs['pads']\n        if not (len(pads) == 8 and\n                # first two dim is for batch and channel\n                set(pads[:2] + pads[4:6]) == {0}):\n            raise ValueError('Caffe2 only supports padding 2D Tensor, whereas padding is ' + str(pads))\n        # Guard the invalid (negative) pads attribute.\n        if min(pads) < 0:\n            raise ValueError('ONNX does not support negative pads in Pad, but get {}.'.format(pads))\n        pads[:] = pads[2:4] + pads[6:8]\n        return cls._common_onnx_node_to_caffe2_op(init_model, pred_model, n, opset_version)\n\n    @classmethod\n    def _create_concat(cls, init_model, pred_model, n, opset_version):\n        # TODO: Caffe2 Concat has an extra output. It should be only\n        # used when doing training, so we should change Caffe2 to allow\n        # 1 output.\n        op = cls._common_onnx_node_to_caffe2_op(init_model, pred_model, n, opset_version)\n        assert len(op.output) == 1\n        op.output.append(dummy_name())\n        return op\n\n    @classmethod\n    def _create_slice(cls, init_model, pred_model, n, opset_version):\n        op = cls._common_onnx_node_to_caffe2_op(init_model, pred_model, n, opset_version)\n        args = {arg.name: arg for arg in op.arg}\n        starts_vals = np.array(\n            args.pop('starts').ints, dtype=np.int64).tolist()\n        ends_vals = np.array(\n            [i - 1 if i < 0 else i for i in args.pop('ends').ints],\n            dtype=np.int64).tolist()\n        if 'axes' in args:\n            axes_vals = np.array(\n                args.pop('axes').ints, dtype=np.int32).tolist()\n        else:\n            ndims = len(starts_vals)\n            axes_vals = np.array(range(ndims), dtype=np.int32).tolist()\n\n        data, = op.input\n        ops = []\n\n        shape_tensor = dummy_name()\n        ops.append(core.CreateOperator(\n            'Shape',\n            [data],\n            [shape_tensor]\n        ))\n\n        axes_tensor = dummy_name()\n        ops.extend([\n            core.CreateOperator(\n                'GivenTensorIntFill',\n                [],\n                [axes_tensor],\n                shape=[len(axes_vals)],\n                values=axes_vals,\n            ),\n        ])\n\n        starts_vals_tensor = dummy_name()\n        starts_tensor = dummy_name()\n        casted_starts_tensor = dummy_name()\n        ops.extend([\n            core.CreateOperator(\n                'GivenTensorInt64Fill',\n                [],\n                [starts_vals_tensor],\n                shape=[len(starts_vals)],\n                values=starts_vals,\n            ),\n            core.CreateOperator(\n                'ConstantFill',\n                [shape_tensor],\n                [starts_tensor],\n                dtype=caffe2_pb2.TensorProto.INT64,\n                value=0,\n            ),\n            core.CreateOperator(\n                'ScatterAssign',\n                [starts_tensor, axes_tensor, starts_vals_tensor],\n                [starts_tensor],\n            ),\n            # Slice only accepts starts as int\n            core.CreateOperator(\n                'Cast',\n                [starts_tensor],\n                [casted_starts_tensor],\n                to=caffe2_pb2.TensorProto.INT32,\n            ),\n        ])\n\n        ends_vals_tensor = dummy_name()\n        ends_tensor = dummy_name()\n        casted_ends_tensor = dummy_name()\n        ops.extend([\n            core.CreateOperator(\n                'GivenTensorInt64Fill',\n                [],\n                [ends_vals_tensor],\n                shape=[len(ends_vals)],\n                values=ends_vals,\n            ),\n            core.CreateOperator(\n                'ConstantFill',\n                [shape_tensor],\n                [ends_tensor],\n                dtype=caffe2_pb2.TensorProto.INT64,\n                value=-1,\n            ),\n            core.CreateOperator(\n                'ScatterAssign',\n                [ends_tensor, axes_tensor, ends_vals_tensor],\n                [ends_tensor],\n            ),\n            # Slice only accepts ends as int\n            core.CreateOperator(\n                'Cast',\n                [ends_tensor],\n                [casted_ends_tensor],\n                to=caffe2_pb2.TensorProto.INT32,\n            ),\n        ])\n\n        op.input[:] = [data, casted_starts_tensor, casted_ends_tensor]\n        del op.arg[:]\n        op.arg.extend(args.values())\n        ops.append(op)\n\n        return ops\n\n    # Note [Caffe2 ConvPoolOpBase]\n    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n    # To understand what is going on here, we have to talk a little bit about\n    # Caffe2's internals.\n    #\n    # First, it's important to know that all of Caffe2's pooling and convolution\n    # operators inherit from \"ConvPoolOpBase\", which is an abstract class that\n    # defines all of the attributes (kernels, dilations, strides, etc) which one\n    # sees on these operators.  Unfortunately, Caffe2's documentation generator\n    # doesn't know how to handle cases like this, so for example, if you look at\n    # the docs for MaxPool at <https://caffe2.ai/docs/operators-catalogue.html#maxpool>\n    # you won't see any of the attributes.  You have to go source diving to\n    # find the information; in particular, you want to look at:\n    # https://github.com/caffe2/caffe2/blob/master/caffe2/operators/conv_pool_op_base.h\n    # This class handles *global* pooling as well.\n    #\n    # Second, it's important to know what Caffe2 expects for padding, which can\n    # be somewhat difficult to understand from the code because Caffe2 handles\n    # both singular/pluralized spellings of padding, and there is also legacy\n    # padding business.  The short version of the story is that, for NON-legacy\n    # padding (which is what we want to output), padding is expected to be\n    # *twice* the size of kernels.  So if you have a 2D convolution, Caffe2\n    # will accept two values in 'kernels', but FOUR values in 'pads';\n    # furthermore, this is *mandatory.*\n    #\n    # Finally, ConvPoolOpBase is not the only class of it's kind; there is\n    # also ConvTransposeUnpoolBase, which backs ConvTranspose.  So don't\n    # be tricked by the fact that Conv and ConvTranspose have similar\n    # parameters; they exercise different codepaths and need to be handled\n    # differently.\n\n    @classmethod\n    def _create_conv_pool_op_base(cls, init_model, pred_model, n, opset_version):\n        if n.op_type.startswith('Global'):\n            n.attrs['global_pooling'] = 1\n\n        try:\n            kernels = n.attrs['kernel_shape']\n            pads = n.attrs['pads']\n        except KeyError:\n            pass\n        else:\n            if len(kernels) == len(pads):\n                # Caffe2 requires pads to be twice the size of kernels.\n                n.attrs['pads'] = pads * 2\n\n        return cls._common_onnx_node_to_caffe2_op(init_model, pred_model, n, opset_version)\n\n    @classmethod\n    def _create_reshape(cls, init_model, pred_model, n, opset_version):\n        c2_op = cls._common_onnx_node_to_caffe2_op(init_model, pred_model, n, opset_version)\n        # Caffe2 has an extra output\n        c2_op.output.append(dummy_name())\n        return c2_op\n\n    @classmethod\n    def _create_sqrt(cls, init_model, pred_model, n, opset_version):\n        (X,) = n.inputs\n        (Y,) = n.outputs\n        return core.CreateOperator(\n            'Pow',\n            [X],\n            [Y],\n            exponent=0.5,\n        )\n\n    @classmethod\n    def _create_reciprocal(cls, init_model, pred_model, n, opset_version):\n        (X,) = n.inputs\n        (Y,) = n.outputs\n        return core.CreateOperator(\n            'Pow',\n            [X],\n            [Y],\n            exponent=-1.0,\n        )\n\n    @classmethod\n    def _create_matmul(cls, init_model, pred_model, n, opset_version):\n        op = cls._common_onnx_node_to_caffe2_op(init_model, pred_model, n, opset_version)\n        broadcast_arg = op.arg.add()\n        broadcast_arg.name = \"broadcast\"\n        broadcast_arg.i = 1\n        return op\n\n    @classmethod\n    def _direct_initialize_parameters(cls, initializer, ws, device_option):\n        for tp in initializer:\n            ws.FeedBlob(tp.name, onnx.numpy_helper.to_array(tp), device_option)\n\n    @classmethod\n    def _direct_initialize_inputs(cls, inputs, initialized, ws, device_option):\n        for value_info in inputs:\n            if value_info.name in initialized:\n                continue\n            shape = list(d.dim_value for d in value_info.type.tensor_type.shape.dim)\n            ws.FeedBlob(value_info.name, np.ones(shape), device_option)\n\n    @staticmethod\n    def optimize_onnx(input, init=False, predict=False):\n        passes =  ['fuse_consecutive_transposes',\n                   'eliminate_nop_transpose',\n                   'fuse_transpose_into_gemm']\n        if init:\n            passes.append('split_init')\n        if predict:\n            passes.append('split_predict')\n        out = onnx.optimizer.optimize(input, passes)\n        return out\n\n    @classmethod\n    def prepare(cls, model, device='CPU', **kwargs):\n        '''\n        For Onnx Caffe2Backend, we require that init_graph don't initialize the actual input of the predict_graph,\n\n        for example, if \"img\" is the input blob for the predict_net, we require that in init_graph and in\n        initializer of the predict_graph, \"img\" is not initalized. We don't have a check for this, since\n        there is no way we can know which blob is the input of the predict_graph.\n        '''\n        super(Caffe2Backend, cls).prepare(model, device, **kwargs)\n        opset_version = None\n        for imp in model.opset_import:\n            if not imp.HasField(\"domain\") or imp.domain == \"\":\n                opset_version = imp.version\n                if imp.version > cls._known_opset_version:\n                    warnings.warn(\"This version of onnx-caffe2 targets ONNX operator set version {}, but the model we are trying to import uses version {}.  We will try to import it anyway, but if the model uses operators which had BC-breaking changes in the intervening versions, import will fail.\".format(cls._known_opset_version, imp.version))\n            else:\n                warnings.warn(\"Unrecognized operator set {}\".format(imp.domain))\n        if opset_version is None:\n            if model.ir_version >= 0x00000003:\n                raise RuntimeError(\"Model with IR version >= 3 did not specify ONNX operator set version (onnx-caffe2 requires it)\")\n            else:\n                opset_version = 1\n\n        # Check whether we have RNN related ops\n        pred_model = ModelProto()\n        pred_model.ParseFromString(cls.optimize_onnx(model.SerializeToString(), predict=True))\n        cls._inplace_rewrite(pred_model.graph)\n        rnn_nodes = []\n        for node in pred_model.graph.node:\n            if node.op_type in {'LSTM', 'GRU', 'RNN'}:\n                rnn_nodes.append(node)\n\n        # Build the C++ backend\n        # TODO: build a predictor that supports GPU\n        #       And for RNN nets, we need to avoid adding init_net\n        if device == 'CPU' and not rnn_nodes:\n            c2_rnn_ops = []\n            if rnn_nodes:\n                init_model = ModelProto()\n                init_model.ParseFromString(cls.optimize_onnx(model.SerializeToString(), init=True))\n                cls._inplace_rewrite(init_model.graph)\n                for node in rnn_nodes:\n                    c2ops = cls._onnx_node_to_caffe2_op(\n                        init_model, pred_model, node, opset_version)\n                    init_ops = [x.SerializeToString() for x in c2ops.init_ops]\n                    ops = [x.SerializeToString() for x in c2ops.ops]\n                    external_inputs = c2ops.interface_blobs\n                    c2_rnn_ops.append(C.Caffe2Ops(init_ops, ops, external_inputs))\n                del init_model\n\n            cbackend = C.Caffe2Backend()\n            rep = cbackend.prepare(model.SerializeToString(), device, c2_rnn_ops)\n            # For testing\n            # Dump the net descritpions to file for comparison with the Python ones\n            if \"ONNX_CAFFE2_DEBUG\" in os.environ:\n                pred_net_str = rep.pred_net()\n                pn = caffe2_pb2.NetDef()\n                pn.ParseFromString(pred_net_str)\n                init_net_str = rep.init_net()\n                inn = caffe2_pb2.NetDef()\n                inn.ParseFromString(init_net_str)\n                with open(\"cpp.txt\", \"w\") as f:\n                    f.write(\"pred_net: \\n{}\".format(pn))\n\n            rep_wrapper = Caffe2CppRep(rep)\n            return rep_wrapper\n        else:\n            ws = Workspace()\n            device_option = get_device_option(Device(device))\n\n            # Directly load initializer data into blobs in workspace\n            cls._direct_initialize_parameters(\n                model.graph.initializer,\n                ws,\n                device_option,\n            )\n\n            initialized = {init.name for init in model.graph.initializer}\n\n            cls._direct_initialize_inputs(\n                model.graph.input,\n                initialized,\n                ws,\n                device_option,\n            )\n\n            uninitialized = [value_info.name for value_info in model.graph.input if value_info.name not in initialized]\n\n            init_net, predict_net = cls._onnx_model_to_caffe2_net(model, device, opset_version, False)\n            if \"ONNX_CAFFE2_DEBUG\" in os.environ:\n                with open(\"python.txt\", \"w\") as f:\n                    f.write(\"pred_net: \\n{}\".format(predict_net))\n            retval = Caffe2Rep(init_net, predict_net, ws, uninitialized)\n            return retval\n\n\n    @classmethod\n    # TODO: This method needs a refactor for clarity\n    def _onnx_node_to_caffe2_op(cls, init_model, pred_model, node_def, opset_version):\n        if node_def.op_type in cls._special_operators:\n            translator = getattr(cls, cls._special_operators[node_def.op_type])\n        else:\n            translator = cls._common_onnx_node_to_caffe2_op\n        ops = translator(init_model, pred_model, OnnxNode(node_def), opset_version)\n        if isinstance(ops, Caffe2Ops):\n            return ops\n        if not isinstance(ops, collections.Iterable):\n            ops = [ops]\n        return Caffe2Ops(ops, [], [])\n\n    @classmethod\n    def _common_onnx_node_to_caffe2_op(cls, init_model, pred_model, onnx_node, opset_version):\n        \"\"\"\n        This translator performs the basic translation of ONNX nodes into\n        Caffe2 operators.  Besides doing a straightforward marshalling from\n        one format to another, it also does these extra things:\n\n          - Renames operators based on '_renamed_operators'\n          - Renames attributes based on '_global_renamed_attrs' and\n            '_per_op_renamed_attrs'\n\n        If you're writing a custom translator, consider calling this first,\n        and then fixing things up further.\n        \"\"\"\n        c2_op = caffe2_pb2.OperatorDef()\n\n        c2_op.input.extend(onnx_node.inputs)\n        c2_op.output.extend(onnx_node.outputs)\n        c2_op.name = onnx_node.name\n\n        onnx_op_type = onnx_node.op_type\n        broken_version = cls._broken_operators.get(onnx_op_type, float('Inf'))\n        if broken_version <= opset_version:\n            raise ValueError(\n                \"Don't know how to translate op {} in ONNX operator set v{} (I only support prior to v{})\".format(onnx_op_type, opset_version, broken_version))\n        c2_op.type = cls._renamed_operators.get(onnx_op_type, onnx_op_type)\n        if not core.IsOperator(c2_op.type):\n            raise ValueError(\n                \"Don't know how to translate op {}\".format(onnx_op_type))\n\n        def kmap(k):\n            if (onnx_op_type in cls._per_op_renamed_attrs and\n                    k in cls._per_op_renamed_attrs[onnx_op_type]):\n                return cls._per_op_renamed_attrs[onnx_op_type][k]\n            if k in cls._global_renamed_attrs:\n                return cls._global_renamed_attrs[k]\n            return k\n        c2_op.arg.extend(onnx_node.attrs.caffe2(kmap=kmap))\n\n        return c2_op\n\n\n    @classmethod\n    def _inplace_rewrite(cls, graph_or_nodes):\n        '''\n        currently we use this to translate ONNX-style\n        consumed_input annotations to Caffe2-style in place\n        updates (use same input and output names).\n        '''\n        is_graph = isinstance(graph_or_nodes, GraphProto)\n        if is_graph:\n            nodes = graph_or_nodes.node\n        else:\n            nodes = graph_or_nodes\n\n        renamed = {}\n\n        for node in nodes:\n            node.input[:] = [renamed.get(input_name, input_name)\n                             for input_name in node.input]\n            consumed_inputs = OnnxNode(node).consumed_inputs or []\n            output_idxes = set(range(len(node.output)))\n            schema = onnx.defs.get_schema(node.op_type)\n            for i, consumed in enumerate(consumed_inputs):\n                if not consumed:\n                    continue\n                _, output_idx = schema.consumed(i)\n                # consumed outputs are not always present\n                # for instance batch norm in test mode\n                # does not return the consumed inputs\n                if output_idx < len(node.output):\n                    output_idxes.remove(output_idx)\n                    old_val = node.output[output_idx]\n                    new_val = node.input[i]\n                    node.output[output_idx] = new_val\n                    renamed[old_val] = new_val\n            for idx in output_idxes:\n                name = node.output[idx]\n                node.output[idx] = renamed.get(name, name)\n        if is_graph:\n            for output in graph_or_nodes.output:\n                output.name = renamed.get(output.name, output.name)\n\n    @staticmethod\n    def _all_names_in_graph(graph):\n        if graph is None:\n            return set()\n\n        names = set()\n        names.update(value_info.name for value_info in graph.input)\n        names.update(value_info.name for value_info in graph.output)\n        for node in graph.node:\n            names.update(node.input)\n            names.update(node.output)\n        return names\n\n    @classmethod\n    def _onnx_model_to_caffe2_net(cls, onnx_model, device, opset_version, include_initializers):\n        device_option = get_device_option(Device(device))\n\n        init_model = ModelProto()\n        init_model.ParseFromString(cls.optimize_onnx(onnx_model.SerializeToString(), init=True))\n        cls._inplace_rewrite(init_model.graph)\n\n        pred_model = ModelProto()\n        pred_model.ParseFromString(cls.optimize_onnx(onnx_model.SerializeToString(), predict=True))\n        cls._inplace_rewrite(pred_model.graph)\n\n        init_net = caffe2_pb2.NetDef()\n        pred_net = caffe2_pb2.NetDef()\n\n        init_net.name = onnx_model.graph.name + '_init'\n        pred_net.name = onnx_model.graph.name + '_predict'\n\n        if include_initializers:\n            init_net.op.extend(cls._create_tensor_filling_op(tp) for tp in onnx_model.graph.initializer)\n\n        dummy_name(cls._all_names_in_graph(init_model.graph) | cls._all_names_in_graph(pred_model.graph))\n\n        success = True\n        for net, model in ( (init_net, init_model), (pred_net, pred_model) ):\n            net.device_option.CopyFrom(device_option)\n            for node in model.graph.node:\n                try:\n                    c2ops = cls._onnx_node_to_caffe2_op(\n                        init_model, pred_model, node, opset_version)\n                except Exception as e:\n                    success = False\n                    print('ONNX FATAL:', e)\n                    continue\n                (init_net if include_initializers else net).op.extend(c2ops.init_ops)\n                net.op.extend(c2ops.ops)\n                net.external_input.extend(c2ops.interface_blobs)\n            net.external_output.extend(\n                value_info.name for value_info in model.graph.output)\n            net.external_input.extend(\n                value_info.name for value_info in model.graph.input)\n\n        if not success:\n            raise RuntimeError('ONNX conversion failed')\n\n        return init_net, pred_net\n\n    # wrapper for backwards compatability\n    @classmethod\n    def onnx_graph_to_caffe2_net(cls, model, device=\"CPU\", opset_version=_known_opset_version):\n        return cls._onnx_model_to_caffe2_net(model, device=device, opset_version=opset_version, include_initializers=True)\n\n    @classmethod\n    def supports_device(cls, device_str):\n        device = Device(device_str)\n        if device.type == DeviceType.CPU:\n            return True\n        elif device.type == DeviceType.CUDA:\n            return workspace.has_gpu_support\n        return False\n\n\nprepare = Caffe2Backend.prepare\n\nrun_node = Caffe2Backend.run_node\n\nrun_model = Caffe2Backend.run_model\n\nsupports_device = Caffe2Backend.supports_device  # noqa\n"
  },
  {
    "path": "caffe2/python/onnx/backend_cpp_rep.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.backend_rep_cpp\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom onnx.backend.base import BackendRep, namedtupledict\n\n# This is a wrapper around C++ Caffe2BackendRep,\n# mainly to handle the different input and output types for convenience of Python\nclass Caffe2CppRep(BackendRep):\n    def __init__(self, cpp_rep):\n        super(Caffe2CppRep, self).__init__()\n        self.__core = cpp_rep\n        self.__external_outputs = cpp_rep.external_outputs()\n        self.__external_inputs = cpp_rep.external_inputs()\n        self.__uninitialized_inputs = cpp_rep.uninitialized_inputs()\n\n    def run(self, inputs):\n        output_values = None\n        if isinstance(inputs, dict):\n            output_values = self.__core.run(inputs)\n        elif isinstance(inputs, list) or isinstance(inputs, tuple):\n            if len(inputs) != len(self.__uninitialized_inputs):\n                raise RuntimeError('Expected {} values for uninitialized '\n                                   'graph inputs ({}), but got {}.'.format(\n                                        len(self.__uninitialized_inputs),\n                                        ', '.join(self.__uninitialized_inputs),\n                                        len(inputs)))\n            input_map = {}\n            for k, v in zip(self.__uninitialized_inputs, inputs):\n                input_map[k] = v\n            output_values = self.__core.run(input_map)\n        else:\n            # single input\n            output_values = self.__core.run([inputs])\n        return namedtupledict('Outputs', self.__external_outputs)(*output_values)\n\n"
  },
  {
    "path": "caffe2/python/onnx/backend_rep.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.backend_rep\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nfrom caffe2.proto import caffe2_pb2\nfrom onnx.backend.base import BackendRep, namedtupledict\n\nclass Caffe2Rep(BackendRep):\n    def __init__(self, init_net, predict_net, workspace, uninitialized):\n        super(Caffe2Rep, self).__init__()\n        self.init_net = init_net\n        self.predict_net = predict_net\n        self.workspace = workspace\n        # The list of uninitialized external_inputs in workspace, we need this to\n        # pair the name with given sequence inputs.\n        self.uninitialized = uninitialized\n        self.nets_created = False\n        self.ran_init_net = False\n\n    @property\n    def _name_scope(self):\n        if self.predict_net.device_option.device_type == caffe2_pb2.CUDA:\n            return 'gpu_{}'.format(self.predict_net.device_option.cuda_gpu_id)\n        return ''\n\n    def run(self, inputs, **kwargs):\n        super(Caffe2Rep, self).run(inputs, **kwargs)\n        with self.workspace:\n            with core.DeviceScope(self.predict_net.device_option):\n                if isinstance(inputs, dict):\n                    with core.NameScope(self._name_scope):\n                        for key, value in inputs.items():\n                            workspace.FeedBlob(key, value)\n                elif isinstance(inputs, list) or isinstance(inputs, tuple):\n                    if len(self.uninitialized) != len(inputs):\n                        raise RuntimeError('Expected {} values for uninitialized '\n                                           'graph inputs ({}), but got {}.'.format(\n                                               len(self.uninitialized),\n                                               ', '.join(self.uninitialized),\n                                               len(inputs)))\n                    for i, value in enumerate(inputs):\n                        # namescope already baked into protobuf\n                        workspace.FeedBlob(self.uninitialized[i], value)\n                else:\n                    # single input\n                    workspace.FeedBlob(self.uninitialized[0], inputs)\n                if not self.nets_created:\n                    workspace.CreateNet(self.init_net)\n                    workspace.CreateNet(self.predict_net)\n                    self.nets_created = True\n                if not self.ran_init_net:\n                    workspace.RunNet(self.init_net.name)\n                    self.ran_init_net = True\n                workspace.RunNet(self.predict_net.name)\n            output_values = [workspace.FetchBlob(name)\n                             for name in self.predict_net.external_output]\n            return namedtupledict('Outputs',\n                                  self.predict_net.external_output)(*output_values)\n"
  },
  {
    "path": "caffe2/python/onnx/bin/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/python/onnx/bin/conversion.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.bin.conversion\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport json\n\nfrom caffe2.proto import caffe2_pb2\nimport click\nimport numpy as np\nfrom onnx import checker, ModelProto\n\nfrom caffe2.python.onnx.backend import Caffe2Backend as c2\nimport caffe2.python.onnx.frontend as c2_onnx\n\n\n@click.command(\n    help='convert caffe2 net to onnx model',\n    context_settings={\n        'help_option_names': ['-h', '--help']\n    }\n)\n@click.argument('caffe2_net', type=click.File('rb'))\n@click.option('--caffe2-net-name',\n              type=str,\n              help=\"Name of the caffe2 net\")\n@click.option('--caffe2-init-net',\n              type=click.File('rb'),\n              help=\"Path of the caffe2 init net pb file\")\n@click.option('--value-info',\n              type=str,\n              help='A json string providing the '\n              'type and shape information of the inputs')\n@click.option('-o', '--output', required=True,\n              type=click.File('wb'),\n              help='Output path for the onnx model pb file')\ndef caffe2_to_onnx(caffe2_net,\n                   caffe2_net_name,\n                   caffe2_init_net,\n                   value_info,\n                   output):\n    c2_net_proto = caffe2_pb2.NetDef()\n    c2_net_proto.ParseFromString(caffe2_net.read())\n    if not c2_net_proto.name and not caffe2_net_name:\n        raise click.BadParameter(\n            'The input caffe2 net does not have name, '\n            '--caffe2-net-name must be provided')\n    c2_net_proto.name = caffe2_net_name or c2_net_proto.name\n    if caffe2_init_net:\n        c2_init_net_proto = caffe2_pb2.NetDef()\n        c2_init_net_proto.ParseFromString(caffe2_init_net.read())\n        c2_init_net_proto.name = '{}_init'.format(caffe2_net_name)\n    else:\n        c2_init_net_proto = None\n\n    if value_info:\n        value_info = json.loads(value_info)\n\n    onnx_model = c2_onnx.caffe2_net_to_onnx_model(\n        predict_net=c2_net_proto,\n        init_net=c2_init_net_proto,\n        value_info=value_info)\n\n    output.write(onnx_model.SerializeToString())\n\n\n@click.command(\n    help='convert onnx model to caffe2 net',\n    context_settings={\n        'help_option_names': ['-h', '--help']\n    }\n)\n@click.argument('onnx_model', type=click.File('rb'))\n@click.option('-o', '--output', required=True,\n              type=click.File('wb'),\n              help='Output path for the caffe2 net file')\n@click.option('--init-net-output',\n              required=True,\n              type=click.File('wb'),\n              help='Output path for the caffe2 init net file')\ndef onnx_to_caffe2(onnx_model, output, init_net_output):\n    onnx_model_proto = ModelProto()\n    onnx_model_proto.ParseFromString(onnx_model.read())\n\n    init_net, predict_net = c2.onnx_graph_to_caffe2_net(onnx_model_proto)\n    init_net_output.write(init_net.SerializeToString())\n    output.write(predict_net.SerializeToString())\n"
  },
  {
    "path": "caffe2/python/onnx/error.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.error\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nclass BaseException(Exception): pass\nclass Unsupported(BaseException): pass\n"
  },
  {
    "path": "caffe2/python/onnx/frontend.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.frontend\n\n\"\"\"Caffe2 Protobuf to ONNX converter\n\nTo run this, you will need to have Caffe2 installed as well.\n\"\"\"\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport itertools\nimport collections\nimport logging\nimport re\n\nfrom caffe2.python import core as caffe2_core\nfrom caffe2.proto import caffe2_legacy_pb2\nfrom enum import Enum\nfrom onnx import (defs, checker, helper, numpy_helper, mapping,\n                  ModelProto, GraphProto, NodeProto, AttributeProto, TensorProto, OperatorSetIdProto)\nfrom onnx.helper import make_tensor, make_tensor_value_info, make_attribute, make_model\nimport numpy as np\n\nfrom caffe2.python.onnx.helper import c2_native_run_net, dummy_name\nfrom caffe2.python.onnx.error import Unsupported\n\nlogging.basicConfig(level=logging.INFO)\nlogger = logging.getLogger(__name__)\n\n\nclass Caffe2Frontend(object):\n    # This number controls the semantics of the operators we target.  Whenever\n    # ONNX makes a BC breaking change to semantics of operators, having this set\n    # to an accurate number will prevent our models form exporting.  However,\n    # we should strive to keep this up-to-date as much as possible.\n    target_opset_version = 5\n\n    _renamed_operators = {\n        'SpatialBN': 'BatchNormalization',\n        'Conv1D': 'Conv',\n        'Conv2D': 'Conv',\n        'Conv3D': 'Conv',\n        'ConvTranspose1D': 'ConvTranspose',\n        'ConvTranspose2D': 'ConvTranspose',\n        'ConvTranspose3D': 'ConvTranspose',\n        'MaxPool1D': 'MaxPool',\n        'MaxPool2D': 'MaxPool',\n        'MaxPool3D': 'MaxPool',\n        'AveragePool1D': 'AveragePool',\n        'AveragePool2D': 'AveragePool',\n        'AveragePool3D': 'AveragePool',\n    }\n\n    # caffe2 arguments that are completely removed in onnx\n    _blacklist_caffe2_args = {\n        'order': {b'NCHW'},\n        'cudnn_exhaustive_search': {0, 1},\n        'use_cudnn': {0, 1},\n    }\n\n    _global_renamed_args = {\n        'kernels': 'kernel_shape',\n    }\n\n    _per_op_renamed_args = {\n        'Squeeze': {'dims': 'axes'},\n        'Transpose': {'axes': 'perm'},\n    }\n\n    _special_operators = {\n        'Conv': '_create_conv_pool_op',\n        'ConvTranspose': '_create_conv_pool_op',\n        'ChannelShuffle': '_create_channel_shuffle',\n        'MaxPool': '_create_conv_pool_op',\n        'AveragePool': '_create_conv_pool_op',\n        'Concat': '_create_concat',\n        'FC': '_create_gemm',\n        'LRN': '_create_lrn',\n        'Slice': '_create_slice',\n        'Reshape': '_create_reshape',\n    }\n\n    @classmethod\n    def _common_caffe2_arg_to_onnx_attr(cls, op_def, arg):\n        # name\n        op_type = op_def.type\n        if op_type in cls._per_op_renamed_args:\n            name = cls._per_op_renamed_args[op_type].get(\n                arg.name, arg.name)\n        else:\n            name = cls._global_renamed_args.get(arg.name, arg.name)\n\n        # value\n        if arg.HasField('f'):\n            value = arg.f\n        elif arg.HasField('i'):\n            value = arg.i\n        elif arg.HasField('s'):\n            value = arg.s\n        elif arg.floats:\n            value = arg.floats\n        elif arg.ints:\n            value = arg.ints\n        elif arg.strings:\n            value = arg.strings\n        else:\n            raise ValueError('Could not find data field in arg: {}'.format(arg))\n\n        if name in cls._blacklist_caffe2_args:\n            assert value in cls._blacklist_caffe2_args[arg.name]\n            return None\n\n        return helper.make_attribute(name, value)\n\n    @classmethod\n    def caffe2_arg_to_onnx_attr(cls, op_def, arg):\n        return cls._common_caffe2_arg_to_onnx_attr(op_def, arg)\n\n    @classmethod\n    def _common_caffe2_op_to_onnx_node(cls, op_def, shapes):\n        node_def = NodeProto()\n        node_def.name = op_def.name\n\n        node_def.op_type = cls._renamed_operators.get(op_def.type, op_def.type)\n\n        node_def.input.extend(op_def.input)\n        node_def.output.extend(op_def.output)\n\n        attrs = filter(None, [cls.caffe2_arg_to_onnx_attr(op_def, arg)\n                              for arg in op_def.arg])\n        node_def.attribute.extend(attrs)\n\n        return node_def\n\n    @classmethod\n    def _create_concat(cls, op_def, shapes):\n        node = cls._common_caffe2_op_to_onnx_node(op_def, shapes)\n        if len(node.output) == 2:\n            del node.output[1]\n        explicit_axis = any(arg.name == 'axis' for arg in op_def.arg)\n        if not explicit_axis:\n            node.attribute.extend([helper.make_attribute('axis', 1)])\n        return node\n\n    @classmethod\n    def _create_shape_tensor(cls, shape):\n        return make_tensor(name=dummy_name(),\n                           data_type=TensorProto.INT64,\n                           dims=[len(shape)],\n                           vals=np.asarray(shape, dtype=np.int64).tobytes(),\n                           raw=True)\n\n    @classmethod\n    def _create_reshape(cls, op_def, shapes):\n        node = cls._common_caffe2_op_to_onnx_node(op_def, shapes)\n        const_tensors = []\n\n        attrs = {attr.name: attr for attr in node.attribute}\n        if 'shape' in attrs:\n            shape = attrs.pop('shape').ints\n            shape_tensor = cls._create_shape_tensor(shape)\n            node.input.append(shape_tensor.name)\n            const_tensors.append(shape_tensor)\n        node.attribute[:] = attrs.values()\n\n        if len(node.output) == 2:\n            del node.output[1]\n        return node, const_tensors\n\n    @classmethod\n    def _create_conv_pool_op(cls, op_def, shapes):\n        node = cls._common_caffe2_op_to_onnx_node(op_def, shapes)\n\n        if node.op_type in ['MaxPool', 'AveragePool']:\n            for i, attr in enumerate(node.attribute):\n                if attr.name == 'global_pooling' and attr.i:\n                    node.op_type = 'Global{}'.format(node.op_type)\n                    del node.attribute[i]\n                    break\n\n        attrs = {attr.name: attr for attr in node.attribute}\n\n        def apply_trans(k, dim=2, ks=None):\n            ks = ks or (k + 's')\n            if dim == 2:\n                k_h, k_w = k + '_h', k + '_w'\n            else:\n                k_t, k_l, k_b, k_r = k + '_t', k + '_l', k + '_b', k + '_r'\n\n            vals = None\n            if (dim == 2 and k_h in attrs and k_w in attrs):\n                vals = [attrs[k_h].i, attrs[k_w].i]\n                del attrs[k_h]\n                del attrs[k_w]\n            elif (dim == 4 and\n                  k_t in attrs and k_l in attrs and k_b in attrs and k_r in attrs):\n                vals = [attrs[k_t].i,\n                        attrs[k_l].i,\n                        attrs[k_b].i,\n                        attrs[k_r].i]\n                del attrs[k_t]\n                del attrs[k_l]\n                del attrs[k_b]\n                del attrs[k_r]\n            elif k in attrs:\n                vals = [attrs[k].i] * dim\n                del attrs[k]\n\n            if vals and not node.op_type.startswith('Global'):\n                attrs[ks] = helper.make_attribute(ks, vals)\n\n        apply_trans('kernel', ks='kernel_shape')\n        apply_trans('stride')\n        apply_trans('dilation')\n        apply_trans('adj')\n        apply_trans('pad', dim=4)\n\n        legacy_pad_attr = attrs.pop('legacy_pad', None)\n        if legacy_pad_attr:\n            assert node.op_type.endswith(\"Pool\")\n            assert not node.op_type.startswith(\"Global\")\n            input_size = shapes[node.input[0]]\n            output_size = shapes[node.output[0]]\n            assert len(output_size) == 4\n            if legacy_pad_attr.i == caffe2_legacy_pb2.NOTSET:\n                pass\n            elif legacy_pad_attr.i == caffe2_legacy_pb2.VALID:\n                assert not 'pads' in attrs\n                new_attr = make_attribute('auto_pad', 'VALID')\n                attrs[new_attr.name] = new_attr\n            elif legacy_pad_attr.i == caffe2_legacy_pb2.SAME:\n                assert not 'pads' in attrs\n                # default behavior in Caffe2 is SAME_UPPER\n                # https://github.com/caffe2/caffe2/blob/master/caffe2/operators/conv_pool_op_base.h#L39\n                new_attr = make_attribute('auto_pad', 'SAME_UPPER')\n                attrs[new_attr.name] = new_attr\n            elif legacy_pad_attr.i == caffe2_legacy_pb2.CAFFE_LEGACY_POOLING:\n                # The problem here is that, Pool op in Caffe may add an additional pixel, if the last part is smaller than stride.\n                # So we use the explicit padding to replace legacy_pad.\n                # pad[end] = output_size[start + 2] * stride[start] - pad[start] - 1 + kernel[start] - input[start + 2]\n                # end = start + len(pad) / 2\n                logger.warning('Converting legacy padding to explicit padding.')\n                for i in range(2):\n                    attrs['pads'].ints[i + 2] = (output_size[i + 2] * attrs['strides'].ints[i] - attrs['pads'].ints[i]\n                                                 - 1 + attrs['kernel_shape'].ints[i] - input_size[i + 2])\n            else:\n                logger.error('Don\\'t know how to handle the legacy_pad, while processing operator:\\n{}'.format(op_def))\n                raise\n\n        del node.attribute[:]\n        node.attribute.extend(attrs.values())\n        return node\n\n    @classmethod\n    def _create_gemm(cls, op_def, shapes):\n        x, w, b = op_def.input\n        args = {arg.name: arg for arg in op_def.arg}\n        y, = op_def.output\n        x_shape = list(shapes[x])\n\n        nodes = []\n        const_tensors = []\n        if 'axis' in args:\n            axis = args['axis'].i\n            outer = np.prod(x_shape[:axis]).astype(int)\n            inner = np.prod(x_shape[axis:]).astype(int)\n            reshaped_x = dummy_name()\n            shape_tensor = cls._create_shape_tensor([outer, inner])\n            const_tensors.append(shape_tensor)\n            nodes.append(helper.make_node(\n                'Reshape',\n                inputs=[x, shape_tensor.name],\n                outputs=[reshaped_x],\n            ))\n            x = reshaped_x\n\n        if 'axis_w' in args:\n            axis_w = args['axis_w'].i\n            w_shape = shapes[w]\n            outer = np.prod(w_shape[:axis_w]).astype(int).item()\n            inner = np.prod(w_shape[axis_w:]).astype(int).item()\n            reshaped_w = dummy_name()\n            shape_tensor = cls._create_shape_tensor([outer, inner])\n            const_tensors.append(shape_tensor)\n            nodes.append(helper.make_node(\n                'Reshape',\n                inputs=[w, shape_tensor.name],\n                outputs=[reshaped_w],\n            ))\n            w = reshaped_w\n\n        gemm_y_output = dummy_name() if 'axis' in args else y\n        nodes.append(helper.make_node(\n            'Gemm',\n            inputs=[x, w, b],\n            outputs=[gemm_y_output],\n            name=op_def.name,\n            transB=1,\n            broadcast=1,\n        ))\n\n        if 'axis' in args:\n            axis = args['axis'].i\n            shape_tensor = cls._create_shape_tensor(x_shape[:axis] + [-1])\n            const_tensors.append(shape_tensor)\n            nodes.append(helper.make_node(\n                'Reshape',\n                inputs=[gemm_y_output, shape_tensor.name],\n                outputs=[y],\n            ))\n\n        return nodes, const_tensors\n\n    @classmethod\n    def _create_lrn(cls, op_def, shapes):\n        node = cls._common_caffe2_op_to_onnx_node(op_def, shapes)\n        if len(node.output) == 2:\n            del node.output[1]\n        return node\n\n    @classmethod\n    def _create_slice(cls, op_def, shapes):\n        if len(op_def.input) > 1:\n            raise Unsupported(\n                'ONNX Slice operator does not support dynamic slice.')\n        node = cls._common_caffe2_op_to_onnx_node(op_def, shapes)\n        attrs = {attr.name: attr for attr in node.attribute}\n        ndims = len(attrs['starts'].ints)\n\n        node.attribute.extend([helper.make_attribute('axes', range(ndims))])\n\n        data, = node.input\n        shape = shapes[data]\n\n        ends = attrs['ends'].ints\n        for i, end in enumerate(ends):\n            if end >= 0:\n                continue\n            if end == -1:\n                end = shape[i]\n            else:\n                end = end + 1\n            ends[i] = end\n\n        return node\n\n    @classmethod\n    def _create_channel_shuffle(cls, op_def, shapes):\n        x, = op_def.input\n        y, = op_def.output\n        n, c, h, w = shapes[x]\n        args = {arg.name: arg for arg in op_def.arg}\n        g = args['group'].i\n        assert c % g == 0\n\n        nodes = []\n        const_tensors = []\n\n        tmp1 = dummy_name()\n        shape_tensor = cls._create_shape_tensor([n, g, c // g, h, w])\n        const_tensors.append(shape_tensor)\n        nodes.append(helper.make_node(\n            'Reshape',\n            inputs=[x, shape_tensor.name],\n            outputs=[tmp1],\n        ))\n\n        tmp2 = dummy_name()\n        nodes.append(helper.make_node(\n            'Transpose',\n            inputs=[tmp1],\n            outputs=[tmp2],\n            perm=[0, 2, 1, 3, 4],\n        ))\n\n        shape_tensor = cls._create_shape_tensor([n, c, h, w])\n        const_tensors.append(shape_tensor)\n        nodes.append(helper.make_node(\n            'Reshape',\n            inputs=[tmp2, shape_tensor.name],\n            outputs=[y],\n        ))\n        return nodes, const_tensors\n\n    @classmethod\n    def caffe2_op_to_onnx_node(cls, op_def, shapes):\n        if op_def.type in cls._special_operators:\n            translator = getattr(cls, cls._special_operators[op_def.type])\n        else:\n            translator = cls._common_caffe2_op_to_onnx_node\n        nodes = translator(op_def, shapes)\n        const_tensors = []\n        if isinstance(nodes, tuple):\n            nodes, const_tensors = nodes\n        if not isinstance(nodes, collections.Iterable):\n            nodes = [nodes]\n        return nodes, const_tensors\n\n    @staticmethod\n    def _all_names_in_net(net):\n        if net is None:\n            return set()\n\n        names = set()\n        names.update(net.external_input)\n        names.update(net.external_output)\n        for op in net.op:\n            names.update(op.input)\n            names.update(op.output)\n        return names\n\n    @staticmethod\n    def _extract_value_info(tensor):\n        return make_tensor_value_info(\n            name=tensor.name,\n            elem_type=tensor.data_type,\n            shape=tensor.dims)\n\n    @classmethod\n    def caffe2_net_to_onnx_graph(cls,\n                                 predict_net,\n                                 init_net=None,\n                                 value_info=None):\n        if value_info is None:\n            value_info = {}\n        if not isinstance(value_info, dict):\n            raise ValueError('Please pass value_info as a '\n                             'name -> (type, shape) dictionary')\n\n        cls._filter_fake_init(init_net, value_info)\n        cls._ssa_rewrite(predict_net, init_net, value_info)\n\n        if init_net:\n            initializer = cls.caffe2_init_net_to_initializer(init_net)\n            value_info.update({init.name: (init.data_type, init.dims)\n                               for init in initializer})\n        else:\n            initializer = []\n\n        # Check whether we have got type shape info of all input\n        missing = (set(list(predict_net.external_input)) -\n                   set(value_info.keys()))\n        if missing:\n            raise RuntimeError('Could not find value info of inputs: {}'.format(\n                ', '.join(missing)))\n\n        inputs = {}\n        for name in predict_net.external_input:\n            elem_type, shape = value_info[name]\n            inputs[name] = np.random.randn(*shape).astype(\n                mapping.TENSOR_TYPE_TO_NP_TYPE[elem_type])\n\n        ws, outputs = c2_native_run_net(\n            init_net,\n            predict_net,\n            inputs)\n\n        for name in predict_net.external_output:\n            output = outputs[name]\n            elem_type = mapping.NP_TYPE_TO_TENSOR_TYPE[output.dtype]\n            shape = output.shape\n            value_info[name] = (elem_type, shape)\n\n        graph_def = GraphProto()\n        graph_def.name = predict_net.name\n        graph_def.initializer.extend(initializer)\n        # This is a mapping from Caffe2 names to ONNX names\n        graph_def.input.extend(\n            make_tensor_value_info(\n                name=name,\n                elem_type=value_info[name][0],\n                shape=value_info[name][1])\n            for name in predict_net.external_input)\n\n        dummy_name(cls._all_names_in_net(predict_net) |\n                   cls._all_names_in_net(init_net))\n\n        for op in predict_net.op:\n            shapes = {}\n            for name in itertools.chain(op.input, op.output):\n                blob = ws.FetchBlob(name)\n                if hasattr(blob, 'shape'):\n                    shapes[name] = blob.shape\n            nodes, const_tensors = cls.caffe2_op_to_onnx_node(op, shapes=shapes)\n            graph_def.node.extend(nodes)\n            graph_def.initializer.extend(const_tensors)\n            graph_def.input.extend([cls._extract_value_info(tensor) for tensor in const_tensors])\n\n        all_output = set(sum((list(node.output) for node in graph_def.node),\n                             [init.name for init in graph_def.initializer]))\n        redundant_output = set(vi.name for vi in graph_def.output) - all_output\n        if redundant_output:\n            logger.warning(\n                'There are graph output not produced by any node or initializer: {}'\n                '! Will drop them.'.format(', '.join(redundant_output)))\n        graph_def.output.extend(\n            make_tensor_value_info(\n                name=name,\n                elem_type=value_info[name][0],\n                shape=value_info[name][1])\n            for name in predict_net.external_output\n            if name in all_output)\n\n        cls._annotate_consumed(graph_def)\n        checker.check_graph(graph_def)\n        return graph_def\n\n    @classmethod\n    def caffe2_init_net_to_initializer(cls, init_net):\n        initializer = []\n        for op in init_net.op:\n            assert not op.input\n            try:\n                data_type, field_name = {\n                    'GivenTensorFill': (TensorProto.FLOAT, 'floats'),\n                    'GivenTensorInt64Fill': (TensorProto.INT64, 'ints'),\n                    'GivenTensorIntFill': (TensorProto.INT32, 'ints'),\n                    'GivenTensorBoolFill': (TensorProto.BOOL, 'ints'),\n                    'GivenTensorStringFill': (TensorProto.STRING, 'strings'),\n                }[op.type]\n            except KeyError:\n                raise RuntimeError(\n                    \"Can not translate init_net with operator '{}' \"\n                    \"to initializer\".format(op.type)\n                )\n            raw = (data_type != TensorProto.STRING)\n            args = {a.name: a for a in op.arg}\n            vals = getattr(args['values'], field_name)\n            if raw:\n                vals = np.asarray(\n                    vals,\n                    dtype=mapping.TENSOR_TYPE_TO_NP_TYPE[data_type]).tobytes()\n            initializer.append(make_tensor(\n                name=op.output[0],\n                data_type=data_type,\n                dims=args['shape'].ints,\n                vals=vals,\n                raw=raw,\n            ))\n        return initializer\n\n    @classmethod\n    def _annotate_consumed(cls, graph_def):\n        for node in graph_def.node:\n            schema = defs.get_schema(node.op_type)\n            consumes = []\n            for i, _input_name in enumerate(node.input):\n                consume_type, output_idx = schema.consumed(i)\n                if consume_type == defs.OpSchema.UseType.CONSUME_ENFORCED:\n                    consumes.append(1)\n                else:\n                    consumes.append(0)\n\n            if any(consumes):\n                node.attribute.extend([helper.make_attribute(\n                    'consumed_inputs',\n                    consumes,\n                )])\n\n    @classmethod\n    def _filter_fake_init(cls, init_net, value_info):\n        if init_net:\n            fake_inits = [op for op in init_net.op\n                          if len(op.output) == 1 and op.output[0] in value_info and\n                          re.match('GivenTensor.*Fill|ConstantFill', op.type)]\n            for fake_init in fake_inits:\n                init_net.op.remove(fake_init)\n            del fake_inits[:]\n            del fake_inits\n\n    @classmethod\n    def _ssa_rewrite(cls, net, init_net, value_info):\n        def ssa_name(name, version):\n            return '{}_{}'.format(name, version)\n\n        if init_net:\n            for op in init_net.op:\n                assert re.match('GivenTensor.*Fill', op.type), \"type is {}, \\n{}\".format(op.type, op)\n                assert len(op.output) == 1\n                op.output[0] = ssa_name(op.output[0], 0)\n            init_net.external_input[:] = [ssa_name(name, 0)\n                                          for name in init_net.external_input]\n            init_net.external_output[:] = [ssa_name(name, 0)\n                                           for name in init_net.external_output]\n        if value_info:\n            ssa_value_info = {ssa_name(name, 0): value\n                              for name, value in value_info.items()}\n            value_info.clear()\n            value_info.update(ssa_value_info)\n        net.external_input[:] = [ssa_name(name, 0)\n                                 for name in net.external_input]\n        ssa, blob_versions = caffe2_core.get_ssa(net)\n        assert len(net.op) == len(ssa)\n        for op, (versioned_inputs, versioned_outputs) in zip(net.op, ssa):\n            op.input[:] = [ssa_name(name, version)\n                           for name, version in versioned_inputs]\n            op.output[:] = [ssa_name(name, version)\n                            for name, version in versioned_outputs]\n        net.external_output[:] = [ssa_name(name, blob_versions[name])\n                                  for name in net.external_output]\n\n    @classmethod\n    def caffe2_net_to_onnx_model(cls, *args, **kwargs):\n        opset_id = OperatorSetIdProto()\n        opset_id.domain = ''  # ONNX default domain\n        opset_id.version = cls.target_opset_version\n        model = make_model(cls.caffe2_net_to_onnx_graph(*args, **kwargs),\n                           opset_imports=[opset_id],  # current supported opset version\n                           producer_name='onnx-caffe2',  # producer name\n                           )\n        checker.check_model(model)\n        return model\n\n\ncaffe2_net_to_onnx_graph = Caffe2Frontend.caffe2_net_to_onnx_graph\ncaffe2_net_to_onnx_model = Caffe2Frontend.caffe2_net_to_onnx_model\ncaffe2_init_net_to_initializer = Caffe2Frontend.caffe2_init_net_to_initializer\n"
  },
  {
    "path": "caffe2/python/onnx/helper.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.helper\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.proto import caffe2_pb2\nfrom onnx.backend.base import namedtupledict\n\nfrom caffe2.python.onnx.workspace import Workspace\n\nimport io\nimport logging\nimport time\n\n\nlog = logging.getLogger(__name__)\n\n\nclass _DummyNameFactory(object):\n    used_names = set()\n    counter = 0\n\n    @classmethod\n    def dummy_name(cls, used_names=None):\n        if used_names is not None:\n            cls.used_names.clear()\n            cls.used_names.update(used_names)\n            cls.counter = 0\n            return None\n        else:\n            while True:\n                name = 'OC2_DUMMY_{}'.format(cls.counter)\n                cls.counter += 1\n                if name not in cls.used_names:\n                    cls.used_names.add(name)\n                    return name\n\ndummy_name = _DummyNameFactory.dummy_name\n\n\ndef c2_native_run_op(op_def, inputs):\n    ws = Workspace()\n    if isinstance(inputs, dict):\n        for key, value in inputs.items():\n            ws.FeedBlob(key, value, op_def.device_option)\n    else:\n        assert(len(op_def.input) == len(inputs))\n        for key, value in zip(op_def.input, inputs):\n            ws.FeedBlob(key, value, op_def.device_option)\n\n    ws.RunOperatorOnce(op_def)\n\n    output_names = op_def.output\n    output_values = [ws.FetchBlob(name) for name in output_names]\n    return ws, namedtupledict('Outputs', output_names)(*output_values)\n\n\ndef c2_native_run_net(init_net, predict_net, inputs):\n    ws = Workspace()\n    if init_net:\n        ws.RunNetOnce(init_net)\n\n    if isinstance(inputs, dict):\n        for key, value in inputs.items():\n            ws.FeedBlob(key, value, predict_net.device_option)\n    else:\n        uninitialized = [input_name\n                         for input_name in predict_net.external_input\n                         if not ws.HasBlob(input_name)]\n        if len(uninitialized) == len(inputs):\n            for key, value in zip(uninitialized, inputs):\n                ws.FeedBlob(key, value, predict_net.device_option)\n        else:\n            # If everything is initialized,\n            # we just initialized the first len(inputs) external_input.\n            assert(len(inputs) <= len(predict_net.external_input))\n            for i in range(len(inputs)):\n                ws.FeedBlob(predict_net.external_input[i], inputs[i],\n                            predict_net.device_option)\n\n    ws.RunNetOnce(predict_net)\n\n    output_names = predict_net.external_output\n    output_values = [ws.FetchBlob(name) for name in output_names]\n    return ws, namedtupledict('Outputs', output_names)(*output_values)\n\n\ndef load_caffe2_net(file):\n    net = caffe2_pb2.NetDef()\n    with open(file, \"rb\") as f:\n        net.ParseFromString(f.read())\n    return net\n\n\ndef save_caffe2_net(net, file, output_txt=False):\n    with open(file, \"wb\") as f:\n        f.write(net.SerializeToString())\n    if output_txt:\n        with open(file + \"txt\", \"w\") as f:\n            f.write(str(net))\n\n\ndef benchmark_caffe2_model(init_net, predict_net, warmup_iters=3, main_iters=10, layer_details=True):\n    '''\n        Run the benchmark net on the target model.\n        Return the execution time per iteration (millisecond).\n    '''\n    ws = Workspace()\n    if init_net:\n        ws.RunNetOnce(init_net)\n    ws.CreateNet(predict_net)\n    results = ws.BenchmarkNet(predict_net.name, warmup_iters, main_iters, layer_details)\n    del ws\n    return results[0]\n\n\ndef benchmark_pytorch_model(model, inputs, training=False, warmup_iters=3,\n                            main_iters=10, verbose=False):\n    '''\n        Run the model several times, and measure the execution time.\n        Return the execution time per iteration (millisecond).\n    '''\n    for _i in range(warmup_iters):\n        model(*inputs)\n    total_pytorch_time = 0.0\n    for _i in range(main_iters):\n        ts = time.time()\n        model(*inputs)\n        te = time.time()\n        total_pytorch_time += te - ts\n    log.info(\"The PyTorch model execution time per iter is {} milliseconds, \"\n             \"{} iters per second.\".format(total_pytorch_time / main_iters * 1000,\n                                           main_iters / total_pytorch_time))\n    return total_pytorch_time * 1000 / main_iters\n"
  },
  {
    "path": "caffe2/python/onnx/tests/__init__.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n"
  },
  {
    "path": "caffe2/python/onnx/tests/c2_ref_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.tests.c2_ref_test\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport json\nimport os\nimport unittest\n\nfrom caffe2.python import core\nfrom caffe2.proto import caffe2_pb2\n\nimport onnx\nfrom onnx.helper import make_node, make_graph, make_tensor, make_tensor_value_info, make_model\nfrom caffe2.python.onnx.helper import c2_native_run_net, c2_native_run_op\n\nfrom onnx import defs, mapping\nimport caffe2.python.onnx.frontend as c2_onnx\nimport caffe2.python.onnx.backend as c2\n\nimport numpy as np\nfrom caffe2.python.models.download import downloadFromURLToFile, getURLFromName, deleteDirectory\n\nfrom caffe2.python.onnx.helper import dummy_name\nfrom caffe2.python.onnx.tests.test_utils import TestCase\n\n\nclass TestCaffe2Basic(TestCase):\n    def test_dummy_name(self):\n        n1 = dummy_name()\n        n2 = dummy_name()\n        assert n1 != n2, \"Got same names in different calls: {}\".format(n1)\n\n    def test_relu_node_inplace(self):\n        X = np.random.randn(3, 2).astype(np.float32)\n        Y_ref = np.clip(X, 0, np.inf)\n\n        node_def = make_node(\n            \"Relu\", [\"X\"], [\"Y\"], consumed_inputs=[1])\n        output = c2.run_node(\n            node_def, {\"X\": X})\n        np.testing.assert_almost_equal(output.X, Y_ref)\n\n        node_def = make_node(\n            \"Relu\", [\"X\"], [\"Y\"], consumed_inputs=[1])\n        graph_def = make_graph(\n            [node_def],\n            name=\"test\",\n            inputs=[make_tensor_value_info(\"X\", onnx.TensorProto.FLOAT, [3, 2])],\n            outputs=[make_tensor_value_info(\"X\", onnx.TensorProto.FLOAT, [3, 2])])\n        c2_rep = c2.prepare(make_model(graph_def, producer_name='caffe2-ref-test'))\n        output = c2_rep.run({\"X\": X})\n        np.testing.assert_almost_equal(output.X, Y_ref)\n\n    def test_relu_graph(self):\n        X = np.random.randn(3, 2).astype(np.float32)\n        Y_ref = np.clip(X, 0, np.inf)\n\n        node_def = make_node(\n            \"Relu\", [\"X\"], [\"Y\"])\n        output = c2.run_node(\n            node_def, {\"X\": X})\n        np.testing.assert_almost_equal(output.Y, Y_ref)\n\n        graph_def = make_graph(\n            [node_def],\n            name=\"test\",\n            inputs=[make_tensor_value_info(\"X\", onnx.TensorProto.FLOAT, [3, 2])],\n            outputs=[make_tensor_value_info(\"Y\", onnx.TensorProto.FLOAT, [3, 2])])\n        c2_rep = c2.prepare(make_model(graph_def, producer_name='caffe2-ref-test'))\n        output = c2_rep.run(X)\n        np.testing.assert_almost_equal(output.Y, Y_ref)\n\n    def test_initializer(self):\n        X = np.array([[1, 2], [3, 4]]).astype(np.float32)\n        Y = np.array([[1, 2], [3, 4]]).astype(np.float32)\n        weight = np.array([[1, 0], [0, 1]])\n        graph_def = make_graph(\n            [make_node(\"Add\", [\"X\", \"Y\"], [\"Z0\"]),\n             make_node(\"Cast\", [\"Z0\"], [\"Z\"], to=\"float\"),\n             make_node(\"Mul\", [\"Z\", \"weight\"], [\"W0\"]),\n             make_node(\"Tanh\", [\"W0\"], [\"W1\"]),\n             make_node(\"Sigmoid\", [\"W1\"], [\"W2\"]),\n             make_node(\"Scale\", [\"W2\"], [\"W3\"], scale=-1.0)],\n            name=\"test_initializer\",\n            inputs=[\n                make_tensor_value_info(\"X\", onnx.TensorProto.FLOAT, (2, 2)),\n                make_tensor_value_info(\"Y\", onnx.TensorProto.FLOAT, (2, 2)),\n                make_tensor_value_info(\"weight\", onnx.TensorProto.FLOAT, (2, 2)),\n            ],\n            outputs=[\n                make_tensor_value_info(\"W3\", onnx.TensorProto.FLOAT, (2, 2))\n            ],\n            initializer=[make_tensor(\"weight\",\n                                     onnx.TensorProto.FLOAT,\n                                     [2, 2],\n                                     weight.flatten().astype(float))]\n        )\n\n        def sigmoid(x):\n            return 1 / (1 + np.exp(-x))\n\n        W_ref = -sigmoid(np.tanh((X + Y) * weight))\n        c2_rep = c2.prepare(make_model(graph_def, producer_name='caffe2-ref-test'))\n        output = c2_rep.run({\"X\": X, \"Y\": Y})\n        np.testing.assert_almost_equal(output[\"W3\"], W_ref)\n\n    def test_gemm(self):\n        # simple\n        A = np.random.randn(3, 2).astype(np.float32)\n        B = np.random.randn(2, 4).astype(np.float32)\n        C = np.random.randn(3, 4).astype(np.float32)\n        node_def = make_node(\n            'Gemm',\n            ['A', 'B', 'C'],\n            [\"Y\"])\n        output = c2.run_node(node_def, [A, B, C])\n        np.testing.assert_almost_equal(output[\"Y\"], np.dot(A, B) + C)\n\n        # transA\n        A = np.transpose(A)\n        node_def = make_node(\n            'Gemm',\n            ['A', 'B', 'C'],\n            [\"Y\"],\n            transA=True)\n        output = c2.run_node(node_def, [A, B, C])\n        np.testing.assert_almost_equal(\n            output[\"Y\"],\n            np.dot(np.transpose(A), B) + C)\n        # revert A\n        A = np.transpose(A)\n\n        # transB\n        B = np.transpose(B)\n        node_def = make_node(\n            'Gemm',\n            ['A', 'B', 'C'],\n            [\"Y\"],\n            transB=True)\n        output = c2.run_node(node_def, [A, B, C])\n        np.testing.assert_almost_equal(\n            output[\"Y\"],\n            np.dot(A, np.transpose(B)) + C)\n        # revert A\n        B = np.transpose(B)\n\n        # scale\n        alpha = np.random.random()\n        beta = np.random.random()\n        node_def = make_node(\n            'Gemm',\n            ['A', 'B', 'C'],\n            [\"Y\"],\n            alpha=alpha,\n            beta=beta)\n        output = c2.run_node(node_def, [A, B, C])\n        np.testing.assert_almost_equal(\n            output[\"Y\"],\n            alpha * np.dot(A, B) + beta * C)\n\n        # broadcast\n        C = np.random.randn(4).astype(np.float32)\n        node_def = make_node(\n            'Gemm',\n            ['A', 'B', 'C'],\n            [\"Y\"],\n            alpha=alpha,\n            beta=beta,\n            broadcast=1)\n        output = c2.run_node(node_def, [A, B, C])\n        np.testing.assert_almost_equal(\n            output[\"Y\"],\n            alpha * np.dot(A, B) + beta * C)\n\n    def test_tensor_filling_ops(self):\n        for dtype in [\n                onnx.TensorProto.FLOAT,\n                onnx.TensorProto.DOUBLE,\n                onnx.TensorProto.BOOL,\n                onnx.TensorProto.INT8,\n                onnx.TensorProto.INT16,\n                onnx.TensorProto.INT32,\n                onnx.TensorProto.INT64,\n                onnx.TensorProto.UINT8,\n                onnx.TensorProto.UINT16,\n                onnx.TensorProto.UINT32,\n        ]:\n            shape = (1, 2, 3)\n            vals = np.random.randn(*shape)\n            if dtype != onnx.TensorProto.BOOL:\n                vals *= 5\n            vals = vals.astype(\n                mapping.TENSOR_TYPE_TO_NP_TYPE[dtype])\n            tensor = make_tensor(\n                name='test-tensor-{}'.format(dtype),\n                data_type=dtype,\n                dims=[1, 2, 3],\n                vals=vals.flatten().tolist(),\n            )\n            op = c2.Caffe2Backend._create_tensor_filling_op(tensor)\n            self.assertEqual(len(op.input), 0)\n            self.assertEqual(op.output, [tensor.name])\n            ws, output = c2_native_run_op(op, inputs=[])\n            self.assertEqual(len(output), 1)\n            np.testing.assert_almost_equal(output[0], vals)\n            np.testing.assert_almost_equal(ws.FetchBlob(op.output[0]), vals)\n\n    def test_slice(self):\n        X = np.random.randn(1, 2, 3).astype(np.float32)\n        starts = np.array([0, 1, 0], dtype=np.int32)\n        ends = np.array([-1, 2, 3], dtype=np.int32)\n\n        predict_net = caffe2_pb2.NetDef()\n        predict_net.name = 'test-slice-net'\n        predict_net.external_input[:] = ['X']\n        predict_net.external_output[:] = ['Y']\n        predict_net.op.extend([\n            core.CreateOperator(\n                'Slice',\n                inputs=['X'],\n                outputs=['Y'],\n                starts=starts,\n                ends=ends,\n            ),\n        ])\n        ws, (Y,) = c2_native_run_net(\n            init_net=None,\n            predict_net=predict_net,\n            inputs=[X])\n\n        onnx_model = c2_onnx.caffe2_net_to_onnx_model(\n            predict_net=predict_net,\n            value_info={\n                'X': (onnx.mapping.NP_TYPE_TO_TENSOR_TYPE[X.dtype], X.shape)\n            })\n        Y, = c2.run_model(onnx_model, inputs=[X])\n        np.testing.assert_almost_equal(Y, X[:, 1:2, :])\n\n\nclass TestCaffe2End2End(TestCase):\n    def _model_dir(self, model):\n        caffe2_home = os.path.expanduser(os.getenv('ONNX_HOME', '~/.caffe2'))\n        models_dir = os.getenv('ONNX_MODELS', os.path.join(caffe2_home, 'models'))\n        return os.path.join(models_dir, model)\n\n    def _test_net(self,\n                  net_name,\n                  input_blob_dims=(1, 3, 224, 224),\n                  decimal=7):\n        np.random.seed(seed=0)\n        model_dir = self._model_dir(net_name)\n        if not os.path.exists(model_dir):\n            self._download(net_name)\n        c2_predict_pb = os.path.join(model_dir, 'predict_net.pb')\n        c2_predict_net = caffe2_pb2.NetDef()\n        with open(c2_predict_pb, 'rb') as f:\n            c2_predict_net.ParseFromString(f.read())\n        c2_predict_net.name = net_name\n\n        c2_init_pb = os.path.join(model_dir, 'init_net.pb')\n        c2_init_net = caffe2_pb2.NetDef()\n        with open(c2_init_pb, 'rb') as f:\n            c2_init_net.ParseFromString(f.read())\n        c2_init_net.name = net_name + '_init'\n\n        n, c, h, w = input_blob_dims\n        data = np.random.randn(n, c, h, w).astype(np.float32)\n        inputs = [data]\n        _, c2_outputs = c2_native_run_net(c2_init_net, c2_predict_net, inputs)\n        del _\n\n        model = c2_onnx.caffe2_net_to_onnx_model(\n            predict_net=c2_predict_net,\n            init_net=c2_init_net,\n            value_info=json.load(open(os.path.join(model_dir, 'value_info.json'))))\n        c2_ir = c2.prepare(model)\n        onnx_outputs = c2_ir.run(inputs)\n        self.assertSameOutputs(c2_outputs, onnx_outputs, decimal=decimal)\n\n    def _download(self, model):\n        model_dir = self._model_dir(model)\n        assert not os.path.exists(model_dir)\n        os.makedirs(model_dir)\n        for f in ['predict_net.pb', 'init_net.pb', 'value_info.json']:\n            url = getURLFromName(model, f)\n            dest = os.path.join(model_dir, f)\n            try:\n                try:\n                    downloadFromURLToFile(url, dest,\n                                          show_progress=False)\n                except TypeError:\n                    # show_progress not supported prior to\n                    # Caffe2 78c014e752a374d905ecfb465d44fa16e02a28f1\n                    # (Sep 17, 2017)\n                    downloadFromURLToFile(url, dest)\n            except Exception as e:\n                print(\"Abort: {reason}\".format(reason=e))\n                print(\"Cleaning up...\")\n                deleteDirectory(model_dir)\n                exit(1)\n\n    def test_alexnet(self):\n        self._test_net('bvlc_alexnet', decimal=4)\n\n    def test_resnet50(self):\n        self._test_net('resnet50')\n\n    @unittest.skipIf(\n        os.environ.get('JENKINS_URL'),\n        'Taking too long to download!')\n    def test_vgg16(self):\n        self._test_net('vgg16')\n\n    @unittest.skipIf(\n        os.environ.get('JENKINS_URL'),\n        'Running vgg19 on Travis with Python 2 keeps getting OOM!')\n    def test_vgg19(self):\n        self._test_net('vgg19')\n\n    def test_inception_v1(self):\n        self._test_net('inception_v1', decimal=2)\n\n    def test_inception_v2(self):\n        self._test_net('inception_v2')\n\n    @unittest.skip('Need to add support for ConstantFill operator')\n    def test_squeezenet(self):\n        self._test_net('squeezenet')\n\n    def test_shufflenet(self):\n        self._test_net('shufflenet')\n\n    def test_densenet121(self):\n        self._test_net('densenet121')\n\n    def test_bvlc_googlenet(self):\n        self._test_net('bvlc_googlenet')\n\n    def test_bvlc_reference_caffenet(self):\n        self._test_net('bvlc_reference_caffenet')\n\n    def test_bvlc_reference_rcnn_ilsvrc13(self):\n        self._test_net('bvlc_reference_rcnn_ilsvrc13')\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/onnx/tests/conversion_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.tests.conversion_test\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport json\nimport tempfile\nimport textwrap\nimport traceback\nimport unittest\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import brew, core\nfrom caffe2.python.model_helper import ModelHelper\nfrom click.testing import CliRunner\nimport numpy as np\nfrom onnx import helper, ModelProto, TensorProto\nfrom caffe2.python.onnx.helper import dummy_name, c2_native_run_net\n\nfrom caffe2.python.onnx.bin.conversion import caffe2_to_onnx, onnx_to_caffe2\nimport caffe2.python.onnx.backend as c2\nfrom caffe2.python.onnx.tests.test_utils import TestCase\n\n\nclass TestConversion(TestCase):\n    def _run_command(self, cmd, *args, **kwargs):\n        runner = CliRunner()\n        result = runner.invoke(cmd, *args, **kwargs)\n        self.assertEqual(result.exit_code, 0, textwrap.dedent('''\n        Command exited with non-zero exit code:\n        output: {}\n        exception: {}\n        exc_info: {}\n        '''.format(result.output,\n                   result.exception,\n                   traceback.format_exception(*result.exc_info))))\n        return result\n\n    def test_caffe2_to_onnx(self):\n        caffe2_net = tempfile.NamedTemporaryFile()\n        caffe2_init_net = tempfile.NamedTemporaryFile()\n        output = tempfile.NamedTemporaryFile()\n\n        model = ModelHelper(name='caffe2-to-onnx-test')\n        brew.relu(model, [\"X\"], \"Y\")\n        caffe2_net.write(model.net.Proto().SerializeToString())\n        caffe2_net.flush()\n\n        init_model = ModelHelper(name='caffe2-to-onnx-init-test')\n        init_model.net.GivenTensorFill([], 'X', shape=[2, 2],\n                                       values=np.zeros((2, 2)).flatten().astype(float))\n        caffe2_init_net.write(init_model.net.Proto().SerializeToString())\n        caffe2_init_net.flush()\n\n        result = self._run_command(\n            caffe2_to_onnx, [\n                caffe2_net.name,\n                '--caffe2-init-net', caffe2_init_net.name,\n                '--output', output.name,\n            ],\n            catch_exceptions=False,\n        )\n\n        onnx_model = ModelProto()\n        onnx_model.ParseFromString(output.read())\n        self.assertEqual(len(onnx_model.graph.node), 1)\n        self.assertEqual(onnx_model.graph.node[0].op_type, 'Relu')\n        self.assertEqual(len(onnx_model.graph.initializer), 1)\n        self.assertEqual(onnx_model.graph.initializer[0].name, onnx_model.graph.input[0].name)\n\n    def test_caffe2_to_onnx_value_info(self):\n        caffe2_net = tempfile.NamedTemporaryFile()\n        output = tempfile.NamedTemporaryFile()\n\n        model = ModelHelper(name='caffe2-to-onnx-test')\n        brew.relu(model, [\"X\"], \"Y\")\n        caffe2_net.write(model.net.Proto().SerializeToString())\n        caffe2_net.flush()\n\n        args = [caffe2_net.name, '--output', output.name]\n        self.assertRaisesRegexp(Exception,\n                                'value info',\n                                self._run_command, caffe2_to_onnx, args)\n\n        args.extend([\n            '--value-info',\n            json.dumps({\n                'X': (TensorProto.FLOAT, (2, 2)),\n            })])\n        result = self._run_command(caffe2_to_onnx, args)\n\n        onnx_model = ModelProto()\n        onnx_model.ParseFromString(output.read())\n        self.assertEqual(len(onnx_model.graph.node), 1)\n        self.assertEqual(onnx_model.graph.node[0].op_type, 'Relu')\n        self.assertEqual(len(onnx_model.graph.initializer), 0)\n\n    def test_onnx_to_caffe2(self):\n        onnx_model = tempfile.NamedTemporaryFile()\n        output = tempfile.NamedTemporaryFile()\n        init_net_output = tempfile.NamedTemporaryFile()\n\n        node_def = helper.make_node(\n            \"Mul\", [\"X\", \"W\"], [\"Y\"])\n        graph_def = helper.make_graph(\n            [node_def],\n            \"test\",\n            [helper.make_tensor_value_info(\"X\", TensorProto.FLOAT, (2, 3)),\n             helper.make_tensor_value_info(\"W\", TensorProto.FLOAT, (3, 2))],\n            [helper.make_tensor_value_info(\"Y\", TensorProto.FLOAT, (2, 2))],\n            initializer=[helper.make_tensor(\"W\",\n                                            TensorProto.FLOAT,\n                                            [3, 2],\n                                            np.zeros((3, 2)).flatten().astype(float))])\n        model_def = helper.make_model(graph_def, producer_name='onnx-to-caffe2-test')\n        onnx_model.write(model_def.SerializeToString())\n        onnx_model.flush()\n\n        result = self._run_command(\n            onnx_to_caffe2, [\n                onnx_model.name,\n                '--output', output.name,\n                '--init-net-output', init_net_output.name,\n            ])\n\n        caffe2_net = caffe2_pb2.NetDef()\n        caffe2_net.ParseFromString(output.read())\n        self.assertEqual(len(caffe2_net.op), 1)\n        self.assertEqual(caffe2_net.op[0].type, 'Mul')\n\n        caffe2_init_net = caffe2_pb2.NetDef()\n        caffe2_init_net.ParseFromString(init_net_output.read())\n        self.assertEqual(len(caffe2_init_net.op), 1)\n        self.assertEqual(set(sum([list(init_op.output)\n                                  for init_op in caffe2_init_net.op], [])),\n                         {'W'})\n\n    # TODO investigate why this is failing after changing Reshape\n    # operator from taking the new shape as attribute to as input\n    @unittest.skip\n    def test_convert_end2end(self):\n        predict_net_f = tempfile.NamedTemporaryFile()\n        init_net_f = tempfile.NamedTemporaryFile()\n        onnx_model_f = tempfile.NamedTemporaryFile()\n\n        x = 'X'\n        w = 'W'\n        b = 'b'\n        y = 'Y'\n\n        predict_net = caffe2_pb2.NetDef()\n        predict_net.name = 'test-convert-end2end'\n        predict_net.external_input[:] = [x, w, b]\n        predict_net.external_output[:] = [y]\n        predict_net.op.extend([\n            core.CreateOperator(\n                'FC',\n                inputs=[x, w, b],\n                outputs=[y],\n                axis=2,\n            ),\n        ])\n        predict_net_f.write(predict_net.SerializeToString())\n        predict_net_f.flush()\n\n        init_net = caffe2_pb2.NetDef()\n        init_net.name = 'test-convert-end2end-init'\n        init_net.external_output[:] = [w, b]\n        x_val = np.random.randn(1, 3, 2).astype(np.float32)\n        w_val = np.random.randn(4, 2).astype(np.float32)\n        b_val = np.random.randn(4).astype(np.float32)\n        init_net.op.extend([\n            core.CreateOperator(\n                'GivenTensorFill',\n                [],\n                [w],\n                values=w_val,\n                shape=w_val.shape,\n            ),\n            core.CreateOperator(\n                'GivenTensorFill',\n                [],\n                [b],\n                values=b_val,\n                shape=b_val.shape,\n            ),\n        ])\n        init_net_f.write(init_net.SerializeToString())\n        init_net_f.flush()\n\n        y_val = np.matmul(x_val, w_val.transpose()) + b_val\n        for _ in range(5):\n            self._run_command(\n                caffe2_to_onnx, [\n                    predict_net_f.name,\n                    '--caffe2-init-net', init_net_f.name,\n                    '--output', onnx_model_f.name,\n                    '--value-info',\n                    json.dumps({\n                        x: (TensorProto.FLOAT, (1, 3, 2)),\n                    }),\n                ],\n                catch_exceptions=False,\n            )\n\n            onnx_model_f.seek(0)\n            onnx_model = ModelProto()\n            onnx_model.ParseFromString(onnx_model_f.read())\n            np.testing.assert_almost_equal(\n                c2.run_model(\n                    onnx_model, {onnx_model.graph.input[0].name: x_val}),\n                [y_val])\n\n            self._run_command(\n                onnx_to_caffe2, [\n                    onnx_model_f.name,\n                    '--output', predict_net_f.name,\n                    '--init-net-output', init_net_f.name,\n                ])\n            predict_net_f.seek(0)\n            predict_net = caffe2_pb2.NetDef()\n            predict_net.ParseFromString(predict_net_f.read())\n            init_net_f.seek(0)\n            init_net = caffe2_pb2.NetDef()\n            init_net.ParseFromString(init_net_f.read())\n            x = predict_net.external_input[0]\n            np.testing.assert_almost_equal(c2_native_run_net(init_net=init_net,\n                                                             predict_net=predict_net,\n                                                             inputs={x: x_val})[1],\n                                           [y_val])\n"
  },
  {
    "path": "caffe2/python/onnx/tests/helper_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.tests.helper_test\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\n\nfrom caffe2.python.onnx.helper import dummy_name\n\nfrom caffe2.python.onnx.tests.test_utils import TestCase\n\n\nclass TestCaffe2Basic(TestCase):\n    def test_dummy_name(self):\n        dummy_name([])\n        names_1 = [dummy_name() for _ in range(3)]\n        dummy_name([])\n        names_2 = [dummy_name() for _ in range(3)]\n        self.assertEqual(names_1, names_2)\n\n        dummy_name(names_1)\n        names_3 = [dummy_name() for _ in range(3)]\n        self.assertFalse(set(names_1) & set(names_3))\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/onnx/tests/onnx_backend_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.tests.onnx_backend_test\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport os\n\nimport unittest\nimport onnx.backend.test\n\nimport caffe2.python.onnx.backend as c2\n\n# This is a pytest magic variable to load extra plugins\npytest_plugins = 'onnx.backend.test.report',\n\nbackend_test = onnx.backend.test.BackendTest(c2, __name__)\n\nbackend_test.exclude(r'(test_hardsigmoid'  # Does not support Hardsigmoid.\n                     '|test_mean|test_hardmax'  # Does not support Mean and Hardmax.\n                     '|test_cast.*FLOAT16.*)')  # Does not support Cast on Float16.\n\n# Skip vgg to speed up CI\nif 'JENKINS_URL' in os.environ:\n    backend_test.exclude(r'(test_vgg19|test_vgg)')\n\n# import all test cases at global scope to make them visible to python.unittest\nglobals().update(backend_test\n                 .enable_report()\n                 .test_cases)\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/onnx/tests/optimize_onnx_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.tests.optimize_onnx_test\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport os\nimport tarfile\nimport tempfile\nimport unittest\n\nfrom collections import namedtuple\nfrom subprocess import Popen, PIPE\nfrom six.moves.urllib.request import urlretrieve\nimport numpy as np\n\nimport onnx\nfrom onnx import helper, ModelProto, TensorProto\nfrom onnx.backend.test.runner import Runner\nimport caffe2.python.onnx.backend as c2\n\nfrom caffe2.python.onnx.tests.test_utils import TestCase\n\nclass TestRoundtrip(TestCase):\n    def _roundtrip(self, model_name):\n        model_dir = Runner(c2)._prepare_model_data(\n            namedtuple('dummy', ['model_name'])(model_name))\n\n        pb_path = os.path.join(model_dir, 'model.pb')\n\n        before_roundtrip = onnx.load(pb_path)\n\n        with open(pb_path, 'rb') as pb:\n            after_roundtrip = onnx.load_from_string(pb.read())\n\n        assert onnx.helper.printable_graph(before_roundtrip.graph) \\\n            == onnx.helper.printable_graph(after_roundtrip.graph)\n\n        with open(pb_path, 'rb') as pb:\n            assert after_roundtrip.SerializeToString() == pb.read()\n\n    # arbitrarily pick one relatively small model to sanity test with\n    def test_squeezenet_v3(self):\n        self._roundtrip('squeezenet-ir-version-3')\n\n    # testing just to be sure that we no-op instead of breaking on an\n    # older IR version.\n    def test_squeezenet_v1(self):\n        self._roundtrip('squeezenet-ir-version-1')\n\nclass TestOptimize(TestCase):\n    def _optimized(self, graph):\n        orig_model = helper.make_model(graph, producer_name='onnx-to-caffe2-test')\n        orig_model_str = orig_model.SerializeToString()\n        optimized_model_str = c2.Caffe2Backend.optimize_onnx(orig_model_str)\n        optimized_model = ModelProto()\n        optimized_model.ParseFromString(optimized_model_str)\n        return optimized_model\n\n    def test_nop_transpose(self):\n        trans = helper.make_node(\"Transpose\", [\"X\"], [\"Y\"], perm=[0,1])\n        graph = helper.make_graph(\n            [trans],\n            \"test\",\n            [helper.make_tensor_value_info(\"X\", TensorProto.FLOAT, (2, 3))],\n            [helper.make_tensor_value_info(\"Y\", TensorProto.FLOAT, (3, 2))])\n        optimized_model = self._optimized(graph)\n\n        for node in optimized_model.graph.node:\n            assert node.op_type != \"Transpose\"\n\n    def test_fuse_transpose(self):\n        trans1 = helper.make_node(\"Transpose\", [\"X\"], [\"Y\"], perm=[1,0,2])\n        trans2 = helper.make_node(\"Transpose\", [\"Y\"], [\"Z\"], perm=[2,0,1])\n        trans3 = helper.make_node(\"Transpose\", [\"Z\"], [\"A\"], perm=[2,0,1])\n        graph = helper.make_graph(\n            [trans1, trans2, trans3],\n            \"test\",\n            [helper.make_tensor_value_info(\"X\", TensorProto.FLOAT, (2, 3, 4))],\n            [helper.make_tensor_value_info(\"A\", TensorProto.FLOAT, (4, 3, 2))])\n        optimized_model = self._optimized(graph)\n\n        assert len(list(optimized_model.graph.node)) == 1\n\n    def test_fuse_transpose_into_gemm(self):\n        trans1 = helper.make_node(\"Transpose\", [\"X\"], [\"A\"], perm=[1,0])\n        trans2 = helper.make_node(\"Transpose\", [\"Y\"], [\"B\"], perm=[1,0])\n        gemm = helper.make_node(\"Gemm\", [\"A\", \"B\", \"C\"], [\"Z\"])\n        graph = helper.make_graph(\n            [trans1, trans2, gemm],\n            \"test\",\n            [helper.make_tensor_value_info(\"X\", TensorProto.FLOAT, (2, 3)),\n             helper.make_tensor_value_info(\"Y\", TensorProto.FLOAT, (5, 2)),\n             helper.make_tensor_value_info(\"C\", TensorProto.FLOAT, (3, 5))],\n            [helper.make_tensor_value_info(\"Z\", TensorProto.FLOAT, (3, 5))])\n        optimized_model = self._optimized(graph)\n\n        assert len(list(optimized_model.graph.node)) == 1\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/onnx/tests/ssa_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.tests.ssa_test\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport onnx\nimport numpy as np\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core\nfrom onnx import helper, TensorProto\n\nimport caffe2.python.onnx.frontend as c2_onnx\nfrom caffe2.python.onnx.helper import c2_native_run_net\nfrom caffe2.python.onnx.tests.test_utils import TestCase\n\n\nclass TestFrontendSSAConversion(TestCase):\n    def test_ssa(self):\n        X = np.random.randn(4, 2).astype(np.float32)\n        W = np.random.randn(3, 2).astype(np.float32)\n        b = np.random.randn(3).astype(np.float32)\n        s = np.random.randn(1).astype(np.float32)\n        np_result = X.dot(W.transpose()) + b + s\n\n        net = caffe2_pb2.NetDef()\n        net.name = 'test-ssa'\n        net.external_input[:] = ['W', 'X', 'b', 's']\n        net.op.extend([\n            core.CreateOperator(\n                'FC',\n                ['X', 'W', 'b'],\n                ['Y']\n            ),\n            core.CreateOperator(\n                'Add',\n                ['Y', 's'],\n                ['Y'],\n                broadcast=True,\n            )\n        ])\n        net.external_output[:] = ['Y']\n\n        init_net = caffe2_pb2.NetDef()\n        init_net.name = 'test-ssa-init'\n        init_net.op.extend([\n            core.CreateOperator(\n                'GivenTensorFill',\n                [],\n                ['W'],\n                values=W,\n                shape=W.shape,\n            ),\n            core.CreateOperator(\n                'GivenTensorFill',\n                [],\n                ['b'],\n                values=b,\n                shape=b.shape,\n            ),\n            core.CreateOperator(\n                'GivenTensorFill',\n                [],\n                ['s'],\n                values=s,\n                shape=s.shape,\n            )\n        ])\n        init_net.external_output[:] = ['W', 'b', 's']\n\n        _, orig_output = c2_native_run_net(\n            predict_net=net,\n            init_net=init_net,\n            inputs=[X])\n\n        value_info = {'X': (TensorProto.FLOAT, X.shape)}\n        c2_onnx.Caffe2Frontend._ssa_rewrite(\n            net,\n            init_net,\n            value_info)\n\n        self.assertEqual(net.external_input, ['W_0', 'X_0', 'b_0', 's_0'])\n        self.assertEqual(net.op[0].input, ['X_0', 'W_0', 'b_0'])\n        self.assertEqual(net.op[0].output, ['Y_1'])\n        self.assertEqual(net.op[1].input, ['Y_1', 's_0'])\n        self.assertEqual(net.op[1].output, ['Y_2'])\n        self.assertEqual(net.external_output, ['Y_2'])\n\n        self.assertEqual(init_net.external_input, [])\n        self.assertEqual(init_net.op[0].input, [])\n        self.assertEqual(init_net.op[0].output, ['W_0'])\n        self.assertEqual(init_net.op[1].input, [])\n        self.assertEqual(init_net.op[1].output, ['b_0'])\n        self.assertEqual(init_net.op[2].input, [])\n        self.assertEqual(init_net.op[2].output, ['s_0'])\n        self.assertEqual(init_net.external_output, ['W_0', 'b_0', 's_0'])\n        self.assertEqual(value_info, {'X_0': (TensorProto.FLOAT, X.shape)})\n\n        _, ssa_output = c2_native_run_net(\n            predict_net=net,\n            init_net=init_net,\n            inputs=[X])\n\n        self.assertSameOutputs(ssa_output, orig_output)\n        self.assertSameOutputs(ssa_output, [np_result])\n"
  },
  {
    "path": "caffe2/python/onnx/tests/test_utils.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.tests.test_utils\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\n\nimport numpy as np\n\n\nclass TestCase(unittest.TestCase):\n    def setUp(self):\n        np.random.seed(seed=0)\n\n    def assertSameOutputs(self, outputs1, outputs2, decimal=7):\n        self.assertEqual(len(outputs1), len(outputs2))\n        for o1, o2 in zip(outputs1, outputs2):\n            np.testing.assert_almost_equal(o1, o2, decimal=decimal)\n\n    def add_test_case(name, test_func):\n        if not name.startswith('test_'):\n            raise ValueError('Test name must start with test_: {}'.format(name))\n        if hasattr(self, name):\n            raise ValueError('Duplicated test name: {}'.format(name))\n        setattr(self, name, test_func)\n"
  },
  {
    "path": "caffe2/python/onnx/workspace.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package onnx\n# Module caffe2.python.onnx.workspace\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport uuid\n\nfrom caffe2.python import workspace\n\n\nclass Workspace(object):\n    \"\"\"\n    An object representing a Caffe2 workspace.  It is a context manager,\n    so you can say 'with workspace:' to use the represented workspace\n    as your global workspace.  It also supports every method supported\n    by caffe2.python.workspace, but instead of running these operations\n    in the global workspace, it runs them in the workspace represented\n    by this object.  When this object goes dead, the workspace (and all\n    nets and blobs within it) are freed.\n\n    Why do we need this class?  Caffe2's workspace model is very \"global state\"\n    oriented, in that there is always some ambient global workspace you are\n    working in which holds on to all of your networks and blobs.  This class\n    makes it possible to work with workspaces more locally, and without\n    forgetting to deallocate everything in the end.\n    \"\"\"\n    def __init__(self):\n        # Caffe2 (apparently) doesn't provide any native method of generating\n        # a fresh, unused workspace, so we have to fake it by generating\n        # a unique ID and hoping it's not used already / will not be used\n        # directly in the future.\n        self.workspace_id = str(uuid.uuid4())\n        # A stack, so that the context manager is reentrant.\n        self.workspace_stack = []\n\n    def __getattr__(self, attr):\n        def f(*args, **kwargs):\n            with self:\n                return getattr(workspace, attr)(*args, **kwargs)\n        return f\n\n    def __enter__(self):\n        self.workspace_stack.append(workspace.CurrentWorkspace())\n        workspace.SwitchWorkspace(self.workspace_id, create_if_missing=True)\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        w = self.workspace_stack.pop()\n        # Strictly speaking, create_if_missing here is unnecessary, since a user\n        # is not supposed to be allowed to destruct a workspace while we're in\n        # it.  However, empirically, it has been observed that during abnormal\n        # shutdown, Caffe2 deletes its default workspace fairly early in the\n        # final calls to destructors.  In this case, we may attempt to exit\n        # to a default workspace which no longer exists.  create_if_missing=True\n        # will (harmlessly) recreate the workspace before we finally quit.)\n        workspace.SwitchWorkspace(w, create_if_missing=True)\n\n    def __del__(self):\n        # NB: This is a 'self' call because we need to switch into the workspace\n        # we want to reset before we actually reset it.  A direct call to\n        # workspace.ResetWorkspace() will reset the ambient workspace, which\n        # is not want we want.\n        self.ResetWorkspace()\n"
  },
  {
    "path": "caffe2/python/operator_test/activation_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestActivations(hu.HypothesisTestCase):\n    @given(X=hu.tensor(),\n           alpha=st.floats(min_value=0.1, max_value=2.0),\n           inplace=st.booleans(),\n           **hu.gcs)\n    def test_elu(self, X, alpha, inplace, gc, dc):\n        # go away from the origin point to avoid kink problems\n        X += 0.04 * np.sign(X)\n        X[X == 0.0] += 0.04\n\n        def elu_ref(X):\n            Y = X.copy()\n            neg_indices = X <= 0\n            Y[neg_indices] = alpha * (np.exp(Y[neg_indices]) - 1)\n            return (Y,)\n\n        op = core.CreateOperator(\n            \"Elu\",\n            [\"X\"], [\"Y\" if not inplace else \"X\"],\n            alpha=alpha)\n        self.assertReferenceChecks(gc, op, [X], elu_ref)\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X], [0])\n        # Gradient check wrt X\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @given(X=hu.tensor(min_dim=4, max_dim=4),\n           alpha=st.floats(min_value=0.1, max_value=2.0),\n           inplace=st.booleans(),\n           shared=st.booleans(),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           seed=st.sampled_from([20, 100]),\n           **hu.gcs)\n    def test_prelu(self, X, alpha, inplace, shared, order, seed, gc, dc):\n        np.random.seed(seed)\n        W = np.random.randn(\n            X.shape[1] if order == \"NCHW\" else X.shape[3]).astype(np.float32)\n\n        if shared:\n            W = np.random.randn(1).astype(np.float32)\n\n        # go away from the origin point to avoid kink problems\n        X += 0.04 * np.sign(X)\n        X[X == 0.0] += 0.04\n\n        def prelu_ref(X, W):\n            Y = X.copy()\n            W = W.reshape(1, -1, 1, 1) if order == \"NCHW\" \\\n                else W.reshape(1, 1, 1, -1)\n            assert len(X.shape) == 4\n            neg_indices = X <= 0\n            assert len(neg_indices.shape) == 4\n            assert X.shape == neg_indices.shape\n            Y[neg_indices] = (Y * W)[neg_indices]\n            return (Y,)\n\n        op = core.CreateOperator(\n            \"PRelu\", [\"X\", \"W\"], [\"Y\" if not inplace else \"X\"],\n            alpha=alpha, order=order)\n        self.assertReferenceChecks(gc, op, [X, W], prelu_ref)\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X, W], [0])\n\n        if not inplace:\n            # Gradient check wrt X\n            self.assertGradientChecks(gc, op, [X, W], 0, [0], stepsize=1e-2)\n            # Gradient check wrt W\n            self.assertGradientChecks(gc, op, [X, W], 1, [0], stepsize=1e-2)\n\n    @given(X=hu.tensor(),\n           alpha=st.floats(min_value=0.1, max_value=2.0),\n           inplace=st.booleans(),\n           **hu.gcs)\n    def test_leaky_relu(self, X, alpha, inplace, gc, dc):\n        # go away from the origin point to avoid kink problems\n        X += 0.04 * np.sign(X)\n        X[X == 0.0] += 0.04\n\n        def leaky_relu_ref(X):\n            Y = X.copy()\n            neg_indices = X <= 0\n            Y[neg_indices] = Y[neg_indices] * alpha\n            return (Y,)\n\n        op = core.CreateOperator(\n            \"LeakyRelu\",\n            [\"X\"], [\"Y\" if not inplace else \"X\"],\n            alpha=alpha)\n        self.assertReferenceChecks(gc, op, [X], leaky_relu_ref)\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n    @given(X=hu.tensor(),\n           inplace=st.booleans(),\n           **hu.gcs)\n    def test_leaky_relu_default(self, X, inplace, gc, dc):\n        # go away from the origin point to avoid kink problems\n        X += 0.04 * np.sign(X)\n        X[X == 0.0] += 0.04\n\n        def leaky_relu_ref(X):\n            Y = X.copy()\n            neg_indices = X <= 0\n            Y[neg_indices] = Y[neg_indices] * 0.01\n            return (Y,)\n\n        op = core.CreateOperator(\n            \"LeakyRelu\",\n            [\"X\"], [\"Y\" if not inplace else \"X\"])\n        self.assertReferenceChecks(gc, op, [X], leaky_relu_ref)\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/adagrad_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport functools\n\nimport hypothesis\nfrom hypothesis import given, settings, HealthCheck\nimport hypothesis.strategies as st\nimport numpy as np\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestAdagrad(hu.HypothesisTestCase):\n\n    @staticmethod\n    def ref_adagrad(param_in, mom_in, grad, lr, epsilon, using_fp16=False):\n        mom_in_f32 = mom_in\n        param_in_f32 = param_in\n        if(using_fp16):\n            mom_in_f32 = mom_in.astype(np.float32)\n            param_in_f32 = param_in.astype(np.float32)\n\n        mom_out = mom_in_f32 + np.square(grad)\n        grad_adj = lr * grad / (np.sqrt(mom_out) + epsilon)\n        param_out = param_in_f32 + grad_adj\n\n        if(using_fp16):\n            return (param_out.astype(np.float16), mom_out.astype(np.float16))\n        else:\n            return (param_out.astype(np.float32), mom_out.astype(np.float32))\n\n    @staticmethod\n    def ref_row_wise_adagrad(param_in, mom_in, grad, lr, epsilon):\n        mom_out = mom_in + np.mean(np.square(grad))\n        grad_adj = lr * grad / (np.sqrt(mom_out) + epsilon)\n        param_out = param_in + grad_adj\n        return (param_out, mom_out)\n\n    @given(inputs=hu.tensors(n=3),\n           lr=st.floats(min_value=0.01, max_value=0.99,\n                        allow_nan=False, allow_infinity=False),\n           epsilon=st.floats(min_value=0.01, max_value=0.99,\n                             allow_nan=False, allow_infinity=False),\n           **hu.gcs)\n    def test_adagrad(self, inputs, lr, epsilon, gc, dc):\n        param, momentum, grad = inputs\n        lr = np.array([lr], dtype=np.float32)\n\n        op = core.CreateOperator(\n            \"Adagrad\",\n            [\"param\", \"momentum\", \"grad\", \"lr\"],\n            [\"param\", \"momentum\"],\n            epsilon=epsilon,\n            device_option=gc,\n        )\n\n        self.assertReferenceChecks(\n            gc, op,\n            [param, momentum, grad, lr],\n            functools.partial(self.ref_adagrad, epsilon=epsilon))\n\n    # Suppress filter_too_much health check.\n    # Likely caused by `assume` call falling through too often.\n    @settings(suppress_health_check=[HealthCheck.filter_too_much])\n    @given(inputs=hu.tensors(n=3),\n           lr=st.floats(min_value=0.01, max_value=0.99,\n                        allow_nan=False, allow_infinity=False),\n           epsilon=st.floats(min_value=0.01, max_value=0.99,\n                             allow_nan=False, allow_infinity=False),\n           data_strategy=st.data(),\n           **hu.gcs)\n    def test_sparse_adagrad(self, inputs, lr, epsilon,\n                            data_strategy, gc, dc):\n        param, momentum, grad = inputs\n        momentum = np.abs(momentum)\n        lr = np.array([lr], dtype=np.float32)\n\n        # Create an indexing array containing values that are lists of indices,\n        # which index into grad\n        indices = data_strategy.draw(\n            hu.tensor(dtype=np.int64,\n                      elements=st.sampled_from(np.arange(grad.shape[0]))),\n        )\n        hypothesis.note('indices.shape: %s' % str(indices.shape))\n\n        # For now, the indices must be unique\n        hypothesis.assume(np.array_equal(np.unique(indices.flatten()),\n                                         np.sort(indices.flatten())))\n\n        # Sparsify grad\n        grad = grad[indices]\n\n        op = core.CreateOperator(\n            \"SparseAdagrad\",\n            [\"param\", \"momentum\", \"indices\", \"grad\", \"lr\"],\n            [\"param\", \"momentum\"],\n            epsilon=epsilon,\n            device_option=gc)\n\n        def ref_sparse(param, momentum, indices, grad, lr, ref_using_fp16=False):\n            param_out = np.copy(param)\n            momentum_out = np.copy(momentum)\n            for i, index in enumerate(indices):\n                param_out[index], momentum_out[index] = self.ref_adagrad(\n                    param[index],\n                    momentum[index],\n                    grad[i],\n                    lr,\n                    epsilon,\n                    using_fp16=ref_using_fp16\n                )\n            return (param_out, momentum_out)\n\n        ref_using_fp16_values = [False]\n        if dc == hu.gpu_do:\n            ref_using_fp16_values.append(True)\n\n        for ref_using_fp16 in ref_using_fp16_values:\n            if(ref_using_fp16):\n                print('test_sparse_adagrad with half precision embedding')\n                momentum_i = momentum.astype(np.float16)\n                param_i = param.astype(np.float16)\n            else:\n                print('test_sparse_adagrad with full precision embedding')\n                momentum_i = momentum.astype(np.float32)\n                param_i = param.astype(np.float32)\n\n            self.assertReferenceChecks(\n                gc, op, [param_i, momentum_i, indices, grad, lr, ref_using_fp16],\n                ref_sparse\n            )\n\n    @given(inputs=hu.tensors(n=2),\n           lr=st.floats(min_value=0.01, max_value=0.99,\n                        allow_nan=False, allow_infinity=False),\n           epsilon=st.floats(min_value=0.01, max_value=0.99,\n                             allow_nan=False, allow_infinity=False),\n           data_strategy=st.data(),\n           **hu.gcs)\n    def test_sparse_adagrad_empty(self, inputs, lr, epsilon,\n                                  data_strategy, gc, dc):\n        param, momentum = inputs\n        momentum = np.abs(momentum)\n        lr = np.array([lr], dtype=np.float32)\n\n        grad = np.empty(shape=(0,) + param.shape[1:], dtype=np.float32)\n        indices = np.empty(shape=(0,), dtype=np.int64)\n\n        hypothesis.note('indices.shape: %s' % str(indices.shape))\n\n        op = core.CreateOperator(\n            \"SparseAdagrad\",\n            [\"param\", \"momentum\", \"indices\", \"grad\", \"lr\"],\n            [\"param\", \"momentum\"],\n            epsilon=epsilon,\n            device_option=gc)\n\n        def ref_sparse(param, momentum, indices, grad, lr):\n            param_out = np.copy(param)\n            momentum_out = np.copy(momentum)\n            return (param_out, momentum_out)\n\n        ref_using_fp16_values = [False]\n        if dc == hu.gpu_do:\n            ref_using_fp16_values.append(True)\n\n        for ref_using_fp16 in ref_using_fp16_values:\n            if(ref_using_fp16):\n                print('test_sparse_adagrad_empty with half precision embedding')\n                momentum_i = momentum.astype(np.float16)\n                param_i = param.astype(np.float16)\n            else:\n                print('test_sparse_adagrad_empty with full precision embedding')\n                momentum_i = momentum.astype(np.float32)\n                param_i = param.astype(np.float32)\n\n            self.assertReferenceChecks(\n                gc, op, [param_i, momentum_i, indices, grad, lr], ref_sparse\n            )\n\n    # Suppress filter_too_much health check.\n    # Likely caused by `assume` call falling through too often.\n    @settings(suppress_health_check=[HealthCheck.filter_too_much])\n    @given(inputs=hu.tensors(n=2),\n           lr=st.floats(min_value=0.01, max_value=0.99,\n                        allow_nan=False, allow_infinity=False),\n           epsilon=st.floats(min_value=0.01, max_value=0.99,\n                             allow_nan=False, allow_infinity=False),\n           data_strategy=st.data(),\n           **hu.gcs)\n    def test_row_wise_sparse_adagrad(self, inputs, lr, epsilon,\n                                     data_strategy, gc, dc):\n        param, grad = inputs\n        lr = np.array([lr], dtype=np.float32)\n\n        # Create a 1D row-wise average sum of squared gradients tensor.\n        momentum = data_strategy.draw(\n            hu.tensor1d(min_len=param.shape[0], max_len=param.shape[0],\n                        elements=hu.elements_of_type(dtype=np.float32))\n        )\n        momentum = np.abs(momentum)\n\n        # Create an indexing array containing values which index into grad\n        indices = data_strategy.draw(\n            hu.tensor(dtype=np.int64,\n                      elements=st.sampled_from(np.arange(grad.shape[0]))),\n        )\n\n        # Note that unlike SparseAdagrad, RowWiseSparseAdagrad uses a moment\n        # tensor that is strictly 1-dimensional and equal in length to the\n        # first dimension of the parameters, so indices must also be\n        # 1-dimensional.\n        indices = indices.flatten()\n\n        hypothesis.note('indices.shape: %s' % str(indices.shape))\n\n        # The indices must be unique\n        hypothesis.assume(np.array_equal(np.unique(indices), np.sort(indices)))\n\n        # Sparsify grad\n        grad = grad[indices]\n\n        op = core.CreateOperator(\n            \"RowWiseSparseAdagrad\",\n            [\"param\", \"momentum\", \"indices\", \"grad\", \"lr\"],\n            [\"param\", \"momentum\"],\n            epsilon=epsilon,\n            device_option=gc)\n\n        def ref_row_wise_sparse(param, momentum, indices, grad, lr):\n            param_out = np.copy(param)\n            momentum_out = np.copy(momentum)\n            for i, index in enumerate(indices):\n                param_out[index], momentum_out[index] = self.ref_row_wise_adagrad(\n                    param[index], momentum[index], grad[i], lr, epsilon)\n            return (param_out, momentum_out)\n\n        self.assertReferenceChecks(\n            gc, op,\n            [param, momentum, indices, grad, lr],\n            ref_row_wise_sparse)\n\n    @given(inputs=hu.tensors(n=1),\n           lr=st.floats(min_value=0.01, max_value=0.99,\n                        allow_nan=False, allow_infinity=False),\n           epsilon=st.floats(min_value=0.01, max_value=0.99,\n                             allow_nan=False, allow_infinity=False),\n           data_strategy=st.data(),\n           **hu.gcs)\n    def test_row_wise_sparse_adagrad_empty(self, inputs, lr, epsilon,\n                                           data_strategy, gc, dc):\n        param = inputs[0]\n        lr = np.array([lr], dtype=np.float32)\n\n        momentum = data_strategy.draw(\n            hu.tensor1d(min_len=param.shape[0], max_len=param.shape[0],\n                        elements=hu.elements_of_type(dtype=np.float32))\n        )\n        momentum = np.abs(momentum)\n\n        grad = np.empty(shape=(0,) + param.shape[1:], dtype=np.float32)\n        indices = np.empty(shape=(0,), dtype=np.int64)\n\n        hypothesis.note('indices.shape: %s' % str(indices.shape))\n\n        op = core.CreateOperator(\n            \"RowWiseSparseAdagrad\",\n            [\"param\", \"momentum\", \"indices\", \"grad\", \"lr\"],\n            [\"param\", \"momentum\"],\n            epsilon=epsilon,\n            device_option=gc)\n\n        def ref_row_wise_sparse(param, momentum, indices, grad, lr):\n            param_out = np.copy(param)\n            momentum_out = np.copy(momentum)\n            return (param_out, momentum_out)\n\n        self.assertReferenceChecks(\n            gc, op,\n            [param, momentum, indices, grad, lr],\n            ref_row_wise_sparse)\n"
  },
  {
    "path": "caffe2/python/operator_test/adam_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport functools\n\nimport hypothesis\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport numpy as np\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestAdam(hu.HypothesisTestCase):\n\n    @staticmethod\n    def ref_adam(param, mom1, mom2, grad, LR, ITER,\n                 beta1, beta2, epsilon):\n        t = ITER + 1\n        corrected_local_rate = LR * np.sqrt(1 - np.power(beta2, t)) / \\\n            (1 - np.power(beta1, t))\n        mom1_out = (beta1 * mom1) + (1 - beta1) * grad\n        mom2_out = (beta2 * mom2) + (1 - beta2) * np.square(grad)\n        param_out = param + corrected_local_rate * mom1_out / \\\n            (np.sqrt(mom2_out) + epsilon)\n        return param_out, mom1_out, mom2_out\n\n    @staticmethod\n    def ref_row_wise_adam(param, mom1, mom2, grad, LR, ITER,\n                          beta1, beta2, epsilon):\n        t = ITER + 1\n        corrected_local_rate = LR * np.sqrt(1 - np.power(beta2, t)) / \\\n            (1 - np.power(beta1, t))\n        mom1_out = (beta1 * mom1) + (1 - beta1) * grad\n        mom2_out = (beta2 * mom2) + (1 - beta2) * np.mean(np.square(grad))\n        param_out = param + corrected_local_rate * mom1_out / \\\n            (np.sqrt(mom2_out) + epsilon)\n        return (param_out, mom1_out, mom2_out)\n\n\n    @given(inputs=hu.tensors(n=4),\n           ITER=st.integers(min_value=0, max_value=10000),\n           LR=st.floats(min_value=0.01, max_value=0.99,\n                        allow_nan=False, allow_infinity=False),\n           beta1=st.floats(min_value=0.01, max_value=0.99,\n                           allow_nan=False, allow_infinity=False),\n           beta2=st.floats(min_value=0.01, max_value=0.99,\n                           allow_nan=False, allow_infinity=False),\n           epsilon=st.floats(min_value=0.01, max_value=0.99,\n                             allow_nan=False, allow_infinity=False),\n           **hu.gcs)\n    def test_adam(self, inputs, ITER, LR, beta1, beta2, epsilon, gc, dc):\n        param, mom1, mom2, grad = inputs\n        ITER = np.array([ITER], dtype=np.int64)\n        LR = np.array([LR], dtype=np.float32)\n\n        op = core.CreateOperator(\n            \"Adam\",\n            [\"param\", \"mom1\", \"mom2\", \"grad\", \"lr\", \"iter\"],\n            [\"output_param\", \"output_mom1\", \"output_mom2\"],\n            beta1=beta1, beta2=beta2, epsilon=epsilon)\n\n        # Iter lives on the CPU\n        input_device_options = {'iter': hu.cpu_do}\n\n        self.assertReferenceChecks(\n            gc, op,\n            [param, mom1, mom2, grad, LR, ITER],\n            functools.partial(\n                self.ref_adam,\n                beta1=beta1, beta2=beta2, epsilon=epsilon),\n            input_device_options=input_device_options)\n\n    @given(inputs=hu.tensors(n=4),\n           ITER=st.integers(min_value=0, max_value=10000),\n           LR=st.floats(min_value=0.01, max_value=0.99,\n                        allow_nan=False, allow_infinity=False),\n           beta1=st.floats(min_value=0.01, max_value=0.99,\n                           allow_nan=False, allow_infinity=False),\n           beta2=st.floats(min_value=0.01, max_value=0.99,\n                           allow_nan=False, allow_infinity=False),\n           epsilon=st.floats(min_value=0.01, max_value=0.99,\n                             allow_nan=False, allow_infinity=False),\n           data_strategy=st.data(),\n           **hu.gcs)\n    def test_sparse_adam(self, inputs, ITER, LR, beta1, beta2, epsilon,\n                         data_strategy, gc, dc):\n        param, mom1, mom2, grad = inputs\n        mom2 = np.absolute(mom2)\n        ITER = np.array([ITER], dtype=np.int64)\n        LR = np.array([LR], dtype=np.float32)\n\n        # Create an indexing array containing values which index into grad\n        indices = data_strategy.draw(\n            hu.tensor(\n                max_dim=1,\n                min_value=1,\n                max_value=grad.shape[0],\n                dtype=np.int64,\n                elements=st.sampled_from(np.arange(grad.shape[0])),\n            ),\n        )\n\n        # Verify that the generated indices are unique\n        hypothesis.assume(\n            np.array_equal(\n                np.unique(indices.flatten()),\n                np.sort(indices.flatten())))\n\n        # Sparsify grad\n        grad = grad[indices]\n\n        op = core.CreateOperator(\n            \"SparseAdam\",\n            [\"param\", \"mom1\", \"mom2\", \"indices\", \"grad\", \"lr\", \"iter\"],\n            [\"param\", \"mom1\", \"mom2\"],\n            beta1=beta1, beta2=beta2, epsilon=epsilon)\n\n        def ref_sparse(param, mom1, mom2, indices, grad, LR, ITER):\n            param_out = np.copy(param)\n            mom1_out = np.copy(mom1)\n            mom2_out = np.copy(mom2)\n            for i, index in enumerate(indices):\n                param_out[index], mom1_out[index], mom2_out[index] = \\\n                    self.ref_adam(param[index], mom1[index], mom2[index],\n                                  grad[i], LR, ITER,\n                                  beta1, beta2, epsilon)\n            return (param_out, mom1_out, mom2_out)\n\n        # Iter lives on the CPU\n        input_device_options = {'iter': hu.cpu_do}\n\n        self.assertReferenceChecks(\n            gc, op,\n            [param, mom1, mom2, indices, grad, LR, ITER],\n            ref_sparse,\n            input_device_options=input_device_options)\n\n    @given(inputs=hu.tensors(n=3),\n           ITER=st.integers(min_value=0, max_value=10000),\n           LR=st.floats(min_value=0.01, max_value=0.99,\n                        allow_nan=False, allow_infinity=False),\n           beta1=st.floats(min_value=0.01, max_value=0.99,\n                           allow_nan=False, allow_infinity=False),\n           beta2=st.floats(min_value=0.01, max_value=0.99,\n                           allow_nan=False, allow_infinity=False),\n           epsilon=st.floats(min_value=0.01, max_value=0.99,\n                             allow_nan=False, allow_infinity=False),\n           data_strategy=st.data(),\n               **hu.gcs_cpu_only)\n    def test_row_wise_sparse_adam(self, inputs, ITER, LR, beta1, beta2, epsilon,\n                                  data_strategy, gc, dc):\n        param, mom1, grad = inputs\n        ITER = np.array([ITER], dtype=np.int64)\n        LR = np.array([LR], dtype=np.float32)\n\n        # Create a 1D row-wise average 2nd moment tensor.\n        mom2 = data_strategy.draw(\n            hu.tensor1d(min_len=param.shape[0], max_len=param.shape[0],\n                        elements=hu.elements_of_type(dtype=np.float32))\n        )\n        mom2 = np.absolute(mom2)\n\n        # Create an indexing array containing values which index into grad\n        indices = data_strategy.draw(\n            hu.tensor(\n                max_dim=1,\n                min_value=1,\n                max_value=grad.shape[0],\n                dtype=np.int64,\n                elements=st.sampled_from(np.arange(grad.shape[0])),\n            ),\n        )\n\n        # Note that unlike SparseAdam, RowWiseSparseAdam uses a moment\n        # tensor that is strictly 1-dimensional and equal in length to the\n        # first dimension of the parameters, so indices must also be\n        # 1-dimensional.\n        indices = indices.flatten()\n\n        hypothesis.note('indices.shape: %s' % str(indices.shape))\n\n        # Verify that the generated indices are unique\n        hypothesis.assume(np.array_equal(np.unique(indices), np.sort(indices)))\n\n        # Sparsify grad\n        grad = grad[indices]\n\n        op = core.CreateOperator(\n            \"RowWiseSparseAdam\",\n            [\"param\", \"mom1\", \"mom2\", \"indices\", \"grad\", \"lr\", \"iter\"],\n            [\"param\", \"mom1\", \"mom2\"],\n            beta1=beta1, beta2=beta2, epsilon=epsilon)\n\n        def ref_row_wise_sparse(param, mom1, mom2, indices, grad, LR, ITER):\n            param_out = np.copy(param)\n            mom1_out = np.copy(mom1)\n            mom2_out = np.copy(mom2)\n            for i, index in enumerate(indices):\n                param_out[index], mom1_out[index], mom2_out[index] = \\\n                    self.ref_row_wise_adam(param[index], mom1[index], mom2[index],\n                                           grad[i], LR, ITER,\n                                           beta1, beta2, epsilon)\n            return (param_out, mom1_out, mom2_out)\n\n        # Iter lives on the CPU\n        input_device_options = {'iter': hu.cpu_do}\n\n        self.assertReferenceChecks(\n            gc, op,\n            [param, mom1, mom2, indices, grad, LR, ITER],\n            ref_row_wise_sparse,\n            input_device_options=input_device_options)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/apmeter_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\ndef calculate_ap(predictions, labels):\n    N, D = predictions.shape\n    ap = np.zeros(D)\n    num_range = np.arange((N), dtype=np.float32) + 1\n    for k in range(D):\n        scores = predictions[:N, k]\n        label = labels[:N, k]\n        sortind = np.argsort(-scores, kind='mergesort')\n        truth = label[sortind]\n        precision = np.cumsum(truth) / num_range\n        ap[k] = precision[truth.astype(np.bool)].sum() / max(1, truth.sum())\n    return ap\n\n\nclass TestAPMeterOps(hu.HypothesisTestCase):\n    @given(predictions=hu.arrays(dims=[10, 3],\n           elements=st.floats(allow_nan=False,\n                              allow_infinity=False,\n                              min_value=0.1,\n                              max_value=1)),\n           labels=hu.arrays(dims=[10, 3],\n                            dtype=np.int32,\n                            elements=st.integers(min_value=0,\n                                                 max_value=1)),\n           **hu.gcs_cpu_only)\n    def test_average_precision(self, predictions, labels, gc, dc):\n        op = core.CreateOperator(\n            \"APMeter\",\n            [\"predictions\", \"labels\"],\n            [\"AP\"],\n            buffer_size=10,\n        )\n\n        def op_ref(predictions, labels):\n            ap = calculate_ap(predictions, labels)\n            return (ap, )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[predictions, labels],\n            reference=op_ref)\n\n    @given(predictions=hu.arrays(dims=[10, 3],\n           elements=st.floats(allow_nan=False,\n                              allow_infinity=False,\n                              min_value=0.1,\n                              max_value=1)),\n           labels=hu.arrays(dims=[10, 3],\n                            dtype=np.int32,\n                            elements=st.integers(min_value=0,\n                                                 max_value=1)),\n           **hu.gcs_cpu_only)\n    def test_average_precision_small_buffer(self, predictions, labels, gc, dc):\n        op_small_buffer = core.CreateOperator(\n            \"APMeter\",\n            [\"predictions\", \"labels\"],\n            [\"AP\"],\n            buffer_size=5,\n        )\n\n        def op_ref(predictions, labels):\n            # We can only hold the last 5 in the buffer\n            ap = calculate_ap(predictions[5:], labels[5:])\n            return (ap, )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op_small_buffer,\n            inputs=[predictions, labels],\n            reference=op_ref\n        )\n"
  },
  {
    "path": "caffe2/python/operator_test/assert_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nfrom hypothesis import given\nimport hypothesis.strategies as st\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestAssert(hu.HypothesisTestCase):\n    @given(\n        dtype=st.sampled_from(['bool_', 'int32', 'int64']),\n        shape=st.lists(elements=st.integers(1, 10), min_size=1, max_size=4),\n        **hu.gcs)\n    def test_assert(self, dtype, shape, gc, dc):\n        test_tensor = np.random.rand(*shape).astype(np.dtype(dtype))\n\n        op = core.CreateOperator('Assert', ['X'], [])\n\n        def assert_ref(X):\n            return []\n\n        try:\n            self.assertReferenceChecks(gc, op, [test_tensor], assert_ref)\n        except Exception:\n            assert(not np.all(test_tensor))\n"
  },
  {
    "path": "caffe2/python/operator_test/atomic_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\n\n\nclass TestAtomicOps(TestCase):\n    def test_atomic_ops(self):\n        \"\"\"\n        Test that both countdown and checksum are update atomically by having\n        cowntdown count from 20k to 0 from parallel the workers and updating\n        the checksum to the value fetched. If operations are trully atomic,\n        each value from 1 to 20k should be fetched exactly once from the\n        countdown, and fed exactly once to the checksum, such that at the end\n        checksum must contain the exact value of sum[i=0..20000](i).\n        \"\"\"\n        init_net = core.Net('init')\n        mutex_countdown = init_net.CreateMutex([])\n        mutex_checksum = init_net.CreateMutex([])\n        countdown = init_net.ConstantFill([], shape=[], value=20000,\n                                          dtype=core.DataType.INT32)\n        checksum = init_net.ConstantFill(\n            [], shape=[], value=0, dtype=core.DataType.INT32)\n        minus_one = init_net.ConstantFill(\n            [], shape=[], value=-1, dtype=core.DataType.INT32)\n        steps = []\n        for i in range(0, 100):\n            net = core.Net('net:%d' % i)\n            _, fetched_count = net.AtomicFetchAdd(\n                [mutex_countdown, countdown, minus_one],\n                [countdown, 'fetched_count:%d' % i])\n            net.AtomicFetchAdd(\n                [mutex_checksum, checksum, fetched_count],\n                [checksum, 'not_used'])\n            steps.append(\n                core.execution_step('worker:%d' % i, net, num_iter=200))\n        super_step = core.execution_step(\n            'parent', steps, concurrent_substeps=True)\n        plan = core.Plan('plan')\n        plan.AddStep(core.execution_step('init', init_net))\n        plan.AddStep(super_step)\n        workspace.RunPlan(plan)\n        # checksum = sum[i=1..20000](i) = 20000 * 20001 / 2 = 200010000\n        self.assertEquals(workspace.FetchBlob(checksum), 200010000)\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/basic_rnn_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import workspace, core, rnn_cell\nfrom caffe2.python.model_helper import ModelHelper\nfrom caffe2.python.rnn.rnn_cell_test_util import tanh\nimport caffe2.python.hypothesis_test_util as hu\n\nfrom hypothesis import given\nfrom hypothesis import settings as ht_settings\nimport hypothesis.strategies as st\nimport numpy as np\nimport unittest\n\n\ndef basic_rnn_reference(input, hidden_initial,\n                        i2h_w, i2h_b,\n                        gate_w, gate_b,\n                        seq_lengths,\n                        drop_states,\n                        use_sequence_lengths):\n    D = hidden_initial.shape[-1]\n    T = input.shape[0]\n    N = input.shape[1]\n\n    if seq_lengths is not None:\n        seq_lengths = (np.ones(shape=(N, D)) *\n                       seq_lengths.reshape(N, 1)).astype(np.int32)\n\n    ret = []\n\n    hidden_prev = hidden_initial\n\n    for t in range(T):\n        input_fc = np.dot(input[t], i2h_w.T) + i2h_b\n        recur_fc = np.dot(hidden_prev, gate_w.T) + gate_b\n        hidden_t = tanh(input_fc + recur_fc)\n\n        if seq_lengths is not None:\n            valid = (t < seq_lengths).astype(np.int32)\n            assert valid.shape == (N, D), (valid.shape, (N, D))\n            hidden_t = hidden_t * valid + \\\n                       hidden_prev * (1 - valid) * (1 - drop_states)\n\n        ret.append(hidden_t)\n        hidden_prev = hidden_t\n    return ret\n\n\nclass BasicRNNCellTest(hu.HypothesisTestCase):\n    @given(\n        seed=st.integers(0, 2**32 - 1),\n        seq_length=st.integers(min_value=1, max_value=5),\n        batch_size=st.integers(min_value=1, max_value=5),\n        input_size=st.integers(min_value=1, max_value=5),\n        hidden_size=st.integers(min_value=1, max_value=5),\n        drop_states=st.booleans(),\n        sequence_lengths=st.booleans(),\n        **hu.gcs\n    )\n    @ht_settings(max_examples=15)\n    def test_basic_rnn(self, seed, seq_length, batch_size, input_size, hidden_size,\n                       drop_states, sequence_lengths, gc, dc):\n        np.random.seed(seed)\n\n        seq_lengths_data = np.random.randint(\n            1, seq_length + 1, size=(batch_size,)).astype(np.int32)\n        input_blob_data = np.random.randn(\n            seq_length, batch_size, input_size).astype(np.float32)\n        initial_h_data = np.random.randn(\n            batch_size, hidden_size).astype(np.float32)\n        gates_t_w_data = np.random.randn(\n            hidden_size, hidden_size).astype(np.float32)\n        gates_t_b_data = np.random.randn(\n            hidden_size).astype(np.float32)\n        i2h_w_data = np.random.randn(\n            hidden_size, input_size).astype(np.float32)\n        i2h_b_data = np.random.randn(\n            hidden_size).astype(np.float32)\n\n        with core.DeviceScope(gc):\n            with hu.temp_workspace():\n                workspace.FeedBlob(\n                    'input_blob', input_blob_data, device_option=gc)\n                workspace.FeedBlob(\n                    'seq_lengths', seq_lengths_data, device_option=gc)\n                workspace.FeedBlob(\n                    'initial_h', initial_h_data, device_option=gc)\n                workspace.FeedBlob(\n                    'basic_rnn/gates_t_w', gates_t_w_data, device_option=gc)\n                workspace.FeedBlob(\n                    'basic_rnn/gates_t_b', gates_t_b_data, device_option=gc)\n                workspace.FeedBlob(\n                    'basic_rnn/i2h_w', i2h_w_data, device_option=gc)\n                workspace.FeedBlob(\n                    'basic_rnn/i2h_b', i2h_b_data, device_option=gc)\n\n                model = ModelHelper(name='model')\n                hidden_t_all, _ = rnn_cell.BasicRNN(\n                    model,\n                    'input_blob',\n                    'seq_lengths' if sequence_lengths else None,\n                    ['initial_h'],\n                    input_size,\n                    hidden_size,\n                    \"basic_rnn\",\n                    activation='tanh',\n                    forward_only=True,\n                    drop_states=drop_states)\n\n                workspace.RunNetOnce(model.net)\n\n                result = workspace.FetchBlob(hidden_t_all)\n\n        reference = basic_rnn_reference(\n            input_blob_data,\n            initial_h_data,\n            i2h_w_data,\n            i2h_b_data,\n            gates_t_w_data,\n            gates_t_b_data,\n            seq_lengths_data if sequence_lengths else None,\n            drop_states=drop_states,\n            use_sequence_lengths=sequence_lengths\n        )\n\n        np.testing.assert_allclose(result, reference, atol=1e-4, rtol=1e-4)\n\n\nif __name__ == \"__main__\":\n    workspace.GlobalInit([\n        'caffe2',\n        '--caffe2_log_level=0',\n    ])\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/batch_box_cox_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\n\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\n# The reference implementation is susceptible to numerical cancellation when\n# *lambda1* is small and *data* is near one. We leave it up to the caller to\n# truncate lambda to zero or bound data away from one. Unfortunately, the C++\n# implementation may be using higher precision than the python version, which\n# could cause this test to fail. We bound inputs away from the critical values.\n# (Note that a tolerance of 1e-6 on _either_ parameter is typically sufficient\n# to avoid catastrophic cancellation when the other is far from zero/one.)\nTOLERANCE = 1e-3\n\n\n@st.composite\ndef _inputs(draw):\n    N = draw(st.integers(min_value=0, max_value=5))\n    D = draw(st.integers(min_value=1, max_value=5))\n    # N, D, data, lambda1, lambda2\n    return (\n        N,\n        D,\n        draw(st.lists(\n            min_size=N * D,\n            max_size=N * D,\n            elements=st.one_of(\n                st.floats(min_value=-10, max_value=1 - TOLERANCE),\n                st.floats(min_value=1 + TOLERANCE, max_value=10))\n        )),\n        draw(st.lists(\n            elements=st.one_of(\n                st.floats(min_value=-2, max_value=-TOLERANCE),\n                st.floats(min_value=TOLERANCE, max_value=2)),\n            min_size=D,\n            max_size=D,\n        )),\n        draw(st.lists(\n            elements=st.floats(min_value=-2, max_value=2),\n            min_size=D,\n            max_size=D,\n        )),\n    )\n\n\nclass TestBatchBoxCox(hu.HypothesisTestCase):\n    @given(\n        inputs=_inputs(),\n        **hu.gcs_cpu_only\n    )\n    def test_batch_box_cox(self, inputs, gc, dc):\n        self.batch_box_cox(inputs, gc, dc)\n\n    @given(**hu.gcs_cpu_only)\n    def test_lambda1_is_all_zero(self, gc, dc):\n        inputs = (1, 1, [[2]], [0], [0])\n        self.batch_box_cox(inputs, gc, dc)\n        inputs = (2, 1, [[2], [4]], [0], [0])\n        self.batch_box_cox(inputs, gc, dc)\n        inputs = (1, 3, [[1, 2, 3]], [0, 0, 0], [0, 0, 0])\n        self.batch_box_cox(inputs, gc, dc)\n        inputs = (2, 3, [[1, 2, 3], [4, 5, 6]], [0, 0, 0], [0, 0, 0])\n        self.batch_box_cox(inputs, gc, dc)\n\n    @given(**hu.gcs_cpu_only)\n    def test_lambda1_is_partially_zero(self, gc, dc):\n        inputs = (1, 5, [[1, 2, 3, 4, 5]],\n                  [0, -.5, 0, .5, 0], [0.1, 0.2, 0.3, 0.4, 0.5])\n        self.batch_box_cox(inputs, gc, dc)\n        inputs = (3, 5, [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [1, 2, 3, 4, 5]],\n                  [0, -.5, 0, .5, 0], [0.1, 0.2, 0.3, 0.4, 0.5])\n        self.batch_box_cox(inputs, gc, dc)\n        inputs = (2, 6, [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]],\n                  [0, -.5, 0, .5, 0, 1], [0.1, 0.2, 0.3, 0.4, 0.5, 0.6])\n        self.batch_box_cox(inputs, gc, dc)\n        inputs = (2, 7, [[1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14]],\n                  [0, -.5, 0, .5, 0, 1, 0], [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7])\n        self.batch_box_cox(inputs, gc, dc)\n\n    @given(**hu.gcs_cpu_only)\n    def test_bound_base_away_from_zero(self, gc, dc):\n        inputs = (2, 3, [[1e-5, 1e-6, 1e-7], [1e-7, -1e-6, 1e-5]],\n                  [0, 0, 0], [0, 0, 1e-6])\n        self.batch_box_cox(inputs, gc, dc)\n\n    def batch_box_cox(self, inputs, gc, dc):\n        N, D, data, lambda1, lambda2 = inputs\n\n        data = np.array(data, dtype=np.float32).reshape(N, D)\n        lambda1 = np.array(lambda1, dtype=np.float32)\n        lambda2 = np.array(lambda2, dtype=np.float32)\n\n        # Bound data away from one. See comment in _inputs() above.\n        base = data + lambda2\n        data[(base > 1 - TOLERANCE) & (base < 1 + TOLERANCE)] += 2 * TOLERANCE\n\n        def ref(data, lambda1, lambda2):\n            dim_1 = data.shape[1]\n            output = np.copy(data)\n            if data.size <= 0:\n                return [output]\n\n            for i in range(dim_1):\n                output[:, i] = data[:, i] + lambda2[i]\n                output[:, i] = np.maximum(output[:, i], 1e-6)\n                if lambda1[i] == 0:\n                    output[:, i] = np.log(output[:, i])\n                else:\n                    output[:, i] =\\\n                        (np.power(output[:, i], lambda1[i]) - 1) / lambda1[i]\n            return [output]\n\n        for naive in [False, True]:\n            op = core.CreateOperator(\n                'BatchBoxCox',\n                ['data', 'lambda1', 'lambda2'],\n                ['output'],\n                naive=naive,\n                # Note examples above with D=5, 6, 7.\n                # A zero value falls back to the naive implementation.\n                min_block_size=0 if naive else 6\n            )\n            self.assertReferenceChecks(gc, op, [data, lambda1, lambda2], ref)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/batch_sparse_to_dense_op_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestBatchSparseToDense(hu.HypothesisTestCase):\n\n    @given(\n        batch_size=st.integers(5, 10),\n        dense_last_dim=st.integers(5, 10),\n        default_value=st.floats(min_value=2.0, max_value=3.0),\n        **hu.gcs_cpu_only\n    )\n    def test_batch_sparse_to_dense(\n        self, batch_size, dense_last_dim, default_value, gc, dc\n    ):\n        L = np.random.randint(1, dense_last_dim + 1, size=(batch_size))\n        num_data = L.sum()\n        # The following logic ensure that indices in each batch will not be duplicated\n        I = np.array([]).astype(np.int32)\n        for l in L:\n            I_l = np.random.choice(dense_last_dim, l, replace=False)\n            I = np.concatenate((I, I_l))\n        V = np.random.rand(num_data).astype(np.float32)\n\n        op = core.CreateOperator(\n            'BatchSparseToDense',\n            ['L', 'I', 'V'],\n            ['O'],\n            dense_last_dim=dense_last_dim,\n            default_value=default_value,\n        )\n\n        S = np.random.rand(batch_size, dense_last_dim).astype(np.float32)\n        op2 = core.CreateOperator(\n            'BatchSparseToDense',\n            ['L', 'I', 'V', 'S'],\n            ['O'],\n            default_value=default_value,\n        )\n\n        def batch_sparse_to_dense_ref(L, I, V, S=None):\n            if S is None:\n                ret = np.zeros((batch_size, dense_last_dim))\n            else:\n                ret = np.zeros(S.shape)\n            ret.fill(default_value)\n            batch = 0\n            v_idx = 0\n            for length in L:\n                for _ in range(length):\n                    ret[batch][I[v_idx]] = V[v_idx]\n                    v_idx += 1\n                batch += 1\n            return [ret]\n\n        self.assertDeviceChecks(dc, op, [L, I, V], [0])\n        self.assertReferenceChecks(gc, op, [L, I, V], batch_sparse_to_dense_ref)\n        self.assertGradientChecks(gc, op, [L, I, V], 2, [0])\n        self.assertDeviceChecks(dc, op2, [L, I, V, S], [0])\n        self.assertReferenceChecks(gc, op2, [L, I, V, S], batch_sparse_to_dense_ref)\n        self.assertGradientChecks(gc, op2, [L, I, V, S], 2, [0])\n\n    @given(\n        batch_size=st.integers(5, 10),\n        dense_last_dim=st.integers(5, 10),\n        **hu.gcs_cpu_only\n    )\n    def test_batch_dense_to_sparse(self, batch_size, dense_last_dim, gc, dc):\n        L = np.random.randint(1, dense_last_dim + 1, size=(batch_size))\n        # The following logic ensure that indices in each batch will not be duplicated\n        I = np.array([]).astype(np.int32)\n        for l in L:\n            I_l = np.random.choice(dense_last_dim, l, replace=False)\n            I = np.concatenate((I, I_l))\n        D = np.random.rand(batch_size, dense_last_dim).astype(np.float32)\n\n        op = core.CreateOperator(\n            'BatchDenseToSparse',\n            ['L', 'I', 'D'],\n            ['V'],\n        )\n\n        def batch_dense_to_sparse_ref(L, I, D):\n            ret = np.zeros(I.shape)\n            batch = 0\n            i_idx = 0\n            for length in L:\n                for _ in range(length):\n                    ret[i_idx] = D[batch][I[i_idx]]\n                    i_idx += 1\n                batch += 1\n            return [ret]\n        print(L, I, D)\n\n        self.assertDeviceChecks(dc, op, [L, I, D], [0])\n        self.assertReferenceChecks(gc, op, [L, I, D], batch_dense_to_sparse_ref)\n        self.assertGradientChecks(gc, op, [L, I, D], 2, [0])\n"
  },
  {
    "path": "caffe2/python/operator_test/bbox_transform_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\n# Reference implementation from detectron/lib/utils/boxes.py\ndef bbox_transform(boxes, deltas, weights=(1.0, 1.0, 1.0, 1.0)):\n    \"\"\"Forward transform that maps proposal boxes to predicted ground-truth\n    boxes using bounding-box regression deltas. See bbox_transform_inv for a\n    description of the weights argument.\n    \"\"\"\n    if boxes.shape[0] == 0:\n        return np.zeros((0, deltas.shape[1]), dtype=deltas.dtype)\n\n    boxes = boxes.astype(deltas.dtype, copy=False)\n\n    widths = boxes[:, 2] - boxes[:, 0] + 1.0\n    heights = boxes[:, 3] - boxes[:, 1] + 1.0\n    ctr_x = boxes[:, 0] + 0.5 * widths\n    ctr_y = boxes[:, 1] + 0.5 * heights\n\n    wx, wy, ww, wh = weights\n    dx = deltas[:, 0::4] / wx\n    dy = deltas[:, 1::4] / wy\n    dw = deltas[:, 2::4] / ww\n    dh = deltas[:, 3::4] / wh\n\n    # Prevent sending too large values into np.exp()\n    BBOX_XFORM_CLIP = np.log(1000. / 16.)\n    dw = np.minimum(dw, BBOX_XFORM_CLIP)\n    dh = np.minimum(dh, BBOX_XFORM_CLIP)\n\n    pred_ctr_x = dx * widths[:, np.newaxis] + ctr_x[:, np.newaxis]\n    pred_ctr_y = dy * heights[:, np.newaxis] + ctr_y[:, np.newaxis]\n    pred_w = np.exp(dw) * widths[:, np.newaxis]\n    pred_h = np.exp(dh) * heights[:, np.newaxis]\n\n    pred_boxes = np.zeros(deltas.shape, dtype=deltas.dtype)\n    # x1\n    pred_boxes[:, 0::4] = pred_ctr_x - 0.5 * pred_w\n    # y1\n    pred_boxes[:, 1::4] = pred_ctr_y - 0.5 * pred_h\n    # x2 (note: \"- 1\" is correct; don't be fooled by the asymmetry)\n    pred_boxes[:, 2::4] = pred_ctr_x + 0.5 * pred_w - 1\n    # y2 (note: \"- 1\" is correct; don't be fooled by the asymmetry)\n    pred_boxes[:, 3::4] = pred_ctr_y + 0.5 * pred_h - 1\n\n    return pred_boxes\n\n\n# Reference implementation from detectron/lib/utils/boxes.py\ndef clip_tiled_boxes(boxes, im_shape):\n    \"\"\"Clip boxes to image boundaries. im_shape is [height, width] and boxes\n    has shape (N, 4 * num_tiled_boxes).\"\"\"\n    assert boxes.shape[1] % 4 == 0, \\\n        'boxes.shape[1] is {:d}, but must be divisible by 4.'.format(\n        boxes.shape[1]\n    )\n    # x1 >= 0\n    boxes[:, 0::4] = np.maximum(np.minimum(boxes[:, 0::4], im_shape[1] - 1), 0)\n    # y1 >= 0\n    boxes[:, 1::4] = np.maximum(np.minimum(boxes[:, 1::4], im_shape[0] - 1), 0)\n    # x2 < im_shape[1]\n    boxes[:, 2::4] = np.maximum(np.minimum(boxes[:, 2::4], im_shape[1] - 1), 0)\n    # y2 < im_shape[0]\n    boxes[:, 3::4] = np.maximum(np.minimum(boxes[:, 3::4], im_shape[0] - 1), 0)\n    return boxes\n\n\ndef generate_rois(roi_counts, im_dims):\n    assert len(roi_counts) == len(im_dims)\n    all_rois = []\n    for i, num_rois in enumerate(roi_counts):\n        if num_rois == 0:\n            continue\n        # [batch_idx, x1, y1, x2, y2]\n        rois = np.random.uniform(\n            0, im_dims[i], size=(roi_counts[i], 5)\n        ).astype(np.float32)\n        rois[:, 0] = i  # batch_idx\n        # Swap (x1, x2) if x1 > x2\n        rois[:, 1], rois[:, 3] = np.minimum(rois[:, 1], rois[:, 3]), \\\n                np.maximum(rois[:, 1], rois[:, 3])\n        # Swap (y1, y2) if y1 > y2\n        rois[:, 2], rois[:, 4] = np.minimum(rois[:, 2], rois[:, 4]), \\\n                np.maximum(rois[:, 2], rois[:, 4])\n        all_rois.append(rois)\n    if len(all_rois) > 0:\n        return np.vstack(all_rois)\n    return np.empty((0, 5)).astype(np.float32)\n\n\nclass TestBBoxTransformOp(hu.HypothesisTestCase):\n    @given(\n        num_rois=st.integers(1, 10),\n        num_classes=st.integers(1, 10),\n        im_dim=st.integers(100, 600),\n        skip_batch_id=st.booleans(),\n        **hu.gcs_cpu_only\n    )\n    def test_bbox_transform(\n        self, num_rois, num_classes, im_dim, skip_batch_id, gc, dc\n    ):\n        \"\"\"\n        Test with all rois belonging to a single image per run.\n        \"\"\"\n        rois = generate_rois([num_rois], [im_dim])\n        if skip_batch_id:\n            rois = rois[:, 1:5]\n        deltas = np.random.randn(num_rois, 4 * num_classes).astype(np.float32)\n        im_info = np.array([im_dim, im_dim,\n                            1.0]).astype(np.float32).reshape(1, 3)\n\n        def bbox_transform_ref(rois, deltas, im_info):\n            boxes = rois if rois.shape[1] == 4 else rois[:, 1:5]\n            box_out = bbox_transform(boxes, deltas)\n            im_shape = im_info[0, 0:2]\n            box_out = clip_tiled_boxes(box_out, im_shape)\n            return [box_out]\n\n        op = core.CreateOperator(\n            \"BBoxTransform\",\n            [\"rois\", \"deltas\", \"im_info\"],\n            [\"box_out\"],\n            apply_scale=False,\n            correct_transform_coords=True,\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[rois, deltas, im_info],\n            reference=bbox_transform_ref,\n        )\n\n    @given(\n        roi_counts=st.lists(st.integers(0, 5), min_size=1, max_size=10),\n        num_classes=st.integers(1, 10),\n        **hu.gcs_cpu_only\n    )\n    def test_bbox_transform_batch(self, roi_counts, num_classes, gc, dc):\n        \"\"\"\n        Test with rois for multiple images in a batch\n        \"\"\"\n        batch_size = len(roi_counts)\n        total_rois = sum(roi_counts)\n        im_dims = np.random.randint(100, 600, batch_size)\n        rois = generate_rois(roi_counts, im_dims)\n        deltas = np.random.randn(total_rois, 4 * num_classes).astype(np.float32)\n        im_info = np.zeros((batch_size, 3)).astype(np.float32)\n        im_info[:, 0] = im_dims\n        im_info[:, 1] = im_dims\n        im_info[:, 2] = 1.0\n\n        def bbox_transform_ref(rois, deltas, im_info):\n            box_out = []\n            offset = 0\n            for i, num_rois in enumerate(roi_counts):\n                if num_rois == 0:\n                    continue\n                cur_boxes = rois[offset:offset + num_rois, 1:5]\n                cur_deltas = deltas[offset:offset + num_rois]\n                cur_box_out = bbox_transform(cur_boxes, cur_deltas)\n                im_shape = im_info[i, 0:2]\n                cur_box_out = clip_tiled_boxes(cur_box_out, im_shape)\n                box_out.append(cur_box_out)\n                offset += num_rois\n\n            if len(box_out) > 0:\n                box_out = np.vstack(box_out)\n            else:\n                box_out = np.empty(deltas.shape).astype(np.float32)\n            return [box_out, roi_counts]\n\n        op = core.CreateOperator(\n            \"BBoxTransform\",\n            [\"rois\", \"deltas\", \"im_info\"],\n            [\"box_out\", \"roi_batch_splits\"],\n            apply_scale=False,\n            correct_transform_coords=True,\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[rois, deltas, im_info],\n            reference=bbox_transform_ref,\n        )\n"
  },
  {
    "path": "caffe2/python/operator_test/blobs_queue_db_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport numpy as np\n\nimport caffe2.proto.caffe2_pb2 as caffe2_pb2\nfrom caffe2.python import core, workspace, timeout_guard\n\n\nclass BlobsQueueDBTest(unittest.TestCase):\n    def test_create_blobs_queue_db_string(self):\n        def add_blobs(queue, num_samples):\n            blob = core.BlobReference(\"blob\")\n            status = core.BlobReference(\"blob_status\")\n            for i in range(num_samples):\n                self._add_blob_to_queue(\n                    queue, self._create_test_tensor_protos(i), blob, status\n                )\n        self._test_create_blobs_queue_db(add_blobs)\n\n    def test_create_blobs_queue_db_tensor(self):\n        def add_blobs(queue, num_samples):\n            blob = core.BlobReference(\"blob\")\n            status = core.BlobReference(\"blob_status\")\n            for i in range(num_samples):\n                data = self._create_test_tensor_protos(i)\n                data = np.array([data], dtype=str)\n                self._add_blob_to_queue(\n                    queue, data, blob, status\n                )\n        self._test_create_blobs_queue_db(add_blobs)\n\n    def _test_create_blobs_queue_db(self, add_blobs_fun):\n        num_samples = 10000\n        batch_size = 10\n        init_net = core.Net('init_net')\n        net = core.Net('test_create_blobs_queue_db')\n        queue = init_net.CreateBlobsQueue([], 'queue', capacity=num_samples)\n        reader = init_net.CreateBlobsQueueDB(\n            [queue],\n            'blobs_queue_db_reader',\n            value_blob_index=0,\n            timeout_secs=0.1,\n        )\n        workspace.RunNetOnce(init_net)\n\n        add_blobs_fun(queue, num_samples)\n\n        net.TensorProtosDBInput(\n            [reader], ['image', 'label'], batch_size=batch_size)\n        workspace.CreateNet(net)\n\n        close_net = core.Net('close_net')\n        close_net.CloseBlobsQueue([queue], [])\n\n        for i in range(int(num_samples / batch_size)):\n            print(\"Running net, iteration {}\".format(i))\n            with timeout_guard.CompleteInTimeOrDie(2.0):\n                workspace.RunNet(net)\n\n            images = workspace.FetchBlob('image')\n            labels = workspace.FetchBlob('label')\n            self.assertEqual(batch_size, len(images))\n            self.assertEqual(batch_size, len(labels))\n            for idx, item in enumerate(images):\n                self.assertEqual(\n                    \"foo{}\".format(i * batch_size + idx).encode('utf-8'), item\n                )\n            for item in labels:\n                self.assertEqual(1, item)\n        workspace.RunNetOnce(close_net)\n\n    def _add_blob_to_queue(self, queue, data, blob, status):\n        workspace.FeedBlob(blob, data)\n        op = core.CreateOperator(\n            \"SafeEnqueueBlobs\",\n            [queue, blob],\n            [blob, status],\n        )\n        workspace.RunOperatorOnce(op)\n\n    def _create_test_tensor_protos(self, idx):\n        item = caffe2_pb2.TensorProtos()\n        data = item.protos.add()\n        data.data_type = core.DataType.STRING\n        data.string_data.append(\"foo{}\".format(idx).encode('utf-8'))\n        label = item.protos.add()\n        label.data_type = core.DataType.INT32\n        label.int32_data.append(1)\n\n        return item.SerializeToString()\n"
  },
  {
    "path": "caffe2/python/operator_test/boolean_mask_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nfrom hypothesis import assume, given\nimport hypothesis.strategies as st\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestBooleanMaskOp(hu.HypothesisTestCase):\n\n    @given(x=hu.tensor(min_dim=1,\n                       max_dim=5,\n                       elements=st.floats(min_value=0.5, max_value=1.0)),\n           **hu.gcs)\n    def test_boolean_mask(self, x, gc, dc):\n        op = core.CreateOperator(\"BooleanMask\",\n                                 [\"data\", \"mask\"],\n                                 \"masked_data\")\n        mask = np.random.choice(a=[True, False], size=x.shape[0])\n\n        def ref(x, mask):\n            return (x[mask],)\n\n        self.assertReferenceChecks(gc, op, [x, mask], ref)\n        self.assertDeviceChecks(dc, op, [x, mask], [0])\n\n    @given(x=hu.tensor(min_dim=1,\n                       max_dim=5,\n                       elements=st.floats(min_value=0.5, max_value=1.0)),\n           **hu.gcs)\n    def test_boolean_mask_indices(self, x, gc, dc):\n        op = core.CreateOperator(\"BooleanMask\",\n                                 [\"data\", \"mask\"],\n                                 [\"masked_data\", \"masked_indices\"])\n        mask = np.random.choice(a=[True, False], size=x.shape[0])\n\n        def ref(x, mask):\n            return (x[mask], np.where(mask)[0])\n\n        self.assertReferenceChecks(gc, op, [x, mask], ref)\n        self.assertDeviceChecks(dc, op, [x, mask], [0])\n\n    @staticmethod\n    def _dtype_conversion(x, dtype, gc, dc):\n        \"\"\"SequenceMask only supports fp16 with CUDA.\"\"\"\n        if dtype == np.float16:\n            assume(gc.device_type == caffe2_pb2.CUDA)\n            dc = [d for d in dc if d.device_type == caffe2_pb2.CUDA]\n            x = x.astype(dtype)\n        return x, dc\n\n    @given(x=hu.tensor(min_dim=2,\n                       max_dim=5,\n                       elements=st.floats(min_value=0.5, max_value=1.0)),\n           dtype=st.sampled_from([np.float32, np.float16]),\n           **hu.gcs)\n    def test_sequence_mask_with_lengths(self, x, dtype, gc, dc):\n        x, dc = self._dtype_conversion(x, dtype, gc, dc)\n        # finite fill value needed for gradient check\n        fill_val = 1e-3 if dtype == np.float16 else 1e-9\n        op = core.CreateOperator(\"SequenceMask\",\n                                 [\"data\", \"lengths\"],\n                                 [\"masked_data\"],\n                                 mode=\"sequence\",\n                                 axis=len(x.shape) - 1,\n                                 fill_val=fill_val)\n        elem_dim = x.shape[-1]\n        leading_dim = 1\n        for dim in x.shape[:-1]:\n            leading_dim *= dim\n        lengths = np.random.randint(0, elem_dim, [leading_dim])\\\n            .astype(np.int32)\n\n        def ref(x, lengths):\n            ref = np.reshape(x, [leading_dim, elem_dim])\n            for i in range(leading_dim):\n                for j in range(elem_dim):\n                    if j >= lengths[i]:\n                        ref[i, j] = fill_val\n            return [ref.reshape(x.shape)]\n\n        self.assertReferenceChecks(gc, op, [x, lengths], ref)\n        self.assertDeviceChecks(dc, op, [x, lengths], [0])\n\n    @given(x=hu.tensor(min_dim=2,\n                       max_dim=5,\n                       elements=st.floats(min_value=0.5, max_value=1.0)),\n           dtype=st.sampled_from([np.float32, np.float16]),\n           **hu.gcs)\n    def test_sequence_mask_with_window(self, x, dtype, gc, dc):\n        x, dc = self._dtype_conversion(x, dtype, gc, dc)\n        # finite fill value needed for gradient check\n        fill_val = 1e-3 if dtype == np.float16 else 1e-9\n        radius = 2\n        op = core.CreateOperator(\"SequenceMask\",\n                                 [\"data\", \"centers\"],\n                                 [\"masked_data\"],\n                                 mode=\"window\",\n                                 radius=radius,\n                                 axis=len(x.shape) - 1,\n                                 fill_val=fill_val)\n        elem_dim = x.shape[-1]\n        leading_dim = 1\n        for dim in x.shape[:-1]:\n            leading_dim *= dim\n        centers = np.random.randint(0, elem_dim, [leading_dim])\\\n            .astype(np.int32)\n\n        def ref(x, centers):\n            ref = np.reshape(x, [leading_dim, elem_dim])\n            for i in range(leading_dim):\n                for j in range(elem_dim):\n                    if j > centers[i] + radius or j < centers[i] - radius:\n                        ref[i, j] = fill_val\n            return [ref.reshape(x.shape)]\n\n        self.assertReferenceChecks(gc, op, [x, centers], ref)\n        self.assertDeviceChecks(dc, op, [x, centers], [0])\n\n        threshold = 0.4 if dtype == np.float16 else 0.005\n        self.assertGradientChecks(gc, op, [x, centers], 0, [0],\n                                  threshold=threshold)\n\n    @given(x=hu.tensor(min_dim=2,\n                       max_dim=5,\n                       elements=st.floats(min_value=0.5, max_value=1.0)),\n           mode=st.sampled_from(['upper', 'lower', 'upperdiag', 'lowerdiag']),\n           dtype=st.sampled_from([np.float32, np.float16]),\n           **hu.gcs)\n    def test_sequence_mask_triangle(self, x, mode, dtype, gc, dc):\n        x, dc = self._dtype_conversion(x, dtype, gc, dc)\n        # finite fill value needed for gradient check\n        fill_val = 1e-3 if dtype == np.float16 else 1e-9\n        op = core.CreateOperator(\"SequenceMask\",\n                                 [\"data\"],\n                                 [\"masked_data\"],\n                                 mode=mode,\n                                 axis=len(x.shape) - 1,\n                                 fill_val=fill_val)\n        elem_dim = x.shape[-1]\n        leading_dim = 1\n        for dim in x.shape[:-1]:\n            leading_dim *= dim\n\n        if mode == 'upper':\n            def compare(i, j):\n                return j > i\n        elif mode == 'lower':\n            def compare(i, j):\n                return j < i\n        elif mode == 'upperdiag':\n            def compare(i, j):\n                return j >= i\n        elif mode == 'lowerdiag':\n            def compare(i, j):\n                return j <= i\n\n        def ref(x):\n            ref = np.reshape(x, [leading_dim, elem_dim])\n            for i in range(leading_dim):\n                for j in range(elem_dim):\n                    if compare(i, j):\n                        ref[i, j] = fill_val\n            return [ref.reshape(x.shape)]\n\n        self.assertReferenceChecks(gc, op, [x], ref)\n        self.assertDeviceChecks(dc, op, [x], [0])\n\n        threshold = 0.4 if dtype == np.float16 else 0.005\n        stepsize = 0.1 if dtype == np.float16 else 0.05\n        self.assertGradientChecks(gc, op, [x], 0, [0],\n                                  threshold=threshold, stepsize=stepsize)\n\n    @given(x=hu.tensor(min_dim=2,\n                       max_dim=5,\n                       elements=st.floats(min_value=0.5, max_value=1.0)),\n           dtype=st.sampled_from([np.float32, np.float16]),\n           **hu.gcs)\n    def test_sequence_mask_batching_lengths(self, x, dtype, gc, dc):\n        x, dc = self._dtype_conversion(x, dtype, gc, dc)\n        # finite fill value needed for gradient check\n        fill_val = 1e-3 if dtype == np.float16 else 1e-9\n        # choose _different_ batch and axis dimensions, w/ axis != 0.\n        axis = 0\n        batch = 0\n        while axis == 0 or axis < batch:\n            inds = np.arange(len(x.shape))\n            np.random.shuffle(inds)\n            batch = inds[0]\n            axis = inds[1]\n        op = core.CreateOperator(\"SequenceMask\",\n                                 [\"data\", \"lengths\"],\n                                 [\"masked_data\"],\n                                 mode='sequence',\n                                 axis=axis,\n                                 fill_val=fill_val,\n                                 batch=batch)\n\n        before = int(np.prod(x.shape[:batch + 1]))\n        between = int(np.prod(x.shape[batch + 1:axis]))\n        after = int(np.prod(x.shape[axis:]))\n\n        lengths = np.random.randint(0, after, [between])\\\n            .astype(np.int32)\n\n        def ref(z, l):\n            w = np.reshape(z, [before, between, after])\n\n            for b in range(before):\n                r = w[b, :, :]\n                for i in range(between):\n                    for j in range(after):\n                        if j >= l[i]:\n                            r[i, j] = fill_val\n            return [w.reshape(z.shape)]\n\n        self.assertReferenceChecks(gc, op, [x, lengths], ref)\n        self.assertDeviceChecks(dc, op, [x, lengths], [0])\n\n        threshold = 0.4 if dtype == np.float16 else 0.005\n        self.assertGradientChecks(gc, op, [x, lengths], 0, [0],\n                                  threshold=threshold)\n\n    @given(x=hu.tensor(min_dim=4,\n                       max_dim=4,\n                       elements=st.floats(min_value=0.5, max_value=1.0)),\n           dtype=st.sampled_from([np.float32, np.float16]),\n           **hu.gcs)\n    def test_sequence_mask_batching_window(self, x, dtype, gc, dc):\n        x, dc = self._dtype_conversion(x, dtype, gc, dc)\n        # finite fill value needed for gradient check\n        fill_val = 1e-3 if dtype == np.float16 else 1e-9\n        radius = 1\n        # choose _different_ batch and axis dimensions, w/ axis != 0.\n        axis = 0\n        batch = 0\n        while axis == 0 or axis < batch:\n            inds = np.arange(len(x.shape))\n            np.random.shuffle(inds)\n            batch = inds[0]\n            axis = inds[1]\n        op = core.CreateOperator(\"SequenceMask\",\n                                 [\"data\", \"centers\"],\n                                 [\"masked_data\"],\n                                 mode='window',\n                                 radius=radius,\n                                 axis=axis,\n                                 fill_val=fill_val,\n                                 batch=batch)\n\n        before = int(np.prod(x.shape[:batch + 1]))\n        between = int(np.prod(x.shape[batch + 1:axis]))\n        after = int(np.prod(x.shape[axis:]))\n\n        centers = np.random.randint(0, after, [between])\\\n            .astype(np.int32)\n\n        def ref(z, c):\n            w = np.reshape(z, [before, between, after])\n\n            for b in range(before):\n                r = w[b, :, :]\n                for i in range(between):\n                    for j in range(after):\n                        if j > c[i] + radius or j < c[i] - radius:\n                            r[i, j] = fill_val\n            return [w.reshape(z.shape)]\n\n        self.assertReferenceChecks(gc, op, [x, centers], ref)\n        self.assertDeviceChecks(dc, op, [x, centers], [0])\n\n        threshold = 0.4 if dtype == np.float16 else 0.005\n        self.assertGradientChecks(gc, op, [x, centers], 0, [0],\n                                  threshold=threshold)\n\n    @given(x=hu.tensor(min_dim=3,\n                       max_dim=5,\n                       elements=st.floats(min_value=0.5, max_value=1.0)),\n           mode=st.sampled_from(['upper', 'lower', 'upperdiag', 'lowerdiag']),\n           dtype=st.sampled_from([np.float32, np.float16]),\n           **hu.gcs)\n    def test_sequence_mask_batching_triangle(self, x, mode, dtype, gc, dc):\n        x, dc = self._dtype_conversion(x, dtype, gc, dc)\n        # finite fill value needed for gradient check\n        fill_val = 1e-3 if dtype == np.float16 else 1e-9\n        # choose _different_ batch and axis dimensions, w/ axis != 0.\n        axis = 0\n        batch = 0\n        while axis == 0 or axis < batch:\n            inds = np.arange(len(x.shape))\n            np.random.shuffle(inds)\n            batch = inds[0]\n            axis = inds[1]\n        op = core.CreateOperator(\"SequenceMask\",\n                                 [\"data\"],\n                                 [\"masked_data\"],\n                                 mode=mode,\n                                 axis=axis,\n                                 fill_val=fill_val,\n                                 batch=batch)\n\n        if mode == 'upper':\n            def compare(i, j):\n                return j > i\n        elif mode == 'lower':\n            def compare(i, j):\n                return j < i\n        elif mode == 'upperdiag':\n            def compare(i, j):\n                return j >= i\n        elif mode == 'lowerdiag':\n            def compare(i, j):\n                return j <= i\n\n        def ref(z):\n            before = int(np.prod(z.shape[:batch + 1]))\n            between = int(np.prod(z.shape[batch + 1:axis]))\n            after = int(np.prod(z.shape[axis:]))\n\n            w = np.reshape(z, [before, between, after])\n\n            for b in range(before):\n                r = w[b, :, :]\n                for i in range(between):\n                    for j in range(after):\n                        if compare(i, j):\n                            r[i, j] = fill_val\n            return [w.reshape(z.shape)]\n\n        self.assertReferenceChecks(gc, op, [x], ref)\n        self.assertDeviceChecks(dc, op, [x], [0])\n\n        threshold = 0.4 if dtype == np.float16 else 0.005\n        stepsize = 0.1 if dtype == np.float16 else 0.05\n        self.assertGradientChecks(gc, op, [x], 0, [0],\n                                  threshold=threshold, stepsize=stepsize)\n\n    @given(x=hu.tensor(min_dim=3,\n                       max_dim=5,\n                       elements=st.floats(min_value=0.5, max_value=1.0)),\n           dtype=st.sampled_from([np.float32, np.float16]),\n           **hu.gcs)\n    def test_sequence_mask_repeated(self, x, dtype, gc, dc):\n        x, dc = self._dtype_conversion(x, dtype, gc, dc)\n        # finite fill value needed for gradient check\n        fill_val = 1e-3 if dtype == np.float16 else 1e-9\n        op = core.CreateOperator(\"SequenceMask\",\n                                 [\"data\", \"lengths\"],\n                                 [\"masked_data\"],\n                                 mode=\"sequence\",\n                                 axis=len(x.shape) - 2,\n                                 repeat_from_axis=-1,\n                                 fill_val=fill_val)\n\n        elem_dim = x.shape[-2]\n        leading_dim = 1\n        for dim in x.shape[:-2]:\n            leading_dim *= dim\n        lengths = np.random.randint(0, elem_dim, [leading_dim])\\\n            .astype(np.int32)\n\n        def ref(x, lengths):\n            ref = np.reshape(x, [leading_dim, elem_dim, -1])\n            for i in range(leading_dim):\n                for j in range(elem_dim):\n                    if j >= lengths[i]:\n                        ref[i, j, :] = fill_val\n            return [ref.reshape(x.shape)]\n\n        self.assertReferenceChecks(gc, op, [x, lengths], ref)\n        self.assertDeviceChecks(dc, op, [x, lengths], [0])\n"
  },
  {
    "path": "caffe2/python/operator_test/boolean_unmask_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestUnmaskOp(hu.HypothesisTestCase):\n    @given(N=st.integers(min_value=2, max_value=20),\n           dtype=st.sampled_from([\n               np.bool_,\n               np.int8,\n               np.int16,\n               np.int32,\n               np.int64,\n               np.uint8,\n               np.uint16,\n               np.float16,\n               np.float32,\n               np.float64]),\n           **hu.gcs)\n    def test(self, N, dtype, gc, dc):\n        if dtype is np.bool_:\n            all_value = np.random.choice(a=[True, False], size=N)\n        else:\n            all_value = (np.random.rand(N) * N).astype(dtype)\n\n        M = np.random.randint(1, N)\n        split = sorted(np.random.randint(1, N, size=M))\n        indices = np.random.permutation(N)\n        pieces = np.split(indices, split)\n\n        def ref(*args, **kwargs):\n            return (all_value,)\n\n        inputs = []\n        inputs_names = []\n        for i, piece in enumerate(pieces):\n            piece.sort()\n            mask = np.zeros(N, dtype=np.bool_)\n            mask[piece] = True\n            values = all_value[piece]\n            inputs.extend([mask, values])\n            inputs_names.extend([\"mask%d\" % i, \"value%d\" % i])\n\n        op = core.CreateOperator(\n            'BooleanUnmask',\n            inputs_names,\n            'output')\n\n        self.assertReferenceChecks(gc, op, inputs, ref)\n        self.assertDeviceChecks(dc, op, inputs, [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/cast_op_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\nfrom hypothesis import given\nimport numpy as np\n\n\nclass TestCastOp(hu.HypothesisTestCase):\n\n    @given(**hu.gcs)\n    def test_cast_int_float(self, gc, dc):\n        data = np.random.rand(5, 5).astype(np.int32)\n        # from int to float\n        op = core.CreateOperator('Cast', 'data', 'data_cast', to=1, from_type=2)\n        self.assertDeviceChecks(dc, op, [data], [0])\n        # This is actually 0\n        self.assertGradientChecks(gc, op, [data], 0, [0])\n\n    @given(**hu.gcs)\n    def test_cast_int_float_empty(self, gc, dc):\n        data = np.random.rand(0).astype(np.int32)\n        # from int to float\n        op = core.CreateOperator('Cast', 'data', 'data_cast', to=1, from_type=2)\n        self.assertDeviceChecks(dc, op, [data], [0])\n        # This is actually 0\n        self.assertGradientChecks(gc, op, [data], 0, [0])\n"
  },
  {
    "path": "caffe2/python/operator_test/ceil_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport caffe2.python.hypothesis_test_util as hu\nimport numpy as np\n\nimport unittest\n\n\nclass TestCeil(hu.HypothesisTestCase):\n\n    @given(X=hu.tensor(),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs)\n    def test_ceil(self, X, gc, dc, engine):\n        op = core.CreateOperator(\"Ceil\", [\"X\"], [\"Y\"], engine=engine)\n\n        def ceil_ref(X):\n            return (np.ceil(X),)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=ceil_ref)\n\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/channel_backprop_stats_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom hypothesis import assume, given\nimport hypothesis.strategies as st\nimport numpy as np\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\nfrom caffe2.proto import caffe2_pb2\nimport unittest\n\n\nclass TestChannelBackpropStats(hu.HypothesisTestCase):\n    @given(\n        size=st.integers(7, 10),\n        inputChannels=st.integers(1, 10),\n        batchSize=st.integers(1, 3),\n        **hu.gcs\n    )\n    def testChannelBackpropStats(self, size, inputChannels, batchSize, gc, dc):\n\n        assume(gc.device_type != caffe2_pb2.CUDA)\n        op = core.CreateOperator(\n            \"ChannelBackpropStats\",\n            [\"X\", \"mean\", \"invStdDev\", \"outputGrad\"],\n            [\"scaleGrad\", \"biasGrad\"],\n        )\n\n        def referenceChannelBackpropStatsTest(X, mean, invStdDev, outputGrad):\n            scaleGrad = np.zeros(inputChannels)\n            biasGrad = np.zeros(inputChannels)\n            for n in range(batchSize):\n                for c in range(inputChannels):\n                    for h in range(size):\n                        for w in range(size):\n                            biasGrad[c] += outputGrad[n, c, h, w]\n                            scaleGrad[c] += (\n                                X[n, c, h, w] - mean[c]\n                            ) * invStdDev[c] * outputGrad[n, c, h, w]\n            return scaleGrad, biasGrad\n\n        X = np.random.rand(batchSize, inputChannels, size, size)\\\n                     .astype(np.float32) - 0.5\n        sums = np.sum(X, axis=(0, 2, 3), keepdims=False)\n        numPixels = size * size * batchSize\n        mean = sums / numPixels\n        sumsq = np.sum(X**2, axis=(0, 2, 3), keepdims=False)\n        var = ((sumsq -\n                (sums * sums) / numPixels) / numPixels).astype(np.float32)\n        invStdDev = 1 / np.sqrt(var)\n        outputGrad = np.random.rand(batchSize, inputChannels, size, size)\\\n            .astype(np.float32) - 0.5\n        self.assertReferenceChecks(\n            gc, op, [X, mean, invStdDev, outputGrad],\n            referenceChannelBackpropStatsTest\n        )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/channel_shuffle_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nimport caffe2.python.hypothesis_test_util as hu\nfrom caffe2.python import core\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nclass ChannelShuffleOpsTest(hu.HypothesisTestCase):\n    @given(\n        channels_per_group=st.integers(min_value=1, max_value=5),\n        groups=st.integers(min_value=1, max_value=5),\n        n=st.integers(min_value=1, max_value=2),\n        **hu.gcs)\n    def test_channel_shuffle(self, channels_per_group, groups, n, gc, dc):\n        X = np.random.randn(\n            n, channels_per_group * groups, 5, 6).astype(np.float32)\n\n        op = core.CreateOperator(\"ChannelShuffle\", [\"X\"], [\"Y\"],\n                                 group=groups, kernel=1)\n\n        def channel_shuffle_ref(X):\n            Y_r = X.reshape(X.shape[0],\n                            groups,\n                            X.shape[1] // groups,\n                            X.shape[2],\n                            X.shape[3])\n            Y_trns = Y_r.transpose((0, 2, 1, 3, 4))\n            return (Y_trns.reshape(X.shape),)\n\n        self.assertReferenceChecks(gc, op, [X], channel_shuffle_ref)\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n        self.assertDeviceChecks(dc, op, [X], [0])\n"
  },
  {
    "path": "caffe2/python/operator_test/channel_stats_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom hypothesis import assume, given\nimport hypothesis.strategies as st\nimport numpy as np\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\nfrom caffe2.proto import caffe2_pb2\nimport unittest\n\n\nclass TestChannelStats(hu.HypothesisTestCase):\n    @given(\n        size=st.integers(7, 10),\n        inputChannels=st.integers(1, 10),\n        batchSize=st.integers(1, 3),\n        **hu.gcs\n    )\n    def testChannelStats(self, size, inputChannels, batchSize, gc, dc):\n\n        op = core.CreateOperator(\n            \"ChannelStats\",\n            [\"X\"],\n            [\"sum\", \"sumsq\"],\n        )\n\n        def referenceChannelStatsTest(X):\n            sums = np.sum(X, axis=(0, 2, 3), keepdims=False)\n            sumsq = np.zeros(inputChannels)\n            sumsq = np.sum(X**2, axis=(0, 2, 3), keepdims=False)\n            return sums, sumsq\n\n        X = np.random.rand(batchSize, inputChannels, size, size)\\\n                .astype(np.float32) - 0.5\n        self.assertReferenceChecks(gc, op, [X], referenceChannelStatsTest)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/checkpoint_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nimport os\nimport shutil\nimport tempfile\nimport unittest\n\n\nclass CheckpointTest(unittest.TestCase):\n    \"\"\"A simple test case to make sure that the checkpoint behavior is correct.\n    \"\"\"\n\n    def testCheckpoint(self):\n        temp_root = tempfile.mkdtemp()\n        net = core.Net(\"test_checkpoint\")\n        # Note(jiayq): I am being a bit lazy here and am using the old iter\n        # convention that does not have an input. Optionally change it to the\n        # new style if needed.\n        net.Iter([], \"iter\")\n        net.ConstantFill([], \"value\", shape=[1, 2, 3])\n        net.Checkpoint([\"iter\", \"value\"], [],\n                     db=os.path.join(temp_root, \"test_checkpoint_at_%05d\"),\n                     db_type=\"leveldb\", every=10, absolute_path=True)\n        self.assertTrue(workspace.CreateNet(net))\n        for i in range(100):\n            self.assertTrue(workspace.RunNet(\"test_checkpoint\"))\n        for i in range(1, 10):\n            # Print statements are only for debugging purposes.\n            # print(\"Asserting %d\" % i)\n            # print(os.path.join(temp_root, \"test_checkpoint_at_%05d\" % (i * 10)))\n            self.assertTrue(os.path.exists(\n                os.path.join(temp_root, \"test_checkpoint_at_%05d\" % (i * 10))))\n\n        # Finally, clean up.\n        shutil.rmtree(temp_root)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/clip_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestClip(hu.HypothesisTestCase):\n    @given(X=hu.tensor(),\n           min_=st.floats(min_value=-1, max_value=0),\n           max_=st.floats(min_value=0, max_value=1),\n           inplace=st.booleans(),\n           **hu.gcs)\n    def test_clip(self, X, min_, max_, inplace, gc, dc):\n        # go away from the origin point to avoid kink problems\n\n        X[np.abs(X - min_) < 0.05] += 0.1\n        X[np.abs(X - max_) < 0.05] += 0.1\n\n        def clip_ref(X):\n            X = X.clip(min_, max_)\n            return (X,)\n\n        op = core.CreateOperator(\n            \"Clip\",\n            [\"X\"], [\"Y\" if not inplace else \"X\"],\n            min=min_,\n            max=max_)\n        self.assertReferenceChecks(gc, op, [X], clip_ref)\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X], [0])\n        # Gradient check wrt X\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/clip_tensor_op_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestClipTensorByScalingOp(hu.HypothesisTestCase):\n\n    @given(n=st.integers(5, 8), d=st.integers(2, 4),\n           threshold=st.floats(0.1, 10),\n           inplace=st.booleans(),\n           **hu.gcs_cpu_only)\n    def test_clip_tensor_by_scaling(self, n, d, threshold, inplace, gc, dc):\n\n        tensor = np.random.rand(n, d).astype(np.float32)\n        val = np.array(np.linalg.norm(tensor))\n\n        def clip_tensor_by_scaling_ref(tensor_data, val_data):\n            if val_data > threshold:\n                ratio = threshold / float(val_data)\n                tensor_data = tensor_data * ratio\n\n            return [tensor_data]\n\n        op = core.CreateOperator(\n            \"ClipTensorByScaling\",\n            [\"tensor\", \"val\"],\n            ['Y'] if not inplace else [\"tensor\"],\n            threshold=threshold,\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[tensor, val],\n            reference=clip_tensor_by_scaling_ref,\n        )\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/concat_split_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import unicode_literals\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nimport hypothesis.strategies as st\nimport unittest\nimport caffe2.python.hypothesis_test_util as hu\nfrom caffe2.python import core\nfrom hypothesis import given\n\n\n@st.composite\ndef _tensor_splits(draw, add_axis=False):\n    \"\"\"Generates (axis, split_info, tensor_splits) tuples.\"\"\"\n    tensor = draw(hu.tensor(min_value=4))  # Each dim has at least 4 elements.\n    axis = draw(st.integers(-len(tensor.shape), len(tensor.shape) - 1))\n    if add_axis:\n        # Simple case: get individual slices along one axis, where each of them\n        # is (N-1)-dimensional. The axis will be added back upon concatenation.\n        return (\n            axis,\n            np.ones(tensor.shape[axis], dtype=np.int32),\n            [\n                np.array(tensor.take(i, axis=axis))\n                for i in range(tensor.shape[axis])\n            ]\n        )\n    else:\n        # General case: pick some (possibly consecutive, even non-unique)\n        # indices at which we will split the tensor, along the given axis.\n        splits = sorted(draw(\n            st.lists(elements=st.integers(0, tensor.shape[axis]), max_size=4)\n        ) + [0, tensor.shape[axis]])\n        return (\n            axis,\n            np.array(np.diff(splits), dtype=np.int32),\n            [\n                tensor.take(range(splits[i], splits[i + 1]), axis=axis)\n                for i in range(len(splits) - 1)\n            ],\n        )\n\n\nclass TestConcatSplitOps(hu.HypothesisTestCase):\n    @given(tensor_splits=_tensor_splits(),\n           **hu.gcs)\n    def test_concat(self, tensor_splits, gc, dc):\n        axis, _, splits = tensor_splits\n\n        op = core.CreateOperator(\n            \"Concat\",\n            ['X_{}'.format(i) for i in range(len(splits))],\n            ['concat_result', 'split_info'],\n            axis=axis\n        )\n\n        self.assertReferenceChecks(\n            gc, op, splits, lambda *splits: (\n                np.concatenate(splits, axis=axis),\n                np.array([a.shape[axis] for a in splits])\n            )\n        )\n        self.assertDeviceChecks(dc, op, splits, [0, 1])\n        self.assertGradientChecks(gc, op, splits, 0, [0])\n\n    @given(tensor_splits=_tensor_splits(add_axis=True),\n           **hu.gcs)\n    def test_concat_add_axis(self, tensor_splits, gc, dc):\n        axis, _, splits = tensor_splits\n\n        op = core.CreateOperator(\n            \"Concat\",\n            ['X_{}'.format(i) for i in range(len(splits))],\n            ['concat_result', 'split_info'],\n            axis=axis,\n            add_axis=1\n        )\n\n        self.assertReferenceChecks(\n            gc, op, splits, lambda *splits: (\n                np.concatenate(\n                    [np.expand_dims(a, axis) for a in splits],\n                    axis=axis\n                ),\n                np.array([1] * len(splits))\n            )\n        )\n        self.assertDeviceChecks(dc, op, splits, [0, 1])\n        for i in range(len(splits)):\n            self.assertGradientChecks(gc, op, splits, i, [0])\n\n    @given(tensor_splits=_tensor_splits(),\n           split_as_arg=st.booleans(),\n           **hu.gcs)\n    def test_split(self, tensor_splits, split_as_arg, gc, dc):\n        axis, split_info, splits = tensor_splits\n\n        split_as_arg = True\n\n        if split_as_arg:\n            input_names = ['input']\n            input_tensors = [np.concatenate(splits, axis=axis)]\n            kwargs = dict(axis=axis, split=split_info)\n        else:\n            input_names = ['input', 'split']\n            input_tensors = [np.concatenate(splits, axis=axis), split_info]\n            kwargs = dict(axis=axis)\n\n        op = core.CreateOperator(\n            \"Split\",\n            input_names,\n            ['X_{}'.format(i) for i in range(len(split_info))],\n            **kwargs\n        )\n\n        def split_ref(input, split=split_info):\n            s = np.cumsum([0] + list(split))\n            return [\n                np.array(input.take(np.arange(s[i], s[i + 1]), axis=axis))\n                for i in range(len(split))\n            ]\n        outputs_with_grad = range(len(split_info))\n        self.assertReferenceChecks(gc, op, input_tensors, split_ref)\n        self.assertDeviceChecks(dc, op, input_tensors, outputs_with_grad)\n        self.assertGradientChecks(gc, op, input_tensors, 0, outputs_with_grad)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/conditional_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nfrom hypothesis import given\nimport hypothesis.strategies as st\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestConditionalOp(hu.HypothesisTestCase):\n    @given(rows_num=st.integers(1, 10000), **hu.gcs_cpu_only)\n    def test_conditional(self, rows_num, gc, dc):\n        op = core.CreateOperator(\n            \"Conditional\", [\"condition\", \"data_t\", \"data_f\"], \"output\"\n        )\n        data_t = np.random.random((rows_num, 10, 20)).astype(np.float32)\n        data_f = np.random.random((rows_num, 10, 20)).astype(np.float32)\n        condition = np.random.choice(a=[True, False], size=rows_num)\n\n        def ref(condition, data_t, data_f):\n            output = [\n                data_t[i] if condition[i] else data_f[i]\n                for i in range(rows_num)\n            ]\n            return (output,)\n\n        self.assertReferenceChecks(gc, op, [condition, data_t, data_f], ref)\n"
  },
  {
    "path": "caffe2/python/operator_test/conv_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport collections\nimport functools\n\nimport numpy as np\nfrom hypothesis import assume, given\nimport hypothesis.strategies as st\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import brew, core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nfrom caffe2.python.model_helper import ModelHelper\n\n\ndef _cudnn_supports(\n        dilation=False,\n        nhwc=False,\n        backward=False,\n):\n    \"\"\"Return True if cuDNN supports this configuration.\"\"\"\n    v = workspace.GetCuDNNVersion()\n    if backward:\n        if nhwc:\n            # nhwc isn't supported in backward ops.\n            return False\n    else:\n        # Forward mode.\n        if dilation and v < 6000:\n            # Dilation not supported until v6\n            return False\n        if dilation and nhwc:\n            # Dilation and NHWC not supported together\n            return False\n    return True\n\n\nclass TestConvolution(hu.HypothesisTestCase):\n    # CUDNN does NOT support different padding values and we skip it\n    @given(op_type=st.sampled_from([\"Conv\", \"Conv2D\"]),\n           stride_h=st.integers(1, 3),\n           stride_w=st.integers(1, 3),\n           pad_t=st.integers(0, 3),\n           pad_l=st.integers(0, 3),\n           pad_b=st.integers(0, 3),\n           pad_r=st.integers(0, 3),\n           kernel=st.integers(3, 5),\n           size=st.integers(1, 8),\n           input_channels=st.integers(1, 3),\n           output_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           engine=st.sampled_from([\"\", \"EIGEN\"]),\n           shared_buffer=st.booleans(),\n           use_bias=st.booleans(),\n           **hu.gcs)\n    def test_convolution_separate_stride_pad_gradients(self, op_type,\n                                                       stride_h, stride_w,\n                                                       pad_t, pad_l, pad_b,\n                                                       pad_r, kernel, size,\n                                                       input_channels,\n                                                       output_channels,\n                                                       batch_size, order,\n                                                       engine, shared_buffer,\n                                                       use_bias,\n                                                       gc, dc):\n        op = core.CreateOperator(\n            op_type,\n            [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n            [\"Y\"],\n            stride_h=stride_h,\n            stride_w=stride_w,\n            pad_t=pad_t,\n            pad_l=pad_l,\n            pad_b=pad_b,\n            pad_r=pad_r,\n            kernel=kernel,\n            order=order,\n            engine=engine,\n            shared_buffer=int(shared_buffer),\n        )\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        w = np.random.rand(\n            output_channels, kernel, kernel, input_channels).astype(np.float32)\\\n            - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        if order == \"NCHW\":\n            X = X.transpose((0, 3, 1, 2))\n            w = w.transpose((0, 3, 1, 2))\n\n        inputs = [X, w, b] if use_bias else [X, w]\n\n        # Error handling path.\n        if size + pad_r + pad_l < kernel or size + pad_t + pad_b < kernel:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n\n        self.assertDeviceChecks(dc, op, inputs, [0])\n        for i in range(len(inputs)):\n            self.assertGradientChecks(gc, op, inputs, i, [0])\n\n    # CUDNN does NOT support different padding values and we skip it\n    @given(op_type=st.sampled_from([\"Conv\", \"Conv2D\"]),\n           stride_h=st.integers(1, 3),\n           stride_w=st.integers(1, 3),\n           pad_t=st.integers(0, 3),\n           pad_l=st.integers(0, 3),\n           pad_b=st.integers(0, 3),\n           pad_r=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           size=st.integers(7, 10),\n           input_channels=st.integers(1, 8),\n           output_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 3),\n           engine=st.sampled_from([\"\", \"EIGEN\"]),\n           use_bias=st.booleans(),\n           **hu.gcs)\n    def test_convolution_separate_stride_pad_layout(self, op_type,\n                                                    stride_h, stride_w,\n                                                    pad_t, pad_l, pad_b, pad_r,\n                                                    kernel, size,\n                                                    input_channels,\n                                                    output_channels, batch_size,\n                                                    engine, use_bias, gc, dc):\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        w = np.random.rand(\n            output_channels, kernel, kernel, input_channels).astype(np.float32)\\\n            - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        outputs = {}\n        for order in [\"NCHW\", \"NHWC\"]:\n            op = core.CreateOperator(\n                op_type,\n                [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n                [\"Y\"],\n                stride_h=stride_h,\n                stride_w=stride_w,\n                kernel=kernel,\n                pad_t=pad_t,\n                pad_l=pad_l,\n                pad_b=pad_b,\n                pad_r=pad_r,\n                order=order,\n                engine=engine,\n                device_option=gc,\n            )\n            if order == \"NCHW\":\n                X_f = X.transpose((0, 3, 1, 2))\n                w_f = w.transpose((0, 3, 1, 2))\n            else:\n                X_f = X\n                w_f = w\n            self.ws.create_blob(\"X\").feed(X_f, device_option=gc)\n            self.ws.create_blob(\"w\").feed(w_f, device_option=gc)\n            self.ws.create_blob(\"b\").feed(b, device_option=gc)\n            self.ws.run(op)\n            outputs[order] = self.ws.blobs[\"Y\"].fetch()\n        np.testing.assert_allclose(\n            outputs[\"NCHW\"],\n            outputs[\"NHWC\"].transpose((0, 3, 1, 2)),\n            atol=1e-4,\n            rtol=1e-4)\n\n    @given(op_type=st.sampled_from([\"Conv\", \"Conv2D\"]),\n           stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           dilation=st.integers(1, 3),\n           size=st.integers(7, 10),\n           input_channels=st.integers(1, 8),\n           output_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           engine=st.sampled_from([\"\", \"CUDNN\", \"MKLDNN\"]),\n           use_bias=st.booleans(),\n           **hu.gcs)\n    def test_convolution_gradients(self, op_type, stride, pad, kernel, dilation,\n                                   size, input_channels, output_channels,\n                                   batch_size, order, engine, use_bias, gc, dc):\n        dkernel = dilation * (kernel - 1) + 1\n\n        if engine == 'CUDNN':\n            assume(_cudnn_supports(dilation=(dilation > 1),\n                                   nhwc=(order == 'NHWC'),\n                                   backward=True))\n\n        assume(engine != \"MKLDNN\" or use_bias is True)\n\n        op = core.CreateOperator(\n            op_type,\n            [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n            [\"Y\"],\n            stride=stride,\n            kernel=kernel,\n            dilation=dilation,\n            pad=pad,\n            order=order,\n            engine=engine,\n        )\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        w = np.random.rand(\n            output_channels, kernel, kernel, input_channels).astype(np.float32)\\\n            - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        if order == \"NCHW\":\n            X = X.transpose((0, 3, 1, 2))\n            w = w.transpose((0, 3, 1, 2))\n\n        inputs = [X, w, b] if use_bias else [X, w]\n        # Error handling path.\n        if size + pad + pad < dkernel or size + pad + pad < dkernel:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n\n        self.assertDeviceChecks(dc, op, inputs, [0])\n        for i in range(len(inputs)):\n            self.assertGradientChecks(gc, op, inputs, i, [0])\n\n    def _nd_convolution_nchw(self, n, input_channels, output_channels,\n                            batch_size, stride, size, kernel, dilation, pad,\n                            use_bias, gc, dc):\n        dkernel = dilation * (kernel - 1) + 1\n        for op_type in [\"Conv\", \"Conv\" + str(n) + \"D\"]:\n            op = core.CreateOperator(\n                op_type,\n                [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n                [\"Y\"],\n                strides=[stride] * n,\n                kernels=[kernel] * n,\n                dilations=[dilation] * n,\n                pads=[pad] * n * 2,\n                order=\"NCHW\",\n                engine=\"\",\n            )\n\n            input_dims = [batch_size, input_channels]\n            input_dims.extend([size] * n)\n            filter_dims = [output_channels, input_channels]\n            filter_dims.extend([kernel] * n)\n\n            X = np.random.rand(*input_dims).astype(np.float32) - 0.5\n            w = np.random.rand(*filter_dims).astype(np.float32) - 0.5\n            b = np.random.rand(output_channels).astype(np.float32) - 0.5\n\n            inputs = [X, w, b] if use_bias else [X, w]\n\n            if size + pad + pad < dkernel or size + pad + pad < dkernel:\n                with self.assertRaises(RuntimeError):\n                    self.assertDeviceChecks(dc, op, inputs, [0])\n                return\n\n            self.assertDeviceChecks(dc, op, inputs, [0])\n            for i in range(len(inputs)):\n                self.assertGradientChecks(gc, op, inputs, i, [0])\n\n    @given(input_channels=st.integers(1, 3),\n           output_channels=st.integers(1, 2),\n           batch_size=st.integers(1, 3),\n           stride=st.integers(1, 3),\n           size=st.integers(7, 10),\n           kernel=st.integers(1, 2),\n           dilation=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           use_bias=st.booleans(),\n           **hu.gcs)\n    def test_1d_convolution_nchw(self, input_channels, output_channels,\n                                 batch_size, stride, size, kernel, dilation,\n                                 pad, use_bias, gc, dc):\n        self._nd_convolution_nchw(\n            1, input_channels, output_channels, batch_size, stride, size,\n            kernel, dilation, pad, use_bias, gc, dc\n        )\n\n    @given(input_channels=st.integers(1, 2),\n           output_channels=st.integers(1, 2),\n           batch_size=st.integers(1, 2),\n           stride=st.integers(1, 2),\n           size=st.integers(4, 5),\n           kernel=st.integers(1, 2),\n           dilation=st.integers(1, 2),\n           pad=st.integers(0, 2),\n           use_bias=st.booleans(),\n           **hu.gcs)\n    def test_3d_convolution_nchw(self, input_channels, output_channels,\n                                 batch_size, stride, size, kernel, dilation,\n                                 pad, use_bias, gc, dc):\n        self._nd_convolution_nchw(\n            3, input_channels, output_channels, batch_size, stride, size,\n            kernel, dilation, pad, use_bias, gc, dc\n        )\n\n    @given(op_type=st.sampled_from([\"Conv\", \"Conv3D\"]),\n           batch_size=st.integers(1, 2),\n           stride=st.integers(1, 2),\n           size=st.integers(3, 5),\n           kernel=st.integers(1, 2),\n           dilation=st.integers(1, 2),\n           pad=st.integers(0, 2),\n           use_bias=st.booleans(),\n           **hu.gcs)\n    def test_3d_convolution_cudnn_nchw(self, op_type, batch_size, stride, size,\n                                       kernel, dilation, pad, use_bias, gc, dc):\n        input_channels = 1\n        output_channels = 1\n        n = 3\n        dkernel = dilation * (kernel - 1) + 1\n        order = \"NCHW\"\n\n        op = core.CreateOperator(\n            op_type,\n            [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n            [\"Y\"],\n            strides=[stride] * n,\n            kernels=[kernel] * n,\n            dilations=[dilation] * n,\n            pads=[pad] * n * 2,\n            order=order,\n            engine=\"CUDNN\",\n        )\n\n        input_dims = [batch_size, input_channels]\n        input_dims.extend([size] * n)\n        filter_dims = [output_channels, input_channels]\n        filter_dims.extend([kernel] * n)\n        X = np.random.rand(*input_dims).astype(np.float32) - 0.5\n        w = np.random.rand(*filter_dims).astype(np.float32) - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n\n        inputs = [X, w, b] if use_bias else [X, w]\n\n        if size + pad + pad < dkernel or size + pad + pad < dkernel:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n\n        self.assertDeviceChecks(dc, op, inputs, [0])\n        for i in range(len(inputs)):\n            self.assertGradientChecks(gc, op, inputs, i, [0])\n\n    @given(op_type=st.sampled_from([\"Conv\", \"Conv2D\"]),\n           stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           dilation=st.integers(1, 3),\n           size=st.integers(7, 10),\n           input_channels=st.integers(1, 8),\n           output_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 3),\n           use_bias=st.booleans(),\n           **hu.gcs)\n    def test_convolution_layout(self, op_type, stride, pad, kernel, dilation,\n                                size, input_channels, output_channels,\n                                batch_size, use_bias, gc, dc):\n        assume(size >= dilation * (kernel - 1) + 1)\n\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        w = np.random.rand(\n            output_channels, kernel, kernel, input_channels).astype(np.float32)\\\n            - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        Output = collections.namedtuple(\"Output\", [\"Y\", \"engine\", \"order\"])\n        outputs = []\n\n        for order in [\"NCHW\", \"NHWC\"]:\n            engine_list = ['']\n            if _cudnn_supports(dilation=(dilation > 1), nhwc=(order == 'NHWC')):\n                engine_list.append('CUDNN')\n\n            for engine in engine_list:\n                op = core.CreateOperator(\n                    op_type,\n                    [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n                    [\"Y\"],\n                    stride=stride,\n                    kernel=kernel,\n                    dilation=dilation,\n                    pad=pad,\n                    order=order,\n                    engine=engine,\n                    device_option=gc,\n                )\n                if order == \"NCHW\":\n                    X_f = X.transpose((0, 3, 1, 2))\n                    w_f = w.transpose((0, 3, 1, 2))\n                else:\n                    X_f = X\n                    w_f = w\n                self.assertDeviceChecks(\n                    dc,\n                    op,\n                    [X_f, w_f, b] if use_bias else [X_f, w_f],\n                    [0])\n                self.ws.create_blob(\"X\").feed(X_f, device_option=gc)\n                self.ws.create_blob(\"w\").feed(w_f, device_option=gc)\n                self.ws.create_blob(\"b\").feed(b, device_option=gc)\n                self.ws.run(op)\n                outputs.append(Output(\n                    Y=self.ws.blobs[\"Y\"].fetch(), engine=engine, order=order))\n\n        def canonical(o):\n            if o.order == \"NHWC\":\n                return o.Y.transpose((0, 3, 1, 2))\n            else:\n                return o.Y\n\n        for o in outputs:\n            np.testing.assert_allclose(\n                canonical(outputs[0]),\n                canonical(o),\n                atol=1e-4,\n                rtol=1e-4)\n\n    @given(num_workers=st.integers(1, 4),\n           net_type=st.sampled_from(\n               [\"simple\", \"dag\"] +\n               ([\"async_dag\"] if workspace.has_gpu_support else [])),\n           do=st.sampled_from(hu.device_options),\n           engine=st.sampled_from([\"CUDNN\", \"\"]))\n    def test_convolution_sync(self, net_type, num_workers, do, engine):\n        m = ModelHelper(name=\"test_model\")\n        n = 1\n        d = 2\n        depth = 3\n        iters = 5\n        h = 5\n        w = 5\n        workspace.ResetWorkspace()\n\n        use_cudnn = (engine == 'CUDNN')\n\n        np.random.seed(1701)\n        # Build a binary tree of conv layers, summing at each node.\n        for i in reversed(range(depth)):\n            for j in range(2 ** i):\n                bottom_1 = \"{}_{}\".format(i + 1, 2 * j)\n                bottom_2 = \"{}_{}\".format(i + 1, 2 * j + 1)\n                mid_1 = \"{}_{}_m\".format(i + 1, 2 * j)\n                mid_2 = \"{}_{}_m\".format(i + 1, 2 * j + 1)\n                top = \"{}_{}\".format(i, j)\n                w1, b1, w2, b2 = np.random.randn(4).tolist()\n                brew.conv(\n                    m, bottom_1, mid_1,\n                    dim_in=d, dim_out=d,\n                    kernel=3,\n                    weight_init=('ConstantFill', dict(value=w1)),\n                    bias_init=('ConstantFill', dict(value=b1)),\n                    cudnn_state=np.random.randint(0, 3),\n                    stride=1,\n                    pad=1,\n                    deterministic=1,\n                    use_cudnn=use_cudnn,\n                    engine=engine)\n                brew.conv(\n                    m, bottom_2, mid_2,\n                    dim_in=d, dim_out=d,\n                    kernel=3,\n                    stride=1,\n                    pad=1,\n                    weight_init=('ConstantFill', dict(value=w2)),\n                    bias_init=('ConstantFill', dict(value=b2)),\n                    deterministic=1,\n                    cudnn_state=np.random.randint(0, 3),\n                    use_cudnn=use_cudnn,\n                    engine=engine)\n                m.net.Sum([mid_1, mid_2], top)\n\n        m.net.Flatten([\"0_0\"], [\"0_0_flat\"])\n        m.net.SquaredL2Distance([\"0_0_flat\", \"label\"], \"xent\")\n        m.net.AveragedLoss(\"xent\", \"loss\")\n        input_to_grad = m.AddGradientOperators([\"loss\"])\n        m.Proto().device_option.CopyFrom(do)\n        m.param_init_net.Proto().device_option.CopyFrom(do)\n        m.Proto().type = net_type\n        m.Proto().num_workers = num_workers\n        self.ws.run(m.param_init_net)\n\n        def run():\n            import numpy as np\n            np.random.seed(1701)\n            input_blobs = [\"{}_{}\".format(depth, j) for j in range(2 ** depth)]\n            for input_blob in input_blobs:\n                self.ws.create_blob(input_blob).feed(\n                    np.random.randn(n, d, h, w).astype(np.float32),\n                    device_option=do)\n                self.ws.create_blob(\"label\").feed(\n                    np.random.randn(n, d * h * w).astype(np.float32),\n                    device_option=do)\n            self.ws.run(m.net)\n            gradients = [\n                self.ws.blobs[str(input_to_grad[input_blob])].fetch()\n                for input_blob in input_blobs]\n            return gradients\n\n        outputs = [run() for _ in range(iters)]\n        for output in outputs[1:]:\n            np.testing.assert_array_equal(outputs[0], output)\n            np.testing.assert_allclose(\n                np.sum(np.square(output)),\n                1763719461732352.0,\n                rtol=1e-5)\n\n    def test_use_cudnn_engine_interactions(self):\n        \"\"\"Make sure the use_cudnn and engine kwargs work as expected.\"\"\"\n        for model_default in [None, True, False]:\n            arg_scope = {}\n            if model_default is not None:\n                arg_scope['use_cudnn'] = model_default\n            else:\n                model_default = True  # the default\n\n            model = ModelHelper(arg_scope=arg_scope)\n            self.assertEqual(model.arg_scope['use_cudnn'], model_default)\n            f = functools.partial(brew.conv, model,\n                                  'conv_in', 'conv_out', 10, 10, 5)\n\n            for op_cudnn in [None, True, False]:\n                for op_engine in [None, '', 'CUDNN']:\n                    kwargs = {}\n                    if op_cudnn is not None:\n                        kwargs['use_cudnn'] = op_cudnn\n                    else:\n                        op_cudnn = False  # the default\n                    if op_engine is not None:\n                        kwargs['engine'] = op_engine\n\n                    calculated_cudnn = kwargs.get('use_cudnn', model_default)\n                    expected_engine = kwargs.get(\n                        'engine',\n                        'CUDNN' if calculated_cudnn else '')\n\n                    if ((calculated_cudnn is True and op_engine == '') or\n                            (calculated_cudnn is False and op_engine == 'CUDNN')):\n                        with self.assertRaises(ValueError):\n                            f(**kwargs)\n                    else:\n                        f(**kwargs)\n                        self.assertEqual(model.Proto().op[-1].engine,\n                                         expected_engine)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/conv_transpose_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nfrom hypothesis import assume, given, settings\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestConvolutionTranspose(hu.HypothesisTestCase):\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           adj=st.integers(0, 2),\n           size=st.integers(7, 10),\n           input_channels=st.integers(1, 8),\n           output_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 3),\n           engine=st.sampled_from([\"\", \"CUDNN\", \"BLOCK\"]),\n           shared_buffer=st.booleans(),\n           use_bias=st.booleans(),\n           **hu.gcs)\n    def test_convolution_transpose_layout_legacy_args(\n            self, stride, pad, kernel, adj,\n            size, input_channels,\n            output_channels, batch_size,\n            engine, shared_buffer, use_bias, gc, dc):\n        assume(adj < stride)\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        w = np.random.rand(\n            input_channels, kernel, kernel, output_channels)\\\n            .astype(np.float32) - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        outputs = {}\n        for order in [\"NCHW\", \"NHWC\"]:\n            op = core.CreateOperator(\n                \"ConvTranspose\",\n                [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n                [\"Y\"],\n                stride=stride,\n                kernel=kernel,\n                pad=pad,\n                adj=adj,\n                order=order,\n                engine=engine,\n                shared_buffer=int(shared_buffer),\n                device_option=gc,\n            )\n            if order == \"NCHW\":\n                X_f = X.transpose((0, 3, 1, 2))\n                w_f = w.transpose((0, 3, 1, 2))\n            else:\n                X_f = X\n                w_f = w\n            self.assertDeviceChecks(\n                dc,\n                op,\n                [X_f, w_f, b] if use_bias else [X_f, w_f],\n                [0])\n            self.ws.create_blob(\"X\").feed(X_f, device_option=gc)\n            self.ws.create_blob(\"w\").feed(w_f, device_option=gc)\n            self.ws.create_blob(\"b\").feed(b, device_option=gc)\n            self.ws.run(op)\n            outputs[order] = self.ws.blobs[\"Y\"].fetch()\n        output_size = (size - 1) * stride + kernel + adj - 2 * pad\n        self.assertEqual(\n            outputs[\"NCHW\"].shape,\n            (batch_size, output_channels, output_size, output_size))\n        np.testing.assert_allclose(\n            outputs[\"NCHW\"],\n            outputs[\"NHWC\"].transpose((0, 3, 1, 2)),\n            atol=1e-4,\n            rtol=1e-4)\n\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           adj=st.integers(0, 2),\n           size=st.integers(7, 10),\n           input_channels=st.integers(1, 8),\n           output_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 3),\n           engine=st.sampled_from([\"\", \"CUDNN\", \"BLOCK\"]),\n           shared_buffer=st.booleans(),\n           use_bias=st.booleans(),\n           **hu.gcs)\n    def test_convolution_transpose_layout(\n            self, stride, pad, kernel, adj,\n            size, input_channels,\n            output_channels, batch_size,\n            engine, shared_buffer, use_bias, gc, dc):\n        assume(adj < stride)\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        w = np.random.rand(\n            input_channels, kernel, kernel, output_channels)\\\n            .astype(np.float32) - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        outputs = {}\n        for order in [\"NCHW\", \"NHWC\"]:\n            op = core.CreateOperator(\n                \"ConvTranspose\",\n                [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n                [\"Y\"],\n                strides=[stride] * 2,\n                kernels=[kernel] * 2,\n                pads=[pad] * 4,\n                adjs=[adj] * 2,\n                order=order,\n                engine=engine,\n                shared_buffer=int(shared_buffer),\n                device_option=gc,\n            )\n            if order == \"NCHW\":\n                X_f = X.transpose((0, 3, 1, 2))\n                w_f = w.transpose((0, 3, 1, 2))\n            else:\n                X_f = X\n                w_f = w\n            self.assertDeviceChecks(\n                dc,\n                op,\n                [X_f, w_f, b] if use_bias else [X_f, w_f],\n                [0])\n            self.ws.create_blob(\"X\").feed(X_f, device_option=gc)\n            self.ws.create_blob(\"w\").feed(w_f, device_option=gc)\n            self.ws.create_blob(\"b\").feed(b, device_option=gc)\n            self.ws.run(op)\n            outputs[order] = self.ws.blobs[\"Y\"].fetch()\n        output_size = (size - 1) * stride + kernel + adj - 2 * pad\n        self.assertEqual(\n            outputs[\"NCHW\"].shape,\n            (batch_size, output_channels, output_size, output_size))\n        np.testing.assert_allclose(\n            outputs[\"NCHW\"],\n            outputs[\"NHWC\"].transpose((0, 3, 1, 2)),\n            atol=1e-4,\n            rtol=1e-4)\n\n    # CUDNN does not support separate stride and pad so we skip it.\n    @given(stride_h=st.integers(1, 3),\n           stride_w=st.integers(1, 3),\n           pad_t=st.integers(0, 3),\n           pad_l=st.integers(0, 3),\n           pad_b=st.integers(0, 3),\n           pad_r=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           adj_h=st.integers(0, 2),\n           adj_w=st.integers(0, 2),\n           size=st.integers(7, 10),\n           input_channels=st.integers(1, 8),\n           output_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 3),\n           engine=st.sampled_from([\"\", \"BLOCK\"]),\n           use_bias=st.booleans(),\n           **hu.gcs)\n    def test_convolution_transpose_separate_stride_pad_adj_layout(\n            self, stride_h, stride_w, pad_t, pad_l, pad_b, pad_r, kernel,\n            adj_h, adj_w, size, input_channels, output_channels, batch_size,\n            engine, use_bias, gc, dc):\n        assume(adj_h < stride_h)\n        assume(adj_w < stride_w)\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        w = np.random.rand(\n            input_channels, kernel, kernel, output_channels)\\\n            .astype(np.float32) - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        outputs = {}\n        for order in [\"NCHW\", \"NHWC\"]:\n            op = core.CreateOperator(\n                \"ConvTranspose\",\n                [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n                [\"Y\"],\n                stride_h=stride_h,\n                stride_w=stride_w,\n                kernel=kernel,\n                pad_t=pad_t,\n                pad_l=pad_l,\n                pad_b=pad_b,\n                pad_r=pad_r,\n                adj_h=adj_h,\n                adj_w=adj_w,\n                order=order,\n                engine=engine,\n                device_option=gc,\n            )\n            if order == \"NCHW\":\n                X_f = X.transpose((0, 3, 1, 2))\n                w_f = w.transpose((0, 3, 1, 2))\n            else:\n                X_f = X\n                w_f = w\n            self.assertDeviceChecks(\n                dc,\n                op,\n                [X_f, w_f, b] if use_bias else [X_f, w_f],\n                [0])\n            self.ws.create_blob(\"X\").feed(X_f, device_option=gc)\n            self.ws.create_blob(\"w\").feed(w_f, device_option=gc)\n            self.ws.create_blob(\"b\").feed(b, device_option=gc)\n            self.ws.run(op)\n            outputs[order] = self.ws.blobs[\"Y\"].fetch()\n        output_h = (size - 1) * stride_h + kernel + adj_h - pad_t - pad_b\n        output_w = (size - 1) * stride_w + kernel + adj_w - pad_l - pad_r\n        self.assertEqual(\n            outputs[\"NCHW\"].shape,\n            (batch_size, output_channels, output_h, output_w))\n        np.testing.assert_allclose(\n            outputs[\"NCHW\"],\n            outputs[\"NHWC\"].transpose((0, 3, 1, 2)),\n            atol=1e-4,\n            rtol=1e-4)\n\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           adj=st.integers(0, 2),\n           size=st.integers(7, 10),\n           input_channels=st.integers(1, 8),\n           output_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           engine=st.sampled_from([\"\", \"CUDNN\", \"BLOCK\"]),\n           use_bias=st.booleans(),\n           compute_dX=st.booleans(),\n           **hu.gcs)\n    @settings(max_examples=2, timeout=100)\n    def test_convolution_transpose_gradients(self, stride, pad, kernel, adj,\n                                             size, input_channels,\n                                             output_channels, batch_size,\n                                             order, engine, use_bias,\n                                             compute_dX, gc, dc):\n        assume(adj < stride)\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        w = np.random.rand(\n            input_channels, kernel, kernel, output_channels)\\\n            .astype(np.float32) - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        op = core.CreateOperator(\n            \"ConvTranspose\",\n            [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n            [\"Y\"],\n            stride=stride,\n            kernel=kernel,\n            pad=pad,\n            adj=adj,\n            order=order,\n            engine=engine,\n            no_gradient_to_input=not compute_dX,\n        )\n        if order == \"NCHW\":\n            X = X.transpose((0, 3, 1, 2))\n            w = w.transpose((0, 3, 1, 2))\n\n        inputs = [X, w, b] if use_bias else [X, w]\n        self.assertDeviceChecks(dc, op, inputs, [0])\n\n        if use_bias and compute_dX:\n            # w, b, X\n            outputs_to_check = [1, 2, 0]\n        elif use_bias:\n            # w, b\n            outputs_to_check = [1, 2]\n        elif compute_dX:\n            # w, X\n            outputs_to_check = [1, 0]\n        else:\n            # w\n            outputs_to_check = [1]\n        for i in outputs_to_check:\n            self.assertGradientChecks(gc, op, inputs, i, [0])\n\n    # CUDNN does not support separate stride and pad so we skip it.\n    @given(stride_h=st.integers(1, 3),\n           stride_w=st.integers(1, 3),\n           pad_t=st.integers(0, 3),\n           pad_l=st.integers(0, 3),\n           pad_b=st.integers(0, 3),\n           pad_r=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           adj_h=st.integers(0, 2),\n           adj_w=st.integers(0, 2),\n           size=st.integers(7, 10),\n           input_channels=st.integers(1, 8),\n           output_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           engine=st.sampled_from([\"\", \"BLOCK\"]),\n           use_bias=st.booleans(),\n           compute_dX=st.booleans(),\n           **hu.gcs)\n    @settings(max_examples=2, timeout=100)\n    def test_convolution_transpose_separate_stride_pad_adj_gradient(\n            self, stride_h, stride_w, pad_t, pad_l, pad_b, pad_r, kernel,\n            adj_h, adj_w, size, input_channels, output_channels, batch_size,\n            order, engine, use_bias, compute_dX, gc, dc):\n        assume(adj_h < stride_h)\n        assume(adj_w < stride_w)\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        w = np.random.rand(\n            input_channels, kernel, kernel, output_channels)\\\n            .astype(np.float32) - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        op = core.CreateOperator(\n            \"ConvTranspose\",\n            [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n            [\"Y\"],\n            stride_h=stride_h,\n            stride_w=stride_w,\n            kernel=kernel,\n            pad_t=pad_t,\n            pad_l=pad_l,\n            pad_b=pad_b,\n            pad_r=pad_r,\n            adj_h=adj_h,\n            adj_w=adj_w,\n            order=order,\n            engine=engine,\n            no_gradient_to_input=not compute_dX,\n        )\n        if order == \"NCHW\":\n            X = X.transpose((0, 3, 1, 2))\n            w = w.transpose((0, 3, 1, 2))\n\n        inputs = [X, w, b] if use_bias else [X, w]\n        self.assertDeviceChecks(dc, op, inputs, [0])\n\n        if use_bias and compute_dX:\n            # w, b, X\n            outputs_to_check = [1, 2, 0]\n        elif use_bias:\n            # w, b\n            outputs_to_check = [1, 2]\n        elif compute_dX:\n            # w, X\n            outputs_to_check = [1, 0]\n        else:\n            # w\n            outputs_to_check = [1]\n        for i in outputs_to_check:\n            self.assertGradientChecks(gc, op, inputs, i, [0])\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/copy_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nimport unittest\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import workspace, core, model_helper, brew\n\n\nclass CopyOpsTest(unittest.TestCase):\n\n    def tearDown(self):\n        # Reset workspace after each test\n        # Otherwise, the multi-GPU test will use previously created tensors,\n        #   which may have been placed on the wrong device\n        workspace.ResetWorkspace()\n\n    def run_test_copy_gradient(self, device_opt):\n        model = model_helper.ModelHelper(name=\"copy_test\")\n        with core.DeviceScope(device_opt):\n            x = model.net.AddExternalInputs(\"x\")\n            y = model.Copy(x, \"y\")\n            loss = model.AveragedLoss(y, \"loss\")\n            gradient_map = model.AddGradientOperators([loss])\n            workspace.FeedBlob(x, np.random.rand(32).astype(np.float32))\n            workspace.RunNetOnce(model.param_init_net)\n            workspace.RunNetOnce(model.net)\n            self.assertTrue(np.array_equal(\n                workspace.FetchBlob(x),\n                workspace.FetchBlob(y),\n            ))\n            self.assertTrue(np.array_equal(\n                workspace.FetchBlob(gradient_map[x]),\n                workspace.FetchBlob(gradient_map[y]),\n            ))\n\n    def test_copy_gradient_cpu(self):\n        self.run_test_copy_gradient(core.DeviceOption(caffe2_pb2.CPU, 0))\n\n    @unittest.skipIf(workspace.NumCudaDevices() < 1, \"Need at least 1 GPU.\")\n    def test_copy_gradient_gpu(self):\n        self.run_test_copy_gradient(core.DeviceOption(caffe2_pb2.CUDA, 0))\n\n    @unittest.skipIf(workspace.NumCudaDevices() < 2, \"Need at least 2 GPU.\")\n    def test_copy_gradient_multiple_gpus(self):\n        model = model_helper.ModelHelper(name=\"copy_test\")\n\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU, 0)):\n            x_cpu = model.net.AddExternalInputs(\"x_cpu\")\n\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CUDA, 0)):\n            x_gpu_1 = model.CopyCPUToGPU(x_cpu, \"x_gpu_1\")\n\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CUDA, 1)):\n            x_gpu_2 = model.Copy(x_gpu_1, \"x_gpu_2\")\n            loss = model.AveragedLoss(x_gpu_2, \"loss\")\n            gradient_map = model.AddGradientOperators([loss])\n\n        workspace.FeedBlob(\"x_cpu\", np.random.rand(32).astype(np.float32))\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n\n        self.assertTrue(np.array_equal(\n            workspace.FetchBlob(\"x_gpu_1\"),\n            workspace.FetchBlob(\"x_gpu_2\"),\n        ))\n        self.assertTrue(np.array_equal(\n            workspace.FetchBlob(gradient_map[\"x_gpu_1\"]),\n            workspace.FetchBlob(gradient_map[\"x_gpu_2\"]),\n        ))\n\n        def get_op_with_output(model, output_blob_name):\n            for op in model.net.Proto().op:\n                if len(op.output) == 1 and op.output[0] == output_blob_name:\n                    return op\n            return None\n\n        self.assertEqual(\n            get_op_with_output(model, \"x_gpu_2_grad\").device_option,\n            core.DeviceOption(caffe2_pb2.CUDA, 1),\n        )\n        self.assertEqual(\n            get_op_with_output(model, \"x_cpu_grad\").device_option,\n            core.DeviceOption(caffe2_pb2.CUDA, 0),\n        )\n\n    @unittest.skipIf(workspace.NumCudaDevices() < 1, \"Need at least 1 GPU.\")\n    def test_cpu2gpu_gpu2cpu_sparse_gradients(self):\n        model = model_helper.ModelHelper(name=\"copy_test\")\n        v = model.param_init_net.UniformFill([], [\"v\"], shape=[16, 4])\n        indices = model.param_init_net.UniformFill([], [\"v\"], shape=[16, 4])\n        cpu_opt = core.DeviceOption(caffe2_pb2.CPU, 0)\n        gpu_opt = core.DeviceOption(caffe2_pb2.CUDA, 0)\n\n        with core.DeviceScope(gpu_opt):\n            vcpu = model.CopyGPUToCPU(v, \"vcpu\")\n\n        with core.DeviceScope(cpu_opt):\n            g = model.Gather([vcpu, indices], \"g\")\n\n        with core.DeviceScope(gpu_opt):\n            ggpu = model.CopyCPUToGPU(g, \"ggpu\")\n            f = brew.fc(model, ggpu, \"out\", dim_in=4, dim_out=6)\n            (softmax, loss) = model.SoftmaxWithLoss(\n                [f, \"label\"],\n                [\"softmax\", \"loss\"],\n            )\n        gradient_map = model.AddGradientOperators([loss])\n        self.assertTrue(\"v\" in gradient_map)\n        self.assertTrue(isinstance(gradient_map['v'], core.GradientSlice))\n\n    @unittest.skipIf(workspace.NumCudaDevices() < 1, \"Need at least 1 GPU.\")\n    def test_cpu2gpu_gpu2cpu_gradients(self):\n        model = model_helper.ModelHelper(name=\"copy_test\")\n\n        batch = 32\n        cpu_opt = core.DeviceOption(caffe2_pb2.CPU, 0)\n        gpu_opt = core.DeviceOption(caffe2_pb2.CUDA, 0)\n\n        with core.NameScope(\"cpu\"):\n            with core.DeviceScope(cpu_opt):\n                x_cpu = brew.fc(model, 'data', 'x_cpu', 16, 8)\n\n        with core.NameScope(\"gpu_0\"):\n            with core.DeviceScope(gpu_opt):\n                x_gpu = model.CopyCPUToGPU(x_cpu, \"x_gpu\")\n                pred_gpu = brew.fc(model, x_gpu, \"pred_gpu\", 8, 4)\n                pred_cpu = model.CopyGPUToCPU(pred_gpu, \"pred_cpu\")\n\n        with core.DeviceScope(cpu_opt):\n            with core.NameScope(\"cpu\"):\n                (softmax, loss) = model.SoftmaxWithLoss(\n                    [pred_cpu, \"label\"],\n                    [\"softmax\", \"loss\"],\n                )\n\n        gradient_map = model.AddGradientOperators([loss])\n\n        # Add param updates (for cpu and gpu)\n        init_net = model.param_init_net\n        with core.DeviceScope(cpu_opt):\n            with core.NameScope(\"cpu\"):\n                ONE = init_net.ConstantFill([], \"ONE\", shape=[1], value=1.)\n                LR = init_net.ConstantFill([], \"LR\", shape=[1], value=-2.0)\n                for param in model.GetParams():\n                    model.WeightedSum(\n                        [param, ONE, gradient_map[param], LR],\n                        param,\n                    )\n\n        with core.NameScope(\"gpu_0\"):\n            with core.DeviceScope(gpu_opt):\n                ONE = init_net.ConstantFill([], \"ONE\", shape=[1], value=1.)\n                LR = init_net.ConstantFill([], \"LR\", shape=[1], value=-2.0)\n                for param in model.GetParams():\n                    model.WeightedSum(\n                        [param, ONE, gradient_map[param], LR],\n                        param,\n                    )\n\n        with core.DeviceScope(cpu_opt):\n            workspace.FeedBlob(\n                'cpu/data',\n                np.random.rand(batch, 16).astype(np.float32),\n            )\n            workspace.FeedBlob(\n                'cpu/label',\n                np.random.randint(4, size=batch).astype(np.int32),\n            )\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.CreateNet(model.net)\n\n        initial_params = {p: workspace.FetchBlob(p) for p in model.GetParams()}\n        workspace.RunNet(model.net.Proto().name)\n        updated_params = {p: workspace.FetchBlob(p) for p in model.GetParams()}\n\n        for p in model.GetParams():\n            g = gradient_map[p]\n            expected = initial_params[p] - 2.0 * workspace.FetchBlob(g)\n            actual = updated_params[p]\n            self.assertTrue(\n                np.array_equal(expected, updated_params[p]),\n                \"Mismatch: {}: {}, {}\".format(p, expected, actual),\n            )\n"
  },
  {
    "path": "caffe2/python/operator_test/cosine_embedding_criterion_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport numpy as np\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestCosineEmbeddingCriterion(hu.HypothesisTestCase):\n    @given(N=st.integers(min_value=10, max_value=20),\n           seed=st.integers(min_value=0, max_value=65535),\n           margin=st.floats(min_value=-0.5, max_value=0.5),\n           **hu.gcs)\n    def test_cosine_embedding_criterion(self, N, seed, margin, gc, dc):\n        np.random.seed(seed)\n        S = np.random.randn(N).astype(np.float32)\n        Y = np.random.choice([-1, 1], size=N).astype(np.int32)\n        op = core.CreateOperator(\n            \"CosineEmbeddingCriterion\", [\"S\", \"Y\"], [\"output\"],\n            margin=margin)\n\n        def ref_cec(S, Y):\n            result = (1 - S) * (Y == 1) + np.maximum(S - margin, 0) * (Y == -1)\n            return (result, )\n\n        # This checks the op implementation against a reference function in\n        # python.\n        self.assertReferenceChecks(gc, op, [S, Y], ref_cec)\n        # This checks the op implementation over multiple device options (e.g.\n        # CPU and CUDA). [0] means that the 0-th output is checked.\n        self.assertDeviceChecks(dc, op, [S, Y], [0])\n\n        # Now, since this operator's output has a \"kink\" around the margin\n        # value, we move the S vector away from the margin a little bit. This\n        # is a standard trick to avoid gradient check to fail on subgradient\n        # points.\n        S[np.abs(S - margin) < 0.1] += 0.2\n        # This checks the operator's gradient. the first 0 means that we are\n        # checking the gradient of the first input (S), and the second [0] means\n        # that the gradient check should initiate from the 0-th output.\n        self.assertGradientChecks(gc, op, [S, Y], 0, [0])\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/counter_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\nimport tempfile\n\n\nclass TestCounterOps(TestCase):\n\n    def test_counter_ops(self):\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'CreateCounter', [], ['c'], init_count=1))\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'CountDown', ['c'], ['t1']))  # 1 -> 0\n        assert not workspace.FetchBlob('t1')\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'CountDown', ['c'], ['t2']))  # 0 -> -1\n        assert workspace.FetchBlob('t2')\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'CountUp', ['c'], ['t21']))  # -1 -> 0\n        assert workspace.FetchBlob('t21') == -1\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'RetrieveCount', ['c'], ['t22']))\n        assert workspace.FetchBlob('t22') == 0\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'ResetCounter', ['c'], [], init_count=1))  # -> 1\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'CountDown', ['c'], ['t3']))  # 1 -> 0\n        assert not workspace.FetchBlob('t3')\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'ResetCounter', ['c'], ['t31'], init_count=5))  # 0 -> 5\n        assert workspace.FetchBlob('t31') == 0\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'ResetCounter', ['c'], ['t32']))  # 5 -> 0\n        assert workspace.FetchBlob('t32') == 5\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'ConstantFill', [], ['t4'], value=False, shape=[],\n            dtype=core.DataType.BOOL))\n        assert workspace.FetchBlob('t4') == workspace.FetchBlob('t1')\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'ConstantFill', [], ['t5'], value=True, shape=[],\n            dtype=core.DataType.BOOL))\n        assert workspace.FetchBlob('t5') == workspace.FetchBlob('t2')\n\n        assert workspace.RunOperatorOnce(core.CreateOperator(\n            'And', ['t1', 't2'], ['t6']))\n        assert not workspace.FetchBlob('t6')  # True && False\n\n        assert workspace.RunOperatorOnce(core.CreateOperator(\n            'And', ['t2', 't5'], ['t7']))\n        assert workspace.FetchBlob('t7')  # True && True\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'CreateCounter', [], ['serialized_c'], init_count=22))\n        with tempfile.NamedTemporaryFile() as tmp:\n            workspace.RunOperatorOnce(core.CreateOperator(\n                'Save', ['serialized_c'], [], absolute_path=1,\n                db_type='minidb', db=tmp.name))\n            for i in range(10):\n                workspace.RunOperatorOnce(core.CreateOperator(\n                    'CountDown', ['serialized_c'], ['t8']))\n            workspace.RunOperatorOnce(core.CreateOperator(\n                'RetrieveCount', ['serialized_c'], ['t8']))\n            assert workspace.FetchBlob('t8') == 12\n            workspace.RunOperatorOnce(core.CreateOperator(\n                'Load', [], ['serialized_c'], absolute_path=1,\n                db_type='minidb', db=tmp.name))\n            workspace.RunOperatorOnce(core.CreateOperator(\n                'RetrieveCount', ['serialized_c'], ['t8']))\n            assert workspace.FetchBlob('t8') == 22\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/crf_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import workspace, crf, brew\nfrom caffe2.python.model_helper import ModelHelper\nimport numpy as np\nfrom scipy.misc import logsumexp\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nfrom hypothesis import given\n\n\nclass TestCRFOp(hu.HypothesisTestCase):\n\n    @given(num_tags=st.integers(2, 4),\n           num_words=st.integers(2, 15))\n    def test_crf_with_loss_op(self, num_tags, num_words):\n        model = ModelHelper(name='external')\n        embeddings_dim = 200\n        embeddings = np.random.randn(num_words, embeddings_dim).astype(np.float32)\n        transitions = np.random.uniform(\n            low=-1, high=1, size=(num_tags + 2, num_tags + 2)\n        ).astype(np.float32)\n        labels = np.random.randint(num_tags, size=(num_words)).astype(np.int64)\n        embeddings_blob, labels_blob, transitions_blob = (\n            model.net.AddExternalInputs(\n                'embeddings_blob',\n                'labels_blob',\n                'crf_transitions')\n        )\n        workspace.FeedBlob(str(embeddings_blob), embeddings)\n        workspace.FeedBlob(str(labels_blob), labels)\n        workspace.FeedBlob(str(transitions_blob), transitions)\n        predictions_blob = brew.fc(\n            model,\n            embeddings_blob, \"fc_0\",\n            embeddings_dim, num_tags,\n            ('UniformFill', {'min': -1.0}, {'max': 1.0}),\n            ('UniformFill', {'min': -1.0}, {'max': 1.0})\n        )\n        crf_layer = crf.CRFWithLoss(model, num_tags, transitions_blob)\n        crf_loss = crf_layer.crf_loss(predictions_blob, labels_blob)\n        model.net.AddGradientOperators([crf_loss])\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        loss = workspace.FetchBlob(str(crf_loss))\n        predictions = workspace.FetchBlob(str(predictions_blob))\n        np.testing.assert_allclose(\n            loss,\n            self._compute_loss_manual(\n                predictions, num_tags, labels, transitions\n            ),\n            atol=0.001,\n            rtol=0.001,\n            err_msg='CRF LOSS is not matching the reference'\n        )\n\n    @given(num_tags=st.integers(1, 4),\n           num_words=st.integers(2, 4))\n    def test_crf_gradient(self, num_tags, num_words):\n        base_model = ModelHelper(name='base_model')\n        transitions = np.random.randn(\n            num_tags + 2, num_tags + 2\n        ).astype(np.float32)\n        predictions = np.random.randn(num_words, 1, num_tags + 2).astype(np.float32)\n        initial = np.random.randn(1, num_tags + 2).astype(np.float32)\n        predictions_blob, transitions_blob, initial_blob = (\n            base_model.net.AddExternalInputs(\n                'predictions_blob', 'crf_transitions', 'inital_blob'\n            )\n        )\n\n        workspace.FeedBlob(str(predictions_blob), predictions)\n        workspace.FeedBlob(str(transitions_blob), transitions)\n        workspace.FeedBlob(str(initial_blob), initial)\n\n        crf_layer = crf.CRFWithLoss(base_model, num_tags, transitions_blob)\n        crf_layer.build_crf_net(\n            predictions_blob, initial_blob, transitions_blob\n        )\n        op = base_model.net._net.op[-1]\n        workspace.RunNetOnce(base_model.param_init_net)\n        gradients_to_check = (\n            index for (index, input_name) in enumerate(op.input)\n            if input_name != \"crf_net/zero_segment_id\"\n        )\n\n        inputs = [workspace.FetchBlob(name) for name in op.input]\n        for param in gradients_to_check:\n            self.assertGradientChecks(\n                device_option=hu.cpu_do,\n                op=op,\n                inputs=inputs,\n                outputs_to_check=param,\n                outputs_with_grads=[1],\n                threshold=0.05,\n                stepsize=0.001,\n            )\n\n    def _compute_loss_manual(self, predictions, num_tags, labels, transitions):\n        low_score = -1000\n        b_s = np.array(\n            [[low_score] * num_tags + [0, low_score]]\n        ).astype(np.float32)\n        e_s = np.array(\n            [[low_score] * num_tags + [low_score, 0]]\n        ).astype(np.float32)\n        predictions = np.concatenate(\n            [predictions, low_score * np.ones((predictions.shape[0], 2))],\n            axis=1\n        )\n        predictions = np.concatenate(\n            [b_s, predictions, e_s],\n            axis=0\n        )\n        b_id = np.array([num_tags], dtype=np.int32)\n        e_id = np.array([num_tags + 1], dtype=np.int32)\n        labels = np.concatenate(\n            [b_id, labels, e_id],\n            axis=0\n        )\n        curr_state = predictions[0]\n        input_states = predictions[1:]\n\n        for input_state in input_states:\n            prev = np.expand_dims(curr_state, axis=1)\n            curr_input = np.expand_dims(input_state, axis=0)\n            curr_state = logsumexp(prev + curr_input + transitions, axis=0)\n\n        total_score = logsumexp(curr_state, axis=0)\n        # Compute best path score\n        unary_scores = sum(w[labels[i]] for i, w in enumerate(predictions))\n        binary_scores = sum(\n            transitions[a][b] for a, b in zip(labels[:-1], labels[1:])\n        )\n        loss = total_score - (binary_scores + unary_scores)\n        return loss\n"
  },
  {
    "path": "caffe2/python/operator_test/cross_entropy_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\ndef sigmoid(x):\n    return 1.0 / (1.0 + np.exp(-x))\n\n\ndef sigmoid_cross_entropy_with_logits(x, z):\n    return np.maximum(x, 0) - x * z + np.log(1 + np.exp(-np.abs(x)))\n\n\ndef sigmoid_cross_entropy_with_logits_grad(x, z):\n    return z - sigmoid(x)\n\n\nclass TestCrossEntropyOps(hu.HypothesisTestCase):\n    @given(\n        inputs=st.lists(\n            elements=st.integers(min_value=1, max_value=5),\n            min_size=1,\n            max_size=2,\n            average_size=2,\n        ).flatmap(\n            lambda shape: st.tuples(\n                hu.arrays(\n                    dims=shape,\n                    elements=st.one_of(\n                        st.floats(min_value=-1.0, max_value=-0.1),\n                        st.floats(min_value=0.1, max_value=1.0),\n                    )),\n                hu.arrays(\n                    dims=shape,\n                    elements=st.sampled_from([0.0, 1.0]),\n                ),\n            )\n        ),\n        **hu.gcs\n    )\n    def test_sigmoid_cross_entropy_with_logits(self, inputs, gc, dc):\n        logits, targets = inputs\n\n        def sigmoid_xentr_logit_ref(logits, targets):\n            s = sigmoid_cross_entropy_with_logits(logits, targets)\n            m = np.mean(s, axis=len(logits.shape) - 1)\n            return (m, )\n\n        def sigmoid_xentr_logit_grad_ref(g_out, outputs, fwd_inputs):\n            fwd_logits, fwd_targets = fwd_inputs\n            inner_size = fwd_logits.shape[-1]\n            m = fwd_targets - sigmoid(fwd_logits)\n            g_in = -np.expand_dims(g_out, axis=-1) * m / inner_size\n            return (g_in, None)\n\n        op = core.CreateOperator(\n            'SigmoidCrossEntropyWithLogits',\n            ['logits', 'targets'],\n            ['xentropy'])\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[logits, targets],\n            reference=sigmoid_xentr_logit_ref,\n            output_to_grad='xentropy',\n            grad_reference=sigmoid_xentr_logit_grad_ref)\n\n    @given(\n        inputs=st.lists(\n            elements=st.integers(min_value=1, max_value=5),\n            min_size=1,\n            max_size=2,\n            average_size=2,\n        ).flatmap(\n            lambda shape: st.tuples(\n                hu.arrays(\n                    dims=shape,\n                    elements=st.one_of(\n                        st.floats(min_value=-1.0, max_value=-0.1),\n                        st.floats(min_value=0.1, max_value=1.0),\n                    )),\n                hu.arrays(\n                    dims=shape,\n                    elements=st.sampled_from([0.0, 1.0]),\n                ),\n                hu.arrays(\n                    dims=shape,\n                    elements=st.floats(min_value=0.1, max_value=1.0),\n                ),\n            )\n        ),\n        **hu.gcs\n    )\n    def test_weighted_sigmoid_cross_entropy_with_logits(self, inputs, gc, dc):\n        logits, targets, weights = inputs\n\n        def weighted_sigmoid_xentr_logit_ref(logits, targets, weights):\n            s = sigmoid_cross_entropy_with_logits(logits, targets)\n            s = np.multiply(s, weights)\n            m = np.mean(s, axis=len(logits.shape) - 1)\n            return (m, )\n\n        def weighted_sigmoid_xentr_logit_grad_ref(g_out, outputs, fwd_inputs):\n            fwd_logits, fwd_targets, fwd_weights = fwd_inputs\n            inner_size = fwd_logits.shape[-1]\n            m = fwd_targets - sigmoid(fwd_logits)\n            m = np.multiply(m, weights)\n            g_in = -np.expand_dims(g_out, axis=-1) * m / inner_size\n            return (g_in, None, None)\n\n        op = core.CreateOperator(\n            'WeightedSigmoidCrossEntropyWithLogits',\n            ['logits', 'targets', 'weights'],\n            ['xentropy'])\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[logits, targets, weights],\n            reference=weighted_sigmoid_xentr_logit_ref,\n            output_to_grad='xentropy',\n            grad_reference=weighted_sigmoid_xentr_logit_grad_ref)\n\n    @given(n=st.integers(2, 10),\n           b=st.integers(1, 5),\n           **hu.gcs_cpu_only)\n    def test_soft_label_cross_entropy(self, n, b, gc, dc):\n        # Initialize X and add 1e-2 for numerical stability\n        X = np.random.rand(b, n).astype(np.float32)\n        X = X + 1e-2\n        for i in range(b):\n            X[i] = X[i] / np.sum(X[i])\n\n        # Initialize label\n        label = np.random.rand(b, n).astype(np.float32)\n        for i in range(b):\n            label[i] = label[i] / np.sum(label[i])\n\n        # Reference implementation of cross entropy with soft labels\n        def soft_label_xentr_ref(X, label):\n            xent = [np.sum((-label[j][i] * np.log(max(X[j][i], 1e-20))\n                            for i in range(len(X[0])))) for j in range(b)]\n            return (xent,)\n\n        op = core.CreateOperator(\"CrossEntropy\", [\"X\", \"label\"], [\"Y\"])\n\n        # TODO(surya) Once CrossEntropyOp is ported to GPU, add the respective\n        # tests to this unit test.\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, label],\n            reference=soft_label_xentr_ref,\n        )\n\n        self.assertGradientChecks(\n            gc, op, [X, label], 0, [0], stepsize=1e-4, threshold=1e-2)\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/cudnn_recurrent_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import model_helper, workspace, core, rnn_cell\nfrom caffe2.proto import caffe2_pb2\nfrom future.utils import viewitems\nimport numpy as np\n\nimport unittest\n\n\n@unittest.skipIf(not workspace.has_gpu_support, \"No gpu support.\")\nclass TestLSTMs(unittest.TestCase):\n\n    def testEqualToCudnn(self):\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CUDA)):\n            T = 8\n            batch_size = 4\n            input_dim = 8\n            hidden_dim = 31\n\n            workspace.FeedBlob(\n                \"seq_lengths\",\n                np.array([T] * batch_size, dtype=np.int32)\n            )\n            workspace.FeedBlob(\"target\", np.zeros(\n                [T, batch_size, hidden_dim], dtype=np.float32\n            ))\n            workspace.FeedBlob(\"hidden_init\", np.zeros(\n                [1, batch_size, hidden_dim], dtype=np.float32\n            ))\n            workspace.FeedBlob(\"cell_init\", np.zeros(\n                [1, batch_size, hidden_dim], dtype=np.float32\n            ))\n\n            own_model = model_helper.ModelHelper(name=\"own_lstm\")\n\n            input_shape = [T, batch_size, input_dim]\n            cudnn_model = model_helper.ModelHelper(name=\"cudnn_lstm\")\n            input_blob = cudnn_model.param_init_net.UniformFill(\n                [], \"input\", shape=input_shape)\n            workspace.FeedBlob(\"CUDNN/hidden_init_cudnn\", np.zeros(\n                [1, batch_size, hidden_dim], dtype=np.float32\n            ))\n            workspace.FeedBlob(\"CUDNN/cell_init_cudnn\", np.zeros(\n                [1, batch_size, hidden_dim], dtype=np.float32\n            ))\n\n            cudnn_output, cudnn_last_hidden, cudnn_last_state, param_extract = rnn_cell.cudnn_LSTM(\n                model=cudnn_model,\n                input_blob=input_blob,\n                initial_states=(\"hidden_init_cudnn\", \"cell_init_cudnn\"),\n                dim_in=input_dim,\n                dim_out=hidden_dim,\n                scope=\"CUDNN\",\n                return_params=True,\n            )\n            cudnn_loss = cudnn_model.AveragedLoss(\n                cudnn_model.SquaredL2Distance(\n                    [cudnn_output, \"target\"], \"CUDNN/dist\"\n                ), \"CUDNN/loss\"\n            )\n\n            own_output, own_last_hidden, _, own_last_state, own_params = rnn_cell.LSTM(\n                model=own_model,\n                input_blob=input_blob,\n                seq_lengths=\"seq_lengths\",\n                initial_states=(\"hidden_init\", \"cell_init\"),\n                dim_in=input_dim,\n                dim_out=hidden_dim,\n                scope=\"OWN\",\n                return_params=True,\n            )\n            own_loss = own_model.AveragedLoss(\n                own_model.SquaredL2Distance([own_output, \"target\"], \"OWN/dist\"),\n                \"OWN/loss\"\n            )\n\n            # Add gradients\n            cudnn_model.AddGradientOperators([cudnn_loss])\n            own_model.AddGradientOperators([own_loss])\n\n            # Add parameter updates\n            LR = cudnn_model.param_init_net.ConstantFill(\n                [], shape=[1], value=0.01\n            )\n            ONE = cudnn_model.param_init_net.ConstantFill(\n                [], shape=[1], value=1.0\n            )\n            for param in cudnn_model.GetParams():\n                cudnn_model.WeightedSum(\n                    [param, ONE, cudnn_model.param_to_grad[param], LR], param\n                )\n            for param in own_model.GetParams():\n                own_model.WeightedSum(\n                    [param, ONE, own_model.param_to_grad[param], LR], param\n                )\n\n            # Copy states over\n            own_model.net.Copy(own_last_hidden, \"hidden_init\")\n            own_model.net.Copy(own_last_state, \"cell_init\")\n            cudnn_model.net.Copy(cudnn_last_hidden, \"CUDNN/hidden_init_cudnn\")\n            cudnn_model.net.Copy(cudnn_last_state, \"CUDNN/cell_init_cudnn\")\n\n            workspace.RunNetOnce(cudnn_model.param_init_net)\n            workspace.CreateNet(cudnn_model.net)\n\n            ##\n            ##  CUDNN LSTM MODEL EXECUTION\n            ##\n            # Get initial values from CuDNN LSTM so we can feed them\n            # to our own.\n            (param_extract_net, param_extract_mapping) = param_extract\n            workspace.RunNetOnce(param_extract_net)\n            cudnn_lstm_params = {\n                input_type: {\n                    k: workspace.FetchBlob(v[0])\n                    for k, v in viewitems(pars)\n                }\n                for input_type, pars in viewitems(param_extract_mapping)\n            }\n\n            # Run the model 3 times, so that some parameter updates are done\n            workspace.RunNet(cudnn_model.net.Proto().name, 3)\n\n            ##\n            ## OWN LSTM MODEL EXECUTION\n            ##\n            # Map the cuDNN parameters to our own\n            workspace.RunNetOnce(own_model.param_init_net)\n            rnn_cell.InitFromLSTMParams(own_params, cudnn_lstm_params)\n\n            # Run the model 3 times, so that some parameter updates are done\n            workspace.CreateNet(own_model.net)\n            workspace.RunNet(own_model.net.Proto().name, 3)\n\n            ##\n            ## COMPARE RESULTS\n            ##\n            # Then compare that final results after 3 runs are equal\n            own_output_data = workspace.FetchBlob(own_output)\n            own_last_hidden = workspace.FetchBlob(own_last_hidden)\n            own_loss = workspace.FetchBlob(own_loss)\n\n            cudnn_output_data = workspace.FetchBlob(cudnn_output)\n            cudnn_last_hidden = workspace.FetchBlob(cudnn_last_hidden)\n            cudnn_loss = workspace.FetchBlob(cudnn_loss)\n\n            self.assertTrue(np.allclose(own_output_data, cudnn_output_data))\n            self.assertTrue(np.allclose(own_last_hidden, cudnn_last_hidden))\n            self.assertTrue(np.allclose(own_loss, cudnn_loss))\n"
  },
  {
    "path": "caffe2/python/operator_test/dataset_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport numpy as np\nfrom caffe2.python import core, workspace, dataset\nfrom caffe2.python.dataset import Const\nfrom caffe2.python.schema import (\n    List, Field, Struct, Scalar, Map, from_blob_list, FetchRecord, NewRecord,\n    FeedRecord\n)\nfrom caffe2.python.test_util import TestCase\n\nimport numpy.testing as npt\n\nimport string\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\n\ndef _assert_arrays_equal(actual, ref, err_msg):\n    if ref.dtype.kind in ('S', 'O', 'U'):\n        np.testing.assert_array_equal(actual, ref, err_msg=err_msg)\n    else:\n        np.testing.assert_allclose(\n            actual, ref, atol=1e-4,\n            rtol=1e-4, err_msg=err_msg\n        )\n\n\ndef _assert_records_equal(actual, ref):\n    assert isinstance(actual, Field)\n    assert isinstance(ref, Field)\n    b1 = actual.field_blobs()\n    b2 = ref.field_blobs()\n    assert (len(b1) == len(b2)), 'Records have different lengths: %d vs. %d' % (\n        len(b1), len(b2)\n    )\n    for name, d1, d2 in zip(ref.field_names(), b1, b2):\n        _assert_arrays_equal(d1, d2, err_msg='Mismatch in field %s.' % name)\n\n\n@st.composite\ndef _sparse_features_map(draw, num_records, **kwargs):\n    sparse_maps_lengths = draw(\n        st.lists(\n            st.integers(min_value=1, max_value=10),\n            min_size=num_records,\n            max_size=num_records\n        )\n    )\n\n    sparse_maps_total_length = sum(sparse_maps_lengths)\n\n    sparse_keys = draw(\n        st.lists(\n            st.integers(min_value=1, max_value=100),\n            min_size=sparse_maps_total_length,\n            max_size=sparse_maps_total_length,\n            unique=True\n        )\n    )\n\n    sparse_values_lengths = draw(\n        st.lists(\n            st.integers(min_value=1, max_value=10),\n            min_size=sparse_maps_total_length,\n            max_size=sparse_maps_total_length\n        )\n    )\n\n    total_sparse_values_lengths = sum(sparse_values_lengths)\n\n    sparse_values = draw(\n        # max_value is max int64\n        st.lists(\n            st.integers(min_value=1, max_value=9223372036854775807),\n            min_size=total_sparse_values_lengths,\n            max_size=total_sparse_values_lengths\n        )\n    )\n\n    return [\n        sparse_maps_lengths,\n        sparse_keys,\n        sparse_values_lengths,\n        sparse_values,\n    ]\n\n\n@st.composite\ndef _dense_features_map(draw, num_records, **kwargs):\n    float_lengths = draw(\n        st.lists(\n            st.integers(min_value=1, max_value=10),\n            min_size=num_records,\n            max_size=num_records\n        )\n    )\n\n    total_length = sum(float_lengths)\n\n    float_keys = draw(\n        st.lists(\n            st.integers(min_value=1, max_value=100),\n            min_size=total_length,\n            max_size=total_length,\n            unique=True\n        )\n    )\n\n    float_values = draw(\n        st.lists(st.floats(),\n                 min_size=total_length,\n                 max_size=total_length)\n    )\n\n    return [float_lengths, float_keys, float_values]\n\n\n@st.composite\ndef _dataset(draw, min_elements=3, max_elements=10, **kwargs):\n    schema = Struct(\n        # Dense Features Map\n        ('floats', Map(\n            Scalar(np.int32), Scalar(np.float32)\n        )),\n        # Sparse Features Map\n        ('int_lists', Map(\n            Scalar(np.int32),\n            List(Scalar(np.int64)),\n        )),\n        # Complex Type\n        ('text', Scalar(str)),\n    )\n\n    num_records = draw(\n        st.integers(min_value=min_elements,\n                    max_value=max_elements)\n    )\n\n    raw_dense_features_map_contents = draw(_dense_features_map(num_records))\n\n    raw_sparse_features_map_contents = draw(_sparse_features_map(num_records))\n\n    raw_text_contents = [\n        draw(\n            st.lists(\n                st.text(alphabet=string.ascii_lowercase),\n                min_size=num_records,\n                max_size=num_records\n            )\n        )\n    ]\n\n    # Concatenate all raw contents to a single one\n    contents_raw = raw_dense_features_map_contents + raw_sparse_features_map_contents + raw_text_contents\n\n    contents = from_blob_list(schema, contents_raw)\n\n    return (schema, contents, num_records)\n\n\nclass TestDatasetOps(TestCase):\n    @given(_dataset())\n    def test_pack_unpack(self, input):\n        \"\"\"\n        Tests if packing and unpacking of the whole dataset is an identity.\n        \"\"\"\n        (schema, contents, num_records) = input\n\n        dataset_fields = schema.field_names()\n\n        net = core.Net('pack_unpack_net')\n\n        batch = NewRecord(net, contents)\n        FeedRecord(batch, contents)\n\n        packed = net.PackRecords(\n            batch.field_blobs(), 1,\n            fields=dataset_fields\n        )\n\n        unpacked = packed.UnPackRecords(\n            [], len(dataset_fields),\n            fields=dataset_fields\n        )\n\n        workspace.RunNetOnce(net)\n\n        for initial_tensor, unpacked_tensor in zip(\n            batch.field_blobs(), unpacked\n        ):\n            npt.assert_array_equal(\n                workspace.FetchBlob(initial_tensor),\n                workspace.FetchBlob(unpacked_tensor)\n            )\n\n    def test_dataset_ops(self):\n        \"\"\"\n        1. Defining the schema of our dataset.\n\n        This example schema could represent, for example, a search query log.\n        \"\"\"\n        schema = Struct(\n            # fixed size vector, which will be stored as a matrix when batched\n            ('dense', Scalar((np.float32, 3))),\n            # could represent a feature map from feature ID to float value\n            ('floats', Map(\n                Scalar(np.int32), Scalar(np.float32)\n            )),\n            # could represent a multi-valued categorical feature map\n            ('int_lists', Map(\n                Scalar(np.int32),\n                List(Scalar(np.int64)),\n            )),\n            # could represent a multi-valued, weighted categorical feature map\n            (\n                'id_score_pairs', Map(\n                    Scalar(np.int32),\n                    Map(\n                        Scalar(np.int64),\n                        Scalar(np.float32),\n                        keys_name='ids',\n                        values_name='scores'\n                    ),\n                )\n            ),\n            # additional scalar information\n            (\n                'metadata', Struct(\n                    ('user_id', Scalar(np.int64)),\n                    ('user_embed', Scalar((np.float32, 2))),\n                    ('query', Scalar(str)),\n                )\n            ),\n        )\n        \"\"\"\n        This is what the flattened fields for this schema look like, along\n        with its type. Each one of these fields will be stored, read and\n        writen as a tensor.\n        \"\"\"\n        expected_fields = [\n            ('dense', (np.float32, 3)),\n            ('floats:lengths', np.int32),\n            ('floats:values:keys', np.int32),\n            ('floats:values:values', np.float32),\n            ('int_lists:lengths', np.int32),\n            ('int_lists:values:keys', np.int32),\n            ('int_lists:values:values:lengths', np.int32),\n            ('int_lists:values:values:values', np.int64),\n            ('id_score_pairs:lengths', np.int32),\n            ('id_score_pairs:values:keys', np.int32),\n            ('id_score_pairs:values:values:lengths', np.int32),\n            ('id_score_pairs:values:values:values:ids', np.int64),\n            ('id_score_pairs:values:values:values:scores', np.float32),\n            ('metadata:user_id', np.int64),\n            ('metadata:user_embed', (np.float32, 2)),\n            ('metadata:query', str),\n        ]\n        zipped = zip(\n            expected_fields, schema.field_names(), schema.field_types()\n        )\n        for (ref_name, ref_type), name, dtype in zipped:\n            self.assertEquals(ref_name, name)\n            self.assertEquals(np.dtype(ref_type), dtype)\n        \"\"\"\n        2. The contents of our dataset.\n\n        Contents as defined below could represent, for example, a log of\n        search queries along with dense, sparse features and metadata.\n        The datset below has 3 top-level entries.\n        \"\"\"\n        contents_raw = [\n            # dense\n            [[1.1, 1.2, 1.3], [2.1, 2.2, 2.3], [3.1, 3.2, 3.3]],\n            # floats\n            [1, 2, 3],  # len\n            [11, 21, 22, 31, 32, 33],  # key\n            [1.1, 2.1, 2.2, 3.1, 3.2, 3.3],  # value\n            # int lists\n            [2, 0, 1],  # len\n            [11, 12, 31],  # key\n            [2, 4, 3],  # value:len\n            [111, 112, 121, 122, 123, 124, 311, 312, 313],  # value:value\n            # id score pairs\n            [1, 2, 2],  # len\n            [11, 21, 22, 31, 32],  # key\n            [1, 1, 2, 2, 3],  # value:len\n            [111, 211, 221, 222, 311, 312, 321, 322, 323],  # value:ids\n            [11.1, 21.1, 22.1, 22.2, 31.1, 31.2, 32.1, 32.2, 32.3],  # val:score\n            # metadata\n            [123, 234, 456],  # user_id\n            [[0.2, 0.8], [0.5, 0.5], [0.7, 0.3]],  # user_embed\n            ['dog posts', 'friends who like to', 'posts about ca'],  # query\n        ]\n        # convert the above content to ndarrays, checking against the schema\n        contents = from_blob_list(schema, contents_raw)\n        \"\"\"\n        3. Creating and appending to the dataset.\n        We first create an empty dataset with the given schema.\n        Then, a Writer is used to append these entries to the dataset.\n        \"\"\"\n        ds = dataset.Dataset(schema)\n        net = core.Net('init')\n        with core.NameScope('init'):\n            ds.init_empty(net)\n\n            content_blobs = NewRecord(net, contents)\n            FeedRecord(content_blobs, contents)\n            writer = ds.writer(init_net=net)\n            writer.write_record(net, content_blobs)\n        workspace.RunNetOnce(net)\n        \"\"\"\n        4. Iterating through the dataset contents.\n\n        If we were to iterate through the top level entries of our dataset,\n        this is what we should expect to see:\n        \"\"\"\n        entries_raw = [\n            (\n                [[1.1, 1.2, 1.3]],  # dense\n                [1],\n                [11],\n                [1.1],  # floats\n                [2],\n                [11, 12],\n                [2, 4],\n                [111, 112, 121, 122, 123, 124],  # intlst\n                [1],\n                [11],\n                [1],\n                [111],\n                [11.1],  # id score pairs\n                [123],\n                [[0.2, 0.8]],\n                ['dog posts'],  # metadata\n            ),\n            (\n                [[2.1, 2.2, 2.3]],  # dense\n                [2],\n                [21, 22],\n                [2.1, 2.2],  # floats\n                [0],\n                [],\n                [],\n                [],  # int list\n                [2],\n                [21, 22],\n                [1, 2],\n                [211, 221, 222],\n                [21.1, 22.1, 22.2],\n                [234],\n                [[0.5, 0.5]],\n                ['friends who like to'],  # metadata\n            ),\n            (\n                [[3.1, 3.2, 3.3]],  # dense\n                [3],\n                [31, 32, 33],\n                [3.1, 3.2, 3.3],  # floats\n                [1],\n                [31],\n                [3],\n                [311, 312, 313],  # int lst\n                [2],\n                [31, 32],\n                [2, 3],\n                [311, 312, 321, 322, 323],\n                [31.1, 31.2, 32.1, 32.2, 32.3],  # id score list\n                [456],\n                [[0.7, 0.3]],\n                ['posts about ca'],  # metadata\n            ),\n            # after the end of the dataset, we will keep getting empty vectors\n            ([], ) * 16,\n            ([], ) * 16,\n        ]\n        entries = [from_blob_list(schema, e) for e in entries_raw]\n        \"\"\"\n        Let's go ahead and create the reading nets.\n        We will run `read` net multiple times and assert that we are reading the\n        entries the way we stated above.\n        \"\"\"\n        read_init_net = core.Net('read_init')\n        read_next_net = core.Net('read_next')\n        reader = ds.reader(read_init_net)\n        should_continue, batch = reader.read_record(read_next_net)\n\n        workspace.RunNetOnce(read_init_net)\n        workspace.CreateNet(read_next_net, True)\n\n        for entry in entries:\n            workspace.RunNet(str(read_next_net))\n            actual = FetchRecord(batch)\n            _assert_records_equal(actual, entry)\n        \"\"\"\n        5. Reading/writing in a single plan\n\n        If all of operations on the data are expressible as Caffe2 operators,\n        we don't need to load the data to python, iterating through the dataset\n        in a single Plan.\n\n        Where we will process the dataset a little and store it in a second\n        dataset. We can reuse the same Reader since it supports reset.\n        \"\"\"\n        reset_net = core.Net('reset_net')\n        reader.reset(reset_net)\n        read_step, batch = reader.execution_step()\n        \"\"\" We will add the line number * 1000 to the feature ids. \"\"\"\n        process_net = core.Net('process')\n        line_no = Const(process_net, 0, dtype=np.int32)\n        const_one = Const(process_net, 1000, dtype=np.int32)\n        process_net.Add([line_no, const_one], [line_no])\n        field = batch.floats.keys.get()\n        process_net.Print(field, [])\n        process_net.Add([field, line_no], field, broadcast=1, axis=0)\n        \"\"\" Lets create a second dataset and append to it. \"\"\"\n        ds2 = dataset.Dataset(schema, name='dataset2')\n        ds2.init_empty(reset_net)\n        writer = ds2.writer(reset_net)\n        writer.write_record(process_net, batch)\n        # commit is not necessary for DatasetWriter but will add it for\n        # generality of the example\n        commit_net = core.Net('commit')\n        writer.commit(commit_net)\n        \"\"\" Time to create and run a plan which will do the processing \"\"\"\n        plan = core.Plan('process')\n        plan.AddStep(core.execution_step('reset', reset_net))\n        plan.AddStep(read_step.AddNet(process_net))\n        plan.AddStep(core.execution_step('commit', commit_net))\n        workspace.RunPlan(plan)\n        \"\"\"\n        Now we should have dataset2 populated.\n        \"\"\"\n        ds2_data = FetchRecord(ds2.content())\n        field = ds2_data.floats.keys\n        field.set(blob=field.get() - [1000, 2000, 2000, 3000, 3000, 3000])\n        _assert_records_equal(contents, ds2_data)\n        \"\"\"\n        6. Slicing a dataset\n\n        You can create a new schema from pieces of another schema and reuse\n        the same data.\n        \"\"\"\n        subschema = Struct(('top_level', schema.int_lists.values))\n        int_list_contents = contents.int_lists.values.field_names()\n        self.assertEquals(len(subschema.field_names()), len(int_list_contents))\n        \"\"\"\n        7. Random Access a dataset\n\n        \"\"\"\n        read_init_net = core.Net('read_init')\n        read_next_net = core.Net('read_next')\n\n        idx = np.array([2, 1, 0])\n        indices_blob = Const(read_init_net, idx, name='indices')\n        reader = ds.random_reader(read_init_net, indices_blob)\n        reader.computeoffset(read_init_net)\n\n        should_stop, batch = reader.read_record(read_next_net)\n\n        workspace.CreateNet(read_init_net, True)\n        workspace.RunNetOnce(read_init_net)\n\n        workspace.CreateNet(read_next_net, True)\n\n        for i in range(len(entries)):\n            k = idx[i] if i in idx else i\n            entry = entries[k]\n            workspace.RunNet(str(read_next_net))\n            actual = FetchRecord(batch)\n            _assert_records_equal(actual, entry)\n        workspace.RunNet(str(read_next_net))\n        self.assertEquals(True, workspace.FetchBlob(should_stop))\n        \"\"\"\n        8. Random Access a dataset with loop_over = true\n\n        \"\"\"\n        read_init_net = core.Net('read_init')\n        read_next_net = core.Net('read_next')\n\n        idx = np.array([2, 1, 0])\n        indices_blob = Const(read_init_net, idx, name='indices')\n        reader = ds.random_reader(read_init_net, indices_blob, loop_over=True)\n        reader.computeoffset(read_init_net)\n\n        should_stop, batch = reader.read_record(read_next_net)\n\n        workspace.CreateNet(read_init_net, True)\n        workspace.RunNetOnce(read_init_net)\n\n        workspace.CreateNet(read_next_net, True)\n\n        for _ in range(len(entries) * 3):\n            workspace.RunNet(str(read_next_net))\n            self.assertEquals(False, workspace.FetchBlob(should_stop))\n        \"\"\"\n        9. Sort and shuffle a dataset\n\n        This sort the dataset using the score of a certain column,\n        and then shuffle within each chunk of size batch_size * shuffle_size\n        before shuffling the chunks.\n\n        \"\"\"\n        read_init_net = core.Net('read_init')\n        read_next_net = core.Net('read_next')\n\n        reader = ds.random_reader(read_init_net)\n        reader.sort_and_shuffle(read_init_net, 'int_lists:lengths', 1, 2)\n        reader.computeoffset(read_init_net)\n\n        should_continue, batch = reader.read_record(read_next_net)\n\n        workspace.CreateNet(read_init_net, True)\n        workspace.RunNetOnce(read_init_net)\n\n        workspace.CreateNet(read_next_net, True)\n\n        expected_idx = np.array([2, 1, 0])\n        for i in range(len(entries)):\n            k = expected_idx[i] if i in expected_idx else i\n            entry = entries[k]\n            workspace.RunNet(str(read_next_net))\n            actual = FetchRecord(batch)\n            _assert_records_equal(actual, entry)\n\n        \"\"\"\n        Trim a dataset\n        \"\"\"\n        trim_net = core.Net('trim_ds')\n        ds.trim(trim_net, multiple_of=2)\n        workspace.RunNetOnce(trim_net)\n        trimmed = FetchRecord(ds.content())\n        EXPECTED_SIZES = [2, 2, 3, 3, 2, 2, 2, 6, 2, 3, 3, 4, 4, 2, 2, 2]\n        actual_sizes = [d.shape[0] for d in trimmed.field_blobs()]\n        self.assertEquals(EXPECTED_SIZES, actual_sizes)\n\n    def test_last_n_window_ops(self):\n        collect_net = core.Net('collect_net')\n        collect_net.GivenTensorFill(\n            [],\n            'input',\n            shape=[3, 2],\n            values=[1.0, 2.0, 3.0, 4.0, 5.0, 6.0],\n        )\n        input_array =\\\n            np.array(list(range(1, 7)), dtype=np.float32).reshape(3, 2)\n\n        workspace.CreateBlob('output')\n        workspace.FeedBlob('next', np.array(0, dtype=np.int32))\n        collect_net.LastNWindowCollector(\n            ['output', 'next', 'input'],\n            ['output', 'next'],\n            num_to_collect=7,\n        )\n        plan = core.Plan('collect_data')\n        plan.AddStep(\n            core.execution_step('collect_data', [collect_net],\n                                num_iter=1)\n        )\n        workspace.RunPlan(plan)\n        reference_result = workspace.FetchBlob('output')\n        npt.assert_array_equal(input_array, reference_result)\n\n        plan = core.Plan('collect_data')\n        plan.AddStep(\n            core.execution_step('collect_data', [collect_net],\n                                num_iter=2)\n        )\n        workspace.RunPlan(plan)\n        reference_result = workspace.FetchBlob('output')\n        npt.assert_array_equal(input_array[[1, 2, 2, 0, 1, 2, 0]],\n                               reference_result)\n\n        plan = core.Plan('collect_data')\n        plan.AddStep(\n            core.execution_step('collect_data', [collect_net],\n                                num_iter=3)\n        )\n        workspace.RunPlan(plan)\n        reference_result = workspace.FetchBlob('output')\n        npt.assert_array_equal(input_array[[2, 0, 1, 2, 2, 0, 1]],\n                               reference_result)\n\n    def test_collect_tensor_ops(self):\n        init_net = core.Net('init_net')\n        blobs = ['blob_1', 'blob_2', 'blob_3']\n        bvec_map = {}\n        ONE = init_net.ConstantFill([], 'ONE', shape=[1, 2], value=1)\n        for b in blobs:\n            init_net.ConstantFill([], [b], shape=[1, 2], value=0)\n            bvec_map[b] = b + '_vec'\n            init_net.CreateTensorVector([], [bvec_map[b]])\n\n        reader_net = core.Net('reader_net')\n        for b in blobs:\n            reader_net.Add([b, ONE], [b])\n\n        collect_net = core.Net('collect_net')\n        num_to_collect = 1000\n        max_example_to_cover = 100000\n        bvec = [bvec_map[b] for b in blobs]\n        collect_net.CollectTensor(\n            bvec + blobs,\n            bvec,\n            num_to_collect=num_to_collect,\n        )\n\n        print('Collect Net Proto: {}'.format(collect_net.Proto()))\n\n        plan = core.Plan('collect_data')\n        plan.AddStep(core.execution_step('collect_init', init_net))\n        plan.AddStep(\n            core.execution_step(\n                'collect_data', [reader_net, collect_net],\n                num_iter=max_example_to_cover\n            )\n        )\n        workspace.RunPlan(plan)\n\n        # concat the collected tensors\n        concat_net = core.Net('concat_net')\n        bconcated_map = {}\n        bsize_map = {}\n        for b in blobs:\n            bconcated_map[b] = b + '_concated'\n            bsize_map[b] = b + '_size'\n            concat_net.ConcatTensorVector([bvec_map[b]], [bconcated_map[b]])\n            concat_net.TensorVectorSize([bvec_map[b]], [bsize_map[b]])\n\n        workspace.RunNetOnce(concat_net)\n\n        # check data\n        reference_result = workspace.FetchBlob(bconcated_map[blobs[0]])\n        self.assertEqual(\n            reference_result.shape,\n            (min(num_to_collect, max_example_to_cover), 2)\n        )\n        size = workspace.FetchBlob(bsize_map[blobs[0]])\n        self.assertEqual(tuple(), size.shape)\n        self.assertEqual(min(num_to_collect, max_example_to_cover), size.item())\n\n        hist, _ = np.histogram(\n            reference_result[:, 0],\n            bins=10,\n            range=(1, max_example_to_cover)\n        )\n        print('Sample histogram: {}'.format(hist))\n\n        self.assertTrue(all(hist > 0.6 * (num_to_collect / 10)))\n        for i in range(1, len(blobs)):\n            result = workspace.FetchBlob(bconcated_map[blobs[i]])\n            self.assertEqual(reference_result.tolist(), result.tolist())\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/deform_conv_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nfrom hypothesis import assume, given\nimport hypothesis.strategies as st\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\n\nimport unittest\n\n\ndef _cudnn_supports(\n        dilation=False,\n        nhwc=False,\n):\n    \"\"\"Return True if cuDNN supports this configuration.\"\"\"\n    v = workspace.GetCuDNNVersion()\n    if dilation and v < 6000:\n        # Dilation not supported until v6\n        return False\n    if dilation and nhwc:\n        # Dilation and NHWC not supported together\n        return False\n    return True\n\n\ndef _conv_1d_output_size(size, kernel, pad, dilation, stride):\n    return max(\n        1,\n        int((size + pad * 2 - (dilation * (kernel - 1) + 1)) / stride) + 1\n    )\n\n\ndef _conv_2d_output_size(size, kernel, pad_h, pad_w, dilation,\n                         stride_h, stride_w):\n    return [\n        _conv_1d_output_size(size, kernel, pad_h, dilation, stride_h),\n        _conv_1d_output_size(size, kernel, pad_w, dilation, stride_w)\n    ]\n\n\ndef _conv_2d_offsets_dims(\n        batch_size,\n        size,\n        kernel,\n        pad_h,\n        pad_w,\n        dilation,\n        stride_h,\n        stride_w,\n        deformable_group\n):\n    dims = [batch_size, 2 * kernel * kernel * deformable_group]\n    dims.extend(_conv_2d_output_size(size, kernel, pad_h, pad_w,\n                                     dilation, stride_h, stride_w))\n    return dims\n\n\ndef _conv_2d_random_offsets(\n    batch_size,\n    kernel,\n    dims,\n    num_deformable_group\n):\n    o = []\n    for y0 in range(0, kernel):\n        for x0 in range(0, kernel):\n            # stay away from integer offsets which correspond to \"ridges\" on the\n            # interpolated surface resulting in less precise estimates\n            x = np.random.randint(0, kernel) + np.random.uniform(0.05, 0.95)\n            y = np.random.randint(0, kernel) + np.random.uniform(0.05, 0.95)\n            o.append(y - y0)\n            o.append(x - x0)\n    o = o * num_deformable_group\n    e = []\n    for v in o:\n        e.append([[v] * dims[1]] * dims[0])\n    return np.array([e] * batch_size).astype(np.float32)\n\n\ndef _conv_2d_shuffle_offsets(\n    batch_size,\n    kernel,\n    dims,\n    num_deformable_group,\n    input_channels,\n    output_channels\n):\n    o = []\n    w0 = [[0 for x in range(kernel)] for y in range(kernel)]\n    for y0 in range(0, kernel):\n        for x0 in range(0, kernel):\n            x = np.random.randint(0, kernel)\n            y = np.random.randint(0, kernel)\n            o.append(y - y0)\n            o.append(x - x0)\n            w0[y][x] += 1\n    o = o * num_deformable_group\n    e = []\n    for v in o:\n        e.append([[v] * int(dims[1])] * int(dims[0]))\n    w0 = [[w0] * input_channels] * output_channels\n    return (\n        np.array([e] * batch_size).astype(np.float32),\n        np.array(w0).astype(np.float32).transpose((0, 2, 3, 1))\n    )\n\n\nclass TestConvolution(hu.HypothesisTestCase):\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           dilation=st.integers(1, 3),\n           size=st.integers(7, 10),\n           input_channels=st.integers(1, 8),\n           output_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\"]),\n           engine=st.sampled_from([\"\", \"CUDNN\", \"MKLDNN\"]),\n           use_bias=st.booleans(),\n           deformable_group=st.integers(1, 3),\n           **hu.gcs_gpu_only)\n    def test_null_offset_convolution(self, stride, pad, kernel, dilation, size,\n                                     input_channels, output_channels, batch_size,\n                                     order, engine, use_bias, deformable_group,\n                                     gc, dc):\n        dkernel = dilation * (kernel - 1) + 1\n\n        if gc.device_type == caffe2_pb2.CUDA and engine == 'CUDNN':\n            assume(_cudnn_supports(dilation=(dilation > 1),\n                                   nhwc=(order == 'NHWC')))\n\n        assume(engine != \"MKLDNN\" or use_bias is True)\n\n        op = core.CreateOperator(\n            \"DeformConv\",\n            [\"X\", \"o\", \"w\", \"b\"] if use_bias else [\"X\", \"o\", \"w\"],\n            [\"Y\"],\n            stride=stride,\n            kernel=kernel,\n            dilation=dilation,\n            pad=pad,\n            order=order,\n            engine=engine,\n            deformable_group=deformable_group,\n        )\n        offset_dims = _conv_2d_offsets_dims(batch_size, size, kernel, pad, pad,\n                                            dilation, stride, stride,\n                                            deformable_group)\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        o = np.zeros(tuple(offset_dims), np.float32)\n        w = np.random.rand(\n            output_channels, kernel, kernel, input_channels).astype(np.float32)\\\n            - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        if order == \"NCHW\":\n            X = X.transpose((0, 3, 1, 2))\n            w = w.transpose((0, 3, 1, 2))\n\n        inputs = [X, o, w, b] if use_bias else [X, o, w]\n\n        # Error handling path.\n        if size + pad + pad < dkernel or size + pad + pad < dkernel:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n        if input_channels % deformable_group != 0:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n        if output_channels % deformable_group != 0:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n\n        def reference_conv_op(*args):\n            reference_op = core.CreateOperator(\n                \"Conv\",\n                [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n                [\"Y0\"],\n                stride=stride,\n                kernel=kernel,\n                dilation=dilation,\n                pad=pad,\n                order=order,\n                engine=engine,\n                device_option=gc\n            )\n            workspace.RunOperatorOnce(reference_op)\n            reference_blob = workspace.FetchBlob(\"Y0\")\n            return (reference_blob,)\n\n        self.assertReferenceChecks(gc, op, inputs, reference_conv_op)\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 0),\n           kernel=st.integers(1, 5),\n           dilation=st.integers(1, 3),\n           size=st.integers(7, 10),\n           input_channels=st.integers(1, 8),\n           output_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\"]),\n           engine=st.sampled_from([\"\", \"CUDNN\", \"MKLDNN\"]),\n           use_bias=st.booleans(),\n           deformable_group=st.integers(1, 4),\n           **hu.gcs_gpu_only)\n    def test_flat_input_convolution(self, stride, pad, kernel, dilation, size,\n                                    input_channels, output_channels, batch_size,\n                                    order, engine, use_bias,\n                                    deformable_group, gc, dc):\n        dkernel = dilation * (kernel - 1) + 1\n\n        if gc.device_type == caffe2_pb2.CUDA and engine == 'CUDNN':\n            assume(_cudnn_supports(dilation=(dilation > 1),\n                                   nhwc=(order == 'NHWC')))\n\n        assume(engine != \"MKLDNN\" or use_bias is True)\n\n        op = core.CreateOperator(\n            \"DeformConv\",\n            [\"X\", \"o\", \"w\", \"b\"] if use_bias else [\"X\", \"o\", \"w\"],\n            [\"Y\"],\n            stride=stride,\n            kernel=kernel,\n            dilation=dilation,\n            pad=pad,\n            order=order,\n            engine=engine,\n            deformable_group=deformable_group,\n        )\n        X = np.ones((batch_size, size, size, input_channels), np.float32) - 0.5\n        output_size = _conv_2d_output_size(size, kernel, pad, pad,\n                                           dilation, stride, stride)\n        o = _conv_2d_random_offsets(batch_size, kernel, output_size,\n                                    deformable_group)\n        w = np.ones((output_channels, kernel, kernel, input_channels), np.float32) - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        if order == \"NCHW\":\n            X = X.transpose((0, 3, 1, 2))\n            w = w.transpose((0, 3, 1, 2))\n\n        inputs = [X, o, w, b] if use_bias else [X, o, w]\n\n        # Error handling path.\n        if size + pad + pad < dkernel or size + pad + pad < dkernel:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n        if input_channels % deformable_group != 0:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n        if output_channels % deformable_group != 0:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n\n        def reference_conv_op(*args):\n            reference_op = core.CreateOperator(\n                \"Conv\",\n                [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n                [\"Y0\"],\n                stride=stride,\n                kernel=kernel,\n                dilation=dilation,\n                pad=pad,\n                order=order,\n                engine=engine,\n                device_option=gc\n            )\n            workspace.RunOperatorOnce(reference_op)\n            reference_blob = workspace.FetchBlob(\"Y0\")\n            return (reference_blob,)\n\n        self.assertReferenceChecks(gc, op, inputs, reference_conv_op)\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    @given(stride=st.integers(1, 1),\n           pad=st.integers(0, 0),\n           kernel=st.integers(1, 5),\n           dilation=st.integers(1, 1),\n           size=st.integers(7, 10),\n           input_channels=st.integers(1, 8),\n           output_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\"]),\n           engine=st.sampled_from([\"\", \"CUDNN\", \"MKLDNN\"]),\n           use_bias=st.booleans(),\n           deformable_group=st.integers(1, 4),\n           **hu.gcs_gpu_only)\n    def test_shuffle_input_convolution(self, stride, pad, kernel, dilation, size,\n                                       input_channels, output_channels, batch_size,\n                                       order, engine, use_bias,\n                                       deformable_group, gc, dc):\n        dkernel = dilation * (kernel - 1) + 1\n\n        if gc.device_type == caffe2_pb2.CUDA and engine == 'CUDNN':\n            assume(_cudnn_supports(dilation=(dilation > 1),\n                                   nhwc=(order == 'NHWC')))\n\n        assume(engine != \"MKLDNN\" or use_bias is True)\n\n        op = core.CreateOperator(\n            \"DeformConv\",\n            [\"X\", \"o\", \"w\", \"b\"] if use_bias else [\"X\", \"o\", \"w\"],\n            [\"Y\"],\n            stride=stride,\n            kernel=kernel,\n            dilation=dilation,\n            pad=pad,\n            order=order,\n            engine=engine,\n            deformable_group=deformable_group,\n        )\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        output_size = _conv_2d_output_size(size, kernel, pad, pad,\n                                           dilation, stride, stride)\n        o, w0 = _conv_2d_shuffle_offsets(batch_size, kernel, output_size,\n                                         deformable_group, input_channels,\n                                         output_channels)\n        w = np.ones((output_channels, kernel, kernel, input_channels), np.float32)\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n\n        if order == \"NCHW\":\n            X = X.transpose((0, 3, 1, 2))\n            w = w.transpose((0, 3, 1, 2))\n            w0 = w0.transpose((0, 3, 1, 2))\n\n        inputs = [X, o, w, b] if use_bias else [X, o, w]\n\n        # Error handling path.\n        if size + pad + pad < dkernel or size + pad + pad < dkernel:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n        if input_channels % deformable_group != 0:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n        if output_channels % deformable_group != 0:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n\n        def reference_conv_op(*args):\n            with core.DeviceScope(gc):\n                workspace.FeedBlob(\"w0\", w0)\n            reference_op = core.CreateOperator(\n                \"Conv\",\n                [\"X\", \"w0\", \"b\"] if use_bias else [\"X\", \"w0\"],\n                [\"Y0\"],\n                stride=stride,\n                kernel=kernel,\n                dilation=dilation,\n                pad=pad,\n                order=order,\n                engine=engine,\n                device_option=gc\n            )\n            workspace.RunOperatorOnce(reference_op)\n            reference_blob = workspace.FetchBlob(\"Y0\")\n            return (reference_blob,)\n\n        self.assertReferenceChecks(gc, op, inputs, reference_conv_op)\n\n    # CUDNN does NOT support different padding values and we skip it\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    @given(stride_h=st.integers(1, 3),\n           stride_w=st.integers(1, 3),\n           pad_h=st.integers(0, 3),\n           pad_w=st.integers(0, 3),\n           kernel=st.integers(2, 5),\n           size=st.integers(1, 8),\n           input_channels=st.integers(1, 3),\n           output_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\"]),\n           engine=st.sampled_from([\"\", \"EIGEN\"]),\n           shared_buffer=st.booleans(),\n           use_bias=st.booleans(),\n           deformable_group=st.integers(1, 3),\n           **hu.gcs_gpu_only)\n    def test_conv_separate_stride_pad_gradients(self, stride_h, stride_w,\n                                                pad_h, pad_w, kernel, size,\n                                                input_channels, output_channels,\n                                                batch_size, order, engine,\n                                                shared_buffer, use_bias,\n                                                deformable_group, gc, dc):\n        op = core.CreateOperator(\n            \"DeformConv\",\n            [\"X\", \"o\", \"w\", \"b\"] if use_bias else [\"X\", \"o\", \"w\"],\n            [\"Y\"],\n            stride_h=stride_h,\n            stride_w=stride_w,\n            pad_t=pad_h,\n            pad_l=pad_w,\n            pad_b=pad_h,\n            pad_r=pad_w,\n            kernel=kernel,\n            order=order,\n            engine=engine,\n            shared_buffer=int(shared_buffer),\n            deformable_group=deformable_group,\n        )\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        output_size = _conv_2d_output_size(size, kernel, pad_h, pad_w, 1,\n                                           stride_h, stride_w)\n        o = _conv_2d_random_offsets(batch_size, kernel, output_size,\n                                    deformable_group)\n        w = np.random.rand(\n            output_channels, kernel, kernel, input_channels).astype(np.float32)\\\n            - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        if order == \"NCHW\":\n            X = X.transpose((0, 3, 1, 2))\n            w = w.transpose((0, 3, 1, 2))\n\n        inputs = [X, o, w, b] if use_bias else [X, o, w]\n\n        # Error handling path.\n        if size + pad_h < kernel or size + pad_w < kernel:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n        if input_channels % deformable_group != 0:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n        if output_channels % deformable_group != 0:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n\n        self.assertDeviceChecks(dc, op, inputs, [0])\n        for i in range(len(inputs)):\n            self.assertGradientChecks(gc, op, inputs, i, [0])\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           dilation=st.integers(1, 3),\n           size=st.integers(7, 10),\n           input_channels=st.integers(1, 8),\n           output_channels=st.integers(1, 8),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\"]),\n           engine=st.sampled_from([\"\", \"CUDNN\", \"MKLDNN\"]),\n           use_bias=st.booleans(),\n           deformable_group=st.integers(1, 3),\n           **hu.gcs_gpu_only)\n    def test_conv_gradients(self, stride, pad, kernel, dilation, size,\n                            input_channels, output_channels, batch_size, order,\n                            engine, use_bias, deformable_group, gc, dc):\n        dkernel = dilation * (kernel - 1) + 1\n\n        if gc.device_type == caffe2_pb2.CUDA and engine == 'CUDNN':\n            assume(_cudnn_supports(dilation=(dilation > 1),\n                                   nhwc=(order == 'NHWC')))\n\n        assume(engine != \"MKLDNN\" or use_bias is True)\n\n        op = core.CreateOperator(\n            \"DeformConv\",\n            [\"X\", \"o\", \"w\", \"b\"] if use_bias else [\"X\", \"o\", \"w\"],\n            [\"Y\"],\n            stride=stride,\n            kernel=kernel,\n            dilation=dilation,\n            pad=pad,\n            order=order,\n            engine=engine,\n            deformable_group=deformable_group,\n        )\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        output_size = _conv_2d_output_size(size, kernel, pad, pad,\n                                           dilation, stride, stride)\n        o = _conv_2d_random_offsets(batch_size, kernel, output_size, deformable_group)\n        w = np.random.rand(\n            output_channels, kernel, kernel, input_channels).astype(np.float32)\\\n            - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        if order == \"NCHW\":\n            X = X.transpose((0, 3, 1, 2))\n            w = w.transpose((0, 3, 1, 2))\n\n        inputs = [X, o, w, b] if use_bias else [X, o, w]\n        # Error handling path.\n        if size + pad + pad < dkernel or size + pad + pad < dkernel:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n        if input_channels % deformable_group != 0:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n        if output_channels % deformable_group != 0:\n            with self.assertRaises(RuntimeError):\n                self.assertDeviceChecks(dc, op, inputs, [0])\n            return\n\n        self.assertDeviceChecks(dc, op, inputs, [0])\n        for i in range(len(inputs)):\n            self.assertGradientChecks(gc, op, inputs, i, [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/distance_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\n\nclass DistanceTest(hu.HypothesisTestCase):\n    @given(n=st.integers(1, 3),\n           dim=st.integers(4, 16),\n           **hu.gcs)\n    def test_cosine_similarity(self, n, dim, gc, dc):\n        X = np.random.uniform(-1, 1, (n, dim)).astype(np.float32)\n        Y = np.random.uniform(-1, 1, (n, dim)).astype(np.float32)\n        self.ws.create_blob(\"X\").feed(X)\n        self.ws.create_blob(\"Y\").feed(Y)\n        kEps = 1e-12\n        cos_op = core.CreateOperator(\"CosineSimilarity\", [\"X\", \"Y\"], [\"cos\"])\n        self.ws.run(cos_op)\n        cos = np.divide(np.multiply(X, Y).sum(axis=1),\n                        np.multiply(np.linalg.norm(X, axis=1) + kEps,\n                                    np.linalg.norm(Y, axis=1) + kEps))\n        np.testing.assert_allclose(self.ws.blobs[(\"cos\")].fetch(), cos,\n                                   rtol=1e-4, atol=1e-4)\n        self.assertGradientChecks(gc, cos_op, [X, Y], 0, [0],\n                                  stepsize=1e-2, threshold=1e-2)\n        self.assertGradientChecks(gc, cos_op, [X, Y], 1, [0],\n                                  stepsize=1e-2, threshold=1e-2)\n\n    @given(inputs=hu.tensors(n=2,\n                             min_dim=1,\n                             max_dim=2,\n                             dtype=np.float32),\n           **hu.gcs)\n    def test_dot_product(self, inputs, gc, dc):\n        X, Y = inputs\n        op = core.CreateOperator(\n            'DotProduct',\n            ['X', 'Y'],\n            ['DOT'],\n        )\n\n        def dot_ref(X, Y):\n            return ([np.dot(x, y) for x, y in zip(X, Y)],)\n\n        # Check against numpy dot reference\n        self.assertReferenceChecks(gc, op, [X, Y], dot_ref)\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        # Gradient check wrt X\n        self.assertGradientChecks(gc, op, [X, Y], 0, [0])\n        # Gradient check wrt Y\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n    @given(n=st.integers(1, 3),\n           dim=st.integers(4, 16),\n           **hu.gcs)\n    def test_L1_distance(self, n, dim, gc, dc):\n        X = np.random.uniform(-1, 1, (n, dim)).astype(np.float32)\n        Y = np.random.uniform(-1, 1, (n, dim)).astype(np.float32)\n        # avoid kinks by moving away from 0\n        X += 0.02 * np.sign(X - Y)\n        X[(X - Y) == 0.0] += 0.02\n\n        self.ws.create_blob(\"X\").feed(X)\n        self.ws.create_blob(\"Y\").feed(Y)\n        op = core.CreateOperator(\n            'L1Distance',\n            ['X', 'Y'],\n            ['l1_dist'],\n        )\n        self.ws.run(op)\n        np.testing.assert_allclose(self.ws.blobs[(\"l1_dist\")].fetch(),\n                                    [np.linalg.norm(x - y, ord=1)\n                                        for x, y in zip(X, Y)],\n                                    rtol=1e-4, atol=1e-4)\n\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        # Gradient check wrt X\n        self.assertGradientChecks(gc, op, [X, Y], 0, [0],\n                                  stepsize=1e-2, threshold=1e-2)\n        # Gradient check wrt Y\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0],\n                                  stepsize=1e-2, threshold=1e-2)\n\n    @given(n=st.integers(1, 3),\n           dim=st.integers(4, 16),\n           **hu.gcs)\n    def test_L2_distance(self, n, dim, gc, dc):\n        X = np.random.uniform(-1, 1, (n, dim)).astype(np.float32)\n        Y = np.random.uniform(-1, 1, (n, dim)).astype(np.float32)\n        self.ws.create_blob(\"X\").feed(X)\n        self.ws.create_blob(\"Y\").feed(Y)\n        l2_op = core.CreateOperator(\"SquaredL2Distance\",\n                                    [\"X\", \"Y\"], [\"l2_dist\"])\n        self.ws.run(l2_op)\n        np.testing.assert_allclose(self.ws.blobs[(\"l2_dist\")].fetch(),\n                                   np.square(X - Y).sum(axis=1) * 0.5,\n                                   rtol=1e-4, atol=1e-4)\n        self.assertGradientChecks(gc, l2_op, [X, Y], 0, [0],\n                                  stepsize=1e-2, threshold=1e-2)\n        self.assertGradientChecks(gc, l2_op, [X, Y], 1, [0],\n                                  stepsize=1e-2, threshold=1e-2)\n"
  },
  {
    "path": "caffe2/python/operator_test/dropout_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom hypothesis import assume, given\nimport hypothesis.strategies as st\nimport numpy as np\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestDropout(hu.HypothesisTestCase):\n\n    @given(X=hu.tensor(),\n           in_place=st.booleans(),\n           ratio=st.floats(0, 0.999),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs)\n    def test_dropout_is_test(self, X, in_place, ratio, engine, gc, dc):\n        \"\"\"Test with is_test=True for a deterministic reference impl.\"\"\"\n        # TODO(lukeyeager): enable this path when the GPU path is fixed\n        if in_place:\n            # Skip if trying in-place on GPU\n            assume(not (gc.device_type == caffe2_pb2.CUDA and engine == ''))\n            # If in-place on CPU, don't compare with GPU\n            dc = dc[:1]\n\n        op = core.CreateOperator(\"Dropout\", [\"X\"],\n                                 [\"X\" if in_place else \"Y\"],\n                                 ratio=ratio, engine=engine, is_test=True)\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n        # No sense in checking gradients for test phase\n\n        def reference_dropout_test(x):\n            return x, np.ones(x.shape, dtype=np.bool)\n        self.assertReferenceChecks(\n            gc, op, [X], reference_dropout_test,\n            # The 'mask' output may be uninitialized\n            outputs_to_check=[0])\n\n    @given(X=hu.tensor(),\n           in_place=st.booleans(),\n           output_mask=st.booleans(),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs)\n    def test_dropout_ratio0(self, X, in_place, output_mask, engine, gc, dc):\n        \"\"\"Test with ratio=0 for a deterministic reference impl.\"\"\"\n        # TODO(lukeyeager): enable this path when the op is fixed\n        if in_place:\n            # Skip if trying in-place on GPU\n            assume(gc.device_type != caffe2_pb2.CUDA)\n            # If in-place on CPU, don't compare with GPU\n            dc = dc[:1]\n        is_test = not output_mask\n        op = core.CreateOperator(\"Dropout\", [\"X\"],\n                                 [\"X\" if in_place else \"Y\"] +\n                                 ([\"mask\"] if output_mask else []),\n                                 ratio=0.0, engine=engine,\n                                 is_test=is_test)\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n        if not is_test:\n            self.assertGradientChecks(gc, op, [X], 0, [0])\n\n        def reference_dropout_ratio0(x):\n            return (x,) if is_test else (x, np.ones(x.shape, dtype=np.bool))\n        self.assertReferenceChecks(\n            gc, op, [X], reference_dropout_ratio0,\n            # Don't check the mask with cuDNN because it's packed data\n            outputs_to_check=None if engine != 'CUDNN' else [0])\n"
  },
  {
    "path": "caffe2/python/operator_test/duplicate_operands_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\n\n\nclass TestDuplicateOperands(TestCase):\n    def test_duplicate_operands(self):\n        net = core.Net('net')\n        shape = (2, 4)\n        x_in = np.random.uniform(size=shape)\n        x = net.GivenTensorFill([], 'X', shape=shape,\n                                values=x_in.flatten().tolist())\n        xsq = net.Mul([x, x])\n        y = net.DotProduct([xsq, xsq])\n        net.AddGradientOperators([y])\n        workspace.RunNetOnce(net)\n        self.assertTrue(np.allclose(workspace.FetchBlob('X_grad'),\n                                    4 * x_in**3))\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/elementwise_linear_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestElementwiseLinearOp(hu.HypothesisTestCase):\n\n    @given(n=st.integers(2, 100), d=st.integers(2, 10), **hu.gcs)\n    # @given(n=st.integers(2, 50), d=st.integers(2, 50), **hu.gcs_cpu_only)\n    def test(self, n, d, gc, dc):\n        X = np.random.rand(n, d).astype(np.float32)\n        a = np.random.rand(d).astype(np.float32)\n        b = np.random.rand(d).astype(np.float32)\n\n        def ref_op(X, a, b):\n            d = a.shape[0]\n            return [np.multiply(X, a.reshape(1, d)) + b.reshape(1, d)]\n\n        op = core.CreateOperator(\n            \"ElementwiseLinear\",\n            [\"X\", \"a\", \"b\"],\n            [\"Y\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, a, b],\n            reference=ref_op,\n        )\n\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X, a, b], [0])\n        # Gradient check wrt X\n        self.assertGradientChecks(gc, op, [X, a, b], 0, [0])\n        # Gradient check wrt a\n        self.assertGradientChecks(gc, op, [X, a, b], 1, [0])\n        # # Gradient check wrt b\n        self.assertGradientChecks(gc, op, [X, a, b], 2, [0])\n"
  },
  {
    "path": "caffe2/python/operator_test/elementwise_logical_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport caffe2.python.hypothesis_test_util as hu\n\nimport numpy as np\nimport unittest\n\n\ndef mux(select, left, right):\n    return [np.vectorize(lambda c, x, y: x if c else y)(select, left, right)]\n\n\ndef rowmux(select_vec, left, right):\n    select = [[s] * len(left) for s in select_vec]\n    return mux(select, left, right)\n\n\nclass TestWhere(hu.HypothesisTestCase):\n\n    def test_reference(self):\n        self.assertTrue((\n            np.array([1, 4]) == mux([True, False],\n                                    [1, 2],\n                                    [3, 4])[0]\n        ).all())\n        self.assertTrue((\n            np.array([[1], [4]]) == mux([[True], [False]],\n                                        [[1], [2]],\n                                        [[3], [4]])[0]\n        ).all())\n\n    @given(N=st.integers(min_value=1, max_value=10),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs_cpu_only)\n    def test_where(self, N, gc, dc, engine):\n        C = np.random.rand(N).astype(bool)\n        X = np.random.rand(N).astype(np.float32)\n        Y = np.random.rand(N).astype(np.float32)\n        op = core.CreateOperator(\"Where\", [\"C\", \"X\", \"Y\"], [\"Z\"], engine=engine)\n        self.assertDeviceChecks(dc, op, [C, X, Y], [0])\n        self.assertReferenceChecks(gc, op, [C, X, Y], mux)\n\n    @given(N=st.integers(min_value=1, max_value=10),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs_cpu_only)\n    def test_where_dim2(self, N, gc, dc, engine):\n        C = np.random.rand(N, N).astype(bool)\n        X = np.random.rand(N, N).astype(np.float32)\n        Y = np.random.rand(N, N).astype(np.float32)\n        op = core.CreateOperator(\"Where\", [\"C\", \"X\", \"Y\"], [\"Z\"], engine=engine)\n        self.assertDeviceChecks(dc, op, [C, X, Y], [0])\n        self.assertReferenceChecks(gc, op, [C, X, Y], mux)\n\n\nclass TestRowWhere(hu.HypothesisTestCase):\n\n    def test_reference(self):\n        self.assertTrue((\n            np.array([1, 2]) == rowmux([True],\n                                       [1, 2],\n                                       [3, 4])[0]\n        ).all())\n        self.assertTrue((\n            np.array([[1, 2], [7, 8]]) == rowmux([True, False],\n                                                 [[1, 2], [3, 4]],\n                                                 [[5, 6], [7, 8]])[0]\n        ).all())\n\n    @given(N=st.integers(min_value=1, max_value=10),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs_cpu_only)\n    def test_rowwhere(self, N, gc, dc, engine):\n        C = np.random.rand(N).astype(bool)\n        X = np.random.rand(N).astype(np.float32)\n        Y = np.random.rand(N).astype(np.float32)\n        op = core.CreateOperator(\n            \"Where\",\n            [\"C\", \"X\", \"Y\"],\n            [\"Z\"],\n            broadcast_on_rows=True,\n            engine=engine,\n        )\n        self.assertDeviceChecks(dc, op, [C, X, Y], [0])\n        self.assertReferenceChecks(gc, op, [C, X, Y], mux)\n\n    @given(N=st.integers(min_value=1, max_value=10),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs_cpu_only)\n    def test_rowwhere_dim2(self, N, gc, dc, engine):\n        C = np.random.rand(N).astype(bool)\n        X = np.random.rand(N, N).astype(np.float32)\n        Y = np.random.rand(N, N).astype(np.float32)\n        op = core.CreateOperator(\n            \"Where\",\n            [\"C\", \"X\", \"Y\"],\n            [\"Z\"],\n            broadcast_on_rows=True,\n            engine=engine,\n        )\n        self.assertDeviceChecks(dc, op, [C, X, Y], [0])\n        self.assertReferenceChecks(gc, op, [C, X, Y], rowmux)\n\n\nclass TestIsMemberOf(hu.HypothesisTestCase):\n\n    @given(N=st.integers(min_value=1, max_value=10),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs_cpu_only)\n    def test_is_member_of(self, N, gc, dc, engine):\n        X = np.random.randint(10, size=N).astype(np.int64)\n        values = [0, 3, 4, 6, 8]\n        op = core.CreateOperator(\n            \"IsMemberOf\",\n            [\"X\"],\n            [\"Y\"],\n            value=np.array(values),\n            engine=engine,\n        )\n        self.assertDeviceChecks(dc, op, [X], [0])\n        values = set(values)\n\n        def test(x):\n            return [np.vectorize(lambda x: x in values)(x)]\n        self.assertReferenceChecks(gc, op, [X], test)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/elementwise_op_broadcast_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\n\nfrom hypothesis import given\nimport numpy as np\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\n\n\n# TODO(jiayq): make them hypothesis tests for better coverage.\nclass TestElementwiseBroadcast(hu.HypothesisTestCase):\n    @given(**hu.gcs)\n    def test_broadcast_Add(self, gc, dc):\n        # Set broadcast and no axis, i.e. broadcasting last dimensions.\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(4, 5).astype(np.float32)\n        op = core.CreateOperator(\"Add\", [\"X\", \"Y\"], \"out\", broadcast=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(out, X + Y)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n        # broadcasting intermediate dimensions\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(3, 4).astype(np.float32)\n        op = core.CreateOperator(\"Add\", [\"X\", \"Y\"], \"out\", broadcast=1, axis=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(out, X + Y[:, :, np.newaxis])\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n        # broadcasting the first dimension\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(2).astype(np.float32)\n        op = core.CreateOperator(\"Add\", [\"X\", \"Y\"], \"out\", broadcast=1, axis=0)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(\n            out, X + Y[:, np.newaxis, np.newaxis, np.newaxis])\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n        # broadcasting with single elem dimensions at both ends\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(1, 4, 1).astype(np.float32)\n        op = core.CreateOperator(\"Add\", [\"X\", \"Y\"], \"out\", broadcast=1, axis=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(out, X + Y)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n    @given(**hu.gcs)\n    def test_broadcast_Mul(self, gc, dc):\n        # Set broadcast and no axis, i.e. broadcasting last dimensions.\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(4, 5).astype(np.float32)\n        op = core.CreateOperator(\"Mul\", [\"X\", \"Y\"], \"out\", broadcast=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(out, X * Y)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n        # broadcasting intermediate dimensions\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(3, 4).astype(np.float32)\n        op = core.CreateOperator(\"Mul\", [\"X\", \"Y\"], \"out\", broadcast=1, axis=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(out, X * Y[:, :, np.newaxis])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n\n        # broadcasting the first dimension\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(2).astype(np.float32)\n        op = core.CreateOperator(\"Mul\", [\"X\", \"Y\"], \"out\", broadcast=1, axis=0)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(\n            out, X * Y[:, np.newaxis, np.newaxis, np.newaxis])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n\n        # broadcasting with single elem dimensions at both ends\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(1, 4, 1).astype(np.float32)\n        op = core.CreateOperator(\"Mul\", [\"X\", \"Y\"], \"out\", broadcast=1, axis=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(out, X * Y)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n    @given(**hu.gcs)\n    def test_broadcast_Sub(self, gc, dc):\n        # Set broadcast and no axis, i.e. broadcasting last dimensions.\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(4, 5).astype(np.float32)\n        op = core.CreateOperator(\"Sub\", [\"X\", \"Y\"], \"out\", broadcast=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(out, X - Y)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n        # broadcasting intermediate dimensions\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(3, 4).astype(np.float32)\n        op = core.CreateOperator(\"Sub\", [\"X\", \"Y\"], \"out\", broadcast=1, axis=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(out, X - Y[:, :, np.newaxis])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n\n        # broadcasting the first dimension\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(2).astype(np.float32)\n        op = core.CreateOperator(\"Sub\", [\"X\", \"Y\"], \"out\", broadcast=1, axis=0)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(\n            out, X - Y[:, np.newaxis, np.newaxis, np.newaxis])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n\n        # broadcasting with single elem dimensions at both ends\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(1, 4, 1).astype(np.float32)\n        op = core.CreateOperator(\"Sub\", [\"X\", \"Y\"], \"out\", broadcast=1, axis=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(out, X - Y)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n    @given(**hu.gcs)\n    def test_broadcast_powt(self, gc, dc):\n        np.random.seed(101)\n\n        #operator\n        def powt_op(X, Y):\n            return [np.power(X, Y)]\n\n        #two gradients Y*X^(Y-1) and X^Y * ln(X)\n        def powt_grad(g_out, outputs, fwd_inputs):\n            [X, Y] = fwd_inputs\n            Z = outputs[0]\n            return ([Y * np.power(X, Y - 1), Z * np.log(X)] * g_out)\n\n        #1. Set broadcast and no axis, i.e. broadcasting last dimensions.\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32) + 1.0\n        Y = np.random.rand(4, 5).astype(np.float32) + 2.0\n\n        #two gradients Y*X^(Y-1) and X^Y * ln(X)\n        #latter gradient is sumed over 1 and 0 dims to account for broadcast\n        def powt_grad_broadcast(g_out, outputs, fwd_inputs):\n            [GX, GY] = powt_grad(g_out, outputs, fwd_inputs)\n            return ([GX, np.sum(np.sum(GY, 1), 0)])\n\n        op = core.CreateOperator(\"Pow\", [\"X\", \"Y\"], \"Z\", broadcast=1)\n        self.assertReferenceChecks(device_option=gc,\n                                   op=op,\n                                   inputs=[X, Y],\n                                   reference=powt_op,\n                                   output_to_grad=\"Z\",\n                                   grad_reference=powt_grad_broadcast)\n\n        #2. broadcasting intermediate dimensions\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32) + 1.0\n        Y = np.random.rand(3, 4).astype(np.float32) + 2.0\n\n        #pow op with the latter array increased by one dim\n        def powt_op_axis1(X, Y):\n            return powt_op(X, Y[:, :, np.newaxis])\n\n        #two gradients Y*X^(Y-1) and X^Y * ln(X)\n        #latter gradient is sumed over 3 and 0 dims to account for broadcast\n        def powt_grad_axis1(g_out, outputs, fwd_inputs):\n            [X, Y] = fwd_inputs\n            [GX, GY] = powt_grad(g_out, outputs, [X, Y[:, :, np.newaxis]])\n            return ([GX, np.sum(np.sum(GY, 3), 0)])\n\n        op = core.CreateOperator(\"Pow\", [\"X\", \"Y\"], \"Z\", broadcast=1, axis=1)\n        self.assertReferenceChecks(device_option=gc,\n                                   op=op,\n                                   inputs=[X, Y],\n                                   reference=powt_op_axis1,\n                                   output_to_grad=\"Z\",\n                                   grad_reference=powt_grad_axis1)\n\n        #3. broadcasting the first dimension\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32) + 1.0\n        Y = np.random.rand(2).astype(np.float32) + 2.0\n\n        #pow op with the latter array increased by one dim\n        def powt_op_axis0(X, Y):\n            return powt_op(X, Y[:, np.newaxis, np.newaxis, np.newaxis])\n\n        #two gradients Y*X^(Y-1) and X^Y * ln(X)\n        #latter gradient is sumed over 3, 2 and 1 dims to account for broadcast\n        def powt_grad_axis0(g_out, outputs, fwd_inputs):\n            [X, Y] = fwd_inputs\n            [GX, GY] = powt_grad(g_out,\n                                 outputs,\n                                 [X, Y[:, np.newaxis, np.newaxis, np.newaxis]])\n            return ([GX, np.sum(np.sum(np.sum(GY, 3), 2), 1)])\n\n        op = core.CreateOperator(\"Pow\", [\"X\", \"Y\"], \"Z\", broadcast=1, axis=0)\n        self.assertReferenceChecks(device_option=gc,\n                                   op=op,\n                                   inputs=[X, Y],\n                                   reference=powt_op_axis0,\n                                   output_to_grad=\"Z\",\n                                   grad_reference=powt_grad_axis0)\n\n        #4. broadcasting with single elem dimensions at both ends\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32) + 1.0\n        Y = np.random.rand(1, 4, 1).astype(np.float32) + 2.0\n\n        #pow op with the latter array increased by one dim\n        def powt_op_mixed(X, Y):\n            return powt_op(X, Y[np.newaxis, :, :, :])\n\n        #two gradients Y*X^(Y-1) and X^Y * ln(X)\n        #latter gradient is sumed over 0 and 1 dims to account for broadcast\n        def powt_grad_mixed(g_out, outputs, fwd_inputs):\n            [X, Y] = fwd_inputs\n            [GX, GY] = powt_grad(g_out, outputs, [X, Y[np.newaxis, :, :, :]])\n            return ([GX, np.reshape(np.sum(np.sum(np.sum(GY, 3), 1), 0),\n                                    (1, 4, 1))])\n\n        op = core.CreateOperator(\"Pow\", [\"X\", \"Y\"], \"Z\", broadcast=1, axis=1)\n        self.assertReferenceChecks(device_option=gc,\n                                   op=op,\n                                   inputs=[X, Y],\n                                   reference=powt_op_mixed,\n                                   output_to_grad=\"Z\",\n                                   grad_reference=powt_grad_mixed)\n\n    @given(**hu.gcs)\n    def test_broadcast_scalar(self, gc, dc):\n        # broadcasting constant\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(1).astype(np.float32)\n        op = core.CreateOperator(\"Add\", [\"X\", \"Y\"], \"out\", broadcast=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(\n            out, X + Y)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n\n        # broadcasting scalar\n        X = np.random.rand(1).astype(np.float32)\n        Y = np.random.rand(1).astype(np.float32).reshape([])\n        op = core.CreateOperator(\"Add\", [\"X\", \"Y\"], \"out\", broadcast=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(\n            out, X + Y)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n\n    @given(**hu.gcs)\n    def test_semantic_broadcast(self, gc, dc):\n        # NCHW as default\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(3).astype(np.float32)\n        op = core.CreateOperator(\n            \"Add\", [\"X\", \"Y\"], \"out\", broadcast=1, axis_str=\"C\")\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(\n            out, X + Y[:, np.newaxis, np.newaxis])\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n\n        # NHWC\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(5).astype(np.float32)\n        op = core.CreateOperator(\n            \"Add\", [\"X\", \"Y\"], \"out\", broadcast=1, axis_str=\"C\", order=\"NHWC\")\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        np.testing.assert_array_almost_equal(out, X + Y)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n\n    @given(**hu.gcs)\n    def test_sum_reduce(self, gc, dc):\n        # Set broadcast and no axis, i.e. broadcasting last dimensions.\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(4, 5).astype(np.float32)\n        op = core.CreateOperator(\n            \"SumReduceLike\", [\"X\", \"Y\"], \"out\", broadcast=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        res = np.sum(X, axis=0)\n        res = np.sum(res, axis=0)\n        np.testing.assert_array_almost_equal(out, res)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n\n        # Set broadcast and no axis, i.e. broadcasting last dimensions.\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(2, 3).astype(np.float32)\n        op = core.CreateOperator(\n            \"SumReduceLike\", [\"X\", \"Y\"], \"out\", broadcast=1, axis=0)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        res = np.sum(X, axis=3)\n        res = np.sum(res, axis=2)\n        np.testing.assert_array_almost_equal(out, res, decimal=3)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n\n        # broadcasting intermediate dimensions\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(3, 4).astype(np.float32)\n        op = core.CreateOperator(\n            \"SumReduceLike\", [\"X\", \"Y\"], \"out\", broadcast=1, axis=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        res = np.sum(X, axis=0)\n        res = np.sum(res, axis=2)\n        np.testing.assert_array_almost_equal(out, res)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n\n        # broadcasting intermediate dimensions\n        X = np.random.rand(2, 3, 4, 500).astype(np.float64)\n        Y = np.random.rand(1).astype(np.float64)\n        op = core.CreateOperator(\n            \"SumReduceLike\", [\"X\", \"Y\"], \"out\", broadcast=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        res = np.array(np.sum(X))\n        np.testing.assert_array_almost_equal(out, res, decimal=0)\n\n        # broadcasting with single elem dimensions at both ends\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        Y = np.random.rand(1, 3, 4, 1).astype(np.float32)\n        op = core.CreateOperator(\n            \"SumReduceLike\", [\"X\", \"Y\"], \"out\", broadcast=1)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        workspace.RunOperatorOnce(op)\n        out = workspace.FetchBlob(\"out\")\n        res = np.sum(X, axis=0)\n        res = np.sum(res, axis=2).reshape(Y.shape)\n        np.testing.assert_array_almost_equal(out, res)\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n\n        # fp64 is not supported with the CUDA op\n        dc_cpu_only = [d for d in dc if d.device_type != caffe2_pb2.CUDA]\n        self.assertDeviceChecks(dc_cpu_only, op, [X, Y], [0])\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    @given(**hu.gcs_gpu_only)\n    def test_sum_reduce_fp16(self, gc, dc):\n        # Set broadcast and no axis, i.e. broadcasting last dimensions.\n        X = np.random.rand(2, 3, 4, 5).astype(np.float16)\n        Y = np.random.rand(4, 5).astype(np.float16)\n        op = core.CreateOperator(\n            \"SumReduceLike\", [\"X\", \"Y\"], \"out\", broadcast=1, device_option=gc)\n\n        def ref_op(X, Y):\n            res = np.sum(X, axis=0)\n            res = np.sum(res, axis=0)\n            return [res]\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, Y],\n            reference=ref_op,\n            threshold=1e-3)\n\n        # Set broadcast and no axis, i.e. broadcasting last dimensions.\n        X = np.random.rand(2, 3, 4, 5).astype(np.float16)\n        Y = np.random.rand(2, 3).astype(np.float16)\n        op = core.CreateOperator(\n            \"SumReduceLike\", [\"X\", \"Y\"], \"out\", broadcast=1, axis=0)\n\n        def ref_op(X, Y):\n            res = np.sum(X, axis=3)\n            res = np.sum(res, axis=2)\n            return [res]\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, Y],\n            reference=ref_op,\n            threshold=1e-3)\n\n        # broadcasting intermediate dimensions\n        X = np.random.rand(2, 3, 4, 5).astype(np.float16)\n        Y = np.random.rand(3, 4).astype(np.float16)\n        op = core.CreateOperator(\n            \"SumReduceLike\", [\"X\", \"Y\"], \"out\", broadcast=1, axis=1)\n\n        def ref_op(X, Y):\n            res = np.sum(X, axis=0)\n            res = np.sum(res, axis=2)\n            return [res]\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, Y],\n            reference=ref_op,\n            threshold=1e-3)\n\n        # broadcasting with single elem dimensions at both ends\n        X = np.random.rand(2, 3, 4, 5).astype(np.float16)\n        Y = np.random.rand(1, 3, 4, 1).astype(np.float16)\n        op = core.CreateOperator(\n            \"SumReduceLike\", [\"X\", \"Y\"], \"out\", broadcast=1)\n\n        def ref_op(X, Y):\n            res = np.sum(X, axis=0)\n            res = np.sum(res, axis=2)\n            return [res.reshape(Y.shape)]\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, Y],\n            reference=ref_op,\n            threshold=1e-3)\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/elementwise_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestElementwiseOps(hu.HypothesisTestCase):\n\n    @given(n=st.integers(2, 10), m=st.integers(4, 6),\n           d=st.integers(2, 3), seed=st.integers(0, 1000), **hu.gcs)\n    def test_div(self, n, m, d, gc, dc, seed):\n        np.random.seed(seed)\n        X = np.random.rand(n, m, d).astype(np.float32)\n        Y = np.random.rand(n, m, d).astype(np.float32) + 5.0\n\n        def div_op(X, Y):\n            return [np.divide(X, Y)]\n\n        op = core.CreateOperator(\n            \"Div\",\n            [\"X\", \"Y\"],\n            [\"Z\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, Y],\n            reference=div_op,\n        )\n\n        self.assertGradientChecks(\n            gc, op, [X, Y], 0, [0], stepsize=1e-4, threshold=1e-2)\n\n    @given(n=st.integers(5, 6), m=st.integers(4, 6), seed=st.integers(0, 1000), **hu.gcs)\n    def test_log(self, n, m, gc, dc, seed):\n        np.random.seed(seed)\n        X = np.random.rand(n, m).astype(np.float32) + 1.0\n\n        def log_op(X):\n            return [np.log(X)]\n\n        op = core.CreateOperator(\n            \"Log\",\n            [\"X\"],\n            [\"Z\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=log_op,\n        )\n\n        self.assertGradientChecks(\n            gc, op, [X], 0, [0], stepsize=1e-4, threshold=1e-2)\n\n    @given(n=st.integers(2, 10), m=st.integers(4, 6),\n           d=st.integers(2, 3), seed=st.integers(0, 1000), **hu.gcs)\n    def test_powt(self, n, m, d, gc, dc, seed):\n        np.random.seed(seed)\n        X = np.random.rand(n, m, d).astype(np.float32) + 1.0\n        Y = np.random.rand(n, m, d).astype(np.float32) + 2.0\n\n        def powt_op(X, Y):\n            return [np.power(X, Y)]\n\n        #two gradients Y*X^(Y-1) and X^Y * ln(X)\n        def powt_grad(g_out, outputs, fwd_inputs):\n            [X, Y] = fwd_inputs\n            Z = outputs[0]\n            return ([Y * np.power(X, Y - 1), Z * np.log(X)] * g_out)\n\n        op = core.CreateOperator(\n            \"Pow\",\n            [\"X\", \"Y\"],\n            [\"Z\"]\n        )\n\n        self.assertReferenceChecks(device_option=gc,\n                                   op=op,\n                                   inputs=[X, Y],\n                                   reference=powt_op,\n                                   output_to_grad=\"Z\",\n                                   grad_reference=powt_grad)\n\n\n    @given(n=st.integers(5, 6), m=st.integers(4, 6), seed=st.integers(0, 1000), **hu.gcs)\n    def test_sqr(self, n, m, gc, dc, seed):\n        np.random.seed(seed)\n        X = np.random.rand(n, m).astype(np.float32)\n\n        def sqr_op(X):\n            return [np.square(X)]\n\n        op = core.CreateOperator(\n            \"Sqr\",\n            [\"X\"],\n            [\"Z\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=sqr_op,\n        )\n\n        self.assertGradientChecks(\n            gc, op, [X], 0, [0], stepsize=1e-4, threshold=1e-2)\n\n    @given(X=hu.tensor(elements=st.floats(0.02, 1)), **hu.gcs)\n    def test_sqrt(self, X, gc, dc):\n        def sqrt_op(X):\n            return [np.sqrt(X)]\n\n        op = core.CreateOperator(\n            \"Sqrt\",\n            [\"X\"],\n            [\"Y\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=sqrt_op,\n        )\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(\n            gc, op, [X], 0, [0], stepsize=1e-4, threshold=1e-2)\n\n    @given(X=hu.tensor(elements=st.floats(0.05,1)), **hu.gcs)\n    def test_sqrt_inplace(self, X, gc, dc):\n\n        def sqrt_op(X):\n            return [np.sqrt(X)]\n\n        op = core.CreateOperator(\n            \"Sqrt\",\n            [\"X\"],\n            [\"X\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=sqrt_op,\n        )\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(\n            gc, op, [X], 0, [0], stepsize=1e-4, threshold=1e-2)\n\n    @given(n=st.integers(5, 6), m=st.integers(4, 6), seed=st.integers(0, 1000), **hu.gcs)\n    def test_swish(self, n, m, gc, dc, seed):\n        np.random.seed(seed)\n        X = np.random.rand(n, m).astype(np.float32)\n\n        def swish(X):\n            return [np.divide(X, (1. + np.exp(-X)))]\n\n        op = core.CreateOperator(\n            \"Swish\",\n            [\"X\"],\n            [\"Z\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=swish,\n        )\n\n        self.assertGradientChecks(\n            gc, op, [X], 0, [0], stepsize=1e-4, threshold=1e-2)\n\n    @given(n=st.integers(5, 6), m=st.integers(4, 6), seed=st.integers(0, 1000), **hu.gcs)\n    def test_swish_gradient_inplace(self, n, m, gc, dc, seed):\n        np.random.seed(seed)\n        def swish(X):\n            return [np.divide(X, (1. + np.exp(-X)))]\n\n        def swish_gradient(X, Y, dY):\n            return [dY * (Y + np.divide(1. - Y, 1. + np.exp(-X)))]\n\n        X = np.random.rand(n, m).astype(np.float32)\n        Y = swish(X)[0]\n        dY = np.random.rand(n, m).astype(np.float32)\n        op = core.CreateOperator(\n            \"SwishGradient\",\n            [\"X\", \"Y\", \"grad\"],\n            \"grad\"\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, Y, dY],\n            reference=swish_gradient,\n        )\n\n    @given(n=st.integers(5, 6), m=st.integers(4, 6), seed=st.integers(0, 1000), **hu.gcs)\n    def test_sigmoid(self, n, m, gc, dc, seed):\n        np.random.seed(seed)\n        X = np.random.rand(n, m).astype(np.float32)\n\n        def sigmoid(X):\n            return [1. / (1. + np.exp(-X))]\n\n        op = core.CreateOperator(\n            \"Sigmoid\",\n            [\"X\"],\n            [\"Z\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=sigmoid,\n        )\n\n        self.assertGradientChecks(\n            gc, op, [X], 0, [0], stepsize=1e-4, threshold=1e-2)\n"
  },
  {
    "path": "caffe2/python/operator_test/emptysample_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\nimport numpy as np\n\n\nlengths = [[0], [1, 2], [1, 0, 2, 0]]\nfeatures1 = [[],\n             [1, 2, 2],\n             [[1, 1], [2, 2], [2, 2]]\n             ]\nfeatures2 = [[],\n             [2, 4, 4],\n             [[2, 2], [4, 4], [4, 4]]\n             ]\n\nlengths_exp = [[1], [1, 2], [1, 1, 2, 1]]\nfeatures1_exp = [[0],\n                 [1, 2, 2],\n                 [[1, 1], [0, 0], [2, 2], [2, 2], [0, 0]]]\nfeatures2_exp = [[0],\n                 [2, 4, 4],\n                 [[2, 2], [0, 0], [4, 4], [4, 4], [0, 0]]]\n\n\nclass TestEmptySampleOps(TestCase):\n    def test_emptysample(self):\n        for i in range(0, 3):\n            PadEmptyTest = core.CreateOperator(\n                'PadEmptySamples',\n                ['lengths', 'features1', 'features2'],\n                ['out_lengths', 'out_features1', 'out_features2'],\n            )\n            workspace.FeedBlob(\n                'lengths',\n                np.array(lengths[i], dtype=np.int32))\n            workspace.FeedBlob(\n                'features1',\n                np.array(features1[i], dtype=np.int64))\n            workspace.FeedBlob(\n                'features2',\n                np.array(features2[i], dtype=np.int64))\n            workspace.RunOperatorOnce(PadEmptyTest)\n            np.testing.assert_allclose(\n                lengths_exp[i],\n                workspace.FetchBlob('out_lengths'),\n                atol=1e-4, rtol=1e-4, err_msg='Mismatch in lengths')\n            np.testing.assert_allclose(\n                features1_exp[i],\n                workspace.FetchBlob('out_features1'),\n                atol=1e-4, rtol=1e-4, err_msg='Mismatch in features1')\n            np.testing.assert_allclose(\n                features2_exp[i],\n                workspace.FetchBlob('out_features2'),\n                atol=1e-4, rtol=1e-4, err_msg='Mismatch in features2')\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/extend_tensor_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\nimport numpy as np\n\n\nclass TestExtendTensorOp(TestCase):\n    def test_extend_tensor(self):\n        # Tensor of size 6 holding info about elements 0 to 5\n        old_tensor = np.array([1, 2, 3, 4, 5, 6], dtype=np.int32)\n        workspace.FeedBlob('old_tensor', old_tensor)\n\n        indices = np.array([0, 5, 8, 2, 3, 7], dtype=np.int32)\n        workspace.FeedBlob('indices', indices)\n\n        new_tensor_expected = np.array([1, 2, 3, 4, 5, 6, 0, 0, 0],\n                                       dtype=np.int32)\n\n        extend_tensor_op = core.CreateOperator(\n            'ExtendTensor',\n            ['old_tensor', 'indices'],\n            ['old_tensor'])\n\n        workspace.RunOperatorOnce(extend_tensor_op)\n        new_tensor_observed = workspace.FetchBlob('old_tensor')\n\n        np.testing.assert_array_equal(new_tensor_expected, new_tensor_observed)\n\n    def test_counting(self):\n        # Tensor of size 6 holding counts of elements with indices 0 to 5\n        counts = np.array([1, 2, 3, 4, 5, 6], dtype=np.float32)\n        workspace.FeedBlob('counts', counts)\n\n        # Indices of new words to be counted\n        indices = np.array([0, 5, 8, 2, 3, 7, 7], dtype=np.int32)\n        workspace.FeedBlob('indices', indices)\n\n        # Extend the 'counts' tensor if necessary (if new words are seen)\n        extend_tensor_op = core.CreateOperator(\n            'ExtendTensor',\n            ['counts', 'indices'],\n            ['counts'])\n        workspace.RunOperatorOnce(extend_tensor_op)\n\n        ones_counts = np.array([1], dtype=np.float32)\n        ones_indices = np.array(\n            [1 for i in range(len(indices))], dtype=np.float32)\n        one = np.array([1], dtype=np.float32)\n        workspace.FeedBlob('ones_counts', ones_counts)\n        workspace.FeedBlob('ones_indices', ones_indices)\n        workspace.FeedBlob('one', one)\n\n        ins = ['counts', 'ones_counts', 'indices', 'ones_indices', 'one']\n        op = core.CreateOperator('ScatterWeightedSum', ins, ['counts'])\n        workspace.RunOperatorOnce(op)\n\n        new_tensor_expected = np.array([2, 2, 4, 5, 5, 7, 0, 2, 1],\n                                       dtype=np.float32)\n        new_tensor_observed = workspace.FetchBlob('counts')\n\n        np.testing.assert_array_equal(new_tensor_expected, new_tensor_observed)\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/fc_operator_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core\nfrom hypothesis import assume, given, settings\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestFcOperator(hu.HypothesisTestCase):\n    def _run_test(self, n, m, k, transposed, multi_dim, dtype, engine, gc, dc):\n        if dtype == np.float16:\n            # fp16 only supported with CUDA\n            assume(gc.device_type == caffe2_pb2.CUDA)\n            dc = [d for d in dc if d.device_type == caffe2_pb2.CUDA]\n\n        if engine == 'TENSORCORE':\n            # TensorCore only makes sense with CUDA\n            assume(gc.device_type == caffe2_pb2.CUDA)\n            # ensures TensorCore kernels can be called\n            m *= 8\n            k *= 8\n            n *= 8\n\n        X = np.random.rand(m, k).astype(dtype) - 0.5\n        if multi_dim:\n            if transposed:\n                W = np.random.rand(k, n, 1, 1).astype(dtype) - 0.5\n            else:\n                W = np.random.rand(n, k, 1, 1).astype(dtype) - 0.5\n        else:\n            if transposed:\n                W = np.random.rand(k, n).astype(dtype) - 0.5\n            else:\n                W = np.random.rand(n, k).astype(dtype) - 0.5\n        b = np.random.rand(n).astype(dtype) - 0.5\n\n        def fc_op(X, W, b):\n            return [np.dot(X, W.reshape(n, k).transpose()) + b.reshape(n)]\n\n        def fc_tranposed_op(X, W, b):\n            return [np.dot(X, W.reshape(k, n)) + b.reshape(n)]\n\n        op = core.CreateOperator(\n            'FCTransposed' if transposed else 'FC',\n            ['X', 'W', 'b'],\n            'out',\n            engine=engine,\n        )\n\n        if dtype == np.float16 and gc.device_type == caffe2_pb2.CUDA:\n            a = caffe2_pb2.Argument()\n            a.i = 1\n            a.name = \"float16_compute\"\n            op.arg.extend([a])\n\n        # Check against numpy reference\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, W, b],\n            reference=fc_tranposed_op if transposed else fc_op,\n        )\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X, W, b], [0])\n\n        # Gradient checks\n        threshold = 0.5 if dtype == np.float16 else 0.005\n        stepsize = 0.5 if dtype == np.float16 else 0.05\n        for i in range(3):\n            self.assertGradientChecks(gc, op, [X, W, b], i, [0],\n                                      threshold=threshold, stepsize=stepsize)\n\n    @settings(max_examples=50)\n    @given(n=st.integers(1, 5),\n           m=st.integers(0, 5),\n           k=st.integers(1, 5),\n           multi_dim=st.sampled_from([True, False]),\n           dtype=st.sampled_from([np.float32, np.float16]),\n           engine=st.sampled_from(['', 'TENSORCORE']),\n           **hu.gcs)\n    def test_fc(self, **kwargs):\n        self._run_test(transposed=False, **kwargs)\n\n    @settings(max_examples=50)\n    @given(n=st.integers(1, 5),\n           m=st.integers(0, 5),\n           k=st.integers(1, 5),\n           multi_dim=st.sampled_from([True, False]),\n           dtype=st.sampled_from([np.float32, np.float16]),\n           engine=st.sampled_from(['', 'TENSORCORE']),\n           **hu.gcs)\n    def test_fc_transposed(self, **kwargs):\n        self._run_test(transposed=True, **kwargs)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/filler_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core, workspace\nfrom caffe2.proto import caffe2_pb2\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\n\nimport numpy as np\n\n\ndef _fill_diagonal(shape, value):\n    result = np.zeros(shape)\n    np.fill_diagonal(result, value)\n    return (result,)\n\n\nclass TestFillerOperator(hu.HypothesisTestCase):\n\n    @given(**hu.gcs)\n    def test_shape_error(self, gc, dc):\n        op = core.CreateOperator(\n            'GaussianFill',\n            [],\n            'out',\n            shape=32,  # illegal parameter\n            mean=0.0,\n            std=1.0,\n        )\n        exception = False\n        try:\n            workspace.RunOperatorOnce(op)\n        except Exception:\n            exception = True\n        self.assertTrue(exception, \"Did not throw exception on illegal shape\")\n\n        op = core.CreateOperator(\n            'ConstantFill',\n            [],\n            'out',\n            shape=[],  # scalar\n            value=2.0,\n        )\n        exception = False\n        self.assertTrue(workspace.RunOperatorOnce(op))\n        self.assertEqual(workspace.FetchBlob('out'), [2.0])\n\n    @given(\n        shape=hu.dims().flatmap(\n            lambda dims: hu.arrays(\n                [dims], dtype=np.int64,\n                elements=st.integers(min_value=0, max_value=20)\n            )\n        ),\n        a=st.integers(min_value=0, max_value=100),\n        b=st.integers(min_value=0, max_value=100),\n        **hu.gcs\n    )\n    def test_uniform_int_fill_op_blob_input(self, shape, a, b, gc, dc):\n        net = core.Net('test_net')\n\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n            shape_blob = net.Const(shape, dtype=np.int64)\n        a_blob = net.Const(a, dtype=np.int32)\n        b_blob = net.Const(b, dtype=np.int32)\n        uniform_fill = net.UniformIntFill([shape_blob, a_blob, b_blob],\n                                          1, input_as_shape=1)\n\n        workspace.RunNetOnce(net)\n\n        blob_out = workspace.FetchBlob(uniform_fill)\n        if b < a:\n            new_shape = shape[:]\n            new_shape[0] = 0\n            np.testing.assert_array_equal(new_shape, blob_out.shape)\n        else:\n            np.testing.assert_array_equal(shape, blob_out.shape)\n            self.assertTrue((blob_out >= a).all())\n            self.assertTrue((blob_out <= b).all())\n\n    @given(\n        **hu.gcs\n    )\n    def test_uniform_fill_using_arg(self, gc, dc):\n        net = core.Net('test_net')\n        shape = [2**3, 5]\n        # uncomment this to test filling large blob\n        # shape = [2**30, 5]\n        min_v = -100\n        max_v = 100\n        output_blob = net.UniformIntFill(\n            [],\n            ['output_blob'],\n            shape=shape,\n            min=min_v,\n            max=max_v,\n        )\n\n        workspace.RunNetOnce(net)\n        output_data = workspace.FetchBlob(output_blob)\n\n        np.testing.assert_array_equal(shape, output_data.shape)\n        min_data = np.min(output_data)\n        max_data = np.max(output_data)\n\n        self.assertGreaterEqual(min_data, min_v)\n        self.assertLessEqual(max_data, max_v)\n\n        self.assertNotEqual(min_data, max_data)\n\n    @given(\n        shape=st.sampled_from(\n            [\n                [3, 3],\n                [5, 5, 5],\n                [7, 7, 7, 7],\n            ]\n        ),\n        **hu.gcs\n    )\n    def test_diagonal_fill_op_float(self, shape, gc, dc):\n        value = 2.5\n        op = core.CreateOperator(\n            'DiagonalFill',\n            [],\n            'out',\n            shape=shape,  # scalar\n            value=value,\n        )\n\n        for device_option in dc:\n            op.device_option.CopyFrom(device_option)\n            # Check against numpy reference\n            self.assertReferenceChecks(gc, op, [shape, value], _fill_diagonal)\n\n    @given(**hu.gcs)\n    def test_diagonal_fill_op_int(self, gc, dc):\n        value = 2\n        shape = [3, 3]\n        op = core.CreateOperator(\n            'DiagonalFill',\n            [],\n            'out',\n            shape=shape,\n            dtype=core.DataType.INT32,\n            value=value,\n        )\n\n        # Check against numpy reference\n        self.assertReferenceChecks(gc, op, [shape, value], _fill_diagonal)\n\n    @given(lengths=st.lists(st.integers(min_value=0, max_value=10),\n                            min_size=0,\n                            max_size=10),\n           **hu.gcs)\n    def test_lengths_range_fill(self, lengths, gc, dc):\n        op = core.CreateOperator(\n            \"LengthsRangeFill\",\n            [\"lengths\"],\n            [\"increasing_seq\"])\n\n        def _len_range_fill(lengths):\n            sids = []\n            for _, l in enumerate(lengths):\n                sids.extend(list(range(l)))\n            return (np.array(sids, dtype=np.int32), )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[np.array(lengths, dtype=np.int32)],\n            reference=_len_range_fill)\n\n    @given(**hu.gcs)\n    def test_gaussian_fill_op(self, gc, dc):\n        op = core.CreateOperator(\n            'GaussianFill',\n            [],\n            'out',\n            shape=[17, 3, 3],  # sample odd dimensions\n            mean=0.0,\n            std=1.0,\n        )\n\n        for device_option in dc:\n            op.device_option.CopyFrom(device_option)\n            assert workspace.RunOperatorOnce(op), \"GaussianFill op did not run \"\n            \"successfully\"\n\n            blob_out = workspace.FetchBlob('out')\n            assert np.count_nonzero(blob_out) > 0, \"All generated elements are \"\n            \"zeros. Is the random generator functioning correctly?\"\n\n    @given(**hu.gcs)\n    def test_msra_fill_op(self, gc, dc):\n        op = core.CreateOperator(\n            'MSRAFill',\n            [],\n            'out',\n            shape=[15, 5, 3],  # sample odd dimensions\n        )\n        for device_option in dc:\n            op.device_option.CopyFrom(device_option)\n            assert workspace.RunOperatorOnce(op), \"MSRAFill op did not run \"\n            \"successfully\"\n\n            blob_out = workspace.FetchBlob('out')\n            assert np.count_nonzero(blob_out) > 0, \"All generated elements are \"\n            \"zeros. Is the random generator functioning correctly?\"\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/find_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nimport hypothesis.strategies as st\nfrom hypothesis import given\n\n\nimport caffe2.python.hypothesis_test_util as hu\n\nimport numpy as np\n\n\nclass TestFindOperator(hu.HypothesisTestCase):\n\n    @given(n=st.sampled_from([1, 4, 8, 31, 79, 150]),\n           idxsize=st.sampled_from([2, 4, 8, 1000, 5000]),\n           **hu.gcs)\n    def test_find(self, n, idxsize, gc, dc):\n        maxval = 10\n\n        def findop(idx, X):\n            res = []\n            for j in list(X.flatten()):\n                i = np.where(idx == j)[0]\n                if len(i) == 0:\n                    res.append(-1)\n                else:\n                    res.append(i[-1])\n\n            print(\"Idx: {} X: {}\".format(idx, X))\n            print(\"Res: {}\".format(res))\n            return [np.array(res).astype(np.int32)]\n\n        X = (np.random.rand(n) * maxval).astype(np.int32)\n        idx = (np.random.rand(idxsize) * maxval).astype(np.int32)\n\n        op = core.CreateOperator(\n            \"Find\",\n            [\"idx\", \"X\"],\n            [\"y\"],\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[idx, X],\n            reference=findop,\n        )\n"
  },
  {
    "path": "caffe2/python/operator_test/flatten_op_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom hypothesis import given\nimport numpy as np\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestFlatten(hu.HypothesisTestCase):\n    @given(X=hu.tensor(min_dim=2, max_dim=4),\n           **hu.gcs)\n    def test_flatten(self, X, gc, dc):\n        for axis in range(X.ndim + 1):\n            op = core.CreateOperator(\n                \"Flatten\",\n                [\"X\"],\n                [\"Y\"],\n                axis=axis)\n\n            def flatten_ref(X):\n                shape = X.shape\n                outer = np.prod(shape[:axis]).astype(int)\n                inner = np.prod(shape[axis:]).astype(int)\n                return np.copy(X).reshape(outer, inner),\n\n            self.assertReferenceChecks(gc, op, [X], flatten_ref)\n\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/flexible_top_k_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom collections import OrderedDict\nimport numpy as np\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestFlexibleTopK(hu.HypothesisTestCase):\n    def flexible_top_k_ref(self, X, k):\n        X_flat = X.reshape((-1, X.shape[-1]))\n        indices_ref = np.ndarray(shape=sum(k), dtype=np.int32)\n        values_ref = np.ndarray(shape=sum(k), dtype=np.float32)\n        offset = 0\n        for i in range(X_flat.shape[0]):\n            od = OrderedDict()\n            for j in range(X_flat.shape[1]):\n                val = X_flat[i, j]\n                if val not in od:\n                    od[val] = []\n                od[val].append(j)\n            k_ = 0\n            for val, idxs in sorted(od.items(), reverse=True):\n                for idx in idxs:\n                    indices_ref[offset + k_] = idx\n                    values_ref[offset + k_] = val\n                    k_ += 1\n                    if k_ >= k[i]:\n                        break\n                if k_ >= k[i]:\n                    break\n            offset += k[i]\n\n        return (values_ref, indices_ref)\n\n    @given(X=hu.tensor(min_dim=2), **hu.gcs_cpu_only)\n    def test_flexible_top_k(self, X, gc, dc):\n        X = X.astype(dtype=np.float32)\n        k_shape = (int(X.size / X.shape[-1]), )\n        k = np.random.randint(1, high=X.shape[-1] + 1, size=k_shape)\n\n        output_list = [\"Values\", \"Indices\"]\n        op = core.CreateOperator(\"FlexibleTopK\", [\"X\", \"k\"], output_list,\n                                 device_option=gc)\n\n        def bind_ref(X_loc, k):\n            ret = self.flexible_top_k_ref(X_loc, k)\n            return ret\n\n        self.assertReferenceChecks(gc, op, [X, k], bind_ref)\n\n    @given(X=hu.tensor(min_dim=2), **hu.gcs_cpu_only)\n    def test_flexible_top_k_grad(self, X, gc, dc):\n        X = X.astype(np.float32)\n        k_shape = (int(X.size / X.shape[-1]), )\n        k = np.random.randint(1, high=X.shape[-1] + 1, size=k_shape)\n\n        # this try to make sure adding stepsize (0.05)\n        # will not change TopK selections at all\n        # since dims max_value = 5 as defined in\n        # caffe2/caffe2/python/hypothesis_test_util.py\n        for i in range(X.shape[-1]):\n            X[..., i] = i * 1.0 / X.shape[-1]\n\n        op = core.CreateOperator(\n            \"FlexibleTopK\", [\"X\", \"k\"], [\"Values\", \"Indices\"], device_option=gc\n        )\n\n        self.assertGradientChecks(gc, op, [X, k], 0, [0])\n"
  },
  {
    "path": "caffe2/python/operator_test/floor_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport caffe2.python.hypothesis_test_util as hu\nimport numpy as np\n\nimport unittest\n\n\nclass TestFloor(hu.HypothesisTestCase):\n\n    @given(X=hu.tensor(),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs)\n    def test_floor(self, X, gc, dc, engine):\n        op = core.CreateOperator(\"Floor\", [\"X\"], [\"Y\"], engine=engine)\n\n        def floor_ref(X):\n            return (np.floor(X),)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=floor_ref)\n\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/gather_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport numpy as np\n\nfrom caffe2.python import core, workspace\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport hypothesis.extra.numpy as hnp\n\n\nclass TestGatherOps(hu.HypothesisTestCase):\n    @given(rows_num=st.integers(1, 10000),\n           index_num=st.integers(0, 5000),\n           **hu.gcs)\n    def test_gather_ops(self, rows_num, index_num, gc, dc):\n        data = np.random.random((rows_num, 10, 20)).astype(np.float32)\n        ind = np.random.randint(rows_num, size=(index_num, )).astype('int32')\n        op = core.CreateOperator(\n            'Gather',\n            ['data', 'ind'],\n            ['output'])\n\n        def ref_gather(data, ind):\n            if ind.size == 0:\n                return [np.zeros((0, 10, 20)).astype(np.float32)]\n\n            output = [r for r in [data[i] for i in ind]]\n            return [output]\n\n        self.assertReferenceChecks(gc, op, [data, ind], ref_gather)\n\n\n@st.composite\ndef _inputs(draw):\n    rows_num = draw(st.integers(1, 100))\n    index_num = draw(st.integers(1, 10))\n    batch_size = draw(st.integers(2, 10))\n    return (\n        draw(hnp.arrays(\n            np.float32,\n            (batch_size, rows_num, 2),\n            elements=st.floats(-10.0, 10.0),\n        )),\n        draw(hnp.arrays(\n            np.int32,\n            (index_num, 1),\n            elements=st.integers(0, rows_num - 1),\n        )),\n    )\n\n\nclass TestBatchGatherOps(hu.HypothesisTestCase):\n    @given(inputs=_inputs(),\n           **hu.gcs)\n    def test_batch_gather_ops(self, inputs, gc, dc):\n        data, ind = inputs\n        op = core.CreateOperator(\n            'BatchGather',\n            ['data', 'ind'],\n            ['output'])\n\n        def ref_batch_gather(data, ind):\n            output = []\n            for b in range(data.shape[0]):\n                output.append([r for r in [data[b][i] for i in ind]])\n            return [output]\n\n        self.assertReferenceChecks(gc, op, [data, ind], ref_batch_gather)\n        self.assertGradientChecks(gc, op, [data, ind], 0, [0])\n\n\nclass TestGatherFused8BitRowwise(hu.HypothesisTestCase):\n    @given(rows_num=st.integers(1, 10000),\n           cols_num=st.integers(1, 128),\n           index_num=st.integers(0, 5000),\n           **hu.gcs)\n    def test_batch_gather_ops(self, rows_num, cols_num, index_num, gc, dc):\n        data = np.random.random((rows_num, cols_num)).astype(np.float32)\n        ind = np.random.randint(rows_num, size=(index_num, )).astype('int32')\n\n        net = core.Net(\"bench\")\n\n        quantized_data = net.FloatToFused8BitRowwiseQuantized(\n            'data', 'quantized_data')\n        dequantized_data = net.Fused8BitRowwiseQuantizedToFloat(\n            quantized_data, 'dequantized_data')\n\n        net.Gather(\n            [dequantized_data, 'ind'], 'gather_reference')\n        net.GatherFused8BitRowwise(\n            [quantized_data, 'ind'], 'gather_quantized')\n\n        workspace.FeedBlob('data', data)\n        workspace.FeedBlob('ind', ind)\n        workspace.CreateNet(net)\n        workspace.RunNetOnce(net)\n\n        gather_reference = workspace.FetchBlob('gather_reference')\n        gather_quantized = workspace.FetchBlob('gather_quantized')\n        np.testing.assert_array_almost_equal(gather_reference, gather_quantized)\n\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/gather_ranges_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nfrom hypothesis import given\nfrom hypothesis import strategies as st\n\nimport caffe2.python.hypothesis_test_util as hu\nimport numpy as np\n\n\ndef batched_boarders_and_data(\n        data_min_size=5, data_max_size=10,\n        examples_min_number=1, examples_max_number=4,\n        example_min_size=1, example_max_size=3,\n        dtype=np.float32, elements=None):\n    dims_ = st.tuples(\n        st.integers(min_value=data_min_size,\n                    max_value=data_max_size),\n        st.integers(min_value=examples_min_number,\n                    max_value=examples_max_number),\n        st.integers(min_value=example_min_size,\n                    max_value=example_max_size),\n    )\n    return dims_.flatmap(\n        lambda dims: st.tuples(\n            hu.arrays(\n                [dims[1], dims[2], 2], dtype=np.int32,\n                elements=st.integers(min_value=0, max_value=dims[0])\n            ),\n            hu.arrays([dims[0]], dtype, elements)\n        ))\n\n\n@st.composite\ndef _tensor_splits(draw):\n    lengths = draw(st.lists(st.integers(1, 5), min_size=1, max_size=10))\n    batch_size = draw(st.integers(1, 5))\n    element_pairs = [\n        (batch, r) for batch in range(batch_size) for r in range(len(lengths))\n    ]\n    perm = draw(st.permutations(element_pairs))\n    perm = perm[:-1]  # skip one range\n    ranges = [[(0, 0)] * len(lengths) for _ in range(batch_size)]\n    offset = 0\n    for pair in perm:\n        ranges[pair[0]][pair[1]] = (offset, lengths[pair[1]])\n        offset += lengths[pair[1]]\n\n    data = draw(st.lists(\n        st.floats(min_value=-1.0, max_value=1.0),\n        min_size=offset,\n        max_size=offset\n    ))\n\n    key = draw(st.permutations(range(offset)))\n\n    return (\n        np.array(data).astype(np.float32), np.array(ranges),\n        np.array(lengths), np.array(key).astype(np.int64)\n    )\n\n\ndef gather_ranges(data, ranges):\n    lengths = []\n    output = []\n    for example_ranges in ranges:\n        length = 0\n        for range in example_ranges:\n            assert len(range) == 2\n            output.extend(data[range[0]:range[0] + range[1]])\n            length += range[1]\n        lengths.append(length)\n    return output, lengths\n\n\ndef gather_ranges_to_dense(data, ranges, lengths):\n    outputs = []\n    assert len(ranges)\n    batch_size = len(ranges)\n    assert len(ranges[0])\n    num_ranges = len(ranges[0])\n    assert ranges.shape[2] == 2\n    for i in range(num_ranges):\n        out = []\n        for j in range(batch_size):\n            start, length = ranges[j][i]\n            if not length:\n                out.append([0] * lengths[i])\n            else:\n                assert length == lengths[i]\n                out.append(data[start:start + length])\n        outputs.append(np.array(out))\n    return outputs\n\n\ndef gather_ranges_to_dense_with_key(data, ranges, key, lengths):\n    outputs = []\n    assert len(ranges)\n    batch_size = len(ranges)\n    assert len(ranges[0])\n    num_ranges = len(ranges[0])\n    assert ranges.shape[2] == 2\n    for i in range(num_ranges):\n        out = []\n        for j in range(batch_size):\n            start, length = ranges[j][i]\n            if not length:\n                out.append([0] * lengths[i])\n            else:\n                assert length == lengths[i]\n                key_data_list = zip(\n                    key[start:start + length],\n                    data[start:start + length])\n                sorted_key_data_list = sorted(key_data_list, key=lambda x: x[0])\n                sorted_data = [d for (k, d) in sorted_key_data_list]\n                out.append(sorted_data)\n        outputs.append(np.array(out))\n    return outputs\n\n\nclass TestGatherRanges(hu.HypothesisTestCase):\n    @given(boarders_and_data=batched_boarders_and_data(), **hu.gcs_cpu_only)\n    def test_gather_ranges(self, boarders_and_data, gc, dc):\n        boarders, data = boarders_and_data\n\n        def boarders_to_range(boarders):\n            assert len(boarders) == 2\n            boarders = sorted(boarders)\n            return [boarders[0], boarders[1] - boarders[0]]\n\n        ranges = np.apply_along_axis(boarders_to_range, 2, boarders)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=core.CreateOperator(\"GatherRanges\",\n                                   [\"data\", \"ranges\"],\n                                   [\"output\", \"lengths\"]),\n            inputs=[data, ranges],\n            reference=gather_ranges,\n        )\n\n    @given(tensor_splits=_tensor_splits(), **hu.gcs_cpu_only)\n    def test_gather_ranges_split(self, tensor_splits, gc, dc):\n        data, ranges, lengths, _ = tensor_splits\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=core.CreateOperator(\n                \"GatherRangesToDense\",\n                ['data', 'ranges'],\n                ['X_{}'.format(i) for i in range(len(lengths))],\n                lengths=lengths\n            ),\n            inputs=[data, ranges, lengths],\n            reference=gather_ranges_to_dense\n        )\n\n    @given(tensor_splits=_tensor_splits(), **hu.gcs_cpu_only)\n    def test_gather_ranges_with_key_split(self, tensor_splits, gc, dc):\n        data, ranges, lengths, key = tensor_splits\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=core.CreateOperator(\n                \"GatherRangesToDense\",\n                ['data', 'ranges', 'key'],\n                ['X_{}'.format(i) for i in range(len(lengths))],\n                lengths=lengths\n            ),\n            inputs=[data, ranges, key, lengths],\n            reference=gather_ranges_to_dense_with_key\n        )\n\n    def test_shape_and_type_inference(self):\n        with hu.temp_workspace(\"shape_type_inf_int32\"):\n            net = core.Net('test_net')\n            net.ConstantFill(\n                [], \"ranges\", shape=[3, 5, 2], dtype=core.DataType.INT32,\n            )\n            net.ConstantFill(\n                [], \"values\", shape=[64], dtype=core.DataType.INT64,\n            )\n            net.GatherRanges(['values', 'ranges'], ['values_output', 'lengths_output'])\n            (shapes, types) = workspace.InferShapesAndTypes([net], {})\n\n            self.assertEqual(shapes[\"values_output\"], [64])\n            self.assertEqual(types[\"values_output\"], core.DataType.INT64)\n            self.assertEqual(shapes[\"lengths_output\"], [3])\n            self.assertEqual(types[\"lengths_output\"], core.DataType.INT32)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/given_tensor_fill_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport caffe2.python.hypothesis_test_util as hu\nimport numpy as np\n\nimport unittest\n\n\nclass TestGivenTensorFillOps(hu.HypothesisTestCase):\n    @given(X=hu.tensor(min_dim=1, max_dim=4, dtype=np.int32),\n           t=st.sampled_from([\n               (core.DataType.BOOL, np.bool_, \"GivenTensorFill\"),\n               (core.DataType.INT32, np.int32, \"GivenTensorFill\"),\n               (core.DataType.FLOAT, np.float32, \"GivenTensorFill\"),\n               (core.DataType.INT32, np.int32, \"GivenTensorIntFill\"),\n               (core.DataType.INT64, np.int64, \"GivenTensorInt64Fill\"),\n               (core.DataType.BOOL, np.bool_, \"GivenTensorBoolFill\"),\n               (core.DataType.DOUBLE, np.double, \"GivenTensorDoubleFill\"),\n               (core.DataType.INT32, np.double, \"GivenTensorDoubleFill\"),\n           ]),\n           **hu.gcs)\n    def test_given_tensor_fill(self, X, t, gc, dc):\n        X = X.astype(t[1])\n        print('X: ', str(X))\n        op = core.CreateOperator(\n            t[2], [], [\"Y\"],\n            shape=X.shape,\n            dtype=t[0],\n            values=X.reshape((1, X.size)),\n        )\n\n        def constant_fill(*args, **kw):\n            return [X]\n\n        self.assertReferenceChecks(gc, op, [], constant_fill)\n        self.assertDeviceChecks(dc, op, [], [0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/glu_op_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import assume, given, settings, HealthCheck\nimport hypothesis.strategies as st\nimport caffe2.python.hypothesis_test_util as hu\nimport numpy as np\n\nimport unittest\n\n\nclass TestGlu(hu.HypothesisTestCase):\n    # Suppress filter_too_much health check.\n    # Reproduce by commenting @settings and uncommenting @seed.\n    # @seed(302934307671667531413257853548643485645)\n    @settings(suppress_health_check=[HealthCheck.filter_too_much])\n    @given(\n        X=hu.tensor(),\n        axis=st.integers(min_value=0, max_value=3),\n        **hu.gcs\n    )\n    def test_glu_old(self, X, axis, gc, dc):\n        def glu_ref(X):\n            x1, x2 = np.split(X, [X.shape[axis] // 2], axis=axis)\n            Y = x1 * (1. / (1. + np.exp(-x2)))\n            return [Y]\n\n        # Test only valid tensors.\n        assume(axis < X.ndim)\n        assume(X.shape[axis] % 2 == 0)\n        op = core.CreateOperator(\"Glu\", [\"X\"], [\"Y\"], dim=axis)\n        self.assertReferenceChecks(gc, op, [X], glu_ref)\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/group_conv_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nfrom hypothesis import assume, given, settings\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\nimport unittest\n\n\nclass TestGroupConvolution(hu.HypothesisTestCase):\n\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           size=st.integers(7, 10),\n           group=st.integers(1, 4),\n           input_channels_per_group=st.integers(1, 8),\n           output_channels_per_group=st.integers(1, 8),\n           batch_size=st.integers(1, 3),\n           # TODO(jiayq): if needed, add NHWC support.\n           order=st.sampled_from([\"NCHW\"]),\n           # Note: Eigen does not support group convolution, but it should\n           # fall back to the default engine without failing.\n           engine=st.sampled_from([\"\", \"CUDNN\", \"EIGEN\"]),\n           use_bias=st.booleans(),\n           **hu.gcs)\n    @settings(max_examples=2, timeout=100)\n    def test_group_convolution(\n            self, stride, pad, kernel, size, group,\n            input_channels_per_group, output_channels_per_group, batch_size,\n            order, engine, use_bias, gc, dc):\n        assume(size >= kernel)\n        input_channels = input_channels_per_group * group\n        output_channels = output_channels_per_group * group\n\n        op = core.CreateOperator(\n            \"Conv\",\n            [\"X\", \"w\", \"b\"] if use_bias else [\"X\", \"w\"],\n            [\"Y\"],\n            stride=stride,\n            kernel=kernel,\n            pad=pad,\n            order=order,\n            engine=engine,\n            group=group,\n        )\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32) - 0.5\n        w = np.random.rand(\n            output_channels, kernel, kernel,\n            input_channels_per_group).astype(np.float32)\\\n            - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n        if order == \"NCHW\":\n            X = X.transpose((0, 3, 1, 2))\n            w = w.transpose((0, 3, 1, 2))\n\n        inputs = [X, w, b] if use_bias else [X, w]\n\n        self.assertDeviceChecks(dc, op, inputs, [0])\n        for i in range(len(inputs)):\n            self.assertGradientChecks(gc, op, inputs, i, [0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/gru_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import workspace, core, scope, gru_cell\nfrom caffe2.python.model_helper import ModelHelper\nfrom caffe2.python.rnn.rnn_cell_test_util import sigmoid, tanh, _prepare_rnn\nimport caffe2.python.hypothesis_test_util as hu\nfrom caffe2.proto import caffe2_pb2\n\nfrom functools import partial\nfrom hypothesis import given\nfrom hypothesis import settings as ht_settings\nimport hypothesis.strategies as st\nimport numpy as np\nimport unittest\n\n\ndef gru_unit(*args, **kwargs):\n    '''\n    Implements one GRU unit, for one time step\n\n    Shapes:\n    hidden_t_prev.shape     = (1, N, D)\n    gates_out_t.shape       = (1, N, G)\n    seq_lenths.shape        = (N,)\n    '''\n\n    drop_states = kwargs.get('drop_states', False)\n    sequence_lengths = kwargs.get('sequence_lengths', True)\n\n    if sequence_lengths:\n        hidden_t_prev, gates_out_t, seq_lengths, timestep = args\n    else:\n        hidden_t_prev, gates_out_t, timestep = args\n\n    N = hidden_t_prev.shape[1]\n    D = hidden_t_prev.shape[2]\n    G = gates_out_t.shape[2]\n    t = (timestep * np.ones(shape=(N, D))).astype(np.int32)\n    assert t.shape == (N, D)\n    assert G == 3 * D\n    # Calculate reset, update, and output gates separately\n    # because output gate depends on reset gate.\n    gates_out_t = gates_out_t.reshape(N, 3, D)\n    reset_gate_t = gates_out_t[:, 0, :].reshape(N, D)\n    update_gate_t = gates_out_t[:, 1, :].reshape(N, D)\n    output_gate_t = gates_out_t[:, 2, :].reshape(N, D)\n\n    # Calculate gate outputs.\n    reset_gate_t = sigmoid(reset_gate_t)\n    update_gate_t = sigmoid(update_gate_t)\n    output_gate_t = tanh(output_gate_t)\n\n    if sequence_lengths:\n        seq_lengths = (np.ones(shape=(N, D)) *\n                       seq_lengths.reshape(N, 1)).astype(np.int32)\n        assert seq_lengths.shape == (N, D)\n        valid = (t < seq_lengths).astype(np.int32)\n    else:\n        valid = np.ones(shape=(N, D))\n    assert valid.shape == (N, D)\n    hidden_t = update_gate_t * hidden_t_prev + (1 - update_gate_t) * output_gate_t\n    hidden_t = hidden_t * valid + hidden_t_prev * (1 - valid) * (1 - drop_states)\n    hidden_t = hidden_t.reshape(1, N, D)\n\n    return (hidden_t, )\n\n\ndef gru_reference(input, hidden_input,\n                   reset_gate_w, reset_gate_b,\n                   update_gate_w, update_gate_b,\n                   output_gate_w, output_gate_b,\n                   seq_lengths, drop_states=False,\n                   linear_before_reset=False):\n    D = hidden_input.shape[hidden_input.ndim - 1]\n    T = input.shape[0]\n    N = input.shape[1]\n    G = input.shape[2]\n    print(\"Dimensions: T= \", T, \" N= \", N, \" G= \", G, \" D= \", D)\n    hidden = np.zeros(shape=(T + 1, N, D))\n    hidden[0, :, :] = hidden_input\n\n    for t in range(T):\n        input_t = input[t].reshape(1, N, G)\n        hidden_t_prev = hidden[t].reshape(1, N, D)\n\n        # Split input contributions for three gates.\n        input_t = input_t.reshape(N, 3, D)\n        input_reset = input_t[:, 0, :].reshape(N, D)\n        input_update = input_t[:, 1, :].reshape(N, D)\n        input_output = input_t[:, 2, :].reshape(N, D)\n\n        reset_gate = np.dot(hidden_t_prev, reset_gate_w.T) + reset_gate_b\n        reset_gate = reset_gate + input_reset\n\n        update_gate = np.dot(hidden_t_prev, update_gate_w.T) + update_gate_b\n        update_gate = update_gate + input_update\n\n        if linear_before_reset:\n            with_linear = np.dot(hidden_t_prev, output_gate_w.T) + output_gate_b\n            output_gate = sigmoid(reset_gate) * with_linear\n        else:\n            with_reset = hidden_t_prev * sigmoid(reset_gate)\n            output_gate = np.dot(with_reset, output_gate_w.T) + output_gate_b\n        output_gate = output_gate + input_output\n\n        gates_out_t = np.concatenate(\n            (reset_gate, update_gate, output_gate),\n            axis=2,\n        )\n        print(reset_gate, update_gate, output_gate, gates_out_t, sep=\"\\n\")\n\n        (hidden_t, ) = gru_unit(\n            hidden_t_prev,\n            gates_out_t,\n            seq_lengths,\n            t,\n            drop_states=drop_states\n        )\n        hidden[t + 1] = hidden_t\n\n    return (\n        hidden[1:],\n        hidden[-1].reshape(1, N, D),\n    )\n\n\ndef gru_unit_op_input():\n    '''\n    Create input tensor where each dimension is from 1 to 4, ndim=3 and\n    last dimension size is a factor of 3\n\n    hidden_t_prev.shape     = (1, N, D)\n    '''\n    dims_ = st.tuples(\n        st.integers(min_value=1, max_value=1),  # 1, one timestep\n        st.integers(min_value=1, max_value=4),  # n\n        st.integers(min_value=1, max_value=4),  # d\n    )\n\n    def create_input(dims):\n        dims = list(dims)\n        dims[2] *= 3\n        return hu.arrays(dims)\n\n    return dims_.flatmap(create_input)\n\n\ndef gru_input():\n    '''\n    Create input tensor where each dimension is from 1 to 4, ndim=3 and\n    last dimension size is a factor of 3\n    '''\n    dims_ = st.tuples(\n        st.integers(min_value=1, max_value=4),  # t\n        st.integers(min_value=1, max_value=4),  # n\n        st.integers(min_value=1, max_value=4),  # d\n    )\n\n    def create_input(dims):\n        dims = list(dims)\n        dims[2] *= 3\n        return hu.arrays(dims)\n\n    return dims_.flatmap(create_input)\n\n\ndef _prepare_gru_unit_op(gc, n, d, outputs_with_grads,\n                         forward_only=False, drop_states=False,\n                         sequence_lengths=False,\n                         two_d_initial_states=None):\n    print(\"Dims: (n,d) = ({},{})\".format(n, d))\n\n    def generate_input_state(n, d):\n        if two_d_initial_states:\n            return np.random.randn(n, d).astype(np.float32)\n        else:\n            return np.random.randn(1, n, d).astype(np.float32)\n\n    model = ModelHelper(name='external')\n\n    with scope.NameScope(\"test_name_scope\"):\n        if sequence_lengths:\n            hidden_t_prev, gates_t, seq_lengths, timestep = \\\n                model.net.AddScopedExternalInputs(\n                    \"hidden_t_prev\",\n                    \"gates_t\",\n                    'seq_lengths',\n                    \"timestep\",\n                )\n        else:\n            hidden_t_prev, gates_t, timestep = \\\n                model.net.AddScopedExternalInputs(\n                    \"hidden_t_prev\",\n                    \"gates_t\",\n                    \"timestep\",\n                )\n        workspace.FeedBlob(\n            hidden_t_prev,\n            generate_input_state(n, d).astype(np.float32),\n            device_option=gc\n        )\n        workspace.FeedBlob(\n            gates_t,\n            generate_input_state(n, 3 * d).astype(np.float32),\n            device_option=gc\n        )\n\n        if sequence_lengths:\n            inputs = [hidden_t_prev, gates_t, seq_lengths, timestep]\n        else:\n            inputs = [hidden_t_prev, gates_t, timestep]\n\n        hidden_t = model.net.GRUUnit(\n            inputs,\n            ['hidden_t'],\n            forget_bias=0.0,\n            drop_states=drop_states,\n            sequence_lengths=sequence_lengths,\n        )\n        model.net.AddExternalOutputs(hidden_t)\n        workspace.RunNetOnce(model.param_init_net)\n\n        if sequence_lengths:\n            # 10 is used as a magic number to simulate some reasonable timestep\n            # and generate some reasonable seq. lengths\n            workspace.FeedBlob(\n                seq_lengths,\n                np.random.randint(1, 10, size=(n,)).astype(np.int32),\n                device_option=gc\n            )\n\n        workspace.FeedBlob(\n            timestep,\n            np.random.randint(1, 10, size=(1,)).astype(np.int32),\n            device_option=core.DeviceOption(caffe2_pb2.CPU),\n        )\n        print(\"Feed {}\".format(timestep))\n\n    return hidden_t, model.net\n\n\nclass GRUCellTest(hu.HypothesisTestCase):\n\n    # Test just for GRUUnitOp\n    @given(\n        seed=st.integers(0, 2**32 - 1),\n        input_tensor=gru_unit_op_input(),\n        fwd_only=st.booleans(),\n        drop_states=st.booleans(),\n        sequence_lengths=st.booleans(),\n        **hu.gcs\n    )\n    @ht_settings(max_examples=15)\n    def test_gru_unit_op(self, seed, input_tensor, fwd_only,\n                         drop_states, sequence_lengths, gc, dc):\n        np.random.seed(seed)\n        outputs_with_grads = [0]\n        ref = gru_unit\n        ref = partial(ref)\n\n        t, n, d = input_tensor.shape\n        assert d % 3 == 0\n        d = d // 3\n        ref = partial(ref, drop_states=drop_states,\n                      sequence_lengths=sequence_lengths)\n\n        with core.DeviceScope(gc):\n            net = _prepare_gru_unit_op(gc, n, d,\n                                       outputs_with_grads=outputs_with_grads,\n                                       forward_only=fwd_only,\n                                       drop_states=drop_states,\n                                       sequence_lengths=sequence_lengths)[1]\n        # here we don't provide a real input for the net but just for one of\n        # its ops (RecurrentNetworkOp). So have to hardcode this name\n        workspace.FeedBlob(\"test_name_scope/external/recurrent/i2h\",\n                           input_tensor,\n                           device_option=gc)\n        print(str(net.Proto()))\n        op = net._net.op[-1]\n        inputs = [workspace.FetchBlob(name) for name in op.input]\n\n        self.assertReferenceChecks(\n            gc,\n            op,\n            inputs,\n            ref,\n            input_device_options={op.input[-1]: hu.cpu_do},\n            outputs_to_check=[0],\n        )\n\n        # Checking for hidden_prev and gates gradients\n        if not fwd_only:\n            for param in range(2):\n                print(\"Check param {}\".format(param))\n                self.assertGradientChecks(\n                    device_option=gc,\n                    op=op,\n                    inputs=inputs,\n                    outputs_to_check=param,\n                    outputs_with_grads=outputs_with_grads,\n                    threshold=0.0001,\n                    stepsize=0.005,\n                    input_device_options={op.input[-1]: hu.cpu_do},\n                )\n\n    @given(\n        seed=st.integers(0, 2**32 - 1),\n        input_tensor=gru_input(),\n        fwd_only=st.booleans(),\n        drop_states=st.booleans(),\n        linear_before_reset=st.booleans(),\n        **hu.gcs\n    )\n    @ht_settings(max_examples=20)\n    def test_gru_main(self, seed, **kwargs):\n        np.random.seed(seed)\n        for outputs_with_grads in [[0], [1], [0, 1]]:\n            self.gru_base(gru_cell.GRU, gru_reference,\n                           outputs_with_grads=outputs_with_grads,\n                           **kwargs)\n\n    def gru_base(self, create_rnn, ref, outputs_with_grads,\n                  input_tensor, fwd_only, drop_states, linear_before_reset, gc, dc):\n\n        print(\"GRU test parameters: \", locals())\n        t, n, d = input_tensor.shape\n        assert d % 3 == 0\n        d = d // 3\n        ref = partial(ref,\n                      drop_states=drop_states,\n                      linear_before_reset=linear_before_reset)\n        with core.DeviceScope(gc):\n            net = _prepare_rnn(\n                t, n, d, create_rnn,\n                outputs_with_grads=outputs_with_grads,\n                memory_optim=False,\n                forget_bias=0.0,\n                forward_only=fwd_only,\n                drop_states=drop_states,\n                linear_before_reset=linear_before_reset,\n                num_states=1,\n            )[1]\n        # here we don't provide a real input for the net but just for one of\n        # its ops (RecurrentNetworkOp). So have to hardcode this name\n        workspace.FeedBlob(\"test_name_scope/external/recurrent/i2h\",\n                           input_tensor,\n                           device_option=gc)\n        op = net._net.op[-1]\n        inputs = [workspace.FetchBlob(name) for name in op.input]\n\n        self.assertReferenceChecks(\n            gc,\n            op,\n            inputs,\n            ref,\n            input_device_options={\"timestep\": hu.cpu_do},\n            outputs_to_check=list(range(2)),\n        )\n\n        # Checking for input, gates_t_w and gates_t_b gradients\n        if not fwd_only:\n            for param in range(2):\n                print(\"Check param {}\".format(param))\n                self.assertGradientChecks(\n                    device_option=gc,\n                    op=op,\n                    inputs=inputs,\n                    outputs_to_check=param,\n                    outputs_with_grads=outputs_with_grads,\n                    threshold=0.001,\n                    stepsize=0.005,\n                    input_device_options={\"timestep\": hu.cpu_do},\n                )\n\n\nif __name__ == \"__main__\":\n    workspace.GlobalInit([\n        'caffe2',\n        '--caffe2_log_level=0',\n    ])\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/hsm_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom hypothesis import given\nimport numpy as np\nimport unittest\n\nfrom caffe2.proto import caffe2_pb2, hsm_pb2\nfrom caffe2.python import workspace, core, gradient_checker\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.hsm_util as hsmu\n\n# User inputs tree using protobuf file or, in this case, python utils\n# The hierarchy in this test looks as shown below. Note that the final subtrees\n# (with word_ids as leaves) have been collapsed for visualization\n#           *\n#         /  \\\n#        *    5,6,7,8\n#       / \\\n#  0,1,2   3,4\ntree = hsm_pb2.TreeProto()\nwords = [[0, 1, 2], [3, 4], [5, 6, 7, 8]]\nnode1 = hsmu.create_node_with_words(words[0], \"node1\")\nnode2 = hsmu.create_node_with_words(words[1], \"node2\")\nnode3 = hsmu.create_node_with_words(words[2], \"node3\")\nnode4 = hsmu.create_node_with_nodes([node1, node2], \"node4\")\nnode = hsmu.create_node_with_nodes([node4, node3], \"node5\")\ntree.root_node.MergeFrom(node)\n\n# structure:\n# node5: [0, 2, [\"node4\", \"node3\"]] # offset, length, \"node4, node3\"\n# node4: [2, 2, [\"node1\", \"node2\"]]\n# node1: [4, 3, [0, 1 ,2]]\n# node2: [7, 2, [3, 4]\n# node3: [9, 4, [5, 6, 7, 8]\nstruct = [[0, 2, [\"node4\", \"node3\"], \"node5\"],\n            [2, 2, [\"node1\", \"node2\"], \"node4\"],\n            [4, 3, [0, 1, 2], \"node1\"],\n            [7, 2, [3, 4], \"node2\"],\n            [9, 4, [5, 6, 7, 8], \"node3\"]]\n\n# Internal util to translate input tree to list of (word_id,path). serialized\n# hierarchy is passed into the operator_def as a string argument,\nhierarchy_proto = hsmu.create_hierarchy(tree)\narg = caffe2_pb2.Argument()\narg.name = \"hierarchy\"\narg.s = hierarchy_proto.SerializeToString()\n\nbeam = 5\nargs_search = []\narg_search = caffe2_pb2.Argument()\narg_search.name = \"tree\"\narg_search.s = tree.SerializeToString()\nargs_search.append(arg_search)\narg_search = caffe2_pb2.Argument()\narg_search.name = \"beam\"\narg_search.f = beam\nargs_search.append(arg_search)\n\n\nclass TestHsm(hu.HypothesisTestCase):\n    def test_hsm_search(self):\n        samples = 10\n        dim_in = 5\n        X = np.random.rand(samples, dim_in).astype(np.float32) - 0.5\n        w = np.random.rand(hierarchy_proto.size, dim_in) \\\n            .astype(np.float32) - 0.5\n        b = np.random.rand(hierarchy_proto.size).astype(np.float32) - 0.5\n        labels = np.array([np.random.randint(0, 8) for i in range(samples)]) \\\n            .astype(np.int32)\n\n        workspace.GlobalInit(['caffe2'])\n        workspace.FeedBlob(\"data\", X)\n        workspace.FeedBlob(\"weights\", w)\n        workspace.FeedBlob(\"bias\", b)\n        workspace.FeedBlob(\"labels\", labels)\n        op = core.CreateOperator(\n            'HSoftmaxSearch',\n            ['data', 'weights', 'bias'],\n            ['names', 'scores'],\n            'HSoftmaxSearch',\n            arg=args_search)\n        workspace.RunOperatorOnce(op)\n        names = workspace.FetchBlob('names')\n        scores = workspace.FetchBlob('scores')\n\n        def simulation_hsm_search():\n            names = []\n            scores = []\n            for line in struct:\n                s, e = line[0], line[0] + line[1]\n                score = np.dot(X, w[s:e].transpose()) + b[s:e]\n                score = np.exp(score - np.max(score, axis=1, keepdims=True))\n                score /= score.sum(axis=1, keepdims=True)\n                score = -np.log(score)\n\n                score = score.transpose()\n                idx = -1\n                for j, n in enumerate(names):\n                    if n == line[3]:\n                        idx = j\n                        score += scores[j]\n                if idx == -1:\n                    score[score > beam] = np.inf\n                else:\n                    score[score - scores[idx] > beam] = np.inf\n\n                for i, name in enumerate(line[2]):\n                    scores.append(score[i])\n                    names.append(name)\n            scores = np.vstack(scores)\n            return names, scores.transpose()\n\n        p_names, p_scores = simulation_hsm_search()\n        idx = np.argsort(p_scores, axis=1)\n        p_scores = np.sort(p_scores, axis=1)\n        p_names = np.array(p_names)[idx]\n        for i in range(names.shape[0]):\n            for j in range(names.shape[1]):\n                if names[i][j]:\n                    self.assertEquals(\n                        names[i][j], p_names[i][j].item().encode('utf-8'))\n                    self.assertAlmostEqual(\n                        scores[i][j], p_scores[i][j], delta=0.001)\n\n    def test_hsm_run_once(self):\n        workspace.GlobalInit(['caffe2'])\n        workspace.FeedBlob(\"data\",\n                           np.random.randn(1000, 100).astype(np.float32))\n        workspace.FeedBlob(\"weights\",\n                           np.random.randn(1000, 100).astype(np.float32))\n        workspace.FeedBlob(\"bias\", np.random.randn(1000).astype(np.float32))\n        workspace.FeedBlob(\"labels\", np.random.rand(1000).astype(np.int32) * 9)\n        op = core.CreateOperator(\n            'HSoftmax',\n            ['data', 'weights', 'bias', 'labels'],\n            ['output', 'intermediate_output'],\n            'HSoftmax',\n            arg=[arg])\n        self.assertTrue(workspace.RunOperatorOnce(op))\n\n    # Test to check value of sum of squared losses in forward pass for given\n    # input\n    def test_hsm_forward(self):\n        cpu_device_option = caffe2_pb2.DeviceOption()\n        grad_checker = gradient_checker.GradientChecker(\n            0.01, 0.05, cpu_device_option, \"default\")\n        samples = 9\n        dim_in = 5\n        X = np.zeros((samples, dim_in)).astype(np.float32) + 1\n        w = np.zeros((hierarchy_proto.size, dim_in)).astype(np.float32) + 1\n        b = np.array([i for i in range(hierarchy_proto.size)])\\\n            .astype(np.float32)\n        labels = np.array([i for i in range(samples)]).astype(np.int32)\n\n        workspace.GlobalInit(['caffe2'])\n        workspace.FeedBlob(\"data\", X)\n        workspace.FeedBlob(\"weights\", w)\n        workspace.FeedBlob(\"bias\", b)\n        workspace.FeedBlob(\"labels\", labels)\n\n        op = core.CreateOperator(\n            'HSoftmax',\n            ['data', 'weights', 'bias', 'labels'],\n            ['output', 'intermediate_output'],\n            'HSoftmax',\n            arg=[arg])\n        grad_ops, g_input = core.GradientRegistry.GetGradientForOp(\n            op, [s + '_grad' for s in op.output])\n\n        loss, _ = grad_checker.GetLossAndGrad(\n            op, grad_ops, X, op.input[0], g_input[0], [0]\n        )\n        self.assertAlmostEqual(loss, 44.269, delta=0.001)\n\n    # Test to compare gradient calculated using the gradient operator and the\n    # symmetric derivative calculated using Euler Method\n    # TODO : convert to both cpu and gpu test when ready.\n    @given(**hu.gcs_cpu_only)\n    def test_hsm_gradient(self, gc, dc):\n        samples = 10\n        dim_in = 5\n        X = np.random.rand(samples, dim_in).astype(np.float32) - 0.5\n        w = np.random.rand(hierarchy_proto.size, dim_in) \\\n            .astype(np.float32) - 0.5\n        b = np.random.rand(hierarchy_proto.size).astype(np.float32) - 0.5\n        labels = np.array([np.random.randint(0, 8) for i in range(samples)]) \\\n            .astype(np.int32)\n\n        workspace.GlobalInit(['caffe2'])\n        workspace.FeedBlob(\"data\", X)\n        workspace.FeedBlob(\"weights\", w)\n        workspace.FeedBlob(\"bias\", b)\n        workspace.FeedBlob(\"labels\", labels)\n\n        op = core.CreateOperator(\n            'HSoftmax',\n            ['data', 'weights', 'bias', 'labels'],\n            ['output', 'intermediate_output'],\n            'HSoftmax',\n            arg=[arg])\n\n        self.assertDeviceChecks(dc, op, [X, w, b, labels], [0])\n\n        for i in range(3):\n            self.assertGradientChecks(gc, op, [X, w, b, labels], i, [0])\n\n    def test_huffman_tree_hierarchy(self):\n        workspace.GlobalInit(['caffe2'])\n        labelSet = list(range(0, 6))\n        counts = [1, 2, 3, 4, 5, 6]\n        labels = sum([[l] * c for (l, c) in zip(labelSet, counts)], [])\n        Y = np.array(labels).astype(np.int64)\n        workspace.FeedBlob(\"labels\", Y)\n        arg = caffe2_pb2.Argument()\n        arg.name = 'num_classes'\n        arg.i = 6\n        op = core.CreateOperator(\n            'HuffmanTreeHierarchy',\n            ['labels'],\n            ['huffman_tree'],\n            'HuffmanTreeHierarchy',\n            arg=[arg])\n        workspace.RunOperatorOnce(op)\n        huffmanTreeOutput = workspace.FetchBlob('huffman_tree')\n        treeOutput = hsm_pb2.TreeProto()\n        treeOutput.ParseFromString(huffmanTreeOutput[0])\n        treePathOutput = hsmu.create_hierarchy(treeOutput)\n\n        label_to_path = {}\n        for path in treePathOutput.paths:\n            label_to_path[path.word_id] = path\n\n        def checkPath(label, indices, code):\n            path = label_to_path[label]\n            self.assertEqual(len(path.path_nodes), len(code))\n            self.assertEqual(len(path.path_nodes), len(code))\n            for path_node, index, target in \\\n                    zip(path.path_nodes, indices, code):\n                self.assertEqual(path_node.index, index)\n                self.assertEqual(path_node.target, target)\n        checkPath(0, [0, 4, 6, 8], [1, 0, 0, 0])\n        checkPath(1, [0, 4, 6, 8], [1, 0, 0, 1])\n        checkPath(2, [0, 4, 6], [1, 0, 1])\n        checkPath(3, [0, 2], [0, 0])\n        checkPath(4, [0, 2], [0, 1])\n        checkPath(5, [0, 4], [1, 1])\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/im2col_col2im_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import assume, given\n\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestReduceFrontSum(hu.HypothesisTestCase):\n    @given(batch_size=st.integers(1, 3),\n           stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           dilation=st.integers(1, 3),\n           size=st.integers(7, 10),\n           channels=st.integers(1, 8),\n           **hu.gcs)\n    def test_im2col_layout(self, batch_size, stride, pad, kernel, dilation,\n                           size, channels, gc, dc):\n\n        dkernel = (dilation * (kernel - 1) + 1)\n        assume(size >= dkernel)\n\n        NCHW_TO_NHWC = (0, 2, 3, 1)\n        NHWC_TO_NCHW = (0, 3, 1, 2)\n        COL_NHWC_TO_NCHW = (4, 2, 3, 0, 1)\n\n        N = batch_size\n        C = channels\n        H = size\n        W = size\n\n        out_h = int((H + (2 * pad) - dkernel) / stride + 1)\n        out_w = int((W + (2 * pad) - dkernel) / stride + 1)\n\n        im_nchw = np.random.rand(N, C, H, W).astype(np.float32) - 0.5\n        im_nhwc = im_nchw.transpose(NCHW_TO_NHWC)\n\n        op_im2col_nchw = core.CreateOperator(\n            \"Im2Col\",\n            [\"im_nchw\"], [\"col_nchw\"],\n            stride=stride,\n            kernel=kernel,\n            dilation=dilation,\n            pad=pad,\n            order=\"NCHW\",\n            device_option=gc)\n\n        op_im2col_nhwc = core.CreateOperator(\n            \"Im2Col\",\n            [\"im_nhwc\"], [\"col_nhwc\"],\n            stride=stride,\n            kernel=kernel,\n            dilation=dilation,\n            pad=pad,\n            order=\"NHWC\",\n            device_option=gc)\n\n        self.ws.create_blob(\"im_nchw\").feed(im_nchw, device_option=gc)\n        self.ws.create_blob(\"im_nhwc\").feed(im_nhwc, device_option=gc)\n        self.ws.run(op_im2col_nchw)\n        self.ws.run(op_im2col_nhwc)\n\n        # there is probably a clever way to spell this in np\n        col_nchw = self.ws.blobs[\"col_nchw\"].fetch()\n        col_nhwc = self.ws.blobs[\"col_nhwc\"].fetch()\n        col_nchw_ = col_nchw.reshape(N, C, kernel, kernel, out_h, out_w)\n        col_nhwc_ = col_nhwc.reshape(N, out_h, out_w, kernel, kernel, C)\n        for i in range(0, N):\n            np.testing.assert_allclose(\n                col_nchw_[i],\n                col_nhwc_[i].transpose(COL_NHWC_TO_NCHW),\n                atol=1e-4,\n                rtol=1e-4)\n\n        op_col2im_nchw = core.CreateOperator(\n            \"Col2Im\",\n            [\"col_nchw\", \"im_nchw\"],\n            [\"out_nchw\"],\n            stride=stride,\n            kernel=kernel,\n            dilation=dilation,\n            pad=pad,\n            order=\"NCHW\",\n            device_option=gc)\n\n        op_col2im_nhwc = core.CreateOperator(\n            \"Col2Im\",\n            [\"col_nhwc\", \"im_nhwc\"],\n            [\"out_nhwc\"],\n            stride=stride,\n            kernel=kernel,\n            dilation=dilation,\n            pad=pad,\n            order=\"NHWC\",\n            device_option=gc)\n\n        self.ws.run(op_col2im_nchw)\n        self.ws.run(op_col2im_nhwc)\n\n        out_nchw = self.ws.blobs[\"out_nchw\"].fetch()\n        out_nhwc = self.ws.blobs[\"out_nhwc\"].fetch()\n        np.testing.assert_allclose(\n            out_nchw,\n            out_nhwc.transpose(NHWC_TO_NCHW),\n            atol=1e-4,\n            rtol=1e-4)\n\n    @given(batch_size=st.integers(1, 3),\n           stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           dilation=st.integers(1, 3),\n           size=st.integers(7, 10),\n           channels=st.integers(1, 8),\n           order=st.sampled_from([\"NCHW\"]),\n           **hu.gcs)\n    def test_col2im_gradients(self, batch_size, stride, pad, kernel,\n                              dilation, size, channels, order, gc, dc):\n        assume(size >= dilation * (kernel - 1) + 1)\n        op = core.CreateOperator(\n            \"Im2Col\",\n            [\"X\"], [\"Y\"],\n            stride=stride,\n            kernel=kernel,\n            dilation=dilation,\n            pad=pad,\n            order=order,\n            device_option=gc)\n        X = np.random.rand(batch_size, channels, size, size).astype(np.float32)\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n        return\n"
  },
  {
    "path": "caffe2/python/operator_test/image_input_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\ntry:\n    import cv2\n    import lmdb\nexcept ImportError:\n    pass  # Handled below\n\nfrom PIL import Image\nimport numpy as np\nimport shutil\nimport six\nimport sys\nimport tempfile\n\n# TODO: This test does not test scaling because\n# the algorithms used by OpenCV in the C and Python\n# version seem to differ slightly. It does test\n# most other features\n\nfrom hypothesis import given, settings, Verbosity\nimport hypothesis.strategies as st\n\nfrom caffe2.proto import caffe2_pb2\nimport caffe2.python.hypothesis_test_util as hu\n\nfrom caffe2.python import workspace, core\n\n\n# Verification routines (applies transformations to image to\n# verify if the operator produces same result)\ndef verify_apply_bounding_box(img, box):\n    import skimage.util\n    if any(type(box[f]) is not int or np.isnan(box[f] or box[f] < 0)\n           for f in range(0, 4)):\n        return img\n    # Box is ymin, xmin, bound_height, bound_width\n    y_bounds = (box[0], img.shape[0] - box[0] - box[2])\n    x_bounds = (box[1], img.shape[1] - box[1] - box[3])\n    c_bounds = (0, 0)\n\n    if any(el < 0 for el in list(y_bounds) + list(x_bounds) + list(c_bounds)):\n        return img\n\n    bboxed = skimage.util.crop(img, (y_bounds, x_bounds, c_bounds))\n    return bboxed\n\n\n# This function is called but not used. It will trip on assert False if\n# the arguments are wrong (improper example)\ndef verify_rescale(img, minsize):\n    # Here we use OpenCV transformation to match the C code\n    scale_amount = float(minsize) / min(img.shape[0], img.shape[1])\n    if scale_amount <= 1.0:\n        return img\n\n    print(\"Scale amount is %f -- should be < 1.0; got shape %s\" %\n          (scale_amount, str(img.shape)))\n    assert False\n    img_cv = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)\n    output_shape = (int(np.ceil(scale_amount * img_cv.shape[0])),\n                    int(np.ceil(scale_amount * img_cv.shape[1])))\n    resized = cv2.resize(img_cv,\n                         dsize=output_shape,\n                         interpolation=cv2.INTER_AREA)\n\n    resized = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB)\n    assert resized.shape[0] >= minsize\n    assert resized.shape[1] >= minsize\n    return resized\n\n\ndef verify_crop(img, crop):\n    import skimage.util\n    assert img.shape[0] >= crop\n    assert img.shape[1] >= crop\n    y_offset = 0\n    if img.shape[0] > crop:\n        y_offset = (img.shape[0] - crop) // 2\n\n    x_offset = 0\n    if img.shape[1] > crop:\n        x_offset = (img.shape[1] - crop) // 2\n\n    y_bounds = (y_offset, img.shape[0] - crop - y_offset)\n    x_bounds = (x_offset, img.shape[1] - crop - x_offset)\n    c_bounds = (0, 0)\n    cropped = skimage.util.crop(img, (y_bounds, x_bounds, c_bounds))\n    assert cropped.shape[0] == crop\n    assert cropped.shape[1] == crop\n    return cropped\n\n\ndef verify_color_normalize(img, means, stds):\n    # Note the RGB/BGR inversion\n    # Operate on integers like the C version\n    img = img * 255.0\n    img[:, :, 0] = (img[:, :, 0] - means[2]) / stds[2]\n    img[:, :, 1] = (img[:, :, 1] - means[1]) / stds[1]\n    img[:, :, 2] = (img[:, :, 2] - means[0]) / stds[0]\n    return img * (1.0 / 255.0)\n\n\n# Printing function (for debugging)\ndef caffe2_img(img):\n    # Convert RGB to BGR\n    img = img[:, :, (2, 1, 0)]\n    # Convert HWC to CHW\n    img = img.swapaxes(1, 2).swapaxes(0, 1)\n    img = img * 255.0\n    return img.astype(np.int32)\n\n\n# Bounding box is ymin, xmin, height, width\ndef create_test(output_dir, width, height, default_bound, minsize, crop, means,\n                stds, count, label_type, num_labels, output1=None,\n                output2_size=None):\n    print(\"Creating a temporary lmdb database of %d pictures...\" % (count))\n\n    if default_bound is None:\n        default_bound = [-1] * 4\n\n    LMDB_MAP_SIZE = 1 << 40\n    env = lmdb.open(output_dir, map_size=LMDB_MAP_SIZE, subdir=True)\n    index = 0\n    # Create images and the expected results\n    expected_results = []\n    with env.begin(write=True) as txn:\n        while index < count:\n            img_array = np.random.random_integers(\n                0, 255, [height, width, 3]).astype(np.uint8)\n            img_obj = Image.fromarray(img_array)\n            img_str = six.BytesIO()\n            img_obj.save(img_str, 'PNG')\n\n            # Create a random bounding box for every other image\n            # ymin, xmin, bound_height, bound_width\n            # TODO: To ensure that we never need to scale, we\n            # ensure that the bounding-box is larger than the\n            # minsize parameter\n            bounding_box = list(default_bound)\n            do_default_bound = True\n            if index % 2 == 0:\n                if height > minsize and width > minsize:\n                    do_default_bound = False\n                    bounding_box[0:2] = [np.random.randint(a) for a in\n                                         (height - minsize, width - minsize)]\n                    bounding_box[2:4] = [np.random.randint(a) + minsize for a in\n                                         (height - bounding_box[0] - minsize + 1,\n                                          width - bounding_box[1] - minsize + 1)]\n                    # print(\"Bounding box is %s\" % (str(bounding_box)))\n            # Create expected result\n            img_expected = img_array.astype(np.float32) * (1.0 / 255.0)\n            # print(\"Orig image: %s\" % (str(caffe2_img(img_expected))))\n            img_expected = verify_apply_bounding_box(\n                img_expected,\n                bounding_box)\n            # print(\"Bounded image: %s\" % (str(caffe2_img(img_expected))))\n\n            img_expected = verify_rescale(img_expected, minsize)\n\n            img_expected = verify_crop(img_expected, crop)\n            # print(\"Crop image: %s\" % (str(caffe2_img(img_expected))))\n\n            img_expected = verify_color_normalize(img_expected, means, stds)\n            # print(\"Color image: %s\" % (str(caffe2_img(img_expected))))\n\n            tensor_protos = caffe2_pb2.TensorProtos()\n            image_tensor = tensor_protos.protos.add()\n            image_tensor.data_type = 4  # string data\n            image_tensor.string_data.append(img_str.getvalue())\n            img_str.close()\n\n            label_tensor = tensor_protos.protos.add()\n            label_tensor.data_type = 2  # int32 data\n            assert (label_type >= 0 and label_type <= 3)\n            if label_type == 0:\n                label_tensor.int32_data.append(index)\n                expected_label = index\n            elif label_type == 1:\n                binary_labels = np.random.randint(2, size=num_labels)\n                for idx, val in enumerate(binary_labels.tolist()):\n                    if val == 1:\n                        label_tensor.int32_data.append(idx)\n                expected_label = binary_labels\n            elif label_type == 2:\n                embedding_label = np.random.randint(100, size=num_labels)\n                for _idx, val in enumerate(embedding_label.tolist()):\n                    label_tensor.int32_data.append(val)\n                expected_label = embedding_label\n            elif label_type == 3:\n                weight_tensor = tensor_protos.protos.add()\n                weight_tensor.data_type = 1  # float weights\n                binary_labels = np.random.randint(2, size=num_labels)\n                expected_label = np.zeros(num_labels).astype(np.float32)\n                for idx, val in enumerate(binary_labels.tolist()):\n                    expected_label[idx] = val * idx\n                    if val == 1:\n                        label_tensor.int32_data.append(idx)\n                        weight_tensor.float_data.append(idx)\n\n            if output1:\n                output1_tensor = tensor_protos.protos.add()\n                output1_tensor.data_type = 1  # float data\n                output1_tensor.float_data.append(output1)\n\n            output2 = []\n            if output2_size:\n                output2_tensor = tensor_protos.protos.add()\n                output2_tensor.data_type = 2  # int32 data\n                values = np.random.randint(1024, size=output2_size)\n                for val in values.tolist():\n                    output2.append(val)\n                    output2_tensor.int32_data.append(val)\n\n            expected_results.append(\n                [caffe2_img(img_expected), expected_label, output1, output2])\n\n            if not do_default_bound:\n                bounding_tensor = tensor_protos.protos.add()\n                bounding_tensor.data_type = 2  # int32 data\n                bounding_tensor.int32_data.extend(bounding_box)\n\n            txn.put(\n                '{}'.format(index).encode('ascii'),\n                tensor_protos.SerializeToString()\n            )\n            index = index + 1\n        # End while\n    # End with\n    return expected_results\n\n\ndef run_test(\n        size_tuple, means, stds, label_type, num_labels, is_test, scale_jitter_type,\n        color_jitter, color_lighting, dc, validator, output1=None, output2_size=None):\n    # TODO: Does not test on GPU and does not test use_gpu_transform\n    # WARNING: Using ModelHelper automatically does NHWC to NCHW\n    # transformation if needed.\n    width, height, minsize, crop = size_tuple\n    means = [float(m) for m in means]\n    stds = [float(s) for s in stds]\n    out_dir = tempfile.mkdtemp()\n    count_images = 2  # One with bounding box and one without\n    expected_images = create_test(\n        out_dir,\n        width=width,\n        height=height,\n        default_bound=(3, 5, height - 3, width - 5),\n        minsize=minsize,\n        crop=crop,\n        means=means,\n        stds=stds,\n        count=count_images,\n        label_type=label_type,\n        num_labels=num_labels,\n        output1=output1,\n        output2_size=output2_size\n    )\n    for device_option in dc:\n        with hu.temp_workspace():\n            reader_net = core.Net('reader')\n            reader_net.CreateDB(\n                [],\n                'DB',\n                db=out_dir,\n                db_type=\"lmdb\"\n            )\n            workspace.RunNetOnce(reader_net)\n            outputs = ['data', 'label']\n            output_sizes = []\n            if output1:\n                outputs.append('output1')\n                output_sizes.append(1)\n            if output2_size:\n                outputs.append('output2')\n                output_sizes.append(output2_size)\n            imageop = core.CreateOperator(\n                'ImageInput',\n                ['DB'],\n                outputs,\n                batch_size=count_images,\n                color=3,\n                minsize=minsize,\n                crop=crop,\n                is_test=is_test,\n                bounding_ymin=3,\n                bounding_xmin=5,\n                bounding_height=height - 3,\n                bounding_width=width - 5,\n                mean_per_channel=means,\n                std_per_channel=stds,\n                use_gpu_transform=(device_option.device_type == 1),\n                label_type=label_type,\n                num_labels=num_labels,\n                output_sizes=output_sizes,\n                scale_jitter_type=scale_jitter_type,\n                color_jitter=color_jitter,\n                color_lighting=color_lighting\n            )\n\n            imageop.device_option.CopyFrom(device_option)\n            main_net = core.Net('main')\n            main_net.Proto().op.extend([imageop])\n            workspace.RunNetOnce(main_net)\n            validator(expected_images, device_option, count_images)\n            # End for\n        # End with\n    # End for\n    shutil.rmtree(out_dir)\n# end run_test\n\n\n@unittest.skipIf('cv2' not in sys.modules, 'python-opencv is not installed')\n@unittest.skipIf('lmdb' not in sys.modules, 'python-lmdb is not installed')\nclass TestImport(hu.HypothesisTestCase):\n    def validate_image_and_label(\n            self, expected_images, device_option, count_images, label_type,\n            is_test, scale_jitter_type, color_jitter, color_lighting):\n        l = workspace.FetchBlob('label')\n        result = workspace.FetchBlob('data').astype(np.int32)\n        # If we don't use_gpu_transform, the output is in NHWC\n        # Our reference output is CHW so we swap\n        if device_option.device_type != 1:\n            expected = [img.swapaxes(0, 1).swapaxes(1, 2) for\n                        (img, _, _, _) in expected_images]\n        else:\n            expected = [img for (img, _, _, _) in expected_images]\n        for i in range(count_images):\n            if label_type == 0:\n                self.assertEqual(l[i], expected_images[i][1])\n            else:\n                self.assertEqual(\n                    (l[i] - expected_images[i][1] > 0).sum(), 0)\n            if is_test == 0:\n                # when traing data preparation is randomized (e.g. random cropping,\n                # Inception-style random sized cropping, color jittering,\n                # color lightin), we only compare blob shape\n                for (s1, s2) in zip(expected[i].shape, result[i].shape):\n                    self.assertEqual(s1, s2)\n            else:\n                self.assertEqual((expected[i] - result[i] > 1).sum(), 0)\n        # End for\n    # end validate_image_and_label\n\n    @given(size_tuple=st.tuples(\n        st.integers(min_value=8, max_value=4096),\n        st.integers(min_value=8, max_value=4096)).flatmap(lambda t: st.tuples(\n            st.just(t[0]), st.just(t[1]),\n            st.just(min(t[0] - 6, t[1] - 4)),\n            st.integers(min_value=1, max_value=min(t[0] - 6, t[1] - 4)))),\n        means=st.tuples(st.integers(min_value=0, max_value=255),\n                        st.integers(min_value=0, max_value=255),\n                        st.integers(min_value=0, max_value=255)),\n        stds=st.tuples(st.floats(min_value=1, max_value=10),\n                       st.floats(min_value=1, max_value=10),\n                       st.floats(min_value=1, max_value=10)),\n        label_type=st.integers(0, 3),\n        num_labels=st.integers(min_value=8, max_value=4096),\n        is_test=st.integers(min_value=0, max_value=1),\n        scale_jitter_type=st.integers(min_value=0, max_value=1),\n        color_jitter=st.integers(min_value=0, max_value=1),\n        color_lighting=st.integers(min_value=0, max_value=1),\n        **hu.gcs)\n    @settings(verbosity=Verbosity.verbose)\n    def test_imageinput(\n            self, size_tuple, means, stds, label_type,\n            num_labels, is_test, scale_jitter_type, color_jitter, color_lighting,\n            gc, dc):\n        def validator(expected_images, device_option, count_images):\n            self.validate_image_and_label(\n                expected_images, device_option, count_images, label_type,\n                is_test, scale_jitter_type, color_jitter, color_lighting)\n        # End validator\n        run_test(\n            size_tuple, means, stds, label_type, num_labels, is_test,\n            scale_jitter_type, color_jitter, color_lighting, dc, validator)\n    # End test_imageinput\n\n    @given(size_tuple=st.tuples(\n        st.integers(min_value=8, max_value=4096),\n        st.integers(min_value=8, max_value=4096)).flatmap(lambda t: st.tuples(\n            st.just(t[0]), st.just(t[1]),\n            st.just(min(t[0] - 6, t[1] - 4)),\n            st.integers(min_value=1, max_value=min(t[0] - 6, t[1] - 4)))),\n        means=st.tuples(st.integers(min_value=0, max_value=255),\n                        st.integers(min_value=0, max_value=255),\n                        st.integers(min_value=0, max_value=255)),\n        stds=st.tuples(st.floats(min_value=1, max_value=10),\n                       st.floats(min_value=1, max_value=10),\n                       st.floats(min_value=1, max_value=10)),\n        label_type=st.integers(0, 3),\n        num_labels=st.integers(min_value=8, max_value=4096),\n        is_test=st.integers(min_value=0, max_value=1),\n        scale_jitter_type=st.integers(min_value=0, max_value=1),\n        color_jitter=st.integers(min_value=0, max_value=1),\n        color_lighting=st.integers(min_value=0, max_value=1),\n        output1=st.floats(min_value=1, max_value=10),\n        output2_size=st.integers(min_value=2, max_value=10),\n        **hu.gcs)\n    @settings(verbosity=Verbosity.verbose)\n    def test_imageinput_with_additional_outputs(\n            self, size_tuple, means, stds, label_type,\n            num_labels, is_test, scale_jitter_type, color_jitter, color_lighting,\n            output1, output2_size, gc, dc):\n        def validator(expected_images, device_option, count_images):\n            self.validate_image_and_label(\n                expected_images, device_option, count_images, label_type,\n                is_test, scale_jitter_type, color_jitter, color_lighting)\n\n            output1_result = workspace.FetchBlob('output1')\n            output2_result = workspace.FetchBlob('output2')\n\n            for i in range(count_images):\n                self.assertEqual(output1_result[i], expected_images[i][2])\n                self.assertEqual(\n                    (output2_result[i] - expected_images[i][3] > 0).sum(), 0)\n            # End for\n        # End validator\n        run_test(\n            size_tuple, means, stds, label_type, num_labels, is_test,\n            scale_jitter_type, color_jitter, color_lighting, dc,\n            validator, output1, output2_size)\n    # End test_imageinput\n\n\nif __name__ == '__main__':\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/index_hash_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestIndexHashOps(hu.HypothesisTestCase):\n    @given(\n        indices=st.sampled_from([\n            np.int32, np.int64\n        ]).flatmap(lambda dtype: hu.tensor(min_dim=1, max_dim=1, dtype=dtype)),\n        seed=st.integers(min_value=0, max_value=10),\n        modulo=st.integers(min_value=100000, max_value=200000),\n        **hu.gcs_cpu_only\n    )\n    def test_index_hash_ops(self, indices, seed, modulo, gc, dc):\n        op = core.CreateOperator(\"IndexHash\",\n                                 [\"indices\"], [\"hashed_indices\"],\n                                 seed=seed, modulo=modulo)\n\n        def index_hash(indices):\n            dtype = np.array(indices).dtype\n            assert dtype == np.int32 or dtype == np.int64\n            hashed_indices = []\n            for index in indices:\n                hashed = dtype.type(0xDEADBEEF * seed)\n                indices_bytes = np.array([index], dtype).view(np.int8)\n                for b in indices_bytes:\n                    hashed = dtype.type(hashed * 65537 + b)\n                hashed = (modulo + hashed % modulo) % modulo\n                hashed_indices.append(hashed)\n            return [hashed_indices]\n\n        self.assertDeviceChecks(dc, op, [indices], [0])\n        self.assertReferenceChecks(gc, op, [indices], index_hash)\n"
  },
  {
    "path": "caffe2/python/operator_test/index_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\nimport numpy as np\nimport tempfile\n\n\nclass TestIndexOps(TestCase):\n    def _test_index_ops(self, entries, dtype, index_create_op):\n        workspace.RunOperatorOnce(core.CreateOperator(\n            index_create_op,\n            [],\n            ['index'],\n            max_elements=10))\n        my_entries = np.array(\n            [entries[0], entries[1], entries[2]], dtype=dtype)\n\n        workspace.FeedBlob('entries', my_entries)\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'IndexLoad',\n            ['index', 'entries'],\n            ['index']))\n        query1 = np.array(\n            [entries[0], entries[3], entries[0], entries[4]],\n            dtype=dtype)\n\n        workspace.FeedBlob('query1', query1)\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'IndexGet',\n            ['index', 'query1'],\n            ['result1']))\n        result1 = workspace.FetchBlob('result1')\n        np.testing.assert_array_equal([1, 4, 1, 5], result1)\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'IndexFreeze',\n            ['index'],\n            ['index']))\n\n        query2 = np.array(\n            [entries[5], entries[4], entries[0], entries[6], entries[7]],\n            dtype=dtype)\n        workspace.FeedBlob('query2', query2)\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'IndexGet',\n            ['index', 'query2'],\n            ['result2']))\n        result2 = workspace.FetchBlob('result2')\n        np.testing.assert_array_equal([0, 5, 1, 0, 0], result2)\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'IndexSize',\n            ['index'],\n            ['index_size']))\n        size = workspace.FetchBlob('index_size')\n        self.assertEquals(size, 6)\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'IndexStore',\n            ['index'],\n            ['stored_entries']))\n        stored_actual = workspace.FetchBlob('stored_entries')\n        new_entries = np.array([entries[3], entries[4]], dtype=dtype)\n        expected = np.concatenate((my_entries, new_entries))\n        if dtype is str:\n            # we'll always get bytes back from Caffe2\n            expected = np.array([\n                x.item().encode('utf-8') if isinstance(x, np.str_) else x\n                for x in expected\n            ], dtype=object)\n        np.testing.assert_array_equal(expected, stored_actual)\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            index_create_op,\n            [],\n            ['index2']))\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'IndexLoad',\n            ['index2', 'stored_entries'],\n            ['index2'],\n            skip_first_entry=1))\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'IndexSize',\n            ['index2'],\n            ['index2_size']))\n        index2_size = workspace.FetchBlob('index2_size')\n        self.assertEquals(index2_size, 5)\n\n        # test serde\n        with tempfile.NamedTemporaryFile() as tmp:\n            workspace.RunOperatorOnce(core.CreateOperator(\n                'Save',\n                ['index'],\n                [],\n                absolute_path=1,\n                db_type='minidb',\n                db=tmp.name))\n            # frees up the blob\n            workspace.FeedBlob('index', np.array([]))\n            # reloads the index\n            workspace.RunOperatorOnce(core.CreateOperator(\n                'Load',\n                [],\n                ['index'],\n                absolute_path=1,\n                db_type='minidb',\n                db=tmp.name))\n            query3 = np.array(\n                [entries[0], entries[3], entries[0], entries[4], entries[4]],\n                dtype=dtype)\n\n            workspace.FeedBlob('query3', query3)\n            workspace.RunOperatorOnce(core.CreateOperator(\n                'IndexGet', ['index', 'query3'], ['result3']))\n            result3 = workspace.FetchBlob('result3')\n            np.testing.assert_array_equal([1, 4, 1, 5, 5], result3)\n\n    def test_string_index_ops(self):\n        self._test_index_ops([\n            'entry1', 'entry2', 'entry3', 'new_entry1',\n            'new_entry2', 'miss1', 'miss2', 'miss3',\n        ], str, 'StringIndexCreate')\n\n    def test_int_index_ops(self):\n        self._test_index_ops(list(range(8)), np.int32, 'IntIndexCreate')\n\n    def test_long_index_ops(self):\n        self._test_index_ops(list(range(8)), np.int64, 'LongIndexCreate')\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/instance_norm_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nfrom hypothesis import given, assume\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core, model_helper, brew\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestInstanceNorm(hu.HypothesisTestCase):\n\n    def _get_inputs(self, N, C, H, W, order):\n        if order == 'NCHW':\n            input_data = np.random.rand(N, C, H, W).astype(np.float32)\n        elif order == 'NHWC':\n            # Allocate in the same order as NCHW and transpose to make sure\n            # the inputs are identical on freshly-seeded calls.\n            input_data = np.random.rand(N, C, H, W).astype(np.float32)\n            input_data = np.transpose(input_data, axes=(0, 2, 3, 1))\n        else:\n            raise Exception('unknown order type ({})'.format(order))\n\n        scale_data = np.random.rand(C).astype(np.float32)\n        bias_data = np.random.rand(C).astype(np.float32)\n        return input_data, scale_data, bias_data\n\n    def _get_op(self, device_option, store_mean, store_inv_stdev, epsilon,\n                order, inplace=False):\n        outputs = ['output' if not inplace else \"input\"]\n        if store_mean or store_inv_stdev:\n            outputs += ['mean']\n        if store_inv_stdev:\n            outputs += ['inv_stdev']\n        op = core.CreateOperator(\n            'InstanceNorm',\n            ['input', 'scale', 'bias'],\n            outputs,\n            order=order,\n            epsilon=epsilon,\n            device_option=device_option)\n        return op\n\n    def _feed_inputs(self, input_blobs, device_option):\n        names = ['input', 'scale', 'bias']\n        for name, blob in zip(names, input_blobs):\n            self.ws.create_blob(name).feed(blob, device_option=device_option)\n\n    @given(gc=hu.gcs['gc'],\n           dc=hu.gcs['dc'],\n           N=st.integers(2, 3),\n           C=st.integers(2, 3),\n           H=st.integers(2, 3),\n           W=st.integers(2, 3),\n           order=st.sampled_from(['NCHW', 'NHWC']),\n           epsilon=st.floats(1e-6, 1e-4),\n           store_mean=st.booleans(),\n           seed=st.integers(0, 1000),\n           store_inv_stdev=st.booleans())\n    def test_instance_norm_gradients(\n            self, gc, dc, N, C, H, W, order, store_mean, store_inv_stdev,\n            epsilon, seed):\n        np.random.seed(seed)\n\n        # force store_inv_stdev if store_mean to match existing forward pass\n        # implementation\n        store_inv_stdev |= store_mean\n\n        op = self._get_op(\n            device_option=gc,\n            store_mean=store_mean,\n            store_inv_stdev=store_inv_stdev,\n            epsilon=epsilon,\n            order=order)\n        input_blobs = self._get_inputs(N, C, H, W, order)\n\n        output_indices = [0]\n        # if store_inv_stdev is turned on, store_mean must also be forced on\n        if store_mean or store_inv_stdev:\n            output_indices += [1]\n        if store_inv_stdev:\n            output_indices += [2]\n        self.assertDeviceChecks(dc, op, input_blobs, output_indices)\n        # The gradient only flows from output #0 since the other two only\n        # store the temporary mean and inv_stdev buffers.\n        # Check dl/dinput\n        self.assertGradientChecks(gc, op, input_blobs, 0, [0], stepsize=0.005,\n                                  threshold=0.01)\n        # Check dl/dscale\n        self.assertGradientChecks(gc, op, input_blobs, 1, [0])\n        # Check dl/dbias\n        self.assertGradientChecks(gc, op, input_blobs, 2, [0])\n\n    @given(gc=hu.gcs['gc'],\n           dc=hu.gcs['dc'],\n           N=st.integers(2, 10),\n           C=st.integers(3, 10),\n           H=st.integers(5, 10),\n           W=st.integers(7, 10),\n           seed=st.integers(0, 1000),\n           epsilon=st.floats(1e-6, 1e-4),\n           store_mean=st.booleans(),\n           store_inv_stdev=st.booleans())\n    def test_instance_norm_layout(self, gc, dc, N, C, H, W, store_mean,\n                                  store_inv_stdev, epsilon, seed):\n        # force store_inv_stdev if store_mean to match existing forward pass\n        # implementation\n        store_inv_stdev |= store_mean\n\n        outputs = {}\n        for order in ('NCHW', 'NHWC'):\n            np.random.seed(seed)\n            input_blobs = self._get_inputs(N, C, H, W, order)\n            self._feed_inputs(input_blobs, device_option=gc)\n            op = self._get_op(\n                device_option=gc,\n                store_mean=store_mean,\n                store_inv_stdev=store_inv_stdev,\n                epsilon=epsilon,\n                order=order)\n            self.ws.run(op)\n            outputs[order] = self.ws.blobs['output'].fetch()\n        np.testing.assert_allclose(\n            outputs['NCHW'],\n            outputs['NHWC'].transpose((0, 3, 1, 2)),\n            atol=1e-4,\n            rtol=1e-4)\n\n    @given(gc=hu.gcs['gc'],\n           dc=hu.gcs['dc'],\n           N=st.integers(2, 10),\n           C=st.integers(3, 10),\n           H=st.integers(5, 10),\n           W=st.integers(7, 10),\n           order=st.sampled_from(['NCHW', 'NHWC']),\n           epsilon=st.floats(1e-6, 1e-4),\n           store_mean=st.booleans(),\n           seed=st.integers(0, 1000),\n           store_inv_stdev=st.booleans(),\n           inplace=st.booleans())\n    def test_instance_norm_reference_check(\n            self, gc, dc, N, C, H, W, order, store_mean, store_inv_stdev,\n            epsilon, seed, inplace):\n        np.random.seed(seed)\n\n        # force store_inv_stdev if store_mean to match existing forward pass\n        # implementation\n        store_inv_stdev |= store_mean\n        if order != \"NCHW\":\n            assume(not inplace)\n\n        inputs = self._get_inputs(N, C, H, W, order)\n        op = self._get_op(\n            device_option=gc,\n            store_mean=store_mean,\n            store_inv_stdev=store_inv_stdev,\n            epsilon=epsilon,\n            order=order,\n            inplace=inplace)\n\n        def ref(input_blob, scale_blob, bias_blob):\n            if order == 'NHWC':\n                input_blob = np.transpose(input_blob, axes=(0, 3, 1, 2))\n\n            mean_blob = input_blob.reshape((N, C, -1)).mean(axis=2)\n            inv_stdev_blob = 1.0 / \\\n                np.sqrt(input_blob.reshape((N, C, -1)).var(axis=2) + epsilon)\n            # _bc indicates blobs that are reshaped for broadcast\n            scale_bc = scale_blob[np.newaxis, :, np.newaxis, np.newaxis]\n            mean_bc = mean_blob[:, :, np.newaxis, np.newaxis]\n            inv_stdev_bc = inv_stdev_blob[:, :, np.newaxis, np.newaxis]\n            bias_bc = bias_blob[np.newaxis, :, np.newaxis, np.newaxis]\n            normalized_blob = scale_bc * (input_blob - mean_bc) * inv_stdev_bc \\\n                + bias_bc\n\n            if order == 'NHWC':\n                normalized_blob = np.transpose(\n                    normalized_blob, axes=(0, 2, 3, 1))\n\n            if not store_mean and not store_inv_stdev:\n                return normalized_blob,\n            elif not store_inv_stdev:\n                return normalized_blob, mean_blob\n            else:\n                return normalized_blob, mean_blob, inv_stdev_blob\n\n        self.assertReferenceChecks(gc, op, inputs, ref)\n\n    @given(gc=hu.gcs['gc'],\n           dc=hu.gcs['dc'],\n           N=st.integers(2, 10),\n           C=st.integers(3, 10),\n           H=st.integers(5, 10),\n           W=st.integers(7, 10),\n           order=st.sampled_from(['NCHW', 'NHWC']),\n           epsilon=st.floats(1e-6, 1e-4),\n           store_mean=st.booleans(),\n           seed=st.integers(0, 1000),\n           store_inv_stdev=st.booleans())\n    def test_instance_norm_device_check(\n            self, gc, dc, N, C, H, W, order, store_mean, store_inv_stdev,\n            epsilon, seed):\n        np.random.seed(seed)\n\n        # force store_inv_stdev if store_mean to match existing forward pass\n        # implementation\n        store_inv_stdev |= store_mean\n\n        inputs = self._get_inputs(N, C, H, W, order)\n        op = self._get_op(\n            device_option=gc,\n            store_mean=store_mean,\n            store_inv_stdev=store_inv_stdev,\n            epsilon=epsilon,\n            order=order)\n\n        self.assertDeviceChecks(dc, op, inputs, [0])\n\n    @given(is_test=st.booleans(),\n           N=st.integers(2, 10),\n           C=st.integers(3, 10),\n           H=st.integers(5, 10),\n           W=st.integers(7, 10),\n           order=st.sampled_from(['NCHW', 'NHWC']),\n           epsilon=st.floats(1e-6, 1e-4),\n           seed=st.integers(0, 1000))\n    def test_instance_norm_model_helper(\n            self, N, C, H, W, order, epsilon, seed, is_test):\n        np.random.seed(seed)\n        model = model_helper.ModelHelper(name=\"test_model\")\n        brew.instance_norm(\n            model,\n            'input',\n            'output',\n            C,\n            epsilon=epsilon,\n            order=order,\n            is_test=is_test)\n\n        input_blob = np.random.rand(N, C, H, W).astype(np.float32)\n        if order == 'NHWC':\n            input_blob = np.transpose(input_blob, axes=(0, 2, 3, 1))\n\n        self.ws.create_blob('input').feed(input_blob)\n\n        self.ws.create_net(model.param_init_net).run()\n        self.ws.create_net(model.net).run()\n\n        if is_test:\n            scale = self.ws.blobs['output_s'].fetch()\n            assert scale is not None\n            assert scale.shape == (C, )\n            bias = self.ws.blobs['output_b'].fetch()\n            assert bias is not None\n            assert bias.shape == (C, )\n\n        output_blob = self.ws.blobs['output'].fetch()\n        if order == 'NHWC':\n            output_blob = np.transpose(output_blob, axes=(0, 3, 1, 2))\n\n        assert output_blob.shape == (N, C, H, W)\n\n\nif __name__ == '__main__':\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/jsd_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\ndef entropy(p):\n    q = 1. - p\n    return -p * np.log(p) - q * np.log(q)\n\n\ndef jsd(p, q):\n    return [entropy(p / 2. + q / 2.) - entropy(p) / 2. - entropy(q) / 2.]\n\n\ndef jsd_grad(go, o, pq_list):\n    p, q = pq_list\n    m = (p + q) / 2.\n    return [np.log(p * (1 - m) / (1 - p) / m) / 2. * go, None]\n\n\nclass TestJSDOps(hu.HypothesisTestCase):\n    @given(n=st.integers(10, 100), **hu.gcs_cpu_only)\n    def test_bernoulli_jsd(self, n, gc, dc):\n        p = np.random.rand(n).astype(np.float32)\n        q = np.random.rand(n).astype(np.float32)\n        op = core.CreateOperator(\"BernoulliJSD\", [\"p\", \"q\"], [\"l\"])\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[p, q],\n            reference=jsd,\n            output_to_grad='l',\n            grad_reference=jsd_grad,\n        )\n"
  },
  {
    "path": "caffe2/python/operator_test/key_split_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core, workspace\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\n\nimport numpy as np\n\n\nclass TestKeySplitOps(hu.HypothesisTestCase):\n    @given(\n        X=hu.arrays(\n            dims=[1000],\n            dtype=np.int64,\n            elements=st.integers(min_value=0, max_value=100)\n        ),\n        **hu.gcs_cpu_only\n    )\n    def test_key_split_op(self, X, gc, dc):\n        categorical_limit = max(X) + 1\n        workspace.ResetWorkspace()\n        workspace.FeedBlob('X', X)\n        output_blobs = ['Y_%d' % i for i in range(categorical_limit)]\n        op = core.CreateOperator(\n            'KeySplit', ['X'],\n            output_blobs,\n            categorical_limit=categorical_limit\n        )\n        workspace.RunOperatorOnce(op)\n        output_vecs = [\n            workspace.blobs[output_blobs[i]] for i in range(categorical_limit)\n        ]\n        expected_output_vecs = [[] for _ in range(categorical_limit)]\n        for i, x in enumerate(X):\n            expected_output_vecs[x].append(i)\n        for i in range(categorical_limit):\n            np.testing.assert_array_equal(\n                output_vecs[i],\n                np.array(expected_output_vecs[i], dtype=np.int32)\n            )\n"
  },
  {
    "path": "caffe2/python/operator_test/lars_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestLars(hu.HypothesisTestCase):\n\n    @given(offset=st.floats(min_value=0, max_value=100), **hu.gcs)\n    def test_lars(self, offset, dc, gc):\n        X = np.random.rand(6, 7, 8, 9).astype(np.float32)\n        dX = np.random.rand(6, 7, 8, 9).astype(np.float32)\n\n        def ref_lars(X, dX):\n            return [1. / (np.linalg.norm(dX) / np.linalg.norm(X) + offset)]\n\n        op = core.CreateOperator(\n            \"Lars\",\n            [\"X\", \"dX\"],\n            [\"rescale_factor\"],\n            offset=offset\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, dX],\n            reference=ref_lars\n        )\n"
  },
  {
    "path": "caffe2/python/operator_test/layer_norm_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import brew, core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport numpy as np\n\nfrom caffe2.python.model_helper import ModelHelper\n\nclass TestLayerNormOp(hu.HypothesisTestCase):\n    @given(X=hu.tensors(n=1), **hu.gcs)\n    def test_layer_norm_grad_op(self, X, gc, dc):\n        X = X[0]\n        if len(X.shape) == 1:\n            X = np.expand_dims(X, axis=0)\n        axis = np.random.randint(0, len(X.shape))\n        epsilon = 1e-4\n        op = core.CreateOperator(\n            \"LayerNormGradient\",\n            [\"gout\", \"out\", \"mean\", \"stdev\", \"in\"],\n            [\"gin\"],\n            axis=axis,\n            epsilon=epsilon,\n        )\n\n        def layer_norm_ref(X):\n            left = int(np.prod(X.shape[:axis]))\n            reshaped = np.reshape(X, [left, -1])\n            mean = np.mean(reshaped, axis=1).reshape([left, 1])\n            stdev = np.sqrt(\n                np.mean(np.square(reshaped), axis=1).reshape([left, 1]) -\n                np.power(mean, 2) + epsilon\n            )\n            norm = (reshaped - mean) / (stdev)\n            norm = np.reshape(norm, X.shape)\n            mean = np.reshape(mean, X.shape[:axis] + (1,))\n            stdev = np.reshape(stdev, X.shape[:axis] + (1,))\n            return [norm, mean, stdev]\n\n        norm, mean, stdev = layer_norm_ref(X)\n        gout = norm\n\n        def layer_norm_grad_ref(gout_full, norm, mean_full, stdev_full, X_full):\n            left = int(np.prod(X_full.shape[:axis]))\n            right = int(np.prod(X_full.shape[axis:]))\n            X = np.reshape(X_full, [left, right])\n            stdev = np.reshape(stdev_full, [left, 1])\n            mean = np.reshape(mean_full, [left, 1])\n            gout = np.reshape(gout_full, [left, right])\n            dstdev_end = (-1.0) / np.power(stdev, 2.0) \\\n                    * np.sum((X - mean) * gout, axis=1).reshape([left, 1])\n            dmean_end = np.sum(-1.0 / stdev * gout, axis=1).reshape([left, 1])\n            dx_end = 1.0 / stdev * gout\n\n            # stdev block\n            dmean_stdev = -1.0 * mean / stdev * dstdev_end\n            dx_stdev = X / (right * stdev) * dstdev_end\n\n            # mean block\n            dmean = dmean_end + dmean_stdev\n            dxmean = (1.0 / right) * dmean\n\n            # final outputs\n            dx = dx_end + dx_stdev + dxmean\n            dx = dx.reshape(X_full.shape)\n\n            return [dx]\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[gout, norm, mean, stdev, X],\n            reference=layer_norm_grad_ref\n        )\n        self.assertDeviceChecks(\n            device_options=dc,\n            op=op,\n            inputs=[gout, norm, mean, stdev, X],\n            outputs_to_check=[0],\n        )\n\n    @given(X=hu.tensors(n=1), **hu.gcs)\n    def test_layer_norm_op(self, X, gc, dc):\n        X = X[0]\n        if len(X.shape) == 1:\n            X = np.expand_dims(X, axis=0)\n        axis = np.random.randint(0, len(X.shape))\n        epsilon = 1e-4\n        op = core.CreateOperator(\n            \"LayerNorm\",\n            [\"input\"],\n            [\"output\", \"mean\", \"stdev\"],\n            axis=axis,\n            epsilon=epsilon,\n        )\n\n        def layer_norm_ref(X):\n            left = int(np.prod(X.shape[:axis]))\n            reshaped = np.reshape(X, [left, -1])\n            mean = np.mean(reshaped, axis=1).reshape([left, 1])\n            stdev = np.sqrt(\n                np.mean(np.power(reshaped, 2), axis=1).reshape([left, 1]) -\n                np.power(mean, 2) + epsilon\n            )\n            norm = (reshaped - mean) / (stdev)\n            norm = np.reshape(norm, X.shape)\n            mean = np.reshape(mean, X.shape[:axis] + (1,))\n            stdev = np.reshape(stdev, X.shape[:axis] + (1,))\n            return [norm, mean, stdev]\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=layer_norm_ref\n        )\n        self.assertDeviceChecks(\n            device_options=dc,\n            op=op,\n            inputs=[X],\n            outputs_to_check=[0, 1, 2],\n        )\n\n    @given(X=hu.tensors(n=1), **hu.gcs)\n    def test_layer_norm_brew_wrapper(self, X, gc, dc):\n        X = X[0]\n        if len(X.shape) == 1:\n            X = np.expand_dims(X, axis=0)\n        axis = np.random.randint(0, len(X.shape))\n        scale_dim = [1] * np.ndim(X)\n        scale_dim[axis] = X.shape[axis]\n\n        self.ws.create_blob('input').feed(X)\n\n        model = ModelHelper(name='test_layer_norm_brew_wrapper')\n        brew.layer_norm(\n            model,\n            'input',\n            'output',\n            dim_in=X.shape[axis],\n            axis=axis,\n            epsilon=1e-4,\n        )\n\n        self.ws.create_net(model.param_init_net).run()\n        self.ws.create_net(model.net).run()\n"
  },
  {
    "path": "caffe2/python/operator_test/lc_operator_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestLocallyConnectedOp(hu.HypothesisTestCase):\n\n    @given(kernel=st.integers(1, 3),\n           size=st.integers(1, 5),\n           input_channels=st.integers(1, 3),\n           output_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           use_bias=st.booleans(),\n           **hu.gcs)\n    def test_lc_2d(\n            self, kernel, size, input_channels, output_channels, batch_size,\n            order, use_bias, gc, dc):\n        if size < kernel:\n            return\n\n        op = core.CreateOperator(\n            \"LC2D\",\n            [\"X\", \"W\", \"b\"] if use_bias else [\"X\", \"W\"],\n            [\"Y\"],\n            kernel=kernel,\n            order=order,\n            engine=\"\",\n        )\n\n        L = size - kernel + 1\n        if order == \"NCHW\":\n            X = np.random.rand(\n                batch_size, input_channels, size, size).astype(np.float32) - 0.5\n            W = np.random.rand(\n                L, L, output_channels, input_channels, kernel, kernel\n            ).astype(np.float32) - 0.5\n        else:\n            X = np.random.rand(\n                batch_size, size, size, input_channels).astype(np.float32) - 0.5\n            W = np.random.rand(\n                L, L, output_channels, kernel, kernel, input_channels\n            ).astype(np.float32) - 0.5\n        b = np.random.rand(L, L, output_channels).astype(np.float32) - 0.5\n        inputs = [X, W, b] if use_bias else [X, W]\n\n        def lc_2d_nchw(X, W, b=None):\n            N, C, XH, XW = X.shape\n            YH, YW, M, _, KH, KW = W.shape\n\n            def conv(n, m, yh, yw):\n                sum = b[yh, yw, m] if b is not None else 0\n                for c in range(C):\n                    for kh in range(KH):\n                        for kw in range(KW):\n                            hh = yh + kh\n                            ww = yw + kw\n                            sum += X[n, c, hh, ww] * W[yh, yw, m, c, kh, kw]\n                return sum\n\n            output = np.zeros((N, M, YH, YW), dtype=np.float32)\n            for n in range(N):\n                for m in range(M):\n                    for yh in range(YH):\n                        for yw in range(YW):\n                            output[n, m, yh, yw] = conv(n, m, yh, yw)\n            return [output]\n\n        def lc_2d_nhwc(X, W, b=None):\n            XT = np.transpose(X, [0, 3, 1, 2])\n            WT = np.transpose(W, [0, 1, 2, 5, 3, 4])\n            output = lc_2d_nchw(XT, WT, b)\n            return [np.transpose(output[0], [0, 2, 3, 1])]\n\n        ref_op = lc_2d_nchw if order == \"NCHW\" else lc_2d_nhwc\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            reference=ref_op,\n        )\n        for i in range(len(inputs)):\n            self.assertGradientChecks(gc, op, inputs, i, [0])\n\n    @given(kernel=st.integers(1, 3),\n           size=st.integers(1, 5),\n           input_channels=st.integers(1, 3),\n           output_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           use_bias=st.booleans(),\n           **hu.gcs)\n    def test_lc_1d(\n            self, kernel, size, input_channels, output_channels, batch_size,\n            use_bias, gc, dc):\n        if size < kernel:\n            return\n\n        op = core.CreateOperator(\n            \"LC1D\",\n            [\"X\", \"W\", \"b\"] if use_bias else [\"X\", \"W\"],\n            [\"Y\"],\n            kernels=[kernel],\n            order=\"NCHW\",\n            engine=\"\",\n        )\n\n        L = size - kernel + 1\n        X = np.random.rand(\n            batch_size, input_channels, size).astype(np.float32) - 0.5\n        W = np.random.rand(\n            L, output_channels, input_channels, kernel).astype(np.float32) - 0.5\n        b = np.random.rand(L, output_channels).astype(np.float32) - 0.5\n        inputs = [X, W, b] if use_bias else [X, W]\n\n        def lc_1d_nchw(X, W, b=None):\n            N, C, XL = X.shape\n            YL, M, _, KL = W.shape\n\n            def conv(n, m, yl):\n                sum = b[yl, m] if b is not None else 0\n                for c in range(C):\n                    for kl in range(KL):\n                        ll = yl + kl\n                        sum += X[n, c, ll] * W[yl, m, c, kl]\n                return sum\n\n            output = np.zeros((N, M, YL), dtype=np.float32)\n            for n in range(N):\n                for m in range(M):\n                    for yl in range(YL):\n                        output[n, m, yl] = conv(n, m, yl)\n            return [output]\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            reference=lc_1d_nchw,\n        )\n        for i in range(len(inputs)):\n            self.assertGradientChecks(gc, op, inputs, i, [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/leaky_relu_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nfrom hypothesis import given, assume\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core, model_helper\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestLeakyRelu(hu.HypothesisTestCase):\n\n    def _get_inputs(self, N, C, H, W, order):\n        input_data = np.random.rand(N, C, H, W).astype(np.float32) - 0.5\n\n        # default step size is 0.05\n        input_data[np.logical_and(\n            input_data >= 0, input_data <= 0.051)] = 0.051\n        input_data[np.logical_and(\n            input_data <= 0, input_data >= -0.051)] = -0.051\n\n        if order == 'NHWC':\n            input_data = np.transpose(input_data, axes=(0, 2, 3, 1))\n\n        return input_data,\n\n    def _get_op(self, device_option, alpha, order, inplace=False):\n        outputs = ['output' if not inplace else \"input\"]\n        op = core.CreateOperator(\n            'LeakyRelu',\n            ['input'],\n            outputs,\n            alpha=alpha,\n            device_option=device_option)\n        return op\n\n    def _feed_inputs(self, input_blobs, device_option):\n        names = ['input', 'scale', 'bias']\n        for name, blob in zip(names, input_blobs):\n            self.ws.create_blob(name).feed(blob, device_option=device_option)\n\n    @given(gc=hu.gcs['gc'],\n           dc=hu.gcs['dc'],\n           N=st.integers(2, 3),\n           C=st.integers(2, 3),\n           H=st.integers(2, 3),\n           W=st.integers(2, 3),\n           alpha=st.floats(0, 1),\n           order=st.sampled_from(['NCHW', 'NHWC']),\n           seed=st.integers(0, 1000))\n    def test_leaky_relu_gradients(self, gc, dc, N, C, H, W, order, alpha, seed):\n        np.random.seed(seed)\n\n        op = self._get_op(\n            device_option=gc,\n            alpha=alpha,\n            order=order)\n        input_blobs = self._get_inputs(N, C, H, W, order)\n\n        self.assertDeviceChecks(dc, op, input_blobs, [0])\n        self.assertGradientChecks(gc, op, input_blobs, 0, [0])\n\n    @given(gc=hu.gcs['gc'],\n           dc=hu.gcs['dc'],\n           N=st.integers(2, 10),\n           C=st.integers(3, 10),\n           H=st.integers(5, 10),\n           W=st.integers(7, 10),\n           alpha=st.floats(0, 1),\n           seed=st.integers(0, 1000))\n    def test_leaky_relu_layout(self, gc, dc, N, C, H, W, alpha, seed):\n        outputs = {}\n        for order in ('NCHW', 'NHWC'):\n            np.random.seed(seed)\n            input_blobs = self._get_inputs(N, C, H, W, order)\n            self._feed_inputs(input_blobs, device_option=gc)\n            op = self._get_op(\n                device_option=gc,\n                alpha=alpha,\n                order=order)\n            self.ws.run(op)\n            outputs[order] = self.ws.blobs['output'].fetch()\n        np.testing.assert_allclose(\n            outputs['NCHW'],\n            outputs['NHWC'].transpose((0, 3, 1, 2)),\n            atol=1e-4,\n            rtol=1e-4)\n\n    @given(gc=hu.gcs['gc'],\n           dc=hu.gcs['dc'],\n           N=st.integers(2, 10),\n           C=st.integers(3, 10),\n           H=st.integers(5, 10),\n           W=st.integers(7, 10),\n           order=st.sampled_from(['NCHW', 'NHWC']),\n           alpha=st.floats(0, 1),\n           seed=st.integers(0, 1000),\n           inplace=st.booleans())\n    def test_leaky_relu_reference_check(self, gc, dc, N, C, H, W, order, alpha,\n                                        seed, inplace):\n        np.random.seed(seed)\n\n        if order != \"NCHW\":\n            assume(not inplace)\n\n        inputs = self._get_inputs(N, C, H, W, order)\n        op = self._get_op(\n            device_option=gc,\n            alpha=alpha,\n            order=order,\n            inplace=inplace)\n\n        def ref(input_blob):\n            result = input_blob.copy()\n            result[result < 0] *= alpha\n            return result,\n\n        self.assertReferenceChecks(gc, op, inputs, ref)\n\n    @given(gc=hu.gcs['gc'],\n           dc=hu.gcs['dc'],\n           N=st.integers(2, 10),\n           C=st.integers(3, 10),\n           H=st.integers(5, 10),\n           W=st.integers(7, 10),\n           order=st.sampled_from(['NCHW', 'NHWC']),\n           alpha=st.floats(0, 1),\n           seed=st.integers(0, 1000))\n    def test_leaky_relu_device_check(self, gc, dc, N, C, H, W, order, alpha,\n                                     seed):\n        np.random.seed(seed)\n\n        inputs = self._get_inputs(N, C, H, W, order)\n        op = self._get_op(\n            device_option=gc,\n            alpha=alpha,\n            order=order)\n\n        self.assertDeviceChecks(dc, op, inputs, [0])\n\n    @given(N=st.integers(2, 10),\n           C=st.integers(3, 10),\n           H=st.integers(5, 10),\n           W=st.integers(7, 10),\n           order=st.sampled_from(['NCHW', 'NHWC']),\n           alpha=st.floats(0, 1),\n           seed=st.integers(0, 1000))\n    def test_leaky_relu_model_helper_helper(self, N, C, H, W, order, alpha, seed):\n        np.random.seed(seed)\n        arg_scope = {'order': order}\n        model = model_helper.ModelHelper(name=\"test_model\", arg_scope=arg_scope)\n        model.LeakyRelu(\n            'input',\n            'output',\n            alpha=alpha)\n\n        input_blob = np.random.rand(N, C, H, W).astype(np.float32)\n        if order == 'NHWC':\n            input_blob = np.transpose(input_blob, axes=(0, 2, 3, 1))\n\n        self.ws.create_blob('input').feed(input_blob)\n\n        self.ws.create_net(model.param_init_net).run()\n        self.ws.create_net(model.net).run()\n\n        output_blob = self.ws.blobs['output'].fetch()\n        if order == 'NHWC':\n            output_blob = np.transpose(output_blob, axes=(0, 3, 1, 2))\n\n        assert output_blob.shape == (N, C, H, W)\n\n\nif __name__ == '__main__':\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/learning_rate_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\nfrom hypothesis import given\n\nimport numpy as np\nimport math\n\n\nclass TestLearningRate(hu.HypothesisTestCase):\n    @given(**hu.gcs_cpu_only)\n    def test_alter_learning_rate_op(self, gc, dc):\n        iter = np.random.randint(low=1, high=1e5, size=1)\n        active_period = int(np.random.randint(low=1, high=1e3, size=1))\n        inactive_period = int(np.random.randint(low=1, high=1e3, size=1))\n        base_lr = float(np.random.random(1))\n\n        def ref(iter):\n            iter = float(iter)\n            reminder = iter % (active_period + inactive_period)\n            if reminder < active_period:\n                return (np.array(base_lr), )\n            else:\n                return (np.array(0.), )\n\n        op = core.CreateOperator(\n            'LearningRate',\n            'iter',\n            'lr',\n            policy=\"alter\",\n            active_first=True,\n            base_lr=base_lr,\n            active_period=active_period,\n            inactive_period=inactive_period\n        )\n\n        self.assertReferenceChecks(gc, op, [iter], ref)\n\n    @given(**hu.gcs_cpu_only)\n    def test_hill_learning_rate_op(self, gc, dc):\n        iter = np.random.randint(low=1, high=1e5, size=1)\n\n        num_iter = int(np.random.randint(low=1e2, high=1e3, size=1))\n        start_multiplier = 1e-4\n        gamma = 1.0\n        power = 0.5\n        end_multiplier = 1e-2\n        base_lr = float(np.random.random(1))\n\n        def ref(iter):\n            iter = float(iter)\n            if iter < num_iter:\n                lr = start_multiplier + (\n                    1.0 - start_multiplier\n                ) * iter / num_iter\n            else:\n                iter -= num_iter\n                lr = math.pow(1.0 + gamma * iter, -power)\n                lr = max(lr, end_multiplier)\n            return (np.array(base_lr * lr), )\n\n        op = core.CreateOperator(\n            'LearningRate',\n            'data',\n            'out',\n            policy=\"hill\",\n            base_lr=base_lr,\n            num_iter=num_iter,\n            start_multiplier=start_multiplier,\n            gamma=gamma,\n            power=power,\n            end_multiplier=end_multiplier,\n        )\n        self.assertReferenceChecks(gc, op, [iter], ref)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/lengths_tile_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestLengthsTileOp(hu.HypothesisTestCase):\n\n    @given(\n        inputs=st.integers(min_value=1, max_value=20).flatmap(\n            lambda size: st.tuples(\n                hu.arrays([size]),\n                hu.arrays([size], dtype=np.int32,\n                          elements=st.integers(min_value=0, max_value=20)),\n            )\n        ),\n        **hu.gcs)\n    def test_lengths_tile(self, inputs, gc, dc):\n        data, lengths = inputs\n\n        def lengths_tile_op(data, lengths):\n            return [np.concatenate([\n                [d] * l for d, l in zip(data, lengths)\n            ])]\n\n        op = core.CreateOperator(\n            \"LengthsTile\",\n            [\"data\", \"lengths\"],\n            [\"output\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[data, lengths],\n            reference=lengths_tile_op,\n        )\n\n        self.assertGradientChecks(\n            device_option=gc,\n            op=op,\n            inputs=[data, lengths],\n            outputs_to_check=0,\n            outputs_with_grads=[0]\n        )\n"
  },
  {
    "path": "caffe2/python/operator_test/lengths_top_k_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestLengthsTopKOps(hu.HypothesisTestCase):\n    @given(N=st.integers(min_value=0, max_value=10),\n           K=st.integers(min_value=1, max_value=10),\n           **hu.gcs_cpu_only)\n    def test_lengths_top_k_op(self, N, K, gc, dc):\n        lens = np.random.randint(low=1, high=2 * K + 1, size=N).astype(np.int32)\n        X = []\n        for i in lens:\n            X.extend(map(lambda x: x / 100.0, range(0, 6 * i, 6)))\n        X = np.array(X, dtype=np.float32)\n        op = core.CreateOperator(\"LengthsTopK\", [\"X\", \"Y\"], [\"values\", \"indices\"], k=K)\n\n        def lengths_top_k(X, lens):\n            N, si = lens.shape[0], 0\n            values, indices = [], []\n            for i in range(N):\n                cur_indices = X[si:si + lens[i]].argsort()[-K:][::-1]\n                cur_values = X[si:si + lens[i]][cur_indices]\n                values.extend(cur_values)\n                indices.extend(cur_indices)\n                si += lens[i]\n                if lens[i] < K:\n                    values.extend([0] * (K - lens[i]))\n                    indices.extend([-1] * (K - lens[i]))\n\n            return (np.array(values, dtype=np.float32).reshape(-1, K),\n                    np.array(indices, dtype=np.int32).reshape(-1, K))\n\n        self.assertDeviceChecks(dc, op, [X, lens], [0, 1])\n        self.assertReferenceChecks(gc, op, [X, lens], lengths_top_k)\n        self.assertGradientChecks(gc, op, [X, lens], 0, [0])\n\n    @given(N=st.integers(min_value=0, max_value=10),\n           K=st.integers(min_value=1, max_value=10),\n           **hu.gcs_cpu_only)\n    def test_lengths_top_k_empty_op(self, N, K, gc, dc):\n        lens = np.zeros((N, ), dtype=np.int32)\n        X = np.array([], dtype=np.float32)\n        op = core.CreateOperator(\"LengthsTopK\", [\"X\", \"Y\"], [\"values\", \"indices\"], k=K)\n\n        def lengths_top_k(X, lens):\n            return (np.zeros((N, K), dtype=np.float32),\n                    -1 * np.ones((N, K), dtype=np.int32))\n\n        self.assertDeviceChecks(dc, op, [X, lens], [0, 1])\n        self.assertReferenceChecks(gc, op, [X, lens], lengths_top_k)\n        self.assertGradientChecks(gc, op, [X, lens], 0, [0])\n"
  },
  {
    "path": "caffe2/python/operator_test/listwise_l2r_operator_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestListwiseL2rOps(hu.HypothesisTestCase):\n    def ref_lambda_rank_ndcg_loss(self, y, r):\n        n = len(y)\n\n        def get_discounts(v):\n            x = np.argsort(v)\n            d = [0 for _ in range(n)]\n            for i in range(n):\n                d[x[i]] = 1. / np.log2(n - i + 1.)\n            return d\n\n        def sigm(x):\n            return 1 / (1 + np.exp(-x))\n\n        def log_sigm(x):\n            return -np.log(1 + np.exp(-x))\n\n        g = [2**r[i] for i in range(n)]\n        d = get_discounts(r)\n        idcg = sum([g[i] * d[i] for i in range(n)])\n\n        d = get_discounts(y)\n        loss = 0\n        dy = np.zeros(n)\n        for i in range(n):\n            for j in range(n):\n                if i == j:\n                    continue\n                lambda_weight = np.abs((2**r[i] - 2**r[j]) * (d[i] - d[j]))\n                rank_loss = -log_sigm(\n                    y[i] - y[j] if r[i] > r[j] else y[j] - y[i]\n                )\n                rank_dy = (0. if r[i] > r[j] else 1.) - sigm(-y[i] + y[j])\n                loss += lambda_weight * rank_loss / idcg\n                dy[i] += lambda_weight * rank_dy / idcg\n        return loss, dy\n\n    @given(n=st.integers(1, 20), k=st.integers(2, 5))\n    def test_lambda_rank_ndcg_loss(self, n, k):\n        y = np.random.rand(n).astype(np.float32)\n        r = np.random.randint(k, size=n).astype(np.float32)\n        dloss = np.random.random(1).astype(np.float32)\n\n        workspace.blobs['y'] = y\n        workspace.blobs['r'] = r\n        workspace.blobs['dloss'] = dloss\n\n        op = core.CreateOperator('LambdaRankNdcg', ['y', 'r'], ['loss', 'dy'])\n        workspace.RunOperatorOnce(op)\n        loss = workspace.blobs['loss']\n        dy = workspace.blobs['dy']\n        ref_loss, ref_dy = self.ref_lambda_rank_ndcg_loss(y, r)\n        self.assertAlmostEqual(np.asscalar(loss), ref_loss, delta=1e-4)\n        np.testing.assert_allclose(dy, ref_dy, rtol=1e-5, atol=1e-6)\n\n        op = core.CreateOperator(\n            'LambdaRankNdcgGradient', ['y', 'dy', 'dloss'], ['dy_back']\n        )\n        workspace.RunOperatorOnce(op)\n        dy_back = workspace.blobs['dy_back']\n        np.testing.assert_allclose(\n            dy_back, np.asscalar(dloss) * ref_dy, rtol=1e-5, atol=1e-6\n        )\n"
  },
  {
    "path": "caffe2/python/operator_test/load_save_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport errno\nimport hypothesis.strategies as st\nfrom hypothesis import given\nimport numpy as np\nimport os\nimport shutil\nimport tempfile\nimport unittest\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core, test_util, workspace\n\nif workspace.has_gpu_support:\n    DEVICES = [caffe2_pb2.CPU, caffe2_pb2.CUDA]\n    max_gpuid = workspace.NumCudaDevices() - 1\nelse:\n    DEVICES = [caffe2_pb2.CPU]\n    max_gpuid = 0\n\n\n# Utility class for other loading tests, don't add test functions here\n# Inherit from this test instead. If you add a test here,\n# each derived class will inherit it as well and cause test duplication\nclass TestLoadSaveBase(test_util.TestCase):\n\n    def __init__(self, methodName, db_type='minidb'):\n        super(TestLoadSaveBase, self).__init__(methodName)\n        self._db_type = db_type\n\n    @given(src_device_type=st.sampled_from(DEVICES),\n           src_gpu_id=st.integers(min_value=0, max_value=max_gpuid),\n           dst_device_type=st.sampled_from(DEVICES),\n           dst_gpu_id=st.integers(min_value=0, max_value=max_gpuid))\n    def load_save(self, src_device_type, src_gpu_id,\n                  dst_device_type, dst_gpu_id):\n        workspace.ResetWorkspace()\n        dtypes = [np.float16, np.float32, np.float64, np.bool, np.int8,\n                  np.int16, np.int32, np.int64, np.uint8, np.uint16]\n        arrays = [np.random.permutation(6).reshape(2, 3).astype(T)\n                  for T in dtypes]\n        src_device_option = core.DeviceOption(\n            src_device_type, src_gpu_id)\n        dst_device_option = core.DeviceOption(\n            dst_device_type, dst_gpu_id)\n\n        for i, arr in enumerate(arrays):\n            self.assertTrue(workspace.FeedBlob(str(i), arr, src_device_option))\n            self.assertTrue(workspace.HasBlob(str(i)))\n\n        try:\n            # Saves the blobs to a local db.\n            tmp_folder = tempfile.mkdtemp()\n            op = core.CreateOperator(\n                \"Save\",\n                [str(i) for i in range(len(arrays))], [],\n                absolute_path=1,\n                db=os.path.join(tmp_folder, \"db\"), db_type=self._db_type)\n            self.assertTrue(workspace.RunOperatorOnce(op))\n\n            # Reset the workspace so that anything we load is surely loaded\n            # from the serialized proto.\n            workspace.ResetWorkspace()\n            self.assertEqual(len(workspace.Blobs()), 0)\n\n            def _LoadTest(keep_device, device_type, gpu_id, blobs, loadAll):\n                \"\"\"A helper subfunction to test keep and not keep.\"\"\"\n                op = core.CreateOperator(\n                    \"Load\",\n                    [], blobs,\n                    absolute_path=1,\n                    db=os.path.join(tmp_folder, \"db\"), db_type=self._db_type,\n                    device_option=dst_device_option,\n                    keep_device=keep_device,\n                    load_all=loadAll)\n                self.assertTrue(workspace.RunOperatorOnce(op))\n                for i, arr in enumerate(arrays):\n                    self.assertTrue(workspace.HasBlob(str(i)))\n                    fetched = workspace.FetchBlob(str(i))\n                    self.assertEqual(fetched.dtype, arr.dtype)\n                    np.testing.assert_array_equal(\n                        workspace.FetchBlob(str(i)), arr)\n                    proto = caffe2_pb2.BlobProto()\n                    proto.ParseFromString(workspace.SerializeBlob(str(i)))\n                    self.assertTrue(proto.HasField('tensor'))\n                    self.assertEqual(proto.tensor.device_detail.device_type,\n                                     device_type)\n                    if device_type == caffe2_pb2.CUDA:\n                        self.assertEqual(proto.tensor.device_detail.cuda_gpu_id,\n                                         gpu_id)\n\n            blobs = [str(i) for i in range(len(arrays))]\n            # Load using device option stored in the proto, i.e.\n            # src_device_option\n            _LoadTest(1, src_device_type, src_gpu_id, blobs, 0)\n            # Load again, but this time load into dst_device_option.\n            _LoadTest(0, dst_device_type, dst_gpu_id, blobs, 0)\n            # Load back to the src_device_option to see if both paths are able\n            # to reallocate memory.\n            _LoadTest(1, src_device_type, src_gpu_id, blobs, 0)\n            # Reset the workspace, and load directly into the dst_device_option.\n            workspace.ResetWorkspace()\n            _LoadTest(0, dst_device_type, dst_gpu_id, blobs, 0)\n\n            # Test load all which loads all blobs in the db into the workspace.\n            workspace.ResetWorkspace()\n            _LoadTest(1, src_device_type, src_gpu_id, [], 1)\n            # Load again making sure that overwrite functionality works.\n            _LoadTest(1, src_device_type, src_gpu_id, [], 1)\n            # Load again with different device.\n            _LoadTest(0, dst_device_type, dst_gpu_id, [], 1)\n            workspace.ResetWorkspace()\n            _LoadTest(0, dst_device_type, dst_gpu_id, [], 1)\n        finally:\n            # clean up temp folder.\n            try:\n                shutil.rmtree(tmp_folder)\n            except OSError as e:\n                if e.errno != errno.ENOENT:\n                    raise\n\n    def saveFile(self, tmp_folder, db_name, db_type, start_blob_id):\n        dtypes = [np.float16, np.float32, np.float64, np.bool, np.int8,\n                  np.int16, np.int32, np.int64, np.uint8, np.uint16]\n        arrays = [np.random.permutation(6).reshape(2, 3).astype(T)\n                  for T in dtypes]\n\n        for i, arr in enumerate(arrays):\n            self.assertTrue(workspace.FeedBlob(str(i + start_blob_id), arr))\n            self.assertTrue(workspace.HasBlob(str(i + start_blob_id)))\n\n        # Saves the blobs to a local db.\n        tmp_file = os.path.join(tmp_folder, db_name)\n        op = core.CreateOperator(\n            \"Save\",\n            [str(i + start_blob_id) for i in range(len(arrays))], [],\n            absolute_path=1,\n            db=tmp_file, db_type=db_type)\n        workspace.RunOperatorOnce(op)\n        return tmp_file, arrays\n\n\nclass TestLoadSave(TestLoadSaveBase):\n\n    def testLoadSave(self):\n        self.load_save()\n\n    def testRepeatedArgs(self):\n        dtypes = [np.float16, np.float32, np.float64, np.bool, np.int8,\n                  np.int16, np.int32, np.int64, np.uint8, np.uint16]\n        arrays = [np.random.permutation(6).reshape(2, 3).astype(T)\n                  for T in dtypes]\n\n        for i, arr in enumerate(arrays):\n            self.assertTrue(workspace.FeedBlob(str(i), arr))\n            self.assertTrue(workspace.HasBlob(str(i)))\n\n        # Saves the blobs to a local db.\n        tmp_folder = tempfile.mkdtemp()\n        op = core.CreateOperator(\n            \"Save\",\n            [str(i) for i in range(len(arrays))] * 2, [],\n            absolute_path=1,\n            db=os.path.join(tmp_folder, \"db\"), db_type=self._db_type)\n        with self.assertRaises(RuntimeError):\n            workspace.RunOperatorOnce(op)\n        try:\n            shutil.rmtree(tmp_folder)\n        except OSError as e:\n            if e.errno != errno.ENOENT:\n                raise\n\n    def testLoadExcessblobs(self):\n        tmp_folder = tempfile.mkdtemp()\n        tmp_file, arrays = self.saveFile(tmp_folder, \"db\", self._db_type, 0)\n\n        op = core.CreateOperator(\n            \"Load\",\n            [], [str(i) for i in range(len(arrays))] * 2,\n            absolute_path=1,\n            db=tmp_file, db_type=self._db_type,\n            load_all=False)\n        with self.assertRaises(RuntimeError):\n            workspace.RunOperatorOnce(op)\n\n        try:\n            shutil.rmtree(tmp_folder)\n        except OSError as e:\n            if e.errno != errno.ENOENT:\n                raise\n\n    def testTruncatedFile(self):\n        tmp_folder = tempfile.mkdtemp()\n        tmp_file, arrays = self.saveFile(tmp_folder, \"db\", self._db_type, 0)\n\n        with open(tmp_file, 'wb+') as fdest:\n            fdest.seek(20, os.SEEK_END)\n            fdest.truncate()\n\n        op = core.CreateOperator(\n            \"Load\",\n            [], [str(i) for i in range(len(arrays))],\n            absolute_path=1,\n            db=tmp_file, db_type=self._db_type,\n            load_all=False)\n        with self.assertRaises(RuntimeError):\n            workspace.RunOperatorOnce(op)\n\n        op = core.CreateOperator(\n            \"Load\",\n            [], [],\n            absolute_path=1,\n            db=tmp_file, db_type=self._db_type,\n            load_all=True)\n        with self.assertRaises(RuntimeError):\n            workspace.RunOperatorOnce(op)\n        try:\n            shutil.rmtree(tmp_folder)\n        except OSError as e:\n            if e.errno != errno.ENOENT:\n                raise\n\n    def testBlobNameOverrides(self):\n        original_names = ['blob_a', 'blob_b', 'blob_c']\n        new_names = ['x', 'y', 'z']\n        blobs = [np.random.permutation(6) for i in range(3)]\n        for i, blob in enumerate(blobs):\n            self.assertTrue(workspace.FeedBlob(original_names[i], blob))\n            self.assertTrue(workspace.HasBlob(original_names[i]))\n        self.assertEqual(len(workspace.Blobs()), 3)\n\n        try:\n            # Saves the blobs to a local db.\n            tmp_folder = tempfile.mkdtemp()\n            with self.assertRaises(RuntimeError):\n                workspace.RunOperatorOnce(\n                    core.CreateOperator(\n                        \"Save\", original_names, [],\n                        absolute_path=1,\n                        strip_prefix='.temp',\n                        blob_name_overrides=new_names,\n                        db=os.path.join(tmp_folder, \"db\"),\n                        db_type=self._db_type\n                    )\n                )\n            self.assertTrue(\n                workspace.RunOperatorOnce(\n                    core.CreateOperator(\n                        \"Save\", original_names, [],\n                        absolute_path=1,\n                        blob_name_overrides=new_names,\n                        db=os.path.join(tmp_folder, \"db\"),\n                        db_type=self._db_type\n                    )\n                )\n            )\n            self.assertTrue(workspace.ResetWorkspace())\n            self.assertEqual(len(workspace.Blobs()), 0)\n            self.assertTrue(\n                workspace.RunOperatorOnce(\n                    core.CreateOperator(\n                        \"Load\", [], [],\n                        absolute_path=1,\n                        db=os.path.join(tmp_folder, \"db\"),\n                        db_type=self._db_type,\n                        load_all=1\n                    )\n                )\n            )\n            self.assertEqual(len(workspace.Blobs()), 3)\n            for i, name in enumerate(new_names):\n                self.assertTrue(workspace.HasBlob(name))\n                self.assertTrue((workspace.FetchBlob(name) == blobs[i]).all())\n            # moved here per @cxj's suggestion\n            load_new_names = ['blob_x', 'blob_y', 'blob_z']\n            # load 'x' into 'blob_x'\n            self.assertTrue(\n                workspace.RunOperatorOnce(\n                    core.CreateOperator(\n                        \"Load\", [], load_new_names[0:1],\n                        absolute_path=1,\n                        db=os.path.join(tmp_folder, \"db\"),\n                        db_type=self._db_type,\n                        source_blob_names=new_names[0:1]\n                    )\n                )\n            )\n            # we should have 'blob_a/b/c/' and 'blob_x' now\n            self.assertEqual(len(workspace.Blobs()), 4)\n            for i, name in enumerate(load_new_names[0:1]):\n                self.assertTrue(workspace.HasBlob(name))\n                self.assertTrue((workspace.FetchBlob(name) == blobs[i]).all())\n            self.assertTrue(\n                workspace.RunOperatorOnce(\n                    core.CreateOperator(\n                        \"Load\", [], load_new_names[0:3],\n                        absolute_path=1,\n                        db=os.path.join(tmp_folder, \"db\"),\n                        db_type=self._db_type,\n                        source_blob_names=new_names[0:3]\n                    )\n                )\n            )\n            # we should have 'blob_a/b/c/' and 'blob_x/y/z' now\n            self.assertEqual(len(workspace.Blobs()), 6)\n            for i, name in enumerate(load_new_names[0:3]):\n                self.assertTrue(workspace.HasBlob(name))\n                self.assertTrue((workspace.FetchBlob(name) == blobs[i]).all())\n        finally:\n            # clean up temp folder.\n            try:\n                shutil.rmtree(tmp_folder)\n            except OSError as e:\n                if e.errno != errno.ENOENT:\n                    raise\n\n    def testMissingFile(self):\n        tmp_folder = tempfile.mkdtemp()\n        tmp_file = os.path.join(tmp_folder, \"missing_db\")\n\n        op = core.CreateOperator(\n            \"Load\",\n            [], [],\n            absolute_path=1,\n            db=tmp_file, db_type=self._db_type,\n            load_all=True)\n        with self.assertRaises(RuntimeError):\n            try:\n                workspace.RunOperatorOnce(op)\n            except RuntimeError as e:\n                print(e)\n                raise\n        try:\n            shutil.rmtree(tmp_folder)\n        except OSError as e:\n            if e.errno != errno.ENOENT:\n                raise\n\n    def testLoadMultipleFilesGivenSourceBlobNames(self):\n        tmp_folder = tempfile.mkdtemp()\n        db_file_1, arrays_1 = self.saveFile(tmp_folder, \"db1\", self._db_type, 0)\n        db_file_2, arrays_2 = self.saveFile(\n            tmp_folder, \"db2\", self._db_type, len(arrays_1)\n        )\n        db_files = [db_file_1, db_file_2]\n        blobs_names = [str(i) for i in range(len(arrays_1) + len(arrays_2))]\n\n        workspace.ResetWorkspace()\n        self.assertEqual(len(workspace.Blobs()), 0)\n        self.assertTrue(\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"Load\",\n                    [], blobs_names,\n                    absolute_path=1,\n                    dbs=db_files, db_type=self._db_type,\n                    source_blob_names=blobs_names\n                )\n            )\n        )\n        self.assertEqual(len(workspace.Blobs()), len(blobs_names))\n        for i in range(len(arrays_1)):\n            np.testing.assert_array_equal(\n                workspace.FetchBlob(str(i)), arrays_1[i]\n            )\n        for i in range(len(arrays_2)):\n            np.testing.assert_array_equal(\n                workspace.FetchBlob(str(i + len(arrays_1))), arrays_2[i]\n            )\n        try:\n            shutil.rmtree(tmp_folder)\n        except OSError as e:\n            if e.errno != errno.ENOENT:\n                raise\n\n    def testLoadAllMultipleFiles(self):\n        tmp_folder = tempfile.mkdtemp()\n        db_file_1, arrays_1 = self.saveFile(tmp_folder, \"db1\", self._db_type, 0)\n        db_file_2, arrays_2 = self.saveFile(\n            tmp_folder, \"db2\", self._db_type, len(arrays_1)\n        )\n        db_files = [db_file_1, db_file_2]\n\n        workspace.ResetWorkspace()\n        self.assertEqual(len(workspace.Blobs()), 0)\n        self.assertTrue(\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"Load\",\n                    [], [],\n                    absolute_path=1,\n                    dbs=db_files, db_type=self._db_type,\n                    load_all=True\n                )\n            )\n        )\n        self.assertEqual(len(workspace.Blobs()), len(arrays_1) + len(arrays_2))\n        for i in range(len(arrays_1)):\n            np.testing.assert_array_equal(\n                workspace.FetchBlob(str(i)), arrays_1[i]\n            )\n        for i in range(len(arrays_2)):\n            np.testing.assert_array_equal(\n                workspace.FetchBlob(str(i + len(arrays_1))), arrays_2[i]\n            )\n        try:\n            shutil.rmtree(tmp_folder)\n        except OSError as e:\n            if e.errno != errno.ENOENT:\n                raise\n\n    def testLoadAllMultipleFilesWithSameKey(self):\n        tmp_folder = tempfile.mkdtemp()\n        db_file_1, arrays_1 = self.saveFile(tmp_folder, \"db1\", self._db_type, 0)\n        db_file_2, arrays_2 = self.saveFile(tmp_folder, \"db2\", self._db_type, 0)\n\n        db_files = [db_file_1, db_file_2]\n        workspace.ResetWorkspace()\n        self.assertEqual(len(workspace.Blobs()), 0)\n        op = core.CreateOperator(\n            \"Load\",\n            [], [],\n            absolute_path=1,\n            dbs=db_files, db_type=self._db_type,\n            load_all=True)\n        with self.assertRaises(RuntimeError):\n            workspace.RunOperatorOnce(op)\n        try:\n            shutil.rmtree(tmp_folder)\n        except OSError as e:\n            if e.errno != errno.ENOENT:\n                raise\n\n    def testLoadRepeatedFiles(self):\n        tmp_folder = tempfile.mkdtemp()\n        tmp_file, arrays = self.saveFile(tmp_folder, \"db\", self._db_type, 0)\n\n        db_files = [tmp_file, tmp_file]\n        workspace.ResetWorkspace()\n        self.assertEqual(len(workspace.Blobs()), 0)\n        op = core.CreateOperator(\n            \"Load\",\n            [], [str(i) for i in range(len(arrays))],\n            absolute_path=1,\n            dbs=db_files, db_type=self._db_type,\n            load_all=False)\n        with self.assertRaises(RuntimeError):\n            workspace.RunOperatorOnce(op)\n        try:\n            shutil.rmtree(tmp_folder)\n        except OSError as e:\n            if e.errno != errno.ENOENT:\n                raise\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/loss_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestLossOps(hu.HypothesisTestCase):\n\n    @given(n=st.integers(1, 8), **hu.gcs)\n    def test_averaged_loss(self, n, gc, dc):\n        X = np.random.rand(n).astype(np.float32)\n\n        def avg_op(X):\n            return [np.mean(X)]\n\n        op = core.CreateOperator(\n            \"AveragedLoss\",\n            [\"X\"],\n            [\"y\"],\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=avg_op,\n        )\n\n        self.assertGradientChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            outputs_to_check=0,\n            outputs_with_grads=[0],\n        )\n"
  },
  {
    "path": "caffe2/python/operator_test/lpnorm_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\nfrom hypothesis import given\n\n\nclass LpnormTest(hu.HypothesisTestCase):\n    @given(inputs=hu.tensors(n=1,\n                             min_dim=1,\n                             max_dim=3,\n                             dtype=np.float32),\n           **hu.gcs_cpu_only)\n    def test_Lp_Norm(self, inputs, gc, dc):\n        X = inputs[0]\n        # avoid kinks by moving away from 0\n        X += 0.02 * np.sign(X)\n        X[X == 0.0] += 0.02\n        self.ws.create_blob(\"X\").feed(X)\n        op = core.CreateOperator(\n            'LpNorm',\n            ['X'],\n            ['l1_norm'],\n            p=1,\n        )\n        self.ws.run(op)\n\n        np.testing.assert_allclose(self.ws.blobs[(\"l1_norm\")].fetch(),\n                                     np.linalg.norm((X).flatten(), ord=1),\n                                    rtol=1e-4, atol=1e-4)\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n        # Gradient check wrt X\n        self.assertGradientChecks(gc, op, [X], 0, [0], stepsize=1e-2, threshold=1e-2)\n\n        op = core.CreateOperator(\n            'LpNorm',\n            ['X'],\n            ['l2_norm'],\n            p=2,\n        )\n        self.ws.run(op)\n\n        np.testing.assert_allclose(\n            self.ws.blobs[(\"l2_norm\")].fetch(),\n            np.linalg.norm((X).flatten(), ord=2)**2,\n            rtol=1e-4,\n            atol=1e-4\n        )\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n        # Gradient check wrt X\n        self.assertGradientChecks(gc, op, [X], 0, [0], stepsize=1e-2, threshold=1e-2)\n"
  },
  {
    "path": "caffe2/python/operator_test/map_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport itertools\nimport numpy as np\nimport tempfile\nimport unittest\nimport os\n\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestMap(hu.HypothesisTestCase):\n\n    def test_create_map(self):\n        dtypes = [core.DataType.INT32, core.DataType.INT64]\n        for key_dtype, value_dtype in itertools.product(dtypes, dtypes):\n            op = core.CreateOperator(\n                'CreateMap',\n                [],\n                ['map'],\n                key_dtype=key_dtype,\n                value_dtype=value_dtype,\n            )\n            workspace.RunOperatorOnce(op)\n            self.assertTrue(workspace.HasBlob('map'))\n\n    def test_map(self):\n\n        def test_map_func(KEY_T, VALUE_T):\n            model_file = os.path.join(tempfile.mkdtemp(), 'db')\n            key_data = np.asarray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=KEY_T)\n            value_data = np.asarray([2, 3, 3, 3, 3, 2, 3, 3, 3, 3], dtype=VALUE_T)\n            workspace.FeedBlob(\"key_data\", key_data)\n            workspace.FeedBlob(\"value_data\", value_data)\n            save_net = core.Net(\"save_net\")\n            save_net.KeyValueToMap([\"key_data\", \"value_data\"], \"map_data\")\n            save_net.Save(\n                [\"map_data\"], [],\n                db=model_file,\n                db_type=\"minidb\",\n                absolute_path=True\n            )\n            workspace.RunNetOnce(save_net)\n            workspace.ResetWorkspace()\n            load_net = core.Net(\"load_net\")\n            load_net.Load(\n                [], [\"map_data\"],\n                db=model_file,\n                db_type=\"minidb\",\n                load_all=True,\n                absolute_path=True\n            )\n            load_net.MapToKeyValue(\"map_data\", [\"key_data\", \"value_data\"])\n            workspace.RunNetOnce(load_net)\n            key_data2 = workspace.FetchBlob(\"key_data\")\n            value_data2 = workspace.FetchBlob(\"value_data\")\n            assert(set(zip(key_data, value_data)) == set(zip(key_data2, value_data2)))\n\n        test_map_func(np.int64, np.int64)\n        test_map_func(np.int64, np.int32)\n        test_map_func(np.int32, np.int32)\n        test_map_func(np.int32, np.int64)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/margin_ranking_criterion_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport numpy as np\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestMarginRankingCriterion(hu.HypothesisTestCase):\n    @given(N=st.integers(min_value=10, max_value=20),\n           seed=st.integers(min_value=0, max_value=65535),\n           margin=st.floats(min_value=-0.5, max_value=0.5),\n           **hu.gcs)\n    def test_margin_ranking_criterion(self, N, seed, margin, gc, dc):\n        np.random.seed(seed)\n        X1 = np.random.randn(N).astype(np.float32)\n        X2 = np.random.randn(N).astype(np.float32)\n        Y = np.random.choice([-1, 1], size=N).astype(np.int32)\n        op = core.CreateOperator(\n            \"MarginRankingCriterion\", [\"X1\", \"X2\", \"Y\"], [\"loss\"],\n            margin=margin)\n\n        def ref_cec(X1, X2, Y):\n            result = np.maximum(-Y * (X1 - X2) + margin, 0)\n            return (result, )\n\n        inputs = [X1, X2, Y]\n        # This checks the op implementation against a reference function in\n        # python.\n        self.assertReferenceChecks(gc, op, inputs, ref_cec)\n        # This checks the op implementation over multiple device options (e.g.\n        # CPU and CUDA). [0] means that the 0-th output is checked.\n        self.assertDeviceChecks(dc, op, inputs, [0])\n\n        # Make singular points less sensitive\n        X1[np.abs(margin - Y * (X1 - X2)) < 0.1] += 0.1\n        X2[np.abs(margin - Y * (X1 - X2)) < 0.1] -= 0.1\n\n        # Check dX1\n        self.assertGradientChecks(gc, op, inputs, 0, [0])\n        # Check dX2\n        self.assertGradientChecks(gc, op, inputs, 1, [0])\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/math_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nfrom hypothesis import strategies as st\nimport caffe2.python.hypothesis_test_util as hu\n\nimport numpy as np\nimport unittest\n\n\nclass TestMathOps(hu.HypothesisTestCase):\n\n    @given(X=hu.tensor(),\n           exponent=st.floats(min_value=2.0, max_value=3.0),\n           **hu.gcs)\n    def test_elementwise_power(self, X, exponent, gc, dc):\n        def powf(X):\n            return (X ** exponent,)\n\n        def powf_grad(g_out, outputs, fwd_inputs):\n            return (exponent * (fwd_inputs[0] ** (exponent - 1)) * g_out,)\n\n        op = core.CreateOperator(\n            \"Pow\", [\"X\"], [\"Y\"], exponent=exponent)\n\n        self.assertReferenceChecks(gc, op, [X], powf,\n                                   output_to_grad=\"Y\",\n                                   grad_reference=powf_grad),\n\n    @given(X=hu.tensor(),\n           exponent=st.floats(min_value=-3.0, max_value=3.0),\n           **hu.gcs)\n    def test_sign(self, X, exponent, gc, dc):\n        def signf(X):\n            return [np.sign(X)]\n\n        op = core.CreateOperator(\n            \"Sign\", [\"X\"], [\"Y\"])\n\n        self.assertReferenceChecks(gc, op, [X], signf),\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/matmul_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport inspect\n\nimport numpy as np\n\nfrom hypothesis import assume, given, settings\nimport hypothesis.strategies as st\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestMatMul(hu.HypothesisTestCase):\n    @given(\n        M=st.integers(min_value=1, max_value=10),\n        K=st.integers(min_value=1, max_value=10),\n        N=st.integers(min_value=1, max_value=10),\n        trans_a=st.booleans(),\n        trans_b=st.booleans(),\n        **hu.gcs\n    )\n    def test_matmul(self, M, K, N, trans_a, trans_b, gc, dc):\n        X = np.random.rand(M, K).astype(np.float32) - 0.5\n        if trans_a:\n            X = X.transpose()\n\n        Y = np.random.rand(K, N).astype(np.float32) - 0.5\n        if trans_b:\n            Y = Y.transpose()\n\n        op = core.CreateOperator(\n            'MatMul', ['X', 'Y'], 'out', trans_a=trans_a, trans_b=trans_b\n        )\n\n        def matmul_ref(X, Y, trans_a, trans_b):\n            XX = X.transpose() if trans_a else X\n            YY = Y.transpose() if trans_b else Y\n            return (XX.dot(YY), )\n\n        # Check against numpy reference\n        self.assertReferenceChecks(gc, op, [X, Y, trans_a, trans_b], matmul_ref)\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        # Gradient check wrt X\n        self.assertGradientChecks(gc, op, [X, Y], 0, [0])\n        # Gradient check wrt Y\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n    @given(\n        M=st.integers(min_value=1, max_value=10),\n        K=st.integers(min_value=1, max_value=10),\n        N=st.integers(min_value=1, max_value=10),\n        axis_a=st.sampled_from([-3, -2, -1, 1, 2, 3]),\n        axis_b=st.sampled_from([-3, -2, -1, 1, 2, 3]),\n        trans_a=st.booleans(),\n        trans_b=st.booleans(),\n        **hu.gcs\n    )\n    def test_matmul_axis(\n        self, M, K, N, axis_a, axis_b, trans_a, trans_b, gc, dc\n    ):\n        X = np.random.rand(M, K).astype(np.float32) - 0.5\n        if trans_a:\n            X = X.transpose()\n        shape_x = [X.shape[0], 1, 1, 1]\n        shape_x[axis_a] = X.shape[1]\n        X = X.reshape(*shape_x)\n\n        Y = np.random.rand(K, N).astype(np.float32) - 0.5\n        if trans_b:\n            Y = Y.transpose()\n        shape_y = [Y.shape[0], 1, 1, 1]\n        shape_y[axis_b] = Y.shape[1]\n        Y = Y.reshape(*shape_y)\n        op = core.CreateOperator(\n            'MatMul', ['X', 'Y'],\n            'out',\n            axis_a=axis_a,\n            axis_b=axis_b,\n            trans_a=trans_a,\n            trans_b=trans_b\n        )\n\n        def size_to_dim(X, axis):\n            dim = 1\n            for i in range(axis):\n                dim *= X.shape[i]\n            return dim\n\n        def size_from_dim(X, axis):\n            dim = 1\n            for i in range(axis, X.ndim):\n                dim *= X.shape[i]\n            return dim\n\n        def reshape(X, axis):\n            dim_0, dim_1 = size_to_dim(X, axis), size_from_dim(X, axis)\n            return X.reshape(dim_0, dim_1)\n\n        def canonical_axis(axis, ndim):\n            return ndim + axis if axis < 0 else axis\n\n        def matmul_ref(X, Y, axis_a, axis_b, trans_a, trans_b):\n            can_axis_a = canonical_axis(axis_a, X.ndim)\n            can_axis_b = canonical_axis(axis_b, Y.ndim)\n            X, Y = reshape(X, can_axis_a), reshape(Y, can_axis_b)\n            XX = X.transpose() if trans_a else X\n            YY = Y.transpose() if trans_b else Y\n            return (XX.dot(YY), )\n\n        # Check against numpy reference\n        self.assertReferenceChecks(\n            gc, op, [X, Y, axis_a, axis_b, trans_a, trans_b], matmul_ref\n        )\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        # Gradient check wrt X\n        self.assertGradientChecks(gc, op, [X, Y], 0, [0])\n        # Gradient check wrt Y\n        self.assertGradientChecks(gc, op, [X, Y], 1, [0])\n\n\nclass TestBatchMatMul(hu.HypothesisTestCase):\n    @settings(max_examples=30)\n    @given(\n        C=st.integers(min_value=0, max_value=3),  # number of batch dims\n        M=st.integers(min_value=1, max_value=10),\n        K=st.integers(min_value=1, max_value=10),\n        N=st.integers(min_value=1, max_value=10),\n        trans_a=st.booleans(),\n        trans_b=st.booleans(),\n        dtype=st.sampled_from([np.float32, np.float16]),\n        **hu.gcs\n    )\n    def test_batch_matmul(self, C, M, K, N, trans_a, trans_b, dtype, gc, dc):\n        if dtype == np.float16:\n            # fp16 is only supported with CUDA\n            assume(gc.device_type == caffe2_pb2.CUDA)\n            dc = [d for d in dc if d.device_type == caffe2_pb2.CUDA]\n\n        batch_dims = np.random.randint(\n            low=1,\n            high=3,\n            size=C,\n            dtype=np.int64).tolist()\n        X = np.random.rand(*(batch_dims + [M, K])).astype(dtype) - 0.5\n        if trans_a:\n            X = X.swapaxes(-1, -2)\n        Y = np.random.rand(*(batch_dims + [K, N])).astype(dtype) - 0.5\n        if trans_b:\n            Y = Y.swapaxes(-1, -2)\n\n        op = core.CreateOperator(\n            'BatchMatMul', ['X', 'Y'], 'out', trans_a=trans_a, trans_b=trans_b\n        )\n\n        def matmul_ref(X, Y, trans_a, trans_b, dtype):\n            XX = (X.swapaxes(-1, -2) if trans_a else X).astype(np.float32)\n            YY = (Y.swapaxes(-1, -2) if trans_b else Y).astype(np.float32)\n            return (np.matmul(XX, YY).astype(dtype),)\n\n        # relaxing the \"threshold\" for fp16 to 150x of the default\n        def relax_fp16_check(check_func, *args, **kwargs):\n            # inspect the default \"threshold\" value in check_func\n            argspec = inspect.getargspec(check_func)\n            threshold = argspec.defaults[\n                argspec.args.index('threshold') -\n                (len(argspec.args) - len(argspec.defaults))]\n\n            if dtype == np.float16:\n                threshold = 150 * threshold\n            check_func(*args, threshold=threshold, **kwargs)\n\n        # Check against numpy reference\n        relax_fp16_check(self.assertReferenceChecks, gc, op, [X, Y, trans_a, trans_b, dtype], matmul_ref)\n        # Check over multiple devices\n        relax_fp16_check(self.assertDeviceChecks, dc, op, [X, Y], [0])\n        # Gradient check wrt X\n        relax_fp16_check(self.assertGradientChecks, gc, op, [X, Y], 0, [0])\n        # Gradient check wrt Y\n        relax_fp16_check(self.assertGradientChecks, gc, op, [X, Y], 1, [0])\n\n    def _test_batch_matmul_with_broadcast_common(\n        self,\n        X,\n        Y,\n        dtype,\n        gc,\n        dc,\n        trans_a=None,\n        trans_b=None,\n    ):\n        if trans_a is not None and trans_b is not None:\n            op = core.CreateOperator(\n                'BatchMatMul', ['X', 'Y'], 'out', trans_a=trans_a, trans_b=trans_b, broadcast=1\n            )\n        else:\n            op = core.CreateOperator(\n                'BatchMatMul', ['X', 'Y'], 'out', broadcast=1\n            )\n\n        def matmul_ref(X, Y, trans_a, trans_b, dtype):\n            XX = (X.swapaxes(-1, -2) if trans_a else X).astype(np.float32)\n            YY = (Y.swapaxes(-1, -2) if trans_b else Y).astype(np.float32)\n            return (np.matmul(XX, YY).astype(dtype),)\n\n        # Check against numpy reference\n        self.assertReferenceChecks(gc, op, [X, Y, trans_a, trans_b, dtype], matmul_ref)\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n\n    @given(\n        C_1=st.integers(min_value=0, max_value=3),  # number of batch dims\n        C_2=st.integers(min_value=0, max_value=3),\n        M=st.integers(min_value=1, max_value=10),\n        K=st.integers(min_value=1, max_value=10),\n        N=st.integers(min_value=1, max_value=10),\n        trans_a=st.booleans(),\n        trans_b=st.booleans(),\n        **hu.gcs\n    )\n    def test_numpy_batch_matmul(self, C_1, C_2, M, K, N, trans_a, trans_b, gc, dc):\n        np.set_printoptions(threshold=np.nan)\n        dtype = np.float32\n        batch_dims = np.random.randint(\n            low=0,\n            high=3,\n            size=max(C_1, C_2),\n            dtype=np.int64).tolist()\n        lbd = len(batch_dims)\n        X = np.random.rand(*(batch_dims[lbd - C_1:] + [M, K])).astype(dtype) - 0.5\n        if trans_a:\n            X = X.swapaxes(-1, -2)\n        Y = np.random.rand(*(batch_dims[lbd - C_2:] + [K, N])).astype(dtype) - 0.5\n        if trans_b:\n            Y = Y.swapaxes(-1, -2)\n\n        self._test_batch_matmul_with_broadcast_common(X, Y, dtype, gc, dc, trans_a, trans_b)\n\n    @settings(max_examples=30)\n    @given(\n        K=st.integers(min_value=1, max_value=10),\n        **hu.gcs\n    )\n    def test_numpy_batch_matmul_1d(self, K, gc, dc):\n        np.set_printoptions(threshold=np.nan)\n        dtype = np.float32\n        X = np.random.rand(K).astype(dtype) - 0.5\n        # TODO: test trans_a and trans_b\n        Y = np.random.rand(K).astype(dtype) - 0.5\n\n        self._test_batch_matmul_with_broadcast_common(X, Y, dtype, gc, dc)\n\n    @settings(max_examples=30)\n    @given(\n        K=st.integers(min_value=1, max_value=10),\n        N=st.integers(min_value=1, max_value=10),\n        **hu.gcs\n    )\n    def test_numpy_batch_matmul_1d_2d(self, K, N, gc, dc):\n        np.set_printoptions(threshold=np.nan)\n        dtype = np.float32\n        X = np.random.rand(K).astype(dtype) - 0.5\n        # TODO: test trans_a and trans_b\n        Y = np.random.rand(*[K, N]).astype(dtype) - 0.5\n\n        self._test_batch_matmul_with_broadcast_common(X, Y, dtype, gc, dc)\n\n    @settings(max_examples=30)\n    @given(\n        M=st.integers(min_value=1, max_value=10),\n        K=st.integers(min_value=1, max_value=10),\n        **hu.gcs\n    )\n    def test_numpy_batch_matmul_2d_1d(self, M, K, gc, dc):\n        np.set_printoptions(threshold=np.nan)\n        dtype = np.float32\n        X = np.random.rand(*[M, K]).astype(dtype) - 0.5\n        # TODO: test trans_a and trans_b\n        Y = np.random.rand(K).astype(dtype) - 0.5\n\n        self._test_batch_matmul_with_broadcast_common(X, Y, dtype, gc, dc)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/mean_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport caffe2.python.hypothesis_test_util as hu\nimport numpy as np\n\nimport unittest\n\n\nclass TestMean(hu.HypothesisTestCase):\n    @given(\n        k=st.integers(1, 5),\n        n=st.integers(1, 10),\n        m=st.integers(1, 10),\n        in_place=st.booleans(),\n        seed=st.integers(0, 2**32 - 1),\n        **hu.gcs\n    )\n    def test_mean(self, k, n, m, in_place, seed, gc, dc):\n        np.random.seed(seed)\n        input_names = []\n        input_vars = []\n\n        for i in range(k):\n            X_name = 'X' + str(i)\n            input_names.append(X_name)\n            var = np.random.randn(n, m).astype(np.float32)\n            input_vars.append(var)\n\n        def mean_ref(*args):\n            return [np.mean(args, axis=0)]\n\n        op = core.CreateOperator(\n            \"Mean\",\n            input_names,\n            ['Y' if not in_place else 'X0'],\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=input_vars,\n            reference=mean_ref,\n        )\n\n        self.assertGradientChecks(\n            device_option=gc,\n            op=op,\n            inputs=input_vars,\n            outputs_to_check=0,\n            outputs_with_grads=[0],\n        )\n\n        self.assertDeviceChecks(dc, op, input_vars, [0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n\n"
  },
  {
    "path": "caffe2/python/operator_test/merge_id_lists_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\nimport hypothesis.extra.numpy as hnp\n\n\n@st.composite\ndef id_list_batch(draw):\n    num_inputs = draw(st.integers(1, 3))\n    batch_size = draw(st.integers(5, 10))\n    values_dtype = draw(st.sampled_from([np.int32, np.int64]))\n    inputs = []\n    for _ in range(num_inputs):\n        size = draw(st.integers(5, 10))\n        values = draw(hnp.arrays(values_dtype, size, st.integers(1, 10)))\n        lengths = draw(hu.lengths(len(values),\n                                  min_segments=batch_size,\n                                  max_segments=batch_size))\n        inputs.append(lengths)\n        inputs.append(values)\n    return inputs\n\n\ndef merge_id_lists_ref(*args):\n    n = len(args)\n    assert n > 0\n    assert n % 2 == 0\n    batch_size = len(args[0])\n    num_inputs = int(n / 2)\n    lengths = np.array([np.insert(args[2 * i], 0, 0)\n                        for i in range(num_inputs)])\n    values = [args[2 * i + 1] for i in range(num_inputs)]\n    offsets = [np.cumsum(lengths[j]) for j in range(num_inputs)]\n\n    def merge_arrays(vs, offs, j):\n        concat = np.concatenate([vs[i][offs[i][j]:offs[i][j + 1]]\n                                for i in range(num_inputs)])\n        return np.sort(np.unique(concat))\n\n    merged = [merge_arrays(values, offsets, j) for j in range(batch_size)]\n    merged_lengths = np.array([len(x) for x in merged])\n    merged_values = np.concatenate(merged)\n    return merged_lengths, merged_values\n\n\nclass TestMergeIdListsOp(hu.HypothesisTestCase):\n    def test_merge_id_lists_ref(self):\n        # Verify that the reference implementation is correct!\n        lengths_0 = np.array([3, 0, 4], dtype=np.int32)\n        values_0 = np.array([1, 5, 6, 2, 4, 5, 6], dtype=np.int64)\n        lengths_1 = np.array([3, 2, 1], dtype=np.int32)\n        values_1 = np.array([5, 8, 9, 14, 9, 5], dtype=np.int64)\n\n        merged_lengths, merged_values = merge_id_lists_ref(\n            lengths_0, values_0, lengths_1, values_1)\n        expected_lengths = np.array([5, 2, 4], dtype=np.int32)\n        expected_values = np.array([1, 5, 6, 8, 9, 9, 14, 2, 4, 5, 6], dtype=np.int64)\n\n        np.testing.assert_array_equal(merged_lengths, expected_lengths)\n        np.testing.assert_array_equal(merged_values, expected_values)\n\n    @given(inputs=id_list_batch(),\n           **hu.gcs_cpu_only)\n    def test_merge_id_lists_op(self, inputs, gc, dc):\n        num_inputs = int(len(inputs) / 2)\n        op = core.CreateOperator(\n            \"MergeIdLists\",\n            [\"{prefix}_{i}\".format(prefix=p, i=i)\n                for i in range(num_inputs)\n                for p in [\"lengths\", \"values\"]],\n            [\"merged_lengths\", \"merged_values\"]\n        )\n        self.assertDeviceChecks(dc, op, inputs, [0])\n        self.assertReferenceChecks(gc, op, inputs, merge_id_lists_ref)\n"
  },
  {
    "path": "caffe2/python/operator_test/mkl_conv_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given, settings\nimport numpy as np\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn,\n                 \"Skipping as we do not have mkldnn.\")\nclass MKLConvTest(hu.HypothesisTestCase):\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(3, 5),\n           size=st.integers(8, 8),\n           input_channels=st.integers(1, 3),\n           output_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           **mu.gcs)\n    @settings(max_examples=2, timeout=100)\n    def test_mkl_convolution(self, stride, pad, kernel, size,\n                             input_channels, output_channels,\n                             batch_size, gc, dc):\n        op = core.CreateOperator(\n            \"Conv\",\n            [\"X\", \"w\", \"b\"],\n            [\"Y\"],\n            stride=stride,\n            pad=pad,\n            kernel=kernel,\n        )\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n        w = np.random.rand(\n                output_channels, input_channels, kernel, kernel) \\\n            .astype(np.float32) - 0.5\n        b = np.random.rand(output_channels).astype(np.float32) - 0.5\n\n        inputs = [X, w, b]\n        self.assertDeviceChecks(dc, op, inputs, [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/mkl_packed_fc_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport hypothesis.strategies as st\nfrom hypothesis import given\nimport numpy as np\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\n@unittest.skipIf(not core.IsOperator(\"PackedFC\"),\n                 \"PackedFC is not supported in this caffe2 build.\")\nclass PackedFCTest(hu.HypothesisTestCase):\n    @given(seed=st.integers(0, 65536),\n           M=st.integers(16, 32),\n           K=st.integers(128, 1024),\n           N=st.integers(128, 1024),\n           **hu.gcs_cpu_only)\n    @unittest.skipIf(not core.C.builtin_cpu_supports_avx2(),\n                     \"Intel MKL sgemm_pack has a known numerical issue with \"\n                     \"non-avx2 machines that will be fixed in a later build.\")\n    def test_packed_fc(self, seed, M, K, N, gc, dc):\n        np.random.seed(seed)\n        X = np.random.rand(M, K).astype(np.float32) - 0.5\n        W = np.random.rand(N, K).astype(np.float32) - 0.5\n        b = np.random.rand(N).astype(np.float32) - 0.5\n\n        # If you are debugging, the following hard-coded ones might help.\n        # X = np.ones((24, 256)).astype(np.float32)\n        # W = np.ones((128, 256)).astype(np.float32)\n        # b = np.zeros(128).astype(np.float32)\n\n        def ref(X, W, b):\n            return (np.dot(X, W.T) + b,)\n\n        for name in [\"FC\", \"PackedFC\"]:\n            op = core.CreateOperator(\n                name,\n                [\"X\", \"W\", \"b\"],\n                [\"Y\"],\n            )\n            self.assertReferenceChecks(gc, op, [X, W, b], ref)\n\n    @unittest.skipIf(not core.C.builtin_cpu_supports_avx2(),\n                     \"Intel MKL sgemm_pack has a known numerical issue with \"\n                     \"non-avx2 machines that will be fixed in a later build.\")\n    @given(axis=st.integers(min_value=1, max_value=4),\n           num_output=st.integers(min_value=4, max_value=8),\n           **hu.gcs_cpu_only)\n    def test_packed_fc_axis(self, axis, num_output, gc, dc):\n        np.random.seed(1701)\n        X = np.random.randn(1, 2, 3, 2, 1).astype(np.float32)\n        K = np.prod(X.shape[axis:])\n        N = num_output\n        W = np.random.randn(N, K).astype(np.float32)\n        b = np.random.randn(N).astype(np.float32)\n\n        op = core.CreateOperator(\n            \"PackedFC\",\n            [\"X\", \"W\", \"b\"],\n            [\"Y\"],\n            axis=axis)\n\n        def ref(X, W, b):\n            output_axes = list(X.shape[:axis]) + [N]\n            return (\n                np.dot(X.reshape(int(X.size / K), K), W.T).reshape(output_axes) + b,)\n\n        self.assertReferenceChecks(gc, op, [X, W, b], ref)\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/mkl_speed_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport unittest\n\nimport numpy as np\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core, workspace, test_util\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn, \"Skipping as we do not have mkldnn.\")\nclass TestMKLBasic(test_util.TestCase):\n    def testReLUSpeed(self):\n        X = np.random.randn(128, 4096).astype(np.float32)\n        mkl_do = core.DeviceOption(caffe2_pb2.MKLDNN)\n        # Makes sure that feed works.\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"X_mkl\", X, device_option=mkl_do)\n        net = core.Net(\"test\")\n        # Makes sure that we can run relu.\n        net.Relu(\"X\", \"Y\")\n        net.Relu(\"X_mkl\", \"Y_mkl\", device_option=mkl_do)\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n        # makes sure that the results are good.\n        np.testing.assert_allclose(\n            workspace.FetchBlob(\"Y\"),\n            workspace.FetchBlob(\"Y_mkl\"),\n            atol=1e-10,\n            rtol=1e-10)\n        runtime = workspace.BenchmarkNet(net.Proto().name, 1, 100, True)\n\n        # The returned runtime is the time of\n        # [whole_net, cpu_op, mkl_op]\n        # so we will assume that the MKL one runs faster than the CPU one.\n\n        # Note(Yangqing): in fact, it seems that in optimized mode, this is\n        # not always guaranteed - MKL runs slower than the Eigen vectorized\n        # version, so I am turning this assertion off.\n        #self.assertTrue(runtime[1] >= runtime[2])\n\n        print(\"Relu CPU runtime {}, MKL runtime {}.\".format(runtime[1], runtime[2]))\n\n    # Note(Zhicheng): Disable the test below we implement to use\n    # RegisterTensorInfoFunction to register for Tensor<MKLContext>\n\n    # def testConvSpeed(self):\n    #     # We randomly select a shape to test the speed. Intentionally we\n    #     # test a batch size of 1 since this may be the most frequent use\n    #     # case for MKL during deployment time.\n    #     X = np.random.rand(1, 256, 27, 27).astype(np.float32) - 0.5\n    #     W = np.random.rand(192, 256, 3, 3).astype(np.float32) - 0.5\n    #     b = np.random.rand(192).astype(np.float32) - 0.5\n    #     mkl_do = core.DeviceOption(caffe2_pb2.MKLDNN)\n    #     # Makes sure that feed works.\n    #     workspace.FeedBlob(\"X\", X)\n    #     workspace.FeedBlob(\"W\", W)\n    #     workspace.FeedBlob(\"b\", b)\n    #     workspace.FeedBlob(\"X_mkl\", X, device_option=mkl_do)\n    #     workspace.FeedBlob(\"W_mkl\", W, device_option=mkl_do)\n    #     workspace.FeedBlob(\"b_mkl\", b, device_option=mkl_do)\n    #     net = core.Net(\"test\")\n    #     # Makes sure that we can run relu.\n    #     net.Conv([\"X\", \"W\", \"b\"], \"Y\", pad=1, stride=1, kernel=3)\n    #     net.Conv([\"X_mkl\", \"W_mkl\", \"b_mkl\"], \"Y_mkl\",\n    #              pad=1, stride=1, kernel=3, device_option=mkl_do)\n    #     workspace.CreateNet(net)\n    #     workspace.RunNet(net)\n    #     # makes sure that the results are good.\n    #     np.testing.assert_allclose(\n    #         workspace.FetchBlob(\"Y\"),\n    #         workspace.FetchBlob(\"Y_mkl\"),\n    #         atol=1e-2,\n    #         rtol=1e-2)\n    #     runtime = workspace.BenchmarkNet(net.Proto().name, 1, 100, True)\n    #\n    #     print(\"Conv CPU runtime {}, MKL runtime {}.\".format(runtime[1], runtime[2]))\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/mod_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy\n\nfrom caffe2.python import core\nfrom hypothesis import given\n\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\n@st.composite\ndef _data(draw):\n    return draw(\n        hu.tensor(dtype=np.int64,\n            elements=st.integers(\n                min_value=np.iinfo(np.int64).min, max_value=np.iinfo(np.int64).max\n            )\n        )\n    )\n\n\nclass TestMod(hu.HypothesisTestCase):\n    @given(\n        data=_data(),\n        divisor=st.integers(\n            min_value=np.iinfo(np.int64).min, max_value=np.iinfo(np.int64).max\n        ),\n        inplace=st.booleans(),\n        sign_follow_divisor=st.booleans(),\n        **hu.gcs_cpu_only\n    )\n    def test_mod(\n        self, data, divisor, inplace, sign_follow_divisor, gc, dc\n    ):\n        if divisor == 0:\n            # invalid test case\n            return None\n\n        def ref(data):\n            if sign_follow_divisor:\n                output = data % divisor\n            else:\n                output = numpy.fmod(data, divisor)\n            return [output]\n\n        op = core.CreateOperator(\n            'Mod',\n            ['data'],\n            ['data' if inplace else 'output'],\n            divisor=divisor,\n            sign_follow_divisor=sign_follow_divisor\n        )\n\n        self.assertReferenceChecks(gc, op, [data], ref)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/momentum_sgd_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\n\nimport hypothesis\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport numpy as np\nimport unittest\n\n\nclass TestMomentumSGD(hu.HypothesisTestCase):\n    @given(n=st.integers(4, 8), nesterov=st.booleans(), **hu.gcs)\n    def test_momentum_sgd(self, n, nesterov, gc, dc):\n        param = np.random.rand(n).astype(np.float32)\n        grad = np.random.rand(n).astype(np.float32)\n        lr = np.random.rand(1).astype(np.float32)\n        param_momentum = np.random.rand(n).astype(np.float32)\n        momentum = 0.9\n\n        def momentum_sgd(grad, param_momentum, lr, param=None):\n            if not nesterov:\n                adjusted_gradient = lr * grad + momentum * param_momentum\n                if param is None:\n                    return [adjusted_gradient, adjusted_gradient]\n                else:\n                    paramup = param - adjusted_gradient\n                    return [adjusted_gradient, adjusted_gradient, paramup]\n            else:\n                m_new = momentum * param_momentum + lr * grad\n                grad_new = (1 + momentum) * m_new - momentum * param_momentum\n                if param is None:\n                    return [grad_new, m_new]\n                else:\n                    paramup = param - grad_new\n                    return [grad_new, m_new, paramup]\n\n        op = core.CreateOperator(\n            \"MomentumSGDUpdate\",\n            [\"grad\", \"param_momentum\", \"lr\", \"param\"],\n            [\"grad\", \"param_momentum\", \"param\"],\n            momentum=momentum,\n            nesterov=int(nesterov),\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[grad, param_momentum, lr, param],\n            reference=momentum_sgd\n        )\n\n        op_noparam = core.CreateOperator(\n            \"MomentumSGD\",\n            [\"grad\", \"param_momentum\", \"lr\"],\n            [\"grad\", \"param_momentum\"],\n            momentum=momentum,\n            nesterov=int(nesterov),\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op_noparam,\n            inputs=[grad, param_momentum, lr],\n            reference=momentum_sgd\n        )\n\n    @given(\n        inputs=hu.tensors(n=3),\n        momentum=st.floats(min_value=0.1, max_value=0.9),\n        nesterov=st.booleans(),\n        lr=st.floats(min_value=0.1, max_value=0.9),\n        data_strategy=st.data(),\n        **hu.gcs\n    )\n    def test_sparse_momentum_sgd(\n        self, inputs, momentum, nesterov, lr, data_strategy, gc, dc\n    ):\n        w, grad, m = inputs\n\n        # Create an indexing array containing values which index into grad\n        indices = data_strategy.draw(\n            hu.tensor(\n                max_dim=1,\n                min_value=1,\n                max_value=grad.shape[0],\n                dtype=np.int64,\n                elements=st.sampled_from(np.arange(grad.shape[0])),\n            ),\n        )\n\n        # Verify that the generated indices are unique\n        hypothesis.assume(\n            np.array_equal(\n                np.unique(indices.flatten()),\n                np.sort(indices.flatten())))\n\n        # Sparsify grad\n        grad = grad[indices]\n\n        # Make momentum >= 0\n        m = np.abs(m)\n\n        # Convert lr to a numpy array\n        lr = np.asarray([lr], dtype=np.float32)\n\n        op = core.CreateOperator(\n            \"SparseMomentumSGDUpdate\", [\"grad\", \"m\", \"lr\", \"param\", \"indices\"],\n            [\"adjusted_grad\", \"m\", \"param\"],\n            momentum=momentum,\n            nesterov=int(nesterov),\n            device_option=gc\n        )\n\n        # Reference\n        def momentum_sgd(grad, m, lr):\n            lr = lr[0]\n            if not nesterov:\n                adjusted_gradient = lr * grad + momentum * m\n                return (adjusted_gradient, adjusted_gradient)\n            else:\n                m_new = momentum * m + lr * grad\n                return ((1 + momentum) * m_new - momentum * m, m_new)\n\n        def sparse(grad, m, lr, param, i):\n            grad_new, m_new = momentum_sgd(grad, m[i], lr)\n            m[i] = m_new\n            param[i] -= grad_new\n            return (grad_new, m, param)\n\n        self.assertReferenceChecks(\n            gc,\n            op,\n            [grad, m, lr, w, indices],\n            sparse)\n\n    @given(n=st.integers(4, 8), nesterov=st.booleans(), **hu.gcs_gpu_only)\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support.\")\n    def test_fp16momentum_sgd(self, n, nesterov, gc, dc):\n        gpuvers = workspace.GetDeviceProperties(0)[\"major\"]\n        if gpuvers < 6:\n            print(\"No FP16 support because major version {} < 6\".format(gpuvers))\n            return\n\n        param = np.random.rand(n).astype(np.float16)\n        grad = np.random.rand(n).astype(np.float16)\n        lr = np.random.rand(1).astype(np.float32)\n        param_momentum = np.random.rand(n).astype(np.float16)\n        momentum = 0.9\n        nesterov = True\n\n        def momentum_sgd(grad, param_momentum, lr, param=None):\n            if not nesterov:\n                adjusted_gradient = lr * grad + momentum * param_momentum\n                paramup = param - adjusted_gradient\n                return [adjusted_gradient, adjusted_gradient, paramup]\n            else:\n                m_new = momentum * param_momentum + lr * grad\n                grad_new = (1 + momentum) * m_new - momentum * param_momentum\n                paramup = param - grad_new\n                return [grad_new, m_new, paramup]\n\n        op = core.CreateOperator(\n            \"FP16MomentumSGDUpdate\",\n            [\"grad\", \"param_momentum\", \"lr\", \"param\"],\n            [\"grad\", \"param_momentum\", \"param\"],\n            momentum=momentum,\n            nesterov=int(nesterov),\n            weight_decay=0.0,\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[grad, param_momentum, lr, param],\n            reference=momentum_sgd\n        )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/mpi_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nimport numpy as np\nimport unittest\n\nfrom caffe2.python import core, workspace, dyndep\nimport caffe2.python.hypothesis_test_util as hu\n\ndyndep.InitOpsLibrary(\"@/caffe2/caffe2/mpi:mpi_ops\")\n\n_has_mpi =False\nCOMM = None\nRANK = 0\nSIZE = 0\n\ndef SetupMPI():\n    try:\n        from mpi4py import MPI\n        global _has_mpi, COMM, RANK, SIZE\n        _has_mpi = core.IsOperatorWithEngine(\"CreateCommonWorld\", \"MPI\")\n        COMM = MPI.COMM_WORLD\n        RANK = COMM.Get_rank()\n        SIZE = COMM.Get_size()\n    except ImportError:\n        _has_mpi = False\n\n\n@unittest.skipIf(not _has_mpi,\n                 \"MPI is not available. Skipping.\")\nclass TestMPI(hu.HypothesisTestCase):\n    @given(X=hu.tensor(),\n           root=st.integers(min_value=0, max_value=SIZE - 1),\n           device_option=st.sampled_from(hu.device_options),\n           **hu.gcs)\n    def test_broadcast(self, X, root, device_option, gc, dc):\n        # Use mpi4py's broadcast to make sure that all nodes inherit the\n        # same hypothesis test.\n        X = COMM.bcast(X)\n        root = COMM.bcast(root)\n        device_option = COMM.bcast(device_option)\n        X[:] = RANK\n        self.assertTrue(\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"CreateCommonWorld\", [], \"comm\", engine=\"MPI\",\n                    device_option=device_option)))\n        self.assertTrue(workspace.FeedBlob(\"X\", X, device_option))\n        mpi_op = core.CreateOperator(\n            \"Broadcast\", [\"comm\", \"X\"], \"X\", engine=\"MPI\", root=root,\n            device_option=device_option)\n        self.assertTrue(workspace.RunOperatorOnce(mpi_op))\n        new_X = workspace.FetchBlob(\"X\")\n        np.testing.assert_array_equal(new_X, root)\n        workspace.ResetWorkspace()\n\n    @given(X=hu.tensor(),\n           root=st.integers(min_value=0, max_value=SIZE - 1),\n           device_option=st.sampled_from(hu.device_options),\n           **hu.gcs)\n    def test_reduce(self, X, root, device_option, gc, dc):\n        # Use mpi4py's broadcast to make sure that all nodes inherit the\n        # same hypothesis test.\n        X = COMM.bcast(X)\n        root = COMM.bcast(root)\n        device_option = COMM.bcast(device_option)\n        X[:] = RANK\n        self.assertTrue(\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"CreateCommonWorld\", [], \"comm\", engine=\"MPI\",\n                    device_option=device_option)))\n        self.assertTrue(workspace.FeedBlob(\"X\", X, device_option))\n        mpi_op = core.CreateOperator(\n            \"Reduce\", [\"comm\", \"X\"], \"X_reduced\", engine=\"MPI\", root=root,\n            device_option=device_option)\n        self.assertTrue(workspace.RunOperatorOnce(mpi_op))\n        if (RANK == root):\n            new_X = workspace.FetchBlob(\"X\")\n            np.testing.assert_array_equal(new_X, root)\n        workspace.ResetWorkspace()\n\n    @given(X=hu.tensor(),\n           root=st.integers(min_value=0, max_value=SIZE - 1),\n           device_option=st.sampled_from(hu.device_options),\n           inplace=st.booleans(),\n           **hu.gcs)\n    def test_allreduce(self, X, root, device_option, inplace, gc, dc):\n        # Use mpi4py's broadcast to make sure that all nodes inherit the\n        # same hypothesis test.\n        X = COMM.bcast(X)\n        root = COMM.bcast(root)\n        device_option = COMM.bcast(device_option)\n        inplace = COMM.bcast(inplace)\n        X[:] = RANK\n        self.assertTrue(\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"CreateCommonWorld\", [], \"comm\", engine=\"MPI\",\n                    device_option=device_option)))\n        # Use mpi4py's broadcast to make sure that all copies have the same\n        # tensor size.\n        X = COMM.bcast(X)\n        X[:] = RANK\n        self.assertTrue(workspace.FeedBlob(\"X\", X, device_option))\n        mpi_op = core.CreateOperator(\n            \"Allreduce\", [\"comm\", \"X\"],\n            \"X\" if inplace else \"X_reduced\",\n            engine=\"MPI\", root=root,\n            device_option=device_option)\n        self.assertTrue(workspace.RunOperatorOnce(mpi_op))\n        new_X = workspace.FetchBlob(\"X\" if inplace else \"X_reduced\")\n        np.testing.assert_array_equal(new_X, SIZE * (SIZE - 1) / 2)\n        workspace.ResetWorkspace()\n\n    @given(X=hu.tensor(),\n           device_option=st.sampled_from(hu.device_options),\n           specify_send_blob=st.booleans(),\n           specify_recv_blob=st.booleans(),\n           **hu.gcs)\n    def test_sendrecv(\n            self, X, device_option, specify_send_blob, specify_recv_blob,\n            gc, dc):\n        # Use mpi4py's broadcast to make sure that all nodes inherit the\n        # same hypothesis test.\n        X = COMM.bcast(X)\n        device_option = COMM.bcast(device_option)\n        specify_send_blob = COMM.bcast(specify_send_blob)\n        specify_recv_blob = COMM.bcast(specify_recv_blob)\n        X[:] = RANK\n\n        self.assertTrue(\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"CreateCommonWorld\", [], \"comm\", engine=\"MPI\",\n                    device_option=device_option)))\n        self.assertTrue(workspace.FeedBlob(\"X\", X, device_option))\n        for src in range(SIZE):\n            for dst in range(SIZE):\n                tag = src * SIZE + dst\n                if src == dst:\n                    continue\n                elif RANK == src:\n                    X[:] = RANK\n                    self.assertTrue(workspace.FeedBlob(\"X\", X, device_option))\n                    if specify_send_blob:\n                        self.assertTrue(workspace.FeedBlob(\n                            \"dst\", np.array(dst, dtype=np.int32)))\n                        self.assertTrue(workspace.FeedBlob(\n                            \"tag\", np.array(tag, dtype=np.int32)))\n                        mpi_op = core.CreateOperator(\n                            \"SendTensor\", [\"comm\", \"X\", \"dst\", \"tag\"], [],\n                            engine=\"MPI\", raw_buffer=True,\n                            device_option=device_option)\n                    else:\n                        mpi_op = core.CreateOperator(\n                            \"SendTensor\", [\"comm\", \"X\"], [], engine=\"MPI\",\n                            dst=dst, tag=tag, raw_buffer=True,\n                            device_option=device_option)\n                    self.assertTrue(workspace.RunOperatorOnce(mpi_op))\n                elif RANK == dst:\n                    if specify_recv_blob:\n                        self.assertTrue(workspace.FeedBlob(\n                            \"src\", np.array(src, dtype=np.int32)))\n                        self.assertTrue(workspace.FeedBlob(\n                            \"tag\", np.array(tag, dtype=np.int32)))\n                        mpi_op = core.CreateOperator(\n                            \"ReceiveTensor\", [\"comm\", \"X\", \"src\", \"tag\"],\n                            [\"X\", \"src\", \"tag\"],\n                            engine=\"MPI\",\n                            src=src, tag=tag, raw_buffer=True,\n                            device_option=device_option)\n                    else:\n                        mpi_op = core.CreateOperator(\n                            \"ReceiveTensor\", [\"comm\", \"X\"], [\"X\", \"src\", \"tag\"],\n                            engine=\"MPI\",\n                            src=src, tag=tag, raw_buffer=True,\n                            device_option=device_option)\n                    self.assertTrue(workspace.RunOperatorOnce(mpi_op))\n                    received = workspace.FetchBlob(\"X\")\n                    np.testing.assert_array_equal(received, src)\n                    src_blob = workspace.FetchBlob(\"src\")\n                    np.testing.assert_array_equal(src_blob, src)\n                    tag_blob = workspace.FetchBlob(\"tag\")\n                    np.testing.assert_array_equal(tag_blob, tag)\n                # simply wait for the guys to finish\n                COMM.barrier()\n        workspace.ResetWorkspace()\n\nif __name__ == \"__main__\":\n    SetupMPI()\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/negate_gradient_op_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport caffe2.python.hypothesis_test_util as hu\nfrom caffe2.python import workspace, core\n\n\nclass TestNegateGradient(hu.HypothesisTestCase):\n\n    @given(X=hu.tensor(),\n           inplace=st.booleans(),\n            **hu.gcs)\n    def test_forward(self, X, inplace, gc, dc):\n        def neg_grad_ref(X):\n            return (X,)\n\n        op = core.CreateOperator(\"NegateGradient\", [\"X\"], [\"Y\" if not inplace else \"X\"])\n        self.assertReferenceChecks(gc, op, [X], neg_grad_ref)\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n    @given(size=st.lists(st.integers(min_value=1, max_value=20),\n                         min_size=1, max_size=5))\n    def test_grad(self, size):\n        X = np.random.random_sample(size)\n        workspace.ResetWorkspace()\n        workspace.FeedBlob(\"X\", X.astype(np.float32))\n\n        net = core.Net(\"negate_grad_test\")\n        Y = net.NegateGradient([\"X\"], [\"Y\"])\n\n        grad_map = net.AddGradientOperators([Y])\n        workspace.RunNetOnce(net)\n\n        # check X_grad == negate of Y_grad\n        x_val, y_val = workspace.FetchBlobs(['X', 'Y'])\n        x_grad_val, y_grad_val = workspace.FetchBlobs([grad_map['X'],\n                                                        grad_map['Y']])\n        np.testing.assert_array_equal(x_val, y_val)\n        np.testing.assert_array_equal(x_grad_val, y_grad_val * (-1))\n"
  },
  {
    "path": "caffe2/python/operator_test/ngram_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core, workspace\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\n\nimport numpy as np\n\n\nclass TestNGramOps(hu.HypothesisTestCase):\n    @given(\n        seed=st.integers(0, 2**32 - 1),\n        N=st.integers(min_value=10, max_value=100),\n        D=st.integers(min_value=2, max_value=10),\n        out_of_vcb=st.floats(min_value=0, max_value=0.5),\n        max_categorical_limit=st.integers(min_value=5, max_value=20),\n        max_in_vcb_val=st.integers(min_value=1000, max_value=10000),\n        **hu.gcs_cpu_only\n    )\n    def test_ngram_from_categorical_op(\n        self,\n        seed,\n        N,\n        D,\n        out_of_vcb,\n        max_categorical_limit,\n        max_in_vcb_val,\n        gc,\n        dc,\n    ):\n        np.random.seed(seed)\n        col_num = max(int(D / 2), 1)\n        col_ids = np.random.choice(D, col_num, False).astype(np.int32)\n        categorical_limits = np.random.randint(\n            2, high=max_categorical_limit, size=col_num\n        ).astype(np.int32)\n        vcb = [\n            np.random.choice(max_in_vcb_val, x, False)\n            for x in categorical_limits\n        ]\n        vals = np.array([x for l in vcb for x in l], dtype=np.int32)\n\n        # Enforce round(floats) to be negative.\n        floats = np.random.rand(N, D).astype(np.float32) - 2\n        expected_output = []\n        for i in range(N):\n            val = 0\n            for (k, j) in enumerate(col_ids):\n                base = np.prod(categorical_limits[:k])\n                r = np.random.randint(categorical_limits[k])\n                p = np.random.rand()\n                if p > out_of_vcb:\n                    val += base * r\n                    floats[i][j] = vcb[k][r]\n            expected_output.append(val)\n        expected_output = np.array(expected_output, dtype=np.int32)\n\n        workspace.ResetWorkspace()\n        workspace.FeedBlob('floats', floats)\n        op = core.CreateOperator(\n            \"NGramFromCategorical\",\n            ['floats'],\n            ['output'],\n            col_ids=col_ids,\n            categorical_limits=categorical_limits,\n            vals=vals,\n        )\n        workspace.RunOperatorOnce(op)\n        output = workspace.blobs['output']\n        np.testing.assert_array_equal(output, expected_output)\n"
  },
  {
    "path": "caffe2/python/operator_test/normalize_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport functools\n\nimport numpy as np\nfrom hypothesis import given\nimport hypothesis.strategies as st\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestNormalizeOp(hu.HypothesisTestCase):\n\n    @given(X=hu.tensor(min_dim=1,\n                       max_dim=5,\n                       elements=st.floats(min_value=0.5, max_value=1.0)),\n           **hu.gcs)\n    def test_normalize(self, X, gc, dc):\n        def ref_normalize(X, axis):\n            x_normed = X / (\n                np.sqrt((X**2).sum(axis=axis, keepdims=True)) + np.finfo(X.dtype).tiny)\n            return (x_normed,)\n\n        for axis in range(-X.ndim, X.ndim):\n            op = core.CreateOperator(\"Normalize\", \"X\", \"Y\", axis=axis)\n            self.assertReferenceChecks(\n                gc,\n                op,\n                [X],\n                functools.partial(ref_normalize, axis=axis))\n            self.assertDeviceChecks(dc, op, [X], [0])\n            self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @given(X=hu.tensor(min_dim=1,\n                       max_dim=5,\n                       elements=st.floats(min_value=0.5, max_value=1.0)),\n           **hu.gcs)\n    def test_normalize_L1(self, X, gc, dc):\n        def ref(X, axis):\n            norm = abs(X).sum(axis=axis, keepdims=True)\n            return (X / norm,)\n\n        for axis in range(-X.ndim, X.ndim):\n            print('axis: ', axis)\n            op = core.CreateOperator(\"NormalizeL1\", \"X\", \"Y\", axis=axis)\n            self.assertReferenceChecks(\n                gc,\n                op,\n                [X],\n                functools.partial(ref, axis=axis))\n            self.assertDeviceChecks(dc, op, [X], [0])\n"
  },
  {
    "path": "caffe2/python/operator_test/one_hot_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core\nfrom caffe2.proto import caffe2_pb2\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\ndef _one_hots():\n    index_size = st.integers(min_value=1, max_value=5)\n    lengths = st.lists(\n        elements=st.integers(min_value=0, max_value=5))\n    return st.tuples(index_size, lengths).flatmap(\n        lambda x: st.tuples(\n            st.just(x[0]),\n            st.just(x[1]),\n            st.lists(\n                elements=st.integers(min_value=0, max_value=x[0] - 1),\n                min_size=sum(x[1]),\n                max_size=sum(x[1]))))\n\n\nclass TestOneHotOps(hu.HypothesisTestCase):\n    @given(\n        x=hu.tensor(\n            min_dim=2, max_dim=2, dtype=np.int32,\n            elements=st.integers(min_value=0, max_value=10)),\n        **hu.gcs_cpu_only)\n    def test_batch_one_hot(self, x, gc, dc):\n        d = x.shape[1]\n        lens = []\n        vals = []\n        for i in range(0, d):\n            val = np.unique(x[:, i])\n            vals.extend(val)\n            lens.append(len(val))\n        lens = np.array(lens, dtype=np.int32)\n        vals = np.array(vals, dtype=np.int32)\n\n        def ref(x, lens, vals):\n            output_dim = vals.size\n            ret = np.zeros((x.shape[0], output_dim)).astype(x.dtype)\n            p = 0\n            for i, l in enumerate(lens):\n                for j in range(0, l):\n                    v = vals[p + j]\n                    ret[x[:, i] == v, p + j] = 1\n                p += lens[i]\n            return (ret, )\n\n        op = core.CreateOperator('BatchOneHot', [\"X\", \"LENS\", \"VALS\"], [\"Y\"])\n        self.assertReferenceChecks(gc, op, [x, lens, vals], ref)\n\n    @given(\n        x=hu.tensor(\n            min_dim=2, max_dim=2, dtype=np.float32,\n            elements=st.floats(min_value=-5, max_value=5)),\n        **hu.gcs_cpu_only)\n    def test_batch_bucketized_one_hot(self, x, gc, dc):\n        d = x.shape[1]\n        lens = np.random.randint(low=1, high=5, size=d)\n        boundaries = []\n        for i in range(d):\n            cur_boundary = np.random.randn(lens[i]) * 5\n            cur_boundary.sort()\n            boundaries += cur_boundary.tolist()\n\n        lens = np.array(lens, dtype=np.int32)\n        boundaries = np.array(boundaries, dtype=np.float32)\n\n        def ref(x, lens, boundaries):\n            output_dim = lens.size + boundaries.size\n            ret = np.zeros((x.shape[0], output_dim)).astype(x.dtype)\n            boundary_offset = 0\n            output_offset = 0\n            for i, l in enumerate(lens):\n                bucket_idx = np.digitize(\n                    x[:, i],\n                    boundaries[boundary_offset:boundary_offset + l],\n                    right=True\n                )\n                for j in range(x.shape[0]):\n                    ret[j, output_offset + bucket_idx[j]] = 1.0\n                boundary_offset += lens[i]\n                output_offset += (lens[i] + 1)\n            return (ret, )\n\n        op = core.CreateOperator('BatchBucketOneHot',\n                                 [\"X\", \"LENS\", \"BOUNDARIES\"], [\"Y\"])\n        self.assertReferenceChecks(gc, op, [x, lens, boundaries], ref)\n\n    @given(\n        hot_indices=hu.tensor(\n            min_dim=1, max_dim=1, dtype=np.int64,\n            elements=st.integers(min_value=0, max_value=42)),\n        end_padding=st.integers(min_value=0, max_value=2),\n        **hu.gcs)\n    def test_one_hot(self, hot_indices, end_padding, gc, dc):\n\n        def one_hot_ref(hot_indices, size):\n            out = np.zeros([len(hot_indices), size], dtype=float)\n            x = enumerate(hot_indices)\n            for i, x in enumerate(hot_indices):\n                out[i, x] = 1.\n            return (out, )\n\n        size = np.array(max(hot_indices) + end_padding + 1, dtype=np.int64)\n        if size == 0:\n            size = 1\n        op = core.CreateOperator('OneHot', ['hot_indices', 'size'], ['output'])\n        self.assertReferenceChecks(\n            gc,\n            op,\n            [hot_indices, size],\n            one_hot_ref,\n            input_device_options={'size': core.DeviceOption(caffe2_pb2.CPU)})\n\n    @given(hot_indices=_one_hots())\n    def test_segment_one_hot(self, hot_indices):\n        index_size, lengths, indices = hot_indices\n\n        index_size = np.array(index_size, dtype=np.int64)\n        lengths = np.array(lengths, dtype=np.int32)\n        indices = np.array(indices, dtype=np.int64)\n\n        def segment_one_hot_ref(lengths, hot_indices, size):\n            offset = 0\n            out = np.zeros([len(lengths), size], dtype=float)\n            for i, length in enumerate(lengths):\n                for idx in hot_indices[offset:offset + length]:\n                    out[i, idx] = 1.\n                offset += length\n            return (out, )\n\n        op = core.CreateOperator(\n            'SegmentOneHot',\n            ['lengths', 'hot_indices', 'size'],\n            ['output'])\n        self.assertReferenceChecks(\n            hu.cpu_do,\n            op,\n            [lengths, indices, index_size],\n            segment_one_hot_ref)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/onnx_while_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nimport hypothesis.strategies as st\nimport unittest\nimport caffe2.python.hypothesis_test_util as hu\nfrom caffe2.python import core, workspace\nfrom hypothesis import given\nfrom caffe2.proto import caffe2_pb2\n\n\nclass TestONNXWhile(hu.HypothesisTestCase):\n    @given(\n        condition=st.booleans(),\n        max_trip_count=st.integers(0, 100),\n        save_scopes=st.booleans(),\n        seed=st.integers(0, 65535),\n        **hu.gcs_cpu_only)\n    def test_onnx_while_fibb(\n            self, condition, max_trip_count, save_scopes, seed, gc, dc):\n        np.random.seed(seed)\n\n        # Create body net\n        body_net = caffe2_pb2.NetDef()\n        # Two loop carried dependencies: first and second\n        body_net.external_input.extend(['i', 'cond', 'first', 'second'])\n        body_net.external_output.extend(['cond_new', 'second', 'third', 'third'])\n        add_op = core.CreateOperator(\n            'Add',\n            ['first', 'second'],\n            ['third'],\n        )\n        print3 = core.CreateOperator(\n            'Print',\n            ['third'],\n            [],\n        )\n        limit_const = core.CreateOperator(\n            'ConstantFill',\n            [],\n            ['limit_const'],\n            shape=[1],\n            dtype=caffe2_pb2.TensorProto.FLOAT,\n            value=100.0,\n        )\n        cond = core.CreateOperator(\n            'LT',\n            ['third', 'limit_const'],\n            ['cond_new'],\n        )\n        body_net.op.extend([add_op, print3, limit_const, cond])\n\n        while_op = core.CreateOperator(\n            'ONNXWhile',\n            ['max_trip_count', 'condition', 'first_init', 'second_init'],\n            ['first_a', 'second_a', 'third_a'],\n            body=body_net,\n            has_cond=True,\n            has_trip_count=True,\n            save_scopes=save_scopes,\n        )\n\n        condition_arr = np.array(condition).astype(np.bool)\n        max_trip_count_arr = np.array(max_trip_count).astype(np.int64)\n        first_init = np.array([1]).astype(np.float32)\n        second_init = np.array([1]).astype(np.float32)\n\n        def ref(max_trip_count, condition, first_init, second_init):\n            first = 1\n            second = 1\n            results = []\n            if condition:\n                for _ in range(max_trip_count):\n                    third = first + second\n                    first = second\n                    second = third\n                    results.append(third)\n                    if third > 100:\n                        break\n            return (first, second, np.array(results).astype(np.float32))\n\n        self.assertReferenceChecks(\n            gc,\n            while_op,\n            [max_trip_count_arr, condition_arr, first_init, second_init],\n            ref,\n        )\n        self.assertFalse(workspace.HasBlob(\"cond_new\"))\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/pack_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\n\nfrom hypothesis import given\nfrom hypothesis import strategies as st\nimport numpy as np\nimport time\n\n\nclass TestTensorPackOps(hu.HypothesisTestCase):\n\n    def pack_segments_ref(self, return_presence_mask=False):\n        def pack_segments_ref(lengths, data):\n            arr = []\n            constant_values = 0\n            if data.dtype.char == 'S':\n                constant_values = ''\n            for idx in range(np.size(lengths)):\n                chunk = data[np.sum(lengths[:idx]):np.sum(lengths[:idx + 1])]\n                pad_length = np.max(lengths) - lengths[idx]\n\n                # ((0, pad_length), (0, 0)) says add pad_length rows of padding\n                # below chunk and 0 rows of padding elsewhere\n                arr.append(\n                    np.pad(\n                        chunk, ((0, pad_length), (0, 0)),\n                        mode=str(\"constant\"),\n                        constant_values=constant_values\n                    )\n                )\n            result = [arr]\n            if return_presence_mask:\n                presence_arr = []\n                for length in lengths:\n                    pad_length = np.max(lengths) - length\n                    presence_arr.append(\n                        np.pad(\n                            np.ones((length), dtype=np.bool), ((0, pad_length)),\n                            mode=str(\"constant\")\n                        )\n                    )\n                result.append(presence_arr)\n            return result\n\n        return pack_segments_ref\n\n    @given(\n        num_seq=st.integers(10, 500),\n        cell_size=st.integers(1, 10),\n        **hu.gcs\n    )\n    def test_pack_ops(self, num_seq, cell_size, gc, dc):\n        # create data\n        lengths = np.arange(num_seq, dtype=np.int32) + 1\n        num_cell = np.sum(lengths)\n        data = np.zeros(num_cell * cell_size, dtype=np.float32)\n        left = np.cumsum(np.arange(num_seq) * cell_size)\n        right = np.cumsum(lengths * cell_size)\n        for i in range(num_seq):\n            data[left[i]:right[i]] = i + 1.0\n        data.resize(num_cell, cell_size)\n        print(\"\\nnum seq:{},    num cell: {},   cell size:{}\\n\".format(\n            num_seq, num_cell, cell_size)\n            + \"=\" * 60\n        )\n        # run test\n        op = core.CreateOperator(\n            'PackSegments', ['l', 'd'], ['t'])\n        workspace.FeedBlob('l', lengths)\n        workspace.FeedBlob('d', data)\n\n        start = time.time()\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[lengths, data],\n            reference=self.pack_segments_ref(),\n        )\n        end = time.time()\n        print(\"{} used time: {}\".format(gc, end - start).replace('\\n', ' '))\n\n        with core.DeviceScope(gc):\n            workspace.FeedBlob('l', lengths)\n            workspace.FeedBlob('d', data)\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'PackSegments',\n            ['l', 'd'],\n            ['t'],\n            device_option=gc))\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'UnpackSegments',\n            ['l', 't'],\n            ['newd'],\n            device_option=gc))\n        assert((workspace.FetchBlob('newd') == workspace.FetchBlob('d')).all())\n\n    @given(\n        **hu.gcs_cpu_only\n    )\n    def test_pack_ops_str(self, gc, dc):\n        # GPU does not support string. Test CPU implementation only.\n        workspace.FeedBlob('l', np.array([1, 2, 3], dtype=np.int64))\n        strs = np.array([\n            [\"a\", \"a\"],\n            [\"b\", \"b\"],\n            [\"bb\", \"bb\"],\n            [\"c\", \"c\"],\n            [\"cc\", \"cc\"],\n            [\"ccc\", \"ccc\"]],\n            dtype='|S')\n        workspace.FeedBlob('d', strs)\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'PackSegments',\n            ['l', 'd'],\n            ['t'],\n            device_option=gc))\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'UnpackSegments',\n            ['l', 't'],\n            ['newd'],\n            device_option=gc))\n        assert((workspace.FetchBlob('newd') == workspace.FetchBlob('d')).all())\n\n    def test_pad_minf(self):\n        workspace.FeedBlob('l', np.array([1, 2, 3], dtype=np.int32))\n        workspace.FeedBlob(\n            'd',\n            np.array([\n                [1.0, 1.1],\n                [2.0, 2.1],\n                [2.2, 2.2],\n                [3.0, 3.1],\n                [3.2, 3.3],\n                [3.4, 3.5]],\n                dtype=np.float32))\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'PackSegments', ['l', 'd'], ['t'], pad_minf=True))\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'Exp', ['t'], ['r']\n        ))\n        result = workspace.FetchBlob('t')\n        assert(result[0, -1, 0] < -1000.0)\n\n        # The whole point of padding with -inf is that when we exponentiate it\n        # then it should be zero.\n        exponentiated = workspace.FetchBlob('r')\n        assert(exponentiated[0, -1, 0] == 0.0)\n\n    @given(**hu.gcs_cpu_only)\n    def test_presence_mask(self, gc, dc):\n        lengths = np.array([1, 2, 3], dtype=np.int32)\n        data = np.array(\n            [\n                [1.0, 1.0], [2.0, 2.0], [2.0, 2.0], [3.0, 3.0], [3.0, 3.0],\n                [3.0, 3.0]\n            ],\n            dtype=np.float32\n        )\n\n        op = core.CreateOperator(\n            'PackSegments', ['l', 'd'], ['t', 'p'], return_presence_mask=True\n        )\n        workspace.FeedBlob('l', lengths)\n        workspace.FeedBlob('d', data)\n        inputs = [lengths, data]\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            reference=self.pack_segments_ref(return_presence_mask=True),\n        )\n\n        op = core.CreateOperator(\n            'PackSegments', ['l', 'd'], ['t', 'p'], return_presence_mask=True\n        )\n        workspace.RunOperatorOnce(op)\n\n        output = workspace.FetchBlob('t')\n        expected_output_shape = (3, 3, 2)\n        self.assertEquals(output.shape, expected_output_shape)\n\n        presence_mask = workspace.FetchBlob('p')\n        expected_presence_mask = np.array(\n            [[True, False, False], [True, True, False], [True, True, True]],\n            dtype=np.bool\n        )\n        self.assertEqual(presence_mask.shape, expected_presence_mask.shape)\n        np.testing.assert_array_equal(presence_mask, expected_presence_mask)\n\n    def test_presence_mask_empty(self):\n        lengths = np.array([], dtype=np.int32)\n        data = np.array([], dtype=np.float32)\n\n        op = core.CreateOperator(\n            'PackSegments', ['l', 'd'], ['t', 'p'], return_presence_mask=True\n        )\n        workspace.FeedBlob('l', lengths)\n        workspace.FeedBlob('d', data)\n        workspace.RunOperatorOnce(op)\n\n        output = workspace.FetchBlob('p')\n        expected_output_shape = (0, 0)\n        self.assertEquals(output.shape, expected_output_shape)\n\n    @given(**hu.gcs_cpu_only)\n    def test_out_of_bounds(self, gc, dc):\n        # Copy pasted from test_pack_ops but with 3 changed to 4\n        lengths = np.array([1, 2, 4], dtype=np.int32)\n        data = np.array([\n            [1.0, 1.0],\n            [2.0, 2.0],\n            [2.0, 2.0],\n            [3.0, 3.0],\n            [3.0, 3.0],\n            [3.0, 3.0]], dtype=np.float32)\n        op = core.CreateOperator(\n            'PackSegments', ['l', 'd'], ['t'])\n\n        inputs = [lengths, data]\n        self.assertRunOpRaises(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            exception=RuntimeError\n        )\n\n    @given(**hu.gcs_cpu_only)\n    def test_under_bounds(self, gc, dc):\n        # Copy pasted from test_pack_ops but with 3 changed to 2\n        lengths = np.array([1, 2, 2], dtype=np.int32)\n        data = np.array([\n            [1.0, 1.0],\n            [2.0, 2.0],\n            [2.0, 2.0],\n            [3.0, 3.0],\n            [3.0, 3.0],\n            [3.0, 3.0]], dtype=np.float32)\n        op = core.CreateOperator(\n            'PackSegments', ['l', 'd'], ['t'])\n\n        inputs = [lengths, data]\n        self.assertRunOpRaises(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            exception=RuntimeError\n        )\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/pack_rnn_sequence_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestPackRNNSequenceOperator(hu.HypothesisTestCase):\n\n    @given(n=st.integers(0, 10), k=st.integers(1, 5),\n           dim=st.integers(1, 5), **hu.gcs_cpu_only)\n    def test_pack_rnn_seqence(self, n, k, dim, gc, dc):\n        lengths = np.random.randint(k, size=n).astype(np.int32) + 1\n        values = np.random.rand(sum(lengths), dim).astype(np.float32)\n\n        def pack_op(values, lengths):\n            T = max(lengths) if any(lengths) else 0\n            N = lengths.size\n            output = np.zeros((T, N) + values.shape[1:]).astype(np.float32)\n            offset = 0\n            for c in range(N):\n                for r in range(lengths[c]):\n                    output[r][c] = values[offset + r]\n                offset += lengths[c]\n            return [output]\n\n        op = core.CreateOperator(\n            'PackRNNSequence',\n            ['values', 'lengths'],\n            'out'\n        )\n\n        # Check against numpy reference\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[values, lengths],\n            reference=pack_op,\n        )\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [values, lengths], [0])\n        # Gradient check\n        self.assertGradientChecks(gc, op, [values, lengths], 0, [0])\n\n    @given(n=st.integers(0, 10), k=st.integers(2, 5),\n           dim=st.integers(1, 5), **hu.gcs_cpu_only)\n    def test_unpack_rnn_seqence(self, n, k, dim, gc, dc):\n        lengths = np.random.randint(k, size=n).astype(np.int32) + 1\n        T = max(lengths) if any(lengths) else 0\n        N = lengths.size\n        values = np.random.rand(T, N, dim).astype(np.float32)\n\n        def unpack_op(values, lengths):\n            M = sum(lengths)\n            output = np.zeros((M,) + values.shape[2:]).astype(np.float32)\n            N = lengths.size\n            offset = 0\n            for c in range(N):\n                for r in range(lengths[c]):\n                    output[offset + r] = values[r][c]\n                offset += lengths[c]\n            return [output]\n\n        op = core.CreateOperator(\n            'UnpackRNNSequence',\n            ['values', 'lengths'],\n            'out'\n        )\n\n        # Check against numpy reference\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[values, lengths],\n            reference=unpack_op,\n        )\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [values, lengths], [0])\n        # Gradient check\n        self.assertGradientChecks(gc, op, [values, lengths], 0, [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/pad_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nimport hypothesis.strategies as st\nimport unittest\nimport caffe2.python.hypothesis_test_util as hu\nfrom caffe2.python import core\nfrom hypothesis import given\n\n\nclass TestPad(hu.HypothesisTestCase):\n    @given(pad_t=st.integers(-5, 0),\n           pad_l=st.integers(-5, 0),\n           pad_b=st.integers(-5, 0),\n           pad_r=st.integers(-5, 0),\n           mode=st.sampled_from([\"constant\", \"reflect\", \"edge\"]),\n           size_w=st.integers(16, 128),\n           size_h=st.integers(16, 128),\n           size_c=st.integers(1, 4),\n           size_n=st.integers(1, 4),\n           **hu.gcs)\n    def test_crop(self,\n                  pad_t, pad_l, pad_b, pad_r,\n                  mode,\n                  size_w, size_h, size_c, size_n,\n                  gc, dc):\n        op = core.CreateOperator(\n            \"PadImage\",\n            [\"X\"],\n            [\"Y\"],\n            pad_t=pad_t,\n            pad_l=pad_l,\n            pad_b=pad_b,\n            pad_r=pad_r,\n        )\n        X = np.random.rand(\n            size_n, size_c, size_h, size_w).astype(np.float32)\n\n        def ref(X):\n            return (X[:, :, -pad_t:pad_b or None, -pad_l:pad_r or None],)\n\n        self.assertReferenceChecks(gc, op, [X], ref)\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/partition_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport numpy as np\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase, rand_array\n\n\nclass TestPartitionOps(TestCase):\n\n    def test_configs(self):\n        # (main dims, partitions,  main type, [list of (extra dims, type)])\n        configs = [\n            ((10, ), 3),\n            ((4, ), 10),\n            ((10, 10), 4),\n            ((100, ), 2),\n            ((5, ), 1),\n            ((1, ), 1),\n            ((2, 10), 2),\n        ]\n        suffixes = [\n            [],\n            [((2, 2), np.float32)],\n            [((3, ), np.int64), ((2, ), np.float32)],\n        ]\n        return [\n            (main_dims, parts, main_type, extra, pack)\n            for main_dims, parts in configs\n            for main_type in [np.int32, np.int64] for extra in suffixes\n            for pack in [False, True]\n        ]\n\n    def testPartition(self):\n        for main_dims, parts, main_type, extra_ins, pack in self.test_configs():\n            ins = ['in' + str(i) for i in range(1 + len(extra_ins))]\n            outs = [\n                'in{}_p{}'.format(j, i)\n                for i in range(parts) for j in range(1 + len(extra_ins))\n            ]\n            op = core.CreateOperator(\n                'Partition', ins, outs, pack_first_input=(1 if pack else 0))\n            x = []\n            for i, (dims, t) in enumerate([((), main_type)] + extra_ins):\n                if t in [np.float32, np.float64]:\n                    d = rand_array(*(main_dims + dims))\n                else:\n                    d = np.random.randint(-100, 100, (main_dims + dims))\n                d = d.astype(t)\n                workspace.FeedBlob(ins[i], d)\n                x.append(d)\n\n            def sharding(x):\n                # numpy has proper modulo op that yields non-negative results\n                shards = (x[0] % parts).reshape([-1])\n                out = []\n                for i in range(parts):\n                    for ind, v in enumerate(x):\n                        suffix_shape = v.shape[len(x[0].shape):]\n                        accum = []\n                        data = v.reshape((-1, ) + suffix_shape)\n\n                        if pack and ind == 0:\n                            data = data // parts\n\n                        for j, s in enumerate(shards):\n                            if s == i:\n                                accum.append(data[j])\n\n                        def join(a):\n                            if not a:\n                                return np.empty(shape=(0, ) + suffix_shape)\n                            return np.stack(a)\n\n                        out.append(join(accum))\n                return out\n\n            workspace.RunOperatorOnce(op)\n            ref = sharding(x)\n            print(x)\n            print(ref)\n            for name, expected in zip(outs, ref):\n                np.testing.assert_array_equal(\n                    expected, workspace.FetchBlob(name)\n                )\n\n            # test inverse operation (GatherByKey)\n            if len(main_dims) == 1:\n                # currently only 1D key tensor supported\n                for i in range(len(extra_ins)):\n                    expected_out = ins[i + 1]\n                    gather_ins = [ins[0]] + [\n                        outs[len(ins) * p + i + 1] for p in range(parts)]\n                    actual_out = expected_out + '_actual'\n                    op = core.CreateOperator(\n                        'GatherByKey', gather_ins, actual_out)\n                    workspace.RunOperatorOnce(op)\n                    expected = workspace.FetchBlob(expected_out)\n                    actual = workspace.FetchBlob(actual_out)\n                    np.testing.assert_array_equal(expected, actual)\n\n\n    def testLengthsPartition(self):\n        for main_dims, parts, main_type, extra_ins, pack in self.test_configs():\n            # For LengthsSharding only 1-D tensors supported as a first input\n            if len(main_dims) > 1:\n                continue\n            ins = ['in' + str(i) for i in range(2 + len(extra_ins))]\n            outs = [\n                'in{}_p{}'.format(j, i)\n                for i in range(parts) for j in range(2 + len(extra_ins))\n            ]\n            op = core.CreateOperator(\n                'LengthsPartition', ins, outs,\n                pack_first_input=(1 if pack else 0)\n            )\n            x = []\n            for i, (dims, t) in enumerate([((), main_type)] + extra_ins):\n                if t in [np.float32, np.float64]:\n                    d = rand_array(*(main_dims + dims))\n                else:\n                    d = np.random.randint(-100, 100, (main_dims + dims))\n                d = d.astype(t)\n                workspace.FeedBlob(ins[i + 1], d)\n                x.append(d)\n\n            # Randomly generate length tensor as well\n            elements = np.random.randint(2, 10)\n            lengths = []\n            total_length = 0\n            for _ in range(elements - 1):\n                lengths.append(np.random.randint(main_dims[0] - total_length))\n                total_length += lengths[-1]\n            lengths.append(main_dims[0] - total_length)\n            workspace.FeedBlob(ins[0], np.array(lengths, dtype=np.int32))\n\n            def sharding(x):\n                # numpy has proper modulo op that yields non-negative results\n                shards = (x[0] % parts).reshape([-1])\n                out = []\n                for i in range(parts):\n                    idx = 0\n                    sharded_lengths = np.zeros(elements)\n                    for ind, length in enumerate(lengths):\n                        for _ in range(length):\n                            if shards[idx] == i:\n                                sharded_lengths[ind] += 1\n                            idx += 1\n                    out.append(sharded_lengths)\n\n                    for ind, v in enumerate(x):\n                        suffix_shape = v.shape[len(x[0].shape):]\n                        accum = []\n                        data = v.reshape((-1, ) + suffix_shape)\n\n                        if pack and ind == 0:\n                            data = data // parts\n\n                        for j, s in enumerate(shards):\n                            if s == i:\n                                accum.append(data[j])\n\n                        def join(a):\n                            if not a:\n                                return np.empty(shape=(0, ) + suffix_shape)\n                            return np.stack(a)\n\n                        out.append(join(accum))\n                return out\n\n            workspace.RunOperatorOnce(op)\n            ref = sharding(x)\n            for name, expected in zip(outs, ref):\n                np.testing.assert_array_equal(\n                    expected, workspace.FetchBlob(name)\n                )\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/percentile_op_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace, dyndep\nimport caffe2.python.hypothesis_test_util as hu\nimport numpy as np\n\nclass TestPercentileOp(hu.HypothesisTestCase):\n    def _test_percentile_op(\n        self,\n        original_inp,\n        value_to_pct_map,\n        dist_lengths,\n        expected_values\n    ):\n        op = core.CreateOperator(\n            'Percentile',\n            ['original_values', 'value_to_pct_map', 'dist_lengths'],\n            ['percentile_values']\n        )\n        workspace.FeedBlob('original_values', np.array(original_inp, dtype=np.float32))\n        workspace.FeedBlob(\n            'value_to_pct_map', np.array(value_to_pct_map, dtype=np.float32))\n        workspace.FeedBlob('dist_lengths', np.array(dist_lengths, dtype=np.int32))\n        workspace.RunOperatorOnce(op)\n        np.testing.assert_array_almost_equal(\n            workspace.FetchBlob('percentile_values'),\n            np.array(expected_values),\n            decimal=5\n        )\n\n    def test_percentile_op_with_only_one_dist(self):\n        self._test_percentile_op(\n            original_inp=[[5]],\n            value_to_pct_map=[[5, 0.4]],\n            dist_lengths=[1],\n            expected_values=[[0.4]]\n        )\n\n    def test_percentile_op_with_all_elements_in_map(self):\n        self._test_percentile_op(\n            original_inp=[[3, 4], [10, 4]],\n            value_to_pct_map=[[3, 0.3], [4, 0.6], [10, 0.8], [4, 0.5], [5, 0.6]],\n            dist_lengths=[3, 2],\n            expected_values=[[0.3, 0.5], [0.8, 0.5]],\n        )\n\n    def test_percentile_op_with_same_value(self):\n        self._test_percentile_op(\n            original_inp=[[1, 1], [1, 2]],\n            value_to_pct_map=[[1, 0.1], [4, 0.4], [2, 0.5]],\n            dist_lengths=[2, 1],\n            expected_values=[[0.1, 0.0], [0.1, 0.5]]\n        )\n\n    def test_percentile_op_with_elements_bigger_than_map_range(self):\n        self._test_percentile_op(\n            original_inp=[[1, 5], [3, 4]],\n            value_to_pct_map=[[1, 0.1], [4, 0.4], [2, 0.1], [3, 0.3]],\n            dist_lengths=[2, 2],\n            expected_values=[[0.1, 1.], [0.3, 1.0]]\n        )\n\n    def test_percentile_op_with_elements_smaller_than_map_range(self):\n        self._test_percentile_op(\n            original_inp=[[1], [5], [6]],\n            value_to_pct_map=[[2, 0.2], [5, 0.5], [7, 0.5]],\n            dist_lengths=[3],\n            expected_values=[[0.0], [0.5], [0.5]]\n        )\n\n    def test_percentile_op_with_interpolation(self):\n        self._test_percentile_op(\n            original_inp=[[3, 2, 5], [6, 7, 8]],\n            value_to_pct_map=[[1, 0.1], [4, 0.7], [4.5, 0.8],\n                              [6, 0.5], [8, 0.9],\n                              [8, 0.6]],\n            dist_lengths=[3, 2, 1],\n            expected_values=[[0.5, 0.0, 0.0], [1.0, 0.7, 0.6]]\n        )\n\n    def test_percentile_op_with_large_sample_size_per_dist(self):\n        self._test_percentile_op(\n            original_inp=[[3, 1], [5, 7]],\n            value_to_pct_map=[[3, 0.5], [4, 0.6], [5, 0.7],\n                              [1, 0.2], [2, 0.3], [5, 0.8]],\n            dist_lengths=[3, 3],\n            expected_values=[[0.5, 0.2], [0.7, 1.0]]\n        )\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/piecewise_linear_transform_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport caffe2.python.hypothesis_test_util as hu\nimport numpy as np\n\nimport unittest\n\n\nclass TestPiecewiseLinearTransform(hu.HypothesisTestCase):\n    def constrain(self, v, min_val, max_val):\n        def constrain_internal(x):\n            return min(max(x, min_val), max_val)\n        return np.array([constrain_internal(x) for x in v])\n\n    def transform(self, x, bounds, slopes, intercepts):\n        n = len(slopes)\n        x_ = self.constrain(x, bounds[0], bounds[-1])\n        index = np.minimum(\n            np.maximum(\n                np.searchsorted(bounds, x_) - 1,\n                0\n            ),\n            n - 1\n        )\n        y = slopes[index] * x_ + intercepts[index]\n        return y\n\n    @given(n=st.integers(1, 100), **hu.gcs)\n    def test_multi_predictions_params_from_arg(self, n, gc, dc):\n        slopes = np.random.uniform(-1, 1, (2, n)).astype(np.float32)\n        intercepts = np.random.uniform(-1, 1, (2, n)).astype(np.float32)\n        bounds = np.random.uniform(0.1, 0.9,\n                                   (2, n + 1)).astype(np.float32)\n        bounds.sort()\n        X = np.random.uniform(0, 1, (n, 2)).astype(np.float32)\n\n        op = core.CreateOperator(\n            \"PiecewiseLinearTransform\", [\"X\"], [\"Y\"],\n            bounds=bounds.flatten().tolist(),\n            slopes=slopes.flatten().tolist(),\n            intercepts=intercepts.flatten().tolist(),\n        )\n\n        def piecewise(x, *args, **kw):\n            x_0 = self.transform(\n                x[:, 0], bounds[0, :], slopes[0, :], intercepts[0, :])\n            x_1 = self.transform(\n                x[:, 1], bounds[1, :], slopes[1, :], intercepts[1, :])\n\n            return [np.vstack((x_0, x_1)).transpose()]\n\n        self.assertReferenceChecks(gc, op, [X], piecewise)\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n    @given(n=st.integers(1, 100), **hu.gcs)\n    def test_binary_predictions_params_from_arg(self, n, gc, dc):\n        slopes = np.random.uniform(-1, 1, size=n).astype(np.float32)\n        intercepts = np.random.uniform(-1, 1, size=n).astype(np.float32)\n        bounds = np.random.uniform(0.1, 0.9, n + 1).astype(np.float32)\n        bounds.sort()\n\n        X = np.random.uniform(0, 1, (n, 2)).astype(np.float32)\n        X[:, 0] = 1 - X[:, 1]\n\n        op = core.CreateOperator(\n            \"PiecewiseLinearTransform\", [\"X\"], [\"Y\"],\n            bounds=bounds.flatten().tolist(),\n            slopes=slopes.flatten().tolist(),\n            intercepts=intercepts.flatten().tolist(),\n            pieces=n,\n            binary=True,\n        )\n\n        def piecewise(x):\n            x_ = self.transform(x[:, 1], bounds, slopes, intercepts)\n            return [np.vstack((1 - x_, x_)).transpose()]\n\n        self.assertReferenceChecks(gc, op, [X], piecewise)\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n    @given(n=st.integers(1, 100), **hu.gcs)\n    def test_multi_predictions_params_from_input(self, n, gc, dc):\n        slopes = np.random.uniform(-1, 1, (2, n)).astype(np.float32)\n        intercepts = np.random.uniform(-1, 1, (2, n)).astype(np.float32)\n        bounds = np.random.uniform(0.1, 0.9,\n                                   (2, n + 1)).astype(np.float32)\n        bounds.sort()\n        X = np.random.uniform(0, 1, (n, 2)).astype(np.float32)\n\n        op = core.CreateOperator(\n            \"PiecewiseLinearTransform\",\n            [\"X\", \"bounds\", \"slopes\", \"intercepts\"],\n            [\"Y\"],\n        )\n\n        def piecewise(x, bounds, slopes, intercepts):\n            x_0 = self.transform(\n                x[:, 0], bounds[0, :], slopes[0, :], intercepts[0, :])\n            x_1 = self.transform(\n                x[:, 1], bounds[1, :], slopes[1, :], intercepts[1, :])\n\n            return [np.vstack((x_0, x_1)).transpose()]\n\n        self.assertReferenceChecks(\n            gc, op, [X, bounds, slopes, intercepts], piecewise)\n        self.assertDeviceChecks(dc, op, [X, bounds, slopes, intercepts], [0])\n\n    @given(n=st.integers(1, 100), **hu.gcs)\n    def test_binary_predictions_params_from_input(self, n, gc, dc):\n        slopes = np.random.uniform(-1, 1, size=n).astype(np.float32)\n        intercepts = np.random.uniform(-1, 1, size=n).astype(np.float32)\n        bounds = np.random.uniform(0.1, 0.9, n + 1).astype(np.float32)\n        bounds.sort()\n\n        X = np.random.uniform(0, 1, (n, 2)).astype(np.float32)\n        X[:, 0] = 1 - X[:, 1]\n\n        op = core.CreateOperator(\n            \"PiecewiseLinearTransform\",\n            [\"X\", \"bounds\", \"slopes\", \"intercepts\"],\n            [\"Y\"],\n            binary=True,\n        )\n\n        def piecewise(x, bounds, slopes, intercepts):\n            x_ = self.transform(x[:, 1], bounds, slopes, intercepts)\n            return [np.vstack((1 - x_, x_)).transpose()]\n\n        self.assertReferenceChecks(\n            gc, op, [X, bounds, slopes, intercepts], piecewise)\n        self.assertDeviceChecks(dc, op, [X, bounds, slopes, intercepts], [0])\n\n    @given(n=st.integers(1, 100), **hu.gcs)\n    def test_1D_predictions_params_from_input(self, n, gc, dc):\n        slopes = np.random.uniform(-1, 1, size=n).astype(np.float32)\n        intercepts = np.random.uniform(-1, 1, size=n).astype(np.float32)\n        bounds = np.random.uniform(0.1, 0.9, n + 1).astype(np.float32)\n        bounds.sort()\n\n        X = np.random.uniform(0, 1, size=n).astype(np.float32)\n\n        op = core.CreateOperator(\n            \"PiecewiseLinearTransform\",\n            [\"X\", \"bounds\", \"slopes\", \"intercepts\"],\n            [\"Y\"],\n            binary=True,\n        )\n\n        def piecewise(x, bounds, slopes, intercepts):\n            x_ = self.transform(x, bounds, slopes, intercepts)\n            return [x_]\n\n        self.assertReferenceChecks(\n            gc, op, [X, bounds, slopes, intercepts], piecewise)\n        self.assertDeviceChecks(dc, op, [X, bounds, slopes, intercepts], [0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/pooling_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nfrom hypothesis import assume, given, settings\nimport hypothesis.strategies as st\nimport os\nimport unittest\n\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestPooling(hu.HypothesisTestCase):\n    # CUDNN does NOT support different padding values and we skip it\n    @given(stride_h=st.integers(1, 3),\n           stride_w=st.integers(1, 3),\n           pad_t=st.integers(0, 3),\n           pad_l=st.integers(0, 3),\n           pad_b=st.integers(0, 3),\n           pad_r=st.integers(0, 3),\n           kernel=st.integers(3, 5),\n           size=st.integers(7, 9),\n           input_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           op_type=st.sampled_from([\"MaxPool\", \"AveragePool\", \"LpPool\",\n                                   \"MaxPool2D\", \"AveragePool2D\"]),\n           **hu.gcs)\n    def test_pooling_separate_stride_pad(self, stride_h, stride_w,\n                                         pad_t, pad_l, pad_b,\n                                         pad_r, kernel, size,\n                                         input_channels,\n                                         batch_size, order,\n                                         op_type,\n                                         gc, dc):\n        assume(np.max([pad_t, pad_l, pad_b, pad_r]) < kernel)\n\n        op = core.CreateOperator(\n            op_type,\n            [\"X\"],\n            [\"Y\"],\n            stride_h=stride_h,\n            stride_w=stride_w,\n            pad_t=pad_t,\n            pad_l=pad_l,\n            pad_b=pad_b,\n            pad_r=pad_r,\n            kernel=kernel,\n            order=order,\n        )\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32)\n\n        if order == \"NCHW\":\n            X = X.transpose((0, 3, 1, 2))\n        self.assertDeviceChecks(dc, op, [X], [0])\n        if 'MaxPool' not in op_type:\n            self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    # This test is to check if CUDNN works for bigger batch size or not\n    @unittest.skipIf(not os.getenv('CAFFE2_DEBUG'),\n                     \"This is a test that reproduces a cudnn error. If you \"\n                     \"want to run it, set env variable CAFFE2_DEBUG=1.\")\n    @given(**hu.gcs_gpu_only)\n    def test_pooling_big_batch(self, gc, dc):\n        op = core.CreateOperator(\n            \"AveragePool\",\n            [\"X\"],\n            [\"Y\"],\n            stride=1,\n            kernel=7,\n            pad=0,\n            order=\"NHWC\",\n            engine=\"CUDNN\",\n        )\n        X = np.random.rand(70000, 7, 7, 81).astype(np.float32)\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           size=st.integers(7, 9),\n           input_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           op_type=st.sampled_from([\"MaxPool\", \"AveragePool\",\n                                    \"MaxPool1D\", \"AveragePool1D\"]),\n           **hu.gcs)\n    def test_pooling_1d(self, stride, pad, kernel, size, input_channels,\n                        batch_size, order, op_type, gc, dc):\n        assume(pad < kernel)\n        op = core.CreateOperator(\n            op_type,\n            [\"X\"],\n            [\"Y\"],\n            strides=[stride],\n            kernels=[kernel],\n            pads=[pad, pad],\n            order=order,\n            engine=\"\",\n        )\n        X = np.random.rand(\n            batch_size, size, input_channels).astype(np.float32)\n        if order == \"NCHW\":\n            X = X.transpose((0, 2, 1))\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n        if 'MaxPool' not in op_type:\n            self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 2),\n           kernel=st.integers(1, 6),\n           size=st.integers(3, 5),\n           input_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           op_type=st.sampled_from([\"MaxPool\", \"AveragePool\",\n                                    \"MaxPool3D\", \"AveragePool3D\"]),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs)\n    def test_pooling_3d(self, stride, pad, kernel, size, input_channels,\n                        batch_size, order, op_type, engine, gc, dc):\n        assume(pad < kernel)\n        assume(size + pad + pad >= kernel)\n        # some case here could be calculated with global pooling, but instead\n        # calculated with general implementation, slower but should still\n        # be corect.\n        op = core.CreateOperator(\n            op_type,\n            [\"X\"],\n            [\"Y\"],\n            strides=[stride] * 3,\n            kernels=[kernel] * 3,\n            pads=[pad] * 6,\n            order=order,\n            engine=engine,\n        )\n        X = np.random.rand(\n            batch_size, size, size, size, input_channels).astype(np.float32)\n        if order == \"NCHW\":\n            X = X.transpose((0, 4, 1, 2, 3))\n\n        self.assertDeviceChecks(dc, op, [X], [0], threshold=0.001)\n        if 'MaxPool' not in op_type:\n            self.assertGradientChecks(gc, op, [X], 0, [0], threshold=0.001)\n\n    @given(kernel=st.integers(3, 6),\n           size=st.integers(3, 5),\n           input_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           op_type=st.sampled_from([\"MaxPool\", \"AveragePool\",\n                                    \"MaxPool3D\", \"AveragePool3D\"]),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs)\n    def test_global_pooling_3d(self, kernel, size, input_channels,\n                               batch_size, order, op_type, engine, gc, dc):\n        # pad and stride ignored because they will be infered in global_pooling\n        op = core.CreateOperator(\n            op_type,\n            [\"X\"],\n            [\"Y\"],\n            kernels=[kernel] * 3,\n            order=order,\n            global_pooling=True,\n            engine=engine,\n        )\n        X = np.random.rand(\n            batch_size, size, size, size, input_channels).astype(np.float32)\n        if order == \"NCHW\":\n            X = X.transpose((0, 4, 1, 2, 3))\n\n        self.assertDeviceChecks(dc, op, [X], [0], threshold=0.001)\n        if 'MaxPool' not in op_type:\n            self.assertGradientChecks(gc, op, [X], 0, [0], threshold=0.001)\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No GPU support\")\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           size=st.integers(7, 9),\n           input_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           **hu.gcs_gpu_only)\n    def test_pooling_with_index(self, stride, pad, kernel, size,\n                                input_channels, batch_size, gc, dc):\n        assume(pad < kernel)\n        op = core.CreateOperator(\n            \"MaxPoolWithIndex\",\n            [\"X\"],\n            [\"Y\", \"Y_index\"],\n            stride=stride,\n            kernel=kernel,\n            pad=pad,\n            order=\"NCHW\",\n            deterministic=1,\n        )\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32)\n\n        # transpose due to order = NCHW\n        X = X.transpose((0, 3, 1, 2))\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n    @given(sz=st.integers(1, 20),\n           batch_size=st.integers(1, 4),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           op_type=st.sampled_from([\"AveragePool\", \"AveragePool2D\"]),\n           **hu.gcs)\n    @settings(max_examples=3, timeout=10)\n    def test_global_avg_pool_nchw(self, op_type, sz, batch_size, engine, gc, dc):\n        ''' Special test to stress the fast path of NCHW average pool '''\n        op = core.CreateOperator(\n            op_type,\n            [\"X\"],\n            [\"Y\"],\n            stride=1,\n            kernel=sz,\n            pad=0,\n            order=\"NCHW\",\n            engine=engine,\n        )\n        X = np.random.rand(\n            batch_size, 3, sz, sz).astype(np.float32)\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @given(sz=st.integers(1, 20),\n           batch_size=st.integers(1, 4),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           op_type=st.sampled_from([\"MaxPool\", \"MaxPool2D\"]),\n           **hu.gcs)\n    @settings(max_examples=3, timeout=10)\n    def test_global_max_pool_nchw(self, op_type, sz,\n                                  batch_size, engine, gc, dc):\n        ''' Special test to stress the fast path of NCHW max pool '''\n        # CuDNN 5 does not support deterministic max pooling.\n        assume(workspace.GetCuDNNVersion() >= 6000 or engine != \"CUDNN\")\n        op = core.CreateOperator(\n            op_type,\n            [\"X\"],\n            [\"Y\"],\n            stride=1,\n            kernel=sz,\n            pad=0,\n            order=\"NCHW\",\n            engine=engine,\n            deterministic=1,\n        )\n\n        np.random.seed(1234)\n        X = np.random.rand(\n            batch_size, 3, sz, sz).astype(np.float32)\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0], stepsize=1e-4)\n\n    @given(stride=st.integers(1, 3),\n           pad=st.integers(0, 3),\n           kernel=st.integers(1, 5),\n           size=st.integers(7, 9),\n           input_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           op_type=st.sampled_from([\"MaxPool\", \"AveragePool\", \"LpPool\",\n                                   \"MaxPool2D\", \"AveragePool2D\"]),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs)\n    def test_pooling(self, stride, pad, kernel, size,\n                     input_channels, batch_size,\n                     order, op_type, engine, gc, dc):\n        assume(pad < kernel)\n        op = core.CreateOperator(\n            op_type,\n            [\"X\"],\n            [\"Y\"],\n            stride=stride,\n            kernel=kernel,\n            pad=pad,\n            order=order,\n            engine=engine,\n        )\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32)\n        if order == \"NCHW\":\n            X = X.transpose((0, 3, 1, 2))\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n        if 'MaxPool' not in op_type:\n            self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @given(size=st.integers(7, 9),\n           input_channels=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           op_type=st.sampled_from([\"MaxPool\", \"AveragePool\", \"LpPool\"]),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs)\n    def test_global_pooling(self, size, input_channels, batch_size,\n                            order, op_type, engine, gc, dc):\n        # CuDNN 5 does not support deterministic max pooling.\n        assume(workspace.GetCuDNNVersion() >= 6000 or op_type != \"MaxPool\")\n        op = core.CreateOperator(\n            op_type,\n            [\"X\"],\n            [\"Y\"],\n            order=order,\n            engine=engine,\n            global_pooling=True,\n        )\n        X = np.random.rand(\n            batch_size, size, size, input_channels).astype(np.float32)\n        if order == \"NCHW\":\n            X = X.transpose((0, 3, 1, 2))\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n        if 'MaxPool' not in op_type:\n            self.assertGradientChecks(gc, op, [X], 0, [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/prepend_dim_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport numpy as np\n\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\nfrom caffe2.proto import caffe2_pb2\n\n\nclass TestPrependDim(TestCase):\n    def _test_fwd_bwd(self):\n        old_shape = (128, 2, 4)\n        new_shape = (8, 16, 2, 4)\n        X = np.random.rand(*old_shape).astype(np.float32)\n        Y = np.random.rand(*new_shape).astype(np.float32)\n\n        net = core.Net('net')\n\n        net.GivenTensorFill([], 'X', shape=old_shape, values=X.flatten())\n        net.GivenTensorFill([], 'Y', shape=new_shape, values=Y.flatten())\n\n        net.PrependDim(['X'], ['X_out'], dim_size=8)\n        net.DotProduct(['X_out', 'Y'], 'Z')\n        net.AddGradientOperators(['Z'])\n\n        workspace.RunNetOnce(net)\n\n        X_out = workspace.FetchBlob('X_out')\n        X_grad = workspace.FetchBlob('X_grad')\n        Y_grad = workspace.FetchBlob('Y_grad')\n\n        # Check the shape of the gradient\n        np.testing.assert_array_equal(X_out.shape, Y.shape)\n        np.testing.assert_array_equal(X_grad.shape, X.shape)\n        np.testing.assert_array_equal(Y_grad.shape, Y.shape)\n\n    def test_prepend_dim(self):\n        devices = [core.DeviceOption(caffe2_pb2.CPU, 0)]\n        if workspace.NumCudaDevices() > 0:\n            devices.append(core.DeviceOption(caffe2_pb2.CUDA, 0))\n\n        for device_opt in devices:\n            with core.DeviceScope(device_opt):\n                self._test_fwd_bwd()\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/python_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nfrom caffe2.python.core import CreatePythonOperator\nimport caffe2.python.hypothesis_test_util as hu\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport numpy as np\nimport unittest\n\nif workspace.is_asan:\n    # Numba seems to be not compatible with ASAN (at least at Facebook)\n    # so if we are in asan mode, we disable Numba which further disables\n    # the numba python op test.\n    HAS_NUMBA = False\nelse:\n    try:\n        import numba\n        HAS_NUMBA = True\n    except ImportError:\n        HAS_NUMBA = False\n\n\nclass PythonOpTest(hu.HypothesisTestCase):\n    @unittest.skipIf(not HAS_NUMBA, \"\")\n    @given(x=hu.tensor(),\n           n=st.integers(min_value=1, max_value=20),\n           w=st.integers(min_value=1, max_value=20))\n    def test_multithreaded_evaluation_numba_nogil(self, x, n, w):\n        @numba.jit(nopython=True, nogil=True)\n        def g(input_, output):\n            output[...] = input_\n\n        def f(inputs, outputs):\n            outputs[0].reshape(inputs[0].shape)\n            g(inputs[0].data, outputs[0].data)\n\n        ops = [CreatePythonOperator(f, [\"x\"], [str(i)]) for i in range(n)]\n        net = core.Net(\"net\")\n        net.Proto().op.extend(ops)\n        net.Proto().type = \"dag\"\n        net.Proto().num_workers = w\n        iters = 100\n        plan = core.Plan(\"plan\")\n        plan.AddStep(core.ExecutionStep(\"test-step\", net, iters))\n        workspace.FeedBlob(\"x\", x)\n        workspace.RunPlan(plan.Proto().SerializeToString())\n        for i in range(n):\n            y = workspace.FetchBlob(str(i))\n            np.testing.assert_almost_equal(x, y)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/rank_loss_operator_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestPairWiseLossOps(hu.HypothesisTestCase):\n    @given(X=hu.arrays(dims=[2, 1],\n                       elements=st.floats(min_value=0.0, max_value=10.0)),\n           label=hu.arrays(dims=[2, 1],\n                           elements=st.integers(min_value=0, max_value=1),\n                           dtype=np.float32),\n           **hu.gcs_cpu_only)\n    def test_pair_wise_loss_predictions(self, X, label, gc, dc):\n        workspace.FeedBlob('X', X)\n        workspace.FeedBlob('label', label)\n        new_label = np.array([label[1], label[0]])\n        new_x = np.array([X[1], X[0]])\n        workspace.FeedBlob('new_x', new_x)\n        workspace.FeedBlob('new_label', new_label)\n        net = core.Net('net')\n        net.PairWiseLoss(['X', 'label'], ['output'])\n        net.PairWiseLoss(['new_x', 'new_label'], ['new_output'])\n        plan = core.Plan('predict_data')\n        plan.AddStep(core.execution_step('predict_data',\n                                         [net], num_iter=1))\n        workspace.RunPlan(plan)\n        output = workspace.FetchBlob('output')\n        new_output = workspace.FetchBlob('new_output')\n        sign = 1 if label[0] > label[1] else -1\n        if label[0] == label[1]:\n            self.assertEqual(np.asscalar(output), 0)\n            return\n\n        self.assertAlmostEqual(\n            np.asscalar(output),\n            np.asscalar(np.log(1 + np.exp(sign * (X[1] - X[0])))),\n            delta=1e-4\n        )\n        # check swapping row order doesn't alter overall loss\n        self.assertAlmostEqual(output, new_output)\n\n    @given(X=hu.arrays(dims=[2, 1],\n                       elements=st.floats(min_value=0.0, max_value=10.0)),\n           label=hu.arrays(dims=[2, 1],\n                           elements=st.integers(min_value=0, max_value=1),\n                           dtype=np.float32),\n           dY=hu.arrays(dims=[1],\n                        elements=st.floats(min_value=1, max_value=10)),\n           **hu.gcs_cpu_only)\n    def test_pair_wise_loss_gradient(self, X, label, dY, gc, dc):\n        workspace.FeedBlob('X', X)\n        workspace.FeedBlob('dY', dY)\n        workspace.FeedBlob('label', label)\n        net = core.Net('net')\n        net.PairWiseLossGradient(\n            ['X', 'label', 'dY'],\n            ['dX'],\n        )\n        plan = core.Plan('predict_data')\n        plan.AddStep(core.execution_step('predict_data',\n                                         [net], num_iter=1))\n        workspace.RunPlan(plan)\n        dx = workspace.FetchBlob('dX')\n        sign = 1 if label[0] > label[1] else -1\n        if label[0] == label[1]:\n            self.assertEqual(np.asscalar(dx[0]), 0)\n            return\n        self.assertAlmostEqual(\n            np.asscalar(dx[0]),\n            np.asscalar(-dY[0] * sign / (1 + np.exp(sign * (X[0] - X[1])))),\n            delta=1e-2 * abs(np.asscalar(dx[0])))\n\n        self.assertEqual(np.asscalar(dx[0]), np.asscalar(-dx[1]))\n        delta = 1e-3\n        up_x = np.array([[X[0] + delta], [X[1]]], dtype=np.float32)\n        down_x = np.array([[X[0] - delta], [X[1]]], dtype=np.float32)\n        workspace.FeedBlob('up_x', up_x)\n        workspace.FeedBlob('down_x', down_x)\n        new_net = core.Net('new_net')\n        new_net.PairWiseLoss(['up_x', 'label'], ['up_output'])\n        new_net.PairWiseLoss(['down_x', 'label'], ['down_output'])\n\n        plan = core.Plan('predict_data')\n        plan.AddStep(core.execution_step('predict_data', [new_net], num_iter=1))\n        workspace.RunPlan(plan)\n        down_output_pred = workspace.FetchBlob('down_output')\n        up_output_pred = workspace.FetchBlob('up_output')\n        np.testing.assert_allclose(\n            np.asscalar(dx[0]),\n            np.asscalar(\n                0.5 * dY[0] *\n                (up_output_pred[0] - down_output_pred[0]) / delta),\n            rtol=1e-2, atol=1e-2)\n\n    @given(n=st.integers(0, 10), k=st.integers(1, 5), **hu.gcs_cpu_only)\n    def test_pair_wise_loss_batch(self, n, k, gc, dc):\n        lengths = np.random.randint(k, size=n).astype(np.int32) + 1\n        X = np.random.rand(sum(lengths)).astype(np.float32)\n        label = np.random.randint(k, size=sum(lengths)).astype(np.float32)\n\n        def pair_wise_op(X, label, lengths):\n            N = lengths.size\n            output = np.zeros(N).astype(np.float32)\n\n            def f(x):\n                return np.log(1 + np.exp(x))\n\n            offset = 0\n            for idx in range(N):\n                offset += lengths[idx - 1] if idx > 0 else 0\n                count = 0\n                for i in range(offset, offset + lengths[idx]):\n                    for j in range(offset, i):\n                        if label[i] == label[j]:\n                            continue\n                        sign = 1 if label[i] > label[j] else -1\n                        output[idx] += f(sign * (X[j] - X[i]))\n                        count += 1\n                if count > 0:\n                    output[idx] /= count\n            return [output]\n\n        op = core.CreateOperator(\n            'PairWiseLoss',\n            ['X', 'label', 'lengths'],\n            'out'\n        )\n\n        # Check against numpy reference\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, label, lengths],\n            reference=pair_wise_op,\n        )\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X, label, lengths], [0])\n        # Gradient check\n        self.assertGradientChecks(gc, op, [X, label, lengths], 0, [0])\n"
  },
  {
    "path": "caffe2/python/operator_test/rebatching_queue_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\n\nimport numpy as np\nimport numpy.testing as npt\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nimport functools\n\n\ndef primefac(n):\n    ret = []\n    divisor = 2\n    while divisor * divisor <= n:\n        while (n % divisor) == 0:\n            ret.append(divisor)\n            n = n // divisor\n        divisor = divisor + 1\n    if n > 1:\n        ret.append(n)\n    return ret\n\n\nclass TestReBatchingQueue(TestCase):\n    def test_rebatching_queue_single_enqueue_dequeue(self):\n        net = core.Net('net')\n\n        tensors = [\n            net.ConstantFill([], 1, value=1.0, run_once=False)\n            for times in range(3)\n        ]\n\n        queue = net.CreateRebatchingQueue([], 1, capacity=10, num_blobs=1)\n\n        net.EnqueueRebatchingQueue([queue, tensors[0]], [])\n        net.EnqueueRebatchingQueue([queue, tensors[1]], [])\n        net.EnqueueRebatchingQueue([queue, tensors[2]], [])\n\n        results = [\n            net.DequeueRebatchingQueue([queue], 1),\n            net.DequeueRebatchingQueue([queue], 1),\n            net.DequeueRebatchingQueue([queue], 1),\n        ]\n\n        workspace.RunNetOnce(net)\n\n        for idx in range(3):\n            self.assertEquals(workspace.FetchBlob(results[idx]), [1.0])\n\n    def test_rebatching_queue_multi_enqueue_dequeue(self):\n        net = core.Net('net')\n        workspace.FeedBlob(\n            \"tensors\", np.array([x for x in range(10)], np.int32)\n        )\n\n        queue = net.CreateRebatchingQueue([], 1, capacity=10, num_blobs=1)\n\n        net.EnqueueRebatchingQueue([queue, \"tensors\"], [], enqueue_batch=True)\n\n        results = [\n            net.DequeueRebatchingQueue([queue], 1, num_elements=5),\n            net.DequeueRebatchingQueue([queue], 1, num_elements=5),\n        ]\n\n        workspace.RunNetOnce(net)\n\n        npt.assert_array_equal(\n            workspace.FetchBlob(results[0]), workspace.FetchBlob(\"tensors\")[:5]\n        )\n        npt.assert_array_equal(\n            workspace.FetchBlob(results[1]), workspace.FetchBlob(\"tensors\")[5:]\n        )\n\n    def test_rebatching_queue_closes_properly(self):\n        net = core.Net('net')\n        workspace.FeedBlob(\n            \"tensors\", np.array([x for x in range(10)], np.int32)\n        )\n\n        queue = net.CreateRebatchingQueue([], 1, capacity=10, num_blobs=1)\n\n        net.EnqueueRebatchingQueue([queue, \"tensors\"], 0, enqueue_batch=True)\n\n        net.CloseRebatchingQueue([queue], 0)\n\n        results = [\n            net.DequeueRebatchingQueue([queue], 1, num_elements=5),\n            net.DequeueRebatchingQueue([queue], 1, num_elements=5),\n        ]\n\n        workspace.RunNetOnce(net)\n\n        npt.assert_array_equal(\n            workspace.FetchBlob(results[0]), workspace.FetchBlob(\"tensors\")[:5]\n        )\n        npt.assert_array_equal(\n            workspace.FetchBlob(results[1]), workspace.FetchBlob(\"tensors\")[5:]\n        )\n\n        # Enqueuing more should fail now since the queue is closed\n        net.EnqueueRebatchingQueue([queue, \"tensors\"], [], enqueue_batch=True)\n\n        with self.assertRaises(RuntimeError):\n            workspace.RunNetOnce(net)\n\n        # Dequeuing more should fail now since the queue is closed\n        results = [\n            net.DequeueRebatchingQueue([queue], 1, num_elements=5),\n        ]\n\n        with self.assertRaises(RuntimeError):\n            workspace.RunNetOnce(net)\n\n    def test_rebatching_queue_multiple_components(self):\n        NUM_BLOBS = 4\n        NUM_ELEMENTS = 10\n\n        net = core.Net('net')\n\n        workspace.blobs['complex_tensor'] = np.array(\n            [[x, x + 1] for x in range(NUM_ELEMENTS)], dtype=np.int32\n        )\n\n        tensors = [\n            net.GivenTensorIntFill(\n                [],\n                1,\n                shape=[NUM_ELEMENTS],\n                values=[x for x in range(NUM_ELEMENTS)]\n            ),\n            net.GivenTensorFill(\n                [],\n                1,\n                shape=[NUM_ELEMENTS],\n                values=[x * 1.0 for x in range(NUM_ELEMENTS)]\n            ),\n            net.GivenTensorBoolFill(\n                [],\n                1,\n                shape=[NUM_ELEMENTS],\n                values=[(x % 2 == 0) for x in range(NUM_ELEMENTS)]\n            ),\n            'complex_tensor',\n        ]\n\n        queue = net.CreateRebatchingQueue(\n            [], 1, capacity=10, num_blobs=NUM_BLOBS\n        )\n\n        net.EnqueueRebatchingQueue([queue] + tensors, [], enqueue_batch=True)\n\n        results = net.DequeueRebatchingQueue([queue], NUM_BLOBS, num_elements=5)\n\n        workspace.RunNetOnce(net)\n\n        for idx in range(NUM_BLOBS):\n            npt.assert_array_equal(\n                workspace.FetchBlob(results[idx]),\n                workspace.FetchBlob(tensors[idx])[:5]\n            )\n\n    @given(\n        num_producers=st.integers(1, 5),\n        num_consumers=st.integers(1, 5),\n        producer_input_size=st.integers(1, 10),\n        producer_num_iterations=st.integers(1, 10),\n        capacity=st.integers(1, 10)\n    )\n    def test_rebatching_parallel_producer_consumer(\n        self, num_producers, num_consumers, producer_input_size,\n        producer_num_iterations, capacity\n    ):\n        ### Init ###\n        total_inputs = producer_num_iterations * producer_input_size * num_producers\n        inputs = []\n        init_net = core.Net('init_net')\n        queue = init_net.CreateRebatchingQueue(\n            [], 1, capacity=capacity, num_blobs=1\n        )\n\n        ### Producers ###\n        producer_steps = []\n        for i in range(num_producers):\n            name = 'producer_%d' % i\n            net = core.Net(name)\n            values = [\n                producer_input_size * i + x for x in range(producer_input_size)\n            ]\n            for _ in range(producer_num_iterations):\n                inputs.extend(values)\n            tensors = net.GivenTensorIntFill(\n                [], 1, shape=[producer_input_size], values=values\n            )\n\n            net.EnqueueRebatchingQueue([queue, tensors], [], enqueue_batch=True)\n\n            step = core.execution_step(\n                name, net, num_iter=producer_num_iterations\n            )\n            producer_steps.append(step)\n\n        producer_step = core.execution_step(\n            'producer', [\n                core.execution_step(\n                    'producers', producer_steps, concurrent_substeps=True\n                )\n            ]\n        )\n\n        ### Consumers ###\n        outputs = []\n\n        def append(ins, outs):\n            # Extend is atomic\n            outputs.extend(ins[0].data.tolist())\n\n        consumer_steps = []\n        for i in range(num_consumers):\n            # This is just a way of deterministally read all the elements.\n            # We make `num_consumers` almost equal splits\n            # (the reminder goes to the last consumer).\n            num_elements_to_read = total_inputs // num_consumers\n            if i == num_consumers - 1:\n                num_elements_to_read = num_elements_to_read \\\n                    + total_inputs % num_consumers\n\n            # If we have nothing to read this consumer will be idle\n            if (num_elements_to_read == 0):\n                continue\n\n            # Now we have to make a split on number of iterations and the read\n            # size for each iteration. This is again just one of many\n            # deterministic  ways of doing it. We factorize the total number of\n            # elements we have to read and assign half of the factors to the\n            # iterations half to the read size.\n            factors = list(primefac(num_elements_to_read))\n\n            num_elements_per_iteration = functools.reduce(\n                lambda x, y: x * y, factors[len(factors) // 2:], 1\n            )\n\n            num_iterations = functools.reduce(\n                lambda x, y: x * y, factors[:len(factors) // 2], 1\n            )\n\n            name = 'consumer_%d' % i\n            net = core.Net(name)\n            blobs = net.DequeueRebatchingQueue(\n                [queue], 1, num_elements=num_elements_per_iteration\n            )\n            net.Python(append)([blobs], 0)\n            consumer_steps.append(\n                core.execution_step(name, net, num_iter=num_iterations)\n            )\n\n        consumer_step = core.execution_step(\n            'consumer', consumer_steps, concurrent_substeps=True\n        )\n\n        init_step = core.execution_step('init', init_net)\n        worker_step = core.execution_step(\n            'worker', [consumer_step, producer_step], concurrent_substeps=True\n        )\n\n        ### Execute Plan ###\n        plan = core.Plan('test')\n        plan.AddStep(init_step)\n        plan.AddStep(worker_step)\n\n        self.ws.run(plan)\n\n        ### Check Results ###\n        # We check that the outputs are a permutation of inputs\n        inputs.sort()\n        outputs.sort()\n        self.assertEquals(inputs, outputs)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/record_queue_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nfrom caffe2.python.dataset import Dataset\nfrom caffe2.python.schema import (\n    Struct, Map, Scalar, from_blob_list, NewRecord, FeedRecord)\nfrom caffe2.python.record_queue import RecordQueue\nfrom caffe2.python.test_util import TestCase\nimport numpy as np\n\n\nclass TestRecordQueue(TestCase):\n    def test_record_queue(self):\n        num_prod = 8\n        num_consume = 3\n        schema = Struct(\n            ('floats', Map(\n                Scalar(np.int32),\n                Scalar(np.float32))),\n        )\n        contents_raw = [\n            [1, 2, 3],  # len\n            [11, 21, 22, 31, 32, 33],  # key\n            [1.1, 2.1, 2.2, 3.1, 3.2, 3.3],  # value\n        ]\n        contents = from_blob_list(schema, contents_raw)\n        ds = Dataset(schema)\n        net = core.Net('init')\n        ds.init_empty(net)\n\n        content_blobs = NewRecord(net, contents)\n        FeedRecord(content_blobs, contents)\n        writer = ds.writer(init_net=net)\n        writer.write_record(net, content_blobs)\n        reader = ds.reader(init_net=net)\n\n        # prepare receiving dataset\n        rec_dataset = Dataset(contents, name='rec')\n        rec_dataset.init_empty(init_net=net)\n        rec_dataset_writer = rec_dataset.writer(init_net=net)\n\n        workspace.RunNetOnce(net)\n\n        queue = RecordQueue(contents, num_threads=num_prod)\n\n        def process(net, fields):\n            new_fields = []\n            for f in fields.field_blobs():\n                new_f = net.Copy(f)\n                new_fields.append(new_f)\n            new_fields = from_blob_list(fields, new_fields)\n            return new_fields\n\n        q_reader, q_step, q_exit, fields = queue.build(reader, process)\n        producer_step = core.execution_step('producer', [q_step, q_exit])\n\n        consumer_steps = []\n        for i in range(num_consume):\n            name = 'queue_reader_' + str(i)\n            net_consume = core.Net(name)\n            should_stop, fields = q_reader.read_record(net_consume)\n            step_consume = core.execution_step(name, net_consume)\n\n            name = 'dataset_writer_' + str(i)\n            net_dataset = core.Net(name)\n            rec_dataset_writer.write(net_dataset, fields.field_blobs())\n            step_dataset = core.execution_step(name, net_dataset)\n\n            step = core.execution_step(\n                'consumer_' + str(i),\n                [step_consume, step_dataset],\n                should_stop_blob=should_stop)\n            consumer_steps.append(step)\n        consumer_step = core.execution_step(\n            'consumers', consumer_steps, concurrent_substeps=True)\n\n        work_steps = core.execution_step(\n            'work', [producer_step, consumer_step], concurrent_substeps=True)\n\n        plan = core.Plan('test')\n        plan.AddStep(work_steps)\n        core.workspace.RunPlan(plan)\n        data = workspace.FetchBlobs(rec_dataset.get_blobs())\n        self.assertEqual(6, sum(data[0]))\n        self.assertEqual(150, sum(data[1]))\n        self.assertAlmostEqual(15, sum(data[2]), places=5)\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/recurrent_net_executor_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import model_helper, workspace, core, rnn_cell\nfrom caffe2.python.attention import AttentionType\n\nimport numpy as np\n\nimport unittest\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nfrom hypothesis import given\n\n\nclass TestRNNExecutor(unittest.TestCase):\n\n    def setUp(self):\n        self.batch_size = 8\n        self.input_dim = 20\n        self.hidden_dim = 30\n        self.encoder_dim = 40\n\n    @given(\n        T=st.integers(10, 100),\n        forward_only=st.booleans(),\n        **hu.gcs)\n    def test_lstm_with_attention_equal_simplenet(self, T, forward_only, gc, dc):\n        self.Tseq = [T, T // 2, T // 2 + T // 4, T, T // 2 + 1]\n        workspace.ResetWorkspace()\n        with core.DeviceScope(gc):\n            print(\"Run with device: {}, forward only: {}\".format(\n                gc, forward_only))\n\n            workspace.FeedBlob(\n                \"seq_lengths\",\n                np.array([T] * self.batch_size, dtype=np.int32)\n            )\n            workspace.FeedBlob(\"target\", np.random.rand(\n                T, self.batch_size, self.hidden_dim).astype(np.float32))\n            workspace.FeedBlob(\"hidden_init\", np.zeros(\n                [1, self.batch_size, self.hidden_dim], dtype=np.float32\n            ))\n            workspace.FeedBlob(\"cell_init\", np.zeros(\n                [1, self.batch_size, self.hidden_dim], dtype=np.float32\n            ))\n\n            model = model_helper.ModelHelper(name=\"lstm\")\n            model.net.AddExternalInputs([\"input\"])\n\n            init_blobs = []\n            hidden_init, cell_init, encoder_outputs = model.net.AddExternalInputs(\n                \"hidden_init\",\n                \"cell_init\",\n                \"encoder_outputs\"\n            )\n\n            awec_init = model.net.AddExternalInputs([\n                'initial_attention_weighted_encoder_context',\n            ])\n            init_blobs.extend([hidden_init, cell_init])\n\n            workspace.FeedBlob(\n                awec_init,\n                np.random.rand(1, self.batch_size, self.encoder_dim).astype(\n                    np.float32),\n            )\n            workspace.FeedBlob(\n                encoder_outputs,\n                np.random.rand(1, self.batch_size, self.encoder_dim).astype(\n                    np.float32),\n            )\n\n            outputs = rnn_cell.LSTMWithAttention(\n                model=model,\n                decoder_inputs=\"input\",\n                decoder_input_lengths=\"seq_lengths\",\n                initial_decoder_hidden_state=hidden_init,\n                initial_decoder_cell_state=cell_init,\n                initial_attention_weighted_encoder_context=awec_init,\n                encoder_output_dim=self.encoder_dim,\n                encoder_outputs=encoder_outputs,\n                encoder_lengths=None,\n                decoder_input_dim=self.input_dim,\n                decoder_state_dim=self.hidden_dim,\n                scope=\"\",\n                attention_type=AttentionType.Recurrent,\n                forward_only=forward_only,\n                outputs_with_grads=[0],\n            )\n            output = outputs[0]\n\n            print(outputs)\n            loss = model.AveragedLoss(\n                model.SquaredL2Distance([output, \"target\"], \"dist\"),\n                \"loss\"\n            )\n            # Add gradient ops\n            if not forward_only:\n                model.AddGradientOperators([loss])\n\n            # init\n            for init_blob in init_blobs:\n                workspace.FeedBlob(init_blob, np.zeros(\n                    [1, self.batch_size, self.hidden_dim], dtype=np.float32\n                ))\n\n            self._compare(model, forward_only)\n\n    @given(\n        num_layers=st.integers(1, 8),\n        T=st.integers(4, 100),\n        forward_only=st.booleans(),\n        **hu.gcs)\n    def test_lstm_equal_simplenet(self, num_layers, T, forward_only, gc, dc):\n        '''\n        Test that the RNN executor produces same results as\n        the non-executor (i.e running step nets as sequence of simple nets).\n        '''\n        self.Tseq = [T, T // 2, T // 2 + T // 4, T, T // 2 + 1]\n\n        workspace.ResetWorkspace()\n        with core.DeviceScope(gc):\n            print(\"Run with device: {}, forward only: {}\".format(\n                gc, forward_only))\n\n            workspace.FeedBlob(\n                \"seq_lengths\",\n                np.array([T] * self.batch_size, dtype=np.int32)\n            )\n            workspace.FeedBlob(\"target\", np.random.rand(\n                T, self.batch_size, self.hidden_dim).astype(np.float32))\n            workspace.FeedBlob(\"hidden_init\", np.zeros(\n                [1, self.batch_size, self.hidden_dim], dtype=np.float32\n            ))\n            workspace.FeedBlob(\"cell_init\", np.zeros(\n                [1, self.batch_size, self.hidden_dim], dtype=np.float32\n            ))\n\n            model = model_helper.ModelHelper(name=\"lstm\")\n            model.net.AddExternalInputs([\"input\"])\n\n            init_blobs = []\n            for i in range(num_layers):\n                hidden_init, cell_init = model.net.AddExternalInputs(\n                    \"hidden_init_{}\".format(i),\n                    \"cell_init_{}\".format(i)\n                )\n                init_blobs.extend([hidden_init, cell_init])\n\n            output, last_hidden, _, last_state = rnn_cell.LSTM(\n                model=model,\n                input_blob=\"input\",\n                seq_lengths=\"seq_lengths\",\n                initial_states=init_blobs,\n                dim_in=self.input_dim,\n                dim_out=[self.hidden_dim] * num_layers,\n                scope=\"\",\n                drop_states=True,\n                forward_only=forward_only,\n                return_last_layer_only=True,\n            )\n\n            loss = model.AveragedLoss(\n                model.SquaredL2Distance([output, \"target\"], \"dist\"),\n                \"loss\"\n            )\n            # Add gradient ops\n            if not forward_only:\n                model.AddGradientOperators([loss])\n\n            # init\n            for init_blob in init_blobs:\n                workspace.FeedBlob(init_blob, np.zeros(\n                    [1, self.batch_size, self.hidden_dim], dtype=np.float32\n                ))\n\n            self._compare(model, forward_only)\n\n    def _compare(self, model, forward_only):\n        # Store list of blobs that exist in the beginning\n        workspace.RunNetOnce(model.param_init_net)\n        init_ws = {k: workspace.FetchBlob(k) for k in workspace.Blobs()}\n\n        # Run with executor\n        for enable_executor in [0, 1]:\n            self.enable_rnn_executor(model.net, enable_executor, forward_only)\n            workspace.ResetWorkspace()\n\n            # Reset original state\n            for k, v in init_ws.items():\n                workspace.FeedBlob(k, v)\n\n            np.random.seed(10022015)\n            ws = {}\n            for j in range(len(self.Tseq)):\n                input_shape = [self.Tseq[j], self.batch_size, self.input_dim]\n                workspace.FeedBlob(\n                    \"input\", np.random.rand(*input_shape).astype(np.float32))\n                workspace.FeedBlob(\n                    \"target\",\n                    np.random.rand(\n                        self.Tseq[j], self.batch_size, self.hidden_dim\n                    ).astype(np.float32))\n                if j == 0:\n                    workspace.CreateNet(model.net, overwrite=True)\n\n                workspace.RunNet(model.net.Proto().name)\n\n                # Store results for each iteration\n                for k in workspace.Blobs():\n                    ws[k + \".\" + str(j)] = workspace.FetchBlob(k)\n\n            if enable_executor:\n                rnn_exec_ws = ws\n            else:\n                non_exec_ws = ws\n\n        # Test that all blobs are equal after running with executor\n        # or without.\n        self.assertEqual(list(non_exec_ws.keys()), list(rnn_exec_ws.keys()))\n\n        mismatch = False\n        for k in rnn_exec_ws.keys():\n            non_exec_v = non_exec_ws[k]\n            rnn_exec_v = rnn_exec_ws[k]\n            if type(non_exec_v) is np.ndarray:\n                if not np.array_equal(non_exec_v, rnn_exec_v):\n                    print(\"Mismatch: {}\".format(k))\n                    nv = non_exec_v.flatten()\n                    rv = rnn_exec_v.flatten()\n                    c = 0\n                    for j in range(len(nv)):\n                        if rv[j] != nv[j]:\n                            print(j, rv[j], nv[j])\n                            c += 1\n                            if c == 10:\n                                break\n\n                    mismatch = True\n\n        self.assertFalse(mismatch)\n\n    def enable_rnn_executor(self, net, value, forward_only):\n        num_found = 0\n        for op in net.Proto().op:\n            if op.type.startswith(\"RecurrentNetwork\"):\n                for arg in op.arg:\n                    if arg.name == 'enable_rnn_executor':\n                        arg.i = value\n                        num_found += 1\n        # This sanity check is so that if someone changes the\n        # enable_rnn_executor parameter name, the test will\n        # start failing as this function will become defective.\n        self.assertEqual(1 if forward_only else 2, num_found)\n\n    if __name__ == \"__main__\":\n        import unittest\n        import random\n        random.seed(2603)\n        workspace.GlobalInit([\n            'caffe2',\n            '--caffe2_log_level=0',\n            '--caffe2_rnn_executor=1'])\n        unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/recurrent_network_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import recurrent, workspace\nfrom caffe2.python.model_helper import ModelHelper\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass RecurrentNetworkTest(hu.HypothesisTestCase):\n    @given(T=st.integers(1, 4),\n           n=st.integers(1, 5),\n           d=st.integers(1, 5))\n    def test_sum_mul(self, T, n, d):\n        model = ModelHelper(name='external')\n\n        input_blob, initial_input_blob = model.net.AddExternalInputs(\n            'input', 'initial_input')\n\n        step = ModelHelper(name='step', param_model=model)\n        input_t, output_t_prev = step.net.AddExternalInput(\n            'input_t', 'output_t_prev')\n        output_t_internal = step.net.Sum([input_t, output_t_prev])\n        output_t = step.net.Mul([input_t, output_t_internal])\n        step.net.AddExternalOutput(output_t)\n\n        self.simple_rnn(T, n, d, model, step, input_t, output_t, output_t_prev,\n                        input_blob, initial_input_blob)\n\n    @given(T=st.integers(1, 4),\n           n=st.integers(1, 5),\n           d=st.integers(1, 5))\n    def test_mul(self, T, n, d):\n        model = ModelHelper(name='external')\n\n        input_blob, initial_input_blob = model.net.AddExternalInputs(\n            'input', 'initial_input')\n\n        step = ModelHelper(name='step', param_model=model)\n        input_t, output_t_prev = step.net.AddExternalInput(\n            'input_t', 'output_t_prev')\n        output_t = step.net.Mul([input_t, output_t_prev])\n        step.net.AddExternalOutput(output_t)\n\n        self.simple_rnn(T, n, d, model, step, input_t, output_t, output_t_prev,\n                        input_blob, initial_input_blob)\n\n    @given(T=st.integers(1, 4),\n           n=st.integers(1, 5),\n           d=st.integers(1, 5))\n    def test_extract(self, T, n, d):\n        model = ModelHelper(name='external')\n        workspace.ResetWorkspace()\n\n        input_blob, initial_input_blob = model.net.AddExternalInputs(\n            'input', 'initial_input')\n\n        step = ModelHelper(name='step', param_model=model)\n        input_t, output_t_prev = step.net.AddExternalInput(\n            'input_t', 'output_t_prev')\n        output_t = step.net.Mul([input_t, output_t_prev])\n        step.net.AddExternalOutput(output_t)\n\n        inputs = np.random.randn(T, n, d).astype(np.float32)\n        initial_input = np.random.randn(1, n, d).astype(np.float32)\n        recurrent.recurrent_net(\n            net=model.net,\n            cell_net=step.net,\n            inputs=[(input_t, input_blob)],\n            initial_cell_inputs=[(output_t_prev, initial_input_blob)],\n            links={output_t_prev: output_t},\n            scope=\"test_rnn_sum_mull\",\n        )\n\n        workspace.blobs[input_blob] = inputs\n        workspace.blobs[initial_input_blob] = initial_input\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.CreateNet(model.net)\n\n        prefix = \"extractTest\"\n\n        workspace.RunNet(model.net.Proto().name, T)\n        retrieved_blobs = recurrent.retrieve_step_blobs(\n            model.net, prefix\n        )\n\n        # needed for python3.6, which returns bytearrays instead of str\n        retrieved_blobs = [x.decode() for x in retrieved_blobs]\n\n        for i in range(T):\n            blob_name = prefix + \"_\" + \"input_t\" + str(i)\n            self.assertTrue(\n                blob_name in retrieved_blobs,\n                \"blob extraction failed on timestep {}\\\n                    . \\n\\n Extracted Blobs: {} \\n\\n Looking for {}\\\n                    .\".format(i, retrieved_blobs, blob_name)\n            )\n\n    def simple_rnn(self, T, n, d, model, step, input_t, output_t, output_t_prev,\n                   input_blob, initial_input_blob):\n\n        input = np.random.randn(T, n, d).astype(np.float32)\n        initial_input = np.random.randn(1, n, d).astype(np.float32)\n        print(locals())\n        recurrent.recurrent_net(\n            net=model.net,\n            cell_net=step.net,\n            inputs=[(input_t, input_blob)],\n            initial_cell_inputs=[(output_t_prev, initial_input_blob)],\n            links={output_t_prev: output_t},\n            scope=\"test_rnn_sum_mull\",\n        )\n        workspace.blobs[input_blob] = input\n        workspace.blobs[initial_input_blob] = initial_input\n\n        op = model.net._net.op[-1]\n        # Just conviniently store all inputs in an array in the same\n        # order as op.input\n        inputs = [workspace.blobs[name] for name in op.input]\n\n        def reference(input, initial_input):\n            global_ws_name = workspace.CurrentWorkspace()\n            input_all = workspace.blobs[input_blob]\n\n            workspace.SwitchWorkspace(\"ref\", create_if_missing=True)\n            workspace.blobs[input_blob] = input\n            workspace.blobs[output_t_prev] = initial_input.reshape(n, d)\n            res_all = np.zeros(shape=input.shape, dtype=np.float32)\n\n            for t_cur in range(T):\n                workspace.blobs[input_t] = input_all[t_cur]\n                workspace.RunNetOnce(step.net)\n                result_t = workspace.blobs[output_t]\n                workspace.blobs[output_t_prev] = result_t\n                res_all[t_cur] = result_t\n\n            workspace.SwitchWorkspace(global_ws_name)\n\n            shape = list(input.shape)\n            shape[0] = 1\n            return (res_all, res_all[-1].reshape(shape))\n\n        self.assertReferenceChecks(\n            device_option=hu.cpu_do,\n            op=op,\n            inputs=inputs,\n            reference=reference,\n            output_to_grad=op.output[0],\n            outputs_to_check=[0, 1],\n        )\n\n        self.assertGradientChecks(\n            device_option=hu.cpu_do,\n            op=op,\n            inputs=inputs,\n            outputs_to_check=0,\n            outputs_with_grads=[0],\n            threshold=0.01,\n            stepsize=0.005,\n        )\n\n    # Hacky version of 1-D convolution\n    def _convolution_1d(\n        self,\n        model,\n        inputs,\n        conv_window,\n        conv_filter,\n        conv_bias,\n        output_name,\n        left_pad,\n    ):\n        if left_pad:\n            padding_width = conv_window - 1\n        else:\n            padding_width = 0\n\n        # [batch_size, inputs_length, state_size]\n        inputs_transposed = model.net.Transpose(\n            inputs,\n            'inputs_transposed',\n            axes=[1, 0, 2],\n        )\n        # [batch_size, 1, inputs_length, state_size]\n        inputs_transposed_4d = model.net.ExpandDims(\n            inputs_transposed,\n            'inputs_transposed_4d',\n            dims=[1],\n        )\n        # [batch_size, 1, inputs_length - conv_window + 1, state_size]\n        output_transposed_4d = model.net.Conv(\n            [inputs_transposed_4d, conv_filter, conv_bias],\n            output_name + '_transposed_4d',\n            kernel_h=1,\n            kernel_w=conv_window,\n            order='NHWC',\n            pad_t=0,\n            pad_l=padding_width,\n            pad_b=0,\n            pad_r=0,\n        )\n        # [batch_size, inputs_length - conv_window + 1, state_size]\n        output_transposed = model.net.Squeeze(\n            output_transposed_4d,\n            output_name + '_transposed',\n            dims=[1],\n        )\n        # [inputs_length - conv_window + 1, batch_size, state_size]\n        output = model.net.Transpose(\n            output_transposed,\n            output_name,\n            axes=[1, 0, 2],\n        )\n        return output\n\n    @given(sequence_length=st.integers(3, 7),\n           conv_window=st.integers(1, 3),\n           batch_size=st.integers(1, 5),\n           state_size=st.integers(1, 5))\n    def test_stateful_convolution_forward_only(\n        self,\n        sequence_length,\n        conv_window,\n        batch_size,\n        state_size,\n    ):\n        '''\n        This unit test demonstrates another ways of using RecurrentNetwork.\n\n        Imagine, that you want to compute convolution over a sequence,\n        but sequence elements are not given to you from the beginning,\n        so you have to loop over the sequence and compute convolution\n        for each element separately. This situation can occur,\n        during inference/generation step of the neural networks.\n\n        First of all, you have to provide actual input via recurrent states,\n        since the input of RecurrentNetwork should be known in advance.\n        Here, we use `fake_inputs` as the input,\n        and it's used by the op to extract batch size and sequence length.\n        The actual input sequence is stored in the recurrent state\n        `input_state`. At every step we generate a new element via input_state_t\n        (in this example, input_state_t is generated at random, but\n        in a real situation it can be created using convolution output\n        from the previous step).\n\n        A few important differences from regular RecurrentNetwork usecase:\n\n        1. input_state_t_prev is not only a single previous element of\n        input_state sequence. It is last conv_window elements including (!)\n        the current one - input_state_t. We specify that using `link_window`\n        argument of RecurrentNetwork. We need that many elements to\n        compute a single convolution step. Also, note that `link_window`\n        specifies how many elements to link starting at\n        `timestep` + `link_offset` position.\n\n        2. First few steps might require additional zero padding from the left,\n        since there is no enough element of input_state sequence are available.\n        So the initial_state for input_state contains several elements\n        (exactly how many pads we need for the first step). Also, because of\n        that all offseting over input_state sequnece is being shifted\n        by length of initial_input_state: see `link_offset` and `alias_offset`\n        arguments of RecurrentNetwork.\n\n        In this test, we assert that we get the same result\n        if we apply convolution over all elements simultaneously,\n        since the whole input_state sequence was generated at the end.\n    '''\n        model = ModelHelper(name='model')\n        fake_inputs = model.param_init_net.UniformFill(\n            [],\n            'fake_inputs',\n            min=-1.0,\n            max=1.0,\n            shape=[sequence_length, batch_size, state_size],\n        )\n        initial_input_state = model.param_init_net.ConstantFill(\n            [],\n            'initial_input_state',\n            value=0.0,\n            shape=[conv_window - 1, batch_size, state_size],\n        )\n        initial_output_state = model.param_init_net.ConstantFill(\n            [],\n            'initial_output_state',\n            value=0.0,\n            shape=[1, batch_size, state_size],\n        )\n        step_model = ModelHelper(name='step_model', param_model=model)\n        (\n            fake_input_t,\n            timestep,\n            input_state_t_prev,\n        ) = step_model.net.AddExternalInputs(\n            'fake_input_t',\n            'timestep',\n            'input_state_t_prev',\n        )\n        conv_filter = step_model.param_init_net.XavierFill(\n            [],\n            'conv_filter',\n            shape=[state_size, 1, conv_window, state_size],\n        )\n        conv_bias = step_model.param_init_net.ConstantFill(\n            [],\n            'conv_bias',\n            shape=[state_size],\n            value=0.0,\n        )\n        step_model.params.extend([conv_filter, conv_bias])\n        input_state_t = step_model.net.UniformFill(\n            [],\n            'input_state_t',\n            min=-1.0,\n            max=1.0,\n            shape=[1, batch_size, state_size],\n        )\n        output_state_t = self._convolution_1d(\n            model=step_model,\n            inputs=input_state_t_prev,\n            conv_window=conv_window,\n            conv_filter=conv_filter,\n            conv_bias=conv_bias,\n            output_name='output_state_t',\n            left_pad=False,\n        )\n        initial_recurrent_states = [initial_input_state, initial_output_state]\n        all_inputs = (\n            [fake_inputs] + step_model.params + initial_recurrent_states\n        )\n        all_outputs = ['input_state_all', 'output_state_all']\n        recurrent_states = ['input_state', 'output_state']\n        input_state_all, output_state_all, _ = model.net.RecurrentNetwork(\n            all_inputs,\n            all_outputs + ['step_workspaces'],\n            param=[all_inputs.index(p) for p in step_model.params],\n            alias_src=recurrent_states,\n            alias_dst=all_outputs,\n            alias_offset=[conv_window - 1, 1],\n            recurrent_states=recurrent_states,\n            initial_recurrent_state_ids=[\n                all_inputs.index(s) for s in initial_recurrent_states\n            ],\n            link_internal=[\n                str(input_state_t_prev),\n                str(input_state_t),\n                str(output_state_t),\n            ],\n            link_external=['input_state', 'input_state', 'output_state'],\n            link_offset=[0, conv_window - 1, 1],\n            link_window=[conv_window, 1, 1],\n            backward_link_internal=[],\n            backward_link_external=[],\n            backward_link_offset=[],\n            step_net=step_model.net.Proto(),\n            timestep='timestep' if timestep is None else str(timestep),\n            outputs_with_grads=[],\n        )\n\n        output_states_2 = self._convolution_1d(\n            model=model,\n            inputs=input_state_all,\n            conv_window=conv_window,\n            conv_filter=conv_filter,\n            conv_bias=conv_bias,\n            output_name='output_states_2',\n            left_pad=True,\n        )\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n\n        np.testing.assert_almost_equal(\n            workspace.FetchBlob(output_state_all),\n            workspace.FetchBlob(output_states_2),\n            decimal=3,\n        )\n"
  },
  {
    "path": "caffe2/python/operator_test/reduce_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nfrom hypothesis import given\n\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\nimport functools\nimport itertools as it\n\n\nclass TestReduceOps(hu.HypothesisTestCase):\n    @given(\n        d0=st.integers(1, 5),\n        d1=st.integers(1, 5),\n        d2=st.integers(1, 5),\n        d3=st.integers(1, 5),\n        keepdims=st.integers(0, 1),\n        seed=st.integers(0, 2**32 - 1),\n        **hu.gcs_cpu_only)\n    def test_reduce_sum_mean(self, d0, d1, d2, d3, keepdims, seed, gc, dc):\n        def reduce_mean_ref(data, axis, keepdims):\n            return [np.mean(data, axis=axis, keepdims=keepdims)]\n\n        def reduce_sum_ref(data, axis, keepdims):\n            return [np.sum(data, axis=axis, keepdims=keepdims)]\n\n        def reduce_op_test(op_name, op_ref, data, axes, keepdims, device):\n            op = core.CreateOperator(\n                op_name,\n                [\"data\"],\n                [\"Y\"],\n                axes=axes,\n                keepdims=keepdims,\n            )\n\n            self.assertReferenceChecks(device, op, [data],\n                                       functools.partial(\n                                           op_ref,\n                                           axis=axes,\n                                           keepdims=keepdims))\n\n        np.random.seed(seed)\n        for axes in it.combinations(range(4), 2):\n            data = np.random.randn(d0, d1, d2, d3).astype(np.float32)\n\n            reduce_op_test(\"ReduceMean\", reduce_mean_ref, data, axes, keepdims,\n                           gc)\n\n            reduce_op_test(\"ReduceSum\", reduce_sum_ref, data, axes, keepdims,\n                           gc)\n\n\nclass TestReduceFrontReductions(hu.HypothesisTestCase):\n    def grad_variant_input_test(self, grad_op_name, X, ref, num_reduce_dim):\n        workspace.ResetWorkspace()\n\n        Y = np.array(ref(X)[0]).astype(np.float32)\n        dY = np.array(np.random.rand(*Y.shape)).astype(np.float32)\n        shape = np.array(X.shape).astype(np.int64)\n\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"dY\", dY)\n        workspace.FeedBlob(\"shape\", shape)\n\n        grad_op = core.CreateOperator(\n            grad_op_name, [\"dY\", \"X\"], [\"dX\"], num_reduce_dim=num_reduce_dim)\n\n        grad_op1 = core.CreateOperator(\n            grad_op_name, [\"dY\", \"shape\"], [\"dX1\"],\n            num_reduce_dim=num_reduce_dim)\n\n        workspace.RunOperatorOnce(grad_op)\n        workspace.RunOperatorOnce(grad_op1)\n\n        dX = workspace.FetchBlob(\"dX\")\n        dX1 = workspace.FetchBlob(\"dX1\")\n        np.testing.assert_array_equal(dX, dX1)\n\n    def max_op_test(self, op_name, num_reduce_dim, gc, dc, in_data, in_names, ref_max):\n\n        op = core.CreateOperator(\n            op_name,\n            in_names,\n            [\"outputs\"],\n            num_reduce_dim=num_reduce_dim\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=in_data,\n            reference=ref_max,\n        )\n\n        # Skip gradient check because it is too unreliable with max.\n        # Just check CPU and CUDA have same results\n        Y = np.array(ref_max(*in_data)[0]).astype(np.float32)\n        dY = np.array(np.random.rand(*Y.shape)).astype(np.float32)\n        if len(in_data) == 2:\n            grad_in_names = [\"dY\", in_names[0], \"Y\", in_names[1]]\n            grad_in_data = [dY, in_data[0], Y, in_data[1]]\n        else:\n            grad_in_names = [\"dY\", in_names[0], \"Y\"]\n            grad_in_data = [dY, in_data[0], Y]\n\n        grad_op = core.CreateOperator(\n            op_name + \"Gradient\",\n            grad_in_names,\n            [\"dX\"],\n            num_reduce_dim=num_reduce_dim\n        )\n        self.assertDeviceChecks(dc, grad_op, grad_in_data, [0])\n\n    def reduce_op_test(self, op_name, op_ref, in_data, in_names,\n                       num_reduce_dims, device):\n        op = core.CreateOperator(\n            op_name,\n            in_names,\n            [\"outputs\"],\n            num_reduce_dim=num_reduce_dims\n        )\n\n        self.assertReferenceChecks(\n            device_option=device,\n            op=op,\n            inputs=in_data,\n            reference=op_ref\n        )\n\n        self.assertGradientChecks(\n            device, op, in_data, 0, [0], stepsize=1e-2, threshold=1e-2)\n\n    @given(num_reduce_dim=st.integers(0, 4), **hu.gcs)\n    def test_reduce_front_sum(self, num_reduce_dim, gc, dc):\n        X = np.random.rand(7, 4, 3, 5).astype(np.float32)\n\n        def ref_sum(X):\n            return [np.sum(X, axis=(tuple(range(num_reduce_dim))))]\n\n        self.reduce_op_test(\n            \"ReduceFrontSum\", ref_sum, [X], [\"input\"], num_reduce_dim, gc)\n        self.grad_variant_input_test(\n            \"ReduceFrontSumGradient\", X, ref_sum, num_reduce_dim)\n\n    @given(**hu.gcs)\n    def test_reduce_front_sum_with_length(self, dc, gc):\n        num_reduce_dim = 1\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        batch_size = int(np.prod([2, 3, 4, 5][num_reduce_dim:]))\n        d = 120 // batch_size\n        lengths = np.random.randint(1, d, size=batch_size).astype(np.int32)\n\n        def ref_sum(X, lengths):\n            Y = X.reshape(d, lengths.size)\n            rv = np.zeros((lengths.size, 1)).astype(np.float32)\n            for ii in range(lengths.size):\n                rv[ii] = np.sum(Y[:lengths[ii], ii])\n            return [rv.reshape((2, 3, 4, 5)[num_reduce_dim:])]\n\n        self.reduce_op_test(\n            \"ReduceFrontSum\", ref_sum, [X, lengths], [\"input\", \"lengths\"],\n            num_reduce_dim, gc)\n\n    @given(num_reduce_dim=st.integers(0, 4), **hu.gcs)\n    def test_reduce_front_mean(self, num_reduce_dim, gc, dc):\n        X = np.random.rand(6, 7, 8, 2).astype(np.float32)\n\n        def ref_mean(X):\n            return [np.mean(X, axis=(tuple(range(num_reduce_dim))))]\n\n        self.reduce_op_test(\n            \"ReduceFrontMean\", ref_mean, [X], [\"input\"], num_reduce_dim, gc)\n        self.grad_variant_input_test(\n            \"ReduceFrontMeanGradient\", X, ref_mean, num_reduce_dim)\n\n    @given(**hu.gcs)\n    def test_reduce_front_mean_with_length(self, dc, gc):\n        num_reduce_dim = 1\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        batch_size = int(np.prod([2, 3, 4, 5][num_reduce_dim:]))\n        d = 120 // batch_size\n        lengths = np.random.randint(1, d, size=batch_size).astype(np.int32)\n\n        def ref_mean(X, lengths):\n            Y = X.reshape(d, lengths.size)\n            rv = np.zeros((lengths.size, 1)).astype(np.float32)\n            for ii in range(lengths.size):\n                rv[ii] = np.mean(Y[:lengths[ii], ii])\n            return [rv.reshape((2, 3, 4, 5)[num_reduce_dim:])]\n\n        self.reduce_op_test(\n            \"ReduceFrontMean\", ref_mean, [X, lengths], [\"input\", \"lengths\"],\n            num_reduce_dim, gc)\n\n    @given(num_reduce_dim=st.integers(0, 4), **hu.gcs)\n    def test_reduce_front_max(self, num_reduce_dim, gc, dc):\n        X = np.random.rand(6, 7, 8, 2).astype(np.float32)\n\n        def ref_frontmax(X):\n            return [np.max(X, axis=(tuple(range(num_reduce_dim))))]\n\n        self.max_op_test(\n            \"ReduceFrontMax\", num_reduce_dim, gc, dc, [X], [\"X\"], ref_frontmax)\n\n    @given(**hu.gcs)\n    def test_reduce_front_max_with_length(self, dc, gc):\n        num_reduce_dim = 1\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        batch_size = int(np.prod([2, 3, 4, 5][num_reduce_dim:]))\n        d = 120 // batch_size\n        lengths = np.random.randint(1, d, size=batch_size).astype(np.int32)\n\n        def ref_max(X, lengths):\n            Y = X.reshape(d, lengths.size)\n            rv = np.zeros((lengths.size, 1)).astype(np.float32)\n            for ii in range(lengths.size):\n                rv[ii] = np.max(Y[:lengths[ii], ii])\n            return [rv.reshape((2, 3, 4, 5)[num_reduce_dim:])]\n\n        self.max_op_test(\n            \"ReduceFrontMax\", num_reduce_dim, gc, dc, [X, lengths],\n            [\"X\", \"lengths\"], ref_max)\n\n    @given(num_reduce_dim=st.integers(0, 4), **hu.gcs)\n    def test_reduce_back_max(self, num_reduce_dim, gc, dc):\n        X = np.random.rand(6, 7, 8, 2).astype(np.float32)\n\n        def ref_backmax(X):\n            return [np.max(X, axis=(0, 1, 2, 3)[4 - num_reduce_dim:])]\n\n        self.max_op_test(\n            \"ReduceBackMax\", num_reduce_dim, gc, dc, [X], [\"X\"], ref_backmax)\n\n    @given(**hu.gcs)\n    def test_reduce_back_max_with_length(self, gc, dc):\n        num_reduce_dim = 1\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        batch_size = int(np.prod([2, 3, 4, 5][:4 - num_reduce_dim]))\n        d = 120 // batch_size\n        lengths = np.random.randint(1, d, size=batch_size).astype(np.int32)\n\n        def ref_max(X, lengths):\n            Y = X.reshape(lengths.size, d)\n            rv = np.zeros((lengths.size, 1)).astype(np.float32)\n            for ii in range(lengths.size):\n                rv[ii] = np.max(Y[ii, :lengths[ii]])\n            return [rv.reshape((2, 3, 4, 5)[:4 - num_reduce_dim])]\n\n        self.max_op_test(\n            \"ReduceBackMax\", num_reduce_dim, gc, dc, [X, lengths],\n            [\"X\", \"lengths\"], ref_max)\n\n    @given(**hu.gcs)\n    def test_reduce_back_sum(self, dc, gc):\n        num_reduce_dim = 1\n        X = np.random.rand(6, 7, 8, 2).astype(np.float32)\n\n        def ref_sum(X):\n            return [np.sum(X, axis=(0, 1, 2, 3)[4 - num_reduce_dim:])]\n\n        self.reduce_op_test(\n            \"ReduceBackSum\", ref_sum, [X], [\"input\"], num_reduce_dim, gc)\n        self.grad_variant_input_test(\n            \"ReduceBackSumGradient\", X, ref_sum, num_reduce_dim)\n\n    @given(**hu.gcs)\n    def test_reduce_back_sum_with_length(self, dc, gc):\n        num_reduce_dim = 1\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        batch_size = int(np.prod([2, 3, 4, 5][:4 - num_reduce_dim]))\n        d = 120 // batch_size\n        lengths = np.random.randint(1, d, size=batch_size).astype(np.int32)\n\n        def ref_sum(X, lengths):\n            Y = X.reshape(lengths.size, d)\n            rv = np.zeros((lengths.size, 1)).astype(np.float32)\n            for ii in range(lengths.size):\n                rv[ii] = np.sum(Y[ii, :lengths[ii]])\n            return [rv.reshape((2, 3, 4, 5)[:4 - num_reduce_dim])]\n\n        self.reduce_op_test(\n            \"ReduceBackSum\", ref_sum, [X, lengths], [\"input\", \"lengths\"],\n            num_reduce_dim, gc)\n\n    @given(num_reduce_dim=st.integers(0, 4), **hu.gcs)\n    def test_reduce_back_mean(self, num_reduce_dim, dc, gc):\n        X = np.random.rand(6, 7, 8, 2).astype(np.float32)\n\n        def ref_mean(X):\n            return [np.mean(X, axis=(0, 1, 2, 3)[4 - num_reduce_dim:])]\n\n        self.reduce_op_test(\n            \"ReduceBackMean\", ref_mean, [X], [\"input\"], num_reduce_dim, gc)\n        self.grad_variant_input_test(\n            \"ReduceBackMeanGradient\", X, ref_mean, num_reduce_dim)\n\n    @given(**hu.gcs)\n    def test_reduce_back_mean_with_length(self, dc, gc):\n        num_reduce_dim = 1\n        X = np.random.rand(2, 3, 4, 5).astype(np.float32)\n        batch_size = int(np.prod([2, 3, 4, 5][:4 - num_reduce_dim]))\n        d = 120 // batch_size\n        lengths = np.random.randint(1, d, size=batch_size).astype(np.int32)\n\n        def ref_mean(X, lengths):\n            Y = X.reshape(lengths.size, d)\n            rv = np.zeros((lengths.size, 1)).astype(np.float32)\n            for ii in range(lengths.size):\n                rv[ii] = np.mean(Y[ii, :lengths[ii]])\n            return [rv.reshape((2, 3, 4, 5)[:4 - num_reduce_dim])]\n\n        self.reduce_op_test(\n            \"ReduceBackMean\", ref_mean, [X, lengths], [\"input\", \"lengths\"],\n            num_reduce_dim, gc)\n\n"
  },
  {
    "path": "caffe2/python/operator_test/reduction_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import assume, given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\nfrom caffe2.proto import caffe2_pb2\n\n\nclass TestReductionOps(hu.HypothesisTestCase):\n\n    @given(n=st.integers(5, 8), **hu.gcs)\n    def test_elementwise_sum(self, n, gc, dc):\n        X = np.random.rand(n).astype(np.float32)\n\n        def sum_op(X):\n            return [np.sum(X)]\n\n        op = core.CreateOperator(\n            \"SumElements\",\n            [\"X\"],\n            [\"y\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=sum_op,\n        )\n\n        self.assertGradientChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            outputs_to_check=0,\n            outputs_with_grads=[0],\n        )\n\n    @given(n=st.integers(1, 65536),\n           dtype=st.sampled_from([np.float32, np.float16]),\n           **hu.gcs)\n    def test_elementwise_sqrsum(self, n, dtype, gc, dc):\n        if dtype == np.float16:\n            # fp16 is only supported with CUDA\n            assume(gc.device_type == caffe2_pb2.CUDA)\n            dc = [d for d in dc if d.device_type == caffe2_pb2.CUDA]\n\n        X = np.random.rand(n).astype(dtype)\n\n        def sumsqr_op(X):\n            return [np.sum(X * X)]\n\n        op = core.CreateOperator(\n            \"SumSqrElements\",\n            [\"X\"],\n            [\"y\"]\n        )\n\n        threshold = 0.01 if dtype == np.float16 else 0.005\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=sumsqr_op,\n            threshold=threshold,\n        )\n\n    @given(n=st.integers(5, 8), **hu.gcs)\n    def test_elementwise_avg(self, n, gc, dc):\n        X = np.random.rand(n).astype(np.float32)\n\n        def avg_op(X):\n            return [np.mean(X)]\n\n        op = core.CreateOperator(\n            \"SumElements\",\n            [\"X\"],\n            [\"y\"],\n            average=1\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=avg_op,\n        )\n\n        self.assertGradientChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            outputs_to_check=0,\n            outputs_with_grads=[0],\n        )\n\n    @given(batch_size=st.integers(1, 3),\n           m=st.integers(1, 3),\n           n=st.integers(1, 4),\n           **hu.gcs)\n    def test_rowwise_max(self, batch_size, m, n, gc, dc):\n        X = np.random.rand(batch_size, m, n).astype(np.float32)\n\n        def rowwise_max(X):\n            return [np.max(X, axis=2)]\n\n        op = core.CreateOperator(\n            \"RowwiseMax\",\n            [\"x\"],\n            [\"y\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=rowwise_max,\n        )\n\n    @given(batch_size=st.integers(1, 3),\n           m=st.integers(1, 3),\n           n=st.integers(1, 4),\n           **hu.gcs)\n    def test_columnwise_max(self, batch_size, m, n, gc, dc):\n        X = np.random.rand(batch_size, m, n).astype(np.float32)\n\n        def columnwise_max(X):\n            return [np.max(X, axis=1)]\n\n        op = core.CreateOperator(\n            \"ColwiseMax\",\n            [\"x\"],\n            [\"y\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=columnwise_max,\n        )\n"
  },
  {
    "path": "caffe2/python/operator_test/relu_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport caffe2.python.hypothesis_test_util as hu\nimport caffe2.python.mkl_test_util as mu\nimport numpy as np\n\nimport unittest\n\n\nclass TestRelu(hu.HypothesisTestCase):\n\n    @given(X=hu.tensor(),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **mu.gcs)\n    def test_relu(self, X, gc, dc, engine):\n        op = core.CreateOperator(\"Relu\", [\"X\"], [\"Y\"], engine=engine)\n        # go away from the origin point to avoid kink problems\n        X += 0.02 * np.sign(X)\n        X[X == 0.0] += 0.02\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n\n"
  },
  {
    "path": "caffe2/python/operator_test/reshape_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport numpy as np\n\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\nfrom caffe2.proto import caffe2_pb2\n\n\nclass TestLengthsToShapeOps(TestCase):\n    def test_lengths_to_shape_ops(self):\n        workspace.FeedBlob('l', np.array([200, 200, 200], dtype=np.int32))\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'LengthsToShape', ['l'], ['s']))\n        workspace.FeedBlob('res', np.array([3, 200], dtype=np.int32))\n        assert ((workspace.FetchBlob('s') == workspace.FetchBlob('res')).all())\n\n    def test_reshape_ops(self):\n        workspace.FeedBlob('res', np.array([[0, 0, 0, 0]], dtype=np.float32))\n        workspace.FeedBlob('shape', np.array([1, 4], dtype=np.int32))\n        workspace.FeedBlob('input', np.zeros((2, 2), dtype=np.float32))\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'Reshape', ['input', 'shape'], ['output', 'old_shape']))\n        assert ((workspace.FetchBlob('output') ==\n                 workspace.FetchBlob('res')).all())\n\n    def test_basic_reshape(self):\n        _test_reshape(old_shape=(4, 2, 1), new_shape=(2, 4))\n        _test_reshape(old_shape=(4, 2, 1), new_shape=(2, 4), arg_shape=False)\n\n    def test_missing_dim(self):\n        _test_reshape(old_shape=(4, 2, 1), new_shape=(-1, 8))\n        _test_reshape(old_shape=(4, 2, 1), new_shape=(-1, 8), arg_shape=False)\n\n    def test_in_place(self):\n        _test_reshape(old_shape=(4, 2, 1), new_shape=(-1, 8), in_place=True)\n        _test_reshape(old_shape=(4, 2, 1), new_shape=(-1, 8),\n                     in_place=True, arg_shape=False)\n\n    def test_zero_dim(self):\n        _test_reshape(old_shape=(4, 2, 1), new_shape=(0, 0, 0),\n                     expected_shape=(4, 2, 1))\n        _test_reshape(old_shape=(4, 2, 1), new_shape=(0, 0, 0),\n                     expected_shape=(4, 2, 1), arg_shape=False)\n        _test_reshape(old_shape=(4, 2, 1), new_shape=(0, 2, 1),\n                     expected_shape=(4, 2, 1))\n        _test_reshape(old_shape=(4, 2, 1), new_shape=(0, 2, 1),\n                     expected_shape=(4, 2, 1), arg_shape=False)\n\n    def test_zero_dim_and_missing_dim(self):\n        _test_reshape(old_shape=(4, 2, 1), new_shape=(0, -1, 0),\n                     expected_shape=(4, 2, 1))\n        _test_reshape(old_shape=(4, 2, 1), new_shape=(0, -1, 0),\n                     expected_shape=(4, 2, 1), arg_shape=False)\n        _test_reshape(old_shape=(4, 3, 2), new_shape=(-1, 0),\n                     expected_shape=(8, 3))\n        _test_reshape(old_shape=(4, 3, 2), new_shape=(-1, 0),\n                     expected_shape=(8, 3), arg_shape=False)\n\n    def test_backprop(self):\n        old_shape = (4, 2, 1)\n        new_shape = (1, 8)\n        X = np.random.rand(*old_shape).astype(np.float32)\n        Y = np.random.rand(*new_shape).astype(np.float32)\n\n        net = core.Net('net')\n\n        net.GivenTensorFill([], 'X', shape=old_shape, values=X.flatten())\n        net.GivenTensorFill([], 'Y', shape=new_shape, values=Y.flatten())\n\n        net.Reshape(['X'], ['X_out', 'old_shape'], shape=new_shape)\n        net.DotProduct(['X_out', 'Y'], 'Z')\n        net.AddGradientOperators(['Z'])\n\n        workspace.RunNetOnce(net)\n\n        Z = workspace.FetchBlob('Z')\n        X_grad = workspace.FetchBlob('X_grad')\n\n        # Check forward computation\n        np.testing.assert_allclose(\n            Z.squeeze(), X.reshape(new_shape).dot(Y.T).squeeze(), rtol=1e-5)\n\n        # Check the shape of the gradient\n        np.testing.assert_array_equal(X_grad.shape, X.shape)\n\n        # Check the gradient\n        np.testing.assert_allclose(X_grad, Y.reshape(old_shape), rtol=1e-5)\n\n    def test_input_shape_changes(self):\n        workspace.FeedBlob(\n            'input_blob',\n            np.array(np.random.rand(10, 20, 10), dtype=np.float32))\n        net = core.Net('mynet')\n        z, _ = net.Reshape('input_blob',\n                           ['z_reshape', 'dummy_size'],\n                           shape=(-1, 10))\n        workspace.CreateNet(net)\n        workspace.RunNet(net)\n        workspace.FeedBlob(\n            'input_blob',\n            np.array(np.random.rand(10, 40, 10), dtype=np.float32))\n        workspace.RunNet(net)\n\n\ndef _test_reshape(old_shape, new_shape, expected_shape=None, arg_shape=True,\n                 in_place=False):\n    devices = [core.DeviceOption(caffe2_pb2.CPU, 0)]\n    if workspace.NumCudaDevices() > 0:\n        devices.append(core.DeviceOption(caffe2_pb2.CUDA, 0))\n\n    for device_opt in devices:\n        with core.DeviceScope(device_opt):\n            if expected_shape is None:\n                expected_shape = new_shape\n            X = np.random.rand(*old_shape).astype(np.float32)\n\n            blob_in = 'X'\n            blob_out = blob_in if in_place else blob_in + '_out'\n\n            if arg_shape:\n                op = core.CreateOperator('Reshape',\n                                         [blob_in],\n                                         [blob_out, 'old_shape'],\n                                         shape=new_shape)\n            else:\n                op = core.CreateOperator('Reshape',\n                                         [blob_in, 'new_shape'],\n                                         [blob_out, 'old_shape'])\n                workspace.FeedBlob('new_shape', np.asarray(new_shape))\n\n            workspace.FeedBlob(blob_in, X)\n            workspace.RunOperatorOnce(op)\n\n            Y = workspace.FetchBlob(blob_out)\n            np.testing.assert_allclose(Y, X.reshape(expected_shape))\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/resize_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\nimport hypothesis.strategies as st\nimport unittest\nimport caffe2.python.hypothesis_test_util as hu\nfrom caffe2.python import core\nfrom hypothesis import given\n\n\nclass TestResize(hu.HypothesisTestCase):\n    @given(height_scale=st.floats(0.25, 4.0) | st.just(2.0),\n           width_scale=st.floats(0.25, 4.0) | st.just(2.0),\n           height=st.integers(4, 32),\n           width=st.integers(4, 32),\n           num_channels=st.integers(1, 4),\n           batch_size=st.integers(1, 4),\n           seed=st.integers(0, 65535),\n           **hu.gcs)\n    def test_nearest(self, height_scale, width_scale, height, width,\n                     num_channels, batch_size, seed,\n                     gc, dc):\n\n        np.random.seed(seed)\n        op = core.CreateOperator(\n            \"ResizeNearest\",\n            [\"X\"],\n            [\"Y\"],\n            width_scale=width_scale,\n            height_scale=height_scale,\n        )\n\n        X = np.random.rand(\n            batch_size, num_channels, height, width).astype(np.float32)\n\n        def ref(X):\n            output_height = np.int32(height * height_scale)\n            output_width = np.int32(width * width_scale)\n\n            output_h_idxs, output_w_idxs = np.meshgrid(np.arange(output_height),\n                                                       np.arange(output_width),\n                                                       indexing='ij')\n\n            input_h_idxs = np.minimum(\n                output_h_idxs / height_scale, height - 1).astype(np.int32)\n            input_w_idxs = np.minimum(\n                output_w_idxs / width_scale, width - 1).astype(np.int32)\n\n            Y = X[:, :, input_h_idxs, input_w_idxs]\n\n            return Y,\n\n        self.assertReferenceChecks(gc, op, [X], ref)\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0], stepsize=0.1, threshold=1e-2)\n\n    @given(height_scale=st.floats(0.25, 4.0) | st.just(2.0),\n           width_scale=st.floats(0.25, 4.0) | st.just(2.0),\n           height=st.integers(4, 32),\n           width=st.integers(4, 32),\n           num_channels=st.integers(1, 4),\n           batch_size=st.integers(1, 4),\n           seed=st.integers(0, 65535),\n           **hu.gcs)\n    def test_nearest_grad(self, height_scale, width_scale, height, width,\n                          num_channels, batch_size, seed, gc, dc):\n\n        np.random.seed(seed)\n\n        output_height = np.int32(height * height_scale)\n        output_width = np.int32(width * width_scale)\n        X = np.random.rand(batch_size,\n                           num_channels,\n                           height,\n                           width).astype(np.float32)\n        dY = np.random.rand(batch_size,\n                            num_channels,\n                            output_height,\n                            output_width).astype(np.float32)\n\n        op = core.CreateOperator(\n            \"ResizeNearestGradient\",\n            [\"dY\", \"X\"],\n            [\"dX\"],\n            width_scale=width_scale,\n            height_scale=height_scale,\n        )\n\n        def ref(dY, X):\n            dX = np.zeros_like(X)\n\n            for i in range(output_height):\n                for j in range(output_width):\n                    input_i = np.minimum(i / height_scale, height - 1).astype(np.int32)\n                    input_j = np.minimum(j / width_scale, width - 1).astype(np.int32)\n                    dX[:, :, input_i, input_j] += dY[:, :, i, j]\n\n            return dX,\n\n        self.assertDeviceChecks(dc, op, [dY, X], [0])\n        self.assertReferenceChecks(gc, op, [dY, X], ref)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/rmac_regions_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass RMACRegionsOpTest(hu.HypothesisTestCase):\n    @given(\n        n=st.integers(500, 500),\n        h=st.integers(1, 10),\n        w=st.integers(1, 10),\n        scales=st.integers(1, 3),\n        **hu.gcs\n    )\n    def test(self, n, h, w, scales, gc, dc):\n        X = np.random.rand(n, 64, h, w).astype(np.float32)\n        overlap = 0.4\n\n        def ref_op(X):\n            N, H, W = X.shape[0], X.shape[2], X.shape[3]\n\n            # Possible regions for the long dimension\n            steps = np.array((2, 3, 4, 5, 6, 7), dtype=np.float32)\n            minW = np.minimum(H, W)\n\n            # steps(idx) regions for long dimension\n            b = (np.maximum(H, W) - minW) / (steps - 1)\n            idx = np.argmin(\n                np.abs(((minW**2 - minW * b) / minW**2) - overlap)) + 1\n\n            # Region overplus per dimension\n            Wd = 0\n            Hd = 0\n            if H < W:\n                Wd = idx\n            elif H > W:\n                Hd = idx\n\n            regions_xywh = []\n            for l in range(1, scales + 1):\n                wl = np.floor(2 * minW / (l + 1))\n\n                # Center coordinates\n                if l + Wd - 1 > 0:\n                    b = (W - wl) / (l + Wd - 1)\n                else:\n                    b = 0\n                cenW = np.floor(b * np.arange(l - 1 + Wd + 1))\n\n                # Center coordinates\n                if l + Hd - 1 > 0:\n                    b = (H - wl) / (l + Hd - 1)\n                else:\n                    b = 0\n                cenH = np.floor(b * np.arange(l - 1 + Hd + 1))\n\n                for i_ in cenW:\n                    for j_ in cenH:\n                        regions_xywh.append([i_, j_, wl, wl])\n\n            # Round the regions. Careful with the borders!\n            for i in range(len(regions_xywh)):\n                for j in range(4):\n                    regions_xywh[i][j] = int(round(regions_xywh[i][j]))\n                if regions_xywh[i][0] + regions_xywh[i][2] > W:\n                    regions_xywh[i][0] -= (\n                        (regions_xywh[i][0] + regions_xywh[i][2]) - W\n                    )\n                if regions_xywh[i][1] + regions_xywh[i][3] > H:\n                    regions_xywh[i][1] -= (\n                        (regions_xywh[i][1] + regions_xywh[i][3]) - H\n                    )\n            # Filter out 0-sized regions\n            regions_xywh = [r for r in regions_xywh if r[2] * r[3] > 0]\n\n            # Convert to ROIPoolOp format: (batch_index x1 y1 x2 y2)\n            regions = [\n                [i, x, y, x + w - 1, y + h - 1]\n                for i in np.arange(N) for x, y, w, h in regions_xywh\n            ]\n            return (np.array(regions).astype(np.float32), )\n\n        op = core.CreateOperator(\n            'RMACRegions',\n            ['X'],\n            ['RMAC_REGIONS'],\n            scales=scales,\n            overlap=overlap,\n        )\n\n        # Check against numpy reference\n        self.assertReferenceChecks(gc, op, [X], ref_op)\n"
  },
  {
    "path": "caffe2/python/operator_test/rnn_cell_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import (\n    core, gradient_checker, rnn_cell, workspace, scope, utils\n)\nfrom caffe2.python.attention import AttentionType\nfrom caffe2.python.model_helper import ModelHelper, ExtractPredictorNet\nfrom caffe2.python.rnn.rnn_cell_test_util import sigmoid, tanh, _prepare_rnn\nfrom caffe2.proto import caffe2_pb2\nimport caffe2.python.hypothesis_test_util as hu\n\nfrom functools import partial\nfrom hypothesis import assume, given\nfrom hypothesis import settings as ht_settings\nimport hypothesis.strategies as st\nimport numpy as np\nimport unittest\n\n\ndef lstm_unit(*args, **kwargs):\n    forget_bias = kwargs.get('forget_bias', 0.0)\n    drop_states = kwargs.get('drop_states', False)\n    sequence_lengths = kwargs.get('sequence_lengths', True)\n\n    if sequence_lengths:\n        hidden_t_prev, cell_t_prev, gates, seq_lengths, timestep = args\n    else:\n        hidden_t_prev, cell_t_prev, gates, timestep = args\n    D = cell_t_prev.shape[2]\n    G = gates.shape[2]\n    N = gates.shape[1]\n    t = (timestep * np.ones(shape=(N, D))).astype(np.int32)\n    assert t.shape == (N, D)\n    assert G == 4 * D\n    # Resize to avoid broadcasting inconsistencies with NumPy\n    gates = gates.reshape(N, 4, D)\n    cell_t_prev = cell_t_prev.reshape(N, D)\n    i_t = gates[:, 0, :].reshape(N, D)\n    f_t = gates[:, 1, :].reshape(N, D)\n    o_t = gates[:, 2, :].reshape(N, D)\n    g_t = gates[:, 3, :].reshape(N, D)\n    i_t = sigmoid(i_t)\n    f_t = sigmoid(f_t + forget_bias)\n    o_t = sigmoid(o_t)\n    g_t = tanh(g_t)\n    if sequence_lengths:\n        seq_lengths = (np.ones(shape=(N, D)) *\n                       seq_lengths.reshape(N, 1)).astype(np.int32)\n        assert seq_lengths.shape == (N, D)\n        valid = (t < seq_lengths).astype(np.int32)\n    else:\n        valid = np.ones(shape=(N, D))\n    assert valid.shape == (N, D)\n    cell_t = ((f_t * cell_t_prev) + (i_t * g_t)) * (valid) + \\\n        (1 - valid) * cell_t_prev * (1 - drop_states)\n    assert cell_t.shape == (N, D)\n    hidden_t = (o_t * tanh(cell_t)) * valid + hidden_t_prev * (\n        1 - valid) * (1 - drop_states)\n    hidden_t = hidden_t.reshape(1, N, D)\n    cell_t = cell_t.reshape(1, N, D)\n    return hidden_t, cell_t\n\n\ndef layer_norm_with_scale_and_bias_ref(X, scale, bias, axis=-1, epsilon=1e-4):\n    left = np.prod(X.shape[:axis])\n    reshaped = np.reshape(X, [left, -1])\n    mean = np.mean(reshaped, axis=1).reshape([left, 1])\n    stdev = np.sqrt(\n        np.mean(np.square(reshaped), axis=1).reshape([left, 1]) -\n        np.square(mean) + epsilon\n    )\n    norm = (reshaped - mean) / stdev\n    norm = np.reshape(norm, X.shape)\n    adjusted = scale * norm + bias\n\n    return adjusted\n\n\ndef layer_norm_lstm_reference(\n    input,\n    hidden_input,\n    cell_input,\n    gates_w,\n    gates_b,\n    gates_t_norm_scale,\n    gates_t_norm_bias,\n    seq_lengths,\n    forget_bias,\n    drop_states=False\n):\n    T = input.shape[0]\n    N = input.shape[1]\n    G = input.shape[2]\n    D = hidden_input.shape[hidden_input.ndim - 1]\n    hidden = np.zeros(shape=(T + 1, N, D))\n    cell = np.zeros(shape=(T + 1, N, D))\n    assert hidden.shape[0] == T + 1\n    assert cell.shape[0] == T + 1\n    assert hidden.shape[1] == N\n    assert cell.shape[1] == N\n    cell[0, :, :] = cell_input\n    hidden[0, :, :] = hidden_input\n    for t in range(T):\n        input_t = input[t].reshape(1, N, G)\n        print(input_t.shape)\n        hidden_t_prev = hidden[t].reshape(1, N, D)\n        cell_t_prev = cell[t].reshape(1, N, D)\n        gates = np.dot(hidden_t_prev, gates_w.T) + gates_b\n        gates = gates + input_t\n\n        gates = layer_norm_with_scale_and_bias_ref(\n            gates, gates_t_norm_scale, gates_t_norm_bias\n        )\n\n        hidden_t, cell_t = lstm_unit(\n            hidden_t_prev,\n            cell_t_prev,\n            gates,\n            seq_lengths,\n            t,\n            forget_bias=forget_bias,\n            drop_states=drop_states,\n        )\n        hidden[t + 1] = hidden_t\n        cell[t + 1] = cell_t\n    return (\n        hidden[1:],\n        hidden[-1].reshape(1, N, D),\n        cell[1:],\n        cell[-1].reshape(1, N, D)\n    )\n\n\ndef lstm_reference(input, hidden_input, cell_input,\n                   gates_w, gates_b, seq_lengths, forget_bias,\n                   drop_states=False):\n    T = input.shape[0]\n    N = input.shape[1]\n    G = input.shape[2]\n    D = hidden_input.shape[hidden_input.ndim - 1]\n    hidden = np.zeros(shape=(T + 1, N, D))\n    cell = np.zeros(shape=(T + 1, N, D))\n    assert hidden.shape[0] == T + 1\n    assert cell.shape[0] == T + 1\n    assert hidden.shape[1] == N\n    assert cell.shape[1] == N\n    cell[0, :, :] = cell_input\n    hidden[0, :, :] = hidden_input\n    for t in range(T):\n        input_t = input[t].reshape(1, N, G)\n        hidden_t_prev = hidden[t].reshape(1, N, D)\n        cell_t_prev = cell[t].reshape(1, N, D)\n        gates = np.dot(hidden_t_prev, gates_w.T) + gates_b\n        gates = gates + input_t\n        hidden_t, cell_t = lstm_unit(\n            hidden_t_prev,\n            cell_t_prev,\n            gates,\n            seq_lengths,\n            t,\n            forget_bias=forget_bias,\n            drop_states=drop_states,\n        )\n        hidden[t + 1] = hidden_t\n        cell[t + 1] = cell_t\n    return (\n        hidden[1:],\n        hidden[-1].reshape(1, N, D),\n        cell[1:],\n        cell[-1].reshape(1, N, D)\n    )\n\n\ndef multi_lstm_reference(input, hidden_input_list, cell_input_list,\n                            i2h_w_list, i2h_b_list, gates_w_list, gates_b_list,\n                            seq_lengths, forget_bias, drop_states=False):\n    num_layers = len(hidden_input_list)\n    assert len(cell_input_list) == num_layers\n    assert len(i2h_w_list) == num_layers\n    assert len(i2h_b_list) == num_layers\n    assert len(gates_w_list) == num_layers\n    assert len(gates_b_list) == num_layers\n\n    for i in range(num_layers):\n        layer_input = np.dot(input, i2h_w_list[i].T) + i2h_b_list[i]\n        h_all, h_last, c_all, c_last = lstm_reference(\n            layer_input,\n            hidden_input_list[i],\n            cell_input_list[i],\n            gates_w_list[i],\n            gates_b_list[i],\n            seq_lengths,\n            forget_bias,\n            drop_states=drop_states,\n        )\n        input = h_all\n    return h_all, h_last, c_all, c_last\n\n\ndef compute_regular_attention_logits(\n    hidden_t,\n    weighted_decoder_hidden_state_t_w,\n    weighted_decoder_hidden_state_t_b,\n    attention_weighted_encoder_context_t_prev,\n    weighted_prev_attention_context_w,\n    weighted_prev_attention_context_b,\n    attention_v,\n    weighted_encoder_outputs,\n    encoder_outputs_for_dot_product,\n    coverage_prev,\n    coverage_weights,\n):\n    weighted_hidden_t = np.dot(\n        hidden_t,\n        weighted_decoder_hidden_state_t_w.T,\n    ) + weighted_decoder_hidden_state_t_b\n    attention_v = attention_v.reshape([-1])\n    return np.sum(\n        attention_v * np.tanh(weighted_encoder_outputs + weighted_hidden_t),\n        axis=2,\n    )\n\n\ndef compute_recurrent_attention_logits(\n    hidden_t,\n    weighted_decoder_hidden_state_t_w,\n    weighted_decoder_hidden_state_t_b,\n    attention_weighted_encoder_context_t_prev,\n    weighted_prev_attention_context_w,\n    weighted_prev_attention_context_b,\n    attention_v,\n    weighted_encoder_outputs,\n    encoder_outputs_for_dot_product,\n    coverage_prev,\n    coverage_weights,\n):\n    weighted_hidden_t = np.dot(\n        hidden_t,\n        weighted_decoder_hidden_state_t_w.T,\n    ) + weighted_decoder_hidden_state_t_b\n    weighted_prev_attention_context = np.dot(\n        attention_weighted_encoder_context_t_prev,\n        weighted_prev_attention_context_w.T\n    ) + weighted_prev_attention_context_b\n    attention_v = attention_v.reshape([-1])\n    return np.sum(\n        attention_v * np.tanh(\n            weighted_encoder_outputs + weighted_hidden_t +\n            weighted_prev_attention_context\n        ),\n        axis=2,\n    )\n\n\ndef compute_dot_attention_logits(\n    hidden_t,\n    weighted_decoder_hidden_state_t_w,\n    weighted_decoder_hidden_state_t_b,\n    attention_weighted_encoder_context_t_prev,\n    weighted_prev_attention_context_w,\n    weighted_prev_attention_context_b,\n    attention_v,\n    weighted_encoder_outputs,\n    encoder_outputs_for_dot_product,\n    coverage_prev,\n    coverage_weights,\n):\n    hidden_t_for_dot_product = np.transpose(hidden_t, axes=[1, 2, 0])\n    if (\n        weighted_decoder_hidden_state_t_w is not None and\n        weighted_decoder_hidden_state_t_b is not None\n    ):\n        hidden_t_for_dot_product = np.matmul(\n            weighted_decoder_hidden_state_t_w,\n            hidden_t_for_dot_product,\n        ) + np.expand_dims(weighted_decoder_hidden_state_t_b, axis=1)\n    attention_logits_t = np.sum(\n        np.matmul(\n            encoder_outputs_for_dot_product,\n            hidden_t_for_dot_product,\n        ),\n        axis=2,\n    )\n    return np.transpose(attention_logits_t)\n\n\ndef compute_coverage_attention_logits(\n    hidden_t,\n    weighted_decoder_hidden_state_t_w,\n    weighted_decoder_hidden_state_t_b,\n    attention_weighted_encoder_context_t_prev,\n    weighted_prev_attention_context_w,\n    weighted_prev_attention_context_b,\n    attention_v,\n    weighted_encoder_outputs,\n    encoder_outputs_for_dot_product,\n    coverage_prev,\n    coverage_weights,\n):\n    weighted_hidden_t = np.dot(\n        hidden_t,\n        weighted_decoder_hidden_state_t_w.T,\n    ) + weighted_decoder_hidden_state_t_b\n    coverage_part = coverage_prev.T * coverage_weights\n    encoder_part = weighted_encoder_outputs + coverage_part\n    attention_v = attention_v.reshape([-1])\n    return np.sum(\n        attention_v * np.tanh(encoder_part + weighted_hidden_t),\n        axis=2,\n    )\n\n\ndef lstm_with_attention_reference(\n    input,\n    initial_hidden_state,\n    initial_cell_state,\n    initial_attention_weighted_encoder_context,\n    gates_w,\n    gates_b,\n    decoder_input_lengths,\n    encoder_outputs_transposed,\n    weighted_prev_attention_context_w,\n    weighted_prev_attention_context_b,\n    weighted_decoder_hidden_state_t_w,\n    weighted_decoder_hidden_state_t_b,\n    weighted_encoder_outputs,\n    coverage_weights,\n    attention_v,\n    attention_zeros,\n    compute_attention_logits,\n):\n    encoder_outputs = np.transpose(encoder_outputs_transposed, axes=[2, 0, 1])\n    encoder_outputs_for_dot_product = np.transpose(\n        encoder_outputs_transposed,\n        [0, 2, 1],\n    )\n    decoder_input_length = input.shape[0]\n    batch_size = input.shape[1]\n    decoder_input_dim = input.shape[2]\n    decoder_state_dim = initial_hidden_state.shape[2]\n    encoder_output_dim = encoder_outputs.shape[2]\n    hidden = np.zeros(\n        shape=(decoder_input_length + 1, batch_size, decoder_state_dim))\n    cell = np.zeros(\n        shape=(decoder_input_length + 1, batch_size, decoder_state_dim))\n    attention_weighted_encoder_context = np.zeros(\n        shape=(decoder_input_length + 1, batch_size, encoder_output_dim))\n    cell[0, :, :] = initial_cell_state\n    hidden[0, :, :] = initial_hidden_state\n    attention_weighted_encoder_context[0, :, :] = (\n        initial_attention_weighted_encoder_context\n    )\n    encoder_length = encoder_outputs.shape[0]\n    coverage = np.zeros(\n        shape=(decoder_input_length + 1, batch_size, encoder_length))\n    for t in range(decoder_input_length):\n        input_t = input[t].reshape(1, batch_size, decoder_input_dim)\n        hidden_t_prev = hidden[t].reshape(1, batch_size, decoder_state_dim)\n        cell_t_prev = cell[t].reshape(1, batch_size, decoder_state_dim)\n        attention_weighted_encoder_context_t_prev = (\n            attention_weighted_encoder_context[t].reshape(\n                1, batch_size, encoder_output_dim)\n        )\n        gates_input = np.concatenate(\n            (hidden_t_prev, attention_weighted_encoder_context_t_prev),\n            axis=2,\n        )\n        gates = np.dot(gates_input, gates_w.T) + gates_b\n        gates = gates + input_t\n        hidden_t, cell_t = lstm_unit(hidden_t_prev, cell_t_prev, gates,\n                                     decoder_input_lengths, t)\n        hidden[t + 1] = hidden_t\n        cell[t + 1] = cell_t\n\n        coverage_prev = coverage[t].reshape(1, batch_size, encoder_length)\n\n        attention_logits_t = compute_attention_logits(\n            hidden_t,\n            weighted_decoder_hidden_state_t_w,\n            weighted_decoder_hidden_state_t_b,\n            attention_weighted_encoder_context_t_prev,\n            weighted_prev_attention_context_w,\n            weighted_prev_attention_context_b,\n            attention_v,\n            weighted_encoder_outputs,\n            encoder_outputs_for_dot_product,\n            coverage_prev,\n            coverage_weights,\n        )\n\n        attention_logits_t_exp = np.exp(attention_logits_t)\n        attention_weights_t = (\n            attention_logits_t_exp /\n            np.sum(attention_logits_t_exp, axis=0).reshape([1, -1])\n        )\n        coverage[t + 1, :, :] = coverage[t, :, :] + attention_weights_t.T\n        attention_weighted_encoder_context[t + 1] = np.sum(\n            (\n                encoder_outputs *\n                attention_weights_t.reshape([-1, batch_size, 1])\n            ),\n            axis=0,\n        )\n    return (\n        hidden[1:],\n        hidden[-1].reshape(1, batch_size, decoder_state_dim),\n        cell[1:],\n        cell[-1].reshape(1, batch_size, decoder_state_dim),\n        attention_weighted_encoder_context[1:],\n        attention_weighted_encoder_context[-1].reshape(\n            1,\n            batch_size,\n            encoder_output_dim,\n        )\n    )\n\n\ndef lstm_with_regular_attention_reference(\n    input,\n    initial_hidden_state,\n    initial_cell_state,\n    initial_attention_weighted_encoder_context,\n    gates_w,\n    gates_b,\n    decoder_input_lengths,\n    weighted_decoder_hidden_state_t_w,\n    weighted_decoder_hidden_state_t_b,\n    weighted_encoder_outputs,\n    attention_v,\n    attention_zeros,\n    encoder_outputs_transposed,\n):\n    return lstm_with_attention_reference(\n        input=input,\n        initial_hidden_state=initial_hidden_state,\n        initial_cell_state=initial_cell_state,\n        initial_attention_weighted_encoder_context=(\n            initial_attention_weighted_encoder_context\n        ),\n        gates_w=gates_w,\n        gates_b=gates_b,\n        decoder_input_lengths=decoder_input_lengths,\n        encoder_outputs_transposed=encoder_outputs_transposed,\n        weighted_prev_attention_context_w=None,\n        weighted_prev_attention_context_b=None,\n        weighted_decoder_hidden_state_t_w=weighted_decoder_hidden_state_t_w,\n        weighted_decoder_hidden_state_t_b=weighted_decoder_hidden_state_t_b,\n        weighted_encoder_outputs=weighted_encoder_outputs,\n        coverage_weights=None,\n        attention_v=attention_v,\n        attention_zeros=attention_zeros,\n        compute_attention_logits=compute_regular_attention_logits,\n    )\n\n\ndef lstm_with_recurrent_attention_reference(\n    input,\n    initial_hidden_state,\n    initial_cell_state,\n    initial_attention_weighted_encoder_context,\n    gates_w,\n    gates_b,\n    decoder_input_lengths,\n    weighted_prev_attention_context_w,\n    weighted_prev_attention_context_b,\n    weighted_decoder_hidden_state_t_w,\n    weighted_decoder_hidden_state_t_b,\n    weighted_encoder_outputs,\n    attention_v,\n    attention_zeros,\n    encoder_outputs_transposed,\n):\n    return lstm_with_attention_reference(\n        input=input,\n        initial_hidden_state=initial_hidden_state,\n        initial_cell_state=initial_cell_state,\n        initial_attention_weighted_encoder_context=(\n            initial_attention_weighted_encoder_context\n        ),\n        gates_w=gates_w,\n        gates_b=gates_b,\n        decoder_input_lengths=decoder_input_lengths,\n        encoder_outputs_transposed=encoder_outputs_transposed,\n        weighted_prev_attention_context_w=weighted_prev_attention_context_w,\n        weighted_prev_attention_context_b=weighted_prev_attention_context_b,\n        weighted_decoder_hidden_state_t_w=weighted_decoder_hidden_state_t_w,\n        weighted_decoder_hidden_state_t_b=weighted_decoder_hidden_state_t_b,\n        weighted_encoder_outputs=weighted_encoder_outputs,\n        coverage_weights=None,\n        attention_v=attention_v,\n        attention_zeros=attention_zeros,\n        compute_attention_logits=compute_recurrent_attention_logits,\n    )\n\n\ndef lstm_with_dot_attention_reference(\n    input,\n    initial_hidden_state,\n    initial_cell_state,\n    initial_attention_weighted_encoder_context,\n    gates_w,\n    gates_b,\n    decoder_input_lengths,\n    encoder_outputs_transposed,\n    weighted_decoder_hidden_state_t_w,\n    weighted_decoder_hidden_state_t_b,\n):\n    return lstm_with_attention_reference(\n        input=input,\n        initial_hidden_state=initial_hidden_state,\n        initial_cell_state=initial_cell_state,\n        initial_attention_weighted_encoder_context=(\n            initial_attention_weighted_encoder_context\n        ),\n        gates_w=gates_w,\n        gates_b=gates_b,\n        decoder_input_lengths=decoder_input_lengths,\n        encoder_outputs_transposed=encoder_outputs_transposed,\n        weighted_prev_attention_context_w=None,\n        weighted_prev_attention_context_b=None,\n        weighted_decoder_hidden_state_t_w=weighted_decoder_hidden_state_t_w,\n        weighted_decoder_hidden_state_t_b=weighted_decoder_hidden_state_t_b,\n        weighted_encoder_outputs=None,\n        coverage_weights=None,\n        attention_v=None,\n        attention_zeros=None,\n        compute_attention_logits=compute_dot_attention_logits,\n    )\n\n\ndef lstm_with_dot_attention_reference_same_dim(\n    input,\n    initial_hidden_state,\n    initial_cell_state,\n    initial_attention_weighted_encoder_context,\n    gates_w,\n    gates_b,\n    decoder_input_lengths,\n    encoder_outputs_transposed,\n):\n    return lstm_with_dot_attention_reference(\n        input=input,\n        initial_hidden_state=initial_hidden_state,\n        initial_cell_state=initial_cell_state,\n        initial_attention_weighted_encoder_context=(\n            initial_attention_weighted_encoder_context\n        ),\n        gates_w=gates_w,\n        gates_b=gates_b,\n        decoder_input_lengths=decoder_input_lengths,\n        encoder_outputs_transposed=encoder_outputs_transposed,\n        weighted_decoder_hidden_state_t_w=None,\n        weighted_decoder_hidden_state_t_b=None,\n    )\n\n\ndef lstm_with_dot_attention_reference_different_dim(\n    input,\n    initial_hidden_state,\n    initial_cell_state,\n    initial_attention_weighted_encoder_context,\n    gates_w,\n    gates_b,\n    decoder_input_lengths,\n    weighted_decoder_hidden_state_t_w,\n    weighted_decoder_hidden_state_t_b,\n    encoder_outputs_transposed,\n):\n    return lstm_with_dot_attention_reference(\n        input=input,\n        initial_hidden_state=initial_hidden_state,\n        initial_cell_state=initial_cell_state,\n        initial_attention_weighted_encoder_context=(\n            initial_attention_weighted_encoder_context\n        ),\n        gates_w=gates_w,\n        gates_b=gates_b,\n        decoder_input_lengths=decoder_input_lengths,\n        encoder_outputs_transposed=encoder_outputs_transposed,\n        weighted_decoder_hidden_state_t_w=weighted_decoder_hidden_state_t_w,\n        weighted_decoder_hidden_state_t_b=weighted_decoder_hidden_state_t_b,\n    )\n\n\ndef lstm_with_coverage_attention_reference(\n    input,\n    initial_hidden_state,\n    initial_cell_state,\n    initial_attention_weighted_encoder_context,\n    initial_coverage,\n    gates_w,\n    gates_b,\n    decoder_input_lengths,\n    weighted_decoder_hidden_state_t_w,\n    weighted_decoder_hidden_state_t_b,\n    weighted_encoder_outputs,\n    coverage_weights,\n    attention_v,\n    attention_zeros,\n    encoder_outputs_transposed,\n):\n    return lstm_with_attention_reference(\n        input=input,\n        initial_hidden_state=initial_hidden_state,\n        initial_cell_state=initial_cell_state,\n        initial_attention_weighted_encoder_context=(\n            initial_attention_weighted_encoder_context\n        ),\n        gates_w=gates_w,\n        gates_b=gates_b,\n        decoder_input_lengths=decoder_input_lengths,\n        encoder_outputs_transposed=encoder_outputs_transposed,\n        weighted_prev_attention_context_w=None,\n        weighted_prev_attention_context_b=None,\n        weighted_decoder_hidden_state_t_w=weighted_decoder_hidden_state_t_w,\n        weighted_decoder_hidden_state_t_b=weighted_decoder_hidden_state_t_b,\n        weighted_encoder_outputs=weighted_encoder_outputs,\n        coverage_weights=coverage_weights,\n        attention_v=attention_v,\n        attention_zeros=attention_zeros,\n        compute_attention_logits=compute_coverage_attention_logits,\n    )\n\n\ndef milstm_reference(\n        input,\n        hidden_input,\n        cell_input,\n        gates_w,\n        gates_b,\n        alpha,\n        beta1,\n        beta2,\n        b,\n        seq_lengths,\n        forget_bias,\n        drop_states=False):\n    T = input.shape[0]\n    N = input.shape[1]\n    G = input.shape[2]\n    D = hidden_input.shape[hidden_input.ndim - 1]\n    hidden = np.zeros(shape=(T + 1, N, D))\n    cell = np.zeros(shape=(T + 1, N, D))\n    assert hidden.shape[0] == T + 1\n    assert cell.shape[0] == T + 1\n    assert hidden.shape[1] == N\n    assert cell.shape[1] == N\n    cell[0, :, :] = cell_input\n    hidden[0, :, :] = hidden_input\n    for t in range(T):\n        input_t = input[t].reshape(1, N, G)\n        hidden_t_prev = hidden[t].reshape(1, N, D)\n        cell_t_prev = cell[t].reshape(1, N, D)\n        gates = np.dot(hidden_t_prev, gates_w.T) + gates_b\n        gates = (alpha * gates * input_t) + \\\n                    (beta1 * gates) + \\\n                    (beta2 * input_t) + \\\n                    b\n        hidden_t, cell_t = lstm_unit(\n            hidden_t_prev,\n            cell_t_prev,\n            gates,\n            seq_lengths,\n            t,\n            forget_bias=forget_bias,\n            drop_states=drop_states,\n        )\n        hidden[t + 1] = hidden_t\n        cell[t + 1] = cell_t\n    return (\n        hidden[1:],\n        hidden[-1].reshape(1, N, D),\n        cell[1:],\n        cell[-1].reshape(1, N, D)\n    )\n\n\ndef layer_norm_milstm_reference(\n        input,\n        hidden_input,\n        cell_input,\n        gates_w,\n        gates_b,\n        alpha,\n        beta1,\n        beta2,\n        b,\n        gates_t_norm_scale,\n        gates_t_norm_bias,\n        seq_lengths,\n        forget_bias,\n        drop_states=False):\n    T = input.shape[0]\n    N = input.shape[1]\n    G = input.shape[2]\n    D = hidden_input.shape[hidden_input.ndim - 1]\n    hidden = np.zeros(shape=(T + 1, N, D))\n    cell = np.zeros(shape=(T + 1, N, D))\n    assert hidden.shape[0] == T + 1\n    assert cell.shape[0] == T + 1\n    assert hidden.shape[1] == N\n    assert cell.shape[1] == N\n    cell[0, :, :] = cell_input\n    hidden[0, :, :] = hidden_input\n    for t in range(T):\n        input_t = input[t].reshape(1, N, G)\n        hidden_t_prev = hidden[t].reshape(1, N, D)\n        cell_t_prev = cell[t].reshape(1, N, D)\n        gates = np.dot(hidden_t_prev, gates_w.T) + gates_b\n        gates = (alpha * gates * input_t) + \\\n                    (beta1 * gates) + \\\n                    (beta2 * input_t) + \\\n                    b\n        gates = layer_norm_with_scale_and_bias_ref(\n            gates, gates_t_norm_scale, gates_t_norm_bias\n        )\n        hidden_t, cell_t = lstm_unit(\n            hidden_t_prev,\n            cell_t_prev,\n            gates,\n            seq_lengths,\n            t,\n            forget_bias=forget_bias,\n            drop_states=drop_states,\n        )\n        hidden[t + 1] = hidden_t\n        cell[t + 1] = cell_t\n    return (\n        hidden[1:],\n        hidden[-1].reshape(1, N, D),\n        cell[1:],\n        cell[-1].reshape(1, N, D)\n    )\n\n\ndef lstm_input():\n    '''\n    Create input tensor where each dimension is from 1 to 4, ndim=3 and\n    last dimension size is a factor of 4\n    '''\n    dims_ = st.tuples(\n        st.integers(min_value=1, max_value=4),  # t\n        st.integers(min_value=1, max_value=4),  # n\n        st.integers(min_value=1, max_value=4),  # d\n    )\n\n    def create_input(dims):\n        dims = list(dims)\n        dims[2] *= 4\n        return hu.arrays(dims)\n\n    return dims_.flatmap(create_input)\n\n\ndef _prepare_attention(t, n, dim_in, encoder_dim,\n                          forward_only=False, T=None,\n                          dim_out=None, residual=False,\n                          final_dropout=False):\n    if dim_out is None:\n        dim_out = [dim_in]\n    print(\"Dims: t={} n={} dim_in={} dim_out={}\".format(t, n, dim_in, dim_out))\n\n    model = ModelHelper(name='external')\n\n    def generate_input_state(shape):\n        return np.random.random(shape).astype(np.float32)\n\n    initial_states = []\n    for layer_id, d in enumerate(dim_out):\n        h, c = model.net.AddExternalInputs(\n            \"hidden_init_{}\".format(layer_id),\n            \"cell_init_{}\".format(layer_id),\n        )\n        initial_states.extend([h, c])\n        workspace.FeedBlob(h, generate_input_state((1, n, d)))\n        workspace.FeedBlob(c, generate_input_state((1, n, d)))\n\n    awec_init = model.net.AddExternalInputs([\n        'initial_attention_weighted_encoder_context',\n    ])\n    initial_states.append(awec_init)\n    workspace.FeedBlob(\n        awec_init,\n        generate_input_state((1, n, encoder_dim)),\n    )\n\n    # Due to convoluted RNN scoping logic we make sure that things\n    # work from a namescope\n    with scope.NameScope(\"test_name_scope\"):\n        (\n            input_blob,\n            seq_lengths,\n            encoder_outputs,\n            weighted_encoder_outputs,\n        ) = model.net.AddScopedExternalInputs(\n            'input_blob',\n            'seq_lengths',\n            'encoder_outputs',\n            'weighted_encoder_outputs',\n        )\n\n        layer_input_dim = dim_in\n        cells = []\n        for layer_id, d in enumerate(dim_out):\n\n            cell = rnn_cell.MILSTMCell(\n                name='decoder_{}'.format(layer_id),\n                forward_only=forward_only,\n                input_size=layer_input_dim,\n                hidden_size=d,\n                forget_bias=0.0,\n                memory_optimization=False,\n            )\n            cells.append(cell)\n            layer_input_dim = d\n\n        decoder_cell = rnn_cell.MultiRNNCell(\n            cells,\n            name='decoder',\n            residual_output_layers=range(1, len(cells)) if residual else None,\n        )\n\n        attention_cell = rnn_cell.AttentionCell(\n            encoder_output_dim=encoder_dim,\n            encoder_outputs=encoder_outputs,\n            encoder_lengths=None,\n            decoder_cell=decoder_cell,\n            decoder_state_dim=dim_out[-1],\n            name='attention_decoder',\n            attention_type=AttentionType.Recurrent,\n            weighted_encoder_outputs=weighted_encoder_outputs,\n            attention_memory_optimization=True,\n        )\n        if final_dropout:\n            # dropout ratio of 0.0 used to test mechanism but not interfere\n            # with numerical tests\n            attention_cell = rnn_cell.DropoutCell(\n                internal_cell=attention_cell,\n                dropout_ratio=0.0,\n                name='dropout',\n                forward_only=forward_only,\n                is_test=False,\n            )\n\n        attention_cell = (\n            attention_cell if T is None\n            else rnn_cell.UnrolledCell(attention_cell, T)\n        )\n\n        output_indices = decoder_cell.output_indices\n        output_indices.append(2 * len(cells))\n        outputs_with_grads = [2 * i for i in output_indices]\n\n        final_output, state_outputs = attention_cell.apply_over_sequence(\n            model=model,\n            inputs=input_blob,\n            seq_lengths=seq_lengths,\n            initial_states=initial_states,\n            outputs_with_grads=outputs_with_grads,\n        )\n\n    workspace.RunNetOnce(model.param_init_net)\n\n    workspace.FeedBlob(\n        seq_lengths,\n        np.random.randint(1, t + 1, size=(n,)).astype(np.int32)\n    )\n\n    return {\n        'final_output': final_output,\n        'net': model.net,\n        'initial_states': initial_states,\n        'input_blob': input_blob,\n        'encoder_outputs': encoder_outputs,\n        'weighted_encoder_outputs': weighted_encoder_outputs,\n        'outputs_with_grads': outputs_with_grads,\n    }\n\n\nclass MulCell(rnn_cell.RNNCell):\n    def _apply(self, model, input_t,\n               seq_lengths, states, timestep, extra_inputs):\n        assert len(states) == 1\n        result = model.net.Mul([input_t, states[0]])\n        model.net.AddExternalOutput(result)\n        return [result]\n\n    def get_state_names(self):\n        return [self.scope(\"state\")]\n\n\ndef prepare_mul_rnn(model, input_blob, shape, T, outputs_with_grad, num_layers):\n    print(\"Shape: \", shape)\n    t, n, d = shape\n    cells = [MulCell(name=\"layer_{}\".format(i)) for i in range(num_layers)]\n    cell = rnn_cell.MultiRNNCell(name=\"multi_mul_rnn\", cells=cells)\n    if T is not None:\n        cell = rnn_cell.UnrolledCell(cell, T=T)\n    states = [\n        model.param_init_net.ConstantFill(\n            [], \"initial_state_{}\".format(i), value=1.0, shape=[1, n, d])\n        for i in range(num_layers)]\n    _, results = cell.apply_over_sequence(\n        model=model,\n        inputs=input_blob,\n        initial_states=states,\n        outputs_with_grads=[\n            x + 2 * (num_layers - 1) for x in outputs_with_grad\n        ],\n        seq_lengths=None,\n    )\n    return results[-2:]\n\n\nclass RNNCellTest(hu.HypothesisTestCase):\n    @given(\n        input_tensor=hu.tensor(min_dim=3, max_dim=3, max_value=3),\n        num_layers=st.integers(1, 4),\n        outputs_with_grad=st.sampled_from(\n            [[0], [1], [0, 1]]\n        ),\n    )\n    @ht_settings(max_examples=10)\n    def test_unroll_mul(self, input_tensor, num_layers, outputs_with_grad):\n        outputs = []\n        nets = []\n        input_blob = None\n        for T in [input_tensor.shape[0], None]:\n            model = ModelHelper(\"rnn_mul_{}\".format(\n                \"unroll\" if T else \"dynamic\"))\n            input_blob = model.net.AddExternalInputs(\"input_blob\")\n            outputs.append(\n                prepare_mul_rnn(model, input_blob, input_tensor.shape, T,\n                                outputs_with_grad, num_layers))\n            workspace.RunNetOnce(model.param_init_net)\n            nets.append(model.net)\n            workspace.blobs[input_blob] = input_tensor\n\n        gradient_checker.NetGradientChecker.CompareNets(\n            nets, outputs, outputs_with_grad_ids=outputs_with_grad,\n            inputs_with_grads=[input_blob],\n        )\n\n    @given(\n        input_tensor=hu.tensor(min_dim=3, max_dim=3, max_value=3),\n        forget_bias=st.floats(-10.0, 10.0),\n        drop_states=st.booleans(),\n        dim_out=st.lists(\n            elements=st.integers(min_value=1, max_value=3),\n            min_size=1, max_size=3,\n        ),\n        outputs_with_grads=st.sampled_from(\n            [[0], [1], [0, 1], [0, 2], [0, 1, 2, 3]]\n        )\n    )\n    @ht_settings(max_examples=10)\n    @utils.debug\n    def test_unroll_lstm(self, input_tensor, dim_out, outputs_with_grads,\n                         **kwargs):\n        lstms = [\n            _prepare_rnn(\n                *input_tensor.shape,\n                create_rnn=rnn_cell.LSTM,\n                outputs_with_grads=outputs_with_grads,\n                T=T,\n                two_d_initial_states=False,\n                dim_out=dim_out,\n                **kwargs\n            ) for T in [input_tensor.shape[0], None]\n        ]\n        outputs, nets, inputs = zip(*lstms)\n        workspace.FeedBlob(inputs[0][-1], input_tensor)\n\n        assert inputs[0] == inputs[1]\n        gradient_checker.NetGradientChecker.CompareNets(\n            nets, outputs, outputs_with_grads,\n            inputs_with_grads=inputs[0],\n        )\n\n    @given(\n        input_tensor=hu.tensor(min_dim=3, max_dim=3, max_value=3),\n        encoder_length=st.integers(min_value=1, max_value=3),\n        encoder_dim=st.integers(min_value=1, max_value=3),\n        hidden_units=st.integers(min_value=1, max_value=3),\n        num_layers=st.integers(min_value=1, max_value=3),\n        residual=st.booleans(),\n        final_dropout=st.booleans(),\n    )\n    @ht_settings(max_examples=10)\n    @utils.debug\n    def test_unroll_attention(self, input_tensor, encoder_length,\n                                    encoder_dim, hidden_units,\n                                    num_layers, residual,\n                                    final_dropout):\n\n        dim_out = [hidden_units] * num_layers\n        encoder_tensor = np.random.random(\n            (encoder_length, input_tensor.shape[1], encoder_dim),\n        ).astype('float32')\n\n        print('Decoder input shape: {}'.format(input_tensor.shape))\n        print('Encoder output shape: {}'.format(encoder_tensor.shape))\n\n        # Necessary because otherwise test fails for networks with fewer\n        # layers than previous test. TODO: investigate why.\n        workspace.ResetWorkspace()\n\n        net, unrolled = [\n            _prepare_attention(\n                t=input_tensor.shape[0],\n                n=input_tensor.shape[1],\n                dim_in=input_tensor.shape[2],\n                encoder_dim=encoder_dim,\n                T=T,\n                dim_out=dim_out,\n                residual=residual,\n                final_dropout=final_dropout,\n            ) for T in [input_tensor.shape[0], None]\n        ]\n\n        workspace.FeedBlob(net['input_blob'], input_tensor)\n        workspace.FeedBlob(net['encoder_outputs'], encoder_tensor)\n        workspace.FeedBlob(\n            net['weighted_encoder_outputs'],\n            np.random.random(encoder_tensor.shape).astype('float32'),\n        )\n\n        for input_name in [\n            'input_blob',\n            'encoder_outputs',\n            'weighted_encoder_outputs',\n        ]:\n            assert net[input_name] == unrolled[input_name]\n        for state_name, unrolled_state_name in zip(\n            net['initial_states'],\n            unrolled['initial_states'],\n        ):\n            assert state_name == unrolled_state_name\n\n        inputs_with_grads = net['initial_states'] + [\n            net['input_blob'],\n            net['encoder_outputs'],\n            net['weighted_encoder_outputs'],\n        ]\n\n        gradient_checker.NetGradientChecker.CompareNets(\n            [net['net'], unrolled['net']],\n            [[net['final_output']], [unrolled['final_output']]],\n            [0],\n            inputs_with_grads=inputs_with_grads,\n            threshold=0.000001,\n        )\n\n    @given(\n        input_tensor=hu.tensor(min_dim=3, max_dim=3),\n        forget_bias=st.floats(-10.0, 10.0),\n        forward_only=st.booleans(),\n        drop_states=st.booleans(),\n    )\n    @ht_settings(max_examples=10)\n    def test_layered_lstm(self, input_tensor, **kwargs):\n        for outputs_with_grads in [[0], [1], [0, 1, 2, 3]]:\n            for memory_optim in [False, True]:\n                _, net, inputs = _prepare_rnn(\n                    *input_tensor.shape,\n                    create_rnn=rnn_cell.LSTM,\n                    outputs_with_grads=outputs_with_grads,\n                    memory_optim=memory_optim,\n                    **kwargs\n                )\n                workspace.FeedBlob(inputs[-1], input_tensor)\n                workspace.RunNetOnce(net)\n                workspace.ResetWorkspace()\n\n    def test_lstm(self):\n        self.lstm_base(lstm_type=(rnn_cell.LSTM, lstm_reference))\n\n    def test_milstm(self):\n        self.lstm_base(lstm_type=(rnn_cell.MILSTM, milstm_reference))\n\n    @unittest.skip(\"This is currently numerically unstable\")\n    def test_norm_lstm(self):\n        self.lstm_base(\n            lstm_type=(rnn_cell.LayerNormLSTM, layer_norm_lstm_reference),\n        )\n\n    @unittest.skip(\"This is currently numerically unstable\")\n    def test_norm_milstm(self):\n        self.lstm_base(\n            lstm_type=(rnn_cell.LayerNormMILSTM, layer_norm_milstm_reference)\n        )\n\n    @given(\n        seed=st.integers(0, 2**32 - 1),\n        input_tensor=lstm_input(),\n        forget_bias=st.floats(-10.0, 10.0),\n        fwd_only=st.booleans(),\n        drop_states=st.booleans(),\n        memory_optim=st.booleans(),\n        outputs_with_grads=st.sampled_from([[0], [1], [0, 1, 2, 3]]),\n    )\n    def lstm_base(self, seed, lstm_type, outputs_with_grads, memory_optim,\n                  input_tensor, forget_bias, fwd_only, drop_states):\n        np.random.seed(seed)\n        create_lstm, ref = lstm_type\n        ref = partial(ref, forget_bias=forget_bias)\n\n        t, n, d = input_tensor.shape\n        assert d % 4 == 0\n        d = d // 4\n        ref = partial(ref, forget_bias=forget_bias, drop_states=drop_states)\n\n        net = _prepare_rnn(t, n, d, create_lstm,\n                            outputs_with_grads=outputs_with_grads,\n                            memory_optim=memory_optim,\n                            forget_bias=forget_bias,\n                            forward_only=fwd_only,\n                            drop_states=drop_states)[1]\n        # here we don't provide a real input for the net but just for one of\n        # its ops (RecurrentNetworkOp). So have to hardcode this name\n        workspace.FeedBlob(\"test_name_scope/external/recurrent/i2h\",\n                           input_tensor)\n        op = net._net.op[-1]\n        inputs = [workspace.FetchBlob(name) for name in op.input]\n\n        # Validate forward only mode is in effect\n        if fwd_only:\n            for arg in op.arg:\n                self.assertFalse(arg.name == 'backward_step_net')\n\n        self.assertReferenceChecks(\n            hu.cpu_do,\n            op,\n            inputs,\n            ref,\n            outputs_to_check=list(range(4)),\n        )\n\n        # Checking for input, gates_t_w and gates_t_b gradients\n        if not fwd_only:\n            for param in range(5):\n                self.assertGradientChecks(\n                    device_option=hu.cpu_do,\n                    op=op,\n                    inputs=inputs,\n                    outputs_to_check=param,\n                    outputs_with_grads=outputs_with_grads,\n                    threshold=0.01,\n                    stepsize=0.005,\n                )\n\n    def test_lstm_extract_predictor_net(self):\n        model = ModelHelper(name=\"lstm_extract_test\")\n\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU, 0)):\n            output, _, _, _ = rnn_cell.LSTM(\n                model=model,\n                input_blob=\"input\",\n                seq_lengths=\"seqlengths\",\n                initial_states=(\"hidden_init\", \"cell_init\"),\n                dim_in=20,\n                dim_out=40,\n                scope=\"test\",\n                drop_states=True,\n                return_last_layer_only=True,\n            )\n        # Run param init net to get the shapes for all inputs\n        shapes = {}\n        workspace.RunNetOnce(model.param_init_net)\n        for b in workspace.Blobs():\n            shapes[b] = workspace.FetchBlob(b).shape\n\n        # But export in CPU\n        (predict_net, export_blobs) = ExtractPredictorNet(\n            net_proto=model.net.Proto(),\n            input_blobs=[\"input\"],\n            output_blobs=[output],\n            device=core.DeviceOption(caffe2_pb2.CPU, 1),\n        )\n\n        # Create the net and run once to see it is valid\n        # Populate external inputs with correctly shaped random input\n        # and also ensure that the export_blobs was constructed correctly.\n        workspace.ResetWorkspace()\n        shapes['input'] = [10, 4, 20]\n        shapes['cell_init'] = [1, 4, 40]\n        shapes['hidden_init'] = [1, 4, 40]\n\n        print(predict_net.Proto().external_input)\n        self.assertTrue('seqlengths' in predict_net.Proto().external_input)\n        for einp in predict_net.Proto().external_input:\n            if einp == 'seqlengths':\n                workspace.FeedBlob(\n                    \"seqlengths\",\n                    np.array([10] * 4, dtype=np.int32)\n                )\n            else:\n                workspace.FeedBlob(\n                    einp,\n                    np.zeros(shapes[einp]).astype(np.float32),\n                )\n                if einp != 'input':\n                    self.assertTrue(einp in export_blobs)\n\n        print(str(predict_net.Proto()))\n        self.assertTrue(workspace.CreateNet(predict_net.Proto()))\n        self.assertTrue(workspace.RunNet(predict_net.Proto().name))\n\n        # Validate device options set correctly for the RNNs\n        for op in predict_net.Proto().op:\n            if op.type == 'RecurrentNetwork':\n                for arg in op.arg:\n                    if arg.name == \"step_net\":\n                        for step_op in arg.n.op:\n                            self.assertEqual(0, step_op.device_option.device_type)\n                            self.assertEqual(1, step_op.device_option.cuda_gpu_id)\n                    elif arg.name == 'backward_step_net':\n                        self.assertEqual(caffe2_pb2.NetDef(), arg.n)\n\n    def test_lstm_params(self):\n        model = ModelHelper(name=\"lstm_params_test\")\n\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU, 0)):\n            output, _, _, _ = rnn_cell.LSTM(\n                model=model,\n                input_blob=\"input\",\n                seq_lengths=\"seqlengths\",\n                initial_states=None,\n                dim_in=20,\n                dim_out=40,\n                scope=\"test\",\n                drop_states=True,\n                return_last_layer_only=True,\n            )\n        for param in model.GetParams():\n            self.assertNotEqual(model.get_param_info(param), None)\n\n    def test_milstm_params(self):\n        model = ModelHelper(name=\"milstm_params_test\")\n\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU, 0)):\n            output, _, _, _ = rnn_cell.MILSTM(\n                model=model,\n                input_blob=\"input\",\n                seq_lengths=\"seqlengths\",\n                initial_states=None,\n                dim_in=20,\n                dim_out=[40, 20],\n                scope=\"test\",\n                drop_states=True,\n                return_last_layer_only=True,\n            )\n        for param in model.GetParams():\n            self.assertNotEqual(model.get_param_info(param), None)\n\n    def test_layer_norm_lstm_params(self):\n        model = ModelHelper(name=\"layer_norm_lstm_params_test\")\n\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU, 0)):\n            output, _, _, _ = rnn_cell.LayerNormLSTM(\n                model=model,\n                input_blob=\"input\",\n                seq_lengths=\"seqlengths\",\n                initial_states=None,\n                dim_in=20,\n                dim_out=40,\n                scope=\"test\",\n                drop_states=True,\n                return_last_layer_only=True,\n            )\n        for param in model.GetParams():\n            self.assertNotEqual(model.get_param_info(param), None)\n\n    @given(encoder_output_length=st.integers(1, 3),\n           encoder_output_dim=st.integers(1, 3),\n           decoder_input_length=st.integers(1, 3),\n           decoder_state_dim=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           **hu.gcs)\n    def test_lstm_with_regular_attention(\n        self,\n        encoder_output_length,\n        encoder_output_dim,\n        decoder_input_length,\n        decoder_state_dim,\n        batch_size,\n        gc,\n        dc,\n    ):\n        self.lstm_with_attention(\n            partial(\n                rnn_cell.LSTMWithAttention,\n                attention_type=AttentionType.Regular,\n            ),\n            encoder_output_length,\n            encoder_output_dim,\n            decoder_input_length,\n            decoder_state_dim,\n            batch_size,\n            lstm_with_regular_attention_reference,\n            gc,\n        )\n\n    @given(encoder_output_length=st.integers(1, 3),\n           encoder_output_dim=st.integers(1, 3),\n           decoder_input_length=st.integers(1, 3),\n           decoder_state_dim=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           **hu.gcs)\n    def test_lstm_with_recurrent_attention(\n        self,\n        encoder_output_length,\n        encoder_output_dim,\n        decoder_input_length,\n        decoder_state_dim,\n        batch_size,\n        gc,\n        dc,\n    ):\n        self.lstm_with_attention(\n            partial(\n                rnn_cell.LSTMWithAttention,\n                attention_type=AttentionType.Recurrent,\n            ),\n            encoder_output_length,\n            encoder_output_dim,\n            decoder_input_length,\n            decoder_state_dim,\n            batch_size,\n            lstm_with_recurrent_attention_reference,\n            gc,\n        )\n\n    @given(encoder_output_length=st.integers(2, 2),\n           encoder_output_dim=st.integers(4, 4),\n           decoder_input_length=st.integers(3, 3),\n           decoder_state_dim=st.integers(4, 4),\n           batch_size=st.integers(5, 5),\n           **hu.gcs)\n    def test_lstm_with_dot_attention_same_dim(\n        self,\n        encoder_output_length,\n        encoder_output_dim,\n        decoder_input_length,\n        decoder_state_dim,\n        batch_size,\n        gc,\n        dc,\n    ):\n        self.lstm_with_attention(\n            partial(\n                rnn_cell.LSTMWithAttention,\n                attention_type=AttentionType.Dot,\n            ),\n            encoder_output_length,\n            encoder_output_dim,\n            decoder_input_length,\n            decoder_state_dim,\n            batch_size,\n            lstm_with_dot_attention_reference_same_dim,\n            gc,\n        )\n\n    @given(encoder_output_length=st.integers(1, 3),\n           encoder_output_dim=st.integers(4, 4),\n           decoder_input_length=st.integers(1, 3),\n           decoder_state_dim=st.integers(5, 5),\n           batch_size=st.integers(1, 3),\n           **hu.gcs)\n    def test_lstm_with_dot_attention_different_dim(\n        self,\n        encoder_output_length,\n        encoder_output_dim,\n        decoder_input_length,\n        decoder_state_dim,\n        batch_size,\n        gc,\n        dc,\n    ):\n        self.lstm_with_attention(\n            partial(\n                rnn_cell.LSTMWithAttention,\n                attention_type=AttentionType.Dot,\n            ),\n            encoder_output_length,\n            encoder_output_dim,\n            decoder_input_length,\n            decoder_state_dim,\n            batch_size,\n            lstm_with_dot_attention_reference_different_dim,\n            gc,\n        )\n\n    @given(encoder_output_length=st.integers(2, 3),\n           encoder_output_dim=st.integers(1, 3),\n           decoder_input_length=st.integers(1, 3),\n           decoder_state_dim=st.integers(1, 3),\n           batch_size=st.integers(1, 3),\n           **hu.gcs)\n    def test_lstm_with_coverage_attention(\n        self,\n        encoder_output_length,\n        encoder_output_dim,\n        decoder_input_length,\n        decoder_state_dim,\n        batch_size,\n        gc,\n        dc,\n    ):\n        self.lstm_with_attention(\n            partial(\n                rnn_cell.LSTMWithAttention,\n                attention_type=AttentionType.SoftCoverage,\n            ),\n            encoder_output_length,\n            encoder_output_dim,\n            decoder_input_length,\n            decoder_state_dim,\n            batch_size,\n            lstm_with_coverage_attention_reference,\n            gc,\n        )\n\n    def lstm_with_attention(\n        self,\n        create_lstm_with_attention,\n        encoder_output_length,\n        encoder_output_dim,\n        decoder_input_length,\n        decoder_state_dim,\n        batch_size,\n        ref,\n        gc,\n    ):\n        model = ModelHelper(name='external')\n        with core.DeviceScope(gc):\n            (\n                encoder_outputs,\n                decoder_inputs,\n                decoder_input_lengths,\n                initial_decoder_hidden_state,\n                initial_decoder_cell_state,\n                initial_attention_weighted_encoder_context,\n            ) = model.net.AddExternalInputs(\n                'encoder_outputs',\n                'decoder_inputs',\n                'decoder_input_lengths',\n                'initial_decoder_hidden_state',\n                'initial_decoder_cell_state',\n                'initial_attention_weighted_encoder_context',\n            )\n            create_lstm_with_attention(\n                model=model,\n                decoder_inputs=decoder_inputs,\n                decoder_input_lengths=decoder_input_lengths,\n                initial_decoder_hidden_state=initial_decoder_hidden_state,\n                initial_decoder_cell_state=initial_decoder_cell_state,\n                initial_attention_weighted_encoder_context=(\n                    initial_attention_weighted_encoder_context\n                ),\n                encoder_output_dim=encoder_output_dim,\n                encoder_outputs=encoder_outputs,\n                encoder_lengths=None,\n                decoder_input_dim=decoder_state_dim,\n                decoder_state_dim=decoder_state_dim,\n                scope='external/LSTMWithAttention',\n            )\n            op = model.net._net.op[-2]\n        workspace.RunNetOnce(model.param_init_net)\n\n        # This is original decoder_inputs after linear layer\n        decoder_input_blob = op.input[0]\n\n        workspace.FeedBlob(\n            decoder_input_blob,\n            np.random.randn(\n                decoder_input_length,\n                batch_size,\n                decoder_state_dim * 4,\n            ).astype(np.float32))\n        workspace.FeedBlob(\n            'external/LSTMWithAttention/encoder_outputs_transposed',\n            np.random.randn(\n                batch_size,\n                encoder_output_dim,\n                encoder_output_length,\n            ).astype(np.float32),\n        )\n        workspace.FeedBlob(\n            'external/LSTMWithAttention/weighted_encoder_outputs',\n            np.random.randn(\n                encoder_output_length,\n                batch_size,\n                encoder_output_dim,\n            ).astype(np.float32),\n        )\n        workspace.FeedBlob(\n            'external/LSTMWithAttention/coverage_weights',\n            np.random.randn(\n                encoder_output_length,\n                batch_size,\n                encoder_output_dim,\n            ).astype(np.float32),\n        )\n        workspace.FeedBlob(\n            decoder_input_lengths,\n            np.random.randint(\n                0,\n                decoder_input_length + 1,\n                size=(batch_size,)\n            ).astype(np.int32))\n        workspace.FeedBlob(\n            initial_decoder_hidden_state,\n            np.random.randn(1, batch_size, decoder_state_dim).astype(np.float32)\n        )\n        workspace.FeedBlob(\n            initial_decoder_cell_state,\n            np.random.randn(1, batch_size, decoder_state_dim).astype(np.float32)\n        )\n        workspace.FeedBlob(\n            initial_attention_weighted_encoder_context,\n            np.random.randn(\n                1, batch_size, encoder_output_dim).astype(np.float32)\n        )\n        workspace.FeedBlob(\n            'external/LSTMWithAttention/initial_coverage',\n            np.zeros((1, batch_size, encoder_output_length)).astype(np.float32),\n        )\n        inputs = [workspace.FetchBlob(name) for name in op.input]\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            reference=ref,\n            grad_reference=None,\n            output_to_grad=None,\n            outputs_to_check=list(range(6)),\n        )\n        gradients_to_check = [\n            index for (index, input_name) in enumerate(op.input)\n            if input_name != 'decoder_input_lengths'\n        ]\n        for param in gradients_to_check:\n            self.assertGradientChecks(\n                device_option=gc,\n                op=op,\n                inputs=inputs,\n                outputs_to_check=param,\n                outputs_with_grads=[0, 4],\n                threshold=0.01,\n                stepsize=0.001,\n            )\n\n    @given(seed=st.integers(0, 2**32 - 1),\n           n=st.integers(1, 10),\n           d=st.integers(1, 10),\n           t=st.integers(1, 10),\n           dtype=st.sampled_from([np.float32, np.float16]),\n           use_sequence_lengths=st.booleans(),\n           **hu.gcs)\n    def test_lstm_unit_recurrent_network(\n            self, seed, n, d, t, dtype, dc, use_sequence_lengths, gc):\n        np.random.seed(seed)\n        if dtype == np.float16:\n            # only supported with CUDA\n            assume(gc.device_type == caffe2_pb2.CUDA)\n            dc = [do for do in dc if do.device_type == caffe2_pb2.CUDA]\n\n        if use_sequence_lengths:\n            op_inputs = ['hidden_t_prev', 'cell_t_prev', 'gates_t',\n                         'seq_lengths', 'timestep']\n        else:\n            op_inputs = ['hidden_t_prev', 'cell_t_prev', 'gates_t', 'timestep']\n        op = core.CreateOperator(\n            'LSTMUnit',\n            op_inputs,\n            ['hidden_t', 'cell_t'],\n            sequence_lengths=use_sequence_lengths,\n        )\n        cell_t_prev = np.random.randn(1, n, d).astype(dtype)\n        hidden_t_prev = np.random.randn(1, n, d).astype(dtype)\n        gates = np.random.randn(1, n, 4 * d).astype(dtype)\n        seq_lengths = np.random.randint(1, t + 1, size=(n,)).astype(np.int32)\n        timestep = np.random.randint(0, t, size=(1,)).astype(np.int32)\n        if use_sequence_lengths:\n            inputs = [hidden_t_prev, cell_t_prev, gates, seq_lengths, timestep]\n        else:\n            inputs = [hidden_t_prev, cell_t_prev, gates, timestep]\n        input_device_options = {'timestep': hu.cpu_do}\n        self.assertDeviceChecks(\n            dc, op, inputs, [0],\n            input_device_options=input_device_options)\n\n        kwargs = {}\n        if dtype == np.float16:\n            kwargs['threshold'] = 1e-1  # default is 1e-4\n\n        def lstm_unit_reference(*args, **kwargs):\n            return lstm_unit(*args, sequence_lengths=use_sequence_lengths, **kwargs)\n\n        self.assertReferenceChecks(\n            gc, op, inputs, lstm_unit_reference,\n            input_device_options=input_device_options,\n            **kwargs)\n\n        kwargs = {}\n        if dtype == np.float16:\n            kwargs['threshold'] = 0.5  # default is 0.005\n\n        for i in range(2):\n            self.assertGradientChecks(\n                gc, op, inputs, i, [0, 1],\n                input_device_options=input_device_options,\n                **kwargs)\n\n    @given(input_length=st.integers(2, 5),\n           dim_in=st.integers(1, 3),\n           max_num_units=st.integers(1, 3),\n           num_layers=st.integers(2, 3),\n           batch_size=st.integers(1, 3))\n    def test_multi_lstm(\n        self,\n        input_length,\n        dim_in,\n        max_num_units,\n        num_layers,\n        batch_size,\n    ):\n        model = ModelHelper(name='external')\n        (\n            input_sequence,\n            seq_lengths,\n        ) = model.net.AddExternalInputs(\n            'input_sequence',\n            'seq_lengths',\n        )\n        dim_out = [\n            np.random.randint(1, max_num_units + 1)\n            for _ in range(num_layers)\n        ]\n        h_all, h_last, c_all, c_last = rnn_cell.LSTM(\n            model=model,\n            input_blob=input_sequence,\n            seq_lengths=seq_lengths,\n            initial_states=None,\n            dim_in=dim_in,\n            dim_out=dim_out,\n            # scope='test',\n            outputs_with_grads=(0,),\n            return_params=False,\n            memory_optimization=False,\n            forget_bias=0.0,\n            forward_only=False,\n            return_last_layer_only=True,\n        )\n\n        workspace.RunNetOnce(model.param_init_net)\n\n        seq_lengths_val = np.random.randint(\n            1,\n            input_length + 1,\n            size=(batch_size),\n        ).astype(np.int32)\n        input_sequence_val = np.random.randn(\n            input_length,\n            batch_size,\n            dim_in,\n        ).astype(np.float32)\n        workspace.FeedBlob(seq_lengths, seq_lengths_val)\n        workspace.FeedBlob(input_sequence, input_sequence_val)\n\n        hidden_input_list = []\n        cell_input_list = []\n        i2h_w_list = []\n        i2h_b_list = []\n        gates_w_list = []\n        gates_b_list = []\n\n        for i in range(num_layers):\n            hidden_input_list.append(\n                workspace.FetchBlob(\n                    'layer_{}/initial_hidden_state'.format(i)),\n            )\n            cell_input_list.append(\n                workspace.FetchBlob(\n                    'layer_{}/initial_cell_state'.format(i)),\n            )\n            # Input projection for the first layer is produced outside\n            # of the cell ans thus not scoped\n            prefix = 'layer_{}/'.format(i) if i > 0 else ''\n            i2h_w_list.append(\n                workspace.FetchBlob('{}i2h_w'.format(prefix)),\n            )\n            i2h_b_list.append(\n                workspace.FetchBlob('{}i2h_b'.format(prefix)),\n            )\n            gates_w_list.append(\n                workspace.FetchBlob('layer_{}/gates_t_w'.format(i)),\n            )\n            gates_b_list.append(\n                workspace.FetchBlob('layer_{}/gates_t_b'.format(i)),\n            )\n\n        workspace.RunNetOnce(model.net)\n        h_all_calc = workspace.FetchBlob(h_all)\n        h_last_calc = workspace.FetchBlob(h_last)\n        c_all_calc = workspace.FetchBlob(c_all)\n        c_last_calc = workspace.FetchBlob(c_last)\n\n        h_all_ref, h_last_ref, c_all_ref, c_last_ref = multi_lstm_reference(\n            input_sequence_val,\n            hidden_input_list,\n            cell_input_list,\n            i2h_w_list,\n            i2h_b_list,\n            gates_w_list,\n            gates_b_list,\n            seq_lengths_val,\n            forget_bias=0.0,\n        )\n\n        h_all_delta = np.abs(h_all_ref - h_all_calc).sum()\n        h_last_delta = np.abs(h_last_ref - h_last_calc).sum()\n        c_all_delta = np.abs(c_all_ref - c_all_calc).sum()\n        c_last_delta = np.abs(c_last_ref - c_last_calc).sum()\n\n        self.assertAlmostEqual(h_all_delta, 0.0, places=5)\n        self.assertAlmostEqual(h_last_delta, 0.0, places=5)\n        self.assertAlmostEqual(c_all_delta, 0.0, places=5)\n        self.assertAlmostEqual(c_last_delta, 0.0, places=5)\n\n        input_values = {\n            'input_sequence': input_sequence_val,\n            'seq_lengths': seq_lengths_val,\n        }\n        for param in model.GetParams():\n            value = workspace.FetchBlob(param)\n            input_values[str(param)] = value\n\n        output_sum = model.net.SumElements(\n            [h_all],\n            'output_sum',\n            average=True,\n        )\n        fake_loss = model.net.Tanh(\n            output_sum,\n        )\n        for param in model.GetParams():\n            gradient_checker.NetGradientChecker.Check(\n                model.net,\n                outputs_with_grad=[fake_loss],\n                input_values=input_values,\n                input_to_check=str(param),\n                print_net=False,\n                step_size=0.0001,\n                threshold=0.05,\n            )\n\n\nif __name__ == \"__main__\":\n    workspace.GlobalInit([\n        'caffe2',\n        '--caffe2_log_level=0',\n    ])\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/segment_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core\nfrom functools import partial\nfrom hypothesis import given\n\nfrom caffe2.python import workspace\nimport caffe2.python.hypothesis_test_util as hu\nimport numpy as np\nimport unittest\n\nclass TesterBase:\n    def segment_reduce_op(self, data, segment_ids, reducer, indices=None):\n        segments = self.split(data, segment_ids, indices)\n        output = np.zeros((len(segments), ) + data.shape[1:])\n        for i, segment in enumerate(segments):\n            if len(segment) > 0:\n                output[i] = reducer(segment)\n            else:\n                output[i] = 0.0\n        return output\n\n    def segment_reduce_grad_op(\n        self,\n        data,\n        segment_ids,\n        reducer_grad,\n        grad_out,\n        output,\n        indices=None\n    ):\n        segments = self.split(data, segment_ids, indices)\n        segment_grads = [\n            reducer_grad(grad_out[i], [output[i]], [segment])\n            for i, segment in enumerate(segments)\n        ]\n        return self.unsplit(data.shape[1:], segment_grads, segment_ids)\n\n    def _test(self, prefix, input_strategy, refs, gpu=False, **kwargs):\n        tester = self\n        operator_args = kwargs.pop('operator_args', {})\n        threshold = kwargs.pop('threshold', 1e-4)\n        grad_check = kwargs.pop('grad_check', True)\n\n        @given(X=input_strategy, **hu.gcs)\n        def test_segment_ops(self, X, gc, dc):\n            if not gpu and gc.device_type > 0:\n                return\n            for op_name, ref, grad_ref in refs:\n                inputs = ['input%d' % i for i in range(0, len(X))]\n                op = core.CreateOperator(\n                    prefix + op_name, inputs, ['output'], **operator_args\n                )\n                print('Operator %s, ' % op.type, gc.device_type)\n\n                def seg_reduce(data, *args):\n                    indices, segments = (\n                        args if len(args) == 2 else (None, args[0])\n                    )\n                    out = tester.segment_reduce_op(\n                        data=data,\n                        segment_ids=segments,\n                        indices=indices,\n                        reducer=ref\n                    )\n                    return (out, )\n\n                def seg_reduce_grad(grad_out, outputs, inputs):\n                    data = inputs[0]\n                    args = inputs[1:]\n                    indices, segments = (\n                        args if len(args) == 2 else (None, args[0])\n                    )\n                    # grad r.t. data\n                    grad_val = tester.segment_reduce_grad_op(\n                        data, segments, grad_ref, grad_out, outputs[0], indices\n                    )\n                    # if sparse, include indices along with data gradient\n                    data_grad_slice = (\n                        (grad_val, indices) if indices is not None else grad_val\n                    )\n                    # other inputs don't have gradient\n                    return (data_grad_slice, ) + (None, ) * (len(inputs) - 1)\n\n                kwargs = {}\n                if grad_check:\n                    kwargs['output_to_grad'] = 'output'\n                    kwargs['grad_reference'] = seg_reduce_grad\n                self.assertReferenceChecks(\n                    device_option=gc,\n                    op=op,\n                    inputs=X,\n                    reference=seg_reduce,\n                    threshold=threshold,\n                    **kwargs\n                )\n        return test_segment_ops\n\n\n\nclass SegmentsTester(TesterBase):\n    def split(self, data, segment_ids, indices=None):\n        \"\"\"\n        Given:\n          data[M1 x M2 x ... x Md]\n                          the input data\n          indices[N]      the index of each entry of segment_ids into data,\n                          where 0 <= index[i] < M1,\n                          with default indices=[0,1,...N]\n          segment_ids[N]  the segment_id for each entry of indices,\n\n        returns K outputs, each one containing data entries corresponding\n        to one of the segments present in `segment_ids`.\n        \"\"\"\n        if segment_ids.size == 0:\n            return []\n        K = max(segment_ids) + 1\n        outputs = [\n            np.zeros(\n                (np.count_nonzero(segment_ids == seg_id), ) + data.shape[1:],\n                dtype=data.dtype\n            ) for seg_id in range(0, K)\n        ]\n        counts = np.zeros(K, dtype=int)\n        for i, seg_id in enumerate(segment_ids):\n            data_idx = i if indices is None else indices[i]\n            outputs[seg_id][counts[seg_id]] = data[data_idx]\n            counts[seg_id] += 1\n        return outputs\n\n    def unsplit(self, extra_shape, inputs, segment_ids):\n        \"\"\" Inverse operation to `split`, with indices=None \"\"\"\n        output = np.zeros((len(segment_ids), ) + extra_shape)\n        if len(segment_ids) == 0:\n            return output\n        K = max(segment_ids) + 1\n        counts = np.zeros(K, dtype=int)\n        for i, seg_id in enumerate(segment_ids):\n            output[i] = inputs[seg_id][counts[seg_id]]\n            counts[seg_id] += 1\n        return output\n\n\nclass LengthsTester(TesterBase):\n    def split(self, data, lengths, indices=None):\n        K = len(lengths)\n        outputs = [\n            np.zeros((lengths[seg_id], ) + data.shape[1:],\n                     dtype=data.dtype) for seg_id in range(0, K)\n        ]\n        start = 0\n        for i in range(0, K):\n            for j in range(0, lengths[i]):\n                data_index = start + j\n                if indices is not None:\n                    data_index = indices[data_index]\n                outputs[i][j] = data[data_index]\n            start += lengths[i]\n        return outputs\n\n    def unsplit(self, extra_shape, inputs, lengths):\n        N = sum(lengths)\n        output = np.zeros((N, ) + extra_shape)\n        K = len(lengths)\n        assert len(inputs) == K\n        current = 0\n        for i in range(0, K):\n            for j in range(0, lengths[i]):\n                output[current] = inputs[i][j]\n                current += 1\n        return output\n\n\ndef sum_grad(grad_out, outputs, inputs):\n    return np.repeat(\n        np.expand_dims(grad_out, axis=0),\n        inputs[0].shape[0],\n        axis=0\n    )\n\n\ndef logsumexp(x):\n    return np.log(np.sum(np.exp(x), axis=0))\n\n\ndef logsumexp_grad(grad_out, outputs, inputs):\n    sum_exps = np.sum(np.exp(inputs[0]), axis=0)\n    return np.repeat(\n        np.expand_dims(grad_out / sum_exps, 0),\n        inputs[0].shape[0],\n        axis=0\n    ) * np.exp(inputs[0])\n\n\ndef logmeanexp(x):\n    return np.log(np.mean(np.exp(x), axis=0))\n\n\ndef mean(x):\n    return np.mean(x, axis=0)\n\n\ndef mean_grad(grad_out, outputs, inputs):\n    return np.repeat(\n        np.expand_dims(grad_out / inputs[0].shape[0], 0),\n        inputs[0].shape[0],\n        axis=0\n    )\n\n\ndef max_fwd(x):\n    return np.amax(x, axis=0)\n\n\ndef max_grad(grad_out, outputs, inputs):\n    flat_inputs = inputs[0].flatten()\n    flat_outputs = np.array(outputs[0]).flatten()\n    flat_grad_in = np.zeros(flat_inputs.shape)\n    flat_grad_out = np.array(grad_out).flatten()\n    blocks = inputs[0].shape[0]\n    if blocks == 0:\n        return np.zeros(inputs[0].shape)\n    block_size = flat_inputs.shape[0] // blocks\n\n    for i in range(block_size):\n        out_grad = flat_grad_out[i]\n        out = flat_outputs[i]\n        for j in range(blocks):\n            idx = j * block_size + i\n            # we can produce multiple outputs for max\n            if out == flat_inputs[idx]:\n                flat_grad_in[idx] = out_grad\n\n    return np.resize(flat_grad_in, inputs[0].shape)\n\n\nREFERENCES_ALL = [\n    ('Sum', partial(np.sum, axis=0), sum_grad),\n    ('Mean', partial(np.mean, axis=0), mean_grad),\n]\n\nREFERENCES_SORTED = [\n    ('RangeSum', partial(np.sum, axis=0), sum_grad),\n    ('RangeLogSumExp', logsumexp, logsumexp_grad),\n    # gradient is the same as sum\n    ('RangeLogMeanExp', logmeanexp, logsumexp_grad),\n    ('RangeMean', mean, mean_grad),\n    ('RangeMax', max_fwd, max_grad),\n]\n\nREFERENCES_LENGTHS_ONLY = [\n    ('Max', partial(np.amax, axis=0), max_grad),\n]\n\ndef sparse_lengths_weighted_sum_ref(D, W, I, L):\n    R = np.zeros(shape=(len(L), ) + D.shape[1:], dtype=D.dtype)\n    line = 0\n    for g in range(len(L)):\n        for _ in range(L[g]):\n            R[g, :] += W[line] * D[I[line], :]\n            line += 1\n    return [R]\n\n\ndef sparse_lengths_weighted_sum_grad_ref(\n        GO, fwd_out, fwd_in, grad_on_weights=False):\n    D, W, I, L = fwd_in\n    GI = np.zeros(shape=(len(I), ) + D.shape[1:], dtype=D.dtype)\n    GW = np.zeros(shape=W.shape, dtype=W.dtype) if grad_on_weights else None\n    line = 0\n    for g in range(len(L)):\n        for _ in range(L[g]):\n            GI[line, :] = W[line] * GO[g, :]\n            if GW is not None:\n                GW[line] = np.dot(GO[g].flatten(), D[I[line], :].flatten())\n            line += 1\n    print(GW)\n    return [(GI, I), GW, None, None]\n\n\nclass TestSegmentOps(hu.HypothesisTestCase):\n    def test_sorted_segment_ops(self):\n        SegmentsTester()._test(\n            'SortedSegment',\n            hu.segmented_tensor(\n                dtype=np.float32,\n                is_sorted=True,\n                allow_empty=True\n            ),\n            REFERENCES_ALL + REFERENCES_SORTED\n        )(self)\n\n    def test_unsorted_segment_ops(self):\n        SegmentsTester()._test(\n            'UnsortedSegment',\n            hu.segmented_tensor(\n                dtype=np.float32,\n                is_sorted=False,\n                allow_empty=True\n            ),\n            REFERENCES_ALL,\n        )(self)\n\n    def test_unsorted_segment_ops_gpu(self):\n        SegmentsTester()._test(\n            'UnsortedSegment',\n            hu.segmented_tensor(\n                dtype=np.float32,\n                is_sorted=False,\n                allow_empty=True,\n            ),\n            REFERENCES_ALL,\n            gpu=workspace.has_gpu_support,\n            grad_check=False,\n        )(self)\n\n    def test_sparse_sorted_segment_ops(self):\n        SegmentsTester()._test(\n            'SparseSortedSegment',\n            hu.sparse_segmented_tensor(\n                dtype=np.float32,\n                is_sorted=True,\n                allow_empty=True\n            ),\n            REFERENCES_ALL\n        )(self)\n\n    def test_sparse_unsorted_segment_ops(self):\n        SegmentsTester()._test(\n            'SparseUnsortedSegment',\n            hu.sparse_segmented_tensor(\n                dtype=np.float32,\n                is_sorted=False,\n                allow_empty=True\n            ),\n            REFERENCES_ALL\n        )(self)\n\n    def test_lengths_ops(self):\n        LengthsTester()._test(\n            'Lengths',\n            hu.lengths_tensor(\n                dtype=np.float32,\n                min_value=1,\n                max_value=5,\n                allow_empty=True\n            ),\n            REFERENCES_ALL + REFERENCES_LENGTHS_ONLY\n        )(self)\n\n    def test_sparse_lengths_ops(self):\n        for itype in [np.int32, np.int64]:\n            LengthsTester()._test(\n                'SparseLengths',\n                hu.sparse_lengths_tensor(\n                    dtype=np.float32,\n                    min_value=1,\n                    max_value=5,\n                    allow_empty=True,\n                    itype=itype,\n                ),\n                REFERENCES_ALL\n            )(self)\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    @given(**hu.gcs)\n    def test_unsorted_sums_large(self, gc, dc):\n        X = np.random.rand(10000, 32, 12).astype(np.float32)\n        segments = np.random.randint(0, 10000, size=10000).astype(np.int32)\n        op = core.CreateOperator(\"UnsortedSegmentSum\", [\"X\", \"segments\"], \"out\")\n        self.assertDeviceChecks(dc, op, [X, segments], [0])\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    @given(**hu.gcs)\n    def test_sorted_segment_range_mean(self, gc, dc):\n        X = np.random.rand(6, 32, 12).astype(np.float32)\n        segments = np.array([0, 0, 1, 1, 2, 3]).astype(np.int32)\n        op = core.CreateOperator(\n            \"SortedSegmentRangeMean\",\n            [\"X\", \"segments\"],\n            \"out\"\n        )\n        self.assertDeviceChecks(dc, op, [X, segments], [0])\n        self.assertGradientChecks(gc, op, [X, segments], 0, [0])\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    @given(**hu.gcs)\n    def test_sorted_segment_range_log_mean_exp(self, gc, dc):\n        X = np.random.rand(7, 32, 12).astype(np.float32)\n        segments = np.array([0, 0, 1, 1, 2, 2, 3]).astype(np.int32)\n        op = core.CreateOperator(\n            \"SortedSegmentRangeLogMeanExp\",\n            [\"X\", \"segments\"],\n            \"out\"\n        )\n        self.assertDeviceChecks(dc, op, [X, segments], [0])\n        self.assertGradientChecks(gc, op, [X, segments], 0, [0])\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    @given(**hu.gcs)\n    def test_unsorted_means_large(self, gc, dc):\n        X = np.random.rand(10000, 31, 19).astype(np.float32)\n        segments = np.random.randint(0, 10000, size=10000).astype(np.int32)\n        op = core.CreateOperator(\"UnsortedSegmentMean\", [\"X\", \"segments\"], \"out\")\n        self.assertDeviceChecks(dc, op, [X, segments], [0])\n\n    @given(**hu.gcs)\n    def test_lengths_sum_gpu(self, gc, dc):\n        X = np.random.rand(50, 3, 4, 5).astype(np.float32)\n        Y = np.asarray([20, 20, 10]).astype(np.int32)\n        op = core.CreateOperator(\"LengthsSum\", [\"X\", \"Y\"], \"out\")\n        self.assertDeviceChecks(dc, op, [X, Y], [0])\n        self.assertGradientChecks(gc, op, [X, Y], 0, [0])\n\n    @given(**hu.gcs)\n    def test_sparse_lengths_sum_gpu(self, gc, dc):\n        X = np.random.rand(50, 3, 4, 5).astype(np.float32)\n        Y = np.random.randint(0, 50, size=10).astype(np.int64)\n        Z = np.asarray([4, 4, 2]).astype(np.int32)\n        op = core.CreateOperator(\"SparseLengthsSum\", [\"X\", \"Y\", \"Z\"], \"out\")\n        self.assertDeviceChecks(dc, op, [X, Y, Z], [0])\n        self.assertGradientChecks(gc, op, [X, Y, Z], 0, [0])\n\n    @given(**hu.gcs)\n    def test_sparse_lengths_weighted_sum_gpu(self, gc, dc):\n        for grad_on_weights in (False, True):\n            D = np.random.rand(50, 3, 4, 5).astype(np.float32)\n            W = np.random.rand(10).astype(np.float32)\n            I = np.random.randint(0, 50, size=10).astype(np.int64)\n            L = np.asarray([4, 4, 2]).astype(np.int32)\n            op = core.CreateOperator(\n                \"SparseLengthsWeightedSum\",\n                [\"D\", \"W\", \"I\", \"L\"],\n                \"out\",\n                grad_on_weights=grad_on_weights)\n            self.assertDeviceChecks(dc, op, [D, W, I, L], [0])\n            self.assertReferenceChecks(\n                device_option=gc,\n                op=op,\n                inputs=[D, W, I, L],\n                reference=sparse_lengths_weighted_sum_ref,\n                threshold=1e-4,\n                output_to_grad='out',\n                grad_reference=partial(\n                    sparse_lengths_weighted_sum_grad_ref,\n                    grad_on_weights=grad_on_weights),\n            )\n            self.assertGradientChecks(gc, op, [D, W, I, L], 0, [0])\n\n    @given(**hu.gcs)\n    def test_sparse_lengths_indices_in_gradient_sum_gpu(self, gc, dc):\n        X = np.random.rand(3, 3, 4, 5).astype(np.float32)\n        Y = np.asarray([3, 3, 2]).astype(np.int32)\n        Z = np.random.randint(0, 50, size=8).astype(np.int64)\n        op = core.CreateOperator(\n            \"SparseLengthsIndicesInGradientSumGradient\", [\"X\", \"Y\", \"Z\"], \"out\"\n        )\n        self.assertDeviceChecks(dc, op, [X, Y, Z], [0])\n\n    @given(**hu.gcs_cpu_only)\n    def test_legacy_sparse_and_lengths_sum_gradient(self, gc, dc):\n        X = np.random.rand(3, 64).astype(np.float32)\n        Y = np.asarray([20, 20, 10]).astype(np.int32)\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"Y\", Y)\n        test_net = core.Net(\"test_net\")\n        test_net.SparseLengthsSumGradient([\"X\", \"Y\"], \"out1\")\n        test_net.LengthsSumGradient([\"X\", \"Y\"], \"out2\")\n        workspace.RunNetOnce(test_net)\n        out1 = workspace.FetchBlob(\"out1\")\n        out2 = workspace.FetchBlob(\"out2\")\n        self.assertTrue((out1 == out2).all())\n\n    @given(**hu.gcs)\n    def test_sparse_lengths_sum_invalid_index(self, gc, dc):\n        D = np.random.rand(50, 3, 4, 5).astype(np.float32)\n        I = (np.random.randint(0, 10000, size=10) + 10000).astype(np.int64)\n        L = np.asarray([4, 4, 2]).astype(np.int32)\n        op = core.CreateOperator(\n            \"SparseLengthsSum\",\n            [\"D\", \"I\", \"L\"],\n            \"out\")\n        workspace.FeedBlob('D', D)\n        workspace.FeedBlob('I', I)\n        workspace.FeedBlob('L', L)\n        with self.assertRaises(RuntimeError):\n            workspace.RunOperatorOnce(op)\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/selu_op_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport caffe2.python.hypothesis_test_util as hu\nimport numpy as np\n\nimport unittest\n\n\nclass TestSelu(hu.HypothesisTestCase):\n\n    @given(X=hu.tensor(),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n            **hu.gcs)\n    def test_selu_1(self, X, gc, dc, engine):\n        alpha = 1.0\n        scale = 2.0\n        op = core.CreateOperator(\"Selu\", [\"X\"], [\"Y\"],\n                                 alpha=alpha, scale=scale, engine=engine)\n        X = TestSelu.fix0(X)\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n        self.assertReferenceChecks(\n            gc, op, [X], lambda x: TestSelu.selu_ref(x, alpha=alpha, scale=scale)\n        )\n\n    @given(X=hu.tensor(),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n            **hu.gcs)\n    def test_selu_2(self, X, gc, dc, engine):\n        alpha = 1.6732\n        scale = 1.0507\n        op = core.CreateOperator(\"Selu\", [\"X\"], [\"Y\"],\n                                 alpha=alpha, scale=scale, engine=engine)\n\n        X = TestSelu.fix0(X)\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0], stepsize=1e-2, threshold=1e-2)\n        self.assertReferenceChecks(\n            gc, op, [X], lambda x: TestSelu.selu_ref(x, alpha=alpha, scale=scale)\n        )\n\n    @given(X=hu.tensor(),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n            **hu.gcs)\n    def test_selu_3(self, X, gc, dc, engine):\n        alpha = 1.3\n        scale = 1.1\n        op = core.CreateOperator(\"Selu\", [\"X\"], [\"Y\"],\n                                 alpha=alpha, scale=scale, engine=engine)\n\n        X = TestSelu.fix0(X)\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n        self.assertReferenceChecks(\n            gc, op, [X], lambda x: TestSelu.selu_ref(x, alpha=alpha, scale=scale)\n        )\n\n    @given(X=hu.tensor(),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n            **hu.gcs)\n    def test_selu_inplace(self, X, gc, dc, engine):\n        alpha = 1.3\n        scale = 1.1\n        op = core.CreateOperator(\"Selu\", [\"X\"], [\"X\"],\n                                 alpha=alpha, scale=scale, engine=engine)\n\n        X = TestSelu.fix0(X)\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n        # inplace gradient\n        Y = TestSelu.selu_ref(X, alpha=alpha, scale=scale)\n        dX = np.ones_like(X)\n        op2 = core.CreateOperator(\"SeluGradient\", [\"Y\", \"dX\"], [\"dX\"],\n                                  alpha=alpha, scale=scale, engine=engine)\n        self.assertDeviceChecks(dc, op2, [Y, dX], [0])\n\n    @staticmethod\n    def fix0(X):\n        # go away from the origin point to avoid kink problems\n        X += 0.02 * np.sign(X)\n        X[X == 0.0] += 0.02\n        return X\n\n    @staticmethod\n    def selu_ref(x, scale, alpha):\n        ret = scale * ((x > 0) * x + (x <= 0) * (alpha * (np.exp(x) - 1)))\n        return [ret]\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/sequence_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\nimport unittest\nfrom functools import partial\n\n\ndef _gen_test_add_padding(with_pad_data=True,\n                          is_remove=False):\n    def gen_with_size(args):\n        lengths, inner_shape = args\n        data_dim = [sum(lengths)] + inner_shape\n        lengths = np.array(lengths, dtype=np.int32)\n        if with_pad_data:\n            return st.tuples(\n                st.just(lengths),\n                hu.arrays(data_dim),\n                hu.arrays(inner_shape),\n                hu.arrays(inner_shape))\n        else:\n            return st.tuples(st.just(lengths), hu.arrays(data_dim))\n\n    min_len = 4 if is_remove else 0\n    lengths = st.lists(\n        st.integers(min_value=min_len, max_value=10),\n        min_size=0,\n        max_size=5)\n    inner_shape = st.lists(\n        st.integers(min_value=1, max_value=3),\n        min_size=0,\n        max_size=2)\n    return st.tuples(lengths, inner_shape).flatmap(gen_with_size)\n\n\ndef _add_padding_ref(\n        start_pad_width, end_pad_width, ret_lengths,\n        data, lengths, start_padding=None, end_padding=None):\n    if start_padding is None:\n        start_padding = np.zeros(data.shape[1:], dtype=data.dtype)\n    end_padding = (\n        end_padding if end_padding is not None else start_padding)\n    out_size = data.shape[0] + (\n        start_pad_width + end_pad_width) * len(lengths)\n    out = np.ndarray((out_size,) + data.shape[1:])\n    in_ptr = 0\n    out_ptr = 0\n    for length in lengths:\n        out[out_ptr:(out_ptr + start_pad_width)] = start_padding\n        out_ptr += start_pad_width\n        out[out_ptr:(out_ptr + length)] = data[in_ptr:(in_ptr + length)]\n        in_ptr += length\n        out_ptr += length\n        out[out_ptr:(out_ptr + end_pad_width)] = end_padding\n        out_ptr += end_pad_width\n    lengths_out = lengths + (start_pad_width + end_pad_width)\n    if ret_lengths:\n        return (out, lengths_out)\n    else:\n        return (out, )\n\n\ndef _remove_padding_ref(start_pad_width, end_pad_width, data, lengths):\n    pad_width = start_pad_width + end_pad_width\n    out_size = data.shape[0] - (\n        start_pad_width + end_pad_width) * len(lengths)\n    out = np.ndarray((out_size,) + data.shape[1:])\n    in_ptr = 0\n    out_ptr = 0\n    for length in lengths:\n        out_length = length - pad_width\n        out[out_ptr:(out_ptr + out_length)] = data[\n            (in_ptr + start_pad_width):(in_ptr + length - end_pad_width)]\n        in_ptr += length\n        out_ptr += out_length\n    lengths_out = lengths - (start_pad_width + end_pad_width)\n    return (out, lengths_out)\n\n\ndef _gather_padding_ref(start_pad_width, end_pad_width, data, lengths):\n    start_padding = np.zeros(data.shape[1:], dtype=data.dtype)\n    end_padding = np.zeros(data.shape[1:], dtype=data.dtype)\n    pad_width = start_pad_width + end_pad_width\n    ptr = 0\n    for length in lengths:\n        for _ in range(start_pad_width):\n            start_padding += data[ptr]\n            ptr += 1\n        ptr += length - pad_width\n        for _ in range(end_pad_width):\n            end_padding += data[ptr]\n            ptr += 1\n    return (start_padding, end_padding)\n\n\nclass TestSequenceOps(hu.HypothesisTestCase):\n    @given(start_pad_width=st.integers(min_value=1, max_value=2),\n           end_pad_width=st.integers(min_value=0, max_value=2),\n           args=_gen_test_add_padding(with_pad_data=True),\n           ret_lengths=st.booleans(),\n           **hu.gcs)\n    def test_add_padding(\n        self, start_pad_width, end_pad_width, args, ret_lengths, gc, dc\n    ):\n        lengths, data, start_padding, end_padding = args\n        start_padding = np.array(start_padding, dtype=np.float32)\n        end_padding = np.array(end_padding, dtype=np.float32)\n        outputs = ['output', 'lengths_out'] if ret_lengths else ['output']\n        op = core.CreateOperator(\n            'AddPadding', ['data', 'lengths', 'start_padding', 'end_padding'],\n            outputs,\n            padding_width=start_pad_width,\n            end_padding_width=end_pad_width\n        )\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[data, lengths, start_padding, end_padding],\n            reference=partial(\n                _add_padding_ref, start_pad_width, end_pad_width, ret_lengths\n            )\n        )\n\n    @given(start_pad_width=st.integers(min_value=1, max_value=2),\n           end_pad_width=st.integers(min_value=0, max_value=2),\n           args=_gen_test_add_padding(with_pad_data=False),\n           **hu.gcs)\n    def test_add_zero_padding(self, start_pad_width, end_pad_width, args, gc, dc):\n        lengths, data = args\n        op = core.CreateOperator(\n            'AddPadding',\n            ['data', 'lengths'],\n            ['output', 'lengths_out'],\n            padding_width=start_pad_width,\n            end_padding_width=end_pad_width)\n        self.assertReferenceChecks(\n            gc,\n            op,\n            [data, lengths],\n            partial(_add_padding_ref, start_pad_width, end_pad_width, True))\n\n    @given(start_pad_width=st.integers(min_value=1, max_value=2),\n           end_pad_width=st.integers(min_value=0, max_value=2),\n           data=hu.tensor(min_dim=1, max_dim=3),\n           **hu.gcs)\n    def test_add_padding_no_length(self, start_pad_width, end_pad_width, data, gc, dc):\n        op = core.CreateOperator(\n            'AddPadding',\n            ['data'],\n            ['output', 'output_lens'],\n            padding_width=start_pad_width,\n            end_padding_width=end_pad_width)\n        self.assertReferenceChecks(\n            gc,\n            op,\n            [data],\n            partial(\n                _add_padding_ref, start_pad_width, end_pad_width, True,\n                lengths=np.array([data.shape[0]])))\n\n    # Uncomment the following seed to make this fail.\n    # @seed(302934307671667531413257853548643485645)\n    # See https://github.com/caffe2/caffe2/issues/1547\n    @unittest.skip(\"flaky test\")\n    @given(start_pad_width=st.integers(min_value=1, max_value=2),\n           end_pad_width=st.integers(min_value=0, max_value=2),\n           args=_gen_test_add_padding(with_pad_data=False, is_remove=True),\n           **hu.gcs)\n    def test_remove_padding(self, start_pad_width, end_pad_width, args, gc, dc):\n        lengths, data = args\n        op = core.CreateOperator(\n            'RemovePadding',\n            ['data', 'lengths'],\n            ['output', 'lengths_out'],\n            padding_width=start_pad_width,\n            end_padding_width=end_pad_width)\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[data, lengths],\n            reference=partial(_remove_padding_ref, start_pad_width, end_pad_width))\n\n    @given(start_pad_width=st.integers(min_value=0, max_value=2),\n           end_pad_width=st.integers(min_value=0, max_value=2),\n           args=_gen_test_add_padding(with_pad_data=True),\n           **hu.gcs)\n    def test_gather_padding(self, start_pad_width, end_pad_width, args, gc, dc):\n        lengths, data, start_padding, end_padding = args\n        padded_data, padded_lengths = _add_padding_ref(\n            start_pad_width, end_pad_width, True, data,\n            lengths, start_padding, end_padding)\n        op = core.CreateOperator(\n            'GatherPadding',\n            ['data', 'lengths'],\n            ['start_padding', 'end_padding'],\n            padding_width=start_pad_width,\n            end_padding_width=end_pad_width)\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[padded_data, padded_lengths],\n            reference=partial(_gather_padding_ref, start_pad_width, end_pad_width))\n\n    @given(data=hu.tensor(min_dim=3, max_dim=3, dtype=np.float32,\n                          elements=st.floats(min_value=-np.inf,\n                                             max_value=np.inf),\n                          min_value=1, max_value=10),\n                          **hu.gcs)\n    def test_reverse_packed_segs(self, data, gc, dc):\n        max_length = data.shape[0]\n        batch_size = data.shape[1]\n        lengths = np.random.randint(max_length + 1, size=batch_size)\n\n        op = core.CreateOperator(\n            \"ReversePackedSegs\",\n            [\"data\", \"lengths\"],\n            [\"reversed_data\"])\n\n        def op_ref(data, lengths):\n            rev_data = np.array(data, copy=True)\n            for i in range(batch_size):\n                seg_length = lengths[i]\n                for j in range(seg_length):\n                    rev_data[j][i] = data[seg_length - 1 - j][i]\n            return (rev_data,)\n\n        def op_grad_ref(grad_out, outputs, inputs):\n            return op_ref(grad_out, inputs[1]) + (None,)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[data, lengths],\n            reference=op_ref,\n            output_to_grad='reversed_data',\n            grad_reference=op_grad_ref)\n\n    @given(data=hu.tensor(min_dim=1, max_dim=3, dtype=np.float32,\n                          elements=st.floats(min_value=-np.inf,\n                                             max_value=np.inf),\n                          min_value=10, max_value=10),\n           indices=st.lists(st.integers(min_value=0, max_value=9),\n                            min_size=0,\n                            max_size=10),\n           **hu.gcs_cpu_only)\n    def test_remove_data_blocks(self, data, indices, gc, dc):\n        indices = np.array(indices)\n\n        op = core.CreateOperator(\n            \"RemoveDataBlocks\",\n            [\"data\", \"indices\"],\n            [\"shrunk_data\"])\n\n        def op_ref(data, indices):\n            unique_indices = np.unique(indices)\n            sorted_indices = np.sort(unique_indices)\n            shrunk_data = np.delete(data, sorted_indices, axis=0)\n            return (shrunk_data,)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[data, indices],\n            reference=op_ref)\n\n    @given(elements=st.lists(st.integers(min_value=0, max_value=9),\n                             min_size=0,\n                             max_size=10),\n           **hu.gcs_cpu_only)\n    def test_find_duplicate_elements(self, elements, gc, dc):\n        mapping = {\n            0: \"a\",\n            1: \"b\",\n            2: \"c\",\n            3: \"d\",\n            4: \"e\",\n            5: \"f\",\n            6: \"g\",\n            7: \"h\",\n            8: \"i\",\n            9: \"j\"}\n        data = np.array([mapping[e] for e in elements], dtype='|S')\n\n        op = core.CreateOperator(\n            \"FindDuplicateElements\",\n            [\"data\"],\n            [\"indices\"])\n\n        def op_ref(data):\n            unique_data = []\n            indices = []\n            for i, e in enumerate(data):\n                if e in unique_data:\n                    indices.append(i)\n                else:\n                    unique_data.append(e)\n            return (np.array(indices, dtype=np.int64),)\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[data],\n            reference=op_ref)\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/shape_inference_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nimport unittest\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core, workspace, test_util, model_helper, brew, build\n\n\n@unittest.skipIf(build.CAFFE2_NO_OPERATOR_SCHEMA,\n                 'Built with CAFFE2_NO_OPERATOR_SCHEMA')\nclass TestShapeInference(test_util.TestCase):\n\n    def testShapeInferenceSimpleFC(self):\n        m = model_helper.ModelHelper(name=\"test_model\")\n\n        brew.fc(m, \"data\", \"fc1\", dim_in=96, dim_out=32)\n        brew.fc(m, \"fc1\", \"fc2\", dim_in=32, dim_out=55)\n\n        (shapes, types) = workspace.InferShapesAndTypes(\n            [m.param_init_net, m.net],\n            {'data': [64, 96]}\n        )\n\n        self.assertEquals(shapes['data'], [64, 96])\n        self.assertEquals(shapes['fc1_w'], [32, 96])\n        self.assertEquals(shapes['fc1_b'], [32])\n        self.assertEquals(shapes['fc1'], [64, 32])\n        self.assertEquals(shapes['fc2_w'], [55, 32])\n        self.assertEquals(shapes['fc2_b'], [55])\n        self.assertEquals(shapes['fc2'], [64, 55])\n\n    def testFCAxis2(self):\n        model = model_helper.ModelHelper(name=\"test_model\")\n        model.net.FC([\"x\", \"w\", \"b\"], [\"y\"], axis=2)\n        workspace.FeedBlob(\"x\", np.random.rand(4, 20, 36).astype(np.float32))\n        workspace.FeedBlob(\"w\", np.random.rand(36, 36).astype(np.float32))\n        workspace.FeedBlob(\"b\", np.random.rand(36,).astype(np.float32))\n        self.InferTensorRunAndCompare(model)\n\n    def testFCTransposed(self):\n        model = model_helper.ModelHelper(name=\"test_model\")\n        model.net.FCTransposed([\"x\", \"wt\", \"b\"], [\"y\"])\n        workspace.FeedBlob(\"x\", np.random.rand(20, 36).astype(np.float32))\n        workspace.FeedBlob(\"wt\", np.random.rand(36, 48).astype(np.float32))\n        workspace.FeedBlob(\"b\", np.random.rand(48,).astype(np.float32))\n        self.InferTensorRunAndCompare(model)\n\n    def testShapeInferenceSlice(self):\n        model = model_helper.ModelHelper(name=\"test_model\")\n        model.net.Slice([\"x\"], [\"y\"], starts=[0, 0, 0, 0], ends=[-1, -1, -3, -1])\n        workspace.FeedBlob(\"x\", np.random.rand(64, 1, 255, 384).astype(np.float32))\n\n        slice_starts = np.array([0, 0, 0, 0]).astype(np.int32)\n        slice_ends = np.array([-1, -1, -3, -1]).astype(np.int32)\n        slice_starts = model.net.GivenTensorIntFill(\n            [], shape=[4], values=slice_starts)\n        slice_ends = model.net.GivenTensorIntFill(\n            [], shape=[4], values=slice_ends)\n        model.net.Slice([\"x2\", slice_starts, slice_ends], [\"y2\"])\n        workspace.FeedBlob(\"x2\", np.random.rand(64, 1, 255, 384).astype(np.float32))\n\n        self.InferTensorRunAndCompare(model, [\"y2\"])\n\n    def testShapeInferenceDistances(self):\n        model = model_helper.ModelHelper(name=\"test_model\")\n        model.net.L1Distance([\"x1\", \"y1\"], \"dl1_D1\")\n        model.net.SquaredL2Distance([\"x1\", \"y1\"], \"dl2_D1\")\n        model.net.CosineSimilarity([\"x1\", \"y1\"], \"dcos_D1\")\n        model.net.DotProduct([\"x1\", \"y1\"], \"ddot_D1\")\n        model.net.DotProductWithPadding([\"x1\", \"y1\"], \"ddotpad_D1\")\n\n        model.net.L1Distance([\"x2\", \"y2\"], \"dl1_D2\")\n        model.net.SquaredL2Distance([\"x2\", \"y2\"], \"dl2_D2\")\n        model.net.CosineSimilarity([\"x2\", \"y2\"], \"dcos_D2\")\n        model.net.DotProduct([\"x2\", \"y2\"], \"ddot_D2\")\n        model.net.DotProductWithPadding([\"x2\", \"z2\"], \"ddotpad_D2\")\n\n        workspace.FeedBlob(\"x1\", np.random.rand(10).astype(np.float32))\n        workspace.FeedBlob(\"y1\", np.random.rand(10).astype(np.float32))\n\n        workspace.FeedBlob(\"x2\", np.random.rand(10, 5).astype(np.float32))\n        workspace.FeedBlob(\"y2\", np.random.rand(10, 5).astype(np.float32))\n        workspace.FeedBlob(\"z2\", np.random.rand(10, 4).astype(np.float32))\n        self.InferTensorRunAndCompare(model)\n\n    def testShapeInferenceReduceBackFrontX(self):\n        model = model_helper.ModelHelper(name=\"test_model\")\n        model.net.ReduceBackSum([\"x\"], [\"x_back_sum\"])\n        model.net.ReduceBackMean([\"x\"], [\"x_back_mean\"])\n        model.net.ReduceBackMax([\"x\"], [\"x_back_max\"])\n        model.net.ReduceFrontSum([\"x\"], [\"x_front_sum\"])\n        model.net.ReduceFrontMean([\"x\"], [\"x_front_mean\"])\n        model.net.ReduceFrontMax([\"x\"], [\"x_front_max\"])\n\n        workspace.FeedBlob(\"x\", np.random.rand(10, 12, 18).astype(np.float32))\n        self.InferTensorRunAndCompare(model)\n\n    def testGather(self):\n        model = model_helper.ModelHelper(name=\"test_model\")\n        model.net.Gather([\"X\", \"idx\"], \"Y\")\n        workspace.FeedBlob(\"X\", np.random.rand(100, 4, 5).astype(np.float32))\n        workspace.FeedBlob(\"idx\", np.array([[3, 18], [99, 4], [2, 5]]).astype(np.int32))\n        self.InferTensorRunAndCompare(model)\n\n    def testShapeInferenceConvNet(self):\n        model = model_helper.ModelHelper(name=\"convtest\")\n        model.NHWC2NCHW(\"data\", \"data_nchw\")\n        brew.conv(model, \"data_nchw\", 'conv1', 3, 64,\n                   weight_init=(\"MSRAFill\", {}), kernel=7,\n                   stride=2, pad=3, no_bias=0)\n        brew.spatial_bn(model, 'conv1', 'conv1_spatbn_relu', 64, epsilon=1e-3, is_test=False)\n        brew.relu(model, 'conv1_spatbn_relu', 'conv1_spatbn_relu')\n        brew.max_pool(model, 'conv1_spatbn_relu', 'pool1', kernel=3, stride=2)\n        brew.fc(model, 'pool1', 'fc', dim_in=(64 * 56 * 56), dim_out=100)\n        brew.dropout(model, 'fc', 'fc_drop', is_test=False)\n        model.Sigmoid('fc_drop', 'fc_sigm')\n        brew.softmax(model, 'fc_sigm', 'softmax')\n        model.LabelCrossEntropy(['softmax', 'label'], 'xent')\n        loss = model.AveragedLoss('xent', 'loss')\n\n        model.AddGradientOperators([loss])\n\n        LR = model.param_init_net.ConstantFill(\n            [], 'LR', shape=[1], value=0.1\n        )\n\n        for param in model.GetParams():\n            param_grad = model.param_to_grad[param]\n            param_momentum = model.param_init_net.ConstantFill(\n                [param], param + '_momentum', value=0.0\n            )\n            model.net.MomentumSGDUpdate(\n                [param_grad, param_momentum, LR, param],\n                [param_grad, param_momentum, param],\n            )\n\n        workspace.FeedBlob(\n            \"data\",\n            np.random.rand(16, 227, 227, 3).astype(np.float32),\n        )\n        workspace.FeedBlob(\n            \"label\",\n            (100 * np.random.rand(16)).astype(np.int32),\n        )\n        workspace.FeedBlob(\n            \"label\",\n            (100 * np.random.rand(16)).astype(np.int32),\n        )\n        # Then do automatic comparison test: run the next once to\n        # initialize everything\n        self.InferTensorRunAndCompare(model)\n\n    def testShapeInferenceTranspose(self):\n        model = model_helper.ModelHelper(name=\"test_model\")\n\n        workspace.FeedBlob(\n            \"tensor\",\n            np.random.rand(4, 2, 3, 3, 5).astype(np.float32)\n        )\n\n        # Testing with axes undefined\n        brew.transpose(\n            model,\n            [\"tensor\"],\n            \"transpose\",\n        )\n        self.InferTensorRunAndCompare(model)\n\n        # Testing with axes defined\n        brew.transpose(\n            model,\n            [\"tensor\"],\n            \"transpose\",\n            axes=np.random.permutation(5)\n        )\n\n        return self.InferTensorRunAndCompare(model)\n\n    def testShapeInferencePad(self):\n        model = model_helper.ModelHelper(name=\"padtest\")\n        model.PadImage(\"data\", 'padded', pad_t=100, pad_l=37, pad_b=28,\n                       pad_r=20, mode=\"constant\", order=\"NCHW\")\n\n        workspace.FeedBlob(\n            \"data\",\n            np.random.rand(16, 3, 228, 228).astype(np.float32),\n        )\n\n        self.InferTensorRunAndCompare(model)\n\n    def testShapeInferenceTwoClass(self):\n        model = model_helper.ModelHelper(name=\"twoclass\")\n        model.MakeTwoClass(\"v\", \"v2\")\n        workspace.FeedBlob(\"v\", np.random.rand(32).astype(np.float32))\n        self.InferTensorRunAndCompare(model)\n\n    def testShapeInferencePadZero(self):\n        model = model_helper.ModelHelper(name=\"padtest\")\n        model.PadImage(\"data\", 'padded', pad=0, mode=\"constant\",\n                       order=\"NCHW\")\n\n        workspace.FeedBlob(\n            \"data\",\n            np.random.rand(16, 3, 228, 228).astype(np.float32),\n        )\n\n        self.InferTensorRunAndCompare(model)\n\n    def testShapeInferenceMatMul(self):\n        model = model_helper.ModelHelper(name=\"test_model\")\n\n        model.MatMul([\"x\", \"y\"], \"MatMul\")\n\n        workspace.FeedBlob(\"x\", np.random.rand(10, 5).astype(np.float32))\n        workspace.FeedBlob(\"y\", np.random.rand(5, 10).astype(np.float32))\n\n        self.InferTensorRunAndCompare(model)\n\n    def testShapeInferenceSoftmaxWithLoss(self):\n        model = model_helper.ModelHelper(name=\"test_model\")\n\n        model.SoftmaxWithLoss(\n            [\"logits\", \"labels\"],\n            [\"softmax\", \"loss\"],\n        )\n\n        # 2D Shape of [batch_size, num_classes]\n        workspace.FeedBlob(\n            \"logits\",\n            np.random.rand(4, 3).astype(np.float32),\n        )\n\n        # Shape of size batch_size with all values [0, num_classes)\n        workspace.FeedBlob(\n            \"labels\",\n            np.random.randint(low=0, high=3, size=(4, 1)).astype(np.int32),\n        )\n        self.InferTensorRunAndCompare(model)\n\n        # Testing with 1D labels arg\n        workspace.FeedBlob(\n            \"logits\",\n            np.random.rand(4, 3).astype(np.float32),\n        )\n\n        workspace.FeedBlob(\n            \"labels\",\n            np.random.randint(low=0, high=3, size=4).astype(np.int32),\n        )\n        self.InferTensorRunAndCompare(model)\n\n        # Testing with weight_tensor\n        model.SoftmaxWithLoss(\n            [\"logits\", \"labels\", \"weight_tensor\"],\n            [\"softmax\", \"loss\"],\n        )\n\n        workspace.FeedBlob(\n            \"logits\",\n            np.random.rand(4, 3).astype(np.float32),\n        )\n\n        workspace.FeedBlob(\n            \"labels\",\n            np.random.randint(low=0, high=3, size=4).astype(np.int32),\n        )\n\n        workspace.FeedBlob(\n            \"weight_tensor\",\n            np.random.rand(4).astype(np.float32),\n        )\n        self.InferTensorRunAndCompare(model)\n\n        # Test spatial model\n        model = model_helper.ModelHelper(name=\"test_model\")\n        workspace.FeedBlob(\n            \"img\",\n            np.random.rand(32, 19, 33, 28).astype(np.float32)\n        )\n        workspace.FeedBlob(\n            \"img_labels\",\n            (np.random.rand(32, 33, 28) * 19).astype(np.int32)\n        )\n        model.SpatialSoftmaxWithLoss(\n            [\"img\", \"img_labels\"],\n            [\"softmax_img\", \"loss\"],\n        )\n        self.InferTensorRunAndCompare(model)\n\n    def testShapeInferenceIm2Col(self):\n        # Test with NCHW\n        model = model_helper.ModelHelper(name=\"test_model\")\n        model.Im2Col(\"X\", \"Y\", pad=1, kernel=4, dilation=2, stride=2,\n                     order=\"NCHW\")\n\n        workspace.FeedBlob(\n            \"X\",\n            np.random.rand(16, 3, 228, 228).astype(np.float32),\n        )\n\n        self.InferTensorRunAndCompare(model)\n\n        # Test with NHWC\n        model = model_helper.ModelHelper(name=\"test_model\")\n        model.Im2Col(\"X\", \"Y\", pad=1, kernel=4, dilation=2, stride=2,\n                     order=\"NHWC\")\n\n        workspace.FeedBlob(\n            \"X\",\n            np.random.rand(16, 228, 228, 3).astype(np.float32),\n        )\n\n        self.InferTensorRunAndCompare(model)\n\n        # Test with different width and height\n        model = model_helper.ModelHelper(name=\"test_model\")\n        model.Im2Col(\"X\", \"Y\", pad=1, kernel_h=8, kernel_w=4,\n                     dilation=2, stride=2)\n\n        workspace.FeedBlob(\n            \"X\",\n            np.random.rand(16, 3, 228, 114).astype(np.float32),\n        )\n\n        self.InferTensorRunAndCompare(model)\n\n    def testShapeInferenceTile(self):\n        m = model_helper.ModelHelper(name=\"test_model\")\n\n        workspace.FeedBlob(\n            \"tensor\",\n            np.random.rand(4, 2, 3, 3, 5).astype(np.float32)\n        )\n\n        # Testing with axes undefined\n        for i in range(0, 4):\n            m.net.Tile(\n                \"tensor\", \"tiled_tensor_{}\".format(i), tiles=5, axis=i)\n        self.InferTensorRunAndCompare(m)\n\n    def testShapeInferenceFlatten(self):\n        model = model_helper.ModelHelper(name=\"test_model\")\n        model.FlattenToVec(\"X\", \"FlatVec\")\n        model.FlattenToVec(\"empty\", \"EmptyFlatVec\")\n        workspace.FeedBlob(\"X\", np.random.rand(17, 5, 13).astype(np.float32))\n        workspace.FeedBlob(\"empty\", np.random.rand(0, 2, 3).astype(np.float32))\n\n        self.InferTensorRunAndCompare(model)\n\n        # test Flatten with default axis (=1)\n        model = model_helper.ModelHelper(name=\"test_model\")\n        model.Flatten(\"X\", \"Flat\")\n        model.Flatten(\"empty\", \"EmptyFlat\")\n        workspace.FeedBlob(\"X\", np.random.rand(17, 5, 13).astype(np.float32))\n        workspace.FeedBlob(\"empty\", np.random.rand(0, 2, 3).astype(np.float32))\n\n        self.InferTensorRunAndCompare(model)\n\n        # test Flatten with axis\n        model = model_helper.ModelHelper(name=\"test_model\")\n        x = np.random.randn(17, 5, 13)\n        for axis in range(x.ndim + 1):\n            model.Flatten(\"x\", \"Flat\", axis=axis)\n            workspace.FeedBlob(\"x\", x)\n            self.InferTensorRunAndCompare(model)\n\n        empty = np.random.randn(0, 5, 13)\n        for axis in range(empty.ndim + 1):\n            model.Flatten(\"empty\", \"Flat\", axis=axis)\n            workspace.FeedBlob(\"empty\", empty)\n            self.InferTensorRunAndCompare(model)\n\n    def testShapeInferenceReshape(self):\n        model = model_helper.ModelHelper(name=\"test_model\")\n        model.Reshape(\"X\", [\"Reshaped\", \"Old_Shape\"], shape=[8, 0, -1, 2])\n        workspace.FeedBlob(\"X\", np.random.rand(4, 26, 32).astype(np.float32))\n\n        self.InferTensorRunAndCompare(model)\n\n    def testShapeInferenceUnique(self):\n        for n in [0, 1]:\n            model = model_helper.ModelHelper(name=\"test_model\")\n            model.Unique(\"X\", [\"Y\"])\n            model.Unique(\"X\", [\"Z\", \"remap\"])\n            workspace.FeedBlob(\"X\", np.random.rand(n).astype(np.int64))\n            self.InferTensorRunAndCompare(model)\n\n    def testLengthsSum(self):\n        model = model_helper.ModelHelper(name=\"test_model\")\n        model.LengthsSum([\"X\", \"length\"], [\"sum\"])\n        workspace.FeedBlob(\"X\", np.random.rand(6, 32).astype(np.float32))\n        workspace.FeedBlob(\"length\", np.array([1, 2, 3], dtype=np.int32))\n\n        self.InferTensorRunAndCompare(model)\n\n    def testConcat(self):\n        net = core.Net(\"concat\")\n\n        net.Concat([\"A\", \"B\"], [\"C\", \"splits\"], axis=1)\n        net.Concat([\"C\", \"D\"], [\"E\"], order=\"NCHW\")\n        net.Concat([\"E\", \"F\"], [\"G\"], add_axis=1, order=\"NHWC\")\n        (shapes, types) = workspace.InferShapesAndTypes(\n            [net],\n            {\n                'A': [10, 12, 9, 10],\n                'B': [10, 9, 9, 10],\n                'D': [10, 2, 9, 10],\n                'F': [10, 23, 9, 10]\n            }\n        )\n        self.assertEqual(shapes['C'], [10, 21, 9, 10])\n        self.assertEqual(shapes['splits'], [2])\n        self.assertEqual(shapes['E'], [10, 23, 9, 10])\n        self.assertEqual(shapes['G'], [10, 23, 9, 2, 10])\n\n    def testSqueeze(self):\n        net = core.Net(\"sq\")\n        net.Squeeze([\"data\"], [\"data_squeezed\"], dims=[3, 1])\n        (shapes, types) = workspace.InferShapesAndTypes(\n            [net],\n            {'data': [64, 1, 96, 1, 4]}\n        )\n        self.assertEqual(shapes['data_squeezed'], [64, 96, 4])\n\n    def testCast(self):\n        model = model_helper.ModelHelper(name=\"test_model\")\n\n        types = [\n            ('bool', np.bool, caffe2_pb2.TensorProto.BOOL),\n            #('byte', None, caffe2_pb2.TensorProto.BYTE),\n            ('int8', np.int8, caffe2_pb2.TensorProto.INT8),\n            ('uint8', np.uint8, caffe2_pb2.TensorProto.UINT8),\n            ('int16', np.int16, caffe2_pb2.TensorProto.INT16),\n            ('uint16', np.uint16, caffe2_pb2.TensorProto.UINT16),\n            #('float16', np.float16, caffe2_pb2.TensorProto.FLOAT16),\n            ('int32', np.int32, caffe2_pb2.TensorProto.INT32),\n            ('float', np.float32, caffe2_pb2.TensorProto.FLOAT),\n            ('int64', np.int64, caffe2_pb2.TensorProto.INT64),\n            ('double', np.float64, caffe2_pb2.TensorProto.DOUBLE),\n            #('string', None, caffe2_pb2.TensorProto.STRING),\n        ]\n\n        for (xstr, xnp, _) in types:\n            xname = 'X%s' % xstr\n            workspace.FeedBlob(xname, np.random.rand(1).astype(xnp))\n            for (ystr, _, yc2) in types:\n                yname = 'Y%s_to_%s' % (xstr, ystr)\n                model.Cast(xname, yname, to=yc2)\n\n        self.InferTensorRunAndCompare(model)\n\n    def testShapeInferenceRoiPool(self):\n        for is_test in [True, False]:\n            model = model_helper.ModelHelper(name=\"test_model\")\n            outputs = ['Y'] if is_test else ['Y', 'argmaxes']\n            model.net.RoIPool(\n                ['X', 'R'], outputs, pooled_h=4, pooled_w=5, is_test=is_test)\n            workspace.FeedBlob(\n                \"X\",\n                np.random.rand(100, 3, 4, 5).astype(np.float32))\n            workspace.FeedBlob(\n                \"R\",\n                np.random.rand(2, 5).astype(np.float32))\n            self.InferTensorRunAndCompare(model)\n\n    def testShapeInferencePow(self):\n        model = model_helper.ModelHelper(name=\"powtest\")\n        model.Pow(\"x\", 'y', exponent=-1.0)\n        workspace.FeedBlob('x', np.random.rand(1, 2, 3, 4).astype(np.float32))\n        self.InferTensorRunAndCompare(model)\n\n    def InferTensorRunAndCompare(self, model, expected_uninferred_blobs=None):\n        '''\n        Runs shape inference, and then the model to check\n        that the inferred shapes agree with the actual ones\n\n        'expected_uninferred_blobs' is the list of blobs for which type and\n        shape cannot be inferred.\n        '''\n        (shapes, types) = workspace.InferShapesAndTypes(\n            [model.param_init_net, model.net],\n        )\n\n        # .. Create net\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.CreateNet(model.net, True)\n        workspace.RunNet(model.Proto().name)\n\n        # ... and then check the shapes mismatch\n        correct_shapes = {}\n        correct_types = {}\n        for b in workspace.Blobs():\n            arr = workspace.FetchBlob(b)\n            correct_shapes[b] = arr.shape\n            if type(arr) is np.ndarray:\n                if arr.dtype == np.dtype('float32'):\n                    correct_types[b] = caffe2_pb2.TensorProto.FLOAT\n                elif arr.dtype == np.dtype('int32'):\n                    correct_types[b] = caffe2_pb2.TensorProto.INT32\n                # BYTE\n                # STRING\n                elif arr.dtype == np.dtype('bool'):\n                    correct_types[b] = caffe2_pb2.TensorProto.BOOL\n                elif arr.dtype == np.dtype('uint8'):\n                    correct_types[b] = caffe2_pb2.TensorProto.UINT8\n                elif arr.dtype == np.dtype('int8'):\n                    correct_types[b] = caffe2_pb2.TensorProto.INT8\n                elif arr.dtype == np.dtype('uint16'):\n                    correct_types[b] = caffe2_pb2.TensorProto.UINT16\n                elif arr.dtype == np.dtype('int16'):\n                    correct_types[b] = caffe2_pb2.TensorProto.INT16\n                elif arr.dtype == np.dtype('int64'):\n                    correct_types[b] = caffe2_pb2.TensorProto.INT64\n                elif arr.dtype == np.dtype('float16'):\n                    correct_types[b] = caffe2_pb2.TensorProto.FLOAT16\n                elif arr.dtype == np.dtype('float64'):\n                    correct_types[b] = caffe2_pb2.TensorProto.DOUBLE\n                else:\n                    correct_types[b] = \"unknown {}\".format(arr.dtype)\n            else:\n                correct_types[b] = str(type(arr))\n\n        if expected_uninferred_blobs is None:\n            expected_uninferred_blobs = []\n        for b in correct_shapes:\n            # skip blobs for which shape couldn't be inferred\n            if b in expected_uninferred_blobs:\n                continue\n            self.assertTrue(\n                np.array_equal(\n                    np.array(shapes[b]).astype(np.int32),\n                    np.array(correct_shapes[b]).astype(np.int32)\n                ),\n                \"Shape {} mismatch: {} vs. correct {}\".format(\n                    b, shapes[b], correct_shapes[b]\n                )\n            )\n            self.assertFalse(\n                b not in types and b in correct_types,\n                \"Type for {} not defined\".format(b),\n            )\n            self.assertEqual(\n                types[b],\n                correct_types[b],\n                \"Type {} mismatch: {} vs. {}\".format(\n                    b, types[b], correct_types[b],\n                )\n            )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/sinusoid_position_encoding_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\nimport math\n\nMAX_TEST_EMBEDDING_SIZE = 20\nMAX_TEST_SEQUENCE_LENGTH = 10\nMAX_TEST_BATCH_SIZE = 5\nMIN_TEST_ALPHA = 5000.0\nMAX_TEST_ALPHA = 20000.0\nMIN_TEST_AMPLITUDE = 0.1\nMAX_TEST_AMPLITUDE = 10.0\n\n\nclass TestSinusoidPositionEncodingOp(hu.HypothesisTestCase):\n    @given(\n        positions_vec=hu.arrays(\n            dims=[MAX_TEST_SEQUENCE_LENGTH],\n            dtype=np.int32,\n            elements=st.integers(1, MAX_TEST_SEQUENCE_LENGTH)\n        ),\n        embedding_size=st.integers(1, MAX_TEST_EMBEDDING_SIZE),\n        batch_size=st.integers(1, MAX_TEST_BATCH_SIZE),\n        alpha=st.floats(MIN_TEST_ALPHA, MAX_TEST_ALPHA),\n        amplitude=st.floats(MIN_TEST_AMPLITUDE, MAX_TEST_AMPLITUDE),\n        **hu.gcs_cpu_only\n    )\n    def test_sinusoid_embedding(\n        self, positions_vec, embedding_size, batch_size, alpha, amplitude, gc, dc\n    ):\n        positions = np.tile(positions_vec, [batch_size, 1]).transpose()\n\n        op = core.CreateOperator(\n            \"SinusoidPositionEncoding\",\n            [\"positions\"],\n            [\"output\"],\n            embedding_size=embedding_size,\n            alpha=alpha,\n            amplitude=amplitude,\n        )\n\n        def sinusoid_encoding(dim, position):\n            x = 1. * position / math.pow(alpha, 1. * dim / embedding_size)\n            if dim % 2 == 0:\n                return amplitude * math.sin(x)\n            else:\n                return amplitude * math.cos(x)\n\n        def sinusoid_embedding_op(positions):\n            output_shape = (len(positions), len(positions[0]), embedding_size)\n            ar = np.zeros(output_shape)\n            for i, position_vector in enumerate(positions):\n                for j, position in enumerate(position_vector):\n                    for k in range(embedding_size):\n                        ar[i, j, k] = sinusoid_encoding(k, position)\n            return [ar]\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[positions],\n            reference=sinusoid_embedding_op,\n        )\n"
  },
  {
    "path": "caffe2/python/operator_test/softmax_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\nimport unittest\n\n\nclass TestSoftmaxOps(hu.HypothesisTestCase):\n\n    @given(n=st.sampled_from([2, 4, 71, 103]),\n           D=st.sampled_from([4, 8, 64, 79, 256, 333]),\n           engine=st.sampled_from([None, 'CUDNN']),\n           **hu.gcs)\n    def test_softmax(self, n, D, engine, gc, dc):\n        # n = number of examples, D = |labels|\n        # Initialize X and add 1e-2 for numerical stability\n        X = np.random.rand(n, D).astype(np.float32)\n        X = X + 1e-2\n\n        # Reference implementation of cross entropy with soft labels\n        def label_softmax(X):\n            probs = np.zeros((n, D))\n            rowmax = np.zeros(n)\n            for i in range(n):\n                rowmax[i] = max(X[i, ])\n                # We need to subtract the max to avoid numerical issues\n                probs[i] = X[i] - rowmax[i]\n                exps = np.exp(probs[i, ])\n                norm = sum(exps)\n                probs[i, ] = exps / norm\n\n            return [probs]\n\n        op = core.CreateOperator(\n            \"Softmax\",\n            [\"X\"],\n            [\"probs\"],\n            engine=engine\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=label_softmax,\n        )\n\n    @given(n=st.sampled_from([2, 4, 71, 103, 555, 751, 1201]),\n           D=st.sampled_from([4, 8, 64, 79, 256, 333, 1000]),\n           engine=st.sampled_from([None, 'CUDNN']),\n           **hu.gcs)\n    def test_softmax_grad(self, n, D, engine, gc, dc):\n        # n = number of examples, D = |labels|\n        # Initialize X and add 1e-2 for numerical stability\n        Y = np.random.rand(n, D).astype(np.float32)\n        dY = np.random.rand(n, D).astype(np.float32)\n        Y = Y + 1e-2\n\n        # Reference implementation of cross entropy with soft labels\n        def label_softmax_grad(X, dY):\n            dX = Y * 0.0\n            for i in range(n):\n                d = np.dot(Y[i, :], dY[i, :])\n                dX[i, :] = Y[i, :] * (dY[i, :] - d)\n            return [dX]\n\n        op = core.CreateOperator(\n            \"SoftmaxGradient\",\n            [\"Y\", \"dY\"],\n            [\"dX\"],\n            engine=engine\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[Y, dY],\n            reference=label_softmax_grad,\n        )\n\n    @given(axis=st.integers(min_value=1, max_value=4),\n           engine=st.sampled_from([None, 'CUDNN']),\n           **hu.gcs)\n    def test_softmax_axis(self, axis, engine, gc, dc):\n        np.random.seed(1)\n        X = np.random.randn(1, 2, 3, 2, 1).astype(np.float32)\n        X = X + 1e-2\n\n        def prod(xs):\n            p = 1\n            for x in xs:\n                p *= x\n            return p\n\n        N = prod(list(X.shape)[:axis])\n        D = prod(list(X.shape)[axis:])\n\n        # Reference implementation of cross entropy with soft labels\n        def label_softmax(X):\n            X_ = X.reshape(N, D)\n            probs = np.zeros((N, D))\n            rowmax = np.zeros(N)\n            for i in range(N):\n                rowmax[i] = max(X_[i, ])\n                # We need to subtract the max to avoid numerical issues\n                probs[i] = X_[i] - rowmax[i]\n                exps = np.exp(probs[i, ])\n                norm = sum(exps)\n                probs[i, ] = exps / norm\n\n            return [probs.reshape(*X.shape)]\n\n        op = core.CreateOperator(\n            \"Softmax\",\n            [\"X\"],\n            [\"probs\"],\n            axis=axis,\n            engine=engine,\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=label_softmax,\n        )\n\n        self.assertGradientChecks(\n            gc, op, [X], 0, [0], stepsize=1e-4, threshold=1e-2)\n\n    @given(n=st.integers(2, 10), D=st.integers(4, 16),\n           only_loss=st.booleans(), **hu.gcs)\n    def test_softmax_with_loss(self, n, D, gc, only_loss, dc):\n        # n = number of examples, D = |labels|\n        # Initialize X and add 1e-2 for numerical stability\n        np.random.seed(2603)\n        X = np.random.rand(n, D).astype(np.float32)\n        X = X + 1e-2\n\n        # Initialize label\n        label = (np.random.rand(n) * D).astype(np.int32)\n\n        # Reference implementation of cross entropy with soft labels\n        def label_softmax_crossent(X, label):\n            probs = np.zeros((n, D))\n            rowmax = np.zeros(n)\n            for i in range(n):\n                rowmax[i] = max(X[i, ])\n                # We need to subtract the max to avoid numerical issues\n                probs[i] = X[i] - rowmax[i]\n                exps = np.exp(probs[i, ])\n                norm = sum(exps)\n                probs[i, ] = exps / norm\n\n            label_xent = [-np.log(max(probs[i][label[i]], 1e-20))\n                          for i in range(n)]\n            avgloss = np.sum(label_xent) / float(n)\n            return (probs, avgloss)\n\n        op = core.CreateOperator(\n            \"SoftmaxWithLoss\",\n            [\"X\", \"label\"],\n            [\"probs\", \"avgloss\"],\n            only_loss=only_loss,\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, label],\n            reference=label_softmax_crossent,\n        )\n\n        self.assertGradientChecks(\n            gc, op, [X, label], 0, [1], stepsize=1e-4, threshold=1e-2)\n\n    @given(\n        n=st.integers(2, 5),\n        D=st.integers(4, 16),\n        only_loss=st.booleans(),\n        label_prob=st.booleans(),\n        **hu.gcs\n    )\n    def test_softmax_with_loss_axis_2(\n        self, n, D, only_loss, label_prob,\n        gc, dc\n    ):\n        np.random.seed(2603)\n        X = np.random.rand(n, n, D).astype(np.float32)\n        X = X + 1e-2\n\n        if label_prob:\n            label = np.random.rand(n, n, D).astype(np.float32)\n            label /= label.sum(axis=2, keepdims=True)\n        else:\n            label = (np.random.rand(n, n) * D).astype(np.int32)\n\n        # Reference implementation of cross entropy with soft labels\n        def label_softmax_crossent(X, label):\n            probs = np.zeros((n, n, D))\n            rowmax = np.zeros((n, n))\n            for i in range(n):\n                for j in range(n):\n                    rowmax[i, j] = max(X[i, j, ])\n                    # We need to subtract the max to avoid numerical issues\n                    probs[i, j] = X[i, j] - rowmax[i, j]\n                    exps = np.exp(probs[i, j, ])\n                    norm = sum(exps)\n                    probs[i, j, ] = exps / norm\n            label_xent = 0\n            for i in range(n):\n                for j in range(n):\n                    if label_prob:\n                        for k in range(D):\n                            label_xent += (\n                                -np.log(max(probs[i, j, k], 1e-20)) *\n                                label[i, j, k]\n                            )\n                    else:\n                        label_xent += -np.log(max(probs[i, j, label[i, j]], 1e-20))\n\n            avgloss = label_xent / float(n * n)\n            return (probs, avgloss)\n\n        op = core.CreateOperator(\n            \"SoftmaxWithLoss\",\n            [\"X\", \"label\"],\n            [\"probs\", \"avgloss\"],\n            only_loss=only_loss,\n            label_prob=label_prob,\n            axis=2,\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, label],\n            reference=label_softmax_crossent,\n        )\n\n        self.assertGradientChecks(\n            gc, op, [X, label], 0, [1], stepsize=1e-4, threshold=1e-2)\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    @given(**hu.gcs_gpu_only)\n    def test_softmax_with_loss_large(self, gc, dc):\n        np.random.seed(2603)\n        for n in [32]:\n            for D in [1000, 2000, 20000]:\n                # n = number of examples, D = |labels|\n                # Initialize X and add 1e-2 for numerical stability\n                X = np.random.rand(n, D).astype(np.float32)\n                X = X + 1e-2\n\n                # Initialize label\n                label = (np.random.rand(n) * D).astype(np.int32)\n\n                # Reference implementation of cross entropy with soft labels\n                def label_softmax_crossent(X, label):\n                    probs = np.zeros((n, D))\n                    rowmax = np.zeros(n)\n                    for i in range(n):\n                        rowmax[i] = max(X[i, ])\n                        # We need to subtract the max to avoid numerical issues\n                        probs[i] = X[i] - rowmax[i]\n                        exps = np.exp(probs[i, ])\n                        norm = sum(exps)\n                        probs[i, ] = exps / norm\n\n                    label_xent = [-np.log(max(probs[i][label[i]], 1e-20))\n                                  for i in range(n)]\n                    avgloss = np.sum(label_xent) / float(n)\n                    return (probs, avgloss)\n\n                op = core.CreateOperator(\n                    \"SoftmaxWithLoss\",\n                    [\"X\", \"label\"],\n                    [\"probs\", \"avgloss\"]\n                )\n\n                self.assertReferenceChecks(\n                    device_option=gc,\n                    op=op,\n                    inputs=[X, label],\n                    reference=label_softmax_crossent,\n                )\n\n    @given(n=st.integers(2, 10), D=st.integers(4, 16), **hu.gcs)\n    def test_softmax_with_loss_label_prob(self, n, D, gc, dc):\n        # n = number of examples, D = |labels|\n        # Initialize X and add 1e-2 for numerical stability\n        np.random.seed(2603)\n        X = np.random.rand(n, D).astype(np.float32)\n        X = X + 1e-2\n\n        # Initialize label\n        label = np.random.rand(D, n).astype(np.float32)\n\n        # normalize labels to sum to 1\n        label /= np.sum(label, axis=0)\n        label = label.transpose()\n\n        # Reference implementation of cross entropy with soft labels\n        def label_softmax_crossent(X, label):\n            probs = np.zeros((n, D))\n            rowmax = np.zeros(n)\n            for i in range(n):\n                rowmax[i] = max(X[i, ])\n                # We need to subtract the max to avoid numerical issues\n                probs[i] = X[i] - rowmax[i]\n                exps = np.exp(probs[i, ])\n                norm = sum(exps)\n                probs[i, ] = exps / norm\n\n            label_xent = np.zeros(X.shape)\n            for i in range(n):\n                for j in range(D):\n                    label_xent[i][j] = -np.log(\n                        max(probs[i, j], 1e-20)) * label[i, j]\n            avgloss = np.sum(label_xent) / float(n)\n            return (probs, avgloss)\n\n        op = core.CreateOperator(\n            \"SoftmaxWithLoss\",\n            [\"X\", \"label\"],\n            [\"probs\", \"avgloss\"],\n            label_prob=1\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, label],\n            reference=label_softmax_crossent,\n        )\n\n        self.assertGradientChecks(\n            gc, op, [X, label], 0, [1], stepsize=1e-4, threshold=1e-2)\n\n    @given(\n        n=st.integers(2, 10),\n        D=st.integers(4, 16),\n        only_loss=st.booleans(),\n        **hu.gcs)\n    def test_softmax_with_loss_weighted(self, n, D, only_loss, gc, dc):\n        # n = number of examples, D = |labels|\n        # Initialize X and add 1e-2 for numerical stability\n        np.random.seed(2603)\n        X = np.random.rand(n, D).astype(np.float32)\n        X = X + 1e-2\n\n        # Initialize label\n        label = (np.random.rand(n) * D).astype(np.int32)\n\n        # Init weights (weight by sample)\n        weights = np.random.rand(n).astype(np.float32)\n\n        # Reference implementation of cross entropy with soft labels\n        def label_softmax_crossent_weighted(X, label, weights):\n            probs = np.zeros((n, D))\n            rowmax = np.zeros(n)\n            for i in range(n):\n                rowmax[i] = max(X[i, ])\n                # We need to subtract the max to avoid numerical issues\n                probs[i] = X[i] - rowmax[i]\n                exps = np.exp(probs[i, ])\n                norm = sum(exps)\n                probs[i, ] = exps / norm\n\n            label_xent = [-weights[i] * np.log(max(probs[i][label[i]], 1e-20))\n                          for i in range(n)]\n            avgloss = np.sum(label_xent) / sum(weights)\n            return (probs, avgloss)\n\n        op = core.CreateOperator(\n            \"SoftmaxWithLoss\",\n            [\"X\", \"label\", \"weights\"],\n            [\"probs\", \"avgloss\"],\n            only_loss=only_loss,\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, label, weights],\n            reference=label_softmax_crossent_weighted,\n        )\n\n        self.assertGradientChecks(\n            gc, op, [X, label, weights], 0, [1], stepsize=1e-4, threshold=1e-2)\n\n    @given(n=st.integers(2, 10), D=st.integers(4, 16), **hu.gcs)\n    def test_softmax_with_loss_label_prob_weighted(self, n, D, gc, dc):\n        # n = number of examples, D = |labels|\n        # Initialize X and add 1e-2 for numerical stability\n        X = np.random.rand(n, D).astype(np.float32)\n        X = X + 1e-2\n\n        # Initialize label\n        label = np.random.rand(D, n).astype(np.float32)\n\n        # normalize labels to sum to 1\n        label /= np.sum(label, axis=0)\n        label = label.transpose()\n\n        # Init weights (weight by sample)\n        weights = np.random.rand(n).astype(np.float32)\n\n        # Reference implementation of cross entropy with soft labels\n        def label_softmax_crossent_weighted(X, label, weights):\n            probs = np.zeros((n, D))\n            rowmax = np.zeros(n)\n            for i in range(n):\n                rowmax[i] = max(X[i, ])\n                # We need to subtract the max to avoid numerical issues\n                probs[i] = X[i] - rowmax[i]\n                exps = np.exp(probs[i, ])\n                norm = sum(exps)\n                probs[i, ] = exps / norm\n\n            label_xent = np.zeros(X.shape)\n            for i in range(n):\n                for j in range(D):\n                    label_xent[i][j] = -np.log(\n                        max(probs[i, j], 1e-20)) * label[i, j] * weights[i]\n            avgloss = np.sum(label_xent) / sum(weights)\n            return (probs, avgloss)\n\n        op = core.CreateOperator(\n            \"SoftmaxWithLoss\",\n            [\"X\", \"label\", \"weights\"],\n            [\"probs\", \"avgloss\"],\n            label_prob=1,\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X, label, weights],\n            reference=label_softmax_crossent_weighted,\n        )\n\n        self.assertGradientChecks(\n            gc, op, [X, label, weights], 0, [1], stepsize=1e-4, threshold=1e-2)\n\n    @given(n=st.integers(2, 5), D=st.integers(2, 4),\n           weighted=st.booleans(), **hu.gcs)\n    def test_spatial_softmax_with_loss(self, n, D, weighted, gc, dc):\n        # n = number of examples, D = |labels|\n        # Initialize X and add 1e-2 for numerical stability\n        W = 18\n        H = 12\n        np.random.seed(2603)\n        X = np.random.rand(n, D, H, W).astype(np.float32)\n        X = X + 1e-2\n\n        weighted = True\n        weights = None\n        if weighted:\n            weights = np.random.rand(n, H, W).astype(np.float32)\n\n        # Initialize label. Some of the labels are (-1), i.e \"DONT CARE\"\n        label = (np.random.rand(n, H, W) * (D + 1)).astype(np.int32) - 1\n\n        def label_softmax_crossent_spatial(X, label, weights=None):\n            probs = np.zeros((n, D, H, W))\n            rowmax = np.zeros((n, H, W))\n            label_xent = np.zeros((n, H, W))\n            for i in range(n):\n                for x in range(W):\n                    for y in range(H):\n                        rowmax[i, y, x] = max(X[i, :, y, x])\n                        # We need to subtract the max to avoid numerical issues\n                        probs[i, :, y, x] = X[i, :, y, x] - rowmax[i, y, x]\n                        exps = np.exp(probs[i, :, y, x])\n                        probs[i, :, y, x] = exps / sum(exps)\n\n                        label_xent[:, y, x] = \\\n                            [-np.log(max(probs[j, label[i, y, x], y, x], 1e-20))\n                             for j in range(n)]\n\n            total_xent = 0.0\n            total_weight = 0.0\n            for y in range(H):\n                for x in range(W):\n                    for i in range(n):\n                        l = label[i, y, x]\n                        if (l != (-1)):\n                            w = 1.0 if weights is None else weights[i, y, x]\n                            total_xent += \\\n                                -np.log(max(probs[i, l, y, x], 1e-20)) * w\n                            total_weight += w\n            print(\"Total weight {}\".format(total_weight))\n\n            return (probs, total_xent / total_weight)\n\n        op = core.CreateOperator(\n            \"SpatialSoftmaxWithLoss\",\n            [\"X\", \"label\"] + ([] if weights is None else [\"weights\"]),\n            [\"probs\", \"avgloss\"],\n        )\n\n        inputs = [X, label] + ([] if weights is None else [weights])\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            reference=label_softmax_crossent_spatial,\n        )\n\n        self.assertGradientChecks(\n            gc, op, inputs, 0, [1], stepsize=1e-4, threshold=1e-2)\n\n    @given(n=st.integers(4, 5), D=st.integers(3, 4),\n           weighted=st.booleans(), **hu.gcs)\n    def test_spatial_softmax_with_loss_allignore(self, n, D, weighted, gc, dc):\n        # n = number of examples, D = |labels|\n        # Initialize X and add 1e-2 for numerical stability\n        W = 18\n        H = 12\n        np.random.seed(2603)\n        X = np.random.rand(n, D, H, W).astype(np.float32)\n        X = X + 1e-2\n\n        weighted = True\n        weights = None\n        if weighted:\n            weights = np.random.rand(n, H, W).astype(np.float32)\n\n        # Initialize label. All labels as \"DONT CARE\"\n        label = np.zeros((n, H, W)).astype(np.int32) - 1\n        print(label)\n\n        def label_softmax_crossent_spatial(X, label, weights=None):\n            probs = np.zeros((n, D, H, W))\n            rowmax = np.zeros((n, H, W))\n            label_xent = np.zeros((n, H, W))\n            for i in range(n):\n                for x in range(W):\n                    for y in range(H):\n                        rowmax[i, y, x] = max(X[i, :, y, x])\n                        # We need to subtract the max to avoid numerical issues\n                        probs[i, :, y, x] = X[i, :, y, x] - rowmax[i, y, x]\n                        exps = np.exp(probs[i, :, y, x])\n                        probs[i, :, y, x] = exps / sum(exps)\n\n                        label_xent[:, y, x] = \\\n                            [-np.log(max(probs[j, label[i, y, x], y, x], 1e-20))\n                            for j in range(n)]\n\n            return (probs, 0.0)\n\n        op = core.CreateOperator(\n            \"SpatialSoftmaxWithLoss\",\n            [\"X\", \"label\"] + ([] if weights is None else [\"weights\"]),\n            [\"probs\", \"avgloss\"],\n        )\n\n        inputs = [X, label] + ([] if weights is None else [weights])\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            reference=label_softmax_crossent_spatial,\n        )\n\n    @given(n=st.integers(4, 5), D=st.integers(3, 4),\n           weighted=st.booleans(), **hu.gcs)\n    def test_softmax_with_loss_zero_weight(self, n, D, weighted, gc, dc):\n        # n = number of examples, D = |labels|\n        # Initialize X and add 1e-2 for numerical stability\n        np.random.seed(2603)\n        X = np.random.rand(n, D).astype(np.float32)\n        X = X + 1e-2\n\n        weights = np.zeros(n).astype(np.float32)\n\n        # Initialize label\n        label = (np.random.rand(n) * D).astype(np.int32)\n\n        def label_softmax_crossent(X, label, weights=None):\n            probs = np.zeros((n, D))\n            rowmax = np.zeros((n))\n            for i in range(n):\n                rowmax[i] = max(X[i, ])\n                # We need to subtract the max to avoid numerical issues\n                probs[i] = X[i] - rowmax[i]\n                exps = np.exp(probs[i, ])\n                norm = sum(exps)\n                probs[i, ] = exps / norm\n            return (probs, 0.0)\n\n        op = core.CreateOperator(\n            \"SoftmaxWithLoss\",\n            [\"X\", \"label\", \"weights\"],\n            [\"probs\", \"avgloss\"]\n        )\n\n        inputs = [X, label] + ([] if weights is None else [weights])\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            reference=label_softmax_crossent,\n        )\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    def test_compare_cpugpu(self):\n        '''\n        Additional test that checks CPU and GPU returns same values\n        with larger examples. This is mainly to test the more complex\n        GPU implementation is correct.\n        '''\n        from caffe2.proto import caffe2_pb2\n\n        for _j in range(3):\n            gpuop = core.CreateOperator(\n                \"SpatialSoftmaxWithLoss\",\n                [\"X_gpu\", \"label_gpu\"],\n                [\"probs_gpu\", \"avgloss_gpu\"],\n                device_option=core.DeviceOption(caffe2_pb2.CUDA, 0)\n            )\n\n            cpuop = core.CreateOperator(\n                \"SpatialSoftmaxWithLoss\",\n                [\"X_cpu\", \"label_cpu\"],\n                [\"probs_cpu\", \"avgloss_cpu\"],\n                device_option=core.DeviceOption(caffe2_pb2.CPU)\n            )\n\n            n = 8\n            D = 4\n            W = 64 + int(np.random.rand(1) * 1024)\n            H = 64 + int(np.random.rand(1) * 1024)\n\n            print(\"W: {} H: {}\".format(W, H))\n\n            X = np.random.rand(n, D, H, W).astype(np.float32)\n            X = X + 1e-2\n\n            # Initialize label. Some of the labels are (-1), i.e \"DONT CARE\"\n            label = (np.random.rand(n, H, W) * (D + 1)).astype(np.int32) - 1\n\n            gpu0 = core.DeviceOption(caffe2_pb2.CUDA, 0)\n            workspace.FeedBlob(\"X_cpu\", X)\n            workspace.FeedBlob(\"label_cpu\", label)\n            workspace.FeedBlob(\"X_gpu\", X, device_option=gpu0)\n            workspace.FeedBlob(\"label_gpu\", label, device_option=gpu0)\n\n            workspace.RunOperatorOnce(gpuop)\n            workspace.RunOperatorOnce(cpuop)\n\n            probs_gpu = workspace.FetchBlob(\"probs_gpu\")\n            probs_cpu = workspace.FetchBlob(\"probs_cpu\")\n            loss_gpu = workspace.FetchBlob(\"avgloss_gpu\")\n            loss_cpu = workspace.FetchBlob(\"avgloss_cpu\")\n\n            np.testing.assert_allclose(probs_gpu, probs_cpu, rtol=1e-4)\n            np.testing.assert_allclose(loss_gpu, loss_cpu, rtol=1e-1)\n\nif __name__ == \"__main__\":\n    import unittest\n    import random\n    random.seed(2603)\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/softplus_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\n\nimport unittest\n\n\nclass TestSoftplus(hu.HypothesisTestCase):\n\n    @given(X=hu.tensor(),\n           **hu.gcs)\n    def test_softplus(self, X, gc, dc):\n        op = core.CreateOperator(\"Softplus\", [\"X\"], [\"Y\"])\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/sparse_gradient_checker_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nfrom scipy.sparse import coo_matrix\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestSparseGradient(hu.HypothesisTestCase):\n    @given(M=st.integers(min_value=5, max_value=20),\n           N=st.integers(min_value=5, max_value=20),\n           K=st.integers(min_value=5, max_value=15),\n           sparsity=st.floats(min_value=0.1, max_value=1.0),\n           **hu.gcs_cpu_only)\n    def test_sparse_gradient(self, M, N, K, sparsity, gc, dc):\n        X = np.random.randn(M, K).astype(np.float32)\n        X[X > sparsity] = 0\n        X_coo = coo_matrix(X)\n        val, key, seg = X_coo.data, X_coo.col, X_coo.row\n\n        val = val.astype(np.float32)\n        key = key.astype(np.int64)\n        seg = seg.astype(np.int32)\n\n        Y = np.random.randn(K, N).astype(np.float32)\n\n        op = core.CreateOperator(\n            'SparseUnsortedSegmentWeightedSum',\n            ['Y', 'val', 'key', 'seg'],\n            ['out'],\n            num_segments=M)\n\n        # Gradient check wrt Y\n        self.assertGradientChecks(\n            gc, op, [Y, val, key, seg], 0, [0])\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/sparse_lengths_sum_benchmark.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport argparse\nimport numpy as np\nimport datetime\n\nfrom caffe2.python import core, workspace\n\nDTYPES = {\n    'uint8': np.uint8,\n    'uint8_fused': np.uint8,\n    'float': np.float32,\n    'float16': np.float16,\n}\n\n\ndef benchmark_sparse_lengths_sum(\n        dtype_str,\n        categorical_limit,\n        embedding_size,\n        average_len,\n        batch_size,\n        iterations):\n    print('Preparing lookup table. ' + str(datetime.datetime.now()))\n\n    # We will use a constant, but non-trivial value so we save initialization\n    # time.\n    data = np.ones([categorical_limit, embedding_size], dtype=np.float32)\n    data *= 17.01\n\n    if dtype_str == 'uint8':\n        scale_bias = np.random.rand(categorical_limit, 2).astype(np.float32)\n        workspace.FeedBlob(\"scale_bias\", scale_bias.astype(np.float32))\n    elif dtype_str == 'uint8_fused':\n        scale_bias = np.random.randint(255, size=(categorical_limit, 8))\n        data = np.concatenate([data, scale_bias], axis=1)\n\n    print('Data has shape {} {}'.format(data.shape, datetime.datetime.now()))\n    workspace.FeedBlob(\"X\", data.astype(DTYPES[dtype_str]))\n\n    # In order to produce truly random lengths and indices, we will embed a\n    # Python operator in the net to generate them.\n    def f(_, outputs):\n        lengths = np.random.randint(\n            int(average_len * 0.75),\n            int(average_len * 1.25),\n            batch_size).astype(np.int32)\n        indices = np.random.randint(\n            0, categorical_limit, np.sum(lengths)).astype(np.int64)\n        outputs[0].feed(indices)\n        outputs[1].feed(lengths)\n\n    net = core.Net(\"mynet\")\n    net.Python(f)([], [\"indices\", \"lengths\", ])\n    if dtype_str == \"uint8\":\n        net.SparseLengthsSum8BitsRowwise([\"X\", \"indices\", \"lengths\", \"scale_bias\"], \"Y\")\n    elif dtype_str == \"uint8_fused\":\n        net.SparseLengthsSumFused8BitRowwise([\"X\", \"indices\", \"lengths\"], \"Y\")\n    else:\n        net.SparseLengthsSum([\"X\", \"indices\", \"lengths\"], \"Y\")\n    workspace.CreateNet(net)\n\n    # Set random seed, so that repeated runs will keep the same sequence of\n    # random indices.\n    np.random.seed(1701)\n\n    print('Preparation finished. ' + str(datetime.datetime.now()))\n\n    workspace.BenchmarkNet(net.Name(), 1, iterations, True)\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(\n        description=\"minimal benchmark for sparse lengths sum.\")\n    parser.add_argument(\n        '-d', \"--dtype\", choices=list(DTYPES.keys()), default=\"float\",\n        help=\"The data type for the input lookup table.\")\n    parser.add_argument(\n        '-e', \"--embedding-size\", type=int, default=6000000,\n        help=\"Lookup table size.\")\n    parser.add_argument(\n        \"--embedding-dim\", type=int, default=128,\n        help=\"Embedding dimension.\")\n    parser.add_argument(\n        \"--average_len\", type=int, default=27,\n        help=\"Sparse feature average lengths, default is 27\")\n    parser.add_argument(\n        \"--batch_size\", type=int, default=100,\n        help=\"The batch size.\")\n    parser.add_argument(\n        '-i', \"--iteration\", type=int, default=100000,\n        help=\"The number of iterations.\")\n    args, extra_args = parser.parse_known_args()\n    core.GlobalInit(['python'] + extra_args)\n    benchmark_sparse_lengths_sum(\n        args.dtype,\n        args.embedding_size,\n        args.embedding_dim,\n        args.average_len,\n        args.batch_size,\n        args.iteration)\n"
  },
  {
    "path": "caffe2/python/operator_test/sparse_normalize_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport hypothesis\nfrom hypothesis import given, settings, HealthCheck\nimport hypothesis.strategies as st\nimport numpy as np\n\nfrom caffe2.python import core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestSparseNormalize(hu.HypothesisTestCase):\n\n    @staticmethod\n    def ref_normalize(param_in, use_max_norm, norm):\n        param_norm = np.linalg.norm(param_in) + 1e-12\n        if (use_max_norm and param_norm > norm) or not use_max_norm:\n            param_in = param_in * norm / param_norm\n        return param_in\n\n    # Suppress filter_too_much health check.\n    # Likely caused by `assume` call falling through too often.\n    @settings(suppress_health_check=[HealthCheck.filter_too_much])\n    @given(inputs=hu.tensors(n=2, min_dim=2, max_dim=2),\n           use_max_norm=st.booleans(),\n           norm=st.floats(min_value=1.0, max_value=4.0),\n           data_strategy=st.data(),\n           **hu.gcs_cpu_only)\n    def test_sparse_normalize(self, inputs, use_max_norm, norm,\n                              data_strategy, gc, dc):\n        param, grad = inputs\n        param += 0.02 * np.sign(param)\n        param[param == 0.0] += 0.02\n\n        # Create an indexing array containing values that are lists of indices,\n        # which index into grad\n        indices = data_strategy.draw(\n            hu.tensor(dtype=np.int64, min_dim=1, max_dim=1,\n                      elements=st.sampled_from(np.arange(grad.shape[0]))),\n        )\n        hypothesis.note('indices.shape: %s' % str(indices.shape))\n\n        # For now, the indices must be unique\n        hypothesis.assume(np.array_equal(np.unique(indices.flatten()),\n                                         np.sort(indices.flatten())))\n\n        # Sparsify grad\n        grad = grad[indices]\n\n        op = core.CreateOperator(\n            \"SparseNormalize\",\n            [\"param\", \"indices\", \"grad\"],\n            [\"param\"],\n            use_max_norm=use_max_norm,\n            norm=norm,\n        )\n\n        def ref_sparse_normalize(param, indices, grad):\n            param_out = np.copy(param)\n            for _, index in enumerate(indices):\n                param_out[index] = self.ref_normalize(\n                    param[index],\n                    use_max_norm,\n                    norm,\n                )\n            return (param_out,)\n\n        # self.assertDeviceChecks(dc, op, [param, indices, grad], [0])\n        self.assertReferenceChecks(\n            gc, op, [param, indices, grad],\n            ref_sparse_normalize\n        )\n"
  },
  {
    "path": "caffe2/python/operator_test/sparse_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport numpy as np\nfrom caffe2.python import core\nfrom caffe2.python.test_util import rand_array\nimport caffe2.python.hypothesis_test_util as hu\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nclass TestScatterOps(hu.HypothesisTestCase):\n    # TODO(dzhulgakov): add test cases for failure scenarios\n    @given(num_args=st.integers(1, 5),\n           first_dim=st.integers(1, 20),\n           index_dim=st.integers(1, 10),\n           extra_dims=st.lists(st.integers(1, 4), min_size=0, max_size=3),\n           ind_type=st.sampled_from([np.int32, np.int64]),\n           **hu.gcs)\n    def testScatterWeightedSum(\n        self, num_args, first_dim, index_dim, extra_dims, ind_type, gc, dc):\n        ins = ['data', 'w0', 'indices']\n        for i in range(1, num_args + 1):\n            ins.extend(['x' + str(i), 'w' + str(i)])\n        op = core.CreateOperator(\n            'ScatterWeightedSum',\n            ins,\n            ['data'],\n            device_option=gc)\n        def ref(d, w0, ind, *args):\n            r = d.copy()\n            for i in ind:\n                r[i] *= w0\n            for i in range(0, len(args), 2):\n                x = args[i]\n                w = args[i+1]\n                for i, j in enumerate(ind):\n                    r[j] += w * x[i]\n            return [r]\n\n        d = rand_array(first_dim, *extra_dims)\n        ind = np.random.randint(0, first_dim, index_dim).astype(ind_type)\n        # ScatterWeightedSumOp only supports w0=1.0 in CUDAContext\n        if(gc == hu.gpu_do):\n            w0 = np.array(1.0).astype(np.float32)\n        else:\n            w0 = rand_array()\n        inputs = [d, w0, ind]\n        for _ in range(1, num_args + 1):\n            x = rand_array(index_dim, *extra_dims)\n            w = rand_array()\n            inputs.extend([x,w])\n        self.assertReferenceChecks(gc, op, inputs, ref, threshold=1e-3)\n\n    @given(first_dim=st.integers(1, 20),\n           index_dim=st.integers(1, 10),\n           extra_dims=st.lists(st.integers(1, 4), min_size=0, max_size=3),\n           data_type=st.sampled_from([np.float16, np.float32, np.int32, np.int64]),\n           ind_type=st.sampled_from([np.int32, np.int64]),\n           **hu.gcs)\n    def testScatterAssign(\n            self, first_dim, index_dim, extra_dims, data_type, ind_type, gc, dc):\n        op = core.CreateOperator('ScatterAssign',\n                                 ['data', 'indices', 'slices'], ['data'])\n        def ref(d, ind, x):\n            r = d.copy()\n            r[ind] = x\n            return [r]\n\n        # let's have indices unique\n        if first_dim < index_dim:\n            first_dim, index_dim = index_dim, first_dim\n        d = (rand_array(first_dim, *extra_dims) * 10).astype(data_type)\n        ind = np.random.choice(first_dim, index_dim,\n                               replace=False).astype(ind_type)\n        x = (rand_array(index_dim, *extra_dims) * 10).astype(data_type)\n        self.assertReferenceChecks(gc, op, [d, ind, x], ref, threshold=1e-3)\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/sparse_to_dense_mask_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestFcOperator(hu.HypothesisTestCase):\n\n    @given(n=st.integers(1, 10), k=st.integers(1, 5),\n           use_length=st.booleans(), **hu.gcs_cpu_only)\n    def test_sparse_to_dense_mask(self, n, k, use_length, gc, dc):\n        lengths = np.random.randint(k, size=n).astype(np.int32) + 1\n        N = sum(lengths)\n        indices = np.random.randint(5, size=N)\n        values = np.random.rand(N, 2).astype(np.float32)\n        default = np.random.rand(2).astype(np.float32)\n        mask = np.arange(3)\n        np.random.shuffle(mask)\n\n        input_str = ['indices', 'values', 'default']\n        input_data = [indices, values, default]\n        if use_length and n > 1:\n            input_str.append('lengths')\n            input_data.append(lengths)\n        output_str = ['output']\n\n        op = core.CreateOperator(\n            'SparseToDenseMask',\n            input_str,\n            output_str,\n            mask=mask,\n        )\n\n        # Check over multiple devices\n        self.assertDeviceChecks(\n            dc, op, input_data, [0])\n        # Gradient check for values\n        self.assertGradientChecks(\n            gc, op, input_data, 1, [0])\n\n    @given(n=st.integers(1, 10), k=st.integers(1, 5),\n           use_length=st.booleans(), **hu.gcs_cpu_only)\n    def test_sparse_to_dense_mask_with_int64(self, n, k, use_length, gc, dc):\n        lengths = np.random.randint(k, size=n).astype(np.int32) + 1\n        N = sum(lengths)\n        int64_mask = 10000000000\n        indices = np.random.randint(5, size=N) + int64_mask\n        values = np.random.rand(N, 2).astype(np.float32)\n        default = np.random.rand(2).astype(np.float32)\n        mask = np.arange(3) + int64_mask\n        np.random.shuffle(mask)\n\n        input_str = ['indices', 'values', 'default']\n        input_data = [indices, values, default]\n        if use_length and n > 1:\n            input_str.append('lengths')\n            input_data.append(lengths)\n        output_str = ['output']\n\n        op = core.CreateOperator(\n            'SparseToDenseMask',\n            input_str,\n            output_str,\n            mask=mask,\n        )\n\n        # Check over multiple devices\n        self.assertDeviceChecks(\n            dc, op, input_data, [0])\n        # Gradient check for values\n        self.assertGradientChecks(\n            gc, op, input_data, 1, [0])\n\n    @given(n=st.integers(1, 10), k=st.integers(1, 5),\n           dim=st.integers(1, 3), **hu.gcs_cpu_only)\n    def test_sparse_to_dense_mask_high_dim(self, n, k, dim, gc, dc):\n        lengths = np.random.randint(k, size=n).astype(np.int32) + 1\n        N = sum(lengths)\n        indices = np.random.randint(5, size=N)\n        shape = np.random.randint(5, size=dim).astype(np.int32) + 1\n        values = np.random.rand(*((N,) + tuple(shape))).astype(np.float32)\n        default = np.random.rand(*shape).astype(np.float32)\n        mask = np.arange(3)\n        np.random.shuffle(mask)\n\n        op = core.CreateOperator(\n            'SparseToDenseMask',\n            ['indices', 'values', 'default', 'lengths'],\n            ['output'],\n            mask=mask,\n        )\n\n        # Check over multiple devices\n        self.assertDeviceChecks(\n            dc, op, [indices, values, default, lengths], [0])\n        # Gradient check for values\n        self.assertGradientChecks(\n            gc, op, [indices, values, default, lengths], 1, [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/spatial_bn_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport numpy as np\nfrom caffe2.python import brew, core, workspace\nimport caffe2.python.hypothesis_test_util as hu\nfrom caffe2.python.model_helper import ModelHelper\n\n\nimport unittest\n\nclass TestSpatialBN(hu.HypothesisTestCase):\n\n    @given(size=st.integers(7, 10),\n           input_channels=st.integers(1, 10),\n           batch_size=st.integers(1, 3),\n           seed=st.integers(0, 65535),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           epsilon=st.floats(min_value=1e-5, max_value=1e-2),\n           inplace=st.sampled_from([True, False]),\n           **hu.gcs)\n    def test_spatialbn_test_mode_3d(\n            self, size, input_channels, batch_size, seed, order, epsilon,\n            inplace, gc, dc):\n        op = core.CreateOperator(\n            \"SpatialBN\",\n            [\"X\", \"scale\", \"bias\", \"mean\", \"var\"],\n            [\"X\" if inplace else \"Y\"],\n            order=order,\n            is_test=True,\n            epsilon=epsilon,\n            engine=\"CUDNN\",\n        )\n\n        def reference_spatialbn_test(X, scale, bias, mean, var):\n            if order == \"NCHW\":\n                scale = scale[np.newaxis, :, np.newaxis, np.newaxis, np.newaxis]\n                bias = bias[np.newaxis, :, np.newaxis, np.newaxis, np.newaxis]\n                mean = mean[np.newaxis, :, np.newaxis, np.newaxis, np.newaxis]\n                var = var[np.newaxis, :, np.newaxis, np.newaxis, np.newaxis]\n            return ((X - mean) / np.sqrt(var + epsilon) * scale + bias,)\n\n        np.random.seed(1701)\n        scale = np.random.rand(input_channels).astype(np.float32) + 0.5\n        bias = np.random.rand(input_channels).astype(np.float32) - 0.5\n        mean = np.random.randn(input_channels).astype(np.float32)\n        var = np.random.rand(input_channels).astype(np.float32) + 0.5\n        X = np.random.rand(batch_size, input_channels, size, size, size)\\\n                .astype(np.float32) - 0.5\n\n        if order == \"NHWC\":\n            X = X.transpose(0, 2, 3, 4, 1)\n        self.assertReferenceChecks(gc, op, [X, scale, bias, mean, var],\n                                   reference_spatialbn_test)\n        self.assertDeviceChecks(dc, op, [X, scale, bias, mean, var], [0])\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    @given(size=st.integers(7, 10),\n           input_channels=st.integers(1, 10),\n           batch_size=st.integers(1, 3),\n           seed=st.integers(0, 65535),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           epsilon=st.floats(min_value=1e-5, max_value=1e-2),\n           inplace=st.sampled_from([True, False]),\n           **hu.gcs)\n    def test_spatialbn_test_mode_1d(\n            self, size, input_channels, batch_size, seed, order, epsilon,\n            inplace, gc, dc):\n        op = core.CreateOperator(\n            \"SpatialBN\",\n            [\"X\", \"scale\", \"bias\", \"mean\", \"var\"],\n            [\"X\" if inplace else \"Y\"],\n            order=order,\n            is_test=True,\n            epsilon=epsilon,\n            engine=\"CUDNN\",\n        )\n\n        def reference_spatialbn_test(X, scale, bias, mean, var):\n            if order == \"NCHW\":\n                scale = scale[np.newaxis, :, np.newaxis]\n                bias = bias[np.newaxis, :, np.newaxis]\n                mean = mean[np.newaxis, :, np.newaxis]\n                var = var[np.newaxis, :, np.newaxis]\n            return ((X - mean) / np.sqrt(var + epsilon) * scale + bias,)\n\n        np.random.seed(1701)\n        scale = np.random.rand(input_channels).astype(np.float32) + 0.5\n        bias = np.random.rand(input_channels).astype(np.float32) - 0.5\n        mean = np.random.randn(input_channels).astype(np.float32)\n        var = np.random.rand(input_channels).astype(np.float32) + 0.5\n        X = np.random.rand(\n            batch_size, input_channels, size).astype(np.float32) - 0.5\n\n        if order == \"NHWC\":\n            X = X.swapaxes(1, 2)\n        self.assertReferenceChecks(gc, op, [X, scale, bias, mean, var],\n                                   reference_spatialbn_test)\n\n    @given(size=st.integers(7, 10),\n           input_channels=st.integers(1, 10),\n           batch_size=st.integers(1, 3),\n           seed=st.integers(0, 65535),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           epsilon=st.floats(min_value=1e-5, max_value=1e-2),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           inplace=st.sampled_from([True, False]),\n           **hu.gcs)\n    def test_spatialbn_test_mode(\n            self, size, input_channels, batch_size, seed, order, epsilon,\n            inplace, engine, gc, dc):\n        op = core.CreateOperator(\n            \"SpatialBN\",\n            [\"X\", \"scale\", \"bias\", \"mean\", \"var\"],\n            [\"X\" if inplace else \"Y\"],\n            order=order,\n            is_test=True,\n            epsilon=epsilon,\n            engine=engine\n        )\n\n        def reference_spatialbn_test(X, scale, bias, mean, var):\n            if order == \"NCHW\":\n                scale = scale[np.newaxis, :, np.newaxis, np.newaxis]\n                bias = bias[np.newaxis, :, np.newaxis, np.newaxis]\n                mean = mean[np.newaxis, :, np.newaxis, np.newaxis]\n                var = var[np.newaxis, :, np.newaxis, np.newaxis]\n            return ((X - mean) / np.sqrt(var + epsilon) * scale + bias,)\n\n        np.random.seed(1701)\n        scale = np.random.rand(input_channels).astype(np.float32) + 0.5\n        bias = np.random.rand(input_channels).astype(np.float32) - 0.5\n        mean = np.random.randn(input_channels).astype(np.float32)\n        var = np.random.rand(input_channels).astype(np.float32) + 0.5\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n\n        if order == \"NHWC\":\n            X = X.swapaxes(1, 2).swapaxes(2, 3)\n\n        self.assertReferenceChecks(gc, op, [X, scale, bias, mean, var],\n                                   reference_spatialbn_test)\n        self.assertDeviceChecks(dc, op, [X, scale, bias, mean, var], [0])\n\n    @given(size=st.integers(7, 10),\n           input_channels=st.integers(1, 10),\n           batch_size=st.integers(1, 3),\n           seed=st.integers(0, 65535),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           epsilon=st.floats(1e-5, 1e-2),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           inplace=st.sampled_from([True, False]),\n           **hu.gcs)\n    def test_spatialbn_train_mode(\n            self, size, input_channels, batch_size, seed, order, epsilon,\n            inplace, engine, gc, dc):\n        op = core.CreateOperator(\n            \"SpatialBN\",\n            [\"X\", \"scale\", \"bias\", \"running_mean\", \"running_var\"],\n            [\"X\" if inplace else \"Y\",\n             \"running_mean\", \"running_var\", \"saved_mean\", \"saved_var\"],\n            order=order,\n            is_test=False,\n            epsilon=epsilon,\n            engine=engine,\n        )\n        np.random.seed(1701)\n        scale = np.random.rand(input_channels).astype(np.float32) + 0.5\n        bias = np.random.rand(input_channels).astype(np.float32) - 0.5\n        mean = np.random.randn(input_channels).astype(np.float32)\n        var = np.random.rand(input_channels).astype(np.float32) + 0.5\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n\n        if order == \"NHWC\":\n            X = X.swapaxes(1, 2).swapaxes(2, 3)\n\n        self.assertDeviceChecks(dc, op, [X, scale, bias, mean, var],\n                                [0, 1, 2, 3, 4])\n\n    @given(size=st.integers(7, 10),\n           input_channels=st.integers(1, 10),\n           batch_size=st.integers(1, 3),\n           seed=st.integers(0, 65535),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           epsilon=st.floats(min_value=1e-5, max_value=1e-2),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs)\n    def test_spatialbn_train_mode_gradient_check(\n            self, size, input_channels, batch_size, seed, order, epsilon,\n            engine, gc, dc):\n        op = core.CreateOperator(\n            \"SpatialBN\",\n            [\"X\", \"scale\", \"bias\", \"mean\", \"var\"],\n            [\"Y\", \"mean\", \"var\", \"saved_mean\", \"saved_var\"],\n            order=order,\n            is_test=False,\n            epsilon=epsilon,\n            engine=engine\n        )\n        np.random.seed(seed)\n        scale = np.random.rand(input_channels).astype(np.float32) + 0.5\n        bias = np.random.rand(input_channels).astype(np.float32) - 0.5\n        mean = np.random.randn(input_channels).astype(np.float32)\n        var = np.random.rand(input_channels).astype(np.float32) + 0.5\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32) - 0.5\n        if order == \"NHWC\":\n            X = X.swapaxes(1, 2).swapaxes(2, 3)\n\n        for input_to_check in [0, 1, 2]:  # dX, dScale, dBias\n            self.assertGradientChecks(gc, op, [X, scale, bias, mean, var],\n                                      input_to_check, [0])\n\n    @given(size=st.integers(7, 10),\n           input_channels=st.integers(1, 10),\n           batch_size=st.integers(1, 3),\n           seed=st.integers(0, 65535),\n           order=st.sampled_from([\"NCHW\", \"NHWC\"]),\n           epsilon=st.floats(min_value=1e-5, max_value=1e-2),\n           **hu.gcs)\n    def test_spatialbn_train_mode_gradient_check_1d(\n            self, size, input_channels, batch_size, seed, order, epsilon,\n            gc, dc):\n        op = core.CreateOperator(\n            \"SpatialBN\",\n            [\"X\", \"scale\", \"bias\", \"mean\", \"var\"],\n            [\"Y\", \"mean\", \"var\", \"saved_mean\", \"saved_var\"],\n            order=order,\n            is_test=False,\n            epsilon=epsilon,\n            engine=\"CUDNN\",\n        )\n        np.random.seed(seed)\n        scale = np.random.rand(input_channels).astype(np.float32) + 0.5\n        bias = np.random.rand(input_channels).astype(np.float32) - 0.5\n        mean = np.random.randn(input_channels).astype(np.float32)\n        var = np.random.rand(input_channels).astype(np.float32) + 0.5\n        X = np.random.rand(\n            batch_size, input_channels, size).astype(np.float32) - 0.5\n        if order == \"NHWC\":\n            X = X.swapaxes(1, 2)\n\n        for input_to_check in [0, 1, 2]:  # dX, dScale, dBias\n            self.assertGradientChecks(gc, op, [X, scale, bias, mean, var],\n                                      input_to_check, [0], stepsize=0.01)\n\n    @given(size=st.integers(7, 10),\n           input_channels=st.integers(1, 10),\n           batch_size=st.integers(1, 3),\n           seed=st.integers(0, 65535),\n           epsilon=st.floats(1e-5, 1e-2),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs)\n    def test_spatialbn_brew_wrapper(\n            self, size, input_channels, batch_size, seed, epsilon,\n            engine, gc, dc):\n        np.random.seed(seed)\n        X = np.random.rand(\n            batch_size, input_channels, size, size).astype(np.float32)\n\n        workspace.FeedBlob('X', X)\n\n        model = ModelHelper(name='test_spatialbn_brew_wrapper')\n\n        brew.spatial_bn(\n            model,\n            'X',\n            'Y',\n            input_channels,\n            epsilon=epsilon,\n            is_test=False,\n        )\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/specialized_segment_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core\nfrom hypothesis import given\n\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\nimport unittest\n\n\nclass TestSpecializedSegmentOps(hu.HypothesisTestCase):\n\n    @given(batchsize=st.integers(1, 20),\n           fptype=st.sampled_from([np.float16, np.float32]),\n           fp16asint=st.booleans(),\n           blocksize=st.sampled_from([8, 17, 32, 64, 85, 96, 128, 163]),\n           normalize_by_lengths=st.booleans(), **hu.gcs)\n    def test_sparse_lengths_sum_cpu(\n            self, batchsize, fptype, fp16asint, blocksize, normalize_by_lengths, gc, dc):\n\n        if normalize_by_lengths == False:\n            print(\"<test_sparse_lengths_sum_cpu>\")\n        else:\n            print(\"<test_sparse_lengths_sum_mean_cpu>\")\n\n        tblsize = 300\n        if fptype == np.float32:\n            Tbl = np.random.rand(tblsize, blocksize).astype(np.float32)\n            atol = 1e-5\n        else:\n            if fp16asint:\n                Tbl = (10.0 * np.random.rand(tblsize, blocksize)\n                       ).round().astype(np.float16)\n                atol = 1e-3\n            else:\n                Tbl = np.random.rand(tblsize, blocksize).astype(np.float16)\n                atol = 1e-1\n\n        # array of each row length\n        Lengths = np.random.randint(1, 30, size=batchsize).astype(np.int32)\n        # flat indices\n        Indices = np.random.randint(\n            0, tblsize, size=sum(Lengths)).astype(np.int64)\n\n        if normalize_by_lengths == False:\n            op = core.CreateOperator(\"SparseLengthsSum\", [\n                                     \"Tbl\", \"Indices\", \"Lengths\"], \"out\")\n        else:\n            op = core.CreateOperator(\"SparseLengthsMean\", [\n                                     \"Tbl\", \"Indices\", \"Lengths\"], \"out\")\n\n        self.ws.create_blob(\"Tbl\").feed(Tbl)\n        self.ws.create_blob(\"Indices\").feed(Indices)\n        self.ws.create_blob(\"Lengths\").feed(Lengths)\n        self.ws.run(op)\n\n        def sparse_lengths_sum_ref(Tbl, Indices, Lengths):\n            rptr = np.cumsum(np.insert(Lengths, [0], [0]))\n            out = np.zeros((len(Lengths), blocksize))\n            if normalize_by_lengths == False:\n                for i in range(0, len(rptr[0:-1])):\n                    out[i] = Tbl[Indices[rptr[i]:rptr[i + 1]]].sum(axis=0)\n            else:\n                for i in range(0, len(rptr[0:-1])):\n                    out[i] = Tbl[Indices[rptr[i]:rptr[i + 1]]\n                                 ].sum(axis=0) * 1.0 / float(Lengths[i])\n\n            return out\n\n        np.testing.assert_allclose(self.ws.blobs[(\"out\")].fetch(),\n                                   sparse_lengths_sum_ref(Tbl, Indices, Lengths), rtol=1e-3, atol=atol)\n\n    @given(batchsize=st.integers(1, 20),\n           fptype=st.sampled_from([np.float16, np.float32]),\n           fp16asint=st.booleans(),\n           blocksize=st.sampled_from([8, 17, 32, 64, 85, 96, 128, 163]),\n           **hu.gcs)\n    def test_sparse_lengths_weightedsum_cpu(\n            self, batchsize, fptype, fp16asint, blocksize, gc, dc):\n\n        print(\"<test_sparse_lengths_weightedsum_cpu>\")\n\n        tblsize = 300\n        if fptype == np.float32:\n            Tbl = np.random.rand(tblsize, blocksize).astype(np.float32)\n            atol = 1e-5\n        else:\n            if fp16asint:\n                Tbl = (10.0 * np.random.rand(tblsize, blocksize)\n                       ).round().astype(np.float16)\n                atol = 1e-3\n            else:\n                Tbl = np.random.rand(tblsize, blocksize).astype(np.float16)\n                atol = 1e-1\n\n        # array of each row length\n        Lengths = np.random.randint(1, 30, size=batchsize).astype(np.int32)\n        # flat indices\n        Indices = np.random.randint(\n            0, tblsize, size=sum(Lengths)).astype(np.int64)\n        Weights = np.random.rand(sum(Lengths)).astype(np.float32)\n\n        op = core.CreateOperator(\"SparseLengthsWeightedSum\", [\n                                 \"Tbl\", \"Weights\", \"Indices\", \"Lengths\"], \"out\")\n\n        self.ws.create_blob(\"Tbl\").feed(Tbl)\n        self.ws.create_blob(\"Indices\").feed(Indices)\n        self.ws.create_blob(\"Lengths\").feed(Lengths)\n        self.ws.create_blob(\"Weights\").feed(Weights)\n        self.ws.run(op)\n\n        def sparse_lengths_weightedsum_ref(Tbl, Weights, Indices, Lengths):\n            rptr = np.cumsum(np.insert(Lengths, [0], [0]))\n            out = np.zeros((len(Lengths), blocksize))\n            for i in range(0, len(rptr[0:-1])):\n                w = Weights[rptr[i]:rptr[i + 1]]\n                out[i] = (Tbl[Indices[rptr[i]:rptr[i + 1]]]\n                          * w[:, np.newaxis]).sum(axis=0)\n            return out\n\n        np.testing.assert_allclose(self.ws.blobs[(\"out\")].fetch(),\n                                   sparse_lengths_weightedsum_ref(Tbl, Weights, Indices, Lengths), rtol=1e-3, atol=atol)\n\n\n    @given(batchsize=st.integers(1, 20),\n           blocksize=st.sampled_from([8, 16, 17, 26, 32, 64, 85, 96, 128, 148, 163]),\n           normalize_by_lengths=st.booleans(), **hu.gcs)\n    def test_sparse_lengths_weightedsum_8BitsRowwiseOp_cpu(\n            self, batchsize, blocksize, normalize_by_lengths, gc, dc):\n\n        if normalize_by_lengths == False:\n            print(\"<test_sparse_lengths_weightedsum_SparseLengthsWeightedSum8BitsRowwise_cpu>\")\n        else:\n            print(\"<test_sparse_lengths_weightedsum_SparseLengthsWeightedMean8BitsRowwise_cpu>\")\n\n        tblsize = 300\n        Tbl = np.random.randint(7, size = (tblsize, blocksize)).astype(np.uint8)\n        atol = 1e-5\n\n        # array of each row length\n        Lengths = np.random.randint(1, 30, size=batchsize).astype(np.int32)\n        # flat indices\n        Indices = np.random.randint(\n            0, tblsize, size=sum(Lengths)).astype(np.int64)\n        Weights = np.random.rand(sum(Lengths)).astype(np.float32)\n        Scale_Bias = np.random.rand(tblsize, 2).astype(np.float32)\n\n        if normalize_by_lengths == False:\n            op = core.CreateOperator(\"SparseLengthsWeightedSum8BitsRowwise\", [\n                                     \"Tbl\", \"Weights\", \"Indices\", \"Lengths\", \"Scale_Bias\"], \"out\")\n        else:\n            op = core.CreateOperator(\"SparseLengthsWeightedMean8BitsRowwise\", [\n                                     \"Tbl\", \"Weights\", \"Indices\", \"Lengths\", \"Scale_Bias\"], \"out\")\n\n        self.ws.create_blob(\"Tbl\").feed(Tbl)\n        self.ws.create_blob(\"Weights\").feed(Weights)\n        self.ws.create_blob(\"Indices\").feed(Indices)\n        self.ws.create_blob(\"Lengths\").feed(Lengths)\n        self.ws.create_blob(\"Scale_Bias\").feed(Scale_Bias)\n        self.ws.run(op)\n\n        def sparse_lengths_weightedsum_8BitsRowwiseOp_cpu_ref(Tbl, Weights, Indices, Lengths, Scale_Bias):\n            rptr = np.cumsum(np.insert(Lengths, [0], [0]))\n            out = np.zeros((len(Lengths), blocksize))\n            for i in range(0, len(rptr[0:-1])):\n                w = Weights[rptr[i]:rptr[i + 1]]\n                s = Scale_Bias[Indices[rptr[i]:rptr[i + 1]], 0][:, np.newaxis]\n                b = Scale_Bias[Indices[rptr[i]:rptr[i + 1]], 1][:, np.newaxis]\n                f = 1.0\n                if normalize_by_lengths == True:\n                    f = 1.0 / float(Lengths[i])\n                out[i] = (w[:, np.newaxis] *\n                          (s * Tbl[Indices[rptr[i]:rptr[i + 1]]] + b)).sum(axis=0) * f\n            return out\n\n        np.testing.assert_allclose(self.ws.blobs[(\"out\")].fetch(),\n                                   sparse_lengths_weightedsum_8BitsRowwiseOp_cpu_ref(Tbl, Weights, Indices, Lengths, Scale_Bias),\n                                   rtol=1e-3, atol=atol)\n\n\n\n    @given(batchsize=st.integers(1, 20),\n           blocksize=st.sampled_from([8, 16, 17, 26, 32, 64, 85, 96, 128, 148, 163]),\n           normalize_by_lengths=st.booleans(), **hu.gcs)\n    def test_sparse_lengths_sum_8BitsRowwiseOp_cpu(\n            self, batchsize, blocksize, normalize_by_lengths, gc, dc):\n\n        if normalize_by_lengths == False:\n            print(\"<test_sparse_lengths_sum_SparseLengthsSum8BitsRowwise_cpu>\")\n        else:\n            print(\"<test_sparse_lengths_sum_SparseLengthsMean8BitsRowwise_cpu>\")\n\n        tblsize = 300\n        Tbl = np.random.randint(7, size = (tblsize, blocksize)).astype(np.uint8)\n        atol = 1e-5\n\n        # array of each row length\n        Lengths = np.random.randint(1, 30, size=batchsize).astype(np.int32)\n        # flat indices\n        Indices = np.random.randint(\n            0, tblsize, size=sum(Lengths)).astype(np.int64)\n        Scale_Bias = np.random.rand(tblsize, 2).astype(np.float32)\n\n        if normalize_by_lengths == False:\n            op = core.CreateOperator(\"SparseLengthsSum8BitsRowwise\", [\n                                     \"Tbl\", \"Indices\", \"Lengths\", \"Scale_Bias\"], \"out\")\n        else:\n            op = core.CreateOperator(\"SparseLengthsMean8BitsRowwise\", [\n                                     \"Tbl\", \"Indices\", \"Lengths\", \"Scale_Bias\"], \"out\")\n\n        self.ws.create_blob(\"Tbl\").feed(Tbl)\n        self.ws.create_blob(\"Indices\").feed(Indices)\n        self.ws.create_blob(\"Lengths\").feed(Lengths)\n        self.ws.create_blob(\"Scale_Bias\").feed(Scale_Bias)\n        self.ws.run(op)\n\n        def sparse_lengths_sum_8BitsRowwiseOp_cpu_reg(Tbl, Indices, Lengths, Scale_Bias):\n            rptr = np.cumsum(np.insert(Lengths, [0], [0]))\n            out = np.zeros((len(Lengths), blocksize))\n            for i in range(0, len(rptr[0:-1])):\n                s = Scale_Bias[Indices[rptr[i]:rptr[i + 1]], 0][:, np.newaxis]\n                b = Scale_Bias[Indices[rptr[i]:rptr[i + 1]], 1][:, np.newaxis]\n                f = 1.0\n                if normalize_by_lengths == True:\n                    f = 1.0 / float(Lengths[i])\n                out[i] = (s * Tbl[Indices[rptr[i]:rptr[i + 1]]] + b).sum(axis=0) * f\n            return out\n\n        np.testing.assert_allclose(self.ws.blobs[(\"out\")].fetch(),\n                                   sparse_lengths_sum_8BitsRowwiseOp_cpu_reg(Tbl, Indices, Lengths, Scale_Bias),\n                                   rtol=1e-3, atol=atol)\n\n\n    @given(batchsize=st.integers(1, 20),\n           blocksize=st.sampled_from([8, 16, 17, 26, 32, 64, 85, 96, 128, 148, 163]),\n           normalize_by_lengths=st.booleans(), **hu.gcs)\n    def test_sparse_lengths_sum_8BitsRowwiseOp_cpu_invalid_index(\n            self, batchsize, blocksize, normalize_by_lengths, gc, dc):\n\n        tblsize = 300\n        Tbl = np.random.randint(7, size = (tblsize, blocksize)).astype(np.uint8)\n\n        # array of each row length\n        Lengths = np.random.randint(1, 30, size=batchsize).astype(np.int32)\n        # flat indices\n        Indices = np.random.randint(\n            0, tblsize, size=sum(Lengths)).astype(np.int64)\n        Indices[0] += 1000\n        Scale_Bias = np.random.rand(tblsize, 2).astype(np.float32)\n\n        if normalize_by_lengths == False:\n            op = core.CreateOperator(\"SparseLengthsSum8BitsRowwise\", [\n                                     \"Tbl\", \"Indices\", \"Lengths\", \"Scale_Bias\"], \"out\")\n        else:\n            op = core.CreateOperator(\"SparseLengthsMean8BitsRowwise\", [\n                                     \"Tbl\", \"Indices\", \"Lengths\", \"Scale_Bias\"], \"out\")\n\n        self.ws.create_blob(\"Tbl\").feed(Tbl)\n        self.ws.create_blob(\"Indices\").feed(Indices)\n        self.ws.create_blob(\"Lengths\").feed(Lengths)\n        self.ws.create_blob(\"Scale_Bias\").feed(Scale_Bias)\n        with self.assertRaises(RuntimeError):\n            self.ws.run(op)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/square_root_divide_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom functools import partial\nfrom hypothesis import given\nfrom hypothesis import strategies as st\n\nimport caffe2.python.hypothesis_test_util as hu\nimport math\nimport numpy as np\n\n\ndef _data_and_scale(\n        data_min_size=4, data_max_size=10,\n        examples_min_number=1, examples_max_number=4,\n        dtype=np.float32, elements=None):\n    params_ = st.tuples(\n        st.integers(min_value=examples_min_number,\n                    max_value=examples_max_number),\n        st.integers(min_value=data_min_size,\n                    max_value=data_max_size),\n        st.sampled_from([np.float32, np.int32, np.int64])\n    )\n    return params_.flatmap(\n        lambda param_: st.tuples(\n            hu.arrays([param_[0], param_[1]], dtype=dtype),\n            hu.arrays(\n                [param_[0]], dtype=param_[2],\n                elements=(st.floats(0.0, 10000.0) if param_[2] in [np.float32]\n                          else st.integers(0, 10000)),\n            ),\n        )\n    )\n\n\ndef divide_by_square_root(data, scale):\n    output = np.copy(data)\n    num_examples = len(scale)\n\n    assert num_examples == data.shape[0]\n    assert len(data.shape) == 2\n\n    for i in range(0, num_examples):\n        if scale[i] > 0:\n            output[i] = np.multiply(data[i], 1 / math.sqrt(scale[i]))\n\n    return (output, )\n\n\ndef grad(output_grad, ref_outputs, inputs):\n    return (divide_by_square_root(output_grad, inputs[1])[0],\n            None)\n\n\nclass TestSquareRootDivide(hu.HypothesisTestCase):\n    @given(data_and_scale=_data_and_scale(),\n           **hu.gcs_cpu_only)\n    def test_square_root_divide(self, data_and_scale, gc, dc):\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=core.CreateOperator(\"SquareRootDivide\",\n                                   [\"data\", \"scale\"],\n                                   [\"output\"]),\n            inputs=list(data_and_scale),\n            reference=partial(divide_by_square_root),\n            output_to_grad=\"output\",\n            grad_reference=grad,\n        )\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/stats_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\nimport numpy as np\n\n\nclass TestCounterOps(TestCase):\n\n    def test_stats_ops(self):\n        # The global StatRegistry isn't reset when the workspace is reset,\n        #   so there may be existing stats from a previous test\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'StatRegistryExport', [], ['prev_k', 'prev_v', 'prev_ts']))\n        previous_keys = workspace.FetchBlob('prev_k')\n        existing = len(previous_keys)\n\n        prefix = '/'.join([__name__, 'TestCounterOps', 'test_stats_ops'])\n        keys = [\n            (prefix + '/key1').encode('ascii'),\n            (prefix + '/key2').encode('ascii')\n        ]\n        values = [34, 45]\n        workspace.FeedBlob('k', np.array(keys, dtype=str))\n        workspace.FeedBlob('v', np.array(values, dtype=np.int64))\n        for _ in range(2):\n            workspace.RunOperatorOnce(core.CreateOperator(\n                'StatRegistryUpdate', ['k', 'v'], []))\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'StatRegistryExport', [], ['k2', 'v2', 't2']))\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'StatRegistryCreate', [], ['reg']))\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'StatRegistryUpdate', ['k2', 'v2', 'reg'], []))\n\n        workspace.RunOperatorOnce(core.CreateOperator(\n            'StatRegistryExport', ['reg'], ['k3', 'v3', 't3']))\n\n        k3 = workspace.FetchBlob('k3')\n        v3 = workspace.FetchBlob('v3')\n        t3 = workspace.FetchBlob('t3')\n\n        self.assertEqual(len(k3) - existing, 2)\n        self.assertEqual(len(v3), len(k3))\n        self.assertEqual(len(t3), len(k3))\n        for key in keys:\n            self.assertIn(key, k3)\n"
  },
  {
    "path": "caffe2/python/operator_test/string_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\ndef _string_lists(alphabet=None):\n    return st.lists(\n        elements=st.text(alphabet=alphabet, average_size=3),\n        min_size=0,\n        max_size=3)\n\n\nclass TestStringOps(hu.HypothesisTestCase):\n    @given(strings=_string_lists())\n    def test_string_prefix(self, strings):\n        length = 3\n        # although we are utf-8 encoding below to avoid python exceptions,\n        # StringPrefix op deals with byte-length prefixes, which may produce\n        # an invalid utf-8 string. The goal here is just to avoid python\n        # complaining about the unicode -> str conversion.\n        strings = np.array(\n            [a.encode('utf-8') for a in strings], dtype=np.object\n        )\n\n        def string_prefix_ref(strings):\n            return (\n                np.array([a[:length] for a in strings], dtype=object),\n            )\n\n        op = core.CreateOperator(\n            'StringPrefix',\n            ['strings'],\n            ['stripped'],\n            length=length)\n        self.assertReferenceChecks(\n            hu.cpu_do,\n            op,\n            [strings],\n            string_prefix_ref)\n\n    @given(strings=_string_lists())\n    def test_string_suffix(self, strings):\n        length = 3\n        strings = np.array(\n            [a.encode('utf-8') for a in strings], dtype=np.object\n        )\n\n        def string_suffix_ref(strings):\n            return (\n                np.array([a[-length:] for a in strings], dtype=object),\n            )\n\n        op = core.CreateOperator(\n            'StringSuffix',\n            ['strings'],\n            ['stripped'],\n            length=length)\n        self.assertReferenceChecks(\n            hu.cpu_do,\n            op,\n            [strings],\n            string_suffix_ref)\n\n    @given(strings=st.text(alphabet=['a', 'b'], average_size=3))\n    def test_string_starts_with(self, strings):\n        prefix = 'a'\n        strings = np.array(\n            [str(a) for a in strings], dtype=np.object\n        )\n\n        def string_starts_with_ref(strings):\n            return (\n                np.array([a.startswith(prefix) for a in strings], dtype=bool),\n            )\n\n        op = core.CreateOperator(\n            'StringStartsWith',\n            ['strings'],\n            ['bools'],\n            prefix=prefix)\n        self.assertReferenceChecks(\n            hu.cpu_do,\n            op,\n            [strings],\n            string_starts_with_ref)\n\n    @given(strings=st.text(alphabet=['a', 'b'], average_size=3))\n    def test_string_ends_with(self, strings):\n        suffix = 'a'\n        strings = np.array(\n            [str(a) for a in strings], dtype=np.object\n        )\n\n        def string_ends_with_ref(strings):\n            return (\n                np.array([a.endswith(suffix) for a in strings], dtype=bool),\n            )\n\n        op = core.CreateOperator(\n            'StringEndsWith',\n            ['strings'],\n            ['bools'],\n            suffix=suffix)\n        self.assertReferenceChecks(\n            hu.cpu_do,\n            op,\n            [strings],\n            string_ends_with_ref)\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/text_file_reader_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nfrom caffe2.python.text_file_reader import TextFileReader\nfrom caffe2.python.test_util import TestCase\nfrom caffe2.python.schema import Struct, Scalar, FetchRecord\nimport tempfile\nimport numpy as np\n\n\nclass TestTextFileReader(TestCase):\n    def test_text_file_reader(self):\n        schema = Struct(\n            ('field1', Scalar(dtype=str)),\n            ('field2', Scalar(dtype=str)),\n            ('field3', Scalar(dtype=np.float32)))\n        num_fields = 3\n        col_data = [\n            ['l1f1', 'l2f1', 'l3f1', 'l4f1'],\n            ['l1f2', 'l2f2', 'l3f2', 'l4f2'],\n            [0.456, 0.789, 0.10101, -24342.64],\n        ]\n        row_data = list(zip(*col_data))\n        with tempfile.NamedTemporaryFile(mode='w+', delete=False) as txt_file:\n            txt_file.write(\n                '\\n'.join(\n                    '\\t'.join(str(x) for x in f)\n                    for f in row_data\n                ) + '\\n'\n            )\n            txt_file.flush()\n\n            for num_passes in range(1, 3):\n                for batch_size in range(1, len(row_data) + 2):\n                    init_net = core.Net('init_net')\n                    reader = TextFileReader(\n                        init_net,\n                        filename=txt_file.name,\n                        schema=schema,\n                        batch_size=batch_size,\n                        num_passes=num_passes)\n                    workspace.RunNetOnce(init_net)\n\n                    net = core.Net('read_net')\n                    should_stop, record = reader.read_record(net)\n\n                    results = [np.array([])] * num_fields\n                    while True:\n                        workspace.RunNetOnce(net)\n                        arrays = FetchRecord(record).field_blobs()\n                        for i in range(num_fields):\n                            results[i] = np.append(results[i], arrays[i])\n                        if workspace.FetchBlob(should_stop):\n                            break\n                    for i in range(num_fields):\n                        col_batch = np.tile(col_data[i], num_passes)\n                        if col_batch.dtype in (np.float32, np.float64):\n                            np.testing.assert_array_almost_equal(\n                                col_batch, results[i], decimal=3)\n                        else:\n                            np.testing.assert_array_equal(col_batch, results[i])\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/thresholded_relu_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport caffe2.python.hypothesis_test_util as hu\nimport numpy as np\n\nimport unittest\n\n\nclass TestThresholdedRelu(hu.HypothesisTestCase):\n\n    # test case 1 - default alpha - we do reference and dc checks.\n    # test case 2 does dc and reference checks over range of alphas.\n    # test case 3 does gc over range of alphas.\n    @given(input=hu.tensor(),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs)\n    def test_thresholded_relu_1(self, input, gc, dc, engine):\n        X = input\n        op = core.CreateOperator(\"ThresholdedRelu\", [\"X\"], [\"Y\"],\n                                 engine=engine)\n\n        def defaultRef(X):\n            Y = np.copy(X)\n            Y[Y <= 1.0] = 0.0\n            return (Y,)\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertReferenceChecks(gc, op, [X], defaultRef)\n\n    @given(input=hu.tensor(),\n           alpha=st.floats(min_value=1.0, max_value=5.0),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs)\n    def test_thresholded_relu_2(self, input, alpha, gc, dc, engine):\n        X = input\n        op = core.CreateOperator(\"ThresholdedRelu\", [\"X\"], [\"Y\"],\n                                 alpha=alpha, engine=engine)\n\n        def ref(X):\n            Y = np.copy(X)\n            Y[Y <= alpha] = 0.0\n            return (Y,)\n\n        self.assertDeviceChecks(dc, op, [X], [0])\n        self.assertReferenceChecks(gc, op, [X], ref)\n\n    @given(input=hu.tensor(),\n           alpha=st.floats(min_value=1.1, max_value=5.0),\n           engine=st.sampled_from([\"\", \"CUDNN\"]),\n           **hu.gcs)\n    def test_thresholded_relu_3(self, input, alpha, gc, dc, engine):\n        X = TestThresholdedRelu.fix_input(input)\n        op = core.CreateOperator(\"ThresholdedRelu\", [\"X\"], [\"Y\"],\n                                 alpha=float(alpha), engine=engine)\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @staticmethod\n    def fix_input(input):\n        # go away from alpha to avoid derivative discontinuities\n        input += 0.02 * np.sign(input)\n        return input\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/tile_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport unittest\n\nfrom caffe2.python import core, workspace\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestTile(hu.HypothesisTestCase):\n    @given(M=st.integers(min_value=1, max_value=10),\n           K=st.integers(min_value=1, max_value=10),\n           N=st.integers(min_value=1, max_value=10),\n           tiles=st.integers(min_value=1, max_value=3),\n           axis=st.integers(min_value=0, max_value=2),\n           **hu.gcs)\n    def test_tile(self, M, K, N, tiles, axis, gc, dc):\n        X = np.random.rand(M, K, N).astype(np.float32)\n\n        op = core.CreateOperator(\n            'Tile', ['X'], 'out',\n            tiles=tiles,\n            axis=axis,\n        )\n\n        def tile_ref(X, tiles, axis):\n            dims = np.asarray([1, 1, 1], dtype=np.int)\n            dims[axis] = tiles\n            tiled_data = np.tile(X, dims)\n            return (tiled_data,)\n\n        # Check against numpy reference\n        self.assertReferenceChecks(gc, op, [X, tiles, axis],\n                                   tile_ref)\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X], [0])\n        # Gradient check wrt X\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    @given(M=st.integers(min_value=1, max_value=200),\n           N=st.integers(min_value=1, max_value=200),\n           tiles=st.integers(min_value=50, max_value=100),\n           **hu.gcs)\n    def test_tile_grad(self, M, N, tiles, gc, dc):\n        X = np.random.rand(M, N).astype(np.float32)\n        axis = 1\n\n        op = core.CreateOperator(\n            'Tile', ['X'], 'out',\n            tiles=tiles,\n            axis=axis,\n        )\n\n        def tile_ref(X, tiles, axis):\n            dims = np.asarray([1, 1], dtype=np.int)\n            dims[axis] = tiles\n            tiled_data = np.tile(X, dims)\n            return (tiled_data,)\n\n        # Check against numpy reference\n        self.assertReferenceChecks(gc, op, [X, tiles, axis],\n                                   tile_ref)\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X], [0])\n\n        # Gradient check wrt X\n        grad_op = core.CreateOperator(\n            'TileGradient', ['dOut'], 'dX',\n            tiles=tiles,\n            axis=axis,\n        )\n        dX = np.random.rand(M, N * tiles).astype(np.float32)\n        self.assertDeviceChecks(dc, grad_op, [dX], [0])\n\n    @given(M=st.integers(min_value=1, max_value=10),\n           K=st.integers(min_value=1, max_value=10),\n           N=st.integers(min_value=1, max_value=10),\n           tiles=st.integers(min_value=1, max_value=3),\n           axis=st.integers(min_value=0, max_value=2),\n           **hu.gcs)\n    def test_tilewinput(self, M, K, N, tiles, axis, gc, dc):\n        X = np.random.rand(M, K, N).astype(np.float32)\n\n        tiles_arg = np.array([tiles], dtype=np.int32)\n        axis_arg = np.array([axis], dtype=np.int32)\n\n        op = core.CreateOperator(\n            'Tile', ['X', 'tiles', 'axis'], 'out',\n        )\n\n        def tile_ref(X, tiles, axis):\n            dims = np.asarray([1, 1, 1], dtype=np.int)\n            dims[axis] = tiles\n            tiled_data = np.tile(X, dims)\n            return (tiled_data,)\n\n        # Check against numpy reference\n        self.assertReferenceChecks(gc, op, [X, tiles_arg, axis_arg],\n                                   tile_ref)\n        # Check over multiple devices\n        self.assertDeviceChecks(dc, op, [X, tiles_arg, axis_arg], [0])\n        # Gradient check wrt X\n        self.assertGradientChecks(gc, op, [X, tiles_arg, axis_arg], 0, [0])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/top_k_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom collections import OrderedDict\nimport hypothesis.strategies as st\nimport numpy as np\nimport random\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestTopK(hu.HypothesisTestCase):\n\n    def top_k_ref(self, X, k, flatten_indices):\n        X_flat = X.reshape((-1, X.shape[-1]))\n        indices_ref = np.ndarray(shape=X_flat.shape, dtype=np.int32)\n        flatten_indices_ref = np.ndarray(shape=X_flat.shape, dtype=np.int32)\n        values_ref = np.ndarray(shape=X_flat.shape, dtype=np.float32)\n        global_idx = 0\n        for i in range(X_flat.shape[0]):\n            od = OrderedDict()\n            for j in range(X_flat.shape[1]):\n                val = X_flat[i, j]\n                if val not in od:\n                    od[val] = []\n                od[val].append((j, global_idx))\n                global_idx += 1\n            j = 0\n            for val, idxs in sorted(od.items(), reverse=True):\n                for (idx, flatten_idx) in idxs:\n                    indices_ref[i, j] = idx\n                    flatten_indices_ref[i, j] = flatten_idx\n                    values_ref[i, j] = val\n                    j = j + 1\n\n        indices_ref = np.reshape(indices_ref, X.shape)\n        flatten_indices_ref = np.reshape(flatten_indices_ref, X.shape)\n        values_ref = np.reshape(values_ref, X.shape)\n\n        indices_ref = indices_ref.take(list(range(k)), axis=-1)\n        flatten_indices_ref = flatten_indices_ref.take(list(range(k)), axis=-1)\\\n                .flatten()\n        values_ref = values_ref.take(list(range(k)), axis=-1)\n\n        if flatten_indices:\n            return (values_ref, indices_ref, flatten_indices_ref)\n        else:\n            return (values_ref, indices_ref)\n\n    @given(X=hu.tensor(), flatten_indices=st.booleans(), **hu.gcs)\n    def test_top_k(self, X, flatten_indices, gc, dc):\n        X = X.astype(dtype=np.float32)\n        k = random.randint(1, X.shape[-1])\n        output_list = [\"Values\", \"Indices\"]\n        if flatten_indices:\n            output_list.append(\"FlattenIndices\")\n        op = core.CreateOperator(\"TopK\", [\"X\"], output_list,\n                                 k=k, device_option=gc)\n\n        def bind_ref(X_loc):\n            return self.top_k_ref(X_loc, k, flatten_indices)\n\n        self.assertReferenceChecks(gc, op, [X], bind_ref)\n\n    @given(bs=st.integers(1, 3), n=st.integers(1, 1), k=st.integers(1, 1),\n            flatten_indices=st.booleans(), **hu.gcs)\n    def test_top_k_1(self, bs, n, k, flatten_indices, gc, dc):\n        X = np.random.rand(bs, n).astype(dtype=np.float32)\n        output_list = [\"Values\", \"Indices\"]\n        if flatten_indices:\n            output_list.append(\"FlattenIndices\")\n        op = core.CreateOperator(\"TopK\", [\"X\"], output_list,\n                                 k=k, device_option=gc)\n\n        def bind_ref(X_loc):\n            return self.top_k_ref(X_loc, k, flatten_indices)\n\n        self.assertReferenceChecks(gc, op, [X], bind_ref)\n\n    @given(bs=st.integers(1, 3), n=st.integers(1, 10000), k=st.integers(1, 1),\n            flatten_indices=st.booleans(), **hu.gcs)\n    def test_top_k_2(self, bs, n, k, flatten_indices, gc, dc):\n        X = np.random.rand(bs, n).astype(dtype=np.float32)\n\n        output_list = [\"Values\", \"Indices\"]\n        if flatten_indices:\n            output_list.append(\"FlattenIndices\")\n        op = core.CreateOperator(\"TopK\", [\"X\"], output_list,\n                                 k=k, device_option=gc)\n\n        def bind_ref(X_loc):\n            return self.top_k_ref(X_loc, k, flatten_indices)\n\n        self.assertReferenceChecks(gc, op, [X], bind_ref)\n\n    @given(bs=st.integers(1, 3), n=st.integers(1, 10000),\n            k=st.integers(1, 1024), flatten_indices=st.booleans(), **hu.gcs)\n    def test_top_k_3(self, bs, n, k, flatten_indices, gc, dc):\n        X = np.random.rand(bs, n).astype(dtype=np.float32)\n        k = min(k, n)\n\n        output_list = [\"Values\", \"Indices\"]\n        if flatten_indices:\n            output_list.append(\"FlattenIndices\")\n        op = core.CreateOperator(\"TopK\", [\"X\"], output_list,\n                                 k=k, device_option=gc)\n\n        def bind_ref(X_loc):\n            return self.top_k_ref(X_loc, k, flatten_indices)\n\n        self.assertReferenceChecks(gc, op, [X], bind_ref)\n\n    @given(bs=st.integers(1, 3), n=st.integers(100, 10000),\n            flatten_indices=st.booleans(), **hu.gcs)\n    def test_top_k_4(self, bs, n, flatten_indices, gc, dc):\n        k = np.random.randint(n // 3, 3 * n // 4)\n        X = np.random.rand(bs, n).astype(dtype=np.float32)\n\n        output_list = [\"Values\", \"Indices\"]\n        if flatten_indices:\n            output_list.append(\"FlattenIndices\")\n        op = core.CreateOperator(\"TopK\", [\"X\"], output_list,\n                                 k=k, device_option=gc)\n\n        def bind_ref(X_loc):\n            return self.top_k_ref(X_loc, k, flatten_indices)\n\n        self.assertReferenceChecks(gc, op, [X], bind_ref)\n\n    @given(bs=st.integers(1, 3), n=st.integers(1, 1024),\n            flatten_indices=st.booleans(), **hu.gcs)\n    def test_top_k_5(self, bs, n, flatten_indices, gc, dc):\n        k = n\n        X = np.random.rand(bs, n).astype(dtype=np.float32)\n\n        output_list = [\"Values\", \"Indices\"]\n        if flatten_indices:\n            output_list.append(\"FlattenIndices\")\n        op = core.CreateOperator(\"TopK\", [\"X\"], output_list,\n                                 k=k, device_option=gc)\n\n        def bind_ref(X_loc):\n            return self.top_k_ref(X_loc, k, flatten_indices)\n\n        self.assertReferenceChecks(gc, op, [X], bind_ref)\n\n    @given(bs=st.integers(1, 3), n=st.integers(1, 5000),\n            flatten_indices=st.booleans(), **hu.gcs)\n    def test_top_k_6(self, bs, n, flatten_indices, gc, dc):\n        k = n\n        X = np.random.rand(bs, n).astype(dtype=np.float32)\n\n        output_list = [\"Values\", \"Indices\"]\n        if flatten_indices:\n            output_list.append(\"FlattenIndices\")\n        op = core.CreateOperator(\"TopK\", [\"X\"], output_list,\n                                 k=k, device_option=gc)\n\n        def bind_ref(X_loc):\n            return self.top_k_ref(X_loc, k, flatten_indices)\n\n        self.assertReferenceChecks(gc, op, [X], bind_ref)\n\n    @given(X=hu.tensor(min_dim=2), **hu.gcs)\n    def test_top_k_grad(self, X, gc, dc):\n        X = X.astype(np.float32)\n        k = random.randint(1, X.shape[-1])\n\n        # this try to make sure adding stepsize (0.05)\n        # will not change TopK selections at all\n        # since dims max_value = 5 as defined in\n        # caffe2/caffe2/python/hypothesis_test_util.py\n        for i in range(X.shape[-1]):\n            X[..., i] = i * 1.0 / X.shape[-1]\n\n        op = core.CreateOperator(\n            \"TopK\", [\"X\"], [\"Values\", \"Indices\"], k=k, device_option=gc\n        )\n\n        self.assertGradientChecks(gc, op, [X], 0, [0])\n"
  },
  {
    "path": "caffe2/python/operator_test/unique_uniform_fill_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\nimport unittest\n\n\nclass TestUniqueUniformFillOp(hu.HypothesisTestCase):\n    @given(\n        r=st.integers(1000, 10000),\n        avoid=st.lists(\n            st.integers(1, 1000),\n            min_size=1,\n            max_size=100,\n            unique=True\n        ),\n        dtypes=st.sampled_from(\n            [\n                (np.int32, core.DataType.INT32),\n                (np.int64, core.DataType.INT64)\n            ]\n        ),\n        s=st.integers(10, 500),\n        **hu.gcs_cpu_only\n    )\n    def test_unique_uniform_int_fill(self, r, avoid, dtypes, s, gc, dc):\n        net = core.Net(\"net\")\n        workspace.FeedBlob(\"X\", np.array([s], dtype=np.int64))\n        workspace.FeedBlob(\"AVOID\", np.array(avoid, dtype=dtypes[0]))\n        net.UniqueUniformFill(\n            [\"X\", \"AVOID\"], [\"Y\"],\n            min=1,\n            max=r,\n            input_as_shape=True,\n            dtype=dtypes[1]\n        )\n        workspace.RunNetOnce(net)\n        y = workspace.FetchBlob(\"Y\")\n        self.assertEqual(s, len(y))\n        self.assertEqual(s, len(set(y)))\n        self.assertEqual(s, len(set(y) - set(avoid)))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/utility_ops_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nfrom hypothesis import assume, given\nfrom caffe2.proto import caffe2_pb2\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\nimport random\nimport unittest\n\nclass TestUtilityOps(hu.HypothesisTestCase):\n\n    @given(X=hu.tensor(), args=st.booleans(), **hu.gcs)\n    def test_slice(self, X, args, gc, dc):\n        X = X.astype(dtype=np.float32)\n        dim = random.randint(0, X.ndim - 1)\n        slice_start = random.randint(0, X.shape[dim] - 1)\n        slice_end = random.randint(slice_start, X.shape[dim] - 1)\n        starts = np.array([0] * X.ndim).astype(np.int32)\n        ends = np.array([-1] * X.ndim).astype(np.int32)\n        starts[dim] = slice_start\n        ends[dim] = slice_end\n\n        if args:\n            op = core.CreateOperator(\n                \"Slice\", [\"X\"], [\"Y\"], starts=starts, ends=ends, device_option=gc\n            )\n\n            def slice_ref(X):\n                slc = [slice(None)] * X.ndim\n                slc[dim] = slice(slice_start, slice_end)\n                return [X[slc]]\n            inputs = [X]\n        else:\n            op = core.CreateOperator(\n                \"Slice\", [\"X\", \"starts\", \"ends\"], [\"Y\"], device_option=gc\n            )\n\n            def slice_ref(x, starts, ends):\n                slc = [slice(None)] * x.ndim\n                slc[dim] = slice(slice_start, slice_end)\n                return [x[slc]]\n            inputs = [X, starts, ends]\n\n        self.assertReferenceChecks(gc, op, inputs, slice_ref)\n        self.assertDeviceChecks(dc, op, inputs, [0])\n        self.assertGradientChecks(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            outputs_to_check=0,\n            outputs_with_grads=[0],\n        )\n\n    @given(dtype=st.sampled_from([np.float32, np.int32]),\n           ndims=st.integers(min_value=1, max_value=5),\n           seed=st.integers(min_value=0, max_value=65536),\n           null_axes=st.booleans(),\n           engine=st.sampled_from(['CUDNN', None]),\n           **hu.gcs)\n    def test_transpose(self, dtype, ndims, seed, null_axes, engine, gc, dc):\n        if (gc.device_type == caffe2_pb2.CUDA and engine == \"CUDNN\"):\n            # cudnn 5.1 does not support int.\n            assume(workspace.GetCuDNNVersion() >= 6000 or dtype != np.int32) \n\n        dims = (np.random.rand(ndims) * 16 + 1).astype(np.int32)\n        X = (np.random.rand(*dims) * 16).astype(dtype)\n\n        if null_axes:\n            axes = None\n            op = core.CreateOperator(\n                \"Transpose\",\n                [\"input\"], [\"output\"],\n                engine=engine)\n        else:\n            np.random.seed(int(seed))\n            axes = [int(v) for v in list(np.random.permutation(X.ndim))]\n            op = core.CreateOperator(\n                \"Transpose\",\n                [\"input\"], [\"output\"],\n                axes=axes,\n                engine=engine)\n\n        def transpose_ref(x, axes):\n            return (np.transpose(x, axes),)\n\n        self.assertReferenceChecks(gc, op, [X, axes],\n                                   transpose_ref)\n\n\n    @given(m=st.integers(5, 10), n=st.integers(5, 10),\n           o=st.integers(5, 10), nans=st.booleans(), **hu.gcs)\n    def test_nan_check(self, m, n, o, nans, gc, dc):\n        other = np.array([1, 2, 3]).astype(np.float32)\n        X = np.random.rand(m, n, o).astype(np.float32)\n        if nans:\n            x_nan = np.random.randint(0, m)\n            y_nan = np.random.randint(0, n)\n            z_nan = np.random.randint(0, o)\n            X[x_nan, y_nan, z_nan] = float('NaN')\n\n        # print('nans: {}'.format(nans))\n        # print(X)\n\n        def nan_reference(X, Y):\n            if not np.isnan(X).any():\n                return [X]\n            else:\n                return [np.array([])]\n\n        op = core.CreateOperator(\n            \"NanCheck\",\n            [\"X\", \"other\"],\n            [\"Y\"]\n        )\n\n        try:\n            self.assertReferenceChecks(\n                device_option=gc,\n                op=op,\n                inputs=[X, other],\n                reference=nan_reference,\n            )\n            if nans:\n                self.assertTrue(False, \"Did not fail when presented with NaN!\")\n        except RuntimeError:\n            self.assertTrue(nans, \"No NaNs but failed\")\n\n        try:\n            self.assertGradientChecks(\n                device_option=gc,\n                op=op,\n                inputs=[X],\n                outputs_to_check=0,\n                outputs_with_grads=[0],\n            )\n            if nans:\n                self.assertTrue(False, \"Did not fail when gradient had NaN!\")\n        except RuntimeError:\n            pass\n\n    @given(n=st.integers(4, 5), m=st.integers(6, 7),\n           d=st.integers(2, 3), **hu.gcs)\n    def test_elementwise_max(self, n, m, d, gc, dc):\n        X = np.random.rand(n, m, d).astype(np.float32)\n        Y = np.random.rand(n, m, d).astype(np.float32)\n        Z = np.random.rand(n, m, d).astype(np.float32)\n        inputs = [X, Y, Z]\n\n        def max_op(X, Y, Z):\n            return [np.maximum(np.maximum(X, Y), Z)]\n\n        op = core.CreateOperator(\n            \"Max\",\n            [\"X\", \"Y\", \"Z\"],\n            [\"mx\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            reference=max_op,\n        )\n        self.assertDeviceChecks(dc, op, inputs, [0])\n\n    @given(n=st.integers(4, 5), m=st.integers(6, 7),\n           d=st.integers(2, 3), **hu.gcs)\n    def test_elementwise_max_grad(self, n, m, d, gc, dc):\n        go = np.random.rand(n, m, d).astype(np.float32)\n        X = np.random.rand(n, m, d).astype(np.float32)\n        Y = np.random.rand(n, m, d).astype(np.float32)\n        Z = np.random.rand(n, m, d).astype(np.float32)\n        mx = np.maximum(np.maximum(X, Y), Z)\n        inputs = [mx, go, X, Y, Z]\n\n        def max_grad_op(mx, go, X, Y, Z):\n            def mx_grad(a):\n                return go * (mx == a)\n\n            return [mx_grad(a) for a in [X, Y, Z]]\n\n        op = core.CreateOperator(\n            \"MaxGradient\",\n            [\"mx\", \"go\", \"X\", \"Y\", \"Z\"],\n            [\"gX\", \"gY\", \"gZ\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            reference=max_grad_op,\n        )\n        self.assertDeviceChecks(dc, op, inputs, [0, 1, 2])\n\n    @given(n=st.integers(4, 5), m=st.integers(6, 7),\n           d=st.integers(2, 3), **hu.gcs)\n    def test_elementwise_min(self, n, m, d, gc, dc):\n        X = np.random.rand(n, m, d).astype(np.float32)\n        Y = np.random.rand(n, m, d).astype(np.float32)\n        Z = np.random.rand(n, m, d).astype(np.float32)\n        inputs = [X, Y, Z]\n\n        def min_op(X, Y, Z):\n            return [np.minimum(np.minimum(X, Y), Z)]\n\n        op = core.CreateOperator(\n            \"Min\",\n            [\"X\", \"Y\", \"Z\"],\n            [\"mx\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            reference=min_op,\n        )\n        self.assertDeviceChecks(dc, op, inputs, [0])\n\n    @given(n=st.integers(4, 5), m=st.integers(6, 7),\n           d=st.integers(2, 3), **hu.gcs)\n    def test_elementwise_min_grad(self, n, m, d, gc, dc):\n        go = np.random.rand(n, m, d).astype(np.float32)\n        X = np.random.rand(n, m, d).astype(np.float32)\n        Y = np.random.rand(n, m, d).astype(np.float32)\n        Z = np.random.rand(n, m, d).astype(np.float32)\n        mx = np.minimum(np.minimum(X, Y), Z)\n        inputs = [mx, go, X, Y, Z]\n\n        def min_grad_op(mx, go, X, Y, Z):\n            def mx_grad(a):\n                return go * (mx == a)\n\n            return [mx_grad(a) for a in [X, Y, Z]]\n\n        op = core.CreateOperator(\n            \"MinGradient\",\n            [\"mx\", \"go\", \"X\", \"Y\", \"Z\"],\n            [\"gX\", \"gY\", \"gZ\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=inputs,\n            reference=min_grad_op,\n        )\n        self.assertDeviceChecks(dc, op, inputs, [0, 1, 2])\n\n    @given(\n        inputs=hu.lengths_tensor().flatmap(\n            lambda pair: st.tuples(\n                st.just(pair[0]),\n                st.just(pair[1]),\n                hu.dims(max_value=len(pair[1])),\n            )\n        ).flatmap(\n            lambda tup: st.tuples(\n                st.just(tup[0]),\n                st.just(tup[1]),\n                hu.arrays(\n                    tup[2], dtype=np.int32,\n                    elements=st.integers(\n                        min_value=0, max_value=len(tup[1]) - 1)),\n            )\n        ),\n        **hu.gcs_cpu_only)\n    def test_lengths_gather(self, inputs, gc, dc):\n        items = inputs[0]\n        lengths = inputs[1]\n        indices = inputs[2]\n\n        def lengths_gather_op(items, lengths, indices):\n            ends = np.cumsum(lengths)\n            return [np.concatenate(\n                list(items[ends[i] - lengths[i]:ends[i]] for i in indices))]\n\n        op = core.CreateOperator(\n            \"LengthsGather\",\n            [\"items\", \"lengths\", \"indices\"],\n            [\"output\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[items, lengths, indices],\n            reference=lengths_gather_op,\n        )\n\n    @given(**hu.gcs)\n    def test_size_op(self, gc, dc):\n        X = np.array([[1, 2], [3, 4]]).astype(np.float32)\n\n        def size_op(tensor):\n            return [np.prod(tensor.shape)]\n\n        op = core.CreateOperator(\n            \"Size\",\n            [\"X\"],\n            [\"output\"]\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=[X],\n            reference=size_op,\n        )\n\n    def test_alias_op(self):\n        \"\"\" Don't use hypothesis because there are only 2 cases to check\"\"\"\n        for size in [0, 5]:\n            X = np.arange(size).astype(np.float32)\n            workspace.FeedBlob('X', X)\n\n            op = core.CreateOperator(\n                \"Alias\",\n                [\"X\"],\n                [\"Y\"]\n            )\n            workspace.RunOperatorOnce(op)\n            Y = workspace.FetchBlob('Y')\n            np.testing.assert_array_equal(X, Y)\n\n    @given(**hu.gcs)\n    def test_range(self, gc, dc):\n        names = [\n            ('stop_',),\n            ('start_', 'stop_'),\n            ('start_', 'stop_', 'step_'),\n        ]\n        # Most random values aren't great here, so use a fixed set instead of\n        # hypothesis.\n        for inputs in (\n            (10,),\n            (np.float32(10.0),),\n            (0,),\n            (0, 0),\n            (10., 5.0, -1.),\n            (2, 10000),\n            (2, 10000, 20000),\n            (2, 10000, -1),\n        ):\n            inputs = [np.array(v) for v in inputs]\n            op = core.CreateOperator(\n                \"Range\",\n                names[len(inputs) - 1],\n                [\"Y\"]\n            )\n\n            self.assertReferenceChecks(\n                device_option=gc,\n                op=op,\n                inputs=inputs,\n                reference=lambda *x: [np.arange(*x)],\n            )\n            self.assertDeviceChecks(dc, op, inputs, [0])\n\n        with self.assertRaisesRegexp(RuntimeError, 'Step size cannot be 0'):\n            inputs = (np.array(0), np.array(10), np.array(0))\n            op = core.CreateOperator(\n                \"Range\",\n                names[len(inputs) - 1],\n                [\"Y\"]\n            )\n            self.assertReferenceChecks(\n                device_option=gc,\n                op=op,\n                inputs=inputs,\n                reference=lambda *x: [np.arange(*x)],\n            )\n"
  },
  {
    "path": "caffe2/python/operator_test/video_input_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\ntry:\n    import lmdb\nexcept ImportError:\n    raise unittest.SkipTest('python-lmdb is not installed')\n\nimport sys\nimport os\nimport shutil\nimport tempfile\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import workspace, model_helper\nimport numpy as np\n\n\nclass VideoInputOpTest(unittest.TestCase):\n\n    def create_a_list(self, output_file, line, n):\n        # create a list that repeat a line n times\n        # used for creating a list file for simple test input\n        with open(output_file, 'w') as file:\n            for _i in range(n):\n                file.write(line)\n\n    def create_video_db(self, list_file, output_file, use_list=False):\n        # Write to lmdb database...\n        LMDB_MAP_SIZE = 1 << 40   # MODIFY\n        env = lmdb.open(output_file, map_size=LMDB_MAP_SIZE)\n        total_size = 0\n\n        file_name = []\n        start_frame = []\n        label = []\n        index = 0\n\n        with env.begin(write=True) as txn:\n            with open(list_file, 'r') as data:\n                for line in data:\n                    p = line.split()\n                    file_name = p[0]\n                    start_frame = int(p[1])\n                    label = int(p[2])\n\n                    if not use_list:\n                        with open(file_name, mode='rb') as file:\n                            video_data = file.read()\n                    else:\n                        video_data = file_name\n\n                    tensor_protos = caffe2_pb2.TensorProtos()\n                    video_tensor = tensor_protos.protos.add()\n                    video_tensor.data_type = 4  # string data\n                    video_tensor.string_data.append(video_data)\n\n                    label_tensor = tensor_protos.protos.add()\n                    label_tensor.data_type = 2\n                    label_tensor.int32_data.append(label)\n\n                    start_frame_tensor = tensor_protos.protos.add()\n                    start_frame_tensor.data_type = 2\n                    start_frame_tensor.int32_data.append(start_frame)\n\n                    txn.put(\n                        '{}'.format(index).encode('ascii'),\n                        tensor_protos.SerializeToString()\n                    )\n                    index = index + 1\n                    total_size = total_size + len(video_data) + sys.getsizeof(int)\n        return total_size\n\n    # sample one clip randomly from the video\n    def test_rgb_with_temporal_jittering(self):\n        random_label = np.random.randint(0, 100)\n        VIDEO = \"/mnt/vol/gfsdataswarm-oregon/users/trandu/sample.avi\"\n        if not os.path.exists(VIDEO):\n            raise unittest.SkipTest('Missing data')\n        temp_list = tempfile.NamedTemporaryFile(delete=False).name\n        line_str = '{} 0 {}\\n'.format(VIDEO, random_label)\n        self.create_a_list(temp_list, line_str, 16)\n        video_db_dir = tempfile.mkdtemp()\n\n        self.create_video_db(temp_list, video_db_dir)\n        model = model_helper.ModelHelper(name=\"Video Loader from LMDB\")\n        reader = model.CreateDB(\"sample\", db=video_db_dir, db_type=\"lmdb\")\n\n        # build the model\n        model.net.VideoInput(\n            reader,\n            [\"data\", \"label\"],\n            name=\"data\",\n            batch_size=16,\n            clip_per_video=1,\n            crop_size=112,\n            scale_w=171,\n            scale_h=128,\n            length_rgb=8,\n            sampling_rate_rgb=1,\n            decode_type=0,\n            video_res_type=0)\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        data = workspace.FetchBlob(\"data\")\n        label = workspace.FetchBlob(\"label\")\n\n        np.testing.assert_equal(label, random_label)\n        np.testing.assert_equal(data.shape, [16, 3, 8, 112, 112])\n        os.remove(temp_list)\n        shutil.rmtree(video_db_dir)\n\n    # sample multiple clips uniformly from the video\n    def test_rgb_with_uniform_sampling(self):\n        random_label = np.random.randint(0, 100)\n        clip_per_video = np.random.randint(2, 11)\n        VIDEO = \"/mnt/vol/gfsdataswarm-oregon/users/trandu/sample.avi\"\n        if not os.path.exists(VIDEO):\n            raise unittest.SkipTest('Missing data')\n        temp_list = tempfile.NamedTemporaryFile(delete=False).name\n        line_str = '{} 0 {}\\n'.format(VIDEO, random_label)\n        self.create_a_list(temp_list, line_str, 16)\n        video_db_dir = tempfile.mkdtemp()\n\n        self.create_video_db(temp_list, video_db_dir)\n        model = model_helper.ModelHelper(name=\"Video Loader from LMDB\")\n        reader = model.CreateDB(\"sample\", db=video_db_dir, db_type=\"lmdb\")\n\n        # build the model\n        model.net.VideoInput(\n            reader,\n            [\"data\", \"label\"],\n            name=\"data\",\n            batch_size=3,\n            clip_per_video=clip_per_video,\n            crop_size=112,\n            scale_w=171,\n            scale_h=128,\n            length_rgb=8,\n            sampling_rate_rgb=1,\n            decode_type=1,\n            video_res_type=0)\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        data = workspace.FetchBlob(\"data\")\n        label = workspace.FetchBlob(\"label\")\n\n        np.testing.assert_equal(label, random_label)\n        np.testing.assert_equal(data.shape, [3 * clip_per_video, 3, 8, 112, 112])\n        os.remove(temp_list)\n        shutil.rmtree(video_db_dir)\n\n    # sample multiple clips uniformly from the video, rectangle cropping.\n    # VideoResType is USE_WIDTH_HEIGHT\n    def test_rgb_with_uniform_sampling_rectangle_cropping_use_width_height(self):\n        batch_size = 3\n        crop_height, crop_width = 112, 144\n        random_label = np.random.randint(0, 100)\n        clip_per_video = np.random.randint(2, 11)\n        VIDEO = \"/mnt/vol/gfsdataswarm-oregon/users/trandu/sample.avi\"\n        if not os.path.exists(VIDEO):\n            raise unittest.SkipTest('Missing data')\n        temp_list = tempfile.NamedTemporaryFile(delete=False).name\n        line_str = '{} 0 {}\\n'.format(VIDEO, random_label)\n        self.create_a_list(temp_list, line_str, 16)\n        video_db_dir = tempfile.mkdtemp()\n\n        self.create_video_db(temp_list, video_db_dir)\n        model = model_helper.ModelHelper(name=\"Video Loader from LMDB\")\n        reader = model.CreateDB(\"sample\", db=video_db_dir, db_type=\"lmdb\")\n\n        # build the model\n        model.net.VideoInput(\n            reader,\n            [\"data\", \"label\"],\n            name=\"data\",\n            batch_size=batch_size,\n            clip_per_video=clip_per_video,\n            crop_height=crop_height,\n            crop_width=crop_width,\n            scale_w=171,\n            scale_h=128,\n            length_rgb=8,\n            sampling_rate_rgb=1,\n            color_jitter=True,\n            color_lighting=True,\n            decode_type=1,\n            video_res_type=0)\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        data = workspace.FetchBlob(\"data\")\n        label = workspace.FetchBlob(\"label\")\n\n        np.testing.assert_equal(\n            label.shape, [batch_size * clip_per_video])\n        for i in range(batch_size * clip_per_video):\n            np.testing.assert_equal(label[i], random_label)\n        np.testing.assert_equal(\n            data.shape,\n            [batch_size * clip_per_video, 3, 8, crop_height, crop_width])\n        os.remove(temp_list)\n        shutil.rmtree(video_db_dir)\n\n    # sample multiple clips uniformly from the video, rectangle cropping.\n    # VideoResType is USE_MINIMAL_WIDTH_HEIGHT\n    def test_rgb_with_uniform_sampling_rectangle_cropping_use_minimal_width_height(\n        self\n    ):\n        batch_size = 3\n        height_min, width_min = 128, 166\n        crop_height, crop_width = 112, 144\n        random_label = np.random.randint(0, 100)\n        clip_per_video = np.random.randint(2, 11)\n        VIDEO = \"/mnt/vol/gfsdataswarm-oregon/users/trandu/sample.avi\"\n        if not os.path.exists(VIDEO):\n            raise unittest.SkipTest('Missing data')\n        temp_list = tempfile.NamedTemporaryFile(delete=False).name\n        line_str = '{} 0 {}\\n'.format(VIDEO, random_label)\n        self.create_a_list(temp_list, line_str, 16)\n        video_db_dir = tempfile.mkdtemp()\n\n        self.create_video_db(temp_list, video_db_dir)\n        model = model_helper.ModelHelper(name=\"Video Loader from LMDB\")\n        reader = model.CreateDB(\"sample\", db=video_db_dir, db_type=\"lmdb\")\n\n        # build the model\n        model.net.VideoInput(\n            reader,\n            [\"data\", \"label\"],\n            name=\"data\",\n            batch_size=batch_size,\n            clip_per_video=clip_per_video,\n            height_min=height_min,\n            width_min=width_min,\n            crop_height=crop_height,\n            crop_width=crop_width,\n            length_rgb=8,\n            sampling_rate_rgb=1,\n            color_jitter=True,\n            color_lighting=True,\n            decode_type=1,\n            video_res_type=1)\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        data = workspace.FetchBlob(\"data\")\n        label = workspace.FetchBlob(\"label\")\n\n        np.testing.assert_equal(\n            label.shape, [batch_size * clip_per_video])\n        for i in range(batch_size * clip_per_video):\n            np.testing.assert_equal(label[i], random_label)\n        np.testing.assert_equal(\n            data.shape,\n            [batch_size * clip_per_video, 3, 8, crop_height, crop_width])\n        os.remove(temp_list)\n        shutil.rmtree(video_db_dir)\n\n    # sample multiple clips uniformly from the video, while color jitterring\n    # and lighting are enabled\n    def test_rgb_with_uniform_sampling_color_jittering_lighting(self):\n        batch_size = 3\n        random_label = np.random.randint(0, 100)\n        clip_per_video = np.random.randint(2, 11)\n        VIDEO = \"/mnt/vol/gfsdataswarm-oregon/users/trandu/sample.avi\"\n        if not os.path.exists(VIDEO):\n            raise unittest.SkipTest('Missing data')\n        temp_list = tempfile.NamedTemporaryFile(delete=False).name\n        line_str = '{} 0 {}\\n'.format(VIDEO, random_label)\n        self.create_a_list(temp_list, line_str, 16)\n        video_db_dir = tempfile.mkdtemp()\n\n        self.create_video_db(temp_list, video_db_dir)\n        model = model_helper.ModelHelper(name=\"Video Loader from LMDB\")\n        reader = model.CreateDB(\"sample\", db=video_db_dir, db_type=\"lmdb\")\n\n        # build the model\n        model.net.VideoInput(\n            reader,\n            [\"data\", \"label\"],\n            name=\"data\",\n            batch_size=batch_size,\n            clip_per_video=clip_per_video,\n            crop_size=112,\n            scale_w=171,\n            scale_h=128,\n            length_rgb=8,\n            sampling_rate_rgb=1,\n            color_jitter=True,\n            color_lighting=True,\n            decode_type=1,\n            video_res_type=0)\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        data = workspace.FetchBlob(\"data\")\n        label = workspace.FetchBlob(\"label\")\n\n        np.testing.assert_equal(\n            label.shape, [batch_size * clip_per_video])\n        for i in range(batch_size * clip_per_video):\n            np.testing.assert_equal(label[i], random_label)\n\n        np.testing.assert_equal(\n            data.shape,\n            [batch_size * clip_per_video, 3, 8, 112, 112])\n        os.remove(temp_list)\n        shutil.rmtree(video_db_dir)\n\n    # sample multiple clips uniformly from the video\n    def test_rgb_with_uniform_sampling_and_multi_cropping(self):\n        # we take left-top, central-top, right-top, left-bottom, central-bottom,\n        # right-bottom and central-central croppings as well as their mirrorings\n        # In total, 14 croppings\n        multi_crop_count = 14\n        batch_size = 3\n        random_label = np.random.randint(0, 100)\n        clip_per_video = np.random.randint(2, 11)\n        VIDEO = \"/mnt/vol/gfsdataswarm-oregon/users/trandu/sample.avi\"\n        if not os.path.exists(VIDEO):\n            raise unittest.SkipTest('Missing data')\n        temp_list = tempfile.NamedTemporaryFile(delete=False).name\n        line_str = '{} 0 {}\\n'.format(VIDEO, random_label)\n        self.create_a_list(temp_list, line_str, 16)\n        video_db_dir = tempfile.mkdtemp()\n\n        self.create_video_db(temp_list, video_db_dir)\n        model = model_helper.ModelHelper(name=\"Video Loader from LMDB\")\n        reader = model.CreateDB(\"sample\", db=video_db_dir, db_type=\"lmdb\")\n\n        # build the model\n        model.net.VideoInput(\n            reader,\n            [\"data\", \"label\"],\n            name=\"data\",\n            batch_size=batch_size,\n            clip_per_video=clip_per_video,\n            crop_size=112,\n            scale_w=171,\n            scale_h=128,\n            length_rgb=8,\n            sampling_rate_rgb=1,\n            decode_type=1,\n            multi_crop=True,\n            video_res_type=0)\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        data = workspace.FetchBlob(\"data\")\n        label = workspace.FetchBlob(\"label\")\n\n        np.testing.assert_equal(\n            label.shape, [batch_size * clip_per_video * multi_crop_count])\n        for i in range(batch_size * clip_per_video * multi_crop_count):\n            np.testing.assert_equal(label[i], random_label)\n        np.testing.assert_equal(\n            data.shape,\n            [batch_size * clip_per_video * multi_crop_count, 3, 8, 112, 112])\n        os.remove(temp_list)\n        shutil.rmtree(video_db_dir)\n\n    # test optical flow\n    def test_optical_flow_with_temporal_jittering(self):\n        random_label = np.random.randint(0, 100)\n        VIDEO = \"/mnt/vol/gfsdataswarm-oregon/users/trandu/sample.avi\"\n        if not os.path.exists(VIDEO):\n            raise unittest.SkipTest('Missing data')\n        temp_list = tempfile.NamedTemporaryFile(delete=False).name\n        line_str = '{} 0 {}\\n'.format(VIDEO, random_label)\n        self.create_a_list(temp_list, line_str, 16)\n        video_db_dir = tempfile.mkdtemp()\n\n        self.create_video_db(temp_list, video_db_dir)\n        model = model_helper.ModelHelper(name=\"Video Loader from LMDB\")\n        reader = model.CreateDB(\"sample\", db=video_db_dir, db_type=\"lmdb\")\n        model.net.VideoInput(\n            reader,\n            [\"data\", \"label\"],\n            name=\"data\",\n            batch_size=16,\n            clip_per_video=1,\n            crop_size=112,\n            scale_w=171,\n            scale_h=128,\n            length_of=8,\n            sampling_rate_of=1,\n            frame_gap_of=1,\n            decode_type=0,\n            video_res_type=0,\n            get_rgb=False,\n            get_optical_flow=True)\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        data = workspace.FetchBlob(\"data\")\n        label = workspace.FetchBlob(\"label\")\n\n        np.testing.assert_equal(label, random_label)\n        np.testing.assert_equal(data.shape, [16, 2, 8, 112, 112])\n        os.remove(temp_list)\n        shutil.rmtree(video_db_dir)\n\n    # test optical flow, rectangle cropping, VideoResType is\n    # USE_WIDTH_HEIGHT\n    def test_optical_flow_with_rectangle_cropping_use_width_height(self):\n        batch_size = 16\n        scale_h, scale_w = 128, 166\n        crop_height, crop_width = 112, 144\n        random_label = np.random.randint(0, 100)\n        VIDEO = \"/mnt/vol/gfsdataswarm-oregon/users/trandu/sample.avi\"\n        if not os.path.exists(VIDEO):\n            raise unittest.SkipTest('Missing data')\n        temp_list = tempfile.NamedTemporaryFile(delete=False).name\n        line_str = '{} 0 {}\\n'.format(VIDEO, random_label)\n        self.create_a_list(temp_list, line_str, batch_size)\n        video_db_dir = tempfile.mkdtemp()\n\n        self.create_video_db(temp_list, video_db_dir)\n        model = model_helper.ModelHelper(name=\"Video Loader from LMDB\")\n        reader = model.CreateDB(\"sample\", db=video_db_dir, db_type=\"lmdb\")\n        model.net.VideoInput(\n            reader,\n            [\"data\", \"label\"],\n            name=\"data\",\n            batch_size=batch_size,\n            clip_per_video=1,\n            scale_h=scale_h,\n            scale_w=scale_w,\n            crop_height=crop_height,\n            crop_width=crop_width,\n            length_of=8,\n            sampling_rate_of=1,\n            frame_gap_of=1,\n            decode_type=0,\n            video_res_type=0,\n            get_rgb=False,\n            get_optical_flow=True)\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        data = workspace.FetchBlob(\"data\")\n        label = workspace.FetchBlob(\"label\")\n\n        np.testing.assert_equal(label.shape, [batch_size])\n        for i in range(batch_size):\n            np.testing.assert_equal(label[i], random_label)\n        np.testing.assert_equal(\n            data.shape, [batch_size, 2, 8, crop_height, crop_width])\n        os.remove(temp_list)\n        shutil.rmtree(video_db_dir)\n\n    # test optical flow, rectangle cropping, VideoResType is\n    # USE_MINIMAL_WIDTH_HEIGHT\n    def test_optical_flow_with_rectangle_cropping_use_minimal_width_height(self):\n        batch_size = 16\n        height_min, width_min = 128, 166\n        crop_height, crop_width = 112, 144\n        random_label = np.random.randint(0, 100)\n        VIDEO = \"/mnt/vol/gfsdataswarm-oregon/users/trandu/sample.avi\"\n        if not os.path.exists(VIDEO):\n            raise unittest.SkipTest('Missing data')\n        temp_list = tempfile.NamedTemporaryFile(delete=False).name\n        line_str = '{} 0 {}\\n'.format(VIDEO, random_label)\n        self.create_a_list(temp_list, line_str, batch_size)\n        video_db_dir = tempfile.mkdtemp()\n\n        self.create_video_db(temp_list, video_db_dir)\n        model = model_helper.ModelHelper(name=\"Video Loader from LMDB\")\n        reader = model.CreateDB(\"sample\", db=video_db_dir, db_type=\"lmdb\")\n        model.net.VideoInput(\n            reader,\n            [\"data\", \"label\"],\n            name=\"data\",\n            batch_size=batch_size,\n            clip_per_video=1,\n            height_min=height_min,\n            width_min=width_min,\n            crop_height=crop_height,\n            crop_width=crop_width,\n            length_of=8,\n            sampling_rate_of=1,\n            frame_gap_of=1,\n            decode_type=0,\n            video_res_type=1,\n            get_rgb=False,\n            get_optical_flow=True)\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        data = workspace.FetchBlob(\"data\")\n        label = workspace.FetchBlob(\"label\")\n\n        np.testing.assert_equal(label.shape, [batch_size])\n        for i in range(batch_size):\n            np.testing.assert_equal(label[i], random_label)\n        np.testing.assert_equal(\n            data.shape, [batch_size, 2, 8, crop_height, crop_width])\n        os.remove(temp_list)\n        shutil.rmtree(video_db_dir)\n\n    # test optical flow, multi-cropping\n    def test_optical_flow_with_multi_cropping(self):\n        multi_crop_count = 14\n        batch_size = 16\n        height_min, width_min = 128, 166\n        crop_height, crop_width = 112, 144\n        random_label = np.random.randint(0, 100)\n        VIDEO = \"/mnt/vol/gfsdataswarm-oregon/users/trandu/sample.avi\"\n        if not os.path.exists(VIDEO):\n            raise unittest.SkipTest('Missing data')\n        temp_list = tempfile.NamedTemporaryFile(delete=False).name\n        line_str = '{} 0 {}\\n'.format(VIDEO, random_label)\n        self.create_a_list(temp_list, line_str, batch_size)\n        video_db_dir = tempfile.mkdtemp()\n\n        self.create_video_db(temp_list, video_db_dir)\n        model = model_helper.ModelHelper(name=\"Video Loader from LMDB\")\n        reader = model.CreateDB(\"sample\", db=video_db_dir, db_type=\"lmdb\")\n        model.net.VideoInput(\n            reader,\n            [\"data\", \"label\"],\n            name=\"data\",\n            batch_size=batch_size,\n            clip_per_video=1,\n            height_min=height_min,\n            width_min=width_min,\n            crop_height=crop_height,\n            crop_width=crop_width,\n            length_of=8,\n            sampling_rate_of=1,\n            frame_gap_of=1,\n            decode_type=0,\n            multi_crop=True,\n            video_res_type=1,\n            get_rgb=False,\n            get_optical_flow=True)\n\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.RunNetOnce(model.net)\n        data = workspace.FetchBlob(\"data\")\n        label = workspace.FetchBlob(\"label\")\n\n        np.testing.assert_equal(label.shape, [batch_size * multi_crop_count])\n        for i in range(batch_size * multi_crop_count):\n            np.testing.assert_equal(label[i], random_label)\n        np.testing.assert_equal(\n            data.shape,\n            [batch_size * multi_crop_count, 2, 8, crop_height, crop_width])\n        os.remove(temp_list)\n        shutil.rmtree(video_db_dir)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/weighted_sample_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom caffe2.python import core\nfrom caffe2.python import workspace\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestWeightedSample(hu.HypothesisTestCase):\n    @given(\n        batch=st.integers(min_value=0, max_value=128),\n        weights_len=st.integers(min_value=0, max_value=128),\n        **hu.gcs\n    )\n    def test_weighted_sample(self, batch, weights_len, gc, dc):\n\n        weights = np.zeros((batch, weights_len))\n        values = np.zeros((batch, weights_len))\n        rand_indices = []\n        rand_values = []\n        if batch > 0 and weights_len > 0:\n            for i in range(batch):\n                rand_tmp = np.random.randint(0, weights_len)\n                rand_val = np.random.rand()\n                rand_indices.append(rand_tmp)\n                rand_values.append(rand_val)\n                weights[i, rand_tmp] = 1.0\n                values[i, rand_tmp] = rand_val\n\n        rand_indices = np.array(rand_indices, dtype=np.float32)\n        rand_values = np.array(rand_values, dtype=np.float32)\n        workspace.FeedBlob(\"weights\", weights.astype(np.float32))\n        workspace.FeedBlob(\"values\", values.astype(np.float32))\n\n        # output both indices and values\n        op = core.CreateOperator(\n            \"WeightedSample\", [\"weights\", \"values\"],\n            [\"sample_indices\", \"sample_values\"]\n        )\n        workspace.RunOperatorOnce(op)\n        result_indices = workspace.FetchBlob(\"sample_indices\")\n        result_values = workspace.FetchBlob(\"sample_values\")\n        if batch > 0 and weights_len > 0:\n            for i in range(batch):\n                np.testing.assert_allclose(rand_indices[i], result_indices[i])\n                np.testing.assert_allclose(rand_values[i], result_values[i])\n        else:\n            np.testing.assert_allclose(rand_indices, result_indices)\n            np.testing.assert_allclose(rand_values, result_values)\n        self.assertDeviceChecks(\n            dc,\n            op,\n            [weights.astype(np.float32), values.astype(np.float32)],\n            [0, 1]\n        )\n\n        # output indices only\n        op2 = core.CreateOperator(\n            \"WeightedSample\", [\"weights\"], [\"sample_indices_2\"]\n        )\n        workspace.RunOperatorOnce(op2)\n        result = workspace.FetchBlob(\"sample_indices_2\")\n        if batch > 0 and weights_len > 0:\n            for i in range(batch):\n                np.testing.assert_allclose(rand_indices[i], result[i])\n        else:\n            np.testing.assert_allclose(rand_indices, result)\n        self.assertDeviceChecks(dc, op2, [weights.astype(np.float32)], [0])\n\n\nif __name__ == \"__main__\":\n    import unittest\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/operator_test/weighted_sum_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom hypothesis import given\nimport caffe2.python.hypothesis_test_util as hu\nimport hypothesis.strategies as st\nimport numpy as np\n\n\nclass TestWeightedSumOp(hu.HypothesisTestCase):\n\n    @given(n=st.integers(5, 8), m=st.integers(1, 1),\n           d=st.integers(2, 4), grad_on_w=st.booleans(),\n           **hu.gcs_cpu_only)\n    def test_weighted_sum(self, n, m, d, grad_on_w, gc, dc):\n        input_names = []\n        input_vars = []\n        for i in range(m):\n            X_name = 'X' + str(i)\n            w_name = 'w' + str(i)\n            input_names.extend([X_name, w_name])\n            var = np.random.rand(n, d).astype(np.float32)\n            vars()[X_name] = var\n            input_vars.append(var)\n            var = np.random.rand(1).astype(np.float32)\n            vars()[w_name] = var\n            input_vars.append(var)\n\n        def weighted_sum_op_ref(*args):\n            res = np.zeros((n, d))\n            for i in range(m):\n                res = res + args[2 * i + 1] * args[2 * i]\n\n            return (res, )\n\n        op = core.CreateOperator(\n            \"WeightedSum\",\n            input_names,\n            ['Y'],\n            grad_on_w=grad_on_w,\n        )\n\n        self.assertReferenceChecks(\n            device_option=gc,\n            op=op,\n            inputs=input_vars,\n            reference=weighted_sum_op_ref,\n        )\n\n        output_to_check_grad = range(2 * m) if grad_on_w else range(0, 2 * m, 2)\n        for i in output_to_check_grad:\n            self.assertGradientChecks(\n                device_option=gc,\n                op=op,\n                inputs=input_vars,\n                outputs_to_check=i,\n                outputs_with_grads=[0],\n            )\n"
  },
  {
    "path": "caffe2/python/optimizer.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package optimizer\n# Module caffe2.python.optimizer\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom collections import namedtuple, defaultdict\nfrom past.builtins import basestring\n\nimport numpy as np\n\nfrom caffe2.python import core, scope, workspace\nfrom caffe2.python.modeling import parameter_info\nfrom caffe2.proto import caffe2_pb2\n\n\n_OPTIMIZER_ITERATION_NAME = \"optimizer_iteration\"\n_LEARNING_RATE_INJECTION = \"lr_injection\"\n\nAuxOptimizerParams = namedtuple(\"AuxOptimizerParams\", [\"local\", \"shared\"])\n_optimizer_instance_count = defaultdict(int)\n\n\nclass Optimizer(object):\n    def __init__(self):\n        self._aux_params = AuxOptimizerParams(local=[], shared=[])\n        self._instance_num = _optimizer_instance_count[self.__class__.__name__]\n        _optimizer_instance_count[self.__class__.__name__] += 1\n        self._lr_multiplier = None\n        self._lr_multiplier_on_gpu = False\n\n    '''\n    Adds optimization operators to the net for given parameter and its gradient\n    Parameter is specified by either 'param' being a ParameterInfo object.\n    In this case  param.grad has to be set\n\n    Or by 'param' being a BlobReference and 'grad' being a BlobReference for its\n    gradient.\n    '''\n    def __call__(self, net, param_init_net, param, grad=None):\n        if grad is None:\n            assert isinstance(param, parameter_info.ParameterInfo)\n            assert param.grad is not None\n        else:\n            if isinstance(param, basestring):\n                param = core.BlobReference(param)\n            param = parameter_info.ParameterInfo(\n                param_id=None, param=param, grad=grad)\n\n        self._run(net, param_init_net, param)\n\n    def _run(self, net, param_init_net, param_info):\n        raise Exception(\"Not Implemented\")\n\n    def get_cpu_blob_name(self, base_str, node_name=''):\n        classname = self.__class__.__name__\n        return '%s_%d_%s%s_cpu' % (classname, self._instance_num, base_str, node_name)\n\n    def get_gpu_blob_name(self, base_str, gpu_id, node_name):\n        classname = self.__class__.__name__\n        return '%s_%d_%s%s_gpu%d' % (\n            classname, self._instance_num, base_str, node_name, gpu_id,\n        )\n\n    def make_unique_blob_name(self, base_str):\n        \"\"\"\n        Returns a blob name that will be unique to the current device\n        and optimizer instance.\n        \"\"\"\n        current_scope = scope.CurrentDeviceScope()\n        if current_scope is None:\n            return self.get_cpu_blob_name(base_str)\n\n        if current_scope.device_type == caffe2_pb2.CUDA:\n            return self.get_gpu_blob_name(\n                base_str, current_scope.cuda_gpu_id, current_scope.node_name\n            )\n        else:\n            return self.get_cpu_blob_name(base_str, current_scope.node_name)\n\n    def build_lr(self, net, param_init_net, base_learning_rate,\n                 learning_rate_blob=None, policy=\"fixed\",\n                 iter_val=0, **kwargs):\n        if learning_rate_blob is None:\n            learning_rate_blob = self.make_unique_blob_name('lr')\n\n        optimization_iter_blob = _OPTIMIZER_ITERATION_NAME\n        if not param_init_net.BlobIsDefined(optimization_iter_blob):\n            # Add training operators.\n            with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n                iteration = param_init_net.ConstantFill(\n                    [], optimization_iter_blob, shape=[1],\n                    value=iter_val,\n                    dtype=core.DataType.INT64)\n                iter_mutex = param_init_net.CreateMutex(\n                    [], [\"iteration_mutex\"]\n                )\n                net.AtomicIter([iter_mutex, iteration], [iteration])\n        else:\n            iteration = param_init_net.GetBlobRef(optimization_iter_blob)\n\n        if not net.BlobIsDefined(learning_rate_blob):\n            # There is one interesting thing here: since we are minimizing, we are\n            # doing \"descent\" so the learning rate is set to be negative.\n            lr = net.LearningRate(\n                [iteration],\n                learning_rate_blob,\n                base_lr=-base_learning_rate,\n                policy=policy,\n                **kwargs\n            )\n        else:\n            lr = net.GetBlobRef(learning_rate_blob)\n\n        if self._lr_multiplier is not None:\n            current_scope = scope.CurrentDeviceScope()\n            if (current_scope.device_type == caffe2_pb2.CUDA\n                    and not self._lr_multiplier_on_gpu):\n                lr_multiplier = net.CopyFromCPUInput(\n                    self._lr_multiplier,\n                    self.make_unique_blob_name('lr_multiplier')\n                )\n            else:\n                lr_multiplier = self._lr_multiplier\n\n            scaled_lr = net.Mul(\n                [lr, lr_multiplier],\n                self.make_unique_blob_name('scaled_lr'),\n                broadcast=1,\n            )\n            lr = scaled_lr\n\n        return lr, iteration\n\n    def add_lr_multiplier(self, lr_multiplier, is_gpu_blob=False):\n        self._lr_multiplier = lr_multiplier\n        self._lr_multiplier_on_gpu = is_gpu_blob\n\n    @staticmethod\n    def dedup(net, sparse_dedup_aggregator, grad):\n        assert (isinstance(grad, core.GradientSlice))\n        if sparse_dedup_aggregator:\n            return net.DeduplicateGradientSlices(\n                grad, aggregator=sparse_dedup_aggregator)\n        else:\n            return grad\n\n    def get_auxiliary_parameters(self):\n        \"\"\"Returns a list of auxiliary parameters.\n\n        Returns:\n            aux_params: A namedtuple, AuxParams.\n\n            aux_params.local stores a list of blobs. Each blob is a local\n            auxiliary parameter. A local auxiliary parameter is a parameter in\n            parallel to a learning rate parameter. Take adagrad as an example,\n            the local auxiliary parameter is the squared sum parameter, because\n            every learning rate has a squared sum associated with it.\n\n            aux_params.shared also stores a list of blobs. Each blob is a shared\n            auxiliary parameter. A shared auxiliary parameter is a parameter\n            that is shared across all the learning rate parameters. Take adam as\n            an example, the iteration parameter is a shared parameter, because\n            all the learning rates share the same iteration parameter.\n        \"\"\"\n        return self._aux_params\n\n    # TODO(xlwang): In transfer learning, parameter initialized from pretrained\n    # model might require a different learning rate than otherwise initialized.\n    # To this end, here we implement a python solution where\n    # `base_learning_rate` is scaled by `scale`, by calling\n    # `scale_learning_rate`; Alternatively, we can achieve same effect by\n    # rewriting the LearningRate operator in C++\n    # Note that it is the responsibility of specific optimizer to decide what\n    # logic should be used for `scale_learning_rate`\n    def scale_learning_rate(self, *args, **kwargs):\n        raise NotImplementedError(\n            \"Optimizer Need to Implement `scale_learning_rate` method.\")\n\n\nclass SgdOptimizer(Optimizer):\n    def __init__(self, base_learning_rate=0.01, policy='fixed',\n                 momentum=0.0, nesterov=1, sparse_dedup_aggregator=None,\n                 lars=None, **kwargs):\n        super(SgdOptimizer, self).__init__()\n        self.base_learning_rate = base_learning_rate\n        self.policy = policy\n        self.momentum = momentum\n        self.nesterov = nesterov\n        self.sparse_dedup_aggregator = sparse_dedup_aggregator\n        self.lars = lars\n        self.init_kwargs = kwargs\n\n    def _run(self, net, param_init_net, param_info):\n        param = param_info.blob\n        grad = param_info.grad\n        if self.base_learning_rate == 0:\n            return\n        assert self.base_learning_rate > 0\n\n        # TODO(zqq): support LARS for sparse parameters\n        if self.lars is not None and not isinstance(grad, core.GradientSlice):\n            assert self.lars >= 0, 'Lars offset must be nonnegative.'\n            lr_lars_multiplier = net.Lars(\n                [param, grad],\n                self.make_unique_blob_name(str(param) + \"_lars\"),\n                offset=self.lars)\n            current_scope = scope.CurrentDeviceScope()\n            self.add_lr_multiplier(\n                lr_lars_multiplier,\n                is_gpu_blob=(current_scope.device_type == caffe2_pb2.CUDA),\n            )\n\n        # We need negative sign for LR when used directly with WeightedSum\n        # below.\n        lr_sign = -1 if self.momentum else 1\n        lr, _ = self.build_lr(\n            net, param_init_net,\n            base_learning_rate=self.base_learning_rate * lr_sign,\n            policy=self.policy,\n            **(self.init_kwargs)\n        )\n\n        dev = scope.CurrentDeviceScope()\n        if dev is None:\n            dev = core.DeviceOption(caffe2_pb2.CPU)\n\n        # Each GPU/CPU must have its own ONE blob, thus modify the name\n        # to include device information.\n        ONE = param_init_net.ConstantFill(\n            [],\n            \"ONE_{}_{}{}\".format(dev.device_type, dev.cuda_gpu_id, dev.node_name),\n            shape=[1],\n            value=1.0\n        )\n\n        self._aux_params.shared.append(ONE)\n\n        if self.momentum > 0:\n            momentum_data = param_init_net.ConstantFill(\n                param, str(param) + \"_momentum\", value=0.)\n            self._aux_params.local.append(momentum_data)\n\n        if isinstance(grad, core.GradientSlice):\n            grad = self.dedup(net, self.sparse_dedup_aggregator, grad)\n            if self.momentum > 0.:\n                net.SparseMomentumSGDUpdate(\n                    [grad.values, momentum_data, lr, param, grad.indices],\n                    [grad.values, momentum_data, param],\n                    momentum=self.momentum,\n                    nesterov=self.nesterov)\n            else:\n                net.ScatterWeightedSum(\n                    [param, ONE, grad.indices, grad.values, lr],\n                    param\n                )\n        else:\n            if self.momentum > 0.:\n                net.MomentumSGDUpdate(\n                    [grad, momentum_data, lr, param],\n                    [grad, momentum_data, param],\n                    momentum=self.momentum,\n                    nesterov=self.nesterov)\n            else:\n                coeff = lr\n\n                net.WeightedSum(\n                    [param, ONE, grad, coeff],\n                    param\n                )\n\n    def scale_learning_rate(self, scale):\n        self.base_learning_rate *= scale\n        return\n\n\nclass MultiPrecisionSgdOptimizer(SgdOptimizer):\n    def __init__(self, base_learning_rate=0.1, momentum=0.0,\n                 policy=\"fixed\", nesterov=1, sparse_dedup_aggregator=None,\n                 **kwargs):\n        super(MultiPrecisionSgdOptimizer, self).__init__(\n            base_learning_rate=base_learning_rate,\n            policy=policy,\n            momentum=momentum,\n            nesterov=nesterov,\n            sparse_dedup_aggregator=sparse_dedup_aggregator,\n            **kwargs\n        )\n\n    def _run(self, net, param_init_net, param_info):\n        param = param_info.blob\n        param_fp32 = param_info.blob_copy[core.DataType.FLOAT] \\\n                if param_info.blob_copy is not None else None\n\n        # If we have a straight fp32 parameter, run the base class\n        if param_fp32 is None:\n            return SgdOptimizer._run(self, net, param_init_net, param_info)\n\n        grad = param_info.grad\n        if self.base_learning_rate == 0:\n            return\n        assert self.base_learning_rate > 0\n\n        lr, _ = self.build_lr(\n            net, param_init_net,\n            base_learning_rate=-self.base_learning_rate,\n            policy=self.policy,\n            **(self.init_kwargs)\n        )\n\n        momentum_data = param_init_net.ConstantFill(\n            param_fp32, str(param) + \"_momentum\", value=0.)\n        self._aux_params.local.append(momentum_data)\n\n        assert not isinstance(grad, core.GradientSlice), \\\n                \"Doesn't support sparse gradients\"\n\n        # Copy gradient to fp32\n        grad_fp32 = net.HalfToFloat(grad, grad + \"_fp32\")\n\n        # update (fused) in fp32\n        net.MomentumSGDUpdate(\n            [grad_fp32, momentum_data, lr, param_fp32],\n            [grad_fp32, momentum_data, param_fp32],\n            momentum=self.momentum,\n            nesterov=self.nesterov)\n\n        # Copy updated param back to fp16\n        net.FloatToHalf(param_fp32, param)\n\n\nclass FP16SgdOptimizer(SgdOptimizer):\n    def __init__(self, base_learning_rate=0.1, momentum=0.0,\n                 policy=\"fixed\", nesterov=1, weight_decay=0.0001,\n                 sparse_dedup_aggregator=None,\n                 **kwargs):\n        super(FP16SgdOptimizer, self).__init__(\n            base_learning_rate=base_learning_rate,\n            policy=policy,\n            momentum=momentum,\n            nesterov=nesterov,\n            sparse_dedup_aggregator=sparse_dedup_aggregator,\n            **kwargs\n        )\n        self.weight_decay = weight_decay\n\n    def _run(self, net, param_init_net, param_info, fp32_update=False):\n\n        fp32_update_flag = 0\n        param_name = str(param_info.blob)\n\n        # should only be triggered in FP16 training by SpatialBN, which\n        # requires FP32 params in CuDNN.\n        if param_name.find(\"spatbn\") != -1:\n            fp32_update = True\n\n        if fp32_update:\n            # doing a 32bit update\n            # Have to assume param_info.blob is FP32 as there is no way\n            # (that i currently know of) to query a blob's type in python\n            fp32_update_flag = 1\n            param = param_info.blob\n            param_fp32 = param_info.blob\n        else:\n            if param_info.blob_copy is None:\n                # doing a 32bit update\n                # Have to assume param_info.blob is FP32 as there is no way\n                # (that i currently know of) to query a blob's type in python\n                fp32_update_flag = 1\n                param = param_info.blob\n                param_fp32 = param_info.blob\n            else:\n                if core.DataType.FLOAT in param_info.blob_copy:\n                    param = param_info.blob\n                    param_fp32 = param_info.blob_copy[core.DataType.FLOAT]\n                elif core.DataType.FLOAT16 in param_info.blob_copy:\n                    param = param_info.blob_copy[core.DataType.FLOAT16]\n                    param_fp32 = param_info.blob\n                else:\n                    assert (False), (\n                        \"Unrecognized parameter format to be updated \"\n                        \"by FP16 Optimizer. Parameter: {}\".format(param_info.name)\n                    )\n\n        grad = param_info.grad\n\n        if self.base_learning_rate == 0:\n            return\n        assert self.base_learning_rate > 0\n\n        lr, _ = self.build_lr(\n            net, param_init_net,\n            base_learning_rate=-self.base_learning_rate,\n            policy=self.policy,\n            **(self.init_kwargs)\n        )\n\n        momentum_data_fp32 = param_init_net.ConstantFill(\n            param_fp32, str(param) + \"_momentum_fp32\", value=0.)\n\n        momentum_data = param_init_net.FloatToHalf(\n            momentum_data_fp32, str(param) + \"_momentum\")\n\n        self._aux_params.local.append(momentum_data)\n\n        assert not isinstance(grad, core.GradientSlice), \\\n                \"Doesn't support sparse gradients\"\n\n        if fp32_update_flag == 0:\n            net.FP16MomentumSGDUpdate(\n                [grad, momentum_data, lr, param],\n                [grad, momentum_data, param],\n                momentum=self.momentum,\n                nesterov=self.nesterov,\n                weight_decay=self.weight_decay)\n        else:\n            # flag set to 1, therefore doing FP32 update\n            net.FP32MomentumSGDUpdate(\n                [grad, momentum_data_fp32, lr, param],\n                [grad, momentum_data_fp32, param],\n                momentum=self.momentum,\n                nesterov=self.nesterov,\n                weight_decay=self.weight_decay)\n\n\nclass WeightDecayBuilder(Optimizer):\n    def __init__(self, weight_decay):\n        self.weight_decay = weight_decay\n\n    def _run(self, net, param_init_net, param_info):\n        dev = scope.CurrentDeviceScope()\n        if dev is None:\n            dev = core.DeviceOption(caffe2_pb2.CPU)\n\n        ONE = param_init_net.ConstantFill(\n            [],\n            \"ONE_{}_{}\".format(dev.device_type, dev.cuda_gpu_id),\n            shape=[1],\n            value=1.0\n        )\n        WD = param_init_net.ConstantFill(\n            [], \"wd_{}_{}\".format(dev.device_type, dev.cuda_gpu_id),\n            shape=[1], value=self.weight_decay\n        )\n\n        if isinstance(param_info.grad, core.GradientSlice):\n            assert \"Weight decay does not yet support sparse gradients\"\n        else:\n            net.WeightedSum(\n                [param_info.grad, ONE, param_info.blob, WD],\n                param_info.grad,\n            )\n\n\nclass AdagradOptimizer(Optimizer):\n    def __init__(self, alpha=0.01, epsilon=1e-4, decay=1, policy=\"fixed\",\n                 sparse_dedup_aggregator=None, rowWise=False, engine='',\n                 lars=None, **kwargs):\n        super(AdagradOptimizer, self).__init__()\n        self.alpha = alpha\n        self.epsilon = epsilon\n        self.decay = decay\n        self.policy = policy\n        self.sparse_dedup_aggregator = sparse_dedup_aggregator\n        self.rowWise = rowWise\n        self.engine = engine\n        self.lars = lars\n        self.init_kwargs = kwargs\n\n    def _run(self, net, param_init_net, param_info):\n        param = param_info.blob\n        grad = param_info.grad\n\n        if self.alpha <= 0:\n            return\n\n        if self.lars is not None and not isinstance(grad, core.GradientSlice):\n            assert self.lars >= 0, 'Lars offset must be nonnegative.'\n            lr_lars_multiplier = net.Lars(\n                [param, grad],\n                self.make_unique_blob_name(str(param) + \"_lars\"),\n                offset=self.lars)\n            current_scope = scope.CurrentDeviceScope()\n            self.add_lr_multiplier(\n                lr_lars_multiplier,\n                is_gpu_blob=(current_scope.device_type == caffe2_pb2.CUDA),\n            )\n\n        lr, _ = self.build_lr(\n            net, param_init_net,\n            base_learning_rate=self.alpha,\n            policy=self.policy,\n            **(self.init_kwargs)\n        )\n\n        if self.rowWise:\n            shapes, types = workspace.InferShapesAndTypes([param_init_net])\n            if str(param) not in shapes:\n                # Type/shape inference is not available for this param, fallback\n                # on Shape/Slice logic\n                shape = param_init_net.Shape(param, str(param) + \"_shape\")\n                num_rows = param_init_net.Slice(\n                    [shape],\n                    str(shape) + \"_numrows\",\n                    starts=[0], ends=[1]\n                )\n                param_squared_sum = param_init_net.ConstantFill(\n                    num_rows,\n                    str(param) + \"_avg_squared_sum\",\n                    input_as_shape=1,\n                    value=0.0\n                )\n            else:\n                param_squared_sum = param_init_net.ConstantFill(\n                    [],\n                    str(param) + \"_avg_squared_sum\",\n                    shape=[shapes[str(param)][0]],\n                    value=0.0\n                )\n\n        else:\n            param_squared_sum = param_init_net.ConstantFill(\n                [param],\n                str(param) + \"_squared_sum\",\n                value=0.0\n            )\n\n        self._aux_params.local.append(param_squared_sum)\n\n        if self.rowWise:\n            assert isinstance(grad, core.GradientSlice),\\\n                'If SparseAdagrad with rowWise=True, gradient must be '\\\n                'a gradientslice. PLease ensure that rowWise is not enabled '\\\n                'for the dense Adagrad optimizer, as it is not supported.'\n        if isinstance(grad, core.GradientSlice):\n            assert self.decay == 1.,\\\n                'Decay is not implemented for SparseAdagrad and must be set to 1'\n            grad = self.dedup(net, self.sparse_dedup_aggregator, grad)\n            if self.rowWise:\n                op = 'RowWiseSparseAdagrad'\n            else:\n                op = 'SparseAdagrad'\n            net.__getattr__(op)(\n                [param, param_squared_sum, grad.indices, grad.values, lr],\n                [param, param_squared_sum],\n                epsilon=self.epsilon,\n                engine=self.engine\n            )\n        else:\n            net.Adagrad(\n                [param, param_squared_sum, grad, lr],\n                [param, param_squared_sum],\n                epsilon=self.epsilon,\n                decay=float(self.decay),\n                engine=self.engine\n            )\n\n    def scale_learning_rate(self, scale):\n        self.alpha *= scale\n        return\n\n\nclass FtrlOptimizer(Optimizer):\n    def __init__(self, alpha=0.01, beta=1e-4, lambda1=0, lambda2=0,\n                 sparse_dedup_aggregator=None, engine=''):\n        super(FtrlOptimizer, self).__init__()\n        self.alpha = alpha\n        self.beta = beta\n        self.lambda1 = lambda1\n        self.lambda2 = lambda2\n        self.sparse_dedup_aggregator = sparse_dedup_aggregator\n        self.engine = engine\n\n    def _run(self, net, param_init_net, param_info):\n        param = param_info.blob\n        grad = param_info.grad\n\n        if self.alpha <= 0:\n            return\n\n        nz = param_init_net.ConstantFill(\n            [param],\n            str(param) + \"_ftrl_nz\",\n            extra_shape=[2],\n            value=0.0\n        )\n        self._aux_params.local.append(nz)\n        if isinstance(grad, core.GradientSlice):\n            grad = self.dedup(net, self.sparse_dedup_aggregator, grad)\n            net.SparseFtrl(\n                [param, nz, grad.indices, grad.values],\n                [param, nz],\n                engine=self.engine,\n                alpha=self.alpha,\n                beta=self.beta,\n                lambda1=self.lambda1,\n                lambda2=self.lambda2\n            )\n        else:\n            net.Ftrl(\n                [param, nz, grad],\n                [param, nz],\n                engine=self.engine,\n                alpha=self.alpha,\n                beta=self.beta,\n                lambda1=self.lambda1,\n                lambda2=self.lambda2\n            )\n\n    def scale_learning_rate(self, scale):\n        self.alpha *= scale\n        return\n\n\nclass AdamOptimizer(Optimizer):\n    def __init__(self, alpha=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8,\n                 policy='fixed', sparse_dedup_aggregator=None, rowWise=False,\n                 engine='', **kwargs):\n        super(AdamOptimizer, self).__init__()\n        self.alpha = alpha\n        self.beta1 = beta1\n        self.beta2 = beta2\n        self.epsilon = epsilon\n        self.policy = policy\n        self.sparse_dedup_aggregator = sparse_dedup_aggregator\n        self.rowWise = rowWise\n        self.engine = engine\n        self.init_kwargs = kwargs\n\n    def _run(self, net, param_init_net, param_info):\n        param = param_info.blob\n        grad = param_info.grad\n\n        if self.alpha <= 0:\n            return\n\n        lr, iteration = self.build_lr(\n            net, param_init_net,\n            base_learning_rate=self.alpha,\n            policy=self.policy,\n            **(self.init_kwargs)\n        )\n\n        m1 = param_init_net.ConstantFill(\n            [param],\n            param + \"_first_moment\",\n            value=0.0\n        )\n        if self.rowWise:\n            shapes, types = workspace.InferShapesAndTypes([param_init_net])\n            m2 = param_init_net.ConstantFill(\n                [],\n                param + \"_avg_second_moment\",\n                shape=[shapes[param][0]],\n                value=0.0\n            )\n\n        else:\n\n            m2 = param_init_net.ConstantFill(\n                [param],\n                param + \"_second_moment\",\n                value=0.0\n            )\n\n        self._aux_params.shared.append(iteration)\n        self._aux_params.local.append(m1)\n        self._aux_params.local.append(m2)\n\n        if self.rowWise:\n            assert isinstance(grad, core.GradientSlice),\\\n                'If SparseAdam with rowWise=True, gradient must be '\\\n                'a gradientslice. PLease ensure that rowWise is not enabled '\\\n                'for the dense Adam optimizer, as it is not supported.'\n        if isinstance(grad, core.GradientSlice):\n            grad = self.dedup(net, self.sparse_dedup_aggregator, grad)\n            if self.rowWise:\n                op = 'RowWiseSparseAdam'\n            else:\n                op = 'SparseAdam'\n            net.__getattr__(op)(\n                [param, m1, m2, grad.indices, grad.values, lr, iteration],\n                [param, m1, m2],\n                beta1=self.beta1,\n                beta2=self.beta2,\n                epsilon=self.epsilon\n            )\n\n        else:\n            net.Adam(\n                [param, m1, m2, grad, lr, iteration],\n                [param, m1, m2],\n                beta1=self.beta1,\n                beta2=self.beta2,\n                epsilon=self.epsilon)\n\n    def scale_learning_rate(self, scale):\n        self.alpha *= scale\n        return\n\n\nclass YellowFinOptimizer(Optimizer):\n    \"\"\"YellowFin: An automatic tuner for momentum SGD\n\n    See https://arxiv.org/abs/1706.03471 for more details. This implementation\n    has separate learning rate and momentum per each parameter.\"\"\"\n\n    def __init__(self,\n                 alpha=0.1,\n                 mu=0.0,\n                 beta=0.999,\n                 curv_win_width=20,\n                 zero_debias=True,\n                 epsilon=0.1**6,\n                 policy='fixed',\n                 sparse_dedup_aggregator=None,\n                 **kwargs):\n        super(YellowFinOptimizer, self).__init__()\n        self.alpha = alpha\n        self.mu = mu\n        self.beta = beta\n        self.curv_win_width = curv_win_width\n        self.zero_debias = zero_debias\n        self.epsilon = epsilon\n        self.policy = policy\n        self.sparse_dedup_aggregator = sparse_dedup_aggregator\n        self.init_kwargs = kwargs\n\n    def _run(self, net, param_init_net, param_info):\n\n        # Note: This is number of persistent scalars in YellowFin optimizer.\n        #       It should always be the number of scalars being used. The same\n        #       number should be used in class for the operation.\n        SCALARS_MEMORY_SIZE = 5\n\n        param = param_info.blob\n        grad = param_info.grad\n        moment = param_init_net.ConstantFill(\n            [param],\n            param + \"_moment\",\n            value=0.0\n        )\n        curv_win = param_init_net.ConstantFill(\n            [],\n            param + \"_curv_win\",\n            shape=[self.curv_win_width],\n            value=0.0\n        )\n        g_avg = param_init_net.ConstantFill(\n            [param],\n            param + \"_g_avg\",\n            value=0.0\n        )\n        g2_avg = param_init_net.ConstantFill(\n            [param],\n            param + \"_g2_avg\",\n            value=0.0\n        )\n        lr_avg = param_init_net.ConstantFill(\n            [],\n            param + \"_lr_avg\",\n            shape=[1],\n            value=self.alpha\n        )\n        mu_avg = param_init_net.ConstantFill(\n            [],\n            param + \"_mu_avg\",\n            shape=[1],\n            value=self.mu\n        )\n        scalars_memory = param_init_net.ConstantFill(\n            [],\n            param + \"_scalars_memory\",\n            shape=[SCALARS_MEMORY_SIZE],\n            value=0.0\n        )\n\n        assert self.alpha > 0\n        assert not isinstance(grad, core.GradientSlice), \\\n            \"Doesn't support sparse gradients\"\n\n        if not param_init_net.BlobIsDefined(_OPTIMIZER_ITERATION_NAME):\n            # Add training operators.\n            with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n                iteration = param_init_net.ConstantFill(\n                    [],\n                    _OPTIMIZER_ITERATION_NAME,\n                    shape=[1],\n                    value=0,\n                    dtype=core.DataType.INT64)\n                iter_mutex = param_init_net.CreateMutex([],\n                                                        [\"iteration_mutex\"])\n                net.AtomicIter([iter_mutex, iteration], [iteration])\n        else:\n            iteration = param_init_net.GetBlobRef(_OPTIMIZER_ITERATION_NAME)\n\n        self._aux_params.shared.append(iteration)\n        self._aux_params.local.append(moment)\n        self._aux_params.local.append(lr_avg)\n        self._aux_params.local.append(mu_avg)\n        self._aux_params.local.append(curv_win)\n        self._aux_params.local.append(g_avg)\n        self._aux_params.local.append(g2_avg)\n        self._aux_params.local.append(scalars_memory)\n\n        yf_in_out_args = [\n            param,\n            moment,\n            lr_avg,\n            mu_avg,\n            curv_win,\n            g_avg,\n            g2_avg,\n            scalars_memory\n        ]\n\n        net.YellowFin(\n            yf_in_out_args + [grad, iteration],\n            yf_in_out_args,\n            beta=self.beta,\n            epsilon=self.epsilon,\n            curv_win_width=self.curv_win_width,\n            zero_debias=self.zero_debias)\n\n    def scale_learning_rate(self, scale):\n        self.alpha *= scale\n        return\n\n\nclass RmsPropOptimizer(Optimizer):\n    def __init__(\n        self,\n        alpha=0.01,\n        decay=0.9,\n        momentum=0.0,\n        epsilon=1e-5,\n        policy='fixed',\n        engine='',\n        **kwargs\n    ):\n        super(RmsPropOptimizer, self).__init__()\n        self.alpha = alpha\n        self.decay = decay\n        self.momentum = momentum\n        self.epsilon = epsilon\n        self.policy = policy\n        self.engine = engine\n        self.init_kwargs = kwargs\n\n    def _run(self, net, param_init_net, param_info):\n        param = param_info.blob\n        grad = param_info.grad\n\n        assert self.alpha > 0\n        assert not isinstance(grad, core.GradientSlice), \\\n            \"RmsPropOptimizer doesn't support sparse gradients\"\n\n        dev = scope.CurrentDeviceScope()\n        if dev is None:\n            dev = core.DeviceOption(caffe2_pb2.CPU)\n\n        ONE = param_init_net.ConstantFill(\n            [],\n            \"ONE_{}_{}\".format(dev.device_type, dev.cuda_gpu_id),\n            shape=[1],\n            value=1.0\n        )\n\n        lr, _ = self.build_lr(\n            net,\n            param_init_net,\n            base_learning_rate=-self.alpha,\n            policy=self.policy,\n            **(self.init_kwargs)\n        )\n\n        grad_o = param_init_net.ConstantFill(\n            [param],\n            str(param) + \"_grad_o\",\n            values=0.0,\n        )\n\n        ms = param_init_net.ConstantFill(\n            [param],\n            str(param) + \"_mean_squares\",\n            values=0.0,\n        )\n\n        mom = param_init_net.ConstantFill(\n            [param],\n            str(param) + \"_momentum\",\n            values=0.0,\n        )\n\n        self._aux_params.local.append(ms)\n        self._aux_params.local.append(mom)\n\n        net.RmsProp(\n            [grad, ms, mom, ONE],\n            [grad_o, ms, mom],\n            decay=self.decay,\n            momentum=self.momentum,\n            epsilon=self.epsilon,\n            engine=self.engine,\n        )\n\n        net.MomentumSGDUpdate(\n            [grad_o, mom, lr, param],\n            [grad_o, mom, param],\n        )\n\n    def scale_learning_rate(self, scale):\n        self.alpha *= scale\n        return\n\n\ndef _get_param_to_device(model):\n    # Infer blob devices by going through the net and param_init_net\n    # ops and observing the device used to create or use the blob.\n    param_to_device = core.InferBlobDevices(model.net)\n    param_to_device.update(core.InferBlobDevices(model.param_init_net))\n    return param_to_device\n\n\ndef get_param_device(param_name, grad, param_to_device=None, default_device=None):\n    device = default_device\n    param_to_device = param_to_device or {}\n    # We first check if parameter's device has been inferred. If not,\n    # we check the gradient. This can happen if parameter is not output\n    # by any blob but created by a FetchBlob.\n    if param_name in param_to_device:\n        device = param_to_device[param_name]\n    else:\n        if isinstance(grad, core.GradientSlice):\n            grad = grad\n            if str(grad.values) in param_to_device:\n                device = param_to_device[str(grad.values)]\n            elif str(grad.indices) in param_to_device:\n                device = param_to_device[str(grad.indices)]\n        else:\n            grad_name = str(grad)\n            if grad_name in param_to_device:\n                device = param_to_device[grad_name]\n\n    assert device is not None,\\\n        \"Cannot infer device for {}: no op creates it\".format(param_name)\n    return device\n\n\ndef get_lr_injection():\n    \"\"\"\n    Gets current value for lr_injection, a multiplier for all base\n    learning rates.\n    Must set allow_lr_injection=True when building optimizer, as it\n    relies on synchronization over CPU.\n    \"\"\"\n    return workspace.FetchBlob(_LEARNING_RATE_INJECTION)\n\n\ndef set_lr_injection(lr_injection_value):\n    \"\"\"\n    Sets lr_injection, a multiplier for all base learning rates.\n    Must set allow_lr_injection=True when building optimizer, as it\n    relies on synchronization over CPU.\n    \"\"\"\n    workspace.FeedBlob(\n        _LEARNING_RATE_INJECTION,\n        np.array(\n            [float(lr_injection_value)],\n            dtype=np.float32,\n        ),\n    )\n\n\ndef _calc_norm_ratio(\n    model, params, name_scope, param_to_device, max_gradient_norm\n):\n    with core.NameScope(name_scope):\n        grad_squared_sums = []\n        for i, param in enumerate(params):\n            device = get_param_device(\n                str(param.blob), param.grad, param_to_device\n            )\n\n            with core.DeviceScope(device):\n                grad = (\n                    param.grad\n                    if not isinstance(\n                        param.grad,\n                        core.GradientSlice,\n                    ) else param.grad.values\n                )\n\n                grad_squared_sum_name = 'grad_{}_squared_sum'.format(i)\n                grad_squared_sum = model.net.SumSqrElements(\n                    grad,\n                    grad_squared_sum_name,\n                )\n                grad_squared_sum_cpu = model.net.EnsureCPUOutput(\n                    grad_squared_sum\n                )\n                grad_squared_sums.append(grad_squared_sum_cpu)\n\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n            grad_squared_full_sum = model.net.Sum(\n                grad_squared_sums,\n                'grad_squared_full_sum',\n            )\n            global_norm = model.net.Pow(\n                grad_squared_full_sum,\n                'global_norm',\n                exponent=0.5,\n            )\n            clip_norm = model.param_init_net.ConstantFill(\n                [],\n                'clip_norm',\n                shape=[],\n                value=float(max_gradient_norm),\n            )\n            max_norm = model.net.Max(\n                [global_norm, clip_norm],\n                'max_norm',\n            )\n            norm_ratio = model.net.Div(\n                [clip_norm, max_norm],\n                'norm_ratio',\n            )\n            return norm_ratio\n\n\ndef _build(\n    model,\n    optimizer,\n    weights_only=False,\n    use_param_info_optim=True,\n    max_gradient_norm=None,\n    allow_lr_injection=False,\n):\n    param_to_device = _get_param_to_device(model)\n\n    # Validate there are no duplicate params\n    model.Validate()\n\n    params = []\n    for param_info in model.GetOptimizationParamInfo():\n        if weights_only and param_info.blob not in model.weights:\n            continue\n        params.append(param_info)\n\n    lr_multiplier = None\n    if max_gradient_norm is not None:\n        lr_multiplier = _calc_norm_ratio(\n            model,\n            params,\n            'norm_clipped_grad_update',\n            param_to_device,\n            max_gradient_norm,\n        )\n\n    if allow_lr_injection:\n        if not model.net.BlobIsDefined(_LEARNING_RATE_INJECTION):\n            lr_injection = model.param_init_net.ConstantFill(\n                [],\n                _LEARNING_RATE_INJECTION,\n                shape=[1],\n                value=1.0,\n            )\n        else:\n            lr_injection = _LEARNING_RATE_INJECTION\n\n        if lr_multiplier is None:\n            lr_multiplier = lr_injection\n        else:\n            lr_multiplier = model.net.Mul(\n                [lr_multiplier, lr_injection],\n                'lr_multiplier',\n                broadcast=1,\n            )\n    optimizer.add_lr_multiplier(lr_multiplier)\n\n    for param_info in params:\n        param_name = str(param_info.blob)\n\n        device = get_param_device(param_name, param_info.grad, param_to_device)\n\n        with core.DeviceScope(device):\n            if param_info.optimizer and use_param_info_optim:\n                param_info.optimizer(model.net, model.param_init_net, param_info)\n            else:\n                optimizer(model.net, model.param_init_net, param_info)\n    return optimizer\n\n\ndef add_weight_decay(model, weight_decay):\n    \"\"\"Adds a decay to weights in the model.\n\n    This is a form of L2 regularization.\n\n    Args:\n        weight_decay: strength of the regularization\n    \"\"\"\n    _build(\n        model,\n        WeightDecayBuilder(weight_decay=weight_decay),\n        weights_only=True,\n        use_param_info_optim=False,\n    )\n\n\ndef build_sgd(\n    model,\n    base_learning_rate,\n    max_gradient_norm=None,\n    allow_lr_injection=False,\n    **kwargs\n):\n    sgd_optimizer = SgdOptimizer(base_learning_rate, **kwargs)\n    return _build(\n        model,\n        sgd_optimizer,\n        max_gradient_norm=max_gradient_norm,\n        allow_lr_injection=allow_lr_injection,\n    )\n\n\ndef build_multi_precision_sgd(\n    model,\n    base_learning_rate,\n    max_gradient_norm=None,\n    allow_lr_injection=False,\n    **kwargs\n):\n    multi_prec_sgd_optimizer = MultiPrecisionSgdOptimizer(\n        base_learning_rate, **kwargs\n    )\n    return _build(\n        model,\n        multi_prec_sgd_optimizer,\n        max_gradient_norm=max_gradient_norm,\n        allow_lr_injection=allow_lr_injection,\n    )\n\n\ndef build_fp16_sgd(model, base_learning_rate, **kwargs):\n    fp16_sgd_optimizer = FP16SgdOptimizer(\n        base_learning_rate, **kwargs\n    )\n    return _build(model, fp16_sgd_optimizer)\n\n\ndef build_ftrl(model, engine=\"SIMD\", **kwargs):\n    if engine == \"SIMD\":\n        assert core.IsOperator('Ftrl_ENGINE_SIMD')\n        assert core.IsOperator('SparseFtrl_ENGINE_SIMD')\n    ftrl_optimizer = FtrlOptimizer(engine=engine, **kwargs)\n    return _build(model, ftrl_optimizer)\n\n\ndef build_adagrad(\n    model,\n    base_learning_rate,\n    parameters=None,\n    max_gradient_norm=None,\n    allow_lr_injection=False,\n    **kwargs\n):\n    adagrad_optimizer = AdagradOptimizer(alpha=base_learning_rate, **kwargs)\n    return _build(\n        model,\n        adagrad_optimizer,\n        max_gradient_norm=max_gradient_norm,\n        allow_lr_injection=allow_lr_injection,\n    )\n\n\ndef build_adam(\n    model,\n    base_learning_rate,\n    max_gradient_norm=None,\n    allow_lr_injection=False,\n    **kwargs\n):\n    adam_optimizer = AdamOptimizer(alpha=base_learning_rate, **kwargs)\n    return _build(\n        model,\n        adam_optimizer,\n        max_gradient_norm=max_gradient_norm,\n        allow_lr_injection=allow_lr_injection,\n    )\n\n\ndef build_yellowfin(model, base_learning_rate=0.1, **kwargs):\n    yellowfin_optimizer = YellowFinOptimizer(\n        alpha=base_learning_rate,\n        **kwargs)\n    return _build(model, yellowfin_optimizer)\n\n\ndef build_rms_prop(\n    model,\n    base_learning_rate,\n    max_gradient_norm=None,\n    allow_lr_injection=False,\n    **kwargs\n):\n    rms_prop_optimizer = RmsPropOptimizer(alpha=base_learning_rate, **kwargs)\n    return _build(\n        model,\n        rms_prop_optimizer,\n        max_gradient_norm=max_gradient_norm,\n        allow_lr_injection=allow_lr_injection,\n    )\n"
  },
  {
    "path": "caffe2/python/optimizer_context.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package optimizer_context\n# Module caffe2.python.optimizer_context\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import context\nfrom caffe2.python.modifier_context import (\n    ModifierContext, UseModifierBase)\n\n\nDEFAULT_OPTIM = 'DEFAULT'\n\n\n@context.define_context(allow_default=True)\nclass OptimizerContext(ModifierContext):\n    \"\"\"\n    provide context to allow param_info to have different optimizers\n    \"\"\"\n\n    def has_optimizer(self, name):\n        return self._has_modifier(name)\n\n    def get_optimizer(self, name):\n        assert self.has_optimizer(name), (\n            \"{} optimizer is not provided!\".format(name))\n        return self._get_modifier(name)\n\n\nclass UseOptimizer(UseModifierBase):\n    '''\n    context class to allow setting the current context.\n    Example usage with brew:\n        - with UseOptimizer(optim):\n            brew.func\n        - with UseOptimizer({'WEIGHT': weight_optim}):\n            brew.func\n        - with UseOptimizer({'DEFAULT': optim, 'BIAS': bias_optim,\n                                'WEIGHT': weight_optim}):\n            brew.func\n        - with UseOptimizer(optim1):\n            brew.func\n            with UseOptimizer(optim2):\n                brew.func\n\n    Example useage with layer:\n        optimizers = {'optim1': optim1, 'optim2': optim2}\n        with Optimizers(optimizers):\n            optim = OptimizerContext.current().get_optimizer('optim1')\n            layer(optim=optim)\n    '''\n    def _context_class(self):\n        return OptimizerContext\n"
  },
  {
    "path": "caffe2/python/optimizer_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom caffe2.proto import caffe2_pb2\nimport caffe2.python.optimizer as optimizer\nfrom caffe2.python.optimizer import (\n    build_sgd, build_multi_precision_sgd, build_ftrl, build_adagrad,\n    build_adam, build_yellowfin, build_rms_prop, add_weight_decay, SgdOptimizer)\nfrom caffe2.python.optimizer_context import UseOptimizer\nfrom caffe2.python.optimizer_test_util import (\n    OptimizerTestBase, LRModificationTestBase\n)\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\nimport numpy as np\nfrom numpy.testing import assert_allclose, assert_equal\nimport math\nimport unittest\n\n\nclass TestLars(OptimizerTestBase, TestCase):\n    def testSparse(self):\n        raise unittest.SkipTest(\"no sparse support\")\n\n    def build_optimizer(self, model, **kwargs):\n        self._skip_gpu = False\n        return build_sgd(model, base_learning_rate=0.1, lars=0.5, **kwargs)\n\n    def check_optimizer(self, optimizer):\n        self.assertTrue(optimizer.get_auxiliary_parameters().shared)\n        self.assertFalse(optimizer.get_auxiliary_parameters().local)\n        for param in optimizer.get_auxiliary_parameters().shared:\n            tensor = workspace.FetchBlob(param)\n            np.testing.assert_allclose(np.array([1.0]), tensor, atol=1e-5)\n\n\nclass TestMomentumSgd(OptimizerTestBase, TestCase):\n    def build_optimizer(self, model, **kwargs):\n        self._skip_gpu = False\n        return build_sgd(model, base_learning_rate=0.1, momentum=0.1, **kwargs)\n\n    def check_optimizer(self, optimizer):\n        self.assertTrue(optimizer.get_auxiliary_parameters().shared)\n        self.assertTrue(optimizer.get_auxiliary_parameters().local)\n        for param in optimizer.get_auxiliary_parameters().shared:\n            tensor = workspace.FetchBlob(param)\n            np.testing.assert_allclose(np.array([1.0]), tensor, atol=1e-5)\n\n\nclass TestSgd(OptimizerTestBase, LRModificationTestBase, TestCase):\n    def build_optimizer(self, model, **kwargs):\n        self._skip_gpu = False\n        return build_sgd(model, base_learning_rate=0.1, **kwargs)\n\n    def check_optimizer(self, optimizer):\n        self.assertTrue(optimizer.get_auxiliary_parameters().shared)\n        self.assertFalse(optimizer.get_auxiliary_parameters().local)\n        for param in optimizer.get_auxiliary_parameters().shared:\n            tensor = workspace.FetchBlob(param)\n            np.testing.assert_allclose(np.array([1.0]), tensor, atol=1e-5)\n\n\nclass TestMultiPrecisionSgd(\n    OptimizerTestBase, LRModificationTestBase, TestCase\n):\n    def build_optimizer(self, model, **kwargs):\n        self._skip_gpu = False\n        return build_multi_precision_sgd(\n            model, base_learning_rate=0.1, **kwargs\n        )\n\n    def check_optimizer(self, optimizer):\n        self.assertTrue(optimizer.get_auxiliary_parameters().shared)\n        self.assertFalse(optimizer.get_auxiliary_parameters().local)\n        for param in optimizer.get_auxiliary_parameters().shared:\n            tensor = workspace.FetchBlob(param)\n            np.testing.assert_allclose(np.array([1.0]), tensor, atol=1e-5)\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No GPU support\")\n    def testGPUDense(self):\n        super(TestMultiPrecisionSgd, self).testGPUDense(core.DataType.FLOAT16)\n\n\nclass TestFtrl(OptimizerTestBase, TestCase):\n    def build_optimizer(self, model, **kwargs):\n        self._skip_gpu = True\n        return build_ftrl(\n            model,\n            engine=None,\n            alpha=1.0,\n            beta=0.1,\n            lambda1=0.0,\n            lambda2=0.0,\n            **kwargs\n        )\n\n    def check_optimizer(self, optimizer):\n        self.assertFalse(optimizer.get_auxiliary_parameters().shared)\n        self.assertTrue(optimizer.get_auxiliary_parameters().local)\n        for param in optimizer.get_auxiliary_parameters().local:\n            workspace.FetchBlob(param)\n\n\nclass TestAdagrad(OptimizerTestBase, LRModificationTestBase, TestCase):\n    def build_optimizer(self, model, **kwargs):\n        self._skip_gpu = False\n        return build_adagrad(model, base_learning_rate=1.0, lars=0.5, **kwargs)\n\n    def check_optimizer(self, optimizer):\n        self.assertFalse(optimizer.get_auxiliary_parameters().shared)\n        self.assertTrue(optimizer.get_auxiliary_parameters().local)\n        for param in optimizer.get_auxiliary_parameters().local:\n            workspace.FetchBlob(param)\n\n\nclass TestAdam(OptimizerTestBase, LRModificationTestBase, TestCase):\n    def build_optimizer(self, model, **kwargs):\n        self._skip_gpu = False\n        return build_adam(model, base_learning_rate=0.1, **kwargs)\n\n    def check_optimizer(self, optimizer):\n        self.assertTrue(optimizer.get_auxiliary_parameters().shared)\n        self.assertTrue(optimizer.get_auxiliary_parameters().local)\n        self.assertTrue(workspace.HasBlob(\"optimizer_iteration\"))\n        iteration_tensor = workspace.FetchBlob(\"optimizer_iteration\")\n        np.testing.assert_allclose(np.array([2000]),\n                                   iteration_tensor,\n                                   atol=1e-5)\n        for param in optimizer.get_auxiliary_parameters().shared:\n            workspace.FetchBlob(param)\n        for param in optimizer.get_auxiliary_parameters().local:\n            workspace.FetchBlob(param)\n\n\nclass TestYellowFin(OptimizerTestBase, TestCase):\n    # YellowFin: An automatic tuner for momentum SGD\n    # (https://arxiv.org/abs/1706.03471)\n    def build_optimizer(self, model):\n        self._skip_gpu = False\n        return build_yellowfin(model, base_learning_rate=0.1)\n\n    def check_optimizer(self, optimizer):\n        self.assertTrue(optimizer.get_auxiliary_parameters().shared)\n        self.assertTrue(optimizer.get_auxiliary_parameters().local)\n        self.assertTrue(workspace.HasBlob(\"optimizer_iteration\"))\n        iteration_tensor = workspace.FetchBlob(\"optimizer_iteration\")\n        np.testing.assert_allclose(np.array([2000]),\n                                   iteration_tensor,\n                                   atol=1e-5)\n        for param in optimizer.get_auxiliary_parameters().shared:\n            workspace.FetchBlob(param)\n        for param in optimizer.get_auxiliary_parameters().local:\n            workspace.FetchBlob(param)\n\n    def testSparse(self):\n        raise unittest.SkipTest(\"no sparse support\")\n\n    def deb(self, val, beta, i, zero_debias):\n        if zero_debias:\n            return val / (1.0 - beta ** i)\n        else:\n            return val\n\n    def get_lr_mu(self, distance, grad_var, h_min, h_max):\n        # First tune based on dynamic range\n        if grad_var == 0:\n            dr = h_max / h_min\n            mu = ((np.sqrt(dr) - 1) / (np.sqrt(dr) + 1)) ** 2\n            lr_min = (1 + np.sqrt(mu)) ** 2 / h_max\n            return lr_min, mu\n\n        p = distance ** 2 * h_min ** 2 / 2 / grad_var\n        w3 = (-math.sqrt(p * p + 4.0 / 27.0 * p * p * p) - p) / 2.0\n        w = (1.0 if w3 > 0.0 else -1.0) * math.pow(math.fabs(w3), 1.0 / 3.0)\n        y = w - p / 3.0 / w\n        root = y + 1\n        root = min(root, 1.0 - 1e-6)\n        dr = h_max / h_min\n        mu = max(((np.sqrt(dr) - 1) / (np.sqrt(dr) + 1)) ** 2, root**2)\n        lr_min = (1 - np.sqrt(mu)) ** 2 / h_min\n        return lr_min, mu\n\n    def caffe2_yellowfin(self, zero_debias, grad_coef, n_dim, n_iter, gpu):\n        caffe2_res = {}\n\n        alpha = 1.0\n        mu = 0.0\n        beta = 0.999\n        curv_win_width = 20\n        epsilon = 1e-6\n\n        net = core.Net(\"net\")\n        param_init_net = core.Net(\"param_init_net\")\n        workspace.ResetWorkspace()\n\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n            iteration = param_init_net.ConstantFill(\n                [],\n                \"iteration\",\n                shape=[1],\n                value=0,\n                dtype=core.DataType.INT64)\n            iter_mutex = param_init_net.CreateMutex([], [\"iteration_mutex\"])\n            net.AtomicIter([iter_mutex, iteration], [iteration])\n        pre_grad = param_init_net.ConstantFill(\n            [],\n            \"pre_grad\",\n            shape=[n_dim],\n            value=grad_coef\n        )\n        if gpu:\n            iteration = net.CopyCPUToGPU(\n                [iteration],\n                \"iteration_cpu\"\n            )\n        iteration_float = net.Cast([iteration], \"iteration_float\")\n        grad = net.Mul([pre_grad, iteration_float], \"grad\", broadcast=True)\n        w = param_init_net.ConstantFill([], \"w\", shape=[n_dim], value=0.0)\n\n        # a hack to create an object with __dict__\n        param_info = lambda: None\n        param_info.blob = w\n        param_info.grad = grad\n\n        optimizer.YellowFinOptimizer(\n            alpha=alpha,\n            mu=mu,\n            beta=beta,\n            curv_win_width=curv_win_width,\n            epsilon=epsilon,\n            zero_debias=zero_debias\n        )._run(\n            net,\n            param_init_net,\n            param_info\n        )\n\n        workspace.RunNetOnce(param_init_net)\n        workspace.CreateNet(net, overwrite=True)\n        for i in range(n_iter):\n            workspace.RunNet(net)\n            scalars_memory_blob = workspace.FetchBlob(\"w_scalars_memory\")\n            g_norm2_avg = scalars_memory_blob[1]\n            g_norm2_min_avg = scalars_memory_blob[2]\n            g_norm2_max_avg = scalars_memory_blob[3]\n            distance_avg = scalars_memory_blob[4]\n            g_avg_blob = workspace.FetchBlob(\"w_g_avg\")\n            res_lr = workspace.FetchBlob(\"w_lr_avg\")[0]\n            res_mu = workspace.FetchBlob(\"w_mu_avg\")[0]\n            g_deb = self.deb(g_avg_blob, beta, i + 1, zero_debias)\n            variance = max(\n                self.deb(g_norm2_avg, beta, i + 1, zero_debias) -\n                g_deb.dot(g_deb),\n                epsilon\n            )\n            if i > 0:\n                caffe2_res[i] = {\n                    'h_max': np.exp(self.deb(g_norm2_max_avg,\n                                             beta,\n                                             i + 1,\n                                             zero_debias)),\n                    'h_min': np.exp(self.deb(g_norm2_min_avg,\n                                             beta,\n                                             i + 1,\n                                             zero_debias)),\n                    'var': variance,\n                    'dist': self.deb(distance_avg, beta, i + 1, zero_debias),\n                    'lr': res_lr,\n                    'mu': res_mu\n                }\n        return caffe2_res\n\n    def numpy_yellowfin(self, zero_debias, grad_coef, n_dim, n_iter, gpu):\n        numpy_res = {}\n\n        target_h_max = 0.0\n        target_h_min = 0.0\n        target_g_norm_squared_avg = 0.0\n        target_g_norm_avg = 0.0\n        target_g_avg = 0.0\n        target_dist_avg = 0.0\n        target_lr = 1.0\n        target_mu = 0.0\n\n        for i in range(n_iter):\n            grad_val = (i + 1) * grad_coef\n            target_g_norm_squared_avg = 0.999 * target_g_norm_squared_avg + \\\n                0.001 * np.sum((grad_val * np.ones([n_dim, ])) ** 2)\n            target_g_norm_avg = 0.999 * target_g_norm_avg + \\\n                0.001 * np.linalg.norm(grad_val * np.ones([n_dim, ]))\n            target_g_avg = 0.999 * target_g_avg + 0.001 * grad_val\n\n            target_h_max = 0.999 * target_h_max + \\\n                0.001 * np.log(grad_val ** 2 * n_dim)\n            target_h_min = 0.999 * target_h_min + \\\n                0.001 * np.log((max(1, i + 2 - 20) * grad_coef) ** 2 * n_dim)\n            if zero_debias:\n                target_var = target_g_norm_squared_avg / \\\n                    (1 - 0.999 ** (i + 1)) - \\\n                    target_g_avg ** 2 * n_dim / (1 - 0.999 ** (i + 1)) ** 2\n            else:\n                target_var = target_g_norm_squared_avg - \\\n                    target_g_avg ** 2 * n_dim\n            target_dist_avg = 0.999 * target_dist_avg + \\\n                0.001 * target_g_norm_avg / target_g_norm_squared_avg\n\n            if i > 0:\n                if zero_debias:\n                    lr, mu = self.get_lr_mu(\n                        target_dist_avg / (1.0 - 0.999 ** (i + 1)),\n                        target_var,\n                        np.exp(target_h_min / (1.0 - 0.999 ** (i + 1))),\n                        np.exp(target_h_max / (1.0 - 0.999 ** (i + 1))))\n                    target_lr = 0.999 * target_lr + 0.001 * lr\n                    target_mu = 0.999 * target_mu + 0.001 * mu\n                    numpy_res[i] = {\n                        'h_max': np.exp(target_h_max / (1 - 0.999 ** (i + 1))),\n                        'h_min': np.exp(target_h_min / (1 - 0.999 ** (i + 1))),\n                        'var': target_var,\n                        'dist': target_dist_avg / (1 - 0.999 ** (i + 1)),\n                        'lr': target_lr,\n                        'mu': target_mu\n                    }\n                else:\n                    lr, mu = self.get_lr_mu(\n                        target_dist_avg,\n                        target_var,\n                        np.exp(target_h_min),\n                        np.exp(target_h_max))\n                    target_lr = 0.999 * target_lr + 0.001 * lr\n                    target_mu = 0.999 * target_mu + 0.001 * mu\n                    numpy_res[i] = {\n                        'h_max': np.exp(target_h_max),\n                        'h_min': np.exp(target_h_min),\n                        'var': target_var,\n                        'dist': target_dist_avg,\n                        'lr': target_lr,\n                        'mu': target_mu\n                    }\n        return numpy_res\n\n    def compare_yellowfin_models(self,\n                                 model0,\n                                 model1,\n                                 zero_debias,\n                                 grad_coef,\n                                 n_dim,\n                                 n_iter,\n                                 gpu):\n        model0_res = model0(zero_debias, grad_coef, n_dim, n_iter, gpu)\n        model1_res = model1(zero_debias, grad_coef, n_dim, n_iter, gpu)\n        assert_equal(len(model0_res), len(model1_res))\n        for i in range(1, len(model0_res)):\n            assert_equal(model0_res[i].keys(), model1_res[i].keys())\n            for feat in model0_res[i].keys():\n                err_msg = \\\n                    'i=' + str(i) + ',\\n' + \\\n                    'feat=' + feat + ',\\n' + \\\n                    'grad_coef=' + str(grad_coef) + ',\\n' + \\\n                    'zero_debias=' + str(zero_debias)\n                assert_allclose(model0_res[i][feat],\n                                model1_res[i][feat],\n                                rtol=1e-2,\n                                err_msg=err_msg)\n\n    @unittest.skip(\"Results might vary too much. Only for individual use.\")\n    def test_caffe2_cpu_vs_numpy(self):\n        n_dim = 1000000\n        n_iter = 50\n        cpu_device_opt = core.DeviceOption(caffe2_pb2.CPU)\n        with core.DeviceScope(cpu_device_opt):\n            for zero_debias, grad_coef in [\n                (False, 1.0),\n                (False, 0.1),\n                (False, 0.01),\n                (True, 1.0)\n            ]:\n                self.compare_yellowfin_models(\n                    self.caffe2_yellowfin,\n                    self.numpy_yellowfin,\n                    zero_debias,\n                    grad_coef,\n                    n_dim,\n                    n_iter,\n                    gpu=False\n                )\n\n    @unittest.skip(\"Results might vary too much. Only for individual use.\")\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    def test_caffe2_gpu_vs_numpy(self):\n        n_dim = 1000000\n        n_iter = 50\n        gpu_device_opt = core.DeviceOption(caffe2_pb2.CUDA, 0)\n        with core.DeviceScope(gpu_device_opt):\n            for zero_debias in [False, True]:\n                for grad_coef in [1.0, 0.1, 0.01]:\n                    self.compare_yellowfin_models(\n                        self.caffe2_yellowfin,\n                        self.numpy_yellowfin,\n                        zero_debias,\n                        grad_coef,\n                        n_dim,\n                        n_iter,\n                        gpu=True\n                    )\n\n\nclass TestRmsProp(OptimizerTestBase, LRModificationTestBase, TestCase):\n    def build_optimizer(self, model, **kwargs):\n        self._skip_gpu = False\n        return build_rms_prop(\n            model, base_learning_rate=0.1, epsilon=0.1, **kwargs\n        )\n\n    def check_optimizer(self, optimizer):\n        self.assertFalse(optimizer.get_auxiliary_parameters().shared)\n        self.assertTrue(optimizer.get_auxiliary_parameters().local)\n        for param in optimizer.get_auxiliary_parameters().local:\n            workspace.FetchBlob(param)\n\n    def testSparse(self):\n        raise unittest.SkipTest(\"no sparse support\")\n\n\nclass TestMultiOptimizers(TestCase):\n    def test_multiple_optimizers(self):\n        from caffe2.python import brew, core, optimizer\n        from caffe2.python.model_helper import ModelHelper\n\n        model = ModelHelper(name=\"test\")\n        fc1 = brew.fc(model, 'data', 'fc1', 100, 50)\n        fc2 = brew.fc(model, fc1, 'fc2', 50, 25)\n        pred = brew.fc(model, fc2, 'fc3', 25, 10)\n        (softmax, loss) = model.SoftmaxWithLoss(\n            [pred, 'label'],\n            ['softmax', 'loss'],\n        )\n        model.AddGradientOperators([loss])\n\n        param_to_device = optimizer._get_param_to_device(model)\n\n        def infer_blob_device(blob_name):\n            return optimizer.get_param_device(\n                blob_name, \"{}_grad\".format(blob_name), param_to_device\n            )\n\n        sgd_1 = optimizer.SgdOptimizer(base_learning_rate=0.1)\n        sgd_2 = optimizer.SgdOptimizer(base_learning_rate=0.2)\n        adagrad = optimizer.AdagradOptimizer()\n\n        # Check same optimizer share the same learning rate.\n        with core.DeviceScope(infer_blob_device(\"fc1_w\")):\n            sgd_1(model.net, model.param_init_net, \"fc1_w\", \"fc1_w_grad\")\n        with core.DeviceScope(infer_blob_device(\"fc1_b\")):\n            sgd_1(model.net, model.param_init_net, \"fc1_b\", \"fc1_b_grad\")\n        fc1_lr_blobs = []\n        for op in model.net.Proto().op:\n            if op.type == 'WeightedSum' and op.input[0] == 'fc1_w' or \\\n                    op.input[0] == 'fc1_b':\n                fc1_lr_blobs.append(op.input[3])\n        self.assertEqual(fc1_lr_blobs[0], fc1_lr_blobs[1])\n\n        # Check different instance of the same optimizer has a different lr.\n        with core.DeviceScope(infer_blob_device(\"fc2_w\")):\n            sgd_2(model.net, model.param_init_net, \"fc2_w\", \"fc2_w_grad\")\n        with core.DeviceScope(infer_blob_device(\"fc2_b\")):\n            sgd_2(model.net, model.param_init_net, \"fc2_b\", \"fc2_b_grad\")\n        fc2_lr_blobs = []\n        for op in model.net.Proto().op:\n            if op.type == 'WeightedSum' and op.input[0] == 'fc2_w' or \\\n                    op.input[0] == 'fc2_b':\n                self.assertTrue(op.input[3] not in fc1_lr_blobs)\n                fc2_lr_blobs.append(op.input[3])\n        self.assertEqual(fc2_lr_blobs[0], fc2_lr_blobs[1])\n\n        # Check different optimizer type case\n        with core.DeviceScope(infer_blob_device(\"fc3_w\")):\n            adagrad(model.net, model.param_init_net, \"fc3_w\", \"fc3_w_grad\")\n        with core.DeviceScope(infer_blob_device(\"fc3_b\")):\n            adagrad(model.net, model.param_init_net, \"fc3_b\", \"fc3_b_grad\")\n        fc3_lr_blobs = []\n        for op in model.net.Proto().op:\n            if op.type == 'Adagrad' and op.input[0] == 'fc3_w' or \\\n                    op.input[0] == 'fc3_b':\n                self.assertTrue(op.input[3] not in fc2_lr_blobs)\n                self.assertTrue(op.input[3] not in fc1_lr_blobs)\n                fc3_lr_blobs.append(op.input[3])\n        self.assertEqual(fc3_lr_blobs[0], fc3_lr_blobs[1])\n\n\nclass TestWeightDecay(TestCase):\n\n    def test_weight_decay(self):\n        from caffe2.python import brew\n        from caffe2.python.model_helper import ModelHelper\n\n        model = ModelHelper(name=\"test\", arg_scope={'order': 'NCHW'})\n        cnv = brew.conv(model, 'data', 'cnv', 32, 32, 4)\n        a = brew.fc(model, cnv, 'a', 100, 200)\n        pred = brew.fc(model, a, 'b', 200, 5)\n        (softmax, loss) = model.SoftmaxWithLoss(\n            [pred, 'label'],\n            ['softmax', 'loss'],\n        )\n        model.AddGradientOperators([loss])\n\n        add_weight_decay(model, weight_decay=1e-4)\n        build_sgd(model, 0.11)\n\n        expected_weight_grad = {'b_w_grad', 'a_w_grad', 'cnv_w_grad'}\n\n        # Check the proto that all weights are decayed and not non-weights\n        # are decayed.\n        for op in model.net.Proto().op:\n            if op.type == 'WeightedSum' and 'wd_0_0' in op.input:\n                if op.output[0] not in expected_weight_grad:\n                    print(\n                        \"Unexpected param for weight_decay: {}\".\n                        format(op.output[0])\n                    )\n                self.assertTrue(op.output[0] in expected_weight_grad)\n                expected_weight_grad.remove(op.output[0])\n\n        self.assertEqual(\n            expected_weight_grad,\n            set(),\n            \"Not all weights were decayed: {}\".format(expected_weight_grad)\n        )\n\n\nclass TestOptimizerContext(TestCase):\n\n    def test_optimizer_context(self):\n        from caffe2.python import brew, optimizer\n        from caffe2.python.model_helper import ModelHelper\n\n        model = ModelHelper(name=\"test\", arg_scope={'order': 'NCHW'})\n        count = optimizer._optimizer_instance_count['SgdOptimizer']\n        cnv_optim = SgdOptimizer(0.15)\n        weight_optim = SgdOptimizer(0.2)\n        bias_optim = SgdOptimizer(0.1)\n\n        with UseOptimizer(cnv_optim):\n            cnv = brew.conv(model, 'data', 'cnv', 32, 32, 4)\n        with UseOptimizer({'WEIGHT': weight_optim, 'BIAS': bias_optim}):\n            a = brew.fc(model, cnv, 'a', 100, 200)\n        pred = brew.fc(model, a, 'b', 200, 5)\n        (softmax, loss) = model.SoftmaxWithLoss(\n            [pred, 'label'],\n            ['softmax', 'loss'],\n        )\n        model.AddGradientOperators([loss])\n\n        add_weight_decay(model, weight_decay=1e-4)\n        # use the following optimizer if none specified in param_info\n        build_sgd(model, 0.11)\n        expected_weight_grad = {'b_w_grad', 'a_w_grad', 'cnv_w_grad'}\n        expected_learning_rate = {\n            \"SgdOptimizer_{}_lr_cpu\".format(count): -0.15,\n            \"SgdOptimizer_{}_lr_cpu\".format(count + 1): -0.2,\n            \"SgdOptimizer_{}_lr_cpu\".format(count + 2): -0.1,\n            \"SgdOptimizer_{}_lr_cpu\".format(count + 3): -0.11\n        }\n\n        for op in model.net.Proto().op:\n            # Check the proto that all weights are decayed and not non-weights\n            # are decayed.\n            if op.type == 'WeightedSum' and 'wd_0_0' in op.input:\n                if op.output[0] not in expected_weight_grad:\n                    print(\n                        \"Unexpected param for weight_decay: {}\".\n                        format(op.output[0])\n                    )\n                self.assertTrue(op.output[0] in expected_weight_grad)\n                expected_weight_grad.remove(op.output[0])\n            # Check the learning rate for each parameter\n            if op.type == 'LearningRate':\n                val = 0\n                for arg in op.arg:\n                    if arg.name == 'base_lr':\n                        val = arg.f\n                self.assertAlmostEqual(\n                    val,\n                    expected_learning_rate[op.output[0]]\n                )\n\n        self.assertEqual(\n            expected_weight_grad,\n            set(),\n            \"Not all weights were decayed: {}\".format(expected_weight_grad)\n        )\n"
  },
  {
    "path": "caffe2/python/optimizer_test_util.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package optimizer_test_util\n# Module caffe2.python.optimizer_test_util\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport numpy as np\nfrom caffe2.python import brew, core, workspace, cnn, optimizer\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python.modeling.initializers import (\n    Initializer, PseudoFP16Initializer)\n\nfrom caffe2.python.model_helper import ModelHelper\n\n\nclass OptimizerTestBase(object):\n    \"\"\"\n    This is an abstract base class.\n    Don't inherit from unittest.TestCase, and don't name it 'Test*'.\n    Do, however, do these things in classes which inherit from this.\n    \"\"\"\n\n    def _createDense(self, dtype=core.DataType.FLOAT):\n        perfect_model = np.array([2, 6, 5, 0, 1]).astype(np.float32)\n        np.random.seed(123)  # make test deterministic\n        numpy_dtype = np.float32 if dtype == core.DataType.FLOAT else np.float16\n        initializer = Initializer if dtype == core.DataType.FLOAT else \\\n            PseudoFP16Initializer\n        data = np.random.randint(\n            2,\n            size=(20, perfect_model.size)).astype(numpy_dtype)\n        label = np.dot(data, perfect_model)[:, np.newaxis]\n\n        model = ModelHelper(name=\"test\", arg_scope={'order': 'NCHW'})\n        out = brew.fc(\n            model,\n            'data', 'fc', perfect_model.size, 1, ('ConstantFill', {}),\n            ('ConstantFill', {}), axis=0,\n            WeightInitializer=initializer, BiasInitializer=initializer\n        )\n        if dtype == core.DataType.FLOAT16:\n            out = model.HalfToFloat(out, out + \"_fp32\")\n        sq = model.SquaredL2Distance([out, 'label'])\n        loss = model.AveragedLoss(sq, \"avg_loss\")\n        grad_map = model.AddGradientOperators([loss])\n        self.assertIsInstance(grad_map['fc_w'], core.BlobReference)\n        return (model, perfect_model, data, label)\n\n    def testDense(self):\n        model, perfect_model, data, label = self._createDense()\n        optimizer = self.build_optimizer(model)\n\n        workspace.FeedBlob('data', data[0])\n        workspace.FeedBlob('label', label[0])\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.CreateNet(model.net, True)\n        for _ in range(2000):\n            idx = np.random.randint(data.shape[0])\n            workspace.FeedBlob('data', data[idx])\n            workspace.FeedBlob('label', label[idx])\n            workspace.RunNet(model.net.Proto().name)\n\n        np.testing.assert_allclose(\n            perfect_model[np.newaxis, :],\n            workspace.FetchBlob('fc_w'),\n            atol=1e-2\n        )\n        self.check_optimizer(optimizer)\n\n    @unittest.skipIf(not workspace.has_gpu_support, \"No gpu support\")\n    def testGPUDense(self, dtype=core.DataType.FLOAT):\n        device_opt = core.DeviceOption(caffe2_pb2.CUDA, 0)\n        with core.DeviceScope(device_opt):\n            model, _perfect_model, data, label = self._createDense(dtype)\n            if dtype == core.DataType.FLOAT16:\n                fc_fp32_for_host = model.HalfToFloat('fc', 'fc_fp32_for_host')\n                model.CopyGPUToCPU(fc_fp32_for_host, 'fc_cpu')\n            else:\n                model.CopyGPUToCPU('fc', 'fc_cpu')\n            workspace.FeedBlob('data', data[0])\n            workspace.FeedBlob('label', label[0])\n\n        # Add some CPU ops\n        brew.fc(model, 'fc_cpu', 'fc2', dim_in=1, dim_out=10, axis=0)\n\n        # Create optimizer in default device scope\n        self.build_optimizer(model)\n\n        if self._skip_gpu:\n            return\n\n        # Run net to see it does not crash\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.CreateNet(model.net, True)\n        workspace.RunNet(model.net.Proto().name)\n\n    def testSparse(self):\n        # to test duplicated indices we assign two indices to each weight and\n        # thus each weight might count once or twice\n        DUPLICATION = 2\n        perfect_model = np.array([2, 6, 5, 0, 1]).astype(np.float32)\n        np.random.seed(123)  # make test deterministic\n        data = np.random.randint(\n            2,\n            size=(20, perfect_model.size * DUPLICATION)).astype(np.float32)\n        label = np.dot(data, np.repeat(perfect_model, DUPLICATION))\n\n        model = cnn.CNNModelHelper(\"NCHW\", name=\"test\")\n        # imitate what model wrapper does\n        w = model.param_init_net.ConstantFill(\n            [], 'w', shape=[perfect_model.size], value=0.0)\n        model.params.append(w)\n        picked = model.net.Gather([w, 'indices'], 'gather')\n        out = model.ReduceFrontSum(picked, 'sum')\n\n        sq = model.SquaredL2Distance([out, 'label'])\n        loss = model.AveragedLoss(sq, \"avg_loss\")\n        grad_map = model.AddGradientOperators([loss])\n        self.assertIsInstance(grad_map['w'], core.GradientSlice)\n        optimizer = self.build_optimizer(model)\n\n        workspace.CreateBlob('indices')\n        workspace.CreateBlob('label')\n\n        for indices_type in [np.int32, np.int64]:\n            workspace.RunNetOnce(model.param_init_net)\n            workspace.CreateNet(model.net, True)\n            for _ in range(2000):\n                idx = np.random.randint(data.shape[0])\n                # transform into indices of binary features\n                indices = np.repeat(np.arange(perfect_model.size),\n                                    DUPLICATION)[data[idx] == 1]\n                if indices.size == 0:\n                    continue\n                workspace.FeedBlob(\n                    'indices',\n                    indices.reshape((indices.size,)).astype(indices_type)\n                )\n                workspace.FeedBlob('label',\n                                   np.array(label[idx]).astype(np.float32))\n                workspace.RunNet(model.net.Proto().name)\n\n            np.testing.assert_allclose(\n                perfect_model,\n                workspace.FetchBlob('w'),\n                atol=1e-2\n            )\n        self.check_optimizer(optimizer)\n\n\nclass LRModificationTestBase(object):\n    \"\"\"\n    This is an abstract base class.\n    Don't inherit from unittest.TestCase, and don't name it 'Test*'.\n    Do, however, do these things in classes which inherit from this.\n    \"\"\"\n\n    def _gradient_ratio_reference(self, model, params, max_gradient_norm):\n        from caffe2.python import core\n        sum_squared_norms = 0.0\n        for param in params:\n            grad = (\n                model.param_to_grad[param]\n                if not isinstance(\n                    model.param_to_grad[param],\n                    core.GradientSlice,\n                ) else model.param_to_grad[param].values\n            )\n            val = workspace.FetchBlob(grad)\n            sum_squared_norms += np.power(np.linalg.norm(val), 2.0)\n        global_norm = np.sqrt(sum_squared_norms)\n        clip_norm = max_gradient_norm\n        norm_ratio = clip_norm / np.maximum(clip_norm, global_norm)\n        return norm_ratio\n\n    def test_global_norm_based_gradient_clipping(self):\n        max_gradient_norm = 1.0\n        model, perfect_model, data, label = self._createDense()\n        opt = self.build_optimizer(model, max_gradient_norm=max_gradient_norm)\n\n        params = []\n        for param in model.GetParams(top_scope=True):\n            if param in model.param_to_grad:\n                if not isinstance(\n                    model.param_to_grad[param],\n                    core.GradientSlice,\n                ):\n                    params.append(param)\n\n        workspace.FeedBlob('data', data[0])\n        workspace.FeedBlob('label', label[0])\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.CreateNet(model.net, True)\n        self.assertIsNotNone(opt._lr_multiplier)\n\n        # Run net once\n        idx = np.random.randint(data.shape[0])\n        workspace.FeedBlob('data', data[idx])\n        workspace.FeedBlob('label', label[idx])\n        workspace.RunNet(model.net.Proto().name)\n\n        reference = self._gradient_ratio_reference(\n            model,\n            params,\n            max_gradient_norm,\n        )\n        norm_ratio = workspace.FetchBlob(\n            'norm_clipped_grad_update/norm_ratio')\n        np.testing.assert_almost_equal(norm_ratio, reference)\n        self.assertTrue(\n            reference < 1.0, \"Bad test, gradient not being scaled.\"\n        )\n\n    def test_lr_injection(self):\n        model, perfect_model, data, label = self._createDense()\n        opt = self.build_optimizer(\n            model, max_gradient_norm=1, allow_lr_injection=True\n        )\n\n        workspace.FeedBlob('data', data[0])\n        workspace.FeedBlob('label', label[0])\n        workspace.RunNetOnce(model.param_init_net)\n        workspace.CreateNet(model.net, True)\n\n        # Test LR injection initialized properly\n        self.assertIsNotNone(opt._lr_multiplier)\n        self.assertEqual(optimizer.get_lr_injection(), 1)\n\n        # Test that we're able to modify the value of the lr_injection\n        optimizer.set_lr_injection(0)\n        self.assertEqual(optimizer.get_lr_injection(), 0)\n\n        # Test that setting the lr_injector properly propogates to the\n        # lr_multiplier. Here, we have both lr_injector and norm_ratio that\n        # affect the lr_multiplier\n        workspace.RunNet(model.net.Proto().name)\n        self.assertEqual(workspace.FetchBlob('lr_multiplier'), 0)\n"
  },
  {
    "path": "caffe2/python/parallel_workers.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n# @package parallel_workers\n# Module caffe2.python.parallel_workers\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n\n'''\nThis module provides a python-land multithreaded mechanism for executing work.\n\nBasic usage is as follows:\n   coordinator = parallel_workers.init_workers(\n      my_worker_fun,\n      worker_name=\"train\"\n   )\n   ...\n   coordinator.start()\n\nFirst argument is the function to run in a loop on potentially multiple threads.\nIt has the call signature\n    worker_fun(worker_id)\n\nArgument 'worker_name' is used to distinguish different workers,\nsuch as workers processing train data or workers processing test data.\n\nOptionally, one can define an \"init function\" that is called once before\nthreads start, and has call signature:\n   my_init_fun(worker_coordinator, global_coordinator)\n\nNote that for data_parallel_models, init_workers will be called\nfor each GPU. Note that the 'coordinator' returned by the function is same\neach time.\n'''\n\nimport logging\nimport threading\nimport atexit\nimport time\nimport collections\nimport six\nimport traceback\n\nfrom abc import ABCMeta, abstractmethod\n\nlog = logging.getLogger(\"parallel_workers\")\nlog.setLevel(logging.INFO)\nLOG_INT_SECS = 60\n\n\ndef init_workers(\n    worker_fun,\n    num_worker_threads=2,\n    worker_name=\"train\",\n    init_fun=None,\n    external_loggers=None,\n    shutdown_fun=None,\n):\n    global global_coordinator\n\n    metrics = Metrics(external_loggers)\n\n    # Create coordinator object\n    coordinator = WorkerCoordinator(\n        worker_name, init_fun, shutdown_fun=shutdown_fun)\n\n    # Launch fetch worker threads\n    worker_ids = [\n        global_coordinator.get_new_worker_id()\n        for i in range(num_worker_threads)\n    ]\n    workers = [\n        threading.Thread(\n            target=run_worker,\n            name=\"parallel_workers worker id {}\".format(worker_id),\n            args=[coordinator,\n                  Worker(coordinator, worker_id, worker_fun, metrics)],\n        ) for worker_id in worker_ids\n    ]\n\n    coordinator._workers = workers\n    global_coordinator.add(coordinator)\n\n    return global_coordinator\n\n\nclass Metrics(object):\n    def __init__(self, external_loggers):\n        self._metrics = collections.defaultdict(lambda: 0)\n        self._external_loggers = external_loggers\n\n    def reset_metrics(self):\n        self._metrics = collections.defaultdict(lambda: 0)\n\n    def log_metrics(self):\n        if not self._external_loggers:\n            return\n        for logger in self._external_loggers:\n            try:\n                logger.log(self._metrics)\n            except Exception as e:\n                print(\"Failed to call ExternalLogger: {}\".format(e))\n\n    def put_metric(self, key, value, count=True):\n        self._metrics[key] += value\n        if count:\n            count_key = '{}_count'.format(key)\n            self._metrics[count_key] += 1\n\n\nclass State():\n    six.add_metaclass(ABCMeta)\n\n    @abstractmethod\n    def start(self):\n        pass\n\n    @abstractmethod\n    def stop(self):\n        pass\n\n    @abstractmethod\n    def cleanup(self):\n        pass\n\n\nclass WorkerCoordinator(object):\n    def __init__(self, worker_name, init_fun, state=None, shutdown_fun=None):\n        self._active = True\n        self._started = False\n        self._workers = []\n        self._worker_name = worker_name\n        self._init_fun = init_fun\n        self._state = state\n        self._shutdown_fun = shutdown_fun\n\n    def is_active(self):\n        return self._active\n\n    def init(self, global_coordinator):\n        if self._init_fun and not self._started:\n            data_coordinator = self\n            self._init_fun(data_coordinator, global_coordinator)\n\n    def _start(self):\n        if self._started:\n            return\n        self._active = True\n        self._started = True\n        if self._state:\n            self._state.start()\n\n        for w in self._workers:\n            w.daemon = True\n            w.start()\n\n    def _stop(self, reason=None):\n        self._active = False\n        if reason is not None:\n            log.error(\"Data input failed due to an error: {}\".format(reason))\n        if self._shutdown_fun and self._started:\n            self._shutdown_fun()\n        if self._state:\n            self._state.stop()\n\n        self._started = False\n\n    def _wait_finish(self, cleanup=None):\n        print(\"Wait for workers to die: {}\".format(self._worker_name))\n        for w in self._workers:\n            if w != threading.current_thread():\n                w.join(5.0)  # don't wait forever, thread may be blocked in i/o\n        success = True\n        for w in self._workers:\n            if w.isAlive():\n                print(\"Worker {} failed to close while waiting\".format(w))\n                success = False\n\n        # Release memory for the scratch blobs\n        if success and self._state:\n            self._state.cleanup()\n\n        print(\"All workers terminated: {}\".format(success))\n        return success\n\n\nclass GlobalWorkerCoordinator(object):\n    def __init__(self):\n        self._coordinators = []\n        self._fetcher_id_seq = 0\n        self._worker_ids = []\n        self.register_shutdown_handler()\n\n    def add(self, coordinator):\n        self._coordinators.append(coordinator)\n\n    def get_new_worker_id(self):\n        worker_id = self._fetcher_id_seq\n        self._worker_ids.append(worker_id)\n        self._fetcher_id_seq += 1\n        return worker_id\n\n    def get_worker_ids(self):\n        return self._worker_ids\n\n    def start(self):\n        # run init and start in separate for loop to\n        # ensure init happens serially before threads are spawn.\n        for c in self._coordinators:\n            c.init(self)\n        for c in self._coordinators:\n            c._start()\n\n    def stop(self):\n        all_success = True\n        for c in self._coordinators:\n            c._stop()\n        for c in self._coordinators:\n            success = c._wait_finish()\n            all_success = all_success and success\n        self._coordinators = []\n        return all_success\n\n    def stop_coordinator(self, worker_name):\n        '''\n        Stop a specific coordinator\n        '''\n        for c in self._coordinators:\n            if c._worker_name == worker_name:\n                c._stop()\n                c._wait_finish()\n        self._coordinators = [\n            c for c in self._coordinators\n            if c._worker_name != worker_name\n        ]\n\n    def register_shutdown_handler(self):\n        def cleanup():\n            self.stop()\n\n        atexit.register(cleanup)\n\n\nclass Worker(object):\n    def __init__(\n        self,\n        coordinator,\n        worker_id,\n        worker_fun=None,\n        metrics=None\n    ):\n        self._coordinator = coordinator\n        self._worker_id = worker_id\n        self._worker_fun = worker_fun\n        self._metrics = metrics\n\n    def start(self):\n        self._start_time = time.time()\n\n    def run(self):\n        self._worker_fun(self._worker_id)\n\n    def handle_exception(self, e):\n        traceback.print_exc()\n        logging.exception(\"Exception in worker\", e)\n        self._coordinator._stop(\"Exception in worker {}: {}\".format(\n            self._worker_id, e\n        ))\n\n    def finish(self):\n        self._metrics.put_metric(\n            'worker_time', time.time() - self._start_time)\n        self._metrics.log_metrics()\n\n\nglobal_coordinator = GlobalWorkerCoordinator()\n\n\ndef run_worker(coordinator, worker):\n    while coordinator.is_active():\n        worker.start()\n        try:\n            worker.run()\n        except Exception as e:\n            worker.handle_exception(e)\n        finally:\n            worker.finish()\n"
  },
  {
    "path": "caffe2/python/parallel_workers_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\n\nfrom caffe2.python import workspace, core\nimport caffe2.python.parallel_workers as parallel_workers\n\n\ndef create_queue():\n    queue = 'queue'\n\n    workspace.RunOperatorOnce(\n        core.CreateOperator(\n            \"CreateBlobsQueue\", [], [queue], num_blobs=1, capacity=1000\n        )\n    )\n\n    return queue\n\n\ndef create_worker(queue, get_blob_data):\n    def dummy_worker(worker_id):\n        blob = 'blob_' + str(worker_id)\n\n        workspace.FeedBlob(blob, get_blob_data(worker_id))\n\n        workspace.RunOperatorOnce(\n            core.CreateOperator(\n                'SafeEnqueueBlobs', [queue, blob], [blob, 'status_blob']\n            )\n        )\n\n    return dummy_worker\n\n\ndef dequeue_value(queue):\n    dequeue_blob = 'dequeue_blob'\n    workspace.RunOperatorOnce(\n        core.CreateOperator(\n            \"SafeDequeueBlobs\", [queue], [dequeue_blob, 'status_blob']\n        )\n    )\n\n    return workspace.FetchBlob(dequeue_blob)\n\n\nclass ParallelWorkersTest(unittest.TestCase):\n    def testParallelWorkers(self):\n        workspace.ResetWorkspace()\n\n        queue = create_queue()\n        dummy_worker = create_worker(queue, lambda worker_id: str(worker_id))\n        worker_coordinator = parallel_workers.init_workers(dummy_worker)\n        worker_coordinator.start()\n\n        for _ in range(10):\n            value = dequeue_value(queue)\n            self.assertTrue(\n                value in [b'0', b'1'], 'Got unexpected value ' + str(value)\n            )\n\n        self.assertTrue(worker_coordinator.stop())\n\n    def testParallelWorkersInitFun(self):\n        workspace.ResetWorkspace()\n\n        queue = create_queue()\n        dummy_worker = create_worker(\n            queue, lambda worker_id: workspace.FetchBlob('data')\n        )\n        workspace.FeedBlob('data', 'not initialized')\n\n        def init_fun(worker_coordinator, global_coordinator):\n            workspace.FeedBlob('data', 'initialized')\n\n        worker_coordinator = parallel_workers.init_workers(\n            dummy_worker, init_fun=init_fun\n        )\n        worker_coordinator.start()\n\n        for _ in range(10):\n            value = dequeue_value(queue)\n            self.assertEqual(\n                value, b'initialized', 'Got unexpected value ' + str(value)\n            )\n\n        self.assertTrue(worker_coordinator.stop())\n\n    def testParallelWorkersShutdownFun(self):\n        workspace.ResetWorkspace()\n\n        queue = create_queue()\n        dummy_worker = create_worker(queue, lambda worker_id: str(worker_id))\n        workspace.FeedBlob('data', 'not shutdown')\n\n        def shutdown_fun():\n            workspace.FeedBlob('data', 'shutdown')\n\n        worker_coordinator = parallel_workers.init_workers(\n            dummy_worker, shutdown_fun=shutdown_fun\n        )\n        worker_coordinator.start()\n\n        self.assertTrue(worker_coordinator.stop())\n\n        data = workspace.FetchBlob('data')\n        self.assertEqual(data, b'shutdown', 'Got unexpected value ' + str(data))\n"
  },
  {
    "path": "caffe2/python/parallelize_bmuf_distributed_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nfrom multiprocessing import Process, Manager\n\nimport numpy as np\nimport unittest\nimport tempfile\nimport shutil\nimport logging\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nlog = logging.getLogger(\"parallelize_bmuf_distributed_test\")\nlog.setLevel(logging.INFO)\n\n\ndef bmuf_process(filestore_dir, process_id, shared_results,\n                 cpu_device=False, nesterov=False):\n    # We need to import caffe2 in every process to initialize CUDA independently.\n    from caffe2.python import core, cnn, data_parallel_model, dyndep, workspace\n    from caffe2.proto import caffe2_pb2\n    dyndep.InitOpsLibrary(\"@/caffe2/caffe2/distributed:file_store_handler_ops\")\n\n    if not cpu_device:\n        if not workspace.has_gpu_support:\n            log.info('No GPU support test is Ignored.')\n            return\n        if workspace.NumCudaDevices() < 4:\n            log.info('Not enough GPU support, test IGNORED')\n            return\n\n    model = cnn.CNNModelHelper(\n        order=\"NHWC\",\n        name=\"test\"\n    )\n    if not cpu_device:\n        device_type = caffe2_pb2.CUDA\n        device_prefix = \"gpu\"\n    else:\n        device_type = caffe2_pb2.CPU\n        device_prefix = \"cpu\"\n\n    devices = [0, 1] if process_id == 0 else [2, 3]\n\n    def _model_build_fun(model, loss_scale):\n        fc = model.FC(\n            \"data\", \"fc\", 16, 1, (\"ConstantFill\", {}), (\"ConstantFill\", {})\n        )\n        fc_fl = model.FlattenToVec(fc, \"fc_fl\")\n        sigm = model.Sigmoid(fc_fl, \"sigm\")\n        sq = model.SquaredL2Distance([sigm, \"label\"], \"sq\")\n        loss = model.AveragedLoss(sq, \"loss\")\n        loss = model.Scale(loss, scale=loss_scale)\n\n        # For testing explicit sync\n        model.param_init_net.UniformFill([], [\"sync_num\"], shape=[1])\n        return [loss]\n\n    def _input_builder_fun(model):\n        return None\n\n    def _param_update_fun(model):\n        ITER = model.Iter(\"ITER\")\n        LR = model.net.LearningRate(\n            [ITER],\n            \"LR\",\n            base_lr=(-0.1),\n            policy=\"fixed\",\n        )\n        ONE = model.param_init_net.ConstantFill(\n            [], \"ONE\", shape=[1], value=1.0,\n        )\n        for param in model.GetParams():\n            grad = model.param_to_grad[param]\n            model.WeightedSum([param, ONE, grad, LR], param)\n\n    def _generate_data(devices, process_id, device_type, device_prefix):\n        np.random.seed(26 + process_id * 10)\n        # Each run has same input, independent of number of gpus\n        batch_size = 64\n        for _ in range(0, 10):\n            full_data = np.random.rand(batch_size, 16)\n            full_labels = np.round(full_data[:, 0])\n            batch_per_device = batch_size // len(devices)\n\n            for (j, g) in enumerate(devices):\n                st = j * batch_per_device\n                en = st + batch_per_device\n                data = full_data[st:en, :].astype(np.float32)\n                labels = full_labels[st:en].astype(np.float32)\n                with core.DeviceScope(core.DeviceOption(device_type, g)):\n                    workspace.FeedBlob(\"{}_{}/data\".format(device_prefix, g), data)\n                    workspace.FeedBlob(\"{}_{}/label\".format(device_prefix, g), labels)\n\n    _generate_data(devices, process_id, device_type, device_prefix)\n\n    workspace.RunOperatorOnce(\n        core.CreateOperator(\n            \"FileStoreHandlerCreate\", [], [\"store_handler\"],\n            path=filestore_dir\n        )\n    )\n    rendezvous = dict(\n        kv_handler=\"store_handler\",\n        shard_id=process_id,\n        num_shards=2,\n        engine=\"GLOO\",\n        exit_nets=None\n    )\n\n    data_parallel_model.Parallelize_BMUF(\n        model,\n        _input_builder_fun,\n        _model_build_fun,\n        _param_update_fun,\n        devices=devices,\n        rendezvous=rendezvous,\n        nesterov=nesterov,\n        add_blobs_to_sync=[\"sync_num\"],\n        cpu_device=cpu_device\n    )\n\n    data_parallel_model.RunInitNet(model)\n\n    def _device_pid(device, pid):\n        if pid == 1:\n            return device + 2\n        return device\n\n    np.testing.assert_equal(\n        workspace.FetchBlob(\"{}_{}/fc_w_v\".format(\n            device_prefix, _device_pid(0, process_id))),\n        np.zeros(16).astype(np.float32).reshape(1, 16)\n    )\n\n    # Run the algorithm for one iteration to have non-zero params.\n    data_parallel_model.RunNet(model, 1)\n\n    # Save iteration momentum and post local update params\n    results = {}\n    v_b_ = workspace.FetchBlob(\n        \"{}_{}/fc_b_v\".format(device_prefix, _device_pid(0, process_id)))\n    v_w_ = workspace.FetchBlob(\n        \"{}_{}/fc_w_v\".format(device_prefix, _device_pid(0, process_id)))\n\n    results['v_b_'] = v_b_\n    results['v_w_'] = v_w_\n\n    workspace.RunNetOnce(model.net)\n\n    b_0_ = workspace.FetchBlob(\n        \"{}_{}/fc_b\".format(device_prefix, _device_pid(0, process_id)))\n    w_0_ = workspace.FetchBlob(\n        \"{}_{}/fc_w\".format(device_prefix, _device_pid(0, process_id)))\n    b_1_ = workspace.FetchBlob(\n        \"{}_{}/fc_b\".format(device_prefix, _device_pid(1, process_id)))\n    w_1_ = workspace.FetchBlob(\n        \"{}_{}/fc_w\".format(device_prefix, _device_pid(1, process_id)))\n\n    results['b_0_'] = b_0_\n    results['w_0_'] = w_0_\n    results['b_1_'] = b_1_\n    results['w_1_'] = w_1_\n\n    # Test sync\n    if process_id == 0:\n        workspace.FeedBlob(\n            device_prefix + \"_0/sync_num\",\n            np.array([2603]).astype(np.float32),\n            device_option=core.DeviceOption(device_type, 0))\n\n    # Compute block gradients.\n    b_g_ = workspace.FetchBlob(\n        \"{}_{}/fc_b_g\".format(device_prefix, _device_pid(0, process_id)))\n    w_g_ = workspace.FetchBlob(\n        \"{}_{}/fc_w_g\".format(device_prefix, _device_pid(0, process_id)))\n    results['b_g_'] = b_g_\n    results['w_g_'] = w_g_\n    workspace.RunNetOnce(model._global_model_param_updates_net)\n\n    #  g_b = (b_0_ + b_1_) / 2 - b_g_\n    #  g_w = (w_0_ + w_1_) / 2 - w_g_\n    v_b = workspace.FetchBlob(\n        \"{}_{}/fc_b_v\".format(device_prefix, _device_pid(0, process_id)))\n    v_w = workspace.FetchBlob(\n        \"{}_{}/fc_w_v\".format(device_prefix, _device_pid(0, process_id)))\n    w_g = workspace.FetchBlob(\n        \"{}_{}/fc_w_g\".format(device_prefix, _device_pid(0, process_id)))\n    b_g = workspace.FetchBlob(\n        \"{}_{}/fc_b_g\".format(device_prefix, _device_pid(0, process_id)))\n    w_0 = workspace.FetchBlob(\n        \"{}_{}/fc_w\".format(device_prefix, _device_pid(0, process_id)))\n    b_0 = workspace.FetchBlob(\n        \"{}_{}/fc_b\".format(device_prefix, _device_pid(0, process_id)))\n    w_1 = workspace.FetchBlob(\n        \"{}_{}/fc_w\".format(device_prefix, _device_pid(1, process_id)))\n    b_1 = workspace.FetchBlob(\n        \"{}_{}/fc_b\".format(device_prefix, _device_pid(1, process_id)))\n    results['v_b'] = v_b\n    results['v_w'] = v_w\n    results['w_g'] = w_g\n    results['b_g'] = b_g\n    results['w_0'] = w_0\n    results['b_0'] = b_0\n    results['w_1'] = w_1\n    results['b_1'] = b_1\n\n    # Test add_blobs_to_sync\n    for j in devices:\n        sync = workspace.FetchBlob(\n            device_prefix + \"_{}/sync_num\".format(j))[0]\n        results['sync_{}'.format(j)] = sync\n\n    shared_results[process_id] = results\n\n\nclass DistributedTest(unittest.TestCase):\n\n    @given(\n        cpu_device=st.booleans(),\n        nesterov=st.booleans()\n    )\n    def test_bmuf_distributed(self, cpu_device, nesterov):\n        self._test_bmuf_distributed(cpu_device=cpu_device, nesterov=nesterov)\n\n    def _test_bmuf_distributed(self, cpu_device=False, nesterov=False):\n        processes = []\n        filestore_dir = tempfile.mkdtemp()\n        results = Manager().dict()\n        for idx in range(0, 2):\n            process = Process(\n                target=bmuf_process,\n                args=(filestore_dir, idx, results, cpu_device, nesterov)\n            )\n            processes.append(process)\n            process.start()\n\n        while len(processes) > 0:\n            process = processes.pop()\n            process.join()\n        shutil.rmtree(filestore_dir)\n\n        if len(results) == 0:\n            return\n\n        w_0 = results[0]['w_0']\n        w_1 = results[0]['w_1']\n        b_0 = results[0]['b_0']\n        b_1 = results[0]['b_1']\n        # Check parameters are in sync.\n        np.testing.assert_equal(w_0, w_1)\n        np.testing.assert_equal(w_0, results[1]['w_0'])\n        np.testing.assert_equal(w_0, results[1]['w_1'])\n        np.testing.assert_equal(b_0, b_1)\n        np.testing.assert_equal(b_0, results[1]['b_0'])\n        np.testing.assert_equal(b_0, results[1]['b_1'])\n\n        w_g_ = results[0]['w_g_']\n        b_g_ = results[0]['b_g_']\n\n        g_b = (results[0]['b_0_'] + results[1]['b_0_'] + results[0]['b_1_'] +\n               results[1]['b_1_']) / 4 - b_g_\n        g_w = (results[0]['w_0_'] + results[1]['w_0_'] + results[0]['w_1_'] +\n               results[1]['w_1_']) / 4 - w_g_\n        v_b_ = results[0]['v_b_']\n        v_b = results[0]['v_b']\n        v_w_ = results[0]['v_w_']\n        v_w = results[0]['v_w']\n\n        for pid in results.keys():\n            for k in results[pid].keys():\n                if k.startswith(\"sync_num\"):\n                    self.assertEqual(2603, results[pid][k])\n\n        # Check block gradients are correct.\n        np.testing.assert_almost_equal(v_b, 0.75 * v_b_ + g_b)\n        np.testing.assert_almost_equal(v_w, 0.75 * v_w_ + g_w)\n\n        # Check params update step\n        if nesterov:\n            np.testing.assert_equal(w_0, w_g_ + v_w - 0.75 * (v_w - v_w_))\n            np.testing.assert_equal(b_0, b_g_ + v_b - 0.75 * (v_b - v_b_))\n        else:\n            np.testing.assert_equal(w_0, w_g_ + v_w)\n            np.testing.assert_equal(b_0, b_g_ + v_b)\n"
  },
  {
    "path": "caffe2/python/pipeline.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package pipeline\n# Module caffe2.python.pipeline\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, queue_util\nfrom caffe2.python.dataio import Reader, Writer\nfrom caffe2.python.net_builder import NetBuilder, ops\nfrom caffe2.python.schema import as_record, Field\nfrom caffe2.python.task import Node, Task, TaskGroup\n\n\nclass Output(object):\n    \"\"\"\n    Represents the result of a processor function. A processor can either\n    return an Output, or it can return a record, in which case an Output will be\n    created for it afterwards.\n    \"\"\"\n    def __init__(self, nets=None, record=None, should_stop=None):\n        builder_children = NetBuilder.current().get()\n        assert nets is None or len(builder_children) == 0, (\n            'Cannot both use `ops` syntax and return a list of nets.')\n        if nets is None:\n            nets = builder_children\n        if isinstance(nets, core.Net):\n            nets = [nets]\n        self.nets = [] if nets is None else list(nets)\n        self.record = None if record is None else as_record(record)\n        self.should_stop = should_stop\n\n\nDEFAULT_QUEUE_CAPACITY = 100\n\n\ndef _init_output(output, capacity, global_init_net, global_exit_net):\n    if output is None:\n        out_queue = queue_util.Queue(\n            capacity=(\n                capacity if capacity is not None\n                else DEFAULT_QUEUE_CAPACITY))\n        writer = out_queue.writer()\n    elif isinstance(output, Writer):\n        assert capacity is None, 'capacity would not be used.'\n        out_queue = None\n        writer = output\n    elif hasattr(output, 'writer'):\n        assert capacity is None, 'capacity would not be used.'\n        out_queue = output\n        writer = output.writer()\n    else:\n        raise ValueError('output must be a reader, queue or stream.')\n    writer.setup_ex(global_init_net, global_exit_net)\n    return out_queue, writer\n\n\ndef make_processor(processor):\n    if processor is None:\n        return lambda rec: rec\n    elif isinstance(processor, core.Net):\n        return NetProcessor(processor)\n    else:\n        return processor\n\n\ndef normalize_processor_output(output):\n    \"\"\"\n    Allow for processors to return results in several formats.\n    TODO(azzolini): simplify once all processors use NetBuilder API.\n    \"\"\"\n    if isinstance(output, Output):\n        \"\"\" Processor returned an Output. \"\"\"\n        return output\n    elif isinstance(output, Field):\n        \"\"\" Processor returned a record. \"\"\"\n        return Output(record=output)\n    elif isinstance(output, tuple):\n        is_record_and_blob = (\n            len(output) == 2 and\n            isinstance(output[0], Field) and\n            isinstance(output[1], core.BlobReference))\n        if is_record_and_blob:\n            \"\"\" Processor returned (record, stop_blob) \"\"\"\n            return Output(None, *output)\n        else:\n            \"\"\" Processor returned (nets, record, stop_blob) \"\"\"\n            return Output(*output)\n    else:\n        \"\"\" Processor returned nets, no output \"\"\"\n        return Output(output)\n\n\ndef pipe(\n        input, output=None, num_threads=1, processor=None, name=None,\n        capacity=None, group=None, num_runtime_threads=1):\n    \"\"\"\n    Given a Reader, Queue or DataStream in `input`, and optionally, a Writer,\n    Queue or DataStream in `output`, creates a Task that, when run, will\n    pipe the input into the output, using multiple parallel threads.\n    Additionally, if a processor is given, it will be called between reading\n    and writing steps, allowing it to transform the record.\n\n    Args:\n        input:       either a Reader, Queue or DataStream that will be read\n                     until a stop is signaled either by the reader or the\n                     writer.\n        output:      either a Writer, a Queue or a DataStream that will be\n                     writen to as long as neither reader nor writer signal\n                     a stop condition. If output is not provided or is None,\n                     a Queue is created with given `capacity` and writen to.\n        num_threads: number of concurrent threads used for processing and\n                     piping. If set to 0, no Task is created, and a\n                     reader is returned instead -- the reader returned will\n                     read from the reader passed in and process it.\n                     ** DEPRECATED **. Use `num_runtime_threads` instead.\n                     This option will be removed once all readers/processors\n                     support `num_runtime_threads`.\n        processor:   (optional) function that takes an input record and\n                     optionally returns a record; this will be called\n                     between read and write steps. If the processor does\n                     not return a record, a writer will not be instantiated.\n                     Processor can also be a core.Net with input and output\n                     records properly set. In that case, a NetProcessor is\n                     instantiated, cloning the net for each of the threads.\n        name:        (optional) name of the task to be created.\n        capacity:    when output is not passed, a queue of given `capacity`\n                     is created and written to.\n        group:       (optional) explicitly add the created Task to this\n                     TaskGroup, instead of using the currently active one.\n        num_runtime_threads: Similar to `num_threads`, but instead of expanding\n                     the tasks with a `for` loop in python, does that at\n                     runtime. This is preferable to `num_threads`, but some\n                     processors/readers still require to be called multiple\n                     times in python.\n\n    Returns:\n        Output Queue, DataStream, Reader, or None, depending on the parameters\n        passed.\n    \"\"\"\n    result, _ = _pipe_step(\n        input, output, num_threads, processor, name, capacity, group,\n        num_runtime_threads)\n    return result\n\n\ndef pipe_and_output(\n        input, output=None, num_threads=1, processor=None, name=None,\n        capacity=None, group=None, num_runtime_threads=1, final_outputs=None):\n    \"\"\"\n    Similar to `pipe`, with the additional ability for the pipe Task to\n    return output values to the `Session` once done.\n\n    Returns:\n        Tuple (out_queue, *task_outputs)\n            out_queue:    same as return value of `pipe`.\n            task_outputs: TaskOutput object, fetchable from the client after\n                          session.run() returns.\n    \"\"\"\n    assert num_threads > 0\n    result, task = _pipe_step(\n        input, output, num_threads, processor, name, capacity, group,\n        num_runtime_threads, final_outputs)\n    output = None\n    if final_outputs is not None:\n        output = task.outputs()\n        if type(final_outputs) not in (list, tuple):\n            output = output[0]\n    return result, output\n\n\ndef processor_name(processor):\n    if hasattr(processor, 'name'):\n        return processor.name\n    if hasattr(processor, 'func_name'):\n        if processor.func_name == '<lambda>':\n            return processor.__module__\n        if hasattr(processor, 'im_class'):\n            return '%s.%s' % (processor.im_class.__name__, processor.func_name)\n        return processor.func_name\n    return processor.__class__.__name__\n\n\ndef _runtime_threads_task(name, group, final_outputs, reader, num_threads,\n                          output, capacity):\n    node_name = str(Node.current())\n    profiler_name = \"{0}/{1}/{2}/{3}/{4}\".format(\n        node_name,\n        \"pipe\",\n        name,\n        processor_name(input) if input else \"NoInput\",\n        processor_name(output) if output else \"NoOutput\")\n\n    with Task(name=name, group=group, outputs=final_outputs,\n              num_instances=num_threads) as task:\n        global_exit_net = core.Net('pipe:exit')\n        global_init_net = core.Net('pipe:init')\n        reader.setup_ex(global_init_net, global_exit_net)\n\n        init_net = core.Net('pipe:instance:init')\n        exit_net = core.Net('pipe:instance:exit')\n        read_nets, status, rec = reader.read_record_ex(init_net, exit_net)\n        init_net.ConstantFill(\n            [], [status],\n            shape=[],\n            value=False,\n            dtype=core.DataType.BOOL\n        )\n\n        if rec is not None:\n            out_queue, writer = _init_output(\n                output, capacity, global_init_net, global_exit_net)\n            write_nets, _ = writer.write_record_ex(\n                rec, init_net, exit_net, status)\n        else:\n            out_queue = None\n            write_nets = []\n\n        with ops.task_init():\n            ops.net(global_init_net)\n        with ops.task_instance_init():\n            ops.net(init_net)\n\n        timer_start_net = core.Net('timer_start')\n        timer = timer_start_net.TimerBegin([], counter_name=profiler_name)\n        timer_end_net = core.Net('timer_end')\n        timer_end_net.TimerEnd(timer, [])\n\n        ops.net(core.execution_step(\n            'body',\n            [timer_start_net] + list(read_nets) + list(write_nets) +\n            [timer_end_net],\n            should_stop_blob=status))\n        ops.net(timer_end_net)\n\n        with ops.task_instance_exit():\n            ops.net(exit_net)\n        with ops.task_exit():\n            ops.net(global_exit_net)\n\n    return out_queue, task\n\n\ndef _static_threads_task(name, group, final_outputs, reader, num_threads,\n                         output, capacity):\n    node_name = str(Node.current())\n    profiler_name = \"{0}/{1}/{2}/{3}/{4}\".format(\n        node_name,\n        \"pipe\",\n        name,\n        processor_name(input) if input else \"NoInput\",\n        processor_name(output) if output else \"NoOutput\")\n\n    with Task(name=name, group=group, outputs=final_outputs) as task:\n        global_exit_net = core.Net('exit')\n        global_init_net = core.Net('init')\n        reader.setup_ex(global_init_net, global_exit_net)\n\n        out_queue = None\n        writer = None\n\n        steps = []\n        for thread_id in range(num_threads):\n            with NetBuilder(name='t:%d' % thread_id) as nb:\n                init_net = core.Net('init')\n                exit_net = core.Net('exit')\n                read_nets, status, rec = reader.read_record_ex(\n                    init_net, exit_net)\n                init_net.ConstantFill(\n                    [], [status],\n                    shape=[],\n                    value=False,\n                    dtype=core.DataType.BOOL\n                )\n\n                if rec is not None:\n                    if writer is None:\n                        # hack so that the out queue gets the right name prefix\n                        # (otherwise they would be prefixed with the thread id)\n                        with NetBuilder(_fullname=task.name):\n                            out_queue, writer = _init_output(\n                                output, capacity, global_init_net,\n                                global_exit_net)\n                    write_nets, _ = writer.write_record_ex(\n                        rec, init_net, exit_net, status)\n                else:\n                    write_nets = []\n\n                timer_start_net = core.Net('timer_start')\n                timer = timer_start_net.TimerBegin([], counter_name=profiler_name)\n                timer_end_net = core.Net('timer_end')\n                timer_end_net.TimerEnd(timer, [])\n\n                ops.net(init_net)\n                ops.net(core.execution_step(\n                    'body',\n                    [timer_start_net] + list(read_nets) + list(write_nets) +\n                    [timer_end_net],\n                    should_stop_blob=status))\n                ops.net(timer_end_net)\n                ops.net(exit_net)\n            steps.append(core.to_execution_step(nb))\n        ops.net(global_init_net)\n        ops.net(core.execution_step('body', steps, concurrent_substeps=True))\n        ops.net(global_exit_net)\n    return out_queue, task\n\n\ndef _pipe_step(\n        input, output=None, num_threads=1, processor=None, name=None,\n        capacity=None, group=None, num_runtime_threads=None, final_outputs=None):\n    \"\"\"\n    \"\"\"\n    assert num_threads <= 1 or num_runtime_threads <= 1, (\n        'Only one of num_threads or num_runtime_threads must be set.')\n\n    if isinstance(input, Reader):\n        reader = input\n    elif hasattr(input, 'reader'):\n        reader = input.reader()\n    else:\n        raise ValueError('in must be a reader, queue or stream.')\n\n    if processor is not None:\n        reader = ProcessingReader(reader, processor)\n\n    if num_threads == 0 or num_runtime_threads == 0:\n        assert output is None\n        return reader, None\n\n    if name is None and processor is not None:\n        name = processor_name(processor)\n    if name is None and output is not None:\n        name = 'pipe_into:%s' % processor_name(output)\n    if name is None:\n        name = 'pipe_from:%s' % processor_name(input)\n\n    if num_threads > 1:\n        return _static_threads_task(\n            name, group, final_outputs, reader, num_threads, output, capacity)\n    else:\n        return _runtime_threads_task(\n            name, group, final_outputs, reader, num_runtime_threads, output,\n            capacity)\n\n\nclass ProcessingReader(Reader):\n    \"\"\"\n    Reader that reads from an upstream reader, calls the processor, and returns\n    the processed record.\n    \"\"\"\n    def __init__(self, reader, processor):\n        Reader.__init__(self)\n        self.reader = reader\n        self.processor = make_processor(processor)\n\n    def setup_ex(self, init_net, finish_net):\n        self.reader.setup_ex(init_net, finish_net)\n\n    def read_ex(self, init_net, exit_net):\n        read_nets, status, rec = self.reader.read_record_ex(init_net, exit_net)\n        with NetBuilder(_stop_blob=status):\n            # Current NetBuilder is optionally used inside the processor,\n            # then its children are retrived inside of\n            # normalize_processor_output.\n            # Once readers and writers also use NetBuilder,\n            # this logic will be more natural.\n            result = normalize_processor_output(self.processor(rec))\n        read_nets += result.nets\n        if result.should_stop is not None:\n            stop_net = core.Net('stop_net')\n            stop_net.Copy([result.should_stop], [status])\n            read_nets.append(stop_net)\n        if hasattr(self.processor, 'setup'):\n            init_net.add_attribute(TaskGroup.LOCAL_SETUP, self.processor)\n        self._set_schema(result.record)\n        fields = result.record.field_blobs() if result.record else None\n        return read_nets, status, fields\n\n\nclass NetProcessor(object):\n    \"\"\"\n    Processor that clones a core.Net each time it's called, executing\n    the cloned net as the processor. It requires the Net to have input\n    and (optionally) output records set, with net.set_input_record() and\n    net.set_output_record().\n    \"\"\"\n    def __init__(self, net, stop_signal=None, thread_init_nets=None, name=None):\n        assert isinstance(net, core.Net)\n        assert stop_signal is None or isinstance(\n            stop_signal, core.BlobReference)\n        self.name = name or str(net)\n        self.thread_init_nets = thread_init_nets or []\n        self.net = net\n        self._stop_signal = stop_signal\n        self._blob_maps = []\n        self._frozen = False\n        self._cloned_init_nets = []\n\n    def setup(self, init_net):\n        self._frozen = True\n        cloned_init_nets = self._cloned_init_nets\n        self._cloned_init_nets = []\n        return cloned_init_nets\n\n    def __call__(self, rec):\n        assert not self._frozen\n        prefix = NetBuilder.current().name + '/'\n        blob_remap = {}\n        for net in self.thread_init_nets:\n            new_net, _ = core.clone_and_bind_net(\n                net, str(net) + prefix, prefix, blob_remap)\n            self._cloned_init_nets.append(new_net)\n\n        new_net, remappings = core.clone_and_bind_net(\n            self.net, str(self.net) + prefix, prefix, blob_remap, rec)\n\n        if self._stop_signal is None:\n            stop_signal = None\n        elif str(self._stop_signal) in remappings:\n            stop_signal = core.BlobReference(\n                remappings[str(self._stop_signal)],\n                net=new_net)\n        else:\n            stop_signal = self._stop_signal\n\n        self._blob_maps.append(remappings)\n        return Output([new_net], new_net.output_record(), stop_signal)\n\n    def blob_maps(self):\n        self._frozen = True\n        return self._blob_maps\n"
  },
  {
    "path": "caffe2/python/pipeline_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python.schema import (\n    Struct, FetchRecord, NewRecord, FeedRecord, InitEmptyRecord)\nfrom caffe2.python import core, workspace\nfrom caffe2.python.session import LocalSession\nfrom caffe2.python.dataset import Dataset\nfrom caffe2.python.pipeline import pipe\nfrom caffe2.python.queue_util import Queue\nfrom caffe2.python.task import TaskGroup\nfrom caffe2.python.test_util import TestCase\nfrom caffe2.python.net_builder import ops\nimport numpy as np\nimport math\n\n\nclass TestPipeline(TestCase):\n    def test_dequeue_many(self):\n        init_net = core.Net('init')\n        N = 17\n        NUM_DEQUEUE_RECORDS = 3\n        src_values = Struct(\n            ('uid', np.array(range(N))),\n            ('value', 0.1 * np.array(range(N))))\n        expected_dst = Struct(\n            ('uid', 2 * np.array(range(N))),\n            ('value', np.array(N * [0.0])))\n\n        with core.NameScope('init'):\n            src_blobs = NewRecord(init_net, src_values)\n            dst_blobs = InitEmptyRecord(init_net, src_values.clone_schema())\n            counter = init_net.Const(0)\n            ONE = init_net.Const(1)\n\n        def proc1(rec):\n            with core.NameScope('proc1'):\n                out = NewRecord(ops, rec)\n            ops.Add([rec.uid(), rec.uid()], [out.uid()])\n            out.value.set(blob=rec.value(), unsafe=True)\n            return out\n\n        def proc2(rec):\n            with core.NameScope('proc2'):\n                out = NewRecord(ops, rec)\n            out.uid.set(blob=rec.uid(), unsafe=True)\n            ops.Sub([rec.value(), rec.value()], [out.value()])\n            ops.Add([counter, ONE], [counter])\n            return out\n\n        src_ds = Dataset(src_blobs)\n        dst_ds = Dataset(dst_blobs)\n\n        with TaskGroup() as tg:\n            out1 = pipe(\n                src_ds.reader(),\n                output=Queue(\n                    capacity=11, num_dequeue_records=NUM_DEQUEUE_RECORDS),\n                processor=proc1)\n            out2 = pipe(out1, processor=proc2)\n            pipe(out2, dst_ds.writer())\n\n        ws = workspace.C.Workspace()\n        FeedRecord(src_blobs, src_values, ws)\n        session = LocalSession(ws)\n        session.run(init_net)\n        session.run(tg)\n        output = FetchRecord(dst_blobs, ws=ws)\n        num_dequeues = ws.blobs[str(counter)].fetch()\n\n        self.assertEquals(\n            num_dequeues, int(math.ceil(float(N) / NUM_DEQUEUE_RECORDS)))\n\n        for a, b in zip(output.field_blobs(), expected_dst.field_blobs()):\n            np.testing.assert_array_equal(a, b)\n"
  },
  {
    "path": "caffe2/python/predictor/__init__.py",
    "content": ""
  },
  {
    "path": "caffe2/python/predictor/mobile_exporter.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package mobile_exporter\n# Module caffe2.python.mobile_exporter\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, utils\nfrom caffe2.proto import caffe2_pb2\nimport numpy as np\n\n\ndef add_tensor(net, name, blob):\n    ''' Create an operator to store the tensor 'blob',\n        run the operator to put the blob to workspace.\n        uint8 is stored as an array of string with one element.\n    '''\n    kTypeNameMapper = {\n        np.dtype('float32'): \"GivenTensorFill\",\n        np.dtype('int32'): \"GivenTensorIntFill\",\n        np.dtype('int64'): \"GivenTensorInt64Fill\",\n        np.dtype('uint8'): \"GivenTensorStringFill\",\n    }\n\n    shape = blob.shape\n    values = blob\n    # pass array of uint8 as a string to save storage\n    # storing uint8_t has a large overhead for now\n    if blob.dtype == np.dtype('uint8'):\n        shape = [1]\n        values = [str(blob.data)]\n\n    op = core.CreateOperator(\n        kTypeNameMapper[blob.dtype],\n        [], [name],\n        arg=[\n            utils.MakeArgument(\"shape\", shape),\n            utils.MakeArgument(\"values\", values),\n        ]\n    )\n    net.op.extend([op])\n\n\ndef Export(workspace, net, params):\n    \"\"\"Returns init_net and predict_net suitable for writing to disk\n       and loading into a Predictor\"\"\"\n    proto = net if isinstance(net, caffe2_pb2.NetDef) else net.Proto()\n    predict_net = caffe2_pb2.NetDef()\n    predict_net.CopyFrom(proto)\n    init_net = caffe2_pb2.NetDef()\n    # Populate the init_net.\n    ssa, blob_versions = core.get_ssa(net)\n    inputs = []\n    for versioned_inputs, _ in ssa:\n        inputs += [name for name, _ in versioned_inputs]\n\n    input_blobs = [blob_name for blob_name, version in\n                   blob_versions.items()\n                   if version == 0 and blob_name not in params]\n    # Blobs that are never used as an input to another layer,\n    # i.e. strictly output blobs.\n    output_blobs = [blob_name for blob_name, version in\n                    blob_versions.items()\n                    if version != 0 and blob_name not in inputs]\n\n    for blob_ref in params:\n        blob_name = str(blob_ref)\n        blob = workspace.FetchBlob(blob_name)\n        add_tensor(init_net, blob_name, blob)\n    # We have to make sure the blob exists in the namespace\n    # and we can do so with fake data. (Which is immediately overwritten\n    # by any typical usage)\n    for blob_name in input_blobs:\n        init_net.op.extend(\n            [\n                core.CreateOperator(\n                    \"GivenTensorFill\", [], [blob_name],\n                    arg=[\n                        utils.MakeArgument(\"shape\", [1, 1]),\n                        utils.MakeArgument(\"values\", [0.0])\n                    ]\n                )\n            ]\n        )\n\n    # Now we make input/output_blobs line up with what Predictor expects.\n    del predict_net.external_input[:]\n    predict_net.external_input.extend(input_blobs)\n    # For populating weights\n    predict_net.external_input.extend(proto.external_input)\n    # Ensure the output is also consistent with what we want\n    del predict_net.external_output[:]\n    predict_net.external_output.extend(output_blobs)\n    return init_net, predict_net\n"
  },
  {
    "path": "caffe2/python/predictor/mobile_exporter_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python.test_util import TestCase\nfrom caffe2.python import workspace, brew\nfrom caffe2.python.model_helper import ModelHelper\nfrom caffe2.python.predictor import mobile_exporter\nimport numpy as np\n\n\nclass TestMobileExporter(TestCase):\n    def test_mobile_exporter(self):\n        model = ModelHelper(name=\"mobile_exporter_test_model\")\n        # Test LeNet\n        brew.conv(model, 'data', 'conv1', dim_in=1, dim_out=20, kernel=5)\n        brew.max_pool(model, 'conv1', 'pool1', kernel=2, stride=2)\n        brew.conv(model, 'pool1', 'conv2', dim_in=20, dim_out=50, kernel=5)\n        brew.max_pool(model, 'conv2', 'pool2', kernel=2, stride=2)\n        brew.fc(model, 'pool2', 'fc3', dim_in=50 * 4 * 4, dim_out=500)\n        brew.relu(model, 'fc3', 'fc3')\n        brew.fc(model, 'fc3', 'pred', 500, 10)\n        brew.softmax(model, 'pred', 'out')\n\n        # Create our mobile exportable networks\n        workspace.RunNetOnce(model.param_init_net)\n        init_net, predict_net = mobile_exporter.Export(\n            workspace, model.net, model.params\n        )\n\n        # Populate the workspace with data\n        np_data = np.random.rand(1, 1, 28, 28).astype(np.float32)\n        workspace.FeedBlob(\"data\", np_data)\n\n        workspace.CreateNet(model.net)\n        workspace.RunNet(model.net)\n        ref_out = workspace.FetchBlob(\"out\")\n\n        # Clear the workspace\n        workspace.ResetWorkspace()\n\n        # Populate the workspace with data\n        workspace.RunNetOnce(init_net)\n        # Fake \"data\" is populated by init_net, we have to replace it\n        workspace.FeedBlob(\"data\", np_data)\n\n        # Overwrite the old net\n        workspace.CreateNet(predict_net, True)\n        workspace.RunNet(predict_net.name)\n        manual_run_out = workspace.FetchBlob(\"out\")\n        np.testing.assert_allclose(\n            ref_out, manual_run_out, atol=1e-10, rtol=1e-10\n        )\n\n        # Clear the workspace\n        workspace.ResetWorkspace()\n\n        # Predictor interface test (simulates writing to disk)\n        predictor = workspace.Predictor(\n            init_net.SerializeToString(), predict_net.SerializeToString()\n        )\n\n        # Output is a vector of outputs but we only care about the first and only result\n        predictor_out = predictor.run([np_data])\n        assert len(predictor_out) == 1\n        predictor_out = predictor_out[0]\n\n        np.testing.assert_allclose(\n            ref_out, predictor_out, atol=1e-10, rtol=1e-10\n        )\n\n    def test_mobile_exporter_datatypes(self):\n        model = ModelHelper(name=\"mobile_exporter_test_model\")\n        model.Copy(\"data_int\", \"out\")\n        model.params.append(\"data_int\")\n\n        # Create our mobile exportable networks\n        workspace.RunNetOnce(model.param_init_net)\n        np_data_int = np.random.randint(100, size=(1, 1, 28, 28), dtype=np.int32)\n        workspace.FeedBlob(\"data_int\", np_data_int)\n\n        init_net, predict_net = mobile_exporter.Export(\n            workspace, model.net, model.params\n        )\n\n        workspace.CreateNet(model.net)\n        workspace.RunNet(model.net)\n        ref_out = workspace.FetchBlob(\"out\")\n\n        # Clear the workspace\n        workspace.ResetWorkspace()\n\n        # Populate the workspace with data\n        workspace.RunNetOnce(init_net)\n\n        # Overwrite the old net\n        workspace.CreateNet(predict_net, True)\n        workspace.RunNet(predict_net.name)\n        manual_run_out = workspace.FetchBlob(\"out\")\n        np.testing.assert_allclose(\n            ref_out, manual_run_out, atol=1e-10, rtol=1e-10\n        )\n\n        # Clear the workspace\n        workspace.ResetWorkspace()\n\n        # Predictor interface test (simulates writing to disk)\n        predictor = workspace.Predictor(\n            init_net.SerializeToString(), predict_net.SerializeToString()\n        )\n\n        # Output is a vector of outputs but we only care about the first and only result\n        predictor_out = predictor.run([])\n        assert len(predictor_out) == 1\n        predictor_out = predictor_out[0]\n\n        np.testing.assert_allclose(\n            ref_out, predictor_out, atol=1e-10, rtol=1e-10\n        )\n"
  },
  {
    "path": "caffe2/python/predictor/predictor_exporter.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package predictor_exporter\n# Module caffe2.python.predictor.predictor_exporter\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.proto import metanet_pb2\nfrom caffe2.python import workspace, core, scope\nfrom caffe2.python.predictor_constants import predictor_constants\nimport caffe2.python.predictor.serde as serde\nimport caffe2.python.predictor.predictor_py_utils as utils\nfrom builtins import bytes\nimport collections\n\n\ndef get_predictor_exporter_helper(submodelNetName):\n    \"\"\" constracting stub for the PredictorExportMeta\n        Only used to construct names to subfields,\n        such as calling to predict_net_name\n        Args:\n            submodelNetName - name of the model\n    \"\"\"\n    stub_net = core.Net(submodelNetName)\n    pred_meta = PredictorExportMeta(predict_net=stub_net,\n                                    parameters=[],\n                                    inputs=[],\n                                    outputs=[],\n                                    shapes=None,\n                                    name=submodelNetName,\n                                    extra_init_net=None)\n    return pred_meta\n\n\nclass PredictorExportMeta(collections.namedtuple(\n    'PredictorExportMeta',\n        'predict_net, parameters, inputs, outputs, shapes, name, \\\n        extra_init_net, net_type, num_workers')):\n    \"\"\"\n    Metadata to be used for serializaing a net.\n\n    parameters, inputs, outputs could be either BlobReference or blob's names\n\n    predict_net can be either core.Net, NetDef, PlanDef or object\n\n    Override the named tuple to provide optional name parameter.\n    name will be used to identify multiple prediction nets.\n\n    net_type is the type field in caffe2 NetDef - can be 'simple', 'dag', etc.\n\n    num_workers specifies for net type 'dag' how many threads should run ops\n    \"\"\"\n    def __new__(\n        cls,\n        predict_net,\n        parameters,\n        inputs,\n        outputs,\n        shapes=None,\n        name=\"\",\n        extra_init_net=None,\n        net_type=None,\n        num_workers=None,\n    ):\n        inputs = [str(i) for i in inputs]\n        outputs = [str(o) for o in outputs]\n        assert len(set(inputs)) == len(inputs), (\n            \"All inputs to the predictor should be unique\")\n        parameters = [str(p) for p in parameters]\n        assert set(parameters).isdisjoint(inputs), (\n            \"Parameters and inputs are required to be disjoint. \"\n            \"Intersection: {}\".format(set(parameters).intersection(inputs)))\n        assert set(parameters).isdisjoint(outputs), (\n            \"Parameters and outputs are required to be disjoint. \"\n            \"Intersection: {}\".format(set(parameters).intersection(outputs)))\n        shapes = shapes or {}\n\n        if isinstance(predict_net, (core.Net, core.Plan)):\n            predict_net = predict_net.Proto()\n\n        assert isinstance(predict_net, (caffe2_pb2.NetDef, caffe2_pb2.PlanDef))\n        return super(PredictorExportMeta, cls).__new__(\n            cls, predict_net, parameters, inputs, outputs, shapes, name,\n            extra_init_net, net_type, num_workers)\n\n    def inputs_name(self):\n        return utils.get_comp_name(predictor_constants.INPUTS_BLOB_TYPE,\n                                   self.name)\n\n    def outputs_name(self):\n        return utils.get_comp_name(predictor_constants.OUTPUTS_BLOB_TYPE,\n                                   self.name)\n\n    def parameters_name(self):\n        return utils.get_comp_name(predictor_constants.PARAMETERS_BLOB_TYPE,\n                                   self.name)\n\n    def global_init_name(self):\n        return utils.get_comp_name(predictor_constants.GLOBAL_INIT_NET_TYPE,\n                                   self.name)\n\n    def predict_init_name(self):\n        return utils.get_comp_name(predictor_constants.PREDICT_INIT_NET_TYPE,\n                                   self.name)\n\n    def predict_net_name(self):\n        return utils.get_comp_name(predictor_constants.PREDICT_NET_TYPE,\n                                   self.name)\n\n    def train_init_plan_name(self):\n        return utils.get_comp_name(predictor_constants.TRAIN_INIT_PLAN_TYPE,\n                                   self.name)\n\n    def train_plan_name(self):\n        return utils.get_comp_name(predictor_constants.TRAIN_PLAN_TYPE,\n                                   self.name)\n\n\ndef prepare_prediction_net(filename, db_type, device_option=None):\n    '''\n    Helper function which loads all required blobs from the db\n    and returns prediction net ready to be used\n    '''\n    metanet_def = load_from_db(filename, db_type, device_option)\n\n    global_init_net = utils.GetNet(\n        metanet_def, predictor_constants.GLOBAL_INIT_NET_TYPE)\n    workspace.RunNetOnce(global_init_net)\n\n    predict_init_net = utils.GetNet(\n        metanet_def, predictor_constants.PREDICT_INIT_NET_TYPE)\n    workspace.RunNetOnce(predict_init_net)\n\n    predict_net = core.Net(\n        utils.GetNet(metanet_def, predictor_constants.PREDICT_NET_TYPE))\n    workspace.CreateNet(predict_net)\n\n    return predict_net\n\n\ndef _global_init_net(predictor_export_meta):\n    net = core.Net(\"global-init\")\n    net.Load(\n        [predictor_constants.PREDICTOR_DBREADER],\n        predictor_export_meta.parameters)\n    net.Proto().external_input.extend([predictor_constants.PREDICTOR_DBREADER])\n    net.Proto().external_output.extend(predictor_export_meta.parameters)\n\n    # Add the model_id in the predict_net to the global_init_net\n    utils.AddModelIdArg(predictor_export_meta, net.Proto())\n    return net.Proto()\n\n\ndef get_meta_net_def(predictor_export_meta, ws=None):\n    \"\"\"\n    \"\"\"\n\n    ws = ws or workspace.C.Workspace.current\n    meta_net_def = metanet_pb2.MetaNetDef()\n\n    # Predict net is the core network that we use.\n    utils.AddNet(meta_net_def, predictor_export_meta.predict_init_name(),\n                 utils.create_predict_init_net(ws, predictor_export_meta))\n    utils.AddNet(meta_net_def, predictor_export_meta.global_init_name(),\n                 _global_init_net(predictor_export_meta))\n    utils.AddNet(meta_net_def, predictor_export_meta.predict_net_name(),\n                 utils.create_predict_net(predictor_export_meta))\n    utils.AddBlobs(meta_net_def, predictor_export_meta.parameters_name(),\n                   predictor_export_meta.parameters)\n    utils.AddBlobs(meta_net_def, predictor_export_meta.inputs_name(),\n                   predictor_export_meta.inputs)\n    utils.AddBlobs(meta_net_def, predictor_export_meta.outputs_name(),\n                   predictor_export_meta.outputs)\n    return meta_net_def\n\n\ndef set_model_info(meta_net_def, project_str, model_class_str, version):\n    assert isinstance(meta_net_def, metanet_pb2.MetaNetDef)\n    meta_net_def.modelInfo.project = project_str\n    meta_net_def.modelInfo.modelClass = model_class_str\n    meta_net_def.modelInfo.version = version\n\n\ndef save_to_db(db_type, db_destination, predictor_export_meta):\n    meta_net_def = get_meta_net_def(predictor_export_meta)\n    with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU)):\n        workspace.FeedBlob(\n            predictor_constants.META_NET_DEF,\n            serde.serialize_protobuf_struct(meta_net_def)\n        )\n\n    blobs_to_save = [predictor_constants.META_NET_DEF] + \\\n        predictor_export_meta.parameters\n    op = core.CreateOperator(\n        \"Save\",\n        blobs_to_save, [],\n        absolute_path=True,\n        db=db_destination, db_type=db_type)\n\n    workspace.RunOperatorOnce(op)\n\n\ndef load_from_db(filename, db_type, device_option=None):\n    # global_init_net in meta_net_def will load parameters from\n    # predictor_constants.PREDICTOR_DBREADER\n    create_db = core.CreateOperator(\n        'CreateDB', [],\n        [core.BlobReference(predictor_constants.PREDICTOR_DBREADER)],\n        db=filename, db_type=db_type)\n    assert workspace.RunOperatorOnce(create_db), (\n        'Failed to create db {}'.format(filename))\n\n    # predictor_constants.META_NET_DEF is always stored before the parameters\n    load_meta_net_def = core.CreateOperator(\n        'Load',\n        [core.BlobReference(predictor_constants.PREDICTOR_DBREADER)],\n        [core.BlobReference(predictor_constants.META_NET_DEF)])\n    assert workspace.RunOperatorOnce(load_meta_net_def)\n\n    blob = workspace.FetchBlob(predictor_constants.META_NET_DEF)\n    meta_net_def = serde.deserialize_protobuf_struct(\n        blob if isinstance(blob, bytes)\n        else str(blob).encode('utf-8'),\n        metanet_pb2.MetaNetDef)\n\n    if device_option is None:\n        device_option = scope.CurrentDeviceScope()\n\n    if device_option is not None:\n        # Set the device options of all loaded blobs\n        for kv in meta_net_def.nets:\n            net = kv.value\n            for op in net.op:\n                op.device_option.CopyFrom(device_option)\n\n    return meta_net_def\n"
  },
  {
    "path": "caffe2/python/predictor/predictor_exporter_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport tempfile\nimport unittest\nimport numpy as np\nfrom caffe2.python import cnn, workspace, core\nfrom future.utils import viewitems\n\nfrom caffe2.python.predictor_constants import predictor_constants as pc\nimport caffe2.python.predictor.predictor_exporter as pe\nimport caffe2.python.predictor.predictor_py_utils as pred_utils\nfrom caffe2.proto import caffe2_pb2, metanet_pb2\n\n\nclass MetaNetDefTest(unittest.TestCase):\n    def test_minimal(self):\n        '''\n        Tests that a NetsMap message can be created with a NetDef message\n        '''\n        # This calls the constructor for a metanet_pb2.NetsMap\n        metanet_pb2.NetsMap(key=\"test_key\", value=caffe2_pb2.NetDef())\n\n    def test_adding_net(self):\n        '''\n        Tests that NetDefs can be added to MetaNetDefs\n        '''\n        meta_net_def = metanet_pb2.MetaNetDef()\n        net_def = caffe2_pb2.NetDef()\n        meta_net_def.nets.add(key=\"test_key\", value=net_def)\n\nclass PredictorExporterTest(unittest.TestCase):\n    def _create_model(self):\n        m = cnn.CNNModelHelper()\n        m.FC(\"data\", \"y\",\n             dim_in=5, dim_out=10,\n             weight_init=m.XavierInit,\n             bias_init=m.XavierInit)\n        return m\n\n    def setUp(self):\n        np.random.seed(1)\n        m = self._create_model()\n\n        self.predictor_export_meta = pe.PredictorExportMeta(\n            predict_net=m.net.Proto(),\n            parameters=[str(b) for b in m.params],\n            inputs=[\"data\"],\n            outputs=[\"y\"],\n            shapes={\"y\": (1, 10), \"data\": (1, 5)},\n        )\n        workspace.RunNetOnce(m.param_init_net)\n\n        self.params = {\n            param: workspace.FetchBlob(param)\n            for param in self.predictor_export_meta.parameters}\n        # Reset the workspace, to ensure net creation proceeds as expected.\n        workspace.ResetWorkspace()\n\n    def test_meta_constructor(self):\n        '''\n        Test that passing net itself instead of proto works\n        '''\n        m = self._create_model()\n        pe.PredictorExportMeta(\n            predict_net=m.net,\n            parameters=m.params,\n            inputs=[\"data\"],\n            outputs=[\"y\"],\n            shapes={\"y\": (1, 10), \"data\": (1, 5)},\n        )\n\n    def test_param_intersection(self):\n        '''\n        Test that passes intersecting parameters and input/output blobs\n        '''\n        m = self._create_model()\n        with self.assertRaises(Exception):\n            pe.PredictorExportMeta(\n                predict_net=m.net,\n                parameters=m.params,\n                inputs=[\"data\"] + m.params,\n                outputs=[\"y\"],\n                shapes={\"y\": (1, 10), \"data\": (1, 5)},\n            )\n        with self.assertRaises(Exception):\n            pe.PredictorExportMeta(\n                predict_net=m.net,\n                parameters=m.params,\n                inputs=[\"data\"],\n                outputs=[\"y\"] + m.params,\n                shapes={\"y\": (1, 10), \"data\": (1, 5)},\n            )\n\n    def test_meta_net_def_net_runs(self):\n        for param, value in viewitems(self.params):\n            workspace.FeedBlob(param, value)\n\n        extra_init_net = core.Net('extra_init')\n        extra_init_net.ConstantFill('data', 'data', value=1.0)\n        pem = pe.PredictorExportMeta(\n            predict_net=self.predictor_export_meta.predict_net,\n            parameters=self.predictor_export_meta.parameters,\n            inputs=self.predictor_export_meta.inputs,\n            outputs=self.predictor_export_meta.outputs,\n            shapes=self.predictor_export_meta.shapes,\n            extra_init_net=extra_init_net,\n            net_type='dag',\n        )\n\n        db_type = 'minidb'\n        db_file = tempfile.NamedTemporaryFile(\n            delete=False, suffix=\".{}\".format(db_type))\n        pe.save_to_db(\n            db_type=db_type,\n            db_destination=db_file.name,\n            predictor_export_meta=pem)\n\n        workspace.ResetWorkspace()\n\n        meta_net_def = pe.load_from_db(\n            db_type=db_type,\n            filename=db_file.name,\n        )\n\n        self.assertTrue(\"data\" not in workspace.Blobs())\n        self.assertTrue(\"y\" not in workspace.Blobs())\n\n        init_net = pred_utils.GetNet(meta_net_def, pc.PREDICT_INIT_NET_TYPE)\n\n        # 0-fills externalblobs blobs and runs extra_init_net\n        workspace.RunNetOnce(init_net)\n\n        self.assertTrue(\"data\" in workspace.Blobs())\n        self.assertTrue(\"y\" in workspace.Blobs())\n\n        print(workspace.FetchBlob(\"data\"))\n        np.testing.assert_array_equal(\n            workspace.FetchBlob(\"data\"), np.ones(shape=(1, 5)))\n        np.testing.assert_array_equal(\n            workspace.FetchBlob(\"y\"), np.zeros(shape=(1, 10)))\n\n        # Load parameters from DB\n        global_init_net = pred_utils.GetNet(meta_net_def,\n                                            pc.GLOBAL_INIT_NET_TYPE)\n        workspace.RunNetOnce(global_init_net)\n\n        # Run the net with a reshaped input and verify we are\n        # producing good numbers (with our custom implementation)\n        workspace.FeedBlob(\"data\", np.random.randn(2, 5).astype(np.float32))\n        predict_net = pred_utils.GetNet(meta_net_def, pc.PREDICT_NET_TYPE)\n        self.assertEqual(predict_net.type, 'dag')\n        workspace.RunNetOnce(predict_net)\n        np.testing.assert_array_almost_equal(\n            workspace.FetchBlob(\"y\"),\n            workspace.FetchBlob(\"data\").dot(self.params[\"y_w\"].T) +\n            self.params[\"y_b\"])\n\n    def test_load_device_scope(self):\n        for param, value in self.params.items():\n            workspace.FeedBlob(param, value)\n\n        pem = pe.PredictorExportMeta(\n            predict_net=self.predictor_export_meta.predict_net,\n            parameters=self.predictor_export_meta.parameters,\n            inputs=self.predictor_export_meta.inputs,\n            outputs=self.predictor_export_meta.outputs,\n            shapes=self.predictor_export_meta.shapes,\n            net_type='dag',\n        )\n\n        db_type = 'minidb'\n        db_file = tempfile.NamedTemporaryFile(\n            delete=False, suffix=\".{}\".format(db_type))\n        pe.save_to_db(\n            db_type=db_type,\n            db_destination=db_file.name,\n            predictor_export_meta=pem)\n\n        workspace.ResetWorkspace()\n        with core.DeviceScope(core.DeviceOption(caffe2_pb2.CPU, 1)):\n            meta_net_def = pe.load_from_db(\n                db_type=db_type,\n                filename=db_file.name,\n            )\n\n        init_net = core.Net(pred_utils.GetNet(meta_net_def,\n                            pc.GLOBAL_INIT_NET_TYPE))\n        predict_init_net = core.Net(pred_utils.GetNet(\n            meta_net_def, pc.PREDICT_INIT_NET_TYPE))\n\n        # check device options\n        for op in list(init_net.Proto().op) + list(predict_init_net.Proto().op):\n            self.assertEqual(1, op.device_option.cuda_gpu_id)\n            self.assertEqual(caffe2_pb2.CPU, op.device_option.device_type)\n\n    def test_db_fails_without_params(self):\n        with self.assertRaises(Exception):\n            for db_type in [\"minidb\"]:\n                db_file = tempfile.NamedTemporaryFile(\n                    delete=False, suffix=\".{}\".format(db_type))\n                pe.save_to_db(\n                    db_type=db_type,\n                    db_destination=db_file.name,\n                    predictor_export_meta=self.predictor_export_meta)\n"
  },
  {
    "path": "caffe2/python/predictor/predictor_py_utils.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package predictor_py_utils\n# Module caffe2.python.predictor.predictor_py_utils\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, scope\n\n\ndef create_predict_net(predictor_export_meta):\n    \"\"\"\n    Return the input prediction net.\n    \"\"\"\n    # Construct a new net to clear the existing settings.\n    net = core.Net(predictor_export_meta.predict_net.name or \"predict\")\n    net.Proto().op.extend(predictor_export_meta.predict_net.op)\n    net.Proto().external_input.extend(\n        predictor_export_meta.inputs + predictor_export_meta.parameters)\n    net.Proto().external_output.extend(predictor_export_meta.outputs)\n    net.Proto().arg.extend(predictor_export_meta.predict_net.arg)\n    if predictor_export_meta.net_type is not None:\n        net.Proto().type = predictor_export_meta.net_type\n    if predictor_export_meta.num_workers is not None:\n        net.Proto().num_workers = predictor_export_meta.num_workers\n    return net.Proto()\n\n\ndef create_predict_init_net(ws, predictor_export_meta):\n    \"\"\"\n    Return an initialization net that zero-fill all the input and\n    output blobs, using the shapes from the provided workspace. This is\n    necessary as there is no shape inference functionality in Caffe2.\n    \"\"\"\n    net = core.Net(\"predict-init\")\n\n    def zero_fill(blob):\n        shape = predictor_export_meta.shapes.get(blob)\n        if shape is None:\n            if blob not in ws.blobs:\n                raise Exception(\n                    \"{} not in workspace but needed for shape: {}\".format(\n                        blob, ws.blobs))\n\n            shape = ws.blobs[blob].fetch().shape\n\n        # Explicitly null-out the scope so users (e.g. PredictorGPU)\n        # can control (at a Net-global level) the DeviceOption of\n        # these filling operators.\n        with scope.EmptyDeviceScope():\n            net.ConstantFill([], blob, shape=shape, value=0.0)\n\n    external_blobs = predictor_export_meta.inputs + \\\n        predictor_export_meta.outputs\n    for blob in external_blobs:\n        zero_fill(blob)\n\n    net.Proto().external_input.extend(external_blobs)\n    if predictor_export_meta.extra_init_net:\n        net.AppendNet(predictor_export_meta.extra_init_net)\n\n    # Add the model_id in the predict_net to the init_net\n    AddModelIdArg(predictor_export_meta, net.Proto())\n\n    return net.Proto()\n\n\ndef get_comp_name(string, name):\n    if name:\n        return string + '_' + name\n    return string\n\n\ndef _ProtoMapGet(field, key):\n    '''\n    Given the key, get the value of the repeated field.\n    Helper function used by protobuf since it doesn't have map construct\n    '''\n    for v in field:\n        if (v.key == key):\n            return v.value\n    return None\n\n\ndef GetPlan(meta_net_def, key):\n    return _ProtoMapGet(meta_net_def.plans, key)\n\n\ndef GetPlanOriginal(meta_net_def, key):\n    return _ProtoMapGet(meta_net_def.plans, key)\n\n\ndef GetBlobs(meta_net_def, key):\n    blobs = _ProtoMapGet(meta_net_def.blobs, key)\n    if blobs is None:\n        return []\n    return blobs\n\n\ndef GetNet(meta_net_def, key):\n    return _ProtoMapGet(meta_net_def.nets, key)\n\n\ndef GetNetOriginal(meta_net_def, key):\n    return _ProtoMapGet(meta_net_def.nets, key)\n\n\ndef GetApplicationSpecificInfo(meta_net_def, key):\n    return _ProtoMapGet(meta_net_def.applicationSpecificInfo, key)\n\n\ndef AddBlobs(meta_net_def, blob_name, blob_def):\n    blobs = _ProtoMapGet(meta_net_def.blobs, blob_name)\n    if blobs is None:\n        blobs = meta_net_def.blobs.add()\n        blobs.key = blob_name\n        blobs = blobs.value\n    for blob in blob_def:\n        blobs.append(blob)\n\n\ndef AddPlan(meta_net_def, plan_name, plan_def):\n    meta_net_def.plans.add(key=plan_name, value=plan_def)\n\n\ndef AddNet(meta_net_def, net_name, net_def):\n    meta_net_def.nets.add(key=net_name, value=net_def)\n\n\ndef GetArgumentByName(net_def, arg_name):\n    for arg in net_def.arg:\n        if arg.name == arg_name:\n            return arg\n    return None\n\n\ndef AddModelIdArg(meta_net_def, net_def):\n    \"\"\"Takes the model_id from the predict_net of meta_net_def (if it is\n    populated) and adds it to the net_def passed in. This is intended to be\n    called on init_nets, as their model_id is not populated by default, but\n    should be the same as that of the predict_net\n    \"\"\"\n    # Get model_id from the predict_net, assuming it's an integer\n    model_id = GetArgumentByName(meta_net_def.predict_net, \"model_id\")\n    if model_id is None:\n        return\n    model_id = model_id.i\n\n    # If there's another model_id on the net, replace it with the new one\n    old_id = GetArgumentByName(net_def, \"model_id\")\n    if old_id is not None:\n        old_id.i = model_id\n        return\n\n    # Add as an integer argument, this is also assumed above\n    arg = net_def.arg.add()\n    arg.name = \"model_id\"\n    arg.i = model_id\n"
  },
  {
    "path": "caffe2/python/predictor/predictor_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport unittest\nimport numpy as np\nfrom caffe2.python import workspace, core\n\nfrom caffe2.proto import caffe2_pb2\n\n\nclass TestPredictor(unittest.TestCase):\n    def setUp(self):\n        np.random.seed(1)\n        self.predict_net = self._predict_net\n        self.init_net = self._init_net\n\n    @property\n    def _predict_net(self):\n        net = caffe2_pb2.NetDef()\n        net.name = 'test-predict-net'\n        net.external_input[:] = ['A', 'B']\n        net.external_output[:] = ['C']\n        net.op.extend([\n            core.CreateOperator(\n                'MatMul',\n                ['A', 'B'],\n                ['C'],\n            )\n        ])\n        return net.SerializeToString()\n\n    @property\n    def _init_net(self):\n        net = caffe2_pb2.NetDef()\n        net.name = 'test-init-net'\n        net.external_output[:] = ['A', 'B']\n        net.op.extend([\n            core.CreateOperator(\n                'GivenTensorFill',\n                [],\n                ['A'],\n                shape=(2, 3),\n                values=np.zeros((2, 3), np.float32).flatten().tolist(),\n            ),\n            core.CreateOperator(\n                'GivenTensorFill',\n                [],\n                ['B'],\n                shape=(3, 4),\n                values=np.zeros((3, 4), np.float32).flatten().tolist(),\n            ),\n        ])\n        return net.SerializeToString()\n\n    def test_run(self):\n        A = np.ones((2, 3), np.float32)\n        B = np.ones((3, 4), np.float32)\n        predictor = workspace.Predictor(self.init_net, self.predict_net)\n        outputs = predictor.run([A, B])\n        self.assertEqual(len(outputs), 1)\n        np.testing.assert_almost_equal(np.dot(A, B), outputs[0])\n\n    def test_run_map(self):\n        A = np.zeros((2, 3), np.float32)\n        B = np.ones((3, 4), np.float32)\n        predictor = workspace.Predictor(self.init_net, self.predict_net)\n        outputs = predictor.run({\n            'B': B,\n        })\n        self.assertEqual(len(outputs), 1)\n        np.testing.assert_almost_equal(np.dot(A, B), outputs[0])\n"
  },
  {
    "path": "caffe2/python/predictor/serde.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package serde\n# Module caffe2.python.predictor.serde\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n\ndef serialize_protobuf_struct(protobuf_struct):\n    return protobuf_struct.SerializeToString()\n\n\ndef deserialize_protobuf_struct(serialized_protobuf, struct_type):\n    deser = struct_type()\n    deser.ParseFromString(serialized_protobuf)\n    return deser\n"
  },
  {
    "path": "caffe2/python/predictor_constants.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package predictor_constants\n# Module caffe2.python.predictor_constants\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport caffe2.proto.predictor_consts_pb2 as predictor_consts\n\npredictor_constants = predictor_consts.PredictorConsts()\n"
  },
  {
    "path": "caffe2/python/pybind_state.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"pybind_state.h\"\n\n#include <pybind11/pybind11.h>\n#include <pybind11/stl.h>\n\n#include \"caffe2/contrib/script/compiler.h\"\n#include \"caffe2/core/asan.h\"\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/numa.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/predictor.h\"\n#include \"caffe2/core/stats.h\"\n#include \"caffe2/core/transform.h\"\n#include \"caffe2/mkl/mkl_utils.h\"\n#include \"caffe2/observers/runcnt_observer.h\"\n#include \"caffe2/observers/time_observer.h\"\n#include \"caffe2/onnx/helper.h\"\n#include \"caffe2/onnx/backend.h\"\n#include \"caffe2/utils/cpuid.h\"\n#include \"caffe2/utils/string_utils.h\"\n#include \"google/protobuf/io/coded_stream.h\"\n#include \"google/protobuf/io/zero_copy_stream_impl_lite.h\"\n\nnamespace caffe2 {\nnamespace python {\n\n// A dummy variable to overcome the pybind11 py::arg::operator= ambiguity\n// for some earlier versions of pybind11.\nconstexpr bool kPyBindFalse = false;\n\nnamespace py = pybind11;\n\n// gWorkspaces allows us to define and switch between multiple workspaces in\n// Python.\nstatic std::map<std::string, std::unique_ptr<Workspace>> gWorkspaces;\n// gWorkspace is the pointer to the current workspace. The ownership is kept\n// by the gWorkspaces map.\nstatic Workspace* gWorkspace = nullptr;\nstatic std::string gCurrentWorkspaceName;\n\nBlobFetcherBase::~BlobFetcherBase() {}\nBlobFeederBase::~BlobFeederBase() {}\n\nCAFFE_DEFINE_TYPED_REGISTRY(\n    BlobFetcherRegistry,\n    CaffeTypeId,\n    BlobFetcherBase,\n    std::unique_ptr);\nCAFFE_DEFINE_TYPED_REGISTRY(\n    BlobFeederRegistry,\n    int,\n    BlobFeederBase,\n    std::unique_ptr);\n\nREGISTER_BLOB_FETCHER((TypeMeta::Id<TensorCPU>()), TensorFetcher<CPUContext>);\nREGISTER_BLOB_FEEDER(CPU, TensorFeeder<CPUContext>);\n\nclass StringFetcher : public BlobFetcherBase {\n public:\n  py::object Fetch(const Blob& blob) override {\n    return py::bytes(blob.Get<string>());\n  }\n};\nREGISTER_BLOB_FETCHER((TypeMeta::Id<string>()), StringFetcher);\n\nstatic_assert(\n    sizeof(int) == sizeof(int32_t),\n    \"We make an assumption that int is always int32 for numpy \"\n    \"type mapping.\");\nint CaffeToNumpyType(const TypeMeta& meta) {\n  static std::map<CaffeTypeId, int> numpy_type_map{\n      {TypeMeta::Id<bool>(), NPY_BOOL},\n      {TypeMeta::Id<double>(), NPY_DOUBLE},\n      {TypeMeta::Id<float>(), NPY_FLOAT},\n      {TypeMeta::Id<float16>(), NPY_FLOAT16},\n      {TypeMeta::Id<int>(), NPY_INT},\n      {TypeMeta::Id<int8_t>(), NPY_INT8},\n      {TypeMeta::Id<int16_t>(), NPY_INT16},\n      {TypeMeta::Id<int64_t>(), NPY_LONGLONG},\n      {TypeMeta::Id<uint8_t>(), NPY_UINT8},\n      {TypeMeta::Id<uint16_t>(), NPY_UINT16},\n      {TypeMeta::Id<std::string>(), NPY_OBJECT},\n      // Note: Add more types here.\n  };\n  const auto it = numpy_type_map.find(meta.id());\n  return it == numpy_type_map.end() ? -1 : it->second;\n}\n\nconst TypeMeta& NumpyTypeToCaffe(int numpy_type) {\n  static std::map<int, TypeMeta> caffe_type_map{\n      {NPY_BOOL, TypeMeta::Make<bool>()},\n      {NPY_DOUBLE, TypeMeta::Make<double>()},\n      {NPY_FLOAT, TypeMeta::Make<float>()},\n      {NPY_FLOAT16, TypeMeta::Make<float16>()},\n      {NPY_INT, TypeMeta::Make<int>()},\n      {NPY_INT8, TypeMeta::Make<int8_t>()},\n      {NPY_INT16, TypeMeta::Make<int16_t>()},\n      {NPY_INT64, TypeMeta::Make<int64_t>()},\n      {NPY_LONG,\n       sizeof(long) == sizeof(int) ? TypeMeta::Make<int>()\n                                   : TypeMeta::Make<int64_t>()},\n      {NPY_LONGLONG, TypeMeta::Make<int64_t>()},\n      {NPY_UINT8, TypeMeta::Make<uint8_t>()},\n      {NPY_UINT16, TypeMeta::Make<uint16_t>()},\n      {NPY_OBJECT, TypeMeta::Make<std::string>()},\n      {NPY_UNICODE, TypeMeta::Make<std::string>()},\n      {NPY_STRING, TypeMeta::Make<std::string>()},\n      // Note: Add more types here.\n  };\n  static TypeMeta unknown_type;\n  const auto it = caffe_type_map.find(numpy_type);\n  return it == caffe_type_map.end() ? unknown_type : it->second;\n}\n\ntemplate <typename Registry>\nstd::function<const char*(const string&)> DefinitionGetter(\n    const Registry* registry) {\n  return [registry](const string& name) { return registry->HelpMessage(name); };\n}\n\nvoid switchWorkspaceInternal(const std::string& name, bool create_if_missing) {\n  if (gWorkspaces.count(name)) {\n    gCurrentWorkspaceName = name;\n    gWorkspace = gWorkspaces[name].get();\n    return;\n  }\n\n  CAFFE_ENFORCE(create_if_missing);\n  std::unique_ptr<Workspace> new_workspace(new Workspace());\n  gWorkspace = new_workspace.get();\n  gWorkspaces.insert(std::make_pair(name, std::move(new_workspace)));\n  gCurrentWorkspaceName = name;\n}\n\nnamespace python_detail {\n// Python Op implementations.\nusing FuncRegistry = std::unordered_map<std::string, Func>;\n\nFuncRegistry& gRegistry() {\n  // Always leak the objects registered here.\n  static FuncRegistry* r = new FuncRegistry();\n  return *r;\n}\n\nconst Func& getOpFunc(const std::string& token) {\n  CAFFE_ENFORCE(\n      gRegistry().count(token),\n      \"Python operator for \",\n      token,\n      \" is not available. If you use distributed training it probably means \"\n      \"that python implementation has to be registered in each of the workers\");\n  return gRegistry()[token];\n}\n\nconst Func& getGradientFunc(const std::string& token) {\n  return getOpFunc(token + \"_gradient\");\n}\n\npy::object fetchBlob(Workspace* ws, const std::string& name) {\n  CAFFE_ENFORCE(ws->HasBlob(name), \"Can't find blob: \", name);\n  const caffe2::Blob& blob = *(ws->GetBlob(name));\n  auto fetcher = CreateFetcher(blob.meta().id());\n  if (fetcher) {\n    return fetcher->Fetch(blob);\n  } else {\n    // If there is no fetcher registered, return a metainfo string.\n    // If all branches failed, we will return a metainfo string.\n    std::stringstream ss;\n    ss << caffe2::string(name) << \", a C++ native class of type \"\n       << blob.TypeName() << \".\";\n    return py::bytes(ss.str());\n  }\n}\n} // namespace python_detail\n\nclass GetPythonGradient : public GradientMakerBase {\n public:\n  using GradientMakerBase::GradientMakerBase;\n  std::vector<OperatorDef> GetGradientDefs() override {\n    CAFFE_ENFORCE(Def().type() == \"Python\" || Def().type() == \"PythonDLPack\");\n    ArgumentHelper helper(Def());\n    auto gradOutputIndices =\n        helper.GetRepeatedArgument<int>(\"grad_output_indices\");\n    auto gradInputIndices =\n        helper.GetRepeatedArgument<int>(\"grad_input_indices\");\n    std::vector<std::string> gradientInputs;\n    for (int i = 0; i < def_.input_size(); ++i) {\n      gradientInputs.push_back(I(i));\n    }\n    for (int i = 0; i < def_.output_size(); ++i) {\n      gradientInputs.push_back(O(i));\n    }\n    if (gradOutputIndices.size() > 0) {\n      for (int i = 0; i < gradOutputIndices.size(); ++i) {\n        int GO_i = gradOutputIndices[i];\n        gradientInputs.push_back(GO(GO_i));\n      }\n    } else {\n      for (int i = 0; i < def_.output_size(); ++i) {\n        gradientInputs.push_back(GO(i));\n      }\n    }\n    std::vector<std::string> gradientOutputs;\n    if (gradInputIndices.size() > 0) {\n      for (int i = 0; i < gradInputIndices.size(); ++i) {\n        int GI_i = gradInputIndices[i];\n        gradientOutputs.push_back(GI(GI_i));\n      }\n    } else {\n      for (int i = 0; i < def_.input_size(); ++i) {\n        gradientOutputs.push_back(GI(i));\n      }\n    }\n\n    std::string grad_op_name = \"PythonGradient\";\n    if (Def().type() == \"PythonDLPack\") {\n      grad_op_name = \"PythonDLPackGradient\";\n    }\n    return SingleGradientDef(grad_op_name, \"\", gradientInputs, gradientOutputs);\n  }\n};\n\nREGISTER_CPU_OPERATOR(Python, PythonOp<CPUContext, false>);\nREGISTER_CPU_OPERATOR(PythonGradient, PythonGradientOp<CPUContext, false>);\n// Always allow running in-place\nOPERATOR_SCHEMA(Python).AllowInplace([](int, int) { return true; });\nOPERATOR_SCHEMA(PythonGradient).AllowInplace([](int, int) { return true; });\nREGISTER_GRADIENT(Python, GetPythonGradient);\n\nREGISTER_CPU_OPERATOR(PythonDLPack, PythonOp<CPUContext, true>);\nREGISTER_CPU_OPERATOR(PythonDLPackGradient, PythonGradientOp<CPUContext, true>);\nOPERATOR_SCHEMA(PythonDLPack).AllowInplace([](int, int) { return true; });\nOPERATOR_SCHEMA(PythonDLPackGradient).AllowInplace([](int, int) {\n  return true;\n});\nREGISTER_GRADIENT(PythonDLPack, GetPythonGradient);\n\nstatic bool ParseProtobufFromLargeString(const string& str, Message* proto) {\n  ::google::protobuf::io::ArrayInputStream input_stream(str.data(), str.size());\n  ::google::protobuf::io::CodedInputStream coded_stream(&input_stream);\n  // Set PlanDef message size limit to 1G.\n  coded_stream.SetTotalBytesLimit(1024LL << 20, 512LL << 20);\n  return proto->ParseFromCodedStream(&coded_stream);\n}\n\nvoid addObjectMethods(py::module& m) {\n  py::class_<NetBase>(m, \"Net\").def(\"run\", [](NetBase* net) {\n    py::gil_scoped_release g;\n    CAFFE_ENFORCE(net->Run());\n  });\n\n  py::class_<ObserverBase<NetBase>>(m, \"Observer\")\n      .def(\n          \"average_time\",\n          [](ObserverBase<NetBase>* ob) {\n            auto* cast_ob = dynamic_cast_if_rtti<TimeObserver*>(ob);\n            CAFFE_ENFORCE(\n                cast_ob, \"Observer does not implement this function.\");\n            return cast_ob->average_time();\n          })\n      .def(\n          \"average_time_children\",\n          [](ObserverBase<NetBase>* ob) {\n            auto* cast_ob = dynamic_cast_if_rtti<TimeObserver*>(ob);\n            CAFFE_ENFORCE(\n                cast_ob, \"Observer does not implement this function.\");\n            return cast_ob->average_time_children();\n          })\n      .def(\"debug_info\", [](ObserverBase<NetBase>* ob) {\n        return ob->debugInfo();\n      });\n\n  py::class_<Blob>(m, \"Blob\")\n      .def(\n          \"serialize\",\n          [](const Blob& blob, const std::string& name) -> py::bytes {\n            return blob.Serialize(name);\n          })\n      .def(\n          \"deserialize\",\n          [](Blob* blob, py::bytes serialized) {\n            blob->Deserialize(serialized);\n          })\n      .def(\n          \"fetch\",\n          [](const Blob& blob) {\n            auto fetcher = CreateFetcher(blob.meta().id());\n            CAFFE_ENFORCE(\n                fetcher,\n                \"Could not fetch for blob of type: \",\n                blob.meta().name());\n            return fetcher->Fetch(blob);\n          })\n      .def(\n          \"tensor\",\n          [](Blob* blob) { return py::cast(blob->GetMutable<TensorCPU>()); },\n          py::return_value_policy::reference_internal)\n      .def(\n          \"_feed\",\n          [](Blob* blob,\n             const py::object& arg,\n             const py::object device_option) {\n            DeviceOption option;\n            if (!device_option.is(py::none())) {\n              // If we have a device option passed in, read it.\n              CAFFE_ENFORCE(ParseProtobufFromLargeString(\n                  py::bytes(device_option).cast<std::string>(), &option));\n            }\n            if (PyArray_Check(arg.ptr())) { // numpy array\n              PyArrayObject* array =\n                  reinterpret_cast<PyArrayObject*>(arg.ptr());\n              auto feeder = CreateFeeder(option.device_type());\n              CAFFE_ENFORCE(\n                  feeder, \"Unknown device type encountered in FeedBlob.\");\n              feeder->Feed(option, array, blob);\n              return true;\n            }\n\n            if (PyBytes_Check(arg.ptr()) || PyUnicode_Check(arg.ptr())) {\n              *blob->GetMutable<std::string>() = arg.cast<std::string>();\n              return true;\n            }\n            CAFFE_THROW(\n                \"Unexpected type of argument - only numpy array or string are \"\n                \"supported for feeding\");\n          },\n          \"Feed an input array or string, with the (optional) DeviceOption\",\n          py::arg(\"arg\"),\n          py::arg(\"device_option\") = py::none());\n\n  py::class_<DLPackWrapper<CPUContext>>(m, \"DLPackTensorCPU\")\n      .def_property_readonly(\n          \"data\",\n          [](DLPackWrapper<CPUContext>* t) -> py::object {\n            CAFFE_ENFORCE_EQ(\n                t->device_option.device_type(),\n                CPU,\n                \"Expected CPU device option for CPU tensor\");\n            return t->data();\n          },\n          \"Return DLPack tensor with tensor's data.\")\n      .def(\n          \"feed\",\n          [](DLPackWrapper<CPUContext>* t, py::object obj) {\n            CAFFE_ENFORCE_EQ(\n                t->device_option.device_type(),\n                CPU,\n                \"Expected CPU device option for CPU tensor\");\n            t->feed(obj);\n          },\n          \"Copy data from given DLPack tensor into this tensor.\")\n      .def_property_readonly(\n          \"_shape\",\n          [](const DLPackWrapper<CPUContext>& t) {\n            auto* tensor = t.tensor;\n            return tensor->dims();\n          })\n      .def(\n          \"_reshape\",\n          [](DLPackWrapper<CPUContext>* t, std::vector<TIndex> dims) {\n            auto* tensor = t->tensor;\n            tensor->Resize(dims);\n          });\n\n  py::class_<TensorCPU>(m, \"TensorCPU\")\n      .def_property_readonly(\n          \"data\",\n          [](TensorCPU* t) -> py::object {\n            if (t->meta() == TypeMeta{}) {\n              // keep this behavior for backward compatibility\n              t->mutable_data<float>();\n            }\n            auto res = TensorFetcher<CPUContext>().FetchTensor(*t, false);\n            return res.obj;\n          },\n          \"Return numpy array pointing to this tensor's data if possible. \"\n          \"Otherwise (e.g. for strings) copies the data (same as fetch).\")\n      .def(\n          \"feed\",\n          [](TensorCPU* t, py::object obj) {\n            if (!PyArray_Check(obj.ptr())) {\n              CAFFE_THROW(\n                  \"Unexpected type of argument -- expected numpy array\");\n            }\n            TensorFeeder<CPUContext>().FeedTensor(\n                DeviceOption{}, reinterpret_cast<PyArrayObject*>(obj.ptr()), t);\n          },\n          \"Copy data from given numpy array into this tensor.\")\n      .def(\n          \"fetch\",\n          [](TensorCPU* t) {\n            auto res = TensorFetcher<CPUContext>().FetchTensor(*t, true);\n            return res.obj;\n          },\n          \"Copy data from this tensor into a new numpy array.\")\n      .def(\n          \"init\",\n          [](TensorCPU* t, std::vector<TIndex> dims, int caffe_type) {\n            const auto& meta =\n                DataTypeToTypeMeta((TensorProto::DataType)caffe_type);\n            CAFFE_ENFORCE(\n                !TensorFetcher<CPUContext>().NeedsCopy(meta),\n                \"Cannot init tensor of this type. Use `feed` instead.\");\n            t->Resize(dims);\n            t->raw_mutable_data(meta);\n          },\n          \"Initialize this tensor to given shape and data type. \"\n          \"Fail if the given data type cannot be accessed from python.\")\n      .def_property_readonly(\n          \"_shape\", [](const TensorCPU& t) { return t.dims(); })\n      .def(\"_reshape\", [](TensorCPU* t, std::vector<TIndex> dims) {\n        t->Resize(dims);\n      });\n\n  py::class_<Workspace>(m, \"Workspace\")\n      .def(py::init<>())\n      .def(py::init<Workspace*>())\n      .def_property_readonly(\n          \"nets\",\n          [](Workspace* self) {\n            CHECK_NOTNULL(self);\n            std::map<std::string, py::object> nets;\n            for (const auto& name : self->Nets()) {\n              LOG(INFO) << \"name: \" << name;\n              nets[name] = py::cast(self->GetNet(name));\n            }\n            return nets;\n          },\n          py::return_value_policy::reference_internal)\n      .def_property_readonly(\n          \"blobs\",\n          [](Workspace* self) {\n            CHECK_NOTNULL(self);\n            std::map<std::string, py::object> blobs;\n            for (const auto& name : self->Blobs()) {\n              blobs[name] = py::cast(self->GetBlob(name));\n            }\n            return blobs;\n          },\n          py::return_value_policy::reference_internal)\n      .def(\n          \"_create_net\",\n          [](Workspace* self, py::bytes def, bool overwrite) -> py::object {\n            caffe2::NetDef proto;\n            CAFFE_ENFORCE(\n                ParseProtobufFromLargeString(def.cast<std::string>(), &proto));\n            NetBase* net = self->CreateNet(proto, overwrite);\n            CAFFE_ENFORCE(net);\n            return py::cast(net);\n          },\n          py::return_value_policy::reference_internal,\n          py::arg(\"def\"),\n          py::arg(\"overwrite\") = kPyBindFalse)\n      .def(\n          \"create_blob\",\n          [](Workspace* self, const std::string& name) -> py::object {\n            return py::cast(self->CreateBlob(name));\n          },\n          py::return_value_policy::reference_internal)\n      .def(\"fetch_blob\", &python_detail::fetchBlob)\n      .def(\n          \"has_blob\",\n          [](Workspace* self, const std::string& name) {\n            return self->HasBlob(name);\n          })\n      .def(\n          \"_run_net\",\n          [](Workspace* self, py::bytes def) {\n            caffe2::NetDef proto;\n            CAFFE_ENFORCE(\n                ParseProtobufFromLargeString(def.cast<std::string>(), &proto));\n            py::gil_scoped_release g;\n            CAFFE_ENFORCE(self->RunNetOnce(proto));\n          })\n      .def(\n          \"_run_operator\",\n          [](Workspace* self, py::bytes def) {\n            caffe2::OperatorDef proto;\n            CAFFE_ENFORCE(\n                ParseProtobufFromLargeString(def.cast<std::string>(), &proto));\n            py::gil_scoped_release g;\n            CAFFE_ENFORCE(self->RunOperatorOnce(proto));\n          })\n      .def(\n          \"_run_plan\",\n          [](Workspace* self, py::bytes def) {\n            caffe2::PlanDef proto;\n            CAFFE_ENFORCE(\n                ParseProtobufFromLargeString(def.cast<std::string>(), &proto));\n            py::gil_scoped_release g;\n            CAFFE_ENFORCE(self->RunPlan(proto));\n          })\n      .def(\n          \"_last_failed_op_net_position\",\n          [](Workspace* self) {\n            CAFFE_ENFORCE(self);\n            return (int)self->last_failed_op_net_position;\n          })\n      .def_property_readonly_static(\"current\", [](py::object /* type */) {\n        auto ws = gWorkspaces.find(gCurrentWorkspaceName);\n        CAFFE_ENFORCE(ws != gWorkspaces.end());\n        CAFFE_ENFORCE(ws->second.get());\n        return py::cast(ws->second.get(), py::return_value_policy::reference);\n      });\n\n  // Gradients\n  py::class_<GradientWrapper>(m, \"GradientWrapper\")\n      .def(py::init<>())\n      .def_readwrite(\"dense\", &GradientWrapper::dense_)\n      .def_readwrite(\"indices\", &GradientWrapper::indices_)\n      .def_readwrite(\"values\", &GradientWrapper::values_)\n      .def(\"is_sparse\", &GradientWrapper::IsSparse)\n      .def(\"is_dense\", &GradientWrapper::IsDense)\n      .def(\"is_empty\", &GradientWrapper::IsEmpty);\n\n  m.def(\n      \"get_gradient_defs\",\n      [](py::bytes op_def, std::vector<GradientWrapper> output_gradients) {\n        OperatorDef def;\n        CAFFE_ENFORCE(\n            ParseProtobufFromLargeString(op_def.cast<std::string>(), &def));\n        CAFFE_ENFORCE(caffe2::GradientRegistry()->Has(def.type()));\n        const auto& meta = GetGradientForOp(def, output_gradients);\n        std::vector<py::bytes> grad_ops;\n        for (const auto& op : meta.ops_) {\n          grad_ops.push_back(op.SerializeAsString());\n        }\n        return std::pair<std::vector<py::bytes>, std::vector<GradientWrapper>>{\n            grad_ops, meta.g_input_};\n      },\n      pybind11::return_value_policy::copy);\n\n  // DB\n  py::class_<db::Transaction>(m, \"Transaction\")\n      .def(\"put\", &db::Transaction::Put)\n      .def(\"commit\", &db::Transaction::Commit);\n  py::class_<db::Cursor>(m, \"Cursor\")\n      .def(\"supports_seek\", &db::Cursor::SupportsSeek)\n      .def(\"seek_to_first\", &db::Cursor::SeekToFirst)\n      .def(\"next\", &db::Cursor::Next)\n      .def(\"key\", [](db::Cursor* self) -> py::bytes { return self->key(); })\n      .def(\"value\", [](db::Cursor* self) -> py::bytes { return self->value(); })\n      .def(\"valid\", &db::Cursor::Valid);\n  py::enum_<db::Mode>(m, \"Mode\")\n      .value(\"read\", db::Mode::READ)\n      .value(\"write\", db::Mode::WRITE)\n      .value(\"new\", db::Mode::NEW)\n      .export_values();\n  py::class_<db::DB /*, std::unique_ptr<DB>*/>(m, \"DB\")\n      .def(\"new_transaction\", &db::DB::NewTransaction)\n      .def(\"new_cursor\", &db::DB::NewCursor)\n      .def(\"close\", &db::DB::Close);\n  m.def(\"create_db\", &db::CreateDB);\n  m.def(\"registered_dbs\", []() {\n    return caffe2::db::Caffe2DBRegistry()->Keys();\n  });\n\n  // OpSchema\n  py::class_<OpSchema> op_schema(m, \"OpSchema\");\n  op_schema.def_property_readonly(\"file\", &OpSchema::file)\n      .def_property_readonly(\"line\", &OpSchema::line)\n      .def_property_readonly(\"private\", &OpSchema::private_op)\n      .def_property_readonly(\n          \"doc\", &OpSchema::doc, py::return_value_policy::reference)\n      .def_property_readonly(\"args\", &OpSchema::args)\n      .def_property_readonly(\"input_desc\", &OpSchema::input_desc)\n      .def_property_readonly(\"output_desc\", &OpSchema::output_desc)\n      .def_property_readonly(\"max_input\", &OpSchema::max_input)\n      .def_property_readonly(\"max_output\", &OpSchema::max_output)\n      .def_property_readonly(\"min_input\", &OpSchema::min_input)\n      .def_property_readonly(\"min_output\", &OpSchema::min_output)\n      .def_property_readonly(\"inf\", &OpSchema::inf)\n      // Note: this does not work yet, we will need to figure out how to pass\n      // protobuf objects.\n      .def(\"infer_tensor\", &OpSchema::InferTensor)\n      .def(\"CalculateOutput\", &OpSchema::CalculateOutput)\n      .def(\"num_inputs_allowed\", &OpSchema::num_inputs_allowed)\n      .def(\"num_outputs_allowed\", &OpSchema::num_outputs_allowed)\n      .def(\"num_inputs_outputs_allowed\", &OpSchema::num_inputs_outputs_allowed)\n      .def_static(\n          \"get\", &OpSchemaRegistry::Schema, py::return_value_policy::reference)\n      .def_static(\n          \"get_cpu_impl\",\n          DefinitionGetter(CPUOperatorRegistry()),\n          py::return_value_policy::reference)\n      .def_static(\n          \"get_cuda_impl\",\n          DefinitionGetter(CUDAOperatorRegistry()),\n          py::return_value_policy::reference)\n      .def_static(\n          \"get_gradient_impl\",\n          DefinitionGetter(GradientRegistry()),\n          py::return_value_policy::reference);\n\n  py::class_<OpSchema::Argument>(op_schema, \"Argument\")\n      .def_property_readonly(\"name\", &OpSchema::Argument::name)\n      .def_property_readonly(\"description\", &OpSchema::Argument::description)\n      .def_property_readonly(\"required\", &OpSchema::Argument::is_required);\n\n  py::class_<caffe2::onnx::Caffe2Ops>(m, \"Caffe2Ops\")\n      .def(py::init([](const std::vector<py::bytes>& init_ops,\n                       const std::vector<py::bytes>& ops,\n                       const std::vector<std::string>& interface_blobs) {\n        auto* c2ops = new caffe2::onnx::Caffe2Ops();\n        for (const auto& s : init_ops) {\n          ParseProtobufFromLargeString(\n              s.cast<std::string>(), c2ops->init_ops.Add());\n        }\n        for (const auto& s : ops) {\n          ParseProtobufFromLargeString(s.cast<std::string>(), c2ops->ops.Add());\n        }\n        for (const auto& s : interface_blobs) {\n          auto* tmp = c2ops->interface_blobs.Add();\n          *tmp = s;\n        }\n        return c2ops;\n      }));\n\n  py::class_<caffe2::onnx::Caffe2BackendRep>(m, \"Caffe2BackenRep\")\n      .def(py::init<>())\n      .def(\n          \"init_net\",\n          [](caffe2::onnx::Caffe2BackendRep& instance) {\n            const auto& init_net = instance.init_net();\n            std::string out;\n            init_net.SerializeToString(&out);\n            return py::bytes(out);\n          })\n\n      .def(\n          \"pred_net\",\n          [](caffe2::onnx::Caffe2BackendRep& instance) {\n            const auto& pred_net = instance.pred_net();\n            std::string out;\n            pred_net.SerializeToString(&out);\n            return py::bytes(out);\n          })\n      .def(\n          \"external_outputs\",\n          [](caffe2::onnx::Caffe2BackendRep& instance) {\n            std::vector<std::string> outputs;\n            for (const auto& o : instance.pred_net().external_output()) {\n              outputs.emplace_back(o);\n            }\n            return outputs;\n          })\n      .def(\n          \"external_inputs\",\n          [](caffe2::onnx::Caffe2BackendRep& instance) {\n            std::vector<std::string> inputs;\n            for (const auto& o : instance.pred_net().external_input()) {\n              inputs.emplace_back(o);\n            }\n            return inputs;\n          })\n      .def(\n          \"uninitialized_inputs\",\n          [](caffe2::onnx::Caffe2BackendRep& instance) {\n            return instance.uninitialized_inputs();\n          })\n      .def(\n          \"run\",\n          [](caffe2::onnx::Caffe2BackendRep& instance,\n             std::map<std::string, py::object> inputs)\n              -> std::vector<py::object> {\n            Predictor::TensorMap tensors;\n            std::map<std::string, TensorCPU> tensors_data{};\n            for (const auto pair : inputs) {\n              const auto& name = pair.first;\n              const auto& input = pair.second;\n              CAFFE_ENFORCE(\n                  PyArray_Check(input.ptr()),\n                  \"Input must be of type numpy array.\");\n              PyArrayObject* array =\n                  reinterpret_cast<PyArrayObject*>(input.ptr());\n              TensorFeeder<CPUContext>().FeedTensor(\n                  DeviceOption(), array, &tensors_data[name]);\n              tensors.insert(std::make_pair(name, &tensors_data[name]));\n            }\n\n\n            std::vector<TensorCPU*> out;\n            instance.RunMap(tensors, &out);\n            std::vector<py::object> pyout;\n            for (auto t : out) {\n              pyout.push_back(\n                  TensorFetcher<CPUContext>().FetchTensor(*t, true).obj);\n            }\n            return pyout;\n          })\n      .def(\n          \"run\",\n          [](caffe2::onnx::Caffe2BackendRep& instance,\n             std::vector<py::object> inputs)\n              -> std::vector<py::object> {\n            Predictor::TensorVector tensors;\n            std::vector<TensorCPU> tensors_data(inputs.size());\n            for (auto i = 0; i < inputs.size(); ++i) {\n              auto input = inputs[i];\n              CAFFE_ENFORCE(\n                  PyArray_Check(input.ptr()),\n                  \"Input must be of type numpy array.\");\n              PyArrayObject* array =\n                  reinterpret_cast<PyArrayObject*>(input.ptr());\n              TensorFeeder<CPUContext>().FeedTensor(\n                  DeviceOption(), array, &(tensors_data[i]));\n              tensors.push_back(&(tensors_data[i]));\n            }\n            std::vector<TensorCPU*> out;\n            instance.Run(tensors, &out);\n            std::vector<py::object> pyout;\n            for (auto t : out) {\n              pyout.push_back(\n                  TensorFetcher<CPUContext>().FetchTensor(*t, true).obj);\n            }\n            return pyout;\n          });\n\n  py::class_<caffe2::onnx::Caffe2Backend>(m, \"Caffe2Backend\")\n      .def(py::init<>())\n      .def(\"prepare\",\n           [](caffe2::onnx::Caffe2Backend &instance,\n              const py::bytes &onnx_model_str, const std::string &device,\n              const std::vector<caffe2::onnx::Caffe2Ops> &extras) {\n             auto *rep = instance.Prepare(onnx_model_str.cast<std::string>(),\n                                          device, extras);\n             return rep;\n           })\n      .def(\"convert_node\",\n           [](caffe2::onnx::Caffe2Backend &instance, const py::bytes &node_str,\n              int opset_version) -> std::vector<py::bytes> {\n             auto c2ops = instance.ConvertNode(node_str.cast<std::string>(),\n                                               opset_version);\n             std::vector<py::bytes> vals;\n             for (const auto &init_op : c2ops.init_ops) {\n               std::string out;\n               init_op.SerializeToString(&out);\n               vals.emplace_back(py::bytes(out));\n             }\n             for (const auto &op : c2ops.ops) {\n               std::string out;\n               op.SerializeToString(&out);\n               vals.emplace_back(py::bytes(out));\n             }\n             return vals;\n           });\n\n  py::class_<Predictor>(m, \"Predictor\")\n      .def(\n          py::init([](py::bytes init_net, py::bytes predict_net) {\n            CAFFE_ENFORCE(gWorkspace);\n            NetDef init_net_, predict_net_;\n            CAFFE_ENFORCE(ParseProtobufFromLargeString(\n                init_net.cast<std::string>(), &init_net_));\n            CAFFE_ENFORCE(ParseProtobufFromLargeString(\n                predict_net.cast<std::string>(), &predict_net_));\n            return new Predictor(init_net_, predict_net_, gWorkspace);\n          }))\n      .def(\n          \"run\",\n          [](Predictor& instance,\n             std::vector<py::object> inputs) -> std::vector<py::object> {\n            Predictor::TensorVector tensors;\n            std::vector<TensorCPU> tensors_data(inputs.size());\n            for (auto i = 0; i < inputs.size(); ++i) {\n              auto input = inputs[i];\n              CAFFE_ENFORCE(\n                  PyArray_Check(input.ptr()),\n                  \"Input must be of type numpy array.\");\n              PyArrayObject* array =\n                  reinterpret_cast<PyArrayObject*>(input.ptr());\n              TensorFeeder<CPUContext>().FeedTensor(\n                  DeviceOption(), array, &(tensors_data[i]));\n              tensors.push_back(&(tensors_data[i]));\n            }\n            std::vector<TensorCPU*> out;\n            instance.run(tensors, &out);\n            std::vector<py::object> pyout;\n            for (auto t : out) {\n              pyout.push_back(\n                  TensorFetcher<CPUContext>().FetchTensor(*t, true).obj);\n            }\n            return pyout;\n          })\n      .def(\n          \"run\",\n          [](Predictor& instance, std::map<std::string, py::object> inputs)\n              -> std::vector<py::object> {\n            Predictor::TensorMap tensors;\n            std::map<std::string, TensorCPU> tensors_data{};\n            for (const auto pair : inputs) {\n              const auto& name = pair.first;\n              const auto& input = pair.second;\n              CAFFE_ENFORCE(\n                  PyArray_Check(input.ptr()),\n                  \"Input must be of type numpy array.\");\n              PyArrayObject* array =\n                  reinterpret_cast<PyArrayObject*>(input.ptr());\n              TensorFeeder<CPUContext>().FeedTensor(\n                  DeviceOption(), array, &tensors_data[name]);\n              tensors.insert(std::make_pair(name, &tensors_data[name]));\n            }\n            std::vector<TensorCPU*> out;\n            instance.run_map(tensors, &out);\n            std::vector<py::object> pyout;\n            for (auto t : out) {\n              pyout.push_back(\n                  TensorFetcher<CPUContext>().FetchTensor(*t, true).obj);\n            }\n            return pyout;\n          });\n\n  py::class_<script::CompilationUnit>(m, \"CompilationUnit\")\n      .def(py::init<>())\n      .def(\"define\", &script::CompilationUnit::define)\n      .def(\"get_proto\", &script::CompilationUnit::getProto)\n      .def(\n          \"create_net\",\n          [](script::CompilationUnit* self, const std::string& name) {\n            auto net = self->createNet(gWorkspace, name);\n            CAFFE_ENFORCE(net);\n            return net;\n          })\n      .def(\n          \"extern\",\n          [](script::CompilationUnit* self,\n             const std::string& name,\n             py::object py_proto) {\n            py::bytes bytes = py_proto.attr(\"SerializeToString\")();\n            std::unique_ptr<caffe2::NetDef> proto(new NetDef());\n            CAFFE_ENFORCE(ParseProtobufFromLargeString(\n                bytes.cast<std::string>(), proto.get()));\n            self->defineExtern(name, std::move(proto));\n          });\n}\n\nvoid addGlobalMethods(py::module& m) {\n  m.attr(\"is_asan\") = py::bool_(CAFFE2_ASAN_ENABLED);\n  m.def(\"get_build_options\", []() { return GetBuildOptions(); });\n\n  m.attr(\"has_mkldnn\") = py::bool_(\n#ifdef CAFFE2_HAS_MKL_DNN\n      true\n#else // CAFFE2_HAS_MKL_DNN\n      false\n#endif // CAFFE2_HAS_MKL_DNN\n      );\n\n  m.attr(\"define_caffe2_no_operator_schema\") = py::bool_(\n#ifdef CAFFE2_NO_OPERATOR_SCHEMA\n      true\n#else // CAFFE2_NO_OPERATOR_SCHEMA\n      false\n#endif // CAFFE2_NO_OPERATOR_SCHEMA\n      );\n\n  m.def(\"set_per_op_engine_pref\", [](const PerOpEnginePrefType& pref) -> void {\n    caffe2::SetPerOpEnginePref(pref);\n  });\n\n  m.def(\"set_global_engine_pref\", [](const GlobalEnginePrefType& pref) -> void {\n    caffe2::SetGlobalEnginePref(pref);\n  });\n  m.def(\n      \"set_engine_pref\",\n      [](const PerOpEnginePrefType& per_op_pref,\n         const GlobalEnginePrefType& global_pref) -> void {\n        caffe2::SetEnginePref(per_op_pref, global_pref);\n      });\n  m.def(\n      \"set_op_engine_pref\",\n      [](const std::string& op_type,\n         const CaffeMap<int, EnginePrefType>& op_pref) -> void {\n        caffe2::SetOpEnginePref(op_type, op_pref);\n      });\n\n  m.def(\n      \"op_registry_key\",\n      [](const std::string& op_type,\n         const std::string& engine) -> const std::string {\n        return caffe2::OpRegistryKey(op_type, engine);\n      });\n  m.def(\"global_init\", [](std::vector<std::string> args) -> void {\n    int argc = args.size();\n    std::vector<char*> argv;\n    for (auto& arg : args) {\n      argv.push_back(const_cast<char*>(arg.data()));\n    }\n    char** pargv = argv.data();\n    CAFFE_ENFORCE(caffe2::GlobalInit(&argc, &pargv));\n  });\n\n  m.def(\"registered_operators\", []() {\n    std::set<string> all_keys = caffe2::GetRegisteredOperators();\n\n    // Ensure we are lexicographically ordered.\n    std::vector<std::string> keys;\n    for (const auto& key : all_keys) {\n      keys.push_back(key);\n    }\n    return keys;\n  });\n  m.def(\"on_module_exit\", []() { gWorkspaces.clear(); });\n  // create_if_missing not used by necessary for pybind to do\n  // properly do function overloading.\n  m.def(\n      \"switch_workspace\",\n      [](Workspace* ws, py::object /*create_if_missing*/) { gWorkspace = ws; });\n  m.def(\n      \"switch_workspace\",\n      [](const std::string& name, const py::object create_if_missing) {\n        if (create_if_missing.is(py::none())) {\n          return switchWorkspaceInternal(name, false);\n        }\n        return switchWorkspaceInternal(name, create_if_missing.cast<bool>());\n      },\n      \"Switch to the specified workspace, creating if necessary\",\n      py::arg(\"name\"),\n      py::arg(\"create_if_missing\") = py::none());\n  m.def(\n      \"reset_workspace\",\n      [](const py::object& root_folder) {\n        VLOG(1) << \"Resetting workspace.\";\n        if (root_folder.is(py::none())) {\n          gWorkspaces[gCurrentWorkspaceName].reset(new Workspace());\n        } else {\n          gWorkspaces[gCurrentWorkspaceName].reset(\n              new Workspace(root_folder.cast<std::string>()));\n        }\n        gWorkspace = gWorkspaces[gCurrentWorkspaceName].get();\n        return true;\n      },\n      \"Reset the workspace\",\n      py::arg(\"root_folder\") = py::none());\n\n  m.def(\"root_folder\", []() {\n    CAFFE_ENFORCE(gWorkspace);\n    return gWorkspace->RootFolder();\n  });\n  m.def(\"current_workspace\", []() { return gCurrentWorkspaceName; });\n  m.def(\"workspaces\", []() {\n    std::vector<std::string> names;\n    for (const auto& kv : gWorkspaces) {\n      names.push_back(kv.first);\n    }\n    return names;\n  });\n  m.def(\"nearby_opnames\", [](const std::string& name) {\n    std::vector<std::string> alternatives;\n    int editTolerance = 3;\n    for (auto it : caffe2::CPUOperatorRegistry()->Keys()) {\n      if (editDistance(it, name, editTolerance) < editTolerance + 1) {\n        alternatives.push_back(it);\n      }\n    }\n    return alternatives;\n  });\n  m.def(\"local_blobs\", []() {\n    CAFFE_ENFORCE(gWorkspace);\n    return gWorkspace->LocalBlobs();\n  });\n  m.def(\"blobs\", []() {\n    CAFFE_ENFORCE(gWorkspace);\n    return gWorkspace->Blobs();\n  });\n  m.def(\"has_blob\", [](const std::string& name) {\n    CAFFE_ENFORCE(gWorkspace);\n    return gWorkspace->HasBlob(name);\n  });\n  m.def(\n      \"create_net\",\n      [](py::bytes net_def, bool overwrite) {\n        CAFFE_ENFORCE(gWorkspace);\n        caffe2::NetDef proto;\n        CAFFE_ENFORCE(\n            ParseProtobufFromLargeString(net_def.cast<std::string>(), &proto),\n            \"Can't parse net proto: \",\n            net_def.cast<std::string>());\n        CAFFE_ENFORCE(\n            gWorkspace->CreateNet(proto, overwrite),\n            \"Error creating net with proto: \",\n            net_def.cast<std::string>());\n        return true;\n      },\n      py::arg(\"net_def\"),\n      py::arg(\"overwrite\") = kPyBindFalse);\n  m.def(\"run_net\", [](const std::string& name, int num_iter, bool allow_fail) {\n    CAFFE_ENFORCE(gWorkspace);\n    CAFFE_ENFORCE(gWorkspace->GetNet(name), \"Can't find net \", name);\n    py::gil_scoped_release g;\n    for (int i = 0; i < num_iter; i++) {\n      bool success = gWorkspace->RunNet(name);\n      if (!allow_fail) {\n        CAFFE_ENFORCE(success, \"Error running net \", name);\n      } else {\n        if (!success) {\n          return false;\n        }\n      }\n    }\n    return true;\n  });\n  m.def(\n      \"add_observer_to_net\",\n      [](const std::string& net_name, const std::string& observer_type) {\n        CAFFE_ENFORCE(gWorkspace);\n        CAFFE_ENFORCE(\n            gWorkspace->GetNet(net_name), \"Can't find net \", net_name);\n        py::gil_scoped_release g;\n\n        NetBase* net = gWorkspace->GetNet(net_name);\n        const Observable<NetBase>::Observer* observer = nullptr;\n\n#define REGISTER_PYTHON_EXPOSED_OBSERVER(ob_type)             \\\n  {                                                           \\\n    if (observer_type.compare(#ob_type) == 0) {               \\\n      unique_ptr<ob_type> net_ob = make_unique<ob_type>(net); \\\n      observer = net->AttachObserver(std::move(net_ob));      \\\n    }                                                         \\\n  }\n\n        REGISTER_PYTHON_EXPOSED_OBSERVER(TimeObserver);\n#undef REGISTER_PYTHON_EXPOSED_OBSERVER\n\n        if (observer_type.compare(\"RunCountObserver\") == 0) {\n          unique_ptr<RunCountNetObserver> net_ob =\n              make_unique<RunCountNetObserver>(net);\n          observer = net->AttachObserver(std::move(net_ob));\n        }\n\n        CAFFE_ENFORCE(observer != nullptr);\n        return py::cast(observer);\n      });\n  m.def(\n      \"remove_observer_from_net\",\n      [](const std::string& net_name, const ObserverBase<NetBase>* observer) {\n        CAFFE_ENFORCE(gWorkspace);\n        CAFFE_ENFORCE(\n            gWorkspace->GetNet(net_name), \"Can't find net \", net_name);\n        py::gil_scoped_release g;\n\n        NetBase* net = gWorkspace->GetNet(net_name);\n        net->DetachObserver(observer);\n      });\n  m.def(\"num_observers_on_net\", [](const std::string& net_name) {\n    CAFFE_ENFORCE(gWorkspace);\n    CAFFE_ENFORCE(gWorkspace->GetNet(net_name), \"Can't find net \", net_name);\n    py::gil_scoped_release g;\n\n    NetBase* net = gWorkspace->GetNet(net_name);\n    return net->NumObservers();\n  });\n  m.def(\n      \"benchmark_net\",\n      [](const std::string& name,\n         size_t warmup_runs,\n         size_t main_runs,\n         bool run_individual) {\n        CAFFE_ENFORCE(gWorkspace);\n        auto* net = gWorkspace->GetNet(name);\n        CAFFE_ENFORCE(net, \"Didn't find net: \", name);\n        py::gil_scoped_release g;\n        vector<float> stat =\n            net->TEST_Benchmark(warmup_runs, main_runs, run_individual);\n        return stat;\n      });\n\n  m.def(\"delete_net\", [](const std::string& name) {\n    CAFFE_ENFORCE(gWorkspace);\n    gWorkspace->DeleteNet(name);\n    return true;\n  });\n  m.def(\"nets\", []() { return gWorkspace->Nets(); });\n  m.def(\"run_operator_once\", [](const py::bytes& op_def) {\n    CAFFE_ENFORCE(gWorkspace);\n    OperatorDef def;\n    CAFFE_ENFORCE(\n        ParseProtobufFromLargeString(op_def.cast<std::string>(), &def));\n    py::gil_scoped_release g;\n    CAFFE_ENFORCE(gWorkspace->RunOperatorOnce(def));\n    return true;\n  });\n  m.def(\n      \"get_operator_cost\",\n      [](const py::bytes& op_def, const std::vector<string>& input_blobs) {\n        CAFFE_ENFORCE(gWorkspace);\n        OperatorDef def;\n        CAFFE_ENFORCE(\n            ParseProtobufFromLargeString(op_def.cast<std::string>(), &def),\n            \"Couldn't parse operator proto.\");\n        const auto op_type = def.type();\n        auto* schema = OpSchemaRegistry::Schema(op_type);\n        CAFFE_ENFORCE(schema);\n        vector<TensorShape> shapes;\n        for (const auto& blob_name : input_blobs) {\n          auto* blob = gWorkspace->GetBlob(blob_name);\n          shapes.emplace_back(GetTensorShapeOfBlob(blob));\n        }\n        const auto c = schema->InferCost(def, shapes);\n        return std::make_tuple(c.flops, c.bytes_moved);\n      });\n  m.def(\"run_net_once\", [](const py::bytes& net_def) {\n    CAFFE_ENFORCE(gWorkspace);\n    NetDef def;\n    CAFFE_ENFORCE(\n        ParseProtobufFromLargeString(net_def.cast<std::string>(), &def));\n    py::gil_scoped_release g;\n    CAFFE_ENFORCE(gWorkspace->RunNetOnce(def));\n    return true;\n  });\n  m.def(\"run_plan\", [](const py::bytes& plan_def) {\n    CAFFE_ENFORCE(gWorkspace);\n    PlanDef def;\n    CAFFE_ENFORCE(\n        ParseProtobufFromLargeString(plan_def.cast<std::string>(), &def));\n    py::gil_scoped_release g;\n    CAFFE_ENFORCE(gWorkspace->RunPlan(def));\n    return true;\n  });\n  m.def(\n      \"apply_transform\",\n      [](const string& transform_key, const py::bytes& net_def) {\n        NetDef def;\n        CAFFE_ENFORCE(\n            ParseProtobufFromLargeString(net_def.cast<std::string>(), &def));\n        py::gil_scoped_release g;\n\n        auto transformed_net = ApplyTransform(transform_key, def);\n\n        std::string protob;\n        CAFFE_ENFORCE(transformed_net.SerializeToString(&protob));\n        return py::bytes(protob);\n      });\n  m.def(\n      \"apply_transform_if_faster\",\n      [](const string& transform_key,\n         const py::bytes& net_def_bytes,\n         const py::bytes& init_def_bytes,\n         int warmup_runs,\n         int main_runs,\n         double improvement_threshold) {\n        NetDef def;\n        CAFFE_ENFORCE(ParseProtobufFromLargeString(\n            net_def_bytes.cast<std::string>(), &def));\n        NetDef init_def;\n        CAFFE_ENFORCE(ParseProtobufFromLargeString(\n            init_def_bytes.cast<std::string>(), &init_def));\n        py::gil_scoped_release g;\n\n        std::string protob;\n\n        auto transformed_net = ApplyTransformIfFaster(\n            transform_key,\n            def,\n            init_def,\n            warmup_runs,\n            main_runs,\n            improvement_threshold);\n\n        CAFFE_ENFORCE(transformed_net.SerializeToString(&protob));\n        return py::bytes(protob);\n      });\n  m.def(\n      \"memonger_compute_blob_recycling_for_dag\",\n      [](const py::bytes& net_def,\n         const std::vector<string>& input_blobs,\n         const std::vector<int>& op_indices,\n         const std::unordered_set<string>& shareable_blob_names,\n         const string& namescope,\n         const std::unordered_set<string>& dont_share_blob_names,\n         const std::unordered_map<string, vector<int>>& blob_shapes) {\n        py::gil_scoped_release g;\n        NetDef net;\n        CAFFE_ENFORCE(\n            ParseProtobufFromLargeString(net_def.cast<std::string>(), &net));\n        NetDef optimized_proto =\n            caffe2::memonger::compute_blob_recycling_for_dag(\n                net,\n                input_blobs,\n                op_indices,\n                shareable_blob_names,\n                namescope,\n                dont_share_blob_names,\n                blob_shapes);\n        std::string protob;\n        CAFFE_ENFORCE(optimized_proto.SerializeToString(&protob));\n        return py::bytes(protob);\n      });\n  m.def(\n      \"memonger_optimize_inference_net\",\n      [](const py::bytes& net_def,\n         const std::vector<std::string>& static_blobs) {\n        NetDef def;\n        CAFFE_ENFORCE(\n            ParseProtobufFromLargeString(net_def.cast<std::string>(), &def));\n        py::gil_scoped_release g;\n\n        std::set<string> static_blobs_set(\n            static_blobs.begin(), static_blobs.end());\n        NetDef optimized =\n            caffe2::memonger::optimize_inference_net(def, static_blobs_set);\n\n        std::string protob;\n        CAFFE_ENFORCE(optimized.SerializeToString(&protob));\n        return py::bytes(protob);\n      });\n  m.def(\n      \"infer_shapes_and_types_from_workspace\",\n      [](const std::vector<py::bytes>& net_protos) {\n        CAFFE_ENFORCE(gWorkspace);\n\n        // Parse protobuffers to NetDefs\n        std::vector<std::unique_ptr<caffe2::NetDef>> nets;\n        for (auto proto : net_protos) {\n          std::unique_ptr<NetDef> def(new NetDef());\n          CAFFE_ENFORCE(def.get()->ParseFromString(proto));\n          nets.push_back(std::move(def));\n        }\n\n        auto blob_info = InferBlobShapesAndTypesFromWorkspace(gWorkspace, nets);\n\n        std::string protob;\n        CAFFE_ENFORCE(blob_info.SerializeToString(&protob));\n        return py::bytes(protob);\n      });\n  m.def(\n      \"infer_shapes_and_types_from_map\",\n      [](const std::vector<py::bytes>& net_protos,\n         const std::map<std::string, std::vector<TIndex>> blob_dimensions) {\n        // Parse protobuffers to NetDefs\n        std::vector<std::unique_ptr<caffe2::NetDef>> nets;\n        for (auto proto : net_protos) {\n          std::unique_ptr<NetDef> def(new NetDef());\n          CAFFE_ENFORCE(def.get()->ParseFromString(proto));\n          nets.push_back(std::move(def));\n        }\n\n        auto blob_info = InferBlobShapesAndTypesFromMap(blob_dimensions, nets);\n\n        std::string protob;\n        CAFFE_ENFORCE(blob_info.SerializeToString(&protob));\n        return py::bytes(protob);\n      });\n  m.def(\"create_blob\", [](const std::string& name) {\n    CAFFE_ENFORCE(gWorkspace);\n    CAFFE_ENFORCE(gWorkspace->CreateBlob(name));\n    return true;\n  });\n  m.def(\"fetch_blob\", [](const std::string& name) -> py::object {\n    return python_detail::fetchBlob(gWorkspace, name);\n  });\n  m.def(\n      \"feed_blob\",\n      [](const std::string& name, py::object arg, py::object device_option) {\n        DeviceOption option;\n        if (!device_option.is(py::none())) {\n          // If we have a device option passed in, read it.\n          CAFFE_ENFORCE(ParseProtobufFromLargeString(\n              py::bytes(device_option).cast<std::string>(), &option));\n        }\n        auto* blob = gWorkspace->CreateBlob(name);\n        if (PyArray_Check(arg.ptr())) { // numpy array\n          PyArrayObject* array = reinterpret_cast<PyArrayObject*>(arg.ptr());\n          auto feeder = CreateFeeder(option.device_type());\n          CAFFE_ENFORCE(feeder, \"Unknown device type encountered in FeedBlob.\");\n          feeder->Feed(option, array, blob);\n          return true;\n        }\n        if (PyBytes_Check(arg.ptr()) || PyUnicode_Check(arg.ptr())) { // string\n          *blob->GetMutable<std::string>() = arg.cast<std::string>();\n          return true;\n        }\n        CAFFE_THROW(\n            \"Unexpected type of argument - only numpy array or string are \"\n            \"supported for feeding\");\n        return false;\n      },\n      \"\",\n      py::arg(\"name\"),\n      py::arg(\"arg\"),\n      py::arg(\"device_option\") = py::none());\n  m.def(\"serialize_blob\", [](const std::string& name) {\n    CAFFE_ENFORCE(gWorkspace);\n    auto* blob = gWorkspace->GetBlob(name);\n    CAFFE_ENFORCE(blob);\n    return py::bytes(blob->Serialize(name));\n  });\n  m.def(\n      \"deserialize_blob\",\n      [](const std::string& name, const py::bytes& serialized) {\n        CAFFE_ENFORCE(gWorkspace);\n        auto* blob = gWorkspace->CreateBlob(name);\n        blob->Deserialize(serialized.cast<std::string>());\n      });\n\n  // we support 2 possible signatures of python op: (inputs, outputs) or\n  // (inputs, outputs, workspace)\n  m.def(\n      \"register_python_op\",\n      [](py::object func, bool pass_workspace, std::string name) {\n        using namespace python_detail;\n        CAFFE_ENFORCE(!func.is(py::none()));\n        if (!name.empty()) {\n          name += \":\";\n        }\n        name += func.attr(\"__name__\").cast<std::string>();\n        std::string token = name;\n        for (int i = 1; gRegistry().count(token) > 0; ++i) {\n          token = name + \":\" + to_string(i);\n        }\n        gRegistry()[token] = Func{func, pass_workspace};\n        return token;\n      });\n  m.def(\n      \"register_python_gradient_op\",\n      [](const std::string& token, py::object func) {\n        using namespace python_detail;\n        CAFFE_ENFORCE(!func.is(py::none()));\n        CAFFE_ENFORCE(gRegistry().find(token) != gRegistry().end());\n        // For global sanity gradient ops shouldn't access workspace\n        gRegistry()[token + \"_gradient\"] = Func{func, false};\n      });\n  m.def(\"infer_op_input_output_device\", [](const py::bytes& op) {\n    std::unique_ptr<caffe2::OperatorDef> def(new caffe2::OperatorDef());\n    CAFFE_ENFORCE(def.get()->ParseFromString(op));\n    // device_info is a pair of vector of DeviceOption.\n    // `first` is for inputs, `second` is for outputs.\n    auto device_info = InferOpInputOutputDevice(*def);\n\n    std::vector<py::bytes> in_res;\n    std::vector<py::bytes> out_res;\n    for (auto& in_dev : device_info.first) {\n      std::string protob;\n      CAFFE_ENFORCE(in_dev.SerializeToString(&protob));\n      in_res.push_back(py::bytes(protob));\n    }\n    for (auto& out_dev : device_info.second) {\n      std::string protob;\n      CAFFE_ENFORCE(out_dev.SerializeToString(&protob));\n      out_res.push_back(py::bytes(protob));\n    }\n    return std::make_pair(in_res, out_res);\n  });\n  m.def(\"get_stats\", []() {\n    ExportedStatList stats;\n    StatRegistry::get().publish(stats);\n    std::unordered_map<std::string, int> stats_map;\n    for (const auto& stat : stats) {\n      stats_map[stat.key] = stat.value;\n    }\n    return stats_map;\n  });\n  m.def(\"is_numa_enabled\", []() { return IsNUMAEnabled(); });\n  m.def(\"get_num_numa_nodes\", []() { return GetNumNUMANodes(); });\n  m.def(\"get_blob_numa_node\", [](const std::string& blob_name) {\n    CAFFE_ENFORCE(gWorkspace);\n    auto* blob = gWorkspace->GetBlob(blob_name);\n    CAFFE_ENFORCE(blob);\n    const TensorCPU& tensor = blob->Get<TensorCPU>();\n    const void* raw_data = tensor.raw_data();\n    CAFFE_ENFORCE(raw_data);\n    return GetNUMANode(raw_data);\n  });\n  m.def(\"make_dummy\",\n        [](const std::unordered_set<std::string> &args) -> std::string {\n          if (args.empty()) {\n            return caffe2::onnx::DummyName::NewDummyName();\n          } else {\n            return \"\";\n          }\n        });\n\n#define CAFFE2_CPU_FEATURE_SUPPORT(feature) \\\n  m.def(\"builtin_cpu_supports_\" #feature, []() { return GetCpuId().feature(); })\n\n  CAFFE2_CPU_FEATURE_SUPPORT(avx2);\n\n#undef CAFFE2_CPU_FEATURE_SUPPORT\n\n  auto initialize = [&]() {\n    // Initialization of the module\n    ([]() -> void {\n      // import_array1() forces a void return value.\n      import_array1();\n    })();\n    // Single threaded, so safe\n    static bool initialized = false;\n    if (initialized) {\n      return;\n    }\n    // We will create a default workspace for us to run stuff.\n    switchWorkspaceInternal(\"default\", true);\n    gCurrentWorkspaceName = \"default\";\n    initialized = true;\n  };\n\n  initialize();\n};\n\nPYBIND11_MODULE(caffe2_pybind11_state, m) {\n  m.doc() = \"pybind11 stateful interface to Caffe2 workspaces\";\n\n  addGlobalMethods(m);\n  addObjectMethods(m);\n}\n\n} // namespace python\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/python/pybind_state.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <unordered_map>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/memonger.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/scope_guard.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/core/workspace.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/python/pybind_state_dlpack.h\"\n\n#include <pybind11/pybind11.h>\n#include <pybind11/stl.h>\n\n#include <Python.h>\n#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\n#define PY_ARRAY_UNIQUE_SYMBOL caffe2_python_ARRAY_API\n#include <numpy/arrayobject.h>\n\n// Temporary solution for numpy < 1.7 versions: old macro, no promises.\n// You're strongly advised to upgrade to >= 1.7.\n#ifndef NPY_ARRAY_C_CONTIGUOUS\n#define NPY_ARRAY_C_CONTIGUOUS NPY_C_CONTIGUOUS\n#define PyArray_SetBaseObject(arr, x) (PyArray_BASE(arr) = (x))\n#endif\n\nnamespace caffe2 {\nnamespace python {\n\nnamespace py = pybind11;\n\n// Add methods common to both CPU and GPU mode.\nvoid addGlobalMethods(pybind11::module& m);\n// Expose Workspace, Net, Blob\nvoid addObjectMethods(pybind11::module& m);\n\nclass BlobFetcherBase {\n public:\n  struct FetchedBlob {\n    pybind11::object obj;\n    bool copied;\n  };\n  virtual ~BlobFetcherBase();\n  virtual pybind11::object Fetch(const Blob& blob) = 0;\n};\n\nclass BlobFeederBase {\n public:\n  virtual ~BlobFeederBase();\n  virtual void\n  Feed(const DeviceOption& option, PyArrayObject* array, Blob* blob) = 0;\n};\n\nCAFFE_DECLARE_TYPED_REGISTRY(\n    BlobFetcherRegistry,\n    CaffeTypeId,\n    BlobFetcherBase,\n    std::unique_ptr);\n#define REGISTER_BLOB_FETCHER(id, ...) \\\n  CAFFE_REGISTER_TYPED_CLASS(BlobFetcherRegistry, id, __VA_ARGS__)\ninline unique_ptr<BlobFetcherBase> CreateFetcher(CaffeTypeId id) {\n  return BlobFetcherRegistry()->Create(id);\n}\n\nCAFFE_DECLARE_TYPED_REGISTRY(\n    BlobFeederRegistry,\n    int,\n    BlobFeederBase,\n    std::unique_ptr);\n#define REGISTER_BLOB_FEEDER(device_type, ...) \\\n  CAFFE_REGISTER_TYPED_CLASS(BlobFeederRegistry, device_type, __VA_ARGS__)\ninline unique_ptr<BlobFeederBase> CreateFeeder(int device_type) {\n  return BlobFeederRegistry()->Create(device_type);\n}\n\nstatic_assert(\n    sizeof(int) == sizeof(int32_t),\n    \"We make an assumption that int is always int32 for numpy \"\n    \"type mapping.\");\n\nint CaffeToNumpyType(const TypeMeta& meta);\nconst TypeMeta& NumpyTypeToCaffe(int numpy_type);\n\ntemplate <class Context>\nclass TensorFetcher : public BlobFetcherBase {\n public:\n  pybind11::object Fetch(const Blob& blob) override {\n    return FetchTensor(blob.Get<Tensor<Context>>(), true).obj;\n  }\n\n  bool NeedsCopy(const TypeMeta& meta) const {\n    return !std::is_same<Context, CPUContext>::value ||\n        CaffeToNumpyType(meta) == NPY_OBJECT;\n  }\n\n  FetchedBlob FetchTensor(const Tensor<Context>& tensor, bool force_copy) {\n    FetchedBlob result;\n    CAFFE_ENFORCE_GE(tensor.size(), 0, \"Trying to fetch unitilized tensor\");\n    const int numpy_type = CaffeToNumpyType(tensor.meta());\n    CAFFE_ENFORCE(\n        numpy_type != -1,\n        \"This tensor's data type is not supported: \",\n        tensor.meta().name(),\n        \".\");\n    std::vector<npy_intp> npy_dims;\n    for (const auto dim : tensor.dims()) {\n      npy_dims.push_back(dim);\n    }\n    result.copied = force_copy || NeedsCopy(tensor.meta());\n    void* outPtr;\n    if (result.copied) {\n      result.obj = py::reinterpret_steal<py::object>(\n          PyArray_SimpleNew(tensor.ndim(), npy_dims.data(), numpy_type));\n      outPtr = static_cast<void*>(\n          PyArray_DATA(reinterpret_cast<PyArrayObject*>(result.obj.ptr())));\n    } else {\n      outPtr = const_cast<Tensor<Context>&>(tensor).raw_mutable_data();\n      result.obj = py::reinterpret_steal<py::object>(PyArray_SimpleNewFromData(\n          tensor.ndim(), npy_dims.data(), numpy_type, outPtr));\n    }\n\n    if (numpy_type == NPY_OBJECT) {\n      PyObject** outObj = reinterpret_cast<PyObject**>(outPtr);\n      auto* str = tensor.template data<std::string>();\n      for (int i = 0; i < tensor.size(); ++i) {\n        outObj[i] = PyBytes_FromStringAndSize(str->data(), str->size());\n        str++;\n        // cleanup on failure\n        if (outObj[i] == nullptr) {\n          for (int j = 0; j < i; ++j) {\n            Py_DECREF(outObj[j]);\n          }\n          CAFFE_THROW(\"Failed to allocate string for ndarray of strings.\");\n        }\n      }\n      return result;\n    }\n\n    if (result.copied) {\n      Context context;\n      context.template CopyBytes<Context, CPUContext>(\n          tensor.nbytes(), tensor.raw_data(), outPtr);\n      context.FinishDeviceComputation();\n    }\n    return result;\n  }\n};\n\ntemplate <class Context>\nclass TensorFeeder : public BlobFeederBase {\n public:\n  void FeedTensor(\n      const DeviceOption& option,\n      PyArrayObject* original_array,\n      Tensor<Context>* tensor) {\n    PyArrayObject* array = PyArray_GETCONTIGUOUS(original_array);\n    auto g = MakeGuard([&]() { Py_XDECREF(array); });\n\n    const auto npy_type = PyArray_TYPE(array);\n    const TypeMeta& meta = NumpyTypeToCaffe(npy_type);\n    CAFFE_ENFORCE(\n        meta.id() != 0,\n        \"This numpy data type is not supported: \",\n        PyArray_TYPE(array),\n        \".\");\n    Context context(option);\n    context.SwitchToDevice();\n    // numpy requires long int as its dims.\n    int ndim = PyArray_NDIM(array);\n    npy_intp* npy_dims = PyArray_DIMS(array);\n    std::vector<TIndex> dims;\n    for (int i = 0; i < ndim; ++i) {\n      dims.push_back(npy_dims[i]);\n    }\n    tensor->Resize(dims);\n\n    // Now, copy the data to the tensor.\n    switch (npy_type) {\n      case NPY_OBJECT: {\n        PyObject** input = reinterpret_cast<PyObject**>(PyArray_DATA(array));\n        auto* outPtr = tensor->template mutable_data<std::string>();\n        for (int i = 0; i < tensor->size(); ++i) {\n          char* str;\n          Py_ssize_t strSize;\n#if PY_MAJOR_VERSION > 2\n          if (PyBytes_Check(input[i])) {\n            CAFFE_ENFORCE(\n                PyBytes_AsStringAndSize(input[i], &str, &strSize) != -1,\n                \"Had a PyBytes object but cannot convert it to a string.\");\n          } else if (PyUnicode_Check(input[i])) { // string\n            str = PyUnicode_AsUTF8AndSize(input[i], &strSize);\n            CAFFE_ENFORCE(\n                str,\n                \"Had a PyUnicode object but cannot convert it to a string.\");\n          } else {\n            CAFFE_THROW(\"Unsupported python object type passed into ndarray.\");\n          }\n#else\n          CAFFE_ENFORCE(\n              PyBytes_AsStringAndSize(input[i], &str, &strSize) != -1,\n              \"Unsupported python object type passed into ndarray.\");\n#endif // PY_MAJOR_VERSION > 2\n          outPtr[i] = std::string(str, strSize);\n        }\n        break;\n      }\n      case NPY_UNICODE:\n        CAFFE_THROW(\n            \"You are feeding in a numpy array of unicode. Caffe2 C++ does not \"\n            \"support unicode yet. Please ensure that you are passing in bytes \"\n            \"instead of unicode strings.\");\n        break;\n      default:\n        context.template CopyBytes<CPUContext, Context>(\n            tensor->size() * meta.itemsize(),\n            static_cast<void*>(PyArray_DATA(array)),\n            tensor->raw_mutable_data(meta));\n    }\n    context.FinishDeviceComputation();\n  }\n\n  virtual void\n  Feed(const DeviceOption& option, PyArrayObject* original_array, Blob* blob) {\n    FeedTensor(option, original_array, blob->GetMutable<Tensor<Context>>());\n  }\n};\n\nnamespace python_detail {\nstruct Func {\n  py::object py_func;\n  bool needs_workspace;\n};\n\nconst Func& getOpFunc(const std::string& token);\n\nconst Func& getGradientFunc(const std::string& token);\n\n} // namespace python_detail\n\ntemplate <class Context, bool use_dlpack>\nclass PythonOpBase : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  PythonOpBase(\n      const OperatorDef& operator_def,\n      Workspace* ws,\n      const std::string& pickled_builder_arg_name)\n      : Operator<Context>(operator_def, ws),\n        ws_(ws),\n        token_(OperatorBase::template GetSingleArgument<std::string>(\n            \"token\",\n            \"\")) {\n    using namespace python_detail;\n    auto pickled = OperatorBase::template GetSingleArgument<std::string>(\n        pickled_builder_arg_name, \"\");\n    auto forced_cpu_outputs_arg =\n        OperatorBase::template GetRepeatedArgument<int>(\"forced_cpu_outputs\");\n    forced_cpu_outputs_.insert(\n        forced_cpu_outputs_arg.begin(), forced_cpu_outputs_arg.end());\n    CAFFE_ENFORCE(\n        !pickled.empty() || !token_.empty(),\n        \"PythonOp requires either pickled_builder or token arg.\");\n    if (!pickled.empty()) {\n      py::gil_scoped_acquire g;\n      try {\n        auto pickle =\n            py::reinterpret_steal<py::object>(PyImport_ImportModule(\"pickle\"));\n        CAFFE_ENFORCE(pickle);\n        auto loads = pickle.attr(\"loads\").cast<py::object>();\n        CAFFE_ENFORCE(loads);\n        auto builder_call = loads(py::bytes(pickled)).cast<py::tuple>();\n        CAFFE_ENFORCE(builder_call);\n        CAFFE_ENFORCE_EQ(py::len(builder_call), 3);\n        auto func = builder_call[0].cast<py::object>();\n        auto args = builder_call[1].cast<py::tuple>();\n        auto kwargs = builder_call[2].cast<py::dict>();\n        auto built_func = func(*args, **kwargs);\n        CAFFE_ENFORCE(built_func);\n        built_func_.reset(\n            new Func{built_func,\n                     OperatorBase::template GetSingleArgument<bool>(\n                         \"pass_workspace\", false)});\n      } catch (const py::error_already_set& e) {\n        std::stringstream error;\n        error << \"Python exception encountered while creating PythonOp: \"\n              << e.what();\n        LOG(ERROR) << error.str();\n        CAFFE_THROW(error.str());\n      }\n    }\n  }\n\n  bool RunOnDevice() override final {\n    auto* pyFunc = built_func_ ? built_func_.get() : &getFunc(token_);\n    CAFFE_ENFORCE(pyFunc);\n    {\n      // Acquire GIL for call to Python runtime.\n      py::gil_scoped_acquire g;\n\n      DeviceOption cpu_option;\n      cpu_option.set_device_type(CPU);\n\n      std::vector<py::object> inputs;\n      inputs.reserve(InputSize());\n      for (auto i = 0; i < InputSize(); ++i) {\n        const auto* blob = &InputBlob(i);\n        // Allow CPU tensors in addition to operator context's tensors\n        py::object py_obj;\n        if (blob->template IsType<Tensor<CPUContext>>()) {\n          if (use_dlpack) {\n            DLPackWrapper<CPUContext> wrapper(\n                const_cast<Tensor<CPUContext>*>(\n                    &blob->template Get<Tensor<CPUContext>>()),\n                cpu_option);\n            // copy wrapper\n            py_obj = py::cast(wrapper, py::return_value_policy::copy);\n          } else {\n            py_obj = py::cast(\n                &blob->template Get<Tensor<CPUContext>>(),\n                py::return_value_policy::reference);\n          }\n        } else {\n          if (use_dlpack) {\n            DLPackWrapper<Context> wrapper(\n                const_cast<Tensor<Context>*>(\n                    &blob->template Get<Tensor<Context>>()),\n                this->device_option());\n            py_obj = py::cast(wrapper, py::return_value_policy::copy);\n          } else {\n            py_obj = py::cast(\n                &blob->template Get<Tensor<Context>>(),\n                py::return_value_policy::reference);\n          }\n        }\n        inputs.push_back(py_obj);\n      }\n      std::vector<py::object> outputs;\n      outputs.reserve(OutputSize());\n      for (auto i = 0; i < OutputSize(); ++i) {\n        auto* blob = OutputBlob(i);\n\n        // Python op is always used with CPUContext only and treats inputs and\n        // outputs as CPU tensors, CUDA version of PythonOp is implemented\n        // through GPUFallbackOp that copies input CUDA blobs to CPU and copies\n        // outputs from CUDA to CPU.\n        // GPUFallbackOp also allows keeping some of the output blobs on CPU\n        // by specifying their indices explicitly in template parameters.\n\n        // PythonDLPack op allows working with CUDA and CPU blobs directly\n        // through DLPack tensors. In order to properly setup mapping we need\n        // to know in advance a type (CUDA or CPU) of an output blob.\n        // Output blob might not be initialized yet, so by default we treat\n        // output blobs as having the same type as operator's context.\n        // This can be overwritten though forced_cpu_outputs argument\n\n        // make sure output blob is initialized before creating the binding\n        if (forced_cpu_outputs_.count(i)) {\n          blob->template GetMutable<Tensor<CPUContext>>();\n        } else {\n          blob->template GetMutable<Tensor<Context>>();\n        }\n\n        py::object py_obj;\n        if (blob->template IsType<Tensor<CPUContext>>()) {\n          if (use_dlpack) {\n            DLPackWrapper<CPUContext> wrapper(\n                blob->template GetMutable<Tensor<CPUContext>>(), cpu_option);\n            py_obj = py::cast(wrapper, py::return_value_policy::copy);\n          } else {\n            py_obj = py::cast(\n                blob->template GetMutable<Tensor<CPUContext>>(),\n                py::return_value_policy::reference);\n          }\n        } else {\n          if (use_dlpack) {\n            DLPackWrapper<Context> wrapper(\n                blob->template GetMutable<Tensor<Context>>(),\n                this->device_option());\n            py_obj = py::cast(wrapper, py::return_value_policy::copy);\n          } else {\n            py_obj = py::cast(\n                blob->template GetMutable<Tensor<Context>>(),\n                py::return_value_policy::reference);\n          }\n        }\n        outputs.push_back(py_obj);\n      }\n\n      try {\n        if (pyFunc->needs_workspace) {\n          pyFunc->py_func(inputs, outputs, ws_);\n        } else {\n          pyFunc->py_func(inputs, outputs);\n        }\n      } catch (const py::error_already_set& e) {\n        std::stringstream error;\n        error << \"Exception encountered running PythonOp function: \"\n              << e.what();\n        LOG(ERROR) << error.str();\n        CAFFE_THROW(error.str());\n      }\n    }\n    return true;\n  }\n\n  virtual ~PythonOpBase() {\n    if (built_func_) {\n      // since it may trigger python interpreter when refcount reaches zero\n      py::gil_scoped_acquire g;\n      built_func_.reset();\n    }\n  }\n\n protected:\n  virtual const python_detail::Func& getFunc(const std::string& token) = 0;\n  Workspace* ws_;\n  // output indices forced to be on CPU\n  std::unordered_set<int> forced_cpu_outputs_;\n\n private:\n  const std::string token_;\n  std::unique_ptr<python_detail::Func> built_func_;\n};\n\ntemplate <class Context, bool use_dlpack>\nclass PythonOp : public PythonOpBase<Context, use_dlpack> {\n public:\n  PythonOp(const OperatorDef& operator_def, Workspace* ws)\n      : PythonOpBase<Context, use_dlpack>(operator_def, ws, \"pickled_builder\") {\n  }\n\n protected:\n  const python_detail::Func& getFunc(const std::string& token) override {\n    return python_detail::getOpFunc(token);\n  }\n};\n\ntemplate <class Context, bool use_dlpack>\nclass PythonGradientOp : public PythonOpBase<Context, use_dlpack> {\n public:\n  PythonGradientOp(const OperatorDef& operator_def, Workspace* ws)\n      : PythonOpBase<Context, use_dlpack>(\n            operator_def,\n            ws,\n            \"pickled_grad_builder\") {}\n\n protected:\n  const python_detail::Func& getFunc(const std::string& token) override {\n    return python_detail::getGradientFunc(token);\n  }\n};\n\n} // namespace python\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/python/pybind_state_dlpack.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"pybind_state_dlpack.h\"\n\nnamespace caffe2 {\nnamespace python {\n\nnamespace py = pybind11;\n\nconst DLDeviceType* CaffeToDLDeviceType(int device_type) {\n  static std::map<int, DLDeviceType> dl_device_type_map{\n      {CPU, kCPU},\n      {CUDA, kGPU},\n  };\n  const auto it = dl_device_type_map.find(device_type);\n  return it == dl_device_type_map.end() ? nullptr : &it->second;\n}\n\nconst DLDataType* CaffeToDLType(const TypeMeta& meta) {\n  static std::map<CaffeTypeId, DLDataType> dl_type_map{\n      {TypeMeta::Id<int8_t>(), DLDataType{0, 8, 1}},\n      {TypeMeta::Id<int16_t>(), DLDataType{0, 16, 1}},\n      {TypeMeta::Id<int32_t>(), DLDataType{0, 32, 1}},\n      {TypeMeta::Id<int64_t>(), DLDataType{0, 64, 1}},\n      {TypeMeta::Id<uint8_t>(), DLDataType{1, 8, 1}},\n      {TypeMeta::Id<uint16_t>(), DLDataType{1, 16, 1}},\n      {TypeMeta::Id<float16>(), DLDataType{2, 16, 1}},\n      {TypeMeta::Id<float>(), DLDataType{2, 32, 1}},\n      {TypeMeta::Id<double>(), DLDataType{2, 64, 1}},\n  };\n  const auto it = dl_type_map.find(meta.id());\n  return it == dl_type_map.end() ? nullptr : &it->second;\n}\n\nconst TypeMeta& DLTypeToCaffe(const DLDataType& dl_type) {\n  try {\n    if (dl_type.lanes != 1) {\n      throw std::invalid_argument(\"invalid type\");\n    }\n    static std::map<int, std::map<int, TypeMeta>> dl_caffe_type_map{\n        {0,\n         std::map<int, TypeMeta>{\n             {8, TypeMeta::Make<int8_t>()},\n             {16, TypeMeta::Make<int16_t>()},\n             {32, TypeMeta::Make<int32_t>()},\n             {64, TypeMeta::Make<int64_t>()},\n         }},\n        {1,\n         std::map<int, TypeMeta>{\n             {8, TypeMeta::Make<uint8_t>()},\n             {16, TypeMeta::Make<uint16_t>()},\n         }},\n        {2,\n         std::map<int, TypeMeta>{\n             {16, TypeMeta::Make<float16>()},\n             {32, TypeMeta::Make<float>()},\n             {64, TypeMeta::Make<double>()},\n         }},\n    };\n    if (!dl_caffe_type_map.count(dl_type.code)) {\n      throw std::invalid_argument(\"invalid type\");\n    }\n    const auto& bits_map = dl_caffe_type_map.at(dl_type.code);\n    if (!bits_map.count(dl_type.bits)) {\n      throw std::invalid_argument(\"invalid type\");\n    }\n    return bits_map.at(dl_type.bits);\n  } catch (const std::invalid_argument& e) {\n    CAFFE_THROW(\n        \"Unsupported DLDataType: \", dl_type.code, dl_type.bits, dl_type.lanes);\n  }\n}\n\n} // namespace python\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/python/pybind_state_dlpack.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/types.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/python/dlpack.h\"\n\n#include <pybind11/pybind11.h>\n#include <pybind11/stl.h>\n\nnamespace caffe2 {\nnamespace python {\n\nnamespace py = pybind11;\n\nconst DLDeviceType* CaffeToDLDeviceType(int device_type);\n\nconst DLDataType* CaffeToDLType(const TypeMeta& meta);\n\nconst TypeMeta& DLTypeToCaffe(const DLDataType& dl_type);\n\ntemplate <class Context>\nclass DLPackWrapper {\n public:\n  DLPackWrapper(Tensor<Context>* tensor, DeviceOption device_option)\n      : tensor(tensor), device_option(device_option) {}\n\n  py::object data() {\n    DLContext tensor_context;\n    auto device_type_ptr = CaffeToDLDeviceType(device_option.device_type());\n    CAFFE_ENFORCE(\n        device_type_ptr,\n        \"Unsupported device type: \",\n        device_option.device_type());\n    tensor_context.device_type = *device_type_ptr;\n    tensor_context.device_id = device_option.cuda_gpu_id();\n\n    if (tensor->size() <= 0) {\n      tensor->Resize(0);\n    }\n    if (tensor->meta().id() == 0) {\n      // treat uninitialized tensor as float tensor\n      tensor->template mutable_data<float>();\n    }\n    CAFFE_ENFORCE_GT(tensor->ndim(), 0);\n\n    auto type_ptr = CaffeToDLType(tensor->meta());\n    CAFFE_ENFORCE(\n        type_ptr,\n        \"Tensor type is not supported in DLPack: \",\n        tensor->meta().name());\n    DLDataType tensor_type = *type_ptr;\n\n    DLTensor dlTensor;\n    dlTensor.data = const_cast<void*>(tensor->raw_data());\n    dlTensor.ctx = tensor_context;\n    dlTensor.ndim = tensor->ndim();\n    dlTensor.dtype = tensor_type;\n    dlTensor.shape = const_cast<int64_t*>(&(tensor->dims()[0]));\n    dlTensor.strides = nullptr;\n    dlTensor.byte_offset = 0;\n\n    managed_tensor.dlTensor = dlTensor;\n    // C2 Tensor memory is managed by C2\n    managed_tensor.ctx = nullptr;\n    managed_tensor.destructor = [](DLManagedTensor*) {};\n\n    return py::reinterpret_steal<py::object>(\n        PyCapsule_New(&managed_tensor, \"dltensor\", nullptr));\n  }\n\n  void feed(py::object obj) {\n    CAFFE_ENFORCE(PyCapsule_CheckExact(obj.ptr()), \"Expected DLPack capsule\");\n    DLManagedTensor* dlMTensor =\n        (DLManagedTensor*)PyCapsule_GetPointer(obj.ptr(), \"dltensor\");\n    CAFFE_ENFORCE(dlMTensor, \"Invalid DLPack capsule\");\n    DLTensor* dlTensor = &dlMTensor->dlTensor;\n    auto device_type_ptr = CaffeToDLDeviceType(device_option.device_type());\n    CAFFE_ENFORCE(\n        device_type_ptr,\n        \"Unsupported device type: \",\n        device_option.device_type());\n    CAFFE_ENFORCE(\n        dlTensor->ctx.device_type == *device_type_ptr,\n        \"DLPack tensor device type mismatch\");\n    int dlpack_device_id = dlTensor->ctx.device_id;\n    CAFFE_ENFORCE_EQ(\n        dlpack_device_id,\n        device_option.cuda_gpu_id(),\n        \"Expected same device id for DLPack and C2 tensors\");\n\n    std::vector<TIndex> dims;\n    dims.reserve(dlTensor->ndim);\n    for (int idx = 0; idx < dlTensor->ndim; ++idx) {\n      dims.push_back(dlTensor->shape[idx]);\n    }\n\n    if (dlTensor->strides) {\n      int64_t stride = 1;\n      for (int idx = dims.size() - 1; idx >= 0; --idx) {\n        CAFFE_ENFORCE_EQ(\n            stride,\n            dlTensor->strides[idx],\n            \"Tensors with non-standard strides are not supported\");\n        stride *= dims[idx];\n      }\n    }\n\n    tensor->Resize(dims);\n    const auto& meta = DLTypeToCaffe(dlTensor->dtype);\n    tensor->ShareExternalPointer(\n        ((int8_t*)dlTensor->data) + dlTensor->byte_offset,\n        meta,\n        0,\n        [dlMTensor](void*) {\n          if (dlMTensor->destructor) {\n            dlMTensor->destructor(dlMTensor);\n          }\n        });\n  }\n\n  Tensor<Context>* tensor;\n  DeviceOption device_option;\n  DLManagedTensor managed_tensor;\n};\n\n} // namespace python\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/python/pybind_state_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Note(jiayq): the import_array function is done inside\n// caffe2_python.cc. Read\n// http://docs.scipy.org/doc/numpy-1.10.1/reference/c-api.array.html#miscellaneous\n// for more details.\n\n#define NO_IMPORT_ARRAY\n\n#include \"pybind_state.h\"\n\n#include <pybind11/pybind11.h>\n#include <pybind11/stl.h>\n\n#include \"caffe2/core/common_cudnn.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/operator_fallback_gpu.h\"\n\nnamespace caffe2 {\nnamespace python {\n\nREGISTER_CUDA_OPERATOR(Python, GPUFallbackOp<PythonOp<CPUContext, false>>);\nREGISTER_CUDA_OPERATOR(\n    PythonGradient,\n    GPUFallbackOp<PythonGradientOp<CPUContext, false>>);\n\nREGISTER_CUDA_OPERATOR(PythonDLPack, PythonOp<CUDAContext, true>);\nREGISTER_CUDA_OPERATOR(\n    PythonDLPackGradient,\n    PythonGradientOp<CUDAContext, true>);\n\nREGISTER_BLOB_FETCHER((TypeMeta::Id<TensorCUDA>()), TensorFetcher<CUDAContext>);\nREGISTER_BLOB_FEEDER(CUDA, TensorFeeder<CUDAContext>);\n\nnamespace py = pybind11;\n\nvoid addCUDAGlobalMethods(py::module& m) {\n  m.def(\"num_cuda_devices\", &NumCudaDevices);\n  m.def(\"get_cuda_version\", &CudaVersion);\n  m.def(\"get_cudnn_version\", &cudnnCompiledVersion);\n  m.def(\"get_cuda_peer_access_pattern\", []() {\n    std::vector<std::vector<bool>> pattern;\n    CAFFE_ENFORCE(caffe2::GetCudaPeerAccessPattern(&pattern));\n    return pattern;\n  });\n  m.def(\"get_device_properties\", [](int deviceid) {\n    auto& prop = GetDeviceProperty(deviceid);\n    std::map<std::string, py::object> obj;\n    obj[\"name\"] = py::cast(prop.name);\n    obj[\"major\"] = py::cast(prop.major);\n    obj[\"minor\"] = py::cast(prop.minor);\n    obj[\"totalGlobalMem\"] = py::cast(prop.totalGlobalMem);\n    return obj;\n  });\n};\n\nvoid addCUDAObjectMethods(py::module& m) {\n  py::class_<DLPackWrapper<CUDAContext>>(m, \"DLPackTensorCUDA\")\n      .def_property_readonly(\n          \"data\",\n          [](DLPackWrapper<CUDAContext>* t) -> py::object {\n            CAFFE_ENFORCE_EQ(\n                t->device_option.device_type(),\n                CUDA,\n                \"Expected CUDA device option for CUDA tensor\");\n\n            return t->data();\n          },\n          \"Return DLPack tensor with tensor's data.\")\n      .def(\n          \"feed\",\n          [](DLPackWrapper<CUDAContext>* t, py::object obj) {\n            CAFFE_ENFORCE_EQ(\n                t->device_option.device_type(),\n                CUDA,\n                \"Expected CUDA device option for CUDA tensor\");\n            t->feed(obj);\n          },\n          \"Copy data from given DLPack tensor into this tensor.\")\n      .def_property_readonly(\n          \"_shape\",\n          [](const DLPackWrapper<CUDAContext>& t) { return t.tensor->dims(); })\n      .def(\n          \"_reshape\",\n          [](DLPackWrapper<CUDAContext>* t, std::vector<TIndex> dims) {\n            t->tensor->Resize(dims);\n          });\n}\n\nPYBIND11_MODULE(caffe2_pybind11_state_gpu, m) {\n  m.doc() = \"pybind11 stateful interface to Caffe2 workspaces - GPU edition\";\n\n  addGlobalMethods(m);\n  addCUDAGlobalMethods(m);\n  addObjectMethods(m);\n  addCUDAObjectMethods(m);\n}\n} // namespace python\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/python/pybind_state_mkl.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Note(jiayq): the import_array function is done inside\n// caffe2_python.cc. Read\n// http://docs.scipy.org/doc/numpy-1.10.1/reference/c-api.array.html#miscellaneous\n// for more details.\n#define NO_IMPORT_ARRAY\n\n#include \"pybind_state.h\"\n\n#include <pybind11/pybind11.h>\n#include <pybind11/stl.h>\n\n#include \"caffe2/mkl/mkl_utils.h\"\n\n#ifdef CAFFE2_HAS_MKL_DNN\n\nnamespace caffe2 {\nnamespace python {\n\ntemplate <typename T>\nusing MKLMemory = caffe2::mkl::MKLMemory<T>;\n\ntemplate <typename T>\nclass MKLMemoryFetcher : public BlobFetcherBase {\n public:\n  pybind11::object Fetch(const Blob& blob) override {\n    const MKLMemory<T>& src = blob.Get<MKLMemory<T>>();\n    CAFFE_ENFORCE(src.buffer(), \"Trying to fetch unitilized tensor\");\n    const int numpy_type = CaffeToNumpyType(TypeMeta::Make<T>());\n    CAFFE_ENFORCE(\n        numpy_type != -1,\n        \"Unsupported mkl memory data type? This usually should not happen \"\n        \"since MKLMemory usually only do float and double.\");\n    std::vector<npy_intp> npy_dims;\n    for (const auto dim : src.dims()) {\n      npy_dims.push_back(dim);\n    }\n    auto result = pybind11::reinterpret_steal<pybind11::object>(\n        PyArray_SimpleNew(src.dims().size(), npy_dims.data(), numpy_type));\n    void* ptr = static_cast<void*>(\n        PyArray_DATA(reinterpret_cast<PyArrayObject*>(result.ptr())));\n    src.CopyTo(ptr);\n    return result;\n  }\n};\n\nclass MKLMemoryFeeder : public BlobFeederBase {\n public:\n  void Feed(const DeviceOption&, PyArrayObject* original_array, Blob* blob)\n      override {\n    PyArrayObject* array = PyArray_GETCONTIGUOUS(original_array);\n    auto g = MakeGuard([&]() { Py_XDECREF(array); });\n\n    const auto npy_type = PyArray_TYPE(array);\n    const TypeMeta& meta = NumpyTypeToCaffe(npy_type);\n    // TODO: if necessary, use dispatcher.\n    if (meta.Match<float>()) {\n      FeedMKL<float>(array, blob);\n    } else if (meta.Match<double>()) {\n      FeedMKL<double>(array, blob);\n    } else {\n      CAFFE_THROW(\n          \"This numpy data type is not supported: \",\n          PyArray_TYPE(array),\n          \". Only float and double are supported by MKLDNN.\");\n    }\n  }\n\n  template <typename T>\n  void FeedMKL(PyArrayObject* array, Blob* blob) {\n    // numpy requires long int as its dims.\n    int ndim = PyArray_NDIM(array);\n    npy_intp* npy_dims = PyArray_DIMS(array);\n    std::vector<TIndex> dims;\n    for (int i = 0; i < ndim; ++i) {\n      dims.push_back(npy_dims[i]);\n    }\n    // See if we already have the right MKLMemory object. The reason is that if\n    // there is already an existing MKLMemory, we want to keep the internal\n    // layout that is already specified by the object.\n    if (!blob->IsType<MKLMemory<T>>() ||\n        dims != blob->Get<MKLMemory<T>>().dims()) {\n      blob->Reset(new MKLMemory<T>(dims));\n    }\n    blob->GetMutable<MKLMemory<T>>()->CopyFrom(\n        static_cast<const void*>(PyArray_DATA(array)));\n  }\n};\n\nREGISTER_BLOB_FETCHER(\n    (TypeMeta::Id<MKLMemory<float>>()),\n    MKLMemoryFetcher<float>);\nREGISTER_BLOB_FETCHER(\n    (TypeMeta::Id<MKLMemory<double>>()),\n    MKLMemoryFetcher<double>);\nREGISTER_BLOB_FEEDER(MKLDNN, MKLMemoryFeeder);\n\n} // namespace python\n} // namespace caffe2\n\n#endif // CAFFE2_HAS_MKL_DNN\n"
  },
  {
    "path": "caffe2/python/python_op_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nfrom caffe2.python.core import CreatePythonOperator\nimport caffe2.python.hypothesis_test_util as hu\nfrom hypothesis import given\nimport hypothesis.strategies as st\nimport numpy as np\n\n\ndef SubFunctionThatThrowsRuntimeError():\n    raise RuntimeError(\"This is an intentional exception.\")\n\n\ndef MainOpFunctionThatThrowsRuntimeError(inputs, _):\n    return SubFunctionThatThrowsRuntimeError()\n\n\ndef op_builder(name, index, extra):\n    iterations = [0]\n    assert name == 'name'\n    assert index == 5\n    assert extra - 4.2 < 0.0001\n\n    def my_op(inputs, outputs):\n        assert inputs[0].data[0] == iterations[0]\n        assert name == 'name'\n        assert index == 5\n        assert extra - 4.2 < 0.0001\n        iterations[0] += 1\n\n    return my_op\n\n\nclass PythonOpTest(hu.HypothesisTestCase):\n    @given(x=hu.tensor())\n    def test_feed(self, x):\n        def f(inputs, _):\n            self.assertEqual(x.shape, inputs[0].shape)\n            self.assertEqual(type(inputs[0].shape), tuple)\n            self.assertEqual(type(inputs[0].data), np.ndarray)\n            np.testing.assert_almost_equal(x, inputs[0].data)\n        op = CreatePythonOperator(f, [\"x\"], [])\n        workspace.FeedBlob(\"x\", x)\n        workspace.RunOperatorOnce(op)\n\n    def test_exception(self):\n        op = CreatePythonOperator(MainOpFunctionThatThrowsRuntimeError, [], [])\n        with self.assertRaisesRegexp(\n            RuntimeError, \"This is an intentional exception.\"\n        ):\n            workspace.RunOperatorOnce(op)\n\n    @given(x=hu.tensor())\n    def test_feed_with_helper_function(self, x):\n        def f(inputs, _):\n            self.assertEqual(x.shape, inputs[0].shape)\n            self.assertEqual(type(inputs[0].shape), tuple)\n            self.assertEqual(type(inputs[0].data), np.ndarray)\n            np.testing.assert_almost_equal(x, inputs[0].data)\n        net = core.Net(\"test\")\n        net.Python(f)([\"x\"], [])\n        workspace.FeedBlob(\"x\", x)\n        workspace.RunNetOnce(net)\n\n    def test_builder_tuple(self):\n        net = core.Net(\"builder_template\")\n        iter_blob = 'iter'\n        net.Python((op_builder, ['name', 5], {'extra': 4.2}))([iter_blob], [])\n        net.Python((op_builder, ['name', 5], {'extra': 4.2}))([iter_blob], [])\n        for repeat in range(2):\n            # check that the builder will be called exactly once for each\n            # PythonOp constructor. Cloning the net will also trigger a call\n            # to the builder when the net is created.\n            cloned_net = net.Clone('builder_%d' % repeat)\n            workspace.FeedBlob(iter_blob, np.array([0]))\n            # Builder gets called once per python op in the line below\n            workspace.CreateNet(cloned_net)\n            for i in range(10):\n                workspace.FeedBlob(iter_blob, np.array([i]))\n                workspace.RunNet(cloned_net)\n\n    @given(x=hu.tensor())\n    def test_feed_with_gc(self, x):\n        def f(inputs, _):\n            self.assertEqual(x.shape, inputs[0].shape)\n            np.testing.assert_almost_equal(x, inputs[0].data)\n        op = CreatePythonOperator(f, [\"x\"], [])\n        workspace.FeedBlob(\"x\", x)\n        workspace.RunOperatorOnce(op)\n        del f\n        workspace.FeedBlob(\"x\", x)\n        workspace.RunOperatorOnce(op)\n\n    @given(x=hu.tensor())\n    def test_reshape(self, x):\n        def f(inputs, outputs):\n            outputs[0].reshape(inputs[0].shape)\n            self.assertEqual(x.shape, inputs[0].shape)\n            self.assertEqual(x.shape, outputs[0].shape)\n            outputs[0].data[...] = inputs[0].data\n\n        op = CreatePythonOperator(f, [\"x\"], [\"y\"])\n        workspace.FeedBlob(\"x\", x)\n        workspace.RunOperatorOnce(op)\n        y = workspace.FetchBlob(\"y\")\n        np.testing.assert_almost_equal(x, y)\n\n    @given(x=hu.tensor())\n    def test_workspace_manipulation(self, x):\n        \"\"\"\n        Verify that python op can manipulate workspace directly\n        \"\"\"\n        def f(inputs, outputs, ws):\n            fetched = ws.blobs['internal'].fetch()\n            np.testing.assert_almost_equal(fetched, x)\n\n        ws = workspace.C.Workspace()\n        net = core.Net(\"test\")\n        net.GivenTensorFill([], ['internal'], values=x, shape=x.shape)\n        net.Python(f, pass_workspace=True)([], [])\n        ws.run(net)\n\n    @given(x=hu.tensor())\n    def test_caught_exception_doesnt_terminate(self, x):\n        def f(inputs, outputs):\n            try:\n                raise Exception(\"Exception in handler\")\n            except Exception:\n                pass\n\n        op = CreatePythonOperator(f, [\"x\"], [\"y\"])\n        workspace.FeedBlob(\"x\", x)\n        workspace.RunOperatorOnce(op)\n\n    @given(x=hu.tensor(),\n           n=st.integers(min_value=1, max_value=20),\n           w=st.integers(min_value=1, max_value=20))\n    def test_multithreaded_evaluation(self, x, n, w):\n        def f(inputs, outputs):\n            outputs[0].reshape(inputs[0].shape)\n            outputs[0].data[...] = inputs[0].data\n        ops = [CreatePythonOperator(f, [\"x\"], [str(i)]) for i in range(n)]\n        net = core.Net(\"net\")\n        net.Proto().op.extend(ops)\n        net.Proto().type = \"dag\"\n        net.Proto().num_workers = w\n        iters = 100\n        plan = core.Plan(\"plan\")\n        plan.AddStep(core.ExecutionStep(\"test-step\", net, iters))\n        workspace.FeedBlob(\"x\", x)\n        workspace.RunPlan(plan.Proto().SerializeToString())\n        for i in range(n):\n            y = workspace.FetchBlob(str(i))\n            np.testing.assert_almost_equal(x, y)\n\n    @given(x=hu.tensor(), in_place=st.booleans(), **hu.gcs)\n    def test_gradient(self, x, in_place, gc, dc):\n        def f(inputs, outputs):\n            outputs[0].reshape(inputs[0].shape)\n            outputs[0].data[...] = inputs[0].data * 2\n\n        def grad_f(inputs, outputs):\n            # Ordering is [inputs, outputs, grad_outputs]\n            grad_output = inputs[2]\n\n            grad_input = outputs[0]\n            grad_input.reshape(grad_output.shape)\n            grad_input.data[...] = grad_output.data * 2\n\n        op = CreatePythonOperator(\n            f, [\"x\"], [\"x\" if in_place else \"y\"], grad_f=grad_f)\n        self.assertGradientChecks(gc, op, [x], 0, [0])\n        self.assertDeviceChecks(dc, op, [x], [0])\n\n    @given(inputs=hu.tensors(n=2), **hu.gcs)\n    def test_gradient_multiple(self, inputs, gc, dc):\n        (x1, x2) = inputs\n\n        def f(inputs, outputs):\n            for idx in [0, 1]:\n                self.assertEqual(type(inputs[idx].shape), tuple)\n                outputs[idx].reshape(inputs[idx].shape)\n                outputs[idx].data[...] = inputs[idx].data * 2\n\n        def grad_f(inputs, outputs):\n            # Ordering is [inputs, outputs, grad_outputs]\n            self.assertEqual(len(inputs), 6)\n            self.assertEqual(len(outputs), 2)\n            for (grad_output_idx, grad_input_idx) in [(4, 0), (5, 1)]:\n                grad_output = inputs[grad_output_idx]\n                grad_input = outputs[grad_input_idx]\n                grad_input.reshape(grad_output.shape)\n                grad_input.data[...] = grad_output.data * 2\n\n        op = CreatePythonOperator(f, [\"x1\", \"x2\"], [\"y1\", \"y2\"], grad_f=grad_f)\n\n        for idx in [0, 1]:\n            self.assertGradientChecks(gc, op, [x1, x2], idx, [0, 1])\n        self.assertDeviceChecks(dc, op, [x1, x2], [0, 1])\n\n    @given(inputs=hu.tensors(n=3), **hu.gcs)\n    def test_gradient_multiple_with_indices(self, inputs, gc, dc):\n        (x1, x2, x3) = inputs\n\n        def f(inputs, outputs):\n            for idx in [0, 1, 2]:\n                self.assertEqual(type(inputs[idx].shape), tuple)\n                outputs[idx].reshape(inputs[idx].shape)\n                outputs[idx].data[...] = inputs[idx].data * 2\n\n        def grad_f(inputs, outputs):\n            # Ordering is [inputs, outputs, grad_outputs]\n            self.assertEqual(len(inputs), 8)\n            self.assertEqual(len(outputs), 1)\n            for (grad_output_idx, grad_input_idx) in [(6, 0)]:\n                grad_output = inputs[grad_output_idx]\n                grad_input = outputs[grad_input_idx]\n                grad_input.reshape(grad_output.shape)\n                grad_input.data[...] = grad_output.data * 2\n\n        op = CreatePythonOperator(\n            f, [\"x1\", \"x2\", \"x3\"], [\"y1\", \"y2\", \"y3\"],\n            grad_f=grad_f,\n            grad_output_indices=[0, 2],  # Receive grad outputs for y1 and y3\n            grad_input_indices=[0]       # Produce grad inputs for x1\n        )\n\n        self.assertGradientChecks(gc, op, [x1, x2, x3], 0, [0, 2])\n        self.assertDeviceChecks(dc, op, [x1, x2, x3], [0, 1, 2])\n"
  },
  {
    "path": "caffe2/python/queue_util.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package queue_util\n# Module caffe2.python.queue_util\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, dataio\nfrom caffe2.python.task import TaskGroup\n\n\nclass _QueueReader(dataio.Reader):\n    def __init__(self, wrapper, num_dequeue_records=1):\n        assert wrapper.schema is not None, (\n            'Queue needs a schema in order to be read from.')\n        dataio.Reader.__init__(self, wrapper.schema())\n        self._wrapper = wrapper\n        self._num_dequeue_records = num_dequeue_records\n\n    def setup_ex(self, init_net, exit_net):\n        exit_net.CloseBlobsQueue([self._wrapper.queue()], 0)\n\n    def read_ex(self, local_init_net, local_finish_net):\n        self._wrapper._new_reader(local_init_net)\n        dequeue_net = core.Net('dequeue')\n        fields, status_blob = dequeue(\n            dequeue_net,\n            self._wrapper.queue(),\n            len(self.schema().field_names()),\n            field_names=self.schema().field_names(),\n            num_records=self._num_dequeue_records)\n        return [dequeue_net], status_blob, fields\n\n    def read(self, net):\n        net, _, fields = self.read_ex(net, None)\n        return net, fields\n\n\nclass _QueueWriter(dataio.Writer):\n    def __init__(self, wrapper):\n        self._wrapper = wrapper\n\n    def setup_ex(self, init_net, exit_net):\n        exit_net.CloseBlobsQueue([self._wrapper.queue()], 0)\n\n    def write_ex(self, fields, local_init_net, local_finish_net, status):\n        self._wrapper._new_writer(self.schema(), local_init_net)\n        enqueue_net = core.Net('enqueue')\n        enqueue(enqueue_net, self._wrapper.queue(), fields, status)\n        return [enqueue_net]\n\n\nclass QueueWrapper(dataio.Pipe):\n    def __init__(self, handler, schema=None, num_dequeue_records=1):\n        dataio.Pipe.__init__(self, schema, TaskGroup.LOCAL_SETUP)\n        self._queue = handler\n        self._num_dequeue_records = num_dequeue_records\n\n    def reader(self):\n        return _QueueReader(\n            self, num_dequeue_records=self._num_dequeue_records)\n\n    def writer(self):\n        return _QueueWriter(self)\n\n    def queue(self):\n        return self._queue\n\n\nclass Queue(QueueWrapper):\n    def __init__(self, capacity, schema=None, name='queue',\n                 num_dequeue_records=1):\n        # find a unique blob name for the queue\n        net = core.Net(name)\n        queue_blob = net.AddExternalInput(net.NextName('handler'))\n        QueueWrapper.__init__(\n            self, queue_blob, schema, num_dequeue_records=num_dequeue_records)\n        self.capacity = capacity\n        self._setup_done = False\n\n    def setup(self, global_init_net):\n        assert self._schema, 'This queue does not have a schema.'\n        self._setup_done = True\n        global_init_net.CreateBlobsQueue(\n            [],\n            [self._queue],\n            capacity=self.capacity,\n            num_blobs=len(self._schema.field_names()),\n            field_names=self._schema.field_names())\n\n\ndef enqueue(net, queue, data_blobs, status=None):\n    if status is None:\n        status = net.NextName('status')\n    results = net.SafeEnqueueBlobs([queue] + data_blobs, data_blobs + [status])\n    return results[-1]\n\n\ndef dequeue(net, queue, num_blobs, status=None, field_names=None,\n            num_records=1):\n    if field_names is not None:\n        assert len(field_names) == num_blobs\n        data_names = [net.NextName(name) for name in field_names]\n    else:\n        data_names = [net.NextName('data', i) for i in range(num_blobs)]\n    if status is None:\n        status = net.NextName('status')\n    results = net.SafeDequeueBlobs(\n        queue, data_names + [status], num_records=num_records)\n    results = list(results)\n    status_blob = results.pop(-1)\n    return results, status_blob\n\n\ndef close_queue(step, *queues):\n    close_net = core.Net(\"close_queue_net\")\n    for queue in queues:\n        close_net.CloseBlobsQueue([queue], 0)\n    close_step = core.execution_step(\"%s_step\" % str(close_net), close_net)\n    return core.execution_step(\n        \"%s_wraper_step\" % str(close_net),\n        [step, close_step])\n"
  },
  {
    "path": "caffe2/python/record_queue.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package record_queue\n# Module caffe2.python.record_queue\n\"\"\"\nImplementation of a queue wrapper.\n\"\"\"\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core\nfrom caffe2.python.dataio import Reader, Writer\nfrom caffe2.python.schema import (\n    Struct, Field, from_column_list)\n\n\nclass _QueueReader(Reader):\n    def __init__(self, blobs_queue, schema, name=None):\n        \"\"\"Don't call this directly. Instead, use dataset.reader()\"\"\"\n        super(_QueueReader, self).__init__(schema)\n        self.blobs_queue = blobs_queue\n        self.name = name\n\n    def read(self, read_net):\n        with core.NameScope(read_net.NextName(self.name)):\n            status = read_net.NextName()\n            fields = read_net.SafeDequeueBlobs(\n                self.blobs_queue, self._schema.field_names() + [status])\n            return (fields[-1], fields[:-1])\n\n\nclass _QueueWriter(Writer):\n    def __init__(self, blobs_queue, schema):\n        self.blobs_queue = blobs_queue\n        self.schema = schema\n\n    def write(self, writer_net, fields):\n        if isinstance(fields, Field):\n            fields = fields.field_blobs()\n        writer_net.CheckDatasetConsistency(\n            fields, [], fields=self.schema.field_names())\n        status = writer_net.NextName()\n        writer_net.SafeEnqueueBlobs(\n            [self.blobs_queue] + fields, fields + [status])\n        return status\n\n\nclass RecordQueue(object):\n    \"\"\" The class is used to feed data with some process from a reader into a\n        queue and provider a reader interface for data fetching from the queue.\n    \"\"\"\n    def __init__(self, fields, name=None, capacity=1,\n                 enforce_unique_name=False, num_threads=1):\n        assert isinstance(fields, list) or isinstance(fields, Struct), (\n            'fields must be either a Struct or a list of raw field names.')\n        if isinstance(fields, list):\n            fields = from_column_list(fields)\n        self.schema = fields\n        self.name = name or 'queue'\n        self.num_threads = num_threads\n        num_blobs = len(self.schema.field_names())\n        init_net = core.Net(self.name + '/init_net')\n        self.blobs_queue = init_net.CreateBlobsQueue(\n            [], 1,\n            capacity=capacity,\n            num_blobs=num_blobs,\n            enforce_unique_name=enforce_unique_name)\n        core.workspace.RunNetOnce(init_net)\n\n        self.writer = _QueueWriter(self.blobs_queue, self.schema)\n        reader_name = self.name + '_reader'\n        self.reader = _QueueReader(self.blobs_queue, self.schema, reader_name)\n\n        exit_net = core.Net(self.name + '/exit_net')\n        exit_net.CloseBlobsQueue(self.blobs_queue, 0)\n        self.exit_step = core.execution_step(\n            '{}_close_step'.format(str(exit_net)),\n            exit_net)\n\n    def build(self, reader, process=None):\n        \"\"\"\n        Build the producer_step to feed data from reader into the queue, and\n        return the reader interface.\n        Inputs:\n            reader:           read data which will be stored in the queue.\n            process:          preprocess data before enqueue.\n        Outputs:\n            reader:           reader to fetch the data from the queue.\n            producer_step:    the step insert the data into the queue. Should be\n                              run with comsume_step together.\n            exit_step:        the step to close queue\n            schema:           the schema for the reader.\n        \"\"\"\n        producer_steps = []\n        for i in range(self.num_threads):\n            name = 'reader_' + str(i)\n            net_reader = core.Net(name)\n            should_stop, fields = reader.read_record(net_reader)\n            step_read = core.execution_step(name, net_reader)\n\n            name = 'queue_writer' + str(i)\n            net_prod = core.Net(name)\n            field_blobs = fields.field_blobs()\n            if process:\n                field_blobs = process(net_prod, fields).field_blobs()\n\n            self.writer.write(net_prod, field_blobs)\n            step_prod = core.execution_step(name, net_prod)\n            step = core.execution_step(\n                'producer_' + str(i),\n                [step_read, step_prod],\n                should_stop_blob=should_stop)\n            producer_steps.append(step)\n        producer_step = core.execution_step(\n            'producers',\n            producer_steps,\n            concurrent_substeps=True)\n        return self.reader, producer_step, self.exit_step, self.schema\n"
  },
  {
    "path": "caffe2/python/recurrent.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package recurrent\n# Module caffe2.python.recurrent\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, workspace\nfrom future.utils import viewitems, viewkeys\n\ndef recurrent_net(\n        net, cell_net, inputs, initial_cell_inputs,\n        links, timestep=None, scope=None, outputs_with_grads=(0,),\n        recompute_blobs_on_backward=None, forward_only=False,\n):\n    '''\n    net: the main net operator should be added to\n\n    cell_net: cell_net which is executed in a recurrent fasion\n\n    inputs: sequences to be fed into the recurrent net. Currently only one input\n    is supported. It has to be in a format T x N x (D1...Dk) where T is lengths\n    of the sequence. N is a batch size and (D1...Dk) are the rest of dimentions\n\n    initial_cell_inputs: inputs of the cell_net for the 0 timestamp.\n    Format for each input is:\n        (cell_net_input_name, external_blob_with_data)\n\n    links: a dictionary from cell_net input names in moment t+1 and\n    output names of moment t. Currently we assume that each output becomes\n    an input for the next timestep.\n\n    timestep: name of the timestep blob to be used. If not provided \"timestep\"\n    is used.\n\n    scope: Internal blobs are going to be scoped in a format\n    <scope_name>/<blob_name>\n    If not provided we generate a scope name automatically\n\n    outputs_with_grads : position indices of output blobs which will receive\n    error gradient (from outside recurrent network) during backpropagation\n\n    recompute_blobs_on_backward: specify a list of blobs that will be\n                 recomputed for backward pass, and thus need not to be\n                 stored for each forward timestep.\n\n    forward_only: if True, only forward steps are executed\n    '''\n    assert len(inputs) == 1, \"Only one input blob is supported so far\"\n\n    input_blobs = [str(i[0]) for i in inputs]\n    initial_input_blobs = [str(x[1]) for x in initial_cell_inputs]\n    op_name = net.NextName('recurrent')\n\n    def s(name):\n        # We have to manually scope due to our internal/external blob\n        # relationships.\n        scope_name = op_name if scope is None else scope\n        return \"{}/{}\".format(str(scope_name), str(name))\n\n    # determine inputs that are considered to be references\n    # it is those that are not referred to in inputs or initial_cell_inputs\n    known_inputs = [str(b) for b in input_blobs + initial_input_blobs]\n    known_inputs += [str(x[0]) for x in initial_cell_inputs]\n    if timestep is not None:\n        known_inputs.append(str(timestep))\n    references = [\n        core.BlobReference(b) for b in cell_net.Proto().external_input\n        if b not in known_inputs]\n\n    inner_outputs = list(cell_net.Proto().external_output)\n    # These gradients are expected to be available during the backward pass\n    inner_outputs_map = {o: o + '_grad' for o in inner_outputs}\n\n    # compute the backward pass of the cell net\n    if not forward_only:\n        backward_ops, backward_mapping = core.GradientRegistry.GetBackwardPass(\n            cell_net.Proto().op, inner_outputs_map)\n        backward_mapping = {str(k): v for k, v in viewitems(backward_mapping)}\n\n        backward_cell_net = core.Net(\"RecurrentBackwardStep\")\n        del backward_cell_net.Proto().op[:]\n\n        if recompute_blobs_on_backward is not None:\n            # Insert operators to re-compute the specified blobs.\n            # They are added in the same order as for the forward pass, thus\n            # the order is correct.\n            recompute_blobs_on_backward = {str(b) for b in\n                                           recompute_blobs_on_backward}\n\n            for op in cell_net.Proto().op:\n                if not recompute_blobs_on_backward.isdisjoint(set(op.output)):\n                    backward_cell_net.Proto().op.extend([op])\n                    # This fires if other outputs than the declared\n                    # are computed by the ops that are recomputed\n                    assert set(op.output).issubset(recompute_blobs_on_backward)\n\n        backward_cell_net.Proto().op.extend(backward_ops)\n        # compute blobs used but not defined in the backward pass\n        backward_ssa, backward_blob_versions = core.get_ssa(\n            backward_cell_net.Proto())\n        undefined = core.get_undefined_blobs(backward_ssa)\n\n        # also add to the output list the intermediate outputs of fwd_step that\n        # are used by backward.\n        ssa, blob_versions = core.get_ssa(cell_net.Proto())\n        scratches = [\n            blob\n            for blob, ver in viewitems(blob_versions)\n            if (ver > 0 and\n                blob in undefined and\n                blob not in cell_net.Proto().external_output)\n        ]\n        backward_cell_net.Proto().external_input.extend(scratches)\n        backward_cell_net.Proto().type = 'simple'\n    else:\n        backward_cell_net = None\n\n    all_inputs = [i[1] for i in inputs] + [\n        x[1] for x in initial_cell_inputs] + references\n    all_outputs = []\n\n    cell_net.Proto().type = 'simple'\n\n    # Internal arguments used by RecurrentNetwork operator\n\n    # Links are in the format blob_name, recurrent_states, offset.\n    # In the moment t we know that corresponding data block is at\n    # t + offset position in the recurrent_states tensor\n    forward_links = []\n    backward_links = []\n\n    # Aliases are used to expose outputs to external world\n    # Format (internal_blob, external_blob, offset)\n    # Negative offset stands for going from the end,\n    # positive - from the beginning\n    aliases = []\n\n    # States held inputs to the cell net\n    recurrent_states = []\n\n    for cell_input, _ in initial_cell_inputs:\n        cell_input = str(cell_input)\n        # Recurrent_states is going to be (T + 1) x ...\n        # It stores all inputs and outputs of the cell net over time.\n        # Or their gradients in the case of the backward pass.\n        state = s(cell_input + \"_states\")\n        states_grad = state + \"_grad\"\n        cell_output = links[str(cell_input)]\n        forward_links.append((cell_input, state, 0))\n        forward_links.append((cell_output, state, 1))\n\n        aliases.append((state, cell_output + \"_all\", 1))\n        aliases.append((state, cell_output + \"_last\", -1))\n        all_outputs.extend([cell_output + \"_all\", cell_output + \"_last\"])\n\n        recurrent_states.append(state)\n\n        if backward_cell_net is not None:\n            backward_links.append((cell_output + \"_grad\", states_grad, 1))\n            backward_cell_net.Proto().external_input.append(\n                str(cell_output) + \"_grad\")\n\n            recurrent_input_grad = cell_input + \"_grad\"\n            if not backward_blob_versions.get(recurrent_input_grad, 0):\n                # If nobody writes to this recurrent input gradient, we need\n                # to make sure it gets to the states grad blob after all.\n                # We do this by using backward_links which triggers an alias\n                # This logic is being used for example in a SumOp case\n                backward_links.append(\n                    (backward_mapping[cell_input], states_grad, 0))\n            else:\n                backward_links.append((recurrent_input_grad, states_grad, 0))\n\n\n    for input_t, input_blob in inputs:\n        forward_links.append((str(input_t), str(input_blob), 0))\n\n    if backward_cell_net is not None:\n        for input_t, input_blob in inputs:\n            backward_links.append((\n                backward_mapping[str(input_t)], str(input_blob) + \"_grad\", 0\n            ))\n        backward_cell_net.Proto().external_input.extend(\n            cell_net.Proto().external_input)\n        backward_cell_net.Proto().external_input.extend(\n            cell_net.Proto().external_output)\n\n    def unpack_triple(x):\n        if x:\n            a, b, c = zip(*x)\n            return a, b, c\n        return [], [], []\n\n    # Splitting to separate lists so we can pass them to c++\n    # where we ensemle them back\n    link_internal, link_external, link_offset = unpack_triple(forward_links)\n    alias_src, alias_dst, alias_offset = unpack_triple(aliases)\n\n    recurrent_inputs = [str(x[1]) for x in initial_cell_inputs]\n\n    # Make sure that recurrent gradients accumulate with internal gradients\n    # (if a blob in the backward_cell_net receives gradient from both an\n    # external connection as well as from within the backward_cell_net,\n    # those gradients need to be added together, rather than one overwriting\n    # the other)\n    if backward_cell_net is not None:\n        proto = backward_cell_net.Proto()\n        operators = []\n        while len(proto.op) > 0:\n            op = proto.op[-1]\n            proto.op.remove(op)\n            operators.append(op)\n        for op in operators[::-1]:\n            proto.op.extend([op])\n            for j, output_blob in enumerate(op.output):\n                if output_blob in proto.external_input:\n                    # In place operation won't cause issues because it takes\n                    # existing value of a blob into account\n                    if output_blob in op.input:\n                        continue\n                    output_blob = core.BlobReference(output_blob)\n                    accum_blob = output_blob + \"_accum\"\n                    proto.op[-1].output[j] = str(accum_blob)\n                    backward_cell_net.Sum(\n                        [output_blob, accum_blob],\n                        [output_blob],\n                    )\n\n    def map_to_dual_list(m):\n        return [str(x) for x in list(m.keys())] + \\\n               [str(x) for x in list(m.values())]\n\n    backward_args = {}\n    if backward_cell_net is not None:\n        backward_mapping_keys = set(viewkeys(backward_mapping))\n        backward_link_internal, backward_link_external, backward_link_offset = \\\n            unpack_triple(backward_links)\n        params = [x for x in references if x in backward_mapping_keys]\n        param_grads = [\n            str(backward_mapping[x])\n            for x in references\n            if x in backward_mapping_keys\n        ]\n        if recompute_blobs_on_backward is None:\n            recompute_blobs_on_backward = set()\n        backward_args = {\n            'param': [all_inputs.index(p) for p in params],\n            'backward_link_internal': [str(l) for l in backward_link_internal],\n            'backward_link_external': [str(l) for l in backward_link_external],\n            'backward_link_offset': backward_link_offset,\n            'outputs_with_grads': outputs_with_grads,\n            'recompute_blobs_on_backward': [\n                str(b) for b in recompute_blobs_on_backward\n            ],\n            'param_grads': param_grads,\n        }\n        if len(backward_cell_net.Proto().op) != 0:\n            backward_args['backward_step_net'] = backward_cell_net.Proto()\n\n\n    results = net.RecurrentNetwork(\n        all_inputs,\n        all_outputs + [s(\"step_workspaces\")],\n        alias_src=alias_src,\n        alias_dst=[str(a) for a in alias_dst],\n        alias_offset=alias_offset,\n        recurrent_states=recurrent_states,\n        initial_recurrent_state_ids=[\n            all_inputs.index(i) for i in recurrent_inputs\n        ],\n        link_internal=[str(l) for l in link_internal],\n        link_external=[str(l) for l in link_external],\n        link_offset=link_offset,\n        enable_rnn_executor=1,\n        step_net=cell_net.Proto(),\n        timestep=\"timestep\" if timestep is None else str(timestep),\n        **backward_args\n    )\n\n    # Restore net type since 'rnn' is not recognized outside RNNs\n    cell_net.Proto().type = 'simple'\n\n    # The last output is a list of step workspaces,\n    # which is only needed internally for gradient propogation\n    return results[:-1]\n\n\ndef set_rnn_executor_config(rnn_op, num_threads=None, max_cuda_streams=None):\n    from caffe2.proto import caffe2_pb2\n    assert rnn_op.type in {'RecurrentNetwork', 'RecurrentNetworkGradient'}\n\n    def add_arg(s, v):\n        a = caffe2_pb2.Argument()\n        a.name = \"rnn_executor.\" + s\n        a.i = v\n        rnn_op.arg.extend([a])\n\n    if num_threads is not None:\n        add_arg('num_threads', num_threads)\n    if max_cuda_streams is not None:\n        add_arg('max_cuda_streams', max_cuda_streams)\n\n\ndef retrieve_step_blobs(net, prefix='rnn'):\n    '''\n    Retrieves blobs from step workspaces (which contain intermediate recurrent\n    network computation for each timestep) and puts them in the global\n    workspace. This allows access to the contents of this intermediate\n    computation in python. Returns the list of extracted blob names.\n\n    net: the net from which the step workspace blobs should be extracted\n\n    prefix: prefix to append to extracted blob names when placing them in the\n    global workspace\n    '''\n    count = 1\n    output_list = []\n    for op in net.Proto().op:\n        if op.type == \"RecurrentNetwork\":\n            blob_name = prefix + \"_\" + str(count)\n            count = count + 1\n            scratch_workspaces_blob_name = op.output[-1]\n            workspace.RunOperatorOnce(\n                core.CreateOperator(\n                    \"RecurrentNetworkBlobFetcher\",\n                    [scratch_workspaces_blob_name],\n                    [blob_name],\n                    prefix=prefix\n                )\n            )\n            output_list += workspace.FetchBlob(blob_name).tolist()\n    return output_list\n"
  },
  {
    "path": "caffe2/python/regularizer.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n# @package optimizer\n# Module caffe2.python.optimizer\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n\nfrom caffe2.python import core\n\n\nclass Regularizer(object):\n    def __init__(self):\n        self.apply_after_optimizer = False\n\n    '''\n    Adds regularization to train_net for given parameter. Its factor ahead of\n    regularization is given when initialization.\n    The param should be a BlobReference.\n    '''\n\n    def __call__(self, net, param_init_net, param, grad=None):\n        assert isinstance(param, core.BlobReference)\n        return self._run(net, param_init_net, param, grad)\n\n    def _run(self, net, param_init_net, param, grad):\n        raise Exception(\"Not Impelemented\")\n\n\nclass L1Norm(Regularizer):\n    def __init__(self, reg_lambda):\n        super(L1Norm, self).__init__()\n        assert reg_lambda >= 0,\\\n            'factor ahead of regularization should be 0 or positive'\n\n        self.reg_lambda = reg_lambda\n\n    def _run(self, net, param_init_net, param, grad=None):\n        output_blob = net.NextScopedBlob(param + '_l1_regularization')\n        net.LpNorm([param], [output_blob], p=1)\n        net.Scale([output_blob], [output_blob], scale=self.reg_lambda)\n        return output_blob\n\n\nclass L2Norm(Regularizer):\n    def __init__(self, reg_lambda):\n        super(L2Norm, self).__init__()\n        assert reg_lambda >= 0,\\\n            'factor ahead of regularization should be 0 or positive'\n\n        self.reg_lambda = reg_lambda\n\n    def _run(self, net, param_init_net, param, grad=None):\n        output_blob = net.NextScopedBlob(param + '_l2_regularization')\n        net.LpNorm([param], [output_blob], p=2)\n        net.Scale([output_blob], [output_blob], scale=self.reg_lambda)\n        return output_blob\n\n\nclass MaxNorm(Regularizer):\n    def __init__(self, norm=1.0):\n        super(MaxNorm, self).__init__()\n        self.norm = norm\n        self.apply_after_optimizer = True\n\n    def _run(self, net, param_init_net, param, grad):\n        assert self.norm > 0, 'norm should be bigger than 0.'\n        if isinstance(grad, core.GradientSlice):\n            net.SparseNormalize(\n                [param, grad.indices, grad.values],\n                [param],\n                use_max_norm=True,\n                norm=self.norm,\n            )\n        else:\n            raise NotImplementedError(\n                \"MaxNorm is not supported for dense parameters\"\n            )\n\n\nclass ConstantNorm(Regularizer):\n    def __init__(self, norm=1.0):\n        super(ConstantNorm, self).__init__()\n        self.norm = norm\n        self.apply_after_optimizer = True\n\n    def _run(self, net, param_init_net, param, grad):\n        assert self.norm > 0, 'norm should be bigger than 0.'\n        if isinstance(grad, core.GradientSlice):\n            net.SparseNormalize(\n                [param, grad.indices, grad.values],\n                [param],\n                use_max_norm=False,\n                norm=self.norm,\n            )\n        else:\n            raise NotImplementedError(\n                \"ConstantNorm is not supported for dense parameters\"\n            )\n"
  },
  {
    "path": "caffe2/python/regularizer_context.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n# @package regularizer_context\n# Module caffe2.python.regularizer_context\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import context\nfrom caffe2.python.modifier_context import (\n    ModifierContext, UseModifierBase)\n\n\n@context.define_context(allow_default=True)\nclass RegularizerContext(ModifierContext):\n    \"\"\"\n    provide context to allow param_info to have different regularizers\n    \"\"\"\n\n    def has_regularizer(self, name):\n        return self._has_modifier(name)\n\n    def get_regularizer(self, name):\n        assert self.has_regularizer(name), (\n            \"{} regularizer is not provided!\".format(name))\n        return self._get_modifier(name)\n\n\nclass UseRegularizer(UseModifierBase):\n    '''\n    context class to allow setting the current context.\n    Example useage with layer:\n        regularizers = {'reg1': reg1, 'reg2': reg2}\n        with Regularizers(regularizers):\n            reg = RegularizerContext.current().get_regularizer('reg1')\n            layer(reg=reg)\n    '''\n    def _context_class(self):\n        return RegularizerContext\n"
  },
  {
    "path": "caffe2/python/regularizer_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nfrom caffe2.python import schema\nfrom caffe2.python.regularizer_context import UseRegularizer, RegularizerContext\nfrom caffe2.python.regularizer import L1Norm\nfrom caffe2.python.optimizer import SgdOptimizer\nfrom caffe2.python.layer_test_util import LayersTestCase\nfrom caffe2.python import layer_model_instantiator\n\nfrom hypothesis import given\n\nimport caffe2.python.hypothesis_test_util as hu\nimport numpy as np\n\n\nclass TestRegularizerContext(LayersTestCase):\n    @given(\n        X=hu.arrays(dims=[2, 5]),\n    )\n    def test_regularizer_context(self, X):\n        weight_reg_out = L1Norm(0.2)\n        bias_reg_out = L1Norm(0)\n        regularizers = {\n            'WEIGHT': weight_reg_out,\n            'BIAS': bias_reg_out\n        }\n\n        output_dims = 2\n        input_record = self.new_record(schema.Scalar((np.float32, (5,))))\n        schema.FeedRecord(input_record, [X])\n\n        with UseRegularizer(regularizers):\n            weight_reg = RegularizerContext.current().get_regularizer('WEIGHT')\n            bias_reg = RegularizerContext.current().get_regularizer('BIAS')\n            optim = SgdOptimizer(0.15)\n\n            assert weight_reg == weight_reg_out, \\\n                'fail to get correct weight reg from context'\n            assert bias_reg == bias_reg_out, \\\n                'fail to get correct bias reg from context'\n            fc_output = self.model.FC(\n                input_record,\n                output_dims,\n                weight_optim=optim,\n                bias_optim=optim,\n                weight_reg=weight_reg,\n                bias_reg=bias_reg\n            )\n            # model.output_schema has to a struct\n            self.model.output_schema = schema.Struct((\n                'fc_output', fc_output\n            ))\n\n            self.assertEqual(\n                schema.Scalar((np.float32, (output_dims, ))),\n                fc_output\n            )\n\n            _, train_net = layer_model_instantiator.generate_training_nets(self.model)\n            ops = train_net.Proto().op\n            ops_type_list = [ops[i].type for i in range(len(ops))]\n            assert ops_type_list.count('LpNorm') == 2\n            assert ops_type_list.count('Scale') == 4\n            assert ops_type_list.count('LpNormGradient') == 2\n"
  },
  {
    "path": "caffe2/python/rnn/lstm_comparison.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import workspace, core, lstm_benchmark, utils\nfrom copy import copy\n\n@utils.debug\ndef Compare(args):\n    results = []\n    num_iters = 1000\n    args.gpu = True\n    with core.DeviceScope(core.DeviceOption(caffe2_pb2.CUDA, 0)):\n        for batch_size in [64, 128, 256]:\n            for seq_length in [20, 100]:\n                for hidden_dim in [40, 100, 400, 800]:\n                    args.batch_size = batch_size\n                    args.seq_length = seq_length\n                    args.hidden_dim = hidden_dim\n                    args.data_size = batch_size * seq_length * num_iters\n                    args.iters_to_report = num_iters // 3\n\n                    args.implementation = 'own'\n                    t_own = lstm_benchmark.Benchmark(args)\n                    workspace.ResetWorkspace()\n                    args.implementation = 'cudnn'\n                    t_cudnn = lstm_benchmark.Benchmark(args)\n                    workspace.ResetWorkspace()\n                    results.append((copy(args), float(t_own), float(t_cudnn)))\n                    print(args)\n                    print(\"t_cudnn / t_own: {}\".format(t_cudnn / t_own))\n\n    for args, t_own, t_cudnn in results:\n        print(\"{}: cudnn time: {}, own time: {}, ratio: {}\".format(\n            str(args), t_cudnn, t_own, t_cudnn / t_own))\n\n    ratio_sum = 0\n    for args, t_own, t_cudnn in results:\n        ratio = float(t_cudnn) / t_own\n        ratio_sum += ratio\n        print(\"hidden_dim: {}, seq_lengths: {}, batch_size: {}, num_layers: {}:\"\n              \" cudnn time: {}, own time: {}, ratio: {}\".format(\n                  args.hidden_dim, args.seq_length, args.batch_size,\n                  args.num_layers, t_cudnn, t_own, ratio))\n\n    print(\"Ratio average: {}\".format(ratio_sum / len(results)))\n\n\nif __name__ == '__main__':\n    args = lstm_benchmark.GetArgumentParser().parse_args()\n\n    workspace.GlobalInit([\n        'caffe2',\n        '--caffe2_log_level=0',\n        '--caffe2_print_blob_sizes_at_exit=0',\n        '--caffe2_gpu_memory_tracking=1'])\n\n    Compare(args)\n"
  },
  {
    "path": "caffe2/python/rnn/rnn_cell_test_util.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import workspace, scope\nfrom caffe2.python.model_helper import ModelHelper\n\nimport numpy as np\n\n\ndef sigmoid(x):\n    return 1.0 / (1.0 + np.exp(-x))\n\n\ndef tanh(x):\n    return 2.0 * sigmoid(2.0 * x) - 1\n\n\ndef _prepare_rnn(\n    t, n, dim_in, create_rnn, outputs_with_grads,\n    forget_bias, memory_optim=False,\n    forward_only=False, drop_states=False, T=None,\n    two_d_initial_states=None, dim_out=None,\n    num_states=2,\n    **kwargs\n):\n    if dim_out is None:\n        dim_out = [dim_in]\n    print(\"Dims: \", t, n, dim_in, dim_out)\n\n    model = ModelHelper(name='external')\n\n    if two_d_initial_states is None:\n        two_d_initial_states = np.random.randint(2)\n\n    def generate_input_state(n, d):\n        if two_d_initial_states:\n            return np.random.randn(n, d).astype(np.float32)\n        else:\n            return np.random.randn(1, n, d).astype(np.float32)\n\n    states = []\n    for layer_id, d in enumerate(dim_out):\n        for i in range(num_states):\n            state_name = \"state_{}/layer_{}\".format(i, layer_id)\n            states.append(model.net.AddExternalInput(state_name))\n            workspace.FeedBlob(\n                states[-1], generate_input_state(n, d).astype(np.float32))\n\n    # Due to convoluted RNN scoping logic we make sure that things\n    # work from a namescope\n    with scope.NameScope(\"test_name_scope\"):\n        input_blob, seq_lengths = model.net.AddScopedExternalInputs(\n            'input_blob', 'seq_lengths')\n\n        outputs = create_rnn(\n            model, input_blob, seq_lengths, states,\n            dim_in=dim_in, dim_out=dim_out, scope=\"external/recurrent\",\n            outputs_with_grads=outputs_with_grads,\n            memory_optimization=memory_optim,\n            forget_bias=forget_bias,\n            forward_only=forward_only,\n            drop_states=drop_states,\n            static_rnn_unroll_size=T,\n            **kwargs\n        )\n\n    workspace.RunNetOnce(model.param_init_net)\n\n    workspace.FeedBlob(\n        seq_lengths,\n        np.random.randint(1, t + 1, size=(n,)).astype(np.int32)\n    )\n    return outputs, model.net, states + [input_blob]\n"
  },
  {
    "path": "caffe2/python/rnn_cell.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package rnn_cell\n# Module caffe2.python.rnn_cell\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport functools\nimport inspect\nimport itertools\nimport logging\nimport numpy as np\nimport random\nimport six\nfrom future.utils import viewkeys\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python.attention import (\n    apply_dot_attention,\n    apply_recurrent_attention,\n    apply_regular_attention,\n    apply_soft_coverage_attention,\n    AttentionType,\n)\nfrom caffe2.python import core, recurrent, workspace, brew, scope, utils\nfrom caffe2.python.modeling.parameter_sharing import ParameterSharing\nfrom caffe2.python.modeling.parameter_info import ParameterTags\nfrom caffe2.python.modeling.initializers import Initializer\nfrom caffe2.python.model_helper import ModelHelper\n\n\ndef _RectifyName(blob_reference_or_name):\n    if blob_reference_or_name is None:\n        return None\n    if isinstance(blob_reference_or_name, six.string_types):\n        return core.ScopedBlobReference(blob_reference_or_name)\n    if not isinstance(blob_reference_or_name, core.BlobReference):\n        raise Exception(\"Unknown blob reference type\")\n    return blob_reference_or_name\n\n\ndef _RectifyNames(blob_references_or_names):\n    if blob_references_or_names is None:\n        return None\n    return list(map(_RectifyName, blob_references_or_names))\n\n\nclass RNNCell(object):\n    '''\n    Base class for writing recurrent / stateful operations.\n\n    One needs to implement 2 methods: apply_override\n    and get_state_names_override.\n\n    As a result base class will provice apply_over_sequence method, which\n    allows you to apply recurrent operations over a sequence of any length.\n\n    As optional you could add input and output preparation steps by overriding\n    corresponding methods.\n    '''\n    def __init__(self, name=None, forward_only=False, initializer=None):\n        self.name = name\n        self.recompute_blobs = []\n        self.forward_only = forward_only\n        self._initializer = initializer\n\n    @property\n    def initializer(self):\n        return self._initializer\n\n    @initializer.setter\n    def initializer(self, value):\n        self._initializer = value\n\n    def scope(self, name):\n        return self.name + '/' + name if self.name is not None else name\n\n    def apply_over_sequence(\n        self,\n        model,\n        inputs,\n        seq_lengths=None,\n        initial_states=None,\n        outputs_with_grads=None,\n    ):\n        if initial_states is None:\n            with scope.NameScope(self.name):\n                if self.initializer is None:\n                    raise Exception(\"Either initial states \"\n                                    \"or initializer have to be set\")\n                initial_states = self.initializer.create_states(model)\n\n        preprocessed_inputs = self.prepare_input(model, inputs)\n        step_model = ModelHelper(name=self.name, param_model=model)\n        input_t, timestep = step_model.net.AddScopedExternalInputs(\n            'input_t',\n            'timestep',\n        )\n        utils.raiseIfNotEqual(\n            len(initial_states), len(self.get_state_names()),\n            \"Number of initial state values provided doesn't match the number \"\n            \"of states\"\n        )\n        states_prev = step_model.net.AddScopedExternalInputs(*[\n            s + '_prev' for s in self.get_state_names()\n        ])\n        states = self._apply(\n            model=step_model,\n            input_t=input_t,\n            seq_lengths=seq_lengths,\n            states=states_prev,\n            timestep=timestep,\n        )\n\n        external_outputs = set(step_model.net.Proto().external_output)\n        for state in states:\n            if state not in external_outputs:\n                step_model.net.AddExternalOutput(state)\n\n        if outputs_with_grads is None:\n            outputs_with_grads = [self.get_output_state_index() * 2]\n\n        # states_for_all_steps consists of combination of\n        # states gather for all steps and final states. It looks like this:\n        # (state_1_all, state_1_final, state_2_all, state_2_final, ...)\n        states_for_all_steps = recurrent.recurrent_net(\n            net=model.net,\n            cell_net=step_model.net,\n            inputs=[(input_t, preprocessed_inputs)],\n            initial_cell_inputs=list(zip(states_prev, initial_states)),\n            links=dict(zip(states_prev, states)),\n            timestep=timestep,\n            scope=self.name,\n            forward_only=self.forward_only,\n            outputs_with_grads=outputs_with_grads,\n            recompute_blobs_on_backward=self.recompute_blobs,\n        )\n\n        output = self._prepare_output_sequence(\n            model,\n            states_for_all_steps,\n        )\n        return output, states_for_all_steps\n\n    def apply(self, model, input_t, seq_lengths, states, timestep):\n        input_t = self.prepare_input(model, input_t)\n        states = self._apply(\n            model, input_t, seq_lengths, states, timestep)\n        output = self._prepare_output(model, states)\n        return output, states\n\n    def _apply(\n        self,\n        model, input_t, seq_lengths, states, timestep, extra_inputs=None\n    ):\n        '''\n        This  method uses apply_override provided by a custom cell.\n        On the top it takes care of applying self.scope() to all the outputs.\n        While all the inputs stay within the scope this function was called\n        from.\n        '''\n        args = self._rectify_apply_inputs(\n            input_t, seq_lengths, states, timestep, extra_inputs)\n        with core.NameScope(self.name):\n            return self.apply_override(model, *args)\n\n    def _rectify_apply_inputs(\n            self, input_t, seq_lengths, states, timestep, extra_inputs):\n        '''\n        Before applying a scope we make sure that all external blob names\n        are converted to blob reference. So further scoping doesn't affect them\n        '''\n\n        input_t, seq_lengths, timestep = _RectifyNames(\n            [input_t, seq_lengths, timestep])\n        states = _RectifyNames(states)\n        if extra_inputs:\n            extra_input_names, extra_input_sizes = zip(*extra_inputs)\n            extra_inputs = _RectifyNames(extra_input_names)\n            extra_inputs = zip(extra_input_names, extra_input_sizes)\n\n        arg_names = inspect.getargspec(self.apply_override).args\n        rectified = [input_t, seq_lengths, states, timestep]\n        if 'extra_inputs' in arg_names:\n            rectified.append(extra_inputs)\n        return rectified\n\n\n    def apply_override(\n        self,\n        model, input_t, seq_lengths, timestep, extra_inputs=None,\n    ):\n        '''\n        A single step of a recurrent network to be implemented by each custom\n        RNNCell.\n\n        model: ModelHelper object new operators would be added to\n\n        input_t: singlse input with shape (1, batch_size, input_dim)\n\n        seq_lengths: blob containing sequence lengths which would be passed to\n        LSTMUnit operator\n\n        states: previous recurrent states\n\n        timestep: current recurrent iteration. Could be used together with\n        seq_lengths in order to determine, if some shorter sequences\n        in the batch have already ended.\n\n        extra_inputs: list of tuples (input, dim). specifies additional input\n        which is not subject to prepare_input(). (useful when a cell is a\n        component of a larger recurrent structure, e.g., attention)\n        '''\n        raise NotImplementedError('Abstract method')\n\n    def prepare_input(self, model, input_blob):\n        '''\n        If some operations in _apply method depend only on the input,\n        not on recurrent states, they could be computed in advance.\n\n        model: ModelHelper object new operators would be added to\n\n        input_blob: either the whole input sequence with shape\n        (sequence_length, batch_size, input_dim) or a single input with shape\n        (1, batch_size, input_dim).\n        '''\n        return input_blob\n\n    def get_output_state_index(self):\n        '''\n        Return index into state list of the \"primary\" step-wise output.\n        '''\n        return 0\n\n    def get_state_names(self):\n        '''\n        Returns recurrent state names with self.name scoping applied\n        '''\n        return list(map(self.scope, self.get_state_names_override()))\n\n    def get_state_names_override(self):\n        '''\n        Override this funtion in your custom cell.\n        It should return the names of the recurrent states.\n\n        It's required by apply_over_sequence method in order to allocate\n        recurrent states for all steps with meaningful names.\n        '''\n        raise NotImplementedError('Abstract method')\n\n    def get_output_dim(self):\n        '''\n        Specifies the dimension (number of units) of stepwise output.\n        '''\n        raise NotImplementedError('Abstract method')\n\n    def _prepare_output(self, model, states):\n        '''\n        Allows arbitrary post-processing of primary output.\n        '''\n        return states[self.get_output_state_index()]\n\n    def _prepare_output_sequence(self, model, state_outputs):\n        '''\n        Allows arbitrary post-processing of primary sequence output.\n\n        (Note that state_outputs alternates between full-sequence and final\n        output for each state, thus the index multiplier 2.)\n        '''\n        output_sequence_index = 2 * self.get_output_state_index()\n        return state_outputs[output_sequence_index]\n\n\nclass LSTMInitializer(object):\n    def __init__(self, hidden_size):\n        self.hidden_size = hidden_size\n\n    def create_states(self, model):\n        return [\n            model.create_param(\n                param_name='initial_hidden_state',\n                initializer=Initializer(operator_name='ConstantFill',\n                                        value=0.0),\n                shape=[self.hidden_size],\n            ),\n            model.create_param(\n                param_name='initial_cell_state',\n                initializer=Initializer(operator_name='ConstantFill',\n                                        value=0.0),\n                shape=[self.hidden_size],\n            )\n        ]\n\n\n# based on http://pytorch.org/docs/master/nn.html#torch.nn.RNNCell\nclass BasicRNNCell(RNNCell):\n    def __init__(\n        self,\n        input_size,\n        hidden_size,\n        forget_bias,\n        memory_optimization,\n        drop_states=False,\n        initializer=None,\n        activation=None,\n        **kwargs\n    ):\n        super(BasicRNNCell, self).__init__(**kwargs)\n        self.drop_states = drop_states\n        self.input_size = input_size\n        self.hidden_size = hidden_size\n        self.activation = activation\n\n        if self.activation not in ['relu', 'tanh']:\n            raise RuntimeError(\n                'BasicRNNCell with unknown activation function (%s)'\n                % self.activation)\n\n    def apply_override(\n        self,\n        model,\n        input_t,\n        seq_lengths,\n        states,\n        timestep,\n        extra_inputs=None,\n    ):\n        hidden_t_prev = states[0]\n\n        gates_t = brew.fc(\n            model,\n            hidden_t_prev,\n            'gates_t',\n            dim_in=self.hidden_size,\n            dim_out=self.hidden_size,\n            axis=2,\n        )\n\n        brew.sum(model, [gates_t, input_t], gates_t)\n        if self.activation == 'tanh':\n            hidden_t = model.net.Tanh(gates_t, 'hidden_t')\n        elif self.activation == 'relu':\n            hidden_t = model.net.Relu(gates_t, 'hidden_t')\n        else:\n            raise RuntimeError(\n                'BasicRNNCell with unknown activation function (%s)'\n                % self.activation)\n\n        if seq_lengths is not None:\n            # TODO If this codepath becomes popular, it may be worth\n            # taking a look at optimizing it - for now a simple\n            # implementation is used to round out compatibility with\n            # ONNX.\n            timestep = model.net.CopyFromCPUInput(\n                timestep, 'timestep_gpu')\n            valid_b = model.net.GT(\n                [seq_lengths, timestep], 'valid_b', broadcast=1)\n            invalid_b = model.net.LE(\n                [seq_lengths, timestep], 'invalid_b', broadcast=1)\n            valid = model.net.Cast(valid_b, 'valid', to='float')\n            invalid = model.net.Cast(invalid_b, 'invalid', to='float')\n\n            hidden_valid = model.net.Mul(\n                [hidden_t, valid],\n                'hidden_valid',\n                broadcast=1,\n                axis=1,\n            )\n            if self.drop_states:\n                hidden_t = hidden_valid\n            else:\n                hidden_invalid = model.net.Mul(\n                    [hidden_t_prev, invalid],\n                    'hidden_invalid',\n                    broadcast=1, axis=1)\n                hidden_t = model.net.Add(\n                    [hidden_valid, hidden_invalid], hidden_t)\n        return (hidden_t,)\n\n    def prepare_input(self, model, input_blob):\n        return brew.fc(\n            model,\n            input_blob,\n            self.scope('i2h'),\n            dim_in=self.input_size,\n            dim_out=self.hidden_size,\n            axis=2,\n        )\n\n    def get_state_names(self):\n        return (self.scope('hidden_t'),)\n\n    def get_output_dim(self):\n        return self.hidden_size\n\n\nclass LSTMCell(RNNCell):\n\n    def __init__(\n        self,\n        input_size,\n        hidden_size,\n        forget_bias,\n        memory_optimization,\n        drop_states=False,\n        initializer=None,\n        **kwargs\n    ):\n        super(LSTMCell, self).__init__(initializer=initializer, **kwargs)\n        self.initializer = initializer or LSTMInitializer(\n            hidden_size=hidden_size)\n\n        self.input_size = input_size\n        self.hidden_size = hidden_size\n        self.forget_bias = float(forget_bias)\n        self.memory_optimization = memory_optimization\n        self.drop_states = drop_states\n        self.gates_size = 4 * self.hidden_size\n\n    def apply_override(\n        self,\n        model,\n        input_t,\n        seq_lengths,\n        states,\n        timestep,\n        extra_inputs=None,\n    ):\n        hidden_t_prev, cell_t_prev = states\n\n        fc_input = hidden_t_prev\n        fc_input_dim = self.hidden_size\n\n        if extra_inputs is not None:\n            extra_input_blobs, extra_input_sizes = zip(*extra_inputs)\n            fc_input = brew.concat(\n                model,\n                [hidden_t_prev] + list(extra_input_blobs),\n                'gates_concatenated_input_t',\n                axis=2,\n            )\n            fc_input_dim += sum(extra_input_sizes)\n\n        gates_t = brew.fc(\n            model,\n            fc_input,\n            'gates_t',\n            dim_in=fc_input_dim,\n            dim_out=self.gates_size,\n            axis=2,\n        )\n        brew.sum(model, [gates_t, input_t], gates_t)\n\n        if seq_lengths is not None:\n            inputs = [hidden_t_prev, cell_t_prev, gates_t, seq_lengths, timestep]\n        else:\n            inputs = [hidden_t_prev, cell_t_prev, gates_t, timestep]\n\n        hidden_t, cell_t = model.net.LSTMUnit(\n            inputs,\n            ['hidden_state', 'cell_state'],\n            forget_bias=self.forget_bias,\n            drop_states=self.drop_states,\n            sequence_lengths=(seq_lengths is not None),\n        )\n        model.net.AddExternalOutputs(hidden_t, cell_t)\n        if self.memory_optimization:\n            self.recompute_blobs = [gates_t]\n\n        return hidden_t, cell_t\n\n    def get_input_params(self):\n        return {\n            'weights': self.scope('i2h') + '_w',\n            'biases': self.scope('i2h') + '_b',\n        }\n\n    def get_recurrent_params(self):\n        return {\n            'weights': self.scope('gates_t') + '_w',\n            'biases': self.scope('gates_t') + '_b',\n        }\n\n    def prepare_input(self, model, input_blob):\n        return brew.fc(\n            model,\n            input_blob,\n            self.scope('i2h'),\n            dim_in=self.input_size,\n            dim_out=self.gates_size,\n            axis=2,\n        )\n\n    def get_state_names_override(self):\n        return ['hidden_t', 'cell_t']\n\n    def get_output_dim(self):\n        return self.hidden_size\n\n\nclass LayerNormLSTMCell(RNNCell):\n\n    def __init__(\n        self,\n        input_size,\n        hidden_size,\n        forget_bias,\n        memory_optimization,\n        drop_states=False,\n        initializer=None,\n        **kwargs\n    ):\n        super(LayerNormLSTMCell, self).__init__(\n            initializer=initializer, **kwargs\n        )\n        self.initializer = initializer or LSTMInitializer(\n            hidden_size=hidden_size\n        )\n\n        self.input_size = input_size\n        self.hidden_size = hidden_size\n        self.forget_bias = float(forget_bias)\n        self.memory_optimization = memory_optimization\n        self.drop_states = drop_states\n        self.gates_size = 4 * self.hidden_size\n\n    def _apply(\n        self,\n        model,\n        input_t,\n        seq_lengths,\n        states,\n        timestep,\n        extra_inputs=None,\n    ):\n        hidden_t_prev, cell_t_prev = states\n\n        fc_input = hidden_t_prev\n        fc_input_dim = self.hidden_size\n\n        if extra_inputs is not None:\n            extra_input_blobs, extra_input_sizes = zip(*extra_inputs)\n            fc_input = brew.concat(\n                model,\n                [hidden_t_prev] + list(extra_input_blobs),\n                self.scope('gates_concatenated_input_t'),\n                axis=2,\n            )\n            fc_input_dim += sum(extra_input_sizes)\n\n        gates_t = brew.fc(\n            model,\n            fc_input,\n            self.scope('gates_t'),\n            dim_in=fc_input_dim,\n            dim_out=self.gates_size,\n            axis=2,\n        )\n        brew.sum(model, [gates_t, input_t], gates_t)\n\n        # brew.layer_norm call is only difference from LSTMCell\n        gates_t, _, _ = brew.layer_norm(\n            model,\n            self.scope('gates_t'),\n            self.scope('gates_t_norm'),\n            dim_in=self.gates_size,\n            axis=-1,\n        )\n\n        hidden_t, cell_t = model.net.LSTMUnit(\n            [\n                hidden_t_prev,\n                cell_t_prev,\n                gates_t,\n                seq_lengths,\n                timestep,\n            ],\n            self.get_state_names(),\n            forget_bias=self.forget_bias,\n            drop_states=self.drop_states,\n        )\n        model.net.AddExternalOutputs(hidden_t, cell_t)\n        if self.memory_optimization:\n            self.recompute_blobs = [gates_t]\n\n        return hidden_t, cell_t\n\n    def get_input_params(self):\n        return {\n            'weights': self.scope('i2h') + '_w',\n            'biases': self.scope('i2h') + '_b',\n        }\n\n    def prepare_input(self, model, input_blob):\n        return brew.fc(\n            model,\n            input_blob,\n            self.scope('i2h'),\n            dim_in=self.input_size,\n            dim_out=self.gates_size,\n            axis=2,\n        )\n\n    def get_state_names(self):\n        return (self.scope('hidden_t'), self.scope('cell_t'))\n\n\nclass MILSTMCell(LSTMCell):\n\n    def _apply(\n        self,\n        model,\n        input_t,\n        seq_lengths,\n        states,\n        timestep,\n        extra_inputs=None,\n    ):\n        hidden_t_prev, cell_t_prev = states\n\n        fc_input = hidden_t_prev\n        fc_input_dim = self.hidden_size\n\n        if extra_inputs is not None:\n            extra_input_blobs, extra_input_sizes = zip(*extra_inputs)\n            fc_input = brew.concat(\n                model,\n                [hidden_t_prev] + list(extra_input_blobs),\n                self.scope('gates_concatenated_input_t'),\n                axis=2,\n            )\n            fc_input_dim += sum(extra_input_sizes)\n\n        prev_t = brew.fc(\n            model,\n            fc_input,\n            self.scope('prev_t'),\n            dim_in=fc_input_dim,\n            dim_out=self.gates_size,\n            axis=2,\n        )\n\n        # defining initializers for MI parameters\n        alpha = model.create_param(\n            self.scope('alpha'),\n            shape=[self.gates_size],\n            initializer=Initializer('ConstantFill', value=1.0),\n        )\n        beta_h = model.create_param(\n            self.scope('beta1'),\n            shape=[self.gates_size],\n            initializer=Initializer('ConstantFill', value=1.0),\n        )\n        beta_i = model.create_param(\n            self.scope('beta2'),\n            shape=[self.gates_size],\n            initializer=Initializer('ConstantFill', value=1.0),\n        )\n        b = model.create_param(\n            self.scope('b'),\n            shape=[self.gates_size],\n            initializer=Initializer('ConstantFill', value=0.0),\n        )\n\n        # alpha * input_t + beta_h\n        # Shape: [1, batch_size, 4 * hidden_size]\n        alpha_by_input_t_plus_beta_h = model.net.ElementwiseLinear(\n            [input_t, alpha, beta_h],\n            self.scope('alpha_by_input_t_plus_beta_h'),\n            axis=2,\n        )\n        # (alpha * input_t + beta_h) * prev_t =\n        # alpha * input_t * prev_t + beta_h * prev_t\n        # Shape: [1, batch_size, 4 * hidden_size]\n        alpha_by_input_t_plus_beta_h_by_prev_t = model.net.Mul(\n            [alpha_by_input_t_plus_beta_h, prev_t],\n            self.scope('alpha_by_input_t_plus_beta_h_by_prev_t')\n        )\n        # beta_i * input_t + b\n        # Shape: [1, batch_size, 4 * hidden_size]\n        beta_i_by_input_t_plus_b = model.net.ElementwiseLinear(\n            [input_t, beta_i, b],\n            self.scope('beta_i_by_input_t_plus_b'),\n            axis=2,\n        )\n        # alpha * input_t * prev_t + beta_h * prev_t + beta_i * input_t + b\n        # Shape: [1, batch_size, 4 * hidden_size]\n        gates_t = brew.sum(\n            model,\n            [alpha_by_input_t_plus_beta_h_by_prev_t, beta_i_by_input_t_plus_b],\n            self.scope('gates_t')\n        )\n        hidden_t, cell_t = model.net.LSTMUnit(\n            [hidden_t_prev, cell_t_prev, gates_t, seq_lengths, timestep],\n            [self.scope('hidden_t_intermediate'), self.scope('cell_t')],\n            forget_bias=self.forget_bias,\n            drop_states=self.drop_states,\n        )\n        model.net.AddExternalOutputs(\n            cell_t,\n            hidden_t,\n        )\n        if self.memory_optimization:\n            self.recompute_blobs = [gates_t]\n        return hidden_t, cell_t\n\n\nclass LayerNormMILSTMCell(LSTMCell):\n\n    def _apply(\n        self,\n        model,\n        input_t,\n        seq_lengths,\n        states,\n        timestep,\n        extra_inputs=None,\n    ):\n        hidden_t_prev, cell_t_prev = states\n\n        fc_input = hidden_t_prev\n        fc_input_dim = self.hidden_size\n\n        if extra_inputs is not None:\n            extra_input_blobs, extra_input_sizes = zip(*extra_inputs)\n            fc_input = brew.concat(\n                model,\n                [hidden_t_prev] + list(extra_input_blobs),\n                self.scope('gates_concatenated_input_t'),\n                axis=2,\n            )\n            fc_input_dim += sum(extra_input_sizes)\n\n        prev_t = brew.fc(\n            model,\n            fc_input,\n            self.scope('prev_t'),\n            dim_in=fc_input_dim,\n            dim_out=self.gates_size,\n            axis=2,\n        )\n\n        # defining initializers for MI parameters\n        alpha = model.create_param(\n            self.scope('alpha'),\n            shape=[self.gates_size],\n            initializer=Initializer('ConstantFill', value=1.0),\n        )\n        beta_h = model.create_param(\n            self.scope('beta1'),\n            shape=[self.gates_size],\n            initializer=Initializer('ConstantFill', value=1.0),\n        )\n        beta_i = model.create_param(\n            self.scope('beta2'),\n            shape=[self.gates_size],\n            initializer=Initializer('ConstantFill', value=1.0),\n        )\n        b = model.create_param(\n            self.scope('b'),\n            shape=[self.gates_size],\n            initializer=Initializer('ConstantFill', value=0.0),\n        )\n\n        # alpha * input_t + beta_h\n        # Shape: [1, batch_size, 4 * hidden_size]\n        alpha_by_input_t_plus_beta_h = model.net.ElementwiseLinear(\n            [input_t, alpha, beta_h],\n            self.scope('alpha_by_input_t_plus_beta_h'),\n            axis=2,\n        )\n        # (alpha * input_t + beta_h) * prev_t =\n        # alpha * input_t * prev_t + beta_h * prev_t\n        # Shape: [1, batch_size, 4 * hidden_size]\n        alpha_by_input_t_plus_beta_h_by_prev_t = model.net.Mul(\n            [alpha_by_input_t_plus_beta_h, prev_t],\n            self.scope('alpha_by_input_t_plus_beta_h_by_prev_t')\n        )\n        # beta_i * input_t + b\n        # Shape: [1, batch_size, 4 * hidden_size]\n        beta_i_by_input_t_plus_b = model.net.ElementwiseLinear(\n            [input_t, beta_i, b],\n            self.scope('beta_i_by_input_t_plus_b'),\n            axis=2,\n        )\n        # alpha * input_t * prev_t + beta_h * prev_t + beta_i * input_t + b\n        # Shape: [1, batch_size, 4 * hidden_size]\n        gates_t = brew.sum(\n            model,\n            [alpha_by_input_t_plus_beta_h_by_prev_t, beta_i_by_input_t_plus_b],\n            self.scope('gates_t')\n        )\n        # brew.layer_norm call is only difference from MILSTMCell._apply\n        gates_t, _, _ = brew.layer_norm(\n            model,\n            self.scope('gates_t'),\n            self.scope('gates_t_norm'),\n            dim_in=self.gates_size,\n            axis=-1,\n        )\n        hidden_t, cell_t = model.net.LSTMUnit(\n            [hidden_t_prev, cell_t_prev, gates_t, seq_lengths, timestep],\n            [self.scope('hidden_t_intermediate'), self.scope('cell_t')],\n            forget_bias=self.forget_bias,\n            drop_states=self.drop_states,\n        )\n        model.net.AddExternalOutputs(\n            cell_t,\n            hidden_t,\n        )\n        if self.memory_optimization:\n            self.recompute_blobs = [gates_t]\n        return hidden_t, cell_t\n\n\nclass DropoutCell(RNNCell):\n    '''\n    Wraps arbitrary RNNCell, applying dropout to its output (but not to the\n    recurrent connection for the corresponding state).\n    '''\n\n    def __init__(\n        self,\n        internal_cell,\n        dropout_ratio=None,\n        use_cudnn=False,\n        **kwargs\n    ):\n        self.internal_cell = internal_cell\n        self.dropout_ratio = dropout_ratio\n        assert 'is_test' in kwargs, \"Argument 'is_test' is required\"\n        self.is_test = kwargs.pop('is_test')\n        self.use_cudnn = use_cudnn\n        super(DropoutCell, self).__init__(**kwargs)\n\n        self.prepare_input = internal_cell.prepare_input\n        self.get_output_state_index = internal_cell.get_output_state_index\n        self.get_state_names = internal_cell.get_state_names\n        self.get_output_dim = internal_cell.get_output_dim\n\n        self.mask = 0\n\n    def _apply(\n        self,\n        model,\n        input_t,\n        seq_lengths,\n        states,\n        timestep,\n        extra_inputs=None,\n    ):\n        return self.internal_cell._apply(\n            model,\n            input_t,\n            seq_lengths,\n            states,\n            timestep,\n            extra_inputs,\n        )\n\n    def _prepare_output(self, model, states):\n        output = self.internal_cell._prepare_output(\n            model,\n            states,\n        )\n        if self.dropout_ratio is not None:\n            output = self._apply_dropout(model, output)\n        return output\n\n    def _prepare_output_sequence(self, model, state_outputs):\n        output = self.internal_cell._prepare_output_sequence(\n            model,\n            state_outputs,\n        )\n        if self.dropout_ratio is not None:\n            output = self._apply_dropout(model, output)\n        return output\n\n    def _apply_dropout(self, model, output):\n        if self.dropout_ratio and not self.forward_only:\n            with core.NameScope(self.name or ''):\n                output = brew.dropout(\n                    model,\n                    output,\n                    str(output) + '_with_dropout_mask{}'.format(self.mask),\n                    ratio=float(self.dropout_ratio),\n                    is_test=self.is_test,\n                    use_cudnn=self.use_cudnn,\n                )\n                self.mask += 1\n        return output\n\n\nclass MultiRNNCellInitializer(object):\n    def __init__(self, cells):\n        self.cells = cells\n\n    def create_states(self, model):\n        states = []\n        for i, cell in enumerate(self.cells):\n            if cell.initializer is None:\n                raise Exception(\"Either initial states \"\n                                \"or initializer have to be set\")\n\n            with core.NameScope(\"layer_{}\".format(i)),\\\n                    core.NameScope(cell.name):\n                states.extend(cell.initializer.create_states(model))\n        return states\n\n\nclass MultiRNNCell(RNNCell):\n    '''\n    Multilayer RNN via the composition of RNNCell instance.\n\n    It is the resposibility of calling code to ensure the compatibility\n    of the successive layers in terms of input/output dimensiality, etc.,\n    and to ensure that their blobs do not have name conflicts, typically by\n    creating the cells with names that specify layer number.\n\n    Assumes first state (recurrent output) for each layer should be the input\n    to the next layer.\n    '''\n\n    def __init__(self, cells, residual_output_layers=None, **kwargs):\n        '''\n        cells: list of RNNCell instances, from input to output side.\n\n        name: string designating network component (for scoping)\n\n        residual_output_layers: list of indices of layers whose input will\n        be added elementwise to their output elementwise. (It is the\n        responsibility of the client code to ensure shape compatibility.)\n        Note that layer 0 (zero) cannot have residual output because of the\n        timing of prepare_input().\n\n        forward_only: used to construct inference-only network.\n        '''\n        super(MultiRNNCell, self).__init__(**kwargs)\n        self.cells = cells\n\n        if residual_output_layers is None:\n            self.residual_output_layers = []\n        else:\n            self.residual_output_layers = residual_output_layers\n\n        output_index_per_layer = []\n        base_index = 0\n        for cell in self.cells:\n            output_index_per_layer.append(\n                base_index + cell.get_output_state_index(),\n            )\n            base_index += len(cell.get_state_names())\n\n        self.output_connected_layers = []\n        self.output_indices = []\n        for i in range(len(self.cells) - 1):\n            if (i + 1) in self.residual_output_layers:\n                self.output_connected_layers.append(i)\n                self.output_indices.append(output_index_per_layer[i])\n            else:\n                self.output_connected_layers = []\n                self.output_indices = []\n        self.output_connected_layers.append(len(self.cells) - 1)\n        self.output_indices.append(output_index_per_layer[-1])\n\n        self.state_names = []\n        for i, cell in enumerate(self.cells):\n            self.state_names.extend(\n                map(self.layer_scoper(i), cell.get_state_names())\n            )\n\n        self.initializer = MultiRNNCellInitializer(cells)\n\n    def layer_scoper(self, layer_id):\n        def helper(name):\n            return \"{}/layer_{}/{}\".format(self.name, layer_id, name)\n        return helper\n\n    def prepare_input(self, model, input_blob):\n        input_blob = _RectifyName(input_blob)\n        with core.NameScope(self.name or ''):\n            return self.cells[0].prepare_input(model, input_blob)\n\n    def _apply(\n        self,\n        model,\n        input_t,\n        seq_lengths,\n        states,\n        timestep,\n        extra_inputs=None,\n    ):\n        '''\n        Because below we will do scoping across layers, we need\n        to make sure that string blob names are convereted to BlobReference\n        objects.\n        '''\n\n        input_t, seq_lengths, states, timestep, extra_inputs = \\\n            self._rectify_apply_inputs(\n                input_t, seq_lengths, states, timestep, extra_inputs)\n\n        states_per_layer = [len(cell.get_state_names()) for cell in self.cells]\n        assert len(states) == sum(states_per_layer)\n\n        next_states = []\n        states_index = 0\n\n        layer_input = input_t\n        for i, layer_cell in enumerate(self.cells):\n            # # If cells don't have different names we still\n            # take care of scoping\n            with core.NameScope(self.name), core.NameScope(\"layer_{}\".format(i)):\n                num_states = states_per_layer[i]\n                layer_states = states[states_index:(states_index + num_states)]\n                states_index += num_states\n\n                if i > 0:\n                    prepared_input = layer_cell.prepare_input(\n                        model, layer_input)\n                else:\n                    prepared_input = layer_input\n\n                layer_next_states = layer_cell._apply(\n                    model,\n                    prepared_input,\n                    seq_lengths,\n                    layer_states,\n                    timestep,\n                    extra_inputs=(None if i > 0 else extra_inputs),\n                )\n                # Since we're using here non-public method _apply,\n                # instead of apply, we have to manually extract output\n                # from states\n                if i != len(self.cells) - 1:\n                    layer_output = layer_cell._prepare_output(\n                        model,\n                        layer_next_states,\n                    )\n                    if i > 0 and i in self.residual_output_layers:\n                        layer_input = brew.sum(\n                            model,\n                            [layer_output, layer_input],\n                            self.scope('residual_output_{}'.format(i)),\n                        )\n                    else:\n                        layer_input = layer_output\n\n                next_states.extend(layer_next_states)\n        return next_states\n\n    def get_state_names(self):\n        return self.state_names\n\n    def get_output_state_index(self):\n        index = 0\n        for cell in self.cells[:-1]:\n            index += len(cell.get_state_names())\n        index += self.cells[-1].get_output_state_index()\n        return index\n\n    def _prepare_output(self, model, states):\n        connected_outputs = []\n        state_index = 0\n        for i, cell in enumerate(self.cells):\n            num_states = len(cell.get_state_names())\n            if i in self.output_connected_layers:\n                layer_states = states[state_index:state_index + num_states]\n                layer_output = cell._prepare_output(\n                    model,\n                    layer_states\n                )\n                connected_outputs.append(layer_output)\n            state_index += num_states\n        if len(connected_outputs) > 1:\n            output = brew.sum(\n                model,\n                connected_outputs,\n                self.scope('residual_output'),\n            )\n        else:\n            output = connected_outputs[0]\n        return output\n\n    def _prepare_output_sequence(self, model, states):\n        connected_outputs = []\n        state_index = 0\n        for i, cell in enumerate(self.cells):\n            num_states = 2 * len(cell.get_state_names())\n            if i in self.output_connected_layers:\n                layer_states = states[state_index:state_index + num_states]\n                layer_output = cell._prepare_output_sequence(\n                    model,\n                    layer_states\n                )\n                connected_outputs.append(layer_output)\n            state_index += num_states\n        if len(connected_outputs) > 1:\n            output = brew.sum(\n                model,\n                connected_outputs,\n                self.scope('residual_output_sequence'),\n            )\n        else:\n            output = connected_outputs[0]\n        return output\n\n\nclass AttentionCell(RNNCell):\n\n    def __init__(\n        self,\n        encoder_output_dim,\n        encoder_outputs,\n        encoder_lengths,\n        decoder_cell,\n        decoder_state_dim,\n        attention_type,\n        weighted_encoder_outputs,\n        attention_memory_optimization,\n        **kwargs\n    ):\n        super(AttentionCell, self).__init__(**kwargs)\n        self.encoder_output_dim = encoder_output_dim\n        self.encoder_outputs = encoder_outputs\n        self.encoder_lengths = encoder_lengths\n        self.decoder_cell = decoder_cell\n        self.decoder_state_dim = decoder_state_dim\n        self.weighted_encoder_outputs = weighted_encoder_outputs\n        self.encoder_outputs_transposed = None\n        assert attention_type in [\n            AttentionType.Regular,\n            AttentionType.Recurrent,\n            AttentionType.Dot,\n            AttentionType.SoftCoverage,\n        ]\n        self.attention_type = attention_type\n        self.attention_memory_optimization = attention_memory_optimization\n\n    def _apply(\n        self,\n        model,\n        input_t,\n        seq_lengths,\n        states,\n        timestep,\n        extra_inputs=None,\n    ):\n        if self.attention_type == AttentionType.SoftCoverage:\n            decoder_prev_states = states[:-2]\n            attention_weighted_encoder_context_t_prev = states[-2]\n            coverage_t_prev = states[-1]\n        else:\n            decoder_prev_states = states[:-1]\n            attention_weighted_encoder_context_t_prev = states[-1]\n\n        assert extra_inputs is None\n\n        decoder_states = self.decoder_cell._apply(\n            model,\n            input_t,\n            seq_lengths,\n            decoder_prev_states,\n            timestep,\n            extra_inputs=[(\n                attention_weighted_encoder_context_t_prev,\n                self.encoder_output_dim,\n            )],\n        )\n\n        self.hidden_t_intermediate = self.decoder_cell._prepare_output(\n            model,\n            decoder_states,\n        )\n\n        if self.attention_type == AttentionType.Recurrent:\n            (\n                attention_weighted_encoder_context_t,\n                self.attention_weights_3d,\n                attention_blobs,\n            ) = apply_recurrent_attention(\n                model=model,\n                encoder_output_dim=self.encoder_output_dim,\n                encoder_outputs_transposed=self.encoder_outputs_transposed,\n                weighted_encoder_outputs=self.weighted_encoder_outputs,\n                decoder_hidden_state_t=self.hidden_t_intermediate,\n                decoder_hidden_state_dim=self.decoder_state_dim,\n                scope=self.name,\n                attention_weighted_encoder_context_t_prev=(\n                    attention_weighted_encoder_context_t_prev\n                ),\n                encoder_lengths=self.encoder_lengths,\n            )\n        elif self.attention_type == AttentionType.Regular:\n            (\n                attention_weighted_encoder_context_t,\n                self.attention_weights_3d,\n                attention_blobs,\n            ) = apply_regular_attention(\n                model=model,\n                encoder_output_dim=self.encoder_output_dim,\n                encoder_outputs_transposed=self.encoder_outputs_transposed,\n                weighted_encoder_outputs=self.weighted_encoder_outputs,\n                decoder_hidden_state_t=self.hidden_t_intermediate,\n                decoder_hidden_state_dim=self.decoder_state_dim,\n                scope=self.name,\n                encoder_lengths=self.encoder_lengths,\n            )\n        elif self.attention_type == AttentionType.Dot:\n            (\n                attention_weighted_encoder_context_t,\n                self.attention_weights_3d,\n                attention_blobs,\n            ) = apply_dot_attention(\n                model=model,\n                encoder_output_dim=self.encoder_output_dim,\n                encoder_outputs_transposed=self.encoder_outputs_transposed,\n                decoder_hidden_state_t=self.hidden_t_intermediate,\n                decoder_hidden_state_dim=self.decoder_state_dim,\n                scope=self.name,\n                encoder_lengths=self.encoder_lengths,\n            )\n        elif self.attention_type == AttentionType.SoftCoverage:\n            (\n                attention_weighted_encoder_context_t,\n                self.attention_weights_3d,\n                attention_blobs,\n                coverage_t,\n            ) = apply_soft_coverage_attention(\n                model=model,\n                encoder_output_dim=self.encoder_output_dim,\n                encoder_outputs_transposed=self.encoder_outputs_transposed,\n                weighted_encoder_outputs=self.weighted_encoder_outputs,\n                decoder_hidden_state_t=self.hidden_t_intermediate,\n                decoder_hidden_state_dim=self.decoder_state_dim,\n                scope=self.name,\n                encoder_lengths=self.encoder_lengths,\n                coverage_t_prev=coverage_t_prev,\n                coverage_weights=self.coverage_weights,\n            )\n        else:\n            raise Exception('Attention type {} not implemented'.format(\n                self.attention_type\n            ))\n\n        if self.attention_memory_optimization:\n            self.recompute_blobs.extend(attention_blobs)\n\n        output = list(decoder_states) + [attention_weighted_encoder_context_t]\n        if self.attention_type == AttentionType.SoftCoverage:\n            output.append(coverage_t)\n\n        output[self.decoder_cell.get_output_state_index()] = model.Copy(\n            output[self.decoder_cell.get_output_state_index()],\n            self.scope('hidden_t_external'),\n        )\n        model.net.AddExternalOutputs(*output)\n\n        return output\n\n    def get_attention_weights(self):\n        # [batch_size, encoder_length, 1]\n        return self.attention_weights_3d\n\n    def prepare_input(self, model, input_blob):\n        if self.encoder_outputs_transposed is None:\n            self.encoder_outputs_transposed = brew.transpose(\n                model,\n                self.encoder_outputs,\n                self.scope('encoder_outputs_transposed'),\n                axes=[1, 2, 0],\n            )\n        if (\n            self.weighted_encoder_outputs is None and\n            self.attention_type != AttentionType.Dot\n        ):\n            self.weighted_encoder_outputs = brew.fc(\n                model,\n                self.encoder_outputs,\n                self.scope('weighted_encoder_outputs'),\n                dim_in=self.encoder_output_dim,\n                dim_out=self.encoder_output_dim,\n                axis=2,\n            )\n\n        return self.decoder_cell.prepare_input(model, input_blob)\n\n    def build_initial_coverage(self, model):\n        \"\"\"\n        initial_coverage is always zeros of shape [encoder_length],\n        which shape must be determined programmatically dureing network\n        computation.\n\n        This method also sets self.coverage_weights, a separate transform\n        of encoder_outputs which is used to determine coverage contribution\n        tp attention.\n        \"\"\"\n        assert self.attention_type == AttentionType.SoftCoverage\n\n        # [encoder_length, batch_size, encoder_output_dim]\n        self.coverage_weights = brew.fc(\n            model,\n            self.encoder_outputs,\n            self.scope('coverage_weights'),\n            dim_in=self.encoder_output_dim,\n            dim_out=self.encoder_output_dim,\n            axis=2,\n        )\n\n        encoder_length = model.net.Slice(\n            model.net.Shape(self.encoder_outputs),\n            starts=[0],\n            ends=[1],\n        )\n        if (\n            scope.CurrentDeviceScope() is not None and\n            scope.CurrentDeviceScope().device_type == caffe2_pb2.CUDA\n        ):\n            encoder_length = model.net.CopyGPUToCPU(\n                encoder_length,\n                'encoder_length_cpu',\n            )\n        # total attention weight applied across decoding steps_per_checkpoint\n        # shape: [encoder_length]\n        initial_coverage = model.net.ConstantFill(\n            encoder_length,\n            self.scope('initial_coverage'),\n            value=0.0,\n            input_as_shape=1,\n        )\n        return initial_coverage\n\n    def get_state_names(self):\n        state_names = list(self.decoder_cell.get_state_names())\n        state_names[self.get_output_state_index()] = self.scope(\n            'hidden_t_external',\n        )\n        state_names.append(self.scope('attention_weighted_encoder_context_t'))\n        if self.attention_type == AttentionType.SoftCoverage:\n            state_names.append(self.scope('coverage_t'))\n        return state_names\n\n    def get_output_dim(self):\n        return self.decoder_state_dim + self.encoder_output_dim\n\n    def get_output_state_index(self):\n        return self.decoder_cell.get_output_state_index()\n\n    def _prepare_output(self, model, states):\n        if self.attention_type == AttentionType.SoftCoverage:\n            attention_context = states[-2]\n        else:\n            attention_context = states[-1]\n\n        with core.NameScope(self.name or ''):\n            output = brew.concat(\n                model,\n                [self.hidden_t_intermediate, attention_context],\n                'states_and_context_combination',\n                axis=2,\n            )\n\n        return output\n\n    def _prepare_output_sequence(self, model, state_outputs):\n        if self.attention_type == AttentionType.SoftCoverage:\n            decoder_state_outputs = state_outputs[:-4]\n        else:\n            decoder_state_outputs = state_outputs[:-2]\n\n        decoder_output = self.decoder_cell._prepare_output_sequence(\n            model,\n            decoder_state_outputs,\n        )\n\n        if self.attention_type == AttentionType.SoftCoverage:\n            attention_context_index = 2 * (len(self.get_state_names()) - 2)\n        else:\n            attention_context_index = 2 * (len(self.get_state_names()) - 1)\n\n        with core.NameScope(self.name or ''):\n            output = brew.concat(\n                model,\n                [\n                    decoder_output,\n                    state_outputs[attention_context_index],\n                ],\n                'states_and_context_combination',\n                axis=2,\n            )\n        return output\n\n\nclass LSTMWithAttentionCell(AttentionCell):\n\n    def __init__(\n        self,\n        encoder_output_dim,\n        encoder_outputs,\n        encoder_lengths,\n        decoder_input_dim,\n        decoder_state_dim,\n        name,\n        attention_type,\n        weighted_encoder_outputs,\n        forget_bias,\n        lstm_memory_optimization,\n        attention_memory_optimization,\n        forward_only=False,\n    ):\n        decoder_cell = LSTMCell(\n            input_size=decoder_input_dim,\n            hidden_size=decoder_state_dim,\n            forget_bias=forget_bias,\n            memory_optimization=lstm_memory_optimization,\n            name='{}/decoder'.format(name),\n            forward_only=False,\n            drop_states=False,\n        )\n        super(LSTMWithAttentionCell, self).__init__(\n            encoder_output_dim=encoder_output_dim,\n            encoder_outputs=encoder_outputs,\n            encoder_lengths=encoder_lengths,\n            decoder_cell=decoder_cell,\n            decoder_state_dim=decoder_state_dim,\n            name=name,\n            attention_type=attention_type,\n            weighted_encoder_outputs=weighted_encoder_outputs,\n            attention_memory_optimization=attention_memory_optimization,\n            forward_only=forward_only,\n        )\n\n\nclass MILSTMWithAttentionCell(AttentionCell):\n\n    def __init__(\n        self,\n        encoder_output_dim,\n        encoder_outputs,\n        decoder_input_dim,\n        decoder_state_dim,\n        name,\n        attention_type,\n        weighted_encoder_outputs,\n        forget_bias,\n        lstm_memory_optimization,\n        attention_memory_optimization,\n        forward_only=False,\n    ):\n        decoder_cell = MILSTMCell(\n            input_size=decoder_input_dim,\n            hidden_size=decoder_state_dim,\n            forget_bias=forget_bias,\n            memory_optimization=lstm_memory_optimization,\n            name='{}/decoder'.format(name),\n            forward_only=False,\n            drop_states=False,\n        )\n        super(MILSTMWithAttentionCell, self).__init__(\n            encoder_output_dim=encoder_output_dim,\n            encoder_outputs=encoder_outputs,\n            decoder_cell=decoder_cell,\n            decoder_state_dim=decoder_state_dim,\n            name=name,\n            attention_type=attention_type,\n            weighted_encoder_outputs=weighted_encoder_outputs,\n            attention_memory_optimization=attention_memory_optimization,\n            forward_only=forward_only,\n        )\n\n\ndef _LSTM(\n    cell_class,\n    model,\n    input_blob,\n    seq_lengths,\n    initial_states,\n    dim_in,\n    dim_out,\n    scope=None,\n    outputs_with_grads=(0,),\n    return_params=False,\n    memory_optimization=False,\n    forget_bias=0.0,\n    forward_only=False,\n    drop_states=False,\n    return_last_layer_only=True,\n    static_rnn_unroll_size=None,\n    **cell_kwargs\n):\n    '''\n    Adds a standard LSTM recurrent network operator to a model.\n\n    cell_class: LSTMCell or compatible subclass\n\n    model: ModelHelper object new operators would be added to\n\n    input_blob: the input sequence in a format T x N x D\n            where T is sequence size, N - batch size and D - input dimension\n\n    seq_lengths: blob containing sequence lengths which would be passed to\n            LSTMUnit operator\n\n    initial_states: a list of (2 * num_layers) blobs representing the initial\n            hidden and cell states of each layer. If this argument is None,\n            these states will be added to the model as network parameters.\n\n    dim_in: input dimension\n\n    dim_out: number of units per LSTM layer\n            (use int for single-layer LSTM, list of ints for multi-layer)\n\n    outputs_with_grads : position indices of output blobs for LAST LAYER which\n            will receive external error gradient during backpropagation.\n            These outputs are: (h_all, h_last, c_all, c_last)\n\n    return_params: if True, will return a dictionary of parameters of the LSTM\n\n    memory_optimization: if enabled, the LSTM step is recomputed on backward\n            step so that we don't need to store forward activations for each\n            timestep. Saves memory with cost of computation.\n\n    forget_bias: forget gate bias (default 0.0)\n\n    forward_only: whether to create a backward pass\n\n    drop_states: drop invalid states, passed through to LSTMUnit operator\n\n    return_last_layer_only: only return outputs from final layer\n            (so that length of results does depend on number of layers)\n\n    static_rnn_unroll_size: if not None, we will use static RNN which is\n    unrolled into Caffe2 graph. The size of the unroll is the value of\n    this parameter.\n    '''\n    if type(dim_out) is not list and type(dim_out) is not tuple:\n        dim_out = [dim_out]\n    num_layers = len(dim_out)\n\n    cells = []\n    for i in range(num_layers):\n        cell = cell_class(\n            input_size=(dim_in if i == 0 else dim_out[i - 1]),\n            hidden_size=dim_out[i],\n            forget_bias=forget_bias,\n            memory_optimization=memory_optimization,\n            name=scope if num_layers == 1 else None,\n            forward_only=forward_only,\n            drop_states=drop_states,\n            **cell_kwargs\n        )\n        cells.append(cell)\n\n    cell = MultiRNNCell(\n        cells,\n        name=scope,\n        forward_only=forward_only,\n    ) if num_layers > 1 else cells[0]\n\n    cell = (\n        cell if static_rnn_unroll_size is None\n        else UnrolledCell(cell, static_rnn_unroll_size))\n\n    # outputs_with_grads argument indexes into final layer\n    outputs_with_grads = [4 * (num_layers - 1) + i for i in outputs_with_grads]\n    _, result = cell.apply_over_sequence(\n        model=model,\n        inputs=input_blob,\n        seq_lengths=seq_lengths,\n        initial_states=initial_states,\n        outputs_with_grads=outputs_with_grads,\n    )\n\n    if return_last_layer_only:\n        result = result[4 * (num_layers - 1):]\n    if return_params:\n        result = list(result) + [{\n            'input': cell.get_input_params(),\n            'recurrent': cell.get_recurrent_params(),\n        }]\n    return tuple(result)\n\n\nLSTM = functools.partial(_LSTM, LSTMCell)\nBasicRNN = functools.partial(_LSTM, BasicRNNCell)\nMILSTM = functools.partial(_LSTM, MILSTMCell)\nLayerNormLSTM = functools.partial(_LSTM, LayerNormLSTMCell)\nLayerNormMILSTM = functools.partial(_LSTM, LayerNormMILSTMCell)\n\n\nclass UnrolledCell(RNNCell):\n    def __init__(self, cell, T):\n        self.T = T\n        self.cell = cell\n\n    def apply_over_sequence(\n        self,\n        model,\n        inputs,\n        seq_lengths,\n        initial_states,\n        outputs_with_grads=None,\n    ):\n        inputs = self.cell.prepare_input(model, inputs)\n\n        # Now they are blob references - outputs of splitting the input sequence\n        split_inputs = model.net.Split(\n            inputs,\n            [str(inputs) + \"_timestep_{}\".format(i)\n             for i in range(self.T)],\n            axis=0)\n        if self.T == 1:\n            split_inputs = [split_inputs]\n\n        states = initial_states\n        all_states = []\n        for t in range(0, self.T):\n            scope_name = \"timestep_{}\".format(t)\n            # Parameters of all timesteps are shared\n            with ParameterSharing({scope_name: ''}),\\\n                    scope.NameScope(scope_name):\n                timestep = model.param_init_net.ConstantFill(\n                    [], \"timestep\", value=t, shape=[1],\n                    dtype=core.DataType.INT32,\n                    device_option=core.DeviceOption(caffe2_pb2.CPU))\n                states = self.cell._apply(\n                    model=model,\n                    input_t=split_inputs[t],\n                    seq_lengths=seq_lengths,\n                    states=states,\n                    timestep=timestep,\n                )\n            all_states.append(states)\n\n        all_states = zip(*all_states)\n        all_states = [\n            model.net.Concat(\n                list(full_output),\n                [\n                    str(full_output[0])[len(\"timestep_0/\"):] + \"_concat\",\n                    str(full_output[0])[len(\"timestep_0/\"):] + \"_concat_info\"\n\n                ],\n                axis=0)[0]\n            for full_output in all_states\n        ]\n        outputs = tuple(\n            six.next(it) for it in\n            itertools.cycle([iter(all_states), iter(states)])\n        )\n        outputs_without_grad = set(range(len(outputs))) - set(\n            outputs_with_grads)\n        for i in outputs_without_grad:\n            model.net.ZeroGradient(outputs[i], [])\n        logging.debug(\"Added 0 gradients for blobs:\",\n                      [outputs[i] for i in outputs_without_grad])\n\n        final_output = self.cell._prepare_output_sequence(model, outputs)\n\n        return final_output, outputs\n\n\ndef GetLSTMParamNames():\n    weight_params = [\"input_gate_w\", \"forget_gate_w\", \"output_gate_w\", \"cell_w\"]\n    bias_params = [\"input_gate_b\", \"forget_gate_b\", \"output_gate_b\", \"cell_b\"]\n    return {'weights': weight_params, 'biases': bias_params}\n\n\ndef InitFromLSTMParams(lstm_pblobs, param_values):\n    '''\n    Set the parameters of LSTM based on predefined values\n    '''\n    weight_params = GetLSTMParamNames()['weights']\n    bias_params = GetLSTMParamNames()['biases']\n    for input_type in viewkeys(param_values):\n        weight_values = [\n            param_values[input_type][w].flatten()\n            for w in weight_params\n        ]\n        wmat = np.array([])\n        for w in weight_values:\n            wmat = np.append(wmat, w)\n        bias_values = [\n            param_values[input_type][b].flatten()\n            for b in bias_params\n        ]\n        bm = np.array([])\n        for b in bias_values:\n            bm = np.append(bm, b)\n\n        weights_blob = lstm_pblobs[input_type]['weights']\n        bias_blob = lstm_pblobs[input_type]['biases']\n        cur_weight = workspace.FetchBlob(weights_blob)\n        cur_biases = workspace.FetchBlob(bias_blob)\n\n        workspace.FeedBlob(\n            weights_blob,\n            wmat.reshape(cur_weight.shape).astype(np.float32))\n        workspace.FeedBlob(\n            bias_blob,\n            bm.reshape(cur_biases.shape).astype(np.float32))\n\n\ndef cudnn_LSTM(model, input_blob, initial_states, dim_in, dim_out,\n               scope, recurrent_params=None, input_params=None,\n               num_layers=1, return_params=False):\n    '''\n    CuDNN version of LSTM for GPUs.\n    input_blob          Blob containing the input. Will need to be available\n                        when param_init_net is run, because the sequence lengths\n                        and batch sizes will be inferred from the size of this\n                        blob.\n    initial_states      tuple of (hidden_init, cell_init) blobs\n    dim_in              input dimensions\n    dim_out             output/hidden dimension\n    scope               namescope to apply\n    recurrent_params    dict of blobs containing values for recurrent\n                        gate weights, biases (if None, use random init values)\n                        See GetLSTMParamNames() for format.\n    input_params        dict of blobs containing values for input\n                        gate weights, biases (if None, use random init values)\n                        See GetLSTMParamNames() for format.\n    num_layers          number of LSTM layers\n    return_params       if True, returns (param_extract_net, param_mapping)\n                        where param_extract_net is a net that when run, will\n                        populate the blobs specified in param_mapping with the\n                        current gate weights and biases (input/recurrent).\n                        Useful for assigning the values back to non-cuDNN\n                        LSTM.\n    '''\n    with core.NameScope(scope):\n        weight_params = GetLSTMParamNames()['weights']\n        bias_params = GetLSTMParamNames()['biases']\n\n        input_weight_size = dim_out * dim_in\n        upper_layer_input_weight_size = dim_out * dim_out\n        recurrent_weight_size = dim_out * dim_out\n        input_bias_size = dim_out\n        recurrent_bias_size = dim_out\n\n        def init(layer, pname, input_type):\n            input_weight_size_for_layer = input_weight_size if layer == 0 else \\\n                upper_layer_input_weight_size\n            if pname in weight_params:\n                sz = input_weight_size_for_layer if input_type == 'input' \\\n                    else recurrent_weight_size\n            elif pname in bias_params:\n                sz = input_bias_size if input_type == 'input' \\\n                    else recurrent_bias_size\n            else:\n                assert False, \"unknown parameter type {}\".format(pname)\n            return model.param_init_net.UniformFill(\n                [],\n                \"lstm_init_{}_{}_{}\".format(input_type, pname, layer),\n                shape=[sz])\n\n        # Multiply by 4 since we have 4 gates per LSTM unit\n        first_layer_sz = input_weight_size + recurrent_weight_size + \\\n                         input_bias_size + recurrent_bias_size\n        upper_layer_sz = upper_layer_input_weight_size + \\\n                         recurrent_weight_size + input_bias_size + \\\n                         recurrent_bias_size\n        total_sz = 4 * (first_layer_sz + (num_layers - 1) * upper_layer_sz)\n\n        weights = model.create_param(\n            'lstm_weight',\n            shape=[total_sz],\n            initializer=Initializer('UniformFill'),\n            tags=ParameterTags.WEIGHT,\n        )\n\n        lstm_args = {\n            'hidden_size': dim_out,\n            'rnn_mode': 'lstm',\n            'bidirectional': 0,  # TODO\n            'dropout': 1.0,  # TODO\n            'input_mode': 'linear',  # TODO\n            'num_layers': num_layers,\n            'engine': 'CUDNN'\n        }\n\n        param_extract_net = core.Net(\"lstm_param_extractor\")\n        param_extract_net.AddExternalInputs([input_blob, weights])\n        param_extract_mapping = {}\n\n        # Populate the weights-blob from blobs containing parameters for\n        # the individual components of the LSTM, such as forget/input gate\n        # weights and bises. Also, create a special param_extract_net that\n        # can be used to grab those individual params from the black-box\n        # weights blob. These results can be then fed to InitFromLSTMParams()\n        for input_type in ['input', 'recurrent']:\n            param_extract_mapping[input_type] = {}\n            p = recurrent_params if input_type == 'recurrent' else input_params\n            if p is None:\n                p = {}\n            for pname in weight_params + bias_params:\n                for j in range(0, num_layers):\n                    values = p[pname] if pname in p else init(j, pname, input_type)\n                    model.param_init_net.RecurrentParamSet(\n                        [input_blob, weights, values],\n                        weights,\n                        layer=j,\n                        input_type=input_type,\n                        param_type=pname,\n                        **lstm_args\n                    )\n                    if pname not in param_extract_mapping[input_type]:\n                        param_extract_mapping[input_type][pname] = {}\n                    b = param_extract_net.RecurrentParamGet(\n                        [input_blob, weights],\n                        [\"lstm_{}_{}_{}\".format(input_type, pname, j)],\n                        layer=j,\n                        input_type=input_type,\n                        param_type=pname,\n                        **lstm_args\n                    )\n                    param_extract_mapping[input_type][pname][j] = b\n\n        (hidden_input_blob, cell_input_blob) = initial_states\n        output, hidden_output, cell_output, rnn_scratch, dropout_states = \\\n            model.net.Recurrent(\n                [input_blob, hidden_input_blob, cell_input_blob, weights],\n                [\"lstm_output\", \"lstm_hidden_output\", \"lstm_cell_output\",\n                 \"lstm_rnn_scratch\", \"lstm_dropout_states\"],\n                seed=random.randint(0, 100000),  # TODO: dropout seed\n                **lstm_args\n            )\n        model.net.AddExternalOutputs(\n            hidden_output, cell_output, rnn_scratch, dropout_states)\n\n    if return_params:\n        param_extract = param_extract_net, param_extract_mapping\n        return output, hidden_output, cell_output, param_extract\n    else:\n        return output, hidden_output, cell_output\n\n\ndef LSTMWithAttention(\n    model,\n    decoder_inputs,\n    decoder_input_lengths,\n    initial_decoder_hidden_state,\n    initial_decoder_cell_state,\n    initial_attention_weighted_encoder_context,\n    encoder_output_dim,\n    encoder_outputs,\n    encoder_lengths,\n    decoder_input_dim,\n    decoder_state_dim,\n    scope,\n    attention_type=AttentionType.Regular,\n    outputs_with_grads=(0, 4),\n    weighted_encoder_outputs=None,\n    lstm_memory_optimization=False,\n    attention_memory_optimization=False,\n    forget_bias=0.0,\n    forward_only=False,\n):\n    '''\n    Adds a LSTM with attention mechanism to a model.\n\n    The implementation is based on https://arxiv.org/abs/1409.0473, with\n    a small difference in the order\n    how we compute new attention context and new hidden state, similarly to\n    https://arxiv.org/abs/1508.04025.\n\n    The model uses encoder-decoder naming conventions,\n    where the decoder is the sequence the op is iterating over,\n    while computing the attention context over the encoder.\n\n    model: ModelHelper object new operators would be added to\n\n    decoder_inputs: the input sequence in a format T x N x D\n    where T is sequence size, N - batch size and D - input dimension\n\n    decoder_input_lengths: blob containing sequence lengths\n    which would be passed to LSTMUnit operator\n\n    initial_decoder_hidden_state: initial hidden state of LSTM\n\n    initial_decoder_cell_state: initial cell state of LSTM\n\n    initial_attention_weighted_encoder_context: initial attention context\n\n    encoder_output_dim: dimension of encoder outputs\n\n    encoder_outputs: the sequence, on which we compute the attention context\n    at every iteration\n\n    encoder_lengths: a tensor with lengths of each encoder sequence in batch\n    (may be None, meaning all encoder sequences are of same length)\n\n    decoder_input_dim: input dimension (last dimension on decoder_inputs)\n\n    decoder_state_dim: size of hidden states of LSTM\n\n    attention_type: One of: AttentionType.Regular, AttentionType.Recurrent.\n    Determines which type of attention mechanism to use.\n\n    outputs_with_grads : position indices of output blobs which will receive\n    external error gradient during backpropagation\n\n    weighted_encoder_outputs: encoder outputs to be used to compute attention\n    weights. In the basic case it's just linear transformation of\n    encoder outputs (that the default, when weighted_encoder_outputs is None).\n    However, it can be something more complicated - like a separate\n    encoder network (for example, in case of convolutional encoder)\n\n    lstm_memory_optimization: recompute LSTM activations on backward pass, so\n                 we don't need to store their values in forward passes\n\n    attention_memory_optimization: recompute attention for backward pass\n\n    forward_only: whether to create only forward pass\n    '''\n    cell = LSTMWithAttentionCell(\n        encoder_output_dim=encoder_output_dim,\n        encoder_outputs=encoder_outputs,\n        encoder_lengths=encoder_lengths,\n        decoder_input_dim=decoder_input_dim,\n        decoder_state_dim=decoder_state_dim,\n        name=scope,\n        attention_type=attention_type,\n        weighted_encoder_outputs=weighted_encoder_outputs,\n        forget_bias=forget_bias,\n        lstm_memory_optimization=lstm_memory_optimization,\n        attention_memory_optimization=attention_memory_optimization,\n        forward_only=forward_only,\n    )\n    initial_states = [\n        initial_decoder_hidden_state,\n        initial_decoder_cell_state,\n        initial_attention_weighted_encoder_context,\n    ]\n    if attention_type == AttentionType.SoftCoverage:\n        initial_states.append(cell.build_initial_coverage(model))\n    _, result = cell.apply_over_sequence(\n        model=model,\n        inputs=decoder_inputs,\n        seq_lengths=decoder_input_lengths,\n        initial_states=initial_states,\n        outputs_with_grads=outputs_with_grads,\n    )\n    return result\n\n\ndef _layered_LSTM(\n        model, input_blob, seq_lengths, initial_states,\n        dim_in, dim_out, scope, outputs_with_grads=(0,), return_params=False,\n        memory_optimization=False, forget_bias=0.0, forward_only=False,\n        drop_states=False, create_lstm=None):\n    params = locals()  # leave it as a first line to grab all params\n    params.pop('create_lstm')\n    if not isinstance(dim_out, list):\n        return create_lstm(**params)\n    elif len(dim_out) == 1:\n        params['dim_out'] = dim_out[0]\n        return create_lstm(**params)\n\n    assert len(dim_out) != 0, \"dim_out list can't be empty\"\n    assert return_params is False, \"return_params not supported for layering\"\n    for i, output_dim in enumerate(dim_out):\n        params.update({\n            'dim_out': output_dim\n        })\n        output, last_output, all_states, last_state = create_lstm(**params)\n        params.update({\n            'input_blob': output,\n            'dim_in': output_dim,\n            'initial_states': (last_output, last_state),\n            'scope': scope + '_layer_{}'.format(i + 1)\n        })\n    return output, last_output, all_states, last_state\n\n\nlayered_LSTM = functools.partial(_layered_LSTM, create_lstm=LSTM)\n"
  },
  {
    "path": "caffe2/python/schema.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package schema\n# Module caffe2.python.schema\n\"\"\"\nDefines a minimal set of data types that allow to represent datasets with\narbitrary nested structure, including objects of variable length, such as\nmaps and lists.\n\nThis defines a columnar storage format for such datasets on top of caffe2\ntensors. In terms of capacity of representation, it can represent most of\nthe data types supported by Parquet, ORC, DWRF file formats.\n\nSee comments in operator_test/dataset_ops_test.py for an example and\nwalkthrough on how to use schema to store and iterate through a structured\nin-memory dataset.\n\"\"\"\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport logging\nimport numpy as np\nfrom caffe2.python import core\nfrom caffe2.python import workspace\nfrom caffe2.python.core import BlobReference\nfrom collections import OrderedDict, namedtuple\nfrom past.builtins import basestring\nfrom future.utils import viewitems, viewkeys, viewvalues\nfrom itertools import islice\nfrom six import StringIO\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO)\n\nFIELD_SEPARATOR = ':'\n\n\ndef _join_field_name(prefix, suffix):\n    if prefix and suffix:\n        return '{}{}{}'.format(prefix, FIELD_SEPARATOR, suffix)\n    elif prefix:\n        return prefix\n    elif suffix:\n        return suffix\n    else:\n        return ''\n\n\ndef _normalize_field(field_or_type_or_blob, keep_blobs=True):\n    \"\"\"Clones/normalizes a field before adding it to a container.\"\"\"\n    if isinstance(field_or_type_or_blob, Field):\n        return field_or_type_or_blob.clone(keep_blobs=keep_blobs)\n    elif type(field_or_type_or_blob) in (type, np.dtype):\n        return Scalar(dtype=field_or_type_or_blob)\n    else:\n        return Scalar(blob=field_or_type_or_blob)\n\n\nFeatureSpec = namedtuple(\n    'FeatureSpec',\n    [\n        'feature_type',\n        'feature_names',\n        'feature_ids',\n        'feature_is_request_only',\n        'desired_hash_size',\n    ]\n)\n\nFeatureSpec.__new__.__defaults__ = (None, None, None, None, None)\n\n\nclass Metadata(\n    namedtuple(\n        'Metadata', ['categorical_limit', 'expected_value', 'feature_specs']\n    )\n):\n    \"\"\"Represents additional information associated with a scalar in schema.\n\n    `categorical_limit` - for fields of integral type that are guaranteed to be\n    non-negative it specifies the maximum possible value plus one. It's often\n    used as a size of an embedding table.\n\n    `expected_value` - anticipated average value of elements in the field.\n    Usually makes sense for length fields of lists.\n\n    `feature_specs` - information about the features that contained in this\n    field. For example if field have more than 1 feature it can have list of\n    feature names contained in this field.\"\"\"\n    __slots__ = ()\n\n\nMetadata.__new__.__defaults__ = (None, None, None)\n\n\nclass Field(object):\n    \"\"\"Represents an abstract field type in a dataset.\n    \"\"\"\n\n    def __init__(self, children):\n        \"\"\"Derived classes must call this after their initialization.\"\"\"\n        self._parent = (None, 0)\n        offset = 0\n        self._field_offsets = []\n        for child in children:\n            self._field_offsets.append(offset)\n            offset += len(child.field_names())\n        self._field_offsets.append(offset)\n\n    def clone_schema(self):\n        return self.clone(keep_blobs=False)\n\n    def field_names(self):\n        \"\"\"Return the children field names for this field.\"\"\"\n        raise NotImplementedError('Field is an abstract class.')\n\n    def field_types(self):\n        \"\"\"Return the numpy.dtype for each of the children fields.\"\"\"\n        raise NotImplementedError('Field is an abstract class.')\n\n    def field_metadata(self):\n        \"\"\"Return the Metadata for each of the children fields.\"\"\"\n        raise NotImplementedError('Field is an abstract class.')\n\n    def field_blobs(self):\n        \"\"\"Return the list of blobs with contents for this Field.\n        Values can either be all numpy.ndarray or BlobReference.\n        If any of the fields doens't have a blob, throws.\n        \"\"\"\n        raise NotImplementedError('Field is an abstract class.')\n\n    def all_scalars(self):\n        \"\"\"Return the list of all Scalar instances in the Field.\n        The order is the same as for field_names() or field_blobs()\"\"\"\n        raise NotImplementedError('Field is an abstract class.')\n\n    def has_blobs(self):\n        \"\"\"Return True if every scalar of this field has blobs.\"\"\"\n        raise NotImplementedError('Field is an abstract class.')\n\n    def clone(self, keep_blobs=True):\n        \"\"\"Clone this Field along with its children.\"\"\"\n        raise NotImplementedError('Field is an abstract class.')\n\n    def _set_parent(self, parent, relative_id):\n        self._parent = (parent, relative_id)\n\n    def slice(self):\n        \"\"\"\n        Returns a slice representing the range of field ids that belong to\n        this field. This slice can be used to index a list of fields.\n\n        E.g.:\n\n        >>> s = Struct(\n        >>>     ('a', Scalar()),\n        >>>     ('b', Struct(\n        >>>         ('b1', Scalar()),\n        >>>         ('b2', Scalar()),\n        >>>     )),\n        >>>     ('c', Scalar()),\n        >>> )\n        >>> field_data = ['da', 'db1', 'db2', 'dc']\n        >>> field_data[s.b.split()]\n        ['db1', 'db2']\n        \"\"\"\n        base_id = self._child_base_id()\n        return slice(base_id, base_id + len(self.field_names()))\n\n    def _child_base_id(self, child_index=None):\n        \"\"\"Get the base id of the given child\"\"\"\n        p, i = self._parent\n        pos = 0 if child_index is None else self._field_offsets[child_index]\n        if p:\n            pos += p._child_base_id(i)\n        return pos\n\n    def __eq__(self, other):\n        \"\"\"Equivalance of two schemas\"\"\"\n        return (\n            (self.field_names() == other.field_names()) and\n            (self.field_types() == other.field_types()) and\n            (self.field_metadata() == other.field_metadata())\n        )\n\n    def _pprint_impl(self, indent, str_buffer):\n        raise NotImplementedError('Field is an abstrct class.')\n\n    def __repr__(self):\n        str_buffer = StringIO()\n        self._pprint_impl(0, str_buffer)\n        contents = str_buffer.getvalue()\n        str_buffer.close()\n        return contents\n\n\nclass List(Field):\n    \"\"\"Represents a variable-length list.\n\n    Values of a list can also be complex fields such as Lists and Structs.\n    In addition to the fields exposed by its `values` field, a List exposes an\n    additional `lengths` field, which will contain the size of each list under\n    the parent domain.\n    \"\"\"\n\n    def __init__(self, values, lengths_blob=None):\n        if isinstance(lengths_blob, Field):\n            assert isinstance(lengths_blob, Scalar)\n            self.lengths = _normalize_field(lengths_blob)\n        else:\n            self.lengths = Scalar(np.int32, lengths_blob)\n        self._items = _normalize_field(values)\n        self.lengths._set_parent(self, 0)\n        self._items._set_parent(self, 1)\n        Field.__init__(self, [self.lengths, self._items])\n\n    def field_names(self):\n        value_fields = self._items.field_names()\n        return (\n            ['lengths'] + [_join_field_name('values', v) for v in value_fields]\n        )\n\n    def field_types(self):\n        return self.lengths.field_types() + self._items.field_types()\n\n    def field_metadata(self):\n        return self.lengths.field_metadata() + self._items.field_metadata()\n\n    def field_blobs(self):\n        return self.lengths.field_blobs() + self._items.field_blobs()\n\n    def all_scalars(self):\n        return self.lengths.all_scalars() + self._items.all_scalars()\n\n    def has_blobs(self):\n        return self.lengths.has_blobs() and self._items.has_blobs()\n\n    def clone(self, keep_blobs=True):\n        return List(\n            _normalize_field(self._items, keep_blobs=keep_blobs),\n            _normalize_field(self.lengths, keep_blobs=keep_blobs)\n        )\n\n    def _pprint_impl(self, indent, str_buffer):\n        str_buffer.write('  ' * indent + \"List(\\n\")\n        str_buffer.write('  ' * (indent + 1) + \"lengths=\\n\")\n        self.lengths._pprint_impl(indent=indent + 2, str_buffer=str_buffer)\n        str_buffer.write('  ' * (indent + 1) + \"_items=\\n\")\n        self._items._pprint_impl(indent=indent + 2, str_buffer=str_buffer)\n        str_buffer.write('  ' * indent + \")\\n\")\n\n    def __getattr__(self, item):\n        \"\"\"If the value of this list is a struct,\n        allow to introspect directly into its fields.\"\"\"\n        if item.startswith('__'):\n            raise AttributeError(item)\n        if isinstance(self._items, Struct):\n            return getattr(self._items, item)\n        elif item == 'value' or item == 'items':\n            return self._items\n        else:\n            raise AttributeError('Field not found in list: %s.' % item)\n\n    def __getitem__(self, item):\n        names = item.split(FIELD_SEPARATOR, 1)\n\n        if len(names) == 1:\n            if item == 'lengths':\n                return self.lengths\n            elif item == 'values':\n                return self._items\n        else:\n            if names[0] == 'values':\n                return self._items[names[1]]\n        raise KeyError('Field not found in list: %s.' % item)\n\n\nclass Struct(Field):\n    \"\"\"Represents a named list of fields sharing the same domain.\n    \"\"\"\n\n    def __init__(self, *fields):\n        \"\"\" fields is a list of tuples in format of (name, field). The name is\n        a string of nested name, e.g., `a`, `a:b`, `a:b:c`. For example\n\n        Struct(\n          ('a', Scalar()),\n          ('b:c', Scalar()),\n          ('b:d:e', Scalar()),\n          ('b', Struct(\n            ('f', Scalar()),\n          )),\n        )\n\n        is equal to\n\n        Struct(\n          ('a', Scalar()),\n          ('b', Struct(\n            ('c', Scalar()),\n            ('d', Struct(('e', Scalar()))),\n            ('f', Scalar()),\n          )),\n        )\n        \"\"\"\n        for field in fields:\n            assert len(field) == 2\n            assert field[0], 'Field names cannot be empty'\n            assert field[0] != 'lengths', (\n                'Struct cannot contain a field named `lengths`.'\n            )\n        fields = [(name, _normalize_field(field)) for name, field in fields]\n        self.fields = OrderedDict()\n        for name, field in fields:\n            if FIELD_SEPARATOR in name:\n                name, field = self._struct_from_nested_name(name, field)\n            if name not in self.fields:\n                self.fields[name] = field\n                continue\n            if (\n                    not isinstance(field, Struct) or\n                    not isinstance(self.fields[name], Struct)\n            ):\n                raise ValueError('Duplicate field name: %s' % name)\n            self.fields[name] = self.fields[name] + field\n        for id, (_, field) in enumerate(viewitems(self.fields)):\n            field._set_parent(self, id)\n        Field.__init__(self, viewvalues(self.fields))\n        self._frozen = True\n\n    def _struct_from_nested_name(self, nested_name, field):\n        def create_internal(nested_name, field):\n            names = nested_name.split(FIELD_SEPARATOR, 1)\n            if len(names) == 1:\n                added_field = field\n            else:\n                added_field = create_internal(names[1], field)\n            return Struct((names[0], added_field))\n\n        names = nested_name.split(FIELD_SEPARATOR, 1)\n        assert len(names) >= 2\n        return names[0], create_internal(names[1], field)\n\n    def get_children(self):\n        return list(viewitems(self.fields))\n\n    def field_names(self):\n        names = []\n        for name, field in viewitems(self.fields):\n            names += [_join_field_name(name, f) for f in field.field_names()]\n        return names\n\n    def field_types(self):\n        types = []\n        for _, field in viewitems(self.fields):\n            types += field.field_types()\n        return types\n\n    def field_metadata(self):\n        metadata = []\n        for _, field in viewitems(self.fields):\n            metadata += field.field_metadata()\n        return metadata\n\n    def field_blobs(self):\n        blobs = []\n        for _, field in viewitems(self.fields):\n            blobs += field.field_blobs()\n        return blobs\n\n    def all_scalars(self):\n        scalars = []\n        for _, field in viewitems(self.fields):\n            scalars += field.all_scalars()\n        return scalars\n\n    def has_blobs(self):\n        return all(field.has_blobs() for field in viewvalues(self.fields))\n\n    def clone(self, keep_blobs=True):\n        normalized_fields = [\n            (k, _normalize_field(v, keep_blobs=keep_blobs))\n            for k, v in viewitems(self.fields)\n        ]\n        return Struct(*normalized_fields)\n\n    def _get_field_by_nested_name(self, nested_name):\n        names = nested_name.split(FIELD_SEPARATOR, 1)\n        field = self.fields.get(names[0], None)\n\n        if field is None:\n            return None\n\n        if len(names) == 1:\n            return field\n\n        try:\n            return field[names[1]]\n        except (KeyError, TypeError):\n            return None\n\n    def _pprint_impl(self, indent, str_buffer):\n        str_buffer.write('  ' * indent + \"Struct( \\n\")\n        for name, field in viewitems(self.fields):\n            str_buffer.write('  ' * (indent + 1) + \"{}=\".format(name) + \"\\n\")\n            field._pprint_impl(indent=indent + 2, str_buffer=str_buffer)\n        str_buffer.write('  ' * indent + \") \\n\")\n\n    def __contains__(self, item):\n        field = self._get_field_by_nested_name(item)\n        return field is not None\n\n    def __len__(self):\n        return len(self.fields)\n\n    def __getitem__(self, item):\n        \"\"\"\n        item can be a tuple or list of ints or strings, or a single\n        int or string. String item is a nested field name, e.g., \"a\", \"a:b\",\n        \"a:b:c\". Int item is the index of a field at the first level of the\n        Struct.\n        \"\"\"\n        if isinstance(item, list) or isinstance(item, tuple):\n            keys = list(viewkeys(self.fields))\n            return Struct(\n                * [\n                    (\n                        keys[k]\n                        if isinstance(k, int) else k, self[k]\n                    ) for k in item\n                ]\n            )\n        elif isinstance(item, int):\n            return next(islice(viewvalues(self.fields), item, None))\n        else:\n            field = self._get_field_by_nested_name(item)\n            if field is None:\n                raise KeyError('field \"%s\" not found' % (item))\n            return field\n\n    def get(self, item, default_value):\n        \"\"\"\n        similar to python's dictionary get method, return field of item if found\n        (i.e. self.item is valid) or otherwise return default_value\n\n        it's a syntax suger of python's builtin getattr method\n        \"\"\"\n        return getattr(self, item, default_value)\n\n    def __getattr__(self, item):\n        if item.startswith('__'):\n            raise AttributeError(item)\n        try:\n            return self.__dict__['fields'][item]\n        except KeyError:\n            raise AttributeError(item)\n\n    def __setattr__(self, key, value):\n        # Disable setting attributes after initialization to prevent false\n        # impression of being able to overwrite a field.\n        # Allowing setting internal states mainly so that _parent can be set\n        # post initialization.\n        if getattr(self, '_frozen', None) and not key.startswith('_'):\n            raise TypeError('Struct.__setattr__() is disabled after __init__()')\n        super(Struct, self).__setattr__(key, value)\n\n    def __add__(self, other):\n        \"\"\"\n        Allows to merge fields of two schema.Struct using '+' operator.\n        If two Struct have common field names, the merge is conducted\n        recursively. Here are examples:\n\n        Example 1\n        s1 = Struct(('a', Scalar()))\n        s2 = Struct(('b', Scalar()))\n        s1 + s2 == Struct(\n            ('a', Scalar()),\n            ('b', Scalar()),\n        )\n\n        Example 2\n        s1 = Struct(\n            ('a', Scalar()),\n            ('b', Struct(('c', Scalar()))),\n        )\n        s2 = Struct(('b', Struct(('d', Scalar()))))\n        s1 + s2 == Struct(\n            ('a', Scalar()),\n            ('b', Struct(\n                ('c', Scalar()),\n                ('d', Scalar()),\n            )),\n        )\n        \"\"\"\n        if not isinstance(other, Struct):\n            return NotImplemented\n\n        children = OrderedDict(self.get_children())\n        for name, right_field in other.get_children():\n            if name not in children:\n                children[name] = right_field\n                continue\n            left_field = children[name]\n            children[name] = left_field + right_field\n\n        return Struct(*(viewitems(children)))\n\n    def __sub__(self, other):\n        \"\"\"\n        Allows to remove common fields of two schema.Struct from self by\n        using '-' operator. If two Struct have common field names, the\n        removal is conducted recursively. If a child struct has no fields\n        inside, it will be removed from its parent. Here are examples:\n\n        Example 1\n        s1 = Struct(\n            ('a', Scalar()),\n            ('b', Scalar()),\n        )\n        s2 = Struct(('a', Scalar()))\n        s1 - s2 == Struct(('b', Scalar()))\n\n        Example 2\n        s1 = Struct(\n            ('b', Struct(\n                ('c', Scalar()),\n                ('d', Scalar()),\n            ))\n        )\n        s2 = Struct(\n            ('b', Struct(('c', Scalar()))),\n        )\n        s1 - s2 == Struct(\n            ('b', Struct(\n                ('d', Scalar()),\n            )),\n        )\n\n        Example 3\n        s1 = Struct(\n            ('a', Scalar()),\n            ('b', Struct(\n                ('d', Scalar()),\n            ))\n        )\n        s2 = Struct(\n            ('b', Struct(\n                ('c', Scalar())\n                ('d', Scalar())\n            )),\n        )\n        s1 - s2 == Struct(\n            ('a', Scalar()),\n        )\n        \"\"\"\n        if not isinstance(other, Struct):\n            return NotImplemented\n\n        children = OrderedDict(self.get_children())\n        for name, right_field in other.get_children():\n            if name in children:\n                left_field = children[name]\n                if type(left_field) == type(right_field):\n                    if isinstance(left_field, Struct):\n                        child = left_field - right_field\n                        if child.get_children():\n                            children[name] = child\n                            continue\n                    children.pop(name)\n                else:\n                    raise TypeError(\n                        \"Type of left_field, \" + str(type(left_field)) +\n                        \", is not the same as that of right_field, \" +\n                        str(type(right_field)) +\n                        \", yet they have the same field name, \" + name)\n        return Struct(*(children.items()))\n\n\nclass Scalar(Field):\n    \"\"\"Represents a typed scalar or tensor of fixed shape.\n\n    A Scalar is a leaf in a schema tree, translating to exactly one tensor in\n    the dataset's underlying storage.\n\n    Usually, the tensor storing the actual values of this field is a 1D tensor,\n    representing a series of values in its domain. It is possible however to\n    have higher rank values stored as a Scalar, as long as all entries have\n    the same shape.\n\n    E.g.:\n\n        Scalar(np.float64)\n\n            Scalar field of type float64. Caffe2 will expect readers and\n            datasets to expose it as a 1D tensor of doubles (vector), where\n            the size of the vector is determined by this fields' domain.\n\n        Scalar((np.int32, 5))\n\n            Tensor field of type int32. Caffe2 will expect readers and\n            datasets to implement it as a 2D tensor (matrix) of shape (L, 5),\n            where L is determined by this fields' domain.\n\n        Scalar((str, (10, 20)))\n\n            Tensor field of type str. Caffe2 will expect readers and\n            datasets to implement it as a 3D tensor of shape (L, 10, 20),\n            where L is determined by this fields' domain.\n\n    If the field type is unknown at construction time, call Scalar(), that will\n    default to np.void as its dtype.\n\n    It is an error to pass a structured dtype to Scalar, since it would contain\n    more than one field. Instead, use from_dtype, which will construct\n    a nested `Struct` field reflecting the given dtype's structure.\n\n    A Scalar can also contain a blob, which represents the value of this\n    Scalar. A blob can be either a numpy.ndarray, in which case it contain the\n    actual contents of the Scalar, or a BlobReference, which represents a\n    blob living in a caffe2 Workspace. If blob of different types are passed,\n    a conversion to numpy.ndarray is attempted.\n    \"\"\"\n\n    def __init__(self, dtype=None, blob=None, metadata=None):\n        self._metadata = None\n        self.set(dtype, blob, metadata, unsafe=True)\n        Field.__init__(self, [])\n\n    def field_names(self):\n        return ['']\n\n    def field_type(self):\n        return self.dtype\n\n    def field_types(self):\n        return [self.dtype]\n\n    def field_metadata(self):\n        return [self._metadata]\n\n    def has_blobs(self):\n        return self._blob is not None\n\n    def field_blobs(self):\n        assert self._blob is not None, 'Value is not set for this field.'\n        return [self._blob]\n\n    def all_scalars(self):\n        return [self]\n\n    def clone(self, keep_blobs=True):\n        return Scalar(\n            dtype=self._original_dtype,\n            blob=self._blob if keep_blobs else None,\n            metadata=self._metadata\n        )\n\n    def get(self):\n        \"\"\"Gets the current blob of this Scalar field.\"\"\"\n        assert self._blob is not None, 'Value is not set for this field.'\n        return self._blob\n\n    def __call__(self):\n        \"\"\"Shortcut for self.get()\"\"\"\n        return self.get()\n\n    @property\n    def metadata(self):\n        return self._metadata\n\n    def set_metadata(self, value):\n        assert isinstance(value, Metadata), \\\n            'metadata must be Metadata, got {}'.format(type(value))\n        self._metadata = value\n        self._validate_metadata()\n\n    def _validate_metadata(self):\n        if self._metadata is None:\n            return\n        if (self._metadata.categorical_limit is not None and\n                self.dtype is not None):\n            assert np.issubdtype(self.dtype, np.integer), \\\n                \"`categorical_limit` can be specified only in integral \" + \\\n                \"fields but got {}\".format(self.dtype)\n\n    def set_value(self, blob, throw_on_type_mismatch=False, unsafe=False):\n        \"\"\"Sets only the blob field still validating the existing dtype\"\"\"\n        if self.dtype.base != np.void and throw_on_type_mismatch:\n            assert isinstance(blob, np.ndarray), \"Got {!r}\".format(blob)\n            assert blob.dtype.base == self.dtype.base, (\n                \"Expected {}, got {}\".format(self.dtype.base, blob.dtype.base))\n        self.set(dtype=self._original_dtype, blob=blob, unsafe=unsafe)\n\n    def set(self, dtype=None, blob=None, metadata=None, unsafe=False):\n        \"\"\"Set the type and/or blob of this scalar. See __init__ for details.\n\n        Args:\n            dtype: can be any numpy type. If not provided and `blob` is\n                   provided, it will be inferred. If no argument is provided,\n                   this Scalar will be of type np.void.\n            blob:  if provided, can be either a BlobReference or a\n                   numpy.ndarray. If a value of different type is passed,\n                   a conversion to numpy.ndarray is attempted. Strings aren't\n                   accepted, since they can be ambiguous. If you want to pass\n                   a string, to either BlobReference(blob) or np.array(blob).\n            metadata: optional instance of Metadata, if provided overrides\n                      the metadata information of the scalar\n        \"\"\"\n        if not unsafe:\n            logger.warning(\n                \"Scalar should be considered immutable. Only call Scalar.set() \"\n                \"on newly created Scalar with unsafe=True. This will become an \"\n                \"error soon.\"\n            )\n        if blob is not None and isinstance(blob, basestring):\n            raise ValueError(\n                'Passing str blob to Scalar.set() is ambiguous. '\n                'Do either set(blob=np.array(blob)) or '\n                'set(blob=BlobReference(blob))'\n            )\n\n        self._original_dtype = dtype\n        if dtype is not None:\n            dtype = np.dtype(dtype)\n        # If blob is not None and it is not a BlobReference, we assume that\n        # it is actual tensor data, so we will try to cast it to a numpy array.\n        if blob is not None and not isinstance(blob, BlobReference):\n            preserve_shape = isinstance(blob, np.ndarray)\n            if dtype is not None and dtype != np.void:\n                blob = np.array(blob, dtype=dtype.base)\n                # if array is empty we may need to reshape a little\n                if blob.size == 0 and not preserve_shape:\n                    blob = blob.reshape((0, ) + dtype.shape)\n            else:\n                assert isinstance(blob, np.ndarray), (\n                    'Invalid blob type: %s' % str(type(blob)))\n\n            # reshape scalars into 1D arrays\n            # TODO(azzolini): figure out better way of representing this\n            if len(blob.shape) == 0 and not preserve_shape:\n                blob = blob.reshape((1, ))\n\n            # infer inner shape from the blob given\n            # TODO(dzhulgakov): tweak this to make it work with PackedStruct\n            if (len(blob.shape) > 1 and dtype is not None and\n                    dtype.base != np.void):\n                dtype = np.dtype((dtype.base, blob.shape[1:]))\n        # if we were still unable to infer the dtype\n        if dtype is None:\n            dtype = np.dtype(np.void)\n        assert not dtype.fields, (\n            'Cannot create Scalar with a structured dtype. ' +\n            'Use from_dtype instead.'\n        )\n        self.dtype = dtype\n        self._blob = blob\n        if metadata is not None:\n            self.set_metadata(metadata)\n        self._validate_metadata()\n\n    def set_type(self, dtype):\n        self._original_dtype = dtype\n        if dtype is not None:\n            self.dtype = np.dtype(dtype)\n        else:\n            self.dtype = np.dtype(np.void)\n        self._validate_metadata()\n\n    def _pprint_impl(self, indent, str_buffer):\n        str_buffer.write('  ' * (indent) +\n            'Scalar({!r}, {!r}, {!r})'.format(\n            self.dtype, self._blob, self._metadata) + \"\\n\")\n\n    def id(self):\n        \"\"\"\n        Return the zero-indexed position of this scalar field in its schema.\n        Used in order to index into the field_blob list returned by readers or\n        accepted by writers.\n        \"\"\"\n        return self._child_base_id()\n\n\ndef Map(\n    keys,\n    values,\n    keys_name='keys',\n    values_name='values',\n    lengths_blob=None\n):\n    \"\"\"A map is a List of Struct containing keys and values fields.\n    Optionally, you can provide custom name for the key and value fields.\n    \"\"\"\n    return List(\n        Struct((keys_name, keys), (values_name, values)),\n        lengths_blob=lengths_blob\n    )\n\n\ndef NamedTuple(name_prefix, *fields):\n    return Struct(* [('%s_%d' % (name_prefix, i), field)\n                     for i, field in enumerate(fields)])\n\n\ndef Tuple(*fields):\n    \"\"\"\n    Creates a Struct with default, sequential, field names of given types.\n    \"\"\"\n    return NamedTuple('field', *fields)\n\n\ndef RawTuple(num_fields, name_prefix='field'):\n    \"\"\"\n    Creates a tuple of `num_field` untyped scalars.\n    \"\"\"\n    assert isinstance(num_fields, int)\n    assert num_fields >= 0\n    return NamedTuple(name_prefix, *([np.void] * num_fields))\n\n\ndef from_dtype(dtype, _outer_shape=()):\n    \"\"\"Constructs a Caffe2 schema from the given numpy's dtype.\n\n    Numpy supports scalar, array-like and structured datatypes, as long as\n    all the shapes are fixed. This function breaks down the given dtype into\n    a Caffe2 schema containing `Struct` and `Scalar` types.\n\n    Fields containing byte offsets are not currently supported.\n    \"\"\"\n    if not isinstance(dtype, np.dtype):\n        # wrap into a ndtype\n        shape = _outer_shape\n        dtype = np.dtype((dtype, _outer_shape))\n    else:\n        # concatenate shapes if necessary\n        shape = _outer_shape + dtype.shape\n        if shape != dtype.shape:\n            dtype = np.dtype((dtype.base, shape))\n\n    if not dtype.fields:\n        return Scalar(dtype)\n\n    struct_fields = []\n    for name, (fdtype, offset) in dtype.fields:\n        assert offset == 0, ('Fields with byte offsets are not supported.')\n        struct_fields += (name, from_dtype(fdtype, _outer_shape=shape))\n    return Struct(*struct_fields)\n\n\nclass _SchemaNode(object):\n    \"\"\"This is a private class used to represent a Schema Node\"\"\"\n\n    def __init__(self, name, type_str=''):\n        self.name = name\n        self.children = []\n        self.type_str = type_str\n        self.field = None\n\n    def add_child(self, name, type_str=''):\n        for child in self.children:\n            if child.name == name and child.type_str == type_str:\n                return child\n        child = _SchemaNode(name, type_str)\n        self.children.append(child)\n        return child\n\n    def get_field(self):\n\n        list_names = ['lengths', 'values']\n        map_names = ['lengths', 'keys', 'values']\n\n        if len(self.children) == 0 or self.field is not None:\n            if self.field is None:\n                return Struct()\n            else:\n                return self.field\n\n        child_names = []\n        for child in self.children:\n            child_names.append(child.name)\n\n        if (set(child_names) == set(list_names)):\n            for child in self.children:\n                if child.name == 'values':\n                    values_field = child.get_field()\n                else:\n                    lengths_field = child.get_field()\n            self.field = List(\n                values_field,\n                lengths_blob=lengths_field\n            )\n            self.type_str = \"List\"\n            return self.field\n        elif (set(child_names) == set(map_names)):\n            for child in self.children:\n                if child.name == 'keys':\n                    key_field = child.get_field()\n                elif child.name == 'values':\n                    values_field = child.get_field()\n                else:\n                    lengths_field = child.get_field()\n            self.field = Map(\n                key_field,\n                values_field,\n                lengths_blob=lengths_field\n            )\n            self.type_str = \"Map\"\n            return self.field\n\n        else:\n            struct_fields = []\n            for child in self.children:\n                struct_fields.append((child.name, child.get_field()))\n\n            self.field = Struct(*struct_fields)\n            self.type_str = \"Struct\"\n            return self.field\n\n    def print_recursively(self):\n        for child in self.children:\n            child.print_recursively()\n        logger.info(\"Printing node: Name and type\")\n        logger.info(self.name)\n        logger.info(self.type_str)\n\n\ndef from_column_list(\n    col_names, col_types=None,\n    col_blobs=None, col_metadata=None\n):\n    \"\"\"\n    Given a list of names, types, and optionally values, construct a Schema.\n    \"\"\"\n    if col_types is None:\n        col_types = [None] * len(col_names)\n    if col_metadata is None:\n        col_metadata = [None] * len(col_names)\n    if col_blobs is None:\n        col_blobs = [None] * len(col_names)\n    assert len(col_names) == len(col_types), (\n        'col_names and col_types must have the same length.'\n    )\n    assert len(col_names) == len(col_metadata), (\n        'col_names and col_metadata must have the same length.'\n    )\n    assert len(col_names) == len(col_blobs), (\n        'col_names and col_blobs must have the same length.'\n    )\n    root = _SchemaNode('root', 'Struct')\n    for col_name, col_type, col_blob, col_metadata in zip(\n        col_names, col_types, col_blobs, col_metadata\n    ):\n        columns = col_name.split(FIELD_SEPARATOR)\n        current = root\n        for i in range(len(columns)):\n            name = columns[i]\n            type_str = ''\n            field = None\n            if i == len(columns) - 1:\n                type_str = col_type\n                field = Scalar(\n                    dtype=col_type,\n                    blob=col_blob,\n                    metadata=col_metadata\n                )\n            next = current.add_child(name, type_str)\n            if field is not None:\n                next.field = field\n            current = next\n\n    return root.get_field()\n\n\ndef from_blob_list(schema, values, throw_on_type_mismatch=False):\n    \"\"\"\n    Create a schema that clones the given schema, but containing the given\n    list of values.\n    \"\"\"\n    assert isinstance(schema, Field), 'Argument `schema` must be a Field.'\n    if isinstance(values, BlobReference):\n        values = [values]\n    record = schema.clone_schema()\n    scalars = record.all_scalars()\n    assert len(scalars) == len(values), (\n        'Values must have %d elements, got %d.' % (len(scalars), len(values))\n    )\n    for scalar, value in zip(scalars, values):\n        scalar.set_value(value, throw_on_type_mismatch, unsafe=True)\n    return record\n\n\ndef as_record(value):\n    if isinstance(value, Field):\n        return value\n    elif isinstance(value, list) or isinstance(value, tuple):\n        is_field_list = all(\n            f is tuple and len(f) == 2 and isinstance(f[0], basestring)\n            for f in value\n        )\n        if is_field_list:\n            return Struct(* [(k, as_record(v)) for k, v in value])\n        else:\n            return Tuple(* [as_record(f) for f in value])\n    elif isinstance(value, dict):\n        return Struct(* [(k, as_record(v)) for k, v in viewitems(value)])\n    else:\n        return _normalize_field(value)\n\n\ndef FetchRecord(blob_record, ws=None, throw_on_type_mismatch=False):\n    \"\"\"\n    Given a record containing BlobReferences, return a new record with same\n    schema, containing numpy arrays, fetched from the current active workspace.\n    \"\"\"\n\n    def fetch(v):\n        if ws is None:\n            return workspace.FetchBlob(str(v))\n        else:\n            return ws.blobs[str(v)].fetch()\n\n    assert isinstance(blob_record, Field)\n    field_blobs = blob_record.field_blobs()\n    assert all(isinstance(v, BlobReference) for v in field_blobs)\n    field_arrays = [fetch(value) for value in field_blobs]\n    return from_blob_list(blob_record, field_arrays, throw_on_type_mismatch)\n\n\ndef FeedRecord(blob_record, arrays, ws=None):\n    \"\"\"\n    Given a Record containing blob_references and arrays, which is either\n    a list of numpy arrays or a Record containing numpy arrays, feeds the\n    record to the current workspace.\n    \"\"\"\n\n    def feed(b, v):\n        if ws is None:\n            workspace.FeedBlob(str(b), v)\n        else:\n            ws.create_blob(str(b))\n            ws.blobs[str(b)].feed(v)\n\n    assert isinstance(blob_record, Field)\n    field_blobs = blob_record.field_blobs()\n    assert all(isinstance(v, BlobReference) for v in field_blobs)\n    if isinstance(arrays, Field):\n        # TODO: check schema\n        arrays = arrays.field_blobs()\n    assert len(arrays) == len(field_blobs), (\n        'Values must contain exactly %d ndarrays.' % len(field_blobs)\n    )\n    for blob, array in zip(field_blobs, arrays):\n        feed(blob, array)\n\n\ndef NewRecord(net, schema):\n    \"\"\"\n    Given a record of np.arrays, create a BlobReference for each one of them,\n    returning a record containing BlobReferences. The name of each returned blob\n    is NextScopedBlob(field_name), which guarantees unique name in the current\n    net. Use NameScope explicitly to avoid name conflictions between different\n    nets.\n    \"\"\"\n    if isinstance(schema, Scalar):\n        result = schema.clone()\n        result.set_value(\n            blob=net.NextScopedBlob('unnamed_scalar'),\n            unsafe=True,\n        )\n        return result\n\n    assert isinstance(schema, Field), 'Record must be a schema.Field instance.'\n    blob_refs = [\n        net.NextScopedBlob(prefix=name)\n        for name in schema.field_names()\n    ]\n    return from_blob_list(schema, blob_refs)\n\n\ndef ConstRecord(net, array_record):\n    \"\"\"\n    Given a record of arrays, returns a record of blobs,\n    initialized with net.Const.\n    \"\"\"\n    blob_record = NewRecord(net, array_record)\n    for blob, array in zip(\n        blob_record.field_blobs(), array_record.field_blobs()\n    ):\n        net.Const(array, blob)\n    return blob_record\n\n\ndef InitEmptyRecord(net, schema_or_record, enforce_types=False):\n    if not schema_or_record.has_blobs():\n        record = NewRecord(net, schema_or_record)\n    else:\n        record = schema_or_record\n\n    for blob_type, blob in zip(record.field_types(), record.field_blobs()):\n        try:\n            data_type = data_type_for_dtype(blob_type)\n            shape = [0] + list(blob_type.shape)\n            net.ConstantFill([], blob, shape=shape, dtype=data_type)\n        except TypeError:\n            logger.warning(\"Blob {} has type error\".format(blob))\n            # If data_type_for_dtype doesn't know how to resolve given numpy\n            # type to core.DataType, that function can throw type error (for\n            # example that would happen for cases of unknown types such as\n            # np.void). This is not a problem for cases when the record if going\n            # to be overwritten by some operator later, though it might be an\n            # issue for type/shape inference.\n            if enforce_types:\n                raise\n            # If we don't enforce types for all items we'll create a blob with\n            # the default ConstantFill (FLOAT, no shape)\n            net.ConstantFill([], blob, shape=[0])\n\n    return record\n\n\n_DATA_TYPE_FOR_DTYPE = [\n    (np.str, core.DataType.STRING),\n    (np.float16, core.DataType.FLOAT16),\n    (np.float32, core.DataType.FLOAT),\n    (np.float64, core.DataType.DOUBLE),\n    (np.bool, core.DataType.BOOL),\n    (np.int8, core.DataType.INT8),\n    (np.int16, core.DataType.INT16),\n    (np.int32, core.DataType.INT32),\n    (np.int64, core.DataType.INT64),\n    (np.uint8, core.DataType.UINT8),\n    (np.uint16, core.DataType.UINT16),\n]\n\n\ndef is_schema_subset(schema, original_schema):\n    # TODO add more checks\n    return set(schema.field_names()).issubset(\n        set(original_schema.field_names()))\n\n\ndef equal_schemas(schema,\n                  original_schema,\n                  check_field_names=True,\n                  check_field_types=True,\n                  check_field_metas=False):\n    assert isinstance(schema, Field)\n    assert isinstance(original_schema, Field)\n\n    if check_field_names and (\n            schema.field_names() != original_schema.field_names()):\n        return False\n    if check_field_types and (\n            schema.field_types() != original_schema.field_types()):\n        return False\n    if check_field_metas and (\n            schema.field_metadata() != original_schema.field_metadata()):\n        return False\n\n    return True\n\n\ndef schema_check(schema, previous=None):\n    record = as_record(schema)\n    if previous is not None:\n        assert equal_schemas(schema, previous)\n    return record\n\n\ndef data_type_for_dtype(dtype):\n    for np_type, dt in _DATA_TYPE_FOR_DTYPE:\n        if dtype.base == np_type:\n            return dt\n    raise TypeError('Unknown dtype: ' + str(dtype.base))\n\n\ndef attach_metadata_to_scalars(field, metadata):\n    for f in field.all_scalars():\n        f.set_metadata(metadata)\n"
  },
  {
    "path": "caffe2/python/schema_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, schema\nimport numpy as np\n\nimport unittest\nimport pickle\nimport random\n\n\nclass TestDB(unittest.TestCase):\n    def testPicklable(self):\n        s = schema.Struct(\n            ('field1', schema.Scalar(dtype=np.int32)),\n            ('field2', schema.List(schema.Scalar(dtype=str)))\n        )\n        s2 = pickle.loads(pickle.dumps(s))\n        for r in (s, s2):\n            self.assertTrue(isinstance(r.field1, schema.Scalar))\n            self.assertTrue(isinstance(r.field2, schema.List))\n            self.assertTrue(getattr(r, 'non_existent', None) is None)\n\n    def testNormalizeField(self):\n        s = schema.Struct(('field1', np.int32), ('field2', str))\n        self.assertEquals(\n            s,\n            schema.Struct(\n                ('field1', schema.Scalar(dtype=np.int32)),\n                ('field2', schema.Scalar(dtype=str))\n            )\n        )\n\n    def testTuple(self):\n        s = schema.Tuple(np.int32, str, np.float32)\n        s2 = schema.Struct(\n            ('field_0', schema.Scalar(dtype=np.int32)),\n            ('field_1', schema.Scalar(dtype=np.str)),\n            ('field_2', schema.Scalar(dtype=np.float32))\n        )\n        self.assertEquals(s, s2)\n        self.assertEquals(s[0], schema.Scalar(dtype=np.int32))\n        self.assertEquals(s[1], schema.Scalar(dtype=np.str))\n        self.assertEquals(s[2], schema.Scalar(dtype=np.float32))\n        self.assertEquals(\n            s[2, 0],\n            schema.Struct(\n                ('field_2', schema.Scalar(dtype=np.float32)),\n                ('field_0', schema.Scalar(dtype=np.int32)),\n            )\n        )\n        # test iterator behavior\n        for i, (v1, v2) in enumerate(zip(s, s2)):\n            self.assertEquals(v1, v2)\n            self.assertEquals(s[i], v1)\n            self.assertEquals(s2[i], v1)\n\n    def testRawTuple(self):\n        s = schema.RawTuple(2)\n        self.assertEquals(\n            s, schema.Struct(\n                ('field_0', schema.Scalar()), ('field_1', schema.Scalar())\n            )\n        )\n        self.assertEquals(s[0], schema.Scalar())\n        self.assertEquals(s[1], schema.Scalar())\n\n    def testStructIndexing(self):\n        s = schema.Struct(\n            ('field1', schema.Scalar(dtype=np.int32)),\n            ('field2', schema.List(schema.Scalar(dtype=str))),\n            ('field3', schema.Struct()),\n        )\n        self.assertEquals(s['field2'], s.field2)\n        self.assertEquals(s['field2'], schema.List(schema.Scalar(dtype=str)))\n        self.assertEquals(s['field3'], schema.Struct())\n        self.assertEquals(\n            s['field2', 'field1'],\n            schema.Struct(\n                ('field2', schema.List(schema.Scalar(dtype=str))),\n                ('field1', schema.Scalar(dtype=np.int32)),\n            )\n        )\n\n    def testListInStructIndexing(self):\n        a = schema.List(schema.Scalar(dtype=str))\n        s = schema.Struct(\n            ('field1', schema.Scalar(dtype=np.int32)),\n            ('field2', a)\n        )\n        self.assertEquals(s['field2:lengths'], a.lengths)\n        self.assertEquals(s['field2:values'], a.items)\n        with self.assertRaises(KeyError):\n            s['fields2:items:non_existent']\n        with self.assertRaises(KeyError):\n            s['fields2:non_existent']\n\n    def testMapInStructIndexing(self):\n        a = schema.Map(\n            schema.Scalar(dtype=np.int32),\n            schema.Scalar(dtype=np.float32),\n        )\n        s = schema.Struct(\n            ('field1', schema.Scalar(dtype=np.int32)),\n            ('field2', a)\n        )\n        self.assertEquals(s['field2:values:keys'], a.keys)\n        self.assertEquals(s['field2:values:values'], a.values)\n        with self.assertRaises(KeyError):\n            s['fields2:keys:non_existent']\n\n    def testPreservesMetadata(self):\n        s = schema.Struct(\n            ('a', schema.Scalar(np.float32)), (\n                'b', schema.Scalar(\n                    np.int32,\n                    metadata=schema.Metadata(categorical_limit=5)\n                )\n            ), (\n                'c', schema.List(\n                    schema.Scalar(\n                        np.int32,\n                        metadata=schema.Metadata(categorical_limit=6)\n                    )\n                )\n            )\n        )\n        # attach metadata to lengths field\n        s.c.lengths.set_metadata(schema.Metadata(categorical_limit=7))\n\n        self.assertEqual(None, s.a.metadata)\n        self.assertEqual(5, s.b.metadata.categorical_limit)\n        self.assertEqual(6, s.c.value.metadata.categorical_limit)\n        self.assertEqual(7, s.c.lengths.metadata.categorical_limit)\n        sc = s.clone()\n        self.assertEqual(None, sc.a.metadata)\n        self.assertEqual(5, sc.b.metadata.categorical_limit)\n        self.assertEqual(6, sc.c.value.metadata.categorical_limit)\n        self.assertEqual(7, sc.c.lengths.metadata.categorical_limit)\n        sv = schema.from_blob_list(\n            s, [\n                np.array([3.4]), np.array([2]), np.array([3]),\n                np.array([1, 2, 3])\n            ]\n        )\n        self.assertEqual(None, sv.a.metadata)\n        self.assertEqual(5, sv.b.metadata.categorical_limit)\n        self.assertEqual(6, sv.c.value.metadata.categorical_limit)\n        self.assertEqual(7, sv.c.lengths.metadata.categorical_limit)\n\n    def testDupField(self):\n        with self.assertRaises(ValueError):\n            schema.Struct(\n                ('a', schema.Scalar()),\n                ('a', schema.Scalar()))\n\n    def testAssignToField(self):\n        with self.assertRaises(TypeError):\n            s = schema.Struct(('a', schema.Scalar()))\n            s.a = schema.Scalar()\n\n    def testPreservesEmptyFields(self):\n        s = schema.Struct(\n            ('a', schema.Scalar(np.float32)),\n            ('b', schema.Struct()),\n        )\n        sc = s.clone()\n        self.assertIn(\"a\", sc.fields)\n        self.assertIn(\"b\", sc.fields)\n        sv = schema.from_blob_list(s, [np.array([3.4])])\n        self.assertIn(\"a\", sv.fields)\n        self.assertIn(\"b\", sv.fields)\n        self.assertEqual(0, len(sv.b.fields))\n\n    def testStructSubstraction(self):\n        s1 = schema.Struct(\n            ('a', schema.Scalar()),\n            ('b', schema.Scalar()),\n            ('c', schema.Scalar()),\n        )\n        s2 = schema.Struct(\n            ('b', schema.Scalar())\n        )\n        s = s1 - s2\n        self.assertEqual(['a', 'c'], s.field_names())\n\n        s3 = schema.Struct(\n            ('a', schema.Scalar())\n        )\n        s = s1 - s3\n        self.assertEqual(['b', 'c'], s.field_names())\n\n        with self.assertRaises(TypeError):\n            s1 - schema.Scalar()\n\n    def testStructNestedSubstraction(self):\n        s1 = schema.Struct(\n            ('a', schema.Scalar()),\n            ('b', schema.Struct(\n                ('c', schema.Scalar()),\n                ('d', schema.Scalar()),\n                ('e', schema.Scalar()),\n                ('f', schema.Scalar()),\n            )),\n        )\n        s2 = schema.Struct(\n            ('b', schema.Struct(\n                ('d', schema.Scalar()),\n                ('e', schema.Scalar()),\n            )),\n        )\n        s = s1 - s2\n        self.assertEqual(['a', 'b:c', 'b:f'], s.field_names())\n\n    def testStructAddition(self):\n        s1 = schema.Struct(\n            ('a', schema.Scalar())\n        )\n        s2 = schema.Struct(\n            ('b', schema.Scalar())\n        )\n        s = s1 + s2\n        self.assertIn(\"a\", s.fields)\n        self.assertIn(\"b\", s.fields)\n        with self.assertRaises(TypeError):\n            s1 + s1\n        with self.assertRaises(TypeError):\n            s1 + schema.Scalar()\n\n    def testStructNestedAddition(self):\n        s1 = schema.Struct(\n            ('a', schema.Scalar()),\n            ('b', schema.Struct(\n                ('c', schema.Scalar())\n            )),\n        )\n        s2 = schema.Struct(\n            ('b', schema.Struct(\n                ('d', schema.Scalar())\n            ))\n        )\n        s = s1 + s2\n        self.assertEqual(['a', 'b:c', 'b:d'], s.field_names())\n\n        s3 = schema.Struct(\n            ('b', schema.Scalar()),\n        )\n        with self.assertRaises(TypeError):\n            s = s1 + s3\n\n    def testGetFieldByNestedName(self):\n        st = schema.Struct(\n            ('a', schema.Scalar()),\n            ('b', schema.Struct(\n                ('c', schema.Struct(\n                    ('d', schema.Scalar()),\n                )),\n            )),\n        )\n        self.assertRaises(KeyError, st.__getitem__, '')\n        self.assertRaises(KeyError, st.__getitem__, 'x')\n        self.assertRaises(KeyError, st.__getitem__, 'x:y')\n        self.assertRaises(KeyError, st.__getitem__, 'b:c:x')\n        a = st['a']\n        self.assertTrue(isinstance(a, schema.Scalar))\n        bc = st['b:c']\n        self.assertIn('d', bc.fields)\n        bcd = st['b:c:d']\n        self.assertTrue(isinstance(bcd, schema.Scalar))\n\n    def testAddFieldByNestedName(self):\n        f_a = schema.Scalar(blob=core.BlobReference('blob1'))\n        f_b = schema.Struct(\n            ('c', schema.Struct(\n                ('d', schema.Scalar(blob=core.BlobReference('blob2'))),\n            )),\n        )\n        f_x = schema.Struct(\n            ('x', schema.Scalar(blob=core.BlobReference('blob3'))),\n        )\n\n        with self.assertRaises(TypeError):\n            st = schema.Struct(\n                ('a', f_a),\n                ('b', f_b),\n                ('b:c:d', f_x),\n            )\n        with self.assertRaises(TypeError):\n            st = schema.Struct(\n                ('a', f_a),\n                ('b', f_b),\n                ('b:c:d:e', f_x),\n            )\n\n        st = schema.Struct(\n            ('a', f_a),\n            ('b', f_b),\n            ('e:f', f_x),\n        )\n        self.assertEqual(['a', 'b:c:d', 'e:f:x'], st.field_names())\n        self.assertEqual(['blob1', 'blob2', 'blob3'], st.field_blobs())\n\n        st = schema.Struct(\n            ('a', f_a),\n            ('b:c:e', f_x),\n            ('b', f_b),\n        )\n        self.assertEqual(['a', 'b:c:e:x', 'b:c:d'], st.field_names())\n        self.assertEqual(['blob1', 'blob3', 'blob2'], st.field_blobs())\n\n        st = schema.Struct(\n            ('a:a1', f_a),\n            ('b:b1', f_b),\n            ('a', f_x),\n        )\n        self.assertEqual(['a:a1', 'a:x', 'b:b1:c:d'], st.field_names())\n        self.assertEqual(['blob1', 'blob3', 'blob2'], st.field_blobs())\n\n    def testContains(self):\n        st = schema.Struct(\n            ('a', schema.Scalar()),\n            ('b', schema.Struct(\n                ('c', schema.Struct(\n                    ('d', schema.Scalar()),\n                )),\n            )),\n        )\n        self.assertTrue('a' in st)\n        self.assertTrue('b:c' in st)\n        self.assertTrue('b:c:d' in st)\n        self.assertFalse('' in st)\n        self.assertFalse('x' in st)\n        self.assertFalse('b:c:x' in st)\n        self.assertFalse('b:c:d:x' in st)\n\n    def testFromEmptyColumnList(self):\n        st = schema.Struct()\n        columns = st.field_names()\n        rec = schema.from_column_list(col_names=columns)\n        self.assertEqual(rec, schema.Struct())\n\n    def testFromColumnList(self):\n        st = schema.Struct(\n            ('a', schema.Scalar()),\n            ('b', schema.List(schema.Scalar())),\n            ('c', schema.Map(schema.Scalar(), schema.Scalar()))\n        )\n        columns = st.field_names()\n        # test that recovery works for arbitrary order\n        for _ in range(10):\n            some_blobs = [core.BlobReference('blob:' + x) for x in columns]\n            rec = schema.from_column_list(columns, col_blobs=some_blobs)\n            self.assertTrue(rec.has_blobs())\n            self.assertEqual(sorted(st.field_names()), sorted(rec.field_names()))\n            self.assertEqual([str(blob) for blob in rec.field_blobs()],\n                             [str('blob:' + name) for name in rec.field_names()])\n            random.shuffle(columns)\n\n    def testStructGet(self):\n        net = core.Net('test_net')\n        s1 = schema.NewRecord(net, schema.Scalar(np.float32))\n        s2 = schema.NewRecord(net, schema.Scalar(np.float32))\n        t = schema.Tuple(s1, s2)\n        assert t.get('field_0', None) == s1\n        assert t.get('field_1', None) == s2\n        assert t.get('field_2', None) is None\n"
  },
  {
    "path": "caffe2/python/scope.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package scope\n# Module caffe2.python.scope\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport contextlib\nimport threading\nfrom past.builtins import basestring\n\nfrom caffe2.proto import caffe2_pb2\n\n\n# The name scope and device scope when creating a new operator.\n_NAMESCOPE_SEPARATOR = '/'\n\n_threadlocal_scope = threading.local()\n\n\ndef CurrentNameScope():\n    global _threadlocal_scope\n    if not hasattr(_threadlocal_scope, \"namescope\"):\n        _threadlocal_scope.namescope = ''\n    return _threadlocal_scope.namescope\n\n\ndef CurrentDeviceScope():\n    global _threadlocal_scope\n    if not hasattr(_threadlocal_scope, \"devicescope\"):\n        _threadlocal_scope.devicescope = None\n    return _threadlocal_scope.devicescope\n\n\n@contextlib.contextmanager\ndef NameScope(prefix, reset=False):\n    global _threadlocal_scope\n    assert isinstance(prefix, basestring) or prefix is None, \\\n        \"NameScope takes in a string as its argument.\"\n    old_scope = CurrentNameScope()\n    prefix = prefix + _NAMESCOPE_SEPARATOR if prefix else ''\n    if reset:\n        _threadlocal_scope.namescope = prefix\n    else:\n        _threadlocal_scope.namescope = _threadlocal_scope.namescope + prefix\n\n    try:\n        yield\n    finally:\n        assert _threadlocal_scope.namescope.endswith(prefix), \\\n            \"The namescope variable is changed from outside NameScope() calls.\"\n        _threadlocal_scope.namescope = old_scope\n\n\n@contextlib.contextmanager\ndef DeviceScope(scope, node_name=None):\n    new_scope = caffe2_pb2.DeviceOption()\n    if scope:\n        assert isinstance(scope, caffe2_pb2.DeviceOption), \\\n            \"DeviceScope takes in a caffe2_pb2.DeviceOption as its argument.\"\n        new_scope.CopyFrom(scope)\n    else:\n        assert node_name, \"At least one argument should be non-null in DeviceScope\"\n\n    # rewrite node_name if it is explicitly given\n    if node_name:\n        new_scope.node_name = node_name\n    global _threadlocal_scope\n    old_scope = CurrentDeviceScope()\n    # nested scope should inherit the node_name if it is not explicitly set\n    if old_scope and old_scope.HasField('node_name') and \\\n            not new_scope.HasField('node_name'):\n        new_scope.node_name = old_scope.node_name\n    _threadlocal_scope.devicescope = new_scope\n    try:\n        yield\n    finally:\n        assert _threadlocal_scope.devicescope == new_scope, \\\n            \"The device scope is changed from outside DeviceScope() calls.\"\n        _threadlocal_scope.devicescope = old_scope\n\n\n@contextlib.contextmanager\ndef EmptyDeviceScope():\n    \"\"\"\n    Allow users to 'disable' the device scope behaviour (so it can be\n    controlled at a NetDef::DeviceOption level, not overridden at\n    OperatorDef::DeviceOption level).\n\n    This sets the CurrentDeviceScope() to None, so that the field is\n    not set in CreateOperator(...), etc.\n    \"\"\"\n    old_scope = CurrentDeviceScope()\n    try:\n        _threadlocal_scope.devicescope = None\n        yield\n    finally:\n        _threadlocal_scope.devicescope = old_scope\n        return\n"
  },
  {
    "path": "caffe2/python/scope_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import scope, core\nfrom caffe2.proto import caffe2_pb2\n\nimport unittest\nimport threading\nimport time\n\nSUCCESS_COUNT = 0\n\n\ndef thread_runner(idx, testobj):\n    global SUCCESS_COUNT\n    testobj.assertEquals(scope.CurrentNameScope(), \"\")\n    testobj.assertEquals(scope.CurrentDeviceScope(), None)\n    namescope = \"namescope_{}\".format(idx)\n    dsc = core.DeviceOption(caffe2_pb2.CUDA, idx)\n    with scope.DeviceScope(dsc):\n        with scope.NameScope(namescope):\n            testobj.assertEquals(scope.CurrentNameScope(), namescope + \"/\")\n            testobj.assertEquals(scope.CurrentDeviceScope(), dsc)\n\n            time.sleep(0.01 + idx * 0.01)\n            testobj.assertEquals(scope.CurrentNameScope(), namescope + \"/\")\n            testobj.assertEquals(scope.CurrentDeviceScope(), dsc)\n\n    testobj.assertEquals(scope.CurrentNameScope(), \"\")\n    testobj.assertEquals(scope.CurrentDeviceScope(), None)\n    SUCCESS_COUNT += 1\n\n\nclass TestScope(unittest.TestCase):\n\n    def testNamescopeBasic(self):\n        self.assertEquals(scope.CurrentNameScope(), \"\")\n\n        with scope.NameScope(\"test_scope\"):\n            self.assertEquals(scope.CurrentNameScope(), \"test_scope/\")\n\n        self.assertEquals(scope.CurrentNameScope(), \"\")\n\n    def testNamescopeAssertion(self):\n        self.assertEquals(scope.CurrentNameScope(), \"\")\n\n        try:\n            with scope.NameScope(\"test_scope\"):\n                self.assertEquals(scope.CurrentNameScope(), \"test_scope/\")\n                raise Exception()\n        except Exception:\n            pass\n\n        self.assertEquals(scope.CurrentNameScope(), \"\")\n\n    def testDevicescopeBasic(self):\n        self.assertEquals(scope.CurrentDeviceScope(), None)\n\n        dsc = core.DeviceOption(caffe2_pb2.CUDA, 9)\n        with scope.DeviceScope(dsc):\n            self.assertEquals(scope.CurrentDeviceScope(), dsc)\n\n        self.assertEquals(scope.CurrentDeviceScope(), None)\n\n    def testEmptyDevicescopeBasic(self):\n        self.assertEquals(scope.CurrentDeviceScope(), None)\n\n        dsc = core.DeviceOption(caffe2_pb2.CUDA, 9)\n        with scope.DeviceScope(dsc):\n            self.assertEquals(scope.CurrentDeviceScope(), dsc)\n            with scope.EmptyDeviceScope():\n                self.assertEquals(scope.CurrentDeviceScope(), None)\n            self.assertEquals(scope.CurrentDeviceScope(), dsc)\n        self.assertEquals(scope.CurrentDeviceScope(), None)\n\n    def testDevicescopeAssertion(self):\n        self.assertEquals(scope.CurrentDeviceScope(), None)\n\n        dsc = core.DeviceOption(caffe2_pb2.CUDA, 9)\n\n        try:\n            with scope.DeviceScope(dsc):\n                self.assertEquals(scope.CurrentDeviceScope(), dsc)\n                raise Exception()\n        except Exception:\n            pass\n\n        self.assertEquals(scope.CurrentDeviceScope(), None)\n\n    def testMultiThreaded(self):\n        \"\"\"\n        Test that name/device scope are properly local to the thread\n        and don't interfere\n        \"\"\"\n        global SUCCESS_COUNT\n        self.assertEquals(scope.CurrentNameScope(), \"\")\n        self.assertEquals(scope.CurrentDeviceScope(), None)\n\n        threads = []\n        for i in range(4):\n            threads.append(threading.Thread(\n                target=thread_runner,\n                args=(i, self),\n            ))\n        for t in threads:\n            t.start()\n\n        with scope.NameScope(\"master\"):\n            self.assertEquals(scope.CurrentDeviceScope(), None)\n            self.assertEquals(scope.CurrentNameScope(), \"master/\")\n            for t in threads:\n                t.join()\n\n            self.assertEquals(scope.CurrentNameScope(), \"master/\")\n            self.assertEquals(scope.CurrentDeviceScope(), None)\n\n        # Ensure all threads succeeded\n        self.assertEquals(SUCCESS_COUNT, 4)\n"
  },
  {
    "path": "caffe2/python/session.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package session\n# Module caffe2.python.session\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n\nfrom caffe2.python import core, workspace\nfrom caffe2.python.task import Cluster, Task, TaskGroup, WorkspaceType\n\n\nclass CompiledRunnable(object):\n    \"\"\" Wrapper for compiled runnable returned from session.compile() \"\"\"\n    def __init__(self, obj, session_class):\n        self.obj = obj\n        self.session_class = session_class\n\n\nclass Session(object):\n    \"\"\"\n    Allows to run Nets, ExecutionSteps, Plans, Tasks and TaskGroups.\n    A session can potentially run in multiple nodes concurrently.\n\n\n    Example:\n        from core import Net\n        from caffe2.python.task import Task, TaskGroup, WorkspaceType\n\n        net = Net('test1')\n        net.Add([net.Const(1), net.Const(2)])\n\n        net2 = net.Clone()\n        step = core.execution_step('step1', [net2])\n\n        with TaskGroup(WorkspaceType.GLOBAL) as init_tg:\n            with Node('node1'):\n                n1setup = net.Net('n1setup')\n                n1msg = n1setup.Const('Hello from node 1.')\n                Task(step=n1setup)\n\n        with TaskGroup() as private_tg:\n            with Node('node1'):\n                n1 = net.Net('n1')\n                n1.Print(n1msg, 0)\n                Task(step=n1)\n            with Node('node2'):\n                n2 = net.Net('n2')\n                n2.Print(n2.Const('Hello from node 2.'), 0)\n                Task(step=n2)\n\n        session = LocalSession()\n        session.run(net)\n        session.run(step)\n        session.run(init_tg)\n        session.run(private_tg)\n\n\n    Global Workspace:\n        At the beggining of the session, a global workspace is created and kept\n        alive for the duration of the session.\n\n\n    Private Workspace:\n        Tasks can be run either directly on the global workspace, or they can\n        instantiate a private child workspace that is released after each run.\n\n    Blob visibility:\n        Tasks running in different nodes in parallel will always run under\n        different workspaces, so it must be assumed that they won't be able to\n        access each other's blobs. Tasks running on the same node will follow\n        Workspace hierarchy rules: tasks running on separate private workspaces\n        will only be able to share blobs defined on a common parent Workspace.\n    \"\"\"\n\n    _compiled_cache = {}\n\n    def __init__(self):\n        self._open = True\n\n    def is_open(self):\n        return self._open\n\n    @classmethod\n    def compile(cls, runnable, workspace_type=None, setup_net_list=None):\n        if isinstance(runnable, CompiledRunnable):\n            assert cls == runnable.session_class, (\n                'Runnable was compiled for different session type. ' +\n                'Need: %s, got: %s' % (\n                    cls.__name__, runnable.session_class.__name__))\n            return runnable\n\n        if runnable in cls._compiled_cache:\n            return cls._compiled_cache[runnable]\n\n        if isinstance(runnable, TaskGroup):\n            if workspace_type:\n                if runnable.workspace_type():\n                    assert runnable.workspace_type() == workspace_type, \\\n                        \"Require {} but already have {}\".format(\n                            workspace_type, runnable.workspace_type())\n                else:\n                    runnable._workspace_type = workspace_type\n            tg = runnable\n        else:\n            if workspace_type is None:\n                workspace_type = WorkspaceType.GLOBAL\n            tg = TaskGroup(workspace_type=workspace_type)\n            if isinstance(runnable, Task):\n                tg.add(runnable)\n            elif isinstance(runnable, core.ExecutionStep):\n                tg.add(Task(step=runnable))\n            elif isinstance(runnable, core.Plan):\n                # ExecutionSteps in Plan() object is supposed to run sequentially, while\n                # tasks in TaskGroup run in parallel. So if we have multiple\n                # ExecutionSteps in Plan() object, we choose to have a root\n                # ExecutionStep to wrap all ExecutionSteps.\n                assert len(runnable.Steps()) > 0\n                if len(runnable.Steps()) == 1:\n                    tg.add(Task(step=runnable.Steps()[0]))\n                else:\n                    # Task takes a list of ExecutionSteps and automatically wrap into\n                    # a root ExecutionStep\n                    tg.add(Task(step=runnable.Steps()))\n            else:\n                step = core.execution_step('runnable', runnable)\n                tg.add(Task(step=step))\n        compiled = CompiledRunnable(\n            cls._compile_task_group(tg, setup_net_list), session_class=cls)\n        cls._compiled_cache[runnable] = compiled\n        return compiled\n\n    def run(self, runnable, workspace_type=None, setup_net_list=None):\n        \"\"\"Run the given runnable.\n\n        Args:\n            runnable: Object recognized by the Session. Currently, we support\n                TaskGroup, Task, Plan, ExecutionStep, and Net.\n            workspace_type: A string defined in the WorkspaceType object.\n            setup_net_list: A list of Net objects or a list of NetDef protos.\n                So far this is only used by the DistributedSession, in which we\n                need to pass a list of special nets to setup the master.\n        \"\"\"\n        assert self.is_open(), 'Session is closed.'\n        assert runnable is not None, 'Got a none runnable.'\n        self._run_compiled(self.compile(runnable, workspace_type,\n                                        setup_net_list).obj)\n\n    def close(self):\n        if self.is_open():\n            self._do_close()\n            self._open = False\n\n    def fetch_output(self, output):\n        raise NotImplementedError()\n\n    def _run_compiled(self, task_group):\n        raise NotImplementedError()\n\n    @classmethod\n    def _compile_task_group(cls, task_group, setup_net_list=None):\n        return task_group\n\n    def _do_close(self):\n        pass\n\n    def __enter__(self):\n        assert self._open, 'Session already closed.'\n        return self\n\n    def __exit__(self, ex_type, value, traceback):\n        if ex_type is None:\n            self.close()\n\n\nclass LocalSession(Session):\n    \"\"\"\n    Session that runs in a single node.\n    Tasks are all remapped to run in parallel in the 'local' node.\n\n    Currently, LocalSession runs all parallel tasks in the same workspace,\n    but this behavior may change in the future. Only tasks pointing to the\n    same logical node are guaranteed to always run in the same workspace.\n    \"\"\"\n    def __init__(self, ws=None):\n        Session.__init__(self)\n        self._ws = ws or workspace.C.Workspace.current\n\n    @classmethod\n    def _compile_task_group(cls, task_group, setup_net_list=None):\n        with Cluster():\n            task = task_group.to_task()\n        plan = core.Plan('task_group_plan')\n        plan.AddStep(task.get_step())\n        return (plan, task.output_list(), task.workspace_type)\n\n    def _run_compiled(self, compiled):\n        plan, output_list, workspace_type = compiled\n\n        # make sure the output blobs belong to the parent workspace\n        outputs = []\n        for name in output_list.names():\n            self._ws.create_blob(str(name))\n            outputs.append(core.BlobReference(str(name)))\n        output_list.set_values(outputs, _fetch_func=self._fetch_output)\n        task_ws = (\n            workspace.C.Workspace(self._ws)\n            if workspace_type == WorkspaceType.PRIVATE else self._ws)\n        with workspace.WorkspaceGuard(task_ws):\n            task_ws.run(plan)\n\n    def _fetch_output(self, output):\n        return self._ws.blobs[str(output)].fetch()\n"
  },
  {
    "path": "caffe2/python/session_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python.schema import (\n    Struct, FetchRecord, NewRecord, FeedRecord, InitEmptyRecord)\nfrom caffe2.python import core, workspace\nfrom caffe2.python.session import LocalSession\nfrom caffe2.python.dataset import Dataset\nfrom caffe2.python.pipeline import pipe\nfrom caffe2.python.task import TaskGroup\nfrom caffe2.python.test_util import TestCase\nimport numpy as np\n\n\nclass TestLocalSession(TestCase):\n    def test_local_session(self):\n        init_net = core.Net('init')\n        src_values = Struct(\n            ('uid', np.array([1, 2, 6])),\n            ('value', np.array([1.4, 1.6, 1.7])))\n        expected_dst = Struct(\n            ('uid', np.array([2, 4, 12])),\n            ('value', np.array([0.0, 0.0, 0.0])))\n\n        with core.NameScope('init'):\n            src_blobs = NewRecord(init_net, src_values)\n            dst_blobs = InitEmptyRecord(init_net, src_values.clone_schema())\n\n        def proc1(rec):\n            net = core.Net('proc1')\n            with core.NameScope('proc1'):\n                out = NewRecord(net, rec)\n            net.Add([rec.uid(), rec.uid()], [out.uid()])\n            out.value.set(blob=rec.value(), unsafe=True)\n            return [net], out\n\n        def proc2(rec):\n            net = core.Net('proc2')\n            with core.NameScope('proc2'):\n                out = NewRecord(net, rec)\n            out.uid.set(blob=rec.uid(), unsafe=True)\n            net.Sub([rec.value(), rec.value()], [out.value()])\n            return [net], out\n\n        src_ds = Dataset(src_blobs)\n        dst_ds = Dataset(dst_blobs)\n\n        with TaskGroup() as tg:\n            out1 = pipe(src_ds.reader(), processor=proc1)\n            out2 = pipe(out1, processor=proc2)\n            pipe(out2, dst_ds.writer())\n\n        ws = workspace.C.Workspace()\n        FeedRecord(src_blobs, src_values, ws)\n        session = LocalSession(ws)\n        session.run(init_net)\n        session.run(tg)\n        output = FetchRecord(dst_blobs, ws=ws)\n\n        for a, b in zip(output.field_blobs(), expected_dst.field_blobs()):\n            np.testing.assert_array_equal(a, b)\n"
  },
  {
    "path": "caffe2/python/sparse_to_dense_mask_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\n\nimport numpy as np\n\n\nclass TestSparseToDenseMask(TestCase):\n\n    def test_sparse_to_dense_mask_float(self):\n        op = core.CreateOperator(\n            'SparseToDenseMask',\n            ['indices', 'values', 'default', 'lengths'],\n            ['output'],\n            mask=[999999999, 2, 6])\n        workspace.FeedBlob(\n            'indices',\n            np.array([2, 4, 6, 1, 2, 999999999, 2], dtype=np.int32))\n        workspace.FeedBlob(\n            'values',\n            np.array([1, 2, 3, 4, 5, 6, 7], dtype=np.float))\n        workspace.FeedBlob('default', np.array(-1, dtype=np.float))\n        workspace.FeedBlob('lengths', np.array([3, 4], dtype=np.int32))\n        workspace.RunOperatorOnce(op)\n        output = workspace.FetchBlob('output')\n        expected = np.array([[-1, 1, 3], [6, 7, -1]], dtype=np.float)\n        self.assertEqual(output.shape, expected.shape)\n        np.testing.assert_array_equal(output, expected)\n\n    def test_sparse_to_dense_mask_invalid_inputs(self):\n        op = core.CreateOperator(\n            'SparseToDenseMask',\n            ['indices', 'values', 'default', 'lengths'],\n            ['output'],\n            mask=[999999999, 2])\n        workspace.FeedBlob(\n            'indices',\n            np.array([2000000000000, 999999999, 2, 3, 4, 5], dtype=np.int32))\n        workspace.FeedBlob(\n            'values',\n            np.array([1, 2, 3, 4, 5, 6], dtype=np.float))\n        workspace.FeedBlob('default', np.array(-1, dtype=np.float))\n        workspace.FeedBlob('lengths', np.array([6], dtype=np.int32))\n        try:\n            workspace.RunOperatorOnce(op)\n        except RuntimeError:\n            self.fail(\"Exception raised with only one negative index\")\n        workspace.FeedBlob(\n            'indices',\n            np.array([2000000000000, 999999999, -2, -3, -4, -5], dtype=np.int32))\n        with self.assertRaises(RuntimeError):\n            workspace.RunOperatorOnce(op)\n\n    def test_sparse_to_dense_mask_subtensor(self):\n        op = core.CreateOperator(\n            'SparseToDenseMask',\n            ['indices', 'values', 'default', 'lengths'],\n            ['output'],\n            mask=[999999999, 2, 888, 6])\n        workspace.FeedBlob(\n            'indices',\n            np.array([2, 4, 6, 999999999, 2], dtype=np.int64))\n        workspace.FeedBlob(\n            'values',\n            np.array([[[1, -1]], [[2, -2]], [[3, -3]], [[4, -4]], [[5, -5]]],\n                     dtype=np.float))\n        workspace.FeedBlob('default', np.array([[-1, 0]], dtype=np.float))\n        workspace.FeedBlob('lengths', np.array([2, 3], dtype=np.int32))\n        workspace.RunOperatorOnce(op)\n        output = workspace.FetchBlob('output')\n        expected = np.array([\n            [[[-1, 0]], [[1, -1]], [[-1, 0]], [[-1, 0]]],\n            [[[4, -4]], [[5, -5]], [[-1, 0]], [[3, -3]]]], dtype=np.float)\n        self.assertEqual(output.shape, expected.shape)\n        np.testing.assert_array_equal(output, expected)\n\n    def test_sparse_to_dense_mask_string(self):\n        op = core.CreateOperator(\n            'SparseToDenseMask',\n            ['indices', 'values', 'default', 'lengths'],\n            ['output'],\n            mask=[999999999, 2, 6])\n        workspace.FeedBlob(\n            'indices',\n            np.array([2, 4, 6, 1, 2, 999999999, 2], dtype=np.int32))\n        workspace.FeedBlob(\n            'values',\n            np.array(['1', '2', '3', '4', '5', '6', '7'], dtype='S'))\n        workspace.FeedBlob('default', np.array('-1', dtype='S'))\n        workspace.FeedBlob('lengths', np.array([3, 4], dtype=np.int32))\n        workspace.RunOperatorOnce(op)\n        output = workspace.FetchBlob('output')\n        expected =\\\n            np.array([['-1', '1', '3'], ['6', '7', '-1']], dtype='S')\n        self.assertEqual(output.shape, expected.shape)\n        np.testing.assert_array_equal(output, expected)\n\n    def test_sparse_to_dense_mask_empty_lengths(self):\n        op = core.CreateOperator(\n            'SparseToDenseMask',\n            ['indices', 'values', 'default'],\n            ['output'],\n            mask=[1, 2, 6])\n        workspace.FeedBlob('indices', np.array([2, 4, 6], dtype=np.int32))\n        workspace.FeedBlob('values', np.array([1, 2, 3], dtype=np.float))\n        workspace.FeedBlob('default', np.array(-1, dtype=np.float))\n        workspace.RunOperatorOnce(op)\n        output = workspace.FetchBlob('output')\n        expected = np.array([-1, 1, 3], dtype=np.float)\n        self.assertEqual(output.shape, expected.shape)\n        np.testing.assert_array_equal(output, expected)\n\n    def test_sparse_to_dense_mask_no_lengths(self):\n        op = core.CreateOperator(\n            'SparseToDenseMask',\n            ['indices', 'values', 'default'],\n            ['output'],\n            mask=[1, 2, 6])\n        workspace.FeedBlob('indices', np.array([2, 4, 6], dtype=np.int32))\n        workspace.FeedBlob('values', np.array([1, 2, 3], dtype=np.float))\n        workspace.FeedBlob('default', np.array(-1, dtype=np.float))\n        workspace.RunOperatorOnce(op)\n        output = workspace.FetchBlob('output')\n        expected = np.array([-1, 1, 3], dtype=np.float)\n        self.assertEqual(output.shape, expected.shape)\n        np.testing.assert_array_equal(output, expected)\n\n    def test_sparse_to_dense_mask_presence_mask(self):\n        op = core.CreateOperator(\n            'SparseToDenseMask',\n            ['indices', 'values', 'default', 'lengths'],\n            ['output', 'presence_mask'],\n            mask=[11, 12],\n            return_presence_mask=True)\n        workspace.FeedBlob('indices', np.array([11, 12, 13], dtype=np.int32))\n        workspace.FeedBlob('values', np.array([11, 12, 13], dtype=np.float))\n        workspace.FeedBlob('default', np.array(-1, dtype=np.float))\n        workspace.FeedBlob('lengths', np.array([1, 2], dtype=np.int32))\n\n        workspace.RunOperatorOnce(op)\n\n        output = workspace.FetchBlob('output')\n        presence_mask = workspace.FetchBlob('presence_mask')\n        expected_output = np.array([[11, -1], [-1, 12]], dtype=np.float)\n        expected_presence_mask = np.array(\n            [[True, False], [False, True]],\n            dtype=np.bool)\n        self.assertEqual(output.shape, expected_output.shape)\n        np.testing.assert_array_equal(output, expected_output)\n        self.assertEqual(presence_mask.shape, expected_presence_mask.shape)\n        np.testing.assert_array_equal(presence_mask, expected_presence_mask)\n"
  },
  {
    "path": "caffe2/python/sparse_to_dense_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\n\nimport numpy as np\n\n\nclass TestSparseToDense(TestCase):\n    def test_sparse_to_dense(self):\n        op = core.CreateOperator(\n            'SparseToDense',\n            ['indices', 'values'],\n            ['output'])\n        workspace.FeedBlob(\n            'indices',\n            np.array([2, 4, 999, 2], dtype=np.int32))\n        workspace.FeedBlob(\n            'values',\n            np.array([1, 2, 6, 7], dtype=np.int32))\n\n        workspace.RunOperatorOnce(op)\n        output = workspace.FetchBlob('output')\n        print(output)\n\n        expected = np.zeros(1000, dtype=np.int32)\n        expected[2] = 1 + 7\n        expected[4] = 2\n        expected[999] = 6\n\n        self.assertEqual(output.shape, expected.shape)\n        np.testing.assert_array_equal(output, expected)\n\n    def test_sparse_to_dense_invalid_inputs(self):\n        op = core.CreateOperator(\n            'SparseToDense',\n            ['indices', 'values'],\n            ['output'])\n        workspace.FeedBlob(\n            'indices',\n            np.array([2, 4, 999, 2], dtype=np.int32))\n        workspace.FeedBlob(\n            'values',\n            np.array([1, 2, 6], dtype=np.int32))\n\n        with self.assertRaises(RuntimeError):\n            workspace.RunOperatorOnce(op)\n\n    def test_sparse_to_dense_with_data_to_infer_dim(self):\n        op = core.CreateOperator(\n            'SparseToDense',\n            ['indices', 'values', 'data_to_infer_dim'],\n            ['output'])\n        workspace.FeedBlob(\n            'indices',\n            np.array([2, 4, 999, 2], dtype=np.int32))\n        workspace.FeedBlob(\n            'values',\n            np.array([1, 2, 6, 7], dtype=np.int32))\n        workspace.FeedBlob(\n            'data_to_infer_dim',\n            np.array(np.zeros(1500, ), dtype=np.int32))\n\n        workspace.RunOperatorOnce(op)\n        output = workspace.FetchBlob('output')\n        print(output)\n\n        expected = np.zeros(1500, dtype=np.int32)\n        expected[2] = 1 + 7\n        expected[4] = 2\n        expected[999] = 6\n\n        self.assertEqual(output.shape, expected.shape)\n        np.testing.assert_array_equal(output, expected)\n"
  },
  {
    "path": "caffe2/python/task.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package task\n# Module caffe2.python.task\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import core, context\nfrom caffe2.python.schema import Field, from_blob_list\nfrom collections import defaultdict\nfrom copy import copy\nfrom future.utils import viewitems\n\n\ndef _merge_node_kwargs(a, b):\n    # TODO(azzolini): consistency checks\n    if a is None:\n        return b\n    if b is None:\n        return a\n    c = copy(a)\n    c.update(b)\n    return c\n\n\n@context.define_context(allow_default=True)\nclass Cluster(object):\n    \"\"\"\n    Context that keeps track of all the node names used.\n    Users shouldn't have to use them directly, since a Cluster is automatically\n    generated at the first usage of 'Node'.\n    \"\"\"\n\n    def __init__(self):\n        # list instead of set to keep order\n        self._nodes = []\n        self._node_kwargs = {}\n\n    def add_node(self, node):\n        if str(node) not in self._nodes:\n            self._nodes.append(str(node))\n        self._node_kwargs[str(node)] = _merge_node_kwargs(\n            node.kwargs(),\n            self._node_kwargs.get(str(node)))\n\n    def nodes(self):\n        \"\"\"\n        Returns the list of unique node names used within this context.\n        \"\"\"\n        return self._nodes\n\n    def node_kwargs(self):\n        return self._node_kwargs\n\n\n@context.define_context(allow_default=True)\nclass Node(object):\n    \"\"\"\n    A Node context is used to indicate that all Tasks instantiated within will\n    run on the given node name. (Only the name of the node actually counts.)\n    Example:\n\n        with TaskGroup() as tg:\n            with Node('node1'):\n                s1 = execution_step(...)\n                Task(step=s1)\n            with Node('node2'):\n                s2 = execution_step(...)\n            with Node('node1'):\n                s3 = execution_step(...)\n\n        In this example, all three execution steps will run in parallel.\n        Moreover, s1 and s3 will run on the same node, and can see each\n        others blobs.\n\n        Additionally, a Node can be passed implementation-specific kwargs,\n        in order to specify properties of the node.\n    \"\"\"\n\n    def __init__(self, node='local', **kwargs):\n        self._name = str(node)\n        self._kwargs = kwargs\n        Cluster.current().add_node(self)\n\n    def __str__(self):\n        return self._name\n\n    def kwargs(self):\n        return self._kwargs\n\n\nclass WorkspaceType(object):\n    \"\"\"\n    Determines whether tasks of a TaskGroup will run directly at the global\n    workspace, which is kept alive across runs, or whether a new child\n    workspace will be created for the run and destroyed afterwards.\n    \"\"\"\n    PRIVATE = 'private'\n    GLOBAL = 'global'\n\n\ndef get_setup_nets(key, steps_or_nets, target):\n    init_net = core.Net(key + '/init')\n    exit_net = core.Net(key + '/exit')\n    init_nets = []\n    exit_nets = []\n    objs = []\n    for step_or_net in steps_or_nets:\n        if hasattr(step_or_net, 'get_all_attributes'):\n            objs += step_or_net.get_all_attributes(key)\n        elif hasattr(step_or_net, 'get_attributes'):\n            objs += step_or_net.get_attributes(key)\n    for obj in objs:\n        # these are needed in order to allow nesting of TaskGroup, which\n        # is a feature not yet implemented.\n        if hasattr(obj, '_setup_used') and obj._setup_used:\n            continue\n        if hasattr(obj, '_setup_target') and obj._setup_target != target:\n            continue\n        if hasattr(obj, 'setup'):\n            nets = obj.setup(init_net)\n            if isinstance(nets, (list, tuple)):\n                init_nets += nets\n            elif isinstance(nets, (core.Net, core.ExecutionStep)):\n                init_nets.append(nets)\n            elif nets is not None:\n                raise TypeError('Unsupported type for setup: %s' % type(nets))\n            obj._setup_used = True\n        if hasattr(obj, 'exit'):\n            nets = obj.exit(exit_net)\n            if isinstance(nets, (list, tuple)):\n                exit_nets += nets\n            elif isinstance(nets, (core.Net, core.ExecutionStep)):\n                exit_nets.append(nets)\n            elif nets is not None:\n                raise TypeError('Unsupported type for setup: %s' % type(nets))\n            obj._setup_used = True\n\n    if len(init_net.Proto().op) > 0:\n        init_nets.insert(0, init_net)\n    if len(exit_net.Proto().op) > 0:\n        exit_nets.insert(0, exit_net)\n    return init_nets, exit_nets\n\n\ndef add_setup_steps(step, init_nets, exit_nets, name):\n    if not init_nets and not exit_nets:\n        return step\n    steps = []\n    if init_nets:\n        steps.append(core.execution_step('%s:init' % name, init_nets))\n    steps.append(step)\n    if len(exit_nets) > 0:\n        steps.append(core.execution_step('%s:exit' % name, exit_nets))\n    return core.execution_step(name, steps)\n\n\n@context.define_context(allow_default=False)\nclass TaskGroup(object):\n    \"\"\"\n    Context that gathers tasks which will run concurrently, potentially on\n    multiple nodes. All tasks in the same node will share the same workspace\n    and thus can share blobs, while tasks running in different nodes won't\n    be able to directly share data.\n\n    All tasks of the task group will start concurrently, and the task group\n    will finish execution when the last task of the group finishes.\n\n    Example:\n        # supose that s1 ... s5 are execution steps or nets.\n        with TaskGroup() as tg:\n            # these tasks go to default node 'local'\n            Task(step=s1)\n            Task(step=s2)\n\n            with Node('n2'):\n                Task(step=s3)\n            with Node('n1'):\n                Task(step=s4)\n            with Node('n2'):\n                Task(step=s5)\n\n        # this will run all steps in parallel.\n        # s1 and s2 will run at default node 'local'\n        # s3 and s5 will run at node 'n2'\n        # s4 will run at node 'n1'\n        session.run(tg)\n    \"\"\"\n    LOCAL_SETUP = 'local_setup'\n\n    def __init__(self, workspace_type=None):\n        self._plan_cache = None\n        self._tasks = []\n        self._already_used = False\n        self._prev_active = None\n        self._tasks_to_add = []\n        self._report_nets = {}\n        self._report_steps = []\n        self._workspace_type = workspace_type\n        self._tasks_by_node = None\n\n    def add(self, task):\n        assert not self._already_used, (\n            'Cannot add Task to an already used TaskGroup.')\n        assert (\n            self._workspace_type is None or\n            task._workspace_type is None or\n            self._workspace_type == task._workspace_type)\n        if task._workspace_type is None:\n            task._workspace_type = (\n                self._workspace_type or WorkspaceType.PRIVATE)\n        if self._workspace_type is None:\n            self._workspace_type = task._workspace_type\n        task._notify_used()\n        self._tasks.append(task)\n\n    def tasks(self):\n        for task in self._tasks_to_add:\n            self.add(task)\n        self._tasks_to_add = []\n        self._already_used = True\n        return self._tasks\n\n    def num_registered_tasks(self):\n        return len(self._tasks_to_add) + len(self._tasks)\n\n    def used_nodes(self):\n        # use list to keep order\n        used = []\n        for task in self._tasks + self._tasks_to_add:\n            if task.node not in used:\n                used.append(task.node)\n        return used\n\n    def report_step(self, step=None, node=None, interval_ms=1000):\n        \"\"\"\n        Add a \"report step\" to this TaskGroup. This step will run repeatedly\n        every `interval_ms` milliseconds for the duration of the TaskGroup\n        execution on each of the nodes. It is guaranteed that this step\n        will be run at least once after every Task in the node has finished.\n        \"\"\"\n        step = core.to_execution_step(step)\n        step.RunEveryMillis(interval_ms)\n        self._report_steps.append((str(node or Node.current(node)), step))\n\n    def report_net(self, net=None, node=None, report_interval=5):\n        \"\"\"\n        DEPRECATED. Use report_step instead.\n        \"\"\"\n        node = str(node or Node.current(node))\n        assert net is None or node not in self._report_nets\n        if node not in self._report_nets:\n            self._report_nets[node] = (\n                net if net else core.Net('%s/reporter' % node),\n                report_interval)\n        return self._report_nets[node][0]\n\n    def tasks_by_node(self, node_remap=None):\n        # tasks_by_node can't be called twice because the setup won't\n        # work properly a second time.\n        node_map = {}\n        for task in self.tasks():\n            node_map[task.node] =\\\n                node_remap(task.node) if node_remap else task.node\n        if self._tasks_by_node is not None:\n            tasks_by_node, prev_node_map = self._tasks_by_node\n            assert prev_node_map == node_map, (\n                'Cannot call tasks_by_node multiple times.')\n            return tasks_by_node\n\n        # now we have report_steps. report_net is deprecated\n        for node, (net, interval) in viewitems(self._report_nets):\n            self.report_step(net, node=node, interval_ms=interval * 1000)\n        self._report_nets = {}\n\n        tasks_by_node = defaultdict(list)\n        for task in self.tasks():\n            mapped_node = node_map[task.node]\n            tasks_by_node[mapped_node].append(task)\n\n        report_steps_by_node = defaultdict(list)\n        for original_node, step in self._report_steps:\n            report_steps_by_node[node_map[original_node]].append(step)\n\n        grouped_by_node = TaskGroup()\n        for node, tasks in viewitems(tasks_by_node):\n            report_steps = report_steps_by_node[node]\n            node_inits, node_exits = get_setup_nets(\n                TaskGroup.LOCAL_SETUP,\n                [t.get_step() for t in tasks] + report_steps,\n                self)\n            # shortcut for single task with no queue\n            steps = report_steps\n            outputs = []\n            grouped_workspace_type = WorkspaceType.PRIVATE\n            for task in tasks:\n                step = task.get_step()\n                step.SetCreateWorkspace(\n                    task.workspace_type() == WorkspaceType.PRIVATE)\n                if step is not None:\n                    steps.append(step)\n                outputs += task.outputs()\n                # If any of the tasks in the node uses the global workspace,\n                # then set the grouped task to use the global workspace as well\n                if task.workspace_type() == WorkspaceType.GLOBAL:\n                    grouped_workspace_type = WorkspaceType.GLOBAL\n            if len(steps) == 0:\n                steps.append(core.execution_step('empty', []))\n            if len(steps) == 1:\n                step = steps[0]\n            else:\n                step = core.execution_step(\n                    '%s:body' % node, steps, concurrent_substeps=True)\n            if len(node_inits) > 0 or len(node_exits) > 0:\n                steps = []\n                if len(node_inits) > 0:\n                    steps.append(\n                        core.execution_step('%s:init' % node, node_inits))\n                steps.append(step)\n                if len(node_exits) > 0:\n                    steps.append(\n                        core.execution_step('%s:exit' % node, node_exits))\n                step = core.execution_step(node, steps)\n            Task(\n                node=node, step=step, outputs=outputs,\n                name='grouped_by_node',\n                group=grouped_by_node, workspace_type=grouped_workspace_type)\n        self._tasks_by_node = (grouped_by_node, node_map)\n        return grouped_by_node\n\n    def to_task(self, node=None):\n        node = str(Node.current(node))\n        tasks = self.tasks_by_node(lambda x: node).tasks()\n        if len(tasks) == 0:\n            return Task()\n        return tasks[0]\n\n    def workspace_type(self):\n        return self._workspace_type\n\n\nclass TaskOutput(object):\n    \"\"\"\n    Represents the output of a task. An output can be a blob,\n    a list of blob, or a record.\n    \"\"\"\n\n    def __init__(self, names):\n        self._schema = None\n        self._is_scalar = False\n        if isinstance(names, Field):\n            self._schema = names\n            names = self._schema.field_blobs()\n        self._is_scalar = type(names) not in (tuple, list)\n        if self._is_scalar:\n            names = [names]\n        self.names = names\n        self._values = None\n\n    def set(self, values, _fetch_func=None):\n        assert len(values) == len(self.names)\n        self._values = values\n        self._fetch_func = _fetch_func\n\n    def get(self):\n        assert self._values is not None, 'Output value not set yet.'\n        if self._is_scalar:\n            return self._values[0]\n        elif self._schema:\n            return from_blob_list(self._schema, self._values)\n        else:\n            return self._values\n\n    def fetch(self):\n        assert self._fetch_func is not None, (\n            'Cannot fetch value for this output.')\n        fetched_vals = [self._fetch_func(v) for v in self._values]\n        if self._is_scalar:\n            return fetched_vals[0]\n        elif self._schema:\n            return from_blob_list(self._schema, fetched_vals)\n        else:\n            return fetched_vals\n\n\ndef final_output(blob_or_record):\n    \"\"\"\n    Adds an output to the current Task, or if no task is active,\n    create a dummy task that returns the given blob or record\n    to the client. This will return the value of the blob or record when\n    the last task of the TaskGroup for a given node finishes.\n    \"\"\"\n    cur_task = Task.current(required=False) or Task()\n    return cur_task.add_output(blob_or_record)\n\n\nclass TaskOutputList(object):\n    \"\"\" Keeps a list of outputs for a task \"\"\"\n    def __init__(self, outputs=None):\n        self.outputs = outputs or []\n\n    def names(self):\n        \"\"\"\n        Retrive the output names.\n        TODO(azzolini): make this schema-based.\n        \"\"\"\n        names = []\n        for o in self.outputs:\n            names += o.names\n        return names\n\n    def set_values(self, values, _fetch_func=None):\n        offset = 0\n        for o in self.outputs:\n            num = len(o.names)\n            o.set(values[offset:offset + num], _fetch_func)\n            offset += num\n        assert offset == len(values), 'Wrong number of output values.'\n\n\n@context.define_context()\nclass Task(object):\n    \"\"\"\n    A Task is composed of an execution step and zero or more outputs.\n    Tasks are executed in the context of a TaskGroup, which, in turn, can\n    be run by a Session.\n\n    Task outputs are fetched by the session at the end of the run.\n\n    The recommended way of creating a task is by using `net_builder.ops`.\n    Example:\n\n        from net_builder import ops\n        with Node('trainer'), Task(name='my_task', num_instances=2):\n            with ops.task_init():\n                globl = ops.Const(0)\n            with ops.task_instance_init():\n                local = ops.Const(0)\n            with ops.loop(100):\n                ops.Copy(globl, local)\n            with ops.task_instance_exit():\n                ops.Add([globl, local], [globl])\n            with ops.task_exit():\n                ops.Mul([globl, globl], [blobl])\n\n    The task above will create 2 instances that will run in parallel.\n    Each instance will copy `local` to `globl` 100 times, Then Add `local`\n    to `globl` once. The `Mul` will only execute once, after all the instances\n    of the task have finished.\n    \"\"\"\n\n    # TASK_SETUP runs once per task, before/after all\n    # concurrent task instances start/finish.\n    TASK_SETUP = 'task_setup'\n    # Setup will run once for each instance of the task.\n    TASK_INSTANCE_SETUP = 'task_instance_setup'\n    REPORT_STEP = 'report_step'\n    _global_names_used = set()\n\n    @staticmethod\n    def _get_next_name(node, group, name):\n        basename = str(node) + '/' + str(name)\n        names_used = (\n            Task._global_names_used\n            if group is None else\n            set(t.name for t in group._tasks_to_add))\n        cur_name = basename\n        i = 0\n        while cur_name in names_used:\n            i += 1\n            cur_name = '%s:%d' % (basename, i)\n        return cur_name\n\n    def __init__(\n            self, step=None, outputs=None,\n            workspace_type=None, group=None, node=None, name=None,\n            num_instances=None):\n        \"\"\"\n        Instantiate a Task and add it to the current TaskGroup and Node.\n\n        Args:\n           step:    If provided, this task will run this ExecutionStep.\n           outputs: If provided, the task will return the provided outputs\n                    to the client at completion time.\n           node:    If provided, force task execution on the given node.\n           name:    Name of the Task.\n           num_instances: If provided, this task will be cloned num_instances\n                          times at runtime, and all instances will run\n                          concurrently.\n        \"\"\"\n        if not name and isinstance(step, core.ExecutionStep):\n            name = step.Proto().name\n        if not name:\n            name = 'task'\n        # register this node name with active context\n        self.node = str(Node.current(None if node is None else Node(node)))\n        self.group = TaskGroup.current(group, required=False)\n\n        self.name = Task._get_next_name(self.node, self.group, name)\n\n        # may need to be temporarily removed later if Task used as a context\n        if self.group is not None:\n            self.group._tasks_to_add.append(self)\n\n        self._already_used = False\n        self._step = None\n        self._step_with_setup = None\n        self._outputs = []\n        if step is not None:\n            self.set_step(step)\n        if outputs is not None:\n            self.add_outputs(outputs)\n\n        self._pipeline = None\n        self._is_pipeline_context = False\n        self._workspace_type = workspace_type\n        self._report_net = None\n        self._num_instances = num_instances\n\n    def __enter__(self):\n        # temporarily remove from _tasks_to_add to ensure correct order\n        if self.group is not None:\n            self.group._tasks_to_add.remove(self)\n        self._assert_not_used()\n        assert self._step is None, 'This Task already has an execution step.'\n        from caffe2.python import net_builder\n        self._net_builder = net_builder.NetBuilder(_fullname=self.name)\n        self._net_builder.__enter__()\n        return self\n\n    def __exit__(self, type, value, traceback):\n        self._net_builder.__exit__(type, value, traceback)\n        if type is None:\n            self.set_step(self._net_builder)\n        if self.group is not None:\n            self.group._tasks_to_add.append(self)\n        self._net_builder = None\n\n    def workspace_type(self):\n        return self._workspace_type\n\n    def _assert_not_used(self):\n        assert not self._already_used, (\n            'Cannot modify task since it is already been used.')\n\n    def add_output(self, output):\n        self._assert_not_used()\n        output = (\n            output if isinstance(output, TaskOutput) else TaskOutput(output))\n        self._outputs.append(output)\n        return output\n\n    def add_outputs(self, outputs):\n        self._assert_not_used()\n        if type(outputs) not in (list, tuple):\n            return self.add_output(outputs)\n        else:\n            return [self.add_output(output) for output in outputs]\n\n    def set_step(self, step):\n        self._assert_not_used()\n        self._step = core.to_execution_step(step)\n\n    def get_step(self):\n        if self._step_with_setup is not None:\n            return self._step_with_setup\n\n        if self._step is None:\n            self._step_with_setup = core.execution_step(self.name, [])\n            return self._step_with_setup\n\n        report_steps = [\n            s\n            for s in self._step.get_all_attributes(Task.REPORT_STEP)\n            if not hasattr(s, '_report_step_used')\n        ]\n        for step in report_steps:\n            step._report_step_used = True\n            if not step.Proto().run_every_ms:\n                step.RunEveryMillis(1000)\n        task_init_nets, task_exit_nets = get_setup_nets(\n            Task.TASK_SETUP, [self._step] + report_steps, self)\n        instance_init_nets, instance_exit_nets = get_setup_nets(\n            Task.TASK_INSTANCE_SETUP, [self._step] + report_steps, self)\n        if len(self._outputs) == 0:\n            output_net = core.Net('%s:output' % self.name)\n            self.add_output(output_net.ConstantFill(\n                [], 1, dtype=core.DataType.INT32, value=0))\n            task_exit_nets.append(output_net)\n\n        # Add instance-level report steps\n        body = self._step if not report_steps else core.execution_step(\n            '%s:body' % self.name, report_steps + [self._step])\n        # Enclose with instance-level (thread-local) setup nets\n        step_with_instance_setup = add_setup_steps(\n            body, instance_init_nets, instance_exit_nets,\n            self.name + ':instance')\n        # Set up runtime concurrent instances\n        if self._num_instances and self._num_instances > 1:\n            step_with_instance_setup.SetCreateWorkspace(True)\n            step_with_instance_setup = core.execution_step(\n                '%s:parallel',\n                [step_with_instance_setup],\n                num_concurrent_instances=self._num_instances)\n        # Enclose with task-level setup nets\n        self._step_with_setup = add_setup_steps(\n            step_with_instance_setup, task_init_nets, task_exit_nets, self.name)\n\n        return self._step_with_setup\n\n    def output_list(self):\n        return TaskOutputList(self._outputs)\n\n    def outputs(self):\n        return self._outputs\n\n    def _notify_used(self):\n        self.get_step()\n        self._already_used = True\n\n\nclass SetupNets(object):\n    \"\"\"\n    Allow to register a list of nets to be run at initialization\n    and finalization of Tasks or TaskGroups.\n    For example, let's say you have the following:\n\n        init_net = core.Net('init')\n        my_val = init_net.ConstantFill([], 'my_val', value=0)\n\n        net = core.Net('counter')\n        net.Add([my_val, net.Const(1),], [my_val])\n\n        with TaskGroup() as task_group:\n            with Node('trainer'):\n                my_task = Task(step=[net])\n\n    In order to have `init_net` run once before `net` runs for the\n    first time, you can do one of the following:\n\n        net.add_attribute(Task.TASK_SETUP, SetupNets([init_net]))\n\n    or\n\n        net.add_attribute(TaskGroup.LOCAL_SETUP, SetupNets([init_net]))\n\n    - With Task.TASK_SETUP, init_net will run once at my_task startup.\n    - With TaskGroup.LOCAL_SETUP, init_net will run once on node 'trainer',\n      before any task of the task group is run on that node.\n\n    The same SetupNets object can be added to multiple nets. It will only\n    run once per Task/TaskGroup run.\n    \"\"\"\n\n    def __init__(self, init_nets=None, exit_nets=None):\n        self.init_nets = init_nets\n        self.exit_nets = exit_nets\n\n    def setup(self, init_net):\n        return self.init_nets\n\n    def exit(self, exit_net):\n        return self.exit_nets\n"
  },
  {
    "path": "caffe2/python/test/blob_deallocation_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nfrom caffe2.python import core, workspace\nimport unittest\n\ncore.GlobalInit(['python'])\n\n\nclass BlobDeallocationTest(unittest.TestCase):\n    def test(self):\n        net = core.Net('net')\n\n        x = net.GivenTensorStringFill([], ['x'], shape=[3], values=['a', 'b', 'c'])\n        y = net.GivenTensorStringFill([], ['y'], shape=[3], values=['d', 'e', 'f'])\n        net.Concat([x, y], ['concated', '_'], axis=0)\n\n        workspace.ResetWorkspace()\n        workspace.RunNetOnce(net)\n\n        workspace.ResetWorkspace()\n        workspace.RunNetOnce(net)\n        self.assertTrue(True)\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/test/do_op_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nfrom caffe2.python import core, workspace\nfrom caffe2.python.test_util import TestCase\nimport numpy as np\nimport unittest\n\n\nclass DoOpTest(TestCase):\n    def test_operator(self):\n        def make_net():\n            subnet = core.Net('subnet')\n            subnet.Add([\"X\", \"Y\"], \"Z\")\n\n            net = core.Net(\"net\")\n            net.CreateScope([], \"W\")\n\n            net.Do(\n                [\"outer_X\", \"outer_Y\", \"W\"],\n                [\"outer_Z\", \"W\"],\n                net=subnet.Proto(),\n                inner_blobs=[\"X\", \"Y\", \"Z\"],\n                outer_blobs_idx=[0, 1, 2],\n            )\n\n            return net\n\n        net = make_net()\n\n        workspace.ResetWorkspace()\n        workspace.FeedBlob(\"outer_X\", np.asarray([1, 2]))\n        workspace.FeedBlob(\"outer_Y\", np.asarray([3, 4]))\n\n        workspace.RunNetOnce(net)\n        outer_Z_val = workspace.FetchBlob(\"outer_Z\")\n        self.assertTrue(np.all(outer_Z_val == np.asarray([4, 6])))\n\n    def test_reuse_workspace(self):\n        def make_net():\n            param_init_subnet = core.Net('param_init_subnet')\n            param_init_subnet.ConstantFill([], \"X\", shape=[1], value=1)\n            param_init_subnet.ConstantFill([], \"Y\", shape=[1], value=2)\n\n            subnet = core.Net(\"subnet\")\n            subnet.Add([\"X\", \"Y\"], \"Z\")\n\n            net = core.Net(\"net\")\n            net.CreateScope([], \"W\")\n            net.Do(\n                \"W\", \"W\",\n                net=param_init_subnet.Proto(),\n                inner_blobs=[],\n                outer_blobs_idx=[],\n            )\n\n            net.Do(\n                \"W\", [\"outer_Z\", \"W\"],\n                net=subnet.Proto(),\n                inner_blobs=[\"Z\"],\n                outer_blobs_idx=[0],\n                reuse_workspace=True,\n            )\n\n            return net\n\n        net = make_net()\n\n        workspace.ResetWorkspace()\n        workspace.RunNetOnce(net)\n        outer_Z_val = workspace.FetchBlob(\"outer_Z\")\n        self.assertTrue(np.all(outer_Z_val == np.asarray([3])))\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/test/executor_test.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nfrom caffe2.python import workspace\nfrom caffe2.python.test.executor_test_util import (\n    build_conv_model,\n    build_resnet50_dataparallel_model,\n    run_resnet50_epoch,\n    ExecutorTestBase,\n    executor_test_settings,\n    executor_test_model_names)\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nimport unittest\n\n\nEXECUTORS = [\"async_scheduling\", \"async_polling\", \"dag\", \"async_dag\"]\nITERATIONS = 1\n\n\nclass ExecutorCPUConvNetTest(ExecutorTestBase):\n    @given(executor=st.sampled_from(EXECUTORS),\n           model_name=st.sampled_from(executor_test_model_names()),\n           batch_size=st.sampled_from([1]),\n           num_workers=st.sampled_from([8]))\n    @executor_test_settings\n    def test_executor(self, executor, model_name, batch_size, num_workers):\n        model = build_conv_model(model_name, batch_size)\n        model.Proto().num_workers = num_workers\n\n        def run_model():\n            iterations = ITERATIONS\n            if model_name == \"MLP\":\n                iterations = 1  # avoid numeric instability with MLP gradients\n            workspace.RunNet(model.net, iterations)\n\n        self.compare_executors(\n            model,\n            ref_executor=\"simple\",\n            test_executor=executor,\n            model_run_func=run_model,\n        )\n\n\n@unittest.skipIf(not workspace.has_gpu_support, \"no gpu\")\nclass ExecutorGPUResNetTest(ExecutorTestBase):\n    @given(executor=st.sampled_from(EXECUTORS),\n           num_workers=st.sampled_from([8]))\n    @executor_test_settings\n    def test_executor(self, executor, num_workers):\n        model = build_resnet50_dataparallel_model(\n            num_gpus=workspace.NumCudaDevices(), batch_size=8, epoch_size=8)\n        model.Proto().num_workers = num_workers\n\n        def run_model():\n            run_resnet50_epoch(model, batch_size=8, epoch_size=8)\n\n        self.compare_executors(\n            model,\n            ref_executor=\"simple\",\n            test_executor=executor,\n            model_run_func=run_model,\n        )\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/test/executor_test_util.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\n\nfrom caffe2.python import (\n    brew, cnn, core, workspace, data_parallel_model,\n    timeout_guard, model_helper, optimizer)\nfrom caffe2.python.test_util import TestCase\nimport caffe2.python.models.resnet as resnet\nfrom caffe2.python.modeling.initializers import Initializer\nfrom caffe2.python import convnet_benchmarks as cb\nfrom caffe2.python import hypothesis_test_util as hu\n\nimport time\nimport numpy as np\nfrom hypothesis import settings\n\n\nCI_MAX_EXAMPLES = 2\nCI_TIMEOUT = 600\n\n\ndef executor_test_settings(func):\n    if hu.is_sandcastle() or hu.is_travis():\n        return settings(\n            max_examples=CI_MAX_EXAMPLES,\n            timeout=CI_TIMEOUT\n        )(func)\n    else:\n        return func\n\n\ndef gen_test_resnet50(_order, _cudnn_ws):\n    model = cnn.CNNModelHelper(\n        order=\"NCHW\",\n        name=\"resnet_50_test\",\n        cudnn_exhaustive_search=True,\n    )\n    data = model.net.AddExternalInput(\"data\")\n    label = model.net.AddExternalInput(\"label\")\n    (_softmax, loss) = resnet.create_resnet50(\n        model,\n        data,\n        num_input_channels=3,\n        num_labels=1000,\n        label=label,\n        is_test=False,\n    )\n    return model, 227\n\n\ndef conv_model_generators():\n    return {\n        'AlexNet': cb.AlexNet,\n        'OverFeat': cb.OverFeat,\n        'VGGA': cb.VGGA,\n        'Inception': cb.Inception,\n        'MLP': cb.MLP,\n        'Resnet50': gen_test_resnet50,\n    }\n\n\ndef executor_test_model_names():\n    if hu.is_sandcastle() or hu.is_travis():\n        return [\"MLP\"]\n    else:\n        return conv_model_generators().keys()\n\n\ndef build_conv_model(model_name, batch_size):\n    model_gen_map = conv_model_generators()\n    assert model_name in model_gen_map, \"Model \" + model_name + \" not found\"\n    model, input_size = model_gen_map[model_name](\"NCHW\", None)\n\n    input_shape = [batch_size, 3, input_size, input_size]\n    if model_name == \"MLP\":\n        input_shape = [batch_size, input_size]\n\n    model.param_init_net.GaussianFill(\n        [],\n        \"data\",\n        shape=input_shape,\n        mean=0.0,\n        std=1.0\n    )\n    model.param_init_net.UniformIntFill(\n        [],\n        \"label\",\n        shape=[batch_size, ],\n        min=0,\n        max=999\n    )\n\n    model.AddGradientOperators([\"loss\"])\n\n    ITER = brew.iter(model, \"iter\")\n    LR = model.net.LearningRate(\n        ITER, \"LR\", base_lr=-1e-8, policy=\"step\", stepsize=10000, gamma=0.999)\n    ONE = model.param_init_net.ConstantFill([], \"ONE\", shape=[1], value=1.0)\n    for param in model.params:\n        param_grad = model.param_to_grad[param]\n        model.net.WeightedSum([param, ONE, param_grad, LR], param)\n\n    return model\n\n\ndef build_resnet50_dataparallel_model(\n        num_gpus,\n        batch_size,\n        epoch_size,\n        cudnn_workspace_limit_mb=64,\n        num_channels=3,\n        num_labels=1000,\n        weight_decay=1e-4,\n        base_learning_rate=0.1,\n        image_size=227,\n        use_cpu=False):\n\n    batch_per_device = batch_size // num_gpus\n\n    train_arg_scope = {\n        'order': 'NCHW',\n        'use_cudnn': True,\n        'cudnn_exhaustive_search': False,\n        'ws_nbytes_limit': (cudnn_workspace_limit_mb * 1024 * 1024),\n        'deterministic': True,\n    }\n    train_model = model_helper.ModelHelper(\n        name=\"test_resnet50\", arg_scope=train_arg_scope\n    )\n\n    def create_resnet50_model_ops(model, loss_scale):\n        with brew.arg_scope([brew.conv, brew.fc],\n                            WeightInitializer=Initializer,\n                            BiasInitializer=Initializer,\n                            enable_tensor_core=0):\n            pred = resnet.create_resnet50(\n                model,\n                \"data\",\n                num_input_channels=num_channels,\n                num_labels=num_labels,\n                no_bias=True,\n                no_loss=True,\n            )\n\n        softmax, loss = model.SoftmaxWithLoss([pred, 'label'],\n                                              ['softmax', 'loss'])\n        loss = model.Scale(loss, scale=loss_scale)\n        brew.accuracy(model, [softmax, \"label\"], \"accuracy\")\n        return [loss]\n\n    def add_optimizer(model):\n        stepsz = int(30 * epoch_size / batch_size)\n        optimizer.add_weight_decay(model, weight_decay)\n        opt = optimizer.build_multi_precision_sgd(\n            model,\n            base_learning_rate,\n            momentum=0.9,\n            nesterov=1,\n            policy=\"step\",\n            stepsize=stepsz,\n            gamma=0.1\n        )\n        return opt\n\n    def add_image_input(model):\n        model.param_init_net.GaussianFill(\n            [],\n            [\"data\"],\n            shape=[batch_per_device, 3, image_size, image_size],\n            dtype='float',\n        )\n        model.param_init_net.ConstantFill(\n            [],\n            [\"label\"],\n            shape=[batch_per_device],\n            value=1,\n            dtype=core.DataType.INT32,\n        )\n\n    def add_post_sync_ops(model):\n        for param_info in model.GetOptimizationParamInfo(model.GetParams()):\n            if param_info.blob_copy is not None:\n                model.param_init_net.HalfToFloat(\n                    param_info.blob,\n                    param_info.blob_copy[core.DataType.FLOAT])\n\n    # Create parallelized model\n    data_parallel_model.Parallelize(\n        train_model,\n        input_builder_fun=add_image_input,\n        forward_pass_builder_fun=create_resnet50_model_ops,\n        optimizer_builder_fun=add_optimizer,\n        post_sync_builder_fun=add_post_sync_ops,\n        devices=list(range(num_gpus)),\n        rendezvous=None,\n        optimize_gradient_memory=True,\n        cpu_device=use_cpu,\n        shared_model=use_cpu,\n    )\n\n    return train_model\n\n\ndef run_resnet50_epoch(train_model, batch_size, epoch_size, skip_first_n_iter=0):\n    epoch_iters = int(epoch_size / batch_size)\n    prefix = \"{}_{}\".format(\n        train_model._device_prefix,\n        train_model._devices[0])\n    train_time = 0.0\n    train_examples = 0\n    for i in range(epoch_iters):\n        timeout = 600.0 if i == 0 else 60.0\n        with timeout_guard.CompleteInTimeOrDie(timeout):\n            t1 = time.time()\n            workspace.RunNet(train_model.net.Proto().name)\n            t2 = time.time()\n            dt = t2 - t1\n            if i >= skip_first_n_iter:\n                train_time += dt\n                train_examples += batch_size\n\n        fmt = \"Finished iteration {}/{} ({:.2f} images/sec)\"\n        print(fmt.format(i + 1, epoch_iters, batch_size / dt))\n\n    accuracy = workspace.FetchBlob(prefix + '/accuracy')\n    loss = workspace.FetchBlob(prefix + '/loss')\n\n    assert loss < 40, \"Exploded gradients\"\n\n    return (\n        train_examples,\n        train_time,\n        accuracy, loss)\n\n\nclass ExecutorTestBase(TestCase):\n    def compare_executors(self, model, ref_executor, test_executor, model_run_func):\n        model.Proto().type = ref_executor\n        model.param_init_net.set_rand_seed(seed=0xCAFFE2)\n        model.net.set_rand_seed(seed=0xCAFFE2)\n\n        workspace.ResetWorkspace()\n        workspace.RunNetOnce(model.param_init_net)\n\n        workspace.CreateNet(model.net)\n        model_run_func()\n        ref_ws = {str(k): workspace.FetchBlob(k) for k in workspace.Blobs()}\n        ref_ws = {k: v for k, v in ref_ws.items() if type(v) is np.ndarray}\n\n        workspace.ResetWorkspace()\n        workspace.RunNetOnce(model.param_init_net)\n\n        model.Proto().type = test_executor\n        workspace.CreateNet(model.net, overwrite=True)\n        model_run_func()\n        test_ws = {str(k): workspace.FetchBlob(k) for k in workspace.Blobs()}\n        test_ws = {k: v for k, v in test_ws.items() if type(v) is np.ndarray}\n\n        for blob_name, ref_val in ref_ws.items():\n            self.assertTrue(\n                blob_name in test_ws,\n                \"Blob {} not found in {} run\".format(blob_name, test_executor))\n            val = test_ws[blob_name]\n            np.testing.assert_array_equal(\n                val, ref_val,\n                \"Blob {} differs in {} run\".format(blob_name, test_executor))\n"
  },
  {
    "path": "caffe2/python/test_util.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package test_util\n# Module caffe2.python.test_util\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport numpy as np\nfrom caffe2.python import core, workspace\n\nimport unittest\n\n\ndef rand_array(*dims):\n    # np.random.rand() returns float instead of 0-dim array, that's why need to\n    # do some tricks\n    return np.array(np.random.rand(*dims) - 0.5).astype(np.float32)\n\n\nclass TestCase(unittest.TestCase):\n    @classmethod\n    def setUpClass(cls):\n        workspace.GlobalInit([\n            'caffe2',\n            '--caffe2_log_level=0',\n        ])\n        # clear the default engines settings to separate out its\n        # affect from the ops tests\n        core.SetEnginePref({}, {})\n\n    def setUp(self):\n        self.ws = workspace.C.Workspace()\n        workspace.ResetWorkspace()\n\n    def tearDown(self):\n        workspace.ResetWorkspace()\n"
  },
  {
    "path": "caffe2/python/text_file_reader.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package text_file_reader\n# Module caffe2.python.text_file_reader\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import core\nfrom caffe2.python.dataio import Reader\nfrom caffe2.python.schema import Scalar, Struct, data_type_for_dtype\n\n\nclass TextFileReader(Reader):\n    \"\"\"\n    Wrapper around operators for reading from text files.\n    \"\"\"\n    def __init__(self, init_net, filename, schema, num_passes=1, batch_size=1):\n        \"\"\"\n        Create op for building a TextFileReader instance in the workspace.\n\n        Args:\n            init_net   : Net that will be run only once at startup.\n            filename   : Path to file to read from.\n            schema     : schema.Struct representing the schema of the data.\n                         Currently, only support Struct of strings.\n            num_passes : Number of passes over the data.\n            batch_size : Number of rows to read at a time.\n        \"\"\"\n        assert isinstance(schema, Struct), 'Schema must be a schema.Struct'\n        for name, child in schema.get_children():\n            assert isinstance(child, Scalar), (\n                'Only scalar fields are supported in TextFileReader.')\n        field_types = [\n            data_type_for_dtype(dtype) for dtype in schema.field_types()]\n        Reader.__init__(self, schema)\n        self._reader = init_net.CreateTextFileReader(\n            [],\n            filename=filename,\n            num_passes=num_passes,\n            field_types=field_types)\n        self._batch_size = batch_size\n\n    def read(self, net):\n        \"\"\"\n        Create op for reading a batch of rows.\n        \"\"\"\n        blobs = net.TextFileReaderRead(\n            [self._reader],\n            len(self.schema().field_names()),\n            batch_size=self._batch_size)\n        if type(blobs) is core.BlobReference:\n            blobs = [blobs]\n\n        is_empty = net.IsEmpty(\n            [blobs[0]],\n            core.ScopedBlobReference(net.NextName('should_stop'))\n        )\n\n        return (is_empty, blobs)\n"
  },
  {
    "path": "caffe2/python/timeout_guard.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package timeout_guard\n# Module caffe2.python.timeout_guard\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport contextlib\nimport threading\nimport os\nimport time\nimport signal\nimport logging\nfrom future.utils import viewitems\n\n\n'''\nSometimes CUDA devices can get stuck, 'deadlock'. In this case it is often\nbetter just the kill the process automatically. Use this guard to set a\nmaximum timespan for a python call, such as RunNet(). If it does not complete\nin time, process is killed.\n\nExample usage:\n    with timeout_guard.CompleteInTimeOrDie(10.0):\n        core.RunNet(...)\n'''\n\n\nclass WatcherThread(threading.Thread):\n\n    def __init__(self, timeout_secs):\n        threading.Thread.__init__(self)\n        self.timeout_secs = timeout_secs\n        self.completed = False\n        self.condition = threading.Condition()\n        self.daemon = True\n        self.caller_thread = threading.current_thread()\n\n    def run(self):\n        started = time.time()\n        self.condition.acquire()\n        while time.time() - started < self.timeout_secs and not self.completed:\n            self.condition.wait(self.timeout_secs - (time.time() - started))\n        self.condition.release()\n        if not self.completed:\n            log = logging.getLogger(\"timeout_guard\")\n            log.error(\"Call did not finish in time. Timeout:{}s PID: {}\".format(\n                self.timeout_secs,\n                os.getpid(),\n            ))\n\n            # First try dying cleanly, but in 10 secs, exit properly\n            def forcequit():\n                time.sleep(10.0)\n                log.info(\"Prepared output, dumping threads. \")\n                print(\"Caller thread was: {}\".format(self.caller_thread))\n                print(\"-----After force------\")\n                import sys\n                import traceback\n                code = []\n                for threadId, stack in viewitems(sys._current_frames()):\n                    if threadId == self.caller_thread.ident:\n                        code.append(\"\\n# ThreadID: %s\" % threadId)\n                        for filename, lineno, name, line in traceback.extract_stack(stack):\n                            code.append('File: \"%s\", line %d, in %s' % (filename, lineno, name))\n                            if line:\n                                code.append(\"  %s\" % (line.strip()))\n\n                print(\"\\n\".join(code))\n                log.error(\"Process did not terminate cleanly in 10 s, forcing\")\n                os.abort()\n\n            forcet = threading.Thread(target=forcequit, args=())\n            forcet.daemon = True\n            forcet.start()\n            print(\"Caller thread was: {}\".format(self.caller_thread))\n            print(\"-----Before forcing------\")\n            import sys\n            import traceback\n            code = []\n            for threadId, stack in viewitems(sys._current_frames()):\n                code.append(\"\\n# ThreadID: %s\" % threadId)\n                for filename, lineno, name, line in traceback.extract_stack(stack):\n                    code.append('File: \"%s\", line %d, in %s' % (filename, lineno, name))\n                    if line:\n                        code.append(\"  %s\" % (line.strip()))\n\n            print(\"\\n\".join(code))\n            os.kill(os.getpid(), signal.SIGINT)\n\n\n@contextlib.contextmanager\ndef CompleteInTimeOrDie(timeout_secs):\n    watcher = WatcherThread(timeout_secs)\n    watcher.start()\n    yield\n    watcher.completed = True\n    watcher.condition.acquire()\n    watcher.condition.notify()\n    watcher.condition.release()\n\n\ndef EuthanizeIfNecessary(timeout_secs=120):\n    '''\n    Call this if you have problem with process getting stuck at shutdown.\n    It will kill the process if it does not terminate in timeout_secs.\n    '''\n    watcher = WatcherThread(timeout_secs)\n    watcher.start()\n"
  },
  {
    "path": "caffe2/python/toy_regression_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nimport numpy as np\nimport unittest\n\nfrom caffe2.python import core, workspace, test_util\n\n\nclass TestToyRegression(test_util.TestCase):\n    def testToyRegression(self):\n        \"\"\"Tests a toy regression end to end.\n\n        The test code carries a simple toy regression in the form\n            y = 2.0 x1 + 1.5 x2 + 0.5\n        by randomly generating gaussian inputs and calculating the ground\n        truth outputs in the net as well. It uses a standard SGD to then\n        train the parameters.\n        \"\"\"\n        workspace.ResetWorkspace()\n        init_net = core.Net(\"init\")\n        W = init_net.UniformFill([], \"W\", shape=[1, 2], min=-1., max=1.)\n        B = init_net.ConstantFill([], \"B\", shape=[1], value=0.0)\n        W_gt = init_net.GivenTensorFill(\n            [], \"W_gt\", shape=[1, 2], values=[2.0, 1.5])\n        B_gt = init_net.GivenTensorFill([], \"B_gt\", shape=[1], values=[0.5])\n        LR = init_net.ConstantFill([], \"LR\", shape=[1], value=-0.1)\n        ONE = init_net.ConstantFill([], \"ONE\", shape=[1], value=1.)\n        ITER = init_net.ConstantFill([], \"ITER\", shape=[1], value=0,\n                                     dtype=core.DataType.INT32)\n\n        train_net = core.Net(\"train\")\n        X = train_net.GaussianFill([], \"X\", shape=[64, 2], mean=0.0, std=1.0)\n        Y_gt = X.FC([W_gt, B_gt], \"Y_gt\")\n        Y_pred = X.FC([W, B], \"Y_pred\")\n        dist = train_net.SquaredL2Distance([Y_gt, Y_pred], \"dist\")\n        loss = dist.AveragedLoss([], [\"loss\"])\n        # Get gradients for all the computations above. Note that in fact we\n        # don't need to get the gradient the Y_gt computation, but we'll just\n        # leave it there. In many cases, I am expecting one to load X and Y\n        # from the disk, so there is really no operator that will calculate the\n        # Y_gt input.\n        input_to_grad = train_net.AddGradientOperators([loss], skip=2)\n        # updates\n        train_net.Iter(ITER, ITER)\n        train_net.LearningRate(ITER, \"LR\", base_lr=-0.1,\n                               policy=\"step\", stepsize=20, gamma=0.9)\n        train_net.WeightedSum([W, ONE, input_to_grad[str(W)], LR], W)\n        train_net.WeightedSum([B, ONE, input_to_grad[str(B)], LR], B)\n        for blob in [loss, W, B]:\n            train_net.Print(blob, [])\n\n        # the CPU part.\n        plan = core.Plan(\"toy_regression\")\n        plan.AddStep(core.ExecutionStep(\"init\", init_net))\n        plan.AddStep(core.ExecutionStep(\"train\", train_net, 200))\n\n        workspace.RunPlan(plan)\n        W_result = workspace.FetchBlob(\"W\")\n        B_result = workspace.FetchBlob(\"B\")\n        np.testing.assert_array_almost_equal(W_result, [[2.0, 1.5]], decimal=2)\n        np.testing.assert_array_almost_equal(B_result, [0.5], decimal=2)\n        workspace.ResetWorkspace()\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/tt_core.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package tt_core\n# Module caffe2.python.tt_core\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport numpy as np\n\n\"\"\"\nThe following methods are various utility methods for using the Tensor-Train\ndecomposition, or TT-decomposition introduced by I. V. Oseledets (2011) in his\npaper (http://epubs.siam.org/doi/abs/10.1137/090752286).\n\nBroadly speaking, these methods are used to replace fully connected layers in\nneural networks with Tensor-Train layers introduced by A. Novikov et. al. (2015)\nin their paper (http://arxiv.org/abs/1509.06569). More details about each of\nthe methods are provided in each respective docstring.\n\"\"\"\n\n\ndef init_tt_cores(inp_sizes, out_sizes, tt_ranks, seed=1234):\n    \"\"\"\n    Initialize randomized orthogonalized TT-cores.\n\n    This method should be used when a TT-layer is trained from scratch. The\n    sizes of each of the cores are specified by the inp_sizes and out_sizes, and\n    the respective tt_ranks will dictate the ranks of each of the cores. Note\n    that a larger set of tt_ranks will result in slower computation but will\n    result in more accurate approximations. The size of the ith core is:\n\n        tt_ranks[i] * inp_sizes[i] * out_sizes[i] * tt_ranks[i + 1].\n\n    Note that the following relationships of lengths of each input is expected:\n\n        len(inp_sizes) == len(out_sizes) == len(tt_ranks) - 1.\n\n    Args:\n        inp_sizes: list of the input dimensions of the respective cores\n        out_sizes: list of the output dimensions of the respective cores\n        tt_ranks: list of the ranks of the respective cores\n        seed: integer to seed the random number generator\n\n    Returns:\n        cores: One-dimensional list of cores concatentated along an axis\n    \"\"\"\n    np.random.seed(seed)\n\n    # Assert that the sizes of each input is correct\n    assert(len(inp_sizes) == len(out_sizes)), \\\n           \"The number of input dimensions (\" + str(len(inp_sizes)) + \\\n           \") must be equal to the number of output dimensions (\" + \\\n           str(len(out_sizes)) + \").\"\n\n    assert(len(tt_ranks) == len(inp_sizes) + 1), \\\n           \"The number of tt-ranks (\" + str(len(tt_ranks)) + \") must be \" + \\\n           \"one more than the number of input and output dims (\" + \\\n           str(len(out_sizes)) + \").\"\n\n    # Convert to numpy arrays\n    inp_sizes = np.array(inp_sizes)\n    out_sizes = np.array(out_sizes)\n    tt_ranks = np.array(tt_ranks)\n\n    # Initialize the cores array\n    cores_len = np.sum(\n        inp_sizes * out_sizes * tt_ranks[1:] * tt_ranks[:-1])\n    cores = np.zeros(cores_len)\n    cores_idx = 0\n    rv = 1\n\n    # Compute the full list of cores by computing each individual one\n    for i in range(inp_sizes.shape[0]):\n        shape = [tt_ranks[i],\n                 inp_sizes[i],\n                 out_sizes[i],\n                 tt_ranks[i + 1]]\n\n        # Precompute the shape of each core\n        tall_shape = (np.prod(shape[:3]), shape[3])\n\n        # Randomly initialize the current core using a normal distribution\n        curr_core = np.dot(rv, np.random.normal(\n            0, 1, size=(shape[0], np.prod(shape[1:]))))\n        curr_core = curr_core.reshape(tall_shape)\n\n        # Orthogonalize the initialized current core and append to cores list\n        if i < inp_sizes.shape[0] - 1:\n            curr_core, rv = np.linalg.qr(curr_core)\n        cores[cores_idx:cores_idx +\n              curr_core.size] = curr_core.flatten()\n        cores_idx += curr_core.size\n\n    # Normalize the list of arrays using this Glarot trick\n    glarot_style = (np.prod(inp_sizes) *\n                    np.prod(tt_ranks))**(1.0 / inp_sizes.shape[0])\n\n    return (0.1 / glarot_style) * np.array(cores).astype(np.float32)\n\n\ndef matrix_to_tt(W, inp_sizes, out_sizes, tt_ranks):\n    \"\"\"\n    Convert a matrix into the TT-format.\n\n    This method will consume a 2D weight matrix such as those used in fully\n    connected layers in a neural network and will compute the TT-decomposition\n    of the weight matrix and return the TT-cores of the resulting computation.\n    This method should be used when converting a trained, fully connected layer,\n    into a TT-layer for increased speed and decreased parameter size. The size\n    of the ith core is:\n\n        tt_ranks[i] * inp_sizes[i] * out_sizes[i] * tt_ranks[i + 1].\n\n    Note that the following relationships of lengths of each input is expected:\n\n        len(inp_sizes) == len(out_sizes) == len(tt_ranks) - 1.\n\n    We also require that np.prod(inp_sizes) == W.shape[0] and that\n    np.prod(out_sizes) == W.shape[1].\n\n    Args:\n        W: two-dimensional weight matrix numpy array representing a fully\n           connected layer to be converted to TT-format; note that the weight\n           matrix is transposed before decomposed because we want to emulate the\n           X * W^T operation that the FC layer performs.\n        inp_sizes: list of the input dimensions of the respective cores\n        out_sizes: list of the output dimensions of the respective cores\n        tt_ranks: list of the ranks of the respective cores\n\n    Returns:\n        new_cores: One-dimensional list of cores concatentated along an axis\n   \"\"\"\n\n    # Assert that the sizes of each input is correct\n    assert(len(inp_sizes) == len(out_sizes)), \\\n           \"The number of input dimensions (\" + str(len(inp_sizes)) + \\\n           \") must be equal to the number of output dimensions (\" + \\\n           str(len(out_sizes)) + \").\"\n\n    assert(len(tt_ranks) == len(inp_sizes) + 1), \\\n           \"The number of tt-ranks (\" + str(len(tt_ranks)) + \") must be \" + \\\n           \"one more than the number of input and output dimensions (\" + \\\n           str(len(out_sizes)) + \").\"\n\n    assert(W.shape[0] == np.prod(inp_sizes)), \\\n           \"The product of the input sizes (\" + str(np.prod(inp_sizes)) + \\\n           \") must be equal to first dimension of W (\" + str(W.shape[0]) + \").\"\n\n    assert(W.shape[1] == np.prod(out_sizes)), \\\n           \"The product of the output sizes (\" + str(np.prod(out_sizes)) + \\\n           \") must be equal to second dimension of W (\" + str(W.shape[1]) + \").\"\n\n    # W is transposed so that the multiplication X * W^T can be computed, just\n    # as it is in the FC layer.\n    W = W.transpose()\n\n    # Convert to numpy arrays\n    inp_sizes = np.array(inp_sizes)\n    out_sizes = np.array(out_sizes)\n    tt_ranks = np.array(tt_ranks)\n\n    # Copy the original weight matrix in order to permute and reshape the weight\n    # matrix. In addition, the inp_sizes and out_sizes are combined to a single\n    # sizes array to use the tt_svd helper method, which only consumes a single\n    # sizes array.\n    W_copy = W.copy()\n    total_inp_size = inp_sizes.size\n    W_copy = np.reshape(W_copy, np.concatenate((inp_sizes, out_sizes)))\n    order = np.repeat(np.arange(0, total_inp_size), 2) + \\\n            np.tile([0, total_inp_size], total_inp_size)\n    W_copy = np.transpose(W_copy, axes=order)\n    W_copy = np.reshape(W_copy, inp_sizes * out_sizes)\n\n    # Use helper method to convert the W matrix copy into the preliminary\n    # cores array.\n    cores = tt_svd(W_copy, inp_sizes * out_sizes, tt_ranks)\n\n    # Permute the dimensions of each of the cores to be compatible with the\n    # TT-layer.\n    new_cores = np.zeros(cores.shape).astype(np.float32)\n    idx = 0\n    for i in range(len(inp_sizes)):\n        shape = (tt_ranks[i], inp_sizes[i], out_sizes[i], tt_ranks[i + 1])\n        current_core = cores[idx:idx + np.prod(shape)].reshape(shape)\n        current_core = current_core.transpose((1, 3, 0, 2))\n        new_cores[new_cores.shape[0] - idx - np.prod(shape):\n                  new_cores.shape[0] - idx] \\\n                  = current_core.flatten()\n        idx += np.prod(shape)\n\n    return new_cores\n\n\ndef tt_svd(W, sizes, tt_ranks):\n    \"\"\"\n    Helper method for the matrix_to_tt() method performing the TT-SVD\n    decomposition.\n\n    Uses the TT-decomposition algorithm to convert a matrix to TT-format using\n    multiple reduced SVD operations.\n\n    Args:\n        W: two-dimensional weight matrix representing a fully connected layer to\n           be converted to TT-format preprocessed by the matrix_to_tt() method.\n        sizes: list of the dimensions of each of the cores\n        tt_ranks: list of the ranks of the respective cores\n\n    Returns:\n        cores: One-dimensional list of cores concatentated along an axis\n   \"\"\"\n\n    assert(len(tt_ranks) == len(sizes) + 1)\n\n    C = W.copy()\n    total_size = sizes.size\n    core = np.zeros(np.sum(tt_ranks[:-1] * sizes * tt_ranks[1:]),\n                    dtype='float32')\n\n    # Compute iterative reduced SVD operations and store each resulting U matrix\n    # as an individual core.\n    pos = 0\n    for i in range(0, total_size - 1):\n        shape = tt_ranks[i] * sizes[i]\n        C = np.reshape(C, [shape, -1])\n        U, S, V = np.linalg.svd(C, full_matrices=False)\n        U = U[:, 0:tt_ranks[i + 1]]\n        S = S[0:tt_ranks[i + 1]]\n        V = V[0:tt_ranks[i + 1], :]\n\n        core[pos:pos + tt_ranks[i] * sizes[i] * tt_ranks[i + 1]] = U.ravel()\n        pos += tt_ranks[i] * sizes[i] * tt_ranks[i + 1]\n        C = np.dot(np.diag(S), V)\n\n    core[pos:pos + tt_ranks[total_size - 1] *\n         sizes[total_size - 1] * tt_ranks[total_size]] = C.ravel()\n    return core\n\n\n# TODO(Surya) Write a method to convert an entire network where all fully\n# connected layers are replaced by an TT layer.\ndef fc_net_to_tt_net(net):\n    pass\n"
  },
  {
    "path": "caffe2/python/tt_core_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nimport unittest\n\nfrom caffe2.python import core, workspace, tt_core\nimport caffe2.python.hypothesis_test_util as hu\n\n\nclass TestTTSVD(hu.HypothesisTestCase):\n    def test_full_tt_svd(self):\n        size = 256\n        np.random.seed(1234)\n        X = np.expand_dims(\n            np.random.rand(size).astype(np.float32), axis=0)\n        W = np.random.rand(size, size).astype(np.float32)\n        b = np.zeros(size).astype(np.float32)\n        inp_sizes = [4, 4, 4, 4]\n        out_sizes = [4, 4, 4, 4]\n\n        op_fc = core.CreateOperator(\n            \"FC\",\n            [\"X\", \"W\", \"b\"],\n            [\"Y\"],\n        )\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"W\", W)\n        workspace.FeedBlob(\"b\", b)\n        workspace.RunOperatorOnce(op_fc)\n        Y_fc = workspace.FetchBlob(\"Y\").flatten()\n\n        # Testing TT-decomposition with high ranks\n        full_tt_ranks = [1, 16, 256, 16, 1]\n        full_cores = tt_core.matrix_to_tt(W, inp_sizes, out_sizes,\n                                          full_tt_ranks)\n\n        full_op_tt = core.CreateOperator(\n            \"TT\",\n            [\"X\", \"b\", \"cores\"],\n            [\"Y\"],\n            inp_sizes=inp_sizes,\n            out_sizes=out_sizes,\n            tt_ranks=full_tt_ranks,\n        )\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"b\", b)\n        workspace.FeedBlob(\"cores\", full_cores)\n        workspace.RunOperatorOnce(full_op_tt)\n        Y_full_tt = workspace.FetchBlob(\"Y\").flatten()\n\n        assert(len(Y_fc) == len(Y_full_tt))\n        self.assertAlmostEquals(np.linalg.norm(Y_fc - Y_full_tt), 0, delta=1e-3)\n\n        # Testing TT-decomposition with minimal ranks\n        sparse_tt_ranks = [1, 1, 1, 1, 1]\n        sparse_cores = tt_core.matrix_to_tt(W, inp_sizes, out_sizes,\n                                            sparse_tt_ranks)\n\n        sparse_op_tt = core.CreateOperator(\n            \"TT\",\n            [\"X\", \"b\", \"cores\"],\n            [\"Y\"],\n            inp_sizes=inp_sizes,\n            out_sizes=out_sizes,\n            tt_ranks=sparse_tt_ranks,\n        )\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"b\", b)\n        workspace.FeedBlob(\"cores\", sparse_cores)\n        workspace.RunOperatorOnce(sparse_op_tt)\n        Y_sparse_tt = workspace.FetchBlob(\"Y\").flatten()\n\n        assert(len(Y_fc) == len(Y_sparse_tt))\n        self.assertAlmostEquals(np.linalg.norm(Y_fc - Y_sparse_tt),\n                                39.974, delta=1e-3)\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/python/tutorials/Basics.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Caffe2 Basic Concepts - Operators & Nets\\n\",\n    \"\\n\",\n    \"In this tutorial we will go through a set of Caffe2 basics: the basic concepts including how operators and nets are being written.\\n\",\n    \"\\n\",\n    \"First, let's import Caffe2. `core` and `workspace` are usually the two that you need most. If you want to manipulate protocol buffers generated by Caffe2, you probably also want to import `caffe2_pb2` from `caffe2.proto`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:root:This caffe2 python run does not have GPU support. Will run in CPU only mode.\\n\",\n      \"WARNING:root:Debug message: No module named caffe2_pybind11_state_gpu\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from __future__ import absolute_import\\n\",\n    \"from __future__ import division\\n\",\n    \"from __future__ import print_function\\n\",\n    \"from __future__ import unicode_literals\\n\",\n    \"\\n\",\n    \"# We'll also import a few standard python libraries\\n\",\n    \"from matplotlib import pyplot\\n\",\n    \"import numpy as np\\n\",\n    \"import time\\n\",\n    \"\\n\",\n    \"# These are the droids you are looking for.\\n\",\n    \"from caffe2.python import core, workspace\\n\",\n    \"from caffe2.proto import caffe2_pb2\\n\",\n    \"\\n\",\n    \"# Let's show all plots inline.\\n\",\n    \"%matplotlib inline\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"You might see a warning saying that caffe2 does not have GPU support. That means you are running a CPU-only build. Don't be alarmed - anything CPU is still runnable without a problem.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"source\": [\n    \"## Workspaces\\n\",\n    \"\\n\",\n    \"Let's cover workspaces first, where all the data resides.\\n\",\n    \"\\n\",\n    \"Similar to Matlab, the Caffe2 workspace consists of blobs you create and store in memory. For now, consider a blob to be a N-dimensional Tensor similar to numpy's ndarray, but contiguous. Down the road, we will show you that a blob is actually a typed pointer that can store any type of C++ objects, but Tensor is the most common type stored in a blob. Let's show what the interface looks like.\\n\",\n    \"\\n\",\n    \"`Blobs()` prints out all existing blobs in the workspace. \\n\",\n    \"`HasBlob()` queries if a blob exists in the workspace. As of now, we don't have any.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Current blobs in the workspace: []\\n\",\n      \"Workspace has blob 'X'? False\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Current blobs in the workspace: {}\\\".format(workspace.Blobs()))\\n\",\n    \"print(\\\"Workspace has blob 'X'? {}\\\".format(workspace.HasBlob(\\\"X\\\")))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can feed blobs into the workspace using `FeedBlob()`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Generated X from numpy:\\n\",\n      \"[[ 1.0922441  -0.65129787 -0.2511869 ]\\n\",\n      \" [ 1.3999398  -0.86516035 -2.0602188 ]]\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"True\"\n      ]\n     },\n     \"execution_count\": 3,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"X = np.random.randn(2, 3).astype(np.float32)\\n\",\n    \"print(\\\"Generated X from numpy:\\\\n{}\\\".format(X))\\n\",\n    \"workspace.FeedBlob(\\\"X\\\", X)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, let's take a look at what blobs are in the workspace.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Current blobs in the workspace: [u'X']\\n\",\n      \"Workspace has blob 'X'? True\\n\",\n      \"Fetched X:\\n\",\n      \"[[ 1.0922441  -0.65129787 -0.2511869 ]\\n\",\n      \" [ 1.3999398  -0.86516035 -2.0602188 ]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Current blobs in the workspace: {}\\\".format(workspace.Blobs()))\\n\",\n    \"print(\\\"Workspace has blob 'X'? {}\\\".format(workspace.HasBlob(\\\"X\\\")))\\n\",\n    \"print(\\\"Fetched X:\\\\n{}\\\".format(workspace.FetchBlob(\\\"X\\\")))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's verify that the arrays are equal.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"np.testing.assert_array_equal(X, workspace.FetchBlob(\\\"X\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that if you try to access a blob that does not exist, an error will be thrown:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"[enforce fail at pybind_state.cc:173] ws->HasBlob(name). Can't find blob: invincible_pink_unicorn \\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"try:\\n\",\n    \"    workspace.FetchBlob(\\\"invincible_pink_unicorn\\\")\\n\",\n    \"except RuntimeError as err:\\n\",\n    \"    print(err)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"One thing that you might not use immediately: you can have multiple workspaces in Python using different names, and switch between them. Blobs in different workspaces are separate from each other. You can query the current workspace using `CurrentWorkspace`. Let's try switching the workspace by name (gutentag) and creating a new one if it doesn't exist.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Current workspace: default\\n\",\n      \"Current blobs in the workspace: [u'X']\\n\",\n      \"Current workspace: gutentag\\n\",\n      \"Current blobs in the workspace: []\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Current workspace: {}\\\".format(workspace.CurrentWorkspace()))\\n\",\n    \"print(\\\"Current blobs in the workspace: {}\\\".format(workspace.Blobs()))\\n\",\n    \"\\n\",\n    \"# Switch the workspace. The second argument \\\"True\\\" means creating \\n\",\n    \"# the workspace if it is missing.\\n\",\n    \"workspace.SwitchWorkspace(\\\"gutentag\\\", True)\\n\",\n    \"\\n\",\n    \"# Let's print the current workspace. Note that there is nothing in the\\n\",\n    \"# workspace yet.\\n\",\n    \"print(\\\"Current workspace: {}\\\".format(workspace.CurrentWorkspace()))\\n\",\n    \"print(\\\"Current blobs in the workspace: {}\\\".format(workspace.Blobs()))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's switch back to the default workspace.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Current workspace: default\\n\",\n      \"Current blobs in the workspace: [u'X']\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"workspace.SwitchWorkspace(\\\"default\\\")\\n\",\n    \"print(\\\"Current workspace: {}\\\".format(workspace.CurrentWorkspace()))\\n\",\n    \"print(\\\"Current blobs in the workspace: {}\\\".format(workspace.Blobs()))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Finally, `ResetWorkspace()` clears anything that is in the current workspace.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Current blobs in the workspace after reset: []\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"workspace.ResetWorkspace()\\n\",\n    \"print(\\\"Current blobs in the workspace after reset: {}\\\".format(workspace.Blobs()))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Operators\\n\",\n    \"\\n\",\n    \"Operators in Caffe2 are kind of like functions. From the C++ side, they all derive from a common interface, and are registered by type, so that we can call different operators during runtime. The interface of operators is defined in `caffe2/proto/caffe2.proto`. Basically, it takes in a bunch of inputs, and produces a bunch of outputs.\\n\",\n    \"\\n\",\n    \"Remember, when we say \\\"create an operator\\\" in Caffe2 Python, nothing gets run yet. All it does is create the protocol buffer that specifies what the operator should be. At a later time it will be sent to the C++ backend for execution. If you are not familiar with protobuf, it is a json-like serialization tool for structured data. Find more about protocol buffers [here](https://developers.google.com/protocol-buffers/).\\n\",\n    \"\\n\",\n    \"Let's see an actual example.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Create an operator.\\n\",\n    \"op = core.CreateOperator(\\n\",\n    \"    \\\"Relu\\\", # The type of operator that we want to run\\n\",\n    \"    [\\\"X\\\"], # A list of input blobs by their names\\n\",\n    \"    [\\\"Y\\\"], # A list of output blobs by their names\\n\",\n    \")\\n\",\n    \"# and we are done!\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"As we mentioned, the created op is actually a protobuf object. Let's show the content.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Type of the created op is: <class 'caffe2.proto.caffe2_pb2.OperatorDef'>\\n\",\n      \"Content:\\n\",\n      \"\\n\",\n      \"input: \\\"X\\\"\\n\",\n      \"output: \\\"Y\\\"\\n\",\n      \"name: \\\"\\\"\\n\",\n      \"type: \\\"Relu\\\"\\n\",\n      \"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Type of the created op is: {}\\\".format(type(op)))\\n\",\n    \"print(\\\"Content:\\\\n\\\")\\n\",\n    \"print(str(op))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Ok, let's run the operator. We first feed the input X to the workspace. \\n\",\n    \"Then the simplest way to run an operator is to do `workspace.RunOperatorOnce(operator)`\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"True\"\n      ]\n     },\n     \"execution_count\": 12,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"workspace.FeedBlob(\\\"X\\\", np.random.randn(2, 3).astype(np.float32))\\n\",\n    \"workspace.RunOperatorOnce(op)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"After execution, let's see if the operator is doing the right thing.\\n\",\n    \"\\n\",\n    \"In this case, the operator is a common activation function used in neural networks, called [ReLU](https://en.wikipedia.org/wiki/Rectifier_(neural_networks), or Rectified Linear Unit activation. ReLU activation helps to add necessary non-linear characteristics to the neural network classifier, and is defined as:\\n\",\n    \"\\n\",\n    \"$$ReLU(x) = max(0, x)$$\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Current blobs in the workspace: [u'X', u'Y']\\n\",\n      \"\\n\",\n      \"X:\\n\",\n      \"[[-0.958609   -1.5830146  -1.1998546 ]\\n\",\n      \" [ 0.77405983  0.08448339  0.57201284]]\\n\",\n      \"\\n\",\n      \"Y:\\n\",\n      \"[[0.         0.         0.        ]\\n\",\n      \" [0.77405983 0.08448339 0.57201284]]\\n\",\n      \"\\n\",\n      \"Expected:\\n\",\n      \"[[0.         0.         0.        ]\\n\",\n      \" [0.77405983 0.08448339 0.57201284]]\\n\",\n      \"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Current blobs in the workspace: {}\\\\n\\\".format(workspace.Blobs()))\\n\",\n    \"print(\\\"X:\\\\n{}\\\\n\\\".format(workspace.FetchBlob(\\\"X\\\")))\\n\",\n    \"print(\\\"Y:\\\\n{}\\\\n\\\".format(workspace.FetchBlob(\\\"Y\\\")))\\n\",\n    \"print(\\\"Expected:\\\\n{}\\\\n\\\".format(np.maximum(workspace.FetchBlob(\\\"X\\\"), 0)))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This is working if your Expected output matches your Y output in this example.\\n\",\n    \"\\n\",\n    \"Operators also take optional arguments if needed. They are specified as key-value pairs. Let's take a look at one simple example, which takes a tensor and fills it with Gaussian random variables.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Content of op:\\n\",\n      \"\\n\",\n      \"output: \\\"Z\\\"\\n\",\n      \"name: \\\"\\\"\\n\",\n      \"type: \\\"GaussianFill\\\"\\n\",\n      \"arg {\\n\",\n      \"  name: \\\"std\\\"\\n\",\n      \"  f: 1.0\\n\",\n      \"}\\n\",\n      \"arg {\\n\",\n      \"  name: \\\"shape\\\"\\n\",\n      \"  ints: 100\\n\",\n      \"  ints: 100\\n\",\n      \"}\\n\",\n      \"arg {\\n\",\n      \"  name: \\\"mean\\\"\\n\",\n      \"  f: 1.0\\n\",\n      \"}\\n\",\n      \"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"op = core.CreateOperator(\\n\",\n    \"    \\\"GaussianFill\\\",\\n\",\n    \"    [], # GaussianFill does not need any parameters.\\n\",\n    \"    [\\\"Z\\\"],\\n\",\n    \"    shape=[100, 100], # shape argument as a list of ints.\\n\",\n    \"    mean=1.0,  # mean as a single float\\n\",\n    \"    std=1.0, # std as a single float\\n\",\n    \")\\n\",\n    \"print(\\\"Content of op:\\\\n\\\")\\n\",\n    \"print(str(op))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's run it and see if things are as intended.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Text(0.5,1,u'Distribution of Z')\"\n      ]\n     },\n     \"execution_count\": 15,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x111998a90>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"workspace.RunOperatorOnce(op)\\n\",\n    \"temp = workspace.FetchBlob(\\\"Z\\\")\\n\",\n    \"pyplot.hist(temp.flatten(), bins=50)\\n\",\n    \"pyplot.title(\\\"Distribution of Z\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If you see a bell shaped curve then it worked!\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Nets\\n\",\n    \"\\n\",\n    \"Nets are essentially computation graphs. We keep the name `Net` for backward consistency (and also to pay tribute to neural nets). A Net is composed of multiple operators just like a program written as a sequence of commands. Let's take a look.\\n\",\n    \"\\n\",\n    \"When we talk about nets, we will also talk about BlobReference, which is an object that wraps around a string so we can do easy chaining of operators.\\n\",\n    \"\\n\",\n    \"Let's create a network that is essentially the equivalent of the following python math:\\n\",\n    \"```\\n\",\n    \"X = np.random.randn(2, 3)\\n\",\n    \"W = np.random.randn(5, 3)\\n\",\n    \"b = np.ones(5)\\n\",\n    \"Y = X * W^T + b\\n\",\n    \"```\\n\",\n    \"We'll show the progress step by step. Caffe2's `core.Net` is a wrapper class around a NetDef protocol buffer.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"When creating a network, its underlying protocol buffer is essentially empty other than the network name. Let's create the net and then show the proto content.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 16,\n   \"metadata\": {\n    \"scrolled\": false\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Current network proto:\\n\",\n      \"\\n\",\n      \"name: \\\"my_first_net\\\"\\n\",\n      \"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"net = core.Net(\\\"my_first_net\\\")\\n\",\n    \"print(\\\"Current network proto:\\\\n\\\\n{}\\\".format(net.Proto()))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's create a blob called X, and use GaussianFill to fill it with some random data.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 17,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"New network proto:\\n\",\n      \"\\n\",\n      \"name: \\\"my_first_net\\\"\\n\",\n      \"op {\\n\",\n      \"  output: \\\"X\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"GaussianFill\\\"\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"std\\\"\\n\",\n      \"    f: 1.0\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"run_once\\\"\\n\",\n      \"    i: 0\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"shape\\\"\\n\",\n      \"    ints: 2\\n\",\n      \"    ints: 3\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"mean\\\"\\n\",\n      \"    f: 0.0\\n\",\n      \"  }\\n\",\n      \"}\\n\",\n      \"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"X = net.GaussianFill([], [\\\"X\\\"], mean=0.0, std=1.0, shape=[2, 3], run_once=0)\\n\",\n    \"print(\\\"New network proto:\\\\n\\\\n{}\\\".format(net.Proto()))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"source\": [\n    \"You might have observed a few differences from the earlier `core.CreateOperator` call. Basically, when using a net, you can directly create an operator *and* add it to the net at the same time by calling `net.SomeOp` where SomeOp is a registered type string of an operator. This gets translated to\\n\",\n    \"```\\n\",\n    \"op = core.CreateOperator(\\\"SomeOp\\\", ...)\\n\",\n    \"net.Proto().op.append(op)\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"Also, you might be wondering what X is. X is a `BlobReference` which records two things:\\n\",\n    \"\\n\",\n    \"- The blob's name, which is accessed with `str(X)`\\n\",\n    \"\\n\",\n    \"- The net it got created from, which is recorded by the internal variable `_from_net`\\n\",\n    \"\\n\",\n    \"Let's verify it. Also, remember, we are not actually running anything yet, so X contains nothing but a symbol. Don't expect to get any numerical values out of it right now :)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 18,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Type of X is: <class 'caffe2.python.core.BlobReference'>\\n\",\n      \"The blob name is: X\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Type of X is: {}\\\".format(type(X)))\\n\",\n    \"print(\\\"The blob name is: {}\\\".format(str(X)))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's continue to create W and b.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 19,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"W = net.GaussianFill([], [\\\"W\\\"], mean=0.0, std=1.0, shape=[5, 3], run_once=0)\\n\",\n    \"b = net.ConstantFill([], [\\\"b\\\"], shape=[5,], value=1.0, run_once=0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, one simple code sugar: since the BlobReference objects know what net it is generated from, in addition to creating operators from net, you can also create operators from BlobReferences. Let's create the FC operator in this way.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 20,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"Y = X.FC([W, b], [\\\"Y\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Under the hood, `X.FC(...)` simply delegates to `net.FC` by inserting `X` as the first input of the corresponding operator, so what we did above is equivalent to\\n\",\n    \"```\\n\",\n    \"Y = net.FC([X, W, b], [\\\"Y\\\"])\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"Let's take a look at the current network.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 21,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Current network proto:\\n\",\n      \"\\n\",\n      \"name: \\\"my_first_net\\\"\\n\",\n      \"op {\\n\",\n      \"  output: \\\"X\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"GaussianFill\\\"\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"std\\\"\\n\",\n      \"    f: 1.0\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"run_once\\\"\\n\",\n      \"    i: 0\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"shape\\\"\\n\",\n      \"    ints: 2\\n\",\n      \"    ints: 3\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"mean\\\"\\n\",\n      \"    f: 0.0\\n\",\n      \"  }\\n\",\n      \"}\\n\",\n      \"op {\\n\",\n      \"  output: \\\"W\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"GaussianFill\\\"\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"std\\\"\\n\",\n      \"    f: 1.0\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"run_once\\\"\\n\",\n      \"    i: 0\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"shape\\\"\\n\",\n      \"    ints: 5\\n\",\n      \"    ints: 3\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"mean\\\"\\n\",\n      \"    f: 0.0\\n\",\n      \"  }\\n\",\n      \"}\\n\",\n      \"op {\\n\",\n      \"  output: \\\"b\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"ConstantFill\\\"\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"run_once\\\"\\n\",\n      \"    i: 0\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"shape\\\"\\n\",\n      \"    ints: 5\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"value\\\"\\n\",\n      \"    f: 1.0\\n\",\n      \"  }\\n\",\n      \"}\\n\",\n      \"op {\\n\",\n      \"  input: \\\"X\\\"\\n\",\n      \"  input: \\\"W\\\"\\n\",\n      \"  input: \\\"b\\\"\\n\",\n      \"  output: \\\"Y\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"FC\\\"\\n\",\n      \"}\\n\",\n      \"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Current network proto:\\\\n\\\\n{}\\\".format(net.Proto()))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Too verbose huh? Let's try to visualize it as a graph. Caffe2 ships with a very minimal graph visualization tool for this purpose.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 22,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<IPython.core.display.Image object>\"\n      ]\n     },\n     \"execution_count\": 22,\n     \"metadata\": {\n      \"image/png\": {\n       \"width\": 800\n      }\n     },\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"from caffe2.python import net_drawer\\n\",\n    \"from IPython import display\\n\",\n    \"graph = net_drawer.GetPydotGraph(net, rankdir=\\\"LR\\\")\\n\",\n    \"display.Image(graph.create_png(), width=800)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"So we have defined a Net, but nothing has been executed yet. Remember that the net above is essentially a protobuf that holds the definition of the network. When we actually run the network, what happens under the hood is:\\n\",\n    \"- A C++ net object is instantiated from the protobuf\\n\",\n    \"- The instantiated net's Run() function is called\\n\",\n    \"\\n\",\n    \"Before we do anything, we should clear any earlier workspace variables with `ResetWorkspace()`.\\n\",\n    \"\\n\",\n    \"Then there are two ways to run a net from Python. We will do the first option in the example below.\\n\",\n    \"\\n\",\n    \"1. Call `workspace.RunNetOnce()`, which instantiates, runs and immediately destructs the network \\n\",\n    \"2. Call `workspace.CreateNet()` to create the C++ net object owned by the workspace, then call `workspace.RunNet()`, passing the name of the network to it\\n\",\n    \"    \\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 23,\n   \"metadata\": {\n    \"scrolled\": false\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Current blobs in the workspace: []\\n\",\n      \"Blobs in the workspace after execution: [u'W', u'X', u'Y', u'b']\\n\",\n      \"W:\\n\",\n      \"[[ 4.6133149e-01  2.6810661e-01 -1.0076044e+00]\\n\",\n      \" [ 1.2857534e+00 -7.2475618e-01  6.1970286e-04]\\n\",\n      \" [-2.4734004e-01  1.1377212e+00  1.2011141e+00]\\n\",\n      \" [ 6.0975075e-01 -6.4694040e-02  5.8932066e-01]\\n\",\n      \" [ 6.5264893e-01 -6.8908274e-01  1.4284889e-01]]\\n\",\n      \"X:\\n\",\n      \"[[-0.09317103 -1.3330355  -0.60921687]\\n\",\n      \" [ 0.7734029  -0.1529701  -0.2614098 ]]\\n\",\n      \"Y:\\n\",\n      \"[[ 1.2134712   1.8459532  -1.2253168   0.67040426  1.7707379 ]\\n\",\n      \" [ 1.5791805   2.1051095   0.32068616  1.327425    1.5728276 ]]\\n\",\n      \"b:\\n\",\n      \"[1. 1. 1. 1. 1.]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"workspace.ResetWorkspace()\\n\",\n    \"print(\\\"Current blobs in the workspace: {}\\\".format(workspace.Blobs()))\\n\",\n    \"workspace.RunNetOnce(net)\\n\",\n    \"print(\\\"Blobs in the workspace after execution: {}\\\".format(workspace.Blobs()))\\n\",\n    \"# Let's dump the contents of the blobs\\n\",\n    \"for name in workspace.Blobs():\\n\",\n    \"    print(\\\"{}:\\\\n{}\\\".format(name, workspace.FetchBlob(name)))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now let's try the second way to create the net, and run it. First, clear the variables with `ResetWorkspace()`. Then create the net with the workspace's `net` object that we created earlier using `CreateNet(net_object)`. Finally, run the net with `RunNet(net_name)`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 24,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Current blobs in the workspace: []\\n\",\n      \"Blobs in the workspace after execution: [u'W', u'X', u'Y', u'b']\\n\",\n      \"W:\\n\",\n      \"[[-0.49780178  0.0896128  -1.1650279 ]\\n\",\n      \" [-1.9277409   0.91667885  1.2152035 ]\\n\",\n      \" [-0.80195075 -0.17179072  0.8195034 ]\\n\",\n      \" [ 0.21857451 -0.9942886   0.9358572 ]\\n\",\n      \" [-0.7295994  -1.085294    1.1550978 ]]\\n\",\n      \"X:\\n\",\n      \"[[-0.94531673  0.2626604  -1.3781935 ]\\n\",\n      \" [-0.15628844  1.2679638   0.40703335]]\\n\",\n      \"Y:\\n\",\n      \"[[ 3.099752    1.3883154   0.58354056 -0.7575747  -0.18730962]\\n\",\n      \" [ 0.71722126  2.9582276   1.2410765   0.0860424   0.20807779]]\\n\",\n      \"b:\\n\",\n      \"[1. 1. 1. 1. 1.]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"workspace.ResetWorkspace()\\n\",\n    \"print(\\\"Current blobs in the workspace: {}\\\".format(workspace.Blobs()))\\n\",\n    \"workspace.CreateNet(net)\\n\",\n    \"workspace.RunNet(net.Proto().name)\\n\",\n    \"print(\\\"Blobs in the workspace after execution: {}\\\".format(workspace.Blobs()))\\n\",\n    \"for name in workspace.Blobs():\\n\",\n    \"    print(\\\"{}:\\\\n{}\\\".format(name, workspace.FetchBlob(name)))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"There are a few differences between `RunNetOnce` and `RunNet`, but the main difference is the computational overhead. Since `RunNetOnce` involves serializing the protobuf to pass between Python and C and instantiating the network, it may take longer to run. Let's run a test and see what the time overhead is.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 25,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Run time per RunNetOnce: 9.96360778809e-05\\n\",\n      \"Run time per RunNet: 8.03422927856e-06\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# It seems that %timeit magic does not work well with\\n\",\n    \"# C++ extensions so we'll basically do for loops\\n\",\n    \"start = time.time()\\n\",\n    \"for i in range(1000):\\n\",\n    \"    workspace.RunNetOnce(net)\\n\",\n    \"end = time.time()\\n\",\n    \"print('Run time per RunNetOnce: {}'.format((end - start) / 1000))\\n\",\n    \"\\n\",\n    \"start = time.time()\\n\",\n    \"for i in range(1000):\\n\",\n    \"    workspace.RunNet(net.Proto().name)\\n\",\n    \"end = time.time()\\n\",\n    \"print('Run time per RunNet: {}'.format((end - start) / 1000))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"source\": [\n    \"Congratulations, you now know the many of the key components of the Caffe2 Python API! Ready for more Caffe2? Check out the rest of the tutorials for a variety of interesting use-cases!\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.14\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 1\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/Control_Ops.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Control Ops Tutorial\\n\",\n    \"\\n\",\n    \"In this tutorial we show how to use control flow operators in Caffe2 and give some details about their underlying implementations.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Conditional Execution Using NetBuilder\\n\",\n    \"\\n\",\n    \"Let's start with conditional operator. We will demonstrate how to use it in two Caffe2 APIs used for building nets: `NetBuilder` and `brew`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:root:This caffe2 python run does not have GPU support. Will run in CPU only mode.\\n\",\n      \"WARNING:root:Debug message: No module named caffe2_pybind11_state_gpu\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from __future__ import absolute_import\\n\",\n    \"from __future__ import division\\n\",\n    \"from __future__ import print_function\\n\",\n    \"from __future__ import unicode_literals\\n\",\n    \"\\n\",\n    \"from caffe2.python import workspace\\n\",\n    \"from caffe2.python.core import Plan, to_execution_step, Net\\n\",\n    \"from caffe2.python.net_builder import ops, NetBuilder\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In the first example, we define several blobs and then use the 'If' operator to set the value of one of them conditionally depending on values of other blobs.\\n\",\n    \"\\n\",\n    \"The pseudocode for the conditional examples we will implement is as follows:\\n\",\n    \"\\n\",\n    \"    if (x > 0):\\n\",\n    \"        y = 1\\n\",\n    \"    else:\\n\",\n    \"        y = 0\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"with NetBuilder() as nb:\\n\",\n    \"    # Define our constants\\n\",\n    \"    ops.Const(0.0, blob_out=\\\"zero\\\")\\n\",\n    \"    ops.Const(1.0, blob_out=\\\"one\\\")\\n\",\n    \"    ops.Const(0.5, blob_out=\\\"x\\\")\\n\",\n    \"    ops.Const(0.0, blob_out=\\\"y\\\")\\n\",\n    \"    # Define our conditional sequence\\n\",\n    \"    with ops.IfNet(ops.GT([\\\"x\\\", \\\"zero\\\"])):\\n\",\n    \"        ops.Copy(\\\"one\\\", \\\"y\\\")\\n\",\n    \"    with ops.Else():\\n\",\n    \"        ops.Copy(\\\"zero\\\", \\\"y\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note the usage of `NetBuilder`'s `ops.IfNet` and `ops.Else` calls: `ops.IfNet` accepts a blob reference or blob name as an input, it expects an input blob to have a scalar value convertible to bool. Note that the optional `ops.Else` is at the same level as `ops.IfNet` and immediately follows the corresponding `ops.IfNet`. Let's execute the resulting net (execution step) and check the values of the blobs.\\n\",\n    \"\\n\",\n    \"Note that since x = 0.5, which is indeed greater than 0, we should expect y = 1 after execution.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"x =  0.5\\n\",\n      \"y =  1.0\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Initialize a Plan\\n\",\n    \"plan = Plan('if_net_test')\\n\",\n    \"# Add the NetBuilder definition above to the Plan\\n\",\n    \"plan.AddStep(to_execution_step(nb))\\n\",\n    \"# Initialize workspace for blobs\\n\",\n    \"ws = workspace.C.Workspace()\\n\",\n    \"# Run the Plan\\n\",\n    \"ws.run(plan)\\n\",\n    \"# Fetch some blobs and print\\n\",\n    \"print('x = ', ws.blobs[\\\"x\\\"].fetch())\\n\",\n    \"print('y = ', ws.blobs[\\\"y\\\"].fetch())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Before going further, it's important to understand the semantics of execution blocks ('then' and 'else' branches in the example above), i.e. handling of reads and writes into global (defined outside of the block) and local (defined inside the block) blobs.\\n\",\n    \"\\n\",\n    \"`NetBuilder` uses the following set of rules:\\n\",\n    \"\\n\",\n    \" - In `NetBuilder`'s syntax, a blob's declaration and definition occur at the same time - we define an operator which writes its output into a blob with a given name.\\n\",\n    \" \\n\",\n    \" - `NetBuilder` keeps track of all operators seen before the current execution point in the same block and up the stack in parent blocks.\\n\",\n    \" \\n\",\n    \" - If an operator writes into a previously unseen blob, it creates a **local** blob that is visible only within the current block and the subsequent children blocks. Local blobs created in a given block are effectively deleted when we exit the block. Any write into previously defined (in the same block or in the parent blocks) blob updates an originally created blob and does not result in the redefinition of a blob.\\n\",\n    \" \\n\",\n    \" - An operator's input blobs have to be defined earlier in the same block or in the stack of parent blocks. \\n\",\n    \" \\n\",\n    \" \\n\",\n    \"As a result, in order to see the values computed by a block after its execution, the blobs of interest have to be defined outside of the block. This rule effectively forces visible blobs to always be correctly initialized.\\n\",\n    \"\\n\",\n    \"To illustrate concepts of block semantics and provide a more sophisticated example, let's consider the following net:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"with NetBuilder() as nb:\\n\",\n    \"    # Define our constants\\n\",\n    \"    ops.Const(0.0, blob_out=\\\"zero\\\")\\n\",\n    \"    ops.Const(1.0, blob_out=\\\"one\\\")\\n\",\n    \"    ops.Const(2.0, blob_out=\\\"two\\\")\\n\",\n    \"    ops.Const(1.5, blob_out=\\\"x\\\")\\n\",\n    \"    ops.Const(0.0, blob_out=\\\"y\\\")\\n\",\n    \"    # Define our conditional sequence\\n\",\n    \"    with ops.IfNet(ops.GT([\\\"x\\\", \\\"zero\\\"])):\\n\",\n    \"        ops.Copy(\\\"x\\\", \\\"local_blob\\\")  # create local_blob using Copy -- this is not visible outside of this block\\n\",\n    \"        with ops.IfNet(ops.LE([\\\"local_blob\\\", \\\"one\\\"])):\\n\",\n    \"            ops.Copy(\\\"one\\\", \\\"y\\\")\\n\",\n    \"        with ops.Else():\\n\",\n    \"            ops.Copy(\\\"two\\\", \\\"y\\\")\\n\",\n    \"    with ops.Else():\\n\",\n    \"        ops.Copy(\\\"zero\\\", \\\"y\\\")\\n\",\n    \"        # Note that using local_blob would fail here because it is outside of the block in\\n\",\n    \"        # which it was created\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"When we execute this, we expect that y == 2.0, and that `local_blob` will not exist in the workspace.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"x =  1.5\\n\",\n      \"y =  2.0\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Initialize a Plan\\n\",\n    \"plan = Plan('if_net_test_2')\\n\",\n    \"# Add the NetBuilder definition above to the Plan\\n\",\n    \"plan.AddStep(to_execution_step(nb))\\n\",\n    \"# Initialize workspace for blobs\\n\",\n    \"ws = workspace.C.Workspace()\\n\",\n    \"# Run the Plan\\n\",\n    \"ws.run(plan)\\n\",\n    \"# Fetch some blobs and print\\n\",\n    \"print('x = ', ws.blobs[\\\"x\\\"].fetch())\\n\",\n    \"print('y = ', ws.blobs[\\\"y\\\"].fetch())\\n\",\n    \"# Assert that the local_blob does not exist in the workspace\\n\",\n    \"# It should have been destroyed because of its locality\\n\",\n    \"assert \\\"local_blob\\\" not in ws.blobs\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Conditional Execution Using Brew Module\\n\",\n    \"\\n\",\n    \"Brew is another Caffe2 interface used to construct nets. Unlike `NetBuilder`, `brew` does not track the hierarchy of blocks and, as a result, we need to specify which blobs are considered local and which blobs are considered global when passing 'then' and 'else' models to an API call.\\n\",\n    \"\\n\",\n    \"Let's start by importing the necessary items for the `brew` API.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from caffe2.python import brew\\n\",\n    \"from caffe2.python.workspace import FeedBlob, RunNetOnce, FetchBlob\\n\",\n    \"from caffe2.python.model_helper import ModelHelper\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We will use the Caffe2's `ModelHelper` class to define and represent our models, as well as contain the parameter information about the models. Note that a `ModelHelper` object has two underlying nets:\\n\",\n    \"\\n\",\n    \"    (1) param_init_net: Responsible for parameter initialization\\n\",\n    \"    (2) net: Contains the main network definition, i.e. the graph of operators that the data flows through\\n\",\n    \"\\n\",\n    \"Note that `ModelHelper` is similar to `NetBuilder` in that we define the operator graph first, and actually run later. With that said, let's define some models to act as conditional elements, and use the `brew` module to form the conditional statement that we want to run. We will construct the same statement used in the first example above.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Initialize model, which will represent our main conditional model for this test\\n\",\n    \"model = ModelHelper(name=\\\"test_if_model\\\")\\n\",\n    \"\\n\",\n    \"# Add variables and constants to our conditional model; notice how we add them to the param_init_net\\n\",\n    \"model.param_init_net.ConstantFill([], [\\\"zero\\\"], shape=[1], value=0.0)\\n\",\n    \"model.param_init_net.ConstantFill([], [\\\"one\\\"], shape=[1], value=1.0)\\n\",\n    \"model.param_init_net.ConstantFill([], [\\\"x\\\"], shape=[1], value=0.5)\\n\",\n    \"model.param_init_net.ConstantFill([], [\\\"y\\\"], shape=[1], value=0.0)\\n\",\n    \"\\n\",\n    \"# Add Greater Than (GT) conditional operator to our model\\n\",\n    \"#  which checks if \\\"x\\\" > \\\"zero\\\", and outputs the result in the \\\"cond\\\" blob\\n\",\n    \"model.param_init_net.GT([\\\"x\\\", \\\"zero\\\"], \\\"cond\\\")\\n\",\n    \"\\n\",\n    \"# Initialize a then_model, and add an operator which we will set to be\\n\",\n    \"#  executed if the conditional model returns True\\n\",\n    \"then_model = ModelHelper(name=\\\"then_test_model\\\")\\n\",\n    \"then_model.net.Copy(\\\"one\\\", \\\"y\\\")\\n\",\n    \"\\n\",\n    \"# Initialize an else_model, and add an operator which we will set to be\\n\",\n    \"#  executed if the conditional model returns False\\n\",\n    \"else_model = ModelHelper(name=\\\"else_test_model\\\")\\n\",\n    \"else_model.net.Copy(\\\"zero\\\", \\\"y\\\")\\n\",\n    \"\\n\",\n    \"# Use the brew module's handy cond operator to facilitate the construction of the operator graph\\n\",\n    \"brew.cond(\\n\",\n    \"    model=model,                               # main conditional model\\n\",\n    \"    cond_blob=\\\"cond\\\",                          # blob with condition value\\n\",\n    \"    external_blobs=[\\\"x\\\", \\\"y\\\", \\\"zero\\\", \\\"one\\\"],  # data blobs used in execution of conditional\\n\",\n    \"    then_model=then_model,                     # pass then_model\\n\",\n    \"    else_model=else_model)                     # pass else_model\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Before we run the model, let's use Caffe2's graph visualization tool `net_drawer` to check if the operator graph makes sense.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAABIMAAAFbCAYAAABGXk4SAAAAAXNSR0IArs4c6QAAQABJREFUeAHsnQecVNX1xw9SpPfeFaQp0hSpEQULgkqRxA4xtsS/LWrUmNhijMYSe+9YsCso2BUbgiAgIIKoVOkdpIr/971w17ezM7uzZXan/A6f4b155ZbvezM79/fOObfUr7/++oLJREAEREAEREAEREAEREAEREAEREAEREAEMoHAx2WCXg7LhJ6qjyIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgArZ1L0EQAREQAREQAREQAREQAREQAREQAREQARHIHAISgzLnWqunIiACIiACIiACIiACIiACIiACIiACImASg3QTiIAIiIAIiIAIiIAIiIAIiIAIiIAIiEAGESBnUDbbvHObNXpqRLZteiMCyUigfc1m9smgm5KxaWpTihLYtm2bLV682H744Qf78ccfs73mzZtna9euTdGelVyzg0kKSq7yFKx5r732siZNmtg+++xj++67r1uy7l/169e3UqVKpWDP1GQREAEREAEREAEREIFkIpBDDEqmxqktIpAbgR27fsltt/aJQA4Cu3btsqVLl+YQeRYuXOi2LVq0yLx4Ubt27awB+GGHHWZnnHGG1axZM0eZ2pA7AcQNWfwEdu7cme3+/Pjjj51AuXXrVldIuXLlnEjUrFkza9GiRdY96sWiGjVqxF+ZjhQBERABERABERABEchYAhKDMvbSq+MikJ4E1qxZE9Wzx3v67Nixw3W8cuXK1rx5czeY7tChgw0aNCibJwb7ZSKQDAQQKPFY8/ewX86YMcNGjx7tBM5fftktjlepUiVLJIr0LGratKlVqlQpGbqkNoiACIiACIiACIiACJQwgVLBj8xsPvwKEyvhK6Lq4ybQpnpj+2LILXEfrwPTg8CmTZtswYIFOQbGDJAJ5dq8ebPraNmyZY3Br/eYYBkeHNepUyc9gKgXGU8AgXP+/PlZn4nIMMfVq1dnMSLMLPyZCK83btzY+NzIREAEREAEREAEREAE0p7ASHkGpf01VgdFILUIbN++3QjX8t4PkcsVK1a4DpE3pUGDBlkD2+OOOy6b2MPAViFKqXXt1dqCEUDA2W+//dwrWgkIpN9//32Oz9Trr7/uhNWNGze600qXLp2VryhSPOV9vXr1lK8oGmBtEwEREAEREAEREIEUJCAxKAUvmposAqlMgLw9y5YtyzEw9aIPQhDHYOQ/8Z4LvXv3tuHDh2e9J8Rr7733TmUUarsIFAsBQsMOPPBA94pW4cqVK7N9Hr1n0fjx450wi0CLlS9fPuvz5z+X4WX16tWjFa9tIiACIiACIiACIiACSUhAYlASXhQ1SQRSnQB5e7y4E23pB5cVKlTIytuz//7727HHHpttsFmtWrVUR6H2i0DSEyBkklfXrl1ztJVI8mieetOnT7fXXnvN5Svy4i2f13AoZnidhNd83mUiIAIiIAIiIAIiIALJQUA5g5LjOqgVBSCgnEEFgFZEpxB2EitvD+EoPuykTJkyRriW9x4IDw7ZRv4SmQiIQOoSQNgN5ytC/PWeRawjDHsLh3X67wS/5HuC7wuZCIiACIiACIiACIhAsRBQzqBiwaxKRCDFCJCQNpo3gPfyWb58eVaPyCPiB3QDBgzIWmdbkyZNNMDLIqUVEUg/Akx136pVK/eK1jsSvkfLV/Tqq686QZn9GPmKckv4XrduXeUrigZY20RABERABERABESggAT0GK6A4HSaCKQyAUI/8srb46eqrlq1apbA06NHDzvllFOy3pO3p2LFiqmMQm0XARFIIIHKlStbhw4d3CtaNSSE9yKzX+JZ9OGHHzpBGmEaI8TMi87RlgopjUZX20RABERABERABEQgNgGJQbHZaI8IpDSBtWvX5hhk+cEWYR1bt251/SMJM/k8GGC1adPG+vfvn23QVbNmzZTmoMaLgAgkLwE8fngdcsghORpJLqJoHopTp061V155xeUrQtjGSF4dGYbq3/P9RvJrmQiIgAiIgAiIgAiIwG8EJAb9xkJrIpBSBLZs2ZIjV4cXe3iyvn79etcfpldv1KiRE3gYHHXr1i2b2NOwYUOFX6TUlVdjRSAzCPDdhZDDq0+fPjk6vW3btqjfgR988IETwhHEsVKlSlle+YoIU5OJgAiIgAiIgAiIQCYRkBiUSVdbfU0pAjt37oz6VNwLPoR5eatdu3aW2HPUUUdlE3sYSJUtW9YfqqUIiIAIpAUBvBpbt27tXtE6tGHDhmzJrP1351dffeVEpJ9//tmdRuLqyHxFeEp6zyI8l2QiIAIiIAIiIAIikG4EJAal2xVVf1KGAOENJGL2A5TwEs8ewiN83h7ybpCfh8EJ0z//4Q9/yCb4sF8mAiIgAiLwGwHynXXs2NG9ftv621qs79/333/fff8iyGPkRYuWp8hvox6ZCIiACIiACIiACKQaAYlBqXbF1N60IDBu3DgbOnSoEeqF4bnDk2nEnpYtW9oRRxyRbfBRp06dtOi3OiECIiACyUKAmRB5ETobaQjxixcvzuFZNGXKFHvppZdcAn6fr4i8aiTCVqhZJEW9FwEREAEREAERSGYCEoOS+eqobWlLgPAEhCBmzEEAaty4sZEfQyYCIiACIlDyBBB2fL6iww47LEeDSMA/P0jE//zzz9u1115rXhjKcaA2iIAIiIAIiIAIiECSEpAYlKQXRs3KDAKHHnqokjdnxqVWL0VABNKIALOTMftihw4d0qhX6ooIiIAIiIAIiEAmEZArQiZdbfVVBERABERABERABERABERABERABEQg4wkUuRjUqFJNO6ZpF7us42C7pMMgG7RPN2tWuY41rVzbutVrndTA29dsZpd3HGrXH3yKDdmnu9UqX8V+36JXUrc5t8Z1rLWvVShdLrdDCrWvfoUa1qfhAa6MmntXsb6Nsj8hLbdXGTusYXu7sP2x1rXuflYq+Be2gc0ODr/VuggUOwESdZ9xxhkuN0hk5Z988ondcMMNduqpp9rrr78euTtp3s+ePdtuvfVWe/fdd+Nq044dO4wEuRdffLGNHTs26jmEL44ePTrqvkRv/P777+3uu++2V199NdFVJU35t99+u913331R21NU9+G3337r7hOmXZeJgAiIgAiIgAiIgAiIQJGJQWX3Ku1ElClD/2eHBKLP9FXzbdLyuda8Sl0bf/yNNn3YXdaldoukJX5iy942dsA1tnbbJhu3cLJ1qdPCJg251W7rcUbStjm3hh3dpLMTs7b8sj23wwq1b0Sbw23YHrFs6L7d7fTWv+VVqF2+qk0aeps1CUTAkXM/sgGB8DPqiEuzCUIrtqyzO3ueZaVLFdltWKj+FPTkSy65xK688kqbMWNGQYvQeSVEgCmmH3/88RzXjiSxt9xyi/3tb3+zVq1audnb/DTUJdTUqNUinDz44IN22WWXRRW0op3EffrCCy/YHXfcYT/99FO0Q+zee+81kuIWt9GfRx55xC644AL7+uuvi7v6Eqvvscces6eeeipH/UV1Hy5ZssTuuusud58wa6FMBERABERABERABERABIpkFL536bL27sB/2fDWh9ugt2+0a758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EAERSAUCIyUGpcJlUhujEkhlMShqh/ZsJDGwH5RELklky8AFq1ixopttyQ9IIpclFR6zpxtaiIAIiEBSEEAcXLp0adb3Kt484e9WvldJFI7xvYmHZuT3Ke/x2tT3alJcUjVCBERABERABESg8AQSk0C68O1SCSKQuQTq169vvLp3754DAgOW8BNsP6BhunCmN2aqcO8VwXTkTM0dOajBswhvIz/Fdo5KtEEEREAEUowA093778NIsQfvnm3btrkekXCd5Ote3DniiCOyviP5bqxXr16K9VzNFQEREAEREAEREIGCEVDOoIJx01kiUCIEmNmI0DFe0ZLJMuCJHAgxQCKHCst169a5djPTkB8QRYpFvCdfUaxptEuk46pUBEQgowls2bLFEHX4Hov2HUd4IMZ3G2K6/1476KCDstbZhtePvtsy+lZS50VABERABERABPYQkBikW0EE0ogA3j5t27Z1r2jd2rBhg33//fdZT9D9k/SJEyc6jyNmYcJ4eo7g5AdU4SVPz2vVqhWteG0TAREQgQIRYPY3wrViiT2Ez3qrXr161ndT3759s9Z9PjV5PXpSWoqACIiACIiACIhAbAISg2Kz0R4RSDsCVatWtU6dOrlXtM6F82p4oWju3Ln29ttvu4EaAzasUqVKUUPQEI2aNWtmVapUiVa8tomACGQwAcJYY4k9eP34EFcSfnsxunPnzjZ06NAswYfQV77HZCIgAiIgAiIgAiIgAoUjIDGocPx0tgikFYEGDRoYrx49euTo1y+//BI1XxFeRc8995ytWLEiazBXp06drMFb2KuIdcI0ypUrl6N8bRABEUhtAoRqIepEC+NCBCLUC2OmxHCYKiGvzKw4YcIEQ3zGswcRaMiQIXb00Ue7ZPmpTUatFwEREAEREAEREIHkIyAxKPmuiVokAklJoHTp0lkCT7QGbt26Neog8K233nLeAD6nB/k6wgPBSLEIMUo5PaIR1jYRKFkC5CRbsGBB1M85Ys+aNWtcA8nbQyJm/9kePHhw1roXhPk+iWaU/+qrr7rXsGHDnDB0zDHH2IknnmgDBgwwvIZkIiACIiACIiACIiAChSegqeULz1AllBCBdJ1avoRwJrxaklfH8hhgAOi9BvAa8iEifjAZXipfUcIvlSrIUALMVrhkyZKYn9Offvopy/uvWrVq2QQePqM+Zw/r5cuXLzRFvA1ff/11N1PiRx995DyEjj/+eCcMHXnkkS63WaErUQEiIAIiIAIiIAIikJkERkoMyswLnxa9lhiUFpfRdYJcIdHyFfm8RSSWJUwNIx9ReNAZFooQkchnJBMBEYhOYNWqVTHFHkTZHTt2uBPxwCH/V/jz5df5/JHEuTiNfEMvvPCCjRo1yoWT1ahRw/AcGjFihHXr1q04m6K6REAEREAEREAERCAdCEgMSoermKl9kBiUOVeexNULFy7MMQuaF4sYKHqrW7du1AEsA1nyFTFTmkwE0pXApk2bYn5O+LywHyNMKzJcMyyyMj074V7JaIhWzz//vI0cOdJmzpxprVu3tuHDh9vpp59ujRo1SsYmq00iIAIiIAIiIAIikGwEJAYl2xVRe+InIDEoflbpfuTPP/+c6wB4w4YNDgG5iJo0aRJTLCJfUbIOgNP9Gqp/8RHAcwcxxAuhkcuVK1dmFRTO2+O9evwSYZREzqluU6ZMsSeeeMIlsV+7dq3169fP/vjHP7rk00pUn+pXV+0XAREQAREQARFIIAGJQQmEq6ITTEBiUIIBp1HxJLaNHDT798x+RGJcjFmMcstXVLNmzTSioq4kIwFCJsnN4+/PyCUhk+T2wcjbE75fw549bK9YsWIydjEhbdq+fbuNGTPGCUPjxo0zPqtnnHGGnX322S6sNCGVqlAREAEREAEREAERSF0CEoNS99qp5e1qNLXPB98sECJQKAJ5Db5JqOvzFclrKP+o4SsrGIHcxEmEH/LmyHIS4DP7yCOP2MMPP+yENZJNn3vuuXbssce68LicZ2iLCIiACIiACIiACGQcgZxi0C+/7rIxC77MOBLqcOoRqF6ukvVpeEDqNVwtTikChOX4fEWEocjyR4DQPFn+CJCvh3AuhS3mj1vk0Yi4eAs98MAD9s4777h8Queff76dddZZEtIiYem9CIiACIiACIhAphHIKQZlGgH1VwREQAREQAREIL0J/PDDD3bvvffao48+aiSkJ+H0hRdeaK1atUrvjqt3IiACIiACIiACIhCdgMSg6Fy0VQREQAREQAREIN0IbNy40R5//HG78847XV6mAQMG2CWXXGJ9+vRJt66qPyIgAiIgAiIgAiKQG4GR8t/PDY/2iYAIiIAIiIAIpA2BKlWq2AUXXGDfffedvfLKK8ZMg4cddpj16NHD3nzzzbTppzoiAiIgAiIgAiIgAnkRkBiUFyHtFwEREAEREAERSCsC5LIaNGiQjR8/3j7//HM3+9jAgQOtY8eO9vzzz2fN2JZWnVZnREAEREAEREAERCBEQGJQCIZWRUAEREAEREAEMotA9+7d7Y033rBp06ZZmzZt7OSTT3bLp59+WqJQZt0K6q0IiIAIiIAIZBQBiUEZdbnVWREQAREQAREQgWgEOnToYKNGjbJvv/3WevbsaSNGjLD27dvbyy+/bL/++mu0U7RNBERABERABERABFKWgMSglL10argIiIAIiIAIiEBRE9hvv/1ckulZs2Y5MWjYsGHWpUsXGzt2bFFXpfJEQAREQAREQAREoMQISAwqMfSqWAREQAREQAREIFkJtG7d2nkKET7WtGlTY+ax3r1726RJk5K1yWqXCIiACIiACIiACMRNQGJQ3Kh0oAiIgAiIgAiIQKYROPDAA+21116ziRMnWqlSpaxbt252yimn2MKFCzMNhforAiIgAiIgAiKQRgQkBqXRxVRXREAEREAEREAEEkOga9eu9vHHH9tLL73kvIPwHPr73/9uGzduTEyFKlUEREAEREAEREAEEkhAYlAC4apoERABERABERCB9CIwZMgQI5/QjTfeaA888ICRY+iZZ55Jr06qNyIgAiIgAiIgAmlPQGJQ2l9idVAEREAEREAERKAoCZQrV84uvvhimzdvng0ePNhOP/10O/zww91MZEVZj8oSAREQAREQAREQgUQRkBiUKLIqVwREQAREQAREIK0J1KxZ0+6//36bMGGCrV+/3pie/qqrrrItW7akdb/VOREQAREQAREQgdQnIDEo9a+heiACIiACIiACIlCCBMgnxCxjt956q91zzz1uSnryC8lEQAREQAREQAREIFkJSAxK1iujdomACIiACIiACKQMgdKlS9v555/vQsXatWtnffr0sQsvvNB+/vnnlOmDGioCIiACIiACIpA5BCQGZc61Vk9FQAREQAREQAQSTKBBgwY2evRoe+KJJ2zkyJEudOzTTz9NcK0qXgREQAREQAREQATyR0BiUP546WgREAEREAEREAERyJMASaVnzpxpTEF/6KGH2t/+9jfbsWNHnufpABEQAREQAREQAREoDgISg4qDsuoQAREQAREQARHIOAINGza0N954wx5++GG77777rEePHm4GsowDoQ6LgAiIgAiIgAgkHQGJQUl3SdQgERABERABERCBdCJwxhln2FdffWW7du2yTp06ufCxdOqf+iICIiACIiACIpB6BCQGpd41U4tFQAREQAREQARSjECrVq3cFPRnnXWWDR8+3E499VTbtGlTivVCzRUBERABERABEUgXAqV+DSxdOqN+iIAIiIAIiIAIiECyExg3bpwThOrUqWOvvPKKyyuU7G1W+0RABERABERABNKKwEh5BqXV9VRnREAEREAEREAEkp1A//79XdhYlSpV7OCDD3aCULK3We0TAREQAREQARFILwI5PIM279xmjZ4akV69VG/SkkD7ms3sk0E3pWXf1KmSI7Bt2zZbsGCB/fjjj/bDDz9kW37//fe2YcOGkmtcCtYs59P4L1qpUqWMacn33Xdf22effXIsSUa81156hhM/0eQ/cvv27XbhhRfaAw884GYbu/HGG6106dLJ33C1UAREQASShMCMGTNsxIgRTmAviSb9/ve/t3vvvddq165dEtWrThEoDIGRZQpzts4VgZIksGPXLyVZvepOUQKIE0uXLs0m8sybN88JQIg/S5YsMS9g1KhRI2tQ3rdvXzvnnHOMbbL4CUi8iJ/VL7/8YosWLcq6N7/88kt3X27ZssUVUq5cOWvevHnWPRkpGlWvXj3+ynRkUhDgmt5///3WrVs3O/fcc23KlCn24osv6nsmKa6OGiECIpDMBHbu3Gk33XST/etf/7KDDjrIRo0aVewPTHhAeN1111m7du3cjJEnnHBCMiNT20QgBwF5BuVAog2pQqBN9cb2xZBbUqW5amcxEuCPczTPHu/pg/cPVr58eWvatGkODwwG2S1atLCqVasWY6tVlQjkJODFS3/vRi5/+uknN0MVZyIGce9GikS8b9asmSE8yJKXwNSpU+34449330tMR0/CaZkIiIAIiEBOAt4baPbs2U4Muvjii4tdCPKt4jfnJZdcYo888ojJS8hT0TJFCIyUGJQiV0rNzElAYlBOJpmyhdAKPCgiB8a8J5Rr7dq1DgVeKfXr1486OCYMp1GjRkZojkwEUpUAn4X58+dH/SwgiK5bt851jc8C93u08DPEIj4n+iyU/F2wbNkyGzRokM2dO9d5COGRKBMBERABEdhNINIb6PHHH08a4fydd96xM88807Zu3SovId2wqUJAYlCqXCm1MycBiUE5maTTluXLl8cc4CIE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YsWKubVLbDcJkAAJkAAJkAAJ+BCgGOSDg19IgARIgARIgATikQDcwSZPniw1atRQEahw4cKyePFi+eabb6R8+fLxiIR9JgESIAESIAESiGECFINi+OCyayRAAiRAAiRAAokTQDDosWPHSuXKlaVZs2Zy2WWXyZIlSzRDWLVq1RLfmEtJgARIgARIgARIwKUEKAa59MCx2SRAAiRAAiRAAikncPLkSXnvvfekdOnS0qZNG6lUqZKmiv/22281TlDKa+aWJEACJEACJEACJOB8AsyH6vxjxBaSAAmQAAmQAAmEicCuXbtUBPrggw/k1KlT8vDDD8uTTz6psYHCtAtWQwIkQAIkQAIkQAKOJ0AxyPGHiA0kARIgARIgARJILYE//vhDhg4dKuPGjZM8efJIjx49pEOHDpIvX77UVs3tSYAESIAESIAESMB1BCgGue6QscEkQAIkQAIkQAKhEDh9+rR8+eWXMmzYMIEYVKVKFRkxYoQ88MADkjFjxlCq4DokQAIkQAIkQAIkEJMEGDMoJg8rO0UCJEACqSeAILqIo3LRRRdF5a9Vq1Zy+PDh1HeENcQdgQ0bNqjrV5EiReSxxx6TkiVLypw5c2Tp0qUaH4hCUNydEuwwCZAACZAACZCAH4GLrFSqHr95/EoCJEACJBDHBM6ePSsDBgyQV155RWrXri2dOnVSMSgtkUAE6t+/v/z777+C2C7I8sRCAokRQEDor7/+Wj7++GMVfooVKyaPPvqotGvXTgoUKJDYplxGAiRAAiRAAiRAAvFGYAzdxOLtkLO/JEACJJAIAVgDPfTQQ/Lnn3/Km2++KV26dElzIcg075577pHu3btL8+bNBVZCiPeSK1cus5hTElACCxYskFGjRskXX3whEIQgHE6dOlVuueUWufhiGkDzNCEBEiABEiABEiCBQARoGRSICueRAAmQQJwR8LcGGjlypFxxxRWOoPD999+rqw8MWYcPH04rIUccleg2YsuWLfLZZ5/JmDFjZOPGjVK+fHlp27attG7dmgGho3touHcSIAESIAESIAF3EBhDMcgdB4qtJAESIIGIEbBbA8E1LJrWQME6CbcxWAlh8E8roWCUYnv+7t27ZcKECZoNbP78+Sr6IBA0zofq1avHdufZOxIgARIgARIgARIILwGKQeHlydpIgARIwD0EnGwNFIwirYSCkYnN+bt2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\\n\",\n      \"text/plain\": [\n       \"<IPython.core.display.Image object>\"\n      ]\n     },\n     \"execution_count\": 8,\n     \"metadata\": {\n      \"image/png\": {\n       \"width\": 800\n      }\n     },\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"from caffe2.python import net_drawer\\n\",\n    \"from IPython import display\\n\",\n    \"graph = net_drawer.GetPydotGraph(model.net, rankdir=\\\"LR\\\")\\n\",\n    \"display.Image(graph.create_png(), width=800)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now let's run the net! When using `ModelHelper`, we must first run the `param_init_net` to initialize paramaters, then we execute the main `net`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"x =  [0.5]\\n\",\n      \"y =  [1.]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Run param_init_net once\\n\",\n    \"RunNetOnce(model.param_init_net)\\n\",\n    \"# Run main net (once in this case)\\n\",\n    \"RunNetOnce(model.net)\\n\",\n    \"# Fetch and examine some blobs\\n\",\n    \"print(\\\"x = \\\", FetchBlob(\\\"x\\\"))\\n\",\n    \"print(\\\"y = \\\", FetchBlob(\\\"y\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Loops Using NetBuilder\\n\",\n    \"\\n\",\n    \"Another important control flow operator is 'While', which allows repeated execution of a fragment of net. Let's consider `NetBuilder`'s version first.\\n\",\n    \"\\n\",\n    \"The pseudocode for this example is:\\n\",\n    \"\\n\",\n    \"    i = 0\\n\",\n    \"    y = 0\\n\",\n    \"    while (i <= 7):\\n\",\n    \"        y = i + y\\n\",\n    \"        i += 1\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"with NetBuilder() as nb:\\n\",\n    \"    # Define our variables\\n\",\n    \"    ops.Const(0, blob_out=\\\"i\\\")\\n\",\n    \"    ops.Const(0, blob_out=\\\"y\\\")\\n\",\n    \"    \\n\",\n    \"    # Define loop code and conditions\\n\",\n    \"    with ops.WhileNet():\\n\",\n    \"        with ops.Condition():\\n\",\n    \"            ops.Add([\\\"i\\\", ops.Const(1)], [\\\"i\\\"])\\n\",\n    \"            ops.LE([\\\"i\\\", ops.Const(7)])\\n\",\n    \"        ops.Add([\\\"i\\\", \\\"y\\\"], [\\\"y\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"As with the 'If' operator, standard block semantic rules apply. Note the usage of `ops.Condition` clause that should immediately follow `ops.WhileNet` and contains code that is executed before each iteration. The last operator in the condition clause is expected to have a single boolean output that determines whether the other iteration is executed.\\n\",\n    \"\\n\",\n    \"In the example above we increment the counter (\\\"i\\\") before each iteration and accumulate its values in \\\"y\\\" blob, the loop's body is executed 7 times, the resulting blob values:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"i =  8\\n\",\n      \"y =  28\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Initialize a Plan\\n\",\n    \"plan = Plan('while_net_test')\\n\",\n    \"# Add the NetBuilder definition above to the Plan\\n\",\n    \"plan.AddStep(to_execution_step(nb))\\n\",\n    \"# Initialize workspace for blobs\\n\",\n    \"ws = workspace.C.Workspace()\\n\",\n    \"# Run the Plan\\n\",\n    \"ws.run(plan)\\n\",\n    \"# Fetch blobs and print\\n\",\n    \"print(\\\"i = \\\", ws.blobs[\\\"i\\\"].fetch())\\n\",\n    \"print(\\\"y = \\\", ws.blobs[\\\"y\\\"].fetch())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Loops Using Brew Module\\n\",\n    \"\\n\",\n    \"Now let's take a look at how to replicate the loop above using the `ModelHelper`+`brew` interface.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Initialize model, which will represent our main conditional model for this test\\n\",\n    \"model = ModelHelper(name=\\\"test_while_model\\\")\\n\",\n    \"\\n\",\n    \"# Add variables and constants to our model\\n\",\n    \"model.param_init_net.ConstantFill([], [\\\"i\\\"], shape=[1], value=0)\\n\",\n    \"model.param_init_net.ConstantFill([], [\\\"one\\\"], shape=[1], value=1)\\n\",\n    \"model.param_init_net.ConstantFill([], [\\\"seven\\\"], shape=[1], value=7)\\n\",\n    \"model.param_init_net.ConstantFill([], [\\\"y\\\"], shape=[1], value=0)\\n\",\n    \"\\n\",\n    \"# Initialize a loop_model that represents the code to run inside of loop\\n\",\n    \"loop_model = ModelHelper(name=\\\"loop_test_model\\\")\\n\",\n    \"loop_model.net.Add([\\\"i\\\", \\\"y\\\"], [\\\"y\\\"])\\n\",\n    \"\\n\",\n    \"# Initialize cond_model that represents the conditional test that the loop\\n\",\n    \"#  abides by, as well as the incrementation step\\n\",\n    \"cond_model = ModelHelper(name=\\\"cond_test_model\\\")\\n\",\n    \"cond_model.net.Add([\\\"i\\\", \\\"one\\\"], \\\"i\\\")\\n\",\n    \"cond_model.net.LE([\\\"i\\\", \\\"seven\\\"], \\\"cond\\\")\\n\",\n    \"\\n\",\n    \"# Use brew's loop operator to facilitate the creation of the loop's operator graph\\n\",\n    \"brew.loop(\\n\",\n    \"    model=model,             # main model that contains data\\n\",\n    \"    cond_blob=\\\"cond\\\",        # explicitly specifying condition blob\\n\",\n    \"    external_blobs=[\\\"cond\\\", \\\"i\\\", \\\"one\\\", \\\"seven\\\", \\\"y\\\"], # data blobs used in execution of the loop\\n\",\n    \"    loop_model=loop_model,   # pass loop_model\\n\",\n    \"    cond_model=cond_model    # pass condition model (optional)\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Once again, let's visualize the net using the `net_drawer`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAABUAAAAHrCAYAAAAOkjcFAAAAAXNSR0IArs4c6QAAQABJREFUeAHsnQecFEXah18lSJKcs4IIAoKiCAIKiiiKCpj1VM54p2f2PuMZ0DOenukMp54B850BEQUUFFFBQRQJIqISJOccJHz9FNTYO0za3dnd2dl/8Ru6p7u6uurpnt7uf79ht+3bt79hKiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiKQfQTm7hYIoNuzb1wakQiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAjY1N0FQQREQAREQAREQAREQAREQAREQAREQAREQAREQASylYAE0Gw9shqXCIiACIiACIiACIiACIiACIiACIiACIiACIiASQDVSSACIiACIiACIiACIiACIiACIiACIiACIiACIpC1BEpHj2zdlk3W4MUB0Yv1XQQyjkDb6k1sTN97Mq5f6pAIhAkQZnnZsmU2b948+/XXX900PM+yuXPn2urVq8ObaV4Eso7AbrvtZnXq1LGGDRtagwYN3MfP+ynLK1WqlHVj14BEQAREQAREQAREQAREQASKlsAuAmjRdkd7F4HUCfy2bWvqlVVTBAqAwG+//WYLFizYRdRE0Jw/f35E9Ny8eXNk71WqVMkh/hx88MHWuHFjq1q1aqSOZgqHAIKcSuER2LZtW+R3wUuAyZMn27Bhw9xvaMOGDZGO7LnnntaoUSP3O6lfv76bDwukzNesWdN0/CLINCMCIiACIiACIiACIiACIpCEgATQJIC0WgREoGQSWLt2bUTARKwJW236+UWLFhkWnpRSpUpZrVq1ItZtbdu2td69e+ewckPoLF++fMkEqlGLQAICS5cujfkb47f29ddfO+vplStXRlooU6ZMjt9WtECKJWm9evWsbNmykW00IwIiIAIiIAIiIAIiIAIiUHIJSAAtucdeIxeBEkkAwRKxxbuj+6kXNZliwblmzZoIH0RLxBREFYSW7t27R+b9MizVEEFVREAEck8Ai04+7dq1i7sxVqL8NvmNhn+vzH/66aduGS8ltm7d4R2AhWjt2rVz/Fb975Wpn8fiVEUEREAEREAEREAEREAERCC7CUgAze7jq9GJQIkigKu5d0n3wqafhgUTXNd9wfXcW4/hdtu5c2cnjHhxhCnCjIoIiEDREuBFRIsWLdwn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je3RKUNt6/Ztbj1xJTvUam4rN62zt34Z69zSfcPlAmvTrvX2s3Y19nL1X585xhas35FJFPHzlcDNensgtg3Y90hbGCwfNnei25R4leyrfhCj9MtApB29YKpv0n5ctcDmrF1ixKREPHz6+xGRdY9Ped9+27Y18p0ZLEJx6d4nEO6mrpgbWD5OstW/bYjUqVS6nB3VqL3tW7VBYPm6zEbN+85ZwPoKuHwfHowRZj+tWhi01SEIFVDHhsweb18vmemruWmifueouPPL7Qefaf/9OXb8s3T0q0WV+q7v+wTTVZvWB8exs9ULBGKY9w5iv3IsutZt5XozbXlON8TvA1YVy5SzXg3b2zuzvox0/4mpH9htB53hOG237ZHlxXWGmHmUH3/80W677Ta7+eabXab3c845x0499VSrW7ducR1aofUbofLEE090AiBup/Xr17fjjz8+sv+PPvrIvvzyS6tWrZqddtppVqNGjci6xYsX29ChQ40pVltYsO29996WrM1IA1EzGzZscPE4Oa7lypWzs846y6ZPnx6JM4jFIsd2zz33tDfffNNmzpzprDn333//SEv8Pr766ivDLZ4+nXnmmRFxc9u2bTZ69GijnYMPTh4XN9HYIztMMMN4Bg8ebCeccIJjRFxHzxcBadGiRfbuu+86wfKUU06xypUr52gNl/0xY8YYMU5h26tXr8hYfMXly5fb//73P5s1a5YddNBB7voQS5TKy1hwReY40OdPPvnEnQfsl5cOhx9+uD399NPGGCmdO3d2Vri4LL/++utWoUIF+/Of/+zW8V+ysZCl+tVXX7VLLrnEPvjgA2fZfc0110S29zO4xWMJTqldu7Yde+yxbj7eubhlyxYXV7NixYq2zz77uOPBixKsWqMtf3HtHjdunNs3VuaxLF8nTJhgn376qRGflH23b9/e7d//lxfOfttk02QM2T5ZHY4P5xzHht8CvxOs6c8//3wrX758si7EXM85+N///tdWr15tnMdkHw+XNWvWuJimxPUkIznnMdPoAlfOsz322MOd76znXF62bJmLbeu/83s/4IADbN26dfbOO+8Y14sePXpYkyZNopvUdxEQAREQAREQAREQAREQgSIgkDuTnBgd3L9GUysTuJBj5bl2y8YYNXYsQkQcHIheZzQ/zKad/i+7t9O5Tmy8tcMZdlsg2rUMLBeJI0pMSBLq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Mrv88suNJHMqIiACIiACIiACIiACIlBMCUgALaYHTt0OCOxXrbF90e9esRCBjCOA+Enc0VhCT3gZ8UtVRKCkEsDKMCxqhuf9S4IaNWqUVDxFPu4VK1bYY489Zo888ohxrcIa9LrrritxYQKK/ECoAyIgAiIgAiIgAiIgAukgsKsAunX7Nhsye3w6GlcbIlCgBKqWrWjd67cp0H2ocREoSAIIDN6alKQkKoVLgIRYKoVHAN4Imt6is0KFCoW3c+0pzwQQP5966im79957bd26dc4a9Nprr3Vu8nluVBuKgAiIgAiIgAiIgAiIQOES2FUALdz9a28iIAIiIAIiIAIiIAKZTgAhFIvQ++67z0hCdfXVV7vM8QrfkelHTv0TAREQAREQAREQAREICEgA1WkgAiIgAiIgAiIgAiKQGoE1a9a4rPEPPPCAlSlTxm699Va7+OKL3XxqLaiWCIiACIiACIiACIiACBQ6AQmghY5cOxQBERABERABERCBYk6AEB533XWXPfroo9akSRPnIt+3b99iPip1XwREQAREQAREQAREIEsJTN09SwemYYmACIiACIiACIiACBQQAZJY3X///TZ9+nQ78MADrX///nbYYYfZ+PGKI19AyNWsCIiACIiACIiACIhAPghIAM0HPG0qAiIgAiIgAiIgAiWZQNOmTe3VV1+1cePG2fbt2+2QQw6x888/35YuXVqSsWjsIiACIiACIiACIiACGUZAAmiGHRB1RwREQAREQAREQASKG4GOHTvamDFjnBg6fPhwa9GihT3xxBO2bdu24jYU9VcEREAEREAEREAERCALCUgAzcKDqiGJgAiIgAiIgAiIQFEQOO2005xb/AUXXGBXXHGFHXzwwfbll18WRVe0TxEQAREQAREQAREQARGIEJAAGkGhGREQAREQAREQAREQgfwSqFSpkt133302adIkq1q1qnXu3Nkuu+wyW7t2bX6b1vYiIAIiIAIiIAIiIAIikCcCEkDzhE0biYAIiIAIiIAIiIAIJCLQqlUrGzlypL3wwgvONb5NmzaGe7yKCIiACIiACIiACIiACBQ2AQmghU1c+xMBERABERABERCBEkTg7LPPtmnTprkEScccc4yde+65tnz58hJEQEMVAREQAREQAREQAREoagISQIv6CGj/IiACIiACIiACIpDlBGrXrm2vv/66vfPOO/bRRx9Z69at7f3338/yUWt4IiACIiACIiACIiACmUJAAmimHAn1QwREQAREQAREQASynMCJJ57orEF79uxpxx13nF1yySW2fv36LB+1hicCIiACIiACIiACIlDUBHbbHpSi7oT2LwIiIAIiIAIiIAIiULIIvPHGG/anP/3JatWqZS+99JLLGF+yCGi0IiACIiACIiACIiAChURgqixAC4m0diMCIiACIiACIiACIvA7gVNPPdUmT55sjRs3tkMPPdRljtd7+d/5aE4EREAEREAEREAERCB9BGQBmj6WakkEREAEREAEREAERCCXBBA9H3jgAbvhhhvs6KOPthdffNGqV6+ey1ZUXQREQAREQAREQAREQATiEpgqATQuG60QAREQAREQAREQAREoLAJjx4610047ze2OhEmdO3curF1rPyIgAiIgAiIgAiIgAtlNQC7w2X18NToREAEREAEREAERKB4EEDy/+eYb23///e2www6zhx56qHh0XL0UAREQAREQAREQARHIeAKKAZrxh0gdFAEREAEREAEREIGSQaBGjRo2ZMgQu+OOO+yaa66xc845xzZu3FgyBq9RioAIiIAIiIAIiIAIFBgBucAXGFo1LAIiIAIiIAIiIAIikFcCw4cPt9NPP92aNWtmb7/9tjVq1CivTWk7ERABERABERABERCBkk1ALvAl+/hr9CIgAiIgAiIgAiKQmQRIiDR+/HhnAXrQQQfZmDFjMrOj6pUIiIAIiIAIiIAIiEDGE5ALfMYfInVQBERABERABERABEomgebNm9u4ceOsS5cu1rNnTxs0aFDJBKFRi4AIiIAIiIAIiIAI5IuABNB84dPGIiACIiACIiACIiACBUmgUqVK9uabb9qVV17pYoLedtttBbk7tS0CIiACIiACIiACIpCFBEpHj2ndlk3W4MUB0Yv1XQQyjkDb6k1sTN97Mq5f6pAIRBNYvXq1/frrrzZv3ry406VLl9r27dujN9V3EcgaAnvssYc1btzYGjZsaA0aNHAfP++ntWvXttKld7k1yRoGGkjeCey222527733Ghahl1xyif3000/27LPPWtmyZfPeqLYUAREQAREQgRgEFi5caBdffLG9++67MdYW/KJOnTrZc889Zy1btiz4nWkPIlCCCOgpowQd7Gwb6m/btmbbkDSeYkYAwXLx4sUJhU2Ez7Vr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1eXJJ5/ULvK0yPXOzNtSxKktXry4VKtWTYtE3rbhMhJwEgGEzIBVM4TPDRs2aHEGL0k6duwoSCLHQgIkQAIkQAI2J0AB1OYniM0jARIgARIgARIggZATQLzj+fPnyyeffKJdXGHV1a5dO+natasW9kLeIAceEKEFGjZsKHPnzpUGDRo4sAdsMgmIbNq0ScaOHavFTySqQ1zPJ554Qr8cIR8SIAESIAEScBABCqAOOllsKgmQAAmQAAmQAAmEnMDRo0fls88+k48++kiQ3Rmx/bp37y4PP/wwY4XGcTbatm0rq1atErjCp0mTJo6tuZoE7EEAIXqmTJmihU8kNII1M15+IEZwxowZ7dFItoIESIAESIAE4keAAmj8eHFrEiABEiABEiABEohOArD+WrBggYwZM0a+//577fbapUsXeeqppyR79uzRCSWOXh85ckSKFCmiwwiMGjUqjq25mgTCSwBhL95//339suPcuXM6oVG3bt10KIfwtoxHJwESIAESIIFEE6AAmmiErIAESIAESIAESIAEoozAvn375IMPPtDxAM+fPy+tW7eW559/XkqWLBllJOLu7ocffig9evTQcRNLly4d9w7cggRCTGDt2rXy9ttvy7Rp0+T222/X1p6dO3fWCdFC3BQejgRIgARIgASCRYACaLDIsl4SIAESIAESIAESiHQCly5d0rEB33zzTdm+fbvUq1dPC6F16tSJ9K773T9YzlapUkUQVxXu8EmTJvV7X25IAsEigOtx1qxZAsvkNWvWSPny5eWZZ56RVq1aScqUKYN1WNZLAiRAAiRAAuEisJ0jsHCh53FJgARIgARIgARIwOEEkBkemaC3bt0qSPpz9epVeeCBB6Rs2bLyxRdfyLVr1xzew8Q3P0mSJAIr0I0bN8q4ceMSXyFrIIFEELhy5YpObla0aFGd0Chnzpzy008/aQtlxPik+JkIuNyVBEiABEjA1gSSqLfSLlu3kI0jARIgARIgARIgARJwDAFkjR49erRMnTpVxwl98cUXtUiaKlUqx/QhGA194YUXdGzFXbt2aTfjYByDdZKALwKI6YmwFXB1P3HihHTo0EH69u0rhQsX9rULl5MACZAACZBAJBGgC3wknU32hQRIgARIgARIgATsQuCPP/6Q119/XVubZc2aVfr16yeIKwir0WgsiJWKhEj169eXjz/+OBoRsM9hIHD27Fl57733BGEqYJGNxGVwdc+dO3cYWsNDkgAJkAAJkEDYCFAADRt6HpgESIAESIAESIAEooDA33//rYVQuIFnyJBBxwjt2rWrpE2bNgp6H7OLX331lcDNGElnKlSoEHMlv5FAAAmcPn1a3nnnHf1BvM9evXrJs88+K5kzZw7gUVgVCZAACZAACTiGAAVQx5wqNpQESIAESIAESIAEHEzg6NGj2jUebriwAn3ppZd0tuloizlYvXp1QRzG1atXC+KDspBAIAkgMRmEz5EjR+rrq3fv3oJPpkyZAnkY1kUCJEACJEACTiNAAdRpZ4ztJQESIAESIAESIAEnE0D8QYgzcMtFApYhQ4ZIu3btoiY7+pYtW6RcuXI6NECnTp2cfCrZdhsRuH79ukyYMEEGDx4scHt/7rnnpE+fPtrq2kbNZFNIgARIgARIIFwEmAU+XOR5XBIgARIgARIgARKIRgKIB4rYoLt375batWvLY489JmXKlJG5c+dGBY5SpUrpOIxIDnXmzJmo6DM7GVwC06dPlxIlSkiPHj2kWbNmsnfvXhk0aBDFz+BiZ+0kQAIkQAIOI5DUYe1lc0mABEiABEiABEiABCKAQJ48ebQV5LZt26RQoULSqFEjLYjie6SXoUOHyo0bN7S1XqT3lf0LHoE1a9bIvffeKy1bttRWxTt37tSW1dmzZw/eQVkzCZAACZAACTiUAAVQh544NpsESIAESIAESIAEIoEAMqPDgm3VqlXadRfWoD179pRTp05FQve89gGJaF599VUtVu3YscPrNlxIAr4IIJ4uLKcrV66sk4lt3LhRJk+eLAUKFPC1C5eTAAmQAAmQQNQTSOJSJeopEAAJkAAJkAAJkAAJkEDYCWBYOnHiROnfv7+2kBw2bJh07tw5IuODIjN3xYoVdXKaRYsWhZ09G2B/AojzOWbMGLd7++jRo6VVq1b2bzhbSAIkQAIkQALhJ8AYoOE/B2wBCZAACZAACZAACZAACCAr+uOPP67jgz766KPSq1cvqVChgsDCLdJK0qRJdbbuH3/8Ub777rtI6x77E2ACS5Ys0bFyETu2e/fuAnd3ip8BhszqSIAESIAEIpoAXeAj+vSycyRAAiRAAiRAAiTgPAIZMmSQN954Q5AxHfOIc4iM1hcuXHBeZ2JpcZUqVaR58+bSr18/bfEay6ZcFaUETp48KZ06dZJatWpJ/vz5BTFyET4hTZo0UUqE3SYBEiABEiCBhBGgAJowbtyLBEiABEiABEiABEggyAQQHxSWbx9++KF8+umnUqxYsYizlnzttde0xev48eODTJPVO43A1KlTpWjRorJw4UKZOXOmzJ07V+6++26ndYPtJQESIAESIAFbEKAAaovTwEaQAAmQAAmQAAmQAAl4IwC3+CeeeEK7/CLpS8OGDaV169Zy7Ngxb5s7bhkErS5dusjAgQPl/Pnzjms/Gxx4AocOHZImTZrII488Io0bNxYkysJ3FhIgARIgARIggYQToACacHbckwRIgARIgARIgARIIEQEsmfPLl999ZV8//33snr1ailRooS2igvR4YN6mFdeeUUuXboko0aNCupxWLn9CXz88cfa0hmu7ogPi+8ZM2a0f8PZQhIgARIgARKwOQEKoDY/QWweCZAACZAACZAACZDAfwTq168vW7du1ZagzZo1k44dO8rp06f/28CBc9myZdOZ7xH39O+//3ZgD9jkxBLAece13bVrV3nqqaf0NV6zZs3EVsv9SYAESIAESIAE/iWQxKUKaZAACZAACZCAEwhcvnxZRo4cKSNGjJArV66Etcl58+bVGZybNm0a1nbw4CQQzQQQExFiUbJkyQQxNOvVq+dYHLi/FS5cWB544AH55JNPHNsPNjz+BGbMmCGdO3eWTJkyyRdffCGVKlWKfyXcI2oJnD17Vv766y/5888/9dTbPEKG8LE/ai+RqOl4lixZJE+ePHLHHXfoj7d5bMMStQS2UwCN2nPPjpMACZCAswj88MMP0qNHDzly5Ii2lCpUqFBYOzBnzhz5/PPPtcXOmDFjpECBAmFtDw9OAtFKAFmyu3XrJlOmTNFTWFGmTp3akTggfiHj988//6xd/B3ZCTbabwLnzp2TXr166QRfTz75pLz11luSLl06v/fnhpFN4ObNmzrWsRE2zfTgwYOCOLEQOjF/4cIFN4jbbrvNq/iTM2dOQTxlltARIO/QsTZHgtBvfidmit8J7rWmYHxghNHcuXMLDBogmJplmObIkUOSJ09uduE0cghQAI2cc8mekAAJkEBkEsDg/tlnn5Xp06dLixYt5O2339YDFTv09qefftKCy969e2XAgAHSr18/SZUqlR2axjaQQNQR+Prrr3UyoXz58gnmkTHeaQUWWhUqVNAPX4h1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xrRhwwZ58cUXNSYTXEidUhBbcPfu3U5pDttBAglLADHdEKoDLs74voJlj5MKEjGh9OzZ00nNSsi2FClSRBOUQAy9/vrr5Y033khIDuw0CcSbAGJ1MoN6+FcBoQPgScBCAolKgAJool559psESIAEYkhg5syZgphG5cqV02zKsQ4/XblyZfn99991adGiRQx7HvpUhw4d0qyiN998s2C5//77Q+/Md0iABKJGALHXICr26NFDY2vi+8pp2dWRUOPZZ5/VTMFOE2ajdmEcXjHiDy5evFi6dOkiLVu2lFGjRjm8xWweCXiHQK5cuWTBggWyceNGueqqq2TixImaQd07PYx8T+bPny8lS5YUJGCaNGmSlC9fPvInYY0k4HACFEAdfoHYPBIgARLwAoFrrrlGB6kQ+eC+icyXCMIey4L4SqnFcYplW2D1Wbp0aXn33Xdl0aJFMnv2bMmfP38sm8BzkQAJGAKYGKlfv75A9ERitEGDBjmSCx5WT5w4oWE8HNnABG0U3OARlgAWVY888oi0a9dOjh8/nqA02G0SiC2Bpk2byvbt26V79+763VilShX19oltK5x/tl27dmmiKBgAIBzUl19+KXfffbdjxsTOJ8gWeokABVAvXU32hQRIgAQcTAAWVcOGDRPESzt48GDcrEHjiQhWn506dVKLT7hNJiUlSZMmTeLZJJ6bBBKWAD5/sA7HwyHifcL13YkF1p9jxozRxEfnnXeeE5uY8G2677775K233hJMbtWsWVP27NmT8EwIgARiQQDxkGF9jQSXsMpGAjskicM4M9ELJmOGDBkiZcqUkZ9//llWr16toaAKFCiQ6GjY/wQmQAE0gS8+u04CJEAC8SBgrUFhCRova9B49NtafSK2IK0+43EFeE4S+I/AkiVLpFq1alK4cGG1GHKyK+C4cePkr7/+kn79+v3XAb5yHAFYEiPWNEQHxAZFtngWEiCB2BCAa/eqVavkpZdekoULF2oiO8R9j3XIpdj0Nu2zIKEfhM+nn35aRowYIZ999plOzqR9JPcgAW8ToADq7evL3pEACZCAIwkkkjUorT4deQuyUQlKAAnRYBEDy+tWrVrJihUrpJakY4EAAEAASURBVGDBgo6lgczvsP7s06ePnH/++Y5tJxv2PwKXX365fPLJJ1KrVi2pW7euxrwmGxIggdgRaNu2rbp44/u9a9eu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SKOefvppOXLkiAwZMiQjh/MYjxIoVKiQWjnD2hlWoaVKlVKrUMS8ZSEBEiABEiABEkg/AQqg6WfGI0iABEiABEiABEiABKJMYN26dYIMuH379tXs2Fu3bpU6depE+ayxrR5JNkaPHi0DBgyQAgUKxPbkPJsrCNx2222a8A/Wz506dZIaNWrIZ5995oq2s5EkQAIkQAIk4CQCFECddDXYFhIgARIgARIgARJIcAJ79+6V9u3bq9CTJ08e+fzzz+WJJ56Q7Nmze47MI488Ivny5VOR13OdY4ciRgD3yMSJE2XTpk2SNWtWqVy5stx9991y4MCBiJ2DFZEACZAACZCA1wlQAPX6FWb/SIAESIAESIAESMAFBP7++29NAlSiRAn56KOP1P0XLu+lS5d2QevT38QNGzYIkh+NGTNGcuTIkf4KeETCEShfvrx+Nl566SVZvHix4LMyadIk+eeffxKOBTtMAiRAAiRAAuklkMXEkWEgmfRS4/4kQAIkQAIkQAIkQAIRIYCh6Ny5c+XRRx8VWH/2799f+vXr50mLTwsMfa5ataoKnx988IHdzDUJhE0AcWOHDh0qY8eOleLFi8uIESM0UVjYFXBHEiABEiABEkgsAkm0AE2sC87ekgAJkAAJkAAJkIBjCCC5S8WKFaVNmzZSvXp12blzp2ZE96K7eyB0WH7CnXn8+PGBm/maBMImkDt3brUe3r59u5QrV05uv/12qVatmqxevTrsOrgjCZAACZAACSQSAQqgiXS12VcSIAESIAESIAEScACB9evXa0Kj+vXry0UXXSRffPGFZrguUqSIA1oX3SYcPnxYHn74YbnnnnukbNmy0T0Za/c8AVh/zpkzRxBSIVeuXHLdddfJzTffLEgaxkICJEACJEACJPAfAQqg/7HgKxIgARIgARIgARIggSgSgNVjkyZNNLs7Yn4i1ueSJUsSSggcPHiw/PvvvzJs2LAokmbViUYAltTvv/++LgglgXihrVu3lqSkpERDwf6SAAmQAAmQQIoEKICmiIUbSYAESIAESIAESIAEIkXg008/lcaNG0ulSpU0zufSpUtlzZo1UrNmzUidwhX1QIyaMGGCjBw5UrO/u6LRbKSrCNStW1c2btyoVqHbtm3TyYUWLVrIli1bXNUPNpYESIAESIAEIk2AAmikibI+EiABEiABEiABEiABJQALzwYNGmjCn99//13eeustgfs7xNBELL169VLLvM6dOydi99nnGBHIkiWLWNFz3rx58vXXX+t916xZM3WVj1EzeBoSIAESIAEScBQBCqCOuhxsDAmQAAmQAAmQAAm4mwAynC9atEgTstSuXVuOHTsmSHa0bt06adiwobs7l4nWQ4hCxnckPsqalUPwTKDkoWESgBCK5Eiff/65LFiwQPbs2SNVqlTR+LvvvvtumLVwNxIgARIgARLwBgGOvrxxHdkLEiABEiABEiABEogrAcT0nD59upQsWVJgaVaoUCFZu3atZqW+6aab4tq2eJ/8yJEj0qdPH+nYsaNaw8a7PTx/YhGAEHrbbbep9eeKFSskW7Zsapl9zTXXyGuvvSanTp1KLCDsLQmQAAmQQEISoACakJednSYBEiABEiABEiCByBDYt2+fPP7443LJJZdIjx49pHr16pp4BVageM0iMnDgQIFAPGbMGOIggbgSqFOnjsD687PPPpMrr7xS2rVrJ8WKFZMnn3xSEKaChQRIgARIgAS8SiCLcVPyebVz7BcJkAAJkAAJkAAJkEB0CMCtduzYsWpBlidPHunevbvce++9Urhw4eic0KW1btiwQa0+Z8yYIR06dHBpL9hsrxL4/vvvNSzDtGnT5OTJk9K+fXvp3bu3XHXVVV7tMvtFAiRAAiSQmASSKIAm5oVnr0mABEiABEiABEgg3QRgxTh//nyZOHGiZnEvW7asiiVt2rSR7Nmzp7s+rx/wzz//SOXKlSVv3ryyatUqr3eX/XMxgT///FNmzpwp48aNk127dmmcUFh0N2nSRM4880wX94xNJwESIAESIAElkEQXeN4JJEACJEACJEACJEACqRL47rvvZMCAAXLxxRerhViBAgUEsQS3bNkiXbp0ofgZgh7EpKSkJJk8eXKIPbiZBJxBIFeuXNKrVy/58ssvZdmyZZIjRw5p2bKlFC1aVENc/Pzzz85oKFtBAiRAAiRAAhkkQAvQDILjYSRAAiRAAiRAAiTgZQJwh12yZIlMnTpVYwbCtb1bt27StWtXueiii7zc9Yj0Da7FZcqUkQceeECGDBkSkTpZCQnEksDu3btlypQpmtwM8UEbNWqkn3+saRUayyvBc5EACZAACUSAAF3gIwCRVZAACZAACZAACZCAZwhs27ZNEK/ylVdekQMHDggyuN99991yyy23yBln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dTUSmwV+ToI/Q/j/6NGjfgywsE7rswOPNFh4sniCAAVQT1xGdoIESIAEPE5g1apV/izqTusqBkU2s3vRokWd1jy2hwQ8RWDXrl3SuHFjtZBatmyZlClTxlP9Y2dIgARIIBwCEHEChZ7A11b0CXS5D6dO7kMCXiIAy+jUJgVguZk3b14vdZl9SZsABdC0GXEPEiABEiABJxBAcqTly5cns3pwQruuuuoqTXTkhLawDSTgZQJr165VV/dixYrJkiVL5IILLvByd9k3EiABEsgUAYTkQRZ7CKKB8QozVSkPDpsArQbDRhWRHW0cTuuqzjBUEcHqtUoogHrtirI/JEACJEACJEACJOA1ArNnz5YuXbpIw4YNBa9z5szptS6yPyRAAiRAAiRAAiRAAtEjkJQ1enWzZhIgARIgARIgARIgARLIOAEkDXnwwQc14VHPnj1l/vz5FD8zjpNHkgAJkAAJkAAJkEDCEmAk5IS99Ow4CZAACZAACZAACTiXAJId3XHHHbJmzRp5+eWXVQR1bmvZMhIgARIgARIgARIgAScToADq5KvDtpEACZAACZAACZBAAhLYunWr3HbbbXLy5En56KOPpGLFiglIgV0mARIgARIgARIgARKIFAG6wEeKJOshARIgARIgARIgARLINIEXX3xRqlatKkhksHHjRoqfmSbKCkiABEiABEiABEiABCiA8h4gARIgARIgARIgARKIO4Fjx45Jp06dpHPnznLvvffKihUrpGDBgnFvFxtAAiRAAiRAAiRAAiTgfgJ0gXf/NWQPSIAESIAESIAESMDVBHbs2CEtWrSQvXv3ypIlS6Rx48au7g8bTwIkQAIkQAIkQAIk4CwCtAB11vVga0iABEiABEiABEggoQhMmTJFKlWqJHny5JEvvviC4mdCXX12lgRIgARIgARIgARiQ4ACaGw48ywkQAIkQAIkQAIkQAIBBPbt2yc333yz9OjRQ/r06SMffvihFClSJGAPviQBEiABEiABEiABEiCByBCgC3xkOLIWEiABEiABEiABEiCBMAksXrxYunbtKrly5ZLVq1dLjRo1wjySu5EACZAACZAACZAACZBA+gnQAjT9zHgECZAACZAACZAACZBABggcPHhQOnbsKE2aNFFX982bN1P8zABHHkICJEACJEACJEACJJA+ArQATR8v7k0CJEACJEACJEAC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\\n\",\n      \"text/plain\": [\n       \"<IPython.core.display.Image object>\"\n      ]\n     },\n     \"execution_count\": 13,\n     \"metadata\": {\n      \"image/png\": {\n       \"width\": 800\n      }\n     },\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"graph = net_drawer.GetPydotGraph(model.net, rankdir=\\\"LR\\\")\\n\",\n    \"display.Image(graph.create_png(), width=800)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Finally, we'll run the `param_init_net` and `net` and print our final blob values.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"i =  [8]\\n\",\n      \"y =  [28]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"RunNetOnce(model.param_init_net)\\n\",\n    \"RunNetOnce(model.net)\\n\",\n    \"print(\\\"i = \\\", FetchBlob(\\\"i\\\"))\\n\",\n    \"print(\\\"y = \\\", FetchBlob(\\\"y\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Backpropagation\\n\",\n    \"\\n\",\n    \"Both 'If' and 'While' operators support backpropagation. To illustrate how backpropagation with control ops work, let's consider the following examples in which we construct the operator graph using `NetBuilder` and obtain calculate gradients using the `AddGradientOperators` function. The first example shows the following conditional statement:\\n\",\n    \"\\n\",\n    \"    x = 1-D numpy float array\\n\",\n    \"    y = 4\\n\",\n    \"    z = 0\\n\",\n    \"    if (x > 0):\\n\",\n    \"        z = y^2\\n\",\n    \"    else:\\n\",\n    \"        z = y^3\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import numpy as np\\n\",\n    \"\\n\",\n    \"# Feed blob called x, which is simply a 1-D numpy array [0.5]\\n\",\n    \"FeedBlob(\\\"x\\\", np.array(0.5, dtype='float32'))\\n\",\n    \"\\n\",\n    \"# _use_control_ops=True forces NetBuilder to output single net as a result\\n\",\n    \"# x is external for NetBuilder, so we let nb know about it through initial_scope param\\n\",\n    \"with NetBuilder(_use_control_ops=True, initial_scope=[\\\"x\\\"]) as nb:\\n\",\n    \"    ops.Const(0.0, blob_out=\\\"zero\\\")\\n\",\n    \"    ops.Const(1.0, blob_out=\\\"one\\\")\\n\",\n    \"    ops.Const(4.0, blob_out=\\\"y\\\")\\n\",\n    \"    ops.Const(0.0, blob_out=\\\"z\\\")\\n\",\n    \"    with ops.IfNet(ops.GT([\\\"x\\\", \\\"zero\\\"])):\\n\",\n    \"        ops.Pow(\\\"y\\\", \\\"z\\\", exponent=2.0)\\n\",\n    \"    with ops.Else():\\n\",\n    \"        ops.Pow(\\\"y\\\", \\\"z\\\", exponent=3.0)\\n\",\n    \"\\n\",\n    \"# we should get a single net as output\\n\",\n    \"assert len(nb.get()) == 1, \\\"Expected a single net produced\\\"\\n\",\n    \"net = nb.get()[0]\\n\",\n    \"\\n\",\n    \"# add gradient operators for 'z' blob\\n\",\n    \"grad_map = net.AddGradientOperators([\\\"z\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In this case\\n\",\n    \"\\n\",\n    \"$$x = 0.5$$\\n\",\n    \"\\n\",\n    \"$$z = y^2 = 4^2 = 16$$\\n\",\n    \"\\n\",\n    \"We will fetch the blob `y_grad`, which was generated by the `AddGradientOperators` call above. This blob contains the gradient of blob z with respect to y. According to basic calculus:\\n\",\n    \"\\n\",\n    \"$$y\\\\_grad = \\\\frac{\\\\partial{z}}{\\\\partial{y}}y^2 = 2y = 2(4) = 8$$\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 16,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"x =  0.5\\n\",\n      \"y =  4.0\\n\",\n      \"z =  16.0\\n\",\n      \"y_grad =  8.0\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Run the net\\n\",\n    \"RunNetOnce(net)\\n\",\n    \"# Fetch blobs and print\\n\",\n    \"print(\\\"x = \\\", FetchBlob(\\\"x\\\"))\\n\",\n    \"print(\\\"y = \\\", FetchBlob(\\\"y\\\"))\\n\",\n    \"print(\\\"z = \\\", FetchBlob(\\\"z\\\"))\\n\",\n    \"print(\\\"y_grad = \\\", FetchBlob(\\\"y_grad\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, let's change value of blob \\\"x\\\" to -0.5 and rerun net:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 17,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"x =  -0.5\\n\",\n      \"y =  4.0\\n\",\n      \"z =  64.0\\n\",\n      \"y_grad =  48.0\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# To re-run net with different input, simply feed new blob\\n\",\n    \"FeedBlob(\\\"x\\\", np.array(-0.5, dtype='float32'))\\n\",\n    \"RunNetOnce(net)\\n\",\n    \"print(\\\"x = \\\", FetchBlob(\\\"x\\\"))\\n\",\n    \"print(\\\"y = \\\", FetchBlob(\\\"y\\\"))\\n\",\n    \"print(\\\"z = \\\", FetchBlob(\\\"z\\\"))\\n\",\n    \"print(\\\"y_grad = \\\", FetchBlob(\\\"y_grad\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The next and final example illustrates backpropagation on the following loop:\\n\",\n    \"\\n\",\n    \"    x = 2\\n\",\n    \"    y = 3\\n\",\n    \"    z = 2\\n\",\n    \"    i = 0\\n\",\n    \"    while (i <= 2):\\n\",\n    \"        x = x^2\\n\",\n    \"        if (i < 2):\\n\",\n    \"            y = y^2\\n\",\n    \"        else:\\n\",\n    \"            z = z^3\\n\",\n    \"        i += 1\\n\",\n    \"    s = x + y + z\\n\",\n    \"    \\n\",\n    \"Note that this code essentially computes the sum of x^4 (by squaring x twice), y^2, and z^3.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 18,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"with NetBuilder(_use_control_ops=True) as nb:\\n\",\n    \"    # Define variables and constants\\n\",\n    \"    ops.Copy(ops.Const(0), \\\"i\\\")\\n\",\n    \"    ops.Copy(ops.Const(1), \\\"one\\\")\\n\",\n    \"    ops.Copy(ops.Const(2), \\\"two\\\")\\n\",\n    \"    ops.Copy(ops.Const(2.0), \\\"x\\\")\\n\",\n    \"    ops.Copy(ops.Const(3.0), \\\"y\\\")\\n\",\n    \"    ops.Copy(ops.Const(2.0), \\\"z\\\")\\n\",\n    \"    \\n\",\n    \"    # Define loop statement\\n\",\n    \"    # Computes x^4, y^2, z^3\\n\",\n    \"    with ops.WhileNet():\\n\",\n    \"        with ops.Condition():\\n\",\n    \"            ops.Add([\\\"i\\\", \\\"one\\\"], \\\"i\\\")\\n\",\n    \"            ops.LE([\\\"i\\\", \\\"two\\\"])\\n\",\n    \"        ops.Pow(\\\"x\\\", \\\"x\\\", exponent=2.0)\\n\",\n    \"        with ops.IfNet(ops.LT([\\\"i\\\", \\\"two\\\"])):\\n\",\n    \"            ops.Pow(\\\"y\\\", \\\"y\\\", exponent=2.0)\\n\",\n    \"        with ops.Else():\\n\",\n    \"            ops.Pow(\\\"z\\\", \\\"z\\\", exponent=3.0)\\n\",\n    \"    \\n\",\n    \"    # Sum s = x + y + z\\n\",\n    \"    ops.Add([\\\"x\\\", \\\"y\\\"], \\\"x_plus_y\\\")\\n\",\n    \"    ops.Add([\\\"x_plus_y\\\", \\\"z\\\"], \\\"s\\\")\\n\",\n    \"\\n\",\n    \"assert len(nb.get()) == 1, \\\"Expected a single net produced\\\"\\n\",\n    \"net = nb.get()[0]\\n\",\n    \"\\n\",\n    \"# Add gradient operators to output blob 's'\\n\",\n    \"grad_map = net.AddGradientOperators([\\\"s\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 19,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"x =  16.0\\n\",\n      \"x_grad =  32.0\\n\",\n      \"y =  9.0\\n\",\n      \"y_grad =  6.0\\n\",\n      \"z =  8.0\\n\",\n      \"z_grad =  12.0\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"workspace.RunNetOnce(net)\\n\",\n    \"print(\\\"x = \\\", FetchBlob(\\\"x\\\"))\\n\",\n    \"print(\\\"x_grad = \\\", FetchBlob(\\\"x_grad\\\")) # derivative: 4x^3\\n\",\n    \"print(\\\"y = \\\", FetchBlob(\\\"y\\\"))\\n\",\n    \"print(\\\"y_grad = \\\", FetchBlob(\\\"y_grad\\\")) # derivative: 2y\\n\",\n    \"print(\\\"z = \\\", FetchBlob(\\\"z\\\"))\\n\",\n    \"print(\\\"z_grad = \\\", FetchBlob(\\\"z_grad\\\")) # derivative: 3z^2\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Implementation Notes\\n\",\n    \"\\n\",\n    \"On the low level, Caffe2 uses the following set of operators to implement forward and backward branching and loops:\\n\",\n    \"- If - accepts *then_net* and *else_net* nets as arguments and executes one of them, depending on input condition blob value, nets are executed **in the same** workspace;\\n\",\n    \"- While - repeats execution of *loop_net* net passed as argument, net is executed in the same workspace;\\n\",\n    \"- Do - special operator that creates a separate inner workspace, sets up blob mappings between outer and inner workspaces, and runs a net in an inner workspace;\\n\",\n    \"- CreateScope/HasScope - special operators that create and keep track of workspaces used by Do operator.\\n\",\n    \"\\n\",\n    \"Higher level libraries that implement branching and looping (e.g. in `NetBuilder`, `brew`), use these operators to build control flow, e.g. for 'If':\\n\",\n    \" - do necessary sanity checks (e.g. determine which blobs are initialized and check that subnet does not read undefined blobs)\\n\",\n    \" - wrap 'then' and 'else' branches into Do\\n\",\n    \" - setup correct blob mappings by specifying which local names are mapped to outer blobs\\n\",\n    \" - prepare scope structure, used by Do operator\\n\",\n    \"\\n\",\n    \"While 'If' and 'While' Caffe2 ops can be used directly without creating local block workspaces, we encourage users to use higher level Caffe2 interfaces that provide necessary correctness guarantees.\\n\",\n    \"\\n\",\n    \"Backpropagation for 'While' in general is expensive memory-wise - we have to save local workspace for every iteration of a block, including global blobs visible to the block. It is recommended that users use `RecurrentNetwork` operator instead in production environments.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.14\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/Getting_Caffe1_Models_for_Translation.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Getting Caffe1 Models and Datasets\\n\",\n    \"\\n\",\n    \"This tutorial will help you acquire a variety of pre-trained models from the original Caffe repo, and translate these models to a format that Caffe2 expects. If you don't already have the Caffe repo, then clone it like so:\\n\",\n    \"\\n\",\n    \"```\\n\",\n    \"git clone https://github.com/BVLC/caffe.git\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"Start by importing the required modules.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from __future__ import absolute_import\\n\",\n    \"from __future__ import division\\n\",\n    \"from __future__ import print_function\\n\",\n    \"from __future__ import unicode_literals\\n\",\n    \"\\n\",\n    \"import os\\n\",\n    \"print(\\\"Required modules imported.\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now you can setup your root folder for Caffe below if you put it somewhere else. You should only be changing the path that's being set for `CAFFE_ROOT`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# You should have checked out original Caffe\\n\",\n    \"# git clone https://github.com/BVLC/caffe.git\\n\",\n    \"# change the CAFFE_ROOT directory below accordingly\\n\",\n    \"CAFFE_ROOT = os.path.expanduser('~/caffe')\\n\",\n    \"\\n\",\n    \"# Make sure Caffe exists where you specified\\n\",\n    \"if not os.path.exists(CAFFE_ROOT):\\n\",\n    \"    print(\\\"Houston, you may have a problem.\\\") \\n\",\n    \"    print(\\\"Did you change CAFFE_ROOT to point to your local Caffe repo?\\\")\\n\",\n    \"    print(\\\"Try running: git clone https://github.com/BVLC/caffe.git\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Here's where you pick your model. There are several listed below such as AlexNet, GoogleNet, and Flickr Style. Uncomment the model you want to download.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Pick a model, and if you don't have it, it will be downloaded\\n\",\n    \"# format below is the model's folder, model's dataset inside that folder\\n\",\n    \"\\n\",\n    \"#MODEL = 'bvlc_alexnet', 'bvlc_alexnet.caffemodel' \\n\",\n    \"#MODEL = 'bvlc_googlenet', 'bvlc_googlenet.caffemodel'\\n\",\n    \"#MODEL = 'finetune_flickr_style', 'finetune_flickr_style.caffemodel'\\n\",\n    \"#MODEL = 'bvlc_reference_caffenet', 'bvlc_reference_caffenet.caffemodel'\\n\",\n    \"MODEL = 'bvlc_reference_rcnn_ilsvrc13', 'bvlc_reference_rcnn_ilsvrc13.caffemodel'\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"As a reminder, in Caffe, the deploy model is saved in two parts:\\n\",\n    \"\\n\",\n    \"    1) deploy.prototxt: contained the network architecture in human-readable protobuf format\\n\",\n    \"    2) .caffemodel file: contained the model weights and parameters for loading\\n\",\n    \"\\n\",\n    \"Therefore, to translate the model to Caffe2, we need both of these files. We already have the `deploy.prototxt` files for all of the models in `~/caffe/models`, so we need the learned weights.\\n\",\n    \"\\n\",\n    \"Below, we'll check to see if the `.caffemodel` file from the last model that we uncommented above already exists. If it does not already exist in the location that we specify, we will download it using the `download_model_binary.py` script in the Caffe repo. **Note that .caffemodel files are typically fairly large files, so downloading one will take a few moments.** We will be sure to print a message so we know when we can continue.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Scripts to download the models reside here (~/caffe/models)\\n\",\n    \"# After downloading the data will exist with the script\\n\",\n    \"CAFFE_MODELS = os.path.join(CAFFE_ROOT, 'models')\\n\",\n    \"\\n\",\n    \"# this is like: ~/caffe/models/bvlc_alexnet/deploy.prototxt\\n\",\n    \"CAFFE_MODEL_FILE = os.path.join(CAFFE_MODELS, MODEL[0], 'deploy.prototxt')\\n\",\n    \"# this is like: ~/caffe/models/bvlc_alexnet/bvlc_alexnet.caffemodel\\n\",\n    \"CAFFE_PRETRAINED = os.path.join(CAFFE_MODELS, MODEL[0], MODEL[1])\\n\",\n    \"    \\n\",\n    \"# If the model folder doesn't have the goods, then download it\\n\",\n    \"# This is usually a pretty big file with the .caffemodel extension\\n\",\n    \"if not os.path.exists(CAFFE_PRETRAINED):\\n\",\n    \"    print(CAFFE_PRETRAINED + \\\" not found. Attempting download. Be patient...\\\\n\\\")\\n\",\n    \"    os.system(\\n\",\n    \"        os.path.join(CAFFE_ROOT, 'scripts/download_model_binary.py') +\\n\",\n    \"        ' ' +\\n\",\n    \"        os.path.join(CAFFE_ROOT, 'models', MODEL[0]))\\n\",\n    \"else:\\n\",\n    \"    print(\\\"You already have \\\" + CAFFE_PRETRAINED + \\\", skipping download...\\\\n\\\")\\n\",\n    \"\\n\",\n    \"# If the .prototxt file was missing then you're in trouble; cannot continue\\n\",\n    \"if not os.path.exists(CAFFE_MODEL_FILE):\\n\",\n    \"    print(\\\"Caffe model file, \\\" + CAFFE_MODEL_FILE + \\\" was not found!\\\")\\n\",\n    \"else:\\n\",\n    \"    print(\\\"Both the deploy.prototxt and .caffemodel files were found, ready to continue!\\\")\\n\",\n    \"    # Now we have init net and predict net .pb files to use\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now that we have both the `deploy.prototxt` and `.caffemodel` files, we can translate the model to the Caffe2 saved model format, which consists of two serialized protobuf files:\\n\",\n    \"\\n\",\n    \"    1) init_net.pb\\n\",\n    \"    2) predict_net.pb\\n\",\n    \"    \\n\",\n    \"To do this, we will use Caffe2's translator script at `~/caffe2/caffe2/python/caffe_translator.py`.\\n\",\n    \"\\n\",\n    \"**Again, depending on the size of the model, this may take a minute or two**\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Set the CAFFE2_ROOT\\n\",\n    \"CAFFE2_ROOT = os.path.expanduser('~/caffe2')\\n\",\n    \"init_net_out = os.path.join(CAFFE_MODELS, MODEL[0], 'init_net.pb')\\n\",\n    \"predict_net_out = os.path.join(CAFFE_MODELS, MODEL[0], 'predict_net.pb')\\n\",\n    \"\\n\",\n    \"# Run the caffe_translator.py script to translate to Caffe2 if files do not already exist\\n\",\n    \"if (not os.path.exists(init_net_out)) or (not os.path.exists(predict_net_out)):\\n\",\n    \"    print(\\\"Protobuf files not found. Running translation. Be patient...\\\\n\\\")\\n\",\n    \"    os.system(\\n\",\n    \"        'python' + ' ' + os.path.join(CAFFE2_ROOT, 'caffe2/python/caffe_translator.py') +\\n\",\n    \"        ' ' + CAFFE_MODEL_FILE + ' ' + CAFFE_PRETRAINED + ' ' + \\n\",\n    \"        '--init_net' + ' ' + init_net_out + ' ' +\\n\",\n    \"        '--predict_net' + ' ' + predict_net_out\\n\",\n    \"    )\\n\",\n    \"else:\\n\",\n    \"    print(\\\"You already have both .pb files, skipping translation...\\\\n\\\")    \\n\",\n    \"\\n\",\n    \"# Print if files are where they are expected to be\\n\",\n    \"if (not os.path.exists(init_net_out)) or (not os.path.exists(predict_net_out)):\\n\",\n    \"    print(init_net_out + \\\" and/or \\\" + predict_net_out + \\\" was NOT FOUND!\\\")\\n\",\n    \"else:\\n\",\n    \"    print(\\\"Protobuf files can be found at: \\\\n\\\", \\n\",\n    \"              os.path.join(CAFFE_MODELS, MODEL[0])), \\\"!\\\"\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"At this point, we have translated the model from Caffe to a format that Caffe2 can use. Have a look at our other tutorials, such as *Loading Pretrained Models* to see an example of how to use these .pb files for inference.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.14\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/Image_Pre-Processing_Pipeline.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Image Loading and Preprocessing\\n\",\n    \"\\n\",\n    \"In this tutorial we're going to look at how we can load in images from a local file or a URL which you can then utilize in other tutorials or examples. Also, we're going to go in depth on the kinds of preprocessing that is necessary to utilize Caffe2 with images.\\n\",\n    \"\\n\",\n    \"#### Mac OSx Prerequisites\\n\",\n    \"\\n\",\n    \"If you don't already have these Python modules installed you'll need to do that now.\\n\",\n    \"\\n\",\n    \"```\\n\",\n    \"sudo pip install scikit-image scipy matplotlib\\n\",\n    \"```\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Required modules imported.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"%matplotlib inline\\n\",\n    \"import skimage\\n\",\n    \"import skimage.io as io\\n\",\n    \"import skimage.transform \\n\",\n    \"import sys\\n\",\n    \"import numpy as np\\n\",\n    \"import math\\n\",\n    \"from matplotlib import pyplot\\n\",\n    \"import matplotlib.image as mpimg\\n\",\n    \"print(\\\"Required modules imported.\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Test an Image\\n\",\n    \"\\n\",\n    \"In the code block below use IMAGE_LOCATION to load what you would like to test. Just change the comment flags to go through each round of the Tutorial. In this way, you'll get to see what happens with a variety of image formats and some tips on how you might preprocess them. If you want to try your own image, drop it in the images folder or use a remote URL. When you pick a remote URL, make it easy on yourself and try to find a URL that points to a common image file type and extension versus some long identifier or query string which might just break this next step.\\n\",\n    \"\\n\",\n    \"## Color Issues\\n\",\n    \"\\n\",\n    \"Keep in mind when you load images from smartphone cameras that you may run into color formatting issues. Below we show an example of how flipping between RGB and BGR can impact an image. This would obviously throw off detection in your model. Make sure the image data you're passing around is what you think it is!\\n\",\n    \"\\n\",\n    \"### Caffe Uses BGR Order\\n\",\n    \"\\n\",\n    \"Due to legacy support of OpenCV in Caffe and how it handles images in Blue-Green-Red (BGR) order instead of the more commonly used Red-Green-Blue (RGB) order, Caffe2 also expects **BGR** order. In many ways this decision helps in the long run as you use different computer vision utilities and libraries, but it also can be the source of confusion. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"<matplotlib.text.Text at 0x7f84b3cd9190>\"\n      ]\n     },\n     \"execution_count\": 2,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84b3dd8690>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# You can load either local IMAGE_FILE or remote URL\\n\",\n    \"# For Round 1 of this tutorial, try a local image.\\n\",\n    \"IMAGE_LOCATION = 'images/cat.jpg'\\n\",\n    \"\\n\",\n    \"# For Round 2 of this tutorial, try a URL image with a flower: \\n\",\n    \"# IMAGE_LOCATION = \\\"https://cdn.pixabay.com/photo/2015/02/10/21/28/flower-631765_1280.jpg\\\"\\n\",\n    \"# IMAGE_LOCATION = \\\"images/flower.jpg\\\"\\n\",\n    \"\\n\",\n    \"# For Round 3 of this tutorial, try another URL image with lots of people:\\n\",\n    \"# IMAGE_LOCATION = \\\"https://upload.wikimedia.org/wikipedia/commons/1/18/NASA_Astronaut_Group_15.jpg\\\"\\n\",\n    \"# IMAGE_LOCATION = \\\"images/astronauts.jpg\\\"\\n\",\n    \"\\n\",\n    \"# For Round 4 of this tutorial, try a URL image with a portrait!\\n\",\n    \"# IMAGE_LOCATION = \\\"https://upload.wikimedia.org/wikipedia/commons/9/9a/Ducreux1.jpg\\\"\\n\",\n    \"# IMAGE_LOCATION = \\\"images/Ducreux.jpg\\\"\\n\",\n    \"\\n\",\n    \"img = skimage.img_as_float(skimage.io.imread(IMAGE_LOCATION)).astype(np.float32)\\n\",\n    \"\\n\",\n    \"# test color reading\\n\",\n    \"# show the original image\\n\",\n    \"pyplot.figure()\\n\",\n    \"pyplot.subplot(1,2,1)\\n\",\n    \"pyplot.imshow(img)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('Original image = RGB')\\n\",\n    \"\\n\",\n    \"# show the image in BGR - just doing RGB->BGR temporarily for display\\n\",\n    \"imgBGR = img[:, :, (2, 1, 0)]\\n\",\n    \"#pyplot.figure()\\n\",\n    \"pyplot.subplot(1,2,2)\\n\",\n    \"pyplot.imshow(imgBGR)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('OpenCV, Caffe2 = BGR')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"As you can see in the example above, the difference in order is very important to keep in mind. In the code block below we'll be taking the image and converting to BGR order for Caffe to process it appropriately.\\n\",\n    \"\\n\",\n    \"But wait, there's more color fun...\\n\",\n    \"\\n\",\n    \"### Caffe Prefers CHW Order\\n\",\n    \"\\n\",\n    \"Now what!? What's CHW, you ask? Well, there's also HWC! Both formats come up in image processing.\\n\",\n    \"\\n\",\n    \"- H: Height\\n\",\n    \"- W: Width\\n\",\n    \"- C: Channel (as in color)\\n\",\n    \"\\n\",\n    \"Digging even deeper into how image data can be stored is the memory allocation order. You might have noticed when we first loaded the image that we forced it through some interesting transformations. These were data transformations that let us play with the image as if it were a cube. What we see is on top of the cube, and manipulating the layers below can change what we view. We can tinker with it's underlying properties and as you saw above, swap colors quite easily. \\n\",\n    \"\\n\",\n    \"For GPU processing, which is what Caffe2 excels at, this order needs to be CHW. For CPU processing, this order is generally HWC. Essentially, you're going to want to use CHW and make sure that step is included in your image pipeline. Tweak RGB to be BGR, which is encapsulated as this \\\"C\\\" payload, then tweak HWC, the \\\"C\\\" being the very same colors you just switched around.\\n\",\n    \"\\n\",\n    \"You may ask why! And the reason points to cuDNN which is what helps accelerate processing on GPUs. It uses only CHW, and we'll sum it up by saying it is faster. \\n\",\n    \"\\n\",\n    \"Give these two transformations, you might think that's enough, but it isn't. We still need to resize and/or crop and potentially look at things like orientation (rotation) and mirroring.\\n\",\n    \"\\n\",\n    \"## Rotation and Mirroring\\n\",\n    \"\\n\",\n    \"This topic is usually reserved for images that are coming from a smart phone. Phones, in general, take great pictures, but do a horrible job communicating how the image was taken and what orientation it should be in. Then there's the user who does everything under the sun with their phone's cameras, making them do things its designer never expected. Cameras - right, because there are often two cameras and these two cameras take different sized pictures in both pixel count and aspect ratio, and not only that, they sometimes take them mirrored, and they sometimes take them in portrait and landscape modes, and sometimes they don't bother to tell which mode they were in. \\n\",\n    \"\\n\",\n    \"In many ways this is the first thing you need to evaluate in your pipeline, then look at sizing (described below), then figure out the color situation. If you're developing for iOS, then you're in luck, it's going to be relatively easy. If you're a super-hacker wizard developer with lead-lined shorts and developing for Android, then at least you have lead-lined shorts. \\n\",\n    \"\\n\",\n    \"The variability in the Android marketplace is wonderful and horrifying. In an ideal world, you could rely on the EXIF data in pictures coming from any camera and use that to decide orientation and mirroring and you'd have one simple case function to handle your transformations. No such luck, but you're not alone. Many have come before you and suffered for you.\\n\",\n    \"\\n\",\n    \"### Library for Handling Mobile Images\\n\",\n    \"\\n\",\n    \"Hooray! We're going to give you something to ease your pain. These are not full-proof. Users can and will defy you and every other developers' best attempts to handle their images. Here we'll link to some resources that can be used depending on the platform.\\n\",\n    \"\\n\",\n    \"Image Preprocessing Libraries and/or Snippits\\n\",\n    \"- [iOS](#)\\n\",\n    \"- [Android](#)\\n\",\n    \"- [Python](#)\\n\",\n    \"In the meantime though, let's play with some images and show the basics for manipulations that you might need to do.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"<matplotlib.text.Text at 0x7f84b3b31b90>\"\n      ]\n     },\n     \"execution_count\": 3,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84c1f41c50>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAAAVQAAAEICAYAAAAA3gw5AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvWewpcl53/fr7jeffM+5OU3YmdnBZmAXgQAIEkswU5TF\\nMoMkJ9FWWSWVQ9klWx9ksmQVWSWXPtgfVLLsMk1JpETRTIJFUCAKBBF2iV1ggU2TZ+7MzeHk8Obu\\n9oc7YC1ZFAEWF0uIvr9Pb+fn9un3fzo8fa6w1nLGGWecccafHvlnbcAZZ5xxxp8XzgT1jDPOOONt\\n4kxQzzjjjDPeJs4E9YwzzjjjbeJMUM8444wz3ibOBPWMM844423iTFDPeMcRQvxjIcTffYfb3BBC\\nTIUQ6p1s94z/fyHO/FDPeLsQQtwHVoAVa233LfFfAZ4Gzltr7//ZWHfGGd98zmaoZ7zdbAE/8bWA\\nEOIJIPpGCwshnG8k7k9axxlnvBOcCeoZbzf/DPiP3xL+T4B/+tYMQoj/Wwjx9x8+f4cQYlcI8T8I\\nIQ6Bn/uj4h7m/S+EEHeEEH0hxL8WQqy8pU4rhPibQojbwO0/bJQQ4tzDPM7D8GeEEH9fCPHCw62A\\njwshOkKIXxBCjIUQLwshzr2l/P8qhNh5mPZlIcSH35IWCiF+XggxEEJcF0L8bSHE7lvSV4QQvyKE\\nOBFCbAkh/qs/TQef8a3LmaCe8Xbze0BdCHH14X7ljwP//OuUWQLmgE3gr/9RcUKIjwI/C/wosAw8\\nAP7lH6rnLwLvA971Ddr648B/BKwCFx/a/nMP270O/NRb8r7M6bbFHPCLwC8LIYKHaT8FnAMuAB8D\\n/urXCgkhJPBx4NWH7TwP/DdCiO/5Bm08498jzgT1jG8GX5ulfoxTYdr7OvkN8FPW2sxam/w74v4K\\n8H9Za1+x1mbA3wE+8NZZJPCz1tr+W+r4evyctfautXYEfAK4ba39lLW2BH4ZeOZrGa21/9xa27PW\\nltbafwj4wJWHyT8K/Iy1dmCt3QX+t7e08Rwwb639e9ba3Fp7D/g/OBXzM/6ccbbXdMY3g38GfBY4\\nzx9a7v87OLHWpl8nbgV45WsBa+1UCNHjdNZ3/2H0zp/QzqO3PCd/RLj6tYAQ4r8HfvKhHRaoA523\\n2PbWtt/6vAmsCCGGb4lTwOf+hLae8e8BZ4J6xtuOtfaBEGIL+H5ORejrFvkG4vY5FScAhBAVoM0f\\nnP1+U1xWHu6X/m1Ol+tvWmuNEGIAiIdZDoA14NrD8Ppbiu8AW9baS98M28741uJsyX/GN4ufBD5q\\nrZ29TfX9C+A/E0I8LYTwgZ8BvvgOuWHVgBI4ARwhxP/E6Qz1a/wr4O8IIVpCiFXgb70l7SVg8vCA\\nLRRCKCHE40KI594Bu894hzkT1DO+KTzcm/zS21jfp4C/C/wKpzPCi7xz+5D/Fvgt4Banh2Epf3BZ\\n//eAXU5dxj4F/D9A9tBuDfwgpwdaW0AX+D+Bxjtk+xnvIGeO/Wec8TYjhPgbwI9baz/yZ23LGe8s\\nZzPUM874UyKEWBZCfFAIIYUQV4D/Dvi1P2u7znjneccFVQjxvUKImw8dtP/Hd7r9M874JuAB/zsw\\nAT4N/Abwj/5MLTrjz4R3dMn/0NH7Fqf+ibucOkv/hLX22h9b8Iwzzjjj3wPe6Rnqe4E71tp71tqc\\n05suP/wO23DGGWec8U3hnfZDXeUPno7ucnpV8A8ghPjrPLyC6Ej5nmYUPnQwtCAEwkCpS4Q8dQOU\\n1qKRgEEqh3GSYqxFSsHpzT+LlBKBAAxCKqRQWCyOklgLeZ7RqIQUeQHWIpRACkleapRUaFOCFXiu\\nIstLPEdRaIMQYBEIAY78mluiIC8NriMx1oI1YC2lFbhKYLRFKYEUgtxYXCnAGow9jbOAMQZrLY6j\\n0MaglMIYAVhKXeI4CpBoXeLI079FOS5KQK4NUoDRGsf1yPMMRyq0tUhx2kZRFmihaDZCZrEhS6ZE\\nnsJYEEJgtUEbjVIu2ho21hfY3u+TZxlV30UKQalLQKAchTb21ClTSKw1KCmwxpCmOUHgY42l0CWe\\n42AAB0tcWObqPpMkR5clUrkgQAkQ4vTzsoC15rSPsSgpyPKSwHUx1qCkRFuLteZh30uyvEApgbBg\\nsChHIYOIeBrDwzGx1G4yHo2QUoKwZGkGCBxHYa2lKEsqQUien8YDJFlB4CqMFSglsRikUGRZhud7\\nSHnatus6FEWJ77kYYxASdG4QSiCsQDgCLFh72mfSUVitscailKS04DkOWZYj1ekYGs4SaoGP5zhY\\na8jykigKSNMcx5E4joM2liRN8RwH33cptMUPQoo0Ji80Qlg83yPLCrQuQQhKA1I5VKIAjKbIMwLP\\nR1uNEJKy0BRFQRD4GK1Px4cCayx5XuD7PtoYrAVjLQKLsQb34ZiUQoKAstAopVBKnI4Va7HitF+1\\ntmhjkEohhEDrEiElSj58Y+2pg3FRlAghMdYgxelnIMTDvuS0P405HROl1r+ffvouCbTRp+PPnGqA\\nUAJrLBJB+fAdtRakFFjE6Ri2EAYe3f6wa62d/3oC9y3p2G+t/SfAPwGYr1XsX/ngs+SpJLYxuXSQ\\n0zHTvKQ0AqEsrs4RTpVcZyjP5wt371NYCDwfAkUUVMhyQbMusIUh8gOUH4GxVFRMZ36Fa9dv86Mf\\nusirbx7g+x65MDjKZThIaM0HxJkhTgocR2JNzkKtySxNiAKXOC1ICvAxGAO+r8iMwXE1aIeDcUqz\\n4lI6Hp4W1MOEwdTFsSULTUmSOESBA9IjcDU7JwkN3+IrsGGHsogJHcHt+10WlyIUPuNJj2q1QyVw\\nGRcps0xRcyVZUdCs1giqFSZFQZakNEPJIMlphC0SPaHZqLK72+WwyPnxH/gOfu5ffZbIxDyz0WQ8\\nK+mOYlxZ0GrXaXgOceySMuHRtUV2Drr88Le/i3/9mWusVUIc16VqNGquRmEsRTIm8OsstiNOhjGj\\n/oibWw9YnG+z2vaZDOHceoPtQUKZjNhYXEZYGMeSm8MTrmyuMc0nVLTDnV7CkxsN0tRle+cu5y5s\\nohNNITNCaVFOSFkY4t4xJ0XGYysbHPdHHE2mNKsVLixHfOXeCXEQES1dZPvmLTrraygh2WwJllxI\\nEQwGPWqNOvVGhdnIsrt3n2pUoSIiyiIGIuaWAx7c2sHIGF04nL+wxvV7R/SnYzq1OVYbDjt7Aypz\\nddZWFpiMhzx65XHubd1iFqc0qiF740PafoPdrW2WN1a5tTvlykoF6VbRJHSPxiyvdcjSAo0gnSS0\\n2y1SYfi1F9/gLz/7NLM8ZnVxjrv7O6AlmTYIYZlfbjPsZxwcH3FxZZnAj2ivLJNMJmxeOs8LX/hd\\narUGjmsJ/TbHx12qTcFnX99hbmmFi4vzPPH4ZW5++Qs0GnV6hzP8ikMWJxQ2JaxWiccpnbVVDg4S\\nTiZ9HAMqEETa4nkBUb3BYb9HanNaYUg8zVhZ6nC0f4yvJEeTKVUHKs0m2WSMG9UZzWJGSUpeGlZW\\nVpimU4QNSMsEoSUzMzoVKS2QUuKGEVYKbGkJlIcKBNPxDCklaWmohAGOlViRoGWELlKk44Ex5GWJ\\nwsFIRZYlUGoqkYtWDhKBNYqsyImiKmDwvIAPP/sEbiXkp//nf/DgG9Gud1pQ9/iDt0jW+Dr3vC2C\\n7mCEW43ASjwP9CwhkgGlA1me0AirDNMS5Trk5ddmoRYtLCEOq8vz9LsDdAph5GKtwpQaz3fQTo2T\\n0Yi1lVVeunZAphVilFALFE5F4dUUvUGJKGMsLgZBuxVxMp6Q6IKKchDG0q45mCJkko6J/AoR4CnD\\nYJyQzwrarQ77ky6p8OkeHvDcpUcoTZVhOqUwJSot8X3N8bhkfq4KVtMdp5hiyFrVZTLNmV9uIigI\\n6i2MVyBLi9Yai6BdVWgZIZyUxLPMt6vowRhtYBaXeMJg9RidS+pRSKoVjpCkIuFCq8lSvcpc1aE3\\n2qcd+bQqTfa6A/bTksRTjCYJjonxghof/+xrxMZhtzfg6pVVul3Jsgf9/ZSl+TnuHU0YTKYs1OtU\\n2m0uC8XxJMbVVR652uDW/WPW2hFvjmNOkoLpLOPc5jKdcsru0YhJEmO0JlAuN67fYmNzjfWNZXrD\\nAqFTCiFw63VuP+jSqBYkGbhBjVcOBoxyzVKlzmLT5fgk5oPf8wyfeWmH7XtvUpnvgLFYa/nODz/H\\n4c17HEymRFGd+bDByVEXP/TZXJzHknPt9j02F9rk+YT+zgjX9zAypNGQpOmId11a5MXXJjz92DnW\\nF+sM8ldxHTg5OMITATd3bnJy1KfdqTGeFFTcFnFsWDl/EYGhU/ep1xo86PfZWGwxM4rAPZ3xjnsJ\\nh+Mp1bDOVn/Ik++6zFEyZDKY0h+PaFQD7u7dY67RQfqK+/dO6MzVuHB+kYoTcjKa0LRVWpsXeOGF\\nF1k7t4a2ittvblH4CUmSkvRKgmYbKRTv/84P8Uu/+MssBCH7gyEgmfUGdBodDmcZ5yp1vnp3D7HX\\nR2sLvgWrUCNBKANmpoc4OKQwKRUvZJxYao2IN47GvPvp89x6MAYlGeMwTHOkX8URDkkQETXnCMqM\\nYZ5jpIPnuoiswApJIOfwPI9Cl4DBt5JEGLwwJMty3NIlCAXCUQTWYJSLKTVCOTilxfHquA4YDFU/\\nopgVGLek2aqQxKea4UiB53kk6YwKIaowfOj5DyBKQyUI8Gr1P06i/gDvtKC+DFwSQpznVEh/HPjL\\nf2wJYamGLuMsI/RC0hkYKymEg80zWkGFuCyJHBjGBUaVnK6MHawuEUrSjycIBZQ5CodcxIROG2sE\\nnbmAnbsjDib7SKG53G6gqxGlKRhRgHYxKkOJAF1mVKRLOU2oEOHahNxaSuUziUvaoSIMq+TFFOFU\\nKHIQAt5zuUOzWSF9MEEFLucrc2hT5fbhAZtzHlHU4mgyJSkLXEcw7I8xnsti3ac3E3jNBiUTnIkm\\naIZMez2062J0zqR0UE5JdxKzuuwTRk3KtODu9h6hkQRSg1tSDXyGU00lylm7sMQXv/ImCQGBdGjM\\nV7h5+wZVV1HkCcaD43hM4DWZ5CPK3CKExvEqBJ4iNQolwPo+rz2Y4gvL9q0RV1fajIZ9Sqs4GCco\\nz6Pbn7LelpwPa3QHQ5I7Jb5TEqqQpXqd6zs95iLB7bt3OLe4zpd29ghESCktjapLV7S4tjsm14bz\\nSxX2him+tUSOIayHeG6IUJYb3ROsFSgnYHeaMDe/wq3BXbY/u0svienU5kiMACnwcXj59bu0S4eK\\ngiSN6ZqAvFBEVYe4gNCvMl+fY31xky/evs7VRx9h99odJrOUJ1bnsUWDQW/Edz75Lg72DilmCZvz\\nHdLS8qA7ZqFmcHSJtAW+UFCvks4GOJHHYDpGSZ9za8tQaELpcXvrmFC5FIHDydGExlyDRzoVsAWB\\nX/Le5z/Eb/7Kb+JrwfpiB60E9dYKRvkstOtgNcVshu8tYhxYai/R28+5vvUSJ3HGjd+7QVKkNOfm\\nyXODH0Qox8GUJVo43Li7y/xanVlcElsfUxrKWoP76RjXr3CnP6C+vEAe55hAnm4haYu0YKSg6gSk\\ns5SoMocuwXEVeWZZW1nk/vaAq5cuU84mOJHPpXc9ST6ecjLoA1CruSSJIEkSVhc7/N5XvkJZNkgm\\nY5YX5unHBWVhGI271Gpt9KCHRGCkQSqD40a4pSI3JRUPtIKPfv/H6O3uEPlVlJC4voMpBZmZYrSg\\nVqlyMhjjSUGl1SDPLHfuvUFEjQtX1hFuiZsFpFqQdaffsMC9o4JqrS2FEH+L05snitNfD3rzjysj\\njCA1JYKAXndIs9Eh1yG+FZTCMCsyKp5kWnpoJyfLwVqBEBZX+Qih8R2XItFIfbr/ZUof4xqEzLF5\\nncGgh+NKfuh9m7x2a4g2hrqvECbAqBIlBNIYkkQgqpJhbJiLDMg6RZpRr9SwImOcDImqFeKxwssm\\nJDJEZzGjniQpplR8RZYYxipisH+bR8+voTKX7WEfz4vQWYYUJf3cpSM1o1hQcz0mvQFSCxJdkI5S\\nhgnU61Wk1TQXqvT7CbXFNgejAXNuwdAoQtfB1QXVwGOU5RwPIPBKZiOfG6/d4/JSk5sjzVdfHXDt\\n1g7vXWtSpi5JPuJuP8FYzbSYEoV14mSMdCNKLFlR4qCQjkVqh8vnqtzeHiItXN/vEfkRo+mUSiC4\\nc9jFER6vHyZ40tBwClpzAUdxyf27uxgVEjsCm2nec+lxXr2/g3ZcSglCu8wvL3O8dczmRoedox73\\nY0tpHdJScHXjKrtb99nuDqnXQgyWmXUIhSSQDi9vbyOljxN3CYIqvdmQRqPFfLPGYDRg51BzmCaI\\nWodBL2VzZUKajTFZncOtLstLdfAVH//S6yxtLPPbX77Jgk6pemBUxBv3bzPXWaJ76x5Yl6jWAaW4\\ns3+IrAQEnuLG7gCbuVRTl7s7e1Tn5nDGMZHrobWmP50Sp4paVKfMNEuPzPHaV+7TqbYJ622ihQ5f\\n+NyLhLU6v/Pbn0HWWjhFyri+wu0H13CDkDKN6fcsUaPBxHo4SYxMLKWZIA1oA6iAxmqTeinRShAJ\\nTZwWvOepK8y1amxunscJIQoMlahOb7CHF83RO+ly98EWZVqiRJ1E50RzPkKF5FmCNDFBrU5pCsJA\\nEbp13DDi+OAOP/QXf4iN+TXyScwknvHbn/oETzz3KE8/9QRlIYlTuHjhHPl4gAoiuuUJa+trKOXy\\n/Ee+nXI8JC8zgrkOW3fv89yTz6GN4f7BNr/+S7/IpSefIk9SKu2QycGMJ559gla9za3Xb+C5irZx\\n6aeWdnMO4+QUseHBrdeROmN94zxvfOWzzM/N0T6/yfXPvcgkLzjoH/KhD32IX/2n/5J3X73K0cEO\\nFzZWKNLiG9a4b/mbUu1q1T7/zLtQRUE9CpgWDulwQBRYhFDovKRQiiTLcXGYyZIv7R5hrMXzPKJq\\nhWanhtAZk5HGE5qo3kQIhet5fOz59/MLP/+rLNUbrC+4HPRimpFAiJAodDnpDrFG0qy6HI8SSp0x\\nnGUEbsClCy30DJQjGA0ndNotlCsQqSY2gkogSIoSkxmqVUmcSbJ8Sm4Fa402UhUkWpElMVprpmVB\\n4LjUIsFkVFKtK1xVZzQtUI7B8XzibEqCT+Bb8lSASagFdRzH5WTUZ3FtiTIVBMKgpSGfZfi+T5nk\\npLKgLHMCT3Hc06ROTl4GTPqH+NUKNaEJnIDDdEbN8UGdHphgfdJsgu96+EHENJ5R8SMqNUVvklMm\\ns99fNpVZiZWC0HUYpjMCv4ooE8TDg5ZcKzJVYNIUN/AJfAe0g1ARJp+cHjBFAQgHTyiQHrpI0NqS\\nFimBClFCU5gS15cUxsVXDkqCF3gkucYpSwpH4AlFYSxZFlPxKlgBTiDoNGv8yPf9AK/fecALX/xd\\nPNUmyYZkZYI0LmWRIh0XR2fEBVy+eInj3hGzUYp1NVYblBAYKdDaEkiJcRxcP6DQJUJr8rzPufOX\\nec+T7+b//Te/hhKSK49fhtxwNEg4OOoTei4FGseeHp4IT5BNMxzHQ6PRWY7yXVAuNb9CXszwpcsz\\nz1zmd196DSUk0yQl9AOaDcv3fvf38ea1m6wsdNhY2+Tw+AA/rDBLEpqNKt/5/A/x4hc+x9HhLqOj\\nmMxMObe2QdCucbh3SP9gl0euXOSoO0FZj8m0z/mLVxj1D6g35rCeh84zytKghMPHP/5xLAVCOvyN\\n//rH6NQvc//GXT712c+QTjP+y7/6vdx64w7h4lU+/Ru/xNpylUmuufze57j+wuc57vc5v3qBN6/d\\n5iidsdBaIJ4aXF9jHY0nFY4UVF1LkkPN8Vl+/CpffeVVepMJTU+xuXmVD3z0KfZu3mWxVuNoZ4dh\\nt4dOLMMiwa9U2drbYWVhkV53wCzJ+bZnH+XNO3soV7FcdZhpzYO9ATgW+/AgrCkkQb1KUpYsVBr8\\n3Gde/LK19tmvp1ff+oJaq9jnH7tI5Edo69ByBbcPDlmsVygLTZqmRM0m6XhKqjS6CPj89n2sMDie\\nS+gHtFotnMiQ9FI6Sy2yGeC4OI7k6uIio+49Xt+fsNpy6Y1KIqW5eOkyu3dvUqnOUa84KAsPTmIm\\n8QiLxFEKFUkuz81RDaE7TXGNQ6Ez2o05xlNNVE/oDQz1UOEpByUlD3ZHZI4kp6CmPOqNAJEZQJNm\\nJa7vECiBH1YYjxL8iotOpxRhB5vH5KUlEzCZZawvtsjGMaOipCwSVlpVqn6FEgdjLZkpqLkuWVow\\nGZ3Qbs6z3ZtSyIxRURI1mxRFQTGcsrq2yMHRMX4YYh2JTjWuVAgBbrVy6nGgAnQ+ww1D3vPMk7z8\\npWv0Tg6o1QOUkZSuxBcOKJ8syxDSUpYlxlp0llKtz6GTCb7nnJ62uxKBh/AEpvCQEpCCsjRIeept\\nUA3BuDAZFHiBSzLLiTyFFh6etMyKDKVchBYYLM2qT2elwXvffZVkLAi80zzHu/tM9ZQPvvf92Kyg\\nUl1g53iP0PMJRMnWcZ9ud8CdrdvkuYsjMh57/Aq3t+7z5NUnuXJhjS+/eo8bt9/A82tcOL/Owd59\\notoCTp6RkVPMEopS8PxHP8zWzh6b5y7wlS98ju/5we/njZtbbKytEo9HyAC6JwOWVjvcvbPFqDfk\\nwqVH0fmErCww1qFei/BkSJZPKR2HfFKgTYYsJLKiqDVabN+5RT+e8l0f+XZe+PxXGCcTHr04z2AU\\nc+36q7SqbeaaiyytdrixtYXvwuUrj/Du93yYg60D7t7c49EnH+GVl77IK19+lYuPnMNzA9K4RBQn\\njEcJg7jPtz35NNYLKFPLeDLAIrh+fMhCc4nD4y1+5Ef+EuPdbYZHOywvnuMTn/xtfuwvfT97Ow8Y\\nTRK0o3nxS3dYX+owFS6uE3K0d4d3P3YRU0hiKZmMhpAJxnmG1vr0C8pqKpFPmhR4jiD0JbqUTDNN\\nJEpW1laRzWWefuIqb371BfZv3wCnRZlO6I5TPNeic4MjJU5gsZlLns1YWmiTJRlZljHfrmOiKnfu\\n77HSbjNOJlTdgMh3qDaqTAcTtHT4F5//0p8PQZ2rRPbHPvAMKCgSSEvNNM6o+oayLMEIDCVKKGZa\\nkJSGl7Z3cTxFNXRQQuIFIXmp0XlBGNaoNiKK0uA4DhKFKBLKUpMXM9xQElhDLWgR+BE+GsqC/VmO\\nUoqTYR9PWqyGteUmC+Ecd3pHNAOPoNlk52jMSs0wGEpG8ZAgCDg/32Ayy+hNJmjjYq1GupILnZCT\\nCdzpDViZaxM4Dp2apT8uKXWCkVXqYc7S/CKvPuijKbCloOkWDDTEicV3C1wkjvSRgUfgekzTHEfk\\npwL40GUsTTOMFWiRI6Uk8GtYa3FdRTyZ4tcjTK7xoxBpAeuQ6IKNxRaL8yHz7TZaeqwtdygLQZyX\\nxKMJnhIMxjHbxz2eenST+blVXnr9S9y+u095+j1x6m5jcqKoDlYipCUvNAqLkC4GixCCv/Cx9zCJ\\nLY5IKXTJ+fMXieMpWWrQWDAFMlc0F+o4bsRs2mNWlOzsDFmYa3F+bYHBZITrhAx7XaR7uoSt1VyK\\nQhH4kt6wyziz5JOEWRYTZykffvY57jzYQhhBZ77Gm2++ydLyJbrdLrXQ47h7xMJii639fYoxvP+D\\n7+M3PvEJ3n35Mo8/+gxTO+HKIxe5t3WXNC9IJwkr6x3ubR3h+xFlPuPZZ57gi199HVtmdBY73L15\\nj+2Dbb7rox/jg+97Py9/4UXSRPOFl1/lwx95L/3DfZ799vfxxhtvQOnSch26vSGpFTg1xcd/41Nc\\nPrdErdbheHubp595jObiOoODLe5ev84HP/IdvPjZz3PlXRcB6PYnlMLHxjMa7SUGB7dQSrC6usH9\\noz6ijOmdHLLYmacsS7QxTLOMer1OOcuYlTHKOlSqHse9lJmKkL7PufU5Ni5uEG/fZDqacXI0Zf38\\nEsl0Rp7nJL0xuuGytXVCq+5DDkvL7dODrRKy3KKV4mQ2YdTrIfwQqzXCEThWsNSqMZlleMIBNOM4\\nxfdgdXmFV649wA0j/tp//p9y86u/y0svfpkLa8ts3T/g/OICjcUWt2/d5bn3PMO9nS1O9gb4FYk0\\nlrX1dfbv7jPXbBLMtUjymN7+CZl0CB1LvTXHdDRkNEuxGj55886fD0FtVyv2h59+ksD1EOXpnukk\\nG6MUCCPxHB+hLOk0Y6ZTRrjcPOrTbNeQhUOiE5SUuEID4AUu1foceZ4T+DWS2Qw8yUanys27R4S1\\nOmWZE7kCYyxSaXzpMdOaxU6T7jhHJwlaFjx9ZYW6rPP6vfucDFOELAkCD1kahDr1j8zzEukICiGQ\\nhcV3BI5bAVVSaIu1kslsTLPeQhhNZjIqygUFgR9x4cIF9naPiQKXm/s7VL0AXaS4TkBhND6SwjF4\\n0sUJK4QOyJrLcHuC8Sx5FvOhDz7GSy/tIhQ4xlAIgckNOAYrFI6BWqT4yAffj7aGorB0FufwHMva\\n8jkGgxFW+ezt36csJYXNOT44ptPp4CtIUij0lOWFZV566SXCRov3PnWZ+/eO6DRD3EoLQc659SWy\\npKQQmt2DByQ5fPmr1/nxv/A8X3rlTbKZ5vITjxC5Fba37xM1KjTdkMRzEcYy7I+ZxlO++2PfyRtf\\n+Qpfvr5Hp6L4zg99gNlsTFJK3MghDAJu37jNysoC+aREBylZXBBEFca9AauLixSU/PrHP89Tj23w\\nkY9+mLt375KZFE82uXfzGtaJWF9dolpxePX6DaqVOuc213jt+l1e/uKXac45/LWf+FFe/L3XqdZC\\npsdHjLoGP1K051y2HxxQD5pMkh6tyhy5K4gzw/piA7+xwrV7W5w/t3zqp5v0KQkZHdxn88Iig5GA\\nNIPAkmmNtT7dgwfMNeaoNkPKoMPL16+Rjyb80Pd/D6P+ETdeuU6ZTHj/B5/F6hKduyRZH2MtZTkj\\njjVRpY4SDnu7D1hoL+OG4ErF7uEBV68+zmuvvUan3URIh9wWFLkh9B2scXBVQGZH7B/PcETBrcMx\\n5y9t8vjTT3FpbZ39my9zuHfIUqvKrbv7NNo1NleW6PWnDMcj8tQlMRm+lOgipcxOT/QXNtfY2d5D\\nK5dp9xBZZK/UAAAgAElEQVQvnCPOMxKdsbKwgIgL/GrI4cEea50Wo/GMpQvLHO2ecJJbnn33B9m8\\nuM7Rm6+ye/8mRQLNpkSXLgUlNRHSzU+ohh2mgwmthRaj4wFhyyftxqyca7M1HLAatsiKggSXk5MT\\n2lWP4/EMl5KKV+FXv3rzz4egtqqR/Q/e+wSyzJkVBeNMEkhFKDRGl2jrYB3IZlOsFEyFz/Z0iBv4\\nVMOApJRorfFcieu6KOlhjAEnxJMWLUueuLBEZ/ECOkspswmFcejO9inzGjtHR3ic+r/NVRK6AwfX\\nM7Q6Fc5tXmG9IRkmluPjCV9+9Q2EglmSUQ1djJYIX/HU6hw2muOVa/egnBKoEGNKpHRoLTSYFTkr\\nnXnu7+6jjCTPUxAaJUs+8NRTmLLkYDpm3BuzurTIrCy5eW8XhcKYEs9xcR2HK5c6XLnwKOsbl3j5\\nhc+wtLpBXBQURYqrBLWgiS1yxkVMu9pgHMekeUYYCg5OYi5ceIT+wQ5ZXtJZqNEdlJxf7WCwjIYG\\nz4VJ3Gd5fYkkEdy8vc9gfMLe1gOef/4DjMcDFheX8B2XB9v38Crz+LmmtT7H4UGfpbkm09mAJAvQ\\n5ZhBXOIIQ0UWfPt3PM/vfPp3uX5vh6XQYzCd8exjl9k5OCQKqqTdfSbJGM9qgqXzhDLlja7Do5c2\\nGKd9PvKB51hstfjNX/wNKnVNq+oR5wqhckb9hMBXuNEiWhqKdMTO9jEX15uUVuG7dRxpOemNmZU5\\nngLlOPhSU8QltlbHbbc5HmiKeMKH3v8ID7ZHhJFkminWFhsc37vDye4Ji50G6SghFwWCHD+qkaVT\\nrAwxeU4ys7hV73R7wHVwraRR0Rhbo8hmCGWRyuC6LkIIjvojms0QpKURdphOp5wMRnzp1jad1TW+\\n76Mf4Py5FT73bz4NccLa5hqz0YCRSYinM5LYkEwGLK1tUGYGQUahDbVKjdFkxGQ8ZW1zg9LmpLFg\\nqV3hpNdHChdV8fAKQ+kWTHtTsiJHCIE0LvtlRtBssbm2zhPrFzCTLa7dvINAY7TP6vlN9vdOmB3t\\nMP/IJkcHXYRQLNTqxEVCtzfgsJdQna/jJjGFXyHLC0yeIoxFyohazWc8G9OsR/gi4KS3T9Wp4dd9\\nKr7PrcEIETR5/9NPwcFtXnn9Os9cfYTj/S6J1vTGKZWKz9Jak2FvQs2rszzf4cHgPmImCUKPURrj\\n4rJ7dMzixjJWSVRSUOiS6SwDI7BOySdfu/cNCeq3pGP/W1FIslijpWEhatH2NIfTKbYwWM9i8hSJ\\nwvEVxhiKac54VrIUVSm1jzEFnudRqXoUOTief+rEqzykY3n/01doOPM06m1OJoesbixzcDykcB3y\\nxPBsp0YSG86ttOiPJrzrchWNpVVvMBgXpNSYxUds75+gQosKGyxXLUYqfuA7niHJBTsH+1xYXWV1\\npcP29i47x10ubixjsoKwGvHIxgZ5rnnq0mVEILjxxhssbGxyuHPA6uoGuweHLLYlj198hFogORiU\\neF6Tpy9t0pt1aURV2p1FtrYe0OulCLYIZcDdO7vs9XtUggpKOAjngFalwnCSMqpvc28nw9oRiY2I\\ngHedX6PaCJCTnOQopk7Cwa1jbFkgrCRsuXiZJp447Nzc5t5uhtE5QVWhtcJVgt29B3zxc69zddGl\\nHM3orMyxYdZ59eXXmb9ylVF3RF4YAl+yVq0zHPZZXW/z6X/88+wc3uC973uOgwfHzA57lPMe1XzA\\n7r2UK5tzdFprfOKFr7DRnXKcp6RBg/n3P0K865LnYz79W9fR5YjtrQnepXMMRyMyKQiky2CS4xdH\\nqLDFcH9M4LkcHM7IsgIljxmOYmr1gDie0qh3qEY+46w4nbEfHnB06xoan+r8EsOte/zOb71B6/wi\\nT129iNU19u4dMFetMuoOcEKH4TDHURnaSPrDMUKXZCYnbEjEOME6Dn5hCSJJkqQ4fkyzFXLc66NS\\nQWXBxxQezXYTr7Ts7Y9heYzRkje29mm0AqRbgisYnczoHY+YX4Htg9s0ak2mowzfC6k2PMayJBkM\\nabSbWDyKiaY37jFfmaMeRBwfHhBUItJYs510WVpeYDScIGaaqdB4VFBKMVevY6RCph5Hh3sMeikr\\n7ZzeaEgxHeMrj2maUa+HvPTiC0hVw3c9ijinVB5J7wSTZaTG0Bt2WVrdRAqfXpzSDgIOxlMqUUBW\\npNhsRhg2yZOYn/pHv8B/+2M/wFJjEb9iSaYFuUqYTgpqnuLBvT0WKg7IkrsnPepehCdjmipkbXWR\\nna19cCWypnjj/h38ZoPQz+knKZPEsNzxWZ7voIzPKJ3hShcBZDJmbW6OvcHw68nU7/MtL6jWGjKT\\nUwtqZKbgxvYBnVaE4/vs9GM6YYiyHpiYibYkaU6ep+Q2w7MBjlS4zqkoIxXacVCFBmGZDzXz1TZl\\nKVlaAhUuk+YJyhraUYtbe9fpbK6z2fG5d5wzNx9xfGDprKe0a+uMxn0cx+Hm3fu0WiWbC09Ra1ue\\nuHKFT/76JwllyOK65OimZhL0KZKMRm54/LlnuXnjDhsbc0wenHB3d4t7Rz0uX75MLUh55vwGO3fv\\n81jNkO3ewIurFNkB06NduiUo6TKIXT51uIcXCVzHMIvh0rkLRGVBd5pw69ZXOeiPOLdQxRd1TKFR\\nBvp9wWG/j15pseEFrG+cZ+/khKXOPNe++ArkhknaRQjBwvplxt0hTiQJjcu90RBTSAav32OrN6QX\\nZzTm23RqC2TxgKuXH+XwxhdpDA945oPfhpMqpswYJpoL65sMxwNu7x7xzLvWGI8mGKFZaNfoHh7g\\nB5ZK2OHurR0enBzzvosXePX1e5zMYp650GCU5XzutVfRQvNgNkVKh0DF7Nw/YH11Ha/QjA5uMx2d\\nENbqHB0doJQiSTMSIuoBDEcJtSwl8BVJHKOCkHKaoXwH4VvIDcvtRYajjNwX7B4e4TmCC5vnkF5B\\nLWxy0u/ywksnjM2UcNbizs0Tvv/57+IzH/9tTBYThJbIbRNFEdv7Q1bnI4wToc2IzEjETDIqBHOR\\nYZrHzFLL1StXGKUzhlND5EW4kYXMYZbFYEuO+gntts+od0zgtdHWMB0XyEDyb3/1U/yHP/g9pMUU\\nX7TJyowsKSGb4tcWqEcB46nHxUvnODo+YGFxmen4LmWWYyOL9DwqFR+/UsfICXNhC6NhNEnxKx5Z\\nbmlXNEYJjg9PqIRzIDUlkvzh1tP5ixd49XPXMFWHqufQGxyzvNzGaIujIuJxxu7BCY+dW6HbPeZg\\nOCMM57ClIibGdX2u391hdbmBF1SwY0EqM9Jyih9V+Ad/8ydPr0P7BiEMnucxjlMqzRq6SJiUEc6o\\nTzXsoMcF4fkmvZOU436forS0F5cpp1N6gx5JDHsn26y164RRlajlcTIYELkhQmlkVqIcQbXmU+aC\\nWVGwUK98w3r1rS+oWFypSJMxRjnMN1wcE9AfTViu1DGqwAssaWkJPIVWJVhLFITg+IhCn94CN4LI\\n80izGJyIIpkyNT6f/OTnePR8nb2vglIV0kBz5/YutRq899F3cffWLWyjhcp7nBzUuNmfsXyQky8e\\noBODry6x1F5iZ/c+d9NbLI8V66112l6d13/nBXrJgDD0OT5RlGlKFAXsff6Iaq3C668mODbBKIdW\\npcP44ISBcOgevkkKHI5dZKoYzx4wHwn6o5xW1SOTlnKW43sRdqTRrQVWFpsYJhyfdImkotef8NjG\\nMifjEaEsMBoSR1Nzalw9t0wlCDnoj0jTksitMJwmZPGAMoeo5tFZWsWagrBSZZrHlMmUfjZD4hKn\\nCZvzNYYHBqM1Tz7VxHVglMzY2095/iMfoshSlAwY9zPKJMYNFUWR8+6LSxw92KazuMHu4RHnFgJ0\\nHBMjiI3mA+eXydI+SZJQrXgcDg6oR48ynPXRaYnjW/JM8ORqyPr7vptuPMP4lrX1CxTZC1QbC3T7\\nx1QrITYHI3KkzJipCK0cjvsDVheWCCsLPLh1/3Rlg0VJjzDwEW5Jey3ilS9d57ErlxAiZ3fnCFRO\\nzRfMhjOSPOfSQos4VMzyAe1OlUc2ljg83EeFVeLBjLsHuyzWW4hIY2cxrhcyyzJGk4RaPWQUD1nq\\ntIi8BfYOu0DJUe+Y9zz5JONkxtGwy1y9Tf+ki19xmRlBrdLhM6/dAGlxZYSjBPX1BTqrHRbnGty4\\n84DVxQ1cx6ferBE5VcbDlNQUbB/fZzwsSNUuyvG4t79Nvd3Ctx6zTFOJFKOjAbUFl8wUNOdamGJK\\nISTSdTCxpd1eJi8yrCeIZi5Bc47D4y5ZGTMa98FpIIlJRyXOcoN0OqTXvc/Fy09Q2GNG4xQ/rOIn\\nhhL4vVs3WIoi5uYbCN+irWKSxGhbIkLF3Vs7zC80mVjJ3NwcRQxxmfGgv4frBJzEUxaWLrN3sM/C\\nag0vlIgwYDooGEymnFtfI9eGaT7BERqlHKqtgErVw1OSMKyRTSdEUtAdHNMUbaKwhjEzZklCUImw\\npeB49I3+E11QP/3TP/1NksK3h//lZ3/mpxdrEZ4bgi7ItSUvLLWqYlbkuOL0x0OkcMnShJn16M5i\\n0iRl0O9T6hnFLGcy7pOmMxxhiHwBucaUM6Z791EZgOBgPOaV12/RnguptuaYypA4y0m691ACzi9U\\nuX5vl7nIo/Qi9rpHXHv1TfaPhxRlweOXNlhfaXF8c4fxcJdx3KfVDDC5RSc5rp8gCkuaTKm5gsJM\\nkTpFFhLcEiFLyDPyVKOUwGYZ2s4oUo1xBGkywxGK4ywlL8tTh/3JEV4YoW2Jg0MtrDAcjVE2pSgM\\n59orTPMRm+c3KWzOeJwwnBZUHIe5RhtdlCTZhOk4Jo5TOosNcu3y5rUHFHrGcDSCXNBeCjk6OmGu\\nUSXLZrTrLRrNJsNZzkefe5ZBt2Tn5h28LOP4YJ9mq04uYBLPcP0Q4ybkOQhdMFI+oeegpGJz6f/j\\n7k16bFvS87wnYvXd7vfOnc3JPO09zb23qu6thsUq0mTRpCSboCkDkihRAifSwJ74N9ADw4DtkQc2\\n7IkN2oANQxIgeSDZIATZpIosslisW7c/bebJfufu9179iggPsqypyrOS4yfEivVG4Pu+93nHfPbF\\nBUoqfE9yfTNjXUteXM7wbZtWq0U/Fnz+5oY7nSEWNSWGWS7JTcV43KcuDfOzK2Y3byjriiQMaOoK\\nN4oRusQLWvjRkMuLCwbdkMXkmuubBcK2uZhfMh4f4NY107Sg3BTcTOdIXNxacXJxTeB3aEyNA0Sx\\nj240r2dL4rCDqAXf+953+Z/+4H8hy7ZYaIRycFyHRtncXM+osWgqTZqtGSQxolF4rgva4U8//gtC\\nz8EJNLu9O5xdXWOh8f2YbballgalbGwFlZJMViuMhrCTYNkWgQeyrDg/vaTfbRP6DlZg8eL1GVrl\\nZKZC1A2O5WI7LvPLLdsq5atPnqIRNKUCS7DdrtFugBc3XF1vaAx04pDtJiffzkgGuxRlihYWRVlT\\nVBbT5YqwFfL0yROuT17Ra/eZbzOiyMHzQ4rS5u7hDpuq4OzynMh3QdtkQtEUDbvdBNt2aZSNH8Tk\\nZYZrA8ajl4QkSYRuNEopKqXQUnI9W3HnYEDXT2hMBJ4iidu0XI3Oc3zLI68ztLAJbIs8z6EytCKf\\nebohsm2SVot5XnB6foblWXiuT1orYttivprSafd4fXFJum0IAh8LxfOr6eXv//7v//f/Jr36uSf2\\nGwODsENRZBjjkHguvmth2y6+bWGFDsYYlFUindsGlFKKoqypqgZVapZZhjYCIS1u1g0X13MyDcva\\nEHRChAUvlltkucRtBzzstri43nL55Y+4eP2CIrNYrOH56RmjQHNydkO9WlCtNuwd3iVvcoy02ORL\\nDgb3+PLFK6p8RT/uoHIHrbb4nqDJLRolsXyHq+UGVRRUjULrlCKtWC9rSllQiobNWrLKDWX+U9tc\\nbpBWSK0148hhtxPREw33x3fZ3RmRZxbCwMc/+QnLTc6gO+Ddx4+4Xt5QZIaL6wltt01RVwRUXM6m\\nvDl7y5vTYzYbxaqoyCrFYpMxX+QMBzGtsE8rcsE2XJzO6QZdOlFC7MVczLbUackvv3vIH/zTP0KG\\nhodP73Nx8oon7+wx3abMrm7ohTucXd5wNROs04JZXtL3W8yXOR+/OuMP/+xTcremE4fMF4pPLue0\\nvNsX4zIrieOYP3oxw/IdLtWGk9UGozVSlXz9vVsXi9IlH3/5ivWypC4rqtrgh110XVE3LlmRc3H6\\nkm7YIrATkt4O2hK4ssTBJt9suS4UAQIZ+ZSNIgg8rCjg7v6Ixi4YtkJm24r5NGdVaQw22zJFBw7/\\n8J/8I4JIcrDTpdpmTJZzbCHJ6jnjXo+9dkS3ZxHFAZ59OzZnmhppw53dXSwhWS9r0myNFwoW2zWZ\\nSomESyhdjNiS11vW+YymKnGMwAtChGUhcDk5u0aLGlXWBJHPYrriw/efIbDougleGFHkiroyyE4H\\nY4dczpdsNhsqYegPd6jcBs+AJ2LuPRjTCgMmywxX1NhRn2q7xA065MWSKInRMsMLPcAiCQO6e0P+\\n5EefslkUrLOUPCspq5RPX59TFDkfPnlMGIYk7ZhASgIfNBZpI9lmG3STY2mLbWo4uzzj9c0E4f/U\\nheV65PMViSO4e7dPk9YID64WV8imIc1W7A3HlEChG0oJgYGX1zOu1yVJJ+Jmk+GFCVeLFRdXU5RS\\n3DvcIVK3UKK6qPHjkChqYxnwvIBRJ8CPfIT42Rv3P/eCihA0pqAqDeusYLttAKgKA1KgKkVhDDQO\\n0vKxwxbG3AIwANKyunUhFSV5VRJaNVqlaK1Zzbe47R4TZfHOwMHzIp4EgtN5xt0kRxUNrbhFIxuW\\nyyvKwjCwfXZbIUrXxJ0Onz2/RAhBVeVcXMxJ11vabQvtttgUa+pmy7Z2WDUZjmfhBxax8PFaPmDh\\nS5tCumjREFmwWRgcJSnY0PIs8rJAiJKi2OJKg3QatquSMkvpxi2iXsSXb96wXMw4ubjgF7/5mG1W\\nMJ0v+PyLVygL2n2PNN3wyavnlJWh0xnjBjbGssnSkiDwiFsR16stn78+B5XhRZrV/JrFck0iasLI\\n4tXJMdP5jLeLDb/4lUPef9Tijz47pRcZtCzxqor9cY+rSUaChQw9pssFcTuh34LLRYl0Ha5nb2hE\\nxfW6xNE58/mcFydTbpYTvnFvn+uFohVLng49VjcLArtgtxNgq5rACcgbTWUE3//oLUiX+dkZTx4/\\nojd0CeMETY2FYF2l2L7F9dUULMlkccXN+ob1csF4Z8Aqq2ksiQSaqsHzbWRtCB2PqBWRFSkXsxWh\\nZbBtyWJ+xd5wj/cejnCkwLUNQRBQKZtvfus7nN9s+Pq3vkktPYwUDNwWN/kChc2oN6btOxyMR0jL\\np9PvcXa+YKfbZnc4wJMuRVHgN2ApC5XaNHVNulixOxhBrfnoyxOU41I5BpSmKkpM3fD+s0fUWYUi\\nYzGf4wSGt8c3+H5IVuTkDSRxwJ3xkMTNGLTaJLaiOxxQ5xnT6zldr8NXvvaYlRLUymAZTRzYmMDH\\n9Qx+HOHZirqCerPm6mJxi0wsS5ZZxvnFlPfef8rBYZftqqH4qV3TaTZsNjUff/o5+bYg3a7J0pqy\\n0Fi2pDeK8L2ExSpjW6Y4luZwPKIfOshyS3/Yo65rnCBkm2eUuUXUiZgsbuvoYXuI5zgUZcrR7j6B\\nGzC/WnB4/x5fe3TIg2ELVWocS7NapRhjaLVDAqm5Wm0ppWJxMyHxQ5azLVHo8ubqnN3BiFJLLqdL\\ntrX6meXq519QMWgp8Xyb0DGM2h6V0mhLII2BsiE2ILWk0VDWBUIILGHfjhM5Hp5r0w49vLiNUorE\\nb7FdbPAch33LwTI1n14taJqCSkZcLzd8cV4itKEpG4SouXt3hzSrKV3J8WxGpRW6qnHdgiItcW1J\\nqW2W8zN8EVBVFaoRaKWwTE1WFFA1bOuM6+2c7fUc0UhWRUNkaiJto12DZ0nsKCS2PVzfJW7HODIi\\niCOCjo+uLZzYwpURnt+wup6CdCgaxXi/x9nZAq8uEZZkVa+xq5rpokBlNaKy6HgelQW9uEfgW0S9\\nDmfX52SzCxAOmzLni+sbnh9PsGVEr9OjtgXTRcbVtuLi7JyHexFX0w2vj7c4rnVrhbUcFjcrvv/8\\nDMcVLGrDdlPw0clbys2Gi+mWZ/sjqEscOti2S9HUvFymbHLJ03sx333/PlVd001ihlLxzX/n2xRS\\ncr+zh6cdJllDQ4NvCZRo8JyIvFrz/gcfkucpwk4oioKmMGxWczzpo5oMP/C4vpzz7OE7yFIz7g04\\nfXNGWigcOyDpdAgDQVqX1LIijgJ8DLZlEbgRO3v7rFPNk0cPuZ4dU25ShDY4RpBlW06PT1CyZqcX\\nM18tGScejiwJ2zHj3oAgcbEQqFrww88/RTUlRjo8uz+i1+uR5zlJ4uFFEZu8wE8spLMhGLo0Vsr1\\n+Q3a0tzf3+Erd3Z59rX3ABvflgTC5vz4JaNhh22haMVtmlzRboVcXE7xYgdXaZy2w+u3byimcyzT\\nYGwfXVS0vBY7/RbpJuOzl19QZgXLRcrbyzPObqYELmjjYmtNUxZYXohnuRjLxnYkqa7pJn0SO2I1\\nm6KUod1tUdQ5omlw/RHlasHTdx4wPGjjeyF+KAgCn8CymF/O8KRm3GkxiBLCMKLE0BSadWZYTuZY\\nTkMcSzzbYbNacnkxpSlr2u02i+0chE3owZ/96DPOLybYwuZifsrnL8+42VRs05SysnEaxf2jPV4/\\nP8a1Bfd6Xdpxmyj26PVCXGm4ubnBwUYXBZPFglAKukn4M6vVz7+gGoHnWhSNhWs7pM1tQds2Na5w\\nsUIHLaA25e1cprqFMweuxKBQ6ta2prWm3mwYj4YgalyrwdQFJ+uMdVHTky7XpcRyaqTKOXAbeq3e\\nrVdbN3iWxXW65fX1kmfDDmrT0HYccu0TRQG1AUFD25V0QjjohUSeTV0plNHsD2JSNPkyoxP4BGFI\\nqQvWxYppmdGQo4yPHUKerlnNUt5e3lBpRVoblJZczddM1wW2segPXBzh41shabrFdjTDpANBC2yH\\nbt/naDCm24sInQCtLDqRQ2U2LG4mbLOGSDkoA7Vo8WbRsFot6Hguf+1bRxyOHqHrU+rS5uTkhlbU\\nImsqdu7t4DkhwkjezDfoosRUBYHb5upqwm67xarYsM43/PjkmnbL5nKWshO0+NHxGa4ruVxeYmmJ\\nbRmysuDf//Yzvv/5iv/zizOSXo++n3Mw3uHF8ynv77dJEotFvuadcYIUNqhbsPPDg10QAetsS15Z\\nnE+mJC0P6VqE7QDfsbmcKMLEZ+/ODteXNxhhcbVdM+y0iEKXnWHA8YsXrKZLer0Oo6jLOoPpasN0\\nvmLnsM2bt6fEbZtXL99guQ7auv12lRbQKIpcsX57w/64x2o5pxM7hNw2PmeXl6xmS7abktBrM+hE\\ndKMIX5dU2jC/mSIcm3VTIpqMgzt3WGwaZB2zOF8yGtxBCMGDwwe8f7hL5Pq8+uRLpK5YZDnT1YRf\\n/52/ixA2sfCZ30wxMgBRM+r3ePnFKUHiUucGoxpa3TFawGK5pcoraqfkxdUV0hZ8+fEb4tDCdgxH\\nh/e5uzvm9dsFsizQno1tecRKE8QRYRzRaAvb8jm/OMU4Di+P33B1s6axbdLcUBubwUGX0U4f3444\\nezUj3Sx4ezoj3WwpK0Wn02JebDg8uIPnW4haMQpihOXSiSpsZdOOhlBDbUFe1zhRQGNLyrLGFz6e\\n5dK/+5ijnQ6P9vfodQJs7dKKJINuzHSzJIm821dxbbh/dMj5+YzZbMZme0m73UI0JXGnTyuO6cQR\\n63JNOwyxLcM21/9Gmfp/178Fgmqotw0jx0IIh9Qo6qZBWYLclEhj2BQNFRY1Gls0t9RvDbZ7S19X\\ntUJaFpaEk7fXbAsHJeH+eMS6KGhZDrXjUBc5oW1z2G9hAg9tGtZGcVlqXr1e8guPI57eOaLQGteS\\nFCZDq5xGFmhj0yhF9/CAH52suJovuL5acj7PyLY5VxND4Dr0OwF51tAPItbbNYGfUJT1LXiiWEMt\\nqCtFq2PhSIWoNKbcIByb2Sznwd6QtCrQdUUrjPnx8Ssc6dM0mm7co1qnjHdDppOUbLMgpyEIFA+f\\n7LGuCrLlGs82ZItrvpi8xM42DPsey5sVwndIG7haWRwdDTnYe8AmnxFGLueTOa7nczQcMZmsmadz\\nnuz0kJZNreCf/+E/Z5UV3D/sc35yTpXWPBz1uT/e54NHHZZpSsszfPzikv07A9J6QW0klAppMp4O\\nQ6SB6SJlsbboDDyuT0/5V58fs04Leu0DTq83NKJB2g5gsylqFrMJ7SSmKZckQvL25IJlUXI9WVKU\\nJd/66hFCWLT8gHWVczyfYsoVpnEQ2mK73dLdb7E7GlKkBavNmjv7IZ2WR38Y0vdj7h0cMlvM2d3r\\nk3QS/uT5CTYW2DbC0TSi4mZ6zU9+8gmLmyuMMWx0hbFshr0+27zg1asXrJYX7I771LKkaeCjjz7m\\n4nJFpzti5Hc4Pj0jz9bc3+3RHQSMRgM830F4LjfTMypheHV5zc18Q1FXJEEbPwz4H/+7/4GqVjhJ\\nQHenTyuCs/SGbT7n3WePsHHoRRHS9ZlmOZ+8OqM2gm21ZrGoKRZb8q26BTwvC4r5gpOLE5qyYrfT\\nxvgeYWNzdXWFGwWcXE3Iq5JaK1TRgOXy9Be+jeVHnF1OEUrT60ecTWfItKI37PH2+ppSaBrHxgts\\neqMRjqsxSnO0f8jzt6/Rrsvk6pptVeAHDq+OLwmHEefTBcuiYZ1mDEYD1tMl7z88QlgWda0om4yz\\nN+ccr1JW1ZYkilBNSVUqLiYz7uwk+NLmwVGfJHLJw5SH4zF7u30e3HvINl1SKUm9XrDbbeO5FpET\\nMOwkXK5Thv3/H71QNRpjaUrLYtks8ZRDJBWJ7SGEzbZUBL7GkyWeqSkbC9u1aJoGD4du4INwkFKy\\nzqnCwjwAACAASURBVAtK1ZAVKXW+RTiaO52YQS/h8bhDjY1KN1iqYpFnbKuCpsoYxjGOY3F2XHE9\\nPSWOPGzRYIkYXTYI7SF0jdEOP/7kL3lyb5csrailIfZthLRI7BSjK/ICwsCwLnP6UZuyUqhM4Vot\\nhG1RK2gU1MLizqjHqhI4vuHy8ppvPthlna6xNGyzkh89f0tnMEa4BtcTXC9OmGWKq8sto7jF9XqB\\nL22ylcPJ+QQ/jKm6XUTjYrV8HowPsHyb2FdoB3RecX804OXxG159/hGlEQzihFGnzcODNr/7a1/l\\n9fEVi/UK1w94PZlQ6gbLVdy9e5+mKhgOB+zvHSGdisBSfPTqDc9PNgRuzTQreHrUQTQdmqbBGEPj\\nSD49XZK0fP7qu4cUsxX7oxZZrnizXvHb3/6Afifgs7cvUZjbCBHHAlsQxAFe6JHXiqvzK9yWTWNc\\nOr5HN4oIXY/J1YqO62G0i1OX3B3EGNnlk+tj7MDhaOcergmQXs319QIpJbbtc3U+5cHdZ3z+8oQy\\nndNuDTm7WTPPSiQGrW8vbqFcpNPh8dN9vvrkqzx7+hVmsxskBZvFEpTL7qjN7viI3Z19Xn95RVM7\\nNEqxv38PVzosJmdsixUP793n5PiSyfUc3RSowpBmS3ptn6y2+MM//YJFnuE6km22QugSbQV0Rx2m\\nyxUX59eEfkQS9dgJBuzu7CFdn3W+5aMXp5xOFqxmE+rNBYvFnGxTsE1LWpGN59lsspyyXpK0x9zf\\nOyRIurQHXZoio65T3ChipWqiwEa4t0Ac7Wg++fhjhrHHOqvQWrOYT/nk0zcIVWM5kj/700/wY5eO\\no1lta0LboWo2LDcVk+Wc9dUVxTZF5pqjezvkdUWd1ez026imodMJabcjai2JAsmw1eLk7AbVZGgU\\nSsH/8X/94F+L63Dk4BjBaKfLL3/7Q2xhY9sNk9klZVliZg07u3usNmsu35wRRy18V7BuSk5vZqyy\\nNVaZ4xqLpjbk2/Rn1qufe0EVAhpjSJstnnHQqiSMHK7TW8iHYxkMPqoRLDdrjMnRjUFhaHR9e+hl\\ng1FgS4ckDmmahvFOj6DZkmnNZLLm9PSCwEChLZSbsBPFVLWhrhRHPRspapRr887BgE26Zr5KScsG\\n13UwSLQAS2q6UYI2DruDLrpp6Pcj9gYdTO2QbhssatZ5QRK62K6kNpqDox1OZxOCqIVSitlqS7ra\\nslhu8eolTWmThBaL7RyN4nhT8epixlqVqHqLIyRC2rx5c03bUrguNHbOwf59bqY52s7oRBFZWUFa\\nM9htg5F4voWWDotFge+EHAwCHt3x+fDBAU8e7uOZhtiVeE4AouGzV8eUZcn9vV10oSmKgiQIkMbB\\nd28bA3/6w8/YVjUWMfPZknWu6HRszi/meLIiSxVNvaHt9m+BMrbNYr7lzmGXojKcK82fn864mWcc\\nDALOszXPX7xlGDvUCoRSSCza/Tb9nf1b3F6RsTtsUec1dx/0MbqipqDUFZfLBWerFCFXPH33fdaZ\\nIfYVHbdN5Ei28xOkEqS1xWgQkoyGbGYpvfGAf/H9H2B0RaElSdKm33K5WtRUdYGWDqYxaKtBpKfk\\nK0VjVRRNxmj/ENdyGR4MmW9mbNMUL9Ycn1/Q390hCG0cK+Dxgz12D/pI1yMMY44vJyRJwnKVcXZ+\\nzscvPkVVATfTJX/x8gTL0xjV4Lo+wgsI4i5aa37nd/8maeNw72jEdDLjzz95wTDpkTdQZiXtuM/h\\n7pCd2OerX3/Mb/zVv8J+t8Ng2Ge/HzA46PPgXp8kbtFt9bH92xwpR5YEtqA/3GNdSUI/YDvb8tHL\\nC4rl8jbPSWlmpzf8w3/2T/Es+OqzJ9ihx4fv7jPqJxTlllanhasNQdLGDxyGoz5BGFNVFXd2dqmy\\nlMFgwLZcsC1rhLCxXZv79+6x2W5xRclquiR2PFbzBULf2qcbrQGNNoa9hw/5ld/89zgc3KExLtKU\\nrOcpF8fHnF/MCAKHVndM2ZQkgc9NtkQYSe01NEXJZrWlF7dwHIduq00uBI1ouL/bI/Tin1mvfu4F\\nVSKRBtp4BF6IbftUlY0vJNLO8aUNdYnrWHSSDo26rXfYto0WkGsFRiCMIvAl2/R2SNdGsG4cTGnR\\n67jcrAsSXzLdCpp8QSglHU/zbHeX1UYTWgaXkOvJlkBGjPptXKERrkQKgxYShGZ/p4ew4Gw6Z293\\nSFkKhFAo2VAgWStN4gVcpprzdYmQDcV2TTafcnVxQ6cbcbDb4733DmkPIwajIbYtWa4yhBYEjkvo\\nWOx2Ih7tJCBdstqgG8WDe0MqUWPjoWuIPChzzV4nYbnIuXfQ4hsffEhWwiev51yfbwjdAIXBj2y+\\n+ZVnTGYNvWiH49Ml29QgQ4v1ZoapEywTEfiSvNFkWYFnR9SmwTSa/bst9odDCseibrb4JuXtuuKX\\n7g64v9flV7/5Ht/52gdMK0Er8rnJ5kgpCSyLp/fvURWSuOPRY4vt+Xx2OeEr+3vMrgoKJ+BgsM+3\\nHu/xK+/fQzWGVm/Iyy+fMxgM2BuMePj0GZbUnB1PcW2P0d4dbGkRxT7DoUe2FXzy+UfQNMw3Nft3\\nQibzAjsMeXs9oZPY+E7E5PSCUm2JXfilD54QtzuoqiSvN+jGY6+T0OhbP4zwLGzrtl559/2D22DE\\nWnN5MeVslbKcr7AsSWj75GnF3sE+geXx7MET3l6dk9U5QRKy2a6QNByN2ghH8OzZIbHTZW9/HyEb\\nfnw6RStBWUtyIxjtHhJGXSpd4YVdLt6c8Y3Hh1RK0+q1eP/xEbPFhCTySTdruoMWoW/hBxGrm4y3\\nJ+dMFjOEVhyfXpIv18xWBarZMGp7OG5Cv9dlOUsR0kZtKrabGavlBssH4RhKUZNlGViSTNSUVcgv\\nffUJTpnTcwJWsw2J38JxfY72elhhzOV0wr2dAapuEFXF3d0+68WWuN8lkrcTHJ3ukG67Q+i5HJ9e\\n4NoB6bahtByKJmNbNPQPBlxNNgR+CyldkBZ/46//Nr/6a7/B6IOvEvoOQXeIsAq0qDnc7ZBXFuQV\\nVQlJK2A+m9AetIjcNk2lSFVF0ygWy5RBp01v0GFV16znW2422f8Hvfo5XwaotKI2UJiSxmg8T2Db\\nDWXtUFPj2C5lI8C2UMJGSrCFxBLgCHFbUxUSUymEENjSoRUkbLOSJDJQZhjpcLffQpDx7jsPSJuK\\n64Xh1WrOII549PQRjigp6oKmztGWYrJeUwFSCUJhg7L44ZfnXE42jPojbhZLPM8wm+UEfguaGk8G\\nbJVG5mv2d9oc9PrUIkB6DgejABqBUorXLyZcXqxZ3ExAWHiOTaFctqoixCOKAkrlUlPhWBLpOBze\\nu4O0u9i+wCjJZFHS64bUocfufod80fDy+cecnF7wjWddBuOQ0XBA13P5lQ/voyrF1XzO6fUZb9++\\nJKVhnW4plGC+XrAzinn4ziOiIKQ7DiiaGtcNiDoOJz85xzMh6arAcyMsZ8C9ccC0hi8vC15Nb3hz\\nMWO3ZSNrRTvysYTku+8/4qO3b/nLj19RzHIcd4DnVPzG1x8x22RUMuXxQUzcCbm+XPPpmxt+/dvv\\nsddOeLjfZ7udsk1zlouUdK148uAeaVaQrTfMNjV7o13ylWKwE9ONEnZHLdbLa8pJwa99+0M64YC9\\n/R3mlwUlJTu7bYLIR/tt0mKLi0vdwOxyiVY1lg3twEJKsKSkG0V85cNvM3ub0olaaFWwM+4x7iSM\\nx2MKrZCeRdIfkHR9pK/54vkL9vbGpKucm+klYRgyXa6ouD23n7w8ZTgaEXd6IA3S9fA8l927h+zv\\nH1I2GbHn4kqXqij563/7b9OYgpYfMh6P8ZyAXBmEZdM4LlmWUjSKfj9i1I1pmppnTx7h+z7PHtzh\\n4N4zTNUQxDGO32I5mVDWGS9fv2C5TZmszlmVDXErocHhydEBkeWTJAlBEGFLh1/4he9gDXdYpCtW\\nZcX9Zw84m8w5GB9ys1gh8w3dVou3V1eUWQ6N5HK6Igod1puMRQE3WcV6fcNmueLtfEqtNFpUtMMW\\n949GIG02dcNP3iwY7o0JYpekHROHCa9P3vJf/5f/FZ++OOPsouKHz8959uTrFF5Mv3cL7FmrjN6g\\ny/EsxdKST7+ccH654O1yQ2N8msjh3t19JpuCq0XBYp3z5Tbji+niZ9arn3un1H/xn/9nv/90vIMw\\nYAlDpRRITd04GLvG5BrL8rBdTV4ZVnnNKs9BNFi2h4PA2AJUQ60UtQLbhvtdhzqrmeaCvC4oak03\\nseiGbbZ5SiuwCYwi9i3aoceXb2e0XIu8MTzcHdLqJzx/OUHEHrg2tm1jO4L1/IZOe4ftbIrtGOqi\\nJkgSsiolDjwSzwJser2EpiwoakM38bh35wBQbFLJcnZJ2A6Iw5C61qRVRTcMmK9T2q0evq1pD/u8\\nOFshfQdsG98OeffhHourFXU2oWkMccvHdRyUKdlMNyRdH0dGjPf6rNYrtI5wpE3VGL548RqFTWA7\\n2A4cHT1mXW3x3Db3j+7QsKbKK0wtMXZOGIbMVhXatvjFrz/m68M9zm4uEDpn3O9ztZkR2hZG1bhe\\niG87XExT9gcx603Bu4/v8ZdfXPJ0p03gS54ejblaTFgXJd98cMDrkysO79ylFYScX825nG8wUlLm\\nG8aDDieXC3bvxPjJHp2kxenJSybzBSYviRKLzTql34vINjlN0yCkw9lkzaCXMO6POL1e4CU2Z1eX\\nuL7F0e6QXmtEqQrKXGMJn8v5DZmCVuRguza9VoJrS+6Oh9wsS/x2wnKx5Je+8T6zq2NW6YYffT5h\\nlaYs1hmLzZqWH+M4AZ+dvCVdb1mtFHmTonFZra/ojfbQjYsxkLQHlKohEJCbClXCslCkqzVGQFqW\\nCNvDkrd2WdcLMXisLz7j5nJBntYYBafTK+6Md1hs1pS1ZtxvsZyscDwLIX2kHSHsksqEDPs9ruYz\\n6rxAej4n5zeMxh3Orrfc3dslrxquVlv294dcnp0iXJ/rdY4XhvithKZu8LyYJ08P+d//8f9G5Hpc\\nLraMum3aocf3f/QxjeWhhOZivkBaNl6ScHxzwyYryfOGdZVRaY0QgqxSzNZL0IKyqVmlJfPthnl+\\n635ywgDPj3A9D99zUY3Adi3evvkS27FZ3FyzwOGbv/RtfvDJx1zOt1yXFTebmrRquMwqcixWSmM8\\nF+OF4AUUtmFZK642a5Z1RdEIMiMIWgGR73N1/bM5pX7uvfxaa7IqRwiHpqxBCzpJj1LPyWobzxNo\\n1C1bVFjU4jbzHsAXAoXBNRLl2Ng2t9i69RrLOEyKLU93BHGQ8OnxksM4pDABf/zlcyzhcbQT4mjB\\nn59s+GDXIumP2UlT5pslvpVw+GDMq1mKbhTKsvEdl24Ey1oT2TmBG1KlNUkUUaUl880W2UuoyoLS\\nE1TSYEuPi+maeFGRJALbddnZGeIlDm+vGzzbpm00sechhj7rxYTBXsx8tmaxnuGWAU4/wmkM2hIk\\nnoKoy7ZMabsR5zc3JEGLXj/icjbF9u+gVjmR06Yqaq7zObZbczAeUaYCY7ssFktawZzY8mm0BFOi\\njU1WN7z6/HPefXrA1bxgWxf4Oiby+qzWS5bza3zbYpUW9EKXF2+ucKKIyeVbfu97XyPbKmp8clac\\nXS6Juz4NNatlTnw04r33vs70hz9mmlY4fsyL00t6nZAw7iByw+nkin7S4v/+6CVGWszXD1kuTxkl\\nNvcO9vGbjLTJyeYFtm3QOmSVz+knMU+fvE/bf8NyO8MLI773vV/k1fEbtLLISsPVbMNmc8Fg2Cbu\\neoQiIssDrmY5xWrD3cNdal1hcDm+nqJsQ1VqfvOv/btsiobVsmKVN4jAZZVm2K5FvqrZHXeYL5bU\\njWGmbbRaMWqH7CQx8/AOpxc3tMKIqs4RZXCb3hm2Kcuawva5Ws5JLRvHDelGAarWuK6H47kkccy3\\nPniKF1jUzhu2WvEf/q2/yT/+n/+ALxbXWLahf3eX+M4Of/8//j3evD7Dkj67u7vYFvzoB3/B6GCf\\n6WTGaGeX6XzDP/lv/xs+eXGNLjOu1w4KizJNSV9eE8YttpuMSrjsDYfYdoTvu/zO3/kt/uUf/xEP\\n3/8GFyfnOG2PLydrik2Ot9snKxSObWMnvdtxJyMZj/dQ1i3iskOF5UjqxlAWBU/feYfVcklZKYTQ\\nOHZMoxWB52NZDlWTEwiXrGlu3ZLS0BmM+eDDr5DnKY5t8/Llc+48OGJ5vSRXCq0EThhQ1zWRF1LU\\nW6TlUhcNji8gF3hhF8cSlHUN0mBXDZ5rUysL+Pxn0qufe0GVQhK6Li4WcyrafsByvsKJJLJWGNsi\\n8H2KdEUiBY4R1LXCCTzyusH1LOqfZnKDhcgybFvyxXTO+3cSpAgwdcGw3+HTyYzdIGYcuGyNZJaV\\nDOKIX/3gAfnshH/xg0/othJsmdERAZFV4rshRaOwjUDbmk0B7z0+oj5ZMV1usXyb5ewax/Fxwy66\\nMdzp7/Enr8/B5Dw41KSbOTuHd6iaiqvVjI7rsC4N8+WEZ/tjZuWcTtzBqUti2ebs7YyH736APJlg\\n9yKUqiHRWLLNtizxfZ9W5JMMQ+RMMz5IOD+dM+j2UKbh+ZsrDvZuk1a7gU/stVlu5gRuSGsQU8cO\\nkxyGbUNpFK8vz5hNFty7u0ev9wRLWyRWgW5uu6off/oTPjjYJXR9Xk9u2Pc9rNJwMByBVWNrzWK9\\npTeImFUVR+N7zJeXPOq2uZil9Dot/vTLCTvxhv1ewrQuiZTE8z0+fzvjnYM2L8/P2UiDziq8oIXl\\nGjr9MbW7RAuXUlRsa7i6KTnaSVikKdt6S3c4QqmSNyef8fpihW4KGu1w9tkbdndiJtMFnuMTeDZJ\\nssvx+TWtfsJ6dck6r7EtsOJd5tolpGKyyHnw5BnXn30Ogc3x9W2X+I9OTohCj9HhEV+7N2bncIf/\\n4K/8JtPFnCjsoIyFMIY4SsiyAq01qtZoKbCFjTSgbIWpBWVR4McRQgukY9M0Fa5lsykyLDtEKI1j\\nN6hGYHwHUcHXf+m3KJWmytf8jX/wn+DHCS3fx1g2VZ0hjM+9u/sYwDIgJXz9O79FU2fs7wPYRIOc\\ni3yFFC5W5INt4XkBfpjQ1JrcKAY7hyjT4NshxhY0Er549YbRTgcjGooqZ5VmUNq4XZ8k6mAVOQpF\\nvx1hsPB9j2yb4rqgK41ltahokL6P48ypMgfw8SOXwLJQCOqywbJthFZYMiTXtyN6R/ffIYgkrW4X\\nnZXMrlOCls8795/hxT7We5rNOscNIN1WrJcbev0WN7MpScvnwdG7HJ++pBXE2FbIVq3Z7Q5Jyy1a\\nQX844Ozqkn/1x//yZ9KrfwsEFYTj0eQFLdfHljbKy3Bzh9EwZLPJWecrHCMQlqTWCinlrfXUFUgE\\nwkDTNEhLolSNUBrteri65uU8ZRBL0rRk0O5S24JJqdgqzXvdFkI1/OT5C2xLU9QVUeAhIo8okJQk\\n1Ks1QlgUTYbJPYQDB3e7zBcxnx5f4KIxdshBD8JaksQW/+jHnzCKb8n+k9dTdsZ9Xp9c8bXH98nT\\nNb3EZ5WnxLbH24sFju9ydrHCcizmRUMtLP7y84/JMQTSpdft8O0PH+A6EY4/JLEW5HXFbFbSHvQ4\\nPp1SFFuM3cE2ObvjDn7ocDAIcFtdVK5x7ACkwkgHEXa5M6wwmaYswMFnEO+iVIAUmkm6oM4V8yJl\\nrz3E9mP+7NWcrzw6xGrg+TRloyvev3/I/OyCnb1dfvx6ybAXgBvxyfPnSN9jbzzk+GzKri84rWre\\nffSIky8vmSpYZRPG/RY3C0VbNTQ9n3pRkrc8lO8QeB0+f33C3p1dnFZEKS2+93f+FmEcEUgf5VRg\\nHLSCnZ0d6rJhtZ4RxR3ybU2D4M///C94/9c7XJxO6fUk3/3uB+wf3eX4bImNwXFshHQAiRLyNolB\\nSmpd83vGoI2FjUAJ+Hu/+x/RSINWJbaUlEqzKQRReBfXFjSVAgR5ITHCw3MdKuv25S9khaolAhsc\\nQez4aKGwBAgNjbDJtcbyAhwshKXRIsSyFKYRaKlQRY1nAV5A4Md4vk1jJLoBo63bn8k2uNgoIWi0\\nRgqNb3koXaOkoNPqELdHeH4IWoBwcNxbhkShClzAtl1c18UYhXB83nt6j3SxwTQbRt0ODx++Q+RF\\nXF6d8oMffExdpQSujYPNaNihv9dFCpfx7gDHWGRVyd27TyjLkrenx1hCEPsBy2zC889eEfsRD9+7\\nT5EbZrNr9vq7FI1inS8IhUuZbqhKhy+ev6W/P6YdDzGmod8GKwrwhKAdtrDdmqojaD9+zGw25Wh3\\nH+FI0u2WcXcXZZXEvoWe2rcs2XZIsUlRRY0wzs+sVz/3ggqCOk3xXY+s1ihR0xEetVuTZRUNBQft\\nLhebnCzNMOY2n0gIg6UMUggc18bSDVLYlAp0Y7Dymk0VYXuQpoJNI3nmSa4WKZ4vqXNo+Yrvv075\\n8KjPX55OeXS4y6pKOXBCPDfhJ2/ektWG8XhMWtRIW7BMK77+rXf4T//Xf8a8qmhbgrpJmawNSRjx\\ng5cXjFzNgaWwooTpZsur0xse7e9RGYGySr68zOhEHu1ORFpB1/U4WUxxdcJW1Dh+wMXpHE3NXmRx\\nf7eLpX1m6yXtJOTtxSt0FbF76PPmixWDnseo55BtDVtd4scRm7SiKB20tWD//l3Cpsf+3h0eP3lE\\npz1ESkMcxkRJC9UYZKORnkNVawLPp6yaW8yakdj2bQqCEOL2IpMCtPkpkOaWq2C0AKMwgtt6uO3S\\nGI1E09T8dLxNodUtT0piIaSDaspbKzEWta4R0rvN5BL8a2aDMTWOsNHSoI2L41jUTYGDjRQOuq5B\\nWoTtNrWqsELB65enrFTEyYUhkn3+3m//OnVZkecRrRCUrkEbjLGp9W1gIUJhjMGyHLTWSAXSsRGi\\noakb6kph2w7adpC6wkiBNpqiVtjSQQiNEQapFKW5hZCjSsDGsm6bqWhNhYU0mhoBGBzbujUSGIMx\\nGmks8rIkcGxsUVFrC+lYCGER2OJ2j7Wiqiscx0FaDsoYaDSNJWka9dP9lhSmwkgLVytMU/AP/v7f\\n5cc//jFO4PPd7/wKf/HDHzLqj9huptjC4fCdd3jx8ktW0zlekPDLv/wd/uz7P+D45Sm94QGmNjR6\\nyQdP3uW9J085fvua+/ce8fLlS25ubvBkyGo2Qe7c1vGrsuGjn/wx7z54l17Spm4y0vKGftzhGx/+\\nAvPJOdl2TWh3OH19RrFY44QhOq/4f7h70xjLzvPO7/du55y71V7dVdX7TnY3KbJJitRCrZZsKrY1\\nHng8nskHZ+JZEgRIAgwMJMEkAfIhQIJBMMgkAeJkAnicmTheJGsGsscWJUqiuG8ie2Gz96X2vepu\\nZ3nf98mHc2UYCBBz4gQQpr7cW7fv0vfeOv/zLP9l6tghOu2U5558lpe+8c/p9DfYXlvhiU98gutv\\nv8N4O2Grv81wPRDDgNW9AeMTCec++wJZs8nDtRU+evkHLEyM0y8y2guzvPP+m1ycP8T6zhbjjSnu\\nzR3g2rUrHx+tftojUKZaDfmZc0dwZAx8Ca7OkmqpWk5qEkNeeVKgUor3Nrps9gZYp3FKcEYhxpKm\\nKWVe4qtIpYSOc3z2RIfVMiHvVaz3uhzqtFnr9wghoCTy6ccv8tZHtyh9pFfmjCWGLB2nlYDNmmzu\\n7pCriunJGbxYjE0A+Mf/3X/Gf/z3/gH4LpPOst0dUkrAZpbNKmFSFSB1eF5ZVTSNoZ0krIWEA1MJ\\nK1tD0sSSEKmMpRx6siRFWSFptsGklDHn+acucPPBBlplNF1JXkYe3F/CmoAqBRJLLBWlH5BZw8zs\\nFOu7u7ScAfFs7d/im396neBrsAtBQAvR15c+xrrtNIyu12qzGCPaGCT6moMbAsJPQFEg1EuTqEDj\\niVgkFJhRtRdVRCmDUkLlBa2g8h4jikBEKUGiRSTU4Cxx1KoqKomjDiSiiBjjkAhBaYzPwToEhTGG\\nKBVSRlSWoqU2/ChQDAcFW/slv/Wtt3j75Rc5dOYizx+J/K1//9eJoSAIpNqitIyeVxEkoqxBByFQ\\n1gGEQBRV/39GJxQfIhbwKtTkfxKM0egINjWEIKgoBAFULRCICMR6ueIrwShdv54yKBUJ3qNNgpFI\\nwOPROK2RIIgCEyLeREzUVCHWn5nTqGioqtqkxGiPwoAxSFQYAkoFotTFRr8qMK02/+A/+vc4MDvJ\\nM089yY9efIO//ff+HbQTxlodltfWufrBZfa3d7lw6TEOHpyn8h6KnM28j680m/eXMM3A85/5Krdu\\nXoYoTM/PcefmfRZv3ObZTz3FAMuPXn6Rpz75OebSlG//03+Cbc1w485tQqy4ePEpJtsNWqePkrU7\\nvP4H/zsn52c58czn8VmTrds/5s6Hdzj+3KfY++BNjh+Y4eaDRVTZZHxas7Gzi6ky2i3NRrdPKzOk\\nSpFmHZZWF8EZUtemDLBwoEMnaXL/4X18I+Pa4h6Jy+rMt7xAN5qs727y0b31fzMiUJSAcw6rHEZ5\\noKSdtvEiCJZSCpwFLxYx9VxKA4RIayKhOTaOxIRh7rFuiLWW6XaDECP3fJuVnR1UqGhONljsFVQm\\n/bOk0Bev36qB21oaMaPnA6UegMkIvX3SpoOY4oNCzE9OTJa//xv/Napt8X6cNVHE8aSOS8bTsAGS\\nJqBBR9rREU3B2p7Hppq9/QHtTgttAlo1iZS00hbNVka/KlBJRq+3z1irzXp/yDDvcfRwm+u3H/Dp\\nS+d54qnz9Ls9fnz5JkcWprlxexGbJ0QTWdzdA5OA0UjpeeHrv8RwOMToBhI9xhiCUmgLVYgYAVGG\\nSEApAyYSAjAC0SiGGCuMMRglrGxscvDgQcKoIjVRCLqmvlmT1ERwNCGAVeBVDY7eB5SYerAnFVo0\\nCqEUQQFRarCOWkEQjK6/eySiBUqrcBGCTRAREmcIUVAk6CSAQJD689ZiaWcapx1bgxLp3aPgsZ/T\\nWAAAIABJREFUEVY3SywKLw5DoCgqkoZFV4wep4hVWbfLEhEcTgleKYiAUgQfsQoCDisQjcUixChE\\nFKGMBAVGK3ygptEZzTAvcVkKlWBEUAhGK7T2QG0vF7Xgg2C0RccKoiZKhVFJnZMVqH0ZopAaW7/n\\nEEisYlh4rDaI1PzrIAGkRJImiVKUhZBYx+7+kN29fcqyYuX+i5AG/ul/+RtonXD0qc+yFQKrS7fR\\nm1sca40xNT3Ld77zXbbvPuDMmWPcfuP7fObTn2NvcZ1//v0XeeSRs6ys7aJDQaYc5+cmePnb3+aN\\nqx/y/Be/yu/+r/8znzs9T8/36a/t0p5pMtmawSZddtaWWb3zAUurDzh9+iy+1+Xqn3yD2w82+Nov\\nfomus4w1xunvD/lw8w6T89P4AOtruyTtFmnDUZQladOixKN0hs08CwsLRLHcW1smyzVyYJa1/gb7\\nKC7fXmZ6cgapCoaDgobRDPtdjs8s8NG99Y+FVz/1gBoVEFOMhdQYJGQ4KdHG0hv0cGj6VWCs3aSX\\nFyMwjFhXU5x0EUlTod1ySDqLpo4tti6wPRiSNROMayNBaE0aJBqShiYOA6VUaGVpJYY8H9LQBofH\\nmQxxdYXVVBnDYR9NA2Xq6i1Lhlz4xOd57bUfE6pe7c0pJZYWttojYslMgyLm9KMhSZs0Jh1lGGJJ\\nGQbBRkfqNIo2KEWeg3EZIQTG2pN88skjHD50it7RgwwrS4rBqknKITR0xulThxj24clPnGZqcob3\\nrtxgZnqSj64vUfkhppny3HPPIQGEEm0cw6rEKo02oIOhwmMIECNGHKIj0YAWUIxs74xGEEqB2dlp\\ngtdIqCO3EQVe0FqQpK7SghYc9Ryy9BqjAkSFjwELGGtqQ+GiHIkz6vFNXQVWJFohaBJtKCNEq0mq\\nQNQGo8KoYhZQQivRDKqINbE2Ii+FygiJEVzm+Pv/9nP8w3wfizA1PuQb3/4eX/3SczTTjDTVGDSV\\nVUj0KEBpS4wROwIxrxVSAUqjfUWk/v6t8ZRR0HVSMhoQHSgxIBonCquFKOCVIU0zEIg2QKgFKZZI\\nXgSaaYKYCoWru4cgGFWb2kRtIQRyiWQqQVmNRlASqcoCrSwxgIQKsXWWWhUDLnGEwhOHFZWqN/A+\\naHq7K1hlCb4edQw3VlFFwcG5A/zo1Zdpm8iEDpw9cQS1d4//8b95mScPjRFUTtpb5NiJw2w9WOad\\nq1d4/ukz7K5usfhwmQtnZhlvGvaqgr2dXT77xEVuvfoyv/Hv/jLf+N1vUngY9AvwmvvLaxzuTDGd\\nKpqdlGOnT0BwDIaexbV1lnolD698yAfXb7O5v87huSPEfp98s8fE1DhvLq1x6exJ1ve2OTIxxcrq\\nHkcnU/r5kDJGnBPW1/fx1YCBduzvrLHW7VGoBifn5ljf6TPTabE/GHDsYJPbD3YIw4/v2P9TD6ga\\nKKWgLEZUKFfLQ5VSGOsIEmlbh5ce/SJQ6joORULJuHWUlWBTS+pSKiqUUagYQRlMjIQguKRNljVR\\nSvBlDlFx/twsH62sI6XGuIjRDtd0VIOAKA/RoZSmjA7bFCQAEYzRnD1/EStDFg4f4uihFq+/+xEi\\nCWVVIsZhlMOLkNomSsW6AnSCUw2wEVvWFdnkgYNs76wjXmMFGuMNnnnsFM32BFII21t7aJcw1skw\\n5hA2eNxkm/v3bnP04AJ73YKxyYSV5U2efPQcoDl98hCxAC0VZy88DVKna1ZVQEVFVNStPwGjVF2Z\\nCoiKIDWIhhCwNiHEAqMsPpRo0VQC1lSIrulqWgRj68pUqhKvDZQwCEKMARGP1vV3KCJUeFRQqCjY\\nxIENJF7wut5MV8ailEbHQBXqVIPgQStH7fpgMCIEHQjR0vMeJ5G872m0FSqxJJUQVcBqzZHpSf7x\\nb/wCv/fNP6SRHcYZyw9fu8rF0wdZOLIwqjwDoLEGCgmjNh0QRYxgQkBR4UcamaBGtpIhR1QLFQpc\\nmiJFBEo0CV57tGgkgtIlSlmUKslIyBoZMXqq/SGmV6JCj8HiKvvrqzy4cYuqNyRu7mB9gd/LSTQ0\\nNCSNDmqwT3RCMLW1YIgDtNQG6iH08d4SOjNc+G//c8S5emMea+GMi4H79+8jKiJiKPJ9kqzDr37h\\nGZqdCa7fus/1+7dQPcXeXp+N/ZJfePox9jbucG1th969RdpTY8zPtvj0kxcpywJlLcemG+S9ih+9\\n+4CL506gp8f5xKVTNHZWeeMHf8rGsCJrjbE72OWRI1NUvX06dsBmd8jZ8cMk0dJ1BYOdXWg5Lo4f\\n5Pgjj/DhnUUG/Yr1lUXW94Ycmp7krctXaTUcw8EOF88/wr2bD5kbm2Vvew3Bk6iEibEJyslAw48R\\ny4LSZewXG5ioSKYyJjPD7mADiyHvVxycaTHb/vjS05/6GepksyFfOn+W1ApF3zM9llGVGp9EfBgy\\nZpvs9PZJlKM0kdce7rHT72KcZqzVZHxiCqUMaZqSK8hEE1SkYzKCEYwx/OxXL/FgcZ0zRw/jQ0qS\\nKnw54NaDVcpBnwPjBxAjvH75Dj7v44wlKkvSsIRhgcsadMuSEwvTnFgYp90cx1EzDS5fv8PhU1Pk\\n/UjseYIN3FtZYxArir6QWUc+3MfobHR607zw/Cm+9+YioSrR2lIFz4GphCcff4TJiWmSbJy9/U12\\nt4eoRNNJwLkWm2trDIY5j5xe4N0P7zDWmqTKe0yM1dSup5+5xL07K/gqZ5gHvvCzz3Jg4fF6tikB\\nrxxuBEZRCgwOlMdHUErqmbVxSNQYAlEURVGiVayrJ3HgDE5J7fzlNcrWbWblhdJ7IhEiVAKagFZ1\\nK+uUQcRjtEZnFl+AURBCHC0ZVQ3ASqFNPYvMkpRBv49rKKocnAKjQZyjGuRorRnrtNjf38dpg04d\\nTe2gqMgHfbbXtlBWIaT8/j/7XbpbXSbShLC9xdmpcWyESWdxAspH8l4XnYETQfwewduaQRKGRO+x\\nYihjJNWRkJdg6zjqKBWqMrXBSKLxZQkGlNcY1aYImzSMIyiHjhU+jlgqzQYu9+R6iPYJ6IKg6sLC\\nAuUQtFUMqshEu0UvDlCFQVrgnEEF6vk49QkJlVLFIarVZOJv/jqzX/kCwQupCKURfue3f5NXfvQ6\\nzayFj4GzBw6wdfsyn3/6MV55930mGg1if0AlGjs2zszYGDfvXMc1Jnj6zGm2Nx7QOTDBa5cX+dlP\\nHufuYo/Th6dZWltjaXWbkwtzLPf6VP0Bzz5+jnc+WuPa0jKnj89y484GuVVcmJul11tndmKaEDyX\\nLn6C929eZ+lhj/HZlEfnZ9jb3sGS0ppO0ZVgJsbYW9tjZ78kk4rjZxf44NptGqYBUpK4FsOqFqOs\\nb27jxhzTUxN0BwVLq12Ma5ClhvHxBg0ci4MBmysbzE1YYkzoDUq+e+v+vyEzVFV7Ttq0Be09hmUF\\nQXDeEsWS5zlWK3rKkcYKkVC39MrQG+Z0JoQsrbfQFoHEoKvAIAwxqaGVCVWvYnq8DWWkzHfo7yZ8\\n77Uf80tfeYydVQj9Pu/efsCwt82jJ6YYbFuee/o4iYkMhx222KNhM2KA3tI2ndkePrZ4/aN7zGcF\\n+/crgnY0VMH9+0MerC0yMT6NSTT4HE3EURA9XDxxnsnxGY7NCxsri1D1WTh6mEbHc/PGFitrlzm8\\ncITtrRs8c+lTfO+7b3L0cIMnnnyWBxu7/OL5Y6ysPmSqN6C3vYkjsHoj0llQ/N5v3+P9u+t87Wcu\\n0pqZZ2Z2jkG/JGvUoIUIpa4XFlY03ldc/R9+i8bmKuzvoXykjDlVdDgp0NFSlgOihgQogqdp2vXm\\nWkfCeAMdDabwlGUJBHzsQ6EQIBrBeMuAHg3tsD6lzLqY0qK1oSoDztZcTQBdNRA1JEbBKUWIFqEC\\nlSK2hyLFJBkh3yVNU9AJZd7DkUHDMkjHybZXkHYTFXw90rAZWZnztHXc3N6n7RynosdsaASPSjQh\\nGgqp6kjk4KlKAR2JpMRYoqMQ8EQ7MokZDkhUho85SrmacmQcmXL4MiISCbrCisWbEhccRYzopKIo\\ncxo2JQroYkBVFQiCUpHSB1xi8WVFZTQmScFAM9FUhafhDEVDSCUlDhXReIxOME1LORjiZZ+00UF5\\nYfcP/wmTj5zFLhxErMGEisXlNXK/h60iSMq9lXucHEv49g9e5uuf+zwfrdzlysOH/O2/8dd5872r\\nHD80QdI8zdvXttjevEt/d4t9XzCnPcGMM+72kWFO241x/EgHXebYxNLbGPLhzVt0eyW//LXPQRSu\\nPFzny+dPs98d4m2Lk0fmefPdj1haWmQ2a+FONvjwg+ucGnOcOHmS7//wfc6PHWZpf8itD5e5MDfF\\n5GTC/maXWx/dp9FogAgGR5Ia4vgYy3cWmZqfZro5xr2tfRaXl5k7UBdLZT5kcWmXYBRJ0qBb9jnZ\\nPkgv9+iYfGy8+qkH1BgjVSjp7hRkDUN0jv1ByZxr0JAePrHEPNKwERmdvZ3RSPC02226O1329Q6O\\nBNeA8alxxCtU2mDQ2+P0RMJkiGyXnjfefJc7Gw/QGHw+5J/9zi2yvMfB2ROo/Xt8+tHzNFtjLOYl\\nr7/0KifPH+LBcsGlRw4y0Yi8c2uXqZnIax/0uDDveGY+oSJjp7dLERRXVrpMTLbolyV/7bljmH7F\\nnc0BK8vb3N/ucfHkLNdu3qII22yubPOpZ8+w9HCbqj/kyvWHfPnMLI8eatFoDSgbc8wOlvnUiUmm\\nj02yt7hKsXaHV8ttinyPRLdQuiJIi7k5x4CCaVXx3LljHJic5Y9f+oAXfv4rNO0MeFCJqmeSEVIp\\neOsffYuxy9+jpXcYDAZIX0hsShAF1S6FAaVTrDiCjiBC6isqXWCYQKgwQ0dRacRVKB/xZR9JUlRe\\nYGwTJ5FIhRNL1AUx9tAlGJ0TRWjoNlXMkcLjlKM0fZQYrFKoCmxaEKVBGCvRXTC+T4wBGwLRK1Sa\\nYypQDSEO9mjs7SAiuGiIXtF4/nn8D15GomO81+WSqxAvVJXHNDShEIwonNPQF6JU9ZJJg7OWQvL6\\nMo+o1KGHgVxKjG5RSY5CURho2gaq8vgQqVwJZcTZJkJAC4jyJNpRVIFE1aCttaUMHqMM1mpC1BiX\\nosQTcXVFGx0x5lgaeBOpJIBocvpYlyLRE6wjFjVNSqsEqzU+ekxfc/O/+i+48Jv/C0oMUYTdvT7j\\nzWmSxEBl2V5b46oRDmQp29tbzCcZ9ycmee0HP+Bab8j4bIvbH63xK19+ipYM+Vev9mjmKY8/d5o7\\nVz7AZB0WDjfqsUlvSHr2FG/9/p/wwnNPMjbTZPGV1/neS29w4vQcF48eYmNzm4nJNtItWL13m1aj\\nydJgwKmxDp+YPkixsc764iZGdWi2m3y0uMHG7oCnTx5hb3uJd5dLjkxY5qcPsj0osCQMVJ8CzTgT\\nzB44QrffJ4aCIpQcnpvHuprn6/OC9mSL/p5nopnST6H0ComGn/vy5/nOlZsfC69+6gFVROgkLcow\\npNnoUAXPbNNSVj0qG8gqRzSBql8yPp79WXuYJgblI2LBRHAJqKApezlZu4NWQttE5g59kh9euUNv\\nZ5tTxw9yZ9Vz6GCba7d6aOUJWZt8uM/jT17klavrXDwwZK6T0Xr0OPeX9iFGPry5yOkj8zTjMsur\\n4zxcXuIzR47y2t1t1vcHxKrki0+f5t1bKzWjIGqmbcq799Z49e4DJscaPH3mCH1vuXjGoaNnZavL\\n975/jV5ZYJ3i86fHGZ+ZomEz9qqSRqPB4naPoix5cG+HvLfDp568wI2r93BJB6ywuxl49FSD3bxi\\nb3MPbccYszl/+Mc/xDYzLBZvIj4KxlPb0Sl47x/+Ho33v03Ic4pYIJTkKqIqg7IVQYOqNKJzTGqJ\\nRUB0beitY4cotfmFLwNKDZBhIJqExDbBK8R6nFX0jUIGCVqVWAxhZA5eGYXDUUqJDfWsNqYpRkaV\\nqgavSyhAadBDi8Kg2h10lRPEkDoo9itUYjFJQswLXJoQiiHF7gCbNvHf/T7WNonsYazGqCa0IIaE\\nUEZMlmI0eO1RaYqxhrISjIZCAcERjcI2Q72EM5bKBgSLsVm9VNMlJYaeV+SUZBML7BSB7NAsqmkQ\\nM8bY8Tma4/MoJ+h2h057AjfWAtHMzixgUkOvm5Nmhv4w4BSkWUaR98iLQKPRIPeBxBmcUQzLiEaT\\nWEXQUBU17UpVJbf+1t/EmJQqCs4PieJBgWhhZ2uTsdSQuhYbgx1Uq8VXTs+xt73D7k6fgwstZlXG\\nVhl59MBB9m8/YOHwQf7V6+/xtU+f48DELMcOH2ZiYY4fvX6N4f6QCxc6vPP6S7QmZjgiJYm1zM20\\nePWVN/ny11/gvVd+zHMXzvG//f53OTw3Q1JW+PEZXr69woSLPD5lOXniMOsrG4zPjhO9ZrW7SVVV\\nSOJ45NA0B6dTvJ/l+SMdQggQS8Yy2Nntc/zR42xubmJUrerLiwKVOh6u7HHx3Dz9/T5rmztMTzQo\\nC4VRUPYGTLYncEYY+sgf/Mt/+bHx6qceUK3RBAps6vBhiAmGXDxVqEhVSi6RVtZhqxwSo8PHOpdJ\\nKU3QESugtQal0EahXRPlFZ6Cv/4rn+KPX9ygk+Z0VMWPP7jLmE2gylBG4ZSiqgouPHqE2zdXaQ8H\\n3Nhocqjocf/aQ86dmuFwJ+HVq1vsbW/R9WM4u81nzk7y7o1l7u9VPHpihnK/weUPb3C8mfGwu8+B\\nMcvrb7zNydNzXNjPCCKcngi8+M5NfvbU0/z2y7dpJYbecJ/xDE5Mt7l44iw73QHBpLzzwUNSu4si\\nIS8C84cOszQIXL9+A4mKZpqytVdvybf2ard/iYq8GNCvarpTiBGJmhg9VmW1IqcqcDiSd7+N8jlF\\nMQQ9hHQcV5X1MiXWFB7XcigSxCqUTWtprzaoMUfsg2/5WukjDk0TsQHvS4IoxLRQNiPLoN/LIWtS\\nOUu326cxPUHfD2m1OjUns52is4yVfkHayMiNoJzDpS16UjA+Nk3uPM3GOK3xCdK0QTLZIhQFrXYb\\nbWF25jDGGIahIrMpJmmQGI2XSBCDL3PSNK2rOGpiPESCjJZyQCyreh46sujTKMKoWnQ6UBIxxmCt\\nRUpPFQWldT371YpY1aDrlFCJqSWnUhFFkWhFoF7GRa1xzlGWBRahioGqsrTb43QHPRrNJjFGPBrt\\nxmgkASSQOYPBITqQOoWSOipIUDhXJ1gEYxlMTNLp9lCZRsd6mB1NhTOGjZVVmkcP0x/2sGLoDwou\\n37jD2cMTnDp+jPXFZRYOdGjMz3NgMuOD6ze5evsun7t4krs3b3L40CH+6I13qd4xOC/8nb/6ad58\\n/RoXnzzPj969xtiEZrLpePXNq8zOzvKtb3yHJ08d5cHNu3zx0jP80ZuvE+cmubW9y7kDk1T9Ptt7\\nOetr+9xc26St4Vs/vs1fefpRtnpbpKP468sf7tMZH+PevXs45xgf75AHxW5Z0f/wNmcOHWJtN0d5\\nQ7fy9Ht9nnrsLDEM+MRnPs/7r7+CqQpQFZUJNFuTzEy2+fDGfXrec3J2mlfvrH48vPrLgJ1S6h7Q\\nBQLgReRppdQU8H8Cx4F7wK+IyM7o/v8p8Ouj+/+HIvInf9FraF2Tcjf7gYONBOUSpPRknRSfR1Kt\\n6ZW7ZC5lGAMyIqinWtdnX91AW4UPFSdOHuTO/QF0HE3X5OTpTzD38h9RDHPW8i5V9BTSYMEJ2kTE\\npDhfMjc1w5tX7vH5J0/zB+/e5ecvHmXfFywv7zJ37hDt1ND3JWliWN7r8tTpIyyu9VloJZhuj1vr\\nPX7u/GHWd7qcPXEIr6c50Ii8/sEmPlRcPL3A3MwCL3xyjN976RWMbvPYoTHmZg9hG4Ex10EbxevX\\nHnJvowt2yMXZDrv9OtDu9p1FDszM0MuH7A4GTE9MkqQtdoa3ifuWyc4kohJc26NjC++3aCiLUQpR\\nCsFTRoWxhlf+0f9Bq7dGog0oRfHrf40zX/kVEuVwSuOVJoYKpWuHrSzLKIoKpQOiFFpc3VaGkkrV\\nFCmtLUYZSjUi2huL1HT2+kcqdEwRE2sypdZECSglhCAY61AEEIPgiSbFysjnVkMUjVCC1CdSK1W9\\n9DIOYyzeV1ijaErEakUQ8BhQkQSFTVLEOiy1oijEHKczIn40WzZYpwkK4oiBIJS46ChiiRdLqhOq\\nEAiAUhZjBaMMQQIJUFlTg7GqqU5a1QRdpQXR4CshcZrgS6SMfzbPVspgjaKiIElTtNYoUTUTQ1X4\\naDDKIbGsucRphjOeIJoYIvgKLzVNIvr69zzVZEVNijMuqYUAQGEsflhAqvBRSJRhECNVH/aKARsb\\nS9zdyunfW+XTjx7kmQvn0eEy25s7DPqew8en+atf+zxLS0uEomR1Z5/V/R36i3dIraa7uc/C9Dyt\\nZs7N+6sMxFLlFT+4tcgzFxKshrXNPT5zdJ6QWVYDzE2lXPnwOs3Ucas3pLSR/jBwf2+HpmnyC588\\nx+rqEh2TsqIgbbbZHZZMT01wSODq0gM6WcbeYMDGEI4uTDLYL1i8v4RraPwHb3D7wTLnDh2kt58z\\nNp6yOeyyu7vL5FSTJFaML8x8bEz8/8IP9Ysi8sSf24D9J8B3ReQM8N3R7yilzgO/ClwAfg74n9RP\\nTv//Dz8KqIi0GpZclRTlgEwJqtCIhe1BiQ4O3x0SywpRBrE1V1JrDVqhxGNFAZak2cJahxjNn3zz\\nJXZ39+kNB0y2m8xOjFMWPTa7e8hP3OG1Znn7IZceP8B797c4Ne556coOF4/MsdLPefmjXWZnE44c\\nmGIyU7SN4sriHt1KM+jtsbWf44PmxoMt2trw/q1l1tce8MdvfMTzT8zylU9dZGJskt/+zlV+fH+J\\nX3z+Ap99/CwnTxzh6o1lJrNJ+lVJVeQ8f+kkl548ylefOEGj0eTu/pCd3S4Hmor9vS0k73P+4DRa\\nlyRmg+FuwDrF7t6A8ckO42MzdBop2gSiFkQniCh0UBgJBAS3soZzKVEq8ixw8ed+jXY2gUoaSNLE\\nW4VpdXCNFtqm5GVAOVcT93UKRlEpwKVYk6KsIyjwJqAwJEmKsRptBbTDNjKMSVFZUgOsA7Co0XeH\\nKKy1KKPrx2lLKnUlp2wtMza6GsWGj4QDqibuayXE4FFKE6md/n2sRQcigqgR1co4NCOqmNUIKV4F\\nDApULTn1CEZrjBbyYoiNlirWLJGoNVHXUTeiFWiQn/hJAKWEmp0gtSigqiqsDrUsVDl0sCTOEsTX\\nFXIQKh9QSmOsIvqI8pqoFPhIkIAygqD/LENNK0ei62DKSF2hKi1USqNRIJYsS7Be4Xolw3JI9BAk\\n4iUS0aRK0I1arpsldaZav4Rkss2Lr1zBTB3k1MljfP7SSQZlZGVzi639kkEBly6d55vfeY333/uA\\npeVtPri9zOb2PoP9PR59/BKlyfjUZy7x49UVmgvzrBaaYR5RvmLoPW/dvk+OoTKO7918SLcy3N/b\\nYntni6XtDRYOjzM91aTT7PDqvSWMzmjGCutSjh8/xU6xx/jBKcbaho21bZQC03AcnD5AfxiIyuES\\nxY9vLFKVQxqNlMR22NgpSbKU4FJm5iY5PL/AndU1kkbKwEdCdFy+tfGxwfD/D4PprwO/Nbr+W8Bf\\n+XO3/46IFCJyF7gFfPIverKIoDwYlxJ1beyrYwNiJAmWdtOSuTZpu0Gz0UYT0crWUk9RGFPLB6OK\\n3Ly5RLMxjniNCcKXX3gcazz7pXCg3WJnCMbCyk6JtgqsJQbN6/d7HFs4RksUG/2U+/0Bb95foawM\\nPuQsL22ytrnH5ZUtzs+nDId9hipy6cwpqolJooEPV5aZbLY43GnT1obnT8+wtlXx/Tdvcv32ChLW\\nePxEmzI5yNb2LmWwPPfEIXIxtDIH2TjXPrxF7+EmwRtWNirKPGdqqs3W3i5zWcLmXp+1jS0q59hZ\\n7rKdb3Po4AKTEy1MWhF0ZLw9jkITYlUfeGEkfUQTBQZzTULDUWqHbqY4q2tOqYlEbWioBC31bE4U\\nNdFfKwYeNnZ6GKNQVqN8TaeKhJrKVHoUEdGAKIQM42JtfRiltsbTNQ82yrBWsRtNkjq81BxYlCLk\\ntWigjsaJFCEQo8aaBFSF0YLWlgJBPFirsUYTgyaKYPXoHK7r92wI+FiBFwIBgsfq2oRclEWCYKIG\\nDIzSIJLMUSnBGkclNVm/9B5G0ebOJRAixkKS1BvioBQheLTU+V9WHEXpRxU3FEWBwtXArFRtzBIj\\niAFdq8TEB7yN9TgLUKKovEJ0RCFEArGqCBIZ+EisakFGUKAkUBUlDQRUJA1C2nAYEWwQQlVQFQVY\\nQyX1dzs2PYMA7UI4e3iG/YerXL+9wtUrS0SV8eq7l2m1GgxDxTuXbzLZTlheWsMpjS5K8nzA2PwM\\nq3vrROCdyw84MdHie699xGPHZjkz32Fpv06PPTI7g/cVR2ZnmZ9sc/32Q2ayNvd2Sg7MH6OMDXZ2\\ncw51xsh94PR0Az0oiEWXlY1lTh2Zpyoiu/sVldOs7qyzudsla1naM45GmjIz3uH4zCznTh8lthMW\\nd7ZoZ8J8Z5wJn7Db7bH4cIMsbdGaSJmcmKHKI41/jT7+LwuoAryolHpHKfV3R7cdFJGV0fVV4ODo\\n+iHg4Z977OLotv/bj1Lq7yql3lZKvT0sPTSb+BhwIdKcmqR0UpPItar136oiRCGa2kVeUS+zolGE\\nkaRSMEw02jSzSSoJlE7xcE1x4959+rubrO8V9HsFVdQcnUkZdmv52X4xoOgN+MafvMfd3pAhwhce\\nO4zF4An8zNPnKL3Q7EzRaSkOzSwwlipKX7G41eOY5Hx1/gBnM1jsd5kZSxh6S38YicMtjo41uL28\\nw2OT4zW9aH2Vtc0uDz+6RSg79DdWuf2gy1tvXebgfIfJxPPa+/e5s7nE507PU2YJnWSWOxtdtsuK\\nsakprl+7z14o+OWffY68zJk/NIbJJmk5x9XVHWzSxKg24hWiHCKBKoaaWnZng9D3WC+iVOLeAAAg\\nAElEQVR4W2vKo2jEO3QUKokoAR0E0Qql6krMaWF2IkOiRkUNiQVqByWvhCRpYGyKqLrlz7RCBYUf\\nPY9GYZXBiBllZGlMNBijakMQpaiqiE4NZTFAC9hRNr2RSCwLtG3U+n9+MssSQllHiSulUIwMXYjE\\nUHNLiwjKpCjtEOrcqhhjHSIYfK06UoLEghJdjx28IihFJJJZjTZSq8tEoURRhgqta/1/VXmsdjUz\\nwVrAoMSSJElt3IOtP78kIY4qXtEV+iez1xgIIdReAS5BVXV7HkIADYk16CgIBh9reWlVVRgqqiCo\\nkEOsiGiiobaatE1Km5BHT+4jpdagLYnL8N06DYAo9LsDSmV42O9z7f4GaryDJJo8eo4dmKSsAqt7\\nuyzv7nPm2AmIwtFj89za3uSFv/FL3Li/SmZgd3+X6abj3sZWveipSm6udGk2xhgkCX1luHpvFQUU\\nZQ+dV2TOM6h6LMwe5MxMg5W1Zfb8AGXK+thWTb7w/EV6ZcXOxoCVrS5b3T28VHzi3BF60fBgp0+e\\ne/YHio1uj15VgY3ksaIZhNRHmjYhmoSHwy0yKxw4OE7DaiZ1k1gMadjIZr/82ID4lwXUz4rIE8AL\\nwH+glPrcn/9HqXuef23lgIj8pog8LSJPN0ZOUQ2bkbgW/V7OIPd1JaQ00VpcIyNqw2Qaav251CF9\\nEiLBR/KyRGKkmw/Y2LhJK21gouatH77Jgdk5QpqyUygsfUzlyQuFy1IUFmUSgsBnnpjhkYUpXnj8\\nCD/84BYbOzlGaaKq6JcpToaEmHF76QFf//JXePbQOPNjmomZcVb3h5w/dxQxGXe3K+ZcxJnI7rDg\\nyuY2g7LLaw/WKLual96/y7lD4zx16RxvX7vK7PxRHq4v0TCK69fvkfvAiemMrz37KLcWuzT3u5x7\\n9ChOR47NNhhTFYfHp+nv5Dy8cQdfVhSiaLVTsrEWcVCA8SgDXglRBF8LFrFAFiqU0qATimDwCFEK\\ntIVAgU4TPAozmgXaEX9VaxBTV1de1e21QVDKYKReikmIKNGIritSrRSJiViliAgmqQEnGle3zlYI\\n4nGqbshNolHa1VJUpYhR8DHUgGAjcaSAiwJOWzAQtR5JVUeEeKnHHCqWVFqjTaidmUIBceRUJrGu\\noo0BnSICibEYiRAD4oS0TjvDV5Fa11BX8kELloDT9Xu1QBljTYHCEE39WRXFEKUCVSyIBGysAynF\\nB3xwIxctCAFEKwx11R9GR2yKQgQKEUIIVLE2RdYotLZEbXFG4VxKkJE5TVRYo8iLIdEPCSHgkoiW\\nnIBHWYNN6vC8IDA9e4DCWiR6DkwqvvDJZ/n5Lz3DxFTGd9+/z3OPn+Zrz3+JltG8dfU6Byea5CHw\\n5JljvP2DV/i3vvg5+oXl4UZO3s359GOnuPTII5gwxCrNbNvRksiMNVxYmOHC3AQT01NUqWF2fJJD\\n09M0BHrbO8yMJTz93GfY2hlgrOLMoUnuPlipRRK2wbV7a+yEkrvLQ7qFwkokxVNWQn93yMmFWfZ3\\n9xnmA4bDkt1iwKeeeJRmq4OPFbv9iumxGdZ3hhyfGme3Klhe2yRq8CH/2Nj1lwJUEVkaXa4D36Ru\\n4deUUvMAo8ufuAosAUf+3MMPj277C14D8sEQCUKzoWhmHY7PNYlekatAU1nyvFYU7fVygihi9LXd\\nmlYYq7Fa1zMn42h3EgLD+sDHM9F27Hf79Ps9xDaIWrE86KMoCcpjlWLgS7rDjMGgyw+uPGRmYgyh\\nworn3avrRAVv392iKEsunD/Df//73+a1u1u8cncT6ZXsm8CrH93jQLPNwWYbOzFGr/CoStHQDQ51\\nmvydL1zgezdu8fSRMYpS89J7VxgWJS+//R4LYxNsbuzTStuoEhKj+eGbV4gNuN0reOfWTQ7MzdBM\\nm2x0AxvVLo1OynsrXV7/cIlrN5YYbqzw9qtvMZXV1ZFRGvEBFTwSaz19FSuSQUCbAmUj3kVcdMRq\\n9EWIg0pGY4KAEVVXTmgSk9QGJdFjo1BVBVo7jATUyGwG6q23irUePyiFKIPSAe0DVV6ila8r0lgv\\ntGqGu9QgH1Td6jtdzxu1QgfBotC6ja3/FmsPXVsDqSJgtB5JauskS2Ui1iQYJSPjZ41VFuMsUZWI\\n0T/5+669BgCPopGmNQevCuQS8LEGdqMVYjRWaSzCoIxUZaCSWM9eVayXfOJRoULQVCMGgdM1uyAi\\nKDyWSOb+7PhCK4+RQNS13Z6BkTF13QFoNTJMCQpN3a5HX9WG1WqkSIsVwdTHQxU8OhY0WmMkaCRo\\ntCQkommkjrzo4VKL1Yq8KBjznuNzHZ599AI3b93hW3/6Bo+dPc6RcYNTht/90xcpBFpjhgPzE6ih\\n58qNe8xMj/PS22+zuLPHeKPB+TPTvHvlOu9cuYq1LVwKVxc3ebC9w8mj03y4vMKD/Yo7t+8zNjnG\\nzc1tPlreR0mFbbYoYpMfvPwWpdYMouLgiRMYk7C6VyDK89iZU6zsVpw7M8eHdxaZnp2nlXWYyBpg\\nNLu9glYjYXfgyUzKhGvx8ME6H95+SJo2ePLkERYXl+l1N1na7FMM+mhtGewMOTj28Yn9/68BVSnV\\nUkp1fnId+CpwBfgXwK+N7vZrwLdG1/8F8KtKqVQpdQI4A7z5F74OiqSR1WFhO1365T6LOz2CeNJG\\nhhBQVYWOFbmv9edO122sGm2xIyCiSK2puZYegi+5dmON60sbOJPy9c8eZ6Y1TRUVmTUUoa6soopo\\nldJpN9mrAoOg2djY4IkjMwiWzUGdWRSN4ue/dIr3PnzIs6enmWkl/OJjRymyhH5/hzMLR3n3/jLa\\nl9xf3mQjjzQmOhRllxMLE9xZG/L8iWM8cvokG/tdGBiK3oCpRoKtIq6jyCvPiu9TaEPLttnueULu\\nSZVA0efh8g6bwy6x0iSJ5dTcYR49chRdKXaLnMmsxeUHK4QQa/29SggotDLE6Eck8xwpNb4s6kWS\\nDqSZJsR6aYOqsE4hAbxRxCCokUGHtw6UQlQkMQlVkJq6I4K3EVRVzwSJtelJVLVaqBS0sSPLPYUd\\nLXhijHWqZax5rkr7ui2OFqMsIhBdgrE1d1egXv7ouiVWWuqKNUaCeGxSW9cpZWr+aAQVBa9ruz8j\\nYJTFohEiSkHAo1Ut3ez7ErD1LD/UJiRRhBIBX9XzUNH1ckjV1ToqIlERg0JQZIlDicLouifwUiK+\\ntiX8v7h702DbtrM87xndbFa31+732ac/99xG99xOV9KVhCQaSYDkKjpTwpErTlwBXAQTQrkSjO24\\ncFFx7FCmDI7LGNuxAWPTxxhMoyCBALVXt+/OOffc0+++Xf2cc8zR5Mfc4EpVnJLjxKV4/txVu9aP\\nteY3vvF97/u8SEVUppE4iRMEofi3lC4hmkFGahrjCtGhhcSG+CdgHiEEiVb4eAJXDxGMIkFSV5ZM\\nikYqVgW8A4Q8ActAO2/RznJUgDxvY+sZtVL83ht7/MELt3j57hbf9R3fwtLqRabVjE+9dp+nHjxD\\nmqakdc2nX7zH8vIy3/7hD1ANxlxY6PLMhXW++spZWknO42fWeWswpN/NGU8rbh6NsSHywu0dytpR\\nEgkk3L93xHJ3gWceu8zZc2usPvkkL964C0CCZjwd8ckX3uLyow/TneuxM51yfXuXxfYSwVekWqAo\\n2JmMuDer6PdyTPQMrUC4yNLqCpsHx1y8uMh7n36Yc8sdsiyhip7VlQskwbOyPM/QVly6vMru6Mu/\\n8v+HyKZWgX/VfNlo4F/GGH9HCPEl4JeEEN8J3AW+g+alel0I8UvAGzQQnr8Y/3jg9X/zRCKZl+iu\\nYFo3P6Cigm5miHFKEB2SjmHv6JhEN8VTqog/ERD4uqKT5UDTjUQX8d4iMsW73naadnuFF774Om9s\\nwvZgBxVKdNB0dUIVSkTQBOGxs0O6wjBLJQ+ce5D9WYmTgqqakrUMhYN/+esv82P/3cf4u3/vN9ir\\nx/xWIamC40y3TaulWZECRUWrnSCnY569NaafJLyxcUxdVjx9+TyvX7/HaHaMdxrZanzzlYxcuztk\\n33rWejmb42PmU8Wir6gLgV7qUkwmXLk0x9EElPbc2hXIrODyoqC90OZoFLl7NMaiWO50mMwKvKhI\\nVYKLAiEDXoGWDXEzEZHu2hqIhi9qoyPXyUnHKDGJPFm0NA41EQLOV2j4k4JGrhFFhTIZrmoWfcGX\\nSJE2+L5Qg5QoI4lCQgiNNCs2QGbrPJrQpAi42OhcRSTKhigVZONk8q4hzkMkkU23F2Oj7VQiEGPD\\nBvVSoGWD84vIk2JVEgJ4GjlViB6PbxCCsSYIQ8BRIwlVDUnWgKBjRIiGcUAtTkhQIIVFqJPPDg3K\\nT4qADwElYVI6tNYkITZ+/pgQacYewkfqWDccWSGJzp0AajTW1s1YBUcZDSCQ3hN8gdCaKBNCgOAq\\n6jRHiIDwNUEG0iip8CjT3OaEVHhqlGy0tpwcPtEYnJQNnyJoUpNRyIKJC5zWnne882n+6JOf5trW\\nHg8+/BBfcz7jD1+6SioDvbVT9Kd7pFT8/O9+klzmuD3DY+cWGRcF7WyR9sUO8e42+6MSYQz9bI7R\\neI920kbmkuAqXNajFI6dyZjDmzNUBP/FV/E6aw4ApRAobt3f4D//sx/jZ/7236Kdt5nOZiiluGgW\\nmVsO7B+XrCzMcbC3j+md42BacDwquDjf4c6tNylC4GBS8NKN21w4e5Hn3rxOkiRs37rOo+tneOXu\\nHt1On3sHNenSGtza+LKK4v/jghpjvAU8+X/x90PgQ/+O//mbwN/89/kcKSRDItWwot9NMFqQK4MI\\ngegzQixxVcJcK2UwLZuBNc3LiVAkSdboBHUk77SZzGZ0OylRCi498RAvv/gma2vriLzT2FwFdLOM\\nWZgibTM3UzJjpdfm01eHzGkYDGvWVxMOJn2+7rEHGAx2GDnD52+8xaBOOXd+ldG25l0PLXDt1j5S\\nJ3gdcLUk7xomVhB0C+9GnF/sg6q5s7VDRuTOtGI8ESx3BTFqdgYO25IMheDyQoIoKt7/zkf5lc++\\nQVcJVvIWuIr2XJ+79wcsnu7z6lv7fOSrHqK30GV7dx+ZGFp9R/tY0u7N4UOHYnIfKTIqJzDCI9Hg\\nVEPIDwKEIKg2dWwKVSYFzgWkjITocFYipUGedHFRGORJtEbiFFY6TNXQ4Ot6gtYJ2iu8auIzYu0R\\nupkTRiLClgRpGppVs2JByQbAIlEooXDCImmu78SK6AVOGhSCGAQyBGrZyJuIDlEHnJRo3eiZo2/E\\nU83MV+DijBANiWwE8M5ZtG7C4lT0aCERwVF6MKIp9iE0B0KIjuibIk9oFA0yGkIQRKNRAkIMEJtD\\nQyKbLCgXEdHioqedGWxpkcEhVE4pAqn0uJARaRCEDZim6TprZ9G7Q27/1R8iFFOid3jpiNGjuwsU\\nTz7DA9/7XeAsSnqEyoixBqExsaaWDUAmhgLvFE4lmFiDTnB1jbQ1pBqTt5CtNqKY0V1YZnd3mwee\\neYbf/PxzlHgef+c7eO2NN3nkHd/AuY0j7h8P2B4U9JYWeOOtO/TzPvuDCYWrObZt1rtz3L+zSf/J\\nx/BOY4RjMhuT9CRJq4dNMzJX4k0LNbeMKobUDqKzOFezePEh7m3ukHV6+Lqi325Bq8MvfOIT6M4C\\nM6lorS5weHjI9dITpp7xqKRwBcJ00JOAdQIrAofjY2rbowySt/Zn7FrJ3q27pLqDVJbEZ2wfHzGy\\nHtlKkd5h/j0AUl/xTqkYAr6Ycmppkdo66toDjtqDUJG6tCiZUNWNtTCeDPabLWlEeYUwEaFT2gn4\\nMiFEiYrwuU98kY3jPZazwHi/ZOoqOrrF2dNdxjdLJsEiTEKaSGza5oxyHJmM7fGQP/XupyhG9/jE\\nC1f51vetc/jqFlpqXn3+Neb6Lb5RB+7szHjodIsbd6Y88Mhl9idbHAWoypphMaN0kecPtnnfmT5P\\nXzrFRAV2hwfMdXus9tu8ujfkaHRAXjT5V9F1ePj8HPeu3uVjDy0yTtoU0yPyuTkOdwecPdul0+7x\\nNe/IsLZgeiBJUGR5n+Ptbaqsop+vs3V0m8uPXsFTIKNCCkktwdU1oWoM/VoZqtTjYt1AlpEQmkuZ\\n1hrvmr6t1W1Ru2Y5kxDwtsRkKcoHCIJ2O2c486DBEkmiJ0SL1QrvA+M/fI7xL/8qrjwm9ZFKgUsS\\nlr71m1n68NditGquy9IRUAhqqDV3f/F3qIuC2fAAXwZkWRFTyeXv/wu89ku/RXvzHkpHYgG6naGK\\nEuGgrse0dULtQ3Nlr2qkCogqEKVlc36ep//aD7H51/8n0sMBNpboUDcRLlnOhX/44zSsRsn17/w+\\npD+CqnFJ+WARSvLMz/0zJqpNLSTOlQQlidLz8nf/AHN798mFx4qI9aASQVCC+yurfMPf/0d4DJGa\\nEJsxlZAB5wOewNZ/85ex23cpqkO0Uo3hQGvwYO026ad/izsvP8fln/xxvMiIBJRKqEPZKCZCbG4F\\ngYZLkHZwQWBCBUlC1kqxVUWlHJTTxuKaGFZWl/jfvvAcSmsS1eIz129zZv0in7uxx5tDC0mXg9Ky\\nBqRqjn5vkcnM0Vtf4erIcn1vH+1q3ItXEVSMshaLC2d5/zueYVY5CnvIw1ceYjKukJVDpjlpvwOT\\nKa20zc07b5LkCVUZsCFy/vQ6K6eWeezxR7l0+SJz+SKTyYj9/U229rc4nEUWL5/leH8PKoE1ERtz\\n+ksGrzROGWQx4igGTq2sUsSIiI3mVyEIsmKu9uhWnwtrp5lf7fL8y19eDMpXfEFFQn95kZmvQTR4\\naKkNtS1oS0WadpjZGhFSatnEYkiZEE8cKLWoIQZCKTg4bGgyWd0GqTi0FSE4xq5LFAGTRBKtOdoa\\n8rVX1vnEKxukmeY7vuoCp5fO8vviS3zk6cdZme/y1t1jNsYW62fkooVNDJ1Esbm9w+FwTJIkVD4w\\nPVDcqyp+5bk7DMsp/8XXXuEPnt+glgqjLU8t9BmWEqcC1/Y2ubyyylv7R3xuVmPLGTIxTMuaK8t9\\nsFNc7GETzfVpwWhnwNpKF3FUsNiZxyWKVtswqAKJF1TTMbPaMhsWyETx1U9/gE89d431uWUGR0O0\\nNwTdCPq1b7SdGIE3zUs8v3YKHSVeKT71g/8Llza+iI8zpNCoumbvgT7v/tF/gg4JMViu/tgvIJ//\\nDVSoEDLB1w5t4OI//4cocxqpFbW3RDR+OOHe9/9V7Pg22kWiEUxnnjTNCaFm+FM/yfCXfo7z/+An\\nEFkfiH+SIJDqyORX/wWZcSRVwwFwrkR3+mTJ9+Cvb5Jd+0wjp9KBOtHkVmJFQRYyZrFARw0tA7OS\\nqDKCrMA6ys5DZCFg7t/GTfaJEibWkRhJ1BnQaDSld4RqhJwNEdKhZpIoPN4pknZKqCI6emSaNZ19\\n8HSHO8gwxUqJctCSCa4cI6Jhve9BCOrKIiR4FFoKQpQYKq7+xf8euXEN4SBLMmJVo3SkcjOUBalU\\nY3892Oba9/8lHv77P4mLDisDplYE1RxI2qUQBT7ExgygFf4EjjKdWaK39FqaUEdMkmGtRemM1dV1\\nPv4dH+MLX/wsjz3xAF/8g2cxacZjjz9M8IL9wS7PvOcphNd8/tnX6a6dAwVaVIR0gWlVIWSgtXaK\\nVt6jriqef+N10ujozHd57cVXSOrI+HjE2z/8fupyyOT4mO7llCfe/U5ub+7y4fc+w7N/9Hs888F3\\nYStP0koRO5aX3vgcdmb5wNd/EF8O+Kp3vZvte9usvuNpjqZTRvu7rJ9Z52hrl/3xEVeefC/19Jg8\\nSanxlGXJ4vwiWd4lBsdguMtb167yrvd/EF9VTAdfPmD6/wth//+rTySCk+ATUh1xKkW4kzmdBKEs\\nOvE4GU7kNAkhOKLzKCQ+QjfvI0yGqy3GpGSpIlGS0tV0OnN87P2XsH7GXNJBCc9b+wM+e2230fcF\\nSapX+NKNET/43d/O5166yhdf22bj/j2+/X1XeM+lBYpZ4PEzazyYSYxsczC13BoWIBRbZYlUgXFR\\nkhrNvaJmWI7wPiW2EqzJ8UEyjobHzq2TG4Exihgj7370Ih9+/Dwr7cjF9QVW+i00ikwZbC24vLrM\\nardHoQwj57h/b5s3r22xv33IZDLj8GDGcFZz6+4W3mlefeUavaTLE08+wsJcD4eAaPBBNMlPMWJE\\nQUaOlimVD42WtypYb+XYYoCYjPGTY2rnGA6HxBix3pFpQ/rmi+h6ig8WVwwRoiJGAShqIZpFERpV\\n1dz5gb9Cog/QoQnqU1ZgEoFlgvSR2s6I+/vc+t7vw3iLDB7rfRPj4WUzgpgUJCI9mV1CUBYjM+Tx\\nLkqdjAiSFO1h5qekvkVMmnmnQOGKplvzoUKWCi8kotcmqJSqnmBlIFpHP0vBCyQN91WKxksvfJPX\\nhJVYb8myFJHAeFw3/FgMNU22fHABXdcI4SFofKwb6Es0uODIZxNi7VBJk6KaSIERESEDyeYuYvtN\\nUtlCGxDOEaWndo0dV6sEYkKWaLz3ZAfbiBt3CCFgvAOt8KQEoE5KoqahLMWaorIEBMrHJhssJown\\nFYKMEFUjhYtw6fw5rl17CaEFt29tgDHAjP7CIv1+jwurZ7h7Z4s7b91mYU7z7nc+zjd88L3ML66y\\n2O+ysjDP+XNnwLRJlaaTaN7/gffQmZ+jOBjx6KOP4ZKcx595is3NXa6/9jKnzq/y1vV73HrlNb75\\nW78B01X8ue/5C/iiYLGzSN5ucfrsWb7p2/4U7/+a9yBV4OErz4ARXLx4Hi09cybhqcceZbnb4/Kj\\nb+eDH3g/SZjSznJSE1A+0jFddjbvcHCwQfQzOr3TPPrw4+y/+RZzSwucf+DSl12vvuI7VInEuQm1\\nSEl0QNUJQYwQMaElukQKptUMEWrG03+76EgUjV88RryrMFIhhCFvd6ntGGkS8jxnLon87CdfYWxr\\nFjqCXCrKJGdQBVR0pC5lMDvg9t19fmvc5ua4ZkscU1YVz33qWVJtuOMP0FJyGFLi0TFC5xhpOZoV\\nfPDJi/zOc7eo6sDS0gLtpQfR2T0WUkc+s/RaijtHQ9a6PbZLhbczWlmbjvFcv72Lio5+K+f27j5z\\nWnL74IjHH1zj+O6IzaMpR/e2Ee0FcuUZW4+bjhCJIOiMA2vRtWc3GPbv7/He9zxJ3pL057s8fPEB\\nnGu6IRUylAxUUmGrGkXEiynLp88ha08mNZPjfXrBEURzjZTS0zl9FiklQUW6aU4YbBDqJq5bBd3k\\nfokmotrJ2CR5RsHd7/trxMO7BNmYCVQUVN6hfQOcJvFI63AO1OEBz/7Uj/Lu7/1BiAmeSEKTHRW1\\noyonKGVw3uFLTxA1OniCmyFCQNgZMSgylTAth6RotDSARXqLDxIhc4IsCVGyeuYs5gSm3UbjpaAs\\nK3Se4rWBWmC1IhEB5ZquVshANIqqqFDBI7TCxwCCk5lvYL3b446MKCsIsSIEcLFsZstKEKrmpqBO\\nQv+8AO8cIUbe+MG/jIqSsjpGaNWkl2rZLLJokg68dAQv0SHiKsWtH/nrnP+Zn2nccLhGNxwsOgRi\\nsDil8TFijGnyp0TjJPOuIE9bOFUjCYQAy3NzfPjrP8RgcEBRjgDFpUuXyDstctXleDwiTxWj4RE7\\nWxs8/tS7KAdjpMl4/PQR1+5t8KGPfIg3X3uFC2fPMRhWXLp4iseefIxYDinWl1hdWyc1kCYt1h88\\nS6v1Tg5u3+Xs+iIiZtx48TrGGK5ffYPLp5Y5/eAyW3c3GR6PWFlaYn9vwNuefhI7Knnj9VfoLHYY\\nHR1x7uxFilHBzv2rZAunuLB8Bl1FTl26zOc//Sna7S6drMvTV97DH/7vv86kLtjb3+biI8+wf+8m\\nPTviaPvgy65XX/EF1YXQCMmDwgQotSco1VjtZhVFVWNUjgiQZ7FxwQSarbGX5JmB4PBC0V2cIxWG\\nQuZoNCjPcGaJWYIRkShzjl2JNgYpBVVoc1zN+I1Xd1jKFvjdGxuYpIW1JUSNMU3G+fbumDRPwGQE\\nX+OUbrSPKuX3r+5ilKa1MMfSXJeXXnuZ3vIae4dDhGzz0tEEOVFMg6ceDomZwStPEgK9U6dIJhNC\\n2uLx9zzM7u1D5swhbxxY9mykDI5HHnkUoQwb93fZLadE4ZlXXbaGBaGVIINmfiXQanW4dWOPoB0v\\nvLqNne3z/o9+C9GB1K7pUqqAiYIQCqKIpPM9nFRkMmCsa3KZAnhbI3WO6WcnoV/wyf/5nzBX10QE\\nobJIpRDBgKNxV4UG2JGXEX/wJrX0BH/iApIRGU5ilWNAVRJHAxipa0f3My/B94hmwRIFQolmMVZE\\njLREH5BKY01oxpv1qIFSC0UtM4ScMSt8k4HlHImSWAcxNtHLMkoqH9CJInR6TZSNrylCjcQ3ziWn\\n8IlEJBLhOVkWSaROmkTRUOGCICaa0lcI0caHgJTNVvrn/s7fYX02IhMgT4j8xhhCiIABV4B3RJU2\\n2loj8Eoj6wJtS2ywpMpQVCVp0mrcTwJkMDgKlGwg6iiJjxPiNCBqh2xl1NaT6EbtIEUGsm5AMkYT\\nhUSpRtsag23gK8rgo4YAf+tv/zC/+Ys/z5NPPsnVN17k5WubfPSj38L+nV2ee+Ez7G5uUk4r/rM/\\n++fZyzLe9fanePXV1zl99gwL86us9bp888eX+JWf/2WGxZRhUbC83OajH/0oL7/wWZT3WGu59+bz\\nLC+cYjFLWGgv8ulf+9e0Ms9oVnJj54ijwnJ5dRFdzxiNLTd293j95m32h0OufTJjMJlw+9nPknTn\\nyFDs3hiQmw6vvPQiE285319ksjPkzvIWexsDXv/sJ5k5ySjTvHljk3Nn1imKgoQZi605ir177O/v\\nI2OFSrMvu159xRdUJaElGuapC5FpOUVUltwYXFKjs0hpa6RWTCczfAgkSv3JiZunEpPOk6aKXGXg\\nLanRTQFRilpLWrpNt7fQvJh1ytLSErNZSdSOU0mbC2eWMUkGcy1G4wExRnJjOINqsesAACAASURB\\nVHvhNIPDI3ZHBR965lH+6IWb1N7RUm2imhBdk9cUnMcR2Jt6WnmbpVOnmOhdlG3SRGNPIiWEXo88\\n7eBChYiSymrWHz9D7TQ3DgsOXKSWKUJF9PwivZiwNSxRokL1MlaT9ZOOoybPcyrnMSYFqbHBYdqK\\nD37g7YyPI+PqEKMULiqicI0mVcUTUbhAKokwCTJErJJ0kkZMr0TEGwmhZn7xPNI3G/P+G68RqEmS\\nBKdqolNI4bChgZOIKAlS8vyP/ANyAsZLlK9QRjWzTqOIf2yzVAHta3wd0SIlTibEyREiX8IRqG2J\\nihYjJLUR6BAQMkMlGUJKekmbGAJRKkyaIbzGimMS0RCffJREOWtoSyKCagp1dIoHH3m0SSxQjdU1\\nIkiSDpX3xJg1EdsCFHXjfBIzopfMrAVtyBEkKqHAQwxwojhpv3IdowIy5gjlCc5jK4dJJCaX1LXl\\n9Nw820VB1OJEUwvbX3gZ4R1SSipbksiUwpXkSc7wfe/l3r0N3r65RRkstq5IpCFNMmIULEnFvged\\nGKSr8YC3E5RPsGlEiIbZKrxrpG9Af34OHSSz8QjlB/zqj/0ISTT8j9/35zl7+hw37+7w2//iX/PE\\nQ6fZ3dhl8dQpqsmEX/2nf5d+v89LrmRh8TRf+sRvcubsCkRNUQzRusMDvS67leTJp9/LZz7zSa6s\\nn+P1vU9waXGNYnLApBqx/WLJxv499rf2uPjwedoqwx8f8fVvf4IkavLWMkXtGe5PKAtLblocDsa4\\numB9ZYU7dzc5uzZHXTieunKam+WEed2l1U7Zm00Z3xlwNJgRbE06P8fOfkmnmyHbmvFRySOnlmi1\\nWohEMb+4QFF4VPhPKKRPSEUZapZ0h3EsiaEkl5ooBfujgoUsQTuJdRUhJBit8QGyPCPPU5QyKKVI\\ntKH2Ci0jkiYSGQfCSHLTw1URYTxZlpDqmq/5uqcZVBOO92ecO7tCisGGwDMLl7l/PGFtocPR/hEf\\n+dp3c39jSiuFDz/zBL1WC1vVTL2jLBspUCeHvLXMbLpHf26Z3kLO9vaYpb5hY2uEUAGVSlqZJtrA\\n7Y0jnnj4AlEHiJput8+zX3qRR545y+27h2RJxuHskMtnzrO+1Of6rR1qZvREn53hgLNrCyyuL4D1\\nJJmmY9oQA2nbMBwXLMwpbt6eQIhI1cifvI90khQrSlpGkURDe6HbJKI6Q101zijvIzJYtFlCzHVw\\nImKiRI3uU4eKuhIYYaiFQziJzg1RRLwPJD6S3XuDUE8alqrQuMLjMgFrq4yeeTeLv/UHCD+h9gGt\\nM2JVIUh5+dd+mUc//l+jEOzdvIO1ASE8yoI2jSPrOIaGThYEMs2ILhJlgVEtkmioRYl0iloGjGwk\\nT1FnCAQm7+BEje52WO4v8KZQJFJgQ8TVvvHptyVGgA8Wr07SWJ0GZRtylMpQi30cAhEl0jTBhUpZ\\n1iYjBAlBeqSEm1rzUDRI4QjWo4Ti+N5bxKVTJDpprKpBcv2nf5plXzSdu06JNPKtjUuXec8P/BBP\\nKHjlO/8rWnubtE0LX02ppUEEye/87D/ind/53eBp5G9SI7wCI4iUjTHD1Q1ISGh8FdnZuE8/yZDC\\nI1RAioKdwwJjerz15iaLCxlB9JAzzerKHNNixtb+Lr1hRjFzrK+u8tLrL3Lp3Gl8UVOLkjTLidHx\\nb/7wOXqL89zdvM13/Zcf5zd/4xdZWZxje3sP6WqiMkyqgrVTi5w6e4bB4RbDSWR4PKI8muBbGbv7\\nR+weDqh01sSIB0s71fh0jtt3NzizvkpRTVmb77C1ex9bOSIWdxDY3d7i/IPnOL16nuevXmVnY4/1\\ntRUW5vrsb+0x30og0RTjCTc27tHrLjSLtVb7y65XX/kFNUQyk3BgJyRKkglDpT2iDsx3WnSzjIPx\\nmDhzSJ1SWUdvrkW73Tg90jzBJILoJWCpRMQJS1439KqWbFPZAik962t96qTPhfkEneX4acmDF84w\\ns5bYEvQSz8RVPPHQWV69do9ubrh+c5tUGW5sH/PMw08wsAVWeV5/8zbvevvTTMshm7sDHnt4mdIt\\nsjMYs328jRQ5a8trHIwnPH55FZN1aM11mR0fY0RDHWqZFkcHuygSHn3bJSobeextfVqdDjoEKldR\\nWMHjb1tn93jEwc6Yhy8u8siVpwBJ5Wq0iYwPp3zmCy+ghSVRKUWoeWB9EZ8Y0tiI81MhiFGQkxFs\\nSakn9OfXmKkMIyJ6ahFaNJIqkVIyZGF+AYHk9370ZzlrK1Q0DaQ3BiIRaWLD65QZGIWrHG62iQ+G\\nJLrGSKANMW9z6Sf+HlK3eW3xQeb++Y9jtabWEVkLgiu5+7nf57GPfw9RKG5eu4GxEpdDYiUOgbcO\\nm0VsVFz5qR8F0cx7g4dlmfDst38LulZErQjOEXVG2Z3jsZ/+aRQpBR4tI1olBNd0kKEODeTFBVyw\\neJZwUTbFNIA7weiVtaNNAsJSDgcUdUAnsUG7eo2MBuFHjc5VJlgfSB48h3lrpxHT1xVRSH7zf/3H\\nfPiv/A28d00Ct48sV65ZvMWSUHtCJijRPPN9PwiyWbq+NrW8Ryq8KIl5uwHECM3kpWt4oVDSoHTE\\nV1NIU4ItEEFQJq5BHUZBHR2yLmgbg0oMwgsurS4zv7jCG689y/LaMq1UczwecXfrkIXLXbIsY1If\\n8r5nnuK5Z19ETicUlWG51WMyKDG5INJiUo6RtrkRuszwzR/9CNaWTI6HlMOSyWTC8vI8NlgSLfF4\\nQhkZDyqW51o8cvkiVTnhaHzI8oUzvHH/ENJD5Ewwv7xE2m7YEoPBIYiSjjFMjkrmOopT68vsbx1x\\nWA24/OgDTEZDbo/vUNcVD6wvI6RkeHxM2mmx2ppnZ2+fi2uLXHzgYcbjKQv9Fq7+8r38X/EF1YVA\\nMSvptluMYo3wgTxReAzOlhxYiwqgZIKdTZASzIknv93qYl1E2YBSBVp0UMGRmRTrKnQUlLbAGIUx\\ngfe843F0lASV4wrB2594kMHBlNqNubC6zlprjjqH7fsHvO3yOkYo5ubmKYtDLiWnKKaR6CqWVtp8\\n28UPc3R0xJnFVS6tn2JWTllsa+ykAJXyjrc/xGe/eJX1Xkq7t8ydm3foTMYMdg7Y3bxP1umhlKI/\\nt8S963fYG3kef+IMm3sjFuYm9PqPQFEyqyMb0z12d/d5/dVbLK+exh4M2dwfMhrMuHT5Avc33uL2\\nnV1+4sd/mBu3jvjiF7/E537/d3nfN/5pnM6I0eOAlbk+TngQDkFCMXP4VgA0URSYAAGJVR5ZJ/TW\\n13AisnjjJbwdI2RslidhglEZSImWzTXeG0jRyCYMlChBeomTNbN2IMicoATv+6Zv5Ll/+uNIDCoo\\nrPAorWiNPV4GRHTsb25xWnl0CVZYEh8JLcPRbIYMDhJFVDk43/BT87xJJtCGWHuMMk2aatJQlso/\\n7rJVY1PFN/NVqRPq4EBLUlqUsYHkCRnwNaig8FbS1m28cEQEKiYYAWBQnOSc2QLhK+IJOjCRkosf\\n/BDu9i8QplNE1thos9s3cbgmFjsEiAEz3sXFgEIjdAACecxZefg8x3WEYIn9FrEcQ2yoVNpIYizJ\\n/PhEneCbUbeXhHKKVhmJdjjdWLWd9KRKIk0C0hLrkigMnbZg984h3kdOrZxmeHjAyuoKOQlFcUxw\\ncG5+CVVZ1vunOHdpkZXls7x0/RVWF5eRyoDq0E7X2Tu+T5SWUEX+1W//Lh//2IcBiQsVS/0uWgTK\\nxguLDIbPf/7zXL5wmr2jfZTJSLMuN27vszV1jGzNUtoiXZCMpyOm1vLwubMs9lfwU0lpC86sr3B3\\nb4gbHbLWyaimI4bbh6TLfer9IVJkTKKgrTW29CSTyEG1xYW1U9zb2ObBRy4xGk7ZGwzI9H9CM1Qh\\nIu08p6oDitAM8iV468l6OXrqGGPptiNhAlop6rpCVYK9yYT59hxONG27VpIygJ0WiATqaUne6mB0\\n4KGLa+wfOvp5wbCs8P6YV/7NHu985gprawtcvXOf5Y5ntjfHpz77Kn/uG5/k5vaQT2+/ypm+5P7G\\nBENJFSxGR979ng9w/bXr1DFw5coDHO7P8L7mudde59v+zFezfW/M8XCLt3/N13Lz2l1e/tyzCFGg\\ndMLUl+RHAxSRLx0+hxGC0d6A6cECdUzw5zq8+oVrmChIe5rN7ZKqmnL+VJv9w3s89+KQxXabq3c2\\n6IkBr93eYaHT4Yf/h7+BcoJhOUYyJQqFDB4v6iZmuC6IUWBUmxhnJ3BuUMGhtD+ZP4YTx5MH4VHO\\n0xnugJJUoYIqkJkWQmuCC1Q1SBER3tIzPYRsJG0xRJJM42zFcW4aeDRNkqcQOb46wihN4jV1bumH\\nBtNH1MxGY2z0RK1IT3KdZOEw/S4ySZq4m4ZuihSew519pNSIUKKUwXsHRlOdULISJCEGbGxAKsXo\\nEITGhwmJMcTaItOMMo0NuNxrhGwg1TJtwNY6KlysSJMELU2Dl4zgiKwmHW561eROxUAdS57+0Dfx\\n/D/+eUg8SdSEuqJTNNbq6D0iNqqIKBxeVgQLEgM1+KSBQsuT3APlGqJ69OLE2KIIBNzEYlSj3ZWx\\n4SEok0Pw1MqTdhqqVfA0i15REWTS5HR5R5Zl7Ncl73rqAvd2dmnlgTtvbZLolCigLCJDOSSVlml5\\nTAgLeA0L8z1GkyGt/go33niZ0+vLODshlhE9L6gt2IMxadImxJJK1hwelpRTx/J6n1B73vv+d7B9\\nd5edynOx2+Nwf0zayrEOVvs5BoWKkrTXod4/4tadTRItsNOc9lqbe1u7zM+3ONg9RnYNQinWH7zI\\ncy++wPz8Keb6koWFeW7f24Ig6C80POHRrOLUqVXu3NxEa4mpDGV1/GXXq/8fFFTBTJT4YDBKIhQk\\nyjS09QoGrsILjXaRWjQ8ydpahHckUlDVM0QRITeYtMtgb4i1Q7qtNu9/7DLF1m3aa2cp79xHB8PM\\nHZCRM99u05WRnRdfRcfIK/cjh8UBupPzaDflZ37tt2mlYHTCwUQTrSHNDDrrQV3x6hdeoHIFY+t4\\n5TMHLC20KW3F+OCYW9c2KPyAEBNuX32Tnatv0daRzWHBShfmkxZJFByXMy532xxXBcVin42DCd0E\\n5sUqs9YIW4M78rzjyjlu3dphe+eIdrfN7uE++8eH6Bh4/q1trBecWRCMjhz9xQ6+doSkpHaC3HgC\\nGqkkduJIZGgAE0IjOhk6CpyKjVDdKyQNrFgpTau3yv3PvYq3BaL2SCLaZARhqesao1KM8Q1diYTX\\nn3sR5z1pbGJLnHOAp85bOClI8Li6JFAQCZTOk7ZTqEEmjVpA+0C1N2xAJ4Uj5o4kGlwqoN8n4iDI\\nRkyvAB/Zu7sF0RO8bLCBsvGNx3a7kXeJiJSKEAN1ZTne2gc/Q2nDrJjQMi2cc0yrBmsXhQBXU1Nj\\nQqQOFUJ6NFnjjAoBoVQTmeIi/+y//Us8SiSiKesZWrWZqIDqzGFmDhsCQSR0qgamXgvQSBwNO1Z6\\nj0wE+Aa0YqSiKh1RNaStajYkRIEKhqAaEIxQ0E4N0hhC3VD9bfBoAZXwKK+wMQU8idbU0RNCggw1\\nVhkgcuP+MRkTDuw8SZqz1G2xv/MWrcUefjxhfDAkkYuUZkov7XCwu814VGERSBWAyJUnrrB58xoq\\nzbDRk9SeXr+DJSBzGt5AHWknkgvr5wjCcjQYMxgMsQ6eunSBvZ1t2guL7O9MqaqabitHScPtu3fQ\\nWU4aJWurcygrKGSJnVkWFhYoK8fpS+fZ2d0nzRqS1iMXH2FY19jRMfv7+4CjlbfQQjKXJmAbh+So\\nKpE2cG5tjapW/J9Rzv/u5ytf2B8iMUZasmFVFt5T1TMQlmBLUi1JhcB62wipXSPwFzT0HakEM1tz\\neDDiYGuDyfi4ASXPLJUtMGtnoJgRlaaazigq2BsM+eyNfa7uH3L99i5FNeORs4YrFxegLHjj8AjI\\nSKWml7Q5rAw6VsjEo6YHdLopxg/ppIGLc5okVRwdj7m/dcz6fIfXrt4lM4EUSztIDodjRsWYU+1I\\nriTDwwNeuXWPcVFy6/CItkk4t5Rxrpvx2MWz3NvYZTqakkpNf1WzeXeHzeNjnrm0TCIVS6ngbWsr\\nXDnb5+JSwqVezWKnTX+uw8b9m1xcbbO4uEimG/2hIGLLiv3dA5wv8QSCiNjCE7RuCoS0BOkAh5ce\\nVzlUK2fw67+PEB6hm8gZT+NaAxDRItMcKUEQuPHZz5MqTYgCeyJ3SqLGJwpRO2IUTfqqdUilyUxC\\ntJCIDFuUyJgQlaKbgAgekYAvPVWooRJ0eyuN3lIIlGjIUnUUHG1vgUtwJzR8GwKYnKPpMU42pB5N\\nE8qYm4TXXnkdTIIUCUneaQAiUdDq9Zt0gyBIEkPi68bemeZAq5mZYnCy0T8TGibv8nB0srwTpGlK\\nkhmEyaiVJEjR0Ku0QKgS60sSebLMEtAMXBTeemJdo7TA46kpaeQGknl0Y5nEEkNNJpMmkCA4auvx\\nUeAC6NjEtpiokDQQbQENpCVEgq/RuoHCtBU8fHaFC+fPUxhYX8jYPRywevoMFI52t8VgOiZrG7rt\\njEwrXNrm1pu3mM6GzLUXKG3B+OCQ+YUlSufxSqATRV2V5P15OmaOgIZaMJs5rt28zt7RMTs72yin\\nWT+9RpL32TqybA+HjJxnoa0oq5rSFjzy2MOcW1hC6oY0NyqOURIGUzge7OOJTIYjep0u02nNxt4x\\ni6dXmEyGjG1DGWtlbcoYKWvL3Z0tYpoQVOTSqRWK2rE7GbBb/Efiof7HeGJsflClC1S1x9SSSYhY\\nBV4mGN1C49AkZLqRWYmTl8kkGoEm0wqpArNqgoge4RusmhgMeGs446i0jGo4HOxzd3/EYFLj3YRK\\nRraHI56/P0CnCbe3jlhpa+ZwaCN58tGzTKuCspjQSxXloECLlJ3dPY4nUwpbMH+qR5JKvJ9x9lSX\\nkfUkpOwdDZnZY+7vDJhrgaCkl+fMdTJOr3ZZ7/dYyds8cGqNM0sLdBUU1SGeyNrqHLlp08oVK90F\\nXts84OueOM9rm4fMp4GlhWXSVsMf1VlO3s25tz1m+2DE4sIC94Y1Z84tnwTHCUKMZElCuT8AYcA2\\nhafVaiGcJfljor5J0SdwaWMkroZk6zrelUQHKBo8n1FkKGopEYkgBoMMgTiyVGGG0IIkbeFCY3nr\\nLy2T6gZycv/WBjJLCPWMOloQAZQl+BIhgVCjgsa7JprEiIiMgihr1JzCu3CSqxQARSIid69dJ3Qi\\nIhSEGEmjRrqAz7pEPAqwJ4kFEcvxxg7Rg6UE2yymhNIcWI/WCQGHrSqsaLSxwZYNU1VGamn/xB3m\\nfKM37bkBMjgcNcJLlGmjTYpP2ljrcWWBlgo/q1FCUDmBFoE6BpTM8WmGyjWhnYHuEgxIneACVN6T\\n5QanaozJiAEqb8FHShtITrB+iECIjVPLhprCWchMg/2rHUiBTgwiCKTWlJVj42ifW/fuEsYlL7/2\\nJkkuGB7tcXRwSDGyZN2UmpTd/SNqrfDDMedOnWM2nuHCFD85ZloWVLMCX9dELxB4RJTMSk/pZ3Sz\\nBYKTZO0WadKlGE25cOYBZuOKyfGQF196hYfXl9BRMacE4yKwOxhxMDxm594Od/Z2WZ9fgbIkMW1m\\nkzFXHriArQI6kZTOk2UZy70uBs31G7cwacrR8R57uwOORlNOLa+Q6C7rq+sMjwf4oPEmJQbNfKuD\\nGE++7Hr1FV9QpRC0QobUTfqp8xVJNHSEQfqKcTGmDAErBFqnjZg6BmoCSEkdSxABV9QsLy/jiJjc\\nQHTMQsG3fOQbGJNRBYlHcHvrmFTCbFrirCJGT2Y0d/Z2aZlIXQZqnbOcKV556QbRO9YXMm7vjDFJ\\n5M29HVITGNaevUPP5z//BqPhhKqQGJ3RNZIyluzsHZMnOasrC7x8c5d2K8HJBLxhY2/KqCjotAQP\\nnpnj1Xs3SRNFr7fC/s4uf/j6Ls/fvI8GtIwYGXjlzUPyCMeFZX29x3gkcFKy2s+orWJQlUzDjPvH\\nBTs7AzY2R1A3xUQJhXWO4XjazDuzGuczHBojEnxQCN0UDidB+ia21gSFL+8jtSII19wIjCRa2xQD\\n1ywQfKhRAbo2YkQTSxOLEkHAx8Cpy+eY2Qof4dbr1/B4EpWjNRh1kqskGkpV8Ip2VTeLF+HxsgF9\\n2BCZO3MWpSWGE0Jz8E0iwXBA7iCIDhoJBkRIyBZ7eO/RJ0YRpQQuJuxtbuB82WhstSLgcEHSOr2A\\nr0qUbHizqQkIEryPCF9BkPigMCoS/w/u3izW1vTM7/q90zetaa89n7P3mc+p0a4qV3nqtN12t9Om\\nEZ1AJ7QUJKJWc0UgIEUChEIUoUgRN5BWUEgEiE4EaSAScdLIiUKPbjy023ZVuexyjWc+Z8/Dmtc3\\nvBMX3+6Iu9RFIpWyrrf22sP6Hr3v8/yf34+GRIISAVF5ULIl8AvJNJHMy4ozGiQKrQpq355qV7or\\nGNUqvn2U7P76r3Pr7/8G1/7+17jxG7/Jzf/9N7jzv/0jKtcKJBWKNFpUEDhXkyrdqq9lwiBPaXwL\\nxPIXkGpCy+4tTM6sihfMYIX3jqaqCSIgPCR5hl+UCKl5cniEKnrMZpJpBdeu3UDmhu2tXfp5h5s3\\nb5Nkit6KYcacrOgxmpfc+/AAYWuWzZJF5UnThOgjXjasbK6hdcLp6Skx0YggKDoanWtOzw74qZ/5\\nDDWW+bLheDmnOyxI0h69fsHQeC4NNyk6hkGRIrWg3++zszMkSRIene+R5hoTBeVyzpOHT3hwdMR8\\nOcM3kem04fbuLphIkC2TttOT9PtdJjqn9h4tFJu5YrKMdIbDj16v/lUUwX+ZryAEtbColrSJUoLE\\nROb1nBpLrnTr4s4Cs3KJlJIo2geqqSNKtrTtKFTrMJetbE16weOThp959RJH+wuOp3PeOZxTSMGP\\njiYclQ3LecUsJhxPF0wmlszkbcTKekazEd3VdRZNycG545mrA9Y21zBKtSuL3jMYpPz8F1+6mNZr\\njkcLZj4SnEAREC6nrMZc2+qio2G0XDJtFmz0c1JtwDf89o8es7uyztnRkg+Opjx74wpzV4IpWO8k\\n/NZPDvm5T+2Qpobda5u8eGWX0VHFUlh2exnj85JrGxk31vt8/uYuX3rhWV69NuDZZ26hJHjhW5e7\\nSjh48rjdi28UNs4QQeCFa/UYS0v07RU2EME1fPNv/59I2+CdRUWJ7vdx3qJMwAjT3hSSljQfhYBy\\ngmsCtirblUjVmmUvXb+JlhqE5WTvgCR4olFY31LFommn8QBRS4JdEuqIrT3RJ5iYIumSr68QnaUO\\nNUF7pIEgJNPzOTZohKzwCXgliBo665dQpNiqNTiE2EKudTlqN4raT2CrOAk1Z2fnCKWwMZAJRaTd\\nw0/TlBpLLQNRgxXt/ruVsNvpIm3ZXsGjxlFyGAJFYjgJGi8gxhJiDQT86BRrHbWbkWmFyjN01sUr\\njUkTZKLBCBJlLlivnp5JLgZkg1ZmKRocjoVo11i1SpBItAx4kaCCpAwNnV6fVCZo4ZA+YpRGBIUW\\nbdj/yuUNttZWuXl9jeu71zmaPGVnc4Vy2ZAJQSfPUWlkfjhBScnDh+fIrEDEgPQpN2/dQhRD7h+1\\nJgZE+wynJiEvepSuQmrLcLXH2XjBaF4T0y5pr8/77z0g1wk3r24wW0ZOj8acTc+pF1MWwTBeVDQB\\nNte2GTtHaSue7h0hjGJ67phMHQcnZ9RVYLgxoAwtZ+PewT7L6HhyumQ43CDTktHxOU+fnmLrhqur\\na5zO59x/ekCVZYymM0oXPnK9+tgX1D+eZHocipaNuZg3ZNq0D4Nur1eNbXF+IcDqSoJCoEREWodz\\nNbmKjM8cPZMgNDQKdjcT/tJ/+rc4XxywcCVX84a01+WV7XXccsmNays451hWJesdz+PjBdlgyHhR\\ncjL2FF7y/KVtbq4VjMYTzg4O6eeG+5OW43h2smA8Knn3cM7evGY1K5jUlijAB8WjvYf0VnI6KqW2\\nS+al5epGwuXNNeyyYknGwWjCrKy4danPL372RY7KJdGViGC5/extVpEkOufR+ZgCeP3+GaP5CLVo\\neDyeMF02RLVKV+UsGkfdOD48OOPGjRtolbZ4wyjx0jM+PSYREBNBnhR464GACgqLx4mITi+GJVKT\\n/+iHRAEqarwIfOa/+Wut6iQqGulJss7FaqnAEyhoN9iMLtBSQBCtikbn7aBIJjSzFriCcOhoENYi\\nQkSsDFoBoGxIAJFrlAYhPUEqTCHo9S4TlECQgFdtXEsGCl/jgkdED14jm0DAErsZyEArLoEgWpBJ\\nIhRK+ws/VgZR4vOUje1dlIBUaXqdAunrdm+/sqQ6b62oXrX9yCiRHv7uf/HXCERssK3iRKSIW7eo\\no8X1MlxsPVkGg7OaH3zta4gIOukQiK0FNrQtptZ0AEJqolC4GPEXfWcfHELMCE1NEhVGJZyPZ+3t\\nzDkEBonBCIeKASklzkhK27Qwdp2024hEKiIOwXIRGI9qjk5nPLj7LkNVcD46JktqZmcTzo5OGI1G\\nTH3N+w/3uXx1h+V4yvFowsH+Q0bzOTRLhonkfHRKkrStJNvA5Y116smcKDQ/fPt9VF8ifEOhFN42\\nKOlYOkfjXbvhFgLG5MytpdPNWV0piPOGcnzE7HRBr9PHBcnK6haXVnKmXlDWC2JQ3D86wwbLg9Ep\\nV7c2GCSCnoGeNqz2e5hE0ckUtTPsHx3jm8jMtnbdfp6g4r/Qdv//q1cf81eMtIDgqNo/sAgUSdI6\\nyRXoEHB+gYyRxrWw3ZPzxQVVvRUCSKmxUTF3JTPbUM1KulnK06M5p/ac21sDXh2mHPoex6enpNrj\\nY+Anj0YYKalt5HBvn1x6nuyf8ae+8ALIlDwLLMoKY2rOl55cd8hFQeHnrHclaz3BvGq4MpB0U02a\\nRVYSBVqhtaQJnsP7x3R7KT5kRNfwgweBu+OGq1e3GC0Dn3nuNlE0HNWBJq6m4AAAIABJREFU33n9\\nLaJvFR0Nkj/4wV3O6xml9UQVOV04ntkd8Nxz2/SGgkf7E65cXmNnq8uP957SX+mysCNubG7iTYKI\\njqADOgo8iq5IsQoMSdu7TFNAokTEuNYLZVSCwaCkxCwOEBGicmiR4Is1bGzFecJ7FuWcUrboOoFk\\nOStBGFx01L4dQtlgGW5ttackajreQdAIp2jpIgIQTEOFQxFqTwoI3yYKJAYba0IV6K32kV60Hisn\\nEFaRBN16haJtfUsyYGVAK8HW9g7ee0KqgVb6Z2vLgJSmDjjA2hEueJRTrF3dBcB5Dy5CVORJ2soL\\nG4sSBm8kIoo29B8j2ydHBC+JQSNTjVSeW1/9OaJI+Nyf/rNI4bHe40JAI3jnn36doAMxKMQfc41D\\nywOwPhK9haZtDygESrdgbYkE20a5rAxtCiE1LeEreiIWKTQxKKRWCB/wBmxov1cMDiMTiB4lW6jN\\n49kJIfNsbW5SmoR55ZgtG4Q0TIPkrJkQS0ndzPipl55BOMuLz1zl8rDP5saAXk8R4pLoLVtrl0EJ\\nlNbgl3xw9y6BGttE+p1V/KKh220FgTpGmui5ffMOV25ex8VAlG0GV6oMYwyX1tfZvblJnq3SzQyT\\n0Yim9kwm59QyZTWDfneLSvmW3PbsTZ67uoHUgrIRdIqEKjac1lPKBk5nltpNiFi2+z0KPH5pyQtN\\nwr9QLPLPXx/72FQEHh3OuLoxIMvBlTkuAkyJIaFuHJ0056y8uLYhWiJ8gKBaurt1jn4iaBrPer/H\\nZDrDlZaZr9la22ZRQ7G5TuomVLLg/31wQJQp2kmMhp7osBCRKxvrvLQ95MOnZ0hZUTtFtwfNwvH8\\nlUvYpsSKmps7azgXmC4q9k4OSaWmnDvenp+QmpRpWUE05InAddY4OnzC+qBHsHA2PWM2iaSJJk8E\\na3nGDx5EVqqGX/3qa3z7nQMuba5RhpJrOyvsrhv2jud87sYG/SKnOxzwez94h9dubvPKMwV1teDd\\n+wuGnYLv/ORDXrq0Td4VrK4X7Yqka3XbxgeMbdBaE5uKUAnixa0gEqlVTd5cRHCIRCdJlKKJFsjR\\nxvDgwxMEChkcXloS3SGaC698sJhQIVSFFgoa2QbVtaKxfxzozkicpg5le6UOEq8iaYwsGpDYtqVT\\nW5rQkCmBVwblJFIHBsNVKiEJoi3W6Ha6bcslIEliTkQTpKWyNVvDfvv7uoATbc/dGEVdLdAGopcY\\nk7aqv1DTW91s389bom2o8OhKtAOl6KiaCK5LEz2FMtgYWZnPqIwgkQnlYorMU66/8Cq1c9x59UXe\\nUmCiwFuHUSV3uoNWFW08sYkc/eZvMjs6RdqG6XiCqlp+52f+xl9pUZVOYpTGJBmVa/8+MgYaLKu7\\nVzBeEnWbishShTA5IVqMEHQ2NtEy0lx4wayrMTpDhja9MMhznt7fJyk6rHUN46Vla73P3ftPuXJ1\\ng6ePpqysp2TTDd6//xCjMvYfHjOdTNjaHrKcOZIsR3cdabdDWExapgOG9XyNJgSyJGdanzPodojB\\nY6Ti3uEJW2vr2HX4xh+9weXOCsYUlMsJSR5Zzi3Hjx9xvLBkqUGHmihydA8CBYtyycxbjJ9yqW9o\\nXI/JZMbCVcwWjv5qj83BKt996x2sUbxy4yrRplQ20ETLMoBIc7Jc8OhshA0f/dz5sT+hCgSXt3rE\\nxhNDymI+RWIxsosVHpMa5suSbqf1HbV+d01mDEIJMp2TyYASmiRP8PUSYwxLYclWVng4G7HaVzw4\\nPmcxtQg9w9vA9jDjbDlDBce///mb/Jkvf5JrKx2qySmT0yWf2ihoLPTyjADsXN/ivb1T1voJUeac\\nL0sub3bp6D7RtFK40XTJ0WhCuZi3JwbdR4kxK0bRySJLu+DZy12aULPTj3z6uW16KNY7Ga/sFNw7\\nmHNtZ5UrK5o7G1u8/tYHHJwvKDoZVWPJ+12qMnBrZZUQKzIi0ypQVQ21U+ysFBxPp3zn3gmd3goA\\nUZo2FB5j65eyFyeBvFXVeVpNsfaaWlrqZkbUAhuXBNeeTBMcRyuCH3/rTQQpQQYiCdY1zIPFBYlB\\nU83qNqcaHFbUrUAxStY2Ll20cwL1aEqmMoSPoD2zjqQxOTc/+SzSN6011VuIGt8IYqjbtoPR9Aer\\nbRbWK4xWQERrSR6hqWq8dng8UXhSMnrrG//8hJaotLW1OseKSBARhGidXsK18juz3iO4moDg3Td/\\nhIwBoTwyZshoyPME2e+QqBYz6BpLNTtpfff1HJ32W4pTJ8cIQXdzC0W7SSaVoBYOOSlbzq8DbMn8\\n//i76N/+x7hvfJ3um79P//3X2Tx6h+gUCImSEK3De0siWmoX0ZAESRUNVgts8AShSULA2TlO1Agn\\nwUJAYUzEdLpEJAhLbS+yvZOS4WbOKy9us7a1yU99+RU6eZ+f/8rPMuz1WbrAe+89QXUioPn8Z15g\\n+8oOCySH05ppuYA0YXMw5OHpmFSm7bBPSr7z+reoq8gHD54gao1zjtXBgJ988AjdWDYGPX73975D\\ntA37ZydM5jO2t4YkGlLR8MPjM6JwbPf7bK/16XcLqtKyu9YnLXLWs4yNNEOIPptrfaajOYOiz2xa\\nknnJ2+89oNsfcGOwyvh8QmVb1m0jKpJEI12D6Pbp5DmZsx+5Xn3sC6oUEGpItMJZj+4YQnDU1Ggi\\nSRB0dEblFX88tLLBU7nQTqNdTWI6OA/LskHJAmLEW8cwU9waDNg/m3E+WyCix8aCxgZeuLpJp9Ph\\n01e6vHNoeXR8zsgu2D+ZUSQeVIcrXcfRyRgJfPjBY168tc6ilDx98ojoM0qbcrJccnRWgTYMuylS\\nFTTRkiYdGhvoyMi13TWOx0t6aYIPhg6SykfevTuijgtOpmNWLq2y0U1584NTXn/7Pk8OztlY79Mf\\ndFg4R6er+d733+TR6ZgT68iKISrTHO1PmM2XDHua08WSK+s9rhQ9NvrbECPK1wghkEBs5AUj1OG1\\nQwhN6zpUGGkxPpCYLsHH9oovJMGX+Ci59Rd+FekgEXVboESKCBHdHyBxF1xU8IuqladK2QroYmBU\\nLolSEWnoKwiqHQJY6+nNPPes5dRFpEqxISKqGV2paIJvp/BaUmtYRLAxYkWgbhrixVXNTufopL3+\\nEhpoPC4GBruXkSGAaU2syEgmNUmoWzlh9G27wrcT9TxfA6VARBbH5yQyEGuL2ewRpMD7dhFFBk8M\\nktUiR1+cbishsc2cIEK7WOEj42qCiBnhQlWtpCEJAeVaHkLWyVDeE1xNgsQESRMWeCHb/1eMoBWq\\nWrRMWOdBterrmGrWr+20MSUh0BKKvI8WmtQbnPD0NjbaZEaEpi5BNGiZIrOEqFOcFpyOG07PFpzO\\nKr7+W9/jfDrDC7h37x6N9QTd8ODJhO31bd7/0V3Oz8d84dXXuLw7ZFk7ekpRlRYa1570m9YTdmv3\\nBqOzMTtr69x67haz0xmj8YReprBLx0k1Y/fyAJN02R4OGHRyDk/PGJ1OmVWBnU6O8ZJUOJZCM1pO\\n2dncpAyehMi8lnT7Q4KrKeua7rDPzEdUt0B0Mswgv4iKKU4WbSxqWQUGyQqjszM2Nzc5OT1kNqmI\\nxnz0evUvq/D9q3wVicG7QAgtBk5r3YarrcYKiUwhlw0xtDQkLUDIgJYpKE8TPD5UdNLsokB48qTg\\n6WTOeVMhgeeu9Hjh5jpfuj3gK88/z41LQ+rGsbM15OGDD3n65ID7T5fMreXa+gq+sewtoEhTLJbG\\nWXzM0Lphe2cXhWBpS9KguLbSY91INoqCT99Y5bVnb+GcwySKN+9bDo9OmdkWI1Y2Y7Y3VthaXaHf\\n6zCeWnxQnJ2M+b9ev4/1E9AwmleczRZ88HiPS8Nt3FJzbfeTCCt44XqP8XTJw8enPPf8Fi+/eJ3x\\n6YyvvPYStYusDAKpLghREpOE6ER7KrILdKJI8i7atVR77yKp0VjrsTK00Z8Y8Fi0AiVTkIK1Fz+H\\n8A0xBpSOlGFElAI12CD6QIMmSTK0kKSpafXR0aJ1gspzpG9PViFGhItE0d4uEpVwvam5/ZmXiTGi\\nhQRtaHxDIhWiCSghMT5lsZiRSJDBkxjR8h2io5dkeNlOsuUFB1SqwEpvSJASGWULQ0HSEPBViRMe\\nHVp9sEwSQLJ2ZRN8wFnP3Q9+ckGS0izPz/EiohLFpLY4As5HmFRUccmymkPj0CZHKI1QBUK0WvAm\\nTNsVXynBCVxTI2Obpa6qiiTLkdIQXIMSgsS2ksGoNDaCrUtkLqht2/LyIbaIwSBYff5ZJAEpNDZ4\\nVHQ4GpahbJ+DJGm1N17ircX5lNi4lokbG8aVZXt1jR8fTJnNJigUg7TL97//fU5cSp7ndHoDjKkI\\n1SFvPH1CIj1/+MYbvPfeEdv9AQtrWV9bIXjJ0taQtMO1u4+f8uInbnL96hVsc8zt525y+OSEK9d2\\nefa1Z3n37X26/ZzcW2bBcbqwdFSBNpKd1Q4LF9leG3IwnpN1CpqFpZtozo8OcFFgpOXB8ROCEJyP\\n5pycnpFrSNKcR/sn2HlDpiK1Dzx7eRctAtsbA4xasLG2zryeY3xGp5eA+uid0Y9/D1XAvFyQpinV\\nsmZl0MVVlqwjcUHhlhNip0DHHk4sW66oAIHEaEeILfJM6YQmVoSmBpEgsFwtuuSdFWZlhfQDmrLh\\nrhOMx4c8OZfc3Brw+hNP0THomKLqMwaDnD96uM9W0adoliy9wnQj84Xm5PCUjTylKhuyQnAyLtFJ\\nJMoElRmOjkaMZMNkVCGlIfEJz3xqQLYsKYzAm4z5vMtiWXI+ddw7eMruyoDbKwNMqhnmGXkKWSdi\\ng6Ob5iRoXn//XTbyDoIl6yuRqvQcnExQJHhb8v13J6gM9g5PwUTW8x6ikIi6VSALpcBZUtte/Wtf\\n432EeCE7DAIh2nyntTXKJKAMwTsQQCIg61DUNVY5pDdomWGUYHB5tYVkiNDaPnXAVbbllLqsZbA2\\nnpAEnAdTe7SUEFqeraNGJjkb127ilbmwjQasLpB2jpIpPtZYGUiKTiu+E5ogJHiBCg0iVmgbEMZQ\\nWddO5IVgFmpMUCgZcEJePAwSLVtGayNAR9GiAlWNMT3EhZN1OV4goyQaQYgK7QOUNXbFo4QhxJr/\\n9b/8yzwTI1qJVt3sKqLu0fUVInp8WaJtgtftNFte3Ab6WnAeffuZb0pCdGgvKLEkpv0pEwm1jwgh\\nSRqNCxAucrdSCwKSwe4VAhLbNKik1bRok6NUQwiR1fUNZBRYHzBpirc1pVCkPsG6QAiRIoWbvQ7T\\n2vHS9Susrq7y4PQI6xW3bl8nnJ8TjcZmQ2bNGSfnC+bW8uLtmyRacToZMxlVHJ4esnr5Cr1kQBlK\\ndGLQ/YJyec7pkynOjRlurnByckKRDrl6+zL7ZyVmMMTjSLxk2nh6g3V+tH+KkB6fas4mDWdPjlkd\\nrvH48BhhCubLOZUwND4wqZZcXl+hWEl4cj5mtvTc2Npi7+QIbTVbwy7nZU0aDVUFebrK+XhCbT1L\\nGQle4q37yPXqY19QCRGtNctg6SStWTKEiNEJc1GRDXKapm6zkrHtdSgEQkIMgjRt97pdDIDBiYhz\\njlp4rEnomEBH5pxacL7i9HiBUIpnb27wvZ88YSXLuLG2wZPxnP4gYzETlF5y6CZcf+kOe8d7DESf\\nmjkiK0A6rLXUVSDNNB2Z8sHTfWI2YDIbkVQN3SIj5ppaBFQzI1QN9/amzKoRrzy/1u6cO8Frt6/g\\ny5ofny3ZaRzzqkJh+OrnX+b9D99hZ/sSlW14eavLb3/vHknR4Qvbu0Sr2Bwael2PKwO3Vz33TqbI\\n1DA6XfJodExZlhALlLho2GlFbccoD1KGNoajUxCCQuZUsSTVoLxoJ8QBlDJoKTh9aQctu7jlss2u\\nRocKgSA0ne4KQrZrl06kZEEQW1c0LlZEnxCdxWuFVhE3n7SF4QJZ29QeE6F/5ToLWwOWQgVqFRDB\\nEISjiZ6sLijLBdFkRC1RISJEIApDbMoLFXUAZSDUeCHwDqJIWsdU9ESVgGvQIcHXJcLkNGVJahQx\\nJERlwSdIrZjs7eOFIY2O4CwOidKa0kqibwhEbp/PEVohm4jAEEUN5Zg3/9yfwcUEMotUbbGsiWgh\\nwAtO774Pt58nyXJUy7Ai6vbGZbGIGClt00bckCzKBYXW7WqqFMig8NLziVc+xb3pAp0KDBoZBSEA\\nlUNow8w7tpUkBItQmiRJQDqitwQRGFtNs5yTK4VoHBu9LQ5rTba2xc9c22FlmDMfD3m6P+LsbEpP\\naTY3NzlyjrefHLJczummKWXw9Pv9ds1YRwrd5Sdvvgkycn5yzLXhgKqqWYxGDNb6WC04HUd0ktA0\\njk6ec3x0xrixzKoMlaQ03jFYXUWej3FYTk7OWNSW1e0uvoSYddjd3qFSmvHknEnpmMsOsrCcoGlE\\nhlGR988nCGkIRpIuy5bspTSd3hBbzcjSAqrmI5erj39BlQItAx2hmMVWquVkxHjA1zjR0n2kbk9S\\n0YPHY6LBR4nzFe3OIqSZZrZskWUhaM6XJZ0io4qWs7Mp2IbSW5yqefvDE5bB8Sdv7bJ/MmF9vUtH\\nGt6bjlm4Cisij58+4Xxao7VEq8D2IOVsGVksGvqFIM8GHNUL1jfXuHs0wYkUokLkHfI8R8iEk9PA\\n0cMTttc7PBgfszFYY2sj5R9+8z3+9Kev8/6jI37h1TugBNfXltzc2WB0PiPrdfjOG++TUnLp6i63\\ndlbo5as8fLxPnq6Sp57xsmKz2+XUOm7fWOf46R61NeTZgOW8boVwIaJDmw+MVSRqja+WqI5o848i\\noVNoEhtpsGRIbJstp/aWBsUn/oO/iI+OJCqUN6josCKC9wy2ttuhlAz4GEk6Oc3ME0JFVvRorECo\\nHGkkwjoSt8A7aJo5wiRkHYkXEhc1whhooFzOSElo3AKRahI5QGQSoQq0kgRABIfTgkBAS0OpFBke\\nhWpztELR1BFXNAjfrui6smnPn8ERlYbgkanGSUUMlraXpJChYTO2j46NAWW6iNjgQyTZWcELQYIk\\nqc6gggZJUWjqqsEZiXYlaRKpF5YQBEhJFhQ+RkSMfOvv/U/8yb/+3zMeLQjB4Fm2ETfvCSZBhApD\\npBEJMgjSTOEWbZ9XCk2Dh5Cwf3aGSg0aQwgWGQx2MWsRl0nO9voa0fkWR7h01ARWii46SenpnPOm\\nIikylFCEaHlwNGfBGIXnt+4dIFzAKIFrmpY2trrGt/ZOkZmGNMOkKaUUFBeLDh5JdJY0z3jl1U9T\\nqA5r14Z85w9+n53NS6yuXOH+ow+59+Ad+sNtpI5gIDjJUHXpugYtBTLJUEEy6XbIVldZOIdYSVlR\\nhqYpcd0UKTV7tia3DptkiHzAhmuYS0lXKNKdy5BIetLggm2XDRLB+qUrVPWE4/unFD1BdIKk+6/R\\nlZ8IVWyRfcFHkjRv3e8+IxqLdoGoFKF0gAQpcLTDAW3ExWqdJo2+3W2WirquwcDaRpfjaaCXw0on\\n41Kyyg8PH9IsLKUBEVL+2btPMEYhoqWbS1Cef+fVm5ydTvnks2t858NjdoabHM2XjGY10miGfYEk\\nMseTG8P3H5zQ7Q1Jhac/GIIKqDzl6vYA9GXU0WPunZ3w/JU1vvHGezxz7QqX+4Z7B/s8Ojjhzs2r\\nPDw7IaaBvZMFMZXsXr7MWrfm8OCMte46bx3fx/qGzc0NggWTFmx0B7x79yE3L19mY6XLwdE+1y4P\\neOe9D2lsQJhIROF9AAOxnBF9g4gCqVrbgROe5bwmKk3mW4upcJGgFVo6pMrJLt0iciGPo41bSQxp\\nquhs90FGiBB6OfaoISqLbQRuOkLlKZ4K7QM+VEg8UkHW7bWg6tD2ea011CKQSoGQBucmhCQhEQKv\\nGvDZHy9T4V3LBKWJJDoB70hFxNem5ZniCVoRlUTFhIDH1RYXXAsQQZJIeREPC6gITirCeIbodYki\\nYd1OkcHiqhq0w+ExumBw6xNYa/HeIlxFoywyKnzdEEVEX2Aoq6oiUYYqWkDRaDAREhdQT86pgiSR\\nAq0ELngSkdJE19paMNigEMKTSEmsa1yAzEicD3gajE6wSqNiwAaPVJLp6Iykk4L1eO1Qg/RiszBj\\nqRsG3SFJauitbiGlpDAClRhuXLrKM89e5XvffZ0v/9wX+Uf/8P+m11nBJBHrArEBlSacnT2h112n\\ntks6g3Wm41NSk4BQ9HHMZiVFr8PW5i6vvvoKhyfHPL3/mE+8/Fmu7GySZBucjU/4ype+ynRe8vzz\\nz9NUM95460dc2tjFsaCfdrj94ouMz04Zj6fEl0qiUjx8csSt29doqpIm1AgbcUGgfZufDlagsgTv\\nLYPVLp0iZX/vhLJquLS1jU5SZtNzEpmhE8/j7VN6Kz3GkwlSar79ze9+pHL1sS+oQkAaBKaINHVA\\niBIhNIsIRVQtecc3VCISo0eISKYVltDGYIQgkZEGRYokLxJMv8d4uaCcSnxTcx4lq3mHscqZe41I\\nFUFrPrGzwkzBq9e3+PrrD4hS8swg8P37Sw7mZ9iOJMt7HM4WJIlDLCQKz5OTCiE9nY5itrRcGvYp\\no2BldQ1rK/7UV36GvYM5r75yi3/wT9/g8dmIL/705zg5HvPue4d88uUNjj98ygd7B7xy8yZP90r2\\nzseMyoaXrw549+kZb73/hK7WHM0XFN2Ez714iwdHxxw9mZD2hhTlgvnII0SPb35wzNbKgpXuGt+8\\nN6fTWYMsQQSFkqpVdYSaNE0RNmJImNs5wUOiJcvRDBk8AYVEYuQFVT0EYjeidEZEIRcTgq8RNiDS\\ngI0Okw0RaLxyxMEmXgaUS0lUxKIRKqEfloyDp6cz9OwhtbVkJkP6BhsUStU4QxtWlwISgfBtMsHX\\nDhkVrivbKb6M6CRFyogJjhgCUnii0hglWoyfVWhj8LRK6PYGE1FK45xllik25poq1CQ6wToH0vPd\\n/+qv8slf+zv42Zj8fA8VI6gUKwMSQSDy7Fd+DiEEq3nBh6JCivY9vW3D6Y6ATLpo56n9xS4/AolE\\nIGmEY00EtLKUXuCdRMcEF33r1ks9Pnre/M/+Y37hv/01/vDP/woi1CRaYZ1rT9hSI4oUh0DGVuXi\\nfOR4f++CCKYIEdK0R4wBjccEULlBp12Ct+TZEJ1rEhX50p94gf29M65vb1CXc/76f/1X+N3f+33u\\n3vuQ5z/xMp//zKu88fqb3Lj5bzObzJkvF2xe2mZ0OmI2mbJ2aZtHjx4yn864cu0aaXQsFgvqZcn1\\n27e4vLbB/ScPeOON3+bS1jbXbt5gejxmuTjj/HTEJ+7cAQJbG9c4HR1zvL8HHtb7ffZP5gy7Ba++\\n/Ay+roiDLp4eKZKqrKmmc0bzCc45NrpXUXmk0yl49P4HrGxvUXQ6nB8fMVzt08wqkhVN1QQ2hl1+\\n8OZPeOHF59HqX6MeaoiRUVUz1Ak+BhZNbM2RPpAqzVxAKCuKosDFgNaaPEsJWiKjoEhTYgw45+l0\\nOmhtsMZRyBWWUoKCQa9g//QcJaZkJiFozWAl573RmGUZOJwuiTTMveaPlhEpG3rdnKbYoqwlh4dP\\nEdLhG4tVCUIkVEphRU4ySAjzwEqaIiXs7Fzld/7gTa5d2eTB0zFf/MwOnZ++wlsfjllWp3zms6/y\\nzuESubrG6uo6ey7BlScYMigUb51MMf01hoM1FJ5rKuN+U/Hg8Zgiy6m7KUJoghOEEMiygq7KqIRm\\nvwkUqwLrLIaE2igSIiG08A7tWq5nlAlOy3b10kcO7z8lGoOxAi8rfFRt5EqmjD95hxgUlXBkJkGl\\nKVErbLDImLK2s0l5sSJw3lf0UERvkVpgEITa8ua/95fg+i38/n479FGaWDuCMOhM8bRIuSZkWwxc\\n3WZUo8QoQR0cAoeklQFKZXDUCCuRImCdxYqalNaf5LSlKAq8yfCxVY7L2AasQgxIabgrHD3aZTEn\\nWq5rEwXm7gMe/cqv4soxTeWRJtAEi9I5ZeNJOppk4zIe+Pqv/R3WY0S62LYFtIIoSZQG4Ql4jBCI\\nCxAKVmCVJMoa0cwgtOmKZjgkHo/RJkVgCR60h+HeHn/4y7/UxrKiQQiNlA1SavBw0BmwC7hoUTFB\\naMH06BQXPN4FhO+zc/kyR94iMJTNHOccVkPVlGiTonTGq6+9RG8w4LJwdDsRVzf84Md/xM9/9cs8\\n/fX7HB/t86NvO86XZ/i4oJxY8sEad999n7fffpvx7JTnbtxhc/cqPpFc2xkyXN/iW9/4Jt1uF2rP\\nO2+8xfxsj1/4/M/ytd/6BjdvLplX59x/5xFZrvDTOW/+5HXWu2vk3Q69QrG59Rw/fP2fsLW2ghRw\\ndH7C6cySD/tIW3N6vMCkgY3NAVUVee/hAVU1pzPooZMVvKhYywuy3DBfWnpRYwtJqiUywux4RnHn\\nBq9/49tcv3nzI9erj31BBVjpCEbTOSv9HkpGGt+gfKBJA7JK0EXBMnqccEgU09qyM9xCyPZE6pwn\\nUzlRAVJRhIaYKawDbVaZ2DlbVy/j60BjPd46vvgnnufJfs37jw9oyiVRFaAdXS0QMsU1CQ9OlrjG\\nIrJu229MDCYKUJbNvM/l3SGb6xt8+OCY85NTXnj+Kstl4IXbdwhEOnnGfO5Yzh2720Oeu77ZBuyT\\nHko7ZrMl09Iy6HQYjybcf7zH2nqXrbV13r77gN1LK2RJzmRckXcynjwe8eoruyzclHrekHU7VLOK\\nTr9LXZYMewXvPjji6d5jbBT4qsGqQGI6mBBwoxpjMlw5Qxd5m5YQguODpyTBEYRrJ8h1656PSvHT\\nf+E/oRIRfUH/ihebV1FovLcg83YPWyj+7H/+H/Hmt75GSDRNE9oEjdSY4HD33gdX4YJEa0cTJcII\\nhEy48St/vh3MEJHCELUnWIOrl2BSomiLSIgCESPGK6KOeHfR86wbpAuITCGdAudbTYvutqt4gfZq\\nLTS2WfIX/8e/xbd+6RdIpCJIxbyakaU5UaTUy3NwYJJAmhZEt2AW1rnUAAAgAElEQVTpK9Ikpeyk\\nlIkiExB/+CZJTKiYY4SE6AgiYXr1CqXKQSekSYfV93+I9yXWt3nXzKQ0XrbDPaUR/+4vof7n/wEp\\nAj4EopREEZG0X+91Br6GJMH6iIwOkpxP/OW/ipYRjIEgidHz9PEew2AQyoGpCDESlaEsG3KjMDrH\\nLvcQEa5tdNjcyHjj//nH/OCfeMqR54Xnr+KiY//pA76b/gPunZ/xi1/+Aov5GY/f+zFbaztMpic4\\nFL2VHvH8jDvDDUYP7zHaf5flxHLw5g8IUjM52GM8rxj0Cg7ncz713B3+5jf+Bt4HvjvZo248C2rq\\nKjBdjmmsIb2iONl/QlkFyvJ7DPspew/3MNKTDFYZz09IFzUn0wUvXr9O01ScntVUztPr9bBWYMmp\\nbEXTVNQVDK3g6OCUjfUVHtw74fOvfZJyOmd/PMa9fw9UwueuX/nItepjX1CllDSNZrhqcIuGkLQw\\n3igSVBQo4WnKmmgkhUhZJhYjDIh2kCAoiAgq6emqlhZltMLH9noe3JzPvHyNbp6zf1JzNne8cucy\\n+0dTnIp8/tPP88GjCbPZIZ2kR2M9d27t8uhgn7TT5bnLuxydj/jw4X1euXkDaRqOpyW3Lq+CHFDN\\nznj5ueuoF+/QyTs4VyMzaCaundzqhv3jGRuDIaNqjo+C4/17KJ2Sdrpc291gvKjprRo+mT+LkZbR\\nbMknn7vJWidjGQSDQURUJfZSznC1S1YqVndXODs/JFlZZWvrEvWiYl5WjGfv84XPvYyWKcokCCRW\\nWHKlUB1PPLJokzE3qt3xRjIfj9iIAaTANq3IzocAGqpiEykVRE9TleAbEqHRwWOlQmuN1poQHJNm\\nhs82EaM9JA4vDDJEltYhjScQEVk7ANG+wduEJlFc/+zPEbkIzYs2WmWVJ+1kNM6CiASRIzGI6AlC\\nQBUQSmKiJEiFExFRAa4mdHOyCxqWxFL5SCINiEiWFJxUS0w2hPK8Ve40KaJRiLylbSmh8b5i4mq0\\nUARrkUqQ/blfRiKxQXCpqqn9EikU6IB3CiEDn/7v/iY1CqRGipq3f+U/JJsuMNpgY0Pt6lY/HZZE\\nJbj51X+D+//L3wYlUbY93Zskp2pqTFIgXUPMDL5aoE3LXnDDDQZXdokiUlcNWrXEtYO9B3RpCNGi\\n6FKpiLWeJE+YNxXl5JReluNizcnpmPPxGQvrmY4qfvFLn2U8OyMuLDrpMV5O+OKrr/Dtb/0hEcuz\\nz95hKaeIYsitK9s8ffABS2249/iQwWBAXVk6usP33r3Hi3cu8XRWo13DF177HNf3p7z147e4fukK\\nT85GXH/uBl/73e/yy//WL/Lt73wDSY+i47HzhiBzarEgZDlnLpIoyWa3x3k5Rcic0WzB1voWB8cP\\nIOmyMehQyoCdLlnppDw9OyUf9MgE+GYGTc7tnVXmowU31tY5Pd3n8uoWg/Wcp+c16SDln339dz5y\\nvfrYF9QQwZiIxiBMpOj3GI9mODGnCF0mzZJe0scypw4NOhEkvYKsA873cS6QpRIlM6qmRABVcCiR\\n0ETJv/mzn8KYPlonVPUBO5t9+t0uTVVyejBlrZiz2SvY2bjNVtcQjUDrhGg3CVIznx1y8+oOO6s9\\nnhzfJWeDjobF0pKlNTF1jBcTrm6ucfe9+ww3Mx58/wNuP/cM/TxlZ2OdThPoFpJ7+wfEWDBQkRt3\\nNsnzDnWz5MloyU/evMtPffZZRgdTio0VXHnO/rGiN8j5YP8EIxXPXF3jdH8P2Sx5uHeX+WjJS699\\nmv233mHhKp4sPV9+6SZLkYJ3rdlABwySMoJoGpR2+CA4nYzQLaMaXXuCh2haSpEQkURLpllEqQJx\\nEdVRUbb65uiQaFIlqVTAu4BSKUE6Hl59kauzA6IHI8HFBhc9mexAbJACYi0gKYgSqi99BlSCj64N\\noLv2JqKEo4mg3f9H3ZvG2pqm51nXO33TmvZaez7zOVWnqk4NXT253W1bdmLHHTmWCDgRkgVyghAI\\niBQpfzCRkIKQrAQsISUGhUxEcSQ7IkgoBmwnMdhxoLvdXT1UdY1n3vO81l7zN7wDP97dTmMl6cKy\\nkFl/9trfHs7aa5/9rOd9nvu+bgFCsLQL4CqJVEhIVTQI+IYEjQ8uxignGcoaXO6REhpSlIEgGkQD\\nta4IJmP8x3+K3q/8PVxdEmRAyoAPHu2hCRah2khbgVeYpKDJFC//6J+4IuJb6uYc6UGqBOuXBC9J\\ndDvaS0Vc9slgEEkr5lfVDrREhAiW3uqtcbyscVrwrbW7fPr4CVaCkoKyrhESvCsJQuOtRSUJTsYl\\n4/Zf/kt4AlJIUp3GJZyUNOMZoVGI4BG1oNdZYXQxRgYw0uBknItrI9m+06djodYpe3qf3eNn3L/9\\nCl/f+SaL8ZI3PvcppJ8wWG8x6F8jTTIm8xE61RweHqI6XYrRiHxtk9H8CI9gb1GT6pSzgzN+7HNv\\n8tZb32B8fsKwnPLCJ17lnW99wCv3N3n3/Sf88Ofe4Mm7b+Psgk63TZrHhIyjywuUkgTXoCyUtmFU\\nzhlfVuiWIk80Wi1ZWE0uPMuFRWqJSFO2b2yiei0ux0NurK3T669wtLdDb9CiWGux2tlm96PH2GrB\\nylqXR+cnaNewkg0+dr36Q19Qv2Oxc66hdBXNEgKWIu3gAmTO4EWFFinOg180rG92UUIzmV5cRegW\\nNHVJaGpUGmUgo9GIu2tdnn5wRKc4BW0YDYdkHcNyFDjZryl9TZ22qGcLHp9Yhqspr3/qAc/3djjf\\nPafTz6i8ZnpwjBJLhocTtm+NCdMFI93mo+cHDAYpNzYHfPU9QTn3/OZvPma712NDK47dlH98MKea\\nXfLixjqH85K19QxVZrw9uaSfKb72cI/72z3udDOev/UBRS4ZT3aYzRsGnYLhpeHsdMpLtzPCJJDP\\n5lzMatIssL6a8c5Xvsz1l19j8WyPJ4+POdpr8enXPwE6w/qAdB7vQWmDEJ46pEhKVDulRmGsZ3Y0\\nZq2d4pczrAehLQ0J6oufjcR377EolCwJweJUQIcKQYdcdPAqifNK4Ef+0n/ER3/mfYpqH28dIYjY\\nHUIESVc1QQq8CFR5lzf/g7+AEx6hEjzg6xovPa7yJN7gtSe1imStixNczRIF3jmci5IpXWRUTUAF\\nh1OOxkMqu9TeYXxDjae2DodHBYXwNZ/6mT/Je//nl0iH7+N8hgoOW9cEmWJSTS0srhbUUiCTnP5/\\n9fOgDaCRziOtQUmPp0ZLg0vBGQUyp2h1mc/nKOl5t5zxprVoJEZEZ5cWkuff+gbF/VdRaP7U3/qr\\n/K8//e/x0vIMt9QUxuGoCWkPV89J0TTeoJNA+rP/Bf3BLUoRVS8uRMoUTUkzm+KFR4qAFxXTZU2C\\np0bQ6bRQHpz0JNJwe7CG1zmP3v+I7Y0eOztnyGaX4XTJzZsrfOvtr/Pq3ZcIMlCNJtTFlE57lb3T\\nA5QHEsdkIVDrlmpuyEybpFiQjS0LNMPJlKVKKXTCo+GEazJDu4r++i0e/s7XCK7kvb1Dbq32cc2C\\ns+kFg/V1Ui+iSkQ76kawPWgTvCLrwbjyMc3VGaZlSdFJGc0u0FmGWza89/A9CCkbm1u0Cs10OmV+\\nMYaNdZ589CHqgaZ/d53Z4Yzh2QlaJbSNZun+AL38Qoj/QQhxKoR497uuDYQQ/1QI8ejqbf+7PvYX\\nhRCPhRAfCSH++Hdd/4wQ4ttXH/trQgjxcR5gxC6kIBJSk0RSv9XUy5JQ1zjnUImhdvFHsQj2dw/Y\\nebZLPVuAdcxmE8aX56gUgvBYAqtrWwhZc3Cyx8XFEePhKY275PR8yvBkzNJ7FmWFb5Yc7A3pJEs2\\nUsvJux+hpxULVxEWFbP5hOHlBdNLj+5k7B4MqbAYX/Lg1iYXM8tb718wPDrgcj6mV2R88sVNqnLO\\nzt6EjVyhW212Z2NMEgizmg/OL8jsgsvZkNs5HJ6egwVrJ9SzCkrBxVnJcDSlrhzD3VNmpyOoFszm\\nFaPhDFvCxdkCnTTsf/gWi0XF9336BWovef/h+zgRQDgICqVixEkoNEZ4lDbo/uB3kX0rnXZEx6l2\\nFMoLRaoVn/13/xxOxhc841yEQUuDIickPZz2BCNp/PLKsgoqUTz4xb+OXX8Nn+WoxIAqqJxF5Ipa\\nBEKSUt+8zYO/87cQMkXpNM5HZaA3WEGgUCrGd0id0hQJYVEhRCAVYK1FSkWiFd47mtKSJl2CStC6\\ngLxLmVeYoCA0SAVWBFKjEGiUMiAkr//1n+fpzc9gUo/MCpKsjUwVUhtCIwhpCv0+N/7GL7CxfS1K\\nmZxlq92DVCJaXdK0j01bhKyH7/cRUlMuone8CYFP/Kl/G9PpIQZt6n4HvbKGXL/Ob/7D/4kgRYz1\\nDoqf/KW/i/nzPwudjCpNEWYAvkaaDJslNJvbrP/C32fjU2/gZMyeEr5BoPDC4aWOab/ax3iXRjKa\\nT7HBIpqS+WxGaaGsLMvGsvSe2fgMYUv+2T//LbSDZ0+f8uqd2/gaPv/pz/LB0118FQh5wtsfHTKd\\njclbGd5VuLkkVAKnMqrSIaSlvzqg0835wg/9IPNeG7+84GLhQKYUa21WNtb4+te+wgu3b+GF5I27\\nd5GmzXCh6K9e4/zklEXZYBtBJjLAM61BZgmmKKjKJZXQtIxie6vL8+NzpDEolWJST+5yXrx+g+nF\\nIcEL1gc5P/Vn/y2SluFTn/kss2XD6HjE6loP0TVoL5Fph0x/fC+/CCH86z9BiB8GZsAvhhBev7r2\\nXwPDEMJfEUL8Z0A/hPCzQohXgV8GPgdcA34DeCmE4IQQXwX+PPA7wK8Cfy2E8Gvf6wGud4rwky+9\\nAEZRyAJvRIziEBH6YJzi4gpb9htPnhNwaK1JtEELSZanBOeRWUqn3cekCVwJuzv1GKMSyByZyjCm\\n4WIqaBvNZFFTJIamqUlVTQiBTmrwUnE5C2yvKZ6fWK6t95E07B4NeflGl6BzbGX5ypMjEqUIQrPS\\nsby8vs54VCJERdFu0W5njMcOrSW9dpuFqzm8GHJ9kONCilSKb3y0w/3rbQqZ4xvP6qBAZhm2KVnM\\nAtI01POGo4Xj0/f75FmPo/MZy+UcqTzz+ZzVlS6TS4uWgYnJORxV5Lnhv/zLP4fQbYKNEGmE58nP\\n/AVY7mOFY/dOiz/2879EkCmz8yEqNHEZqOJcVChJq9VBKoVVCuUFNEt8pODROEuSJDH91HSwIkCw\\ncRYKBNvQzCve+Ru/SPfJB/j5Aq8S/A98hpd++k+TtgpQGQGLJ1wtnmLaajWf4nBkJsNaS5JqgoOg\\nkxgLEhxOWggJhIBIoF44pIBExkVV7Uq0yvFEPkEIIfraURgRafvCe7wQyKZmdrlk72u/Qzg6IfTa\\nJC/e4+ZL99GJgKCj1lGAdnF2LF2kZzXeYnSCCwAOZTKcr5FSxTjtEAgSvA8kUuKFvkqWjYmnQsRs\\nLOs9kniawDbkeUJtBcv5BJPnkZvguYKxh9iVQqRZSU9TwS//2X+HH5iMsdrgVOAz//M/4sOP9njn\\n7f+LtNXlF//7/w5HoJ23+aHve5F6MueTr97nH//WV1lrG2Sj2LjeZXQ2Y1Fbng/P+ZHX3uStb36b\\n7ZsdJCm9lQFGw6zwzE4D+/v73FhfQScplatZLVLGs4ZXPv/9XEwnrBYlj771AVm7x+nOOUmqqIWj\\nrBXL8Zi0aDEan3P91guosERUgiAENhVMziesdBLKBowKtPMBhbLsH53T3RxggqN0cyYXU25s3eCd\\nx0957f5dTpczVte2KHTNSr/DydNTNm5uEFSNrQJf+/pHbF+/xpPzGVnephsyfv3Lv/X1EMJnv1e9\\n+p5H/hDCbwsh7vyey38S+CNX9/8e8FvAz15d/wchhAp4JoR4DHxOCPEc6IYQvgIghPhF4N8EvmdB\\nBRl5nQjKxEHQVMslWZJcQSGaGJkh3NUfRYw6dsHFDsV6vBR0swRr63gqcwqJAGlYVGNask2dC5gH\\ntvI2F9MpGk+vo1iVbfYnM+ZlSX8953Q4R3tN22TUnPL0JBLQe7nhg8cndPqrHJ6ecHctYV43fOLB\\nXdpyweGoot8zJHnBbBZIUsPKiqH0NVkbuqrL5XhCp2jx1ffPGHRTPnNrnbwwaJVxenHOxXSOrkpS\\nrZAKptMlWsNmN8HOBY+GMTtpZaXFaDiPaQNNICkMK502h/vnMcUQkErF/CBj8HWNIkOGmrpuCKLE\\nyQEagaWmu9oFoCUkgoZEmigFCkCQaFycC5oUKRVOWLRL4xE90TS+jiJ5ATJoXHARATAY8Pn/9D+m\\nEQ7lBE4JEqGwSIKPmmICKC+pXI2XGhpPkmSxaGhNolMcDWkrpSobQhB4JZDEWa4PHtkkMb1BK+oQ\\nA/CkyrDe4lXEFjrn4zJIiDgjtSqiAoUiqJTeqqHzEz+O85GAH6REYPBYgovvq6BoJGghcEpGY4JM\\nCCImiQZpcL7B+zjfRF1ZchEoLE5qROOu1A0CYyTOepxvQIBtHGliUK0Oi0VFkA6Tt1AqEu3RkUWr\\npaSuSpTULK0jSxKUEbhqSZ1YpDeUNvBP/pdf55996bcpK8e0nDAvK7QxzJYLRqMRq/0Bbz96yKv3\\nrvPOu49YN4py0ub4cky5tPRCwtO9fW7dvUcISxohqZqSW9du86UvfYXNOzdJ6gqvBItmQWewwfHF\\nLh98Y4dia5vjnT3em56zsrqCm0Vzgp/PKDpdvK4og2Q5m7OxeYOTs3PWuzkmMZyejVhZX+Xa1hqX\\n0yXSl3ivEJ2Gi9mc9lpOOZrgk4SinSM6Cpm3KTo9vv38iAcvX2Nyfk53rc/p/jlCNuw8f876+jrL\\n5ZI3XrpBrdoko4o0BNpF/r3L1O9Wq9/fbTOEcHR1/xjYvLp/nf9ngPX+1bXrV/d/7/V/6U0I8R8K\\nId4SQrxV2gYva6rGIqtYvLRWoBz42AEqE1+tJTGqwAMqEPOPvEP6wGjsEEpS1RanBISGQgU6eZ80\\nT/AeKumxzRSnJNtrmie7pzwazzi9HGGKFvvjwHq/wElQ2QqbquDmag51w/7FgqKVIQJ84sV1XtxY\\n54ce3CcJNUtvaBlJXmh8vWT/YoYIgo+eHMOiws1K3v3wfQ4uRuweDbm2otA+BuJZL/nW0z1MJkiE\\no9AJ0mu0q8jwkXA0W/L47JBuUpG0QafgxZJOMSA0Cd4J6sqDddS1jdQuwMgU70qkhECNrSsybUhE\\nh5UbG3h5pSnCE6qKuq4gGBobEE5Te0cQQDBxfonE24CzEeptHdiqBudRwceoleBQ3oFOCFVc1hAM\\nQaZIArWXKCmIQeAR4bewnkRqlNBYH22OAfC+pvINQeirmA9P8NGlBGBdiPlIISBVBMp4W1N7j+Dq\\nZOY0trZRCRAvYEPsBmtqlGsQIaaL2qtwQiEk2sWYFuEDQiUkLiBoYtfpIpc0lxodSy4IgQkO6TUZ\\nAR+aKDELDhFsjIYRgFaR+G8dy+WSxosrFYXCKI110CznON8gfDRYECwuxO8JUC1rvIqeMCkltq7A\\nVigCmhyXBPbCgl/71d+gqiqqMmZ0+RCt20oJ9naP8T6wutajri23rq/R2dpgMl1QZCm9rubmtZtY\\nI1Fp4KP9pwzaOf1uj2+9+wH97WtUk5p8pUNoBLMy4Ks5uU740z/1R8mkZe/xDkUrYbV3DVFaLscz\\nrr3wEqlKuLd9gxubA7Zu3aBtiJAbarS7culVjsnFiI1um6l0pEXkIQ+KnEQG2p0i0vZlziI4Ts8O\\nsXVDO83pJR0yrcg6LVaur5Ku9Li+foPp5YhWpyArukyWl5SuweoE5f8/tJ6GEIIQ4l8/N/h//z3/\\nJvA3AdY77RBEhlAGLxqUd1hrKVJDTcOy9iybGTLrorTA2oBExjmXj4FyWZFR2St7ojIxxTyXmERR\\nlgus83RFQVNLpq5hPhszVJu88spLPN7djchAoTmaLjkYzmm3+hzt77C92eHRszNuDFqkaUor1Swq\\niTZLxrM5adJm4RVCVuwdD8mLPqZq0O6M1fYWd7dbrK7k/Ma3npLnKYUMCOshzXjzk6/w+L3H7J+c\\n0UthJS0Y11NEyHh+vse11S1KM2ar02M6umA8qZi1GopEsZx4ep2MMsBK0WY4nvD4aJ97d24zfHZO\\n01QxxoOaRGsI8qq7D9S2QnpP1o8Z9MJ5BAKZ54jGgndYAeLq+C+Fi10XGhqHVy5S/YUhCI8Q4KTF\\nOo0RlsY6gtaxi9KO4GP4fPA1Upmoo4zZI6AMRsbTifMlmpwqgGgq8AJpFMYLRHAs7Yw0yWOGvXN4\\n14Cv8MRZm7MeruhRTbCEq65auAi+ED5S7H2QJEAj6mg88A6kRVhLEBYoUNR4kaGdR9DQ+CoWPg9G\\nqJhOoPSVYL9Ch9i3VMGjZEXQEllWSBlJUEmSIOoKbzUGweIKoKO1RkmHbZbkLnZ/naKAxtKSgqZa\\n0pIRHi0bR1IZGmrs9JLxpKS6dgtpdOzYvaeXdqGcMPOO/81JsvmI+WhBlhX0+u14IpCCYB1Ft8/5\\n+Yz5HFaSHq0CFvNAfzOh71bY2NhgtBixngz48OFH3N66xWg24cPHz7l57SYm8UhjyJYKgactMo6O\\nzyiUZP/4jGcPn2PySHeazo/xMiBUzWI4x6rA2WTEcDpl+3oBqoXyR3TNOqWoaQmFEZKi22axWPDy\\nxhY7T57R3lxHdwtaC0HIJZPTEemqppso9ocjbq5dJ0kl+2cX3Lt7k1ba4uT0EE3Kyfic3toKzgZa\\nqwV9JPbZQ0QPNrZ7H7t2/X4L6okQYjuEcCSE2AZOr64fAN+tgr1xde3g6v7vvf49bwHIGs/UX7CS\\nrjNzNV5YGpeglCRNoKFgWpbYKxBwCB5XO4IR5EqymM5JihaaJHap1Zy26TKvomRnxSfMrSUIRVlX\\nrPf7VMspe09rJrOKlQKqqsGWS7xSvPrigMVozPO9c1Z6HWazIVm+TZJLvrU35t6mxJg1ympOvRTk\\nPcEgy1lWE2Y+YdBf5a2395l4w+OjCV+4d43D85qVFaitoJcLvvyN50hbsr5qyPM+p2cjslbNR6ND\\nbna6PDk+5TP3rzMazzmdV9xZ7yBDSpZlzGYLaqBYWWEyHoELXOutcnK6RAdP0BLnAtI2lFlCEgQ4\\nS4MlVQYbKlr9Dt46dv7if0sYPiYQodhNE7swRYULGictTinaPqWySxosRiq8CAirCLKK7FMiUi6E\\nQJYGXBOwTYSsCO3ACoRy2JCgmuaqC61xWmKIIv3gBCiJVAHnKiQpXqkYKWcBmSN0hWwUDTZ2p1Qg\\nPDItsLYmOIX0HidrdANWaERoECbga48MkqRlcNOGRWnJC4PzEqkswYH1DnKJki2ca5Be0FhLCHOE\\nTMnTgsn8FCMKvLcYadCEKPXy0Lg45zRZjnMV3jZkSUYInsViSSvJcDJHSRutwTrQVCUQnWtCSdCK\\nUMfAPtwCKVK0VNTCIpxCeUdIU175u3+fiXOoRBOkZ7lcssDycFoyTnMSA6ksMMbQLjpYH8iFRAVP\\nlhRMxzOkalEZhwZsuuT8omJto8M33/4GdelpihZ3BgOqqsIqR9XUPNvZ4d6tqJ92ynFxec5L119g\\nunNEZ3OVs4MRnW6Pqqp4eHiMdoFPvfkqYv+Ub+485nOvvcBWf42Lo/cQNZyeHVHkLSobmE+nzMqG\\n29cGnJ5esNrNmA8nXL+9TULC5f6M9Vs9Hj/b5YUb97icX6J1m2p8wjJZMi0Ds3rB02fwhc9+kuZw\\njpeG/nYXLSuODye0ioLT53sYY7DWRkvzx7z9fgvqrwB/BvgrV2//0Xdd/yUhxH9DXErdB756tZSa\\nCCE+T1xK/QzwCx/nH4qzTkUhYmsvmkAiWygjsAEqXxFkJBs1TUNuMqQMaBX92VUT4RQGy7xe0BE5\\nWhm0TElMgyolVgowEiUXdJMWk3mNrD0tZbl9r8fkskElJdlmj6U17Dw94Ua/R6drcLVnY3WD8+GQ\\ntGhDU+KqlKk9J1lvc1w1XKsNF8saKT3rbcHmYJNqY8HZRUnwBucVo3LC/l7Fg60N5lXN/v6QVm5Z\\nH9xgdHJGXhjOlobtQvHk7BxXLjjY9+i0zYsbOZlKmdolh4djLidH3HrxVcrzU0RmUMowqefIrKCa\\nWtqVQARQmURKkE5QNh5VO5wNKCnprW/EscnFBa46Q88WVEpQlZZUGBoTqfay9nitmNk65tqjovMI\\nj0UjXI01GoRAGyhrh8UgnAdhCWlOqGuUySnrOalSKKWpAbxDKIv1CkILlVdUyyiuN0JThyWqmxHG\\nNSIJSGVRzlLVXP0xLBBCxVSBqkLWMc3TKIEqLWUm0DLgrYK6wVpHoTKWF5cEn1F4hV/O4xFcQEND\\nS2bMlxWiafCqQgSF1xli9RpyuESUDmMytHUI1cZXU5Z1SZKmWEI8IbkGv6hQCqgdVYg5Wy2R42xD\\nIhus8ghbEWyC0inCWSpbo9HYpgEfUBKEF1gsTjUYZ1jKkixoPI7VrS2WZyd4xO9638fzBV/yFmdr\\njk/PaWVtSlXTavfQaEJwNFqTGslMLOi0r/Nw9xkdmbJWZEiRUtaG69fucXSyz3ZvlfPRIVQB3e9z\\nffsa5XzGfFYh8KRes9rp83zvITJN2Ns95Pr2DWo/YTGCz7/+Bro9py0t2xvrzHePcY1l7+kRWzc3\\neXpwRD2r+aM/+FmG8wnSQDZvKMuaPE3JB7dpLg8wnQQ7KmkPEi7OTlhZ7TKrF0jlkXnB7bv3qGRD\\nM51RpBkr3ZRHh0/xmcF4w97uCTe3b+CrM87PhohM4hcB7z3Vx6+nH0s29cvAl4GXhRD7Qoh//6qQ\\n/rgQ4hHwx67eJ4TwHvA/Au8Dvw78uRDCdx7OfwL8beAx8ISPtZCKr+ZWRL+ztRZldCygtkGrFK1y\\nMm1oQpyhSgkyCASgZIIWBqUkykTeoxc1GEfdzPCNZBEq5r5iOZ8xvywZjS7IpObGVptBt41dVsyW\\nJaIWUDbkboyWHfaOzxl0uyRa8JWnZxSZp2syZpUjNZJX71Zw3TAAACAASURBVN0iTQ0bKTx8fsnx\\ncE4hJZVVfPXDJzw7qxjNp6z0++ydXfBjn7vPWpIRtCekORjLtRXJjdUuSS9l/3LIfFaStVLW231+\\n9M0H3Lm9TtIxWJEwmU9IFQxW2hS9G6RVQ5pJEgKKwCAfEGobZ55FgUyjUwgkVga63S5ZkqJU7PL7\\nW7eRKsW7Ci7ncS7oYiBc0A6sQlrPldMWIzKkF2RX14TQaAJeS4SMHIamsuhagnBUokGTYqsl0gls\\nPY8M2xBY1BXC26ghdYals8CC5XJOJjUqkQRfkxYJqg64xBAaiVw2lEuLo6JpKlIhCNUc4T1JGZBX\\n1Hppa3SvQy5ANgITBFnaIzNRlaBlhpQeq5uoTVUChEO5nDqAdLD0C4STeOdQ1QJ1tI9vZthqgaga\\ngrVYO4mgbpPF/8wWVL1EKo2QNo4IsoJcJXH2mgjQjppFXFbJgNIC5xq8Bp2kOG8JQiCkx+NxMsEY\\nE8cmCpTXlKJhXpeMJxOkNHFW24DRkrETXGiDwLC6tkGRGnqDHomRIAOVr1EBRuWcJEl49OQZuUzI\\ne222bl8DJTk7PObxziPWei2ePn1IM1/y5pufwZcz9veOSRLPxsYGQTrmTYXHYrIeaxvbLAKsbHTZ\\nHS1ZujFf++pvU16O0LLDaDrkJ37iB6nqKUXLMB5NmI7OefP163z49Bmurigyxfe9/jLWN1R2wfB0\\nj/HwAj+JLpRaQb+7RkvmlGUMw1zOp+wcPkeVnl6rzeb6Knfu3KMjDHZaEpxnq7/KdHzGxvW42jk/\\nG6NkhCcFPr4O9eNs+X/6X/GhH/tXfP7PAT/3L7n+FvD6x35k/+ILCQsBrYBE4Zv4BxmSaGdMZcK0\\ntCgh43HoiodZB8hEQ+khEYLz0Yz19RYORYLANhDUjEInICpoZYxHc65fW0WHhIvhkFLMWFYJLjha\\nvkDIKb3uOkdTR6edsSgbJk3N6y8OuLPWJ9UpUp7Q2CWTyzFv75+z1St4cG+T33n3Ed1um8W85vtf\\n3mB3qDEtx3g0QeDZ3z8n67dYX9vkybPnvH57lUGnR5AVqoRrvQ4iKTg7HbK9sYrVgeVckIY2ZX3E\\njes5ZzPD5ThGg+wMZ+TG0CoSZDnGpUtGl5BJhUQhrMcnDiUhCQbvoQ6OoCIDMjVtCBGpZ6SJCyFV\\nIfXVAkg4nE7JABsqXGhoZznz2qJ9QBhDKEu0klTOx9hnbTCJpgoVEkmQMQnAG42wAWEUjQ2kQoBJ\\n8Q4WPpC2CqwVKJ3jnYc6IJKUamGREhIULpU0yyUiCDLVovIlS+uRxJgSlwq8SEi8xznHcj5Bp4qg\\nBSDxdo5tHIlOcCpEyyjgXUMlRIxOSYjjASFIpQaXohJLEySZkCx8g78ib/k6Jsj6bk6/nTAdXkYY\\nSlrgqxIZErz0qCjRRyUpQggUbdARuoI0WBxBE39nUhFUIE81dR2XYxWSQBwteK0x2scTmVQs6wYv\\nPTJoghJxPq8Nzlc40dDMa27fvcXOzlPm4wlN00SpGxZX+chZDSVStjk7OWNyesnmRpe7d++yv/ec\\nJM9IM83WzQ3OFyfMhxM++ep1svYmo8kIak0rMSyWNasbKxwcD7l7+xbvfvSM21u3WFTPMVry4QcH\\nPMzPMUnB6PyIQXeD0XjG0fE5650V3v1oj05WcNxUXL95i28/fw9fKw4uZ1wb5FgfOajzxZhWltPk\\nOY6KqpxzOXX4RLK1uklILbZOCEqyu/+MZx894+UHL2GXDaPZJf1Bi6WrWNoa6yVV2dBf63Nwevax\\ny9UfeqeUDx6VQwgSowztjR5nzyoSHWVUNnjKasasCogQXTvIEIPuakeRKbyFLDWR8KMjiepTL25x\\nsn+Cswu0yDlflKxlmuF5xUpeQ9plLbN88/EF3/9CRu0W5Emb0gr6KWgLys/Z7q7R6eYs5hV70xky\\nMbS7PfrdDrfGF9y9ucWj3X3ur+eU80BRFOgkZ61nWTYZ7SSFVFN0Cu4Ugtl8xL2NDcYC+nnO2x8c\\nYJAUhWGQWlorAz58fsb9G5vMq5rVNZg0ks2miOFsSU4iHWDpdg2TRWA2XTAalRghsUqjXdTwElNO\\n8HgG7SJqRJ1FIKHTwgkHg2uM3ZRea40mxLRRlSiklOBgXE3J2wJlC6ZeYHLNsrLkGGSomVETUk2L\\nNstlg0sU0i0RaRo7rQYWlIQrsT6JxC0rfJJTY0l1i+FiTNZKsCUUec4oXJJOU5JCMZtWlLomNwXt\\nokedQOuFm2AU5emY+dkp7fUVqBvq2iGDJF3JCI1mGiIoPBUtrJQUrYRgNNVCYNIarwukEJS2Ikk1\\n1VKQdDXCJIxHp6z01mkECJ1RJYamXiLyeCx2AqaiJDc5lUoItsbmAu0NRmu80aTm6nkElCnwriLP\\n4zhLpTmzyZRut8syeERTYYwhyQo0AedrlMywkmitpaFcLGl3O5TTJY13TOuKPIujhul8hvagnSAV\\ngiUGJxUfffgeSWaQ0iCEIJExDLPbaSON48XBOnmes/OoIhjHaDRio2hzcnJCJ8948Po9sJrh5YTX\\nPvkCezsXZONdZLdNrz9gvhiy3h+w83yfy0WFtQ1BeL7vjdc5PBtyt1tw78VtfuU3v0zbOtbWvo+9\\n431Gswm3NraZ2TF31tY4PRzR0oJqesLxyZys3SPUEt942kkBUpCrHiE07B084+btDTpyFbUcU7kc\\n5yx3Nm/y7HCXZzsX3Nxa48Ebr+OWl4zrks7KJl4u2X1/F50VjOYNqtfGBBUjeT7m7Q99QZVXrEpX\\nNYREMR1PMYmnSgRSZNSyRssWSeoJ8hwlBNIYhIe2TvCIq6DKGu0hUYKqdBwcjFC2oqzjprZcNkyL\\ngvpySNHeZvdiiD+v+eEHcZZ4tliQZ4bV3PP8LNAsG25tDpgtZywvGrrtFKkEX3hjE1153t87Znul\\ny8H5JctQ84kbm5xMlmwMcqazBauDAdZJjs/PcHPJWYiRG37m2RkfkxSKtByw1W1DYhFV4PBsyfWN\\nVW6teZJu4HRvTnsepUpWB2aXc6oQWO8PePvJhIcHJ7wwKLi5tUoyadi/HCNEDyUcUuroeiLaDVNt\\nSETAOYnIEtrtNpdC8eCv/ud4qiiSR1xl1Nc4IUlCINobAREITY1MUpwPqCvJVQgCd9XtKWFR3kQh\\nOw4fTFzQOIvQUUsZiNrPulxCEkuFI8ah+OBACKwXeLeknbaovYcQ/fFRNyowmcLbhsprEuFwXmBE\\nTIP6zguucDYuwJxFZQnBS7wUiGDBgycmFoggkd7iZIKU9kqqKkASl2TCITx4GSVKEsG8qmknkkbK\\nfyHgFwq8vZKERSH/d8wAEYIOwTmUUhipcS5QFClBGXJvEaKFcwGhFF5YhEsjwd95ggwEqWi1C5xt\\nkKkiRUHwBMAITbvbJSsEg4nn81nG/15VKNVQh0Bdxs40hMhoVUoxLx1Z6flw/xErnTVW2jk4T1Bz\\nzuc1n/zUJzg9nlM+PyFtJ/T6XS6GDXkn4+zklLCYICtPa3uDspxz4+YW00dPudHv8O7hAf/wV36F\\nB7evc//eDZ6f7DJIW+jC89v//MsMlzVaWM7sGa+8cJ16WlOZlNGwJssNukiYlyXdfpvzuqKXwUan\\nR7KSMJ6NWPUrlDOBShTTpUSUc9Jeyul4gSens5rRafeQeHYvRrTTDvPFJa2sz4PXX2b/+Aw3WnJz\\nbYvlco4u0o9fr/6A698f+E0QLXoyF/RaLZz3jCvPfB7Z8G2p0KqmaioEirJu4uxOxEKhgo1LCJnh\\nlMKhMSrh4PKCSjoaJN2WpqksSilefKHH2cUlb2xnrGnLe8eOg/MZsvY8uqiovKW2jvtbGc+Ox2wM\\n+qyvFTy/mPPh7rMYA2wSVvOM0+GSL/zo91PNPePScu3WC+wenaHynEe75/zTtx9zdj7HsaQsLacn\\nxwQqrm30uLFxjS/tnPLN/VPe3R3x5Hif03mg9Ja3T86ZXUChE54dXVJkil6ScnP7Orf7G1yOl/S0\\nJ0kVd64NKMc1HWm5NriGFBYlA9a7mCuPRwdFuaioqylBSZqmZtaUka0JaJnDVaELeLRKMYJIi5IS\\nKwJSJYg8x/lAUBZBQsDghEIRTw7OJ4TgKasKHRRCBhIpcEIAHiG/07E5MDEoL4QI/HfeErxE+kj0\\nbyWtaC5Q8Wu8AqRACeJ8XScYLLWTKCGpkXgp0Voi0KAytAkoo3HeR4IZNsbm+BgEGGn2Hm/i8yRl\\nJFcJSXRuXellg9JYKXEhQqbzNKUJAuElwsdIbC0iq9crgxCSoCVBKIyM6mktDFobgo/Pvwe0NkgE\\nQsffgzEmyr2CRqAwRpN8J0NLC/yV08qLqMEOCLy4yvhC48qS4Bs+N+hjgsA1Fa9+8hM0zZKyLFFS\\nEoIgM5IsaPrra9zc3Cbt5qwM1rksG2al4IOnH/Lw8SFJpmgNtlhf3+Tczjk7O+OF2y/SXu3Sa60g\\n223c1CESze7hCT4tmBHot1b55P1NTs6HPNo9pCkDn/3ca3Q7q7R7bTYK2EglRknefnbC2o1rXE7G\\nbG9kXM5mBDTj+YLD8ZSttTX6/R6HJ+d88xvvsLxc0LgAzvPo8T5rvYJiTZGkbcblHGmgXCwYz0eM\\nZiXDizmnFxcUKqe2I07PLtk5v6TdHzCZDjGZZHw5/dj16g99QUXENlqRMqqWpDrQMoZuVwMZSimC\\n1yilohRIx7dWKAwaJzQhNCzKBXmmI35NGSQF/ZUNah/YPb1ABMtstkCHjJdvtDk59VhZYBenfDRq\\n2J81fGKg2TmoEVLy1v4592+nPLsYcbGYcHo25LOvXyPRiuA8WgVu3Rjwd/72rzLoZQQlOTjYodvt\\nstUraKc5qx3NUgiMzAmN5+b2gHwlYzS1LKoFy6aiZTQ/+NJ1XnvxBbqpRsiKN2+uIFJB5QS9QU6/\\n22Hn4JLj0zOe7R0ymlSExvHDr9xlVgreOz7n3f0hS1FibaAJIi7opLpifmrK0YJUZsQ5gKLorMR4\\nlLqK8h8hwEfNYyzGUSHQBB/niTiED2gjUMEQrrpgBQgZTRX6qiNL04zaexQCgo8powiUFHAl6U+1\\nifdFnHEGpXA64BAEa3F4pBD40MQY6GAjUNo7muCpahvBK1pghUcJj3QB7wRCS0rXgEgJSiGEBmvh\\nSgjvUSgVu2oTiMmX0uGdiHlkQUbBvwiRYCUCtilBeEIIeN/glQAVn+sQfFxgCQihIkhBIiRSgXCR\\nFWv9krgiBKVjtlT8eWI0t5T6dw0LSimCgNpHK20iwTc+fh6eDINIox1VhghRF86jZSDPJFyccAtH\\ncJ5vv/0ORrfotjsoFEWicFaC8pydnXF0OaeeV+wcPKfo5QQtee3BK8i8zcVswWQ+5vDgmHApmM8m\\nfP3hQ8qFwOQJtllwcvacpa0IwVEva/6NP/GThMUJrc4Kd1/Y4sUX7/Dyyze5PBuRZz1YzBmNZ3z2\\nBz7PjdUNnFtwtrPDoN3h/psv0Wp1WJQNSTvj5sY6o/Mpw/Gcw7Mpt1+6Q9pOYmw4gVdevsl0UtHv\\nrLFYlKRe0FQ1vaLN2cUYgKmvee3WTbr9jE7RQfcKXnjtVYqiIMtb/PgX/wjf/7nPfOxy9Ye+oHqi\\nNtOZOj7YRqFCggwZ8+oSKRJMlhOiziZuoYWgMFHgbbTHyAKTdXBWRGSdlCSpZGPQ5VZbcmerQyMC\\nd661Ec5yfKlIUhgYzSvXB3zx5Vt0dOB8UpMXktvrin6vw7PzhnZeUNWCJtEkbgWCYTSd8c7DE955\\nMuZHPnGNjZahaPfp5zmbA83Epryzd8x2d420aLH0NXfvb0ENZ0dzZPC8+3TEdqF44/YqjpQPn4/Z\\nWuswnVnOLhYcHJywvdam0855+vyIg+MxR0dj8s46z0/PObpc8BvffsTBtGQtz6h04ODkjEQltPMi\\nQmUCRIm7ZW93Hyc9QWikbjgfjtA+2jwDMsJNXABXAly5fxwhQIOLR9cgIEgkGtQVlEMIBBnagQ8V\\nznuckHgkrlrinMN5SxUUwXqsF4QQXU0Qi5+Kp3R0ECD0VbGzEESEfkiFktHtJpAEr0lEZIASQrTD\\n+iYyAbTEupq2UmTBo0JA4HBXCQUxIMSBt3gB9ZVFmaBY2BohPErGn5dgEc4CgTzNidsjjboKy/NN\\niPHZMuAMcbH1XVM2GcArR+kDkhSkQUgLwcTlnwukUiN8wIVA8IqABG9xAaSIlKzag9AC6SOPoBYB\\n01hcxABcdfCBpGpIvSKT8EUjMcZw79Z9bKiwLrB98wbeg8kMSy9ohKLINCYTFK0WOrSo50suLyec\\nDsfUziHTjMPRkveenHDiHI92DthY7bJ/csHZ2ZT22jbUmju3H9BLBP/k1/4Pbt9+gNcZKumw8+SQ\\nt956n4PTISsrXWzw3L93n9I1fPJTr/Hq9ZsU3Q62XPL84S5NHUjbOb72mKxFo0pm1rO6uRaB0es3\\nsLLi9Pw0jleM5vnBKfmgYFrPUQHe29/heLTg4ZNd2q0V2qvXuSxrvvzeU07GNUdP96Cp+OIXf5it\\n/jau/FgcJ+D/BzNUIKaKlhpVCCpbkWSKYANJu8ViNqGqHV5ebUKFwBgTY3aVwdoGKxqc93GLKnNc\\nkHgPX/36u3SywMUYgjK8tN5jaBuO9vdoLS7I17d5+vyC1c6V5bXI6CvPam+Dcr5D7T2+mqDTPt6P\\nGM3mvP94xoOe4IUHt/HzIWhFd7DJtx6dEsKU4Ycz7t0as97WlNWE9U7KxSgwHU6RuWDDtGmWJcms\\n4d61DlmR8/xsykrX8MHROV0lUdpicsF0VjOcVgxWUs7nlmurK/83d28aZFl6l3f+3u2cc/fcl6qs\\nyqrqrupd3a0dLQixyBgNGDwYCMw6BgYjAwbbmjADYYbQjCewgxmCAGHA2BhjPMziCbNDS0JCW2tp\\ndbe6u6prr6zKrFzvvXm3s7zbfDgJEfNloj/IEYo5n0/kvXnvjee87/t/nt/D9b19vJDMdwyZEgQ3\\n4+rE4nBsnt1kZiMx1Gd6QtaZ+iA0+7sHNJBYAVZ0SFSTGANRBfCRKAwmTQhB4nyOjgovUrRyOCvq\\ndmZxEncUARUFIdZlhfVr5UQERpm6Y0kppEgpgyMRCh0tVYhINFFEYtRIX4KKxNQgyvpMNgiBiAav\\n6geCtw6jAK9AgJf1qtaHihAlHocrBI1EYYNEeFAkNRIQXyP3fKSpBLPCUlKhVIZ1Fd6erHCdRShD\\nO20RQ0UZBEaBjwajJF4Jog8kOlIFQNdeXPXX7IJIDSHyyBBwUYJSSAJSGIyEkoDwjsqFk2guKCFJ\\nU0P5V6tn4UCeQGKiQIvIpCxJMwNlgWq2cGUdZAgyqdkENv710Y0IEedmyGhYVHVFztHxIYF6aOvL\\ngjPnLzA46tM/HOBiRQiBxx66wGwyZdDfpiIw7E9Y3lik3Zvn+p0tVEsTSZE+I222+My9YxCG+VPn\\n6AfLxsIyNw8PMCsL7MYJxUAwu3OLTmcV0xK4UlDJlMG1m3gvGR8P2PrCIT5RuImnRYJcXuLqcUkZ\\nFEXh6M21GFWW2OohvGRgHYfVhGs3pqSA73Z4eaePF5aRhdn+kNJr2gl055ZrAI4D0cz48Isvk2hJ\\n0ukwnVS0Wgs4F/jsn38cKQVWFK9dq77U4velvqQQSBWZVRpTOhpZm8IWNBOP8orcNejoCYWf1ZXT\\nQtNIMhyKYCdI2cQGSzNtobXG2Zx2Ns+omCJNh/uDo5PpJtw6OGBvFLlwpk2XdW73DyhlSm/jQb7h\\nDYv83h98nEefWON6f8xgFtCZxwTD9HiIDHA8HhFLz43tI16/ssY0NWSp4uq9bVBNnn70Ae5cO6Sd\\ntbi2fYOnu236o2OKkDEsChbXV7ly+QZWNrn02CP0b91gEAK7R2N6vZTNuQ7OKC5uLFCNc+4cjWkk\\nkmZ7FT++z+du7RJ1Bu0m/9W3fhPTGBgPjzhVHFOME7bv7aPijPMXlzEiYKPAKI0JkcOtLdajxvkK\\n6wqESepOqZN2zTqAWuGjQqgMR1VTnbxHKYNHYfCEvzL0B4+MINA4XxDqJRpW1MxRYqiHLFoSCg+6\\nHh5aKowzQAkKsJEo6r4oX1NXa2anSAkx0s4SCufrs0YBeBBSMMkLTJrgJWjhqIJBCF/HkhFYXxB1\\nvSMRKiG3NZfAVAJvIl7IEzZBQKgUhKMKdVVJpjQxqnraHkPtRJGGynmEjPV7pq6/FkqCE0iha0i2\\nVihRV7U46pbTUupaBKPFmKT+3ES9Ui/LkihrSIokYARUKta4RRRpVtOuohS40tV0rKhAKdra1Ll+\\nUdURX0AUAqsLfJQ8dvGNfMcP/rcU5ZRf/sVfIFRQFZbmXJdiMiZNm0iv2dkb1iStboukMAitGB5P\\n6Y9zZBS4UiGSjKbMalhNYiEIprZmF2/t7WJMSikUdjIjbc/RSiJRSmwVEZ0uqU+YBUdzaQU3KxBC\\nkCmNTQt02kVLQ7dp6M0n7G2PmRVTsqzJuMxJ0pRGGfBpRGvNdJqTSoE3BYlosKygmszq5oDpDJNa\\njGwj3Jg2Tc6+/mne8c43c/PWZaZ5oKkMG2sLFN7SbHSJoeD3P/TJ16RXX/aCChClJO06fBRYXaKs\\nx7pIiJEs8fQnHlfV2LigNcPJFCU0aSNDpynaNGmatKYdaYV1eZ2fFoKzZx5iaXWe23eOsCtdqnDM\\nvWEf0Qycufg4b7u0QT4OXD0KxIU1nr1j2Lq/T6kLeqzgywKRSbKuZj9oQseStFe4OoNilnBrPCOK\\nNlLCp64dEqIiHE2ZX1rn6pFFdttMfeRg5GjMhpQYOkmDl27cxqRdzGRGmRgmASZeUBSRe5d30DNL\\nnhik8ITiiDRt0mt1iN6yttzmzz76KfzJlp5oKT1IkWISydOXHqwHSV7XNqpgidMZVpRIFchOMHhW\\nloioCVKB90gnQHqkrzmkEUkVIkbViaPoItYGjHAEAtYoRCxRGISosEKio6y/g2ARwiOihETgXKgb\\nSrWg7qj1EFOclEgC3ldIJeoaa2pHgFaRsqqhHjK4k6BCXWQn0xoKIoM8geUUSJEQo8XKBClTlC+J\\nylDZoi6/kxJh6goWQw3Fdiqi7RTrJZmpV95KC/CSihrWrDAoEUCLeogmVU3n8QLvfV0cGC3S1/cK\\n7/AqqbfushZKpZsEV9SAElchtcDZ2i0hS4erSoyGMjaJQqFFXSwoJ8e88mM/TsdDFJFrAt75wQ8i\\npSTPPUkjpZhWKKFwAVRD04iaQgZuXLlCu9OskXlBotMMLRVpq8mZtU2sm1ERePqxx/jCCy/STOoi\\nzFBUeP1XjglJq9NGu8jf/tZvIR8fUlmLNpFms81wlKNNxCK59so9rl69SuGgYTISoyBEMtXgDe9+\\nAxSe+aV5rl6+Qm++x9LcPCaJ5KMZXkuUcIz3xzx4yhNNwsapUwwPhmTdhEmRM9eaoypyWp0OW9dv\\nYXpzUB7j8IwOZ6yun6J0lv7hNqbRZnF+mel0xPz8Anevf5GXP/eXPPH0OyhdYDqZcOvuTVppi40z\\nZ1+zVn35C2qENGrGoymNVgNZCHSaMh0f0eg2GJdV7edLFDrIeroZDGk7pdFsk2qFMHUHe/T15NSh\\nQFQkRnPpdJvCN3jLxTMoZVhemaOpLjCaOpIoUFbQTRUjBe940xPkpeGhmLO+2OPeQU6aNRgXR9za\\nnzDY2+NvveWtjGZHfOaFe3z9O9/MLESC1jRji+dv3ub4cA8vHQUK0xbMvKPdblB4i5KCtLcA0ZEl\\nXUIIBG1Zba8zHuckSZ2z9k5CFkndtN7OJRIdwMeA13McDitMw9S2KFuBNEilEEFgUPzFs8/xxNu/\\nmSBrg7oXkeBN/dlFj6s0MaiaNC8koqrReZ5I7ZSS2CgopgNajTZaaZy3YCPXf+BHSMKUyuW87t/8\\nL+Ttc8zKgAaEi6jEULgSFSRR1EwBHWvE4q0f/zn89DLaKYZPP8Abf/RnETojnPhmQ6x75qUUxBBw\\ndZ8DGlhttPnYt38LPuSkqoVtRZ74td/m6u17VD/zs2S+ILcO1VBc+nf/AQBPQrQOgUBQEoKuy/SQ\\nCOqKGBUkV//BP0RO9lAucu+BM7zrf/rFGhBDwHiD045ILaAhCrwI6ChqkZUST0DYOrkXdcQJgQge\\nbSQxpLUxzNu6TDpYtDFYEUkM/Kd/+nM8ff0VPAW5hod/+/dA1mfaH/uJH2Nt+zaiKnAqIRI4K2D3\\ne7+fi7/xq+SNDlVeglJ4GzEBtI0UBpSoe+rbaQOnDHmZ0/Aer8CkCaPRMc1Gynd/yzcwnByz0H0D\\nvaV5jE74o//8h7zrXe/CecH+1h3aix2EEJTHu7hyysrmeXbv3sJVnmJW0uvOM+wfsHP7KtghvbSH\\nK8f819/2dyjyEbv9PowPUSblEx/7JG9549u5de0aOzeu8J3f+3d55plnGB9XtLs9YnXA6x97J//y\\ng/+CR848whve/CZs5YnTCZ/6/Of4xm/+O9y4fpNiWtBd9owHnlxYdra2aPWWaDYzltY20Fngxiu3\\nuHXlRd793vcy15jnbW95D/u727zpK9/Jy688x5OvezNlPmHncOc1y9WXvaDaECmDYK7dxiYwOMpZ\\nb7UwzQ7R+XpV6j0VtUkdFUhSTW4dSZyB7J7k1SUy8VgCKih6vQ6RiiK0yYSl22xwHDRtmWF9xQOn\\nelzdOuTCUpujY0crDVg3wnpBmmZsDUfkxRSTCLbvHvLkI+uweYpz55eYTbt0mm3mlxcwRcX24ZD5\\nFcHbnzrPvfstsmaXz77wInj49q9/B/3BGC0zDg6OefThM4xmBa1EMnUSU46ZBMHGximq0ZRpBa1M\\nMZgM+cxzW8zKHKMiD1w4S6fVYtI/4Pa9+7z7HW+kioZyOiPRgkarzc2tO3Q6Pcajfm3LEXWnkxYS\\nEz1ROGJU9bQEajENFZ/9wM/TygTCOy6977ugcx4RK7K0U/NDfT0k0QTibEzljlFCUjiHtKGGOgdZ\\nr0rdDEQNL1FRkZq61loKQ5EfkeaRqEqsAy/qKKoUNc9ZOgAAIABJREFUtT1JBI9W4GJttypDPPGX\\ngi8rVD7ByEgVjpmFOWZFxfnTq1yWBdEKGsJTqZRgQep66i5VvZ1OVVa/lkzBWXyMyCAQ0iNHFT6U\\nqCBIVIsg6w23IRJ0hVJJvaVWDhnqFXWMkulsQrPXwVf1+/ayFugsRkpR30Pwf10oGZWs01GxnspL\\no7CDXVzMoSpJREYItVOgrRWn7m3VXfRInHCIKAgyMnFTXvjhv8eFf/0fSHWCjwKReaDEC0ckEL3k\\n6aefxiGQsnZupMbgk4hJUwgBO5kgG4rV+TMM9/ssLXb5xJ/+Gd/7Xd9NmrR47oufoN1p8NKLL/L2\\nN76FfDxi7+42Xik+/Kd/yNe85+toq8AXPvlRZkXOYf8QIzSve/tDzAYVo/19PvkXz/D1b38nn3v+\\nk5xeOc1j7R69+9uU117g/OZZ/vKXf4lyPGNlpctgv6IlPH/x8hWWcsHpluXqp59hzrQ4OBpTDg95\\n6f/4j1iXo7ykf/dFBiXc2rrLxoVLHF3+JLdHJbN8wsr8KiZmnMs0N//8Iyyuttk7GtJE8KEr11ha\\naXH3zk0ee+JRrrzw/GvWqy97QdUnPrrSajpJil5IKMocHRO0iRjn8SIirCdSE8qrqqDRnOd4OMGs\\ntJFRUVRTjK+h1N1Om2/++nexd/cW66unuLt3i6VTp2hMjpGlpLexQn93ykNn2khnicKw1JSUXpAo\\nh1CSxGve8sAZjgZjXhjl3Llyl92R46B/l65qcWZ9GeU9q92EWC4QZwOCWmRn6w7zyyt86998F/O9\\nhGu3+zSTBka06G1q/LEnVmNmlWZxfZ3+fsnpbo/jrT323YR21qCVLXB+scvKVz6Ms4IXrtzg3GqP\\nwXRGdBXf+N634SeeYX+Pi5ubfP6Fq1y60GJhoc3CXIOqPDkfFR4VNVZEdG5PuI8VmICPEYUjBk33\\nyhWUG2GDxU7fS2yePxEycSJG+mTrKyBUaF/hZJOk2eHYVzRkveISQlCGGlttkAgZqGx1sh4MKOeI\\nLiKcYG5pEYXCibpdQTgHBJKkgS8dpUiQ0uIBHNy7t4VoptgiR5a1LSBBIqOkXXgqWyASiQyqBlyH\\nmrVtFHXktSrxKFIZ/9oJEkUkFA6hBW4YkFnEtFOSCFZGvFegwPgIRJCKKGPtNoieZrNJrBwSSVAS\\nYUGYglIkpMpQOlvbpERdrue8QxqFCB4l6y11Mc6JTkMsTo40QJDwib/7nTSKGYnQ+ChIosNG8D6g\\niFAYPv0rv8RX/PhPgPfEQqJ0BtLTCB4Swft/6p9yv3+MlYFgC3JbEQYVpj/hm975CNvTio//7u+w\\nfbTPOA+cW1nmsTe9mX/+T97PI5fO42JCuxWx+xMObj5PNZiglOVjH3mGSw+fZvTCi/juHKc70M8n\\nXDs4Itu4wMLCInZ2yPxim9dtzPH7f/h/8/pHH6WaTZiMxohRyf3dnKY5oD+Y0JGwvw+HwzFnlue4\\nvbPDk1/5bm5//nlEo4Fc6GLDjKzd4iNffIUnHnqI4XTMSzfu08gUw5FjfO0VJpXi8YvnKGaRT+28\\nwoOnN5ib0+zd2sGYZexoRtnusHt4H9M4xd7tXW7dvIfSyWvXqy+5An6JL0GA6Ok1DAM3pZ22sdOA\\nTwNFGYAmIVi8UlhrUUKDsEwnRxgtmR4cMqX27vkYIAhWzvX52B/8IZ1UMLp1jSSd46XLz5DMFXTU\\nPM9+dERwFctnF6mmiulwRC5yQlEngWbFFCcSPh0CLZ3QaUAYNlgUY5449QBbV++z27/P7myGyz1J\\nIvFe4JJFNh/eZDx03L33MqPtOc42G+TFMV947gusnl2lzYSptUwP+ti1DUw84rM7Yy7OL9Hfv0+2\\nukRcOcXz94946cYVHjpzlsXWPH/+f/0Jb336LK++cJ3JwQ5X7/Vpmkjy2A6DF7e5ee8yPluiH0fs\\nD/vI7/iB2pQvA9ILtBBEHZCVR4WExGQ4UrQE5S1VlaOkJO3OMYsVMYBOAsF5QpA1yk4phKkhyGlL\\nMhnnZM352iOp6sFMYgTBG+TJFF5LhQ8CjUNp6uI8Dbrdgbo9CoEnSkUUimlRnqR/aosTUVOpwGw4\\nAQIJDSo5QzZNvTMJEWdzgvKoKhI7YBKBQGNMPYUn1vYsLXMqFxHUFdplUZAlCZ6SJElqSHOrU5v2\\nUbURHlc7C1wkCEEaNbNoMVITTlaOOniCkwQZCF4iELjoqEqHSSQIiRCSJCiCc0hqlmwgkAiNZIKT\\nNfMUoZHC0bMFMdGoUK/YXdQn6a6CNEgqMWP1xk0Euh7W6UgVc2S0oDMKX4FsILUlcZb9/X3avUWU\\n9DR9YOvyTeYvnufOzNJtzdHIAv3+kMuf+RyPXLxEZIYLltHAkWrJ1St3mUz2efTxp5nEHcZlzmKj\\nRf9gj1arhWy0Ob+5zGHw/NEf/QlvffL1fOzPPsTdV67wdV/9dl5+5TkWu8t0uxkvXb/M9myMvD7g\\nwsZ5vCjY6W/TWVjhqPScXlnj9stfZDbJUaJib9eBzLEOWmmT+0cH7Owe0UgE48pipeR4DIvLKfn4\\nGKUFi40emW4x27+PiJKFhSW0mjHJB2Q6JYiCXnOJLMs4GNz9/5Ko/9f1ZS+oAUHDCPLg0KaHcyUi\\n1UhvQEW09EyK+Fe7VAI1LclISYpEaosmIShPLGqA7itbY1bnMrTQlGVJM92m2zbEI03b3K+xaKli\\nd2fAtBijRIvjYoz3nqwoWD91lsnxkN1+n9BuYWea5SSQzLV49lMvEm1JlmWo4BgHTydpE5zn3tEe\\n/cs5s0nBmZUOl+YNH/7057l9dMRXPXqG/u1Xkb05Xrh+lcc2T/PilZuMx2Oefvg0z9+6TdJZZnd/\\nzPV7r1DauvPKTcY4YVjseJ795Ku86alzfOGlu7SN5KsfPsPWrbuMZM7F0w9x7cYtdJ6zm9d2mCAV\\nSayJUHE4hsrhtK8rk71HaE8qG3VCSiTMcIzHJapXYXWD1MV6uxo9yiQYWnUradT1AAuQUlAKgYoS\\nLy1JNMxiDkrXW+xKInQkRI1pb4Bq4lMNnR7GaCpf0+iNUJR4ogsgwbkKqTK8rUB4Bnt7xELgxBQl\\nan5qCCAdRC3BR3xVnx1iBYWs8E6QpGBiigseEerfjI0eIyUyrX0LMXfoYPHSYBqNOnZbc9BwQSJD\\nqPMQtqISgZonXUN8VFAIEU64/QrhIUsizkGSKqSsQTVSxZNz2TqJJUIkAG3hETpFBA+ZQjrLfMNw\\ns+YJUqlASDJOffA3OT2/wGe/5T0EJdBREI7uoYOnkmCERFPzG7wv0GnC3tEudTeqRHiFrXKazYyH\\n11dYP7fKZz93lWw95d7WAWtrG0ysp6sS7ty4xtR7zqyu0TSSa7d26LUNncV5PvLxl3hgrc3eocfZ\\nOyytLzMbTZk6KKYVcxvnGA8PGB9PeOLiaVZ0yXOv3GJy6Lh3tM+7nrxI0ljg6fNt1rpdPvPC87zt\\n6TcSGi2+ePUejz+0ztF2HzW3TExKhG+RpZpX7u5xZnWR3nybppEcpJrFRgMxnnKu12RUVQzzkqbQ\\npEmLBeXIR/eYm+9iGpbt+/cYTAvamWF9ucv97QNW1pbY2b+HDv8/8qFKAYGUltGMbYXMFCLMSLWi\\niJrj4wptE5zv1xPgutcYIQJOS0RI8ELiXYlKIEqFFk2Wm4JOq8HuGBZaTe4fH/PgQpNbW/cZKM1S\\nppE4CqVZFYKoJUWIbCz1OJoMsNazudKpUzpSo7PAaFCw2OsxKY7RlaGVCDo6EPwMHxUH4ymp0KAk\\n49GMvrvOYDLie97+JH/68l2MKlhanWOxu8Ir2/s8cfYM+ypl+2Yf3WwT8gFzq0vYwwEvbR+zSE44\\nLcj3hjx26hyHyS6D+xOU0rz+0hofeXGLwpesJAkf/vRlGAxpzM8zOdqrPblaYoNE+chs3CcLksw1\\nKaStLUtRIKWjrCqMqiVBtxKQBh08VljkCQrOBkuDvI4/OkuINTzFUQO/nXJIKyj9DCMMocoxJiMk\\nAhEEyMjmz7+/Ps+z8aRNoSSq2uZUUSB8glB1mqn2FpXopO6O8mVFUpUUGUhbnwkGGegtLONdrCvH\\njaKoZggdSaM6sUI1SGOoH84WCh2RHqaTY+Y6c+S2TkR5VXuy9EIbEUEGBzpBEQlBIEKdeAJBjBp7\\nsrp01P5nE+sclBcF43xCt9UlyARhHWWs+6ikm9UWtFiLHEh0ZoiTOrDgAwgduPmxZ6nCjMTUKEHX\\naJF1e0x8hZAGZwu0MkhfByjq6G+B9BlWlgAUaCrr6s9PeGSY0er0SGWCaipG4wHrK4IsaWIWF7i1\\nu8N0NObu0ZR3PHWBv/zCVXaGfdYX5iGBqXOsJ106c4o7wzEXN9foFxX3r92g05zj9tYWLkswt++Q\\nJSlp0mZr+xbtrMebXjfH3TsZl1/eYjSdMDnYYVAp9KUOm48/xbN3tjFJYKHXZTIuSVfWuXz7Pqvd\\nBoQK1e3SSCXddgc3K9k6PKDX7HL74IAnLj7A9tGAaQmbS3PYwiN1SW+py2hyTLAGYS2d3hwlR3Sy\\nDK8llx55mFvX7qLRrCyvA7dem179F1HBL+EVEUzJsdYS8NgKBCmV89jSkWiPVyVVqM++IKBFnZbS\\nRGKwzOyMGDQuSKKtt6cqDUQ0mdQMR8ecmltg67DPwlKLR+YbbC53Ob00z8NzXUTTUjrN2dUeRkM3\\nSchSQXthgYZpoJQk5JYsTQhVydL8AguLXYpoaWU1zi0BKmtPTO6OQMne3jEPrrT48Eu32e4fsNlt\\n4CrLK3tDNpfnOJrOKMYDqjhiOt3hzHKbhhDMzTU5s5yQN+f5pqcf4IG1Bayacm4p5cregIYpmY0H\\nvPWxdR5bmWc0GJKV8FVvfYwnN+f429/01SglEKHOwXsBHSNoZZqYQKPdwrlAqCzVtELqElt6VKkJ\\nVhKipSTgI/i8pCrGqNLSTjKknaKFIhYw1+0QqxEmegiRKDzGVqj8PkSPjR7hI1FEbKwz75UADLjo\\nEUbXaSzpkQ6YHrCuK6IdEosZBIH3FcF7du/cIyQK4+v/qSotIkoqV4GXpEktsjZqfFBUsbYKGQKW\\nOpwgpET4kkR42o1m3U5wYpNSAXSUZGmv9vBKibW+Th4VY9TOTdo4bDXGxFD/Xe9JtIRYH42I4pj+\\nM79P4/JLlJMB0Za1XzdYdKhAJzVxSuQEVzIYDEhnDoSnKnOEksSg+PPf+i1cebLLEDDRAmKFiwIX\\nAkJKvIxEfN2wikJKTRAlytU9sg0FShmiDKhoaHZ7OFsSdeDWjZsMBkNmXrJ1MMQ5R6PRIrY6KO24\\ndus23V6bnkqYa7QQMbC20Gb/aJ8YPefWN8iPc3ZGM6ZV4Oad7boXLG2DquPhN159menogL/87Av8\\nwTOf4drN+6wsd7i2tY/rLNBZbvLRF69ydO0WzRQaKBZ7C9y4u4chsrHYrm1zVOzd36WVJly/sw2J\\nYuP0GomIrC8sMOz38WXB5mKb/YMxKkvIp4FJLJmEyKlzGxQCjgvLYL9EqYSGTjmaHjHzgfnOHLfu\\nXnnNevVlv0KNRBZlSqlSgs2RScA7QVO1EBFyb9ExkMqECTkxBCphaWgBKKoQaSqDlwHnA0bWAxF8\\noJFlhDmPrwTTYsAD5xc52h+RpZqGTrh5MGNjNTIbS44dlAcjHj2zTD44pKnm2T884Ox8j617Y5aX\\nEmTwhJCRyialLdgbFmwut0mcZeIC3ZahlSQcOxhXnqVmD6Uq0hS+4fUP8ML1O5xxklDl9HobPH/j\\nPlp7GlFw8fSDfP7aPTZWMmaFphocksaE33zmMk9fXGfraIe1pQc4uzih19U8dPY0W/e3+PTVHb7m\\nqdM88eAmSwtn+Jf/+x+zNt7jq3yAWNCQGVYItGjgAhAsx3nOBvD5/+HX6d79eD2YEQIvSq6/70fZ\\nXVriK/7HX+ber/17qs/+KVILHvzAP2JSvp6gU8grdCIpJxU3fvQnufAr/wKt5nnhH/4MzeF1tJBY\\nHA/+83+MO/U0TqQor3n1h36YxOdIEtIf/1bW3/ANCDTpaMLln3w/qum4NTjCBEM51+TiL/wyyAQt\\nFWEypighUTXQZCQcXkAzgFSBIFNEEpD6pB3XlSQqqQv7ggIC1/7++7H5FpSBopPx6K/+W5IYkUYj\\nrMQHR9bI8N4To0dMp7z6gz9KsCNCVDhZYFpNLv2r30KmLZSIdVIPx9YPvw/Xv42PgrFOKGXk8IlH\\nePtP/c/1TkEIzNEeN973vpqwn0jO/8ovMd9IcbOATjQuQIxQHe7TSJuoCEEY9kdTlEyQgCGhxKJd\\n3Q6QKUMuLZShFlNdW++Cata7lKjw5FTjApnMEwrHe/7mV/PMJ1/m4TNrfOi5l5CtDDmb0Elb/MyP\\n/gj/7t/+Dks4VleXuXr9LovNHvO9JZYWFDsHA+ZPn+UjH/soD6z1OOwXPPDgKXYO+hSlQ2eqLhZM\\nMmwx4fHHnmC+m/IHf/Eci2ttVtUyR/0dntvq857HHmI03WdvaGnowGx6wKMPXaDMxxwfT2h0WygM\\nw0Ef1W6x3EpJignStFhaneNgaw/ZSmh5zSQvmZubJzMp07KgKCBUgQ9/9jMMJ54nzrRZWuswHI1I\\nXMbOcMjiQodxNa6Hea/x+rJfoSohObYFkYIoFUlsIwJUQjGlxEXFJExxwgOSyloQviZO+br/PdSF\\n6zSMQeq6V0pFKBlzPBwiQu0WEEWg0VaMnUIIQ6/l6TQXaSpPT0cuzbfZuTvj3Ooyzli67RbX9ydM\\nxgP2D6c0lEHEwLgsET7ywFoP5yoKmTPfUuShol+OUURaImO9u8Th0TGN7jx7exMOxpH7h0O+9vHz\\njI+OOByV3Btr7o0897eHdExCYSXnF1vI1kI9NDKGOwdD2tkyhR0RdMFgfMjNO7vcOhyjdGA2S/mz\\nz9zhQ899msxL2mkbrRKMUlQEQoyE2az+3HRCbGZEoWgWFWFyjJIBH2tSfJjkJI2ERllSfvKPiVVJ\\nNehz58c/QC+Z4G0OqsIjyJIGfjblkz/4Pbz0Mz9P4/AyssgJRYGbzXj1v/sAerpDQkSEClPNKPMx\\neXlI0qzrQKStuPxjP0kc7xEOBsTS4ssZ7B3z2fd9N0IprAgUhcPIUJv8ZeT8cMbWT/4YH/m+bwOv\\niLMpShpmFmIM6FTilKi/+xhACj63uIjPS1yRIwcHmJP8f8hHRBFrDKAxSJUSbcWN7/pv8OURITiw\\nMxKvCUXJqz/4fRBLvLMEb3nlh34E+tvIqDGxdkM0isDa89eQO9eYuhM60j/4R4QqJ+YzGB5z5wd+\\nBKohwXsSaqqUUZr1+TmE0djosKKg21kgIPDWEnRFggQJVpQIBcprEkUdG3YWgyYahRSBEBw+aKRW\\nRFHLwQuXr7HWzPjEC6/W95WB1116EqTg85/7FIfFlCmaF65v0WopdLfDF+7cxVrLzuERz73wOR45\\nM8/EF/TmmvSLyNFwysVTS6RSgtIIY1heWeHG7l1+788/jheSVtpjlg/QNDjVaTLXbtHJOpxaatJr\\ndmh3UqrBlLxUHE9ndLMulctJOhm4Ei8jW0cFN+4c1t9p1mBvd4jUuj74kIHtvX20VAhhWV7p0W10\\nOLu2gfRDEpXRMg1ILO2GJNi6in2u3XrNevVlL6g+BKKUOKvItCLGHBMDSgYyA0RPQ6cnRVoBrWpm\\nZxFqElEjy8BJrIdpWVAVBTZEtieRo37B8TRihCQxgqqE4+MI4wFVVTGXNCimQ3YHUzZaCRMcYz9B\\nddokaU5TJyx3My5dWOfxzXOMpgFjPLLKmZY5OmmwN54hSBgVjk57DiMN0ke8dzhTcen8EjoWbM5n\\nPHV2AesEo3JEiWajqzChoOtGJF3NWlfR0YZnbw/ZXG1DJriwnCJs5IHzGySqQbfdJhEZB7Mhg+OC\\nTiPjiQfmWJpvMt9o8MRDKwzK2QmL06BOvJ4x5CgXyasSW8V6sFMdI8tA5WvaUeHBV4CJPPsr/xvR\\nTVDRYITARsuHvvd70HXrND7M6KQNBAVrhSa58VmoKlBVzQ4VCTpqnv+Jf1xXMQuBiBojfG3fUk1k\\nhE/9/K9SHu9RMSPGiA8S08wQsmJuIglhhBKKtpZoEwnaEqLDVAF7d4s4PABZ4mPdOabTFI3AeYXw\\ngSgFlTTgLN/6z96PFCkSh/Hw8u/+BpWAqBKsrfF9upnh3ISP/OwvotKyBpxEj1CGIla4saNy0HGz\\nmvpfFDSGh1Qn90UU0VqKaBG+4tpPf4CGACED3g9BRHSo6gp062j3+6ggKGNOIhtEEal8Aq151MIq\\n6eJZ8t4ijUZa81i9rNtqAe99jR1UniqK2jOrU2L0BGdqFgWSYOp6oRBLrHdcvXXA5+8d0hcRi+Eb\\nv+6rabXhTKPFH3/sWVwAa0uapoFF0evM89TmWQaDnKcePEMiUqY2IjCEhuHw7g5f8eSD3NvfpbAB\\nKYHCM+hPUMGgtKEpI+srHcrCo2NJQxbc33+VqDRfuHWfu0d9Woni7ugAIyo6rQTrYG5xgVRmBGlo\\nCc3GSouNdpvRwZALZ8+yvthjNvPMomP/cEBDCUokRREYHB7TNQkbHYmWKamwVDbHpIq5XguQzM+n\\nyPS1y+SXvaBKQW07AYSVzMqKKBX5eIQLkSTmCBkg1LlqhUJGgZYQZG0ZccJDlGhtQBqKIjCdFSjh\\naSaS0awAozg6PqKIFd1eg+PpiLszx/5+wdPnlznG8uCpRXpNzWhvTOJSJlVFmVccDsfsHA1ZP7UE\\nUVFFi5aKg8Mhm4tdmkpSVoLjyRAjIrKhQYw48j1smZAw49bhiME48OrxkB6G1e4yr3/4Yf7+1z7J\\nd37z17B7eMD+xGKk53XrHQ4OxrW4+IqBKxgOd+mP+sy3MxYWMqYzxzseOs3bHznDs9e3uXm/z2wy\\nQlYFcysrlM5itDwxzkekKPFKgvREWRG8JHiFlZ6oNHlZQvTIVNFcX0LsHNUtnsUAokEQEdYhRSQq\\njRYKZQzKtWvPq8+RMmGyfp5Tv/47xKwHIZLOItIXaClxwVLgkaHk9Po6UXqWrl7B6ECqWww3N1n7\\n4G9SRAExRdgZw+c/AcHjq9qfaZB1FNQkSASJ1+ATEhQqSmRrDnvC2PLq5GyUiFGaIAWzU08iVF1H\\nEv7kw8iiwMeAlgKlBEZrlEy4cPcawtbc1sQ0cFmGjBItA6YY8rs/9PcI0fOff+bnCKVFRw+mxeKv\\n/hb993wHBoP3ETs7RkaHLiqkDcQqYrWCVoIKnqA1KEEWFQqLk5p3/atf4Nxv/RvO/tpvsPnrv87f\\n+IVfoCxLCBJ98j9FBIlM6bbnQRqi0phoCKI+d61UQabqyKyJEZkKSiyzMjAupqRNRWYy5hPFjSsv\\ncWphjVf2tglI3n7xFC0CZ5YDh5MpdrzLYDykqQP3dvbYnE84tbrCQrfD+nyHs+vLOAmbGxeQIkIQ\\nzK8tkLUNbjbhfLfF6uIS5RQWuj3Wzi4xmimOBprtvUM2u10SZ6nGM84vrVCKyMMXLnBt6w6hsjQb\\nko6R3Dnc53hScOdoQMwUO3e3GVSRpAOz8QRlFKunOzSU4cLpM3R682QNRe4DVYjM8pzlpTXsDJZW\\nlkmE4aDvONg+fu169V9OCr80V4zQ6rXoNRVSK2QQBBnImg2qWc7Ue4ygjmCGSFD101laENGRW4/A\\n00pi3aIpQEtD4esSu9VmA6clB4OKlZU2aQlpqlicN7RDSW+hxbX7E7b2Sp6/1efe/UNu93MCklar\\nRaOR8uB6h6yVMdjvkzUjnaSLjCnTXHB39wjSlFQm2CjwqgYeN02LfNTn8zdvMx0kvHzrPkvNkvc+\\n0KG9tEqZ5Gzt3uOwP+D5G9s8ce4Cy8sd1hcXmVYFD51e5PGzG1y48CAb3Q5LrS7NwlHlAW0Dg5mj\\nXl84ltsaOzrgoTc8THd1hWy+jVGC4kQsRBCklcSFGuzhsgbH5QT33e8l/eGfxZ1UT6dZhvnpn2X5\\n3d9Fs5oSpEImEpdJ9E99ANG7BDIhUJJLz83xIZv//rdxc4+TqSY+SB776X9C1tSIuU1CoWpGaSxx\\nfoYTnszXx/oVCuMUFEdEpYlV4Ml/9t/Tm5/joPckIlqiCjz7wV+jiiUir3DBEoLHiwSixmMR2hNC\\nRdQCqQM+WrRonnBWqeutVaSSkWgUb/1ff7puIAhgpgWZH5H4gPO2ji63ND5CKEaIKIm2ovy6r+Wh\\n3/09QutcTceKigdcJEkSLh4d1KT/GDlYWaO73OPx7/s2gvBIFTGzkpVug/nuPMoLnPToqCne8m5O\\n/8f/hNYJItRDurGLyFDX6CivKWyFEqFOc4m60DI6B9IQpMFaS/94CMGRqED0Fh1rZ2+lmjVXAoMR\\nkU7WJZQGozQymSNVgr/19CZnOpHDvT77Wy8Tg2ZzocWNa1dxPjI/f4ZF49hYW2bUHzNTjlNzCxTR\\nMRoekRhFOcsxDUmn1WQ8OsKVDucc586dZW9nj53JjP64ZPtgn85Cg6wFh/enbK70qKRkMpvSPx6y\\nNDfH3HyHlZUVCIEXX77O6lIHZyHPc0aVZ2F+jlnlePjSBvPLp0haGcZbolN05xdY6xomI8HUHjMr\\nZhzs7pMIT5mPaXcSaKTcvHyZuZZkcNRncXGepfkOG6eXX7NefdkLqhCCw4NjjsY5w1kfmWVIrwiV\\no2FaEBs0qDvSjTH1j1xEUHXZamELIgIfIzrVdamb0qQook+YVCXrTcNGJ2Hv/oRmN+XaUclHL/c5\\nmFV86sYu0+qQhQ5MRyWPPbjBuQdX6qfxvQFJ1BwNAsrVr3/3oGR/NODweJcs9XRahmIypdB9evNz\\ndfrFOppSMhfHPLbW487kgBzDnaMxpneWjz/7OXZ2Z2zdHTKa5VT9ihfu38SXM/oVHM0s+5Oce3v3\\nORpsYW0geMXKxgL96ZRX9wr2xlNeub3PlXsHUAqS9gKvfO4q23d3WFlbOZkGa7SsCfIh5jhRYaNg\\nUIzR0fDwo2e48NQTSAmNZhfvSjbPn2Vt89wNCzvyAAAgAElEQVRJZYlEiYyjduDsU09x76k34lDo\\noMlk3XaqE0XZ6uCURauIbtfUL1FWJFmKE57oa+eGdAF/Aj5udzJMErHSIwkIpVG6hYgR3W7U03En\\nWXcBHTJCzGtEnhNECnwosR5iSBBGoG0gekGpJT5WZEKhhESJiIyRVGtUhImtSJKlmsqP4//8/m9H\\nuAytDBAwnVVkqJA4gox4FI9//3dDFFwzEh8USgls6fBFhcyP68oTDBtf/25EhCyVOC2J0ROVpBqO\\n0c4TNDVou6F544/9MFmi8LpZ82OVwktQ0aNsRVXmNJXEBo/z5UldCwSVEpSlYS2JqXGWBEGsItE0\\nkS7gREC3U3S0CB2ooiekCVoJMgNSOt58/hLPvnCPw7FDTIa8cveI+UYCBLrzSzy8vkCmArnVfOr5\\nW7zh4Q1e98gjXL2/w/4wxzQalCEyRfH4W9/NTn/MYDBAGoFwkpaQPHBhg7mG4vx6k7c9eZaV0+tM\\n9/pYO8E0NcXxERdW1njysU1MVRK84Mqtq2Rpk1mRkyYtwmyMlgmtVgNrFSvtLrHMCVVOt6WYW11k\\n++CYU90mR3nJ/uEh51bXGA722Nzo0b93zHCUE1zFkknpLJ8m7SwymE65cXebm3fuM5zNXrNefdlP\\n+ZNWk7lOB6EkXgoWW4Jr93LmWoYyzwkqsFMeg4x1jI+6nyfUZF06SVZXWUQBPiCEJsp6KGGTQMOk\\nOAJJZjBBYIJmtaHYOJfgguOhlR5lbKPJGc8swUuKg11EEjgznzHJc86stRnngXERaKSKlVYbS0XD\\ntNnaPqLUAh88ZTjGJHO1j7Ld5vr2PoPDEUkrJafkLQ9f5EOv7PCer7jI3iSh359xfXuXRmeefJZy\\nbWZ521pJhqqZnbHgmS+OuLCe8ZH/h7s3DZI1Pcszr3f7ltwqa686+9anV3W3Wt1aUEsCzCJbLAYM\\nloQRq2CAgbGNF2HCDjPGwwzGeBhDaAwOTIAx4MBjhEAaEKAFgdSiF7V6O1udvU7Vqb0qM7/t3ebH\\nV2YIwhPqYWYiFFO/sr7KyMyoynryeZ/nvq/7+cucP7XM81dv4aMHKTl6ZJnVuyOcr1gY5GzvF5gk\\nZ7o7IJEGL1rKk7OeRmkIgVpHOv3pdtGiErY31ojBYKsDpMhQaUv1Fy4Qg0UIEGePgfBMLc4dpphK\\n/H+J3o2aiEE0Ek9F6uuWp+D3qPwEiULJrF0maUmsJUI7DqqK+WSZxLXBbtIEnv2xv0+3N8PU3gbo\\nSPSKiMGYSDIxRBlRiUZqTcSTyi4B2zJIAREUaTdDS9PG6ogAQlDbQEcKMArpBBfve4hzz7dAjIfi\\nAMQOoDEEelM96hgIOkf6jE6qCCrHR88kWHTSji7SmEL0GBsQtSMkgv3/7dfY+fXfhFDSsaENKRSR\\nW9dXuP/BRwneAVAVDUJ3sFFQGshjazpItCGieOH7vpdsvIWoLMIkbLztTTzx3r9NpEHIiHKaSRow\\nWpCahP26ASLe1kQViE6xvXGXRgdklDTWM97bZnE4i48VuUq5ePEpTi4eZbo75OjZ8/zmb3+E1587\\nwsWNA06dOAlIrl69zjh6Hj61zLnjJ/n4C9c4fmSWnW1LtzckNK2j7rnPfI6TZ09z7dpdujpFigYX\\nNdZajs3OcHXlIqdO3sNv/uffYfPAMdSB62v7DPMOo2bE5Qsjziwc49LNa6QmwzUFdBRZJ0dngrou\\nUVHiUsHO/ohye8LCHJw7vcjt23dYyjWTumKqM6AzJaiKmoOJY5DB8MRRyo117Ljm1s6ISkXcnTHR\\nSeaPz9GMPcn/jSr5BV9Qq4N9Dpo+RxZr6piwvjWhm4HpS8ZjiYgNp7oDnosTIu3WUh/GVxACpG2I\\nXUdrai+IviFqmEp7mMbhkwSBZH9yQDdLcDZiXU2e5USdU/qS3bJhd2/MfBqYmTMEb+hkgqIGUTeU\\nrsOd7ZqFXgfnA1Xd4IRmY/uA2gWGgw7OQ1IlmLSHRjBUgiAiAz3h2iQ95Hw6dkcTPva5momTVDGh\\n0Rl/48QSR4Z7nFycYWcsOT5d8tztA5SMVLHhviNzSA5oQsPyVJ/bByN+4MkHUb2cxKSkPrJ+8y5O\\nBnCWoyfupfYNMao2Eyl67PiAGFNirBiNDtrOxnnGW2OkjlirSWQDzkLWR7gCjyXEFjZCNNhxTYg1\\nIgi0bGlZMoWq16ErIiEqXvref0jTm0GObqOixBjwRhJcSWjqNkNMSqKFK69cxDUGnbTOremXb+PV\\ndUTTRo9oE5AOnPeknQaxGSEEVPSMv/ytnH7nd3H9Y0+R/of342KB9wXC5ERavmoQAkU7b0cEovWg\\nNW9/3/dx+d1/gELSNDW5bmN0gtAkmaGMKed+5VfbWBjh2nGTg/npKVSxjRUWIyVCteF9wRhUEKQ7\\nI6Qftbrl2BL8nYo0owmT/Qntyqqd1YrYEg5SG/ExQclWkWFjg3YBVxVIFxHBkcguTkRykxJ8g/eh\\nLb5RMaqK9gMmtrldBkkwiqgGxNDSuozOmOkO8d6TKsPyVJ9cKV66tsHpZcWNtU3e8Za38isf+X3e\\n9fa38aFPfArtYfnEFPNVDzmq+ORnnqZxgsFgQH9ZoYLl9pUrnDiywO7YsrJm6XQA2UZmv/5Nj/KL\\nn/otqknBa594EhEljz34GEaV3L6xR+AWlJ6pnmF1U3FqecDy1Em2iopOd4piU3Lnzjonj02ze3fM\\nYGoG7z1Hzwzp5ae4duUGOwdddGKZzmcZlw1SVvgi0p1KWBym7I8O6CE4ttClsZ5BlnCk2yXqyPZ2\\nTbG9h0w1uf//kWxKKkMnHHD9zgQ3AqMVfZUgao04FCUfxIjwAVs3BCFbP7aSCJVgXUArReUjoYkE\\no9AhJ7iI0gZXVXhXkSU5Auj2EiaTksXpIbmI3B15+qbg3NkhjVYUQbC2O6apI0JH+sOMuqxZGvYJ\\n0bE3HuNiwOCYUoKZYU5pJ+0yTSmUDngsy6dOs1fWTJ88RZQ1TVOxNFjgr5yepmoi3/CW87zm1IAT\\nvQ6/+ewVvIMP/ukFPv7CNT61VrA8bVjoZwySlA8+vc4XP3yOM0eG3NktmGHMhbWaV1ZucHt7xOrW\\nLsPpDv0pyaQKHF1YaEX50RGCp2w8Wjs0FuU1taRVI6SK3b0NYoyoGHDCgFHYGPA0JDpBaMi6CSJ6\\n/P4I4QLGO4KqQbeEqnf8D+8jiJToLLYsUGvXkSHgrWPz+AJCHC6MiMgoMa7NjpqUY0RXgpL4mOBF\\n087GVcRbh6sDeadDDcjGEgQEZaljQ35iEdXJyQ47RpAIJdgabROjByVRsaVVtWCTNocsSs9HfvS/\\nxwioZRvc2ML5DDa2sSAhRKRq5+ERTWF92w2WDSFNyKOhoSa4NsVUKUVUkShTQm5oosIYg1UglaIq\\nGzbX7pBG0xrAlKR0Tfv+lxKhLagKlWbt+MpWKN8qYLwENciQMRLLMSFCajo0RdHiB5Ug1RqCINEp\\nMQqsq0mUPoTZtAkL3UzjaWPZjy3O8eJ6RS3g/D0PcWtji2vjbWSWs769wb33nebxh05TjyW+moCy\\nzM8OmVQHnD5+grLZ49kXLnD+xDK92SN80Zd+GZPbGxiREdHYEPnU008xNzfHQ695LcXogMXlAZ97\\n6U955fY6S7N9QPHN3/iVrKxskRrDC5cuc2Njl85wyJ2Nu3R6iuks4oqGuoKmLjAKlOgxmUxASlbv\\nrLN2d4KlYH39DseX5igmDWSG6emjBGFI0pYx0REZZWO5vb7GwahgZ7SNznLq3YJnr11+1fXqC75D\\njUAtCgamgw81adbB2QZbOkIAKwWpDQQVkV7io0PLVhKS4HDRE2laHqoWhCbgpKWoUypnme52sLEk\\nVTkiFNza2qXX73H5xk1cHZiZ7lBOPHUo6ecaWzgWZmewjcVZj9KBnskZHYyYNGOOz02ztV9Q9SWT\\nEPF7HiE1S7ND1kpHPa5JOl2WTi1QZEOevbmBMYaH75nilz/+PGtlSWo0n727y59evE0iJQ8vDvn4\\npXX++mtP8qHnbzLf73F5c0wT4MyRRZ5Zuc2kGnN5U1OoQNCzDGYSPvDUXZRSfOVjDzMV4bm1O2yM\\n9zhoalRqW9wSkkQqfJCAJkZHv9+DaFHk1D7Q9YoQAdFmRmnRHie16RCaktpqvJY046K1rIrs0N8e\\nEUKxUW/hB2dIR6/gvEBJgxceqQWv+ZF/3IYsJgJXCYSqQeUYKVh84Bw7f/17sMGTSIgqkAhD+Z9+\\nBRM3kTGyW404pyIKBfHQakxJzNr54fWL1zke4+HxWjGcmieIBBFCS3gK7XjEO4fQAVcH7l+7S0lL\\n5q87GXFUoWQgeI9OEtT+iGd/5AeZ7I1JOh1e/7P/hsZrep2MuL3XxpvohGhSmm/9OwQtkVmHam+E\\nmeqTVDX2X/+TdkwVBcPlBW5cuUSUHmsdJmhEFokNpN5TConzns1RxUNKUYUKIwVKJigkaW8KKSJT\\nU1MooDmc5xMP88B824nXTcvPVcYwc2QOSyAFXFT42iPxNL7hwo1bPPHACT778hWiq6kTw9PPXiTX\\nks++tMJr7z2HEo6CQB0ESXfIZLSPbQRPPf88jz70IJPyCqGq2LG32N5cp5tobu0cMJdPY2TCm978\\nZj586RWee/5ZnIg88xyMKs/tC3e4968sksiMp5/7HF/+xod55vmrPHj2Hla3d+lmgs4wJXjBWAmk\\nTnC2ZDCYQ3rJ5tptjh8/yeyUw/qGceUIpePI/BTFeIf7z59l5fYKs50+tXXUVWC3apgeJPgYmJ1f\\noJhUTA1nmDSB/YOaNzzxej5566Ovql59wRdUQQvGSJQA5alsiVQJvY5mv5ygvCXptto6IVRLJwoB\\nIyKN9/RyTawiJtE0dcvRjEFSugIV+6zcusuZo7Nsj/fRAWY6KaaT4MvI7JElttbXqYOmKxJG1Rjh\\na2aHGZPgCcbSjAV390ecmB+wsnJAnlfoJMPQgIB0LqOpPFsHE0w65J5zJxnZhrubY+6ZzUkHc5yb\\nm+I3PneNkRM00SCt48LKDpaMoCQv7llOnjrGJ1Z2mNSOXRwySeialFub23gVGXvN5Rtr2BhIouQD\\nT6/w5GvPMJ8n/OFzF5lONaWFJ08u0s1SykPepyOSSlC+zXPyxpJkKRGNR2En7XKP4NCxjT9WMYD1\\nuNohUMyfPYrEog5TQyscjkgSE7y2+Gg49XM/xur/+EvIS5/A1ZYsOkpd0006ROlITI+smxFtiQsW\\nZbrQyXnom74MHaCRrTtJusCl3/8obG2SCkXdnUL5Vkts0gxbVKjpKTLTR9oGV9WtBCoEhACfSaJv\\nkCRoPFGCD5GgBK7yqKbG1QftiSdI+t/5LtzP/xJ1U9MdzmFMRm9g6K/u0VUOMa6JUWGiR48PiIjW\\nJp3nmCi4/yvfQAgCL0AFh0z6+HKfa+83SBw2BpbvvY8Ln36GoZAY2Y6rfOWwMdLEGuloo2LaMTfZ\\nYB5TlVjvWt1piIQoyRR4qZHS4xoFpk3pOxSJoTIDVUB6RWU9OrQSregjRiWIaKl8xnbdcOPCDQgZ\\n586/hi/aHYNruHJnzHd82zfxM+//Rebn55ExstzvcnR+wPXNwIPnBqzf3eTSpSvMz89TFRvMHz3O\\npReuITPJdlnQc54sg2eeewHdCdx3/jhFUbB09DgnTkxxsLHPreu3SLOa6WP3UO2VZFmCSA2EhmJU\\n4Z3ioKwZDvrc2dgky3O8jTR1wf54grh1je29ikfvO8deOYEqcOzIAjdurBOmNhF6iqh7nDo5zcWr\\nN+h2EqoyweQdSm8pQ02wGpMH7nvNA7z04pVXXa++4I/8xNZzrGRC3Wh8WSLqQBUsvSRBKMMktDR6\\npURLP5eHRxypcE1EpwpXO+rgaAJUNNQxZWwjZ4/MU9cCT4o1KQe1ZfVuiW40G9tbGJkyNzQUNlI0\\nDalJ2Js4XLAkuoMl4cRwCuU9s0MJtpWr4FpoSDGeoKKlbg54y+OPMOj1meunHDt2gqWhYWaY8x+f\\nvQKhDYMb5F3qIPmKR4/wlofO8Xe/4jR9HI8tGx67/xyd6Ywf+ro3MfA1eZDcLS3eR4rS8bWPn+W7\\n3vIQPa2ohGOhP+SDT11n2qTsNZZji9N88tYGSa9HplKccy2xH493gTpU2BAg6xKlR1AQixFeWIxq\\n4zFMUAiVEBxEapwMZN0ZtOghoiRRCdKLVtQtFDG0i5fMGM6871s5+Qv/lvP//pfY+KpvJVaeC9/1\\nnXhvmUpygquRskeapljfcPF3Ps7Kt38Ll7/jW1l577vbnCWlCZMdFCk+CF7zpidAp7hyhJAeryxV\\nMSLPc1SSUuztUYQGpMa5Gjqm1SMHj5WR6Fpxf3QKaRyv/LN/gS/LlhU7yLj/K78Wq1pnUTXZY7S3\\nTxMtAtXOMGVABouPkLsKdCSROV72cKFm5T3fwtVv/iauvPfbsLduEHTD0tSQKFqtaNppYdubV69j\\nfVtMo5eEKNrE2RhpfI2TLWowiMAL4wMq20CARgaufvA30CHy09/wNQjv2r+NrKlmF4jR44NFaYlu\\nRPs7dBFv2hwwESMqeoJqATBpf5pk9ii9bJaf+Jmf4mVXE6Xi7l5Jv5fxnz70EYYzc6xv79L4SGcm\\nZ2dvCx9KXri8ymsevZ/t8YSgIlY6Ln9uhT3r2d719JKMpblZHn/L45w4vcDNG3e5srLBaBS5dWOH\\n3Z0xIWiurO2zsDxDmOywunYb34OdgxFkGXt7e8TGsVc6hFcsz84znOqxsTvGdVvoymSsWFqYY2t3\\ni2AlR06dQGU5i4uLLJ5dRAuowh5Xb6zS67TvN5EkbO7tUxcB23gOioK6Fly5fY3SpK+6XH3Bd6hR\\nCNIsUtmCfpZR14qQNiihidJCcEhr2vlZaAEbiECDIAF88AgMWgtSIZFYRDTI4Chdw3oZGciAcI5x\\nUXJsZppkkFBUJZVPKG2JriHp95ExoxYTZvQs+3WgrjxFXXB10jDbkaQio4mCmRnB7rgm2JzhoMd+\\n5QjNPs+8fJnxZJ/5uSFUNZfEAsn+bZpoOH9knmUct/Ybvuytr6Pon+FjT3+YaXGUvaLi5/7zJzlz\\n6l6qUcPLL11merjEtYMNkk7OieVjbGQJz1ytmezepBGB43PTfPCZS3Smp7i5s4lXmud3DiiCQEpo\\nXMQo2eL2vMOLqqUjiUBvONX+4wWBHtUk0ROVYtKUeCNRIkEqDypFRQ0mxbmG6MAHiwyWTGeAJzUd\\n6romjA7ouzE7L77Mcx/5ILMrY2wQuNLSPViFqUWkkYRygqAVzz/8ljfy0r/71/hQoWUKdkJmFYzX\\niVgChua1jwKH4PtKIpIMg0L2em20tYh0Yp8oSgSSXtYleknUEYQmSaAmInTAV5BduwgyoL1kcvZ+\\nDuoa2ViQkqAlnU7G3siBanDWk1jJUz/69/lrP/o/cX2ygbeOaDSj192PkG0wnwkNYTLhkz/5o3zx\\nT/8CH//+9xLDAagcX0n2C08xqRCxbgHRHUNQAhFbYb/RHUTjKYYg8bzjH/ww6z/yvsMcasuJg30u\\nvevreQSPkBCcR0pN/w1PEnyDkgkCS6NAeoXSjnTYJ0RLpjJIIck1wffIckVHWaYX+/ybn/1p6rLB\\n+YayUkg/QezBvrd4OsgY2FvbwQeJDwLZz/nYMy9T+MhnL94kzTUyn2PQFzgUuQ5sjXb44098lM98\\n8k9wtkOBY22nQiiPFpI6jEk6U3z6VkEqJgTnMd5zc28b01SHM2iLx3Bpp8S6iEkkVmt27xZ4kaJy\\nzerYIgnUNnL32i063Yyd3QndnYO2uZKxBfPYClUatJhQNp7NUFFVFqEkZtwairyvX3W9+oIvqERo\\nakhSQdM4VDC4UhG1IMqcRno6nQwf2gC+RKdkWqESg4+OTLW55lEKhlmCYEDwJS5KdivHuGnYFi05\\naHY4xcu7BbgKozy5CBS2RsSADCAi2EKxke2RCEUsI3XRkGUZV0atcaAja/ZdwkEZmO5You9xcXOX\\nNOkwtb+PjY7NjT1+7ld+nY7uMXI16bDDpd2KJJVkvSmGsws8f+E2g4UFPrK6gZxf4tiJk3gl8Xmf\\nD1/bJU1y8sESqZAIprh1Z8wTT76FP34uISlqJrGBPCFmGcmCboXuoaY3zJHKEAjoIAjKIGNE0842\\no4/YNEejCFIj9/dxooWJpL0OOkicqNG0qZ2OA6bSDkI6hqmAkBKFpaAiKAE7e1z/vh+kqdeJvoV8\\nDKo29VlJiYiWlz/zKd689BjBK5wQGEG7/JKm7ZwAguXGt70X5xxaRkQUuG7k5GveSC00aWgoVIMI\\nBpkbpqaH+KCJhUdoh7OBaCT5zBRGRYKToGvK2L4/glA89S9/lkVhcRK8SXjgh/8euU7wSiFcDVoy\\nLhqESfCqiwpjUILhy9f59LvejfICHTW1CLz5278fhMabGSg3wDtObR1w7Z3fQALY2EeIwKg7JFEa\\nN9ohUZpKB3SU6CjQEYR3RGmpa0+aGhQpvbMnEdrg/QQESCuwNEgXCE6h0hSk443f891sHexgI8ha\\nIn0kSQxV3ZCmKalOsK5kPLYElWKSBJkkmF6f2jmayZhOd4rQVHRm+iij8XXFwEuUDEiZMHPsMDk2\\nyXnL257ABcef/NGnEaVgp9nnrW99ksQ3TEKNDA29dEAIjokXpEIR8AgtMErQlJHd8Q57ew27mxuM\\nmwYZIEZBpiJN8Mx2Z0l1ztzRHnk2TTdXfPZzL2J9g53U+Bi494F7mV6eo9ovuPzKJYqqIuAZpNP4\\nukL1NcnhmAgFRVkTZIrOu7joybMZvPDgHVLG1kLMq4NMf8EXVKUkThjKiaeXQxUFylsqIejrlHHM\\ncNHhvUebdqMqjSRJNJ3eFMFr8twwHhegBDE6er05Gu9oImRa4SN0omASPaKTohqLynKqaNFiCKoB\\nrwkBkkyitKCxEZ0I8p7A+0gaBFZKGuGJ0qCThImKFDEws7CISFOUFPR0hhfw9je+huHyHL/3iWep\\nioqqqugkCanqkKqEL3n8FJN6iUE+IOrIR//4BUJRcP51j/DKK5eQqp0nNjrS7aW86aEH2SkbppKE\\n0/fez6Xrt4lhzPLMDGt3t4nO4kKHGC11kyCAoARJEpl4A8pRU5MYhTSaCkhjpLO9jY6ASNl3rdFC\\nIvBOoIQnMx10NwckVblPFouWZp8lNAcjLn//PyAWm6gYCUhsUZHoNvFTCIU0HY6+8W2gFEE4RHA4\\nlRGDQioJnWXM/lWcDDgXkCGgQweVQVxYppYaEYE8QU80QsV2pppkRBxdaZEBfGJACJLONCF4ojaI\\naBCy1aPGAHMXLqBMgoqupTLpDpUXJGQE6dolXHREkfFC/yQPHLyEqCWRGi0TYqbwlSDKDNHtUHtP\\n8u53w8/9FAGFP4xHdjLBqEBhA0d/8O/ilUFUAk9EWrBJSTGxdGe6LfeBhCxVxMwQY8TlHQ4Gy/QP\\nruObBqcFComWApeAEtB0Zlnf3UWYDGqPExYhI005RmpFrzdoQxllRqYDSdYhFQKVJNggEMDM3DRv\\n/aI30u322d0fc+zEEovTs/zhR38X7wNf+vavYHv1DoVz7NxZxYgSgefMySU6psvi0iwrq9eIpSVI\\ng1KG1dtXOXPsHDdWLnD23vuZnZ7CBk+awSef+RPuu+8+MuNYmO0Ro6Aq91g+epS1m7d585tfx9r6\\nFk502Nu8jTYNp07ew9xCh527W/SmexxZPs1nn/k0ay98hqnpozzy+IMoKZlfWODqpRW8bUiyyIWr\\ntzm2eJqtjTsUjcXWDhE9JkuxzlEUBUJqolPYaF91vfq8BVUI8QvAVwEbMcaHDq/9U+C9wObh3f5R\\njPFDhz/7YeA7AQ/8YIzxdw+vvw74RSAHPgT8dzHG+Pme33tPIjxl8GidEeoKaSJJmrO7V2F0w+1R\\nJAZHFJIQG4QcoBNN7R25TghSked5a+MTrXWvgwQcMQoWjy0x2a/pDRR3t/aZGnQoRhXRZOyPC3Kh\\nSZSmiAckIUUaODoledMjj/Inz1zj4UfOYA8mVL4mizCzOMeonpAbTZr3qUcFIlcEB0mScOXmddY3\\nNtgdjXnw+DKzgx6rm5tM9zNmhtMU1rK5tcf0TIYPGSeWZnjHF2usNegsZWlhhhAsc8MpijLw/IvP\\nc3Vrl8cffJh+7pmdynntA69n5foqWkUGvZyTp5b53ItX2diuiM4TfMQYxaR0GC8QrvW0EyLRCEwI\\nCFLM6BJOG6qqIH/rm5GixciRNMimQxUK8kQhhUL6FlgsQ2Q/OIoLN5HNNkJorPHIoNCmAwiCEAQU\\n7ugxzNQRmkkJWpAkgzanKWk/oM78L/8z1773Xei6JsSAEgZUTUynuOef/ThRaGSoEU1NSD0uQGYj\\naX+WCRKcwGlFEsDGQKPASU2mNFVTt1BybcjLBhkOiEoRtWb8ti89ZBIIorHgJZ4aISUqUXz9+3+c\\ny+/5dkj3EDal9mAah0p6JH/7B4lKYaTk1Je/led/4wMMNlfQ3RwfBImIKKFRDzzI4hNPoCVU9QQh\\nJTqRWGNIOqYFlqSaWAWCtCyfOtsut9A88m9/mpVv/lsEf9Dm13t7aGYBOzDc/4u/hgsVztZILZB1\\nC3dxKByBIFoOrQyeWkqU9wglyNIUiOgk4+GHz3D//fdz4cLL3Hf+LJNqxJ986uOcP3+etdt3uLZy\\nlVRHNm9vQmZo6siVl19ifnGJ2cUhR47MMioO2KKmY2BcbKKqgivXr/DEk49x+/I1qt2UaAKvfeQB\\nFpaOkHUzJhvbLJ86xunlU4zrTf7wgx/m9U++lUnhWF/dYX90lWPHF5mfn+bKygV83TYV4+0Rf/zZ\\n32bx1H2c/pJ7MZ2EP/qd32bh6Enu7Gxz+vy9FEXB1Uuv8OVf8hVcW7nA+fP3MZiZpmkO2F9bZ2b5\\nKIPZPuXYs755k4//4aeY7Q7/3yuoh0XwZ4Bf+gvX/1WM8Sf//AUhxAPAO4EHgSPA7wshzscYPfB+\\n2iL8FG1BfTvw4VfzIhsZ6XU11aSiSciMX28AACAASURBVCvmzByjiUMoh7GKI72MV7QmkQYbwTYF\\nyfQCMUbSNEUphVXQSTJ6vR5lE7F1QZtsIHj0vqPgUio74YETc9TOU5Yliwtz1LUh+KIlsCtH3p3n\\n8rUbHOlP40Tk1NEOd++ukqYpc4OUS1c2McMeue6iQ8mLL32Oxx55mL3dbZaWjxO85fjCkBs3x5xe\\n7NE4QWkbBr0uG7sbBO3Y2xfce26JycGEYT9y6+ptpoYd9uodeskMJ4926OQJsVasN3f4G3/1zazv\\nHLB3sE7Sk6zv71Fv73LP0lEcFbPTAya15au/7A0c7G1S1oFoPC6odikVAhoFwVELz8zRoy1dalIy\\nese7UdaSKs3Jtz+GlwEQ2C97F15ZnA/0jz8CIuJf9zpG955HxUiiPdc+9Gn6oYGokCrBfuM3o888\\nhN3aw/zqr5GXK5z6R+8BoditC6o3vINqYpEoGi/AO/Rsh9Pv/2Vuvu/HcJNVtBSEhSN0fuA78GlC\\nSmTsC3aX7wVzglAI4nSXOoLSCn32XvoPPMLdW7cIwTG1cAIlBE1sUErRxAbpJE/93sfo3Pd2qjTS\\njA547Du+Cwcoo9k782a08hQhck4qkghlmnL+V/49r/zDf062tgLhANE/ivq+72b54fsIBBAKpQyv\\ne/9P8uHv/VFOjS+iVCRJO8Sv/Dru+8avQkSH9ZreuUeYIHGlZVSOOCPbUczo9JsRSaQeCYZnHwbZ\\nLpaQiuO/+O9Y/zvvwx7cQjZd0IZ67hgP/cy/wroRSnbQQKIltmjdUtE7VNIh63Twvt1KKyXxtgaZ\\nUFUFWZbxznd9Lb/+y7/KV7/9q5mbG/CJ3/sD3vRFT9BPO1x66mM4q6g3riGVZzBYQnnLnVurzCWS\\nlZcvsdiHFz75TAuA2asIPjIzO8OZszNcf/lF/ug/vsz00lHOLE7T7IwpX97l4YHh9it/zN7NfUaX\\nn+ZGPkA0Y84Mc9b/9CO4KNhd22F1Z0y50uFS9Ixqw85kH2zknrPLiKLhmYsvcc+ZY3zRk4+x0HEc\\nXLvE6XOz3P7MB7h+YZsnv+FL2L11hYW0oWHCaFwyuXWRuZkeo5sWWxzhdz/2AR6991GkSpmaXXg1\\nZaqtga+iSUQIcQr47b/QoY7/KwX1hwFijD9++P3vAv8UuA58NMZ43+H1dwFfHGP8ns/33HPdTvyr\\nrzlPnnYQCqrJhE6a4YlUUbK/u0NpEp69dYd+x6BVDqpdHkyKCqUUxqR0u320BlxkXLW+72/5mjdw\\nc3OMiRGfKLrRctBEsiTF+RInDLnO6GjNaFJwpJezWVpevrrCVzx5LxubJS+9cou3vf1x1lcbjsx1\\nCbJHVaxhJ4p6vM+5U8dYuXWXELa4uuPpmIyTR4+xNa4ZDkqy9ASTZowSBZcurjC/tMzSzBKh3EcK\\nx2T/gGo0odcbMB7tokTCre1tpqbneXlllYVh64se9KYYuYambFCxpjOYoRw3dIY5w66A2hNlh1Fu\\n+Jvv+VuY7DgI3bpybOTSu74FF7fBOnr/7bs4+sXvJqDQMhJEBlT4AEooIg4lM1ysUEHR0GbQC5UR\\nseAahFB89od+ivzqH6KlYPon/jFT59+IcIEkMfR0xjPv+HJkanngP/wmhW/hN/yZWiNgYqva8EIi\\nAwRhkVEjCHjRHsMV4GLAWYGWvn19QtLpGkaFhRBRugWhxCDAN0SdAJJEOWTMmJT7dDrdVmrXOJxs\\n866UBhXBHeILW6CKh8bjE0WI7eOGpkbrNrRP47EyI9qGSEqQNVrolq7vIRGt7rWRGiFKVDS4IEik\\noAyWVGqsEAjbIJMOMrRYyijbmBUfJU5YTNDth5triEITg8DI2HbYQSCBgGz/Hr6935V3fz3UrelF\\nvuc7OfXXvgZ0pHKWf/L3fqidMw6GnJ/rcOzkAkFoxrubbN5cw4bIidOL7GztImVOXR9QN45ubw4b\\n9/DWo/QMibRkqWKxo2hMTmkb9g5GzKczuNEOa8U2wWbUFmL0ZEkXZMnt3X26sk/R1MzN5PiiptdP\\naMrAuJpwd78iUQ39fIY8Uazu7uBkB6M1VVVwZKHPMM9wNSydWqSY7KBixtU7ayzOpfRNYHH5FCuX\\nLhJknxvrG3Q7Mxw7N48aWaYXB7j9CZUSzGQzrBW7jGzCrZ09jgxm+LXf+uAzMcbHP1+9+n8yQ/0B\\nIcR7gKeBH4ox7gJHgU//ufvcPrxmD2//xeuf/0sI8u4UMjb4EEhkTiM10jlSACOYlAXR0x5FpUN7\\nSTmeEL0n1pJ9Copyn7nZZUrbkKU5bz6Vcmd1l5dfusKR2RmGWc2eAd8oNkYleZ6zvV9xfDHn1rhm\\nOo9cXheYfEDXNty9tsrBbsXZacP6xTXqouTKuuWl2yXDTJGmnis31rh+7TYhluyOSkZB8vCJOS68\\n9CxXVyccP9JlSl+l31Fob5gj0ty+xo1bV2lquLyxz3xHMdfVBOu4ur7PyUHO3t4e12/v8uCJWURo\\nGDdjqoP22GdkTt4J7N/ZZ3pOUO6XeGaRUjERkZVbLfhEhIg0lsYLTICoKmTlEcKgev+nTCSKNihP\\niRb64YkkMgFrEVIRdSQJEi8lhJooWpmblAo7PUfPpBAi8uqzDM4/Qe082Jrnf+YnEdITq5zoNlH6\\nKFGJ9hqaGKBUoLwkMQnWNkQbCRogI8QaFQyEGiU0Usv2SCsCKgQmZd1qK7Voj87OkRuNVbqNulYp\\nBINXjjTN2tOKgqSbYsclSiU4GxBKoFULMgkhIo0hZgLRRJSWSGERaUJjHcLEdhHmaqQCYrslDsEi\\npQKtmJSWLNHgLTG2Y6lEJYgY6CS6hfzElEZqTChwaIyUbUGlZbgK0UrdtEhAgReq3ZhKiRCHMrAI\\n0VVEoRFKEK1st+ja4RT0el2cUkgBUXrSTOKdRgdQueGFz72EERmZgcYImlKwf6CIMmEcCpJ0ik5a\\nth1tkpL0BzSlZ3vvNt1ul8WZJVZXtxmPKorKsseIsRfUuxN0J6KyHiE6tva2OKhrigPL0fk+mYFT\\ny8e4cOkSmxsHnDy7QHagmc3ncGFCOshIkoyX7+wBEwa54eSRJWSs6OR9trbvsr9xh7ynSFzCvaeW\\nSLUm70g219fweZ9iVHD8yFE6PUGoCjRd6t2SWkVE0Nxcv8nR5WNcePEGKlHE5v97Hur7gTPAo8Aa\\n8C//ko/zX/0SQny3EOJpIcTTlbWEWLO5XeC9x1GjY4lOTAs+jpKDOuKCZTypSI2k9m1ctEaQpJqO\\nThjIHjFGEqnQBqoq8NLlq5RNSaSgqQoO7hbUtccET1FMMKrB1xWTsqFuEmLd4dqN2+zXnmsrW7jg\\n+cxL13juhWt87vJ11jfXMdUqK9duo4LD24DqQ+ETziwtcKIr2doYwWTEwpSgS8LdrRG7Owfc3tig\\n9jUrN/ZIgqLfSXnT2SXOLc9jqw4Xb20z2dvmwsYGw/6AJ88vcLRvqA7GHJmfpbPQYWrYAV+QCEXo\\nBExnwMxsn42tXYR0xKLENxO8aKVTQRpSpZmZmT4MhhMoGcmnFhHakEiJrWy7FRcBESERCovEK9GC\\nZkgIUUFsNZRSpFgvaRrHY//NN+N9CsZy93/933nhG9/JK+/+Jlbe8zdpPv0HSNpomkkV0VIQrUOR\\nokJDpjUpgsRIvPftfPAwONCL5hB441sYiFHE6Agh4OoWmB2AVERCU//ZotJGaIXNGU5EogZpG3wU\\nCO8POQCh1akCWkp8FAQfidGgVdslBi8QCiIe4QINgSgUWN9qO4XARU0DrbNMCSpvET7QSzTyMJFX\\nhIA2KbW1OFrLtKOFhrR81gRi250r14JTogAXPJng0J8vWp5tdCjpCER09IcSQgEiID1kUtNYA0Jh\\nvCSiIE6wdYHEUNcOkgQrPJPxLp3cUBf7eAOJ1ExNTTEeb9HElBNLZ5Ai0MsH2ODYOxjTlAcUOzsc\\nPXqcFy/d4ekXLrKxv4FD4lxgeXGR3c0NhEkIAXITGJU1gRSpuzS2YNKM0QIuvnwJYy1L8wvcvnGH\\nGGsmzQbSQFU5CgT9Qca4apifkezvriNEpCpG6MzxwPnzpCajlI461PS7Odv7e9S+g/eeueEJZLDU\\nVYDSEcwEqyqcazCJYGauS9rRRB05mDSUsnrVtesvVVBjjHdjjD7GGICfB15/+KNV4Pifu+uxw2ur\\nh7f/4vX/q8f/uRjj4zHGxzPTdlP5UCKlIhEJPmZ46TGZQSOYTnp/lngqkwyjMqw/nJF5iw0lqgsQ\\niJo2g0hNM0gSjnUV2xt7rI88XkXK8YSXVifsjCYEKShdSScz+Kamtns4p1jqeHx0jIrI8fl5jg4T\\nelIxlyY8dPY8Z49PIT0cmx3yyPFZBrbk7tY+SaJplGV+vsvp6VmurW1wfXOXq5dXGXRnWN2YcP/J\\nDB8biCW7MZCoQJJNuPfkEBc8zgrkeJ/d0Yibmzt0ul1W19bY3bE00bO0OMXcbJ/5XkaeSkKTMzWd\\nYBvJ2qikm2TIWCGCRzQNWij2dkuEizgpCFEzuzALMlKEiMoFMUYyIQmh7WZkiHgiRIeLFmkkIvr2\\n6Bk9ykh0kiBnJaJ/HNcIkIIm1u2Wug7t9jo2pAODSmewEnSicM5hlaGmfT2Hp2q08kRaULKrLS2p\\nL+JkgnU1LV5aoBKQaJKoWxbuoUvKCIHWGg+oGNrjv5Ogs9ZFRSsti0IgIodW1bYgRS1bCEowuMMR\\nmUSA9ziZtOxU1QbhCe9ASIQGbxsk0IRIKhXgKYXAHy7IUIro2uZAm4iS6eGISqFEbM0DgJMKqxK8\\nj7hDeZuVEiVazmrwgJDEIEkQyAh5niOEQMtI1VTUrkEZC84jI8hUo1WG0ZqEQAiBVLSpFYkIKJEg\\nepq97TFN4xGhZG80oqkO2FvbIPiaSV2QR8ncwjwqOvbtNntrWyRR8/pHHmNaTbG6uUkqNUU44OHX\\n3M/i/DRpxzCpK4apBleTKMmp4wsMOh3m5zoM53J0r480iqpqWF3fRvuEomzZtuKgZGGYtiaPOmV+\\nMEdUmkQoFoZT7O7s4auGXp4irGVrfwMjM5IpTb3v2Nm/js4NwVuUkYz3R1RVQ4KgLrZYuXSDF1+5\\njq8r0o6griavujb+pQqqEGL5z337dcCLh7d/C3inECIVQpwG7gE+E2NcAw6EEG8U7SDqPcAHXtWT\\nxYCNkaw0bSchIlE1iEZimzFoQdEUSAlGt8FkjStRGpxo4xaUUqRpjkxN+2asHWXlW/lPOmRpcY6m\\n0QjfcHVjzJnlnJyIKSzrdyfMZCmjOrLrPKNxzd19x5HlefKOxJiWRHTf6SmU6bGzs890r8PEes6f\\nmOLS1Tvkw4w8NeyUBWmUKJ/y/NVVljqevjF8yaP3MpmMeXC5z/G5BUpSBnmXmbzH5fU9dCfjsyur\\nVCLhkWNTHJs/xuWNEUnM6EjD8aWTLM0PWJiexaQJa1sjdKqpi7LN6FEJpXUMejlWVJikR9DtMdKG\\nhpl+hspbf7ulwUiDiLSzx6hQGoKUYFrbrhSBNJo2QE8dziVlW1RiVBhCG4IsM878/E+gps+Tygyl\\nDhF5iUYojRyeYvDP/wVJ2kUSCNHglUB7wAd0sDivIAa8a4uYiJ7UJMTgUQiSCAaDkoIygrAOJ3wL\\nktYSoSTBOZookMIdUqA0CkWgBAKJbi24LeMVkAopLBCQh0sgqSIyEYj/AnIWESkVCk9oHfMoFYk4\\npGlh51miSRPVHr8j+CjbiJjo2zgVrUEnCPl/cPdmMZZl2Xnet6dzzp1izoics7KGzqrqquqBbDab\\npJpN07AMg7JIkKBMEpZoWZQgkoKkB9sQDHiCCQumSQOSQEK2DBOQANky5AmwBMkyKVocmt1sVlV3\\ndU1ZmZVDZMZ8b9zxDHtYfthRbcCQrXrQQ1v3LQOREXEj7l177bX+//uFGDQxtahOCEpoY0OMmUGg\\nARU02umcSXXBcRXReb5sICqNihEvwiJ21M0yH4BJgS0QICpQxiEWqmqNKBpE4bXQ75coiVgVuXv/\\nHkWpKXF0XhhPJyxWOba6ioplXLI8X1FEwK8olWKtXGNUrNFqjzZwsP+Q2gpbWxvMuo6HjyecHh5h\\n+n2sb+jqRN1Jfr5Nzd3Dc/ZPp0zmAR/g4eEpi3qKjwW3dq9QbQ9wXWBysuCw7vjmw2OarkUVluly\\nxfxkwr1Hh1RrI978+tucHC9oQ6AqBnQrw6ppOT89oK1bvC9oziMDt07wKzY3LvF0POFwPGE5aRnt\\nbvN0NWelcv7Dyv+z90wfPT6ObOpvA18CdpRS+8B/CHxJKfVpMrvkAfBnAETkm0qpvwO8DQTg5y42\\n/AA/y/8tm/r7fMwNv9IaZUp0H2Kcga6w6IuPj6iX53m+pBTDUT/b9oLgpaUsNa33FLZEK0/oLCm1\\nuKpPl/IVsU2J47nncLJkd32dT+wNuXGp4PfuzhkONbujDc7O5zijWbRLrm32MZXCL2t6/RJL5K17\\nc9aHI1ZNx3nnWc3OmSwVl9cMhxPP7qbw8HDG7at9xGimtfCpF65w/70HvHZ1k1YiRRAeHh7hk6fA\\n8eb9A0KyjNYK3ns045N76zhbcTY+ZtCPvLx7ibJn8cslpi2ohg5vAqdn5wwKS1Fp7t8b88LzN5l1\\nnnkzwxcFMfbpQkRbUKIQNKtlg+86jCiUKZj6QD8qlApoZVHJoDQoydlPPmmM8UhQkDRBwCpNF1uM\\n0iQxRAIpelzR4xP/9S9x9+4h6atvwsOntNdGuO94medefRFtCkQrQhSsynEsSSeUaJIUhNSiVYnT\\nEC+ipgNQFQYfBK8yMNqoKouwTbaBKlMgkkgpYpxDBw/KAjk2W6QjiaVyBV5WGUoeEySP1pau0/nr\\nRQVa8EljtRBjlh/FGBGbRx1Vkrw8iwnQhGZFMiVRhBAD1hQX8OsOLZEoFm8tLnY4LD5GEh5BE50m\\ndkJpCqISkAsHoErEoBGtMSrRNIItyEusGIkS6bRBScKqCqUSWimCBJzSbI6GOCqCzEnK4qoSVCRq\\nkJhoGs9wVNHFjueuX6Gt5xTasbHeY1kLR0/O6fUd158dUHc1o8tr9LRjuVLsP9xnsDFk0UxJYch3\\nf+cdUlyyEQZovWT35md49P5XMTZxsH+AKgzRaerZGFOs46xlLQxZ6yeGVUE9nXN9VNHvrXNtR+Gn\\nC04XMy5fv0paaA6WU1ZtonCGnZ5jXhgKqbAozs7O6G9U2MowP5mxtbfBqp2xPtxmtlpx7XpF6hTe\\neYSGZHqkUFO2BZt7I9alz8w3JG3xbcCLouh9/FXTP/MzReQn/ikf/m/+Pz7/F4Bf+Kd8/PeBVz72\\nT/bR/wM2ByWNJNJUSMljVI+GhrQKF2FkghKNNoblcg5a4SgoxFK4HHMhtk+lNLNGcmfqc06MSnkz\\nfO3aOgeTwNPpCeKuMlk0XNvo0+s5iJoraz1+/b1TXrntaJPhZLZgJALJ0u/De4/e5xM37jCeT3nt\\n2ha//s4JHx4tuHN9hwdH57hK88ylTd47nNDvKY7OWo4WSy7XW7xxNOHO9XU2yx1AceflZ6lfX3F5\\ne51xrbj78JDtV2/x4YcH2Kri8DyyWcHeWo+n0ZHaQH1yijjHxugSs9kRrddc2dhGE7Eh0qfHN4/G\\nrG9qjFIIF2/GJEiMKCET9sVTlQNE5WtwMoKLeatolCbklREihmQDCo3onFPkxGUPfIqIzhleSQUM\\nmufuXMG+fAVUASmSjMKHBqsEJQZrs33UKk1SGi2BJOSvkQKdCIXWtAmMinReE41kMbo2BDyRBNGh\\nDKjkiRZ62uFTJGoFRuV5bEgURV7A+FSDMtiL208yGqMVNlkgESXPmy2JLuW48pQC4hwSIiB0SlBK\\n0zSeXlHSK0r8RcZZuiDkmwTWGkgOdKIkEiIZdm4thEQyDpsEY7LKQEvufJUGnxSYAF5QNv8NlRZ8\\n9GijMSEfRioJ6iKoMlwAa5RRBN9SmyWVBq0stsiHQfJN3k0kjW8D+RIxQqcVXheUesFqZbl56yo9\\nHTk9PWW0WXF6fMbVnT2KUjF7PGXn8jqXR1d4eHrIG9+4y61rV2lJHD1ZUB2+ie8ii57mcHJMf3iZ\\ns9kxm2WfvlN8+PQAxDKdJpTbQC+WaNejmC8ZT065cekyn7nzPK+/+w7LOi8Yt7bXmU6ndH7J3sbl\\nHGw57dBlhbWa5ek5129d4ezkgEGvT9Ou2BgYsD2QFrdyqIGD1YTB9h7XnutzfjzFrud56fhkQR4r\\nb1B9xFf+GI9veziKEpjNZiwXHsWIKJYQEjYFatVQh0C4iDkuyx5KGQqtSCpj+oLWREmEZkHrFcoY\\nUhBuXL+EaIPvGvpaSLXwaDrm+uY6sZvw8t4GLnhWy46T6Qm/9fYjklf0y4rzs0DbWSaTCYMicda2\\n3Nrd4KiGUWU5mXVIM6dvNc1qwfWtbV64ts54OuMTexuEZPnG4QnallgX+fSNPRKJmODN/TO+9tZ9\\nrA187UHNcnrC97z8LL/17kPun0zZ3t1hFoT95YLxrEX6mktbI1Ro6dD0RiNcUTAoNZt7A8bzGt/M\\nidrzpT/0PdAfojPGmFYiqEhCX8z+Qr7aF3mmpmzOfMJqJCmi5MVLIgGCTvnl49CopJjOxlilsuVU\\nAq3PyxExFmU1kVxwtdZoEfpFn0IulktKZzOrhMxKVRplHEYE+9EsUHIECChEC5UEIkKKQlIac3EA\\nSFJ450jR0EgkJqHoVcSYxwSlLlBJCCGL4eWjLhDAK6RLKJWyM86kzDVQFkkGrwMoQacA5APeqfz8\\nB0UF2tIFwYeA0ZlnarRDG4WoApzCYAgR2uCzcYBAQFOEkClXSkhSYxQkky37SM7wosiw6MKR7/CS\\ntataWdSFXViUJkYBY1DW0ERPoRylL4nG0NFg+xVGJUzhsNZSloItDK4VLl29TJAecTljbXMdY5aU\\nvR7F2oh+v0+76lBKM5nWOFG88OIzTFZTbBHpnGLZdEzPHqGX52xtlVy7uscnXnqOx+Mln37hJcaz\\nIy4Viq2tilnbQBKubfd49c4tXn32Nk0wNE1Dt6oZDjcwleIrX3+L81nN0Fm8iVzdGLJuK6Z1ZFWf\\nc//uKW3bst0fYW1BWQw4my/p9UqSz4fWrM1ozyjg1gJdmKKocKVF6hk6NqSomE1XDEwixA6rDeLT\\nx65X3/YFVZSQtKXXRbQTlOTQvbIc4FtNzwidF4xxnI/PaZo8T5WLF7kiz9+S0miTGOg+rmeYnp2z\\nDD4jQcs1ltHzA8/v8NqtITpaRAuI4tbtPQyGcdtxedTDR4Wval56oc8zz15hkQx/9JXLXLt8i55r\\n+eBoSu0j2xtDdIiMdMFkNqeZT1jNE/cOZ/juHOlW3Lm0zaDQLGJg5VtaX/DazS2SX/HijetIM+HJ\\ndAlV5GBeE6RAdw3rvchps2Jvb4NhT/Pm3X2stVzZvcbkfIwh0qTEYu4pQ8T2Nxifjqknp4TFlKgS\\nkvIbHnEspzO0pJztFCOhsYg2uWgog49QaJXBMjZirKANueCKzphUpVjbGBHSRdY9jtI5tGiEiBEg\\necoLiI2IEGPEK42YBDFilQFlSMbl2a2S3A0qSxRNbHM361PGBnrKvJQxCkfAYLBicEZhYsR91LFZ\\nTWjzYZEUKPIyR5TFxgCSUBJouggmorXJkBcElTRKAqQAKROeolIX3WXGGQoK0YmoheLCpmiNIYhF\\nbH4eIQQktqSuwxNIWEpbIKLRQaO1ImhLSyIFQek+SZvcrRqNNQOoO0wMQEQkLwuVForKgVbElHW3\\nIjG/B5JDYlYMiIWgPNIa+v0tPtKJ6WRRrkAuqG5l1Wcw3CF1DXVYcbp/yHKRWIxPSU2DLuBses6g\\nv44rauo28vRwTN9U3N/fZ00VGGV58aVP0voAvuLSZsXdDz/gMzevcrZa8uzNGwxHm9zYvcSoMHz+\\nc5/lybRjdjrmy2/8AZISlWh8oRgvZpzPa25eu8GljSHLtuGZnXWqtuZzN9fxwXI2PWdrHS4NDe1q\\nSmpg55khV69eJkmkGG6hoqe0jsn5jHYlzM6BzmI3HA8OHjM9bwjG0Td9BpXlcy/e4rnbNxltOmz/\\nXyBiP+SiWOuAUm2mp9uCpqvZHPQZFEOszTJmMfnN2oaOThQBBSF3rKW2WFvQ4lFi8bHC+ZaBdazG\\nR9waGQ5Pax4+nXDYBbSPJDH83juPeTqu+cFPXuHaWsV4vmS3tMzOAwPbY7EIjBP8zlsf8pX3HtIE\\nS51gY6CxrqI1iUeTCXtb6wQiYgq++uQUoyyjkWEchBjzTOjByQE+OS6t9fny3ROaFPmh736W3397\\niUV48UbFJEbeO52xaqFb1hjf59rOGqu65vH+PbZHIwZ6QGVNJjIVFbGruXLtCvc+OKZ0PZIUEEGC\\nIM7TndYkY7IsiLwwUkqRokEn0KqmlTYDMVRBJ3m5l2eGiTYETGwzvg+hQOWRgYB2mrbx+BCJoklt\\nImhNluebLD2KGqXy91QkQmxRIsxXS5Ip8aEldh3WZtqS0QXW5A2+VRaUyV9bB5QxiM6qAh/z60eL\\nweiISeC0IQWPKEuJznEr2oEtMM6ibUWky9dwdTGjjRFRBqcuOuEERllUSiAme+IxaG1oY8KI5M5U\\nKUxMeVFlJUurlMIqh0i+ZiMBTJY4hSRYZamsoiosRmUlQQRS1ChnMxPAWEKM2TWVcsieNQqrNE4L\\nRkNEEJ2pbFo0fVdiFFgb6BYNZn0EwRNNIIYOR/6baJsTYBfLmo2NLbTtY7rIaLPk/Uf7rKZz+m5E\\nbBZ4qVi/tM3QrlOpkpAST54c4FS+MfZ21nj2hcu8c/8h6B53D48okmdy1rCoG0b9bcoYOTo6oFMd\\n7x/P8PUK5Ru+4xOXmcxapnXkeNqyWJzy4dMJC99weLTk0dk5J8vA3u4GZTngpdvPYqoNZnVk2p4z\\nP6+ZPT2m17uE0h29/pDBsGA4KFnvDzEOXGHwbWDgDN4K231hEk5Y1h5ESPU5i2mNln+BOlQFdD5S\\n6YIUdRZTS0dhS6LXpDjHhIT3jBlHtAAAIABJREFUkeCzPz3GhJWITpFAfnFHpZn6HKEhPuDbBTc2\\ntlgszun1etw/bVl2iTZZtrTm5vY6XaFxTcP33rrE77x3zPHZggfnLdvr21jg73/tLgfTCTdHl/C6\\noiqHfN+dXZIE9vqb3LhWsmw8P/jqdd47mKCKIW/t76OVy3lFYrEp8JV9z/3zjrMOSjS/8+YHSBeo\\npOOr75wRpOHVvXVu7V1hcrLglb0N1g30BkITVkwXczY2+mxvjfC+5my2JNYNuoRHR8eMj+esmiVn\\n9ZjQZIIOJiFaCKvE7PwMpYQQ40U0dAE+X4/R+ltGAB8TPiVUiojvcg6WGArlcK7MQGqlWXWBLqRM\\nEkqKsixxzuaYFJNjUZLWKBEKYzLcWXV0TYtgcn3Rmspm8IlDo6yhIG/WMQmiRpOICpwSjHYYFCm0\\nxAQKQ2EVYjWRCMqgTJZQdfqCTi8RLQolmWGQUkRoAYtJmqT1hXzJolJLQKFTXgpBQimNJc92nRJ8\\nSIh2JKtxzl10+FkpYbWDlC54CTlapXSKqBQpZlRicaEsCJLw3tP4mkje1ItLKPKhpFIkGp0z7rXD\\n6YIYMlHtW6kKAF4wkl1UJllSV+TDwyRGg3XEaLQYgmTt7sovWXYdEhUimit7l9FFxZVbNwhR8cLN\\nZyhdwZW9y4zW1xgNe0jsEZYTFl1iVSdsv+DVO9d5dHTCatzyu1/+OkGVFwGZcBYr7h0/4XS+4N79\\nd2kkcHnnGipkvW50BZ986Q5/8OSMfgk3ru0xaxeE1Gdzd52RNYhKJByKxHw+5fR0xmQ15nw5oV5M\\nef7KDYZ2QDmwdPMTlvM5bYz4pHBU1HFKUZWsFkuOnxzRactab8CkMUxPG/au3GS1WjCbzCAaYoz/\\nr/Xp//n4ti+ooOgViigBozSSwFSZM2kHkWkntOKzJCcpgvcX1x+Ll4TRCmUcSjs2XYlXgmihZwy2\\n8FT9TdbWKnY2NK+9sEuzEkqJvP5kiW8Ue/0er5/O8FYY7Yz4rudGfHgy53yZ+PTza+xsbDBfzqhr\\nz+eubiIiXFsvadKM05OOg7Mpk7MFO/0BUToiwubagD/82Vv4JjHuOl661ucHX7nC529d4mjccuf6\\nVRoMizSgDYZ/84vPM+g5/vc33ufReMGVjZKbQ+Hp8QpbVNzZu8zu3iZaND4YBpVmOKqYnM7ZHFYU\\npWPhQ442Np4UQCWFiKJ0jsn4NMvLqgJNoiqHqLJAmY4okW+pPFMWvGuts6kCwaouQ52Dh5RQIujC\\nYTU5aE5dCElFKFzuQo3RxBgQLTRdXojpaCmdy9d5pSAlkmhC7Agp4KJBJNFIhKhJCEosMbSZWB9r\\nYszX9Uo0LnaIgI55tukjhKQAzcj1ATBaMFoItPk1AzkKRhTBZOdVilkNklSB1RajIlE5lASSylHT\\nXue5rkVInScpxXJVE/EosjU2SgAjdAlEEhJqUorZKKDAKMFj6ZJc/LoSxlh0FIySiwNOY4wQu0iB\\nptAaUR0pdGhSxqOmhA8tYgy61HRKSNLSxmVmvoYVSUeGPU2XJAcUhosi3gaqqmI5OWU+O+PJ8VMG\\nBp4+eoR00HUrlO3RhgWr0JG6ltXsKbvP7rB9eYPv+/yn+MzL11n4REGfelGzvrPL9tU97j064PqV\\n5yhMpMTw0u1neffghKJYY//pIz75ief4vs++xp3rlzk8esJ8usB1NcvlCXvrfQ5ODkid59VPfRe6\\nKLl5fY82BppaeOmZa8SocL2K/uY2Pnnq1YLFqqUcrdPfXqMc9nDGsuxWJLEUyVCMHLvbV4iNJ3SK\\n5XJJ4Qx1fcZo5HCKfFv5SAz9MR7f9vg+pRQq9oiyIETJYW6dYxkVse0YWE30+U1ojWBU1hQWNi8/\\nUIHohRiXiIJekbe5tY8sTwOHdcPl7QGq6HP/cMG1nTUu75TIcYdqWq5t7RAXp5RjxYOnT/H928Sy\\n4N6Dh3zf9rMc+TlvnwW2XGR/vsLXgWf3Sgb9HTY2S1IQvvFgzNpGyXbf8p3X9+i7ITEIq9hyZfsS\\ni+k5i9mcLlmuPzNgOvY8t7ZBcrs05zVvH0z43UdzjFieGRjeOJjyo1/8LOeHSx6Nz9jYXOfue49Y\\nv36JFAYUA4sP4EqLcz2C8xTG8eSoIyqDLcHoglXXYXols/0TNqND6KAQVvUC1R+AMRBBK0eSFm0s\\nOgnESOFKYswLH11UoDWSOkQFqqBZoujpmBUY2uODQ6mIkYTSlrIqaZqGSitCF8AZguR1mU4RLzHb\\nLLsOtCLpCy6qOLQVSJqoPaUqcudIQedXODF0JpISuOjpxBPahKtGWTEq0EZIIWW9KQoTDcop8B5X\\n9PES0Cngg8Jqg7K5+GRxVE4aTcqhxeNFYSIULutTsSZzAIwmJAM+YG1BsgW6DSSbFRbJXORuIUS/\\nwpkekqBSMRP1TUnyLRQFWuVDTLTCSgRy/lH0CSMWpXOUihaNLkqgAa0wbR6RYApGRba3In1cYXl0\\neMhwe482JGazGUpberZPJw3zyUOMdAx1n1V9zq2bl2lMZHy+pDKOoydjru5eotULtkbb7O/vs713\\nhclkgsJhSs1sNqO4ucsffO2buA/v8tzzt/nyO6+T2ghFyXQ1ZjAYMbBCjIoPH7xPEod2Pb7w8nWe\\nvbzg8HzG6STw/N6Ilz7xEodnE5rZKboc8eToiM+9cIv9ozNWnTCZzdlYv8LGmmbVzlgpw9AW1E2i\\n0iVn5yf0VYHVitPTc+LGGs2qYWe7YNFWLGgoij7L8QzTnrF1eZukA/3+AF26j12vvu0LakLQLmJ9\\n5ndKjCRbgCwJKlGZMtv4lMLHQERRWUuhHMqYnBrpPFAhMYGz+MZjtwtmteelvXWeTFtSpdles3x4\\nHJk3ZyCW9c0B56woRKGM5vLmNXpS8cbjh3zxlZuoYoSSmrB03Li8w/l0ytgZnp6c8JlXtvnmg6cg\\nJWWZAdi/93DMlz7zHO/de8rJYkqZFMMty8Zaj/5an/OTJWf7c4ZDOD2ZkkrLpKl5aXePl68kPnx0\\nxMG84F/6rmeoxyeYssQC+0dTnr1+if2ZoNUZ9XxF4SqUCCkETK/k9HDMk/mYZ9ohyVuUC9jSkYLH\\nKiAEgkkUdbZDSsqLE5ELu6UE+tqyTAYrQpck+8CdpWuWFKKhKEEsLR3Gajpx2JSItfDoz/9pfLtA\\nUtZlapM4P59RFJa14RWe/5VfoFYbaCP4bsXDP/+X8OdPSSJgIqkoePlXfw0pBS29rE4Q8BeBdEoC\\n1lo0Os9hv/E6j/+zv4x3BV19jhjNjZ/9Sda/99/A2yytiiTEaIh5FKDNR2+HPHfFpFz8pMWivwVD\\nEUkYZ/L1XxvERHyIfNTHpLrJB5fVYIQogupahEhx93c5+uADbnzxj7AqNjMZ3g6J3YzXf/pf51La\\nIOKxRckHReAH/qu/g7aWoA1W26yDdQE1ecpXfvE/hj7YZ+7w2k/8BWLq0AEqpb+VkyUp61Hrrs0O\\nNxvxbeL2rRfZn04YjArqtEbn62zk6DS+0fQvX+XpZMx61SeYxHvvPOKTL9whFpEdoxjtjOhWlqeH\\nB9x64XkW9ZwrN69w8uSMSzsjXDFicnSM04amE0ap5fs/+wpff+d9+rag53o8nO4ztI61fp9COTpn\\nGTjNJd1x77DmvaMxn3z2GquTKQeh4WBSsz1y3N7awaqGx08fs763w4N7j9je3ubs4DGrZcWgdPRN\\niUorTqcdvaZga2OHjkRYJTb2Njk4POHZG7cZjw+pfcfu7h7z5ZRy3bK1PmK5XCG2h0qKuq4/dr36\\n/8GVHxbeEwsLYnC9ii60VLGHFcO4W+FdpvkURUFlSmIytKElhECMGW7ryPIXpQxt6viDe8fEoDmb\\nC20KrE6XfHDU4tSCfm/IznqfEDS///CURSfs9ByFhV7Z8aXn95imkqPTKRubQ0Z9wwfHU06WgujE\\n/spyOguIMoyXLQfLJUFZnr085Lfefkx/o8R7g0jNsBwRLzzrl3oDRpt9FqbPGw+PufdkynPXrxJd\\nx6rz/Pj3f4rPPTOknhzz+48aZq3CE7i01+N0esp0eoZJlukyUKiKd45nnMxqxpOa9d0dbt+8ge2t\\nYZ1cFEqN6MhsMkNswKWAL2MGHSsFVoipIcQlKmR/eqGzrtGJx5PQKlFZh3Flvn5KJk1JUllJ4AqK\\nwtHNpqj5HL1cIrNT1HjGMEX6XaAeH2R/vgO88M6f+rPE8UPwHgkNtlHYScMHP/dvoWODuVg+amUp\\nXZG/n8qZ9pHI8T/8dR78p/8RPjQwP6PwmiJEjv7K3+Tp//BXQeoM1zYG5RWiIb8VBKMiWoFXGpWE\\nmLrsKEoaiYKNAVMUACRtc5d+sXFPxKzztA7jHBJTtqiqlLtrVfD1v/RLHPz1/5knb75FMHlOy/yY\\n+3/iT7LdVnT1Amlb/KLh1jjw8Od+GukSNoGOHlQiJMXJ+4/YeHef7TePWPsHv42kiLUZaiMSCSEr\\nIUSyTnvy9BClExIEQmTZzlA6UM9resZS2AorDqN7nM0OsXVLv1+xtbfOzsYGn/rsF5DYcHxwyO7O\\nHtP9M/qFxpqS8+Mj0rLDYbh2fY/zwxnzyT7RBl67/Qw9EU4XK87HE3Z391hzPc6WY1xZsGhWHJ2d\\nsT5ao6csa70Rj48nrCSQjONoPKPrafq9NYbacTqvefdojBLNxtomy0nLYDDkaDzDG8PGZo8W4Wx5\\njhts0e9VXN/t0XQNBYrlZE4Cnr15i6BqRqNtNnsln/zkDa4/fwVjoREIXrPySxrfoXT3sWvVt31B\\nTUkYVX2CCqjgUAioFl96YlJUCpLOW7juIupA2UTPDuiZ7IW20RC1RRUKSBgKti9vo7Xm+GSC7xJX\\nNwdcHsJrL96kWy0JKGrxvLC5zvVbe5SjEf2eo24Ndw+WXLt1mWQrutUKsUtCXXM8W/La83soCgqj\\nWS4CV69eJ4hi2VnqoNgYjNAifOHONbq6wdcNp6sF796b8o/fu8/r7z3gjftHeFMwWYwpfMtX3z5k\\ns1fyj964y29/eMonX36JQW/I3QNPFE1R9DDlJQa9Nc7nC0LR5+7ZAXE1ZlI3rBWJ+4+PMKqhUNmG\\nKSmA8iCaHgGJIBpMLOm8JymLipbC9DFFibYFPnYkiSCCGJdZqoHsI1cJnQzKaEgRHT1ahBiz7z5P\\nBmM2B3SR0Hl0+mgs43FYdNS8+wv/JXY+xvsO9ZEXP2YJk19M+V9/7seJPs9KncnyuJQC6kK3qruW\\n87/xNzCNR5qQIdYpQfAgJYu/+4+Iqzk2kouOhkIU2sTMfggBIcumouTM9kJyJ2usJrniongKEvLh\\nEzuPaIVRFp8ionQ2VDhFxF0gEDX4SGks/Z5iMTvPxK8UuPszfxa9akAUPV1QFBUqdVRakQ4m/C9/\\n4d8GCXQISDZSvPfWe2gjiI+E04bRwd287EuJGPOCTmL++UQZhsMRQee5MbGgbVsMGqU0q7YFLShj\\ncIXCFJvsXrtC3Xkef/CY6SqSZmM8ib2dPZqlp1obMp0Ehv1eBlgXBbOzI95+602i6aiGA9pOk0Tz\\n4jM3OFu1dFHxjbsP0H3LpY1dok9876vP8OlPPkezHLNRRVgtSXXgZDJFSLxydYPNXsHJ8QQzWGc0\\n3GDaek5nMx6crTibrXhyes7WwPDKc9ehbVjO5oTOcffeY5bnkWamSG3gww8eQB/ODxfMzmpirXj7\\n3XssQsU/+Sf3eProiLXeJrVKvLN/TFxV+LBgVX98Yv+3fUG1WtM0DUVwCC1N6rBqCElTphZUge00\\nzlpSUgSJqARRPCF5RIRGcsuuoyUFoXQGouX529e5trtN34BxDat6wuP9MbEqOG9qNgcFpop85Z0P\\naWLNSgUWqyUvPLvJk/sP6BnPo4MpH+xPqaPiuVt7VL1NOok8mI25+/Sce+/f4/bIcN4s+cztIYsu\\ncHhwj6/cG/PyC5eZNh2VK3LEh7Z86uVrVCnRw/BTP/z9fP3BGU2TQS+fur3L0PX4+v0FVc8QXced\\nW1c5nnTM2inz0GEcHJ1MOJm2/Mufe42QIq8/PGK6ahlPG+q2wYlCGweicze6UhgVcKKxpaYqhiQS\\nEgOQkFbQ9mJTrSxJK1ICnQSn8jJQqQw2sT6hbCSZElB0XZf1rQm0dsSksWWB0w4dNbELqGgJaIzR\\n2PfezO4syUQnUuYxiEkUqeCVWQ+JHRaIoSGpiHYGjEIlxdf+5v+ETR1aZ7usFYUu8gKOuiGqwJf/\\n2i/SEkE7iIlVyFZXubDWYgRHLoqQ9bZCNiloEVzIcJeec1jlMK7KxV0EYx1J58t/iKC0oELCiCKq\\ngNee1Ca6OuJE0fMaG5dEG1BEurjCJEgouqAwBl6dtIQoFDovwUwSVocHqOTopIYi8I2/+O/y3p/+\\ncfSDr6ClA11hlKbQoJNnfjYmRk8QS3J5S45YghL6zkDo6FJDEsXV7S1Wyw5rhLPTU1azKUtZ8fDB\\nIdEo3N4tFsuWVFpiqdFmxNPTY1x/jVu3n6P2HaTAoOeYLWbMo6cq8sxbYbi+s0WvKHnh5i4HRxOo\\nz/n0ndt8/tWX+cLL13npM1f5sT/2Q/zoH/5+Rr0NknXcmy/ZXy0YrI04mS/oX96jTYrTpiGYPlKt\\nY3uOotzizovPsHtpwO5Gn90rBd4kfApsXVlHuo5J63lr/wmnJw3Xbm2ju8ioL5iq4P3jBV/55gFT\\n0RTrfVTQ9Kvhx69X/3zL3z//R0yJhELpDm3KLIeyNbJMuL6DrkZMR2gCRa+g6g0pnCYpR+waTFnR\\nM5C0oXA5SlgSLManvP7WhNonmpA48X22B7vcP53ziZ2KYCuWwXIy17x4exMVOxQj3jl6wtbVbdoE\\ntA3TJKyjWXSeG6PAV9+6i7OeDx6PGfQsdlQwcxVlu+I33zpEhYLG3aQVz1k3JPkFhSu5tV3y5FhY\\nLnt0aIx1LJcLHk8alqtEsz/j4dDzhz7/Ku8+PaevExvOEIqSLu6z/3TJs5+4w+vvP2BlNNZU/MO7\\nxxRrG1R9j288vV4PnCEmjb5wLImGykQUFUE6TJ1AJxQJZQR8nhdKDER1ATD2WfbTEDA6KwNC02Kc\\npSOhU4EioZ1BiWYw6MPWTaStMVHRygopaprJIZUrSdKRQguEizmgITqVZ94//PPo/+2/h+YE8vKf\\nngu0qkRJgVEaxGTZlxbKb35A7FokrLCuhwxG1Fe+wODBP6YJM6qmwH7wiB7QkWV1vaJ3oYqNtFGy\\nOlYLRhWgPE3UWIkXETuZgSpimNcrjDEkpQgduOyUyLPli99xjOC1wSmwpofyESRQlg5Rwq/9/M/y\\nGZ81sqro0x+MaNSQQo8JviN5hSoSfaeJmKxuUZZmtSRGQWOQpFFRoSeed/69X+DFv/U/kqTJ3Wo+\\nCjjff5yjt23uUnuVY9F5TNSsgidSoiLEuuX9J57z1ZzSJhjt8ub+CV2XzRD3v/wOlX2f3lrWp0al\\nGZSWum54cvIAtBCTwaiaum0Iuo9Oga7uiEzZ2+7zlfcPQGowJYVTvHU2YTgcEe+f0jeaRdSUgzHe\\ntwwG66zaDlUN8SK8uX/KzmCN4/0z7MbmRXiiYb9dMvugZascEc/OOZ3P2Nra4u47Jxkq1iu5vvsM\\nd88esZwv0EXFabtive6z9A16HDg/P8dLhSkMhTG0TYvtjVD241tPv+0LqtYKo/qUBUSfkKjxTQtF\\nDxsV270eIS1ou0BvUOG9J2GxWpNQDExJsBHVttl5kixaebb2tukC6HVN2SpUipy1Ebe2x70mkLoW\\nx4ImwvGDDmsDRpaE3hZffeuEoCJInts2OuJGhl+/N2XU62PLNSptCRa6YLFK09vexidhaAuarmNo\\nDWatYrXaIITE1yctsrXL29MOu75NVQ359fdqys1L9LYTMXk6hvzmW/cIqcHqCq0VH8yeoFSf2O/x\\n5qOnmNGIkTJoDMNRxWoZMFWPsjTEwoDqSKLwZN+8EU2KOcStjBGqzDQFRRCNtRpREE3EmoIYI8rp\\nzOmMGsijF104kMwTjfgc5ewz8WteL3jhV/7zDE4hJ8j+xl/5W1z/zb+NpIR1jp7rUxZ98JlxGoLH\\n/fGf4tkf+yH+j2+8y3MPv4wNDahAWI2h7KGrvFGHgGhDQqjOxyiTkFigrOHqf/GXqXZv89ZPvo6z\\nNUELbtnRxoS1mf8Q6LLkCKE0jkTAommIuVBJg1BkK6hEki6wSpBUAXm0oU0GP5uiIHYdaJCo4WIc\\nFaPggycaRfKG1dN9bqB4JbUUaHSZExCaP/rHeeGP/QRf+7k/x+jwAUF30C3oF45JEyhN5qm28wXO\\nQvCOJtVU1hGkRdkeHRFDidI+22eV4vW3vsGGBEzQpJjogqeL3YVB44IhKwUMeozWr/Hy7jb3D45Z\\nrJaodkGRLPVqwt66JV85DPQiXfQ0piCVFY4sNQJLFGFtLcvbYvRsunyDCXWk0x1bW1cJKdJ1HTsb\\nWzS+o9jeIhroJZUtyl3CR4cqeqBrJApVaQjSIl1HGRJmuIV2WUfsVcWj1TLfRqpNxqGgG66ho6LT\\nhnvHR4ix7OzdJEqgNZrDRUvQmiIFqDYplCLpSEGf4Xa2squPbMkf4/FtX1BBkfyK7luaQcHaAb0E\\nbezQtuWV67cZ19/IV9KclYFyULoBXgnrw026kLO2EaHzkb4t2Nke8gNf/C6aWeC8bqj6lrODU+5+\\neEDXTRE6XOtwxhIlZRI9EasNoc0Qhb4tgWyl7CuVtZdF/nfvYnnhbInWjpHToBRDCdQiBO/ZvnKd\\n21c3eefeGT6usLHE6YbhqKDuPN/zuc/w279/n3p6ivcNur9O8A3iDKrtLuj4FlEdUa0hqsGpPlpZ\\nQDEYDuhkRakHdHGJaIMzgjUQY8JLx/JkQV8JUWtSMkRqrNpAi8IrUG0AK/h2ii0cIRQY6XL8SJM1\\nwP6CI6qFvMxxBh87tDFYV2aTRUooLsTsKaFwRBpC9KxWcyp3GXXjVVxs0QmufPfnSSmi13qgOqIW\\nwDHdf8LoxVsX3vULoEpKpBAREeq2ZtQfEJOhv3sVKTSd8hil0VahTKKyBn9hf7W2JInP5gXpsnBe\\nS/YOKUAcSmeoNikhElARUBebfR/RRUWIHu19RvxdQEpsUiTJSymnNS72UGaFKE1LzLNriUhoSUnz\\nmR/7UeYu8s70nE9pRalLklYM+hXn7QofBZMCvpnlRaxWqF6PVG3TdDDa2gBdZsmZyZyAJIFVPWct\\nRUQJYl2GxHQNrgej0Yima9kc9DOQWsNP/Ykf4etvvEdnNL/x936TOjT0bNYmhwTJL3G9IZeKIXVo\\nKFSJoqPxGmcT9cqTUqBUmtTrUZiCoCJqmFAURBF2h0NWq0TTLVkbJgZltngGqxEfCBiUdoSmJvo+\\nPrTUPuB9oD9cw9kehUSgonCG4Vof77Ou2XerPF4aKsrBkKSBGKhwF2ohEBuxwdKkcyozxHdCMgHn\\nivwcdYIuZKngx3x82xdUhaIaFHl50TToIiFREU0gGWgWBd88vYcri+yQEYOoiAkGX0Z6g0He7jtN\\nYQ2t76iqIeiC7/uOVzk/H6Olz2KxwNeK2geuX9/m6MhRtxP+le//TrpW8fpbD7l5Y0CctdhKsfKJ\\nW3uX+Or7T5CmJUiJc4E7L9zmhduX+Hv/4MtIShhjGG6tYYoRN66MKG0fZSvWqwJdj9m7ucv8fIpB\\nuLT1AsvlnMuXNti6dBO/XPLgaJ+XrhdUd17h8PFTPjw5pRqso3xkmUCZrMKMSaFJlNUmOpWEkEgy\\nZzha44c+/x3MFnPmbUtp1nBFQZsMWglGF5SppZPs9tGuuuhQJTNKO0GRuPfTP0tqTjE9x42/+kvY\\n4TXe+MVfofiD30BrePmXf5649Yd472f+FCrVVM5y5Vf/Gkn3aEODXk64++d+nr6GtR/7XlQHkhZY\\nHEGVBBJn7YLnf/k/yHrPLlCWFmUq6HIHWZYj2i53ZillcbuyiovqRk9bnCtRrqTzAW0iIXYQC/r/\\nyS9TiCJJYJQsITka39DrF3jvMcYgqeH+z/wMxb/6Mtd/9C/yd//9f4er7SHN+QSKii/96n+H0RWJ\\nQJiNefxn/iRRDMpApzyf+G9/Dc86cFHkRRBTomNAdCTEQHLgusTZ+YTnAS06W2eNwihDt1qgqh1G\\nW7sUExi4iqWsqLsL8lWK+XBb5EVJSIEuJno/+Ud49Yd/hC7mZWNMgmAIWmM6RT2doq1FhYREoesa\\ninJISh2zZQYoxyCMhgXbvQHvf/Mup6f7bIw2+JF/7bs5nDa8/c1vZGng6ZTgYWM04Atf/BJbwwJl\\n4O4HbzHqb7Oqp1y+fjn/XoPhwf5j1kfbFL1EM29pE7z00m2Oj09ZSz0au+Tg6SmXtndJ0VBUkd5A\\nMV85zo6OOTk54rnbl2jNFl/7P7+CN31OT8cUNuFsyUsvvcjVG7eYTw+5fes1FvMnHB6POTiYsqwz\\n26NpGjY2dinLHrPpMYONdWbLGf8Xd+8ZdHl61mdeT/qHk9789ts5T09Pz0xP0GgULI1gJLDAJBsw\\nSWuLXe/KWFrAQMHWGhuzxqXdogwObFFbtosCE9YmCxElhCKTY/dM5/zmdPL5hyfsh+dF+3W2Cqqm\\n9nzqD119+q33nPv/PPd9/64rywwz6iQ613R3lymqPEaGGeNtRhEG6LT9luvV276gBgJST3ClioRz\\nGRv1ucnZ7NYII7C1wNUeG0oSk6GNRhtIlIHKMphMyJsp47GIX5zgOHWgg0oTUumRruTB+xYZjibc\\n35glb6f0tsaMLYSyRHjLex/Zx3A04sKdLv2tG3z9N/w9mq2Ed0jBeDxA5h2awTG/b4abV6/RaRWc\\nPvEoC7NTWDei8jDVSXFW4/yAlfU+pw4ssbPV4969LU6fmGdtY43pqRYyWHrbb3DhzTVu31lhtzcm\\nbicoUl3z/q/7IJlUfOaZG1TjdZCK6XyG0gXe/9gpesOS2XbKVq9HkhU8+/JV5joGJzTZtKN2FUKk\\nIBXCWRq5QmmNri0YB3vaHsN6AAAgAElEQVRyt8pLZBL4y0/+EvPjdYStsL7kjR/4Ad7xC79K+uXP\\nEsQYFwTXfvD/5L7/9BB2NEDaAQOTcVhKrAjI3oTLH/sB5LjL0Dkmv/hpDmuJRGEl+FCRmoSJiBP/\\nUAyZPPOnNM4cwe57jKQcIWxKVQ+QaYodWXKlsNIhg9ojRjmcsoyTnAYKJQMuKFrKUuA5e3oRpXSc\\n4guJNo6mTmKEVcdTmasEbjhh/Ftf5F5Ima8TFpZ7mKCwSfxZfPD43XVufex/IhQThAekRhvB8o/9\\nMCd/9t8wkFNILVHeIuUegNoLhNYomTBy0JApSmlGJqGRNLBuiAyWv/iFf8t7/9ef5sP/7t/gRVy9\\nWDCwPp7E/39VYdKUIAoCcdhnjELlOcFHTYzGEpSmri2ZjL6vUXeIdB4vVERYsRfJdX/1QLJ4pyCk\\nfOO3fJDLVy+RJ03Wd3cp3RTb67dZX73EcJIh0GRJSnftGq//0RZZK+GR8++k0d9Apgl6tMXu5XXG\\ngy0GdysWjy+SpJLP/9mL3Lp3l8X5RcZvdlicMmw1Uqg0YlRT9m+yfuUms1NLcOIguzuBfUsZDz3+\\nCLe2Nti8dYWHnjjExddvYHxJUYDpCI4cXGBuSiJsgmCD2cU2R44e5PrlK2z1Njly8BSd2Tmsm5Am\\nOdeuX+LRBx/hpQsXmDOzJLMJWih6xUHGwxHXr67y2JPv48IrrzI9dZh2J+MPf/933lK9evsX1ADO\\nKkyimBSeRJdUBOwYqtIjNVgfr51CxT/7usYFjdECLzPK4Tauygky0ExThqOKVbHDm1cuksg2Dxxc\\n4ErYJRPTuKLPwlzKVn9IW7SxxjPSOdurPfI5TX9nRGJKkJrBdsG1q7c4f+4YL752kaX9c7x44Q6P\\nPrDIEw+c4ObqOh2xQn/UYKwaiLrPeCsQ7Dqm0Fy+sUzhepy//xRqc5sOYzZujai6OyzuX6K9e5Mj\\nzQ77nOVyv4RUULucyxcvsq/dYiopOHtgPy++cZn5/fs43JGk4yEz2nDrwiUWpnLqrYpFFLK7xpkD\\nR7lx8x5egA92L8qoCBO+2kO0VUnlCoyaRut4mpm9/gbC1gQEug7kdc2zP/5x2tJGGHNwODvm0j/9\\nGNqPKR2ItKRyjlQlvPm//EvyYkQpAx3dYBKiLaFysUdbVo7RsCCdlvjVDW780D/Bu4oVodBNQ7rv\\nPlwYoYWEUFGXE4Lc21TwEdfnCKigsO95L+p3XsfXQ9CSN777H7C8mPHoJz5B+9xTSB0jrKGO6Sin\\nwt76kEArhRTRazX5gy+CmiNYC15T65iz195x8eM/RFrX2BAIWpHUgko6Rvc2ufA/fIz7/vOvQQg4\\nD0Vdk5r4njWW4d/5x+i5Ds35qHjuPvgQnc/9PsHWiMRw6NqleM00CSoEhFSUZYnRKcZbZJ7jypLO\\nXmG00uG0wHiP8CUxrBUovCDRAmcDUifYahy5AKFGJoqg1B6yUNJIMvykRGYDklqyfv1ldtc26W1u\\nE+yIa1QkZj+NxiFG4x1anZzRaMDHv/d7Wb/0DLYsuPfKi2SJZOPua8zNJtiywNWGVCmuXnwTyRWa\\nItBSgq9/+AiHjx5k/c4af/a559g/3eDh0w9y6fJFtro1rfaICxdfYUZ0uH5rxJ3XLqBzgysdn/vi\\nXWwyjWg3Eb4idYZOU1HsbrBQrVDdHnCnqOldeYGFuUWa88dYu3WFP/pvL3Dw6FHe+3VP8dnf+x3c\\nbpf+6hbZ4RPcXFmn3O3SXd3m/nc8zpmDU3z+U/+Vc48+xssvPE/enH7L9eptX1Cl8HimcK5HblIq\\nBLJqINMeShgqV6ClYWq+wcZ6j3ZDIoWhspbJZELZG9LQCSKUCKkY+THCl3g/hSsVmap5/tol7mvP\\nsevusZgrLl3r4lzJC+UuHzh5kFvL2/S6fcodSafT4YlHH2dw5TUaWUDvrDJYrUl6I2SribNddK/B\\nb/7FRb7m/HnkxpDNquBUvsubb9zByzaHZ2q0WkDoPpktaKRwb/kKL1zdYV/bc3PNUF+8ylJiOXD8\\nGC/eWWWnLFncv0Sat7hxp8eT75zBdUec2DfLYHeJqn+drTWLProPbZvorU2Wtxrc7C3zrifP8eaL\\naxinmWkGfA1GBeq6BqWRvkRYjyMu+mudoIXbIyeBLidoNFWokVmOCYHpjW1scNjao4UmaA27FcE7\\nWtJQeYnauyGInatYVaOqKFNURkHt0RKs3BMpNhp4UXP1J/45oXKoIMikww4d86PXcT46mISPO7Ae\\n0D6GNVIClYoErfN//31c++KXMdsvIyqPaksWVrpsL28gT5URxiJENLbWHmUEjhjjHPX6OF9iQyCZ\\n5BzWawhyvI8W1MQbQjEiq0c47xB5QlVOkGRoqUBCWYyRVASnUBKEdAhtwAVSn/Pgd72PxNdUShKC\\n4IOf+Bg3vvA5EgXeldS9PrNJzY6QSHKCt3upt4gx9N6Dksw1ZgmTAa72UAdaczM4WZHqjMJpRF3v\\nKW5qtIN6PEYBaEPtK0JtCVqgpEaa2PeuxmM6qeTSy8+TKwm7Q5bX78KkweypJlaOGPY2mEyatBsp\\ndtAltKbZ2LrBrdUV5qeaJHmDflUyuDfBLLTJ2jndbc+11S2OLXX40CP3M7OvybOf/RyvrW7xxPkz\\n3HzzHiQV290Bx5bmuHNrmTppsP9Ig4vLq3zgzGFu316mtoqRDZh6B9OQdFrTyFTxxhsvoXrrzJg2\\n1y49w/z8LATNzvIKF16+yk5hGUwKGtLwn37un7G9O831559FSthav0mmDQ5HI8259+qXWJib4kw7\\n5aDoc3F3G7N8+63Xq7+hOvjX9nI+4N2IyknKcoJxkKoRSS1wTMilxgfLxtoWSnpsGVmfjTRDSMPc\\nfIck08xMNVEy4EuLEppRf8j9+1NmOooH52fJ88CRuQampTm1f47zJ07x2L4OK6ubNFzN6X0ZnSTH\\nB8FM3kHqlOXlbbqV476pKc4dbTErJrzv2DRfev1NpqsSVdzBBti4cJM/euk6ly5doru+i7Itnr98\\njUm3x7GFOZ595gp//pWrlN11lrcrTs4lzOtAc+4I040WVWlpzzTi+lEwnDtygtWVe6R+wN07N2n4\\nEiaequpz+9YKw2GftfEaldvA9Qb072zxLR96L7eWb9MvI4Yv+CQqcoNHSIvTSWR2qggqRknQCRKJ\\ndBM8Di0ku8cOsfCzv4xIZiKCLwRqERA/8a8JnQeovSIkHq8tBMeFT/8ZSYikZC8Fqt1G/vOfp97/\\nBDIx+KLEOwh4RF3CeBB/7yFQhUhPirZagw51BJZIReojzKSq6rgXG6IdV5s2K8l69FBJRzEukShM\\n3qSV50gkWkUzQWQCBFQAi+TQ/BxSxvdzwSIqBWFAicWEGAr5vX/xSayNgwrpYOa//3H4xE9HiEpt\\nUZMaubWBE3EvNngNLkBQ1MEjZIyE6r0dYCeisK92fUpRI0PCM9//UfCCoi4IIdpYjYik6YgslIS6\\nonJ1BP94qIPDkOFRCCxSKyoRsYBSBey4ACXxoUapNqKRInRC8GOGwwkQCLbkA9/wHkbLA1555QpV\\nMWZp4RDn3/0go+0Nnn/uCnljnk4jRzrY1SW2O0RmDW6ujxkUlmY2x/bqgHRxjtWVTZb7W3T7YxyC\\nI1MzbGxt8/t/8ByqbXjnuftZnD7A/mM5dhiYn11kXGvWJhWtZkrSSnjisQf508+/yahwbG9242cj\\nzUjSKaTQDMYDDh0+xpH9+yi7u5i8g/A1t++tsuvj8LORB44fPcja7hr3HXqI8wcWSETJsBxxd32d\\nW+v3WO2OGBVder2azW7NYGtEsb7BE6eXeO+7zr7levW2L6hSSTSLKAmlqlCJwRsodZOm0kzquLwf\\n0PggSJXE+khyr7zFlgFvYbM32lMKRwfRYGgZ9WuOLzVxPqG2nt2BYFxanr+9zRcu3aLSgpnpFgtt\\nzcpE4FSNFIGsEXAYtrcnnJvLuXrnDre3Jly/ucL63e24N2odZw8s4krLqRMZ5bDH3IEHOHygybga\\ncGBesr41ot93lGXNsaUmJ08fo1H16ZYjFudmaeSOUW+Xmak2ShmqyjEZrTMYrLIxUty4s06LCuHG\\nvHJjjSAMB5qG7nCNd77n3Vzt9/m2Dz5KbUd86YU3MXmDowcPIIUmyBoVIMiAUzXBDtDS4UOcmNeu\\nInEVPtTUfkSNw6I4/xM/SuNQC5cdwopIQt8+usjxR05z97GH0ELjioC3DiEl9o3bVK4g1BYjE4Zf\\n9xRHz59m7gc+ilQ52iT4FLyt+eNf/HV81SVQYN1fqaID3sUoa+k8HkVzuokXMVSQmiTCVVxE2X3+\\npz7J0dUVhItG1zp4pEqYP3I8Glf3sIFx0isRe1oUIQQTFKNzX0fxnf+MsO8JUAXSaRKtcErhEBzv\\nV4i9VSCC5sCHn+bY0w9jEx0HgxK++Bu/HoMHArTWscViLT7USCKqr/YOJQ2JVFw8cgYpDFpI6rpG\\nDPt09ABlPEoIvPJ45QnBY3yM3SbCx5ZNvSdInJrGSxmLvYqQ6UylZFqDjQ+JwkcUoWOML8q9L1hK\\nkqbUISCEYbw25sD9+5hdWmJ1Y0A9Ety+foOpluGx8w+SippgS4rg+NKX3oBWzue+coGFqTblKOA1\\nIGqmmhkhSyhGNSFP2N9pgFMU5ZAnzu6n2Vrk8NxBVlZu0E4XmJ7POLX/IBvbPXacxqcaWQzZuX2X\\nVtvwwOGjkawfAlneipBy6TBS0VANisGY7VHFaDzm3naXteGY/k6PZmsKLAy3dpnqNNkdwlpvg/5w\\nSJbkzM7MYJghm1TUhWCnVzAeAlnNzbsr3Fje5g///LW3Xq/+JorgX+fLO4dnnVQJGiZnXFWIWjGa\\nDCkQlEFQ1fEDpKRkGOo9g2RBq2OogyNYRxJAyBTvA4UN7J+fpZMIXr+5SWELhmXN8qDL6ysFicqY\\nagrKScLWboHODZNhXGL2tkTJFDseMvYlmzsjnJlFi4zZTpPt3pAnD0/zoccPs7Zbcn1txLWNikTC\\n+x87wFZ3lzsbAxYbS5w9krMx2qI/6nHo4AK99Q3OnjnOYjNh/2wDO3FoA2U1igvk3uCD4PwjBzh/\\nZAljA3/64uuoNOFrzx3BugaTMmH1dsEff/YC3/HgCbaHE7STlHVFv+f5zAuXow1TSaqqQspIShLS\\n7A38HEYpMhVP40IapEgRXmGEj+tazuPyJtI3wTqG04ogHYeOHyZJNE54EFF7THeAkB6fJVhhOfn0\\nuwhSsP8d5xh9/KdwH/s/EJ/4JC5bon23R8AgnSDJEqonH+PYr/4mJp1HmBAjjgaymUW8lNReUIVI\\nr1dCgq05+OZr+KAQPhYy87Xfwanf+iPc4knqqiL9qzgsArTACfVVtmgVBpz/yU/w8Hc8ybGf/wmE\\nnsPGiAOF2COa9dYJHkgSBqlECo20nixPQQpkDeX6PUoCIuxBVwJIrUlUNAwEGXvXSgSElHzrz30S\\nn8+CihqYUJa8/H3fjyg9hfAoHx8oHoVXAZBkApQUOCWQ1tNptXEigmeUl3gkLgRqr6i0x0yKaCCI\\ndFmEEGRSkum4aiQFSCzPvPQcde3A1jxw9hQuCYRgaCztx1nojXZxKqWZZnEtqbKUGJaLCbfGFZ95\\n8SKHD5/m3vItyt6QYlTSECm7ozEugUazyRs3trl5a4PLNy4x35hi7fYyr1y6wp+88BpT+/fjEdy8\\nPWLu8HHGzrE+GHHxxlWGgwlHlpbY3dqKenEXH7iFtVy9cpugAqgB9bBmv87xRcH+lmM4KtDSMejV\\njIY7CCT7ZmZpiIowHuGEZ2p2GuU8s7MZld1ia3dI5TzzU3N86Hu+/S3Xq7d9QZVCkWcwLj2hEhjp\\nmPgKE5rYSYnUKZIQY4MoDJLUaFTwyEJBUaOUJDiPdRNECDSNwcgSGzwzbc1SS7NbBIy3PLIv58hU\\nxmwqaMoBzVZClgcarRylNKnOOXFwjmurPY4utVgZj/HK8MbtVaYywe3ukHFdImVGNalY3r3Noq54\\n/NRR7t1bYf7AIda7fbZ6mwxGiiv3xhzZP8+XL2xiGgkbu0NubfbYndQsTLewXmILTV2X1EUfR2D3\\n7ip3d9Y4cWyWJ86ew44rPnvhHjNtQa0kheiRuILLa9us3N1hp7tOPRlx9sAChxYb8dTkQaYZSEXw\\nGpsqhIeJjfucpbM4sxenDB6TaiYhxCGhkFH1rCq81swu7iMExdbGbgw8SIVXVUyYTCp8GdUqyksW\\nDx1DeSjKMWfedR+nPnCGU+88g0fQmYyQmaUyEi0SHvjhf4JOM8azR6lEHYmjlWVx39IeNd+jpYPM\\nYBFRu5xWUJa4EAhJxskf+H48NTpJaDSbe/HSSNnXxJ9NOoEMEuc0XkXugEgEPs2QudjjYWq8t+jg\\nEMQ4KSrB+3GkmtV5HJCZhJ3lTagdNY6ApQwBG+o9rY5E+Nh3Fnvg4tQobj3+XrRMCCLB+wy3O+TG\\np34F6oD3Hisg0TKaXF0UJkoiwCXoBmmrw6g3Agd1CBi1B0b2FcYLGs0OIniCK5EYAnuw7XIS1+eI\\nhgtDStpscne9x0xrmiTJuL66wRc//xJ3b1wiyBxJybCasNUbcOfuJidPzjCVZAhn+dvvfBhjarTI\\nOXb/KXaGQ5rKo4Tk4o17GG8wvuaJx+9jemGOlc11pGkRipRMTrh5a4VEa07NKS69/BKPnj3Ew4uN\\n6K46cIDB1t24kaLjXrDQirWdLcZijtsrPVp6hpA6Tt+/j/m5DmUB9x1cxNWWTqeDFgaTNhmOIj/i\\n0PQ8C02Fw9CeasSwh0yoJwXXVwbs9jd5/s/fkqA51qu/kSr41/gKBCZViMxLkWKFiykcNaIIgbIs\\nY39PKZACG3yMv1lJtXetqauCwtVoaSj3RF3SKfK8iQyS/iTw+JEZlhbnmBQWJyqcn2WjF0MAxUii\\n2xlGOBw1xW6Xd5w7jhCGGV3TG41ptASX7uzy0MnjXF3e4Q9euMpfXF7m4QP7aXY6bBTrZFmDYuse\\n9x2a443lPjJtcmzK8NqddU7MK7wTXLg3pG0kxWSbiavJjEbkNYKM2jsEioaQLN/b5MbaFmUoqKXG\\niorgJZsDT0mHYVVyYXmXy1s9tusGSZIxP9/i6Pw0zsUcuQDwAd3OyFULbwyVVkhlCN7v6WN03Cd0\\nsZ0SQsAIR8ku7BG8pvcdwAgoNwd4Dx6JlBqCJHM1SIvysb0gVI73knvPv8KX/un38cbPfJyv/Mj3\\nkI3eRFUjqCQaj8VjXYqQElSF9DH+J4NgbWsXEeL1P1gFNia0mmmGm5SULu4o1zkoLbEevvxDH+Fz\\n//Pf48s/+J289q8/jvMW5wLVHlAkSI+WOgYTtEZL8E5gi7/ivsbrvPeWUFu8lqgsoVYJAoWT0WFl\\nQ8W+fQeQ2qC1QQRJKkD7QHBFhKrUFhE8IpF4mRAcfOgnfpBJZwmjJalMkDKgfusP0cojdAxNDMsJ\\nQsQ5sg8VZbAoEdXZQiva7WlqUeGdpizH8QukJEmjTTONNw0bYhy1quKBIpiaqrRYa/HekiQJSzPz\\nPHR6kWt3b1DVA6wX2FBhncAYxbjySDSllnzgu76RD7/jST5wbgkjAvNNyXg45M7qMlev3GBqfolS\\nWILwKCU5/9BxKl/wwl++xJ0bV3ChTWcqpZcMWTp8H8JZHj98lKefeifDieczz7/C3S5YGXjl7jK7\\nNnJonXNkaYqragbO8re/8ynm8oI3V27jvWN9tU9V1UyZhMFggMo0zaZjqz8i2BjLJdE4adHBIsKI\\n/mjIxs6E0tY05xaYakaxZGrSt1yv3vYFVUiJUA2KosLrCl81IGiUbERaUQAbwAUbCUaEryoLnHOk\\nQmGSjDw1BAFJGsnqeR64ttHnwvIO3XrIm/eG3FjZpVcFpjs519bXOLiQRjBHldDfWqMoLAbFdLPB\\n8u17XLi2TrPTZmVtlYXWArshp7e+zn0HGrz77AxPP3YEi2Rnx3Fhueb6+oDn10teuNelDhUv3drh\\n5Y0BIyc5sjDNYqp44sg8J5Y6rO/WvHlrlaoMdFSG9x4jHEF4Okqw2etzdWvEc9e3mW5rnjh1OKLj\\nii4PH53l8GzG0+eO8viRJT58/hBHFxpcWhnw6rUbeByVlGgiANpVI5yxZEIxkTUOh1Qawx5jVIoI\\nvNApMjFxRamsYyxPeLKpBk5q5rKoLMEEvGzjQxzUGBf3h6VPKUZjkJa1166z784uzdfXOHi34u6F\\ny9QuMhcICqc1WoAUAlnFgiGCxitNMAk2WLwtY8KprrAIcqPJnMDkkZHrfAcALQMHVhyLb26xcKcg\\nfXOVRMVYqBBiD2cYqEMEmksnwOn4YFYTnBK4VEe4NQ4SvQdVKZEuIhRltSctlOATgQfqKhCkiQ8k\\nnYJMMXh01kCEKI/UBkIq8dZy5hf+LVIragqEAzmu+PTHvj2mxfZSRyo4UGCCwASJkwprXRTJeYcx\\nhkRZjMnQMq6UWTchpwQV4d4y2JhKkuCsoD3dRMhAqjStmQbPP/sCa+sFnek2/f6YOkCiNVrttVdk\\njlKCalzw85/897z+2nM8+8Yq003DRrfg2Zdukmct0tYs3c01tGqQCsVH/8e/yxeefZUzJ47Qmwy4\\nN6i4uLnKUCuKgWLRGD7wyCmeevIBvvLl59kpSvpjycSXVCZnt7ZICZV3WOvpD3tUznL64CFe/vKX\\neW0bJmqK973zPew7vMDQaKamM8pxwdnjx2ipFrqp6I9HHFycYne3Tyoc7dyQCY21lgRNCI5ed0gh\\nEjaGIxqdt77Y/7YvqFme0R8MmOsYVEiZyg1elmhVIqWmdnH6StAIFcnmBI8L0Ule2RFlFbcDbB0x\\nbGMXWB1Fj3pZWDLdJMsTDk9LdCi5t73NwfkZdno1vta8tHqH3KRoqZjUlt6w4vZOnzQ1XN0omMkz\\nesNN3ndujq3ScfLwUf7stV0+9dwVnr3R47Vul+AFJ/c1OTNlONTuMG8anF/IODKt2JdmbK13eW21\\nQjHhpWvrKJ9waHaK7cGA3WJC8EN87ZEK3twc08wbPHRojnzc5XOv3KKdtfA28Oj7z3H57g6PHj/A\\nZDLh+t27vHZrk972AO1GpOzZLYXfi6x6dL9AVFA7h2nEuCw+gDYopZAmrtbUdY0UGTooBB4vI8DY\\nNKdwzjIYFpGCXxmsnKCFxmuJVYogIFDHm0Lw1Ls9jEmpyhGVKFG6Ewt0cFSVBVuidPx46jCOOmsV\\np9uJyWioNtc+8t3c/sj3cvsjHyEdr1BYRx1AuUAlBaXfiQ9X67FMqGX8rFgrcXtX3jR4rIv9zERL\\nRBAoEWIvM0wwUqLTFCs8hffRaQUEYfEyIdUOE6B0w4g3JNA4fBLhLEYFhLMkSRq3CRAI4Yj3HBsH\\nULVFWkiMxEzlDL/lo5F85RKsd9zft+iyT+UkQhqKEGHfwYqYCAwaI1LSqdYeNlDglIwa9eARSlLb\\ngBIa3J4UEMmkdvgCjIDRsEuetBlPHK0sxxs4efIkKysrPHTuJIUPZFIQgsIkDaraMZlMMHlKMj3H\\ny5fXuD0q2CwEJstptSRj06IqRhzZ1yJR0brwS//xU9zuW168eZdd0SbrTOM8DPoVthyTthV//soN\\n/sNv/xnP71iqSpMmOdiEA7NLCB9Fm1PtWZppQnAeZTQLU/tp1p79U1OcbDdYWb7Da69doeEramc5\\ndmiO3d0ul9bWsSPHiaUlul1HJ2+DyvAhQZQlk7KItyELWSaZaiQkpo3ircNR3vYF1dYVjVRTVQVe\\nFwzGNdY7vFNMhEeq2PQ3STRmir0FdaNSRrbCOo1OMkpivyW4yORcmG7w4Xc8yIceO4SXjvXeDvvn\\nmoDi5tqIg7Nt1roVJnd86P4ldnYHuBA/pLLVBGGZzgzvOD5LQ0t0bbiz3EUJzV+8doOJn9AbeZ68\\nf44Ez/1zHbbXtvmGDz/BdNszqifUznJqdpYjcym6qZhteF641eXwzBR5o0Eng/ncYF1J1uhgOjmJ\\naXBosUXnyEG2hyWH5mcYVpJEltzd2uV3P/0CH3jgIM/dvMkzVzd58qEjvOcdD7BbFMy2Mu5srFA7\\nh7NRbZzrHDsek+CpRUlzdgGEx+NBSGTpqe0II3OUUgihY/InFCQqRoKbnTYSSdHfwtsyUvH2bKHV\\n9AJBij23VEBsXsM6x9SNy1g3wiQJidC0jx5mohugBFkK0jqEqwh1gZp0wQtCKONVHk8DTxiOsZMB\\nVdknzZpMigKpDSIRaASqqnDBEooRJlQoGVNBVSOeSKVUVEGQSIV0UTFiEbgQUFITvMILkE6xYx2J\\n94w9RIVUQlYMqFEIX5GIeA3XtaR18ljcIpACJ6F2ARciqMTJaE0VKkEaRQg13lX42hJGNQ985zey\\nPPcgwpQUwuJqz+sf/yip4P+dE0iFrD2lC/hgqeoxZW+MMfE0LNkT9QmJCKCCJ9NJ9GIJRYUlSQXC\\nBCon8UExrAdo5VBpYO32Bsv3bjKzeJwvvXwN4Wq0afF3P/AebFWSNwxKZ+AFXjZ46rGzZASOzB2k\\n2RE4l7Kxs83qdo8bG31u7UwYB0U3eEqj2BpHK+v6VpcPP3qGkwsNvu3rv5Z27vmWDz3IE8fafNO5\\n4zz15En2NSTWj/jyqy/TnoZWq0FvMqKwNQGJ8po/+NQfMykG7Pb6+FHA+ZokS9k/s499MylKeyon\\n6G31OLTQYGN7iE8DZTlB5YJyOEbmgno0odNISfMmRTFmUpVUdsSdte23XK/e9ov9VWXJlWDiFUbH\\n1JIOfergmRGa7crjJLjS0khSgo+T4EQpQjBIpajrmlQnVK4mFxKRGJ76mndy4dk7CKnpjgbcd7CF\\nDW1We9v0LExPCzIDBxc7PPfyRUYjSWNGU9uc7X7BifkcNEgrWN5YY2quxc07u7znkaNs7Yy4t93n\\nm+6foypHzAKHc4nKUn7jt59he1zy9PED7PiSxbkOn3n1Cu++/whiRrE03UYiMJR4pbiz5bDWROjH\\nqMboAmSTNy5e555d4KkAACAASURBVGvecZzr129RWsebV++w3u8xN7/AH7x6lacfe5A3vvQ6L13a\\nYPjqNZxtsLCkcUIgAesi2tCXJcJYikFB4hNuD1fwDrQyVGVBa2oe4+KqGVIQjIo9lqCoQkA4ST43\\ni9CBeXJEotHBIxrzoA33mhnHbYUQEmcEV370ZxGJJK08PhhQIDCIzizD2Vnau5IgUpSQXPiH34Uw\\nbdS4RkiPFCl1KyNRCdsrayhlovZqT5YXdKCuPM5OUErQFDm9X/5ptv/kdVRZ46XDa0M1v4DHIW08\\n8Qch4kOidiDtVwlD2laEoBAy0GpGyLZrHcLbHYSXtKzD1jVrf/h74DxZNourLY889SH6KJSSeCdA\\neqQ01DaANRGHWBckUnP1o9+LGdcQamoR1d9HJxU1hlQCpSAvNYUPGCUAB1rhXUnDpISqImhFa3aW\\njd1u1Mt4gTIFVSlQosYjKSZD0rQkVAEjM4KK61RaOmTWoKEytBG89OI1nv66p3nhhZe4eOEaS8f2\\n00gMR5MGzz/7JRaOnGY88DElFjzvefpp7Mt/CtbxNe89zp//yV9y/4lFdl+5yFPv/1tsrq5wo1ux\\nstmlJTNyLAtTGWkmODw7z+KMoapmmJru8IVn7rK55bn/xDxTh6a58cYd5g+2udUv2K48jVJQTwZ0\\niPSnUI2pGlN0i11OHDvK4ROKf/ANX8uv/9f/hkNhq8CNtQGuUmz0dphutun1AnNzc0xGJa2pBrdu\\nr3B4/hBlXXBwZj9Iyeqwx1Ri6BYgJDT+P9Srt/0J1SQJEyypTCgKSW7HJD5HOE8lSvq+BqcwKomn\\nR/VXU9FAqgImJCgkdV3F65CH00v7+NXf+DyuWOHeVkXVG3L1bpfnL19hsdXiycPT3LzXY3syYWN3\\nhMwPk2cGJebRSTy11FWg1x3QG9d05jusbnlOLM3xhdeXeXl5jdk8yuPWV8Y8ed9hZKZxleJgs8m3\\nPnKaKzsFDaG5ur3No0f3E2ROXSu2xyOWt7c5sTTPoC/R2iKFJQBJmmKUoq4Eh5uOGze3aGUZpS3Z\\ndDnpzCH2ze/j2IzEjwdMpzCeTFiaWeDp9z/E2m6Xd58/g9ACZXKUl+BBewfW4dEsHjqHkg0ckei+\\nu7GLlBW+qsh0A5xFoPG2RNQ11nhmppeQKJyrYztA5WxO1vHW8b3/249gGvMkRkbItCspBxUq+Dgk\\ndIEqNSjd5Jv/9x+n9ilGSkJwJGWOHBRfBT9XoUC89904NL2NHWyo8NYjak3hFHhJMrVIluWkMkdb\\nx/bvPI+u96Kl0mAQ7P/wdyKFwRmJD3Ho5oJE7sVXXZDUQiGcw4dJHFplbawbcWV6muA1BhBlye3v\\n+laqX/0vjOuCuppgDQylRgQLtd+j+3uCBaEkak9lrfWe+K0UWDvGWgvlEEYFrnZ7TNqAkG5vFhBP\\nqMELXOVJjaS2e0yGkDIaTvBSU9QTrK9x1d7+qovbD9O6g7EeEQxep5goYSUIxcbdu7GN4GoarcDr\\nz13k8SfexYnj+9jd7uOVY+nUAru1YmW9z7gekpoGGsHzz3yBC0VGpQ2bK9vcd2I/QWjyziFuXb5B\\npgzbWwOSwSai6rNVlNwZeq7tWDqnH+by9S4rW1v84ac+w61NS6vTpN8f8hu/9xVWuzW7PehPanIh\\nwFoq66hETUVBozOFtB4tMg622iyV8IXPf5mtQc3DJ05TC8e19S4iy5i4mtnZBkePLHFnZ4teXdDd\\nnbA4v0Bdl3hvGbkug8mQVtKCJONgJ2FOG0J46/i+t31BdVUN1lB4E5mPvqLZySNeS6XYuiYot5dN\\nF3GxWaTYqsZ5SRkmhBBI0wwl4t9bXr3KUgvudisqN+JD73uA+U6Dpek2+5fmuTfssbK6yoH5nNGw\\nJjMWqRIQsSgPl1d4Y6XH7fVuFLvVgdOzORe3tyl8RV0rpqczpMo4dmyORAn6RYHShvXdLvrAEkvT\\nmoX5GTKp6ZYVz1++QpY5el1Pr6+ZuMDYVoxtPME18xZS1wQFjakm1wYxOdQbBZrSMSf7nJ2WrK5s\\ncXmj5tNvLnN6vs1D9x/isUfO0L23ivGOL730BsIHdFAgPElqqIPH2YAmYKZSpCgRaIxSlJMKRxqt\\npJQxRSQdQkiC9kgkZR0tCdrH63hVO5zQoBN6doiYfSDGPZ3AiYxcg7M1QgSMT0n+4UeRJme72kU1\\nZhhXESpe+xIVJC5E5mqaTXH2+/4RhMDqyjZGKbyQCFFF1qqBVw8+iM9y6rqMKhatwNZ4LRFOQzrN\\n0fe/CyklKsREWJLk8fSnJEFGUr/0LjYYg8GYlKIqQLX4yM/9JCLN8TojCBnbIg5MkuKsx00fIM1y\\npDG44OIUPmiCEJjg478bJrE1JQVxiuex4wJhTWxzKg3OYuuwxzYNBCXQEiQeR5zKKyWoZY0zsfXl\\nrSPXGcHXe/hBSRByD4JdIKwg8RGIM6mG1FgCDnTUrGvVoN8refAdZ3j+xRcIXnB0YQbhFL/3+Vfx\\nKKQrkUFhfQ9Bzbd9x3fxzX//I0gJaWq5dned0kr+1gMHaM8v8NrtLU7sb8D0AqOQopTi+ELOuPbc\\nvvAG+4+2uL2xi0wM06nm3OF51roDzi7sZ1+z5tXrq3ipeOzoCQprsT4qXJQzjEZj0JKHzpyl3+tx\\n9tw5Nld2GA5qFhc6bI67LLQ6GOnJfRyyrq3do8ay1h2xUU0QzrG9M2SnrhnXKUuzi2S6RIqaYhKo\\nXB1li2/x9bYvqLEdFAhuzHBcYPLAZq9PJ1F4P6bAo5Ui2WvEK6ORwpKaCMzQElIdZWpKpsgkY7bd\\n4sDsHKfn58lQvHJrhyOH9zE32+HynZssZm2StMXDBw4xrAoICm8CZR0QXjEMiqoc8z1PP8p2b8jY\\nKVZHYyZlJLt/y2MnePLYImiBMikTH82XE6d5z6NnWLt8m7Y2bO/ew7nAVLPDBx85z6gqOXBglnef\\nnULWIFWTwWDCGBiO+thRgpKSK3fXeO/pg2xPat73yFmOzGRM6sBmLela8EHyzY+c5N0P30+iCq7c\\nWuHiao+Tp0+Qz8whMF9VeiRCE0qHzAQ+SUimZ3DCoxEEael1Byid402bUhmkSdA+GhRQOdI0Uc0W\\nXioqWyJV5E229y+hAB0Up//9v0A2lmJxCw5rJEkyTVAJ9sEHOfr001EVEqDxw/8Snc1EfYfJ4zpS\\nq43INOX3fYQqb+FQTPq7UeFiY69SKBBe8m0/8zGcOYFqNiBL8S5Ep1TwhEzBx/4xSqZgLUrGnVAR\\nfNx2QCFDiIVDJgQR++81JVOLR1BKMMbTPfju+J4BVGYovCVYS9KY4qF/9x/wnpi93zMiiOCQam/R\\nX8X1v0g9E4gg9tTYEis9QkFpJ3hhUInAaA0iRQpBHWLvMZUabTwiNPb6sI66CihNDC3oJLZYggcd\\nh2BUliADVgm8qJFJjpIGJQ2NPI/bEsaR5xmDjW2aUx1OnT3I5TvXWNsZovL4c0qlo8nVA4nht3/1\\n1/jd//uXuW92jolo0uuPmCxvsby+ys7qCtP7Zri+PaJCkijBTKq4sNrdi0skfPorb3Lg4DwTJXjw\\n/jP8xcVLnOjs4+yZRZzPOH1okdRIBnLCJGik1jQ6UygdAZMpDuMtazffpNUa0OrkvOf8GdY21yhG\\nAcsYbTroVDPxgdnpJrONBjuF4+5uTeklWcdwZ61P7STNbAZRCaqiphI1iRRY+/8rHmokI7VUgvMW\\n7xo0sWwMhyRkqGqAt4FKhQij8ArR0JSFR5oYDPDSgxBxwb+q6Q4lN+hTTCZo6SkdXNsekLQ0C62c\\nelhSigZ//NwFFhemaKeK2gVSKpBNyn7J+fsX+fxLN+g0M+4WgeFogqCirCV3VrdZmMlQStGYbrO+\\nvM309DTd3S1Gk2mCq1ixJfsbORuDms3ePXbrASFIQlGiGxk3NnrcXe+yVdUo4wgoUDV1HZia2cfl\\nF25AqPjKi6/z8LmzNNUEEWbJxFWeWXbsOzzHr/zOXyJzTaeTU0iB8zWtvEX8inly3SBJclAVwoJq\\nGJqL+zDBYF1FUCnNE7O0fv3XqAtI2hmhnGBTmPn5X6TTalN4xzjEodOhn/pRbH9IUIqZMEJKRVAe\\nheXkr/xfvPCvfpPG7edJfI1vTiG+77s5e/4koHHWgknZ//BB/C/9F2792L9C9W+hEqB9FPWP/jtO\\nPXicJDi8UPhWm+1z34CyimYeqKRCCxAm577//Emu/MxvIC58FrJdhNKQzpH8yI+x/+Gj1CEO14Kd\\ngDEETwzWyoi1IzhcqDCLZ6jsJK5rzc7HE7EXvPPnf5wvfOJnOdB9Bqoak01DZ4qpn/wkPldR4xJk\\nLFh4hAFvPfXe5zlIifUOJT21Tcg684SyAKvQ0iJygXcGhAVR4hs53gW0CjihcXVN0DO4fEilMrSo\\nCDhkEFgR+bXBCwLggkZRoIOILR7nsBnxhhECIgTW1tYwMmEyCdSp57mLN+jVNZev3KaYWJzy6DqP\\n8HSVksqcShRkaYssT3jf176P2RuvcmX9NlakzB9scvHGXR49fYY3lu9EGWIQJCKlmNQ0Q+Cb3vcE\\ndy+8yrd/8Cl+97OfRVk4PtXk8TMn6W/v0htbbuyuU1pFUTku317DpIqJlchJSXuqRaPVwrkRhx85\\nxbW//DKXrt/FWk/hxgwGA4KF7nafi8s7jJzHdBVTx1PSZoMzx1qMNlcRnQ66N+bRo5IZk7K6fpU8\\naTO12GJndYtKRLD9W329/QuqkBgpUE5Ti7gHVxDxb0EFrIhXHyklOAuJx45LhJSkISPRksoXEExc\\n6RGBSel58tQUK9ue1ZFl1C84Pj9Nu9Fge7DNocNTzNc1s3mbrbGgTvs02w2k0nhRoZrT3Hn9Jqu9\\nITOtimZ7il5Zs9RI2T/9/3D3nsGWZed53rPS3vvkm2/fDtPd09OTMAGYwQwCAROgDVIGi8GiCapI\\niYQCWSKlslhlqUjZ5XKoogCXZQVbFk2RtAnTokiIYhQoUGAAAQaEAWaACT3T07n75nTyDiv5x7qg\\nUSqamB/8wfL+c0/te/c9fW6f/Z21vu99nzfn8oVVdo+mSCO4uXlI4SOz6ZTVbo/gBF/cmiJchV9c\\nYLesef+bzvHZu2O6UjKflNwSke1JxLoaKwWd1krqxYmCEB39lSd486OSdg7z2ZiPP3edb3jLOp/4\\n0kssCIUIgj/87FUunV9jSXfYjYJHVld4/cYhT739zYgYMWiaEKhnc0LjyBc6xNGEzLRxeITJ8dGT\\nFwbfRPIChGuI2qCFZ22tT5AS3fikbRSO1QGohQF1cGShg0PgpCAGhQyet/43fwkVvpvmZOKtVQY0\\nBDx4jYqOKB0xlzz8j/9uer1ElDRE5ZBC41VEhMATf+Gd6Pe/CyEi6JiKhRDYaJEm4+KPfgBlvofo\\nLSJCEyJ5YRAhEZgypQi0kDG5j3IUIUbwkAlFAB785/+A2jq899yvoZxP6Ha7+Oh57//296ljSae1\\nSjmb4uMcKbIELhHpGoknCEmLnKloMARsEAgVaaIlI+PyL3wEKU7Sa7XAOpkcX9GjlCHEGhkFwmhq\\nF9HB4ZXi/p/8KYKKON8gYqREIWUkkwXe1kQpMV7g0CmyRevEPTUC6VJUUJYZRFQURYZ1NZlqszdO\\n8SYdC2QtbB7R6ETbR+OFp8GhZZemaTh2DS+9/DzffqnPnS8c8eQji1RDyZs2Fvm69z3Lx/7nV1Gq\\nwDlLp5vTjRE3LvnOv/kt/PD3fYbH/YxT/WXe8tj9FLnnF3/rSzx2aoHX7lzncO45s7TAzsziYrLT\\nLq0uI7RBKk30MMg6nFk/xTe951mu775IlJ7heE7VVKwuLVDGgpt393jfY2/iy3e3EP0BN67cYdbM\\naZxn9OodVjoZi5nnqAlsHs1508Uu070xq48scev1Q5T9M5RNCSHOCSF+VwjxihDiZSHE3zk5vySE\\n+IQQ4vWTr4tfdc3fF0JcE0K8JoT4pq86/7QQ4sWT7/0vImlp/tQjxoDzFq8jmc7JjMcKhcHhUtg5\\ndeOIziP0SXa6iRghiVqmbUo0COlo7IyKOe2e5ndf3eLuqOLtj6xwabnL6dWcytfUszFf2ttie+q4\\nsLrIg2dabB5FfDNPvxfDvVnNa7sHTMo5R5VlWDZkyvDQmQGFFHz2y1fZGh5xY3OHaRmovKZxnt06\\nEHODUBmlFlyZzJnbKUchEGzgeG45dWGNg4llbz7lwCm07BDzjEz3KbqRwWDATAhu7hzybz93hWkw\\nyG6f9fP38+5HzzCTETM7pBIZy+0Bv/fKC9y4eRdFZGl1hVa3mzSKJ3ARnbUxJscfz/C14PwDF4gY\\nBJYchW08Pjg4kQDF6AhRJl+/bwgxEkSgCYlOH08msJVON/pXerNJIRlTommW0hOCdCn2OxZoI/EE\\notAJGq1aoFwi8huBForgHD6G1Ko4mc4jNSoqopKgJMKliFWda9qZQesMYTLaRYG0aW2eqQwPeDdH\\neEekgSxHiAQvRwqizHHBE4VAqFRgi24XGwRBaoJ0GGVw9TFaWQqToZTCiRTDooVOJoEIla/QIeA4\\nMQ5EQSZM6tnGSIip0IdoyKIg0qCESMQrNEopCJZcxJP2hkRlCQ+YmYLMtBJRyqcPYQCCw4qIEenv\\nFWuLlwXeNXglKFSOEJIAzKcTGlI44yNrPfqm4hvf9zAPPXyaJy6d4X3vuEShBWNbo/Okj211M/Ju\\nwemFNb73u76Lf/KRX+fSg/ezuTmlu5axV0X+1c//OrlSDGJJS3kyoC5rNoXiB37oH1MWfe5ubXNx\\ntcW9V2/x8hdf5+vfcp57hyNuHDU8fv9pbh2NCFriVcPaqfuSIiRYRMyQIXJff50rr73MzeEN9o9q\\nbBkZzT256TCynso2EDWfu3qLwgu++NIdatug8z5BZBzNKnShefY97+XawZj2+jm+cHOfoRN86o/u\\nYusW09q94YL6RlaoDvgvY4xfFEL0gC8IIT4BfBD47Rjjh4UQPwr8KPAjQohHgb8EvAk4DfyWEOLB\\nGKMHfhz4fuCzwG8AfwH4U42yEciDYh6gm1tms0hGxFmJyTxN1BR5zldW5UpJvJcU7ZzKB3JhUt6S\\n90TTIcsKStWmjnP6rVWOUIwWVinGM65u7XN6ocXhXs3q2YyPvbJJu+gxchVZEMh2RqvfhfYqjz/9\\nbqZHOxgPk2Chl/PSLMfoPhOTkc08ptPjcDKhoEQHT2vQ4ua9Q84vW57fy8FGJqrHtDEcNDUxCu6+\\ntoVtJFmnje9krK0sMqscRdHB2jlveeoJbt7aZu3hR+lna7x4fEh7aY3/+7duoJWljBmdB9/GrdmU\\nrXpOvnEJEwXzpVPcen2P8ywRrcAUkhCSztP5KuUgURKVAWEJssBFi3AOpTKMiAmaoU9C4pzAxByU\\nJYiUxlmQhlFCNORCYPEnBcugcnOCxQtIbwGT3E91xKokbldKY30KoiM6bNAUJmUuSeGppUr0pOhB\\niAQmwSGFx5FjiASj04oOhXWJWEUA6SNOaQwRZx0YULpNSyvqKJOtV4gTR3tiQUQl0NFiEYmepSVC\\n62R6kAEfA1FIIhoZU2pqEwOaE/1qjMREBkDIE+2rTnATSMU1NxLvHUJrPAKpQTiDEBKFgyjT9Whs\\niEglkThENEQU3tdkWUZ0kkwGosjwoSZGmSJifE3mFQqFrUtkK7FnIy71ZiWMhyOKLGdaVbx8EHBN\\n4Df/8C6r/Q5Fr8Unr9wkdLtsDNZpYaiNI3poyTbv/7Zn+J/+u3/A/RdPc/XGNtsHnv1ml6WWYnp0\\nm0H3FKOppQk17/6Gt/PRX/sdpDF8x9/4G3zsp/9P2ucf4KXnrsBkh/vWV/nE83eIMuOZR9aQ5+6n\\nPVcwa2h3BhSZZ2V9FZ0t8+hTD7E8UKwsZfzKz/4Oi6fatNqWqwf72CawOy/J85zhdI7sdHDSU+YD\\npLAUwnHx3Fl2D8dsrC7hG8tvfOo5GtFhNj5AdbqM6wlRF5TKotSf4ZY/xrgNbJ88ngghrgBngG8D\\n3nPyYx8BPgn8yMn5n48x1sBNIcQ14FkhxC2gH2P8zMmb6f8Cvp2vUVCJEHNNKzpqJ2i12ogwY1QZ\\nbNDIeIyzgUBMvnMkQkPRX6GDSL0qakxeIEJEygyZC/J8kTpUbG1rjuclUwG6t8CBEvSWWtyeVyyc\\nOcdkOCGKDqLIkJlBasPD96/RLzI6T9yHFg2dYoHGz/DWQdRUPq0uonPsHM5oXM3WjZvMfaQOGdfL\\nHosrAm9haXmZF48b1Mo58IFWqGiJ1JrIbKDxilOr67zlzRcZ7h+jCs3TT9zHaLzHd337u9g92D+x\\nNgZ2tkaYzgp1WbJQNHz25buE2RiC5LkrOxAazj/0aPKGe6icp9XUKbveCbzUTOaObNmAt2iT0dQe\\nIwU2RNAKJSTKe5wQWOkJAVRMW1tBBBEQ6GR79B6Td5I7Rxqiq1NCaFQEGYkpuB4ZPeiAswFFwMcW\\nQkaUUNjoUUDjIsaItFUVSdqEL5F5dgJrSfzTeCJkDyes1igC0UViltS93lVEHVFRoxTMbI2UMhVe\\nIlFoXNOQ5wXeO3wQiBjJTIEkBcBFBTKE5H5SkhBLmijRMQ3hamsTLV8ktpMVApxNIGfv0Mak6OdA\\nCs2TEkRExgT9zpTCxTQ01AJCkAg8SiuCByGzFBSoBK6sUDJL4XoRgmuICFQMnGAO6GWKwASdebz1\\nzMo5QStEcESh2D0+IniFUTW10jjlOJwOmcqa3LYgJtqVlHMCbdp5i4U84/S5HtdeucXUj3n+VsCp\\nwNRYBrXk/MWzbFaemLcJ2tNRkt/45JdoL6ywPFjgE7/0UVqDAZ9/ZQtrutSDy+zPHdlyh4DmpUOP\\n276Ck5rMKLQSRJlxPHWsr7Y5e/Y0YTxkeNxhFjKG10fMLXjRYd6WdLzEqkA/P4NqJSZHCJ6lImNW\\nCg4PDyF4Ng/uoU1BO1tk6VQP13iUmxOUxriGWQUxFn92BfWrDyHEBeAtpBXm+kmxBdgB1k8enwE+\\n81WX3Ts5Z08e/4fn/6Tn+QHgB4BkMYs1ZcokIxqFzAo8FYIpjczQWY1UoMjIs4yi1UFJmYhHTqKz\\nHkRQuUZkklgnN8mZsxv011ZYnMP27V20nyGEotOT5FmLvNXl9AbsH+3R663z5sfOc3opR5sWrM5Y\\nWl5gNm04njiqWcW9rTFvurxGWc/od1ps7c44t9bHtLs8eHaBnaOaV69cR4u0UtOtSHAVly9f5Ohw\\ni9LWdFnGCs/5s8tkWRtXB04tCm7f3eftb75M6SSv39hk0DW88NIV1k8tsrc3ptdWDBYXWF5doZpP\\n2NrbpdeSnFk5y/7RlKosaXeXSCgPhw0GXbTZfvUGNpRkUuKbSDFYIAaP1prGpVaGEz6FuwmNzz1C\\nKWITMDpioyE6ixIK59NkW2iw3uEVSEmK7nYVAY1UJ0U3CDKhKGNDbloE1yTSk0i9WiUlDo8MEi+S\\npAkSZUmcbIc18iSRUiSJEhqpHE0DSkeCTxBnYdIKNcQSJdPKraxraGV/7P4yItIoiMGhlcI5S0Ch\\no8dpgxCRgAQ8wnqEMUgsIQiELpAxDT5jSNHiBIc84c1mAhokwQlQgeAjhUwti4g8SVJNKU9fIfIT\\nJVLIE65CxKgWdagxQtDEQBSCPAqKvEOIESGgjoJcRKSAgCa4Chk1U29RKieUZYLiyNSOUFFinaec\\nTwhaI9QCCEm7103OthAJMoNWGxNEes255unHn+L0mVVMjESj+E+Xz/CxX/8kUll0WaG04JX9QGf5\\nPoQWhNrTNBOEyVIQoYx89we+h/H8mHZ7hcPj26xvPMiXv3iFyWTG7HiXSWPJZJFaQHiCn/D0u95L\\n10Cnp8mDYmw9C+s53/2Xv53p7Ag3n9FZXeWXf/W3Od7bpmgCDSC1oFsYosyhsfRWerimQgrH2tIl\\nnnzmIhunVxjuz/ij577M8fYW6+fOcuvGDaybpf7zGzze8E8KIbrAvwF+OMY4/ur2Z4wxCiHe+Lr4\\naxwxxn8B/AuAlV43ajJiBC09Igpc1aQbTxZIN8daS6EzkJ5Op8B0crSQOJcRVEyszK8Iqp0kRonS\\nOU8//jDDaU1ruc3l8wscDqcMBi3apmB7e5elpVMs9AWj+QUWOpqjUcrlaWzFdD5jXjZ02jntdg42\\nZ3EhMpxMk5tI1BwdjQh+xhLrNI3l0UfOsry8yI3be7R6XXxQ0NQ8/sSDdFvPsrHSYTIZEaLFyJzD\\n4T7zSYOXnseXApO6phpPOXu6T/CSy/ef4/U7W2ycXmKx16WdG4blmH5/QJEVPHDpPPPpiIvznN3R\\nlFNrLZRcAmEwIhKw7NzdTVHFsaHVKfBOEWJAn6yWclOkrbA2aBVxTqSCqBU+puKY6zylbars5Eb0\\nWKERMumCDamfLXxABmiEQJNcKFqrE5oTSBnT1lwm/Z9AI4RHKI0gFSkUaWqsFWhFaRuMVGRZlqKk\\nfVr9GhlxXqBjQElJE0ELATLFR6viRFoUBIUSVK5CiozgBUKKJKdSkkYG9MlbW0qZZFUmyc6iUImg\\n5QVETzI/n/RJtQKf4qN9EEiT9K1EhUBg0am9EE7o/wZikEidoaTChhoZQetkdbUkJkEtHEYYnJA4\\nBEIZJA7vAwqwRHQQECsMCm8EsZG4xgMKL9wf0788aXBWHzUo3cLoNAxbHCwwqxwby0us3XeJ5YWc\\nl195nbXVU4AkLyRCeG7eusHSqQW67Rbf8M4nCe2GySTy1Fsf4bOf+gzzmeOpdz7DvVvbZLng937v\\n8ywvL/Pmhx/g+PAencFZOr0Kpdb4/G/+Jv31Je5/cAVnNphXE2YHIwZL66h2xkJHcenBN7Gzcw0t\\ncqp5zcULD3B3+xZZlpYJTS2od8Zc3Fjkve98K/tHJddf30a1JdOjOQ88ch+Twxn9hRwjHM9+3TsQ\\nocQ7RV2XDLo57376AebNefAVD58bMJoNOZ7CJz/5qTdUu95QQRVCGFIx/Zcxxl86Ob0rhNiIMW4L\\nITaAvZPzU3l9ywAAIABJREFUm8C5r7r87Mm5zZPH/+H5r3mEIMh7mnJY084DZVmjbEO32yKKRAzy\\nLnmTvfdMdo9ZXV1HSsA3uBjQmUJKTSA5TzZWHTWB+WQHGfssrq6RyYpqckSZ98mCZf/aC0zWcrqm\\nw43Xy/QHG7egstzd3GFlo82rd0vOPfYQrmyoY83VL7/Go/c/zsHhEU+eaXP36ssczsY8+uAjHB3s\\nMtCSv/juC2ztjTiaW9a6fcJ8n1GYMzp2XL1+l/MXziCbmq2dbc5vrFLkhsNrL5MtrvPw5cvce+06\\nO75ilAd67Zpy7DgoS1rtguF8zPlViFpz5ZVrrC4UWN1hYwChnLF2bhXrUvib9IHZ0ZhCaKqmodPN\\nWTo14LBSWBHT4AiP8jLpTr1BCFBCEUPifSohqaJFm4h1EkU4IXSllV48Ed87n6bEiIAxmqZ21E1F\\np99jOp1idE5Vz8lUlpJEtUQ4RzQF3ibQSCANnYTKCcGe+PEljfcoH4giQbDrCNan3xEai0Sf6FyT\\ndOwrA58s6+LqmhBESg8QEBDIGIgqMVOlEuBTHLi1AaNAS0kdNRqL9ylEVImTgZqHKALOeQoBFoMU\\nIJC4GJFCQUx93+CTfjZGQfDJyolLRRjvCUZDTIkCPjZoKYkYbBQQE/AaaZKeVQhiDEit8D5ASAwB\\nI2KSaxlJdAIdFS508N5hjKFBcuvgFgiLMgVNDZPJhMFin+/4z7+JrNBIFRgN9xGiZn15BSlqrj6/\\nydR6mnqHbu88b3nX2+j3u+zt3MVXlre+7XGGM8dw74CHHjjNnWs3+Wt/9Vv43Kf+gNW1PhKDkXPK\\noaaVG84+ep71xQGmvUKr3Sf6IfKywdY1RmVs7m6xee8GR7dv8Ow3fBvXX32BveM9pAapPNPjkv7S\\nGoejLe6//35Mx3D/SpszK2281vQHOXXVZvB0i7s3rmD0EmE+Yjob0R+sMx8f0+osMlh+kCIT3L19\\nh0m9R1lJmvGfoZf/ZBL/08CVGOM/+qpv/RrwfcCHT77+6led/zkhxD8iDaUuA5+LMXohxFgI8XZS\\ny+B7gf/1az6/hMZr6mlF3mrTiIhQHi0lx+OShpM3e1PRbg2SJi9GvKsYHR0TlaRoZfTFMlUoydst\\nTCvnqceeYLizz3RzSKV3OL67jRFTghaUVcRVnlxYuO4ZWlhcXuD4aEZbLTE5GFFPPUPv6MeGe194\\nkfl0yPLKEk/0csTea2yPjjl736NoUdBhxt2XvkBT1bSU4Y++7MnbBaquuBk1xhjm4yFFt0NPKV68\\n+llWF9exbs613evIOXiTE25d4epnX6TT71D6mvnVbdzkiGGU7I/HLBvNfjBMH7uI0pG7W9vMdtpo\\n2WXh/AbBK2azI/LWKl46ZNRUo2GCSRhBU1fM5g1SJ9dTLTTm5EMIMqQirVBlSi9QAFFihIUoUXg8\\nAuHSBBwp8FFgY1oJIQUxQGiSaywIRVU2J1Nsh9Ed8BVRKmLjknXSJutpVJoYQUSNizUSiMFQyIAx\\naeAVhceJJH2K3iG1oHEOmRlMiDgVQWp8Y5FBUM9LpFE4V6JUnnYueJySKW6XJN6XMplEci2IpDgR\\nEyMuSpSC6CNSGaxzaVjmAyFKBIIikzhh8PXsj/OxokhF+yvMALSkcY5MGILySJG2/YYkoUIqtE6r\\nfSA5oKSGCI6QUIM4FBHrIiFEMpmj9Vc+QAIhNmnrrAS19EhpqLxDOE89q+i1W4ycJ1Ylfa149sIK\\nn/6Vf80z73iWvcNtfDXhu7/vh7h6/Tk6QvL853+H/+wvf4BP/Kuf4XQu+PSv3eXt73mGF3/vtxBT\\nz9Lpddy8YvfgVdztVX7pd17me775KbZevcJGp82Srrl9sMvISR58+CGe+/efYyVv8eAzD9Aq4O7z\\nt2if3mDR1Dz43m/ky899nMwKnn32PXz+d36X5774aZ5+5AL7915nceks9XCfut/BlzVNZZFnz/Lp\\nf/cJ8qngze//en79D7/II2f69M6d5uqtG8yPPKsLqyysdLh+7SVk02KwFMlX+hTFMlFHJqPIyzf2\\nuXDqT+xM/onHG1mhfh3wV4AXhRAvnJz7r0iF9KNCiL8O3AY+ABBjfFkI8VHgFZJC4G+dTPgBfgj4\\nGaBFGkZ9TRR29BEjLSLTEAIqFNjYILTDqAzpY6IFFSmPvK4L2q2M8XAfQcpHynQb6xtMliOtpNWT\\n9Hodbt3ZIspjYmOY2yNauaGalxiZo5uGWe0SwitOmB5JMqn50ou3WF8wrC9nvLp9yFonp9Pp4oOl\\nYzy3D6c8eOoUi92Srcke7Xab6tjS7xuG8xHZSoduN+Nwc0jWFhgE9bhkoTVg7hqmo4bzq6scDScU\\nLcNs7JmUxxTFIr3OACOmlJMxg+4A5yeEXsGqUBxPxywsLdJHQjOjmUTOL3QZTWZ86cYBTy1nbN4a\\n8q3f8V7KpqbdyZL1sgIRJTY2FI1E6M4f3/QmBGSsCSf5R86nAZHwEikD1mmyXBKcxEdB8B5BoBYS\\nQ8C5GhmLhPmTEl9HMh0JwhB8UmsEmpS2GtM20ksItiaTktoptLJoZZLGPUiCcqR1cETjUiwIEqlI\\nHFNiAp5EBXjyvE2qsYEQBCqQNIwkFJ/Ek5k2PoIjDYiiO3mdQiZpViSpBqInOkvLZNTC470kCIEk\\nEJ2gqmpaRY47iegupUQ3DSiPEgqtTib6EeJJDEkgDQgLZU54rOBjIJeC2ic7tY8C0ViECPiTIh58\\namXgk2pARI/RBhs8Wgiic0QJ2ksqWyOjBgQi5ERjsMGipCE4S5iXHFcVRbfgoYfexLK8xvb1WzSi\\n4ktf+F2O9qacffhh/tv/4vtZ21hBEvmmb/6L/P6//QXiccVhr2R5pc3u9g6TWc3u9h4P9Vo4pRnt\\nRq6++jwu5Pzcr/4RWbfNs/Wcl67dZe/IUtVT2l6w0g8YlfHz/+a3eOr8GcrJlNuvbbNoam5eO+a+\\njdOUdo/f+LlfZjif8+jFJT79qT/CyS6tuy+jG4u3Nb3lNfYOZqzeO2aptcrieo9rX7qCbSLd3hKH\\nd/ZZyzq8Mtqh6s44Oo44PyCIQD+2qcuM6eGQ1ZU1jvb2eMeb30k++DPsocYYf59k8PiTjv/4/+Oa\\nHwN+7E84/xzw2Bv+1wFCCrwAFRokPWyYoWWOjQ0uNgBYHIVs0USBRuKbGiUkjYAQPLNqzMp6L8VJ\\nyIZBlvHyleuYeU3lFEFJTIxUpQMhqZqKepYGV7O6Ig8BJ+ZoBA+cHWB0iz946RoP33eapp5z8cxp\\nPrm9yYXlU2xuXoPQQFxiuT/D6RY7e/vsjXP6HYOdOzbvTem1FcxL7jaejUIwnhwz9ppzp5eYjycI\\nU1Ae1+h2zmxq6fc9w8leyk1aO0XtLEFkhNmMKYK2VoyrOe28wFWB0aSi3zVsbGywuXODe7d3mFpN\\nVXq6i63kDhKJIu99jQoZozhj3kzQeSdZNbVk/8ouR//9D+PCLAXJFTVUhkf+2f+AXH0c62waewfL\\nS//0Z+m//HEmzTRxOINHRcdb/uGHsOtvwUjH8//136LRR5xaOks5tSxsLLDxV34Em/WQQXL8sZ9A\\nZBKlIl4COmflnd+G04sYarLRK/ioMe2M2bSm1e8zHM4p1p4kiIAUGkWDUAo/2qQ5CfaIAogKZzw6\\nW0FLTbAlTTNFCIMXyRcShSRr9bDzCVJKHCkV1RhDUBlKa4LRqCaAnUPUxODwMlCc6E6RBhkkJiia\\nMDtBDwYEHqwArRIC0ESIghgT6DsXUBOQNuKiR0qFVwYfSqQsUBq09ShhsNoig0b4KmV4+aQFlsJB\\nI3Fao/FYX9NSGkKZgNKhTIsLaZIlVyvKqmGhbVBS8ej5RV59JWepUyBk5O7mCCEUX3zhJpMK1L0Z\\nTng+8pM/zWrvDLFuuLi2AF6xc/U1mAbuf/AhqnnN9viQU6urnH7kAcQLV3ns3BJuusm8HnLx0ga9\\n9hG21uxtH9NfXkA2c775mUd54bNfwAxWePC+NtEOQFVs33yZ+TGsr3Y5217m3t0d9PoiZhKZjuYs\\nFgN0W7O+2GM4Sn9zfMPN3X0uLLfpbhiaUclwe4yQgTPrfYyR6AIGqz3KkaVsSk5nHcqmZvP6DeZV\\nyRde/gzOfk25/B8ff+6dUiASjk10cG5K3m4xnVqyDLSNBKUItaemoS01lhrpFHUIaGPQJifTGZk3\\nlNEinODyhVWODyJTv0/pNCsavIrJsztvEASUDhgpMEIwmTV0l5cRwaeoCDfisQsLhAayXPPitXuc\\nWVihqeesLZyineUMJ/foLT3A+OCIhaXkj5/MGzqiZKGfoZnwuds173qow+7WkO5yGyng2u0t2kay\\n0O8w14pX7tzjbL+HjobhccXFUyvc251zY7jPkxt9nA7cvTeh1x6gKFg+u8j25pDjckRuBtT3dmkN\\nBjRlRGmNzDySVHislJjSkecdGj9lEAdpkCEFwiXZ03xnh1gNkR5EaHDzFjJafvFH/x7f9r9/HJ1p\\nnE3C/c5rV7GTGd2Y8pdyJQk+Y/PGPmdOSaKM9G45CiKz5nXaMqd8eQ8+4FIYYSjZ+5efRDUzQqxR\\nymB1ZP1t3woi9Uqf/3s/AaHEB4ukxDSSrsm59JGPIExBEB6awFKR87kPfn96CxkSVKMw1MWAN/3z\\nn8Th2Xr+NY7+2YcYlCXWBGQDjVY8/LMf5Us/8k9oHX4OadpoV6N8i2d/4ac4kH2apqZoKl7563+V\\nxjeo2qFNju/1uPzjP55srSJQlyW3PvidSUIVNNa0EoXKVzzzCx9l6DsokWosUbGa9fn0t/xHKWAw\\nBIJuMKJgPDA89hM/B01EKEXlK2QTeO2D3453CiUrmspRFDmlEFz+yEeRuKRsURpvIzZm6FgSMORZ\\nl8aB0B4VMypXEWUfpGDvaIeqnGJbhtlwTn9hiddv7dDPLP1Bi9FwlrCJRtIa9Hjkibez9doVprMR\\ne7ubPPbIE3zpc1/g4gMr1HuH7OpIuZ+jvef3X7jKB973NMejHV76g9tUtDmztoRrthkdJVXHc7ev\\nsLZwjuWFBusls1GDKyu6S31WLuRsHewhpoHpLNIpJUY2WA/zeko/a/EHn/8C9585RzmvWRws0qlK\\n9ucNobGos561cwUaTZSKWkZ2tkdUMTJtHIXyZEdjchNwsYKYM5pMydqLf2qF+urjzz8chUC/twyx\\nJu90QRhicDhtiCKDEFOfKXqa4PBWgBQsdAYnU2WPkhHrk4PlvgtnGB5NEH5E9BBDRennlLOGKDze\\np63Q8uIStfNJdpF1aOvAbFbibcWorLC1o44TtBMsdeDuwYij0rG+mGGyggv3ned4Z8zxUeK5bm8d\\n0OlqRNHmcLiPzrs8cXbA9c1jdCuh7w4PxvRzQd5pU5UeYSJaes6eXmNlbZlnnnmAmwf7HFdHXO7m\\n1ASM1Ex9w+lVjfcj5AgKE3j0wXO0BhkCWPBA5qjtmJZu42za5mvvyHD4ukJ4QSMsiJxgHV4ohJNs\\n3d2FaJKVUiq08AgROV/madJtHQSLiJE4uokrK4JvEFEibcAIWL94CmFrnJeI0FCWdZr8I3GuwaqU\\nWJt7hQCclERd4JynZbrJchoqOkWH0s2Y+YpYNqjYJhqRYMM+OeWD88Ro+NUf+4fgLcLaFNRoHbJW\\nyOjRRDSCneu30XUkFDmq8mkSLyJCRC7+4PfizQK181gyrJvxM3/nB1HOoxH87N/+u7h5Q2hKQqfD\\nWFbI939rWo0GSYyCYniIFpr5pMKLDGsrbKjJrOC5n/95Mi1ovEOgiKECPyc4S22HODfD1w11PSEf\\nTojW4lWSVSmlycIcak8sJ2iXLLRuXqOdI4ZAjIIkTBUEITEq0pQBqTzH8zFCNugoEaqimZXU85Lg\\nG55/+XVGo2Oqeo4QHW5eu0mwU4LKsLVkY+0MOu9y/vR5rt96juc/9XGKgWBpkCG85ZUXX+C+9UUO\\nbx+wvrDE9m7JcHTEa/fu0Pc1t3fu0OqtMDaepWUNix1qG+m2lmibNg8sLNOSFa3uMvVsyvojZyid\\nYHy8x/HRjDA3jMdj8i74+ZhqVtHKWvQGAw5HRwzMAuNasLq+wNhOmGnP+HCPcVNxuD/lzq1D9odT\\nbtzahCZj5/gY6wMFgUxkeOGJpqDXW2RhRdHUHqjecLX6819QI5T1HIUh15Hga5QSxGgxWUaIDUJq\\njDS0iwKFIMs0IoNO24ARzKsZ6DSo2N/fJ7iC6AXEjMXeBlWdbmw3bxjHispV1JOKllIUmUFriC5S\\n2cjOcIIMyS7YbSU9X13XPLCg6RY5lVUcH01xtacOFTo3HNUzTm0sk2lNWU3YWGhxtD9kXs954MI6\\nujCoJmO1n+GjZDyp0dqxX9c8fv48w8MD5sNjNu/coddW7I8r8k7keG+IBs4tZHg0MRSE2DAe1hxs\\nDZnPHFneYrueUDcOo7r46FK6gQIlNM24IgiPilCfTLeVFikoTzmOtrYSElGmPCPnEjV/4CSBOllP\\nUayoglDOUcJQk8Ty0TpC8AjTokEiXKCsK5SMeBtS2mZLIFWGjZGIgagxHgoXEEFS+4omRIRMvclO\\nk6JEYi4JYYo5GUCut+uUhAoIZ1l9/otoYorMjj7JseoJwXockiAi2/fu4OOUpnEEY3BWpigdbVi4\\nfJYoAtJajHVYk3H/QQKbRxd4erKLlw3eNlRuiokFl775/aA1SkRU9Pz2j32YaZxjOj2aPOC0OEH2\\nBe78xr+jaSxS6FS8VJYMCDEmd1+MKaUUAdaTyZo876VEgarh//jbP0Qu0tC2Dg0eh9ASF5JONwpB\\nDJqYZlf/b6qFl5hOj0AKU5wNp+StgkiCYf/NH/xB3vX17+DS/Y8wGu6zvr5OEwp8mNM2glkzZWml\\nze5wyAMX38a5M6dpjg8QBA6PZnzHd76f7YMdLl9cYzge0+tmjKeBxe4i7373kwyHNdfv7LDe71E3\\njntXr2DRuFBzZ2sXkfVYP3eGne0DgijYfO0uly/dT2/xPJmwlE26N1pZynnqtAs6hcFVJTJKuust\\npLe8evUWpXXksmBp/RS5FOTaE0TGdNLgpORwNOTsxXVevXWbxtfsTSeMpwFhJY2oMKqgMyjw4v9H\\nESgAsWwQSjItHU4WiblZK0wU6GAS/MI7+t0OeZ5TliUyRIRsIRH0igFuPgcZmU+mzJt9hrMh3sHR\\n5JDcwbwcsbbUI7cpfyfKkqg00/GYw9rTG7TJVGSl0wckzpZMjyrq0FBZCVowntXkJkObyPi4JFOa\\nhb7mbe96kKWWYTSbczi21DHnyDly4HD/iGgDk2bCmUv30esXNCFwVDaoytNIy73DMVtHE+7s73Nm\\ncYEywqzyPHB+nc1RyanlM1TlhKVVye5wyNrZJVptRSeXQOD4aEz0SV4SQhcf09QaEWgH0FGCjOQx\\nAhEZPF4GjO7SURkYThJlQRDQASauppmNTuQ6kY9/6J8iZQUElNVYV2E1xBCSY4mE2evkJ3pRItPJ\\nCFcntxA+YGOFkA0iN1jVoDDYJmksjQAZHfPc0PGBclKirMFZhVKKX/zwh5PPH5MsndUY55KUSvpU\\nZFKfyJMrRYhgyykto5FaEOpIiFXytAtNIwJ28c0IKdG9jNjMCF6nD3TvEGWk7RR5yMnLkmlvCaUN\\nMpwkSAjB6uGQoNuU0tGalvRKh6grmqZmLcaUHRUjOs+ASFkOCURsdKiYJvHGJzj61Y9/jFk5Tdg7\\nA++aeZrG40MguBITJaAQIhJFQ1QCF+2JEsMSBGihaaiYuhKtFUFCd7GHtTWdbk4MGTEYppOKF1/7\\nPBQtQHHf+Q0GnRVa7RwvGu7u7nDp/KPcfuUzxDBnZ2eHz75wjTc/cZlP/fbvIoLm3t6UpV5BFAJl\\nGt52fgHlW2zvjbl17QDTXWDj1CkmNtGcmvII7yqu3blFv7+AUIamqWlnht3jQzZvbTKvDLoQWKsI\\nKuKDo54nvkPWEshcMT+WzOYpP6ypSkbNmFklOLO+zM3NQ1qtSKsQ9IuCXHuasuL0uTWC7COznLmP\\nBKlpCNw72IVGotwb76H+uS+oEXAKbGgQ1oMtwYOLNY3zOOVSYiMgVArxavf6RKOIMiOPmqgagjR4\\nCybrEuY25f3YmuhqfMtxav08d7ZG7I4jnVYHW2fcPZrhQsGbVrocbdc4J0Erqhg5toa1BzdQEnwz\\nYTgZo4Ml1w7rararBGVAK37tX3+K2dwxyA0yNmzvH3FhocPKxirlZMy4rtmvHFu3t9naP+Tg4Agj\\nIqtLPerKUhQ55y+e5tLSKa5vHdARsDUuGGQtLp5dpZOlyXB5GMlaBc+/fJdYCXa3D9jc2ebC6gJB\\n5WiRkxc6pYrGgBWRlnA0wmOtJWaKnOwkJA5CrNHVHNFI5InXPSKZe0vsD8gLgyvnCBnpv/Jiil1W\\nESEMRIVBoUSbrD1AKwghffB50pQ5b3eQLUUMmhAtMZD+T51D1BqvGgqZQgOdDbREmzwEWpMxSzlo\\n4clUQMvIytU9iA0ERzdqvEvbNOEdTienVWZLdNYhhiaxC0owqo92Ed/RSJVj8hbgyVTO+37iQ1BK\\nqpkjEzlaWBbqA379Q/8jMzFFhfT8LQT/yU/+FE5EpNJIH5JqIla05yWtyqFRyKJAeTAyY8GSQiWB\\nLCikMAz3D1MLK9hEwRIeJxy5jAx/5ZdQUSUsoA3MJ/s0JDWBFBEXAy5anI8oOujgUUi8J6WVOksl\\nAia2EDHpZaVwuCaetLlytPG89Zkn6PUGLPTPMp0fcm9/gkFyVM3Jesvcd/Yhnnz0CRq7z5NPPsm9\\n61vYxrMWI6OjXXztqOZjRO4ZzxpEK8M0cO7SJTZHm1R1YHG5wE2hrCdcXD+Fry2VtFx6ZIO3PnaB\\nne1N2q0BaMndwwPaBooFwWR2QCE1Xllibcljm8xEhG04HNd0jKHbbsiKyEJ7gbOnziBrha2OqcaO\\nuWsYDhumpaeQXeZ1RW5zVlWXc4sdltoZoa64vnWTyShwbWtGLQLWv3E4yp/7ggqQqxZeaSpnkb7G\\nNjNsA0HYpEF1DkfE+kC7XaBDDlZQzsfItiHUEJxHmkAUge7iMiJEUJGcLnYeENKyvmI4s9TjaDpl\\nGMdIC1VTsu8k96YjBosZUnk6WnJmYJjvjOl2+jz5dU+x1F5hsFyQFRl5FnngVJ+gIvfuHPHQg/dR\\nzuZMq5p7h2NczNgezfn3z19hsLyApMMDG0uMJoc4V+CdxNHm9e27tE1gsV9w884h50/3ef1gyjOX\\nN3jHowOGUTPbO2LneEZvoYtTgr2jMR/84DeS9TSLawPW1zfQpoNWghAanA8oETBIhIOG6mSLqJAR\\nrExADqkVqIBsPFKlIhdjpBIBhcB+y3t57fWbqHYfyhpR7hOVRoQcQU0UnnhC0XfCgxcYFROXU0Ta\\n2uDLkjCrMDKRllpZi1yFNPjT6fmC1wShCZkgK1rMYsm410bHtGoItiGUEXV8yHw6xsfIL3/ow0Ql\\nCEol0HOw6JMe+s7hDkLmeBmwkxGT+SHBNZgocLnAd1rJCRYdu8Md/NI6UYhEkaotP/PXvo+z114l\\n8xHX7dDUnnlvnXFdoYLEBgcBFmQOoUQpjfBQtiUzo5LwHo9r5ghvkcElBUJwbN25g1aSaAXBJGNB\\n8MnhtzCxiJMb+7xWtHQbSJZTkGhxokYQnMS2JMyL0BB9BiImwhQW23iEUOiYoWRBlAZna0TUjOZz\\ndrbvYjqBdqdD3rLcunmdhy5d4mhvn8nokO0719i+t8mtW7fIul3mM8dxXZIXHYTRnDt7lum0YeIE\\ndp7UKK+8fpMrr19nVjdMj+Zc375GVXrmXnFmYwlbNxxvldy+MyciqZspmQrMnedwb4SfWg6OJ3ip\\nMUJjMhBqTl4IEBpRaKITNConBkPLVByP9plPZgzaLVSmaOdtOu0Wvc6AuZvS1n1QDVp4tqdDgpZ0\\n+wVStTgYTlDtzv/D3ZvGbJbm91nXvZz9Wd+13nprr+7qnl6mZ7pn6fZ4xmOPNXbwwmBw4oCwCQZs\\nIiKMhQHLAQeEoiBFAoKIkYyxYzBxAngBx7Y8M97G41ncPb1vtW/vvj7b2e6ND6civk4kJIacz1Wq\\nUr11/udefr/rQoqAVP8cKVCgU0KkQSKsJFUJRZTQ1xm+td03WkqCSokdtHWDZ0FrLcJZGrvA05GB\\n8AnDPKaezzhpLbtHM0TcIBPFvG25vTtlYS1ryz1OpoGNJUmsFHhYzhP2DxasFCvoWGJ90ymlm5I7\\n79zGak0sFccnFbunjtv3T5gdlzRIXr2xx82TOQ+nLXkkiGSncnh+cwVjAhvjhFdv3mcp6XFQl3z8\\n2lkOjo/oC0VtJFGiSYLlt7+2zYsXl7m1M+Vr7z1AC0uWQlVVzE5nGO+5uHGGX/+l3yRL+/QHCVkE\\npmvCEqQgRF0334VOYaJqj/ICLSQLbzCiW1jLoMEo8iZgQyBSMT4EEmJqPMuPPcugGOCc470/+DLO\\nVGhr8BikjFBO0oa6U9W0j7QprcCGkta1GBsIWiB01jWRhEdpgW0FsQPfKAQRXlQkouufm/kUrSXC\\nR0ROg2/xweCVwwtLqlvwhnPv38Y2NaJp0Ykm8imN85hGEq8Muzyz1cRSIDIFkSCtaoQxeG/wDhrj\\nESrm3tXnkPOyy4xax3N1gT49wcocP50TqZizP/efo2VXQ5U+4BX8/t//BbQF5z0qGPZVj/nCEloP\\nBISUxJHASo0HWmfYevcWLlhUHCGcRfkEEYFxgrquCc6ifMsv//hfwwrTNcqkIDjfEYJtVwiwaJwA\\nTUDY7uJL+RzT1ISQEfIYZMcJMMFybWMd6w1Ix/GkZDGp2Dqu8SYmEimrG+d45esvg5szn7WEIDg+\\nPuHi1Us4v2B9PODcuRWME5hGYytHT2dkWYTOJTMnEMTMFg1WCq6eH/Pk5Q8wMwt0KGmDY2V5g0il\\ntNUpB4cPCVZirCISit4wZePCOh964VkiBa4JJEozTHtUbUrdeqIq4J1iNptxdnmd0niki+mPhiyC\\nJ80/8550AAAgAElEQVQLWmtRoYuYnemdQcXgMfRHCUvDHBWgbRqCkBzMW0bFKlI/Oi76Jp9v+YEq\\nRNdaKV1L3E+ZerDeoZUgyVR3KeEC0hsW7QKvBErGtKaESCFtipaaIA02WE6OjjltG1I8S4OCWe1R\\nIieIgs0zF9hcHTMvZ1woMoreChubIxp3ytJKwnHj2JmWHM4FtfcUSrNxpsf+4YxQTtjfL7l/umA9\\nl6wPcrIsIY1gUras9XKePzvi7NkzPCwX5KLFe8/p3PD6w13qynLjsObFSxeYzBeERnHxbMLOUc0r\\n1/fZnpYg57x995BrF1Z4ajjiy6/foRKSzfUBeZozXhown0+5fGWTql5wvGd4+/YJhydHCNHQ4tAe\\nEhKE7roWkS8JoXPep1mPXOUI34nllIf5fIoQ3TmrdJKFa2F5DackJgQiW+G/9AoCj1eBHNFR4YUl\\n8RmR9wTZUaqiTCJE1DWAvESGFCsCXsV4GWOmNVJZjDeEBKToYMdWxHjraE8WpKUn0pZGSbxrKUiI\\nG9CLmvj4YUd1qg+IsxyUxLUGoRwREalW1HRsXbRiUDsip0Bq6lijjee0qQBFFEUIDd/7Mz9OHOUk\\nQqCjAhdaAi2RXbBIJSrvs3TpAtAxBiyAE6TvvY8hIrQtrYL1T30at7aJFBqcJ449upogHnVeBJq9\\nve2OX+BDB5cJDSIIEh0hveNMpgnA+skE6QQejzAO6SDJ+iAjnAclk86YCuAbgvAgWrzoKq+jtVUC\\nDms8OpHc2dohiQtcgK/+6R/TyAq7sIzTPg9377G9tcfls6uc2bzE/e0tLl96DNMuyCJo6sCk9uwu\\nAnXlkMJgXM2dnW1cWaF7K2TBcXRwF10MSKOI2/vHHLYHvPXePsPhkNYJ2nJBbeZceuIDOCtpzaJj\\nYpjA7tGUk4MjJvsnpEJwYWOJOI6JIsX+zjZrZwryRON6gV5RMCuP6KUFZVvhG8PufsXe4YRMC3Qe\\nM68nnPpDnArE0QDhAqJ2HO6WeDLKxuJDQKeWOETE0TdPm/qWH6gA1lcUOkYETyoDxJqGkmn5qFqn\\nBJZA3VZI75iU0+6Aynm01sg0RquMSEY0Lubs8llWRz2EiDBVycIYUuHQaopA8saNEy5f6GF9y9Hh\\nKaM042jqePLciMpVmGrO1lHD3uKQh3f3GCWSuYMsS1gaFCgVUVYTZouKae1RQvPYuSX2phXCCzaS\\niGEx4HRS0bQVhdDMjCdJEpSb8M7dHRodkFHBExcv8u1PrfDMuSG4mF4h2d47ZXimIM0k+w+mvHPv\\niCSDr1/fpQ2Opna8ef0+ZXtKFM0YpQOsULjWE+BRyF0jUdh2RpACrMNgaK1Bis5rH7REubqDHotA\\n0IF87Sy9f/ffpjdafvSzcST7b+Fs0zneB31q4btQvLKdbCUEfBCd614IQBMpSyMeSeuERdGQRBHW\\ngEpjCDE4kElECB00+u6D+2hT4itPKyyxSahsjY8gqB6/+7P/KWd02onk6gYnPF4qbOjMDl5YsniI\\n8AovHKmKwEpk0/Eegg4kQhJDB882AaUU1fBcFyWzJVZ2A0+g6dUtJy9+O+juMFSrmFhKULBystfJ\\nA3WEDpqzH3+B262nbubISFNVgX/8cz8HkSK4FiUC9XRCoFupEyRSeYS1uEcXf7/2038D0wQSZx9F\\n03x37iwMTbXAqBYb6q4oQIcArEOA4GmD6fiz0iGyDC0kmeo8XsPxKk1VI4KnKApWV9ZI84Rbe1v0\\n01U2NjaY1iXXr9/ku156hnJ3DyVTdm/fRjgYjAyjxDI52mNhLDMzZ31lGSMFwgWifsHU50yqQFk7\\nWisoZE7SyxmsjllfGrJ9esKN/WP6g8D5y1e49PjjVC1cPJ+RxxHTssK1jvF4mePpjKqqMI1lZZxT\\nzSyTuiRrFG3Vsnd8SGMMeZahU8taP2F9NWVldUw5neGDQinN5KQlBIf3Kb1CMerH+OAQzrE8HDIc\\nLFOb9hHR7Jt7/n8xUCMh8UbibQdhJnRQijhS+CAQgBSi0+lWC/AOQuiUvdbQtB7vLd6XJMpweHzA\\ndN4gtGF5qd8pZOsF9dSxM5kTBFiZ8vb9I6RIuXPUMl8seOfelKtra+hUMBqMqLxka++UubXMrGPW\\n1uzsnzBeHrK2NOTSuR6rSwXXNhLe3Z1xumjYnrVMJoYiH5JqwbmlEXlUsT6K2S3nvLpXc6w1g9GI\\nfi9mdrrHK/cXPDxqmVrHxpklBoWmbbqhs92UPPvYeXr5Oh+80mN375DGBs6tL5NHY2QY8eDwgBBC\\nV9L0EUIHnDDgIBUCW9d4KbBSdg2jYEFJtJakQnYD0QdaArtXhySrZzGVRdJpoV1dEh4dvXzwb/88\\nsczwQSK9pzYlkUqxIaCMprUGLwVOCWLpkEmKkRqPwtmASBTaCmxoQIGJ9KMef6CaLAi9HkE1ZCja\\nHCbPfA9BFwRXs9lo/uef/ClsW1MHC1HB8fJjqLiTNGLgpO44kLGO0MKCdVgfiLEQImrZ8Utl3FkE\\nZCRZ//d/CovCWksiNV5FeAkqTvjwX/9xglBdTdUZkAHhDK4pETLGC7AIBtee4umXXkLHOd4EpBCM\\n9g5Qj15WQaCZTLsPURshoghHxiLqQ7CkkWR9a5urgxQbakQwRElKmy8jgsQKR+S6LXp3eeiQj4L9\\niVZIESN8wEqJko9+TRwTLEQ6xuNomob/8b/5e7zx5++wff8OsdLUIbB3sM/JBAZFzFdeeYcvvfMG\\nNZKFLKiaFnwf1wbGZ4acTA/5wMXzSJ3RmgVvv/0GtjXsTudYCSqV4DwPto75wPoKv/NHr/CF127w\\n8NRjhOS1b7zJ19+6zZ27N1nppahGUKMYDlbpx5rJ6Yy01yNWmhAJIqWYm4rxUsGsKhn2BBfOrKMj\\nT5rGpHFCVMDhZMHh8YKl5QKpWu7tTimy7u+CsswqT5HESO+QkUZJy43r73XzRf/ztOWHrgPNlJaY\\nynlEFJMWQ+rZI2d6eERDl3TACtm9QDaAFZZY0SG+fITWmvE4o2obWqOYV6bjQrYBVcDxdMq19RTq\\nU5byiIcnByzKhuPKk/cT/vTdXe4e1lSmZRBHPHl5DMGxmSkKmXF8eowNAaOhaVKWhznvHlYEL1HW\\n8ewTG3zswx/C5GMezCRRrCiTdSZVw7c9foFMJigSKtOSiD4yG/Lhyz0+9dILPH12zMneEV4k3Nw+\\nZWe2YKm/zNbhIZ//xnUunNvk8SfOcTo9wjp498EOSdIwXB2TFhkqpAgd4bxCm44tam0HF4lsYO5q\\ntOf/efkcRDbglMBHsquC1hUB8EqgpeTh7duo0CIzjdKC3tol7D+tWQaFVhlGQAeta1G++zMRHvuo\\n0RwBIQR6eYQUouvCZxGNjKERKCmRLnC49QCwMG0ITYWQA859/Hm87ji5og1s1BOUAi0k03zAuDG4\\nqkUrReMbBuMeHoG1hp5UXbc/dJc/IQRs3F2cRY9y8ULA0rPnsFFOnCZYWpzueqr7F8/RCI0PtouC\\nCTpjqvVErrsIFSpDKIlD8pkf/SuIR3Vp1xrG9Rxrqo4Riu/aPFaitIXgscrwF/kQGRR1a0lay3/5\\nuX8RISO8VDS+5R1jELKLQAXhQdpH+eBOWSMDHelfC7SIUc4xXF2lhe4DoKCsK/AdW+AX/vGvMj47\\npDUR0jnKpqWeTkmV49LmeTZWM8bjc1xdWuKx5YKPPXcNfIXNEu4d12xunGVaBZyw7Jx48mKVk6qi\\n9YG+0lwZ5jy2MSTTkKSKxpXUj5Ti54cDEgXf9aFrXFofMZnP2D42ZDonTRSlapm0xwjXoIuEtnac\\nNlMuPv4EvaRPMRowbR2T0mKE4MbNuxxXM2IxBC9YG8WUjUT4lMgrkizu8rcCEi1otUFEGtsYpE6I\\nYsi1Jo+Kb3pefcsPVB88NkqRbojSllAafOOop8foROOcIZIKIQRaxxAcMk7wWAKgVIxQAu9aHIJF\\n67i9dYCSOUeTKaN+r6uoqoQQFFUL+1XKpO2xvDJGiMDaWsH6YEyRSYZJybNrSxSuJM/GHE0km6tL\\nTIVltzrl8uYmtpYEI2nairv7M6qgOWln2GLA/PCI7aMHhPk9rq4rTA2ybnhiaZXUO57cXGa1UHxg\\nGDNvAm/c3aKeNfyvv/dHvL9zgvPwzsMdHsxnfPjimI9e7XN2uaBsLfdv7nN/d8az164Sxynnzow4\\nt7nO0UnJbF4hlSfIgLMVNpKP6PotIVIYLbAu4BHoRONFdzse7AxnBPO2obWWRe2QokBKibGS0cYl\\nTOggyT7K2Lp3ghCCNE66H2AU0FJB8JweniCigBJtR2MyhsY6XIAIjbdxlyaQHtkGdGgJvoLWIqRn\\nvv0A0TREedrtWuo5T33/ZwhWYXRBo0Ed7eB9wMcJqz/0l/Ch7UDJAYq4wA97HcPUB3ALjKhp8wTq\\ngG5NB2uWntZZpPcEC8G0qDDrQv8oIpniIsnD6T1SArHSeBEeiSId21/+KhZD0C3OLfBxjE80yXiM\\nFBnedznTyCm8l7TBEaHQtsEYg29Nx+71Mf/JL/4CQiiUCERty4v2FOcsIGn6a1wrVGf3bSzeOYLq\\n3FjWGxQRXoROqe5cl832AfICaTvsJSbQTzX94QAhFD/1E3+dx59+iR/7kc8yujAk9pKL5y/w7AfO\\n8MaNdziYGi6uFqyMYTo74PrdY+YzR5HEnBmNiDU0XjIrDf1xylObGc+MIv7yp1/g6UtLmDrQOkuh\\nFuzsb3Gmr4lF4Pkr6xRizpnBgN2m5vX3t7l/NOPELjjeP+BkWjKtPNU8xrSSYFpEFNEvBuzdfIvb\\nd+7gTUuiNKad09aGs6sbDKICoorEC5RMqKojLIHloaAsW5wE03oeHhwRtd1HSec9BokkURIToAMg\\nfnPPt/xAFVIRak/pO2kckcJVFeXCsLAd+NiLTh6X5AnSCaypiXyMwqDlgrqsu7qhEsjUs/ApO0dT\\nvvvTH+L+iYO8ZmZa3rsz4cUrQ3rqkPWljHZ2yHPnrnC+t8LEGPbmFSvjEXfKmI2VNe6eWB47N+Yb\\ney0RQy6Oxzx5bpl+qqjqKe6RHC3zLRf6PXpZxDv7c4ZZQUgyekmOSVqW0oammVHbwNtbOwRn0LHj\\n828/wAbJjYOajeGQS5fWuGUjrAQ9XKVB8/52ydbuhIQKlyoOj6bsTOfMbODu7oSvvXkbRUWiJBZF\\nIiKUitBofBBkxiKtR4lAI+ksotYAkoBCB4vMJAiFEp50FFH0uqzqaNAjtXX3cqI53uxxeHSKcJ6p\\nazDBk8i8c7gDo35MaEAEQWQ1JJok6Wq3OovwzRSAEHVbMedbRGtBdu2mclaD7iOcRQpPFClmdYlU\\ncGobrJSctnOcsQhvePIHPkcku1taicBJw3K+SiwiDAHZSEQb0IuWOBEEFQMKRyfZc0Lgheerv/wP\\nsBKc7AadqSegFc/MAwiLwaNFhBSaECRv/cNfRQUNTnbFANVRxbaPJgQZ8M5ivcI2FcKX6EdCtF6c\\n4rVGJQkq0qA0R7qLPikX4UPnUhM+IBHUVx5DecBptFK4R8uICE3cyaTxQWJbg2u783GDJ0kUSvtH\\nJmCPiOH0dIqQjiSOaSf73HrnBh9+4grXLi2xfnaZl994yNm1DbzxPNg/4mTi2Zv7R/GlmNmkpV4Y\\nJnPHu3fucenSBYaxQq4/xe0S/uDL3+C197Z58iNPYpKMO6eKY59jSrh8ZhnvFHlvgzcevM/kqGRr\\nNsc7SYg11y5dINaStWKJjZWMfq4Q5NTTQ2ToBJjDdERtNM5HeBmBjUh7HcwmCjkTW9K6CtRat/tA\\nIaRF+YpWtZxZGXN6eko/7VFELa51yCCJM/nPJOn7lh+oPLKe9jLdKTJcg0pH2DhFxd3Fk5SSPCko\\nF0d40aIRZLkmzXvodAmtcsqmBOtoFi1Gdv6oz3/pDnV1wt7DObpt8ELwjQNDrcf8zms3eX37gHeO\\nDLeODngwmXJm9Rz7peXKasTUep5YWvDqrWMUlkmz4OHBKV+5tc2kPOHktOXNhyXjXkBFBZc3Rty6\\ns8fh8YSNC2eZ7k95sO+5v3tCnPWRCRy2htuHc06M5vDglOcvXyHGMw1D7tUzyuNjXtxcJc0GmNND\\nru/OuH7qeeXQ4ooBX7t5QOklp6dzHty+jxOBiojB+Aw6zYi1ohGWPE4InXqO1mnAYz3USUfc8h6s\\nEcRSYqSlrGtkFBOHBCMidDGk6I+ZVAua4ynKgw+GzR/6PqbbpwjVVT0jATbUSCEIwnL3+g5BtpSy\\nxYsWacBrTSxSqnnF4YO9DiTdRF2eUvcQ4zWEjGmahkgGnJkiVU7rPco5ZAwh6lPEoFrFcjbEpRrn\\nBaX3OLNACf/oAkcjVjK8sURGYOZTIg8RnmAdzs9powznu7C7cxblJUt//IdENeiGbrjpjMyl6ErR\\no0JaB7hOeyIDV6qO6SpRKAKL3gBCwIuWSHiUTJESGiJGpunOj32J9xaEoWkaQmvBVsyDoQ2CVjoq\\n26KFJBWdtO/Jf+3fQKNQMiCISaSgiGRH/pddC6uzUSUoFKTdcLVOYLzDdldgxBQMh32qylPXgfGZ\\nEe+9d58vfe0OtTXcvfeAajYhG62AlDxx7Sr7i4Y8G7Ko5mwdz9g6mbC5vMzG2QvEyqPKY/LeBkW0\\nYLFomBowIuHG9X1e+vbv4cq1a8way9R77h+U9Mcaz4zv+eznwE4Y9pd47tImn3zmLGmiGfd7qEJh\\nRc7Nfc2rt+/SyARfl4yHS/SW+6g4sHRxjUjnJLF+FIMDpx3eBtaX1tC6RUQxQWcEL5k1DbnKSWTG\\naHMV0bYs9Xo456itoa4MsY6+6XH1LT9QfQigSmywEBR1XRK0ZaAtvqUT4wFedplNKSJa71k0DXne\\nR0tF3o+IkhE6TSiyEdpLWlMitSCkBTYZsOsVSRITfEuJRKiE4WCFhS15cFpSFAUPd3Y5Mi3vHBxz\\npzW8vRgwyVOK5SXUyhLT3ho+z7llFAdGMbWBt2eOqJ/y5VsVVTFAZiN2j/Z4MJ/RO7/Klac/xKt7\\nlvdOMl78xJP4OMNKxVtHLV+4dZOgLKPljLKM2KoUp72MyWRCieuAwq5z2YMkjyPSLOf+3COWV0l6\\nG/RGGwyGm3ziO76bb3vxIyihadruLNM7hZUzoqiLFK2eW0fligDoqPsPZVyX8dRYrLAMloZo3+Jc\\nSZGt8Nrf/W8JWtLiufTCZzjeuouOi27bKQJGdAZQ6xWL4ynImEQmHQRcCYLSSGERNAyfvojwMTJq\\nqVUgFh5Xn+AEFGmBaC2xT2jsnCjLIdZ4NFOWiISEPKGOM4ToLrOkSiBInJAIIdEIevkyBo/VHplC\\nnVisElTeokRM6x0WQaqjznDaTFB1RXAtmIZgJbWOqIWn0ZZf+Tf/dQICEwQhCJzzuOqUxhpsaHFK\\nMx8WBARaBlrrUNriAqhg+D/+zt/u9C8iI/edmFAnHYPC4ylk1FVZ8cQ6RqIxAVpds3TpIlI5WtsQ\\nfINH0piAC7azriII0pNGFqJAXvRIkxyjQKsEYUxnMtAe5xxF1kMlGbuHJ6iBxJkSpQtUPOSZj3wU\\nqinBWIabfepmQdm06Cjlwx98nGcvj0FU1O6EYX+F49qhw4Tdh/usjs7hQtupr9sZr/3Zn/PSd36G\\nJ556iRrB5WVBfeK5dukx3v3ql+gPRqz0Mvb2j5jtN5T1gmlZMT0sufjxj3HpuTFXLz3F+OyY7Pw1\\n9Ogsj3/nZwgs8+DBFgrBaJxgE0FrPcJZRssF2yf7tI0j0OC9xbYRSvQoJyXT6ZTMR+RS0I/Tzl6x\\nfh4loW7Kb3pefcvj+0SAIskxvqMfrQ5HtMJygsS6FishiyRN0yAUtAFMCAx6PaROIXTKk34SU7aG\\nVEsiDa2MyJNOp6t0TnCmewlcwLiafp5QPYrTDHprNNaQ9VYwokS0glhJgrI8/9wzvPraDbwTmABa\\nFVgC/c0x4zzn4tqYnUNJcCVnxj2ubia89ta7MLrGw1kNJejhkLXBkN/40n1GgyHOOVS0QuIDXmkm\\ni5r+uEAJzcPtBfnqBVKt+NwPfDv90TKNifmzr79Hr4ipFiBDw9PPXWFxOufalVX2dreZTI8opxN4\\n1B/3KnQiPAWytXgCrYrBSEQa4axlVs9xZU2WjDDVHCck2eoGi8ZQzyse/k//PcPJfao47s4DVUY5\\nnRJsQIoILy1KKSKlCSLm/r1brHQGZqwUoCxeBCwKrWP+0d/7X3jct4TGEEtFoxu86Iav955m7lG2\\nRocEXzWoflehLDc2ie/cYC5ashSyqWB/ean7fQISZAe71hnxKMXhyEOXjY2TrLv4wiKUJCoKVHBU\\ntrMQfOFn/zMuuRaBwomup5+EgPENQgQ+2CS0zhADjoCsms5dpQOhsmiV0P/w851B1YFUOcYtCKFB\\necnZw1N0aAhEJFFEsBYRBCiF1pLgBaFYJaoeIKTHuhaDJ1ID2iAxpgNw/1M+gkLg5SMTAIIIDU0H\\ntZ5NT1AuYXVzg2AMQnVAbpxFJzFtu4A45rW3brMyigmyob9xls3lIbffusW8nCP7GV/56g1EscSD\\nNqCs5872PrPTGUrGfOpDZ9iu9zGH+8RpxPPPP8++vUVfrrEyGPCd3/cD/Mqv/X0efsFxtH2ASC4w\\nbSrKxQ7jzRVunRgG6ph6rrl0dpX1x64wVS2n93c5fe89Dr74B4RI4YRg6/Y+y8khLzz1LHfv3cIu\\nNZzLLjHZ2uZ4WlFVC4p+itfL3N3awRhHqmGtSCjrY6J0SG+cc7LwFKpl//iEUS+FqibPBkxtQ912\\nl5Xf7PMtP1CDgEVTI6Uk8jlOO6qywUeO5lFHum08SRIBisa0IBS9Xg7UtK1DZxFBSnSsECrmtGnR\\nIcfj0Tqmn3qS4iJRtUvja/KQdKBgXaCspfZtp94QgSJbwaqWcnFKHGsGWY9rj28SJwNqp5DesL13\\nyne89CyHh4eMl/qE+IR6CiezCa+9MeeTn3iReeVROsEaqGZzTg52GC4NKZaWefkr72LDnI9+9OM4\\nP+flNx6ipSd4wUevrjGpG3b2Z9jG4k3J/fdf41Mf+QCxjshyzfRkysHBfZZHy7x/4y69yGOqEiVa\\nhLN4HeOcJQSIne1qiFLSXz/brVba7uMStb7bMjYtXnZKUqUj0iJn52/9MsnOm50RobLUgz6KGGka\\n2uCABm8Uquig8lI7vDMYEZBBoxJQQjCd14z7OdY7loPvqFAqoREWaQRKJOAqXFywOu5THili23Yr\\nK1vQCkP67JNw98+ICDgZ8Crnwg/+IM52JHsvug+IlII8LpBCU3qPsBLbgKQl4Ghdw/rFdbwPRCrC\\n03Bu7z5WOSKbYERLFnWJEKEEpZMk3hC7Eq1GBOc5G6Xc1ApVt8g8JVjP6Ld+m51/8rs0tkU2Bq8e\\nuai1Rp3uooXGKkXmwckY5VuCbQmqwCnBu0HzoThGed+ZBxCYwRIyGIKMOvq/VDgk2ks8jshHJBk0\\ntWd1fQ3nAsFCk3fnqk508sBId9VtITVRlJHEjmBjtqZzvNM09x/i720RI9FFD2FqtJcQCaSRIAU6\\njRgMzxGc4/ZhQTY6R9q3BBzfuLmFpkeSNlTS8uu/+5vExVkmpwt01mcUe6o4QnCGv7izT7x2Hp/3\\nkKnl9qLk7uuvd7ZSYRDrK5RNQMUJrTWsrJ4nlhHf2LrH7I09ZnPL+vomxpYE0xJHOerQUccnqCQl\\nKjotz34rkaGlL2Br65A25OQJaNdSTRrwApfEHJ82FOMhg3+GYP+3/EBVUpHGOW1jIQFaAzomLz0L\\nHzpNLdBag9aB1Vwj45jZyTGLKKY3jGnnBlm0SJlT1gtSlbOy3ueTL3yA92/f5PmnrmIjjW+X2T9q\\nQfR579YDLmwss7I2pjcoOD7cYdjPWZxWmHaB7PXoS43DMPY5eaYZ9YcsKsPTTz2JFi2lWaCODUtJ\\nYOlixlu7ntXeCr/3+y/z0sef4MkPrDFvKm7c0jz9/FV2dxf0k4ylFc1nPvGXuH+4RWw1L374cd59\\n930uXzjDzv6EjY0h3/5tL3C8s8MrL7/Ok1eu0EtjTiZzjk9bTo9mXHj8Ejv3H7K7e8jFjXWK4YCi\\nGGOlJkEiVYxwGkmEFRXCOcYbZwlC0vruQ9R6gQzd1jUERRRJRpvrCBS1K0iNpBYSrSzt42u0wuLI\\niPF4IWlSiZcRHom3cPmZ5zj9/V8hSI+rNS4BnUqqZo61oN9/E2NcF+1yNSrO2BeWx6Ic7Ty2mlMQ\\ncCTEwmGFw3vL9/zYD/PH/+SXcdYRCEwLzWf/pc9xMLMIXHdm+sgZJfsZWoILAacDiWw6porSZF5y\\nUne7Io9g70/+DMo5kpiaBpCs/xf/Nds//7OE2Zw40cziwG/+5I/yQ7/02wSl+e/+vZ/kqojJCkVC\\nQuNP8I3F+hZvHJHUyBBwEXhn0Cbvjj+CRzqLcCVCaHzoLAPaw/f+Rz/N7s//h8ytIQldhrV5/gWS\\nJAZr8UoQBbDOUEuJkh1YpXlEmp+fHqFVg3OKGMV4dY3Ie4SMaZ0hCPAE4lSjXQKFJAtjXvqu7+X9\\nV1/HhwUiDNncXEcOulPZ7fd20HFMlsWc7NziwpWLYE85f/kit28WHE93mU4dhqZjaSQRjfM479FL\\nSySpxoluRS+lRkvFY489waWrZ/niF/6IYB1lOychp6HGGY9Xjl5/RECSK4MxBkLESn6R9XNPU1UL\\nXFPj3TIq1wSrAE9cG6xfYI1nNF5ndbSGUkO29m7RT2uapiSOCoqlmOPZMW07I1aatc2c1dESy70c\\nvvT/ovX0/8sneE+9WJDoiGZeE1SEaQNOKVTb/aMKKcmzHKkVdZBoH8h0jpeBZmY6XNtpwLKLFBmR\\nnvP0Ss7uvfe5msf8yj/8P8mF5vzFMecuPcbJg/f5YFZQVnto/yyn23sc3niPNx8uWFntI/wpO0dz\\njqXmfNrj/a0DvJYUsWfQy/nYpz/O53/nZaKBYGkwhJDQ1EcsLZ/n5TffIM36HGy9y2SzR7u9w1uJ\\nkvsAACAASURBVOmdbXxVsHtjwteOS77zo9f4/S/+A+aLizy10bDIFU1t2Hlnj9Drc1ZrbPUQEQxP\\nXVridHbMBz7ybZTlG0xvvkEvXaI52eHlr/0ZT1xYRQfJdOeUzaefQHtP4w1SQj9NcRZwAUPD0niI\\nawPGmkf4w46r6YMikR3VZzRcwXnD3tkLbG7/CcIbghpw8Qd/CB0E4js+yMzViBt/TiQNJh0gvICo\\n4cmPP89XRYTDkSCoVUx20nLn1/4DovQi/d23UTrDh5YQ9QgEeh/+GMJ2RgtvLCGKQAlsY1F5iiDm\\ndHFA66COJIXwGCXYnZSkFLjWkCZdosFLh4yKDiweAklTEWyCFHWHu1OwcukCQgiMCyx+4ZdwxoGS\\nXcsoihlcOs9ro8c4U57ilCRBcdX1caFEknKtqolkQLctJoLER51R1Fpk1Mf7OVhBwKJljPWhU6gE\\n29Vm4wTzKDoWRRKvJeNnnmXbWYSQnQnVBJ76l3+Y1pSoyCDnEU42aKEg7Y5INJo2dNyA8aBPoEcU\\nC1o8Ud6nDqC9Rauoq8JmEVHcp3U1wimWxyOurBdc+76nyHUPokDTCnb2DjjYmvHJT1zkQy+8yDvv\\nvMbq+AV05HjzzVc4s9ZnvCS58MTnuHvjPVA5TaX5R7/x6zSLCcVggG5a/spf/j6+/o2HpHFGlEie\\nfn6TXAYOd/b4qb/xY7z93k2kF/R6koOjmrpcEJRidTxGSENrPbpXMEh6tGbOfFajZM79/SNW8pQi\\ni1BZQpwYylPLtFwwKDSZzmjaOcEqPhK/wN6DLXrFmHSYonXM3v09emdXEU3L5GQLJ3KGSe+bnlff\\n8gMVASIpCKaFSJDplMXJhOEgovULfAhkkcJaQwoY6YmCQMgGHxyyFZBpyrYi1Yo083zssfPsvLvP\\n6oWzzAJ87wtPsru94Eov58Zb7/DOgxniUsH5QcLdP/oCt2ctmsAPvvgUv/iFV1kqFJ947BJ39nZ4\\n8/4WCw/Pjge8vTPjuy4vcfMPfpvvfvJD/Omb1zmfa+7vTbk52WejXcMnA2wkOZ4u8GqDL7/yJU5P\\n9lk+WmZRtuTK8dqt++iFYjkp+d0vvcuPfuZ5zpzReJ/QTwoevHebM8cnWKXYO52TDDS/9Yv/FUoX\\nSB949b1XuNiLiI3noH7I8a0tVlaHvPrFz/PkR74fpSK8kMzLujMdqEBMSshjymaOR6JEB5kWdHg+\\nhMJHEiuhkJrnf+Qz7L7y28S+pE5g6eITtELwyU+9yJ3VhMAnMbZB6x4Ii/OSI3OK0AXanaDSlMg1\\nIDJm//sdUNv4YJGuO8+VwuNFwsa/8D0dNVpGHSAYTeQdNo5wTYWSAhskLstIbYOTFpIeSmhcaPGR\\nwjUaTwsSBuOCxrfE3oHRlNpRmAhUgm0bQOGDQddzWneCihTeekSUMckHBO341N/6j7n5Ez8MRmCC\\npPYlY204NSkRDc5VyDTCtgKfZwQZQVMjqoZUClrfEqmc2tXoSDPOYo5a0FqhtcZb033IHARjWWDx\\nKiHyBt8Ygk5IllbwwqOswicNhBjvDNO26lbYwaJERBAtxkIIpoNK157gW7RM8FoQmjkqT2l9QEvD\\n9N59zi0t0exe5+u/c52nP/FpmnTEW3/0ZTY/eJ7a5PTSQJ6u8P7tN1gbKq7feJ2LTz7O8oW1DvbS\\nZhzePeLBjYdcfOIc88V9zq1kbIuY0AZQDlse8JHnMvAtr/zW73JndgGhcy4+do5f/bt/k8efeQaX\\npcwfWLLlSxSjjJs3XoG9mGAWTFTBcn+dfVExznpMvWRmTomMxMVjTvYUMpmxeuYy16+/zORomycv\\nf5BjZ1m9cJ7Toz2QGaPRANca5osFwQqSUcxs6x2UgXR5kyAclZ190+PqW36gdszjBcFJ3KLGrPSI\\nh31OqgkhCJTQXT5Sd9m9VGm0hLay5P2UJtfUzYIsSpA6QLDUISFEhlGvz8wcc3hwTBGP2KumnEwX\\n+KZkOT/D9cM50nlkUnCtn/FbX9nDi4hZ1bI3bVhP+7yjKopYcXNhWCokcayRy8/yB289JLGBq0s9\\n+mnNsxtX+OoNQ1l2nfk8kzy8fpOVQY+eFjz31OM8OJ0zP53Q7/eIZY+bt/f4kZeeYTb3RM2Md7dO\\nefrKJsJ53n7/Ommxyr179xivrtBPY97d2uLy+TXmdcm9oGkWJUEnrA4T3j84wLPPX407731jHWeS\\npc45agNWGtY3z9EE8UhR7KhNt5JTOsa6GpqUpeVNFiJw5fxFrgtYljEiiwlZgQqCw90HFMUSR0cP\\nyIsBF65cRmpNcN1t1OLyZ8lv/CalaUjiAmNbBBLd1h0IJfZYPLGMEcMBm5eexcuIsixRk4AFnDJE\\npMhhjwhJ6z39aIWJv42QKYd51umcjUFZQBjwARpBOhhjlcJbQRAlmVU0WQTKYxPNytqALM75v37q\\npznvWryFSMTUrmb87/wMghg3gDZWxI2nCgukyPi9v/Zv8dn/4VeRtaEWMdo04BOElNQf/FdZnFiS\\nVBK///u05i5COnCAcbz+G/8bG9//r6B8jTUCrTKaZo4QnqBkB+CWMcgG5y1W5xgJovF4WaGaGOsb\\nnAgkqURKgReiOyeXgkh7FAneNkiVkPT71KSI1tM4TzutGI4TKBu+95MvYg7uMKmX+Mx3fx8PH25z\\n89abXL97l4f7e2iRMFof8cS5db786j303tsc7Fqu//krOAKjpYz56RypDb2lM/zh9b/gwf0pLl8C\\nPN5DkS9j3nyd92/eIyoUpjYs55rjrV2+8PLXWMlWefUPX+PqxUuQGg7evs3+ySHvH9T8yGefZzLt\\ncbT1NifyLpuXVtiazXFtA1JA2/Dm/oKs6DFr4YOfLXn5y9/gw/0rBHmP6f4eN774RZIQs7w+YJYn\\n0PO4ShE3DaUzxD7CZ472vXexomE0+OadUt/yAxUC0mmEUojIY2ygtYYkVrSu7WRoVpFEAZQnEh4h\\nElTanYUZaxhmKRKFMQ09LfDtjKeffZbJZIJuW/LRiGpmKU8l1zYu8+zTAdNKnrt0lsZYHgsQrKWN\\n9nk6rPDavS0GSxuM+oGlo5ZqUbI6TvjB55/jT169yXE558NXV7kYj/j864fkQ8Prrx7ykU9+N7u7\\nhiLrcelSj6Njy7RuubSywu0H17l3ULF3OGVF9ehngTf2dolcy43Dkg9eWSPQ8vr9LZ48M2bvgWcy\\nn7BSBLKoz439CesrYx7uzemnEd/+0cep51PefP+ElSiiDhX7JyXCOIxUJE5Rm1nXcBKBOChCKLBC\\nElwDOkZoTyvmpCIi+BybVNRmjkJxbCvGWuKUwfbXaauWtOghvCMfWppmyJmN82zfu83mY08RqY7r\\n+dTf/Kvc+4mvIOYP8MqhPDjX3fQrLcBrtNI4kbL2Mz9LLQKx9yRZDs2UpJqjXKDpdStKT0BqxyTv\\n09gYFVqufPpTBNHBvUUMVB07IESBRKfMQkBraIMi8QKiQKRzFtWUkOSUdc2V3SOc1fgoEBxkxZir\\nH/8obbBIJZHxGqLdJykDMmsYGseNr36ZKg5IobCuAMX/zd2bxlqWXfd9vz2d4Y5vHmroquoaemI3\\nyR5IiZRCjZRkKpBoxXKEGAKC2IKBQLIFD4i/JA4SwYaQOHAEW4AFC4FsKYlt2ZYlKLElUSIpiWKr\\nu9nd7Oqhqmt+VW8e7niGPeXDflQIIxD7QwIwvsDDu++ce9/Bu+/etddea/3/P9os44W/+aMEBETH\\nb/7UV7g4vn8KQXREpXj7X/5TNj7zI9g2oEWanSxNB4tGCI1yllF3gYXjGkVgsnYWiScaRQgFvq2R\\npUI5mMccfEyzwEElUoPUBGXxCITwFKWmciFZHwZNXR3S7ZXY9oS86RJVxuxwhxuvfJXbW3s8nFbk\\nC0u084o88+y8f5efe/VNFpfX2RvtsLayzvNPX+PW/bvsPtijsoa1swt86UuvclgHpC5Z6ywnMx4C\\nH3/pCm9/4XM8eDjhhMCV5WXGd4/Y399jdWWDjV5GcxLZ2b7H4rAHxpPlHTrRc3x8xOt3d8iC4KmL\\nS8wOdzBOoEtNHnImQfL4ZhepAqaIhFvX+fS1Myz1NMd7FWVhWFvJ2TuoQPeQtqHZKXB2is81m9/y\\nFNtfeotw4GgEdBaGPNw5+MDR6ps+oArS2EKUllk1ZbGzShPGOJchlIQgUDK5auetTA7hSjH3La5p\\nWcxTE6JpKjp5xrOXN5k1M8rFgpPDMVOvydsOhZwi+p6846lGEVM4HtzeRinF9/7oZ/jCv/oSTy6W\\n/LOvbPFf/NCL3N+ac+fWMc+vCbaPM5Z7sD+Z8/SVi1yZHyOFpm3mvPDcWfZ3jnj6DMymI9ZW+pQm\\n4+knnuRzv3uTG/f3sO2cO7tHLGRdtCz46Ic3eO/RAZfaDe7uei6uD7BW0QRJr9vlD2/uUvuSpy/3\\nGT+a0jCh0ZE7hzU/8LEn2N16n/eu3+S4aslcy3a9zCc//hG+9PIxyIQ6DioyPTghahAOZlpy7Kfk\\nJAlviBCDged/At9GmtmIEB2i2MT5pB/3j/0AihELn7pCXnYhtgw2znCyN6HXz9jbf8DCcAXhAw4w\\nWY5qai79/M/x/s/8KnHrN/Fijsxl4j4FAZ0e+dIlen/9rzFY7yKiJyIRwWHXz9LbP0Raj/CBOQqv\\nFXjJpLfAhe0TnOjw9Hd8D14EbPRUxZC+i0hanJLIXkmcBtqQlFYqCELlsVlLrjsMlteJt27jxARZ\\nKISXiCxwJAUXvUWqnCgDtwePc/HkkKzsoEm0iJN/8POoZo4tM2Rekp20jDbX0UbRuEiIiuFLHyHc\\neoNatUidcDAbbfpuRFKRhdCCSibYHkeUipvR8LxzWBM599k/DwikTLDFmElC8GghcIZU8/UgjSA2\\nFtEmtpcREkdA6YIYZshWUuSK6DtM9nfIfeCPXrlJ8HOGSz3eee91rBiwuDTkxo0bnF9d5d17O7z4\\n3JPM7G0m3jNuM9qjE/KbNxiPR4xOasqix+j6CWfPnuWc0rx/5xHh1L9VRNi79z4XLl/k6vkxY9uS\\ne43XNUobLi4t8mhrm+6gy8pwncnhu4wOJC5XrC4aHu0f46uGm2PPk+dbOtFQt7MELDSRTlngadAy\\nIzaRZi44PjmgXZiRiRybBcZHhqOq4mKxxMH+AzDzhI2pMt7/t1/GqZLcFDw82KU4ObUR/IC3/x8E\\nVElDoJMphoMSi8NGgTAKLQ1CeqTM0fJrzlOOg6omzzNs7ekax6wCLUEbw8HhIRtLa7zx5nUGepGq\\nmhKLltks4uScxuUs9JKCpPJz6vmUL/zGF2nDMe8ftLy0qjm5u8tX30nBdtqBdx6N+ZHNcxzs36Mo\\nNzmeVkyO97lzLDi7cchorDi/AK/dvMnyhYtUYsTh4QOmoxbnPMPBKp++tMBvfOEG3/n0FW5tTcli\\nw7OX1nn5+l0+8dQlXn53l+968Sn++J1bvPT4MkJE3rj5gNoFvvvqGVAn7O7vIMKM0dzx3c8/w//+\\nxbfol7AyaBhvPUCoJskyTYH1LZOtPWIbUBGgZVAsJQ06OUhF1gs8/ZPfRxACI2XSyIdAVAHlIk/+\\nd3+BKBQxzBNWI3qMFKxt9Am2pGcXKXKDixItAtE1CC0x2vPU3/5RXPgRDk8izYMRjZyTD/ssbQzQ\\n2pJlWUKqhIDwDWWnz9N/56+jnacoCuZNiylyYggQLZ/6r/8iMfw0B0cjvE6adaMyBj/zDyEIch8p\\nhGJmu8AMFcH+5N/BW0+IFqqAVBr6K3zl3fvkf/a/hdYxG8/Ioif/0CKIDBMSd+vP/cO/y+f+q19A\\ntRM6ZZI7yxCR85ayX9LOKrzyrHzH89RNi8wNufV89i//FF/6/Mu0zRwX5/ToEYsaESSiWCNTPWzw\\neOmYR01r05z1X/mnv8jv/2c/Qt5GPvzZP8Pu8T5tHaFQCecxb4kyYrLun6gHrRUYIZkcj1AxEKNB\\n6kBlffJOzcAFiwwWHyOVhLV+4NPf+xl++X/9LZ55PMONPOOjY65dvMLx8SFttBwf7jFY6HPn0Qm2\\nbVlZ3uTpZ57g/r0tnr2ULA93946Ze8H1Ww/YPLPKpLb0ipyA5P72nKvnLV72uPvgEc8//SxvvvIa\\nR23AVTPINLXz/O5rb9DNJEulZmmhy87eMR29yPE8cHVhhbMrK7z/7p0kxChaXN1QypZ5A7LfJnGJ\\nBx0dhwcjFodLNCc1pqu4vNBnPnpER+bMplN8plBSUAOZDOwcjSBonIr0yvIDx6tv+oDq8XQKRXQG\\nXaTB5xxJkVsaG4lIpITaJba7E4aFLAMsslcQhSSPEHVGmLeYxS5TKcnaHEtFDC02ZqhcMugu01vs\\n8+D2I3xdo2XB0uoie3aCn0zplxeYz2e8tz3hzOaQL98+wh1K/vzHHqe3OCQcHLL76IRiwWBkZH1p\\nle39XWYzS2g0Umr2dh5hjOGt19+lM7yKEQI3GzHJhzx3tsc7D7bpFCucW17k7dv7PHFhlYdHE3Zn\\nDb/y+bf4wRcuce/uFsPNM8ysZ0U3vHP7hMzA1c2zbN3b4/LaAp/78lsc1RXnugMunl/j3m7L+XOP\\no01OlFAow9bhHAg0ziKVoZENJgBGEnzNtKnpd7p4JwlCJI2/lMkxSoFwgahAyYwMQYNLmGRRIKUm\\ny9Sf1GNDBOEalOnQRoHJJNSWlUWFWFrCqBWkVjjf4GMKpslKLXkPAHRUSDOzytLNBFEEYoh4JEYq\\nlGhYGkAQGXa8i1lY5sy5HEMijUoU83oKIRKj54kXl1GZSdJUNJ6I85HnPn0FGRXe1mht8EJicNgY\\nkFlGNW+wYspL//2P4fHgA1ECItkMaiUQ3mKkoQ0eJQU+ChySnek25//RzyY7QUCKREmNOK78wt/H\\n2wZlcrz3XCUSpCDKyHZTcekf/y9kSrE7GWOdQHUMV37xn+AxpybhERkVBE8UgkxD60GoFhcEKjjQ\\nGq0VtB4BRC8RRiCc42/+9E/xxV/7Vf7Zv/4D1gddaHucjLY5tIFcSdpxy7UL58h0hzv37qIJfPza\\nOdbPP8Zbb21RLmTs7u9T24y8yGnthBAFh0cnFAtLROfRUnAyb5iLkt/50uscVI4LF2puHI0IouAH\\nv/clvvDHbzJxgn7p+fRHPsTn33ibUFuWV3o8PDikX3R48kzBtB4zqVrKwYDWVqhezqyVxNzjWo2U\\nkip6Km9ZW1th1lhElsQguMDhdE6vX6D6PWzdIKjQQTOZV8ycS5NFvmZR+g8cr77ppadSCLwVRG+R\\nUqCynCLvcTiG2rvESjq1K1REXFXhXIsSKjGNYpO2tdFTCc3dgynVYc3GMGNWl6ycWaebZ/QyjbSS\\no91DcqkxnQ6yqGmbKe5wn+W1JY5Ht1k7s4zRKxA13//Sh3npbJ97uwe8dW+byoHLBbvHh+zbkiAc\\nn7x6gQ9dGfLAKqQx9LIBRZGxszcj+jp5Ni506DHmxknLxtoiWweP2D08YtIc41vLF15+hxDm/Ccf\\nu8JbNx7S6WuKOOeJZUOn2wUmjGeH1DOLrRs2lrusDTt85tnzbGwuIJ1kdzpm2E0qoaRbt2xv30Hk\\nAZFrApGyGOBUQfAeETTDvEfAJIS0CKB1wiDLSBZjGgc6ZRk1EbTMyWXC7jrhaYSjbhuED1ifhtZt\\ncMnlyKdAg7NoQiJYOotAJxS4FqAN1luilkRaRLQoxal80CUHJaCMMXHpJSilQDh0Z4mARESAZNgc\\nA2lhEB6nBUomqz4vJDEKjIhkp5Z+InqUKUgCzlS7F0IQg0BlkhBtsij0AUQiApgYUTG5OPmgUuPI\\npDenDhBjIKhTy0JjkDEiokO45HgkSV6cBIsk4KSidQkPk/xsFY2PiJiAiQSIJEmtUZALyBSgTQrE\\nMRK84P7th+gsxwWflFcy4gXpOsLj25ZM5Lz8219k++EO1WwX4495+qlVls90OG7mXLh8jU+8+Byt\\nBx8dZ872eObyKm/fe8St+w+YTw85PNijihnWWESUCKF44rFNxvMqWSPqDCtP/RfUkB/41HexZiIX\\nN1ZxUbLWHfLerXfICkNVt4ydYWf3mLmNyNDBhkjUkouLBTfuPGB7dAJaMZtPmTSa23f22DuosdZS\\nuZa6qmjGU27uHLGzO6G3WRCDJRORqDSdXkmGxFVtUgrOBLWKHNSBWWXBtQzzAfXsPyCmlA8Bj0Ya\\ngXUe61uiMkgahK/S+zmkD4L1AhE16ByBQkRJ4wTWejwaFVs6UXA4H1EpRdb3HOzu40UGoaHsDXD1\\njJWVJQZlFxkz+kUfk3c52Dmm3+/z1v0Zd0a77M4sd3YeckJkpheZ14a9k5rGWtaXl1kuBbWt2arG\\n3Duq6RB5/MwqVkqcMERhmE9nVK3l3MoCN3bnPPnYGc6s9PjQlTM88/hVRNbjsQuLXLu2wVNr5xjb\\nFllXdIxia+uI1/cD3/axj7G2ts7ljSXG7Yhe17DzyJKpHGkkj/Yqfu/th9jjEcPlJYBk8RYEJ1sP\\n8DYppkTQNFOLkj7VR0UkCHmafYmUMQoIIeBsIMZk/SGIuOiTPaKDiALXEskwUlGeZpcZ4dT4WJxu\\ni0Qidpqk9c8yjZGGXCls8MyrKdE1SGHwMdA2HhEM1tqU8QmF8IkIMLcTbLCEmMB+3ga8kDQ+EnwK\\niVLxJ/6rwcUUWDBJtBBaIJBwTzU65W6IkBhTKftLs6PWNSkzj0lpJFVMFFccSIHAgvfoLEvZog/Y\\ncCpfDCQHqlMP33DKKnI6Ug56pxMtkrLIUEqiRCQzX/sbILkMnAbXLC10Wmt8FpFS4YXCkmwZYxR4\\nAp1SUWQ5bfCgEjG0tm0arRIAkbLsYAVcv32T7/zUJ5Gmw4XHN3ntlfdoo0qjUEfXcXpKnuesDAaM\\nRg2N7/Bd3/kRoq9pY0Kq5METXWRSJ4+Ik9kEnEeqDOUjWWbomx6DYpHD3XdZ6PWY22O+/ZlLvPjM\\nOXYewdkzF1nQC3zns1fZHh9w7coFDk5O2Nk5YlQHQgj8+E/9DeKoINcFQmlMplgc9uitKiqX5NU+\\nBLTKeO7SeXoDw6N3j5nhOLBjfNUgoqRqfGJu+YAqNFI09JRjuZclgkSY0qjmA8erb/qAqqSgCBEZ\\nyiQznTVECUqD0AZNTLOJUpIJQ1EU6NieYg4EJqpE+AzJ+9AZidGOg4lj0cCw1yP4EcF1uHtnCyU0\\nJ6MpTXBkxRIm67C83CfLMu4czvEm0oqM5XxI6zMGwzMo46iDY+PSCpvnhzQSjmcpi9jZmdPOPZ94\\napNbOwdpBs9afITl1cf5/m+9yO+/coN5aKCd8AdfuYebV7x76xEvPrbC1oMJa71F7u0d08wrbs4d\\n3vXJVyQ/9LGr7J3s8O6dB7Rugp03zOaOdx9tI4zgD69vcTwagQ2YfJGL154kqgwjLTEoFrLFU+MQ\\nIJc4kRODpAW01ASadC6kbCyEgHc1QiucdrRo3GnGIhToHNpo0TEgZCA0gYaWNgRE+bUw6rGkJkmr\\ngFMiQN02hHbO3FYpI866hAg6WhCCjs4IrvkTeqsKyZbPRcizAVJ7hCMhcYxOXPsYE8qFZP4hSBA/\\npQ1anELtokGQIbxHR0WIGe2p2Yn3DSooiihRQWGsQEWPVwoZEoW3dmk7LGNaWGyUxBDwwWJDGtcy\\nQhKCBWFxBPypOZWKERsSuytMk2+EiJ66CTjnsaIhSE2WZajoAYGMkRg0PnpiDBAj2kNzqjeXIS0W\\nMnhEkFTzhuuvv56Cmgx4B41wNN7jHcioaFxLqQNPra6yd/SQc6uLSJtMWO49GPPRJ17ANpp33rhJ\\nlmXc3X+IKgb4UcUbr+xRhSFrmxcpsdRll1sPHoHp0u2WjCdzlDS0bYuUgLN0bI1bltx/NOHZJ5bY\\n2Trk7sNtFoaKxy50mBzu4fSMd258ha3jijvv3+Ljz3+Ic+dWuHg2Z6Hs8ke/+UuM3BHzcJrZ4ynL\\nEjF3lDqQk1MoCdFz7vkXcHXyAna1JLQ+JVqzmjktOA8hcnh4zPEoIkVBITXRRqTqsFhmHzhefdMH\\nVJBMY40wLcGU5L0i6XSzDGRMJE8hcSLSBM+oqqicSPZ0QmGkwkaJEhJaxdGo5elLG5xfyPFRMTus\\nmM4iVZ7x6T/3WVTsMBgqgjPcffiQw+MxhycNk7Gjm7Vg5zxzdsi98R6f/NanydSI0bTLwqDkxns7\\n3Lw9YntnTBSCZzaX+eQnn+XppQUe7tpTfIujyHKCBt/ucrg3Y9xIGEf2TlrOLC+wutan9RX3TiqK\\nHE5OKk5ii5SSLi33j6esyRVu33mfrYMG21ia1nF2eQkbco5qx+17e3z08lmeWV3i+SvLrBSOS5cu\\nkIu0wgscwifrPd+k7quQCd+ce386UiPxpE5y8IqAJcuSPj26SKZatCIN0ZPjbUBHsMagfURoidAC\\nLyP1bPonASMEjRee4GtscMlU2gdCEAgUBJvMrZVDaoEJHi8jXkiS50fKCDUh1TbxtPNUN4zO0bgG\\n7z0hCHy0eG8RLvkSfM3oQhBogsOHmugTAqb2DTbWCBkw1hJlidORiqRkqhG4qJEBlPIUWiUBgj/1\\nFvAuZaWA8A2ZlPggcCFhqt957wbe2+SJGhLQT8gU7C02lQFIckkhBIoc6SPTpiKIQBQgpUSKgAiG\\n2EaEbwlCkMVUvxU6cda0TGUXKVUaYdWCECQhCmTlKaPF1h5Hc6rcCtzdfcgbD094uDvlzvsP2Ty3\\nwuawy9Hu27y/dcJHX/gE27duk/tIVwhkIXFyjHQHSNcy8Z6j8T4uGEaP9jnaP8KLgMwVIGkbIBpO\\nxsfEOOCJxzb54zcesbDc56mrZ/mtL73KOw932K9qHh5D1unTdREtJO+9d5137++z1luncS2HB2O8\\n00TZIFWaIvGhoCgKlO9QtWMmquakttz40h+nRlxoiK6mqwbkZQZ9gxQ5rVBJOTYc4IQAmxhcssjp\\nFyq95z5wtPomvwmglIbQWpybYyL4GLBeJs9IIqWONNYhRKDMcoyAyjoa39CK5PvoowPZlF82RwAA\\nIABJREFUkBvS74oFq+dWmEjP2Y1N5PyENz73bymGJQcHc2KYcu3sAjvzR2TekXVz/HjK2cVF7m4d\\n01Sam9fvMZ1Hzq5JDo+nbCyWzOuWTmfA5toGYjBgdOwYXMh5ZXuPRnq8i2jpWRtoigA2VHzbs2s0\\nGmRrOZ7MWFjsczK1fOhaDzKDzJNq6fX373NpucPO5JBXb73PcOEM7azi3KqmFgVra0v0Cs/F5Q6X\\nN5YY6oyZjkSpeWe/ZmlpidalYW7vI0wDxAydZxAsedFPjSDhTzM6hXKW1lXE6BEYYhTkOmKMwTct\\nKTwFVHRMqirNf3qFI6JUJLZQGg3RkEmJC6BFi/EKFSQmz1MgMR2i9siQENdGCQwZ0ZNc08MpN4wA\\nJGa9lxDJkEjKMsOHNs3PCoElgoJ4ajAdpCZIRQwSH1rq2KJUKgu5IHFBJTxJlFjvsQIQKdil7Xra\\n3ofokOH0PBHpBUIaiAaPQYr04XYix3mPFFDXNU4qnrz21CkpIBm2aJlmRb1Mzl/RRXy0uFPJaFr4\\nIlpkRFkio0p/FxBiA0ZgUTibJh0iGmvTwvs1KkCQngfv3kBlHYQKlFLTuvR6lB2FFAapIspBf2GB\\nrUf7TOox5coKTS0Yjw+YTCZ8/LkneO/6V6l1xt7+EWcWBoyEoNAlIjfM6hFdnTE/brh6bkg5lDTk\\ngCS2gVwYonGMbc2zL73AF3/9V3jnwX2+42PP8OrLr7F3CB/72H/E5YVNYjvFhhmCjGlu8KKD7i3T\\n6Ra8e2Obc2sDvMzJhEBLQy4zCjw69+xPJxyPDrFKUY0E0gS297bT5ETUWOeYNBWNs9Tz5rQcFLDW\\nU81qYvA0Yoq1isxYatfS1P8B1VARSWONzmjbihhztEm8qBgjSmVEkVaxhMZN2UlpMkwQzF1EuIqm\\nsYRT44jDkaDb09y7f4sFk/PezUdsrHeZNIfUsynDQjM6mtHp9VBVRu2g3xsy6PWYzyy9TuDiUsvR\\nwSHTasr+yS6D5QWWh2sUPVhbzdg53CMf5OyPtnnvXk1la4zsUHQ6VDPLzsNDRCfSGS5yNG+5fGmJ\\n3Tk896ELfPkrt7AEfueVXTLVp7GRodEMs5xz59fJheLKmQ2cbbi+c0RwBlXDa9dvIUJF7Ty7JzMw\\nfTqZ4fUbO7xwcYBQOT46JJE2CsK0whlLwBO8oLZtYuxEkUijMSYrklOoW/AeoQJKpWZJkAobUqMl\\nxkiRdxAxYmTanjoXwDuEC5RFcpxXSiCEQohkN4erET4SvSU2NpFQI8SQsB1GKlI+KeiWhogEAtnp\\nDKYkmVm7IACRPD69R8YE1YvOp4DsA9jErRcoYh3xviXI9BHQoUbJInGdogeR5jqJEhEgCo+IHiMl\\nXhn8bIxzHiEiNniIDiVb2tikcgIhxX4ZyQsDtqJxES0ERktEtKd1zojB0HiHJoLRqcasUh0QIU/5\\naA1CJDu+OpwC+E5rwzoztFIQQ0smBUKQsnmvEDFj0rbYdo53imNZIVVE4FExpt2AjFgFVdS4CEjN\\njd0dXr15A0/ExgI1WGLveMbljYyoMnycEppDRuMTjg4r7j3aI4o+ShpuP6h4794J49kBbdtipWf/\\nZAfX1JRG8+Y7b9B6OJpF5vMp/aHmK1t3eeXVl3l/65iHu3O0Esy9IdTgPdiwT+4VC2VGKwVSdZFZ\\nyXg8YVpbmmhoZ5ZenrO0tnCaYBmoBasrm6gg6ZgS0+2SZdD4GqLChgpiRqYNrpEslBJUicCS55pC\\nZ3Q6nQ8crr5hQBVCnBdC/K4Q4m0hxHUhxF85Pf63hRAPhRCvn379ma97zt8SQrwvhHhPCPF9X3f8\\nBSHEV0/P/c9CiG+IEwwhkEeP9tDvdomhxcWCQmdgVAKs8X93Tp2wp3yoiBMRGgcScqkplKFQno2N\\nTa6/+y6hciycHZJrwZ0HgV6xTmZKipUlVteWuH5/mwk109mMW3fvsPn4ObJS01bwBw8mvLk34+Fh\\nTawDpoq8+2CbKEvuPtrm2uMXOL53wOLiMlsnLUuDNMeI9ahSI0vJjXdvcO/uHq6ZsbOnWR4sI2ZT\\nXrr6OBNr2RgMCLpmNjnm/rzh3tRyfuM8CyancnOyEr77qctsj+dUVvKRJ9d4cGx49swKUjge7e3S\\nWMHC+hJlt0ToDELEOZdMnReWUzYjAjET9MpxMiIJAS0lPlR4lxhAUdTJ0LppqeYWQprz0wAuNXFk\\nTKYe0TcoKZA6AfdApeDqfHJQt2lbbgR4G3DREmSDyTpkITW6fEj+ts45ctKY09ZBxcH2fbQKOBVo\\nYqpHyxBoAU8qL1j3tUwy0FiPcgEnApZAaOcoKen1CnKTn45bRZrgaF0gilQaEEKkjr1MmaIUAhUk\\nPliCrwl559R3NCJCwMZA9JDrnBhOFWAxEH1q4tXBIaNDRdJrITN0FCjAhZYsCjypOdIS0/gaERsc\\nmdaYoBDR4/FkKidKQRsaWhcJ3pIRUdIQpEaGREtQmULGmjhN9ouh8AxDKg2EmBYpJQu0MNjgOPtY\\nJ71eRrM6XOfS+bOYbIEza6u8/HtfRPsZr92e4AUcjeZYtcryQs7Z9T6X15dpZ7soleGUI89zxnOF\\nyDq4Bjq9JXSnoKktVRsxtWG96zGDVf7gfkXj4GAeeeKJdYLyHE9raDwry4Kj+Qlv3jzg0mN9rJvy\\n8HBO46bM2jmDckhtItXco1Vk7lvGkwqpMowxqKHB06KKZDReFAVxCipkGKOQTuOrGYfVnE6vZObS\\n5MmwX+JDg4wgwwdHoHyQOVQH/LUY42tCiD7wqhDit07P/U8xxv/h6x8shHga+E+BZ4AzwG8LIa7F\\nGD3w88BfAr4M/Cbw/cD/8addPBKZt4FceoQw5AZsNccXDmR6c5jocTFhiHOVp6BhI0iBVRJtLXqY\\nUQdHz2gOTx6yvrpG28zZ2bVoERgODqmmfUZ2jD+A2AqcnSNyy3DQ53y/5M77h+zO5vRUl74bMejD\\n048tc3N3jGwaTLfg3NDQNQPa0tFMHNLOCaKl9WB0IocqoSjlkM0zGUdHM1xUXFnNOZgJDpuGN+/d\\n4VuuPkXbPGLnkWbsu5TScWZBM21rojBUrWfFG7548y4ig+W+4mhS86ErfWYedEeyNhywvbfP3vGE\\nzfIso/ERgcXTD57gwg9/iqPr/wIRA8IXvPljf5VtfUC36JMXMFctLli0MVAWDFaWyPIhyjhiR9PR\\nfVS/T391hXlr6S90aIKhFYrFzSXcvCWKHJtlGJHTiDSP6ZVCihwfNEKnOpZraoLuIoLCmJzWgzyd\\n6IgqR2jBUscQikWCnWOiwceaICSz1qNIWa0IwGmd2LaRTreglSBdZKG/QKXEaUMI6uCIwSOjJkjQ\\ntgZt8KdOWy6CDAEyg7eWIETSxreRoMKp4kxAcGilQElCY0EbFJHWNYiihOAS8M0LGuFQUuHbCq9K\\novIIL/A6oqxHaDARmnDadJIGHxq8BNF6yDTOBYIIlFHSSoEXAS0EmRRYF1LWHSEEsF5Tbd9HWo/X\\nisrXWJ8WCKMFzkVaW6G1YXPlPIjrRA8ffeoxXn3jFt/98af49c+/Ri4sV68+w+Gt+3ziyTNsH1ua\\n3QMWzwxZ6PXZn0xpUIzGe1w9/zi3H97mx77v2/lXn3uVYwJaCaKLlHmHnJbnP3SFlaHk7Xu3yGWk\\nJzMeX11mdDRj4gRSS1aXBtzen9KqguBzHh3OqZVmPUhGJw1ryx2KTkEmAivDJbaP92kqyAuDclC3\\nc0plCKrA13NCbPHWILqC6BuEK5F9DZOAqTw+zslQlL0hg06Htp4SNJTyg2Okv2FAjTFuA9un9ydC\\niHeAs3/KU34I+N9ijA1wRwjxPvAxIcRdYBBj/CMAIcQvAT/MNwioAkG36KaRkUzRNDY5dleeYMNp\\n9pAjlcNQ4HyEPAVTI6AsDL6FXqaYVZ66FRzsz1geSua24uxiybSjKLMh5UKX1TxyZ2uKLQLzOWQh\\nY6Yc8+qY4UIP708wIbJ4aYXj0RF3H5xwZD1xwbKO59EejBsN/ojnL1/jd956QFXDwkKJJ9KJJa1r\\nOXNhCX/csDroYKMnUmOo2Dx7jqXFnO2DBmEN0+oILXJCsOycwN5uTRFG3NmHMx3DhXWFaAQir7Ej\\nqK1kqdtidMHuwRYPDkZcOXOOwlTU85qsIxAEdIwsPb7KrnkcFW9gmwmlLlhrlwlOwFQyFAXBwymj\\nA3tjzCwc00EhjaaWO1jboHXCTu8KRVAO0WY8oiFXJVVsyUTCPDdVjRaaqFNzRauM0DYYrWlji3KS\\noJNLlLDZqaQ4kJsMT83pZhgpQWFo6wplCqIONCHipUyoajkkqgryRWJ9QOk8XnRQoaFfCFqRdje1\\njBgb0DbitUvMqSx1+JXWMK1QeU7djqiso9cpOJ7uo8qc1lYUw4yT8ZzOYo9G1XR6XXS/wGQF+XKJ\\n6vbo9xYxvUW6y6vUIqMoSjpLi1SzGfnyGvODmpiVKFlS9HpUkxnjtsbIAmMMoKljTlEqahvJg6D1\\nibBa4YnkiCDxJuekrekUConGR4lQElE94EXpk4pKluQbZ8nLAhcDMXpiaFEYRIz8i3/3RTInKboZ\\nO1v3+Ngzfe7cu0cbAs+de4wnLvd597ajDZH9wwM++eIzXH/7PSZGYZsGi2Ru4d0H9yiMYPvBAa11\\nJHfZkHA0EU5sxe/dnDAoBf0i4+rGJgvR8u7+CVM3Z723yHhWgXCUeUE9nqOlRKxuoPdOqJsxZb+P\\nbS2yrLh9/5jlpzeZzhW1q+mIyOHxESvLC+huh2Y+RgpJFBn9jqKeNFRBYrVFTkwqKGWQR0lRyDS1\\nM6vJhyXRtUzH/x8hUIQQF4GPkjLMTwI/KYT4ceAVUhZ7TAq2f/R1T9s6PWZP7//7x/+frvMTwE8A\\n9PIMH2ry/oDZSUXjZnSiTtlpaJGn9TutNS4EdBGJviEXXTQOKTwuSOpqQrSKOrZMGs3+vYSTvfvg\\ngN7KgNndwNog0hn2ubc3picccy94+kKXSQ26zKjGc3qdkusP5xxODumqis2FVa6trHD/4CEH+jxq\\ncojINSezwKPRKYbWeE5mFUt5SaNaMiV45rnHeO3zb9OGmmYOskwd8fn8gI7QVJM9pmOLU3NKr+kK\\neObyAGE8o2yRVX9MnZUcHQma8SGdRUXdWAbdgJddJseHHFQTvu3jz1FPGyajKZnpYKXFKE2IGikD\\nF37277L9N/4SMoxoLUkaKgtaP8cjkELihEOLPl7NGThFQCcPTyfRSiK8IcQaqJFqSBQ1megQRTLj\\nsM7hbYE2idjpWoHSaXsNYNsKmZvU+a8sRackioCXMdVHY0XwknxY4MYnCEoCDp2DrRt0lpOrgpYp\\nqu0QQk3o5ISmRswltXeUpgIbqbwm4E4bfeCqmkYL7LTGmAzhMmJb47KcAOQnLVFoeiLDjyx9tUwe\\nCurG4o8cG3EBuSdADLDOkQmDyEpCWxP0hHG7Rxs80ViUzBAqNUZ00UW0cxpajMmRVtHYOYXIiCZi\\nfYsiRwtJEyty18HmLi1KOkPJ8vT1m2Ninhpy0SByBbYlyjQnK1Qq2ZiYIdyMeP5TxBjw3pHlhmnj\\naUgwwuXlDtFGvu3aJuvn+uw+qvjKwx0aIfjoS5f4/T++xUlTs7S+xJnRLjdv3qQyGblrybTh8NE+\\nGEPbWJbLPvlQ0u8bDsYBoWIyLDGKYXeT7/nhb+EX//4vIduWP/vxF2ireyzURzAKPLZcIDcCe8f7\\nCLFMJic44PatB3zbtcd56/Z1UB0+cf48J9M9FjsFIp7Q70RyN0AqS6dT0FqHmszwNuHllZLUcwsq\\nEQtsVZNrxbwVdJTGa0u/6HD3YMRqKalmlpP9EWc21j9wjPzATSkhRA/4VeCvxhjHpO3748BHSBns\\n//iBr/oNbjHGfxRjfDHG+GJpDDYG3HyCkJqyLKmdwvoWFzxBaKZ1jbUelGDeKowxRGkRweDaSLHQ\\nZ573Cd1lfL7BSHWZ61VatYhfWGG7jcy0Ybftcf1eYieN20jjLCsbZ9iZHHJ8csLYW6xaYmG5xw9+\\n5lt58oVPs7Zxhhu7Y66cv8RmMce7hsZIxqbLzekI0e1Tlsv0syGZ7qZaG4ayc417RxMm44a6nnP/\\neMzWzozPv7bPuw8a1s8ssuM843nOyM+wwnDzxg0660+wvHmRVvY5s77A+U3Nf/zDn8KJAb6X40SP\\n3aNjZHeBx89vcPfuIw6P53RWF5FFAc4TnSBEiSkk/WXBs7/8T/DX/gKyO8CYPk5mSDMgmB4+y8mK\\n5dTQ8xmhMLjMIEyByjOUHhAzg9FdpB6kRU5mID2RijwOkFmBNgGhNOQl2gSUKGgbhw0WoTJiY4h0\\nQCh8kDidQVCQSzJVkhcSPZ1jZElQScARyIm5xhYZkziCNnXMbaFoQkY0aSogKzsJmV0YTFmQDwfJ\\njEV4nHRIC4U2ZDFDhCYFcRsIQdJoh1pYIhYKWwpEFMzmLUJBX3VwPjleERU6ZggZEFWC+umg8QKE\\nAuVzaCw+zFEyMlhfofUBqQogw/oaiaKNkegC0mVIL2hbR3ARlzls5dFK4qPD2RnBWYLVzG1FHTyN\\nqmkaSyNiGq3EM22TEMDFmlZKLv6XP4F1DYWMtHWFDoEYZMKijAP/+We/g/e3tvj1z79N1dTI4Oll\\nhtjM8PqELO/w27/3Cjf3az7y/IfZXFAIVzGaTdlY6bCUacpM8ZGry7x36xFFd0BZKoq8i1ImNeSU\\n4sEt+Pin/iJ/+W/9LOtnFrn+YMI7o5qrl87R65fcOwrcrNa5d3zC2uZHOHvpGS498WG2RjVHbc64\\nDlSZZmG4ymPLy+zvTSEDIS3zecL6HB6MiCiiTsINqT3ztsWKyEIuybKM1jXM7QzywFI54MHBPmvr\\nK7ggubU95vzF82T5B887P9AjhRCGFEx/Ocb4L0+D3u7Xnf8F4DdOf3wInP+6p587Pfbw9P6/f/xP\\nv7aUGAxkBSG02HlEZOFURghSRILOsTFgInTzAg9kUVDj8Z0+AkdH5FgxIaicqEpMH3CKqA0dX1Br\\nRyNaisUhgQBzT78v+O2bx4hQoF3DUZPhxgd0ujmvf3WL+axCa82krZIWOQpqM4BZg1aeppoSpKbo\\nZDjXEqPFnEpU/vm//k2cLNhqPN5XKJdMgrNSsz9veHjbkXcWEdpjvIeoqd1FfuePvoDMckQv57du\\nHCGF4c7Lb+MrQXSWopliii5ZDGwfTfGhQIkp6mjE908n5N01bGzJRTjNIKGtGx7/b76fzH4m2dzp\\ngPQK5z0xShwNeZREWab5PJlhkOSdnONRfYoMiThb4WJBVc1wTUA0AaEVeeuYTVuU9ygvmI0bKj+n\\nEB2MdBw/fIi2GVJ5mI7wBC4+/QRv/p+/zYIpabXABEHIPcI1aJ1DPUepFikdtWvplA3etTTTOfr8\\nGnL/hNCM8MMlfN3QMVly0JcZNpS0UmJVoMxK6tiiXUtQEOjjOx7TeDBdAtVp8PIocmyuwFq8MEzb\\nhsx0iehksJLn5B2FnURk0FgV8KIhJpESKnQIuiGEDqOtLaLpYJqG4GaIIpJRYpUjxJS925DGfnwU\\n+MaDBldHvPJoodF4gvAQJSoLeJchsQQr8CRisFGaoHOiihQ//TNYWlQQCRvuJS0V8nSncFQ7vvyH\\nXyUKSalq8r6gcS3f++wLfPXWIxaufDvVvT/ELz7Ghhnz737/i1y6vMlkHBBBkw+WqaYHLC4v8MaN\\nfYrekCgsFy5eIStLrA8gC7Is47Fzc+7eeo3f+bVfozvcYKvuUmaS9/amnLtyjiMzZyHL6QwusL4K\\n00ng/Jkeh91A5/CEyp2wV+Vc6x2x8NhHkdtfZTr2ICUyswiV09tcwWOppw29sgNGMVB9pk2Fi13a\\ndoKLkp4xDMnZGe/T2JwHD/apQ+DSuSGynVH/v9mUOu3E/2PgnRjj3/u645un9VWAzwJvnd7/N8Cv\\nCCH+HqkpdRV4OcbohRBjIcS3kEoGPw783De6fogBoQyxmqMxBNPio6bQSRYZhMJ7jzYps9GxwQQJ\\nmcY2JEZ4sNhW47UhzzsIoWhtS7fb59zlZ3Btxs6je/R7PY6nxwwyQVzqMh7tY4ylPH+Z6niO9zuY\\nPAejsD5ncb1HJ1/hygvr3H7rTYzWoAKZ6GLbOUIozi7lXHr6o7z6yk2UHfH8h69w4dw6DkFlHTv7\\njq++cZ3KjwhR0VNgcWRBkxNpnMNrhRCCUqV/VxBdbDNGFQrpRDJQzlu80MQgqL1nHgL0yzTLGZN2\\nO+oeNsgEs4uREAMBiQQyYZBZAOfAKbQSgEL6SCYMzkVkrMlURMs0vBQbR7dUyOBBRGQ/R3gLCxkB\\nDaTaa/QOZJ8YJOhIFlSSs+LTOXEZ6QJROrzKEUIg8Xziey4QMkEm0tbXZpDZVN4JQkIU/xd3bxaj\\nWX6e9/3+29m+vfau6n26p6enZ6aHw500NVooidosR5EQOHGcIIgTwIbhWAgSIAgcJgqcXPgi17lx\\nDCd2YluLRUeCLEuUxEUccobkrD3d00t1V3XtVd/+ne2/5OJ0nLuIARyYybkpoC4Ldd7zLs/ze6hs\\n8/8gXMWibmyhUtRY0QSrRQq80CRRRJQkpO2E2WxGbReIyuG8ARwiGEzkqWqPSWIq57HWYrRAiDZ5\\ntQAnqMuq8cGXgtqVyKiNXcwQvtGDWhvAetw8RylDOR5hMNjCYqtFQ+uaW0RRMaEiqSyidojRECss\\nQgTErEConA4Jk8UcVY1ItMG5Jg3ALUp04hHWI2SNpX4mzp9SVwlRFlHVrgHJtAYUn/lFNn/mE3T6\\ngqr2SBEIHrSvqJHIOCX4mla3zzYWbzp41eZ72xW9jYt8Z28fiKlm99i4cpEoFiTZJpHfYCoUpF3i\\ngSYIaG8kmEhRtDTtbMCCQKLaSAQtA15LKiR/8IfvoArLzLeYzKZ0BhnWJcyrKbuHj0i8wCeCbpYy\\nrTzSaJ7sD1laXWLlyiXqyQrL187z1e+fYu/9KW3l6bUjTo5HPLe5jNIxIaQktUN256RLGxycHhCq\\nmrLyJNQMsoy8ypEm4cPjEzAtFnJB8C3qRcHaUh+5qBieTf6sMvUvnx+kQ/088O8C7wohvv/sd/8F\\n8BeFEK8CAdgG/mOAEML7Qoh/BHxAoxD4a88u/AB/FfifgJTmGPV/e5BqHk9JzSDqMS1zhIlwi4CL\\nNVoaStcs1iO1hAuWUS5YbgfqskZFCcIGLAlp2+Atz5wyltRoXry1xeZAM5/kPLeyTNIa0Os0FKey\\nFOTlBoNuwmxeQlAMZxtMZzkXVhOIl2glMCskkpIXf+p16nKCTjVZFGMJFKWikynmVcGPf+YS7bYm\\nSpaYDOfoVBILxYX1mstfepUQFKvnLzOdnVHNZyzmBScTx7ysuHf3AZ+4tcVrH/8UR0cnFMWC4TjQ\\nSh2tDoQgGI1GrC1dpqymRO02VkQ82X7Mw3tTkDmRDkgUQjos/5efPJaq6VhVDEKRhwrnm+C2gHy2\\np1OIqLFyImm6Vwk6NpiqIcV7AspKhDZ4b/F1AVKikagow9qCICx1CbUJCCeJdcRCemIZsEEghEZS\\ngzKoWuKegbDr4IhSiEKEFw4vJM570DT647JCCo9BUvgarTR1NSNLO0jVQDqU0My85D/4q79GJFr8\\n7b/xk6h00DiPvEUnzRVaSVB1RaQCkdYIGbBhSk9JdCtQlZJaBKJgCEKjNITQIjiPQeO1xFoLISMI\\nMCxRSUccJE4YajtBhAgnoG0MDoF1/pnbrGw0ss+ivsGxGQBCI3WyHqklua3RwiBCY5kUOtCQUhqy\\nV+0NTtSNCcY5bAgY1bjhtCrxxJRVTRQLXOFomRbEjiRuDmHOWJSJEa45+sadlMobuokiyyQQo3WM\\nJjDLPfGGAwsmUsSmpBAF3XaGc45+2m+snVGG9wsUMYOlVX75l/489fwYqVIKb/m93/kGx0dHkO/j\\nFwLXrnC+S2EtFoXP5yStlKOnYww1UkkevvMeXQPVoEMVHONaQH+Ju0NLmlSIpEIGg64le0/3UEpT\\nCkAYUgJJL0VHGUvdCxzYD8i9QtUDWv02VfmQkKYsvGaY/eA8VPH/JHP6X8ez2m2Hf/vHPkPhBMIW\\n2DowK0rqCL753iMKWyOlxkSCJG43Nj737KuvPesbqxBkc3CQESEIkliRxI7br36G85s9ZtMSrzM6\\npuLJzimtTBJEm5WlmOF0hLcJRJrD/V3WuimDtfNs795FmAGimnH92hXKQlNXJfP5Ai9bLK90yUwg\\ntwvq2QIX9ehn4E1MmU+Q3jDJpyx3l9h7+pjrz11lOJmjlOLsZEi336OoG1BHlBiyNMKSYlTM6fAY\\nbWC5s8pwdMY0n9HSnu5ym9OjKR5I4ozFYtHoX7UiSQU3br0Mqo2JIup5ExnhQkCE0KQbhAaPh61R\\nOsGGCiUEtXMELdFWoSRUNMcXLzwKgxQByzNxfgjPdlO2IT/REOOrUBOHiFk+Icu6WCBygVrQwJFN\\nU0RCeOYMcrYpFL4ZeeM0wtcSlEfqCGyJ8AqkorCOqi7QVpBLiy9Kgvdk7RRnA2knxcuUX/3v/ilP\\n3/hfuPTKF2gXj/nr/8lfa9xhymF0ijGGiEapEILCi4b85MoCFSfUtSUymkCj1VVSIpAI6alD47N3\\nUhD5mqAk1BJ0YybxdePiIlQErZvi6z1COEpUY69thLc4VIMbdKFZI+uI0laN0SFpYxdTau/QkcGX\\nFvnMnOAkSOEJTiPdguAFlZB4LHiJCTWlNwgKbADhJFWd81//5/8ZysSsbF4FCkwckek+STslH1W0\\nl7eo8hFLS0t4WzQFztb89E/9CEni+cNvPcbPx+T5DFvOmZc5aZTRbg146TO3SLEcnRRkWcbFi0uM\\njh6yvLrBcDhEaehlbeZVwXw058nBRyzmikgPePFTL/Pud94nKEUnVjz3/Hrz3fAlJkqobE27s8of\\n/PFbPHmww3T8FCklsyInlhHSSErniQVYqWgZjaugdp6imKHCguBrkAEhAs5apIhEy3wrAAAgAElE\\nQVQRyhGCQoUKgkeEhD/69ptvhRA+8WfVqx96HmoAFrkjjQJlCDz/yiXe+e5DggNvHUIGZMNAaMZs\\nDKlSxL0ME6XUZbNzFS4Qa01dl+zuH/PFT12iF425994h7XafRw++xwsvrLHUanF29pTIGu49mrJ6\\nbpkwDhxMj0lSQZQtMX08pByesbTsaKU1+BIfpnSjNpXIiUOf4fA7zMQmDx7ucv3KRZZ6jeB8eLTD\\n6uoqdV0yOjki9gta7YRqfozRFk/EzRvLqLTLh/ceY4uc7Y/OuPXSRebVgrx0jEZjWtrBikRJQRY1\\n4GAplxkMOoxHQ4SKaXUNZ4sTtDD8k3/6Br/2a9cRts3OwQlLyy1UHZpO1Ds8jeNHeYkymkBjy6vL\\nBm0m7f9ZWDVBC+rgGhtlXJFbibbNi1hUDu09TnqU1ITa4oLAlyVlBLERjUtICLzwGKEpjW4o9QBB\\nUwqLVAItGjSjVoJQBGQUAIGyAQeoqDlYpramKguCigjFnFkV6CctKr/AqBRpJVEE7VaXX/mP/lse\\n3f82rcWMnUcfcPnyi5hWirCNCSFEDm0VUllqH4gCSBPjAqRK43yFEDHOlyiZUvoaHVST+wQo31Ct\\nfBVQssYHg8cjpCKUOVEU4XygDtUztgDE1BShsd0G2VC0vPNNsq93WFc+S0cNuMUYIzSSQO0DcaSo\\n65qqbtB/zgmUsgQlcc+KvAgNLzWvLb0kZeYitPOEKFDXFhOnmCjDC0+sEl5+/lO88MIKaZoSpTCd\\njlhfv8349IwbL7yGl458OEVnCcErfvbHDbFKSVptRmf71HbOydkJ5y9cxeYznGjz3JUNPBbrKtrX\\nriGKBXJ1hdPDOXQTqsWQ9kqfc9UaZt0QhMTM97l4zpNmLXxVMDsZ0l+KkZlkcnxMf9BlOjzg4zfO\\n8cKlPib+OONxwaWLLzCabtNvD7AuELcj5vkc4TLSDuTFlKPdEV54Hnw04fTkKbPpIZNF1bjvXI71\\nDhX10SIhbmkaIdOf/fzQF1QZQLjmSILyfPT+AdLXWB9T4xEYtNYIfPPHSzVlndON20gV4UOFsJqg\\na6yoG5dKVWEWjqfv3qEsFUdnb7Kw8M7+NkZnHOZzzncV/fN9ho+OcSLDLA5p6TWe3ptRVoHLmykn\\nO4eQtHj0/a+iI0VdOqI40HvuHIfbe5T1HYT39F+9yh/83m9yviV49/Eut25epTgLDKeC0ySgqgnV\\nxjVO82P2D07YPL+Ma53jrW8/4ude32DqJP/w736Fn/2xdUwekw3P+O79x/zF//Rv8J3f+yqb/R4h\\ns8zGQ6b5Plk/44++8gHn11ps3LhK1rvJ1rkNykVJ0q7YWO1S1zX2GYFL1AJvPAoNEry3lM6jgqS2\\nJSiJDBKMfzbOCqx04GQTpmcrUI1GWOBxtUWKiuCbkVMrhVGS+hmtykuDEIJKeHSQUNUIQ3PVD4KE\\nmhLwXjQjeZB4AwSLlgaUJI3bDdHfVpRaghEQ6maP7iuErsHGoJsU06ry/J2/+SP83V//Ph2bc/nK\\nFfbOatJkj/PP36AOOQhJ8AlCebCGspjis6jJkPKSkZ3Q1T1cqBEYKptjVNQAJ3xNIIAwxFFESYUX\\npqFd1RDweKkpgeAC6pmDzOGYWfsvwwal9DjRdP3KNfASSQS2QqHwwTVZU0oRBYH1C8qgUNIgnnW8\\nISiUMA0MRCf4Z0cVWQTmoUSERvwvUMRSN8F5aY9+u8XP/NzrmGrO/GyPx8dnHB884ou/+AtMj59g\\ndMTdD7/RTBexoD1vMTwZERlP1tlART0qOyRpddkw6wQmmHiZenEfW6/zdHuf3tKApzvfQkhNWc0R\\nccLB/gFJssRisqC7cZlLV7Yo64pQWzbiDe689z6ry2ukfc98UXByf8yg32Jp6Tx+uk/UhZ5N2H16\\nn1CUDEeWlWyZMj8k+IjDvRmdpQF5OcSoZVxV0cpquvE6z71+gcXsGif5kNnZiEob3n/3Pq2oxenw\\njD/3qc9w6/ZlvvLbv/UD1asf+pF/pdMKf+Gzn0Vri89rhGtxWhxgZcI37t4DAxJNCM2IqbUmBMnq\\nxjnwDolp/OvKQwlRlnJzNbDcTTg6muBqy9pyl6fHY4rcsT+zTOqKC/0lcu+pK0vc1vRDTZLGXBik\\n3D9VXFyx3NsdoYxgSSoOizlGRfR6PXaPRsyLwI1zPc5GQyZ1oGNSkILlpR73tg+4tJpxOMn5zAvL\\nJCrmnQeP2R/Nubw6YFh16MianVHOL//oDe5+cJ8XP36RdvsSu08e8N4HT2inipXVAf005o17x9x6\\n/hzHJwvW2m0mVc6T0xxtBK+9cpM3P3oCSvK3/tYvoZPnGA5zvA14UROcJ1/UZK2E9fUL7O1tE1AI\\nZTAioSynDXNTQKirZ52MRYUMrZrRt65rkjR7JgNyDc6OFE/AP4NZK+mwvmosnHqAUqohLwWHTlrU\\nVYFVjlQmOFdD0ERS4ZUjUppa1GhrECai286AxuM+LwvGsynj/T1a/WXqoCgnY9J2h7QVoZ1GRc3u\\nfDEviFuGyXDC9+48weFpK42KBbc/9jypalN7hRIeowxGCGSSUpWLBkkSHMqAd4Zgc5yQzV5aBCqb\\nI0iAQCwFTiqcrwHQWkMdqEKOcgoRJUjTHFNdCAQLSlbNCiBoggeEAxE9UwhYvG8AMaWAlgjUUjW2\\nVesQrsYRcEIhtULUFql0c3A0gkg0SQhFXSFc4/qpvQNbMikK/stf/Zv8N7/6V/hnv/2PEbomFZDn\\nBb3OGsfTIbYWbGxtcLA/JM4i+u0ep6Mp3TRmejSn10s4O5uztNZFGd04vcqcrNdD+JqqLDGJoHcu\\noy4U3/nGXRbB000SZNyi22lWHtiS6axiWtZsnVtDa0e/1YHimJULm1Slx0Qp+eSMXm9AZ9Nw9mjU\\nROFEnlKssXV5wFd/6/tsbRryfM7ac1scHhyQ0OblT97m4f4uS1nGtWtXqejz/W9+i+sfu4Un5o03\\nvkFveYO3336b5XjA53/8c+hMcC6J+Pl/51d/oJH//xMF9Zc+8zFqk5DJCFvNGY0mTHzCd7bvokID\\n6egkMQSJFpLCOdbXm7Ea9azLURFaR+SzIV+40SFfRNw4v8UH2w9INZg0YVGUyMrw3cd73Hz+HLcv\\ntvn290d87KVzHDzeo040sYp46+EZWVvQjpoOpJO1MCFnUSlOFgvGM0Pu5oSyBGHoJhntjuds6Hjp\\nhT6iaLHcTpCJRYQarxN2Hz1mrzKUc8PNlZy3DmqKcozSKX/pc5fYGwm+9/gYu1gwWOqz2i04HGf8\\n2KcH/O7vfMjHX7tIZVLODsaoOOXe3hHPb65yNHecVCWdOGV1qcXnP/M63/yDP+SDxw9wQjZxIK6m\\nDhKjAWdxNUSmEYZ7mmMW0hEcWO/QSKIkBe+w1iO1INQWqTVGiWcppqIBhywqojR6RrVXSKmxtqIK\\njihA8BKlPXXpUGnA5c3Oud1uI5WldAt6vQ7T0ylZ2vBG8goyA8JAbxBhVfNSZjqh1WrhVGB9fY3u\\n+hWK+RnLW+epFwV5Pmbj/A3ifoc7H+5QVJDnc9Kkj63hi1+8xXxuSeIewQqCFhidIXzAS4+zglgb\\n8kpwNtxmfe0CQUdkIqJ2UKLRQhJLifU1rqyIWg3s3FpPomKsCBgNtRBI2wCxq1AjrSFQAhK0aZgK\\n2iABiYegEEFThJyAoG2ihmblA4UrsTRc2UQqKlmgQtPBqqiFl4756QGl0GRRGydqpAtIGWEJ/PX/\\n8C/z6vMbnB1MWL/YIckNVRbodAYsZo40ksyLOa4MmFQRtzzaDpiMDkmCovQ5tqwIZLQ6muloClHG\\ndDSm182a5IWyJA+Bs9M5M+/oRTGIKSuDZabOsxiXxJkh1REOR1skTPwMLVvUeYEtSrr9jMV8yiDS\\nxKtd4kIyyicsd/uMFxMiYVjfWGZ0dELc7pH1I+aLmrgdYXzexMCohiJ1NCxQ1hPSBDMVmJUB7+4d\\noUrNpMxJ0jaf/XOfhMJzY0nzV/6r/+H/HztUiWBw7gKz+Yg4OKbTmqANoSzxrkYYRRZpyuDANy4I\\nbRTzsmhGTZ008pdIMR9PcL7k8uVX+JM/3WNy+pDVjS6yPiSlg5IpX9/5CC0FdlKxszen9jnD0zP2\\nykBsA0dHQ54bxMjIMFjr8/jhIWNXsHc8ZClpEXcz1jtjBt0O66sXiYGzImd15RXefPd7rKcpu6MF\\nOWPefmePkUi5dXmN4ayBjfzo7WXefHdMIj25jHh+pc+vv/GEuYVz/TZzVyHtjMH5LZQq+Po3P+Ll\\nl7dYhIj5UcnGcpe94RnOCh7tH1HKjMHKOs4FxnnJT37pk/zDf/D3GvODFxjlKSzgHUbEVCEQ6WbV\\nUjswicR41WTsqBQVCpRSWCtRqkaZJmZERy2KUCF0gpSO4ANKNFa+oDzeSkrr0ZFCqRTvClykqEpP\\nO+lioorCe3SnjcgEIYkoC4tzJQsbo/tXKMWMMghkZJk6sBLGowb+u3fkUCqgYkGSdXj7Qc7m5hGb\\nF2/z7bcfotMuh492KN0jglQNAyJKKKdTWsExqRf84f/6FaSUGAFxorF1oJX1KELBoizwpUOrtOGb\\nEgjC4ZxnXkOiA3MrSFopsiqaIhosRhpK2xw+rK2QUqOCR4qIVqIp8opKBLSwQIJXgUxEiLqkkA6h\\nU+Jgcc43ESquJtUJeZjRTvuougTviWLIfYmrCyohiJ1E2ymypZEq5XicsbkxZmU5otNdZlZOkPWU\\n/GQFJS2TYcHKSpvIGZ7ODnl+9XrDwdKB1YsbzN/bpr2W0YtSFqMJZV2g0oTUGcbznJQ242JMp7dF\\nv6+YTmZ0e23yYk4/kVzZuMoHe09Rcc21JKGVKEh6HJ/OiGpBaMe0laYSKWWYUxaWxCSUbso8d1hf\\nE4msga3EhslpRUsbtIyYL0qkN7TaPfaOhuSLmp6YU9VN2kRlK7JWiq0WSKs4pWI297R1zCifY4Qk\\nPg5c31ricAzjvWa19Ce//zW+9KUv8M7b3/iB69UPfUFFwunRDsorfCwbnSCOWMUNBg7dfL19hdRN\\nXAbO4ahJY93gh52nqGqQGlzMk8Mx28c7vH77Mhc2N/iTPzlhc3XOe7tnvPD8BmE4pmUCUhiK4Ljz\\ncIJOBc+d7zKdzOitaL5254yNYc4i5Ni5Zz1tYaUlwtLvdTk8rgjuiMgLhsDXv/87ZLHku1OFNG1m\\nZUS7d4mWqbi3c8yVtT5SJHz3ziGlNXzu1SUePBhxPCvZ6rV4Mqx4+ep1tp/uEFzEd944pJtkHM0M\\nV4JmdjZn7+iEy5c+Sb8uOL8ieOlKxtOxYvskxwXL1rk1vv7Nd7i2usk7j3bRkcQ5QWwMCQaLBQsu\\nVhA8aSSaDCPAyAQhJcqkVAQiaUFGuCrQiiB3jdRI+AJpI5So0TqmdhrpCgovaBkJIqCUJhERBGhH\\nEWXRSKoSpZtkzlBi5wXeNDErhS3JYoNzYIJCKkHtajQRwXuUdtTOgVBI5/He0on63Ng8z3tvfo0X\\nXrzAoqx5MJmQ9XuUWLRKiSUIAuNqThIklRC0jKasLF+4cYO0azgZnpLbFt+7u4MQnl/5pZ/lH/3j\\nf4ALAlSESjMSnVPXhutXO+zsTBEYEBVaNzto++xyP0hi0BrrJbUNdFYG1IcjolCysBKFwHiPTKBE\\nkMYZhEBeKSIZEWlPbRNK5fBVi9p6SmJqIbFlhRcpQljKukJLRZIsY4uGwlZEBUejDU6mBjpdbN0H\\nIEhLkBMSo+mspEibsTFIufj8Tb71ja/T6nbY3ztBxoZQ14yrkuHZlMvnNzg9LShCjgmBPFhWljcI\\ndkpAMi49qhqRrayxvnWOg91dBnHgcTnl8y89x9PTJ7SyFcbjCYUXnB2VXH7lEiORcnbvAK8jzk4X\\ntLstglQMugn1rMDJwGS2YNDKyDKBryOiNKZyM0LkodLEnRZFbhtSVxLQ0jAtc6T3pGlCmJe0W4q6\\nVgzaLVwOKnIsjqeMxyNEHTErFiitMEFQhH+FcJR/3U9A4GpLkiQUiwWJUtQWxrMpITgSZbChkaeL\\nZz+VEYTaNzxIHFI2ue+LYgEyMJ/VvHrpHNv7Uw6ffkS3I/jewx1e3rzIu/dzPnltg9Sd8ubdx1w7\\nt4w3Me3IcjpfcPXyOR4/fsxrV9vsPJ4xI+Xm+VXOpkNCgFLW7B4HFjjy05KTuWOl08YF+OytFzk+\\n3qGqPCurGR8+PORct8/FpTVyO+XcUsyd3GGk5/S0Yvu04Nxaj6VOxOs/scG3vrnPS5dapCbC+hpv\\nM3aPNJsrS+TljPUVxd0H+6wP4PHxHONyvI6oQgsdGYJvMoh05Pj4zS2G4wLpYOfojHa/w3A6b7Kd\\ndBNZEUUGXE4cIpI4YlTOkEBLSpI4xgWPEjGVn7Nkupg0ppgt2NrqsTsas7HcYXdvCiZBVkVzN/Ka\\nlolxPsEri61cU8hljFEKWYPTBuEV/W6L+XxOSKCjU1pxm+eubPLe3UfcuLTFnUc7+EhAiDkdT+jE\\nGTcvLZH7mivPX6OyNV/4c7e5d+8edx4+heBwBLSKSXRE7Sq01gxIyGtLJFxj3XQVcQqne8cEbXn7\\n3iFVUfDv/9Sr/JPf+HVC058SKyjzBYamQz3aGRPrgA+BWErG82Yy0rJxPjkRIZ4BxpUSnB6dYYMn\\na29gZkNyAYbAdFbQShS2so1iwAeEsFTKNIYM62mlMXVdUweHVu4ZEEVQ2NBYPPHPZGvNbrIjIXiL\\n9ZaN9jrHs5KiKEijlGsXz6GVw5iAKBy6m3D3vQ/ppS1kEORnYwbnlihry1Im6LdbbD98zGClSzFy\\ntNpd5qVj7gu6KkUbwcs3OsxnGYcnU/aePCaOW+zu7pKIBEMBVcTx/JjYSOaLgpefX2U4GZIXx2jT\\nxlDRW1ui103QQePrkkmApV6Xuig5HE3JYoVUMF14UmMwsuH42qIiMQqLJS8cOgi6LYlsJbj5CKUD\\nzguMsmin0a2Ac4a0XdCaKxZGNQoMLSAx7D4Z/sD16oe+oBICRimEtEijCXVN5UMzk8qGOYlooMVK\\nhWaP6sOzbqfBsElfE0SEEAElAi9cW+Yb35oQS0+mA7Ld5pNpCkojh2OUK5jWEVFqsMqQT0oObeBo\\nNOFHb/dpDZa5d3BC7iK+9PnL/P7X3+PV588zny5YWUoZmwVZb52W7rJ38BGDpSWuP7fK0sYKo+M9\\nnk7nbJ5TrK+mXD3X5cH2Y46HY44OAkvdhKidgYLcL7iydoVyEZHvT7naqxmPAu9Pn3I2EuioZDwe\\nIxOLt4ZOK+Lp8RS3NODy0gCdObYubnF45wBZWvb3jynHQ6YLB9T0M5gtHLeuDpjPHdc7XTYu9ZHe\\nczZecDoKrPYM73404upaCy8GpFoSZZLTYc1Gt8P+eELpM25eOc9i7tDpCmejfTIkMvZsXuk2W1iR\\nUcqYzFhS1efBwR51HghRwXAIg7Zm60IXHRIWvkkbnU1mvLiRktc1iYx4+/4+3QR+5kdf5Gvffodr\\n51aQUczZ+Ijra6ts79zn/gNL0lbM8m2snfDczQ2e7J8xrSeoqEOcSKoKQm1JWyngqCY5cfBUSqK8\\nwyjJWx/cBd9mb3qMlJI0M9zdPcDVtuHsAqWNGnG9UI32WRlk8I2m1wYG7RZFWeKERvhAajShqPmp\\nT9/in3/nHTajmAPvEYtjpk7QzWLy3HGu22Ztpc/D3SPWjGfPBwoRiK3n2lKLJ/tj2sKQLHdgYZiq\\neeOkW17h6MkOeVmAhLKY00kyLi6vsNzT3HkyJZKBV1ZqvjkTiFaClIL1pTauOmRz4yo7dg9vCmxd\\ncenGdQ4f7bI8MJzsH9AddHn6ZEorE4yP5yz1lrDV5FmooSZyMblbsNxe5fj0GBMJQFIvLPPRjMnc\\nNuwNIsbzBd1uF+tyNtZ6xHGHk7M9uksS3Vth96OHrKy2We6s8nDnIS0Ts5S28W4KaNY6CUGGxoBT\\nF6gs5rSqqFwz6TghiAToOEbaqCFTaUdsEpAVdlaCUIyKEV3TwUvPycjSG7TZn05ppR1cVTLZHSM7\\n8Q9crn7oif3eB4wy2KpGVgIvFVJWLCqLEgr3TFYlhMCHCpQkylpNBpL3SKGfRVzUFIVlMpmye+yZ\\nhZxcarCSe9tHvP/0hEk1Z62T8f5hjveeyCVEIbCUpbx0sc+P3b6C04GinPHxS5e5vtrhm9/dYWW5\\nz92TEVGiuL8zZe38eYbHJ7x//z7XL2d8+PiILoHdJw84mUzIEoWRgV6mefPdfcp0g3Pr1ygWBWez\\nGad7Zzy+d8ZatkY/cVw453nno23efDCk9jkX+qusZDGfuLXJWifh+Y1NJnbGpJhxaavL2eSMmZ8R\\n5RXffuMjKgcL5cEYstY6caoRoSIWMYN2H+diLm91iLWjCpBPZixmEiMqiBK2Lq9QjGvORkPi5RZ5\\nVbDWT6gjyfF0waeuX2B/Z8bR8JBqeMyHHx0x6F4iM4bKKZxxmMSxv3fMztEZD08nnA4X7M7mXN7c\\n4tOvXSDOJHfffwLGMjnboZcsWO9JDic537u/y4c7Z6wMFJfOb/C/feXrtJJlhAhsP9ph0OkStWKe\\nu36b51/tU87GjPfepbKG/SenXLzQ5vbzz5PPSsp8jlCSWkGcZo3we7CEjjWR1zhEgz6cWl640uXS\\n6gDhFYmIeO/xHKkNkVBNemuoG6WXDsS6cVw1JglJSxluX7vMz79+DaUFK702Hd3mZz77SbrdPs5L\\n9qsAteBzt84TUXOl16GtJC9tVbz/aJtf/POfY7som/QE5/nctS3mp1NWWhGf/WzGtbU2Vb3P1Z4l\\ndROGT3e50o8wOuZHXrzJpy6t8sVb53jp6mVOhgu2LkhWopqNSzco8jnaCFpJzGC9w3A05f6HD/FV\\nTmwL2rHm7PiA2tVMXE6rt4xRgjjto1XG9eeuEJlWE4Q5aHNYzUg6JWmsebr3mMnxlNHEsrp2jvNb\\nq/S6igvnVjDasvvRHoqavKzwpaeqpxw8eQKipHYVrXrEWldj7Jw79+7QzzTWTuiutpgWiqry6Fgj\\nZEKcGLppSlFbxpM54zywqCqKuqCSgvlkyqI6IvgCU9aUhcMXFWkCqhWxvDQAFFpIOtqQxZJ6XFHb\\nEi8tx0c7rLU7P3C9Ul/+8pf/XyuG/yqev/Pf/+0v3zh3jiAEToJxFbOyefEPRiMiLZFR0oj8RUN7\\nF94iM01kEipn8T5QBYtEcWMzY293SsuAtorOoIVRitMChjkcD89Ybiccnc2Y+xn53LK0nHE0XOAT\\nQRvBvac5x7M5Ny8vMzrN2ZnntIqU+SQnNSlGFWgCSiveezRnc7XPt+48QUnF3ukEX1acDCdsrK0S\\nacGg2+LcxS06xrJ1cZ1eK2KlbSmLM8b5guODCTtnjkubS4xGJ3S7fZJWlzffesCsmrGze8SnXrjI\\n3BYsCkGqDdo70k5KpRPyeYHREqUzKGeocs5gEDFf5AwGXebTCfsHQwqbsrm8yt7BkM2NAVmmKaYV\\nm6tdhnPHjWsXef+dO7STFY6Ox1y9doH5eMFkVnL16gpPdx4zrGKuX1jh/v4pLSVQURPycXA6Z1QE\\nXrq8QaxhbdDipZsbnA0rdk8mrK71SZKU0cmEcS042jsi27zA9qM9NnqGmzdWWRr0ONjf4yz3XFhq\\n0WoHkt46o9mMD957wIULGW0Ts3V+ne7SEmApqmNmI4fwz7ilvtVkZUnJT7z+afZ2dulKwevXN3m8\\nt8OwXmCUpKwqhvMFw3FOpiRf/NgGZ9MRn7y4xePhpDkwkdCWHlcWfOGl69R1YFiVfObmJsODJ3SX\\nW7z5/Y9IdMwXX32VG5vwwbvvQ2w5Pj6m10q4dm6ZNz58yMIFrg4GlOWID/YrChlz7/5jytLihIGg\\nmE9GXNlap5rPOHx4wNalVSbzOdujml4c09eWVgvm05KtNUWLkvuHcyaLA07OJhxPa65sdHmws8sp\\nLYTRLPV6/OQv/DT7dz4k6RkSnaGkRyvIR3MEBhMFZvMZKyuXm4+RHTGeTwi2YQHgJK24iwownRXE\\niaPV7eJjSTGv2HlyzFkxpihTaptzeXWJ7kqCz3PW1lYJNmDimBAE89OCuD0gay9zbiWmDqBlRhSb\\nptuvA0JK2pFBUWFtY49OpUOEgIwkzgakzpjNZsgoos4VsRJkrQSZKoSQCKFxi5oCh68cRdVgLaNU\\nsjteEOkYpWOqheXyWsKfvvdo/8tf/vL/+GfVqx/6DjU8S6qUMiImUJsYpR152SSaegTC143Wz0mS\\nKIEgsc5hqfG1xRiDCYo4EVxfX2a9n1AHOJrM2X16RqwVbRmYzyb88k9/jJbpcP7igE+/fJWJi1hq\\nt1lqKYK33Hl6gEkMnZZmVpYkKpD5HJ0tsKKikDWP98f86aNTXOl5+dIFVjqB61ttPvXCeT7/6k1e\\nvXmJV145z87uU9K2YjIZ8b9/9ats7y148viQ5UzywfaQi6s9JnlNMBEvXxnwzpMjhNF8/YNdtC7I\\nupJMJPz4J56j9oYoaBLpKcqSfndArgPnVgbUWjT8R5vTXdbMbM0b7+xgOp7Z7ISFiNAmgCp556MP\\n0G3J3QcPufv4hDcfTQhWoLVl52DGredfoRSB9bU+b73xHleub7J7XBP1EnbGM6T3xMHw8//W59Gy\\nRtYCJTwraY/b5/pEpkOIJI8Pj/n21+/TjiUb/QHbTw6wuWNRLri43uHjH3uZ+dNdIhFx8+I6Dx4+\\nJo0USbrMr/z8p3ny9Awp4Xt37vHosKSSKe98uMeV65/i0ZOnbB/vc+/JIaZu8drL17l0/Trb2zOk\\nEHgVEFhUfoxRFS9d2sJHMWsr66RqwKC7ThonlFNLpgR1lfP44Rmjo5yvvfch1laIYHB+TGwtS2mX\\nwcoqJ+Ocvo5JtaCIB5giZ6Wb8G/+4ueIim2+8vX3Uat9cH0muSPSCTuPHzLIevSTFtdurHBWSX7i\\npef48atbqDhpDl9akMURlprt3afcvHWVudJ87bsP2T4s+PjVDXaHJbdfeSIZnJEAAAuSSURBVIU4\\n7vHZl68ja8/bH40xIuHHfuoLfO7VG9R1zXvbZ3SDxo7nhLqirCyRhygzRCHByYrCa4TokK32OJtN\\niJM+WkYsxk9xxZSqUGRRB+trssgQaokROYWtWRQ1lY0ICsKsxE5KNi+sgkmp/RxtYfv0mKf3h2AC\\nVXAMZyOkhG4UkcYJqatZNhXKtdBxH6MKBCC9RCpLz0SNO8zElLaEUHF8NmfuYkRdo6WhcjleGerS\\nYoVj7iyjGvJJQeUDpWjuKhmKpV6fOI1ot1Kkhnw6oXKWOjTHxE76g29Gf+gLKiEAEq8LTG+A8gXS\\nCUqXN+De0PAcbbAEAc5ZvAr40mNIQGmM0gjpUCIhlwkHswrpBXGoWFtuU9gSQcEnLmUEscCWR9x/\\ndIQdVTifM7IVue8yOR0jVcpq1izB7z7JIYJMR2TtDt3ugJbWbK1t8IlL6xyUjrkT/PH3n2KC4tvv\\nvIfihNGiYm/7hCyT3Plol/v7c147f55BTxLFsD2sOH9pjUolXFlfo5V4joczbp4fkKg2n7iwyuSk\\noCXb3Ljc5XRywnx+SuEERVDEJmFeVdQLyf7hAaky2FqQ6oTNwUX29mZ0pWcxgfG0QlETTIfLF/us\\ndbqcHucsr29y6/w6r15Kef/eNk9PzsgSxeF4gityJuMTRrnj3Ts7rC1X3Lm7x6duv8YwX/D1j57w\\n5E/fRGUdvvloH4/i3Ceu0DvX5198+zvM8wnG1dy6tcXcat6+95gfee0V3t/b5bXr58liyd1Huxij\\nuHVzkyJu8elbt/nq197iweEBf+83vkflSk7nks/cvkAvPePf+5nP8ctf+gxf+We/zb3dMVfPv4iW\\nko0rGd+784TvfucjpJhjogRhQQfDwfiMk+Mxb7xzh48OT58hBmt+7rUVXl7p8/prV/nLP/86k6CI\\n0xX+wr/xaUY+xqomQqbWKWOluHmlz4W1NWI5Y1jm3H30gGsXN2lvXORCr03PlaxcfJFLVy7zhZdv\\n8v2H21xeusRzF68xKlJWl2PWUsMH93ZBSbKWYTw8ZGNpicgLnh+sga2ZhpRLF5Z588EjzkrNjAb2\\n8a33nmBdyTfe+pBv3z/gn3/3Q37/wwPWL5/nZHbG3/+ffx8XL7Cl5eXnzjOfCeowRQpPWY6JdE6x\\nyJkWE4qqRqPQnYwi96z2V1jMHGHRuNAqF5E7RxUkthZEZOT1gvm4orYFWlVk2mOLEp5J40pn6aQJ\\nRiZ88rMvUhwPWdtsE0KLKAtcv32NdteQ9mHQaxGwSK04W5zhRqOGe1AEIhNomZhaWaQBaUHpQFEF\\n+st9UmokitI2xP3Ee4pKUAXDeJZzNplxPF9weDzHusBoXlDVgoPxiOlwzgLH4yfHBBvoZn0iJJ1I\\ns7c//oHL1Q99QRVCgqgRKGaTOcEJqlCTFwVSCOSz/HHvPVIFiqp8lnPepGoGK6l8RZwkSAnT0ZjY\\nBEQFUax5fHJKLDWzMvCtxxN+43c/ZHda8PmP3aYygsJbzrdSdDWi24rYPzqm39lk0Ndoe0qkUzaW\\n2oTK085K+ist8vyU9w9nbLYM28dPeeHCKjdevsxKd5OH2ym1VaSRot/vs7a0xrl+QKQV6xsJeVGw\\ns3tAqKCd9cltha9jEqNJTIIINSKumRQFnY6nEjFFWOb+41Pyhada5NR4ZkXBvC54fDTGSU8WBayf\\nMpodMs1nnLu4xOZghbqumVdTjPC0zDLd7hJrawllPmX3dEacpdQBzm89hw0V1eIplXd0Vtbotjss\\ntyNcCcd7J7z1zhPOL62gTcZcDDgZF1xaWqItKv7gN79Flqzy8uaAJ/tz1s5f4o/e3mHv8IAKwfbT\\nIZ+8dYvxXOGI2T8boxLJrBij8mMePT1GJF1OTnNEKNhYXefdB3vsHhwgHfzGv/gmv/k7fwzBcPnK\\nEpGb8Zd+4fNMylWy1hIXr20QZHM8AvBaU7uKz916mWleYs8WbHQi4pYmSmKuXlvj7t273Hmwy8cv\\nrbA/3ue3fvtrBFxjBZUC4+EXPvUK/azLb/zuH9IbrPErn72NIOHp/X0G8YL26hb52PHBR3cppsf8\\n6Vvf4/a1AVuDwPHB+2xdyPBlwEUZJRpvHW9/dJ+V1R4n+08R2jIp5/hQcjXxRDhuX3wBFyJwAaE0\\n7UEPHRTHiwIvICjDZm8NyjHzEPHFz73K4VPLzSvrmDBGhglGChois+GNb77JIp8gnSDSjV137+EO\\nvqpx0uHcgtZaC48jiwti2VhP40hQ2ZyiyhlNR/wf7Z1NbFRVGIafd4bpdIQWKJCGAFFISAgLg2gM\\nCyBx4Q9s0B0rWZi4MUYXJmLYsFQTXRgTF0YTNEY2aiQxLsRoXCmiFig2lfITaQUKCAU6baftfC7u\\naRwbZkrpzNxT+Z5kMmfO/ckzb+/9es+9c2cK+QJLFraTybVDxhifLNCxop2eMxe5emWIQkEUhyfZ\\nsGEDY2MjQJFbg0Oc7emnbdFiysMtlDM5hidKDJVGmJgcpThxg87OZeQ7JpKfYG8VpWKRP89fZmyk\\nxMhohny+wNjoKNeHR8gtmKA1ayxdvJBMS55CQeRzZQr3LSWbyTNeglLGuHqliGXa6L82wq3rY5Sy\\nrfzVP4SVF8OCLKXyKCUbZ7x8k8vFO//6vujvlJJ0E+hN26MKy4EraUvUwP3mRsx+MbvB/8/vfjNb\\nMdNM8X9sCnrv5JavNJB0NFY3cL+5ErNfzG5w7/pFP+R3HMeZL3hBdRzHqRPzoaDO+NmvFInZDdxv\\nrsTsF7Mb3KN+0V+UchzHmS/MhyNUx3GceYEXVMdxnDoRbUGV9JSkXkl9kvam6HFO0glJXZKOhr4O\\nSd9IOhWel1bM/1pw7pX0ZAN8PpQ0KKm7om/WPpIeDu+rT9I7ku78Sx9n57Zf0kDIr0vSzjTcwnrX\\nSPpO0u+STkp6KfSnnl8Ntyjyk9Qq6YikY5J6JL0e+lPPbga/5uY39dO9MT2ALHAaWAe0AMeAjSm5\\nnAOWT+t7E9gb2nuBN0J7Y3DNA2vDe8jW2Wc7sBnonosPcATYQvITc18DOxrkth945TbzNtUtrHcl\\nsDm024A/gkfq+dVwiyK/sK5FoZ0DfgK2xZDdDH5NzS/WI9RHgT4zO2NmJeAgsCtlp0p2AQdC+wDw\\ndEX/QTMbM7OzQB/Je6kbZvYD8PdcfCStBNrN7EdLtqCPKpapt1s1muoW/C6Y2a+hfRPoAVYRQX41\\n3KrR7L+tmdmt8DJHctBzjQiym8GvGg3xi7WgrgLOV7zup/bG1UgMOCzpF0nPh75OM7sQ2heBztBO\\ny3u2PqtCe3p/o3hR0vFwSmBqSJiqm6QHgIdIjmSiym+aG0SSn6SspC5gEPjezLqJKLsqftDE/GIt\\nqDGx1cw2ATuAFyRtr5wY/otF89mz2HyA90hO3WwCLgBvpasDkhYBnwEvm9l/vvki7fxu4xZNfmY2\\nGfaF1cA2SY9Nm55qdlX8mppfrAV1AFhT8Xp16Gs6ZjYQngeBL0iG8JfC0IDwPBhmT8t7tj4Dod1w\\nTzO7FDb0MvA+/54CScVNUo6kYH1iZp+H7ijyu51bbPkFp+vAV8AjRJJdNb9m5xdrQf0ZWC9praQW\\nYDdwqNkSkhZKaptqA08A3cFlT5htD/BlaB8CdkvKS1oLrCc5wd1oZuUThmg3JG0JVzCfrVimrkzt\\nbIFnSPJLxS2s7wOgx8zerpiUen7V3GLJT9IKSUtCuwA8DnQRQXa1/Jqe31yvrjXqAewkudJ5GtiX\\nksM6kiuBx4CTUx7AMuBb4BRwGOioWGZfcO6lTlenpzl9SjJ0GSc5v/Pc3fiQHF10h2nvEu6aa4Db\\nx8AJ4HjYiFem4RbWu5VkSHqcpBh0he0s9fxquEWRH/Ag8FvYF04Ar97tvtBkv6bm57eeOo7j1IlY\\nh/yO4zjzDi+ojuM4dcILquM4Tp3wguo4jlMnvKA6juPUCS+ojuM4dcILquM4Tp34B7d0daxbMDW6\\nAAAAAElFTkSuQmCC\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84b3c5e2d0>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Image came in sideways - it should be a portait image!\\n\",\n    \"# How you detect this depends on the platform\\n\",\n    \"# Could be a flag from the camera object\\n\",\n    \"# Could be in the EXIF data\\n\",\n    \"# ROTATED_IMAGE = \\\"https://upload.wikimedia.org/wikipedia/commons/8/87/Cell_Phone_Tower_in_Ladakh_India_with_Buddhist_Prayer_Flags.jpg\\\"\\n\",\n    \"ROTATED_IMAGE = \\\"images/cell-tower.jpg\\\"\\n\",\n    \"imgRotated = skimage.img_as_float(skimage.io.imread(ROTATED_IMAGE)).astype(np.float32)\\n\",\n    \"pyplot.figure()\\n\",\n    \"pyplot.imshow(imgRotated)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('Rotated image')\\n\",\n    \"\\n\",\n    \"# Image came in flipped or mirrored - text is backwards!\\n\",\n    \"# Again detection depends on the platform\\n\",\n    \"# This one is intended to be read by drivers in their rear-view mirror\\n\",\n    \"# MIRROR_IMAGE = \\\"https://upload.wikimedia.org/wikipedia/commons/2/27/Mirror_image_sign_to_be_read_by_drivers_who_are_backing_up_-b.JPG\\\"\\n\",\n    \"MIRROR_IMAGE = \\\"images/mirror-image.jpg\\\"\\n\",\n    \"imgMirror = skimage.img_as_float(skimage.io.imread(MIRROR_IMAGE)).astype(np.float32)\\n\",\n    \"pyplot.figure()\\n\",\n    \"pyplot.imshow(imgMirror)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('Mirror image')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"So you can see that we kind of have some problems. If we're detecting places, landmarks, or objects, a sideways cell tower is no good. If we're detecting text and doing automatic language translation, then mirrored text is no good. But hey, maybe you want to make a model that can detect English both ways. That would be awesome, but not for this tutorial!\\n\",\n    \"\\n\",\n    \"Let's transform these babies into something Caffe2 and the standard detection models we have around can detect. Also, this little trick might save you if, say for example, you really had to detect the cell tower but there's no EXIF data to be found: then you'd cycle through every rotation, and every flip, spawning many derivatives of this photo and run them all through. When the percentage of confidence of detection is high enough, Bam!, you found the orientation you needed and that sneaky cell tower.\\n\",\n    \"\\n\",\n    \"Anyway, to the example code:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"<matplotlib.text.Text at 0x7f84b21d8e90>\"\n      ]\n     },\n     \"execution_count\": 4,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\nCDgcWbpNj6an0QTodIH01zBexcHZkO0o4t6LUxqqYGOnSbPKmWUFXuDRazVI5jOCIGScO9q9Fifj\\nCkRF5Mdo5whVRJGkOGlx1qGNxWDASozVlNrQiiLWY58ogLi/RmXnoBXWVfzGf/B3/mqU/P/jf/c7\\n36wcTE0dapYsZqy015hNE1wQUS5KxpnB9xsskpxKWlwJvh/jqRApNL4HygtoxB7KWXzfMJyXSBxh\\n1CDLHdI6Tk7OiTxJ1A4wlcPrVGytbPDy7IgwkmghSPIc32+gKkV30OXZ0wNkUaBdidUVW+uXMdIj\\nyxNG4xnHo4Tr17ZI5wY/cnheh0gpvDDgYjgC6Yh8hfQsfhwirKHdFGhrCNoBVVnWC95GMzobEbUa\\ndXhdYUjSKZd3b/Pjjx6jtcDlC7b8inbTELfalInk02/f4v7BiL5foVSXRVXihCOvLMpAa9AlKw3K\\ngbUeP/epN7HFGL8sGZclQilSnaLTBbd3Bjx6ecAsc3Q6bUrnE1hDs6eokjNWmwGXLrV5+slzknRO\\nkWd0gxa5F9GNCz78yQFBqPjGp+8yyjQPHj7l2sYqn7p6iccvT3FSokSA0QX7T5/jhGF3o0eWaR4/\\nfsidt6/TbEV8fHDBtV6TMtGcLlK+dPcuVVBxOtIYo3EC8mTGnTvXkK6B8br8yhduMx8PcaHFZJJ7\\nDz7m/tMTfM+SVQVPD6ZcW9/gfDohTSrCuPbYjcWcaVEhJLw4HrO5tkqZCdbWN5jNCw5GU5zOubc3\\n5I1b13h2OOSNlQYb27f4wz/4c3ZXA6wNqKzDb/Q5G2dcWx1w/5NTXJ7hC0m7FWGNx9pmn1mS4aoh\\nXtSg1VpnkUxZXe1xPi6wNqPb9ek0V2hGXUbTCQudII3jysYmMnYcni+4fmuDJ3vP8URtCHh+MaHR\\nUBht8DxJmeboSnB2NqTR2SLRBYFnmGclrbZAFyVB1AKjES7CyIpea0C6qFeilOfY2e5QuhyFptPo\\nMRjEUGkKG3F+5rPS7XDjrW1cYTicTthsBDy8SOkGBe1+B+kWFLlk0PJRMqbIztne7hJ5azzef86V\\n9RaqrLi62uUvX56yvd7h5tXrfLR3wL1HF+xPC6RTrLVbvBzVrQ9lPUQgELpknFv8sDaOtpVE+ZK0\\nSHCmjvKRSqCNxQr3qrS3+MJjpe0ReSGVqTeGnNFIGfLrf+unm/L/zJ+eLm+njH8AAADYSURBVFmy\\nZMm/LvzM91CXLFmy5F8XloK6ZMmSJa+JpaAuWbJkyWtiKahLlixZ8ppYCuqSJUuWvCaWgrpkyZIl\\nr4mloC5ZsmTJa2IpqEuWLFnymlgK6pIlS5a8JpaCumTJkiWviaWgLlmyZMlrYimoS5YsWfKaWArq\\nkiVLlrwmloK6ZMmSJa+JpaAuWbJkyWtiKahLlixZ8ppYCuqSJUuWvCaWgrpkyZIlr4mloC5ZsmTJ\\na2IpqEuWLFnymlgK6pIlS5a8JpaCumTJkiWviaWgLlmyZMlr4v8Dlood18YVNR4AAAAASUVORK5C\\nYII=\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84b3dd8310>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Run me to flip the image back and forth\\n\",\n    \"imgMirror = np.fliplr(imgMirror)\\n\",\n    \"pyplot.figure()\\n\",\n    \"pyplot.imshow(imgMirror)\\n\",\n    \"pyplot.axis('off')\\n\",\n    \"pyplot.title('Mirror image')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"<matplotlib.text.Text at 0x7f84b224ed10>\"\n      ]\n     },\n     \"execution_count\": 5,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\nRK5CYII=\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84b2101510>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Run me to rotate the image 90 degrees\\n\",\n    \"imgRotated = np.rot90(imgRotated)\\n\",\n    \"pyplot.figure()\\n\",\n    \"pyplot.imshow(imgRotated)\\n\",\n    \"pyplot.axis('off')\\n\",\n    \"pyplot.title('Rotated image')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Sizing\\n\",\n    \"\\n\",\n    \"Part of preprocessing is resizing. For reasons we won't get into here, images in the Caffe2 pipeline should be square. Also, to help with performance, they should be resized to a standard height and width which is usually going to be smaller than your original source. In the example below we're resizing to 256 x 256 pixels, however you might notice that the `input_height` and `input_width` is set to 224 x 224 which is then used to specify the crop. This is what several image-based models are expecting. They were trained on images sized to 224 x 224 and in order for the model to properly identify the suspect images you throw at it, these should also be 224 x 224.\\n\",\n    \"\\n\",\n    \"** Make sure you double-check the input sizes for the model you're using!**\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Model's input shape is 224x224\\n\",\n      \"New image shape:(256, 256, 3)\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAAAQYAAAEICAYAAAC9P1pMAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvXn0bVtW1/eZc6219z7n/H733tdWC1W0GgldSCiJECCA\\nokISm4Ro/kAzMjRghmMgGaCJDDUQTTswpiE2iTAoRBANimKTkIgBIYPGQAWQghQQqnvd7X7NOXvv\\ntebMH3Od3/29VzyrnqnHu4Q7xzjv/s7Z/dprzeY7v3M+cXceySN5JI/kuuhrfQOP5JE8kodPHimG\\nR/JIHskHyCPF8EgeySP5AHmkGB7JI3kkHyCPFMMjeSSP5APkkWJ4JI/kkXyAPFIMr4KIyE+IyOd8\\nmM/5jSLydS+z7d8Skb/34bzeI/nVLb+qFYOI/LyI7EXkXESeEZG3i8jN/6/ndfdPcPe//2G4xQ/1\\net/i7r/xl+t6/7TSx/vz/ymPfVpEvlVE3isi90Tk+0Xkbde2f46IWH+Xx8+XvuQcny8iPyoiFyLy\\nbhH5Nx7W677W8qtaMXT5Ync/AT4Z+ETgj77G9/NIfmk5AX4I+DTgceCbgL8lIifX9nmvu59c+3zT\\ncYOI/DrgLwH/IXCTeN8/8hBf97UVd/9V+wF+Hvj8a9//M+C7r30fgf8C+H+AZ4D/Htj0bU8CfxO4\\nC9wG/ndAX3revv28fy4AB97at30R8H/2ff4h8EnXrv2pwI8CZ8C3AX8Z+LqXeY7fA3zfte8OfDnw\\ns/34rwU+BvgB4F4/39D3faw/x3PAnf73m6+d66OAf9DP878A/y3w9mvbf32/97vAjwGf8zL3+M2A\\nAfs+Fl/Vf/9XgJ/ox/994J95Be/vPvBp/e/PAd79T9j3LwFf+zLbvgT4OeBG//6bgfcDT72a132Y\\nP6/5DbymD//iBfxm4B3AH7+2/euBv0FYilPgu4A/1bf9qa4oSv98FiAvPe9Lrvcn+yIrfeE/C7wN\\nSMCX9uNGYAB+AfiKvu/vBNZXqBj+OnAD+ARgBv434KMJq/WTwJf2fZ8Afgew7c/4V4DvvHauHyCU\\n4wB8Zl8Ub+/b3gS8APwWwvv8gv795RbUi8YF+HhCWX5Bf86vIpTZ8CG8u08BDsDN/v1zgIVQ4D/X\\n393u2v7vIhTkO4D3AW8HHr+2/VuAb+zj8V7gi345rvuwfl7zG3hNHz4m6jlhDY+LKfdt0iftx1zb\\n/zOAn+t//0d9/499mfN+/kt++5L++1P9+zfwEksC/DTw2cC/1CenXNv2D3lliuE3XPv+I8BXX/v+\\nXwJ/+mXO9SnAnf73RwIV2F7b/vZriuGrgW9+yfF/l650Pti4AF8DfPu17wq8h5fxOq7td6MvtD9y\\n7bfXA7+un+Po5fzZa9uXfv2PJ8KDvwp8y7XttwjP8B3Xj3u1r/uwfh5hDPCvufspofk/l4glAZ4i\\nrOiPiMhdEbkL/J3+O8B/Tli3vyci7xKRP/xyFxCRTwX+G+C3uftz/ee3AF95PHc//0cAb+yf93if\\nWV1+4RU+1zPX/t7/Et9P+r1tReTPisgviMh9YmLfEpHU7+O2u19eO/YXr/39FuBff8kzfCbwhg/x\\nHt/Itedyd+vnf9PLHSAiG8Jz+0F3/1PXjn2/u/+ku5u7/xzhffyOlzzzX3T3d7r7OeG9/ZZrx98l\\nvKV/llCcvyzXfVjlkWLo4u7fC/zXwH/af3qeeKmf4O63+uemB1CJu5+5+1e6+0cTcfIfEpHPe+l5\\nReRp4DuBP+Du/+japl8E/uNr577l7lt3/1bC5XyTiMi1/T/yw/3MXb4S+DXA29z9BuGtQHhM7wMe\\nF5Httf0/4iXP8M0veYadu/8nL3Otl5byvpdQLnHBeN6PILyGDxARGYmxfDfw+z/Iczkvnt8//pLr\\nv+heRORTgH8b+Fbgz/xyXfdhlUeK4cXyp4FPF5Ff363Xnwe+vi9uRORNIvKb+t9fJCIf2yfzPaAR\\n4NqViEgGvoNwvb/9Jdf688C/KyJvk5CdiPxWETkl4voK/EERKSLy24FPf5We+ZRQgHdF5HHgjx03\\nuPsvAD8M/HERGUTkM4Avvnbs24EvFpHfJCJJRKaevnvzy1zrGQLnOMq3A79VRD5PRAqhpGYibHqR\\n9O3f0e/1S/v7ub79c0XkLX0sP4JQ8H/92i5/Efi9IvLRXdH9YQJoRUSm/iz/AfB7CaX85a/2dR9q\\nea1jmdfywy+NBXwDHXwDJsL1excBuv0U8Af7tq/ox18QluRrXnpe4K2EhbjgQWbiHPjIvt8XEqmw\\nu4R1/ivAad/2zwP/iAdZiW/jlWEMH3vt+/cBv+fa968D/kL/+41ENuAceCdhEZ0HWMvHEBmXM+B7\\ngD8H/A/XzvU24HuJzMxzwN86Pt8vcZ//KhHH3wX+/f7bbyPA0Hv9PJ/wMsd+dr+vy5eM5Wf17X+I\\n8DQuCU/mzxzH8to5/kS/x+eILMlj/fevB/72tf0+uT/Px72a132YP0cU/ZE8kg9JROTbgH/s7n/s\\ng+78SH7FyqNQ4pH8E0VE/gUR+RgRURH5QsLqf+drfV+P5NWV/FrfwCN56OX1wF8j8vvvBr7MXwyi\\nPpL/H8qrFkp06/JfEeSdv+Avj1Q/kkfySB4yeVUUQ8+Bv5NgtL2bANh+l7v/5If9Yo/kkTySD7u8\\nWqHEpwM/6+7vAhCRv0zEpr+kYtCSPY8DIIjAB+gqARRcQJAHv0Hs33+Q/pebIR67hOLzvrv3A38p\\nZXjcp59cnCPA7+649aPc+n3GeUQERFBV4k+5+jd+i32l36wArTVaM8wMczCzq2d5cBPxAO5Ozsp1\\nQoM7VGsfMETxX+HBKMU5RPTq10CdH2SjrD0YDRUQFST18wkx6MS/fSgfDOG1m/Kr8b52//HCHoy2\\ne39LdnWwqiAq5CGhKqj2Z+gnMuIYSXJ1UyKwriutOq02rBp4PLMdb+xIXu3PK/08xzF58J7p7+va\\nvV9dp49qgpQVUelj9GDEzcFbzA9rHp9quPnVO/fj4Fx/iTiiSiqJYczH7MW1f2HdV17EZLkaRu/z\\nC0QckQ4WHhHD/iLchWYxHpISl7cPz7v7Ux94xg+UV0sxvIkXM+TeTaS1rkREfh/w+wDSMPDkJ/4a\\ntC+i42gESVbQnLDRWbMxSIKsuMTgiMYxKoI1w5ohyyViTm4NccOtkcTJ3qgqiLerSSAiqLWrwY6P\\nISq0FtSE1pzDfmVZG62ucWdiCLH4c84Mw8Q4ZUpJpATjOLLdbRiGwjiOZM3knEki3L93xtn5Jfcv\\nLjnsV/b7A6pQNhovVwxTMG2oOLttoeRY4OaNWp37F3tE4z5FHVrGHaxmBEUQUm5kzZSUyJKhQmtO\\nXZ15XqmrcXnhuBu5COOYKZMy3siohuJpVcEyWMKbx3r3vor6eJkZ1ujbnNYsFnxKuDaaKxBjLmtD\\nWsIsYb4ybQvDtvD4G3ac3txShsQw5HjW1iinG1IubHYju5tbDKe2lWefeY67z93jhWfv89x772D7\\nuCVvAi1Ta8Nd8OboCrYu5AS4oSmTSyJlQzNoURKOuQCJ5iCakZzQ5JQTZft4YbMb2GwSw5hwT7gZ\\ny2zMF439WePybKVewuW9A+vZihFxNOI0HEuOiJNSKMfpZOLmG2/xprc+iYiwrhWzRquNw7zyvnc8\\nG/MzCSJOU0NFcQPVxFicaWOUnBnzSsoZEog0zFdaTVzsDVKi7Hb84Df99IfMnn3NwEd3/3NETpzh\\nZOsKSJ9sR2umKfXvsQCydJUojgr4NesQC1vJArU5CWg4ycMXEHdEQYl/Q2G8yERgZqQU1zgqCXdQ\\nUUoewCqHCs4BEQNPNGtIU9a14d5YV2WaRtxXhmFEMPCKToqZdWttmDXc4t8+HnizrhhBVXEapRSQ\\nROsW2EyprXVlOKDY1XnDqlfch5iAoqSi5OQs+4pUMBPWFdbVWOZGa0fPxrsltBgrA1cB11hg5pgl\\nnIpiqIaVO3o71i2iuJA1IapRDqYZdQcyhrPOK1IlLGx11lqxM7iTL2lN2Z1s8V1lHEdSydx68hbT\\nZmSaJspmIJXE5Xzg/HCgtsah7dm3Dcu5c3Z7T/Ics0KEtjYUpbVG0gQYJL16zlIETQ7SSA5ZEi5K\\nqy28AGmgQWTMWRmGjGZHk+A13tF+XqizUVfv8ynmpLljHl7B0bMUaWHlFcYykkcNBZUSqk5KhVqF\\nqkpr3pUTcQ8CKkJOGU/QqjObkZuQxoSVvr86mjTWiiijQcsrOrzYw/xg8mophvfwYursm3kZmisQ\\nHkIuYe0t3HiR1N2qStIc2lc0tObRL3NHulJAHaGSady4scHXlf1hwVsjiZAQHEfUQMCu1DlIa7H1\\neFpiYuWcaU1wMfJgIMraJH6zjGrqLmrpoQHgjebOmAfKcEmrG8QyczNWXUkpM89CbcK6VObDzFqN\\nUkpM1KLooLgaSMYcUMfFwARrUKuRmXA3WoOkCfOw/HjB2TNNG6adMO4a4hEbrGcaHsphwRalrkrS\\nsEhlFFKBkjW8A8nU1REpXZlFoKZacIPm7crtVWAYFFHFcFJREIdikBW8IsXDyzs4huNrQ6tjh4rv\\n4c77L7n77F2mG1u2t7Y89tSON77laZ5401NsthuGXY4FKZD2ict5g8nM7Dew5KxLY7sb2V807j97\\nH5sTeS24VsqoCEaWWIAqjiYjlUzi6DEIQqKtzkALZZImciq4OdMwUjSjNFqrqGYMZdgMHA4HlrpS\\nV/B6nJ+GCoBFjJaEMiYUJYlQTiduPn2L17/xKR578gSzipnRWsObco+7bJ/MZDrHWpSWHoTKoBiZ\\nfW00M7xCTo0kiosi6shY2G7AdEDTK1vqr5Zi+CHg40TkowiF8G8Cv/vld49QwHvAZz3ej3BTw5r2\\nCFFVr7n9cbRqwllREZIqijFsCtN0wnJ+YD8vWGtkkR77hQYwh4qRjr9LxIki168RHoSKkFIjZcMl\\n4S3i47jvRkoJSKAGOM0by1IRW0iSUDdSyh1fqBhOzplpM+KHA+OU2GwyWhKmhgk0N0pS/DghMETD\\nylSLuDZwAw3swxOOUUpie9oYTw2S4NUxTTRgrY1WE+tiPT7NpCQ0jFFjOphZeBE9JHGvIIpK6YH4\\nEROJF5CH0pVXhDuuTlLFE5gIKTueDNTJ20RtDS3CeoiJHIBQhqqc3T+wqDGeJhavMUOLk4YS1lcg\\nrTXeSRbKBoZVaHtnuCnoJgGnnL33Alsr5o1cEpmEWCN3tzzicg/Lag1RRcWpQMoZScT9m+NmMYbN\\n0Zxi3rnQureXkzAOBZtX2hLGDOHKI1VJuEDOGUWZUiEPI+M4UnJmMxRMCgCHwwFbGqUkxmnELUKP\\nWhtJY+ybBNAhrZEUrAmLgqmQTeP1uJA0UUZH9QgafejyqigGd68i8u8RJbgJ+B/d/Sc+2HGSNGLC\\nQFWuHsY8FEUjPIR+jQcYmAQymABV0CThvoszbTKSjHVpsLZwt+UBciZOeB5yjJ3Ds/DkV/cgHhYg\\nZ2E6yax1xGpDLJSDewsOccq41DizO80a87qgkkldGblGfJk1sdluGDcj292Iu/R7hSaVCuAVV8M1\\nFqHj4Qo7mDcUwSWDRXzsOKmsbG8WphuVtK2IDZg4F145rMJ8MNoKSsJoSBI0QdbUFfCDBX8Mo4TE\\n0UnGj0ohXGQzJ+VMZX0A1hFhnrij1NCXCTwbRiMb+OpkosAEnOQJ9k4RpySBElbWMJo1qjcSSp0X\\n5nmlYaxtxXVBBye5BeiXlPK4Mx0KB6voqiR1kjpihuqAqpAEsrTw648scnME68+dQBzz8AD2y0Ia\\ngKwEDtoARSXCz5ScYUisi9OKBebVQWh3AwPxTCqKlhQeYi5oUjQFhmQNZIIDMzllxk1hXWq/38YR\\nwTScZiuO4eqIZ8wNc8VisqApxXzIGVQjLHwF8qphDO7+3cB3f4h7BxrtMRG8o8JHYFnF+7vrCLMc\\ncQDF3KjVmXIsvqzdyrrhVsGNkgUhs5qBG63aFUIf0/sBjm8Wcb61hqaIFcUDANXSeOqxE5YV5suV\\nZalhFXwIC9wXjfYFsiyNUpTL5YAkRSUhU6JMmWy5h0wRspgZ1ReaNswNsRYLXhzL4aKSwurFylLM\\nKtIVhzaPViqbjN408o6YgJ4wa4gUllrBQcVw1fCukiEdV3HtC9Wkx9ZHlN8xHTFbA5UPVBj3RikZ\\npKF9QkYWhwhf7JKcKpZKKDwqWgQz8OSoKKC0UfC1wRjz4OTGyPZkwgSWeaGkzP7+AUkrl/s9dljY\\nn12wzueYHxgGYW0N2QkMRpaF6YagizJfVEpf/CoR1uSUEatYE3LyK+VvZhHON6FJQ1MiZSg5h7te\\nHE2hVFurSAqcBBOSZEQNs7XPoWO2o+NULkR0mNEpk0sA6K5KGgtqK6lkVKCOTp4G0qA8KNkKjMRd\\nSRZZHReN+euAJcQV7XhESolcQFOEeOgrIzk/FMzHq0VJA9WrtJf0Seh9kE0g07EH0UgVdfCrtUop\\n3cJJn8xwLT3YcDdUE5qFamHxIfAGO4YOSXGctUEWCaCyW7xhGtjdLJxqoplxeVY5HFbWxdG90+YV\\nc0E09dScUpeKZ6itUnMhkxmHQi6KiJJSB58kcXa4x77uUevqKmYehjMmxVSQFq6j5u7JaFgGSmO7\\nm3jiqS1V30M5GXni1sTd2/dJOqFuCCm8KxGqG4nwFgIp73Btx2zMjlkfwBUlhTckgnSQTROkLIjU\\nwB4kQEXveIdYQizh64qtK2SB4pEJMGVpKyKKWiGPUCXSljKBDGGxl8NKYsGZWdbG/lA5nN3h/HCP\\n6itJBdPKOCmrGiaGjJBPwytZvSGHDgDSn+eINXnCzdF8zEBFtgHCA7XmqGR0Gpg2iZwSpQzh3qO0\\nJszLyn5fOSzC4TBfgcnXJRavYU2QteJjwYBcBFEjl4KSw/tSIbsx5MwwDlADe9CkuEnP+vQU0PG8\\nJqQcnmjgPqkrBmcYBoxKe3FR6AeVh0IxhIckCEpzIubvD3LMG8SE7Ttf/d0rwcxpDk0jpg2712ND\\nC6XQ1hXEcGKwCon9fr6aEBFxRlrJzXASYpAkXlYanHGT2ewmxjFcwstd4/yisT9fOJdDpJQWA8tx\\nNosXpeGf09wYuocwjCUs9jWuQ65CFuH43sWEJhHfuwDaMIucvqpFPN9N02ab0O3ArZsbNvo6br7h\\nSV44f4aTzQl3Lg/AgNaKe2ArJWUkW4CPIjE2HgrCXUgpXeESkhI0rvgYkTGClKR74t7dCLl6YyJC\\nXVbMNFzkpIg7pjOaAgsqhDWV2mAE10zeCOkk8AlwDoeFZWnUZeX8fM/l2SXrfJ80zVhqVN9DcUgO\\nQ6VIoRikG4ALxRQ/hGsvQNIjZhLhmAB6zCI0R4l0q3VYedCBUgZObkzheYphHsi1kHCr1GoBPFrM\\nT7lGSAlgPDxYqxX2TtsMpFJIWcJ7aw2GwC6qxz0ITskJK5naU+QuQqtOrRWzawbP7Ao01xSXFm2M\\n4xRhjia81Ve0JB8OxQCAkCSsVoe/A1OQiMsN6Xnp0P3ePDINCmoJ6spSG6ZK2UQut1WDWqm10lq4\\n+jkrjjGMhc12QhSee/4erdYr8k8gT8LS5vAqSubGycSwc05OB05ORza7zNpgf1m5d3vhmXdnLtMM\\n8wFbhZQKSQSrjeaN/eGAiqLLOWXckU0DlJMWzyMNS6FMWotMxewzi16QUaoWikyQOkFpHBANDbI5\\nnUgbYZOFp29NnEynvO3jPpM7957l59/3Tp7RO7z7/jOIT2iKcEBVyDkyIZICtW8iOBnRgomSUngk\\nmGDiYUitYxD+IC0nljtSHNaszpVq3l1yx7PjJTgDpRR0MmxcyZuEmmDeWEzZqKLjQNkIqgvzuue9\\nzzzDfF45nC3M55fU/YzLwvZxh3RAxhUfGrrN7G48xn65x644tQzIAK6Jdr/ibUCt4s2ozUAGwKk4\\n2SI9u66GeGFtgm4yuxuFvCmIClMegl+RHd3A/rDHmlGmxDgXfG6gwr4brMhSBQ4hgGm8t5whWaPa\\nAuxAE4svFFOWZaXWldqMy7oHn0FqXxMG1fHVUHfWuoILdV3QWlhXyCcZ1YGhxL0GhyXjGmHsK5GH\\nQjEcgS13RyXRvAaSa4b0vLpZ8BzakQHpHGOAwAMAa40qjYNGzphm0EIxuBmpo/AixhFMzCVx8+YJ\\nF/uZ/WEGc5orgqOakSYs88K6ZlLeUMZMGRNlMzLlgc1OaHaf558/kCvcGE9oVhEEM6FV8Cq0xVnW\\nFRKkJZMKkBKp57GhUdvKYbnksCzMdabKitOCSJXCjihhfTXFgjYxSM5YCkLjo05fz5KVp9LTfOG/\\n+Jv54Z/4Xn7op/8vfma357k1lOM8V7REPj5ChRr3Q8FMKFkow0DKGpZUHakPQEU6J8TMSBL30lpn\\nUi6GmqIOrqHosMj7W1fsdWkRwugDxmfepvAkisMQ2Mrl5UxdZi4uGpd3LrH9TEZBV6ZbqY+Jgq8I\\nibFk0jCxHGYOzUgb0KlSJ8MvDGrgG4hg1igpUdVZKyQy3hZMwriM40iaMjkPiEQacV2dJBJKsOe2\\nVZRUBnSojC3RDs7e5u5FCa0ZRQQ1Q6cS7zoJq6/Mbeaw7rm4TAzDEOGyW4yZhwI+hrjNADdqa5g7\\nuQSJSzSRklJtwYm/SynkXALEJxTvQ5GVeOUSaZieIOhpw4pouNDeE2fHsCLAhcAX9CrWAu1stMPl\\nimK0tuDrgWoLCmyngrNGzHxE3gVu3DwhDwPjNLLfLyxzZWmRd1eCW7G/XKh1w1AyaRRSLozDltOT\\nLd4K7/3Fe6hOTGWDA8uysr9c0Cr4oXJYZubqUIWhrSwNlLBaZgvucDlfcFhWDutC8wopFl6UnsSy\\nDNJXVywdfRaEIsrJJrOVG/zGT/18hnNnZMtn/nO/nadufRI/+Y73cHbv2chyJLDsqEQ4cmSbCkoS\\npYyZYYoYH+vgjh3fTaett87g65kMM79iRmpnGHqNsMVXoGWaGuvsFE0dgj9moALn0OSkISEqmFe8\\nCWdnM5d3Fy7uHtAqTIOQU3iMDGAoY9qyHW9xOt2gauZ+vUcdK76fyYNSMzSN92k4EUk0PMXzts58\\npQOQzYzDYWZzWjrxKFNbIw2KqmCtP3P3bEWNMmT284wXifR5t3ZTGWJxF6GMhTSkwFj0mFGLMGNt\\nKzlngtDXaLYGBVvDwByZuhESSTd0KTy+zpmoraIijNNAKcKQQ9m6K7X+CsQYxAmaMnCsJwDvtQPS\\nPQTBpddDdG8hUoYNyYGQC4pIZr28RHyl2oqtS1hdh7Eog6RAm1OK+F4zOSVOT7bkLJRSWJaVO2eX\\nuFckx/XWQ9CizVZUEylltsMOUubWzce5des267JSSsSey1pxuWR/tqBqQKPVA7WOrOvCxbywyMzk\\nCcnG2pTZLqlWad4i3k1CSuMVQJlUkSadE+9kzZFqJJHIlKTosOHp/CSPPXWT9//jd3J6csLHf+xn\\n8Guf/mjOnr/DL6Cc3LrF82fPBtFHYiW7SgC9hHtchgDstDmtwtotzpXhMUFy5OprNZoeg9wANgP/\\naDRWkufAKCzQ/ELnh1wxAw1VI0sg/jjM7YCvjfN7M+3eil8aqwvKio6KW8Osgic2402euPkUN09O\\nsfQERXa84M9zed4i7WzhZlZZgiFjCUmGU8llA2LUQ8M8402Ckbk3DucrZVPIeeBYfKOaWGu98hpq\\nrYiHZ5sSUATJiZKDmiTaU95DogyZPEinY5cjTS84Cu60NcLZdVlYW6RkXcJDZJXuL0bKXppcedkp\\nSeBhCcqQGYbCZpPZDJnWDCzR6vKK1uRDoRjgAanmmHG4KqaB7h047i3cUo8QQswC/cYjTLAAk1wj\\nJWnugAZ+0GCZjSEJjAEQpaRhcSXc4DwkRDOqzi0fOSxOnVuAks24f++Cy8Mpm3UKtF2UIQ0wOK97\\n3dOcXZxdpadKrezXPfMB/BDAXm0r68GxtCC1UVanWEIGwz0He84Nw8hDQXMjF4EOUmqKrItVorBK\\nMm6VJo2lzjyxeTMfefJWDi9c8oM/82PM95/FqrN9x//Bv/Nlf4L23301Zz/1TnY3Huf+xbNozoDh\\nYleKQVKg4XlIPeMTCrrWCvSxd488fY08etCm+6s6Usl5EBo68f4ageabxzGpe3uq6Yp1ijQ6DxhM\\naMtKqw11wU2xWmlDYp1hmBJD2bCdTrmxfYzHbjwJyclSmFfjcmwwXtKmRr1MqKer+hpVDbbnVshM\\nDNvG5X1jf2ZYB2QPa2VbLcBK8hXxzQl+g1lwVda60iyBQXZY1UmTYkTaclAlDZk8EaFjUSxVJEOz\\nyroCFFJSWrMrULGZU1tFrNfwaFCz1zUMTT0axRXKMLLZhBEpeWAaR8ZSWJeV5gu8wlji4VAMDqk5\\nVmcohWPlorsgVkg5SC5X7n/kkijerSqdv576RPaRljKtzrQMHILCO9/ZU+eV1XboY0BxNmvBxo7K\\nN4t4DCMlY8gTOTtWG7VW1n3jmffsmcpj3NptWQ6RxhuGzNNveIrtYeLi8gw83Mnzywvm6cD+3Fja\\ngcPsiF9AyUwDpM0KA2hpeJtIm4y0gWShjDRtGAYjjTmUV2sRt7ejwlxpBiVvmPf3uJE3bO/Cj/z4\\n3yWXHZKUJ2/e5P5zl7zzu76Jr/oj38jn/tDf5k9+w9eye+yUlRYFWBjelCZKVmNzoiBRoWo1cZgb\\nJsdFHUrOBKp5AKPH19jTyioRy4vkKAryFSTTHPLs1NzQIajYKhVS6R6K0WwJd34V6uVKPXPawcAa\\n7sp6ENSU+UYjT8rNJx/jda97K69/4xt48oknWWxmHHeUtEHqwHPpDvqmc56Te8gFpH0JljJw441b\\nZFM4PdliDssKLzx/4HCxIp4YZKLqnmbBMznbH9AlskJZM5Iy6itFB0waMgocIA+KnyZSmsLrzYok\\nZ9oWmoJIIueCe2VZF3xfO0bTooamBQEOAmBsbQ2PuAU9XUioNkYmvDlLu2Sz2XLz9ITHTk/YDht2\\nUxTwpXxAZpjnwytakg+HYsDwtvRKPbqmfcBtcA/Nu1ojd/dX1VFfyVl7iqZSNINHbtia0lJirUIa\\nlYNVVhMO/RTrAAAgAElEQVTa3shp5kKDXCPDwtgLgsQ6UaiGkhCTq2o3EdDU2O8rF+cz+8uFkjdY\\nbZAHTjc7LDmtVao1Us5oNlyc1unL7nDjxo48rWx2wrQBhnA3XRRrGnl1FZSElEwZlTzkODi1wFON\\n7lpmkIq3ytO33sB6Z+W5+b1x7U50uX9+yTAVnr/9HO/7vr/Bp33Wl/DZ3/M/8Tff9cPcXxrkSI+a\\nAN4YN4Uh99Rt9R4v157Ci3N6DxskKc2DLxIZy2CVtk4QeRDVdnKNg9eErBJj7B5xungvYMtHXhWr\\ntau54NLd50g4UttKreCWKGXDdnPK6e6U7XaDVKG2lf1+w2baMU0HNk+MpFq4/b57zPtG9gBzy5CY\\nNltIzmbaMFowBJd9Zt5XfF2RMdNYMc+0tXUA1amspFQiDGk1QsAGOiakGmnUXpchaBE05yh8UwlQ\\nWwIYVA3MprUW6e7WqPNKXVbEjJSDXm4VcGc+zsUc3oWY9lKjMFDb3ZaTk0hTigjDkLGWgk36CuTh\\nUAzuUe1IAIrSi4KEsEwapX6duW+9ctAZM2iqURCTuusrRhanNSdlIbXCqo6QuZwjlXY4W3EPtkPb\\nOOgUrERRrBl4sA7datT7G5HJSHB2f8/Z2Z57dy/YbW8GqJWUaZOZdKLWiYvLC1BY6sJiCybBUNyc\\nCJutMN0oTCe9MKukzoBLrHMCAuxS6SQmMkMZe0wenkujIh6TpWgiiVHncz76oz4dubswjTdo7ZJx\\nKLQatR+bfMov/sxPcestP8iX/f4/yvd/7ZdwOCyYaDDF1cgoeQoSlBLAr6ng1qhrxYg0pbt3rkkP\\nBYaM1fByjmw/EaEf0enmUTLcFsGTQFXqArm0TkVLketf6WnpFZWCZoESFY/QcE0YFV+j0jTpQNaB\\n3fYG01Ex1Mo0bimlsN1M2Lbx2BNPYEvihf1d7P4BHSfKppDGRB6Ush1RVSxXDiPoAFSJsCZVjhW9\\n5g1NURJuqwXNWQmMIMAy0hi8j1xCoWtKpJKR7qqIeBgcEw7zniKZlCxAx1ZpawMz1K2HEkH/L4PS\\nrOKWg0hXBF+NnEMpnJxs2e2mUHjTGHPcVlKKTNMrkYdDMQBtDUBHJJpvSMcYhNbjOouSVYycnJKF\\nlCMvLKqUpIiB5KANuydYhbk5akGXXjaBQazVkUNDdKYtMTFTUjQftbcFaaVGL4ZmFc0SWQ837p1d\\nMN6+yxM3nyAzsGgjj5FWSrkEv385YFY72xJ2JwObzYhYY3fDKZtQNKuvpKy0o6XtNRGeM0kzQ0qI\\nJoZSYmG1FcszKys+GlkV1pXtOJCzklPQaDebE/AWCLg7026Hlcy7f+J7+djP+3L+5U/+TP7qj34P\\nK7BgeKvhIg9H5iggsC6VuvZS7GPlnjygq5sbTz/+BCJw79595nmPWU8tHdNIEkVeIsF4RASphjTH\\nmyIWC8ZbP8AcRVjNyCNIGqiiLJcr6j1LTWK7OSWVwma7JeVMGQfmOkfREJG2G1Oh7QrtcMHu1kS7\\n3LGv0ZPDUpRdT7sRyzBuJxgUyTM6LNjq2NyQPNLkQC5Kks40taBGWWowCIPuyM1YDpVanNaCci56\\nzCqBSOl0j1jQdV9xU+jFWkJkdupcaW1FlzWa0NDrTpSe23XUo5JVFCRnticTm+3EOI5M04gmhSTI\\nkpg7H+iVyMOhGI6cBBRaT6E1wpJCz+MDHsh1sqDzlrEFEp8TQriIksINd/MoMGmJJrAoLJuEJUH2\\nRlsrh4tKW4IjkYuSinTmX/BJ1lq7P2xIToGH2crlYc/zt1/gqcceJ6UhAMOS0LEEIFXXiB17OjUl\\n5+Zjp+SSwBvTdiaPylLnsDZpCFBPejUkx/4G8XJVE6o5cuYaWQmlIWokYNpuOUkbTqYhcuY5eAVJ\\noOQEXrmcG7c2N2mz8MyP/a/87t/5B/if3/ED3GZPaopnmIpSsnaAMcawVQ9wLPLGvb6Bzv5TUs7c\\nunnCbrfj1s1TXnjhBe7cvUttHAEHxFIPCZ1M1G6wAs2xWpC0xHN7EK/COwDEuHlrwtbMOkRefrmo\\nEYokoYwD4zSRS8F87fiaMM8z83IgqbCZRuYm6A3Dm7PuBmxnLBdntMUpNwqkxPZkwzgO5LEgOZH3\\nmf1+hkEjPSkg6iRJ1N7/Qhw8QdYBGUZsaYxl4lgRfAQRA7w1WlWs1SjXN8GWOGervbivtcCzlt6r\\no7UHdRcqVK80DDrrVd0ixSrGtB0ZNxN5yEhKpKyQlLZAq9bZkx+6PByKAa6qB48lro72BhUJc1Bf\\n0dyZkd1lKwnGDFKMpClINUIwKN2pUf5I6h2fpjpg2ijJuLyIUtZaGxyITkZIpJMS0fSC6AhFBzYl\\nB9mo7WfmJLz72fdx8/Fb+LxQz5Wtb6nzTFtX5v2CWSzicZsYN5khF0peGbaC60L1QPHxow5IwUDE\\nMa+s7gxyctVvQYgOQDkpuo17LCpMknhiuMmpbhC7REsmuWLLgo5bVqtoE873B27snuZ97/opPukT\\nP5fP+LWfxnf97PcjKSb8NAyMuZAkU9dQUOvaGaTWeejHMvSUEYRxOzBtR6Yxs9s8xnY3kYfEM88+\\nj9VoIiM8KJF3N6Q6bQXWhMwgSaipkZJe0ZXFlTIIj29PyHLKejAu7p1z54ULbt+5JE1C2WROdqeo\\nJpalsiwLh8OB/X7PYb7AbaFkZRZj2G645QNyIaxnsUj9YDRzRqLaEWDcDKRBAtfJyrxfosNXLfT6\\n1rC+vZxlkh2sFg1zSkYl0t+a8hWIbubs93vu3L7oTXWcUgrVenu6NZFbJ/CtFVsNzKLJkLdeLFjj\\n/WdB+1xwMRJKzontSaYMAXzmkoITocpCp07br0AeA3i4spLDbRcglQgHPGI7upbVUsnuFFGGUci5\\nBYc9tV4klAhYUZGxUJuyrhXNQQpqszHPM3mT2G5uBUusjMzzzPn9PS+8cI/LQwuwxhNoQ5IznI5k\\nEpqC4rwc4M792zxz5308/djjFBKXy8o67zm/f4+LZUFJjNPAcCJsx0IaYMiCDitza2DBwvRmLDOQ\\nPTpIVWGt0YTlzC5IjAyDknIlleDQb3Y7RCupGW968kk+MX8u8v4VyQVapGi1ZGZrCAPSzpEm3Ls4\\nB4X3/YNv5it+19fwd77ui1lZ2Awjuymh0sAHqhjzCnNb8bwySmLuIJlScF/JJ5k3vuFJbm23lDGz\\n2Yw8LjvGIbGsK8+9/yx6YHiQd6o1gqZQaIcaqbbimGdSXoIa4A4JdjowjDve8ta3clJukGWAFe7f\\nvs9z929z4Iw3vPkjeeL1TzOM0f3q9vMvcPuFM973/vfw3N1302xhO+4Yy8RSoSYYTk544ml4jsa9\\nZ85oqlScRZ3NbsNmF4VcY3EoiWEcuDi7QD0qbpEwNDkJWCZ5QXIhM0ZYpyAW/6oGQOgeOMB2M3G5\\nP3D//MA8r8GTqTDvnZlK3c8shwVrQqJxsoPNZsM0DcHEHKJQ6lBXrC5X9Sqn4y3e8Ian2Ww2bDYT\\nOWdKKTSPkKTaGqHFK5CHRDHA0aQkAGsYCSQKb47lC4YThYdBLpEetxrW+x08AHY62B0vqiQG7f3/\\nXBCZyKeZYdhQcmE7nQDC+ckFeUg8//47rIfOkFRDDGxtWHZaDULSboiYfj/fI403MRq1OYst1NY6\\nWSXy4akEkJeidQKCslSjWpBfmoVXJO3Iae81Exit7qlTQ6uSNfVWagnzC1SVk2nD7dsvMH1Uwg4X\\n0e2qNXJOOI3D5Tm3bryeakt0lirK2eWe2hrrvRdIlzMnMnQWYg/DCNTb2JOTYyXqS9CeGRCHpqzV\\nWNdKGgrjFPTunBK3Hj/hiXsnvPD8bVojSrA1OiQd8xWaOtBsvSLTghjkHjTfQZRpO3LrxmPcvHmT\\nQUcOl0uwLTfOeS3stltwp5mxLCuHe+ec37vH5fk92jJzOV9gq5G30a+gAqYrszXW3qru/IV9EJKm\\nCSsN8cBQSiq0EhyCZcksy3rFzYheE9bDqog7j2GvSrjwqfMljt2vWm/bt9ttqDhGY14iK2NLZV1n\\n1rl1DyJ6UohlzIScJzabDSfbx0m5cbbewdvS51Zi0MIwDAzjgObo9+Di0BqrrdS2MM/7V7QcHx7F\\n0HGGaA927ItgwQDUyANbbTSNhqZm8d1y79tHlNKaBxKOKvVI0EmJhDJMGVNjKMowjZQysZkmttMO\\nPFOGAU8xad/7rucQd5JmSD2tZrCuzjRmNpuJaVcwP7DUM8Zpoq2V6r3VVouibyFiZplK9BAk3PP5\\nsMQEcaKeQhJFEq1FTCg9/deascyRz86aUE2YC23tHY7KyFQyZ8/O7Dqdtwwl8L5aKRqVjLK7yXq4\\n4Pad24ybif/759/NeOvHedPpEzx/uB8kKgksAxe0QlJQjd6ICw1agWY0r2Qy62rcv7ggTwPjIIHC\\ni5OS8ditG2y2OYBC6/wTg7pUYMR9AXPWuUV2Bu3vPPw9FMqQuHnzMU4fP6HoyPakkQTa6OzYcLqd\\nGMzIJtg8c+/2He7dvc35/ftc7s/Yr5fM+wNjOQGr1KaYNFqKjl1uhcuzmTRWdo9Vxl30AXUkMBTp\\nHkBSGqF80KDnJ3FyCsOUpFdJitKEcPmdboQikyMCkhIije224Ey0i8Zhnmm10nqHKDHIlMCKpDCW\\nE8ayYTvdYBwLQ9mxPdnizNGHUxpFnWHM5CmhBTw51oxWF6wFAP4rGGOIFl9HIlPyFgVC0sEqS9Rq\\niGa8NqgtsorNsBQuuHXOu9LLTHv8rgT4MJYAIp3MkAe22x3jMHBycgqulCGIKlbh3uk5l/s9lHTV\\nGstNoMYLTwqbzUTZJPZ1priwrAfqEmzZas66RpcdRYM6mwSxymoHlmWJBS4KhEJIRE9BmmAt2qkX\\ncQ6HA25QJCxybY3JR0yU/WHmxG7xupPHua/vCeVSo0As4uHE+eU5m2lgt9ty//5tdHGefupJ7j7/\\nPr7gUz6Pv/aD34FvJlIyJAWvQmmQDIaGRNnGVYztTXvqsnF2dsaUEnnIvbFIjcWUnZtP3eBwmIOt\\nKkarjWUOsLfWSDeGA5g6VyV6Jigx4ROJ6Wbh9PFtVLkuA8u6YqxomShr5ek0cna2J2nicHaOrI35\\n4j6Hec+hXoIIF/tzVDK1Ql1WmhuaC3W5BFfO78/k58/Z3thyuNxTfCAVSMWoLTwZwyJr1fkUKiVS\\nxilo5Bkja1QAr7VF78uce3YtuDciAllIruw2m6tmsef3z/BVYTWoDbwhnsnDwDRsOdndYsgbcgql\\nebK9RUrO6jOrH0jWKJsSncZ7nUZtLTpfrSuY92M/dHloFEN0ggZa+P9ypMf0zJetThbBFsNypzxX\\njy7HzWgawKBoFMWIaG/JFq6cIFGLH546OcNQMqenW05PdkCizEtQXRfhmdP3I0PUAUROv/eHMKM2\\np+SRk+0p4zY689a2sNaFwyG0tbiw1jifpkJdPer/pYcalVAKrrhHEw4MqII0xRu9jVuUgJsZS63I\\n0pCcWGtmKE8yeOGpm29hubgkpSgQSpp6nwkhlUJtlfOLhfNL5+bNm9S5cn7vjJOTDb/h0z+H7/6B\\nb4NpB3KJaGK1hqQG0jsuee+NiAcV3aOqUyQAXBOj0Wh1iRoGCYLPdFrI2yE4Kh7U4TLHWCxztEt3\\nCfTdLZB87S2JrPX+EJvc2+/DOjmbOrLundcvp7whbdhdKJd5i5nz/vPG85d3ub8/j9Bprbgq++WS\\nohO4YtowaaxUmkdasV2unD1/zvz6x8l7C1dJAl9KSTq5TaFXjSqKekE1Gv64eZRy24p7praePcuJ\\nzWbszWcjk9MsWvKRhWkcgcBNbLXoYrU65gtVwVE244YkQy+4C3JUqNMWhiNlxDr/RXoLLg/w0pzw\\nqq2j8q9AHhrFoKn1rkGpp8IIpUCN+FuicYYZ0UYta6TRBkObI+rkFDF4X8qdQtwbsUYzyADWLf4n\\nLnlIbHYTJzdOwn3OmVaN/eWB7W4LCGXsOWuJ+vrZZ6wVctqSZWA3bkij4XbJfGgsc6SbbF2ZDzOp\\npHAVk1DbjOjKPFcW82gf5imYjObQhDZDa5BVmZfoo6jpQRUi/y91bxJr7Zbed/1W9zZ7n/5rb1u3\\nrl1ll9PYLhsHEWJs2QRGMIFMIwUpDJhDmDCMGDBGIjMmSEGgCCYgIFKEEFJshThyOeVqfH1v3aq6\\nX3+avffbrO5h8Kx9btkyjm8cpKotffrO1+x9ztnnfdd61vP8/7+/CZTqMHZLMAOlgLiOmhymCM54\\nnDcE60m5kGIk9CPDyRWeyjTtGfteG54p88Gjx/zMOx/yaV5JXWiwkobL9WCzxdmCeNPoYKpDUOam\\nOgU/u37FQzM2tWXjX46W7VnAul4VkaL29xQr05R0cYiRtVaqU+2/Vo3NKiStnBdt4jljmdPEI9NR\\ncs9f7d4jJMNm0xNzZr9OfOIu+ObNd9janus4q6uwVg7LDWN3icRMLur4LBJZikDW8n3/es+bz25g\\nu9VJUQmYThASxhY9jnlHjU3s5PT7N+39SElH27EkaqmkJdENniWtjJ2lC5Zc9DxqfcBWCEF9Id72\\nLPkO08CzOeoppqRCjJoDkmzFBzUAKvQXJNMk8Y6SYZWoiwPaJE5r0q+r3Tdf5PFjszBIO4dVVqD7\\nXFVnrMqTUQNMMJW6WrKrahDBYIqoSi2ol0CZDtJq+owYR61G4SGok7AYYTjxjEPP6YMNxnj8IZJr\\nZVhXticnWKcaeaRggydXYVMy48kWyT01jdR1Q9d5pqmQ19eUxTDtVg6HibxGSm2sArr7RmnKAQiQ\\nKzEbaoa4JnqryktrVTXogiFVoXM6TlPqT8dmc8q4ecgoAw8ePOCknlJlYVotndkTJND7DZYN1qql\\n29lAKit26NnNO0br2Z5f8OK3/jH/8X/4n/B3/4e/SxpGdnkmTTtUuqDGNMmFZXXMUeUmVmAcLH50\\nOG/49OUnJPOA7XarY2Sv1t9HTx+SqkJcjfUMXa+LrBimw8x+mjmsO2KKHMqLdgxR8VGxhpwT892B\\nXXfL5mD50gvL6Z3h8V3gk/23cUEjB2JZccCv/uLP873Xf8gn00teizS5emAfV2L+jLz0lDWzHDrW\\nVCmLgZK0J+AGPvvDN1y9O3IwM0tZCQtI9YgYcsk443AuNBivwXvDydATfNfk7IYYC/v9BCZTJLJf\\nJuZiGGXk8dVDZI3EmNQ3YjX/Y9s5qhvY3x4oq6WSSGbis4+FvDMsDzNnp2eUU08eEwb1mlQKhIqI\\nYU5zE5Ed+znC7nrH3V0irhMp/UR6JfRxRLYfxT5GaSBNZqscwaOBp1ZDToXe9dSSqbaSxUGwOs0w\\nRq3BIlTvsLWhu+xxHH9MUYoYW5rzTnc87xTAoTp2h7MBGzTwJqXEOIx0LugOH6EuUBZLXCx5yUjW\\nnAOE5gcwLPNK1wdtUKVKqRYptp17M2WF6NUenqr66k3z8ZtG+LXWNA28ZQznGODuds/p06eE2wNd\\n58hTQiRhmXHe4L3DOI/3hpjBmsDYb6jzyn63o7fCT48/zc89/hq/t/+YVDJZdDErWShrJa+FvIIt\\ngSoF1+nUwjiLURYs0+FArUI/WAQHsXJydkqSiqBmDOMMYexxYvEhQHC4pRJTIN4GjDOkEnXoVFTi\\nWFIlPE88vg1cvph5XSe20iHDBm8NS1nobCClhQf2MV+6eo/v7j+jWIMx+vVSBXzAhITJgg+QO8Na\\nK5RmDiNhRY+h1OYcbWhAZ51yNq1tVHGjNOqu4+TslBACUgy5VIYiDNuOF88XYtV+l6oQhWHriR76\\nzhPnpMawotMe6wzdEJjW9LmytK7s93eEThH/mFG5Cha6wYKNBGPIIvdQndxIZHGNrFEXoZoyZfmJ\\nRbt9/rCuqo8coVTXSDTSfqkbqSRDSUIJmbwqiwFvqa5iG0bLOqNlLE3Xb7Wkst7Sef3WjcukknA0\\nIKhCCFVIFJxCPZxpiHDPMq8M3YCzgZKhJqjRsx4S615IS2KdMyUWxbOLHiWwTll9bQpRsgI/ShWV\\n3maIJev5WpRM3VmLD9rsqlVtvrUZsrpugOAYonDYTwz7PTEX8lrorY65XA0tm8IQpdL3J8R1Zpki\\nkiO2t6xxZvlsx6/+wq/zjf/lv2YMlqkIKRXyUtrCIOS5UJLDtnK2WoMzVeEqYtjtD+RcKMXTF08t\\nnrPzc7rgsdUQs35fXdAjTjVwenrKuAkcDncclg2p7Im1Uh10xmoM26uFp2nDY+m4s4Uz2bIawTst\\nx8fRIykhpTJf77g73LGmGYwlt0vGVCh5QVyvOgpbqTZTUBm9NGxdcJYYs4J6vaUaaZJ8Qy2GXMEq\\nwU2viS7Q9b0i2kW0Aen0va82crPfEdOCt47NsCHXhBuCWubFUGJsU5pG0zJqvJJ2jYZeq4Ld/hYh\\nczj0bMaBLFu6raPvtZned0reslY3Lj2yJdZ10WtuieTpJ3hhuKc/1+YZOO7ilPtsiYrF2artl6JV\\ng2n0ZKHixFC7Bi2tFes7VZI5TxHTbnpPNeBMppqeuiSKqaQYiXUllahJS0UwzquPwtiGXNfdwgaP\\nrZ4cIbnE3WFm3QvrkpimAyXXNvbinvKTalUTUPbqP8hCTVU79AVSji2aLuO9wdoRV02TxwoxFayv\\nrDmDtWzsBr91mOcLrhsoh1uyqbi8EIYNeIu1PUvK/PCb3+LJo8dMadVMixCotuPBw3d4/vEf8uVf\\n/zc4LxD7LbnsiFNEoiPGSo6GNQk1C11vKVIUmloNrngmY3Elclj2OLfBNul6TYLNuliXVBEqVVQS\\n7IPyN23yCFs2hy3zXFnqjDWOYEc2fsuXp5537EjMKxIzxQfGvsfagZxWPLDWwunZlpvyhsENDCFg\\nDweyJAIOyZXVO1zWiUQyaNe/wYMlCXYQFRDlCEVvbkvDo0mjJFWvikdbGbqBEFRC7ZrC0XqHdz2F\\nymU4pZ8C86z6AW8dOQsnzpBrxo9Qc3svRTcz6wrWm/tKr+scGTVSXV/fIiS6jUOGt9maQCodXTGk\\nvqPrPdZCyYkUC+scWZesqtwIKcr/x133Jz9+TBYG/aL1eHSfSKk7bhv3KO1HsVfQHHhiqCVTikWy\\no2s7RDVyn5+YUwXv1DvQrKj3n7MKIpklzVjrmJeZZdkxzTtyXqnF4o5HGPS5PlgO047Ts1OESowz\\n1oysi7AsMykmHVnFFozbciqyqeAK3tl7IrHEQs7aZCpZFXLqjTiOnZShmGJS51+GnDxxXVjjymB6\\nxbFVdRSWWnHeMseFsOzoGcAWvA+89d77lJKRuNBvPIf9DlMyz579kKdPHuGun/OrP/+b/P1/+r9x\\n2EdKsqRl1dFiFbw1JNsaxNlqw9O2CYyBIpaSMisrnW+jvCgYV4lLZE3a/8nLgAnaMbfOY3KmFuHs\\n7BLnHFO6w1dPvz2ns4af7h5gimEuiaEbMThKqXhn8S5ASQxDR8oL02HixG1Vf4VplG3BVNvSyk0b\\nXTfoj9ERtxENr+2GUd//Vh2a1vSshRa8o/+mPEUUAGQLpikNLeoNscYifoNxlnHbN51CRaI2djfd\\nhmVeiCFTbGVzGsgUpHbkJncOndNoPdEqsaR2HxiIcSWs7X4xUGvG+a1GBx6Nf6WAZOY5UpbEMv+E\\nEpyMzXp2q40pKEfUd0PEH+fAaL/BNDtwLRpainVkUZeaRYlE4kzLDyxoKrUOQm1r8uVaWONM6PXz\\nrHFiWffEdGBZZoIbqKLjIQ2hUQRZjCs5Za0oghJ3qFXL73y8yaVVKnpeLAacaGc6F9VKlKgx9DUd\\nBVEAgrOOzmnOQJVCjlEnLjnjxVKs43Bzy+bCE2qhryO1zJrONC988MG7PP/B95Ga2WzOSNlw+egd\\nqmQ2ZxeUHEkZcpqwTUV6+/wz/tpf+w/4h7/9j/hWMkh02o+JC45MtUqNNmKa+ak0a7WFJEjOSIkw\\nVYZhA4OjxKQKv1SZYyblhWAd/XbTUOj1HrEfjKLyttsrtsUxDAM/XR8gNjDXhLehpUN5Ld3bz9GZ\\n0NyLlTUX3n3wNvaHnmq92pZr0Qh7UTBMbb0rcVCMjkl1rGtxQfQIAQSrC7gg9/6GY6r6MeFbCUvp\\n83Qp51XJSqWvI9Y5Sl2pVSXqeQWXVHA22pF5mXEduE7YXnR0nVMrtRScQ1GD0pFiIrmKpwNjiHFh\\nKB4ILWPTqlLWFHKpzR2cSaVQsyUueiz8Io8fj4WhLYauSUXFqvhFH8cMyeOioE8QKVAgmUIlkFLC\\nmqxlf/B4B9YLzoKvGk2valWDlUJh5fZwS2Qm11ONHl9W1rgS0ww+EVPF5IUqI0ECxntKduS68MMX\\nH3N2etZYqZk57znME2VdVaFZW+BKVmBrLcJMgawQGQ3Fqkr6NdIan9pHAFU1djiqL1hjiXFhjYll\\nWpkOM3H9fT57ecGvvfdX6IeOeTqQBFKM/MF3P+Lq/JK333+Lj77zLawb8SFw8fAtwniCiQtb45Ay\\nMi8rL27vuJIO/8+/wX/6t/4L/tZ/9Z9z6BaWCv1mS86ROeroTI7K0qKuwFQTpeTmijXMdofrDGeb\\nC+JUiHGmFOHmbuGj733M07cuCb1hPBk4PztnHAdKziyysjGWp6dv8Sg5fvOtn+ddu+Fud8C7Eec9\\nrutxtoNa6TrfEsgCJWe8dTx/8QkffvivsaTKaAIpGHJNxHakWEvCG4P1uqAb73DVUiQSNlvsiaUf\\nvMITCzjrEWNY44x3PaZtVhaDDwYfKsuUm2wgcXp2St9t8RaKJAyWSk8tQbMvO9EbtGQqhc3pCEbI\\nOeJMwvWG4bzXRUis6nRiot8MpJQxBZAOa7OOUH1lHMf7HpwlqJ08T1pJr5U0wXqwrNNPqMDJ0gi4\\nzpKlKJ68NO9/qxpA9QwGcw8FUiBnwnj1Heh8PSMSsCKINxirpaMNLQK9gUNyyeRsmrJQWkhpVenp\\nGNT9WPSHnouozbUh3Uu17Kc9uKxRanEmxVm72aVgjGuinYIkEKN0ppzqfaKQLgIWY8t9VeQclBKx\\nVeDhoOkAACAASURBVCcYzitBKZdCioVqHTlODGwx5cDDrsNnixEV4IhRm3VJKy+fv+Ts8hExZ0Qs\\nn376KaUU3nv3bWrOxJhwUjlxhps3tzy6esz48C2ufM/hsNfoeFsYw0i2O81GbLbykiGlhHdBdf0e\\nrM8YBmJaSbGw2ETjcUEV8rzy5tkrfOdwnWO6nHny5InuwFSGrsPXwK9sn/BlrpinCTGe0PdY48gY\\nrA10nVK7jmpJ27RhkoXT7Ybe9wxidFySZqpTCJByMlWf0Q2GySYF5YhmkrpOWmNVqzrrDYiG+1rn\\nVOZsLBav0yrTUWJln/aYWQidp+9OCM7jbaBzQjWVuKyUmljKqk3klMixkEukmojvHLkmQuf1mq1a\\n0aZSCb5jjQnnQKrDWc+49Qp1yQlQfFwuCWOKBi9VS66OVAo5wbzoWPyLPH5sFgbskQh0pP4IHGPE\\nACG3cZHmK2BbWXh8ftXQW0SweMVrO1Q1WCrD6AijmlOca51odMcWWRsLHE1ICo7NuGFKiwamlIJy\\ngCsY7Rjr0WFCciY1d2iVpovHQbHY5lY2RUEzRkSPPrGomAUQ3yYtLZ5dE61VO1GtysSdEWJqlUbS\\nWLfFriCWh9sLyusdpak7KeA6VTvudhOPHr1DlYjxHZeXI9Z6lv3EvBzYbkeWaWK/u+PhxSXzfsfw\\n4hU2Fs5PTrieZqx3iKmE6qkFYlR4bIkavdSPHdtziHGHDxUkUE0k11n1Gk3tV0U5htN+0mh5p0yM\\nsdvig6NHOD0755eu3uGrZ0/ZLddMpbDpThU9h+BD0Jj3osuNbeBdaxx4y9gPpGXlZDxlkoWaVgyV\\nxKLuXNM2COtIpWKtJ9nIMPa40YLXn4kLQX/uIuQlK2W6io5nrVXQah8QZ1jjjlIq1g9Mt5Vtn/F+\\nIPSu3fAZK5YlA1ZjDTDhnggmFmxX8cniB/08kg0ZQ7aZkgwxRVXuGj1mBx/wvW2Zp0Wrh9a0zw0g\\nW6IgSchtGGH9T2TFoCVqqaUlVjdlnYiCKZrCzBylyc62BqXVvqSV5lpEq4GiDstcE6kY+uApHZxs\\nLmgqVGUKGqdS5RpxRiEt3muuQT94SgqkOZGNmroamwTJGuVWJVMopJo5OgZrVZ1BEfXNaw5lcycm\\ni61HErCO+kzR2LO21oE0751p3EeBYirGaHq3kZ6yGpa64K3BI0zzHaVWKlkdnAguDPTjCXf7W84u\\nzun6DSF49tOeB1cPOOUBNzfPsSawu5vZ+pF17JhunnF5MuKlcCiqzksmYehIOd2TlzCFy3FDDZXz\\ni0AqjtwW5LyqFmVdF6QqjizHhSqRUgySM1kgxgOn3R1+M/BoY/nlB1/lw9Mz5mlPWZPuzk6pUaHf\\nakNadKxopSDGNLmv4Lyjs445zW3EbRh6MMmyZEehYqshi0Fqppbmguwtw1XAj4beK40rNKepjigT\\nHQODH3BeN45x7PF9p/wQ2Wr/yAXSKhwOK2HcMLixJZ95co0KAeq85n5a8KWHKhSfKMGSxWKro7SN\\notZKSo64LpQcMNVon6XX68nZXmEsRs161ipYxwm4YjGlUmIh5oRxhs7+BCZRHccQxlr1vUtzF5pj\\nVqIBGqLcHp+gv4uYe4XD0WqtAinBojdo9gLOErxnu+3w3rMsKylpp9aIItmcU3WltRbfO/rcQRFk\\nbZOFWu8x6rVWqi+tktFFqlaFp9I0FCUrrBNjsFW5jqZUTUquIJKxXaMNu4yxuitVq2EvyoLMQNb+\\nSAjaWc+GZV2xAl21TDgd26Kz/1pVfhxKoe9P2IwXiPMcpj0GePnqBU+evsVud8fV6ZZ1yqw5IQSm\\n/cxvfvXr/J8ffYPpzGFqIpaFzcUZ35pfYIOBIeNr5eJiS7aJy/ORpRSyFXbzjs4Ja95hCtiiR8OY\\nEqVqDqe1nr7qTpgOM25N/JUPvs5Xz6+Qw0xswcDBBfW5OK/+j5yVsIXch8faY2q31fyRuVTW0dC7\\nDaY0LUKyTWTF/WTLOs2WCKNlczrQbaw2IL25F5LFFAne48Wy9R39EAiuV2CQM6SUCEGZGkaKGuSW\\nzDItDNtBfTLW0Pc9OWWcyxino+fgDSWpyxMjeO+0IsyZXBO5CjVFalTTXk6F6MD0GiXgnFo69Hik\\nlZMe81qSFRahR2QHrtKP/Re6JX88FgaO48cjibgl9zblGGhXX/+filHENE6eFWpxiClqvDIaW2aw\\n90rHfhwIQyCEDucCfR/YbgZyLkzzwrqsWOPoul5ly7WyHTeYurTYu4UahZIjMVWOaEJXRZFwoqNQ\\nMXqWxWgASC2BLPrDqs0YZYpgklBrwnUdxtf7YFs/OggQOj1G+VAxLuGt1bTiRnTGCbZWnlw8wMRK\\nP2xYZ1imCQUICkU8w/aEzfacWNU6fXp5wbpGPvmDb7N/85qTqws+/fRjzs9PuL5bcX3g6YMH/Oq/\\n++9x8z++4oNFAR+Pr6548+oVJ4tnc3bCb3/jmzz68jv8YP+cn/3gPR4/6ui2j/ne9cf0O888V9K6\\nkneVRXr6UPSm9uqMzbnw4PQRL19+xjYbLvLKX3/755jjjvUwM2xOKAC+IwxbpR2tmXEI1KI3hDRu\\nggG6oMeEzfYEe9rxlQ++ys184DrtGec79jER4y2RiBdaj8ASRuHsYoMfhLAx2BEkCMnBvEQ2oaPD\\nc9KNXHRbum5Uk1mp5JIoxhDXBREVhIUacAdL7gL5PCGm0BmP6xwnJycqFCui42lpgbSlqnq2aIWV\\ncqZGQ5wMyy6TF0MtK5vNBnpD12vzVI7J8EY3oxwLcU3Mh8R6SBx2iWUymK4Suh63+WL344/RwiDN\\nutOETPfyaFQe3SjCmkXV0qqOi0kbb6od1ty/nkJQNCzG+6BjKjhahHQx8IFM1ig40dJU7coK8TTW\\ntoxIvSKPcE7dtaAkAVuwFq0cGmnaOo8r6My8VAq6kCmVqlmTO6chqa5CADuILhROdy1sbT6PihSH\\nqR5bdJdxxtP7HhtCi6mz999zP245Pb/Ahg3TGvGhZwgdN3c3nIxbHlw95u76Fc+ePWN/d0epkYvT\\nrTZgbWXcnPCXHj7m47sd4zjy3tkF111PAnaS+fqXfooQAg9PRmwqPDl/h2gOXJyekaPB1h1ZRm7L\\nTLYVYqaKcDJ23GVt9Ka4crHp+NLlFf/OL/0KOa6ktCBOg1qD75XGZfXsPA4dzumZXdBxtZEjJSmr\\na9EN+C4g/cim88TbzG5ZCL7Hl04X9KSj4ppU82H8MZC3hc8em3pGowO6MKirtwo5J3yoTVELQqN2\\nl6yU7JrpQ8c8z5zEjPWqK7BBwbTb7YYlC3nVMbYzDms8JR+b6xaD1z5ErqScqLVHTEd1sBla8lhT\\n4lpFXukxMtM8OVpFY6xWRCeKmfPhJ1T5KEe6pvl8UdDjmNp+Sz1SifRMJe04oU+rzftg719LU630\\nwpkOK+dXJxiO53gd7lirpaV3gWD7dnxRmbJt57WiiinVVpjjolCBqpOHFkJ6JAAbYwhWS+BU83EJ\\nuqdN0b5+5wP4jPMW11eMr5rObAzGW6wt0FW94aMmSpcVEItkYXNywluX70BsBG3nm75DcWSh6zDO\\nsh23nI5bPv7kI0Qqy+2Oq4sLdnevyYeZq0ePcQiuC0zzRC1X4AJvP3lMXAunp+dsRRi3Z3zPvOLJ\\n5RUX1WKHLa/WA5enI2f9OTluMWPEbUd2q8d0W7oHI+72jiUJm77H0RHnjKPn8bjl/Xce8m//xV/m\\nL771PrcvX4FR10JpHoXejaSc6bqusSPBe0dJEc2i0BI8pkRj/BM2G/qTERMNne/xYdQ+T2Pt6vWi\\nF4FzDttZjDck0f6WadoHsmB6j7NHj05SJWuB6gTjCiWuCgdKkWoqyzJRqByWCX82Mm57Ojym08+1\\n3Wxx2VOyZV7vcL7DOZ04GeReIm9QCLAPlmpUVGU99IP6LrQnlXXUHbwK5qp6NUo25NSqkprACP3o\\nGLZf7Fb/cy0MxpiPgR3qAs8i8svGmCvg7wMfAB8Df0NErv+019Ed2hxfFCr3Ewdpai9rW+/BfK6S\\n/FEkthzlku21dKHRI0lKMB8WleRWJSDZKnjvWcW0TAndiY3Rw4sVdxSaIaJd9WpURKLQUns/QtVw\\nloIJhs4ruNPhEYu6CV37tlqmo7UK/zC26JnTFroe5doZA74gXpuZ1hrSqk68XIQSVYV5dnLC46tH\\nVGsw1tN1ulOsayKlO2o1XPkHOBt5eTjw1ltPEYRPPv4EdwM+BHznefTkbfIyMQ6WdTmw2WwweEJ3\\nysX4hiF4ZJnIOfGlx4+wQ+Bh17Ppt3x284aHDx9xG1euzrbMr67xm3PmeInNibvbZ/zU5QWHJXIx\\njEiE17uJTbfhKw8f8Ru/9Mv8hcdvYfOEdB4awSiEgTBu6ceNsgekdZCkUlKi5oyYDAJxUYCKWKWF\\nZ4Fxc4KxMAyq09Cs0g5KxFQdnUrVPpAYzR/RnBFt2NGAtcllEol+DNSaibESWvc610gpiZhvSTWS\\n1kTMld28J3Q9m7tLsFsqXqMpjGfYbAhdaU3OcH99qWtMRVBGDEtUb02/6ShJ8M7gAwgR53ptMksb\\naZgmOKtCTollTcRYiTEiUhUO24Pvvti9/a+iYvh1EXn1I3/+O8A/FJH/0hjzd9qf/7M/7QXMj3xU\\n2s2prDxAaktDqvfHBE32bfRhnei1JKXjjdp2+Ga4siZw2B+YDkuDkcK276jGqPTYqRxZlYafNxKx\\nyjXITj0DtRxfVxcVi1FCzlFUUbUp5rzu6tVWxBscjprBJKVf2yCNOl1Vf+EF2xldyFxtenlUmyAZ\\nZ0ekqLqurInz84EnVxdYySSToHisTdRiKMtKrpnvv3rJZ59+h8vLhzx48pRpPnByfkp/suH27kDO\\nK845Hj19m3/6W/83v/BzP4U9PSXnSL39DOss26FXVaezyFr48OljUinY04ekPHO+eRexjncuzrF4\\nyunP8undczgJ7A4zX7uo7A4HvvrkIUMYmFPmk1eveXp5ytfff5cPHzwiSGHKK7YKpY1iq3FUDHFN\\nDF3AA94KUotCT7wlxlZh0hp4GDY2sLrA6D2lD7jQ48OAsVqyO+vJojJvqujuKxlnh/u+UU3tKCiG\\nKWYGBBcXzRUxnpz31JyZ4sKSFg5JISt5VXn4WgvTunJ785qhNwS/pa4FmiFKm5uK0TseW6V9PSVl\\nUlI1LwjOB7w7PkcwTo+X9hjI3LQxNVdSbDJo1HfhnPYdujHgg24EX+Tx/8dR4t8Hfq19/N8C/4h/\\nwcIg8EcETJqPqAcGY2wDuPzR59hmXAE9ekhtFYQ194Yr2nFBxHDYJ97czBSEvC1NFKMx885pKrD2\\nAI6hpdJ8DmpVDt7gSgFJCt70+kYrHKZQa9LxZz4uTgqCSangMNRgsFnn4tYaqgcXDGJpsFil/Ig3\\nrXIAh6MIGNNTq6emzMXpOe+99YCnjx6y7QKDMyzOsEhutmXlH/gwMnSeuMz88Ht/yMnlAzabkdOT\\nU14/e0FOK3Ha8dEffItf/PovkG5f8f47b/PZZz/kwdUltWa6ccthmknVMK+R98/OWNZIHzaktQdv\\nub25Y2ssve/oTs4Zy0ofPC/NLe9cPeGff/NbfOnkks1wwq4k3jx5h/ffe8ovfekvcBEcaTpQsjBP\\nM5txA9UwbjaUapR27DvIBec8VTK1RGrNbVFAz9ilKGwVp/mfqRG72o009j1rCsQIvQ9kyTirNvpc\\njuNmRdCXApSCdw5SZTKrJmKJcg+KyWRJrHViXVeqab4XMfcxciVVbm7u2G429H1P31fymqmjJqYN\\nY2AOHgTSmtq1brHGAwUpWdOyvcVbPd6G0OG9+5xv2sbrtYkBS0lNTateGx8MFU8YnAYk2y9WMvx5\\nFwYB/g9jTAH+GxH5e8ATEfms/fsz4Mmf9ERjzN8G/ja0G+O+L9B2giJNiy9aMnEcR+qNj23QTqcX\\nBFIoRp2IVpR2dJ8b2fTxH390w8XDjkcPNqSzRN/DOGzonSE2u6oRTfGRIrhqsAWc0YZPcNCfbgnB\\n03cdvqh5ZqmJVzdvqKJZEkas5lx4Q+90PCYd9G0mPy+Tqh2xWJ9xvmCtlqgWUSCpsWRTKMXAUpGY\\n2PQbPnj3Me+//S6X55c8HK54fb3nJIycnl2o1bhEvJh2RCp044BYy6Mn7/Dq1Q3f+fY3+bd+49d4\\n/skPsI8f8igEXr74lG0tfPdbv8db73zAx9/6BpfnJ4zDho3r+c53fp/Qd7x+c800HRCBnBRRp/Lg\\nK6w1rIc7nmzPePTOE9662PPx977Pe1//JeIyMYaA7bY8cCfgeh5fXbAc7siigp7OHcldgds3t5xe\\nXmAQcoqMJ+eUWUEjxnvSlFhXbVY6Z7RB6D0Prh7z7fgGGyw9Dr8xnNkNu8MJIJyMPdMyU9cbTCoU\\naxFXmdao485SqC5r7H3VG3JKhmVdWHygmKX1gVTSLKaSkoEs5OgUzZYtZOHm5WtKSiq+qo66SuMv\\nesphYV0mMArLjTHjUBhMCKHxIXVRcNiWyu7pep3QKc3LkYt2sBwBb0FcwQyGbCuud/QWjUdwR3TB\\nn/3x510Y/k0R+YEx5jHwvxtjfv9H/1FExNznnf3RR1tE/h5AtwnyJ/ULjn0CgFqkyYM/p9Qgqi7U\\ntoRr+oVjDLs2jay1RysGKRXmw8o0OLxThBrVYILXOXtpSPCi5qV7PYRRfn/fd0pq9g7vPL1TTYQV\\nR/BBUem2tkLmKNEGqHQuYDqoubKslibixVjbzpodlKS5LtVo7LtVnqAPjpoKxqvS8GTTMZ6OiI+8\\nebmy7XvOzi6Z9gekwOvr1+S0kNaF7WakP9tyOD/j9GTk5//SX2a/29NfbDnd9gQTWVNic3FGmWfe\\nvH4JkvEW+tBxe3tDSoV+HJiWhRQTh2lq6U9CFwIY4c31G2JKPLm6YJ1m1mXhyaMHxLTwcn9Lt9nS\\nDT1xWnj/K++T1pkYI6UUainYFpBijWM7jPr+Za3o8mGh6z1SYJkPqlyMUUNUjOoJjDWcPbjiev6I\\nbJWO7DtHlyybcSDmiTJVkEpwPXSZmFQ+rKxRGlfhKJURpCqlmwqTjXrMKxXrVZDmvMOkqopXaKYq\\nPQrYmNnf7nn+7CXWqs2+AhIVdhzXSIoLIhqPaFrFa63FBrX5WyPYxudwbYwOnzfWay2qfmzXa5Wk\\n5LLG5/Te3YN9avliJqovtoz8sYeI/KD9/gL4B8CvAM+NMW8BtN9f/BlfC1DVnj6a+rHFfR2bd58/\\nQTvIiCjDEVUKUo/ob7n3WBxTmhEhLpl1zixTYV0q+0VvjJSShnPkSFxXNQapNlvLNuuwxhC8I1gt\\nnUPo6LoO7zs24wbnNxjTYYweTUptQb2t4gmdMiS9d8dvUYGd1RJX1TlIDpQlUJegAa9VZ9YuWLAF\\nEyBsHN5DkhUXPGM/AvD0yVP6zZbt+SU5rToWLYm716+ZdjteXb8mUYl3e372F75GMHAXZ2yKeN9h\\nrCdXS0wzh8PEfrfj1atXOi7cnDClTNiOdN6TK9zuJyqG3W5mmheGcURE2O3uuLm9YegCBsNoAwaI\\n68Tl+RkPL89UuZhT+/6kAVgtQz/ei5ZMBetHXK2UdSWtERFhWbUZatsFr2RwS90OvJ5vmacda1zJ\\nSV/fdULXaygspjaGqDabXKN8UTN5jSqYKe2oWiq2Om10O92daU1wBefoJlBqUb6oKBRWRMeUcYm8\\nefmG65dvmHZ7dtc79tOB/e7Auq7EuDSPA/eO31pVEm9s+Tyrwn7OccxN43x0fKacFTJ7DIUWwBpc\\n8Pf6mOOY/4s8/qUXBmPM1hhzevwY+OvAN4D/Gfib7b/9TeB/+he+FuBcK5O0pdjGfI3/Lypkoi0M\\n0koK/Ss9UohIQ5wbEFpGgbl/TatrBXkRlqkQ10xcVuZpYU0r87qwxqwI+KLmH9OaW0chjWsGeO89\\nQxcYh56+7xjHjq4LrWzrsARtVIJ+3UbAa6nnfMvhNEoTplhKssQlkVdLXZvGPRUka/hpUFsfJgiF\\nBRNUDGVtx9PLx3jr2J4MYCuXVw/JpRD6EdefcLdktieX/OCTTygls7u7RSjcvHhOf7rFesu03/H6\\n+g1vDgveO842G5Zl4Xa3Z1kWqlTupluKVLquVzchlm4YiDHhhxGplSEMlCw8e/YMRPDB8+b1G4Lv\\nmCblYH75qx8qwbgkjHGUWrUvYnqCH4hroWaB6nGux3Wa9CSikx8tx9XARdOSOGMoBvzFGTd3N1zf\\nXrPf7TnsblnXfbteoQuBcdSI+JwLea2UnKlZWohx1iyIxvcw4rDaz0NywSuorU2uTDPYZU1NpP1/\\nQmuUexyBuCRurq/Z3d2xLjPT7sA0HTRKL84qqy+qVHVeewaahP35JnicstWi8NfjoiCigculJDAO\\nTABURm6dgeYnMW0690Uef56jxBPgH7SbzwP/nYj8r8aY3wb+e2PMfwR8AvyNP9OriWCNoYhgdX5J\\n4fOmorQE5VqPUwqUSioWU7SbL9T7o4fBaS6FaGQ7zT1pquFwt7LpDbZWbBIOtlBbIlIXdNV194sQ\\n9xMOa/XzhxBwrUwL3jb9fEcphXlWpL1acQu2qRgFFTXZ4MAlTHFYCUgCmz2QtcFIwneiZB9jdMwm\\niS4EXFdJ9YCxwpIOjMsJHQ7fCfvDjsN+T4yJddqxLpFu2LA9GwjDlsuTns3guTw/J8aZl3/4CV/7\\n13+R9PITUtcxLzMffuVrvPjudzGjQZzndr8n1ULoPDe3N3zl0YeYDEsWTGfZbDac9j1dH1i8526/\\nw/uON6/e8DM/+4TnL56TUyL3nTpmjaXkFWPCseNMyVWbjdmpNN0FQhhwPiDVqPCs32BL88VIaSV3\\nc91WUaiKsdjQ8fL5M974A4P3iFEHY06qMLTG4W3gZHNCnG6gZIoxuKpaABvQa0I7PerzcJnQq9el\\notXp5+I7QRItTcugIRNqk3fo1KECy5q42x9w1tLZwFpSWwxUll9pgTReCdlOXBvTGpDPF4GYs05k\\nUlKdikhLuAKqR2ppRwy9R0LoaEJh/BesGP6lFwYR+Qj4+T/h718Dv/GFXos2bmxy6CMgQwNfj+dx\\nA3KEuDQBFAVX/b3Zqjrf3HYtvETalAMtDW1rTqa1sLuzpA5cX3ESmtrQkUVhprUq3CXfq9LgOFg9\\n/tE5tYl3wTFuRuY14tZAlqRilWwwFILXBibWIFbdiTp/BiJUk7AmkFZFuuViML1QjcWLkIeCszPB\\nBlJeOMQdofRcuTMGMzAte65f3eKdZ7/scUZYlgRyIItlODvn5c01/clDNCzKEJeVb//O/8O4rBRb\\nGUzgcHPDi+sDF9Xy8MFDbt+8Jka1CovryMUw9p5udCyxsOkGnDO8fnXN5ekFz1+94OGDQEqRYRh4\\n+eIlt7sdw3aD79R3Eqc9xXTMux2pqkMwxhWk19GxUfybFDVFxVwYjKXrNgSnZXFZc3teZBwGbUcI\\nCI7vP/sMBotsOqyzRBKFtf0s1aBmrGW7PWWSN2rRs2ByURFR6DnSlnWEbUkla9BxNRhvqGIwVfGC\\nwXTgPTGrm9dLaL0ApS8F77A40lI4uANT0czVWvUIUu8nX5oy7nxQPGEBKXrNH5Ozj0a+Uh229QxS\\nqdoIre7+2HE/ypRjmPCPiHz+jI8fC+WjAZrGtKHc9Jcg93ZovRk/X6n1eBAo6LjGFNsqDbWY1qIB\\nNsdXqqgazIhQ18rtsz1xsNjekONA6C1953DOtTGXNnmKSIvN04WGVJjNXglLXW0RcMKm60nbjQI2\\nUiWlyHybMX2ijIbQgdgIBN0Nc8UUQ03SNBsVQUgi2M7SF4MtAdtX+s7hnaHrHd4X5uWaB+YJ25OB\\nh48u+PSTW3JK3Ly6Yf/ymmVeMbawzOrjH2zi7Mk5X/3Zr/BPfuv/wpTIg8vHlLuXPPraz/CD3/s9\\ntt3I888+4i//wl/l9s1LdrsDt3c3vH79nIuzx9BlShUO88I6Jy6uLolx5Waf8Nstc4oY63nx6oa3\\n3v8yt9Oel69f0oeel69ecnZ6xtnFFXFemPbXLCViuw0pC8YPGNuz5oyRTHGFYB1TXiEoScktK3k+\\n0PcdmzByt9MIu64fqQKbrufjT7/LP/sn38QNTmPhtx47QHeiEYdZMiUVUgq6ExlDLol6KIgthK5j\\nawq+c5rPkj2TzMRYKcZSWk8Cc9TNqNOxFv3YIFixWDdQSsa7wDBsGIYeKJQlNlFWwVirsNaSW4NQ\\nsXm2WI7o7VKz2r1bVGEpFVvRxU5y2yAdWSrIiiAtT1OrjVKi9rV0/vWF7skfi4UBjosDHLUJNOnq\\nvYoRuX8jRHQEWesxr+p4fBAt69pRRH9Q6M1tjosETdpaSYu+kcuSMXiMOJwrhE6dd6W0qqXRiXRt\\nKuSYSDEi43D/2Q2mdYHdfUBKXhUoS7NfV6fHClBfRTmWoaKLYsUgzTRmjNdcB+cawMZRmls05Znt\\neEUnnrhMbLdblnnl7mbHtJ8A5UVIrVSnDISTi4F/9ju/w3vvvc/rZ8+43V3jqXz80R9wefUAV0FC\\n5XA4UHKmD0rFMgb2h4XLzSV3dztOhkDJSTkAaCNuf5joz8/RTnjg5PySWmPDpmnTWKowzxNYz+6w\\n4/LhFde3e2wYsFSWZaaKMjaNOJaD9jTG7ZZSVjUMBcfddGDb6QRo7EZ86DhMOy6GK26mA7uXE8UX\\nbG/pNo7xrKOPwtn5OcVm3XVxBNdRYqFUQcm1Ris7ZzTIl4oLA2uqpKqgm5IFxLVYAYu1QX+QGk+l\\niV1OmsHvmFv5Ob6O4w0qhnVOqqHImeo8BDX+Ua3iH9oClFuDvTbFX606wqxN1KX9kib2a2CfjqDH\\nLe8wGk31hdOu/1xTiX9ljz9e5TS5c7M+3Jfu+vHnAo/jHLI2s5KpynCQVubZJnRyGGwRTThqXePj\\nRECSpayiMNcpE6Ooiiw7anKQrOLZqsUUS24xBaUFyB6/Vmct3jic6DnRADll8lLIs7BMQk46Ng81\\n8wAAIABJREFUplImZNFza6ktgFRBNH3v6HtL6CD4TncT187X1ZBFU6ZP+4G3HzzlcH2L8zrSHIau\\ncSczoQ/kooIajKHmxLS/482L13ShY5p25FR4+eoF3Tiwm/YMmxOmVclO1lic7XBuQETYbk/pQsea\\nFDq7rqv6FrJGpb1+/RrvXIOoepzrMM4xTbcMQ8cSF1LK1Jw4e/CA3c0OUtbUaITtdmTc9MrizJn5\\nsGpexZvX7F9fEw8HaoqMwbXy2OHCQEoriOHi/Ixvf/YDbufEtBMOt4n9deJwMzNPM2tc0BvJ0YWO\\nLvRcnl0S8MRUyVpTYlwEk+i90sg7H/A6jLg3tmEU0VdSwYvD4gmuI9igPSSjgizXpk+1CLYaJFtN\\nlUpVE8tSbSNZVbSpkpHWSxF9v+oxSgGc0zFkKUcGZQsCPhJfTFCRlK0YV/HBaqao/1EI8p/t8eOx\\nMNAWAmNar4HWnW2sA/PHO6pGO8VVP9aO7Y/kHSqQ+f6MdnzIUWddyv3co6RCilrepbXqKHOO5EZt\\nLjWpsi5rKCvFIUXPb9JW8oIKrrxzuDZqc/b4jTjyaomrfp6SDPd+MdvUnjRdxADdCP1g6bxrr6Gy\\n7BAcxkIqmVQU/+4wjNsNZVnorKcLHnwlFWVDnp+d0vc9YiGnxBAsN7evcd6xGbYUSdRUePbsGePp\\nKWenpyzzjpgz69pIUXNsgajCPC3kUsiS6HutKA7LgaHbaJNyHOjHjpgi+/1dYyU2AVIInF6cE4LO\\n4J0Fg3pQSsma/OUDw/acfjhhzeDcqFF684Hd9Q2vnr9k2R948eI5w7ChH0aWJXK62WCt5Xe/+buQ\\nGmuzOGw1kAwpZ5b1oGI3DH0IBOcYuw2bzRZTVa04TQlTkwJ2LHinbEWrkA89wqqbDuc0Fc1bQ+96\\nvPH6M/QqKPLO3F/TpeVeUFQqX7JBskC2SHV447AYnOjPOzcjlRHXxuUeTVBzYNz9AlKbDsfiMRhC\\nc2pev75hmWdKju0mKX/CPfSnP35sjhKfN/haA7JNGI4W25p/lBJt2kpp/sjSJm2lVa6qJjip56HN\\nytGS38qxu6zpIXHVUlFqpcMrac0X8Cp+ufdmGACrvMOYybm2SYceVSwW73SkFUKg84E1Kom4lIwc\\nFwXbQncdSgWu+rp+tITe4YJTS66vOG/oek8NGdcZMJUlCrfXdyz9xBInLErMBsP2fEPOuotW1DEo\\ntTJNC53LGISXr57z9ltv8+LlZ5x3njKvnLx7wrPvP+crX/kqz1++UkFYsx+nEnnz5jlPnn7AvExY\\nZ9nd3bI/TBgDu2bPtrbRijIgGVOFMJxQUqU7GchVdCychCE4Or+hGK+NwMNEycpRDMPIO++e4aWS\\n84SYleA6utMzcoykJbPdjIhUciqMY8eSVv7x7/42LoAP0tR+zSotjiUtyNLTeUfvV0TUqWkxlJQp\\nRphn9Oa1arn2QTUOzkOx6leoBQ2SbRCW4DxVPBKgmnKvWl1janQpDaMpqbQEat1sao445/Gua6Nw\\n1eBYC6mNwkLQ618bjwUxDheaJR/XbNd67ThnyDGyv92zzDNjv22jfnTCJz+JlGjQBl+tZBE8+kZV\\nFb9h7XE89aNPOEJi/+jrKNfBUmhjHnVIa4c2OqyoxbaKOtQkV6QmcrJUa8hLJfSBzoIMRvMTzHFR\\nUAWasQ6qxRSoayIlp55/gcGNcAKnw5br1zvSjZAXoS7omLU3bE4qxlk8qJkKEIQwqAsudILUldDB\\nuB3YnPlm9VVy8TIduGHiVf+G0z6wP+wRga53dMGz2Y70naMfHc5U4rrDDlt2+x3BW3pveP6D73FY\\nZi4ePuHidMvhzR2Xl4959eYZj55+yIsf/AGnF5d0mxNub3eUWPj040/58CsfsLsu3N3tqFS24xax\\nCW8CN9dvyDVzsT3j+9/7lLPzE3x3Quh7KoZpXhQwu+4Qd8YcJ84vzsAYHjy+YlkL035B6sS8y8Tl\\n/6XuTXptS+80r9/br7V2d7rbR4QjwmE7bFfaWemEUlIgRFVNYIAEFHwFaoJqBBIDPgUqIVRDUCmp\\nCTChVCUEEgWZSZUzy047m3A6bEd3b9zuNLtbzdsx+K9zbyQMsAGJ8J7cUJwTN/bZZ613/Zvn+T1H\\nnErEoWfUI8PwjJQr6+U5N5cvGaaetvFgDVOGn14/pl0FvDIUnTBGUY2jTlBi5dDfkDqDqplz/5Am\\nWDrT4dWB43hkKnA4KJouURVok/FOzEuHFDHWYo1B1UKDxxuPSVLlFhRWOZKRkGWrJLFrPA6yVCua\\n42ESBaKG0FpZTxqJFFBK5lO1VlQRlWTXBIyWTcQUI2grYN+ZFKbnKEY1Q4WbxhF8QKkTvNfSzqmM\\n1p70K6bafilaicqseNQKAxSlyFqGfbe0RzkYv9AW/OUuQf7dPDzIedY/MC87ZuFTVUnK51pnNaLk\\nFJQsLUXJSDhuQtj8ucxDn1nbXGRuYbSQp3ORSf3tarKiUKaCESBr27YEYyXTAAUqiQXXWW5tXsoY\\nMDIMEyLPfLhpi3Ue5Sy4jLEedKYSsSrQGEsaC8dhYBh7ttstKUn4rjYFRaKxisXCslw5UjqijZCH\\n4uysbNqG5cmaMRba5ZKUR4JfMRx7QrMGNOvTM3TT4dYrrIU+QVi29FOmIoTtUuRn32/3HA9X9MNE\\nzYkpSQT9enNCTAI46Y89aM3+cENoG6ZpYuj3vHwhmgcfLCVJq4JSsgVJiWmIHPZHjDVMKTLmhEUR\\nlguC9bSbNRiN9wVlpzkuoM6bqTxvvAqxVPo80ucDpQ44Kz4NqsYVx+FYiKMixkJKcnsYbXHGYKvG\\n5jrL8M0sw5+H3qpSswjT5FIVLW4tIsp6paydxXMoOXBuH2ylvNbN3CZfGSUp7caI4tYajTN2Vvhy\\nq6oHsjxIS8I5Q9NJSpbCoJUXfH351dyVX4qD4YtyTaX1raJI9Ae37YF+3Ua8aju++M/z1263F6CE\\njCP2+tk9OZdgEiHzStJaQcr9WOc4uEJNiBpN7nMpzYv6AhdSuA6yj75VpBmoFq0d2lhCaEWGawq4\\nig2SkGyMzCD0LPs1iIBKVNuaksWmnZIMU5WuWKfwtoWiSPSoqacfj1xf3ki6MpX9/gqrIovGcXIa\\nqGpkHEduyVUpZbHfGkucP4vnzy+5uHOPJ59/ijaOi5M7jNMgzsGaiSnz8I1HWL+kH3taq6kpMcYo\\nh+KsMbk6DLx4/pjcj2QUsRScb9icnLI9JJpmwTRGYhaGpLENVM+LF885HK5omw5rGqZhwhjD9fMX\\n3Lx4yThEpiHSDyP9MDKMUeS+TuM2C7xzaO9Z3bvLg3t3cU62Q1rLnMBYNa/qFDWLyG0qmUPcM5YR\\n7ROLzuGspFX3fWKYCqUaxqmgihX4jfev1lrGGKFG5UJO8rnWXIlZEHYxxldCI8Vc7ZYs3goKpUSm\\nNL1WMBZFRVBvMpcoKFUkF8UmjKtYWzA6Y3VBW43gHMRlqrWVoKYaqNVSswwib7VAcn3/avfkl6aV\\nuA2muxUvMcuQZ9gOajaGUF8H0PxfD4XXswpJjTJCtyFzmzpNlptNvreAMuiq5+pC6DeoRClB9Pcx\\noZx+tU+9FZHI0wCpNnICn1B4arEoCUjDWINzmqbVxCrsBTWfe68EN7m8rhTKjHGr81YjV2Ia8D6g\\nrUIh+PSxP/Jp/znLbo3OEW0kmSk0DlLAdI4Ye6DleNxTEVqx8x2rxSmlwGKxEDVnynz00Yfcu3ef\\nFCuHvsc5zW460nl5yrRNQKlzHv/sxwzHAzlluq5lHA6E5ZLVesOHP/lz0NAsLlhslvCZpluuaLuW\\nZ8+2GBWxVs2im4ZYKnF/w6K1BN+S0gRpD6Vw9fKGcZxQWhOHCVUiuSqabo0LAeuh+pZuvcTM85nJ\\nGJpFYGPPmIYd0ySg36rlc62lzsM6GJmYdE/wDU5rFpOi6zVxqHLIV0XMUJXGRgkMMjZR8+1quc43\\nvay0UXomk89+hpxmkVKVVW2Wmcc4DChdRSfDay+PpJxV1EwFYx4UOqdARYy2WCNEszrODJEiJPBK\\nkVV4ATfL7QUFEInJ4DQ411Lrr2lE3W3RUG57JlXnkkomyXJfCnFHqVvm3l8eSN5+yFUkj6/sWMI1\\nEHGTuLGM7IMt5CihMEprMV/VAtUTU8WNmhzm1ZCZw0qUwypJWi5FVko5V3IZaPytTFd+HFMgNEFm\\nGoP8PboIe6FUgYGUeVWVa6YkMz9pxChTlcEtFEp7JFhFEauUsZfHa/ppZIGg8K0NeB9ozxuGYUcT\\nAhWBuY7DiNKVlEbG6YCzWhBgU+GtN95guw0453FuySElgdwqzzAOnHRLqbFmQw/OsN9tqTnNAaqV\\n7c0l4+6SB1/5Kps7dwjWcrY5o5SJD3/6jPsPH9LvJ4bjgbBo2b7c45rMNEQwltCIW7JpHIdxIvgO\\n2ywZXl6xH0ZiVCinSWWiDWv2fc/5yTnKWBbrwObBA3708w/RjacrBq2XKDtRUmVKGaUkLeqVzFhn\\nxjJyagtGV0ooLFvFNhvQkrCei0KrwlgULslmQmnBp5WcUUmTa0JFaXRLFhHckKaZMjdSipEqNFW0\\nljlArQmUk2FhjgSjKXWi4gAZThoNRhucFz9MVRGjPVoZjCmU3ABF0rlQTEkOfmvkbim5Ai1NI74J\\n5giDX+X1pTgY5AYX0catN6LU2QL96jvmauB2EMs8N/hCBaHmUQDzn9Jfvm4ZZH44r500oArKqBmk\\nKb2h0lICppzQSZGMOCtVqVj7WiI13/qUItVJmjK6RDmoEqBvST0i+7V2LhVrpSQ9Dy8VFBkq1aLF\\n1COx3a/apzJq0qSwFuqcW5FrJRqJhyvKopVkNyy6jpozzjs0mhgT1nnGQZ4Wp6fnXF5esuzWbDYb\\nhqHn+uaa9WrFannK8uSUbCwvPvk5wQf6MRJLJjSO4xDFu2ENSgUKAze7G7pmw6cf/4KmW3Fyes7Z\\n2Ql5GFivVyRj+Mrbb3D59HOpYJQh7g9oO4es1oDzjsNhZNPA8TDivaPbbPj5z3/B6dkZ19fXEiTr\\nPcMwst0PuEYw6+2yQZXK4Ax/9IsPKLZSg8MjwqNxzBgi6ES1hmRkY2Xq7Hx1BZMNTWPpOkM/Ccsg\\n1zqLzRT1Vj5bZWip0dScyamiVKHGKEloWPKtn8FoEhpdbgdc86at3Hp5pHLKFMYysgh+vm4lvFhZ\\njVEGF2Z9jAsY49DaolWl5DDHLRpSTiij2WzW8xhMUtZVlarbWEecEv3wf5rS/9+8vhQHAzDnBMyh\\nocjpXsvceWuBjpSaqdnOt+WcyjMLSmpNiKZRk18F487lmpoPiCo0JWWkpLezlXk8akmnrgqqkagw\\nlcmjoc8VY2dUVsws1uEVv79oAWvUarjZ7hjshPfNbPqKTFMGRPjEfEAZrckHyFMhl/kAk+yzuTed\\nZyS1MPYFa1quXo60nbwnlS0lae6cvUHbLTFpJNhA51vSJHQji0XrSixQUuLi4pwYRZtwcX6fk/WG\\n/X7PnbN7nJ6est3e8PzFY9yi4e1H3+Ly04/o44HV6pzqDL7r2BTF+9/4q9TqaFrYbg1Ns+b+Gw/5\\n5Puf8O5v/ks8ePQWLlhpQ7qWSQUeP/kYYyyPHn2dX/z8ExwTNixwdsXl5SVjTKw2J+wPe4xtqKnn\\n6uU1dx6csj4/5bNPPuZwODBdv2C5WVGLQWeNMobTN97ElZ6bccuPtz+jazwDyLrWSkiL1rMXpVZ0\\nyULVVo5AJbhE6xOxVtba0qdIrxRJRfpoKGharUgTNI1g56eU5phBR0mRaTfNojJmE2DCGEfbLIk5\\nIoaY2Q2qhJ8gnr5ECCJga5Ye7xSqOJRRGGcILhAWE41TWG8FnZfnyrg4+X/lTIPDaC/gWC2xdhKR\\nEGgazzj1UD0n/P+Pdvt/9ppbg1kdLLME81pIVNXcepkqpVEFUYtJy2CtFU7/vMKUZB4Z/ogL4bYh\\nmeErRvDaSmm0rbMKzxOCwpiK1hmFYxgiIcgAq85kYhlRGMmJsBqrhf8YxxGSJFlNcWQaBpiHigbF\\nmCSpiVqpoxbkuXxxNozNn8M8KSp95bitLHZCdjI6Y6uUDnWqONfRhg6TIuM0YpC4+pxhGHpyFsKy\\nUprGL1guPVVVpmnkwcOHVKXRoeXuwxWxFB5/9pgUDcM4EnNiP4ws/ZIU5Uk21UqrMzkJ+qzUwuG4\\n5cH9N1ltTqlKhrwx9lxdXzEVxdnpCb5Z8/TpU1arhpg8Q9/TDzvaZcC7Dq2cbA5KwRpD9YnN6Zrr\\nyxvGccDqigoaVR1xjDg0w82WbrXBdxd88Bd/QDITKEtVCVWkx1bIoWuNp9QBXcChsUrNT2eDLYmk\\nJhluq0RWgpNPWpLSrSqoIoNgwfSrV5sDklSL4mfgVaXhlBiotNLyECqAcZTUvxq0u2BoQoP3huDA\\nmYzxVmT1zmJmB6k20k6jpleo+VLKLLpCGJxNoCr1igUhEmhFqkmWFzXPc41f/vWl2EqATONvnWRa\\nm1fkJeEZZKkilIA2XsuVKqg5o5L6KttBtOxf+MtvJdZUlEpYrwW2qsVBaazg3xXyATovMeRKZdG5\\n364eSoXqqEXSlGTvLCYu70Sf3vdHjvsjw6EnxihQj5xlEBUztVfEQyJOUbYmsc5AkXnvXjQlWUpy\\nEDXTMTLtMsNNZtxXpiETdMPb54/YNAtUUYzDSM6RphXVHYh81hiLsxbvHWoOrVksNzx462vs+kzj\\nV4yTwjcr3n3366xWZ3zwk5+inGcslVIN3jdCIC6F1ekZ+90Baz1DP7BabtAqsF6vcaGRiq5AxRPa\\nFW2zpAtLrrYHNqfn9P0oIp80IhzDQN8fGcej+BbiRKojy0Xg8vI5NSdSmthtryhFzHHkRIkFyohp\\nG/TyAq08dc7VqLUKtKTKElxrmecUmHf5Mp9yOKzKZNI8xyozA1S2QEpVKImSE4lCzpCmClWj8eSo\\nKEmJupZZU1CgsUL5IoOd52VGW5xtCKHBWU/wgUW7ZNF4Fo38foxRWKuxVuOUxiqLKuK3STFSozzY\\ntBJBVoqFGBMxR45jTyxJOA759s88g11EQVzqr2OuRFVQ5nzHW1VDrV+Y4CvBfivm6e3tTf46dEQo\\nNzJAjEkmx9roeb4g9YLWBW897cKT6jjveetsy9YUEi4UkR8bGSSRvVzsuVK9/FJC4+ZIOT97GGCx\\nXBLtyMv+hjgkGc4hOvYxjdI2DJUibhukQ7qVRwuGbNHCURWmvsrPAOQj7C4ncvb4UKhOES2c+Q05\\n9aRhRyw9XrlXRh6QNsrM1UyplloLVsMYJ4ZUePjmW5QUuXz5nMcf/4Q3Hj3g4uIBY1LcXD8G7Sla\\nUbSmnxJOKdp2Qd2ccfn8Kd5bunbJenFKNYo4HtGLBUorclY439FYy9hH7ty/x+HmBkpif7PFNJrl\\nYgVV0y1apjjS9ze4pmOzOCOWyOl6w8efPCMOA7oWfNsxHHusmlCuY3N6B2NbVBuYomI4HBnHgXEa\\nJWg4SeiuVoVBZUyRDNAaEzgvKDXqvPIrKGVFpq3Elg2FojJD0nS6UpPlFiNEFq+MqtKWMtO/vQt4\\nJ4nUucxkKaXxVmP1HCFoFCYYqq+vsjC9AWW6ealqb5mDlGjQJoLVoEUPY7XhECVNXR46FWrERkOp\\nkiORUmGclLh3i/xMr7wGv+TrS3EwzKPFufRTs/hj7r9nFZM4EmajCZmqNVUXfCPS0VJmA0rW6Jns\\nW5UwHytyLxpr8Y0lBE2jDVPUFJXQVKyrTFmCPWyQ1oXRgRZsvChbFblOWGdQuswEajAGuqZjCp7j\\nODIMN0xTwjVFlGcFbBXMNzUJjYnXswSjCl0rPgnXNPQGhn1m7EdqNqQDDDUyNQazVvicGaeINpWk\\nEtSCNY7xGFFVTDdxKrRtO89LYMSRY2Kc9nRdy/Zq4Prykj/58b9gmkZ+9Ed/yMWDR/zLf/1f5fe/\\n/0/59vvfxXQrkjaoIYKWWD2NIuUJHyzHYaCqQhpHwuJCZLulgm7p+yta47i4/wbKKeJxRwiBwyGx\\nbE8xyshacIoi4rECaKkVpmnienuFzhmjC65tcK7lOFxzducUnOXu3TPSeIWpZxz6A9eXN/T9QN/3\\nkDMlZZSqZKXBTqRSAcc4JForbElVKg5DRMJdqjOUpFBp1s1oESjVqOWXrCAVYR/oCjmrea6oMUil\\noZWlojFG5kvaVKyX718YhVsEUs5MevZPKI2hwSon242ZRh0rmKpJ2WLy6/ebo2I/RIoS5mWOiXKs\\nHMcJM79fKHg/4ILFe0stcfa7/PKvL8XBoLilztzqE8qsB4fbA0P067ffLwGmxgn0wntHivrVZkHP\\nykQh29R5fallJddkTIj44Ij7AXLFONCuoOOstrRF0pG9o9ZErVaEPLVgbECCbA2VjPMBpy2rVcuY\\nIimf0B8OVCZwMx5uLChjUNqKDZuKqZpqZmRc0CxOHc1ao2pDmRIGh9aV8TjhTIetilAdjQrcDWek\\nImapEhPBBREuZdmZ9/3xFTC0ViEbmTLJqhXN/vqam13P1YtLPvzFgX3fE6zn6fXPuNwO3GwTV9uJ\\n0xZ8LixcgzWFrmu4OT7BB8t47Llz5y5KaWwIwrFwjmGKDDExTiN3HjxksVmwu77EUjnstty9e4eY\\nxbnZ+sDT509ZLxtSFoZGGiVIJg8T11dPWZ6u5pYj470RRelyyWHscTdXhAeVlDWHw479zZEyTTMm\\nDZzWKKdn+Is8ZKYkVmunmW9khYlGNA5GZgPG3HoLNHbelsUS50BdoTXVLCIoERdJL68UKMMrS/os\\nx8F7yRMNzmFbxzAVYilCpVairPTWy4AyQ0aRIxRbsd6ShgnMiEqJfjJc3/SMWbwYJSWs8ey2PXqe\\n+1QK3lqcN6xXkrp9i078ZV9fioOhznuI28nBLDKVm4p54MecFDUDnRSV5bLj9MRTvzAoHPqBUB0l\\nlXl4WdHKUKvG+sT6jsO4TBMqicJhl3CmEFpDnmTfoa1oHFIqKANaFyh2bk8S2svE33lN23V0oeH8\\n/ARrHF95pDg7a/n4yWc8u/kEZy1tAEYkWUoZojZoI/p34zWrC8vpuWYVWvqtklbCKZQ2hLbj7voC\\nYx3ZJobjkZ99+oQL9QPevf8VlsqTq2G3v+G4O5KHHZuTM+KUOPY7mhGyBte2VN1xSIaf/eyKz15e\\n8ennz3j4la+RL6/48NmnqK3lf/rD/52/+x/+++xiz8PFhikleqMZrl/wpj6R0tdaTOgwyrJYtZyc\\nPyDVSMbRj5khVXZ9xLUdL3fX+Fl1+sY775CLkIqOh71sRk7PKMDheI2e9pTgBcVnGu4/uCCOiieP\\nd6ASd+5e4FctDx7eA6NpfEscXvJnP/tjri8PHF7upOXUBdt4qq9gEtYYJj1SMKigOeSIqRVlDd6I\\n0Q1VCE4xmipGJWVwdlYM1tdp0iVlyHl231aUdihdRTJtLZkia+2iKSVRayHmga4xOFsxFIKFMonP\\nwRgr1vupYmpLHhIxZ6l+9hkf9qwXmmxGxpRI2bDfjYxR3iNKDrtgPUUZDv3EzfaaQiV0isWilev5\\n1/FguFUmSESc4pbmxGxx1ZpXnnQZKoBC48KE9k4UdUoxpEI1CmNFFyHjYLHI5gzaFIyT3q7agm01\\neV+wZIyTKDHpLSveOkIwdGtIg/SHqoIOdUbEZ1I8klKHatUcLNJirOEsnrGd9lwOn6B0pbGarKHJ\\nEFaKXOTREosGA82y0qw0pggN2jaGqhVOAQnak5ZGW6pVRGW4c7Jkc3rGkCqNzigicepJeaKUzG63\\nxTnLfvcSTyVoh+sNi8VD7r75VQ5pwz//s3/CN7/7Pd77xrdwYcPv/je/y+7qUy7eeo8P/uJj3vn6\\n20wp8vajN3n28jPM/CvRCtJUCM7RdgFrG1RVuOUSPTmMheP0nNOLC653AycXpxyvrljef5PYH4DM\\n4WYLteK9o8zMxrYVgY/RBqM9LniWiwWfPLnCtwpVHejKxdmSaTqyPDmFxnLcHvn85RXTdCAfMq5x\\nLNs1m82SVCcGdSBrTSRicKQ8ynRfa0pVOGPwVlSCRlfZjiiJqZdsU8mJuHXu1nwbNT/PG5R8v1YS\\nV/A6iDdTq+gMpnGiDU7aY4PIMWd2ZNYVnbTwPpTCY8mlYI1lihNGV2JJWNOAzugaIQ+USaHMklwi\\nuhqM66ilsu6WLBdrLq+vOY57MBnvDM7+Gs4YpJVIaG3lQxMNNK/zIeY9HnN1UatMb33F+ELTio8h\\nRsM0FnByc5cqIiMJ/1UonaWnMwqtKk3jaJtEjvI0d7ZinSDDjC24UFluNL2SE906JcwDJWCVKY5o\\n3dL5Jc46XCve+kW35vTknMW1JScZ+hUNznjak0xUlilmWmtQymL9hAuVfEyYpsN3mawmjKp47Vms\\nGloXSLqQ+j39dASU0JFzgjpDQQGUmRHjiRgncsoc45G7D+9z98134eQej3/4ERdvvcnQR958+6uU\\nCh8/fcr28pJvfeMd/uzDj3jn3fdom5aqFG1oSAfDMEbiOBIaTzBWfmYTsMuGUXXooKn2yJvvvIVO\\nE9ubLcN+oOkMx35i2XQYo1iuFI1zTNPAsT/MKdKWNEacM9gQWCzXhKblja90PHtyxXg8cn7nFJGE\\nFELo8KsNn/30L/jo04+px4xRioUPdLaldR0unLBLO4b8CaUarFJoPCZLFqSdrzNrPbrK/t/oMrMU\\n5GrTdW5dqaQZpFGVxlqFQs95FLPUnSLQXl2IOVEx5CIW/VKMKDBrAYzMq5Jcp+M4UgDvOpwJdHgq\\ne4Yxk1IkpoRRhcKBVDQQGUcBvChl6BYdTll8aDDaElzg/uKcp9tLng1PMdp+QSz4y72+FAfDbez7\\n/JnNyGv52helznM2yyvJs3EF6+fcx5REiKQBK/+9QdKBc6kYbchZ+kfrjBhRlMb7zFQgm4oJSowr\\nRhG86BuKKoSFZaxItWGQTYbS5AmuxyOrJmLcHFZrJN2qaVpso7GpUnOhWTWwmAhtQ3EZZNVVAAAg\\nAElEQVQdQ1/QVhx61hiMHUlWeuFiC1EfMbO5Z7loxO6rE0vX4PWMMa+IdyIOjOOELfK+apo4Hgdq\\nNRjTkNOR1Z37hLMH7LPn88+fsr6z4rA78J/8p/8ZRVuePX1CzfDPf/BHvHNyShwEi269xxjFdhi4\\ne9aC96zXC1SG1XpDzgeU/yq6ylS/W28waeDnHzzmRz/4PbZXW5brBXfv3+Wd997n4cM3GccDOngs\\nhdofRCSWKtoId0A7g/aBWiveeR68ccHN5TVNa4hxpA0dNrRo4MOffUBrDd9561t8/8c/ItgG7zTe\\naBpnyWaBGRxisNQYxSuj3K21sWoZNmurMVljrKXWLBVDrK9iA25zTcw8yNNa0sVfJagVhVJRQmBS\\nRhlDmmStmKujkma5fEvOhcN+YFQTrfFkI5oYXUW8FYqbB70i3iu5UvQA1aDVrWlrog0LbNVs2iVG\\nBxoXsBis9Zyszhk/3hPr8DrL5Je9J/8/ubP/X760UiyWQSzLSiCuVf3lgJlb1JtWYK1k8ykjnnml\\nI8bNJb7R+DbgW4t2r+cFVoO1rcAxnFQS1mt861FaDhnjE9bLurRtNNYUfHDYLuJWGd8EvLVoPBpP\\nzJrtixsO+55UB/JspEFXrJe9dNM6WtcSvCOsAs1iyapbslw0LLsVi9bivca7jNWKoo+YUDFe4RqN\\nWyi8sZj5KWWtRlfFWI7k2pNipowjpMwwTVQSQxyY4gR4MJ7T8xMevvk+vlky9QduDkfeuP+AF08+\\n5ZOPPuHjX/yckjJ3717wX/y9v4/vNjy7vqTkLFWLdRQdCV5hjSGOI8YHxpjkPXoBnsZYWHZL/sX3\\nv88f//DPeLHtOOQVV5eGj376jH/2e39Amo60qxXtaiXGLmfJUWLhQrvEtR1WV7yy5JxQaqIJivO7\\np3hvWSwWAPg2UA83dK3jG++8R5kKFk0IC6wSPYd3Gmc0S9fRaI9V4Cho0iw789IG3MqOahY0X1VY\\nLCTNLT0p5QrVYHWYrx2wruCdcB4lL0ceVC5IellKE7FEUklMcRK/hs5QJkzOlAmOh0gqBWfMvIIv\\noCec0YTWUEyiFpG0qyxEdGqk1kiKI7YMBFtwwbPeLOm6JdoHXONw2vHw7AHr0ND8irkSX4qDQWnF\\nat2yWrtXm4TZzChf/0tWaxGfGJux1uC8Fmy3lZsxeI0Pmqb1NK3FeU3TSvnvrJmtuBqrDUaDcxql\\ntajgnH0lMlFGYJpNq2lbw3LlWW/CLJTSEn6SKv124uZqx/5wJKXMNE3EKKtEaz3BBLztCLaj8yes\\n2gVdG1h0Hu8MTXD4xuL0/HdbWXH5xmAbhWsUeg66dV7hvWazbDkS2ceBm+HArj9SS6FddMKGyJkK\\nOO+o2nJy9oi7Dx5gLYz9kbZp2G73DGPBeIe1sua6++ht/r1/998B4xhjFuhHyaQpoXWhW65Em4/G\\nBM/p+V1SLJjlGuM9zgU++fnHPP70iqfXip++KHx2MPzw8Y5PLi3jwfL48SdU4OZmy3GccM7Tti0Z\\nOfFvJfHb3SXWaLSVzAgxFTmJC3SacHrCmI9cby/56ZNPeXp1xenijJonnBMoqvMG78EUg8VitcNh\\n0VWRx0lyIZXBGYfBygaLMqtMJfRWfDsSWqtvXY1GRHHeZaoaMEb0EkYrNAWjEjATm2oFLDlJFIEo\\nFyMVCdaVRCwnUXlGC1WsinJxERoaEwQlqGQrZrVkqFqdMTqjdabQM4w3WKdo2hZvF1AtoW04Xa45\\nXZ7gfh2Hj1orzs4XbM46Hn/2gmmQnk7PrqhbkRMojC4slgtWG4s1CW2zDBWtYbEqlCwKNOsMMVa0\\ndnjb0PcjMUecj4RGth4lFZknuCMxKtqVY7P2aBvJgDMat8gsMNQ8r5y8Zxojfb8nHys5W25e7Hn2\\n7IXIp71liCNjHdh0p7iM7MD1EmsMXajUMFEqTDkzTQVnRLjV+ECaKtlnQhPAa2y1JBNnHz4YL466\\ncxrMZLCq4Ny86uoH0jSyWZ0ypYngHN5qwc67jpUqbNaOlzdXvK3eY7VeU+edfUHxFz/8A/6jv/N3\\nefrsJeF73xYOo9Y0QXNxdp/dbkBh8G1H0y7RoaFsDdk0pP7I7vme/+33/oT/9c+f8d53v83d0yX/\\n8x/896xcw/t/5dvcpMyzZ4WqrlksASXodW0dbeOgJgyBFDNWa0qqdM2cFWKiYNWcxrQNccj8w//2\\nH/Cnz35GUw33N2dMK83Lw2fE2pOyDHm9i3hjaVMrgcg54UxDi8M5RXAd61XDMltWC80nL685HPbk\\nGtFWNhG6GFw1OO2xC/Au07oGYyLDITINhVo1xlSCnjClEloLNNhjBp1QZIoeZkGSR7sW6zXWdlxe\\njsR1I4NqL4NMoys+VU67M2zTYvyROMmDZ7EwpKSJk+VkGXABbDjw9PIDlt19LpZv4Kohqcyqa9Gc\\nYRh/pXvyS3EwKCWBsc41HE9Gbq6PTFOcU3a+YKeuFecsTatpWkXbemoZMVaRkgzFpiAZkMYgdKSq\\nRMmoAoc+0XULrB2l7Ic5Lu41O8F5CZGxRhNjxbuAMZqctMScE4XZVyoxyobEGc325oDTAb0IjGPP\\nVEesEc17qRVnvfw8tZ1BVEdqkTWS8w5dPTlbjI5oK8Yo7T11MoBUINpI5VOiADqsNlgncBmxBCem\\nMXI8XrLabARmYq2AddOWklrWYcGjuxdcXW559PAeNR3R3mJU5f1vfIWTk47xGLHaMY2JpnH0u0yt\\nFoNm0TXEOctSazg9u4NKGaMDn3z2OT/84Ofcvf+Irtnw4w8ec7Uf+fjxR2xWLb/923+V53vPO197\\nk9Yd6NMzploEiTbG+RowTNOerlkS44TRS0qtc6CLoQkt7XLN1c01Yx5oncGcLhmfj7iFY6BlYk+q\\nRazMVdaVUhkggqcaCXqFMw3KVLwz+LYFZck28PK6YbfbcYw7VJV4+q5dYZ3HB4VzEV372bYPNUvq\\nlJ4fYspAqF7mM7nImlTt8aEQo0jsjVG0zYKaF3gfKCXTjxPWOqwXOb4JAvVBFawRAVYuGt9W/BAx\\npmCaRNOIgreqwrPtY7Rpub+5h8qzXD/IzOJXeX05DgatCK3DWcPF6SmlJPa7wn6fZqmzaBm8D4RQ\\ncT5hXcUHoSPJwZGxTkQdYx8FmV1n16aKaANd16JNxhqH0RrqRM4TtyAY5z2hgVQmAV54hyZIChHC\\niqCIzfYVaKNolHPkUbBeQz8yjSORAbzBOUcqiVwyTVhglIU6oWrEGeldrdIEu+AYM9SMUhbvWpxr\\nKFpTY8F6jTJRIuycRSfNGCeomilO4sPIiWaxRGm5CKxpiGnieOj5wfd/n6989Xso6/kP/s2/xX/+\\nX/1DvvaNt/n6e29ynI7cu3uf7/3Wb3B9dcXF2Zo756eCSB8l/CUqab+Ox56zu/dl0IMG16BVZjgm\\nHj95wT477qwW/A//+B/z8ZMr+uNAPyX+/IOf8uDRfcriAaY5gzjb1EsiTRNGKZwVVWBoGqY4UskM\\n44RzAk2tFEod8acX2KQ53Zzz+eUTrJm4c7ZgHyPjomUfR6wD1IA1RVpOq2idJdeI0+JHaJ2hWzis\\n0xinqbalOgih5aZbc7N9wX7X441hvehE8txoXBBGqFI9ZRzJqs4pVbfXXJKtS1aYLpCrI9YJqweK\\nhRwzIQh/tETDZrNEZ8UxHhlTZByhDRrvxHyHVTIXs5pCwQ2K5UqQbc4hZDBE4BaqZte/ZBUCznhS\\nPWBspml+DQ8GrRVNcJIgfRKYyoGcM2PMxHEmKivJYgitw4WEcwrvKuOcVKWNIpcR5x1pUihTJJgj\\n5nkmoTBoQlOlH7SC3DZKaMEpZqyRoVKNQgQ23iGQTtmaxJqZ8ii8/3ybZwEpR1rdUHNl7KUNSSXL\\nzawELDbmEWvPxRVHlb50jhazSpQ0Mr9w81S5o3WebBRWWbQuGCsbFglzERrMFBM1JaZhkN7Xt2gE\\nRaaKuE6nvic6x9XLJ3SrJXfWG/7Wv/49/pd/+s/49nffxxp44+GbvLzK/OTPP+B3/tpvseg8OY9s\\nr29I0xHvNVlVnPd461HaYFygH0dCLDz56FN+/w9/QNIeVOLq6hm76xuG3Zb/+nf/AX//v/x7XL/c\\ncu/sPkMpME5cv3jMcHNNJrHabIh54uTsDilFjsMW58LrEB9rSDEKTWq1wm0HgjI8unuX3XFgrA56\\nzUE1jKqFepStAhO6WqyCLvi50hLuh7Vy4IZgsb7B1Aa0wQeFdR3OOZahJ49H2sYQgqNp5dpKpsM6\\nxbjfoZRkaA7HiF0BJREWDo2lZIsPS7Q5JdefYMyOnESFS7ZkVVh2LV43uGi52r1AGUONiRAK1oI2\\nBuvU7CKCdnBYbTFqOROxFE4vgAzOYEtgu33OnTv3qJM8FJ371W71L8fBoBTLppMySlU2Jxv6MTHm\\nDAVyMlQiuRzReoO1MoizTlOyR8+x4VpnrBfrUi5xTkKSNsPaIFHoRhGcQWuH1Y4mWLb6EmvF0250\\noRiHcoaaKtaA02EWo4i+AJOBQC0F6yqUidB0BBxVJxIJssIpRUwTaNAqzdwIizUij721cUMRp948\\nX7Xa4I2nc56YMlZZfBCPiJ1bkkM8oiZosqEMBywaXQtxPNI0nZjCSmaKmZonHrTnPP34J4za8sbb\\nX+d3vvN18n7Pnzz5HI3mwz/9gOurPZ3K/PXf+k3qsOXyxVPWi/uzbsQwxoll25C1xuVCnibaxZp4\\nOLLdj/zow1/w1d/4bQ7bAVtBW0M/jfzGX/kmf/Nv/E3+x3/0jyhJotvPly2XRXHYX6KMJpXKiXKM\\nx4EYI2PfY1rDfn9J1y5ZhhOs1RgdIGdi7OlSJSjFZTzSeEfVhUVYMJSDhIUqULrBh4RPShSbVuG1\\nDBOLyhQjlC3fLPGqQVmHi2Ieq0BjHDU11BTl4eULGGF/Wu3QxpNyZEqFPBQmX2idomkcRlkMG9bL\\nM1IaSf4ehwlxiNaJbnFOHJH35Sutc0y14+XxUly8plJdlrmKETFcLplF25Buc1q1h6pQxaCVw2ot\\nLYSq9MNWgESkVxkmv+zrS3EwKKUIjYBSrdaMaE5PG2IaRKZ6jOz3Fe8VVUXaBYRuol3AuCvkUvEm\\noE1F6QGUiJ1SlPVhzZLKc39zjnGG5eoEqxumKePvtOx216iaadqK807gL7aQVUfrW7wLTCXhVeE4\\nZVybqSkTWo3pLN947+tsNitqKhAsTAmrPNYVUoSsEujCFI+UYnArhbaWXPaExpKGilYB7zvaMpJi\\noguOxgY8FaffIKwiKd+Qa+ZmPHJUmdY6gu2oXWEaRpk1qIaUNIqEKhGjFYd+5E/+9Me8/83vcD+0\\n6OMVf/TTn/DGvfv89vvfYbFYsz1IWtNhd4XKR67LjpIquxuH18K+rMPA3XfeoDYNx/6Aa5a0aeTp\\n8xdcXx44Wd3j5cvP+e43v8GjN97i6fM/5sG9B3z3t77H24/u8Dv/yl/jcD2xWTjuv3uPwG/y2AWe\\nvnzCOI68vLxhHBMnJ2us6eiHHh1kK5Fyomlb0bwMEX+8Znm+ZnN5ZFzuebL9DPSKYC13bcuuZJRL\\nNL7Ddy1N6wjOUN2EihpKQquBOlbq+Yr2rKWxJ/QjjMeJxlf2LYw9pMmSR6FLOZeExpQHyhTxOuO1\\nhiiHe0pZNmZ+4vzkXWpeYCiAI9q7hJDZqUtiHMn1htOz+5S8ResWayMdlWoMY+zJOmNNoeoBZZM8\\n2BoPtZBDj9FKcPR5Iy1lSrg6uzCNDE2tywzjr34wfCnWlSCrPZEjl9lpZwmNxrlM0xmROeMxVGqN\\nGC3ILh8cYOdyX6N0kgGiUpQixN6UC+NQyHVCmSyiFOcwrqXrlmxWJ3Sdx/uADUYSsPVS9ux2IRr5\\nOUZdaxGPuDbTLDN37m5YrTuc8/jGYatG4fC+RVUNJBRKdtpxJ5VMHTFKDE+61nllKsnIznqsc3hn\\nKWqWeDsxTwXXoQh0qsUXR5oSQ+6ZimDRY1W0yzVtt8C5DucbUq34tmG57nj58nOqrfTHI197cJel\\nmTgOEzH33Hn0kLff+zbL9SkpbTkeLkn9gTz2XF1f4vE4A+MQCcYyHg7E4YD3hpfPP2d92vH1rz7k\\n5ZNLisl87Z2HxBQZxpH7d9a8895XaZo1b54spXTvVjSLjrO7F5ye32O5WOCsIqeR3e6KKQ74YHHW\\nU8jkGCXObbWiLhrGw46u6fBtoPUdJ4szTtcLVo3DtxrvHU3bsmgWnJ/eY7NZo52Yt7SxpFRIpZBn\\nczymRXlLWDhsW+iWsPSOxluCcxiTUVVMSsZm8c/YLAYoZ3HaYbS0PgIWOpJ4jnYTGIMyBmsatJE2\\nz3sHpSfmPVklhmlPyRPeKladpwtOzINoUA1at1BanA20nSG0YJzg4q2XdbIg8xPKVLTx4EYJv1WQ\\n8q9h4MwtVKPUSFYFpcsM8oDiJbq+Ca+95WYGwhoje33Bb88JVCic18Rpds/NiKQUiyjIihGqjjE4\\nK1isuxcPacMVmB2NS5KuzJJaJHIuoVFVU6uhaT25jnhdOb0wXKzOpGy04hOvSag71kuVoankUpnS\\nUUhFxhBTT1UjlTizHoK8/xIld1NVZh8f2kmmgLGeXI6UPGAqBN+wXJ3QJk9xR8pKwBzGW9KUKFmG\\ne7ISLMTksGbkcNxRTUuTI6pGvvWt79E0C4oLXD27FFjKcMOwf8nX3v0Oz549Z7VaY63heBTw7bg7\\n4DT0uy3DZkE6FNb3Tvjb//a/xY//9Md8/Isb3n3/Pe7dO6GUzDe/9T7vvPMV7j96h+9t7nH25gW5\\n1Uwm03RBPBMUSoHjNGKs5+RkhbYW6xphDziLbzoxJ6URVeG0PaM/bLFdogstTw9X7F2laA2twXdL\\nOn/O6mSBaxwvLl8Q8xHrKmW0r6IHjfZMSWF9pesach5JOdIEgzaO0QAZSt5BGaCOGDWSyogxFe8N\\nzidK8UL/MgO1RnLZ4vQJWouHpBpN1gpfHM62eGsYpwh2otbENBbapsEYCz4QU080Fesswa8wSoOy\\naL2nCVbEbRiKktSvWrTI/msWeE2RP9vGon61beWX42BQCsbpiDGVOPXzqRzxAUq2lDTRtJZUBMcl\\nJqmCMRpXISoxwDDTgNWsEsxZpKpqRsCVKtzInAveKkLjqaNl3Z3R2JasXqLdtRwMymH1CblIdmMd\\nCqkMOK1ogiEnxarxLBuNa+S0Lqqy399I9JzqsM4zpUxhoqqJIR6x3jAMEW2TZBNEWak1zWruGWUS\\nXUmoaqEk4VMqg7InJAeuhVNzD6cqNU1UpcglCebcWGwXqDlyc7UlTyNtcNSqWCwvePriirfeWlOV\\novMt2xePeTLHs0Mh9lt2hy2rdcfl5XNKntAqkqcDSluGaWSKI9ZCzJXdi0vKuEOXE3Q88h//nb/N\\nf/dP/pDdiyv+xr/xr2F15fziEWcnJ/zio49Yff0hy3sXjLtnBJXY9kcoERc64hBBa663exarE5ah\\nFfVl4zC1UDWYbkPte1Sp6GlgrQ0htNRYuXJW1KqqoCz4dsmi6QhmyZ2TBVTP02cfUeuEc47jMbJY\\nOlJU1GpBOVKUp7BzUEOeh9aewy4K6VkVSrIY1aOVKB2Nk6F4SlLNinQ/EdMBVS4JbvZL6GuqGXHe\\nUpLF6gajREqfShZxXxU+RTCKnDJOtTg8pi5wTmFsw2TEDVpKRKtKiqDnB6hQxqTSrlniFq0LWP1r\\nOHxUCmLsqVXRT/v5As9fqBicUI7CCqVGeeLbRFUVY6o8+RVyVyEDwWmUAdEtPMQaWbdRIcWR6iWm\\nzruAt5rGNWAMQyokEp1foGqLkAMzsexJ04jVHucyWle6JuBsj/ULptwT88D+uOXk9FSqAytqRnRB\\noYUmrCopyooyTuIoNVo2FZL9IDHwSv0f1L1ZrG1rep71/N3o5pyr3+3Zp606VS43RC5jE4WAAiji\\nxpINFiYgIZAiGQkkbglXXEWKhBRxkYsoSBEBicYClIAUJSKQyBHCcojtsitOnXK5Tp1m93uvbs7R\\n/O3HxT/OcSUoVFVkReWxL/bac60911xrjvGP7/++933emnKlxNK6mhxttSLmPVO8ZUNLnjWmQEoL\\ndqVRU+oE5vowIuJoO4uf9kDg5lAtvp8+fsrDuw94+vgxL188JRdwWtienKNKRoxhnhcuP/mE89MT\\nQozM44jrN4QQsLrU5O1UUGkH8RpTXlFEeHSy5ef+1a/wwYc3fDgeUyQw3izMN094tN3y5T/yHv7m\\nJTePv4VcXXI7HRDJ2LbHpArzHYae69tLUglcnF/gfWBnHLbp4OiY8MlzlmlP8hODqb4KkyP0PSIL\\nrWrQThAdaLqGvhtQxnD/vGd/fcU4vv6c3RiCZ/YLQ3UyYKRQSkIRgAlFX8NiVEsq0+pbSChbS3Pn\\nDG2n2W4dpYyIrT2vXCI+TmR9xbQslAL9sGAb6DpHsRqSRmtHjortcExKhSJL9QUpQ2trQplCf668\\nNLqlcUf4cLVCixLW1kpK20RJaQ3YqQrREkGvbs0f5PihWBhQCiHiQyLm23qnFHBOqvmlGAZt0abO\\nmRsHyFJHmLoGv0oxODdAKZgYMEaTlFqj2oXW9VQEuKZIQumC9we224fkVtHYDu06dIApRtrubKXx\\nWmIYK4dBazIVw0bMtTPdBLCe6fCaaT6Qi69yWm0RYzBO12g7rauSU1cQTYqxIsyLR+uBnJeKB9dV\\nuPSZUMY1lhQWlLUoWSgqspcrnu6vuDBH9KrKiOfJo3ygcU1tvqZYK5Y4grEs44FjyRVDLrDf33D3\\n4dt4P9LbhhQm/BKZ9iNQeY9oR4yFxnT0/RFBClo5SgmU5Alz4GUSJN6SlitMd8LZxRnRRI76nvLr\\n3+LFPnLn7JjOad5+8x5aNPnVpyxPP+T2+VNijiQxDP0WbQxhLqS0kFKkbRq0dTR9h+p79GZHiIHW\\naLquJ8cZ0JANXdPRmoEhJbweQDfMaQTA2hp737eOtx68z0efeJawxyhNSRM5e0JK2JKwnyVCFVB5\\nwYe6hd10J/igWUIgpJm2iRgbMbal7/s1wwJicZRkSHFd3NWCoaGG0LZoqgjK6UySTKt7omj6dkdQ\\nVQCFgqYdiP7V6h2qbAekq0YtLEa3iCxVT5JbUK5Ci1UmZ08plrY1hABIqsnZP8DxPRcGpdRfBn4W\\neCEiP74+dgb8j8A7wHeAXxSRq/Vz/xnwp6nc7P9ERP7m9/we1PJ/8csKXSmgcoWYousFSL0A2qbH\\nukjrBkyekdTS65NadpSMVhuy87g2ksWTw7b6J4yl6+rkIsQDs3doaXn26je5s/sphm7AtoFWLuh8\\nIYSKl9di0b0wxoBtImE5oFShaQG3IK4hyYHRPyGViLItovdY14MZ2AwnJF7XCPuQUToQs0akYVoW\\nVMnorhBFcXzUr/qIQNssRBlJ5RJvTyipw9gZpT1aNE40oiv2npxRTWG5GYla431kf7ii7zbotqNR\\niu3xObeHwNAX9vsbrm3H73zz6/zkP/czGB24OD8jxwXrNIdngZhmzi9OOTt/wNHJA4LR2BhAN/jZ\\nM97c4v2E60a22564v+K9dx8Ryw1v/thPoLdbbG/IYeTBg7cIpeY3/s7/+T9z++wThuMjctuii8JP\\nNxgLbVvH1TFlXFaEsGBs5vjojO7+W8QHb9K8fFbHcynTmgrwTaZwf+iZD5nJOsY0E1vHPm94dfkx\\n98/fXZF+GzrzNhfbtxinK77x6X8LambxHzHeGLR7Cz1UdoeUhiVsKN7T6R1jFFqO6PqBmO4S0gty\\neY1RLeJmhq1DqWNAWHxDWEYKEaPq9MDolhw35DJiXYdpOtrGUZJDuxPmacS2iq17uG4lJywP0W6s\\nmhSt8PGGkArjdImQ1j7FjiQ1BFo5i9eZeZ4Rmbi+3XPcv4Wkum39A10YgP8a+AvAf/Ndj/0Z4P8Q\\nkT+nlPoz67//U6XUjwJ/Cvgx4CHwt5RSXxL5XhnclYGlPgto0Z+Zptb8wVZjisW5ijuzzqF0pGAB\\nh7aVvydEkGqOMqagtQObcLalazStrfNrhSbFPZ3TtaSzQqLSdI1e2Q7JV9ssE0lmbNNUUq8a0dqQ\\nc0RcIJeEMaUKr1LLPI/EdEDpO6AjTno6d8QsN4jUZmeSBVkFUtZWXsRnBh1KpU0VVbC6qialeFJU\\ndeVPwkbvuHNyD0kZrTJaGpYpAIn9fmaZanhqWG7ohsxSEnmpcX2vXi117+osR8cnvPXoIa9fP2HY\\ndBjVM4YXhKTYHB2j7IZ+OEGbjs22Jc8R7QSjj7DLRIgTym2J/obhaEs6zBRrCE+/w1IW7h13XF3v\\nefbpN1FaMe4PNE5x/MbbXL96Sry9ZLs75mR7SrYNSTLX+xtyqvt5UwpKNKbboLY92o+U5YBKCUqq\\nuP4caVw1fW11S8wZo7oqotIdvS2M4QXGPaIXhd0cQ/H4dMvJ7l3m/BhZImqeKX6mmC2SNCVltNmQ\\n08jsbyn5DG1SRdjJBl2OoSwszJRMRdUNkEJCUsecIIUR5RzOOtTKZoBIiELjNhRxtXqVXAOHQmF3\\nXCMTY9QgGUmFrG9WhB6rR9JBsWi1Qeuero/koKtfQ3coFSiSEKm9CK3s55Dg7/f4nuNKEfkV4PIf\\ne/jngL+yfvxXgJ//rsf/BxHxIvIh8C3gZ76P71HZh9rijKNxbRVrGLcCVoS2M6sZyFZkm7JIsYhU\\nKfJnobJa61ouK/Nd48WKUbfW1rwIBSILUV6TiyekPSGNiKSqTCxCjB4fR1JZiDmgbYMxHZ3bYFWP\\nM0OdIavayzg5vst2d4JphFBuyGWsjs6mwZoO59w6S66Bp5/9rCJViGR0qT9HEoxpkDXfwGhLkUTO\\nkRgKKRZCiFzvXxLThEikrOCYxS/M00QIgXGcmJbA68srxhi5GSeuxgnXDMw+k7HY/oj97BmOzlhE\\no5qBkztvoJqOQsM8Hvj2B1/nd3/nV5lvXmCHHmyH7U7J7RmiWzqTGcfDyh6YGeUK0r4AACAASURB\\nVHZH6N0GuyT8/pIyH1AlkJaRxmr8OPLi2WPmONG3AzkLc/AVJpIdF+dnbDZb+qFHaV1HktstpIWy\\nf4HK/nNkz2c3jxQ9ughOKY5Mw4lYmiQM2rGxG26uv82SXlOywdqW1vU0rme3O6VvL2pIbTwQlpEw\\nhQpYLbnyEw3MfgQVanCs6nCtQ7JCq2oBN7qp52jTMvR99dasW+CSKwvE2trfYe01zfOBnCvN2oeR\\nxjaQzO8rfVfGg8YiGfwciD6TYl45pBolPYajmtFKrJRoav9LqYxWkSW8BjuS1c33ugz/keOftsdw\\nT0Serh8/A+6tH78B/Op3fd2n62P/n0Mp9UvALwFsVztzXQg21D5JbQ7pNWXKWFaF4+9z/FMKNaiW\\nanQpqvoilKppP1ZrirhV31AvMlRAbAIJxFgw6sA4vcTuzonZVC+EJHKeSWHEOLWGlyisbtk0J6Sc\\nSSVTJGL1gFIWrXa47oDrWlLwFBZU6SsqDIVWGmMiNb/OQM4V+LoixIyVdVGoyHuzpvmKCCVHJK+I\\nulJ/N5vtCVoZwu2ELQWRghQIoSo+m6ZHuRoBN+w2hLCwcQ1RCm57hGoadqfn7E7vkkvm8uqGefac\\n37uH7T/hw48+Is/PuXfSYhvN137tFQ8evEmzPWZzdE4WjR3u0FjP8fkFxjaEsGeLqp4SYwnek6TS\\ndfpuw+I9V1evODo+xeo6yuy7jhAzcRkxrqFIrLoO1WAEtLWIsRSfcHGCVGPe2q7D+6UG0Niat9Cl\\nOrJOxnIqMIfKAn2tL9mPj9nZU9oi5BJQSmiMo2t3lBAoaSLNE7H0aK3WLI2KX0vMSLrGqhYjdYsq\\nKq1VnAWlPndEKu2wtkFJoG22GGMxpkOLWZkPBu8npFi0caB6SsrkEiAl9jdXNOd30Ephbe3niGSE\\nQgyaJAvYGaUsBYNuQGNQKhPTQoxhXSAS2kSCPEPLFmX/GTcfRURUpbb+oP/vLwF/CeDOw61oXX8R\\n2lTghXJpDcwoJF1QrjrFjKpW2FwiNcJJrylUCqOquARl1kAZhZUOLdDYesc2SDVeocmhJbMwLze0\\nrWOwA56MSCbmRFEBqwTIaAxWNRjb44xZ7yI9VjV1S9DXhKB6UmtCDrTWI6ni5DSm8gWUwUS1Cl5q\\nMpQzdfFIOYC4mmwcQUklOsmavRn8Qko1ZCQV6I2habbkfKDpBpZx5uT8nPngUdZSROMazTzPdH2H\\nj6lCQrstbzy8x9079xiOj3BNx4NH73J19Ypuc8T9u2/w9Mk1Hz9/ir+64f23z1Bq5uWTb3Fy8QA/\\nX7GI4+zOA6xVXNw7wqjKPpASaDZ3KEtmefwxw3BE07dMt3v8NHF2crcyF7SmiDAuNc07xkAio5Wl\\n7weSj4gk2m6DshbtWlg8ebokpcQ8T1irMGLJRZCcGYwm+cDWGIxq6LUFAq9KYX/1Ea/LXXo3UqQh\\npUgplcdgSkssmTSORL2t5Tf1rr/MC1ICB/+Uvt1AsKukvt4sKphYrX4GB0XTDg2db5EiONfQtS0x\\nVIFczmYVHC3E2KO1JmZD8jON7Skpcjhc0Q0dpUykEpBV7p+CrVRpM2C0XlkOCW0atEoU4ufXl7YZ\\nozRKPEn2ONP/QNfnP+3C8Fwp9UBEniqlHgAv1scfA29+19c9Wh/73odUKCiqxoEbPQALzq5359VE\\nVUeOVZNQDUqV3qz4LBCXdaxD7VUUVyGuxqB1jfysTJD6JqYsFFmI6Rahah9i8IiSz8eL1eGp1pGP\\nqfxAu5BijS+vFYlHqwZtE1oHQrnF5K52uHVBm4oyQ4GzVeKrjal2apXqiZqhaEtJitbULVQtSSNG\\n9RURVhKiqu3amLbSfpoWEmw3PS8vr1EOlItkL4y3HuMsl4drlLZ0wwn95oLt+Rt84cs/yqfPXnFy\\nckIKe9778hdpmw3XVzNvfyHw+DtP+Ppvf435+oovfumE9uwNbm4WjE9MamB7nBCnKMuCGRqUHtBN\\nD8c7+otH8PHvcTW+orye2GxbTo4cH/3eE9RwTmcUzbCh7Tu6foMShU+etmk4HA5kmZHisctCef0K\\nzs6qgCPNlJjp2xYf8hpNX+E7RismD0aEJFC0wmhBi2XJN1xPHzFygjEnlJiJGVIGq1tSDuSQSXFG\\nKchSYTzeB6pQIOJjwJietukwTmiLIqTqX9Cmq5ZoqzFW4ZoaZ1cDjT+LI3TkXE1hKQVyCogu+Gww\\nqcJjMcI4X4LZ4ONYsy2kahMyE5LVGlP4+wDaEEc+D2AsVGAyNWHbaAcIJc8/0AX+T7sw/K/Avw/8\\nufXvv/Zdj/93Sqk/T20+vg/82vd6MqWgtRbBIKpDmQVUQGdLwdEXR8w3DH1H1xyjZLNisy7JMZJS\\nXS2VKDSW1hiUDmgHSWpUm2RLKXsweU0AUmSTIWnQnpBu2R8iWnVVXLRCZ0uGkiy5aGxXVk27oxgB\\nWTgcaqx84ZZu2HF2/A7GNLy6/JD9/AyrBlARY6VyG5WiWLWCait2XFtLKZFpjuQ0VfITisYZclmw\\nypBYEMmkPJHKyMIlOS6Y2HE09JycntPxLptXT1nmxOX+BZ6Z3WbH8ck599/8ImJq3qYkOD49IxTN\\n6cUb3NxOdF3PJ0+uaPuFuDvlx7/6U7z1hS/yP/1Xf5mjcs04ae6/+wZ3Hv0kSeBBKzx4q+eN0y0v\\nn1+hzEB/cRe5+x5ijtGSePer/zLf/Hv/F5f7j/FXhf31SwoGE67YZ9joiJMtt7e33Lk4pzGaHJbK\\ngIgDxTp8f0zTtjDeIkmjbY8shxo1sArClFTJb5FCL5ossKQDKiZ2Rjh2DcYVJv8JwgvCYiE6hm5L\\n11/gwzXBB0SPlLxQawBLksyyjFgyzvZVY2JGgrwC7VCdRs0tIpBS1ZMgQpYDfT9QVWOpGrZCwOuI\\naUAZS+cc4/wataaieyw+LaigGIaGeT7QtBeYHhrbUiRQimEJ1xSuMUoTU8KViGZgWSIxFSLTemFv\\n0AiaOp5P6+j2+z2+n3Hlfw/8CeBCKfUp8J+vC8IvK6X+NPAR8IsAIvIPlFK/DPwOdTP9H3/viUQ1\\nUVlrKgiVCtgUNErXu6sTw5w1Vre1iVdaEE3RQsk1oFQVjVI9zvQYM2KygWal+UndpiCOgkdli9GV\\n5qtUjSmLscI9G2cJPpNLlf9KoWoTUHhf0AaK2DUxKK5y2gmVCz0GZzc4M7DtHvBqfk42EatbSp6w\\nrvaUtarpQHXbZLC2BpLGFMhJkVJCO1tP0FXRWSSArota9oWDP3B+dMx7j75CXgJ+Xnj18gUffeu3\\nkew4ujjivXffhgz7ceaDr3+tSoWV5eGD+2x2DVfXN5yfdNy7fx+tHLu7Z+w2p3z7o99laDtKga/+\\ni3+Mr/2t/4U2dQSfMbbDx9c0reFkOMN1x9z98gOKj9jmtDaRmw2UhVcvn/Ls+XPUfODq9acMbc/u\\n5C5NNxAl1XQwrWg0TPtblDWVYrUEjrY7XNvQ9R3KGHIslLIQlqqSvH31uqoEqSHEUiDnXBdtSZii\\nGUyDthmnNKloukaYfaiRBDgQiKEu/jknRBIpLWuT0VEYKUFRjCZLXDNAM9olpCRau2E8CDpbRBR9\\n1xPTbc3wpPopSsko0cQUUM5VxaayFBVRpkJpG+vQLkNyGFF4H1C6RSgY3a7PkwCLMwNJXpOKYHVi\\n8hN909Tts9GYLGtCWiZnTcka6H4/fuEPamEQkX/nn/Cpf+2f8PV/FvizP8iLUCiMcSipOHSrHaJi\\nbeoYhWSpJhXTo3CAwtqW4FuUqgYSUQZnq/Sz6yq4s1hBY1bsllqpTRUwYnW3YrVHQk4UMkoFUtzX\\n3gaekqrBJsRUGZFSAbIpLzhbVWcgiFJIsThzvuZPZJzZkvOzz+m8WjuKjFWtJuuCVOqaqZRehUML\\nRTSavCLha+JyygEh0DSGnDJnJ/d5dPJlwq3hxSePiWWGpNAl0e3ucnF2yrx4nj95zvXVK/b7iX/p\\nT/4bnJxfkCXz8ce/x+nFfVzf8HsffkjTD1jj2L7e0XVHbLdbri9vMcbx4z/9R3n59COeff3rqPYM\\n3Sp2g+HOnQtAcXW1x240qm1pNxtMfwwoBIfcToRlzzLO7M4fcLi5pBwuKeMtISx07cDiKkDk4aO3\\niTnTOEdOievbF7z51o/A2TGolkZFcr5FdZpxf0NKvkbbmZ4kU50KaIeXOp1oVGFOgaSEpvS0nNam\\nowmg6vmScsboBqRHpKpvFblWoEXVG1UBbFOl0o3BNS25eNo2U/Ke7e4eXrUso0GSwZotKSWyTDUp\\nrAgxJ5JkTLbgCpp6Xpvc1nyJlV6uqLDXUiDGTE9LSRrRFk0h5UjOM6Lrb7hIJXmnMiKyAXG41YyY\\nc+V9lGLWHtUfQuYjKJqmJadSMwZVlXBq3UDpCHlfx4WqR+sWpdbOvabKWHXBKIszDW7l+g99R0xz\\nJThJrrkAMRJjpust2vZobatjUK5QIoQoxDRVmaqSz1DVVf4bFUtO2JyxrqLFpagqsfYZbQRRC8Ix\\nRWa0zRXuWRRGKwRLzh6lFFlalK6xfEqXdRRqUXqkBI9pOhAQXYEwohOFCdjy3ns/jhPH4w+fcNY8\\nYNufkUpLDJlE4e333+DJp7/FqxdXvHz8IW9/8X3+5M/+Ah9++9v8yt/+G5zceYOf+4V/i3mZ+PjJ\\nC+6f3yOkRFgyL19fcXIC87SgSkGKEDy8+aWf5pv/4Ns8fP89ulPNtj2v6sRsef3iJaG9ZGjO8JPh\\n9H5LaxuUVXRuy0/+Cz/NN37zt4hS2N7ZEcdrkkT6YUdYEtpY5nlknCY2w4aSC67pUKmgXIMqjjJd\\nEQ/zmq+Qajq5GZAUSGVCOWhdNWJp1bLkQIk1MNbGSlB2nEC6pqhATBGTI41VNf9RNNDWRG2VyelA\\nyZpSerIWxHu0hmnSuLZdG9kFxULfDJjeoqXBLxFjNyAzMd1SFaQFEU+SQEwOk8E6EKmiJ3RByWcL\\nVKU0NVJRbzHWtKwiBUWLqAOiliq+E7C4mmRdbtDS1z6d/WxqZyllqdssCaDi//8l+I8dPxQLg/o8\\nEMZ9bmk1xqJLFS8pBkRNaDIKs5aLIKnCUzQDWtULXelagtsmonTt4NeYMUvMkIulmJZiKyDGuZqS\\n7aMgyrOkBeG2npxoslR5cEoBX4ReOwKJRtcMw1KEJBodNVc3L7lzusGnTMrCYI5W92SkJKHoOscu\\nqSACCo0WIAlGNzSmoRhBZCYrTUMlSOXieXDvfYbOcn37nOl25HT7PtZqDmlBF43ojDjh9dVzpklz\\nenLGl7/8I8QIf//XfpWb/SX/yr/+J7gdM//P3/873F4n3nn/C7RDj47VX3J6fsI4jjx5/JjNZkc8\\nzIzTjGssbnfMJ09f8cg67EnLYBuub29xg2Y7HCG2RRTkGIjzLUYbjt55h+snidO33iVPB3Iu5N0R\\nkNnf3CDpltvbK3a7DZI8KRp029I4y9HRHY5OTzDBo3yk+IU870mSiLmwPb4gxYUYZ2xJiKk5jkqD\\nCXVCEYtCtKXJlV0xFQvBoEskxAN9cw+KwRbNxp2QwwF0qsI4EXKq8uiYBOdapKQK87E186FtO2af\\nsGaoGpm+IWSNlKbyQpuaMm2MqUyJmEE1NXQm589H041pMNJQT/YKAAbFOL6s0B9V4xuVDpQSKQWM\\n6Whch2RDLC+q8tGaOn0QS8grYiBHkPSPRDF8P8cPxcLwGbLK2BZVfF25tUNpXWfDpWGcD4grn2Pk\\na2itA109B72rASXWWbSxWCugBZcTISqURFJ0NRXbNaSo1kRitY4/dQ0cMU1lKJaKSc+54JcKjxUp\\nGDvgdGH2HiWWUlQ9obXjMO7Z9K9X6lRh02wJMeFzrYRiUBibEar+otrLHSUXsggKi7WwPdohagYO\\nGN3x1puPePV05HB7g3GK3fYYcuBmfsGgBvpyjNUdu92G/dXH/Mwf/eM8f/0pv/Grv8LZbseTly/5\\n2Z//U/xvf+2XOb/7CNdvsXbLF955l1eXLzk+PmMJM5evSz1Rm4arq2t2w0DjLN572rbn8mYE9ZRl\\n3tGYyHZoyMXzpfvvkHXL8ekFNSq+oHRLNFCGLW+/9xVuXr7icHPFIT4j50Lfb3BamEQIwRODr/tg\\nUegC7cVAnA+0FnAOu9ngSsIqYZwOzIfXgOb4+AJtDct4Qwy3xCgY47AGlsWzeE/bW1wUrO5QPhLD\\nniWMDO49xGQU4PSG481DDlPBak3GEyTjfe05WNsTgyEslumQaJstrU2I+LpgmJ5cLJaGTWc4zK+I\\ncY8xTcXT6QbMGoIrsU7S7No81RlnHBRFLKByZZLOWbE/PCckaDsNagGV6Nqjyi1VLaa1NPIQkZWs\\nrhUGA8pQgiGqUPED8ocwog50Nes0FYQqRX1O5y250HYt8fqKWBZEC13p14aUkENlIM5yYNefkVKi\\na9vKKXUDRSdMLIQ5o6xmOQhaDDX9o1Jx2nazNmoWNm5DZwaCvqpvnlTfRk3SFpZ4S1xzDFOcqxox\\nCLvdEUPseVY+Zdu39O5OBcKoFp0V83hDKGC7kWGwSDKUrJjngnUdzhhcMzC0msYudP0RTfsmxgx8\\n8MEHGO046Vu0UhTrUc7SO4PyQkyexm2wTnB9z+9+69v0reat9/4Ib739iPaDf8iv/d3/nft3HzBn\\nxX/4H/xH/Pn/8r+gcz2PD8/w3uN9AlG0bc+LZ8/JOfOdDw+kacEfZvx+JueIbdrquRgcJlvatuPJ\\nywNaBZYZtLPcuXtGYaEZenbH9yjRs3nQok/PmH77BX3TMx0yp3cfsHcd3s/4ZaZpIjlqNidnbPoz\\n3MkFSYGVhA/XFCW8evUcidVjYq3hOhwwgOk32MZiG0dRPXHlVmxMy5wmjunxs+EQJ8I8cHvwDHrE\\nuY7d7qjeBHyhdffJ/jVZrrB2wuaqH8m5RQrcXCeQluwj8UwoOZGWCZ13ONPTaoPJLRvzJtm8JHNA\\nBIZ+IMwNRlVPSEw1Rds6iOkKJTO77hFtc4TT22qou9JM4RbKNX6+qdW0gW7bovSwouvAGoX3seoa\\nCMzBM08Hkt+ju3rzVGtozfd/Rf4QHCJCCoFlHpGk1ryFjNUdohXKtLjmhCzCYd4T8kgphSy1Wsg5\\nE2XBxz0xLHX6IEKOVd2mWKoewZiKWZdEiIWQb4lxQVSsMfWm6tmNKRUgKgZWjg4r00Gvbr4UCjkJ\\nfgnMc6hshZxrSpQPK+LLrilXBmMVWWpTC1QVnxhDFkPMUjlCxqzS7SqYOTne8OzZB0jpEdHM8UAQ\\nzxJnjBjUemdI2XN8clatxKlwdnLGnDVf+NL7fPDBNynKsDu7h9Dz7/27v8Rf/It/gZ//hZ/nr//1\\nv8nlq5d8/NFH3Ll7SkoJH2Y++eQTXr56TVkCfp7YbAfuvfM2169fc/3qGlWkTkEuL4lFGLYb+qMN\\nrnd0fYuPASGTRk9OCe8DMu0hRk7e+XEOo2d/fcmnn3xYyUwlU/JC37Y8fPQ+Z2+/jz06xd9ekl8/\\nZv/iCcv+QJhmFDDPI3YF2TjbYvtdhaNqjVKwhAN5HulEMWhLpzRaPFBQuapaBVimiWVZWOaqRtXK\\nIbmQvSC5EpmVkSrBr4hfcjSUtMWZY6Z9Q0ypxgXEiULVM1jXVU5osuSSq27GVD1DrRhqWS9Kqqqx\\nVF7H7A80nWPb9xwNWzqnaTVkHyipoFNBS2Tx+xqKpDXGVv5onaBpYoyIZEJKdUvrE1pUPVd+gOOH\\npGIQQq7cwqHrCHGuY5ymEny0Nuy6I5SPLPkZWRZssZRcjU8xZYxeWDLgCj5orAEjnxmUmsrX19BZ\\nhxRFiDMuJ4o1aKCzhlI6QvKV/qxqY7M2KGrstJBR9Bir1851pJQa8Z6TkCzMIdK0hiXd0JdjnHOU\\noti2PakM9fmpKk5dDGVtYsYYiT4xbLdoteHkeMurq2+z2QnjXBASuSiWsGAksqiFbTcgSfHGw/c4\\n3Fwy7285v7jL9Ysb3njrEZ9++glHx6ekODP5hZ/45/8Yf+83fx2lIi+eP+fkdEPfd4Dw0Xc+Zp6r\\nsrJzjvPzC1KItJ2j0Y5mOEJU4fmLZ4Q0c3q6o9t0jIuwH0e6NjH0DcZpYops+p5cCvv9Qpn3NY3b\\nLxyWyDtf/eM8//Y3eP34G9zcXNGYiGm25GyYpz1bLejuiLYoytVI9guIxs8j+9trlAgZwQIxxrrI\\nWlcFDQVsroaiXALzsicQmcn4ENAlYRB0EWafUCYxzbcgNYQoJccSNcUKWTRGZ9qmZlnW/JMWEYXT\\nO4zrWOJMyhO53KJ9j2ocKZm1fKdqLZSq50GuqWmQaw+Nqv6swuGCtoWYbtDuom41rdB0mjHVM69k\\nSD4hzS0+aLr2AimanKdKKFsXRpFS6VPKUXLF2Cn1h5DHIBR8noEMWpHKRMoTSzCctG9htGFoh+py\\nS/WiVFK71DlnSlaE6FGmEJMixRbj1DrWqQISdPkcBZeSIhepYyUnKGWxxpBX+XGpHc+KFCsrHk4J\\nRWpy1LZt6gtXK2cSjXz2fHPN0Jy44qir8l9nDMUY+mZHSTOKqaYjK0VZ04tL0cQcmBbNxZ23ubp5\\nirMWj6md5eJROpIiVVGp90x7z6O754yHaw77W46GCx5//ISv/sRX+e3f+S2Ojo5YwsjxcIeHw5ac\\nAl/7rV/lF//tf5O/+lf/Bl9490t8+umnvPXWW9ze3rDfHyoyfduQ08KDB/eZF8+837MZLEYdM42O\\n/e2BlCJvbN7ldozsxomh7/Heg1LsdjumaaYZOu7eucezT295+folqmTml0959tGHfPHLP4KxMy8/\\nfYyNB47PH3B2fpejo1O0ZFgOzOOCT4FOGZbpUPkMRzv211f1vBGpOoGSa69GhFwySiv6TU/wip2p\\n416UcEgHxtDSqp5OF4IqLNOE1oqxXAOKaVkIYaHIgu1KBaCoQk4RYzu6thry5mVisKYi1BjRZmGJ\\nN5RSsGaoWZeqirCqIldhbFNxfbpHqQKloLWQ1UIKht3QEH3G22vaZlhVt0B1RdR+V/KQhMMUyKv5\\nLhORcg/JCtcoUlrIJaznpCFH+AE5LT8kC4MIwgJkUtJonQkxEdIIKmJNT3IOTYcWS85LnfuKRlYR\\nkv5sa0Eg5YTLDcaUGlJSKs5da8FZQFcsvWhFJuJ0S4oLKRVCSbS2VChL1hjVksXWKDclVd4afE0d\\noqxSa1AYmtax+AN+WXX23Z5NazHGkkylNhnlqr9Da4RqCsupRq+XUDAb4Xr/CUleEbOiyCnaRtAK\\nH0M1C7kBH27pmgdo6VnGG4b+iBgiD+8/4Dd+6zfomvXk3W5RKNphyz/8xtf56ld/ir/zt/9vfuwr\\nX+FwmLhz5w4vXryg61qOj05rVRQT221PijOSI/funuFcgxzt8DHw4sVLpvHANB3YHB2z3W6w1tI0\\njrZt63taMsb13Dz7iNY6jvuOqxePGYaGeH3L8+/8NrvjYx688y6Hq0tOzi44PjmnP7+P7Y6J/hJ7\\nfkxaXnD14ik+eEpJ6FwzPpBCzqyOWUCVCqkBYtag6jhYW8PV7YzRho1uGJsNOzIhgSqGnAMpeUqp\\nWaQhLIS0UJhRLq7akYSYiNYZUR5jHUkSSg3k4kHXiiKXkSIGXzS+jBQVaxI2hVRmnNtR80N0tUbj\\n6w1COjC5+n9EuBlfcsQDil4I2VflrmhS9sRkMSGhxTOlTyvWThskzmixKOlq5ZND9dQkS04GJc0P\\ndE3+UCwMCsGagtIWURND39DaM8ZpZjpaON69BRJom54pmjW+LoMydK5hzoWSbZWEmgNZaxIDbcgY\\nFxGEGBPGJoy2SBRiSIQUQQnWCjknDoeJpBPOOZzpsRyjw0JjW7rhGKMDixxQOWOtZo6ZFAIpNKTQ\\no6XnaNOjbWLxM69ubogbODs5wblSsyXGQk6ORjWVLdEoooIYRt699xO8fP0xYtdIuzSidUabSMZj\\nTEHRoHXLO29+mQ9+93chCqf2HtPlM6z0TGHm0f37+BCIFIx2HB0f8fzFM95770f48Nsf8pUf+VFe\\nvnzNOF+xP9SoNKWFnEJF0w0th8Mr1LDj7p273N7ccHbxiDiPnJ3vuHPeMy0B0zjcsOHk5ISubWnb\\ntgYFLQu7o2NMvOH0vS/z+Ju/xsOHDzm/84DLj77GZtPTuo55HjnfnbDpTrh45wso12C3R/hphO6U\\npu/ZPvgSds68fPExeZlwnawy87Ja7CtnIoRQFwJjVhVkqWM7P7NR1YYfreLy2qLchmHXUMJM2C9c\\nXe/Zbc9q1VZm+s6RjMW5Qi6erjGkoojxJYWCTwFrt1zfLBQ1AbE2Pi3EeU/KDXNcMDZidbMa+wpW\\nb8k5YrImS0bpA95PNLZDlGbcZzQzUgrTfFMJ6NojUsihsOCI2dJ6RSgaUQFKS2Km0ZaYAjGvyPkc\\nyDLW7ZPsKPKHMIlKEHL2WLvBakNJtRowukekIatcvekacolkKcj6R+tq1y65lmshFEzn0YtBtYWG\\nQBFZOX2ljqdK/X5ojcQ6AxeVyaxBJJJozRk+WoweyLngtEPbQqMcOdU8QmdbsjUkY+maLc46ukYT\\nkqexDSlmwpKIccF0lsY1tNqSMxRd0NjVRp7IJXL5+lWNHNMNiuPakFQR1wglWERnDC0inmcvnjG0\\nEU1PTCPQcXpyj+CF/X5PLoXjiwsOh1umQ0MMFXfXNIYnT55xenZGTBPjOFIkEWPF0WkMWoHognOq\\nvnYl3Fy/ZLfZYgwM/RG7E00zbDCuYdhs6wLb9Vigbetd1qhC2V9x580v8vrDb9A0Ld12i08K63qO\\nNyfooWOrO7TZ0B6fkpeI1RqcJc8jJhns5pjN5pjrcCAEoZSJfujQ1taoga6tlK91kZiWjDIOIYDR\\nWONY4kLIUIwFCsY42tbg5z2eSoUWscQw09gC1O2DdfX8VKsKN8uCxhFChgRNV4h5wgkovQULYR5J\\nKWFMVXFW674nFo9VqkJ+CoQSySoSy1BH38kQUkSywViNNomCQpuWoAIldBm2GwAAIABJREFUVFOU\\nlIQSjVU9KQTmkGrWSckImkgiSSRnWQFAoaaJ/wDHD8fCIAVEiFkYmpYQVXWalYRVFrMKf1jTlXIu\\noFdgSxQQW6GrJJSqF4GSBmuAImhlkJzJquofhAwafEgYt/YGioBZjWmAFodRBtu0+DShrAGtGNqG\\nkITs52qhNlQ+pXF0bsu271jCnpgzN8uenErlKrqKmEud42YPWWeU1rW/YQspLkhaGBqLUmvuYJlB\\nMtYoxFqsGmjMMYtfGA+v2fVbSp5YpG57Qk5c3dxSBLabHeM4cnx8wmHvuXv3Ls+ePea9L7zL48eP\\nOexv8EtApNrYU0rImuu5TCPDZmCaRo6PTtZuOsSU2RjF9ugY0YZ22NBuhqr/sBrrmhon52onHxrK\\nfIvrztjefRN/+YT27E2cH3G6ox+2FGdBNE0IiPeozRH5tjA+/5iNeG5vbumMZVkmtBb8lGgaR4ih\\ncjBrKjGlCKZ1xOArYavUqlApRdGW2c9E1TC0A0vWmEZIuWZ6WGuZlj1IQymJsCSyzTS6OiTzCl9W\\ndBQRvB/JfqKUws5YTJMoMuFMC0bVGISkSLZOMpS2ZC0441EruM37TMgRmowioiQTl9o01Rqs6igy\\nk5WpnBFTI+qMttVSn1d3sRK81wSz1PyKFCk5UQiVNYojoz9DP33fxw/FwlClo7lmTaa26tXXgJcS\\nMpILUJOuFa7amzEosYiKaKNZfEKrmiJVqNmXYVGIUTUcROwqTTZIqQy8EAKtbUn5Mz+CIFpIWSHi\\ncLYDNElXNaa2da+piqAt6Fwx9caqKlKKgrMD2rRM04HWltpJDtQMTR1rlVE8JQ8UJRSdEODhw0dc\\nvgBr2oqnMw6ndizhFqdqLoFROzp3zsm2Je2fEqOnpBucu8u9u3cI+0DJAVEW0zhSCBzGsYbkNvWu\\nOs8zb7zxBk+ePEFpjXNu1WhUK3rXt9iUmOeZ7WbLvExE7zk6P4YMOQeUSljXsdtt8SnjVr+Daxu0\\nqZZy1ziUasm2EGbP7vQuTY7o9oTx5hmSIqbd4LqBkhWFjEwHbGdRRyccSyJfveTmya+ztANKQUoZ\\nrevNwZkWUIQYEQVd1zHPtSGtdR0Hd02LKIMvB86PT8lLIcpEXxSpBFSOdF1HTor9YcH7GecUKa7s\\nhABBNMYWjOmJqZCTqlVlhBAyziUG6xANuRxQyq2vFXTUONeSk2CdIpexUp1KWaXSiRws2BlKlc3H\\nWBWVADkbMpUaLrkFRlAV3yY51f6aVvjgOeiANlIBtbpmosZYb5zG6j94tNs/i0NpjXWVl+BDnTKU\\nFU5CgTgHlsVjraN1A1o1qwuxVgNGOwwtGlvRXbZFSiZGCIshekdKqy+9VAxcSoWYMstn/QoKECk5\\nkFOuAhPlUGIISRjHkZQiKc0olWoVUypg0zmHNpaSDWHJtHZbTVrGUaTgQ0FyDQUx6OoDkW1lRWBo\\nm57b2xuULiuWbpXGmmqM6d05nTujsS1GtZyd3EOrDQM9R/YUF47Z2jPmm1s0wm67ZdwfuHPngsP+\\nwDD0vH79mvPzcy4vL5nneSVmmZU1AcYYYky1idi1FARjDcs8k0LAGovSNXdSaU3Ttig0jXOIqouC\\noCgiWPcZmq6FdXZfloDZ3iH4md3dd+g3F8SciTlguhZ1fI4eziiXLxB/SQoLthFOzu+TpolpmpAC\\nTWPR1qyakvoziAgx1vfRWUvbtLRNg3MNVrccNRu2puNYGQalq2zb++pdQTMMO/r2CIphCYF5WQi+\\n+kRSrJj8lIQUFTkZUlIkKShlmGdP8royG3Nc30OLXyB6Swo9UnpysoS4ENNUtxU5IKqG3VRuclnR\\ngpYUU62AVAvFoqRZt4Htaje3aBqkWJCKFIix5nnmUq3jOSZiqK+5lB+Yo/TDUTEopSuJKCpiXujN\\ngLGWECP78Za2O1qbjRlFh7aJlGcUnkwLWtM0HUoEYwzoFpGwQlS2Nd1J51VOrX7fwFIOTJPn/HhH\\nzql2pWOGUkdfrbbk1bIdY42pQ2VUI6gMUE8OayrSS9GQgkK6RNP+v9S9Sc9lWZam9azdnube+3Vm\\n5u7hEZFRnqnKUqYYgRBiwhCJCSMkxgyY8QOYIdUUxAQJqQYIMWLAT4AJqEQNEpKqCogsyjOa9N7M\\nvuZ2p9ktg33cKgCpKh2VkOeVTCZdc7+yz+4566y91vs+r4Gp+fHDUpiWSG9HjPNYGzDFN7FVKfRd\\nRdJKXhyYTCwKXStKObTs0GVP79q0X9WecM44N+KTxdUDUgyPj19DaTDcEiN1EyEJMM1XLpcrr19/\\nxDiOzPP8gY0JiqK3WY1x5JpICN04kmtBUdCm5Uz4zlFoMxQjtZGVaiHHBMaT1hXnHVVFKopaVogG\\nbQVVA1l1aB2pIVG7HqsSy/mKcjtkmrCHAyX21Hffge14efcVtQbu7naktxeSVt9fMO2ZUQvruqKM\\n4JyhlEJKebMc5y1XpKLFIazstGKqmp3rWUoihRXne2rsOAyeEuCv3r/D2sJyDYzSodT3g+tMLYYU\\naCIlaXmo1J5SFCkWpGSKCpRiScGyFGm6AmnXZSVRlWpqWA2VTOcca1hbHoi0/DEtgjMea4SSFakG\\njF7JxVHyQs22bWVSyyv53k9jHNskQZFiJUehxth8O8PuB92TP47CgNDbV3i7I7nMPL3glEGMMC0v\\nfPs2MQy+gVeVpQRLbzusLYRypciRYbiF7FhnwbmRWoWsGhVJFYcWoWbAQiYgAs73lLJgdME6aW1w\\nEtISmeaC6wzdKGi7x5iVmiaq9KzhiZgaX1E2tdkXX/+Wz376t6Aoao2IUyitsGokrIXv3j3yyZtb\\nnLvh/lVkPQuiHEVPXKdnut7jyg1a2oYmJo03A73r8PKAEuHu7jWn4zPL/EItiuVyImH57PVnfPvN\\nd5gqRNGUIjw83PH+7Vu075iXmdvbOy6XC13XcTweWdeVvu/JOeNcmw04bzavRGMAQKLTjsH1oD22\\nv2Hc35DFkVLi/fu33Nzc0/WW8/nIMAzEdcL3HcpYakwwWCSuCAbiDFFT6nuW48z+9c/xy0J4+o7h\\n/hP46lfk7gZ3d0v4y39IPzzw61/9OV5FPv3ZZ/z2138J1hBywnQd2vU46wg5clpWbvYHRATnHDnn\\nZjdm4Xw+Ao1DqY1hPj/xeP6K6nv00Lfja0kc7C0lOqbQ7N/zBXJsqdeVBWMTxgqoBZEtF7TecXnJ\\n7HZ92yDIhIjG+USIgWkSbFTk4Oj6ZevIbPsZxCHSeI45VUJ4zzAM3N/8gr675XqZ6d3I+XqilBHr\\nF3I1vJxS08aIpqwgYuk6QemldYG1x6uHpiiWxq2s5YcNH38URwmqYEuHlITXO6wbGzm6Koz2XOYn\\n1tQIPlp17Lp7DD1jv2NwPVZXlIpos+K92ay0sZ19VSJJoVSHKI1GWmS8rihdcdaiVERJxGrZOIy2\\nhZDkqWVAqor3DmkEDEQsSn+fsakRMfTGc7qcGnYNTUkJpx3GdPTDiDd3xOAxRth3H2NMQ6CX1ARe\\nVmswzWugdN/s3puUW5sOpRxpBWcaWt8tFW8sHZbr5YxzYPuheflDIKWIdg4lQopxg+EYjsdzW+sp\\n1SzgOVMrGzdzC9HRLVvRmh122CO+A+fJokFpYsmENWKtY1kmYmxDviWEdtauwrouZBLzNZHmlTRf\\nUKmSUKzHQIgT6Eqt4E1PjgtJ9+g0k8JKUiMv3/6O8fXHrDlyuh7R3rLmhLFtfpLWmRgmjAiddUip\\nOGNbYE8pzceQE853aGtYS+IUZowdOOxescZACWujfatMVmu7sWg3hhJDzh21tCJQaPxPQdA10ilH\\nTZ4UPKdjpAYPwVHW0DQrSNsU1BZvCLZt1UpAmQR6auzHFBo1TC8tzpAVpRTOOmq1iCgwCS/QWUfO\\nhRAKZW3JYwqPUrLRoAbG4WN8P9L1e6zp0EZh7Q/rAX4UhaH5zSu1FLQ4jBmafbqA1R5jHCHOKFkp\\nNdPbEacHqE1N1jrMGeWuiLmSSqRI3r7wQlGFKkJBmkdfC955tCi00pt6sZ1ZoXnlc5opLI3PT2mJ\\nUlpt7rVmydbaonSbxjtjWJfrpldok2ajLVr1jMOBw/iq0YL1iDd7RCKxXoh5otIuiPYjV9joVTGt\\nlA32afWeGJpmnpLQqbEMrWo0qN47jGk6/JQjp/ORHNqRovOedV0ppRBjK5haa3JOONeEL6U0DD+o\\njWxsMaYNzqoYRBuWpVGIQwjkXFiWpRUZUcSU2+eHAAjrunJ8fkdJmeUysc4TYQ1NT0DFILz7+mtq\\nf8P56RHdabh/IPUdfPUt3W6PSgtd5xh2tzw/v+A2rYRSTSCmtbToQd0IzEo1kM0yL+TS5g273YGS\\nK1naZN53A/vhgK4t/PdyPJO2qIBQJrxT7EbTqEoApRGrjdZAI3Eb2uxCqqfkjpo7wmK5LoV5rcTU\\nRFhayXbDNiZlLQmRxBpfELWSmdu/Z8qEGFCmda5IRKu63RcKqChtWgdqN2ZEboSmWjQ1a0qCkgRn\\n96A8xhl6f6BzPd617/uHvH4UR4lSC0s+YdhjNuGKc0OLnkez7x84z18SzZWKazl+0iNATiegUCSg\\nBLRT1GVANvWX1g2kSa3kkClaY7VFEC5y2mLKoIpqdm8NSiqxLKQ0U7HbOrR1CFocFcWAENdrOzNW\\njaqOmGbScqKqocXXK4M3I9b2DJ2m5IbqElUR41muj4hOWCksYWVwQlmFlBNiKjUaclpJNuFcT4ml\\nYd90JkrFJIXrDCkurFURQhtAibIQFUp7Ukr4ocdozfPzM865Ro3uOpxzG9nacrlceHh4RSkZY8yH\\n3M9S6ta9tQ1ACBFjmhU7pSYGm5cJqiYskVwmfN+DCOF8xo43WGtY55VeZ6Zry+/olOP8+BXcvmoJ\\n0bUNhd1ayZ/cEo5nfNe3vA3TUU3eig4fOps1BEQpUqlY1+L0jNFYq9HGNtAH0A1986gIeNWKaaHB\\nhd9fH9lnIYeFVFeUmbcYg0ba0uIoRJI0MZVVGopFlKUUR4qVUhQ5VaZLwniN8wql2pEslYo1unUy\\n+krIKzVPaLMjhsSy0OIEAYPaslM0MZ8JQbEuy7bl6ci6KSk7X1loYbZSfKOax8JgB4zaUYvBaIf4\\ngZxCC7X9m5hdWWvhukzs/Y4sG0rNtHw+ResK3q+R0mtqLVzCmb47tKdZqeRcqDajckVJbAOu0lY9\\nLYijErNH1eaeVFsYrtFtdbSsCa3a56RcKCLkEkgshM1jXzfCs+8GalxQ2lMGTVgTVjS5CtZ0SFlR\\nWGq1pAh3hwNDf4u3lrAmQpmQqlG6ZTW24ZnGSG322VII60pdc5NdK1jWY0PbKQO6ohAiC506EMOK\\nFduOTVWoZaVmMLZve3IR7qwlxkgIAeccSmmstZvXoDAMA8fjkXHcEWNsXYSA5Jap4X1HLQXvPdM0\\n8fDwwDLNDMPQ5ha9Zzqf6PuRZW1hN1QFJZPnM9btWvsfZ5TKLFnThSujCjx98TnjoaOuEdMZAgsu\\naq4xEUKixInOu5btWRuer4mHDM5+j/kzGGMxmwVZbQWhdTOCcR5TMrbrSKcj1+lELc20NoeZnN63\\nh0xZQW1FubbMCVUVBdXCaqSloFs1kiPkpAkxbErLREqBTrkmhDMa0FjVtjLt7wU1ZYqszGskRlo2\\nZgTtLEoJKS9AYAkX1tWxrpFCxdYWrlQlM/Stg7ueQGp7ANWiUdJB1TjjEbqWVWEC1ius+WEdw4/i\\nKEGFebk0FJsoBEtKAWsHpBZUFXbd0C70GrkuL6z5zLyeMNph9J6a7GbBTmgVcFaoue2rq8xM6amJ\\nioyiQZ4S2lhKqYR1C/OIlTWuJBK5LGRZWeJCzFODym6TeGsdXdfTdx3eDOw6j9EFv7XlioSQyGnG\\nGIc1HWN3YD+24ZjVexQdo79BZEeIFhWEmhZUSaQ1sF4Sy3xsqUXLiWWZCflEoZCLcDPsqEXY70aq\\nMuhtg5DD0m4CrZFaGLzHabspR9V2hCh437eELuu2gmFxrnUK1lqUqKbk7Jr7cp4ntFJ0nf9nxYNm\\nOotzwGpFCgtOdyCWHBa0G7FUzvPcMkPXK4SV3c097999RVKe6YtfspaFfDyRHt9i7l5TXp64ub9n\\nigFEMQwD1npc53HeMo5jYxtKIyOBQsRSt5szxkjJtXkSaFj5rvOkaeJ0PfFu+o6lToj0GPbMy8Jl\\nfSakBWUUVSqm0zi/a12L7lDiqbi2OVtB8g6dTTsC01SGEi2kisrQW4eRSuda2razBrEGK4ZlgWk5\\ns4QTKa3EkshVoGYkr6R8peRMSq1jK7VynWd0Vu3B5zKdr/TeQMlYpbEMKEYKYF07BioHmICYinY/\\n7Fb/cXQMVNb8HZc1ses+xmhLTAPT8g2D31HyyqubX3C6fMFlSiQib9//jmFvqVj6Yce8gqk35Doz\\n7IR1uTJoxxKawcWYii8VmzTVdFACmg5VEzkJOTZCsCqVGhLdzQNhBkoAMlllqgqULWRX6Oh9T05P\\nrHFGvIKcWEpCckAphx9vef/8az5683MoFdd5+nxDjhcO+g0pBe7snpcsHOPMtJ5ARi7X74d7FcuZ\\nezPwfPoCJwOHXYfVHd715K5AtqScEanEkuj6DqvvEGl6hmkOTNPUAmfWlVorNzc3TScCGKO5XGZu\\nbm4Bxd3dDTE24U/f96TU5hDH5yO7/f73zvhtNqMUDKMnx8owjg24IgXxnlALqRQ6o6hxglx4/O2v\\nqOUfcf9H/yqHyxP59WtOv/ucw+0D0lf47jeYP/1jvvr7/4A3D7ek+cLz0zPH8xHnPN471rA0knhu\\nV0+ICzkH3PjQckuNRfWbmShG0nJGKeHjVx9xc/OKj6dP+e3LE1++/3N6I9Q8krNHm8RoMtqWpiKs\\nJygeZy1TbgI3gLyu5GIQDDFN5NwESwjNzViaiUx0AbmiVcbpkZg8WWdq8izLlsgeOsadpusq1imU\\n8pwvj0yXC8t5pBSD8wM5rUjac3//h1S+pKqVw60Q55HT+YIxDl16JEKcr1QuxI1BEdYr1B/GfPzR\\ndAy1CvN6JYQZLRZvHLUIczw34YhkentDZ+zGSCjoolDablqDgRQVSnpyWVE64WzFa42W0hKoxIA0\\nYxG1CZ5qcaw5s6RCzLVV6QQkRW92aGldxBRmltxswLUWpGhKNrhuB0aDDSgr5AprpBGpS2YNE/N8\\nIkRhWZov4TqdSakJbEoNkA1JOgqmPe1qogBKC0jjQGhp+/kaBa96mBuAY4mVXhtICVvB2QHRK1Va\\nbJ3vOrzvmaaJ3W7Ae89ut9uSmNo8p80UWnfwveBJKYX3HX0/4n3Xwly1xjnHMAxtmCWV3a7tx78v\\nGF3XtVa+JHKYyesVKYllXpGcmOKFNL/n7befsxBxdseoNEuFEiIhBcrvfserjz8l5czp9MLDx59y\\nuLlvYUMI1jlKLpt/oW2FlGpHC2s7UJUcVvI6k8JC0u2sL9K6vs529Nqw8zvWNRBKQllH728aCDgJ\\nIQRCKNQqxJCoRVGSQSMgK1VWYlpabmQFVGkIeMnknJjChRDbnCqly7ZhakxSa3usPBCXgVpgMAO9\\ndU3cVFoITiFTRVFVAYl4e4u3PSFkOvfQZh+55ZV0QwUawj7FJvZKeaFKarOxCsS/gYVBRBAcOTti\\nzlQiSoFznjVGQr0Q44QxA77r6ExHpztM1agiUMxG2VXk1KSqpSaqLPhucw+KIqtEKAqqoopGKQ/Y\\nLYeyfSExQUpCTRrB4uoNpSjWtJBSZY0XCrkNnWolxTa1zjWSyoxYtWUe0hRoaeX9++9Yw8wSV2Kc\\neXz5glin9pl1QWtw2iAMCB6jO5wb6IdDU7uVgDGCN4JVCi2QtaLUjNPtqd13rb1uU+yEtU1roM22\\n7gLs5mWYpgmtW0GY55m+H9ntdnRd19p0a+n7dtQYhgGlFOM44pzDe0/f9/R9T61Npm61wXuPQEuJ\\nVlsmiFPkEFpWY4pMx3e8eniNtpYUE3/x+S9xvqBVJoUzOaxUDbJEUmqhLc4ZQonc3Nw2fYRWdH1H\\nVlB1g6tovR2J1gshNIVqTi00SFlHN4x0fU8pmVAi17SgKNyNO3b9oV1LYtHW0Pl7vLsjl+ZWTCmQ\\nUktOTyk3uLBuv8+h3WxaC0WVNptQkTUtLDGwxtA+oyzMy7klQ6HozR1e9qjSo4prTl7tsLanAnFt\\nWRexFAqFkAK73YjW7c80bTCf8rXJ7F3L0JymwLIk1hBIKbUA3JIwm039h7x+FEcJ4INzMtQrg3SU\\naltLGIQlX7DRI6ah0IwMaO2ROlOX0sJBaZFtMWVybqBVozOdV5Ri2e32PD9HtAmkZFEiaNXjHExn\\ncBqkbJmRWGrpKKXpEJxJTNOCrZVgAkknnGkFaA2NIpxy2IJGZTN0gaJgteL48shud4+IYY0Tp/l3\\nLPWJVGdSSWjjseJANeVjrzUpNgqV6/foFJEc0RJRpVDmtrLS3qOKpubA8/EZJYp+6BBlmotwSzmi\\nfj84vDKOe6y1TeegG3ew73t2ux3WWpxrRWEcR7Q2m+ozMo5jm49Y27YZSmOdIaYIKJyrWGuJsW0O\\nfNdjFFyOJ7zxzE/f4lXA4Lh9+EMmGfn4Fz2n0wu9MSglPD6+5/b+Y4pTnF4eud/d4TvL6d037Pf3\\n7A8H5nlGG8V1mlGiUNJ0HTEExOq2+6cFGIk2LU8yNf7C6DuCVPqUQArPlyOVlhdRasEog1EDnb3B\\nd694//w1l1PF9z3WCrUmlpAakwNY04pCUwpkEZyDVGeM0oSoUaaiSsbkQi7zFm1gMblvVOhcEOMR\\naTBhJUJcF6iuSZ1rizFECjEGbDYYUUgeKLlH6wlRC6q2jVeKbbNWiWiT8F3zsGg8Iv/C3Kf/2+vH\\nUxhMRnQihAuz7vF+t91gilwSsQQkuyY9xTb3XmnxX+t1RXsLuiUMxW1T4YxHSUs3UtLTucqyHDHD\\ngDUerSJdvyM9CVLL5tr06CoYfUCwFBLO9kjtiOmECopLPqNGT8qeHAthzVQTQDlUBbXhvqnNaq22\\n53jJsQ1a4xNTemphNGIwYrGmrVBjqqDS9vQXvO3IqUFsGlYfOjeyridqWFjrRF4yw37Y2tqmKUgp\\nYN2W5tR5Ls9XhmFgHFt0+7LMlKLY7fYMw46UGrosxsw47prEXGAYBqZpou/7lgL2vbJQEkp7pBbe\\nv33HMO45HV9wzhCXCWWbJ2HoLTGvxOnIHGdsEvSgWaVDjQ4lGtf1zDXR+ZF1mSh55f7+I17efkda\\nJ5zvWGPEYtpgVAlOa0qIFGmJ2LEKt/1Nk7prmlxbOUoBqZFam+T4ftyhleZxPqNEWNYXFjJ9f6BW\\nwzDe0rsRpQfuD3/A+WXi7dM3eKORmskqEOKC1cNmra/EEMmAGsxmWMqwCcu0Lu0YpgoxddRiGrat\\nNi9OJYDSlJwJdaORhfZ0F6lt4ypwOr3nze2nGJqSteYBa1vyNhSMUSyrIqWKrA0DpyQ1cE61+P72\\nB92PP4rC0OLB64co75QDJi3tiWcsNWpCWaHoBmOtCqU0ikIJVyRpllDp9harFVNQWNsTI3gv9L2H\\n0vPzTz/i3dMXLOsLxnSIhMZ7SJZaIjU7NBYrBgFiai281gqN2T4XogRO8zPeHFiWubn+NJQacFaj\\nVZs+S20Gn2HnievUWuxqSHVB1douEAzKDBQJeOVozKmm7quxoLFklSk5kMSz5CusBZVzA9YaxXg4\\nkGpmf3vH8fGJeV7Y3dxhzEi/Gzld/lmn0Pcd7969RymN957D/gatbSsUG9NiHNvcIOd2LDgc9kzT\\ndeMdNl1D13VUKlYbXr9qfpd5unI5zxitONeCJSMlYP2IyEoKC5RMJw/sbm84Xk4MRvP+6YXx4wf6\\n/sDx8khYCt044rue6fRETIW7m35L8cqMu5Hr9YygG+ZPKko3vN8wdpuBSzdKd21qRW0NThTHcysI\\nh2Fk33ker5oSAqRK1Zm+u+Ew3LEb7yFr8qvM69Mn/OUXf4ZTA0nvWJYFzIpS389eVIMYzxm331LV\\ntafETJonkhSqi8xLABxSE7kUtNKNap0hbAK7GNqcQBQogVIzOQaMiU2ElSGVI5mWuyqb50Lbiikt\\nYDelZvyqNdL7e4xxzV/xA14/isKgROOVY6kX1vzEcc7MeUTpNmkXozldzsT0Asnzavwp9/vXLNcT\\nj3Pksh65ObzhfMzc3R54te8IaUYZxTonaiq8eXXPTf+Gm+ENL5e3XJZvEdkj6kznPbkITh9QQK/g\\n7Re/5PDmF5QCXefY7w+4dc81foGWxBLfc64vVDSpaNS8xw4rxho0I85adE0YE5nn77BO4/UBZww3\\n/R9znH5DzQGlpPn7TaAfPkGrI6WsVLnS2Y4cV1JYmWOzg4vRWD9wo3fkbDG6MQI67wjLirWGTz75\\nhCXrlrewTAzD0Dz8pTDPC/M88bOf/gHOWzrfU0rl7u6WEFoGwfcKyvZ7Zbfbczq9fPAgpJSYr+1z\\nc0xtjeg67u7uuJwqOS2U9crl+Rvm6wU77CnnZ1SuuNtb5rJy+vZrbu/viXGm6zuml0f64Q69OKxW\\nPL9/y67veHj1QIyZlBM3h3vev3/PvOHtoW5oN0WNBbwihIDShVIV2awopKVq5Ybt08YS15XRD1j5\\nHs7aZhqN66C4Pbzmk49/wcP9K0ou/Po3v+Lt8y+5zIFBv8aYA5f5ETs2EZozmnmpGJWpa0LZyn13\\nQHceIw3JNi0nJjLetXxJ73XD1utEWGeiyo0JInucdZRcMaYwh0sT+umOty9fMnQ9kjqUqUjKLYRJ\\nKqgLxnlUgbi2sKWUI5fpK273f4hS/Q+7J//l3+Y//KUQet2SpEJdWdK0TXX5kKAjtoAOmE2q6p1j\\nd/MabRombF0mer2HqBj7e/ruFq08RnWcTifO1xfaqEpRi2mQFCNYXXAmY6pm7A29NVjRGJV5ef6K\\nHAIpV5zueX14hVS74dUUSjJGabx1WGUgGtYlIdIkxkU5RI1AYblf1pKJAAAgAElEQVQesdZgquGn\\nD/8KRvo2IC2aZQmk1HiSvd+hqeTQ7Lk5rRRViKoQamKtrRBel4XKjDEt/LSQOZ+O27Yj4jd9Qtd1\\n7Pd7uq5DRLhery0DY9hDbavXN28+akYqI6QUW8JWbuTqZseOdN3QYC6lftAxNFJ2IYVISRErhfn8\\nzOn910yPX7OcjtSUIbcjnzGeNQS8ttzfv+J6OqOLtLCVBO+fHtFaCGHFGUVKkZAi/WHEe8vp+Mz9\\nqweKal6M8/nM+XwmxIgyBu96RBlSig3ZbnqU7lhjRmmL8z1dN2DNQK4W24+oaphD4jqfgcLLy18x\\nTzMpwm5wfHr/ms9+9guqMShVqayIqjg9kLPCG03nhd3gcNpgrCFRkORRQeiLxzOQK5jiUEmjdKNO\\n2c7ih3acKhFyAHKHZUenduzdA4O9o0qmyhlrVqQGlnjhshwJOZKKgLbMYSWX1DI4S6GgUNZSOXOd\\nvyGX6Qfekz+G12b8SqX5+VM9k8qRJb4jl5laVoxt9KVcL3hv8L7nsD/gnKMbLEUnMBFoa5/Ojwga\\nZSzGdnz39ltO8wuhTiiTW1BMWqlEnFP0/dhYDp1Fm+aPmKcL17UNk7R0GOkZ3A1SBrTs0cqjjcZ7\\ni4glJs26VOZpbe1gbRqDwsoUvyXUF1xnudk98HD4Y0rsGjY+ZnJp3g6jPJ05oMSDOJRYtG3qtmtY\\nOa9HlnwiGT4gy0A4n69432Gd43x+4Xo9M/Q9XddtYjFLSk0A1vm+sS5oHv+cy4d1ZPOJtSdxSm1g\\nlXPG+44UIkqEsKyEEKi1EpaV+TqxhpVUAneHG+KykKaFXFakh6F3KD/gdzcohDBfqXHGCpvEuPBw\\ne4d1hul65XI+Ml2vLOtKFWkeiFwQpZjngBLThnI081cphZwyoDHW4/sdvhsQo7cf0xALLCmzhIZc\\nCxSWmtr0vq7M65U1TLx7/oJ3L/+U4+VbjqcTRSo3N/fc7T6ipAaqKXXGOrC65U1qpTcGxIBXDkt7\\nj2KIQaGKZaf31GSoVbe1qjFo3Qx6pRpyzORQIBac6fB2jzN3HIZPuRs/QYtGmzZYVDqhbdnyKWQ7\\nAqqNe5mawrImRNcWvlzfcbp+/oNuyR9HYaBuX26hlkCMCzFfSZxako9KQEFpRZETfW9xrmfcDfTj\\nnlQLxgtreaIKpFga6cZ0KBzW9MzrhW+efs378xfEdMYYRyxnlnhm3PfsdwOdH5q5ShkSiVgr0+UZ\\npx1121VbI1g9NHjGVhhQmZwUOVly0ixz2qS7bcZQamVNZ06XZ5xr7Ijb7hfc9j8npdaapxTItQAG\\nrW5aqlCxjUrlLGPvuDsccMqji8MpyzK3opBSoHOGdZ7J1YLyHA67pgRteX9cpxbSI2hub++wzjJN\\nU5vya/lAcdrvbzZKUtmGvw3yUT+4C6Hvey7XCyGEJke2ntPpmcv5DJK5f/MGsYphOJDmxLJGDndv\\nUH5HzYlMZbm8QL4wL4/UMvPy8si469iNHct0ZrffNy+G6YlzQjuH9SOxVIyydL7j5vZm25J0HG4O\\nII30JapJ1BGDMp5uGPBdj3Udznd0w8BufMDrgX3Xs3ca6yCmZpr7zRd/wdvHv+Td85e8Oz0Sc+Zw\\nuMcoS8gzqICthZ0bMFswUQkVK7pJprUh5pmSAmtKSBFuuKPQCkMJmc77psEplVxWcqmoWvHKsc4z\\nqio0lsHseLj5jPubnzQjl9RNo6Pp+m5zxZa2qv8eALPlliDNJm6NIqWXH3RH/igKQ62QisKpASMK\\np2wDtgZakEuIzaUmM8YIIc2IQO8P7Mc7tNNYXygE5vTC9bJQo8FgUbXQxtQ9z6cvmS5vuVyeyOmC\\nrg3dvj/ccbi5wdkBY0aCVKx1OCtILpzOj8hmA/duh1W68SCVb2ulktBmRSOUqFsyVojbUSF/+NKu\\n65mQE846jO253X1Gb95Qi2kRY7FSY2oHg9Sj1IBsXMm+H9j1Pff7N4zda0zquLv/hLEf0brHjzfc\\nv/mI4/GZYTgQYhu4pRQ5n46cT0fCujKO+03OnDken5tdelmaeMl29N24Ye+a9XeaGtTldDpRct4c\\nlm27klLc3Jxnht6RSubx+aW5NJUmJ7h5eLMVvgVlKkUMNTWewjQtxHXlcj4Sl4XL8xkpwicffcL1\\neiWm5iMpRT4keCkg1cw0TUzXiRQXSg6EZW4K0NouKFWBkhsbA49oh/Q9rvcUJVhjONx+jHED3npK\\nypSyUKUyhxOf/9U/5t3TO755+09ZpicGcwDfkUqmRzfAiwO0JqObcY12zXVqx/HyzFRXyAldDc72\\nHNQOVYWiWzhO7w44af/eNShKsqQ1EpaJaT62LFfdjpd33R8x9h/jfEMCVsmUEtvqNEFKlVoMOcm2\\nlSlAJqOaTNr+DZREp1zJ5YFBFNo/Mi1HSlkopXJdBKUSbggYo0kl87v3/we7+3vsarm5GXnJe67z\\nmbzOvH+ZiIdXTHHlsBuptWe+niAtBK5E05E5E3PDdf3hH/wb3N9+zPky8fbdW44v7wmiqWPmYQ8K\\nzWU9IqsmlDuQhLcGCQqLJqRArGCkkO0MxVKLZVnOjAdFzGC1QtSO5+O3vNt9ye1P/jW8d5jF8dPX\\nf8Lj6UvenT7nfDoz22a26eyOmjUp9+wPPdq0ecZ6DixrRMuet++/pXcjTjy1Oq7Tmd2+p5TI+fhC\\n192xv7un7zrifOXnP/0Zw7BHieLtu6+4vbtlWZaGy7eW3W7X4B45onTTB5zP5w9rzsv5hPe+wVy8\\nJ6wrukBaX/ju/MzDbkeazxRr8N0I9UQtFa00x+MJVQr97pbL5Zn89Mzt7S3zMgMa7xvE9xQS4+Dx\\nzjLaW+IyYXd39MZyvZ4QKs47TrFQaPzNS0iEJbJ/gJIDbrhF24Zbq8ZRdEKTYYpMywXRDkLCG+ju\\ndvS15xKWFlQ8z3jref/4Df/D3//v+Lf+zX8PW275Oz/7U3qz588/H3l//g0/ufsph/HAl19+zuV8\\nJZcZFLhOkZNCj5qn4wv2ZgQNiPCzn/yM+Zqh77FaqDFsEmbFMgl5TUQzY71mWhdWZbm/+QledXTD\\na+7sZ5zO3/K75X9q8QJ5ZVkyIS6ApkrdVtqNT1JrhjIgUrDd30BQS6mQkkLoMdWj6kiOmpwVKRVK\\n1qxXWCdYQ2GKM++ev+F0fmGNbfJcayWVSKyBJQaWeSHFgtU9qrQBmpJhQ3MPgGU/3nN3e8tPf/IH\\nvH74iCqKKbYcwm7Yoa1FeUF7zXU+ssYreV1JIbQOolSE1hH4rkfbivMNiuG8oeSN51DrBnltOoRQ\\n1hbgUhv199D/hPvxD1nCmZxnUj6yzi/UPCNScfKA93syAWUzyrR11H63p/cDvt+TYuJ8eiaExLff\\nfU0MC0PfkWJgXmZev/qobScEpvmCMZZhIzgppem6tg5s8JWAte4D5LXBXOqH40UIAUXLGy05NSu2\\nCMt0RaSgBVIKaGupQAiBvvOUsmJ0Zbe7abOKdeHu9g6lFd5bvLfb0aqw39+wLjNrrtQ1ME1noOH1\\nQmhBLc5YrGscCm00fTduw+nGTchpZXl5pJzPlGlFWYXTmul85Ho80tuevbvnbvea3g6wJVvFLX7w\\nOj3y67/6X5nWE1kX7m9f84uP/5TXh8+QcoOzr/j41Z9wc/gI33lCnhvvwhsQjfGGy3omklGiUSI4\\no+g73eBCVaGlJUW5La29cz1Dd4+Skcen9zydnkjZ4NQNVjSH/gFN3zACpbIsDWJb6yZ/VnU7blQQ\\noRRAdZTyLxnUIiL/lYi8FZFf/t57/4mIfCUi/9v269/5vT/7j0XkcxH5JyLyb/91/hK1Qo6C1J6a\\nPaU0tWKOhloscVWEKbMcFddLJoXM0/OXTMsz03Qhx0AugZg9iUItM04rcjCUYLjZfYSqPb3qyUWR\\ncsd+d8fd/SsUiv1uz2F84OH+I2JO7Z8lV5KyFOUpEiiSWfPKFK/My4xURcgBlEJw7A8HDuMtVQzG\\nC0pXapYGQt3ELH405HVlXs/YzoAYSjZ413E//i2m0JgERhxSV1I8Nt+Ie4Wx91QZSGVGKUF5hy4V\\nvYmOlmmmpLbS2+93HA63lJL55tsvuV5OLRqdVrRiDHjfsdvtt3Wf3lDkpukR5nnjMZRNzdgYDK2t\\nLzjXzsFhXTdmRUWjybVi3UCcr5S08nK+ENczpUzEdcL4PS/ffYWWQlZgXMe6tqKRN2Lz/e0dKTZo\\nTN87jO24zhNrakVIpJCWhVIyx8uVyzS3eZJWrPNCDCspLizLjFRN53tU72CzaNvuwDDsWOPEdDqh\\nrcX2A8OuR0vLpcw5twcJml/96n/ht2//Ceuy0FnFm1cf87Of/h36/gGte+5vf87rh08Yux034z0J\\nmOKMUyOD7VjKxDFdkAohRtAJ8kpRkGuglNAMUFoYdh03h0847D7mMHxKiCvfvf2KsCwQr8Q5U0vE\\nsYOoqVHICQSD6NSuuRoQiSAZbTTVQlUaZ7ofVBj+OmXkvwb+C+C/+X+8/5/XWv/T339DRP4E+PeB\\nPwV+Avz3IvK3a63/XD1mrZXzKTA4hVYDRgfKEilFQ2w5EDk3r3oq4Dvh5fzMlB4pFeb8yBoXQsyU\\nXFnjgr01lAzTHPG9o+v3aFtwdo9RTdzU+IyGeYqcz2essox+RJxnWhu4Q2uFKNmi4i9QEp2FWAuF\\nBRjR1jZ7sL+hcKHkiMKQsuCNY9fvUAZs0EzzO8LyGUoSikLYeAc2V0b7Kev6DmcFZT0xFUxJCANO\\nPNkCppL1Qkgv5GxQ54oqlsvlGWsV18sJhUcrxXdvv2YJmXE84LftRNv9b8VkWbdoOfchRSqGRiyO\\nMW7AHEeMkXWdOT6/8PrVq2bdThmNcDod8aqyLhNDZ6nbpD/GyLjriSHRKU0uBWMyqvM8P3/Dpx//\\nEc8vj9zd7FDWsF6u1BKwSnN7e8s0TfhecXM4EEJgmieuYUVXwDr6oVmiQ1wpOaFkbBZrUZQam01e\\nO5SyFC2Iarg9JYWuH/jk/jV/8fyC34KNhm7H3T6xThFRDbeutUbqlV/+8h/wi0/+Nkl5xq7jvjyw\\n6w6cz890o6FfD/S7A3WKjNqzprWln1cwynKdrjzZiV0StHeEMKNKO4auaws5zhTubz9jP/6MWpuT\\nUz3uECrfPf6WXadRojBWcN0eXS050Ax3JaC1+eCJySVjRNoDrQxYqR8YFX/d17+wMNRa/0cR+cVf\\n8/P+XeC/rbWuwG9E5HPgXwf+53/e/1RKGyYtS2R/6xhMT8yJl+cFXT1IoORCSZUiS2uVlGdaj4hZ\\nCeVETAtSK7U6Yg5cpiP3uwOUQgoOZ3qsyYz9Hd7tmdf3LCFwvV54/+6Ry2VmWQPWyhY04smoLe1I\\n8M7x/PJCqYWEglKItUK90Js74hrx3qONYHSbPudVobLCYxEM2jtO12+p5UTCoNSM0oWaBa0d+/0t\\nl+lCKZXOHUg4rtNKiUB26LrHGSGaMzXBmiKDznjb4+wbvNctlcgOLFPgej1zuLtv4bubQg+EGMOm\\nUTAfTFPte2hYOGsbocltbfr3RwnnHPOy0I09SHN/LpcZOzTC1TxP7If2ZFJbxyFikc5BikzLhJdM\\nrz3X9cRwGMlbuK+zXUPG1YjopsqcpxNSHcMwYr3j/ftHYoyczxf2vSPVJh23ut0M/jASI2gEpRyg\\nWkhxiiC6PWBSS8oOYUWLpqSAtwO7cYeuwqk0FkJQGWUNrna8ffdb3j59x6v9G4yx9N1I5wslJmJe\\nUabD+h61VIzVJBpQBqmIEqrSfHd5xjqL2tSly7xScmGJK6ZzlEUxjnccbnbEUBpNTDnGcaSWiSk8\\ntg4uG5RxjRK9FnIKWxGzkOuWnt1Yk6Vsjkot1Pz/X67EfyQi/2g7atxt730KfPF7/82X23v/r5eI\\n/Ici8mci8mdxLY1lMJ3pnGewHXf7Aa9da1OlZw5rc8p1tvkgvCeXRKmRQkRL22k7Y+m84zy/o9pM\\nIpHKBBi8caiq6GxH190xTQtPl295+/jI+XKmlqkBVnRqBibTkFhWa6w17A97jOlIVQi1ghZimVnL\\n1ByVpWKVadgviU0Xn6UdT6TxA8Q0JNy6viBlRYcVpypWHFp1jOMbtH6Ftq/px4/I0IrmvJIT1KKx\\n3Y5UIn7o6fsduQh+GLCm4/7VA0/P3/L89FeUPHM9X1HA+XRCa8s0XTBW8d3br1jWK7vd7gMUNoS2\\nnTCmKRy/1wiU0lSQve8gZZbLFbQ0KbISFBVnDSkGXp6eWmEs2wpU1i0Pose4jnlqJOOySZlhY34q\\n1YoJldP1iVgCxg7EnFsOBi1F25j2fbycTptl3OK7Hd1upO9vcJ3CDQN9P6K02wKDNRITEgIpZY7n\\nE4+nMy/nJ9Y5E9Ky2dF7XNchekBVT14iVfcMw57/83e/4ni+kmJ7Gqc1cdjtebmcqa7QDSO6HxHT\\nVrwt7SxQ9YqyhVRWLimw5ImUEzWm1jGkiPGNGzoe9gy7Ees0YhK7bmBwnt4PPB+fOE3vuSzvEVU+\\nIPJjqBgzsvN3dG7EbNSznDOltDDmkoUfmDfz/3kr8V8Cf5e25P67wH8G/Ac/5ANqrX8P+HsAtw+u\\nkmdyKuzGB6DnNFc+/Wngu/cT0znRG8/Ye3w/INKMLFoUvfUgO6osJFa6Hox2KAvfnv4xpvbs+luc\\n85RisM6Q8oQWw7wmvvzuLyn5z7BO03U7RnfDdF034c+K5Oa8KyXiNbiDp+K38JULFIeWlbIuXELF\\nWYUyhbK0dd5LDKzlyC56jG2rzXfP/zu19HTaI1rz8vJ1Y/rFHda+phssY3+DqpnuYeQ3X/9DjPHs\\nd6/pnUEruHMdSlmqCNqr/4u6N2mSLEvP854z38Gve0yZWVkDuhtAA5wNpoWkHfkXtNQv0EI/Qitu\\n9QdkMu4kMy1l3Mu44AYSBBIgKELV3eiqriEzIyM8fLr3nlmL490SzWQEmgajdfuqMioqK7Iq/MS5\\n3/e+z8P+8ET0Z6wc0SpTqmHc7hg2t8Qc0arwf/2bP+XHf/h3+Msv/x25PZwiRZO3vLy80PcOpQQp\\npSvdqbEg/epJQXCzu+X9u++4XE7M4cTOdnTW8v23P6Ozv6RI5QYIKRltBkJcsLbl9pWsvP38x8xx\\nhhTIa0A7h5RQnKTrR4buge/efcv+ec/bt5/z9PzMm7sH/LxyWeb2tSnFq1cPLOczFQG10g9bQvIo\\n2bXV4yARpkcqQw0rVUEKpdGrS+X13Svc91/zcji2hOhk6IbCdB/JzzN+UW0edUmUmvnXf/ov+Msv\\n/4L/4o/+MW/vv8DY2uLKYeYyN/HxZw9/yHl94nT+ltPzR4oQrElQ0oLtK1V6nN5QkieHgK2CkBMB\\n2N7eop2gkrGd4rSfOV9ObHcTWgmUrsQyXwllCzFEauzp3YRIkuoVVvVUeSIiiIshnRumLpqIs/8J\\nmI+11ve//GshxP8A/PPrL78Fvvj/fOrn14/9B19CiPYNGWb86nFdh9UbLpwxxjAMiuWcW+SVllyT\\nOSIqlGyhtK59DqnJZwyI2n4CWa2anEZm5pDo+4FaB5D9taa9sPqVXlZ6NLvt77KbBn76zf8JRULV\\nDQMn20R5u91eq60CbQpKV6iJTgJFk1JodVkMi59BKC7hzJw149Q3rHhJFHFGZIFVjlRnLqcFIytV\\nOsoccdqhqkJKTUqF0/xCDIXb3R3WGIbbV5QoWNaVuiRyzBjVkcLM7u6GdUncv37D8XhBac3z4yO3\\nNzd8/PgBqw3qinmLqen69vs9xtxTa7lyIVuSTl2Vc5vNRKnlOoBM1JA4p4TKBaXa1dX7FSMzSjRk\\nXkOsZZJfgYZoS7XS9RuCP2FN137Pq1IOIKP4nd/7Me9+/lecL2c248j+8MIwDnTGoGXlsi6EdWW7\\nnbDKIJTGaQUCKqrddJAoKZHOkajooptGkEKqidPxQk6pbUNEpeZKMVBkQhqocybFRC1QMtSi2e+f\\n+Isv/xStJRoBpt2YahUUroWtIrB6g9YeH86InEi5UpNA7QzIK/exJHKhVapTAJsoOYBotq/G4Sg8\\n799ze7dFq9wKhSSKSFRREEITlowWmmQqVjV9X5vJVcqqULqiSrnKlv7mr/+og0EI8bbW+v31l/8V\\n8MuNxf8K/E9CiP+eNnz8MfDHf93vJ4Vk7DTn8yMfPrzlk7c3SDq0GVEqYQdHCo2fWFXz1klpiTld\\ngyxNBLv6hZw81hmMaoGkTilCblevtZzZn58YbDM5KxQlaXIoyE6z6XbsNq+4nX6XP/u//1WzLJWV\\nlAJSZcYOXr96hRSKkBNSGeawYhSYFFnjTI2FHDOxdsTSBnm5evAJYQWdtNiuEKInJUXUtSnLFkmo\\nR0wZMUpxKB8Z+gEre+6mT/nqwy84XxaEeOHh/jXVWpTM+NkT44qzG2qEN28/I9e2T48hcjg8orRh\\n2GqGzcTxdIJcGDZjA6uUzLouOGfwoQlRgF/RpL1fkBJSzqxhpd+MvHx8bJ+/rtToWeeItYJN7zjt\\nH4ERa01jTMqelFaMvfZHakKK2irWfmEYemIsCBnJpVKSoIjC/avP8OsFVQulVla/4M8L02QZtyOH\\nl2dS1Nzc3JEqhJTohomU23xDSAOpULREPdyBDyhtGK1geU6c1xfW5BvWT2hqvtqglMQ6g5SFkiup\\nSGLIpJyRFd59/wuUhi8++QHGKDabDRt7j48Xas2IbFG5RyZDzT0xLYTgKShSSUQv2uFQFfnaoqUu\\nBH8i5guh+DbXio673ZazPzQAEM1SJYTCR998pMqArKzRswaJNg5V20wohZmaLGsEFRu+8Nd5/bUH\\ngxDifwb+CfAghPgG+O+AfyKE+CPao8TPgf8GoNb6F0KI/wX4t0AC/tu/biPR/rmCkQKjBCUW1iVi\\nO8Pg7ji7QpcVeRwhr/RdY0BmkUnJo1JCyp5aFUIY5vXM7Fc6YemsBVQDo0rJGg6EkHB3PTK7tp6j\\no2SDUZa76e8w2tdoafn09g94PP0VxUdS9mQfGZxtjy/DiAiRkmc6IxBqaQeRhpIUJVZKajKVUj1S\\nN0GInC22ryAyVrXc/ZIkskAmcT4cGWIhOK7G74F+85ZOCiYzsT98xBjB+XJkCYrbweDlmao0h/MJ\\nhWKKtzw9v2fcbAlroBS4294SUmBdGnex73vEVd8WYwQK4zgyL+emUrtO5JdlbpkFKX81gFzm+ap3\\nDxiteDleUEYT/ZlQGk0qpYwx7WDZThNC9qSyYqXEGsfi2zd6zoEQJDfb14RwppZWkfbHI273QL/Z\\ncnl6ZHt7x/nlmagCP//6a3bTDusc82Vlr5/opxHXbRHWIFvgDyHbQSRLJedrHUdWOuvYbibO60IM\\nkIpCmcZpnJcLg+nIzjNuCpe5sCwZv1wgN9u5krDfP2Kt5pOHN6To6DYblB45HD6ilGX2lRQEJZtW\\nMMuVdPVTZgIpSKRqJjMtNTIXQrzg4wW17NmMt8iQmKaOomaUiKja/CmlttuANVcrtlRNumME3heU\\n0a20VhoAuM16CiT/t3sw1Fr/6/+fD/+P/4HP/6fAP/11vogCVF3ZTtuGfs+CHEGagc14iw4Ziifm\\nmcHdU4mUC4R4acIZF5C6TYRbLz+gbSVmwboklGlEp1giOb+w+jMpW0TV9N3E8fCR82WGCs4OlOS5\\nGW94OnX0vaGWwiVVBBPZC0Qnr5r6C+irm1AKlNFoccvT/kApoV0xc8I4Qb6yJGt2kAJKFooF4Ssh\\nJWqVxBC45APVB4Sr1DKyVRs60WFqbV2R65pUoohBo+2W+fSE0pL73QMvh4+ENLOVN2jr6PsJMNS8\\ncNg/IWTbRMzzzP3Da7rOXotcTUTzy/4DNNGtEKKtDl1zZ7ZrcETViJCa3bQhzBfmec+aWgBKXUXD\\nSFoTs1OI3CFQDf7S91zOB6Zpx7p4DucnrBlQqjAfDtxud8yHR4QbQFT8MrOZtghh2fiF83JizI6S\\nIjF6zGrY7HoqGak7ULkhwRDE8wV1heUKUUi14oTifpp4e/uGb5YLjozoJuZ5pZ8MlTMpJfrBsn95\\nwoeAKQPIhN1uMLpyOD/SO8duvEFSUVKy6XuePj6jtQVFu4kKg6hcN0KFqlq3JtV2WDQfKigpiPHC\\n8xyvzEyJ6yQ3qqfIxpasxOu7RZJCun6sYJxC68bprOR26GeBqAHXddeh7m8l8xEQhX6wJHlE6Lbq\\nyTHTS0fnRoxQ9O4GZ3uc2jL2r1vYKF7IJdEZy3bqsE6TcuQyn5mXlfMcOZ5XLpcLQml8WjnMR9bl\\nTM0rTjsKsFwE373/GUIKYhbEENCqoAW4zuEGR0yyraiWSIkRaqYkiaiKmCUoT7/puX/4nCJAiIDS\\nGr+IFuoJgRwjOSRKUqArWieq6EAUNBktG33Jx4U1Bp7335PD2uYCSlNFbYNQoyEJpt0WN3bc7G4Q\\nVfHy8oIUCmM7+t6xhrmZltZGgfql+NU6BwJCbBZxgGma/r1n0VLqr/5e+/8koEasLvhlhZSotbCe\\nzpRScNPmamKWTXTTGZSRpCwa6l3JBqKpFW1bFXnabQkhXb0Mke39Ay/zmfHVPTFd2L8cOOyfObwc\\n2G0nJHB3f49PGakktdQWiFo9NSZqiqAECAtaI4wkHz9Q44pQFrO5wdiBHBJf7Cbebno6u6HStjrW\\nbZm290zTPZttK2CVbMkExvGGqZ/ojcNKyfHwjDWbBgaOEac0Q98s6FlUlDVoqXHS0SmDEG1t3g+q\\nvZmNRkgQXDdByQOeD/uvCPWANppxc0M37HCDZTNNCFGbzk5khGg/9JzTWNsw/02MJ6gqgCuYPmL6\\njHW/hV2JAqh+gzA9D3d3HE7fYuyOxUec6Rltxyl5ejs1zVdV5FiYT4Jc9lTdc7OZmDaSu90Njx8W\\nwgrhnFntitGOohPVd40MHCOjK6R6YbORbDYbzi+en/z0Z3x4+mc83L2lcME5jxNgSHRdsyM/zmc2\\n1xyANgVtM8pKdlPPbvsHyDqym2AzfML//uf/gpwbE1EKQwg1oYYAACAASURBVDdmsjgjxAZhoBOF\\n4gI+wJwKn9xs6ZwkyTu8uNJ8Mnz48A0Xlag1o2SPFpk///pf8sNP/yFh7RB6y+H5IyKc+Lt/9x8y\\nX2beffs11mmm7S3juAWheP/xHb//B3+PD++fefXJA8aYKzRVonXP/mX/KxNVpa0oQ4yMmw2nw5nX\\n93cs+8riz2xcx3p+4dtvfsYXn/2Q/f6Fl6cnpnGLHTtKaSbqYWyC4SatCaQSEFVgjSL4lZIr47hp\\nq1xROB0O3D088PLxmbxk3FW4u/qZx8fv+PSTz3g6vLDZ3pJTIJWMFZWaErhbRE1tlw9ILWHcIHQP\\nslJ9Q6Hp7Q0DlfX9N9xME/jC++MRXTsm/WOGDazjI4Prke6vePNmg0337HYPKGVJ2VPJFHHiy5/9\\nMfe7t1AzUiRi8SDX6xYgQ9VomciyUGXjPhrVoWVBG80lHJGyRxdBSCs1ezqleXleGAaLRDJ0WzAW\\nkeB+umN//nfXhGNuIOESuSRJKgUZQVXN/bAlU9ju9iitWcNvI/Oxgo+JYYCqBUIZ9pcnKBKpLT6t\\nBBKkld7uiDmxxpVaDTlU/LpQRkkRDmMkU2dJQrGqiKeQqkcXkFgsA9oKjMjNHKU8fdcjNxvOLy88\\nPy1cLj/l5kExOkMSBasbiCNHx/5lQfWOWjUpnLEiozFc5kTXHZmGu4Z6IyPEL3mPEilWrM1YbcjF\\nIUtGGpqOTjaSkxECQ8aaLQqL7XfUsPC4/znVghUVJyu1pMYOePkZn4x/0OhNRqERPD9fWZJSMW1v\\nePf9d4ybwLQdGdzQjEs5XPsbmpubG/b7A845rHG8vOyxpkcKQ04rzhhylugOtAZnFX6BmgPOdGz6\\nkZoXpmliXRe4VrYlTaDi15nObRq6D4XWkhQTMZSriWtmHG/xMbUh9DBw2r9glMRsHJdzuxrrapiX\\nCzkGumHkeDw38I3usNZQhEAITVYKERbQkKSh5oKUCtUov4hcqcsFpy3ZjuwvL7zExDl5OjvS2R5Z\\nK525YRoXpu4bBlcJF4PpFUZadG7xZr8GQlh5plGYtabh5FShCtVsZjlTRSbXSqmtWi8L7VFHgBKb\\nq+C4IGppersUkQmWeEargbwe2NhXSG0oufVxarZEZkpZQAnWc/tzSqEwxiFRhNxSlZLCrzd6/A05\\nGASC6DO9aa5CoSRPT3uctO0ZWTTjT4mROR3xPjL7lZoEwmjSOpOSxyrd0nxOk9F02rLKwioy+EyM\\nCSUsd9MdKXmkakOyXT+B7eik4bvHb7hcKm7IaKDbGKxpBRiu2LPzekbLjlrBF0GfM8YYnvcf6e1n\\nrCvkEhi7jrC24WdVCiMSSjWHRi3qqsQDcX0mDjlihKa3Di0mnNmA6fkgv6aEiLWt8YAAIQN+fSEN\\nnigFbjvAuUlSV5+Zpomff/0VvRtxxnE6Hbm9uefdt9/x8OoVzjrOxxP3dw+AoO971nVFK9sgI1Ug\\nhaTrLJfLTKptk9K8DIbz8RFnNOOmJ2ePsQOlEw32cl3DbbZb5rkNMBtIVkJVaAOH44lx7DFGcTye\\n0FJTSiXniiywpoSTGu0c67Kir2/8ZVk4Lmd6O16frxsTowpawhMJukdQUVpTUVAvlCVSl0srgaVA\\nRnGz3XKKkYM/42Ng2+8QouU4KoLebtl2W875PTEuoJtopg31PLl4+m7E+9gs1tlf4+Xq6ndIV7Fy\\nm9LnHFljIAuNwrRpgexwtiJVIaWIJpNyAVE5nA+MQ0RmiQ09ShpKyU3dKFybj718z+ojRvfUXHC2\\nQ1SFVpaSFClHchGIMvxa78nfiIMBIRi0QcRCLxSr6lHFUVlRRaLa8hKrO+I16agVyFpRRaBx+Itg\\n6wYouim7tCCLiDSGsRNcjmd8bG/K290X5Jw4zl9Rq+L2dosRO6Z+h88rH1/2LGeBrgLbW6yxIBJL\\nSFhnqGv7JkgIZOpQMlOxzMvKu4+/oDM3rGtgsM1ZWIrA2gmjEtpUUIYQV7xPJFHI2aOdYJ0lImds\\nzSghcVpTAnSm5+JLa+ihmz7OWYqvzOED0n2GU4qsBDUESsk8758RZO7u3lzZkm2NdXd7yzwv7O4f\\nSLFyeHlpP2mkxPs2uXbOkUtBSNC6gVCin1kWj7aWGjXzciYniZbga6EEz3a7IwZPjJF+6DG6o3OS\\nZZmJsUXG52VBG8Fm07Msns1mg/ceZSQhNjz7m7sH/HrG+5mcmvT1cDwy9j0pJgSFxIIbeyBj+y3K\\ndFQpyd4jNyNVCESuCGWoyoDrEUTSsuJ95JRO5PnE3XTLxYOuR2Jua1lBbpLhsWcYXrFcTtjhQszN\\nYppLBLlgbEbJhM4asmVdPUUUUk7XR6nQiOY0MlaOgaQT1jiijw1YK8DqDcIVugI5rsS4EEtl9QUl\\nV/pBcz5/T9cPrcSm7wCHv3jG7gcMXebd82OjTZc2MBaiOTOjV6S0NHbIr/H6jTgYpIDdaOltQsst\\nVp0Zh23LhksNVMgF5STW9GRAlZnFaCQJjUIWS1wVuh/bf4hSMbKtAUsSGCOufMYRKQ2b/oFK4Dx/\\ny93dPdl3jGbDZfkhp8uFVGYqhhILfi1Y1xpqPgS0M9TUtOuUVhc/XY74NHO8LNxM95QEShTs9Q0r\\nJQjTbgpKGLTK7adclgjAdoXzaQUq4vzEbrIgDYUCCWQVON1hhEFLQVUaoQI+X+hlwNgbnFA8Pn3V\\nbmAhcv/wBm0Ev3j3NZ++/V1O5xllu+ajrJXtbsP+Zc8PfvBDnp/3jMNATBEpVWMtNMknSiu0MnRW\\nEUNGSIVAY6zFX2aEVLipw68LrusIJZJiopSKkpp+GMilsCwL2ih8uPZKlKXkllTNuZKuFuvLumKV\\nBR8xRrGuK85anvfPjF3XbN01UajUnJg2d7irY1MIEDHA3StyEGjdQk8iQ5UWM3b4OlPmTMAQkWQl\\nEabFuOfLBdcpYixcLol+s8HFLf7iyXkm1EhFkmqrvyvlkUphUs8aV3JJgIKSEFXifUI7gdKSmtp4\\nMJfSbhQ1kmLm7uam1fdlpQpLSTMxFkoR1KobqVRXQo6IXFF2YuxvoYPT6ZlKwNmeFPKv6gIIQa0R\\nv1oyK+W30SuhpGDoDZ2WSFWIqTA4yxIGaoqEUig5M0fPXd+3XXs/wfkdJTcZxyR3lKzISeNzu44N\\nSmNUJiCbc0Jc6K0h+0IxFSEMPs9IeYd2FmUMr+5f837/LfvjmZgqcRGcUqbrDINT9LLjWBaMHpqN\\nWhhKkczrypoyQp4IB4+THbZalJ7QwjTzFYZaEom5rR6LZDNM5NgUak/iBVEU2T+DMqRa0RiEEogE\\n1vR0pqfgiXJGO8ecAn054UuPDAuiwjKfeX3/loJmv3/PuNlxODzT9bdtVbkGum7g26+/optGQlyR\\nspmtS47kFPGrZ+gGYkhI2eAtNWfWZcZqe/3JpVs1PTf+YEoZV9vQL+fEGtZrilJfC0cNKd85025U\\n/XC1Yqmr3VwzX5Y25XeGpAyWZuO+HBLDeMXY9wPJL1hdMcKQVk/eVpTRjdgkVPMq9D2lesTRI1Kl\\nrCtZKTb3dyil+Om8omvHMMLQPeJL4Ti/sO061nRGiR6lMoiCzIaUFnKJ1DJAVaQiGcYBWSzBQ+8s\\nIbfHh7BGTG48RkprTyj9y1SiJ6TIHAsldAgR0UKSayUnSUiVSGmlK1OR2lJlJCSLlROvd18wjQ+N\\naL15xf7lPTEEsgiEfEbKAtVSa+a8ahAjRv4WbiW0kWwmi0yCEo4IEegGQRSwxMhl2fPy+IjpRnKp\\ndL0FKbC6QxhABJZwoooK9HSbe15eHnk5HMjGYa1kKZIlZb55/JJhmEjHQChHTscLz8O3bHpJmg1S\\nF37/i9/h63eB6C9czivagMw7htLhBoH1M6WcqNVQsyTWys3295hKIJUz3h8QJbRASnoG6xCiw/tK\\n9J5SZ8ZhxA0NpNL3DVzy+58NzLNnvqzEbHi5NABHUJW66TGbvj1iCEX2R7A9yXu+f7dQd5WtuAHb\\n01VFuqZ6pvGO83xAGMX93S1PH/e8/vQtT89P/OiHP+ISPbUWttsdL4dnhr5n//yB7Wai7xQpBmrN\\nmFqQtWJlRdbE7uaGKjNSCTbT1NatumVIlnllGsdfsSNzqc2hmQvzZU+tMA43bcXpHA1me6TrerbT\\nhsPhwOXdM9tpy9PqW8FpcJyP4Zpq1NhxAKVIsm1J7NIjph1aGbKWqHCh1kIuzQhWc0JYi7SOl8fv\\n2B/3bK3jvMBoE588dLx7+sBL+sjylKkkXB0oJ4OfJ54/Xli8pORISocWk6iKXt8xjBXjFgaxEk6e\\nnDKjamtz1ReqyWQbsUqi0sLiFc/HGWte87D7Efc3P+K8PnM8fsnh8shaC9oaXk0KMGhtKFmio2C7\\nvefz13+P7XDL9x++5Xh+5nA6o20k5rmRvpRqEX7nOKWJGHv+BjnDf/89+bf/Nv+PexmnSUFzOXwk\\n04ZUm74jJ4OPlcOloPyJIjLbuxElm3ykEDC6a4Wl8JGuMzzYh2upZqVURQwKHws5dax+5un5W4w+\\nUvTS5KHA4fAdNTYkuEQwDRtmUfHh0ryUNVBFbAPMClIIfFpa41EOODug6Kj0LFIQ456QV5SxlOSR\\nSiBkc2TO80ynO8zQnt/9OjPcquYbKI5Nf09aHedlxUdBMYqQV5Y4o0xPCZmoI5lIlc2gvF/eYbsB\\nVQSq6+j7DdJZkm8eic8//32WxZMorYlZSitOTTumccP7999x//CKw8sBUSpSwHw5ME037Pfv6TuH\\nJhJKJfi5RaI9pBi5zIer/3KLX2ectY2TsSy/6l0k39gPUrQyWUtcZvqhI/iCEPD09J6b7Q23Nw+I\\nkvE+YqUgrzOPxxfWdcYYR4gL07gBrbFmwJjm7JQ5kyLocSDPZ2TM6O0D+aKQTlHoEFKxvX/LcT5z\\neHlHNhMFj5Ae5S6E80fSkpHSEINAhMIyZ14OsUW3RaWWer2VtNDaunikXgjhCCIgZU+hNnWcVC1w\\nFQN+dghViQSs3fLq9gs+f/UHPNx8yg/7P6Kq/5Kf/+LP+cl3f4KuJ6gGrS1LEuTSI0Rino+EELn7\\n5A5TJfPlSHw3Axe0iVQaUDkXQRESY9p8o3UH/uav34iDodaKv/INTsuKsA5tDFoOJNUxdokQMipV\\nnvYnqsw4pzFOI0VzTiavCbVwnj/y6u4VxnRst284nY5UGbDScT4njNQcTo9sNokUUyNC51aYmY8z\\nQhg6Yxj6XUtkSsjVI7Ql15Z3LxWEasWfmBNVNGWaFD21BvTwCYdTk/Ea1TWhbm7aMm1AiYEcNeFS\\ncKNBVcf5uGcwEuu2qNpBknSuI9cToS5YmUksLClR0pncVZRaqHKliIXZF4J+xd2wYxi3kNr1f108\\n1Ia+Cyly//CKFFu6MZbCZjPy4cMHNtOGEiNWSqRuOYR1XZi2t1inkaVdxbXShOQRFKy2LKUghYSS\\nWeczUgr6fmgAkmt5p5WxFEpIvG9QkZwTQjZniLWGdQFrDKfTCakU47jB+5V1vlByQasW4vklgLbW\\nNhitCLRzaN1RlW6JSwzYkUKhJo+OkWqu+rhSkQjefPIF3yxHPsY9MS+EeGYJJy6XM8sckIwM3RZV\\nEyVX1pARpQ1pxTWpKIXgcvH0BMi+WdmlJymFzw4lDUoLQlnJaKroUQmk0hg9QGn4/rdvf4TIlZdD\\nYOof+OTmc87Hn0GtpGwxtedm9xpN4Wn/hO1G1mXB6p7RbVCiDW6FihQ8JQlqDfickSqDKiB/veTj\\nb8bBQCWJzCoUXjkMFVkz2/EVixeUjW0xzxQhWU7Hhbo1SG0xorDpN5zWgig963Ig50zveowZkVge\\nz98hpEBgcChSSCAXUlQMV1qTkQMxL6QcoU5Mww5r2oAwJImxAxWNtY5lCSgSUFsov7Z+gDWq6fPY\\novWOTKAW22QtKjUJqqwITBtcRk2cJbFANZIsmjujloq0CishXBVl6HolWAVCfEHLwmArqZxAGiqG\\n1R/p+7do0ZH1QvVtFdz3jmVd2O3usVqTQ6A3Bp8Tq2/FqaEf2D8+MvYdUuv2XC1rsyy1MR+1rDi9\\n4Zw82iiqbNf0lMHWyrycmTbTrw6C5sKMv7JFnU4nttvpV/HrnDPHwwXXGbRuiLtYV87HJ9z9K4xR\\nfLisjE6zxhWjG4ujWbIMpVZyKRSpKEIhikA53b6urkcIRQ6JJAsqR2qmbfRjIGRPle3P+fHwHmsF\\nVo1cXmB/XHDGoIWiNzRWp07IopGyJVClKM2bGhJITxEzpXqKFsTkQWsSAicqvZRIs0GoxoLMsdXt\\na73wfPiaef5HbAd7xe55whwRSeAMZDmwGX5AVhpyZnAzqqugGvPybrfh/mbH4+n7lvasiVQiwUfm\\nlFCuImT+bZXaVnz2FGEwZsSZ0hJfbkMMHqstUNGqI1fam09opEiIVJFFsh1HdFbEpFnWmc2woxTL\\n/e4HnNJMiE90bmA0LdVHEThjMKYj5UiRiSIhhkqqTb7i9EDBgx4pmSa1iQJnRhb/iKr/r3T0dH6h\\n6xJOWwQJKVr4RouRHDXadGA6YrgQy0dAY8SWFAU+F1KopLTgeoHTEKNnWc8IoZimHfNyRtIeiVJV\\nED2rFyhXmxHLXg+nzjGfFy7rE3FpXIRKYTvtMEazLBdevfqEHANKiaZIp3I6HhCloJQkpqZ5G/qO\\nsJ64nM58+uqBl8vH6yous5xb21Ubg88NoJpSIqbUnIz6epu7SnB/SYBa1xXrHPF6eBjTvJnTuEEA\\nWlRCLhz2R4wz3N1suZxPCDTn00zXO6iZlGN7HNPNUSl0u9VUaag1gpBUKZDWkDvdKn0IVKn4knl6\\nfkSryuo958sLW3mLkzuOz4U1WNx2gy62HUKqXEUvsh2WTrY1ZG3+Se/bCh0hCKk214jyDEpyv9lw\\nN/YYpTmugWOszLmS6sp5WVjWI19++UPevN5SZeEcnrnEPbIs6AqqjmipWqqRFaki7z5+yd1nu9ay\\nlAmjm/hZK9ecqCkSQmqV/TjjekCGX+sd+RtxMJSaWNYnjFZ8svuCsILTjt3NLdVe+Oav/hU3D4Xs\\nA9kblHAU75AbMDpCWnnz8ClF7nh1U/h4+UuO50fe3v19pu6Gm5t/zL/9+b+E4QOd3jDYjqwLkUpN\\ngW6wHI5nUlKk7FnDhZp6rKrk5DHKs6bCy+XIbrhHy47bYcc8H1h4QsvA8zHgHz/y+uYtUlZyXbjZ\\n7pjGV8xrZA3HxrN0N9xOPafLB6JbMXogZsHsW6BlMySUOEOW1Npxd/OWm90n6FvNOu/5+PE7Zl84\\nnhaSrwzTiLALejxS1Z7vTu9QXjKNE88Xz9CPPDy8ZlkDXd9zf3fDh/df8fTxyI//wT/i6buv2e5G\\nxs2WzEpOktPhwHa3ZY2R5Xxkux356uufIHJi7BVuHCkXgescgopOES0lzlpqLqSUCOuC6QydNjw/\\nP9N3PfZKov72/feokri7vcPaAaMcFYXpBqpxrMfKMDo653j88B2msxAzinawhPWEFBlyxr3pMKnF\\n0ukniigIbSnDLbJqmPcod4swhVwzMkecluSP7/j+tEeqHW9e/eeQj9gSyKXic2b1CVWgxEpVTS4j\\nhMMYjdIVqyxODkgUyDvO/h0v519QbUL6zH/2yY/50Zs33NyMaNsOoP/j598Rzp7n85nldGzqw27D\\np5/9MR9/Jon5wuPxSy7hgCbzWedQNWDlPbe7N5yOR8r8kT/51/+cy/5ECQtVdFQF2k74tbVWB3VD\\nZwJZCpJ1hDJTxW/hwSAwxAjWFIJfCSFzt3vDq9sv6PsLX737S7ZbQVhgUZHoMxVF9hbRDUihIFem\\nYUCqjOeGZTmipCDFhEbwevsDwrJHikQVGZCUCs71CDICTynpVxxApGc0G8I6E6JHUnFIlMxI2SFE\\nZjNtQdWmiRNtt18qSOE4z0/cbjeUnLHKck4XbN81i9LtZ/S9Y/YfCWUhFkghEUoh6AwlUBOEeEbL\\nDXfT5828dPcZnYGvvzlxyBF/FpRU2N0D2SC71tYb+oEQM69ev0ZUwcenPW8/+Yxpmsgp8bx/z2ef\\n/ZhSS8v0C6g5QWlbk2HoMVqTvG8T/eR5/P57Pnn1qtm9Q8RHj+3aTe7lZc/tbtfq1lohamnMwdAs\\nWO2Z/EqiNpa72weeH99xWWa06ZFKswTfHhViZjNNfPvtN9zf3WK7iePHd5Ra6TvHsBlxvQMS5noQ\\n5ZqoOVBFRkyvqaVQLzMoCUrDeqYEjzSV6nrEJfL5my/46eXIy5IJPvHZmzfsHz9iTI/NlXVdWMMF\\nXwLaNvmt0QUpE8YUBIJcCmP3CQiD0gpREym8MLnCICspzqxrIc+Zl9PKh8OB48kTzzMqV0Yr6TrJ\\nvDxCUSx+JsdCTYosMyfvebW7RQg4X/b0XcatFnWofPv9zzHKoIcbeuPYdJ8xX75CkZv2TyliSazR\\nk9FQ//Yp0f8JXgrFxOV8pMgNMSXG4b4dDOHAw+5TLqc/o2BRMuKFacmxm4mqHUrRQjDjijUjNo8I\\nmTken7gdO2qqxDVhnKX6qwK2th1zSoWNlVhtOJcFbRWyCiQRWQzOCcIlowRAJaaPdF2PtQ6jDb22\\nvDt938QtekNKGmN6Ui7kqqhV4axoxqB8YXB3FGHZ7t5Szpl5+Z4uJUiCWCyyDkhdiGWm5MTHx3f8\\nzps/xKiRkg6MfcfoOi6njLEj5bJQbw1KWpSVqNVhtMbYHn8+Enyk6/vWK1Car77+KW9ef46Ugv3+\\niclphG4BqkIlzJ6xc2QvWS9ntpNjOR2RQjXOpNUc9ieMpkFRpEEKWOcZKWWDmyrJMIxtaOcz3WCR\\nWiGtYjkf6V3HD37nRxzPJ0IKOGNJKTQiVy1cjhfudjdcDkeG0VGVJPuVda1NeCM1N/cPzVrmOuTQ\\nI4RDxNxcl0qC1XCaG9JtnLBI8jIjk8fPM2sIbEzPYV0p0RNDwCqNlaa5HkTm8fBIqhHrNMYklJBU\\nYRDVodRCzhnRG0RymFi4mz6jLppBHrnMkZIPHE8nQoXHc+Tbfdt4GAHTJJk6TT+1QmARoQ22s6JG\\nDSZyXgLjOLNxClsVUmdkzSA1h5dHopT8YLpFVkPNGiMd1Z+oOWO1I+bAikDWEcMEfPU3fkf+ZhwM\\nVSCZuMwrRZ2gKAY7MI07qpE4t7tGgTWjKmw2PVKPzDkCglQVa8lof8JqyDGjdMeyLjjjCacApg2n\\nkoykmqm5RZVXv9C5jk4P3HStlo2qxLJyjJVYMqVWKgFtIOYAcgVhkFKzmR5QrufPj/8Gd/UoSmXQ\\nsoFCNQ6tFDfTDYfzC5flHZvxh0DmdnqF9JV1/YqNCkh5S2e3CBMaT7BW/LryzbsvQUQGK5DCQ0nI\\n0pNCU+kVbxFFoIUhszIHSfIXTAbnLH3f4b1HyDPTtCWmQk4rw2ZDyCtbueF8PpKFIK4Lq1G8fv36\\n2rQ0BB/YbCaU1hijrsM3QYyZcexw3UAOLQEohEIZS4Jr4jETUwWRKCFQc2a/f2SzmRinDZfzhZwL\\n/uKJIiKUQEpJLBkhZZt9SEEpFWElQghc7zDG0g0bTDeg+w1FQLW2lbWEQNiBurHUcMAKi39wuBfH\\n+vgtL4ePvD8feDmeOS9wCSshjSwxYnrQKVNiJITIEiPLIthsLVXSujxCg9QIKVj8C1P3GoSB0iGU\\nxAjN+bzy8RTxohATXKIgyoIQrVfTd4phrHQjnHJqxvMgqFEha0/0AYRif3jirj9gjaPEjBaZ7K9I\\nPgHr3ORBy+rxPjEIDSkSU8BTkWKHVR3i1+Qx/EYcDK2UojG6DRuJGquaQdjWhRzbIEVITacVgg6U\\nQ4oOLSoZ01p7BcrlQowJ1zsygv1hj6gSqzckr0kshLpeARqGalZ8Ngx2oN9MnC5njumR+MuMQhVo\\nY0is6Nr+HSE+UatkdDc4d8+0fcOf/eQvqFf6kRDNX/H0/HOmbkKcC10nkXJmXY84e4MuDq02vLnd\\nsQ8zT9//FePtlqG/pRjP8/xIxlMjfPfua7bDRB47jPagG406leaJyL6H6MhxIdZnchZYCZKObmiE\\n7U/efMb3j++pORBiYOy3fPLmU7w/UWPief8R1214uL9D1MCyXthtdw06SuX25obFnyil0vcDnXWc\\nzy+kLClC040tASmVQGqF0S1J2tkBvyS0hRwjlErnBkKMCB/ou47z4UiJESUVKRV22x2hRHJSlLXV\\nk03nKBVQGiWbMbvrHMpYjHEgBbUfm480R6Sy0PcIVynPz1h7C7s7dFx5+u4n7JcLP/3+K94fjhzD\\nCed+j7zM1LqiTMLISl5ax0QIWNc2SNU6ooiUalAis+aPDGokx0pMFwYtyQhecuKweNZa0banCkEv\\nNZ6MEOBswVqFFJXVr5zPZ2IcWHxCG03KEkSPVyuPzz/hbrqhExrSSoqWkiOIysv+O/IouCxPpHim\\n721bUeZKlhEr7hjdDdr9Fm4laq0NQqocGaiqUmiEJu89Z/+Mj2ClpDMjkolMRXfNVehM1yAi2eLD\\nwvF8YsSw3U4Qm/pMo7HqFr9c2ixBtbx7oTDkE7afGGxzLBwf3/Nyeo9WD5RambYKlZpJOfpCiZ4i\\nPT6dUeJTZB5wFkScyWpEyYFx2PJhH3k5HeiMYiPApwtKGPbHX/Bw+3sAaOl48+YfsJwyRQiGYUPV\\nFmsc53Jm8R6jOx4/vuMUEpkD1kj6EYJXVLEghW4+hXBGqRXNBSFGXD9wmZuL8ee/+JqYEqJm7h8e\\n6LuOp6cnttOIzwvLMtP1U0sjmrYtsE6TYiUvsbENqGhjsKZDG4u2HTmB63oGq1uJiEqWsOTIegbn\\nmvZNW0sKLYGodM9u3LCsF4R12L7j8PQ1neoQtmNdznTTroWWSmsa2m5obkmtqVSMamtHJSo1Z1JR\\n1MsJud0i0wpipdYNUiqy69BPH0iv7tH3r/jkd/8+P/uT/41vvv+GJ/9CLDNffZcwVTGvS2MdUDB9\\nRniHMe5anm/MRYRt5jRd0GZmCd8S00jJGW0kEcelPhAFvwAAIABJREFUZtYqWx38St6SsjVptdX/\\nD3Xv0mNZm6ZnXc97XGvtQ0Tk8TtVV3XbbbBBRkbQEpaQmDFkygxGnmKJgc0/8Mg/wBIDBiAmIGGJ\\nCY2FAYHdttyN2t1V7q6uU3+HPEVGxD6ttd4zg3dXUQyMXVIbVe1JZO7MUETGzv2uZ93Pfd8X2UBQ\\nQqqF01NlviRCPJPygvMbEEtSCRc15/MBw4qZbrrVO2dyiiijSOnEx8NXtDYz2doZLCZRRHrORoNz\\nwmb3K5iurDXT0hkRRxOhtMLxfOCrDz/mcJ75eP+nxCIsOXAA9mPBu8x5Thg1oFtjMBsa0Kgc53ua\\nF9o5cLu9IWSNGgu76Xm/6qQjNT/hN8JC4FIObNqGz17+a2gmzPicf/q9v4c1R7QeWM79KiIeSgok\\nI1ieePPhwrpmlJm42dyQ10BuT5zOCWMcz25Gnk5vKVljHxvbyZLzmWV9y1fvf8xvfuffx+4mrH7J\\np9/5q/yzH/8OE4GtHbBlIpzvSWGEYWA5NYze4XYDl/iO6ZUwFE2Me/zwmo26Q/IDl/KebfZEPfB0\\nfGTvR+blxP72JcZ13BxKY/zE09NH9nvH9//kD3nx7CVffPY5xmqM05xPM3PsYuQwTMxhxeihk6el\\nglSGocNuT6cT83xm3N1ck4RCEyhNcTqeaS2TstBqw6JQJfL2/XvGwaFloYTI3bOX1CxMm4mqLDml\\nzrRoMF9WhsEz2ImaE5mGv71l3Ow7TWxeUGbA+A3VWMT/GtQn2uEt7D/FbLfE3QvMj/6Ysh6YPPyl\\nb/9l/sff+fss6xE/aN6cviEummH4gr3XoCvjoBmMhjLQ8kDMZ0o4YIxiWTLnS6Zxxg1njNlh/Eui\\nGsnZcbgcWNdERjEH+ve/Gbi763mZKAVqocwnDg+ayzmRa8N6TVYr1ilK0ZzPF0pQxFRIaaABYjS+\\nGVIV0gpreWRyhkJm1t3jEFBccuHZ/oDZBJp1v9B78pfiYGi1EcIRrTeUamkV1rDw8fGekBKKjChH\\nVQVFI5YLYT5SmoeqyM1QW6FJBkAZyHXG+JHSVrTyrOmC01t203PSYhDdCJyxdiCUM0s40iSgzZbt\\n9g5len9eK405L7i8ghnQTvrEoYTYMu8ev0Hh2AyQxZJTY9w5aN3Xvts+583b+2vxKQgjpSZyKXz1\\n9p+iFDwbv03Own5z2xmD4vFmSymGmuF0XNhtdlANcb3QLIhaySVjxueMw3D1I0zAW2JNOAJaO9Yc\\nWS4X3LDDe8fNzQ1ffvkTfuu3/j1++MN/RqsB7w03+1tSTsQccaWbbWptaOuJ+f9BoLVWoV1tvy0T\\nYgQEP0wo3Q1ASqneLxEqZtziaByfHtnsJmpJtBSotY/UIawIFW0cw7Ah14xW6gqkEVLKtFp5/PiW\\naRzQSmGdI4Uzq244p8FuUZuJRu8qbC3R1IZmj8TLR+x2xLaR+slrHr5/Tw6Bc1p4vrkjSveBKCri\\nR7bmM5w1GAPjZDFGKMmj2KF1QOIDT6dvCPSDK6dGky5yjuYFlg7rWdbIErtBztTANGqMVVgt5FZ+\\n1vtQcuvgHKeoMeNcxfqfZoEsl9OFdc6UohDpxTkhBxQGMLTavRQ5BkKFlnpvyNwqetBYn1EqdOft\\nL/D4peh8RITzfGKZn5CqEPGEPPdCluXEaLZ4O/VCz7ZAi4QaacGSYkPpgaYqmUyxjhe3L9Bkcj5h\\ndMP6RpVG03S+ZLNE3YnXGgNNEeLMaXkitxnRF5pNKN3R4jkJpTja6qB6TPOMbt8r41qhpoVSMkoJ\\noitKV5DAzf6OF7cv+OL1Z6S1koKCYqnV4OyWmFbeffgTHs4/IdaFwXpe3r1mO96h/A01aZzA4/HC\\nvF6oLUBulIuwlkqslZTPFJ1IKpLzgi4KnBBzRmkIqVepzctKzpkP7z/ya7/2bb773d/n809fU2Jk\\n8hvkupU4HA5obTryTCCXSlWWYZw61s508xdyFQlTLysxdgBl0M4jdsCYkVYb+90OtObD/ccuRNbe\\nd1lSJa4BFMRUQY1kqWijWMOMu1Kw5vmJ8+GBFiuX84nj0wPhfOJyOBIuF9Iys65dC6h2oNWVuh6Q\\nMKPHW6zu3QTSFsy4wz//nCUXUq28u7aENbHANTCmK6kkcmuE0qGxuSZKXchrRtXeoqRqpeRAaULT\\nCVQh1ZklnZhDJMROGU8xkGuhSAfJKGXw2lKSIsZCiAktCW8cfjQkAe0sSjmWUyFcYDkVllCY59jN\\nVClSezMNrgkmFdqSSHPiOEfuTydqVYiu2LFR2koIv4I+BoAaC6Et6CkyDntKVlzmE00qftiz0Y2S\\nI7EcqFLR9GCOHzT62va8rhFtKq7uMc4R05FGwuhth8Rcq8KVsTw+rtfV5NIdlXXl4+GPGfxAKqA1\\nGBUISwfahFm6D7+NvcIrarzfIGTyGkEEkYyYipgZJRnrtuimububeHjwhJSJ0VFbv0/33lBK4Ot3\\n3+d2d+TZsOd2/5p5XjCqXxms9mgFT09HpqHgN0KphbIolN0gprGmN2j7nNpWnFhGLVQXSTWgRGiq\\nYelKe8kJ5zyqCY+Pj9C6bnBeLoy77RVL15utQK5hqaV3Q2iDsh5rh55TaI3WDBiNyoAyGOuoaFoD\\nN3guywKlcV5X5iWgciQZoeXIxnXWpPMeqEj7ad0ZhJSwzhNDQVvLfD7x/O6OXBZKWIhxIcWJWgqt\\nrdR4RgdFGQdkfqDmiOwnmEZyXNEVGoGNc/wkNx6eZiyex/UB4xxKZ1o1xHhGRLFmg5kzZSOUrJgG\\ni77yH2uoQGA00CShqQiBUo9XAnXrOZFcSBWUNEIKtDqglCGGwpoStTVyqmg1UVpjHCbWONOKola4\\nHAMxKUKAaTtQh14dn3LBCGgaThUmiRxj5rJKt0E7xbCNeEOPm+fCPP8Z18f///FQIlhtSGv/gYa4\\ncrp8JKUZdWUBbr1nCYoSFUpdDSYSceaWtBbqINRayOWEsENaYnJ70pJxKmO0pbZIjEdKnTmvR3yr\\neAVVwIwwx5WH89e0PGFM66YZFmieUq9jbWr4QVhKx6tJrRitfjYWV1lINaGVUOoNCouowGdfvIKs\\n+fKbD90Lz45RDVhd+Xh84svzH/P813+rTx2iCGnB7gXWhK+CMSvzqhE39JSGOJTSbLY7kMwxfEW4\\nVF7cPifKPU7fUWuiVUEjLFe8myjLV19+hR80y7LiB80w3tCq4nB4QinFPC98+ukd5/OJcfL8tDDQ\\nWkfMhaa7jjAMA6lkQipY62liENHX7kFDC4kQM8vl3MtfYmG03XK9HaeOlxNASV8x9iwzIrqHqZQi\\nxAVnHM9uX7DGhdvnd9imcMPw04Z4Yiq4kDGXY3+DW0WNERVAzIi0CLnQ5siHN2/Rdsua7xlGh0Tp\\n3AgypQglCjHXPmmlxnanKEW43d2xM55RA3LBKLBGs+YCxZFDopkLpu0orfMyKQ1apdIIayQXi1xJ\\n4HHtz69LYBSNtxoxmpQTpTRa7YdDTr3urrUufsZQiBGg4ExhHAyqFdpcWUIhVcFtFNgFb0fi1TQV\\n1l9BjUGUoK2ntL5+u5wvzJeVdQ2MG01MB6qqiBL86BAFpVbcIOSkkZqZw0yrmtoig1WsqyWnivFC\\nyZle4dNYQmZNZ87zAy2DtAFMoorgx8xp/oiWC7V2m682ffRVRpOlQmqkYMBmlBZaqYChihAFTusT\\n1aw4q2hPmo29ZeMt+3Fg41+TxfDwgyfWZeHV7S0WxbOt5eknf3zdxPSAz7w8sN1oioHmG74axu0N\\nRndyVk4ryo2ITGxGxePj14RameNCaoUb65A8YGTs01LLnE8nvvji1zmtM63SdQM0IAzjxOV85Nnd\\nM7TRlCasKeNoiLkmUHPDew30VqE1po6dywG0oqKxxiLaAqoj1GJhXi4Ya65YNo02ltQKKmecsYSU\\nmJz7GTw3pg7X/cmPfsirl685Pr1lmO6487dUqTjrsQaQRq4Vrw1iNNIW1q/f47/4HBm2lMsROym0\\n9qAC6Vh4Oj7woSSqyyztTMwrMS+o2r++0nR9oyZSvXA4Nawz3D88UNyebD16aBQK6HhNi2osnSEi\\nFrQorPE4M1OkX9iERgqZXDJUyDGRqiWuHj80nDYoNEZZ4jWfp5TGuh70arVPC7RMzBtEV4aNZrQr\\npQViyh2sYwriFNrSsYHJkyKkUn+h9+QvxcGAaOzuJa0cUHPh5evPsGJpAvcPb3l//JKte8HoBhI3\\nNBN6Os+ceTx8TY4Wdegju9cD56e31KKgCU8tY0xAO6G0hWHQnC8L1itijhACkm33xdeM3EWc07x4\\n9ow4rxTVKNoRqyPWFb/1lFKYdhM1Lf2NnBQbt8Vbjy2fUE8WMTvezE8cT7/Pt754zm9++9/i5ecv\\n+eyLz3g6P+KUxVqPUwOlOZ6/+oS5nHjz7kfkplBW8FbwNxpvCk69Yjd8Rg2N01FTlwNqHqltS46G\\nvdkjOvL+/ddsd4q6PXNTR7ZYFJbL5QnvN6zhQAoBqye2mw3GWJSyrOuMH8buAdkMrLlRqqZVQ1gD\\nIFQloCx2dJQmWDPRlEGPBi0Wpw0oB1qIa6b8tKrtvDBtd3x8OvDJ6084Hx4QyXz+6Wccj094q9mN\\nI0opLpcLxhgupwvPnt2iWuT1F38erXoEfnAOP90wDgObmxucHxmHDbloaD3h2t78KexvsK8+oYQD\\nenhFKyecWnnx4lN+/7v/G998/BF343O4e+LhVLicBGMsg/UkFC0a4iIs+UItitubHeiRc+0egUqj\\nGUM9WeZYmKYtftqRlZApaNUYzAguMux7IbBG07TvhbrTSF0WGpV1blTTW8uM2bHMpytnxWOTJmhF\\na8I6F0qBGhXbbaWayFkV0ho4xISMipu9Y9omjG6kolgPmppH2vor2PkIDW0cRjx2MDijGb0jl8Z6\\nrpRQ0a7fi6UCyzx3NmOuPcpqMpVCag3LQG2ZWCqpVEosvWCDhnHgfKMSrwxF1SGrylNJXOaAsme2\\nE4xOsb/ZMB8KtXlGtaWYgXl+y4tnNwxuJM2NsKwY6Yq9HzwKi5EdRj1Djzvmw0fiUlkuglITg4Hd\\nOFGa4JzHaEsLAWcN2vXuh5wct9NnzKcTeX3AbwraTb2dB4N3lpQHLuczLTtqNphh7B77pkjJwmpw\\ntkeEa6296VrB/cf3jNO29whqT84ZZwfePnzNdrvtyHhlWEOitB4Bz7kLq9YOnX7UGq32160vIDRa\\ne8Q4lDZUKko3UoqkFBFjWcJKTIXLfOnqu1WUXKD2PEtrHdWulPDx/p4YIrvNSI0BYxtK+mgtwLKc\\nmAaDrhVFJaYZZa8V9X5EpUJNAVJFcqSdvoLtS5pqLOsFqxVrjEz2ljp9SqJQa+sQY51x1wLXbApN\\nLmgD2liaMoSYyCGhVcbqXtdHU5SsoThKaNAKpioMFbGCc4KiU6mMm1CiOhNHKVJqLB8Tbr+hNo0z\\nCmN6u7PVA04rrCnUunQoby6AIKpRVGOOicvcCE2z21i0741oqmVCSOQ8UHPFtF9J8VHQVIwbyVVY\\n04WdfkUpiRADZMEOA0Xq1YpLb6m5ln1Yp3twpikiK2iLFUNLkZwERJHiSgiNeU0Y33rBh9F9V369\\nnxv87trIo3m2u0V04fWrkc322+x2rylp5fe/d8Y7RUnp2q1QuxuzgFiNsx6VhXHyNKu4ubtDW2Ez\\n7NFpoOqINa2j6f0dNDBDY2ibzrkUhaoNYkXLjnl+z60bSeZMUAZVLaPOmN0tGc/D45EQNJttA+W4\\nnZ5TW6IWRfX9J9tMJF8S8/nSp51hSy6CsQN6qLx984HSCnbYcFkDNz0ziFGdkGRMr6lbw4Uojc1m\\nw/l8Zpq2nfmgPbUKVje0buS1UEsGZci1d0KUUoghcjrcYxGsGVnOFygFdCOuCeM0y3zCKY2bBnLM\\nTJsNugrOKZRTlJwxrTsfgxso0piGHXazIRzuGZ5/AlpocaXN96jda+KXP2R9esI0y+Nx5vT4wHx8\\nQtnPMfYWr98xjTONCSWFmi2D7puK86K7k1K5jpdvQo6dNKUGhRKNGAXNUaKi6kxTBVTAjpFhGtG+\\n9DUqMA0ee/25llSYzcxjKmwzJCVMVTDSNyHO3fbXe4BlLcQYyCVidKE00zcnOXJeukjsnGb0FdNh\\nqMTY4bmahtT8C70jfykOBrliy3QTlIbSCo/nB5ZlptbCdnuLdfZKnQajNSEsjN7RdKa13qqUy0pr\\nle3wAtUUyoDSQk6KePEsSyKnldLKNVk50Jow6MpgRv7yn/u3+eKLXwdVWMOB3XZgXR95/erbvHrx\\nBYfLwne//3vM5wvSurBnRVCl39u31l2ASnWlPNWI1w6nLX64oTULzaJLIsVMImKlA3XtbkOpj4R0\\nAZkQo2lVMGyIZ4s1iayPqDbhdMNvRo6x4WzhdD6BPmGcx4vFeocSS6seZQxNuicAenYi54wT4XB8\\nT2udf7Db7blcFsZhIKc+IZSSSTlirWENC+fLTENhfG9cLvR76Zw7Lr5Iw2hNKX1v7txAKY3SFDkL\\nT4cntAxsbm7JtTDPJ6wSqPDw+J7nz19wfHrgdnPH0+nAzc0NxhhEelaitQ5V8cZS0N0QJArt99RU\\nWEVwIcI0doE69tfIPPuEn/zO/87j3DikMx9PD6xhpeULRWVEW8x47mi3/BKtgWgYx5FYRlJaO6gn\\nF1qtV4hMQ0JGWYuxgtUjoMhtJpeegVBtRHmHHTqKLiNwzdfsd8/Zb1dK+h6P79+C6k7FkArQqDlj\\ntaE1zWANMVny0oipYicNrVGLIsbWNZpRsfMwaEEKpEKvlkOhRTNufgXFx9ZxHB2DngSRETMNuFIZ\\nxh5eyqmv0Eqbe72YFFILiOqrxU5YqggRZ4XBbqHdcjw9km3GikOpQq6e2M6kHCgFqI51SfzGZ9/m\\nOy/+An/xN36TYRr50Td/SIoL1nV5rqSVlvupPc9nSJqcFqbqmKzmMSxocazLis+GkB8IKpLrzKRu\\nKDl33Pzg2Y43vD+94Xh6YDduudl9Sq2OU3gg1gNKGlVnnBdU2iBBsZ5WchUGcYgxOCtsbgy1bKEm\\ncswYm1DK9gYoI1DrtYLfErXCaEOIETc2Ylw4nh8Z/YZx9BjrqZlOAFeq91J6RykVa821WLVDX07n\\nGa3kKqkJOTWU6gGqGBMxRkqpLPNCzoWcC09Px2vrEijRnC9HsIq5RDaDx2hYR0/JiRR7azVUau3G\\nnJ+WvbTWiK2wcQ5rLNoOnNYzzjqmly+QwwqhwjTQakXyBdm+4sVnn/N//Pb/ysfLD1l4YC5HYmho\\nazmuF2QoTONKKRGtDMYbUqpYM1By7mnZ2ogpdMHRD71DwilEtasgXlhTBAFvx27Ich5xFhBaXLB+\\n5PmLT3m2/xaXU0Cq5+2bv482vaVsTTOlVVpp1NI3EmsqtKZ68C8bWhOQfjAsaxclra1o1RDVCCVT\\nsVcEocGqev15/ss/fikOhlL6SssoRU2O3WZLWnoKbRo1WisO5yeqNKZp4na/ZxoUb55+hBKNKYbB\\n9XhzSQHvK6/uPkOpEesGfvjlP8M5i1EbvNrgl4FzODKvF6yyLEvjBz/+itd3f4obLZubibcPF0Kc\\neffuB8QyM9gNHz8ufHh7YHuzQVmFxWKpnJaFSz7jmuuItmLYmUwiMzXP+u7IW/1j5uMDr9MrPnv9\\n53g6L5zmA2csd3c7Jrvj8d0fQA202lhiwPiOM8eB1xNYz1rgmxj4LJ/ZTF2d1+OeNSyoGqmEXmgS\\nVtYpMeqEyEiulVYqRoRwORDOJ/Z3Lygxs6KI+8p+3PL0eOBwmfnkk084zzOXeeHu7o6wZu5u7jif\\nzxhjcG6kFcU69/5IYwwhRHLOLEuvjT9fzuSceDi8Y41HrLK0lFiXmfnhA2o74p3l8vSB3XbH0/09\\ntzevyeHMbnPHcjmQbMYoIc69CUqJMGx33OwnihJUM2yHkbCumNMK2+4GLDFipy3NWFCJm5vPeHZ3\\nyw8+nnj7+KdclsSc7zkfLzy7e8V3vv3vYvw9p/oe3T5F6oDRmiH3OvqYCyEsnM8XYlpp58jZaDa7\\nkf12S6aS0sLxvIDKbD//AvEbKoqWMtICpd5zXAJvP2i00p1yrmZefrJjXrvvYdwY0uIRhPuPbzBm\\n6itn2zCqryaVghQ7JmC9AE0zeNBe9wuK7e1k6xohReaoiPpX0MdAA6EwWIhLh3sa0ygxEuYziHA5\\nn0F6oahSDq0sqggpR7TtI1orQgmNkz7z+lnuBSEGqrqQ6JToVnadMCzSOwyUY/KWeQ38n//4H/JP\\n/vB3cZPj88+/hbHCcX4ihAdUcxw/xj5m1sw4bBCnIAcEgwuO2nrclXXl5saxtyPHlpGkOT29A4Gb\\nm1tevZx4sXvB5d0jOURyWslaKFVRc0KoSBWWeUZpgzhBmmC16vg4ZTku6SrANtAZ5RoaR1oDmQTK\\ns5Yzc7JslUZRqK1AreQWUUqTUybWTLuW1T4+PjJaR02FuKyEGDDWdnzdsqCUwjlHSqXrO8I1Taq4\\nXOafdTz+vP32crl0oVI087Kyu52wrtuGc8ncjCO59q8vql8ktFI8fPgGEcUwVZpSvYq+5F40O5/I\\n6RnKaaSmHt5qlbo8oNwdonqHZKGAcehw4eHdGz6eTqxz5nxKzHNhKd3U9PnL7zDUDYaVxfwpSi7U\\ncECrDcY5Co00L5RWiHklpYhWliV0ZL3FIk2RYyAvK0kScZ7RNIzV5BbJ7UJuR9IaeJtmSolMww1h\\nXqhVYY1COUGpimrdcdpqpebW7cmloqvgrYeiiCETYyKX1ktkXEObRlNdq6l0otZlDSjlek7gF3j8\\nUhwMSjSDFganYOgwzxhmtrs9rWiWdCasK6FUmoAyBa0qqipqUiQqUlasHoiLodTI4fDEbieUfEFY\\nr7xHRUwV3RwWR6T0jgE34uzI4+MDh7dfISKM08Dzm1uc2bKEFTGCHiJSC1YaIa0MfgDrQRS+ZYo4\\nrFiQTFCFjTf4BjUUUo2s+cISZybzkpvtC9SHH1BL4PHwJa/VZ6g8UEJDG1ClsRn3WLslCphyZjAe\\nEU1Gc3h4y7zOvVxUBGs0NGjSaK0QSkVHy2RLL0dtgqqJmCINg9KN8/EB5TdY141ah9OB4DzjOPLw\\n8SO1VoZpZHaew/HMMI7EGK/x4w31urEQUT97vpRC7A4cUkr94EDIqbc7S+tqvnEaq00njTmHUoJS\\nvZpe0Tc8Dx/focwOrQZaSdRWcBqKVGpYUOIppnBZZ0ZvyS1jl5mmPXrYIcZQc+Xy9p7vv/mSSz1j\\nsKSlUaPgjefuk+fsB8/gDKk4RCoiC8iFXMFYByjWucf/O1xHCHOCprv4V854Z6glE5YEvrKWM+pa\\nFRfrTG0RI40cZ9Yws9GvMAyM43RtvO7rycoZZxu1Oo6ckdr7Q3UzPT8hQlOaNS6k3DDaMk0N64VU\\nFrQWYsyUErvHRxzSVO85/QUevyQHA0xWYXQXDOP5xGifc7d7zcbf8eOvv4sSRauRp+MTJRumcSAs\\n3fVWSqU5Rc4rNWviKfD0+ITCkmLBKoc2kUs4klKkyZ5WPBoFLXVlWeDu2QuMsdx/uKeWgOSIKCFH\\nSCTG0ZPnxIAhGU2uBcGhzcBu01eApUIzCWM1WIMtiTBWbBMKmUs4o0pjN0zs/Ya3x/fYSbGGkcEJ\\nG/eamhqTFeywZ9rc8O7whnwKnUblDHNYOJ3OhBhwzjLuNmy9p9CY44q2ndbslWFdV1Y/Ypoi5YTU\\nQhOFoudDdpst0gof379lt7/j6XxgmkYOhyPOWbx3PD09ElPm4enAbrcl54SIsIZAq7V/bN0unFNi\\nWRZaa5zPZ7TWlJKB2leyCkJY2N7sqCEjGJr0QtdlvuC8ZRw2XTQ0jfVywNuEtY5cIoenj+y2O3Ku\\nOOURO0JeOR2PTM/uoBTENkopKDtgxPJ4uXBZIm+/fMt5nvF2Sy0zd3d3PLt7hkhlLmc+PL7D3wFq\\nRdmVmh0iBl011NLxcE3/jCtRUqM1aKagrO6htuvauaqFWFdyXRGV+6al9SBgxWOMphUNYpk2gpjC\\nOKa+5g2KVjXOCTVHjOnTbRGwIiy6bx1EgR0V06YSSg+lxQVovYSo1AHrCk4rnP3FNIZ/YYhKRL4l\\nIv+LiHxXRP5QRP6z6/PPROS3ReT71493P/c5/4WI/ImI/JGI/If/wu+iNbhCTkoBSuHZzS2qWaze\\nMfg7BGFrLC1HzscLx8PCulSUeHIUSlR9/JJ+qi/L0gWuqlHRYUrFGYWSgtTUr8qqkZaEICCCNYa7\\nuzs+/+ILnj7eE8OCU4ITQysG0ZbN2NdoKTRqBpLQSkNpw3AVxKQJS66cUyQpA97TvGLwnjWv3Tff\\nKoMf0DaRyhFtV/bbT9lsnrF/9pL97TNu72549uyOm90WabaLXC2gJLCmmRACKazk5YmtDuxMIVxW\\nwjniGJj8yMZP3eGpFc5vEGMQXck1Q6ms84XBW2KcSSnCNe58f3+PUpqUMk9PR0Lsm4acCyKKZV37\\nAUCfDGKMXC4XLle69U9/b62ltYIg+GEgl0wMZ/b7G4x33Xviem9nXPvUkcvSD7xxjzRBqYJWimkc\\nsd5TRKPcgB43GD8weI8A2jrEWMR7lHNUbWnK8ZNvvgbx5Fh5PN0jpjCMhhcvnzF6S22Vjx/fc//h\\nI2F15FwpnKjMIAml8zWT0VC54Bo4I3hFr9Ez9jpFGcQISmtyXagtkXO7OhqFcRywMnQfg06MkwMy\\nt3cdk5f5gLY9eVmJbPcWP/ahVOmKdQ3vBVSitYIyDWsF5R2talJVhCikqsnVoMUwTt27Y53+hQ6G\\nf5mJIQP/eWvtd0VkB/wTEflt4D8F/l5r7W+JyN8E/ibwN0TkLwH/MfBvAJ8B/7OI/IX2/8HIqrUR\\n5obz9Gowadd1TKXRle7JDVgdqVJYUiSXxjTsEavYbMerah7IayDVRCoXStPU4nHaM58rZnSQGzFF\\nrM4oer6iXJVvozXODTg3cHo8k1Jlyp4X+8/LRi3ZAAAgAElEQVR5f3wklwvSBDFbtHZYnRkmcLpb\\nhmNdcSgYS4fp6s5TyCXTaqO0QgoLh8uRohVqGnlpnxHJ/T5RMpWeMKxNocxy5U/ek+LSoaejxpjE\\nuHUYW5kGxcZWtC3UkpCSyXlLKwWvJia3oSmL5IIX6exFEkKnFPnREWNgsI7T0z21NU7HE875n702\\nD48PfPH5Z4x+RFBM48T5fL6+6TtQprX2s0mB67SQc+J87v/2n5bC1pZBLLXCOG46w7MUNtstCsGZ\\nDY1MjIH9ds9ZK6D0sVJ6RHkcd92DYXwPcCnFMI40FMp2g1azDtW6zfh3v/d9vjl+YG1PaE2/rYud\\nyF1S4XA5cbh/RNMbqtktWMlYLZS0Uovv0JsKg3bkmqjSiKqwnUacU4hu1JL7pkagpYFAv9gIhloa\\nfueY3IZUV2KaKXKPdxts1pTYWC4nwppoZewhNF8wYqlFk0vrt16iUWnp2ESlsFqTm6Bat4eLku4Q\\nHR1GCuvawcI5/hnfSrTW3gBvrr8+icj3gM+B/wj4D65/7b8C/j7wN67P/7ettQD8SET+BPgt4B/8\\n875GqY0PT2ey8uQ8E0Pkj77/j9lvvyCmhRBmPnmxxRqDUo535zOiLF/cbNjejLjtHtEjISWOxwMf\\n778mliNzvmCUYzWPSBNoEGLhdGgYfcLoAauF0VmUMbz/8BajDbv9hhevfp01F7Zo1qVyO7zg7YeE\\n3lu2N69Z0z1rfotRC6PZ4f0NNd+zxg+M+06kHt2eUp/hL5Hl+MhKIEbPH3x5xg17ptGwu/0W2i7Y\\njXC4/wE5JYzuuYI5XIghE043TOuecaNpNjG5xEu2LKeRUgrNGGY0gzPsbh2HJfDm+B6/eY7yG6at\\nZVAbwqK4uWLrQgjUEjFS0HQx7ZwSCo1qlVrytVdgIcS+mxfdg1PLslxR60KMkffv33N3d8e6dnjN\\n42Ovi1/CSi0ZRXfzxZi4228Zx5FluUCOjDfPwBiO5xPD4PrYbPpGQETz+vlzcuwEMW8doht3d5/y\\n7JPfpOGY1wu0hrVjX4X6Ce5uacbAOnP+8BXPX3/GP/6T/4uv334NkliXlWG0UDt27t0399zoHVIT\\n79+cmM+W3d3CePs13jwntYb2FRMqSUNqQmmN6i3WCSNCXXNf7bZeSEwaGPWEngvlsIDLPMSVaRPA\\nuGsZzhPBntlvtwz+Bts0hJGHhze0GBhzRU07UJBrg9QwNfOJt6QmzMWR00q6KMTOjKPBOMHUQs0r\\nKSqW9xMlXafbP8uD4ecfIvId4K8AvwO8vh4aAG+B19dffw78w5/7tK+uz/3zvwl9RaqHSM4KWoEW\\nOR3fd6y4VObg8dWQS8OII1bVTUYo1nxi0NJbm/2EYsO6CoenC8N0TaaZRim9ELOf7l2Ea0qjjWYY\\nR3b7Wx4P70mnldFPtFZ4d7xHNdVTg7p0DoIbuQTheLxwWQJhhDs/UBQoc71SFWhJdd+8VDZWdwp0\\nCjwu77AlkMqWsQ3s9gNl0Xz7k7/Kh/dfcjj9mFJXLudCiJVREvuxr6tCDmSbmfyWEvvqT9A0PEvO\\n+N3Ai20ih8ZxecfgRpSArrq/2ZrQaDhrQQsCWIEcLmys9ChwDR1GozWHwxkj0GolhUhx+WdTgrOW\\nEMK1qzETY+wlLU2IIXVDUOkuSCWCSEfNrWlFt9p9CFqTYiQtM7pFYs3UVtlOe9w0YazrRqlaEOfY\\nThP+Zk/MkePhkdhWJj9grce6EayjXevXQPDW8f0ffRc/WIZpw8PpIy3Dq+fPEYSn0xNGfK+pi0Jp\\nlboWpmwpa6XtZlQTmi4Ulam6dzTEpSIj2Kqgdauyro3JKZQquMFh8djaITyXtXsyWlU0Ektc8Nqy\\n3Y9M3mKN58XtjnSp6LZlmR3Pnn0LPUwoVYlpJS2ZFjI/+uZrQmosuYEUrE2IMuQk1KCItVGioVwc\\n6jT0Upf2r4h2LSJb4L8D/npr7fhTJx1Aa62JyC9EzRSRvwb8NYDdzmM3G1K+0OpMu47WNec+QSrN\\naQmkalhDJNVKbYmYHPMpMLRALAmrBhDTRZ080IIwp4DzE5gOpBWt8BNQNeSMNor91pKr4/XLz1jj\\nkfMy8/HpLdvNjuV0TymN3eaGVy9fcXvzkrtnn7KuJ9a5cjzNnJ3gXybK2Chm15HoUjvSQGZiDGyN\\nYOqAkzOLirR84ekIqWQUmtv9jufTb/Cdf/Ov8Ec/+Uf8wR/9NrUaTkfB3yqUyjhlqbW/8F4Etdth\\njGcNMzFVaum77FFV1DBwvhTW3P8DamOx4lmXM1pptFGcn2Y2E6ynR5wbSKHhtv0/4u1+wugGNTB4\\n36vJgdP5zLTdUC6ZdV3JMeGsJ4SAUooYAjknUouUHFGtItI9FNPGMQ4Th8N7Pn/xinW59A1G6/mC\\nmhrVWkIMbDZ3iNIYbbtQJ40YA610KG6uGVGwnA5YUex2tzTnENE9zt00dXnk/cdv+ObxG55OT2AM\\n2mjuXj1nMw2cDkceH09oGUlaiKqhjSVnIa4V5wslR6xW3f2pAQWGREgNIwqx9Zp9EIpuWGOoRqOM\\nQaXulGyxUsJKmSbWuTHHwHLKtLrw/IVi2n+48lYNylcGdcvm9R3717+G9UIuM8uycH//wOPDIy9e\\nvubN/ZGQHtndWkYnhOXMGhOSHa0a1kuFBV47jxfP0v4V+BhExNIPhf+6tfbfX59+JyKfttbeiMin\\nwPvr818D3/q5T//i+tz/69Fa+zvA3wH45JObZt2OOSSUS+QUwSrEapRyCI0QMmVZyAVKzTQt3B9n\\nJGmWmJm2Ge8Xaum9C630sInRvlfFqYaxDj900aZVTYoVpVZyO2Ftb2R6/fwz1q9+xNu3b7nZB3Kb\\ne036eEvLA7ebV+zHWzbjDVo2WJ05PDbepUd2Lyx6Mqyt+wvMTRfKUmnEWnFuQ2wa3Z4wulJroFxN\\nQsdyYv+vP+fTZ7/OqxffYRxu+Af/5O+izcJ51iyDx6mKYClBo33Das12swMpxCUSlsJztWMaAW2p\\nnu6tiBG73dDWgrQuAp5OJ4bNhLTum6h5RRvH6C3GafabkZgj09ADYmE5on2joUB338HlcumvZYWU\\nI857jocD67r2ejUKIo2cC4NzCOCUxoiwLhdaLZzPJ57d3SGrvqLaNdZ5SsmU1ug9RYIoweheBCsC\\nKXYmplaa+XLhxUtBxpFiNSpXMI2UA7//gz/i/vSBJSyEVLndbbm7MfhB8ac/ecf5UvHWoGygiUJQ\\naG2IYWadM8o2zBRwxlzj1YWmBG8VrugO5Cm9/co46bqSWCSBKQUpGXGWnRuIKOK6sJ5hOWmaEh4e\\njuwuDWMSwTj29jl3Ny8xjCibcM6x9Rt22y23t894/+GB3/vdP2SzUfzafkeyK1tzy5t3ieNTJcdI\\nTYUcYCSjbAcw1/JnnJWQPhr8l8D3Wmt/++f+6O8C/wnwt64f/4efe/6/EZG/TRcffxP4R/+Cr4LV\\nU6c8GUdVhiSRWhR+tGilKGTiWshNkGZJqfJhvuAuhnFjqDUi+z4ml1YQ0wnAuhq0QKmGNhaUFsZR\\ndYVZFaQlLuE9z/wrclZYPfR7/NJ4fPhIaa3be28S6rnjs1ffpolm9FtEwWYzcbw/8ubdgcE9ZzKe\\nDMzxROaTjm3PB1oKZCyb8XNOa8ApS5JEzgY1KmJonNaZb28MezPwF//8v8PT4QO/973/icthZVET\\nW7fg7ISKGw6Xld3QiVFa+qVMmkLySE3d+EXrtlhjHUtNqJTw3nJ4fMA5x24zkpYz+noDerPb0hps\\nvUfVjJSEJyBSWS4fsE6RWxdp16XrCT+F1nasfTco5RwpNVxt6gWtDUYpKI2cLljV+PjxLc+fP6OE\\niDaKJIJ3ntYKxuhecJLizzIeoxvBb8itax/KdMuydxukQc4ZUwtq2NNCoswnRA+EBCUESljYjgPP\\n73b4oXNKPzyeWM/Qdh5rSwfVaLC6krKihBtiWHG+0dqCVVBbw4n9v6l7kx7bsjRN61n97k5nZrfz\\n6+7hEZmQFFSVVFSmhMQIRkhMEQMkRgz5B/wemNSvYICAQiCVSCqzKptoMsLDb2vNOWd3q2Ww9rUs\\nCamUgVLgsSWXu1wms3u3nb3217zv89LsHfFaqds5JKTOVTSHoBSJToWyerQSuE5hdMd0nVmWTDkb\\nckwsYqGREqV65jUiksLmwn2aaKxCPj1iL4K2b3BmhzENL27f8u1PRn64/x4fNcfTT/DThXe+J14k\\nRnYYYRFWYpWi3d/UvM3l738r8R8D/zXwp0KIf7H9v/+OeiD8MyHEf0ONuPkvAUop/1II8c+AP6Nu\\nNP7bf9tGAqjUppDIUdI0O2TWKO0JJaGKpDEtNFUBtqwzSghMCiSVmKeJaYnE7ChZ0rSCInQ9/S0s\\nS8KphJUKHwoIDzIDAtcKlNAUcWVdHyEdSGllaPfc68/Ml5EULRTLDz+856df/4zO3nBdnvDLStNU\\nJsF4Kszv70nzjIkNRresq8Un6NWB1mTW6wRkWlk4Dt/SOsXqL4xrDTbRueNP/9X/Qt/s+PrrnxIy\\nnI5f8eb1H/KXTz9nipksAqIIWr3j8+WKylPV9Odco82SrgrPqVT9RUhYXSlJY1hpcmYdZ5RSHA8H\\nZIlgDN5Huq7HmoZSJI3RhOQxMlDwFBSTn/DLjiXkKvPN5XkIWQeLdTNRV5qhbj+K2ARBiVigMZbr\\n+IifrxQS1/FK1/TM04jYGJJfvkdWAb9IUk4Ya6HUtDDbdOSYSVTpdSkFZytsRgpBmiOid8jHiacP\\nj1jV0ETBmhZuBoczEaMslxDwWeO9x4VESpIoI9o0aLlgtKLEjhIlYfEIEfhiNwg5o2yg2fVoEkgI\\nJKQU5EJdl8fIoBSigWJAy4LPEX8VrNcZ1Sd2O8HuZAjBklJAyO3+NwUtF37z87+mM5o3X73k1YuW\\nYWeQQvH2q+/oj0f87JHySG4k5psIrwpda9FCY63F6Ia+kRUAFD3//f/wv/+dDwbxRZjy/+d1c9uW\\n//Q//w94XB5wekdOgev1AtnT9y3tboAC4xzQ1MjznDI6ZqY48zRPQKHrCk3T0Lk7hLbEELleLxgj\\naduJEjVxKcSSmEg4M9PtLFonsq+JTo17Q0Lw7odfcnkMnB89Qkj2R0vbd/wX/9l/RVTwl3/zPxPy\\nO/a2Y75EfvOvP/L2eMPuMHHoDO/Pmeabf8gf/Owfss4Xfv3z/5U1ziwpM5zueLU/IWzmfL1HWsPj\\np0gQhct0wTnFy9tbTqcWZwV/+ue/oM0Tt2bPbv+SJFsels90TamJRLkKXIpPjCHX/M1ppBiF6S2N\\nbrkVdzRJY4OkNbYmKPuJ1Y+0bcPd3QuWxTNfP3E8HjCmwZqGGDMxBC7rzP01g2pJWC5P92jn2B1P\\nkCUheJqm5Xx+JKbazy7LQtM0lOS5PR3pVKTMF+brmRxqK3E4Hmic5fXrn1BYISb8OmG0xRqLcz3H\\nm1uafmC32yOVpT8cWLLhfD4jgNO+R5me8uIG53pYPd//+gf+x//tn/PP//x/Avl+Q8hl5scKap0K\\nPJwLfvG8uLnj0B8oOdC0Pd2uJ5KwWjKHyL43vNi/RdOjsRjZsawj4+Uz12t1gUJmmWayVEhluLl9\\nSd9VnoJzHRnJdJlYV1/NXVKiGkk3SLR2lKSR6BqWaw3OuZolGlaEqpEK0gqOhyOnYc8yT8SYCTGi\\nVctgDygt0Y2u1bCQaKUq6ZuCVILXf/gf/h+llD/+uzyTPwrlY71WjCrcPz6Q5gW/RpQEYiKmGteV\\nkyCQQDXkACIJtCo4t0lq80IJBdklSq6cx2HYI2SiHzpcVEz5TPQBvxZC6LgGj9QZawrJT8R0T6LS\\nhJxr6HeQfELpgtaZv/7NvyQKOF/fI9SIzgWirgEqIaCXgtIZbTKn3WvevPoZl4ePfDa7yrCcPW5v\\nmZdEZxratifGiaapBKKdtNx/fs/49D3+6zte3L3l5ubA+nClyIWYJmSj6dwLRH7AWoPIAePXipcr\\nV0IOSANFF9YlE1Kiazq0bOlMT0yJ1tUpPMDpdMP1emWeJwxVq9+5HcbW+UIMGWEt9+cPzGtiiYm4\\nRNYYUNagsmL2VbRVW4mAEHUKnnNmGDpuDwOOzPf3H7l78YIwO+bxjFV1p4IQKO3IJeBcSwieGBMl\\nXXgSib5vWccrb779jusSuPpADIG+r7RvocD0NzCPXN595P2795znGZ0UomkoCVbviWtBJMWx72mk\\nwaqGF6db9v2Alpqbmxd0ux6QVcZNprcdN7e3NMbRNS1SSOY5cP/4iXG+0HUDsiiu05XVe7q24eZ0\\nwtkWqDYFoagD2+CJYUXLnm6wtJ1CFLvBcCCLgpEWZy3Wuvq2z6nCZTcjXPKSxuyJeJpGYozj0O0p\\n1ERv7z3BR7CmgnWkpOjfQ3dlKYXWKaIyqDGxlsIaEzU7I5FSIJbKWFTa1JI6aoJPSGc5HRzFFIw0\\nxGWGVBORSoHWDVhbJdTaj7RtAOtY48qDj5WjpxLeL4iiyfmekB2NUvT7BqELxQdMaxh2LR8eP3Jd\\nnxB5QlpPEImDOqCFgzwhZMvkA7YR3N7ectMda/JyKphSEEZDlLRmT4oe3TTkFOl0YlojWsGu63h8\\nmHj37rcYq+l0Q9aGQib4M7bXaHNLCFeUuaBzi49nsojcnnrOjyNPzMhcg2yX2aP7hkN3A+tKP7QE\\n70klcXO6JSfB508faBrH7rjDOYdSEq3NljyVUc7i3j0yLgvrsjKPK7p1TOMFkbftiszVzFQ2Q7Yo\\naC057k80xiGKp21bQggMuyMpLlBASYEqGYUm6xpsS1kI64QyluQfOT+0nF5+VYEyzuCERCHqm9FY\\nlG0o+oCwket85fE6cdod+cOf/BFTeKR1PWvILDdLxdCUyOoTnR3YDcOWmyH45tXXtK6tSlwtkWqD\\n70hB1zU0TYvRhjCstE4xL3sO+wNCKELwrOuKNhpr7EZbqqpTaRTIgg0zfrUYY+m7Adu2WGW23JFt\\nnS4ySkmMNaQCJME8LyhVWxVjdBWg9R2iFKSuRjsy5DXiw1LXxcXRdh1F1LnH73L9OA4GqsmnaRq6\\n3pNDi0QS11omJ59YY8FpSbYFZatOXGhBNxiG3lFkQpSCD4qwTiQ8KTuc2SHoEEUzrSNHmRFMnAbF\\n5w8ZIRVCGEJeyVFQUiIVj3E9tpH0Q4MIIJzlxd0r3r7+Q/7Pv/oXXO8vuN6hRV1zHQ571mtivnro\\nCsWBVgW/Ji6XlfPjjANWErbRWKNJupBLRGFoh45kOp6un5ACdl3Lsl44Pz7S3NziXEOYEo2Deb7H\\niG8Z7FeM4RdIqzC2Q5WZ/X6HpeG8vqcUjVMNRWcEuvIcpSLkwjwv7Iaexlnev3+PMZpd21Xgrm3R\\nRuEaV9FwMZJUYjcMTCFwmcGHQK6c0xoYYw0hRZTcNkmiIEWpD5LUPDx+xqhC2/WMl0d2w4B2PSVu\\n6P2Sq6FKW7LQxLgiFDRNCyKgpUDGQFpmlOmeP+h5g6ZI10D0lJCQtqVte3Y5V6Cv+Yq7u1tkaTg/\\nPBJiJFN4PD/SWVO3LD5U4vU6UWKkb3uUFlhlUUoihNxUsQ6/elKI9WUj6yGwmSbQSqF05WTGGDHG\\n4GxduXo8vdnROIs2FuMs1tXhehxzTQHTepu1ZGLKIAvrumyqT4UQglJyZUaYmvGRcyasgVLiNlSX\\naKNrNF5OSKlR5fewYiCXCmJpNMYZrAlYLEsRLClWAU+prjJQSKVR0rA/dFibSaKy87OPxFnixUKI\\nC4XAuTxsOG6DiIWlRJxcKz06Q46J3SYbHudIDDVn0ItIETOuVTgtkU3m22/+Hf7o23/Abz++4+nT\\nA9ELsJqcDK4xyKbj4/1MyAVMZpof+fj4W76//xXvPv3Ay64jWwEyMa4XdFbkHIDEMLxA2UJOC6qs\\nrGLEtR1+eSSVI41riKUStXOYKCHR7V9jbOQ6vUOkDlkSQzNwfvhU0XYoBtdwMK4mYeeM1IbVB043\\ndxA95/MjIU4cDkdub088ne8RwmJNA1DDZSioJOmHjl1YuUyeJyGYl4WCwKiKPlfCYF27rf0KsmTa\\ntocSuF4ecdZgpEAqSyqZlDIISRZy66UVtqmZFfv9Hd77atpKEe06huMtpt0xB0Eq4JxDls2WbBzZ\\nfwafEaJlaDvmdSEpw8u7W168fI1C0huLNYaI4Hq9IkjEnBivI/M8cX26EkOgbTv2hwNDn5Bacjzc\\n0HcdWldjmlBfyEojMaY6EDeGGCM5J6ZprLZ/KRCiYVnqOlcpiaRFKqqlXklS+BJvUCnZSEEMdS1P\\nAets3ezoOj9IMdWVfdgqtFTJVkJuYUCANYUiZP15UvK7ThJ/HAdDKRAzaQWFpNGGXCyisazLBAh6\\n6+oJq0DoUnX6IrP4SJoLS/TkJZOnSNFxyyhIXMJnlnVmdi2NyCRTaJ1l4YJ2iuuTQCWJkz3SRK7R\\nE0pFaOUiECogTKA3e16++Yrj7objcFMtzqmQERRRWIonxMQ4BQiKRQUul09c18S7h19RXOScZlxu\\nWOeFxXusa4n+itKZVzuD7BTvPnucha7viH5lXq4UItI4nNRknwkhYVMmhkizv8GnlSw9qUAMColm\\nHq+03YCRGWMkloYWR0Hw5tVLnh4/0xnH48PK0O/Z7fa1ES7UUtiaWsnJ6tSzWmKtoXENrrG0rSON\\nAe9XpG3RSJqmo9sNCFmHw4pC3zjSeoVS8GvEdpb96QU5LTVoJsWazJxX5slj7Q05V5Wocw5hLG9e\\nv8W2Hd3NC4RqsEjyeEYODY2t7taSMzJQ8fTG8AXZ3jUWLQ3TOBJDVVWaxtFbx9B3NE2D957r9cL1\\neuXx8ZF5rp+5moQaIOvaVmmDtQajFSGkTeVZhVdKKdK6GaByRusKGEq5krsKYLE0TVOHw6U8I+v8\\nurE+v3AnJBt+sKCkqgYxKTHPB0P1koDAGFVl7FogtURIiRWSUgApUFsS1+8nwSkXlhDhGkAsNK7j\\nafEUEekbg08eZRTGFZQSzNNIzoYQBf5SuN4HPo0rksJu19L2CmEyi5+JEbJ4REnF0O1xUqFsoTt2\\nnA4RpyIlSXSzo3eKKT+g0kSOkugzTz7Rtom337zhj7759/nFb/+aJbzj5cs9799/YBWatrN0Lw88\\nDgtd14G3yBx49/mKkPdcLn+DPiRSSaxAlokiLtw/vWcOsEyZy4Pn7nZH39QyXkhP6VdO6o4SW0o6\\nIIUFqdhZy/QxcmsVFsut+g4/R1IJ2Nnypv0Jp5/+U6zWWKMqd3FecE2DSJ4PHz+jysr7p88cb16i\\nhKp286d7RC7PuYo+zDh7AxlKWnFaMLQtp92e6brw+FSVo+N8oRt6pFY459BIsq4zBkrg6emRV7d3\\nkAXJT1we3vHm9UuCHGhaQ0kRLRVhXRnPV0zTsmt35JQZTgea0wmtHaLtYaHGvYnCOj7StC9h50g6\\nombD49NnPn8+b2W8Zbfbc7lceffuXdV0aElMgeBrIterV6/Y73e0raUfWr777jvmeWYcq3u1BuAI\\n/uZvfsn33/+a/X7/jJn74h6FGj1QH2yFtY7T6fRM6IZ6wNoNUDNO18rddE2lX8ma3LUuC9M0kWXG\\nGFurr7SZ/CIkH0hbG1TTs+p6N4aF62Wtgq+ttUDwLDn3m/v1d7l+FAdDfVElNB1Zr5QUmJYLJVMZ\\nDVIyT4EcVeUbpkwMHqPAlobGOsw5okxN/ilCVpI0GpEjOdesg/N5RiIZnKHRDU0TEc7wcF45aI2V\\nA0Z55nFCyoJWGpENa0h0bkAXwffvf8tleofSkpQD0+JpbMK1HS/syy1TUJKWUoekIaBti+zAe1/f\\nHjkTcyKWhPSW3hyhaHrzCuMsQlWgiTQarSQUR5EWIxps27CsES/OyFVhbItJ0BmY00xnW7qmq/6C\\nmAnJM81XrmsiIZ7TqKbxghDVDaiNJQvBZRy5Ox1rWGuMG969sC4LMc6IAlrVnbxrHMOw43y5IEUh\\nxkAMkfP5gixUglKK3BxadruB6/VC13UoYyhjIIYVqzRWOJJVdMbhVR2wClHIOeJcR1gDbuiRmEqa\\nKoHHywWdI66RiBCgHcBZrr/+Nef5zOoro/HL2/sLN1JISc6FeRxZ1pW+y7x//wPX6xmoZXmKVePi\\n/crDwwNKKdq2IedKAHt6esLaClZVSnFzc/NsKGua7rmUN8ZijCHnigBIKTOOIzlnlmUhrDWspu26\\nTUYeSSWzLitpC9V59bJH2ooKnOapqhpzoW1bSikoIUkbD6MWOIWSE9M4siwr3q8415BSIpXfQ1CL\\n0AXpAqoISuNIMdJ0kg8fFvrG4FzdAUdRkDKC0hQHSguUkVibaVqJshrtDDGvSKuxFOY1I0I9XHxJ\\nSALXFdRDQe4sXs8oa3C2x4odRl6YLzO2UaAjshikFoS88sPTO374zW+4jE+IopA6kdPCuF5xWZH1\\ngGtHHq5PCKHZaUkqGVskSlYMes1mbDCqpVcSZRus3bHrTvSu2sdLpO6kiyaFwN7sQQla0yK0YBkX\\ngtvKZSErvYlCypWIJKRE5MpKDDFTUn1jaSTS9lymkSJAZYfIbPZzTwgZbRqapmGcrmhRSHEmp4VS\\nIlJZlKy4vOQnZEmIkp9TsGvJahA5IqTg0O6Z5yslzOS4VH6hD5WGvKxIa4nR07QNoRR066rBbRu8\\ndVqjm4HL50+cXn4DRSPzhCyQo0fJFnSufzZ5x8M085vffM/jvCKKwDqL0RofVqSSrMGjZN3vS2Nx\\nrq3u0XXdHuyGFK+AoJSEtfWNL6XgcDiwrjUdbV0v9H0P1MNeKbX18XWYHWPCj1eU+jJIrAPEEAIx\\nRUKsMvXiF3wM1VK+lf7TurCsI03T8fLFSzSa83Th88ePPF7OWGv59u3bLRG8ism0ltv2yNZ18Tlw\\nuT5U1akGqetQ9He5fhQHg5RQ9ERjTqzO4aym3Cme7hf8Ck4neqO5LnP9C0aP0ZX9b5yiZMXNC8sS\\nAyVLxhjZtS2tE/VNc56gSIwQpAQhCQPjyUsAACAASURBVMaSESWgh4JyEVkCpQQQMI+ehCEbgxOe\\nm5sdIsG7T3/F5frEMkFhxljJeJlQCu7PM22vyFkjvaZp9pzciaxbnHyJsZqlLYhUaM0JJUAWi6Zg\\nm5797kgqhXWZUELSu55lmfGjZ+lqD+u0xUlH0zpssQS/4n3gS/pYzrWzHq9XnmKs8FQpKRSMsSil\\nKDHiuo7iBa2ujs3dbuBf/8X/VcNjqGXyNI7cHHasq69IeARGOHK+oqTEdQ7/sbZoUGPZtDacjidy\\nXCkUoo9AZBwvdNYwXmc0AWcbQvR0ThPTQsoSqWqKdN/1xBDqSnCrCl3TIY0F7SjNgaPsWSZLsz8g\\nGgVSUbCo9sA1Gu4/v6OUwu3tbbVC2wYh6wfNqIZWG9CCpnFoZ54f7rZtySlvFUFL3+9Yg9+4B5oQ\\nO6Z5xuraPnwZRCpVNyTrutJ01Qq/zCtGa3a7HcOwp2kcT09PNJ0jhMD5XL0mIaz0/Q5p6iG0rDPj\\nNIGQTPPIXAqXy4XreGFdFgBiSVhZJyBKSaTRGC1RWpNywFjD8XSs84qcUZsm4ne5fhQHQy6iDo1U\\nQy4epwLHQ8Obrw78+hdnwtLRWMBIUipoFFpC3/bIlGve3xqQU2HJEZMFJoEuClxGHDrWybN6jadi\\nydeYMV5ihMbaBtBo5fiDV2950eyq003V3fPt3Z7bu4EmSY7tHePTGWUS+31POSistOzdnqEZ6MwN\\ncpC0tuHl3SvENkWWSEpJFOA6B0SBRneb/ZvKE6SGoTZti9ayqhpT5nK5Mgz1DZVz/TCM58vz/Usp\\n1a+N8XmXvgZPSAktq+Ep51y9CzljTccaCjF5Dr3jF7/8y603rhkJ4zjjw8w4Cw6mgrmUUoTteyil\\nMGqzTC+ermtpuw7XuJrVoQXzspJz5vHhE2G5cHt4yzxNKF3Y73aktOJci1/m53Sr4GekzHRbSS6E\\nQJAxpqXYBnZHil0pq6Y/3CBsj3ANRTskGmUtu+NACHeUlCvJOheUqm9TpSXaajpj6rqzMZggEErR\\nugYt5TZXEDhXW4Em1CDJL/zK0+FI07RYa5imaYPfRtbVV2v45QzUKD1JQuk9h5sa4VMFS9WVuq4r\\n67LUtW/2GFW/37KMLPOMkpr7z58pOeF9QCnF7d0NZptvLMuyzTcyFMkSC01TZxxt29I0FikLT09X\\ncobw9w1q+f/iih7uP4G3H8CNXJ3imxcv+YOf3tEPHb/8y08Mhw5rAkZquqywQnJZPLazxFB7+bxG\\nwtXTDQoHqFzq221okEoSzzNuyYQSsXuDGArCOv74Z/8Jt6dbdt0Ray3OOW5vbhH1DKnhtaLGj79t\\nviOEP6FA9cCXgBKS4/GAM5YiBJLyHDdvjKnlYwgsS8CvM61daxmYcsW+SQEpE9eV3TCQS+Hp/MRl\\nqg//3ekGrTXzstRcCGv58PEjs1/xIUApNG1L2zR0fQ9SIq3BdB0CtuFWLfVVKkQ/U/TMZRyZvv+A\\nUoLFr/R9ixSWx6cHum7AmDrP6Lq2CqXGhZSrlTgsnqapgbS239Hvb8gx8fnzZ3zYAn+XkZQTL2++\\nIsXq39gf9kDGiJYiDEUnQsnsXAeAknojRieG3a7SuKVA9C1+CVjRI25OSLODbQknRCaHhQ/vP0CB\\nfb9nWUdKCVjXcNgPz739ZbwwuYZPnz/zzZuv6fsGpWoWxxq3+VVaUVoT4sLj4yPTNOFj5Ob2htdf\\nveEw7DBfhoy3N3jvmeeZ+/t7lmVBSsnueKDbDTjX8PnjR4zUaKur30IW+l1Hv6vE7BwiPkamEXa7\\nPUaZqnLMAaUkfV9t7WuckTpxHetnDgQ+RYqvG4z9fk/jHNM0MU8rMWUaZ5Aygfm32pX+H9eP4mCQ\\nCHpzIC0r3WAhN0zTyvHQ8/bFkbAEztMEObOmlZuuZ2gs8+cJlRNGQ5ARXVZ2jSQC4xpxZBrjcEri\\ndnucsSyjJ8qEdIVu53h78xO+un1N2+2qkEUWYgpApoiaESiVJvqEzImh35HilxI+4KRGKlnfQmSc\\ncXXQs3lQpBQYYTbtuqZ0DdfLSE4Qg2de17o98REkzMvyzA9su5YYYqU6bRPnL96WlBLXywWp1Fau\\nDgghmINHWY3VLUa3GGOetww5J0KOz1uIYlqm63uUrAfY0B/xYWEYhmqbLqWmfMeIM3WYKLcJ+hfA\\nilSSvt8zrSvXaaXv9zw+PVCK4HbnsBYu4xN3pxPX64Kf6z7eGcv1ekHpgpIWrQ3LMleBj6jW8LZt\\nsW2POt4g3IDWPXH2iJIoFKQQ221WCKH59PmeIgXT5cLHT+9o24a+31U9gVIYrXHaopHcHG9RyhBT\\nQgpB3gJ4v9zftm0RQrBug8GQIk3bYK1lnmcepxnvPf2u36o4xel0qm2HMYickELip5lxHDckXY+2\\n9SXQmJqunXyow+h1xciqjehOR3KuK3lrNGmr1MhwOU98+vyZpm3Y7/ZY53BC0DhHoTCvK2uMJBJC\\nF2xvaUzD7+qJ+lEcDEIIWm3IueoVbKdJaSVHTW8MX3/zmn/1i19h0EznC35wuL6jnVrSPKK0puky\\nWhtiVqyxw7iOodXsu57j/kDf71nywrLUeDhkIuH56vQztJMUE/EUTNHkHLhMTxAzQoNzA6JUWIdz\\nBu8FPiWc1M9o7hgrqAT0Npk2tf8TdRgKmyBHSkIIGO3wYSU/FT4/fOZyufDy5d1ziSikIMT4/G+7\\nRcR/6Rudc7x8VaFZXdc9J0WHUoNh9vsDSjooFeleVH2ISom1dUmZGDIpZ4wWW7jPANQUqFIUMQba\\nvoNcSLEQfPVACCmQUgMCiqA1DcJavC8sq+fp6QnvI6f+DSkFGmW5Tmdap8kpMXvP6eWOxY/kUKf2\\nu3aH1op5mui7+rCFEHBdQ257Vh+x3QGMQcSnTQEoEKX+MRCat99+yzxPLNczKVWg7bt3HzgcBqSU\\ndK4Dkdn1A6btuYyRnOqUv+ZlBGKMNaF7Y00IIXj18iU+BNquI8XE09Mj8/mKVArXuuoO3kjZjas+\\nm7AuiPjlQK7I+RBWrucRckGZWhnN84xUiiJqm3h3e1tL/1hZmblEtNZ1TrGtTouo8GLXtux3u2rd\\n1roqSJXENu55C9G2DqdrBfK7XD+OgwGB0gWRMv2wr3qCVbP6QuMUXaPohGFdA6JY/JpZItx2LUE5\\npJIc9xqhWiQOZ49o02CMpW1aGuNo2w7T6HrDIzxePvM0PTA0PQVBDvXG+eLRxnA9P1WhinUYaenb\\njq5vKTkiRSUOxVizEnJRjLOnbR1rSEhZ5avee5a8YK1DSF1nKTFyuV6BClM1zrHfH0mp1PJdeVLO\\njL7mGCitwBqmdUH4ugvXWtPvd5QSyDFRSiKE+iE7nV7RNifaZocUkuBXYqihquu8MM8L5EAJEZ8C\\nzg00rud0OJJEpKwJ2zasy4xUIIpAm+otaduW87QgpabrFG3fsZ5nLmsijPdIYWjbDtd0+HBFyESc\\nErqTJO9pW03X9Tw83FM2pmPwZ0zXEcJK1w/EEFDaoKVElkIxPWp4XYfIxSNKhiVQeKLYfTVNokBq\\n/ugf/Qnnj79lvHzgkg747z2X6yfafcN8nbl/fGDoB1KG9f5jtYenyE++/UOUNcx+ptGW1rWsy8g8\\nr3Rdh769JafEdLkS1xVSRpr6Anh8fCSnRNqGvTkGGKtkf/UrbBJlayFlmKaJcZzQWqK0RWnDYX8k\\nhMB+Z6obdUPnxRi33A6BUhq0xhjD26++AQRd1z3rJMIWjSelQEmDUgahZfVSqN/9Mf9RHAylFFKO\\nSJlwuUGxkIPgPC4opdDK0rUHrpdPGKVoSocIAy9OX1eYi21QxiJNU337tkNpg5KKQq6tSt9yur0j\\nhMg6VynvGjJIUbMIVXreR6eU8KXu8tsmEawFZ0nRo5TcBlN6cxLWCPXL5YKU9Y2uNrurD/XwCDHR\\ndQPee3JOm56hluzOtTSNo2kc1mq0rHkNea2/cL0RqGuad6lZlFt7sC4ev64oo1nXVAEmqmLqu67F\\nL/5v8x6932AqhfP5iRIWlNIc9yfE5iT0C6iSt4qiVNxbSiidWdcJISxqw+9LUbDO0HeSeZ74dP+Z\\ndYm8evWau9sT18uV8+MTu96ipGRdI27f0zQNbdswzxNCwtDviT6yrjPOVUlxezohlcC0rs55iqeY\\nAREjeXzi+u6CagXti4Jo9n/7OdKG3au33L36lvcfP9G2A6+M5jDsccrVzNFSdRlL8EAd4j09PdL1\\nA9fLE6lpWKZpq8AmxrFjnufnlaS19d5KkYg+MU4zMaVqkwYQaUvnEpzPZ1JKVe2oFMfjEWMUh8O+\\npnUJSdNW9eVut3v+3ULN6hRCME0TJSUKYiNqRwQZaxoaa9DOQqFmhgi1EagswhiKVpuZ7fc0cKaU\\nzGW6pxEKPyYokNZEXDMf3j1yczrR257eTiiheHn6mv3pBbf7rxHkig9vOpSufVuREq0lkJ/zFFOq\\nePmcI0JUNt9pf0KIQIzheWVljGH1lVXovSetnsG5ejjkTH/YbQ+mJPiEENWaO5SCs46cIcX6c1Vj\\n6yR/W2/VcrNKaff7HUppxvHCNNWVZ5gmYkosIcD2dQUI61o3JNRAly/Q1XWZKxRk9VXdmNJmF3dI\\noWAT+KSUCCEgpdyAKIIsRL0nFJJfyUrjnKNpaw8tZBXpeL9ilayHolTobXUnVAEyTWe5ngPL4lnm\\nSozuu+qg9LEghCXEgLYapMKvma7rmaYZazR915N8QkpFznUFmHNGty2xQDkcK8j0/IDQFlkMv3j3\\nK765u8O1Z4TbUWplDRSE1Lz6+qf89he/QGSFFGC1ZGg6wrBnnEd88DSubhZKiczLwnF/pG9alk3e\\nrLesiHEcmaaJ0+lISpll+QRkxvmCNobGNAzDHikVy/LAsk6bytGxLFWNOM8zIdSQntbWsBkhqlfn\\nC7Pib/UOte3UuqZt55zxIVROp4LoPdPlgjcraZs9PWswRAQliTmgs6n6HVU/B7+XrURVChqScwRf\\nyT7LemZaFlIuXO8n/sEf/RP2371hv7vlZrjDmW3PrUHrBmurjtxIQcyVKmSdox+6ZzpQXGu/Z5zk\\n7TevcEYDkvvHB5Zl2d7oteeFCn15fHraDp4W13VoVR+6ms8QMUbyJarxfDmzes/1emVdV5xzz1sO\\n2sgwDHi/cD5fMUZV8m+ob32tNU/TFZ8i5MpLEEojiuDYt1TRTf05MVbFoqRGlGlnmOeF63Xi1auX\\nDMOAdvV+XM51fVZVeFXGq7SGqOrhkSKNaXl5OuDDyv39ew7dwP4wEOLK+PSJu+E7StPhk6QIhXOa\\nNmSGZuXTw4VSBnbDnrapk/+PHz+itEZJQWtsHf4JR9PvWPxE8hf6rmO+PJL6F9hmMwltBOmUEiUm\\ndvs9tDcIPSAGx/mH96T1ysOHe+6OR/YlI2IAo6u1OCeKlJzuvqbdnVj9QiZvD6vizddf13Xx9VrZ\\nETFSCix+5eHpEeca3r79FiUN2tQhYkppy+KsLUGMkcvlUuGwi+fVq1coJQnR83Q+8+vf/IZ1XfmP\\n/uSPefPta5q+e67Wfv7zn9M2PYfjgcP+UFuPbVsyTVM9BHwNCe77HoSoOgpRYb6Nc8hObuvRtW6Z\\nVJ1VXC6Xip1zFl0EQmv8lnZen4vfQ4GT1pJDv0MiK003BJTUWO1YxxXb9JAdx/0dN7sD1rr6tku5\\nrm1UIkVRWxJR/e9VF6+276+3Mjyhjal5lcpsP10y9D1d1/H4+LhlI6hKAJYCqRWfHh+IMbJ/OPDy\\nxd2z/fXf1KZfx7qaa21D13Ws61r39kpxPp95enpkGIbtAR6ZZ00IC+vqyalgtK3IdqFIYmsjNt7h\\nl0l3zuB9QErF7emWkGqoaswJIeXW+3ekHCBUscyXA26a6n8rJZnHgETg14Qwgt1+t/EUBCkVlKyU\\n5WkaKZLnFsR7j9C2WuFzpDEWqy3xupJS2Hbylb6Vc+GbF2+IfqXtepxzXK5XWg3TPFUGg9Is80wK\\nCec0Xkm6rqtE6a4jpgQKwnKG7Bhe3pDuIYtcw45lBZ0+X3kCMVAovPnJd4xPn7hOI+/fv+fu7iU5\\nZ1rXoHY7Yoy8e/eOkjLrZeTY7zjsdrRNUx2fubYElc4E8zyTc91cOOfwfmAcR7799jvWda0lP9WX\\n8vHTJ/7q5z9n9iunmxucczjjuLt7QYr14X98fMJas/kualUnhKDve6y1WGufzVVVfp+qq1Imnh4f\\ngULfD8/Y/hBq25hTNRD2qq9bqOApuRD/Dar73+mZ/H//OP/9XVIK0Ak/B1bvSSSUpQpuikPpHVY5\\n2s0Ek3JNlVaqknZjSgit6hCoZBptn91kXyb5FVpqnh1sISzEWKEi2tT1mdWGoOrXKQGFOrHOur45\\nlmWpODEhMMY+VyJQp8ghhipUynkrFetkPYRQD4p52X4+NY5NihqMC3W4RKEuPTVGW4ysM4/V11ah\\nvrFCJQhNI33fo7XiMl7rh77rqKmDVf04XUeEkDRNw3WcthmERihNSQnbdOx3pjIl/IpRhrZtqqcw\\neq6XC0Nfce7oKpRqnEKUerDkkrFacR3vuYxL9SWkTAhrZT3mO/rhiLGa6fxA1zgSiuQT2SZc0zAt\\nC7e3fc3AnCZs0+CanhgKL958VenQriU+Lnz89J5eSN68ecm4zPh5Qu88wrV1M3S5Yo4Vsnj3+jU/\\n/PLA5GesdTw+PiIE7LqBoR8YjnvmX86QM1Ipnp6e2O/35BxJ1OgCv2WD1vmCIIS6lhyGnnGsyd5K\\nqeoBUZq26+h3e053t/zZn/0pv/7t9/gUuT3eYg8tb15/zefPnzeAjX4WnjnnMKaglHo2aJVSmJeF\\n4bDHNk3NIF1njDFcr1ecc8+DyWWpM5AQPN4vxBgZrxdcY/FrJKX4+6l8LMBcFqIImMmSxYKVtfzU\\ntsM0PU4blEj4sGzkYIFuNErU9V2iroDImaa1VY8QPULUwJmUKjLsi2psWSaEktsDrMgxVCaAKDW6\\nzVXpqkBg21pBJGopnlLidDptvXvEmHobL+dLzaRsu1qu58B1Gmmbhr7rNv6kpW0bZKtIKW0rUPc8\\n9FRKUICcyvPM43qt+v26L9coJXm8nFnWhRQjMScw6vntLqVkmVbmecS5lhg9MdQWRyrN4XBC5MAw\\ndLw5NlyfzvSnE3GeOdmG8fLAON6jVEYrRwKS93g/Y9vq/pQyb/DTTCy11E4p1IBhwGhwziKsIfiZ\\nVsN6fcLshmedgnWOkOs9POxOWKsIPtK+7rbfSUJxQEiH3reYh3ueljO3t7f8/K//gmkcUeMTzu7I\\ngAyCvJwR7Q1dsyMhuIxX9vtj9aCUyLiemVfPmgONM9ihYw0LhYRUMC8jPvpKllKKpyeBlHB393IL\\n6P3bw945x69+9Sucq27KYRg43tyw+gUhCvM8cXM6MnQDUiqMsdsD7Z+NWLWytShdnvNTvU/EEGi6\\n+nkVVKBuzlXMBHC5VPHb8XgEqq6lFAihql8v1wtSCs7nJ6zpquv3d7h+FAdDLplpmclkXLvC6ClT\\nZug6aCxKWpQUVbghany6lAK7VQChWikpErQxtQyNYVsn/q2c9QuBuK6B6w5cSIFfVwJVvSa3LMS2\\nreKgOY0oIQgh4qN/Ho5V+eqyPdzmWQzjnK0kI6UwQmH7lqHvt0qhvmW6rk7nHx8faZq6laiVDZtp\\nR3K9jqRUEJvCrutapBR4n5imGa0N58uZcRyrhbe1tL1l8WuN4ltXns5PNE0k58I0jazrTN9ViMlh\\nd4O1muPdkbc//Xdpu47Lwwc+/vrPSeeRw27H9XLBNDumEAjTiFQaHwJCWVzXoaZIzCOdbWldS8px\\n+11kVMloqSFFJAZBPbCmZUFbwxxlrQ5c5Sn4VGPpe2uZroGvvnlbc0JFSwwjKkVyTPgYaZoe75eq\\nupwX2lMALFEp/IcHum+OCGOwtqHExLQG7u5u0Ebx+PDINH3i/ad33N3cIJD0fV/7cGPIKeMaV+Ep\\n2mwbpImbmzuca7her4TgnwVI03Ql50Lfe9Yv3MuYOAw9Q9tsmDyxtVieUtJz5ueX1XNlX3iWZUPu\\nx0wpIDKYIqtblQoqWtaKx1vX9dn27b2vYihb//Fek3OpTlDT1mH076OJqsRC8QKMxDnDnFaWkMCv\\n7BtbQ2MoxFRQMsP2d0zb1K+KPqirzKapgNRt+v6lZKua9i+lW0OMla+HEMQtoUpujjW7Pag+BGa/\\nYoVESUHXNNU2vB0yWiuk5Nne2/ddfSOk2jO2xtK4hr5pWKPncNiRUuH29g6lJOfzE0op+n7AGI33\\ndQAqhaB1jnn1nJ8e8cvCsozb4eG4Xia6wSGERDcWGaudOMWEXxZEqa6/h/snhl1GILiOTwhVqpIu\\neYausgAeLiNZKA77G775yb9H2zZ0h++5//4H2kExhaU6etN2P6VkDQFQoCpo9OZwRMhKPg4xsK4j\\nmsRxv2d8/MzxeCIngTYNi68Is8PhNT4X9jcHjNGU5cJ1qlqGYTgx+pX+5kgmIwqsj5+4vbll/lAr\\nuxQjHz5+ZLh5UbUl0lK0ZP34QP/6W3CGrtvT9C2k+vtqm57fTu/58PEHQNK3LV4szw/a9XqhAK3o\\nqkrSOQ77jpT2TNOEczVAx1rLbrdjHC/Pv3OAjz+84/2H3yKN5p/843/E/nDkfD7z6f6eGBPONhhj\\nnoeYQtSZRN/3XK5jZSvESAhxQw2MrIwc5QmRv2gbEkLk52H7p0+fAHj16tVmFb/ncrngmoauG7i7\\nu8MY/bwG/bteP4qDQWvN3fFNLX/HTBRQpEY0PaZpsEbWVGBtMU4+D4C+uAfrcDE9T3Ov18uzW+6L\\nP74CLWp5LlXBmCN5aw2U1ZRc6PquDmpi5HJ+YrqOnIZdHToh2B8P7I570iYmCstUJcHdvqr1ZEZr\\nV7ULKRKXuW45lroXL5t09nJ5RAjBzc0RKTXn8xmlFPN83XiBy0YVuhLWFdd33H965P7zPVJpjLHo\\nB1MHY0WijdtoQoFxvJJSYVpmRMm8+80v+GgNrXZ4Kei6nmO74+Hxid1+x7ff/Yy+6fApc//+e9Lq\\ncU7zj//pHzPHRE6Cv/7FXyDdBasF1/FCTJCjICVFypLRj9wcT5AzypjaFyM4r1dilrQIhDvy5u13\\nvHr9E67XJ372s58xDHuMtaQYK+tBK1zJPD1+Yt84itn93+29WYxtWZrf9Vt7ns8c50TEHXOurHZl\\nVnXLjenhEXD7oc2becBGWJgHy4AED237pSU/gbART5baAskgjAWyES2EhGwLBA8Gd3Z3TVk53syb\\nd4zhzHseFw9rx6nsysrsLDpd9yaKT7q6ESemL1bs/e21vu8/AA0aFl0tudheUNSq6Xm+XFMVLXde\\neAE0m1bC5t67zO6eIg2JkB0DX+fb3/gWZ6szpOywTINfeP0V4v2CH737rlIB7yc+hmlgmsobtSk7\\nQt9D1pK4igmCCCkbdAOiQUTTtKyWa5arC2bTBYPBkCRJ6ITg+MYpbdvyvR++zRtvvEE0HJKkOet4\\ng66p4u+4DppmIquUfbzl/Q++T1XV3H3hRcZjtbOp64rziwvCKGDzYKv+xlJpawohkLRUlcLQCE2w\\n3WwQukY0GDIeT9FNE6M/rriew4GC+2Xvya/4Hv//FJquM79xTFW1fPDOPco2x9ZMQqGgnp2UeL6D\\nZXrohqrYpqmaQlcsPCm1w8z+CmSk9Ww5z/OUsIWuXq+qoj/bGTT9TqJtFfOxa2ryLOVoNiNwFRw4\\njhOqtkbPdMaTKXQ1u92eYeTjug7DwRjLstCV7hx1T6oRlnmA2KrmUI3R4yTU5KI6jKjatmG1ukRo\\nGmmeUeQ5dFJ1wOOYtmmxLF3h7Q2TzW5/QMl1XYfjOGrunsVomvpZaZpxdnaJ67kMohDT8zFNpRw8\\nCD2GwymOF/Q3RI1jByxXl8TrJWbXMJwtkPi89OorrJ5cKsyG5VNfXpJ1DXXb0XbqnFz3O52yrgkM\\nD8NysG2d6fAWXugxHEXcuvUiTSPxfB9Nt9F0A8/1KSs1rrVsG90fEHnKa6LtUjShgSaRCCanp7z/\\nz/4PjLunJHHMjfmx4qS0BUJ3ScoUp6yJNAvoGI2GnBcxnucerpO2rpkvFlR1x7377zNpZwAMemWm\\nfBeTtwWGqZqKXSvZ7TZUdYG5N4iiAVVVst/vKXIFElMTgZYwCjBNQVXVJEnCW2+9xWKxYDgYqbFj\\nqSM0NQ6XpCB7vIvu4nouaVETP3iM7zoEgcfRfEpZlliWxWg0wDDUtVPkBUIYeJ6L53kUVUkURQrD\\nYtkH7kaL7HkpJl3zFVvU/TyikxLH9wiHLk8uz2l3BW3ToWsmrVS6gZ7v41gupqUfjgfQAbJHiamt\\n0tWo8GpacAXqEUIhCkM3opUGnbK2RNMVJ8A0TTopKaoS2bXQNpiaWtzBIKIoM8oq5+nZY5panTu9\\nkwWh5+K6NtvdniAK8Dyb0BtyWVeUVY3lOAoLL9UfqemLVxzHh15BEAT9iK9TEGskhm3hGhZZkdNU\\nXS/7lZCnJY7roguNqqoOF85VgdgnMUhBmmbsk5jdPibPSvwoJHRtRd21HQzbxXZswtBH1g2O4xFr\\nMaPJmHL3lCrfsLussLwZVVcjdRvL83AxSLOYarVXkmFly9OnTzldHKNrGlVV0jY2miNw3BGOH+D4\\nPn44wrBs1tsLZNdg2jbD8YiybZBIpanoeHSujWvdoG0LZKshsGnJwDCIdxuk0Pno4/vUZU3TdVRV\\nSZPuEUZGJwyKsiZqStqqQEOy3e7pRHcAAgldIxxGnEi49/GHVHWFQKhmnvQp8oK8Kimrkjt37zKZ\\njtjtNmR5QZZVxPGOsqzYbrfITh0DHz+uCIIBg0FAXVes1yuEEKxWK9I0ZTQekyQp4zBC01Vz2vMC\\nhG5gGB7HiyGT6ZROdlxcXGAZOp4bsNttKcqWKAwU2ct0FJ+lEwitxbEdgiBgYCreioag7ZS4rWma\\nVGWBLgSNppSefpZ4LgqDlNDWz9YNYgAAIABJREFUAss0GI0ipVHQCnTdxnEchoMRfuBj2yambvVP\\n/ZK2bQ76BFfz3ytAx1XVvFLuFUJQ1ZWaGjgOWa5gsUVZKDEPU6EKbcvA0FwMXXEdOmA2O0LKAUmc\\nsNouybMExw2oqppdU2PaNouTE9I0VseYSFGB66bBdtQ4sigK8rI8eD0qQEtL0yhyTds2+L6vsBCB\\nGt+JFjTbRJM6VVmqMWJd43ouruuR9zuOuq6J41jpECYZZV6R5hmb/YbNfgetZDSfszgOGA+Hqldh\\nWAp30NT4foDjOGAKXKMhW4aIcsvl44eEow7NNgm9Ea1pY3kSwwvRNgldp5iBcRxj3bpNXVXKmbpt\\nKasK2wsRhug78IIsyzEtB9ex2Md7juZzyrrGtiwM1wfTRusEaCa68NRRQgp0M8B1h6w++AH+YMAH\\n7z1FdpDkOft9gm2tyPOcpEzIH31EutsSeSZpEiOFGg9rmqZGlhq4vkcQuhwdnVJVKWEYomsaruth\\nmRZFP16uuobFYkEQuBRFCgjSNGGz2ZLnFdPplPc/eAfLdHn99SlBEHF2/piiyHEcj9lsxnq95uHD\\nRwrtqWXYjgFUyK4kiGzGoyGe52EaOk0jOVksejFZk7puGY2P6GRDHO8QRotu6gyiEUXTYBqKqaqb\\nJppm9NOnBoRQvbGeFiBQmhM/SzwXhUFoGlJzoRNMwmOKTFAXBb4TMJvOOJrM8fp+gdWPfCzLOMBH\\nbdvuu7/6gZ3YNA2e5/0RT0XHcfoiAV0vCFrmJYamU+VKemsQBHRdy3CoiC1SCPI8IwwjbpzcIBwO\\n+eST+ximxeVmxfFsQhCGjEYD0nzHxfk5u+0KXdOpm5qiFDR1R56r5tYVjsIwzJ7t2LHbKcIWUqo/\\nYqcsxoRlYLgWujDIs4y6GSClxPc9pFSMyCiKDr9f27Zq+ys72kQhSMtSGZlcXFzy+uvfRKITxzui\\nwZAi74jjPb7vkRcp+9Wa6SgiCCMutk9Zb3YsbrxOLVocBzprQNZJLD1QugldQ9OUDEdD9rsdWZ7j\\nODajwQjfi3pknvVj3EfvL6lpGmEUqadb1SiKs+8hNQuBiaJLlgg6wEHSkpUl5xdLvv/2j1itd1im\\nZHW55CPzQ/bJlqpusDQdy/eJN2dku47z5ZLxaEZRVsznC1zXZbVest2sMQydmzdPefr0sWKn9uAi\\nXdcZjEasNxvuP3rAarkly2KqqiXPa+J9RtOo3V+aJoCG4zr9VMBmNl0gJVxeXmCaJpPJBF0YSpSm\\nrtBNDSFMvCBkNBoxGAwOmg9hGDEcDnny5CFVVZJmOYPBiMlsStdBJ1v22x2jaIjnqgZ61zSUVUXg\\nBXSN2hUURUGeZQSuRxRFJFl2kAH4svFcFAaQ1E1BIzwib0g9NcniLZEz4mgyZzQYK6Zd2SD7LaEC\\nBJWHmyLP88NOwXVdjJ6Jpqy/FA7d8zzquiLPcrquoetACHlQNsqzDNfQmc/n7Hd7wijEdl0ePXzI\\neDxlPp+zMG8wGAyIk5SHTx/Tdh2GabDZbri4PKepSzQa0G3Oz58ShiGDwbCH0Cri1BUZp2nUSOxK\\nB6DpyVVVXijPQcchCF12m4QkVUo8vh/0vzsUVQ5wmLiAUtz2fI+5ZeF5HqfzU5abFZ7rs9uvKMq4\\n53O0lG2rRD3yjKpSu4em7vDCiLysuVinfMsbKK8Py6azXKXobOd0neh7OxLTVKzVPM/RNUU1N0yT\\n2XiO6zo0bcd2s0eMDGzbOnADNKExno4wDBNaNcdHGIAiGClxGYHoDDQ/4g/f+SGfPLyH1A3sXFJ3\\nBUWT8fTyEfPpmFdf+1NomoHu+7x//z3uPbjPL49GDIdjHMclCAIePX7IdruhaSpefukbTCZjuq5j\\nvVoR7/f82q/9GmWtFLGmszn373+CbVuEwQDLdGlqSVleEicbmqZjMIiU7kJVsl6vFQGvbAmC8IC8\\n9Wyf0B9gD33yPEXvNHw/4GCD6Pu99J5Gmu4pq4IkidU1bJkMJjN8f8xbb/1zbNukLRvG08lBp0Nq\\noKNwMFfXfWerh+AVT+LKjfzLxnNhauv4hvylX30Rx7cYDoaEno+lW9yM7nD75k0izyfPc2zbxgsd\\nNKGOCFVdU8tWQWgd9yC4EQwHqreA0uBv+jFlkedkWY6QUhmbVgU0LZ7rEYYhs9kMAez3MU1bIVoJ\\nGgRRxGKxAOg5/meK3+95CsfQO/5UZXnwC2iahqptSJLkAJsWAqWh2I/C2ralqTsGgwFCaJRldXCQ\\nbprqAIGOBoP+GKGUf+umJUkLPLu/wQxdgcRyJWxaFAVhGCoiU9XbwjUVju1x4/R2j94LsE2D+WJC\\nFA4whRqhDYchXZvz9P5HpHnN4uQFNFPHdjyKquBiuWK1vOT+vXu8++4P+fCjj5QHRyfRNEEQhIzH\\nQ27dusu33/wldF1hQOL9junxESfTKf3eHtexmYwmVHlBODnCCEM6TQnEmpqjRFL7Z5eUkrrIeeet\\n/4vvfu/3cD37AJSaTecEgU9Z5riuh+97pFmOaSoR1E42bDYb1us1gT/oryWLyWRCFIas12uePHlC\\nFEUczedsdxuFV2lbup4qrejNgrIslF5CrRCFs9mMo6OjQ8Pb7dmSUnYHkVmg5+AorQvbtnFdpduw\\n38d8fP8ejqOMcALP58MP7lE3Db/8q79CW3XomjKOaVvJLt7y4MGD3mOCQ2P1yvMiz7PDaHK33/Sg\\nOw0/cPn13/iLXy9TWylhud7iZh2rywvGocfN+V1aq2S326DLDk1TbDPPUTZqei9Y4tiuYkZqGp7r\\n9nj9Dtm1GEI5TRVZRlnmlL2Mt2lYB8WbTms5OjpiMBgwmUw4OzujbCosy6DuGu7cvtWLd+7VubOq\\niKIBjuOy2agnT1KVuK6jZLz76LqOqqkPVFoFezURIiVJ4kOD1HV80jTFMCzW66U6b5oWRdEdwFNl\\nVfTCIep77fcx0WCEbRpst2vCwRDdMBCaGv1eYfevLtaiKLBs86AHecXGE11LWVZ4Ry679UaZr9QV\\nlukRTRaUqw2abSE1QdW0tP26llWJMGz2aUmSlr3rktbTxxVw6+ZNSds2mJaDrWk83O2YLGY0dYvl\\nmXi+zzAMqfICQ9cQjo1SxhQYCNoiRtoGhh4B6mj19NFDDNtgOBiAgNls1h8hjf5YFVCWFXVZoiMx\\nhAZtx5OzJwgh+vG23gPIEnV0EIK6KHEcj+1+j2YoiH7TqKNnWbSEYXh4GleVi2U5B3j9aKSQhwpE\\nx0H05fLyQgny9OA3KSXj8Yh9HJPnOXGSIFDb/rbruLg8Q3Ydx/M5lm32blTKR2K5XGLqFuFwiK2b\\naALSNObiYnm4Zm7duoWm6TiO219DJoE/YH50S+F3tK9h81Gg/Pigo04blnnB0eCEvClYri4RSKbj\\nGZquoQkdXVNPAV3TiDyfbY82a1vl3VdmGUJAXJbUddMzJ0uQnaLDViVGp5ycXEcBfZJEmYBstlsA\\nHNvB1NX2PEkSlpeXHC8WrDcrojAkDD3ieIdyTTKo2gbDMilyhUFAQF23GLpJluZYdst0coTReyEq\\nXQebsqx4+uQMiUTXTJIkxfU68iJnt90xHo9pc+UinWYZAsE+3mPoOoVuUFYN+cUlpqW26GqEpuTY\\n1IhWQa932wzP80nSlPF4dJjUND0qNCsL8jxnYc+REmwvxKtayqZiuVxx88aNg8JRUVakWUJd13Sd\\nGoMpAlbXF88cx3EwLYO8yHEsG8tWAih125FsNuhCwz4+ZrPdQ1vh3biN0DygpS72WJ6PPFyegq5r\\nKbMNj54+wLVtatmxWq14+eWX8f0A27bIsoymaWlqJb2Xry4I+p5R26rJRNNUOI7CmmRZxma1YjgY\\nMJsdYVgGT548pG0a0iwFqRFFgwOcOMtiBfMWih59Rc67gjp3vVlPlmX4foAQsNko5u4VazfNM1xX\\nUbLrSh1rT45P0MSchw8f8tG9+9QtaJrOh+9/yPF8SpxsMTSX5XbFcDjGtiy22w1lWfb5+TiOS1Up\\nToSUEMcJddNiGB7oWt+7+fLxXBQGHbB7qbSaGt8f4FkuTVNQdC2beM/JySmGqQqCBocLv2saNKnO\\np2VZ9RTWFKXsq3QLrtRw1B/fYr1aK+Zej140dIHQdMqyoCzUjZUmCbbtkiSJgsWWJY8fPyYtUuJ4\\nzz7ek6UFTaMw9qZlQg9zdTxlf15XCiy13yf4fkRdtzRNRxJnDAaRuqCtjnSQKqyDZrNcr1hvtmqH\\n0AusFEVxaLBqmpqvn1+ckxWlKhxti+cHZGl2IHBdPaWuHIhM0+x3H6XCUcgOrYU0SQA4Pj7mB3/4\\nXY6PFximRppVveqzy+XFBYv50cEJqawa9smeuiox+gIHYNsOVVVy4+YC27bJ8pQHDx7x6quvMp5M\\nyLOM0WDMe99/l8l4rNa8abA0BU0Xir6ETqs0Fq4uTyFIkj1PHn6k3JzqDt92ePLkiUI0um5PIKrI\\nspzZbMLFxSW6rub+8/mC9WqLaZk0tWIjDodDkNA0Hegmpycn1F3HvY8/ZhhEGIbD48ePCcMBR0dz\\nNE3j6dOn/Q1eohvKC5R+bHw1FlcFo9fO6FWdQO0osjyjzBUyVdAxm84oCmVso0kwdYcbt14gK1Rf\\nzPE87n38CYYpCP0IqzeqLcoS1/V4442bByRlWRaMRk6vPdIyGAzQdJMw8tllMZPR5Ge6J5+PwqAL\\nhkMff2RhOha2HdLKjqaqSesCKXSM/inYtTVlqTAKQtPY73YUTU3TtsqxuFFegVq/rdZ1nSiKKMsK\\n3wtwXAdD00nSjLLIybKUNPP6jr7PzZs3EUKw3aw5Pz/HspQ4aRiG5EVxkNNSluWib6JB1Z/tr6Yj\\ndV3jOqrLffv2rb5BquTG81zZlOd5TRD4RMFIdZglyK7Ddhxs10W0ik8fRZH6Q/fCrF3XqcK2WSMl\\naLpqfkkp1ba3V4kqi/JwdLBtJcBSNyiUp+chAV1XpLJBNMD3AsqqwHYMhUlolErxYn5MHKcUWUpe\\n5CRpynqzIy9K5W5tmwd+CaJlPB7T1MqabjIdHbAW6old0nUtUTSApqGtCkaLuWqSiRKkRpnVuL5S\\n5lZndInj2ORZRpLv0aTG7cktXnjhBcqqJEkSZTQrBEEQ0DYtvu/j+XPqsmS9XTEcDNlst4oy3wPf\\nTNNWvJqupcgLvveD7xNEAYvFCaPRgBs3brDb7hFCNQkXixM2mw1JEiM01adpWuWyftVLqKoSTXP+\\nCEdHNYaVqYwuNM6ePCXwfS4v1pi2xeXlOY7tKSh+1yAMl+FozGQ06n/vFE1zuXPzFp2s8Dy/72k1\\nyE4ZNl0dla7G9oPBAIRBHO/RhSBP9z/TPflcNB+ni1D+5r/zy9RVR7KrcDoDR5jYnY2mWYyjiNde\\ne60/pyttgrquubzcKLEWy1LIr/5JqesaXafGlVddWiEEWVqy3F4yncxoazUms22T6fQIx3F48OAB\\n68slZZUxmk2I93vatldmQiI1JVbbds3hQiiLktF4qGiyeaF2DI5DmsaUWYOUHdvdjsvlksFggO86\\ntEIghUZXNnRtSy3go0/u88rdu8znR7Rtw263O/QhoiBUuyPZMJ/PaZqWzXJNkhUkSUKaphwt5ui6\\nTllUdD1RpyzLAxI0yTIldSck+yRTDskCXnv1VV64cwdDg7qpyLOKW7duI2lpW0mRVwShx4/ef4+L\\nR5esNkvee+89Li7OqHsL+3EY0HRKXduyHObzY1577XWCwOPu7Re4//gTBoMBoecTBgEPHz7kz/yZ\\nf4XRbKZ6AkXMaLZAGAZSU0pPMsnpHAvDHClmZJnw6MN3+R//8T/gF7/zHWhbTNPG0FXh1g2TaBRh\\n2zZpEaN1BsPRGE3Tifc7WiQaGrbj4Lsulm0T73bITlIUFZ88esDx8QLfV0Y/jx8/xjCURqUQgslk\\ncihweZ4zGETYtnPAkFwVbMNQ+hug+CpX4j+KlGcdGr+u6x40Ha9o1FEUKRGZRsP1Qpq2Zr3ekdYx\\nRZ1jIaiqiuVyzc1btzE1jf1my/RoTtPU7Pc7NF0Vx08++YSmq3jppVcwTR1dt3jjX/1zX13zUQhx\\nE/hvgDmKIf07Usr/Ugjx28C/B1z2n/o3pJT/a/81fx34y0AL/AdSyv/ti3+KRlN2iKbDaMAzbCxp\\nYmgKDn11PjMtk6qCIk+Jkz1F3uAFrrIn1zWEUDsJKbvDFq6qKhzHYb/fY1sOjudzdnbGfHaEZVk9\\n67BgtVqrJ4plYenK/6+qaizLVVTWpjl09y8vL1Uj1PNo2prNekuaZViWOsc5jkNV1uz2aX+xGEyO\\nFnieS1sriTTdMGhlCZ2Oaxi8fPsF4niPpgnl42AohlwQ+PiearD6/rDfntqMJlPCgZJEi+M9F8vL\\nvgBCFIZ/5PvkeUYYBDS18st0bIsnj59wvlxhGDpFmTP0I8Io4P7H9xHoai2FpKor4sQji1suLs54\\n7977xLstjudg1jrC1EHXME0Nx1BMUdPU2O7W2LbJertBCo1dHCuEZT+NiXcxQtMYDUbs0xLfzdB0\\n5VTdtjpVukemguB4QluuQdZYmuAXvvktqrImGg5wbBfDUPgI13UZjSJVUPOKIs9I85LA9xFSEo1G\\nlD3GxevZrlWvoSGFxksvvXhQahJC41vfeoN4n2LZahSLlCRpgtO6zKZTsiwljhPVS+mnAKvVCsdR\\nx6qro+4Vc9O27UPDUvFi8gOXR0rZk//UrqpqW7a7Na5ns92d08iWRkIc7xkOR5yenuD5FjQdUege\\nWLtNUzMIIsIwZDgcsNvHpEnKk6ePGQ5HX6YeHOLLHCUa4D+WUv6BECIEfl8I8U/6j/0XUsr//NOf\\nLIR4HfgLwDeBE+CfCiFekVJ+ruNF2zSkFxtMTUcmkk1XMRkeYTgmXfVjoZM0TTE0q3+/JgwjHPfK\\nYNTou8YVdd0cxjh1XbNeb0mShNMTn2SXEHpBD3aq8X2fy8tLmqbF6C/MzT5BbxpmRzOyJKeqWqLA\\n7ynRHI4TCnXYkCY5oldZKvKKPFMeh2lRMBwOCYIAz/NYrTbEac6LL75ImiQUZa18FUwDEfhKeam/\\nqK6MZZW+gksYegrmvI9ZLI57TUKtZ9sJxuNxfyb2adtGGZeYds8LCSjSgkEQULcNRW0wiF5mfDnh\\n3ffeJc0ThsEIQ5gUeYauuVRVTdWWdJrENgxWq5jl5WOaSkGyLctAAzB1gkB5N1i2gyaVanWa7nny\\npMUwLPKqpCgy9KMpy+WSXbxjF++RQiqvkLphuV7jug4IsA2PP/zRDwkClzeOX4a2ockTNA2+8+3v\\nKBkzJFVZ4bohtuUyP12Q73does6t0zsEoxFN07BbrxkEA9A0Ii+ko1Vnek1jtVkTDiNu37rLdrWk\\nyHOOZicYlpKjs0yPLFeclOVyyenJKUmS9IK2ynditV5j9lwYxVdR482rj19Na05PT1it1gfQ3dXk\\nyPf9fmendpea0LFsdTRMdjsMDUaDMcI0mL72quLAlAX7XYptGuR5SYt6AG23W/bxnslkygsvvkQc\\np+ohOJ//WKz2qyoMUsqnwNP+7VgI8Q5w+gVf8pvAP5RSlsDHQogPgT8N/PPP+4Kmqtk/XSEMi84y\\nKdKSIBhgNxZlVaIpgXnapsN0wDA1HBxsx6LIc8aTCbbtUBZKYEPdtJKmUcUkjhVlebXe4jo2URiR\\nxQlSQNs1hzNbXVdouoHWCXZJjBv4SpSjKRn7ygU6TVMlvNm2GIbKqaprbt+6RZpmfSOs7pt9Fa7r\\n4DoOSZqw32+JoojNdo0mQHcsLFPpS4i25chbsFovKcocx3U4GswIwwBNgyLPMSwbwy7VU7gDx7bV\\nTiLwSZKM2WxyEKcBqOsSx7EwDZP9ds98MSNJ9wyHMyzXYTaZcjKfcb68IIlTLleXqnnVtRR5jhu4\\n7NOkpwwrDYbxVAMBbVuxOD5mMBiQxRlVW9PJlqZukR0UVcXy4iGW6bDbb7BMhWhcyzVV23F2fkHT\\nNpRVwfJiiRcEOI5N1wnWqyVxVvDkYcY3fuHbXJw/wdE0/Cik6VqOJmOyoqTQe19JA+L1GtcP8JyC\\nTrasLy/Rdf3QPB4EEZvlCkPXuNiusW2X+WyBYerkaYLtOJyfnTEaTXuPBommN4xGIy4vL6mrmroo\\nifd7Pnj/feaLI2zbZr9PlAZCXbPdbHuTG5/hcIBp2gcJuXfeeZcXX3yxdyQrDtdJVVZE4ZDL5RlF\\nUWKZDsIUGK5J3TX40ZC2A9/0qIsSoWm0eUlXVnz88QMePnqE49l85zu/yC/+4i/x8NEjfvT2u/i+\\nTzSMmE6njEYRRV586aIAP2PzUQhxB/g28P8AvwL8NSHEXwTeQu0qNqii8X9/6sse8VMKiRDirwB/\\nBcA0NTabklZvwO2wpUHXNrS10jJ0Pf+gdHPlKXh1/p5MJoRRpIA9vUKvbdu9DXjRj6mUM3SapUzG\\nY9BFjy9X6k1NU9G2VyxHSV0VuJ6nFI/7in52dqZugjw/wEvPz88wTRfPU0eMK8nvKxGOJMuxDAOk\\nRNd0ZjPVPxiPx+iGps7GQqPIcxbjCZ88fMRsNiVJEgQd+92GolBPFcdxSPMMwzKJNzFV2XD7zk1u\\n3DpBEzqbzQYY8fbbbzObzZRGZTTA6YVi4zhBaJLhcIQmlCWdZRksjqaMooCu7WiEges4nD+9UCPP\\nTq3HJ/cf4vke/iBEE9A0LVkSs16vuXF8gq2bVJ0SXa2rhqZWSNKyqTg7O+NidaY697rJ3Ru3GA0m\\nfPf3f5+TG6eYlqsg5IbOaDhEN0yqvGQ0HXLnlVeJl+cEjk2ZZCRNS5KnOEaL1EwQAtdTgjoISVHs\\nqZvigENQ47yI9z/4gFdfeJGqruhaHUM3qOoK2zZ5+vQpaZbx+uuv4zgOdZ2z2V7Q1A1B6BMEOo8e\\nPWK325IORyRJzHQ248nTS+o6586du4pO3+8Y/V4/9KrXYBgmnuvz6NEjzrxzhIYiRPW6oXVTsduu\\nCcOIBw8fcvfOS7z33tucnJ7S1iVurzad7dasy5z1Zstuv8UybeIk4+h4wWI+4+hoxoOHD5hNjxgO\\nRmx3G/bxFs/1SVNDoSD/ZRQGIUQA/CPgP5JS7oUQfxf4W6i+w98C/jbw737Z7yel/B3gdwBcR5dx\\nWtNYHaGhhCubVmJoGqPpEcNwgOf42K6D7egHqvJgMMDzPdI0PYi4ar2Jq2EozQbHcXj69BzZG3+A\\nUnP2ggDTsqjSlLosqetWgZSKkiAMAEXFvQK2rNfrg/bDcDAgyzK22w11VbOYH2GZDlXVkKYJURRh\\nmibbvWJM7nY7hK4zHo+Vn6VlUJaZ0vRzPVzXJc8zhqOwP6PrnJ+fU6QZN4bDHuJtotcVnZQEg4Cm\\nbIiiENnBcrOkrAqGgyHT6fSgQ5gkKXmmRqbD0fCg++d5Hlqu8+DBfWaTCa5r9wApNXK7dfsYx3HI\\n85yqbhgOhpxfXDA/Vs5XTduwX9sY/Yg3CiLcKKKuK7IsoywbdG3O7OiI/T6h1bue+g7JLqXlkrZr\\n8OOQrtvjeg5e4GM7Su0quhlxdHyM1jd4m7rGcCy6tmMYDWjrEnQF5gmjiLIokF3HcrVSQj9FhWWa\\nlKXasnuOy8cffayAP2HAYjzn4nLJBx98QNe2zI5m1L2Sc5ZlrJYrtpsd+zjmzW+/yXA4JC9KmhaC\\ncMhiccJ0fITj6cRxRpaldJ0kDIODjsZgMOD8/JzRaEzgDzg9vUVdN0CN53m4rmp213WFYZmEssX3\\nPGzLVD4RVcUfvvUHHM3nNFIpSXu+R9s2BNFAKVrfukuRF4R+wOXlJcvLJU3dcnp6QxG5vCFNU3N+\\nfk5ZV1/21gS+ZGEQitP8j4D/Tkr5j/sb+/xTH/97wP/Sv/sYuPmpL7/Rv/a5ITVBY9foWkOa67gm\\nOIbJ8Z2bnIwWWI6N4yp7uN1uj+e4TCcT0iwljruefyBYLldIKTk+mrPerJkfL8jznKOjI8VlL3PF\\nZKwr6rxgs1yqxl6ZowlBss+xXPvAuXj8aMnpjRMM0+L45AZlWeKaGl1dYQg1+5edzmq1wncdNF2Q\\nZCloAs91cRxFiw1DgW5agIbQ1BzdcdXERNLR9MxKU1qqs6xp3Lx580AAa5qOeL9Fdh2drAl9n6TL\\nePjwAcfHxwyGAyxrRNtKhsMpT548YT6fk6Yp+33Gfp8wmo5wPRdd09VuwbZ45ZVXVZOsV6Cq67IX\\nD21p2wLX9RVC0DOZDCKqvokZb/Y4tsFwGB6afm0HOjq65+C6qvl783jBYrEgzXtiW1dDXVG3kBc5\\nXStxbIfJZMLR0RzXc/n44/uqYdjCcnPJeDhkPJ0RHd+gSWNkntISoBtqx7fbKrs62zAQHbRVSxSE\\n2I6D45S0bcftmzd4fHbG/OQG52dPeHz2GMeOOL31Mul+SVl1rLYxp7de5KOP30dYHndeWRDvEtoW\\nwiiipkETFrdu3eTRg/vQaty7/4gw8BiPJ0wmYwW3F4Lvfe97mKbFyy+/wn6/Zx+vCUOfoiwYj8e8\\n88O3AYEfuDRdg1kbXF4sqdoOzx/wwkuvUFUlr/6pN4miIUJIbty4RVs1FHnKvXvvoGsayyePeeGV\\nl/A8jzTLCAcBtmti2hrCEKRFRtfBNo65ceOLTv+fjS8zlRDAfwW8I6X8O596/bjvPwD8m8AP+7d/\\nF/gHQoi/g2o+vgz8iz/u55iOiaZDFpdMowUv3XmJm5MFlmf3UlgtRbZF9A3FLE/ReoxCnucKPmxZ\\nVFXFw6dPkF2Hs9srQ1bbpK4LhPgxyWy7XRMEgTpDdh1JlqJrBmbvUF2VOfP5AttxWS5XPQGrxg+G\\nZL2jcd0jzQzToWk65pMpbhBi2A6WYSA0Q+nyWRZNf3NouugVrPNeR0HBYpMkOXhQ6J9SLZZS0lYt\\njuvSVBVV3QOz+obXfL6gbkqkVCKgnazxfY/VaqXoxLqOrmlMp1PqqqKUoJu6ouF2LWmeEoaRMkNx\\nhwgJRVH2eAPJcDikKstyRvy1AAAG40lEQVS+r6AYnEo+vezHvTZ1rfrKZVGgGwbjaMjx8THr9Qqp\\ndUBLlpVUXU1XlESDKY7rMR4qW/erHZgQ8I3XXiNNErY71dQbDmf4gxnVLkECjdTI0j1ezzWwbRuh\\nKXHgxY2bbNbnpFmq4Ns9c/XRg4+5efsujx49wLFtXn31G2rcbAjefucRb7z5JrKDsi6ZTCakSc7l\\nhTpOPTnf4vkuWZpy+/bLVJVkMJ6gIXAiF4Fgt97Q1C1o6mh4cnxKUSp/jaOjI4oi48mTx9y585J6\\n4JyfMR6PmQTHVHXL8fECb73jg/feZ7tJuHv3DrLbc+vGbUzb5MGDT5RQMZLtfoNuuqonJgXvv3eP\\nm7dvMR6P+cY3Z2x3O4LBCNPx2O33OLZqpBbFV99j+BXg3wZ+IIT4bv/a3wD+LSHEm6ijxH3g3weQ\\nUr4thPgfgB+hJhp/9YsmEqBs5r3QxdA6BA6/8OKrTIcjDNdF0JIXudIzbCqEFJi6jkRi9ZDiPM+U\\nIYylmpGGocgzioOgk6Q5tm2ANA6z5atRUZqm5IUaS3atsnH3e3LU0WxBXtQcHS3IMtV0RNPQelUc\\nx3TpWsl4MibyAjStpatKnMAnS5WnxHq9ZjQeH4RCulb5Wl51scuyYDqd0jRND9/tDg5GlmUpBShH\\nqSe3psXQiijKnJPJlM12y+WlsrHX+nGt7WhkKQRBQFkq5y3PtxlPJhiKHYIwf9zDGUQRtuPSdRLb\\ntHvknt2z8ZQQTpqmpHmK7SgZ9rKqQEpu3z6mLEvqfh4fDQb4vk+WZSRxTFXnPH26R8NGaBLXizg+\\nfYkbt26QpaohbBoGRo8z0VBw4jROiPOcpq6pZUVVZ9CpUadp+9i2TlmUfYGqqNqWcBCxXq6wPRfd\\nULZtm82W3W7HyfERmiZwHBuBgrjXVYfmmkxmMxCC3W7LYn5E3aiHzmAw4OTkhKbR2Cd7xqOaxfwY\\niVSyanmMZyupgC5oybOc27fvIiRMJzO6Tvls+L7HeDzm9/7F73Hr9h3e++A9kjLHzjPSNEF2ihwW\\nxxtu3Tnlm998jeVSNUebtkVH/V19P6AqYgaDCMcJeHr+FMt1GY1GeJ5PEISgacwXpxiGThBGyN5N\\nbTwe8eTJk5+pMDwXACchxCWQAstnncuXiClfjzzh65Pr1yVP+Prk+tPyvC2lnH2ZL34uCgOAEOKt\\nL4vKepbxdckTvj65fl3yhK9Prn/SPLWvMpnruI7r+P9HXBeG67iO6/hMPE+F4XeedQJfMr4uecLX\\nJ9evS57w9cn1T5Tnc9NjuI7ruI7nJ56nHcN1XMd1PCdxXRiu4zqu4zPxzAuDEOLfEEK8J4T4UAjx\\nW886n58MIcR9IcQPhBDfFUK81b82FkL8EyHEB/3/PxvZ/avJ678WQlwIIX74qdc+Ny8hxF/v1/g9\\nIcS//hzk+ttCiMf9un5XCPEbzzpXIcRNIcT/LoT4kRDibSHEf9i//lyt6xfk+dWt6ZVz87P4h5J7\\nvAe8AFjA94DXn2VOPyXH+8D0J177z4Df6t/+LeA/fQZ5/TrwHeCHf1xewOv92trA3X7N9Wec628D\\n/8lP+dxnlitwDHynfzsE3u/zea7W9Qvy/MrW9FnvGP408KGU8iMpZQX8Q5Sew/Mevwn8/f7tvw/8\\n+Z93AlLK/xNY/8TLn5fXQSNDSvkxcKWR8XOJz8n18+KZ5SqlfCql/IP+7Ri40h55rtb1C/L8vPiZ\\n83zWheEUePip93+qdsMzDolSofr9XkMCYC5/TCA7Q8nePQ/xeXk9r+v814QQ3++PGlfb8+ci15/Q\\nHnlu1/Un8oSvaE2fdWH4OsSvSinfBP4s8FeFEL/+6Q9KtVd77ma+z2ten4q/izpCvolSCPvbzzad\\nH8dPao98+mPP07r+lDy/sjV91oXhZ9Zu+HmHlPJx//8F8D+htmDnQohjUPRz4OLZZfhH4vPyeu7W\\nWUp5LqVspXIa+nv8eGv7THP9adojPIfr+nkaKV/Vmj7rwvB7wMtCiLtCCAslIvu7zzinQwghfKEE\\ncBFC+MC/htKd+F3gL/Wf9peA//nZZPiZ+Ly8fhf4C0IIWwhxly+pkfEvM65utD5+Us/jmeT6edoj\\nPGfr+kUaKZ/6tD/Zmv48ur1/TIf1N1Bd1XvA33zW+fxEbi+gurnfA96+yg+YAP8M+AD4p8D4GeT2\\n36O2izXqzPiXvygv4G/2a/we8Gefg1z/W+AHwPf7C/f4WecK/CrqmPB94Lv9v9943tb1C/L8ytb0\\nGhJ9HddxHZ+JZ32UuI7ruI7nMK4Lw3Vcx3V8Jq4Lw3Vcx3V8Jq4Lw3Vcx3V8Jq4Lw3Vcx3V8Jq4L\\nw3Vcx3V8Jq4Lw3Vcx3V8Jv5fJ7Z1Gr+BQssAAAAASUVORK5CYII=\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84b22ae490>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Model is expecting 224 x 224, so resize/crop needed.\\n\",\n    \"# Here are the steps we use to preprocess the image.\\n\",\n    \"# (1) Resize the image to 256*256, and crop out the center.\\n\",\n    \"input_height, input_width = 224, 224\\n\",\n    \"print(\\\"Model's input shape is %dx%d\\\") % (input_height, input_width)\\n\",\n    \"#print(\\\"Original image is %dx%d\\\") % (skimage.)\\n\",\n    \"img256 = skimage.transform.resize(img, (256, 256))\\n\",\n    \"pyplot.figure()\\n\",\n    \"pyplot.imshow(img256)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('Resized image to 256x256')\\n\",\n    \"print(\\\"New image shape:\\\" + str(img256.shape))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note the resizing has distorted the image a little bit. It is important to recognize this effect during your processing as it can have an effect on the results of your model. Flowers and animals might be ok with a little stretching or squeezing, but facial features may not. \\n\",\n    \"\\n\",\n    \"This can happen when the dimensions of the original image are not proportionally exact to your desired size. In this particular example it would have been better to just resize to 224x224 and not bother cropping. Let's try another strategy of rescaling the image and maintaining the aspect ratio.\\n\",\n    \"\\n\",\n    \"### Rescaling\\n\",\n    \"\\n\",\n    \"If you imagine portait images versus landscape images you'll know that there are a lot of things that can get messed up by doing a slopping resize. Rescaling is assuming that you're locking down the aspect ratio to prevent distortion in the image. In this case, we'll scale down the image to the shortest side that matches with the model's input size.\\n\",\n    \"\\n\",\n    \"In our example here, the model size is 224 x 224. As you look at your monitor in 1920x1080, it is longer in width than height and if you shrunk it down to 224, you'd run out of height before you ran out of width, so...\\n\",\n    \"\\n\",\n    \"- Landscape: limit resize by the height\\n\",\n    \"- Portrait: limit resize by the width\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Original image shape:(360, 480, 3) and remember it should be in H, W, C!\\n\",\n      \"Model's input shape is 224x224\\n\",\n      \"Orginal aspect ratio: 1.33333333333\\n\",\n      \"New image shape:(224, 298, 3) in HWC\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84b210c550>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Original image shape:\\\" + str(img.shape) + \\\" and remember it should be in H, W, C!\\\")\\n\",\n    \"print(\\\"Model's input shape is %dx%d\\\") % (input_height, input_width)\\n\",\n    \"aspect = img.shape[1]/float(img.shape[0])\\n\",\n    \"print(\\\"Orginal aspect ratio: \\\" + str(aspect))\\n\",\n    \"if(aspect>1):\\n\",\n    \"    # landscape orientation - wide image\\n\",\n    \"    res = int(aspect * input_height)\\n\",\n    \"    imgScaled = skimage.transform.resize(img, (input_height, res))\\n\",\n    \"if(aspect<1):\\n\",\n    \"    # portrait orientation - tall image\\n\",\n    \"    res = int(input_width/aspect)\\n\",\n    \"    imgScaled = skimage.transform.resize(img, (res, input_width))\\n\",\n    \"if(aspect == 1):\\n\",\n    \"    imgScaled = skimage.transform.resize(img, (input_height, input_width))\\n\",\n    \"pyplot.figure()\\n\",\n    \"pyplot.imshow(imgScaled)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('Rescaled image')\\n\",\n    \"print(\\\"New image shape:\\\" + str(imgScaled.shape) + \\\" in HWC\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"At this point only one dimension is set to what the model's input requires. We still need to crop one side to make a square. \\n\",\n    \"\\n\",\n    \"### Cropping\\n\",\n    \"\\n\",\n    \"There are a variety of strategies we could utilize. In fact, we could backpeddle and decide to do a center crop. So instead of scaling down to the smallest we could get on at least one side, we take a chunk out of the middle. If we had done that without scaling we would have ended up with just part of a flower pedal, so we still needed some resizing of the image.\\n\",\n    \"\\n\",\n    \"Below we'll try a few strategies for cropping:\\n\",\n    \"\\n\",\n    \"1. Just grab the exact dimensions you need from the middle!\\n\",\n    \"2. Resize to a square that's pretty close then grab from the middle.\\n\",\n    \"3. Use the rescaled image and grab the middle.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Original image shape:(360, 480, 3) and remember it should be in H, W, C!\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"<matplotlib.text.Text at 0x7f84b1e0d410>\"\n      ]\n     },\n     \"execution_count\": 8,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\nRU5ErkJggg==\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84b1f523d0>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Compare the images and cropping strategies\\n\",\n    \"# Try a center crop on the original for giggles\\n\",\n    \"print(\\\"Original image shape:\\\" + str(img.shape) + \\\" and remember it should be in H, W, C!\\\")\\n\",\n    \"def crop_center(img,cropx,cropy):\\n\",\n    \"    y,x,c = img.shape\\n\",\n    \"    startx = x//2-(cropx//2)\\n\",\n    \"    starty = y//2-(cropy//2)    \\n\",\n    \"    return img[starty:starty+cropy,startx:startx+cropx]\\n\",\n    \"# yes, the function above should match resize and take a tuple...\\n\",\n    \"\\n\",\n    \"pyplot.figure()\\n\",\n    \"# Original image\\n\",\n    \"imgCenter = crop_center(img,224,224)\\n\",\n    \"pyplot.subplot(1,3,1)\\n\",\n    \"pyplot.imshow(imgCenter)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('Original')\\n\",\n    \"\\n\",\n    \"# Now let's see what this does on the distorted image\\n\",\n    \"img256Center = crop_center(img256,224,224)\\n\",\n    \"pyplot.subplot(1,3,2)\\n\",\n    \"pyplot.imshow(img256Center)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('Squeezed')\\n\",\n    \"\\n\",\n    \"# Scaled image\\n\",\n    \"imgScaledCenter = crop_center(imgScaled,224,224)\\n\",\n    \"pyplot.subplot(1,3,3)\\n\",\n    \"pyplot.imshow(imgScaledCenter)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('Scaled')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"As you can see that didn't work out so well, except for maybe the last one. The middle one may be just fine too, but you won't know until you try on the model and test a lot of candidate images.\\n\",\n    \"At this point we can look at the difference we have, split it in half and remove some pixels from each side. This does have a drawback, however, as an off-center subject of interest would get clipped.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If you've run this tutorial a few times now and are on Round 3, you'll notice a pretty big problem. You're missing astronaughts! You can still see the issue with the flower from Round 2 as well. Things are missing after the cropping and that could cause you problems. Think of it this way: if you don't know how the model you're using was prepared then you don't know how to conform your images, so take care to test results! If the model used a lot of different aspect ratio images and just squeezed them to conform to a square then there's a good chance that over time and lots of samples it \\\"learned\\\" what things look like squeezed and can make a match. However, if you're looking for details like facial features and landmarks, or really nuanced elements in any image, this could be dangerous and error-prone. \\n\",\n    \"\\n\",\n    \"#### Further Strategies?\\n\",\n    \"\\n\",\n    \"Another strategy would be to rescale to the best size you can, with real data, but then pad the rest of the image with information that you can safely ignore in your model. We'll save that for another tutorial though since you've been through enough here! \\n\",\n    \"\\n\",\n    \"### Upscaling\\n\",\n    \"\\n\",\n    \"What do you do when the images you want to run are \\\"tiny\\\"? In our example we've been prepping for Input Images with the spec of 224x224. Consider this 128x128 image below.\\n\",\n    \"![cells at 128x128](images/Cellsx128.png)\\n\",\n    \"Now we're not talking about super-resolution or the CSI-effect where we can take blurry ATM photos and identify the tattoo an a perp's neck. Although, there are [some advances](https://github.com/david-gpu/srez) along these lines that deep learning has provided, and if you're reading this in time (before 3/1/17), go [check this out](https://developer.nvidia.com/zoom-enhance-magic-image-upscaling-using-deep-learning). What we want to do is simple, but, like cropping, it does have a variety of strategies you should consider.\\n\",\n    \"\\n\",\n    \"The most basic approach is going from a small square to a bigger square and using the defauls skimage provides for you. This `resize` method defaults the interpolation order parameter to 1 which happens to be bi-linear if you even cared, but it is worth mentioning because these might be the fine-tuning knobs you need later to fix problems, such as strange visual artifacts, that can be introduced in upscaling images.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Original image shape:  (128, 128, 4)\\n\",\n      \"Upscaled image shape:  (224, 224, 4)\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"<matplotlib.text.Text at 0x7f84b1cdbcd0>\"\n      ]\n     },\n     \"execution_count\": 9,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84b1fbc610>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"imgTiny = \\\"images/Cellsx128.png\\\"\\n\",\n    \"imgTiny = skimage.img_as_float(skimage.io.imread(imgTiny)).astype(np.float32)\\n\",\n    \"print \\\"Original image shape: \\\", imgTiny.shape\\n\",\n    \"imgTiny224 = skimage.transform.resize(imgTiny, (224, 224))\\n\",\n    \"print \\\"Upscaled image shape: \\\", imgTiny224.shape\\n\",\n    \"# Plot original\\n\",\n    \"pyplot.figure()\\n\",\n    \"pyplot.subplot(1, 2, 1)\\n\",\n    \"pyplot.imshow(imgTiny)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('128x128')\\n\",\n    \"# Plot upscaled\\n\",\n    \"pyplot.subplot(1, 2, 2)\\n\",\n    \"pyplot.imshow(imgTiny224)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('224x224')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Great, it worked!. You can see in the shape outputs that you had (128, 128, 4) and you received (224, 224, 4). Wait a minute! 4? In every example so far the last value in shape has been 3! When we used a png file we entered a new reality; one where transparency is possible. This 4th value describes opacity, or transparency, depending if you're a half-glass-empty type. Anyway, we can handle it just fine, but keep an eye on that number.\\n\",\n    \"\\n\",\n    \"It's appropriate to put this discussion towards the end, but before we do further manipulations to the image, it's data order, and its overall payload. You can really mess up your data and the image if you do a simple resample on the image in its current format. Remember that it is currently a cube of data and that there's more going on in there right now than just Red, Green, and Blue (and opacity). Depending on when you decide to resize you'll have to account for that extra data.\\n\",\n    \"\\n\",\n    \"Let's break stuff! Try upscaling the image after you've switched the image to CHW.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Image shape before HWC --> CHW conversion:  (128, 128, 4)\\n\",\n      \"Image shape after HWC --> CHW conversion:  (4, 128, 128)\\n\",\n      \"Image shape after resize:  (224, 224, 128)\\n\",\n      \"Here come bad things!\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\nctojEun6xKYAAAAASUVORK5CYII=\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84b1ffc6d0>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"imgTiny = \\\"images/Cellsx128.png\\\"\\n\",\n    \"imgTiny = skimage.img_as_float(skimage.io.imread(imgTiny)).astype(np.float32)\\n\",\n    \"print \\\"Image shape before HWC --> CHW conversion: \\\", imgTiny.shape\\n\",\n    \"# swapping the axes to go from HWC to CHW\\n\",\n    \"# uncomment the next line and run this block!\\n\",\n    \"imgTiny = imgTiny.swapaxes(1, 2).swapaxes(0, 1)\\n\",\n    \"print \\\"Image shape after HWC --> CHW conversion: \\\", imgTiny.shape\\n\",\n    \"imgTiny224 = skimage.transform.resize(imgTiny, (224, 224))\\n\",\n    \"print \\\"Image shape after resize: \\\", imgTiny224.shape\\n\",\n    \"# we know this is going to go wrong, so...\\n\",\n    \"try:\\n\",\n    \"    # Plot original\\n\",\n    \"    pyplot.figure()\\n\",\n    \"    pyplot.subplot(1, 2, 1)\\n\",\n    \"    pyplot.imshow(imgTiny)\\n\",\n    \"    pyplot.axis('on')\\n\",\n    \"    pyplot.title('128x128')\\n\",\n    \"except:\\n\",\n    \"    print \\\"Here come bad things!\\\"\\n\",\n    \"    # hands up if you want to see the error (uncomment next line)\\n\",\n    \"    #raise \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Epic fail, right? If you let the code block above swap the axes, then resize the image, you will see this output:\\n\",\n    \"\\n\",\n    \"`Image shape after resize:  (224, 224, 128)`\\n\",\n    \"\\n\",\n    \"Now you have 128 where you should still have 4. Oops. Let's revert in the code block below and try something else. We'll show an example where the image is smaller than your Input specification, and not square. Like maybe it came from a new microscope that can only take imagery in rectangular bands.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Slice image shape:  (56, 128, 4)\\n\",\n      \"Upscaled slice image shape:  (224, 224, 4)\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"<matplotlib.text.Text at 0x7f84b1c1fa50>\"\n      ]\n     },\n     \"execution_count\": 11,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\nHOQAAAAASUVORK5CYII=\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84b1fd1b90>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\nAAAASUVORK5CYII=\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84b1b01d10>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"imgTiny = \\\"images/Cellsx128.png\\\"\\n\",\n    \"imgTiny = skimage.img_as_float(skimage.io.imread(imgTiny)).astype(np.float32)\\n\",\n    \"imgTinySlice = crop_center(imgTiny, 128, 56)\\n\",\n    \"# Plot original\\n\",\n    \"pyplot.figure()\\n\",\n    \"pyplot.subplot(2, 1, 1)\\n\",\n    \"pyplot.imshow(imgTiny)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('Original')\\n\",\n    \"# Plot slice\\n\",\n    \"pyplot.figure()\\n\",\n    \"pyplot.subplot(2, 2, 1)\\n\",\n    \"pyplot.imshow(imgTinySlice)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('128x56')\\n\",\n    \"# Upscale?\\n\",\n    \"print \\\"Slice image shape: \\\", imgTinySlice.shape\\n\",\n    \"imgTiny224 = skimage.transform.resize(imgTinySlice, (224, 224))\\n\",\n    \"print \\\"Upscaled slice image shape: \\\", imgTiny224.shape\\n\",\n    \"# Plot upscaled\\n\",\n    \"pyplot.subplot(2, 2, 2)\\n\",\n    \"pyplot.imshow(imgTiny224)\\n\",\n    \"pyplot.axis('on')\\n\",\n    \"pyplot.title('224x224')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Alright, this was a bit of a stretch for an example of how upscaling can fail. Get it? Stretch? This could be a life-or-death kind of failure though. What if normal cells are circular and diseased cells are elongated and bent? Sickle cell anemia for example: \\n\",\n    \"![sickle cells example](images/sickle-cells.jpg)\\n\",\n    \"In this situation, what do you do? It really depends on the model and how it was trained. In some cases it may be ok to pad the rest of the image with white, or maybe black, or maybe noise, or maybe even use png and transparencies and set a mask for the images so the model ignores transparent areas. See how much fun you can have figuring this out and you get to make medical breakthroughs too!\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's move on to the last step which we've already mentioned and that is to adjust the image input to be in BGR order. There's also another feature that Caffe2 uses, which is a `batch term`. We've already talked about CHW. This is the N, for number of images in NCHW.\\n\",\n    \"\\n\",\n    \"### Final Preprocessing and the Batch Term\\n\",\n    \"\\n\",\n    \"In the last steps below we are going to switch the image's data order to BGR, stuff that into the Color column, then reoder the columns for GPU processing (HCW-->CHW) and then add a fourth dimension (N) to the image to track the number of images. In theory, you can just keep adding dimensions to your data, but this one is required for Caffe2 as it relays to Caffe how many images to expect in this batch. We set it to one (1) to indicate there's only one image going into Caffe in this batch. Note that in the final output when we check `img.shape` the order is quite different. We've added N for number of images, and changed the order like so: `N, C, H, W`\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Image shape before HWC --> CHW conversion:  (224, 224, 3)\\n\",\n      \"Image shape after HWC --> CHW conversion:  (3, 224, 224)\\n\",\n      \"Image shape after BGR conversion:  (3, 224, 224)\\n\",\n      \"Final input shape is: (1, 3, 224, 224)\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAAAXoAAACRCAYAAADNVHNlAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvXnwZdtV3/dZe+8z3PE39vxmvVHSE09CCDMldiAgZjAx\\nlRAzmgRjkyoqJAQTEhd/YBsZDCk7xIkzONiJUxjb2GUMKECEQUJgQAjQgN6g192vX4+/6Y5n2EP+\\nWOfeX4vioZbUQv1Uv1X1q+5777nn7HP23mv4ru9aV1JKnMiJnMiJnMinr5hP9QBO5ERO5ERO5JMr\\nJ4r+RE7kRE7k01xOFP2JnMiJnMinuZwo+hM5kRM5kU9zOVH0J3IiJ3Iin+ZyouhP5ERO5EQ+zeVE\\n0X8MIiLvEJFv/1SP44+KiCQRefRTPY5Xq5zM66evnMytyj2t6EXkRRFZishMRK6LyD8WkY0/csyb\\nReRfi8iBiByKyPtF5IdEZKv7/FtEJHTnmInICyLynZ+aO/rTFxH5ehF5l4gsROQdn+rxwMm83g0R\\nkR8RkWdFZCoiHxSRb/pUjwlO5vZuiIi8TUQud3N7RUR+TESyT+Sc97Si7+QrU0pD4DOAp4EfWH0g\\nIp8LvAN4J/BkSmkTeCvgu+NX8usppWF3nq8D3iYib/xTGv+nWvaBHwf+1qd6IH9ETub1E5M58JXA\\nBvDNwP/QPbd7QU7m9hOT/x14XUppBLwF+GLgE4pKXg2KHoCU0jXgF4DX3fb224D/I6X0N1NK17vj\\nLqWU/npK6R2vcJ73AB8Annqla4nIV4vI74rIRESeF5G33vbxgyLyzs7avl1Edm/73j8VkWsiciQi\\n/1ZEXnfbZ/9QRP5HEfnZ7ru/ISKvue3zJCJ/ufPSDrtj5bbPv01EPtB5Qb8gIg/e4XP7xZTSTwEv\\n38nxf9pyMq8f97z+9ZTSB1NKMaX0G8CvAp9zJ9/905KTuf245/aDKaXJbW9F4NqdfPeV5FWj6EXk\\nPuBLgd/sXg/Qhf3PPsbzfBbwOPBbr/D5W4CfBP5rYBP494AXbzvkG4BvBU4DOfBf3fbZzwGPdZ/9\\nDvB//ZHT/8fADwJbwHPAD/2Rz78C+CzgDcDXA1/Sjemrge8H/jxwCt3U/+SObvgel5N5/cTnVUR6\\n3fnf97F+95MpJ3P78c+tiHyfiMyAl4CfTSn9izv97h8rKaV79g+drBkwBRLwLwHXfXZf996Ttx3/\\nNuAQDWt/oHvvW9Cw8PC28/xdQF7hmv8z8GOv8Nk7VuftXv8V4Odf4djN7lob3et/CPyvt33+ZcAH\\nb3udgM+/7fVPAd/X/f/ngL9022cGWAAP3vbdRz/Ks/x24B2f6jk9mde7O6/dcf8n8POvdO8nc/vq\\nnFtAgDcBl4Cv+0Tm5dXg0X9NUqzqzwJ/DvjM7v0DNKQ5tzowpfS9STG/fwG4287x7pTSZnees2go\\n+Tde4Xr3A8//CeO5PYRaAEMAEbEi8re6sHHCsUex+9G+ewefP4hisIcicoji7gJc+BPGea/Lybze\\nhXkVkb8NvB74+tRph3tATub2LsxtUvkd4CeAb7zT7/1x8mpQ9ACklH4Fteo/3L2eA7+BhkYfy3mu\\no6HjV77CIZeB17zCZ3+SfAPw1cAXoQmyh7r35ZW+8DHIZeA7uoW/+uullN51F879KZWTef3451VE\\nfhCFRr44fSSme0/IydzetT3rUCPyccurRtF38uPAW0Tkz3Svvxf4tg7POg1rXPDhVzqBiOwAX8sr\\n45n/G/CtIvKFImJE5IKIPHkHYxsBNbAH9Hll7+Pjkb8P/LVVokhENkTkL9zJFzuvpUQXixGRUj5B\\nqtYnQU7mlY95Xv8aqqi+KKW0dxfHdLflZG6587ntxv8dIrIlKm8B/irwzz+RwbyqFH1K6SaKR35f\\n9/rXgP8ATb58qAuRfh7F5f7ubV/9HOk4uWj2/ibwX7zCNX4TTdz8GHAE/Aoahn00+UngInAFeD/w\\n7o/x9l5RkiZifhj4f7oQ8w9QT+5O5BuBJfA/AV/Q/f8f3K2x3Q05mdePa17/BvAA8Jwc882//26N\\n7W7Jydx+XHP7tSgUNUGplv9dSumnP5HxyL0D653IiZzIiZzIJ0NeVR79iZzIiZzIiXzscqLoT+RE\\nTuREPs3lRNGfyImcyIl8msuJoj+REzmRE/k0F/fRD/nky1uf/oGUVi0irEBKYAzJCuIjZrIgLZYQ\\nAgBSFOAscWOA3+jhB452YGj7hpBDeRTpX6lw+3NS4SCCxAgpETZ6xMwiPuL2ZuAsiIAPECPkGbGX\\nETMLRrDzBpktkdkCsow0HhAGOckaTOU/4j5kldj2EYmRZIy+13q9Rkr6BzQXNlmcyWn7QswhGfB9\\nod5K2EoYvxjZ/MM59sYhxAQxkpr2+FrWQK8kOQt5RrICxiCNXiuMCkLPUe1khFywTcItIuX1BXZv\\nqvebEsl7iAnJM3CWn/vw37kbHGIAPv9r//Y60y8RJCaiE5IRjE9kM49desRHAGJuSdbQjh312NIO\\nhHYghFKfTz6B/s1IceiJTgiFIVmITvQ5OpAAxTQROgKpCWDaRLJCyCDZ7trzSDYLuGlDzC3NZk61\\nZUkW3DJhQtIxh0Qy+khsExGfSE6I7vgx2Spim0goLLPzjvkFwQ8SMUuEQUT6nnOnD1k2GUcvbDF+\\n1jC+6JGQcMuAaSOm9phlSxIh9TJibmlHma6Xbm/ETFjsWppNodpOJAemhWwijC9FBlcq7Kxer7GU\\nWWLuSM7wS7/y/XdtXgHe+vr/Nq2uQ0zHa1sEaT1UNWm5hJhITYOUBRQF0u8RBz1iPyMMMnzfEjOh\\nOGjJbi2Q+VLPGXS/prZFMl2bVDVpsSSlhIiAteBUhUmR63NyVvdx3Wh1aUxIkZPKHGk9qcj1mNue\\nqz6spNe05lgfpIQ0LaksVHds9mm2CmJuCIUQcl2f9YbgKhi/6Bl8+Ag5muk5M3c8F8sKQkDKcq3f\\nAHC65rGWVDiStYSBzr8Jut5M7THTSp8rqD5ZPXtr7mjP3hOKPjkD4Zj9IyERraw/S70CSYm0WgQx\\nQhMxR3OcCBJykuTd5hNsnTBtQBYVmB4pd8RMd34S0c/qcDzh64F0CmGQQQQ7azBHc9JsQUpRF2l+\\n/MjWiv22RY4IKbMkY0mZRUIC75CUkLrVxRQjxcU9iktCc2GL2f0Fe08L7YaHLCGVIZtZiqMeeWHJ\\nrh3pBugUjjinyr3M9VpFRnRqVExKpMwC0A4d0QrJoMq1iapUjYHQ6LmMgdyp4Rz07u68WoGkz0li\\n0qJvnSKiE3xpkZiwq7lPID6SzXynXHUzmCCEHGydsE3ELjxpmIGAL7p1YtSY2AaSgPF6HQkJSeAz\\nqLYNxieGL0fyiccd1khKtOOcmAkmqO6SpGMh6XNbzXMoDHGgYwnZSvmCW1o1HovI5rMV4xcNR6/J\\nmT4onH/9Td6w/TJniyNutUPe3jzJrBnRjB0bL0RMSJg6gMjawYiF1WtlgkQdh60ivuf0nnt0Bk4N\\nm2nVmJES0gaSMaTSEQtHMsLydH5X51Ufhq5jAFk5DZnr1r9DfEA2xqSjCWTZWiGzrBBnsat1Wupa\\nTSKqwKpa95G1YA0SjCrfEMFaZNCHpunGEKBtjpWnV0WYmqYzPlGdwk6hp0zXecp034uPx/vWGJLt\\n9mwbEML6PqVuIETcssZdhfa+HeqdgsNHLc1GItmEqSGfWLLZAJc7zHSh+91Z8EH3bJYdK/mUdA8b\\nQaqGNB7oGuhn+gwNpCSY1qtutAYWXnWAEa0vtoa4Mbij6bo3FL0xYMA0noSQRHTBFra7aUMa9nXb\\nr6wukJxFqhZjDHmCZAuMF0ybVFmf3cIPMqJTL87UQZVOSHruuiVZu/b2U7+gOj8iCdg6ks2WGkmk\\niGSZXm9l+ekWJ7eV0aWkRqvz3iUkkgCFJcWEWFHF0wZSrYs1v7xHOTgD0k1FAqIqyLZvMI2DC5tk\\nt3K4daDj6JeQOfX+ckd0hpQbpNZNTkhgknoDIa0XTuhZNZyZQzqbiTGkIiMNStrtu6voJaRO2eqz\\nkpgwTeoiGAGjxigUFrsMYPS4ZHUOXZUwAZoA4lV5tz1DPFvgS/XiVw8/Wj1/MqrsJR57bM3AMLtf\\n1FtfqCPgpg2SEjG3xMx046VTrDrulfEA8IUhOp3saPVcoRR8Ce0ITCO0S/XyiqPA8IpncSbjdH/K\\n2eKIoa04aPs4F1hut0SnnnZ5M2PnDzxRDLHolN7KoMekBqhJxFyNlEQwTWfIDN28quJPmVEl1kUh\\nobDE3FBtfhIQ2pXCCoFkjT5vUOXv7LFyznKkEFXKMUGZq2JLCXeQSNInNhbTBOKgxHSGAgNS++Pz\\neg9lAcsKnENESMZCXROnM6Qs1PPvIlRAX6/2Ykwa/a68eGM6i87aMUJEdVDnsMntkXjbQr9HMkJ2\\n+RaxOEMyrnNaEqaWtaMiKZGGPcgcMl3os/ARilyNmfdrZS+1P3Y4Q0JCJDl1HEAdXdN0RqdbF4iQ\\nyoxU5NRnXk2KPtObSc6sw3hSQhZBvdUuzKsf3yA/8mRHNdJ4fUjWYOpWJ8gK1amCetOy3LWErASg\\nfysgi6BKp+oMSOyUMpAwhFN9FmcLkhVsE6m3MspnG32oVuERMoe0QXdXJ5KSTqKVtWcNarRIaW2U\\nJKEWOzeYlBByZFkTtkb4vsFNBQkWNxfyCeSTRHTQDi3JCLafI6e21eNxVhVi6TpvRJAmqnfQDS0M\\nMjDgS8F4Ougm6PhBPaYOCtMBylrh3S2JmRpY0rFnnQBbJUIB7cBSbQrVTnfPU1Xu2SyoF9tEbAPR\\nWfzAUO3oOVcK1y06AxAgWr0H00KyGpklgXrDsjwDzVbAVoZQgu086JRZYs8pRJhSdx6FlkgrY6FK\\nPmRqLM1txjOGhPGC70EoNIJJxpCMhvPNZuSl6SaztuCoLrmxNyZWFskicRSoskQylsO6x+Cax7QR\\njI4bVtGnOgwSErHvFKLKIJR677YSstmxMVVFlpA2YutAO7aE8q6iNiqZg6Y9VojGqJLqlFHq9ks8\\ns6l7tWrVCHTKU0KERYUtMkJp8cOMuJUT8iGSEuWNGrdsVLk1ra7X1quSD4EUIzLsa5DoPXE2h6cf\\nw1y+ATGAsXqMc+vIgxh13xij0KrV8DIZAx1MHHOnezfGYzjXdAai9UjmSIMefmBxcyAJRSv0byQG\\n11vMKkoICVnWnZIPGtGEqHvOdueLOt8roxmHuRroTI26aSOmCcjqOawkqUGImV2vlY8m94SixwjN\\ndo/QM+QHDe7mdA2pSOuJFzaZn8tZ7hqapyy9WzmjlzzFXq3ecWaROuCmNW6YKc7bN7oxW1RpLDx2\\nrmFhovNsC4Vzmo2cUFpufYbhq7/y1/mp33gLZ34VHUOWQZmTMvcREFOyBpFEUvuhiwV0AUMHDRlV\\nrCGp95U7JEEYFurBjEtiqfDO9gcDxYHHLRSHbzaL7jrgB4al7ZEfOezCrkNO8d1ijUbvDZCqpT03\\nxvcti11HzDoPNyRCrh6fLBvwntS23ZhDt/nuMnRjhOWWKtfyIJFPVIGDwjDzs8L8AtSnPXFnydGN\\nPoOLlvKW5hSS0X9trX8hF0KhHpQkVax22Rlbd2wA2oEQLdTbenzzUMU3P/Nu/tH73oJ9fx+JSb3d\\nwhJyo9575+mFQjroJ+Eq1htJPUshWx7DUGmiUUW1bWiHUG/ptasdwff1Rm88t8PkpqXYg82QWJzT\\nz/yGx51aUpUFfuCImWN0xWPatPbmIOKqoGF6Sswu5Pg+tBuJmCtOLSHhSzVCK6dDgkZH0hrEf4Rf\\nchcnNxHHPWLhsLNa82idxyxVQxz3ib2MequgHRqyeSQ/bDSHENG90XjMoiZ3hnacQ6ZRma2TKriq\\nIR1NiMsKu7ut1+2VUCfYGpNSYvHkaa58Y8vpnynZeO8tPcY5JD+Gq1KvUKMzKNZjJ6J/RvN3CfW6\\nTYzriJ0VfFg3qgeietupzHCLwOYLgpsHskmNmVa6J6dzcI64ob3NUuZUJ5gO91/BNylB05IGPSRE\\n2q0eyRn8wBKdYKuoe7Z0SJsh8+rYUAC4hKlbJJR3NF33hKJPRj3d6IT5hZJeYbHzFrs/Q2IiuzFD\\nzhX4PlSnI/UOgKMZaciVzSL5UUu2Nyc7ahDvMN7iS0M+DWo8jpaKZ2ea/Fh5r6vQfX7W8he/5v/j\\n6zd+m+/58nfyo5/9ebyz+myGlxaYea3hnQiYROxl+EGGaSJ22UKrmyyhCkFxcCDGDr5RKEp8JA4K\\nkkAonSb1MoNbRorrc8xUE85xc4iMc6IVJvc7mk2hd73zyAuHqypdNFaNlam8Jpxbj9QNySj85epE\\nk8uxN+3kOAlkrSawYiL1ClKZHYfJd21e1VCFXJhdEPKhkC2SJjt9on8zMr9gkL7n/NYRYWPKpXCG\\nZmQAwS2Ech96ewm3UBgs5upxuyW4Gbgq4XsKpcRMr2cK9XpJ0I4jX/X636Nva37ks36aX33icX7t\\n4mdTTAOm6XIy+THE04wV2pGonrHxCgcZhBQT0YKIRiq2ikgUfCE6BpNY2cpQgHhh9JJhdDlgQqIZ\\nGoh6XLld8eDOPpezTZbLIc2GhSso3NZGQtE5Dl6hPvWWexqVekgmISj8lRy0A0O5t3qdEQY5zTij\\nHZhPjqJvPQwKUmaoTw/IygxTe3UiADNZEAabIDA7b0nWMr5kyKZKHHCLgK087sYEM1mSt4FYKhxp\\nKo89nJGmUz3XcKDrtlDlnfolyRhiP+Plb2p4zZlbnPsvJ3x4skPx35/FTmvYP1IvepU3yB0xV31h\\n6i7aNnTKPiFRIZePgHashdBFEzGuvfCUO+y8JTuokNmyg4EE8YE0GrB4zTbNhmV4sSB7eV/H0HqF\\nubzXKAN0D64inATBiSb7c4MkNPpvoiId1irEkzlN3ha5vh/vbLruCUVvak+SvAuluiRiZog9h6kD\\n9mDB+NkpbX/M4hzEQWDyiKPcM9gK8kIXDj6QXbqJ6xWkfoHf6OniC5FYZLqQrJAyVfQrDDmbeY4e\\nd7x7/2F+9srryEwks4Hxd13m5j96kJ33xHVohzFUuyXJQjYFO1t5+LL2ptbKPSlbRlaJply9Aknq\\nScbcdB5MQBa1WuugCVO7DMwfKpnfD83Zhmac4eqM/jWwMwFxpMIS+jn1dkY2DeR7C4gRUwfCdqaR\\nTM0aJ5eQ9L57+Vrhp8KR8m4zyB3GgXcobhmpx8pkSUa93WQ1geqqRD6LjJ+HvVFOdl9gI18yeeiQ\\nyaxHqBzt1OGWGsb2pgFXW8AgmwrRrKCpUEDIVdFL0r0JCumk7YZ/9QdvIM0d0inZ+//iNQ7ffo7R\\n5UgS1uOrdqU7hyDzLg9jBNsmYlxBM9LlAdSrN82KnQMShVgkjTiijrE4SLilGnxTGFwF1f2BR3f2\\n+eLTH+B3ygd4d/0Q9aKH7xlICk/5vmV2wSIhY3zRKzvHawKWBKbuMOYoSEfG8ANLzIcQE35oaYYG\\nXyir626L+KCGt4MqY08jXgvgLLKocLdmtKMtQgl+kDjMLOUtg22gyIX+lVZhmf0DhUWaFmONstvy\\nDBkMjj1YEY0YQCHUqubwTVvwIlx5z4O8JJpPmX2L54F/XTKcqwLWXJCh2elj2ogEnYvV+pe6UWbe\\nbWSQtVLvrrV+nZIq51qVv8wrNcBAKguk9Sxes83BExmLs4nl7oBdZ8iuHiKt3sOKQRS2h4rnzysA\\n7KzGDwZI0vxgMqLj9XGd4F7BNymzem+ZvWPn7J5Q9PblPcoIzamewgsWTaiEpAnGzGGWLb29gGk7\\natOZhkpysomoMqODeXY3iIWj3ilBwGYGW/luoYgaESe4ZSTmurHaDUcoIzfmQ84Op/houH9wwLfu\\n/hrf/hXfRPyAGgg7bwnjHN9TNkTqwj6lUGqIvVbuq8x6VPqGhKi0sDog1iDrBGCXJA5RjxVBjmbY\\nXka92cM/sGQwaJh7YXEmI0mG748ZfGiPVFiajYy2b1juWNyZjHwayeYet4yE0uCqCKIsEatrap18\\nStYSS3ecTb7LUO7gxSkSRix3rSZO0USibTo8PFMopLxh2FsMMIPEkzs3uNobszfvM/VD9V4SVNuK\\nTy9PC6GnFFS7YuJ1iiQZNWxrtk8AjjLEC2kYSBHsqOWzdi/yz5/aIZtmxFw0Z1Aq1g46r2aVvpBV\\nIlnnNqzYOV3OXSLkc40qYqbGRucVbKP5Hn1DyKcBtzDYvudrzv4uT5eXMRJ5bnOXG63h4PGS3d9T\\n52N2wbI8nfCDxPyCI5tmisVHZSFRKZQkSd8LmSbvpWMK1SPp4K27P68A6eAQJwKMNEoWMEu/zgGl\\nzCF1QzbzSMohCdVppRznR+DqztFoW6QskRJSVZFCRIpcmTRAGvaV3gjIil4cI6ksiBbKW6LMF4HQ\\nT7zxtR/m2RceY/CH7tiTdp1CNCjRYaW4Q+qglfSRTk73veRW7ByOKdIxQgTTdsyf1iM4hXdiZLnr\\nmD4cSf3APDrK/YKB28JNG8zF64gxxNGQWGbUWzlu0cctWqXaTtuPmKtQumNmn1N9kkSIfbXc0Zl1\\nTuSjyT2h6NPWGHttj97VpJl1UL6rkWOWjTEMLs7YHY05eMpCsoReojgUiqOECZH5a0/TDC2hELJF\\nVLpdHRVP9xFfOtqBJTqoNq0qawv1pvD46y+S24CTwGa5wEfLu5ev4buefAc//G1fxkP/DBBhcTpf\\ns3jyo2adSwDAGSaPjpiftmxcbOk/d3B8jx0/V+KxV2FrxayTEeZPnAKgvLnEvHiVdlxQ7YIYsCZi\\nlpa2D+E+o7mK0SlGLy6xTaTacvihKiRbG3o3DbbtcPnS4OqoFMOBoR2WClUoMkExieSTtqs1uLvQ\\nTbvdo3dlTv+lROhr7iT07BoDb4eWlAvDlxIHf7DD5OGSPPecHs2YHvSxEzVIB0842gH4fjpWsh5W\\nzZZDmQi9RMoSYQjNFh2EERmenjPb60MQpKeu7zuvP8ITj1zlheEO9gNDspnQjhSPNy3kR2mdS4hO\\n8f7pg4Z6K9G/JowudcrMKKbslpHiCGJmCOUxJZIIh08YxBv61xLjSy31Btx36oC+qRmZhqvNJpvl\\nkvxs4OVsgxt5n60PRrJZ4vB1gWyzxuWepnEsr/UwrayxZdM5OO1IazBmD1iS1QgjmwrZVP/vZnd1\\nWgGQ0Yg0X2AnU/VuO/oyrkuaGkNqGrKLN9nZOM/BExnlnqEdwOBGoNj3Come3VEHyEe43iJZrjUi\\n3itUk2f4rR4xM5g24g6WYIXQzzl8EmwFptW6BWmF93zgIcwblly0Z3ng3xwgPuDHZZdotWRdng4R\\nUmFI1rC4r89y2zB6ydO7dKT3kbkOU3e6FPIMadr1X8ozws6QZnOH4uYCuz8jnNpgfl7ARXARt9Ta\\njcmDBa7K6Y0LymevI8uG5aMbNENDPG2xTUZ5ELQeY+EVkgkR00b8ICOURmtGOhZYNgvYZVAW2yqf\\n8FHknlD0fqOH6WW0GyUxN/QuHsK1W8dFEGWhoYsVhi81TB4uFXcUKA6jKo2+o9q0+L4m07JVm/4O\\nm5YA2bQllIZo9aHVWxqGz95Qsx0czkQihiY69tuCB3p9cAv+3Vt/nK/6pe/pcGC1oK7SxFeytnOp\\nEtX5AfPTFj+AvacyymsFZrLUxR/oaFoe6bj4K0XRbOYcPJbRbABkjD88IuTQ7AYkwuTGkNElQ3GY\\nqLeURdP2hWY7JztqKQ8dldFisVBCMxbK/UTKFJ9ve5r/kJTWnjXo8wu5pdqy9G56NVx3UZancsxm\\npkVPuTC61FBePNTnAcjpAcmogihuGeqHIATDtC6QqdMN3IN2qIVHKY8wtZD0GawSzW4upC76jr2I\\n3WpIUTi/e8TL1zfVi1vd7zRjMciwJvL33vxP+O73/Wea4LWp488f4/10WPz8PkuzqWOYPJYo9yzF\\nkVJ1o+tom13CmA4KkqgeZnrNnLJsmSxzDq/0CFsNn7lziWns8XPTp/nlK48zmZdsj+cYSTQbkcUp\\nw+BaIDu0+J6j7DUMxgtutZa4l3fRihaSSdBFnuxqTnUMMYNmA/JDIZ/eXQMOkIoMsYawMyIWjuzq\\nIWmvc2wyh2SZRrXe07s0ZXZhqzPQQnHgMUGx7jDI1zkIm+dKEBDbUTcjsqiQzZ7C4DERxgV20bL/\\nuj6my1WkjqmUzQQ/FuIs4z/9T36Jd7zjz2A7hk/KnRYgVb6DPCISoD5VUm0YQi4cPpJR3MzW+bR1\\n8ZTTHINU9fH993IOnhhQ7QpJNhhfHJKsFjwSwN3MGV1MDF5uWJ7WNV5vObKzW9gbh+SHHl9kkKSr\\nKTG4RSAWSrbQWgqL7xl8Kap3Euvo3Iwt+SRoFHAHck8o+mY7px30aIaC7wvzM7tsvDgkf/6GWnYf\\n1KIuaqrHN3ALaMbgZh39rMNPJXbFI16rQFeJNN28iocXhy3J5oRcMVTfU/3rTCQ3Hmcilc+40D/k\\n2flptvM5Py2Ps/udL3Lz7z+0ptnZWgjjHLvwsFSGTXltTnxqk2YzIV7Y+4wxWx90XZ4g06pVY5C6\\nRfoZYIhWCKVh9mAk7TQkb4h5ponFMpAqR//FjP71SHkQKA8Myx2Dq1bJwMToYkXb760XQ8iF6BLZ\\nImHapJBEt0Ck8zQxSklMRl8ud906R3K3ZLkt+IHB98H3EoszBaMzu2x+cIrUAdNExa+TKEOmtWyP\\nF8zrHFMbpQxaSFmHhy8txnceLcolF6/BSTYRGEPKhVA5sl5L7R2psWA61tPSkm9VzA77VGXG3/zw\\nl/GGt36Q9/6/T5IytQcSoB0KsQK3VIXdu5WYPhow4xYB9l9r2XjOrteabRRyc0v1LmOm6y7miTff\\n/xKft/k8l+pt3rX1MMOsYRYK/nBxll97+REOro+RpeHaQYkZtmuvzXjY/CDcGjmqIqdwgaLXUvUc\\ndmZUyVnWCj65tI4kku3YQg7q3UTM7z52Ezf6tJslzYZCavZCyeiFIeaS7tkUItIZ9DAqGFwN1JtG\\nYbsOzkrOaBQVU0dbTh2G3cEuHT5vJzV+q0coLXbhCYOcUOr+jZnmRLKZUJ0NFLcsoYR/8M5/H/7z\\nwGP/C6Qv3N2AAAAgAElEQVTcaEFhTJpr6+iWKbPk+xXtYyNirrpj8uiI8XNTNTQx6ri6SHzFfU+d\\n4p88IjTbQRPs1mouLCayqWH0Aow/XJFfOSDfG7C8MFD6LJAGPfL9JfWWU6dCVNnHzODmHtMELdcq\\nbFdNbtYR5srBjVbwfUO6w98QuicUfSgM9UiYPgztZsBNDb5XslmcI7+51CKnVjmts/MW39cNWe5r\\nNWJ+2NEEk2NVRWiX/pjWFFNXpaoazlYRlwm+D/Uu3H9+n5iEmAxOPAc+5+pyg1PFjA9NTlNHxzef\\nexc/eP4RbK2QiBxCKCzSRFxKMK+Iwx5nf33Gtc8eEjOFhPaf6nfeoeLT5VEkm0VlB0UthLFVJBWG\\nwbhiZ7Dgc9/0AjebETEJz4wu8xMvfbkmjaeeYj/gFrmyFpYt9uYRGMOpo4qj123SjNR7DrkmPGMm\\na065JOjtRUybyKa6oI4e7WO8eqb1XS6saYcKiTQP1mxszTk66hN6Bb4c098La554MkK7HRiNKhpv\\nme31KadCfrTCvDuqbLPCqBW2WFEwo6MruANphNSDQb+mcB6CgEuISeAtzTRH8kh7UHI5GkYXaqrT\\nCulIEM0BdUpkJUlg/CHH5LUJySPxvoq9cQauK0yrLW4qZJPuOXeQoHjhQnnIa8uX6Jua383u4/nr\\nu1za3+Ls5oSDq2PcgdOksQXfCm6uxtotAuXNmvIo5+rn9rm1lZOPa6QMpMqQVjkEo968mwm2Vk49\\nERbnlUKYBNrx3ffoY+4IhWHygKU6lbALg++NGfcyspsz9Z6mcwAmj/Roh2oIy1vKgJNKH7BZOmTv\\nUKGUpO0KViyXlGcdzKkedupZDp7sU50SZo+19C5l4Dpjl8BNNarNjrQmxZ/3+EGn4oyQHVbHSVeD\\nkiescOY3phw8NUSiJvcX9w8ImRAKwTaQzQPZxJNd80jVIEZp08kmUi8gLjEvDdm+U9LBRqD3W4Kb\\n1qTpHHNwxKA5vW5dIFWDWMPGexbMnzy1NsSa6Nf9GnO7Vuq9fQ9J979pA8szZefEKlvxTuSeUPSA\\nPnhRzyRmiVAoLhWGOaEwuIUn9IZa6h0FE9XjymYeW2mxVHaklY62q4A1VYtULalfQKVVsHFs9UE6\\nhW5sBT3X4pNhv+rj+oGdco4h4UzgQv+ITCLvWTzI9DWe7d+1uEpLzu0yKH+99cTNATG3+IFjdCVQ\\nbRnaoSYPV2yJ5GBxzlLsW0ZXFBaQjjNtZ4ZB0fC5p17g0PeJSbhZD/m55ev4wq/4bd7ztmf0OcWE\\nqwKmDZhFo9GORMxsgavGIIZoNXw3YdVbRuGcdiQsvcUtEv2bgltYXJWYnTeYBlx1l6c0qMcFMCga\\nmoGl7ut8rhghtlUqolkkFotCYZmpI59ozxmPKq9QaKJVPF1RlfLqTUi0fcWoY6befzmqqZqM2Fk3\\nqSypDND3mCxS9hpqFxHgQ9dPIQNPWjgkCBIEN4PisKvKHYoazwLyG07htEFkcHZOjIJzgcwG6jZj\\nMS2w14pjlhNwpdrkxy5/MX/48hliByFVByUv3uzxxJNXeP63HgAPq5YLklaQVFT64cziZo7QNzQH\\nmqA0HTc+5gk2WrJeS/CWZuHwt5wmDwX8+Zq0cNiZ/eOm5xMT0SjaNGrQkgNfol73qCRmFjPuEUpH\\n7JQmacWPb4+LiZa1cuNXBAYfSIs50i+7FiYGegXJaUuI5emuZqA2hCJhl0IoE3543FsIserwXS2Y\\nPgAbH65xh6s6k1rzB3lGKhWjT1YYXm1pRpa2b5idtRivlc8rRyM/dGwC7mCJWWjfGbsUzMRhG40y\\ntU+SkB86XvoSz5N/LyJ5RooBmcw10hn2WbddiJFs0hJK+xGFcqF0hNLQjC3NUA2/beiKDzUnNDuX\\nYbvq8TuRe0LRS8dP1vDcYJeiXOsmEjOjhQTDTGGSpAopnyTK/YCdt2ol64iJ3UJKKDxStch8qb0q\\njCFsDyEoH109XWhHkaO6pG4djbdslktiCsRkOGx6xCRsFwvuL/f5xS//O3zNC99LeRhxCy3QkkW3\\ngBoPTnmv5a1IdLk2KuupUTEt+EIVVXUaknWU+5HBtZblluO+N73MW3YvMvE9nARGRcWZYsIi5jzd\\nf4nfzt6oBk3ATprOI+kqPNuWtDlSI1geUwJBn6kvFRILBcRhotmEetuSTW1XgQd+yEdW390FSV29\\nibjIpCpYzgtc27FWpHv+mc6naQR/lGMqQ++aIZ+kNdMlOoXKSJDNdXG7KhIr9X7W1b+VEHMhzzzD\\nsublK9vYmUW84LOIZJHkDYtbfQDMwDMqG370Tf+U73r7N2GXQnGoFbrZMq7rEbQhG+RHWnbfJmhc\\nxNhIXWcUg0DuPMNTNbfciHiYkx0a/CDx+9fOEaNBTCRzCWsjPguEYNguFjwfwXghkjDtcUQQckO+\\nbGnvHyhEVAuhiEhjkKDJ9FQG+qOKQdmQ20BxxlPf79if9QmNGi6z0RDs3f+JYNMqbLiiGRsPxSTh\\n5ppMjF1Bml202pPH69zZOqiSX1WFxgi+w/yrjtzgPWm+AO+R0Qi8cu5932pF9Tjipga3ENxcW0BE\\nl5Ag5DctJgi+TIRh5MI3v8jh2x4g29PqXIkJfKt5hFUiUwz5foVpc4zvCBsZ6oR1cGi1I0zbgn5h\\n6D23oD07ImWs2XcxT8R+JBYGWwnlzlIreOta25Ysl5pzyLO1kk+DntbhiBB6XV8sowZUnSE1kDGH\\nxkA7zMinThldrRbKvap49FoBqckUEhQHQnkQyCatLppVeGKFYpLIZ4rZ5ofNcfFDRCsCo1IeaVpt\\nthQjREPc6IEIdtlquwUr9K+pl1A6z3RZIAI35kM2ioqNYskT4+sAvO/oHENb0SSD+fwDip8YEHod\\nj7XI9RohYRqPaZRi1rNCtZVjK7XGw5cDi12DHwjNWL22atsQs5zDx+GLN6/ho8FIZOwqMgn0bc1G\\nWvK64gq2UQ68JuJum92uh0jqZR3PG/Xm246K5TolWCvXPAzpKHiJUAim1UR1ctz1wppkQVqIC8fC\\nFHCUkU2EbKYsIEk630kgmwu2UUZUPlHcW0LnvYp65sYfF1uZNiEO6g29v2yaMI0g3jDpjVhuK+i+\\nMjZmaUneIBsN584e0AbLzRtjchf42YNnKHaXZB8eaUFUTPiiq3FoABJuoZGEaQyhb2gnOQQh37Pc\\nOlVixw0bo4WSsIYtLRkpi6QkxCgYk8hzhYis1fcL2zXhMmnt0a8Sqat8STMyhJ4aO1BoClSxEYW2\\ntZhe4vzwCCeRKjhO96dM25JFm1G1jpm9s+rJj0WiMx18GvF9oTiM9K/WuFtTUr8gFrYrFIwUEy1I\\nMm1ScoL32p6gq8gGdczwXrupJsWcVs3KZDLDNi1FGjG6lDE/bxSOSjq3+USIC2F5LsB9NT4I7mKJ\\nd4n3v3yW7A0ZD3444k+NyF64pmyeukEyqxFfGzGLhsxHlqfGGtkm6N/0VFuWZiRIrntpeToDc5rl\\nrqMdRlXyWVKigCRiqdXqZ8czMPnxPQCJrg9P6iZ6xf7pOrq6hddWKl3fJtsAkvCDbi2Mj/tA6brh\\n1UWvjE5L3cs9KA504bcDQ3WmoNhrcfMVhhoZXazWTX7MvGvJ2iVOCMDNfVYNXxJoQsiIet4iWtSx\\nP8MezHHTEXtvKpg3OW1rSdHQy1vO9Cfs1wP2mwF1tIyzil+4+TrK0y2/9Vn/mKe+5K+y/XtCMj3c\\nPMctWuy+4pFhXCDW4OYtts6xXWOu0e/fZARgDPPHtvE94eBJy5/9ln9HJoHC6D3umDnbHR8uU0oF\\nv7p4nGZgMKcL3CLiZupRKSPAIUVOKLVVc7LaXmDVRjfkQr2pi6N/HWIh1LuKLwJkh1ZD0JZ14c3d\\nm1c1Ku7AkY6cBiGDxOKsoXdTq11XCeTe9a5612gPG1snTbZGVfjZvGtwFZJ6i5m2LjAe2qEyq4qj\\npJW0Ny37n6MdSE2jTkRrwWw2RC8czPr41uKKwPXrG7zXRL779b/Mj374q8gPhfk5LWyyy0R/LxBy\\n9a58qZGmXQp2qZ7V6fdo/5SYlRw90seMleo5fPCItnVYG7E2kjtP7gJWEj4aYhJ+6+r9WmCVa34g\\naQsmYq6VrvWZIYuzgh9EUhGh7boaukRyCTNs8bXj+uUt3IORZ3aucCqfUpqW359e4CZDls3d9+ZX\\n4mYtg7aLuqWrMt8cYGYV2a3FuhPkxu/vqSIH0nS+3pepbRHntHHgKpfmuz4zTUtaVshoqEVVyyVm\\nsWSnDUwf3FqPwbS6zpbnA6YW/DSDIPhhItu3tBQ8+dYXuHH5IQYvt9jTW5ofCEE70/YK7X0zmWtj\\n1UdHGnm0icF7rzCI2jWzfuzMuq3IxbcqIyz2IslFpIjYLBJbQ2oNySYuX97h0d2AGRW460fQihqy\\nptGEs1khFbnm+nzED7Mu2SvUY4VVy4NIO3Asz2itQLMF+UGX+G9WjshHl09GcfTHLNP7DPWG6ao3\\nlcLYjIR6ZKm3daGaNmCWHrNo102KUt5Vdd7WIliKoutgp4VJOK36lBC1uZLXYguMwe3NMMOW2bLj\\n7gO1t9xYjnjDxhVenG3z7KHy23eKOe+d389v1sIv/Ec/wvKMMHnAcfONBYvzPfzukINnNjl8vM/B\\na4fU2wXFJNK/FZSKF5Q/H/sF/Rcn2Cbx3d/wM/SNzlQmgXP5ISOr3nxpWqxEdtyMz+4/j+9BtWG1\\n7UNCn4E14CzthW3asbutR4pWoc7uM0wfENpB12mxL101b5fFN0k56KUqm7tdQbm8EJTfHLoq1UwL\\ngOqtRLX1kV0qXdXh2iv82Sk8oe0Iuk6cS6+hLh2/fdXgrDMQvnNce/sRgmCWx9i08UJYOHZ3pywn\\nJe1RgbGRrNdy83DIL+49xQ997f+NHyT8EJanE+1IaHuGg8cMh4/D0ROwPKNGMT8U8qPOG+3YLjvv\\n8wwvCQ88dY1qmRO89kPaGSzoZZ7CBkISRBL9rOXp01eRMxVxwxN7EfFdUk50Dxw+ltMOta8Nrovi\\nXCJst9jdmrzUnIPpe24eDXlpsUlEMBI5Vx5xqjdjq7/Eudua190lWZwvaTbz7v6V2eMHXXOyrqeM\\nLBulRy4qTURGZeKINerRZxkpREj6WwtxsVDFf3th1GxOOJqQul7sZm9Cslowt3pWoNCWOb/Eziz5\\nvtYThF7CTSzvf+kcD/zlZ2k2HPWpPsuHtwjbQ1LmqO4b4zdLwplNCIH+yxXlniebBh2HtaRBj+JD\\n1+i9NOPm5/lj6MxF3KjF5AEkkaKATUge2Do9ZXE6p9ksCDsj1UlitP1BSoSdEaGfa0FUUpgyFMLy\\nlGN6n8P3tIK8HhtslXCL29ZGTtdIT9Zr/qPJPaHoEaVL1ptdO92Crn+HMnKko1+ZRa3QSNCHE0tV\\n9H6zRywyxay71qmrjnOpX5KKXP9d9auxoh6Es5zamTLqV/jakaKw3V/SRlUQo6wiRMPvXr3Av33u\\nUW7VQ675TV6TDXn4y1/g677jl5k/s+TKF8Ll/3CA76n3vPdMYrnjOsWl3RjD1kDDLCu0p/r8+b/x\\ndv5gfoE6OjIJ7GZTBqbmTHZIJp6RqTjrjsjEE5NhfgGWp4XlTteEy3ZUr+7f6I4x7HpDWJzpuir2\\n1TM2beclN6p0teGVIW62+FEkunTHnfDuVJJLhA2PH2k1YcyjvleA79PBJJo0to3mCkzQhRxy6XIL\\nZp2EMlVLLLXtdDMyVBtGG5i5VaWy1gokgeHOgtgPuErXlN/wSBbJbaAcaWOtcLmP+cMhvspoouOr\\nB7do72sYvvkWzWZkfiFx+IQhZRCGkeETB9RbqpCzqRbrLLeOe8n4nqH4ihtcPxrhsoCxkVPDOdZE\\nelmLNZHSeTaKisJ6RlnF4+duMNxekPqqLEwHV0W3Yk/pcyQpe4hRi80jRdniW0sKQqwcbeW4Nh9x\\nrR5z5Ps83b/MA7193J2CuB/r3Fqdg3pLe977UmHQZuwIpRYbSd2RBfxxb3dAC6x6pVKNrVF4I0bM\\naKjKvixUyd/+Ax3GsGo/XJ8KtANdL6GE6pQmYo2NhLFmwXtXLZsf0H5JsTV8xe57OXzUcvmLcqb3\\nO44eG3D0zCnN+4kweWykDcZ8JN9bkN9awOZI819lRhr2+eB3jnD7To1IkXCjFjER5wJGEiYL2ELv\\ntfGOw0ct0/sdy3N96JUdutBx89dNG/X3F0Jv9cNJQrSypu5mi0R5mJSI0NFum81IO1TCxZ1WPd8T\\n0A2s8KbjgiTocMp0WzfIrqWnNJ4kmXKme055uQb1cFNSDq6RdROk1Q+KrCGeCITI8uEtrNln4Dx7\\ndkhsLD4adnszJr7H1fmYrXLJvMr50ifezzzkjIzW3f839/8b5rGgeGPLjWZMYTzPzU/x0nSTP3fq\\nEv/KfiYpS2y915HNYf7mgnPvyrn1GT2+9C/9GlebTXyyGElsZXMskZAMMRkihr0wpEoZlsjTvVs0\\nOwGwuOVxTQAxdb8upRRNS0f93MlVYcK6T7ZpE7aC4cstoytKi6u3oL4vIFsNLTlm7y6zM1Y6pmu8\\nJV6UVbPq+94t1JWRsm0iIJCpx6L4PGtqaLIW37O0Q8VNk9Vkd3IK92QLjQ7mZy117TB9T3ROufcG\\nXOGZ1znVfqk/8BKF/hv3iPOS0rZ8oImcO3vAsskYPjAhBIOzkbpx9Gxko1dxuOmp+gZJDjdXD9+2\\nhmpL6H3JDRYdVBKjsDXSqr0QDQGovCMmoTKOzESeGlzlfHHEon2c5aKAlGldQNCWBqHoHlEr0BhS\\nTyupY2torNXuqdGAF+xBwc39Xd5V57xme4/78n2eGVxkrx1wYza8u/O6mt5M53IFE64psKuIc/Uj\\nIqDNvIz5SI58tx/Vu3ekutHmXavPmoa0XCJGNBcGtA+e0kh0EMC4rtYikYpIWzvcniMUifxA2H+z\\nV/pqHni+PsPytBqH2f2smXfutX3sEqpTkWZ0FtPA+FKNmzYsnthl8CFh/vCIy18ZsfuWlKn3Hbe6\\nyDIaUoTQwTarivNHdvZ43+kRxhtsZRk4uy4go8gVVvRRo5w6YAauU+y6N9wyrRsy2kXD8HLB/mt7\\nNCNhcT7RbmvFf//aqwij3/5Ay/yMo9lQL9QtukXTKiQTSoupHSEvaDZzTBPJZq02Q1tzSXWD2cl8\\nXXSRVj+AgCoJrPZtlxC48QWn2X8m8niurJlT21Nu3Njg6s0NDhc9rvQ2uP78LvUDhwC8ON/mS069\\nn5thzM/Max7LbvL26dN83uBDxNIwMkueLc/ypguXeENe8le+6ld4tt3hF978NDvZHCuRf/kFb+Db\\nH/lFjnyfi9U2TZdhO2gHnOkdMbYVTbJMfY9FzGmtZWBqRmLItmrCvE87gPn5gt6tFrNwYAx20WBa\\ni+mq/oYvG7KFXf8YR7ZQ78H4hF1GsqOW3nW4/uY+7YYjtl23PHt3+daj5xzVqYTvK/zg5sd8eFBs\\nVT1XpUdK0AVua0g95eGHQiEaW0fiZkE7cpqg7DpUhqJjPnQJ6JvPGPxDS8a9hum8pDnbUr6Uk19z\\n+IHlcCNj+FzG/EHNUxzcGrF9asJeNeB7nv8LnBtM+L2DC5zemmIk0c8ark7GPLh1wFPja2wWS24t\\nB9zYGNEkYTxccuOlDR55/BrXJyOqZU4MgnWRRZ0zzBswkTbYNV6egH7ecj47YNvOeH60S+Ud1xcO\\n2Xdd50KNwtxMsAsLBtqm+/WyLNEurEIIgK2MHtcI/miT594g/Hb5EA/3tG2vs3ffqx9eWlBvF1pp\\nnmvLkdi1GU5OSP1CK1LLnDDU/9tbE+h+WANnj38ar/vJQSUWxHX7hBQCpigwmxuklJh/1kPcetpB\\n8mATi3OR/hVDccsQJwY/cGw8D9MHdG0UVzPacSROcn7y/6fuTWMty677vt/e++wz3PnNr+ah5+bU\\nHE22KNGkINkSJVuhFDu2ANmKkcQRpCCApATIJ8dxgCSOAceA5QQJoAyWZSuxFEcxo8SkKEoUh+Ys\\nNtljVXd1TW9+745n3Hvnwzr3VuuT20AJaB2g0FWv37tVZ1p77f/6D1/6Phg2xLct5bqXBXPQoI4s\\nxa7DjgqO+wmq0swupiifUg0Dncd2mL63wOwnbZKXphp5qDVRrxKxexnBzEqNVwFMYBQv8Jmn6Yjt\\nQ3VhRHznVOIMbYQua5g7+XNkiE9Lopleud+uqNMA3hPNC7bmFUfvG1IPNK4WSNS9Rbj1bVHos3tz\\ngu4RlTL0WopVlJftfZMZIKHJDNVAE5QhizRxy43VhVwQl1n0oIs6m64w++Uhgiu1cn5bnFOE1HPz\\nYIOPXbtBahr2i3X03FLVKXvDLutXTtnsLDjfHXNadhi7jBvFFolucF3Na4sN1qIL7EZjvlNcZOES\\nOrrki4seP9p7iUvRGT+/9Xv81uQZrsRHPLWxxwV7ysyl+KApXcTV7JiOKTEqoPEYNA7FUd3joOpz\\nNT3mX+XnqHOLtkEESANFPNM0a5nQS5fnB6tM1OTMCVSkBAZRnpUFrmpFI9tfX+CTjvhoKNElPMyj\\nd8+jvKYaKnwsKTwrZS6t97sRPrxPWqWuUdiWVaVaxKLqKqJhRDxpVtCMKYVOZ6oHD3uTKqqdhgub\\nY05mHTZHM05UF1PEdE7EFmOxHTN9rEFljvR8wWKaoBW8cbCO0p75IKYeJ0yyik5cczhZxznF/emA\\nm8cbvHPnPoOkYPvClOfvnieLa05TT9dWJLYmn8foyNPplCS2YVFbrH5QaOeLFr8GUlXzWr3NyOZs\\nduYcdXv4eWsAF1Tr1dQuiHE75/AKp0BF0skqJ121qWQB1RU0Xx/yZXWF72U71M5wOn5rKUT/Joc5\\nmZO6gOs8SAjzVov3igttnGGMz6x4zWuFNRozbgevS0gnMkKeeBN7RCUJGINWCr9YrKCOcqTxMcTH\\nBn8tpzEBU8UMbjnsXAzFTp8Cl0jHa0+kw46PDbpSVEOPnUHdl+cuudca3mmDOu7CVkOIAvmVmuyW\\nxcdiGRKcfD+1kkF4x0l4TKuL0CbQWI+ZGlStaYaOk7JLNJF5msukWQ3dFKqGZR4uIIue1g+88Je5\\nz8uoxqWFQ2RQs5zNr3lcvIbLhLv/Vu0t3haFXu+fEPcTTGUwlZGBbMTK1mAp6Q4tnW5pF6t8wE6q\\nB8rXAD6NMW2qjKqbFddcvNtjQqLxqZFpdaPI0hr9JhxTNQqXBFSlGWUFlTd8YPA6351f4LXFJpmp\\nSXTDZ8dPk5ma+9WIp5O7vFCc55w947Vym7Vozq+efoifGT3HV8sLaOXZa4b8/O5nuVltUweD1Y5L\\nHfEGSVXDhplhVcNek2FavbNWAmX8k/sfJrsRt4waodYVIyPOgK1EG+8lEnBpZbr8rxEMXFetl0sL\\neS7TvLr3fBvUIT7sD/Po3S7wUYopFHVvuc2nDclAGGatHUM1CNKhZ0Bo/VmCdOwSJqLRlUHXgWS6\\ntBcWL3iVtZjxUIEOJFFDUxt8UGjjV7OHYr3dWseeUGved+42X6mvMpmn0oUrxcHhABJHWVrODyac\\nzTLStGZexBjj+dqty3zoyi1eOd1C6cB4kfF9T77K3fmQorJoE4iTGh8U1jj6cUlsHAfzN8EnQREZ\\nx+cnT/LFg2sYFSibCK09da8hnMbYeast+WMgrDh36ga8BoWSIWRF63kj3x4VML3fY5ZlogyuH/4o\\nTi0KtNGo3BBFQjU2efjjdL8WY6/7UfsOJyQ+oBeVvJshCJa/FBVFYZUVS4NkvxpDmC9QwwHxzBPl\\nmnwQ8LWGprVItor5biT3OQhNO1xYwFkXs1CrIXd6KHGQ0VxRbntCsWxA5BnMblny6xXRoRXx01yR\\nP1pCIZCmjwM+EzW0zWo6aYXWnrOz7gqyCQaIAi/vbdF7XTQjppBnvl7LiA9mYorWScXqWasV6rCi\\niC8pp233H2Irv88SlAv07zrqjvyMnb+13drbYxgbAvHrh6R3JnTuFkS5l0SaqSPKhX/7Zv6oS0TV\\nWg2Ej2bGOWZcSNHTrAatq1VxkT9IfnKeamCxE/msv3z9G/zR8Xm+c/s8qpWg61rBsOa1e5tMioT/\\n6ZXv40p6zItn23SjkrM6I9MVr083uJYcYpQnUQ1X4yMuxicAfHt8gVvNgK/Pr/FavsWTyX1erzd5\\noThPHQyZrrDKMWlSFj7m2PWY+4RL9piOLpm7hLVoQapqXv5Xj7D2iqd7N9C9Lw+OqaEa2JUpVDCG\\nuh9TDWOxPM3dim++9DgXlorky6Kh6VthkEwC2bEnecjmV6p2DG7kDF9v6N/2RPOAnQbsDEmG8uLZ\\nsVzYXeapB4G6Jx17duxIJjJAdq2Vgy69eO9PHOlRTTL12MVyvgNUmvtnA54+v8fh3hB/o0dUQDIW\\nOmc1gPi+RS0Mf/jlp7m4cUZzr0OnV+IaTRQ79Ngy6BakpkHrwCAtGXQKMb07STirMo6Pe1TTmJ3B\\nlJdPt7h7PJQwZ+Mlda421M5QNJbKGXa7U7K4xtcaGzdY4/k/P/8hjr+1zd2bmxzdH9KUETRaEqha\\nG4bltfGxXC9VI9qHWrXmPA+YJyGS3zcZRDODPrVEJ5Zo+iegjG0a1OkEfTpBTyScR1UNuhCdilri\\n7G00qLPyvtaDpE1K8oJX1y2LzrlVV6+iaBW5pzudlSd8tlegGug+MsbsJXTeiIgWgWTsiGcBlymG\\nr0Jyouh8vke14ejeVVSbru2sA/E4UK150ThoaDoe1xHmWXIKKjckp6LGrkeeaD8mPnrgdCudvcY5\\nTVlHhKAYDHIwInjziQcdGHymy/qLJb17jv6dSixLFjX1ekcyn6t6NU8Ut0onRJNWh4PRhFRyNYgM\\nvp+JkKqfSLNz1pAd1tjZW+NEvy06+tBIYK5qGuyiwMw7+E4smYiRdJsqAHVYDei8hWKkyfYjorJC\\njafopcvlUpQQZO8f+l1CFhPiiHqQoF0gngbWL54xcwmPjQ4ZzzPcJMW3wxZlPH4RwRqMxx3+++c+\\nxsWLlFEAACAASURBVKWLx9yYbXJcdLnaP+H+tM9Xutd5Ldlix074v0+f4Xp2yM18i5/e/Qovlud4\\nabLDY/0D9pohZ66DC5qTusu17JDTuotD86WT6zwx6OKDYi1a0DElj2YH3My3+NnR1/m//sdXZZCT\\nxPhhl/xCT5RzfU0wCf3bx+h+h7rfFduIWgvM4dsAFCdDS5BZhSlapk6sMXV4EJ2XP1wsVxc1qnaY\\naYkdJ8RrMXXP0CSs0qeWykJdtwafWjD9YCAe16RHjrpnqYaR4JceokUtMvGls59hJfjShaabVuwv\\nevTWF+SnA5JTVoIjHweimaIeeJJjw/5nLhIuOebTFCaWOnUkY83RYZ/JXGyT795fozMoKPKYR566\\nx9GiC1OLGtRMipS8snivKQvDxtoM5xVNk3B4b4TtyUDCRB5rG7qDgvk05UPXXuR7f3eAN4pqGLHY\\nNoyfMITY49JA3VN0DgOVEu2Dj9pdXq1wRrJqVdlexwiarlBTlZc/R3PVXt8Hvv0P8wh1vQrTWWUy\\ntAlIqGVmr0IvSkzZkejGSCiYSWRWStElfx61ZMt54df3e+CcMHLaDlcXDfNLHmYpftRgZ5Z4tmSc\\nyY7QzgPVUJEde4a/HTh9FJJDQzyRdyDKA8mhCBdVgM5dQzUU+uLZuxuisSGeyPXX5TJvWXyE8vON\\n0FydgvspRRooQlv8daDeqomOLRuPHrP+L8ZCI20jA9ndxHdi6kGKT/qkL+3JeYYATSTXMS8f7IiM\\nwXcSfKd106y9BA2lEaYUmEz5QDR7a0T6t0WhXwZVh8ZBPUcrSXNSmcWnVu7zEq5pqYJLBWGxFWPy\\nAfrWPsxz4c5bu+oCgo3ww44obCNNPWjDtmvYvz+kvmCwypPGNdNIMM6lPwtBcXLUx2Y1tuuovVgY\\nb2UzcifDly/fvsq7zt3jKOrhUdwtR2zFU14td/jc4eMiTdcNh02fW/km55Mz6sjQ0RU72Zg71Qa2\\n7zipBEctfcS8SbicnZA7yyuNJAaFUa/dpQSSowKfRUwvJjSpFG11MsaUQ3wU2kGlXuGATUdM43QD\\nvg4E3UI5TcD4sOoK3qrK7q0eqhYcVjWOyIn/kCktdV9iHoNuO3ovL5MrNUunyaqvKNctnbs18bjC\\n1J4mi1pf7oi6H1EOtIR9RELPdal0VUd3Rlx9ZJ8ZCT6V7th6YVks1bbZnqHutXCWg9Bo9KgiFMLB\\nTt5IKC8oKmQAUBYxWafi/njA4nYfVMBEnrIxLCYpcaciBIXRnmH64OWrckvwikYHiiLFDsSh9KDs\\nUw0jvJGdlnJgx1pstq3oGkwZSJtANTTC6e/5FTwQdFgphsUMS3Y+plTS9TcQLVQ7ZH+ot3V1BOfE\\nubWSgqWiaBXgoUJLb06iNoFL1KEuUdTrHeK8L+KpxUKYNiB1IHjQrb99lkoASCp25aqsSQ815TYQ\\nBGJdZfoGqQvKB3p3HOVQi7Wwp2VHBeIzWfzWX3ScPWZWpIBo0QqsTg3DV6SuCOtLEU9Eh6I8YANx\\nXyywm9TAWYzJW/inUeKzE2CaJ6x7t1oESWLC0RkmiTHDFJdqWSiXux4QiNk5FKZNxVI0w0Tgaysw\\nq65cq+rX2Hm5Eo6+lePtAd00b9p+OE9Y5OLw1ohN6HKA581Szi/e30Epqq6m3MxQPfEvwRhRuxkt\\nD07yYCzt0jbCrw2T0FnDS9MdvnN8DqWCQAcjT9gu8ZUBE2QrXVhCUDy9ts9jvQPW4gVxq2RN45px\\nmXFjssm4SjmsBIs9qnvcnwwYxQseTfe5W64RaceL8x127IQ3yg3GrotRHq0Ch0WPw6LHcwdXmLuY\\nV+bbfHDwGpfMDIY9ifyzZpU4YxYN8ax98QddSGKyV4+wM7n5TSbWEU1XHP1MxcoCF9puOohLos4b\\ndN7gH7JNsfxFrSCkqDAnc6JpRTQTWEmKbssnboSXbhZiW1ANFPmGwVuJbnOJEeqtlsg8GeS22Gdf\\nrYRFy873/umA2X5P8P2eolhTLM4pTN6aYjkxoAoa9GZJZ5hjIsGJm46IbVRp0NMIKo1bRHivqCpD\\ncqwJqWdjNKPIY9CBcpwy6BZMFillE2GNR+tAyCPULCK6J4Zt9WnC5vaE9w9vUQ4MTSZ8eSU0+tWz\\nvqQamyLQ2fct1CVD65X1thUutQoPMPqgBdazc4gWbdbun0Q75wPKmFUIdyir9j66Bz7u7bOqK//A\\newmo+xH17hAVW+HMZ6n8MhqSRL623JEvvWEAdLuwn8Wke3JSLlHUHTHJS088PlLYuScZe5xVFFuB\\nYksWhKYjM75irTXxWwgMGuUIBblSdPcbqqGiWvOyK1KQnIknVnQS0VSGpjIEJ147plR07osa1p5p\\nmu2aaxsnouNpbZZDCBBkp2JPC6GDD3qoLCNM5yurlmCjVfShG2Yr3H91yZMIXXvMQkLW1aJ8y9bi\\nb49CH0WydQv+wfS9rMSEqJZhmmokJFe7gJ0jOG8VWi61xo168oDEbTe/PNr0Gp3XRIuGOpOYNW8V\\n6fcyXjuRdHmtYHT1jO5FCSSmVbmZTkPWL7i6ccK0SZg0Gcdll/3FgB++/CIf2L3NwazHKMlZNPLQ\\n/+Zr72E7nvDI+hHn0gmfOXmaaZ3ig2JWJ7wwPyd86hDx6mKbV6ZbHOZd9heySPSjkvcN3uDJ5D6H\\nPmH2ji3q9Y6kNC1zIr0n2y+ISs/i6nCl9rWzprVAEBYKoaUsVgGbixlYNHfCqy9dKzJCuPjlQ6bh\\nOd/CLW2xrxvMvCJaSNSh8uJb482DDjRaKFE9qpZFsxbTDFKCfhDibUqZPehGXtIoB9d1uJ4TyOfI\\nUJ6lbbcbmF92suVXYRWvV/cCdT9Qbzb4ylCVljq3MIvoXR+jLi2IploUq06hYkd4fsCon1PsNthB\\nyf7eiHphUSqAh6PDPnVtKJqIw+M++UmGPTXYSZsOlDVceuSQc/0JO9GY2QUpOk3aLrwt9GJKYXnM\\nd4VGqpeLYNF28u1iZgqFrmQYawpF1FpZ6AopXC2+/2bL5Yd2xG+yVmg70FAUgrfXDxq3EIt1ry7F\\nUlzsfzVNJ/pjn7EKzG5hXEAgj7oBG+F7kvO48bwjPjL4Vh2bbyvyzQeq+qAlW2G+a5heETdSvHgp\\nxWOYXfNMrkNn34sdtQs0KWx9w9P0PJNLEeUokJxooflqgYq7d9rB79yiTmP0WSTPay6iraChuVyQ\\nDQpGcU7z2HlZvKJIMqMBfEDf3sNOKknQC0FMzkA6+9iuromeVZi8QdeSAR3NKkwhGD4eCTBqHDr/\\nUxQ8opSCxMoAoighL2BtKIVh3KBciusl0uFUASxCH4yW/iNegsQTK0WlrMQhD5lcq7omhIDtdugM\\nY5qu3LSmq4mBjq1RKlA7Q+U8dWKwvZKysFzbPqYfF6zFOYdFj1+4+Fn+4cHH0SrwZHaf+9GIrctT\\nvnh4nd3uhF5U8cOXX+SHut9jJxpz1Ax4bb7BrE6Yu5i1OOels212u5PV+f/g5ov8+hsf5HjcZX2w\\n4LjsMBwsuG4n/Pn/4T9hlHmUE+HVcoVXIeAyyb8tBwZTDIkP5lJIc9vKo0VVagovtre5eND7WLA+\\n5dsQiBB42M6V7Y0lZAk+jtBFhZouCG2IQnJYoAcx9UAYGcvilJyFVZJXPJMoRFOI9bSuPNFCHuxo\\nbsiQ4XqxnVCuRdRd8f6ohoKZqpZdE5TGa3CNoul4TKmothtU7LCxo15YPvXUt/jNF56haTRX1k7Z\\ns31mjzqa+11C12GiQHh6yjs29vi20zivqacJOIVvFNiAPrU0CuZW1JlXrx1w5+w8dqKoRoEwF8Ox\\nR3pH/Odf+3HMUKwDorkU8zcTbEIE+VYAxFZaHDQlH9kloqCN5mKzbWeyYDorHb9yrIgLwIP0qYd9\\nxFaKsZH4QNXJoChRSSz0ytjiY7MSx9lJgyk8LtZEucMPOui6ERjDe2n2VMu2aX1vdLcj8F+k8WlE\\nviGaD2dbbBxpZpoOVEONrgP5VpvfmwTsWHHu2T32vrmLcgo/EN760TOa/i3ZOaLh6BmN3yqZ+ESg\\nu5NWiNcW8vRYwk1cO+tSuwVh3CGaS5SjnWiy6zn9tOT5f/o0o2ENjNBlIyyjyVyg18ZhpmK/HNJY\\n2Efey/8LQQazIaCqGjN2RK2jZ4gtoS+RiKoqhbFjHsCz/7rjbVHoSROWKe9KKTHy64rbpJrMRUVW\\nNqgmJrSxeHVPE089nbMak9f4pb1BVUFRyHYpimSLVNdQlgTnSA97NEVMNYjgsTnDrGBSJDy+ccj+\\nok9eR2wM5ljt8R3FU6M9rHL4oPizuy/y177913lqc597syEnoy7fGl/ko+uvMq9i+sOSd3dvC5sm\\nJIxdF4diaAveKNfwKA7zHuvpgsZr9ss+l7NTDuoBZ4tMVI7AtE55Jn2Dy1Fv5UO9HKyGSKNrh0sj\\nmsysEq8AsXZoPNGsJmhLpMRwy1R+lerjrRR5UaRqfKbRqWkl7Q+3IoQsbn3EDUQaU1TSnRuFPa0w\\nhcFlZjVzobV1TsaB+EAyNH3S0miPFq07qSxKfrsnkFPZ0Lnr6Q96FGsKl0F1tUTrgJ9Z4rWCapyA\\nVzQDBybgRoH1nYnYB6vA5fN7/B+f/zBqq4RGMSlTTs56nN884/7NPm5Ys7k2ZZTmnJYdaieq1Khb\\n404SUf3ONL7joTDkpHQGBbMyaVlcLUw2MVzsnPELG1/gn+tnVt4+0PoMPRgNsTQ3fKAYb7/XsNrx\\nLGGHFWxjpHsXmOJBWHmx/nDZVKujkWFsaIOxqcVyl6UKVilQqQiojKLuRcSTGntWiCAIpKuNIkLZ\\nGhQ6J++tUagolYFlVbf0aMv0KrjUE481dU+aAuWkYLtEaLbFxVqupQn4S47j3zsHS7fLWmHPNNWm\\nw5SGYl1RbkiYN64lfThFtRZIj2SuYycS3alrKehNL+Cmls5Uhr91Xzr7YVbwC1d/l/+y+mm0Cy0H\\n/k3X3pg2C1uj5vmDPOwQUItCaJRKtbOFlme/FHymYtIXjMKvdcW1Vylc960ppt4WhX6ZOxmNCxEQ\\nzOa4NtDXTIRbGtqJ0nKwNDtvsHPN1ldnckEjMfgKRZueoUVyXG8PCDsD7MEUxlP02RxbNJSjIQCL\\nyhJHri3ysm3q2YomaIoq5s5ixHf3zuFe6fHp8sP0PnjE1z/3JL33HPPqYpuhLfBBs9ObMrILLsXH\\n3K42eL3a5Eaxxfu6r/P9Wy/z35R/nkh7LnbOyJ1lWif4loEDkMU1690FszJm2M9xKFzw9N/wpEf1\\naghDEB6yLhqqYW/VBXorA+xgDbppYxSNQvsHPHJREZuWqSEPUDkUHDwoUTc+zKPczGi6RmCaJqDP\\nDNVIrnE0XqoAw4qBE5SYiVUjxe5XnHjYOBnk6Xkuz4aSKjjfTQgmoXe7xB7N6N2JSSaGyeVIungA\\nE6hmMapq999p+zIvDJNpB+5kjF6Eg/ka/mOetS+knL7bcTjtEtkGF2RxsHHDKM0pGosLmrKwXNo6\\n5fLuHT5/+rRU8VGbZlVIdGFZWIrCYmyg2JYCXO/UGDxbJsG8lpGctIyY1pBtaZZWDYM0O0a6SV2p\\nFTRhSvXHO/92UXCxaofb8vUma38+C2/Zs/zf5FBZKlnO88VKzQpI8WqNu1TdmhC2KU/FhqHuG4bf\\nnAjd2cuvULe7AhDsP4oI/S6rlKrGSSEcZgQNdiq7QlPKTjBEcr60s4roNKJzT7H+Yk00h3vfD7tf\\ncex9WARNSyO8uiO6hJC1z0UsmH15rsGOCvT9PrqGasQqh7rugi4UupBzml0ST/xiM7CoLY/YQ4av\\nN8THuSxmbcHGOcHj+115j5cLofdt8/KmaxuCzOSWorHEQtRSVZVYoAjCoTGLP0XQTbmZ4VJN73Qh\\ngqdE1IMus6hhT7Y3GkIkopt8U/wemr7D2xGb31rgU4NPetB2esp5fBIxuZZiykBHKayNmF8bUvc0\\nykN9kDHfhso6jk77KynmdJ5ireORzWO+/sI10nsW3/ec+5JDfXWI2obD7SH7g/HKs34nnfJYto/F\\n8Ynuy/zK0cd4unOPqc849l1+cucbuKD42uwa20nBup1zWPVwQbEVz3hs7ZD7iwFPbhzwkdFNYjy/\\nNj1H73aOmZRUO13Z8hauVcE6orkTG+Kh5M4Ga/CpmEqpJqC17ARM4dotYdsdJho8RIVrmSxtEakf\\nLnwzuRLTdBRrr9T4VBGSGJcI1m62MuxZiY9FTEWAahRozlXobsWdbo/+TSlWdmQIj5wnymX4WA4U\\n8wuKeAKQkPTF8c+3JmB6HBGsYNl2HK26Zn0aE3SgulyhX8uwU8X48cD5P3A8+k9qfGyYXo2Jzzuc\\n8WJZYD1boxmjJOf92y/yG6+/n0tbp1jtKF3E40/dAeDVvS2IQKcN9dziAiSdmuJChT6zuJ2K3e0x\\nG3bOf3X0Hrq3oXPoWGxrXDtk/mOUU9MKdNrIS5fyQF3sWSWCefPARiK0sw5dSuHxFtBB4gkf8hEi\\n6TrDIscvFpjRcMV9V0tIIQT0vCA2iun1HvNd8ZFXfovB514R6qRpPWB0q45tZ03EVuiZswXF47u4\\nVGOnDb3bkuegPKRHAuUuvWEADt8vC2OTwd2PRWx+27DztQqzaLDjDvWmIxQKXWhchhjuWc/W9oTD\\n/SHlboNOG5xTnL1Xdh3JXdsKNhXpsXx20w2SDe0U5WagWW/o2JpfvvlTZK+dohaFOGIqhZ7kD1g2\\nQaCoZdOm2gCWYFp66ZK8oBTEtF3/chgN1AGzqPBJJB5Ab3Gz9rYo9L5lwtSbPUxeE7KEpmNpugIp\\n2JMFzSCh6mmywxoXW2ZXgNgzP2/oHKYrjLLutgHErU/O8lA+UK93mF0wIvcvAC3FsMhjfKMITRtm\\nrKUDfOfgHrdeuI5L4No/uinbqkGP+LTH8Q9qHusfcDcfsWmnfGz0IgNT8Jg9pUbx19f/kP/wxb/K\\n/sGQ0Gh+5gNf4gf732U0XPBSeY6DasAgKhnZBe/uvMHj6X2OhgPqYEh0zTviiH/rcz/B8AMx69+L\\nKNcti03N8DWIfSBksWRctlBWMArXsW3HLl2fasUqTTdqOwEIStFk8oKESkRoLpbvf9g8+mUUW7Fu\\nsHOPG6bUmYSF+8jS9ZI74GNITuR+zS96ht2cg92YxSLBlFCOoOkF8ArtVKujCDQdRd1VlMOIfLul\\nuVUPOt5oalZRdy1JCpfChx+7yct/+CRVHx79e6+KN/raQHaGYZ3t3owbe1sMB3O2dsd0bM2Hhq/T\\nMwU/dul5/pc/+H6ye4a7FWQfP+Q9m/foXSq5ebrBbJGgYk+S1VxeP8WvK2a7MY0zJMbx5/rf4d/+\\n9C+QbimiXPJpi03h9qsWXVCNIsQBn4hLomrk3FTzphdbCy5NkHNS4YEVhCnaLr79wD+JQg8CK+j1\\nNVG21g0qS6UbD0Ew5G4MNsIcT7FbGSDd9PiqYbC9ARPxg8dGK2tjvMccjOUUTyeQpRQbElxvSr+C\\nKaOFmOAtXR6X1taPv/8We79xBW/hyj8/EEGSEesTn0Dcr2Df0mSBfFfcVC+cO+WJ0QF3swW3Pn+F\\nwU0xGNv7mKe/O2VmM/R+gsqDsKSyQLXTZmRU0jSh4KPbN/gXv/b99N/rGdyYU/djik1L73UwZ5Ew\\nlOpG8Ph26BqUEvJI9abOXD1Qy4ZUePRBa3RRtbsAMLVQLZeW7f+6421R6JtM4w1UI0taNvh+Bx+L\\nZSe9iKbTb/nTYArH8OWK+W6fedKaPWVCu5KtfxuSPG9tTGPIKil2b2ZuVBbsqaawKXiF6df4aev1\\nPqj52PVXqYNEivVui3IvVDX/81d/kx/5O78EqmDTztiOJwxNzkY0Y9dMWDeGMnh+4qt/g/QzfexF\\n6TD+t+KjfPudF/mJnW+SqoZEN2zaGS5oDpsBF+zpytTMB83Ml6iZobvnZfeRKZoelGuGtJeIr49b\\nqvuWW/fW4M0Lu0S7IKEZA4E8lrhfk4oniZ1U2KFkei59Zh7qfe1Il1mMZEhWjawYmGVLQ7K4/XvB\\n5oHuCw2LcwmTrEYZUSyGSIaQ1ZZAI7oUhgVe4XxodzPCofdWFI6qEbGLnSvqbiCdCb5drgX0kzMO\\nix5NqhjdbPUDec6vPfeb/NDf/kVcHDDasz6akUYC2YziBU+ndwH4u7/9F9n9RmB8Xc6x+p0tPvv+\\nAY9f3qOXVBSVpdeZUzvDuEw53xvTjSoqb4i0Z88NUJUi25dwC5comu6bCnIQ7N1lEKz4QwgtVrr+\\nlQeQhrorDpy6lq97K/CPKRAaaUs/fatd37/R0VoVBK2F/ADSfVolHHgbiaoTIVt0bo0p1tdluF6C\\nG2aoToxPLdXQtg60ivikeKAclR9eWaG4RJOcyQA2ngbqriI5cygv9hYn7wyU4yGkiv4bTuIC85Jf\\n//1f56N//xdxSSCJG2bbDaoUN1CTNTyzcZduVPLF33k3g5uByTV5dja/ophcHxEuVrieQ9eGZkOG\\n+WZicKOGkDgpPCZwtxgRjwPZgQgFXSbUZte1RJkYx4d5Dt2MZpBi8nb+AEIDb1zb3RvUMiGvhStV\\nEDsXVVSiK6Clsqq39s6+LeiVdUcKsLcybGyGCS5tKXYdTb4RibmQVisTr8Ebnuy+IT6VrW3dk/Br\\nl7b+KLQdjpIiWaxFLLbE1tTH8lJUmw6VOkgcbh6JrLzf0OmXjOuUHx99k+QkcPqUajn5CR//lV9m\\n67kJWaeiDoaFSxjonKvRKeutAiNRmvQzfT71c59jcBOu/693CCZwlHexyvFGuc7F+ISOrliPZozM\\ngvPRKVY1jPSCZ7Pb/Dsv/yWiuWa+q5ldjCXGrhQecLWW4hND1bfUXekIT5/UHHygQ9U3q5eiGkTk\\nmyIsKgfi2+6sFNmqq5lf6oimIBLudZM+5EKfyT2Q661YbEY0Hfk7XAKLbUU1kg49ngi3vveGprjX\\nJZzG+CjINrkjDBri5UA5tOpLYdjUg7DyQvIR+MQTbMDFoaVrCizkEygXlg+u3yI79hy9I5KZjjH8\\nwD/4Jba/eILPPK4dilvjOJeN2YpnrBtJ/dr8ZuBH/7PfIzsMXP5nbzB5zBFKTWwcZ3lKLysxOpDF\\nNZEW/3uPomdL3jm4xy9/66fQtfjyzHf1aiDrUoGlJGtABo6dzQX+ek5+rcLFAVWzwuGrkYS4NF2/\\nio8MUZBh9EjeHZfIgt9kD/W2yj3opy12bMA7VJaJlfByAUjsSjxF3aCKis5+TWdP7DZcGtEMEhbn\\nEhZbkTjU1vL1YKNVhoQbdomK1oiv5cIvxZLCNoJyKPCXnSme2DyQd/YJI0QO4Nl/+IvsfK2gGXix\\ng/ayiGbDgjStuZSe0DcF3duBv/jLvwsKLv4/Rxy/11NtOjqDAjPXAvOotqlIAiryqyLfX5/zxc++\\nk86Rp9i0LC72SPdz4qmnSQ1+qfPZHOH7KeVGwuTxAWfv36be6ELd4DsJzXqXaqcrw9cgHl60nl0h\\ni3G7a8IknC1Q8xzff2vJI2+Ljr4cqdadT5HEhmLT0iSKpVd53RM8Nso9umoITjG9JAMZOwtUg5Y/\\nvAymSaBYV6vsxygPlCNFsS4PnreCZZqpwTsZdKHADxtM7MjnMS8c7vDzR3+Fp3/2JfbmA177m4+S\\nnAo3+fi/qKGyjJuMy8kJc5/QUY4tE6HR/MKdTzC9Dl/68cfYKm7w4n97ge53I/Zm21x47JR/mb+b\\nrxxe5YnRAe/o3eWV2Q7TLOXD2U0S5ThnMp4a7XHjyibj7YjOjZjsUP6NdV+x2LHCMkiFaeA2asI5\\nR1EZZlcisr1kNYCrWy+tKBe/9njicbFhfkGRV0ZeHCA5EQzyYR71wBMiMJW8iMWGWi2yIDinWQhl\\n0JTyMhebgWihUTU0/SDc40oRai2Ogf0GVZgVTNN0A77vUJWW7sor8SCvID2WAWWxIQtONFeouwn/\\n7ORZen9pTDlLufvvvYvsMJAeB174pR56rDmcd1nr5NTOMLI5j6d7APynz3+K8inFl37kOjvc4sX/\\neof+1w3V0LD1nhnP371CPtU0OxX9tQUnZz0ar3l87YD1eMFfGH6T/zd5knzYMO9qzKzlahtZnOpK\\n0fQETiD2PLF1wFY6o/IRd+Yjbt7fxJcGnMIOSiIF1cISjxOSM1gkUOw01D2N63hIPWEa0XQf/jTW\\ndS11J8JUnvRkTOh3JWhEa/ygg+slREcz1HgK1hLmC6aXz4GCZOwo1yym8MRnDcUwbim2QvVtRimm\\ndFQ7XZqOqKgB1Fzed1NCeuYISlEOBQ6KZ4HoBry8/wTjj0gK1L2/cJlk7MmOAjd+2hCdahobgQ1Q\\nilXGpcEpWnn+9xsfYPEYfOGn3sk1e8zNv5XQ+bqhWgtcefKUe783JDv2TC9pyo1AdldTrWncbkkU\\nN3zk/Ot85uZ7KIa6TTsz2HmMnQmpIGSWeriOt5poWlKONPNz8m+fXMlIj1OiPKzmM3VvQOdu6yFU\\nN1CUlJfWqEYR5dM9+rcr0lf2aXpvjXXztujoXSoCluklzfx8TN2R5CAXy+BjadSEUjSDlIOPrFGO\\nRL1Y91XrdCmftXR3BFZfr/qyWPhEsN566Fddf1CBkMrLpSJRqYagmJ1lfHD3Npmpabzmysdu8ed+\\n9ot84FPf4droGO8VN2abaOWZ+pSbzRBH4DP5iNJLCs3sXedQ1vL4z73aBnN7PpKW/Pu7nyeJGsZ1\\nyu1inUjL33vmM3bb83hX5w676xNUbqj7wg12iVDlFruaui9YnY8AD8PBgvWtCWyVlOsS11f3pKvz\\nVgRUdaba2LcW5toN1L0g12TAH1PhPYzDZwHfb8i3PcXmcufVQiytfN2lgq+Wo4j7H0mp1n0bbygd\\n6oph0iiJbFt6iyDpWT71EHlCpyHuV6jY4+NAsC10FMnnrPJo5wq1UzBfJPhFxPRdJeqvHLL45ARl\\nZNg5HncomohpkXBrsc7CJ/z9ez9M3Uh4ef6O84Sm4Ymfu0HVl+v/727/vgxmNVAYFvMUpQJKYDnI\\nFwAAIABJREFUBRZNTM+UFMFyYTDB9BpUrXA9MXHzXUc0qqg3Ghg0AtOYwDAueG/vDT4+eoF3jO6z\\nvTGhvzHHDkq6WYW1wqv0NqygG6KAOl+geg02qwmpW/nWP8yjySKKdcP4Wkx9bUcYb7El2IhmkOJj\\nLZ1562cz+8hVqqGi7ijqrll5Dy1TlIJW4o3kxfq4WhNbcpdIU1cOlQgjY9kBLtOY6t5y9iRUx3xb\\nsmLtqWZ+IbD/iYaT93hUoYXlNI6EYVNpziYdFk3Mb7z+fupGYODZ0xtQNzzyHx3gErm2z4zuMH5P\\nJSSCCuxYGpZgAqHSJEmDUQHf8VQDRffAoXygGspJznct5UZKkxnZmWipb9UgUG76NhpQNEFyfkr0\\nBm2CHo0jhEB8kjO5LHYY83OWMOhiT9+akdHboqNfGui7NHCaauIJK3xRXk65EGUD1VMd8q3WFzo8\\n2MpFhXTuKzOlNpbOxcJQcG1xc0kQTwqvCOcKOllFsYjxpcEmDXUREeYRdr3gxbNtPrpzk8fOH/DO\\n7DaHzYAXJrsczHsMuzmdqOaNcgMfFI8le7xQxbxUnuMH1l7mm0dP88aPeQaPXWGxI5mof/P7PkcR\\nGnZMzfvWb/Otk4vszQf8xIVvMzLzlV2yRvHB7HXunx/xtewKf3TrAv4glhd5WDPdUnRejXF9iCeQ\\nK6idIbUNg37OeFfB1Eo2rBLhiHPQdCWaz7f2B7pixbip+2HV3T/Mw6QOpwOzJMLMtRRhBaiALjU+\\nkaJ9/E5DNfSr4GtipGhVBlWDmWtcLO6OqlKExKPaQA6AKHEkSU1dRPiew/cD3lgJBu97orkmWgjk\\n4c5i1i+doQdwaXBK5SP290aoaYRPZKE/GvcIAc7KjK+Mr/H84S5XN044eK3Pa5/SDJ94lMW5QDyB\\niz98iyvRglGS488XqNOYsJ/QvS6D3Fg71qM5Z77DKF7Q7RZUcUOZW1CBQb9gtz9F7QZeev0cdBxq\\nFuGCoqNLNqIZ7+u+znw94eZ0g0PEAC+xNWViqXutj8uywfGKKJb5Q7JWUHcf/muuGgmtrvuKg/d3\\nWX/JEp8UNL2YfDsmOalBg+pmhCSmGMn8S/nWalwpsqOa+GCGSwZixTEtcKNO+/lBFt5Gmpl8U4QW\\n0yvyGcmpFkiuD/FYYReOuivFfH65bdyGFZEJmNsx8WSpOgZ1KtejmVlu7G3RFBHXLx9wdqPPnR9S\\nDM/vkO9uo0vYfOqIR9IDVOyZn1PYOSSnMH3Ey65JQy8t2cv72GFJsWk4iSPSI7kXZ4/EFJuK2WXL\\n1rcafKKx+xPqbl+anJZY4GOF84rsvqjWo3mzgqn9xgB19wA1L1h7uWZ2ISIqAs0gpR68tfD3t0Wh\\n160JU70mw6fZpic+MjQdwWKV09hZa+CfCM3MTsVKNN8RrDZMZWX3rZptKV/2HRlsedvKx9uOOZ4o\\nXJmyOLcM21bUuSW03tP1acqJCfzLxTv4gUs3+OLJdWLdUDnDZJGST1Iu9MZ0tGD1r5S7PNd0eWm2\\nwyfWX+Q3/oO/x+/On+RXNn4A7RU/987P8zPDF/lCscbvjp/menZIOYq4lw95bnyVzY0JMY6BTjFK\\nc+I6vDt7g4OqzwvJDtVIo6cRg7U5/bTkjl0jfTmVhWwaUWQxSgWJNnMaTKAZuJVFbTBQrkM8EbjA\\nFMLJlvxWwcLRD7elV5WoRuNOTeUVar3Az2J0JkXITSxmbnCZ4M2h26BnEdFcUW02qMQRCi0ikSSg\\nciNzFCXdlM8Et9eRR6lAEjmmXgZlPmpfaqeIZrq1FRCeOsowfnGD+NqUbx1dln9soTG54Lx5T5MM\\napzT3B0Pubm/ST1OGKclP/xzf8jv7z/K/mQHHwUuf+QWf+PCF/iPb/0E37p9kV6voLCO6ixhctRl\\nmBVkpuaj3Zf4an6do6JHPy3JjcNauQ4+KK72j9mwc2pvuPnaDiEK7C8G1KOIM9dl4ROmjWxD+2nJ\\nJE9Xtshuo6KepGKTW2pCGVNHAd2rsVmF7ZYP9b4CRLnDzgOLXYWxsPfBmK1vaxbbhiZVdO57VFET\\nuhk+s3QOGkxpSCaOs0fsyjtedB9iU4IX4y7xe1mqx1hlpfZvN+gqohqJB5By4j1vaqGh2rnHRxrz\\nqmbyqCe+ka1mIHYW2qEtbaMH0ZkkenXGipvFLld+8j6ToxGLCxkuCay975gPbN3m73z5k3ReTig3\\nPC4Ti45sTzO/KkHgH9x6g+cOrlCfJZhOoKK1sYgMyThw+m4RZO2llt0vyYA1OQ3k22LmpxoJ2/GR\\nYnbOMHijIZqWK658fDgXymVV07lxim5GKBdoutGfMnpla6uqayO4buypB2JAhVYrmMXO2pteKxnQ\\nuQfbtmIzrIY/USsLl59TbRB1aIM7FKoxeBvEp9rJQ6XnhviOJcpb97puwJ/00DPF79x7Dz/57HN8\\nuHeD3zp6Hwdxj3Szpm9L1iPpxG/mW5zV0o2kqkITuJ7s8+kP/SN+a/pu9usBvzW7wq/f/RDnu2Nc\\n24o2XpMa2fq9N/GAoQ5OvHfMgj/Tv8n0SsoLJzscnfU42+tz4YkxH3/8Zb67ucv+3ojo0NLMLHMv\\nrA15OVpp+HqDyjWmkC3v9KrCTmT3o1xrnJXLnKIaPmTsJkCYR1SlQXcaskwcHl1l0LFDdRxOg85l\\nYUKDHzQ0RDJ4VaDWK2orSUCqES8cFyNeRNajY4cxHh8U41kKTlwEVSNsjeQUsgPRDIgthCI9hmQS\\nmBwO6D97ymMbh/zRnQu4KiMfOEynYb2TY7Tn/tmAphIR1KKMmTQZSdTwiT/7Lb55eJHXj9f576of\\n5M6LO4RuwzwotParnYZWgb+2/QVS5Rg3HRZ1TABS22B0oKgjBlnB8yfn+OT57/JXLzzHc4NrPH9y\\njrvjIV/uPcLF9JTSR8zqhMNZFwWsdXJO5h1CAKUDxaUKPY4wC9GINH2Pn1lyr+j0Hn6hJwSGNxbE\\ns5STpyKChdkFQ7SAKAS81fh+ijmeYmYLbBrRdDTR3BFPIuquYno5Yf00b/OcW8JDSwmuRhHxxBFN\\nHL27miYRMka5/iAIvne/IR43RNOKpi9YdXYI0biks9/j/scc9Bqi+zEqyHNT7dZkazk2ckxvD4hP\\nDKYEOzEcTrs0ZzHJYzPCnS7j72zw6RvrbDyvqDstDdyG1iUTaBQ/8Oir+KCpmrYJCSJSKzbFu6lY\\nVwy/B5NnKwbvmfDG7oDBdzbZ/fKMptujXGth5yDeXdVAcXY9wtsB2f0CXTqq7S56mGLm4v+FF+Qi\\nPsyZPfLW8oDfHoW+lX+bEvSRJk/EblaK8lLhJ/iunbeYfCTFOJo/oKd5K3ANQN1tueQecGKjgQdV\\nK0wrOFFOCYXNSdhIiAJNV+FtkEUGKNcD2bkZ39d/BRc0H197kW5U8rnPPsNXKssflI8yHMzZ7Cx4\\ndvMm69Gc7WjKdWuJ1RGfnj/FR7sv8Y+Pn+Ubsyt8bOsVxi7DB4UPikd6R2gVeCa5Qx0UWmkWoaKr\\nDEWIuBCd8oHB6/SjgoNhn+devcqLd3eZb8fUTqNjh+sYVG5WIjxVGCneQIPQO0U+HWgy2SEp1w67\\nW4hMVxCPH+7IxnfaIaBThOOEKmkIQREKg2tavnvs8dZDKTxAZQJhvYLcoDoNocWrA3LvvEG6Pa8E\\nfjMBryVxyDURqhSb4WAE319SbF3cGoQ52S5PLmtmVxs+eeEmHsWHr77GK6Mtit/eoVyLOJ11qNag\\nuFDT3VgQrzWsdXJ+ZO3blP79fHXvMu/Zvscf3HiUO4s14nNzXCPZYEoHbL8keM0To30GquQPFo+x\\n8DEdWzEpU1wrV04iR9VEOK/48uk1rnaPyZ2lH5dUTcRLZ9tMe9LpTMqUpjE4p4kjR10bvDMoE8iG\\nBbnLYN7u4HTLTCoNi5A81PsKrSVJ7ejeagi6z+SKFMx45rFzJ3OXzQx/rku0cLh0ialHdPYb8q1I\\nimInFnO9SOEH2UowRBAihq6ERGAqMJUwjFwMbdQzLtbUu51WLCZY//x8zPiaZuPqEZFxVBuGk6M+\\n278bM3rVkJxl5JsR+VNQrTvJcOh5ntnZ4wW9w+L1AeZcjnmhi50qpleF8bY0nFvCxWqjZN3O+crh\\nVWpnBC4qWv67fsB2snPofiPj6GkDjehGiq2U7p7DtV5AUQ7d/VrYcr1WNR4CPjEU6xY707LT6Se4\\nzKz+f+du8Zbu19uj0Fu5MHbeYsfFA8l3VIg83MVhFS4h/tatEnAOUZvdGCJwrfeFamQBMZV8T9NR\\nK+kztJj+VFMnYkDV9BzNALCeta0piyKmk1Zc6s55dvMmd+s1Pnf8BH1b8iPrf8StP7POzS9fJlwu\\nOH1tjcFTJU9ld/lgcpdX6jUiDOta8670Nv/4+Fl6pqQOhm+ML/Hs2k16piBuJZtTl5EqR6IyPJ7X\\nak1HNYx0xZmPuRof4oOWZKteSb7X4/XpjpxIqwszpQi+fCSdBKq9VpWS+LMgfhyqkYWsGso11i0N\\nOGhWCtKHdsQepQPMZYtZVxJEDqwKMomT4l5qiYbTDh0FXFD4PFp17phAaJTU98SjKnFudIBzUvRx\\nCho5rxALvbIcKYpNmUU0WxVUmnitYNjLecfomDpovrp3mWFW8KlL3+JXP/ER1v9pl+lFQ+deoLgA\\n1zeOudo95l4+5Hp0wi/v/n/8rebH+MLNR+h0C5rGkB9nnL9yTD8u0e1+en/WYyuesecGLHzMq/Mt\\nUlOj08C8jslrS9mK9LT2HOcdjvMOeWVpvEYBeSUJVbFxnC4yvFckSU3jNMNezniWUZcRwSp01kjA\\nu5frIP5BgdD8CXAutCYkGlXWJGc18UgTz/wq3rNcT/CxGO5lXhbd5MzhraLzxhTtemIZnogoUrmA\\njw26csSnJXZmhHJZOZSL0EB8UpAeWfIdMRwrRga3Y6j6iny3pT6uNdhOQa9TkkQNe8dDsk7JJ9/5\\nPJ/W7+D6r0Ldi4hyTz0KmEGNPVfjFjE/uvkd/vL2V/nb6pPkL4yo+7LrTY8Vs+sNpPKsAnBm6XZL\\nbudrNF4zO8vEWycOrTeXiPRUG5S0+XyFnVmartSququJJ47+ndZPZ+7bfAhH77QSmnlX9AWmCpjK\\ny/VwAZM7yaC1Bl38KUqY0rW8iN5I4bET3UI0bbHPAa1QbWScnQEo8p1AvuPp3dIiEW87/eVWSLi2\\nYKdLdWFrKaqW8Iyns5azOM1AK9L70f9P3Xs865Ze532/N+305ZNvDt2NRgONSIEyIIJJglSSLTpI\\nZbtcdlm2BvbME8/tuWf2X2AXB3Y5SRblkmxKpEwKKhIgCKAbaKBv9+2b78nnizu+wYN3n3PJgaua\\nrsuq5h6dOufGb++93rWe9QTq2y2rn26RnQrm7zbUreGD7AAfrvHXd37KL2SP+GF9m79347v8V+E2\\n8nnG+Atn/JXdh3w7e86WStlXGywCIySHdkLlEl5UExLp2E43/GR9nZ2052WbFSNZc1vHIn/uGrYU\\nPLJDDlTkAQ9ESyEb7hVnLHdyfnReIBqJ6GJHG/Rl7qVAu9jBB82VgCiogBs5VKv6gv5KUq02IPrw\\njj8VT/o6LhcN6uLpDX5louVviN25QOCBEFczyJUiJBK9V6K3LN3zQWTVuL7Yy55xYwKiiXAUSHwq\\nXjF0VEBuNQyKhuXhCNsKxg9h+QaM3k9I54GzX8g5rQyDpOVBvcNfvfkhvzr+gPerW/wnn/9X/K/p\\ndxg/cTz/FcH9e0f8jd2f8J3Bzzh0Az5nMs58xUBHGGr9dEzIPGpoeXk05Szv0NqTaItWnl8cfMzS\\nZyxswVZS8mC5e5VBoPrQcB8EoyQu3jovKft8Wq08dW1oG41zEt9JhABnFdvTNYOkxXnBwka/pNBH\\nC6I9cm2uYu24LE6v+bIDg9QSVVlGzyUulb16VZAsOppZwuAwQg2qduhVw/wLE06+MWP7JxsY9hnH\\ngehn1Nho2ld1iHmJSgzCOdTIxOCSzkX9xXVHeqoAwfRBx/wtw+wngtHTlpe/lNKONevrsN5kfOv+\\n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\\nrXbIbc3d/Stcp70/qwfciub0dMWeWbLyWbebqfnp/mO+M77HzHiqRUoSt7z11lM+aO/Re2SpJ4G2\\nJ9169Cjl/FZ3kHXO4mv1Ehr8Xk29fvWveXK6pZ/0qKYR0UWPN/+7p6y/fkS8qiT7NYlkbzRrsZuW\\nEEf4PObynZRmoLj9GwshNjat5KUmkZjmArhJTtCKZpgQrVrSC4+uWkys2f8Xmouva8pdJQKO12H0\\nCWwPJXQnudJianuw5tnlmC8fnnFZ5DTecCtf0k8qijYiMeJajT/JqCaeuFJUu77zJgg+wm8stTWo\\nRA5dpQLOCdW0KSJCpbmYJ2ACxZ1WDH+NhMiMspLEtAyjEh0Hzos+w7RknBU8fJ7hMoUKmtUDT9CG\\n+/UOyfmW5VtDih3xEO29X3H5XkK0DkQb2cMB6K0gvaupZOl+ns8XoqNf31Y34QCr+5pqYNh/v2T4\\nxGGqQLGrqKaK9CKQnQfipagx4jOLqhWbW5p6oG7IdteES+3kuhsUlLuiLrHbTnecisRPryx0sXPz\\ny75cF5cx2EDy+pL+sKBylr9w631GpqD2llUjp8h+vOIgEhnkx8s9Xhtf8vF8jzdGFwBkpua15JyT\\neszYiEHjeTNhzyz5avyCK9dHK48LmqEuOW+HfFwd8qPyCKMCX4rOqYLHKH1DunTB4xF2fxkC7+Qn\\nbFYpLheXXtMYtndblq8pXJeTqxuJDMxPNP2nHYqgli5btyJntGv1ytUZ9UQYNq7vKHelsE9/IEhl\\n3SiakaceBaKlIplJhCCtZnXap62NmGI6fIHLuvFUZze//nfb95QHDl2/VBQpB21l0cMGnwZR5wB2\\nqQkayjsNalizrWJ+9ehHjKMt2zZmWWd4FD1TM4oKrHacXg1JhhWbWUZ/sqVuLUYH9ntrLrc9cltj\\ntWNW5kzigjd758zq/IZZv5+tKNuI4/WIq42Azr48PiNSjoEpiFTLjtmwZ5fMXc5JPeZ7m3u8PTnD\\ntQYVO7QKXBY5yU5B8ZWCZtIKxqFWNAOPPYuIX1jMVgtcq1A3RSeU5g+FXjl/d4CLhZ64fX1Mc2eH\\n/vePiR9doJcFIU/Es3JWYConCONBzOhRiy1ge79Hs9vH7Y8hBJqBYAH0tsYsS5QPrO/EXHwtEYaO\\nVnijyM4b0nMli08N6YW4Z6+DdNYPPC73uNbwrfsPya0snqrW0rMVo7gksw1FG6GeZrRZkL3ejlBR\\n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\\n8rJoywAAAABJRU5ErkJggg==\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84b1d16290>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAAAXoAAACRCAYAAADNVHNlAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvWm0ZdtV3/eba63dnPZ21ddrpddKeuJJFiKmie1AQLQC\\nHJOEmNYkGJsPjJAQTJx48AE3GIwzTIhjp3EgsTMwxgxiDChAhEFC2IAQoPY1elX16lV7u9PuZjX5\\nMPc5t54Gkkp6V6ikcecYd1Sds/fZZ+291prNf/7nPJJS4kRO5ERO5EQ+e8V8ugdwIidyIidyIp9a\\nOVH0J3IiJ3Iin+VyouhP5ERO5EQ+y+VE0Z/IiZzIiXyWy4miP5ETOZET+SyXE0V/IidyIifyWS4n\\niv4TEBF5u4h8x6d7HB8pIpJE5JFP9zg+U+VkXj975WRuVe5pRS8iL4jIUkRmIrIvIr8gIvd/xDlv\\nEpF/1R0/EJH3icgPichWd/xbRSR015iIyB+KyNd/eu7oT15E5BtE5J0ishCRt3+6xwMn83ocIiI/\\nIiLPiMhURD4gIt/86R4TnMztcYiI/LCIXOnm9qqI/JiIZK/kmve0ou/kq1NKQ+A8cAP4B6sDIvL5\\nwNuBdwBPpJQ2gbcAHvicO67xW901NoEfB/6piOz8yQz/0y57wN8H/vaneyAfISfz+spkDnw1sAF8\\nC/A/dM/tXpCTuX1l8r8Br00pjYA3A18KvKKo5DNB0QOQUqqAnwFec8fbPwz87ymlv5VSutGddzml\\n9DdSSm//Y64RgZ8CCuDVH+27ROStIvL7nTfxnIi85Y7DD4rIOzpr+zYROXXH5/65iFwXkUMR+Tci\\n8to7jv0TEfkfOw9nKiK/LSKvvuN4EpG/3HlpB925csfxbxeR93de0C+LyIN3+dx+JaX008BLd3P+\\nn7SczOsnPa9/I6X0gZRSTCn9NvAbwJ++m8/+ScnJ3H7Sc/uBlNLkjrcicP1uPvvR5DNG0YtIH/iP\\ngXd1rwfowv4Xn8A1LPBtwCHwwY9yzpuBnwT+a9Sb+PeBF+445Ru7a5wBcuC/uuPYLwKPdsd+D/i/\\nPuLy/wnwg8AW8CzwQx9x/KuAzwVeD3wD8GXdmN4K/ADw9cBpdFP/s7u553tdTub1lc+riPS667/3\\nE/3sp1JO5vaTn1sR+X4RmQEvAr+QUvqXd/vZP1ZSSvfsHzpZM+AAaFGv9Knu2H1AQsO/1fk/3J07\\nB/569963omHh6hpL4Is+xnf+z8CPfZRjb19dt3v9V4Bf+ijnbnbj2+he/xPgf7nj+FcAH7jjdQK+\\n8I7XPw18f/f/XwT+0h3HDLAAHrzjs498nGf5HcDbP91zejKvxzuv3Xn/B/BLgJzM7WfP3AICvBG4\\nDPz5VzIvnwke/dcmxfFK4LuBXxeRc8A+GtKcX52YUvq+7tx/Cbg7rvGu7v0t4OeB7/sY33c/8NzH\\nOH5nCLUAhqCeh4j87S5snHDkUZz6eJ+9i+MPohjsgYgcoLi7ABc/xjjvdTmZ12OYVxH5u8DrgG9I\\nnXa4B+Rkbo9hbpPK7wE/AXzT3X7uj5PPBEUPQEoppJR+FgioFZ0Dv42GRnd7jRnwXcCfEZE/+1FO\\nu8LHwAI/hnwj8FbgS9AE2UPd+/LRPvAJyBXgO1NKm3f89VJK7zyGa39a5WReP/l5FZEfBL4c+NL0\\nckz3npCTuT22PetQI/JJy2eMoheVt6IW/v3d298HfHuHZ53pzrsPePijXSeltAf8I+D7P8op/yvw\\nbSLyxSJiROSiiDxxF0McATWwC/SBv3k393WX8g+Bv7ZKFInIhoj8hbv5YOe1lOhiMSJSyiukah2n\\nnMzrJz2vfw1VVF+SUto9xjEdm5zM7Sc+t934v1NEtrrn92bgrwI/+0oG85mg6P+fLikxQRMh35JS\\nei9ASuk3gf8ATb58qAuRfgnF5f7BH385QOmGf05Env7IAymlf4smbn4MTQD9OhqGfTz5SeAScBV4\\nH10C6jgkaSLm7wD/dxdi/hHqyd2NfBOKcf5PwBd1///HxzW2VyAn8/rK5vVvAg8Az4ryzWci8gPH\\nNbZXKCdz+8rm9utQKGqCUi3/u5TSz7yS8ci9A+udyImcyImcyKdCPhM8+hM5kRM5kRN5BXKi6E/k\\nRE7kRD7L5UTRn8iJnMiJfJbLiaI/kRM5kRP5LBf38U/51MtbXvffJlZJ4ZggdX8iSOuhqknLJcRE\\nahqkLKAokH6POOgR+xlhkOH7lpgJxX5LdnuBzJd6zRAhJVLbIlkGzuo1F0tSSogIWAtOH4cUOYjo\\neTFC3WilWkxIkZPKHGk9qcj1nG6sa0lJv9Mafd8HSAlpWlJZQEqEzT7NVkHMDaEQQi60A6HeEFwF\\n4xc8gw8fIoczvWbmWD2jtKwgBKQs9T3T2WtnSdaAtaTCkawlDDJibjEhIj5hao+ZVvpcAVq/vi7W\\n8Isf/nvHwSEG4Au/7u+uM/0SQWIiOiEZwfhENvPYpUd81KnPdfzt2FGPLe1An0koIRnIJ9C/FSkO\\nPNEJoTAkC9EJbV+IDiRAMU2EjkBqApg2kawQMki2++55JJsF3LQh5pZmM6fasiQLbpkwIemYQyIZ\\nfSS20WeYnBDd0WOyVcQ2kVBYZhcc84uCHyRilgiDiPQ9588csGwyDp/fYvyMYXzJIyHhlgHTRp2X\\nZUsSIfV0ztpR9rK1FTNhccrSbArVdiI5MC1kE2F8OTK4WmFn9dE6ySwxdyRn+NVf/4Fjm1eAtzz1\\n11NarXkr63WYrCA+YiYL0mIJIej8FwU4S9wY4Dd6+IGjHRjaviHkUB5G+lcr3N6cVDiIIFH3bdjo\\nETOL+IjbnemeW+2rGCHPiL2MmFkwgp03yGyJzBaQZaTxgDDISdZgKv+y+5DV2vcRiZFkjL7Xev2O\\nlS4CmoubLM7mutZyXZO+L9RbCVsJ4xcimx+cY28eqB6LkdS0R99lDfRKkrOQZyQrYAzS6HeFUUHo\\nOaqdjJALtkm4RaS8scDuTtd6JHmvuihXXXY3e/aeUPSEqBMGyOpmMgcipMwhPiAbY9LhBLJsrZBZ\\nVoiz2JRImYXSApBEdKKqWifLWrAGCUaVb4hgLTLoQ9N0YwjQNkfK0+uCSE3TGZ+oi7Vb3Clzqlgz\\nVfTi49GmNIZk9Zi0ASGs71PqBkLELWvcNWjv26HeKTh4xNJsJJJNmBryiSWbDXC5w0wXSN3qAvcB\\ncU6fw0rJp6QLxwhSNaTxAESI/UyfoYGUBNN6CEmfwcKDEf2LgDXEjcGxTmuyAkk3k8SkRd+if9EJ\\nvrRITNjQbbYE4iPZzHfKVQ2YCULIwdYJ20TswpOGGQj4opsPo8bENpAEjNfvkZCQBD6DattgfGL4\\nUiSfeNxBjaREO86JmWCC+huSdCwk9Jl2Gz0UhjjQsYRspXzBLa0aj0Vk85mK8QuGw1fnTB8ULrzu\\nFq/ffolzxSG32yFva55g1oxoxo6N5yMmJEwddL46ZRULq9+VCRJ1HLaK+J7Te+7RGTg1bKZVY0ZK\\nSBtIxpBKRywcyQjLM/mxzitAckbXUicSEtHK+ljqFUhKpJWzFSM0EXM4x4kgISdJ3hlMwdYJ0wZk\\nUYHpkXJHzNRaJxE9Vocjx2o9kM6IDzKIYGcN5nBOmi1IKaozmB+pubViv8OZVD1jSabbsyGBd7pu\\n63atn4pLuxSXhebiFrP7C3afEtoND1lCKkM2sxSHPfLCkl0/VEezcxLEOd2jZa7fVWREp0bFrPQX\\n0A4d0XZ71gimiapbjIGgukqMgVz1Txz07mq+7g1Fv1JYIZCsUaUAujicPVLOWY4Uoko5JihzVWwp\\n4fYTSfrExmKaQByUmM5QYEBqf3Rd76EsYFmBc4gIyVioa+J0hpSFev6d5QT0dWfhJSa1yiuPxphO\\nO7CeMEQwjVejs4pMVh5C20K/RzJCduU2sThLMq5TgAlTy1rpSUqkYQ8yh0wX+ix8hCJXY+b9WtlL\\n7Y82QkhIiCSnSgh0A5qmMzrdAkSEVGakIqc+e7yKXkLqlK1uVokJ06TOGxIwurBDYbHLAEbPS1Yw\\nbcJVCROgCSBelXfbM8RzBb5UL35VwxitXj8ZVfYSjzzhZmCY3S/qrS9Uqbhpg6REzC0xM9146RSr\\njntlPAB8YYgOnSOr1wql4EtoR2AaoV1qZFYcBoZXPYuzGWf6U84VhwxtxX7bx7nAcrslOvW0y1sZ\\nO3/kiWKIReeorJRDTGqAmkTM1UhJBNN0hszoGEOn+FNmdP11UUgoLDE3VJvHj9AmY8CgaxzR+W0D\\nqbCdc2FIw76a6lV0CyRnkarFGEOeINkC43W+wyCDc1v4QUZ0GnmZOqjCDUmvXbcka9fefuoXVBdG\\nJAFbR7LZUiOJFJEs0+9bRdh0TiB3lL6mpEZrtbdDIglQWFJMiBV1FtpAqlXR5ld2KQdnQTr1mYCo\\ne7btG0zj4OIm2e0cbu/rOPolZE7Xd+6IzpByg9RqmAkJTNKoO6S1gxZ6Vg1n5pDOZmIMqchIg5J2\\n+zNJ0WcOmvZIIRqjSqpb8CnPIHPEs5tI45GqVSPQKU8JERYVtsgIpcUPM+JWTsiHSEqUN2vcslHl\\n1rTq4bdelXwIpBiRYV8dTu+Jszk89Sjmyk2IAYzVc5xbRx7EqJNmjIZ8Vj2TZAx04WvMHabx+plV\\nmGk6A9F6JHOkQQ8/sLg5kISiFfo3E4MbLWYVJYSELOtOyQeNaEJUg2e768Wo99cZzTjMdbNnqiBM\\nGzFNQFbPYSVJDULMrC7wY5SY6WYlHXnWCbBVIhTQDizVplDtCPkE8qkq92wW1IttIraB6Cx+YKh2\\n9JorhesWnQEIEC1qXFtIVr28JFBvWJZnodkK2MoQSrCdB50yS+w5hRtS6q6j0BJpZSxUyYdMN565\\nYyPGkDBe8D0IhUYwyRiSUQiu2Yy8ON1k1hYc1iU3d8fEyiJZJI4CVZZIxnJQ9xhc95hW53A1D+rJ\\nqvKRkIh9p7BBBqHUe7eVkM2OjKkat4S0EVsH2rEllMc8sahRMU0gObOG3kgJWQT1Vjs4tX5sg/zQ\\nkx3WundrD9Zg6laNhBWq0wX1pmV5yhKyEoD+7YAsgjoKVWdAYqeUgYQhnO6zOFeQrGCbSL2VUT7T\\n6NxahUfIHNIGtYidSErqLFlZe9agRouU1kZJkq6pmBtMSgg5sqwJWyN83+CmggSLm3frd5KIDtqh\\nJRnB9nPk9LYiC86qvihdF/UL0qiOk25oYZCBAV8KxtNBN0HHD7rXOyhMByhrJ+Xjyb2h6FMijnvE\\nwmFnteJ7nccsVUMc94m9jHqroB0asnkkP2gUj4yAU5zLLGpyZ2jHOWTq4dk6qYKrGtLhhLissKe2\\n9Xt7JdQJtsaklFg8cYar39Ry5udKNt5zW89xDsmPQt/UK9ToDIr12Inon1FcMaFet4lx7UmwgiLq\\nRmGXqN52KjPcIrD5vODmgWxSY6aVbprpHJwjbmifpJQ5NWqmwydX8E1K0LSkQQ8JkXarR3IGP7BE\\nJ9gqIiERSoe0GTKvjgwFgEuYukVCebzTaoTllirXcj+RT1SBg8Iw83PC/CLUZzxxZ8nhzT6DS5by\\ntuKTyei/tta/kAuh0KhHkipWu+w2rjsyAO1AiBbqbT2/eajiW55+Fz/13jdj39dHYlJvt7CE3Kj3\\n3nl6oZAO+km4irXS1WhQyJZHMFSaaFRRbRvaIdRb+t3VjuD7eqM3n91hcstS7MJmSCzO6zG/4XGn\\nl1RlgR84YuYYXfWYNq0jMIi4KujaSonZxRzfh3YjEXPNLUlI+FKN0EqBSdDoSFqD+JfpuOMTIzTb\\nPULPkO83uFvTNaQirSde3GR+Pmd5ytA8aendzhm96Cl2a/WOM4vUATetccNMczN9o8a0RQ39wmPn\\nCr8mOs+2UDin2cgJpeX25xje+tW/xU//9ps5+xv6nMgyKHNS5l4GMSVrEEkktR/qlIHuKeigIaOK\\nNSSNmHKHJAjDQpGCcUksFd7Z/kCg2Pe4heLwzWbRfQ/4gWFpe+SHDruwa2hXfOcURqP3BkjV0p4f\\n4/uWxSlHzLqoNCRCrlGaLBvwntS23ZhD5+R+Jnn0rYdBQcoM9ZkBWZlhaq83B5jJgjDYBIHZBUuy\\nlvFlQzbVhIZbBGzlcTcnmMmSvA3EUsMkU3nswYw0neq1hgOFOgpV3qlfkowh9jNe+uaGV5+9zfn/\\ncsKHJzsU//057LSGvUP1old5g9wRcw2zTd15AYZO2SckKuTyMmjHWghdNBHj2gtPucPOW7L9Cpkt\\nOxhIEB9IowGLV2/TbFiGlwqyl/Z0DK1XmMt7jTJAcw6rCCdBcKKJw9wgCfVKmqgemLUK8WROk7dF\\nru/H453WZHTRh1yYXRTyoZAtkiY7faJ/KzK/aJC+58LWIWFjyuVwlmZkAMEthHIPersJt9CQOubq\\ncbsluBm4KuF7CqXETL/PFOr1kqAdR77mdX9A39b8yOf+DL/x+GP85qXPo5gGTNPhu/kRxNOMFdqR\\nqJ6x8QoHGYQUE9GCiEYqtopIFHwhOgaTWO27UIB4YfSiYXQlYEKiGRqIel65XfHgzh5Xsk2WyyHN\\nhoWraOjeRkLRKSGvsIFGuD31cD0kkxAU/koO2oGh3F29zgiDnGac0Q7Mp0TRJ6OebnTC/GJJr7DY\\neYvdmyExkd2cIecLfB+qM5F6B8DRjHTfZLNIftiS7c7JDhvEO4y3+NKQT4Maj8Ol4tmZJulX3usK\\nbpufs/zFr/3/+IaN3+V7v/Id/OjnfQHvqD6P4eUFZl4rjCUCJhF7GX6QYZqIXbbQqmFMqBFXHByI\\nsYNvFIoSH4mDgiQQSqeJ+MzglpHixhwz1YRz3Bwi45xohcn9jmZT6N3oPPLC4apKnTOrxspUXhPO\\nrUfqhmQU/nJ1osnlKAJ2ckS2sFaJIjGRegWpzI7g6I8j94SiFx90E3chVOypJbYAziKLCnd7Rjva\\nIpTgB4mDzFLeNtgGilzoX20VltnbV1ikaTHWaNY9z5DB4MiDFdGIATS0q2oO3rgFL8DVdz/Ii6LY\\n7OxbPQ/8q5LhXBWw4sqGZqePaSMSdFGsJkLqRhkDdySp1kq9+67165R04mpV/jKvdDMDqSyQ1rN4\\n9Tb7j2csziWWpwaccobs2gHS6j2sGERhe6h4/rwCwM5q/GCAJMUtkxEdr4/rBPcKvkmZ1XvL7F0v\\nmrsVt4zUY2WyJKPebrKaQHVVIp9Fxs/B7ignuy+wkS+ZPHTAZNYjVI526nBLhZ5604CrLWCQTYVo\\nVmFuKCDkquglqT0FhXTSdsPP/9HrSXOHdEr2/r94nYO3nWd0JZKE9fiqU9JdQ5B5h+kawbaJGFfQ\\njHR5APXqTbNi54BEIRZJI46oYyz2E26p68QUBldBdX/gkZ09vvTM+/m98gHeVT9EvejhewaSwlO+\\nb5ldtEjIGF/yys7xmoAlgam7vFAUpCOS+IEl5kOICT+0NEODL5Qhctxiak+SvIMsuyRiZog9h6kD\\ndn/B+JkpbX/M4jzEQWDyKke5a7AV5IU6aPhAdvkWrleQ+gV+o6dOXojEIlOHzQopU0W/yvtkM8/h\\nY4537T3ML1x9LZmJZDYw/u4r3PqpB9l5d1xDqBhDdaokWcimYGcrD1/WEdBauSdly8iK0JFr9C1J\\no7+Ymw4pCMiiVp0SdG/ZZWD+UMn8fmjONTTjDFdn9K+DnQmIIxWW0M+ptzOyaSDfXUCMmDoQtjON\\nZGrWuS0JSe+7l6/1TCocKe+czjvZfh9D7glFn/YPcCLASK23gFn6NTaVMofUDdnMIymHJFRnlAqV\\nH4KruwfQtkhZIiWkqiKFiBS5MmmANOwrvRGQFe0pRlJZEC2Ut0WZLwKhn3jDaz7MM88/yuCD7siT\\ndp1CNGgCZqW4Q+qglfTyh999LrkVO4cj6laMEMG0HfOn9QhO4Z0YWZ5yTB+OpH5gHh3lXsHAbeGm\\nDebSDcQY4mhILDPqrRy36OMWrdL2pu0dGSf1RtaMA6fJ2yRC7KsWiM6scyLHJYMXpkgYsTxlNXGK\\nJhJt0+HhmUIh5U3D7mKAGSSe2LnJtd6Y3XmfqR9qxJGg2lZ8enlGCD2ls9kVe7Yz/snoJlmzfQJw\\nmCFeSMNAimBHLZ976hI/++QO2TQj5qI5g1KxdlClb1ZQqKwSyeoBhhU7p8vfSYR8rlFFzNTYgH63\\nbRQ71jeEfBpwC4Pte7723O/zVHkFI5FnN09xszXsP1Zy6g9Ukc0uWpZnEn6QmF90ZNNMsfioLCQq\\nhZIk6Xsh00SgdEyheiQdvMXL1sFxiX1plzJCc7qn8IJFiQshaYIxc5hlS283YNqOQny2oZKcbCKq\\nzOhgnlMbxMJR75QgYDODrXznkIkaESe4ZSTmuh7aDUcoIzfnQ84Np/houH+wz7ed+k2+46u+mfh+\\nNRB23hLGOb6nc5g6eFUplAqLrZX7isEWlXIlISr9ug6INcg6ad8liUPUc0WQwxm2l1Fv9vAPLBkM\\nGuZeWJzNSJLh+2MGH9olFZZmI6PtG5Y7Fnc2I59GsrnHLSOhNLgqgiizy1bdmuxIHslaYumO5vQu\\n5/aeUPQyGpHmC+xkqt5tR6vCdUlTY0hNQ3bpFjsbF9h/PKPcNbQDGNwMFHteQ7VzOzoxPsKNFsly\\n5a56r1BNnuG3esTMYNqI21+CFUI/5+AJsBWYVjnQ0grvfv9DmNcvuWTP8cC/3kd8wI/LLtFqyTr8\\nEBFSYUjWsLivz3LbMHrR07t8qPeRuQ5TdwpR5xnStOu/lGeEnSHN5g7FrQV2b0Y4vcH8goCL4CJu\\nqTzwyYMFrsrpjQvKZ24gy4blIxs0Q0M8Y7FNRrkflNu98ArJhIhpI36QEUqj/POOUZLNAnYZlBET\\njhe7abd79K7O6b+YCH3FYUPPrjHwdmhJuTB8MbH/RztMHi7Jc8+Z0Yzpfh870cW9/7ijHYDvpyMl\\n62HVbDmUidBLpCwRhtBs0UEYkeGZObPdPgRBeur6vuPGq3j8Vdd4friDff+QbCa0I8XjTQv5YVrn\\nEqJTvH/6oKHeSvSvC6PLnQNiNA/klpHiEGJmCOURJZIIB48bxBv61xPjyy31Btx3ep++qRmZhmvN\\nJpvlkvxc4KVsg5t5n60PRLJZ4uC1gWyzxuWepnEsr/cwrazzQaZTlu1I+dyzByzJaoSRTYVsqv93\\ns2OdVr33rTH2+i69a0kZbKB1JUaOWDbGMLg049RozP6TFpIl9BLFgVAc6nqbv+YMzdASCiFbRKXI\\n1lHxdB/xpaMdWKKDatOqsrZQbwqPve4SuQ04CWyWC3y0vGv5ar77ibfzd779K3joXwAiLM7kaxZP\\nftiscwkAOMPkkRHzM5aNSy39Z/eP7rGrg5F4FL3bWvNMyQjzx08DUN5aYl64RjsuqE6BGLAmYpaW\\ntg/hPqO5itFpRi8ssU2k2nL4oToRtjb0bhls2+HypcHVUWnBA0M7LBVeVDSRYhLJJ21Xa/AZBN2k\\nIkOsIeyMiIUju3ZA2u0eeOaQLFNr6z29y1NmF7e6zS4U+x4TFOsOg3yNZ9o818SF2I66GZFFhWz2\\nFAaPiTAusIuWvdf2MR3umTrWQzYT/FiIs4z/7D/9Vd7+9n8P2zF8Uu60AKnyHeQRkQD16ZJqwxBy\\n4eBVGcWtbI3zrYunnOYYpKqP7r+Xs//4gOqUkGSD8aUhyWohBgHcrZzRpcTgpYblGdVu9ZYjO7eF\\nvXlAfuDxRQZJOn66wS0CsdAkkPKyLb5n8KUQM/V4V16DGVvySdAo4BhleTrHbGZa9JQLo8sN5aUD\\nNeCAnBmQjN5PcdtQPwQhGKZ1gUydGt0etEMtPEp5hKmFpKyEVdLKzYXUIWaxF7FbDSkKF04d8tKN\\nTY286O53mrEYZFgT+fE3/TO+573/uSZ4ber480d4Px0WP7/P0mzqGCaPJspdS3GotL/oOtpmlzCm\\ng4IkalSYXj2nLFsmy5yDqz3CVsOf2rnMNPb4xelT/NrVx5jMS7bHc4wkmo3I4rRhcD2QHVh8z1H2\\nGgbjBbdbS9zNu2hFC8kkACT1qEHhHNHxNxuQHwj59HghOUAhll5Gu1ESc0Pv0gFcv31UbFgWChFa\\nYfhiw+ThUnMFAsVBVEPfd1SbFt/v9tzqpzU6bFoCZNOWUBqiVeek3lLobPb6mu3gcCYSMTTRsdcW\\nPNDrg1vw797y9/maX/3ebi51/l2lyepkVxOUqC4MmJ+x+AHsPplRXi8wk6Wu0YAahdYjHRd/Zdyb\\nzZz9RzOaDYCM8YdHhByaUwGJMLk5ZHTZUBwk6i1dr21faLZzssOW8sBRGS0WCyU0Y6HcS6RM8fm2\\np/kPSWkdDa/XcG6ptiy9W14N113IPaHo40afdrOk2dDw3F4sGT0/xFy+qZnmEJFOOYRRweBaoN40\\nCgF0oXFyRj2ymDo6Veow7A526fB5O6nxWz1CabELTxjkhFLx1JgpvprNhOpcoLhtCSX843f8Gfgv\\nAo/+I0i50UKHmBQD7OiWKbPkexXtoyNirp7h5JER42enamhi1HF1HsKK+546xT95ldBsB03WWasY\\nXUxkU8PoeRh/uCK/uk++O2B5caBUPCANeuR7S+otp4taVNnHzODmHtMELdcqbFeZatbe6mrjRSv4\\nviEd8++RLLcFPzD4PvheYnG2YHT2FJsfmCJ1wDRR8eskypBpLdvjBfM6x9RGKYMWUtbh4UuL8Z1H\\ni3LJxaujk00ExpByIVSOrNdSe0dqLJiOQbG05FsVs4M+VZnxtz78Fbz+LR/gPf/vE6RM7YEEaIdC\\nrMAtVR/0biemjwTMuEWAvddYNp61Wqjkk+L1ogli0wox06gj5ok33f8iX7D5HJfrbd659TDDrGEW\\nCj64OMdvvvQq9m+MkaXh+n6JGbbrSMt42PwA3B45qiKncIGi11L1HHZm1DGxrBV8cmkdSSTbsYUc\\n1KcSMT9+7KbZzmkHPZqh4PvC/OwpNl4Ykj+nexYfNHJd1FSPbeAW0IzBzTrKaJfzkMj6ObpFXCe/\\n1eDqPisOWpLNCbnuU99T/etMJDceZyKVz7jYP+CZ+Rm28zk/I49x6rte4NY/fGhNjbW1EMY5duFh\\nqQyb8vr8DEBIAAAgAElEQVSc+OQmzWZCvLD7OWO2PuC6PEGmVavGIHWL9DPAEK0QSsPswUjaaUje\\nEPNMyQBlIFWO/gsZ/RuRcj9Q7huWOwZXrRL4idGlirbfWztdIRfVPYuEaZPCiJ0jJl10iFEacTL6\\ncnnKrXMkH0/uDUWfO0JhmDxgqU4n7MLge2PGvYzs1kxndToHYPKqHu1QN1V5WzPzUqknapYO2T1Q\\nKCVpu4IVyyXlWRd+qYedepb9J/pUp4XZoy29yxm4buMkcFO1ttmhcmX9BY8fdI/LCNlBdZR0NWhS\\nxwpnf3vK/pNDJGqicHH/gJAJoRBsA9k8kE082XWPVA1ilM6VbCL1AuIS89KQ7TlNYG4Eer8juGlN\\nms4x+4cMmjPr1gVSNYg1bLx7wfyJ0+tNrUlDLT6KuV0r9d6ehwTZ1GPawPJs2W0uZVEcp7RDhUSa\\nB2s2tuYcHvYJvQJfjunvhjVPPBmh3Q6MRhWNt8x2+5RTIT9cYd4d7a5ZYdQKW6womNHRFe+ANELq\\nwaBfUzgPQcAlxCTwlmaaI3mk3S+5Eg2jizXVGYV0JIjiyZ3hX0kSGH/IMXlNQvJIvK9id5yB64pc\\naoubCtlEPdMVvCBeuFge8JryRfqm5vez+3juxiku721xbnPC/rUxbt9p0tiCbwU3143vFoHyVk15\\nmHPt8/vc3srJxzVSBlJlSKscglFv3s0EWyunngiLC0r7TQLt+Pg9+lAY6pEwfRjazYCbGnyvZLM4\\nT35rqUVOrdaOzC5YfF+NaLmnFcT5QUcTTI5V5a9d+iP6cExdlapqOFtFXCb4PtSn4P4Le8QkxGRw\\n4tn3OdeWG5wuZnxocoY6Or7l/Dv5wQuvwtYKicgBhMIiTcSlBPOKOOxx7rdmXP+8ITFTSGjvyX4X\\n0emaKg8j2SwqOyhq8ZqtIqkwDMYVO4MFn//G57nVjIhJeHp0hZ948Ss1aTz1FHsBt8iVHbhssbcO\\nwRhOH1YcvnaTZqQRb8iVpBAzWdeBSILebsS0SfdsEzh8pI/xGk3Wd1kMd08oeg13RTeq1zDcl6jX\\nPSqJmcWMe4TSETulSVrx49ujYqJlrdz4VWLFB9JijvTLrrTaQK8gOS0vX57p+Me1IRQJuxRCmfDD\\noz4liNWFeK1g+gBsfLjGHaz4r7XmD/KMVCpGn6wwvNbSjCxt3zA7ZzFeqyhXSis/cGwCbn+JWWjf\\nGbsUzMRhG/VYteeKkB84XvwyzxM/HpE8I8WATOYa6Qz7rMu4YySbtITSvqzoJpSOUBqasaUZqhKx\\nDV0hk+LLs/MZtqtEPU7RhKtec1A0NANL3c8JhVkzQmyrVESzSCwWhcIyU0c+0Z4zHlVeodBEq3i6\\noirl1ZuQaPuKUcdMvf9yVFM1GbHbKVJZUhmg7zFZpOw11C4iwIdunEYGnrRwSBAkCG4GxUFXlTsU\\n3YgF5DedhuaDyODcnBgF5wKZDdRtxmJaYK8XR4wJ4Gq1yY9d+VI++NJZYgchVfslL9zq8fgTV3nu\\ndx4AD6uWC5JWkFRUyvDM4maO0Dc0+0oqMB03PuYJNlqyXkvwlmbh8LedJvwF/IWatHDYmf3jpucY\\nJli/J7luvxSa/wlDnWO38ITeUNszRMFEjZKymcdWWiyVHWp1su0qYE3VIlVL6hdQaRVsHFt1WJxC\\nN7aCnmvxybBX9XH9wE45x5BwJnCxf0gmkXcvHmT6as/271tcpW0i7DIof731xM0BMbf4gWN0NVBt\\nGdqh6oUVwyk5WJy3FHuW0VWF8qSrc7Azw6Bo+PzTz3Pg+8Qk3KqH/OLytXzxV/0u7/7h7sewYsJV\\nAdMGzKLRaEciZrbAVWMQQ7QKuZmw6gelcE47Epbe4haJ/i3BLSyuSswuGEwDrrq7qbonFL1pNZxZ\\n0Z+Mh2KScHNNJsauuMUuWu3v4SGbdxSnZX1UFRoj+A7zr7qki/ek+QK8R0Yj8Mq5932r1ZnjiJsa\\n3EJwcy0njy4hQchvWUwQfJkIw8jFb3mBgx9+gGxXq3MlJvCt5hFWiUwx5HsVps0xvkskZcqHpgvT\\nqh1h2hb0C0Pv2QXtuREpY80KiHki9iOxMNhKKHeWWsFb11pOvVxqziHP1ko+DXrKDxYh9Lp+HUYN\\nqCpWNZAxh8ZAO8zIp07ZIa0W3Rw7j76rERMXmVQFy3mBazvWiii23Wa6WE0j+MMcUxl61w35JK2Z\\nLtFp2E3SeXdVwlWRWKnns64krISYC3nmGZY1L13dxs4s4gWfRSSLJG9Y3O7ruht4RmXDj77xn/Pd\\nb/tm7FIoDrRCN1vGNbdZG7JBfqitMtoEjYsYG6nrjGIQyJ1neLrmthsRD3KyA4MfJP7w+nliNIiJ\\nZC5hbcRngRAM28WC5yIYL0QSpj2KCEJuyJct7f0DhTZqIRQRaQwSNDGXykB/VDEoG3IbKM566vsd\\ne7M+oVHDZTYagj3+nwiWrqZAITWDXYrWRzSRmBkt2BtmCpMkneN8kij3AnbeajRaR0zsHLaEwiNV\\ni8yX2hPKGML2EILy0dXThXYUOaxL6tbReMtmuSSmQEyGg6ZHTMJ2seD+co9f+cq/x9c+/32UBxG3\\n0AItWXSOWuPBaX1JeTsSXa6NynpqVEwLvlB9U52BZB3lXmRwvWW55bjvjS/x5lOXmPgeTgKjouJs\\nMWERc57qv8jvZm9QgyZgJ00X+XdV2W1L2hypESyPaLy6cRQN8P2uQHCYaDah3rZkU9tVuoMf8vIq\\n948h94Sij850YV3E94XiINK/VuNuT0n9gljYroAhUky0IMm0SZMm3mt7gq5SDHTB4L12eUsav66a\\nlclkhm1aijRidDljfsFoaJsAA/lEiAtheT7AfTU+CO5SiXeJ9710juz1GQ9+OOJPj8iev65snrpB\\nMqveYxsxi4bMR5anx2pxE/RveaotSzMSJFccfXkmA3OG5SlHO4yq5LOkSUdJxFKr6M6NZ2Dyo3sA\\nEl0fntRlVVfsn647pFt4LfHuesDYBpCEH3TFQeOjnjLAuonScUqyIC3EhWNhCjjMyCZCNlNGgSRV\\n3kkgmwu2UXZFPlHcW0LnvYp65sYfFVuZNiEO6g1V8tk0YRpBvGHSG7HcVtB9ZWzM0pK8QTYazp/b\\npw2WWzfH5C7wC/tPU5xakn14pAVRMeELc/TcSLiFRhKmMYS+oZ3kEIR813L7dIkdN2yMFjodw5aW\\njJRFUhJiFIxJ5LlCRNbq+4XtGueZtPboV4nUFfbajAyhp8YOFJrSPaNecttaTC9xYXiIk0gVHGf6\\nU6ZtyaLNqFrHzB5vxbOOuYMiZzqHxb5Q7geySavO2QoGtEIxSeQzvcf8oDkqMozovl11qmxabWoY\\nI0RD3OiBCHbZarsFK/SvazReOs90WSACN+dDNoqKjWLJ4+MbALz38DxDW9Ekg/nCfYqfGBB6Xb1I\\nket3hIRpPKZRKnfPCtVWjq006h2+FFicMviB0Ix1XqptQ8xyDh6DL928jo8GI5Gxq8gk0Lc1G2nJ\\na4ur2EY58Jo8v8OL6np1pV7W1WbovJu2ozy7znGptT4kDOlos4lQCKbVRHVy3HUx3D2h6AHcrGXQ\\ndt6AdNVvmwPMrCK7vVh3gtz4w11V5ECaznVvOKsceue0odEK4/Ndn5mmJS0rZDTUoqrlErNYstMG\\npg9urcdgWg3XlhcCphb8NIMg+GEi27O0FDzxlue5eeUhBi+12DNbmh8IQTvm9QrtfTOZa5PGR0Ya\\nebSJwXuuMogRrKV+9Oy63PnSW5RdEnuR5CJSRGwWia0htYZkE1eu7PDIqYAZFbgbh9CKGrKm0YSz\\nWXlQuWKQPuKHWZfsFeqxhnvlfqQdOJZntVag2YJ8v0siNiuldnwSnS5Qt+9Ih04dmkFicc7Qu6XV\\nrqtkVO9GVwlotIeNrZMmW6Mq/GzeNaULSSO8TFsXGA/tUFkaxWHSStpblr0/rd0MTaMKqbVgNhui\\nF/ZnfXxrcUXgxo0N3mMi3/O6X+NHP/w15AfC/LwWNtllor8bCLlGRL5Ur9UuBbvUaOjMu7XnUcxK\\nDl/Vx4yV6jl88JC2dVgbsTaSO0/uAlYSPhpiEn7n2v1aYJVrfiBpOxdirpWu9dkhi3OCH0RSEaHt\\nuhq6RHIJM2zxtePGlS3cg5Gnd65yOp9SmpY/nF7kFkOWzfF78zq32p6i3IViX5VQOzBUZwuK3RY3\\nX+U9IqNL1bqZnpl3bZQ7ggIBuLXHquHLaj9jRD1vES2e3Jth9+e46YjdNxbMm5y2taRo6OUtZ/sT\\n9uoBe82AOlrGWcUv33ot5ZmW3/nc/5Mnv+yvsv0HQjI93DzHLVrsnub9wrhArMHNW2ydY7tmeqM/\\nvMUIwBjmj27je8L+E5Y/+63/jkwChdF73DFztjsOa6Y0KH5j8RjNwGDOFLhFxM0UuVDmnUOKnFBq\\nq+Zkdb2vWl+HXKg31Qnr34BYCPUpzeMBZAdWod6WdbHcx5NPRReMT1gWF0qaTS3c0WSH4Addc7Ku\\np4wsG6VHLipNREZl4og16tFnGSlESNoDOi4WqvjvLIyazQmHE1LXi93sTkhWi2+ANa5tasFcWGJn\\nlnxPucmhl3ATy/tePM8Df/kZmg1HfbrP8uEtwvaQlDmq+8b4zZJwdhNCoP9SRbnryaZBx2EtadCj\\n+NB1ei/OuPUF/igMdxE3ajF5AEmkKGATkge2zkxZnMlpNgvCzkhZDWK0/UFKhJ0RoZ9r4UdSyCMU\\nwvK0Y3qfw/e0GrUeG2yVcIuj+405XVMu7cR4nLK8GLQmIXRVqpkWANVbiWrr5V0qXdXh2iv82elz\\n0XYEXVe/pVd4io7fvmpw1hmI1fh7exGCYJZH2LTxQlg4Tp2aspyUtIcFxkayXsutgyG/svskP/R1\\n/xQ/SPghLM8k2pHQ9gz7jxoOHoPDx2F5VjdYfiDkh10E2bFddt7rGV4WHnjyOtUyJ3jtrbIzWNDL\\nPIUNhCSIJPpZy1NnriFnK+KGJ/Yi4u+Yl0w4eDSnHWpfG1znEbpE2G6xp2ryUnMOpu+5dTjkxcUm\\nEcFI5Hx5yOnejK3+EufuaIR1TDK9z1BvmK56U8fbjIR6ZKm31biYNmCWHrPomhACKe+qOu9oESxF\\n0XWK1cIknFZ9SojaCM1rUSPG4HZnmGHLbNlx94HaW24uR7x+4yovzLZ55kD57TvFnPfM7+ff1sIv\\n/0c/wvKsMHnAcesNBYsLPfypIftPb3LwWJ/91wyptwuKSaR/Oyh9Nih/PvYL+i9MsE3ie77x5+gb\\n9YgyCZzPDxhZ9eZL02IlsuNmfF7/OXwPqg2rbR8S+gysAWdpL27Tjt0dfY20cnx2n2H6gNAOuu6o\\nfemqeTu2nElaN1Kqg3C3Vc/3hKJPVmhGRimCRnngMROasSOUWmwkdZfE8Ee93QEtsOqVSoGyRuGN\\nGDGjoSr7slAlf+cPdBjDqv1wfTrQDhTzCiVUpzWxZGwkjDWj1rtm2Xy/9l6JreGrTr2Hg0csV74k\\nZ3q/4/DRAYdPn1Y8UoTJoyNtMOYj+e6C/PYCNkeKy5UZadjnA981wu05NSJFwo1axEScCxhJmCxg\\nC73XxjsOHrFM73csz/ehV3ZeT8fNXzeT0l7uobf6QQchWlnT17JFojxImtTsKHzNZqQdaiLouCso\\nk0uEDY8faQVwzKO+V4Dv08EkmoCyjc6BCbp4Qy4dTmnWiWNTtcRSW9g2I0O1YbSBmVtVPSrvOAkM\\ndxbEfsBV6gX7DY9kkdwGypE2wwtX+pgPDvFVRhMdbx3cpr2vYfim2zSbkfnFxMHjhpRBGEaGj+9T\\nb6lCzqZaYLfcOuol43uG4qtucuNwhMsCxkZOD+dYE+llLdZESufZKCoK6xllFY+dv8lwe0Hqq4E3\\nHVwV3YqJoc+RpOwhRi02jxRli28tKQixcrSV4/p8xPV6zKHv81T/Cg/09nDHnXhZiShdst7s2ukW\\ndD13lJEjHc3ZLGqFRoLOYSxV0fvNHrHIdD93LcpXnV1TvyQVuf676ldjRSN1Zzm9M2XUr/C1I0Vh\\nu7+kjWrUR1lFiIbfv3aRf/PsI9yuh1z3m7w6G/LwVz7Pn//OX2P+9JKrXwxX/sMBvqfe8+7TieWO\\n65wN7aAatgYKZ1qhPd3n6//m2/ij+UXq6MgkcCqbMjA1Z7MDMvGMTMU5d0gmnpgM84uwPCMsd7rG\\nebajVHf/RneUd6o3hMXZrhNqX6NZ03aRbaP7QpvUGeJmix9Fokt33XH2noFu9IcfWIcvazrdyhKu\\nfkQEtJmXMS/nyHehoHr3jlQ32rxrdaxpSMslYkQxOqB98LRayEEA4zrediIVkbZ2uF1HKBL5vrD3\\nJq9UuDzwXH2W5RlVTLP7WTMC3Gv62CVUpyPN6BymgfHlGjdtWDx+isGHhPnDI658dcTuWVKm3nfc\\n6rzUaEgRQgfbrCrhXrWzy3vPjDDeYCvLwNl1ARlFrhCFjxrl1AEzcJ1iV0XqlmndKMouGoZXCvZe\\n06MZCYsLiXZbKxH7149Z0690TNd4S7woq2bV970zLqsFb9tEQCDTKEPxedY0s2Qtvmdph5rrSFYT\\nZ8kp3JMtNDqYn7PUtcP0PdE55d4bcIVnXudUe6X+WEQU+m/YJc5LStvy/iZy/tw+yyZj+MCEEAzO\\nRurG0bORjV7Fwaan6hskOdxcPXzbGqotofdlN1l0UEmMwtZIK4BCNASg8o6YhMo4MhN5cnCNC8Uh\\ni/YxlosCUqZ1AUFbGoSie0StQGNIPa3KjK2hsVY7MUYDXrD7Bbf2TvHOOufV27vcl+/x9OASu+2A\\nm7Ph8c5rJ5rXOSpIgi63kO7oBtm1zpbGkyTTOoee0/oXg3q4Kek+NrJuNrj6QZE1xBOBEFk+vIU1\\newycZ9cOiY3FR8Op3oyJ73FtPmarXDKvcr788fcxDzkjo70y/pv7/zXzWFC8oeVmM6Ywnmfnp3lx\\nusmfO32Zn7d/ipQltt7jyOYwf1PB+Xfm3P6cHl/+l36Ta80mPlmMJLayOZZISIaYDBHDbhhSpQxL\\n5KnebZqdAFjc8qgmgJi6X5dSiqalo37u5Kr/YP17FKZN2AqGL7WMrir9vN6C+r6AbDW05Jjdu2NU\\n3ROKfnh5Qb1daAVcrqXQsWsznJyQ+oVWpJY5Yaj/t7cn0P2wBs4e/TRe95ODmvCI6/YJKQRMUWA2\\nN0gpMf/ch7j9lIPkwSYW5yP9q4bitiFODH7g2HgOpg8oba+4ltGOI3GS85O/9QWw4cmvZNTbUTff\\n2CO3M6pzgWyzYndUII1hdl+JxJJmI9F/9CzTN1TYG0X3q0CGZjNCa3DDRovwagezTHW8JLCJzXxB\\n7EV8X9s+NBc3yV/c158zzBymbmEe9LWz5Ps1bmbWXfnWlC6AGHHzitPzhttv3KAdG0Kr8Eo45uZX\\no2cd1emE7yv84OZHfHhQDF89V6VHSlCjZGtIPeXhh0IhGltH4mZBO3KaoOw6VIaiYyt1yaxbTxv8\\nQ0vGvYbpvKQ511K+mJNfd/iB5WAjY/hsxvxBxTz3b4/YPj1htxrwvc/9Bc4PJvzB/kXObE0xkuhn\\nDdcmYx7c2ufJ8XU2iyW3lwNuboxokjAeLrn54gaveuw6NyYjqmVODIJ1kUWdM8wbMJE22DVenoB+\\n3nIh22fbznhudIrKO24sHLLnum6j6tG5mWAXFgy0TfdLSFmiXViF/QBbGT2vEfzhJs++Xvjd8iEe\\n7mmrbWeP36vffn/L/Kyj2VAv1C0656xVSCaUFlM7Ql7QbOaYJpLNWm2Gtq7Z0OdhJ/N1cWNa/dAQ\\natix2rddQuDmF51h7+nIY7myZk5vT7l5c4NrtzY4WPS42tvgxnOnqB84AOCF+TZfdvp93Apjfm5e\\n82h2i7dNn+ILBh8iloaRWfJMeY43XrzM6/OSv/I1v84z7Q6//Kan2Mnm/P/UvWmsblla3/dba+21\\nh3c+87nzUHP1VPTk7qKh3Y3AhgabNMSOjYRNnMFBEEUCEimfHMeRkjiWnEjGiRKJDMbYJIYQ4g6J\\nu2kamh6qZ7q6a7y36tadznzOO+5xrZUPz37fW3yiolykYktXVXXuOW+dPT3rWf/nPxjl+a3vezf/\\n1vVPM2463CrWqdqp+GndZScbMzAFVTBMm4yFj6mNoatL+kpj10rcvEPdhfn5hOyoRi8i0BqzqNC1\\nQbfq+t49jV2YVYCOXcguXTcBk3vsuCbbh/33d6iHEb5uXWnNnyELBHMyJ3UB13mQNuStFu8VF9po\\ntBifWfGa1wprNGbcDl6XkE5kZKjzJvaIShIwBq0UfrFYQR3lSONjiI8N/lpOYwKmihnccti5GIqd\\nPgUukY7XnkiHHR8bdKWohh47g7qvCCaQ3GvNs7RBHXdhqyFEgfxKTXbL4mORMgcn30+tZKjWcRJE\\n0XKstQk01mOmBlVrmqHjpOwSTQTnc5m8RKGbQtWwzMMF5CXR+oEX/jKPchnVuLRwiAxqlrP5VY+L\\n13CZcPcftlS+d8+jvKYaKnwsyVkrlR+t97sRPrxPWtWfUdiWoaFaxKLqKqJhRDxpVtCMKYUCa6oH\\nC1STKqqdhgubY05mHTZHM05UF1PEdE5EYr/Yjpk+1qAyR3q+YDFN0AreOFhHac98EFOPEyZZRSeu\\nOZys45zi/nTAzeMN3rlzn0FSsH1hyvN3z5PFNaepp2srEluTz2N05Ol0ShLbsKgtVj8otPNFO3MC\\nUlXzWr3NyOZsduYcdXv4eWsAF1Tr+9IuiHE75/AKp0BFsvtUTnZIppIFVFfQfG3Il9QVvpvtUDvD\\n6fjhJocBZPfmBN0jKmVQvRSYKS/3sckMkNBkhmqgCcqQRZq41aDoQhoPl1n0oIs6m64w++Uhgiu1\\nclhdnFOE1HPzYIOPXrtBahr2i3X03FLVKXvDLutXTtnsLDjfHXNadhi7jBvFFolucF3Na4sN1qIL\\n7EZjvl1cZOESOrrkC4seP9J7iUvRGT+39Xv85uQZrsRHPLWxxwV7ysyl+KApXcTV7JiOKTEqoPEY\\nNA7FUd3joOpzNT3mX+XnqHOLtkFEgwNFPNM0a5nQS5fnhzzzugkkZ06gIiXQpfKsbKtVK87c/toC\\nn3TEr0qJLuGtHG+LQq8WBdpoVG6IIqFAmTz8cbpfi7HX/aiVeCckPqAXlXTzIQiWvxQVRWGVFUuD\\nZL8aQ5gvUMMB8cwT5Zp8EPC1hqa1SLaK+W4k2FcQ+li4sICzLmahVgOz9FCi5aK5otz2hGJZzAAN\\n2S1Lfr0iOrQifpor8kdLKGSr5eOAz0RZabOaTlqhtefsrLuCbIIBosDLe1v0Xhf+uSmkc6rXMuKD\\nmZiidVKxetZq1Q2tqGtLymnb/YfYyr9nCcoF+ncddUd+xs4fbufXu13goxRTKOreEpqjDclAWKGt\\ntLsaBOnQMyC0/ixBOnYJE9HoyqDrQDJd2guLF7zK2jnPUIEOJFFDUxt8UGjjVzhmsd7CYbEn1Jr3\\nnrvNl+urTOapdOFKcXA4gMRRlpbzgwlns4w0rZkXMcZ4vnrrMh+8cotXTrdQOjBeZHzvk69ydz6k\\nqCzaBOKkxgeFNY5+XBIbx8H8TfBJUETG8bnJk3zh4BpGBcomQmtP3WsIpzF23vLU/9jgRJw7dQNe\\ng0IJcaCi9byRb48KmN7vMcsyUQbXD38Up/dPiPsJpjKYyshANmJla7C0YQgtBXZp8ax8wE6qB8rX\\nAD6NMW16m6qbFddcvNtjQqLxqRFWWKPI0hr9ptmDahQuCahKM8oKKm94/+B1vjO/wGuLTTJTk+iG\\nz4yfJjM196sRTyd3eaE4zzl7xmvlNmvRnF85/SA/PXqOr5QX0Mqz1wz5ud3PcLPapg4Gqx2XOuLB\\nlaqGDTPDqoa9JsO0viJaCfz4T+9/iOxG3DJqhA5bjIw48LZWKHgvkYBLy/DlP43MrXTV+i+1t2+Z\\n5tW959twHclOeEv36+Hc9v+fR9OgTifo0wl6IqEBqmrQhfBn1RJnbyPLnFVUA0M9SNqkJC94dd1O\\n951bdfUqilaRe7rTWXnCZ3sFqoHuI2PMXkLnjYhoEUjGjngWcJli+CokJ4rO53pUG47uXUW16drO\\nOhCPA9WaF760hqbjcR2ZiCenoHJDcirKznrkifZj4qMHDnzS2Wuc05R1RAiKwSAHI+IZn3jQgcGn\\nu6y/WNK75+jfqURKvaip1zuSRVnVK5xT3CqdDMBafjBGE1Lx+yYy+H4mQqp+IoXzrCE7rLGzt8jV\\neouHqh2DGznD1xv6tz3RPGCnATtDkqG8+Owsi4TLPPUgUPekY8+OHclEhlGulYXr0ouP98SRHtUk\\nU49dLLFioNLcPxvw9Pk9DveG+Bs9ogKSsdA5qwHE9y1qYfjDLz3NxY0zmnsdOr0S12ii2KHHlkG3\\nIDUNWgcGacmgU4iB1knCWZVxfNyjmsbsDKa8fLrF3eOhBLAbL0mRtaF2hqKxVM6w252SxTW+1ti4\\nwRrP//65D3L8zW3u3tzk6P6Qpoyg0ZJA1dowLK+Nj+V6qRrhUdeqNed5wBYLkfx7k0E0M+hTS3Ri\\niaZ/CsrYEIhfPyS9M6FztyDKvSS/TR1RLjqXN+s0XCKq1mogHFIzzjHjQp5PzWrQutp9LvIHyU/O\\nUw0sdiKf9Vevf50/Oj7Pt2+fR7W2EbpWMKx57d4mkyLhf3jle7mSHvPi2TbdqOSszsh0xevTDa4l\\nhxjlSVTD1fiIi/EJAN8aX+BWM+Br82u8lm/xZHKf1+tNXijOUwdDpiusckyalIWPOXY95j7hkj2m\\no0vmLmEtWpCqmpf/1SOsveLp3g1070uDZmqoBnZlvhiMoe7HVMNYrMVzt9KILHMJhFkm+bJoaPpW\\nWF+TQHbsSd7iLvxt0dGHul6Z/K+8otsEJNQy/1OhFyWm7EgMXCQUzCQyK6Xokj+PWk7xvfDr+z1w\\nThg5bYeri4b5JQ+zFD9qsDNLPFtOwqW7tPNANVRkx57hbwdOH4Xk0BBPZAgY5YHkUAQVKkDnrqEa\\nCoFb2MIAACAASURBVH3x7N0N0dgQT4J0s+UyB1I8SfLzjVDmnIL7KUUaKEJb/HWg3qqJji0bjx6z\\n/ltjoZG2kYHsbuI7MfUgxSd90pf25DxDgEbS5lVePtgRGYPvJPhO66ZZewlASCNMKTCZ8oFo9nCJ\\n9LqoUbXDTEvsOCFei6l7hiZhlT61VAPrujUL1ILpBwPxuCY9ctQ9SzWMZObgIVrUYu2wdOM0D8Qj\\nutB004r9RY/e+oL8dEByykpw5ONANFPUA09ybNj/9EXCJcd8msLEUqeOZKw5OuwzmYtt8t37a3QG\\nBUUe88hT9zhadGFqUYOaSZGSVxbvNWVh2Fib4byiaRIO742wPbmmJvJY29AdFMynKR+89iLf/fsD\\nvFFUw4jFtmH8hCHEHpfKM9M5DFRKeNQ+ajvGWuGMZNWqsr2OETRdoaYqL/8dzVV7fR/49j/MIzQS\\nTK+aBrsoMPMOvhNL9nAk3aYKQB1WQ3VvoRhpsv2IqKxQ4yl66XK5FP8FwetCv0vIYkIcUQ8StAvE\\n08D6xTNmLuGx0SHjeYabpPiW1KCMxy8iWIPxuMN/+9xHuXTxmBuzTY6LLlf7J9yf9vly9zqvJVvs\\n2An/5+kzXM8OuZlv8VO7X+bF8hwvTXZ4rH/AXjPkzHVwQXNSd7mWHXJad3FovnhynScGXXxQrEUL\\nOqbk0eyAm/kWPzP6Gv/Hf/+qECaSGD/skl/oiUK9rwkmoX/7GN3vUPe7YhtRa4EmfRuA4qTGgMwq\\nTNEydWKNqcODuMv8re3C3xaFHiA4J45ylRQsFUUPEttDS7tKojbNR9ShLlHU6x3ivC/iqcVCmDYg\\nsE3woFt/+yyVAJBUbFRVWZMeasptIMjWb5UPGqTwKB/o3XGUQy3Wwp6WaRGIz+RFWn/RcfaYWQ0Y\\no0UrsDo1DF+Rh1sYJIp4IvxY5QEbiPtip9ukBs5iTN7CP40Sn50A0zxh3bvVIkgSE47OMEmMGaa4\\nVMtCudz1gGx9nUNh2lQsRTOUEGVlZfunK9eqDTV2Xq4ELQ/zULXMTlTjiJx4mZjSUvclMi7otqP3\\nsgC6UrN0mqz6inLd0rlbE48rTO1psqj10o+o+xHlQEvYRyRUP5fKTujozoirj+wzI8Gn0h1bL8yo\\npdo22zPUvXZr7CA0Gj2qCIXoJpI3EsoLigoZAJRFTNapuD8esLjdBxUwkadsDItJStypCEFhtGeY\\nPlgwq9zKPdaBokixA3E7PCj7VMMIb6RrUw7sWItlrxWOtCkDaROohkY4/T2/gvSCDivFsBjYyc7H\\nlEq6/kaeRRnYPfRbuwqqDo2Deo5WkuakMotPrfRTS7impQouVb/FVozJB+hb+zDPhTtv7Wq3HWyE\\nH3ZEYRtp6kEbtl3D/v0h9QWDVZ40rplGMpdYeioRFCdHfWxWY7uO2ouF8VY2I3dCcvjS7au869w9\\njqIeHsXdcsRWPOXVcofPHj4udhK64bDpcyvf5HxyRh0ZOrpiJxtzp9rA9h0nlcw+Sh8xbxIuZyfk\\nzvJKIylfYdRrdymB5KjAZxHTiwlNKkVbnYwx5RAfhZZcoFfztqYjpnG6AV+H9v6KnsT4sNp9v1U1\\n+9sDuvEBZcwqhDuU8qKoNmPxzVxxXfkHnhBA3Y+od4eo2ApnPkvlj9GQJPK1Zaew9IYB0G2ROItJ\\n92S9c4mi7ojhVnri8ZHCzj3J2OOsotgKFFuyIDQdwR6LtdZcaCHbsyhHqFGVorvfUA0V1ZqXDktB\\nciZeHdFJRFMZmsoQnHjtmFLRuS9qWHumabZrrm2cCL+4tVkOIUCQnYo9LYSmNuihsowwna8k5MFG\\nq+hDN8xWuP/qkicRuvaYhYSsq0X5li1P/z8dSxFXUWFO5kTTimgmW1Qpuq0GoBFeulnIdaoGinzD\\n4K3ELbrECI1PS2SeDHLbeUVfrYRFy873/umA2X5P8P2eolhTLM4pTN4a2TkxjQsa9GZJZ5hjIpnt\\nNB0RyKnSoKcRVBq3iPBeUVWG5FgTUs/GaEaRx6AD5Thl0C2YLFLKJsIaj9aBkEeoWUR0Twzb6tOE\\nze0J7xveohwYmkz48kpo9Kth9ZK2aIpAZ9+3UJcMrVc2vlb0Dyo8wOiDFojAzuWZjBaB8KfRzjVv\\ngvmcJyxycVJtxI57eR7eLOX84tcflKLqasrNDNUTzyGMEVW50bKAJA/oXy5tI/zaABidNbw03eHb\\nx+dQKgjcN/KE7RJfGTBB4K/CEoLi6bV9HusdsBYviFslaxrXjMuMG5NNxlXKYSXzk6O6x/3JgFG8\\n4NF0n7vlGpF2vDjfYcdOeKPcYOy6GOXRKnBY9Dgsejx3cIW5i3llvs0HBq9xycxg2JPIP2tWyW5m\\n0RDP2sV60IUkJnv1CDuTm9dkYh3RdMU511SsbKuh3QEHcTbVeYPOG/xbfGffHoU+fpNMu+1AQ1EI\\nVlc/eKBCLNa9uhSrU7H/1TSd6I99xiowu91eAgJ51A3YCN+TzLiN5x3xkcG36th8W5FvPlD7BS2e\\nz/Ndw/SKOBvixZclHsPsmmdyHTr7XqxtXaBJYevrnqbnmVyKKEeB5EQLZVDLFrZ7px38zi3qNEaf\\nRWKqlotoK2hoLhdkg4JRnNM8dl4WryiSLEsAH9C397CTSpJ9QhCTM5DOPrara6JnFSZv0LVkU0az\\nClMIho9HghUah84fbvAIzrdwS1vs6wYzr4gWEpumvPjWePOgA40WSpTKqmXRrMU0g5SgH4R4m1Jw\\nTN3Iwhrl4LoO13MC+RwZyrO07XYD88tOYDoVVvF6dS9Q9wP1ZoOvDFVpqXMLs4je9THq0oJoqkWx\\n6hQqdoTnB4z6OcVugx2U7O+NqBcWpcS35eiwT10biibi8LhPfpJhTw120iZ6ZQ2XHjnkXH/CTjRm\\ndkEahSZtX+IWejGlsJHmu0Ij1ctFsGg7+XYxM4VCVzKMNYUiamXxukKajRbf1w/5tgKtelW3u+b2\\nmSwrMSWrZQCuGgmj1y5g58hspgqt/kHjRj1pxOK2m18ebUqczmuiRUOdSTSit4r0uxmvnawD8r8d\\nXT2je3EqP9eqyU2nIesXXN04YdokTJqM47LL/mLAD11+kffv3uZg1mOU5CwaWVR+47X3sB1PeGT9\\niHPphE+fPM20TvFBMasTXpifEw1EiHh1sc0r0y0O8y77C1kk+lHJewdv8GRyn0OfMHvHFvV6R5LV\\nlnnM3pPtF0SlZ3F1uFL72lnTWiDIM09oacZVwOZi4BfNnfDqS9cKAxEufvlnDLohtlKMjcQHqk4G\\nRYlKYqFXxhYfm5XQxk4aTOFxsSbKHX7QQdeNwBjey0OoWrZN63ujux2BEiKNTyPyDeGiOtti48hF\\nbjpQDTW6DuRbbRZoErBjxbln99j7xi7KKfxAeOtHz2j6t6QLRcPRMxq/VTLxicAAJ62opy3k6bGE\\nm7gWg1O7BWHcIZpLLJydaLLrOf205Pl/9jSjYQ2M0GUjLKPJXLaEjcNMxX45pLEwFryXvwtBBrMh\\noKoaM3ZEraNniC2hL5GIqiqFsWMebBsf2qEUIUvwcYQuKtR0QWiDT5LDAj2IqQfColoWp+QsrFKB\\n4pnEqplCbGx15YkWUrWiuSFDBnXFdkK5FlF3xa+nGsqcQ7XsmqA0XoNrFE3HY0pFtd2gYoeNHfXC\\n8smnvslvvPAMTaO5snbKnu0ze9TR3O8Sug4TBcLTU96xsce3nMZ5TT1NwCl8o8AG9KmlUTC3oqi+\\neu2AO2fnsRNFNQqEuRiOPdI74j/56o9hhmL3Ec2lmL+ZYBMiyLcCIBa14qApWasuEQVtNBfLXjuT\\nBdNZ6fiVYzUEBR6kTz3UW6sgsTLoL0rIC1gbymI+blAuxfUS2ZVUASxCH4yWnkFegsQTK0WwrMSJ\\nFmGIqbomhIDtdugMY5quNEdNVxMDHVujVKB2hsp56sRgeyVlYbm2fUw/LliLcw6LHj9/8TP8o4OP\\noVXgyew+96MRW5enfOHwOrvdCb2o4ocuv8gPdr/LTjTmqBnw2nyDWZ0wdzFrcc5LZ9vsdier8/+B\\nzRf5tTc+wPG4y/pgwXHZYThYcN1O+Iv/3X/IKPMoJ8Kr5U5ahYDLJP+2HBhMMSQ+mEvzk9vWhkSU\\n4KbwYlWdiwe9j2WmpnwbthQCb9W5Et5Ohb6RYWxog7GpxXKXpQpWKVCpCKiMBAbHkxp7VoggCKQT\\niCJC2RonOSedh1GoKJWBZVW3tC3L9Cq41BOPNXVPCoxyUrBdIlSx4mItN8oE/CXH8e+dg6XbZa2w\\nZ5pq02FKQ7GuKDckzBvXDqOcoloLpEeCEduJRIrpWgp60wu4qaUzleFv3ZfOfpgV/PzV3+U/q34K\\n7ULLgX9TITamzejUqHn+IKczBNSiEBqlUu1soeXZL4UoqRh+BaPwa11xE1QK1324iqmQxa33v4FI\\nY4pKunOjsKcVpjC4zKzwW1qL2GQciA8k99YnLSXvaNE6HcoD7rd7sn0tGzp3Pf1Bj2JN4TKorpZo\\nHfAzS7xWUI0T8Ipm4MAE3CiwvjMR+2AVuHx+j//tcx9CbZXQKCZlyslZj/ObZ9y/2ccNazbXpozS\\nnNOyQ+1ElRp1a9xJIqrfmcZ3PBSGnJTOoGBWJi0jpN1yTwwXO2f8/Mbn+Rf6mZW3D7SeJQ9gZpaG\\npA/Up+33GlY7niVUuIJtjHTvAi0+CCsv1h/yAg6SE6uUaDKUEvPNrrhNqslc1Nplg2piQhuLV/c0\\n8dTTOasxeS3PhVJQVVAUAktGkUCRdQ1lSXCO9LBHU8RUgwgemzPMCiZFwuMbh+wv+uR1xMZgjtUe\\n31E8NdrDKocPij+/+yJ/41t/k6c297k3G3Iy6vLN8UU+sv4q8yqmPyx5d/e2sGlCwth1cSiGtuCN\\ncg2P4jDvsZ4uaLxmv+xzOTvloB5wtshEmQxM65Rn0je4HPVWeQ/LwWqINLp2uDSiycwq8QoQa4fG\\nE81qgrZESkzyTOVX6XneSpEXFbnGZxqdmtY65s+QMlZlqWRMzhcrNSsgxas17lJ1a47UpjwVG4a6\\nbxh+Y9KmscufULe7AhDsP4oI/S6rlKrGSSEcZgQNdiodpimlqwyRDE9pcc/oNKJzT7H+Yk00h3vf\\nB7tfdux9SARNS1OtuiMc55C1RT4WzL4812BHBfp+H11DNWKVj1l3QRcKXcg5zS6JJ36xGVjUlkfs\\nIcPXG+LjXBazZRCwc4LH97tSvJcLofdtIXzTtQ1BsMKlaCyxELVUVSXSbOm8NGbxcPf45WZG0zUC\\n0zQBfWaoRgInReOlcjesGDhBiZlYNVLsftmJh42T4bue5yIIU1IF57sJwST0bpfYoxm9OzHJxDC5\\nHEkXD2AC1SxGVS1mlrb3ZmGYTDtwJ2P0IhzM1/Af9ax9PuX03Y7DaZfINrggi4ONG0ZpTtFYXNCU\\nheXS1imXd+/wudOnpYqP2jSrQqILy8JSFBZjA8W2FOB6p8bg2TIJ5rWM5KRlxLSGbEuztGoYpHAa\\n2QHqSq3gRFOqP975t4uCi1U73JavN1n781l46DkDwCrfORoXcl9mc9xAdolmIhqO0P4yy99pdt5g\\n55qtr8xkQYjE4CsUbXqGFmuPentA2BlgD6YwnqLP5tiioRwNAVhUljhybZGX56lnK5qgKaqYO4sR\\n39k7h3ulx6fKD9H7wBFf++yT9N5zzKuLbYa2wAfNTm/KyC64FB9zu9rg9WqTG8UW7+2+zvdtvcx/\\nWf5FIu252Dkjd5ZpneBbBg5AFtesdxfMyphhP8ehcMHTf8OTHtUrsgNB9D66aKiGvdX981YG2MEa\\ndNPGKBqF9g+0H6IiNi27Sl7sciizq6DEReCtHG+LQh8i6TrDIscvFpjRcMV9V0tIIQT0vCA2iun1\\nHvNd8ZFXfovBZ18R6qRpPWB0q45tMTBiK/TM2YLi8V1cqrHTht5t8ZlWHtIj2WIuvWEADt8nL1mT\\nwd2PRmx+y7Dz1QqzaLDjDvWmIxQKXWhchph3Wc/W9oTD/SHlboNOG5xTnH2P7DqSu7YVkijSY/ns\\nphsks9Ipys1As97QsTW/dPMnyV47RS0KccRUCj3JH7BsgkBRy4dJtQEswbT00uUgVCmIabv+5TAa\\nqANmUeGTSDyAHnLjN7kS03QUa6/U+FQRkhiXCNZutjLsWYmPRUxFgGoUaM5V6G7FnW6P/k0pVnZk\\nCI+cl/uioBwo5hcU8QQgIemLS6dvTcD0OCJYwbLtOFp1zfo0JuhAdblCv5Zhp4rx44Hzf+B49J/W\\n+NgwvRoTn3c448WywHq2RjNGSc77tl/k119/H5e2TrHaUbqIx5+6A8Cre1sQgU4b6rnFBUg6NcWF\\nCn1mcTsVu9tjNuyc//zoPXRvQ+fQsdjWuHbI/Mcop6YV1bXxeS7lgbrYs0oX8uaBjURoZx26lGbB\\nW0AHiSd8yEe5meFSTe90IYKnRBS/LrOoYU9gRC3PuXaQb4qvUtN3eDti85sLfGrwSQ/a3ZlyHp9E\\nTK6lmDLQUQprI+bXhtQ9jfJQH2TMt6GyjqNTYT8RFNN5irWORzaP+doL10jvWXzfc+6LDvWVIWob\\nDreH7A/GK8/6nXTKY9k+FsfHuy/zy0cf5enOPaY+49h3+Ymdr+OC4quza2wnBet2zmHVwwXFVjzj\\nsbVD7i8GPLlxwIdHN4nx/Or0HL3bOWZSUu10BVouXKuCdURzJzbEQ8mdDdbgUzFvVE1Aa9kJmMK1\\n0Gu7o0s0eIgK17LP2oW/fmv39m1R6EFgBb2+JsrWukFlqXTjIQiG3I3BRpjjKXYrA6SbHl81DLY3\\nYCJ+8NhoZW2M95iDMQD6dAJZSrEhgbqm9KvtU7QQQ62ly+PSJvfx991i79ev4C1c+RcHIkgyIsn2\\nCcT9CvYtTRbId8WZ8cK5U54YHXA3W3Drc1cY3BSDsb2Pevq7U2Y2Q+8nqDwI4yILVDutd3clNxMF\\nH9m+wW/96vfR/x7P4Macuh9TbFp6r4M5i4ShVDeCx7dD16CUDLWqN3Xm6oFaNqTCow9ao4uq3QWA\\nqYVqubSSfVjHMj6xWDfYuccNU+pMwsJ9ZOl68TD3MSQnAk/NL3qG3ZyD3ZjFIsGUUI6g6QXwCu1U\\ny8kONB1F3VWUw4h8u6WmVg863mhqVvGULeECl8KHHrvJy3/4JFUfHv0Hr0qewdpAusywznZvxo29\\nLYaDOVu7Yzq25oPD1+mZgh+99Dz/0x98H9k9w90Kso8d8p7Ne/Quldw83WC2SFCxJ8lqLq+f4tcV\\ns92YxhkS4/gL/W/zr3/q50m3FFEu+bTFpnD7VYsIqkYR4oBPxNlUNXJuqnnTYqxllkSQc1LhgRWE\\nKdouvv3AP41C71smTL3Zw+Q1IUtoOpamK5CCPVnQDBKqniY7rHGxZXYFiD3z84bOYbqaK9Rd3bqX\\nhgesOITeXK93mF0wIvcvAC3FsMhjfKMIjWwXnJZd2zsH97j1wnVcAtf+8U2BlQY94tMexz+geax/\\nwN18xKad8tHRiwxMwWP2lBrF31z/Q/69F/86+wdDQqP56fd/kR/of4fRcMFL5TkOqgGDqGRkF7y7\\n8waPp/c5Gg6ogyHRNe+II/61z/44w/fHrH83oly3LDY1w9cg9oGQxZIl3UJZwShcx7Yduzy3qhWF\\nNt2o3XHLe91k0oiGSkRoLpbv/7PFo2+tCoLWMpQB6T6tEg68jUTViQyBOrfGFOvrMqgrwQ0zVCfG\\np5ZqaFtnPEV8UjxQjsoPryTaLtEkZzKAjaeBuqtIzhzKi1T+5J2BcjyEVNF/w0lcYF7ya7//a3zk\\nH/4CLgkkccNsu0GV4ixosoZnNu7SjUq+8DvvZnAzMLmmsHPF5pcVk+sjwsUK13Po2tBsyGDQTAxu\\n1BASJ3txE7hbjIjHgexAREcuE8qV61qiTIzXwzyHbkYzSDF5O38Aoac1ru3uDWqZ3NNCHyqIzFwV\\nlegKaKms6uEWhKYjXWYxksF2NbJiYJYtDcni1t8GbB7ovtCwOJcwyWqUEZVxiGQIWW0JNKJLYUXh\\nFc6HtjMSDr23okpWjQjU7FxRdwPpTPDtci2gn5xxWPRoUsXoZstFznN+9bnf4Af/7i/g4oDRnvXR\\njDQSyGYUL3g6vQvA3//tv8zu1wPj63KO1e9s8Zn3DXj88h69pKKoLL3OnNoZxmXK+d6YblRReUOk\\nPXtugKoU2b4E0rhE0XTfVJCDYO8ug2DFH0IodtL1rzyANNRdceDUtXzdW4F/TIHQSFv66cPeqUFb\\nrAxUI0taNvh+Bx+LNTa9iKbTbzUPYArH8OWK+W6fedIatGVCbxa4rg02n7d24TFklRS7N7OtKgv2\\nVFPYFLzC9Gv8tPV6H9R89Pqr1EGiO3u3RSEfqpr/8Su/wQ//vV8EVbBpZ2zHE4YmZyOasWsmrBtD\\nGTw//pW/RfrpPvai7OT/l+IjfOudF/nxnW+QqoZEN2zaGS5oDpsBF+zpytTMB83Ml6iZobvnZfeR\\nKZoelGuGtJeIr49bquiXcFtr8OaFEaZdkKCbQfuutnBtk4r3l51U2KFkZy+9od7K8bagV/p+2mLH\\nBrxDZZlYCS8XgMSuxFPUDaqo6OzXdPZEBuzSiGaQsDiXsNiKxDmvlq8HG628rd2wS1S0BkEtF34p\\n4hDmApRD2UrbmeKJzQOSk8DpE0YGTMCz/+gX2PlqQTPwYi3r5YXMhgVpWnMpPaFvCrq3A3/5l34X\\nFFz8v444/h5PtenoDArMXAvMo9oClQRU5FdFvr8+5wufeSedI0+xaVlc7JHu58RTT5Ma/JJ/vDnC\\n91PKjYTJ4wPO3rdNvdGV4ONOQrPepdrpyvA1iLcIrZdIyGLc7powHGYL1DzH9x9u8kiTyUvrY8Ej\\nF5sRTUfuo0tgsa2oRtKhxxPh1vfe0BT3uoTTGB8FgbY6wqAhXg6npNoFIwybehBWvio+Ap94gg24\\nOLR0TYGFfALlwvKB9Vtkx56jd0SCDxvD9/83v8j2F07wmce1AzZrHOeyMVvxjHUjCUKb3wj8yH/8\\ne2SHgcv//A0mjzlCqYmN4yxP6WUlRgeyuCbS4n/vUfRsyTsH9/ilb/4kuhZfnvmuXg1kXSrPpPiW\\nC0mgs7nAX8/Jr1W4OKBqVjh8NZIQl6brV1F0IQoyjB5Jt+cSKR5N9lBvK9DOpGKhPIZI0wwTXNrS\\nYjuafCMSEz+tViZegzc82X1DfCpwVN2T8GuXtp5GtLsSJUWyWItYbIl9uDxDSDh76iBxuHkkVhD9\\nhk6/ZFyn/NjoG/LOPqVaTn7Cx375l9h6bkLWqaiDYeESBjrnanTKeqt0TJQm/XSfT/7sZxnchOv/\\n8x2CCRzlXaxyvFGuczE+oaMr1qMZI7PgfHSKVQ0jveDZ7Db/xst/hWiume9qZhdjiZ4sRW9TraX4\\nxFD1LXVXdnGnT2oO3t+h6ptV81kNIvJNEQOWA8lacFYao6qrmV/qiKYgEr1Ek/4Zgm5c11J3Ikzl\\nSU/GhH53FQ7sBx1cLyE6mqHGU7CWMF8wvXwOFCRjR7lmMYUnPmsohnFL1xMKUjNKMaWj2unSdESR\\nCaDmUtxNCemZIyhFORQ4KJ4Fohvw8v4TjD8sKVD3/tJlkrEnOwrc+ClDdKppbAQ2QCmy+0uDU7Ty\\n/K833s/iMfj8T76Ta/aYm38nofM1Q7UWuPLkKfd+b0h27Jle0pQbgeyuplrTuN2SKG748PnX+fTN\\n91AMdZucZLDzGDuTAWXILPVwHW810bSkHGnm5+R3n1zJSI9TojyssN66N6Bzt/UQqhsoSspLa1Sj\\niPLpHv3bFekr+zS9h8u6qQeeEIGpZPEsNtTqhQWZTZiFUAZNKQtwsRmIFhpVQ9MPoheoFKHW4vLZ\\nb1CFWcE0TTfg+w5VadkReSW5ARWkxzKgLDakgERzhbqb8M9PnqX3V8aUs5S7//a7yA4D6XHghV/s\\noceaw3mXtU5O7Qwjm/N4ugfAf/T8JymfUnzxh6+zwy1e/C926H/NUA0NW++Z8fzdK+RTTbNT0V9b\\ncHLWo/Gax9cOWI8X/KXhN/i/kyfJhw3zrsbMWn2FkcWprhRNTyBAYs8TWwdspTMqH3FnPuLm/U18\\nacAp7KAkUlAtLPE4ITmDRQLFTkPd07iOh9QTphFN9+FPY8uRah01FUlsKDYtTaJY5gvUPZmhRLlH\\nVw3BKaaXhPhgZ4Fq0HL+lwFwCRTrapWxHOWBcqQo1qWQeSvzBzM1eCfDaRT4YYOJHfk85oXDHX7u\\n6K/x9M+8xN58wGt/+1GSU9ETHP+nNVSWcZNxOTlh7hM6yrFlIjSan7/zcabX4Ys/9hhbxQ1e/K8u\\n0P1OxN5smwuPnfIv83fz5cOrPDE64B29u7wy22GapXwou0miHOdMxlOjPW5c2WS8HdG5EZMdyu9Y\\n9xWLHStsvlQYfW6jJpxzFJVhdiUi20tWQ/O69b+LcslYiCceFxvmFxR5ZaRBBZITmYG8leNt0dE3\\nWUSxbhhfi6mv7cgkPrYEG9EMUnyspTNv/WxmH75KNVTUHUXdNSsfk2WKUtBKfFa8WB9Xa2KX6hJ5\\n2MqhEsFGLN3kMo2p7qkV+8POA/m2ZMXaU838QmD/4w0n7/GoQgtjYhwJi6PSnE06LJqYX3/9fdSN\\nbE9nT29A3fDIv38gae428MzoDuP3VDKQrMCO5YUJJhAqTZI0GBXwHU81UHQPHMoHqqGc5HzXUm6k\\nNJmRnYmWlKVqECg3fRsNKFxlOT8leoM22YfGEUIgPsmZXBZp/fycJQy62NOHa4ris4DvN+TbnmJz\\n2cW1EEtrOeFSmYmUo4j7H06p1n0blSYd6oph0iiJWVz6ASFJPD71EHlCpyHuV6jY4+NAsC10FMnn\\nrPJo5wq1UzBfJPhFxPRdJeqvHbL4xARlZNg5HncomohpkXBrsc7CJ/zDez9E3cizlr/jPKFpeOJn\\nb1D1AQ//5vbvy2BWA4VhMU9RKqBUYNHE9ExJESwXBhNMr0HVCtcTEzffdUSjinqjgUEjMI0Jepjo\\n4QAAIABJREFUDOOC7+m9wcdGL/CO0X22Nyb0N+bYQUk3q7BWeJXehhV0QxRQ5wtUr8FmNSF1K9/6\\nh3m4VO7l9JJmfj6m7shz6GIhGCzN1VCKZpBy8OE1ypEojuu+ap0u5bOW7o7A6utVX95Hn8h8ph76\\nVdcfVCCksiCqSFSqIShmZxkf2L1NZmoar7ny0Vv8hZ/5Au//5Le5NjrGe8WN2SZaeaY+5WYzxBH4\\ndD6i9JL2NnvXOZS1PP6zr7bB3J4PpyX/zu7nSKKGcZ1yu1gn0vL/PfMZu+15vKtzh931CSo31H3R\\n4LhE6K2LXU3dl5mYj+SZGQ4WrG9NYKukXJeIzbonOzFvRUBVZ6qNV21hrt1A3QtyTQbAW4Tl3hYd\\nvWoktLruKw7e12X9JUt8UtD0YvLtmOSkBg2qmxGSmGIkuJzyrQWqUmRHNfHBDJcMRCI8LXCjTvv5\\nQV7iRi5yvimk7ekV+YzkVMv2vg/xWGEXjrorxXx+uX2ghhWRCZjbMfFkqWAEdSqXsJlZbuxt0RQR\\n1y8fcHajz50fVAzP75DvbqNL2HzqiEfSA1TsmZ9T2LkEK08f8dKBaeilJXt5HzssKTYNJ3FEeiR3\\n8+yRmGJTMbts2fpmg080dn9C3e1LwWyHlD5WOK/I7ouaLpo3q+2z3xig7h6g5gVrL9fMLkRERaAZ\\npNSDhx8kbVKH04FZEmHmWoqwAlRAlxqfyLkdv9NQDf0q+JoYKVqVQdVg5hoXi7ujqhQh8ag2kAMg\\nShxJUlMXEb7n8P2AN1aCwfueaK6JFgJ5uLOY9Utn6AFcGpxS+Yj9vRFqGuETKRpH4x4hwFmZ8eXx\\nNZ4/3OXqxgkHr/V57ZOa4ROPsjgXiCdw8YducSVaMEpy/PkCdRoT9hO612WQG2vHejTnzHcYxQu6\\n3YIqbihzCyow6Bfs9qeo3cBLr5+DjkPNIlxQdHTJRjTjvd3Xma8n3JxucIiYaSW2pkwsda/1XloW\\nS6+IYpk/JGsFdffhv+bLoBqXBk5TTTxhNROQBVUajrKB6qkO+VabvxAeQKZRIZ37yrSwjZJ0sbCK\\nXFvcXBLE+8krwrmCTlZRLGJ8abBJQ11EhHmEXS948Wybj+zc5LHzB7wzu81hM+CFyS4H8x7Dbk4n\\nqnmj3MAHxWPJHi9UMS+V5/j+tZf5xtHTvPGjnsFjV1jsSI7x3/7ez1KEhh1T897123zz5CJ78wE/\\nfuFbjMx8ZZesUXwge53750d8NbvCH926gD+IZfEd1ky3FJ1XY1wf4gnkCmpnSG3DoJ8z3lUwtZIN\\nq0Sg6Rw0XYnT9K39ga5YMW7qflh193/S8bYo9FHusPPAYldhLOx9IGbrW5rFtqFJFZ37HlXUhG6G\\nzyydgwZTGpKJ4+wRu/KOFz6qyKfxYtwlfi9LJQqrrNT+7QZdRVQj8RNRTrznTS2UNjv3+EhjXtVM\\nHvXEN7IVnmpnoR3a0j6AEJ1JOlBnrLhZ7HLlJ+4zORqxuJDhksDae495/9Zt/t6XPkHn5YRyw+My\\nkftne5r5VQkC/8DWGzx3cIX6LMF0AhWtJD4yJOPA6btFkLWXWna/KAPW5DSQb4sxmGokuMNHitk5\\nw+CNhmharrjy8eFcKJdVTefGKboZoVyg6UYPfWinKlGNxp2ayivUeoGfxehMipCbWMzc4DLBm0O3\\nQc8iormi2mxQiSMUWoRdSUDlRjBZJV29zwS315FHqUASOaZehts+ahdip4hmurUVEJ46yjB+cYP4\\n2pRvHl2WX7bQmFxmM3lPkwxqnNPcHQ+5ub9JPU4YpyU/9LN/yO/vP8r+ZAcfBS5/+BZ/68Ln+Q9u\\n/TjfvH2RXq+gsI7qLGFy1GWYFWSm5iPdl/hKfp2jokc/LcmNw1q5Dj4orvaP2bBzam+4+doOIQrs\\nLwbUo4gz12XhE6aNtLT9tGSSpytbZLdRUU9SsbYuNaGMqaOA7tXYrMJ2y4d7Y6FlqEG9JgPj2aYn\\nPjI0HZmfKKexszYoJxFqqJ2KZXe+I/OVMJV74lvV+NImxHdkGO0tYvnQdszxROHKlMW5Zdi2os4t\\noc14qE9TTkzgXy7ewfdfusEXTq4T64bKGSaLlHyScqE3pqMFq3+l3OW5pstLsx0+vv4iv/7v/gN+\\nd/4kv7zx/Wiv+Nl3fo6fHr7I54s1fnf8NNezQ8pRxL18yHPjq2xuTIhxDHSKUZoT1+Hd2RscVH1e\\nSHaoRho9jRiszemnJXfsGunLqSxk04gii1EqSISo02ACzcCtbKWDgXId4olAfKaQWiCZyzK/WtW2\\nP+F4WxR6QmB4Y0E8Szl5KiJYmF0wRAuIQsBbje+nmOMpZrbAphFNRxPNHfEkou4qppcT1k/zNmey\\nHcS0VKVqFBFPHNHE0buraRIZEpXrD0Kle/cb4nFDNK1o+oJVZ4cQjUs6+z3uf9RBryG6H6OC8Jar\\n3ZpsLcdGjuntAfGJwZRgJ4bDaZfmLCZ5bEa402X87Q0+dWOdjecVdaelp9nQumQCjeL7H30VHzRV\\n0xa0IIKXYlN8YIp1xfC7MHm2YvCeCW/sDhh8e5PdL81ouj3KtXY7HMRTpBoozq5HeDsgu1+gS0e1\\n3UUPU8xcfEnw0lHFhzmzRx5ytmiAMI+oSoPuNGSZODy6yqBjh+o4nAady0OOBj9oaIhk8KpArVfU\\nVtK7VCNeOC5GfE2sR8cOYzw+KMazFJw4f6pGGFbJKWQHwj8WibkiPYZkEpgcDug/e8pjG4f80Z0L\\nuCojHzhMp2G9k2O05/7ZgKYSEdSijJk0GUnU8PE//02+cXiR14/X+a+rH+DOizuEbsM8KLT2q52G\\nVoG/sf15UuUYNx0WdUwAUttgdKCoIwZZwfMn5/jE+e/w1y88x3ODazx/co674yFf6j3CxfSU0kfM\\n6oTDWRcFrHVyTuYdQgClA8WlCj2OMAvhmzd9j59Zcq/o9B5+oV+G7ujayCwm9tQDMY1DqxXMYmdt\\nc1UrGaq7B/BosRlWJIuotXKQn1NteHxogzsUqjF4GyQPwknzpueG+I4lyluX2G7An/TQM8Xv3HsP\\nP/Hsc3yod4PfPHovB3GPdLOmb0vWI+nEb+ZbnNXSEqeqQhO4nuzzqQ/+Y35z+m726wG/ObvCr939\\nIOe7Y1x7UxuvSY1ArN+TeMBQByfeO2bBn+vfZHol5YWTHY7Oepzt9bnwxJiPPf4y39ncZX9vRHRo\\naWaWuZf3XJrQ1oJlvUHlGlMItDy9qrAT2f0o15rd5TKnqIZ/hgq9LiUgo3urIeg+kytSMOOZx86d\\nYLibGf5cl2jhcOkSU4/o7DfkW5EUxU4spj+Rwg+ylWCIIAMiXclww1RgKmEruBjaCEpcrKl3O63w\\nRLD++fmY8TXNxtUjIuOoNgwnR322fzdm9KohOcvINyPyp6Bad+It3fM8s7PHC3qHxesDzLkc80IX\\nO1VMr8okfmletdzGqo2SdTvny4dXqZ0RuKho+e/6AXPCzqH79Yyjpw00wkEvtlK6ew7XegFFOXT3\\na5ni91oFagj4xFCsW+xMy06nn+Ays/r7zt3iod5X32mnS04RjhOqpCEERSgMrmn57rHHWw+l8ACV\\nCYT1CnKD6jSEFq8OgKqFUoYWTj1ezL+8lpQw10SoUmyGgxF8f0nXc3FrEOYEOppc1syuNnziwk08\\nig9dfY1XRlsUv71DuRZxOutQrUFxoaa7sSBea1jr5Pzw2rco/fv4yt5l3rN9jz+48Sh3FmvE5+a4\\nRnKGlA7YfknwmidG+wxUyR8sHmPhYzq2YlKmuFb6mESOqolwXvGl02tc7R6TO0s/LqmaiJfOtpn2\\npGpOypSmMTiniSNHXRu8MygTyIYFuctg3naDumUmlYZFSB7qfYUHlg2mBH2kyROxiJaivFTlCo5v\\n5y0mH0kxjuYPKKXeClwDUHdbLrkHHCgNeLnvphWJKaeEduokbCREgaar8DbIIgOU64Hs3Izv7b+C\\nC5qPrb1INyr57Gee4cuV5Q/KRxkO5mx2Fjy7eZP1aM52NOW6tcTqiE/Nn+Ij3Zf4J8fP8vXZFT66\\n9Qpjl+GDwgfFI70jtAo8k9yhDgqtNItQ0VWGIkRciE55/+B1+lHBwbDPc69e5cW7u8y3Y2qn0bHD\\ndQwqNyuxuyqMFG+gQeidYlMSaDLZISnXDrtbiExXEI/f2pj1bVHo0ZqQaFRZk5zVxCNNPPOr2LFy\\nPcHHYgSUeXmBkzOHt4rOG1O064mVaSJiDeUCPjboyhGfltiZEcpl5VAuQgPxSUF6ZMl3xHCsGBnc\\njqHqK/Ldlvq41mA7Bb1OSRI17B0PyToln3jn83xKv4PrvwJ1LyLKPfUoYAY19lyNW8T8yOa3+avb\\nX+Hvqk+QvzCi7stqnB4rZtcbSB1qGex7Zul2S27nazReMzvLxFsnDq1niAh+VBvgsPl8hZ1Zmq48\\n2HVXE08c/Tutn87ct77Vjt5pJfS3rugLTBUwlZfr4QImd5JBayWo+KEesUfpAHOBhepKQo2BVUEm\\nkesQSi1xjtqho4ALCp9Hq84dEwiNkvqeeFQlzo0OcE6KPk5BI51OiIVeWY4Uxabgms1WBZUmXisY\\n9nLeMTqmDpqv7F1mmBV88tI3+ZWPf5j1f9ZletHQuRcoLsD1jWOudo+5lw+5Hp3wS7v/D3+n+VE+\\nf/MROt2CpjHkxxnnrxzTj0t0i4Htz3psxTP23ICFj3l1vkVqanQamNcxeW0pW8GP1p7jvMNx3iGv\\nLI3XKCCvJKEqNo7TRYb3iiSpaZxm2MsZzzLqMiJYhc4aCYv2ch3EPygQmofPuRBYRRoPFcTKY2nT\\nEBVi6eDisAqEkRyJVr07h6jNSA4RuNZjSjWygJhKvqfpqJXFCLSY/lRTJ2Ia1/QczQCwnrWtKYsi\\nppNWXOrOeXbzJnfrNT57/AR9W/LD63/ErT+3zs0vXSZcLjh9bY3BUyVPZXf5QHKXV+o1IgzrWvOu\\n9Db/5PhZeqakDoavjy/x7NpNeqYgbmXWU5eRKkeiMjye12pNRzWMdMWZj7kaH+KDlmSrXkm+1+P1\\n6Y6cSKsLM6UIvnwkO3ZUe60qJTGjQXyvVCMLWTWUa6xbuU3QrFTff9Lx9ij0IIUo0pi8oX9X4xLd\\nqlcV8bimXIvp7gnUYApHNC05e3rI4QfW2PjOHHpt9mJovVHKRsyE8hp1tsDEFuUcpm8luKR2wuU+\\n70iODKAYvVJz9phl7Tv/L3Vv8uxbduV3fXZz2l9/+9c3malUqpfKKiO5VJ0tO2xw0dgBBAGBwQOY\\nMWEOc2bwF0DUAILOxmXCNmVXmSqXo0oqlaRMKaV8ma9vbn9/7en33gzWufdVDYDE8YhIn9GLd+9r\\n7u+cs/Za3/VtFKNnDa9+KaEZW9bXYb1J+fb9T/il6cf8eH2Lv/aFn/BB/hWSi4aXv5xhJyXv3Tjk\\nO9sPqH3Evz18zgetBFGYWjF82ithE8ifibmRUA1lPH135xirPJs6Jh02tE8HAu04RYggtPLeiheP\\nZnDsaAaaZqxoBor80BGtJDv30mPDJ1JE4pONWKZmBhcr0rJDRQZdd1gf6IYx3mrs5g2P+E7MrqQS\\ngF9FYvkbpEtTKDwQBOZFrwwh1ti9ArvV0b4YCKvG9cVe94ybKKBqGW1B4xP1mqFjAnqrZpDXLA9H\\ndI1i/BCWb8Hog5hkHjj7hYzTMmIQNzyodviLNz/iV8cf8kF5i//o8/+c/zn5LuOnjhe/orh/74i/\\nsvsTvjv4GYduwOeilDNfMrACQ62fjQmpxww7Xh1NOctarPXEtsMazy8OPmHpUxZdzlZc8GC5e+Vn\\nbvrQcB8Uo1iW5a3XFH0+rTWeqopoaotzGt9qlALXGbanawZxg/OKRSfeK6GPFsR69Dq6iqLksqF4\\ng5du5fD0RgpPtNQ9RNMX+xLQCtXHPEZrAEW5Hyj3PcMnWmwd+k7/EnIUTQtEq0tFcG/drS7hGU8+\\nKykuMtCK9JWlut2w+ukW6ali/qWaqon4MD3Ah2v85Z2f8gvpY35Y3eZv3fgD/otwG/0iZfyFM/7C\\n7kO+k75gyyTsmw0dikhpDrsJpYt5WU6ItWM72fCT9XV2kl5LEa0Y6YrbVor8uavZMvC4G3JgRG8z\\nUA25rrmXn7HcyfjReY6qNaqVKTTYy3xphXXSwQfLlegvmIAbOUxj+oLeM6c0mA2oPnDnz0QK/z9c\\nn4lCX+1KUpJuLfFFQ3JcoC9W4s7Y2/HaI08YpFcS/hAZhs9rqt2Y4nrWj5Ge/MkSn1pU5/GxwWcR\\n2gWUc6i6FVgn1rRbGdrB8InE/63vBMo9S34I2YWj3I2Y/czTZorFdUjSlp+cHvD+8XUWFwNx9P2b\\ngfRJjsuFwvbBk+uclzlf3HrF71cDUt3yt9/+A35n9i4/eHhbtuobzeC5KGN8JxhbemPNosmoO8mN\\nrTcxYdKRTSvKRUpYWensOxnnFnctw1eeyYMNp18b0o4Vi3sJg8OO9LDAzMU0ys0GdKNEcPnaUc0S\\nin2NtznjT9boVUXIYkzViUlS9Gb9bM3c4sYdpB61tJilhLRc0mGjlerpoYEQSSaAbxS1zQS2uXQB\\nrTXBSU5q6JdPwQq05hP5s/GsolnHkAZ8p1meD2QqulFxMYyxK41phKs9+khcM6t9y1Ze8v2z2/zh\\nyV1enk1k4vjLDfHjlBB1FG3E33n1NR5Od7mfnXBgfkZE4D/d+x3eG7zi7736MssqYVMmNGe5RP9F\\njiRq+bXrDzh3Q47aCcsu5bgaMooqbuZzjusRh5sxk6xiVcdcFBnDtGa+zmmqCGM9Ie6w1tG1Mgnp\\nRSTiqsRy3Gr+3FtP0CoQGc98naF0IGz01X7AlBoX+zcteJa/u+qnpEEgXsrewxa9v44SvB0FzVAR\\nrLryW0/OxV+q3uLKZ59Gfn2ZnuWjXvzWu282E9lleS/PQPlqCLkjmlbUmUWtIkytqLcC6YOEYBOO\\nJitmScH/fvxlfouv8PhiRtcZ4i8sWJ/lLFc5J82Q/+bs2/zi8CFTXfCLSYVG81fyQ27bc357/UUK\\nF3PSjHi42karwMDWpKrju4Ofo8kpgrTUhy5hoBp2tSKiYRNiPp+8xKH5ud0nnVbURznRStENwFkR\\n+plKYRvJtxDRlOw96vsVodM0O9LU+Eb3RILez6iTVLVPa0H9meDRo6QLVy4QIo3LItqb2+K94jyq\\nFKGPnq/hbI46PMEsNthVjXJBEmsm0lGoxRpzsUFVrSSwRIZ2N6fbGnD069fIHy1ITyp5qBRXyT1u\\nIJ23i+H5X+oNzlxg+z94ym6fJHRxMWS5zGBlSZ7FjH8Uk5zTe5JrtAkUTcRROebn9XVetjN27ZJv\\nTJ7xlbsioQ8msHzHXUnV0XBjtuDu8JxhXFMWMfoswswtzZMhdAofe9qZEyVkDs2sV4DGhuErR5dK\\n8PLmmqXZzUT9WlbodY3dtOhauPjxUoI51jc0i3eG+FFKN0noBpHIsN/wFW2kSF/+3Mr3vPne9kD1\\nYmDdSYxeciasl2huCJ1GZQLe2o3uqZh9CEglFLRuEGDUsX/7nO5FjioNwSts5ERpbALDUSULSgfF\\nd9ZXFL5v/Zs/4sZwwel6wMlqyPFiiFtF2JcJ4++nZEegguJ8OaBsI36yuMb3l3d53E5Z+Igt3fDV\\n7Anf3f8ZAFoHZvcuCLWha4U2dzO+4MAu2I8WnNZDXqwnvFxP+PH5DTqvGScVe/mKQdwSGUfdWtrG\\nos5iWeRHHdZ6We6G14eccgp1EfPjl9cp2kiKvAJtAtgAkReKadwriv9/uKK1uLPCa8y4maoreuWl\\nt5AtIT335CeOeBmIVvI1l4l+4jI4xvfPgynlmahnino7sH6rY/BClo9XcYEecIrpqIR1BE4x+MYp\\nuhUDtd/4N/6At8enHBUjXq7HvFiO2axSqpcDmg8mRMcRAfjw4oCjesxvz7/ID8q7vHINLY5cxdyP\\nKr6UPacNhrEt+e7+z1i0qexPTMmOMdShI1WWly7msJtw5gY87CwtiqmuSFWHwXOQLrk+W0CA5EIR\\nX2iIAiH2MtH3n5nUQIjnYA4TqDV6Y8DIs94O5b1vRrKz8xH/cvHog1akJ7UAVz5QbyfE84aQJqLk\\n7DrxX58MYbnGr1aYJEG1CYMna7p3x714qndtRPjgqhWIp97LWd+I2fvdQ/wox6xqzPmG6MYu9VRR\\n7zjis56iNYbsheHic1B9seTVBzeJVppu2xGfGKI+7GH1tgCO9X5H+tLiypjuWo3zGt/PUyNTsvEJ\\nP1jc4rzKCTbw1tdf8PSf3r6inLFd882tJ/zdh1/GGE84S7CVYJM+CldsFHsuXb0tINTCqtGd7DGy\\nYxGhuJje3kE6ZdW0tKMJ6xsxUekZfrLG3h5LoLZV4ALNqLcNLhzd+M1aIHgTiFaaJvG9CZfHVJqg\\nw5WI5wq3bfsukD5DdWVh0kLq6TqFKTU+Fexdtb3wJPfs7i45XwwEr48CVJqWCALMdldcnA2JnBQW\\nnucUNwLm7pr/8/FbtCcZdqfEHebYjSJfK4o7HaUzNHsd6cuIpsk5VYFo5hjZCqM8iXJUQfOj8g4L\\nl9E5w6/e+Zh/9ODzqMRhrOdz0xPeSQ75x8sv4IPiyWrGukrI4pa2t1gY2pqnqxkuKKomEvZQq9Fe\\nuvH5+QATe4IHtbKExOPsJXMlEEWOqrU0VYQ2QSAwE6BTROOGVkfihvj/A3TjY+lCL5WZ3aCnR1qF\\nuoyHdOBTiDeBaCXqcxcHkjNFeSCiuXqiyE4Cqo+GvPTub4dQ78l7qbqAqWX56o10w6PdNWcXQ8n7\\njTwXT2aE3Y7sxpJ/8OQ9lsdDRntrVocjUSC3Cn+npIlj4lmFP8l43s2oOsvb01Pezj0ORa5i2uB4\\nvxmj8TwptvjXd3/I3zv9KiNbk+iOu9EpE51x4QoiNJ+0+8zdgNvRGZsQs6saRqrj/UYaPYCq6x1V\\ntTR40ZkVWwuniFb9FNMHqrsYXOZRXkJpXM/KCTagCrGxvozBDJ+yP/tMFHqhKijsvKSbpMTzBt2J\\npa4b5ujEXgVNA+jhQOwRUos5XjD50DP/0lTUj6OBGHT1tr1eiTPc9u8+k5xGEN+ayDJ6XrO5nhGf\\na4lk6+XYxXVPSB3ZzzOSc9jclLDvS9+QZhzQlRKqlwpU92ri5zGh1YzSmndHR1TB8s9Wn+NXRx8S\\na8fzwxmqUzz6o1t0uw7GLW/dPOE3Dn7Mq2bCIG2Y/3iHqI8wVP2NxSmwAZd7fKyotCxn19cNuk1l\\nwTv3RH0QtF01Qi/1EM4uiLZHZIkwb9Aw+aSh2o6Ilw6sxJitb8a0Q42p32xB6IbCxMApgvVEi16Q\\npLgKy0CLAZd4Dsk9uORcdz6CWUtIPZSvJwM6cbHEBE5eTQTfT8QSwVtRKuu9isUih7IPNgmK9lpD\\nlHZ0zwYkZ5rmVkd7kaIRXnI7Fky7vS3BJe7dBv00x3WG3WzNlwYv2PiE77UzvpM9JlKOH13cIItb\\n/smjdzjYWjJNS/7S7of8teFPOHQDhqbmf/zk6xIIpAPOKyap+OAkpmMvX1G5iEh71nWMTVvavBcE\\nFhZXSjHQba/ZAFmumkCxTgj9Qt6VRvDaVkPkcU6jEyeFXr/5rr7Le9fXUop8evpaMIXpbZN7EzYJ\\nQJdn0CUQL+VAL66LUvZyCXmZk6pa6VzzJ7a3a76EhRTxUlHsOjbrFF8ZTKsIXqH3KtKsYfFsIk3b\\nrYbVubzvbuDxmcMAs+sLkqgjmS158myHurXcyOZ8LX1KFQx/VCu+GntGuuJBfcB7o0N+++ILHKRL\\nPp+94t8ZPWCsU9oQeOkUf1zdpAoxYy1+9GMle65IKW7YJZtg+c7oIxpvWRQZ9ckYUyvsWmG00LtN\\n06u4kYMuGCQzeiSKbqFSSoMph6G6UpR/2uszUeh1FzDr+krkBIBSFLdHtLkm2qRkRyUuMcR9XF57\\nbUZxPSXayYhWLcMXNdVWDEbTTfM+jATcMMaWHeuv3SB/uhSMfzyg3h+inYQENCPF5ibQQbXrCaMO\\neyz2w+1tUcuaqjdW6t0Uo5WkSfldR6gN9/7CUz4/OeJGcgHAnfiUO/EpK5/xGzs/pHKWxltqZ3ln\\nfEKsO+6mp7yTHPJ/nL7HcpPSXWtIP0l6dat0r9iATpz4e/Qb+nguB878bUu0Dmz9pMSlBlv2dsOd\\nk5QtYzDPTxicxISiguBJ3D7pi6UwnYwiqlvsdiTB22/48qnHVAa7Mvgk9MIv+X2voBtyZYOQnF8W\\nArFululF4esYd70mWINdGtxADmFaI66fWxKp5LOeO28DNIpuEQvE0UvO252ObFRTnuaoOFDc7rBL\\ng657qiDQJR69NoTYs7e7YFmk/Nqv/4AvDZ5zKz4jwnE3ml9RI/+14U+IVEfhEwofcy85AeCt6Jip\\nhr+7eosHmz1uTuc8OtkmihyR8Qxsg1WezLRsdEzdWDLbcrKUDyTf29DUEeGF7JGEHoywafqFMx44\\nSyjOE5g2RMOG7jRDVwo36L/VepxXhM2b9TCCfgnbBExvpWsLsY2+zHfwfeC5j8BWUpTaXNNMe5rz\\nJpAfKspdsdzVfRD2Jf/e1OI2akuhIKuk5+YrMOcRPjWQO8nE3akZjwrOT0cQe+rbHWplZQpMZLGp\\nrKcrLWYcuDc+57zO+c/+ld/mbnzKLXtOpDzXjcOYikhFfCkKwGPIgDG824vbUhVRh47nru1x+Fd8\\nr7xPEwxtsOS6QwOpMmg8hU8Y6JrjaojWnuagwywM+Uv9Z/YSV66k/VSTFgr6fYaPAvFCDtZLewmv\\nLhXIn+69/UwU+qAAFyRFPraosiGkEV2a0wwV2akTP+pVLc6Ms6HIpo1ifT1icKiIVi3RqqOb5ZKa\\nlBnsusUuSjb3xnirKG+N6FLB8le3DPmxp0sUq7tiENQO5bSMnohzYnPQ9SpLBSlXAibdSAGJVpp6\\nRz71G/mCvX4bPzUFt+05DsWBKXjQbvO3rv0zPqxusB8t+Elxg68NnvJOfMh/d/pL3M5kVH92AAAg\\nAElEQVQv+Ihd9EksmJ0S7w/lRBrugxEPFy2LxgpDvFDEZ2LKtryXMThqMasKvSrFk77rUINcYJzO\\noawBm6COzvHbU1Tb0W2NUM6THdWESH9qvO/TXqrrY/S8ou3hA1OB6wO/TKGka+k9+C951HajaGYO\\n30g33hVWdigrI/YHfd3ySRD6ZoCQOEKkxHKhsNApQuaIDyOxLm40zaMRKg7Y/YJ2leCtCHZUAJfL\\nqKwChLXF7WqM8bKAjRaMdMW2Lrlj5R93IbAOLd/JP+bEDch1zctuxi17zn3b8Yf1FvfjE36qr/Ny\\nOSaKHFp7rPaUXYTVDh8yRrZGZwEfFLvjiNPVgHKToHTA7zSYFzF2I2N6bQwhkslM6IyC27vSys9i\\nAtHSoLyiizyhsPCnYLI3eelOCpKtwhXkYKoAY3FaTOZS+K+MugayVxEnUSEW6Ebet3YkRUwOCOGH\\nX+pGfCy+R8r3Lqadwo0cdtzQLWPCoMM1movHM4gCo4MVm1WKtwGX9buN1F9RTherjGZi0CpwKzrn\\nur1gpFumGmZa/lFPQCv4UuQ49Q0jpTl1nh1j0AhXdFcrXGj5aXPASJd4NLmqWfVe0d537BpFqpZ4\\n4Dd2f8j/xtf44yM5zKvdQP5SkZ57iQQcqyvLblPToxzyWVzutOLVJbQpXb5uZBr4VPfrDd//f7FL\\nQch6K12rxfu989gq4FJFM7H4TH6PtkWvSqKzjSwvvGBb3cCSHG+Yv5PT5YZ2aDBFS0iiq6DkcstS\\n7GmOvyFFrRn19K1Etvx9WI1I8odSQEwlzI5LIyEQ75R6O1DvOEKjSbdLShdRB8t5N+RZu8XTbosH\\nzQGftDMcCqM8kXI8qncpXczc5fzm2bf5tcmHfHBxjfDREDdy6FahfK8e7KPv6LszdVn8tXTB2bkn\\nWTraIaxuRGLnHEeEQYa6jGas+0DwYU4YZpLr2V+mlFQl5QLeKFzyhh8H1U9ALVefoakUZqMJsafr\\nDc50JyIb7V7T6pRTQqe0kBxZVNbhtlqxPfBiNhVsEFdLK8yS3YMFrtGimrxcAmuuCqLPxT4ieI0q\\nNcGKjYLLvFDeUoffbbDbFWUTcW20ovAxS5cydzmPuxlPuoYPmsCTrsOFwJZ2pKrl3A0564ZUwfK9\\nesKX4jOeNDv84fM73J7OaVsROmkVmCQllZPdSGZaBqah63F7ax2hNPh1hNICJ12K4KK1IjkzxGf6\\nak8BkkXrVlE/+/eh4QsrlDzF1RL3TV/iKCnTZz1RV/9Pl0i+Q5cKRKc7KdTJ3Mmv3Wu65PC5pKs1\\nYynuupGGK+jXYjcfBbrblVAOBw7VKuKkFfW0V2jrCZlD5R3ea3xhRXeR9fnNWhqCaFKzNdmwaDK2\\nkoIzN2TlU+Y+5mVnufAlj7uCta9pgyALOzpmEzyRAoNC9/u3SGl+r3ybd+JjVj6j8hEthqluOHcp\\nVVAYFKmCJmhi5ThIV9AHvHsrVhAoiAqPLQLJIpCeSwCLafuvbbgSgvm+2EcbXmcQfErWzWeio/eR\\nwuUWNc575o0B78lebujSIS5SuMTALCNqOwmd1prRx0s290YEA/PbMd1XtnApVDsx1//JAn22pHpn\\n/wqWSFaO9U3pckwTsIXYBCTnUjzjOaK2jOSBYyUvSzPtX7aNppk5wkAeHm09xgTaxvJouUXjDXvp\\nmqkt8EEzMQW/efItvjl+QhsMr5oJ8zbnrM6x2vHt0cf80eY+SgWa6y32JMLFUvgkANriU/8aa01b\\nfOTxXYJvFeW2ZvZR1WsNOnTREBKLG6XoPMa8lDARvzOhGyWgITot5EM3EtqtXEBXHdnjI8L4DVsg\\n9Ifm5a99LxePVgqXKdqJQzcaH+RADxqCEqpel2tRBY48pI7huEKpwPrpmBBDslVSrxNQEgJuRi3z\\nVSaLx2UkBWZp+1zgXk6oIVQKvzJop3CzVl68lcGPOpJBg9ae2DqSqOO0yPnh4iaLQcaN5IKRrohw\\nGOX57eI+fy5/iAual90MFzSP6l2mpuBXslf8vJfWf+XaSz652BGRU2fYNBEXNmeWFGSmxWrHvi0Z\\nRRWVi9g0ESp1qPMYqhhdi197l9Mv33q9QN+km0qeFVtYuqxPpHKvKayq1AIBvuHLJWK/K2lsPR6/\\nlE6+Hah+Ou6/byAHt0sUydyLjXIqTJLNDXDjjm6kGH9k8RHUE4H5VJDDrZkGfGHlx241IXeUK7G7\\n0Bsjhz1AZdisrUB2s0YK4SoimjUMBxVGB2y/r3i+nvLD6DbrLOVmfMZYVzSsifE8aMfcj86ZanjW\\nRewa+GG9x1/K5qxDzURnFL7lV/MHvF9fY6BrNj5h5TIOdc5U10w15DpijCFVFT9ASBoq77BPLXYj\\nnbuLxdvfWyU+Xb3FgbdyQNoyYDfiGdTlki4VDKAhXn36rIHPRqG3Cm+0MEbaVsJFnCfkKdlJS7kb\\nobpAN4wI0US28FVHl8boWkbZale8uPPnlnI3UNweMHCO+HiDj0fii3NR0QxHFHuyJfKRBB9cjou6\\nlcQer2WJ2GW9JN8GVCMFP+QOkwpmHiUdbW2FwqcCp+UQH8RzOtKO827AbrwmUh3/bP4WRRdzXuac\\nr3MW04yPlnvcGlxwY7DgSXlAN3KYjSF04cqNUqUOXxtU6ogjB5GjSCLiV5bs1OGtZvBsg15V4D3K\\nSgB3fBFQOxNU2dDOUsxGwooJAQX4QUKwAnEpZzEh4D765I3e12ACIQlES0s011fFyVtZKLu8h6kG\\nHp8EdKX5M6TvAPFWxWxUcL4YMBqWhExcK+tVAk5hzyNh9CDBDdp6fOx7VpFMBARRFvu4t1HIeosE\\nDXQKt9WS5A1Z0uC8ZpA0LKuE6+MlVnteVRO0CrTRioGucWhmdkMbLA/qA067IRdtzmktB+XH9T53\\n4lPuJ8f8/fJL7A9XzKuMsrVkUYcPCqs8pYvITEtiOyLtGEY1D8ttQm3QnVBNle9FNDH49DVseNUp\\n9zGJqpazrBuGK0EONmAvLLxZZwu5Nfqyoxc3RoLEcbpYEa8k/lEFETsV+70l94qrZCTTQL3jiGY1\\n/jQlDBzNWCDUaNMb0J33Owk0bX8vfeoxg96JVfeK6v5Z05UmpIGgA0YHXGOw04ZBXjNMGlxQjJOK\\nZZ1yczTHKseLeopWnjg+pfAJqSnYMmsKb5l73Xf8GW9FZxw5x5YxXLiCoU74Xp3yTnzMs25K7hOm\\nuhCcXhVsPAxVwCoY6oQb9oKXxQTmMbqD9LSnGydS/9rxpXdQuIJtlAu9MZ9MTc1E9SpZ+XrUBNLN\\np7tfnwnoxkc9HDGIwQfUugDv6capUC/PO2zREhRsrvXRd40cBslZBR4mn/grnwxbKpqhpt4bEBJD\\nPK+JLypU50kWnvzY4xIJ6IXLIi+n5qXvcz0TX/RmW8RQLveExJNNK7xTDEYVN7YWREnHwWjFwWDJ\\nW+NTZnHJdrQhUR2dl8T4V+2Uxhne//A2mzrm2nSJVZ4Xiwk/OL7F95/fEm+Py0xQJdil7oCNJZ+W\\nRLGMpVUZg/Vs7nYc/znN6maMTyz1jQluJmwk8QOyqKpFtR12WaOcR5U9oBcCellie/95l1nCZITZ\\n233j9/YSH9adUMVML77VrZJlaIeM2TaAFhbGpf+Jj6A9zWidpmsNdRtB2xe6jcGsDNH6dZSeWkai\\nDrU9M6d9/WJ0uXS13dQRMofeqSGAHraY2HMwXeG8Zm+05p3pCYOk4c7wnFvZBV8dP2MnWrFrV+S6\\nJlId6x7OWbiM3zn8HPM24/7glIktKHzMw3qP/+X0G/iguKhei+EaZ6g7y7JN2UnWDGxN7S2H9ZjU\\ntHx+/5jx3hqf+t4KWIRG6Esqqvj+XEJSl3bZlylTureGuIQ1gpGUrTd9XVoiC11QpuNLG3CCdJuX\\nnHgf/ymWVRA4IhjIXlpcp9F1z8zSlwwbiC8gvZC/VznZh4VIHDl9pwleETqB30Iif3mYNai8I9sp\\nCEGRDBqiuOPGZIELimuDJe+NDxknFffzU+6mZ3x18JTr0QVbZs2u2TBQHSufXtlYfL+4z4P6AIfi\\nxCe0waOV4uO2xijPy27Cxie0wVKEhCpEnPiEkVYYpahDx5Er2TIF//H13+Mb3/iYcl+minaoxKcn\\n7jUll/Cx7WuSExq1SOJlv6G7XimsBbI2LZ/q+kx09MoFXGZAJ5hVBUoR4ohuFFHsWSYfbVCtJz0u\\naMYjutwQvyhRnceNEqJCEufjM0v+KrD1Y1HHohXVfi44YiTjUbRytLmcb7oTPND03F3XGzW5FEIU\\nsGuNHwZ84jGlJru/5GC84tCMuLd1Tuc1O+MNu9mau/kZJ82QTZewE60peirHVrThwXqPP/nkNtNr\\nS5Ko4+GjfVTkYRExuL2kXqTQL4y6RAxtzEb3I3GgWKaYxJGmLbtbK07ORvhG9xhsoB0Ja6bYH+Dt\\nkMnHBe04ot0bET88Qi9L0P2ZfjZHaQ3Woowmfe7pphnl3SnxxZvNnLt04Aw2YNavE4VMJZ+x3SjJ\\nRi0k/Fld+tTo0CuBgUazWmeE2lAfjtFxuLI7uIwSdCMJJMEGsaw1QYyvOt13lbLbcJmIqFSlUcOe\\nzVRY7t475u3xKQ/1Nl+cvqJ0EXfGF9xKL7iXnEhB73IO7IKNTzAE7sSnPGx2+YOz+7w7Pab2hu+d\\n3WE/X/Jgvsuf33tC5SJuDBacVgPyfAXAcTG6gg/OmwFb8YZZVPBufsTH5R6VizBadgauh2JCH4+Y\\nPbcSthL1L/2fWsZ5e1nk+7zZlZV9hpIdyZu+TKm4NAtM574Xvl1+zpAdB7pUrIW7TAq9/F8DaIV3\\nYDyEZYwtFYNn0VXymLfSgVYzUZFe7qSEWquxiUxFynhCEqARSqmOPG4doYeBqPf7/8qdF7w7PuKB\\n3uVrk+cULubt0Qm3kzPeSQ6Zu0FPmthQBYMG7toFP2t3OGyn/MrgZ7zoZvxB8RYH0YKHTcSvZM9Y\\n+Ix3owVHLuaWneNQPGj2ZDrAU4QAvmGiU27aIbHb8KCxWO2lgRlKge9ymdgGz3tkwyqiVejVr9Ld\\nKy+HY1TIZxqMfKbtiKvG6f/t+kwU+vnbhtkDB2jq62PsKCUYTfJyTT2dUl7LyF+WqLpl+v45OPHA\\nUeuC5tZYVJZdkKCQ67D7j+foTYEaDujeOyC+qCXxZhgTLWuxQMglt9OlgWYmua12JVmuIQoQe9ph\\nh55HZHdWxLZjmMjS7Mt7rxhFFXfScz4pdplGBalu+crwOalq2bVL/vvjP8/LzYQXp1OU9qi1ZbGZ\\nCAyUSs7o7N4F8/PhlQGV6n3aw8b+mXCOkHcM8hofFMsiRRtZYrYjTzPSbP9wQ72TUd6zLL5eM/wP\\nL3j2JzfY/yNDM73J4MMTQhoRrMbUKYSAn43ohjHx83Os8/hk/MajBO1uSTtPacceuzFoJ5hidiqY\\nvEsD8ULR9hTCaCMdaj0NdMN+06gCeI3JO9SpFfpgLsU+pH2b2Imz36U1MSpA5vDDTmh1qwiVeJT1\\nGB1Qw0C7jrlz54TUtuS2YdGmfGvnERNbcD8+4eN6nx27IlUtX0xekGc1u7rmt9Zf5Hkz49Fmm85r\\nlnVK0cb4HhZ4vNzm23uPeFJscVHnjKKanXSDD4p5k2G1J7UtmzZmK5Ein6iOhcvIdINWOQejFfN8\\niD6W17MZd7z3zgve+8VD/sHj9yhfDkVU5oSuauqeiz2Arlce27Ui1OKmeRnu8iav8oYjOZZNoC0V\\ntgqUW5p0Lve2HQqjpHUC7URFuDoYJBgb6VQ7RTcMRI8RCC6TJa7QmPtpoBOqrUs1etwQJx2TXKbR\\nRZGhtceoIEZwec1mlfLF26/IbcPI1qy6lF/a+oQtu+Z+fMyD+oBdu2SgGm4lS6YaJjrm96uUhz5j\\n6VKOuglbZsMfV3cpfMyuXaHxfDN9xpMu49BNyHXLvmloA5z7mKnZMNIVy5CQh44dHeHx1MGTKM3d\\n6JRfnDzm+zt3sB+kdCjcViD/0gX3fu2UP/7oLtnDmC5XpGfhKoVOu9B70Ku+Qbq0dFZ02aeb1j4T\\n0I3LAi4SfCpaNrg8kjG0qBj/dE6xazj5+hA3SlFljZvl+OmAkArptss07ciw9bOWZhp48u/fEYVs\\n20pe7CzBzAvBqF0gntcMjjtsKSwQ3SjsRkyyooUmmlako1o6hd0a0y/org8XrOuEW/kFRgUOmzHT\\nqKANBk3gVTOl8Alzl/OHj+7y9ME+PBfia7A9N3zcgA7Mri25OBui5hG6lEIVvCI4jQqK5EJjC1lI\\n5iMp8uOswhhPnLTE47qPGwR9tiSe1+z8uMKcRfx7N/6IO19/QTsQQVS3OyZEBp9Y/HQIdUOzlckk\\nMMzxQ4labMdv2OvGCDk4RIEuDzSTfnGnIH8le4jimhdRlRIhmkvkpb6EfIIN+POY0bDE367ELmEl\\ngiGdOrlHiDpS12K5oAoDpdgodJtI/sGlZXu2ZjbZ0DWGfKvAB4VWgXuDMyoXcS2e03rLy3bGxBRU\\nISJSHS+6GYVPWPiIv3/0Jf7pq7f5+ekew6iWDi0org8X5LbhX73+AR8uD3iy2KJoIxpviHofXh8U\\nh/MRZ8WAWDt24zWtN4xMResNmWnZS1bE2jGYlNi1LFuTQ8ur5Zj/ZOf3+Nvv/vPXSVI9rNMOBOvV\\n9eW4j1AZe0fEZvrmBVNXcY59ASp3NN1AhFLDV4K3X7yrRbinoJoqmpEUrkuXy2AgOdeErYbzr3nh\\n1696iK0XC12GCiXn8r3dPKaqIskGKFNCgPUy49Z0zsF4RdNYtrfEfMwqz73slHUXczM+wwXNs3ab\\nqSnwaLTyHLoBVQhUoeP31+/yQXmTj6prvJMc0QZD7SO+mj7lur3g2+kJv1u8zQ+r2zTBcO5yiqBo\\nUax8yo/KOzxrtzF49o3G40lURBU6DIpdI4EnNupIzz22DOQvFMtlxn9+4x/yV7/yASHqqaSZ7Prq\\nqaIeC+TZZX9qKd8z2trRp7tfn4lCv/2+2OemL1dUeynrG7Es5UJAb+TkrrcU1U5Mtz9BrxsxLRul\\nmMr1+FUgf7zA5Z5q13PyN78oEJAW8/5ud0Q7ifCpZX0rY33dUk+Fx42GduIktWUQaFcJTSUVaXdr\\nxfJ0wLpK0AS+tvuC9+fXmTcZmWnRKnBcj/jp+hptMHxYXOP94hbjUQkeurGjXcXYSYNyim4jfioX\\nZ8Kn9cNOJohRi1JIzqeRIkiAEHtCkKAKBUyyiu1hwdZ4w+hgRfl2TRhmmKM5pup4+3/Y8F+9/10y\\n27K8D9FFhU8N3TDGpRY3iPE7M9LHZ0TrjuV7E0mojxTN4M0+DtWZHHK60DS7He3IXzkVisuhIuTu\\nCp64TLiSxZ0cwMopdK3Ik4bZZEOz44SR1AlGq4ISBlTe4a9XhMwJnp3LcxGNatCBMOpYrDM2lUwt\\nd7YueHk2YVELHvsL06f8YHmHiy4n1zWpbnnVTPmT4i4uKH5WX+NpN+PWYI5SgTxpeLaecX2woGwj\\n1m3Cz0/3+OPFbaz23BgtSG3HTrpm1SaMogqtAkncoXqKpUOTmwatPNfiBTfjc97ND/nC+JVAg5e2\\nAgqqP9nivz7+dbTyDA/WfZZBz7IJ/XI0kc/N9BYal9huGLj/23v0L3qlRzJtxCuodsRgS6xIRUhF\\n6NXGQylYl3x65QLROlzZbkdrsEmHmjUs7/ceU/1zEBRXENb6dqDe8qjcMchrXFBsDwSLzwY1r1Zj\\nFpWkbn1udsLDs23O6xyjPN+efML31vc5dwMGuibVDc+abX5Q3qUNhp81M553cCc5xQXNTrTiQb3P\\n1BSsXMrKZ3xQ3eKn7YCpKfhm9kgmd7OhDZpUBbTy7NglRnkOTIEPgUhJ4zTRGUOdcsPkfD17zJ3t\\nC4pdLUEiPjD9/ZT/8vFvsB8vqW60RKtAO5JpRmArQR/Ss4Bdw+bWa41CO/x009pnotAnC0d2VPPi\\nu1u8/I7FWynOIbIQAqNnLePHAtf4yIBRYkG8KLDrhvSsxVSOditn9LHBDR0+gsW375A8OSc5LlHO\\n42LN5mbaizsEA07OhcOdP7WYWhSvWI+vDXffOhKGRN5htGcUVSS642g9xKN4tNnm+2e3uahyplHJ\\naT3k8XqL3/rkSxRVfMXhxSvcaULYajALQ7vVQW3EmbEwwu2uDPm4omsNRIJ5mlphF5ZyneC8ovOa\\nzmvKNuL4bCyL2dLQTXPoOsxFIayan45YNwl3f+kpL359ytkXUi4+J9OPWdeE1BKsER69VSzvxayv\\nG5rxm13aqU6oi9ndFW+/fSjePiAe8bvitW3P7WtloHvNMNGNurJnDRaOz8dMs1IsJ/Yc8blBXUSy\\nxK41xnoGo0pUowFYW2g17jBHtRpKgzGetjV87d4zamcZDSoi7ZnYUgp7OSZSjo+qA35v/jmO6jE7\\n0YrnzTaP6l1+8+hblC5iHNfkUUvrDA8X2+wPVzybT7k1nVN0AuPM64zdbM26Tbg3OGPTJQwjAVTr\\n1vJovs2izXBBU/fg9MqnfLi5zkkzouwiofW63qvcw+8+e5vH1Q5//e4HdNdrzEGJO2gkTDqWoi8p\\nTjIJVTdaurF740I4ubdSpJefb/FfWAtcVMnupNyReMvsqA/3znu2TfTa5uIy0jMYcC9z4qQjmMDq\\ntiY9Da+dGvtkKpf3k58OrJYZbWt5erglS/o6IrYdVWv55buf0HjDNC+JtWNmN6S65Vk5wxD4eXWN\\nf7L4AkftmC275swNOewm/E+LX2DucraswC8+aJ40O9xJTvm43ud+cszc5aS64UGzz55ZUQXDvvFs\\nvL6yPqh8xPvNAavgcSHQBnfFyX/lCloMRnvakSxSo57t/POPbvBgs8df/dr7LN6B8sBTHIQr879L\\nCmsyD3gbWN+Rr7v0093czwRGf/FOTLKwdCl0Bw3Ny4RmGtOOtiRPdtWSHvVFrGxobkwA6PYyoqXQ\\nBuutlM31mHgZyB9HKB8odjXld6+x//vnuMSQnjc8+4spplIMn/fue6pfgsSAkpSa+GVMs98yiStO\\nVkO0EejmTnrO83pG68xV2sx2uuHD430S2/FsPkWpIOKY2ohN76QTVR6gjxNZHOogeailhWkLncYM\\nJaM0bCyq6UO0rXQ0SgeW64zxsCS2DqUCw2FFUSToRnP61Zzp4AbxvAYXuPN3F6zf3+eTv1GT/fIF\\n89MBamNZ34q5/Q/F6M2PM2EhLR3Ki0+9dm+2IoTMiQmXCtwczPk436cd6ivjKnyf4Yp0o13PuW+m\\nDt37zV9myaqgeH4+lUVr6mlMIL4wYvVaSKFPrGMTC/VVFxLH6AcOVWtsYShVjhm3bCcFL9cTyZk1\\nHTfjcx7Vu9TO4pDDdD9Z8gcn9xnYmqebGbltKbqYhTMcr4dcGy+voJ/TYsDOcEOsO6z2XNQ5BwP5\\n+iSuaIPhxWZC4wxZ3KL6ltUFxUkzIkpkComUYzdecdKMWNUJer9io1KSE5HLux9N+K1Pvsk3vvUR\\nf/Hdn/NsM2XdJCwmKcXjMbp+ncGqG0UoDD72qOrNQnLQx/ZFwnC6NlvyMh9QbQlmrDvp6geH0py5\\n5E9DEX0+bCVEiG4gh0N1momgexgoEkV6AvVM8Ojqmhe9RCtTHB4abcmHNVUV4QrLq27KYFSxG694\\nup5htSc2HbejMz5p9qi6CIfCBc1BsuCfn99nJ1rxop5xLV5cwTQv6ilfyF+S6JY2GFYuY8eucEGz\\nZdccdhPeiQXWSZWjDYGfNtdY+ZRc18RKBHSC2zds6Zhcx9ShZaotN4xAfMV1j480yblMMNMfW374\\n6AtMfu2Qz3/zMU8uZtRVxGo7Iv8kJu6pqaYODF5o6q1eab7+l8gCoRuAjzUH32soH0bY2qFbeUi6\\n3GCMosstpvbEq5Lk8RnN7a2eWtZj8KWHgWb6sMJWiYRbuEC5rbn4yoxmLKPO6FE/ag4VtgiU+4p6\\ny5OeauxGfm8xBVUbfvTJLbG+1YFrB8ectQM0gVEqKUKNs3RBMx2UNM6Qxi1HJxPwCrW2Iquv5c+H\\nOBB8zyZJHMErzKjFz2PIxXe83sSovENvYqKlpt516O2aYV4TW0cWtVwUGXVt0TrQbSLyI020CZR7\\nkdg+bBym6sgPa278ZsTTf1dfqSVtoVjdSZn9pEEXDaqsyUKA60PaoZaO7A1eynqIPOXDMb979B7K\\nKQkS0f3DGRBMsu3pl4USKX/viIiSRV+bK/xFTJVYmYJ62mQ7Fl1DN3C4o5xuy0jIcgCfOxEezSMJ\\nxXCKbhbwTvF7T+7T1hYUfH3vBSfdiFw3zBJprxyatUvY6knKw6jmVSHNxbzMSKKOiyojMY7MtjRO\\nJPWzuGTjYm4PLni6mTGJSwam4fF6m91szYOzXYoq5ub2nHcnx9zLTmi9ZWILntbbbLoEj+Lxeouz\\n+VCydfcqyjQmOTboRpGeKX70O5/j5rdekJiOZdWbw+zUhKOE4NWVTYdvQUUKu3rzg/ulqdbopzFH\\nz65je3ToUmmuE4WPZe8mhT3Q9AKrS3zeFlLssyNNvSVTvHaAlyJ/ac2bvrJXokUqjR61TMYFy1WO\\nW0Sy05rUeK/4B8/eo6wFnvvmzhOOuxGpatlO5F56FLWPuJnPKZww5B6Wu4xtyXk3YBYVHLUTclOT\\n6+Zq2tq1S3zQvBWd8H59k3fiQ1LleOaEI//j+hZPyh1+bfRT3rFr9kxOh0OjWfiSOnhWPvBH1ef4\\n5HwHUymqXU+XK7IjKeDpOZR/Z5/lX1lgtWe9jqFVtJOA6RlVktUry4tm0gs7P8X1mYBuopU8NOmz\\nBbN/9BH5y5LTL8fkTxakrwriow3RsqEbWJpbM/wgwywbwd4HEdG8Jj2t2P7jM1TrmXy8ITtpSS46\\nph83V1ilSxTZuWPvBzW2CmxuysOVv9QEJR4cq/uCc9qFZjAtwQnXelmn/OHJXeZtxn6+Yt0mHBVD\\nyi5iP1/xYjFhvs6F3re2shjsRHpvCo1KHezUqNyhNOjI453izucOhVbZiZe6LMaZxLwAACAASURB\\nVG6h2fIwkR3AZRrRVlrw5b1X3Nhe0LX2KlPWR6+XVgC67DCbhvzJknv/rSJ5FmMq4eiXe5rVWyOa\\nvSF+kIGTjkq6sDe7tAtOAkOSU834Z1YK9KwhnmviuSZa9lYU245629GOxRZBOekK7VoRLRXxob1S\\nQepKinZ80dNL+zCWaKGJPswJhSHdqsAG1GmM8uIyWl9rUaWBVcS12RLfaQbDiosm4/fO3mHRZdzM\\n52y6hOfFlE2XcHtwwUfLPY7LEedFxtkmxwewWhKiijYisR03R3NmScHGxYxsjUfxN679gM4byt7q\\nACCJOm5uz9nPVsS6o3AJke44sAt+efQz3s0PqZ1l3SS4ThNqjdYelTq6rLe3XcPwORz+45s8Ot2m\\naSxlGWNjh5v0HvT9stOn/goSe9OXruUwHrzy7P5Jh26g2vPkr8Q2O7kIeAPlnmJzTVHuiijo0lo3\\nWgXyo8D0o94e4UwRL2Qfk57y2vDLBuI5zD5QREuNmbQEp7k4GcnuarckO1hTlxFVGXN/dob3iumw\\nYN7m/M78PRYu515+yqLLeVJus3Ipt9JzHpU7vVo946ge44MiUqJ8br1lagpuxmeMTMnKp6SqZeVT\\n/ubwqdgeBNMv7B3bZs23Bg+4b9dsmYQ6dGg0kTJMdMaeGXDmE47bMUoJa+5yb+FjYSXFq8Dw0JH/\\nrxPmr8Zy71pNN/RU270ffdRbKPSPlfqUr+xnotBPHnXYItDuDFFpyuO/PmBzy/P439rB5xF6sUYF\\nqKeGNrfodYGuW6KiQ7fSIeqLNWohyUqqbokWNdGyIX22YPS4ZPcHK4YvHMWOwVSO4cuOdhjwiTjv\\nBdur1ZywGUytqB6OBFpRYLQni1pOqwFWe6ZxSecMmW05rwbUtaWpLWbQEV3oK56xLjVuKEEYNhJv\\nHFcaCKKsfXE2IR42GOPZ2RPv6uREoyslPPu8ZpTWpLajC5qzasAorplNNtidCh/3EvMA9VTjUo1Z\\nbNAXK+gc8dGG3R85oo24AeLFYCoYhZukYBSm8sTLDvOGC705t1BLFJqpA9H1DVvTDeWdBuUgP5ST\\nSQ06Qupx044wa+mm3WsFqBcutu56kU4QDnd2Ehg+1WRPI/Ta0o480RLiM4NzCpM4TC2hLfGp6T2D\\nREj1+NEew2mJUYHYOIa25qwdyAsbr+m8YRRVnNZykFddxO5gw2KZs1jllK1lWaZM0gqrHKOoZtmk\\nLBsJpoh1xwebm+xnSxLT8a2th3Rec3ox4nQ9YN5k7ERr7iSnTEzJxscctlOuRxd8efScr20/x0Zi\\nCNPOU+K8wU07onUgmXvipRTS9vGQrrVsTTa0lUB+ppSQjksRmS71py4G/1+u7OSyw5SuffN2i96v\\nOPuqLFoHrzqUFyZVO5TlYjPpU5Wanifeir+L6q25dSPL3fzEkb8KTD6GaK0prgWiIpCegSssynj0\\nyqIU1Ec5bWtQKhCc4ofPbrI/XWFUINEdI1tx1MruZT9a0gXNyFQc1hN8UGy6hBvZnKNqxKPNNsX/\\nRd2bxdiWZOd5X0Ts+cwnx5t3rLFr6q4e2CS7SU2U2aAAU7ZgA9aDDAiQYBjwo+E3+9UwDAMeHmwD\\nnmHDgB4sSwRFUuIgyqSb3Wy2qqfqqYY7Z96cznz2HBF+WDvz0oABle1LoLiBiyxUFfJmnr13xIp/\\n/ev7bcRZPWBgSgyOoS5Z2B7n7ZCFywhVy7G13A3mDLTls2HO1kd8ff0qz9oRT9oU6z2xCgiVudbn\\nrXe8E3r++vA7GC3ae7jWNAPh/ERrRzJrMYXIqcOfBOjcwKSWotHL5mcqT1BeDZH9OfPRx6c5/bhH\\nNQ0JL3q8+l8/ZvPuEdG6kuzXOBQ9a94SbFt8FOKyiMs3E5qB4ubvLIXY2LSSlxqHMoDjwU4yvFY0\\nw5hw3ZJcOHTVYiLN/p9oLt7VlLtKGksvw+hDyA8lDCCeaRmQubfhyeWYzxyecVlkNM5wI1vRjyuK\\nNiQ2MrUafZhSTRxRpah2XedzllF0tw2oA4OK5QVWymOtEBKbIsRXmotFDMZT3GpleKiRQIpRWhKb\\nlmFYoiPPedFnmJSM04L7T1NsqlBes77n8Npwt94hPs9ZvT6k2BFv8957FZdvx4QbT7gVfRBA54IH\\nrqaSpfsir3At1Xc9dZhGk/1en+W9PqEXt4CNFcEW/LO4m4y2+Mgx2N9QjUL4Tv95MEktpy+XSsBs\\nM9Dd8AjEF/oacYCG9jQTQuXIEqwMza2a5EFMtWcJlgpzGbJtBtx45ZyHqwlvTU5ZNimFDXklu2AS\\n5yyblFi31NZw/GRKb1rgasN0b4X3il5cY5Trmq+acVxIs7yDhDsU8zpjVSd8vN4hC2pu78+wnff+\\nrB5wI1zQ0xV7ZsXapZ3OW/PF/kO+Nb7D3DiqZUIctbz++mO+396h9yCgnnjanlTr4YOE8xvdRtZN\\nFl+5VtDg9mrqzYt/zZNLkQ/yQzCV4fY/cixezlBWZBebBASFJz1T17gE5RD31Viz/0eykdtQGrsq\\nkCE6gHKqKXcEDR7PIIjVdRh59nFI/qqXAcN1yPj2kuWjEXqnwq1D2BqetJrXbp7xwWqPz0+fsGhS\\nKhfwSnLOIChZ24SdcEvhIr51eod3dk9YVQn3pjNWbcpOtEErx8Jm1N6wH6zYOpl6zaiY2YStj9g2\\nIpuNTc5XBx/Khm1HvMk5MQGNt9fOm6vryFi+fPCIP2gCyp+MYFIT3K64LEf0TjzFvqLpS3Ti4GPN\\n2kUdk1hkGxsrOuoD1a7vcpP/xdenoqJfvDXARkJPzF8e09zaof/dY6IHF+hVgc9i8dKeFZjKCsJ4\\nEDF60BIUkN/t0ez2sftj8J5mIFgAndeYVYlyns2tiIvPxcLQ0QpnFOl5Q3KupPGpIbmQybQrwP/m\\nnsNmDtsavnr3PlkggljVBvSCilFUkgYNRRuiHqe0qRe9cUcIiyjwfYvKDbrXijZfGVTghNtiFc5K\\nyojpt6I/bwPCuZGbu1+htWM33TCIyuuR+U0TMdtmPJ2PsDsNuoZqxLV1cXUvoh3EhNurcHWLTTTD\\nxy2jB5X8ewXBtgGjCJYVQe6uWTQv6qonwrCxfUu5Kwv79AeCZ9WNohk56pEnXCniuUQI0mrWp33a\\n2sggW4cvsGl31O0QEVdf276jPLDo+rk7QVloqwA9bHCJF3cOEKw0XkN5q0ENa/Iq4leOfsQ4zMnb\\niFWd4lD0TM0oLAi05XQ2JB5WbOcp/UlO3QYY7dnvbbjMe2RBTaAt8zJjEhW82jtnXmfXzPr9dE3Z\\nhhxvRsy2Ajr7zPiMUFkGpiBULTtmy16wYmEzTuox39ne4Y3JGbY1qMiileeyyIh3Cop3CppJKxiH\\nWtEMHMFZSPQswORagHiFui4UfGn+TOiVm5vqOoRnfVdTDQz775UMH1lM5Sl2FdVUkVx40nNPtBIH\\nVXQWoGrF9oamHqhrguwV4VJbkai8gnJX3CVB3s0KJGLL1esAuqjIxWVfZNlVBIEnfnlFf1hQ2YC/\\nceM9RqagdgHrRnaR/WjNQSg2yA9We7w0vuSDxR6vjC4ASE3NS/E5J/WYsZFByKfNhD2z4rPRM2a2\\nj1YO6zVDXXLeDvmgOuRH5RFGeV4Lz6m8wyh9Tbq03uEQdn/pPW9mJ2zXCTaTafimMeS3W1YvKWyX\\nk6sbiQzMTjT9xx0+pJaKXrdiQQ426hM7qj4VC32xpzn7kiYoHNuDgJNf7OEmMthDYHBJKBOPGmxs\\nqA77BIuK9MML9t7LiS9qmn5APYmpdlPanhFefWigaQkuNvSeNYQbT5sFtIOItmdoe4bRA0EONAOR\\nEmwsD9pV0IOPRHL5/R9+huPNiEFU8fmdp1xWPfaSDY0Vu9SVFlpNulHtDg+rtMf3W1xlcJUs4K4y\\nojE6hW01QSTHO+VEp1RXoRurkL3BlrpDEd6MF6SmYZIU3B4v6CU1NJpmKHc7XGqCQpEfKKqdiKCw\\nTH9SYSp3Xa27UGMqS7RqwHnBFHsP3hOuXqyY6yKHOSjERXRUsvpcTT1QhFsv/PEOWWu7+QVlpfEW\\nPwtRpzHKQjP0z9nkiSCM4U+99JU8FyiuU41cJtVeEFrcqCG+1LQdQE1CTRTaiBf+733wRT7a7LET\\nb/nK9GPmTcZRvKCwIUZ5XKOpi5CoXxMaSxTI9HLrNDcGK7ZNTO0Camd4sJnyrcu7lDbkvOyTmAbn\\nxcUzW/ZorWZRJDzNR3wme0bjAxLVkCn5nnvBms8kJ0yCnNNiQJZVeKdYznvkVcTucCvPigbazpkV\\neeH3GK5lmyCnsxDLgh+s/2xcN8WdBlMq8iPH+Zcd1TgkXLeYstt0jdwnG8t97j31DD+C3hORk+qR\\nsF68QZDGVzZCA+m5pIyJj/z5VKgLIb7UUhTFFnMZ4lNx5ajE0raG0FjGccF/89Ev8EG+z2604auj\\nD1nalP1wRe4iYt1QtQHLKuWovyTtoDGhstQ+4E58yXk7vA6V+aA+5Le3n2FlEx40u0TKopWj8Ybv\\n57eEYtqMedBO2NHptWSzcWWn1ysCDDMX8KP8BmHcylDgMqTOQ1Svpe25rknd2csNNAN5b6/YQVes\\n+qt3J/jzBDVLL5wEacxres9alIXZuxPsrT1UWeON7siVBt06dOPY3utT3psSLArCWU5yLoNVQdGS\\nnBbYWFMe9ihfmtLu9AkXFb0zKyP2E/Hqx+clwdYy+lC06zaTF6QZQHYqU50EnihsMYnEvgF8++IW\\nzivWnUtiW0fYgUDQfBdgfFVVeSsNVmU8Zh6gMhnLp1uwvFW4JxkcJ5IJGnd8/G7xWhYJiWlITEvu\\nIkJl2TYR2ybi4nwgslDPEq1h+iPH7nue7JmnHmi8VgTrGlO21KOANtbo2qGrLpKxajGrCq8U4aYl\\nuviET80nvHStaYqQcKlxpcgLq5cRqWwL0UJ3eQKI9l6ra75LsBUbppyMPMFWppaFIW8p7jQ0Q1kM\\n4nNJ3mpGneZ/NWl7mcjGGnWkwNTLIgEEoSULG/ppxa1sgcbzzfk9tPJUPiDs6JK9YUnSq68tkett\\nQt2KvRYgNJanqyGjSOS1opXTQ2lDvnt6k++d3WAYl0RxS2AcobEE2nHWDNkzK3paRNZENWxdzMql\\n/GRzwDTOuTFcydygVaw3KWfzAfU8IToNSI4DknNpThM5bAdtU1eUx0C48LqS09KLvoJcLKzRUiZw\\nda24fMuwPYoISk+4kUX5akTfVJ62J4M/puimY13XmN1AvOzomxmsXhEWTDz39B9qgq0UYLY7eSsL\\n8bMQ3212eNBZi7oQt00StgyjgkFccy+9RCvPtzf3iHWDwTEJtjivOeovmSQ5gXJYrzguhmxsTOMN\\njTfEuuFHxRFHncS2dnIqyF3M76zf5p+sPss02DAKCkJlSboNe+MrQmVosaQqwnRE1o2v+KDe5yBa\\ncW93houdVOoXESzEMJHMpOBML3wXq9lJWl4syE1fnu02k0Knd/LJGjCfCo1+8oMVg8cxbT8kOS85\\nOi8p9xMuvjCgd5J106+eehSRPlnTZiFtosj3IqrphNH7c5pRjAsVwVZ49m1Pqpje/Q3ljYxiJyAo\\nHdm6JpoV4Bx6XWDu56QfJLjgFrPPygMabOWBdLFUCqt5xu7eWnzUVUJZh5wpmT0+X/YJAst19F0o\\nL5pNOyBR3qUCtQq32+CbrhpJLaxDacjsNoRnIaabNgxyRXFoUb2WcVZwlC4ZBQV9U3I/3yENxI0z\\n3V0zn/WZfCPGG0//UUH4dMb8KzcJN7Kgt/1IsjaXLaZyxA8vpcfRj/GhwQ4CfKhpU4Pbe7Gsm8HH\\nmvxGiIvAzEOihYS4FAdOtMWu2SpALBlc81qCkX0o1alQ+uT7mVwRatNNgXqaSUszugLAddVsCy6W\\nJhdaQaOpjrpZhS7SUKUtxjiezke8vHsJwNZGwoNvY5xXPMnHJEFDGjVczvuYwNJYQ5o0eGBWZCRB\\nS9kG3BisKdqQygbsZ2tOtkOWecqdyZwPTvb5sNwFr2gbw9H+giyo6ZuSoS7ZMwUa+GG9Q6JqjPK8\\nMzjmg3yf++c7JP0K+5MByXnE5o4jrCQ39UqzTU417Sa8zg/13WLZZl2gi+Xac/0ir/5jCX53IaSn\\nirCTV1b3/tSptMMje61QTmS4ppv2jRbdIFW3AulatDhTyfPfDKGaSsWqG0U88yROTsltJr0dF4cE\\nrwr2wBYBJI4sqUmClgerHe4N5d6GymK9IrcxSccV2g9X7MdrHm2naOVIjWEv2VC5gKfVhL6pqHzA\\nm+kxa5uQu/iagXTR9Pl87xH/ePY2my77cN0kfHl4nxBLiMF6R4DBKE3lWrTyhBjeiE75XnGbnz48\\nhNQSfxwwfORYvKIxNSQzQWzbUMBwQS6NddMIshglhZJy0rzODz9Zrf6pqOi39/o0PQmJKHcTkRNa\\ngfo0fY0LNbr12ETjMgGTpecNkw8qwo2lOuizuhvRJprlKym6tiSXNbrx6G1J9mDJ8H5BtHac/OKA\\nxVtDmh2J2VNJjM9Loq2n90QT5DKxWRx0lfksBKso6pCnyxFVE7BaCXZ2XcbUlwl1Jek+NukaYU5d\\nTyeiwGw0DBv0ZYhZPN9b9bimGVlo5Ta0fbmxphQZBiANGkJlyXTNy9E5d9I5rw7OGUcFq3VG9DCm\\n3JEosrYvCVM2fA47sommHgVsboYUe6EErDtHvZNy/qUB8zdTyp2QILf03794ofe13O0wDl0yEB1a\\nly5BS5wEssBfae7BRpEeG0y3abYj+zxGztNhEaQBG14GhAsjxMSXC6pd22Fu1XUqD4EnmAlPSLfS\\nDFTGs52nKOUp2pAn+RiQxbuwIYsm5eFiIkM2TpGk0puxVmOdJjQWox2LIuGov+LhfMLZpi+2Oa+4\\nPVgw7eVs6pggtPTSmqYM8HnA5bqHVp69YC06vVaMtea16Iw34hNuBnPuF7v8dCHIaK2lKhcMsJI8\\n4U4SaQaeZiS8myBX6FZRTxzmiwumb13Q7jYy9X384iv6Yr9LQbvirTjxyqs/dTI2MsgswfQeooVn\\n8EBku7b3fAOqB1Klhxv5mlx6kgsZptMNLN+0bG+KfGM6pLjrmrjFWSbRkbUm3i3Q2nG27l+fuC4b\\nCQif19IfWduEH6yPWNoU5/V1363xGuf1tYRzXI25Gc35P5ev8ePihjzPPuTV+JT9SJqzN5IVkyBn\\nVstz86SeMtQloZIFHqDyDbEK0ChiFfB+fcg/ffY6AGHSohuIlq0UhxFdrxKqiSI/EhJouJXPb/Uy\\nJP/aKf2/eMb2jqR1DR5/sor+U7HQ60qm7GyiaTONLhvCjdjJih35EcOZlAezt/pdR9+KNFFYfNAF\\nFSxbyoni5BcH1MOQ7PEG5it8FBDOcrKfnuMMLF7TrO7E+NUamgY16BFuJZKv6Xmanoziq3EtY9dW\\nUeQR2zxmucowgUMpT1mFEHjaMiDINbbvurQfRbjR6EZhNlL14CTr0o5bVKnxeYDtJhaDlaHtO9Jn\\nGm9kgWxGDm081mleS095KT7jR+URoyBn3SakpuFguiL87JJqx1HuO4rdAN9LGH1ckJ3V6NoSblvC\\ntUg1q5cMy5+9Sbs7wGtFduYYPmwYvj8jfnAB/gVPxnbyk1dAFw2nWln421RcG1eh69WelaAX37Fu\\nth2PX8nm4GJHfrehGQgWID3r5Jy1ov9YYdcheqeiutEQzzyDB51TJ2tpRy0udVI9ho7RMJfjb2vY\\n1hGzMuN4M2IUC+KidgFx2LJuYvIyZpBWtHVAVYYUeURrDcttShKK5XW3v2W3v2VVxsyrjHmZ0TjN\\n+arPqFewuOijA0c8LZgOtqRGpi73TE2mDB+3AQPVXjf6Xkov+CuHH2CMY5QVNAOPixTJmSJayvNk\\nY6mQXeCxPUcz8LSJSJCbWcbsxzuk9yOSS3XtZnnR9/YKOuaDDvXdSTH1yF+HY9gY8hvQ9p5XpvFC\\nqlNvpPnaDD2rV+iszpDOLKYU62X/qZPQn1s165cc0drRO3XPn6/EYbIWQkcUtdwYrPFeUTQBpQ0p\\nbMTTcsxRumTa4RB2oy3zpsdxMeRGspRAoKrHZZXhumnlYVCwtCl300teSc45qUfcr/Y4biZULuR7\\nm9tMgy3vr28Qa8t+Ik3eRIke33iLUfq6qr+6vhw/5W/d+SYmtuxP1tRjaHuG/iNPcikY4jYReQYH\\n9dizvqdYvOFpppaz+YDL7+8x/Y4mnju2B59sCf9USDfRqqHtBQSbhmoaM39ryPiDLdlZS2kNYd7i\\nkoDeRytWvzTlwb+ccvcfV9TDgLqv2f3DY+rBIdU0ZO+7JYtXYuavB1SjIRNg9tkR459sCFZbbv/a\\nKee/sE+8cjAewianORiJp3cuAKFq0gUqn8XoGsaHaxYXfaLjkOZ2TbMOOV9FqNSiU2m0tjcq1Cqk\\nHnZMDjqOR7+F0EMhleeVEyc6DrGxEUZJ6ojPzfUL6SLR+o92F/TCipvhjPN2yF/s/5jjZsJbu0/5\\n7eU7nPxwn/5DzcGJY3VHExRWZghWuXCCjEbnDW06YPCkwcah4B4SIzMGTyrsIJYq3zrh1L/AS9fy\\nOSgLrdaUO6KpB92MgQ88bSZNw3biCN/e4N4biR6bdIxuY7B9RzQ31FOL61labbCJphl58V4vFJP3\\nAhZva5R6zlAJcoV1CtVIxqqdSnTg/HQIreKle5ecrgecnow5uLFgU0XMy5RpmjNNc4o25M50zrxM\\nyfoV1mqCQKr5yVB6QvMypW4DAmM56G/48NkeJnAkUcO4n3N2OSTMGpzTZEnNIKp4o/eMgS4ZacPa\\ntXwp7nNhG/5qmvPHleK3Tt7i8aNdzCLg9CgELfF80dITlCJnNX2xHrWZoCZcK59pPNcET6NrDLAL\\n/2ykm2jp8Vq89ChYvAa9p8JND3L5WZq+VOnlDqx+rmDndxNsJJF4o/stbRpQjT3xXFEPPeWOhx3w\\nOqCayGZuGs/hNxTnX4yuYwv1lWdAg14EuNgQ7RZUVcj9yylVFfILtz7m4/UuPzo/4O39Z6ybmIuq\\nz73sksN4ydomvDt6ymk9ZBx3/T3lRLtPFgBsbIL1msYbbsZzvru+zX68vkZV/HR7wCTKaZ1hEuSM\\nzJYD0wAhGkXlG4JOxgFosfx7j/5Vvv2jl+h9HHL8hiaxsrAnS3vN+Kr7HaxPQetlU0dB70FAemae\\n829CCRn/JNenYqFfvpIy/LhAN47ej8959rcPWb3U56X/7RJT9zGrGoxCr7fsfytmcye9Zte0SUz5\\n8i69xzlmW0NVs//+hvLdOxz/hRhnRngNm3s9xqcLfBrRO2uJLytU00ISs3olJSg8gyeW1V3JNNUN\\nVLvihMl/MEHdrqh3LEmvJpk0lHVI08g0q4qk+mZa41pFWwSYQYPRDuU0ttbXDUJVK7BGkpTiLiDD\\nC9MlWgrAyVQKt9divcJ5jfWaX8o+5n9ZfImv9j6g9oZvnt1l+vqM8mSX7KQiudAU+yHnX9ntLImK\\npgeDR57ecU00Lzm8P+Py5w8od0L6H1Uoa2Xj6SXPg0le4OVC8fn6AHSpSN9YsFmm9L6fdIAraEYO\\nU0JyHFJfDPE9T9jxO4oDyY/VJ6bTgiUmsj5qKHdD0fK1Ir2UAZTeI0M9lurQFNIPUB3zRjmx5elK\\niRzUKn7y05sc3r0kHZXsZlum8ZZVndJ2TplAO9KgIe63VEnAqo4ZxSWJaVnWCds6Yp0nEuhdh1in\\n6fdKAuPIwobWaQnAWMWE/ZqiihhOS6yXiDqD4kbQ5/264O2oR+5qfmf9eb5240f898++SpCH+I8T\\ntFVUO57tK60gOWKHzjXJuQxItVVIM7GYrSZaSs5ouSNcd2HSvPiFvhlKjyUoIZ57indKFqOI3W9r\\niceL5LQWFOK0sU9Til0Jt/Ya1rcMvWPP6L6njWH0sWO7b1i9LlgA5YVYO/mgpRkYsqcBPqDz13ts\\n2nHqW0UTeOo8hMpgBw221vzuB2/wzq1jjoYrbiYLJmHOvBGa5bJN0cqT6IZb8ZxxkHPZ9DiMZWDx\\nWTWkdYZ5nTKKSjSeJjZMwy0H4YpQWXIXkZqGJ/mYW9mCVZuw09uQaUPlWzIdgYfC1/R1gvWOh23N\\nv77/bb7903sEWxh/MxI79F1NMxRJ0sZyos2eQnoq7K1yl+7k6ohXjmLHdM1pcVh9kutTId1sbyjq\\ncYQLNW6QMf6JVAYg+p7thTSjGDsdUk8isrNa9EGliNaO7WHI5bt9aS5O+zAdEc1L7vzWFlN7Zu96\\nli9LHqk+nckiX1moG1CKZGaJlq2MF5eiIV7xYcrDFmcgfCRNl3KWsC0ikqiRIOm4JYxbwqjFO0h6\\nNbrfMBltGQ9zwqgVi2XQwbdqBQbswAqwrNUQOOKZuAyihSJ7qjAnMedzafjeDBb8w/XbvJo841Ez\\n5Vk74ldv/oD5KqP/VIafdCPeeOXkAXChVNL5gSLIJTicxZrsrCEoHGa5Rc1XmLzBBxqXxaj6xUYR\\n2Uw0W9V5pPNtQhBZ2i6DPFpd5WTKBncVeecioTCWhy2b1+RnuqriTKnIPpIXZHJvjp22mMaTPWuE\\nhrkVz7FsJF685B0G2ZRSaapKEx/mqMTy7OEOzikeLcac5kOSQKiU/bBiFBVkQY3zir10wyguuZkt\\n6YcVadDZ8cKWIo8p8gjrFKO0JDSWxmnioKWaJ5hFQLOKqc4yfnR+wP1il0xX9HXC+3XBS4G5tuH9\\nteF3+d7qJuY07mQs4f+ojtKJ6n6n0IvnvGvQqVoRrjXh2hOtxEraZqLnh6sX/5rXQ3lPvAZnFFzE\\nYLqhNYTHYjo2i03lvboCCNpYNvH52zIwBYAX+uXk+/Lf6zcKOVk7T/YkJ9zK6S3Ydu6ittvMA5H2\\n1FZqVtdoDg8XJGnND08OqJ3h/eUNnlUjmYCu+9eLfN8IcG4/WrEXbeibkli1pKahcgFaeZ5sx5yW\\nAzY25nYyI3cRuYvom5IP17s82w54Vg54mE95L7/HcSt44qsqPlYhlW8wa+uhJAAAIABJREFUSnM3\\niPjdxVukDyJ0+zx60dTy+9CxfprR81hU5QUCF60hWViiRSMI6EQkuWjx56ii7x17ej+5wPcSVFEz\\n+fGG7DyBuiGclxJuXVqUc+haFjavFeFFTvywoJclbF8esXptINOz5yvKwwleK4b3c85+NiM7Ea84\\nQSAs+yxE9zOaoxHRvCa4WBNdpqhX+uS7mjDvtDIXXFvz6Ngh7UVK79UNYWBpWkPdGrxXJFndbQCe\\nYVLivKJpDdVxj7BbfPA8H2DpvPbxcSgPtRfZKD+SBdJ7Re0M/+nJ1/iXpj/kW5uX+ZuTb/LPtm/w\\nP//kZwGIVo740QyfxAycB+cJT5f4OATraA6GmFwWJZXEhOuG4iDBDTN0UaKXW3waQ9Pi15sXel91\\nrUjPPPVIeijbQYQNPSoUcddX8rJGayj2n2u2ulLoAMITQRus3mxJTgJ6x578UDawaK5wTqNXAZtD\\nTTCWxcxU8md7JItdcq6px8+rWklDUtS2J9bFVJwaAPfPp9y6t6DXqymshIbEpmXPbIh0i4sVvaCC\\nFrTyzJY9bB5gUosJLN7/35ueD052CEcVjYlIRhVR2BIYS+s1C9vjv1um/Cv9j3ivjvn5GNYu5784\\n+WUGQYU3HarXg+nQvkGpBXdhpeFpQ9HGg63CI7wmG0uVHa3AxQrVyCLxoq9woxg8bcl3DcoJL8rG\\n4LpAEt2CKWRcv4iU4IlbiFayAcRzKPYUs89C/wFk547lS93J7dxTvapI54p6FND0gg5rLPiEaiwR\\nkb0nMkla7XSEi0bhG8WzeipN+KSlF9Zo5Xnv8iZfu/Fj9qI1jTeULsTgmAZbtHIMTMlAlyy96PQ/\\nne/RWi0bt1YUNqJyIRqP9Zp/evkZ7vZn9MMer/QFZTEwJTOXUPmaUBkCDJVvSJXsfv/V4jXmdYpq\\nIZn5Lm9XitnRfS/wPqPYHhlh0RtxI10hu5ueQVeOeO2wsYZCTkif5PpULPQ7374ErVFFTbvTIzhb\\nkdQtzcEQlOL8cwnZuSO9CHCheL5dqChuD1jdm+ACxc1ff8qzrx1x+dmII71HsRNgak98qRh8rMWt\\nMOihlxvyWxkuUETpRGBp/YjyrV3iWUO4cfhDg6o8/RPLWhs29xzhWhE9DWmmjv6tFYs8xXRwq8DI\\n7j1IJHHoxmDNXrLhvOxzUg3RtSJeyFFaqJoGm3jcoBVHSC166/amNBpRwFHJ7njD68MznFf81uU7\\nfGH4mN9cf44b4YJ+WhH8/SnLlzTp0wzbj1DOU48jglkASlG8PCXYtrjIoJ2DNKYZRqRnFTgHgfx/\\nth/TDAeoO5MXel8HD7pGXUfYG3xkqCbQDB1uaimPPKrSVDuyQMYzBUpRj6+adZ7Bfc3yTc/450+5\\nfG+fdiQzCMrD5qKPaUUC2k4UzVCqpHClGH4s7B9vIDlX1GP5WXQjlj0bKZrdFtVo5s+GxOOSVw8u\\n+Hgt9tVAO/pBRWFDdiNZDMbZnJEpeMaQVbWHzQPC85BmorE+ZFEHFFXEwWjNxaaHawzKeA6OFlgn\\nG9GbO894JTvnTniJRfF7xRFfTZ5yYmGgJWXqd/7526jYUxxIw9lrqe6uHCfVRKo8GeqDcAWgruFl\\nTaaoh9IobSaOZvTiXTeTn8pfliwdbawYf+AodjTFnjDoN4nH5EpmIyKIF7JBy4i/WAN3ftRy+mVD\\n/ksb7NcHcv9qiYPUp7FYRWNNedjZnTtXSnYmfTCbCJ/KRoq2JwljwUbTTCEYlLS14YPTPabDLW9N\\nTvnJ5oBBWGKUZxzknDRjdoMNlQuZBFvGZkvuRMtfbROqeUIxldP71f17vfeMp9WE1hmc1/zc5D6N\\nC8hMxbvpQ45MTqzSa/RBSkSLxaAxeP75e69gxp7NTS0ylhL7aVCJb74aKrLTq0a7bNouEi6QjaDc\\nCamHsnE2fajHf44wxQIiq3HjPrq2/Pjfn/La7VM+/KiPKgzh4Yb8YY97v9YSb2rqnRRTSnU9eGKx\\nscL1UordDnOrFdlZy/pWwPpuSv/YUg01Lgvx6YTegw3VfnaN9s0eb1AuwcZagF/6+RHUVJ7RTxXl\\nrqLcdfjIUVUh1mpeOrjkyWxM0xiG/YL9bE1pQ7TynBYS/mGMo+1+zWQmVrL4Uqxwm3sBUTfMcjU9\\niJImpdGOQVwxqzOc19xO51w0ff729Ov86j/7dxh9K2F0UhJtApT3RA/OaW9MQIfUR0PMtumGo1ph\\n/vRiVBISrsSNg9bUd3dp+yFB0TJ/PWbxTvv/eHv+v15BIQ+oC2UYJv2bz/hbd77JHy5epXWGnx3f\\n5+8/+QLnf3QDU3Bd4XglTT4fdGTOzJJXkRxtc40dtVShgqpzNGnJoUXLopgfCQ+nd+wp9kQK0K2i\\nDbxkkG6ksjfH0jynL4vWskqo24DXR2d8vN7lssi40VuxE22YN2Kh27YxF3VP7HvtlSPM0A4cLEKq\\nRcgqrtkuUlCeNK2xTksqVSiTspmuubR9EtVwJ5hxaiO+FEf8xe//DR5/tEe4MvL7K6h2xcnlkd/B\\nlN1EcBfJ50Jou1xVEFte2/O0mcNUCnVUcmd/9kLvK4ApHEFpsbFBtwr7b1/wb9z6Lr9z+ia1M/zM\\nziN+/aN3CH9/QNwKEkGkOvHRKitNxzbzqNZgUmmql/tyj8K19ABsJCeBeOHFcnhTGtLZuaMca7mf\\ncrtxSRcNWir84ww/bjE9qTIuqx6lDXh3+IT3Nzc4zkfc6c24E12ydgm5i9jYhNNmiEPRNrI0lvME\\nNfVc5BnzMuUgXvGsHKKVYy9a47zGKNfp9jHObJi7khBFX8doFBrN33n0i/zed94iPTOEW7l/TV+s\\nxTaG0mqCXE4spvJddKBgJkwlslXdV10TXk6txQ1HfOuTncI/FRp9fmeI64vPVTWW+EHMTx8cYpYB\\n2bFm9Bs97vxWjQ80zSRBV5b4eEW4rjGFJblscFnI+COprC/fDgnXDS6EeqiI5y3ZhcVmIS4y8j0a\\nR7yw2EjJxpE3XdXkxQKo5KiYzjoP8LJD+a4N9lEPYxybOqKuAhlV94raBViniXTL+Va0wGIbE88U\\n0cKja5laNTWk547+Q40PpGEWbhTRSl9DioLA0QtqZlWPcVhQ2Ij9aMV3qlu8dPOC/lNLdLKi//1n\\nuCQgf+sQM9sInbIf0PYjdOMwixxzviRY5PjQYDYVZrah3kkp92NMaQlPVoweNmRPXuy+X42EHaRb\\nT1B5Hj/Z4ddO3+X7Z0f88cO7/Od/8DXmv/t8kZdqXCx22j6fnkweRlinsbdLwq348EmcJFhVwuWu\\nR1eJVLIQlvuO9R1pBEO3iXZZm1fpSKoVl4qvDdUi4fjBLlHQMq8zLrcZg6hiXSfdIh8R65YH2yka\\nzyJPCZaGcCVVa3xuUE5QuotHYyEsho7tOmG+yljnCZU13EiWaOU4bsYkumblEqa6ZuNKvrj7mGhm\\nyI4V2TEkM4Hitanvvop/3lQigWRnjuTCXy+E8cJfYwJMhxG285iLTe+F3leAcmqwkUa1Dl15jh/s\\n8g+evMtHz/Z48MEB//Cf/Dy93+pjSk+byCBUeuEIc1nYXCgLV3YiKOb8XiObWC1+cpGqZMDqCm2c\\nnHuCjaLYVRRTfc1mMjWoSmzQeJH+XOQxK0NVhlwue3zv4U208pzUI86KAXvJhm0byyJvYxLV8GGx\\nj8HxbDvELSKCZQBOUaykANgUMd+8vIdGsgceFVMeloI9dl5zaFYMtGLrPH0dX1ssHY6/NP4x6dOA\\n/mPP4LElWgl22CZ0NlQlkL/Sk8wsvWcN/ZMWU0G8cCQLMU5cUSyVlWG5YvnJvLOfioo+fboWm5+C\\nehxx99dXmJMZ28/dZH07oPesIdjU+NBQj0PA4KLhtZe3moQEsSF7VhO+lEjEVmwYf9SQ7wU0AzlG\\nbY+kEx6tHU1PM7i/JXFe8lSzkHBZom1EvNKSwF7IQ6xbTZMq6mEXRK2hfdjjdF+ok7lKGA5yVlVC\\nGjQ8WY9ZrlOq1OA3AcqLFUo5j++QrOVUX2dspmeKcC1atm4VTd9QTEIWVUrjNK3b5Ua2YmQKrFc8\\n/O4Rh9qDtbh+xvLVDFN5sk1O9mjF6o0x3hiSsxa1LcBaVFUTVg3UDb5piB8b4seg1lu8c2RfnzHu\\nfeaF3ldTiw7ZBmKpO/ydgOJ/PaL9Qoy/6Yi26vrzbLowaBeq60nYpu9BCVl0s0qY7KzZ6oRwbmgm\\nYArd6dUy+3DV3EqfSdhKm0IbyimBFjragEwZ1jJxaSMwi6DLXfUc399leZBSliFGe3ayLWfVgJ6p\\n+WC9z8l6wDaJyDcxgee66Xs1GNYMZVFWXuGXodg6E4f1cFYFnIxGMnXpAh7rHfaCNWO9pPGO3/jg\\nLcKtSBNNpli/JMNSXkmD2nbZAXElC0K0dmSlpdyGkh+aClrC/6m3OnlmWPvhC72vANHWYRP5/Ouh\\n5t4/sMSnKdWXEtb35JlGyyJ/VXVX424hbjpktlIEG9jMEyY3lzQf7hDPFcVe9ywEIk2Y8jkWpPdU\\n+lcuAtehjYPcE88FJuhNx/jpZKx6E+JcCMbz0+MD8iZiXUVo5bmZLTmpxwxMyQ/zIx5tJ2ziiMtt\\nJpPHkYfQoYwjCVvitMUod92krWzAOC44ZkTRD/lqpji3QpzcM811UzbA8B/94FdEm19a2kSzeL3r\\nR7XdoFQMoDoUscMULcHaoa48194TL/R18eMCyE7BxZ9smv1TsdDrVQ5K9FndDykOM9qX+5jaEZSw\\nuRkyqhz5QURy2eCNotwNGdwXa46uHfU4RDnN7vdbli8HrO9E9E4awsJTDQXcZUNF77SVCrOw6KpF\\nrwtULR+mKhvKw4xiVxgsxV4gQx4dP1tZYeBsj8TZ0f+TmNUrjnBastkKGqGp5SNtVxFNHhLNjcgR\\nWiqPaiqWqHKXaw73VUWqWqnW8tuWd26fyPdxmlcH5xxGK4a64Fk74u5vCG9fbXI27+wx/KiQ/NeD\\nKXq2JjtJqCeR2E3DAKyFJKbdHRCczFFZKo4jrfH9DLzH7g7ITj4h3PoTXvGiy7zs+CvFrmZzO+5Y\\n2up6bL/tOC31xNIMFaaQngoKyp4jmmmC44hZO4QjyQcIswY37CQN5dEfZQS5otx33aYqC7DXMPwI\\n1neFlhnkmnIqGr/q9H1tpT9QTUAvDP7RCPtKTTRtucx7bOqYvA5RyrNY9tjkCcyjzjoqjph6Khqx\\n26uvIaB6XOMdKKvxreLm3oKvjD+iciGjwHE7uuReMEcrxYeNIft6/3qBWr4OvSeycNsIikNNPRQ5\\nxnRJYC5S4kUfKqK18GB0JY3qeiSguHrnzyB1BAg2FhcK4lpZTb4XsLorG4opFW0m0t1VxVruih6d\\nnneMoLW6/nnH7wcsmjFmR3Rqm3mK7PnmOfzAoNvnE7eSvSDmhumPLfN9Q7kr/S2b+C58RV034KO5\\nod6xuEXEyYMbqNc2JKM5T/MR8zpl0+GGT1ZDLqIem0UqiPFuA++PCvIq5OZgyTAqaJ3hTn8OQOUM\\npQ15t/eI20HDpVUcmIK+7l9TKxtvyX5rgHJyql98VjN4II4iU0uAej3onhoP9Sgg1GATQ7FrSC+s\\n9C1aL8aFPWl854Pn4SX/wvv14m79/4+rqtELaZbaSBMtG7InFdt7fZKZpR7IEbGcKMbfnsnD9eou\\nqmpQRY2OQlzUJ7os0Mst2aOU4taAchqwuaUJN579b6zR6xw36bO5m5Ge19Q7GWFosEmAcp6gsaSP\\n12xuTMSHnskL5EJZGNJzWTjiuTTEQI7IttXYVo4XbRmA8uhc4zJ5oHWnnzY9YXS0fXEaOQM+UJQ7\\nUE0daI+PHAe357RO82w94M5oQd9UvJU+xXrN33v0JfROSPx4QfPyIQDeKNpRTLAoIQwIzlbgh6i6\\nlcXcexmeqlpoGggD7LSP7UeETxco6zCzLUHzYjV603jMxtEmmmqkxT66hnL63AJpCrAHnuypJigC\\noUwaJHpReSGWRrIYJ08i2szT7jb0sorVKsXPI8IL01Xw8mKWB474sgvcULB6WapCk8sG0vbEqUK3\\nCQVbhesGrJq+WDFVYSibgLoNUMqzLaTf4vKAJpGJXNWCj0R6uwqYMacxLnaySAwb9ndX6E6f/7md\\nB8zbHvMm415ySaIaXg8jLmzBf3n6yzL38FimHZ3xBIW/ljGSM0lAs4lot+EWVO5wsSYouioveo78\\nHd6Hpq8lo/cTxs39v7mCwsK2ldS3oSK9dKSXju1hIA3SuKvEExjedwRbTX4kv4+LZaJX19Ik7z/2\\nTL+nqcaKaupxwxa1NSRnhuRc5KhiXxrT2xtKgF8dlnpzaEhmHptqmr7DJR5qde1sU1asuzrXuNTJ\\nRHEZytRsGxJox6IUY0VRRELiWIWCWQjAZC1lEbE3WfPxfMo0K+hHFdMo5256gcEzDTb8fPqQS6t4\\n3I54I5JNwHUM4f9s9lmanmLy04b1nQAbeUwjsnJQieRWD1Q3DOalEN22nbsQ2kzL52ZEs08vHG0i\\nJ92g+GSum0+FRk8S46MQPV+RPV5z8Vnh0g6+8ZD+D54RrR2Pfzlj8ZUKN8igbkjfP5ZFLIupDvq4\\nUIsGP+qh1wXp8ZZ4aSkOHL/8b/0R1X+8wScxelOiW0+TyeK+fqmHiwx4yO+NKY8GxKvnO6WNFW33\\nJ52567F+GUSRxqo9SfGriGaWoLRHrULimab/cYDpGpK4ruHqJJTcdpa5K83Yhw6fONCSZhVpy7aI\\nGUYFb2dP2bqYD6sDNr97QHZSUd0a0wwjsodbYfRrBd7jA4MqKoLz1fUQlIoicTU1FrIUlyV4o2UQ\\nLQrxSYSqanjBC32+p9kcBjSZcGZWr8rLufPDhvGHlnADy7da0pdXmBL23ms5+sOW7Kli8KEhvjCE\\nc9M1H+XYH64lOMR5xX/w5X/E3/3Lvw9a0nZA5BgclAdW7s9MiJj12Hd5pArl1HW2qUzmCmeozTzt\\ntKXpecKVZnY2ZLtKuLgcYIyjWCaEs4DgSSzebcmQkQrSg650R8rs5gFaRagdo7gk1JaBKcl0zXnd\\nZ2S2/Ey84dQWPGxTfu+9t4iWsLmpKW54eseia3stg3am9kQLjeumib3iuvoPSkfTU9d9DlP65xnJ\\njxw7P3yx8xEA5W5EPZHZF9XC7A0DCna+v2H644p46bn4gqd9e4OysPN+yeE3LEEptsggl5PtVdMR\\n3/UZ5goVOP7dv/ob/NJf/7YMxF04YUh1Uu32pgIN8cxT7kGxK5sKXhZ4H3tc4nDDFiy4cYMbtUxv\\nLSTFbBvw8fkO5+s+D+YSJH656tHOEjaPh508ovDG08sqgtBS1CFZJEOQIBC8gS65FV2S6YqB8iTK\\nMbN9pjroqnnHqS34b//gL9M7dazuBhT7iv4TiDaOcOOFx9X6rqCUYrBNFPUkopzKulT3lGRohLKZ\\nV0NZO3qnjtFH5Se6X5+Ohb61MqwTGPQqp3dm2bw8gDTBL9f0vnkfU8JkuqEdxTR390Br9HKLiwNM\\nZbl4J0TXluJmj/nP3WD96gBTOW7/dsv//ptfYZGnPPrVKT40xJcN1cRgcsmVVV4cG9p6aS41nmAr\\n8WVtKn5fb2C7LzusjSSkJNhK8HFyrolPDfF5QHASSYOulGOptnQeYol/GzwRjkdxKJVrUEqH3eQa\\nnbYM9zZkYcPDxYSj6ZJ+UNN4w9hs+c2Tt69dJigwpZXBp8bijNgk3SDB7cgRWm0LWfzjUCQcJQ+M\\nDw0uDqj3UrYvj3G9GLs7xB6+WHulcrJR5gcypZtcKKqxos00/Uc5+3+SEy4N/aSimsLsjYBw1RKt\\npGKLZ4p26IjW8tLVY0e5b9ENNN+a8B9+51d4WE7xb6077Vf07GipMVtpbLtA7Jbx7DktU7WCvK12\\nHC70lLvS8Gwzjyr0tfRi5gHqMkJdRpTHPfQqEHhX3UUb6s4+utXEM1n41Y544FUtTd5tHRJpy5vD\\nZ8S64Sf5Aa9kF5gOe5woxf9w8YuoRoiNzUAajvFMxv+VFwmrzQTvG27UdepWNda4UF1LVbqWgkIc\\nYvLPpvKsb7/4g7tu5eeqR+Kjjxaw3Q+wSUD0ZM7uNy+IlppBr6TY1cw/ExMtJCTHVII3qCeecC2A\\ns/XLsH5JTi/TP4j5T/6Pv8ZxMWTxOcGWhytFm3jBQGxkM/NaEqjCrdzXcKMwW4OPHPFuAcajdyvi\\nXk1vItGfYa+B2FHnEduzHuvzPsfPJlSrGJwUFFfPkUosRRFRXKZ4r7gznOO8orIB2zbioh0QKcvn\\n42MSpfl+vc9b8cm1tTJWIf/j4mfwxrO9oSn2FdES+scW3VxFKCqaTEtGw7YzIHho+kb4X132+9Uw\\nZJtCuaeuG9HLV/4cNWObO7uYTYVeF/htzugbT6TSjEL83RuovGLvew3mjwfED85p90e0N6fookHX\\nlvN3++z/yhOWT4/oH1fywMUKG4VEK8et329I/qcW3DOKV3aIL0vSC8PJXxgw/rCVBtdFjakswbwg\\nyGOiZcDszRibSOUAkMylUjIl5DdkeCcohMmSzhzlRMKupVkiC7luYHsk/Yf01LN4LaB37GgGmvyG\\nNJPihUc/0djzlO27HgYbdvtbQm1Jdc2Xk4f8nR/+m5wej0k1PPpaIukyCoLPH5F3w0H+0DEcWsLA\\n0oscWsG9/iXzOuXBYsrLk0uWdUpsZiTKUdqAfljhvLqGer3ISzzP9hp2BVwHUqzvZegW0hNF/fE+\\nqZLG3fZmTFhIYzo/9Hzxcx/x/tnrYrcD6omXcf8qQH3U45tf/wJ6BJuXLOFC/pLydg1W0Y4UwdKg\\nPIRLccdEayh3RMeNllIhh2tFPRGpSE1qgqOGchMT34+vh3K0RRp0fyqUuTh0uJ4lvAzIb1miS0PV\\nCzHTGtdoKAyz4xHrTcreyxtCZTmMV2RG4gP7KubvPvlLfOPxPXzg2b5eozZGFvy+pt2VE9Zkf81B\\nb0saNPTDili3vN47Zdb2+NHqkDeHz5jVPVIjw0GFDdkJt6zalCf5mL3kxQ7CAZx/LmT3+y3/F3Vv\\nGmtZdp7nPWutPZ/5nDtX1b01dVV3V1fPzWaTIkWKkiXKg2ANNhQnoOKMig0EcJTEdhADdpAYNuQJ\\nFgLZjhAlUuzItmhTtiWKkihRIsWmyGZ3s9lDVXXXfOd7z7jPnvda+bFOXTpAALeC+tE+P+ui61af\\ntfe31vq+933eaL9YmAAFKquoA4d8s29NQO9p3Dd6YGridcXkfIAq7IyrDuHKx27w5pcu2kzgQlAH\\n1jsgdgWdtxx2f/cC/ZZg9KiV2woD8RagDfnSgoNf2rmZqKygITtV4TRK8kmAcDX11CNcjQncijPt\\nEctBzO3ZgOs3NmjcdihbZqGwW/gSFjel8nTBYGnGaBqxfHrMJA7JapeL7SNLOC1C3onXbNB4d8iy\\nrNh0RgSiRmIfkr87Ossv33oahGH2TAZTl2zDEG9KzFqOECUbS2OudA/wZUXXTXBFzWPhNuO6wWvx\\nJlca24yqBpEsTtALvrQ3tHeTVZa9GfAX/p3r9YEo9DIpEUluT5/rSxCnlkOjbLvBeC6HfzaheK/N\\nxf8zAilIVwP8ocLdm+DO29z+9gaXXzlEt0NU4aM9ex3c/ahi5RWBH3rIw7F1hq6FJwX54DlF95pB\\nFi5lU9GKC/KBT+1/J+syWxJ4U0PRljipIe/Zq75xDIUPslAEY0F0WFttcChIV8RJ+8coCI4X13AW\\nvfrEoJ0HQzNAGOrAcGZ5hK8qHKHxVMXlaI/fSy7y/PI9fjMOyR+r+d5L7/DudJlTjTFHWZNpHrB3\\n3EEnDrP9LupMQuq5fOTULbaCIe+xjNO3COKWm5FUHrvzFqFrOdl9P+Fja+8hxfvr973fj3Yh70ja\\nd0vKpsKbVBZp4SvyriJekXR+cJd72wPWv+DgJLbdEww1wdCQbMCrt8+w+o4mXZbkA0ALa8R5akJ5\\ns0W68p2Ql6ppkKW1wwenYtLDyIbBeBp36ljbfvqdTadsaWQhyJbtIM9ENc1Whu/UVj67rmhsWyZL\\n2bTtnmxg7GHK2AQtZ/RAv2nhU6IW1ImD38kwocBxahpBwXOt20zqiEgWRLLggjvidgWPRAdcby2T\\nBTl/5tw3eCM+xdnwmLtpn3ntcWO4xDz1ub7X5uy5Ayoj+Z6la5z3D7iZr+B2anLt0HUT5rXPzrxD\\n081xhWbNn/BE4z6uePgDWVnZtQ33CoqujxuXiLxGaEPZdElXPMo/PWT/Xpdzn63xG4L5mmWuhweG\\n+Izg1bfPsvEtzXxNnbSjZA3JS3OctxrUgcSJF8qU0JzkQJdrJc6BSx0ZSscQHAuSDXvDwNVoLXBb\\nOXWliFYyjBH0w4TTkU1oO90Yc7DepLrZJzi0t03jQN22GBEcgxuWHA8tq6OsFI5TU9SWf3Ouccyq\\nP6XnJiw5M570FPerAl/AqpInJ/pH/V0u9I+YNWP+3OYXeTU5yzn/gPvFgEkd8uZkneM04ku3LvD8\\nmXuURvJHe69z1h1yu+zzoVZFpl2WnBmTOuJ2NmCwYMOsuyP+RG8b732u7Qek0OeIWoPWGCGoVzs4\\nhzNrpEpzkkeXeWb9Bm98uYMoK7QTUDYk0f0C0whw55r1LwtEUVKHbbxY07pbIvMK7dhMyWIQ4jqW\\nbZ+3JUYJ3KkhXYbpOcl83b794wsdtM9J7Fy0a08Kec+2b9zYokNlKah8jZovXK4OljNiNGVD2RZD\\nYfvB3si62JyFhjhfuNm8qR1a1ZGh7NT0T49ZjWzG6N1Jl0+eusFu0SWQJTfjAY6jOTM44jevPYoX\\nVOSV5XH8+ObXeXNwipaT8V68xHHWoNaS398+x7XGKtHCBv4g9jByCq4szfCljUNb86eMyoh55T/U\\ndW3frUkHkrJpH/x0ycWb1shSE+3XTM4FPD24z+7ra4haUzesJl7UkvBYExxIqiQk61t8qywgnEqr\\nTKJlcb2N2vZThe2Ni1zQ2JbMux74GpHaoJe8r6kbGh0KjKtxxg4cfquSAAAgAElEQVQqExRLljmk\\npgq/m1FVishbICPCiipyCI413hSSdUG1UkKxSKmaKuqGRqa2N1x1apAGkUnK0CGMClbbM15ausVZ\\n74gb+Rrfnm/w44OvURjJzarPnayPp2oudQ/5P959kV5k804B/rO1L/FW9zQdNeeN5AxHRZNSK359\\n/3E2GqfouJa6+CBqMpQFz/fu4IoaV9RsuCOO6ybDsvlQ1xWg/3ZFHQiqlodxJNlSgH+cI8sa/yhl\\netbj+dV7/N4fLCFMfQLsCg9tSyo8BG/mUnv2NCQLaIwE/thwFIYoZXk6ZWNR3VngKxIoV6Dq1shM\\noj3N5JKk7lZQShy/psocdOIQDRIiv2ASh5xtHZPWHj03wVcV/UbCdrNnpc0xZCuG4MyMPHcRQBF7\\nNPsJRWGR1ZFfEKiK46zBwJ+z7MWc8w/5WHgHVzRpScGdosljniY3JZmpuJFvEaiKq0s3+ft3P3US\\nbALw5wZf5o3WEgM557Vsk/2yQ2kUnzt+ls1wSH+REZgttLJNlfFi6yaBKHFFxSlnzHHdYK/qvK/1\\n+kAUejGNMY0QUYMax4ye2GDp5h44Dib0KVqKV7bPsPnVGDGZ4SqJORtw/GSb3nUbG+jOa4ozA9zj\\nOelqj2TdJx0EzE+BbWp7aMejjqy7FWmQc5tWVHZrnFhR9SqEqzGZwh1+J2fzgWNVZbZIC2MWMC5l\\nTwDiO6dEb1qRd6xiQHtWZfBA/YGwG0XZtO2Lsm1vHRgQUUXDK2mogvuzLr5bsZ11+cypX+UnvvUZ\\nJhPLwd+dtBESylJxMGrRaaX87LWP2fi0BV9nd9Sm10pwpGbnuIMflCw15zTcgrONIemCJOXKmmER\\nsZ+2GWYRaenyMD+NewkqDSjaClkZ8q6gfT1Bhw7aswTP145P035voaoZa+anFPGmQXvSyulqOyPx\\nJlZB8SBqUbuL2EYBJlgUWFdj+oZ44EJhg7V1pHE7dqCWZ66FbwkbU2hBYQJcqJo1LpDNPeraOlml\\nY07kgW6qUZnCPXCpffu7tWPbPcYRdhDbqBGuRrVK5AKL0fFS1r0xhVHczfu0nYyxjvhYUPHf3/kE\\nt0c9Iq/kbtzDUTWz3OPdcom15oy/efvTtLyMgW9zg2+Ml9loTlBS8/ZwlY6fcboxpuVmPBLuk2gP\\nbSS+LDko2tzN+0zKkFEePdR1BYhuT0m3WlYKOK2JNzwaN+aYwEWHLmVT8PLOWfpv18hCEx2UTM77\\nTC9A+6adK6CtVNAfPZAYWtmxbtRkLdusFo4haOb4boUQhjjxcY0gDAvmic9SO6Hp5xzPI2ZHDXQt\\nLOFTGMrCITaCwC+ptOIwtxteZSQNt6AK7XuocutXSPeaGM/y7YVj18/zKtLC5Ux3jKcqzreO0Uai\\njcAVFR1ptfLXypANZ0ZtQiSSvz98hi8dPkLDKXgvWUIJzXHWIK58Tkdj/urOD9B1EwbunNIo3pyt\\nsxUNcWTNq+MzDPw55yLL0HnE32OufWoEgSzYK7vcKZbZL9uMy4j/4H2s1wei0JvKctQB4idXOXrG\\ncPDSJqJRYbTADVLq3OG9H/Mx6py1fnsG2c8Zfa9AqoQgKEnmAfWsi8weFFCL/H2g0w6OJMYR1A8A\\nV/NFnzt2LDZ4ppCVg5G2h6s9Q7Jq7ZRODF5smJ2xJ3RZQtZYXDdLyxcJRjZ8e3bGJv0Yac073sRY\\nCWQDeBCaHFrlwUm2p6u50Dnihwbf5Or6AcvKQSIZ6oqN9hQhDLN5gJKafjdmlgQnOaaPLe/jyZrh\\nIjjh4sqRPb0Lw/Nr97jS3Ga36HKUN/nCe9YUdWow4Up3j4/1buCKmhvpKm+MNx7quso4w3MkRvlM\\nzjlMH60ZPhehGhXGGJrNMaMkJH6mZqiMjVgMchqdFPOooeVWBE7FLPOJ5wFV7FpXrLDDToIatMA5\\ndqnaNSxeTjm3G7B74FCFhlJ6lIv/jkCDqyn6Bnes7CnxSJJslaRHEUiD28rR+jtoCq1stFuyYSjb\\nNSiDd+BYrEIlqNq1xU8rgxcVFJndMKNmysBPuOJvc9Gd8r3h2OJrgURXrAYz8pbDKLPY3NVmTFz4\\n1heA4cneNpEq2MvbOELz9GD7ZJP+5PI1ngruslP1OKxa/MLtFwF4ammbD7Vu8eHOewDcrfp8bXbh\\noa4rgChKgp2Esh8wfsRj+FzF6FMtwihH65peY4+0dNj+wRrpgqnBDWOWOzGNjxaETknkFBxmTYZJ\\nSDGzm5EBRKHwIqsJLQ4iClejlMZzKsrYfn/1fogONGPguGjZjd4xCGUwzRLmDmXsUeWS9vlj3hqt\\n4quasw17sgd7G9SuXcdsrUI0K6Q06JHFbSTS0O/NqWpb2NeCKQd5i76X0HPnXHAPaQofjeGjgQIW\\nDmSjkRjOt47YTTsoYdhqDtlNO3iyotSKZ9t3aMmU+8UAJTQvdG+Ta5fSKH549RU+Gt5mu26yV3X4\\nmfvfgxSGJzvbvNS8wR9v2rW9WQZ8af7o+1qvD0Shz57aRJWa4eWA6UXsiRuQrsZogefb3bzeKklH\\noX0ZI3tq8rzKhjp4pe2dNjLS1EMbgdmzE2knFdSeba+IRUAD2k7XVQ551xo1EFCv5jheTV1LTOqg\\nK4H2BJ3bFfNVKyGrmlYZIWpOtNgPQotnp1xUCc3bC9dmwxpHqtDgTi1POzowJKuLIh9ZKdip/pQX\\n2zd51j/gqHbZdNwT+3Rc+Ay3uzhjRRwYpp0SoQytdooQhsO0iSctFdERtjVTCIe8djgXHvE7R5dP\\n+pPffe49Vv0p+3mbXDv8xuHj9PyEeeXR9h7uMHb43IDas6qb9FQNDYtsjhr293hOjedUNLdyjsdN\\nhDT0WglKagKnotaSrp/iOxVLzTnHzYiyVsT79mQm5g7G1RbjWwmMEegHyokCin6NzCUoQ9DLLHeo\\nkpSFY4elAryh1W4jwWkUVJlDWSqrma/EidohXZKoDPwja6LL+5zgl52JoupXqJFD6dZIZQjCgoZX\\ncKmxx2V3Sl/5JLokwhYZVyj2sxbv7iyjY5dDVxP1UpTSDBoJlZHsZh18WVEaiSs0DSdHCk2uHR7x\\n9/nVyVNcCA6JZMGf2vwmG+6IvarDpI74F5Pn6DtzaiPpue8TWv6HWdsPLYOB6VlJeqHACSqk0rTD\\nDAE0vZylcM5WZ8R+0kIJw1IY46kaT1ZUWrHkx/iq4nRjzH6zRa0l7+7aCMUi8VBebcPgF2tb1cpm\\n/86lDZEpJMYIeoMZrtKUtSQrXHLjUUtwhg7VcokQhjOtMZM8ZF75OLImrxweoIDzPjgzhbNv1zZd\\nW+hmgTj12RoMuTvq0fcTQlWy7M3oqzmXXU1FjS9cSlOf9OaVkLyXLPONvTPEcYDj1qx2Z/hORTdK\\ncGXNdt7Dly1Ko06iQhUGX1Y86u/yr+InOO8d0JA5//HGVzjljBhruxl+dnaJZWdGjeCcf/C+1usD\\nUehv/4fg3g9QubCa80RipEHEobVCXyjpN1PyykEtaapKkc+9k12/LBWHSYtOO2Eah+iRbzeLXomZ\\nOxjH4IyV/TsNeEML7i96Gm9kB0TOXFCdzVgZzBhOGlYPn0qCI6sTjtfVAlEAZoENrQOzaOtY4l7e\\nVZQtQfdGzeiSOuHaVJHVCteBxcqW0SKXdtkOdFGGbpDyQniLo9rlK+lFjvUOnwhKmjLgt658lo/r\\nH2Xv7RVLK9xfnAp3fVINh4MKKgGOQfj2IQ2aOUppfuX+1cVm0GCShCw157ytVwE4GDfptxM0guM0\\nOiH0PazP8Q9mmO0QUS9cjqndKGdJywLpzh2x3phSaIfILckqh/E8pB3ZjSApXYZJyGor5ngeMR41\\ncLwat5tRZS5CaZja0xcGxNCz+ul+iZw6iFJaddJGwUZvwsGsaU9ssYt3rGxUW886SEUuqT2rmgp8\\nGwAu1MJd2rYzlu41zeSCxB8btG/NVVW08D/UgrqhIXHors0QwtBwC5adGSsqIjcVn5uf5SPhLS65\\nDVyh+JVHPs+Pyu/llbfOgREkB/ZEOMN+P27PKqKU0vh+SVUpllpzXFXz8zsfJXIK9rI2x1mDzcaI\\nN9lACsONyTKnm2NUqLmfddHm4auokx+dEN9rWwpqJagyByENu1kXjOD5i7fZioaktUfTzUkqj4Ok\\nZYu9rIgrn/20xanGmP2kzf1xh8Ar6XfnxKmP51ZMh/b7MKVkfhghwhqnXVBJFwob9t5am3Ghd8xO\\nbHvVo7kPY9tnr3oVUSclK1ymhb0Bd92EXLv4ToVs2mS7qmHoXIfpRYuGduaScqmi1croNxJmhU83\\nSjnKGrw4uI0rahoyxxcuGk1tNHerlCWlaAofJSQ/t/ll/iv1YX7jxqMYLdg+6IKAW2IJY6DfnVNU\\nitAraXgFZa3YaE7wZMUvHn+EnpvwB+UFtrMuF6JDvm1OA/B2vMb56AgpDAdlm/p9ru0HotCbTFGs\\nlVALZKps7mq86KMaKGtJXjmsNmcczJsYtyLwbMpTfKeDXP63ToheRdZQtkd65C9kV9aq/iBx3hZo\\ngTu2v0MbW7gH/ZiGV3BYtxDSnvK0YzsCwgZB2WFs26CMsNz0hZ66agDHi9BibejdqCkaEjcxljrX\\nfgDssin2VcSJi0G1Sv786S+yoQp+afYEXzx6lLgf8Knwhv25kPydy7/Ef5J+hmTuUxYKoTRCGXQp\\nEYljNzZHEzQKitwlm3s02hn7u138+x5Fr8ZbS9h5ZR3tGsILU1a6MbPM59pshdXujEv9/Ye6rloL\\n1Gl7mjRzF5EqVPIdXkdxxiGpPFZD63ay8sGCaeFz8/YKvRX7576q8N0KLyzxvYrpYdNqnmcuOJb2\\naZQ5sayrsWNP+Moqnta6M1puznbZQUrrodcKnFJY17AwiGJxene1zRgoHKQ0C0u9dUY7maF3zSqr\\nVGq17aZRI1KbYqVDjQhrikrhuxXLQcyfat4nNjX/en6azx8/AQO45H7nFPZXzvxr/nzy44ySkKpS\\nSKkXNw9Fnrp2Y3Nr2kHOPPcYziNW2zPe3lul2okwg4JzG0d88bXHwdNcOb/NhfYRkzLgq0fnONsa\\ncrV176GuK9ig9M7mhEpLkrmPTh2YWW48xr5TpVGs+ROgQ6hKOm7GrPL52t2znF0aoo0gVCWhU9IK\\nc0K3ZPu4g9aS7DgEV1stvF/blowWVBPPtu0W8uWt7oiOm3GrGthEN7nAbhQCo+0mmZcOkVNQGYUj\\nNYeFT9NZ3Nx9+8w4C0pt1bAqrrK2ITKTNKDWkpVWTNvLiCufnptwxj1enOAVt8qYfzJ5nh9pf5PH\\nvO8U3v925TeZlgGHaZO49KwoSNWUtWKchIReiadqBsGcSR6yN2+z2Rry9cNN9nZ6NPsJT63u8PNv\\nvIRyar7v/DWebG1zULb4neFlHm/t8lR0932t1wei0MtGiZ679gWMaoySGGHNSbIEc6vB0I8YlwOq\\n1gM7oLHSNt8g7oUIAUe7C2aNWkCRAoP2F0oLtQAUzReIglxQNgz1ao1MJTrSREZw6/4y0rOnYplb\\nRUzStMM/J7bONH9kUQbp6gIZmtprfBUIetcqvEmJyioalebo+TZVJOhe0+QdWzBUbvk2soCya3CE\\nIZI5SgjuZX1OR2Nupkvcr15lXUV8PTd8OHBpBjnxKEIkCuMLljbGHO50cceS8nRJs2NzM3udOQ2v\\nYCWaEXd9DlebhG5JP0g4e+FtdtIO37i5RZp6fPLCDbpuwtePtjjMHq46I2rkpKkFSLmNklIZKkfZ\\nfmopOb7d48jv8HYtkA07o1FuTTkKwNOM7ndAwpt7NmkLZciNNbIIYTBRhZA25Smfe3YnLqRVwrRK\\ndK7QjsZVNW/truJ5NVoLRKbQviFvaWQvx8xd8GpM4tggamUQwlDlCmmgCqB9W+NNK1RaEUnB9ici\\nirYmetej6C5AZrmECMrCYbk1pzYCVyh8HIZVkydb2xxWLRJ9n0h63CpjnvSaNL2cg0mTInUJGgXn\\ne0Pe2V/BjD280zNOdSZklctWd0TbS1kPplzuHLC33qLpWjv+D3/8VbbzHr9+/1HiwufT62/Sc+Z8\\ndXKBvfz9KTP+MJ9uI2WaBifrnDs1pfJAaUyhePXOGb7lbKBrddKqC9yK41ET5dS8u7OMMYJ7B337\\nPkp7kvID22ohyhFgVTNz65QvCgfVKei0EssbAhxZ88r+acIFM16njn3POxVbKyOyyqHbnrKftEgL\\nl2U/RgnDUREipSFvGbrv2KQ6lWm0J5mel6h2wb07S/jdjNAvmeU+y0FMaRQ9Z05pHEpjwWV3qjZP\\nhnfZr5s8hlXdSCTn3CaurJmXHtM0oBNmXO3t8PXDTZLY59SpCRfaRwyLiMudfXpuwilvxNXWNtsr\\nPTpOypIT8+MvvszNfJXP7jzNXa/HZ9Z/n0E35rfjx9gu35/J8QNR6IVkQSuyjBGVCZsH2qhh6FrK\\nW2HRo+7I9sGM5ASYpT2Dbi6GcUbQW5oxut/BX0rtIs0DMIJ6Zil/OjTUYQW1wB3agVymYBIHMHXQ\\ni6/FyQVVSxOszzFG8CcuvsE3jje5eW8Zb8dDLUKuAXtin1ujSzoIcBNt+389y0QpG2KRfCTIB3ph\\nmReIbsFaf8pZJ+ZX5+e52rjPG/PT/O7uRa4NOpx2an55/AwvrL7C4719Do7aqEGJ3glJry/D5YJi\\nuYJCEY8jgqY9Gc0Lj6ljX4b11pS9uMW88vjct56CSnL10j2aTs5rh6ds7zBIqR5y60YIqzkX0lCm\\nLmQKEdb4jYJsGCBKaVtOrsZMLFm0VPYmRS3ANbjNAs+v0Fqw2R9x/f4qq8sTBmHCwbxJrQWTaQMq\\nidMoqR2JVAY99CyHqF1zFDcoJz6lsURFAehOxamNIQb4j57+Gu+k67y8f5b9+z3qTJ0MfXWzwhx5\\nFG1B1vdRuYd2rINVVhavYCMna5xugetVlIWlGl5tbeMKxbeKjMv+DjeKNf7N/lV+qPUtLkmPnx+/\\nyF9ZeoOzzSHbkw7tRsbhvR5vXT+PvBjjrqSUpWJ70mGjPaXnJ0yKgJkTUBvBucYxt+YDXKH52bsf\\nQ2vBn7zwLVoq4/XpaaQwLPk2WONhf8S/tb5p6lHlDm5Y0mxkjEcNMFBXCsetiachaMF8cQs1WuIF\\nFd1mgq9qKi15rLfPyztbPDI4ZC2ccT/pIjHcnXTRWtAIc1yntsleB20oJbJRsjdvM5lGTMyijhiI\\nBgnPbdwjq11+6tTnuVGs8cXRY9ycDXhvukTk2njIKMgpdIOyJTh+3DmBsJlBhjACGdq838ZqzlI0\\nRwrDUd7ku9pTtpwpGp+jOuNxD94pNZ8bPctT3pfpqYjfSSWfCLX1RMQ9zvWHvL29xr++/iyrjxyx\\nsTJmXnrcmC6zHk0YuHNGVURTZWgjeSTc50a6iitq/u6d7yMtXX7y3JdoyZSvxJdwRc2qO33fa/uB\\nKPR+UKC9iiz27PXMB5lJSOzpr1Yg1jI8tyY7CpGtkqiR4zkVeelS5A6msEHdgJUidkrK3CGfBFAL\\n1EwRLsK3m3ccKntIoGzbPqzKBOXMR+WS7nX7s2RFUK7UVJXkiY1dZlXANLN/37mX7nJjewWxF9hr\\nffIAwWqoFjZ/izq2csrkB2a0o4yzYcJ3L93gTjZg1Zsyqqx+fa4lkcz5rfHjtJ2M41s9ZpdDIKbj\\nWL1035tzdXOHvXkL3U44VgOcoUu9miNdja4kdS2ZpAFLzTnjLORS74CXb59DG8GHVu6y32uS5y5v\\nvHOGTz/7BpNZSKeVsjtr0fIfLv2qG2a0g5zRPER7EuPV1KlDnrpWFulp+usTWn7B/aMuUZTTj1La\\nfsas8JkXHnnpnAR37M9aDPoxceYzmkXoWlLOPJyxA80a51qE9KyxRrc0OrRKnjgOELmkecceEvKe\\nwawWFLXiw6u3Oapa7GUWG/Gjz3+DL++f53DYtqf6iWejC2eGeMMGPxhlEQBVaDj90i4dL+N0NOZ7\\nu29yI1/ljDskM+5J/zTRLn+QXGDFnfL2extk56wk71KwhxKSx6JdGps5d5I+m+0Rr7y7BaOA1mpM\\n5JXkpQ0mF6LNqWjCtAx4vnObz95/hqJWfHzrBrtpm7Ry+eUbT/NTT/wGt6Z91htTdnSHjvtwh+wA\\nS+GcfpiwO2tTujWeX5FnLknm4Xg1UmkurhzRdjNuTgb0w4T1cEqoSsalJUZmtUOpFdoI7sY9tnoj\\nRnnE7rxNUSlmcUg58a2f4U5zwRECWhV49nkfxhE6U/jbnh3Adw2t0zlx6fNd/fe4XS5xK1/GkTV/\\n/cJn+Zm9TzHMI5puzk5t09+cOWTLUHgLNHbsolsln7x0nb4352xwxA823uZO1WZVxXSlpiM9JBIN\\n/E66waYz5At3H+Wnln+X0tRcdmOgyXc1r7HlH/FmcoonHtvhV25d5WjUYnNlSM9PKLRDVrvcz3ps\\nhceMygYfbr7LL+6/RFa7fM/GW9xr9ZiVAT9z8xP89Uv/gvfiJU5HY9J68L4H7R+IQt8Kc+LMh0Ii\\nFuEBRoCznFLGHmE3I/BKZm/3oVVzZmXEJ1ev8/mdxyhrRVUqur050ziknnroesE1qSy3RCYS7Wvy\\nwYKZkltQVtWw6hnj2DZKcOTSfa+m/do+9aDFzv9Q4RcOS+05gar4aPsG58JD/pl4ltu/twXnU5au\\nHBK6JTsvb+AmhmgvR7sBlW+plGXbUK/lnG7NKbWk1pJEe/xI/+uccaYsS4EvHFzhE4htfvrwj3B8\\nY4DQ8A/vf5yfNVZx8Jt7jzIvPC70jrjS3+PN4RqPPX2H4zTicNhmqWsL4HIr5kxzxHuTJfb3uhy8\\nO7DKE2n49fvP8n2ffJUv3noEXMOvvfIkuJrjYYDxa/Tyw7XKr0QzjrOGVbnUdhMWyrC6PGEcR6x2\\nZkRuwdvXTyEbFc+t3eeHBq/yueNn0EYwzXweGRxyd9pjNGlQ1xJd280MQKcOIqipljVCQNGVljEf\\natTcxgiqAuSeQ/umYfDKEVUn5PAv5gReyVZ7RFPl/LHW6zwf3eKXnef5l+88xfnVI5659Da1EXzh\\nlat4U/BHNWVowyHiTUPVrWmuxnQWSqVcO2Ta5TOdbzGQIUrIRUC05LKb89eOz/POG2fANfy9ve8F\\nLF7514cWc/tYa48Pd2/xzekmP/7017ker3BnYgv/KI/YbIw4Fx1xJx3w5uEaX7t9liq1ypG/d//7\\n+Asf/QK/svcknlvx09/+PjuAH7ZxvYqtwcNPmHoQlZmVDlrbZ9TzS66s7rETd9hsjWi5Gb9141HC\\nKOfTp97iRzuv8E/GH0IKzbgIebSzz814id2sbXMXapu/DPaW4PkVamCdrkVHQSUwgUbEyqrdckF5\\n4DF4F/rfnlO2Xcx/c0TkFosc14Tvj/b4SLDN72en+AcHn+Bqa5vTgyHDqslb+2u4sSVv1r5F/9aN\\nGtku2Vwd0nIzcu0wqSKG2uNjQYXGP1HXAPRkwCvzc/zlt/8kUmr+9/Fz5MbBFxWRyjkqWzwR3udj\\nrWu8HF/kzz/6O9xIV7k2W+VS84DDosVWeMw5/5Bb+TJfH27x+XuPMU99pDT81M6P8Xde+CV+6fBD\\nRG7JX7r+J1HCcGvcp+GVXO7+e6S6Gc0iirlnr/ILWE9wKiY9ioiW57TC3D7wzyfcH3aZ5R5fH22x\\nt9Mj7GSYxGFUtqzJQViYE56G0PZdjWNQySI8urDa+aJtToa9xjyAj8HBM5Lh5Q2cDGZHBW6zIC0d\\nXts9xfnGES2V8eHV2/yGWebF87c5E40YFg221QZOZijaLuFByfSsReqqszGhV6GNYBDa3XfdHfO1\\n+UXOdL5JJF18Ya9f59wmSe6hEoE3Fbz35S2iJ0c8v3aPUBV8/t3HOAyajKXtUd8bd60UMSw4Hjf5\\nzBMv83+/+xzzwvbF2/05rY2cg2+u2lzKCzHvTZfYGox47MIed+c9LjQtGmFcRpwORg91XbfjDtMk\\noCqs2gYt2FgbcTBqcWppzHIYc6l5wKmnJ7w9WmWUR3w1vsjXdzZZ70yZpz7XqhVcVeO49v/Z9+yt\\nI0l8tDKYuWMdsYV9cKq2dUxKK7FfFH7D0XOG8aMDVCqoEjssPM4a7M3bPBru0FYZf7z/Kr/5zSt8\\n5MpNLge73Cv7fMGAqA1VQ9I4qBlfcNC+obcxIfJKHFnTWvBn+irm5WyZ748mdigs7DPXUxG7U3tj\\ncGaK3/7GFZ68eptPLF0jECW/ePdFel5C4nhURvLa+DSO1LSDjHuzLv/1+S/yD+5+nEkZIIVhvT2l\\nv7zH7796GSMNG+eO+FZ8ms3GiB9Z/ybvZSs8EtrB+qQOOeM+/EK/m7atMMIIPK+iriXPbdzj+miF\\nK/09+t6cK9E2W1eHvDY9zaiK+Hq2xdeOz3K2OWSUhbxWnCZwShxlWzLtIMMRmqPEmgOz1MNou6ED\\nyHaJnjsL6eOidRYYjp+vGT0eojJBmIQQwW7W4U7S5+ngLl1Z85y/zd8efh9/eukP2HJG3JBLlIWD\\nV1k6ZHhomJ4T4Gsun9qn7WWktcv58MjmBcuC7bpgVfmAvZEpYXEHf3C0hS4Uderxv33zu/jBK9/m\\n073Xcan52eNPEMjShgYheTXexBWavj/nZrLE/3jq3/DXdz7NqIyQwmrvXxzE/ONvvYBUhqc37/F6\\nssWlxgGfWfkKN/I1HvH3UBjGOuKM8/7W9gNR6IuZh/A0xhO0VmKeW7vPV++epbs+JfBKlDC8vn0K\\npTTGwPCoxTy1dv30KAJl7IDStalNTqxOQifkwtVaRzZ0omwavKlAFQszTGl18XVkCXnezLLJqwCc\\noUupDKmj8d2Kb082eKZ7j8ejHXY+dZ2+l9B3bAJR1dZEuznO2BIj27Q4ftYhkAYlDEWtqLRkqzlk\\n2Zlx1jsiEoZhnXOoa570bD/9hY27fGl6CfVIwdn+iNVoymHWZF55PL6+z6QIuDvsEfoFcRzQ7cyJ\\n/IKqkvziOy9YVQngSE3kFxjg0kdusxrMWPWnuLKm58y5maqtLzsAABJ6SURBVC7T8TLGZcRGMGY7\\n7fL65NRDXdfhtIHnVThezdbKkE8uX+dXd67w+MaefcGF5rf3HsFTNQZ493iJySIE5s5hD8exjPDS\\nqakrRZU4ZK71WJjKHgpEWMPcQbcq5MxB5Pb2VmnrVK3aAndsI/+0Y41q5cg+OxM3YCma8434HN/V\\nvs6KSvgzL32VjpMs9Oou+JrObYM7KRC1puVFTK/WKGlwVU2hHSQZW+ERKyqmJUskIRrDcT1nRVmJ\\n4Esbt/lSdYFuI+Xx3j6ngxH3sj6p9nhp5RbDosG3RnZe8iCHoONZSfE/uvcxQqdECoMna7peijaC\\nH/jQ65z2R5z2jlHCsKym3CjW6DgpcR2w4Y7YLbq8Upx9X+7JP8xnnIU0FpvulcEe39/7Nv90/3mu\\nDnbpugm+rPjVo6t4skYbwRujDYaF1YG/NVpFCcMkDcg9e4qPkwDHqRGCE2eyHxSkcx+3VVDOPHSm\\nEIF9VkRYU0UCMXVRM2VZNYFhOrIFs+0HLPsxL6cX+J7oGsvK8Bcf+TyFUeRGMdMhyqlp39b4k3oB\\nInTgao4jNd4iREIKzXnvkNOOj4OiwopBclMRCStz/sTKDX7bCFaiGU+377PlH3G7WKY0ij+6/AZH\\nVYtvzjZpqIJ78x4XW4ecDsa8M1vlf97+QRqORZRYsJld25989ktseUdsOkOk0Axkzo1yQFclzLXP\\nKWfM3bLPy+UFXngf6/WBwBSH3QxT2Bc3z12+fbROK8opa8XxpMHuYYci9sj2GuTDEDF3KO83ELFj\\ni7xXWy2zXyOi2gYr9wvolIi1DGcu8EZqkWxkk3rKpoUhOYlN5PHG4iQQ/AEn250IROxQVZLxcZOn\\nuvcZVRGJ9um4Gb+/e5btvMtu0ibcVqANOnSp2wF1KDHKkB5FFJWirBSRU7CftmksMu0UsFN7dBcP\\nVW00X7u/hTFQZA4tzybWZ7XDchBTGYmvKtpRhhTQbGZsdUYoqXlyY4cyd3hyfYc49fn46ru8sHyX\\nwKlsxKEqiWufvbzNUdlCCkNtBPPK4/ePzvPtwzWO0oerulntzshzB6k0aeny8ugc/dCas+5Me7x1\\ntMoojrh32GN/2Cade9zZXiKde7iupXA6To3jaKvGSBTNTkqjndFfmSJmDmLsQg1y6qCbNSaq8Q8V\\n3lTiDh28Y3WSNOTMrZLKHSvKuUtZKW4dDXixdZNx3UAKTd+Z82/2rrJfdrmTDvB27W1L+4qy7dkQ\\nHGUYjhsUtWJW+ISq5F7WXxR5a4aa6OykyAO8fryB1pI48+l7c5TQTKuQZW9Grh18VdELEiSGtdaM\\ny+19HKl5aeUWoyTkaneHvXmbTw/e4KO9d/FVxbCIaKmMSd1gp+wyrJso7EY4rBr8xugKXz66wHba\\nfajrCnC6NSbOfZTU5LXDl6eX2AinVEbyXrzEN4ab7M3bvHm4xs3RgMO4wTe2N9mfNRGA71iJtK9q\\nWn5BmbqsdWYst2LOLR2TTgOSowhTC8rYw23nuI0ScWxnMuLYQw3dE3OlShZy54nLLA4pasU74xVe\\nCG8y1AEaiETO546fZbvuWLbM/cYCRicpWpKyZSF0t0c94tKn0A6uqLlX9pFIKmoc1P/LHAXwxnSD\\nSktGecSSO0OhmVQRfRUz0wFSaFb8GVJozjRGPNHYJtEeP7D8JjvzDi+0b3Ev7fHH2q/xydbb+LLi\\noGijMBzrBjtVj2PtLyYCMKyb/NrsSX5vcpmjsvW+1ksY83CJhf9/Pmf/15820UZMkTucWhpzZ2eA\\nKZRV0dQCqgcgmQetGYGYK8yDTMdEWccroH0b3oEGYQTBrgWMGV8jKonsFlYtIG1f1xioDsITJxzK\\nYFyNO3SspDIRdK8bkhXJ/NkUDnya5y1vxFGao3cHtG5KBm/mi5xLy3xPVhzGl6FqaYyvCXp2zrDV\\nHfFja9+gNgJP1Kw5E26XS/xE+4CXs5p/dPDd/N6tC3TbCb0gZTmM6Xtzvrp3jqafc7W3gy8rfFnx\\nTHSHV5Mtbs6XWA8mxLV9OENVcj/pMvDnzEofT9asBxNS7XFtskJW2QFnrSXtIKPjpUROSahKfu6F\\nnxf/36v0h/+c+8f/izmzkLhd6e/xtZ0twGqJ89I5iV2MIrvxaSNIYh/Xr/C8inganhwA1EJSqWvb\\nepPbAfVajhtUVKViuT/DkRpHakotKWvF4cGCyy9AOBrl1JTDwAZY5JLuO4J0WRA8N2S83+L8edvu\\naLgFb7y9SXTHoXfdnuC0a7+WZEUyvVhjmjVOULHUm9H2ci539vmzgy+TGYdIlnRlxe2qyccDmOiU\\nv338PL++/RinW2Nabs66P+GUP+JLw0t4subFzi18WRKIgueCe7ySneHN9DSb/jGJ9khqn5bKuJ0N\\nWPFmjMoIX1aLn/u8EZ8irV17czSKgT+n46Z0nJRIFvylK7/60NYV4NHP/lXz9Po2SeXxfO8OX9h9\\nDLAFPC1d0tKxrtXICglqLRkmIQ2/IHAqjuIGReFgtKDZyKj0Yp4mDOPtNuFKQiMoyCvF2d4IT1Y4\\n0t7UCu1w42AZY+zaOk5tpZvHTRyvppx5tK65ZMuGcy/c49bBgA+fvYUShoYq+PyNx5C3Q7rXbDl5\\nwHxPVgTphYKgleM4NZeXDui4GU+37vHj7bcWm4XCFy6xzumpiNpo/uW8yy/svsTZ5jEdJ2XTO+aU\\nO+I3JlcIVcl3Na/jiopAlDzv13wjV7yWbfGIt0difObapyVTbhYrrDkTjusmgSg56x0y1z6vJmdJ\\ntEeuHSqtrDPXmdNRcxqy4McuvvLvXNsPRKG/9M//milzB10oVLB4sUae1bE3NG43s/I8LSy6YK5Q\\na8l3JHu1QPZsPqfrW0mUDGqUU6OUId+NMI7BG2RIaahKRTX2bIBzVOFFJcUosDS8doUbFZTjANks\\nUYu2gRDgvxmyEMDY/Msjc5L16KSG6QVOoulUZumUbmI3iXTVfs/FUk3/1JiyVgRuxSQOqWvJX3rm\\n87yRnGbJjUlqj0R7uKJmxZuy4Y65nq0xqayFeyMYcy1epdKSc41jltyYr4+3eLZzj+ejmwzrJr87\\nvcztuE+tJTvTNo8MDpkUIbd2lnh8c5e+P+dCdMQvvPkhfujyt7g2W6Xvz/nFF3/uoRWEF3/9L5pZ\\nGmAMRH5JrQXjcQNTC/xGwUonZpIGlkihJfNJwKn1EZM0sBiLStLtzqm1pBnkjOehJQl6to1xf7+H\\n61es96a4qiYpXQ7HzRPtdjfM2Bu1qAqHZjtluTnn3mGPtf4UJTVFbRUfh28u442tK5ZFmEXZXOT8\\n5jC7WCFziUptBKIbWxVOsmozakUlMGsZVzZ3qbTEkzV78xZJ4fIzT/4TXk+32HBHZMZlXNv2xbIz\\nZc2ZsFP2OK6bTKqIDW/EG/PTlEZxPjzkvHfAV+JLfKhxk6v+Lol2+OL8Md6er1ulyrzHle4uszLg\\nD3Y3+eSZG6x5U877B/yNd76f/+KR3+Pb89MsezP+6tXPPdRC/8Nf+UkzKwLSyqXt20J9f9y1RqpG\\nymZ7xHHWQBtBrSVHswZPrO1ymDaZZj556bLemZJXDm0/42DepOEVDAILcLu+v0zol1xeOsCTFcO8\\nwb1xFwMMGgmDYM6tcZ+scFluzdloTHh3vMTZzpBAlUwLK6t7/e0t3NECPiixWOKWfRdlbgs7lUDG\\nVtbtzhau9RVD2bewvObynE9tXmdWBieHqHEe8g8v/1+8Vaxy3rVzrsO6QW0ka86MNVWzUzkc1E3G\\nOuKCe8h75TL7ZZfL/g4rKuYr6UU+Gr7LVc8lNQVfTPtcy9fRRvLOfI1nW3eZ1CG/tX+ZH9p4nTVn\\nwiPePn/55g/z3539NV5Pt1hypvzEpa/++1Hot37ubxrEgj5YS2ttn7nIfm6L/55HvZGjHE019nC6\\ntvdcZw5OUFElDm6zoEw8tk4fkVUOs9Sn10iZ5x6tIGd/3KKYeQSdHCEMWeIhjjw2r+5y/7BHGOXW\\n0t2qLL1yrGg8NrKn9vtdwqXEuuxyB6MtY6POFzTDUxniwLcP07L9NwtlEEqjY/cklkzFEuOAblqk\\namN1zrPr97ja2uagaLPiTQlExbCyV/4t/4i59rmbD9hOu0ih2U/a9AI71L3YOOS3di8xCBP6/pwb\\n42Uitzw5oU/LgK6XcHOyxH969sv8ysFT3JsuKIlpQJZ6rA4muFIzyz0Ct+Krf+RvPLSC8Mg/+5+M\\nUtbpqRcuxTTxWerNyAqX8X6LpY0JDa9gZ9im304QwjBZYBAm85BuM2EcRzyzcZ/KSCZ5yGo0Jak8\\nVoKYa5MV9qctVtszS+NMI46HTb7n0nVuTJZZCmNeu3uGVjMlLx3Sw4iPPnUdKTTf3D3D+cExkVMw\\nzsMTPPDxPGK206J/Zmw12wKWVyck+YJTo2pmcWhJicIiFQisP6CqFGeWR3xq9Rrn/EPrDnUmuKLi\\nsLI3jPPeAYn2T6R/rqi5lSzR9+a4ouZKtM3nDp9mxY8ZeDFvz9YIVEnfS2iqnHEZ0XCsHPO/XPsd\\n/tX4aW7EK3jSYnTHSchqa0agKgqtaLo5//wjP/tQC/3zv/aXjTGC0LWbrpKaURJyujMhrxxu7i/x\\nyPoBbS/jvdESp1oTAI7SBkvhnP2kyXI0Z3fW5qPrNym0w7CIOB8dcVi0WPWnvDNb5SBpcbo5BuAw\\na7IzbvOnLr7KG9MN1oIZv7t9ntVWTFq67B51+NErr+KKmt/dv8gT/V08WZHWLrPSzn52kzZ39/ps\\nrg3ZPu6glOHc0jHD1AIBA6fiaNY4mRPkcw83qGhEOUWluLx8wI+sfpMzrs38HcgcV8BYO5RGsuWU\\nlMZws4q4UazZtc2XOe0NqY3kanCPfzx8iU1/SEclvJVs0HRyes6crkq4X/TpOXN2iy7/ef+rfG52\\nhWvJGr4s2c/bHKZNzraOCVWJNgJfVvytp//pv3NtPxDDWOHXMHUJeynJOITp/9PenbTIUYdxHP92\\nLV1Ld3X39PRkxsySkWRcII5R8KAnPQkRBHPwBeTuu/LkQVAT8SBCIJCLRpAgiHGSMDHO1nvX1rW0\\nhx48e0ggFL/Pi/jWv6jn/5RNrTOnmNmwgHw1p1bWKM6nN/KpTS0xIchpNhIKv0aa2phuzum0gWEs\\naPkJg5mPaZacTprMwzqGU5Bl5/P2ZY3SLQnqKZu90fLhYC2w3YzcWJCVnN9sXHBx94zBtEHdyml7\\nCXFmESUOhlmQrdYgtKk559M+ZW35wIosLuyMmTUcvHrGaOJTeAaOn7HTHfLx+u9kC5Ot+oDbZ/vs\\n+n2uNx9wmHe4l1/BqeWMCp/ASGibMXHdZtNZLiaLC5tZ5vCa+w87u30Aduw+39v7rNohtw6v0vUi\\nhonHjcv3ifJ9vj15m+MoYDMY8+Dpckvl1a1nPBmt8OpKn37oEyXPdx+962TMpi67r/Q5GgeEU5fu\\nSsgscUgTG68bk+YmeeFSlgb9UZM8tmithlxozOi4MdO5g++m9JMGtdqCNW/GSRxQNwv+mvQYhD5N\\nNyWc15nnJqaxwLRK1p0JrdWYUbY8Qbe9hEnNYd7KmJcmDavgw+2HPA67WLWSS80B48wjypcTS8W6\\nQRg72H5GURjkhYFXz5hGDtc2/mbQ8vGtOcPUZ5K4OFbO650TbvR+BmDDnHBrco2uFXK98YhnucUR\\nHQIjZlA0CYyYjhnRs2Zcqi8Xk0VlnbOsyV79iJsbdwmMmK6RcMfewzdSvjp6j53GkGdRmy+2fuSH\\nxT7fja7xJOqy2+hz5+kVAD7a/pODWY/LzVPuD7YZxM9/TXHbTTiZNnlz9YiDcY9+6LPdGTFJXaZp\\nnd7KlDi3SQuLoqxxOGmTZjab7THr3vLy1zD1abkJR0lr+W3CnXAQ9fDMjIfhGidRgHd+YMlLA9so\\ncOsZ6/aYYCX570C04kSATytYXvozjZJPLj7gIO4t34rdKUPLJyyWm0HnayZxZhM0ErLCJCtNul7E\\nWdTg/QuPOGs3aVgpT6MOSWHjmhnvdg75vP0L2cJgy4JvZtts2kPeqRsMy4SotAmMOYMSghp0jJQN\\na8wla0hgxExLj2nhsWdl3Fy9i03JmrmgY0Z0zJAvTz9g0x1xnLb4bP1Xflq8wdfTqzxOelzxj7l9\\n9BYAn278xh/RBnveMfdGl5lk/+9Xgi/FiV5ERF6cl2LqRkREXhyFXkSk4hR6EZGKU+hFRCpOoRcR\\nqTiFXkSk4hR6EZGKU+hFRCpOoRcRqTiFXkSk4hR6EZGKU+hFRCpOoRcRqTiFXkSk4hR6EZGKU+hF\\nRCpOoRcRqTiFXkSk4hR6EZGKU+hFRCpOoRcRqTiFXkSk4hR6EZGK+xc5X8SsKDbLFQAAAABJRU5E\\nrkJggg==\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x7f84b1fc6b90>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# this next line helps with being able to rerun this section\\n\",\n    \"# if you want to try the outputs of the different crop strategies above\\n\",\n    \"# swap out imgScaled with img (original) or img256 (squeezed)\\n\",\n    \"imgCropped = crop_center(imgScaled,224,224)\\n\",\n    \"print \\\"Image shape before HWC --> CHW conversion: \\\", imgCropped.shape\\n\",\n    \"# (1) Since Caffe expects CHW order and the current image is HWC,\\n\",\n    \"#     we will need to change the order.\\n\",\n    \"imgCropped = imgCropped.swapaxes(1, 2).swapaxes(0, 1)\\n\",\n    \"print \\\"Image shape after HWC --> CHW conversion: \\\", imgCropped.shape\\n\",\n    \"\\n\",\n    \"pyplot.figure()\\n\",\n    \"for i in range(3):\\n\",\n    \"    # For some reason, pyplot subplot follows Matlab's indexing\\n\",\n    \"    # convention (starting with 1). Well, we'll just follow it...\\n\",\n    \"    pyplot.subplot(1, 3, i+1)\\n\",\n    \"    pyplot.imshow(imgCropped[i])\\n\",\n    \"    pyplot.axis('off')\\n\",\n    \"    pyplot.title('RGB channel %d' % (i+1))\\n\",\n    \"\\n\",\n    \"# (2) Caffe uses a BGR order due to legacy OpenCV issues, so we\\n\",\n    \"#     will change RGB to BGR.\\n\",\n    \"imgCropped = imgCropped[(2, 1, 0), :, :]\\n\",\n    \"print \\\"Image shape after BGR conversion: \\\", imgCropped.shape\\n\",\n    \"# for discussion later - not helpful at this point\\n\",\n    \"# (3) We will subtract the mean image. Note that skimage loads\\n\",\n    \"#     image in the [0, 1] range so we multiply the pixel values\\n\",\n    \"#     first to get them into [0, 255].\\n\",\n    \"#mean_file = os.path.join(CAFFE_ROOT, 'python/caffe/imagenet/ilsvrc_2012_mean.npy')\\n\",\n    \"#mean = np.load(mean_file).mean(1).mean(1)\\n\",\n    \"#img = img * 255 - mean[:, np.newaxis, np.newaxis]\\n\",\n    \"\\n\",\n    \"pyplot.figure()\\n\",\n    \"for i in range(3):\\n\",\n    \"    # For some reason, pyplot subplot follows Matlab's indexing\\n\",\n    \"    # convention (starting with 1). Well, we'll just follow it...\\n\",\n    \"    pyplot.subplot(1, 3, i+1)\\n\",\n    \"    pyplot.imshow(imgCropped[i])\\n\",\n    \"    pyplot.axis('off')\\n\",\n    \"    pyplot.title('BGR channel %d' % (i+1))\\n\",\n    \"# (4) finally, since caffe2 expect the input to have a batch term\\n\",\n    \"#     so we can feed in multiple images, we will simply prepend a\\n\",\n    \"#     batch dimension of size 1. Also, we will make sure image is\\n\",\n    \"#     of type np.float32.\\n\",\n    \"imgCropped = imgCropped[np.newaxis, :, :, :].astype(np.float32)\\n\",\n    \"print 'Final input shape is:', imgCropped.shape\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In the output above you should note these alterations:\\n\",\n    \"1. Before and after of the HWC to CHW change. The 3, which is the number of color channels moved to the beginning.\\n\",\n    \"2. In the pictures above you can see that the color order was switched too. RGB became BGR. Blue and Red switched places.\\n\",\n    \"3. The final input shape, meaning the last change to the image was to add the batch field to the beginning, so that now you have (1, 3, 224, 224) for: \\n\",\n    \"    - 1 image in the batch, \\n\",\n    \"    - 3 color channels (in BGR), \\n\",\n    \"    - 224 height, \\n\",\n    \"    - 224 width.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": []\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.14\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/Loading_Pretrained_Models.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Loading Pre-Trained Models\\n\",\n    \"\\n\",\n    \"## Description\\n\",\n    \"\\n\",\n    \"In this tutorial, we will use the pre-trained `squeezenet` model from the [ModelZoo](https://github.com/caffe2/caffe2/wiki/Model-Zoo) to classify our own images. As input, we will provide the path (or URL) to an image we want to classify. It will also be helpful to know the [ImageNet object code](https://gist.githubusercontent.com/aaronmarkham/cd3a6b6ac071eca6f7b4a6e40e6038aa/raw/9edb4038a37da6b5a44c3b5bc52e448ff09bfe5b/alexnet_codes) for the image so we can verify our results. The 'object code' is nothing more than the integer label for the class used during training, for example \\\"985\\\" is the code for the class \\\"daisy\\\". Note, although we are using squeezenet here, this tutorial serves as a somewhat universal method for running inference on pretrained models.\\n\",\n    \"\\n\",\n    \"If you came from the [Image Pre-Processing Tutorial](https://caffe2.ai/docs/tutorial-image-pre-processing.html), you will see that we are using rescale and crop functions to prep the image, as well as reformatting the image to be CHW, BGR, and finally NCHW. We also correct for the image mean by either using the calculated mean from a provided npy file, or statically removing 128 as a placeholder average.\\n\",\n    \"\\n\",\n    \"Hopefully, you will find that loading pre-trained models is simple and syntactically concise. From a high level, these are the three required steps for running inference on a pretrained model:\\n\",\n    \"\\n\",\n    \"1. Read the init and predict protobuf (.pb) files of the pretrained model\\n\",\n    \"\\n\",\n    \"        with open(\\\"init_net.pb\\\") as f:\\n\",\n    \"            init_net = f.read()\\n\",\n    \"        with open(\\\"predict_net.pb\\\") as f:\\n\",\n    \"            predict_net = f.read()        \\n\",\n    \"\\n\",\n    \"2. Initialize a Predictor in your workspace with the blobs from the protobufs\\n\",\n    \"\\n\",\n    \"        p = workspace.Predictor(init_net, predict_net)\\n\",\n    \"\\n\",\n    \"3. Run the net on some data and get the (softmax) results!\\n\",\n    \"\\n\",\n    \"        results = p.run({'data': img})\\n\",\n    \"\\n\",\n    \"Note, assuming the last layer of the network is a softmax layer, the results come back as a multidimensional array of probabilities with length equal to the number of classes that the model was trained on. The probabilities may be indexed by the object code (integer type), so if you know the object code you can index the results array at that index to view the network's confidence that the input image is of that class.\\n\",\n    \"\\n\",\n    \"**Model Download Options**\\n\",\n    \"\\n\",\n    \"Although we will use `squeezenet` here, you can check out the [Model Zoo for pre-trained models](https://github.com/caffe2/caffe2/wiki/Model-Zoo) to browse/download a variety of pretrained models, or you can use Caffe2's `caffe2.python.models.download` module to easily acquire pre-trained models from [Github caffe2/models](http://github.com/caffe2/models). \\n\",\n    \"\\n\",\n    \"For our purposes, we will use the `models.download` module to download `squeezenet` into the `/caffe2/python/models` folder of our local Caffe2 installation with the following command:\\n\",\n    \"\\n\",\n    \"```\\n\",\n    \"python -m caffe2.python.models.download -i squeezenet\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"If the above download worked then you should have a directory named squeezenet in your `/caffe2/python/models` folder that contains `init_net.pb` and `predict_net.pb`. Note, if you do not use the `-i` flag, the model will be downloaded to your CWD, however it will still be a directory named squeezenet containing two protobuf files. Alternatively, if you wish to download all of the models, you can clone the entire repo using: \\n\",\n    \"\\n\",\n    \"```\\n\",\n    \"git clone https://github.com/caffe2/models\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"## Code \\n\",\n    \"\\n\",\n    \"Before we start, lets take care of the required imports.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:root:This caffe2 python run does not have GPU support. Will run in CPU only mode.\\n\",\n      \"WARNING:root:Debug message: No module named caffe2_pybind11_state_gpu\\n\"\n     ]\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Required modules imported.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from __future__ import absolute_import\\n\",\n    \"from __future__ import division\\n\",\n    \"from __future__ import print_function\\n\",\n    \"from __future__ import unicode_literals\\n\",\n    \"%matplotlib inline\\n\",\n    \"from caffe2.proto import caffe2_pb2\\n\",\n    \"import numpy as np\\n\",\n    \"import skimage.io\\n\",\n    \"import skimage.transform\\n\",\n    \"from matplotlib import pyplot\\n\",\n    \"import os\\n\",\n    \"from caffe2.python import core, workspace, models\\n\",\n    \"import urllib2\\n\",\n    \"import operator\\n\",\n    \"print(\\\"Required modules imported.\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Inputs\\n\",\n    \"\\n\",\n    \"Here, we will specify the inputs to be used for this run, including the input image, the model location, the mean file (optional), the required size of the image, and the location of the label mapping file.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Config set!\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Configuration --- Change to your setup and preferences!\\n\",\n    \"# This directory should contain the models downloaded from the model zoo. To run this \\n\",\n    \"#   tutorial, make sure there is a 'squeezenet' directory at this location that \\n\",\n    \"#   contains both the 'init_net.pb' and 'predict_net.pb'\\n\",\n    \"CAFFE_MODELS = \\\"~/caffe2/caffe2/python/models\\\"\\n\",\n    \"\\n\",\n    \"# Some sample images you can try, or use any URL to a regular image.\\n\",\n    \"# IMAGE_LOCATION = \\\"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Whole-Lemon.jpg/1235px-Whole-Lemon.jpg\\\"\\n\",\n    \"# IMAGE_LOCATION = \\\"https://upload.wikimedia.org/wikipedia/commons/7/7b/Orange-Whole-%26-Split.jpg\\\"\\n\",\n    \"# IMAGE_LOCATION = \\\"https://upload.wikimedia.org/wikipedia/commons/a/ac/Pretzel.jpg\\\"\\n\",\n    \"# IMAGE_LOCATION = \\\"https://cdn.pixabay.com/photo/2015/02/10/21/28/flower-631765_1280.jpg\\\"\\n\",\n    \"IMAGE_LOCATION = \\\"images/flower.jpg\\\"\\n\",\n    \"\\n\",\n    \"# What model are we using?\\n\",\n    \"#    Format below is the model's: <folder, INIT_NET, predict_net, mean, input image size>\\n\",\n    \"#    You can switch 'squeezenet' out with 'bvlc_alexnet', 'bvlc_googlenet' or others that you have downloaded\\n\",\n    \"MODEL = 'squeezenet', 'init_net.pb', 'predict_net.pb', 'ilsvrc_2012_mean.npy', 227\\n\",\n    \"\\n\",\n    \"# codes - these help decypher the output and source from a list from ImageNet's object codes \\n\",\n    \"#    to provide an result like \\\"tabby cat\\\" or \\\"lemon\\\" depending on what's in the picture \\n\",\n    \"#   you submit to the CNN.\\n\",\n    \"codes =  \\\"https://gist.githubusercontent.com/aaronmarkham/cd3a6b6ac071eca6f7b4a6e40e6038aa/raw/9edb4038a37da6b5a44c3b5bc52e448ff09bfe5b/alexnet_codes\\\"\\n\",\n    \"print(\\\"Config set!\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Setup paths\\n\",\n    \"\\n\",\n    \"With the configs set, we can now load the mean file (if it exists), as well as the predict net and the init net.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"No mean file found!\\n\",\n      \"mean was set to:  128\\n\",\n      \"All needed files found!\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# set paths and variables from model choice and prep image\\n\",\n    \"CAFFE_MODELS = os.path.expanduser(CAFFE_MODELS)\\n\",\n    \"\\n\",\n    \"# mean can be 128 or custom based on the model\\n\",\n    \"# gives better results to remove the colors found in all of the training images\\n\",\n    \"MEAN_FILE = os.path.join(CAFFE_MODELS, MODEL[0], MODEL[3])\\n\",\n    \"if not os.path.exists(MEAN_FILE):\\n\",\n    \"    print(\\\"No mean file found!\\\")\\n\",\n    \"    mean = 128\\n\",\n    \"else:\\n\",\n    \"    print (\\\"Mean file found!\\\")\\n\",\n    \"    mean = np.load(MEAN_FILE).mean(1).mean(1)\\n\",\n    \"    mean = mean[:, np.newaxis, np.newaxis]\\n\",\n    \"print(\\\"mean was set to: \\\", mean)\\n\",\n    \"\\n\",\n    \"# some models were trained with different image sizes, this helps you calibrate your image\\n\",\n    \"INPUT_IMAGE_SIZE = MODEL[4]\\n\",\n    \"\\n\",\n    \"# make sure all of the files are around...\\n\",\n    \"INIT_NET = os.path.join(CAFFE_MODELS, MODEL[0], MODEL[1])\\n\",\n    \"PREDICT_NET = os.path.join(CAFFE_MODELS, MODEL[0], MODEL[2])\\n\",\n    \"\\n\",\n    \"# Check to see if the files exist\\n\",\n    \"if not os.path.exists(INIT_NET):\\n\",\n    \"    print(\\\"WARNING: \\\" + INIT_NET + \\\" not found!\\\")\\n\",\n    \"else:\\n\",\n    \"    if not os.path.exists(PREDICT_NET):\\n\",\n    \"        print(\\\"WARNING: \\\" + PREDICT_NET + \\\" not found!\\\")\\n\",\n    \"    else:\\n\",\n    \"        print(\\\"All needed files found!\\\")\\n\",\n    \"        \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Image Preprocessing\\n\",\n    \"\\n\",\n    \"Now that we have our inputs specified and verified the existance of the input network, we can load the image and pre-processing the image for ingestion into a Caffe2 convolutional neural network! This is a very important step as the trained CNN requires a specifically sized input image whose values are from a particular distribution.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {\n    \"scrolled\": false\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"/Users/nateinkawhich/anaconda2/lib/python2.7/site-packages/skimage/transform/_warps.py:84: UserWarning: The default mode, 'constant', will be changed to 'reflect' in skimage 0.15.\\n\",\n      \"  warn(\\\"The default mode, 'constant', will be changed to 'reflect' in \\\"\\n\"\n     ]\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Original Image Shape:  (751, 1280, 3)\\n\",\n      \"Image Shape after rescaling:  (227, 386, 3)\\n\",\n      \"Image Shape after cropping:  (227, 227, 3)\\n\",\n      \"CHW Image Shape:  (3, 227, 227)\\n\",\n      \"NCHW image (ready to be used as input):  (1, 3, 227, 227)\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x1c203fb450>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x1c240cdc10>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x1c24126e10>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Function to crop the center cropX x cropY pixels from the input image\\n\",\n    \"def crop_center(img,cropx,cropy):\\n\",\n    \"    y,x,c = img.shape\\n\",\n    \"    startx = x//2-(cropx//2)\\n\",\n    \"    starty = y//2-(cropy//2)    \\n\",\n    \"    return img[starty:starty+cropy,startx:startx+cropx]\\n\",\n    \"\\n\",\n    \"# Function to rescale the input image to the desired height and/or width. This function will preserve\\n\",\n    \"#   the aspect ratio of the original image while making the image the correct scale so we can retrieve\\n\",\n    \"#   a good center crop. This function is best used with center crop to resize any size input images into\\n\",\n    \"#   specific sized images that our model can use.\\n\",\n    \"def rescale(img, input_height, input_width):\\n\",\n    \"    # Get original aspect ratio\\n\",\n    \"    aspect = img.shape[1]/float(img.shape[0])\\n\",\n    \"    if(aspect>1):\\n\",\n    \"        # landscape orientation - wide image\\n\",\n    \"        res = int(aspect * input_height)\\n\",\n    \"        imgScaled = skimage.transform.resize(img, (input_width, res))\\n\",\n    \"    if(aspect<1):\\n\",\n    \"        # portrait orientation - tall image\\n\",\n    \"        res = int(input_width/aspect)\\n\",\n    \"        imgScaled = skimage.transform.resize(img, (res, input_height))\\n\",\n    \"    if(aspect == 1):\\n\",\n    \"        imgScaled = skimage.transform.resize(img, (input_width, input_height))\\n\",\n    \"    return imgScaled\\n\",\n    \"\\n\",\n    \"# Load the image as a 32-bit float\\n\",\n    \"#    Note: skimage.io.imread returns a HWC ordered RGB image of some size\\n\",\n    \"img = skimage.img_as_float(skimage.io.imread(IMAGE_LOCATION)).astype(np.float32)\\n\",\n    \"print(\\\"Original Image Shape: \\\" , img.shape)\\n\",\n    \"\\n\",\n    \"# Rescale the image to comply with our desired input size. This will not make the image 227x227\\n\",\n    \"#    but it will make either the height or width 227 so we can get the ideal center crop.\\n\",\n    \"img = rescale(img, INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE)\\n\",\n    \"print(\\\"Image Shape after rescaling: \\\" , img.shape)\\n\",\n    \"pyplot.figure()\\n\",\n    \"pyplot.imshow(img)\\n\",\n    \"pyplot.title('Rescaled image')\\n\",\n    \"\\n\",\n    \"# Crop the center 227x227 pixels of the image so we can feed it to our model\\n\",\n    \"img = crop_center(img, INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE)\\n\",\n    \"print(\\\"Image Shape after cropping: \\\" , img.shape)\\n\",\n    \"pyplot.figure()\\n\",\n    \"pyplot.imshow(img)\\n\",\n    \"pyplot.title('Center Cropped')\\n\",\n    \"\\n\",\n    \"# switch to CHW (HWC --> CHW)\\n\",\n    \"img = img.swapaxes(1, 2).swapaxes(0, 1)\\n\",\n    \"print(\\\"CHW Image Shape: \\\" , img.shape)\\n\",\n    \"\\n\",\n    \"pyplot.figure()\\n\",\n    \"for i in range(3):\\n\",\n    \"    # For some reason, pyplot subplot follows Matlab's indexing\\n\",\n    \"    # convention (starting with 1). Well, we'll just follow it...\\n\",\n    \"    pyplot.subplot(1, 3, i+1)\\n\",\n    \"    pyplot.imshow(img[i])\\n\",\n    \"    pyplot.axis('off')\\n\",\n    \"    pyplot.title('RGB channel %d' % (i+1))\\n\",\n    \"\\n\",\n    \"# switch to BGR (RGB --> BGR)\\n\",\n    \"img = img[(2, 1, 0), :, :]\\n\",\n    \"\\n\",\n    \"# remove mean for better results\\n\",\n    \"img = img * 255 - mean\\n\",\n    \"\\n\",\n    \"# add batch size axis which completes the formation of the NCHW shaped input that we want\\n\",\n    \"img = img[np.newaxis, :, :, :].astype(np.float32)\\n\",\n    \"\\n\",\n    \"print(\\\"NCHW image (ready to be used as input): \\\", img.shape)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Prepare the CNN and run the net!\\n\",\n    \"\\n\",\n    \"Now that the image is ready to be ingested by the CNN, let's open the protobufs, load them into the workspace, and run the net. \\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"results shape:  (1, 1, 1000, 1, 1)\\n\",\n      \"Prediction:  985\\n\",\n      \"Confidence:  0.98222685\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Read the contents of the input protobufs into local variables\\n\",\n    \"with open(INIT_NET) as f:\\n\",\n    \"    init_net = f.read()\\n\",\n    \"with open(PREDICT_NET) as f:\\n\",\n    \"    predict_net = f.read()\\n\",\n    \"\\n\",\n    \"# Initialize the predictor from the input protobufs\\n\",\n    \"p = workspace.Predictor(init_net, predict_net)\\n\",\n    \"\\n\",\n    \"# Run the net and return prediction\\n\",\n    \"results = p.run({'data': img})\\n\",\n    \"\\n\",\n    \"# Turn it into something we can play with and examine which is in a multi-dimensional array\\n\",\n    \"results = np.asarray(results)\\n\",\n    \"print(\\\"results shape: \\\", results.shape)\\n\",\n    \"\\n\",\n    \"# Quick way to get the top-1 prediction result\\n\",\n    \"# Squeeze out the unnecessary axis. This returns a 1-D array of length 1000\\n\",\n    \"preds = np.squeeze(results)\\n\",\n    \"# Get the prediction and the confidence by finding the maximum value and index of maximum value in preds array\\n\",\n    \"curr_pred, curr_conf = max(enumerate(preds), key=operator.itemgetter(1))\\n\",\n    \"print(\\\"Prediction: \\\", curr_pred)\\n\",\n    \"print(\\\"Confidence: \\\", curr_conf)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Process Results\\n\",\n    \"\\n\",\n    \"Recall ImageNet is a 1000 class dataset and observe that it is no coincidence that the third axis of results is length 1000. This axis is holding the probability for each category in the pre-trained model. So when you look at the results array at a specific index, the number can be interpreted as the probability that the input belongs to the class corresponding to that index. Now that we have run the predictor and collected the results, we can interpret them by matching them to their corresponding english labels.\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Raw top 5 results: [array([985.0, 0.9822268486022949], dtype=object), array([309.0, 0.011943653225898743], dtype=object), array([946.0, 0.0048101237043738365], dtype=object), array([325.0, 0.0003407070180401206], dtype=object), array([944.0, 0.00023906621208880097], dtype=object)]\\n\",\n      \"Top 5 classes in order: [985, 309, 946, 325, 944]\\n\",\n      \"Model predicts 'daisy' with 98.22% confidence\\n\",\n      \"Model predicts 'bee' with 1.19% confidence\\n\",\n      \"Model predicts 'cardoon' with 0.48% confidence\\n\",\n      \"Model predicts 'sulphur butterfly' with 0.03% confidence\\n\",\n      \"Model predicts 'artichoke' with 0.02% confidence\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# the rest of this is digging through the results \\n\",\n    \"results = np.delete(results, 1)\\n\",\n    \"index = 0\\n\",\n    \"highest = 0\\n\",\n    \"arr = np.empty((0,2), dtype=object)\\n\",\n    \"arr[:,0] = int(10)\\n\",\n    \"arr[:,1:] = float(10)\\n\",\n    \"for i, r in enumerate(results):\\n\",\n    \"    # imagenet index begins with 1!\\n\",\n    \"    i=i+1\\n\",\n    \"    arr = np.append(arr, np.array([[i,r]]), axis=0)\\n\",\n    \"    if (r > highest):\\n\",\n    \"        highest = r\\n\",\n    \"        index = i \\n\",\n    \"\\n\",\n    \"# top N results\\n\",\n    \"N = 5\\n\",\n    \"topN = sorted(arr, key=lambda x: x[1], reverse=True)[:N]\\n\",\n    \"print(\\\"Raw top {} results: {}\\\".format(N,topN))\\n\",\n    \"\\n\",\n    \"# Isolate the indexes of the top-N most likely classes\\n\",\n    \"topN_inds = [int(x[0]) for x in topN]\\n\",\n    \"print(\\\"Top {} classes in order: {}\\\".format(N,topN_inds))\\n\",\n    \"\\n\",\n    \"# Now we can grab the code list and create a class Look Up Table\\n\",\n    \"response = urllib2.urlopen(codes)\\n\",\n    \"class_LUT = []\\n\",\n    \"for line in response:\\n\",\n    \"    code, result = line.partition(\\\":\\\")[::2]\\n\",\n    \"    code = code.strip()\\n\",\n    \"    result = result.replace(\\\"'\\\", \\\"\\\")\\n\",\n    \"    if code.isdigit():\\n\",\n    \"        class_LUT.append(result.split(\\\",\\\")[0][1:])\\n\",\n    \"        \\n\",\n    \"# For each of the top-N results, associate the integer result with an actual class\\n\",\n    \"for n in topN:\\n\",\n    \"    print(\\\"Model predicts '{}' with {}% confidence\\\".format(class_LUT[int(n[0])],float(\\\"{0:.2f}\\\".format(n[1]*100))))\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"source\": [\n    \"### Feeding Larger Batches\\n\",\n    \"\\n\",\n    \"Above is an example of how to feed one image at a time. We can achieve higher throughput if we feed multiple images at a time in a single batch. Recall, the data fed into the classifier is in 'NCHW' order, so to feed multiple images, we will expand the 'N' axis.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Batch Shape:  (7, 3, 227, 227)\\n\",\n      \"NCHW image (ready to be used as input):  (7, 3, 227, 227)\\n\",\n      \"Squeezed Predictions Shape, with batch size 7: (7, 1000)\\n\",\n      \"Results for: 'images/cowboy-hat.jpg'\\n\",\n      \"\\tPrediction:  515\\n\",\n      \"\\tClass Name:  cowboy hat\\n\",\n      \"\\tConfidence:  0.85009265\\n\",\n      \"Results for: 'images/cell-tower.jpg'\\n\",\n      \"\\tPrediction:  645\\n\",\n      \"\\tClass Name:  maypole\\n\",\n      \"\\tConfidence:  0.18584323\\n\",\n      \"Results for: 'images/Ducreux.jpg'\\n\",\n      \"\\tPrediction:  568\\n\",\n      \"\\tClass Name:  fur coat\\n\",\n      \"\\tConfidence:  0.1025313\\n\",\n      \"Results for: 'images/pretzel.jpg'\\n\",\n      \"\\tPrediction:  932\\n\",\n      \"\\tClass Name:  pretzel\\n\",\n      \"\\tConfidence:  0.999622\\n\",\n      \"Results for: 'images/orangutan.jpg'\\n\",\n      \"\\tPrediction:  365\\n\",\n      \"\\tClass Name:  orangutan\\n\",\n      \"\\tConfidence:  0.99200517\\n\",\n      \"Results for: 'images/aircraft-carrier.jpg'\\n\",\n      \"\\tPrediction:  403\\n\",\n      \"\\tClass Name:  aircraft carrier\\n\",\n      \"\\tConfidence:  0.9998779\\n\",\n      \"Results for: 'images/cat.jpg'\\n\",\n      \"\\tPrediction:  281\\n\",\n      \"\\tClass Name:  tabby\\n\",\n      \"\\tConfidence:  0.51331687\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# List of input images to be fed\\n\",\n    \"images = [\\\"images/cowboy-hat.jpg\\\",\\n\",\n    \"            \\\"images/cell-tower.jpg\\\",\\n\",\n    \"            \\\"images/Ducreux.jpg\\\",\\n\",\n    \"            \\\"images/pretzel.jpg\\\",\\n\",\n    \"            \\\"images/orangutan.jpg\\\",\\n\",\n    \"            \\\"images/aircraft-carrier.jpg\\\",\\n\",\n    \"            \\\"images/cat.jpg\\\"]\\n\",\n    \"\\n\",\n    \"# Allocate space for the batch of formatted images\\n\",\n    \"NCHW_batch = np.zeros((len(images),3,227,227))\\n\",\n    \"print (\\\"Batch Shape: \\\",NCHW_batch.shape)\\n\",\n    \"\\n\",\n    \"# For each of the images in the list, format it and place it in the batch\\n\",\n    \"for i,curr_img in enumerate(images):\\n\",\n    \"    img = skimage.img_as_float(skimage.io.imread(curr_img)).astype(np.float32)\\n\",\n    \"    img = rescale(img, 227, 227)\\n\",\n    \"    img = crop_center(img, 227, 227)\\n\",\n    \"    img = img.swapaxes(1, 2).swapaxes(0, 1)\\n\",\n    \"    img = img[(2, 1, 0), :, :]\\n\",\n    \"    img = img * 255 - mean\\n\",\n    \"    NCHW_batch[i] = img\\n\",\n    \"\\n\",\n    \"print(\\\"NCHW image (ready to be used as input): \\\", NCHW_batch.shape)\\n\",\n    \"\\n\",\n    \"# Run the net on the batch\\n\",\n    \"results = p.run([NCHW_batch.astype(np.float32)])\\n\",\n    \"\\n\",\n    \"# Turn it into something we can play with and examine which is in a multi-dimensional array\\n\",\n    \"results = np.asarray(results)\\n\",\n    \"\\n\",\n    \"# Squeeze out the unnecessary axis\\n\",\n    \"preds = np.squeeze(results)\\n\",\n    \"print(\\\"Squeezed Predictions Shape, with batch size {}: {}\\\".format(len(images),preds.shape))\\n\",\n    \"\\n\",\n    \"# Describe the results\\n\",\n    \"for i,pred in enumerate(preds):\\n\",\n    \"    print(\\\"Results for: '{}'\\\".format(images[i]))\\n\",\n    \"    # Get the prediction and the confidence by finding the maximum value \\n\",\n    \"    #   and index of maximum value in preds array\\n\",\n    \"    curr_pred, curr_conf = max(enumerate(pred), key=operator.itemgetter(1))\\n\",\n    \"    print(\\\"\\\\tPrediction: \\\", curr_pred)\\n\",\n    \"    print(\\\"\\\\tClass Name: \\\", class_LUT[int(curr_pred)])\\n\",\n    \"    print(\\\"\\\\tConfidence: \\\", curr_conf)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.14\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/MNIST.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# MNIST\\n\",\n    \"\\n\",\n    \"In this tutorial, we will show you how to train a small convolutional neural network (a CNN) model. We will be training the model on the MNIST dataset, which consists of labeled handwritten digits. Each sample is a 28x28 picture of a handwritten single digit and the label is a digit from 0 to 9.\\n\",\n    \"\\n\",\n    \"We will be constructing the [LeNet model](http://yann.lecun.com/exdb/lenet/) with the sigmoid activations replaced with [ReLUs](http://www.cs.toronto.edu/~fritz/absps/reluICML.pdf). A flag below will allow us to toggle between the LeNet model and a simple MLP (multilayer perceptron) architectures.\\n\",\n    \"\\n\",\n    \"We will be using ModelHelper - the class that helps us deal with parameter initialization naturally.\\n\",\n    \"\\n\",\n    \"First, let's import the necessities.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:root:This caffe2 python run does not have GPU support. Will run in CPU only mode.\\n\",\n      \"WARNING:root:Debug message: No module named caffe2_pybind11_state_gpu\\n\"\n     ]\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Necessities imported!\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"%matplotlib inline\\n\",\n    \"from matplotlib import pyplot\\n\",\n    \"import numpy as np\\n\",\n    \"import os\\n\",\n    \"import shutil\\n\",\n    \"import caffe2.python.predictor.predictor_exporter as pe\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"from caffe2.python import (\\n\",\n    \"    brew,\\n\",\n    \"    core,\\n\",\n    \"    model_helper,\\n\",\n    \"    net_drawer,\\n\",\n    \"    optimizer,\\n\",\n    \"    visualize,\\n\",\n    \"    workspace,\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"# If you would like to see some really detailed initializations,\\n\",\n    \"# you can change --caffe2_log_level=0 to --caffe2_log_level=-1\\n\",\n    \"core.GlobalInit(['caffe2', '--caffe2_log_level=0'])\\n\",\n    \"print(\\\"Necessities imported!\\\")\\n\",\n    \"\\n\",\n    \"# If True, a more complicated convolutional model is used\\n\",\n    \"# If False, a multilayer perceptron model is used\\n\",\n    \"USE_LENET_MODEL = True\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We will track statistics during the training time and store these on disk in a local folder. We need to set up a data folder for the data and a root folder for the stats. You should already have these folders, and in the data folder the MNIST dataset should be setup as a lmdb database for both the training set and the test set for this tutorial. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"lmdb train db found!\\n\",\n      \"lmdb test db found!\\n\",\n      \"Looks like you ran this before, so we need to cleanup those old files...\\n\",\n      \"training data folder:/Users/nateinkawhich/caffe2_notebooks/tutorial_data/mnist\\n\",\n      \"workspace root folder:/Users/nateinkawhich/caffe2_notebooks/tutorial_files/tutorial_mnist\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# This section preps your image and test set in a lmdb database\\n\",\n    \"def DownloadResource(url, path):\\n\",\n    \"    '''Downloads resources from s3 by url and unzips them to the provided path'''\\n\",\n    \"    import requests, zipfile, StringIO\\n\",\n    \"    print(\\\"Downloading... {} to {}\\\".format(url, path))\\n\",\n    \"    r = requests.get(url, stream=True)\\n\",\n    \"    z = zipfile.ZipFile(StringIO.StringIO(r.content))\\n\",\n    \"    z.extractall(path)\\n\",\n    \"    print(\\\"Completed download and extraction.\\\")\\n\",\n    \"    \\n\",\n    \"    \\n\",\n    \"current_folder = os.path.join(os.path.expanduser('~'), 'caffe2_notebooks')\\n\",\n    \"data_folder = os.path.join(current_folder, 'tutorial_data', 'mnist')\\n\",\n    \"root_folder = os.path.join(current_folder, 'tutorial_files', 'tutorial_mnist')\\n\",\n    \"db_missing = False\\n\",\n    \"\\n\",\n    \"if not os.path.exists(data_folder):\\n\",\n    \"    os.makedirs(data_folder)   \\n\",\n    \"    print(\\\"Your data folder was not found!! This was generated: {}\\\".format(data_folder))\\n\",\n    \"\\n\",\n    \"# Look for existing database: lmdb\\n\",\n    \"if os.path.exists(os.path.join(data_folder,\\\"mnist-train-nchw-lmdb\\\")):\\n\",\n    \"    print(\\\"lmdb train db found!\\\")\\n\",\n    \"else:\\n\",\n    \"    db_missing = True\\n\",\n    \"    \\n\",\n    \"if os.path.exists(os.path.join(data_folder,\\\"mnist-test-nchw-lmdb\\\")):\\n\",\n    \"    print(\\\"lmdb test db found!\\\")\\n\",\n    \"else:\\n\",\n    \"    db_missing = True\\n\",\n    \"\\n\",\n    \"# attempt the download of the db if either was missing\\n\",\n    \"if db_missing:\\n\",\n    \"    print(\\\"one or both of the MNIST lmbd dbs not found!!\\\")\\n\",\n    \"    db_url = \\\"http://download.caffe2.ai/databases/mnist-lmdb.zip\\\"\\n\",\n    \"    try:\\n\",\n    \"        DownloadResource(db_url, data_folder)\\n\",\n    \"    except Exception as ex:\\n\",\n    \"        print(\\\"Failed to download dataset. Please download it manually from {}\\\".format(db_url))\\n\",\n    \"        print(\\\"Unzip it and place the two database folders here: {}\\\".format(data_folder))\\n\",\n    \"        raise ex\\n\",\n    \"\\n\",\n    \"if os.path.exists(root_folder):\\n\",\n    \"    print(\\\"Looks like you ran this before, so we need to cleanup those old files...\\\")\\n\",\n    \"    shutil.rmtree(root_folder)\\n\",\n    \"    \\n\",\n    \"os.makedirs(root_folder)\\n\",\n    \"workspace.ResetWorkspace(root_folder)\\n\",\n    \"\\n\",\n    \"print(\\\"training data folder:\\\" + data_folder)\\n\",\n    \"print(\\\"workspace root folder:\\\" + root_folder)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"> If the database wasn't found in the last step, [download the MNIST lmdb database](https://download.caffe2.ai/databases/mnist-lmdb.zip) or review the [datasets and databases notebook](https://github.com/caffe2/caffe2/blob/master/caffe2/python/tutorials/MNIST_Dataset_and_Databases.ipynb) on how to create the database from the MNIST dataset.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We will be using the `ModelHelper` class to represent our main model and using `brew` module as well as normal Caffe2 operators to build our model. `ModelHelper` is a special class which stores a lot of information about parameters initialization, their names and later on mapping to gradients. We will see how it is used in `brew` and other places below.\\n\",\n    \"\\n\",\n    \"model.MyOperator is a syntactic sugar for model.net.MyOperator, which adds the corresponding MyOperator operator to model.net.\\n\",\n    \"\\n\",\n    \"`brew` is a collection of helper functions designed to simplify the addition of complex logic to our models. When we want to add parameter initialization as well as a computation step, for example, `brew` comes in handy. Lets explore this in more detail.\\n\",\n    \"\\n\",\n    \"`brew` module has a set of wrapper functions that automatically separate the parameter intialization and the actual computation into two networks. Under the hood, a `ModelHelper` object has two underlying nets, `param_init_net` and `net`, that keep record of the initialization network and the main network respectively. Also model.params keeps track of parameter names.\\n\",\n    \"\\n\",\n    \"For the sake of modularity, we will separate the model to multiple different parts:\\n\",\n    \"\\n\",\n    \"    (1) The data input part (AddInput function)\\n\",\n    \"    (2) The main computation part (AddModel function)\\n\",\n    \"    (3) The training part - adding gradient operators, update, etc. (AddTrainingOperators function)\\n\",\n    \"    (4) The bookkeeping part, where we just print out statistics for inspection. (AddBookkeepingOperators function)\\n\",\n    \"    \\n\",\n    \"`AddInput` will load the data from a DB. We store MNIST data in pixel values, so after batching this will give us data with shape `(batch_size, num_channels, width, height)`, in this case `[batch_size, 1, 28, 28]` of data type *uint8* and a label with shape `[batch_size]` of data type *int*.\\n\",\n    \"    \\n\",\n    \"Since we are going to do float computations, we will cast the data to the *float* data type.\\n\",\n    \"For better numerical stability, instead of representing data in [0, 255] range, we will scale them down to [0, 1].\\n\",\n    \"Note that we are doing in-place computation for this operator: we don't need the pre-scale data.\\n\",\n    \"Now, when computing the backward pass, we will not need the gradient computation for the data preparation part. `StopGradient` does exactly that: in the forward pass it does nothing and in the backward pass all it does is to tell the gradient generator \\\"the gradient does not need to pass through here\\\".\\n\",\n    \"    \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def AddInput(model, batch_size, db, db_type):\\n\",\n    \"    # load the data\\n\",\n    \"    data_uint8, label = brew.db_input(\\n\",\n    \"        model,\\n\",\n    \"        blobs_out=[\\\"data_uint8\\\", \\\"label\\\"],\\n\",\n    \"        batch_size=batch_size,\\n\",\n    \"        db=db,\\n\",\n    \"        db_type=db_type,\\n\",\n    \"    )\\n\",\n    \"    # cast the data to float\\n\",\n    \"    data = model.Cast(data_uint8, \\\"data\\\", to=core.DataType.FLOAT)\\n\",\n    \"    # scale data from [0,255] down to [0,1]\\n\",\n    \"    data = model.Scale(data, data, scale=float(1./256))\\n\",\n    \"    # don't need the gradient for the backward pass\\n\",\n    \"    data = model.StopGradient(data, data)\\n\",\n    \"    return data, label\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now we are going to construct our own model. The input will be our data blob, and the output will be vectors of length 10 containing the network's prediction on each of the 10 possible digits.\\n\",\n    \"\\n\",\n    \"We are going to use the Multilayer Perceptron (MLP) architecture. The ReLU activation function is going to be used:\\n\",\n    \"\\n\",\n    \"Relu(x) = x if x > 0 else 0\\n\",\n    \"\\n\",\n    \"Each layer of an MLP is just matrix multiplication with a bias plus an activation function. In our case, with ReLU activation, that is:\\n\",\n    \"\\n\",\n    \"layer1 = Relu(X * W1^T + b1)\\n\",\n    \"\\n\",\n    \"layer2 = Relu(layer1 * W2^T + b2)\\n\",\n    \"\\n\",\n    \"...\\n\",\n    \"\\n\",\n    \"Ultimately we will use the Softmax operator to convert scores for each of the digits to probabilities. So (p_0 + ... + p_9) = 1.0 and 0 <= p_i <= 1.0. \\n\",\n    \"\\n\",\n    \"There are more detailed MLP explanations online. A good example is [here](http://deeplearning.net/tutorial/mlp.html).\\n\",\n    \"\\n\",\n    \"In this function we are going to use Brew for the second time. Please refer to the explanation given above. When below we call brew.fc(model, layer, ...) under the hood the following happens. FC operator is going to be added to model.net by calling model.net.FC([layer, W, b], ...). Where W and b are the weight and the bias of this fully connected layer (output = layer * W^T + b). Initially, we get W and b by adding their initialization into model.param_init_net. All of these is happening under the hood. You could just use Brew! :) \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def AddMLPModel(model, data):\\n\",\n    \"    size = 28 * 28 * 1\\n\",\n    \"    sizes = [size, size * 2, size * 2, 10]\\n\",\n    \"    layer = data\\n\",\n    \"    for i in range(len(sizes) - 1):\\n\",\n    \"        layer = brew.fc(model, layer, 'dense_{}'.format(i), dim_in=sizes[i], dim_out=sizes[i + 1])\\n\",\n    \"        layer = model.net.Relu(layer, 'relu_{}'.format(i))\\n\",\n    \"    softmax = model.net.Softmax(layer, 'softmax')\\n\",\n    \"    return softmax\\n\",\n    \"    \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Below is another possible (and much better) architecture called LeNet. This section is optional and you could run and use this tutorial for MLPs without understanding this function.\\n\",\n    \"\\n\",\n    \"It uses convolutional layers. To understand convolution first you could look into [an explanation of kernels in image processing](https://en.wikipedia.org/wiki/Kernel_%28image_processing%29) \\n\",\n    \"\\n\",\n    \"The next step would be to understand convolutions in machine learning. There are also a lot of great resources online. Such as [this one](http://deeplearning.net/software/theano_versions/dev/tutorial/conv_arithmetic.html)\\n\",\n    \"\\n\",\n    \"This function is also using Brew, this time for adding convolutional layers as well as fully connected ones.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def AddLeNetModel(model, data):\\n\",\n    \"    '''\\n\",\n    \"    This part is the standard LeNet model: from data to the softmax prediction.\\n\",\n    \"    \\n\",\n    \"    For each convolutional layer we specify dim_in - number of input channels\\n\",\n    \"    and dim_out - number or output channels. Also each Conv and MaxPool layer changes the\\n\",\n    \"    image size. For example, kernel of size 5 reduces each side of an image by 4.\\n\",\n    \"\\n\",\n    \"    While when we have kernel and stride sizes equal 2 in a MaxPool layer, it divides\\n\",\n    \"    each side in half.\\n\",\n    \"    '''\\n\",\n    \"    # Image size: 28 x 28 -> 24 x 24\\n\",\n    \"    conv1 = brew.conv(model, data, 'conv1', dim_in=1, dim_out=20, kernel=5)\\n\",\n    \"    # Image size: 24 x 24 -> 12 x 12\\n\",\n    \"    pool1 = model.net.MaxPool(conv1, 'pool1', kernel=2, stride=2)\\n\",\n    \"    # Image size: 12 x 12 -> 8 x 8\\n\",\n    \"    conv2 = brew.conv(model, pool1, 'conv2', dim_in=20, dim_out=50, kernel=5)\\n\",\n    \"    # Image size: 8 x 8 -> 4 x 4\\n\",\n    \"    pool2 = model.net.MaxPool(conv2, 'pool2', kernel=2, stride=2)\\n\",\n    \"    # 50 * 4 * 4 stands for dim_out from previous layer multiplied by the image size\\n\",\n    \"    fc3 = brew.fc(model, pool2, 'fc3', dim_in=50 * 4 * 4, dim_out=500)\\n\",\n    \"    fc3 = model.net.Relu(fc3, 'relu3')\\n\",\n    \"    pred = brew.fc(model, fc3, 'pred', 500, 10)\\n\",\n    \"    softmax = model.net.Softmax(pred, 'softmax')\\n\",\n    \"    return softmax\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The `AddModel` function below allows us to easily switch from MLP to LeNet model. Just change `USE_LENET_MODEL` at the very top of the notebook and rerun the whole thing.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def AddModel(model, data):\\n\",\n    \"    if USE_LENET_MODEL:\\n\",\n    \"        return AddLeNetModel(model, data)\\n\",\n    \"    else:\\n\",\n    \"        return AddMLPModel(model, data)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"`AddAccuracy` function below adds an accuracy operator to the model. It is not going to be used in training. But will allow us to track accuracy of the model during training and build a nice plot.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def AddAccuracy(model, softmax, label):\\n\",\n    \"    \\\"\\\"\\\"Adds an accuracy op to the model\\\"\\\"\\\"\\n\",\n    \"    accuracy = model.Accuracy([softmax, label], \\\"accuracy\\\")\\n\",\n    \"    return accuracy\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The next function, `AddTrainingOperators`, adds training operators to the model. Please follow inline comments to understand all of the steps. We are going to use `build_sgd` helper function here. You can also build the whole update process yourself. The model object contains all the required information such as parameter names (`model.param`) and a mapping from parameter names to corresponding gradients.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def AddTrainingOperators(model, softmax, label):\\n\",\n    \"    \\\"\\\"\\\"Adds training operators to the model.\\\"\\\"\\\"\\n\",\n    \"    xent = model.LabelCrossEntropy([softmax, label], 'xent')\\n\",\n    \"    # compute the expected loss\\n\",\n    \"    loss = model.AveragedLoss(xent, \\\"loss\\\")\\n\",\n    \"    # track the accuracy of the model\\n\",\n    \"    AddAccuracy(model, softmax, label)\\n\",\n    \"    # use the average loss we just computed to add gradient operators to the model\\n\",\n    \"    model.AddGradientOperators([loss])\\n\",\n    \"    optimizer.build_sgd(\\n\",\n    \"        model,\\n\",\n    \"        base_learning_rate=0.1,\\n\",\n    \"        policy=\\\"step\\\",\\n\",\n    \"        stepsize=1,\\n\",\n    \"        gamma=0.999,\\n\",\n    \"    )\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The following function, `AddBookkeepingOperations`, adds a few bookkeeping operators that we can inspect later. These operators do not affect the training procedure: they only collect statistics and prints them to file or to logs.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def AddBookkeepingOperators(model):\\n\",\n    \"    \\\"\\\"\\\"This adds a few bookkeeping operators that we can inspect later.\\n\",\n    \"    \\n\",\n    \"    These operators do not affect the training procedure: they only collect\\n\",\n    \"    statistics and prints them to file or to logs.\\n\",\n    \"    \\\"\\\"\\\"    \\n\",\n    \"    # Print basically prints out the content of the blob. to_file=1 routes the\\n\",\n    \"    # printed output to a file. The file is going to be stored under\\n\",\n    \"    #     root_folder/[blob name]\\n\",\n    \"    model.Print('accuracy', [], to_file=1)\\n\",\n    \"    model.Print('loss', [], to_file=1)\\n\",\n    \"    # Summarizes the parameters. Different from Print, Summarize gives some\\n\",\n    \"    # statistics of the parameter, such as mean, std, min and max.\\n\",\n    \"    for param in model.params:\\n\",\n    \"        model.Summarize(param, [], to_file=1)\\n\",\n    \"        model.Summarize(model.param_to_grad[param], [], to_file=1)\\n\",\n    \"    # Now, if we really want to be verbose, we can summarize EVERY blob\\n\",\n    \"    # that the model produces; it is probably not a good idea, because that\\n\",\n    \"    # is going to take time - summarization do not come for free. For this\\n\",\n    \"    # demo, we will only show how to summarize the parameters and their\\n\",\n    \"    # gradients.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, let's actually create the models for training and testing. If you are seeing WARNING messages below, don't be alarmed. The functions we established earlier are now going to be executed. Remember the four steps that we're doing:\\n\",\n    \"\\n\",\n    \"    (1) data input  \\n\",\n    \"    (2) main computation\\n\",\n    \"    (3) training \\n\",\n    \"    (4) bookkeeping\\n\",\n    \"    \\n\",\n    \"Before we can do the data input though we need to define our training model. We will basically need every piece of the components we defined above. In this example, we're using NCHW storage order on the mnist_train dataset. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"BlobReference(\\\"softmax\\\")\"\n      ]\n     },\n     \"execution_count\": 10,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"arg_scope = {\\\"order\\\": \\\"NCHW\\\"}\\n\",\n    \"train_model = model_helper.ModelHelper(name=\\\"mnist_train\\\", arg_scope=arg_scope)\\n\",\n    \"data, label = AddInput(\\n\",\n    \"    train_model, batch_size=64,\\n\",\n    \"    db=os.path.join(data_folder, 'mnist-train-nchw-lmdb'),\\n\",\n    \"    db_type='lmdb')\\n\",\n    \"softmax = AddModel(train_model, data)\\n\",\n    \"AddTrainingOperators(train_model, softmax, label)\\n\",\n    \"AddBookkeepingOperators(train_model)\\n\",\n    \"\\n\",\n    \"# Testing model. We will set the batch size to 100, so that the testing\\n\",\n    \"# pass is 100 iterations (10,000 images in total).\\n\",\n    \"# For the testing model, we need the data input part, the main AddModel\\n\",\n    \"# part, and an accuracy part. Note that init_params is set False because\\n\",\n    \"# we will be using the parameters obtained from the train model.\\n\",\n    \"test_model = model_helper.ModelHelper(\\n\",\n    \"    name=\\\"mnist_test\\\", arg_scope=arg_scope, init_params=False)\\n\",\n    \"data, label = AddInput(\\n\",\n    \"    test_model, batch_size=100,\\n\",\n    \"    db=os.path.join(data_folder, 'mnist-test-nchw-lmdb'),\\n\",\n    \"    db_type='lmdb')\\n\",\n    \"softmax = AddModel(test_model, data)\\n\",\n    \"AddAccuracy(test_model, softmax, label)\\n\",\n    \"\\n\",\n    \"# Deployment model. We simply need the main AddModel part.\\n\",\n    \"deploy_model = model_helper.ModelHelper(\\n\",\n    \"    name=\\\"mnist_deploy\\\", arg_scope=arg_scope, init_params=False)\\n\",\n    \"AddModel(deploy_model, \\\"data\\\")\\n\",\n    \"# You may wonder what happens with the param_init_net part of the deploy_model.\\n\",\n    \"# No, we will not use them, since during deployment time we will not randomly\\n\",\n    \"# initialize the parameters, but load the parameters from the db.\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"source\": [\n    \"Now, let's take a look what the training and deploy models look like using the simple graph visualization tool that Caffe2 has. If the following command fails for you, it might be because your machine does not have graphviz installed. You'll need to install it through the package manager of your choice.\\n\",\n    \"\\n\",\n    \"If the graph looks too small, right click and open the image in a new tab for better inspection.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAALaAAAAXzCAYAAAAe5fADAAAAAXNSR0IArs4c6QAAQABJREFUeAHs3QlwXVd5OPDvSbYWL/IS2/JueZUt2Za8BCc0aYEAWdw4oR3K0kwIaSjQhmFpJlNCS5KyTwullJLCMKwp0AIhcfYESgw0JHiTbHm3ZdmWN9lWLG/yKv117/z1xkqc3YuW35355px7373nfOcnvTfj56NzMq2trf8TDgIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBB4gUCmte14wVUXCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQCByGBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAmQVyznzZVQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDIQUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECZxbodebLrhIgQIAAAQIECBAgQIAAAQIECBAgQIAAgbMn0NraGo2NjbFr167Yt29fPPfcc7F///402utJeejQoWhubo4jR45ko/38xIkTcerUqQ5x8uTJ9PzsZaolAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIdA+BnJycyGQy2Xil57m5udGrV68O8Uqv5efnR15eXhqn15NrL3VeUFAQSRQWFqZxej251n6etOMgQIAAgXMrkMzXPXz4cDqXNynbI5nTe/To0XSub1Jvj/Zrx44di/Y4fvx4tp5cS86TSOYDJ/N/k7I92s/b5wWfPl+4paWlw9zhZE7ymSIRab9+bnW0ToAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAqxFI9s/q3bt3dl+s0+vJflnJeRL9+vWL/v37v2QMHDgwhg0blsbQoUMjOXcQIECAAAECBAgQIECAAIHzLZBp20Sz9Xx3qj8CBAgQIECAAAECBAgQIECAAAECBAgQ6D4Chw4diq1bt2Zj27ZtkcTu3buz0dDQECdPnuww6MLCwnTS1KBBg7JlMvGqT58+aSSvn15vn6yVm5sbp0cycctBgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAh0FWlpaIvkz4vYyqb+S82Su16lTp9I5X0n99DjT9fZrJ06ciOPHj6dx7NixM9aT109/7fTzjtmf+SxZBKygoCCS+WVJJPW+ffu+5kgWCisqKkoXC0vmryXtOwgQINAVBZLP+oMHD8aBAweiqakpLZP6q4nDhw9HEsnn9Msd+fn5HT6Pk8/k5FpeXl5aJvXnn7cv1Nhenr544/PnCSefx6fPF26vZzKZaI8kx/b66eXL5e51AgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgTOn0D7PlfJfljJXlft+2KdXk/2szp06FC6B0uyD8uLRbK/yulHsu/J0KFDY9iwYdkyqY8ePTrGjh2bjeLi4nSvk9OfVSdAgAABAgQIECBAgAABAq9VINO2UXTra33YcwQIECBAgAABAgQIECBAgAABAgQIECDQMwR2794dGzZsyMbGjRvT+pYtW+K5557LIhQVFaUTnZJJT8OHD49kslN7tJ8PGTIkBg0aFHl5ednnVAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECg5wokf+589OjRaG5uzpYvVT/9tWQxr1caR44ciZaWljNCZzKZ6Nu3b/Tv3z8byZy49vPn1wcMGBBJDBw4sEOZXMvNzT1jHy4SIEDg5QSSz6j9+/dHY2Nj7Nu374zlmV5ramqKMy0dkZOTE/369YvkM+xMkXxmJZ9zyWvJZ+CZok+fPun1wsLCSKKgoCCSdh0ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBA4nwLHjx+PPXv2pNHQ0PCCMrmWxLZt22LXrl3ZPavy8/NjzJgxMXbs2GyMHz8+pkyZEqWlpXHRRRedz2HoiwABAgQIECBAgAABAgS6uECmbRPR1i4+BukTIECAAAECBAgQIECAAAECBAgQIECAwFkS2L59e9TU1GRj1apVsXbt2jh48GDaQ2FhYUyaNCmNyZMnR0lJSXYSUzKhacCAAWcpE80QIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGzL9Dc3ByHDx9OI5kbl8SBAwfS8tXUm5qaImnrTEf//v3T+XQDBw58QZlcS2Lw4MHZGDRoULbep0+fMzXpGgECXVgg+czZvXt3Gsnigu3155d79+6N/fv3ZxcdbB9yXl5e9jMiWWgw+fxoL0+vJ/N4i4qKOkS/fv0ik8m0N6UkQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAjxA4ceJE1NfXx9atW9PYsmVLtp5cq6ury+5DlewDU1paGlOmTEnL9vqkSZOioKCgR3gZJAECBAgQIECAAAECBAi8coFMa9vxym93JwECBAgQIECAAAECBAgQIECAAAECBAh0B4FTp07FmjVrYunSpWksX748ampqYv/+/enwhg8fHtOnT09j2rRpMXny5DRGjRoVmUymOxAYAwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQeF0CyeJgyby7pqamDuWZrrXfk7z23HPPpXHw4MEX9J+fnx+DBg2KZDGx9nj++ZAhQ+L0uOiiiyJ5zkGAwPkV2LdvX2zfvj1dJDApk9i1a1fs3r27Qxw+fLhDYgMHDoxhw4ZFcXFxhxg6dGj6vk/e08n7v73s169fh+edECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMDrE2htbY1t27bFunXrYv369WnZXt+6dWu0tLRETk5OTJ48OSoqKjrE6NGjX1/nniZAgAABAgQIECBAgACBLi2QaftHZWuXHoHkCRAgQIAAAQIECBAgQIAAAQIECBAgQOBlBTZu3BhPP/10LF68OJYuXRrV1dVx5MiRKCgoiJkzZ8bs2bNjxowZMX369CgvL4+LLrroZdt0AwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIPDaBU6cOBHPPfdcNDY2Zsuk/lLn+/btS+9NFhY7/ejfv38MGTIknf+XlC8Ww4YNiyQGDx4cmUzm9CbUCRD4/wInT56MnTt3Rn19fWzfvj0bzz8/evRo1qyoqChGjRoVI0aMiOLi4vR9lpTPj+T9l5+fn31OhQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQ6l0CyN82GDRti3bp1sWLFiqiurk5jy5YtaaLJHlAVFRXZmDVrVkyfPj1yc3M710BkQ4AAAQIECBAgQIAAAQLnRCDT2nack5Y1SoAAAQIECBAgQIAAAQIECBAgQIAAAQIXRCCZMLR06dJ4+umns9HQ0BD5+flRWVkZc+bMyUZ5eXn06tXrguSpUwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQODVC7S0tERjY2Ps3bs39u3bl5ZJ/UzR/vr+/fvj9D8rTxYZGzp0aAwbNuwFcabr/fr1e/WJeoJAJxVI5tpu3bo16urqspEszNd+vmvXrkjeZ8mRk5OTvkdGjRoVSYwePTotT68n17xHOukPW1oECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBA4SwJNTU1RXV3dIWpqaiLZE6dPnz4xd+7cuOSSS2LevHlpOXLkyLPUs2YIECBAgAABAgQIECBAoDMJZNo2Cm7tTAnJhQABAgQIECBAgAABAgQIECBAgAABAgRencDx48fjmWeeiV//+tfxv//7v2k9uVZcXBxvfOMbszFnzpzIz89/dY27mwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEOjyAqdOnYp9+/bF7t27o6GhIY09e/Zk6+3X2suDBw92GHNhYWE6L3H48OHxcmGuYgc6JxdAoLm5ObZs2RJ1dXXZOP08eR+0L7NQVFQUJSUlMW7cuLRM6mPHjo1Ro0alMWLEiOjdu/cFGIUuCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKCzCyT7Q61evTqeffbZeOaZZ9IyOW9paYnRo0fHJZdcEvPmzUvLuXPnRkFBQWcfkvwIECBAgAABAgQIECBA4GUEMm0bo7a+zD1eJkCAAAECBAgQIECAAAECBAgQIECAAIFOJJBM5lm6dGk8+eST8etf/zr+7//+L5qbm2PcuHHx5je/OY3LLrssJkyY0ImylgoBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDQVQSOHj0aDQ0NHWL37t2xa9euF8T+/fs7DGvgwIExfPjwF8TIkSPj9BgwYECH55wQeDUC+/bti02bNsXGjRvTMqnX1tam9Z07d0b7MgrJ72NJSUkayVzb59cHDRr0arp1LwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQeEmBgwcPxuLFi+OZZ56JZ599No1kD6j8/Py45JJL4k1velMaSb2goOAl2/IiAQIECBAgQIAAAQIECHQ+gUzbxqmtnS8tGREgQIAAAQIECBAgQIAAAQIECBAgQIDA6QJ79+6Nxx9/PB599NF44oknYs+ePTFy5Mh485vfnMZb3vKWGD9+/OmPqBMgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEzrnAsWPHYteuXS8bO3fujOTe9qNPnz7pXMhkPuRLRd++fdsfUfYwgaampli/fn0a69atiw0bNsTGjRvT2L9/f6rRu3fvKCkpiQkTJsTEiRPTSOrJvNrk+oABA3qYmuESIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQGcT2Lx5c/zmN7+Jp556Ko26urrIz8+PefPmxZve9KY0Lr300igoKOhsqcuHAAECBAgQIECAAAECBJ4nkGltO553zSkBAgQIECBAgAABAgQIECBAgAABAgQIdAKBqqqqeOCBB+KRRx6JJUuWRG5ublx22WVx1VVXxdVXXx0zZszoBFlKgQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDwygT27dsXO3bseMnYtWtXnDx5MttgUVFRjBw5MkaPHv2icdFFF2XvV+laAseOHYtNmzbF+vXrs7Fu3bq03tDQkA4mLy8vJkyYEFOmTIlJkyZ1iLFjx6ZzbLvWqGVLgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAj1ZYMuWLfHUU0/FokWL0nLz5s2Rn58fl1xySVx11VVpVFRURCaT6clMxk6AAAECBAgQIECAAIFOKZBpbTs6ZWaSIkCAAAECBAgQIECAAAECBAgQIECAQA8TOHXqVPzud7+L+++/P426uroYNWpUzJ8/P66++uq44ooron///j1MxXAJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBniTQ0tISe/bsiR07dmRj+/btUV9f3yGampqyLIWFhemcy9GjR8eZYsyYMTF06FALoWXFzn/l0KFDsXbt2li9enWsWbMmjaReW1sbyRzaZJG65Gc3ZcqUNEpLS7P1kpKSyM3NPf9J65EAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECJwHga1bt8ZTTz0Vv/rVr+Lxxx+P3bt3x/Dhw+PKK6+Mq6++Ot72trfF4MGDz0MmuiBAgAABAgQIECBAgACBlxPItLYdL3eT1wkQIECAAAECBAgQIECAAAECBAgQIEDg3AicPHkyfvnLX8b//M//xIMPPhh79+6NadOmxTve8Y64/vrrY+7cuZHJZM5N51olQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECHRRgUOHDkV9ff1Lxr59+7KjKygoiDFjxsTYsWOzZVJvj+S1Pn36ZO9XeW0CTU1NUVNTE6tXr05jzZo1aZn8rJKlDfLz82PKlClRVlaWzplN5s1OnTo1Jk2axP+1kXuKAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBLqRQLLXz/Lly+Oxxx5L4/e//320tLTEG97whrjqqqvSuPjiiyMnJ6cbjdpQCBAgQIAAAQIECBAg0HUEMm3/cGvtOunKlAABAgQIECBAgAABAgQIECBAgAABAl1fIJk8s2jRovjv//7v+NnPfhb79u2LefPmxZ/92Z/F9ddfH1OmTOn6gzQCAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMAFFjh69GjU19ensWXLlti2bVts3bq1Qxw+fDib5ZAhQ2Ls2LExZsyYtEzqSYwbNy5KSkpi2LBhkclksvf35Epiu3r16li5cmUaq1atipqamtQ6cenXr19MmzYtjbKysmx9woQJkZub25PpjJ0AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECLxigaampvjlL38Zjz32WBrJvkzFxcVx3XXXxfXXXx9XXHFF5OXlveL23EiAAAECBAgQIECAAAECr08g09p2vL4mPE2AAAECBAgQIECAAAECBAgQIECAAAECr0Rg8eLFce+998ZPf/rT2LlzZ1RUVMS73/3ueNe73hXjx49/JU24hwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4CwKNDY2xtatW9PYtm1btt5+LZnzeerUqbTHgoKCGDduXJSUlKRxej25Nnz48MhkMmcxu87RVF1dXVRXV8eKFSti5cqVablx48bUJTGZNm1aTJ8+PRvl5eWpU+fIXhYECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKD7CNTU1MQDDzwQ999/fyxZsiT69+8f11xzTVx//fVpWVRU1H0GayQECBAgQIAAAQIECBDohAKZ1rajE+YlJQIECBAgQIAAAQIECBAgQIAAAQIECHQLgR07dsS9994b3//+92P16tVRWloa73nPe+Jd73pXTJ06tVuM0SAIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAt1V4OTJk1FfXx91dXVpbNmypUO5bdu2SO5Jjvz8/Bg7dmyUlJRkY9y4cWl9woQJMXz48MhkMp2Wqrm5OVauXBlVVVWxYsWKqK6uTssDBw6keY8fPz5mzpwZM2bMSCOpT5o0KXJzczvtmCRGgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgS6q0Cyh9IDDzwQv/jFL+I3v/lN5OTkxBVXXBHXX399XHfddVFcXNxdh25cBAgQIECAAAECBAgQuGACmda244L1rmMCBAgQIECAAAECBAgQIECAAAECBAh0Q4GjR4+mk2C+973vxZNPPhlFRUXx7ne/O973vvfFvHnzuuGIDYkAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0DMFTp06Fdu3b4+6uro0tmzZkq0n15LF1U6cOJHiFBYWRklJSUyYMCGN8ePHdyj79et33hAbGhpi+fLlUVVVFdXV1Wm5fv36SMaT5DFz5sw0Kioq0nLGjBnRv3//85afjggQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFXLtDY2BgPPfRQ3H///fH444/H0aNH481vfnO8+93vjj//8z+PQYMGvfLG3EmAAAECBAgQIECAAAECLyqQaW07XvRVLxAgQIAAAQIECBAgQIAAAQIECBAgQIDAKxZYt25dfOtb34rvfe970dTUFFdeeWXcdNNNsWDBgsjPz3/F7biRAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgewi0tLTE9u3bY/PmzVFbW5tGez0pd+3aFe1/8j906NAYP358TJgwIY3T62PGjInc3NzXhJL0s3z58g6xY8eOtK1Ro0ZFZWVlh5g4cWJkMpnX1JeHCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgQsr0NzcHI888kj8+Mc/jocffjiSvZSuvPLKeM973hMLFiyIvn37XtgE9U6AAAECBAgQIECAAIEuLJBp24y2tQvnL3UCBAgQIECAAAECBAgQIECAAAECBAhcUIHjx4/HL37xi/jP//zPeOqpp2LcuHHxgQ98IG6++eYYMWLEBc1N5wQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAp1bIFlkbfPmzWnU1tZGEsl5e3no0KF0AL17946SkpKYNGlSTJw4MVsm9QkTJkR+fn66QNv69etj2bJlsXz58rRM6vv374+cnJyYPHlyzJo1q0MMGTKkcwPJjgABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEHjNAgcPHoz7778/fvzjH8eTTz4ZeXl5ce2118Z73vOeuOqqq9L9j15z4x4kQIAAAQIECBAgQIBADxTItLYdPXDchkyAAAECBAgQIECAAAECBAgQIECAAIHXJVBfXx/f+MY34tvf/nY0NjbG/Pnz40Mf+lBceeWVkZOT87ra9jABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBRGDPnj1RW1sbmzZtio0bN2bLpN7Q0JBFShZkO3nyZLS0tKRzWceOHRsVFRVx2WWXxRvf+Ma03rdv3+z9KgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEDPEti7d2/87Gc/i5/85Cfx29/+NoqKiuKd73xnvP/9749LL720Z2EYLQECBAgQIECAAAECBF6jQKa17XiNz3qMAAECBAgQIECAAAECBAgQIECAAAECPU7g97//ffzbv/1b/PznP48hQ4bEBz/4wbjlllti9OjRPc7CgAkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBM69wMmTJ2P16tWxdOnSbFRXV0dzc3Pk5eXFmDFj0nmtST251tDQEPX19dHS0pImN3To0Jg8eXJMmTIlLZN6e/Tt2/fcD0APBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKcS2L59e/zkJz+J73//+7Fy5cqYOnVq3HTTTXHjjTfGiBEjOlWukiFAgAABAgQIECBAgEBnEsi0th2dKSG5ECBAgAABAgQIECBAgAABAgQIECBAoLMJnDhxIn7605/GV7/61Vi8eHHMnTs3PvrRj8Zf/MVfRF5eXmdLVz4ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJdVODUqVOxevXqWLJkSSxdujQtV6xYEc3NzVFYWBgVFRUxZ86cNGbPnh3l5eXRq1evF4z2+PHjsXnz5ti4cWNs2rQpNmzYEOvXr0/LrVu3RtJPcowcOTImT57cIaZMmRITJ06MgoKCF7TrAgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINC9BJL9kr773e/Gj370ozhw4EBceeWV8f73vz8WLFgQeXl53WuwRkOAAAECBAgQIECAAIHXKZBpbTteZxseJ0CAAAECBAgQIECAAAECBAgQIECAQLcUOHToUHzrW9+Kr3zlK7F79+54xzveER/96Efjj/7oj7rleA2KAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEDg/Am0tLTEmjVrYsmSJZEsnpaUVVVV0dzcHIWFhVFRURFz5syJuXPnpmVZWVnk5ua+7gSPHz8etbW1sWHDhjTWr1+frdfX10eyBEEmk4kxY8bE5MmT05gyZUqUlpamUVJSclbyeN0D0QABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBw1gSOHTsWDzzwQHz3u9+NJ554IgYNGhTvfe974+abb47Kysqz1o+GCBAgQIAAAQIECBAg0JUFMm2bv7Z25QHInQABAgQIECBAgAABAgQIECBAgAABAmdboKGhIb72ta/FN77xjThx4kR84AMfiI9//OMxZsyYs92V9ggQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBHqAQPJn/evXr48lS5ZkY/ny5XH48OEoKCiImTNnxty5c9OYM2dOlJWVRa9evc67THNzc2zatCnNdcOGDdEe69ati927d6f55OXlxaRJk6K0tPQFMXjw4POesw4JECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBsyuwffv2+MEPfhDf/e53072M5s2bFx/+8IfjXe96V7rv0tntTWsECBAgQIAAAQIECBDoOgKZto1qW7tOujIlQIAAAQIECBAgQIAAAQIECBAgQIDAuRPYvHlz/Mu//Es6waRfv37xkY98JG699dYYNGjQuetUywQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAt1OoLa2NpYsWRKLFy9Oy2XLlsWBAwciLy8vZsyYEXPnzs1GeXl59O7du9MbNDU1xfr162PdunUdYsOGDdHc3JzmP2TIkCgtLY0pU6akZVJPYuLEienYO/0gJUiAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAh0EFi0aFHcc889cd9990X//v3j/e9/f3zoQx+KSZMmdbjPCQECBAgQIECAAAECBHqCQKa17egJAzVGAgQIECBAgAABAgQIECBAgAABAgQIvJhAbW1tfO5zn4sf/OAHMXr06Ljtttvi5ptvjsLCwhd7xHUCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECqcC2bdtiyZIlaSxevDiWLl0ajY2N0atXrygrK4u5c+fGxRdfnJYzZ86MvLy8biWXLFmwdevWWLdu3Quivr4+ktdzc3Nj4sSJMW3atJg6dWqHsqioqFt5GAwBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEOiOArt3745vf/vb8a1vfSuSvZve+ta3xoc//OFYsGBBuk9RdxyzMREgQIAAAQIECBAgQOD5Apm2zVpbn3/ROQECBAgQIECAAAECBAgQIECAAAECBHqCwKZNm+Jzn/tc/PCHP4ySkpL41Kc+FTfccEP06tWrJwzfGAkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBF6lwK5du2LJkiUdIlnQLCcnJ0pLS2Pu3LlpXHzxxVFZWRmFhYWvsofudfuRI0di/fr1sW7dulizZk2sXbs2LZNrR48eTQc7cuTImDp1akybNi2N9npy3UGAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAh0LoGWlpZ4+OGH45577onHH388RowYEX/913+dxvDhwztXsrIhQIAAAQIECBAgQIDAWRbItLYdZ7lNzREgQIAAAQIECBAgQIAAAQIECBAgQKBTC2zatCk++9nPxr333hvjx4+PT33qU3HDDTdEbm5up85bcgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAudPYO/evbFkyZIOsX379jSBSZMmxdy5c7Mxe/bs6N+///lLrov3lCz+VldXF2vWrIm1a9emZXu9sbExHV1RUVFMnTo1pk2blpZlZWVRXl6ezv/Nycnp4gLSJ0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECXV+gtrY2vvnNb8Z3vvOdOHDgQLz3ve+Nj33sY1FRUdH1B2cEBAgQIECAAAECBAgQOINAprXtOMN1lwgQIECAAAECBAgQIECAAAECBAgQINDtBHbu3Bmf+cxn4tvf/naUlJTEP/zDP8Rf/uVfRm5ubrcbqwERIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi8coHGxsZYunRpLFmyJI2kvmXLlrSBcePGxdy5czvEwIEDX3nj7nxVAg0NDbFmzZpYu3Zth3Lbtm2RLI9QUFAQU6dOjfLy8igrK8vGxIkTzQt+VdJuJkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECZ0fg6NGj8V//9V/xr//6r7Fq1ap4y1veEp/4xCfimmuuiUwmc3Y60QoBAgQIECBAgAABAgQ6gUCmbYPV1k6QhxQIECBAgAABAgQIECBAgAABAgQIECBwzgT2798fX/rSl+JrX/taDB48OO6666646aabIjc395z1qWECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIHOKdDY2BjLli2LJUuWxNKlS9PYvHlzmuyYMWNizpw5MXfu3DSS+pAhQzrnQHpYVocOHYo1a9bE6tWr08XhkjKJurq6SJZNyM/Pj9LS0igrK0ujvLw8LSdNmhS9evXqYVqGS4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIELozAE088EV/5ylciKSdPnhwf+9jH4n3ve1/06dPnwiSkVwIECBAgQIAAAQIECJxFgUzbRqqtZ7E9TREgQIAAAQIECBAgQIAAAQIECBAgQKDTCBw5ciS+9rWvxZe+9KXIzc2NO+64I/7mb/4mCgoKOk2OEiFAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEDh3Avv27YulS5fGsmXL0jKpb968Oe1w9OjRMWfOnDTmzp2blsOGDTt3yWj5nAgkc4bXrFkTq1evTmPVqlVpmfycW1paIi8vL11Arry8PJKYPn16GhMnTkznGJ+TpDRKgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgR6uECyr9BXv/rV+OEPfxh9+vSJD37wg3HrrbfGyJEje7iM4RMgQIAAAQIECBAg0JUFMq1tR1cegNwJECBAgAABAgQIECBAgAABAgQIECDwfIHka89777037rjjjti/f398/OMfj9tuuy2Kioqef6tzAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBbiKwa9euWLZsWYfYsmVLOrrRo0fHnDlzOkRxcXE3GblhnEmgubk51q5dG8kCckmsWrUqampqYvPmzdHS0hIFBQUxbdq0mD59eocYO3bsmZpzjQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEHgNAnv37o177rknvvGNb0RjY2PceOONcfvtt8fkyZNfQ2seIUCAAAECBAgQIECAwIUVyLS2HRc2Bb0TIECAAAECBAgQIECAAAECBAgQIEDg7An85je/iU984hNRVVUVf/VXfxX/9E//FMXFxWevAy0RIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhccIEtW7bE8uXLY9myZdnYuXNnmldJSUnMnj075syZky2HDh16wXOWQOcQOHLkSKxevTpqamrSWLVqVVrW19enCRYVFUV5eXlMnz69QwwbNqxzDEAWBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgCwocP348fvCDH8SXvvSlqK2tjXe+853x93//91FZWdkFRyNlAgQIECBAgAABAgR6qkCmte3oqYM3bgIECBAgQIAAAQIECBAgQIAAAQIEuo/Apk2b4vbbb4/77rsv3v72t8eXv/zlmD59evcZoJEQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBHqgwKlTp2LdunWxfPnybFRVVUVjY2NkMpmYPHlyzJ49OxuzZs2KwYMH90ApQ369Avv3749Vq1ZFTU1Nh9i7d2/a9JAhQ2LGjBkxc+bMbFleXh59+vR5vV17ngABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI9BiBZG+qn/3sZ/GFL3whqqur45prrolPfvKTcdlll/UYAwMlQIAAAQIECBAgQKDrCmRa246um77MCRAgQIAAAQIECBAgQIAAAQIECBDo6QKHDh2Kz3zmM/HVr341Jk6cGF/+8pfj6quv7uksxk+AAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQ6HICybzQFStWRFVVVbqgV1KuXLkympubIy8vL8rKymLWrFnZqKysjH79+nW5cUq4awns3r07ampq0kh+H5Pf0VWrVsWRI0ciJycnncM8Y8aMmDlzZrSXEyZMSF/rWiOVLQECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQOL8CjzzySHzhC1+I3/3ud3H55ZfHJz/5ybj66qvPbxJ6I0CAAAECBAgQIECAwKsQyLS2Ha/ifrcSIECAAAECBAgQIECAAAECBAgQIECg0wj85Cc/idtuuy2OHDkSn/nMZ+KDH/xg9OrVq9PkJxECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIEzC9TV1UV1dXWsWLEiW27cuDGSP38fMGBAVFRURGVlZRqzZs2KsrKyyMvLO3NjrhI4zwItLS2xadOmWLlyZfo73F7W1tZG8lqfPn1i+vTpMWPGjJg5c2a2vOiii85zprojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKdX+C3v/1tfOELX4hHH3003bvq05/+dFx//fWRyWQ6f/IyJECAAAECBAgQIECgRwlk2jbgbe1RIzZYAgQIECBAgAABAgQIECBAgAABAgS6vMCqVavi1ltvjUWLFsXNN98cX/ziF2PIkCFdflwGQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDobgJNTU2xcuXKbKxYsSKtHzhwIF2Ua/z48VFRUREzZ85MF+yqrKyMkpKS7sZgPD1E4MiRI5HMdU5+z9t/15Pf/71796YCI0aMyP6uJ7/3SZSWlkZubm4PETJMAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDw4gJVVVXxuc99Ln7+85+ne/zcfffdsWDBghd/wCsECBAgQIAAAQIECBA4zwKZ1rbjPPepOwIECBAgQIAAAQIECBAgQIAAAQIECLwmgYMHD8add94Z//7v/x6VlZXxH//xH/GGN7zhNbXlIQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgbMn0NzcHKtXr45Vq1ZFTU1NNrZt25Z2MmDAgJgxY0YaM2fOjCSS8/79+5+9JLREoJMK7Ny5M1asWBErV66M6urqNNauXRsnTpyIgoKCKC8vTxeqq6ioSOdJJ++PgQMHdtLRSIsAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKCzCSTrwf3qV7+Khx56KBobGztbeuc9n1GjRsWCBQvi8ssvj169ep33/nVI4PUKPFD3bPxi8zOvtxnPEzinAnk5veKLl9wYg/OtLXpOoTVOgAABAgQIECBAgAABAgQIECBAgACBHiyQ7Plz1113xf333x+zZ8+Ou+++O+bPn9+DRQydAAECBAgQIECAAIHOIpBpbTs6SzLyIECAAAECBAgQIECAAAECBAgQIECAwIsJLFy4MP72b/82kj9G//znPx+33HJL5OTkvNjtrhMgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECJwDgQMHDsSaNWvSWL16dbasq6uLlpaWyM/Pj2nTpkV5eXlMnz49ZsyYkcbYsWPPQTaaJNB1BY4fPx7Je6i6urpD7Nu3Lx3UuHHjoqKiokNMnDgxMplM1x20zAkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBM6awK5du+Khhx6KBx98MJ588sk4evRoXHzxxZGsY9XTj1WrVqVrfQ0cODCuueaa+NM//dO4+uqrIzl3EOgKArcs+nr8fNP/RWtXSFaOPVrg0fl3xaXFpT3awOAJECBAgAABAgQIECBAgAABAgQIECBA4NwLVFVVxZ133hkLFy6MN7zhDXH33XfHVVddde471gMBAgQIECBAgAABAgReRCDT2na8yGsuEyBAgAABAgQIECBAgAABAgQIECBA4IIL7NixIz7ykY/EfffdFzfccEN85StfiaFDh17wvCRAgJZTv78AAEAASURBVAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEOjOAjt37ox169bF2rVr041z1qxZE0ls3749HXZhYWGUlpbGtGnToqysLI3y8vKYNGlS5ObmdmcaYyNwTgXq6+ujurq6Q2zYsCFaWlqiX79+MXPmzKisrIxZs2al5fTp06OgoOCc5qRxAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBziGwatWqWLhwYRrPPvtsug7VFVdcEQsWLIhrr702hg8f3jkS7QRZ1NbWxgMPPJBa/e53v0sz+pM/+ZOs1fjx4ztBllIgcGaBWxZ9Pe6rfTpaWlvPfIOrBDqJwKPz74pLi0s7STbSIECAAAECBAgQIECAAAECBAgQIECAAIHuLrB06dK488474+GHH45LL7007r777njb297W3YdtfAQIECBAgAABAgQIdEKBTGvb0QnzkhIBAgQIECBAgAABAgQIECBAgAABAj1coKWlJe6555644447YujQoWnd5Ioe/kth+AQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMBZFThw4ECsX78+G+vWrcvWDx06lPY1YMCAmDZtWjbKysrSeklJSeTk5JzVfDRGgMCZBY4cORI1NTVRVVUV1dXVsXz58lixYkUcPnw4evXqlb4nKysrY9asWWlUVFTEoEGDztyYqwQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAl1G4MSJE/Hb3/42HnzwwXjggQdi8+bNUVxcHPPnz48FCxbE29/+9igsLOwy47lQie7fvz8eeeSRWLhwYTz22GPR1NQUM2bMSA0Tx4svvjgymcyFSk+/BF4gcMuir8d9tU9HS2vrC15zgUBnEnh0/l1xaXFpZ0pJLgQIECBAgAABAgQIECBAgAABAgQIECDQAwT+8Ic/xJ133pn+39/ll18eX/ziF+ONb3xjDxi5IRIgQIAAAQIECBAg0FkEMq1tR2dJRh4ECBAgQIAAAQIECBAgQIAAAQIECBBIBNauXRs333xzLFmyJP7u7/4uPv3pT/tDdL8aBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIHXIHDgwIHYtGlTbNy4MRsbNmyI9evXx+7du9MWe/fuHRMmTIjS0tKYMmVKhxgxYsRr6NUjBAica4GWlpZI3stVVVWxfPnyNJJ6Q0ND2nVJSUlUVlbGrFmz0kjqY8aMOddpaZ8AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQOB1Cuzfvz8ee+yxWLhwYTzyyCPR1NQU5eXlsWDBgrj22mvjkksuiUwm8zp76bmPnzhxIhYtWpT6PvTQQ7F58+YoLi7O+r71rW+1l3LP/fXoNCO/ZdHX477ap6OltbXT5CQRAmcSeHT+XXFpcemZXnKNAAECBAgQIECAAAECBAgQIECAAAECBAicc4Hf//73cccdd8RTTz0V1113XXz+85+PsrKyc96vDggQIECAAAECBAgQIJBpbTswECBAgAABAgQIECBAgAABAgQIECBAoDMInDp1Kr785S/HnXfeGdOnT4/vfOc7MWPGjM6QmhwIECBAgAABAgQIECBAgAABAt1O4MiRI9HQ0JCNvXv3xoEDB+LQoUNx8ODBNJL6sWPH4uTJkx2ipaUlcnNzo1evXtkyqffu3TsKCgrSKCwsjD59+kS/fv2yUVRUFAMGDIhBgwbF4MGD46KLLkrv7Xa4BkSAAAECBAgQIECAAAECBAgQIECAAAECBM6jQPK9/fbt26Ouri42bdoUtbW1Hco9e/ak2STf7Y8dOzYmTZqUxpQpU6I9xo8fn37nfx7T1hUBAudIYMeOHbF8+fKoqqpKy6SebGqVLC2R/P/crFmzorKyMmbPnp3G5MmTIycn5xxlo1kCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFXIpCsF/Xggw/GwoULY9GiRekjl19+eSxYsCCuu+66SNaMc5wbgRUrVqTuif/ixYvT/XTe9ra3pfbz58+P4cOHn5uOtUrgJQRuWfT1uK/26WhpW0fOQaAzCzw6/664tLi0M6coNwIECBAgQIAAAQIECBAgQIAAAQIECBDoAQKPPvpofPKTn4yampq48cYb4+67744xY8b0gJEbIgECBAgQIECAAAECF0og07ZJqNldF0pfvwQIECBAgAABAgQIECBAgAABAgQIZAXWrFkTN910U1RXV8edd94Zt99+e+Tm5mZfVyFAgAABAgQIECBAgAABAgQIEHh1AseOHYu1a9fG+vXrI1kgsK6uLhvbtm2LQ4cOdWiwT58+UVRUFP37949+/fply4KCgujVq1cayfc1ST0pT506FSdPnsxGcn78+PFI+m1ubk7jyJEjaT9JX0mcOHGiQ5/JSd++fWPo0KExbNiwNJIF85IYMWJENkaNGpVe69279wued4EAAQIECBAgQIAAAQIECBAgQIAAAQIECHR3geQ7+Pr6+vR7/i1btmS/72+vJ9/7t38Hn3yvP2HChDQmTpwY7TFp0qQoKSmJvLy87s5lfAQInEHgwIEDUVVVlcby5csjidWrV6efHcn/DVZWVsacOXNi9uzZaUybNs1c7jM4ukSAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQOFsCra2t8Yc//CEWLlyYRk1NTQwYMCCuuuqqWLBgQVxzzTUxcODAs9Wddl6hwK5du+LBBx9Mfya/+tWv4ujRozFv3rz0Z5L8XMrLy19hS24j8PoEbln09biv9uloafuscBDozAKPzr8rLi0u7cwpyo0AAQIECBAgQIAAAQIECBAgQIAAAQIEeohA8n+wP/rRj+If//EfY8eOHXHrrbfGHXfcEYMHD+4hAoZJgAABAgQIECBAgMD5FMi0/SPE7K7zKa4vAgQIECBAgAABAgQIECBAgAABAgQ6CJw6dSr++Z//Oe66666YOXNmfO9734uysrIO9zghQIAAAQIECBAgQIAAAQIECBB4aYGdO3emCwIuW7YsksUAV61aFRs3bozku5ecnJwYPXp0lJSUZGPs2LFRXFwcw4YNy0afPn1eupOz8GqyIF5TU1M899xz0djYGPv27Yu9e/fGnj17Yvfu3dHQ0BDJInpJJGNKXm8/MplMDB06NEaNGpVGMqakfnqZ1Pv379/+iJIAAQIECBAgQIAAAQIECBAgQIAAAQIECHQJgQMHDkR9fX1s3749Lbds2RJJ1NXVpZG8dvLkyXQsBQUFkXzPf/r3/uPGjcuejxgxIpLv1B0ECBB4OYFjx47FypUrI/k/xvZYsWJFJNcLCwvTud2zZ8+OOXPmRFImm13l5eW9XLNeJ0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQeBGB5ubm+OUvfxkLFy6MBx98MN2vZfz48XHttdfGggUL4o//+I+jd+/eL/K0y+dbIPl5PfHEE+nP6+GHH87+vJKfVRKXX365n9f5/qH0oP5uWfT1uK/26Whpbe1BozbUrijw6Py74tLi0q6YupwJECBAgAABAgQIECBAgAABAgQIECBAoJsKHD9+PL75zW/GZz/72XQvnttvvz0+/vGPp3vydNMhGxYBAgQIECBAgAABAhdAINPadlyAfnVJgAABAgQIECBAgAABAgQIECBAgACB2LhxY9xwww1RVVUVd999d9x2222Rm5tLhgABAgQIECBAgAABAgQIECBA4CUEWlpaYunS/8fefcBXWd1/HP9mL0jIBhL2JuwRCEtRVJRRFyoq4t9iFbdFW6u2blGpVq1WREURtVWRtoACogzBQJiyZO8kJBCyyJ7/e4691wQSBAVMyOfx9eOc5zznOc8573uJ5Ln3OWeNlixZooSEBBsHDhyQm5ub2rZtq06dOikmJsYVpqy2TgxoHq45ePCgkpOTbSQlJdk0MTFRJm/C5M1ke84tMDBQ0dHRatKkiU2ryps6bAgggAACCCCAAAIIIIAAAggggAACCCCA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IECAAAECBAgQIECAAAECBAgQeH+B6dOnxxe/+MU4fvx4/PKXv4ybb775/U9wLwECBAgQIECAAAECF1wg59QXgv/vN4Iv+KVdkAABAgQIECBAgAABAgQIECBAgACBCynwxhtvxI033hhpMoBJkybFZZdddiEv71oECBAgQIAAAQIECBAgQIAAgQsusG/fvrjtttuyyfaefPLJuPXWWy94Di5IgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBswmcOHEilixZEvPnz4958+Zl5ZtvvplN2tawYcO44oorYsCAAaXRsWPHszVjH4ELLnDgwIF45ZVXYtasWTFz5sx47bXXIi162Lp167jmmmtKIy2sbiNAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIVASB/fv3x/Lly2PZsmVnRGFhYaRxwbm5uZHG+6bxkz169Mgi1VPUr1+/InRRjgQIECBAgAABAgQInAeBw4cPx6ZNm0pj48aNpfW0P93euXNn6ZVq1qwZLVu2jDZt2mTzs6Q5WlI9rSub9qdo0aJF1KhRo/QclYolsHv37njuuedi4sSJ8fzzz0daK6dPnz4xZsyYLNI8Ujk5ORWrU7L92ALps4Q0R9Ozzz4bzzzzTKTPF5o1axajRo3KnhcjR46MunXrfuzraKDyC9w149F4unBOFJ88Wfk7q4cVWmDSqHExKK9bhe6D5AkQIECAAAECBAgQIECAAAECBAgQIECAQHkU2Lt3b3zta1+L3/zmN1k5fvz4qFOnTnlMVU4ECBAgQIAAAQIEqqRAzslTW5XsuU4TIECAAAECBAgQIECAAAECBAgQqEICc+fOjZtuuin69euXDRxu0KBBFeq9rhIgQIAAAQIECBAgQIAAAQJVUWDz5s1x8803ZxMrpsnU+vfvXxUZ9JkAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgXIgUFxcnC04PW/evJg/f34WixcvjiNHjkS9evXi8ssvj7RI2MCBA7OyS5cuFgwrB4+bFD6cwNGjRyONWZg5c2YWc+bMiQMHDsRll10W11xzTWmkRfHS4uo2AgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEBFETh27FisWrUqli1bFkuXLi0tV65cmY0VzsnJiTZt2kSPHj3eFQ0bNqwo3ZQnAQIECBAgQIAAAQKnBE6ePBnbt2+PDRs2xPr1689avv3226VWtWrVilatWkXr1q2z9wWpLIn0PiHVmzVrZi6hUrHKXzl+/HhMnz49Jk6cGGmtnPQ8at68eYwZMyZGjx4dw4cPjzp16lR+iCraw71798bzzz+fPf7PPfdc7NmzJ/usID326TnwiU98whxMVfS58XG6fdeMR+PpwjlRfOpvlI1AeRaYNGpcDMrrVp5TlBsBAgQIECBAgAABAgQIECBAgAABAgQIEKjQAv/2b/8WX/3qV7PvKaR6r169KnR/JE+AAAECBAgQIECgsgjknPoCsm93VZZHUz8IECBAgAABAgQIECBAgAABAgQInEVg1qxZMWrUqLjmmmviP/7jP6J27dpnOcouAgQIECBAgAABAgQIECBAgEDlEUiTsd90001Rr169eOGFF7KJFitP7/SEAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIHyLJCmcEiLSM+fP780Fi1aFAcOHIi0YFy/fv1iwIABpdG9e/eoVq1aee6S3Aick8CJEydi4cKFMXPmzCxmz54du3fvjkaNGsWQIUOysQ1pfEP//v2jevXq59S2gwkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC5UGgqKgo1q5dG0uXLj0jli9fHgcPHsxSbNmyZfTs2TN69OhRWqZ648aNy0MX5ECAAAECBAgQIECgygkcO3YsNm7cGBs2bIj169e/q0z3HTlyJHPJzc2N5s2bR7t27aJt27alZZs2baJ169bZOihNmzaNnJycKueowx9e4PXXX4+JEyfGs88+m81HVadOnRgxYkSMGTMmW2c4Ly/vwzfmyHIpsG7duuzxTY/zjBkzIs1BluZZSo/xrbfeGh07diyXeUuq4gjcNePReLpwThSfem7ZCJRngUmjxsWgvG7lOUW5ESBAgAABAgQIECBAgAABAgQIECBAgACBCi+Qvu/wuc99LhYsWBAPP/xwfPWrX63wfdIBAgQIECBAgAABAhVdIOfUFwd9u6uiP4ryJ0CAAAECBAgQIECAAAECBAgQIPAeAlOmTIk/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N998Mz7zmc/Ejh074l//9V/j5ptvfu+D3UOAAAECBAgQIECAwHkTyDl5ajtvrWmIAAECBAgQIECAAAECBAgQIECAAIGLJjBhwoS4/fbb4/7774/x48dftDxcmAABAgQIECBAgAABAgQIECBwoQQeeuih+J//83/GI488kn0mcqGu6zoECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECJR/gePHj2eLJi9atChSLFy4MBYvXhwHDx6MGjVqZAsmX3755dG/f/+44oorom/fvhZ6Kv8PqwwJZALbtm2L6dOnl0ZaFL169eqRFsQbNmxYFoMHD4569eoRI0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhUeoETJ07EypUrs/HVS5YsiZJYs2ZNFBUVRc2aNSM/Pz969eqVjbPu3bt3Vnbo0CFycnIqvY8OEiBAgAABAgQIlI3Azp07s9eh6bXo6bFq1ao4cuRIdtH69etHp06dsujcufMZ9TZt2kRubm7ZJKdVApVMIM2pNWPGjJg4cWI8++yzsW7dumjevHmMHj06xowZE8OHD486depUsl6f/+7s3bs3Jk2alDk+99xzkW5369Yt/viP/zhzvOqqq/xeOv/sWvwYAnfNeDSeLpwTxSdPfoxWnEqg7AUmjRoXg/K6lf2FXIEAAQIECBAgQIAAAQIECBAgQIAAAQIECBB4T4G0JtNXv/rV+PWvfx3f/va344c//GFUq1btPY93BwECBAgQIECAAAECH18g5+Sp7eM3owUCBAgQIECAAAECBAgQIECAAAECBC6mwIQJE+L222+P+++/P8aPH38xU3FtAgQIECBAgAABAgQIECBAgMAFEfjJT36SfRaSyq9//esX5JouQoAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBA+RTYv39/vP7667F48eJYtGhRFgUFBXHs2LFsQay0APIVV1xRGmlh5Fq1apXPzsiKAIFzFti6dWu2ON706dMjxYoVK6J69eoxcODAGDZsWBaDBw+OevXqnXPbTiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIVFSBI0eOxPLly2PJkiVnxIYNGyItbZ/GXvbo0SPSeOw0BrskWrRoUVG7LG8CBAgQIECAAIHzLHDw4MFYtWpVrFy58l2xe/fu7Gp16tSJzp07R9euXc+ITp06RV5e3nnOSHMECCSBN954IyZOnJjF/Pnzs7m2brjhhhgzZkx88pOf9LN32tNk7dq18eyzz2ZWM2fOzN4PDx06NPr06RP/+q//ms1H9tBDD8UXvvCF085SJVA+BO6a8Wg8XTgnik99jmMjUJ4FJo0aF4PyupXnFOVGgAABAgQIECBAgAABAgQIECBAgAABAgSqjMATTzwRX//617M1WyZMmBAtW7asMn3XUQIECBAgQIAAAQIXWiDn1CA93+660OquR4AAAQIECBAgQIAAAQIECBAgQOA8CqR/rN9+++1x//33x/jx489jy5oiQIAAAQIECBAgQIAAAQIECJRPgcceeyzuueee7LOQb33rW+UzSVkRIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFAmAhs3bozXX389Fi9eXBqFhYXZgk6NGzeOfv36xeWXX14a+fn5Ua1atTLJRaMECJRPga1bt8b06dNLIy1gWaNGjRgwYEAMGzYsi8GDB2eLopfPHsiKAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFB2Avv374+CgoJYsmTJGfHWW29lF23SpEn06tUri969e5fWGzVqVHZJaZkAAQIECBAgQOCiCZw8eTLWr18fy5YtyyLN1VESmzdvzvJKc/i0b98+unbt+q5o06ZN5OTkXLT8XZhAVRdI8+38/ve/j4kTJ8bUqVPjyJEjceWVV0bbtm2rOk0sX7483nzzzWjYsGHcdNNNMWbMmLjlllui5P3trl27YuzYsfGLX/wihgwZEj/72c+y98BVHg5AuRG4a8aj8bs1L8fJcpORRAicXWDSqHExKK/b2e+0lwABAgQIECBAgAABAgQIECBAgAABAgQIELjgAm+88UbcdtttsWfPnvj1r38dI0aMuOA5uCABAgQIECBAgACBqiCQc+qL2L7fVRUeaX0kQIAAAQIECBAgQIAAAQIECBColAK//e1v43Of+1z8xV/8RTz00EOVso86RYAAAQIECBAgQIAAAQIECBA4XeBXv/pV3HHHHfHggw/Gd77zndPvUidAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoBIJHD58OFu0+PXXX480KVlJuXv37qyXHTp0iH79+mXRt2/frGzXrl0lEtAVAgTOl0BaIG/69OmlkRa4rFGjRgwcODCGDx8e1113XQwaNChq1659vi6pHQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAhRPYuXNnLFmyJIs333wzKwsKCmLv3r1ZX1q3bh29evUqjd69e0f37t2jTp06Fa6vEiZAgAABAgQIVEWBY8eOxapVq2LZsmWxfPnyrEz1FStWxKFDhzKS5s2bR7du3aJr165nRKdOnbL5Oqqimz4TqEgC6Wd5ypQpMWnSpNi1a1dFSr1Mcm3ZsmV88pOfjGuvvfZ9f4fNnz8/vva1r8WiRYviG9/4Rnzve9+LBg0alElOGiVwLgLPrHst/nPtq+dyimMJXHCBmrnV40ef+GI0qeX35gXHd0ECBAgQIECAAAECBAgQIECAAAECBAgQIPA+Avv3748vfelL8dvf/ja++93vxrhx4yI3N/d9znAXAQIECBAgQIAAAQLnKpBz8tR2ric5ngABAgQIECBAgAABAgQIECBAgACBiy/wzDPPxKc+9am4995745FHHrn4CcmAAAECBAgQIECAAAECBAgQIFDGAtOmTYubbropvvnNb8aPfvSjMr6a5gkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQuBACxcXFsWbNmkgLEJ8eq1evjnRf3bp1swWI+/btG3369ImSsmHDhhciPdcgQKASCmzds7CLAAAAQABJREFUujWmT58e6XsIKQoLC6N27dpx9dVXx3XXXRfDhw+PgQMHRvXq1Sth73WJAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIHBuAhs3bszGgi9ZsiRSpHHhy5cvjyNHjkRubm506tQpevfunY0L79WrV1Z26dLFWM1zY3Y0AQIECBAgQOC8Cezbty97vbZs2bJIkV67pTLN81NUVJS9huvQoUN079498vPzszLVUzRq1Oi85aEhAgQIVCSBNOfZL37xixg7dmw2H9HDDz8cn/nMZypSF+RKgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBdwn8wz/8Q9x///3ZmiwTJkyIvLy8dx1jBwECBAgQIECAAAECH00g5+Sp7aOd6iwCBAgQIECAAAECBAgQIECAAAECBC6WwPPPPx+33npr3HnnnZH+qW4jQIAAAQIECBAgQIAAAQIECFR2gTSp+JAhQ+KWW26JJ598MnJycip7l/WPAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKUSSFMbrFu3LluIrmRh4YKCguz24cOHs0XpOnbsmC0s3KdPn6xMiwx37tw5u69SYegMAQLlSmDDhg0xderUeOmll2LatGmxefPmqF+/fgwdOjSuv/76LPr16+d3Ubl61CRDgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIXEyBoqKiWL16dbz55ptRMn48lWlfuq9WrVqRn58fvXr1OiPatWtnfumL+cC5NgECBAgQIFCpBPbv3x9pDp8UJa/Jli1bls2dkTpau3bt6Nq1a3Tv3j2L9Pos1dO+dJ+NAAECBN4tsHPnzvj2t78dv/zlL2PEiBHx2GOPRadOnd59oD0ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCoIAKLFi2K2267LdIaUU899VRcffXVFSRzaRIgQIAAAQIECBAo3wI5pxZpPVm+U5QdAQIECBAgQIAAAQIECBAgQIAAAQKnC7z00ksxatSo+PSnPx3/9E//ZDKs03HUCRAgQIAAAQIECBAgQIAAgUopsGXLlvjEJz4RHTp0iMmTJ2cTh1fKjuoUAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgUogcOLEiWxR4KVLl0ZajC5Fqi9fvjybRCx1sVWrVmcsEpwWDe7Ro0fUrVu3EgjoAgECFV1g5cqVMW3atCymT58eO3bsiMaNG8ewYcPiuuuui+uvvz569uxZ0bspfwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAeRc4cuRINsZ8yZIlcXps2LAhu1aDBg2ycZppjHmKNGYzlc2bNz/vuWiQAAECBAgQIFBZBE5/jVVQUFD6Oiu9xjp58mTUq1cvm7+nZB6f7t27R35+frbGR25ubmVh0A8CBAhcUIGXX345vvKVr2Rzqo0dOzb+6q/+KmrWrHlBc3AxAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQInC+BPXv2xO233x4vvPBCPPzww3Hvvfeer6a1Q4AAAQIECBAgQKDKCuSc+jL3ySrbex0nQIAAAQIECBAgQIAAAQIECBAgUMEE5syZEyNHjozRo0fHk08+GSZjqGAPoHQJECBAgAABAgQIECBAgACBcxY4evRoXHPNNbFv375In400btz4nNtwAgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC519g165dsXLlyixWrFgRKZYuXZotlHT8+PHsu87t2rXLFqZLC9L16NEjUpmiYcOG5z8hLRIgQKAMBNK0LGlx82nTpmUxY8aM2Lt3b+Tl5cV1110X119/fVZ27ty5DK6uSQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBA5RBIc0ynMZvvjB07dmQdvPTSS6NXr15nRM+ePc1JXTkefr0gQIAAAQIEPqTAiRMnsvl80mumgoKC0tdOa9asiaKioqhZs2bk5+eXvmZKr5fSa6gOHTpETk7Oh7yKwwgQIEDgwwqk38uPPPJIfP/734/WrVvHY489ls059GHPdxwBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgTKk0Bag+XBBx+M733ve/HZz342fv7zn0fdunXLU4pyIUCAAAECBAgQIFChBHJOvcg+WaEyliwBAgQIECBAgAABAgQIECBAgACBKiqwePHiGDZsWFx33XXx1FNPRfXq1auohG4TIECAAAECBAgQIECAAAECVUngS1/6Uvz7v/97zJs3L7p27VqVuq6vBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBC66wOHDh2P16tXZonQrV66MFStWlNbffvvtLL/atWtHp06dsoXpunfvHiWRFqqrU6fORe+DBAgQIHA+BdJinAsXLoyXXnoppk2bFrNnz46DBw9GmzZtsoXhrr/++qxMi8XZCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIE3l9g+/btsWTJkjOioKAg9u3bl53YsmXL6NWrVxY9e/bMyh49ekT9+vXfv2H3EiBAgAABAgTKucCOHTvi9ddfPyOWL18ex44di2rVqkXnzp1LXwel10PptVCXLl2sY1rOH1fpESBQOQU2bNgQ9913X0ycODG+8IUvxN/+7d9Gs2bNKmdn9YoAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgUov8MILL8TnP//5SN/Tffrpp7PvqVT6TusgAQIECBAgQIAAgTIQyDl5aiuDdjVJgAABAgQIECBAgAABAgQIECBAgMB5FFi1alUMGTIkevfuHX/4wx+iVq1a57F1TREgQIAAAQIECBAgQIAAAQIEyqfAL37xi/jyl78c//Vf/xVjxowpn0nKigABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAFF0gL7xYWFsaaNWti9erVZ8TmzZsjTUmQm5sbbdu2ja5du2bRrVu30nran+63ESBAoCoKHD9+PF577bWYNm1avPTSS/HKK6/E0aNHs8kRhw8fHiNGjIjrrrsumjRpUhV59JkAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg8JEENm7cGEuWLMmioKAgK5ctWxaHDh2KnJycaNeuXfTq1St69uyZRarn5+dHnTp1PtL1nESAAAECBAgQKCuBoqKiWLFiRbz++utnxNatW7NL5uXlRd++fUuj5HWNNUvL6hHRLgECBD66wDPPPBNf//rXY//+/TF+/Pi48847s/eoH71FZxIgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQODiCKxfvz7+9E//NFur6le/+lWMHj364iTiqgQIECBAgAABAgQqsEDOqcVeT1bg/KVOgAABAgQIECBAgAABAgQIECBAoNILbNq0KQYPHhwtWrSIF198MerXr1/p+6yDBAgQIECAAAECBAgQIECAAIG5c+fGNddcE3/5l38ZP/zhD4EQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIPARBY4ePRobNmyIdevWRZq4a+3atVmsWbMmCgsLY+fOnVnLubm50aZNm+jcuXN06tQpunTpktVLSgvSfcQHwGkECFQpgcOHD8ecOXNi2rRp2RiQBQsWRJra5fLLL4/hw4dnMXToUIuXV6lnhc4SIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC50OguLg4Gyu/ZMmSKCgoiJJy+fLlcezYsUhj5jt27Bi9evWKnj17lka3bt3CePnz8QhogwABAgQIEPgggT179sTrr79+RqTXLUeOHInq1atHfn5+9O3b94zIy8v7oGbdT4AAAQLlSODgwYPxwAMPxE9/+tMYMmRIPP7445Hed9oIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQ0QTSd1ruvffe+OUvfxljx46NH/zgB9n3cStaP+RLgAABAgQIECBA4GIJ5JxapPDkxbq46xIgQIAAAQIECBAgQIAAAQIECBAg8P4CO3fujKFDh2aTPcyYMSOaNGny/ie4lwABAgQIECBAgAABAgQIECBQCQT27t0b/fr1yya//MMf/mCQQCV4THWBAAECBAgQIECAAAECBAgQIECAAAECBAgQIECg7ATSRFwbNmyIdevWnRHr16/Pbm/dujVKphVo0KBBtGvXLjp06BCdOnXKIi2gm+rt27e3aG7ZPUxaJkCgigqk70C89NJL8eKLL8bUqVMjLWCeFii/+uqr44Ybbojhw4fHgAEDolq1alVUSLcJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAh9P4MSJE7F69eooKCiIJUuWZGWqr1y5MtJ9aRxn586do2fPnmdE165do2bNmh/v4s4mQIAAAQIEqqzAW2+9FQsXLsxiwYIFWZnm/EnbpZdeGn379o0+ffpkZVp/o0ePHl57VNlni44TIFAZBdLv/rvvvjuWLl0aY8eOjW9/+9t+z1fGB1qfCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECFQBgSeeeCLuvffeuP766+M3v/lNNGrUqAr0WhcJECBAgAABAgQIfHyBnFMLxZ78+M1ogQABAgQIECBAgAABAgQIECBAgACB8y2wf//+GDZsWOzZsydmz54dLVq0ON+X0B4BAgQIECBAgAABAgQIECBAoFwKfPazn43p06fHG2+8EZdddlm5zFFSBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBC6EwNGjR2PLli2xadOm2Lx5cxYl9Y0bN8a6desiLURXMm3AJZdcEu3bt8+iXbt2pfWSfU2aNLkQabsGAQIECLyHQPpdPnXq1HjxxRezMv2Ob9iwYTZ+5IYbbogU+fn573G23QQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAh9W4Pjx47FixYpYunRpFBQUxJIlS7JyzZo1ceLEiahevXp07tw5evbsmUWPHj2ysmvXrlGzZs0PexnHESBAgAABAlVAIM0XsXDhwliwYEFWpnral7Y0z0///v3jiiuuiMsvvzz69u0brVq1qgIqukiAAAECRUVF8Xd/93fx13/919nfg5///OcxZMgQMAQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKhwAnPnzo0/+ZM/ibp168YzzzwT3bt3r3B9kDABAgQIECBAgACBCy2Qc2oh2ZMX+qKuR4AAAQIECBAgQIAAAQIECBAgQIDA+wscO3YsbrnllmyyqZdffjk6duz4/ie4lwABAgQIECBAgAABAgQIECBQSQT+5V/+Je6888544YUXYsSIEZWkV7pBgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4EyBNMx/9+7dsXXr1mwhuU2bNmVlWlTu9PqOHTtKT0yL1zZv3jxbXK5169aRon379lmkRehSvXHjxqXHqxAgQIBA+RdYtmxZvPjiizF16tSYPn167N27N1q2bBnDhw+PG264ISstKlr+H0cZEiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAhVHIK0PtmLFimx9sKVLl2ZlQUFBrFmzJk6cOBFpbH+nTp2iZ8+e0aNHj9Lo1q1b1K5du+J0VKYECBAgQIDARxJYv359LFy4MBYsWJCVqf7WW29FTk5O9hrhiiuuiBT9+/fPyiZNmnyk6ziJAAECBCqPwLp16+JrX/taPP/88/HlL385fvzjH8cll1xSeTqoJwQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIVAmBbdu2xZ/8yZ/EkiVL4te//nWMGTOmSvRbJwkQIECAAAECBAh8VIGcUwvTnvyoJzuPAAECBAgQIECAAAECBAgQIECAAIHzL5A+svvc5z4Xzz33XMyYMSP69et3/i+iRQIECBAgQIAAAQIECBAgQIBAORRIk2unz0LSRGjjx48vhxlKiQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMB7CxQXF8eOHTti+/bt2YJxadG4s0W6P8Xx48dLG6tbt260bt06WrVqlUVJvaRM+5s3bx65ubml56gQIECAQOUSKCoqinnz5sXUqVPjxRdfjFdeeSWOHj0a+fn5ccMNN8Tw4cPjuuuui4YNG1aujusNAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKAcCBw7dixWrFgRS5cujYKCgqxM9VWrVsWJEyey8f4dO3aMHj16lEb37t0jRb169cpBD6RAgAABAgQInKvAtm3bsrke5s6dGykWLFgQb7/9dvZ3v2vXrnHFFVdk0b9//7j88svN+XCuwI4nQIBAFROYMGFCfOMb34iaNWvG//k//ydGjx5dxQR0lwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBii6Qvk97zz33xD/+4z/G97///XjggQciJyenondL/gQIECBAgAABAgTKRCDn5KmtTFrWKAECBAgQIECAAAECBAgQIECAAAECH0ng/vvvzwb5Tpo0Ka6//vqP1IaTCBAgQIAAAQIECBAgQIAAAQIVTaCoqCgGDx4caUDAq6++mk2EVtH6IF8CBAgQIECAAAECBAgQIECAAAECBAgQIECAAIHKI3D8+PHYtWtXFmlBuFR/Z1myb8eOHfHWW2/Fzp07o7i4uBQhLf5z2WWXRV5e3ntG8+bNo1WrVtGoUaPS81QIECBAgEASOHz4cMyaNSumTp0aL774YixevDibVHHAgAExfPjwGDlyZAwaNMh3LDxdCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJlKJDmH1i1alUsXbr0jFi5cmUcPXo0G//Zpk2b6NGjR3Tv3j2LknqTJk3KMDNNEyBAgAABAucicODAgViwYEHMnTu3NDZs2JD9Le/SpUtcddVVkeZ0uOKKK6Jfv35Rv379c2nesQQIECBAIBNI89Wltal//etfx3/7b/8t/v7v/z6bjw4PAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEKpLAY489lv3vc/To0fEv//IvvktTkR48uRIgQIAAAQIECFwwgZyTp7YLdjUXIkCAAAECBAgQIECAAAECBAgQIEDgfQV+/OMfx9ixY2PChAnx6U9/+n2PdScBAgQIECBAgAABAgQIECBAoDIJjB8/Ph544IFYuHBhNkl2ZeqbvhAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECFxYgTSEPi32tm/fviz27t0b+/fvL62X7C8p0/27du3KIi3ak+rp+HdudevWjUsvvTTSAq+nl02bNo28vLx3RePGjd/ZhNsECBAgQOAjC6S/US+99FK8+OKLWaxZsybq1asXw4YNixEjRmSRFiO3ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIlL1AUVFRFBYWxtKlS2PZsmWl5fLly7M5D1IGzZo1i/z8/OjevXtppNtt2rSJnJycsk/SFQgQIECAQBUVOHHiRLz55psxd+7c0kh/s4uLi6NFixYxcODAuPLKK7NI9UaNGlVRKd0mQIAAgbISmDRpUnzlK1+JgwcPxt/93d/FF77whbK6lHYJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQJgIzZ86MT33qU9m6XM8880x07NixTK6jUQIECBAgQIAAAQIVVSDn1OK5Jytq8vImQIAAAQIECBAgQIAAAQIECBAgUJkEfvWrX8Udd9wRP/nJT+K+++6rTF3TFwIECBAgQIAAAQIECBAgQIDA+wqsWrUq+vbtGw888ECMHTv2fY91JwECBAgQIECAAAECBAgQIECAAAECBAgQIECAQMUVSIuvHTt2LI4ePZrF6fXDhw9HiiNHjmRlye2S8tChQ9kCOmkRnbPF/v37Y9++fVmk+tmG0afFV+vVqxeXXHLJGdGwYcNo0qRJaVx66aVZ/Z1lrVq1Ki6+zAkQIECg0gmsXbs2pkyZEpMnT45p06bF7t27o2XLljFixIgYOXJk3HDDDdmi5JWu4zpEgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECjHAmm+g02bNsWyZcveFTt27MgyT3MfdOvWLfLz88+I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nSZAgAABAgQIECBAgAABAgQIECBwEQWKiopi9OjRsXDhwnj11Vejffv2FzEblyZAgAABAgQIECBAgAABAgQIXHiBF198MUaMGBHPPfdc3HzzzRc+AVckUIkEfvOb38TYsWNj27Ztcffdd8df/uVfRtu2bStRD3WFAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBMpK4PDhw9kirJMnT44pU6bEm2++GTVq1IghQ4bEjTfemEWfPn0iJyenrFLQLoELIrB79+7YsmVLFps3bz5rPX334sSJE6X51KpVK5o1axZ5eXml0bx582xfKi+77LJo2rRpXHrppVmZjrcRIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBMq7QHFxcRw8eDD279//nnHgwIHYt29f7N27911l2ley/8iRI2ftbv369aNx48ZZNGrU6H3LdFwan9GkSZMs0vgmGwECBAh8PIFDhw7F6tWrY9WqVe+KNJYubbm5udG6devo3LlzaXTq1Cmrp7JevXofLwlnEyBAgACBswik9xlz5szJ5jmYPXt2zJ07N9L7ihYtWsTQoUOzSPMd9O7dO6pVq3aWFuwiQIAAAQKVR2D+/Plx5513xrp162L8+PHx5S9/ufJ0Tk8IECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKjUAmnth1tvvTUKCwvjd7/7XQwbNqxS91fnCBAgQIAAAQIECJxNIOfkqe1sd9hHgAABAgQIECBAgAABAgQIECBAgEDZCXz961+PJ554ImbMmBEDBw4suwtpmQABAgQIECBAgAABAgQIECBQDgWOHTsWffr0ie7du8d//ud/lsMMpUSgYggsW7Ys7rnnnuxzxrvuuiseeOCBaNOmTcVIXpYECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUC4F0qLhU6ZMiRdeeCErt2/fHs2bN4+RI0fGjTfeGCNGjIjLLrusXOYuqaorcPjw4di4cWNs2LDhPctDhw6VAtWpUydatmxZGq1atSqtp/3pOZ+XlxeNGjUqPUeFAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEDg3QJpntm9e/fGvn37sjLVd+/eHXv27MnK0+sl+04vjxw58q5GGzRoEE2aNIlLL730rGW6r2nTplmksU6pfskll7yrHTsIECBA4OwCBw4ciNWrV8eaNWuyMtVLbm/atClOnjyZnZjG2XXs2DE6dep0Rpn2tWjRInJycs5+AXsJECBAgMBpAmkOg9mzZ8esWbOyeP3116O4uDi6du0aQ4cOjSFDhmRl+ntjI0CAAAECVVEgfb72ve99Lx566KG44YYb4h//8R8jzYViI0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQHkXSOtE3HHHHfFf//Vf8bOf/Szuvvvu8p6y/AgQIECAAAECBAicV4GcUwOx/u9IrPParMYIECBAgAABAgQIECBAgAABAgQIEHgvgfTP6fvuuy/+/d//PW677bb3Osx+AgQIECBAgAABAgQIECBAgEClFfjf//t/x4MPPhjLli2Ltm3bVtp+6hiBshJIk+L+6Ec/inHjxkWfPn3i8ccfj/79+5fV5bRLgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAlVUIE1Ls2jRonjhhRfi+eefj1deeSVOnDgRV1xxRdx0001x4403xqBBg6J69epVVEi3L5TAwYMHY926dbF27drSsqS+cePG2LlzZ2kqdevWjTZt2mTfS0plSb1169bZ4ootW7aMxo0blx6vQoAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgcPEEjhw5Ert374633347du3alUWqv/N2uq9kXyrTeadvNWrUiKZNm8Zll12WlWerN2vWLPLy8rJo0qRJ5OTknN6EOgECBAicEki/XwsLC2PNmjVZpHrJ7TTOr+T3b506daJ9+/bRoUOH6NixY1amekk0bNiQJwECBAhUUYE0DnzmzJkxY8aMmDVrVqxevTqqVasWffv2jaFDh2YxZMiQ7HV5FSXSbQIECBAgcFaBV199Ne64447Yvn17/P3f/33cfvvtZz3OTgIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECJQngbSuybhx4+IHP/hBfOtb34q/+Zu/idzc3PKUolwIECBAgAABAgQIlJlAzqkXxCfLrHUNEyBAgAABAgQIECBAgAABAgQIECBwhsALL7wQo0aNiu9///vx3e9+94z73CBAgAABAgQI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oFwJDB06NPbt2xevvvpq5OTklKu+6yyBSyVw+PDhbCG/3/72t/HjH/84vvSlL12qrmiXAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAicscCWLVvipZdeismTJ8eUKVNi69at0aJFixgxYkSMGjUqhg8fHo0bNz7j+txYtgQOHToUK1asiGXLlmVpyqdIi3mm7welo0GDBtGlS5fo2rVrFp07d45OnTplka45CBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECJR3gbQ1+M6dOyPNp0qxcePG2LBhQ0mk8/Xr18fmzZsj7WFUfDRq1CjatGnzodGsWTNr+BaDSQkQqBQC6XUyvWauXbs22+/txDTl161bF/v37y+xSPNU27VrF+3bt8/Stm3bZq+rrVu3jpRPaf369UvulyFAgACBPy1QUFAQ8+bNixkzZmTxyiuvxN69eyO9N+3fv39JXHnllVGlSpU/XaE7CBAgQIAAgY8s8Pzzz8cXvvCFqFu3bjz22GNx8803f+Q6VUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIELKbB9+/a4/fbbY8mSJfG73/0uBg8efCGbUzcBAgQIECBAgACBSyaQc3ySadEla13DBAgQIECAAAECBAgQIECAAAECBCqBwL333htPPPFEzJ07N3r16lUJRmyIBAgQIECAAAECBAgQIECAAIGTBSZMmBBjx46NtEjoTTfddPINSggQOElgz549MW7cuFiwYEH89re/jSFDhpx0jwICBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFDWBdLyNm+99VZMnjw5Jk2alM2xKSwsjOuuuy5GjhwZo0aNit69e9tgtgz+INNmwMuXL4+lS5dmsWzZsix99913I/0Mq1atGpdffnl06dIlunbtWhLpvGXLlmVwRLpEgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4OILpDlWW7ZsiQ0bNsTGjRuzNOU/GLt37y7pXLVq1aJ169bRpk2bkjTl27Vrl0X79u2z8tzc3JJnZAgQIFDRBXbt2hVr166NdevWZXFifv369dlr7KFDh0oY6tatm72OptfP00WaD+m1tIRMhgCBSiZQUFAQb775ZsyYMSOLtJfEvn37okWLFjFgwIAYOHBgFt27d69kMoZLgAABAgTKlkD6XOkv//IvY+LEifH1r389HnjggUifHTkIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQVgUOHz4cf/7nfx7/9V//FT//+c/jrrvuKqtd1S8CBAgQIECAAAEC5yyQc3zyaNE5P+1BAgQIECBAgAABAgQIECBAgAABAgQ+VODf/u3f4ktf+lL87ne/izvuuOND73WRAAECBAgQIECAAAECBAgQIFBRBQoLC6NXr16RFgZ95plnKuowjYvAeRXYunVrjBo1Ktsc4qWXXsr+/pzXBlRGgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQukcCePXsi/S48Pz8/Jk+eHGlD72bNmsWIESNi5MiRWdq0adNL1LvK2ez+/ftjyZIlsWjRoixdunRppEgbsKejZs2a0bVr1+jRo0f2HYaUpujUqZPNCCvnHxmjJkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQuAACBw4ciA0bNnxobNq0KQoKCrLWc3Nzo1WrVtG+ffto165dFilfHB06dIjGjRtfgJ6qkgABAmVXYMeOHSe9jm7cuLFU2fbt26OoqCgbRJUqVaJly5bRpk2bUtG6detS5/Xr1y+7g9YzAgQInKFAeh/5xhtvxIwZM2LmzJnxyiuvxL59+7LXwQEDBsTAgQOz6Nat2xnW6DYCBAgQIEDgYgo8/PDD8bWvfS26dOkSTzzxhL1sLia+tggQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQOGuB9F3Nb37zm/H9738//v7v/z7+8R//8azr8AABAgQIECBAgACBsiyQc/xN73/PUCrLvdQ3AgQIECBAgAABAgQIECBAgAABAuVQIC2MMWzYsPjWt76V/cK5HA5BlwkQIECAAAECBAgQIECAAAEC50Xgsccei7vvvjuWLl0anTt3Pi91qoRARRZYv359DB48OFuA/KWXXorLLrusIg/X2AgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgkgssWrQo8vPzs5gzZ04cO3Ys+vTpE6NGjYqRI0dGv379Im3e7fjoAmlD4JUrV0YyX7x4cZam/Lvvvpt9T6F27drRo0ePUtG9e/e4/PLL/Qw+Or8aCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECHxkgTT/atOmTbFu3bpYu3Ztln4wv3Xr1pJ26tSpEx06dIj27dtnUZxPaVrztE2bNuYOlWjJECBQWQSOHDkSGzdujA0bNpw20vVDhw6VkNStWzdatGiRRcuWLUvypypLr70OAgQIlAWB9N7xzTffjGnTpkXaWzPN59+3b1+k17GBAwdmMWDAgOjWrVtZ6K4+ECBAgAABAmcgsGrVqvif//N/xsKFC+PBBx+ML37xi2fwlFsIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBw6QR+8YtfxL333huf/vSn49FHH41q1apdus5omQABAgQIECBAgMB5FMgpOn6cx/pURYAAAQIECBAgQIAAAQIECBAgQIDAcYE1a9ZEnz59YvDgwfH0009HTk4OFwIECBAgQIAAAQIECBAgQIBApRQ4evRotmDooEGDsi/jV0oEgyZwFgJpA4e00G6tWrViypQp2SLiZ/G4WwkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQLkW2Lt3b7z88suRn58fkyZNirVr10aTJk1i+PDhMWrUqBgxYkQ0b968XI/xYnV+69atMX/+/FiwYEEWixYtiuXLl0faFL1q1arRuXPnyMvLi169emWR8pdffnlUqVLlYnVROwQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIXACBQ4cOZXOz3nvvvSxN87SK8yldv359Ns8oNV2tWrVo165ddOjQIS677LKSSOcdO3aMNm3aRG5u7gXopSoJECBQ9gV27NgRGzZsiI0bN2bpli1b4sTYvHlzdr5r165Sg6lTp062tnTLli2ztEWLFqXSE8vr1q1b6lknBAgQ+CgChYWF2RzzGTNmxLRp02LWrFmR5vC3atUqW/9+4MCBkaJr164fpRnPEiBAgAABApdYoKCgIB544IH43ve+F2PHjo1f/OIX0bRp00vcK80TIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEDg9AKTJ0+Oj3/843HjjTfGM888E/Xq1Tv9za4QIECAAAECBAgQKCcCOUXHj3LSV90kQIAAAQIECBAgQIAAAQIECBAgUC4EDh48mP1iOX30Nnfu3Khdu3a56LdOEiBAgAABAgQIECBAgAABAgQuhMDPfvazuP/++2PVqlXRvn37C9GEOglUGIG0cHhadDdtqpAW5rU4X4X50RoIAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECJyjwNKlSyM/Pz+L2bNnx9GjR+Paa6+NUaNGZXHddddlv2c/x+orxGPHjh2LlStXxoIFC7LNgIvTtGl5Olq3bh1XXXVV9OrVK4u8vLzo3r171KhRo0KM3yAIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEDg7gcLCwti0aVO89957sWbNmixOzK9duzYOHTqUVVqtWrVsXeGOHTvGqaJ58+Zn17i7CRAgUAEFjhw5Elu3bo00tzOtM/3BOLF8165dkfa3Kz7SHnctWrSIli1bZmmzZs2ytanT+tSnytetW7f4USkBAgSy15MlS5bE9OnTY9q0aTFz5sxIrzPpNWTAgAExePDgGDRoUDa/HBcBAgQIECBQ8QTSejyf/exno6CgIB577LEYMmRIxRukEREgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUGEE5s2bF6NHj8720Jg4cWL23ckKMzgDIUCAAAECBAgQqJQCOccnCf1xllClJDBoAgQIECBAgAABAgQIECBAgAABAudXIE2czc/PjzfeeCNb9Oz81q42AgQIECBAgAABAgQIECBAgED5EUiLxHfq1CnuuOOOeOihh8pPx/WUwCUQ2LFjR7YQ79GjR7PFedNi3w4CBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEDgjwL79+/PNr1N83ZSrFmzJho1ahTDhg2LUaNGxciRIyv8ApHp+zgLFy6MtDBmivnz58fixYvj4MGDUbVq1Wzj36uuuiquvvrqKE7T5sAOAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEEBMkS4AAEAASURBVCBAgAABAgQInKlA2vJ8y5Yt8e67754y1q1bFwUFBVl1devWjcsvvzyLK664olS+Q4cOUb169TNt1n0ECBCoFAJpDeqtW7fG5s2bs9fa9Hp7Ymzbti22b98exenhw4dLudSoUSPS3NFmzZplaco3adIkGjduXJKemE/XGjZsGLm5uaXqcUKAQPkVWLFiRUyfPj2bez9jxozs9SL9Pe/fv38MHjw4Bg0aFL169YqcnJzyO0g9J0CAAAECBM5YYPfu3XHPPffEM888E3/zN38T3/3ud6NatWpn/LwbCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgcDEF0nfU094iR44ciUmTJkXXrl0vZvPaIkCAAAECBAgQIHBeBXKOT8YsOq81qowAAQIECBAgQIAAAQIECBAgQIBAJRb48Y9/HF/72tciPz8/hg8fXoklDJ0AAQIECBAgQIAAAQIECBAgEPEv//Iv8c1vfjNWr14dLVu2REKAwGkEDh48GEOGDMkW/J49e3a0adPmNHcqJkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBIoFli9fni0ImebxzJw5M1sgsnfv3jF69OgYM2ZM9OnTJ6pUqVJ8e7lL9+/fH/Pnz4958+Zl8eabb8ayZcuioKAg6tevH9dcc01cffXVcdVVV2Vpjx49Im0a7iBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMCFFEhznNavX5+tO5zWHk7xzjvvlJzv3Lkzaz7N72rXrl1cccUV0alTp5JI5ynq1KlzIbupbgIECFQIgX379sW2bdti+/btWXwwv2PHjkiRXnuL08OHD5cae05OTjRs2DAaN25cEo0aNcrKUnpiPt134nmDBg0iNze3VH1OCBC4uAJr166Nl19+OYtp06bFpk2bol69enHzzTfH4MGDY9CgQdnc8/I8t/7iimqNAAECBAhUTIFf/vKXcd9990W3bt3iP//zP7PPYSrmSI2KAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIHyLpC+E3nrrbfGqlWrYvz48XHDDTeU9yHpPwECBAgQIECAQCUVyCk6flTSsRs2AQIECBAgQIAAAQIECBAgQIAAgfMqMGPGjBg2bFj80z/9U3zjG984r3WrjAABAgQIECBAgAABAgQIECBQ3gQOHjwYHTt2jM997nPxgx/8oLx1X38JXDSBY8eOxcc+9rGYO3duFp07d75obWuIAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhUFIEDBw5kG+ZOnDgxUqRNdJs1axajRo2K0aNHx/Dhw7PNrsvqePfv3x/z5s2L119/PUtTfsWKFVFYWJht5H3NNddE796949prr82iU6dOkTb7dhAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKCsCezevTtWr14d77zzTpa+/fbbURwbNmyI4i3VW7VqFWmuVIq0JmtxpPM6deqUtWHpDwECBMqNQJq3unPnztixY0eWnphPZbt27SqJ9JqdzlO6Z8+ektfo4sGm+az16tWLhg0bRoMGDaJ+/fpZ+mH54mvp3vRsilq1ahVXKSVA4E8IbNu2LaZPn57Nn582bVr2PqpmzZpx0003xZAhQ2LQoEHRp0+fqFq16p+oyWUCBAgQIECgsgmsWrUqPvnJT0ZKH3744fjUpz5V2QiMlwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBciJw8ODB7PebL730Ujz11FMxbty4ctJz3SRAgAABAgQIECDwR4Gc45Mli/54KkeAAAECBAgQIECAAAECBAgQIECAwLkIrFu3Lnr37h39+/ePZ5555lyq8AwBAgQIECBAgAABAgQIECBAoEIJ/Ou//mt84xvfiDVr1kTz5s0r1NgMhsD5FPirv/qrePzxxyMt4Hv99defz6rVRYAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEKq3A4sWLY8KECZGfnx9z5szJNru+8cYbY/To0VlceeWVl8zmyJEjsXDhwnj99ddLYunSpVFYWJh9z+baa6+N4kjzlS677LJL1lcNEyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4HwKHDp0KFavXh1vv/12Fu+8806sWrUqi7Vr12bzrFJ7rVu3js6dO2fRpUuXLO3atWtcccUVUb169fPZJXURIECAwP8vkOa6vv/++7Fr167YvXt3lhbn0/mePXuy6+meFCeeF+cPHz58Ss/c3NyoW7du1KtX75Rx4rU6derEmUaq10GgvAvs3bs3Zs2aFS+//HIWixYtivRnu0+fPjFkyJAYPHhw3HTTTVGjRo3yPlT9J0CAAAECBC6CQFrb5n/9r/8Vae+ou+++O0tr1659EVrWBAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBM5O4NixY/GlL30pHn300XjooYfii1/84tlV4G4CBAgQIECAAAECl1ggp+j4cYn7oHkCBAgQIECAAAECBAgQIECAAAEC5VogTYzt379/pMU3fv/732cL1JTrAek8AQIECBAgQIAAAQIECBAgQOAjCqTPS9JC7HfeeWf8+Mc//oi1eZxAxRV48MEH4+tf/3r87ne/i3HjxlXcgRoZAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBC4hAJpA+vJkyfHxIkTIz8/P7Zu3Rpt27aNMWPGxOjRo7NNd9Mm1BfiSBttL1u2LF5//fWSWLBgQaTv19SvXz969+4dffv2LYkOHTpciG6okwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAmVe4PDhw7F69epYtWrVSbF+/fpIW7Hn5uZGmofVtWvX6NKlS0mk8zRvLCcnp8yPUwcJECBQkQXSa/mePXsize9Nadrbb9++fVma8qeKE6+n/P79+0sizcn9sKNGjRqR5gnXqlUri9q1a58yn66feK34/po1a0aqI0Vxvjg9VVm6Vr169ahWrdqHdcs1Ah8qkP6ezJ07N6ZNmxYvv/xyNg/92LFjkZeXl819HzJkSLY3ZpqP7iBAgAABAgQInKvA888/H5///OejZcuW8fTTT2fvNc61Ls8RIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEDgQgp85zvfib//+7+Pf/iHf4hvf/vbF7IpdRMgQIAAAQIECBA4rwI5xyc9Fp3XGlVGgAABAgQIECBAgAABAgQIECBAoJIJ3HffffEf//Ef2eIb3bp1q2SjN1wCBAgQIECAAAECBAgQIECAwMkCjzzySHz5y1/OFmtv06bNyTcoIUAgJk2aFGPGjIkHH3wwvvKVrxAhQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIELoJAWmrnjTfeiAkTJsTEiROzfNrkecCAATF69Ojsd/mdOnU6555s3bo1fv/732fx2muvxR/+8IdsI+y0mfTVV18dffv2LYmuXbtGTk7OObflQQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECFQWgQMHDsSqVati5cqVWaxYsaIkv2vXroyhdu3a0aVLl0hzt4oj7SeVyurWrVtZqIyTAAECFUqgoKAg9u/fXxL79u0ryReXp38jUhw8eDCLE/Op7HTnhw4disOHD0dxmuYhn81RrVq1SPOUU5yYP9151apV44ORnvtg2QfPc3Nzo0qVKlmcKn+qsuL7i+cyp7Q40hiL86e7fjYO7v3TAunP1rvvvhuLFi3KYvny5XHkyJFo3rx59OrVK4u8vLyoX7/+n67MHQQIECBAoJwKFL/vSN0/2/yJz6T3OcXvZYrLi8/PNi2u68T3TqcrO7E8tVP8zInpie/LUvmJ5x/Mp75fjGPdunXxqU99KubNmxc/+clP4gtf+MLFaFYbBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQOGuBRx99NO69996455574qc//Wn2e9mzrsQDBAgQIECAAAECBC6yQM7xCQNnNxvlIndQcwQIECBAgAABAgQIECBAgAABAgTKssBTTz0Vn/70p+Ppp5+OT3ziE2W5q/pGgAABAgQIECBAgAABAgQIELgoAseOHcsWVB86dGg8/PDDF6VNjRAobwJpYd/rr78+7rjjjvjlL39Z3rqvvwQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoMIIbN26NfLz82PixIkxZcqU2L17d3Tu3DnGjBkTo0ePjgEDBmSbPp9qwGlT3/nz58drr71WEmnj37RJYJcuXbLvBqTvB1x33XXZpr9ps2cHAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECJxfgW3btsXKlStjxYoVpeKdd96Jo0ePZo21bds2unXrdlK0adPm/HZGbQQIECBQbgXS3OHDhw/HoUOHPjRN9xVH+nemOJ/S052n8hQFBQVnHWnt/8LCwvhgemLZifni+4qKiuLESD+YMzkvtz9AHSdAgAABAgQIlCOBtD5NlSpVIjc3N0tTPq1Nk84/alSrVi2rI9WXIrWV9slZunRpdOzYMW655ZaoVatWqXtSm8X3F6epnpQ/XXom16pXr549n+o4MZ/Oi+stRz82XSVAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4AILPPvss/GpT30qxo0bF48//vhp9wq5wN1QPQECBAgQIECAAIEzFsg5PlGj6IzvdiMBAgQIECBAgAABAgQIECBAgAABAiUCaeJrv3794i//8i/jxz/+cUm5DAECBAgQIECAAAECBAgQIECgMgv8+te/jrvuuitbcP3yyy+vzBTGTuCUAu+//3707ds3mjRpEjNmzIgaNWqc8j6FBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwMUVSBs2z507NyZMmBATJ06MxYsXR506dWLo0KFx6623Ru/evePtt9/O7nn11VfjrbfeyjaPbty4cTbH6Prrr48Uab5Ro0aNLm7ntUaAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECpQTSnLHVq1fH8uXLs1ixYkVJfufOndm99erVi27dumXRvXv3KI4rrrgiqlatWqo+JwQIECBAgACB0wns2LEjpk2bFlOnTs0ivQdJc9VvueWWbL76sGHDolevXpGTk3O6KpQTIECAAIFKIVBUVFQyznPJp2c+LFLlH3b9xGuFhYWRIpV9MH+qshPvPfF6Kj927FhJHR88P/Hah+XTtY8a6bOQ4kh1nZjfuHFjvPnmm1G9evXIy8uLmjVrZu0V35PS4meOHj2aPfvBNN1TXJYMPuqR3htVq1atVKT+nViWzj9qpL2BUqR6ivPF6anKPnhvlSpVPupQPU+AAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwBkKzJgxI8aNGxfXXXdd/Nd//VfUrVv3DJ90GwECBAgQIECAAIGLL5Bz/Mt0H/3bdBe/31okQIAAAQIECBAgQIAAAQIECBAgcEkF9u3bF3379o3GjRtH+iVxmljqIECAAAECBAgQIECAAAECBAgQiGzh0quvvjoef/xxHAQInELgYx/7WPz+97+PN954I1q1anWKOxQRIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECl1ogbYY3ZcqUeOyxx2L27NmRNtArXqanZcuWcfPNN8fYsWPjhhtuiM6dO9vo91L/wLRPgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBsxDYtm1bLFu2LJYvX55FyqdYu3ZtNpcs7UeV5o517969VHTr1i1q1ap1Fi25lQABAgQIEKiIAocOHYpXXnklXnrppZg6dWrMnz8/m3Oe9rccOnRoFmkuevXq1Svi8I2JAAECBAgQKKcC69evj0984hOxcOHC+NnPfhaf/exnz3kkaX2egoKCOHr0aKm0uCyVpzhy5EiWFp+fS9nhw4ezelJd5xqpjhSp/bM9qlatGjVq1IiaNWtmUZwvTlP5qfIfLEufKaUorqc4f7o03Zeu5ebmnm2X3U+AAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgXAu89dZbMXLkyOjQoUNMnDgxmjZtWq7Ho/MECBAgQIAAAQIVVyDn+MZ2RRV3eEZGgAABAgQIECBAgAABAgQIECBA4MIIfOpTn4qXX3450i+H27Rpc2EaUSsBAgQIECBAgAABAgQIECBAoJwJTJo0KUaNGhULFiyIK6+8spz1XncJXHiBH/3oR/H1r389pk+fHjfffPOFb1ALBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwBkJ7NmzJ1577bWYM2dOzJ07N37/+9/H3r17o379+nH99ddHv379sk3eVq9enW0CnDbTa9WqVYwZMyZuvfXWGDJkSNSpU+eM2nITAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECJRNgQMHDsSKFSti2bJlWSxdujRL33777Th69GhUqVIlLrvssujevXv06NEji+J8vXr1yuag9IoAAQIECBD4yAJFRUWxcOHCmDJlSjbffPbs2XHo0KHo2rVrDB06NIYNGxYDBw6MBg0afOS2VECAAAECBAgQuJAC6fONtHfOv/zLv8Q999wTP/nJT6JmzZoXsskyVXd6X3fkyJE4fPjwSXGq8uKy9N4vPZPS0+X/1PWDBw9mzxanhYWFZ2RTtWrVqFWrVvZzql27dpYvTlN5ihPP/1Q+rZOU7imOE89zc3PPqE9uIkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIHChBdL3t4cPH57tEzJ58uRo3779hW5S/QQIECBAgAABAgTOWiDn+JfSis76KQ8QIECAAAECBAgQIECAAAECBAgQqMQCP//5z+OLX/xiTJo0KVusoxJTGDoBAgQIECBAgAABAgQIECBAoJRAWtg0JycnW/i01AUnBAjE3LlzY8CAAfHd7343/vZv/5YIAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgcAkFtmzZEmlT3+JIm/0eO3YsLr/88rjpppvixhtvzCIvLy+qVKlyUk/nz58f48ePjxdffDFef/31qF69egwePDjGjh0bY8aMiQ4dOpz0jAICBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQKJ8CBQUF8fbbb8fSpUuzWLZsWZauWLEiDh48mA2qXbt20b179+jZs2f06NGjJBo2bFg+B63XBAgQIECgkgts3LgxXnrppWzvhalTp8bWrVujWbNmMXTo0Bg+fHiWtm3btpIrGT4BAgQIECBQXgWeffbZ+Iu/+ItsvZ3f/e530bFjx/I6lHLb76NHj2afKx06dOik9FRl6TOoFAcOHMjSM80X35fq/FNHWkepdu3aUadOnSxN+eIoLqtbt25WltJUdiZR/Eza28xBgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4EwFNm3aFCNGjIhdu3bF5MmTs+9nn+mz7iNAgAABAgQIECBwMQRyio4fF6MhbRAgQIAAAQIECBAgQIAAAQIECBCoCAILFy6M6667Lr72ta/Fd77znYowJGMgQIAAAQIECBAgQIAAAQIECJwXgQULFsTVV1+dfXE+LXjqIEDgjwI7duzI/n5cddVVMX78+LCY2R9t5AgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwMUQeOedd2L27NklsWrVqsjNzY30u/xbbrkli5tvvjlatGhx1t1JGwVPmDAhXnzxxWwD4b1790avXr1i7NixWVx//fVRpUqVs67XAwQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQtgUKCwtjzZo1sWTJkli6dGksW7Ysyy9fvjz27duXdb5169bRo0eP6NmzZ6m0UaNGZXtwekeAAAECBCqZwIEDB2LmzJkxZcqUbN54+ve9Ro0akeahp/0Xhg0blq03b535SvYHw3AJECBAgEAFFkhr8tx5552xbt26eOKJJ2LkyJEVeLSGVlRUFAcPHoz9+/dHeu/7wThV+QfL0vmJkT7/Kj5P9RUUFHwodO3ataNu3bofKerVqxfFkeqyttOHkrtIgAABAgQIECBAgAABAgQIECBAgAABAgQIECj3Art3745bb701+552fn5+9O3bt9yPyQAIECBAgAABAgQqjkDO8S9mFVWc4RgJAQIECBAgQIAAAQIECBAgQIAAgQsnkCYk9u7dO1q2bBnTpk2L3NzcC9eYmgkQIECAAAECBAgQIECAAAEC5Uzgc5/7XMyfPz8WLlxYznquuwQuvMAdd9wR8+bNi7feeisaN2584RvUAgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQqMQCaTmdtJnvjBkzYvbs2Vls2rQpatasGf369YtbbrklixtvvDHbSOx8Uh05ciTbVPjFF1+MFKtXr46mTZvG6NGjY+zYsTFixIioX7/++WxSXQQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQxgTSPLe1a9dmc92WLl0aKdK8t2XLlsXevXuz3rZq1Sp69uxZKnr06BENGzYsY6PRHQIECBAgUDEFCgsLs7Xjp0yZEi+99FLMmTMn0nzxXr16xfDhw2PYsGHRv3//qFWrVsUEMCoCBAgQIECAwHGBgwcPxj333BNPPvlkPPDAA/HNb34zcnJy2BA4J4HDhw/H/v37Txtpf/hziWPHjp22P3Xq1MnWkapXr94ZpWn9pw9GejaVVa1a9bTtuECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIHDpBNLvNe+888545ZVXYvz48TFgwIBL1xktEyBAgAABAgQIEDhBIOf4RMKiE85lCRAgQIAAAQIECBAgQIAAAQIECBA4jcBnPvOZbHGP+fPnR+vWrU9zl2ICBAgQIECAAAECBAgQIECAQOUT2LBhQ3Ts2DF+/vOfx1133VX5AIyYwIcIPPLII3HvvffG9OnTs0WCP+RWlwgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFzEEjL5yxatChmzJgRM2fOzGLHjh3RoEGDuOmmm+KWW27Jom/fvlG9evVzaOHcH1m6dGm8+OKLWcydOzeqVKmS9WXs2LGRonPnzudeuScJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoNwJrF27NpYsWRJp/llKi/P79u3LxtKmTZvo2bPnSVGvXr1yN1YdJkCAAAECZU1g48aNMWXKlJg8eXK2L2Wal96yZcsYNmxYSaRzBwECBAgQIECgsgn89Kc/jb/+67+OkSNHxuOPP56t3VPZDIy37AocOnQo0mdnKfbu3XvG8cH79+zZkz1bWFh4ysHWqlUr6tevf1Kkz+VSeVrT6sQ4VVm6N60z5SBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEDi/AkePHo3PfOYz2d4fzzzzTIwePfr8NqA2AgQIECBAgAABAucgkHN8A72ic3jOIwQIECBAgAABAgQIECBAgAABAgQqlcCjjz4a99xzT+Tn58eIESMq1dgNlgABAgQIECBAgAABAgQIECDwpwS+8Y1vxGOPPRZr1qyJ6tWr/6nbXSdQaQRWrlwZ1157bdx3333x/e9/v9KM20AJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMCFFEibdy1cuDBmzJgRM2fOjFmzZsXOnTujUaNGcfPNN8fAgQNjwIABcc0115SpjbhSHydNmpQtSJnSXbt2Rbdu3eK2227L4oYbbihT/b2QP0N1EyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMAfBdI282vXro0lS5aUiqVLl8aBAweyG9u3bx89e/aMvLy8krR79+5Ru3btP1YkR4AAAQIECJQSOHToUMyePTsmT54cU6ZMiUWLFkXNmjXjlltuyfakHD58ePTq1avUM04IECBAgAABApVVYO7cufHxj3886tSpE88++2z2GURltTDuii2wb9++2LNnTxZ79+4tyReXnZieeP3999+P4kj3FBQUnASVk5MT9erViwYNGkT9+vWzNOVTNGzYsFScqizdk/7P4iBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEDhZ4NixY/GFL3whfv3rX2fxiU984uSblBAgQIAAAQIECBC4iAI5xycGFl3E9jRFgAABAgQIECBAgAABAgQIECBAoNwJLFu2LPr06RP3339/fO973yt3/ddhAgQIECBAgAABAgQIECBAgMCFFEiLprZt2za+8pWvxN/93d9dyKbUTaBcCRw9ejRuuOGGrM+vvvpqVKtWrVz1X2cJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEBZEUjL46SNfKdNmxbTp0+PWbNmxe7du6Nx48bRv3//GDBgQBZXXXVVVKlSpax0+0P7kTYOmzNnTrzwwgsxfvz4WLVqVTRt2jTGjBkTt912W6SNiuvWrfuhdbhIgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAhVbIM2ve/fdd2PJkiVZLF68OEuXL18eaW3oNKeuY8eO0bNnz8jLy8si5bt16xbVq1ev2DhGR4AAAQIETiOwdOnSmDJlSkyePDlmzpwZBw8ejB49emRzuEeMGJHNTa9Vq9ZpnlZMgAABAgQIEKjcAps3b45PfOITMW/evPjVr34Vd955Z+UGMXoCHyJw4MCBeP/9908Ze/bsOak8rZuVIj1TnB47duykFmrUqBENGzaMBg0aZGnKp0hrbjVq1CjLp/RUUb9+/XKzDtdJA1dAgAABAgQIECBAgAABAgQIECBAgAABAgQIEDgDgfT96q9+9avx0EMPxSOPPBKf//znz+AptxAgQIAAAQIECBC4MAI5x9+gFl2YqtVKgAABAgQIECBAgAABAgQIECBAoPwLHD58OPr16xdpkY9XXnklqlatWv4HZQQECBAgQIAAAQIECBAgQIAAgfMo8Mtf/jLuvffeWLt2bbRo0eI81qwqAuVb4Nvf/nb8v//3/7IF8bp27Vq+B6P3BAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEDgIgusXLkypk2blsWMGTNi27Zt2WZXAwcOjBQDBgyIK6+8MnJyci5yzy5Mc8uWLYsXXnghxo8fH6+++mpUq1YtBg8eHLfddluMHTs22rZte2EaVisBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIlDuBY8eOxTvvvBOLFy+OJUuWlKRpbt7Ro0ezfbY6d+4ceXl5WfTs2TNLO3XqFLm5ueVuvDpMgAABAgQ+TGDXrl0xderUmDx5chbr16/P5qYPHTo0RowYkYX52h8m6BoBAgQIECBAoLRA+mzhK1/5Svzbv/1b/N3f/V088MADFWadn9IjdUbg0goUFRXFvn37Yvfu3SfF+++/X6os/b+nONL9Kb9nz55IdZx4VKlSJerXr5/9n6hRo0YlaePGjeN0ke5L12rXrn1iVfIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgTIt8K1vfSu+853vxI9+9KPs95tlurM6R4AAAQIECBAgUGEFco5/gaf0N3gq7FANjAABAgQIECBAgAABAgQIECBAgMDZC9x3333x2GOPxfz586Njx45nX4EnCBAgQIAAAQIECBAgQIAAAQIVXKB3797RvXv3+PWvf13BR2p4BM5cIH2e2K9fv3jwwQcjfcboIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBD5cYO3atTFt2rQspk+fHmlT37p168Ytt9wSgwcPzuLqq6+OtLlVRT+2b98eL774YrzwwgsxZcqU2L9/f1x77bVx2223ZXHNNddUdALjI0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFzEDh69GisWLEiFi9eHEuWLMnSlF+9enUUFhZGzZo1o1u3bpGXl1cqOnTocA6teYQAAQIECFwagfRv2uuvvx6TJk3K4g9/+EPk5OTEddddFyNGjMiib9++lWJu+qX5CWiVAAECBAgQqCwCjzzySHz5y1+OUaNGxeOPPx716tWrLEM3TgLlQiD932j37t2xa9euUnGqsp07d8aJsXfv3pPGWKNGjWjcuPFJ0bRp02jSpElWntIPRvXq1U+qSwEBAgQIECBAgAABAgQIECBAgAABAgQIECBA4GIIPPjgg/E3f/M38cADD8S3vvWti9GkNggQIECAAAECBAiUEsgpOn6UKnFCgAABAgQIECBAgAABAgQIECBAgEAmMH78+Ljtttviqaeeik9+8pNUCBAgQIAAAQIECBAgQIAAAQIEPiDw6quvxo033hgpvf766z9w1SmByimQFtlPiwrXr18/Zs6cmS04XDkljJoAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECJxeIG1ENW3atJg6dWoW77zzTqTNp9J3UQYPHpxFv379omrVqqevpBJcOXz4cOb0wgsvRJrrtGHDhmjbtm025ynNexo4cGDmVgkoDJEAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEzlHg4MGDsXTp0li8eHFJLFmyJNatW5fVWK9evejZs2fk5eVFr169sjTlmzdvfo4teowAAQIECJxfgc2bN8fkyZNj0qRJ8dJLL8WOHTuiXbt2MXLkyBgxYkQMGTIkGjZseH4bVRsBAgQIECBAgEDMmTMn7rzzzmjSpEk8//zz0alTJyoECFQAgYKCgkjrgJ0Yu3btKnVefG379u3Z/8HS/8P27Nlz0ujr1q2bvUak14kPRtOmTeNUUatWrZPqUUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQOBeBRx55JO6999746le/Gj/84Q/PpQrPECBAgAABAgQIEDhngZyi48c5P+1BAgQIECBAgAABAgQIECBAgAABAhVUYOPGjXHllVfGbbfdFv/+7/9eQUdpWAQIECBAgAABAgQIECBAgACBjybwmc98JlasWBFvvPHGR6vI0wQqkMC3v/3t+MEPfhALFy606F0F+rkaCgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAh8NIHDhw9nm8lNnTo129B33rx5kZOTE3369ImhQ4fG4MGD48Ybb4yaNWt+tIYq+NNvvvlmvPDCCzF+/Ph46623Im28lTZETnOgxowZk22+VcEJDI8AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEzpPA+++/H0uWLIlFixbF4sWLS2L79u1ZC82aNYu8vLzo1atXlqZ8inr16p2nHqiGAAECBAicWqCgoCDmzp0bkyZNivz8/FiwYEFUr149brnllhg5cmQWPXv2PPXDSgkQIECAAAECBM6rwPr16+P222+P1atXx29+85tsvaDz2oDKCBAoNwLp/2o7duyInTt3ZmnKf1ikzxnT9fTciUft2rWjadOmHxrNmzeP9PlkiiZNmkSVKlVOrEKeAAECBAgQIECAAAECBAgQIECAAAECBAgQIFAi8NRTT8XnPve5uOeee+KnP/1pthdKyUUZAgQIECBAgAABAhdQIKfo+HEB61c1AQIECBAgQIAAAQIECBAgQIAAgXInkD4yGzp0aKTJqfPmzYs6deqUuzHoMAECBAgQIECAAAECBAgQIEDgQgts3rw5OnToEA8//HDcddddF7o59RMoFwJp8eG+ffvG//2//ze++tWvlos+6yQBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIELgQAml+Tvo9+tSpU+Oll16K2bNnx8GDB6Nz587ZvJ1hw4bFoEGDomHDhhei+UpR57p162L8+PHxwgsvxPTp0+PYsWNx4403xq233hrjxo2LLl26VAoHgyRAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACB8yuwZcuWWLx4cSxatChLU37JkiWxb9++rKG0NnVeXl4WvXr1ytJu3bpFjRo1zm9H1EaAAAEClUpg7dq1MWnSpCxefvnl2LNnT3Tq1ClGjhyZRZqfXrt27UplYrAECBAgQIAAgbIicOjQobj77rvjN7/5Tfzrv/5rfPGLXywrXdMPAgTKuEBaj+z999+P7du3nza2bdtW6tquXbsiPVd8VKlSJRo3bhzNmjWL5s2bZ2nKnyrS9aZNm0Zubm7x41ICBAgQIECAAAECBAgQIECAAAECBAgQIECgEgg8++yz8clPfjI++9nPxiOPPBLp9wsOAgQIECBAgAABAhdaIOf4l1z++C2XC92a+gkQIECAAAECBAgQIECAAAECBAiUA4Ef/vCH8X/+z/+JV199NXr37l0OeqyLBAgQIECAAAECBAgQIECAAIGLL/C9730vfvSjH8X69eujZs2aF78DWiRQxgQKCwvjhhtuiKpVq8bs2bNNCiljPx/dIUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIELL7Bx48aYMmVKTJ48OdKGvmlDp7Q505AhQ2Lo0KFZpE3lHedfYO/evZn7+PHjY8KECbFjx47o0aNHjBs3Lm6//fbo27dv5OTknP+G1UiAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEClUKgqKgo1qxZE4sXL85i0aJFWbpixYo4cuRI5ObmRufOnaNXr16Rl5dXEp06dbJWb6X4E2KQBAgQOHuB9O/HrFmzIj8/P4tly5ZF7dq1Y9CgQTFq1KgYOXJkXHHFFWdfsScIECBAgAABAgQumMA//dM/xT/8wz/Efffdl+1dlT4PcBAgQOB8Cxw7dixbRyutY5Zi69atWVp8/sE0rbmVnik+0npbTZo0iebNm0eLFi2yNOVPd163bt3iR6UECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAORaYOHFi3HnnnVn86le/yr7fXI6Ho+sECBAgQIAAAQLlQCDn+KS7onLQT10kQIAAAQIECBAgQIAAAQIECBAgcFEEFixYEP369Ytvf/vb8b//9/++KG1qhAABAgQIECBAgAABAgQIECBQ3gTSV47SYqu33357tpBXeeu//hK4EAIPPfRQfO1rX4u33norevbseSGaUCcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEChTAocPH47Zs2fH5MmTs0ibxdesWTNuueWWGDZsWAwdOjSuvvrqSBsxOS6eQNoEK/1cnn/++SzefffdaNWqVYwbNy6LwYMHR/Xq1S9eh7REgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAhVWoKCgIFauXBlpjuHixYtLYvXq1VFYWJjNO+zRo0fk5eWVinbt2lVYEwMjQIAAgdMLvPfee5Gfn5/Fyy+/HPv374/078SoUaNi5MiR2Vz1GjVqnL4CVwgQIECAAAECBC65wG9+85u46667YsCAAfH0009H/fr1L3mfdIAAgcotkD6H3LlzZ2zbti2LLVu2xNatW0vig+fvv/9+KbBatWpF8+bNo0WLFlm0bNmyVJrKi8u85pWic0KAAAECBAgQIECAAAECBAgQIECAAAECBMqcwNSpU7N9OUaPHh1PPvlkVKtWrcz1UYcIECBAgAABAgQqjkBO0fGj4gzHSAgQIECAAAECBAgQIECAAAECBAicu8ChQ4eiT58+0bhx45gxY0ZUqVLl3CvzJAECBAgQIECAAAECBAgQIECgAgtMmTIlRowYEUuXLo3u3btX4JEaGoEzE9iwYUP2d+G+++6L7373u2f2kLsIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEA5FFi+fHlMnjw5izT/5uDBg9nvzNN3SVKkTeHSRkqOsiOwcOHCeO655+L555+PefPmRb169bINmG+//fYsbdiwYdnprJ4QIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQIUQOHDgQLaG9eLFi+PESGv5pqNBgwbRs2fPyMvLKxXNmjWrEOM3CAIECBD4b4EjR47EK6+8Evn5+TFx4sTs34Y6derEkCFDsrnOo0ePjvbt2+MiQIAAAQIECBAoZwKvv/56jBs3LtsHfPz48dGxY8dyNgLdJUCgMgscPnw4tm7dWhJbtmzJ8ilNsXnz5ixN+e3bt0dRUVEJV1pjrUWLFlm0bNmyJN+qVatI58VpytesWbPkORkCBAgQIECAAAECBAgQIECAAAECBAgQIEDg4gnMmjUrxowZE4MGDYrf/va3UaNGjYvXuJYIECBAgAABAgQqlUDO8S+W/PGbJZVq6AZLgAABAgQIECBAgAABAgQIECBAoLTA/fffH7/61a9iwYIF0aFDh9IXnREgQIAAAQIECBAgQIAAAQIECJQI/I//8T+yBW5mz55dUiZDoDIL3HHHHbFo0aIs0gJPDgIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUFEE9uzZE1OnTs029J0yZUqsXbs2GjVqlG3oO2LEiEjRrl27ijLcCj+OdevWxfPPPx/PPfdcpEUv0zFw4MBsM7+0oV/btm0rvIEBEiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMClE9i1a1csXrw4iyVLlpTkd+zYkXWqefPmkZeXFz179szS4nyDBg0uXae1TIAAAQJnJZDmNOfn52eR5qrv27cvunXrFqNHj45Ro0ZF//79o3r16mdVp5sJECBAgAABAgTKnsD69evj1ltvjQ0bNsT48ePjuuuuK3ud1CMCBAh8RIGCgoLYtm1bbNmyJdu378T0xPymTZti586dpVpr2LBhtGrVKlq2bPmhaePGjUs954QAAQIECBAgQIAAAQIECBAgQIAAAQIECBD46AKvvfZajBw5Mq6//vp49tlno1atWh+9UjUQIECAAAECBAgQ+IBATtHx4wNlTgkQIECAAAECBAgQIECAAAECBAhUOoHJkydnC4o8/vjj8ZnPfKbSjd+ACRAgQIAAAQIECBAgQIAAAQJnKrB169Zo27ZtPProo/G5z33uTB9zH4EKK/DCCy/EuHHjIn3GOHz48Ao7TgMjQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgcojsGjRomwz34kTJ8acOXOisLAw+vXrFyNGjMgi5XNzcysPSAUd6e7du2PChAnx/PPPx6RJk2Lv3r3Ru3fvuP3227PvQvTq1auCjtywCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKCsCWzevDkWL15cKpYuXZrNfUt9Teti5+XlZdGzZ88s7d69e9SpU6esDUV/CBAgUOkEjh49ms1LT/PT8/Pzs9fy9Po8aNCgbH/I0aNHx2WXXVbpXAyYAAECBAgQIFAZBPbv3x9/9md/FtOmTYsnnngi7rjjjsowbGMkQIDAKQWOHDkS6XPOFJs2bTptumXLlkj3Fh81a9aMVq1aRevWrUvSU+UbNWpU/IiUAAECBAgQIECAAAECBAgQIECAAAECBAgQOAOBN998M4YPHx7XXHNNvPDCC1G7du0zeMotBAgQIECAAAECBM5cIKfo+HHmt7uTAAECBAgQIECAAAECBAgQIECAQMUT2LVrV7YYVP/+/eOpp56qeAM0IgIECBAgQIAAAQIECBAgQIDAeRT4wQ9+EN///vdj48aNUatWrfNYs6oIlD+BQ4cORY8ePeL666+PJ598svwNQI8JECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMBxgb1798bUqVMjbeg7adKkWL9+fTRr1ixGjhyZbeg7YsSIaNy4MasKLHD48OFsE7/nn38+W/gybVx1+eWXx7hx4+L222+Pm266KXJzcyuwgKERIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQFkTKCoqirVr18bixYuzWLJkSZYuW7Ys0trAOTk50bFjx+jZs2cWeXl5WdqtW7eoWbNmWRuO/hAgQKBCCWzZsiXy8/NjwoQJMWXKlNizZ0907do1m58+atSoGDBgQNSoUaNCjdlgCBAgQIAAAQIETi1w7Nix+PKXvxw///nP40c/+lHcf//9p75RKQECBAhkAulzz507d8bmzZsjrfeV9gMsjhPPUz6tD1Z8pM88W7duHa1atcrSNm3aRHGk8uK8vQWLxaQECBAgQIAAAQIECBAgQIAAAQIECBAgQCBiwYIFMWTIkLjyyivjxRdfjNq1a2MhQIAAAQIECBAgcN4Eco5/EaTovNWmIgIECBAgQIAAAQIECBAgQIAAAQLlUODTn/50zJw5M1sYqlGjRuVwBLpMgAABAgQIECBAgAABAgQIELg4AumrRl26dImRI0fGQw89dHEa1QqBMizwwAMPxA9/+MNYsWJFtqBSGe6qrhEgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgVICabP1tKHvxIkTY86cOZE2cevbt2+MHj0629S3T58+2cbrpR5yUikE0neE/vCHP8Rzzz2XxfLly6NJkyZx6623xsc+9rEYNmxYpE2oHAQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4FIIFBYWxjvvvBNLlizJ9h0rTleuXBlHjhyJ3NzcuOKKK6Jnz56lomvXrlG9evVL0WVtEiBAoNwLpDnIr7/+ejY/fcKECfHmm29GjRo1YsCAATFmzJgsLr/88nI/TgMgQIAAAQIECBA4d4Ef/OAH8Y1vfCPuv//+ePDBB6NKlSrnXpknCRAgQCAT2LlzZ2zcuDE2bdqUpSfmN2zYECnStYKCghKxRo0aRZs2bUpF69atS87btm0bzZo1s85ciZgMAQIECBAgQIAAAQIECBAgQIAAAQIECFR0gYULF8aQIUOy7xWn77/VqVOnog/Z+AgQIECAAAECBC6SQM7xyRZFF6ktzRAgQIAAAQIECBAgQIAAAQIECBAocwK/+c1v4s/+7M8iPz8/Ro4cWeb6p0MECBAgQIAAAQIECBAgQIAAgbIkMGvWrGwR1wULFsSVV15ZlrqmLwQuusCaNWuie/fu8cADD8Tf/u3fXvT2NUiAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBM5G4NChQzF9+vRIixm++OKL8d5770XTpk1jxIgRMXr06Cxt0qTJ2VTp3koisHLlynjuueeyeO2116J27doxatSouOOOO7KNoBs0aFBJJAyTAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBMqyQEFBQaxatSoWL14cS5YsKYlUlq5VrVo1OnXqFD179owePXpkacp36dIlqlevXpaHpm8ECBC4JAK7d++OKVOmZHPUJ02aFFu3bo127dpl89PHjBkTQ4YMyeYeX5LOaZQAAQIECBAgQKBMCjz99NPx53/+59l7xieffDJq1qxZJvupUwQIEKhIAoWFhdn/2Tds2BAbN26MlJ4q0v/zi4/0eWibNm2ibdu2J0X6v38qb9GiRVSpUqX4ESkBAgQIECBAgAABAgQIECBAgAABAgQIECjXAun7xYMHD47u3btn34mrW7duuR6PzhMgQIAAAQIECJQNgZyi40fZ6IpeECBAgAABAgQIECBAgAABAgQIELi4Aps3b468vLz4+Mc/Hj/72c8ubuNaI0CAAAECBAgQIECAAAECBAiUQ4G777473nrrrZg3b1457L0uEzi/ArfffnssW7YsFi1aZHH480urNgIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBA4TwJp858JEyZkMXXq1Dhw4EBcffXVMXbs2Cz69u1rY5/zZF1Zqtm0aVM899xz8eyzz8aMGTOyYadFMj/2sY/FbbfdFi1btqwsFMZJgAABAgT+P/buBL7K6s7/+PdmZwkQtoQkJARCAmETQQQVZVEBgywJ4No609JaW+0orbbjtP9CR6vVti5D7VDtTOtYFcEQEiBCtRoUlE1AIGwhLAlhCVtCErKR/HPOzL1N2GQJ4S6fp69fz3me+yzn977JJd7nOecggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAh4iUFVVpR07dmjLli2NYteuXaqpqVFAQIDi4+PVp08fJSUlucrExETGHvaQ95hmIoBA0wmYz0pnH/WVK1eqrq5ON910k5KTk3XXXXepX79+TXcxzoQAAggggAACCCDglQKfffaZHYvG/Hd2Zmam2rVr55V5khQCCCDgaQJmHLrCwkIVFBS4Ij8/31U324uKiux3ASY3871pZGSkoqOjbXTt2lXOiImJsfXOnTvL4XB4GgXtRQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEPBRAfN8nJlfIyEhQVlZWWrdurWPSpA2AggggAACCCCAQFMJOOo7XdQ11ck4DwIIIIAAAggggAACCCCAAAIIIIAAAp4kMH78eG3btk0b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NYGKigotXbpU8+fPtxNlFxcXa8iQIUpNTbVhJrpnQQABBBBAwClw+vRpnThxwsbx48dlwqybfz9MNKyfa91sM+c4c3E4HGrdurXatGlz0WH2Dw0NtcedWZrzsSCAAAIIIIAAAggggAACCCCAAAIIIIDAlQvU1taqqKhIBw4cUEFBgfLz822cWa+srLQX8/f3V1RUlLp166a4uDhbOuvmvpN5je/vrvx94QwIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIHDtBUz/iD179mjnzp2uyM3NlQmzvbq62jYyLCxM5n5pfHy8DVM30b17d0VGRnIP9dq/lbTABwTKysq0bNkyZWRkaPHixfZZCPM7ePfdd2v8+PG67bbbFBgY6AMSpIgAAggggAACCCDgSwJz5szRI488ol/+8pf62c9+5kupkysCCCCAgAcLHDp0SHl5eTbMd6/O71xNefToUZtZQECA7a/Ss2dP1/euzu9fTV8Wvufx4B8Amo4AAggggAACCCCAAAIIIIAAAggggAACXi+wevVq3X777brjjjs0d+5c5pbx+necBBFAAAEEEEAAgcsXcNRPVlh3+YdzJAIIIIAAAggggAACCCCAAAIIIIAAAu4tMG/ePE2bNk0fffSRRo0a5d6NpXUIIIAAAggggAACCCCAAAIIIICAmwlMnz5d69ev17p169ysZTQHgeYRMI/XDRw4UNHR0Vq0aFHzXJSrIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgh4rICZhPvTTz/V+++/rwULFmj//v12wpeUlBSZGDJkCBNse+y7S8PdSaCqqsr2F5s/f74WLlxoJ1u67rrrlJqaaqN3797u1FzaggACCCBwmQI1NTU6fvy4jh075gqz7owTJ07YurM0203dRElJyVlXdTgcCg0NVdu2bdWuXTtbXmy9TZs2MmGON+dhQQABBBBAAAEEEEAAAQQQQAABBBBAAAHPEzD9houKirR3717t3r1be/bsseGsm+2nTp2yiYWEhKhHjx72fq+zjI+Pt+uxsbHy8/PzPABajAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCJwhYPrFmnulubm52rVrly1N3YS5l9rwHmpcXJy6d+9u76Wa0hndunVTq1atzjgzqwggcLECBw4cUGZmpjIyMmzfWdOHdujQobr77rtt9OnT52JPxX4IIIAAAggggAACCHiswBtvvKHvfve7+vWvf60nn3zSY/Og4QgggAACCBgBM/bNzp07G33f6lw3/VrM4u/vL9M/xdlXxZQ9e/a06+a72ODgYLsf/4cAAggggAACCCCAAAIIIIAAAggggAACCCBw7QQ+++wzjR07VhMmTNBbb73FmEPX7q3gyggggAACCCCAgFsLOOoHuqxz6xbSOAQQQAABBBBAAAEEEEAAAQQQQAABBC5TwEwonZSUZAdAef311y/zLByGAAIIIIAAAggggAACCCCAAAII+KZAZWWlIiIi9LOf/Uw/+tGPfBOBrH1ewHTGeOihh7Rhwwb169fP5z0AQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBA4W8BM4vvRRx8pLS1NCxculJnYpW/fvkpJSVFqaqr69+9/9kFsQQCBJhOoqalRdna25s+fr/T0dB08eFBmMu0pU6bYML+PLAgggAAC11bA/L1k+vwePXrUFWbdGcePH3fVndtMefLkybMaHhQUpLCwMLVr186WDevObc7yzNfatm3LAM1nibIBAQQQQAABBBBAAAEEEEAAAQQQQAABBJwCZtqOQ4cOKTc318auXbtcdbPtxIkTdteQkBD17NlTiYmJZ4X5HpIFAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQ8AYBcw/1wIEDMvdO8/LybDSsm/urzqVz586Ki4s7Z8TExCgwMNC5KyUCCNQLbNq0SRkZGTbWrFmjFi1a6I477tCECRPsnIudOnXCCQEEEEAAAQQQQAABnxOYM2eOHnnkEb322mv63ve+53P5kzACCCCAgG8IlJSUNOqrsnPnTte6GT/NLH5+furatavi4+NtmD4spm7KHj16KDg42DewyBIBBBBAAAEEEEAAAQQQQAABBBBAAAEEEHADATMXzfjx43XffffpT3/6kxwOhxu0iiYggAACCCCAAAIIuJOAo74TVp07NYi2IIAAAggggAACCCCAAAIIIIAAAggg0FQCDz30kD788EPl5OSIyViaSpXzIIAAAggggAACCCCAAAIIIICArwikp6crNTVV+/btU1RUlK+kTZ4IuASqq6vtpM/Dhw/XX/7yF9d2KggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCFRWVmrp0qWaP3++ndi3uLhYgwcPts9apKSkKCEhASQEELgGArW1tfrss8/s7+b777+vwsJC+/zHlClTNHXqVA0YMOAatIpLIoAAAt4lUFpaqiNHjtg4evSoLiZOnjx5FkJoaKjat29vIywszFW/0DbzWsuWLc86FxsQQAABBBBAAAEEEEAAAQQQQAABBBBAAIHmEDDfh+7cuVPbt29vFLm5uTL3kM0SERGhPn362EhKSnKV5vtNFgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMCbBMrKyrR7927l5eXZ0tSdsWfPHjn7k/j5+SkyMlLdunWzERsb26geExOj4OBgb6IhFwTOEqipqdHy5cttv/SMjAz7u2KeMRg/frwmTpyo22+/XSEhIWcdxwYEEEAAAQQQQAABBHxN4JVXXtGMGTP0zjvvaNq0ab6WPvkigAACCPi4gBnXZ9euXbbviumr0jDMeGp1dXUy37fGxcXZsdXMeIeJiYmuMN/DsiCAAAIIIIAAAggggAACCCCAAAIIIIAAAgg0vUBWVpYmTZqkb3/723rttdea/gKcEQEEEEAAAQQQQMCjBRz1N/TrPDoDGo8AAggggAACCCCAAAIIIIAAAggggMA5BJYtW6YxY8ZowYIF9obpOXZhEwIIIIAAAggggAACCCCAAAIIIIDABQTuu+8+HThwQJ988skF9uIlBLxX4I9//KMeffRR7dixww5I7r2ZkhkCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAwMUIVFRUaOnSpZo3b54yMzPtxNc33XSTpkyZopSUFJkJrlkQQMB9BMywSitXrtT8+fP1/vvvKz8/X/Hx8fZ31vzeDho0yH0aS0sQQACBayRQVVWlI0eOuKKoqMhVb7jdWTevV1ZWNmptYGCg2rdvrw4dOlx0mP3NcSwIIIAAAggggAACCCCAAAIIIIAAAggggIA3CJw+fVp79uzR9u3btW3bNm3ZssXG1q1bVVJSYlOMiIhQUlKS+vbtq379+ql///623rJlS28gIAcEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDgLAHTH8XcSz0z9u7da7eVlpbaYxwOh8LDwxUbG2v76p6rDAsLO+v8bEDA3QWKi4v1wQcfaOHChcrKytKJEyfUp08fTZw4URMmTNCQIUNkfv5ZEEAAAQQQQAABBBBAoLHArFmz9Nxzz9mxnm677bbGL/rg2s/X/FX/sWmRD2ZOyp4kEODnr88nv6CebSM9qdm0FQGPEigvL1dubq7tu2L6rzhj586d9nsnk0yrVq2UmJjoioSEBFs3ZevWrT0qXxqLAAIIIIAAAggggAACCCCAAAIIIIAAAgi4m0B6erqmTp2qxx57TL/73e/crXm0BwEEEEAAAQQQQOAaCjjqJ0qru4bX59IIIIAAAggggAACCCCAAAIIIIAAAgg0ucCpU6fsICmDBg3SvHnzmvz8nBABBBBAAAEEEEAAAQQQQAABBBDwdoGKigp16tRJzz//vH7wgx94e7rkh8BZApWVlYqPj9f48eP1hz/84azX2YAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg4BsCpo+KmdjX9E/JzMyUmXzl5ptv1pQpU5SamqqoqCjfgCBLBDxcwAyxtHr1as2fP9+GmbA+Li7O/i6bgTpvuOEGD8+Q5iOAAAL/K2CefTt8+LCKiopccaH1kpKSRnR+fn5q3769OnbsaMM8S+qsn1ma1zp06KA2bdo0OgcrCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAv8QyM/PV05OjrZs2WLLzZs3y0RZWZnMd7I9evRQ//79G0X37t3/cQJqCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgJcKHD16VHv37pXp72fKffv22XDWTf8Y59K6dWvFxMSoa9eurrJhPTo6Wi1atHDuTonANRMoKCjQwoULlZ6eruzsbJn+rcOHD9eECRNs8EzANXtruDACCCCAAAIIIICAhwl8//vf1zvvvKPPP/9cvXr18rDWN21zp2fP1vu7Vqqu/n8sCLizQFbyTA0LT3TnJtI2BLxW4NChQ9q+fbuNHTt2uOq7d+9WdXW1zduMmZiQkKDExERXmPVu3brJ39/fa21IDAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQaEqBd999Vw8++KB+8pOf6Nlnn23KU3MuBBBAAAEEEEAAAQ8WcNR3HuHpLg9+A2k6AggggAACCCCAAAIIIIAAAggggMDZAuam6Jw5c7R161Z16dLl7B3YggACCCCAAAIIIIAAAggggAACCCBwQYEFCxZoypQp2r9/vyIiIi64Ly8i4I0Cr776qp566inl5ubKDB7OggACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDgOwLl5eXKysrSvHnztGjRIp06dcpO7GuepUhNTaWviu/8KJCpFwusW7fO/o7Pnz9fu3btshMgmd/xqVOnasiQIV6cOakhgICnCdTW1urIkSM6fPiwK8yEb851Z72oqEgmTp482SjFoKAgderUyRWdO3d21c12s96xY0dXhIWFyc/Pr9E5WEEAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBoWgHz3W9eXp6++uqrRmG2malD2rZtq4EDBzaKXr16KSAgoGkbwtkQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABNxYw/Xv37dunvXv32jI/P99VmroJs49zMX1kzFjSUVFR54327ds7d6dEoMkEtmzZovT0dBtr165VaGioxo4dq4kTJ+quu+6S6bPFggACCCCAAAIIIIAAApcmcPr0aaWkpCgnJ0erVq2SL//33PTs2UrLW6na+ueMWRBwZ4Gs5JkaFp7ozk2kbQj4nEBNTY3tv7J9+3Y5Y8eOHbZuxi0yixmfKD4+XomJiUpISLClqZvo0KGDz5mRMAIIIIAAAggggAACCCCAAAIIIIAAAggg8HUCf/7zn/Wtb31Lzz//vJ566qmv253XEUAAAQQQQAABBHxAwFE/iCRPd/nAG02KCCCAAAIIIIAAAggggAACCCCAgK8ImIlUBg0apFdffVWPPPKIr6RNnggggAACCCCAAAIIIIAAAggggECTCtx///3av3+/srOzm/S8nAwBTxAoLy9Xjx49NG3aNL3yyiue0GTaiAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCBwhQIVFRXKysrS3LlztWjRIpn14cOHa+rUqXYisoiIiCu8AocjgIC7Cnz55ZeaN2+ejV27dik2NlZTpkyxv/833nijuzabdiGAgAcLVFVV6fDhwzp48KDMRGymNOumbsqG9SNHjqi2ttaVbUBAgDp27Kjw8HB17tzZFZ06dbJ1UzaMtm3buo6lggACCCCAAAIIIIAAAggggAACCCCAAAIIIODeAmVlZTJjKa5fv96GuY+1ZcsWVVZWKiQkRP369dP111+vwYMH64YbblCfPn1kvjdm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AAIIIIAAAggggAACzSNgBv6NiIjQjBkz9NOf/rR5LspVEHATgVdffVU/+clPlJeXpy5durhJq2gGAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggMD5BMygeXPnztU777yjtWvXKjw8XNOmTdN9992nYcOGne8wtiOAAAJXXcAM5/TFF1/Yz6h58+apsLDQTlRkPqPuuecerVq1Sg899JAaDvsUEBCg9u3ba+HChTKTlF/qcvjwYT355JN68803NW7cOP3Hf/yHevTocamncZv9c3Nz9eijj2rZsmXW6oUXXlCnTp3cpn005OIEzDOJ5mezoKDAFebfb+e6qZvfj7KyMtcJg4ODFRkZaSMqKuq89VatWrmOoYIAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJNJ1BTU6N169bp008/1fLly+1kdsePH1fbtm1100036dZbb9WIESM0ePBgmXtcLAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACniFg+nudOHFCx44dc4W5H2y2natsuK24uFjm+DMXc9+4TZs29p6yszT3l004103ZunVrhYaGnjfM64GBgWee/orXTW6LFy9Wenq6PvjgA9uXzdzvnjRpko2kpKQrvgYnQAABBBBAAAEEEEAAgeYXmDhxog4dOqQVK1bI39+/+RvQzFecnj1baXkrVVs/pg0LAu4skJU8U8PCE925ibQNAQQ8VKC8vFw5OTnavHlzozBjWJnFfB/Zp08f9evXz4731rdvX1t27NjRQzOm2QgggAACCCCAAAIIIIAAAggggAACCCDgqQLm2UszNk9FRYUdv6dz586emgrtRgABBBBAAAEEELgEAUf9RGQ83XUJYOyKAAIIIIAAAggggAACCCCAAAIIIOA+Ajt37rQdMn71q19pxowZ7tMwWoIAAggggAACCCCAAAIIIIAAAgh4oICZAPa2227T1q1b1atXLw/MgCYjcHkClZWV6tGjhx3wefbs2Zd3Eo5CAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEELjqAmaS3/nz5+uvf/2rsrOz7WQfKSkpuu+++zRy5EifmAzsqiNzAQQQaFIBM6G6maxw7ty59vPLTF6YkJCg3NzcsyZbd05o+Pvf/14PP/zwRbXDnH/OnDl6+umn7cTr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IIAAAggggAAC+Ubg4sWL+uabbzRx4kR9//33KlCggHr06KGRI0eqWbNm+caBgSKAAALZJeDu7p5dTeWrdv6sm1kg99ChQ4qKitL+/fttuvw4JiZGqam/Lo4XEBCgiIgIhYeHq1atWurYsaN9XLZsWZtnFtVlQwABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQuDGBkiVLqm/fvjaZ72nWrFmjuXPnas6cORo1apR8fX3VqlUrtW/fXnfddZdCQkJurENqI4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAghku0BsbKy9B/2rr77S0qVL5eHhodatW8usSdipUycVK1Ys2/ukQQQQQAABBPKTgJubmwoXLmxT+fLlszT0tLQ0nThxQseOHdPRo0cz3W/fvl3Lli2z50+ePOm419504O3traCgIJUqVcqmzI4z8goWLJilmCiEAAII3KjAk08+ad9nvP3223rnnXdutDmXrv9A9baqXjRUPae8rZj4k3YsX+z5QcOaDVQp/yIuPbasBN9q1gtKTft1jpislL+RMqXTPd9rOkCDl43Q+eSLN9LU79a9vUwtfbxtni1zW5mamh+96YryD9dopw5zX1XUuaM2P01pOn7x3BVlQgKKafupaO05E6s6xctdcc48WHt0twp6+euDZn/VkBWjrjpPBgIIIIDAjQuY92633XabTRmtxcfH66efftKmTZu0ceNG+xnq8OHDdenSJfn5+al27dqqW7eu6tWrZ/c1atSQj49PRnX2CCCAAAIIIIAAAggggAACCCCAAAIIIOCCAmYNn7i4ODtnzo8//qgqVaq44CgIGQEEEEAAAQQQQOByAc/LH3CMAAIIIIAAAggggAACCCCAAAIIIICAMwqYG8v/+c9/6oknnlBWb0p3xnEQEwIIIIAAAggggAACCCCAAAIIIOBsAosXL5aZPMIsqsqGQH4ReO+991SuXDl16dIlvwyZcSKAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAI5ImAWFFyxYoUmTpyoqVOn2msQ2rRpoy+++EIdO3aUr69vjvRLowgggAACCPwZgSNHjmjfvn3av3+/3UdFRdlj8/jgwYNKTk62zZpFlyIiImyqWbOmOnTo4Hhs8kuUKPFnuqcOAggggAACCCCAAAIIIIAAAggggAAC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+Ph4jRw5Ug899JBdKBgXBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIHrC/zwww+aMGGCpk2bpuTkZLvo75w5c9SmTRsWkLg+HyUQQAABpxKoV6+eHn74YY0ePTrLcVWqVMne4/f5559nuU5mBf9M35m1c708Ly8v3Xffffa163plOY8AAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIJD3BGrWrCmTnn/+ecXExOjbb7/VzJkzNXjwYHvdQ7Nmzey1D127dlVERETeA2BECCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQA4K7N692953Pn36dG3atEnFixdX586d9dprr+n222+XucePDQEEEEAAAQQQyIpA4cKFZVK1atV+t/ipU6fstWBmfZ3Dhw/bZI5N2rx5s3184sQJRxvm75Hg4GCFhISoTJkydm+OTQoLC1NoaKhKlSold3d3Rx0OEEAg7wiYddDfffddDRs2TK+++mreGdgfGElCyiXd9u3z+viWB9U6pI5G3fqwelVooYeXj1JM/ElHSztOH3YcZxxsORGVcWj3P8b9onE7Fur1xn116MIJjdg2x+anpqbqsdqdNH3vjxq0bITN23/2iIbWbC+39H/nky/qhXWfq1/l2xUSUCy9zHBdTElSAU9f7e8zRn+v01Xt575q817fOE0H+oxTy+Aa2nBsj23rr1XbaNGhn+xx9Pnj2noySneG1tP49Fgm7/lBt5WpqR7lmys2/pRazHxGFQODtftMjLaeOqA7w+qrSVAlW9f858C5o5q+b6XS0v/5eXjr81aP63D6WJ5fM8mWCfD00UfNB6npN39XfHKitqT3dXuZ2hqYHsOXe5Zr/X9jcjT434MaRcK09ujuK7K93D00vuUj+nr/as06sM6eG/7zbNUuVlYfNhukTcf3aWe6+4vpNh0jGulSSrLuW/KBLffWpq+0qsvbeqNJP82OXp8e40mb3m82UB9smaWVR3bohfo99N5PM7Ui/XnJ2BYf3iKTzFajaJjtv2W6zyM1O2jYlm8zimV5fyThtE4nnled4mU19+CGLNejIAIIIIBA7ghUqFBBJvXq1csGYF6jd+7cqfXr19u0bt06ffLJJ0pISLDrNtapU0cNGzZUgwYNbDJzzPHeKHeeO3pFAAEEEEAAAQQQQAABBBBA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CoF0gVmzZqlTp052MsBKlSphggACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCDidwNmzZ/X5559r1KhR+umnn1SrVi0NGjRIffv2ZXEBp3u2CAgBBBDIfYHExES98847ev3112WuARsxYoRatmyZ+4HlcgRLlizRQw89pAMHDuj555/XU089JW9v71yOKve6v3TpksyiqdHR0ddMFy5ccARYokQJhYWFXTMFBQXJzc3NUT7jICIiwppnPDb7jHKhoaH65z//af+mMQu3ZmzBE+9TfHJixkP2COS4gJe7h+6t1FLDmg7M8b7yawfLli2z72fMPeI+Pj72+uTBgwfb9zb51cQZx22mmPz+++/tc2Wurw0MDFT//v3t+0+zoDZb9grMjFqj/ouHZW+jtOZ0At4ennq2bnc9VquT08VGQAjkZ4HqU4bo8IUT+ZkgX4zdz8Nbsf0/zRdjZZAIIIAAAggggAACCGQmcP78ec2YMUMTJ07U0qVLVbhwYfXq1ct+L9OkSZPMqpCHAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJOJHD48GFNnz5dU6ZM0erVq+01/l26dFGPHj3UunVreXp6OlG0hIIAAggggAACCCCAgHMJmLkSdu3aZdeh2bFjh3bu3GmPTV5CQoIN1syhULVqVVWvXl3VqlVzpFKlSjnXYIjGKQRiY2Ptz8g999xj53FxiqB+J4iBy4brq30rlZp+/zgbAs4sMPeulxQZxFwGzvwcERsCCCCAwLUF9uzZo+XLl9u0YsUK7d69W+7u7nZOpRYtWqh58+Y2BQcHX7sRziCAAAIIIIAAAggggAACCCCAAAIIIHCTBWbPnq1OnTrplVde0XPPPXeTe6c7BBBAAAEEEEAAgQwBt/TFQbi6K0ODPQIIIIAAAggggAACCCCAAAIIIIDATRO48847dfr0aTuZzU3rlI4QQAABBBBAAAEEEEAAAQQQQACBfCqwf/9+lStXzi4meuutt+ZTBYadHwTatWunS5cuaeHChflhuIwRAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQRcSGDz5s0aOXKkvvjiC6Wmpqpnz54aPHiwmjRp4kKjIFQEEEAAgdwSMNeADR06VGYizz59+ujf//63goKCciucXOs3Li5OTzzxhH097dChgz78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fNoXuprasChmgrvPHuJCmD+mn1+P+JRELLq2S24/jY/BnzcPyO01fofTHprldkl9Y3zo2Aoz6gxQL/WV98xYee9E3GiX9i8db6priPtRj1+KZ0TuCqi+aywmAxbPhoeEhMiJwsSktcbGxrlbWA5ymzBhAqZNm4YZM2agS5cu0NLSUh+Vqvw/SUlJwdatW9VxaTey25827etu37lzR06a7OHhga+++gq2trbqrF6l/D59+sDKygozZ86Ej48PnJyc+L1vtWT+2xAT44nfRbFu0KABlixZgrCwMGzevBldu3aFrl7683D+a8HLNVL+O2UZHsY+VfqTaBgp5+m04U36GJFPOaOSMrt/gy6+UR9zMPiK0p9FIUHp10S4/TQEJfSLw9qwhHyt+nEn8oHcjEqMU0XJNfuYdBx8QQEK5LFAVqfgzM6/ooriOr5GqYry/iJtlW9HhsqXOb3O/+XKVqy9fVTJRxPDjy7Eb9f2oIRecYw4skiem4OiM78GvxFxHyEx4Xig9BMZhQ52tbH93jmI+5uMgqmeoYy+n0UZGR3HOApQgAIUoAAFKEABClAgfwno6uqiV69eOHr0KC5duiT/Zib+Xir+5vnhhx/KuPxVY9aGAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCgMAqcPn1azo8nPqdq0aKF/G7+lClTEBQUhD179kA8F2FiYlIYm842UYACFKAABShAAQpQgAIUeGUBIyMjeHp6YujQoVi4cCGOHz8OMfe4n58f1q5di+7duyM6Ohq//PKLvMcqXbo0ypUrh/bt22PixInYtGkT7t2798rlFoYD3NzccPLkSfmcuxjD7MyZM9k26+HDhxg5ciTs7e1x8OBBLF++HFeuXJHPz/fu3RvCVxXE88t//vknBg0apIrimgIUoAAFKEABClCAArkuULlyZfncjxifNzQ0VF6fTp48WY6nI675XVxcYGFhgR49esix+8S9AgMFKEABClCAAhSgAAUoQAEKUIACFKBA4RKYO3cuxLja4nNg8ZkmAwUoQAEKUIACFKBA7gho5k42zIUCFKAABShAAQpQgAIUoAAFKEABClCAAukF5syZIx9sHD16dPodfEUBClCAAhSgAAUoQAEKUIACFKAABSiQJwKHDx9Gw4YN86QsFkKBvBZYunQpvL29YWlpmddFszwKUIACFKAA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ea1cuVLLli2zt9DQUKVKlUqVK1dW1apVVa1aNWXIkAEsBBBAAAEEEEAAAQQQQAABBBBAAIEnFDDfXzZs2NCu79KuXbsn7IXTEEAAAQQQQAAB9xDwdI80yRIBBBBAAAEEEEAAAQQQQAABBBBAIDYEzETGDz74QC+//LKee+652BiCPhFAAAEEEEAAAQQQQAABBBBAAAEEoigQGRmp9evXq2zZslE8g2YIOIfA5MmTVbFiRWXMmNE5AiZKBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBC4T2DTpk1q3LixcubMqUWLFmno0KE6evSohg0bpvTp09/Xnh0IIIAAAggggICjC5jijWbB03379qlJkyYKDg5WqVKltHnzZkcPnfgQQACBWBNInTq1+vXrZ/82muJc5v1ehw4d5O/vr+7du2vXrl2xNra7dGyuGf/hhx/sc4+5tnb48OGqXbu2tV2zZo1at26thAkTugsHeSKAAAIIIIAAAggggAACCCCAAAIIIICAmwqkSJFCffv21cGDBzV9+nR7HVKZMmVUsmRJzZ07VxEREW4qQ9oIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQMwJXLt2TTNnzlStWrXs/IA333xTefLk0YoVK+z3Mx9++KEKFiwYcwPSEwIIIIAAAggggAACCCCAAAJxLDBx4kQVKlRI586d09q1a/Xbb79p+/btunTpkn1s3gunSpXKvj9+6aWXbP32559/Xo0aNbI13X/88UeFhYVp586d/4j8Qdcx+vr6ytPT8x/t/v7ArF1iao+xIYAAAggggAACCCCAwOMFUqZMadc5/vzzz+33VmZtK/P6/eLFi+rataute5o3b1716tVLy5cv140bNx7fKS0QQAABBBBAAAEEEEAAAQQQQAABBO4J1K9fX2+88YY6d+4sU3OKDQEEEEAAAQQQQODhAg+/Kuzh53AEAQQQQAABBBBAAAEEEEAAAQQQQAABKzBr1ix7QbQpvMCGAAIIIIAAAggggAACCCCAAAIIIBC/Anv37tX58+dVunTp+A2E0RGIQYHTp09ryZIlatOmTQz2SlcIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIBAXApGRkfr222/14osvqmTJkjp8+LCmT5+u/fv3q1u3bkqSJElchMEYCCCAAAIIIIBA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AggggAACCCCAAAIIIIAAAggggEAUBLZs2aLGjRsrZ86cMsWVhg4dqqNHj9riIM8991wUeqAJAggggAACCCDg2gKmgMySJUv07bffavPmzcqVK5eGDBmiGzduuHbiZIcAAghEUcAUx61SpYrmzp1r30/26dNHCxcuVJ48efTiiy9q+vTpunnzZhR7c85mZ86c0YgRI+xzhClUfOTIEX322Wc6fvy4Ro8erXz58jlnYkSNAAIIIIAAAggggAACCCCAAAIIIIAAAgg4iECiRInUqVMn7du3T3PmzLGfvwYEBKhMmTL65ptvFBER4SCREgYCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggEDsCt2/f1nfffacmTZrIzAE33534+flpwYIFOnHihMaNGyfz/QkbAggggAACCCCAAAIIIIAAAq4mcOXKFXXo0EF16tRR1apVtWPHDpk53TG1mbnioaGh97oz8+d9fX3l7e19b5+5kzx5chUoUEANGzZU79699emnnypV78ry8PH6RzseIIAAAo4kEBkZaa+3Ll68uKpVq6YECRLop59+0po1a1SjRg1HCjVKsZjPQM1aUL/88ouyZ8+upk2b2vVNTJ288PDwKPVBIwQQQAAB1xNInz692rdvr3nz5uns2bNavXq1GjRoYH+a57tUqVKpZs2adi2o/fv3ux4AGSGAAAIIIIAAAggggAACCCCAAAJRFDCfq3fr1s1+/7pnz54onkUzBBBAAAEEEEDAPQQ83SNNskQAAQQQQAABBBBAAAEEEEAAAQQQiEmBoUOHqnLlyipRokRMdktfCCCAAAIIIIAAAggggAACCCCAAAJPKLBp0yZb1LJUqVJP2AOnIeBYAnfu3NGMGTPUqlUrxwqMaBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAQMuXL1elSpVkioEcOnRI06dPl1kE3Sz0lSRJEoQQQAABBBBAAAEE/iVgClHu2rVLb775psz8zDx58tjiav9qxkMEEEDArQXSpk2r/v3768CBA1q2bJlSp06tNm3ayN/fX7169ZKrLSptCo01b95cpgDZ4MGDbcHi7du3a/369TbvRIkSufX/B5JHAAEEEEAAAQQQQAABBBBAAAEEEEAAAQRiWsDT01MNGzbUhg0b9N///ld+fn5q0KCB/e5u0qRJun37dkwPSX8I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eenOnTuPzcbb29u+Nt20aZPSp0//2PY0QAABBBBAAAEEEEAAAecR+M9//qN169YpIiLisUF7eHioefPmmjx5snx8fB7bngYIIIAAAggggAACCCCAAAIIREfg6tWr+vzzz/XRRx/p5MmTatGihfr166c8efJEpxvaIoAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIxLrBlyxZNnTpVM2fO1NmzZ1W+fHkFBgaqUaNGeuaZZ2J8PDpEAAEEEEAAAQQQQAABBBBAwNEFrl+/rm7dutnr/l599VV77V+iRIkcPWylmxKk63duOXycBIhATAr4evmoU94aGlSseUx2S1/RFAgLC1NISIjGjBkjM1+3S5cu6tGjh5599tlo9uTazS9fvmzXgzJrc168eFHt27dX3759lSlTJtdO3MGy+/PKGRWY09XBoiIcBO4XKJo6u36o8979B9jjlgJ79uzR4sWL7W3NmjV2fb1SpUqpVq1ads29QoUKuaULSSOAAAIIIIAAAggggAACCCCAgGsJmHVDzecdu3fv1rZt2+Tn5+daCZINAggggAACCCAQDQHPaLSlKQIIIIAAAggggAACCCCAAAIIIICAGwscP37cLhrUu3dvO6nHjSlIHQEEEEAAAQQQQAABBBBAAAEEEHAogU2bNsnX11dMBHeoXwvBPIWAWcDcXPCfKlWqp+iFUxFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAIDoCZkGuNm3aKGvWrJozZ44GDRqko0ePasiQIUqTJk10uqItAggggAACCCCAQDQFUqZMqU8++US//vqrzP2yZcuqZcuWCg0NjWZPzte8evXqypMnjyIjIx8bvJeXl5ImTarVq1crffr0j21PAwQQQAABBBBAAAEEEHAuAVOc2hSRiMpm1r+ZNm2afHx8otKcNggggAACCCCAAAIIIIAAAghESyBJkiTq3r27Dhw4oAkTJsis65EvXz7Vq1dPGzZsiFZfNEYAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEnlbAzDEaNmyYnYNTvHhxLV++XD179tThw4e1atUqO0f8mWeeedphOB8BBBBAAAEEEEAAAQQQQAABpxPYtWuXzHvlefPmacGCBfr000+VKFEip8uDgBFAAIG4EDCfM5q5vJkzZ9bEiRP1+uuv688//9TgwYP17LPPxkUITjWGWeeoX79+9nPYESNG6Ntvv1X27Nn18ssva9++fU6VC8EigAACCMStwAsvvCCzJob5Hu/MmTN2nedcuXJp9OjRKly4sDJkyKDg4GAtXLhQV69ejdvgGA0BBBBAAAEEEEAAAQQQQAABBBCIIQFPT0+7JqjprlWrVlGqMxBDQ9MNAggggAACCCDgcAKeDhcRASGAAAIIIIAAAggggAACCCCAAAIIOKTAqFGj7CSeFi1aOGR8BIUAAggggAACCCCAAAIIIIAAAgi4q4ApVFmwYEElSJDAXQnI24UEzCJDq1evVuvWrV0oK1JBAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwHEFNmzYoLp16ypfvnwy3z9/9tlnOnTokF2onELDjvt7IzIEEEAAAQQQcE2BvHnzasWKFbaopXmdZorFvPfee7px44ZrJnw3Kw8PD3344YeKiIh4ZI5mIdmECRPaYjo5cuR4ZFsOIoAAAggggAACCCCAgHMK1K5dW6lSpXps8CEhIfZ9hHk/wYYAAggggAACCCCAAAIIIIBAbAr4+PioTZs22rVrl+bPn6+TJ0+qdOnSqly5sl0XITbHpm8EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBwb4ErV65o6tSp9nuJ559/XiNGjFDFihW1ceNG7dmzR2+88YYyZcrk3khkjwACCCCAAAIIIIAAAggg4NYCEydOVPHixZU8eXL9+uuvqlevnlt7kDwCCCDwMIH9+/erQ4cOypYtm+bNm6f3339fR44cUf/+/ZUsWbKHncb+/wmYNY86d+4s42jW6ly7dq1y586tpk2bavv27TghgAACCCDwSAHzXNuwYUN9+eWXOn78uLZs2aLg4GD7HNKgQQP5+fmpWrVqGjNmjA4fPvzIvjiIAAIIIIAAAggggAACCCCAAAIIOJqA+Wzj66+/trUDBg8e7GjhEQ8CCCCAAAIIIBBnAp5xNhIDIYAAAggggAACCCCAAAIIIIAAAgg4rYBZTGjChAnq2rWrfH19nTYPAkcAAQQQQAABBBBAAAEEEEAAAQRcUWDTpk0qUaKEK6ZGTm4oMGPGDKVIkUK1atVyw+xJGQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIG4E1ixYoUtMly6dGmdPn1aCxYs0K5du9S2bVv5+PjEXSCMhAACCCCAAAIIIHCfgClquXv3br311lv64IMP9MILL2ju3Ln3tXOVHTVr1lTJkiXl5eX1wJQ8PDzk7e2t77//XoUKFXpgG3YigAACCCCAAAIIIICA8wqEhYVp2LBhypUrlyIjI+3r/39n4+npad8zzJo1S927d//3YR4jgAACCCCAAAIIIIAAAgggEKsC5vsq8x3ehg0btHLlSkVERKhChQoqU6aMli5dGqtj0zkCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgi4j4D5DsJ8FxEYGKjnnntOHTp0UNKkSe28ouPHj2vMmDGsRe4+/x3IFAEEEEAAAQQQQAABBBBA4CECly5dUrNmzfTKK6+oR48e+umnn5QpU6aHtGY3Aggg4L4CO3fuVMuWLe3aRatXr9bYsWN14MABO083UaJE7gvzhJmbNTrNWp2///67Zs6cqb1799q1kOrUqaP169c/Ya+chgACCCDgTgJmflLRokX19ttva+PGjTpx4oQmTJig5MmTa8CAAcqSJYvy58+v/v37a926dXb+kjv5kCsCCCCAAAIIIIAAAggggAACCDinQIkSJTRy5EgNGjRIphYWGwIIIIAAAggg4I4Cnu6YNDkjgAACCCCAAAIIIIAAAggggAACCERP4PPPP9ft27ftxMjonUlrBBBAAAEEEEAAAQQQQAABBBBAAIHYFLh165a2b9/Oos+xiUzfcSowffp0NW7cWL6+vnE6LoMhgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg4A4CkZGRWrBggYoXL66qVavKy8tLq1atsgUr6tatK7MQORsCCCCAAAIIIICAYwgkSJBAr7/+uvbt26dy5cqpSZMmKl++vL1m0DEijLkoFi1apMuXL+vOnTsP7NTT09O+ji1btuwDj7MTAQQQQAABBBBAAAEEnFNg06ZNCgwMVMaMGTV8+HA1aNBA5v2B+Sz775v5LDthwoS2mETTpk3/foj7CCCAAAIIIIAAAggggAACCMS5QKVKlew1V2vXrlWKFClUs2ZNFS1aVPPnz7/vPW2cB8eACCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg4JQCu3fvtvOInn/+eVWpUkV79+6119UdP37czqmpX78+6zU75W+WoBFAAAEEEEAAAQQQQAABBGJaYNu2bSpcuLBWr16tZcuW6f3335e3t3dMD0N/CCCAgFMLmPm79erVU4ECBfTbb79p6tSp2rNnj9q3b8/njDHwmzVrIZm1oH799Vd99913Onv2rAICAlSxYkX98MMPMTACXSCAAAIIuItAmjRp7Jobc+bM0ZkzZ+yaGmbe0tdff60yZcoobdq0CgoK0ty5c3Xp0iV3YSFPBBBAAAEEEEAAAQQQQAABBBBwQoHOnTvLrBXasmVLhYaGOmEGhIwAAggggAACCDydgOfTnc7ZCCCAAAIIIIAAAggggAACCCCAAAKuLmAKdoeEhKhdu3ZKmTKlq6dLfggggAACCCCAAAIIIIAAAggggIBTCWzfvl03b95UiRIlnCpugkXgQQI7d+60Cw6Zi/vZEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg5gTCw8P11VdfKW/evGrYsKEyZswoUxhkxYoVqlChQswNRE8IIIAAAggggAACMS6QLl06TZkyRevXr9eNGzdUpEgRBQcH6/Tp0zE+Vlx3aIqomUI3derUUZ48eVSuXLn7Cnt6eHjY17I1a9aM6/AYDwEEEEAAAQQQQAABBGJBwLyvmTx5sooXL66SJUtqx44dGjNmjI4dO6aRI0faAsumqLW3t7cd3fw0692sW7eOz7Nj4fdBlwgggAACCCCAAAIIIIAAAk8uEBAQoMWLF2vbtm3KkiWLGjVqpHz58mnatGky67iyIYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKPEggLC9Po0aNVrFgxOwd85syZCgwM1J49e7Rx40Z17txZfn5+j+qCYwgggAACCCCAAAIIIIAAAgi4lcD48ePt/DNzzZ6p2VW5cmW3yp9kEUAAgccJrF69WlWqVLHzd0+cOKEFCxbYenAtWrSQl5fX407n+BMI1K5d286BXrVqlTw9Pe1zU6lSpfTtt98qMjLyCXrkFAQQQAABdxXw8fGxzyMhISHav3+/du/erb59++rQoUNq1qyZnn32Wfs8P2rUKB08eNBdmcgbAQQQQAABBBBAAAEEEEAAAQQcWGDixIn2M4wmTZro9u3bDhwpoSGAAAIIIIAAAjEv4BnzXdIjAggggAACCCCAAAIIIIAAAggggIArCXz99dcKDQ1Vjx49XCktckEAAQQQQAABBBBAAAEEEEAAAQRcQmDTpk1Knjy5cuXK5RL5kIR7C0yfPl2ZMmVS2bJl3RuC7BFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBCIIYEbN27o008/VY4cOfTyyy/bAsQ7d+7U/PnzVbx48RgahW4QQAABBBBAAAEE4kKgZMmSWr9+vaZMmaIlS5bY13gffvihbt26FRfDx+gYx44dU9u2bVW0aFFdvXpVa9eulZnLaorehIeH/2OssWPHqnnz5v/YxwMEEEAAAQQQQAABBBBwPoEjR47o9ddfV4YMGRQcHGzf06xZs0a//PKL2rdvr8SJE99LqmvXrva9gSlmbeaZbNmyRQULFrx3nDsIIIAAAggggAACCCCAAAIIOJJA4cKFNXfuXO3atUtFihRRmzZt7BogpjCiM36X50i2xIIAAggggAACCCCAAAIIIIAAAggggAACCCCAgKsJ3Lx5U/PmzVPdunWVPn16vfHGG8qfP79WrVqlw4cPa8iQIaw17mq/dPJBAAEEEEAAAQQQQAABBBB4agGzLkXLli3VuXNnvfbaa1q+fLnSpk371P3SAQIIIOBKAgMGDFCFChXs/NwVK1Zo48aN9nNIDw8PV0rTYXMx9itXrrTu5jmqXr16CgoKcth4CQwBBBBAwPEFcufOrX79+um///2vTp8+rUmTJsnPz08DBw5UtmzZlCdPHvv+yKzbcefOHcdPiAgRQAABBBBAAAEEEEAAAQQQQMDlBZIkSWKvk96xY4f69u3r8vmSIAIIIIAAAggg8HcBj8i72993cB8BBBBAAAEEEEAAAQQQQAABBBBAAIG/CxQvXlxZsmTRnDlz/r6b+wgggAACCCCAAAIIIIAAAggggAACDiBgFgg5duyYXTjEAcIhBASeWMBcxmY+h2zWrJmGDRv2xP1wIgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIISJcuXdK4ceP08ccf6+LFi3r55Zft4lqZM2eGBwEEEEAAAQQQQMAFBK5du6bhw4frww8/lL+/v0aMGGGLjjl6aqaop4n7o48+Upo0aTR06FB7zdjf427SpIldIDYiIkKDBw+WKW7HhgACCCCAAAIIIIAAAs4pYOaKmGLJY8aM0aJFi5QuXTp17NhRwcHBMsWTH7WZYpeJEyfWsmXLbBHMR7XlGAIIIIAAAggggAACCCCAAAKOJHDw4EG7ZsKUKVPsd2KmKKJ5L5wwYUJHCpNYEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAIA4F1q9fr6lTp2r27Nl27nflypUVGBio+vXr22vl4jAUhkIAAQQQQAABBBBAAAEEEEDAqQR27dqlxo0bKywsTNOnT1fVqlWdKv5HBZtuSpCu37n1qCYcQ8DlBHy9fNQpbw0NKtbc5XKL74SaNm2qGzduaOHChfEdCuPfFejSpYu2bdumdevW4fGUAn9eOaMCc7o+ZS+cjkDsCxRNnV0/1Hkv9gdiBLcXCA8P188//6zvvvvOruPxxx9/KFWqVKpZs6Zq166t6tWrK3ny5G7vBAACCCCAAAIIIIAAAggggAACCMSfwKxZs9S8eXN73bSpOcCGAAIIIIAAAgi4g4CnOyRJjggggAACCCCAAAIIIIAAAggggAACTybw008/acuWLerTp8+TdcBZCCCAAAIIIIAAAggggAACCCCAAAKxKrBp0yaVKFEiVsegcwTiQmDNmjU6cuSIWrZsGRfDMQYCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACLilw5swZvfXWW3r++ec1ZMgQBQUF6fDhwxo7dqwyZ87skjmTFAIIIIAAAggg4I4CiRMn1jvvvKO9e/faawjr16+vSpUq6bfffnNYjhkzZihXrlz65JNPNHDgQO3Zs0fNmjW7L17zOtbT01M9e/bUgAED7jvODgQQQAABBBBAAAEEEHB8gcuXL2vMmDHKnTu3qlatqgsXLsgUgTCfV5vPsNOmTfvYJMycqfXr18vPz++xbWmAAAIIIIAAAggggAACCCCAgCMJZM2aVRMmTNCBAwfUoEEDvf7668qSJYs+/vhjXb9+3ZFCJRYEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBCIRQFzzdx7772nnDlzKiAgQGb95f79++vo0aNatmyZXYfZzBFiQwABBBBAAAEEEEAAAQQQQACBBwt89dVXdk2NlClT6pdffrFz1R7ckr0IIIAAAkbA19cXCAcR4HfhIL8IwkAAAQRcUMDb21sVKlTQyJEjtW/fPrue3xtvvGG/g2zVqpWeffZZuybhqFGjdOjQIRcUICUEEEAAAQQQQAABBBBAAAEEEHB0AVN7oEuXLmrfvr2to+Do8RIfAggggAACCCAQEwKeMdEJfSCAAAIIIIAAAggggAACCCCAAAIIuKbAiBEj9J///MdOlnTNDMkKAQQQQAABBBBAAAEEEEAAAQQQcF6Bixcv2oueS5Qo4bxJEDkC/xOYPn268ufPb2+gIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBA9ASOHz+unj176vnnn9f48ePVu3dv/fnnnxo+fLjSpk0bvc5ojQACCCCAAAIIIOA0AhkzZtSMGTO0du1aXb58WUWKFNErr7yi06dPO0wO27ZtU9myZdW6dWvVqFHDFqvp16+fEiRI8MAYc+TIYV/LmsI2bAgggAACCCCAAAIIIOBcAnv27LGFHtKnT6/XXntN5cqV02+//aaffvpJjRs3lilkGdXtmWeekY+PT1Sb0w4BBBBAAAEEEEAAAQQQQAABhxPIkCGDRo0apUOHDqlFixZ68803lSVLFn300Ue6du2aw8VLQAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIPD0ApcuXdIXX3xhr5/LmjWrxowZo5o1a2rr1q3asWOH+vbtK39//6cfiB4QQAABBBBAAAEEEEAAAQQQcGGBW7duqVOnTgoMDNSrr75q56eZa/LYEEAAAQQQQAABBBBAAAEE/imQK1cuuw716tWr7fqDU6dOtWtRDxo0SOb7SlMf1sxp2rRpkyIjI/95Mo8QQAABBBBAAAEEEEAAAQQQQACBWBIwa6vkzp1bTZo00Y0bN2JpFLpFAAEEEEAAAQQcRyDqK687TsxEggACCCCAAAIIIIAAAggggAACCCAQBwKmqN/ixYv1zTffxMFoDIEAAggggAACCCCAAAIIIIAAAgggEF2BLVu22EnYJUqUiO6ptEfAoQRu376tr7/+Wv369XOouAgGAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAUcXOHLkiIYPH64vv/xSfn5+GjJkiIKDg5U4cWJHD534EEAAAQQQQAABBGJQICAgQBs3btRXX32lN954QzNnztSAAQPUvXt3JUiQIAZHinpXYWFhNoYvvvhCpUuX1ubNm1WkSJEodZAuXbootaMRAggggAACCCCAAAIIxL9ARESEXZ9m9OjRWrFihbJly6Z33nlHbdu2VYoUKeI/QCJAAAEEEEAAAQQQQAABBBBAIJ4F0qZNK1MY8bXXXtOIESM0cOBAe81Xnz591LlzZyVJkiSeI2R4BBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQeBoBcx2duX5u8uTJWrhwoczjl156Sd9++62qV68ub2/vp+mecxFAAAEEEEAAAQQQQAABBBBwK4HQ0FA1atRIu3fv1rx589SgQQO3yv9hydZ5vrh8vXzuHT56JUx7zoeqSsbC9/aZOxdvXdXK0O12X7rEKRXwXO57x7eG7dfhy6fvPX7Qnc55a+rGndv6Ys+KBx1+4L5EXr4q559PJdLk0LtbZz+wTUzuzJncX9Xu5r3j3J9afXzHva59PL3ULGka3TkAAEAASURBVPt/lCdlJh27elbrT+3VhZtXlCpBUm0O++Neu/i84+XhqaKps2nT6diLp7x/fv1+/qhOXb+gMnd//0fu/s5D73qY7RnvhGqUrYwyJ02jg5dO6usDa3X9zq37SIqmzq6yd8+9Exmhbw9v1J9XztxrU9Avs87euHyvz3sHuINALAn8/vvvdh5zwYIFVaVKlSiPsmvXLi1ZskRmXaYyZcpE+Twauo5A/lTPq2amYkrik1C/njmon07sVKX0BTXnwBrXSfIBmZi/8X0K1tf7277W8WvnHtAidnYV8suqvRdCH/i8EhMjPur5zfSfIYmfSqbNdW8o77vPuVdu39DiP7fc2/eo50Ge3+4xccfJBVKmTKnmzZvbm6kP+9///td+Zzl9+nS7ZrVZ369OnTr2u8xKlSopYcKETp4x4SOAAAIIIIAAAggggAACCCCAgKMK+Pr6atasWbYuQY8ePTR+/HhHDZW4EEAAAQQQQACBGBHwjJFe6AQBBBBAAAEEEEAAAQQQQAABBBBAwOUETIGCHDly2It4XS45EkIAAQQQQAABBBBAAAEEEEAAAQRcQGDz5s1Knz69/P39XSAbUnBnge+//17nz5+3iw24swO5I4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIBBVgf3796tdu3b2mn9T2OLjjz/WwYMHZRbNSpw4cVS7oR0CCCCAAAIIIICACwl4eHgoMDBQ+/btU8+ePfXOO+8od+7cmjt3bpxmeefOHY0aNerea9WvvvpKa9assYu8xmkgDIYAAggggAACCCCAAAKxKmDmgYwYMULZs2dX3bp15enpqUWLFt17T5IiRYpYHZ/OEUAAAQQQQAABBBBAAAEEEHA2gTRp0uiDDz7Q4cOH9fLLL+u9995T5syZNWzYMF25csXZ0iFeBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQTcXmDPnj3q37+/MmbMqOrVq+vo0aMKCQnRyZMnNWfOHNWuXVve3t5u7wQAAggggAACCCCAAAIIIIAAAlEVWL16tYoWLapLly5p06ZNatCgQVRPdfl2p65fUP/CjfRF+a6qmL6AfjlzUJduX9e2sAN6q2jTe/t/PrH7nsWJa+d18dZVeyxd4pQKvXL23rH/x959wNd09nEA/2VHBlkSIyIiCLElEsTem5i1qT2qtbUogpptraq9qjXeorVXS82Q2HtLIkSGIHu+53n03iYEQRI3ye/4XOec5zzze6/7v+495zxv2uhWsg4+c6z5psNppte3rYDZ1Xqh03uWS7OydyTam1qjt1MDeFXthsLGFurceXT08VfL6Whj7469fr4Ii3mBb106w7f9D6hqXUKd71Nu5NXLgy/KtcTVMP9M64aBjh42NhgFfe2X30mtq/clTJV2xeKYt6D0GFa2OQY7N8MCj/440XY2rPPkS9Wf6YrtgDKNseXOcez3P4eprl2xpu5wdZ7LYX4YUaE1qts4qdO4kbECYWFhGDBgALZs2YLo6OiMrTyb1Xbnzh0sXboUo0ePRkBAQLp7L+6/9N1332HMmDHye9t0F2TGTBeYM2eOPKfez88vU9vqrMSk3c2/xdPYCOzx80GV/MVx2nMu5lXvk6ntakLlFSyLQcTzMhZFsqw7TYpUhqWhKaIT4zKlzbfFN1WDU1y7yM884rOSePxcazBuPgtUHX5nHGR8U1NxIwcJ6OnpoX79+vKegPfu3cOFCxcwePBgnD17Fi1btoSVlZX8P9eaNWsQHBycg0bOoVCAAhSgAAUoQAEKUIACFKAABSigKQLFihXDypUr5Xf9mzZt0pRusR8UoAAFKEABClAgUwS0M6VWVkoBClCAAhSgAAUoQAEKUIACFKAABSiQrQWCgoIgJvUeMWIExKTjXChAAQpQgAIUoAAFKEABClCAAhSgAAU0T8DX11fe9ErzesYeUeD9BDZs2ICaNWvCzs7u/QoyNwUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAVymcDVq1fRrVs3ODk54ejRo/ImWbdu3cKgQYNgYGCQyzQ4XApQgAIUoAAFKECBtASMjIwwefJk3LhxAx4eHujYsaM8P8vHxyet7BmadvLkSXleo5h8bciQIbIPXbp0ydA2WBkFKEABClCAAhSgAAUo8GkFLl++LCeutrW1hZeXl5xY8vr169i7dy+aN28ObW3e6vjTPkNsnQIUoAAFKEABClCAAhSgAAU0XcDKygozZ87E/fv30b9/f8yYMQP29vaYPn06nj9/rundZ/8oQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAArlaIDw8HD///DPc3d1RunRpiPsq9+7dGzdv3sSxY8fkd/9mZma52oiDpwAFKEABClCAAhSgAAUoQAEKfIjA3Llz0aBBA9SqVQunT5+W91n7kHpyapnTT25h0pkNcnjGugaIT0qU2/deBGGKz29yW19bF7GJ8akIbjx9CN/g21h0eRcSkl+WSZXhlZ36OyaixR6vV1LfvrvzwRmceHz97Zn+PWppaIr6hSukK29ame6/eILV1w/KQwn/Goidgc5N4WxRBMOOLcORR1fw6+1/0GrPNKy5cQgFjMzTqipL0woqfVhaewhWXtuPiISYTGu7uo0T/CND5KO8hb3yekjAtfAA2d53bj3gue87VPl9BEpvGoy1N/5Csbw2mFilk7o/la2KY0jZ5vI1FRgVhpvPAvHtmV/R2t4NtQo6y3yJyUkYdXI1virfGmXMi6jLciPjBK5du4Zly5bJewZZWlqie/fu8hrehISEjGskm9RUvHhxeU2z6K6urm66e12yZEkMGzYs3fmZMesEFi5ciLFjx6Jo0aLye/YlS5YgJCQkQzsg4uGcar3x+90TWHZtH04G3cA3p39B011TkJScDBFHc/Lyx31vOGzoj4MBF7JkmEOcm6GwsSUOPcy89t4W38QgixhbQVdbB2U3DVU/Sv42CLeUOKZa3hUHGd9UUlznZIHy5ctjwoQJOHPmDAICAjBv3jzExMRg4MCBKFCggLxX4Zw5c+S9A3OyA8dGAQpQgAIUoAAFKEABClCAAhSgQNYKeHp6YujQofIc6zt37mRt42yNAhSgAAUoQAEKZKEA78qehdhsigIUoAAFKEABClCAAhSgAAUoQAEKZBeBRYsWIW/evOjZs2d26TL7SQEKUIACFKAABShAAQpQgAIUoAAFcp2Aj48PXFxcct24OeCcJRAZGYkdO3agS5cuOWtgHA0FKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFMlDg/Pnz6NChA8qWLQuxvW7dOojJQcSExHp6ehnYEquiAAUoQAEKUIACFMgpAra2tvJzo5hYUyxVq1ZFjx495KQvGT3G0NBQ9OvXDzVq1ED+/Plx6dIlTJ8+HcbGxhndFOujAAUoQAEKUIACFKAABT6BQGJiIrZt24a6deuiXLlyOHLkCGbNmoWHDx9i/vz5EBMxc6EABShAAQpQgAIUoAAFKEABClDg/QQsLS3lb2r379/HoEGDMHv2bNjb28PLywvPnz9/v8qYmwIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAgUwTEOfQ7dmzB507d0aBAgUwcuRIODo64sCBAxDf80+bNg0lSpTItPZZMQUoQAEKUIACFKAABShAAQpQICcLREREoGPHjhg3bhxmzpyJLVu2wMTEJCcP+YPHtsfvLB5GhqKpXRWY6f93P4+dD84gLOYFWhR1RT59o1T1Nyvqgo23j6ZKe9tOVEIsYhLj35YlzWOJyUlppqdM1NbSwsraw2Bnkj9l8ntvJyUnyzKqtdgpb1EU2lraMNXPk6q+yWd+g4Whaaq0T7Ezo2p3iOfpeXx0pjZfr3B5/PXwomyjXuFy6u2KlsWw+c4xXHnqJ4+FKq+XGWe3IEl53tys/7s+tKCRuTxeysxW3c/YxAS5ra+jq04T9ouv7ML8Gv3UadzIHIHo6Ghs3LgRTZs2lff0GTJkCI4fP47kf/8dZE6rmlWrtra27JBqnd7e6ejoyKxaynsPF80UEPfFGjZsGGxsbNCoUSOsX78eL168+OjO2ptaw1QvT6pYKSq9+SwQa24cQoF/3+s+uiENriAs9uMd0zO80kq86Fe6EVZeP5Ce7B+c503xTVXh4LLNcCjgAoJjniNA+awkHsExz1SHkd44yPimJuNGLhAoVKgQBgwYgN27dyMkJET+P8zBwUHeR8TJyQmlSpXCmDFjcOzYMSQlvfuzfi4g4xApQAEKUIACFKAABShAAQpQgAIU+AiBuXPnynOvxe/CsbGxH1ETi1KAAhSgAAUoQAHNFXh5doPm9o89owAFKEABClCAAhSgAAUoQAEKUIACFMhiAXFR0JIlSyAuBjI0NMzi1tkcBShAAQpQgAIUoAAFKEABClCAAhSgQHoEQkND5c2lXVxc0pOdeSigsQJ//PEH4uLi0K5dO43tIztGAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAgU8l4O3tjZYtW6JSpUq4c+eOvCH3pUuX0KVLF6gmtfhUfWO7FKAABShAAQpQgALZQ0CcZ3j06FFs3rxZTiBXsmRJTJw4EWISzo9dxGR0K1askBPFiElkfvvtNxw4cACiDS4UoAAFKEABClCAAhSgQPYXCAsLw+zZsyEmimzfvj2MjY2xb98+XLt2DUOHDoWJiUn2HyRHQAEKUIACFKAABShAAQpQgAIU+MQCFhYW8PLywoMHD/DFF1/g+++/h729PWbMmJEhv+l94uGxeQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKBAthUQ58qNHTsWRYoUQbNmzfDw4UMsXrwYjx8/xi+//IIGDRpAW1s7246PHacABShAAQpQgAIUoAAFKEABCnxqAXFftWrVquHw4cPyXhWjRo361F3S6PaTkYwNN4/AQEcPHYt7qPsan5SIBxFPkEdXH+0cqqvTxUZ7ZX/rvZPqtAJ5zNGtRB2MqeiJ2gWd1emqDSvDvPK4al+1rmpdAqMqtMXoim1Rr3B5mBu8+drCKvkdZf0dHGqoikNfWxer6w5HncLlUL2AE3qVqg+bPGbq43UKlcXICm3wuVPDNOuubuMk6/yyXEvYmljJcsJDtfwVeFFu/lxrEAoZWaiSER4XicWXd8l90V7f0o0wyLkpnMxsZVrNAmXkvkizNbZUlzNUjD2LVUMeHX3YKe2JfjW3c4G2lpbMk98wH3qWrIfuJevAVC+PulxaG5WtiqNRkUr44573a4dNdA3Rtpg7xlVqJ+sqbPxf30VmHS1t6e1uUwqizV6l6mGyy2cQximXzxxroZ8ytpZFXWV/xHY7xV+8JsR2bGI8ttw9nrIIgqLDcT7knjRSHfjr4UVExMfgm8odYKZvLJM7O3rgylM/HH10VZVNrg8HXoaJnqFsM9UB7mS4QEJCgqwzPDwcy5cvh4eHBwoXLoxx48bh4sWXr/0Mb/QtFQYEBOCnn36CuO+QeP8eP348Fi1ahOjoaHWpp0+fyjwiYc+ePZg1axZU4xBpBw8exPTp02UeMRfjq8s///yDqVOnynJ+fn7ysNa///5ezZuefXG99NKlSzFnzhw572N6yjBP5guI11BiYiKSkpJw6NAh9OzZE1ZWVvKa9u3btyM2NvaDOnHr2SP4RQSjhfKeKN4DUy4/Xd6Np7GRMqlJkcoyBvQoWVfui/dkVZwQ780pl5L5CkHEDC3lTyPbShji3Ayq92yRVk15nx6uxCiXV96fPyaeiLINbCvI+Phl+VYoaGSesksQsdmjQGkZH0TbIt6IRfRH9LWSlYM6fyflc0MXJVa8+qhoWUydR2y8Kx6nyqzsTHHt8lp8UeWpYGmPgWWa4KvyrWUsU6Wr1iJei/gqFjGOb6t0ls+XGLdqeVd8EyYiXnVXnsMFHv3h120lVtX5IlVMF3U9UF4P6YmDIi/jm1DgktsExH1DPD09sXbtWgQFBUHE4VatWkG8F9esWRM2Njbo3bu33I+KisptPBwvBShAAQpQgAIUoAAFKEABClCAAhkgYGBggE2bNuHWrVvgb8IZAMoqKEABClCAAhTQSAFeYaaRTws7RQEKUIACFKAABShAAQpQgAIUoAAFPp3Ahg0b5CQDgwYN+nSdYMsUoAAFKEABClCAAhSgAAUoQAEKUIACbxXw9fWVx6tUqfLWfDxIAU0X2LhxIxo2bChv3KLpfWX/KEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKJBVAuJm240aNYK7uzuCg4OxY8cOnD17Fu3atcPHTH6RVf1nOxSgAAUoQAEKUIACmifQvn17XLt2TU6uJiZtK1GiBFasWCEn4PqQ3l65ckVOSDdw4EB0794d169fR6dOnT6kKpahAAUoQAEKUIACFKAABTRM4NKlS+jfvz9sbW0xY8YM+d30zZs3sXPnTvndNb+n1rAnjN2hAAUoQAEKUIACFKAABShAgRwhYGZmhsmTJ+PevXsYOnQoZs2ahWLFimH27NmIjIzMEWPkIChAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACmi7w9OlT/PTTT3Bzc0OZMmUg7p38+eef49atWzh69KjcNjU11fRhsH8UoAAFKEABClCAAhSgAAUoQAGNF9i/fz9cXV2hr68PHx8f1K1bV+P7rAkd/PX2EdmN7iX/8yqRrxD0tfVkeo8U6aXMCuNpbARCY17IYzULlMG4yu1wMfQ+boY/xIYGozC3Wm95TFtLC10ca+Fchx8xySX1vUP6l26MEeXbYMGlHTjx+Do2NhiNc+1/wO+NxqGCpb0sL/7S0dLGHPde6FmynpJeDMvrDMXICm3kcUMdPRwKuCC3A6PCcPtZIGIS46CnrYP5NfrBwsAU+/zPombBMvBpNw+i76plYpVO6ORYE4su78Lvd09ibEVPeShZlUFZ/+/OCfhHhKCSVXH80/o7dCruoT569am/3A6KDkdI9DN859YDrtaOMu3o46sw1csj04SjWGoUKI3jbWZhVd0v0MepgRy7nWl+OZ7VdYdDGE9364ZahZyxoEZ/LKs9RJZ701/Dy7fEmSe3EJEQkypLWQs77GsxBQlJiVh+bT/y6RvD23MeOitjFUshIwuI9rY2Ho8vyrXAopr9UdaiqDy+r/lktCpaVV3fgxdP8EQZW0FjC/zv7gkERobB2aIINt4+imvK+O8rx9NaChtb4kDAefWhaOU5mX52M6rkd8ThVtPxTeUOKKP0s+WeaYhNjFfnU214B91UnuO2ql2us0AgPv7l8/Do0SN8//33qFChgryH0PTp0xEREZHpPdiwYQPKly+PUaNGYfDgwVi/fj0uXryIYcOGoU6dOhD9W7t2rbw+efjw4RD3OBo/fjzGjRuHq1evIi4uDv369UNISAhatGiBv//+G05OTvKYqvPffPONrHfkyJHo3LmzvFeSOPah1zbv2rUL9evXx59//okpU6agcuXKOHPmjKo5rjVEICkpCcnJyfI18scff6Bt27awtLREnz59cOzwP0hOSvmu//ZOJyMZCy/thK4SY+YocW5dva9Q0MhcFhKxICz2ZVzcq8Qd8Z4+tlI7eUy8T2+89Q/GV2qPQc5NZZqJriG8XLvitBKb+pVprNTXC+42JdG8qCsudliIhrYVsVyJA83sXNBfOb5XeX8W76Fi+Zh4YqxrgLPtf0RMQhx+uPgHdLV0ZMwQ8bSIsRU2NxyD/S2mooXSjx9r9JVj+Kp8Kxk/Vyvxa0eziaho6SD7If4aXLYZnsVF4YLyGeBi2H0MV/LOq/45XsRHyzzpicfqyv7dKG1mi0ZFKuHgv/E95fHpVbvhy3KtIIxF/J/q2gU7mk6EuYGJzNbBoQZOtJ2FaUq+76v3kXHeWYk34vna1WySHK/I+K74Fh4bKZ9nL99NymeEEwiOfg5Ph2ry+WpgW0HdJfF5KK3l1TioysP4ppLgOjcK6OjooGbNmpgzZw7EPUbEfQtHjx4tt8U9sa2srNCmTRusWbNGxvPcaMQxU4ACFKAABShAAQpQgAIUoAAFKPBhAo6Ojli+fLn87WDr1q0fVglLUYACFKAABShAAQ0W0NbgvrFrFKAABShAAQpQgAIUoAAFKEABClCAAp9AYMGCBfLCGGtr60/QOpukAAUoQAEKUIACFKAABShAAQpQgAIUSI+AuPFVkSJFwO9w0qPFPJoqIG6ovm/fPvl9pKb2kf2iAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgQFYKiEkwxOQZtWvXRmxsLA4cOIBTp07JCTKysh9siwIUoAAFKEABClAgZwqIiTfFBG63b99G+/bKRFeDBqFixYryc2d6Ryw+p06aNAmVKlVCQkICfH198cMPP8DU1DS9VTAfBShAAQpQgAIUoAAFKKCBAomJidi+fTvq1asnJ38+evQo5s6di4CAADkJdfHixTWw1+wSBShAAQpQgAIUoAAFKEABClAg5wmYmZlh6tSpuHfvHvr16wcvLy84ODjI/59HR0fnvAFzRBSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKECBTywgzp/bvXs3OnbsiIIFC2L06NEoVaoUDh06hPv378vv6h0dHT9xL9k8BShAAQpQgAIUoAAFKEABClAg5wiI69aaNWsmH8eOHYOdnV3OGVwmj+T+iyc4FXQD5SyLooKlvWyta4namHNhG7yDbqKilQOczV96fuZYC5vvHJN5jHUNsNCjP772Xo+LYfex/b43tt49ib6lG8ElvyOSkpPx6+1/8PfDS6lGYKqXB1Ndu+BPJX9cUgKOP76GQw8vKHm00G7/TFwIva/Ob25ggp+v7sUXx5eh66F5OB9yFy2Lusrjz+OjcTbkjty+FR6IY0o9z+KiMKBMEzyKCsPWeydxOcxP9s/SMC9mVO0u8zawrYAvy7XChNO/ICohFv6RIVh78y91m6qN6MQ41P3zGxwMOA+rPHmxtPYQbGs8HoWMLFRZ5Pp6+MNU+2LnYooxiH0xxpXXD4pNBESG4ssTK/DtmV+x9MpetLZ3w9PYCPQ/shifH16IeReUazILl1c0tGT+tP4qqzwfj6Kepjqkp62DVXW+wM4HZ7BDeYTGvMCiy7uwx88XC2r0RymzwghUXCad2SDLxSUmoNOBORh1cjVqbh+P8NhIfOfeAzpa2vL4iaDrctv7yU389fAiYpPicSXMHwcUD2EtfF5dqts4ISE5ET9d3p3q0JIre/CN8jqxz2uDr8q3xj+BV+SYU2X6d+daeADKWthBjOd9lp86j4GWlqLGR5oGHh4e6eKMj4+X+cR9hCZMmCC/43z48PXXeLoqS2emrl27onnz5oiJicHQoUOxcuVK7Nq1CxMnTsTp06exatUq9OzZE23btpX3JSpcuDDOnz+Pa9euyWuXFy5cCJHWuXNnVKhQQd6zKCQkBCNGjJA92LNnD2bNmoV58+bB2NgYRYsWledTp7N7aWbT1tbGuXPnZD/Fd74RERGy72lmzuDE69evp/kc5+bXfnh4+DuVxT2txBIZGYnVq1eja8t2iPrpZTx7Z+F/Myy/th8DlPdqEWta2VfF6Xbz0KNk3deK33glLkQkxODu8yB1PrE/UXkvFvXYGlvKeDTVdxM67p+FJOXP2IqeGHpsqczj8r8RMp7WKVRWlv+YeNLMzgUFjMxwQ4mZIkbv9feFnUl+lDYvImPh2FNrZRvuynt597++R7nNwzD8+Aol/0PMPr9V3X/Vhnhv3+XngytP/ZS4X0LGmelnN+PO88cyy7visaqelGtn5f1fLI9fiXGdHWuiu2I9/PhyiM8t4nNHz79+RM2CZTDTrYcss+XucezzPwdDHT0su7ofw44tQ8cDszH73FZUUT6XdCtZR+ZLT3wLiXmOpcrnDxGXK2z5QsZmUe9ij4HIp28k60nrrzfFQZH3Q+NbWu0wjQLZXcDJyQljxozB8ePHERgYiPnz50N8Bhk4cCAKFCgg76H9448/yt9Us/tY2X8KUIACFKAABShAAQpQgAIUoAAFMl+gU6dO6N+/Pz7//HN+n5D53GyBAhSgAAUoQIEsFtDN4vbYHAUoQAEKUIACFKAABShAAQpQgAIUoIAGCxw+fBiXLl3CmjVrNLiX7BoFKEABClCAAhSgAAUoQAEKUIACFKCAj48PqlSpQggKZGuBrVu3Qtxgp02bNtl6HOw8BShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABT5WQExGMWXKFBw9ehT169fHP//8g5o1a35stSxPAQpQgAIUoAAFKECBNAUsLS0hJmQTE7mNHj0ajRo1QtOmTTFnzhw4OzunWUYkiok7+/Xrh4CAAJl32LBh8hywNxbgAQpQgAIUoAAFKEABClBA4wWePn0qJ3hevHgxHjx4gGbNmmHfvn1o2LChnNRY4wfADlKAAhSgAAUoQAEKUIACFKAABXKogIWFBWbMmIERI0Zg9uzZmDhxIubOnYtx48ZhwIABMDAwyKEj57AoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAlkjcOXKFaxduxbr169HUFAQPDw88NNPP6FDhw4wNTXNmk6wFQpQgAIUoAAFKEABClCAAhSgQC4SiI6ORt++fbFp0ybMmjULI0eOzEWjz7ihbrz9D9xtSqFbiTq4FLYWTe2q4Ltz/4OJriHcbEqie8k6GO+9Hs2LumDWud9lw+0dasBQVx9TXbuoO2JjlA/3ngfBIW8B+ATflulxifHq42KjoJG5LFfI2FKd7v3kpmxTtBeREKNOj06Iw53nj9X7V5/6o5mdi3pftZGMZNUmhpRthnMhdzG3Wm912q1ngTA3MJH7I8q3wfnQu3gRH60+7ht8R24nJ/9Xj0gIiXmO9vtnwbNYNcx274W6hcvjaJvv0GbvDMXpgbp8ejaex0XJbFfC/NTZRb/EkrKum0qagY6edAqMClPnVW3oaevA3tQGOx6cUSXJdYPCFVHSrDDOBN9KlX7o4QV0KF5DeQ7rYsLpXxCVECuPXwy9r84XHPMMa2/+hZEV2qCoqTXu/mteTxnv4YeXZL46hcrhcODLbXXBFBvaWlr4unIHfHZgLiL/bUN12F6ps5W9G748vhzjKrXHopoDUFh5/medf/laUuUTa+Gkq4xRvIZuhD9Meeit242/7IZWNpXfmic3H7x+/TomTZqULgI9PT3Ex8fDyckJYtva2jpd5T4mk7GxMXR1dVPdo0ic3/zdd9/Je2iK85wLFSokm2jdurVci/6J5fvvv4eLiwuGDBki98VfpUqVQljYy38/og4xL2PevHnVx6tWrSq3tZTX7Ycsnp6e6mJubm6y/lOnTiEkJARWVlbqY5mxUbhwYSxdujQzqs62dYr7Vb148eKd/RevsYSEBPldfYt2bbDD/sk7y7yaYdOdY/hbeS8UMaFNMXcs8OiPyvmLK+9vK17N+s79F3HRuPciCDH/xkkR/x5FPZVxT5UWnRiHh5GhKGry37/DD40n/7t7AhdC70G854s4U6NAGdnH4sr7rYibj5W2xbLf/xySlHgYGvOfaewrsVzk23j7qFgp7+cW8KraFd5BN7H48m6ZJv56VzxWZ0yxUUqJY2IJig5PkQoMcm4KETOfp4jd4vPB/RdP0MmxJkadXC3juohxCcmJuB4eoC7/w8U/MKJCa2W8pbHmxiGZ/j7xLTE5CV6+mxAUFY7Z1XqhZkFn7HwlBotK3xYHxfEPjW+iLBcK5GQBGxsbeQ9CcR/CiIgI7NmzB9u3b5f30/7qq69QoUIFOSetmJe2YsWKOZmCY6MABShAAQpQgAIUoAAFKEABClDgIwTmz58P8T19p06d5JwH4vcNLhSgAAUoQAEKUCAnCOjmhEFwDBSgAAUoQAEKUIACFKAABShAAQpQgAIZIyB+FKtRowYqV+YFVBkjylooQAEKUIACFKAABShAAQpQgAIUoEDmCPj6+qJ///6ZUzlrpUAWCfz2229o1qxZqhv2ZFHTbIYCFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACGiFw4MABeaPs48ePo2HDhvLmVuKcfi4UoAAFKEABClCAAhTICgExAduff/6Jv//+G6NGjZKTt/Tp0wdTp05FgQIF1F149uwZxERvYkKzJk2aYN++fbCzs1Mf5wYFKEABClCAAhSgAAUokP0Erl69ioULF2LdunVyoufevXtj6NChcHR0zH6DYY8pQAEKUIACFKAABShAAQpQgAI5WMDKygqzZ8/GyJEjMWvWLPm7ndj/+uuv0bdvX+jr6+fg0XNoFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQIGMFQgLC4O4J/KaNWvg4+MDe3t7eZ/vnj17wsHBIWMbY20UoAAFKEABClCAAhSgAAUoQAEKqAX8/f3Rpk0b3L9/H3v27JH3XFMf5MZ7CWy7dwqz3HuhQ/EaOPLoCryDbiI2MR7b7p3ETPee6FjcA38HXoLPk9uIToyTdTuZ2+JxVDhGnVz9Xm3dfBaolHuKeoXLY+6FbbKstWE+nHlyCxEJMW+tKyEpCTra2q/lSU5Olmn59I1Q0MgCX91Yib3+Z1/LJxLKWtjhj/veqY6pyqdKTLGzVXE4HHgZq+oMQ53C5eDl2hVt9s1IkePDNmOTEl4rGP9vmpGuwWvHRIK5gYk0iE54+TyoMpUyLyw3I+NTG558fF2ml8r38rgq/6vr288eySQrQ1P0KlUfpnqGaGZXBb7Bd/BDdSs0sauMm+GByvbn+P7CH/CPDElVxTTXblh8eTcuht1PlS52/mgyARNP/4I/H5zGzgc++K3BKIyv3B77/M/hfOjdVPlV/S+kPI83wh+mOva2neLu5dHBpcPbsuTqY+K+lJMmTXqjgZ6eHuLj42FrawvxvWaXLl1QpkwZdOrUCUnKv7tPsRgZGcn+BAcHy+a1//23r1qLxPDwcAQGBsrzn1u2bJlmNy9cuID27dunOqalpZVq/2N3qlevjlOnTsm+iPO0M3MxNTVFhw58rac0FufEv2nR0dGRr2Fxfrz4zNCtWzc0btwYj2KfYffmYW8q9tb0J9HP0Ovv+WirxJIlNQfJ98zfbv0D7yc331ouPQfjEtOOC8Z6accEVZ3piSfJSIbo+9eVOiBGieVnQ16+/2prvYyrScpxsSQmv9+/+R+r94Oulg4GH12i1JD+eKzqe8q1lWFeiJgco3wGSbmIGJaWr4hx9qbWKJGvkDKeOymLqLfF55aHkWEQ8W2qEr/fN76pKhKfBWa690DxvP/dQ011TKzfFgfF8Q+Nb6IsFwrkFgETExMZ40ScE59LDh8+jO3bt2PFihXyPtviN9jWrVvL9/OaNWtCvMdzoQAFKEABClCAAhSgAAUoQAEKUIACQsDQ0BCbN29GlSpV5P1T5s2bRxgKUIACFKAABSiQIwReP0sqRwyLg6AABShAAQpQgAIUoAAFKEABClCAAhR4X4EHDx5gx44d+OKLL963KPNTgAIUoAAFKEABClCAAhSgAAUoQAEKZKGAuEmKn58fXFxcsrBVNkWBjBUICgqSF/t/9tlnGVsxa6MABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKBANhDYt28fxOQTjRo1grhp9okTJ7B//37UqFEjG/SeXaQABShAAQpQgAIUyGkCdevWhY+PD1atWiUn6ixRogS8vLwQFRWF3bt3w9nZGb///js2bNgg9+3s7HIaAcdDAQpQgAIUoAAFKECBXCEgJo7euXOn/G5afM4/dOgQZs6ciYCAAPz4449wdHTMFQ4cJAUoQAEKUIACFKAABShAAQpQIDsK2NjY4Pvvv8edO3fg6emJESNGQPyut3z5csTHx2fHIbHPFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQIEsEUhISJDnznXo0AEFCxbE2LFjUaZMGfz111+4e/cupkyZAgcHhyzpCxuhAAUoQAEKUIACFKAABShAAQrkRoGTJ0/C1dUVcXFxOHPmDBo2bJgbGTJszM/iorDHzxdmBib4ofrn2HTnqKw7MiEWW++ehIWhqZLeV0k/pm4zMTkJJfIVhK6WjjotvRudDsxGIWMLTHXtCs9i1eCQtwD6HVmU3uKv5Uv+NyUp+eVWGfMir+URCTpa2jDSNYBL/rSve0zGy/JFTfKjmV2VVHWExb7AkGM/I1G5ptKjYBnk0zdKdfxDdpL/7W9aZVV9efXYk+hnCI+NhImeYapD4bERcr+qdclU6X4RIYhPSkB4XGSq9Fd3iphYyaT7L55gwaUd2Kw819qKlxjz8mv7YWmQF0OPLsWMs/9DQGRoquK9StXDxbD72OPvmypd7HgUKI3CynN98OEFeSwk5jm6HfoeScrrp00xt9fymxkYy7SHr7TxWkYmfLSAnp6erMPCwgKDBg3CqVOn4O/vj2nTpsnvOj+6gY+sIDY2Fo8fP37r96za2tqylUuXLqXZmvgeV9zryNvbO83jWlpaaaa/b2KhQoUg6ipWrNj7FmX+TBAQrwvx0NHRkde/i/tbhYWFYePGjWjRogVUr/33abp/6cbKe2Lq18u2e6ew8d942aKo6/tU98a8b3rvf1u8EJW97biqThHbjraZCd+Q2/j+4h/wjwh+Yz/Se6CzY000LFIR03w34c7zx+pi74rH6oyvbNx8Fij/LRkrsTrlImJY5fzFX3sOVG2+Lcbpa+vCJk8+fEh8S9kHEb+eKrH29rNHKZPl9tvioCoz45tKgmsKpE9AvFeL/+MtXrxYfj45ffo0unTpggMHDkDc31BcD9WrVy9s375dxvr01cpcFKAABShAAQpQgAIUoAAFKEABCuRkgVKlSmHJkiXyHio7duzIyUPl2ChAAQpQgAIUyEUCL8+KyEUD5lApQAEKUIACFKAABShAAQpQgAIUoAAF0hZYtGgRChQoICcSSDsHUylAAQpQgAIUoAAFKEABClCAAhSgAAU0QcDX9+XNd6pUSX3jIk3oG/tAgfQKbN68GUZGRmjevHl6izAfBShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABbK9wJ49e1CtWjU0adIEZmZmcvKOvXv3yrRsPzgOgAIUoAAFKEABClAgWwuIidF69OiBmzdvYvz48Zg9ezby588vz/GqVasWrl27hs8++yxbj5GdpwAFKEABClCAAhSgQG4VePHiBRYsWAAx0UKrVq3kRLy7du3CjRs3MGzYMJiamuZWGo6bAhSgAAUoQAEKUIACFKAABSiQ7QQKFiwo/59/+/Zt+Vve0KFD4eTkhF9++QVJSUnZbjzsMAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClAgswQuX76MUaNGwdbWVp47FxwcjKVLl+Lx48dYu3Yt6tatC3E9DRcKUIACFKAABShAAQpQgAIUoAAFMk9g3bp18v/gVatWxYkTJ+Dg4JB5jeWimn+/e0KONjoxDscfX1OPfP3Nv+W2vrYu/nl0WZ1+OewBjPUM0cepgTpNbOTTN8LnTg1Tpb26E5UQh9XXD2Ldjb9w7PFVdD44B/dfPHk12zv3k5NfZtHR0pYbL+KjZT2fl24IQx29VOU7FvdAQSNz3Ah/iNLmRZDfMF+q4yl3QmNeYIZbD4gxp1weRobh1rNAmRSbGC/XCUmJcm2go58ya6ZuXw8PQP48qfvvE3xbtlm9gFOqtssoY9VTxnH6yc1U6a/u1CrojPMhd/Ek+hlCYp7D2aIojj66IvfLKtuivH9kCIJjniFZ+aNaWhR1VTa1sPH2UVWSXNcoUFquy5jbQVv5vsxEea2olqDocPgG30EREytVknptY2SGZOWJfRARrE7jRsYJ6OjoyMqMjY3RrVs3HDhwAE+ePMH8+fPh5uaWcQ1lQE0nT55ETEwMWrRo8cba8ubNi2LFimHJkiWIjo5OlU+cBx0YGIjSpUvjypUrCAoKSnU8I3eOHDmCGjVq8NrqjET9gLp0dXXl9/Pi3qw///yzfG3v3r1b3t9KzGv4MYt4v+pRsu5rVRx+eEmmqWKC2ElMTnotBr1W8BMkjKvUXokHOtjnf062rv1v7PzQrlgrcWimEiu9g27ipyt71NW45HfEu+KxrbGlOn/KjWtP/eVuWjHOVC8PylsUS5kdFSztEazErfsv3vzvu6p1CRjq6mOv/9n3im+pGlJ2qtmUUuKZNk4G3Uh16F1xUJWZ8U0lwTUF3l9A/Pbq6uqK6dOny5gu7mk4duxY3Lp1C+3atYOVlRXatGmDNWvWIDQ09P0bYAkKUIACFKAABShAAQpQgAIUoAAFcoxA9+7d0bt3b/Tq1Qv+/i+/b8wxg+NAKEABClCAAhTIlQKpz1zKlQQcNAUoQAEKUIACFKAABShAAQpQgAIUoEBUVBRWrlwpb3gkLpzgQgEKUIACFKAABShAAQpQgAIUoAAFKKC5Aj4+PihatKi8AFpze8meUeDtAhs3bkTr1q2RJ0+et2fkUQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQrkAAExmcGUKVNw+vRpNG/eHN7e3hCTIXGhAAUoQAEKUIACFKCApgmIc7qqVKkiJ0kLDw+Htra2nMTF19cXjRo10rTusj8UoAAFKJCLBcSkjiNPrkJEfEwuVuDQc6uAmCx0WtVusMljllsJUo07Li4Oc+fOxfnz51OlZ8WO+LzctWtXtGzZMiuae+82bt++jYULF2L16tVISkpCjx49sGPHDjg5pZ6U/L0rfkuBHy7+gQuh99+Sg4cooPkC9qbWmOzymeZ3lD1UCyQkJODixYvyN7gzZ84gIiJCfYwbmSug+i7J3d0dFStWhL6+fuY2yNopQAEKUIACFKAABdQCtra2+OmnnzB27Fh5bpqYVHHmzJmYOnUqPD091fm4QQEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhTITQKhoaH49ddfsXbtWohrYYoVK4aBAweiZ8+ecjs3WXCsFKAABShAAQpQgAIUoAAFKECBTykgrmcbP348Zs+ejTFjxuC7776T96/4lH3KSW3vDziPF/HR2HLneKphnQm+hTvPHuHoo6tISk5WH9t69yQmVO4kr0011NHDXv+zKGNhhzb2bhh6dKk6n75yLK+eEXS0tJGYnARxPeu2xl/jx0t/wkQvD7S1tKCrpYPAqDB1GbFhYWCiHDeEvrYu4pIS5DFzA2MY6RjAQKlTXBf+OPqpTHe1LoFfbh2Gs7kdFlzage+rf44dTSdiis9veB4XjeZFXRAc8xwBkaH48eKfWF5nKOZU64X+RxYjPikRng7VZD3VbJxwOPAynsZGwEhXHz/W6Isvj69Qt1/GvAiczG3xy83DiFHaF8vt54/w4EUw2hWrhn1+Z2GolGtTzF0eq2BpL+tLRrIci0gUfVctYnxiMVfGev/FE7ltrPsyzVDnzdfTnHx8HfVtK8j8qr8uh/nh11tH0NK+KmyNLeVYxTF3m1Ly+Vtz45Aqq1w7K8+VailoZI7K+YvjswNzVEmoV7gcDgZckPv1C5fH34GX1MdUG3UKlcWX5Vpi051j6Ff65f1jxPPsZGaLq0/9cfzxNfz18CLiEhPQoqgrVl0/KIsa6RqgtGK58NJOVVXqtZ1JfllGPL9cMk4gf/78yJs3Lxo2bCivYW7WrBkMDAwyroEMqElcz3ft2jWULl1a1vb777+jdu3aaNGihdyPjIyUa/F9raWlpbrF0aNHY/DgwahXr56MC/ny5cP27dthbW0NOzs7eV50t27dMGzYMKxfvx56enrYtGmTLH/s2DE0aNAgVX3qit+y8ezZM/XR4OBgnDp1CgcOHFCncSNrBcTcm+bm5vI7+06dOqFw4cIZ3oG7z4MwqUpnXHsaAO8nN9X1ezpUR1RCLDYr74OqRbzvtVPSu5aojW33TqGtEhMsDE0gYqWZvjHC416+lo31DGSMU5UTaxEDRExIuRgpaWnFjrTS3hZPjJT2Cijv9w1tK8I3+A76lm4omxExIJ++kRKPteW+paFpyubltqqtlMfmVeuj9Esfg48uUaLcy88HIsZ3LO4Bn+Db74zHrzWiJJwPuSc9RbxVxUWRb/KZ32S/Ozt64HzoXVlUC1qoal0Sk5VYn/LzifhMUTJfIdx8FijztVI+lxxTPsPs8z8n99MT34aVba7cdyYWG2//g+jEOFmuj1NDDD+2HGGxL+S++Cs9cVCVmfFNJcE1BT5eoESJEhDxXzyCgoLw559/ytgvfr/t27cvatWqhbZt26JNmzYoUqTIxzfIGihAAQpQgAIUoAAFKEABClCAAhTIVgKLFi2S39uL3wb+/vtv/p6crZ49dpYCFKAABShAgVcFdF9N4D4FKEABClCAAhSgAAUoQAEKUIACFKBA7hMQF8NER0ejf//+uW/wHDEFKEABClCAAhSgAAUoQAEKUIACFMhmAmfPnkXlypWzWa/ZXQr8J/DgwQOcPHkS33zzzX+J3KIABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKBADhTYuXMnpkyZAh8fHzkhxpkzZ+Di4pIDR8ohUYACFKAABShAAQrkBIHnz59j5MiRWLFiBcQEXYsXL8aTJ0/k5GyNGzeGeMyZMwflypXLCcPlGChAAQpQIJsLPIp6Kif8zebDYPcp8MECnznWgk1hsw8un1MKimtsevXqhbt376JJkyZZPmFASEgIWrVqhe7du2P+/PlywltNsD148KDsz+7du+WEy5MmTZKTL5qZZf5rZtm1/XgUmXryeE0wYR8o8D4CBtp6mOzy2fsUYd4sFggICJATxnh7e8u1r6+vvH9W3rx54erqCgsLiyzuUe5t7vHjx/L30LCwMBgYGKBSpUpwc3ODu7u7fNjb2+deHI6cAhSgAAUoQAEKZJFA0aJFsWrVKvmbnvgOoH379qhSpQqmTZsmf9/Lom6wGQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKDAJxNISEjAnj17sGbNGohru/X19eX35fPmzUOtWrWgpaX1yfrGhilAAQpQgAIUoAAFKEABClCAArlR4MWLF+jatSv279+PtWvXokePHrmRIVPHHJsYj533z2Dj7aOvtbPl7nGcCrqRKj0uKQGe+2bg1wajMLVqV/m4+tQfA4/8hIiEGBjq6KFHyXrwKFAahrr6mFilExZd3oWnsRF4EPEEc6v1TlXfs7gofOO9Hr/cOgzPYtXgblMKuto6stysc7+jiV1l1FDq0tHWxjeVO8LLdyNCY17gcOBl9CxVD8Xy2mDwP0uw6vpB2Bpb4otyLbGz2SQkJCVi4aWdWHntgGxPjKWAkRnGV+4Av24rcS3cH7/fPYkwpS7xjU8RYyvZx6tPA2CsZ4g/m07AhdB7MFDG06poVaxQrneceHpDqr7PvbAVXq7dcNJzDvb6+co+1CxYBtZ5zOCg9MvCwBRdS9SRZYaUbQYxniImVujj1FCmjavUDpPO/Iq8ekZyLCJxVIU28Dq7GXefP5Z5Uv41/9IOdCtZB/am1rj/4on60FcnViIyPgZbGo3FAmXMuopVoyIV0WrvNMQrDikXG6VvC2r0R0jMM9QrXB4DjizGkUdXZBYdLW3leSuDr5XnQyx1C5fD0qv75LbqrwqW9thQf6Q0crEuoUqW65iEODhtHCy3bz9/hC6H5mF61W6okt8Rl8MeoKldFUz12Yg/H5xOVU5Peb6b27mgz+EFqdK58/ECJUuWxLNnzz6+okysQVt5vf7000/IkycP/P39ERkZiR07dsgWV65ciW3btsntwYMHy3sZVa1aVe4PHDhQ5hf3L6pbty50dXUxatQoDBo0SB4XsePRo0f49ttvIa6JLlu2LDp37gxLS0skJyfDz89PbsvM7/hL3B+pT58+8to3cT26kZER7ty5gwMHDqBChQrvKM3DmSVw9OjrcSuj27r3Iki+336rXCf8JDoct54Fom6hcjAzMEHnA3NwU9lXLdvvnUKvUvWxuOZAGYu8fDfhfMg9GOsaoJV9VWxVYs5A56YwV8pWs3FC22Lu2O9/TuYtZGwBU/086Fe6Edbf/BsDyzSBrYklTJR41NmxJu48e/zB8WTRpV2oZOWAX+qPkO2N814LN+uS+Kp8Kxn/SpnZyiGI/ojYs0KJmwnJifK9e1jZ5vKYp0M1XAy9r8REXbRUxiIcBih9FIuIk6L+M09uyf13xWOZ6ZW/wuMi8f2FP9BSibe7lXiqWkQsab13OpbWGoIk5d/t0UdXpeXs81ux4dYRVTa5Fsf7Kn7RiXHy84CR4i6eI7GkJ76JfM4WRaX3JJdO2HLnuBJDE/Dz1b3wDb4tDsslvXFQZGZ8e2nGvymQGQI2Njbo16+ffIj/M+7duxdbt27FhAkT8MUXX8j7dbdt2xaenp5wcnLKjC6wTgpQgAIUoAAFKEABClCAAhSgAAU0TEB8d//bb7/J+9aJe6SIe6ZwoQAFKEABClCAAtlVQEs5sSE5u3ae/aYABShAAQpQgAIUoAAFKEABClCAAhTIGAFxIYy4iEZMGsCFAhSgAAUoQAEKUIACFKAABShAAQpQQLMFihUrhs8//1xe7KzZPWXvKJC2wNy5czFjxgwEBQVBT08v7UxMpQAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoEA2Fti9e7e8MZWvry9atWolJ7GoXLlyNh4Ru04BClCAAhSgAAUokNMFjh07hu7du8sJ3cQEb+3bt0815MOHD2P06NEQE6r16tULXl5eKFSoUKo83KEABShAAQpkpYCYYLfiluFZ2STbooBGCWxtPF5OEq1RncrCzsTFxcnPpDNnzoSHhwfEZMQODg5Z2IP/mhITIg8YMEAmLF26FC1btvzvYBZuRUdH45dffsH8+fNx5coV1K5dG8OHD5ffUevo6GRZT0pvGoJHkWFZ1h4bokBmCBho6yGo17rMqJp1foBAVFQUfHx84O3tjVOnTslHYGAgxHubs7OznDTG3d0d4iEmkxWT13PJWgFxi/tbt27J50b1PF28eBEJCQmwtraWz42bm5tcu7q6wtTUNGs7yNYoQAEKUIACFKBALhM4f/68vB/Jrl27ULNmTUyfPl2ucxkDh0sBClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAK5QECcK7d69WqsX78ewcHB8rw5cc2LuCbG2Ng4FwhwiBSgAAUoQAEKUIACFKAABShAAc0TuH//vrzGTvxffdu2bahWrZrmdVJDelRwbU9EJ8Z9cG8sDEwRFvvitfL59I3wIj4aScr1HmktRYytkKz8CYgMTetwqjR9bV1MqNIJK67tg2jPVC8PDHX1YZPHDGMqeaLylq+QkJyYqsy7dgoameNR1NNU2Qx19GBvaoMHyvXjaZnoaGnLNgOjwqCrpQMtLSA+6b92RX+CosNlnYWNLWBpkBd3nj9CZEJsqnZUOwZKe3pKPREJMbK+xOQkaaI6ntHrXqXqw9m8CEafWvNa1XkVUydzWwREhEKML+VinScfbn72M6b6bMSSK3sg9h9EBKfMkmnbhYwsoK+jCz+lvbReS23s3dChuAe6Hpr3Xn3QV+wHOzfFZJfP3qscM79boFOnTkhKSsKWLVvenfkjcgwcOBCrVq2CuNbb398f+fLlQ968ed+rRnFN9N27dyHmYjQyMnqtrLgu7fHjx7C1tUV8fDzE9Wv6+vqv5UtvQkBAACwsLNJsK711vG++ESNGyOvtTpw48b5Fmf8VAb+IEJTfPOyV1Dfv5tHRh562Dp4rsVBpoiqGAABAAElEQVTEl5L5CuNpbAT8I0PeWMjS0BShMS9jqogRsYnxb8ybVQe0oIU8SsyNShHLxLhSxr+M7su74vGr7Qmr421mocVuLzyOTh3bRV7HvAVhosS5q0/9EJeUkKr4D9U/R7eSdZB/TXeI2P08Llp+fkmVKZ07VoZ5lc8pJjJGfuxz96HxTXS1Sn5HHGrplc5eMxsFKKASEJ8pDh06JP//+Mcff+DJkyfyngFt27aFp6cnXFxcVFm5pgAFKEABClCAAhSgAAUoQAEKUCCHCixcuBBfffUVjhw5gho1auTQUXJYFKAABShAAQrkdAHdnD5Ajo8CFKAABShAAQpQgAIUoAAFKEABClDg7QLihFhxMyQxiSAXClCAAhSgAAUoQAEKUIACFKAABShAAc0WePr0KcSNsipVqqTZHWXvKPAWgU2bNkFclK+np/eWXDxEAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAgewnsG/fPkyaNAmnT5+WEyAtX76cv+9mv6eRPaYABShAAQpQgAK5SkBMuDZ58mR89913aNasGVauXAlra+vXDOrUqSM/5/7222/4+uuvsXHjRoiJzsaMGQNTU9PX8jOBAhSgAAUoQAEKUIACmSVw9uxZ9OrVS04s/MMPP2DIkCHKZOHKbOGfaGnZsiU8PDwwfPhwtGrVCt27d8f8+fNhbm6eJT0Skx4vXrwY4vvoiIgIfPbZZ/IeMhUrVsyS9tkIBShAgYwUEBPB37hxA97e3nJy9VOnTuHSpUtITExEgQIF4O7ujmHDhsm1mCzWxMQkI5tnXR8oIOJwyZIl5aNHjx6ylujoaPj6+qqfy59//hnffPMNtLW1UaZMGbi5ucnnUTynYl+kc6EABShAAQpQgAIUyBgB8Z3Azp07cfLkSfkZrFatWmjSpAmmTZuGKlWqZEwjrIUCFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUo8IkEwsPD5TUtq1atwpkzZ2Bvb4/BgwejZ8+ecvsTdYvNUoACFKAABShAAQpQgAIUoAAFKKAIiPPW2rRpg0KFCsn/txcpUoQumSgQFvsizdqfxUWlma5K9I8MUW2+c72s9hCcfnILfhEh8pGygLmBMRKSE1MmpWv7UdTT1/LFJMbjenjAa+mqhMTkJARGhcld2Way6sjLdVB0uDrhYWQYxONtS6zSXiziZZYPGcPb6k7r2Nobf2FFnaEob2GPi2H3U2V5Hh8tjVMlprETnRiHBxHBaRzJnCSVd1q1l8hXCB2Ke+DzwwvSOsy0XCTwoe/zefLkgbOz8xuldHV1YWtrK4+/Osed+D74XUv//v2R8jprVV3vKsfjOUNAvF9G/xueRHx59X03rVGGxvwXU0WM0IQlGcmISohN1ZX4pPePu6kqeMfOu+Lxq8WF1RfHlmF85fb48vgKpcepA/Tt549eLZLm/rvidpqFUiSGxDyHeHzswvj2sYIsT4EPE9DX10fTpk3lQ1yPfvz4cWzbtg3ifofi3oji84b4f6anpydq1qwJHR2dD2uIpShAAQpQgAIUoAAFKEABClCAAhTQWAFxb8H9+/eja9euOH/+PMzMzDS2r+wYBShAAQpQgAIUeJOA7psOMJ0CFKAABShAAQpQgAIUoAAFKEABClAgdwgsWLBAnuya8oKW3DFyjpICFKAABShAAQpQgAIUoAAFKEABCmQ/AXHSslgqVaqU/TrPHlNAEbh79y58fHwwffp0elCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClAgxwgcOnQIkyZNwokTJ9CsWTM58ZGLi0uOGR8HQgEKUIACFKAABSiQMwVu3ryJbt264cqVK1i8eDEGDhz41oFqaWmhS5cuaNeuHcS1qTNmzMCyZcvw7bffQky8JiZv40IBClCAAhSgAAUoQIHMEoiLi4OXlxdmzpwJDw8PbN++HQ4ODpnV3HvVa25ujnXr1qFDhw4YMGCAnPR46dKlaNmy5XvV8z6ZT548ifnz5+P3339H/vz5MXz4cNm2tbX1+1TDvBSgAAU+qUBoaCi8vb3l49SpUzh9+jTCw8NhaGiIypUro27duhg/fjzc3NxQtGjRT9pXNv5+Anny5JHxWsRs1RIYGAjxPIvnXKw3btyIyMhImJqawtXVVT7P7u7ucm1jY6MqxjUFKEABClCAAhSgwAcKVKtWDX/99RcOHjyICRMmyM9cnp6emDp1KsqUKfOBtbIYBShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUCDrBZKSkuR33qtWrcK2bdugra0N8Z23OJ9QnF8irnfhQgEKUIACFKAABShAAQpQgAIUoMCnFRDXCPTu3RsNGzbEr7/+ChMTk0/bIbaeIQJV8jvCJo8ZTj+5iVvPApGQlIiKVg6oal0St5V9LukTSEYyBv2zBLOr9cLaG3/hXMjddBU00jWQ+fIZGKcrf1ZkKmJshRHlW2PI0Z8RkxifFU2yDQ0TiIqKQkJCAiIiIj7Je734Tvhdi7jumgsFKJA1AieCrkNfRw/TqnbFhNMblIiXnK6G8ygxTldLB8bKOjIhNl1lMjMT41tm6rJuCqRfQPwOXLNmTfn4/vvvce7cOfn78NatW7Fw4UJ5b5VWrVrJ34obNGgAfX399FfOnBSgAAUoQAEKUIACFKAABShAAQpotMDq1atRvnx5OdfB5s2bNbqv7BwFKEABClCAAhRIS4CzNaWlwjQKUIACFKAABShAAQpQgAIUoAAFKJBLBO7du4edO3eCP3Tlkiecw6QABShAAQpQgAIUoAAFKEABClAg2wucPXsW1tbWKFSoULYfCweQOwXEd5FWVlaoV69e7gTgqClAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABXKUwOHDhzFp0iQcPXoUjRo1wqlTp+Dm5pajxsjBUIACFKAABShAAQrkTIFly5ZhxIgRcHJygjg3sVSpUukeqIGBAUaPHo3PP/8c06ZNw1dffYX58+dj5syZaNu2bbrrYUYKUIACFKAABShAAQqkV0B8Zu3Vqxfu3r2LH374AUOGDIGWllZ6i2dZvpYtW8LDwwPDhw+HmLSwe/fu8rOyubl5hvQhPj4e//vf//Djjz/i9OnTcHFxgZgooWPHjpwYMUOEWQkFKJCZAuI97MKFC/D29pa/qYnf1W7fvi2bdHR0hLu7O7y8vOS6QoUK0NPTy8zusO5PICCui/X09JQP0XxiYiIuXbqkfk1s27ZNfr+UnJwMe3t7+VoQv72K10alSpUgvpPiQgEKUIACFKAABSjw/gINGjSAePz555+YMGECypUrhx49emDKlCmws7N7/wpZggIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABSiQRQLinME1a9Zg7dq18PPzk+cQLFiwAJ06dULevHmzqBdshgIUoAAFKEABClCAAhSgAAUoQIF3CYjrQb799lt5Xd28efOgra39riI8nk0EOu6fhSFlm2NV3S9QxNgKgVFhOOB/Hkuv7sW18IBsMgrN6GZcUgK+PL4CtsaW6eqQnYkVxldqL/O2LloVN8MfYvOdY4hPSkxX+czKJMYx6OiSzKqe9X6EgLge78CBA1iyZAn69OmTKddibdiwAfv374e4/mvs2LHo168fKlas+BG9fv+iHTp0eP9CWVhCXDO3ceNGbN++nedqZ6E7m/q0AocDL+HqUz/oKp8B0xOnOjjUQL3C5eT9Iqa4dsHaG3/hUtiDTzoIxrdPys/GKfBGAXF9uXhMnToVN2/exNatW+Vj5cqV8vfiFi1ayOvWmzRpAmNj4zfWwwMUoAAFKEABClCAAhSgAAUoQAEKaL6AlZUV1q9fL+f+WrFiBfr27av5nWYPKUABClCAAhSgQAoBLeVkiuQU+9ykAAUoQAEKUIACFKAABShAAQpQgAIUyEUCI0eOxJYtW3Dv3j3o6OjkopFzqBSgAAUoQAEKUIACFKAABShAAQpQIHsKdOvWDaGhodizZ0/2HAB7nesFKleuDFdXVyxdujTXWxCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClAg+wocO3YMkyZNwt9//4369etjypQpqFGjRvYdEHtOAQpQgAIUoAAFKJBrBJ49eyZvnComURETuYnPsmICuY9ZxDWqX3/9NTZt2oTq1atjzpw5qFat2sdUybIUoAAFKECBdwrcf/EEFbcMf2c+ZqBAThXY2ni8Mqlm+Zw6PPW44uLiICafnzlzJjw8PCAmAXRwcFAf1+SNHTt2YMCAAbKL4hqKli1bfnB3xbVEoo7FixcjKChIToA4fPhwjfxeuvSmIXgUGfbBY2VBCmiCgIG2HoJ6rdOErmTrPvj5+eHUqVPw9vaW67NnzyImJgZmZmaoWrUq3Nzc4O7uLteWlpbZeqzsfMYJhIeH4/Tp0+rXjXj9iDior6+PihUrpnrdFC9ePOMaZk0UoAAFKEABClAglwiI6Yl+++03TJw4EQ8fPsSgQYPwzTffQEzEyIUCFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUooAkCUVFR+N///ofVq1fjyJEjKFCgALp3747evXvDyclJE7rIPlCAAhSgAAUoQAEKUIACFKAABSjwr4C4/q9v377yvLQFCxbIc9KIk36Bgmt7IjoxLv0FPnFOPW0dxCclfuJe5J7mhbeRrkGqAT+Li0q1nx139HX0MNi5KSa7fJYdu6/RfQ4MDJTXZIvvVs3NzTF69Gj0798fJiYmGdZvcd8icT6yajEwMECePHlUu7l6LWLiunXr8N133+HBgwfo1KkTJk+ejBIlSuRql4wYvF9ECMpvHpYRVbEODRHIq5cHWlpa6t7EJsYjRnlk96VKfkccaumV3YfB/lMgWwj4+/tj27ZtEPdSFPcIF9ehN27cWN6LRdzbRdzPgAsFKEABClCAAhSgAAUoQAEKUIAC2VNg/PjxEL89+/r68tzx7PkUstcUoAAFKECBXCugpZxQ8d8ZFbmWgQOnAAUoQAEKUIACFKAABShAAQpQgAK5TyAyMhK2trYYN26cnCQ89wlwxBSgAAUoQAEKUIACFKAABShAAQpQIPsJODs7o3Xr1pgxY0b26zx7nOsFbt26hZIlS+LQoUOoV69ervcgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgQPYTOHnyJL799lscOHAAtWvXxtSpU1GrVq3sNxD2mAIUoAAFKEABClAgVwqIG6Z27NgRUVFR2LBhQ4afx+Xj4yMnnzt8+DDatWsnz3UU54xxoQAFKEABCmSGwP0XT1Bxy/DMqJp1UiBbCGxtPB71CpfPFn390E6ePXsWvXr1wt27dzFz5kwMGTIk1USiH1pvVpZ7+vQphg8fjvXr16N79+6YP3++nLQ5vX24fPmyLCM+vxsaGqJv374YOnQo7Ozs0ltFlucrvWkIHkWGZXm7bJACGSlgoK2HoF7rMrLKHF9XREQExPcC3t7eOHXqlFw/evQIOjo6KFeuHNzc3ODu7i4fpUqVynbv5zn+CdTwAd6+fVv92hKvrwsXLiA+Ph5WVlbyNaV6fbm6uiJfvnwaPhp2jwIUoAAFKEABCmiGgPg8tWzZMnh5ecnfDkePHo2vvvoKJiYmmtFB9oICFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAVyncCJEyewatUqbN68GbGxsWjRogV69+6Npk2bynNQch0IB0wBClCAAhSgAAUoQAEKUIACFNBwgdDQULRt21ae4y/+P9+4cWMN77Hmda/g2p6ITozTvI6xRxTIRAF9HT0Mdm6KyS6fZWIrubtqcV3fvHnzsHTpUhgYGMjrnMW1yebm5rkbJpNGL+7jtHz5csyZMwfBwcHo2bMnxo0bBwcHh0xqMfdV6xcRgvKbh+W+gXPE2U6gSn5HHGrple36zQ5TILsLiPj7xx9/YOvWrXKO3OTkZNSvX1/eB1HM+5w/f/7sPkT2nwIUoAAFKEABClCAAhSgAAUokKsEEhISUKNGDXk+ubivofitgwsFKEABClCAAhTIDgLa2aGT7CMFKEABClCAAhSgAAUoQAEKUIACFKBAxguIyQnFzZLE5IJcKEABClCAAhSgAAUoQAEKUIACFKAABTRfQNwo5MaNG6hUqZLmd5Y9pEAaAuKGb9bW1qhdu3YaR5lEAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAc0VOH36tJyguHr16oiOjpY3lD58+DBq1aqluZ1mzyhAAQpQgAIUoAAFKJBCYOHChRCfZ8XkZOfPn0e9evVSHM2YTRcXF/z999/YsWMHrl+/DmdnZwwZMgRPnjzJmAZYCwUoQAEKUIACFKBArhCIi4vDxIkT4ebmBktLS1y8eBFiYmMtLa1sN34xGfO6devw559/4uDBg/Izsvi8/LZFTGa4a9cuNGzYEOXKlcPRo0flRM/+/v6YPXs27Ozs3lacxyhAAQpkuoB4n7p69SpWrVqFAQMGoEKFCjAzM0PdunWxYMECaGtr48svv8SRI0fw/PlznDt3Dj///DN69eoFJyenbPl+numobOCtAo6OjujatSvE91tnzpyRr6vjx4/j66+/hpGREVasWCHjpoi74vuoPn36YNmyZfIzRGJi4lvr5kEKUIACFKAABSiQWwX09PTk73h37tzBmDFjMHfuXBQvXhyLFi2C+G6GCwUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClAgKwQCAwMxc+ZMlCpVCjVq1ICPjw+8vLzw8OFD/P7772jRogV0dHSyoitsgwIUoAAFKEABClCAAhSgAAUoQIH3EBDnnlWrVg1+fn44ceIEGjdu/B6lmZUCFKAABTJToGDBgvLc4AcPHmDw4MH44YcfULTo/9m7C/iqq/+P4+8VG905pGPk6Ebp7hIpQTBRQUVCJQREFItUkEZFQVCkFJCQRrpHdzMGjF787zn+tx8jBwy4d3t9eVzuN873nM953rvzvXXOyazu3bvr5MmTj7PoOJX3+fPnNWDAAGtr+rk1adJE+/bts/3azPhOLAgggAACCCDwZARSp06tDh06aM6cOXa8QzMGQvz48fX222/LvC4yYyCY/lLmO2gWBBBAAAEEEEAAAQQQQAABBBBwfgFPT09NnjxZ+/fvt+OhOH/ERIgAAggggAACCPwn4OYYsDkcDAQQQAABBBBAAAEEEEAAAQQQQACBuCdgJu4qWrSoncgr7tWeGiOAAAIIIIAAAggggAACCCCAAAKuJ7Bq1So7eNaePXvsxI2uVwMijusCBQsWVPny5TV8+PC4TkH9EUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAGnFjAT+6xfv96pYzTB+fj4qGrVqvb+cQW7adMm9ezZUzNnzlSpUqXUt29fW+bjKo98EUAAAQQQQAABBBCIaYGgoCC99NJL+uOPP9SnTx+ZCcvc3d1jupjb8gsNDdX48ePVu3dvmQnT3n//fb333ntKmDDhbWnZgQACCCCAwMMIHLh4SoWmdnqYUzkHgVghML16D1XyLRgr6nJzJcxn023btrUT7A4cOFAdO3aUm5vbzUlcdv3cuXPq1KmTJk2apNatW2vw4MFKnjx5ZH0uXbpkX0MPGTJEu3btUrVq1dS5c2fVqFHDpQzy/NJRxy8FRtaLFQRcUcDb3Usn2050xdAfS8ynT5/W6tWr7c30cVyzZo0uXLhgJ14141eVLFnSfo9mvkvLmDHjY4mBTBG4n8CJEydknp/muWru165dq+DgYPtZVPHixSOfp+b5aiYLZkEAAQQQQAABBBCIKnD27FkNGDDAjgWRIUMG9evXTy+88MIT+V4xaiRsIYAAAggggAACCCCAAAIIIIAAAggggAACCMR2gevXr9s+LuPGjdNff/2lpEmTqkWLFmrXrp2KFCkS26tP/RBAAAEEEEAAAQQQQAABBBBweQHzm/26desqS5YsmjVrltKmTevydXpaFUg/4UVdCb3+tIqnXASeikA8Dy+9ka+m+hR74amUHxcLvXjxokaMGKGvv/7a9gvs0KGDunTpokyZMsVFjkeus+lvafqIDxs2zOZl+sK/8847SpUq1SPnTQZ3FjgUfEYFp7x154PsRcCJBIqmzqG/6/ZzoogIBYG4LWDGcJk7d66mTZum2bNn237nZjyERo0aqXHjxsqaNWvcBqL2CCCAAAIIIIAAAggggAACCDi5wE8//aSWLVva76Rr167t5NESHgIIIIAAAgggILmFOxYgEEAAAQQQQAABBBBAAAEEEEAAAQTilsCKFStUtmxZO1lSiRIl4lblqS0CCCCAAAIIIIAAAggggAACCCDgogLffvutunfvrqCgILm5ubloLQg7rgrs3LlTefLk0eLFi/Xcc8/FVQbqjQACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgg4tcCWLVs0cOBA/fLLLwoNDXXqWCOCS5cund577z29+uqrSpw4ccTuR74PCAhQr169NHXqVBUuXFj9+/dXzZo1HzlfMkAAAQQQQAABBBBA4EkKbN26VQ0bNtTly5dlBkt9Gr/dunLlip2A7rPPPlOCBAnUp08ftW/fXp6enk+SgrIQQAABBGKhwIGLp1RoaqdYWDOqhED0BKZX76FKvgWjl9gFUl2/fl39+vWzn1GXK1dOY8aMUbZs2Vwg8gcPcebMmfYzbXPmyJEj5e/vr6FDh2r06NG6du2aWrdurU6dOilv3rwPnrkTnJHnl446finQCSIhBAQeXsDb3Usn2058+Axc+EzTHm/cuNGOS7Vq1SqZ2759+2x/xpw5c8pMqlqyZEl7X7BgQd7fu/BjHdtDN993b9u2LfK5vHr1au3YsUNhYWHKlClT5PPYPKeLFCkiHx+f2E5C/RBAAAEEEEAAgWgJHD58WL1799bEiROVL18+ffrpp6pVq1a0ziURAggggAACCCCAAAIIIIAAAggggAACCCCAAAL3EjC/SRk7dqzt33Lu3DlVq1ZN7dq1U/369eXt7X2vUzmGAAIIIIAAAggggAACCCCAAAJOIjB9+nS1atVKVapU0eTJk5UwYUInicw1w0g/4UVdCb3umsETNQIPKRDPw0tv5KupPsVeeMgcOO1hBa5evarvv/9eX3zxhY4fP277M3fr1k25cuV62Czj1HlHjx61dqNGjbLXP9MX/M0331TSpEnjlMPTqOyh4DMqOOWtp1E0ZSLwQAJFU+fQ33X7PdA5JEYAgScjYMZymT9/vqZNm6Y//vhDgYGBdrzxxo0bq0mTJsqdO/eTCYRSEEAAAQQQQAABBBBAAAEEEEDggQRefPFFzZ07V5s2bVL69Okf6FwSI4AAAggggAACT1rALdyxPOlCKQ8BBBBAAAEEEEAAAQQQQAABBBBA4OkKmMkGt2/frnXr1j3dQCgdAQQQQAABBBBAAAEEEEAAAQQQQCDaAq+88ooCAgK0ZMmSaJ9DQgScRaB///4aNmyYjh07Jnd3d2cJizgQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQcAmvWrNGAAQPsAMj58+dX9+7d9fzzz8vDw8OpfU6ePGknYRg5cqQ8PT1lJmF46623lCJFioeOe//+/fr444/1ww8/yM/PT3379lXDhg3l5ub20HlyIgIIIIAAAggggAACT0Pgl19+Ufv27VWkSBFNnTpVadOmfRphRJZ55swZ9evXT99++62yZcumTz/91L7WjkzACgIIIIAAAg8ocODiKRWa2ukBzyI5ArFHYHr1HqrkWzBWVGj9+vVq27at9u3bp88++0xvvPFGrP9M9ty5c/Yz7UmTJkVOZGA+3zZ9h1KmTOnSj2ueXzrq+KVAl64DwSPg7e6lk20nxgmIAwcOaNWqVVq9erW937Bhg8wEqsmTJ1fJkiXtrVSpUipRosQjfQcXJzCppNMLXLhwwX43HvF8N/enT5+Wl5eX/P39I5/v5rmfM2dOp68PASKAAAIIIIAAAo9TwIxX++GHH+r3339X+fLlNWjQIPt66XGWSd4IIIAAAggggAACCCCAAAIIIIAAAggggAACsU8gMDBQP/30k8aMGaONGzfa7+PbtWunNm3ayNfXN/ZVmBohgAACCCCAAAIIIIAAAgggEIsFvvnmG7333nt67bXXNGTIEKcfo84VHor0E17UldDrrhAqMSIQYwLxPLz0Rr6a6lPshRjLk4weTODGjRsy/ZtNn+49e/aoWbNm6tGjhwoWjB391h9M4/6p9+7da60mTJig1KlTq0uXLrY/eIIECe5/MiliROBQ8BkVnPJWjORFJgg8ToGiqXPo77r9HmcR5I0AAjEgEBISokWLFmnatGn67bffdOrUKZnx2Bs3bqwmTZrY9RgohiwQQAABBBBAAAEEEEAAAQQQQCAGBIKDg1W4cGFlyZJF8+bNi/Xj1MYAGVkggAACCCCAwFMUcH+KZVM0AggggAACCCCAAAIIIIAAAggggMBTEDh79qydLPz1119/CqVTJAIIIIAAAggggAACCCCAAAIIIIDAwwps2rRJhQoVetjTOQ+BpyowdepUNWrUSO7u/GTtqT4QFI4AAggggAACCCCAAAIIIIAAAggggAACCCCAAAII3CSwePFiVa1aVSVLltTx48ftoMfme8kWLVq4xMQ+adOm1aBBg3Tw4EG9/fbbdkKizJkzq1u3bjpx4sRNNb3/6tGjR2V+Y587d24tX75cZoKHzZs32++43Nzc7p8BKRBAAAEEEEAAAQQQcBKB0NBQO1FZ8+bN1b59ey1cuFDmtfPTXlKlSqXBgwdr586d9reQZqKVMmXKaNmyZU87NMpHAAEEEEAAAQQQeEoC169fV8+ePe1n1ClTprSfyXbs2DFODOqfPHlyTZw4UTNnzpR5DR8eHm4nITQOLAgggMDjErh48aL9nODTTz9V/fr1lS5dOmXNmlWtW7fWP//8oyJFimjUqFEKCAiQGaNq7ty56tOnj2rUqKEUKVI8rrDIF4EnJpAkSRJVqVJFH374ob0GmwmB9+7da78bNp9T/fvvv/bztFy5csl8llW7dm317dvXTjoUFBT0xOKkIAQQQAABBBBAwBkE8ubNa39PuHLlSvtZTalSpdS0aVPt2bPHGcIjBgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEnFggLC9P8+fNl+rVkyJBB3bt3V+HChbV06VLt2rVLPXr0kK+vrxPXgNAQQAABBBBAAAEEEEAAAQQQQOBmAfNev1OnTnr33Xc1cOBADR8+3CXGqLu5DqwjgAACCPxPwMvLSy+99JJ27Nihn376Sdu3b5e/v7/q1aunVatW/S9hHF/btm2bWrVqZccnNeM3DRs2TPv27VPnzp2VIEGCOK5D9RFAAAEEEHBdAU9PTzse+3fffWfHYzfjs1eoUEHff/+9ChQoID8/P9sXfcOGDa5bSSJHAAEEEEAAAQQQQAABBBBAIJYIJEqUSJMnT9aSJUv0zTffxJJaUQ0EEEAAAQQQiK0C7rG1YtQLAQQQQAABBBBAAAEEEEAAAQQQQODOAuPGjZOPj49eeOGFOydgLwIIIIAAAggggAACCCCAAAIIIICA0wmYwbS2bNliB1pxuuAICIH7CJjB3Tdv3mwnFL1PUg4jgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgg8AYE5c+aoTJkyqlixokJCQuwkvqtXr1b9+vXl5ub2BCKI2SKSJ0+uPn366ODBg+rVq5cmTpyorFmz6s0337T77lXa6dOn7aRGOXLk0OzZszVixAg7GUbLli3l7s5wDPey4xgCCCCAAAIIIICA8wmcOXNG1apVs69rJ02apMGDB8tMdOJMS7Zs2fTzzz/r33//Vfz48VW+fHn7XsRMRseCAAIIIIAAAgggEHcE1q9fr2LFiunrr7+2A/mbiXfNa8W4ttSpU0c7d+60kxOaiZnbtGmjc+fOxTUG6osAAo9BwPRH3Lp1q0aPHq2XX37ZTnaaLFkyVa5c2X5uEC9ePHXp0kVLly7VhQsXtG7dOg0fPty2Q7ly5XLJ7wwfAyNZxgEB8/rDjMVmPkcz35mbv4eVK1eqZ8+eSpIkicaPH6/q1asrRYoUypMnj9q2bSszobCZNNh8186CAAIIIIAAAgjEdoFSpUrZiRj/+OMPme/z8ubNq7feekvmd3csCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgjcLHDgwAHb39v08TZ9Ww4dOmR/j3LixAmNHTtW5cqVuzk56wgggAACCCCAAAIIIIAAAggg4AICV69eVZMmTTRq1Cg7TsT777/vAlETIgIIIIBAdATMWJvPP/+8Nm3aJPNbYfP74NKlS9s+iKbfd1xdzJhIDRs2tH0yjc2ECRMUEBBg+2mafpksCCCAAAIIIBB7BMzroeeee05Dhw7VkSNHtHz5ctWqVUs//PCDihQpouzZs6tr165as2ZN7Kk0NUEAAQQQQAABBBBAAAEEEEDAxQTMuLVmTrIePXpoy5YtLhY94SKAAAIIIIBAXBJgxtO49GhTVwQQQAABBBBAAAEEEEAAAQQQiPMC4eHhGjlypFq3bq2ECRPGeQ8AEEAAAQQQQAABBBBAAAEEEEAAAVcR2LVrl65cuSJ/f39XCZk4EYgUmDp1qtKkSaNnn302ch8rCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACT1YgLCxM5nubQoUKqXbt2kqRIoVWrFihRYsWqUqVKk82mMdUWqJEiWQmJ9q/f7+++OILzZo1Szly5FC7du3spA03FxsUFKQPP/xQ2bJl0+TJk/X5559r9+7d6tChgzw9PW9OyjoCCCCAAAIIIIAAAi4hsH37dpUoUUL79u2zr/VbtWrl1HEXLVpUf//9t+bOnauDBw+qYMGC9vX40aNHnTpugkMAAQQQQAABBBB4dIGvvvpKJUuWVMqUKe0A/h07dpSbm9ujZ+yiOSRPnlwTJ07UzJkztWDBAuXLl09mcmIWBBBA4GEEZsyYYSd4T5YsmZ3gvFOnTjJ9E2vWrGm/KzTvuw8fPmzXu3TponLlyil+/PgPUxTnIBArBby9vVWqVCmZvx3zPbL5rO3kyZMyf1uNGjWyfz9momAzaXDSpEntpMKTJk2KlRZUCgEEEEAAAQQQuFmgbt262rx5s4YPH65p06Ype/bs6t+/vy5fvnxzMtYRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEE4pjA1atX7ffrVatWtX22v/vuOzVr1kw7duywfVvat28v0/+bBQEEEEAAAQQQQAABBBBAAAEEXE/g3LlzMu/5Fy9erPnz59v3/K5XCyJGAAEEEIiOgPmt8MqVK+1YQCZ95cqVbR8r0/c5PDw8Olm4fJolS5aoevXqdvwm0w9z+vTp9vfTLVu2lIeHh8vXjwoggAACCCCAwL0FzLg3ZcqUkRkTx4yLuGbNGjVp0sS+JjBj5GTOnFnvvvuufc0UV14f3VuMowgggAACCCCAAAIIIIAAAgg8OYHu3burePHiatGiha5du/bkCqYkBBBAAAEEEEDgAQTcHyAtSRFAAAEEEEAAAQQQQAABBBBAAAEEXFzAdLjcs2ePXnvtNRevCeEjgAACCCCAAAIIIIAAAggggAACcUtg06ZNdhCRfPnyxa2KU9tYITB16lQ7uToD4cSKh5NKIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgIsJ3LhxQ+PHj1eePHnUvHlz5cqVSxs3btSsWbNUunRpF6tN9ML18fFRx44d7W/nv//+ezswc968efX8889r1apV6t+/v7JmzaqRI0eqZ8+e2rt3r9566y15e3tHrwBSIYAAAggggAACCCDgZAJ//vmnfX3v6+urf//9V4UKFXKyCO8eTo0aNbR+/XqNGzdOCxYsUM6cOfXBBx/o/Pnzdz/JccS8hp8zZ84903AQAQQQQAABBBBAwDkFzGs/M3D/woUL7We1zhnlk4+qTp062rZtm+0/ZD7DZ0EAAQQeRsB8N3bixAl9/vnn2rBhgy5cuCAz8bnZbtSokTJkyPAw2XIOAnFaIE2aNKpbt64++eQT/f333woKCtKWLVs0ZMgQXb58WcOHD4/TPlQeAQQQQAABBOKOgBkv4uWXX7a/y+vatat9n5EjRw6NHj1aoaGhcQeCmiKAAAIIIIAAAggggAACCCCAAAIIIIAAAgjYfiBvvvmm/S1KmzZtFD9+fP322286cuSIBg0aJD8/P5QQQAABBBBAAAEEEEAAAQQQQMCFBQ4fPqxy5crp4MGDWrZsmV134eoQOgIIIIBANAUqVapk+0+ZMTtNn6r69evbcYx++eUXhYWFRTMX10o2d+5ce52rUKGCrl27pnnz5mnNmjVq0KCB3NzcXKsyRIsAAggggAACMSZQvHhxffbZZ7YflRm3oVWrVpo9e7bKlCmjTJkyqXPnzvb9cmx9jRRjkGSEAAIIIIAAAggggAACCCCAQAwIuLu7a9KkSTp06JC6d+8eAzmSBQIIIIAAAgggEPMC7jGfJTkigAACCCCAAAIIIIAAAggggAACCDirwHfffadnn31W+fLlc9YQiQsBBBBAAAEEEEAAAQQQQAABBBBA4A4CmzZtsgNn+/j43OEouxBwXoE9e/bIPH+bNm3qvEESGQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQCwWuXr2qESNGKGfOnHrllVfs4MQ7duzQlClT5O/vHwtrfHuVPD091bZtW23fvl0///yzdu3apdKlS9vBmxs1aqR9+/apa9euSpAgwe0nswcBBBBAAAEEEEAAARcRGDJkiOrUqaOGDRvaCdxSpUrlIpH/L0wzeGvr1q0VEBCg/v37a+TIkcqWLZu+/PJLOzHb/1L+tzZnzhwNGzbMTtT2zz//3HqYbQQQQAABBBBAAAEXEMiSJQuT7t7hcUqePLmSJUt2hyPsQgABBKIvUKxYMb322mt2YncPD4/on0hKBBCIloD5LCt//vxq3769nnvuuWidQyIEEEAAAQQQQCA2CZjf23300Ufau3evzO/w3njjDRUsWFAzZ86MTdWkLggggAACCCCAAAIIIIAAAggggAACCCCAAAK3CAQGBmro0KH2NylFixbVggUL1L17dx0+fFh//PGH6tevL9O3mwUBBBBAAAEEEEAAAQQQQAABBFxbYMuWLXasNtMnZeXKlcqbN69rV4joEUAAAQQeWKBkyZL2c9+NGzcqT548atGihZ0/cezYsbpx48YD5+dsJ4SFhenXX39VkSJFVKtWLSVJkkTLli3T4sWLVbVqVWcLl3gQQAABBBBA4CkLFCpUSJ988okdJ9HMy9uuXTvNmzdP5cuXV8aMGfXWW29pyZIlMq8xWBBAAAEEEEAAAQQQQAABBBBA4PEImDFshw8frsGDB2v+/PmPpxByRQABBBBAAAEEHkHA/RHO5VQEEEAAAQQQQAABBBBAAAEEEEAAARcSOHr0qB2Q30wOxoIAAggggAACCCCAAAIIIIAAAggg4FoCpqOwv7+/awVNtAg4BKZOnarUqVMzkTrPBgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDgCQlcvHhRgwYNkhn46L333lOdOnW0Z88ejRs3Trly5XpCUThXMe7u7mratKk2bNigWbNmqUCBAjKTV5iJjM3kxiwIIIAAAggggAACCLiiQEhIiF5//XV17txZ/fv31/jx4xUvXjxXrEpkzN7e3nr33Xe1b98+vfrqq+rZs6d9HzNhwoTISVXM5CpdunSReZ0fGhpqJ3Ezv7FkQQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBJydgxpEYNmyYtm3bprx586pevXp2XIk1a9Y8uSAoCQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQOCxCpg+HPPmzVPz5s2VIUMGffDBBypSpIiWLVumnTt3qmvXrkqXLt1jjYHMEUAAAQQQQAABBBBAAAEEEEDgyQksXrxY5cuXV86cObV06VL5+vo+ucIpCQEEEEDA6QQKFiyon3/+2X4ebK4Pr732mrJnz66hQ4fqypUrThfv/QIy4zWZcYzy5cun559/3tZl/fr1mjNnjsqWLXu/0zmOAAIIIIAAAgjIvD7q27evtm/frq1bt9rxEs176QoVKtjv1N944w0tWrTIjpEIFwIIIIAAAggggAACCCCAAAIIxKxAq1at1KxZM7Vt21Znz56N2czJDQEEEEAAAQQQeEQB90c8n9MRQAABBBBAAAEEEEAAAQQQQAABBFxE4Pvvv1fy5MnVuHFjF4mYMBFAAAEEEEAAAQQQQAABBBBAAAEEIgQ2bdokf3//iE3uEXAZgalTp6phw4by8PBwmZgJFAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBwRYHAwED16dNHmTNnVr9+/dSmTRsdOHBAw4YNU6ZMmVyxSo8l5tq1a2vFihV2IGZPT09VrVpVJUuW1IwZMxQeHv5YyiRTBBBAAAEEEEAAAQRiWuD8+fOqWbOmJk2apGnTpql79+4xXcRTzS9p0qQaMGCA9uzZo+rVq6t9+/YqVKiQZs+erZ9++slOShcWFiZzu3r1qipVqqS9e/c+1ZgpHAEEEEDANQVSeCdWhQz5XTP4R4g6V9IMeit/bUfdC9hcKjruG2crE+XmlyzjI5TweE/1cHNXiTQ5H1sh6eInj3xemOdIZd879+VIEz+pyqXLc9c4nk2fT/2Kt9SbDuv0CZJHSVcnc/Eo22zEjMCNGzf0999/65133rET/cZMruTyJAXiuXuqaOocapu7knoXba6X/KrYvzMfDy81zRZ7J25OGz9ZlDb41jY5Yrtkmlz24ciSOI2GlXtVGRKksNuebh56ztHmDCjRWlUzFop8yDImTGkNh5R9JXLf/VYedxtryjfXH1Nns5R1tKMmzjsttTIVlbfjsb/bUi1j4ShunQrUVXyPePJPmeWued4tL/Y/eYFDhw7p22+/VYcOHZ584ZTo1ALBwcGaOXOmunXr5tRxEhwCCCCAAAIIIIDA/wRy5swpM67EypUr7fd3pUqVUosWLXTw4MH/JWINAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwKUETB/13r17K2vWrLZfx+HDhzVixAgdP35cY8eOVdmysff3XC71QBEsAggggAACCCCAAAIIIIAAAjEoYH4HVqNGDftZwJ9//ikz7gMLAggggAACRsD8XnjMmDF2fJ8GDRrYvl9ZsmTRZ599posXLzo9khmfyPRnNPUwfRrN+KPbtm2zv4EuXLiw08dPgAgggAACCCDgnAL58uWz36tv2bLFjo345ptvavny5XZcxAwZMuj111/XwoULFRoa6pwVICoEEEAAAQQQQAABBBBAAAEEXFDgu+++k4eHh155JfpjbLpgNQkZAQQQQAABBFxQwN0FYyZkBBBAAAEEEEAAAQQQQAABBBBAAIEHFAgJCdHo0aPtJNvx4sV7wLNJjgACCCCAAAIIIIAAAggggAACCCDwNAXOnDmjo0ePyt/f/2mGQdkIPLDA/v37tWHDBjVp0uSBz+UEBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACB6AmcOHFCXbt2VebMmTV06FB16tRJBw8e1Oeff660adNGL5M4mKpChQqaP3++1qxZo/Tp06thw4YqWLCgJk+ezKDMcfD5QJURQAABBBBAAAFXEjhy5IjKly+v7du3a+nSpfa1rCvF/yCxmslTRo0apa1btypHjhyqU6eOOnfuHCULM6nKhQsXVLFiRZn3RywIIIAAAgg8iMCLuSupX/FWD3KKy6fNkjiN2vlVUb8SreSbMIWtz+bAAyqeOofGVHhLo57tqFOXg7T3wnGnrGsSr/h6u0BdbQ88/Njia+tXSU2zl7P5N85WWm1yV4xSVkqfxI7nTUttajpEdTIXj3IsYqOzI8aBpV5UYke8b+Wvo23PD1O1jP+bfPbUlSANLvuyPNxce0hY81pswYIFunz5ckTVn+q9mZBvypQp+uabb3Ts2LGnGktsKPxJP75FUmXXioaf63PH347kpjmH1uni9SvqmL+2jreZoK/Kto8NrHesw6kr53Xw4ikNKtXWtsVl0/nZ9sG0EfHcPZUufjK9ka+m3shfy57vnzKrWuWqoLwpnrHb+Rz3DbOWtsfTJ0hu9yX09FbJtLn1fqGGqpIxen3SnkQb6+3hpZ+rdLH1MoFOrNTZtpU26P//z7SXi+t9op8c6eJ73HmcpJxJM+iXqu9bL3P9MreCKbPoSuh1bQ08pHf966tMWr+bs3XJ9Y0bN8r0z4ptS3BwsJ04tX///vrzzz9jW/WozyMKmOfE22+/rZ9//vkRc+J0BBBAAAEEEEAAgSctUKpUKfv95bRp07R27Vr5+fmpe/fu9ru8Jx0L5SGAAAIIIIAAAggggAACCCCAAAIIIIAAAgg8uMDVq1f1008/qUqVKsqWLZvtz9G8eXPt3LnT/tbjpZdeUqJEiR48Y85AAAEEEEAAAQQQQAABBBBAAAGnFxgxYoTM5wCvvvqqHYfN29vb6WOODQF6unvEhmrctw7hV27o+soDuvT1Yl0asfyO6cNvhOra/ABd+2vnHY+zM/YIXA+9IXcX718dex6NB6/JM888oyFDhujAgQNq166dPvnkEyVJkkRubm5OfYsfP77effdd1axZU7t379b48ePtb50fXIAznoaAp7trj8nwNMwo8+kIeMWR13ZPR5dSEXB+gdy5c+ujjz7Spk2bFBAQYPvLr1y5UpUrV5YZT/G1117TwoULGffc+R9KIkQAAQQQQAABBBBAAAEEEHBygWTJkmnChAn6/fffNXbsWCePlvAQQAABBBBAIC4JeMalylJXBBBAAAEEEEAAAQQQQAABBBBAIK4KzJw5U8ePH9crr7wSVwmoNwIIIIAAAggggAACCCCAAAIIIOCyAqYTsFn8/f1dtg4EHjcFfv31V6VIkUIVK1aMmwDUGgEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBB4jAIHDx7U559/bgczMoMb9erVS6+//joT9z6gefHixe2gUFu3btWAAQPUunVra9mtWze1adNG8eLFe8AcSY4AAggggAACCCCAwOMTMK9bzURmZuK1VatWyUzKFhcWPz8/TZ8+Xe+//76++uorhYeHR6l2SEiI7UNbqVIlrVixQuY9EgsCCCCAAAL3E3B3TGLa3q+qMiZKqfLp8mrpie33OyVWHD9w8ZTG7Vyg1/PVVEhYqK3T2asX9fOepXrNsW9L4AGntUifILm+KtNery4ZruCQq4/t8ajkW1DfbfvT5l/Rt4D+OrQhSlmZEqW2Xm8VqBNlf8RGlsRpdDD4tMr81tXu+nDNJG1vPkJvOHznHfkvrzWndiuxVwINLvuy3lw2MuJUl7tfvny5qlatKh8fHzVq1EgtW7ZUtWrV5On5dIa6LVKkiDp27KhRo0a5nKUzBmwmMox4fBs2bKhWrVrZbS8vrxgPt0m2Mvru2Tf02/5V6rj0O10PC7Fl/Ht6t6buW66eRZ9X5wL1Yrzc+2WY0iexCqXMpr+P/te3637pH+T4zXmHK1xrT++RaRtqZCqi3/atuq0t/mH3Yg0p99+4QTMOrFa2H19R4LWLtshNZw/o+x3z1NavcmQIl0Kuadq+FWqQpaSKps4Ruf9uK0+qjS2T1k+HL52xt4IpsuhaaIh2BB2JDCtjwpTafu6Q9pw/rkKpskXuv3WlY/5aqju3n8x1zSzG8IzjemaW0PAwdVk5Tr9U6aqgtZcc+R22+13xvwYNGsh8J1isWDG9+OKLatq0qdKmTeuKVYkSc6JEifTCCy9o6tSpWrNmTZRjsWnj9OnTWrdunWrUqBGbqvXY69KkSRP73Fi7du1jL4sCEEAAAQQQQAABBB6PgPkcoU6dOhoxYoT69u1rf+PYp08fOx7u0/rc6PHUlFwRQAABBBBAAAEEEEAAAQQQQAABBBBAAIHYIbBhwwaNHj1aP/30k4KDg1WrVi3b99rc87lu7HiMqQUCCCCAAAIIIIAAAggggAAC9xL4+OOPZX7j9cknn+iDDz64V1KOxbDA9Bo9dPRSYAzn6hzZXbtyVf/+vUxLZ/yldYtXKtQxLohZmnRsq5aVXosM8sa165r38wxNGTJGVwODVL5uVb1bqXPkcVZin4Cbo0pl0+WJfRWLYzVKkyaNBg4cqO7du2vhwoUKDf1v3AJnZXBzjC1Rrlw5pUuXzllDJK57CGRIkELTqnfXxRuPb4yJexTPIQSiLZA3edwYDy7aICREIA4L5MqVSx9++KG97d7tGDfFMa6CuY0cOVKpU6e2Y/SYsSMqVKggDw+POCxF1RFAAAEEEEAAAQQQQAABBBB4OIGKFSvqvffeU6dOnfTcc88pe/bsD5cRZyGAAAIIIIAAAjEo4BmDeZEVAggggAACCCCAAAIIIIAAAggggICTCnz77beqXr26smbN6qQREhYCCCCAAAIIIIAAAggggAACCCCAwN0ENm3aJDNgCoOP3E2I/c4qMG3aNNWvX5/B4p31ASIuBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQcEmBgIAAO9nCDz/8IF9fX3355Zd66aWX5OPj45L1cZag8+fPbydG7tevn/Xt2LGjzKRI77//vjp06KAECRI4S6jEgQACCCCAAAIIIBBHBRYtWqSGDRvK399fM2bMULJkyeKUxMWLFzV69GiFhYXdsd4hjskmzSQrNWvWtBPTxY8f/47p2IkAAggggECEQO1MxbT8xHY1y15Or+evqaWO9biyhIWH26pG3JuNizeu2H2XQq7Ze2f8b0CJ1pp18F9d+P9YH0eMSeMlUOFU2bT42FZ5uLmrfPp86rpyfJSiNpzZJy/3u0/e5unmod/2r4o8x5iauBN7RX198vfRTXq/UENV9vWXWXfFxbwGM8vVq1c1ZcoU+xlr0qRJ1bx5c7Vs2dJOxGsm5H2Si6fnf8PsPulyn2Qdn1RZNz++ZiLDyZMnK0mSJHrhhRfUokULlS9fXjHhnMonib4o3c62Q++uGKPrYf89r26u56frf1XT7GUVz93zjsdvThtT6+6O5+6Y597SjANrYirLyHzulnfwPdq389cva9DG6ZF5BF67GLluVkLC/5ucO1z/tfERB0PDwxx7ou6LOHbz/ZNoY015lXwLauHRzbboSr4FItcjYjly6axdPRR8OmLXbfdp4idV/uSZ9PmF6Tp2OfC242aHucYN3zZbg8u+rKqzet0xjSvsvHHjhg1z3bp12rBhg95++207iWibNm3spKLmb9KVF9Nmx0Q74owGoaGhtq1s3LixM4bn9DG5u7vL3FgQQAABBBBAAAEEXFfAy8vLTtRo3r/0799f77zzjoYOHapBgwapTp06rlsxIkcAAQQQQAABBBBAAAEEEEAAAQQQQAABBGKJQFBQkP3Nn+mnYX6X4ufnpw8++EDmc920adPGklpSDQQQQAABBBBAAAEEEEAAAQQQuJeAGbvhrbfe0siRIzVq1Ci9/PLL90rOsccgUDx1ThVP/RgyfkpZXrt2TXPnzrV9Ec24KNevX7f9Q0w/G9NPJFOmTJr0xbd2zETTb2rs2LHq3bu3Tp8+HTmWyDd9BqpIliJPqQYUiwACDypgxj9q1KjRg55GegQeWMCMycCCAAIIIICAKwrkzJnTfhdvvo/fs2ePzPgt5mbei6dOndq+lmrWrJmee+45eXjcfTwjV6w7MSOAAAIIIIAAAggggAACCCDwOAXMWCbzleA7gAAAQABJREFU589Xq1attHTpUkWMRfs4yyRvBBBAAAEEEEDgXgL/jYx/rxQcQwABBBBAAAEEEEAAAQQQQAABBBBwaQHzQ9AFCxbYCcVduiIEjwACCCCAAAIIIIAAAggggAACCMRRgU2bNqlQoUJxtPZU21UFjhw5ojVr1qhnz56uWgXiRgABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQScSmDjxo0aMGCApk2bJjNwsJnQt2XLlgxgFMOPUvbs2fX999/bCWm++OIL9ejRQ2bQqHfeeUcdO3ZUkiRJYrhEskMAAQQQQAABBBBA4P4CkydPVtu2bdWgQQNNnDhR3t7e9z8plqX48ssvdfHixXvWKiQkRGvXrlXjxo31xx9/8H7pnlocRAABBBB4ya+KXv1nhNLET6oazxRR1sRptf/iyUiY8unyqmjq7HY78FqwJu5aZNfLpcujYqlz6PTVC/px9xK7L1385KqS0V8ZEqbQ6pMBWnJ8W2Q+ZqVEmpyK5+6pgKBjapHzWS09vl3rz+yVj4eXyqXPK/+UWRUaHqZf9izV8cvnopyb0NNbz+cor2cSptLeCye07vQeBZw/qrDw8CjpKmTI74g3h4KuXdL0/St1zhHzzUuZtH62rOuhN7Tx7AF7KFxR87g5fXTW/VNmUWlHvvEdMW46u18Lj26OcloqnySq/kxhpXbc7794yqY54LiPWIxlAUceoY5JynedP6bFx7ZEHLrtvkiq7KrmyOutZaNuO5bI00dVnymk3Ml8dfTSWRvH0UuBkek83Nz1nMPncsg17T1/QrUzF1UWx+M98+C/1tMkzJU0g02T03F//tplNc5WWukTJFe4w7lmpqL2cZnlSB+dZc+F41GSucnNPr8+Xjs5yn6z8e22uepT7AUb86M+Hrdl/oR3mNdiZjl//ryd9NtMdJc2bVq1adNGLVq0cIp+MevXr7cTA1y+fFlFihRRtWrV5ObmFil16tQpzZ49W+befFZs0mTLls0eN8+FJUuWyHxWbybs8/PzU9WqVSPPje0rEY/vhQsXIh/fNGnSRD6+hQsXfmiC9ws1VDLvRBq0cbou3rhyx3xCwkPVY9VEud/0eN2vDfJ1tMl1M5fQyO1/yc/RPtTKXExHgs9oyt7ljtbvf+3fndqqY4425PsKb6qCbwHb3pv0cw+t08krQTa+7EnSqbijbc+XPJNWn9qlm9uH+5Vrrgf3yvtOAMniJbRt/N9HN9nDpl0xbWhwyFVtOLPvTqc80L57tbEmo/tZZ0iQwraVY3bOt3GZSZyPXQ7UJMe186rjumOWF3I8q0RePo7HpLhWnNypl/NUc7S1ZR3t/1G7bgxvvQbaE+/w36t5a6iYw3978+Ey15XPN0zTT3v+uS3l4mNb9WnJNrZM0+a78mLaoNDQUFsF0xaZ2yuvvKJatWqpdevWql27tnx8fFy5ilFiv197fa82+V7HohRyj40rV65o8eLFMnGYNt8Y+/r62jNmzpypvXv3KlGiROrQoYP9rMJ8VnPjxg2lT59ezz//vK5du2a/xzVjfpm20lxr6tWrZ4+bTO5XP5MmODhYkyZN0qFDh+x3wyVKlFCePHmiTBp77Ngx/fnnnzL9+MqWLavKlSubU+1i2u1FixbJ3d1dpUuXlok7ICBAzZs3V65cuSKSRfs+OvGsWLFC169ft3FOmDBBFSpUkIn7Xp4RAQQGBurXX3/VgQMHVKxYMfsa7OZrdEQ67hFAAAEEEEAAAQRcTyB58uQy3/OZ395169ZNdevWVaVKlew+xlFxvceTiBFAAAEEEEAAAQQQQAABBBBAAAEEEEDAtQXM7yr++ecf20fd9Fc33803bdpUQ4YMUbly5Vy7ckSPAAIIIIAAAggggAACCCCAAAIPJGB+/2/6S8yYMUNTp05Vw4YNH+h8EiMQIWD61MyfP19mXJTp06fL9N/09PRURJ/EiD5RYY6+xOPGjbPHxowZo169eunEiRMy+81i+vCY/jGmXycLAggggAACCCCAAAIIIBAbBXLkyGHHOjfjne/Zs8e+H58yZYrMGD1mXIJGjRqpWbNmevbZZ6OMKxAbLagTAggggAACCCCAAAIIIIAAAo8qEC9ePP3444923Dozr1ifPn0eNUvORwABBBBAAAEEHknA/ZHO5mQEEEAAAQQQQAABBBBAAAEEEEAAAacXMD/4zJgxo50wzOmDJUAEEEAAAQQQQAABBBBAAAEEEEAAgdsEtmzZogIFCty2nx0IOLOAGdQrceLEqlKlijOHSWwIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgNML/Pvvv6pdu7YKFy6s3bt36+eff9b27dv14osv2klUnL4CLhqg+Q3+N998owMHDqhDhw4aOHCgMmXKpI8++kjnz5930VoRNgIIIIAAAggggIArCnz55Zdq2bKl3nzzTft+wNvb2xWr8Ugxnz59Wp999pkiJpa8V2ZmEsp58+apXbt2Cg8Pv1dSjiGAAAIIxGGBPMky6ty1Szp15bxGbZ8ndzd3vZavRhSRpSe2q0TaXOpTvIW2nzsceWzZiR1q61dFC49utvvKp8ur7kUaa/PZA9oVdFQ/VumiL0q3s8eeSZhKU6p21bw6fVUnc3F9U7aDuhVurHcK1lNCT2+tb/KNroZc19ebZ8jTzUN/1flYPh5ekWUli5dQS+p/qh2O8gdt+k3VnymslY0GaUGdfhpQorVN5+XuocFlX1YK78T66/B6lU+fV2sbf6ncyXwj8+lZ9Hk9n6O8hm2drWn7VqpboUb22KNcKT8p0UqdC9TTn44y/z6ySX0dTjNr9lRy70Q276TxEujXat30+/5VGrJ1luo66u+fMmuUmLIlSadvt83VmlO71bNos8hjd1rpVLCu/nWkCw65GuVw/hSZrFtIWKi+3zFPSR1mqxt9qeaO+polQ4IUGlexk6ZX76G3C9TRsPKvKH+KzPb4X7X7qF7mEjbdJUe+xjlz4tS2TrvPH1f2pOlt3cz+PY7th1nSJ0iuUc91dNRxl1Y7brcuq04GqEDKzKrxTOyaENtMGG6WkydP2s9Zzefb2bNnV79+/ewEeLc6PIntd999176mrFu3rmrUqKGuXbuqUqVKOnv2rC0+KCjIjsvStGlTdenSxU50vn79+sjQzGfDZvK+zp07q3Tp0vaz4siDcWwl4vE9deqUBg8ebCd0N49v37597fcYD8pRLHUOe8qWswfveersQ2t1NfS/59b92iDzN7Wk3qcaWOpF2753zF9bxVPn1EjH32NnRxscsdytrTJtsWnbzHLscqCjDTjmKPu63X49X01He/6yft6z1NHu/KVPHO3xS47rglmiU+698raZ3OG/ljmfi2zXTfs+ruLbmlmrpwqlzHaH1A++625trMnpftZNs5XVioafqb/juvBVmZfs9Safo20e5LgWzq7Vy17fTD4HL56y1930CVPo130rdOxSoPKleMY6mnY2yHFdju6y3HEtHrxlplae2ClfR34jnn1dv1f/wHE9d7sti9Und+k9/4a37XflHWFhYTI387c4a9YsNWnSRClTplSbNm3s+2FXfy98v/baPHb3apPvdSw6j3twcLBy5syp+PHjq3v37jKfM5QtW1ZXrlyxp5vryOjRo/Xxxx/bbdN/ztj37t3btolm59WrV+21xqz7+voqd+7cNj+zHZ36nTt3TkWLFlX+/PltXc3jbPqXmuvPO++8Y7LRokWL7EQ35hqbJ08eNWjQQB07drTHzPmtW7dWtWrVNG7cOL388stauXKlRowYoQoVKigwMNCmi+5/94vn4MGD9vts4/Tbb7/p1VdftT7m+9X7eZoYAgICrJepo7mWnDlzRr///rvc7vA3Hd2YSYcAAggggAACCCDgfALZsmXT1KlTtXz5cl26dMm+5jXf5x09etT5giUiBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAglgkcP37c9pPOlSuX/e2A+a7+66+/ltk/fvx4lStXLpbV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5sx5z/0FCxaMdT8bE07AnPPMueVu55foJcUnTfT0ty/Hda4y6U9cDbn9MF0Oux5j281bYTHW47sSW97xPTah093rHGvKiq/1sSv/vNckdHzR8zPe+y/+HX3THcslMuVV62J19MzqcXfsY0PKF7jX+dpEH3nOju2cfLd9P/74o8x8r8lcmzG/aTo7O9t2nZFpTTzu7u6Rq1GPOXLkiFr29PSMWo5cMMfFdr3nXvXbtGmTunTposOHD8vFxUXFixePzE7169fX7Nmzo9bNgvld999O5rffuKY6deqof//+8Y4ntvz+jWeGDBliy4ptCCCAAAIIIIAAAulYoEKFClq5cqXmzZunfv362Ws85vP7yy+/HOvn9XRMRdURQAABBBBAAAEEEEAAAQQQQAABBBBAIJUKmDYmkydPtvOhQ4dUt25dO/ZYq1at5OXllUprRdgIIIAAAggggAACCCCAAAIIIJDUAqavuGeeeUYvvPCC7e8tqcunvLQrcPLkST355JPavn27vvvuO9sP4O217dy5s0y7+ho1atg+Ak0bmbVr12rFihUqW7asTR4YGGivfR07dkwDBgy4PQvWEUAAAQQQQAABBBBAAAEEElDA9G1g+gww80cffSTT5+WsWbNsv/ODBg1StWrV1LZtW7Vp00YFChRIwJLJCgEEEEAAAQQQQAABBBBAAIHkEzD92Jnfzh988EF7T775DZ0JAQQQQAABBBBIbIF/erhP7FLIHwEEEEAAAQQQQAABBBBAAAEEEEAgyQTMD07e3t4yHUAxIYAAAggggAACCCCAAAIIIIAAAgikXoFt27apfPnyMo1umRBIDQLz58+3jcNz5syZGsIlRgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAYdA/vz59d5778n81uPh4aHZs2dblzfffFN+fn7q27evmjVrJjOoy7Jly/TNN98oX7582rJli/788097fIsWLWR+3/Tx8VGlSpW0dOlSffbZZ+rUqZMdBGbx4sUaNWqUzp07Z9OYdM8++6zWr1//r54DM2iMybNUqRpkaAMAAEAASURBVFJ2gBlT/tdff62nnnpKjRs3/ld5chACCCCAAAIIIIAAAkkpcOjQITVo0EBeXl52oMRcuXIlZfGUhQACCKQqAXMdoEePHvZagBnseOTIkfb6QqqqhCNYc094r1691KRJE3td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CCCAAAII3Clw6dIlffvtt/riiy+0efNmlSlTxranfvrpp5U9e/Y7D2ALAggggAACCCCAAAIIIIAAAggg8B8EzD3xvXv31sSJE+298q1atfoPuXEoAv9fwLR7bN++vapVq6Z58+YpS5Ys/3/nPZYOHTqkRx99VKZthmmHZNogMSGAAAIIIIAAAggggAACCKRdAdMXvOmf3sxHjx617bdMe37TV5Dp175169a2/4ratWvzHTHtvgyoGQIIIIAAAggggAACCCCQ6gRMv4mm7+vXX39djz/+uEqWLJnq6kDACCCAAAIIIJCyBZwcN/f9MxJHyo6T6BBAAAEEEEAAAQQQQAABBBBAAAEE7iGwf/9+FS9eXD/88IOaNGlyj5TsQgABBBBAAAEEEEAAAQQQQAABBBBIDQL16tVT6dKl9fnnn6eGcIkxnQuUKlVKTZs21fvvv5/OJag+AggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIJDwAqZD3bffftt2qJvwuf+T499//608efJo+PDh6tu3r06ePKnChQvfdwe9ZhAYM/BLwYIFYw01LCxMpqz8+fMrNDRUpqsDd3f3WNPGtfHatWs6c+aMChQoEFfSBN1fsWJFe8++sWJCAAEEEEAAAQQQQOBeAufOndODDz6o69eva82aNfYz973SJ8S+NSd2qNniYQmRFXkggEACCvi6e+twx0kJmGPaycoMJnvw4EHNmTNHfn5+aadiCVST7777Tp06ddKgQYM0ePDgBMr132VjruscPnzYPl/mOTPzgQMHotaPHz+uW7du2cwzZMhgry0VKVLEPhZ2XGcyc+R6bAMO79y5U+XKlbsjODc3N5tvly5dbAfdJp/I6eClU6o458XIVR4RSHcC8x8ZpAb5/NNcvTdt2iTzf96cY0aNGmWvjaeVAcdDQkLsNfhp06apY8eOGjduXLwHYU9NT3SZWb114sq51BQysSJwh4CHs5tOdpl2x/b0uuH06dMKCgqyc2BgoH7//XdduHBBXl5eqlKligICAlSjRg07m98BmRBIDgHzO7R5fZrXqnncuHGjLl++LB8fH/vb9M2bN21Y5vflypUrywwEZL6HmkfTd5uLi0tyhE2ZCCCAAAIIIIAAAkkgYD4XDhs2TGPGjFGZMmX0ySefqE6dOrGWPHr0aH377bdau3atMmbMGGsaNiKAAAII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qZILAggggAACCCCAAAIIIIAAAggg8JcCS5cuVbNmzfTQQw9pzJgxctW2bX+ZIBtumoBp/2Kura1evVqzZs3SHXfcke66165dq/r166tbt262/710F0gBCCCAAAIIIIAAAggggAACCCCQqoB5HnLq1KkKDw+345SbfvTatm2rTp06qUmTJvLx8Un1OFYigAACCCCAAAIIIIAAAgg4r8CGDRtUq1Yte6/1ueeec95AiQwBBBBAAAEEbomAV6JjuiU1UykCCCCAAAIIIIAAAggggAACCCCAQJoEzMOfd999t8xDdrfddluajmEnBBBAAAEEEEAAAQQQQAABBBBAAAHXEjDXf0yD3//973+uFTjReoTAoEGDNHr0aEVHR3tEviSJAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDg/gITJ07U448/rhYtWmjcuHEyHfAyIYAAAggggAACCHimwKJFi9S0aVO9/PLLevPNN10aYeHBjWod8ZZL50DwCLijQJBfoKLDRrljapmWk+keceTIkfrXv/6lwoUL23asZjBbd5u+/vpr9e3bV3nz5tWoUaPUoEEDd0vxuvMx5/7QoUPavXu3du3aZV+vXN63b58SEhJsuUFBQSpTpoxKly5t51KlSilpLlmypMx2JgQQQAABBBBAAAEEEEAAgb8W6Lt0lMK3zVdsQvxf78QWBBBwa4FtocNVICCny+W4ceNG1alTR+ZaUsuWLdW5c2cFBwcrS5YsLpcLAd+YwMGDBzV+/HiNGTNG69ats+c/IiLixgq7xUcNWjVBn2+I0OWE2FscCdUjgAACKQVy+AVob9jolCtd5N369etVr149nT9/3vblbb4nhISE2P69+b7gIifRDcM0/TZFRkbKfGeZO3euzp49q65du+qLL75ww2xJCQEEEEAAAQQQQAABBDxJwFyrnz17toYPH67p06crV65ceuKJJ9StWzeVLVvWkyjIFQEEEEAAAQQQQAABBBBAAAEEELDjjjds2NA+Lzx58mT5+vqigsANCcTExOjBBx+UaQczZ84c1ahR44bKufKgkydP6o477lCJEiXsNT0+n1fqsIwAAggggAACCCCAAAIIIIBA5gkcOXJEpj980w/+ihUrVKhQIXXo0EGdOnWyv9Uzr2ZKRgABBBBAAAEEEEAAAQQQyGiBAQMG6NNPP9WmTZtUrFix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nuaZZ57B+fPnsXz5cuTMmfO2ipa1NidOnMDevXvV+kZxZT2s7E+WOFm7UqJECVSoUAHlypVT7xr1vmO9D1nWqDiz3sfZhsm6l8jISMge6PDwcLXGVdaMaivrbP744w/VxpSUFFWsrImR9sn+a1kPK2txxa1YsSLc3d2drZrpSIAESIAESIAESIAESCBDCcgcW+awZvdO/HeSR9creW1Z6bgtuZY5G6/n6Dqfrlfnd+RKPm20/05d3RZdnnZ1eWmFRW6dRqfVrqN4nU67jvafWcebw2a/lOdMWKfRrjmflmk3rTgdL67Zr9ObZdZ+6zx3Gtb5nHWlbXKGljPp7aWzjrMOS/m2ZNZyaU9Gm7x582L48OHKym/5efPmYfbs2Zg6dSrKGHtEHnnkEQwePFj9Ts7otrB8EiABEiABEiABEiABEiABEshuBDw8PNTZQvL9d9GiRZAzhGhIgARIgARIgASyHwG37Ndl9pgESIAESIAESIAESIAESIAESIAESIAEMjeBa9euKSURsjmUhgRIgARIgARIgARIgARIgARIgARIgARI4MEmEBgYCF9fX6WQ68HuKXuX1QiIMj1RMC4bkmlIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIIDsTKFKkCNavX48BAwagdevW+Omnn3hYU3a+Idh3EiABEiABEiCBLEVg586dGDVqFF566SU1n8tSjX8AG/vEE0/gm2++cbpnojxDFFmI4go3Nx6X5jQ4JiQBEiABEiABEwFXV1cUK1ZM2QYNGphiUnvj4uIsipfDwsKU8mXtnjlzBlu3blXxERERqTLmz58f8v5MlEOLa8tfuHBhiNJork1PhY4BEiABEiABEiABEiABEkg3AnquHR0djYULF2L+/Plwd3dHhw4d8NBDD6F79+7puo95RJX2+OHYGkv7F57chHcDHsGIKu3w2f4/EJ+caImbdmQlBlVsif4VmmO64c/tlgt5cnoiJPqSSvNU9S7Yc+kkPm083JIn6GoofDxyW8JhMVeUf+npncqVeDFdl41HTFK88lfOXxwlvAuqspXA+FPUywe53NxRzJBrs+1iIDqXqqfaEZUUh37lmqo04xsM0klQ2CsfTl27gHJ5i8DLzQP1C1XER3t/tcSLZ7fR5hoFS6eSORtoV7w2Khnt3REelCrL3+f24aHyTTGk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ESHGkwAJkAAJkAAJkAAJkAAJkMDtEZC9rlOmTMGMGTMg6/Z69eqFNWvWoHXr1rdXEFOTAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmkIrB79271rqVv376YOHFiqjgGSOB2CPz2229qLf/48ePVGpLbyesoray5lfNVypYti08//dSSXNbXbN26FbIWROyuXbsg61wbNGiALl26qPWvTZs2RcGCBS15HkRPrly5ULNmTWXN/YuJicGBAwewd+9eZWVt7LRp0xAXF6fWztSpU0ftvRBeAQEBqFixItfKmgHSTwIkQAIkkOUJyFxB5hFy1octV2RmubNhnU5cXYdZlpZfp9WudX4t1645PiXlv/Xy6XVhZL2yIytzK0kjrtlvS6bjdZmyJ1TLzK74zWFdltmV9cnmsDmPyM3xsiZZwrbK1Pl0GrNrTq/TiavLMvvTiznLIQESyFgCeu+AdjO2Npae2QnExsZi0aJFmDNnDpYvX45NmzahT58+GDJkCNq0aZMtzm6V8xdkDmHLyhkNtuTWMnvp7iQurTxpyaU9acVlpFzKlrlYfHy8TU5p1W3NT8L2roN1vDmttV+Hza747Vn5fyrxNCRAAiQg8yP5LSW/icRqv5y56YyV33fayvto7U73Zs8AAEAASURBVBfXHBa/WC8vL/UOWvZw2rKSRn570ZAACZAACWRNAnKG8kcffaS+Ccs5MzVq1FAdkbnnzJkzERkZiRdeeCFrdo6tJgESIAESIAEScEjAzWEKJiABEiABEiABEiABEiABEiABEiABEiABErhnBK5fv64WyE2ePPme1cmKSIAESIAESIAESIAESIAESIAESIAESIAE7h+BwMBAtDIUcNKQQGYisHTpUjRp0gT58+fPTM1iW0iABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEggUxB47bXXULp0aYwYMQJnzpzBrFmz1GF+maJxbAQJkAAJkAAJkAAJkIAi8Prrr2Pz5s3K+vj4kEomIBAcHIzp06djxowZShmRKHKyNnLIthx0vXjxYq6ttIbDMAmQAAmQAAlkQQIyDxNbtWpVh62XQ9AvXLiQyl68eBHh4eEQVxQ467CkNSvuEWUdfn5+Fuvr6wuxItN+67DkoSEBEiABEiABEiABEiCBe01AK9IUZZrWVhS1W8vMYXvxISEhDrsic2hR1ilm586dyoo/z5jmcKlXXLxOmVHVOmHQ6k9vSXsk8iz+CTuEFkWroVWx6lgXetCS5rdTW/B+wGB82HAIVp/bh5R/FXFeT4xF8PWLeKxqe0w6tAxxyYmWPP3LN8Pm80dwNvqyRWb2lM7thxdr98Gzm6ZZ8rnkuFWBXkxSAr4/uhpLT+/EtcQYSFvM5mDEaXjnzIURVdrhuyMrLFH53L3Qr1xTHL5yRsmkX38Gb7PE34lHyqhVsAzWhh5Q2ZsUqYKJBxZbivL3KYmcLm7YfjEQ7obbu2wjLDixEeO2fI9lZ3bh1w6voEeZAPwYuBZDKrXCnMD1BucDaP7HK/ip/Yt4wr8TvjTK+2zfH+haugEGVmiO7w4vx77LwZY67sTzSp1+RrtcsSJkj8pu5px8IwXHIs+hWoFS8MuVD+FxV52uwhH7GUdXOV2W3N+uNq6/uYAjV27+P/HzzIdo477TZmf4ceW1dz10Wms3oFBF5HJzx/KQ3YhJilc2wK8S3t/1i2LRqHBljN049ba4SB0XY6/i77P70MAo32zK5y2i7pFtF46Zxchh/Ctk9OvU9Qup5FkpIN8M5Le6PCdLliyJYcOGYeDAgfD398903ZD3Eh9//DGWLFmC559/HhMmTEDfvn1Rr1491dZatWohOjoaU6ZMwdixYy3tl3zz5s3DmDFjLDJ7Hq3IXT+77aW1jpPvLUWLFlWK5+SdSI8ePZxe2/T222+r69CtWzdVrChoNhut2G7NmjXo16+fOYp+g4Dw0vdygQIFMHjwYHUvN2rU6J7y0WOOv0+pNJ/h9hok41VeYyxcfXYv7nS8vhIfhQ93/4L3jfF/fINHMHTNF2lWWaNAaRwwxuS0jDyruxljW80CZbH38klLMmlnuPHMDDaef/bGzvnHN9gdVy0F2vEI07p+5fHl/kV2Uv0X5czYpFM70z+d1to1j0U6LjElGZONudW7xvzrXYP9mzvm6ijlpudcwdGcLDPMFf6det7VXMHR/ZcKsClgvj7pOVcwVaG8Pcs0xDObvrMWW8IPwlzB/HytVKmSZa5QtmxZSz8zq0fWSAwfPhzt27fHnj171Lj89ddf4+WXX0bDhg1Vs//55x+89NJLli4cPHhQjceNGze2yGx57ma+YF3eqVOn8N5772Hq1KlKabnEW88DROZoLiRpnDGcrzhDiWlIgARIgARIgARIgARIgARIwD4BeQe1YsUKTJo0CXKGbJEiRdQ76CeeeALFihWzn5mxJEACJEACJEACJEACJEACJEACJEACJEACDgkEBQWhc+fOkO/3csaY/k7vMCMTkIAVgXXr1mHQoEEYPXo03nzzTavYuw/KmXiHDx/Grl27IGtyFy1ahF9//RXLly/H1atXIWtsOnbsCEnXtm1b5MmT5+4rfQBK8PLyUut39Boe6ZLwE5Z6z4WcWyNrgmU/h+jwCggIsOSRfLJOl4YESIAESIAEzATkG57eB6hd2SMofm2twzqdjteurXQSJ3tvdJq0XHMa7bd2pa13Y2QtqOyfuBsrZ43lzp3bUoa5TO3XrtRjy69l4mqr01rHabl2dXpr19XV9W7QMC8JkAAJkAAJZHoCnp6eGDBggLJyvtf8+fMxZ84ctfdF1h3Je4whQ4agZs2amb4vd9pAedcnYz7H/TslyHwkQAIkkD4E5Lep/n1qy69lstdRzl0Q15aVOLHyjtfar2Xald/HYiWs/bbCIkvrd7f5N3tUVBQuXbqU6syguLg4m2Epzxnj4eGh9nnKmO3Ient7q9/WZlf8tsJaJu/H+d3LmSvBNCRAAiRwZwRkT8cPP/yAUaNGYePGjdi9ezdEJq48i+W8Ij6H74wtc5EACZAACZBAZifgltkbyPaRAAmQAAmQAAmQAAmQAAmQAAmQAAmQQHYiIJvsZJFBr169slO32VcSIAESIAESIAESIAESIAESIAESIAESyJYEZKPHmTNnIIrNaEggsxCQ95NyCNi4ceMyS5PYDhIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARLIdAQeeeQRpYS7d+/e6mDcP/74AwUKFMh07WSDSIAESIAESIAESCA7EpC52SeffIIZM2agTp062RFBpumzHKa9ZMkSTJ06FStXrkThwoXx2GOPISQkBD/99JM6aFs3VpQwFSpUCKtWrYK/v78W0yUBEiABEiABEsgmBETxstjKlSs77LEo5BDFVWIvXryorPaLAg6x+/btU/Hij4iIUOd4mAvOly+fUuxcsGBBOGvloHYaEiABEiABEiABEiCBzE/ArLzNrLBN5DrsjGtOo/26DB2250qc2UperfTOWYqisNTd3R2iGM7aarkoWRfFc84aUY4uc+rSpUujeMe6OFo9J5KdzNypZF0kpSTjXHSE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xzAJkAAJkAAJkEDWJ8C3pVn/GrIHJEACJEACJEACJEACJEACJEACJEACDwiBxYsXQzZYyGI3GhIgARIgARIgARIgARIgARIgARIgARIggQefgCh0LlasGDcVPPiXOkv1UDbndunSJUu1mY0lARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggftJoGjRohAFPw899BBatWqlFHp369btfjaJdZMACZAACZAACZBAtiQgh2qGhYVBDtgUxU8094bAmTNnMH36dMyYMUPxb9++PX755Rf06NHDrkKIp59+GsOGDVOHoU6aNAmiTIKGBEiABEiABEiABDILgRw5clgUSVSoUOG2miWKL0Q5eEREhHLFL4outNWKLyQcEhJikUtY8to6JF7mSnnz5rVYUYZhDovycbMV5Rk6bO2XsPSPhgRIgARIgAQyKwEZC5OSkpRNTEy060q6tNLYizPnEb+t8O3KdX06n3ZFoab2O6tM3d61cXd3V+9cRImMWAlrq8PWrsRrmbe3t0qvw+Y4LdOuOU7XoV1zGi2z5Uo6zj3sXdH0iTO/DxUlrXI/+vr64vHHH8fw4cNRqVKl9KnoLkuJTU5Is4S45EQcjTybZnxaEVFJcamiElKSLOHvWj6F7ReDcCbqkrKWCMPj4+GNpBvJZhFCoi+lClsHLsddt4iik+It/rvxXEuMVW20VUbyjRQkGzqkzkZfviVa4sRcirt2S5wtgbTd3H5baezJbuAGYqz6nJiSmp+0KTQmQhWj2N6q/yrNKhyxTzOjKUJYOjLxxn32zMbv8Grdfnh203SjV6kbae96mMs+F32zn2ZZRvrPx15Js/gPAobg831/Ykd4UJppGHH/CJQuXfquKpcxtHjx4pYyxowZY/Gn5WnWrBlefPFFyPcbeZ9gfq/RunVr/Pzzz2lltchdXFwgcwZtpB0yzlsbPz8/i0jeN9BkTgLOPMOl5TIWO3q+3el4rclEJkRjy4VjOnhH7vFrYWnmczR2pjWuplngXUY4MzZZV2Gvf+a0jq5V9QKlEHz9Iq4mxJizpem/27mCvTmZrvR+zxXCYtIeT3UbHc0V0uv66PrSwz0dFW63GM4V7OK5J5HyG01MoUKF7NYn7/2bNGliN42tSOv5gq00tyOTbwtmY/69aZan5ed8JS0ylJMACZAACZAACZAACZAACZDA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xalq1aqB1VhagwACCCCAAAII5IFAenq6G2PDxtnYu3evC+dL237bl5aWdbwKezYQGxurcuXKZYltW/btMTExeVBrskAAgZwE7P3zbt26yZ7f2Dvp3qVOnTpas2aNd5UYAQQQQAABBAJEIORsZ4uce1sESANpBgIIIIAAAggggAACCCCAAAIIIODrAvaiqHVM++WXX5iYwtc/LOqHAAIIIIAAAggggAACCCCAAAII5JGADcZ01113ucFSihUrlke5kg0Cly/w9NNP64MPPpANTMaCAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJ5J7Bjxw517txZu3fv1meffaabbrop7zInJwQQQAABBBBAIEgFFi9erNtuu00vvfSSnnrqqSBVyLtm2zXrhAkTNH78eG3fvl133HGH+vfvr3vuuee8E1TmXenkhAACCCCAAAIIIIAAAggggAACCCCAAAIFITBk8QRNWT9faZ70giieMhFAwAcE1veMV2xUCR+oSd5WwePx6Msvv9TkyZP14YcfKjU11fXdsQmprQ9PoUKF8rZAcsszgW3btmnq1Knus1u7dq2uu+46N1arfXbXXHNNnpXjSxkNWzpNo1YlKNWTdUJPX6ojdUEAgeAUiI6MUnLcxIBvvE3MmpCQIJu8ddGiRe66oWHDhurYsaM6dOigFi1aKDw8POAdaOClCVg/G/uZmT59uqwP0/Hjx1WjRg33M2M/N23atFHRokUvLVOORgABBBBAAAEEEEAAgYATsKnC58yZo5EjR+rzzz9XhQoVXB/9fv36qVy5cgHXXhqEAAIIIIAAAggggAACCCCAAAII+JOAPfe1PgHVq1d393DoV+hPn55v1fXQoUNq1qyZYmJi9NVXX6lw4cIXrKD1Uxk+fLiWLFmitm3b6sknn1T79u0veI7tfPzxx13/ytWrV7t7jRc9gQOCSsDuR2/cuFE//PBDRli2bJn279/vHOx3nfWJuvHGG9WgQQMXatasqdDQ0KBy8ofGWj/4w4cPu2C/Xyxt8fnS3mO9sR1nfedzWqw/U4kSJVS8eHEXvGlvbNu9aYujo6MzjvWeY9vCwsJyyp5tCCCAAAIIIIAAAggggECuBJKSktz7o/Y+hn1vtTFl7f3Rbt26ue8gucqEgxBAAAEEEEAAAQTOEbD7xHv37nVhz549bm6kzHHm9IED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XbtcyJw+depURlUKFSrknjlUrFjRxfb8wULmdUvbvf6QkJCM80jkvUDfeSM0c0ti3mccJDme3rBXOvsjGl6rbJC0OHCaWTumkhLvfTVwGlQALbF3ssaOHSubO8X+Btj3ffu97a9LWlqaG3PE4gceeEADBgxwY0f4a3t8od7TNy7SgIWjz1bljC9UhzogkKNAWEiofn/DrzW0cY8s+0POdm7hJzcLCSsIIIAAAggggAACCCCAAAIIIIDA1RGwB5DlypVzE50z2fnVMacUBBBAAAEEEEAAAQQQQAABBBBAwBcErPNekSJFNGXKFPXokbUzjy/UjzoEl4D9PFqn2L/97W9uwoLgaj2tRQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQKDgBGxyn27duqlx48b64IMPZBOnsCCAAAIIIIAAAgjkLHD48GE1atRI1113nRISEhi8NWcmt9Xj8WjOnDkaM2aMm/y0RIkSeuihh9SvXz/nd4FT2YUAAggggAACCCCAAAIIIIAAAggggAACQSQwZPEETVk/X2me9CBqNU1FAIHMAut7xis2qkTmTUGdPnr0qN5//303IfX8+fNlz1hsPIjevXvrlltuCWqbS238kSNHsliWKlUqw7J58+aXml1AHz9s6TSNWpWgVE9aQLeTxiGAgP8JREdGKTluov9VvABqbGP3fP3115o1a5br07Jy5UpFRUXptttuU8eOHdWhQwf6axTA55JTkceOHZNd53k/q59//tld87Vt29Z9TvZ5VapUKadT83zb3r17lZSUpMTERBe+++47HTx4UDb5dcOGDd2kok2bNnVxrVq18rx8MkQAAQQQQAABBBBAAIHLE1i4cKFef/11ffTRR7L7no8++qgGDBigypUrX16GnIUAAggggAACCCCAAAIIIIAAAgggcFUEJk+erD59+ig+Pl79+/e/KmVSSGAKHDp0yI0ZV61aNdkYcmFhYa6hq1ev1nPPPefuHbZq1Ur/7//9P7Vu3fqyEaxPa7169Vy/gXfeeeey8+FEBPJbwMYD2rx5s7Zv364dO3bkGKekpGSpRrFixVS2bFl3n936a9s8ftZnxv4/paeny/pjpaam6sSJE7I+yVaG/d+zvjU2pk7mJTo62s27ZnOvlSlT5qJpO65o0aKZsyCNAAIIIIAAAggggAACCCDg4wL2PfGzzz5z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TYEcE5BnKuRZOTnHzEqW79TUydraGnL+qJ+dnJx003IeamNjo7I2Ls9VyPOITBSgAAUoQAEKUIACFKAABShAgYIiIOfCr776qspy31di13h5ean81Vdfqeu57dq1Q/fu3VV2cHAoKDT59jivXr2qXt/ff/8d3t7eqr8qics0depU9RrXr18/3x67XAOSLNfp00oSk0Pib/j7+8PPz08NZVzy0aNHcefOHURHR6tVpZ2D9BMvfX1JrlSp0jPjcl1q7ty5KpaHdk9A2+/p06dx4MABFV9EK0trKPcHRo4cqWJmffzxx2kt8kzZoUOHVPuMwYMHPzOPBRSgAAWMXeD69etYsmQJvv/+e5ibm2PChAmYPHkyypYta+yHlqX6y/2OpUuXYtasWfjmm2+wYsUKNS2f/TNmzFBxFrO0QS5MAQpQoAAJSNsdNzc3lRctWoQTJ07g559/VnnBggXqt/2AAQMguVmzZpDf/kwUoAAFKEABClAgLwVcXV0hefHixerarrTHHj16NCZOnKh+s8j1w1deeYW/W/LyReK+KUABClAgXQF5jliehZH7bOnlwMBASBtySUWLFkWFChVUfytyb1qeoZGhluW+nLT/ZqIABXJO4MKFC+p3ZkZ7KFKkSEazOY8CFKAABShAASMQMDWCOrKKFKAABShAAQpQgAIUoAAFKEABClDAIAWkw3EJqshGxgb58rBSFKAABShAAQpQgAIUoAAFKEABClAgTwUkUJUkafjORIGcFti/f78KTMaHLHJamtunAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoUHAFpEMueYZGggVKYMHLly/D19cXGzZsgHSqJfOckjtPadq0KZo0aaLuYzdq1Eh1EJabaseOHUNsbCx69OiR7m6trKxQuXJlrFy5Em+99Zbq8EZb2NPTE61bt0a9evVUkXQqJh0VFLT09ttvF7RDNqjjPXjPB5fC/WGa3JlGfFLic+s2sIob2lWop/4OP3EdinVXD8AnzO+56+XkAptueON+dHhO7sIot21tbY3p06dj2rRp2L59O5YtW4bOnTujdu3amDJlCkaMGKE6CzXKgzPASj9+/Bjr16/H8uXLceXKFUgnrFu3bkXPnj0hndVkR5LgudLZp+Rhw4bpNhkREQEfHx+VJbDnxYsXVUc4oaGhapkyZcrA2dlZ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12CnNda/lZ1jO61q9t/Rdlz6kuwBAQQquoCHh4d4eXk5gqenp8u01rHLNPb29jZBv9PnTNt5OWOtlzNP1319fSUqKkq+++47+eabb+Tw4cNy6aWXyqBBgyQiIkLCw8Mr+sfA8SGAAAIIIIAAAi4FbrzxRvM32KJFi1yWk4kAAggggAACFVvAs2IfHkeHAAIIIIAAAggggAACCCCAAAIIFF9gzZo15uG+a665pviNsCUCCCCAAAIIIIAAAggggAACCCCAwEUjEBkZKVWqVDEvNl00B82BnheBY8eOyfbt22XKlCnnZX/sBAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEChNgcDAQGnatKkJ+e0nMTFR9DnKnOH48eMmb9euXSbWdZ34xV50YgR9rrdatWqOUL16dUda853X/f397U2JEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAoAABnQQ9ISFB9Dq+xnYoifUzZ86Yidvz64JOThwQECB6v0FDznTVqlVz5buql3N7d3f3/HZLGQIIIIAAAggggAACCCCAAAIInGeBrKwsiYuLk+joaBNiYmIcac3La/306dMSGhpa6N56ep6d1rdrn56yrk1Wobc7l4qnUhNk7fGdRWoiOjleNBR3yZIsSUz/37OW2k5aZka25jKyMuVIYozJS8+yyorAcSghKltbxVk5mhhb4GYpGWny0Kp35R/t+snDq9+3upi9k7vjjhbYhlaITDh7nIWqXMxK3xxcX6gtX+44VKZsWiDrTu4qVH0qIYAAAggggAACCCCAAAIIIIAAAggggAAC50PgyJEj8uWXX8rHH38sOs97WFiY9O/fX95++23p3LlzobuwceNGeemll2TixInSuHHjQm9X2IoLFiyQPn36FLa6y3p//PGHtGzZ0mUZmWcFct5TwKV0BdT7dGpi6e6E1su9QO/eveWmm26STz75RCZPniwdOnSQbt26yaOPPio333yzuLm5lcgxBgUFlUg7F0MjP/30k0yaNEkWLVokTZo0kalTp8qwYcPEz8/vYjh8jhGBQgnEpSUVqh6VELgYBUJCQuThhx82Yfny5ea71wMPPCBjx4415xNNN2/e/GKk4ZgvoEBycrLj3UH7HUL7vUJ9l9BVSEpKEjtouZ12jrVdXbfj1NTUIh2ll5eXmTvcx8dHvL29swU7T+to0HLntL6LqM8Map5zrGk76DiGmnaO9d1DXbdjTevf3LquwU5rbAc9KE07x2aFfxBAoEwL2P8P5xVr5/Mqs/Odf1fo74ucvz/sPOfY1Tb27yX791hZgrvttttEn/VetWqVzJ07V1599VWZMGGC3HDDDRIREWGu2er74CwIIIAAAggggMDFIHD48GFZunSpfPrppxfD4XKMCCCAAAIIIOBC4Oyb6i4KyEIAAQQQQAABBBBAAAEEEEAAAQQudgF9KLZRo0ZSu3bti52C40cAAQQQQAABBBBAAAEEEEAAAQQQKIRAZGSkhIeHF6ImVRAomsCyZcvMy8VdunQp2obURgABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBMqxgL+/vzRs2NCEgg4jNjZWjh07ZsKJEydEw/Hjx02s6V9++cWxfubMmWzN6WQw1apVk6pVq5pQpUqVfGOdHFEnd2BBAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQKCsCaSkpJiJ23UCdnuCdlcTuReUl9dE8JqvE7vnt+iEx3qNX6+/27Gm7aB5oaGhLstz1rfXAwMDTX2NtX0WBBBAAAEEEEAAAQQQQAABBBAoXwJ6TSEmJkaio6NzxXnl6XOB6enp2Q7Uy8vLXFfQawthYWEm6DigrVq1cqxr/syZM2Xx4sWSmZmZbXt7xd3dXbKyskxbDz30kIwcOVLWJR+Uwd9PtqsQl2GBNcd3iLeHl7zYcbA89et8ybL+K8zi5+kjnm4eEmDFCekphdmk1Os83Kq3bIzeJwsPrCv1fbEDBBBAAAEEEEAAAQQQQAABBBBAAAEEEECgIIH9+/fL559/Ll988YWsXbtW9Jm93r17yz/+8Q/p2bOnmT+poDacy9PS0mTYsGFy5ZVXil6PL+llz549snXrVnn99dfPqWlt4+abbz6nNtgYAQQQuBACnp6eMnjwYBN++OEHmTx5svTp00cuueQSefTRR2XIkCHi6+t7Ibp20ewzIyND/vvf/8qkSZNk/fr1ovMM6nm0b9++jE1z0fwUcKAIIIBAyQt069ZNNOjYabNmzZL33nvPfO+55pprzHNOt99+u3h7e5f8jmmx3AqkpqaKjq2nIT4+3pHOuZ6zTJ/ryyvo+4f6t05+i4+Pj3l/UN8BtIOfn59o0HWNQ0JCpFatWmZd/za1y+20HWtbmraD87qmnQNjAOb3qVCGAAIInD8B/X3ctWtXE9544w1ZsGCBzJ07VwYNGmSuLQ8YMEAiIiKkc+fO569T7AkBBBBAAAEEELgAAvPmzTPvqOm9dRYEEEAAAQQQuDgFPC/Ow+aoEUAAAQQQQAABBBBAAAEEEEAAgYIFli9fbh6KLbgmNRBAAAEEEEAAAQQQQAABBBBAAAEEEBCJjIwUnXiCBYGSFli2bJl06NDBvPhW0m3THgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIFARBHSCGQ3Nmzcv8HCSkpLMxErHjx83sU6ypOHkyZMSFRVl0tu2bTNpzdMJdJwXnYCxSpUqjhAaGiphYWHZgp3nHHt5eTk3QxoBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDgIhBIS0sTvS6dnJxsYk3bQSdht9N5xVrH3lbT9jZ5xZmZmfmq6mS+Ogm7PaF7QECAI23naazXwZ3XNW3X1dg52GV2ubbPggACCCCAAAIIIIAAAggggAACFVNAr1PExMRIdHR0ttg5z04710lJSckGotcogoODzXN3zs/ZNWjQIFue/WyeXady5crZ2slrZdWqVfL9999Lampqtir6/F96erpceumlMm7cOBkwYIB4e3ufrXPwYLa6rJRtgeVHtsi22IPi6e4uaZkZBXa2f8PO0iO8lejP3nMdBsncnT/KlpgDBW5X2hU+2bNKjibGlvZuaB8BBBBAAAEEEEAAAQQQQAABBBBAAAEEEHApoM8c/vLLL7Jo0SL55ptvZNOmTead/T59+sgTTzwhPXv2FB8fH5fbFibz+eeflz179ph29fpsSS9fffWV6e/VV19d7KYzMjJk586d8vjjjxe7DTZEAAEEyoLAtddeKxp0vJRJkybJmDFj5MknnzTxqFGjzH3YstDPitIHHYtm5syZMnXqVDl8+LDcdttt8uabb0rHjh0ryiFyHAgggAACZUCgWrVq5rvZ+PHjZcmSJfL222/LkCFDRJ+lGj58uDzwwAOiz1uxlE8Bfbbt9OnTEhcXJ/Hx8dliV3mu6ujfJBpyPifnLKLPxwUGBkqlSpVMbKft9wP158xOFza23zt0t57dYkEAAQQQQEAF/Pz8ZODAgSYcO3ZM5s+fL/PmzZN3331XGjVqJEOHDpWIiAj+duHHBQEEEEAAAQQqpMDs2bNl8ODB/3tHrUIeJQeFAAIIIIAAAvkJeOZXSBkCCCCAAAIIIIAAAggggAACCCBwsQroxAu//vqreeD1YjXguBFAAAEEEEAAAQQQQAABBBBAAAEEiiYQGRkpDRs2LNpG1EagEAI//vijefmtEFWpggACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCBQgIBOTlCvXj0TCqhqipOTkyUqKkpOnjyZK46OjhYNe/fulfXr15u0rickJORqunLlymbiprCwMDORbUhIiCMODg52pO18O09jDw+PXO2RgQACCCCAAAIIIIAAAggggAACCCCAAAIIIFA4AZ1APSUlRfR677kGHa9Ug7bjHDun7TLNy8zMLLCTbm5u4uvraybX1cnX7bRez9Z1jTXoJO/Vq1d35Nll9oTtzvWd8+x8u77ujwUBBBBAAAEEEEAAAQQQQAABBC5uAb1+ERMTky3os292np22Y83XtF7vyLnYz8aFhoaKPh+nccuWLR1pO8851ufk3N3dczZVYuu6L+fFy8tL0tPTpXfv3vLII49Ily5dnItJl1OBE0mnC93z7w79JksO/+6on5KR5khfyMTRxNgLuXv2jQACCCCAAAIIIIAAAggggAACCCCAAAIXoYC+M//999/L4sWL5dtvvzXvzzdo0EBuueUWmThxonTr1k30uvq5Lhs2bJBXX31Vpk6dKo0aNTrX5lxu/+WXX5pr/56eni7LC5Opc47pc6al1cfC9IE6CCCAQEkKtGjRQmbNmiUvv/yyvPHGGzJt2jR55ZVX5J577pGxY8dK48aNS3J3F11bR44ckenTp8s777wjaWlpMnz4cOOq51IWBBBAAAEESktA3wW74YYbTNDvMO+//76899575jvcTTfdJKNHj5Ybb7xReGestD6B3O3qc3SnTp0y4fTp02KHuLg4R1rznNdzpvUZPleLfsfV9wg16LN5dqzp8PDwbHmBgYGmXGM7aH3ndEl8x3fVT/IQQAABBBDIS6BGjRry6KOPmrBx40aZN2+evPXWW/Lcc8+ZZ7gjIiKkf//+EhQUlFcT5COAAAIIIIAAAuVGYOXKlbJ7925zH6bcdJqOIoAAAggggECJCxT/6bUS7woN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ThyhwcPDw5HOK0/rqKdOdKmxHfJaz/cgKUQAAQQQQAABBBBAAAEEEEAAAQQuAoGsrCzHURaU1vKihszMTLONxsVJZ2RkmO10W+d0znXnMk0XNWh76enpZjuNndPalqt15zx7GzvW62t22o7zy9MyO2j9klj02poGvfaWM23n2dflcsb+/v7mmmXOfLs9+9pecWLdxg7aHgsCCCCAAAIIIIAAAggggAACCCBQ3gX0+tGpU6ckJibGEWJjYx1pO99VXs5rQfazZKGhoeIcWrVqlW3ducxO6zNVF/uiYziuX7/+Ymfg+BFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQuAgF9T3zt2rWyZs0aWb16tfz666+SkpIiDRo0kGuuuUbuuece6d69u9StW/e8aKxbt05ee+01+fe//236UFo7/eSTT6R3796i762fy3LgwAFjo/dmWBBAAIGKLKBjcPTr18+EVatWyaRJk6R///7md/XYsWPN+ULfH2AR807Hxx9/LJMnT5bNmzdLjx495Ntvv5WbbroJHgQQQAABBMqkQLNmzWT69OnyyiuvyIcffigzZsyQjh07Svv27WX06NEycODAc/7uVFoHru+p6lhp+mydq+fq7GfuXMWpqam5uqV/z4SEhGQLVatWlaZNm5r3JHOW2es67htzBufiJAMBBBBAAIFSEQgKCpIRI0aYsHfvXpk3b5588MEHMmXKFGndurVERETIoEGDpEaNGqWyfxpFAAEEEEAAAQRKSuCzzz4TvT4xePDgkmqSdhBAAAEEEECgnArwZn85/eDoNgIIIIAAAggggAACCCCAAAIIlJ6AvuCoLzred999pbcTWkYAAQQQQAABBBBAAAEEEEAAAQQQqFACR44cMcdTq1atCnVcHMyFF1i2bJkMGzbswneEHiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIlAsBnSRXQ/Xq1Uukv2lpaZKYmCgJCQmOkHM9KSlJkpOTRWPntJ2ncXx8vJw8edJRru/16wDJzsE5LyMjo0T6TyMIIIAAAggggAACCCCAAAIIIIAAAgiogJubm3h4eDiCu7u7I+2cb6c9PT1Fg667il3laV0vLy/RScrt7e1Y8+20HefM03U7z04Xdt3b21s02Ns5p/kJQAABBBBAAAEEEEAA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b7doJ41AAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQcE+BjIwMSU5Ovqyg3wc4HpOenu4Ux9/fX4KCgiQ4ONhp/Efb9HgWBBBAoLIK7N27V5YvX27Chg0bRL9b6tChgwwcONCENm3a5KHRaXyrV68uiYmJJt/T01N0LMiPP/5Yrrvuujz7slI2AvoziYyMlEceeUSefvrpsjkpZ0EAAQQQQACBCi+QmZkpx48flxMnTsjJkydNcEznz0tJScnTZv3OvWbNmk6DXi86htDQULPu5+eXpwxWEEAAAQQQQAABBBBAAAF3F8jJyZEuXbrI2bNn5ccffyyzscL0mf2dd94pv/zyi7Rq1apUmCMiImTSpEkyduzYIpWvz7J37Ngha9aska+//lp0XDUdP037GHr16mUPtWvXdlrezp07Zdy4cbJ27Vq5++67ZcqUKVK3bl2n+2rm22+/be55tY7ltei99V133SWbNm0y87Pp2HiffPKJDBkyxFRJ+1iGDRsm77zzTnlVkfMigAACCCCAAAJuLZCamipHjx6VuLg4E6y0FWv+qVOnRK9VddHrM70erVOnjgn6LoamrdhKMwawW/+zcdnG6T3UzJkzZenSpVK/fn1zLzZixAjh/VeX/ZFRMQQQQAABBBAoREDfQdJn2DExMaLvrteoUcM8R9Xnp+3atSvkKLIRQAABBBBAoKIKNGvWTLp37y5z586tqE2g3ggggAACCCBQygK2i511/9tbV8onongEEEAAAQQQQAABBBBAAAEEEEDAVQXOnTtnBlF99913ZejQoa5aTeqFAAIIIIAAAggggAACCCCAAAIIIOCiAtdee635OOcf//iHi9aQalUUgYMHD0qjRo1k/fr10rVr14pSbeqJAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALFFNBB22fNmiXz5s0TDw8Pefjhh80A2IVNIFTM05XL4Tp5b9u2bSU8PFxeeuklue++++SHH34wA9R36NBBNm/eXC714qQIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAAClUMgIyNDkpOTLzskJSXZj0lPT3eK5evra+Y7CAoKssfBwcH2tGO+Y9pxnypVqjgtm0wE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LvDjjz+avoEZM2aY8dn0/vnXX3+VXbt2yc6dO+3h6NGjhiI0NNT0H2gfgoZWrVqZce30HYjLWfS574YNG8x4ZeHh4ZdzaInue/78efH19ZVly5bJwIEDC5StY9hNmDBBHn30UTOeXZUqVQrs48oZFy5ckKioKFm6dKksWLBAhg4d6srVpW4IIIAAAgggcBkCmZmZomPu7t+/34QDBw7IwYMH7SEpKcmU5uHhIREREdKwYUOnITIy0ozXexmnZlcEEECgQgvk5OTIkiVL5IUXXhB9Z65Pnz7y17/+VXr06FGh20XlEUAAAQQQQAABZwLnzp2Tzz77TGJiYmTNmjXm+8c///nP5rlxly5dRL+NZEEAAQQQQACBvAKTJk2S999/3zxr1eerLAgggAACCCCAQFEFbBcHJ8ot6s7shwACCCCAAAIIIIAAAggggAACCLiLwNy5c+Vvf/ubJCYm8iKCu/xQaQcCCCCAAAIIIIAAAggggAACCCBQxgI6EFW3bt0kPj5eateuXcZn53TuItCiRQsZMGCATJ061V2aRDsQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQRcQEAHg1+8eLHopD47duyQnj17ysSJE6V///4uULuyrYIOfD9v3jyZM2eOHD9+XO666y4zoU+bNm3sFdm8ebNMmTJFPv/8c7n66qvliSeeMIMf+vj42PchUbjAokWLjNe3334rnTp1KnxHtiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIHBRQN93TktLyxPOnj2bZ123XyovNTXVbNfY2rew6dh0QnRfX18zUbq/v3+eOCAgIM96Ydt1P8d9/fz8GN+cf80IIIAAAgggUGEEjh49Kl9++aWsWrVKVq9eLfrNVcOGDaVfv34m6Pdneh3EgoArCej9QI0aNeTll1+WESNGuFLVqAsCCCCAQDkJ6DOg33//PU/Yu3evWdfrG130GdBVV10l/4+9+4Cvosr///8OSQiE3quEHiB0AVFQZCnBgooC0hUr4MJasIMiIOgqit21axBEEHdVXKlK0y9damihCUgVEAKBQPL3c37/yQYIkISUm9zXPB5nz5kzZ055XtbMvTNzTs2aNd174xZ7wf6usCGAAAIIIIAAAggggAACCCDg7wIJCQlatWqVm5POvkc3atRIq1evln3HPn36tGzutdq1a8vWVKpfv77q1avnQoUKFS6Z7rXXXnNzwNm9itatW19yfZdSgTkEBgZq8uTJuu22286o6sknn3Tz0r388st6+OGHzziWE3bsd5KbbrrJfc7//ve/3RprOaHf9BEBBBBAAAEE/idg90Q2b96smJiYM8KmTZu0fft2d91mpYsXL66qVau6Z0DsOZDkISwszF3b/a9WUggggAACnsD06dP1wgsv6Mcff9QVV1wh+x5o36PsvQM2BBBAAAEEEEAgtwns3r1b48ePV1RUlH799VfZ98VevXq59UXCw8Nz23AZDwIIIIAAAukSsHvll112me655x4NHz48XXVwEgIIIIAAAgj4r0DAXxMeJfrv8Bk5AggggAACCCCAAAIIIIAAAgj4q0CfPn1kDyXYQ5lsCCCAAAIIIIAAAggggAACCCCAAAIIpEfgyy+/VPfu3XXy5Ek3GVR66uAc/xbYv3+/SpcurW+++UY33nijf2MwegQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgQwRsAVf3nvvPdkCO/v27VPXrl01ePBgNWzYMEPqz8mVxMfHu4kNX3nlFa1cuVJt2rRRZGSkvvvuO82dO1fNmjXT448/rltuuUV58uTJyUPN0r4fOHDALRTVuXNnvf3221naNo0hgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCGSugD2De/ToUcXGxqYYp/aYlUserN7zbYGBgSpQoIAKFiyYFM7et2NeXqFChWQhefnkaTtmZQMCAs7XJPkIIIAAAggggECuEzjTM9OoAABAAElEQVRx4oTmz5+vH374QdOmTdOqVauUP39+tWrVSh06dHAhPDw8142bAeU+gZtuukm27PK3336b+wbHiBBAAAEEziuwY8cOrVu3LimsX7/epS3ftqCgIFWpUkU1atRQzZo1zwgVK1bkd6DzynIAAQQQQAABBBBAAAEEEEDA3wTsu/Tq1auTgt0viI6O1vHjxx1FWFiYGjVqpHr16qlu3boutu/a9uxGRm+//PKLu08xbNgwPfXUUxldfbrqCwkJ0YcffqhevXq58+336Pvvv18ff/yxPvroI/Xu3Ttd9WbnSb///rubX+/gwYPuPlFERER2doe2EUAAAQQQQOAC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a+3atS546T/++MOdV7RoUff9OiIiIul7tqUrVKhw3nrTcsDas3nDHn30UT333HNpOTXdZVesWKGrrrpK/fr189n1vWrWrKmNGzfqX//6l+677750jzW7Tvzqq6/Uo0cPFz744AMFBgZmV1doFwEEEEAAgRwhcOTIEW3atMn9/fdiuxaw9J49e9wY7O9pWFiYqlevrho1arjgpatUqaLg4OAcMVY6iQACCCCQsQL2XN/777+vl156yf3NuPPOO/XEE0/I/jawIYAAAggggAACuV0gISFBP/30k6KiomS/S9u7vvZeQ+/evXXTTTcpf/78uZ2A8SGAAAII5GKBZcuWuXvpCxYscPe3c/FQGRoCCCCAAAIIZKJAwF8PCSZmYv1UjQACCCCAAAIIIIAAAggggAACCPiUgD1IUKxYMY0ePVoDBgzwqb7RGQQQQAABBBBAAAEEEEAAAQQQQACBnCVgL+raBFWPP/54zuo4vfUJAXvZ+5tvvnGTuvlEh+gEAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAzwnYBPSvvPKKPvnkEzdpnt2fHDhwoMqWLetzfc3NHZozZ45efvllTZ061U38//DDD6tPnz5+O5GhvZdx5ZVXukWG5s+frzx58uTmj5+xIYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIJCjBWxpqri4OB07dswt6myxF2yRZy+dUnyx43aOV8biiy2DFRwcrNDQUBcKFCiQlLa8S9m3c73zAwMDc/TnRecRQAABBBBAAAEE/FNgzZo1mj59ugv2LpNdX9euXVvt27d34dprr3XXz/6pw6gR+H8CDRs21FVXXaW3334bEgQQQACBDBY4fPiwVq9eLbsmsdgL+/btcy2VKFFCERERLtSrV0916tRx1yqlS5fO4J5QHQIIIIAAAggggAACCCCAAAI5WyA+Pl4xMTGKjo5OCuvWrZOFo0ePusHZ92nvu3XyuFy5cpk2+JMnT6p58+bKmzevbN6woKCgTGvLq/jgwYNq0qSJKlWqpBkzZmRJm17bqY1nz56ttm3bqm7dulq5cmVqT/OZcp9++qnuvvtu9e/fX6+//roCAgJ8pm90BAEEEEAAgewUsOebN27cqA0bNrjY0ja/r8V79uxxXbPnje06pUaNGi5Ur149KW3r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QNt1Q6NGjdS6dWs3V/Dll1+umjVrKk+ePL7ReXqBAAIIIIAAAggggAACCCDgdwKnT5/WgQMHtG/fPtm6SBafHfbu3avdu3drz549OnjwoFs7yYMKDQ1VmTJlXChbtqzq1Kmjtm3bqly5cipfvnxSbGstpWf+L68dYt8SsN85evbsqcqVK7vfSzO7d++++67efPNNTZw4UU2aNMns5tJd/9ixY/XLL79o0aJFyps370Xrsf/v2P+3smvbvn27OnTooHr16mnChAmyOf7YEEAAAQRyr4A977BmzZpzwp9//ukGbddyERERsnsXvXv3dtd1tWvXVunSpXMvCiNDAAEEEEAAgRQF7DmGqKgoPfPMMxo5cqTuuOMODR8+XEOGDFGPHj34/piiGpkIIIAAAggg4G8ChQoV0p133umCvVdiv7PbNdR7772nsLAwdx+lV69est9X2BBAAAEE/FsgISFBn376qfr06cN3av/+p8DoEUAAAQQQyBSBoEyplUoRQAABBBBAAAEEEEAAAQQQQAABHxLYunWre+m1adOmPtQruoIAAggggAACCCCAAAIIIIAAAgggkFMFbII122xSNDYEUiuwYcMGNwnf1VdfndpTKIcAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAtgjMnj3bLTZgCw+cb2vQoIFiY2NliwENHDgwqdihQ4c0fvx4DRgwwE2it2rVKnefzBbXudStfv36uvLKK/XKK6+4xayuuOIKVa1a1U2A3KlTJ73wwguX2oTfn//NN99o8uTJmjZtmmxBMTYEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEslvA3lmaM2eOZs6cqVmzZik6OlohISHuXaN+/fqpTZs2atasGYu/Z/cHRfsIZKGArUFUuXJlffnll2rcuHEWtkxTCCDgiwJxcXFasWKFli5dqiVLlrh47dq1OnXqlIoVK+b+OxEZGamnnnrKpatXr66AgABfHAp9QgABBBBAAAEEEEAAAQQQyAUCx44d0x9//KEDBw4kxcnTyY/t37/frblteYmJiUmjt++tKqsFVAAAQABJREFUxYsXd2sjlSxZ0sXh4eG65pprZPN5WbA5wrx0wYIFk84l4T8Cw4cP16+//qrFixe7+yaZOXK7P2PzzQ0bNkxdunTJzKYuqW67h/T000+7uekaNmyYqrrs/0sbN25MVdmMLmT/bbDfrUqUKCGbAy5fvnwZ3QT1IYAAAghkk8Du3bu1Zs2ac4I9/2Bb6dKlFRER4eZ+7dOnj0vbvl0DsiGAAAIIIIAAAskFatSooU8//VRDhw7VyJEj1bdvX40YMUJDhgxRz549eW4yORZpBBBAAAEEEPBrgQoVKmjw4MEu2Do548aN02effaZRo0a532B69eql7t27u/trfg3F4BFAAAE/FbD3En/77Tf3vdpPCRg2AggggAACCGSiQFAm1k3VCCCAAAIIIIAAAggggAACCCCAgE8ILFq0yD2wyIRvPvFx0AkEEEAAAQQQQAABBBBAAAEEEEAgxwvs3bvXjaFUqVI5fiwMIOsE5s6dq9DQUPeiUNa1SksIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIXFzAJr/ztp07d7qFhGwRGm+LjY11SVukxhaose322293EwzbBHq2KPONN94oq2fy5Mn68MMPXZnHH39cNomeLRgUFRWl4OBgTZw40R2bP3++2rZtm1Sfy0zF/9x///268847NX36dFfa7tt26tRJCxcudAvopKIKipxH4MiRI3rggQfcZ9a+ffvzlCIbAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgcwVOHHihBYsWKBZs2bJFndfunSpEhMT1bBhQ/ce09ixY3X11Vcrf/78mdsRakcAAZ8WsPcc7Z3FF154waf7SecQQCBjBU6ePOneaV6yZIm8sGbNGsXHx6tIkSKyNcoiIyP19NNPu3mAq1WrlrEdoDYEEEAAAQQQQAABBBBAAIFcL2Bzah0+fFiHDh1ysaWT7x88eNAds9gLVtZL23fX5FtAQID7zmrzd1koXry4i8PCwlSyZEkXbC6t5MHKBQYGJq+GNAJnCMybN0+jRo3SG2+8oYiIiDOOZfTOhg0b1KVLF3Xu3FnPPPNMRlefYfXZ70O9e/dWgwYN9OSTT6a63jJlymj37t2pLp9RBY8fP64bbrjBzeM3e/Zs99+JjKqbehBAAAEEsk7A1rS0+xRnhz/++MN1wq737G+1/X3q0aOHS9u+5bMhgAACCCCAAAJpEahevbo++eQTDR06VCNHjtTdd9+t559/Xs8++6y6deumPHnypKU6yiKAAAIIIIAAArlaoF69enrxxRc1evRozZkzx62XY9dNtr6OrZNj9xNuueUWFShQIFc7MDgEEEAAgf8JfPzxx7rqqqsUHh7+v0xSCCCAAAIIIIBABgkEZVA9VIMAAggggAACCCCAAAIIIIAAAgj4rMDixYtVt25dhYaG+mwf6RgCCCCAAAIIIIAAAggggAACCCCAQM4R2Ldvn5tkrVixYjmn0/Q02wVs4rXmzZsrODg42/tCBxBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBILvD777/rnnvuUenSpTV9+nQ3+V2bNm1ckQ8//FBff/21Sw8YMECPPPKImjVrppCQEE2bNs1NivfYY4/Jgj23/9lnn6lQoUKufM+ePWV120R6RYsWdcdtImJb2CoxMVHbt2936eR9uVi6a9eu+vzzz9WuXbukov369XOLKTDBcRJJuhK2kLYtRvTqq6+m63xOQgABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCA9AgkJCVq2bJlmzZqlmTNnav78+YqLi1P16tXVtm1bPfroo/rb3/6m4sWLp6d6zkEAgVwqYO8rvvjii/q///s/N99nLh0mw0LArwVOnz6t6Oho2fpjS5YscfGKFSt08uRJ9z5zo0aN3DWCvefcpEkTd+0QEBDg12YMHgEEEEAAAQQQQAABBBDwNwGby8rmTTp27JhiY2N19OjRpHD2vh07cuSI/vzzz6Q4edqOHT582H3vPNvRvm/a3FpFihRx82nZmkUWypQpo/DwcJf28iy237Ntri2LbT8wMPDsKtlHIN0Chw4dUq9evXTDDTeof//+6a4nNSceOHBAN954o2rWrKmPP/44NadkW5nhw4e735KWL1+epv/PlS1bVnv27MnSftu9sR49emjTpk36+eefVa5cuSxtn8YQQAABBNIuYNeSq1evTgqrVq1y6b1797rK7LovIiLCBZs31UvbPK9sCCCAAAIIIIBARgpUq1bNfUcfMmSInnvuOfXp00cjR4506c6dO4vnJjJSm7oQQAABBBBAIKcL2Bo2rVu3duHtt9/WN998o3Hjxqlv3766//77deutt7p7LrY+D/fzcvqnTf8RQACB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weMcvHSBl19+WZs2bdLXX3996ZVRAwIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJ/CRw+fFhz5szRzJkzNWvWLK1du1Z58+aVzcl33333qU2bNmrWrJmCglhSlX8wCCCQfoFu3bpp2LBh7r83F3tfMf2tcCYCCFyqwB9//KHFixcnhUWLFmn37t3KkyePatWqpaZNm+rmm292cYMGDZQvX75LbZLzs0Hg5MmTshAfH58Un50+e98r7+Xb/qlTp1wdyePkaSubfN9LJ88/ffq0K2PHvLQXJ89Lnj77uO0nJCS48y2dfD8xMTEbhGkSAQQQQAABBBBA4GyBgICAM7KS718sndJxy/PyL5ZO6biXlzy27z3J9y3t5Xnx2XlevsVe8Mp4+14cGBjoynix5Xtpiy3Y77LJ8+w3We/Y2Wnb9/K8dHBwsMtLKbY8L1g7FmzfS3txSEhIUp6lrZ9sCCCQdgFbp33QoEF69NFH3X2WtNeQ+jOeffZZTZw4Ud9//70iIiJSf2I2lBwwYIBiY2P10Ucfpav1cuXKufN+//33TB/rt99+q4ceekijR4/W7bffnq7+chICCCCAQNoFDhw4oFWrVmnlypVasWKFVq9erTVr1ri/H3atXblyZdWtW9fdq7DYgt2/sGtbNgQQQAABBBBAIKcI2Pf3SZMmuesd+17fsWNH94zm8OHDFRkZmVOGQT8RQAABBBBAAIEsF6hWrZrs+snCwoULNW7cOL3++usaOnSoWrRooV69eqlr164qXrx4lveNBhFAAAEELl3A7gksWbJEL7300qVXRg0IIIAAAggggMB5BJgt4TwwZCOAAAIIIIAAAggggAACCCCAQO4QWLZsmXvBol69erljQIwCAQQQQAABBBBAAAEEEEAAAQQQQCDbBfbt26dSpUplez/oQM4RmD9/vho3bqzQ0NCc02l6igACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggkK0CJ06cyNT2t27d6iaws0a++uor1a5dWz179nSLdGVqw8kqr1OnjizkxM0WBbZFiHP6tmnTJtkE0MOGDZNNbMiGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQHoEjh8/rgULFmj27Nku2MLsCQkJatiwoa6//nqNGTNG11xzDXPypQeXcxBA4LwC4eHh7r8zEyZMUOvWrc9bjgP/H3v3AV91df9//H0TAmGTBTIFWRICYWaAIYOCoFSpOAoEcVK3RawbLbhbwFWLVLS2JGgZThCLlBFGBmGHqYhMhYQNISSE/Dnn90j+CRAgcJPcm7y+j8fH7z7jeWNu+J7vOQcBBMpOICsrS6tWrVJKSoqN5ORkbd261Rbg6quvVkhIiJ544gm77tq1q2rVqlV2hatAOWVnZ8tYFxemr3pxYe4p7pw5btLOX5vt/DjfscLXXgmvw+Gw88xVrVrVrqtUqXLOOv+Yl5eXzHb+fuG12a5WrVrBeU9PT7udvzbnz7d99jGzXzg8PDxKtG/uNfeUJIxB4etNGuZYfphz+dvnWxd33hzPX8x9ZslfF7edfz5/bW/iPwgggAACCCCAAAIIIIAAAuUqYMY8GzJkiK699lq98sorpVqWKVOm2LHJPvzwQ/Xp06dU87rSxE1Z4+PjNWvWLDVo0OCykvP19bXPE3755ZfLuv9Sb0pLS7Nj/d199916+umnL/U2rkMAAQQQKIGA+b7ctGmT1q5dWyT27NljU/H391fHjh0VHh6u+++/Xx06dFD79u1pqyiBMZcigAACCCCAgOsLBAcH68svv9SKFSv04osvql+/furZs6f9t35MTIzrV4ASIoAAAggggAAC5SgQGhoqE2+99Zbmzp2ruLg4jRo1So899pjtFxMbG6sBAwbI29u7HEtJ1ggggAACJRH45z//qRYtWigyMrIkt3EtAggggAACCCBQIgHHmYlN80p0BxcjgAACCCCAAAIIIIAAAggggAACbiRgGs4XLFiglStXulGpKSoCCCCAAAIIIIAAAggggAACCCCAgCsLmJd7g4KC9P7777tyMSmbCwmYAWP69u2rcePGuVCpKAoCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggICrCoSFhSk1NVW33nqrHUyue/fuTi+qmcw3MzOzSLp169YtMllskZPsWAEzMbIZ5G/8+PHauHGjPvroI91zzz1uq9O7d29lZGTYwaDNhMksCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCFyKQE5OjpKTkzV//nz973//U1JSkkyfpdatWysmJqYg/P39LyU5rkEAAQQuW+Avf/mL3nzzTf3666/y8vK67HS4EQEESi5w+vRp2982JSXF/l1g1uvWrdOpU6fk5+enkJAQG6avtNkOCAgoeSYufoeZGj4rK0snTpywfbePHz9ut81+cZF//YXOm2suFCWZkt78bqxWrVqx4e3tXeRc1apVlR/mvvzt/PX5jplzhY+bPE2Y45ey7enp6eKfNMVDAAEEEEAAAQQQQAABBBBAoHwFnnrqKf3973/XqlWrbFtMaZVm0aJFdo6lkSNH6o033iitbJyS7pYtW9S1a1eNGDHCjgt3JYm2aNFCDzzwgJ5++ukrSabYe81Yb+b5WNOmTTVv3jyeZRcrxQkEEEDg0gX27NmjtWvX2jBtE2bbjBFq3mUwz6avvfZambkD86NDhw5q1KjRpWfAlQgggAACCCCAQAURMO92vvjii/r+++8VFRWlsWPHKiIiooLUjmoggAACCCCAAAKlL3Ds2DF9/vnndp4a03+mZs2adi6h2NhYRUZGysPDo/QLQQ4IIIAAApclYNoMmjRpokceeUSjR4++rDS4CQEEEEAAAQQQuBQBx5nOfnmXciHXIIAAAggggAACCCCAAAIIIIAAAu4oYF4+NANMfvjhh+5YfMqMAAIIIIAAAggggAACCCCAAAIIIOCCAoGBgbr99tv15z//2QVLR5FcTeDgwYN2wPsvvvhCN998s6sVj/IggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgi4oICZVHnatGl2MpuVK1faQeOefPJJ3XjjjXI4HC5Y4opfpAMHDmjixIl67733ZNoABw8eLPOZBAUFuW3lP/nkE917771KTEy0kxK5bUUoOAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQKkL5ObmatWqVZo/f76NxYsXKzMzU82aNVN0dLRiYmLUu3dvNW7cuNTLQgYIIIBAYYHt27erRYsWmjVrlm644YbCp9hGAAEnC+zevVspKSk2kpOTlZqaqqNHj6p69erq0qWL7a8aEhJi19dcc42Tc7+85E6ePKnjx4/bMH+75G+b9YX2zTkTJ06csOv8/fx1/nGzvpTp4atVq2adjNXFwtvbW1cSJq/C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qeNSdaAwCCCAAAIIIIAAAiUTMOOpT5o0SWZOZvN3n3l28OSTT9r3VkqWElcjgAACCCCAAAIInC1g2n/i4uL02WefadeuXbYtYejQofr973+vRo0anX05+wgggAAClyHw4IMP2j6W69evv4y7uQUBBBBAAAEEECi5gONMx9O8kt/GHQgggAACCCCAAAIIIIAAAggggIBrC/z4449q3bq17dQXGhrq2oWldAgggAACCCCAAAIIIIAAAggggAACbiMwceJEjR49WhkZGW5TZgpafgL9+vWTr6+vpk6dWn6FIGcEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIFKJ2Dep58wYYI++eQTVa9eXWZQo0ceecROnF7pMApVeNGiRRo/frxmzZpl+xuMHDlSw4cPt0aFLnP65okTJ5SYmKiFCxfKlCE5OVlmEo3mzZsrMjJSUVFRdt2iRQun552fYG5urkJCQlSzZk1bBofDkX+KNQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgJsKHD582E6GbvqtLFiwQGvWrJGZnrRjx46Kjo4u6LdSr149N60hxUYAAQRKJjBgwAD7e3D27Nklu5GrK51Adna2Dh48qAMHDhSJ4o7lHz906JBMn83Ci+mzWbt2bfn4+Ngw37tm++z12cfMeRM1atQonFyx2+Y7fuPGjXY+rqSkJLtev369Tp8+raZNmyosLKwgunTpIm9v72LT4gQCCCCAAAIIIIAAAggggAACCCCAgGsKTJs2TXfccYe++OILDRw4sFQKmZCQoL59+9rx6d56661SycNZiebk5GjQoEFasmSJli5dqnbt2jkr6SLpZGVl2fHovvrqK910001FzpVkZ/78+erTp4/effddPfzwwyW5lWsRQAABlxA4cuSIVq9erVWrVhXEhg0bdOrUKTuWpXkXoXPnzgURFBSkatWquUTZKQQCCCCAAAIIIICA8wUyMzPtv3H/8pe/2MSfeuopPfbYY5f8rovzS0SKCCCAAAIIIIBAxREw7/+a+Wvi4uI0c+ZMHT16VDExMRo6dKhuueUW1alTp+JUlpoggAACZShg2n4bNmyoF154QaNGjSrDnMkKAQQQQAABBCqzgONM58+8ygxA3RFAAAEEEEAAAQQQQAABBBBAoGIKmM6OphHbdDapXr16xawktUIAAQQQQAABBBBAAAEEEEAAAQQQKHOBl19+WfHx8dq0aVOZ502G7iVgJgPw9fXVG2+8YQdMc6/SU1oEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIGKILB//369//77Ng4fPmzfsTcDGwUGBlaE6l1SHUy73YwZMzRu3Dilpqbquuuus4M7mcl9PDw8LikNZ19kBpoyE3svXLjQDuhnts2xZs2aKTIyUr169VJERITatm3rtKzHjx+v559/3k7mce211zotXRJCAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBAoOwEz/8bixYttv5QFCxZo1apVMtORBgUFKTo6WlFRUbZ/ihkHjwUBBBCojAKfffaZhg0bpt27d6t+/fqVkaDS1fnUqVM6cOCADdOv1oTZL7xd+Hz+9rFjx86x8vb2tmPJmu/RwuHj46P8MMfzt83a7NerV0+enp7npHelB0xZk5OTbZ9U0xfVbJv+wmYurm7duiksLKwgGjVqdKXZcT8CCCCAAAIIIIAAAggggAACCCCAQDkLbN++XcHBwXa8ODN+XGksGzduVM+ePRUTEyMz/3t5jcV2KXUzY8gNGTJE3377rb7//nv7LOxS7rvca/z8/GTmJnvooYcuK4m9e/eqU6dOdgw5Y8uCAAIIuLrAoUOHtHLlSq1YsaJg/eOPP9p3EPz9/e3vtM6dOys/2rRp49LfG67uTfkQQAABBBBAAAF3FjDvq5gx3t9++23VqlXLjnU+YsQIVa1a1Z2rRdkRQAABBBBAAAGXETh58qRmzZql+Ph42y7icDhk5tQZOnSo+vfvLy8vL5cpKwVBAAEEXF1g6tSpGj58uHbt2qUGDRq4enEpHwIIIIAAAghUEAHHmQEf8ipIXagGAggggAACCCCAAAIIIIAAAgggUCDw9NNPa86cOVq7dm3BMTYQQAABBBBAAAEEEEAAAQQQQAABBBC4UoHHH39cqampWrp06ZUmxf0VXMBMxtOlSxf7jLJDhw4VvLZUDwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwJUFsrKyNGXKFE2YMEGbN2+2A8Q9+eSTio6OduViX1HZzMTfkydPtgMSmwGdfve738nUOTQ09IrSLY2bzWB+ZtLvhQsXatGiRXYi8MzMTDu5/XXXXWcnEjJrM/HG5UxA/vPPP6t9+/Yy/SxefPHF0qgCaSKAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCJSCwNGjR7VkyRLb72TBggVauXKlTp8+rcDAQNs3KCoqSpGRkfL39y+F3EkSAQQQcD+BEydOqEGDBnrllVf02GOPuV8FKnmJTd/K/fv3KyMjw67P3jb7Z8eRI0d09tTcNWrUkJ+fn3x9fQvWZ2+b/bPD29u73D6B3NxcpaWlKTEx0fYzTUpK0pYtW2zdWrVqpbCwsIIIDg5WlSpVyq2sZIwAAggggAACCCCAAAIIIIAAAggg4HwB83yoV69eMs+7li9frtJ4VrVnzx6Fh4eradOmmjdvXqnk4SwZ0x52zz33aNq0aXaOetMeVtqLee42YMAAvfrqqyXOyjyj7Nevn7Zu3Wrb8+rUqVPiNLgBAQQQKE0B075i3jcwsWLFChs//fSTzfKqq65S165dC8KMe2m+K1gQQAABBBBAAAEEEDhbID09Xa+//romTpxo39F66aWXdOedd17WuOlnp80+AggggAACCCCAwP8JHDp0SNOnT1d8fLwSEhLk4+Oj2267TbGxserZs6ccDgdUCCCAAAIXEOjTp49Mn5KvvvrqAldxCgEEEEAAAQQQcK6A48wLZHnOTZLUEEAAAQQQQAABBBBAAAEEEEAAgfIX+M1vfmM7mPzzn/8s/8JQAgQQQAABBBBAAAEEEEAAAQQQQACBCiNgOkiYSVh44bfCfKSlVpH33ntPL774og4cOECHmlJTJmEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIGSCJihBWbNmqVx48bZgeK6dOmiUaNG6fbbb68wE02byY3effddTZo0STk5OXbyoJEjR6pFixYloSrXa025zcQcixcvtrF06VI7IXqtWrXsxE0REREyERoaqurVq1+0rP3799f27du1evVqVa1a9aLXcwECCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggED5CBw/flxLlizRwoULtWDBAq1YsUKnTp1Su3btFB0draioKBsBAQHlU0By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y8uIuxBAAAEEEEAAAQQQQAABBBBAAAEEKp2Aee5knl+++OKLev75551e/08++UR33323/va3v+nhhx92evrOSnDNmjW68cYb5ePjozlz5qhJkybOSrrE6aSlpalDhw5av369HcPtQglkZ2erc+fOatSokb7//vsLXco5BBBAoIiAeZ9g48aNSk5OVkpKig3z++fUqVP2d2HXrl3VrVs3mTEhzbpZs2ZF7mcHAQQQQAABBBBAAAF3ETh9+rQ+/fRTjR49Wnv27LHPJ5577jn7bpO71IFyIoAAAggggAAC7iqwa9cu+7dYXFyc1q5da58zDhkyRLGxsTLvQLMggAACFU3A9MMx82/99a9/1UMPPVTRqkd9EEAAAQQQQMANBBx5ZxY3KCdFRAABBBBAAAEEEEAAAQQQQAABBC5ZwAxk9tJLL+mxxx675Hu4EAEEEEAAAQQQQAABBBBAAAEEEEAAgYsJ7N692w4ytXTpUvXo0eNil3O+EguYDjADBgzQm2++WYkVqDoCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCJSHgJlI6r333tOGDRsuOJHSiRMnlJqaaic+NxOQmDADAZqJ5QMDAxUaGmrDTFBl9j09PcujOuSJAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCLiMQEZGhhISErRo0SIb69atk5kONCgoSJGRkQUREBDgMmWmIAgggEBlFRg3bpxee+01/fLLL6pWrZrTGI4fP6709PSC2Ldv33m3868x1xdevL29Zb4nCkf9+vWL7Bc+V7du3cK3s12KAuZnJTExsSBWrFihrKws+fr6yvS1NBEeHq6QkBDVqVOnFEtC0ggggAACCCCAAAIIIIAAAggggAACFVng2LFj6tq1qxo3bqx58+bZcb+cWd/Zs2dr4MCBevrpp/XKK684M2mnpjVnzhzdcccd6t69uz7//HOV97PQAwcOyM/PT3PnzlWfPn0uWNcXXnhB77zzjkxbYfPmzS94LScRQKByC+zYsUMpKSkFYcZ/NG1HNWrUUJcuXWybg/k92K1bN7Vq1apyY1F7BBBAAAEEEEAAgQopkJ2drUmTJtlnFOY9nD/96U8aOXKkatasWWx9c3Jy9N133+m3v/1tsddwAgEEEEAAAQQQ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JWDGDl61apUSExNtJCUl2X6QZjzh9u3bKzw8vCDatGkjh8PhXhWktAgggAACCCCAAAIIIIAAAggggAACbilg5ls343Lde++9euutt5xah59//lk9evSQed713Xffydvb26npOyMx8+w2NjZW3377rT7++GMNGTLEGck6NY1evXqpQ4cOev/998+b7qeffqqhQ4fKPHMMCQk57zUcRACBiilgfs+a//fzw7RDZGdny8fHR2bcRfM7IT8aNGhQMRGoFQIIIIAAAggggAACThTYsWOHXnzxRU2ZMsX+W/z1119X//799dRTT9nnJqdOnSrIberUqXY+8YIDbCCAAAIIIIAAAgg4TeDQoUOaMWOG4uLilJCQYJ953n777bZNx7Q98Z6106hJCAEESkngmWeekfl3o2nLMX1GWBBAAAEEEEAAgfIQcOSdWcojY/JEAAEEEEAAAQQQQAABBBBAAAEESkPgd7/7napUqaLp06eXRvKkiQACCCCAAAIIIIAAAggggAACCCBQiQX+/ve/66WXXrKTDVRiBqp+EQEzQVBQUJAmTZp0kSs5jQACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCDhHYNeuXQoMDNSjjz6qV1991TmJliCVjIwMLV++XKmpqQWxZ88eOxhg69at7aQo3bp1k4lOnTqpVq1aJUidSxFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAAB5whs3rxZixcvVkJCgo3t27erWrVqCgkJUWRkpI0ePXqoRo0azsmQVBBAAAEEzhE4cuSIHds1PT39gut9+/bZ8ydOnCiShvkdHRAQUBD169cv2DbHC+9Xr15dLVu21Pjx4/XAAw8USYediiNg+jMmJiYqKSnJrlesWKGsrCz5+voqLCxM4eHhNsz3fe3atStOxakJAggggAACCCCAAAIIIIAAAggggIDbCGRnZ8u0QeXl5dlnWFWrVnVa2c2zVDNfknn2tXDhQtWpU8dpaTsrIfMM76abbtLPP/+sL774QhEREc5K2qnp/P73v5f5rD7//PNz0j127Jjatm2rG264QR9++OE55zmAAAIVR+D48eN2bEXT7pAfe/fuVZUqVdSxY0fb9mDaH0y0atXKjrlYcWpPTRBAAAEEEEAAAQQQKFuB9evX67nnntPXX3+t0NBQrVy5Ujk5OUUK4enpqTlz5qhPnz5FjrODAAIIIIAAAggg4FyBnTt3aurUqYqLi1NaWppatGihIUOGKDY2Vtdee61zMyM1BBBAwAkCubm5atasme6++2698sorTkiRJBBAAAEEEEAAgcsTcJx5MTDv8m7lLgQQQAABBBBAAAEEEEAAAQQQQMD1BExj8X333afnn3/e9QpHiRBAAAEEEEAAAQQQQAABBBBAAAEE3Fpg7NixtuPCpk2b3LoeFL70BMzkAnXr1tXkyZM1bNiw0suIlBFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIFCAjfffLM2btyotWvXytvbu9CZ8ts0kz2lpqbayVPM2kRGRoY8PDzUunVrde7cWV26dLFrs+3n51d+hSVnBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQqHACp0+f1po1a7R48WIlJCTY9b59+1SjRg2Fh4crIiJCkZGRCgsLc5n+GBXuQ6BCCCBQ4QXMlMgHDx5Uenr6JUd2dnYRl1q1aikgIOCcqF+//jnHzHU1a9Yscv/FdoYMGaIdO3ZoyZIlF7uU824gYH5+Vq1apaSkJCUmJtown6/puxgUFGS/4813u/mub9OmjRwOhxvUiiIigAACCCCAAAIIIIAAAggggAACCFR0gZEjR9r5jFauXGnH4HJWfY8cOaKoqCgdPXrUPgNt0KCBs5J2WjorVqzQTTfdpDp16mjWrFlq2bKl09J2dkJPPPGEli1bZp8/np32s88+q0mTJmnLli3y9/c/+zT7CCDgpgKmrWvz5s32/3vT9mAiLS1Nubm5atSokX2fwLQ7mOjWrZuqV6/upjWl2AgggAACCCCAAAIIuLaAeQ/ovvvu0w8//KCcnJwihTXv/1SrVs0+++jatWuRc+wggAACCCCAAAIIlI6AmfsmLi5On376qXbt2mXnlhk2bJgGDx4sV2yPKh0FUkUAAVcX+Pbbb3XjjTfqxx9/dOl2aFd3pHwIIIAAAgggcOUCjjMvoORdeTKkgAACCCCAAAIIIIAAAggggAACCJS/wKFDh+Tj42M7ApqGGBYEEEAAAQQQQAABBBBAAAEEEEAAAQScKfD444/LDEjFhAHOVK1YaZnJhXr16qVt27apefPmFaty1AYBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBFxSYObMmbr11ls1f/58RUdHu2QZ8wu1fft2mcmvTJgJ3k3s2bPHnm7atKkdNLBz584y0aVLFzVp0iT/VtYIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAwAUFsrOztXz5cpkx4RISErR06VIdOXLEzmFx3XXXKSIiwo4VZ/oseHl5XTAtTiKAAAKVVSAnJ0cZGRlKT0+3UXjbHDt7f//+/crNzS3CVbduXQUEBFxS1K9fX97e3kXud/bOnDlzdMMNN2jr1q265pprnJ086ZWywO7du5WYmKikpCS7NmMDnzx5Un5+fgoNDVV4eLiNkJAQ1a5du5RLQ/IIIIAAAggggAACCCCAAAIIIIAAAgiUXOCbb77RTTfdpClTpig2NrbkCRRzh3lO1q9fP23evNm2i7Vo0aKYK8vv8PTp0zV8+HDbTjdt2jSZ58euvIwbN07vvvuuduzYUaSYZqy0Vq1a6dVXX9XIkSOLnGMHAQTcS+Dw4cO2zcG0O5hITk7WwYMHbXuVeZcgLCysIMz4iCwIIIAAAggggAACCCBQNgLr1q1TcHCw8vLyzpuhp6enfTcoJSVFrVu3Pu81HEQAAQQQQAABBBBwvsDp06e1aNEixcXFacaMGTp+/Lj69OmjYcOGaeDAgapRo4bzMyVFBBBA4BIFbrvtNu3bt8/+nrrEW7gMAQQQQAABBBAoFQHHmYfb53+6XSrZkSgCCCCAAAIIIIAAAggggAACCCBQegILFy5UdHS0zMBfjRo1Kr2MSBkBBBBAAAEEEEAAAQQQQAABBBBAoFIKDB061HZM+PLLLytl/an0xQXeeOMNvffee/YZ5cWv5goEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDgygTMBCbt2rWzE0B9/PHHV5ZYOd29d+9erVq1ysbKlSvt+qeffrIDPfv7+8tMxNK5c2c7+LMZALpNmzaqUqVKOZWWbBFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMBVBI4dO6bExEQtXrxYCQkJSk5OVlZWlho2bKhevXopIiLCroOCguRwOFyl2JQDAQQQKFMB0wctIyND6enpRdZnH8s/b64vvHh6esrPz0+mr1dAQEBBnL2ff84c9/LyKpxEuW+fOnVKTZs21YgRIzRmzJhyLw8FKF7g5MmTto+h+X43kZSUpJ07d8r8HJrv87CwMIWHh9swfQ1ZEEAAAQQQQAABBBBAAAEEEEAAAQQQcHWBXbt2qVOnTrrpppvkzHHCcnNzdeutt8rM527ayTp06OByFC+//LJeeuklPfzww3r77bftcz6XK+RZBYqPj9fdd98t86yycPvi/fffr7lz52rLli2qVq3aWXexiwACrizwww8/aNmyZQWxfv16O87hNddcY9sdTNuDCfO72tXauFzZlbIhgAACCCCAAAIIIOBsgb59+2rBggUy73oVt5hxya+66iotX77crou7juMIIIAAAggggAACpSNg+mx99dVXiouL03fffSdvb2/dcsstGjZsmGJiYuTh4VE6GZMqAgggcB6B/fv3q1GjRpo0aZLuuuuu81zBIQQQQAABBBBAoOwEHHlnlrLLjpwQQAABBBBAAAEEEEAAAQQQQACB0hN466239Prrr2vfvn2llwkpI4AAAggggAACCCCAAAIIIIAAAghUWoHrr7/eThgwefLkSmtAxS8sMGDAANWoUUPTpk278IWcRQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBJwg8MADD+iLL77Qxo0b5evr64QUXSOJI0eO2EniV61aVbA2dTSDP5uJlwIDA9WxY0cFBwfbtdkOCAhwjcJTCgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEECgVATMp+JIlS7R48WIlJCTYPgemr0HLli3Vq1cvRURE2LXZZ0EAAQQqosCJEyeUkZFxTqSnp8v8jjTrs8/n5OQUoTBjZpq+WP7+/jbyt836fNum35rD4SiShjvuPPXUU3as0G3btlWI+rjjZ3C+Mu/cuVOJiYk2kpKStHLlSmVnZ9ufzbCwMIWHh8usQ0JCVKtWrfMlwTEEEEAAAQQQQAABBBBAAAEEEEAAAQRcViA3N1dRUVH2+W1qaqqd08gZhc3Ly9O9996rzz77TN9//7169uzpjGSdlkZmZqbuuecezZw5U++++64efPBBp6Vd2gnNnz9f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gAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACzhTIzc217/0vWbLETvS9ePFi/frrr6pevbrCwsLUq1cvRUREKDw8XDVq1HBm1qSFAAIIFCtw7NgxHThwQPv377frwtvmWP7xs9fmd1rhpWrVqvLz8zsn/P397bH8tbkmf9vHx0cOh6NwMmw7WeCDDz6w44aa75s6deo4OfXKndzx48e1fPlyJSYmKikpyca+fftsf71OnTrZ73bz/W6+11u0aFG5sag9AggggAACCCCAAAIIIIAAAggggECFF9i7d6+Cg4MVFRWlzz77zGn1fffdd/X4449r0qRJGjFihNPSdUZCpkyPPvqoBgwYYOeXr1WrljOSLZc0vL299eGHH2rYsGE2Vq5cacd74xl+uXwcZFqJBTIzM5WcnCzzToEJ0wZx9OhR1atXz7Y39OjRQyZCQkLkzr9zKvFHTNURQAABBBBAAAEEyklg2tYl+nbHinLKvWTZZh/P0uEdv+rw9r06dCYO/rRHh7b9osyMwwUJdXtwoNrfFlWwzwYCxQlcd1U73deub3GnOe7CAunp6fr444+1YoV7/O4qjtI8d7ztttt0ww03yNPTs7jLOO5CAhPWfqW1+392oRJRFATOFWhQvZ7eDBt+7gkXOJKdna3Zs2drypQpdm1+9918882688471bdvX34XusBnRBEQcGcB024+Z84cbdmyxZ2rQdkRQAABBBBAoAIJOPLOLBWoPlQFAQQQQAABBBBAAAEEEEAAAQQqqcDq1avVuXNnrV+/XoGBgZVUgWojgAACCCCAAAIIIIAAAggggAACCJSWwKlTp+yg9TNnztQtt9xSWtmQrhsLDB8+XLt379a8efPcuBYUHQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEXF1g06ZNMhOuv/baa3aye1cvryuVLysrSxs3btSGDRts/xOzNrF161adPn3avhfQpk0b2y+lffv2BevWrVvbc65UF8qCAAIIIIAAAv+PvfuA76LI/z/+TqWEBEhASuhNeugtNAXsKIr0I4CV2E4P9U7v59lPUE89C9gFQVAPy6l/FJEivXcQOAWlSQ0BQkIICf98xttcEgKGFEh5LY9hZmdnZ2ee32+++/3uzu4igAACCCCAAAIIIIAAAggggAAC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IoCAE7Brb+xc+pdffqmrr74aFQQQQAABBBBAoEAK+BfIVtEoBBBAAAEEEEAAAQQQQAABBBBAIJsC69atcze0bNCgQTbXoBgCCCCAAAIIIIAAAggggAACCCCAAALnLhATE+Nu3HXua7JGURawCz7sgvWIiIii3E36hgACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAAC51ng9ttvdzevuv/++8/zltkcAjkX8G4GX61atRxXYjctT/+Am8w3w7eb23t5lk5KStKJEycyxF6e3XzfHnKTOdhDkX7v4Tc57gArIoAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACeSRgY6LtHngHDx7UgQMHXGzjp/38/FS+fHmFh4e7++OFhYWpRIkSZ9yqj4+PLHiTl/Ziy88qnTnP5n19fV1ZS2cVzrbcluUm2Pas716wury0xdmd9/f3d+tZnD5tdWTOO9tyz5MYAQQQQKBwC9x666166qmn9Nxzz6lUqVJ50pnExEStXr1ay5cv17Jly1zYtGmTu6apUqVKatOmjaKiotSuXTu1bdtWFSpUyJPtUgkCCCCAAAIIIIAAAggggAACCCCAAAII5L/AXXfdpX379unbb79155xyukW7f9K1116rzZs3a86cOe6eYzmtK7frLV26VP3795edn/z+++/VqVOn3FZZKNf/9NNP3Wti517tflqHDx8ulP2g0cVX4KefftLMmTP13Xffafbs2W6cgY0nuOSSSzRmzBj16NFDDRo0KL5A+dxzG89x5MgRHT161MVe2ubt3noWZyftlT158uQZWxwYGKigoCD37Dx7fp6F9PP2uqeft880m88cMufbvI29YEIAAQQQQACBcxew8Yfevvbc187ZGnZ/3fT36fXu12ux3d83czhTvpX79ddfTyvvrW/r2e/Fs0021tLrvxdn9V3DW2ZxdpZ733P4jnJm/euuu04rVqxQdHS07J7mNWrUOHNhliCAAAI5FLDfqF988YXGjRvnjj3YZ82oUaN0yy236KKLLsphrayWHwK277T9gYVFixZp7NixsmPa9rwLG7c6cuRINW7cOD82TZ0IIIBAgRQoW7as7HoBC3YMfeLEiXr//ff1j3/8Qy1atNCwYcM0ePBg9mcF8tWjUQj8T2D8+PGqXLmy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AQQQQKCwCdx9993avn27OwdoxwxzMqWkpCgqKkrTpk3T119/7c4V5qSe3KxjbRg9erT+9re/6aqrrtL48eMVGhqamyqLxLpJSUmaM2eOnn322bT+lClTxh0jTssggcB5Eti1a5fsnoTTp0/XzJkzdeDAAVWqVEk9e/bUG2+84WI7p1FQJvtciY2NlT3j79ChQy5Y2pu3ZZafVXz48GHZ+pkn+/uzZwWWK1dOZcuWTYurV6+eYT7zcm++VKlSmatkHgEEEEAAAQQQQKCYCti43ZCQEBfyiiA5OVlHjx51vxmzE1sZC/v379fWrVt15MgRN295lrYxx1lNPj4+su/GwcHBrv0Wp09bv2z+2LFjWa3u8qytFj766CNNmjTJjWH+4x//qJOVTpxxHRYggAACCCCAAAIIIFBcBezY+1/+8hcXli5dqgkTJrhzRw8//LB69erlriXo06ePOAZdXN8h9PtCCNi5pE8//VQvv/zyhdg820QAAQQQQAABBM5JwP+cSlMYAQQQQAABBBBAAAEEEEAAAQQQKEAC69atc61p3rx5AWoVTUEAAQQQQAABBBBAAAEEEEAAAQQQKGoCdgMam+ymMkwIeAILFy50D32wmwwxIYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAArkRiI+P18iRI3XjjTeqd+/euamKdRFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQOA8CuzcuVOLFi3S4sWLXbxy5UolJiaqQoUK6tChg6Kjo13crl07lSlT5jy2jE0hgAACCCCAQGaB5ORkbdq0Sba/XrFihQurV69WXFycdu3apT179qht27YaMmSIWrdurWbNmikwMDBzNcwjgAACCCCAAAIIIIAAAggggAACCCCAQDER+OCDD/T222/rs88+U61atXLU61OnTumWW27Rp59+qq+++kpdu3bNUT25WcmOfQ4dOlRz587V888/r3vvvTc31RWpde08rx0j7tWrV1q/SpcuLbs3HBMC+S2QkJCg77//Xt9++60LGzZsUMmSJd3nxJ///Gf3vmzevLl8fHzytSk2xiEmJkYHDx7MENuz+yz/TPHhw4dln3HpJz8/P/esP3veX7ly5Vza4vr162eYz7zcm/f3909fHWkEEEAAAQQQ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QAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQKHoCx48f19q1a7V69WqtWrXKhsjISLm4uKhRo0bq0KGDQkND7bply5by9PQsep2gRQgggAACCBRDgdjYWG3btk1bt27NEcw4bJYyZcrYsbhJkyYaN26cGjdurGbNmqlOnTpydXUthj0u2k0eMWKEHnroIc2bN0/33ntv0W4srUMAAQQQQAABBBBAAAEEEEAAAQQQQACB8wrMnz9fJixevFg1a9Y8b/6sDI899pimTp2q2bNn6/bbb89KLtR1WlqannrqKT399NPq3r27vv766wK1sVAbU8Ir27t3r51TDwkJybWn5pllF+N5ZSdPntS+ffu0f/9+mTYcOHDAxrPWR44cUWZmpm2Tj4+Pff3N36lpp/mbyIqbdfXq1UvdefodO3bo008/tcFcz2BeowEDBtj5+2uuucaeQ8n1BSURAQQQQAABBBBAAAEEECiBAoGBgTJzCedaMjIynLvM99zPP/9cn332mU0rM6mrPFpXd+4vapH3d/6kip6+urFOF03vdJvSMtK19fhBvb9zmZ5e95FSHdss+RNIyUjTvb+9peq+lfJXwJFrSJ0r1Ltac3vfwBPtb9H87T8q7Nj+fJe/GBlNPyb8MvNiVE2d+RDYtGmTndOqVatWPnKXrizmWl4TisNi5h2///57JScnF4fm0kYEziuwLDLM8fnggNwd18/n57NBURzfTCcX7v5VUaePn7e/ZEAgNwFzP+fgwYNtiIqK0rvvvmvve5gxY4aaNm2qMWPG6NZbb1WVKlVyK04aAkVGwJy/r1Spkj3vV2QaRUMQQAABBBBAAIECCrg4Jl/+uOqrgAXJjgACCCCAAAIIIIAAAggggAACCFwOAXODW+3atbV8+XJ17tz5cjSBYyKAAAIIIIAAAggggAACCCCAAAIIlCKBhQsX6pZbbrE3xZoHHrGUXoFZs2bpwQcflHkglpubW+mFoOcIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIJBvgRtuuEGbN29WWFiYvL29812OjAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIXCqB+Ph4rVu3TmvWrHGG3bt328PXqFFD7du3V4cOHWxo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AQQQQAABBBBAAAEEEEAAAQQuksAtt9yixMREezPURToE1RYDAfNja4sWLdLWrVuLQWtpIgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIXC4B82ObrVu3tg+S/+abby5XMzguAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQAkRSE9P17Zt27RmzRpn2Lhxo5KSkuTr62uvYW/Xrp2yQoMGDeTCQ+NLyKtPNxBAAAEELoaAGVv379+v3bt3a9euXdqxY4cz7Nu3T2lpafawwcHBMuNqVqhfv76N161bV56enhejadRZxAUeeeQRvf322zpw4IA8PDyKeGtpHgIIIIAAAggggAACCCCAAAIIIIAAAgicOHFCbdu2VUhIiL799lu5ubnliZKZmWmfmT5v3jx99NFHCg0NzTN/QXZmZGTo3//+tyZPnmznms0xWrZsWZAqyHsegbi4OG3evFmbNm2yISwszD5ryqSbpUqVKmratKkNM2bM0JQpU3TXXXcpKCgoR83vvPOO/TswzyxjKRwBc17mww8/tGHDhg3WfPDgwRo6dKiuuOIKrnEoHGZqQQABBBBAAAEEEECgVAjEx8crJiZGsbGxdn2+uMmffXF3d1elSpXsd0TzPTEgIMAZz227fPny2YsXuXjv3r3td98HH3xQd955p8qVK3feNg75brq+O7jhvPnIgEBJE2juH6JfQqeVtG4Vqf6Yebj58+dr1qxZCg8PV+fOne3825AhQ+Tl5VWk2lqcG2Pm2t5880299dZbOnr0qAYOHKgJEyboqquuYp6tkF/YRh9M0OHEP+aXC7lqqkOgUAWOjV4gV+4lLFTT0liZuX917ty5evfdd3XkyBH16NFDY8aM0Y033qgyZcqURhL6fAkEunXrpqpVq2rhwoWX4GgcAiV2EI4AAEAASURBVAEEEEAAAQQQuLgCLo6LDzMv7iGoHQEEEEAAAQQQQAABBBBAAAEEECgcgcjISFWrVk0//fSTunfvXjiVUgsCCCCAAAIIIIAAAggggAACCCCAAAJ5CPTv31+BgYH2ovU8srGrhAuYGxXMwz7MjXEsCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAwLkEpk6dqmnTptkHENWuXftc2UhHAAEEEEAA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DQQQQAABBBBAwKYFXM6/kFBu0zkkcwgggAACCCCAAAIIIIAAAggg4NQCr776qnz22WeiL8AwIIAAAggggAACCCCAAAIIIIAAAgggUFcC2jiLdoaljSqNGDGirpIlHRsQGDVqlGiD799//70N5IYsIIAAAggggAACCCCAAAJ1J6CdpR4+fFi0U+njx4+bxo61wWPLuF4racfS2iGKxpagDZhqI9TaiWvFoNfWDAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggMDVBFxcXMTV1dW0Aejp6SkeHh4XhMqm6TKVTb/ctAYNGoiXl5cJFcd1WsXPVxvXvDIggAACCCBwOQHta+mdd96RgwcPire39+UWY7oDCOTk5MjKlSslMTHRhPT0dHMuEx0dLfHx8RIXFyf9+/cXHx8fByit8xXh5MmTsmrVKklISDD7d9u2baLdwEdGRpp9q/t34MCB0rhxY+fDsZMSZ+RlS/SiaXaSW7LpbAJebh4yLWKYTI8a7mxFr9fynj59WjZu3ChJSUmSnJws69evN+0v6v2I0NBQiY2NtYawsDBxc3Or1/ySeP0LaFubmzZtMseLHjcafvnlF5OxLl26WI+XmJgY6dmzp7lPVf+5dowchH4xVbLycx2jMJQCgasIJA9/S0ICWl5lKWbXlEBWVpakpqZKSkqKNd6+fbsUFBSYOor27dtLeHi46LmAJe7atau53q+pPLAdBKoiUFRUJGlpaRccs3oMHzp0yGzG39/feqxajtkePXpIkyZNqpKM0yx74sQJCQoKkmXLlsmQIUOs5R46dKipP/zqq6+s0+piRO81/fDDD/LXv/5VVqxYIREREfL73/9eRo4cybllXewA0kDgGgUyMzNl165dJuzevVssYe/evaK/0zo0atRI9DqxU6dO0rlz5wviFi1aXGNKLIYAAggggAACCCCAAAKVCeh1vPYz3apVK1m8eLE5365sOaYhgAACCCCAAAIIIIAAAtcqsG7dOpk9e7YsXLhQSkpK5K677pKJEyfK4MGDhfcGr1Xxysu9/PLL8uGHH5q6bXd39ysvzFwEEEAAAQQQQMABBFzOPwxW7gDloAgIIIAAAggggAACCCCAAAIIIOCgAg8++KAcO3ZM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6LZ6msSmJBSxBLMQqDkCUWELpIuPb83JUA3MibaftGfPHomOjpbdu3db+/o+iXZNmjQRbVfJ39/f2tfhevXq1UANslQegfj4+DzHkOWY0jaUtGvVqpX1GLIcU927d7/q/oS+u6T3m7QONg3btm2TjIwMs/6gQYNk4MCBon0tt2q7XSXp0tLSTDthy5Ytkx9//NGUix566CHTzrwe47advmf18ssvy0svvSTaFv2bb75p7qXYLlOZw4cOHRJtS+3KlSum/bBHHnlEDh8+LP369RNtl2zx4sWVuXm7j1uvZfQepKWsq9cy+u6btlWmx5NexwQFBZm+Hmdanx+d/QvobzwlJcUEveesw/obsA0FTdP5BU0vapr+PShpp/dPPTw88gRtEy7/NB3PP92yrvYLC7peYfMs6+t1Oh0CCCCAAAIIIIAAAghcLXDixAm58847Rd89+uCDD2TYsGFXL8QUBBBAAAEEEEAAAQQQQAABBIoQGDFihOjz4e+++66IpSpm1oABA8x7mtouWUGdPtfUd/m++eYbufbaawtahGkIIIAAAggggEC1CjjlfpCQU60pYOMIIIAAAggggAACCCCAAAIIIIBArkBWVpb5sGfFihUyduxYTBBAAAEEEEAAAQQQQAABBBBAAAEE7EhAK00aPny4zJ8/v0pSpZVsPvHEE/KXv/zFVDx03333mReDtUIirThq586dMn78eHn22WfluuuuE72npMNaoZQuqxUZTZs2TVavXi3jxo0zFXY2bdrUjGvFVlohW6NGjUxeRo4cKV988YVs3LhRtFJv7fR1mo4dO8ovv/wifn5+Zpr+l56ebiqUmTFjhqlAyjqjFAPquHfvXunRo4fZjn5Mu2/fPrn33nvl/fffL0VM5VtUK3VatWqVjBkzpnwROdja4eHh8vPPP8vWrVsdLOUkFwEEEEAAAQQQQAABxxHQSkPnzp0rr732mugHmNpwWbdu3RwnA6QUAQQQQAABBBBAAAEEEEAAAQTsWuD111+XJ598UrZv326eu9p1YkkcAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAghUi0BmZqacP3/ehHPnzpn+2bNnRcOZM2dM33Y4MTExTzq18clmzZpJ8+bNTV+Hbcd1uoYWLVqYemycnJzyrM8IAggggAACCCCAAAII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ctcV7T5mu76YfJx82tc8y0dk6ycf7HdWRESEChQooIIFC57X2PIQZafhpZde0o8//qiZM2fqtdde03333fe33es1f5RmbY792+VYAIHsJlApsqRibx2R3bqV1B+7z2L8+PH68MMP3edmhw4d1LdvXzVr1ozzU0lKmTvx+++/a8KECZoyZYq7T6ZFixbuPbH8stn1OcLp6xep/6KxSkhMyFxs9obAeQjkDsqlAVXbakidzinWCkj0DClqeIEAAggggAACCCCAAAIIIIAAAghkskCfPn20bds2ffnll5m8Z3aHAAIIIIAAAggggAACCCCAAAIIIHC+An/++afGjRvnbtzbsWOHWrVqpX79+smCO1swJoasIWDvk93IZ++VTd94443ufWrXrl2Oe58eeeQRzZ8/X6tXr84abw6tQAABBBBAAAEEEEAgiwnY9+Xu3bu7QLnvvfeerrnmmizWQpqDAAIIIIAAAggggED2FTh9+rRGjhypIUOGqHr16po8ebJLRJEVerxgwQLZPb4WAOff//637rjjjqzQrExvQ6mpvXX45LFM3y87TB+B6lFl9W37F9NnY362lVWrVmngwIEu+Y0FYXnhhRdUqFAhP+tFxjR34cKFuv/++7V27Vo98MADevrpp0linDHUGbLVEydOuGBuyYO22XTqYgHe7BiWuhw+fNhnuyyJlAV3S17CwsIUHh7u/j7SOrZ1uHfCJzGVCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALpLLBlyxZVqlRJL730kntmMp03z+YQQAABJSYm6ocfflB0dLRiYmLc8+sBAQFq1KiRWrdu7eKSW7wMBgQQQAABBBBAAAEEEEAAAQQQQOB8BCwWp8XHGzp0qFvN4uRt375dISEhadrMpk2bNHv2bHe+wnJS7dq1y53HsJUtpljZsmXVuHFj3XLLLWratKmLJ5amDbMQAggggAACCCCAAAIIIIAAApdIYOPGjbr11lu1detWTZs2TS1btrxELWG3CCCAAAIIIIAAAggggED2ErDcRZbHaN++fec1jouLOwPCrkUWKFBABQsWTBpHRUW511bnnU49trxHWXFo0KCBYmNjXdPq16+vJ554QjfffPM5cy/1mj9Kszb/3zpZsU+0CYGMEqgU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jhd33HGH7D2tW7eu37JMX79I/ReNVUJiyuOh33aIhmdLgdxBuTSgalsNqdM5Rf8CPP8wE1PU8AIBBBBAAAEEEEAAAQQQQAABBBDIZAG78PPqq6+6E4WZvGt2hwACCCCAAAIIIIAAAggggAACCCCQjgIWIGn69OkaM2aMli9frsqVK6tfv37q0aOHIiIi0nFPbMoEvv/+e2f9wQcfKDg42D3s279/f1WrVg2g/y/w448/uuDkFsAcF/4sEEAAAQQQQAABBBD4PwFL7tOrVy/3sOeHH36oGjVqQIMAAggggAACCCCAAAKZJLBmzRr17t1bP/zwgwvkOWjQIFmgFH8Yjhw5ImuvXQdq3769GxcrVswfmn5RbSw1tbcOnzx2Udtg5UsnUD2qrL5t/+Kla0Am7rlDhw4uoe/DDz+swYMHK3/+/Jm4d//flX0u33///Vq8eLE+++wztW3b1v87dR49OHXqlAsstnv3bu3Zs8eVvXv3njUImc2zQGQnT55MsRc7phUoUEAFCxZ055680zY+27Qta/MsaBgDAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCJy/wOjRo92z4L/99psuv/zy898AayCAQI4TiIuL01dffeWeUY+JidH27dtVpEgRtWrVSq1bt1bLli3dM8A5DoYOI4AAAggggAACCCCAAAIIIIBAugtYfLenn35aCxYs0JVXXik7jxkYGOjOS9x44406ceKEVq1a5WLnr1u3Tnny5JHlQbO4ZpZvqlGjRmrYsKEr5cuXv+D2Wdw0y2W1bNkyxcbGuvH69evd9q644grVr18/qdSsWVMhISEXvC9WRAABBBBAAAEEEEAAAQQQQODvBA4ePKiePXvKrtm/9tprGjBgwN+twnwEEEAAAQQQQAABBBBAAIFsLJCQkKADBw4k5Yuy3FCWIyp1LilvTimrt2m7tpp88OaPKlSokMsfFRUV5capX1v9ddddl3zVM6Zz5coly2tlOQsfffRRNW7cWL3mj9KszbFnLEsFAtldoFJkScXeOsIvuzl79mxZjkPLE9ejRw/17dtXlStX9su+0OgzBexYMHXqVI0fP16//PKL+vfvrzfffPPMBf2gZvr6Req/aKwSEhP8oLU0MacK5A7KpQFV22pI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ORvpLL23azizZs3SW2+9pQULFsiu6T/xxBPq06ePC9R9ltWoTiZggbhuvPHGZDVMIoAAAggggAACCCCQ8wTs98Qdd9wh+31mAWstoC4DAggggAACCCCAAAIIZLzA6dOnNXLkSJfYonr16lq1apUqV66c8TvOhD3YcyVvvvmmbrvtNnfdwhJ0vPHGG+ratWsm7J1dIIAAAv4tYAGxdu/e7Z7127lzpxvbc3/eaRvb/D179rhiiZ2TDxaIp2DBgipcuLC7bmyJhyxhUuPGjZNeJ59n8wnqklyQaQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBLKfgD373aRJEz344INavHhx9usgPUIAgTQJrF27VtHR0YqJidG3334ri3F99dVX684771SbNm1Ut25dWY5SBgQQQAABBBBAAAEEEEAAAQQQQCA9BSz+5owZM/TCCy/ol19+UZkyZWRxOJcsWaKFCxe6XeXJk0f9+/dX69atVb9+fYWFhZ3RhMjISC1atMit+9hjj7l8XR07djxjuYyqCAgIUKVKlVzp2bOn282xY8e0evVqF+PfcmG99957evbZZ928cuXKqV69eq7YeZfatWsT9y2j3hy2iwACCCCAAAIIIIAAAgj4uYD9drac1CNGjNA//vEPvfrqq8qVK5ef94rmI4AAAggggAACCCCAAAII+KOA/R79/PPP/7bpQUFBbhn7TVuyZEm1atVK64qf1I8l4v92XRZAAAEEEEAAAQRykkBwTuosfUUgowQSExN16NChM0pcXNx51e3fv18JCQk+m2k/hsLDw12xm1i90za2JMCp67zzU9dHRET43D6VCCCAAAI5W8CS1u/bty/puJWWY9iGDRvc8smXjY+PP+uxzB7KsMT2qY9NdsxKa50ta9thQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAf8UaNq0qazs3LlT48eP17hx49wDq82bN1e/fv108803y3sTsH/2MH1avXXrVr3zzjuu7Nq1Hp8f3wAAQABJREFUywXmtqDdLVu2JDj3eRDbtSsLKPbPf/7zPNZiUQQQQAABBBBAAAEEso+A3eP80ksv6emnn1aHDh00ceJEn8F0s0+P6QkCCCCAAAIIIIAAAllHYM2aNerdu7d++OEHDR06VIMGDcqW10CaNWumn3/+2fWve/fu+uijjzRmzBgVK1Ys67wZtAQBBC6ZgCUjtqTEc+bMUYsWLdx1z/NtzPz5812C4+LFi6tLly4ukNT5biMzlrdn7Ow6uF3ftZJ8OvVre47Pztt4h5CQEBUpUiSpFC1aVFWqVHHPjxcqVEjeYs+T27QlRrLkRAwIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggEBygVGjRqlu3bqaPn26ey4z+TymEUAgewpYDGZ7JjsmJsY9l71582aXM/TGG290McAtprU9y8yAAAIIIIAAAggggAACCCCAAAIIpLfAqVOntGzZMr3++uvu3MSRI0fcLixOWp48eWTnLSweneUja9SokT799FNFRUX9bTPKlSunL7/8Utdee607z/ndd9+pfv36f7teRi1gfWnYsKEr3n3s3btXy5cvTyrDhw/X7t27XV8tjpydp/WWatWqKVeuXN5VGSOAAAIIIIAAAggggAACCORAAfvN2LlzZ/c7+t1331WPHj1yoAJdRgABBBBAAAEEEEAAAQQQyCoCe/bs0eLFi89ojl3rteu7di04IiJCzZs3l92XbmO7jmtDr/mj9NPm2DPW9VVRLryYahX6v/W88+duXa0qBUurZGjKa8dL/1qrP+P3ucVuKFlDkblD3fTRU8cVvWWld3Wf4wFV2ujY6ZOasHaez/m+KvMGhei6ElVVr0gFPbfyQ1+LpGtdaHBuXVO8ihoUraRnvv8gxbarFbxcbUpfrdBcefTDno1auOO/MoMZG75LsdylfFEzqpx+O7BNR0+fyJBmNC1RTWv2b9XOowfUuNhV+iNul7Yd2Zu0r2s9di1K1XTzP9m4RDvi9yfNSz5xY6laCgvJm1RVyvN3Nu7XL1UxsoT2HotLsc2khZjIkQKzZs2SPW9j94Qw+L+AP3yOZoTyhRz/LrYdQQGBqlO4vJbv+v1iN3XW9dPjmFCn8BVq4jmenE5M0Gee7y1bDu9J2l+NqDIZckwITtoDEwjkQIGjR4/q0KFDsqS+Nk5dfNWnrtu/f7+OHTvmUy8wMFD58uVTeHh4ihIWFuaS/pYvXz6p3upSL2evrb5gwYIKDuafq09kKhFAAAEE0kXAjjPeJPUXu8GDBw/6PLamPoZ6j7t2wm/jxo0pjsO27IkTvk/m2PE1MjIyxXHT13HUV5332Oo95tq2GBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCDzBYoWLarBgwfrySef1Oeff64xY8botttuU/HixXXPPfeob9++KlGiROY37BLuMTExUXPnznUWc+bMUaFChdSnTx/de++9Kl269CVsmf/ueuXKlTp9+vQlDQDmv3q0HAEEEEAAAQQQQMDfBewe5169eumLL77QiBEj9OCDD/p7l2g/AggggAACCCCAAAJ+IWDnpUeOHKkhQ4aoevXqWrVqlSpXruwXbb/QRoaGhurNN99013rs2oYlu3jjjTfUtWvXC90k6yGAQDYR+PnnnzVjxgyNGzfOfTacb7f+9a9/6b333nMJg95//30NGjRIn332mdq2bXu+mzrv5e3z3AIy79q1y5WdO3eeczr1s+b2/Js9r2fXxm1sx4KmTZsmvU4+z5ZlQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEELlagdu3auvPOO/X444+rffv2yps378VukvURQCALCvz++++Kjo5WTEyMvvnmG5f3s1atWurWrZvatGnj4jEHBQVlwZbTJAQQQAABBBBAAAEEEEAAAQQQ8GeBffv2admyZVqyZIm++uorWW6oU6dOuS5ddtllLteWxYk7fvy4HnjgAVn8NhssH9no0aMVHBzsXqflf40bN9a///1v9e/fXy1bttTq1atVtmzZtKyaKctERUWpdevWrnh3uHnzZq1YscKV5cuXuzh8cXFxypMnj4tPevXVV8tbLDYd52+8cowRQAABBBBAAAEEEEAAgewtYL8VO3bs6H4X22/qmjVrZu8O0zsEEEAAAQQQQAABBBBAAIEsL2C5wOz6rV3vDQkJcfej586dW3adtlWrVmrevLn7/RoQEHBRffkrfr+qFiith2q01+mEBDX89DHFnTyq1Xs2qnGxq/R0nc46lXBaTWY+oT/j9yXtK3bnb5rW4lGVzFdQt3zxYlL92Sa6V2yqIyePacLaeWdb5Iz6G0rV0Iv1eygoIFDPrfzwjPnpXdG8VE09V7ebAj2mz3z/QdLmu1xxjV5p2FvDvv9Q3+74r9pdXtfz+k6FBOXSjA3fJS13KSdaXVZbJz3v09HTJzKkGbk9fZ3e/FHV/eQRt/13r39Q7aKHJe3rwWo3qZPHafnOdepUvonHsau6zBuhudtWJy1jExUiSujDFo8p+d/tJxuXuHb/d98W5/rxhiVasnNtivX87cXw4cOVK1cudenSRaVKlfK35l/y9n7++ecaOnSou+fF7oOxezoYMkZg8uTJ2rRpk/tbveqqqzJmJ56t+sPnaEZ1/kKOfxfTlvBcedXnqhv1zq9fXsxmzrluehwTXqjXXYXzRuiZFR8of6487vhrO71zwetu3xl1TEj7nXHnJGAmApknkOD5gn7o0KEUxW54TF1nr33Ve+sOHDgg25avwX5shIeHuxIWFpY0bXWW6Dd1ndX7qrN6BgQQQAABBHKaQEREhKxc7GAnAPfv3590jPcew1Mf85PX24+51N8Bjhw5ctZjvv24LlCggM/j+NmO7b7qCZBzse826yOAAAIIIIAAAggggAACCCCAAAIIIIAAAgjkVIHAwEDddNNNrth5/rFjx7rATcOGDXNB6vv166frr78+xQ2W2c1q7969mjhxot5++21t2LBB1113neyG6VtvvdXddJnd+puZ/bFAWkWKFFHp0qUzc7fsCwEEEEAAAQQQQACBSy7w008/qUOHDi7AriUFatSo0SVvEw1AAAEEEEAAAQQQQCAnCKxZs0a9e/fWDz/84AIDDBo0KEcldGjWrJl+/vlnWb+7d++ujz76SGPGjFGxYsVywttPHxFIN4ETJ064BDeWHMbfk8JYkvIBAwZo3Lhx5+2zceNGlSlTxn2u2MojR450wWpGjRolSyyUUYMlLLKk6gMHDlRiYmLSbixgTuHChd1z5nYN0ooFJPFO2/Pn3mkb27PqDAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQGYLvPDCC+5Z7xEjRujpp5/O7N2zPwQQyACBY8eOacGCBYqJiVF0dLSLYx0ZGakWLVq4mN6tWrUitkMGuLNJBBBAAAEEEEAAAQQQQAABBHKygMVhW7dunRYvXqwlS5a4snbtWhefLX/+/Dpy5Ijs/MSdd96pwYMHKyoqSnFxcS4epeUgs9hsFlfPYlLed999F0RpucuWLVvmcnnZeZCVK1cqIiLigraVGStZ7Dwrt99+u9tdQkKCzGzFihX6/vvvXZk0aZKOHj2qvHnzqlatWqpTp44s7qCNr7zySr+PP5gZzuwDAQQQQAABBBBAAAEEEPAngfHjx+sf//iHmjZtqmnTpqlgwYL+1HzaigACCCCAAAIIIIAAAgggkE0F7Lql5Rw8ePCgGzdv3tzlP0/vnFvxp47r2ZXTdUOpGqoeVUYBAQFO9Pjpkxr540x1Lt9EFSNL6kTCqRTSh08d09bDezT659naemRPinm+Xtww+2klJCb4mnXWujl/rFC7y+vqmuKVz7qMd0ZUnjDVjCqnr7f/6K067/GszbG6pUx91SpULmndkMBgvdKwtz7ZuETj1nzp6pfu/E1TfpuveTcNU2hwbh3xGF7KYUCVNjrmeb8mrJ2XYc1oVPRK9z7be129YBkdP31Kaw5sc/srE1ZEfxzerUafDnKvBy+fql+7vKX+VVpr7rbVKdo0oGob3RQzTJvjdrn6RCVqz7E4N33a8/fx6NJJ+rD5IB34/oh+3b81xbr+9OL111/Xzp079dhjj6lhw4bq1auXbrvtNnffhj/141K0dcuWLapWrZoqVqzo7kG5FG3ISft899133bNQzz33nCpXruzuMerSpYsuu+yydGPwh8/RdOusjw1dyPHPx2bSVFU8XwG92qiP7l34puw4nVHDxR4TahcqrwFV26rKhwP0Z/w+18yhK6bph9tf17XFq+jbHb8oo44JwRmFwnYRSC0QHx+vQ4cOuRs2bZy82E2cyV97p1PXHzhwQPbQqq/Bkijny5dP4eHhKUpYWJi7MbRChQpJ9VbnazmrK1CggIKD+afhy5g6BBBAAAEEMlPAjseFCxd25WL3a98tUn+vONv3Davft2+fNm/enOL7ia1//LjvE16BgYHugZHk3y98fd+w+b7qk9fZdxoGBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQRyokDZsmX1r3/9S3bj1scff+yCP9mNwnbjnAWButMTLMqu6WeXYenSpa6PM2bMUJ48edSzZ0/XT7tpjSF9BJYvX6769eunz8bYCgIIIIAAAggggAACfiIwffp09enTR3Xr1pX93ihSpIiftJxmIoAAAggggAACCCDgvwKnT5/WyJEjNWTIEFWvXl2rVq1yD6n7b48uvOWhoaF68803XSAJ+21SpUoVvfHGG+rateuFb5Q1EchhAnPmzFHHjh3dc9A2tmLXTXPnzu2XEt5ntr3Bm9LaiZMnT6pz585Ji1uioQ4dOrjnvZIqM2DC2tuyZUuXuMfOq1jyIhtnp2vVGcDGJhFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAgiwgUK1ZMTz31lIYNG6a77rpLJUuWzCItoxkIIHA+Ahs3blR0dLQr33zzjY4ePaoaNWqoU6dOat26tRo2bEj+7fMBZVkEEEAAAQQQQAABBBBAAAEEEDinQHx8vFasWKElS5a4Yrm19u7dq7x586pWrVoqUaKEiwO3fft2d45i4MCBLk6eN9acncewHGOHDh1ycdssdt7ChQvVqFGjc+7372aOGzdOP/74o3799Ve3vy+++MJvzokEBga62KSWj6xXr16uq6dOnXJ9+f7772Vl2bJlsj4eP35c+fLlc7a1a9eWlTp16riYnl7jv7NiPgIIIIAAAggggAACCCCAQNYRsN959tt5/Pjx7j4ey9VtvxMZEEAAAQQQQAABBBBAAAEEELjUApZj0a7BWq7Bxo0bZ0pzJqyZp9eb3K1uFZpqyIr3k/b57roFer5ed/Xw1D+7cnpSfVBAoGpGldXA795OqjvXRPyp4+eafdZ5pxMTzjrPOyMwIEATrhuoWZuXe6sueJygRFnxDmXCiigsV15FhoR6q9x43cE/Nfm3r1UsXwFtOPRXinmZ+eKqyFK6+6obVfPjBzN0t9eXrK75239y+7i+ZLWkaasIDgjSp5uWJe3/iOe9nvPHCueWVOmZKJI3QlULlNbLh/6jP+P3JZ+VNJ2QmKg3f/lcrze+Wy3mDEmq9+cJu+dg+fLlGjBggMtz2LNnT7Vv316WT5ThTIHSpUu7yjJlypw5k5oMFbD7fux5x0GDBqlBgwbuPprbbrtNhQoVuqj9ZvXP0YvqXBpWvtDjXxo2fcYiL9br4T5/D508esa89Ky42GNCcc+x04ZKnmPY9iP/dzw4fvqUqwsJCnZj+19GHBP+t/Wk3TCBwP8ELPG53VyZvMTFxaV4bfN81SWvP3jwoBISfH+JtZs2w8PDFRYW5sY27S3Fixf3We9rWatjQAABBBBAAAEEfAl4v1tcbBAbe6jiwIEDKb4L+foelLzujz/+cMsnrzty5Ijse5avIU+ePIqMjPT5Hehs35l8fTeyB2oCPCcIGRBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQ8DcBu4+gW7durvz8888aM2aMhg4dqsGDB6tz585q1aqVXz/0+tdff2nChAnupmgL1PTGG2+oa9eu3ECZAX+osbGx6tu3bwZsmU0igAACCCCAAAIIIJD1BOx+pCeeeEIjRozQ/fffr5EjR/pNANysp0mLEEAAAQQQQAABBBA4P4G7775b06ZN0zPPPKPHHntMQUFB57eBbLh0s2bNZNd57CH97t27688//9Sjjz6aDXuasktF80aqSfHKKSs9r3bFH9DvB3for6P7z5j3dxUWvKRxsSvV8rLaWvDnz5q37Ye/W+W85teMKqffDmzT0dMnzmu9tC7ctEQ1rdm/VTuPHvD04yr9EbdL247sTVq9UdErVb9oJR31BGVZtONX/bJ/S9K8GlFltPdYXIrlk2Zm4wnv80D2nPbUqVM1adIkl3jnpptukgW7aNOmzSW5tmjJgE6cOKGrrrpKU6ZMUdOmTVWvXj33Tnz11Veya3MFChRw13SjoqLO+g7Nnj1bGzZsUP78+d21PHvm6t1339XJkydlz5XbNWEbKlWqlGIb9py6rTd8+PAU9en9wj7Dr732Wmed3ttmewgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQGYIPPTQQxo3bpyLS2XPqzJkXYHNmze7OMX2HC9D5glYnsc6deq455szb6/n3tPx48e1cOFCxcTEKDo6WuvWrXP5vZs3b+5iWLdu3VolSpQ490aYiwACCCCAAAIIIIAAAggggMDfCPz6qyfe2y+//M1S/jnbYrhZbLTSpUv7ZwcyudXbtm2TxZfzltWrV+vUqVMqWbKkGjVq5HKF5cuXT4sXL9Ynn3zi8oVZTrH+/furevXqSa2181t2PnLmzJmqUqWKbLu33HKLy89lsekudrBcZrNmzVK1atXcuROLwf/WW29d7GYv2frBwcHOzwzvuusu1w6Lw2f/LletWuXKypUrNXHiRB09elTWf1vW8pzVqlXLjc0iT548l6wP7BgBBBBAAAEEEEAAAQQQQODcAvbbuGPHjlq7dq0+/fRTtW/f/twrMBcBBBBAAAEEEEAAAQQQQACBTBSwfGgVK1ZU48aNM22vn2xcouENeqrLFU307Pcf6HRigtv3ugN/unGXCtdo2KoPlZCY6F43L1XDk8/ul6TXVtm0RFXVKXyFDhw/ov9sWqr9xw+7Ze1/hfKEq5Unx997v3+TVGcTocG51fmKa3RZaCFtOPSXVu5er98Obk+xXe8Ktu0bSlbXpkM79dHGxa46JDBY7zT9h5qWrKbdxw4p0fNfzJaVLh+fLXCuNtn8yJBQ3VK2vkrnL6zVezYqwPNf4v/vo823vIZbDu9Wu8vr6u6rbtQ7a+ZatRve+m+0TiacluVFvKlMPeUKDNKC7T9rrSff4DXFKqtq1OVuudmblyfl+MsTlEttSl/t2lg4b7halKqlv+L3K2brStfnwnkiPPPrKMHz38xNsYo7efT/78336Nm6XZMsUi9h+QUbevIP5vUY/7h3k+Zv/ynFIiXyFVRrz74mrJ2nJp7chTeUrKE/4/dp6roFOnb6pFv2jiuuVf5ceXSTp/9Ldq51Bh3LNdY6z3tkHnP+WKH1h3ak2K4Zlg0r6v6Oks+4t3IrXV2kgn7t8qY2e/Ikvrz6E01b/23yRdz0N3/+V8Pr93T7nO3Zvr8P9vd0+vRp1w3LIzh37lyFhIS481F2j0erVq3c60vVz8OHD7v7SX777Td330fLli0VERGRojl2r8SiRYsUHx/v7om48cYb5c3jaAtu3bpV//nPfzRw4EDZfU92D4ndG2T9CwwM1IIFC7R8+XK3Tcud2LdvXzf9zTffuLyKRYoUUe/evV1devzPu13bVo0aNXTdddfpnXfecfd4WF3Dhg1dDkQ7R/jhhx/K7r3p16+fzcrxg92XZIPlu7T3bMCAAbJnpnr06OHuNbK8luc7pOVz1LZpx4iy4UV15OQxvev5HMofnEddKlzrPlvtc/LTTcvcri/mc7RiRAn3mf3dX2s8n781VSGiuGZuXqbtR/a5z/8GRSuqXpGKWuyZ/73neJR8KB9eTHU9n2FVCpRW7K517vMv+fx6nnl2TPrNc9zs6mm35XxdtWeDz+Nf5/JNFBQQmHx1N/2rJ5/sD57PaxuK5S0gO9aWCC2o2J2/aaHnmHuuoXah8rrxsloa+N04n4ud65hgbbnOcwyP9+Sq3XDwL7W9vI7KeD7H7TPYjsveIb2OCXY8Oux5nwfXvl2rdm/QgRNH3PcPy5FrbsmH9D4mBCffONPZQ8AOtHaAssTClnTXxslLWusOHDgge3jU12DJc0NDQ92DpOHh4W4cFhbmxpbc15L6pq73vraxd9nIyEjZzYkMCCCAAAIIIICAPwjY95ZChQq5crHt9X4n847/7vuafTfbsmVLiu91ts7Zvq/ZyYfk37+8097vYd7Xyb+bna2O72sX+26zPgIIIIAAAggggAACCCCAAAIIIIAAAggggMCFCliwIgvY9K9//Uvvv/++xowZo8mTJ1/o5rLEehZcvFOnTnr77bdVv379LNGm7NiIXbt26Y8//nCB3bJj/+gTAggggAACCCCAAALJBfbs2aMuXbq4IL2W6Kt79+7JZzONAAIIIIAAAggggAACGSywadMml7jhiSeeyOA9+dfm7RnUN998U//9739lRjlh2HX0oLZ6grF8fOMTCg/Jp3d/W+Aerq9Vupxe9wQyyRscov7fjtWCP39OM0eVgpepQ9mGuvPKG1zgljSvmIYFLZiBBYg5ejpjkhHn9gSTmd78UdX95BHXmnevf1DtooclteyVBncqj8dk0NLJKpW/kN674WGN9wSw8Qax+e++LXql4Z36eIMnMY0nuEtOGSzJi3fwBruw5C8WwOSjjz5yz2VbEJTbb79dN910k9IjsY53f77Gds3NEv1YkmJLuDNq1CjNmzdPy5Yt0/Tp010AjhtuuEHt2rXT888/r6FDh7rkPJUrV/a1OdfmqlWr6uDBgy7Qij3r1LNnT5UqVcolDercufMZ623fvl2DBg1ygVEyM/DUGQ2hAgEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQ8AMBe171lVdecc+jDhw4kPisWeQ9s1yEK1as0PLlyxUbG+ue1929e3cWaV3ObEbJkiXVoEEDFyfaYkXXrl1b+fPnzzSMzZs3KyYmxj3LPX/+fJeP3J7FvuWWW9S6dWvZs9W5cuXKtPawIwQQQAABBBBAAAEEEEAAgewpsHPnThczzOKXr1y5Mnt28v/3KiAgQNddd5169Oih2267TeHh4dm6v2ntnMW0+/HHH10c+yVLPLH9PGXLli0url316tXVqFEjPfTQQ24cGBioKVOmuFiSGzZsUJ06ddy5Rot9n9zz+PHjrv7FF19U0aJFVaFCBdnyb7zxhotPl9a2pWW5yy+/XBMnTnTnO8eOHeti1g0YMCAtq/rFMnb+p2bNmq7cddddrs2nT5/WmjVrtGrVKvfvdvXq1Zo2bZri4uLc+3bllVeqVq1aScXWj4yM9Iv+0kgEEEAAAQQQQAABBBBAIDsLfPPNNy5ndeHChd09IhUrVszO3aVvCCCAAAIIIIAAAggggAACfiZg13ntuuNjjz2WqS0/fOqYPtu8XF2uuEaWN+/zLd+7/Xe+ool+8eSpq1KwtFqUqqkvt6529Xdcca1e/3m2m84VGKQRDe/Swj//65m/So/W6KCnat+mNtHP6feDf6pL+Wv0L0+eu6Onjuu9379x69j/IkNC9dVNwzTwu7c1ff0ivX1tf/37mnu1avcGLdv5m55aPtUtGxQQKMullzsoRFF5wjzbvl2lwwpr5I8zlceTg+/rbT+qfZn6+jN+n9Z79nfMk+/vXG367cB2t90rwotr3HUD9PiyKZq67hv1qNhUbS+/2pPjcI+bb/9L9Pz3xs9zPHn6ertyTfEqnuUna0f8fu08eiBpuT2e3IiTPfn/rC9rD2zTor9+VcNiV7q2rt2/TduO7FXjYldpdOO7VT6iuAbHTlWFiBI6eDJew+p107xtP7h+NCleWdbfWz05EduUvlp3fDUiaR+pJ66KLKUbL6vlHFLPe6Fed5XIV1DPrpyu8Fz59Na19+mh6u3Vc/5r2n/8sG4v19jlHjRTy8GYKzBYRfNG6qEa7d3fQMs5z+hU4mn9EbdLhfNGqHhoQX28cYny/v/ln/Ns197PA8ePpNh18XwF9Fzdblq+a51iPSX5sPivNQr2/K3UK1xBVxe5wtOmfupUvolunTtcCYmJyRdV7M51esTzdzT7jxUp6v39hd1nYIP9O7d8hzNmzHDPx1hOwDx58igxlUNG93ft2rV65JFHNHz4cHXp0sXlKbRciPZMVbly5dzuH374YVl+QlvGchreeeedeumll/Txxx8rKipKs2fPVp8+fWTPXln7f/rpJzf9z3/+U9u2bdOTTz6pZs2aubyKn332mZYuXZrULbt/yO7BWLRoUVJdekxce+21smf11q9fr8OHDysoKEg33nijqlWr5vI4Pv744243lpPRDCy/I0NKAXsvvX+PX331lcuJaffOWE7OMs1rKzHI87ccFJBypbO8Suvn6Bee48fSDi//X37ZdQtkx6Xpv3+rX7u86T5XP9207II/R/MH59HjtTpqYLV27ljXvmx9HToRrwZFr3SfWV2+ekWdPZ9H9tl+a7mGerpOZ7X8/Bmt3L3e9apfldbuM/mmmGEq7cnnOrv1EBXxfDZOXPuVLgstpJGN7nKfx2N/iVG/KoXVrGQ1XV34CsVsWenz+Ne/ahu9vPo/2uz5jPXcSqcJTe/3bLewmsz8v7/Na4pVVsfyjTRxzVc6fPKo3vfkmp2+/ls9unTSWZSlB6rfpBW7fnduqRc61zHBPtdfatBLN5epp2jPsd+OQXYcbHd5Xf2jalvdtWC0Pvtjudtkeh0TLC/vC6tmaHj9nvrm5hf00cbFujysiG6KeV7HT59M3fx0PSYEn7F1Ki6ZgN0weejQoRTFbry7kLqEhASf/bCDq91Qacl4bZy8FC9ePMVr7zxfy2bmw6w+O0IlAggggAACCCDg5wL2HcvKxQ52YsUCwiT/zvh33yG3bt2atLx3WTtZ4D1Jk7pNFgjIHvo42/dIX98XfdXly5fP84MvbT+cU7eB1wgggAACCCCAAAIIIIAAAggggAACCCCAAAI5W8DOO993332uXCqJCRMm6P7773c3DQYHc8vNpXof0rpfu+nThrp166Z1FZZDAAEEEEAAAQQQQMAvBSzga4cOHVzbFy9e7AK9+mVHaDQCCCCAAAIIIIAAAn4uwL3yZ38DLYFIThksiMByz8Ptq/ZsVNMSVTXpt6+02jNtw8urP9Hcds/pPy2f1LWzntTP+/5IE8uPezfrnTVzdeeV6RsAYkCVNp5ANCc1Ye28NLXjQhZq5AlcsPXIHleqFyzjeWj+lNZ4gs/YcJPnwf2ela5XpQ/6yR60t0A8gz0BdT6+8Qn9uHeTczydmOCCCXzYfJAOfH9Ev+7feiHN8Lt1QkJCfLbZnkO34eTJky6J8Oeff+6e07EgJZ06dXL1Ple8yEpLuPP666+7pMXfffedC76yb98+t29L8GNJlS04iw2vvfaaLrvsMllQli+++OKse77qqqtcQmzvAnY9+IorrvC+TDG24B7/+Mc/9Ntvv7l6C/by3nvvpViGFwgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIpBTp27ChLav/AAw9oyZIl5I9LyZPhr+yZ4J9++kmxsbHu+Vwb2/OyiYmJKl26tOrXr6/HH39c9erV09VXX628efNmeJvYwf8ELH+jxS/2vj/2nPSOHTtk8RGqVq3q3h97j+z9qVy5soKCgv638kVMnThxQosWLXLPi0dHR2vNmjWyHOE33HCDe1a7devW7nnti9gFqyKAAAIIIIAAAggggAACCCDgBI4ePapZs2Zp6tSpmjt3rvLlyyc7X/TKK6+oadOm2fJckTdGm/W5f//+Ln7ZzTffrB49eqhly5bKSfmm9u/fr6VLl7rzgnZu0M6BxMfHq0CBAmrQoIHuvvtuNW7c2J37CA0N1bFjxzRz5kxX//XXXysqKkrdunXTXXfdpWrVqqX4V2Xnt6ZPn67Bgwdr586datKkib755ht3jmv27NmqVKlSiuXT68Vtt92me+65x/1N2zlP20/z5s3Ta/NZbjt2PsrOU1np2bOna5/Zr1+/XqtWrZLlJ7AyfPhw7d69280vU6aMy1NQs2ZNeYudi2RAAAEEEEAAAQQQQAABBBDIHIGRI0e6e0Esn9ykSZPc/QCZs2f28v/YOw/4nK7/j39kSyKJiBWxV+yd2JtSe2/VGh3KT1v6a7VVtBSt1f5VtVVa1N5qVYkttiBizyDIkCE7/vd79D6/J/FEEhkSPievk3vu2ed973Pvc+9zzvdDAiRAAiRAAiRAAiRAAiRAAiRAAqkjIPMIHj58aPgNMnWlMibXskt70LtMI/Qv1xR/3TgKRytblHEsjM8PL8baNmPRv2xTbLt5whCva/+9XbEN7jwKwpqrB1VHxnovgm/vOZjsMQDdtk/Bn1q9bYrVQt2C5RJ1dGSV9rAyt8DBgCe6Z9+dWosOJTyw8sp+zD27xZA3r7U9fvLdisuhd1WcV8dJSl9v+ql1CI2N1PQIL6v4iyG3se/uORV+v3K7Z/ZJMv3U+F3sveuLI/cvqjILz+/EqCodVdj4n+gThsY8wrR6b6Kj1r9mRargM22Mf1zYZcjmF+JvCOsBH03b0Njt1/o2328HJnsOwK2IQMw5u1klJyQk4INqnbDq8n4M2z1HxV0NDcAIjU8u7U90F025Ss5Pfmu++yg4UbIcwwHlmqHy8vcVH0l8Y+csHOs+E1M8B+LtPT8qxi3dqqFn6Yb42Xc7/P7VLRxbowc+rtFVnQMLz/+DAwF+6FqyHrzvXcBOfx9ImbNBN/H3rZOJ2pQd0YT8VmNU1tFVpbnaORvGIxFSXry4ylrff2s6Ek01liOrdMAsnw0qXv8nOopyHlqamSM2IV6PTnHrN2ElcnWbnmK+zM6QnMahcbu63mF4eDjmz5+vkmxsbIyzZGo4Pj4effr0UfN3qlatqtoaPXo0Vq9eDV9fX5QqVQp//PGH6tuNGzfg6Oio8qxcuVLNBxk1apSaH9KhQwcMHjwYU6ZMUfNXJF5crVq1VF2ffvqp2pc1QZs2bVJe5sWIk3plXoloLGakk3VHI0aMwNtvv63WjdWoUQPu7u5o3769mschczt0Xdtbt26hR48eGdl8quoSrUm9D6kqkEmZLC0tU6xZrlHiZL2VnB/aP5i5OSHPhDYpltUzpOY6KnnPa9fSOgXK6sUQHheFK9r1UHfPex2Ver44skTpsrrZ5dOuTf+nNGLtLWxwtf8v+G/1rmi/5SsVN/n4SlzvP19d047dv6SaHlqhNf65dUqFb4Q/0DRur6FN0Zr4Tbumiw7sfw/9jtZFa2j3OXc03/gZ5L6lnWYIig4zef+Te5zcZ8UNKt8C5Z2KqHut3OfsLKzxQ8NhqL/uv3gUFw0fra3mRaphiNaHZZf24ui/fVKFjf5VzltMacsaRalgau4J4zQ2cn+L0XRsB+2arcpNPbEGB7tMwzd1B6q+imZtRt4ThIGZdo+bpN0TP6jaCaP2/4rg6PCk3Vf7z3tPMFWZhalIxqWegFxAIyIiIAs/Q0NDn/Km4k3FyZfd6Ohokw3LpDxZzOng4GDwIqQr+3LDEJFdCetxxvmM452cnDJswanJjjKSBEiABEiABEiABEggywnId0VZSCM+vU5eyMh3WlPfV03FyXdYeYmQ9HuwLPQx5eShP+l3VePvq8ZpKX23Tc3Du6k+MI4ESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEnpdA/fr1lSGskydPKoNVz1sPy2UNATHgXrZsWWW4LGtaZCskQAIkQAIkQAIkQAIkkPUEZEHToEGDUK9ePSxfvjxD5hBl/SjYIgmQAAmQAAmQAAmQAAmQAAlkDQERMlm7di1EBKZZs2bIzHUJ4ZrRl6RODMHIYnr3vG54TVuUfzroetIsye7HPX5iYCQ5QyvJFkwmoYKTG8RYQPVVTwxhJJMt3dHNi1Q1GFRprhlS0Y2rSMVvubfEjfD7CImJMLRz7P4TQzkfVu2M3ju+VfEJ2jruOWf/wuwGQ9Fq0zhD3uwQEJEhWRsuXgxPZFT42rVrKQ5PjKOIk3XuIriza9cTYz8XLz4xFpRiBWnM4Or6xGhOu3bt1Fr1/PnzqxpmzJihfjsdPny4oUYR4wkKCjLspzcgBlj8/PwgXMRo8pIlS5RhGOkLHQmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQQPIEZs6cqdaCyvrM/v37J5+RKekmcPXqVXh7eysvdnGPHz8O0fNzdHRUx6Br167w9PRE3bp1UaBAgXS3xwrSR0B0Elu0aKG8XtONGzcMx0+O5Z9//qk0y+3s7FCnTh11/Dw8PNRW9MRT627evIktW7Zg8+bNELsHohcpWuSvv/46fvjhBzRq1AhWVlaprY75SIAESIAESIAESIAESIAESIAESCBZArpdskWLFmHVqlWIjIxE69atIfudOnVC7ty5ky37MiSIfUGxNSg+JCQEK1asUGPv0KEDxHZanz59MGDAANSqVetlGG6iMZw/fx4HDhzA/v371VZst8n5UK5cOYjmVt++fdVW3knkypVLlZV0yS/vQJYuXareWbRt21adO+3btzdpr1Fs3o0ZMwYnTpxAmzZt1Dm1b98+TJkyBaNGjYKZmVmifmX0zqxZsyDtia277t27Q97DyRhfFSfHTvS4xPfq1cswbH9/f4immhwX2f7xxx8YP368Ogfy5s2L6tWro1q1amorYTkP+D7KgI8BEiABEiABEiABEiABEiABEkg3gYiICAwePFg9U3/zzTfq2TndlbICEiABEiABEiABEiABEiABEiABEsgEAgsWLMBrr70GXY8sE5pItsrdt8/idkQQWrlVR8HcTni9WG2su+oNr9tnlJZdm2I14WLjgPbF62DDtcOGeoZXfh0nHlzBd/XeNMRdfHgbea3tDfsx8bGGsB4omaegqs/SzByxCfFKMzAiNgpF7PLpWdQ2Mi4Gl0PvGuJ8g2+qvhki/g0Y6wem1KfGhSuhdoGymHJydaJqjmvjqJKveKI42Vl+eR923T6NaXUHoXPJuvi+4TDUzF8ao/b/+lTeZ0WExjxSyWeDbhiyCStxxpqJF7Q4a3NLFLbNi9uPTGvNlXd6smYiIDLEUJcE3q3UFlKnaDLqTvhdC7uHXmUaYfTBBQjT0h7FRUO0F/1CbunZMNNnPT6s1gkNClXAwvP/qHjROfTyP63CTV2raOfDk7Ch0L8BOU/qrP4IxexdsLjFR+hZuiFWXT6A7bdOJM2KM9r4m6wfi6PdZ6B7qfqY5bMhUR7hZKGdF6UcCuF8iH+itGftuHavh18//+5ZWbIk7e2331aaiSk1ZmFhgbi4OLWuqVKlSrhw4UJKRTIsXdbPyPwFY53BmjVrIiwszDBfQeaAuLu7q/7pDcsckJIlS2Lx4sWYM2cOHBwcDPOdJK/uKlasiG3btum7KFWqlJrH8ttvv6n5EjJ2CQ8bNsyQJyMDMg9n9OjRqp81atRQVcv6sevXr2Pnzp1qrdKhQ4fU+iNzc/OMbDpVdQmrsWPHpipvZmb66quvcPq06c+0cbvCKCEhQZ0bNVs2xOnKT+Y2GedJKZwdrqNhMZG4GhaAqH/vSeFxUbjzKFjdY/S4yPgY+EcEorj9/9YWtts8UV0zZYxy7XXT7lN5LP83z++uVoe47TdPQHRdA6PC1L78M3X/W3Zpr0ovYueMrzz6wTvgAuac2aziupdqABsLK0ys01fty7+Cto64GhqgrolH718yxOsBuY+W0O6pG68f0aMM29TcE+R+IM4n8Jrayr/7UQ/x+4Wd+KhaZxTPUwBX/r0PZ9Q9oYRWZ8cSntp99Bd8UqM7/q/R2+r+PzXJfVn68rz3BCmb1FkkjXhV9kXUNzQ01ODlYm+8r4dNxSeNk0mNppxMfpWbgiwKla2xl4WexvvJ5ZN4WShKRwIkQAIkQAIkQAIkQAKZTcDe3h7i0+vi4+Px8OHDRN+vk36HTvp9WxaXnDt3TpXR84qBE3lJZMpZW1urlzPJfY829R3cVF75rq0vVjLVDuNIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIQCcgE9zEIJIYy6pdu7YezW02JSAGvsQgOx0JkAAJkAAJkAAJkAAJvIwEZP76eM1wqyyAGT58OETgSxYl0ZEACZAACZAACZAACZAACbwaBPbu3Yvdu3dDRD169OihxFxSM3IxKCAGFQoXLozevXsjLYKmqak/u+cRIVYxBiFe1hKIMEy3bt2UwYesWsfraGWrMFmYEEYplDsvWrpVg6u22N474Dx23zmbLFIxftOhhAdkQf0uzfCJGEhpVKgiKv9rGGajZvzmlmYcIDk3QVu0v/LKfpPJ1fKVQL2C7shtYY1TgVex098nUT5XW2e0LVYL8/3+RkPNCEuLItWUAZhFF3YZDBb0KdMY9pY26KAZ4jkQ4IehFVqjm2Yw4MJDfxXepBkAKOtYRMsfk6ju4OhwZQSmbsHyieLFcMs3ngNVfaaMByTKbLTzOC4et/86iumXpiM6OloZPJFtWsMxMTGqjHE5iUtufblRFxIFZf2KCK2Il3Ux4k2FRawptU430FKwYEHVn6JFi6a2aJry6WI+xoZIRFjp9u3bGDJkCERYKbNdiRIlIOLmYohGDKMYG4bJ7LZZPwmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAnkRAIiYj906FCMGTNGrUsXbTe69BMIDg7GkSNH4O3tbfAPHjyApaUlKleuDE9PT8VdtmLTmDp56WeeFTUUK1YM4sWOgzjRYzx9+jTEzrEc602bNuHbb79FQkICXF1dUbduXWX/WI5zrVq1lH65lBPN9P3792PLli3KxsOZM2dga2uL5s2bY9q0aWjbti1k7TQdCZAACZAACZAACZAACZAACZAACWQUAV9fXyxatEjZ6bp586Z6ThX75X369EGBAgUyqpkcVY+TkxOGDRum/NWrV7F48WLlZ8+ejQoVKmDAgAHo16+feheQowamdTYqKgpHjx7Fvn371DuIgwcPIjAwELlz51aaWp06dcLUqVNRr149uLi4PDU8ec+xfPlyrFy5Erdu3VK23caOHauYFCpU6Kn8EiHvwr788kv1vqNp06bK9tyGDRvQrFkzrFu3DmXLljVZLqMjZYy///67ei8j73HEpqSMx9HRMaObylH1iW1R8cb2+UJDQ+Hj44OTJ08qL3ZMf/rpJ3X+yHtM+RxUq1Ytkc+fP3+OGjc7SwIkQAIkQAIkQAIkQAIkQALZgcDFixfRtWtX3L17F9u2bUOLFi2yQ7fYBxIgARIgARIgARIgARIgARIgARJ4ioC/vz/+/vtvLFu27Km0rIh4jMdYcXkfRlXtiD5lGqGdpm/35q7ZWuxjLLmwG5/W7G6If2fPj6pLovlXWNPM++D8fGy9eTxN3dxzxxddStVTenx7ND1AJyt7WJlbKM2/Z1UUp60XMDehMajr1aWmT5Wdi6smzgXfTNSUjDU5dy/yIQZpPLpc88bcRu9iUPkWWHpxD7zvXUiuSKrioxPinsoX+2+craZTmJxzsXFQmnhR8bGJspTXtAdN9engXT+UyFNA0yZ0xfEHlxOV0XciNc1C/4gguNjkwcQ6/ZBH0zl8XdNDPHb/MmbWd0GbYjVxIeS2Fh6MGafW42bEA72oYXsj/AGG7v4/eHf9DnUKlMH2WycMacYBaWvz9aPoX66ZcbQKR8RGqa3oMZ4P8X8qPbkIh0pu6NH1yXqT5PJkRfyIESOSbUY0BmXNi+gjdunSBf3790fr1q2VdqfMJckqd+rUKaUVmnQegmg2ipPP07lz51C/fv2nutSoUSPIXB/RafXw8HgqXSJknPpnUs8wfPhwNW9C5rN07twZ0ocJEyboyRm6tbe3V2z/+OMPfPPNN7h//z4iIiJQunRp/Pbbb+od4S+//JJp7ac0GJkvpK+NSilvZqbPnTs32ep1XUxZc9emTRvFU+YBbbh9DO/t/QkJjxOSLZtcQna7jko/Y+JNX4PtLP93/b3zKBjNXKuoa+D+O+dwNTQA1V1KGYaZ8O+9Iz6NTGbVHwqLXOYaz7nqXisVuud1w91HIRh9cIGh/pQCea3t1X0xMi6x7qyUS8094ZaJa7mUvfTwjmzUPUHueRl5T1jf5nN8cXgxNlw/jE3avWBpy9Hqe8a2mydwMvCKalf/97z3BL288dbCeCe7h+UiGh4ejrCwMMhEM2NvKk7Sk4sXkV9TToR25YLp4OBg8LLAWfZFdFeP1+P0fdkax8nkQGMRXVNtMY4ESIAESIAESIAESIAEXkYC8j3Y2dlZ+fSOT15cPOt7vanv+7dv3070rCDlIyMjTXZFHvCNv8fr3+9NxSX9zp80r/4CyWRDjCQBEiABEiABEiABEiABEiABEiABEiABEiABEiABEsjxBOSdshjGOnDgAEaOHJnjx/OyD0A3PPayj5PjIwESIAESIAESIAESePUIyHyagQMHYuPGjZg3b54SmHr1KHDEJEACJEACJEACJEACJPDqEjh27JgSJF21ahWmTJmCXr16ISgoSImNPouKGG0Q0RMxlLBkyRJ8/PHHEAMHxgIRzyr/MqSJMQuZ9x8TE6OMLIjgydKlS1WcGLfo3r27EjKR9RCZ4SrmLYqOJTwgi8RXXT6QqIlGhSqiW+n6+O3cDoTHRmKJtsh82aU9yS6uD4gMwQPN2MvC5qMwYt88+IXcwt67vqhXyB1ja/aAX/At3IoITNSGvlPByQ2ti9bA9FPr9CjDdpJHf4hBkwnHlsHB0hY/Nn4HH1TthIE7ZyI4Ohw9SjXAt/UGwdrcCpWci8LSzAIFczvhg2qd0FszyPPapvGIexyP62H3kD+3IwrbOWPVlQPI/W/+iVq9kXHRCImOQGR8NMo4FNbayY1Qbcy6u6YZLWhapArsLWwQHvfEyIqkeQdcwEfVumDj9SN61pS38Y9xY8V+zHbwU8ZM9HNAtknDIiYjQkFJ4+WcMZX/eeJTu+5ERFpEzDs5J79bipf1O2KgZfDgwWjZsqUqY2Njk1yxDI/XjW+IaHKHDh0yvH5TFVasWFGJMScnTmSqDONIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARI4FUmMGnSJMja6vHjx2P69OmvMornGrusT/fx8YG3t7fBX7x4EaKxXaJECXh4eODTTz+Fp6cnatWqhaxc7/tcA2KhVBOQ9dzVq1dXftiwYaqc6KqL3WP9fJg9ezbu3LkDWXtdqVIlWFpawtfXF1FRUShbtqyy6SCfuyZNmqh166lunBlJgARIgARIgARIgARIgARIgARIIAUCAQEBypbeokWLcPz4cRQrVgz9+/fHgAEDUKFChRRKv1rJJUuWxBdffKG8PNMLsxkzZuCzzz5Tz+zCTGwROjg4ZEsw9+7dU3pZ+/fvh3ixSSnvrFxdXdGgQQN8/vnnys6k2K+TdxOm3IkTJ7B8+XLlr127pt5bDBo0SNmzrFy5sqkiKu7w4cOYMGECNm/erN6DDR8+XNmztLe3x7Jly9CzZ89ky2ZWQu3atfHJJ5+oY+jo6IjevXvjr7/+Uu9nMqvNnFivnM8NGzZUXu9/XFwczp8/j1OnThn8d999h7t376osYuOvWrVqyletWhXi3d3dkz2v9Hq5JQESIAESIAESIAESIAESIIFXlYBoyMl7BZkfIM/r8n6GjgRIgARIgARIgARIgARIgARIgASyK4E//vhDabR17NjxhXVx2aW9GFW1I4ZXbqfp7vkbdPaWXPTCf2t0xXuVX9f07+7jmqaBJy5BW7chTvT/tt48rsKp/ffHhZ0o5VAQM+oPxtfHlqNR4UqYcHQZ/vE/ldoqEuV70pPU9SmPpssnrnb+svCP8E5cz79jkshhFV7Dr37bDeOUuLVXD6GJa2UMKt8C7YvXgfe9CxL93E7WviTnHiP5tAsPbyudPDsLa0RouoO6C4mJQM38pWGmaejpx0fSLoc++d1Z0pNzVkrz0BE7tWPw/emNKOtYGF1K1sPwfT/BxcYB/co2xft7v0RUfCweRIUmVw3Oa+fOnUdBCNB0HJ/lZAyXHt55KouTtZ2K809G5/GpAtk8QtcSlG2bNm3U/CH5nIsu44tyCQkJSit0165dEI3QpE40GPPmzavW58THxys9Rj2PvGsTJ+lpcW3btkWpUqUwb948tbZL9jPTvfPOO5g7dy7WrFmj3g2OHj0aXl5eav3elStX1Pjd3Nwysws5sm5ZqyXnh2jqDhw4UM3ZSuuxNh54dr6OSj+Tu84aX5s/07RnG2hatl23TVbXP9G7Ta8TXdlWRavjM+9Fhuuz1Bn/OEFdey1ymSvN2dS0c0+71or2rL3l0xqp6bknFLV3Uc3LPT8j7wkNC1VAEU1Dd8e/93u5n/T/ZwbO9Z6DziU9cTLwSqJhZ+Q9wSJRzZm0IxMHQ0NDDT4sLMwQ1uNNxUmacbyEjU9E4+7a2toiT548akKlTELTvYj9Fi1a1LCvx5vKK3F2dk9uuMZ1M0wCJEACJEACJEACJEACJPBiCMj3c/GFCxdOVwfkRY7+7GG8NX7e0OP1ODHIIgtZjJ9LJE0WuJhyVlZWkMVC8sxh6nkjufikeWXhk7yEoiMBEiABEiABEiABEiABEiABEiABEiABEiABEiABEsh+BGQCmUz2o8veBC5duoSgoCBleCx795S9IwESIAESIAESIAESIIG0Ebh+/To6deqE27dvY8eOHWjcuHHaKmBuEiABEiABEiABEiABEiCBHE9AhEvk9wqZvy7hIUOGQNbXPsuJEYESmnDt6dOnVTYRJxWDArNmzVJipc8q+zKlJRXq1dcGyBroLVu2KFETGW+jRo3Qo0cPdO7cGUWKFEkXgs4l66KVW3VlfKa6Syn8eXEPFp7/x2CcRioX4yg/NByG+uv+i0eakRSfoGtoXqQahlRoDTF2c/T+JZN9EOM3SZ1P4LWkUU/tV3J+Ynz27qPgRGmy0H9AuWaovPx9hMZGqrQ3ds7Cse4zMcVzIN7e8yNWXtmPlm7V0LN0Q/zsu10zwHNL5Rtbowc+1ozv9C/XVI3vQIAfumpGWcTwzE5/H1XmbNBN/H3rpDh6COsAAEAASURBVKHNPbfPagYEXFFfW2RvbJjHwcoWwdHhCI+LMuSVwDmtLanf0swcsQnxidKS28llbYG6v/8HezpNTi5Ltoy3trY22S8R9ImNjVWCLCJQ3KdPH2WQyWTmLIiUdTIiqCQGTD744INExmIWL16s3lskZ+zYwsJCiSantZv3799HSEiISYMwaa2L+UmABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEjgVSCQL18+TJ48Ge+//z4GDx6MihUrvgrDfu4xXr58GYcPH4a3t7fyJ06cQHR0tFrXW6dOHbUW3dPTE3Xr1kX+/Pmfux0WzJkERGOwWbNmyusj8Pf3x6FDh9T58tdffyEhIUElSfzx48ch68RF/1DOG7H1QEcCJEACJEACJEACJEACJEACJEACz0sgMjIS69evxx9//IHt27fDzs4O3bt3h9gXbNKkCXLlyvW8Vb8y5eT5XPzMmTOVDcJFixbhvffeU+/OOnbsiAEDBuC1116D2Ep7Ee7x48c4f/489u/fj3379qntxYsXYWZmhkqVKqFBgwaqv7IVO3DPcidPnsTq1auxfPlySB2Sv1evXsrXqFHjWUWxZ88eTJkyRTESXmPHjsXKlSvxyy+/KLtzYgtTzr8X5caNG4cNGzZA7Pbt3r0bH3/8Mb777rsX1Z0c066c13Ieie/bt6+h3/fu3cOpU6fg4+Ojtlu3blX2SsVWp9g+dXd3VzYQq1atiipVqkC2hQsXNpRngARIgARIgARIgARIgARIgAReNQIyL2DChAn46quv8Oabb+LHH39Uz6ivGgeOlwRIgARIgARIgARIgARIgARIIGcRWLBggfqdUH4DFHfz5k2sW7dOzTeQ3wGzYs6BaN6dDboB0dGbcHSZAeCtiEB43T6jafdVxSyfjYb4ME1L71rYPQyu0Ao/nt2MqPhYQ5ro6B24ew5S1pSLf5yAgMgQDN/7EwKjwrD5xjHEJMSZyvrMOO1nfOXMc5mpbWr65Bt8Q+VtXLgS1l/zflKBif9F7V0wUNMNXHh+Z6JUL//TGFS+BaL/HW/cv5p91uZPjl2izJm0cy74pqo5f25HRGjHQHeiqdi+eB1UdS6Jk4FX9GhUy1cC9yMfascrwBCXNOBRoCxsLKyUZuGDqFCIxuPeO2dxTyvX1LUKDmuahzcjHiQt9tR+Pps8cLSyU/qITyUaRUg/N984ahTzJFjQ1gkyP+N6+P2n0nJShLm5uVq/0rBhQwwcOBDdunV7oZqGxuzkmiLuzz//TKQ3GBgYqOakdOnSRc3fkWuQrNuqXbu2obiswylQoABKlSpliEtNQK5h7777rppDIhqlUndmOpk7Ua9ePcyYMUPNoZD5NdJnmdMi45N5N3RPCMh8FTkmwmzQoEFq7pKrq2uG4EntdVQak/uCjbllhrSbUZUUt8+PMdW7YtT+Xwz3OLN/7zfP20YB7bot2rPeARe0e+cWQzW185fBmaDrsLO0wVvuLfHzuW2GNEdNP7Z7qQaY7/e3Ic44IPdvuR8kdam5J7jYOCQtpvblHnnywRV1D5CIjLonVMxbDGba9cBeG6doA4uT7wPH7l+GnC9JXUbeE5KdcSg3HVlcKD40NDSRT22cXk4mdJly8kHLkycPRGBWvHG4ePHiJuNN5ZU4ucHQkQAJkAAJkAAJkAAJkAAJkIApAvK8kDdvXuVNpacl7tGjR4bno9Q+G929e9dQRn9OknpMOXlZJAZi9Gcf463xM5Me/6w4WbxERwIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkkHEE6tevj88//xy3bt2ise6Mw5rhNYmRfjGsnpKxsgxvmBWSAAmQAAmQAAmQAAmQQCYSEGO/svBHDKrKd94SJUpkYmusmgRIgARIgARIgARIgARIILsSOHv2LBo1aqS6J3PPU7P4PTY2Vi2U18ck89Xl+ULmtr9K7lnz6+Pj4w0oROxExFZEQFl+awgrpxltaV4KuSzSvo75TkQQ3PO6qQXpf5zfhfFHlxra0QOyWF4Mmkys8z8BjoK2jrgaGoBSDoUgC+Mz0pV3KqKqk4Xsxu7dSm1x8eFthGoGc3R3OfSuMp7Tq0wjjD64AGK4RhbCxz2Ohyzk191Mn/X4sFonNChUQTNE84+KFkM8YoRGnBhm8br9JKwitH9TTqxWxnpmNxiKr44tx8OYCDRxrYyKeYvi8P2LejbDNjTmESzMzBWT8yH+hviXMaAbWJKxyXoYOT9lTYwYEX7rrbeUYEtWjzsiIkI1+eBBYuM6Y8aMUSJEzZs3xzfffANHR0dlNEWMrxQrVkyVefjwodqGh4cbut26dWssW7YMYlSqZ8+eWLFiBcSwS1RUFIKDg9V4RXhGxGhE2MrW1laVnT9/PqZOnYqyZcsa6mKABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEjg2QSGDh2KX375BSNGjMA//zxZD/zsEq9GalBQkLLpJXa9vL29lZc1r2LXVkTtPT098c4776ht+fLlIWv86UggKYEiRYqgW7duyk+bNk2tDxfbEPo5JeumZ8yYoeLFjpycV7qvVauW0itMWif3SYAESIAESIAESIAESIAESIAESEAnkJCQgN27d2PRokVYtWoVIiMjIXa8Fi9ejE6dOiF37tx6Vm7TQEDe/3Ts2FH5kJAQrFy5UjHu0KED8ufPj969e2PAgAGoXbt2GmpNe1axvXb06FGIDXrxBw4cUDbZxPaah4eHstPWoEEDiGaW2Hl7louJiYGXlxc2bNig/M2bN5W+lth669Wrl6rvWeXF5t2aNWvw7bff4siRI2jYsCGmTJmizjuxM9e3b19s27YNJUuWfFY1WZIm9vp+++039Y7l7bffxvTp01GtWjV1zLKkAy9ZI2I7sFWrVsrrQxM7pn5+fvDx8TH4mTNn4vbt2yqLi4sLqlSpot6j6ttKlSoZ7Abq9XBLAiRAAiRAAiRAAiRAAiRAAi8bAbGh3r9/f+zYsQNz586FPJfSkQAJkAAJkAAJkAAJkAAJkAAJkEB2JyCafBcvXsTy5csNXd2yZQtGjhyp9p2cnNRcBJmP0LJlSxQvXtyQL6MDq68cUDp06695J6r6z4u70UzTutuQJP770xsxo/5gbGz7BSZoGoChMZFoV7w27keF4lZEoKrDytwSDpa2MM9lhvjHCSruLfeW6FTCEycfXIGVuQXc7F1w71EIwuOiDO06W9vD3tIGVmYWiEmIU/F5re1ga24Na63O6PhY3I0MVvF1CpTF4oteqJS3GFLq051Hwbig6eyJ9p+M90CAHwrlzqs0/uwtc6s6RAPwiqZTOK5Wb5wLvgXvexcM/epaqr7SClxxeZ+KuxR6B9fD7qNbyXrYduO40jzsXLKuSquWr4SmD3gGj7U/GYs46bvu9Li82livhd1T0XYWT/LZmFvp2Z7annxwVfVBNAX1cpJp/JGlaOVWHb3LNMTJwCuqXC7kgkeBckqjMeHxY0NdFrnMUc7RFRc0bURxHbXjse+OL7bdPKH2mxepgh23TqlwC03zcFcSnUNJaFGkGvLndsD6q96IjI9ReQeUa4Yvj/yp8bur9ktrWo9DKrTG0ot74BN0TcW5O7nBzsIa355cq/aN/xWzz4+d/j7q+BrH55RwiRIlIGtT3njjDTUfRMLZzcmcHNEA/f3332FjY4MePXqouQcyr0X0CsXJnBS5DsmcKH1+jsyTOnjwoEoT/UZxut6qzIvRnWgpRkdH47F2vhmv9RKNx3HjxqFMmTLIkyePnv2prbzjEyfzdtLj5N3goEGDsH37dlWNzDkSjVhZT/Taa6+lp+qXoqzoWJYqVUqdq3369MkU7cnUXkcFqHzuu2nX135lm2Dt1UPool1HnW3sYaNdM52s7BCiaanq18znuY7aWVqr+4nxwZPrrVx/jZ2tFqfXb/fvdVuu+6uvHERl52KoX8hdpcs1TK6vlto9TFw+m6fPaVP3v+n13tLKW+G9vXPVvUHKWmoasD1LN8Tnhxfj85q98LVHfzXurTePo6LWZmft+vz+3nmS1aQ7eNcPLdyqPZWWlntCJa0d3RW2zYua+Uujz9/f6lGarm3G3BPkOMfEx6F98Tr4zW+Hqt9WY1lBu5/9cHqToT09kJH3hCdHSq9Z28rkK5n8JxcduWCZcnZ2duqC5eDgoBYZ6lsRr5UvZPq+vpWLmx423nIiqSm6jCMBEiABEiABEiABEiABEsjOBGThlPhChQqlq5uyGCosLEy9RJIXSeKT7ieNDwgIUC/Mk8bHxT15SZy0Q7KIqV+/fmohU9I07pMACZAACZAACZAACZAACZAACZAACZAACZAACZAACaSdgBjWkkmCYmxLjGPRZU8CMhlSjHpZW1tnzw6yVyRAAiRAAiRAAiRAAiSQRgJLlizB4MGD0aZNG2XU2d4+8YKPNFbH7CRAAiRAAiRAAiRAAiRAAjmQgIi+iMCoiHiIeO28efOU8QYxkCAuPDwc69atw/nz55UIgxgN0AVCRMjW2IlxhMuXL0NEPLLKyfz5hw8fKmMLYixBjC6kdqvn1fPr+2ndWlhYQMaekpO11dJfcSdOnEAuXwvYVS8E8wJPL5pPqS4xyPLrub8hhkQGlm+G05phkV/OPTHyoJd1z+uGu5pRmdEHF+hRmbp1sXFQ68ejNMM0xq68Y5FEBmT0NFmwXyJPAZTVjLAcf3BZj060FaMq/hFBcNEMC0ys0w95NGMErxerhWP3L2NmfRe0KVZTM2ZzWwsPxoxT63Ez4oFmfOchmqz/VBm4EWMFZ4NvYPGF3coIy947ZxPVLzsRsU+MbLjaOuO8ZhjnZXb6+nszMzO0bdsWQ4YMQbt27SDn8ItwFy5cwKRJk1TTYnhFDKO89957SjhbBLPluiRCQc2aNVN9HD16NN59912VX65XEyZMUGEx5FKuXDk1JjHm8vPPP0OMrUhZqV/EkSMiIrB69Wo1Zqn3ww8/VELmIsIkwstNmzZF48aNXwQGtkkCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACOZaArFv9v//7P9SvXx+yXvRVtKsbExODkydPQuzWyhpY2V68eFEd05IlS8LT0xOff/45xP5wzZo1YWNjk2OPNzv+YgmI/eqqVasqP3ToUNUZWUd95MgRw/knn8dPP/0U8tmsUKGCOv/kHJTzr3Llyi9sbfmLJcfWSYAESIAESIAESIAESIAESIAEjAn4+vpi0aJFEPvkYpNL7HR99dVX6NOnDwoUKGCcleF0EnBycoI8w4u/du2asgMv7L///nu4u7tjwIAB6N+/P4oVK5bOloD79+8r3av9+/dj3759OHbsGOS9VeHChdGgQQP1fkq2NWrUSNX7gaCgIPz111/YsGEDtm3bhrCwMFSvXh1vvvkmxFalvOfKlSvXM/sdEhKChQsXqvFev34dXbp0UWNeu3YtPvnkE2Ub//jx46reZ1aUxYm1a9dWNu/kfeeoUaMwbNgwdbzq1KmTxT15OZuztLRUtk2rVKmCfv36GQYZGBgIHx8fnD59Wm1Fx+2XX37Bo0eP1LuuUqVKGcpJWfFiv1DemdGRAAmQAAmQAAmQAAmQAAmQQE4nIM9D8twsGgl79uxRv/Xn9DGx/yRAAiRAAiRAAiRAAiRAAiRAAq8GgQULFqjffOW3aN05OzvrQcjvxqIbtmrVKqXzV7RoUbz++uto2bIlmjdvDuO8hkLPGVhz9SAqOxdHWGxkoho23zgGr9uncedRcKL43/x2wM0uH0ZW6YBNr49DXEI8fji9CfM1bUAbc0sMLNccDQtVgI2FFb6o1Qv/d+YvPIgKRYCmC1gxb1FVxrhCL//TGLZnjlamIuoWLA8LM3NVbuqJ1Uprr4FWl7k2z/+zmj3x1bFlCIwK0/p1Bm+Ub46SDgXx3p65eFafpK34xwnovn0KFjYbhc3tvsS10AAcuX8JJx5cgZOVHTwKlMOl0Du4GhaAa2H38GXtPrgXGYKLD2+jmWsVOFnbo/ff3+KCtq+7706twVd1+uNg12+xVWMlfWhUuCIK5HZCKa1fztZ50K9sU5V9eOXXIeMpau+Ct9xbqbhPanTDuCN/wsHSVo1FIkdX64yvjq/AldC7Ko/xv5CYCKU32KG4B+TY6E763WnrJMxrPBwJmt7j3ju+6FjCA9NOrsGSi7v1bGor6UMqtIZoHMoxtLWwVuOSRPNcZuoYjPVepPI2K1IF83y3qbDxPyk3yXMAptUdhDVXDuL2oyDs09o8EOBnyGavaSb2LdsE71Zqiz2a/uFxTTsxODoc7bd8hbjHT/Qo9cyW2vFuV6w23vL6Xo/KcdtDhw5l+z7LHIGNGzeq+SuiVSi+SZMmak6OtbW16r/oq+7YsUPNUZG1NaKBKNehL774QpWTTKLfKvNXxE2ePFnNnfLy8sLevXvVHJmJEyfis88+M8yxkWuVzK16++23VZmk/wICArB06VKsWbNGJcm8GJkT1KrVk89J0vwp7cv6PJnbZVxedB2rVaum5lCkVP5lT5f5SJnt0nIdXXf1EAaVb4E5jd5R95Svji3HyQdXYaddm+Q6di741nNdR6f7rEebojWRV7t21yvoji4l62L7zROqDVc7Z+Sxyo2h2rVw0YVdeKdiG7jZ54Nct3qXaYRll/aq+D5lGmN3p8n4Xru/fXxwIX5tOgJ/thyN8UeWYljF1xRGqVeu16KNa6F9Zkzd/+pp97UO2ljkfvK21pY4a+1eWcOlFI7cu4iYhDh03TZZ1T3Rox/E+wbfxDu7f0R43BOtWFUoyb/Zpzeif7mmStNW7lu6S8s9oaB2v/q+wTDtHv0QzYtUxdu752D3v5q1GXlPkD71/Wc6Jnn0R638ZXAm6Draarq6E48uw4brh/Wuq21G3xNyaULIj41b2LVrl/oiNX/+fCXE6uDggDx58kC2epiTqoyJMUwCJEACJEACJEACJEACJEACL5ZAZGQkQkNDlZcFWnp47ty56mWULATLiU5eNHus/igndp19JoFXnoC8YPTtNeeV50AAJEACJEACJEACJEACJEACJEACJEACLycBMajWqFEjzJo16+Uc4EswKhEzEGNmP/7440swGg6BBEiABEiABEiABEjgVScwfvx4TJgwAR999BGmTZvGhT+v+gnB8ZMACZAACZAACZAACWRLAmJwoGLFipgzJ/PmTooQiXhpS4wSDBkyBPb29krgws/PTz0zfPPNN6ofAwcOxPbt25XorYgvGDt/f398/PHHcHNzw9SpU42TMi0sfRZhE5nrnhpnYWGhxHjFwIOI8hpvjcNJ01LaFxHgn376CfHxiY15JO2TCF3ExsZCBEWE9bgELzyyeHaZpHUsbvEh2hevg2YbPlMGWwrlzou9nb9RC/u7bJ2MvXd9DUW+1haXv6kZEyi+eMhThkb0TOWdisC763cYsW+etsjfS0W7O7nhkGbIxThOjAcsazUG0sYuzRCOKScGTr7xHIgifwxCRFy0IYtf7x9hpxkTKL54sDLKoieMrtYFn9fqiZqrPlBGA2bWH6wW8OdfOEDPAiszC9zoPx9LL+3BpOMrUdaxMJa2HAOPNR/BxcYBXh0no5ZWPio+VhnWeYxES9sN9cxqMASt3KqjjraO4JFR3yTDwHLN8H3DYWqNgbFRG0PhZAJV85XEHs0gQk5zIuQj4jVpEXgSgyEi9CPiUFntZF3LlStXIOLatra2qW5eRJDy58+v8kdFRT0lxJ2QkKCEkoRDSuJFqW40hYwFCxZEixYtMGjQICXO7OrqmkIJJpMACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBAziDw1ltv4e+//4asUbezs8sZnX7OXl66dAne3t7Ky1rzEydOICYmBnnz5lXreD09PaF7FxeX52yFxUjg+Qncvn0bhw4dUrYh5FzV7TLIem2xgS3np4eHh9oWK1bs+RtiSRIgARIgARIgARIgARIgARIggRxDICAgAEuXLsWi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hMY8MtmNpZf2YLLnADRxrfxUenhsFJys7fAwmbJPFciEiLRwnOwxAJuuH0HoMxilt4uOVrao4VIKXrfPwDyXGRoVroSPDy5U1TYo5I56mu/19zRDMwna53LpxT14X3txNO3kGjyKi1Zp//ifwpjqXdCiSDVImI4ESIAESIAESIAESIAESIAESIAESIAESOB/BOrXr4/Jkyf/L4KhbEEgMjJS/cYvv+/SkQAJkAAJkAAJkAAJkEBOIjB9+nSMGTMGI0aMwMyZM9M0ty8njZN9JQESIAESIAESIAESIAESSB8Bf39/VcHdu3cNFYmYiIjliriMjY0NevToAR8fH3h5eSnRGckoAiT37t1TojO2traq7Pz58zF16lSULVvWUBcDrxaBAwF+sNIWn3/t0Q+fH16Cx9pfalxuC2tY5DKHnbaN+HfecWrKPU+eonYu+LBqJwzXDDlExcc+TxWvZJk+ffoooSH5PfO///2vEsGZPXu2Wv/8SgIxMWj5XXHatGnqOihGrDZv3oy2bduayJm+KFn/JddZ8WJYRXeytubatWvw9fU1eBEoEoMsYWFhKluBAgVQoUIFJdAkIk26L1asGN8d6SC5JQESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESeG4Cst583bp1Sqdzzpw5z11PegqKrqqso69Xr57SCvX09ISHhwecnZ3TUy3LksArT0BsSzRq1Eh5HYbYqjh8+DC8vb2VX7VqFdavX69sUuh5uCUBEiABEiABEiABEiABEiABEsgYAgcOHEDv3v/P3nnAV1FsYfwAoSWhhNB7R+lden0CIiAd6UVUELEgiPJE0AeICmJDQXoXUURAqlIE6b333ntCSUiBd7+Dc91cbksgEJJv8tvs7uyUM/+dnbvlnDkv6txW06ZN0zmoMFcgQ/wnkDRpUmnYsKEu165dk1mzZun8Yt27d5eCBQtKrVq15I033pBq1apJtmzZYgQEc0suXbpUn+kxh9qVK1ekcOHC2udq164tZ86ckR9//FG++eYb8fHxUVl++eUXef755wXyxdeQN29e6dixowwaNEjat28vkydPlgoVKkjTpk3lr7/+kuTJk8fXpifIdqEvFylSRJeWLVvaGdy+fVv2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ARIgARIgARIgARIgARIgARIgARIgARIgARIgARLwhgD84cL2tFOnTtKxY0e7f2Fv8j6sNPCv+O6778r777//sIpkOSRAAi4IZM6cWf3HwocsAvx3w6aZgQRIgARIgARIgARIgARIgARI4OETwDsPhClTpkhgYODDr4AlPhEE0qZNKy+//LIUK1ZMKlasKH/++adkz5492rKjP2E+yiVLlsjixYsF/pl8fHykSpUq+l4Nc1Nu375dfvvtNxk2bJiWX6dOHZkwYYLOWenv7x/tOp/UDB988IFMnjxZJk2aJC+99JL8+uuv+t6zR48eMnbs2Ce1WZQ7GgSSJ08uJUqU0MWaLSQkRN+F7d69W8wybtw4OXbsmNy9e1fnb8XcfkWKFNGlcOHCus6bN68kSZLEWhS3SYAESIAESIAESIAESIAE4iGBP/74Q304ZM2aVTZt2iT58uWLh61kk0iABEiABEiABEiABEiABEggfhGAT/Dg4GC5evWqXLt2LcraGodt+KnCYraRHvmtIbHNhxv8AcLOIl26dPY1viFZ9822SQedAPiTiosB34wrVaokhQoVcikevocdPnxY8E319ddfV/+GLhPzAAmQQLwiAD+H0K3AAp95Y8aMkS+//FIGDBggzz33nHTt2lX960FHheHxEdi6davqvEybNk1u3bqldkELFy4U6Abht4uBBEgg7hDgaBl3zkWclGT//v3Svn17+eqrr9TJ8Pjx49VJba5cue6T9/fff5ehQ4cKPlytXLlSPvnkE1UezZgxoxqFLliwQN544w394V66dKkqmM6ePVuWL18uM2bMkO7du0uqVKmkcePG0qFDBxk5cqTWgR96KJouW7ZMjh8/LjVr1pRz585peiPEf//7X7lw4YKWfenSJWnXrp0eggNeE9zVA+VNGK6GhYXpQxcUN6HgipuL4sWLmyLcrsPDw2X16tUCRg8S8OBXv359WbFihT60gT8CHoIgHx6CateuLQ0aNJBBgwbpTRB4Q3nQXTtmzpypjojfeust/bCIcuAw2gRH+d3JgTzenBdTtll7kr9hw4aqZLxjxw45dOiQ9iXc7OGhr2TJkqYYXaPvwNm1CXCgDIfP6Hcvvvii9iGc0w0bNii7GzduqCPlqVOnynvvvafpKleurAqacAxdpkwZ7beRkZG6jXLxIO0sHv0UwV2fcqWID8fPkMvb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IkWKWBXnzj8fmQoH5JBFJ7c8UF135a58uHGaLDu9Q4ZV7Cwjq3aTDClSy5c758m1sJtSOkM+SWy7rk2dqOxw8DmtE8djM0Snfut3t2u3b6hY5TMWlLXn99tFPHHjkoTfidB2IdJd2+2ZPGxUyvyURNrG9OVndnpIKRJ5945svnRY8qXJIiUD88rik1s95olpghz+6TXrsesXJF3ye4aq3pRl5egs/YWQILl2+6b4J43ax71l7qxMxBl5/zi1TeYe2yBfVekqo/cukon7lsnH5drIlAPL5csd8yQk4rYWsf/aaV1n8wuUo9fP6zb+Baa419Z9/xy3H7BtpE3mJ/uvnrJGcZsESIAESIAESIAESIAESIAESIAESIAESMBGoFKlSjr51rp166Ru3bpk8pgJQAkd35LKlSv3mCVh9SRAAiRAAiRAAiRAAiTgnACeHaDDiok8of/kqNflPBdjSYAESIAESIAESIAESIAEngQC33zzjTp3hQMA2CP06dMnwTvmgL0fbBG/++47tauDAxYGEiCBuEXgmWeeEbyvwGQssNOCDefgwYPVWZSx+YhbEnuW5syZMzr2TJ06VZ1cbd++Xe2VPedMWClwfmF/jOXZZ5+N0viwsDA5fPiwHDx4UJdDhw4JlmXLlsnJkyfVPhgZMmTIoE654MgGCxyLme2H5dw8imDcIQESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESiDUC8BsGf36wO5wzZ440btw41upK6AXD7y7s7zt06BAFxcO0z/Tz84tSdlzZcWyjKxaxJa+r+uIqr9jiwHJJgARIgARIgARIgARIgARIgATiL4FTp07JW2+9JUWLFpWuXbve19CcOXNKr1695IMPPpCxY8dGSYM5pLJnzy5LlizR9xazZ8+WRDbf7CYEBgZKeHi42dV1qlSufX8HBAToO5AoGdzs/PXXX7J69Wqdv90kS5IkiXTs2FGGDx8u/fv3F2+f4YcOHSqlSpXSxZTVrl07ef3112XcuHEyYMAAE/1I140aNdK5usAVc3rt2LFD38lduHBB568sUqSI+l1q06aNVK5cWedQs56DRyrsE14Z5tkDw6+++kqqVaumrUmdOrX8+uuvUr58eb1ORo4c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AggggAACCCCAAAL2AjpJpf72oN8dBw4caL+J9WwQ0N+e9PefTz75xARPy4ZTUCUCCCCAAAIIIIAAAiaYa+vWrU1g3VWrVknJkiVRQQABBBBAAAEEEEAAAQRuKaATdrZp00Z0TGJq9zBqcPlatWrdsi52QAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIHsE9Apey5evGjip2s8dCtbcdN16ShOupZb86DoUvOVK1ccNlTnIfH19ZXixYubbK3rMnn28/NLUVaoUCGH9VKIAAIIIIAAAggggAACCCCAAAIIIIBAbgqEhYXJQw89JD/99JN07Ngxx5sSGxsrGzZskMjISJM3b95sxvjr9bjGjRtLcHCwhISEmHzHHXfkePs4IQK3K6BzJVmvb11u3bpVrl69KnoduUmTJrbXeNOmTYmRd7vYHI8AAggggAACCCCAAAII5IKAxilctmyZ6DWWb7/9VhITE6Vbt25mDp4uXbpIgQIFcqFVnDItAb3PbOHCheY5W7FihZmnSr+X//jjj1KwYEETg7Jr166iuVq1amlVlea2nTt3mtfE4sWLJSoqysTI79Spk9x///3SvXt3c940K2BjqgJHjhyRihUryurVq0XnH0hviouLkwYNGphrMt988424ubml91D2QwABFxXQe4L37Nkj0dHREhMTY7Kua5lep9X/B/T/k8DAQJODgoJsS72GS0IAAQQQQAABBBDIvMCnn34qI0eONN+zw8PDxd/fP/OVcSQCCCCAAAIIIIAAAggggAACeVTA+v587Ngxc395Hu1mnurW9u3bze/++rt1QkKC+b1J7xXRpONjypUrl6f6S2cQQAABBBDIbwJufwezu5nfOk1/EcisgE747ii4Y3rLrOCQly9fdtgEd3d3KVq0qLnhtlixYrZlZsq4odshMYUIIIAAAhkU0I+Kly5dEvtAx9b7WfKy1Mqt98nU3v88PDzEx8fH9r6n74Hpfe+z31cDJ3t6emawh+yOAALOLLDnbKwER4xy5ibSNgQQSEUgwNtfokOnpbI154rbtm1rBmSm94w66Mq6VPbhhx/K448/nt5D2S8fCUydOlUmTpwo+oM3CQEEEEAAAQQQQAABBBBAAIHkAhrUSwPMf/DBB8k38TiLBTSov1prUB0NokNCAAEEEEAAAQQQQCCrBXTgXJs2bUzQ3lWrVkmpUqWy+hTUhwACCCCAAAIIIIAAAnlYYMeOHdKqVSszQa1O7mIlHfegk+d+//33VhFLBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEMgDAlevXpWzZ8+amO66PHPmjHlsv568TB9bWfe7ceNGCgkvLy8z4aOfn59Z+vr6Jnlslfv7+5tyXVrrBQsWTFEfBQgggAACCCCAAAIIIIAAAggggAACCGSVQGhoqPzyyy+yfft2c00qq+rNTD2JiYmyc+dOiYyMNDkqKso81mtuZcuWlZCQEFvWmLY6ByIJAWcROHnypGzcuNH2+tXX8alTp0wcvLp165rXbnBwsFnWrFlTdJ4tEgIIIIAAAggggAACCCCAQN4ROH/+vCxYsEDCwsJkzZo15h4gve4yaNAg0bmQSLknoNeWVqxYYZ6bRYsWid4j1qVLF/PcdOvWTQoVKiR//fWX/PDDD7J06VJZtmyZuWdMv7937drVZI1Lmdn7uHTu5CVLloieW9vh7u4unTp1kvvvv1969Oghei8ZKWMCAQEB8swzz8jzzz+foQP1OqjOlT5u3Dh56aWXMnQsOyOAQN4R0PeFffv2SUxMjMk6f56u79q1y8Qg1p6WKVNGgoKCzLx6OreetV6uXLm8A0FPEEAAAQQQQACBbBDQ79xPPvmkfPLJJ/Liiy/KG2+8Yb4HZ8OpqBIBBBBAAAEEEEAAAQQQQAABlxdo0aKFVKxYUebMmePyfckvHfj111+lZcuWsm7dOvN707Rp02TDhg2m+xpvhjEu+eWVQD8RQAABBPKqgNvNv1Ne7Rz9QsBZBXTC+HPnztmy3pBt/1jXHZUlL9d9HAWA1H4XLlzYfFjXD+xFixa1revjjJR5e3szMNhZX0i0CwEEEEhD4Nq1a7b3FkfvKY7Kkr/PWI81IIqjpAGHM/Ke4uj9SI/X9xoSAggg4Ehgz9lYCY4Y5WgTZQgg4OQCAd7+Eh06Lddbeccdd8jRo0cdtsPT01P0sph+r9b1OnXqiP6QqYGyRowYIe+8844MGzbM4bEU5m+BqVOnysSJE0UHUZMQQAABBBBAAAEEEEAAAQQQSC6gwaE0ENuWLVuSb+JxFgt89NFHJhCXTqKpwc1ICCCAAAIIIIAAAghkpYD+FtSmTRvx8PCQVatWmSCNWVk/dSGAAAIIIIAAAggggEDeFtBxGAMHDjQTgOhYCp0QRMcUWkl/S7j77ruthywRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAETJ1Xjx2ssDft8+vRpSZ51e/KyhISEFIoaA97f31/8/PzMUtetXKJECYfrul1j0JMQQAABBBBAAAEEEEAAAQQQQAABBBC4lYBeo9I5f5o1aybz58+/1e45vv3ixYuyadMmiYyMlKioKLPUuYzc3NwkMDBQQkJCbPmuu+4ycxjleCM5Yb4TuHLlivz222+216S+Pvfu3WscqlatantN6jxaDRs2lEKFCuU7IzqMAAIIIIAAAggggAACCORngSNHjsjs2bMlLCxMoqOjpXr16jJo0CB58MEHRb83knJGYNu2beY5CA8PN/MXN23a1DwPoaGhovddpZY07uSvv/5qYlEuXbrUPIcak7JDhw7StWtX6dKli5QrVy61w9Ms12tx3377rSxYsEB+/vlns6/WN2DAAOnevbsULlw4zePZ+I+AmulzqH9jGU1TpkyR5557Tn766Sdp3759Rg9nfwQQyMMCN2/eFH0Pj4mJMf/32y9PnTpleu7r62t+mwgKCjJL/Z1C1ytVqmR+t8jDPHQNAQQQQAABBBC4pYDey9GnTx/ZtWuXfPnll9KrV69bHsMOCCCAAAIIIIAAAggggAACCORXgd27d0vt2rXN75adOnXKrwwu1+9HHnlEtm7darLVeL0WsnnzZjPnpVXGEgEEEEAAAQRcU8Dt75tHbrpm02k1AgiogA5I1yCQ586dS5Idlek+jsq17NKlS4AigAACCOQDAQ8PD9GBIsWKFbPl5I91W3rKPD0984EYXUQAgdwU2HfuuDRa8ExuNoFzI4BAJgUqFS0l2x54P5NHZ81hN27cMMGvdOnu7i76OejatWtmIFS1atWkZcuWokGyNNetW1cKFChgO7EOpJo8ebIMGzbMVpZbKzrQSwf81qtXTzp27JhbzcjQeffv3y/jx4+Xf//731KhQoUMHZvazhcuXJCvv/5aDh48KDpoWi3sn7PUjsuO8qlTp8rEiRPNAO7sqJ86EUAAAQQQQAABBBBAAAEEXFtAA93379/fTOLo4+Pj2p1x8tYPHTrUBEZfvXq1k7eU5iGAAAIIIIAAAgi4mkBcXJy0adPGTNSunzfLli3ral2gvQgggAACCCCAAAIIIJCLAjpkV+8/1Elbvv/+e6lZs6a0bt1aDh06JHpPo96zqBOBkhBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgawUuHz5spw6dUpOnz6dZGlfputWPnnypFnXeUySJy8vLylRokS6c8mSJUXjubq5uSWviscIIIAAAggggAACCCCAAAIIIIAAAnlcYMWKFWYemZkzZ8pDDz3k9L09duyYbNiwQaKioiQyMlI2bdpk5vnVa2KNGjWSkJAQk3VOo0qVKjl9f2igcwtoDIq9e/ea15r1mtOYE1evXhV/f39p0qSJ7TWnrz29LktCAAEEEEAAAQQQQAABBBBAwBLYsmWLhIWFyZw5c0Rjp7do0UIGDRokffv2FT8/P2s3llkkEBsbK+Hh4fLVV1/J9u3bpUqVKsb7wQcflBo1amTqLDoPs8al1LmpV61aJQkJCeZ6QM+ePaVHjx5y1113Zares2fPyqJFi0x7V65cKUWKFJHevXvLwIEDpX379mYu70xVnA8OGj16tPz444+ybdu2TPU2NDTUPJf695lV83ZnqiEchAACLiOg7+ExMTEmR0dH25b6e4Um/T+8du3aEhgYKEFBQbZltWrVxNPT02X6SUMRQAABBBBAAIHMCugccfpdS8cmLVy4UGrVqpXZqjgOAQQQQAABBBBAAAEEEEAAgXwh8OKLL5o5EvX3aHd393zRZ1fv5Pnz56VcuXLyn//8R0aMGOHq3aH9CCCAAAIIIOBAwO3vgYQ3HZRThAAC+Uzg+vXrZtC6BnfUrF8GdHnhwgXhv4l89mKguwggkCcEChYsKEWLFpVixYrZsj729vbOE/2jEwggkH8Elh3dKpeuX80/HaanCOQRgSpFy0i9EpVztTf6vbZUqVImUFbz5s2ladOmZoCsBmrz8fFJs206YcHkyZNl2LBhae6X3Rv37dsnU6dOlffee08+//xzGTJkSHafMkvqX7BggTzwwANmgPK9995723Xu3r1bunXrZhzuvvtuWbJkiYwZM8YMYtfHOZ30OZk4caJYA+xy+vycDwEEEEAAAQQQQAABBBBAwLkF/vzzTxPYSQPft2vXzrkb6+Ktq1+/vplcQK/jkBBAAAEEEEAAAQQQyCqBv/76S9q2bSt6X60GFtKBdSQEEEAAAQQQQAABBBBAICMCTz31lMyYMcNMxtG1a1dzaHx8vPmusXPnTjN5S79+/TJSJfsigAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALZJqBjKk+dOmXLJ0+eNOu6TCsnJCQkaZOHh4eJA1uiRAkpWbKkyfbrVpm11LixxYsXT1IHDxBAAAEEEEAAAQQQQAABBBBAAAEEXFPg2WeflU8//VS2bdsmVapUcalOJCYmSnR0tERFRUlkZKTJO3bskBs3bkiZMmUkJCTE5ODgYDP3Ede0XOrpzfHGnjhxwvZasl5Tp0+fFp1btF69erbXk76uatSokePt44QIIIAAAggggAACCCCAAAKuKaDXKZYtW2bm8f3mm29Er2dorMNBgwZJly5dzPdO1+xZ7rf6woULJnZkWFiY6HxTeu1H52RW25YtW2ZpAy9fvizLly+XxYsXm7mZ4+LipGrVqtKjRw/p2bOnOZ+np2eGz3n8+HGZN2+ehIeHm+sSd9xxhwwePFgefvhhqV69eobry+sHzJo1Sx599FG5ePGiZMZbXzN6rbBYsWKydu1a/v7y+guG/iGQjQJnzpyRmJgYk/V3Cmv94MGDcvPmTfP/i15HDgwMlKCgINuyZs2aUrhw4WxsGVUjgAACCCCAAAI5J/Duu+/K6NGjpVevXjJz5kzx8fHJuZNzJgQQQAABBBBAAAEEEEAAAQRcUEDvH6hYsaIMGTJExo8f74I9yJ9N/uSTT+R//ud/JDY2Vvz8/PInAr1GAAEEEEAgjwu4/X2zx8083ke6hwACCCCAAAIIIIAAAggggAACCCCQDwV0AoPMDMT09fWVyZMny7Bhw3JdTQdt6eCsr776ygweTm+DdH8drJtbSYOZ6YQOGU3x8fGyefNmueeee2yH6mD0gIAAEyjQKtRByPv37zcDZa2ynFpOnTpVJk6cKMeOHcupU3IeBBBAAAEEEEAAAQQQQAABFxOoVKmSua7w8ssvu1jLXae5GhBNg2hpQK7Q0FDXaTgtRQABBBBAAAEEEHBqAf2tql27dqITnq9evVrKly/v1O2lcQgggAACCCCAAAIIIOB8AvrbwKRJk2TOnDlm0hD7FmpQ9y+++EKefPJJ8fDwsN/EOgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg4HICly5dkpMnT5qscUg162P7ZfJ1PcY+FShQQEqUKGFimGoc01KlSiVZ18dWtrbrMSQEEEAAAQQQQAABBBBAAAEEEEAAAecSuHLlijRp0kSKFy9uYni5+ph6vY6l8+dERkba8pEjR8TNzU1q164tISEhEhwcbJZ16tQRrlk51+sxp1qjr/utW7faXiNRUVGyb98+c/rq1avbXiP6eqlfv74UKlQop5rGeRBAAAEEEEAAAQQQQAABBPKwwPnz5yUiIsLMc7xmzRrROZh17h6dw7hp06Z5uOdZ17UbN27IihUrJCwsTBYtWiRXr14VnTt50KBB0q1btxz5Dn/z5k1zTeHbb7+VxYsXS3R0tPj7+5t29OzZUzp37ixFixbNcKd3794tM2fONH2LjY2VVq1ayaOPPip9+/YVLy+vDNeXFw/47bffpEGDBsY8MDAwU13ctWuXuR6qf3fTpk3LVB0chAACCKQmoL9R6P/n+t4QExNjlrqu15+vX78u7u7uUrVqVdH/w4KCgpIsfXx8UquWcgQQQAABBBBAwKkELl68aL6vLliwQCZOnCgvvPCCU7WPxiCAAAIIIIAAAggggAACCCDgrALff/+9dO3aVfbu3SvVqlVz1mbSrmQCOqagRo0aMmvWrGRbeIgAAggggAACeUXA7e+bAm/mlc7QDwQQQAABBBBAAAEEEEAAAQQQQAABBG5XQAc/T548WYYNG3a7Vd328TpQSwPH6Y91AwcOTFd9q1atMoOOjx49mq79nWUnHUB9zz33SJ8+feSJJ56wNUsH1RYuXFjWr19vK3v88cdFB9xqoL2cTlOnTjU3kR87diynT835EEAAAQQQQAABBBBAAAEEXESgf//+cvbsWdGbp0nZI6DXCZo3b24C2mggGxICCCCAAAIIIIAAArcroBOYt2vXTjSwkAZMrlChwu1WyfEIIIAAAggggAACCCCQzwQmTZokL730knz++efy8MMP57Pe010EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQODWApcvX5b4+HjRcZ32OXmZPtZ88uRJ0Xil9ql48eJSqlSpFLl06dK2Mvv1QoUK2R/OOgIIIIAAAggggAACCCCAAAIIIIBANgls375dmjRpIq+88oqMHTs2m86Se9UeP37czJOjc+Vo3rRpk5w7d87MqdOoUSMJDg6WkJAQkytXrpx7DeXM2SKg07//8ccfSV4D27Ztk2vXrkmJEiXM82//GvD398+WdlApAggggAACCCCAAAIIIIAAAvYCR44ckdmzZ0tYWJhER0dL9erVzXzGDz74oDCfj73UP+v6XV6twsPDReckbtq0qfEKDQ013+9THpFzJXv37pXFixfLt99+K7/++qt4eHhI+/btpXfv3tKrVy8pWbJkhhqj91wtW7ZMZs6caer09vaWwYMHi84HHRgYmKG68trOOheBj4+P8e7evXumuzd//nzp27dvhuYcz/TJOBABBBD4W0CvR+/Zs0diYmLM+7611LKEhARjdMcdd0hQUJD5v16X1jrXrHkJIYAAAggggIAzCeh34Pvuu0/0Poy5c+ea77/O1D7aggACCCCAAAIIIIAAAggggIAzCzzwwAPy119/mfnXnbmdtO3/BHbs2CF16tSRlStXStu2bf9vA2sIIIAAAgggkKcE3P4egHgzT/WIziCAAAIIIIAAAggggAACCCCAAAIIIHAbAr6+vjJ58mQZNmzYbdSS+UPXrl0rq1evFp2goGHDhtKpUyczIHvAgAGmUp0sQbdv2bLFDOgdNGiQlC9f3mxbtWqV9OzZU9zc3OStt96SgIAAsQaj6kCuDRs2yO+//y4tWrQwN0VnpJVLliyRffv2mUGuQ4cOlfPnz8tXX31lBo6VK1dOdMCzpsTERPOjsA6G1cB+mq5fvy7aNnd3d2nWrJloXbt375Z+/fpJzZo15cqVKzJw4ECJiIgQ7efdd98tPXr0EK33jTfekFdffdUMstZB6BcuXDAD0d977z3p37+/qT8n/5k6dapMnDjRDPbOyfNyLgQQQAABBBBAAAEEEEAAAdcR0O+O+l321KlT5ju667TcdVr6/vvvy7hx48wkka7TalqKAAIIIIAAAggg4KwCZ86ckXbt2oku16xZIxoUkYQAAggggAACCCCAAAIIZETggw8+kCeffFJ0OWLEiIwcyr4IIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACqQjolEqnT5+W+Pj4JPnEiRO2xzo5pv32q1ev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KS0tTXfccYcdrN90Pn7//ffVq1cv7dixQ++++67uueceff/99+rUqZPt0DR8+HA9+uijMudOnjzZdn566qmnVKFCBfXt21etWrXSzJkzVa5cuXy16tixY7Zs88C16TT1xhtv6LPPPtPx48ftw9jmIWzThlmzZtnOWaZTb9u2bW2n1dGjR9tOsR999JGuu+46PfLII5ozZ44aNGigTz75RI0bN1bHjh21ePFitWvXThMnTlRYWJhOnDihIUOG6KefftK9996ruLg47d27V6NGjVKNGjXyFXdBZhozZozefPNNO3FCQZZbVGVln74dfPXU+3Qo7VhRVUk9BSDQpGItLer2WgGURBEIIIAAAggggAACCCBQlALm+/jvv/9uv6sXZb3Fua6DBw+qfPnydsAXc2+EBQEEEEAAAQQQQACB/AqY39xeeuklTZo0icmn8otGPgQQQAABBBBAAAEEEHAImGf0zGQh5pk1MyiHn5+f4xgbCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgi4skBWVpb27Nljx1pNTk62496adW5KSUmx22adnp7uaKqvr6+qVKniSMHBwY7t3P1mnxkH18PDw3EeGwgggAACCCCAAAIIIIAAAggg4H4CTz75pMaNG2fnngkPD3c/AFqMAAIIIIAAAggggAACCCCAAAIIIHBFBXbu3KmuXbvKzMs8b948O+fyFQ3oClZ+6NAhzZgxw85HvXz5clWsWNHOm3333XerSZMm541sxYoVev755/XDDz+oR48edk5rV73Xd/ToUZUpU0bz589Xly5dztvmSz0wYcIEDRw40M7b3bNnz0sthvMQQAABtxEwz7Ga+R43bdqk+Ph4x3rr1q1KS0uzz6CGhYXJfO7Uq1fPrs123bp1Vbp0abdxoqEIIIAAAgg4s8B///tfPfbYY+rUqZP9vhkYGOjM4RIbAggggAACCCCAAAIIIIAAAi4jsHv3bpl75DExMbrttttcJm53D7RPnz7at2+fli5d6u4UtB8BBBBAAAG3EfDIOb24TWtpKAIIIIAAAggggAACCCCAAAIIIIAAAn8jYB4mfueddzR48OC/yVk4hzMzM+1A+6GhocrIyJC5fVeiRAlHZdnZ2Tp58qT8/f3tPnPc5DszT2pqqjw9PXXVVVc5zjOdU898bQbsNwP1X8piflA0HXzNYjpQlSxZ8lKKOesc0xbzQ3NISMhZx06cOCHT4TooKEhly5Y963hR7RgzZozefPNNO/FBUdVZkPWkZ2Wo8qR+BVkkZRWBQJBfoLZEjy2CmqgCAQQQQAABBBBAAAEEClLgk08+0ZAhQ2S+p3t7exdk0W5bVmxsrDp37qy9e/c67k24LQYNRwABBBBAAAEEEMi3wEcffaT77rtPH3zwgR5++OF8n0dGBBBAAAEEEEAAAQQQQMAIJCYmqnXr1ipfvrwWL16sgIAAYBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQcEuBgwcP2jFZzfixycnJedKZ+8w4srmLj4+PHU+2SpUqCg4Otulc2xUqVJCHh0fuaawRQAABBBBAAAEEEEAAAQQQQKAYCZi5eNq2bav9+/dr9erVeebPKUbNpCkIIIAAAggggAACCCCAAAIIIIAAAk4scOjQIfXs2VPr16/XnDlz1K5dOyeOtmhC++OPP/TZZ5/Z9Ntvv+naa6/VXXfdpTvvvFPVq1c/ZxDz5s3TCy+8oLi4ODv39/Dhw11uHikzf7aZT2zq1Km67bbbztnOy91p5kWfOXOm1qxZo1q1al1ucZyPAAIIuKVAdna2duzYofj4eG3atMmuzfbWrVt18uRJ+8xpWFiYwsPDbapXr55j7e/v75ZmNBoBBBBAAIGiFkhLS9NDDz2kiRMnaujQoXrllVfoF1LUF4H6EEAAAQQQQAABBBBAAAEEirXAW2+9pXfeeUdmLAdfX99i3dbi0jjTb8aMq/Hxxx+rX79+xaVZtAMBBBBAAAEE/kbA4/RDaTl/k4fDCCCAAAIIIIAAAggggAACCCCAAAIIuI1AYGCg/aHTdLR0h+Wbb76RSRdaQkJC9OKLL14oi9scGzNmjN588007wYErNjo9K0OVJ/FjsKtduyC/QG2JHutqYRMvAggggAACCCCAAAJuL7B582aZwUTMoPZNmjRxe4+CADDfyceNG6edO3cWRHGUgQACCCCAAAIIIOAGArNnz7YDt+YOLuQGTaaJCCCAAAIIIIAAAgggUIACe/fuVZs2bezEEEuXLlX58uULsHSKQgABBBBAAAEEEEAAAQSZqAqPAABAAElEQVQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEECgeAocOXLEjt1qJjJNTk62E5qeuZ277/jx4w4AHx8fBQUF2ck0zYSa50vlypVznMMGAggggAACCCCAAAIIIIAAAgi4joC5N9CoUSPdeOONmjFjhusETqQIIIAAAggggAACCCCAAAIIIIAAAsVGID09Xf3799ecOXM0fvx43XnnncWmbZfbkJ9//lmfffaZpk2bpv3796tt27a655571KdPH/n5+eUpPicnR5MnT9YLL7wg8/zPSy+9pCFDhqhEiRJ58jnzizJlyui9997ToEGDCiXMtLQ0tWjRQtnZ2Vq5cqVKlSpVKPVQKAIIIOCOAubf1h07dmjTpk2Kj493pC1btsj8++vh4aFq1aopPDw8T6pbt+5Zn2nu6EebEUAAAQQQKCgBM69w79699fvvv9vvkzfffHNBFU05CCCAAAIIIIAAAggggAACCCDw/wSuvfZaderUSWPGjMHERQRGjRqloUOHKiUlhd8lXOSaESYCCCCAAAIFIeBdEIVQBgIIIIAAAggggAACCCCAAAIIIIAAAgi4pkCNGjXUrl27CwYfEBBwweMcRAABBBBAAAEEEEAAAQQQQACBswXMw9Rly5bVihUr1KRJk7MzsOeiBdasWYPlRatxAgIIIIAAAggg4L4C3333nR2496GHHtIrr7zivhC0HAEEEEAAAQQQQAABBC5J4NChQ4qKilJWVpaWLFmi8uXLX1I5nIQAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgLsJlClTRibVqVPngk0/cuSIkpOTtXv3bpvMdlJSkt1ev3695s+fb4+fPHnSUU7JkiVVpUoVBQcH2xQSEpJnbfabff7+/o5z2EAAAQQQQAABBBBAAAEEEEAAgSsvYL6zT5061fbjf//99/XYY49d+aCIAAEEEEAAAQQQQAABBBBAAAEEEEDArQR8fX3tPapnnnlGd999txISEvTcc8+5lcH5Gtu0aVOZ9O6779pndiZMmKB7771XjzzyiG677TYNGDBALVu2tKd7eHiof//+6tOnj0aOHKmhQ4dq3LhxGjNmjLp06XK+Kpxqf+nSpXXs2LFCi8k84zRr1iw1btxYDz/8sMaPH19odVEwAggg4G4Cnp6e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BBBAAAEEEEAAAQQQQACB8wscOnRI1113nWrVqqUFCxbI09Pz/Jk5ggACCCCAAAIIIIAAAggggAACCCCAQCEKrFq1St26dVONGjX09ddfq1KlSoVYm+sXffLkSc2aNUsff/yxfvjhB1WvXl2DBw/WwIEDFRQUpPT0dL311lsaMWKEIiMjNX78eDnj2J6tW7dWo0aNNHr06EK/KFlZWerYsaN27dqlX375RYGBgYVeJxUggAACCFy6gPks27Jli+Li4hQfH2/XZvvPP/9UTk6OnQenXr169vPNfMblJvNsKQsCCCCAAALFVWDr1q3q1auXDhw4oBkzZujGG28srk2lXQgggAACCCCAAAIIIIAAAgg4hcDkyZN17733yox7UKFCBaeIiSAuLJCWlmbHpHjuuef0zDPPXDgzRxFAAAEEEECg2Al4nH6gIKfYtYoGIYAAAggggAACCCCAAAIIIIAAAgggUAACp06d0vz58xUTE6OvvvpKmZmZ6tKli/r27avu3bvbjjoFUA1FOInAsWPHNHfuXHu9Y2Nj5evra6+zud6mQ7LpZLtx40bbYcuszY/iZilbtqztpBUREZFnXVQdco8ePaqrrroqX4rpWRmqPKlfvvKSyXkEgvwCtSV6rPMERCQIIIAAAggggAACCCCQbwEziVxAQID9rnnbbbfl+zwyni3w2muvacKECdqxY8fZB9mDAAIIIIAAAggggMBpATO4kBms1UwiZQacLVeuHC4IIIAAAggggAACCCCAQL4FzIQMt99+uxYuXKjFixerSZMm+T6XjAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIFC8BFJTU5WUlHTelJiYqH379ik7O9s23PRxrlSpkkJCQvKk0NBQ+zp3nd8xZIuXJq1BAAEEEEAAAQQQQAABBBBA4GwBMwdMq1atNGTIEI0cOfLsDOxBAAEEEEAAAQQQQAABBBBAAAEEEECgiAS2b9+url272udAzDzWtWvXLqKaXbuaLVu26MMPP9SkSZN05MgR9ejRQw888IA6dOggc2zgwIFau3atXnzxRT3//PPy8fFxmgbfeOONMvNQf/DBB0US0549e9SoUSM1bdpUc+bMkYeHR5HUSyUIIIAAAgUncOzYMW3atEkbN25UXFycI6WkpNhKypYtq/r169tkPmPMtlkHBgYWXBCUhAACCCCAwBUQMN9h+vfvr3r16mn27Nm2f8QVCIMqEUAAAQQQQAABBBBAAAEEEHArgXbt2tm52c13cRbXEJg2bZruvvtuJSQkKCgoyDWCJkoEEEAAAQQQKDABj5zTS4GVRkEIIIAAAggggAACCCCAAAIIIIAAAggUUwHTOcc8nBwTE6NFixbJ19dXPXv2VN++fRUVFSVvb+9i2vLi3axTp05pwYIF9rp++eWXysjIsNfTXFfT8djf3/+8AAcPHrSdtEyHrTM7bZkJAsxiBvY/s6OW2a5bt65975y30Is88Nlnn9kO0Y8//rheeOEFBQQEXLCE9KwMVZ7U74J5OOh8AkF+gdoSPdb5AiMiBBBAAAEEEEAAAQQQyJdA48aN1bp1a40aNSpf+cl0bgHzPd3cj5kxY8a5M7AXAQQQQAABBBBAwK0FTpw4ofbt28sMrLd8+XIGGXLrdwONRwABBBBAAAEEEEDg4gVMN1szcKkZKOXbb7+19/UvvhTOQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAXcSMOPYJicnKykp6ayUmJjo2JeWluZgKVOmjO0LbcatDQkJsePX/nVdoUIFeXh4OM5hAwEEEEAAAQQQQAABBBBAAIHiKjB58mTb13/q1KmKjo4urs2kXQgggAACCCCAAAIIIIAAAggggAACLiCwb98+devWTb///ru+/vpr3XDDDS4QtXOEaJ6NmTVrlsaNG6cff/xRderU0YMPPqh+/fpp4sSJeumll1S7dm2ZOZjDw8OdIug2bdqoQYMGGjNmTJHFs2zZMjufwhtvvKFnnnmmyOqlIgQQQACBwhU4cOCA4uLiHGnjxo12OzU11VZsnhetX7++IiIiHOu6deuqZMmShRsYpSOAAAIIIHCZAtnZ2fb73FtvvaX77rtPo0ePVokSJS6zVE5HAAEEEEAAAQQQQAABBBBAAIG/E9ixY4dq1aqlL7/8UrfccsvfZee4kwhERUXZe//murEggAACCCCAgPsJeJyeGDvH/ZpNixFAAAEEEEAAAQQQQAABBBBAAAEEELh0AdOpd8aMGYqJidHKlStVvnx53Xrrrerbt69atmzJAO2XTlskZ5pboj/88IO9fqaD8aFDh9SqVSt7/cx1NNfzcpaEhASZTlq5HbXMesuWLUpPT5eXl5euueYaR0et3E5bV199tTw9PS+62scee0wffPCBPdff31+mE/D9998vb2/vc5aVnpWhypP6nfMYO51XIMgvUFuixzpvgESGAAIIIIAAAggggAACFxR49NFH9dNPP+nnn3++YD4OXljATJI3ZMgQPfvssxfOyFEEEEAAAQQQQAABtxPIyspSr169tGLFCpvMALIsCCCAAAIIIIAAAggggMDFCDz00EP65JNP9NVXX8kMwMGCAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCBSUwIEDB5SUlKTExMTzrg8fPuyoztfXV2a8ndDQUJvO3M7dV7lyZTvOreMkNhBAAAEEEEAAAQQQQAABBBBwUYHHH39c48aN0/Lly9WoUSMXbQVhI4AAAggggAACCCCAAAIIIIAAAggUB4ETJ07otttu05IlS+y81TfffHNxaFaRtsHM4fyf//xHU6ZMkZk/2sz13aNHDzvf8q+//qqRI0fKzOfl4eFRpHH9tbI2bdqoQYMGGjNmzF8PFerrf/3rX3ruuef03XffycTAggACCCBQfAUSEhIUFxcn89loktnevHmz0tPT7fOftWrVUkRERJ5Us2ZNeXp6Fl8UWoYAAggg4DICBw8e1B133KGlS5fq//7v/zRw4ECXiZ1AEUAAAQQQQAABBBBAAAEEEHB1gaFDh+qjjz6yYxN4eXm5enPcIv5du3apRo0amjVrlnr16uUWbaaRCCCAAAIIIJBXwOP0w3I5eXfxCgEEEEAAAQQQQAABBBBAAAEEEEAAAQTyK/Dnn39q6tSpiomJsR1wwsLC7MPMpoNqZGRkfoshXxEImI7C5jpNmzbN/qhtOuqa62QePq9atWqhRpCZmalt27Y5Omrldtr6448/lJ2drVKlSik8PFz169e3HbZy11WqVLlgXK1bt9aPP/7oyGM6d1WvXl2jRo1St27dHPtzN9KzMlR5Ur/cl6xdRCDIL1Bbose6SLSEiQACCCCAAAIIIIAAAn8VMPcN+vfvr9TUVPv976/Hef33AikpKTLfkRctWqQOHTr8/QnkQAABBBBAAAEEEHArgfvvv1+ffvqpHSi1WbNmbtV2GosAAggggAACCCCAAAKXL/DM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zyxntpE77rhDq1ev1l133aUXXnhBU6ZM0csvv6xrrrnmL5tYt26dmjdvrnfeeUfXXXedVq1addwx+/fvd9v2nHV0dPRx+6yfpKwrYM/U2O+QXQN37NhRXbt2dT8T9rvx2cpf5DdrkZSauf73rXy1PloxUan/fcbejhq9brauLFFVvSq20nUx9TRn+/E/O5lr+X+10t8r/V/psbU0HXu2/8Ry7/ap9qdv84naN6lJsSrqNOElJaYkq0Nsfe/hZ73sUr6ZroquqSfmfK41+7f52klJS1WFyOIK9AvQ0bQUX/npVrYn7NPepEPKExR6UrW9Rw6pdpFy8vf8zqV/DbzntP2nStF5Crtd6w9s15tLfnT9uqFMI/Wb8S8VDs2nrhWa657pTzuTnYn71ax4FZWKKKzBDXv5mvT+qncs01BtomvpnhnvK37zXt9+74odvyfpoFbv2+ot8i3zhxyby3zzoV2+MlYQQAABBBBAAAEEEMgpAn/++af+85//yO4jkLKPwLBhw1S1alV3fyf79JqeIoAAAggggMClEAi8FCflnAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAII5GaBevXquS+Lz5w5U7EEVvD9KFgwCgs8QUIAAQQQQAABBBDIOQIjR47USy+95AKONmjQIOcMjJEggAACCCCAQI4WsPt2AwYMcAEbb7nlFo0fP/6MgidmJRwLAP/zzz+7MTzyyCMuCIEFwn/22WfdZCFZqa/05dwE9u3bp61bt2rbtm1u6V33TigTHx/vJpGx7eTkZN/JbFKJwoULuwlkoqKiZNkmQyhSpIgv237btqUFSb3QgTotAOv111/vgkxaYNQLfT4fBisIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICARyA0NFQVK1Z0OSOQxMRExcXFaf369Vq3bp1b2rp9N+qrr75y3+XyHmffybJYY95cpkwZt+5dRkREeKuyRAABBBBAAAEEEEAAgQso8MILLygmJkZ33XWXNmzYIItnEBjI1N4XkJymEUAAAQQQQAABBBBAAAEEThAICAjwxfg7YVeGm0ePHtXOnTt9cQQtlqA3vqAtLebg4sWLXQxC205NTfW1ky9fPhUvXvy0uUSJEoqMjPQdwwoCCFw6gRtvvFFhYWGy5eHDhzVs2DDuXZ3hy/Hggw/qo48+0pgxY9S+ffuTjl67dq0rs5irJ6YuXbro2muv1YsvvqjHHntMQ4YMcbF7b7vttiz/OtizB0OHDnVxXK+44gr17NnzxOGxjQACCCCAAAI5SMDeMzZq1Mhl77Ds/eNvv/2m+fPny+YC/eGHH/TKK6+4a8RChQq5uUHr1q0ryzaHaqlSpbyHskQg2wvYZ/6VK1fW7NmzZe/507/f//LLL9WpUyd550z79ddf9fDDD/vG/Mcff7g5G+x36kyTzWFi1yD333+/rrnmGtk9nxPT6NGjlZaWph49ehy3a9q0abryyiuPKzvTDWujSZMmyps3b6YPNSu7V/TJJ5+4OSc6dOigkJCQTB3/zDPPOKvrrrvO1U9/D8oKqlWr5sonT57srmvdBv9kWQGbn8SSzQHSpk0bdevWzV1Tnusz7dF5CqvHZS302crJx4196uYl6lWxlZJS/jdHSkpaqkIDgo6rd6k3YvIU0YCandT/Px8q8b999fc7ZnW2fSsaFqlBDXpoTvwqvbt0nK+ZukXK64/dcYoIClXvSq31wfIJvn2RweG6sWwTfbxikq8s/cqKvZtUxNPuiWn+jtW6Lqaeqhcso992HbsHYnVqFIrVjoR9Wn8gXoVD8514mNu+ovjl+m3nWm331LPUsUxDTd+61G03L1FNc7ev0sZDO90+++dXz74qw/v5tm0lLCBYW3sO1bPzv9GnK38+bl/6jUZRFWWus+JXpi9261Hh+d3fzbiDO07aRwECCCCAAAIIIIAAAtld4NNPP1XJkiXddVh2H0tu6X9SUpJGjBjhPj/OLWNmnAgggAACCCBw9gJ8S/Xs7TgSAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIGzErCJpWrVqqWZM2fKAkWRjgksXLhQAwcOhAMBBBBAAAEEEEAghwgsWeIJ3NKrl/r166fevXvnkFExDAQQQAABBBDIyQIrV650X9K3YIwtWrTQ3LlzXTDUnDBmCzxpwftsAoGnnnpKFvDSglIOGDBAefLkyQlDzJFjSElJcRO82MQu27ZtcxO8eJfpy2w9MTHRZ2ABPKOiolSsWDHfskaNGm69aNGibmn7bb1w4cIZBiX1NXaRVyxIsU260LJlS33++efyBqC8yN3gdAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggMApBSyOWMWKFV3OqNLhw4cVFxen9evXa926dW5p69OnT3ff89u5c6fvsCJFiig2NlZlypQ5bmlllu1cJAQQQAABBBBAAAEEEDg/AnfeeadKliypm2++WZs3b9bIkSOVN2/e89M4rSCAAAIIIIAAAggggAACCCBwngUsrqDFFLT8V8liF8bHx7uYhRaf8MQ8Y8YMV2Z1jhw54mvO4lGWKlXquGzXzunLLGYhCQEELrxAu3btNGbMGHXo0EE33nijhg8frpCQkAt/4hxyBvsM32LvvvXWW2rfvv1Jo1q9erWLv2qfzWeU8uXLp8GDB+vee+/VM8884+ZWsBi+Fr+3T58+Cg8Pz+iwLFFmPzMPPfSQ7r77btWpU0dVq1bNEv2iEwgggAACCCBwcQTsfUrjxo1d9hDbNAYAAEAASURBVJ7x0KFDWrRokRYsWKD58+fL5l4YNGiQUlNTXYz6evXqyZvr1q0re46RhEB2FXj66afVuXNnN7/I888/736e7XqqdevWCgsLk83R0LNnT40aNUobNmxQ6dKl3VDtXkmFChXUt29ft71//363TH/fxJ73TUpKUlpamvz8/HxE9myvXSe8/vrr7rkD347/rvz888/u+qJbt256++23Xandu1m2bJl7v37llVeeeMhpt/ft2+fbv2PHDs2ePVuTJk3ylWVm5b333nN9tWsGG6NZ2D2n9M9M2N8OS+mfc7ZtK7d7TWPHjlX9+vX17rvvWrG2bNmivXv3uuvYSpUqubklunTpoiuuuMLtmzZtmg4cOKDFixerSpUqsntdpEsj4P35tWXDhg3d74TdeyhYsOB569Da/fF6qk4XLd+zSXO2r/K126lsYx0+mqQRa2b4yiZvXqzOnvKuFa7U6HWzdUOZhioYmkehAUHKHxyhvUcOKU/QsWfoQzxl3uQtKxCSR+sPbHfFEYHH6oUGBHurKSIoRMH+x/+8WT07Ln0K95R524/47/msv/9eO0tVC5ZW42KV3P6IwBD5ef4LCjjWZqHQk581Cvb0M19QuAL8/JWSlupO83qj3p7jg3X39PeU5vnPUpB/gG4u11QD536hgbVv0Qv1u7lxj9+4UFU85+wY20D3TH/f1c3on1nbVqhVqRon7Xpm3te6qlRNdSnfVL/tWuv2W5/rF71Mz8z/Wqmev2PedLnnPN5UPLyAahcpp1snveotUsuS1fTzpt/ddquS1TVlyxLfvjNZubdqOx1MTtI3q39VQsqxe9K9K12l+2Z8qN1JB05qqnSeIrKfjaSU5JP2UYAAAggggAACCCCAQHYWsHsCNn+o3Z9gTsbs80r+9NNPsnsyXbt2zT6dpqcIIIAAAgggcMkEjr8jfcm6wYkRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEMhdAhZowb7QTDomYBNl2RfF7QvlJAQQQAABBBBAAIHsL7B792517NjRvb974403sv+AGAECCCCAAAII5GgBmwDDArt/9NFHssCEFmz/2muvzXFjtqARvXr1kgVdHDJkiF5++WX961//kgXFtMCWBFy8uC+5BcO0yUgtW3DMjJb2s2mBP7zJJmexQJzFixd32YLy2nr6Mlu3QL3eII7eY7PDcvny5W6yBhvXiBEj+JnMDi8afUQAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgZMEwsPDVblyZZdP2ukpOHTokNatW6f169cft5w0aZLbtu+fWbLviUVFRalMmTKKjY31Lb3rpUuXVnBwsKvLPwgggAACCCCAAAIIIJA5AYspYjGBr7vuOll84J9++kkxMTGZO5haCCCAAAIIIIAAAggggAACCGRRgYCAAJUoUcLl03UxLS1Nu3bt0tatW10MxE2bNsmyxUO05fz5893S+3mVtRUSEqJSpUqpZMmSbmnr3myfV9l1deHChU93WvYhgEAmBVq2bKmJEye6uLgdOnTQ6NGjZZ8/kzIncPfdd7v5EezzePucPX1avXq17G9WUFBQ+uKT1u3vm8UofuKJJ/T666/rkUce0bPPPquePXu6+L0VK1Y86ZisUGBxhmfNmqUbb7zR/S23GLYkBBBAAAEEEMi9AhEREWratKnLXoUDBw5o4cKFmjdvnstDhw51cxTYfruuq1evniw+vC1tPtHIyEjvoSwRyNICnTp10ocffqgBAwa49+358uXTK6+8om7duvn6bXNy2Htke17A6h09elRjx47VL7/84p7DtWcI7PrL0ksvvaTnn39eU6dO1fTp02W/O88995y7Rkg/n8ddd92lVatWuXskvhN5Vuz3zOZts2eF58yZk36XQkND3T2Y4wpPs1GtWjX17t3bXZNYu3Z9uGbNGtnzxjVq1DjNkSfvsjktlixZohYtWhy3s3Xr1vr888+1f/9+vfjii26fzRNRvnx52TWWXUM9+OCD7jrDrM3Q5jqZOXOmBg0apKJFi7o5UMaNG6ebb75ZV155pcqWLauGDRu6vyl79uxxdS+77DLmnjhO/uJu9O/f3/382Vw1do/vQqR1B+K1/sB2PV33Vm1P2Ks/921RixLVlD8kj7pMelWrPNve9N262epVsZXeafZ3/V+19np+wXD9tnOdIgJD1CG2vpbv2aSuFZq76v2qXqvBi/6t6DyF1bvSVa7s0Vqd9dS8r5QvKFw9K7Z0ZQ/V6KjXF3+va6Jrq4DnnI2iKumGMg01ceMid44SEQWVNzhMd1Zuo89XTdHfq1yjUnkKKU9QqLqUb6ZvVk935beWv0LTrn9Jby75SQ/P+kwfNb9XX7V+SM/M+1p9q1ztzmXtrt2/TR8tn6RAzzxAPS5rqabFKis0MFhP1rlFb/8xxnP+imrvGYs5/M1zLkshAUGqVbis5m3/U0dSj6rThJdc28/V7yrLy/Zs1N+nvauDRxNd/Yz+GbLkR3W7rLli8xZ13t46q/dv1fXjX9T7V/RTquce8PSty5zlK7+N0pd/Hj93elRYfr3ZpK92Ju5Ty5LV9bdp72ja1qWuqQA/f89YqujxOZ+77RYlq+n9ZRO8pzmj5eUFY5ztU3Vv0bdr/qNkz5j/tWy8FuxYfVI7Qf4Bale6rnpPffOkfRQggAACCCCAAAIIIJDdBew63p7JsDlESdlHYNiwYWrVqtVfPoeTfUZETxFAAAEEEEDgQgr4eR7OTbuQJ6BtBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgawhkJSSrKihPbJGZ+hFpgWKhefXii7vZbo+FRFAAAEEEEAAAQQQQCD7CHz77be69dZbZUHzCLgkjRo1SjfddJP27duHR/b5MaanCCCAAAIIIIBAhgIpKSlq27atVqxY4QL/WJAfEgIIIIAAAgggkBUFLOjia6+95rIFMbXAjRZcwd8ToC03JJv044UXXtC7777rJp63QPEWtJF0bgL2fnjbtm3auHGjmzjFlha4Y8uWLcctExISfCcKCwtzASIs2KJlm7jFu7T1YsWKyYJy5uR7yWvXrlWzZs3cBA0TJkyQBScmIYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAArlRwGJxrV+/XuvWrctweeDAAcdi34e076CVKVPG5djYWN+6ldn31AICAnIjIWNGAAEEEEAAAQQQQOAvBTZs2KD27dsrPj5e33//vRo0aPCXx1ABAQQQQAABBBBAAAEEEEAAgdwiYPE6LY7ipk2bTsre8h07digtLc2RhIeHq3Tp0oqJiTlpaWX2uVVgYGBu4WOcCJyzwMKFC3X11VercuXKGjNmjPLmzXvObeaGBiwmbHR0tPr06aPnn3/+uCH37t3b/V2zmKdnknbu3Kn3339fH374oeLi4tS8eXPdfvvtLoZvVosTa3+fa9WqpVatWunrr78+k2FSFwEEEEAAAQRyqYDNVTB//nyX582bJ8sWT9/Pz08VKlRQvXr1XK5bt657n2HXfiQE7L1m9+7ddfTo0SyFkZqa6u5hlCpV6pTzjdjzuUuXLnX3LqzeuabDhw/rYv1e2D2aggULnvX5Jk2a5K6JmjZt6ubSsL7b/Z+RI0eqWrVqevTRR0/LYb42v4Z3Dgm7J5ScnKzg4ODjjrP7RWZi9Q4ePJjl5tew6zl7TmTs2LHH9ZuNkwU+W/mLHpr1qY6mppy8M4OSsIBgBfkHaH9ygkIDgnRZZEntSTqojYd2ZlD7WFGh0LzalXjsufgQzzFJKcmnrHuxduQJDNXBo4m+0wX7B+pI6oX9excdUVhpnv82HdrlO+/pVnpVbKXLC0RrwOzPMqxWPp9nbpugMC3bs+G4vhcNi9SqW/+l5+Z/o/eWjpNtxx3ckWEb56uwcGg+FQzJ485zute3Y2wD3VSuqbr+8voZnTrYP0gDat7g8hkdSGUEEEAAAQQQQAABBC6iwM0336ytW7dq+vTpF/GsnOpcBOwzYvvu9ieffKJu3bqdS1MciwACCCCAAAK5RMDP88HJsadpc8mAGSYCCCCAAAIIIIAAAggggAACCCCAAAIIIJBbBeyB2KihPXLr8LPtuIuF59eKLu9l2/7TcQQQQAABBBBAAAEEEDi1gAVcsi+N//LLL2rZsuWpK+aSPU888YRGjx6tZcuW5ZIRM0wEEEAAAQQQQCDnCjz88MN66623NGPGDNWpUyfnDpSRIYAAAggggEC2FbBglB999JGeffZZWWBDC2bYv39/hYWFZdsxnUvHbQJ6uz/3zTffqGHDhnr11VfVpEmTc2kyxx5rEwlYEA4LsLlx48bjlt4y22/1LPn7+ysqKspNdmITnli2gBAnLgsUKJBjzTIzMLNr1qyZChUq5O6ZR0ZGZuYw6iCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAK5UmDXrl1av3697PuBltOvx8XFKSEhwbkEBgaqdOnSKlOmjGJjY90y/XqxYsXk5+eXKw0ZNAIIIIAAAggggAACJnDgwAF16dJFU6ZM0dChQ3XTTTcBgwACCCCAAAIIIIAAAggggAACmRRISkpycRnt86kNGzYo/dLWLWbjkSNHXGsBAQEuFqN9dhUTE+M+w7Kl9zMsW4aEhGTyzFRDIHcILF26VK1bt3a/M+PHj1f+/Plzx8DPcZQWZ/jrr792n6On/zzc4p5Wr15d77zzzlmdITU1VRMmTNCHH36oMWPGyD6Pb9eunW655Ra1bdtW4eHhZ9Xu+T5o0qRJuuaaa9xcEXfffff5bp72EEAAAQQQQCAXCFic/Xnz5vny/PnzZc8s2vufqlWrqn79+r5cpUoV2fUeKXcJ2Pvt7t27y+b8IJ2dQGbeq/ft21c1a9b8yxNkpq2mTZtqwIAB7v7Nib+ze/fu1YgRI2Tnyw3p9ttvV3x8vMaOHZsbhntOY/xs5S96aNanOpp6bO6Vc2qMg8+rgJ/89FHzezRk8Y9avHt9ptsuGhapVbf+S8/N/0b/WPx9po+70BUrRJbQM3VvVZ+pbyoxJfmMThfsH6QBNW9w+YwOpDICCCCAAAIIIIAAAhdJYPfu3e5Ziffee092TUrKHgJvv/22HnvsMW3btk0RERHZo9P0EgEEEEAAAQQuqUDgJT07J0cAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAIJcKlCxZ0gW0mzlzplq2bJlLFf437AULFqhOnTr/K2ANAQQQQAABBBBAIFsKjBo1Sq+++qo+++wz3t9ly1eQTiOAAAIIIJDzBb777jtZIHibUP3vf/+7nnzySRUuXDjnD/w0I7TJ4r/66is9+OCDevjhh2UBIDt27KhBgwapYsWKpzky5+3as2ePb9ISm8AkfbbJSyzobUrKsQCH/v7+ioqKUnR0tEqVKqV69eqpU6dObt3K7B6w5aCgoJwHdR5HtH37djehhQXIsIkUIiMjz2PrNIUAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgjkPIFChQrJckZxu9LS0hQfH+++R7l+/Xq3tO9U2vrUqVPd9+aSk5MdSmhoqGJjY2XfM8woFyhQIOfhMSIEEEAAAQQQQAABBNIJ5M2bVz/88IPuv/9+3XLLLVqxYoWLRZKuCqsIIIAAAggggAACCCCAAAIIIHAKgZCQEJUvX97ljKrY51bbtm1zn0/FxcX5lrY+btw4F/tx79697lA/Pz+VKFHCfXZVtmzZ4z67sm2L7WgxIEkI5CaByy+/XL/++qub16pVq1aaOHGi+5w4NxmczVi7deumwYMHa8aMGWrWrJmviT///NPFjfUVnOGK/Q1q27aty7t379a3336r4cOH6+abb5Z99n7NNde49q+77rpLGlv1qquu0sCBA/XAAw+oQYMGGT5XcIZDpzoCCCCAAAII5DKB4sWLq0OHDi57h27PIM6bN09z5szR3Llz9cUXX+jw4cOy2PK1a9dW/fr1fdmeSSQhgMDpBVq0aHH6Cp69RYoU+cs6ViEzbXnn2fjoo4/cvBAxMTHuuWL7fV68eLEee+yxTJ2LSgggkDUE0pSmu359T6806qWhKydr0c61mepYeGCIqxcZEpGp+hejUnREYT1Q/Xr1m/4vJaYc+47DxTgv50AAAQQQQAABBBBA4GIJ2PyggYGBuummmy7WKTnPeRAYNmyY++zX7n+SEEAAAQQQQACBzAj4eR6YTctMReoggAACCCCAAAIIIIAAAggggAACCCCAAAIIZG+BJM8Dr1FDe2TvQeTC3hcLz68VXd7LhSNnyAgggAACCCCAAAII5A6BW2+9Vfv27dPYsWNzx4BPM8qiRYvq8ccfV//+/U9Ti10IIIAAAggggAACWVlg5cqVqlevnrp27ar33uPedlZ+regbAggggAACuVFg1qxZGjBggGbOnOkCs7/00kuySSRIJwvYRByPPPKIli9frjvuuENPP/20ihUrdnLFbFaSnJysTZs2uUlHNmzYkOHy4MGDvlEVLlxYpUuXdtmCYEZHR6tUqVIu27pNTmJBOUhnL7Bnzx4XlPTQoUNuUgsLKkxCAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEELhwAqmpqdq8ebPWrVuntWvXav369W7dti1v2bJFVsdSvnz5VKZMmQxzbGysmDz5wr1OtIwAAggggAACCCBw8QXeffdd3XffferUqZM+/fRThYeHX/xOcEYEEEAAAQQQQAABBBBAAAEEcpmAzdnj/dzK+3mVd2mfYyUkJDiRoKAgWVxI72dXFk/Uu16uXDkVLFgwl8kx3NwkYL8TLVu2VN68efXzzz/L5nginV6gevXqatKkiW++hP379ysyMlI//fST2rVrd/qDz3Dv9u3b9d1332nUqFGaPHmy/Pz81KpVK3ef8frrr1eRIkXOsMVzr26f+V999dVas2aNFi5cqPz58597o7SAAAIIIIAAAgikE0hJSdHSpUs1d+5cX/7jjz9k5fb+p379+i7bPFa2XqhQoXRHs5rdBb7++mt1797dPYsaFRWV3YeTK/qflpamN954Qz/++KNs3habY6NatWq6/fbb1atXLwUHB+cKh6SkJN1yyy06cuQIc0hn4hX/bOUvemjWpzqampKJ2lS5VAKlIgpp06Fdf3n60nkK64naN+uW8s20fn+8Xv19tEasmaHkS/z6RoXlV3zC3r/s/6kqBPsHaUDNG1w+VR3KEUAAAQQQQAABBBC4lAJ16tSRfXZp308gZQ+BFStWqHLlyu6zefvcl4QAAggggAACCGRGwM/zYUxaZipSBwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCB7CySlJCtqaI/sPYhc2Pti4fm1ost7uXDkDBkBBBBAAAEEEEAAgdwh8NZbb+npp5/Wrl27XPCn3DHqk0e5YcMGF6jv119/VbNmzU6uQAkCCCCAAAIIIIBAlhc4dOiQC9iVJ08eTZ8+PdcEBcryLwwdRAABBBBAAAGtWrVKjz32mAvC3rx5c73yyiuyYKOk0wtYoPihQ4fqqaee0p49e/TQQw+5bO/3smqyQI12r9EmCfHmuLg433r6Se9DQkJUqlQpd1+ydOnSyiiHhYVl1aHmiH4dPHhQrVu3lr0udg1hk7mQEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQurYB9V8++m7du3TqX7ft63nVb7tixw9fBokWLqkyZMr5ctmxZ33p0dLSCgoJ8dVlBAAEEEEAAAQQQQCA7CEyZMkU33XSTi4Hw3Xffyd7XkhBAAAEEEEAAAQQQQAABBBBA4NIIpKWladu2bcd9VmWfV61du9aVbdq0SSkpKa5z+fPnV/ny5X25XLlyvvVixYpdmgFwVgTOo4DFW23VqpX7DPaXX35R8eLFz2PrOa+pF198UW+++aa2bt0qf39/LViwQHXr1nVxiitUqHDBBrx37179+OOPLg7yhAkTZJ+/21xcnTp10g033OBi4V6wk5/Q8Pbt21WrVi0Xh9nudZIQQAABBBBAAIELLXD48GEtXLhQc+fOdXnevHnu+s3Oa88W1q9fXw0bNlSDBg3c+xSbK4CUPQVmzZqlFi1auPe71atX19VXX+3mHLD3vqGhodlzULmo18nJybnq+d6lS5dq4sSJLtt8yfa36v7779c//vGPXPSqn91QP1v5ix6a9amOph67/3Z2rXBUVhEI8g9QeODx/+/dd+RwVuneWfcj2D9IA2re4PJZN8KBCCCAAAIIIIAAAghcIIHff/9dNWvW1LRp03TFFVdcoLPQ7PkWeOKJJzRs2DD3PWv7rJmEAAIIIIAAAghkRsDP88BrWmYqUgcBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAgewskpSQramiP7D2IXNj7YuH5taLLe7lw5AwZAQQQQAABBBBAAIHcIeANLmVfKq5SpUruGHQGoxw9erRuvPFGWRCsvHnzZlCDIgQQQAABBBBAAIGsLtClSxdNnjzZBVBlsqSs/mrRPwQQQAABBHKHgAU5f/bZZ/XBBx+oYsWKGjx4sNq1a5c7Bn8eR5mQkKAhQ4Zo0KBBLmDnM888ozvuuEOBgYHn8SyZayopKckFU7DJ6r3ZJrH3rltQf+/XJfPkyaPY2Fg3oactvbl06dKyHBUVJT8/v8ydmFrnXcB+rtq2basVK1Zo+vTpupCTL5z3ztMgAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggkIsFDh06pHXr1vny2rVrfev2fb8DBw44nYCAAJUqVUplypTJMBcvXpzv+eXinyOGjgACCCCAAAIIZGUBe4/boUMH7dy5U6NGjVLjxo2zcnfpGwIIIIAAAggggAACCCCAAAK5VuDo0aPasGGD1qxZo9WrV/uybVtOTEx0NhERESpXrpzKly/vy95t+zzL398/1xoy8OwlsGXLFrVq1UopKSluTgD7+SVlLLBy5UpVqlRJU6ZMUfPmzfX111+rZ8+eOnz48EWLqWufrY8bN87dY/zpp5908OBB1a9fX506ddL111/v4iVn3PvzV2oxX1u0aOHiMj/44IPnr2FaQgABBBBAAAEEMilgn7nOmzdPc+fO1Zw5c9xy165dCg4OVs2aNdWgQQNftms2UvYRsPe79n574sSJLtt78LCwMF1xxRVq06aNy1WrVs0+A6KnOUZgx44d+uWXX9zP5YQJE2TX0gUL/j979wEfVZX///+dEBJ67x1CDS3U0EIJVREVUVFRsa3suqurSNG/rqKoWLJidy0o61qwizQpoQqhBBAUQoAkdKQLhJ6EP5/z/SUSilIyyczkdR+Pw71z58455/OcmXDnls8ppaioKPe57NmzpxuvxG8C9mAgYxNiNCT2Q6Wmp3mwFapG4PIEggPza2h4X1curyZejQACCCCAAAIIIIBA9gs8+OCDmjRpktatW5f9lVOjRwRsHFQb8/Tmm292Y7d6pBEqRQABBBBAAAG/FAg4tSNx0i8jIygEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBLALH0k6o/H9vz7KOB94vUKFQCa256W3v7yg9RAABBBBAAAEEEEAAgUsSsGR0xYsX16uvvqp77rnnkurwhxc9/vjj+vrrrxUfH+8P4RADAggggAACCCCQ5wReeeUVDRkyRJYwxxLPMiGAAAIIIIAAArkt8N5772nw4MEqVqyYnn76ad1xxx2ygcqZLl3AkrGOHDlSb7/9thvo/csvv1Tjxo0vvcLzvHLnzp2ygTjPVbZu3ar09HT3SntvLcFC9erV3dyWT39cunTp87TA6twWOH78uK699lqX6Hf27Nke+Rzldoy0jwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCORVgd27dys5OfmcZePGjbJ7zGwqUKCAu0ewVq1a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vKVL9AOBswSCA/NraHhfV856khUIIIAAAggggAACCOSSwPbt21W1alV98skn6t+/fy71gmYvVuCLL77QLbfcoi1btqhChQoX+3K2RwABBBBAAIE8LhBw6oTmyTxuQPgIIIAAAggggAACCCCAAAIIIIAAAggggAACeUzgu+++03XXXad169YpNDQ0j0VPuAgggAACCCCAAAIIIOBtAnbDepkyZfT999+7ZE3e1j9P98cSVmUk0C9WrJinm6N+BBBAAAEEEEAAgWwSGDJkiN5++21ZstBGjRplU61UgwACCCCAAAIIZI9ARESES2geHR2dPRWep5YDBw7o448/1sqVK2WDjQ8fPlylSpU6z9a/r7bBJMeMGaNNmzapd+/e6tq1q0tY+PsWf760atUqTZ48We3atVP79u3//AXZuEWVKlW0detWV2Pp0qXd4Os2APuZxZJXWCJGJgSGDRvmBlr48ssvde211wKCAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAtgrYcIzbtm1TcnKykpKS3Dxj2R7bcxlDNpYsWdLdE1mzZs0sc7tPsnr16sqfP3+29o3KEEAAAQQQQAABBPxDwPKM3HHHHZowYYJGjRoly8XGhAACCCCAAAIIIIAAAggggAACCJhASkqK1q5dq/j4eFfWrFnj5uvXr9fx48cVEBAgy9FZv359NWjQwJWM5XLlyoGIwCUL2DGrXr16ufOjs2bNcp+xS67MD1943333ae7cubI8vjaPjIz0mSgPHz6sadOmaeLEiS4H8fbt21WpUiVdeeWVLp9xt27dVKRIkYuK59VXX9XDDz+s6dOnq0uXLhf1WjZGAAEEEEAAAQS8TWD//v1avHixYmNjXbExtPbt26cCBQqoRYsWatu2rdq0aePmth/F5J0C6enpiouLc/u+tp9q72dqaqrCw8PVo0cPV2wsjpCQEO8MgF5lm4AdU7HfQPY5mD17tg4dOqTatWtnfg6ioqJUtGjRbGuPiv5cYGxCjIbEfqjU9LQ/35gtEMglgeDA/Boa3teVXOoCzSKAAAIIIIAAAgggcJbAiy++qOeff152fo9jGmfxeO2Ka665RseOHdMPP/zgtX2kYwgggAACCCDgvQIBp5KZnPTe7tEzBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSyXyAtLU2W3Lt///566aWXsr8BakQAAQQQQAABBBBAAAEELlLAkrv17dtXzz333EW+0vc3f+KJJ/T5558rISHB94MhAgQQQAABBBBAII8IfP/997Kb2v773//q9ttvzyNREyYCCCCAAAII+JJARESES+geHR3t0W5ff/31uv/++11y/WbNmumee+7R008//Ydt7t27V61bt1a7du20detWl7ywZcuWsqSkFzrZoBLWzieffKLPPvtMN91004W+NFu2s2su+vTpo5EjR6p48eLZUieV+K+AfVafeuop/e9//9Mtt9ziv4ESGQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICA1wrYwM8bNmxQcnKykpKSzprv37/f9T0wMFBVqlRRrVq1XLF7Kk9fLl++vNfGSMcQQAABBBBAAAEEckbAxjh59NFHXS62Dz/8UMWKFcuZhmkFAQQQQAABBBBAAAEEEEAAAQR8TiA1NdWdm4qPj9eaNWtk84zlAwcOuHhKliwpG7coo4SFhalhw4aqVq2az8VLh3NH4ODBg7riiiu0fv16zZw5U/YZYvo/gSlTpujKK690D3bs2KFy5cr5JM3Jkye1fPlyTZo0yZUlS5YoKChIHTt2VO/evV2pU6fOBcVm+ZTnz5/v6qtQocIFvYaNEEAAAQQQQAABXxCwfSYbD3XhwoWKjY11819++UXp6enu95WNDZFRmjZt6vanfCGuvNbHlJQUzZo1S9OmTXPFxuUoVKiQOnXqpB49eqh79+7uN3Nec/HHeHfv3q2YmJjM93rLli2yYyRRUVHufe7Zs6dq1Kjhj6H7TExjE2I0JPZDpaan+Uyf6WjeEwgOzK+h4X1dyXvREzECCCCAAAIIIICAtwrYuX/7ffvmm296axfp1xkCNr6snTsdM2aMbrvttjOe5SECCCCAAAIIIPDnAgGnTlae/PPN2AIBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAf8SePbZZ/Xyyy9r69atKlCggH8FRzQIIIAAAggggAACCCDgcwJ33323EhMTNXv2bJ/r++V2+KqrrlLRokX12WefXW5VvB4BBBBAAAEEEEAgBwRsMM3mzZvruuuu0/vvv58DLdIE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dq5169atmjhxonr37p2HIpfS09NlOYynTp3qyqJFi9w6y1vcs2dPxcbGatmyZVqxYoUqV66cp2wIFgEEEEAAAQQQOJ9AamqqfvrpJy1YsMCV+fPna8uWLcqfP79atGih9u3bq0OHDm5etmzZ81XD+lwQ2Lhxo6ZNm+ZKTEyM9u3b5/Zze/ToISvdunWTXafJlH0CJ06ccN+TDHf7fWHXurZu3dqZm7st58uXL/sapaYcExibEKMhsR8qNT0tx9qkIQQuViA4ML+Ghvd15WJfy/YIIIAAAggggAACCGS3wMcff6y77rrLnZvkuFF263quvmeffVavvvqqtm3b5sag9VxL1IwAAggggAAC/iwQcCppyEl/DpDYEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBD4I4GmTZuqWbNmGjt27B9txnMIIIAAAggggAACCCCAgMcFLMFU0aJFNW7cOI+35S0NjBw5Uh9++KEbAMlb+kQ/EEAAAQQQQAABBM4WWL9+vUtgdeutt+rNN988ewPWIIAAAggggAACXiYQERGhyMhIRUdHe6xngwcPdjf7p6X9nuzum2++0QMPPOCSgGY03K9fP23atElLlizJWKVDhw7poYce0ieffCJLyl+iRAmXiNKSr1/oZMnbbfuBAwdmXvNgSdwtbrsOwpY9Ndmg5YMGDdLw4cM91QT1+qiAJffs06eP7r77br311ls+GgXdRgABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAIGcEjh49quTkZJeLLSkp6ay53YdqU3BwsKpXry67xzOjhIaGuuWaNWuqWLFiOdNhWkEAAQQQQAABBBA4r8CWLVt00003afny5Xrttddc7oXzbswTCCCAAAIIIIAAAggggAACCCCAgIcFtm/frp9//lkrV650c1uOj4+XnZ/Kly+f6tSpo8aNG6tJkyZubst23ikgIMDDPaP6nBCw84w2FtaqVas0c+ZMNWrUKCea9bo2jhw5osKFC7vzql27dtU777zjdX3MyQ7t379fMTExmjp1qisbN25033nLjfzoo4+qR48e7m8Cfwdy8l2hLQQQQAABBBDwBQHbb/rxxx81f/58N7f97PT0dNWtW1ft27dXhw4dXLHHTN4hYO+PjQ9iYydY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gAACCCCAAAIIIIAAAggggAACCHhOoHz58rLStm3bsxo5evSoLLeI3RObmJjo5rb87bffuuUjR4641xQoUMDdM2v3zYaGhrrljLkNim7PMyGAAAIIIIAAAgicW6BcuXKaPHmyXn75ZT366KOaNm2aPvroI1WpUiXLC1544QU99thjGjVqlB555JEsz/EAAQQQQAABBBBAAAEEEEAAAQQQyE2BqlWrysrpOVMPHDigFStW6KeffnLzuXPn6q233tKxY8dcTsqGDRsqPDw8szRt2lTFixfPzTDyfNtFihTRlClT1KtXL3Xp0kWzZ89WvXr18oTL2rVrVbduXRerjbfwzTffuONweSL4iwzyzTff1JIlS3TjjTdq3rx57vscGRkpKyNGjNChQ4fcuGqW+9jK//73Px0/flw1atRQp06dMsvpOZkvsgtsjgACCCCAAAII+JWA7Ydaufvuu11c27Ztk/1+sn0tO3ds55FtsvEtbJ+rY8eObm6/wZh8W+DgwYOKiopyv5vz5cun6tWr+3ZA9B4BBBBAAAEEEEAAAQQQQAABBP5QYObMmdq4caPuvPPOP9yOJ71LwN63X3/9VQMGDPCujtEbBBBAAAEEEPBJgYCTpyaf7DmdRgABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAgGwW6du2qggULauLEidlYK1UhgAACCCCAAAIIIIAAAhcn8I9//MMlUlq0aNHFvdAHt7ak9m+//bY2bdrkg72nywgggAACCCCAgH8LnDhxwiWUssEgbd+UwR79+/0mOgQQQAABBPxRICIiwu3PREdHeyy8/v3764svvtDOnTtVtmxZ144lke/Xr5+qVaumkSNHuvWff/65unXrpltvvVW2f2XJPK+55ho1atTIvWbPnj3q27evJkyYcFEDIsTFxalVq1ZuYMnbbrvN1bVr1y7ZQAvTp0+XDbDgqckSuQ8aNEjDhw/3VBPU6wMClqzEEtJWqlRJM2bMkA0owYQAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACvi1gQ1vawNnr169XYmKikpKSssztflabAgIC3D2GoaGhsmL3n54+L1OmjG9D0HsEEEAAAQQQQCAbBZYtW6YBAwZox44devfdd3X99de72leuXKnmzZsrLS1NgYGBmjdvntq1a5eNLVMVAggggAACCCCAAAIIIIAAAggg4HmB1NRUrVmzRj/99FOWYjlXbapZs6bCw8NdrlSbW6levbrnO0YLWQQOHjyonj17asOGDZo7d65q166d5Xl/fNCmTRtZeeWVV7RgwQK1b99edkyucePG/hjuZceUkJCgli1b6q677tKrr776h/VZnuWFCxdqzpw5rtjy0aNHVaVKFXXq1MnlrDVvy5Vs55aZEEAAAQQQQAABBLIK7Nu3T/Pnz3fniO08sY09YWOG2W+ljh07un0q26/KC/vtWWV8+9GBAwcUFRWlFStWyH4rBwUFuX3r++67z7cDo/cIIIAAAggggAACCCCAAAIIIHBeARuj1O7FtPNlTL4jMHDgQHedw6JFi3yn0/QUAQQQQAABBLxWIOBUko6TXts7OoYAAggggAACCCCAAAIIIIAAAggggAACCCCAQA4JfP3117rxxhtdIusaNWrkUKs0gwACCCCAAAIIIIAAAghkFfj00091xx13aP/+/SpYsGDWJ/3s0Q033OCSFHz33Xd+FhnhIIAAAggggAACvi8wfPhwvfnmmy6xVP369X0/ICJAAAEEEEAAgTwnEBER4ZKMR0dHZ3vsltzcBnJ8+umntXfvXv31r3+VJQCwZPI2vf/++xo6dKh+++03FStWTC+++KIGDRrknjt06JDrlw2IYMnUe/XqJRs825KqFylSxG1zof8cO3ZMlijSkqz36dNHhQoVctc8PProo25AhQut51K2swHALSbbb2TKmwLbt293yWcLFy6sWbNmqWTJknkTgqgRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBDIYwIpKSnuntakpCQ3T0xMVMbypk2bXH45IylatKhCQ0NdsXtTbTljXq1aNQUFBeUxOcJFAAEEEEAAgbwucPjwYT388MP6z3/+4/IPv/zyy+rQoYPWrl2r1NRUBQYGqnz58lq1ahV5HPL6h4X4EUAAAQQQQAABBBBAAAEEEPATgS1btshysJ5e7LzSyZMn3fGPpk2bKjw83JVmzZopLCyMc0gefu8PHDig7t27y/KKzp07V/4+dm+pUqX0zDPPuBy+9rmrXr26yyM8cuRID0v7bvWfffaZbrnlFn31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CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAghkh8CxY8e0YcMGJSYmKikpyc0zlpOTk2X3OdoUEhKimjVruoHZQ0ND3f2OGXNbX7BgwezoDnUggAACCCCAAAIeEzh69KgaN27s8jekpaWds518+fJp0aJFatGixTmfZyUCCCCAAAIIIIAAAggggAACCCDg7wJ23GT16tVaunSp4uLi3HzFihU6cuSIChQooKZNm6ply5bu+IkdQwkLC1NQUJC/s2RrfDt37lSnTp1cnXPmzFG5cuWytf7cruzjjz/W3Xff7T4zgYGBmd2xY3Nt27bVu+++m7mOhbMF7Fxt8+bNdcstt+jtt98+e4NsWJOSkqKFCxe6YsdDrezatct9l+19shzTGaV+/foKCAjIhlapAgEEEEAAAQQQ8A8BG2vMfiPZvrwVGztgz549KlmypNvP79y5s7p06eLOTbMf5bn3/LfffpNZr1q1SqmpqWc1ZL9f7XcsEwIIIIAAAggggAACCCCAAAII+J9AfHy8O089Y8YMde3a1f8C9NOI9u3bpwoVKui9997T7bff7qdREhYCCCCAAAII5LRAwMlTU043SnsIIIAAAggggAACCCCAAAIIIIAAAggggAACCHirQP/+/bVx40Z3A7G39pF+IYAAAggggAACCCCAgH8LvPzyy3r++edlSbb8dYqOjta///1vbd++3V9DJC4EEEAAAQQQQMDnBEaNGqURI0Zo/vz5LmGuzwVAhxFAAAEEEEAAgdMELDF4+fLlNW7cOBUqVOi0Z1i8XAEbsLt169YaNmyYK5dbH6/3DQEbtP2qq67KTCTboEED3+g4vUQAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABnxew4TZ//fVXJSYmyu51PX1uyxl5+wICAlSxYkWFhoaqVq1aql27turUqeMe27qSJUv6vAUBIIAAAggggIDvCwwePFivvfaa0tLSzhtMvnz5VLlyZf3yyy8qWrToebfjCQQQQAABBBBAAAEEEEAAAQQQQCAvCdjxlNWrV2vp0qWuxMXFuTyZR44cUYECBdS0aVOXY79FixayEhYWpqCgoLxEdNGx2thRHTt2dH6zZs1SmTJlLroOb33Bk08+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mWNz586t6tWr222ffvqpZs6cqd9++02xsbHORkG8CCDgQgJ58uTR+++/r71792r+/PnKkiWLQkNDVaBAAXXv3l379u1zoWxJBQEEEEAAAQQQQAABBBBAAAEEEHAsgRIlSqhdu3b65z//qatXr94xuOLFi2vnzp22Vu0dG7EDAQQQQOCmgLkG16hRI9WpU8deo/v11181duxY5c+f/2YbV31h5p0fOHCgdu3apfLly6tFixaqVq2afvrpJ1dNmbwQQACBZBHw8PBQvXr19O233+rw4cN67733tGDBApUuXdr+Hp0wYYJD3d+xbNkyrVmzRj179kyW/Onk3gKenp5Pwf13AABAAElEQVRq2rSpli5dqm3bttn7g9599137/cLMzREVFXXvTmiBAAIIIIAAAggggAACCCCAQDIKjBkzRk8++aT8/f2TsVe6Si2B1atX68SJEwoODk6tIRkHAQQQQAABBBBItIBnolvSEAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBO5boGvXrtqyZQsP/923IAcigAACCCCAAAIIIIDAnQSqVq2qkydPKjIy8k5NUm375s2bVa5cuVQbj4EQQAABBBBAAAEEEEAAAQQQQAABBBBAwD0F9uzZozfffNMWiDSFOhs2bGgLjMbFxd0C4u3tLVPAed26dapQocIt+xzlzSeffGInxU1ISLAhmYKpDz30kKOERxwIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIJKvAI488oqeeekqvvPKKfd5+9uzZ2rlzp2JiYmTqCcyfP1+mlkDZsmV1+PBhDRs2TC1btlTp0qWVKVMmFSxYUPXq1dOrr76qr776yrY3x/215kCyBk1nCCCAwJ8EPD09bb0T8/vrwIEDCgsL06RJkxQQEKD69etr1qxZ/E76kxcvEUAAAQQQQAABBBBAAAEEEEAAgeQS6N27tw4dOqQRI0bcsctSpUrp4sWL2rt37x3bsAMBBBBAQPY63EsvvWSvyZ04cUJLly61193M71F3WwoXLqwpU6Zow4YNSpcunapXr67g4GDt3r3b3SjIFwEEEEiyQO7cufXOO+8oIiJCS5YsUb58+dSpUyf78//+7//s/SBJ7jSZD+jbt6/q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tBbIli2bTE2CF154Qb169dLkyZO1ceNGnTt3TseOHdOPP/6oQYMGqUGDBoqLi9N3332nV155RVWqVJGpo5ozZ077ul27durXr5++/fZb/frrr7p48WJap8b4CCDgpAKFChXSJ598okOHDik8PFyxsbFq0qSJChcurD59+ujo0aNOmhlhI4AAAggggAACCCCAAAIIIIAAAmkv8PDDD6tLly764osvdP78+dsGZM4Zb9q06bb72IgAAgi4m0BCQoI9V/3YY4+pe/fu6tixo50bx8yZ4+vr624cic7X09NTHTp0UGRkpK2VPnDgQAUEBGjYsGH2mmOiO6IhAggg4MYCHh4eql+/vmbNmqWDBw/q1Vdf1aRJk+zv00aNGmnu3LmKj49PMSHzN8HChQv1wQcfpNgYdPxgAjVq1JCZu2Pnzp2qXbu2/vGPf+jRRx+119XPnDnzYJ1zNAIIIIAAAggggAACCCCAAALXBczcl1u2bLHnewFxToGZM2fa+/DLlSvnnAkQNQIIIIAAAgi4vICny2dIgggggAACCCCAAAIIIIAAAggggAACCCCAAAIIOIhAhgwZ7E0gw4cP17Vr1xwkKsJAAAEEEEAAAQQQQAABVxCoWrWqduzYYSc2SKt8zM3v5cuXT6vhGRcBBBBAAAEEEHA6gZ9//lmhoaEqXbq0jd3Pz08ff/yxTAG5NWvWJCkfLy8v294UTGJBAAEEEEAAAQQQQMDZBdatW6eQkBBbqGHs2LF64403FBUVJfO6bNmyd03PfDf+7bfftHz5cmXNmvWubdNq59KlS9WiRQuZotu3Wx555JHbbWYbAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggkUeDhhx9WjRo11LFjR3366aeaOXOmtm3bppiYGO3fv18//PCDrf/1xBNP6MSJExozZoxat26tcuXKKUuWLHr00UdVt25dde3aVV999ZXmz5+vPXv2KC4uLomR0BwBBNxRwMfHRy1bttSSJUu0e/duPf/88xo8eLD93dK8eXMtWrTojjVI3NGLnBFAAAEEEEAAAQQQQAABBBBAAIHECrzzzju6dOmS/v3vf9/2kMqVK2vjxo26evXqbfezEQEEEHAXgRUrVshcBzPz45hr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//u/JCR0Aek9QEyCVIHYRRAbchCliHjAYKMJgiCIDRGwUZSiUqSICtKkqOjRHERUUIOxI0loJpRDN0JoIeVlrfc/3ElIJ2XKd9+7z96z9yrP+kwMM2syz0IAAQQQQACBCwQ2bdok84W85cuXq1SpUrr//vs1YMAAXX755ReU5ULeBf7880/NmjVL7777rk1S16NHDw0cOFDXXntt3hujBgIIIIAAAgggUAwCH374oZ599llt375d4eHhGjNmjOrVq1cMkXhul19++aU1Nskuu3TpoldeeUVFnbzf3XRbtWpljUyCSDYEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgeITOHv2rHbs2KGYmJjze2xsrD0/dOiQDaxkyZK67LLLFBISouDgYHt0nF966aXFFzw9I1BMAua/lzlz5th818ePH7d5Z/r3768OHTrIx8enmKKiWwQQQAABBBBAAAEEEPAkgbS0NJsX/fTp0/Zo1t05efKkEhMTdeLECXt0Ps94zbxX+eeff/T3338rISHB7uZxSkrKBUwmv715f1+5cmVVqVIl3bFq1aoye7Vq1dIdS5QocUE7XEAAgawFzBxcVFSUzPpWX331lb799lv733GNGjXselft2rWzRzPnVpjbzz//LJMX9qOPPlLnzp1tV+Z3hMlXbHIYjxo1qjC7z7btmgvDdSolKdsyxX3znfZDFXlwm97a8ukFoQxp2kkz/lh7wXUuXCjgIx/NbTdY0377WL8d/evCAllcqVa6gmJ7zdK4H5fotd/WZFGq6C9fVqGWxrbppYe+mq7TKWfzFECAn78GNelo6+epIoXzLTB06FD99NNP+uabb/LdRnFVXLt2rcye3WbWTnzmmWeyK1Ko98z6kOb168qVKwu1H29uPD7xsJovG+LNBC499murh6pn0A0atnGu0s79X2631699SL2D26nqO31yW6XQy13Mv28muNZVg7Su8/hCj5MO/k8gKSlJjRo1UteuXTVt2rT/u1FIZ/Hx8ZoxY4befvttmfc7Zi2UYcOG2b8nKKQuadZFBMz80tNPP23X9bvxxhv11ltvsT6Lizw3hIEAAggggAACCCCAAAIIOAtceeWV9v2aWZOdzX0F2rZtqwYNGmjhwoXuOwgiRwABBBBAAAGvFPA590ewuf/E1CuJGDQCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAkUnsHHjRl1//fWKjIxUWFhY0XVMTwgggAACCCCAAAIIIOBxAldddZV9X/HGG28UytjMF+UnTJggR2L8QumERhFAAAEEEEAAgSwEjhw5ovnz52v27Nl2AR+TJHTgwIG6++675e/vn0UtLmcmYBISrVq1yiamMUlYTWIks5jLgw8+aJMeZ1aHawgggAACCCCAgCsK/O9//1OtWrVsksdXXnnFLuLoinF6Skwm8fLw4cPtohnfffedpwyrwMdhXmOb9yv8HUiB09IgAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggUKACCQkJio6OVmxsrGJiYs7v27dv1+nTp21fVapUUUhIyPk9ODjYnpscbgEBAQUaD40h4GoC5r+DFStWaNasWTLr6wQFBalfv3564IEHZP7bYEMAAQQQQAABBBDwLoG0tDSlpqba3XGe8WjuZ7zmeJzdPUeZvBwzK+t8zfncOW7n69mdO99zxO58zZzn5bFzG466mdXP7FpWdTMr69x2bu47l8+uH0dbpkzGPSUlRcnJyRfsSUlJymw/c+aMzJ7TZvLPly1bVuXKlbO787m5VqFCBVWqVEkVK1ZMt2e8VrJkyZy64j4CCBSwgPmdEBUVpa+++krr16+38wqJiYmqUaOG2rdvr5tvvlm33HKL6tatW8A9y+Yq3rVrl3766Sf5+PjY9p988km9++67MtdLlSpV4H3mpsGaC8N1KiUpN0WLrczv98zQnsTDem7zIm37Z5+SU1PUskpDXVktWNvPPf44bnOxxeZuHQf4ltDEa/pqYcyX+vnwzlyFH1i+mn7pMU3Tfv9Yz29enKs6hV2obtkqerpVDz31w7tKSDqR5+4C/Pw1qElHjW3TK891qZA/gaFDh9rff998803+GijGWn/++ae2bNmSbQTm9d+tt96abZnCvHnffffZz49WrlxZmN14ddvx5/4dar5siFcbuPrg29VqplvqtNCzkYt07l1xrsKd1XaQeja6XnXee0AnknN+L5irRi+i0MX++2a6bl01SOs6j7+IKKiaV4E5c+ZoyJAh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QXlu01GhVatW2rFjh1q0aGFzIpuj2S+//PJs14GouTBcp1KSHM1wRMArBAL8/DWoSUeNbdPLK8brSoM0n1OYfOKrVq1Su3btNGnSJLVp08aVQnT5WMznO8bQrIdk1oScOHGiWrdu7fJxu3uA8YmH1XzZEHcfBvF7gUDrqkFa13m8F4y0+Ido5oYbN26sa6+9VgsWLCj+gJwi2LNnj2bMmKE5c+bIrPfbt29fDRs2TJdddplTKU49SeCjjz6y6wCazy7eeOMNde/e3ZOGx1gQQAABBBBAAAEEEEAAAbcQWLNmje6++2779/n169d3i5gJ8kKBs2fPqkqVKpowYYKGDGFO+EIhriCAAAIIIICAqwv4pJ3bXD1I4kMAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwNsEHnvsMa1du1bbtm2ziW28bfyMFwEEEEAAAQQQQAABBApGwPyhukl+9eyzzxZMg/+vFZPI2ySv2rJli01WVaCN0xgCCCCAAAIIIFCAAtHR0Zo5c6beffddnTp1yiZYGThwoK677roC7MV1m9q4caMd/4oVK1S6dGmFh4drwIABCg0Ndd2giQwBBBBAAAEEEMinQFxcnAIDA2USxIeFheWzldxVM4n7THIB87rqiiuu0MMPP6xx48ZlW/no0aO68sorbTLKvXv36quvvrIJln/44Yds67nyTZPsOCIiQlFRUa4cZrHElpCQILOQp1lYYOjQocUSA50igAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCLieQFJSkrZv366YmJgLdpOjxGxlypRRcHCwzW8SEhIix37ZZZepfPnyrjcoIkLASeD48eNavHixZs2apV9++UXNmzdX//791bt3b11yySVOJTlFAAF3F0hLS1NKSordk5OTLzjP7Jopn9n1zK5lVTa7PvNSJ2NZ58fO8WTWX1bXTL3U1FQ7xqzaKMrn3dfXV35+ftnuJUqUsGukmWNOZfNy39FebmLITbs5teO47xhPxjYd9/N7PWP9onwe6QsBBBBAAAEEEPBWAfPa+osvvtDbb7+tjz/+WFWrVtWwYcPsPENe5xiee+45zZgxQ7GxsapWrZolNW0tW7bMztWZ+bj8bGbdi02bNtmqAQEB9r2Aidu8Lg0KCrL5iFu2bKkWLVrYvXLlyrZszYXhOpWSlJ8uqYOA2woE+PlrUJOOGtuml9uOwd0DN7+vRo0aZX9v9ezZ0+bPbtiwobsPq1Dj379/v1544QXNnTtXjRs31quvvqqOHTsWap80/n8C8YmH1XzZkP+7wBkCLirQumqQ1nUe76LReVZYS5Ys0b///W9t3brVfp7uiqNLTEzU/PnzNW3aNP3111/q3Lmzhg8frrZt27piuMR0kQL//POPRowYoXnz5qlr16568803VaNGjYtsleoIIIAAAggggAACCCCAAAK5FejSpYtOnjxp19PMbR3KuZ7AunXrdPPNN2vXrl12PVrXi5CIEEAAAQQQQACB7AV8zn25Ji37ItxFAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBohaIjo62Xwpcu3at7rjjjqLunv4QQAABBBBAAAEEEEDAQwTuvfdeHTt2TJ9++mmBjmjBggV69NFHZRJ5mySpbAgggAACCCCAgKsLmC/yffDBB5o5c6aioqLUrFkzDRw40C5A4mkL6JjXaO+//74d6++//67WrVvbsfbq1csuJOTqzxXxIYAAAggggAAC+RWIi4uzX/iPjIxUWFhYfpvJsZ5p3yR2Nws5+vj46PDhw3ZRxpIlS2Zb1yyGd8899+jSSy+15caPH68xY8bo22+/te1lW9lFb44ePdomzDCvsdnSC0ycONEmz96zZw+Ldqan4RECCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQBYCR44c0YFGMxEAAEAASURBVNatWxUTE5Nu37lzp86ePWtr1a5dWyEhIRfs9evXl6+vbxYtcxmB4hH44YcfNHv2bC1dutTm6zF5EQcMGGDzJBZPRPSKQPYCqampSklJUXJysj2ac+fHztedz53LOJ9nVcb5ek7nOd137i+r85zayOm+c7vOZY1XUWzm37cSJUrYnPgmL/7FnGesn9lj52vOfZnrzveyeux8PbP6mbWRl2uO9rOq43y9KJ4f+kAAAQQQQAABBBBAoCgE9u7dq9dff11z5syxc2BPPPGEHn/8cZUtWzZX3Z84cUKXX365zQO8ePFiW+fgwYNq1KiRnn32WT355JO5aidjof79+2v+/Pn2fWTGe+axeX1u3tM45vaqVaumVq1a6ZuAffK7OUg+ZQMyq8Y1BDxSIMDPX4OadNTYNr08cnzuNKhVq1bpqaee0q5du+x6Ns8995wqV67sTkMo9FgTExM1efJku1eqVEnjxo1TeHg4n8MUunz6DuITD6v5siHpL/IIARcUaF01SOs6j3fByDwrpLS0NDVv3lxNmza1a9G5+ujMZyirV6/WlClTtGnTJrVp00YjRoxQ9+7d7eccrh4/8eVNYN26dXrkkUeUkJBg37ua1w1sCCCAAAIIIIAAAggggAAChStw4MAB1alTRwsXLtR9991XuJ3ReqEKDBs2TOa99e+//16o/dA4AggggAACCCBQWAI+5z7ISiusxmkXAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEE8i/QoUMHlS5dWp988kn+G6EmAggggAACCCCAAAIIeLXAjBkz9Pzzz8skjvfx8Skwi6FDh+q7775TZGRkgbVJQwgggAACCCCAQFEJbN68WTNnztSSJUtsIp3evXvbhHbNmjUrqhAKpZ/ffvvNjuv999+3i1Pce++9dlxhYWGF0h+NIoAAAggggAACriYQFxenwMBAO2dVmK+BFixYYBewO3PmTK4JkpKSZJLUN2jQ4HwdR7zmdZy7vhYdPXq0IiIiFBUVdX5cnMgm/DfP9T333GMTemKCAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIDAxQgkJydr586diomJuWA/ePCgbbpkyZK67LLLFBIScn4PDQ1VcHCwKlaseDHdUxeBixZISEjQe++9p9mzZ2vLli1q06aN+vfvr169eqls2bIX3T4NSGlpaTYXZUpKSqEfze+k3PRjyqWmptqyznWczzO2k9W9jG1lLJfxsWk3N9ccZRzHovpZ8vPzszlRzTGv5yVKlLB1Lqaec93M2svsWk51nO87n+fUVm7KFtXzQj8IIIAAAggggAACCCDg+gJmjmH69OmaPHmynVMwa3M98sgj9n1STtGvXbtW//rXv/TZZ5/p9ttvt8Wfe+45vfHGG9q2bZuqVKmSUxMX3H/99df15JNP6uzZsxfcy/ZCgJ/KPXWz/OpVyrYYNxHwJIEAP38NatJRY9v08qRhue1YzHzY3LlzNXbsWJ0+fVom37hZm9Cspe7NGy6u9ezHJx5W82VDXCsookEgE4HWVYO0rvP4TO5wqSAFVq9era5du8qs79G0adOCbLrQ2/rhhx/suhUrV65U7dq17b+5Dz/8sC655JJC75sOik7gxIkTevrpp+17zFtuuUVz5sxRvXr1ii4AekIAAQQQQAABBBBAAAEEvExgypQpGj9+vP73v/95/dy2uz/1jRo1Us+ePfXSSy+5+1CIHwEEEEAAAQS8VMDn3Je60rx07AwbAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAGXFjBf5OjRo4e2b9+uBg0auHSsBIcAAggggAACCCCAAAKuKfDjjz8qLCxMf/75pxo3blxgQbZr184mjjdfSGZDAAEEEEAAAQTcVeDvv//WwoULNXPmTMXGxrrrMNLFbRb7GTBggMLDw1WpEslC0+HwAAEEEEAAAQQ8XiAuLk6BgYGKjIy0c2IFPeDExEQtWrRIH330kb744gubtM/00blzZ9WqVct2Z+bjvv76a5ss+Y477lDLli2zDOPjjz+2Cf/MMbfbqVOn7OtXk1C+VKlS+ve//63o6Ght3LjRNlGuXDndf//9Kl++vD788EP79wYdO3ZU8+bNc9tFnsqZhNARERGKiorKUz1PL/zBBx+oT58+2rFjh+rXr+/pw2V8CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALFKJCQkKCYmBi7m1wkjnOz1smZM2dsZNWrV5fJVZdxN2uhlChRohijp2tPFEhLS1NqaqpSUlLs0fncXNu0aZMWLFigTz75RAEBAbr77rt177332p9PR53cHE0Zx+7oI7N6ztecz53rONpxvu+4lrFcTo+zqpdZ2xmvJScnZzomR5vZHY17UW9+fn5y3s3vE+fHzuf5vWfayFjX+bHzuaO/3F5ztO1c3vnccT9ju7kpk1VdR1tF/VzRHwIIIIAAAggggAACCCDgaQKHDh3ShAkTbK7epk2batasWbryyitzHGbPnj1t/uItW7aoTJkyMjmHg4OD1aVLF9tWjg38vwJmfsDkQ37vvff0/PPP56qav7+/nSsx60ksCzmopLJ+uapHIQQ8RSDAz1+DmnTU2Da9PGVIHjEO83tw8uTJmjJliipWrKhx48bZNW98fX09Ynx5GcSqVav01FNPadeuXRo4cKCee+45Va5cOS9NULaABeITD6v5siEF3CrNIVDwAq2rBmld5/EF3zAtphNo06aN6tWrp5UrV6a77k4PzL8x06ZN07x582T+rX3kkUc0dOhQ1a1b152GQaw5CJjPwx966CHt27dPU6dO1QMPPJBDDW4jgAACCCCAAAIIIIAAAgjkR8B8Rnj99dfbzwnzU586riFgPrc1z6V5P33NNde4RlBEgQACCCCAAAII5FHA59yXuor+W115DJLiCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgh4o4BJ6hYYGKh///vfevXVV72RgDEjgAACCCCAAAIIIIDARQqY9xUVKlTQ9OnT7ReIL7K589UrVaqkF198UYMGDTp/jRMEEEAAAQQQQMBdBcyfU2/cuFH/+9//XGoI5rWcSf5555132j2n4GrWrGm/tJhTOe4jgAACCCCAAAKeKmASrpvP2CMjIxUWFlbgw0xKStJvv/2m1157TUuWLLH9mE7Moorly5e3iYDNomCjRo1SbGysTALKwYMH6/XXX08Xi3n9uXz5cr3wwgv6/PPPVadOnXT3c3qwbNkymUT1Zn7u6aeftsV79+6tRYsW6ZdfflGLFi3stb179+qWW27RH3/8YZNH5tRufu6PHj1aERERioqKyk91j61jFhswP4vmuWJDAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEECgOARSU1P1119/KSYm5oJ93759NiR/f381atTI5lAxeVSc9ypVquQ7bJNjxewmBrPndO5837m847rj6HzP+Ty7+ykpKefjcK5jzjPbndsqyPsZ4zBtZ3bNuc/83Dd1HGPIWN/x2HHMGENW153LOco4jpndM/27wubr6ys/Pz+bf8f5mPHcPM7smqN+bu9nVS6ztjNeM7mLMvbnaC+nY37r5dSuuW/iyqqc6ZcNAQQQQAABBBBAAAEEEEAAgeIWiI6OtmtobdiwQf369dPEiRNtruCs4tq/f78aN24sk893xowZtti7776rBx98UD/99JOaN2+erurx48dt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LNCcOXNsXp0bb7xR/fv3V7du3RQQEFD0AdEjAggggAACCCCAAAIIIIAAAgi4jYCZzxowYIDM+lqDBw/WxIkTVapUqfPxnzx5UrGxsYqOjra5fbdv366goCDt3LlT5p7ZzHpcLVq0UGhoqN1DQkLs0eT+9fPzO9+W80lwcLC2bdt2/pKZXzL5h02+4ilTpqht27bn7zlOai4M16mUJMdDjgh4hUCAn78GNemosW16ecV43X2QGzdu1MiRI/X999/r3nvvtb83zfo5nrCZeWizDtDbb79tf8e/8sor6tSpkycMzePGEJ94WM2XDfG4cTEgzxNoXTVI6zqP97yBuciIvvzyS3Xo0EGbNm3SNddc4yJRFXwYERERmjx5sr744gs1adJEZl2U3r178xlpwVMXeYvHjx/X448/rnnz5qlPnz6aMWOGKlSoUORx0CECCCCAAAIIIIAAAggg4CkCp0+ftn/H/swzz9i1QD1lXN44DvOdherVq2v58uXq2rWrNxIwZgQQQAABBBDwEAGftHObh4yFYSCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg4JECPXv2VFxcnP3CpEcOkEEhgAACCCCAAAIIIIBAoQp88sknuvPOO23S9ksvvfSi+zJJT95///10SasuulEaQAABBBBAAAEEEMhSYNSoUVq4cKFd/KRMmTJZluMGAggggAACCCDg7QLmc/XAwEBFRkYqLCys0DhMosVp06YpJSXF9mGOZcuW1fPPP6+nnnoqT/3ec889WrlypU6cOKGSJUvmuu6+fftUv359m8TeJAocMmSISpQooalTp9ok9iYZvUkO+dZbb+W6zfwUHD16tEwiyqioqPxU97g6u3btsosGfPDBBzLPLRsCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKeInDy5EmbkzgmJkZmj46OtntsbKwSExPtMCtWrKjQ0FCFhITY3XEeFBSkgIAAT6FgHEUgkJaWps8//1yzZ8/Wxx9/LJNbu2/fvurfv78aNWpUBBHQBQIIIIAAAggggAACCCCAAAIIuKvAG2+8IZM3t0KFCrrpppt08OBBO4+1e/dumTkHk8fX5PY1OX6rVaumZ599Vo0bN9aCBQu0Zs0abd26VVWqVMn18Lt166ZVq1bZtn18fGx+2okTJ+quu+7Kso2aC8N1KiUpy/vcQMATBQL8/DWoSUeNbdPLE4fnsWMyOdRN7vW//vpLjz76qJ555hlVrlzZLcdrcsGb3O1mN/9GjBs3zs47+/r6uuV4vCHo+MTDar5siDcMlTG6uUDrqkFa13m8m4/CdcM3r+nN7+r//ve/rhtkAUb2+++/a8qUKVq8eLF9X2LWIhkwYIAqVapUgL3QVHEImLWlH374Yfu3E+b9Z4cOHYojDPpEAAEEEEAAAQQQQAABBNxewLxnDg8P1549e1S9enW3H483D2DhwoX2b8MPHz6scuXKeTMFY0cAAQQQQAABNxfwOfdHaWluPgbCRwABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAowW+/vpr3XjjjYqKilKrVq08eqwMDgEEEEAAAQQQQAABBApe4MiRI/aL3+bLwp06dbroDu6++26bBGv58uUX3RYNIIAAAggggAACCOQscOjQITVo0MAmnhs+fHjOFSiBAAIIIIAAAgh4qUBcXJwCAwMVGRmpsLCwQlMwr8mmTZumlJQU20dycrLKlCmjNm3aaNOmTXnqd86cOTZZsnnNl9eta9euNhn9hg0bNHDgQC1atEi1atWySRBSU1PVq1cvtWzZMq/N5qm8SaQfERFh/54hTxU9tPCwYcNssv+dO3fKz8/PQ0fJsBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEgvsHfvXsXExCg6OtoeHefx8fEyy6ebXBwmp15ISIjdQ0NDz59Xr149fWM8QiCDgPn5mjdvnubOnas9e/aoQ4cOGjBggLp06WJzZWcozkMEEEAAAQQQQAABBBBAAIH/J2DeQ3344Yf67rvvZHKVesvm7++vm266SXfddZcqV67sLcP2unGePn1a27ZtSzcn5ZibOn78uPUwc1Jmburqq69W586d5ZiTCgoKkvk5MWsEX3fddRo5cqTGjx8vU69x48Z27mHhwoW5Nn3uuec0YcIE1ahRQy+99JLuv//+HHPT1lwYrlMpSbnug4IIeIJAgJ+/BjXpqLFtennCcLxqDCYPu8mn/sILL+jMmTN66qmnNHToUJUqVcotHEz8Zo557NixOnnypJ588kk9/vjjKl26tFvE781BxiceVvNlQ7yZgLG7iUDrqkFa13m8m0TrXmGa9T/Ma/b169erXbt27hX8RUa7b98+uy7K7Nmz7dooDz30kMx6GGZNFjb3FTh8+LBdX8bM1zz22GN69dVXVbJkSfcdEJEjgAACCCCAAAIIIIAAAsUgcMstt9i1Q9esWVMMvdNlQQr06NHDfkb7n//8pyCbpS0EEEAAAQQQQKDIBXyLvEc6RAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgTwJtG3bVk2bNtWbb76Zp3oURgABBBBAAAEEEEAAAQSMgElkZhKrmy+/F8T2yy+/qGXLlgXRFG0ggAACCCCAAAII5EKgatWqGjRokCZOnKhTp07logZFEEAAAQQQQAABBIpSoESJEjYx/Pfff6+dO3em63rRokXZvobbsmWLTT6frlIuH5gF8ExS+169ep1PtBweHi6TnN60yxxeLiELqNixY8c0f/58m6TRLC7AhgACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgLcI1K5dWzfddJPNmzdt2jT95z//0V9//aUTJ07o119/1eLFi3X//ferYsWK2rBhg0aMGKEbb7xRNWrUUKVKlXT11VfL5E556aWXtHLlSps/5cyZM97CxzhzEDA/X2PGjNGuXbu0Zs0aBQQE6J577lHdunX17LPPKi4uLocWuI0AAggggAACCCCAAAIIeI+AeY/02muv6dprr1W9evX0/PPPZ5sb1RNljh49qsGDB9t5h1tvvVVz5szRoUOHPHGoXjGmAwcO2Pkk8zwOHz5cd9xxhxo1aqSyZcuqefPm6tmzpxYsWCDzvJu1fydPnmzLm3qnT5/WqFGj9N133+n333/XzTffbPMI+/v7W7vWrVtrxowZevHFF+2cVPny5fXGG2/o3XfftfNbuQV+8MEH7c+ZmQ974IEHRG7a3MpRDgEE3EXA5GE36+bs2LHD5kMfP368goODbT701NRUlx7G6tWr1axZMw0ZMsTOK5sxPP300ypdurRLx01wCCCAAAL/v4D5N+e6665Tu3btvI6kVq1aevXVV7V7926NGzdOq1atUlBQkF2fJCoqyus8PGXAVapU0fLly+37TvNe9sorr9Sff/7pKcNjHAgggAACCCCAAAIIIIBAoQvEx8fryy+/tJ/JFXpndFCoAsnJyYqIiFCnTp0KtR8aRwABBBBAAAEEikLAJ+3cVhQd0QcCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgjkX2DmzJk2AeLevXtt8sP8t0RNBBBAAAEEEEAAAQQQ8EYBk2TKJMdev379RQ3/n3/+sUnaP/74Y/3rX/+6qLaojAACCCCAAAIIIJB7gYMHD6pBgwYyCY1MYlM2BBBAAAEEEEAAgQsFzAIHgYGBioyMVFhY2IUFCuiKSSq/bNkymddoVatWta2aRQu7detmF1Ywr9nM9aVLl9qk8r1797YLLZjFF7p06aKmTZvaOkeOHNHdd98tM9dWoUKFPEdnvhZoEjyaRRQ3btxo60dHR9sk9vPnzy+SxBajR4+2iRdIMClNmTJFL7zwgk3AmZ/nM88/AFRAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAjQX27dsnky8lJibm/G4ex8fHKzU1VX5+fjYHX2hoqEJCQuQ4mvNq1aq58cgJvSAETL6ht99+WybXzoEDB3T77bdrwIABuuOOO+zPTkH0QRsIIIAAAggggAACCCCAgLsImDWJPvzwQy1fvtzmZa1YsaLNgdq9e3fdeuutCggIcJehFFiciYmJ+uSTT6zJZ599pqSkJLVr107GxOSDrV69eoH1RUMXL2Cenx07dpyfKzJzRI55o4SEBNtB+fLlz88ROeaJzPGyyy5TyZIlsw3i888/l8kRXKVKFa1YsUJNmjRJV/7RRx/VO++8o6+//lqtW7fWfffdpw0bNuiPP/4otPWDay4M16mUpHRx8AABTxcI8PPXoCYdNbZNL08fqsePb//+/Xr++ec1b948+zv11VdftXO0rjTw7777TqNGjbL523v06KGXXnpJjRo1cqUQiSUXAvGJh9V82ZBclKQIAsUr0LpqkNZ1Hl+8QXhg7z/++KNdd8S8pzOfBXr7lpycbNdJmTx5sn7++WfddNNNGjlyJDZu/INh5nPM+89ff/1Vr7/+uvr37+/GoyF0BBBAAAEEEEAAAQQQQKBoBMw6oTNmzNDevXvl7+9fNJ3SS6EIfPXVV2rfvr22b9/O/H2hCNMoAggggAACCBSlgM+5hebTirJD+kIAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIG8C5gEBLVq1dLYsWM1fPjwvDdADQQQQAABBBBAAAEEEPBqgblz52ro0KH6559/VKJEiXxbmCRXN954o3bv3q06derkux0qIoAAAggggAACCORdwCTree+992SSvpQuXTrvDVADAQQQQAABBBDwcAGzIFxgYKBd6CAsLKzAR3vq1CnNmTNH48aN09GjR+1ic+Hh4br66qttX2YOzrxmM0npL7nkEk2cOPF8gr4TJ07ohhtu0C+//KI2bdrYJIwm4fyDDz6ocuXK5TtWk1T58ssvV+fOnc+3cdddd+mDDz4okteMo0ePVkREhKKios73740nKSkpNvGEsZ86dao3EjBmBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEECkTg9OnT2rZtm6Kjo7V161bFxMSc383aLWarWLGiQkNDFRISYo+O80aNGikgIKBA4qAR9xBITk7WmjVrNHv2bP33v/9V7dq19cgjj+ihhx6y5+4xCqJEAAEEEEAAAQQQQAABBPIusGPHDq1YscLuP/74oy699FJ16dJFPXr00M033yx/f/+8N+qhNUxe2LVr11qrTz/9VCbHbNu2bdW9e3d17dpVNWvW9NCRu96wDh8+bOd5zLyP2c28jznu3LlTJr+rj4+P6tWrl+m8j1nP92K2vXv3qmfPnvr555/15ptvqm/fvuebM3136tRJv/32m77//nuVL19eTZs2tet0LV68+Hy5gjyp+96DOn72VEE2SVsIuIXA8BZ3aUzrnm4RK0HmLGB+h5s85WaO1hU3s97ipEmTVBg5611xvJ4Y076TR3X5kkc9cWiMycMErq4eov90Guthoyr+4Zi1H8zr+M2bNxd/MC4Wgflc1Pwb98UXX6hZs2Z64okn1KtXL+YCXOx5yk045vPusWPH6uWXX9add96pefPm2Tme3NSlDAIIIIAAAggggAACCCDgbQJpaWkKCgqyn4u/9tpr3jZ8jxvvqFGj7OcL5jNjNgQQQAABBBBAwN0FfM69WE1z90EQPwIIIIAAAggggAACCCCAAAIIIIAAAggggAAC3iAwePBgff7554qNjbVfbPeGMTNGBBBAAAEEEEAAAQQQKBiBP//8U02aNJFJ+Na6det8Nzp9+nSNGzdOJhkXGwIIIIAAAggggEDRChw8eFANGjTQhAkT9Pjjjxdt5/SGAAIIIIAAAgi4gUBcXJwCAwMVGRlZbMl8U1NTtWfPHtWpU0e+vr4XqCUkJNgFCsuUKXPBvfxcMIsllixZMt3fEJhFDEqXLp2f5vJcxyR2joiIUFRUVJ7relKF5cuX695777ULVzZs2NCThsZYEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQcBmBvXv3Kjo6WjExMemOu3fvllmm3c/PTyb/R0hIiEJDQ9Mdq1at6jLjIJDCEdixY4fmzJmjBQsW6OjRo+rcubP69++vW2+9NdN8QBmjGDFihG655RbdfvvtGW/xGAEEEEAAAQQQQAABBBBwCQGzjumKFSvs/vPPP6tKlSq666671L17d3Xo0EElSpRwiThdOQiTt/XTTz+1hp988olOnjyp6667Tj169FDXrl1Vu3ZtVw7fLWJLTk7Wzp07z8/fOM/lHDlyxI6hbNmyCg4OTjd/Y+ZyzLXCzKtrYnv66ac1efJk9enTR2+99ZZMLGY7duyYbrjhBpl8v99++63Nt9uxY0ctXbpU99xzjy1TkP+z+dA27T1xtCCbpC0EXF7A51yE19VorCqlLnH5WAkwbwImR7n53e9KW40aNezvdVeKiVjyJ7Bu7686fvZ0/ipTC4EiEri8Ul0FV6hVRL15Rze///67WrRooVWrVqlLly7eMeh8jPLXX3+172+WLFmi6tWra9iwYerXr58uuYTXW/ngLNYqGzZsUO/eve3fPbz//vtq165dscZD5wgggAACCCCAAAIIIICAKwp89dVXat++vcy8QdOmTV0xRGLKg4B5Ds3fbb/++ut5qEVRBBBAAAEEEEDANQV8zn2xMc01QyMqBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQcBbYsmWL/cOT//znP7rtttucb3GOAAIIIIAAAggggAACCGQrYP5EqHLlynrhhRc0ZMiQbMtmd/PBBx9UfHy8/vvf/2ZXjHsIIIAAAggggAAChSTwxBNPaNGiRTZxXmEmPy2k8GkWAQQQQAABBBAoVIG4uDgFBgYqMjJSYWFhhdpXYTQ+aNCgHJs1yRpbtmyZY7miKjB69GhFRETYhPhF1acr9nPNNdeoZs2aWrlypSuGR0wIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgh4tMCpU6cUGxur6OhoxcTEnD+aa4mJiXbslSpVUmhoqEJCQtIdGzVqJH9/f4/28bbBJSUl6cMPP9Ts2bO1YcMGNWjQQI888ohMfu3q1atnyvHTTz+pdevW8vX11axZs2z5TAtyEQEEEEAAAQQQQAABBBAoYgHzXnfFihVavny5fvvtN1WrVk133323unfvrvbt28vPz6+II/Kc7k6fPi2zNqyx/eSTT3T8+HFde+211rZbt26qW7eu5wy2EEaSkJBwfg7G/Jw65mW2b9+us2fP2h5r165t52Gc52TMeZ06deTj41MIUeWuybVr16pv3752PS+z9oOZEzDb/v37df3116tChQpav369nnzySS1ZskQ///yzzXucu9YphQACCCCAAAIIIIAAAhcrcO+992rr1q365ZdfivW9w8WOo6jq7969W1OnTtXbb79tP+8cMGCAHnvsMdWqVauoQqCfAhA4evSo/Zx69erV9v3ouHHjVKJEiQJomSYQQAABBBBAAAEEEEAAAc8QCA8P159//qnNmzd7xoC8eBTx8fGqX7++XQf15ptv9mIJho4AAggggAACniLgk3Zu85TBMA4EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBDxdoF27dvbL5GvWrPH0oTI+BBBAAAEEEEAAAQQQKGCBO+64w76f+OCDD/LdcqtWrWzyuClTpuS7DSoigAACCCCAAAII5F/gwIEDatiwoV588UUNGzYs/w1REwEEEEAAAQQQ8ECBuLg4m4g9MjJSYWFhbjdCs9hATptZhMAkzneVbfTo0TbxQlRUlKuEVORxfP/997rmmmv0zTff2AUCijwAOkQAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBLAX27NmjmJgYRUdHpzvu3r1bZmn3EiVKqFGjRgoJCVFoaOj5ozm/9NJLs2yXG+4hYJ732bNna+HChUpMTNRdd92lAQMG2DzbPj4+5wfRr18/vfPOOzp79qy99uyzz2r8+PHn73OCAAIIIIAAAggggAACCBSlwJYtW7RixQq7//HHH6pRo4buvvtu9ejRQ23btpWfn19RhuMVfZ05c0aff/65Nf/oo4907NgxXXXVVerevbvd69ev7xUOGQeZmpqqv/76K928imOOxaybYLZSpUopODj4/JyK8xxLuXLlMjbpMo/37t2r+++/3+aUHTNmjJ566in739auXbtsjtl69erJrBt88803q0yZMracv7+/y8RPIAgggAACCCCAAAIIeKqA+Wz38ssvl1l395577vHUYRbKuBISEjRz5kxNnz5dR48eVe/evfXEE0+ocePGhdIfjRaOwJw5c/T444+rRYsWWrp0qerWrVs4HdEqAggggAACCCCAAAIIIOBGAsePH7efm0+aNEmDBg1yo8gJNTOBWbNm2TkLM38REBCQWRGuIYAAAggggAACbiXgc+5LimluFTHBIoAAAggggAACCCCAAAIIIIAAAggggAACCCDgxQLLly9Xr169tHPnTpkvlLMhgAACCCCAAAIIIIAAArkVmDBhgt5++23FxcXltkq6csnJyTJJuUwbffr0SXePBwgggAACCCCAAAJFJzBixAgtXrxYJvmoSajKhgACCCCAAAIIIPD/C5h5r8DAQG3cuFHXXnstLEUgYJJFrl+/XlFRUUXQm2t20bNnT/s3HJs3b3bNAIkKAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBC4QOHnypGJjYxUdHX1+j4mJsdfMPbNVqVJFoaGhdg8JCTl/bNiwofz8/C5okwuuK3D69GktW7ZMs2bN0nfffafg4GD169dPffv2VUBAgKpVqyZTxrH5+Pjovvvu04IFC+Tv7++4zBEBBBBAAAEEEEAAAQQQKDSB33//XWat0hUrVmjr1q2qVauWunbtqu7du+uGG26Qr69vofVNw+kFkpKSFBERYZ+LNWvW6O+//1ZYWJh69Oihbt26ycwLeNp2/PhxmXkRxzyJ43zbtm06c+aMHW6NGjXOz404z5fUr1/fbX8+09LSNHXqVD399NO64oor9O677yooKMjOD7Vv315169bVjBkzZM4HDhyoSZMmedpTz3gQQAABBBBAAAEEEHA5gfDwcEVGRmrLli1u+16juFHN+7j33ntPkydPtu9vOnfurJEjR+r6668v7tDoP5cCf/75p52H2L9/v32v2qlTp1zWpBgCCCCAAAIIIIAAAggg4JkCc+fO1ZAhQ7Rv3z5VqlTJMwfpRaO688477bzP6tWrvWjUDBUBBBBAAAEEPFnA59wfoqV58gAZGwIIIIAAAggggAACCCCAAAIIIIAAAggggAACniSQnJysevXq2QR0L730kicNjbEggAACCCCAAAIIIIBAIQt8+eWX6tChg/bs2aPatWvnuTeTaK558+b67bff1KxZszzXpwICCCCAAAIIIIBAwQgcOHBADRo00Msvv6yhQ4cWTKO0ggACCCCAAAIIeIDAP//8o0aNGtmRjBo1SoMHD1aZMmU8YGSuN4Rdu3Zp7NixWrRokV34YOnSpa4XZBFEFB8fbxd9MIsDmMUC2RBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAvQXMcu8mr0hMTIyio6PPH835vn377OACAgIUFBSkkJAQhYaG2t1xXqFCBfcG8ILoTa7tWbNm6f3339eZM2ds3u2oqCilpqamG72fn5+uv/56ffTRR7rkkkvS3eMBAggggAACCCCAAAIIIFAQAr/88otWrFih5cuXKzY2VnXq1LF5Prt3767rrrtOPj4+BdENbVyEwNmzZ7Vu3Tr7HK1Zs0ZHjhxR69atZZ4js5v5AXfZMs55OM97ZJzzMPMdjrkOx7knz3ls2bJFvXv3tv8dPv/88xo+fLhM/t327durRo0aCg8Pt+tCrF69Wnfeeae7POXEiQACCCCAAAIIIICA2wmY1+HBwcGaP3+++vTp43bxu1rA5n2geS87adIkbdq0Sddcc43MWi7mfY2vr6+rhUs8GQROnjypQYMGyayHMnLkSL344osqUaJEhlI8RAABBBBAAAEEEEAAAQS8Q8B8fl6vXj198MEH3jFgDx6l+dvtypUr67XXXlO/fv08eKQMDQEEEEAAAQS8ScDn3Icyad40YMaKAAIIIIAAAggggAACCCCAAAIIIIAAAggggIC7C4wdO1ZvvfWW9uzZI5NUkA0BBBBAAAEEEEAAAQQQyI1AYmKiKlasaP+wvUePHrmpkq7Me++9p0ceeUSmHb40nI6GBwgggAACCCCAQJELmKSjS5Ys0c6dO1WqVKki758OEUAAAQQQQAABVxX4+++/bfLC6dOnq3z58nrmmWdsYgA+Wy+YZ2zv3r02qeDcuXNVv359mb9f6NWrl9cmh3ziiSe0dOlSuyAAc6YF8zNGKwgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCLiqwPHjxxUTE6Po6Oh0x23btun06dM27OrVqys0NNTuISEhaty4sT2vV6+e1+ZpcdXn88SJEzZft8kjc+zYMaWlpV0QqskrExQUpIiICNWpU+eC+1xAAAEEEEAAAQQQQAABBPIqEBUVpRUrVth9+/btMu8Xu3XrJrOW0NVXXy0fH5+8Nkn5IhJITk7Wl19+aZ+71atX69ChQ2rZsqW6d+9udzMP4Aqbeb8bGxt7fu7CMY9hrp08edKGWLVqVZl4necwzHmDBg3k5+fnCsMo8hjOnj2rV1991ebeDQ4O1pw5c1SlShXdeuutdq2uK664Qp999pkiIyOtXZEHSIcIIIAAAggggAACCHiBQL9+/bRu3Tr7fob1Hwr2Cd+4caMmTpyojz/+WOY9j/mMtE+fPipZsmTBdkRrBS6wYMECPfroo2rVqpVdH6V27doF3gcNIoAAAggggAACCCCAAAKuLGA+5zSfbX7++ef2sztXjpXYchb44osvdNtttyk+Pl5169bNuQIlEEAAAQQQQAABNxDwOfeltAu/leYGgRMiAggggAACCCCAAAIIIIAAAggggAACCCCAAALeKrBv3z7Vr19f77zzjv797397KwPjRgABBBBAAAEEEEAAgXwImC/83njjjXr99dfzXHvEiBFav369fvrppzzXpQICCCCAAAIIIIBAwQrs379fDRs21CuvvKLHHnusYBunNQQQQAABBBBAwAMEDh48qJdfflmzZs1StWrV9Pzzz+v++++3ydo9YHhFPgSzoIF57fnWW2/JLBAw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+VSyZEmNHj36iXWpgAACCCCAAAIIIIAAAggYAfNh3tixY9skquERuX79uk0Enj9//vBUpw4CCCCAAAIIIIBAFAh8/PHHunTpkiZMmBAFozMkAggggAACCCAQfQRMUrzNmzfrp59+0pYtW5Q7d261aNFCx48fd/pJLl++XMWKFbMLSJnF/bZt2yazoJT5Pm7cOP388892EcDx48crODjY6ecbGRMwCUhTpEihunXrRkb39IkAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCLwQAVdXV2XJkkVvvPGGOnXqZHPQrF69WufPn7e5DdeuXSuTb+W1117TjRs3NHXqVDVo0EDe3t6KHz/+f7aNaPB79+4NazJ//nyb86d169Y6ffp0WDk7CCCAQHQWOHTokLp166Z06dKpfv36dg2BuXPn6ujRo+rVq5ctj87zf3BuefPmtX9/zN+BsWPH6tixYypXrpxy5MihoUOH6sKFCw824RgBBBBAAIEYLXDt2jW7jmmbNm1kctX7+vqGy+PKlSsaNmyYOnTooCVLligoKChc7e6tZK4VzfOW3r1731vsNPt79uyxzy9+//13p4mZQB1TIF68eNqwYYPM2sIhISFycXGRn5+fze9rcvzee49j2rRp//lv5uzZs0qaNKnef//9p/o36Zg6RIUAAggggAACCCCAwH8LmOvKL7/80j4HTpQo0X9X5IzDCGTOnFmjRo2y9+7NvYjhw4crY8aM9vXmkydPOkycMTWQrFmzav369apYsaJef/11DR48OKZSMG8EEEAAAQQQQAABBBCIBgITJ06062r+73//iwazYQpGYPHixfaaFQ0EEEAAAQQQQCA6CrhGx0kxJwQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEYpJA27Zt9euvv4oPR8SkR525IoAAAggggAACCCDw9AKxYsWSj4+P/vzzz3B1sn37dpucKn/+/OGqTyUEEEAAAQQQQACBFy+QJk0atWzZUoMGDdKdO3defACMiAACCCCAAAIIRCMBk6S9Tp062rlzpyZMmKDly5fbBBIm+bpJxO5sm7kPaBZOevXVV/XSSy9p48aNmj17tswCS2Yz823WrJn279+vhg0byrwH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DQoODv7PGLZt22aT1ObNm/c/63ACAQQQQAABBBBAwLEETGKkFi1a2CSj/v7+jhUc0SCAAAIIIIAAAgg4tcBrr70mc89w5MiRmjZtmrJly2b3AwICnHpejwt++PDhMgthlC9f/nHVOIcAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQAYG9e/eG1Y4VK5ZcXV3tsZubm11/p379+jav4u+//67z58/r0qVLWrp0qSp3fEcemZOFtWUHAQQQiAqBwMBAzZ49W5UqVVLWrFk1adIkmw/28OHDWrhwoapXr25z/EdFbDF9zPjx46t58+Z2HYYtW7aoXLly6t27t0zO3oYNG2r16tUxnYj5I4AAAghEYwHzPGTr1q2qXLly2CwLFiyo69evq0qVKmFl5u/luHHjbLnJu2nOt23bNux8RHby5Mljr+FMGxcXF7377ru2+YIFC8LdzalTp+Tn56eTJ0+GtTHrKl25ckU3btwIK0uTJo3dN8+1zJYzZ077feXKlerXr5/d3717t06cOKEDBw7YY/O/gQMHysfHRy+99FJYWZEiRey+iZkNgWcVMM81P/zwQ5mfv02bNqlkyZIy9zrCsz1u7bB725t65r5I4cKFdfTo0XtPsY8AAggggAACCCCAgNMImDUuzDVb165dnSZmAn0+Ap6enpowYYLM6yj16tVTr169lDFjRn322Wf2deDnMwq9PEngrbfe0tq1a3Xw4EEVLVpU+/fvf1ITziOAAAIIIIAAAggggAACL0xg3bp12rdvn5o2bfrCxmSgyBdYvHixKlasGPkDMQICCCCAAAIIIBCFAv98Ii4KA2BoBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQeD4CdxMmTJ06VdeuXXs+ndILAggggAACCCCAAAIIRFsBkyjNXDvs3LnzP+doksKZD1rHiRPnP+twAgEEEEAAAQQQQMDxBEyCJLNAynfffed4wRERAggggAACCCCAgFMLmMX52rRpYxMCtmzZUh999JHy5s2rX3/91ann9ajgz5w5o59//lkffPDBo05ThgACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIPCUAu7u7kqePLnKli2r999/X5MmTdK2bdt0+/Zt7d27V1OmTFHnzp1Vvnx5pUiR4ilHoRkCCCDwfAWOHj2qHj16KEOGDKpdu7bMmmGzZs3SsWPH1LdvX2XMmPH5DkhvzySQP39+jR49WqdPn9bXX3+tAwcOqHTp0sqVK5eGDx8uX1/fZ+qfxggggAACCDiagLmmih8//kPXULFixXpkqK6ururYsaNq1qypLVu26M6dO4+sF5HCokWLyvRr/v6Gd8uZM6dSp06tJUuWhDU5d+6cTF8JEyYMKzP9mu3u97sn0qZNq40bN6p9+/bas2ePsmbNquDg4Lun7bWmyZ1672aex7EhEBkChQoV0urVq+Xn56eVK1eqUaNGYWt/Pfhz9+Dxk+IJDAy0OYELFCigP/7440nVOY8AAggggAACCCCAgEMJmOu0AQMG6O2331amTJkcKjaCeXEC5hp+2LBh9nUVcx1v7t2b11bMmiAnT558cYHE4JHMayfmPkqSJEnsvZcVK1bEYA2mjgACCCCAAAIIIIAAAo4kMHHiRHl5ealgwYKOFBaxPIOAeX/eX3/9pYoVKz5DLzRFAAEEEEAAAQQcX8Dd8UMkQgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEAivQOPGjfXJJ5/o+++/V7t27cLbjHoIIIAAAggggAACCCAQAwXy5ctnk6StW7fOvhn+UQQmMZz5cC8bAggggAACCCCAgHMJpEuXTs2bN9fAgQPVrFkz/VdiX+eaFdEigAACCCCAAAIIOJLASy+9pEGDBqlNmzb6+OOPVb16dZUrV84ma4wu9xRHjRplkx42aNDAkeiJBQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQcHqBRo0ayXyxIYAAAs4gMG/ePI0ePVpLlixRypQpbb7XFi1aKFOmTM4QfoyPMX78+DZXr8nXu2XLFo0bN06fffaZunXrptq1a6t9+/YqUqRIjHcCAAEEEEDA+QWCg4N18+ZNrVixQhUqVAj3hMqXL2/bxI4dO9xt/quiyVeaIEECZcmS5b+qPFTu4uKi+fPn27/LXbp0kY+Pjw4ePKhp06Y9VPdRBZ9++qlWrVqlxYsXK27cuJo5c2ZYtcDAQN26dUsbNmwIK7t3x4zNhkBkCJifrdKlS9uv8ePHa8GCBZo8ebIWLlwo83Mpdze5l82qgGUHpOCQcIcQFBSkK1euqGTZ0nppZE25xHYPd1sqIuAIAoOLNlbr3JUcIRRieEYB87zD/K3u3bu3Dh069Iy9RU7z1KlT23XeW7Vqxbo9kUMc6b1e8b+p3D+11a1A/0gfiwEQeBaBaple0fflOj5LFzGi7fTp0+21nrnfzoZA0qRJ7X36Dz/80N6zHzZsmL7++mv7+rFZ/8TT0xOkSBQwr3WtXLlSTZo0UcWKFTVmzBj7OkokDknXCCCAAAIIIIAAAggggMBjBcxruj///LP69Onz2HqcdC6B33//Xa6urnaNU+eKnGgRQAABBBBAAIGICfAOroh5URsBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAYcWSJw4sRo2bGiTTbVr186hYyU4BBBAAAEEEEAAAQQQiFoB82bpV155RevWrVObNm0eGcy2bdvEtcUjaShEAAEEEEAAAQQcXsAksJ8wYYImTpyo1q1bO3y8BIgAAggggAACCCDgnAJm4SuTcKNDhw7q1KmTXaTBJAns16+fTHJlZ91u376tsWPH2gWhnscCGM7qQNwIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQEwWOH78uKpVq6Zy5crpl19+UdWqVeXu7h6TSZx67gUKFNCYMWP0+eef66efftKIESNUqVIl+fr6OvW8CB4BBBBAAAEjkC9fPgvxww8/qEKFCmEoly5d0urVq1WjRo2wsnt3du3aZZ/j3Fv2tPtbtmzRtWvX9Prrr0eoi3jx4tn1k6pXr65EiRKpXr164Wp/5MgRmwPV5BCNGzeubRMcHBzW1jxvy5Url3bs2KFz584pZcqUYefYQeBFCZjctjVr1rRfly9f1owZM9R+TF95lM2uePUK2jBCgkMU4h8olxApJDD0Z9h8BQUrJPTL7oce/7Mf9M9xbHe5hH6xIeBMAu6ubjpz67IzhUysjxAICQnRzJkz1bNnT+3fv1/vvPOOBg4c+IiaUV9k1mfs0qWLvQdg4m3cuDH3dKL+YYlQBNf8b+tWoH+E2lAZgagQOHXzUlQM61Rjmr8f/fv311tvvSVPT0+nip1gI1cgfvz4+uCDD/Tee+9pypQpGjJkiCZNmmSvn7p27WrXP4ncCGJu7+Y+inmdxDxPatGihfbt26dBgwbJrHXNhgACCCCAAAIIIIAAAgi8aAHzvjw/Pz+9/fbbL3poxotEgcWLF6to0aL2PQCROAxdI4AAAggggAACUS7Au7ii/CEgAAQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEnq9A27ZtNX78eK1YsUJly5Z9vp3TGwIIIIAAAggggAACCEQrgeLFi2vatGmPnJNJcnry5El5e3s/8jyFCCCAAAIIIIAAAo4tkC5dOjVv3lwDBgxQs2bN5OHh4dgBEx0CCCCAAAIIIICAUwuYe41//vmnfvzxR3Xr1k3Tp0/Xxx9/rM6dO4ctwOBME5w8ebJu3rypd99915nCJlYEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIHnKODv7297GzZsmPLnz/8ce6arqBRIkCCBWrRoIXd3d7Vr1y4qQ2FsBBBAAAEEnkrg1KlTCg4O1oULF5QiRQrbR7Vq1VSgQAGZnJpx4sRRnTp1tH37dq1cudLmCb19+7a++OILVa9eXXnz5rVtLl26pC1btmjevHlPFceNGzdsHK6urrb9jBkzVLduXb366qvh7s8836pQoYLNY3r9+nXbX2BgoNKmTSsXF5ewfkyeULOZmJMlS2b3zfhm++mnn1SvXj1t27ZNq1ev1p07d2TOhYSE2H7ffvttvf/++5oyZYrN2f/zzz/bdn/88YfKly8f1p8t5H8IRKJAkiRJ1KpVK30We61uB/1zrWGGc3F1kUucf9aT+PenPhIDoWsEokDA1eWfvxVRMDRDPieBhQsXqkePHtq6dav9ez9nzhx5eno+p96ffzfmudCHH36ovn372lzjgwcPVq9evexzhrvPXZ7/qPSIAAIIIPAogblz52rXrl12LYtHnacMgVixYtn19po2baqZM2dq0KBBKlSokCpWrGjXPyldujRIkSBg7ruY50o5cuSwr5ns37/frnEdP378SBiNLhFAAAEEEEAAAQQQQACB/xaYOHGiqlatquTJk/93Jc44ncDixYtZC9TpHjUCRgABBBBAAIGnEeAdMU+jRhsEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBxYwCSZKl68uEaPHu3AURIaAggggAACCCCAAAIIOIKAuXY4dOiQzp8//1A4JiGa2by9vR86RwECCCCAAAIIIICAcwh069ZNZ8+elfkQJBsCCCCAAAIIIIAAApEtYJIDNmjQQPv27bNJGE0yZZMo8Pvvv7eLN0T2+M+rf7M4xIgRI9SwYcOwRTSeV9/0gwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIDA0wjcvn3b5sxcunSpbd6zZ0+tX7/e7ru5uWnevHl67bXXNG7cOPv9119/teuaxo4d2+YGnTlzpry8vFSkSBGZttOmTdPChQuVKFGiCIfToUMHJUiQQBUrVlTv3r3Vpk0bubu7a+rUqRHqy9XVVZkzZ1a7du1UuHBh5c6dW+nTp1eSJEn03Xff2b6+/fZbzZ492+63bdtWGzdutPv58uVTs2bNtGbNGvn4+Gj37t366quvdOPGDVWvXl0BAQE2v+jnn3+uBQsWKHHixHrllVcUP358JUuWTCYH6fHjxyMUL5URQAABBBCIaQIrVqywa6VXrlxZGTJkkFnf8Mcff5Snp6fDU6RNm1bffPON9u7dq2LFiqlRo0Z2XcY5c+Y4fOwEiAACCEQngX79+unNN99U3rx5o9O0mEskCJh7BHXq1NHff/+t3377TeY+SJkyZVSiRAl7XR8JQ9JlqMDbb7+tZcuWae3atSpZsqROnTqFCwIIIIAAAggggAACCCDwwgQOHz6sVatWqWnTpi9sTAaKfIFdu3bp9OnTqlChQuQPxggIIIAAAggggEAUC7iEvhExJIpjYHgEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBB4zgImEUOTJk107NgxpUmT5jn3TncIIIAAAggggAACCCAQXQSuXr2qpEmTyiR3Mx+ov3f78ssvNWDAAJ0/f/7eYvYRQAABBBBAAAEEnEzAJME1iXsPHDggDw8PJ4uecBFAAAEEEEAAAQScWeDcuXP69NNP7WINefLk0cCBA/XGG284/JTM82eTyHrnzp0ycbMhgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIOIbA0G2zNWTrLPkHBYYroLJp8ilpnIT31fUPCtCha2d1OPTLL3Q/olu6+MlUIX0B5U+WRe3Xjoto87D6qeMlkXeyzMqXLKNCQmTj2XLxsK7431ShFNn0+8mtYXWf105899gqmTqPiqbMoV5//RjW7Xt53rAW3+79PawsqnbcXFzlkyKrNp4/EGkhlAn9udhz+YTO3b6iEqly6dj18zp585IdL4F7HNXOWkKZEr5sH5MZh9bqdpD/Q7FkTJBCr6bzll+gv5aEPlYX/a6F1fFOlkmX/K6H9Rl2IgI7ZvytdUZEoAVVI0vg4MGDyp49u7Zs2aL8+fNH1jCP7HfNmjVatWqV9u7dqzp16qh69eqPrPdg4fLly20+2tSpU6tevXpKmzbtg1Uc7vjGjRtasWKF/vjjDw0ePPiFxTdp0iS1a9dOZnw2BBBAAAEEIiJgcr5PnjxZDRo0iEizF1b3ypUrCg4OtusSPTioORcrVizFixfvwVNPdXz79m1dvHhR6dOnf6r2d+7cUY8ePfTee+/p0qVLunbtmkyfZ8+eVZ8+fcKVY//69etKmPDfaz/TZ+zYse+LJzAw0PaZLl06BQQEhF6HhViH+yq9wIPevXtr+vTp2rVr1wsclaEcRSD15MaPvNZ0lPiIA4HIEIjl5qG2eV5Xr0L1I6N7+owEgfXr19u/0cuWLdNrr72m/v37q3DhwpEw0ovrcs+ePerZs6ddt9HHx0f9+vVTxYoVX1wAjBQhgeM3Lspr+vsRakNlBKJCwCf0NZ1lVftGxdBOMeaiRYvsGhV///23ChYs6BQxE6RjCaxbt86urbxgwQJ5eXmpW7du9jUTNzc3xwo0GkRz+PBhValSRWat6/nz56tAgQLRYFZMAQEEEEAAAQQQQAABBBxdwNyzHT9+vE6ePCmu9Rz90Qp/fCNGjFCvXr3sewl4XMPvRk0EEEAAAQQQcE4BV+cMm6gRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDgcQIm2VOSJEk0btzTJ5l7XP+cQwABBBBAAAEEEEAAgeghkChRIuXOnVvmA9EPbtu2bZO3t/eDxRwjgAACCCCAAAIIOJmASXZz5swZTZw40ckiJ1wEEEAAAQQQQAABZxdImTKlfd/Cjh07lCVLFlWuXFllypTRhg0bHHpqw4cPV4UKFZQnTx6HjpPgEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBB4vsMP3mEqmyq1vy7yvvoUbKqFHXGVNlFpdvGvoVKNJGl2yjeK7x358J/ecNXVfSZlDXfLXUPl0T5fD293FTb0LN9D2t0aqdJq82nnpuNac2aVkcRJqwRs9dbjBOFt+z7DPbbd8uvwaXLSJamUpfl+fb3uWUf1sJe8ri4qDl0Ifn/b5qmq374lIGz62m4d+Kv+hYrm62zG+L9fR/lyYg2wvpdbftYfr/byV1TbPGxr5v1ZaV2OIXo6b6L54OobGOCr0Z2fV6Z06fP2cfdyKhf5c3N12+h5XJ+/qKp4y590iviMQYYG///5bn3/+uT766CN5enqqbt26unXr1hP7GTx4sDp06KDr169r6NChypAhgxYsWPDEdlFd4bffflP79u31008/RXUojI8AAggggEC0EEicOLGSJk36yLmYc/HixXvkOfO8oW3bto/96t+//31t48aNq/Tp099XZg6e1I85v3XrVjVq1Ehp0qRRpkyZ5OPjo7Jly+qNN95Qs2bN1K5dO7m7//Pc/aEB7ilImDDhPUdS7NgPX+eZftKlS2freXh4KFasWPe14QABBBBAAAEE/hEwf5+rVq2qYsWK6c6dO1q1apWWLFmiwoULOz1Rrly5NGPGDG3evFkmh3qlSpVUqlQprV692unnxgQQQAABRxXo16+fvcYrWLCgo4ZIXA4uULx4cc2fP19mjWWz/vLbb7+tnDlzavz48fL393fw6J0rPLOuzJ9//mmdzXMk89oNGwIIIIAAAggggAACCCAQmQLBwcGaPHmy3nnnHbm5uUXmUPT9ggWWLVtmX/vncX3B8AyHAAIIIIAAAlEi4BolozIoAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIRKqASUbQokULjRs3ToGBgZE6Fp0jgAACCCCAAAIIIICAcwuYD0OvW7fuoUmYD0d7ez9d8uqHOqMAAQQQQAABBBBAIMoETMJdkyB3wIABCggIiLI4GBgBBBBAAAEEEEAg5gqYhMqzZ8+29yFNoo6iRYuqdu3a2rdvn8Oh7NixQ0uXLtUHH3zgcLEREAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggEDEBC76XdNPh9bYRjt9j2nSvmX6cvuvarpypDqt+1YNspfWN6XahrvTm4F3NPPwOv11/mC429xbMY6bhxZX6aWmOV5Vxfm91G3D91p04m9tOL9f4/csUfHZH+nA1dOK6xbr3mbPbX/u0Q3afOGggoKD7uvz1XmfqsqivveVPa+DetlKhqur1PGSaGzp9/RtqMONQL9wtXmaSsVT5tSJmxftl1fSTLoTFKg9V07arga+8o5qLh4on5mdlOvntpq8b7kyv5RSn/rUDRvq1bTe6lmonrpvmKJD185q/bl9+nrnAk17tbPSxEtq6wWFBOvDPyfqA6/qyp0kfVhbdqJeYOLEiUqRIoXef//9R+bnj/oI/43g008/VZEiRWTWIjP7hw8fVrx48f6t8Ig9UydTpkwy+bTGjh2rAwcOKGHChPryyy8fUduxikxuMjNfd3d3xwqMaBBAAAEEEIhhApkzZ1bZsmUf+1W4cOFwqTypH3PePDfbsGGDZs2apfXr1+vKlSu6ceOGVq1apYEDBypLlixycXEJ13hUQgABBBBAAIFnE9i7d6/eeustFSxYUGfPntWiRYu0Zs0alSpV6tk6dsDW+fPn1/z58+39IQ8PD5UuXVoVK1bUpk2bHDBaQkIAAQScV2D58uX2d22PHj2cdxJE7jACXl5e+vHHH2Wes5QpU0bt2rWz9w2++OIL3bx502HidPZAEiVKpIULF6pWrVqqWrWqJkyY4OxTIn4EEEAAAQQQQAABBBBwYAFz7+D48eNq2rSpA0dJaBEVCAwMtK/5ly9fPqJNqY8AAggggAACCDilgKtTRk3QCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg8ESBNm3a6Ny5c5o9e/YT61IBAQQQQAABBBBAAAEEYq5A8eLF9ffff8vf3z8Mwbypevfu3fL29g4rYwcBBBBAAAEEEEDAeQW6deumM2fOaNKkSc47CSJHAAEEEEAAAQQQcHqBYsWKafXq1fr111+1b98+5c2bV61bt7bPVR1lcsOHD1fu3LltomdHiYk4EEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBJ5e4EbA7Uc2nn7oDwUGB+nVdN6K5er+yDr/VRgUEqyQ0P8iun3oXUM+KbLpi21ztPnioYeaX/G/qY/XT1Yc91gPnXteBSZy83XvdivwjvyCAu4tei77JVPlVk+feuHqa0CRRpp/bJOu/cfjFa5OwlGpXFovLT+13dYslzZf2H7+ZJllfiZ2XT5uz13yu64Bm2coOPSxfuVlz7CeP/Cqpu2Xjmq779Gwsp9D28X3iKNGnmXDyoJDQvT1rgUaUaJlWBk7US9w6tQp+fr6auzYsSpRooTSpk2rTz75RDt27Ij64B6IYNeuXXJzc7OlLi4uSpMmzQM1Hj4MCAhQ3bp1w04kSJBANWrU0EsvvRRW5sg7rq6uMl9sCCCAAAIIOIvA4sWLtWfPHmcJN1xxmpycderUeexXhQoVwtXXk/ox583zsQULFih79uyqV6+ekiZNqly5cumHH35Q1apVVbNmzXCN5WyVNm3apLVr1zpb2MSLAAIIIBBNBY4cOaImTZrYnOHmuc3MmTNl/lZVqlQpms7432mZvOnLli2zX9evX1eRIkXsvRRHvFf0b9TsIYAAAs4j0K9fP5UrV07m9y0bAs9LIFu2bBo/frwOHz6st956Sz179lTGjBllft6uXLnyvIaJ0f14eHjY9Q7Na2gtW7ZUjx49YrQHk0cAAQQQQAABBBBAAIHIE5g4caKKFi2qnDlzRt4g9PzCBTZu3Khr166pfPnyL3xsBkQAAQQQQAABBKJCgE9ARIU6YyKAAAIIIIAAAggggAACCCCAAAIIIIAAAggg8AIEMmTIoCpVqujrr79+AaMxBAIIIIAAAggggAACCDirQPHixeXn56fNmzeHTcEkcLlz547y588fVsYOAggggAACCCCAgPMKmPvFTZs21YABA2QWAmBDAAEEEEAAAQQQQCAqBcwCDtu2bbNJGRctWiSToLF79+66evVqVIalc+fO2QUmOnbsKLPQFhsCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBA9BWI7xFHbi6u8nB1U3BIyH0TTRU3id7OXkYf5a+p0qnz3HfuwYOUcROrRa4KejfP68qZOJ09XTJVbntsytLFT2bLTJ/tjTFJMAAAQABJREFU81WVX6C/Ru9a9GA3YccrTu/QNw+cL/Jydv0vVS6liJNIHUL7KJg8q60fx81D5dN5q7P3m+roVU2p4yUJ6+fuTuJY8dUkRzn19KmrqhkLyyX0v5AH5ps8zkt2vnfb3P1eJk1e23fznK8pSewEd4vt97Txk6pN7kq2v1yh8zYx1M36P3tsKhiDH177UAlCnZvkeFWV0he8r/29B2Y+FdIX0NwjG+4ttvsJ3OOoRuai6lqglhp5lpEZ997NPIbl0nqpaMoc1sfMtVeh+vJJke3eaqqfrZRahj5OxiChR1y7XytLCcV1j2X37wQFaMbhtfe1OXf7irZePKIr/jdtedLYCVU8VU7tunzivnqm7ZFr52yc955YeXqnnb8Zk81xBNzd3cPys54+fVpDhw6Vl5eXcuTIYXO3HjlyJEqDXbVqlUaPHq0TJ05o48aNGjt2rH799dewmG7cuKGpU6fq008/1fTp0+/L32XmcO8WHBysQ4cOqVOnTvcWh3vfjDVmzBh169ZN3333nXbu3KmgoKD72q9bt04rV660ebyGDBliYzYVbt++LZNnrH///ho0aJBOnTp1Xztz4Ovrq3HjxumTTz7RrFmz7O8mcoA9xEQBAggggICDCnTt2lVLlixR7ty5lTdvXvXq1Uu7du1y0GgdOyzjZ55rHD161K6hZJ4HmedApjy6bOYabMOGDercubMyZcqkIkWK2Odp77//fnSZIvNAAAEEEHBCAXOt/u6779p7ImvXrtXkyZNt7vAaNWo44WyeLeRy5crJ3OOYN2+efU7i7e2tBg0a6MCBA8/WMa0RQACBGCxgfq+uWLHC3suOwQxMPRIF0qZNqy+++ELHjh1T27ZtNWzYMGXMmNG+pnH+/PlIHDnmdN27d297z2bw4MFq1KiR/P39Y87kmSkCCCCAAAIIIIAAAghEuoBZt9K8Z8ysuc4WvQSWLl2qdOnSydPTM3pNjNkggAACCCCAAAL/IeD6H+UUI4AAAggggAACCCCAAAIIIIAAAggggAACCCCAQDQQMB9YMAmhdu/eHQ1mwxQQQAABBBBAAAEEEEAgMgSyZ8+u5MmT28Qld/vftm2bYsWKpZw5c94t4jsCCCCAAAIIIICAkwuYRPomeZ9J2MeGAAIIIIAAAggggEBUC7i6uqpJkybav3+/TNLAb775RlmzZtXw4cN1586dKAnPLLaVMGFCm7gwSgJgUAQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIEXIuAiF3XJX0MuLi768cAaBYYEhY1bMlVudS1YS9svHdX+K6c0rfyHGlqsadj5B3fO3b6ii7evauAr76jwy9ns6TVndyuhR1xblj1RGltWIHkWxXJz14mbF+UfHPhgN/cd7/A9Zo/Tx0+u6a99pCVV+qhKxsL6skQLfVyglj7wqqb47rG1ufaX8gv01/Dtc+Xu4qbFVXorjptHWF/ZXkqtWRW7aZfvCfXfPEPJ4iRU5YyFFPL/NVxD598gWyltqfOlehaqG9bOw9VNI0q0VNLYCbX4xGaVTJ1bf9UaphyJ09o6ldIX1KpqAzWoaGO1yVNJ7+WtrMIpsmts6ffUMTQ2s13xvxk67nH5BwXq4NXTOnXzki1/1P86eFXVpvMHdCPQ777TeZNmsHMKDA7S+D1LlChWfG2oOUz1spW09dLES6qJZTvYObbPV0WjSrZS3qQZ7fnFlXupWsYiYf0du35e50Mfp9Txk+qXw+t0+qav8iRNr58OrtGeyyd0NPT8o7a08ZPp95Nb7alMCV+Wq4urzt26/FDVC37XlOWlVA+Vbzi3X529azxUToHjCAQEBNhgTE6szz77TFmyZFHhwoX11Vdf6dy5cy880IwZMyp37twKCQlRypQp5ePjI09PTxvH3r17VbduXXl5edlY58yZY/N3HT58+KE4TR7aRo0aqVixYipRosRD559UcPnyZTt23rx51aNHD82fP1/58uWz/X3wwQc6duyYKleubPuePXu2WrdubXOKDRo0SDdu3JBZAyFu3Ljq2rWrAgMDbb3bt2+HDbtv3z5VqlTJ9tmnTx9dvHhRZj7m9zIbAggggAACziDQt29fm/fdrBVatmxZjR8/XubvZq5cudSzZ0/t2LHDGabhcDGa9ZKiy2aez61bt06dOnWSeY5XtGhRzZs3T/Xr19fff/+tQ4cOqU2bNtFluswDAQQQQMCJBC5cuKDOnTsrW7ZsWrhwoUxu7j179qhhw4YyucNj8lalShVt3rxZP//8s7Zu3Wqf2zVv3tzeB4nJLswdAQQQeBoBc91s7k2XKVPmaZrTBoFwCyRLlkzmdQbzukW3bt303XffKVOmTGrfvr1OnDgR7n6o+GiBpk2basGCBZo7d659Xefq1auPrkgpAggggAACCCCAAAIIIBBBgR9//NG+V6xevXoRbEl1RxdYtmyZypcv7+hhEh8CCCCAAAIIIPDcBNyfW090hAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAg4nUKFCBfthTPNBzFGjRjlcfASEAAIIIIAAAggggAACjiFQvHjxsIRjJqJt27bZxLIeHv8miXaMSIkCAQQQQAABBBBA4GkFMmTIIJOIpX///mrcuLF4rve0krRDAAEEEEAAAQQQeJ4CceLE0YcffqgWLVpo8ODB6t69u0aMGKFevXrZRavc3Nye53D/2Zefn5/GjBmjd999VyYmNgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIHoJZAuQXK9n7eyUsdLqpKp8+iS3zU1Wf6l5h/7K2yi8d1j66v/tVLxOR/rVuAdbfc9qnJpvdUiVwX9dHCN/rpwMKzuvTt7r5y699Dub7909L6ynEnS2eNj18/fV373oHRoTFUzFZGHq7stOnvrskbumKeP109WhfQFVDRlTpWb111JYidQSIj0RoZCShUvsfZdOa3g0ILfTvytHj5vKVeS9Npy8bDt45tS72rN2d3adOGAPZ60b7k65qt2d0jb7oeDq1Upg09o/55h5a1zV9KZW76adeRPW/bJhinaXe9rDSjSSLWWDAoda7Om7F+hD7yra7fvcY3ZtcjWW1ltgKqHzmH49rna4XtMF0ON08VPrj/O7gnr+1E7eZNk0Mbz/8R497yHq5u+K9Nes4+s17xjm2zxqJ0L5J0ss0aWaGXnuC/UveemaaoWOqZ/UKCarBhh6w3eMkt/1hiigUXf0YLjfykoJFjrzu1VzczFtOH8fi0/tV3l03lrl+8J/X5y690hH/pePNQ8MCRIo3cutOdejpvIfr8d5P9Q3duhPy+x3NyVNHZC+d65HnZ+z5WTetuzTOjj6qaA4KCw8iftXNlwUDNC/2N7vgI7d+58bIeBgYH2/N9//60tW7aoQ4cOKlKkiC0LMf/wXsCWKVMmJU2a1I6UNm1aFSpUyO4HBQWpfv36atu2rby8vGyZyeE1c+ZM7d69W1myZAmLbunSpWrXrp327dtny06dOqWpU6eGnQ/Pzueff647d+6oZMmStnqPHj00e/ZsNWjQQB07drRlJmfYwoUL9ccff2jjxo3y9fWVi4uL5s6dqzNnzihXrlwyucSqVq2qTz/9VMa/cOHCtq3Jj1umTBkVK1bMHrds2dLmIrMH/A8BBBBAAAEnEXB1dVWpUqXs18iRI7V27Vr98ssvmjhxovr27StPT0/VqVNHtWvXVv78+Z1kVoT5LALBwaHXHuvWacaMGfb5l3l+ZJ6nvfPOO/Znwdvb+1m6py0CCCCAAALPJHDlyhUNHTrU5gBPkCCBhgwZolatWil27NjP1G90a2zubZjncDVr1tS0adPUu3dv+3fdWH3yySdKnTp1dJsy80EAAQSeu4C5x/7bb79p0aJ/Xj967gPQIQKPEHjppZfUtWtX+9rOhAkTZF7n+Oabb+y6J6Y8e/bsj2hFUXgEKlSooDVr1qhy5coqUaKEfW3IrInIhgACCCCAAAIIIIAAAgg8i4B5Xb1WrVoy13Ns0Ufg5s2b+vPPP+3rD9FnVswEAQQQQAABBBB4vMA/nwJ7fB3OIoAAAggggAACCCCAAAIIIIAAAggggAACCCCAgJMKmA8cvvvuu+rVq5cGDRok8+FMNgQQQAABBBBAAAEEEEDgQYHixYvbhC53y7dt2yYSjt3V4DsCCCCAAAIIIBB9BEwiOvPhyO+//17NmzePPhNjJggggAACCCCAAAJOL5A4cWINHDjQLlZlkimbRaDM8WeffaZ69erJLKoRmZtZFOvq1at2Ua3IHIe+EUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBKJG4E6Qvw5dO6vO3m/KzdVNLVZ9pX1XTt0XTO0sJRTHPZb6FG4QVp4yXiIduXZOWV5Kpb8uHAwrj+iOf1CAbeLq8uh8OqvO7NKR6+e0/a2vFBAcqGw/tNbNwDs6e+uybbfkxBYFh4Tokt91e/zL4XXadumILvhdVWw3D5VIlduWZw2Nc8vFwyqVOo8KvZxdg7bOtOV3/7c59Fy+ZBnvHtrvd2O7W/he3jdsH0OLNb1bpANXTytJ7H/XProd6mm2/aHld7d9V07q1bTedw/t9xCF3Hf84IFH6GORKWFKzTu26b5T5dPml2fitNp04cB95ctObVOdrCXUyLOsemycqluhRmbbfumo/W7+Z0wm719uH+uMCV/W4dDH3Wzl0npp5akddr9MmnxaefqffVvwwP9cQ9d9+qRgHdX/fah9HMzpmwF+tlbow/DQ5hb6uN4JfYyv+N+479w1/1tyD52j+fl58OftvooPHBwfuVhvBSx8oJTD5yHg4eHxxG5CQh/koKAgW2/Dhg32u6+v7xPbRWaFhQsXauvWrapcuXLYMAULFtT169cVK1assDKzU758ee3du1dHjx5VjRo1NG3aNNWvX/++tvc1eMTBoUOHdOHCBfn7+9v+zdoF8ePH14kTJ8Jqp0mTxu6bmNzc3JQiRQp7bMYysaVMmVJ+fn5atWqVLT9w4IAKFy6s5cuXy7iaPGN3N7PWmjln5siGAAIIIICAMwh0795dmzdvtn/vkidPbv8Omu9ly5ZV7dq1derUKfs30OSE79+/v7Jmzao6derYcz4+Ps4wRWIMp0BwcLDWrFmjX375RTNnztSZM2eUK1cuZc+eXVu2bNHhw4ft87HT/8feXcBVeT18AP/R3SIgbYJMlImFBfacHVP/dndNZ89EZ82YzppOp5tz6tyMmdjOLuwuQkVUkJJ+7znu3peLKKCixO/xc7nP6XO+z/We5z4XzhMaCl9fX/kacXR0zGLtzEYBClCAAhR4f4Ho6Gh5j8LZs2fLtb7HjRuHgQMHwtDQ8P0rz8c1iGsdnTp1ktdUVqxYAX9/f4jnAQMGYOTIkbCyssrHo+fQKEABCryfgHjP9Pb2RoMGDd6vIpamwDsIGBgYyHOdPn36YM2aNZg+fTpWrVqFr776CqNHj4anp+c71Moi4nui48ePo2HDhqhSpQp27NhBS74sKEABClCAAhSgAAUoQIF3Frh8+TJOnjwpP7O9cyUsmCsFDh06hMTERNSuXTtX9o+dogAFKEABClCAAjkhkPFfieVES6yTAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKECBTyLQtWtXJCUlyT9S+CQdYKMUoAAFKEABClCAAhSgQK4X8PHxkYuPiUVgxRYYGAjxx7ncKEABClCAAhSgAAXyl4CzszO6dOkiFxoW1425UYACFKAABShAAQpQILcJ2NvbY9myZfKmVZUrV5aLK4sFGMUNFMTNuHJqmzdvHv73v//B1tY2p5pgvRSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKPAJBZ7EvcD2B2fQ6+AiGGvr4/c6w2Gua6TWIzcLBzyKjcDwYytVjzZ7ZsFr4xCsv31ELW92A4FP78oiRU3fvM7Ng+hwpKSm4HbkI0QmxMr8KXi19k6yIj7tlqqID4uLxBiv1ujv0RDXI0JksqaGpnz+zNJZPl99HpS2mKLU29fyMdM1hJ2hJVZf368yEB4V/hyGWlvHqdWVPiD6qKGhHptZexZ6xtDS1ERcUoJawVIW9jIck/hSLf7Yo2syXMrsVbpaYprArciHMlRI3wSTK7THXJ/uaOhUHlVs3OR+y6JV4GnlIvcdjQqlKflq179CB/x4aTsuPLunSguOeSr3jbT1VHHKHWMdA4g2U9KtlaTsfxGFaXY2z9/6y3WXxNpLfHw4gylTpiheo+lepBkcGC0tLZmvatWq8Pf3lzmsrKwyyPnxosT9A4yMjGBtba3WqK6urlo4bcDFxQW//fabjDp+/HjapEz3/fz8EBsbiyNHXr33PX/+HAkJCahbt66qrKbi/67YhFfaTcTb2Nhg/PjxmDNnDtzd3WVySsqr9zExFrF99tln8ln5IyvHRpmXzxSgAAUoQIFPLXDhwgXs3LkTv/76KxYsWCDnvd69e6NZs2aoXr062rZti8WLFyMkJAQmJiYIDw/H3Llz4e3tDTs7O4wYMQKnTp361MNg++8okJycjH379qFfv34oUqQIfH19ceDAAYjXwOXLl3HlyhWI868XL15g//796Ny5M27fvo1evXrByckJxYsXR8+ePeW5Wmho6Dv2gsUoQAEKUIACbxd4+fKlPP8oWrQoZs6cicGDB+Pu3bsYOXIkDA0N316YqSoBHR0d9OnTB7du3ZL3+1m1ahVcXV0xceJEOderMnKHAhSgAAWkwMWLF7F582aMG/f275TIRYGcFhBzeLdu3eS9T9auXSs/q5crVw5NmjSRn9lzuv38WL+DgwMOHz6MUqVKoUaNGjh48GB+HCbHRAEKUIACFKAABShAAQp8BIGVK1dC/G6b+J6VW/4SCAgIgIeHB+8Jmr8OK0dDAQpQgAIUoEAmAq/+qiKTTEymAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClAg7wpYWFigXbt2WLRoUd4dBHtOAQpQgAIUoAAFKEABCuSogFhcTvxx89GjR/Ho0SOEhYWhbNmyOdomK6cABShAAQpQgAIU+DQCY8eORXBwMFavXv1pOsBWKUABClCAAhSgAAUokAWBYsWK4ZdffsGlS5fkzaFat26N8uXLY9u2bVkonb0su3fvljdnGDJkSPYKMjcFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQrkOYHdwecwO/AvFDW1xc9+g6CpoaEaQ3JqCkqY2UFbQ0sV96F2Ljy9h5jEl3AyLgRnY+s3VpuSmgrxL7NN1HG42XScCb+FORc2Iyj6iVoREx0DGfa2LqEWLwKpijbetIn2xVbawvFNWbIV/5amZD1hcZGIiI+BsY6+Wr0R8dEyXLFwSbX4B9HhSExJQkRCjFp8+oCjwlls96LC8MPFrVh/+4jiWGui/5El+OnqbljpmWLA4aWYdnYjgmOeqhXvUqoWLjy7hx1BZ9TiQxT5xDG0N7ZSixcBK30TXI8IeS3eXM9Ixomy3HK3gFirX2yenp6YOXOmXL/1yJEjaNOmTa7oeEpKCmJiYrB///5s9ad06dIoUqQIbG1ts1WuR48eGDZsGPr27YsNGzZg/Pjx+O6779CgQYNM67l79y68vLxQsWJFjBkzBs7OzmplXrx4IcMnTpxQixcBjTTvya8lMoICFKAABSiQiwTq1asHLS0tJCcnIyEhQT7EfvpNzOFRUVGIjIxEfHw8tLW14ePjg02bNsm50sXFBcOHD8fx48ffep6evl6GP75AUlISAgIC0Lt3b9jZ2aF27dryXk8DBgzAtWvXcPHiRUyYMAHi/Eu56enpwdfXFxMnTsTBgwfx/Plz7N27V95f9urVq+jatSvs7e3h7u6OQYMGYcuWLVCeKynr4DMFKEABClAguwKJiYlYsmQJihcvjnHjxsn55s6dO3I+MjU1zW51zP+fgL6+PoYOHQphOWrUKMyfPx+urq6YMWMGYmNj6UQBClCAAv8JTJ06FWXKlEGTJk1oQoFcIaCpqSm/6zl//jw2b96MJ0+eoEqVKvJz/b59+3JFH/NSJ8zMzLBjxw6Ia2P169eX17jyUv/ZVwpQgAIUoAAFKEABClDg0wuI711//fVXdOnShb8r9ukPxwfvgfg+vE6dOh+8XlZIAQpQgAIUoAAFcrOAZm7uHPtGAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKDAhxHo378/Ll26hEOHDn2YClkLBShAAQpQgAIUoAAFKJCvBAwMDOQirMeOHUNgYKAcW9myZfPVGDkYClCAAhSgAAUoQIFXAmLBffEHkmKhJfEHk9woQAEKUIACFKAABSiQmwXc3Nywbt06ed1SnMs2btwYlStXxp49ez5Yt+fMmYNatWqB10Q/GCkrogAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABSiQawQ0oPFaX6ad3Yj9IRdQy94Tk7z/p0q/9Ow+jHT00c2tjipO7JjpGqK7W121uLSBpJRkGdTT0k0brbb/IjEOXx9dAU0NTUyt1FEt7V0Co7xaQUdTC7uCzsniot6025XnD2Swhp1H2uhM96MU/bwXFYbu7nWhr6Wjlv+rYtXgYGSlFve2QGpqKrTS9Suj/NcigmFtYKaWdPrJLRn2sXVTiy9t4agYtzZOht1Qi08fEOM+H34HYXGRCH/5Ah6Wzjj88LIMf6bYF+WDYsLx5GUkUhX/lFsj5wqKXQ2su3VYGSWfq9q6IyElCWtu7EcF6xKKHP//ujLRMUAxU1v8dfeYWhkRsDE0h3C4H/3ktTRGfHoBHZ1Xr3FXV1eMGzcON27ckGteff311yhSpMin72CaHpQpU0aG1q5dmyYWePr0Kf766y+1uLSBJ0+eICIiAvXq1Usbnem+trY27Ozs8PPPP8PT0xNz587FsGHDMi0nMkycOBGJiYlo1KiRzJ+SkqJWTjmWffv2qcUzQAEKUIACFMhLArVr10Zy8qvPAVnpt6amJsqXLy/vKfrnn3/i1q1bOHv2LNq3b4+tW7eiSpUqcHJywtChQ/Hvv//Kc8is1Ms8OSsgzml27dqFnj17wtbWFnXr1sWpU6fkcbp58ybOnz8vzyNLlSqVpY6Ie0SJNVCnTJmCI0eOyPM0Ub9Yb1WEmzVrBisrK1StWhUTJkyQcaIP3ChAAQpQgALZEfDz88PgwYPRvHlz3L59GzNmzJDzS3bqYN43CxgbG2PMmDG4e/cu+vbtC39/f3h4ePD87c1kTKEABQqQwPXr17FhwwaMHTsWGhr//z1KASLgUHOxgHhNis/f4r7Ne/fulT0V13fE/U/EtRnxfR63rAno6enJ+8j06NEDrVu3xuLFi7NWkLkoQAEKUIACFKAABShAAQooBLZv346wsDB07tyZHvlMQPyu4oULF1CnjvrvYOezYXI4FKAABShAAQpQ4DUB9b/mei2ZERSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABCuQHgc8//xyVKlXCjz/+mB+GwzFQgAIUoAAFKEABClCAAjkgIBaSO3r0qFyYzN7enou95IAxq6QABShAAQpQgAK5RUAsRBcUFITVq1fnli6xHxSgAAUoQAEKUIACFHirgLhRlLi51enTp2FpaSlvYlWjRg0cPHjwreUyS7xy5Qp2794NcaMvbhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKJD/BMx0DeWgnIytVYNLRSq6H1iI4OinGFimEdoVryHTNt05JuP8K3bAoM8aoaRZETRzrYz5VXvij1uHVeVNFXUaaeupwrdePMT9qCdo6VoFjkaFUOK/ciJDWSsXaCj+ie2P20ew6NJ2NHKugHk+PWCgpSvjlT8s9IyhpaGJ5NQUZRQM/2vHSt9EFSd2DHX0YGtogboO5WCpZ4Ie7nVlup0iTox5+4MzuBERgjbFq8PHxk2m2RpYoKqtO4oYWcHDwkm2JRJ0tXRgqmOoCv9wcSvsFXm2fvEtqinye1q6YLRXK4hxB8c8lXWZ6BjIZ11Nbfksfog+irqU2+O4CNgYmsHFpLB8KMeiTFc+H3t0DaUtHJVB+Xzp2QOsvXkQPor2HRR9UW6VbUrhduRDrLq+Vxklnz0snVRhYfC5dTFMOLVWFVfLvgz2h16U4dr2nqp9VQbFjm+RzzCkTGPoaGqhp3s9+ehTuoE8VsJLbD8qjp+5nhGaulSUYfGjheK4b7t/ClsVj/SbeN3tC7mA+OTE9EkMfyKBhIQE2XLhwoUxePBgnD17Fnfu3MH48eNRokSJT9Qr9WZDQkJkxKNHj1QJTZo0gZeXF3755Rf06dMHe/fuxdy5c9GtWzc0bNhQ5tu5c6dcbzY2NlZVbsWKFZgxY0a2x7Z48WJs3LgRiYmJEGYPHjxAVFSUql6xExMTI8Ph4eGvxT98+BDbt2+HSFu0aJFMDw0NRUREBMRY3NzcsGbNGhw6dEiVJtYVCw4OxoULF5CUlKRWJwMUoAAFKECB3CZQunRpuT5mZv3S1taGeEydOhUnTpxAqVKlVEXE3C7ir1+/jsDAQHTp0gViPq9WrRocHBwwaNAgHD58GCkp///5QFWYOzkmIM59xHmMOM+ysbFBgwYN5P2cRowYgdu3b8vzx9GjR6N48eLv3QdDQ0O5xurMmTNlvWFhYfj1118hXl/ivK969eryddaoUSPMnz8f165de+82WQEFKEABCuR/ATFfTZ8+HQsWLICtrW3+H/AnGqG5uTn8/f2xcuVK3Lt3D8nJyZ+oJ2yWAhSgQO4RmDZtGkqWLIlWrVrlnk6xJxTIQKBWrVrye5bjx49DfF/UtGlTlCtXDuvWreN1mAy8MorS1NTEwoULMWnSJPTr1w8TJkzIKBvjKEABClCAAhSgAAUoQAEKvCYgrqn6+fnBxcXltTRG5G0B8TuNWlpaqFmzZt4eCHtPAQpQgAIUoAAFsimgmc38zE4BClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoEAeFRC/PP/XX38h7aJQeXQo7DYFKEABClCAAhSgAAUokAMCPj4+cjG5M2fOyD9czoEmWCUFKEABClCAAhSgQC4REH8g2blzZ7moMBfTzyUHhd2gAAUoQAEKUIACFMiSQPny5eVNGI4ePQpdXV34+vqibt26EAszvss2b948uQip8sZZ71IHy1CAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoEDuFGjsXAHflm8rO1fK3B6zKndBcVM7GX4WH4XO++chITkJP1bvjWkVO8JIRx8tdk3Dg+gnmFyxPU62/B4jyrXAnMDNiE56CT0tHfT1+AJVbNxgrmeM0V6tUEjfVNY3O3AT3C0ccazFLIxUlFl9fR9CYp6isIE5ipraqIDGnFyDBv9MlHlPtZyDVX6DMUHRxyU1+mF93RH4RVGu2/4fZP5iprayXyLQ3LUy+pRuAG0NLZm28OI/sp+/1v4a86v2wA8Xt+J8+B0M9WyCL528kZyagla7p+NGRAi2fzkB51vNwxTFmM4p8lx8eg8VC5eEsWK8vdzro5qtO/S1dRVWbeR4fr4WoBjz3/AqVBTbGo7Hvib+0NXUxoqre2TbVRX5ha3Yvi7bTDFGM7RwrSJdTHQMFONvCS0NTfx99zg0FP8ONJmGeg5eiE2Kl2XS/5iv6LudoQVcTAqrJQ09ugLrbh7Chnoj0a54DXQs6Yt6juXQZKc/ElOS1fLaKJx/qNoL4xVj+L3OcPQ++CMOPrws84i+VLMtjX0hF2TYz74M9odcVCtf1soFv9UeBu/CJTCrSlfVY3rlzmhbvDrW3z4i8wfFhOOLfyahh3s9TPRuh34eDSFeW8OO/axWnwjoaGrJY7Hg0rbX0hjxaQTq1KmDgQMH4uDBg/I+XrNmzYKXl9en6cwbWhVrao0dO1ambt26FatXr0ZkZCS0tLQgwmLdrWXLlsnnLVu2YNGiRdDT05P5g4KC0L9/f9jZ2aF3796YPHkyxD0IBgwY8IbW3hwt6rh48SL8/Pzg6ekp1+syNTWV7Yp7oN24cQNff/21rGD9+vWYP38+EhMTZXjYsGFwdnZGixYt0KtXL3zzzTcQa4hNnz4df//9N7S1tbFjxw64u7ujZs2aKFasmMzj7e0t75Mg1hnjmrlvPjZMoQAFKECBTycQHh4OMe+JebZ48eJ48eKFnKPf1CNNTU053507dw6jRo16a14x306ZMgVXr16Vc3DPnj2xd+9e1KhRA/b29nKOP3DgAJKT1c+D39Q247MnEB8fj23btsn1+21sbNCoUSNcuXIFY8aMwd27d3Hq1CmMGDECRYsWzV7F2cxdqFAhtGnTBj/99BPu3buHmzdvQpyzivO9SZMmydeTq6sr+vbti82bFZ9To6Oz2QKzU4ACFKBAQREQn725fRwBWn8cZ7ZCAQrkfgHx2Wnt2rXyc5T4PMyNAnlBoFKlShDftQQGBqJ06dLo0KED3NzcsGLFCtV3HnlhHJ+yj+PGjcPy5cvl/RDFNTNeu/qUR4NtU4ACFKAABShAAQpQIPcLhIWF4Z9//kHXrl1zf2fZw2wLBAQEoGLFijAxMcl2WRagAAUoQAEKUIACeVlAI1Wx5eUBsO8UoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpkTUAsSuDg4IBBgwbh22+/zVoh5qIABShAAQpQgAIUoAAFCoxAcHAwHB0d5WKs7du3l398W2AGz4FSgAIUoAAFKECBAiggFlwqVaoUli5dyj+aLIDHn0OmAAUoQAEKUIAC+UVA3PxC/A7EkSNH0KBBA7kvbnSVlU3cuENcE50zZ468cUJWyjAPBShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKfDqB2YF/Yeb5TUhITsrxTjgaFUKq4l9wzNMst6WnpQMdDS1EJ72EtuI5OTVF1vGmCkSeYma20NPUwfXIEMQnJ74pa4bxGtCAgbYuYpPiVek6mlpITElWhcWOlb4J4pISZD4jbT3EpMmvljFdQF8xHhcTG9yPCkNcckK61KwFTXUMkJKaKk3eVqJLqdrwsHDEN8dXvZZN1OFm4YDg6KcIjX2mll7YwAw32i3B5NPrsPjyDojw/egnanlyKmCpZ4IXCbFISlX3VrbXzKUSWherhvZ7v1dGZfnZxaQwzreen+X8zJhzArdu3UKJEiVw7tw5lCtXLucaykbNERERSElJgaWl5WulRPyTJ09QuHBhaGhovJae1Yg9e/YgJCQE1apVw6NHjxAbG4uYmBhs3LgRZcqUwahRo95alehHXFwcjIyMZL5UxftAYmIidHV11cqJvhoaGsp80dHRMDY2VkvP6cCqVaswYMAAiLa5UYACFKAABdILiLnv8OHD2Lt3LwICAhAYGAhNTU1UqFABtWvXRlJSEmbNmiXn5bRltbS0ZFCslzl27Fhoa2unTc7W/tWrV+X8u2HDBly8eFHO8S1atECrVq3g6+sLZVvZqpSZpcDLly+xa9cuCNutW7ciKioKlStXRuvWraWvWLM0N23Jyck4ceIEdu7ciR07duDMmTPytSXO18SarF988YU8T8tNfWZf3ixg90vnd/6c/eZamUKB3C2gq7jO1M/jC0z0bpe7O5oPemdnZ4cxY8Zg4MCB+WA0uX8If//9N5o3by6ve7zPeV/uH+nH7+GD6HB4rufr+OPLs8XsCpS3Lo69jadkt1i+y9+7d2/52fn69evv9Tk438FwQHlK4ObNm5gxYwZWr14NW1tbjBw5Et27d4e+vn6eGsen6OyWLVvQtm1b1K9fH7///jvNPsVBYJsUoAAFKEABClCAAhTIAwLinpGTJk2Sv49mYGCQB3rMLmZHwNXVFZ06dZLHODvlmJcCFKAABShAAQrkdYF3/w3RvD5y9p8CFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoUMAE9PT00K1bNyxduhSjR4/mH9AUsOPP4VKAAhSgAAUoQAEKUCAzAQcHB4hHUFAQypYtm1l2plOAAhSgAAUoQAEK5HEB5R/UTZ06FR07duQ14zx+PNl9ClCAAhSgAAUoUFAFxM0uxM04du/ejcmTJ6Nq1aqoVasWxE02RNrbtsWLF8sbTnXu3Plt2ZhGAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACBVAgKCY826OOT05EPBJluaTU5EzLizzXI0IyzfemDKlIRWxSvFpyYsrr7T59GaXKE5Muvyohg52XivFciwjOICXrUS8S47KU+Zfr+7DcdwA8LV1w4dk9tTKijpNhN9XiMgrEJSfgfvSTjJJyJO5Z/P+7pm+ghFkRtC5WDd0P/JA+iWEKvLeAubn5G+vQ1NSEjY3NG9P79ev3xjRlQrVq1fDNN9/gwYMH0NLSQvHixZVJ8PPzw/r161XhN+2IfhgZGamSNTQ0oKurqword6ytrZW7MDY2Vu1zhwIUoAAFKPApBJKSknDy5Ens3bsXAQEBOH78OBISElC6dGnUqVMHkyZNkmtdmpqayu4FBwdjxowZal0Vc2eJEiWwdu1aeHl5qaW9S8Dd3V2usSnW2bxx4wY2bNiAjRs3YsmSJShUqBCaN2+O1q1byzlaW1v7XZooUGXi4uKwY8cOabht2zbExMTAx8cHU6ZMQcuWLWFvb59rPcRrS/RVPMQarE+ePJHrsYrxzJ49GyNHjpT9b9CgARo3bixfs2nPx3LtwNixTAUaO1eArpaOKl+Q4nPvtefBqOuo/h4TmRCDgOBAmc/O0AI+tu6qMmee3MK9qDBVOKOd/h4NIa4DrLi2J6PkDOMMtHRRs8hnqFi4BCaf+SPDPB8ysqTis3Z9xbgvPnuAA6EXVVXraGqhbfHqKG3hhJCYpzj2+Doi4qNhqWeCU08yv56gqigHd7Q0NFHeuliWrm+8azd8i5TB1edBeBwXgaqK439fccyDFR5iM9M1RMeSfnAwssKuoHM4+PASUlJT39hUQ6fy2BtyAeJal3Ira+UCcY1JWacyns8FV+DFixf49ddfceHCBZiZmcm5yNLSMlOQiIgIrFixQl53+PLLL1G7dm15/SHTgsxAgSwKlLF0RkMnbxjp6ON8+B35nlfbvizW3z6SxRryZjYXk8IYXrY5pp3dgNDYZx9tEOWsiiq+5wiGuD6fE9vb5rf07TVzrYwHUU9wNvx2+iRV+DNLJ/jYuCMhJQm7FXOitYEp5zeVzrvviM/Hq1atwsKFC3kftHdnZMlcICCu6SxfvhwTJkyQ13yGDx8Of39/iOc+ffqofe+RC7qbq7rQpEkT7NmzR16TqFevHrZu3SrPEXNVJ9kZClCAAhSgAAUoQAEKUOCTC6xcuRJt27aFgYHBJ+8LO/BhBe7duwfxEPcR5UYBClCAAhSgAAUKmoBmQRswx0sBClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUKMgC4o8LHj58iC1bthRkBo6dAhSgAAUoQAEKUIACFHiDgFg0LiUlBZ6enm/IwWgKUIACFKAABShAgfwkMHbsWNy/f18uyJefxsWxUIACFKAABShAAQoUPAGxgOCRI0ewb98+pCoWLhc3p6pevTp27dqVIYa4gceiRYvQu3dvGBoaZpiHkRSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABCnwcgVSkou+hxejmXgdehYpmuVFDbT2Z10zPKMtlcjqjo1EhfO3ZFP0PL8HL5MScbo71UyBbAmKNrswejx49kvc5W758OW7fvo2kpCTcunULa9euxfTp09GmTZtstcnMFKAABShAgdwscOnSJcyfPx+NGzeGpaUlqlatip9++glFixbFihUrEBoaisuXL8s8TZo0gampqWo4Dg4OcHFxkWEtLS1oaGhg5MiRCAwMhJeXlyrfh9opWbIkxNry586dw82bNzFs2DCcPXsWYk1OGxsbdO/eHTt37kRiIs9B05rHxsZi48aN8hzG2toarVu3lsd12rRpCA4OxuHDhzFo0CDY29unLZbr98VY2rdvL+8z8PjxY5w8eVKusypery1atEChQoXQsGFDLF68GEFBQbl+POzgmwUex0VgtFcrrPAdiFr2njgXfgcvEuNw9sltfFu+jSr+8MMrqkoexj5HZEKMTLMztEBw9FNV2pt2OpT0Rbvi1d+UnGF8bYeymFmlC9pks1yGlWUS6WJSGF3d6mBKxQ6wN7JU5TbQ0sW+xlPRzKUydj44g2cvozDBuy3OtJqLioVLqPJ9yh1THQMMKtMYV57l3P9FPS0drKszHLqa2nKoq2sNgYmiXbGZ6xrhQJNp+MzSGe4Wjviz/ijsaTRZpqX/Uc/BS5F3KtYq6hK2abdLzx7g67JN4WPjljaa+zkocOzYMQwePFjOVWLd7dy2devWDR4eHpg0aRLWrFmDefPmZdrFZ8+ewdvbW54vifOwL774Aj4+PpmWY4a8L3Dq1CkMHDgQBw8elPfKzKkRtVXMSdu/nIDn8dHY8eA0ylsXw8kWs/G9T7ecajLX1FvWyhViPi9t6fjR+tTA8XNY6ZsgLjkhR9p82/yWvsFyVkXxU83+EA4ZbZZ6Jvihai9MKN8O2xWvjVXX9yI09hk4v2Wklf24mTNnys+lnTt3zn5hlqBALhRwdHTEwoULcffuXXTo0AETJ06Es7Mz/P39ERkZmQt7nDu6JK6riess4rsl8V1UWFhY7ugYe0EBClCAAhSgAAUoQAEK5AqB06dPQ1wX7tq1a67oDzvxYQUOHDgAfX19VK5c+cNWzNooQAEKUIACFKBAHhDQzAN9ZBcpQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhT4QAKurq7yDyN//PHHD1Qjq6EABShAAQpQgAIUoAAF8pOAWPhLbMWLF89Pw+JYKEABClCAAhSgAAXeICCuGXfq1EkuSpOcnPyGXIymAAUoQAEKUIACFKBA3hEQiwju27cP//77L4yNjdGgQQNUrFgRW7duVRuEuIHV06dPMWDAALV4BihAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFPg0AgkpSRjy73I8iYvMUgecjAthtFcrmbepc0W0L1ETOppaWSqbk5nEOPoeXoyIhJicbIZ1f2SB2NjYj9xizjTXunVrZPYYOnQoZs+ejXXr1sHDwwPm5ubo2LEjoqOjMXnyZJiZmeVM5z5BrfnluH4COjZJAQpQIM8KPHjwACtXrkT79u1ha2uLMmXKYNKkSdDV1cX06dNx/fp1KPN06NABdnZ2bx1rw4YNZbqLiwuOHz+OqVOnyrreWugDJIp7C40aNQqnT5/GnTt35P7FixflvUptbGzQtWtX/PPPP0hISPgAreW9KsR5yx9//IFWrVrB2toabdu2RXh4OGbNmoXQ0FAcOHBArkma2fHNKyPX0NBAhQoV8O233+LYsWN4+PAhFi1aBAMDA4wYMQJOTk4oV66cTD958iRSU1PzytDYT4XAybCbGH/qN2lhpK2HxJRX95W4G/UYk07/LuN1NbURn5yo5nX9eQjOPLmFhZf+QVJq5veiqL31WzTaMUWtjswC2+6fwtFH1zLLJtOt9E1Q275slvJmlOleVBhWXguQSUn/GYhAH48v4GHpiIFHluHgw8tYe+sQmuzwx6rre2FraJFRVR81zk7Rh6U1+2PF1d2ITnqZY2372LghKCZcPjwtXRSvhyRcjQiW7TV3rQK/LWPR59AiNN05Fd+d24jy1sVRqXBJtf44GFnhyvMHuBX5UC1eGUhOTcHwYysx1LMpSls4KqP5nIMCu3fvxg8//IAaNWrIcxLxnn7+/PkcbDHrVYv5ZPPmzbJv4tzjwoULGDt2bKYVrF+/HqLs6tWrsXfvXkycOFGGxZri3PK3gDjeCxcuhK+vrzwPHz58OM6cOfNBBy3mw1lVuuLPO0ex7OouHHt8HWNP/oov/pmEFMX5j5hH8/O2+d4JFP2tFwKCAz/KMPt7NIS9Yu7YG5Jz7b1tfks7SEPFsR39eUvF9wPaaaNV++K7hFMtZ0NPSxut98xAcMxTVRrnNxXFO+88fvwYP/30k/zsIT5bc6NAfhIQ147EtYR79+6hb9+++P777+Hs7Ixx48bJ6wz5aawfaizieyVxbvfixQtUr15dXmf7UHWzHgpQgAIUoAAFKEABClAgbwv8/PPPcHd3R+XKlfP2QNj7DAXEd/Di2Orp5e/rsBkOnpEUoAAFKEABChR4gYy/pSzwLASgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABCuRfgf79+0MsNHHt2jW4ubnl34FyZBSgAAUoQAEKUIACFKDAOwvcvHlT/gL9O1fAghSgAAUoQAEKUIACeUZALMAnFtb79ddf0blz5zzTb3aUAhSgAAUoQAEKUIACbxPw8fHBjh075I0wpkyZgqZNm6Js2bJyAeqWLVti7ty5aNOmDYoUKfK2aphGAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKDARxYIjnmapRYfxj7HiOOr5ENZIDElWbn7yZ4fx0V8srbZ8IcXsLS0hJWVFWrVqoUWLVqgV69e8PX1/fAN5aIaNTQ08PXXX8tHYmIidHR0clHv3r8rYkxbt27F0qVLsWfPHrlG2fvXyhooQAEKUCC3Cjx79gz79+/H3r17ERAQAHFPHn19fVSrVg1DhgxBnTp18Pnnn0NTU/OdhtCvXz84ODhg6NChst53quQ9C7m6uuKbb76Rj/v372Pjxo3y0bhxY5iamqJmzZrQ09N7z1byTvGoqCgcOHAASUlJ8rxNrEHavHlzWFtb551BvGdPCxcujK5du8pHfHy89BDnP2vWrIG/vz9sbGzQpEkT6VK7dm3o6uq+Z4ssntMCOx6cRYjis/IXTuVhrmuEiIQY2eS2+6fw7GUUGjlXgJmuISITYlVdaejsjXW3DqvCme3EJsVnliXD9OTUlAzj00ZqKj5jrKg5EJvvnUwbne39lNRUWUb5LAKels7Q1NCEia4B8P/Dx8RTv2NqpY7ZbuNDF5hWsSPEcXqRGPehq1arr5a9J/aFXJBxtezLqPZ1NLUU+4Gq14zIsO7mYYz9/CtEpeuT8nrMg+gnanWnDQj7Hy//g/lVe6LutvFpk7ifQwLiPTohIQGPHz/GvHnzMGvWLBQtWhRdunRBu3btULx48Rxq+e3VXr58WZ4/iWsIYitUqNDbCyhSxTjq168Pca1FuXXq1Anjx4+X5yzKOD7nXwHl6/nJkyf44Ycf8P3338PFxUX1ei5ZsuR7Dd7FpDBMdAzkXJm2ohuRoVh1fS9sDS1w+8WjtEn5bv9ZfNRHGZO7uQN6utdDuY1DcrS9N81v6Rud4N0Ws8//jfqOn6dPgpgLV/kNwfP4GAw9uuK1dBHB+S1DlixHzp49G+bm5ujRo0eWyzAjBfKagPieSNz3RFx/WbhwobzfiTg369OnD4YNGwY7O7u8NqQc7a+Y348cOYJ69erJa3Diu5hSpUrlaJusnAIUoAAFKEABClCAAhTI3QIvX77E77//jjFjxuTujrJ37ywgvqPv1q3bO5dnQQpQgAIUoAAFKJCXBd7tt0/z8ojZdwpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKBAARcQfygp/tBz0aJFBVyCw6cABShAAQpQgAIUoAAF0guEhoZCS0sLR48eTZ/EMAUoQAEKUIACFKBAPhUQ14s7duwoF3tNTv70N8nIp8wcFgUoQAEKUIACFKDAJxLw9vbG5s2bce7cOZQoUQJt2rSRiwlfuHABgwYN+kS9YrMUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQq8r0BiSjIiE2LVHu9bJ8tTIL2ApaUl7t+/jx9//BG3bt2Cn58f3NzcMHfuXDx9+jR99nwX1tHRyTdjunv3LsaOHQsnJye0bt1a3pdh06ZNOH36dL4ZIwdCAQpQgAJAXFwc9uzZg1GjRkGsSWltbS3Xojxz5gxatWqFgIAAPH/+XC2PpqbmO9N5eHhg9OjR0NfXf+c6PmRBZ2dnDBs2DMeOHZPnMBMnToSBgcGHbCLX1yXO3xYsWIBHjx7J49yrVy/5Osj1Hc+hDurp6UHcw3bhwoW4d+8elGuyirVaGzZsKG3+97//YePGjYiJicmhXrDa9xVIRSp+u3EQelo6+KpYNVV14nPx/egwGGjromVRH1W82GmlCG+6e0wVZ2tggQ4lfDGiXAvUtPNQxSt3CumbynRlWPlcsXAJDC/bHN+Ua45a9p6w0DNWJr32XN66uKy/ddGqqjRdTW2s9BsMX/sy8LF1Q5dStWFjYK5K9y3yGYaVbYbubnUzrNvHxk3WOaRMYzgYF5LlhIdy2xd6Qe4uqdEXRQwtldGISIjBj5f+kWHRXg/3eujr8QXczB1kXHXb0jIs4hyMrFTl9BXGLVyrwEBLF06K9kS/vnTyhqaGhsxjrW+GziVroWNJX5jovP399fNCxVDP0Qub755Q1a/cMdbWR3PXyhjl1VLWZW/0/30XebQ0NKV3ZZtSEG12KVULE73bQRin3doVr4GeirE1dq4g+yP2Wyr8xWtC7Ivjej/6Sdoi8LB0ws4HZ3HleZBafFYDB0IvwVhHX7aZ1TLM92EEEhMTZUV37tzB5MmT5ZrbXl5emDdvHh4+fPhhGsmklujoaCxdulTOGykpKXJfhMX9D5Wb+Jw9Z84cTJs2DefPn1dGQz56QJ4AAEAASURBVFdXF66urqqw2BHzUqNGjVCmTBm1+LcFxPmeqH/GjBmYP38+wsPDceTIERkWceIaTlRUlKzizz//lPGiHW65S0D5ehbnJ1OnTkWpUqXg6emJ77//HiEhIe/U2ZuRD/FA8Z7XSPGeKN4D026LLm3H8/hX5zoNHD+Xc0Cnkn4yi3hPVs4T4r057VbSrAjEnKGh+FfPwQv9PRpC+Z4t4qoo3qcHK+Yo73Tvz+8zn4iydRzKyvlxiGcT2BlapO0SxNxczdZdzg+ibTHfiE30R/TVq1BRVf42ivOG/ynmivSPclbq/xczm49VFf63M6nC/7Dhzr/po2W4rJUL+pRugKGeTeVclj6TmK/F/Co2MY4J5dvK4yXGrdwym9/SmojjfUtx7K9FBCuLqz1/q6j/c+timH9xC2KT4tXS0gY4v6XVyPq+uEa+ZMkS+Tk0t3wuznrvmZMC2RcwNTXFmDFj5OdrcT62du1aiHsA9u/fHw8ePMh+hfm4hK2tLQ4ePAgHBwdUr14dZ8+ezcej5dAoQAEKUIACFKAABShAgcwE/v77b4jry+Ie6tzyn4D4vTzxO5a+vr75b3AcEQUoQAEKUIACFMiCgHYW8jALBShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIAC+UhALFDRp08f+Ydx3333HYyMjPLR6DgUClCAAhSgAAUoQAEKUOB9BC5duiQXVzl69Ci6d+/+PlWxLAUoQAEKUIACFKBAHhIYN26cXEjtt99+Q6dOnfJQz9lVClCAAhSgAAUoQAEKZE2gbNmyWL9+Pa5evYo6derIQu3bt5c36ejQoQPy082tsibCXBSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAlkREPf4Emv3i8f58+exdOlSTJw4Ua5j1apVK/Tq1Qs1atTISlXM85EFkpKSsHXrVixbtgw7d+6EsbExmjRpArEer7u7+0fuDZujAAUoQIGcEEhOTsaZM2cQEBAgH+KeO/Hx8XLddbH+5NixY+Hn5wdzc/OcaD5X1+no6IghQ4bk6j6ycx9foEyZMhCPMWPGICgoCH/99Rc2bdqEtm3byvVZ69Wrh+bNm8tzJktLy4/fQbb4RoG1tw5ihFcLdCzph2VXd8l8JcyKQFdTR+53UsT/fC1A7pcyt8fz+Gg8fRklw9VtS6NlMR/8fDUA0Ylx+K3OcKy7dQjDj62EpoYG2harjhlVuiAuKR6/3jwgy4gfvdzro5a9Jzrtm4sKhUvgr/pjEJv0Emee3MbkM+sQ+PSezKuloYlZlbtAT0sXVvomGPN5aziZWOP7wL+hr6WDvcGBaOpSCaGxz3ArMhQvkxOgo6mF2VW64WDoJewKOovhZZsryrVCw+2TcT0iRNb7bfk2KKRvitEnVsNKzwTLavaX8any56sfG28fxYhyLeFVqBgONf0OY0+uwR+3j8jEK8+D5PPjuAiEx0ViVa0hGHhkKa5FBOPwoyuoYusm+3rteTCCY56iqq07fqjaE8XM7DD2xBoI38jEWEyp2B57gs/LcVSzKw0x3hauVdDQyRvtAman6Y367mDPxjgVdhPRCrO022eWTlhaoz+mn9uIn67uRrviNXCixfeK4/Gz4rgcRhFDS0yv3BlNXCpi+4PTsr2g6HA0cq6AAZ99iW77f8CW+ydllfejwmBtYAY7I0tsvHMUBopj4GHpKI+POJ4R8TFpm0Yz18oYpfBqses7tfjsBk48voFhimO29f6p7BZl/g8kID7vii0wMBDffPMNvv76a1SrVk3ecyYlJeUDtfJ6Nbq6uihfvjwOHjwIcR4m9sVmYmIin7/99ltoa2tjxIgRuHHjBry9vTFgwADMnTtXpit/pKamYsOGDZg0aRJ27Xr1nqZMy+zZwMAADg4OaNOmjbw/e6FCheTYlyxZAnHfHXHtRtmfypUrQ/RJGHHLvQKJiYmycxcvXsSoUaPk8apSpQoat2mJFMRD01gvS51PRSoWXNyGWVW6ykd1Ow+MPL4KD2OfQ8wFym2nYt451nwmTHUNsfrGfvk+ve7mIVxp+6OcI/66exzG2voY6dUSA8s0wpZ7J9HUtRJeJMSiso0bJldoj7YBs9CmWDVZd4uiVSDmrPr/TFTMkbfeaz4x0tbDqZZz0OvgQsy9sBlfezbDrkaTUPHPYbDWN8P3Pt1Qz9ELSy7vQF8Pa/jZl4G3dXH4n12P0V6t5Pv80H9X4Fz4HTncfp81xMxzm3BPMV8opnys8B0EJ2NrVPt7pEzPynysdFM+u5s7yD6IeT79NrViBzmPTVKcJ5jqGGJRjT4Y6tlUnkuIc5PWRasqjs2rcwYxX+loasPGwBxDyzZF2+LVUX/bRCSlJiOr85utgQUaK+bH3ocWwUTHIH13ZLhVUR8kpSSjtIUTtnwxDuUV5wziHGb0iV9U5zLKgpzflBJZfxbv7/r6+ujbt2/WCzEnBfKBgPjeSJx/9e/fHytWrMDMmTPx008/oWPHjnIuK1GiRD4Y5fsPwcLCAnv27EGLFi3k9TnxfQ2/T3t/V9ZAAQpQgAIUoAAFKECBvCiwcuVKNGjQALa2tnmx++xzJgIHDhyQ14gqVaqUSU4mU4ACFKAABShAgfwpoJk/h8VRUYACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUo8DaBbt26yYUtxB81cqMABShAAQpQgAIUoAAFKCAEQkJC8PTpU4hfrBaL4XGjAAUoQAEKUIACFCg4AkWLFpULz/j7+8sF+grOyDlSClCAAhSgAAUoQIGCJqCpqYmHDx9i8eLFqF69Onr37g1XV1fMnj0bL168KGgcHC8FKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoEA2BMqVKyfXsQoNDcXChQtx/fp11KxZE+7u7pg3bx6ePXuWjdqYNacE7t27h3HjxsHJyQmtWrWSzbRv3x7W1tZYu3YtPD095X0Zhg8fjs2bN8v7NORUX1gvBShAAQp8eIFr167Jebh58+awsrKS7+k//vgjHBwcsHTpUgQHByNtHnNz8w/fCdZIgXwg4OjoiEGDBuHAgQN49OiR/H+VnJyMPn36wMbGBnXr1sXy5ct5jptLjvW9qDAcf3wdZaycUdbKRfaqfYmamBX4F048voFyhYrCw8JJxrcrXgPrbx+R+0baelhQrRfGnFiDC8/u4e97J7DpzjH0cK8Hb+viSElNxdpbh7A/5KLMr/xhomOAyRX+hy2K/AkpSfj30VXsDQlUJGug5e7pCHx6T5kVFnrGWHJlJwb9uwzt936P8+F30Ni5gkx/kRiHs+G35f7NiFAcUdQTmRCL3qUb4GHsM2y6ewyXnj2Q/bPSN8W0ih1l3joOZTGkTBOMO/krYpPiERQTjl9u7FO1qdyJS06A35axCAg+j0IGplhasz/+qj8aRQwtlVnk87WIELWwCFxIMwYRFmNccS1A7CI45imGHF2OCafWYunlnWjqUgnP46PR6+CP6H5gAb4P/Bu17D0VGhoyf0Y/PlMcj4exz9WSdDS18LPvIGy7fwpbFY+nL6Ow8NI/2PHgDH6o2gulzO0RqnAZf+rV/aYTkpPQZs8sDD+2EtX/Ho2I+Bh8V7kTtDQ0Zb1HH1+T+yfCbmBfyAXEpyTi8rMg7FF4CGvhIzZDxetgXtUeWFS9D9wsHHC0+Ux4KV4z77pdjQjGZ5ZOEOPJzrao7QhoaCjU+MiSwT///JMpb6ri/3BSUhLE8+HDh9GzZ0+EhYUhKioq07LvkkFXVxfe3t6wtbWVYxD74mFiYoJNmzZh5cqVmDBhAgwMDFC2bFk0adIER468ej9SthcTEyPXBu/atSuuXLmCMmXK4NSpU8rkLD23bNkSYh47ceKEKv8333wj98+ePauKE+lirhNrk+f0pqOjk6XjWlBf/7NmzcrSIVC+nsW9NEcPHoaoYZuR8jwuS2VFpp+u7kZvxXu1mGuauFTEyZbfo1NJv9fKX083L0QnvcSdF49V+UT4W8V7sajHwchKzkeTz/yBr3bPQIri38hyLTDgyFKZx3vj13I+9S3ymSz/PvNJQyfF/y9Dc1xXzJlijt4ZdAZOxtZwt3CUc+HI47/INirbuKHjvjkos34gBv+7XJE/BDPPb1L1X7mz+PIO/PPgNC4/f6CY90vIeWbq2fW4/eKRzJLZfKysJ+2zh+L9X2yP0s1xbYtXR0eF9eB/f4I4bxHnHZ33zUN1u9KYXqmTLLPhzr/YFXQO+lo6WHZlNwYeWYav9szEzHObUF5xXtKhpK/Ml9X5zb9ie8WxeTVnyoLpftgZWqCIkSWuPA/CjHN/oskOf9TYPBpFTW3wT8MJEOlpt3ed39LWUZD2IyMj5WeIIUOGwMjIqCANnWOlgEpAT08P/fr1w82bN+U1KXE+Jr4vEt9HXL58WZWvIO+I94etW7eiXr16aNCgAbJyjluQvTh2ClCAAhSgAAUoQAEK5EeBoKAgBAQEoFu3bvlxeByTQkB831ylShWIz8ncKEABClCAAhSgQEEU0C6Ig+aYKUABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIFXUAsdNGmTRuIhS569epV0Dk4fgpQgAIUoAAFKEABClBAIRAYKBZnAho1aiQXPRWL01paqi+CJDPwBwUoQAEKUIACFKBAvhQYO3Ys3Nzc8Ntvv6FTp1eLbuXLgXJQFKAABShAAQpQgAIFWkDcmKtYsWLydyXEzQwmTZqEuXPnYvLkyfD395c3OBg8eDDs7OwKtBMHTwEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUo8GYBIyMj9OjRQz7Onz+PJUuWYPz48Rg1ahRat24t17qqXr36mytgygcXSEpKwrZt27B06VLs3r0bNjY26NatG3r27AlnZ2dVeyEhITh8+LB8iHxz5syRaaVLl4Y4ZsqHo6Ojqgx3KEABClDg0wqEhoZi7969CAgIkM/ivdzMzAw1a9bElClTULt2bYj3cW4UoMC7CxQqVAjdu3eXj6ioKGzfvh3r16/HwIED0a9fP9SpU0feA7dZs2by/9+7t8SS7yOw7tYhVLYphQ4lfHHx2S/4wqk8vju3Ecba+qhkUxIdS/pi9Ik1+NLZGzPO/SmbalW0KvS1dTG5wv9UTdsYmuHui8coamqL009uyfiE5ERVutixM7SQ5YoYWaniT4TdkG2K9qKTXqri45IScPvFI1X4yvMgNHTyVoWVO6lIVe6i/2cNcS78DmZX6aqKuxkZCgs9Yxn+2rMZzj+9g6jEOFX6mSe35X5q6v/XIyLCX75Aq90z0MK1CmZW7gI/e08cbvYdmu2cpnC6ryqflZ0XCbEy2+VnD1TZRb/ElrauG4o4PS0d6RQa+0yVV7mjo6kFFxMbbL1/Shkln+vYl0NJc3ucenJTLX5vSCBaF6uqOIZ+GHfyV8Qmxcv0C0/vqfI9eRmJX27sw7CyzeBsUhh3/jOvpRjvgZCLMp9vkTI4EPpqX1VQsSPqG/Lvcgz9dwX6eDSAf4UOmOPTDX5bxqXNluV94aStGKN4DV2PCMlyufpDOqCJzedZzl/QM4rzH+W9Bd9moaGhAU1NTaSkpMDPzw9nzpyBsfGr/0tvK/eh06ZOnYovv/xSrdqNGzciOTlZLU5cU1m2bJm8lvLDDz9g+PDhcq45dUr9/4taoXQBLS0teU1GtBkeHg4xj5mamspcP//8M7p2ffXe8vvvv0OEP8Ym2hL94paxgDi3WLt2bcaJaWLTvp4r+lTBhVLJ0DDXT5Mj890/bh/BfsV7oZgTmrlWxg/VeuFz62LyfTDz0uo5ohLicDfqMV7+N0+K+e9h7HM57ynj4pITEBLzFM7GhVWF33U+2XjnKAKf3oV4zxfzTFXbV59ziineb8W8+UjRtth2B51DimI+fPoyStVmfLq5XCSsu3VYptsbWWJKxfY48fgGfry0XVUms/lYlTHNTinFPCa2x3ERaWKBvh5fQMyZL9LM3eL84F5UGNoUr47hx1bKeV3MSUmpybgWEawqP/fCZnxdtqlivO5YdX2vjM9sfuvv0RDCS1i9aStr5SqT/rl/GhEJMXJf9GmM4nzpZ79B6O5WF/5n16uKv+v8pqqggO0sWLAA4v/sgAEDCtjIOVwKvC6go6Mjzz/Evf/EZ+lp06ahTJkyEJ+hxb0By5cv/3qhAhSjq6uLdevWoXfv3tJk9erVaNeuXQES4FApQAEKUIACFKAABShQsAV++eUXWFpaolGjRgUbIh+P/sCBA/KafT4eIodGAQpQgAIUoAAF3iqg/dZUJlKAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKJBvBfr374+KFSviyJEjqFatWr4dJwdGAQpQgAIUoAAFKEABCmRN4MKFCxCLmNatWxdigaRjx469thBL1mpiLgpQgAIUoAAFKECBvChQrFgxdOjQAf7+/mjfvj0XpcuLB5F9pgAFKEABClCAAhR4q8CzZ88gFhKcMWOGXAxbZHZwcMD333+Pb7/9FosXL4ZYbHrevHny3Pibb75BqVKl3lonEylAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABQq2QLly5bBkyRK5ptXatWuxbNky1KhRA+7u7ujVqxc6deoES0vLgo2Ug6O/f/8+li9fjhUrVuDx48fyfgsbN25E48aNoa2t/VrL9vb2aNu2rXyIxOfPn+Pff//FoUOHcPjwYVlPYmIinJ2dUb16dXksxbObm9trdTGCAhSgAAVyRiAyMhIHDhzA3r17ERAQgKtXr0JXVxc+Pj7o06cP6tSpgwoVKnAt9ZzhZ60UgImJCdq0aSMfUVFR2LJlC9avXy/PbcX5bf369WVakyZNZF6SfTyBv+4ex4zKXdC6WFUcfHgZJx7fQHxyIv66ewzTK3fGV8WqYX/oRZwOu4W45ATZMTcLBzyKjcDwYyuz1dEbkaGKcs9Ry94TswP/kmUL65vhVNhNRCe9fGtdSSkp0NLUfC2PuC+a2Mx0DWFnaImh11dgZ9DZ1/KJiM8snbD53gm1NGV5tcg0gU0KhwOhl/Cz70D42pfBlArt0WzXtDQ53m03PiXptYKJ/8UZauu9liYiLPSMpUFc0qvjoMxUysJe7sYkqhsee3RNxpcye5WuzJ/++VbkQxlVSN8EXUrVhomOPho6lceZJ7cx16cQGjh9jhsRoYr97pgTuBlBMeFqVaQiFYsv70ClwiXR2LkidDW1kZDB+NQKZRBQ9r+I4jhejwjJIEfGUcUqe6K1d+uMExn7msCVK1dei0sbIT7zJiUloXz58ujcWfEe8NVXKFy4MOzs7KChoZE2a47vJycn4/Lly2jVqpVaW6IfGX02F5k0Fe8TQ4YMwdGjR7Fp0ybEx8dDTy/j/1Nqlf4X6NGjB6ZMmYI1a9Zg6NChmDNnjqxPrDF+8+ZNea5obW390eYqMfY3jTWj/he0uNu3b791yMrXs5eXl7yWJs5FEoy14bl+4FvLvSkxLC4SXfbPR3PFXLK4el/5nvn7zUM4EXbjTUWyHJ+QnPG8YKTz9tdvVuYT8T4t+j7GqzVeKubys+F3ZL80NV7NqymKdLElp6bI56z+mOfTE9oaWuh3eLGihqzPxxnVX0jfVN7r9KXiHCTtJuawjHzFHOdiUhglzIooxpPx60Cct4TEPIOY3yYr5u/M5jddLW00da2EBRe3KeazCrIbBv/NyWWtXGTcScU5y4uEWJn2ND4qbVfl+YyIKGFeRC3+Xec3tUoKSCA6Olre02HgwIEwMzMrIKPmMCmQuYCWlhbatWsnv3/4+++/MXXqVHh7e+OLL77AuHHj5LWtzGvJnzmEjfgux9zcXN4PJiYmBuJ8jhsFKEABClCAAhSgAAUokL8FxHc7q1atkp8DdHR08vdgC+jo7ty5gwcPHsDX17eACnDYFKAABShAAQpQQPH7D0SgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCgYAqIRS/EHw0sWrSoYAJw1BSgAAUoQAEKUIACFKCAmkBgYCDKli0LKysrlCpVSi6oopaBAQpQgAIUoAAFKECBfC8wduxYiD+6Ezcq4EYBClCAAhSgAAUoQIH8JrB06VJ5w5AuXbq8NjSxyODo0aNx7949LFiwQN78Sdy0q3nz5jh27Nhr+RlBAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAgbQCRkZG6NmzJ06dOoWzZ8+iRo0aGD9+POzt7dGxY0ccOXIkbXbuv4dAUlISNm/ejIYNG6Jo0aJYvnw5unbtitu3b2Pnzp1yDTFtbe0stWBhYYFGjRph5syZct2xiIgI7Nu3D926dcOjR48wdOhQiHXJChcujBYtWmDu3Lk4ffo0kpOTs1Q/M1GAAhSgQOYCCQkJOHDgAMaNG4cqVarIe+eI9SDF3Cneo3ft2oXnz59j//79Mk/lypWhpaWVecXMQQEKvLeAiYkJ2rdvL8+9wsLCsGzZMohzMXHuJc6PvvrqK2zduhWJiYnv3RYryFwgMiEWOx6cgbmeMeb6dMcftw/LQjFJ8dh05xgs9U0U8T0U8f//2SM5NQUlzOygrZH99802e2aiiJElJldojxauVVDU1BY9Dy7MvKNvyJH6X3xK6qu90haOGebU0tCEobYevK2LZ5ieilflnY2t0dCpvFqeZ/FR6H9kCZJTUlDNrjTMdA3V0t8lkPpffzMqq+xL+rSwuEhExMfAWEdfLSkiPlqGKxYuqRb/IDociSlJiEiIUYtPH3A0LiSj7kWF4YeLW7Fecaw1FV5izD9d3Q0rPVMMOLwU085uRHDM0/TFVeEDoRcVbUUjQdHmu2zmekayWMhb2niXelkmcwEdHR2ZqWTJkpg8eTLu3r0rr0MMGDBAvi9nXkPO5BD/T1IU/+/EnJDdrU6dOhCfzfX09LJVtEiRImjcuLGcm8QcFRoaiu+++07W9fPPP0OsSd6rV69s1cnMH1dA+XouXrw4JkyYIK/rnDlzBoMHD4atrW22O9PLvb7iPVFDrdxfd49j3X/zZSPnCmpp7xp403v/2+YL0dbb0pV1irntcLPpOBN+C3MubEZQ9JN37aaqXNvi1VHXsRz8z/yB2y8eqeIzm49VGdPt3IgMhYbC2UgxV6fdxBz2uXWx146Bss23zXG6mtqwMTBDVuc3eyMrOBgVwozKXVSPSRXaye40c60s4zwsnXDrxUMZV87KNW1XERTzat6NTnypFs/5TY3jrYHFixcjPj4eQ4YMeWs+JlKgoAqI90lxbUt8l7Bjxw68ePECVatWhZ+fn/z+oaC6iHHPnj0b3377rTxPE/eG4UYBClCAAhSgAAUoQAEK5G+Bw4cPy+t+4rtFbvlTQPyuh4GBASpVqpQ/B8hRUYACFKAABShAgSwIaGYhD7NQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABSiQTwX69++PP//8E+KPHLlRgAIUoAAFKEABClCAAgVb4MKFC/D09JQIPj4+OHr0aMEG4egpQAEKUIACFKBAARQQC6p16NAB/v7+XMC+AB5/DpkCFKAABShAAQrkZwFxE4KFCxfKm3IZGxu/cahigWlx466rV6/K36cQN30S10urV68uF65+2+K8b6yUCRSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAgVKwMvLC0uWLEFoaCjmz58v17YS61l5eHjI8PPnzwuUx4ca7IMHDzB+/Hg4OzujefPmcg3dDRs2ICgoCFOnToWLi8t7N2VoaAg/Pz/Zzp49exAREYETJ05g5MiRSElJke1UqFAB5ubmqF+/vlzL9+DBg3j58uV7t80KKEABChQUAbG247lz5zBr1iz5XmphYSHfe9etWyfvnfP777/jyZMnOHv2LGbOnIl69epBvD9zowAFPq2AmZkZOnfujO3bt0Os2bpgwQJ5L9ymTZuiSJEiGDBggDxv+rS9zP+t/3nn1X3F4pIT8O+jq6oBr7mxX+7ramrj0MNLqvhLz+7DSEcf3dzqqOLEjpmuIbq71VWLSx+ITUrAymsBWH19H448uoK2AbNwLyr79z9WvO3LTUtDUz5HJcbJerq714W+lo5as18VqwY7QwtcjwiBu4UjrPXN1NLTBp6+jMK0Sp0gxpx2C4l5hpuRoTIqPjlRPielJMtnPS3dtFlzdP9aRDCsDdT7f/rJLdmmj62bWtulFWPVUYzjZNgNtfj0gRp2HjgffgdhcZEIf/kCHpbOOPzwsgx/ptgX5YNiwvHkZSRSFf/+j707gbOp/v84/p7V2Ped7IwlZMs2soxKJUvW7CWkJK2yZ+lH6ddPK0olu4SUqIZkCEm2ZMbYsiY7wzDr/36//Wci22CWu7yOx3HvPed7vt/P53lmzjn33Dvf77WmwBxFtGTfr9dafcPl+TPlkDmf/xF59IZlKXB7ArGxsfLz+/v3pHDhwnrhhRe0detWhYeH65VXXkmR98G3F+HfW/v6+qp8+fJau3atdu/efVmVM2bMUFRU1GXLLn2xbds2NW/e/NJFyX7ep08fhYWFqWPHjurfv78CAgLsuWrq1Kky9VatWjXZdVEw9QUu/XkuWLCgnnvuOW3atEkREREaMmSISpYseVtBFM2SR13LNrqijhUHt9pliecE8yIuIf6Kc9AVG6bDgoF3tXGcD3z07f6NtnXv/z933moo+RznobGOc+W6Izv0/rYlSdXUyFtaNzofF8mcO6n8pU+2n9xvX17tHJfVL6Mq5ypxaXFVyV1cRx3nrb1nj1y2/NIXtfKVUYCvv5bu/zVZ57eVjnNfhTlPXTZX+3yArfLVX2bb5csPbrHnx2UHNqumo/5Lp1LZCtjz7roj4ZcuFue3yziu+cIc099880317dtXuXLlumY5ViCAwN8C999/v1atWqUffvhBPj4+atKkierVq6elS5d6LNGIESM0duxYPfPMMxo3bpzHOpA4AggggAACCCCAAAKeIPDxxx+rWrVq9nsAnpCvJ+a4YsUK1alTR/7+afcZnCc6kzMCCCCAAAIIOLfA39+Icu4YiQ4BBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQRSSaBDhw7KkiWLPvzww1RqgWoRQAABBBBAAAEEEEDAFQRMZ6SmM5gqVarYcOvWrauff/5ZpqMNJgQQQAABBBBAAAHPEjAdqu3atUumc2UmBBBAAAEEEEAAAQTcRWDOnDl2QIJ+/folKyVvb287qNSaNWu0cuVKmQEOzGAGZtCuTz75RNHR0cmqh0IIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKeK2DGB+vVq5d++eUXbdiwQfXr15fp/7VQoULq2rWrVq9e7bk4ycw8Li5OixYt0oMPPqgSJUrY8da6detm+9D99ttv1bp1a/n6+iaztpsvZuquVauWnn/+eS1cuFBHjx7Vtm3b9MYbbyhPnjyaPHmyGjZsaPsrM/v3lVde0TfffKPTp0/ffGNsgQACCLixwO7du+0xs127dsqbN6+qVatmj6U5c+bUhAkTtGfPHu3cuVOTJk1S27ZtlTt3bjfWIDUEXF8gV65c6tmzp1asWKG9e/dqwIABWr58uWrXrq2yZctq5MiRMr/3TCkv8N2BTTobE6XPd13+XmL90QjtOn1YX/+xXvEJCUkNz9+9Rgcij2t0rc56ptJDKpu9kFqWqK0J9Z7QnJ2hSeX8ffyUzS+TfLy87TI/bx8tuG+QzsVeVBa/jMrhn1mFMuVKKp/4JFeGLI71AfL3/ueaPGeGzMrkk0EZHHWa6c+ok/axZr4y9rFizjv09tavVDhzbn3VbKjqFyivyrmK65W72iibfyYdOHdc/9uyyJZ9o053W7eXvNS6ZB27rE7+QOV0tBsZe0GZfP31v3o9L2u/Qs6iCsxZRLMd+V2Ii7Hb7DxzWH+cPapHStRR0cx5VOb/HczKKrmLO2r3suVMLmZKjN08T1xm2kycMvv+XS7Axz9x0RWPa/4Mk4nl0um3E/s0M+JH1XXkXMSRf+JUO385u/8+DV+WuMg+Vsx1R9LrgplyqlreUhq+fmbSssaF79QPh7ba100KV056nlggwLEPnq/SUuVzFElcZO0q5y6hQes+S1p26ZMcjv1npksNLl1vnt+RJa+WH9yii//v++/1vE4ZgcKFC8scb3v37q2ffvpJBw4c0GuvvaZKlSqlTAO3UcvBgwcVHx9v3yMnVjN8+HAlOI4/jRo10meffaYlS5aoe/fudlnGjBkVFRWlMWPG6LfffkvcRMePH9fGjRv11ltvJS27mSdNmzZVyZIlZcZhbNCggd3UeB0+fFiPPPLIzVRF2VQWMD/P5trfXD+sWrVK5mdo7NixSWNnpkTzu88c0bDqHXR3vrKXVde6ZF2dd5zP5u5albTcHMNyB2RTpzL3OM4lGexjroAsKp41nz3nJRbM7JfhsnOMWW7OAZeeE8yyTI5llx43E88dV1t26bb/Pp9kcrRXwHG8b1qkqnJlyKqe5Zua6mXOAdkd50gTq5lyB2S1j5f+l9jWpeverPOYIy5/9Q39QAmOf2Yy5/h2perb5zc6H9tC//pv07E91vPf57gR62fZ80KH0n/XbTYz59dajv0x4pdZl12f+Hr52GuSxKofLn63Vh3+Xd/u32gX3ej8lrhdch4H/zzdXnPU+v/rELNNUMGKCj91UDMc5+RLJ85vl2pc+/lHH32kM2fO6Lnnnrt2IdYggMAVAuZzhJCQEHtdZ8Y/adasmWrWrGk//zDXUJ42vfTSS3rnnXfsZyrDhg3ztPTJFwEEEEAAAQQQQAABjxCIjIzUvHnz9Nhjj3lEvp6apPm82HwuwIQAAggggAACCHiywD/fmvJkBXJHAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMBDBQICAtSjRw/bUcbAgQPl4+PjoRKkjQACCCCAAAIIIICAZwuYDkpNx6lVqlSxEHXrOjq6OH9emzdvVvXq1T0bh+wRQAABBBBAAAEPEyhdurQ6deqkUaNGqWPHjtw39rD9T7oIIIAAAggggIC7CpjOo9u0aaOiRS/vcD05+QYFBcnMiQM9mc6jBw0apCeffFJ9+vRRvnz5klMNZRBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBDxYoFq1anassDfffFMzZ860z+vXr68KFSrI9G/VpUsX5cyZ04OFLk993759mjJlip0PHTqk4OBgzZ07Vy1atJCvr+/lhdPwlZeXl91nZr+ZvsjM9Mcffyg0NFQrV67UwoULNXbsWHl7e+vOO++0/Zg1aNDAPhYoUCANI6UpBBBAIH0Fjh49quXLlyskJMTOe/fuVebMmWWOia+88oo9rleuXFnmuMqEAAKuLXDHHXfYvlpNf60bNmzQtGnT9P7772v48OEy17uPP/642rZta48Brp2pc0R/MS5GX+9dr9k7Q68I6PPdq7X2SPhly6PjY9X629c0M/gFjazVyc6/n9yvPj++r8jYCwrw8VPXso1Vv0B5Bfj6a2j19nr3t8U6eTFSf0T+pfF1elxW3+no8xq8bpqmR6xQ6xJ1VDt/Ofl6+9jtxm38QvffUU31HHX5OK6HB1drp1EbZuv4hbNaceg3dSvXWCWy5VfflR/o47AQFcmcW8/c2VxfPzBMsfFxemfr15qy/XvbnsmlQKYceqVaW+3rPEXbT+3XF7vX6ISjLnPmKJo5j43x95MHlNkvQIuaDdHm43uUwZHPw8Vq6aPt32nozzMui3385vkaVbOz1rR+Q0v3bbAxBBWsoHwZc6ikI65cGbKqU5mGdpunKj0gk0/RLHn0WGBTu2zgXY9o2PqZyuaXyeZiFr5QpaVG/TpXu8/8actc+t+ErV+pc9mGKp41n/ae/Stp1YCfpuhczAV9fu/LetuRs6/D6t6iVfXw0tGKcThcOuV3xPZ2vV46duG0GheurN4/vqcfD2+zRXy8vB37rYIGOfaHmRoVvlOTfv/WPk/8z9tR5uHitTTEsS82HtutkAObdfziWbX9bqzOxV5MLGYf8wZkV5tSddXc4WemETU6as6uVY59t9W+TvzPz7G/H7yjhh5b8XbiIh5TSaBnz54yszNNUVFRmjx5sr2+M3ENGzZM3bp1U+3atdW6dWt9+OGHevHFF+2ybNmy6fXXX1fnzp1tCvHx8friiy80dOhQ1ahRQ/fff7/y5Mmjb775RlmyZLmlNM21ZK9evez79MQKAgMD7b2DDh06JC7i0QkEzH0vM6fmtOfsEXu8He44fv0VdUoRpw+pUaE7lSNDFnX4/g3tcLxOnBbuWavu5ZrovaA+9lw0asMcbTq2R5l9M9jj5nzHOadPxWbK6di2Tv5AtSpRW9/t32jLFsqcS1n9M+qJ8vdq2o4f1KfC/SqSJbeyOM5HHUoHadfpP2/5fPLu1sW6K09JTW/ynG1v4LqpujtfWQ2o/LA9/5XLUcSmYOIx556PHOfN2IQ4Vc9bWv0qPWjXtS5ZR1uO73WcE33V3HEOMA69HTGayZwnTf3r/4qwr290PraF/vXfqehz+u/mL+354hvH+TRx2nnmsFosHaNJDZ5SfEKCQg//bi1f3zRfMyJ+TCxmH836ng6/qLhoez2QyeFu9pGZknN+swWT+V/YqQO67+vhGnN3F61zXCeZa6laDtOHl4xWXEJ8Ui2c35IorvskOjraHtufeOIJ5c+f/7plWYkAAlcXqFOnjr3+Me+fzTiBLVu2lLlHNmTIEHs9ZT5b8JTp6aefVqZMmWSOKWYM7fHjx3tK6uSJAAIIIIAAAggggIBHCJjvecXGxtrx0T0iYQ9McteuXdq/f78aNmzogdmTMgIIIIAAAggg8I+AV4Jj+uclzxBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMDTBMyXKMqUKaMFCxbYP270tPzJFwEEEEAAAQQQQAABBKRPPvlETz31lM6ePSsfHx+ZrxTlypVLI0eOVL9+/SBCAAEEEEAAAQQQ8DCBiIgIlS9fXlOnTlWnTp08LHvSRQABBBBAAAEEEHA3gR9//NF2LLFu3TrVqvV35+W3k+PBgwf13nvv2U6uIyMjZTqR7t+/v+66667bqZZtEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBG4gMH7zAr2+ab6i42JvUJLVCLi2QPGs+bSp7QTXToLokyXwyy+/2D6tZs2apbi4OLVt21a9e/dW3bp1k7W9uxUyBosXL7YmS5YsUd68edWjRw898cQTKlmypMuke/ToUa1atUqhoaFauXKlNm3aZPdv6dKlFRQUlDSb10wIIICAuwicP3/eHvNCQkK0bNkybd682Y6BY/qBbNKkiYKDg1WnTh35+fm5S8rkgQAC1xGIjY3V999/r08//VQLFy5UhgwZbB+ujz/+uO6+++7rbOkZqwpO7aaouOhbTjZXhqw6cfHsFdtn98+kszFRineMP3a1qWjmPEpw/Dtw7vjVVl+2zN/bV0Oqt9dH27+VaS+rX0YF+Porf8Yceumu1qr2+QDFJsRdts2NXhTMlFOHz5+8rFiAj5+KZ82vP87+dVUTHy9v2+ah8yfk6+UjLy8pJv6fdk08R6JO2ToLZ86l3BmyadeZwzoXe/GydhJfZHC05+eoJzL2gq0vLiHemiSuT+nH7uWaqGLOonpx7adXVJ3NYRqYs4gORB6Xye/SKV/G7NrRcaJG/jJbH2xbIvP6j8ijlxa5qefmZ8PcT7udn7vEBlsWv1ttS9VXp2VvJi5K1qO/w75vxWYaUaNjsspT6NYFChYsqEGDBqXb+IPx8fE6cOCAihQpIm9v7ysSOXXqlPz9/ZUpU6Yr1t3KggsXLtjzjJc5QPz/FBUVpYwZMya+TNVHc55r1aqVYmJi5Ovrm6pteVrl+yKPqfLc5I+jmdHHX37ePjrjOBea80vZ7IV18mKk9p87dk263AFZdfzC3+dUc464GBdzzbJptcJLXsroOOeev+RcZvK69PyX0rHc6Hz87/aM1eqW4/TQN6P0Z9Tl53ZTtnS2gsriOM/9fnKfouMv/zznrbqPq3PZhsr7aReZc/eZ6Ch7/fLvNlLjdYGMOXXBcQ12KvrcFdXf6vnNVFQ9b2ktaz7qijrdccHkyZPt+WX37t0qXLiwO6ZITgikucCWLVs0evRoffHFFwoMDNTgwYPVvn17e18tzYNJpwbNZ2Rdu3a1nwWZsWAuva5Lp5BoFgEEEEAAAQQQQAABBFJAwHxHqlChQpozZ04K1EYVzigwZcoUe68o8b6/M8ZITAgggAACCCCAQFoIeCU4prRoiDYQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAeQWaNWtmOxf67rvvnDdIIkMAAQQQQAABBBBAAIFUE3j22Wf1008/6eeff05q44EHHlD27Nll/pCWCQEEEEAAAQQQQMDzBExnKub68Pfff79qh3yeJ0LGCCCAAAIIIIAAAq4q0LJlSx07dswOwpSSOZhOpadPn64JEybot99+U4MGDdS/f3+1aNHCozpjTElT6kIAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBK4nMH7zAr2+ab6i42KvV4x1CLi8QPGs+bSp7QSXz4MEki9w9uxZzZw5U5MmTdLGjRtVsWJF9e7dW126dFGOHDmSX5GLlty/f78++ugjTZkyRYcOHVJwcLB69epl+/Xy8/Nz0az+CTsyMtKOBxEaGiozr1u3TqYvs4IFCyooKChpvvPOO+kL+B82niGAgJMLxMXF2X7Mly1bppCQEK1Zs0bR0dGqUKGCPY6bY3nDhg2VNWtWJ8+E8BBAILUFjh8/rmnTptlrPdOHa6VKlfT444/ba93cuXOndvNOWX/Bqd0UFRftlLElBvVpo/76+a8Ivb/tm8RFSY/9Kj2od35bnPSaJ9cW8JKXPmr4tCZs+UpbTuy9dsF/rcmXMbt2dJyokb/M1n+3fPmvten3skz2QhpRo6MeX/G2LsTF3FQg/j5+6luxmd3+pjak8E0LmPeagwYNUr9+/W562/TeYPHixTLz9abChQtr8ODB1yuSpusWLlyoVq1aKSYmRr6+vmnatrs3ti/ymCrPdb2fY3ffL4n51c0fqPalg/Ts6o+U4PiX3Omtuo+rc9mGyvtpl+Rukurlbuf8ZoKrnre0ljUflepxpncDsbGxKlu2rO69915NnDgxvcOhfQTcTmD79u0aM2aMZs+erZIlS9rruc6dO3vM9YW5pmrfvr0effRR+3mRj4+P2+1jEkIAAQQQQAABBBBAwJMEIiIi7H2EJUuW6P777/ek1D0q127duunAgQMy3xthQgABBBBAAAEEPFnA25OTJ3cEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBD4W+Cpp56yHW/s2LEDEgQQQAABBBBAAAEEEPBAgS1btqhy5cqXZV63bl3bCellC3mBAAIIIIAAAggg4DECQ4cO1c6dO21nMh6TNIkigAACCCCAAAIIuJ2Auab96quvNGDAgBTPLSAgQD179tTWrVttxxXZs2dX27ZtVapUKY0fP16nTp1K8TapEAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwP0EsmbNqt69e+vXX3/V+vXrVadOHb3yyisqVKiQunfv7pbjBsTFxdl+wpo3b67ixYtr0qRJ6ty5s+0T97vvvlObNm3k5+fnFjs7S5YsuvfeezVq1CitWLFCp0+f1qpVq/TMM88oMjJSQ4YMUdWqVZU7d2499NBDGjdunN3n0dHRbpE/SSCAgPsIbN++Xe+8845atGihXLlyyYxtM3HiRHscnzJlig4dOqRt27ZpwoQJMsd3c35jQgABBMw1zrPPPmv7cF27dq291h02bJi91m3fvr29PkLJ+QSq5y2t5sVqqobjMbt/JmX2zaB6BcprQOUW2nv2L+cL2EkjSlCCnlz5gR4rH6y78pRMdpSZHN5myp4hc7K3Se2CRTPn0XOO/f9U6ERdiItJ7eao30MFSpQooUaNGl13rlmzpofqkDYCziXw05EwLdizVqNrdZKX419yp4yOc5yvl4+9tkjuNqlZjvNb8nVnzJih/fv3a+DAgcnfiJIIIJBsgfLly2v69OkKCwtT/fr11atXL5UpU0aTJ0+WJ3xW0LJlS3355ZeaM2eO/azIfIbEhAACCCCAAAIIIIAAAq4r8Mknn6hIkSL2+1KumwWR30jAfP8tKCjoRsVYjwACCCCAAAIIuL2AV4JjcvssSRABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSuKxAfH69SpUrJfDn+rbfeum5ZViKAAAIIIIAAAggggID7CeTJk0fDhw9Xv379kpJbvny5mjRpogMHDqhw4cJJy3mCAAIIIIAAAggg4DkCXbt2tQMOmM6avb29PSdxMkUAAQQQQAABBBBwGwFzz3Px4sWKiIiQj49Pque1a9cuOxiK6bjEdEhorqnNwE6BgYGp3jYNIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAAC7i4wfvMCjd34hWLj49w9VfLzcIHiWfNpU9sJHq5A+mfPntWMGTM0adIkbdq0SZUqVdLjjz/uFmMHmP5up0yZooMHD9oxEXr16mXHT/Pz8/PIHW/GkNu6datCQ0O1cuVKrVq1SocPH1ZAQIDuvvtuBQUFqUGDBqpTp46yZMnikUYkjQAC6SNw6NAhLVu2TCEhIXY2r7Nnz65GjRopODjYHsPpbzF99g2tIuDqAufOndPcuXM1efJkrV27VhUqVFDfvn3VvXt3Zc6c2dXTu2H8Bad2U1Rc9A3LpWeB8jmK6KlKD6pBoYoqmjmPDp0/oe/3b9Kk35dq+6kD6Rmay7ZdJHNuHTh3/Ibx35EljwZXa6f2pYO098wRveG4HzZ31yrFpPP9sPwZc+hI1Kkbxn+tAv4+fupbsZlG1Oh4rSIsTyGBO+64Q/ny5bNjlJv3k0ypJ2Du1zz//PP2vfyFCxfSpB/01MvG+WreF3lMlef+M7am80VIREYgX8bsOnkxMlnnqbYl62nM3Z0d2+TQR9u/09Tw5dp64o90hbzd85sJvnre0lrWfFS65pHajZv7l+aa3dyfNOMwMCGAQOoL7N27V2PHjrW/c+ba7uWXX1bPnj3t5wap33r6tfDDDz/ooYce0oMPPqiZM2fK19c3/YKhZQQQQAABBBBAAAEEELglAXMfwdyn7tatm8aMGXNLdbCR8wuY748ULlxY33//vf3+iPNHTIQIIIAAAggggEDqCXglOKbUq56aEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwFUExo0bZ/8QwHSolClTJlcJmzgRQAABBBBAAAEEEEDgNgXMe4AiRYpoxYoVuueee5Jqi4yMVI4cOTRr1iy1bds2aTlPEEAAAQQQQAABBDxHYMeOHbbjps8++0yPPvqo5yROpggggAACCCCAAAJuIXDq1Cl773P06NF69tln0zQnM2CX6fz0nXfe0a5du3Tfffepf//+9tHLyytNY6ExBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAXQSmR6zQ06GT3CUd8kDgmgL1C1bQ182GXnM9KzxPYP369Zo0aZLmzJkjM46Aq0/58+dX9+7d9cQTT6hUqVKunk6qxL9z506FhoYmzea1j4+P7rrrLgUFBSXNefLkSZX2qRQBBDxT4MyZM3b8mpCQEJl5+/bt8vf3V926dRUcHGznGjVq2OORZwqRNQIIpIbAxo0b9f7772vmzJn2mGOuEZ955hnbr2xqtJeSdcbHx8vb2/umqyw4tZui4qJverv02sDP20cx8XHp1bzHtWu8M/lmuCzv09HnL3vtii/8ffzUt2IzjajR0RXDd6mYzT2El19+WT/88IPtE9v00W2u4ZhSTiAsLEzDhw/X559/rmrVqtkx4c31MlPKCuyLPKbKc/ulbKXUlq4C2fwy6tJ++i/GxeiCY3b1qXre0lrWfJSrp3Hd+GfPnq1OnTrZ+wRly5a9bllWIoBAygqYMafHjRunDz/80I4x/eKLL6pPnz7KlClTyjbkRLX9+OOPevDBB+21rDn++Pn5OVF0hIIAAggggAACCCCAAAI3Eli6dKmaNWsmMy56mTJlblSc9S4qMHfuXHu/6OTJk8qSJYuLZkHYCCCAAAIIIIBAygh4JTimlKmKWhBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMCVBY4dO2b/QP7dd99Vz549XTkVYkcAAQQQQAABBBBAAIGbEPjmm2/sH8aeOHFCOXPmvGxL0ynJPffco7feeuuy5bxAAAEEEEAAAQQQ8ByBLl266JdfftG2bdtuqfNSz5EiUwQQQAABBBBAAAFnE3j99dc1ZswYHThwQFmzZk2X8MwgAIsXL9aECRO0bNkyO8BV79691aNHDzFYU7rsEhpFAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwOUEDh8+rNDQ0KR569atMn2dlS9fXkFBQUlzsWLFXC43AkYAgfQTiImJ0Zo1axQSEmLn9evXKy4uTlWqVFFwcLCdzTEmU6ZM6RckLSOAgMcImPGzJk2aJDOm7tGjR9W5c2e99NJLCgwMdFqDu+++W9u3b7fHzRo1atjHqlWrqkKFCvL3979m3AWndlNUXPQ117MCAXcU8PfxU9+KzTSWU1HLAABAAElEQVSiRkd3TM8pczJ9Yg8ZMkRr165Vy5YtNWrUKFWqVMkpY3WVoPbs2aNXX31V06dPt+enkSNHqnXr1q4SvsvFuS/ymCrP7edycROw5wlUz1tay5qPctvEExIS7HVuxYoVNWvWLLfNk8QQcHaBP//8U+PHj9fEiRPtvbrnnntOTz31VLqNxZLaXqtWrVKzZs3UpEkTzZ0797rvsVM7FupHAAEEEEAAAQQQQACBmxNo166dzHuYlStX3tyGlHYpgX79+tnPH8z3TJgQQAABBBBAAAFPF/ByfKCW4OkI5I8AAggggAACCCCAAAIIIIAAAggggAACCCCAAAJ/C3Tp0kXbtm3Tr7/+CgkCCCCAAAIIIIAAAgh4iMDYsWP1/vvva9++fVdk/PTTT8t86XrdunVXrGMBAggggAACCCCAgGcIhIeHy3TeNG3aNHXsSEeMnrHXyRIBBBBAAAEEEHB9gdjYWJUsWVJt27bVm2++6RQJ/f777/rggw/stfWFCxfUpk0b9enTR/Xr13eK+AgCAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQRcQ+DUqVNavXq1QkND7fzLL78oOjpaRYsWVYMGDRQUFGTn8uXLy8vLyzWSIkoEEEh1gYSEBG3dulUhISF2Xrlypc6dO6cSJUooODjYzo0bN1aePHlSPRYaQAABBK4lEBMTo+nTp2vcuHGKiIhQu3btNGzYMJnrGmebGjVqpBUrVtiw/Pz8FBcXp/j4ePn4+KhUqVKqVauWqlataucqVaokHV8LTu2mqLhoZ0uHeBBIVQF/Hz/1rdhMI2ow7kmqQl+l8q+//lpDhw7Vli1b1KFDB40YMUJlypS5SkkWXUvg4MGDGjNmjD766CMVK1bMGpoxfLy9va+1CctTQGBf5DFVntsvBWqiCgRSV6B63tJa1nxU6jaSjrUvXLhQrVu3tueRSpUqpWMkNI0AAkbg2LFj+u9//6v33nvPvvd89tln9cwzzyhHjhxuB/TTTz+pWbNmuueeezRv3jz5+/u7XY4khAACCCCAAAIIIICAuwmcOHFChQoVsuM19ujRw93SI59LBO666y6Zz0rNe1QmBBBAAAEEEEDA0wW8HF/OTfB0BPJHAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBvwXWrl2rOnXqaM2aNapduzYsCCCAAAIIIIAAAggg4AECpgOSyMhIffXVV1dkO2PGDJkv1585c0YBAQFXrGcBAggggAACCCCAgGcIdO7cWb/++qt+++03Oq/zjF1OlggggAACCCCAgMsLzJ49W+Y6dufOnSpevLhT5XP+/HnNmjVLEydOlBmoqWLFiurTp4+6dOmi7NmzO1WsBIMAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAs4vcOHCBa1bt06hoaFauXKlHYfOjEORO3du1a9fX0FBQWrQoIHuuusu+fr6On9CRIgAAikmsG/fPoWEhNh52bJl+uuvv+yxoVGjRmratKmaNGmiUqVKpVh7VIQAAgiklEB8fLy++OILvfrqq9q+fbseffRRjRkzRnfccUdKNXHb9Tz99NOaPHmyYmJirlqXj4+PHd8hcX2ePHlUvXp1rfI/JO/g0vLOkuGq27EQAXcU8PfxU9+KzTSiRkd3TM/pc0pISNC8efM0bNgw2293t27d7HNnOqY6I+LRo0c1duxYvf/++8qbN6+GDh1qx3XkfXXa7K19kcdUeW6/tGmMVhC4DYHqeUtrWfNRt1GDc29ao0YNew0+f/585w6U6BDwMIGTJ09qwoQJevvttxUXF6d+/fppwIAB9r6fO1GsXbtW999/v+rVqydzHMqQgffR7rR/yQUBBBBAAAEEEEDA/QTeffddDRw4UH/++aeyZMnifgmSkRU4c+aMcubMqc8//1ytW7dGBQEEEEAAAQQQ8HgBL8eXMhI8XgEABBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQSBKoVq2aKlasqGnTpiUt4wkCCCCAAAIIIIAAAgi4r0CFChXsF6tHjx59RZJ79uxRyZIlbSehplNQJgQQQAABBBBAAAHPFAgPD5e5bpwxY4Y6dOjgmQhkjQACCCCAAAIIIOBSArVq1VKxYsVsxxLOHPiGDRs0ceJEzZo1S+ZPPTt27Kg+ffrIdKTKhAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCNyKQFxcnDZu3GjHmggNDbWPx44dU+bMmVWnTh0FBQXZuXbt2sqYMeOtNME2CCDgpAInT57UDz/8oJCQEDtHRETY33Mz7kxwcLCdq1atKm9vbyfNgLAQQACBywXi4+NtH7ODBw/WwYMH1b9/fw0aNEjZsmW7vGA6vHr33Xc1YMAAxcbG3lzrfj7KMrCJfIrnurntKI2ACwv4+/ipb8VmGlGjowtn4fqhm/eK06dP16uvvmqPqb169ZI5vhYoUMD1k0vBDE6dOqXx48drwoQJ9n20Oe/07t1bGTJkSMFWqOpGAvsij6ny3H43KsZ6BNJdoHre0lrWfFS6x5EaASxZskQPPPCAzJgK1apVS40mqBMBBG5T4MyZMzLvTf/73//q4sWLeuqpp/T8888rb968t1mz82y+fv163Xvvvbr77ru1cOFCBQQEOE9wRIIAAggggAACCCCAAAKXCVSvXl2VK1fWJ598ctlyXriXwNKlS9WsWTMdOXJE+fLlc6/kyAYBBBBAAAEEELgFAS/HoOQJt7AdmyCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggICbCkyZMsV+uX///v1u9eV+N91dpIUAAggggAACCCCAwG0JXLhwQVmyZNHMmTPVrl27q9ZVsGBB20nVSy+9dNX1LEQAAQQQQAABBBDwDIFOnTpp06ZN2rp1K51Ce8YuJ0sEEEAAAQQQQMBlBVavXi0zsMlPP/1kBzdyhURMp4yfffaZJk2apN9++02mA5Qnn3xSHTp0sJ1Lu0IOxIgAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAs4rsH37doWGhibNf/zxh/z8/FSjRg0FBQXZuV69esqZM6fzJkFkCCBwhcDFixdt/4shISH6/vvvtWHDBlumWrVqCg4OVtOmTWV+tzNkyHDFtixAAAEEXEkgOjpa77//vkaNGmXHSxg+fLj69OkjX1/fNE8jPj5e5lpq1qxZGjx4cLLaN9ddZrtevXppXuAxRWfxSdZ2FELAXQT8ffzUt2IzjajR0V1Scuk8YmJiZMYwHz16tE6ePKmnn35aZpzC3Llzu3Retxt8ZGSk3n77bb3xxhv2XGNM+vXrp0yZMt1u1Wx/CwL7Io+p8tx+t7AlmyCQtgLV85bWsuaj0rbRNGrN3E/IkSOHFi9enEYt0gwCCNyqgLmOMe+Z33zzTZ07d86Od/LCCy8of/78t1qlU21n7nma+5xmLJdFixYpY8aMThUfwSCAAAIIIIAAAggggIC0efNmVa1aVStXrrTfQcLEfQXM56Pz5s1TeHi4+yZJZggggAACCCCAwE0IeCU4ppsoT1EEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBwc4Hz58+rcOHCevnllzVw4EA3z5b0EEAAAQQQQAABBBDwbAHzB7CmM8+wsDCVK1fuqhiPPPKI4uLitHDhwquuZyECCCCAAAIIIICAZwiYa8aKFStq5syZat++vWckTZYIIIAAAggggAACLilg7mkePHhQa9eudcn4V61apYkTJ9qOMQICAtSlSxc9/vjjtlMUl0yIoBFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwOkE9u/fr9DQUK1cudI+bt++3cZYqVIlNWjQQEFBQXYuVKiQ08VOQAh4skBCQoI2bdqkkJAQO5vf46ioKJUuXVrBwcF2bty4sXLmzOnJTOSOAAJuLHDq1CmNGTNG77zzjsqWLWv7ca1bt26qZHz27Fnt2LHDju9lxmsIDw+3zyMiInThwoVktenr66vY2Fi1a9dOr732mkqVKqWCU7spKi46WdtTCAF3EfD38VPfis00okZHd0nJLfIwx7L3339fY8eO1cWLFzVgwAA999xzypYtm1vkl9wkjMMHH3yg//znP/b4bhyef/55j3NIrldaldsXeUyV5/ZLq+ZoB4FbFqiet7SWNR91y9s764bLly9XkyZN9NNPP6lOnTrOGiZxIYDAvwTOnz9v3ye/8cYbOn36tHr37q2XXnpJBQsW/FdJ13u5ceNGNW3aVNWqVdOiRYtkxnNhQgABBBBAAAEEEEAAAecRePbZZ7V48WKZz9GY3FvgnnvuUZkyZfTRRx+5d6JkhwACCCCAAAIIJFPAy/HF3oRklqUYAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIeIiA+UPNBQsWaNeuXfL29vaQrEkTAQQQQAABBBBAAAHPE/j444/19NNPKzIy8prX/m+++abGjRunv/76y/OAyBgBBBBAAAEEEEDgMoFHH31Umzdv1tatW695/XjZBrxAAAEEEEAAAQQQQCCNBfbs2WM7lJg5c6btVD+Nm0/R5o4dO6ZPP/1UkydPtp2h3HXXXXrsscfUqVMnBnJJUWkqQwABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACB48ePa9WqVQoNDdXKlSu1ceNGxcbGqmTJkgoKCrJzgwYNbH9vt6t19OhRrVixQm3atJGXl9ftVsf2CLi9gOlrcdmyZfr++++1fPlymf4K8+bNqyZNmig4ONjOxYoVc3sHEkQAAQQuFTDj7fbt29ceG3v27GnH2MqZM+elRZL1PCEhQfv371dYWJjCw8Mvezx48KCtw9/fX6VKlVJgYKCdy5UrZx/Lli2rggUL6uLFi1e05ePjo7i4OHusHj9+vKpWrZpUpuDUboqKi056zRMEPEHA38dPfSs204gaHT0hXZfL0YxjOGHCBJnjlRnL/OWXX7bjG2bKlMnlcrmZgGNiYmTGchw1apROnDhhcza5586d+2aqoWwqCeyLPKbKc/ulUu1Ui0DKCVTPW1rLmo9KuQqdpKbGjRvb+3bmfgQTAgi4nsCFCxfsWCdmPGpznWPeN5vrnCJFirheMpdEbD63MPdEa9WqpS+//FIZMmS4ZC1PEUAAAQQQQAABBBBAIL0EoqOjVbhwYT377LMaPHhweoVBu2kgYPZ19uzZ9cEHH6h79+5p0CJNIIAAAggggAACzi/g5fgCWoLzh0mECCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggkJYCERERMn+Qbr743rx587RsmrYQQAABBBBAAAEEEEAgDQXMl+h/+ukn/fzzz9dsdc2aNapbt67M+4TSpUtfsxwrEEAAAQQQQAABBNxfYPv27apUqZJmzZqldu3auX/CZIgAAggggAACCCDgcgIDBgzQ/PnzZQYA8PX1dbn4rxWwGYTJdEL9+eef20GYWrVqpccee8x2bGg642ZCAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEUlLg3LlzMuNVmL7QzLx27VpFRUUpf/78CgoKSpqrVKmim+0TbfTo0Ro6dKhq1aqlqVOnKjAwMCVDpy4EXF7gxIkTWr58uUJCQuxs+ljMlCmTGjRooODgYDtXrlxZXl5eLp8rCSCAAAK3KzB79myZPmnj4uL05ptvqkuXLlet8vz589qxY4fCwsIUHh5uH81zs8ysM1OePHnsWL7m2sTMZlxf81iyZEn5+PhctV4zfsO2bduS1pnrovj4eFWrVs3G07Bhw6R1iU8KTu2mqLjoxJc8IuARAv4+fupbsZlG1OjoEfm6apKnTp3S+PHjNWHCBGXJkkWDBg1Sr169lCFDBldN6apxm3PGjBkzNGLECB08eNDmOHjwYBUoUOCq5VmYPgL7Io+p8tx+6dM4rSJwEwLV85bWsuajbmIL5y9qxritV6+efvjhB13tetb5MyBCBBBIFLh48aKmTJmisWPH6siRI3ack4EDB6pYsWKJRVzuccOGDfb+aJ06dbRgwQK3u1Z1uR1CwAgggAACCCCAAAIIOATM2JFt27bVH3/8oSJFimDixgKJ94127typUqVKuXGmpIYAAggggAACCCRfwCvBMSW/OCURQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAUwTuvfde+wfqS5Ys8ZSUyRMBBBBAAAEEEEAAAY8TaNy4se2c6qOPPrpm7tHR0cqWLZsmT56srl27XrMcKxBAAAEEEEAAAQQ8Q+DRRx/Vli1btHXrVjqW9oxdTpYIIIAAAggggIDLCJw5c8Z2GjJs2DC98MILLhP3zQR69uxZzZ07Vx9//LFMBxp33HGHunfvrh49eqh48eI3UxVlEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgWQLxMTE6JdfflFoaKidV69erZMnT9rxLOrVq6egoCA716xZUxkyZLhuvWasjB9++EG+vr62nOlDbuDAgfLz87vudqxEwF0FLly4IPM79f333yskJEQbN260fYDXqFFDwcHBdq5bt678/f3dlYC8EEAAgdsSOH36tAYNGqSJEyfa65HevXvb65Tw8HCFhYXZef/+/UpISLDXHyVLllRgYKDKlSt32WPu3LlvOo6OHTtqzpw5tm4vLy+VKlVKr7/+ulq1anXNugpO7aaouOhrrmcFAu4o4O/jp74Vm2lEjY7umJ7b5XT06FH95z//0QcffKB8+fJp6NChti/sxPdwrpqwOQ/MmzdPw4cP144dO9StWzeZ96PFihVz1ZTcOu59kcdUeW4/t86R5NxDoHre0lrWfJR7JPP/WTzwwAMyYz+sWrXKrfIiGQQ8WcCMTf3JJ59o7NixOnjwoL0OMu+jS5Qo4ZIs69evV9OmTe09gC+++IL7pi65FwkaAQQQQAABBBBAwJ0EHnroIcXGxmrp0qXulBa5XEVg3Lhx+t///qfDhw9fZS2LEEAAAQQQQAABzxTwcnwZIcEzUydrBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQuJ7Al19+af/gPCIiwv4B+vXKsg4BBBBAAAEEEEAAAQRcUyBPnjy245JnnnnmugmYzjrvvPNO20HWdQuyEgEEEEAAAQQQQMDtBbZv365KlSpp1qxZateundvnS4IIIIAAAggggAACriPw3//+13bafODAAWXPnt11Ar/FSM3gBR9//LGmTZumI0eO6J577lGXLl3Upk0bOwjTLVbLZggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggMANBRISEvTbb79p5cqVCg0NtfOhQ4cUEBCgmjVrqkGDBgoKClLdunWVNWvWpPri4uKUJUsWXbhwIWmZj4+PypQpo6lTp6pWrVpJy3mCgLsKxMfHa+PGjQoJCbHzqlWr7O9E2bJlFRwcrKZNm6pRo0Ye0beiu+7j9M7r119/1fz587Vjx470DiVd2vfy8lLVqlXVunVrlStXLl1ioNHUFTDXEWas3fDwcJk+WhMff//9d50/f942njFjRlWpUkWBgYH258A8mrlUqVLy8/NLsQBHjhxp+8TNnz+/xowZo+7du8tc21xvKji1m6Lioq9XhHUIuJ2Av4+f+lZsphE1Orpdbu6c0MGDBzV69GhNmTJFxYoV06uvvqoOHTrI29vb5dJevHixhgwZoi1btqh9+/Y2F/M+lMl5BfZFHlPluf2cN0AiQ+D/BarnLa1lzUe5jceGDRtUo0YNLVmyRPfff7/b5EUiCCDwt0BMTIw+++wzvfbaa9q3b586d+6swYMHq3Tp0i5HtG7dOt17771q2LCh5s2bl6Lv9V0Og4ARQAABBBBAAAEEEEhHgcOHD6to0aKaMWOGvfeajqHQdBoING/eXOZz2Llz56ZBazSBAAIIIIAAAgi4hoCX40v1Ca4RKlEigAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAWgqYDnZKlCihdu3aafz48WnZNG0hgAACCCCAAAIIIIBAGgiYjjcLFy6sH374wf6x6/WafPHFF/Xtt9/aTk+uV451CCCAAAIIIIAAAp4h0LFjR9uxu+kUz3Sgy4QAAggggAACCCCAQHoLmO84mM77W7RooQkTJqR3OGnafmxsrO2A1XTS+NVXX9mOtx9++GF17drVdnbo6+ubpvHQGAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIeKbA7t27FRoaaueVK1cqIiJCPj4+qlKlioKCguycNWtW3XfffVcAmXLx8fF65plnNGbMGGXOnPmyMisObdWp6POXLeMFAs4s4O3ovzu4cBVl8s1gwzS/HyEhIXZetmyZTpw4ofz586tJkyYKDg62c9GiRZ05JWJzYgHTL6c57n755ZeaP3++9u/fr2LFiqlmzZoe2Ze86atz9erV+uuvvxQYGKjWrVurZcuW1sOJdyOhXUXgyJEjCgsLU3h4uH1MfL5371573eDt7a3ixYvb/Wz2dbly5ew4vOZ3YeLEifbY+tFHH6lIkSJXqT1lFv3xxx92bC/TF2xAQECyKi04tZui4qKTVZZCCLiLgL+Pn/pWbKYRNTq6S0oelceePXv06quvavr06SpfvrxGjhypVq1auYTB8uXLNWTIEK1Zs8b2YT5q1CjdeeedLhG7pwe5L/KYKs/t5+kM5O8CAtXzltay5qNcINLkhWiO7wcOHND69euTtwGlEEDAJQXMvZMZM2bYe/HmvuWjjz6qwYMH2/fVrpSQucYznzeY+6tz584V47O40t4jVgQQQAABBBBAAAF3EXj99dc1duxYHT58WBky/P39CHfJjTyuFMiTJ4+95/7ss89euZIlCCCAAAIIIICAhwp4JTgmD82dtBFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQOAGAq+99prefPNN+8c6GTNmvEFpViOAAAIIIIAAAggggIArCSxdulTNmjXT8ePHlStXruuGvmDBArVp00YnT55UtmzZrluWlQgggAACCCCAAALuL/D777/bzvBmz56ttm3bun/CZIgAAggggAACCCDg9AKff/65OnToYAcYKlmypNPHm1oBnjp1ynZqOG3aNDvQRd68edWxY0d16dJF1atXT61mqRcBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQ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IIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJ3WuBseLACpvW/083SHgK3JeDh5q6xjfqqR0Cz26qHwulT4Ny5c5o9e7Z+/PFHLVq0SGZOx8aNG6tTp052K1asWPocGL3+m4BZ53XmzJn2tf7zzz+VK1cumfVezWvdpk0b5cyZ829lUiLi+PHjMm2bbffu3a7jY8eOKTY2Vp6enipTpozKly+vChUq2M15nDdv3pToQoarIzQ0VO+8844++ugjFS5c2B4/9NBDcnNzy3BjZUAIIIAAAggggAACCBiB06dPq1SpUho9erSeffZZUBBAAIFbEli4cKFGjhyp1atXKygoSGbt68DAwFuqKzUKffzxx3r++ec1btw4Pfzww2rZsqV27txpr9f5+vratVeyZs2aGk1TJwIIIIAAAggggAACGVLA3Is01xO6du2qf/3rXxlyjAzq7wIdOnRQ/vz5NXny5L8nEoMAAggggAACCGRyATfHD8mxmdyA4SOAAAIIIIAAAggggAACCCCAAAIIIIAAAggggEASBcwD7eYB9rlz5yaxBNkQQAABBBBAAAEEEEAgLQoUKVJEL774ogYPHpys7i1YsECtW7e2f+hfoECBZJUlMwIIIIAAAggggEDGFjB/tLl3715t3LiRSVAz9kvN6BBAAAEEEEAAgTQhYBbQePDBB+0CBwEBAWmiT5mpE+bPUs2iFnPmzLHbpk2blCNHDjuZY/v27XX//ffbST4ykwljRQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACB1BE4Gx6sgGn9U6dyakUglQQ83Nw1tlFf9QholkotUG1aEzhy5Ih++uknmTkzV6xYIS8vL913333q1KmTOnToID8/v7TWZfqTwgJHjx51vQeWL18uT09P3Xvvva73QP78+ZPVYnh4uJ3/f9euXTLb7t27XfuQkBBbV548eVShQgW7lS9f3rUvU6aMfQ8mq0EyWwHzOr722mv6+uuvVa1aNb355ptq164dOggggAACCCCAAAIIZDiBIUOG6JtvvtHBgweVJUuWDDc+BoQAAndWYPHixRo5cqRWrlxpr4cMHz5cjRo1umEnNmzYILP16dPnhvluN/G9997TK6+8IrOe95kzZxQVFWWrdHd316effqqnnnrqdpugPAIIIIAAAggggAACmUbg119/tT/zb9++XZUqVco0487sAy1durSee+45DRo0KLNTMH4EEEAAAQQQQOBvAm6Ohb5j/xZLBAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQiMDMmTPVtWtX7du3T6VKlUokB1EIIIAAAggggAACCCCQ1gXOnTsnM5nYggULFBQUlKzuBgcHy0waZn436NixY7LKkhkBBBBAAAEEEEAgYwts3brVToA6Y8YMO4ltxh4to0MAAQQQQAABBBC42wJNmjRR7ty5NXv27LvdFdp3CJiFEebOnWtfj99++02RkZGqV6+eWrdubbfatWvLTJxIQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBJIrcDY8WAHT+ie3GPkRuKsCHm7uGtuor3oENLur/aDx1BXYsWOHfvzxR7utX79evr6+atu2rZ2rvU2bNsqRI0fqdoDa06zA+fPnNWfOHPveWLhwoZ2rs1GjRva9YdZ9KlGihKvvp06d0q5du7R79267dx4fPnxYMTEx8vDwUMmSJVWhQgW7lS9f3nVs1qEipI6AWX9h2LBh+umnn1SnTh0NHTpUHTp0kJubW+o0SK0IIIAAAggggAACCNxBAfM7i/k9Y/jw4Ro8ePAdbJmmEEAgowssWbJEI0eO1PLly9WiRQv7OWPWmEksVK9eXZs3b9bbb7+tf/7zn4llSZG4vXv3qn79+rp8+bKioqJcdZrf8cuUKaM9e/bw+75LhQMEEEAAAQQQQACB1BSIiIjQqlWrNH/+fB06dCg1m0q1ug8cOGDXLWzatGmqteGsuFKlSmrVqpXq1q1r75k649nfWYGrV6/aZx/M+zYoKChJje+7fFLvbJiumNjYJOUnEwJ3S6BfpdZqULD83WqedhFAAAEEMoiAW6wjZJCxMAwEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBIZQHzQLv5g57u3bvrvffeS+XWqB4BBBBAAAEEEEAAAQRSQ8D8IW3Lli118uRJFSpUKNlNVKlSRWaCutGjRye7LAUQQAABBBBAAAEEMrZAly5dtH//fm3YsIGJUDL2S83oEEAAAQQQQACBuyrw559/2kn3zbXO5s2b39W+0PjfBUJDQ2UWtvj555+1YMECHTt2TH5+frrvvvvsJCxmIpbChQv/vSAxCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQCICZ8ODFTCtfyIpRCGQdgU83Nw1tlFf9QholnY7Sc+SLRAbG6s//vhDP/74o9327NmjggUL6oEHHlCnTp3UokULeXt7J7teCmRsATNX5y+//GLfM2a+zuDgYFWvXl1nz561xyEhIRYgV65cKl++vCpUqGA353HZsmXl4+OTsZHS8OjWr1+vN998U7Nnz1blypX1yiuv6OGHH5anp2ca7jVdQwABBBBAAAEEEEDgxgKvv/66Pv30Ux0+fFjZs2e/cWZSEUAAgVsQWLp0qUaOHCmzb9asmUaMGKGmTZu6ajLXSNq1a+c6f//99/XSSy+5zlPqYPPmzfaa3eXLlxUVFZVotea6TevWrRNNIxIBBBBAAAEEEEAAgdsV2L59u13Pb9GiRVq2bJnCwsJUrlw5Va1alfW/b4AbHR0ts2blkSNH5Ovrq5YtW7rWQixVqtQNSpKU0gIbN25UzZo17ZqU/v7+Sap+2r7lembFp3I8YkFAIM0KeLi56cVqnTS0Ztc020c6hgACCCCQPgTcHA+W8mNP+nit6CUCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAmlCwPzh+scff2wfxmASgTTxktAJBBBAAAEEEEAAAQSSJTBmzBi98847OnPmTLLKOTP369dP5g8NVq5c6YxijwACCCCAAAIIIICAFdiyZYudrHbmzJnq2LEjKggggAACCCCAAAIIpIpA9+7dtWPHDpnJJAhpX8BcT54/f76dvGb58uWKiIiwk9aYyRODgoLUsGFDZcmSJe0PhB4igAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggMBdETgbHqyAaf3vSts0isCtCni4uWtso77qEdDsVqugXBoTeP311/XFF1/oxIkTKl26tDp16mS3Bg0ayN3dPY31lu6kVYHIyEgtWbJEP/74o7755huFhobK399fAwcO1Msvv5xWu02/HALbtm3TqFGj9P3339vX7Pnnn1efPn2UK1cufBBAAAEEEEAAAQQQSFcCwcHBKlGihIYMGaKhQ4emq77TWQQQSH8CZp2SESNG6LffflPTpk01fPhwNW/eXDVq1NDWrVsVHR3tGtS4ceP07LPPus5v9+D333+366KEhYXFayduvR4eHmrWrJkWL14cN5pjBBBAAAEEEEAAAQRuWeDMmTP69ddftXDhQrt238mTJ+Xn56cWLVrYn09btWqlYsWK3XL9ma3grl27rKXxXLZsmUJCQuz9euNo1kE0v1/4+vpmNpY7Ot5p06ZpwIABunTpUpLbnbZvuQaumKDo2JgklyEjAndawMfDS89Vaa+hNbve6aZpDwEEEEAggwm4xTpCBhsTw0EAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIFUFDh16pSKFy+uSZMmqVevXqnYElUjgAACCCCAAAIIIIBAagj07t1bR44cueU/TJ0yZYr69+8v80f/3t7eqdFF6kQAAQQQQAABBBBIxwIPPvigDhw4oI0bN6bjUdB1BBBAAAEEEEAAgbQqcPz4cZUqVYpnFtLqC3STfl29etVOvjJ//nw7qc3u3bvl4+OjwMBAOwGLmdymbt268vLyuklNJCOAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQGYROBserIBp/TPLcBlnBhHwcHPX2EZ91SOgWQYZEcPw9PTUI488oiFDhqhq1aqAIHDbAjExMVqzZo1GjBihY8eOaceOHbddpREncAAAQABJREFUJxWkvsChQ4c0ZswYTZ48WW5ubnriiSc0cOBAlS1bNvUbpwUEEEAAAQQQQAABBFJA4K233tL777+vw4cPy9fXNwVqpAoEEEDg5gIrVqzQyJEj9euvv6pevXpau3ZtooUmTJigp556KtG05EbWqlVLGzZsSFKx7du3q1KlSknKSyYEEEAAAQQQQAABBOIKREREaNWqVVq4cKHdNm3aJHNvuX79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jz32mPbv32/v/6xZs8aunzd9+nT17dtXxYsXz7xYaWjkFStWtPfD586da++jm/t3Dz30kNauXavOnTvLz89PjRs31ptvvinzGnLPPGkv3t69e1WuXLmkZSYXAggggAACCCCQCQXcM+GYGTICCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgikgMCAAQP07bff6uLFiylQG1UggAACCCCAAAIIIIDAnRAwE0yZP5KtVKnSbTUXGBio1atX31YdFEYAAQQQQAABBBDIuALVqlVTx44dNXLkyIw7SEaGAAIIIIAAAgggcEcFzER7Y8aMUc+ePe3kG3e0cRq7awL33HOPnnnmGft8ypEjR3T48GF9+eWXqlmzpl2c48EHH1SBAgUUEBCg7t27a+zYsfbatZm8loAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAplToHmRKnqwdOBNt5xeWeMBebt7qlb+snq8fAsNr9VNvSvcq0aFKiqLh5e6lm4YL6/zpHC2PGpdrKaGVO+kwdU6qXOpBiqVs6Dy+OTQfUWrO7Ol6D67p49tc0TtR1z1lsxZQP9u1E9FsuV1xd3NAw83d9UtEJBqXSiUNY+aFbnH1p/XJ6da+leL11YOzyyO17GljFGvcs2V1cM7XnrCk7bFa8nH8TrHDe1K1Il7yjECaUagd+/eqly5sp0H/uuvv7bzdd6scxcuXFDt2rW1efNmbdu2TW3atJFZfygzh/3792vChAkaMmSIjh07lpkpGHsKClSpUkWfffaZjh8/rrfeektLlixR1apV1bBhQ5n/r+Hh4Ym29s033yg6Olrjx49Xq1atFBwcnGg+IhFAAAEEEEAAAQQQuB2BSZMm6fLly3rppZdupxrKIoAAArcsMG/ePG3atMn+DnyzSmJiYvTwww9rzpw5N8ua5HSznvdPP/2kdevWqUmTJracWefbhKioKHud7dq1a/acLwgggAACCCCAAAKZT2DHjh32Z8L7779fefPmVYsWLTRjxgw1aNDA/hx5/vx5LV++XK+99prq1asnd3f3zIeUjkbs5eWlZs2a6Z133tGff/6pM2fO2Pt1Zs3Dzz//3L6u+fLlU5cuXTRx4kQd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AQQQQACBfCCQkJCg1157TSNGjJA5f8CEAAII5JXA0aTjZ236WNKJs6abxMTUZG2KiT7n9qwbri1ZTe+0Ha4Gn92vNGcM+MfxA5mbzTimZ7WWmes5XTBjzdOnnMR0JPF/fY1PScpWRYIzTktI/SvJ1LXm6M5s209fSXLyJCnZJqek/13w9Ey5uD5l82xNan+/GgVVPWts2+L+GodeqMk/449eKMt5t5vxuxmbXmgy49jzTbX8y6t3jdYaOufN82VjWwEXCAgIOG8P/P39FRYWdt48F7OxePHiF5M9M++PP/4oM59vqlChgkaNGpUtS7FixbKtmxVvb+8z0kgomAIHEmIyAzfvnRd6/7zax4XM4M6zYN6zT552vEtOu7LHrJwcj7OGbNweXPC+nmp6mx5eOOmM88RxyQlaenBr1iJnXb7Q/jlroSuU+HLzOzV+9XdadujCcV+hEIpUtV5eXva7ss33ZY8dO1aHDh1SRESEnd966y373m3G3J07d1Z4eLi6dOkic38LU+EU2Lx5s933M2fOtPdkmOui1atXt/v+qaeesr8r5rppUZyOHTsm03dz7xcTAggggAACCCCAwIUF3C+chRwIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQO4JmBubatasqbffflvmxicmBBBAAAEEEEAAAQQQyH2BtWvXqkGDBnl2U7X5Q7pff/019ztGjQgggAACCCCAAAJFSuDZZ59V27ZtNWfOHLVv375I9Z3OIoAAAggggAAChVHg888/t19e8sADDxTG7tEnBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAoNALJCYmatmyZVq4cKGdFy1apKNHj8rHx0ctWrRQv3791KpVK4WGhsrPz++sHp06ddLHH3+slJQUubu7Kz09XU888YSeeeYZeXl5nbUMiQgURYEyZcqoT58+djb9//PPPxUZGanZs2drypQpeu6551SsWDGZ74np0KGDOnbsqJCQEPv/qih60WcEEMh9AV9fX/Xv39/OsbGx+vbbb/XZZ59p0KBB9r3mpptu0p133qkbbrhBHh4euR8ANeZYoIpvaTUIqqyfdq/ILHM06bhGLnhXa3pP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RKXUiQACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgjkVMDc5LFv3z59//33OS1CPgQQQAABBBBAAAEEEDiHwI4dO2QeuGcerpcfprCwMEVFReWHUIgBAQQQQAABBBBAoBAItG/fXubh0c8991wh6A1dQAABBBBAAAEECr/Al19+qb1799oH9hf+3tJDBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBA4PIE0tLStHHjRk2ePFn33XefrrvuOvn5+cl898OLL76omJgYDRgwQD/99JMOHz6srVu36r///a9GjBiha6+9Vm5ubpcXAKURQACBcwj4+Pjo+uuv19ixY7V06VIdOXJE3333nTp27KjIyEj16NFDQUFBCgkJ0eOPP65ffvlFJ06cOEdtJCOAAAL/E2jSpIk++OAD7dmzx75/TJs2TdWrV9eQIUO0adOm/2VkCYECLpCclqrYUyezzQW8S4SPAAIIIIAAAldQwJwLNGPthg0bXsFWqBoBBBBAAAEEELh8AXd3d91yyy2aMWOGdu/erUceecRePzCfY8w1zo8++kjx8fGX31ARrmHLli164oknVKlSJd12221KSUnRp59+qujoaL322muqV69eEdah6wgggEDRFEhMTNQdd9yhxx57TOnp6RahZcuWRRODXue6QGxsrL1fLdcrpkIEEEAAAQQQQKCQCrg4H8r/+lReSDtItxBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCD/C3Tr1k0JCQn67bff8n+wRIgAAggggAACCCCAQD4W+Pbbb9WrVy/FxcXJ19c3zyM1D/O74YYbdODAAZUpUybP4yEABBBAAAEEEEAAgYIvYB4ibR4mPX/+fLVu3brgd4geIIAAAggggAAChVigRYsWqly5sr744otC3Eu6hgACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCFyawN69e7V06VI7L1myRMuXL7ffN+Ht7a0mTZqoefPmMs91M681atS4tEYohQACCFwFgSNHjmjOnDkyzxGfPXu2Nm7cKHd3d4WEhNjninfo0EFhYWEqVqzYVYiGJhBAoCALJCcn6+OPP9bYsWO1detW3X777XrmmWdUr169gtytS4p92NyJ+nrHIqWlp19SeQohcLUEfr5ptELL1rlazdEOAggggAAChV7g559/1o033mjPFTZr1qzQ95cOIoDA1RPoPXOsZu75/eo1SEsI5BOB4KAqmt9jTD6JpmiEke6czzLXCiZNmqRvvvlGnp6e6tu3r4YNG2avexYNhcvrZUJCgr788ktrOG/ePFWqVEmDBw/WkCFDVKVKlcurnNIXFKj7yQjtT4i5YD4yIJDXAkcHT5eri0teh0H7V1kgOjpa3bp10/r165WSkpLZ+oEDB1SmTJnMdRYQuBSBxMREe1/H999/r+7du19KFZRBAAEEEEAAAQSKnIB7kesxHUYAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIF8JzBy5Ej7x0ibNm1S3bp18118BIQAAggggAACCCCAQEERWLdunapVqyZfX998EXLLli3l4vzhSFRUlG655ZZ8ERNBIIAAAggggAACCBRsAfOA6LZt22r06NGaNWtWwe4M0SOAAAIIIIAAAoVYYNGiRfZLjP7v//6vEPeSriGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAI5Ezhx4oSWL19un9O2ZMkS+xodHW2/08F8d1vz5s1122232ddGjRrJw8MjZxWTCwEEEMgHAiVLltStt95qZxPO/v37NWfOHM2ePVufffaZXnrpJXl5ecl8l4151njHjh3VokULeXp65oPoCQEBBPKTgPkMNHjwYN1111366quv9Nxzz6lhw4bq37+/fS+pXLlyfgqXWBBAAAEEEEAAAQQQyHWBF198UTfccIOaNWuW63VTIQIIIIAAAgggcDUEzHfad+rUyc5Hjx7V1KlTNWnSJH3wwQcKDg7WsGHDNGDAAAUFBV2NcApUG6tWrbJW06ZN08mTJ9W9e3f99NNP6tq1q1xdXQtUXwgWAQQQQCD3BRYuXKibb75ZcXFxSklJyWygQoUKKlOmTOY6CwhcqsDx48dtUT8/v0utgnIIIIAAAggggECRE+CMTZHb5XQYAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIH8J3D99derevXqevvtt/NfcESEAAIIIIAAAggggEABEli7dq39A7j8EnJAQIDq16+vRYsW5ZeQiAMBBBBAAAEEEECgEAiMHj1av/32m8wfrTIhgAACCCCAAAII5E+B8ePH2y/yCAsLy58BEhUCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCBwhQSSk5O1cuVKvf/++xo6dKj9Hgl/f3916NBBr7/+utLS0nTfffdp1qxZiomJ0YYNGzR58mSNGDFCzZo1k4eHxxWKjGoRQACBqyNQrlw59e3b174Pbt26Vbt377bL1apV00cffaS2bdsqMDBQ4eHheuWVV7R48WKlpKRcneBoBQEECoSAq6urevfurTVr1mj69OmKiopSnTp19OSTTyouLq5A9IEgEUAAAQQQQAABBBC4WIHIyEj7/a9PP/30xRYlPwIIIIAAAgggkC8FgoKC9NBDD2nt2rX2WkCLFi00atQolS9fXv3799fs2bOVnp6eL2O/WkHFxsbqnXfesdeJmzZtar+r1RhFR0frq6++0g033CBzvpQJAQQQQKBoC3zwwQdq166dvc8o67V1Nzc3tWnTpmjj0PtcE8i4DluiRIlcq5OKEEAAAQQQQACBwi7AWZvCvofpHwIIIIAAAggggAACCCCAAAIIIIAAAggggAACBUDAxcXFPrRoypQpio+PLwAREyICCCCAAAIIIIAAAvlTwPwRXHBwcL4KLiwszD6AIF8FRTAIIIAAAggggAACBVrAPCTf/GHq6NGjC3Q/CB4BBBBAAAEEECisAjt37tS3336rRx55pLB2kX4hgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACViAlJUVr1qzRRx99pPvuu0/NmzdXiRIl1KxZM/3zn//Ujh07dOONN+rzzz/Xnj17tHfvXn3zzTd66qmn1KlTJ/n5+SGJAAIIFHqBSpUqaeDAgfrPf/6jXbt2afv2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e02zZ8+2ncw6ImCCRAABBBBAAAEEEHCsQO3atVWoUCEtWLDAsTkQOAIIIIAAAggg4I4CZhCLsmXLavLkyTId5TEhgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggED6C1y8eFG7d++2ff/v2LEjeWn2Xbp0yQZUsmRJVaxY0c5BQUEy87333qs8efKkf8C0iAACCCCAQCYScLlcWrNmjcLDwzVr1iwdP37c/r0NCwtTaGioKleunImyJZVbFTDjra5fv97+fMycOVOHDx9WQECArv581KhR41arpHwGCWzcuFFvvfWW5s2bZ6+jX331VT366KPy9vbOoIhoFgEEEEAAAQQQQMCpAm+//bY+/PBDHTp0SH5+fk5Ng7gRQCCTC5jPl7755huNGjVKkZGRqlevnvr27at27drJy8vLUdkPGjRIw4cPt+MYtmzZ0lGxEywCCCCAAAIIIIBA1hPo2rWrlixZYp//yps3b9YDIOMMF4iPj1f27Nn11Vdf2c9DMzwgAkAAAQQQQAABBBwi4HHlYcEkh8RKmAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIJCFBBISEjR37lyNHj1aq1evVrZs2WQeEPnjdPbsWTqi+iMK2wgggAACCCCAAAJZRmDnzp224zjzhdrq1as7Iu8FCxaobdu2+uWXX5Q/f35HxEyQCCCAAAIIIIAAAs4UWLx4sR544AH99NNPCgkJcWYSRI0AAggggAACCLihwMCBA/XZZ5/ZjklNJw9MCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQdgIxMTHasWPHn+YDBw7I5XLJ29tbAQEBduyCihUrJi8rVKig3Llzp11g1IwAAggggEAWFXj11Vdt35wnT55UUFCQwsLCFBoaqkqVKmVREdL+O4H//ve/Cg8P18yZM2Wu4cqWLauXX35ZvXv3/rtTOe4mAlFRUXrvvff09ddfq3jx4urTp4+6deumvHnzukmEhIEAAggggAACCCDgzgLnzp1TmTJl9Pzz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AggggAACCCDgHAHTgXC1atX05ptv2g5znBM5kSKAAAIIIIAAAukjYO5BLlu2TIsWLUqfBmkFAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDgtgWSkpL066+/6sSJEzp58qRdmvW/2jblXC5Xclt58+ZVsWLFVLx4cbs0/eCb7WvnokWLKkeOHMnnsIIAAggggAACCCCQsQJbt25NDuDo0aP66aefNG/ePLuvatWq6ty5s2bNmqXDhw+rVKlSdv/atWtVrlw5Pfvss3b7/fff1+LFizVt2jSFhITYfeY6cd26dTLHvLy87L6YmBidP39e5prTw8PD7lu+fLk++OADPfnkkxo7dqzdl5iYqO3bt9txlRo2bGj3/d0/ly9fVosWLfTPf/5TsbGx9jrVjGdqrk2vtmXqMO2byVzLFihQwK6fO3fOLr/66is99thj2rx5s1avXq1Lly7JHDPxmnpNjC+88ILNM1u2bPr666/tecajWbNmyfXZnfyDQAYIeHp6ysfHx87XNu930F/el/PJdeVnmQkBBBBAAAEEEEDA/QVWrlxpx2Y147MyIYAAAn8nYO63mHsyEydO1LZt21SzZk27bu5x5MyZ8+9OzzLHT506Ze8d+fv723tf5v0zEwIIIIAAAggggAACThIYPny4oqOjtXTpUieFTaxZUCAuLo73o1nwdSdlBBBAAAEEELhzAe87r4IaEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIHUEzBfUOjSpYuioqL09ttvq1+/fjJfZnenyXTyYDrM6N+/vx599FGFh4dr/PjxKlSokDuFSSwIIIAAAggggAACmVjAXDd37NgxU2RYr14928lBpkiGJBBAAAEEEEAAAQQcJfD666+rffv22rJli6pUqeKo2AkWAQQQQAABBBBIa4GRI0eqYsWKatWqVVo3Rf0IIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJ/IRATE6PTp0/r1KlTdnmjdXPczAkJCcm1ZMuWTYULF1aRIkXsXKpUKdWqVSt5++qxYsWKydfXN/k8VhBAAAEEEEAAAQScIXD8+HF169ZN/v7++vbbbzVt2jQ1bdo0OfiJEycqd+7ceuCBB/TKK6/Ya8VFixbpu+++U/bs2W258uXLa/ny5erUqZMdM7Rx48aaOXPm/2PvPuCrKhI9jv/TAwkphCSU0HvoIF1EXMTKWuigiArLovKehdW10IvI2hDQxQcWUFFCWQsiYgHRUBRYmvQWAqEGSEglCS8z7r2bQCKipNzkd/yMM2dmzpTvTcIt585o5MiRuv/++5WamirTzqpVq5SSkqIxY8bo4YcfVmxsrO68804lJSVp7dq1zj5NwtfXV4cPH86V92snZq/SmjVr6pFHHslVLTAwUC+//LIeeOABzZ49W4sXL7blDz30kN3j1Dy3bdKkiS2fM2eOWrVqZfcXnTZtmvr376877rjD7jM6YMAAGavRo0crKChIjRs3Vt++fRUSEqILFy4oJibGpnN1zgkCCCCAAAIIIIAAAggg8DsExo8frxtuuEHt27f/HVdzCQIIlBaBDRs26I033tC8efPslPv16yfz3kbLli1LC8FvnmdiYqJuueUWZWVl6YsvvpB5v4gDAQQQQAABBBBAAAFXEti7d68mTpyosWPHqnr16q40dMZaCgXM/QFlypQphTNnyggggAACCCCAwB8TcMu+EfHCH2uCqxFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBPy5gFh+bMmWKvVnJfEHh7bffVoMGDf54wwXcglnwwiycYRavmDFjhnr37l3APdI8AggggAACCCCAQGkXOHLkiKpUqaIVK1aoc+fOLs/x0ksv2dcCx44dc/m5MAEEEEAAAQQQQAAB1xIwt9Kb96Nr166tBQsWuNbgGS0CCCCAAAIIIFCAAmYDMbNJ2Guvvaa//OUvBdgTTSOAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACJV/ArIF55swZnTp1KleIj4/PdW7KT548qRMnTtiQnp6eCycgIEChoaE2hIWF5UqHh4erYsWKzhAcHCw3N7dc13OCAAIIIIAAAggg8McFzHM7d3d3u7Z5jx49/niDV9jC0aNHValSJU2cOFGPPvqo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li3tGva33npr3heRi0ARCSzav1orjmzVW9cP1/VVmmh86wG6c9mkPzyatKyMS9o4/5+8sp4+l5TlzEjPzPtaP6//XheXfFpdKjfRzdVa6oe47dqfcEzNK9RyNnPhwgWbvqBfYmfBryRah9bVXxvdrLm7vtXXhzc5azYIjtDR5DMasfptZ97lEsE+/vLI/jwlJSM9V9X6wVXsedL51Fz5q4/usOf1A38pz1WY42TP2Th7ZuY+vPFtquYfavuY0m6QwssGKTUzXWNb99e204c0a/uXmtR2oDae3Kdbq7VytlI7oJJ8PLzVvXprnU1P1ndx25xljsSdNdvpkwPrHKeXxEkZqarsV/6SfDIQQAABBBAorQLmnpopU6ZoyJAhCg8PL60MzBuBAhX4+eef9f777+uDDz6w90A1btxYI0aMUP/+/dlXpUDlr37jEyZM0KRJkzR79mz169fv6ndAiwgggAACCCCAAAIIFKHAkiVL7P7KX375pXx8/vu5RhEOia4RuKoCaWlptj3zHTQOBBBAAAEEEEAAgSsT8Lyy6tRGAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBIpW4KmnntI999yj4cOHa+7cufLy8tJHH31kB/X999+ra9euOnfunD03iyv07dtXmzZt0nfffSdzk4kpM1/4rlSpko4ePWoXwTXnFStWVFJSkuLi4vT555+rTZs2ev311207R44c0ZkzZxQUFFS0k6d3BBBAAAEEEEAAgVIpkJCQoJiYGJkvMZeWo0OHDnr66aftBiTVq1cvLdNmnggggAACCCCAAALFWMBsijZy5Ejde++9Nq5Xr14xHq3rDi02Nlb/+te/7JeiV65cKU9PT/u+/2uvvabbbrvNvrfvurNj5AgggAACCBS9gPncfP369ZoxY0bRD4YRIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIFAsBb29v1a1b14a8BmTWb9+/f78z7Nu3z6Y//fRTGzvWhTfrxVSrVk21atWyoWbNms60yStfvnxezZN3GQGzjr5ZP9/hbuKcwZSZOuYICQmRw/2mm26yace5eWzMY82BAAIIIIAAAggggAACCCCAAAIIIIAAAggg8McEzD5SCxcu1MaNGzV6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ly9fwJaohgACCCCAAAIlJdCsWTPVqlVLH330UUl1ST8IIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIFLvAsWPHtGfPHsXHx1u5KdvTiRMnrP5tNptq1qypqKioPFOlSpUui/Po0aOOduzt2XMzP6bZvLy8rGf5c7YbGRnp6KNixYqXtcsBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQTsAllZWVq2bJkmTpxofSbdv39/Pf/88woPD7dXIUdAgzbO0or4OGVdXCOAzfUFynh5q5yPX66BnE//Lde+q+6s7TxBzUM9b61qV329iBsBBBBAIH+BtWvXqlOnTvr222/VuHHj/CtyBgE3F0hJSZH5fli+fLlWr16txMREtWzZUr1791avXr1UvXp1NxdgeJcKfPLJJ9ZrP3DgQJm1ts19eWwIIIAAAggggAACCLijwIULF3T77bcrLCzM+r3IHcfImBDIS2Do0KH69ddftX79+rxOcwwBBBBAAAEEEEDgCgK27IvbFc5zCgEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBAoEoEdO3ZowIAB2rlzpyZPnqyRI0dak+MWSeNO3Ih5qGXUqFGaM2eOoqOjrYcaqlat6sQRExoCCCCAAAIIIICAMwk8+eST2r59u/75z386U1glFss333yjpk2b6qefflK9evVKrF86QgABBBBAAAEEEECgIAKZmZmqX7++WrRoocWLFxfkEpet88svv+jdd9+10r59+3TrrbeqX79+euihh1SjRg2XHReBI4AAAggg4O4CcXFx1iSMmzdvtt6zuPt4GR8CCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggID7CKSnp2v//v3as2eP4uPjrdxeNvvJycnWYP38/BQREaGoqChFRkZauSmbZI6XLVu2yFASEhIccZhY7PGY/ODBgzLzEpktODg4Vyw546pZs6ZHzNFfZOg0hAACCCCAAAIIIIAAAggggAACCCCAAAIIuLGA+Vx5yZIlmjRpkg4cOKBHH31UY8eOVVhYmBuPmqEVVGDQxllaER+nrOzsgl5C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CCCAwPUKmIetq1SpoqpVq15vUy59ffPmza2FLLZs2WI9bO7SgyF4BBBAAAEEEEAAAbcU8Pb21rhx46zPiU1uFrtz5m337t2aO3euFi1apDNnzuiee+7R+++/b03Yw+JwzvzKERsCCCCAAAIFF1iwYIGysrI0ePDggl9ETQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAY8USE9P1/79+7V3717t27fPyk3Znk6ePOlwMXNkRkREWKlLly5WbubcMSksLExmPh62ohWw2WyqUaOGlVq1anVZ46mpqdbrFh8fb71m9nzdunXW/vnz561rvLy8VLNmTcfrFx4enqtcq1YtmTpsCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAs4j4O/vr2eeeUZDhgzRzJkzNX36dCsfOXKkhg8frgoVKjhPsESCAAIIIIAAAggggIALCmzbtk1r1661kguGT8gIOASys7O1detWrVy50kpmnbJq1apZ65K99NJLatu2rVifzMFFIR8Bc89Z9+7ddf/992vhwoXcT5aPE4cRQAABBBBAAAEE3EvghRde0G+//aZXXnnFvQbGaBAohEBaWprKli1biCuoigACCCCAAAIIIGAXsF38I022fYccAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEChNgR07dmjAgAHauXOnJk+eLDMpgTNPNJuYmKhRo0Zpzpw5io6O1uzZs2UmPmZDAAEEEEAAAQQQQKAoBB577DHt2rVLGzZsKIrmXLqNunXrWg8PT5kyxaXHQfAIIIAAAggggAAC7iuQmZmpevXq6fe//70WLFjgdAO9cOGCVq1aZX2evX79epmF3gYPHqxHHnnEKjtdwASEAAIIIIAAAtcsYN6X3HDDDTIL9b7xxhvX3A4XIoAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIuIdAenq6Dh48qH379uVKe/futfaPHDki+9K2FSpUUEREhCOFh4c7yuZ4QECAe6B40CjOnDkj81rnlfbv36/U1FRLo0yZMgoLC7Neb/O6X5pq1Kjh1OsGeNBLylARQAAB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z2azKTQ01Jqvsnbt2qpTp85lqWLFigXum4oIFIXA8ePHtX//fivt27fP+jo2++Zr16SzZ886ujFfn+ZrNywszJHs+yavWbOmfH19HfUpIIAAAggggAACCCCAAAIIIIAAAggggAACCBRM4MSJE5o6darefPNNBQcHa+zYsRo4cCCfuRaMz2lrDdo4Syvi45SVne20MRIYAkZgbecJah5aFwwEEEAAAQRcSsCsL1W/fn2999576tOnj0vFTrDuL5CVlaW4uDh98sknVvrll18UEhKiTp06WeuRdejQQYGBge4PwQiLXWDFihV64IEHrM8QZs+eLXOPIhsCCCCAAAIIIIAAAp4gYP62Vq9ePeu98PTp0z1hyIwRgSsKmGfUhg0bpmefffaK9TiJAAIIIIAAAgggcLmALfvidvlhjiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACziOwY8cOPfLIIzL5mDFjrJunSjq62NhYzZ07V9HR0TIPMFStWlWffvqpunfvrpdfflmjR48u6ZDoDwEEEEAAAQQQQMCNBT777DN17NhRZ86cUaVKldx4pAUbmrnFyUxMNnnyZD311FMFu4haCCCAAAIIIIAAAgiUgkBGRobq1q2rdu3aad68eSUSwebNmzVr1iwtX75cZnE183n6Y489pqioqBLpn04QQAABBBBAwHkEzKR8u3bt0rZt25wnKCJBAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBDxUICkpSYcOHXKkgwcPXlY+e/asQycgIEC1a9dWWFiYI5n9OnXqOI77+fk56lNAwBUEEhMTdeDAgcuS+X6wf0+kp6dbQ7HZbAoNDVWtWrUcyXw/2PdNuUaNGuL7wBVeeWJEAAEEEEAAAQQQQAABBBBAAAEEEEAAgdIQOHr0qLW+qll7tVq1aho3bpwGDBggHx+f0giHPq9TYNDGWVoRH6esi+t3sSHgzAJrO09Q89C6zhwisSGAAAIIIHCZgHmfvHXrVv3444/y8vK67DwHEChpgXPnzunzzz9XTEyM1qxZo1OnTunGG29U165drdSyZUt5e3uXdFj058YCH374oR566CE9/vjjmjlzpsy9W2wIIIAAAggggAACCHiKQL9+/bRx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K01KStK5c+fyTWfPns11zuyfOXPGSubaS7eAgACFhIQoODjYSnmV7cfsualbpkyZS5tiHwEEEECggAIpKSnWz2UzZ635GW3P8yvbz6elpeXqwcxxFRQU5PgZXqlSJVWsWDHPdOk5c125cuVytccOAggggAACCCCAAAIIIIAAAggggAACCLiXQFxcnMaPH++Y73by5Mlq0aKFew2S0SCAAAIIIIAAAgh4pMDzzz+vN998U/v375e5/4kNgasJnDhxQv/4xz+0bt06ff755zp+/LiqVq2qe+65x1qPzORmnw0BZxUYOXKk3njjDS1YsEAPP/yws4ZJXAgggAACCCCAAAIIFLvAsGHDtHTpUv3yyy/WffTF3iEdIOBiArt379aNN96o7777Tg0bNnSx6AkXAQQQQAABBBAofQGf0g+BCBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAAB9xN4+umntWvXLvXr109hYWFq2rSp+w2SESGAAAIIIIAAAggUi8DOnTt11113FUvbrt5oRESEqlWrJjPRWKtWrVx9OMSPAAIIIIAAAggg4AECZtHDsWPH6sknn9SYMWNUuXJlLVy4UK+99po1mVh0dLQ1sViTJk08QIMhIoAAAggggEBRC5j3FLfddpvatWtX1E3THgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCJSiQEZGhhITE/NNCQkJMun8+fOOPGfZfs60kZWVddlI/Pz8VLFiRStVqlTJUa5bt67MfnBwsCOFhITkKvv6+l7WHgcQQAABBIpXwN/fXzVr1rRSYXr67bffdPr0aZ05c8ZK9rLJz549a6Vz585p7969MnnOZP4tunTz8fFRhQoVFBQU5MjzKps6gYGBVh2TX5rKly8vLy+vS5tnHwEEEEAAAQQQQAABBBBAAAEEEEAAAQRKWaBFixaKjY3Vxo0bNX78eLVs2VIdOnTQpEmTWJO1lF8bukcAAQQQQAABBBC4dgFzX9XMmTM1atQoBQQEXHtDXOnWAuYei02bNlm/E61fv17ff/+9zH0S5vei4cOHW78bNWzYUDabza0dGJzrC2RnZ+uJJ57Q/PnztWTJEvXp08f1B8UIEEAAAQQQQAABBBC4RoFt27Zp9uzZ1lrS5lkpNgQQuFwgNTXVOli2bNnLT3IEAQQQQAABBBBA4KoCtosfzGdftRYVEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEECi2QmZmprl27ytwItnXrVoWHhxe6DS5AAAEEEEAAAQQQ8CwBszCLmQDfPEgwYMAAzxp8AUcbHR1tLWCzcuXKAl5BNQQQQAABBBBAAAEESlfgwoULioyMVGhoqPbt2yczUdQjjzyikSNHKioqqnSDo3cEEEAAAQQQcFmBI0eOWH+Dnjdvnh5++GGXHQeBI4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDgygIpKSnWfCJmThF7Sk5OzlVOSkqSPZlzVyonJibKJNNuflu5cuUUGBioChUqKCgoyJHnVzb1KlasmCuxIHx+uhxHAAEEELALmH+vzp0750jnz5+XSQkJCbnynMfsZVPH/HuWlpZmby5XbrPZZP49M/9Gmbl4c6aAgADHfl5lc8wkc/2lyRz39fXN1Rc7CCCAAAIIIIAAAggggAACCCCAAAIIIHDtArGxsRo/fry2bNmiLl26aOLEibr99tuvvUGuRAABBBBAAAEEEECgFASmTJmiV155Rfv377futyqFEOjSCQUyMjL09ddfa/369VbaunWr0tPTdfPNN+uuu+7S3XffrbZt21r3MDhh+ISEQJ4CmZmZGjhwoN5//3198MEH6tatW571OIgAAggggAACCCCAgCcIZGVlqWnTpta95xs3bvSEITNGBK5J4Ntvv1WTJk0UHx+viIiIa2qDixBAAAEEEEAAAU8W8PHkwTN2BBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQKA4Bby9vbVs2TK1atVK9913nzZv3szDYcUJTtsIIIAAAggggIAbCJibos1CL7fccosbjKZ4htC8eXNNnz69eBqnVQQQQAABBBBAAAEEilhg9+7devXVV3X8+HEdOnRIw4cP19ixY1W5cuUi7onmEEAAAQQQQMDTBGbNmqWQkBA9+OCDnjZ0xosAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgEMgPT1dl6a0tDSZ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+/fv1759+7R3797ss6O+Z88ee9/0MZuI5DyqVKmi2rVr21KrVq086zVr1lSNGjVk+nIggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIuJZAZmamTL71c5Xjx49n93HUHWfzrKnnLKbN5MXM7/D29rb5TwMCAmRK5cqV7dlRN9eONkc9r+vAwEDbr1KlSvlNRbsLCJi8qC+//LKioqKUmpqqESNG6OGHHybnqQv87AgRAQQQQAABBBBwRQHze1Dr1q114YUX6s0333TFJZTLmLOysrRlyxatWLEiu8TExMjd3V1t2rRRjx49skvjxo3LpQGLQuB8BZYuXaqBAwfqsssu04IFC+Tj43O+Q/I8AggggAACCCCAAALlRuDdd9/VHXfcoZUrV6pbt27lZl0sBIHSEHj66af19ttva+vWraUxHXMggAACCCCAAALlTsDt1AdhWeVuVSwIAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEHARgXXr1ql379669dZb9frrr7tI1ISJAAIIIIAAAgggUBICt9xyi0yyq0WLFpXE8OVuTJMUbOzYsTpy5Ij90n+5WyALQgABBBBAAAEEECgzgYSEBL344ot6/vnndeLECQ0bNswmoa1du3axxXTy5Ek1a9ZM1113nV544YViG5eBEEAAgbikw/r54DYgECiyQICnj/qEdCjy8678oHlvbsaMGZoyZYr8/Pw0fvx43XPPPfL09HTlZZ0R+4EDBzRx4kT7+XRoaKhNvH/ttdee0Y+GshMwrxX37NljS3x8vPbu3WvLvn37ctUPHjyonF8PNRtl1K1bN7vUqVNHppjXsabUqlUr+2w24uBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQKAoAiaH+/Hjx88ox44dy27Lq27aHO2OujknJSXlyrOZMyYvLy+ZvJumBAYGZtdzXjvazfls9UqVKuUcmnopCpifscnLP23aNGVkZOihhx7SyJEj7c+rFMNg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AAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCBQvgTS0tKUmJiohIQEe86v7rif85yznp6enidMQECAgoKCbAkMDMyuO9pOP+fVx8vLK8+xXaXx8OHDmjZtml588UVVrlzZ5si97777VKlSJVdZAnEigAACCCCAAAIIOIlA165d7R4Bn3zyiZNE5PphmNfra9euldnjY/Xq1frpp5+0f/9+eXh4qG3btgoLC8surVq1ktmrgQMBBM5fwOyTMmzYMM2ZM0evv/667rrrrvMflBEQQAABBBBAAAEEEChnAubzN/NegNlv8euvvy5nq2M5CJSewN133634+HgtXbq09CZlJgQQQAABBBBAoBwJuGWdOsrRelgKAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIOCyAhkZGerXr5/9MtSqVavUqFEjl10LgSOAAAIIIIAAAggUTuDjjz/WTTfdpKSkJPn4+BTu4QrcOzIyUvPmzVNMTEwFVmDpCCCAAAIIIIAAAoUVOHnypE2K89RTT+no0aN64IEHFBERoRo1ahR2qPPuf+LECTVr1kwDBgzQrFmzzns8BkAAAQSGLZ+t+dt+VGZWJhgIFFng1xtnqlHlWkV+3pkf3LFjhx577DEtWLBA4eHhNrF89+7dnTnkUo3NfGZtkgdOmDBBx48f15gxYzRq1Cj5+fmVahylPdmxY8e0e/fufEtcXJwcmzSYhKV16tRRSEiITR5rzqfX69WrJ09Pz9JeBvMhgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAEwokJycrMTFRCQkJtuSsn60t572UlJQ8V+br66ugoKBcpUqVKrmuT79vrnP2cYZcqgcOHFBUVJRmz56tatWqady4cbr77rvl7e2d57ppRAABBBBAAAEEEEAgp8DSpUt19dVX6+eff1aXLl1y3qJeQIH9+/dr7dq1WrdunS2mbvZxMEeDBg3UrVs3hYWF2WKMy/s+FgVkoxsCxS5g9rUbPHiwvvjiC82fP1/9+vUr9jkYEAEEEEAA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nTE2DQgggAACCCCAAALlW8C8PjTHt99+W74Xeo7VHT58WL///rvWr19vz6a+YcMGpaSkyLzebt68uTp37qwuXbrYc6dOnexr6nMMy20EECgFgc2bN+uqq66Sn5+fli5dKrPPCgcCCCCAAAIIIIAAAgjkLbB48WL17dtXX3/9tfr06ZN3J1oRQKDAAhdffLHatGmjl156qcDP0BEBBBBAAAEEEEDgfwKe/6tSQwABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQcBYB8yWqefPmqX///mrWrJkiIiKcJTTiQAABBBBAAAEEEChmAZPM6o8//rB/KbqYh64Qw4WHhys6OrpCrJVFIoAAAggggAACCBRcwHyZNzIyUr/++qtuuukmffTRR2rZsmXBByjlnkOGDNGUKVMUFRWlGTNmlPLsTIcAAggggED5EzB//i9atEjmz9jx48erdu3a5W+Rpbii0NBQ+3pq1apVGj16tMLCwvTuu+9q8ODBxRKF2TQgNjZWO3bsyLMcP37czuPl5aUGDRqoUaNGtlx00UX2bBIfmrZ69erZxKXFEhSDIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAII5BKoVKmSzfd7Pjl/k5KSlJCQkF2OHj2aXc/ZburmXs2aNZWVlaU///xTc+fOtSVXUP+9cHd3V2BgoIKCgs5Z8utXuXJlctzmhUsbAggggAACCCDgpAJmL9Nly5bZ4qQhFntYaWlp2rx5s37//XetX7/enk19z549dq4aNWqoffv2Mnu9Dh061NbbtGkjX1/fYo+FARFA4PwFVqxYoX79+qlVq1b67LPPVK1atfMflBEQQAABBBBAAAEEECinAsnJyRo+fLhuvfVW9enTp5yukmUhULoCKSkpvG9UuuTMhgACCCCAAALlTMCznK2H5SCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAAC5Uagb9++mj59ukaNGqWmTZtq4MCB5WZtLAQBBBBAAAEEEEDgfwJ//PGHzBfwW7du/b9GagUWMEkJ3nnnHWvo5eVV4OfoiAACCCCAAAIIIFA+BX744QeNGTNGq1at0nXXXac3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bFue/ezT3f6uYL9qmrj2AwV6+enl3kM1qv11un3Z8zpy4nh2v3NVWlUJ0RX1O+m53z49o+u5YrmxSQ9N636nKnl4q021+vJy91Rt3yoa1eE63dKsl65cPEHGPik9VZuP/Kk+p9a69M91iknYq551W+vbuN9s+6HUY2fMbRourN1cWaf+t/pAzBn3f9r/hx7uMECf71pzxr2zNVS6NFQrn52vlrUbyvwZVNaHv7//GSG4ubnJFPM64uabb9bQoUNl3hcqi2P9+vXZ08bHx2vNmjX67LPPbNuUKVO0ZMkSmUR/Xbt2tW3mddDKlStl7nl4eKhDhw664447tHDhQu3evVsNGjSw/X788UeFhobq3nvvzR4/ISFBSUlJyjr175VZf1pamm644QZ17NhRH3zwQXY/8/7YDz/8YOfObqSCAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALnFPD29rZ5j++66y69+uqrmjx5sj0PHz7c7htQvXr1c45BBwQQQAABBBBAAIHiE5gxY4YqVaqkYcOGFd+gjIQAAgicp4DZP2bcuHH2d8bRo0fbvWjc3d3Pc1QeRwABBBBAAAEEEECg4gr89ttvmjVrlmbPns3ezRX3HwNWXsoCycnJMp+Nssd8KcMzHQIIIIAAAgiUKwHPcrUaFoMAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIVHCBESNGKCYmRrfddpvq16+vbt26VXARlo8AAggggAACCDivwKZNm9S6dWvnDdAFI+vYsaN8fX0VHR2tli1buuAKCBkBBBBAAAEEECifAl9//bUeeughbd26VSYpamRkpKpUqVI+F1tCqxo6dKimTp2qadOm2XMJTcOwCCBQwQXei/lej3W+QX0bXqDGlWtr57H9VmRYm6s1a/3ifHWGtLpC38b9Zu/vPn5I6/+K1VX1O2vulm9s2/H0VN39/Qv64brJGtmun5b8uVY/7N2oT3auynNMP09vNQioofoBNdW5RhON7Xyj1h/epSH/flFe7h6ae/GD9tnPd62xz7+44Qt1qN5Ys3rcq18O7dDWo/G6pVkv3db8ErWdP1yJaSm23x3L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nAcEBMjLyyu1HwcIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJ3V6BGjRoya0Ps2rXLWmvr8ccfV8OGDa3cyK1bt767wXF3BBBAAAEEEEDgDgskJSVp0qRJ6ty5sypVqnSH787tEEAgPwqYdUKef/55rVy50vo9bOTIkTJrxLEhgAACCCCAAAIIIIBA7gj89NNPmjNnjhYuXCh/f//cuQmjIoBAlgQuXrwob2/vLPWhMQIIIIAAAggggMC1Aq7XnnKGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQXwTatGmjGTNmaNCgQapQoYI6dOiQX6bOPBFAAAEEEEAAgbsqEB4erkceeeSuxpAfbl6/fn25uLgoLCxMHTt2zA9TZo4IIIAAAggggMBtCxw5ckQvvfSS1qxZo169eumNN95QkSJFbntcBsh5gTfffFMeHh6aMmXKNYObr2G5cuWuqeMEAQQQQAABBDIn0KpVK23fvl3Lly/XqFGjtGLFCg0cOFDDhw+Xr69v5gax81bmtUkzn9WrV8skkN+xY4dq165t51Hnj/C8vLysRLa3SmYbHR2tkydPWuXUqVPX7M1zwa1bt1p158+fl0mSm7KZ8YsXL66AgABrf/1x2vPChQundGOPAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQiwImR/AXX3yhbdu2acyYMXr44YfVtGlTTZgwQS1atMjFOzM0AggggAACCCBgPwLm+dCePXus9SLsJyoiQQCBvCpw8OBBPfXUU9b6HuvWrbPWcMmrc2VeCCCAAAIIIIAAAgjYg4DNZtOLL75ovf/Rs2dPewiJGBBAIFkgJiZGPj4+WCCAAAIIIIAAAgjchoDrbfSlKwIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgIMLDBgwQOZDKj169FDZsmVVv359B58R4SOAAAIIIIAAAvYtYD6csH//fg0aNMi+A80D0Zk/NK9Ro4a2bNmijh075oEZMQUEEEAAAQQQQCD3BBISEjRjxgyNHz9e5cuXV2hoqJo1a5Z7N2TkHBGYPHmyPDw8NG7cOGs8Z2dnHTlyhK9djugyCAIIIIBAfhVwcnJSt27drNeT3n33XZmft++//75effVV9e3bV25ubg5Jc+LECY0dO1aLFi1SzZo19fXXX1tJ5B1yMvk8aF9fX5lSuXLlm0rExcXp9OnT15QzZ86knh86dEhhYWHW+blz52Reu07ZzPd5QECAVYoWLapixYrp+n3aOn9/f7m4uKR0Z48AAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIJBFgQcffFBr16611twyOZFbtmyphx56SBMmTFDjxo2zOBrNEUAAAQQQQAABxxKYNGmS2rVrZ61B6liREy0CCDiawKpVq9SnTx9VqlRJO3bsULly5RxtCsSLAAIIIIAAAggggIDDCcyePVs///yzdu/e7XCxEzACeVXArHcZHx8vb2/vvDpF5oUAAggggAACCNwRAdc7chduggACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggYLcCs2bN0uHDh9W2bVtt3bpVgYGBdhsrgSGAAAIIIIAAAo4u8Ntvv+nq1auqXr26o0/FIeIPCgpSWFiYQ8RKkAgggAACCCCAwN0S2LJli1544QUdPHhQo0aN0tChQ+Xu7n63wuG+WRQYO3asPDw8NGLECNlsNh05ciSLI9AcAQQQQAABBNITMD9fX3nlFfXu3VuTJ0/WsGHDZN5XNcedO3eWk5NTet3sri4mJkZTp07VzJkzVbRoUS1atEjdunWTs7Oz3cVKQDkrYJ7Tly1b1iq3GjkxMVF//PGHTp8+bZUzZ85Y+7Nnz8oUc808zzR7cx4ZGamkpKTUYc2/B39/f+t7rFixYta+SJEiVp2pT+/Y1Hl6eqaOwQECCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIDUqFEjrV+/XqGhoRozZoyaNGmiRx55RBMmTFD9+vUhQgABBBBAAAEE8pzAunXr9NNPP+m9997Lc3NjQgggYD8CsbGxGjRokObOnavg4GDNmDHDWv/NfiIkEgQQQAABBBBAAAEE8qbAyZMnNXr0aA0ZMkRVq1bNm5NkVgg4oMDFixetqH18fBwwekJGAAEEEEAAAQTsR8ApeaHv/630bT9xEQkCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggMAdFDB/jGOSAiQkJGjz5s3y8/O7g3fnVggggAACCCCAQP4R+OKLL/Tkk08qOjpa3t7e+Wfid2mmH3zwgXr37q2oqCgVKFDgLkXBbRFAAAEEEEDAngWuXr2qDRs26PLly/YcZqZj8/Ly0kMPPZSp5z6RkZEaPny45s+fr1atWmnOnDmqWLFipu+V1xteSojVht93y+Ygf27/5YIVWjRxlh7q+IT6Tx+d17882Zqfi5OzWpWpJU8X92z1zy+d4uLi9M033+jKlSt2N+VSpUqpcePGdhdXbgW0f/9+ubu7q2TJkpl6XE+Jo+8Pc7Xy103Jj1+2lCr2CGRZYFenWQr0Cchyv7zc4dixY3r11VdlXm+qV6+epk2bphYtWtjtlOPj4zVv3jwrIbx5D3jkyJHq378/iQvt9ivmWIGZ76lz587p7Nmz+uOPP27Ymzpz/fz586l785r49R9lNa/Z+vv7q0iRItbeHJtSqFCh1GL+dqFw4cKp5ynXeH3dsb5niBYBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSyJ7B+/XqNGTNGW7Zs0RNPPGHlHa5Tp072BqMXAggggAACCCBghwJNmjSRr6+v1qxZY4fRERICCOQFgQMHDujpp5/W4cOHFRISoo4dO+aFaTEHBBBAAAEEEEAAAQQcQsA8F9+2bZv27t0rT09Ph4iZIBHIDwJHjx5VYGCgfvzxR9WvXz8/TJk5IoAAAggggAACuSLgmiujMigCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIBDCXh7e+urr75SgwYN1KlTJ+uDcq6uvJXkUF9EgkUAAQQQQAABhxAIDw9XuXLlZJ5/seW+QFBQkOLi4rR9+3Y1btw492/IHRBAAAEEEEDAYQR2796tBQsW6IMPPtCFCxccJu7MBFq4cGF1795dvXv3Vq1atdLtsnz5cr388svWNWPQtWvXdNvl58pPf9ui/pvmOQ7BPZJn7wba6nFB2ze+5Thx3+FIQ1r0V8d7g+7wXR3jdjabzXpMHDt2rI4cOWK3QTdv3lyTJ0+W+X0vL28xMTGqUqVK6hS9vLxUvHhxlSpVynpdoWTJkjKlRIkS1j7l2PwMYEMAgdwRMK/pLVmyxHoONXToULVs2VKPP/64pk6dqurVq+fOTbM56qpVqzRy5EgdP35c/fr106hRo8TjQzYx6ZaugPlbAvNzyZTMbomJiTp//nxqOXfunHWcsjfXzLH5vv3ll18UGRlplaioKMXGxt5wGxcXFxUqVCi1mITApvj4+FyzT68ubRvzWr2zs/MN41OBAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCNiDQKtWrWTK2rVrNWbMGNWrV09PPvmkxo0bp5o1a9pDiMSAAAIIIIAAAghkW+Bf//qXNm/ebJVsD0JHBBBA4CYCH374oV588UVrPagdO3bo3nvvvUlrLiGAAAIIIIAAAggggEBOCnz99df66KOPtGbNGnl6eubk0IyFAAK3KXDx4kVrBLO2IxsCCCCAAAIIIIBA9gWckpK37HenJwIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQF4SMB9cadasmbp27ar58+fnpakxFwQQQAABBBBAwC4EevTooXPnzlkfUrCLgPJBECVLltTLL7+sIUOG5IPZMkUEEEAAAQQQuJlAdHS0/vGPfygkJETbtm3Tfffdp969e6tXr14qXrz4zbo6zLVTp05p8eLFWrhwoQ4ePKgHH3xQffr00TPPPCNfX1/9+uuvCg4O1vr16/V///d/ev3111WoUCGHmd+dDHTx/o0avGWhEmyJd/K23CsXBZydnDS3WbCertAkF+/imEN//vnnGj16tPbt26dnn31W48ePV9myZe1uMmFhYRoxYoS+//57PfHEE5o0aZLuv/9+u4szpwIqU6aMfv/99xuGc0r+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Sw8//LD1u++++2xioml7VkgbNqYY47ezB3U5OlIl8hROKD46dCn4b68NvXI1WW09n3LacHqPo+2RyyGKjotRaFSYoy6lO8uO/aZDF0/r8QqBioyNljn+uy044oJCr4Qpt2vOJM3uNDb/3N62P2PwwR/zVTZfEXUs2VAD1n4i75x51aNsMw1c87qNMSTyoq7ERdv2NQqWTBLH0bCrFsb1+i2/Wy7tOX/s+upMf5z4mqxfv7569+5tX5fe3lc9R40alWrzu9W9MkeOHPZeaJJTxsbG2q+RxGDKli1rd1PrXhkaGqqRI0fqiy++kLu7e+KwPCKAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAJhPImzevTClTpswtIw8PD9epU6d0+vRpWxL3Ex9/+eUXx7mwsP/L729yq5t1KAoXLixfX99kHxPPFSpUSMePH78hlujoqzn0ly1bZtecqFOnjl555RW7toSTk9MN7alAAAEE7paAuf+sXLlSc+fO1Q8//GDvc+XLl1f37t3VqVMnmfsRW+YQqFu3rkwZO3asduzYYZ9T87x+8skndg0Qs96QeU5btWolDw+PzDEpokTgFgJmLahx48bZtbv8/Pxu0ZrTCNxdgZCQEK1YsUJBQUEy7+MPHTqk3Llz23XpzHv5wMBAVatW7e4Omk168/T0VOvWrW0xUz5z5ozD+fPPP9fhw4dl1t5q1qyZzM9YZq3gSpUqZRMdpokAAggggAACCCCAQNYWML/PWrhwoVatWmXX+c7as2V2CGQfgXPnztm/p2afGTNTBBBAAAEEEEAg9QRcUq9rekYAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEBAcnZ21qxZs9S4cWM99NBDWrdunfLlywcNAggggAACCCCQLQR27dql+++/P1vMNTNN0iRTMR8uX79+vQICAjJT6MSKAAIIIIAAArcpYL7nV6xYURs3btSBAwdUqlQpRw8zZ860SdTq169v637++WcNGzbMcf7PP/+USarXsGFDR11Kd7p27aohQ4Zo8ODBeuCBB+zvyK6/9vvvv1d8fLx69+6d5NTq1at17733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k08+UePGje0Dh85nfMyNAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALuKdC0aVNNmDDB1n379mn27NmKj4/XddddJ09PT8XFxdlcLcOGDVOdOnXccxEXSFQnTpzQvHnzrK/JLZORkaGYmBj98Y9/tMZVLY+lyd9ZUFDws++ej4+P8vPzlZeXZ/tmbj8s/+OZ8grw/dl76YAAAmcXyM3NlcnNlZqaeta6c+dOZWVl2ZtNjq769esrMjLS1iFDhriOR44cqVOnTp11Em9vb5l5unXrpmnTpqlfv34l+pmcW6bee++9ysnJsfm3EhISZOrrr79uf9937NjRfi0yX4969uypgICAEuPQgMAvFTBff9avX28/a+bztmTJEp0+fVrma7H5rN1zzz32MxsSEvJLh6Y/AggggAACCCCAAAIXrID5nviuu+7SmDFjzvp3uAt2YQSOAAJlJmB+ntShQ4cyG4+BEEAAAQQQQAABBM4IeAOBAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAAC5yZgHmDRsmVL+6CUcxuJu91BIDY21j5oZMWKFerfv787hEQMCCCAAAIIIIAAAr9CICIiwj5cyjxgyjzs6r333tOMGTP0yiuvKDQ0VOYBVldffbV69eol86CriixTp07VO++8o48++sg+BMvMXb16dc2dO1d//vOfFR0dXZHhMBcCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCFygAvXq1dPtt99ua1pamubMmaP4+HjdeeeduvXWW9WzZ0+NGDFCV155pUxOF8rPCxw6dEgffvihdVy4cKEKCgrUu3dvPfHEExo+fLjCw8N/fpBK2mPdunXKzc11rc7T01Pe3t7Kzs62bYGBgWrXrp06d+6sDh062GrOu817UPtOp7nu4wABBEoKZGRk2Hxb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AAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAghUFgFfT2+1D2ms9rUbKbJ6Xe06dVibj+3R14e2akhkN81M+fK8L7VeQLA6hjRx4oxUQYGUcmK/vj2comPZp9QltLkW7F5T5jG2rFlfAxt20vqjO/X53vV2/MZBdTWh45V69JuZ2nv6aJnP+UsHjA5pquRju5WRl/1Lby1V//BqwWod3MBZ/wbV9gtSpzpNtXDPWte91b39NapZDxkX857M3PblWWPpVa+t+kdE60DGMc1KSdS+02muMQZHdtVHqatc5xycP4FWrVrJ1HHjxikvL09ff/21EhISbP3973+v7OxstWnTRnFxcbaaPGJBQUHnL+BKPHN+fr6++eYbl/+yZctk8rqZ98f4P/LII+rTp49q1qxZiRVYGgIIIIAAAggggAACFS+Qk5Oj22+/XYMGDdLIkSMrPgBmRACBC04gNTVVkZGRF1zcBIwAAggggAACCFwIAp4XQpDEiAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAuwps2rRJUVFR7hoecZ2DQGxsrLZs2aJDhw6dwyjcigACCCCAAAIIIFBZBfz8/Oxm6RdeeEG7d+/WypUrdf311+uLL77QgAEDVKNGDU2ZMkU33HCDunTpUlkZWBcCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAmG6FmEAAEAASURBVAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALlInBRnWZKvPJxPd79Bmd8D328c7XSszM0rt0V2nf9G3qyx+/KZd7SDurt4aUpXcdo3dXTdWn9dtpwZKeW7tuoEP8gzRs0SSljXrLtpR2vtP0aB9XVja3jNK3bb9UgsLbrto4hTfTblr3VpnZDV9v5Oris4UXWISMvu9xCGNu6r65qdokdf2TTGF3fqo9rruY16mn1qKd0l/NZuaPtIE2/5Bb7Wapbraarjzm4p/0Q/c35fAX5VHP6DtbGa57VgIhOrj4HM47p6R43y8vD09XGwfkX8PLy0sUXX6w//vGPWrx4sdLS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gcHy9vRyLFcXNvGKAAIIIIAAAuUkkJSUpBtv/PmHAZTT9AxbwQKxsbF67rnndPr0aQUEnPl+s4JDYDoEEEAAAQQQQAABNxD405/+pPj4eH366aeKiopyg4h+WQg+Pj7q2bOnrVOmTLHf33755ZdatGiRFi9erFdeeUW5ublq27atfShf37591atXLzZ5/jJmeiOAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKVUiA0NFQmD05OTo727dun//znP3rzzTeVn5+viIgIDRw4UCbvTe/evVW/fv0yM8gvKNDWE/ts/blBT+RkaOXBLT/XrdTXM/KyS9U3tyBPycf2lKrv2ToVqECnc7OKXcrJzyt2nleQr72nj9o2M59zS6mLySO/6djuUvc/W0dj+3Mly5ln/LKXNPGiUTanvVlX0VLa98e4p548VPTWcjk28R3KPP6TYz/a7To9ufZDrTpUdp+rn5yQiwgggAACCCCAAAIIIIAAAlVS4O9//7v27NmjhISEKrl+Fo0AAmUnsHXrVrVo0aLsBmQkBBBAAAEEEEAAgRIC3iVaaEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEECgHAS+S9utQB9/zbn8T1p7ZLv8vHw0NLKbXkmar0dWvm1nNBswr13wDz3X8zaNbdXP1mX7k3TLkmeVnZ9r+zy7fp461Wmq//S7T/N3fauHvnpDF9dtqXs7DNWhjOPO5sM9eqjTSNt3dItedjNnwu61GvHZo3onboKmdvuNrd+l7dJtS57TydzMH13t0+vn6rcte6txUF3tSD/o6ndv4r91KidTMwc8qOnrP5K3p6cGNIzW0E//oh9upgyrVkvTe9yiw87mv74NOujWJf/Skn0b7VheHp66JLyNHv7qLXvep0F7vfjdZ655zEHHkMZ6u98frF2XusX/Z6rM3Gy1fveOYv2vbBKjFQeS9fWhrcXaOUEAAQQQQACBshXIzMzUjh07FBUVVbYDM5rbCsTGxio3N1crV660DyJx20AJDAEEEEAAAQQQQKDcBF577TU9+uijevXVV+0D6sptogocOCAgQP3797fVTJuenq6lS5dq8eLFWrRokZ577jkVOD+3bdOmjXr16uWqZflgvgpcLlMhgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCJyDQFhYWLG7TZ7HwvL/7N0LfFXVmSjwLyGE9yMISEER5SmgYEEg4aXVOipKqQSrtxXazrRXrVXboY5WsZ2O1mrteMe2Ot52qqCtWvBV9FottoKSQMG3CIgvsIi832+SXPaeSQoCCkjISfLf7frtvddee63v+59jyDk5Z62//e1vMX78+HSNn2Tdm2OOOSbOPPPMOPXUU2PHmk0RueUt7Wu6QNHSeZG7cx37G3auKX/dzjXsy3b+b3+2hjn10mbN6jXan+aHpc2VJ5wbL698NyYvnHVYxjMIAQIECBAgQIAAAQIECNROgbfeeitdO/mGG26Io48+unYiyJoAgUMmMH/+/OjcufMh609HBAgQIECAAAECewpk7fyj+P79FXTPe9UQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjUUoEH334+Lpl2Z5SWle63wJENmsfSzWvS9u0atYgj6jWNt9ctiY07tu61j2a5DSNr5//WbNu4x/WkvkFObmza5d662XVie2nJHm0/WnF0o5bpFwX/tnHlRy/t9fyrXU+LHnlHx/dm3LPH9aZ1G0S3vKPibxtWxgebVu12vXWDZvHmhf8ZP5r9QNw558lIzhduWL5bm8o4+cvwG+Kq4vExa/mCA+w+K35z6rfjvGPzD/A+zQkQIECAQO0UePXVV6NXr17x+uuvR48ePWonQi3MOvmizCWXXBLf//73a2H2UiZAgAABAgQI1G6BP//5z+lEdN/73vfixhtvrDUYa9asieeeey6mTZuW7l944YVIJuzr2LFjDBkypKIcd9xxtcZEogQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKitAr///e/jS1/60n6nX6dOnSgp+e+1xxt+Z2jU7fmZ/b5Xw+ovkKztvnrrhv1af75945Zx7WfPjy91GhzvrVsaP33lkfj928/v172VKfWZhnmxZNPqgx6iXnZOXNFreHz/pFEH3YcbCRAgQIAAAQIECBAgQKDmC5xxxhmxdOnSSNYPzsnJqfkJy5AAgUoV6Ny5c4wePTrGjRtXqePonAABAgQIECBQmwW8cqvNj77cCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKHUWDp5jUVoy3euCqS8nHb2m2b9nm5LMpi046tu13fXvrfXwDdrXIvJ+9vXLGX2n1XjZ//5/j1KZfFiS06xKur3tut4brtm+OvyxbsVre3k80l22LhhuV7u3RI637c76L491cei1nLPzmmQzqwzggQIECAQC0UmDdvXiSTUCQfeLbVHoGCgoIoKiqqPQnLlAABAgQIECBAIBWYO3dujBw5Ms4777y44YYbapVK8+bN49xzz01LkvjGjRujuLg4pk2blpb7778/tmzZEu3atYvBgwfHwIED03LiiSemr5lqFZZkCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUM0F1qxZE8uWLdtnWbBg/9fPzsrKirKyskjWwck5vXNs69yqmusI/0AFlm1eu9+3LNm0Oq6acU9aym/a37Xry9tXxj6Jy0aAAAECBAgQIECAAAECBCpT4IEHHogpU6bE9OnTIycnpzKH0jcBArVAYNu2bfHuu+9G165da0G2UiRAgAABAgQIVJ2AV29VZ29kAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBaiBQFmVxybQ745b8r8b4+X+Ol1a8s19RN8ypl7ZrVq/RfrX/tI2uPOHceHnluzF54axP25X7CRAgQIAAgf0QmDdvXhx33HGRm5u7H601qSkCBQUF8aMf/SidgCSZiMRGgAABAgQIECBQ8wWWL18ew4YNi+7du8c999wTtf33wEaNGsXpp5+eluTR37p1a8yaNSumTZsWzz33XIwbNy6SCQAbN24c/fv3j+R36IEDB8aAAQOiWbNmNf8JI0MCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhkkMDmzZtj2bJlaUnW41m6dGkk+/K6XfdJ/bZt2yqiT9brad68ebRu3bqidOvWLV5++eWKNns7qFOnTpSUlESHDh3i6quvjtGjR8dJj343lmxavbfm6gikAttLS2Lttk00CBAgQIAAAQIECBAgQIBArRJYu3ZtfOc734l/+qd/ivz8/FqVu2QJEKgcgbfffjt9f7Zr166VM4BeCRAgQIAAAQIEUoEcDgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIfLzAttIdceX0X8dRjY74+Ib/c7V945ZxzUmF6dkXjukXb65ZHL9/+/lIvnxYWduDO/v35dfK0tUvAQIECBDYU2DevHmRTFphq10CBQUFsWrVqpg/f77Hv3Y99LIlQIAAAQIEaqnAli1bYvjw4ZFMYvfYY49F/fr1a6nEvtOuV69eDBo0KC1Jq7KyspgzZ04UFRXF9OnT4/77749/+7d/i+zs7OjRo0ckv1MnZcCAAdG5c+fUdt+9u0KAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECuwps2rQpli9f/oll2bJlkZQNGzbsens0bNgwWrduXVHatWsXJ510UrRq1aqirvx6Ule3bt3d7k/WdHzggQd2qys/ycnJiR07dsTJJ58c11xzTZx77rnWqCnHsSdAgAABAgQIECBAgAABAgQI7EXg2muvjZKSkrj55pv3clUVAQIEDlxg3rx56fuyXbp0OfCb3UGAAAECBAgQILDfAjn73VJDAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABArVc4G8bV+6XwJJNq+OqGfekpfyG7aUl5YeVsk/GtBEgQIAAAQKHTyD5sPPpp59++AY0UkYI9O7dOxo0aBBFRUXRrVu3jIhJEAQIECBAgAABApUjUFZWFqNHj4758+dHcXFxtGzZsnIGqmG9ZmVlRc+ePdPyzW9+M80umUQw+R16+vTpabnnnnti69atkZeXF/369Yv+/fvHgAED0uMjjjiiholIhwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAvsWWLt2bSxfvvwTy4oVKyJZC2bz5s27dVa/fv1o1apVWpJ1dpLjjh07RuvWrfdaGjVqtNv9B3qSrDtTp06dKCn5+5rddevWjR07dsTw4cPjX/7lX9K1aA60X+0JECBAgAABAgQIECBAgAABArVNYNasWXHnnXfG3Xffna71W9vyly8BApUjkKzL3r59+2jQoEHlDKBXAgQIECBAgACBVCCHAwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECh1Zge2lJrN226dB2qjcCBAgQIEAgYwTKysoi+bDzZZddljExCeTwCCSTkpx88slRVFQUX//61w/PoEYhQIAAAQIECBCoEoHvf//78dhjj8VTTz0VXbt2rZIYasqgyUSCI0aMSEuS07Zt2+Lll1+OmTNnxowZM+K3v/1t/OhHP0rT7dSpU/Tv37+i9OrVK+rVq1dTKORBgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEANFti0aVOsWLEili9fHitXrkyPk/OPK9u3b99NpHHjxtGqVauK0qZNmzjhhBMqzne9lhwn7Q/nlpWVFXl5eWlOderUiZycnPjmN78Z3/nOd+LYY489nKEYiwABAgQIECBAgAABAgQIECBQbQVKSkri4osvjiFDhsTo0aOrbR4CJ0Ag8wTmzJkT3bt3z7zARESAAAECBAgQqGECOTUsH+kQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQqFSBRYsWRTIpR7du3Sp1HJ1npkB+fn5Mnjw5M4MTFQECBAgQIECAwCER+K//+q/4yU9+Evfcc0+ccsoph6RPnfxdIDc3N/r165eWb3/72+mFZLLDmTNnVpQf/OAHsXr16qhbt2706NEj+vbtm5Y+ffrEiSeeGEkfNgIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECFSWwObNmyNZVyUpK1as2K+yZcuW3cJJ1l854ogjomXLlhWla9euUVBQkJ63atUqdi1Ju/r16+/WRyaeHHvssWlY3/3ud+Piiy+OvLy8TAxTTAQIECBAgAABAgQIECBAgACBjBX45S9/Ga+//nq88sorGRujwAgQqJ4Cc+bMidNOO616Bi9qAgQIECBAgEA1EsipRrEKlQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgECVC8ybNy+NoVu3blUeiwAOv0Ay0cott9wSq1evNknJ4ec3IgECBAgQIECg0gWeeeaZuOSSS2LcuHExZsyYSh/PAP8tkExyePbZZ6clqSkrK4u33norZs+enZYXXnghHnzwwVi/fn0kkyKecMIJ0bdv3+jTp09aevbsGfXq1cNJgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACB3QRKSkrS9QdXrlwZSVm1alW6Lz/f137Lli279VOnTp1o0aJFtGzZMi3JmisdOnRI11Epr/vovlmzZrv1UVNOpkyZEvXr14/c3NyakpI8CBAgQIAAAQIECBAgQIAAAQKHTeCDDz6I6667Lr73ve9Ft27dDtu4BiJAoOYLlJaWxrx58+Lb3/52zU9WhgQIECBAgACBKhbIqeLxDU+AAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgWgkkH3Ru3bp1OnFHtQqHRE5+AABAAElEQVRcsIdEoKCgIMrKymLGjBlx1llnHZI+dUKAAAECBAgQIJAZAm+88UaMHDkyCgsL41//9V8zI6haGkVWVlZ07tw5LRdeeGGqkPwe/uabb8bs2bPjhRdeSPe/+93vYsOGDZGTk5N+2b13796xa0kmWbQRIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFD9BUpLS2PNmjWxatWqtKxevbriOKlbuXJlWnY9TuqSe5K1T3bdGjRoEMnaJuWlRYsW0aVLl4rz8vryfcuWLSMvLy+SdVVsEU2bNsVAgAABAgQIECBAgAABAgQIECBwkAJXXnlltGrVKq699tqD7MFtBAgQ2LvAu+++G5s3b44ePXrsvYFaAgQIECBAgACBQyaQc8h60hEBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBWiAwb9686NatWy3IVIp7E0gmbuncuXMUFRXFWWedtbcm6ggQIECAAAECBKqhwLJly2LYsGFxwgknxN13322ivgx8DJPJE7t27ZqWL3/5y2mEyaSOb775Zrz88ssV5ac//WksXbo0vX7UUUdF796909KrV6/o2bNn+vt8nTp1MjBDIREgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCo+QJbtmyJVatWxerVq9N9crw/52vXro2ysrLdgHJyciIvLy8tRxxxRCSlVatW6ZqT5efJvkWLFum18roGDRrs1o8TAgQIECBAgAABAgQIECBAgAABAodD4I9//GNMnDgxnnzyyfAe1eEQNwaB2iUwZ86cdH327t27167EZUuAAAECBAgQqAKBnCoY05AECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEqq3AvHnz0slAqm0CAv/UAgUFBVFUVPSp+9EBAQIECBAgQIBAZghs3rw5hg8fHslkgI8++mjUq1cvMwITxScKZGdnp6/PunXrFhdccEFF+6VLl8bLL79cUSZNmhQ33XRTlJSUpI9v0r5nz567lWOOOSb9YmtFJw4IECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIENirwPr162P16tVpWbNmzR7He6tL2if1yXoxH90aNWoUeXl50aJFi7Qkx0cffXT06tWr4rz8WrIvb9u0adOPduWcAAECBAgQIECAAAECBAgQIECAQEYKJO+Lfetb34pRo0bFmWeemZExCooAgeotMGfOnGjfvn00bty4eiciegIECBAgQIBANRDIqQYxCpEAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAxgjMnTs3hg8fnjHxCOTwCxQUFMR3v/vdKCkpiTp16hz+AIxIgAABAgQIECBwyATKysrioosuigULFkRxcXEcccQRh6xvHVWdwJFHHhn/8A//kJbyKLZs2RLJ67nXX389La+99lrccccd8f7776dNmjRpEt27d48ePXpEt27dKspxxx3n9/5yRHsCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFqL5Cs2bJhw4ZYs2ZNrF27tmK/63FyLSmrV6+u2JcfJ/XJWn4f3Ro2bBh5eXlpad68ebpP1oPp2LHjbvUtWrRIz5N9ecnNzf1od84JECBQYwRKtu+oMblIhAABAgQIECBAgAABAgQOXuDGG2+M5cuXx//5P//n4DtxJwECBD5GYM6cOeka3R/TxCUCBAgQIECAAIFDJJBziPrRDQECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIEaL5BMVLJ06dLo1q1bjc9VgvsWKCgoiI0bN8arr74aJ5100r4bukKAAAECBAgQIJDxAldffXVMnjw5/vSnP0WXLl0yPl4BHrxA/fr109/fP/o7/Lp16+L111+vKHPnzo2nnnoqFi9enA6WTC7ZuXPn9HVg8lqwvCR1zZo1O/iA3EmAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEDgIAQ2bdoUyZoba9euTffJOovJcVLKjz+63/VaclxaWrrHyHXr1k3X40jW5GjevHla8vLy4phjjonevXtXnCd1yfVkv+txcr+NAAECBHYXKNv58/bHp38jJne7LfLz8yNZEzXZt2/ffveGzggQIECAAAECBAgQIECgRgvMmzcvfvrTn6albdu2NTpXyREgUHUCr7zySpxzzjlVF4CRCRAgQIAAAQK1SCCnFuUqVQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKfSmDu3Lnp/ccff/yn6sfN1Vuge/fu6cQ2RUVFcdJJJ1XvZERPgAABAgQIEKjFAr/61a/illtuiQkTJsSQIUNqsUTtTr1p06bphGrJpGq7buvXr4/58+dH8uX68vLoo4/GggULYtu2bWnTVq1aRadOnfZaWrRosWt3jgkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBGqxQFlZWWzcuDHWrVu3W1m7du1u5/tzvaSkZA/JrKysaNy4cTRv3jxda2/X/VFHHbVHXbNmzfZo27Bhwz36VUHgYAQmTpwYb7/9drRu3TotRx55ZGxfvj7K6u6IrNycg+nSPQSqr0BpxPBr/jFaf1Aa06dPjzvuuCN27NgR7dq1S9dOys/PT/fJ+qi5ubnVN0+REyBAgAABAgQIECBAgMDHClxyySXRs2fP+Na3vvWx7VwkQIDAwQps3bo1XYv72muvPdgu3EeAAAECBAgQIHAAAv7yfQBYmhIgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECNRugfnz50eDBg2iffv2tRuilmefnZ0dAwYMiKKiIl+wqeXPBekTIECAAAEC1VfgT3/6U1x66aXxgx/8IC666KLqm4jIK02gSZMm0bdv37TsOkgygea7774bCxYsiLfeequizJw5M63fvn172jwvLy86duwYHTp0SMsxxxxTcZzUJRNu2ggQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECA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NVatWhWrV6+O9evXx7p169J9clxeyus2bNgQmzdvji1btuyzJP2VT1RbPpntR899XeVQPHL6IECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFDLZCVlZV2ubd9UpeU7Ozsfe6Ta+WlTp06kZTkfG/7nJyctH7X/a7HyT3Jed26dQ+o7Oue3NzcSEq9evUq9p90XO5wqJ31R4BAzRZI1iaZM2dOTJ06NZ599tmYNm1aLFu2LJo0aRIDBw5M1zZJ6rdv374HRPJzp6CgIMaPHx8dO3bc47oKAgQOXGDHjh3x6quvRlFRUbrOarJ/77330t9RevbsGfn5+el/d8m+c+fOBz5ADb4jWZt20qRJ8fvf/z7eeuutGpzpvlNLfnYPHz48Ro0aFWeccUY0aNBg341dIUCAAAECBAgQIEBgvwRuvfXWuO666+L111+PTp067dc9GhEgQOBgBRYsWBBdunSJGTNmRP/+/Q+2G/cRIECAAAECBAgcgEDWzj+alx1Ae00JECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEMhwgeRLsZdffnnMmzcvrrjiirj++uvTL81meNjCI0CAAAECGS/w2GOPxRe/+MVYv359NGrUKOPjFWDlC9x3333xj//4j7Fu3bp0kqTKH9EIBAgQIECAAAECByNw1113xcUXXxy//e1v43/9r/91MF24hwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI1FqB0tLSeO211yJZP3vq1Kkxbdq0WLlyZTRr1iwGDRoUQ4cOTUufPn2iTp06UVRUFAMHDtzNKycnJ7126623xre+9a3Iysra7boTAgQOrcCSJUuiuLg4/e8x2b/wwguxdevWaNWqVeTn56eloKAg+vbtGw0bNjy0g2d4b4nFxIkT0/LOO+/EMcccEyNHjozCwsIYMGBArfr59MEHH6QOkyZNSp8ryXPhnHPOiVGjRsWZZ55Z654bGf7UFR4BAgQIECBAgEA1EXj//ffj+OOPj6uuuiquv/76ahK1MAkQqM4CDzzwQFx00UWxfv36qF+/fnVORewECBAgQIAAgWojkFW2c6s20QqUAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIH9EigpKYm77rorxo0bF7m5uXHTTTfFmDFjatUXzvYLSiMCBAgQIHAAAjfffHPccccdsXDhwgO4S9OaLJB8wb9jx47x/PPP7zExSU3OW24ECBAgQIAAgeok8NRTT6WTECXvk/mydHV65MRKgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAlUlkKyR/fLLL8fUqVPT8txzz8Xq1asjLy8vBg8eHEOHDo1TTjklevfuHdnZ2XuEuX379mjSpEls3bo1vZaVlZWu+zh+/Pg47rjj9mivggCByhfYtm1bvPjii1FUVBTFxcXp/oMPPoicnJz0v+X8/PwoKChIS/v27Q9JQEn/yXgjR46M5OdAVW1lZWUxa9asmDhxYlqS9YmTn0VJXIWFhdGvX7+qCi2jxv3www9j0qRJaUl+7tevXz9dBy4xOvvss6NRo0YZFa9gCBAgQIAAAQIECGSqwIgRI2Lu3Lnx6quvRr169TI1THERIFCDBMaOHRvPPPNMvPTSSzUoK6kQIECAAAECBDJbIGvnH6DKMjtE0REgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgcLACq1atinHjxsVdd90Vffr0idtvvz369+9/sN25jwABAgQI1GqBr33ta5F86fypp56q1Q6S312gTZs28c///M/xve99b/cLzggQIECAAAECBKpc4LXXXotBgwZF8oXpZNI4GwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQILCnwI4dO+LFF1+MqVOnpuX555+PtWvXRsuWLWPw4MFxyimnxNChQ+OEE06I7OzsPTvYS83nP//5mDJlStSvXz9+9rOfxSWXXBJZWVl7aamKAIGqEli4cGEUFxdHUVFRun/55Zcj+XnQtm3bKCgoiPz8/HT/2c9+NnJzcw84zGuvvTZ+/OMfR79+/eI3v/lN9OjR44D7ONgbysrKYsaMGTFx4sSYNGlSvP/++9G5c+cYOXJkFBYWRp8+fQ6261px37Jly+Khhx5K7ZJ/G5LH/+yzz07thg0bFk2aNKkVDpIkQIAAAQIECBAgcKACf/jDH+ILX/hC+p7IaaeddqC3a0+AAIGDEkjev03e9/jVr351UPe7iQABAgQIECBA4MAFsnb+MarswG9zBwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC1Ung1VdfjSuuuCL94u3o0aPjJz/5SbRp06Y6pSBWAgQIECBQ5QLJF9ZPPvnkuP3226s8FgFkjsB5550XyUewHnnkkcwJSiQECBAgQIAAAQKxZMmSGDBgQBx77LHx9NNPH9SkUxgJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEBNFNi0aVP89a9/jeeeey4txcXFsWHDhmjdunUMHTq0ovTo0SOysrIOiuD++++P3/72t/GLX/wiOnTocFB9uIkAgcMrkPxsmD17dhQVFaUl+dmwYsWKqFevXvTp0ycKCgoiWeM32bdp0+YTgxs0aFBMnz49cnJy0vVfr7rqqhg3blw0aNDgE+89mAalpaVp3JMmTYqkLF68OLp27RojR46MUaNGRe/evQ+m21p/T/IcePjhh1PTv/zlL+njeeaZZ0ZhYWGce+650bRp01pvBIAAAQIECBAgQIBAIrBx48bo3r17DB48OO677z4oBAgQOCwCZWVl0axZs7jlllvi4osvPixjGoQAAQIECBAgQCAia+cvYmUgCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCoHQITJ06MsWPHxurVq9MvyF1xxRWRm5tbO5KXJQECBAgQ+JQCLVq0iBtuuCEuvfTST9mT22uSwK233hpJ+fDDD2tSWnIhQIAAAQIECFRrgWTyqSFDhsT69etjxowZkZeXV63zETwBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEPg0AitXrozp06fH888/H88991y88MILsX379mjfvn0MHjw4Lcm6P8cff/ynGca9BAjUQIEFCxZEcXFxFBUVpWXOnDlRWloaHTp0iIKCgrTk5+fHiSeeGDk5ORUCO3bsiMaNG8fWrVsr6urUqRNt27aNX/3qV/EP//APFfWf5iCJJfm5NmnSpHjooYdiyZIl6c+ywsLCGDVqVJxwwgmfpnv3fkRg1apV8cgjj6TezzzzTGRnZ8cZZ5wRiffw4cOjefPmH7nDKQECBAgQIECAAIHaI3DVVVelr3fmzZsXRx55ZO1JXKaHRGDbtm3x+OOPx/jx4+PJJ59M37s7JB1nUCeNGjWKESNGxJgxY+K0005LX1NmUHjVNpS5c+dG9+7dY/bs2dGnT59qm4fACRAgQIAAAQLVTSCrbOdW3YIWLwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBy+wefPmuPnmm+OWW26Jo446Km677bYYNmzYwXfoTgIECBAgUAsEli1bln7BIvlS8uc+97lakLEU91cgmbxg4MCB8dZbb0XHjh339zbtCBAgQIAAAQIEKkkgmcTpvPPOSyeqmzFjht/RKslZtwQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECCQuQILFy6M559/Pp577rm0zJ07Nw22R48eMWjQoBg8eHBajj766MxNQmQECGSkwLp162LmzJlRXFwcybquyXpha9eujUaNGsXJJ58cBQUFkZ+fHw0bNozTTjttjxyys7MjWW9s1KhRcfvtt0ebNm32aPNJFSUlJTF16tSYNGlSPPzww7F06dLo2bNnFBYWxvnnnx/HH3/8J3Xh+iEQWLNmTTz66KMxceLEmDJlSpSVlcXnP//59HH4whe+EC1atDgEo+iCAAECBAgQIECAQPUQeP311+Okk06Kn//853HxxRdXj6BFmRECyWvs8ePHxwMPPJC+vk5eSyevbZs1a5YR8R3KID788MP43e9+l76X0LZt2/jKV74So0ePjuQ9S9vBC/zmN7+Jyy67LH3+1K1b9+A7cicBAgQIECBAgMABCWTt/MNI2QHdoTEBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAjVCYNGiRTF27Nj0S1Vnn3123HbbbdGlS5cakZskCBAgQIDAoRaYNm1aDB06NBYvXhzJh8htBMoFtm7dGk2bNo1f//rXcdFFF5VX2xMgQIAAAQIECFSRwHe/+92444474plnnomBAwdWURSGJUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECh0egrKws5syZE88991w8//zz6f7999+P3Nzc6NOnTwwePDgGDRqUlry8vMMTlFEIEKg1AsnPoDfeeCOKioqiuLg43b/55pvRoUOHSH4W7dixY68WOTk5Ub9+/bj11lvjm9/8ZmRlZe21XXll0s9f/vKXmDRpUjzyyCOxfPny6NWrVxQWFsaoUaOia9eu5U3tq0Bg7dq18Yc//CEmTpwYTz/9dJSUlMRpp52WPjZf+MIXomXLllUQlSEJECBAgAABAgQIHB6B5HVR8v5L8roleW2UnZ19eAY2SrUVWLRoUdx7770xYcKESF5Dd+/ePUaPHh1f+cpXol27dtU2r/0NPMk5yf2+++6LhQsXpu9hJvlfeOGF0apVq/3tRrv/EfjGN74R8+fPj2nTpjEhQIAAAQIECBA4jAJZO18Mlh3G8QxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgECGCTz77LNx+eWXx7x58+KKK66I66+/Ppo0aZJhUQqHAAECBAhUrcD//b//N8aOHRvr1q2r2kCMnpECBQUF6YQBd955Z0bGJygCBAgQIECAQG0RuOOOO+Kyyy6L3/3ud3HBBRfUlrTlSYAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQK1SGDbtm0xe/bseP755+O5556L6dOnx+rVq9N1qZP1FQcNGhSDBw+Ofv36RYMGDWqRjFQJEMgUgVWrVsVFF10UTz31VJSUlHxsWFlZWdGnT5/4zW9+EyeccMJubbdv3x7PPPNMTJo0KR599NFYuXJlfPazn43CwsIYNWpUdOrUabf2TjJDYP369TF58uSYOHFi+hxIHsdTTjklfcxGjBgRrVu3zoxARUGAAAECBAgQIEDgEAn8+te/josvvjh9v6Z3796HqFfd1DSBDRs2pK9vJ0yYEM8++2y0bNkyXYd7zJgx6evimpbv/uRTVlYWU6dOjfHjx8dDDz0UW7ZsibPOOitGjx4d55xzTtSrV29/uqn1bXr27BnDhg2Lm2++udZbACBAgAABAgQIHE6BrJ2/0JYdzgGNRYAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBA5gkkX5676667Yty4cZGbmxs33XRTJB8MTL40ZyNAgAABAgQivvvd76aTYsyaNQsHgT0Exo4dG3/605/ilVde2eOaCgIECBAgQIAAgcMj8OSTT8a5554bP/zhD+O66647PIMahQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIVLLA0qVLo6ioqKK88MILsXXr1mjTpk0MGjQoBg8enO579eoVderUqeRodE+AAIH9E2jbtm0sWbJkvxrn5OREaWlpfO9734urr746/Xk3ceLEeOyxx2L16tXRt2/fGDVqVBQWFsZxxx23X31qlBkCGzZsiMcffzwmTZoUyXpzyb9fQ4YMSR/PL37xi+m/ZZkRqSgIECBAgAABAgQIHJzAihUromvXrjFmzJj493//94PrxF01ViB5rfvMM8/EhAkT4uGHH44dO3bEOeeckz5fzjrrrKhbt26Nzf1AE9u0aVM88sgjMX78+NSsWbNmccEFF8To0aNjwIABB9pdrWm/bt26yMvLi4ceeihGjBhRa/KWKAECBAgQIEAgEwSyynZumRCIGAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQqHqBVatWxbhx4+Kuu+6KPn36xO233x79+/ev+sBEQIAAAQIEqlhg2LBh0aJFi7j33nurOBLDZ6JA8kH4888/P9asWRNNmjTJxBDFRIAAAQIECBCo0QKvvvpqOnndyJEj4+67767RuUqOAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBGquQGlpabz22mtRVFRUUd55553Izs6OHj16REFBQUXp1KlTzYWQGQEC1VpgyZIl0bZt24PKoU6dOpH8LOzXr1+MGjUqCgsL45hjjjmovtyUWQKbNm2KJ554IiZNmpTuN2/eHIMHD04f4/POO++gnzOZlaVoCBAgQIAAAQIEapvAV7/61ZgyZUrMnTs3mjRpUtvSl+8+BN54442YMGFC3HfffbF48eIYMGBAjB49Oi644ILIy8vbx12qywU++OCD1C4xnDNnTnTp0iX1+8pXvuI9gnKk/9n/6U9/ijPOOCOS92LatGnzkatOCRAgQIAAAQIEKlMgq2znVpkD6JsAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgeon8Oqrr8YVV1wRU6dOTT/49pOf/MSHu6rfwyhiAgQIEDiEAh07doyvfe1rcd111x3CXnVVUwTKJyVIPhh/+umn15S05EGAAAECBAgQqBYCyRc5+/fvH507d46nnnoq6tatWy3iFiQBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEFi7dm3MmDEjioqK0jJz5sxYv359NG3aNF2bp6CgIJIyYMCAtI4YAQIEqoPAww8/HCNHjqwINScnJ7Kzs2PHjh1RWlpaUd+wYcN07bENGzZESUlJHHPMMTFixIj453/+5zj66KMr2jmoeQKbN2+OJ598MiZNmhSPP/54JM+BgQMHRmFhYfrcOeqoo2pe0jIiQIAAAQIECBCocQLTpk2LoUOHpr/X7voaqMYlKqH9ElixYkXcf//9MX78+HjhhReiffv2cdFFF8Xo0aOjS5cu+9WHRnsKJJYTJkxIbRPj5L+5MWPGpK8dmzRpsucNtazmhz/8Yerzzjvv1LLMpUuAAAECBAgQqHqBrLKdW9WHIQICBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBDJRYOLEiTF27NhYvXp1jBs3Lq644orIzc3NxFDFRIAAAQIEKk1g69atkXyZ/MEHH0y/QFxpA+m4Wgsce+yx8bWvfS2uv/76ap2H4AkQIECAAAEC1Ulg48aNMWTIkNi0aVM68V1eXl51Cl+sBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFDLBBYsWJCut1NUVJTu33jjjSgtLY2OHTtGQUFBRenZs2dkZ2fXMh3pEiBQUwTGjx8fl156aRx55JGRrPl63HHHRfv27aN169axZMmSmD17djz77LOxefPmGDhwYLpmcGFhYbRt27amEMjjAAS2bNkSTz31VEyaNCn+8Ic/xPr162PAgAEVz4vkuWMjQIAAAQIECBAgkGkC27dvj169eqWveZ544olMC088h0lg27Zt8fjjj0fyOvjJJ5+MevXqpa9lRo8eHaecckpkZWUdpkhq/jDJf3OJ8YQJE1LzOnXqxBe/+MUYM2ZMnHbaabX2vdTPfe5zcfTRR6fPwZr/LJAhAQIECBAgQCCzBLLKdm6ZFZJoCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDIJIHky3M333xz3HLLLXHUUUfFbbfdFsOGDcukEMVCgAABAgQqVeD111+PE044IV599dV0X6mD6bzaCnz5y1+OlStXxh//+Mdqm4PACRAgQIAAAQLVSSCZ8G7EiBExY8aMtCQTQ9kIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgECmCGzYsCFmz56drrFTXFwcSVm+fHnUq1cv+vbtGwUFBWnJz8+PI488MlPCFgcBAgQOqUBZWVk89NBD8fvf/z6eeOKJ2LJlSwwZMiQKCwtj5MiR0aZNm0M6ns6qt8C2bdvi6aefjkmTJsVjjz0Wa9asiX79+qXPl6997WvRsmXL6p2g6AkQIECAAAECBGqMwE033RT/9m//FnPmzIljjz22xuQlkf0TmDlzZowfPz4efPDB9HXLaaedFqNHj47zzjsvGjZsuH+daHXQAqtXr44HHnggJkyYkL732q5du/jyl7+cPgY9evQ46H6r243Ja+jmzZvHL37xi/j6179e3cIXLwECBAgQIECg2gtk7fwjWFm1z0ICBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhUusCiRYti7NixMXHixDj77LPjtttuiy5dulT6uAYgQIAAAQJVLZB8wfz888+PjRs3Rv369as6HONnqMAvf/nLuPbaa2PVqlWRnZ2doVEKiwABAgQIECBQcwSuvPLKuOuuu+LPf/5zJJPf2QgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQFUJlJaWxhtvvBEzZ86MGTNmpPs5c+ZEUt+uXbsYMGBAFBQUpOWzn/1s5ObmVlWoxiVAgMBhFSgqKoqBAwfGqaeemq4RfN5550Xr1q0PawwGq54C27dvjylTpsTEiRPT8pWvfCXuvPPO6pmMqAkQIECAAAECBGqUwLvvvhs9evSIcePGxTXXXFOjcpPMvgUWLVoU9957b0yYMCHefPPN6N69e4wePTqS1yrJ+3+2qhFIHovkMbnvvvti4cKF0adPn/RxufDCC6NVq1ZVE9RhGnX69OkxaNCgWLBgQXTq1OkwjWoYAgQIECBAgACBcoGssp1b+Yk9AQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEPkng2WefjcsvvzzmzZsXV155ZfpB1CZNmnzSba4TIECAAIFqK3DjjTfGf/3Xf8U777xTbXMQeOULvPTSS5FMwvLaa69Fz549K39AIxAgQIAAAQIEarHAL37xi/T9qQceeCCdCKoWU0idAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBKpA4MMPP4yZM2dWlFmzZsX69eujYcOG0adPnxgwYED0798/LUcddVQVRGhIAgQIZIbAs88+G6eeemosW7YsWrVqlRlBiaLaCZx55pnRrl27dJ3pahe8gAkQIECAAAECBGqcwLBhw+K9996Ll19+OerWrVvj8pPQ3wU2bNgQkyZNigkTJkTy+vaII46ICy+8MMaMGZO+B/j3lo6qWqCsrCymTp0a48ePj4ceeii2bNkSZ511VowePTrOPffcyM3NreoQD/n4N910UyTrvS9evPiQ961DAgQIECBAgACBTxbI+eQmWhAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQODvAqecckq89NJLcdddd8W4cePi3nvvjeSDYMmHErOysv7e0BEBAgQIEKghAvPnz49u3brVkGykUVkCJ554YjRu3DiKioqiZ8+elTWMfgkQIECAAAECtV7giSeeiCuvvDJuvPHGOP/882u9BwACBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQqFyBLVu2xIsvvhgzZ86MGTNmpPuFCxem6zkn6132798/Ro0aFQMGDEjXM8zJyancgPROgAABAgQIECBAgAABAgQIECBQJQIPPfRQ/L//9/9i6tSpUbdu3SqJwaCVK1BaWhrPPPNMTJgwIR5++OHYsWNHnHvuufHoo4/GWWed5XGvXP6D7j0rKytOOeWUtPzyl7+MRx55JMaPH5+ug96sWbO44IILYvTo0el7uAc9SIbdOG3atBgyZEiGRSUcAgQIECBAgEDtEciuPanKlAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBQyVQp06duPTSS2PBggVx3nnnxT/90z+lH2xLvsBsI0CAAAECNU1g3rx50bVr15qWlnwOsUDy+1G/fv2iqKjoEPesOwIECBAgQIAAgXKBl19+Of2S5ZgxY+Kaa64pr7YnQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKHRKCsrCzefPPNuPfee+Oyyy6Lk08+OZo2bRoDBw6Mm266KTZt2pSu5/z000/H6tWr44033oi77747Lr744ujdu3fk5OQckjh0QoAAAQLVU2DRokVx5513pv9W7CuDxx57LLZs2bKvyxlVv2HDhpg8eXL8y7/8S0bFJRgCBAgQIECAAAECVSGwfv36uOKKK+KrX/1qDBkypCpCMGYlCiTv81199dXRvn37OOOMM+Ktt96KW2+9NZYsWRKTJk2K4cOHR926dSsxAl0fKoGGDRvGl7/85Ujew01epyeP67Rp0yI/Pz+6du0aN954YyxcuPBQDVcl/Wzfvj2ef/75GDp0aJWMb1ACBAgQIECAAIGIbAgECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBA4WIEWLVrEL3/5y3jxxRcj+dBb8gG35AOqH3744cF26T4CBAgQIJBxAvPnz49u3bplXFwCyjyBgoKCKCoqyrzARESAAAECBAgQqAECixcvjnPOOSf69esX//mf/1kDMpICAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECFS1wMKFC+Ohhx6Kq6++Ok4//fTIy8uLrl27xje+8Y2YPXt2JOsUjh8/Pt5+++1YtmxZTJ48Oa677rr4/Oc/H82aNavq8I1PgAABAhkksGHDhpg+fXrccMMN8cc//nGPyJ544ono27dvjBgxIjZv3rzH9UysSPK4/PLL44EHHsjE8MREgAABAgQIECBA4LAKXH/99bFly5b46U9/eljHNVjlCaxYsSJ+/vOfp6/VevToEffff3989atfjfnz50dxcXFccskl0aJFi8oLQM+VLtCuXbu46qqr4vXXX0/f7z3zzDPjP/7jP+LYY4+NU089Ne65555Yv359pcdxqAeYMWNGJO9DJO9p2wgQIECAAAECBKpGILtqhjUqAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQI1SeDEE0+Mv/zlL/Hggw+m+y5duqQfVN22bVtNSlMuBAgQIFALBZYsWRLr1q1LJ++ohelL+QAFkoldFixYEMuXLz/AOzUnQIAAAQIECBD4OIHkS4jnnHNONG3aNJ1or27duh/X3DUCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQILCHwIcffhiTJ0+OH/zgB3H22WdH69ato0OHDnH++efH448/HkcffXTceOONMXPmzHQtyxkzZsR//Md/xIUXXhjHHXfcHv2pIECAAAECuwo0btw4/Tejf//+u1anx4sWLYoTTjghunTpsse1TK4oLCyMfv36RU5OTiaHKTYCBAgQIECAAAEClS7w0ksvxc9//vO4+eabo2XLlpU+ngEqX2Dq1KnRtm3b+P73v5++Xvvzn/8c7733Xtxwww3V7rVb5WvVjBH69OmTvt+7ePHiePTRR+OII46I//2//3f6PHjnnXeqVZJTpkxJ39vu1KlTtYpbsAQIECBAgACBmiTgLyc16dGUCwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIEqFhg1alScc8456QdVky9B/+pXv4rbbrsthg0bVsWRGZ4AAQIECBycwPz589Mbu3XrdnAduKtWCQwYMCCysrKiuLg4hg8fXqtylywBAgQIECBAoLIESkpK0omQPvjgg0gm02vevHllDaVfAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKghAqtWrYrZs2fHrFmzKvaLFy9O1xzs1KlT9O3bN6655pp0/9nPfjYaNWpUQzKXBgECBAhUtUBOTk76782ucbRv3z497dChw67V1eI4Ozs7kmIjQIAAAQIECBAgUFsFSktL4+KLL44BAwbE1/8/e3cCX9Px/3/8nUUSYi+11r7Ulth3JVVLUUuFprSo2ou2Urp9qa9qFa1SVFHVWLoQ+1ZaS/GN0pYv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9/IDDzxg3+OpPhgCQAABBBBAAAEE7oOAeZCSeWCf+d5owoQJ96EFVIkAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggkFwC169f144dOzRz5ky98sordj7HbNmyqUiRImrbtq3mzZsns9+/f3+tWrVKZ86c0b59+xQeHq4333xTjz/+eJqa8zK5XgfKRQABBNK6QIkSJSzBiRMntH37dpvOmDFjPJY6derYffO5lZg88S5Opp2HH37Yzgm9YsUKZw2RkZEycyFnypTJHvP09LRbLy8vZx5HIl++fNq0aZP69u1rP4+LFi0q89lslq1bt9pt2bJl7dbxw8y/zIIAAggggAACCCCAQFoTuHLlinr27KmWLVvqiSeeSGvhE28KCpj7su7du9savb2976jmadOm3VF+V8x8L/G7YjzJ3SZz727mgW/atGlyV0X5CCCAAAIIIIAAArcR+L//jblNJk4jgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACSSkQEBCg1atX6+uvv7ZbM0hu5MiRio6OTspqKAsBBBBAwMUEzGDoUaNGqXTp0qpVq5a2bNmiESNG6MiRI5o+fbrq1q3rYi1O+eZkz57dDp7etm2bNm7cqBo1amjgwIEyA6uffvpprVy5UrGxsSnfMGq8IwHzukVERNzRNWRGAAEEEEAAAQQQkKKiotSsWTP78D7zsL47HayKIQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICA6wicO3dO69at07hx49StWzdVq1ZNmTJlsvNadu7cWatWrVLhwoU1dOhQOwfghQsXtH37ds2cOVOhoaEKCgpS1qxZXScgWoIAAggg4DYCBw4csLEUKVJEZk5hs2zYsMFuHT8KFiyodOnS2bnVEpPHcV1ybj08PLR48WIdPnxY/fv311dffaU9e/bY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AAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggkl8DVq1e1a9cuLViwQO+99546duyoqlWr2rlfzLx5DRs21Ntvv23n/S1ZsqQGDhyo7777TseOHdPJkye1Zs0ajR8/Xj179lSdOnWUPXv25Goq5SKAAAIIIPAPATM3csaMGe1cuo899pgmTpyo48eP/yNfchwwdY8dO1ZnzpzRzz//bOeyT5cuXXJUlWJlli1bVp999pn279+vy5cv69ChQ9bUHHenJTo6WsuWLdPzzz+vXLly2Xm1S5Qo4U4hEgsCCCCAAAIIIICACwmMGzfOfrc2YcIEF2oVTXFXgVdffdWG1qdPH125ckXXr1/X119/bY+tX79ep06d0sWLF3X06FEtXbrUfsf78ccf2/NHjhzR2bNnbTpPnjz2O+B9+/Zp7969znvtr776SufPn9e6deu0du1ae08cFRWlCxcu2OsS88P8WwgPD1dMTIzM/dnBgwf/cb1po1lMe+Mut2t7YuKPWx5p2ffA8uXL1a5dOzgQQAABBBBAAAEEXEjA04XaQlMQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACB2wqEhYXZDmmm4+KNS2xsrMyA9saNG+vw4cM3nk5w/8svv1Tp0qW1detWrVy5Uqazo7+/f4J5U+qgl5eXXn/9dW3evNl20KxQoYJGjBiha9eupVQTqAcBBBBQcHCw6tatqx07dujDDz+0HcOnTp2qWrVqoZNKBbJmzapevXrZzxczYD0oKMg+5CV//vzasGFDKo3KtZtds2ZNRUREuHYjaR0CCCCAAAIIIJDCAr1797aDRhctWqQCBQqkcO1UhwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCLimwOnTp/XDDz/IzN/7xhtvqHXr1ipTpoyda7dkyZJq1aqVPvnkEx0/fly1a9fW+++/r3Xr1unEiRN2Xbt2rSZOnKi+ffuqfv36ypUrl2sGSqsQ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AQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBwEwEzd+7Bgwe1e/du7dmzx25N2qz79++XmYfXLDly5FCxYsVUvHhx1ahRQyVKlJCZ285sM2TI4CYahIEAAggggMDdCfj5+enJJ5+065UrV/TNN98oPDxcb731lp2fvnr16goODlbr1q1VsGDBu6uEq1xC4K+//tKyZcvs67to0SJdunRJtWrV0tChQ+1rnDdvXpdoJ41AAAEEEEAAAQQQSDsC5m/S3r17q23btmrcuHHaCZxI06xA4cKFFRQUdMv4s2TJom3btuno0aP69NNP9dhjj9n7cfOd96ZNm+y5119//ZZlcDJpBcLCwvTggw+qYcOGSVswpSGAAAIIIIAAAgjcs4BH7N/LPZdCAQgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggEAKCVy/ft0OhN+yZYs2b96sH3/80W7Pnz9vW+Dj42MHyMf9r9Dx48cra9as6tOnj0wnwylTpty2M2IKhXNX1Wzfvl2dOnWyHSKHDBmi0NBQeXl53VVZXIQAAgjcTCAwMFDNmzfX22+/fbMsaer4sWPHtHPnTtWrV8/t4vb29taMGTMUEhLidrG5QkCPPPKIfUDP5MmTXaE5tAEBBBBAAAEEEEhxAfOQw2rVqql8+fJavHgx32Gk+CtAhQgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggcDcCFy5c0L59+7R37167OtJme+DAAV29etUWmz17dhUvXty5FitWzJk2c+qyIIAAAggggMCdCURHR2vFihUKDw/XwoULdebMGVWtWlVt2rRR69atVbhw4TsrkNz3ReDixYtasmSJfR3N9sqVK6pTp46Cg4Pt65g7d+770i4qRQABBBBAAAEEEEDACLz55psaN26cduzYobx584KSBgW+/PJLtW/fXm3btlWHDh3UqFEj5t/++30QGxurDz/8UIsW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qYCHh4dq1apl11GjRmnjxo0KDw+38yEPGzbMznEcHBysNm3aqHLlyklad2or7NixY5o7d671Wbt2rXx9fdWkSRPNnDlT33//vSZPnmznj86XL5+dry61xUd7EUAAAQQQQAABBNK2wNWrV9WjRw81aNBAISEhaRuD6J0CXl5eatmypb1HNO+RxCzmPvPRRx/Vl19+qezZs2v58uWaNm2a+vTpY9cWLVqoY8eOatiwoUz5LEkrcPnyZS1cuFBhYWFasWKF/P397T392LFjVbt2bZnXxx2Wjz76yL43CxQo4A7hEAMCCCCAAAIIIOC2At5uGxmBIYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAmhCIiYmxcZqOj6ZDHsv/BEwnUNP5eNCgQYqOjv7fCVIIIICACwps3brV/h7PmTNnvNb5+PjY/e3bt9ttxowZ452vU6eO3d+xY0e843F3HJ3i4z5Mxpx/4YUX1LRpU9u523TKN23497//HffSJEubdj/zzDO2474ZHH7ixAldvHhRRYsW1Weffab69evbQdDJVf/tApk0aZImTJgQ74E7t7sm7nlPT087qNu8XveymoHh6dKls2XcuDXl3njMsX+zc47zZutYHXlTsuN+6dKlZR5gFBERoQoVKsSlI40AAggggAACCLiFwLlz5+zf1rlz59bXX3/NwFS3eFUJAgEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwF0EzFx+58+f15kzZ3T69GmdPHky3nrq1Kl4++a8OXblypV4BFmzZtWDDz6oXLly2W2ZMmUUFBTk3DfH8+TJY9f06dPHu5YdBBBAAAEEEEDgfgqYuWyrV69u1/fff18//vijwsPD7TpixAgVKlRIwcHBqlKlilJy3tv7aWLqPnLkiObOnav169crQ4YMdk66r776Sk2aNLH7Js9TTz2lPn36KDQ0VI8++qhatGihkSNHqnjx4uY0CwIIIIAAAggggAACLi8wevRo7d692/7t6/KNpYEpKtCqVStNmzbttnV6enraPO+8845effVV532juXcyq5nje9asWbYss2/m+m7Xrp06duyogICA25ZPhlsL/Oc//1FYWJg1joqKUsOGDTV9+nS1bNlSfn5+t744lZ2NiIjQhg0btHbt2lTWcpqLAAIIIIAAAgikPQHvtBcyESOAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgi4msD169d18eJFrV692na0vrF92bNnt4dMJ+U6deo4TxcsWFDp0qVTtmzZnMcSm3j88cdVpEgRTZw40XboNvvJufTo0UMTJkywg0J+/vlnvfLKK1qzZo0GDx6sffv22fjz58+fnE1IsGwzIN10bA8JCdHVq1cVHR2doutff/3lrM88ICgmJsa570g7tqZtjrR5z9zL4uXlJR8fH/v+Me+hm6XNubtdvb29ndeaBxl9/vnntp64x03Zcffjph3nHMfM9lZpx6CRe3HhWgQQQAABBBBA4E4FzN+Q5mFHZ8+e1caNG5UxY8Y7LYL8CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCBwCwEzd9uFCxd0/vx5nTt3zm5N2rFv5g45c+aMcz19+rQzbY6b8zfO/2bmRXvggQeUI0cO51q8eHFVr17duW/OPfjgg8qVK5fdmjnfWBBAAAEEEEAAAXcQqFKlisw6fPhwbd68WbNnz9acOXP0/vvvu0N4iY4hc+bMatasmcLDw9W4cWOlT58+wWtLlSqlpUuXavny5QoNDVWZMmXUu3dvDRw48K7mlk6wEg4igAACCCCAAAIIIJAMAgcPHtTgwYP1+uuvq1ixYslQA0WmVgHznXmWLFlkvis383TfbDHnc+bMae+batasmWA2U07Xrl3tum/fPk2bNk3Tp0/XqFGjFBgYqA4dOqh9+/b2u/YEC+DgPwSMozE06969exUQEGDvQY1j7ty5/5HfXQ68++67Mu+zOnXquEtIxIEAAggggAACCLitgLfbRkZgCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQBILREVFafXq1Vq/fr0d0JfExVMcAgggkKYFypUrZ+P/4osv1LBhQ6fFqVOntHbtWlWrVs0e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u0G0hIgAAggggAACCCDgLODnvEIZAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQOAvgW+++ca6mdc8XGZfbDab9VDZzz//bN+kBQsWKCgoyFpfvXq1MjMzrcEHHRX+f8Eca1969uypa665RhERETIDG6anp1u71q1bx0NrdiRyBBCodIF3333XeqD2pZde0ogRI3TLLbdYD9t26tSJhxwq/dU59wDMwLhTp05VcnKy1q9fb33uTJgwQd26dTv3RjnyrAXq169vff4vXbpUrVq1OuvjqIgAAggggAACCLiCwMCBA/Xdd99p4cKFMgNGsyCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACpRG44IILZFKtWrVKc1iJdQsLC3XgwAEr7d27V/v27ZPJncvO28ycMqtWrTpuf15e3kltmzlmQkJCFBYWptDQUCu3l6tXr66aNWuqRo0ajtyU7cnU9/HxOalNNiCAAAIIeIaA+dzYs2ePdu/ebaWcnBxH2Wwz686fQ87lw4cPn4RgPnOqVat23GeN/TMnNja2xM8is98k81llUtWqVU9qlw0IIIAAAggggAACCCCAAAIIIIAAAgi4koCZ987Mp/zQQw/p0UcfVbNmzdSrVy+9/PLLiomJcaVQiQUBBBBAAAEEEEDAAwR27typJ554wvr+edVVV3lAj7y3Cxs3blRaWppSU1O1aNEi+fn56YYbbtDYsWPVrVs3RUZGei8OPfcYgaNHj+qRRx7R3//+d8XFxXlMv+gIAggggAACCCDgLQJ+3tJR+okAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgiURmDFihVW9SuuuOK4w8yAS86LefDs66+/1meffaa2bdvqoosu0o8//uhcxSo7H2cG+ouIiNAzzzxjDWrYtGlTq05RUdFJx7EBAQQQqCyBSy+9VOPGjdNrr72mWbNmKTk5WV26dJEZXO7ee+/VfffdpwsvvLCywuO8pRAwA99+8cUX1mv4+eefW4P/mYekBwwYoEaNGpWiJaqWhUCLFi20ZMmSsmiKNhBAAAEEEEAAgQoTeOmllzRt2jTrgVkefK4wdk6EAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACpxDw9fVVWFiYlcw8Cuey5OXlae/evVbat2/fSWXnbXv27NH69eu1e/du5eTkWHWPHj163GnNnDQmpho1aqhmzZpWbsolJef95hjTHxYEEEAAgYoROHLkiON6bq7rp0rmeu+8Lzc396QAq1at6rjmm2u7uaZHRUWpQYMGVjk0NNTKzXZ72Tk3nx0sCCCAAAIIIIAAAggggAACCCCAAAIIeIPAtddeq4yMDH300UcaPny4LrnkEiUmJuqJJ55QUFCQNxDQRwQQQAABBBBAAIEKEBg2bJjM33Gfe+65CjgbpyhrgeXLlys1NdVKK1eutP7e3qVLF82cOVOdO3dWcHBwWZ+S9hCoVIG33npLf/75p7744otKjYOTI4AAAggggAACCJybgN+5HcZRCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgGcL7N+/3+rgsmXLdOIggTabzdH5p59+Wunp6frqq69UpUoV68Ezx06ngvMxGzduVFxcnMaNG6euXbtq7dq1TjUpIoAAAq4lYK5tvXv3ttK6deuUnJyspKQkvfDCC+rQoYP69++v+Ph4BQQEuFbgRKMNGzZo8uTJmjJlirZt22Z99kydOlXdu3dXYGAgQpUk0LJlS40ZM6aSzs5pEUAAAQQQQACB0gt88MEHeuqpp/Tmm2/q5ptvLn0DHIEAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIuKCAGbc/IiLCSucSnpnfZvfu3SelnJyc47Zt2rTpuPX8/PzjTmfmtQkNDVWNGjWsVLNmTdWqVcuRTrXOPBHHMbKCAAJeKmCuxbt27ZK59prcnk5cd75eHz58+CSt4OBgx3XYfj2+5JJLHNdl+zZzTa5evbpjO9fikyjZgAACCCCAAAIIIIAAAggggAACCCCAwGkFzNzKt9xyi9544w29+OKLmjRpkpX36dNH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pElq166dfZfX56tXr1bfvn21cuVKK9WvX18//fST0tPTZR50W7Rokfbu3avq1atbN9W2bdtWbdq00dVXX82Ad17/7gEAgYoRyMvL08cff6zk5GTrJn8z0KC5bvXr10/mmuVNS0FBgT777DPL4ssvv7Suzb1791b//v3VsGHDUlP8+uuvSkpK0vTp02UGhrz11ls1aNAgmWs9i2sI9OrVSzk5OTKvNwsCCCCAAAIIIFDZAuaBwfbt22vIkCF6/fXXKzsczo8AAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAiQJFRUXasWOHtm7dqi1btli5c9lsM8nMB2BffH19FRkZqejoaEeKiYk5qVy1alX7IeQIlIvAzp07rfeneY9mZWVZZXtuf++aOaXsi7+/v8x8Lua9W6dOnZNys82kkJAQ+yHkCCCAAAIIIIAAAggggAACCCCAAAIIIICAxwoUFxdr6tSpGjFihA4ePKinnnpKDz30kAIDAz22z3QMAQQQQAABBBBAoGSB5cuXq2nTppowYYLuu+++kiux9bwEfvvtN6Wmplrp+++/l7mvplOnTkpISFDXrl1VvXr182qfgxHwFgHzb9nbbrtNCxculPlZuvDCC72l6/QTAQQQQAABBBDweAHbsS97xR7fSzqIAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIeLbB9+3YNHjxYaWlpGjJkiEaPHq2goCCP7vPpOldYWKjXXntNzz77rBo3bqyUlBQ1aNDgpEPMYHgrV65Uenq6lcxNgrt27VJwcLCuv/56tWnTRm3btlWTJk1kBhJjQQABBMpTYMOGDZo0aZJ1zdq2bZvatWun/v3769Zbb/XoB3DXrVtn9ds8eJydna0OHTpY/TYPPQQEBJw3+aFDh/TBBx8oKSlJy5Yt02WXXaaBAwfqnnvu4YGK89Y9vwbGjRtnPWy+e/du+fj4nF9jHI0AAggggAACCJyHwPr169W8eXPrdwEfffQR303Ow5JDEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEXEMgLy9PW7du1ZYtW05KWVlZ1jazPz8/3xFw9erVFRMTo+joaCs35RNTaGiooz4FBOwCZi4oM4+WeW+VlOzvQ/O+tC9hYWHWe828305M5n1Xp04dhYeHy2az2Q8hRwABBBBAAAEEEEAAAQQQQAABBBBAAAEEEDgmkJubq1deeUWvvfaaIiMjNXr0aN12223YIIAAAggggAACCHiJgPkbfYsWLRQQEKCMjAz+rl5Gr7tx/fbbb5WammqldevWWfct3HLLLUpISFCHDh10wQUXlNHZaAYB7xF45pln9K9//Uvz5s1T69atvafj9BQBBBBAAAEEEPACAVvxscUL+kkXEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDACwTee+89PfTQQzKDrE2ePFlxcXFe0Ovju7h69Wrde++9WrFihUaNGqXExET5+voeX+kUa+bPx+Z4c3Nzenq6lW/btk1Vq1a1bnxu27at2rRpo2bNmp3zDblmkLMuXbpo2LBh6tmz5ykiYTMCCHizQGFhoWbPnq3k5GQrDwkJ0d13363+/furUaNGHkFz5MgRzZo1y+qjud6aQRvNtfu+++5TvXr1yq2PP//8s5KSkmQ+L48ePaoePXpo4MCBatmyZbmdk4ZPLbB8+XJdc801WrVqla644opTV2QPAggggAACCCBQjgK7d++2/s1vvneb76bmdwAsCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCHiDgJmvZ+fOndqyZYuysrIcuSk7rx88eNDBUa1aNWueCTPXhEmxsbGOdXu5evXqjvoU3F/AzKVi5nHKzMy03hf294fzutlfUFBgddZmsyk8PNx6X0RHRzveH+b9Yl83eVBQkPvj0AMEEEAAAQQQQAABBBBAAAEEEEAAAQQQQKASBczva//xj39Y8zW3atVKY8eOVZMmTSoxIk6NAAIIIIAAAgggUBECb7/9th555BEtX75cl19+eUWc0mPPkZeXp7lz5yo1NVWffvqpsrOzdfHFFyshIcFKLVq0kI+Pj8f2n44hUN4CkydPVv/+/TVhwgQrL+/z0T4CCCCAAAIIIIBAxQrYjj2QUFyxp+RsCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQPkJbN++XYMHD1ZaWpqGDBmi0aNHe8VAWWaQsddee03PPvusGjdurJSUFDVo0OC8odetW6f09HQrZWRkaPPmzQoMDFSzZs3Upk0btW3bVuZm3bMdjGz69Onq06ePFddNN92kiRMnWoOanXegNIAAAh4pYAZHNNezSZMmaf369R7VR39/f3Xt2tW6Qbtz584V+tCDGZj0vffeU1JSkvVQS6NGjTRo0CDdfffdCgkJ8ShnV+6M+ewOCwvTmDFjdP/997tyqMSGAAIIIIAAAh4qkJ+fr44dO2rjxo1atmyZIiMjPbSndAsBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBcxfYt2+fsrKyjkuZmZmOdVPev3+/4wRmLp+YmBjFxsbrMrmlAABAAElEQVQ68hPLoaGhjvoUKk+gqKhIZn4U8/qa19H+ujrnZr+ZY8IsPj4+1njO5vV1fo3t6yavU6eOAgICKq9TnBkBBBBAAAEEEEAAAQQQQAABBBBAAAEEEPAyge+//16PPvqolixZot69e+ull15SdHS0lynQXQQQQAABBBBAwDsEtm/frssuu0yDBg3Sv/71L+/odBn3cs+ePfr888+VlpamL7/8Urm5uWrSpIkSEhKs1LBhwzI+I80h4J0CH330ke644w7985//1HPPPeedCPQaAQQQQAABBBDwcAFb8bHFw/tI9xBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwAsF3nvvPT300EMyA6VNnjxZcXFxHquwevVq9e3bVytXrtSoUaOUmJgoX1/fcunvn3/+qYyMDKWnp1tp/fr18vf317XXXqu2bduqTZs2uv766xUSElLi+fv166dp06apoKBAfn5+CgwM1NixY9W/f3/ZbLYSj2EjAgggYG5vWbx4sTXgoidomMEgzbUyIiKi0rvz3XffKSkpSe+//751He7Zs6cGDhxoPaBR6cF5QQDt27e3BnxNSUnxgt7SRQQQQAABBBBwNYE+ffpYD+kuWrRIjRo1crXwiAcBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABtxE4cOCAsrKylJmZecp8//79jv4EBwdb8xXExsY68piYGEfZbA8KCnLUp1B6ATPfyc6dO63XxLwuJaWtW7dacymZ1s18ImYuEWNvfy3suX1bnTp1rHmXSh8NRyCAAAIIIIAAAggggAACCCCAAAIIIIAAAgiUt8CHH36oJ554QtnZ2Ro2bJiGDx+uqlWrlvdpaR8BBBBAAAEEEECgAgXuuusuLVmyRKtXr+a7XinczT0Tqamp1nzW6enpstlsateunRISEtStWzdFR0eXojWqIoDAmQS++uor62dr4MCBevPNN89Unf0IIIAAAggggAACbipgO3bDerGbxk7YCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAwGkFzANagwYNsm4+HTJkiEaPHu1Rg6IVFhbq1Vdf1ciRI9W4cWOlpKSoQYMGpzUp651mADRzY69JGRkZ+u233+Tr62vF07ZtW5nUunVr1ahRwzp13bp1rYHUnOMwNwVff/31mjJlii666CLnXZQRQAABBCpIYN++fZo+fbqSkpL0yy+/6Nprr5W5kdw8AMOAouX3Ijz99NP64IMPtHbt2vI7CS0jgAACCCCAAAIlCDz33HN6/vnn9dlnn6lTp04l1GATAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgiUpcD+/futuXsyMzOVlZXlKJt1ezp06JDjlGbOn9jY2JOSmQPIbI+OjlZAQICjvrcVzFwbmzdvdtgZQ+d1Y5yXl2exmDmSateubbnFxMTIGJrc2bdOnTry9/f3Nkb6iwACCCCAAAIIIIAAAggggAACCCCAAAIIeJSA+b3wv//9b7300ksKDg628t69e8v8npgFAQQQQAABBBBAwL0F5s6dqxtvvFGffPKJbrnlFvfuTAVEv3LlSqWlpSk1NVU//fSTQkJCdNNNNyk+Pl5dunRRaGhoBUTBKRDwPoFPP/1Ut99+u+644w6lpKTw71HvewvQYwQQQAABBBDwIgFb8bHFi/pLVxFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwAsFZsyYoaFDh1o3nk6ePFlxcXFur7B69Wr17dtX5mbbUaNGKTExUb6+vpXerx07dmjhwoVKT0+30qpVq6yYrrjiCjVp0kRTpkwpMUY/Pz/5+Pjo5Zdf1iOPPGKVS6zIRgQQQACBchdYvHixxo8fr1mzZlkDhfbq1UuDBg3SlVdeWe7n9rYTfPHFF9bDMTt37lStWrW8rfv0FwEEEEAAAQQqSeC9997T3XffbX3nGzhwYCVFwWkRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQOBEgd27dyszM/O4tHnzZsf6li1blJ+fbx1ms9kUGRmp2NhYK9WtW/ekPCIiQqaeuy1HjhxRVlaW1W97/0/MDxw44OhWWFiYw8HuYXK7SUxMjAIDAx31KSCAAAIIIIAAAggggAACCCCAAAIIIIAAAgh4tsCOHTv0zDPPKDk5WVdddZXGjh2r1q1be3an6R0CCCCAAAIIIODBAnl5eWrUqJGuuOIKffzxxx7c03PvWmFhoRYtWqS0tDSlpqZq48aNioqKUnx8vJVuuOEGBQQEnPsJOBIBBM4o8MEHH6h3797q27evNYe8j4/PGY+hAgIIIIAAAggggID7CtiKjy3uGz6RI4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAmcnkJ2drUGDBlk3qQ4ZMkSjR49WUFDQ2R3sQrXMzbavvvqqRo4cqcaNGyslJUUNGjRwoQiPD2XPnj1auHChMjIy9Pnnn+v333/X6f5MbW5aNP2aPn26Lr/8cn2R+aN6znnt+EZZQ8CNBK6tfZHm3fKCG0VMqAj8n0BOTo6mTp2qpKQkrV27Vs2bN7c+S3v06KEqVar8X0VK5yxgPidr1qxpPUBz44036ocfftCSJUusz8s33nhDwcHB59w2ByKAAAIIIIAAAiUJmH+jm+8dQ4cOtX6/UFIdtiGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgGsKmLl/zFxMmzdvVmZmppXsZXtu9hcVFVkdCAgIUExMjGJjY1W3bl1H7lwOCQmp0M6a2LZv3+7ogz1u53zHjh2OmC644AIrbuc+nFhmfgcHFwUEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABJ4FffvlFjz32mObMmaPu3bvrlVdeUf369Z1qUEQAAQQQQAABBBBwB4GRI0dqzJgxWr16tXUPgTvEXBExHj58WF9//bVSU1P12WefadeuXWrQoIHi4+OVkJCg6667TjabrSJC4RwIeL3A22+/bc0d//DDD+v111/3eg8AEEAAAQQQQAABbxCwHbu5v9gbOkofEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAASMwY8YM60a50NBQTZo0Se3atXMbGHMTct++fbVy5UqNGjVKiYmJ8vX1dZv4hwwZouTkZB09evS0Mfv5+cn8Kfvpp59WdPfmeuL7aSos/mtAutMeyE4EXFAgskqY1vR8xwUjIyQESicwf/58jR8/Xv/9739VrVo19enTRwMHDrQe/ihdS9S2C2zZskVLlizRs88+q7y8PG3atEmFhYXy8fGxBmLduHGj6tWrZ69OjgACCCCAAAIInLfAunXr1KJFC7Vp00azZs2yvnecd6M0gAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACLiVg5gfKyspSZmamNm/efFJutu3bt88Rc0hIiOrWretIsbGxjrLZHh0dLX9/f0f9MxX2799vndecx57ssZh1M1+DfQ4jM0dDVFSUdT7n85qyfb127dpnOiX7EUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBA4rcDs2bP1+OOPa8OGDXr44Yc1YsQIhYaGnvYYdiKAAAIIIIAAAgi4hoCZl7lRo0Z68cUX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R46obNmydp9XqlRJEyZMUGhoqP13n3lnPP/eO/t9O8W55j646B1l5XDdPXs91jhfAn5ePhpU9wYNb3jb+dok20EAAQQQuEgFvC/SftEtBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQKCYC/v7+ysnJ0YkTJ07ZYvNwzLVr16px48Z2MH2NGjVOWbY4LYiOjtZzzz2noUOHyjxo1twcwE0AxWkP0lYEEEAAAQSKhsDIkSP1xRdfaNasWbriiiuKRqNoBQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALnUSAlJUWxsbHat2+fDbnTrrz9+/fLvP/ANYWFhSkqKkoVKlRQ9erV1bZtW5t25Zn84OBgV3HiS1zAy8vLHh/muGjWrFmBGubdEwcPHtTevXttMMeeK71p0yb7zg2Td/z4cff6QUFB9jh0HXeRkZHueZNn5suVKydvb2/3OiQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEzq+AeQfz448/rv79+2vEiBHq16+fJkyYoFdeeUUtW7Y8v41hawgggAACCCCAAAIIIHBWAmYsx/fff2/HbcyZM8eO5TBjglq3bq1Ro0apQ4cOvBv8rESLZ+HLLrtM9957rw1mjM+vv/5qj4m5c+dq+vTpSk9Pt+N0lixZosDAQDuWrHTp0sWzs7QaAQQQQAABBBBA4JwLMLL7nBOzAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEPgzgddee005OTmnLWYewmoextqkSRPNmjVLLVq0OG354rJw8ODB+uCDD7RhwwZVqlSpuDSbdiKAAAIIIIBAERH46KOP9Nxzz+n9999X+/bti0iraAYCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAAChSOQnp5u31UQGxvrjk163759yh0nJSW5N+jr66vy5csrKirKhmbNmrnT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EEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBA4FwI5OTmKj4/XwYMH84QDBw4UOJ+Zmeluhp+fnyIiIlSuXDkbatSoYZ/7W758eeUOpoy3t7d7PRIIIHDmAiVLlpQJ5vN1usm8D2T//v0Fhl27dmnp0qUyn+sjR47IfO5dU1BQkPsz7Po8m7igUKJECddqxAgggAACCCCAAAIIIIAAAggggAACCCCAwCUjYH7jGDRokPr06aNnnnlGDz74oF5//XW9/PLL6tix4yXjQEcRQAABBBBAAAEEEDiXArGxsfr+++81d+5cG8x86dKl1aZNG40ZM0YdOnRQzZo1z2UTqPsSFoiOjtbdd99tgxlXs3r1avex+M9//lPHjh1T1apV7XFojsV27dopJCTkEhaj6wgggAACCCCAwMUvwN0PF/8+pocIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCBRpgYCAADVu3Pis2/hb8iHV//yfZ70eKyBQnAS2b9+ukSNHaurUqWrQoIFmzZqla6+9tsh2oVKlSpo0aZIee+wxPfnkk+rWrZuaNm1qb5oyN6kwIYAAAkVVICEhQWPHjtWECRNUoUIFTZkyRd27d5eHh0eRa3Lr1q21bNkyffrpp/ZcGxMTYx8W88QTT9ibVYtcg2kQAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAkRNIS0vToUOHdPjwYRsfPHjQxibPhPzzmZmZ7j54e3srPDxcERER7lCnTh2VK1fupFCmTBn3eiQQQODCCgQFBal69eo2nK4l5vNuzgEHDhw4KZj8tWvXau7cuXbZ0aNH81RltmHODWXLlnWH/POuZaGhofL09MyzPjMIIIAAAggggAACCCCAAAIIIIAAAggggEBxFjC/i7z66qu6//779eijj6pTp07q3LmzXnrpJdWqVas4d422I4AAAggggAACCCBw3gWSk5O1YMECO0bBjFPYsGGDfH191aJFC/udu0OHDmrcuLG8vLzOe9vY4KUt4OHhofr169vwyCOPKCMj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+qVLl6b7TzzxRJx77rnx5ptvxpQpU2LlypXpuZJVz51dd9015s6dG8kfeif7u+yySyxbtizmzJkTzz77bLRr1y4efPDBtJ/Zs2fHokWLolGjRuuMyUEC5Snw6bIFG9XdnOVfxjWv/jYtpQ0KiotKN7fKOrlmdV5atmwZQ4cOjZNPPjl+/vOfx3e/+93qzLFVck+ewbfccks88sgj6fN8q1xEp1VGwPOyytzKdSaSm5sbt912W5x11lnpM7dVq1brrLc5B5PBl/fdd19aStsn3ymrw/LBBx/EddddF3/605/i+uuvj169esW//vWveOONN9L1XXfdFfPnz08p9thjj/SlLYceemi6PuSQQwyArA4/JHIkQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgWop0KFDh5g6dWo8/vjj6dyHyRzhyRyIV1xxRdSqVatamkiaAAECBAgQIECg6gq0bt06knLZZZdFUVFR/OMf/4jJkyen5cILL4z8/PzYf//948QTT0zLscceG/Xr16+6IBUksy+++CJeeOGFsnvx4YcfRt26dePoo49O52ZP7kebNm0iIyOjgkQsDAIECBAgQIAAgaookF0Vk5ITAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECFQNgbrZOXH6Hu2qRjKyIEBgmwmcd955MWfOnBg0aFA0atQoDjzwwDj33HOjSZMmUVJSEh9//HEceuih0bdv33jsscfiO9/5Tlx99dVx3333xQ9+8IM444wzYsyYMXH22WfH8OHD0/O33HJL9O/fP6666qr0D/K7du0ap5xySgwZMiT+9re/xZ133hlNmzaN3r17b7M8XYjAhgQKioviq/zlG6rmfDkKJM+I5JkycODA+N73vhdnnXVW3HrrrbHvvvuW41WqZ1dffvll3HXXXfHrX/86fZ4/8MAD0adPn+qJIetyF/C8LHfSbdJh8nxNnrXJMzf5vnfJJZfEjTfemH4n29IAatasGUmpTsv8+fMj+c47bNiwaNWqVTz99NNx2mmnpQTJ/jnnnFPGMXv27HjjjTfiX//6V1p+97vfxc0335x+106+cyfftQ855JC0HHTQQennYI0aNcra2yBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQqp0BGRkb07NkzunXrFvfcc0/cdttt6VyIyTzfq899WDmzEzUBAgQIECBAgACBdQtkZWVF+/bt03L99dfH8uXL4+WXX47JkyfHpEmT4r777ovSOieeeGIkJalvfu91e27K0RUrVpRZJ97JHOuJdTLP/fnnn59aH3744aw3BVVdAgQIECBAgACBLRbIKFm1bHEvOiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECCwiQLDhw+Pa6+9NhYuXLiJLTeuemFhYcydOzd22223KCgoiOTPZGrWrLlG4yVLlkT9+vXLjq1cuTJycnLK9r/66qvIzMxco05xcXEkfxxet27dtF7Sb9L/2n2XdbKZG1vbZzPDqlDNZi2ZH4eM+UmFikkwWyaQlZEZ3XM7xNCj+m1ZRxWg9R//+Me44YYb4t13341evXrFzTffHLvvvnsFiKxyhbBs2bIYMmRI3H333emAm4EDB8aPfvSjqFWrVuVKZK1on5zxSvSbMjSKS4rXOmOXwMYJZGdmxT2H943erY/fuAZVuFZRUVGMGDEifc4m3+2uueaaGDBgQNl3tSqcermkljxnf/WrX6XP2eR78eDBg6NPnz7pwMdNucDixYvTAZP/+te/IinJ4MnkMzA/Pz/9nrz//vvHwQcfnJaDDjooXe+4446bcgl1CRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgQomMHv27Ljuuuvisccei8MPPzydI7Fdu3YVLErhECBAgEB1EFi0aFGcc8456dy8TZo0iYYNG8ari2fEO1/PjaidvarUiIzaNVeVVes6/7edrHNWnbMQqIIC322aG5M731oFM6uYKX3++efx/PPPx+TJk9Py0UcfRb169eKYY46JE088MS0HHnhgxQy+gkVVXFwcr7/+ekyaNCm1/Nvf/hYrV66M/fbbr8zy2GOPjQYNGlSwyIXz++lTov/Lw6KopBgGgQorkJNVIy5vc1pc3/bsChujwAgQIECgcgj4bULluE+iJECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQ2ESB7Ozs2G233dJWNWrUWGfr+vXrr3E8Jydnjf1kUM/aS2ZmZtStW7fscEZGRtSsWbNs3wYBAgQSgTPOOCNOO+20+P3vfx+DBg2KVq1axaWXXpoOZm/atCmkDQjk5+fH8OHD47bbbovly5fHlVdeGVdddVWs/dzeQDdOEyBQDQSysrLioosuivPOOy9++ctfxt133x0PPvhgDB48OPr27ZsO1q4GDJucYlFRUYwYMSL9jFq2bFlcc8016bO2Tp06m9xX0iAZJHn00UenpbSDgoKCeO+99+LNN98sK3l5eTF//vy0SrNmzeLggw9Oy0EHHZSuW7du7Z6VAloTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIEKLpDMTfjb3/42Lr/88hgwYEAcdthh8YMf/CDuuOOOaNGiRQWPXngECBAgUJUEMjMzY/LkyVFcXBzJdjL3cVGURHFJcaz6r/8t60o4IyKjXk7Uve7EyGpaf101KvWxkqLiKHxnbhR99GXknLp/ZGSsSthSLQRqZmRXizwrSpI77bRTnHvuuWlJYpo+fXr6TEqeS7feemv6XbmixFpZ4th1113jhBNOiOHDh6fr5s2bV5bQq22cWRmZUZR87loIVGCBlUUFkfysWggQIECAwJYKZJSsWra0E+0JECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIbKpA8gfW1157bSxcuHBTm1aL+nw2fJtnLZkfh4z5yYYrqlFpBJI/kO6e2yGGHtWv0sS8MYEWFBTEQw89FLfddlssWbIkHZxz1VVXRcOGDTemebWqU1RUFI8//ngMGjQo5s2bF/369Yvrrrsudtxxxyrl8OSMV6LflKH/O2i0SmUmmW0lkJ2ZFfcc3jd6tz5+W12y0lzn888/j1tuuSWGDRsWubm5ceedd8bpp59eaeLfFoE+/fTT6ffwZPDoJZdcEjfddFMkA0u31ZI839988801ynvvvReFhYVRq1atOOCAA6JNmzZx4IEHputkOxmkaSFAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQqtsC4cePimmuuidmzZ0cyh+/PfvazqFevXsUOWnQECBAgUGUEjjjiiHj11VejpKRkk3I6tmun+NEd10WNmjU2qV1FrjztzXfipQkT48XxebHsqyXRaKfG8fDf/xRZWVkVOWyxlaPA/ju0iFYNm5Vjj7raXIHi4uL45z//GR9++OHmdlGt2mVkZMR+++2XznderRKvAsmuKMqPSZ++EcWb+DlcBVKXQiUSyFgV61G77h+Nc+pXoqiFSoAAAQIVUSBj1S8fNu23DxUxCzERIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhUOoFHH300Lr744njkkUeiV69elS7+rRnwvHnz4vzzz4+333475s6duzUvVan7nrVkfhwy5ieVOgfBrymQlZEZ3XM7xNCj+q15oorsLV++PH7961/H3XffHcmgk2uvvTZ+/OMfR+3atatIhluWxvjx4+P666+P999/Px2M8+CDD0aHDh22rNMK2vrJGa9EvylDVw1cKa6gEQqrogtkZ2bFPYf3jd6tj6/ooW63+KZPnx4DBw6MsWPHxlFHHZU+ew877LDtFk9FuHAycP2nP/1pvPLKK3H22WfHHXfcES1btqwIoUV+fn6888478eabb8Zbb72Vfg9e/btwkyZNok2bNmuUAw44IOrXN7CqQtxAQRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgf8TSOYoTObxve2226JOnTpx++23xw9/+MPIzMxcr9Fvf/vbOOOMM2KHHXZYbx0nCBAgQIDAhgTuvPPOuPHGG6OwsHBDVSM7OzudY37EiBFx1llnbbB+Zagwc+bMGDVqVCQ5zZo1K2rUqBEFBQVp6Mlcz926dasMaYiRAAECBAgQIECAAAECBAhUeIH1/7a7wocuQAIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgcos0KNHj/jRj34UvXv3jtNOOy1mz55dmdMpt9ifeOKJOOCAA2L69OmRbFsIEKg6Aslg9WuvvTaSwXOXXnppDB48OHJzc2PYsGEbNZCw6kismcnkyZOjXbt26aDBNm3axE033RRfffVVHH300XHKKafEM888E8XFxWs2skeAAIENCCTP1zFjxsTUqVMjIyMjDj/88Dj77LNj2rRpG2hZ9U4nOSe5JwbJy1ISk6eeeipatmxZYZKtWbNmHHLIIekLXe69997485//HHPmzInPP/88XnjhhfSzIbmnr732Wlx55ZVpLg0bNoy99torTj/99Lj++uvT787//ve/I3lRjIUAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDYPgLJHIVXX311Ok93ly5d4qKLLorvfve78eKLL64zoD/+8Y/Rp0+fdC7blStXrrOOgwQIECBAYGMEvv/972/UnPHJnMcdOnSId999N84666yN6brC1lmwYEEMHTo0nSs+mbP4lltuiVmzZqXxFhQURHZ2dpx88snpPPIVNgmBESBAgAABAgQIECBAgACBSiaQWcniFS4BAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAVEahVq1YMGTIkXnrppXRgzAEHHBAjR46sItltehrz5s2Lrl27xg9+8IPo3r17vP3223HsscduekdaECBQ4QUaNWoUt99+e8yYMSP93/3ll18e++67b4wePTpKSkoqfPzlFeDUqVPjhBNOiJNOOil23HHHeP311+PJJ5+MQYMGxcyZM2PcuHGRDCw87bTTIjc3N+65555YuHBheV1ePwQIVBOBdu3apd83n3766XjnnXci+c754x//OObPn1/lBZIck1yTnJPcE4Pku3diUlmW5PPhuOOOi+Sz8qGHHoq///3vsXjx4vRzYsKECXHhhRdGnTp1Itnu2bNntGnTJurWrRv7779/nH322XHzzTfHmDFj0vyTzxQLAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgsG0EkjkJH3jggXjrrbdi5513Tucn7NKlS0yfPr0sgGSuwSuuuCIyMjLitddei/POO69azfFbBmGDAAECBDZbYN68efHYY49Fjx494sQTT4x69eqtt6/s7OxIyr333hsvvPBCNG/efL11K/KJr7/+Op23t3PnzulnbP/+/eMf//hHGnJhYeEaoWdmZsZvfvObNY7ZIUCAAAECBAgQIECAAAECBLZMIHPLmmtNgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYMsEjjrqqHTwZq9eveKHP/xhnHbaaTF79uwt67SStR49enQccMAB8a9//SsmT54cDz744LcOLKpk6QmXAIH1CCSD1u+77754//3348gjj4zzzz8/DjnkkPjv//7v9bSoGof/85//RDJQ/7DDDov8/PyYMmVKPPvss3HooYeWJZiVlRVnnnlmTJo0Kd599930s+HWW29NB1L27ds3HcxfVtkGAQIENkIg+Y6ZvDAkeXHI+PHjIzc3N2677bZYvnz5RrSuXFWWLVsWyTMzyTHJNflumeSeGFSFJXmpy1577RWnn356XH/99fHEE09E8tmS5J3kOXLkyOjWrVsUFRVF8j07GbiffNeuW7duHHjggXHOOefELbfcEn/4wx/ivffei7UHtFcFIzkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGKIrD//vtHXl5eTJw4MaZNm5bOMXjVVVfFokWL4v7774+PP/44SkpK0nkIk3kYf/rTn1aU0MVBgAABAhVQIJm39q9//WvccMMN8d3vfjd23XXXuPjii+OLL75I57vt1KlTZGdnfyPyZO70ZM7ff/7znzFgwIBI5smtjMuYMWOiSZMm6Ty9yWdr4pGU5LN07SUzMzNuvvnmdD7gtc/ZJ0CAAAECBAgQIECAAAECBDZfIGPVP8S/+S/xze9PSwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKbLfDyyy9Hnz59YsGCBTFkyJDo1avXZvdVGRrOmzcv+vXrFxMmTIhLL7007r777qhXr15lCL1CxDhryfw4ZMxPKkQsgigfgayMzOie2yGGHtWvfDqsZL2888476WDDZJD6EUccEXfccUccffTRlSyL9Yf74YcfpoMEH3/88WjTpk3cfvvtceqpp66/wVpnli1bFiNHjowHH3ww3n777fjOd76TPkN79OgRderUWat2xd59csYr0W/K0CguKa7YgYquwgpkZ2bFPYf3jd6tj6+wMVbkwJYvXx6//OUvy757DR48OPr27RvJAO7KvCSDtEeMGBGDBg2KpUuXxjXXXBNXXnllpXtGlvc9WLlyZbz33nvxn//8Z40yc+bMKC4ujpo1a0arVq3SF8gkL5XZd999Y7/99kuP5eTklHc4+iNAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAtVWIJl7cfjw4enci4WFhZGfnx/JvLVrL/fff39cdtllax+2T4AAAQLVVGDu3LkxceLEyMvLi0mTJsWXX34Ze+21V3Ts2DE6deoUxx9/fNStWzfVGTt2bHTv3j1KSkrS/czMzHQe2yuuuCLuvPPOqOxz1r7yyitxzDHHpDl9249DMl9zYvTOO+9EjRo1vq2qcwQIECBAgAABAgQIECBAgMAmCmSs+sXD//7mYRMbqk6AAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgawgsX748Bg4cGPfdd1+ceuqpMWzYsGjWrNnWuNR27XP06NHRv3//qF+/fjzyyCPpoKLtGlAlvPisJfPjkDE/qYSRC3l9AlkZmdE9t0MMParf+qpUi+OvvfZa+hx8/vnn04GHt99+e7Rt27bS5p4MqrztttvSgfl77LFH3HLLLXHuuedGRkbGZuf017/+NYYOHRrJIMzatWtHr1694tJLL4399ttvs/vclg2fnPFK9JsyNIpLirflZV2rCglkZ2bFPYf3jd6tj69CWW37VD7//PP0mZR838zNzU0Hb59++unbPpByuOLTTz8d1157bUyfPj0uueSSuOmmm2KnnXYqh56rbhdff/11vPvuu/Gf//wnHcSerJP9mTNnRvJCmdIB7slny+pl3333jYYNG1ZdGJkRIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGtLPDVV19Fly5d4uWXX47CwsJvXC2Z+3bChAlRWeeZ/EZCDhAgQIDAJgkknw1///vfIy8vLy1vvvlm5OTkxNFHHx2dOnVKS+vWrdfZ56JFi6JJkyZRXFwc2dnZ0bhx4xg1alSceOKJ66xfGQ8+8MAD8eMf/3iDob/44otxzDHHbLCeCgQIECBAgAABAgQIECBAgMCmCWSUrFo2rYnaBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBLa+QDJos0+fPrFgwYIYMmRI9OrVa+tfdBtcYd68edGvX7904Omll14ad999d9SrV28bXLnqXWLWkvlxyJifVL3EqnFGWRmZ0T23Qww9ql81Vvj/qb/wwgsxcODAeO211+Kss86KW2+9NdY3GPH/t6o4W8kAyeQZlzzDd9hhh7jpppuib9++6WDJ8oryiy++iBEjRsSwYcNi5syZceyxx6bP2DPPPDNq1qxZXpcp936enPFK9JsyNIpLisu9bx1WD4HszKy45/C+0bv18dUj4a2c5fTp0+O6666LMWPGxFFHHRW/+MUvon379lv5quXT/auvvhrXXHNN+sKTs88+O37+859Hbm5u+XReTXvJz8+PDz74IN59992y8t5778X7778fK1asSFV23XXX2G+//dYo++67bzRr1qyaqkmbAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhsvEAyR+D+++8fxcXrnt81IyMjnZ92ypQp0a5du43vWE0CBAgQqLQCs2fPjokTJ0ZeXl5MmjQpvvrqq2jZsmV06tQpOnbsGMcdd1zUqVNno/I77LDDYurUqdGlS5d4+OGHo3HjxhvVrjJVuuCCC+J3v/tdFBUVfSPs7Ozs6NGjRzz22GPfOOcAAQIECBAgQIAAAQIECBAgsOUCGSWrli3vRg8ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEyl9g+fLlMXDgwLjvvvvi1FNPjWHDhkWzZs3K/0LbqMfRo0fHj3/842jQoEE88sgjcfzxx2+jK1fNy8xaMj8OGfOTqplcNc0qKyMzuud2iKFH9aumAutOe8KECXHDDTfEe++9F717945BgwZFixYt1l25AhxNnt1DhgyJu+++O7KysuLaa69Nn321atXaatElfwr53HPPxdChQ+OZZ55JB2ImVhdddFHss88+W+26m9vxkzNeiX5ThkZxybpfULC5/WpXfQSyM7PinsP7Ru/WvkuU511PBnRfc801kbwc5Kyzzoo77rgjcnNzy/MS5dbXtGnT4rrrrouxY8fG0UcfHb/4xS+80KTcdNfdUfJSmY8++ijefffdb5Qvv/wybVSvXr1o1apVtG7duqwk+0lJzlkIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBiE6dOsXkyZOjsLBwvRyZmZnRsGHD+Mc//hF77733eus5QYAAAQKVU6CgoCD+9re/RV5eXlreeuutqF27dhxzzDHp50TyWbG5c5S//PLL8dlnn8W5555bOXE2Iuo33ngjOnbsGAsWLPjG52n9+vVjxowZsdNOO21ET6oQIECAAAECBAgQIECAAAECmyqQUbJq2dRG6hMgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDYlgLJAJs+ffqkg0+GDBkSvXr12paX3+JrzZs3L/r16xfjx4+PHXfcMSZOnBjf+c53trjf6t7BrCXz45AxP6nuDFUq/8yMjDgn96gYelS/KpVXeSRTXFwcjz/+eAwaNCjmzJmTPlOOOOKI8ui6XPv4+OOP45577omlS5fGlVdeGVdddVU0aNCgXK+xoc4+/fTTeOSRR2LEiBGRxHPcccfFxRdfHF26dImcnJwNNd8m55+Y/nL0mzI0Slb9x0JgcwSyMjLj3iMuiN6tj9+c5tpsQOBPf/pTXHvttTFt2rT0+ZEMGK9Iy0svvRTDhw9PB6/feeedcdppp1Wk8KplLPPnz4/33nsv3n///fjggw/SdbI9c+bMssHzzZs3j9atW6elVatWZdt77rlnJC+lsRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgeogMHny5DjppJM2KtXs7Oxo0aJFvPbaa9GkSZONaqMSAQIECFRcgWQO8ry8vJg4cWIknweLFy9O5+nt1KlTJCWZS7h27doVN4EKEtmTTz4ZF1xwQRxwwAHx4YcfxsKFC6OoqCiNLiMjI53/+MILL6wg0QqDAAECBAgQIECAAAECBAhUPYGMklVL1UtLRgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAlVNYPny5TFw4MC477774tRTT41hw4ZFs2bNKnyao0ePjv79+0f9+vXj1ltvTeP/97//HUOGDAmDZrbs9i1cuSQOfPLHsbwwf8s60rpCCVx58Jlx03fOqVAxVaRg8vPz00F3P//5z2POnDkVKbQ0lmRQ5SWXXBLXXXdd7LTTTts1vuLi4nQA6PDhw+OZZ56Jhg0bxg9/+MO46KKLYt99992usb04++3oMvGOKFn1HwuBzRV44qSfRscWbTe3uXYbEEgGO48YMSIGDx4cn3322QZqb9vTzZs3j5tvvjn69OkTWVlZ2/birrZJAgUFBTFz5sz44IMP4v3331+jzJ8/P+0rJycncnNz0xcVlK732Wef9Nhuu+0WyYB7CwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQqCoCyVyRybyMyZKdnZ2uCwsL0/W6/iup07Zt23jppZeiVq1a66riGAECBAhUUIFkftdXXnkl8vLy0vLvf/876tSpE8cdd1x07NgxOnXqFC1btqyg0Ve8sJLPy5/97Gfxy1/+Mvr37x/33ntvvPHGG3HkkUdGYp18Zh566KExdepU8+JWvNsnIgIECBAgQIAAAQIECBCoQgIZJauWKpSPVAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBKq4wMsvvxx9+/aNL774IoYMGRK9evWqkBnPmzcv+vXrFxMmTIhLL7007r777qhXr146cObGG2+MX/ziF9GtW7cYPnx4NGrUqELmICgCBAhUFYHZs2fHiBEj4pFHHolZs2bF0UcfHRdffHH6HDbov6rcZXkQIECgcgksWrQoPvjgg3j//ffTMm3atJg+fXpaFi9enCaTfEYlLzDYZ5990pKbm1u23bx5c4PwK9ctFy0BAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI/J/AwoUL46233iorr732Wrz33nuRn5+fztVXo0aNdD7wkpKSMrMuXbrEH/7wB3P5lYnYIECAQMUU+PjjjyMvLy8mTpwYkydPjqVLl0br1q2jU6dOaUnmGDe3+Kbfu3nz5sU555wTyWfm8OHD47zzzivrZOTIkdGrV6/IzMyMN954I9q0aVN2zgYBAgQIECBAgAABAgQIECBQ/gIZq355/f9/e13+/euRAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQLkLLF++PAYOHBj33XdfnHrqqTFs2LBo1qxZuV9nczscPXp09O/fP+rXrx8jRoyI44477htdPf/88+kgmuzs7Bg1alR06NDhG3UcIECAAIHyFSguLo5J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CBAgUM0EZi6eG23HDqhmWUuXwPYTeK7z4GjftNX2C6ASX7mwsDCmTJkS48aNi/Hjx8fs2bMjNzc3unTpEl27do327dtHMnemZU2BZF7RZ555JnXLy8uLZcuWxWGHHZaaJXYtW7Zcs8E22kvmPk3iScpf/vKXWL58eRx44IHRsWPH6NSpU3To0CFq1qy5jaKpXpdJ5ljv1q1bLFiwIEaNGhUnnXRS9QKQLQECBAgQIECAAAECBAgQqOACGSWrlgoeo/AIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIbJRAMmjoz3/+cxQUFGxU/fKslAycatu27RZ1uXLlyrj22mtjyJAh0b179/jNb34TjRo12qI+NSZAgACBzROYOnVqPProo/HEE0+kg2U7d+4cffr0iVNOOSWys7M3r1OtCBAgQIBABRVYuHBhzJo1Kz766KN0vfp2cmzRokVlkTdt2jT23HPP2GOPPdZYlx6rV69eWV0bBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQILBlAjMXz422YwdsWSdaEyCw0QLPdR4c7Zu22uj61b3i119/HZMnT45x48bF008/HQsWLIg2bdpE165d03LQQQdVd6JNyj/x/POf/5x6/ulPf4pkztGDDz64zPPAAw/cpP42pXJy7RdffDHy8vLSMm3atGjQoEGccMIJ0alTp+jYsWO0aNFiU7pUdzMERo0aFZdcckl635966qlo3rz5ZvSiCQECBAgQIECAAAECBAgQILA1BTJKVi1b8wL6JkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBg0wQmTZoUvXv3jqysrHjsscfi2GOP3bQO1CZAgACBchNYsWJFOlB2xIgR8Ze//CWaNm0aPXv2jD59+sT+++9fbtfREQECBAgQqMgCX331VcyaNSs++uijdL36dnIseTlH6dKkSZP0ZQ677757uk5e7FBakmPNmjWLGjVqlFa3JkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFvEZi5eG60HTvgW2o4RYBAeQo813lwtG/aqjy7rHJ9LVmyJPLy8tK5n5955plYtmxZtG/fPrp06RLdunWLli1bVrmct0dChYWF8dJLL6XOEyZMiNmzZ0dubm507do1Le3atYuMjIwtCm369OnpvUzu54svvhjJnN4HHXRQdOrUKTp27BhHHnmkeUi3SHjjG69cuTKuuOKK+M1vfhMDBgyIu+66i/3G86lJgAABAgQIECBAgAABAgS2qUBGyaplm17RxQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQ2KDAggUL4qKLLoo//vGPcfXVV8ett94aNWvW3GA7FQgQIEBg6wnMmjUrfvvb38bvfve7SLaTAcl9+/aNc845Jxo2bLj1LqxnAgQIECBQwQWWLl2afjZ+9NFHkZSPP/44Pvnkk7Qk28kLJgoKCtIsMjMzY5dddokWLVrEbrvtFs2bN0/Xq283a9YsatWqVcGzFh4BAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgS2vsDMxXOj7dgBW/9CrkCAQCrwXOfB0b5pKxprCSxYsCCefvrpGDduXEyaNCmKiori6KOPjq5du0aXLl0imYvSsvUESkpK4tVXX039x48fHzNmzEjnBE3sk3uQ3IusrKwNBrB8+fJ48cUXIy8vLy1JP8n83CeeeGJ06tQpOnbsmPa7wY5UKFeBmTNnRvfu3WPatGkxYsSI6NatW7n2rzMCBAgQIECAAAECBAgQIECgfAUyVv2ypqR8u9QbAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLlJfDII4/EFVdcES1btozHH388DjzwwPLqWj8ECBAgsJkCyZ9evvDCC/Hoo4+mg2WTbpLBlH369InjjjsuMjIyNrNnzQgQIECAQNUUKC4ujrlz58Ynn3ySlo8//jhdf/bZZ/Hpp59Gsp49e3YUFhaWAey4447pCyOaN2+ernfdddf0ZSDJC0GSkuzvvPPOG/VyirJObRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKCSCcxcPDfajh1QyaIWLoHKK/Bc58HRvmmryptAOUaezDc5YcKEdB7nl156KWrUqBEnnnhidO3aNc4444xo3LhxOV5NV5si8Oabb6b3Zfz48fH2229HkyZN0nuS3JvkHuXk5JR19/7770deXl5MnDgxkvu4cuXKOPjgg6NTp05pOfzwwyM7O7usvo1tKzB27Ni48MILY88994wxY8bEPvvss20DcDUCBAgQIECAEshbZAAAQABJREFUAAECBAgQIEBgkwUySlYtm9xKAwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEtpnAjBkzolevXvH666/H7bffHgMGDIjMzMxtdn0XIkCAAIH1C3z11VfxxBNPxIgRI+J//ud/0gGWyTP7/PPPN8hy/WzOECBAgACBbwgUFxfH/Pnz49NPP43kBSGrr2fPnh1JmTNnTixatKisbVZWVjRt2jSaNWsWu+66a+yyyy7rLXXr1i1rZ4MAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKVRWDm4rnRduyAyhKuOAlUeoHnOg+O9k1bVfo8NjeB6dOnx7hx42L8+PExderUqFevXpxyyinRtWvXdJ3sWyqWQOk9S+5bMs926T1bvHhxvPPOO/HRRx9Fo0aN4uSTT46OHTumJZkH1LJ9BVauXBlXXXVVPPDAA3HppZfGr371q6hVq9b2DcrVCRAgQIAAAQIECBAgQIAAgY0SyChZtWxUTZUIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIENhuAkVFRXHXXXfFzTffHEcccUT87ne/iz322GO7xePCBAgQIPBNgf/85z/x29/+NkaNGhVz5syJww47LHr27BnnnHNONGnS5JsNHCFAgAABAgQ2WWDFihUxe/bs9LM2WZeW5LN37ty5ZWXBggWx+nCJ5OUVu+yyS+y8885padq0aayvNG7cODIyMjY5Ng0IECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQ3gIzF8+NtmMHlHe3+iNAYD0Cz3UeHO2btlrP2ap5+K233opx48al5e23307nYz799NOja9eucdJJJ0VOTk7VTLwKZvXZZ5/F+PHj03v54osvRmZmZjrXdt++feOMM84w13YFuefTp0+P7t27x4wZM+Khhx5KtytIaMIgQIAAAQIECBAgQIAAAQIENkIgo2TVshH1VCFAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoAIIvPHGG3H++efHJ598EkOGDInevXtXgKiEQIAAAQKrCxQXF8fkyZNj5MiR6UDZ/Pz8OOWUU6Jnz57RuXNng51Xx7JNgAABAgS2kkBBQUHMnz8/5s6du0aZN29eejw5V1oWLFgQyed36ZKdnZ2+0GLHHXeMtctOO+1UdqxJkyZpvcaNG0eDBg0iIyOjtAtrAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIlIvAzMVzo+3YAeXSl04IENiwwHOdB0f7pq02XLES1ygpKYmpU6fGuHHj0jJjxoxo3rx5nHnmmdG1a9c45phjIisrqxJnKPREIJmv8+mnn07v8aRJk6KoqCi9t126dImkNGvWDNR2EHjiiSfi4osvjlatWsWTTz4ZLVu23A5RuCQBAgQIECBAgAABAgQIECCwJQIZq37BVrIlHWhLgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMC2FVi5cmVcd9118atf/SrOOOOMGD58eOy0007bNghXI0CAAIGNEli2bFk6OHbkyJHx/PPPR8OGDaN79+7Rs2fPOPLIIzeqD5UIECBAgACBrSuQvMAieanFvHnzYv78+Wn54osvIimff/55ui7dL10XFBSsEVTyYpMddtghGjduvEZp0qRJNGrU6FtLgwYNIjMzc43+7BAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQCARmLl4brQdOwAGAQLbSOC5zoOjfdNW2+hq2+4yhYWF8dJLL6XzLU+YMCFmz54dubm50bVr17S0a9cuMjIytl1ArrRNBZYsWRLPPvtsev+TdTL3dvv27cvuf8uWLbdpPNXxYsuXL48rrrgiHnrooejfv3/cc889UbNmzepIIWcCBAgQIECAAAECBAgQIFDpBTJKVi2VPgsJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKiGAi+++GL07t07ksE+Q4cOjW7dulVDBSkTIECg8ggkA6JHjRoVI0eOjLfffjv23nvvOP/889Oyzz77VJ5EREqAAAECBAjE4sWL44svvoiFCxeusyxYsKDs+KJFi6K0JP9+W3tJXpBSv379aNCgQVlZez85lxyrV69e1K1bd73r5Fzt2rWjRo0aa1/GPgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEClVBg5uK50XbsgEoYuZAJVE6B5zoPjvZNW1XO4NeK+uuvv45JkybFuHHj4umnn07nWTzooIOia9euaWnTps1aLexWB4GVK1fGn//85/Tn4k9/+lMkc3D6udi6d/6tt96Kc889N+bOnRuPPPJIdOnSZeteUO8ECBAgQIAAAQIECBAgQIDAVhXIKFm1bNUr6JwAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAga0msGTJkrjyyivj4Ycfjh49esT9998fjRs33mrX0zEBAgQIlI/Am2++GSNHjozf//73MWfOnDjssMOiZ8+ecc4550STJk3K5yJ6IUCAAAECBCqcQEFBQSxatOgb5auvvorFixeXleTfeqvvJ9vJsWXLlsXSpUsjeQnLty1ZWVlRu3btqFOnTrpOtlcvNWvWjJycnLSsvp0cS/aTkp2d/a0lMzMzMjIyonS99naybyFAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBiiGwrjll1nes9Pi3rZNzq5cky9X3195efa6b5Ny69kuPrWu9+rFke+1S2mfp8WQen6Ssvr/6dlLfQoAAgcoiMHPx3Gg7dkBlCVecBCq9wHOdB0f7pq0qbR7J3IfPPPNMjB8/Pp599tl0HsRk/uSuXbtGly5domXLlpU2N4GXv0BhYWFMmTIlxo0bFxMmTIjPPvss/RlJfl6S0r59+/R7fvlfufr0eP/998fVV18d7dq1i1GjRkWLFi2qT/IyJUCAAAECBAgQIECAAAECVVQgo2TVUkVzkxYBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBaiOQl5cXF110URQXF8fw4cOjc+fO1SZ3iRIgQKAyCxQVFcXzzz8fI0eOTAdUFxQURKdOnaJHjx5x2mmnRZ06dSpzemInQIAAAQIEtpJA8h1i2bJlaVm6dOka6xUrVkRpWb58edl26bGvv/46Vq5cGfn5+ek62V57P/lOkrzE49tK8u/PpCTDUpKy9vZWSl23BAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgXAQyMzMjKVlZWel6Y7aTuptTSvteV9vs7Oy0z/Je16hRI5I+y7NkZGSUi71OCBDYNIGZi+dG27EDNq2R2gQIbLbAc50HR/umrTa7/fZq+PHHH8dll10WkyZNimTOw2OOOSa6du0aZ555ZjRr1mx7heW6lUggmZty6tSpMW7cuHS+7enTp6c/OxdccEHccsstlSiTihHqF198EX379o1nn302brzxxrjhhhvS7/0VIzpRECBAgAABAgQIECBAgAABAlsikLHqFyklW9KBtgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIVAyBRYsWRf/+/ePxxx+PPn36xH/9139FgwYNKkZwoiBAgACBDQosXbo0HRg7atSoeP7556NWrVpxxhlnxLnnnhvf//73o2bNmhvsQwUCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQILAxAiUlJWtUK93fmHVSZ30l6XT1c8XFxRu1n9Qrrbuuden51c+tfmzt7aKiorS/0nVyfn3b33autE2yXruUtlv7+ObsFxYWpv2X17r0Pq5xk7dwJzMzM7Kzs6NGjRpl69W31z63KfvJnI2lfZVuJ/ulpbSv0v1NWZf2t/Y66SMjI2MLVTQnsGkCr7/+ekydOjWdr7Rx48Yb1Xjm4rnRduyAjaqrEgECWy7wXOfB0b5pqy3vaBv3MHLkyHR++4cffjhOP/302NhnzDYO0+UqkcBbb70Vd999dzz11FORn59fiSLf/qG+8MIL0bNnz/Q7czJneYcOHbZ/UCIgQIAAAQIECBAgQIAAAQIEyk0gY9X/Ebfm/9Nabl3riAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACB7SEwYcKEuOSSSyInJyeSQXonn3zy9gjDNQkQIEBgCwTmz58fY8aMidGjR8ff/va3aNSoUXTr1i169OgRxx57bCQvTbEQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBNYlUFRUFIWFheVeCgoKIilJ3+W1TvrKz89P+yvtf2PWSY5bumRnZ0eNGjXSUrNmzTXWyfHSY6tvlx5L1muX0nqrH9/YY+vqN5mjeO2+tjRn7bevwGWXXRYPPvhgJD97Z555Zlx88cVxwgknfOtcpTMXz422Ywds38BdnUA1Eniu8+Bo37RVpct45MiR6TNlxYoVlS52AVdcgVGjRkWfPn3S72oVN8qKE1nynfbGG2+Me+65J7p27RoPPfRQOj95xYlQJAQIECBAgAABAgQIECBAgEB5CGSXRyf6IECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECg4ggkg347dOgQyUDg73//+3HBBRfEvffeGw0bNqw4QYqEAAECBL5VoGnTpulzPHmWf/LJJ/HEE0/E6NGj4+GHH45ddtklunfvHj169IjDDjvsW/txkgABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKh+AllZWZGUnJycKpt8SUlJFBYWRkFBwbeWlStXlp3Pz8//xvbqx5K+SvfXXq9+rnR7yZIlaX9J3dVL6fn1HSsqKtqs+5KRkRE1a9YsK8n93dz9pG1pSfoo7WvtY6sfL623+rHVtzMzMzcrr+rU6Ouvv47EKfnZnTBhQowdOzadq/SSSy6JPn36xB577FGdOORKgAABAgSqjMC7774b5513XnzwwQcxbNiwuPDCC6tMbhIhQIAAAQIECBAgQIAAAQIE1hTIXnPXHgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECVUFgxx13jCeffDK6d+8el112WUycODGGDx8ep5xySlVITw4ECBCoVgItWrSIn/70p2lJBn6OHj06nnjiifj1r38de+65Z5x77rnRo0ePOOigg6qVi2QJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACB6iuQkZERNWrUSEtlUyguLo78/PwoKChI18n26iU5vnLlyrJjq28n9TZnf8mSJWv0V9pHsl7XdlFR0SazZmVlRU5OzlYvtWrVKrvGurZLjyXxVLTl66+/jpKSkjSswsLCdD137ty4/fbbY/DgwXHcccfFxRdfHF26dElz3Jz4j971gNipdsO0aXKtCbNejeL/u+a6+jt859bRrG6TslPPfvSPWFGUX7a/uRtH7Lxv7Fq38RrNVxYVxGfLFsSMr+bE4oIVa5wrr53dVuVycotD45Ame8flfx3+jW5rZGbFATvsEQc32TP2rN80Pl0Vz7SvZsdr86fFKXt8N8Z/+Pdv9fpGhxt5oFXDZvH9VXG9vfDjeHH222mr5PpXH9wlfv7PMTF7+cKN7GnrVUvM3l/0abnc/3VFuUvtHWLfHZqvyv/f0Tinfhy6497x/GdvllVt13SfOL75QVFQXBR/+ezt+OcXM8rOrWtjh5x60bv1CfGrt/5YdrrzHt+L//7otbJ9GwRWF/jjH/8Y3//+9yP5nKjoy9KlS+Mvf/lLvPLKK3HXXXdV9HCrfXz3339/XHPNNek842+88Ubk5uZWexMABAgQIECAAAECBAgQIECgKgtkV+Xk5EaAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgugt069Ytjj322Lj88svj1FNPjV69esV//dd/xQ477FDdaeRPgACBSinQqlWrGDRoUFqSQaCjR49Oy5133hn77bdf9OjRI84999zYZ599KmV+giZAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIVHWBzMzMqFWrVloqaq5FRUWRn58fK1euTMu6ttd37Ouvvy5rV9p+feulS5dudN2CgoJN4srKykqNc3JyIimJ+err9W3Xrl27rN7qbdbeXnu/tL/VjyfHkvtduqxYsSJKSkpKd8vWhYWF6faUKVPixRdfjLp160bv3r3jggsuiAZ771JWb2M2ps7/IM7e+8i4/6hL0urFfymJCR++us6mdbJzYvSJV0ejnHrx5oIP45KXHowVRfnrrLupB9/58pM4ucWhccVBp8fc5V/Gba8/FbkNdo2++54Yh+3c+v+xdx/wVVTp/8e/N71BCCWQBAQDhF4MNQSkI0JAiKhghwVsKypWFFSwADZk7T/buvYVA4ggvYh0QUFQaoBACgECpPf7v3PY3H8iSJGWhM/wGubMOc855znvmdy7m91h9Pn2pXpq7WfKLTy+9rMd/2Txvo71tHOM/WjLgQ7nEyNaVrlSH3Udpcz8HH3wxwLNiVuv6j6VdHP9zprZ+ynZbDbNi9ug9PzsEzufQ02dCoEa6lj3PU2u1X3L33WO1MKRz61hXTRjz2olZKY46y9FoXetcOUVFpy363+yNdzZsJtq+VXT0oTNuj40Qh2DGmtR/EYTOqnd7RriuA6puZmOmKoaG36jnv35S039bdbJhjJ1b3QcqbaB9TVl00xnTHLWUU2NHKHRKz9Ugb3QWV/WCuvXr1fVqlVVu3btspZ6qcx39uzZ5p3QlmtKSkqp/v4tApw7d64effRRFRYWavLkyUXVHEuZQFJSkoYNG6b58+frqaee0rhx4+Tm5lbKsiQdBBBAAAEEEEAAAQQQQAABBBA43wL8t//zLcp4CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgiUMoEqVaro888/10033aS7775bTZo00bvvvqv+/fuXskxJBwEEEEDgbARatmwpa580aZJWrVqlL7/8Um+99ZaefvpptWrVSjfccIMGDRqkunXrns2wxCKAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIXOYCrq6u8vb2NntpobDb7crJyXHu2dnZplx0tNpOVj5ZXfHYovbU1FTT3zovqisqFz+3+lq5nOnm7u4uLy8vs1vjnGorKCgwzenp6Xrvvff05ptvKqxxQ+V2qCSPiDqn6upsyynI07exK/V65HC5ubjqgWZRmrF7tbO9eGFIvauVV3h8zkX7N2rr0f3Fm8+pfDQ3Q5/vWKYHm/fXrtQkfbZjqXO8R1sO1FPhN8rP3Vv3LH/HWX+uhYz8HLP2AXXaqVW1eiWGuz60g97pdI9pf2DF+8otzHe2f793ndYc2K4pkf+Ql5uH0vNPfZ2cHc+wsCctWR9vXah7mlyr/P95W11n7lmj0M9HKiUn7QxHOvOwKl4V1LJKqBbFbzxtp/ua9FG24775cOuC08aeS0C3kOZ6d8tcM0TXkGaaF/eLKfer3UaFjp+pKz8fbo6dg5ro390e1LhWNxkjy+/P2x1h3dSoUs0/V2tt8g5VcPfR1MgR+udP753QXlYqoqKilJSUpLZt2+qOO+4w7yquVq1aWUm/VOUZFxenZs2aKSwsTOvXry9VuZ0qGevd1N98841+/vnnU4XRdgkFZsyYoREjRsjf31/Lly9XRETEJcyGqRFAAAEEEEAAAQQQQAABBBBA4GIKuF3MyZgLAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEELp1A//791alTJz344IO67rrrNGTIEE2dOlU88HfprgkzI4AAAudDwGazqUOHDmZ//fXXtWTJEn399dd65ZVX9MQTT6hly5ayHva19gYNGpyPKRkDAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBiypgvcPTy8vL7Bd14pNMlpubq+zsbLPn5OSctmzFWnHWu0fT0tJOMuKJVXl5ebLWvP33rXItDJRHRJ0Tg/6iJqsgV9uPJchFNl1Vta461Wis5Um/nxA9tGF3/Wf7Ej3cYoDS8rJPaD/XirTcrJMO8f7v8zXmqkEaeGV7jVrxf8orLDhp3N+tLLAXyu74U7RV8aqgVyOG6Vhuhh5b/W/lFuYXNTmPH29bqMH1OsnHzcNZdz4Lhfbj+RQdi8ZOyTmz+6Eo/kyOLo775sPO92vmnrWnDW9UqaZGNOqlltMePG3suQT4e/g47sVQLU3YLFebizoFNdFjq/5thmwTGKax6z5Tkc2yxC2KiV2lfzTqafrsSUsuMXXdijXUvEodzd23QTfUjSzRZp0sit+oR1sOVPeQFqZ8QkAZqLB+/q1t3bp1Wr9+ve6//35169ZNt99+uwYMGKAKFSqUgVWUjhSvuOIKk0idOnVKR0JnkYWLi4usna10CRw7dkwPPPCAPvnkEw0bNkxTp06Vn59f6UqSbBBAAAEEEEAAAQQQQAABBBBA4IIKuF3Q0RkcAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEESpVAQECAeZjopptu0t13363GjRubB4ZvueWWUpUnySCAAAII/D0BV1dX9ejRw+zvvvuuli5dqmnTpumNN97Q2LFj1bRpUw0aNEg33HCD+Q74e7PQCwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgctXwMPDQ9ZesWLFs0L4+OOPTxnv4uIim82mgoICXXXVVbrxxhvVumdHDdr41in7nayx0F6oNzfP0dtX36NRzfppedLvJcJ61mypDQdjlZx1rER90YmXq7s6BjVWiypXqsAx1tc7lysx84hpbhxQSy0d9dZmtS2O/00tqtZRoJe/8goLNH33auXbC0z7yf7KKchTod0uF5tLieYWVeooonpDebt5auPh3Y5xN5Vot0783LzUs1ZLNagUoviMwyYmPiPlhLjiFY+3vF6VPH01ccM0peVlFW8qUb7rx7eUkp3urKvk4avrQzvow60L1KNmCzUNuEJvbJ5t1ly3Yg21CayvJo66Ncnb9f3edc5+RYUOjrVYhrmO9f56eI+ptste1Cyb40/HGo2Unp+tXw7FOutreAeY+YJ9K2vNgW1alrjF2ebqMOsU1ER2h/va5B3qfUW46vsH69vYldqVmiQPFze93+Wf6hLSTAezUx2z2fVD3HodyDrqHKN4YXybm/VN7IriVc7y6a5HsE9lXXtFK+NjraN7SAslZKbo0+1LlO1Ys7WFOXLrHNzU5HgsJ9PhGaEgnwBH/nbT17qnpv72nbkfnBM7CnP3bdA/GvXU0ZyM4tVys7lqXKub9M/l72lM+KASbcVP3tnyg55tPcTcH8XNi8eUhbLlZH0eWNvixYvN7ubmpn79+unWW2/VtddeK09Pz1K1lPT0dM2YMUPbtm1Ts2bNdM0118jf379Ejhs2bNDy5cuVmZmp8PBw9erVy3z2WUH5+flasmSJrM/DiIgIzZo1y4w1ePBghYWFmXGs9rVr15pylSpVNHz4cFO23ue8Zs0aBQYGaujQoabufP5lre3TTz9VXFyc6tevr7Zt26pRo0ay3itdtK1cuVK5ubmm/pNPPlGXLl1MXFZWlqz8rLVb8bfddptCQkKKupljSkqKeR/1nj171Lp1a/NzYn0nsJUegUWLFpl7y7rGM2fOVP/+/UtPcmSCAAIIIIAAAggggAACCCCAAAIXTaDkb7Yv2rRMhAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAApdSoE+fPtqyZYt5+Nd6QMw6tx42Y0MAAQQQKD8C1kPA3bt31zvvvKOEhATzcHDnzp313nvvqUmTJmrcuLGefvppbdp04j+EUH4UWAkCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKlQyArK+uERKx3kNpsNlnHq6++Wv/6178UHx+vDRs26IknnlBo/Xon9DnTim9iVyghI0U9a7VU44BaJbrd2+RavbVldom6ohNfN09tGPS6svNzNWXTTLnZXDUvary8XN1NyO9H9snuKL199T3qFtJcB7OPqdBu1831O2th/Ebl2wuKhjrpsXvNFnJzcdWqA1uVV3g89oW2t+rBZv01d98GLdq/URPa3KxZ145TgKefc4ymla8weeQ7+rz/x3z5e/hqTfSrGlyvkzPmZIV21cNM9Y5jCSdrdtbtSUtWen62OR9S72r9PvgtTW5/h0Y06qVnWg3Rs46cGlaqqXscdq9HjtBXO5c78pinF9repmENezjHsQrjWt2kmxx5vbl5tr6NXaXHW0abdsvN2hpUCtHHXUdpVp9xalkl9Hil4+9ONRrrifDrtenwHm0/Gq/PezyiVyKGmvZKjvX+X+f7NKP3k7olrIv+1XGk2gaGaXijnprd52lZ7dY1svysLSEzRTsda84uyDXnf/6rkWMtvWpdpYX/iy/efrrrcUNopFYOnKznHdfttQ7DzFqbOK7Py45crVyse8baMhyefzjul9oVqplru+NYour6B5kcrfqdjvPD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y7YzbUjMPHLa0JyCPI366f80JnyQHlzxgSPFkkmeyfWwJonPOL7O0054EQJebHubXts4U+sOnts9chFSLbdTVKpU6ZRrs9513KBBg1PGnE2jj4/P2YQ7Y2fPni1rP9UWEhKi3r17684771RcXJxcXV1Vr149Z5euXbvqv//9r/P8rwrW+599fX2dzZaBh4eH87yoUK1ataKi/Pz8nGUK50/g448/1iOPPKIKFSpozpw5uvbaa8/f4IyEAAIIIIAAAggggAACCCCAAAKXhYDbZbFKFokAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALnTcDd3V1PPPGEBg8erFGjRqlbt2669dZb9eqrryowMPC8zcNACCCAAAJlW8B6APvxxx83+5EjRzR37lzzMPRLL72kxx57THXq1FGfPn3Ut29fWQ85e3t7l+0Fkz0CCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIFDGBFYe2CoPV3c93/YWjV37ueyOP2eyebt5ys3mKl/HMSM/50y6XPCYB5v106+Hd2vW3nUXfC4mKPsCV155pXm38qlW4u/vr02bNikxMVEffPCBevToodq1a2vPnj1au3ataRszZsyphqCtlAjs3LlTd911l5YuXar7779fL7zwgnx9fUtJdqSBAAIIIIAAAggggAACCCCAAAJlScCtLCVLrggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIFB6BOrUqaPvvvtOM2bM0AMPPKAGDRpo4sSJGjlypFxcXEpPomSCAAIIIHDJBQICAjRkyBCzFxYWas2aNZozZ45mz56td955R15eXuZB6b59+6pPnz6yvmPYEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBC68wNKE3/T7kTi5Od5PnldYcNoJbwiNVLeQZrLZbBrf5mZ9sm2xfkvZe9p+Fzrg610/KTHzyIWeptSOn5eXp//85z+KiopS5cqVS22epSWxxo0by9pPt9ntdh05ckRfffWVHnjgAbm5ualZs2YaOnSoJkyYIA8Pj9MNUSbbf/vtN82dO7dM5l486ZycHE2aNEkTJ05UgwYNtGrVKrVt27Z4CGUEEEAAAQQQQAABBBBAAAEEEEDgrARsjl8Y2c+qB8EIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAwJ8EMjIyNH78eE2ZMkXh4eF666231Lp16z9FcYoAAggggMCJAomJiZozZ45mz56thQsXKi0tzTw03adPH/Xt21eRkZFyd3c/sSM1CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIlCGBd999Vz4+PoqIiFD9+vXPKPPY1CSFT3vojGIJQuBiCVR095bNZnNOl1OQp2zHXh62eVHj1S4wrMwtJS4uTvfee695R3BBQYG6dOmi6OhoDRgwQEFBQWVuPaU14by8vHL7vmW73a5169Zp2rRpmj59unbu3Kng4GD94x//0IQJE0rrJTllXgsWLNB9990n6z3azz77rB544AG5ubmdsg+NCCCAAAIIIIAAAggggAACCCCAwOkEbI5fpNhPF0Q7AggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIHAmAps3bzYPQf30008aNmyYXnzxRVWrVu1MuhKDAAIIIICArIeff/zxR82ZM0ezZ8/Wtm3bVLFiRfXq1Uu9e/c2x1q1aiGFAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg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AmVIIDs7W/Hx8dq3b98J+/r163X48GHl5eWddkUuLi6qWrWq7nr4fk2tuFE2D7fT9iEAAQTOXWBe1Hi1Cww794HK0AjWu4FjYmLMvmXLFvPZc9111yk6Otq8G9jT07MMrebyTjUhIUEzZsww13LZsmVydXVVjx49zLW0rmmVKlXKDZD1DuvXXntNzz//vKpXr64pU6aof//+5WZ9LAQBBBBAAAEEEEAAAQQQQAABBEqvgM3u2EpvemSGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAAC5UkgJydHL7/8siZOnKjg4GBNnTpVffr0KU9LZC0IIIAAAqVAIDMzU8uXL9eiRYvMbj2Abm2tWrUyD5x3795dkZGR8vb2LgXZkgICCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQFkQyM/PV0JCgvbt2+fc4+LitH//fud5cnKycymenp4KCQlRrVq1zJ6amqpZs2bJbrc7Y4oXbDabOQ0KCtK4ceM0bNgw7c9OUfi0h4qHUUYAgQsoMC9qvNoFhl3AGUr30Dt27FBMTIzZ161bJz8/P/Xt21fR0dG69tprzXnpXsHll92uXbs0ffp0c81Wr14tX19fc62sa2ZduwoVKpQ7FOu7dPTo0eY7ecyYMXr00UdlfeeyIYAAAggggAACCCCAAAIIIIAAAhdDwOb4Jf/Jf8t/MWZnDgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEELgsBayHmx9++GF98803ioqK0uuvv666detelhYsGgEEEEDgwgukpKRoyZIlWrRokdm3b99uHubt0KGDunfvrm7duql169Zyd3e/8MkwAwIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAqRMoKChQUlKS9u/fL+sdzCfbrfbCwkKTu5ubm4KDg1WrVq0Se82aNZ3ngYGBstlszrWuWLFCHTt2dJ4XFYpirrjiCj3zzDO67bbbZI1vbbGpSQqf9lBRKEcEELjAAvOixqtdYNgFnqVsDG99Hk6fPl0xMTFavny5ef9vr169FB0drX79+qly5cplYyHlMMvffvvNXBfr2mzatElVqlQx18S6Nj179pSXl1c5XLXMWkePHm3eU33jjTfqlVdeMd+55XKxLAoBBBBAAAEEEEAAAQQQQAABBEqtgM3u2EptdiSGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAAC5Vpg8eLFGjVqlHbu3KlHHnlETz75pHx8fMr1mlkcAggggMClF7AePLe+gxYtWmT2+Ph4+fr6qkOHDurcubO6dOmiNm3ayMPD49InSwYIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAuckkJOTo4SEBFnvtLR2612Wfy4nJiaqoKDAzOPi4qLq1aurVq1af7kHBQXJijubLS4uTrVr13Z2sfoXFhaqXr16Gj9+vG666Sa5uro6261CbGqSwqc9VKKOEwQQuHAC86LGq11g2IWboIyOfOjQIc2cOVMxMTHmncDW52UXx3uAo6Ojdd111yk4OLiMrqxspG2327V27Vrjb12DnTt3GvMBAwaYa2C9l9nNza1sLOZvZHngwAGNHTtWH330kVq1aqUpU6YoMjLyb4xEFwQQQAABBBBAAAEEEEAAAQQQQODcBWyOX9bYz30YRkAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEE/p5Afn6+3nzzTT377LOqWLGiJk+erMGDB8tms/29AemFAAIIIIDAWQps375dy5Yt09KlS83R+kc8vL29FRERYR5Ctx5+bteunTw9Pc9yZMIRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAA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1qxZY47/j707D9KqOhMH/H4NDWFtaEAgAsqiKKsEIjKiI24RMwkaNiUIgwoxpTPKZB+TlDNxdLKVZgwToqASVETAgBUdIpboKILsKBhxQcB2WrZuaPaG7u/X3zfT/aNVIkhjb8+peuuce++557zvwx9QdN97ly9fHvv27YsmTZrEueeeG+edd170798/PW7dunWNtlAcAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgcPIE8vPz48MPP4wtW7aU9UeOc3NzIxVbt26N4uLiskQaN24cbdu2/dTIzs4uu8eAAAECBAgQIHA8Aps2bYqf/exnMW3atOjUqVPccccdMXLkyMjIyDieZcwlQIAAAQIECBAgQIAAAQIECFSaQCJZ0iptdxsTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEDgMwgsXbo0vvOd78SiRYti1KhRcdddd0WHDh0+w0puIUCAAAEClSNw+PDheP311+PVV1+NJUuWpOOtt96K1K/2tmvXLvr161cWffv2jZYtW1ZOonYlQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEKl1g165d8eGHH8aWLVuO2qeupaKwsLAs3zp16kSrVq2iTZs20bp163SfGrdt2/Zj0ahRo7L7DAgQIECAAAECFSmQk5MTd911V0ydOjX9Deef/vSnMXr06Ej9W0UjQIAAAQIECBAgQIAAAQIECFQngUSypFWnhOVKgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBUoE5c+bED37wg/jggw9i4sSJ8aMf/SiaNGlSellPgAABAgSqlUB+fn6sWLEili9fXhabNm1K13DaaadFv379yqJv377RvHnzalWfZAkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIH/FSgqKort27fH1q1bPzG2bNkSqfjwww/T/cGDB8voMjIyomXLltGmTZto3br1X+1btGgRqfkaAQIECBAgQKAyBN577734+c9/Hg899FD63yw//vGPY9y4cVG3bt3KSMeeBAgQIECAAAECBAgQIECAAIETFkgkS9oJr2IBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIVJJAYWFhTJo0KX72s59FZmZm/OQnP4kJEyZEvXr1Kikj2xIgQIAAgYoTSL3MZfny5bFixYp0nxrn5OSkN+jYsWP06dMnevfuHeecc046OnToUHGbW4kAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBA4ZoGdO3fG1q1bPxbbtm372LkdO3ZEMpksW7tu3brRsmXLOOWUU9LRunXrSEWbNm0+1qfm1alTp+xeAwIECBAgQIBAVRNYv3593H333fHoo49G+/bt44c//GH8/d//fdSrV6+qpSofAgQIECBAgAABAgQIECBAgMBxCSRKfsDz/3/Cc1y3mkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGqI5CXlxf/9m//Fv/5n/+Zfqj9jjvuiOuuuy4yMjKqTpIyIUCAAAECFSCwZcuWWL58eaxcuTJWr14da9asiQ0bNqRf/NK8efPo3bt3nHPOOWV9t27dPBRdAe6WIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIHaI1BcXBz5+fmxbdu22L59ezpKx6X91q1bozRS5woLC8sBpb4xeMoppxw1WrVqVXYtOzs7EolEufsdECBAgAABAgSqm8Brr70Wd911V8yaNSvOOOOM+Od//ucYNWpU1K1bt7qVIl8CBAgQIECAAAECBAgQIECAwCcKJJIl7ROvOEmAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFqKJCTkxP/8i//Eg8//HCceeaZceedd8bVV19dDSuRMgECBAgQOHaB3bt3x5o1a2L16tVl/dq1a+PAgQORmZkZZ599dvTo0SMd3bt3T/cdO3b0cphjJzaTAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBKqxwN69e2Pbtm2xffv2dHzaOC8vL4qLi8tV3LRp02jVqlW0bNkyHaecckocLVLzUt8T1AgQIECAAAECtUHg+eefj1/+8pcxf/786NWrV9x+++0xbNiwyMjIqA3lq5EAAQIECBAgQIAAAQIECBCoRQKJZEmrRfUqlQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBWiLw1ltvxU9+8pOYNWtW9OvXL+6+++645JJLakn1yiRAgAABAhFFRUWxfv36WL16daxZsybWrl0b69ati82bN0fqV4gbNmwY3bp1i+7du6ejR48e6b5Dhw74CBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAlRRIfasvPz8/duzYkY68vLy/Ot6+fXuk4sCBA+XqqV+/frRs2bJctGrVquy4dFzap+ZmZmaWW8MBAQIECBAgQKA2Cxw+fDhmzZoVv/rVr2LlypXxt3/7t/G9730vrrzyykgkErWZRu0ECBAgQIAAAQIECBAgQIBADRZIJEtaDa5PaQQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI1HKBVatWxe233x7/9V//FRdffHHcdddd0b9//1quonwCBAgQqM0Cu3fvjjfeeCPWrVsXa9euLev/53/+J83SpEmTOOuss6Jr167l4owzzogGDRrUZjq1EyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAFCRQXF0dBQUHk5+fHjh070pGXl/ep4127dkUymSyXRepbe9nZ2dGiRYt0HDlOnWvVqlW0bNkyHaXj1Lf7NAIECBAgQIAAgeMXSH0jeerUqXHvvfdGTk5ODBs2LL773e9Gv379jn8xdxAgQIAAAQIECBAgQIAAAQIEqplAouQHVeV/UlXNCpAuAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEjkXgpZdeih/96EexaNGiuOqqq+LOO++M7t27H8ut5hAgQIAAgVohsHPnzli7dm288cYb8eabb8b69evTsXHjxigqKopEIhEdOnSIrl27lovOnTunz9epU6dWOCmSAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBP5XIPWtu9S38PLz8z8Wpefz8vI+cc6uXbsimUyWo6xbt25kZ2eno0WLFpGK1HHp+KPHpdcaNGhQbh0HBAgQIECAAAECFS+Q+u7xpEmTYtq0aVFcXBzXX399/NM//VOcfvrpFb+ZFQkQIECAAAECBAgQIECAAAECVVQgUfIDrvI/4aqiiUqLAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECFSHw9NNPx+233x6vv/56jB49Ou64447o2LFjRSxtDQIECBAgUCMFCgsL45133on169d/LFIv4km11Et2TjvttOjcuXN06tTpY32TJk1qpI2iCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUJ0FDhw4ELt27SqLnTt3lo2PPF86zs/Pj9JIzS0oKPhY+RkZGZGVlRXNmzc/ajRr1ix9LTs7O1LjFi1aRGqcuk8jQIAAAQIECBCoOgLFxcXx9NNPx3333RfPPfdcdOzYMW6++ea4/vrr0/+OqzqZyoQAAQIECBAgQIAAAQIECBAg8PkIJJIl7fPZyi4ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCoGgKpX5t6/PHH46c//Wls3rw5xo8fHz/+8Y+jTZs2VSNBWRAgQIAAgWoisH379nj33Xdjw4YNH+s/+OCDKP1V5VatWkWnTp2iQ4cO6Wjfvn3ZOHUudV0jQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgWMTOHjwYOzevbtcFBQUlB3v2rUrjiUKCws/tmEikYjGjRtHVlbWx6J58+aRimbNmqX70uMj+6ZNm0ZqDY0AAQIECBAgQKD6CqS+XfzQQw/F7373u9i4cWNcfvnl8Q//8A8xePDgyMjIqL6FyZwAAQIECBAgQIAAAQIECBAgcIICiWRJO8E13E6AAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFqKXD48OF48MEH41//9V8jPz8/br311vje976XfvlAtSxI0gQIECBAoAoJpF4o9N5778W7774bGzZsSI83b94cpbF169Yo/VXmL3zhC9G+ffvo0KFDOtq1axdt27ZNR5s2bcr6+vXrV6EKpUKAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFPF0h9t23v3r2fGHv27Indu3eno6CgoGxceu7I/sjrhw4d+sSNE4lENG7cOLKyso47mjVrlr6nadOmkZGR8YnrO0mAAAECBAgQIFCzBV5++eWYPHlyzJ49O1LfHR4zZkzccsstceaZZ9bswlVHgAABAgQIECBAgAABAgQIEDhGgUTJD/+SxzjXNAIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECNRIgQMHDsRvf/vb+Pd///dIvfzg1ltvjYkTJ0bz5s1rZL2KIkCAAAECVUHg4MGD8f7776dj8+bNURqpczk5OZGbmxt5eXnlUs3Ozo62bdumo02bNum+VatW0bJly3S0aNGibJx6+ZCXDpXjc0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwEcEiouLI/Vt0/3795f1qfGRkbq+b9++2LNnT+zdu/e4I3XvX2v16tWLpk2bRpMmTY4ax3q9UaNGkUgk/tp2rhEgQIAAAQIECBAoJ1BQUBDTp0+PyZMnx9q1a6Nv377x7W9/O6699tpo2LBhubkOCBAgQIAAAQIECBAgQIAAAQK1XSCRLGm1HUH9BAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQSAns3r077rvvvvj1r38dRUVFceutt8bEiROjWbNmgAgQIECAAIFKECgsLIwPP/wwcnNzy8WR57Zt2xY7duxIv0TpyBQzMjIiOzs7WrZsmY7UOPXSo1RkZWWV6488lxo3aNCgLOrXr3/kssYECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECJyAwOHDh+PQoUNR2qfGqe+WpeLIcem50j517cCBA3Hw4MGjxpHX9+/fH6WROl86PrIvnX8s5WRmZkajRo2OKRo3bnxM81LrpebWq1fvWFIwhwABAgQIECBAgECFCixfvjx+//vfx4wZMyKZTMa1114bN910U/Tr169C97EYAQIECBAgQIAAAQIECBAgQKAmCSRK/iMlWZMKUgsBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgROVGD37t3xH//xH/HrX/86iouL47bbbouJEydGVlbWiS7tfgIECBAgQOAkCaRevrR9+/bYsWNHuk+NjzzOz8+PXbt2RUFBQVlfOk69DOpoLZFIxBe+8IVo0KBBuUidS73EqU6dOp8YGRkZZedTa2gECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEKksgmUymt071Hx0feZz6jucnRVFRUbnzqePDhw9Hqj9y/NFzqe+Epc6l+tLxiRikvh1Wv379skh9U+zI49Jx6vzRvkH20e+SlR5/dH7quGHDhmXr1K1b90RSdy8BAgQIECBAgACBKiGwZ8+emDFjRkyePDlWrlwZPXr0iAkTJsSYMWMiKyurSuQoCQIECBAgQIAAAQIECBAgQIBAVRZIlPyA9X9/+lqVs5QbAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEKkGgoKAgfvOb38Q999yTfrnFbbfdFqnw8Fol/GHYkgABAgQInESBAwcOxK5duyL1d38q9u/fXy5S1488V3p85AuqSl9e9Un9SUzd0gQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgSOSSCRSEQqUu2TxqlzGRkZnxp16tSJ0qhbt+7HxqXnUn0qMjMz03G0cen1evXqxadFKj+NAAECBAgQIECAAIHjF1i9enX8/ve/j0cffTQOHToUw4YNi5tuuinOP//841/MHQQIECBAgAABAgQIECBAgACBWiyQSJa0Wly/0gkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIPCpAgUFBXHvvffGPffcE6lfubrlllti4sSJ0aJFi0+91wQCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIPB5CuzduzdmzJgR999/fyxbtizOOuusmDBhQowdOzays7M/z1TsRYAAAQIECBAgQIAAAQIECBCoMQKJZEmrMdUohAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAidRoKCgIO6777649957Y//+/fHtb387vvOd70SbNm1O4q6WJkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECny6watWquP/+++PRRx+NwsLCGDp0aHzrW9+KCy+88NNvNoMAAQIECBAgQIAAAQIECBAgQOCvCiSSJe2vznCRAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBMoJ7N27NyZPnhy/+tWvYufOnTF+/Pj4/ve/H+3atSs3zwEBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEDiZArt3747HHnssHnjggVixYkWcddZZMWHChBgzZky0aNHiZG5tbQIECBAgQIAAAQIECBAgQIBArRJIJEtarapYsQQIECBAgADRVPidAAA6nUlEQVQBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKgggQMHDsSUKVPiF7/4RWzZsiXGjh0bP/zhD6NTp04VtINlCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAxwVeffXVeOCBB+Lxxx+P4uLiGDZsWIwfPz4uuOCCj092hgABAgQIECBAgAABAgQIECBA4IQFEsmSdsKrWIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQK1WKCwsDD+8Ic/xN133x2bNm2K4cOHx/e///3o06dPLVZROgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhUpEBeXl5Mnz49pkyZEmvXro2ePXvG+PHj47rrrotmzZpV5FbWIkCAAAECBAgQIECAAAECBAgQ+IhAIlnSPnLOIQECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAh8BoGioqJ44okn4he/+EWsXr06Lr/88vjBD34QF1988WdYzS0ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUNsFkslkPP/88zFlypT44x//GJmZmXHNNdfEjTfeGP3796/tPOonQIAAAQIECBAgQIAAAQIECHxuAhmf2042IkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQA0XqFOnTlx77bWxatWqmD9/fhw6dCguueSS+PKXvxyzZ8+O4uLiGi6gPAIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCoCIGcnJy48847o3PnznHppZfGxo0bY9KkSZGbmxsPPPBA9O/fvyK2sQYBAgQIECBAgAABAgQIECBAgMAxCiSSJe0Y55pGgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAscpsHTp0vj5z38ec+fOTT9Y993vfjfGjh0b9evXP86VTCdAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBmixQWFgYTz31VEydOjWeffbZaNGiRYwePTpuuOGG6N69e00uXW0ECBAgQIAAAQIECBAgQIAAgSovkEiWtCqfpQQJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQzQXeeuut+OUvfxnTp0+P5s2bx2233RY33XRTZGVlVfPKpE+AAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECJyLw2muvxYMPPhiPPvpo5OXlxVe+8pW44YYb4utf/3pkZmaeyNLuJUCAAAECBAgQIECAAAECBAgQqCCBRLKkVdBaliFAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBTxHIzc2Ne++9NyZPnpyeef3118c//uM/RseOHT/lTpcJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoKYI5OXlxYwZM+LBBx+MlStXRufOnWPcuHExduzYaNeuXU0pUx0ECBAgQIAAAQIECBAgQIAAgRojkEiWtBpTjUIIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQTQR27doV999/f/z2t7+NDz74IIYMGRITJ06MgQMHVpMKpEmAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECxyNQVFQUzz77bDz88MMxb968qFu3bgwfPjzGjRsXF1544fEsZS4BAgQIECBAgAABAgQIECBAgMDnLJBIlrTPeU/bESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMD/CRw+fDiefPLJuOeee2LJkiXRr1+/mDhxYvohvczMTE4ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUM0F3njjjZg2bVpMnz49cnNzY+DAgTFu3LgYMWJENG7cuJpXJ30CBAgQIECAAAECBAgQIECAQO0QSCRLWu0oVZUECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCo2gJLliyJe+65J+bMmRNt2rSJW265JSZMmBDZ2dlVO3HZESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEA5gR07dsTjjz8e06ZNi2XLlsVpp50WY8aMibFjx0bnzp3LzXVAgAABAgQIECBAgAABAgQIECBQ9QUSyZJW9dOUIQECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBGqPwObNm+O+++6LBx54IA4dOpR+gO/WW2+Nrl271h4ElRIgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKCaCaS+T//000/HH/7wh3SfmZkZQ4cOTX+zftCgQZFIJKpZRdIlQIAAAQIECBAgQIAAAQIECBAoFUgkS1rpgZ4AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEqo7Anj174qGHHorf/OY3sWHDhrj88svj5ptvjq9+9auRkZFRdRKVCQECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBGqxwJIlS+KRRx6Jxx9/PPLz8+Oiiy6KsWPHxtChQ6NRo0a1WEbpBAgQIECAAAECBAgQIECAAIGaI5BIlrSaU45KCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUPMEiouL409/+lNMmjQpFixYEB06dIibbropbrjhhmjVqlXNK1hFBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQqOICGzZsiEceeSQdb7/9dnTr1i1Gjx6djvbt21fx7KVHgAABAgQIECBAgAABAgQIECBwvAKJZEk73pvMJ0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIHKEUg9+Pe73/0uHn744di3b18MHz48br755jjvvPMqJyG7EiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoJYIbN26NZ544ol49NFHY8mSJdG6deu49tprY/To0dG3b99aoqBMAgQIECBAgAABAgQIECBAgEDtFEgkS1rtLF3VBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQqL4C+/btixkzZsSkSZNi1apV0adPn7j55pvTDwc2bNiw+hYmcwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECFQhgd27d8fcuXPjscceiwULFkSDBg3i6quvjlGjRsVll10WderUqULZSoUAAQIECBAgQIAAAQIECBAgQOBkCSSSJe1kLW5dAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEDj5AkuWLIlJkybFrFmz0g8Ljhs3Lr71rW9F165dT/7mdiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEANEygsLIz58+fHY489Fk899VQcPnw4Bg8eHKNGjYqvf/3r6e/K17CSlUOAAAECBAgQIECAAAECBAgQIPApAolkSfuUOS4TIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQDUQ2LZtW0ydOjUmT54cmzZtigsuuCBuvPHGGD58uAcIq8GfnxQJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKg8gaKioli4cGHMnDkz5syZEzt37kx/M/6b3/xmDBs2LLKzsysvOTsTIECAAAECBAgQIECAAAECBAhUukAiWdIqPQsJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgECFCRQXF8eCBQtiypQpMW/evGjYsGGMGjUqbrzxxvjSl75UYftYiAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAtVZIPVt+JdeeilmzpwZs2fPjm3btqW/CX/NNddEKtq3b1+dy5M7AQIECBAgQIAAAQIECBAgQIBABQokkiWtAtezFAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhUIYHUA4bTpk2LqVOnxptvvhl9+vSJG2+8Mb75zW9GVlZWFcpUKgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEDj5AslkMl555ZWYOXNmzJ49O3Jzc6NXr14xYsSIGDlyZHTp0uXkJ2EHAgQIECBAgAABAgQIECBAgACBaieQKPmPpWS1y1rCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIHDcAi+//HJMmTIlZs2aFalfHRs2bFiMHz8+LrjgguNeyw0ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCoLgLFxcWxePHimDNnTvp77zk5OdGtW7cYMWJEjBw5Ms4666zqUoo8CRAgQIAAAQIECBAgQIAAAQIEKkkgkSxplbS3bQkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgEgQKCgriscceiylTpsSKFSuiS5cucd1118Xo0aOjU6dOlZCRLQkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFCxAkVFRfHCCy/Ek08+GX/84x8jNzc3unbtGsOHD4+RI0dGjx49KnZDqxEgQIAAAQIECBAgQIAAAQIECNRogUSypNXoChVHgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAkcVWL16dUybNi1mzJgRW7ZsiYEDB8Z1110XI0aMiGbNmh31PhcIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQ1QQKCwvjueeeizlz5sS8efNix44d0atXrxg6dGg6unfvXtVSlg8BAgQIECBAgAABAgQIECBAgEA1EUgkS1o1yVWaBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIHCSBA4fPhzPPvtsTJ8+Pf0gY3FxcXzta1+LMWPGxBVXXBGZmZknaWfLEiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4LML7N69O+bPnx9z586NP/3pT5E6/vKXvxxDhw6Nb3zjG9GlS5fPvrg7CRAgQIAAAQIECBAgQIAAAQIECPyfQCJZ0mgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFSgYKCgpg9e3ZMnz49XnzxxWjRokVcc801MWbMmPSDjqXz9AQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKgMgZycnHjqqafSsXDhwigqKoqBAwfG1VdfHd/4xjeiffv2lZGWPQkQIECAAAECBAgQIECAAAECBGqwQCJZ0mpwfUojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgRMQ2Lx5czzyyCMxffr0ePPNN+OMM86IkSNHxogRI6Jnz54nsLJbCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgcOwCa9asiXnz5sVTTz0VK1asiMaNG8dXvvKVGDJkSHz1q1+N7OzsY1/MTAIECBAgQIAAAQIECBAgQIAAAQLHKZBIlrTjvMd0AgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKiFAsuWLYsZM2bErFmzIicnJ84+++wYMWJEjBw5Mj2uhSRKJkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEDgJAns3bs3nn/++XjmmWfSsXnz5vjiF78YX/va12LIkCFx8cUXR/369U/S7pYlQIAAAQIECBAgQIAAAQIECBAgUF4gkSxp5U85IkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECRxdI/drZK6+8EjNnzozZs2dHbm5u9OjRI0aOHBkjRoyIM8888+g3u0KAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGjCLzzzjvx9NNPxzPPPBMvvvhiFBYWRt++fePKK6+Mv/u7v4t+/fpFIpE4yt1OEyBAgAABAgQIECBAgAABAgQIEDh5AolkSTt5y1uZAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBGqyQHFxcbz00kvxxBNPxJw5c2LLli1xzjnnxMiRI2PYsGHRpUuXmly+2ggQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQOAGBffv2xYsvvhh//vOf45lnnom33347mjVrFpdddllceeWVMXjw4GjduvUJ7OBWAgQIECBAgAABAgQIECBAgAABAhUjkEiWtIpZyioECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUJsFioqK0g9Xzpw5M5588snYvn17dO/ePa666qoYMmRI9OvXLxKJRG0mUjsBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBWi1QXFwcq1atigULFsSzzz4bixYtisLCwujZs2dceeWV6fibv/mbqFu3bq12UjwBAgQIECBAgAABAgQIECBAgEDVE0gkS1rVS0tGBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFCdBQ4fPhwvvfRSzJ07N5566qnYuHFjfPGLX4whQ4akY9CgQVGvXr3qXKLcCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBA4BoFNmzbFc889FwsWLEj3O3bsiDZt2sSll14al19+eVx22WXp42NYyhQCBAgQIECAAAECBAgQIECAAAEClSaQSJa0StvdxgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQKwTWrFkTc+fOjXnz5sWqVauiadOmMXjw4LjqqqvSfVZWVq1wUCQBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBmi7w/vvvxwsvvBALFy5M9++99140aNAgLrzwwrjsssvS0atXr5rOoD4CBAgQIECAAAECBAgQIECAAIEaJpBIlrQaVpNyCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKAKC2zevDnmzZsXc+fOjf/+7/+ORCKRfljziiuuiFT06NGjCmcvNQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEjhTIycmJF198MV544YVYuHBhvPvuu1GvXr0477zz4qKLLopBgwbFgAEDon79+kfeZkyAAAECBAgQIECAAAECBAgQIECgWgkkkiWtWmUsWQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCoMQL5+fnxzDPPxNNPPx0LFiyI7du3R7t27eKKK65Ix6WXXhpZWVk1pl6FECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKjOAsXFxbFu3bp4+eWXY9GiRel+06ZNkZmZGeeee24MGjQoHQMGDIgGDRpU51LlToAAAQIECBAgQIAAAQIECBAgQKCcQCJZ0sqdcUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBCpBIPWw5/Lly2P+/PnpWLp0aSQSiTjvvPPiiiuuiMGDB0efPn3S5yohPVsSIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQqHUCe/bsSX+D/JVXXomXX345Fi9eHDt37owmTZrEgAED4vzzz09HatywYcNa56NgAgQIECBAgAABAgQIECBAgACB2iOQSJa02lOuSgkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgugjk5eXFggULYv78+fHnP/85cnNz45RTTolLLrkkBg0alI4uXbpUl3LkSYAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKBKCxQVFcW6devi1VdfTcfSpUvTx8XFxdGuXbs4//zzY+DAgem+V69eUadOnSpdj+QIECBAgAABAgQIECBAgAABAgQIVKRAIlnSKnJBaxEgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgZMhsGbNmpg/f348//zzsWjRoti7d2+ceuqpMWjQoHRcdNFF0alTp5OxtTUJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI1CiB4uLiWL9+faxatSpWrlwZy5YtixUrVqS/Id6oUaPo169fnHvuudG/f/903759+xpVv2IIECBAgAABAgQIECBAgAABAgQIHK9AIlnSjvcm8wkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQGUKHDp0KJYuXRovvPBCLFy4MF555ZXYv39/dOjQIQYNGhQXXXRRuj/ttNMqM017EyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKh0gcLCwli3bl2sXLkyVq1ale7XrFkT+/bti8zMzOjevXv069cv+vfvH+eee276uE6dOpWetwQIECBAgAABAgQIECBAgAABAgQIVCWBRLKkVaWE5EKAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBI5XIPXQ6auvvhoLFy5Mx5IlS+LAgQNx6qmnxoABA8riS1/6UtSvX/94lzefAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQJUXSCaT8d5778Xrr7+ejrVr16b7t956Kw4fPhwNGzaM3r17R58+fSL13e9U36NHj6hXr16Vr02CBAgQIECAAAECBAgQIECAAAECBCpbIFHyH3DJyk7C/gQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoCIFDhw4EK+++mq88sorsXjx4liyZEls27Yt/fBp6mHUAQMGlEW7du0qcmtrESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEDipAkVFRbFhw4b4y1/+Em+++WY61q1bF6nYu3dvJBKJOP3006Nnz57Ro0ePdN+7d+/o2rVrZGRknNTcLE6AAAECBAgQIECAAAECBAgQIECgpgokkiWtphanLgIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUCrwzjvvxJIlS2Lx4sXpeO211yL1cOupp54a5513XvTp06cs2rZtW3qbngABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEClCHz44YeR+kb322+/ne7Xr18ff/nLX9LjwsLCdE7t2rWLs88+O7p16xY9evSInj17Rvfu3aNx48aVkrNNCRAgQIAAAQIECBAgQIAAAQIECNRUgUSypNXU4tRFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgSOJrB3795YtmxZLF68OJYuXRqrVq2KTZs2pae3bt06+vTpUy46d+4ciUTiaMs5T4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQOC4BA4ePJj+vvbGjRvjvffei1T/7rvvxttvvx3vvPNO7NmzJ71egwYNIvWt7a5du8ZZZ52VjrPPPjt93Lhx4+Pa02QCBAgQIECAAAECBAgQIECAAAECBD6bQCJZ0j7bre4iQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQI1SyAvLy9Wr14dq1atKov169dHUVFRNG3aNHr37h09e/aM1AOxqYdjU/2pp55asxBUQ4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgcMICyWQytm7dGjk5OfH++++n+9Lxxo0bIxW5ubmRmpdqWVlZcfrpp0fnzp2jS5cuccYZZ6T71Dj1Pe1EInHCOVmAAAECBAj8v/bur6eJJYwD8LvLVhGsimIEBUWv9Pt/EBMvRK8gGkOrgFKL5a+czpjdFA4HiYknhT6T/PK+O922M89l080QIECAAAECBAgQIECAAIE/FyiGP+b9+jXvzz/DOwkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwLUVGAwG8ebNm3j9+nXO27dvY3V1Nba2tvKe2+12vHr1Kl6+fJlr6lNevHgRVVVdWxcbI0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhMosDe3l5sbm5Gt9uNjY2N6HQ6TerrVD99+hQHBwcN0fz8fCwvL8fS0lI8e/Ysnj9/HisrK02dm5tr7tUQIECAAAECBAgQIECAAAECBAgQIDB+AsXJcIzfsqyIAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAiMt8CXL19idXU13r17l2vqUz5+/Bjpr3lVVeWHcNODt3XqB3HT9ZMnT6Isy/HepNURIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBK6pwPHxcXz79q3J169fY3t7Oyf1m5ubOelM69TXtd/vnxJpt9uxsLAQi4uLudb90tJSpCwvL+dzraenp0+9zwUBAgQIECBAgAABAgQIECBAgAABAldLoDgZjqu1ZKslQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLjK7C7uxvv37/PWV9fj5S1tbVcP3z4EPv7+3nxrVYrnj59GisrK/H48eN49OhRfqj3bH3w4EGUZTm+G7YyAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMBfFjg4OIjBYJDz48ePGE06Wzql3+83NfXfv38/lZ2dnej1ejmpT/ecHelM6bt378b9+/cjnTH98OHDmJ+fb+pon86kXlxcjJmZmbMf45oAAQIECBAgQIAAAQIECBAgQIAAgWsoUJwMxzXcly0RIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGxE0h/2dvY2Ij19fVYW1vLNfVprtvtRqfTic+fP0d6CLkeU1NT+aHghYWF/KBwemj4zp07Of/V3759O1qtVpOqqpp+dD59tkGAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAj8/wLpzOM06jrap7l6vu5/V3/+/Jnfc15Nc+fl+Pi4mU99nXRv3R8dHeX+bD08PIw0N5o0d17Suc1pPtU6+/v7uU91NHt7e1EnzQ8Gg5y0potGURQxOzubk855Tmm326dy9lzoe/fuxWjm5uYi3VOW5UVf5TUCBAgQIECAAAECBAgQIECAAAECBCZUoBj+UPvrl90JBbBtAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAwbgLb29vR7XZzOp1OU7e2tqLX6+Xs7Ow0fT33u4eXx22f1kOAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhcTYGyLKOqqiatVitS0lzd1/XGjRtxXm7evJnnU02Znp5uaurr3Lp1K0YzMzMTdWZnZ3OfXi+K4mpiWjUBAgQIECBAgAABAgQIECBAgAABAldCoDgZjiuxUoskQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIELhTo9/vR6/Vid3c3Dg8P/5Wjo6NTc+naIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBP6uQFEU535BPX9RTa/VSR9S96mWZXnudT2f6mg/NTWVr+v5VOu5VM/29VxVVfm10Zq+3yBAgAABAgQIECBAgAABAgQIECBAgMAkCRQnwzFJG7ZXAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIXFagvOyN7iNAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMCkCZSTtmH7JUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwGUFysve6D4CBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhMmsA/17Xro0qBfmAAAAAASUVORK5CYII=\\n\",\n      \"text/plain\": [\n       \"<IPython.core.display.Image object>\"\n      ]\n     },\n     \"execution_count\": 11,\n     \"metadata\": {\n      \"image/png\": {\n       \"width\": 800\n      }\n     },\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"from IPython import display\\n\",\n    \"graph = net_drawer.GetPydotGraph(train_model.net.Proto().op, \\\"mnist\\\", rankdir=\\\"LR\\\")\\n\",\n    \"display.Image(graph.create_png(), width=800)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, the graph above shows everything that is happening in the training phase: the white nodes are the blobs, and the green rectangular nodes are the operators being run. You may have noticed the massive parallel lines like train tracks: these are dependencies from the blobs generated in the forward pass to their backward operators.\\n\",\n    \"\\n\",\n    \"Let's display the graph in a more minimal way by showing only the necessary dependencies and only showing the operators. If you read carefully, you can see that the left half of the graph is the forward pass, the right half of the graph is the backward pass, and on the very right there are a set of parameter update and summarization operators.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAAF1cAAAK1CAYAAAA6FqOEAAAAAXNSR0IArs4c6QAAQABJREFUeAHs3QmUFdWdP/Bf093Q7JuyyKKggCgooqII7hoRjbtxXxMzRmP+mpjFmDgxZpwszkxiRrNMNNHERGN2jVvUuKBGRRBFQZRF9n3faeBft8x76UZAUBq64XM9r6te1a26v/rUu8U5yTnfKlmbtdAIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQywXq1fL6lEeAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFcQLi6HwIBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAnVCQLh6nbhNiiRAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoAwBAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCo6wJzli+K6168O1auqazrl6J+ApslUL9eWXz74AujVYOmm3XcjtK5cu3q+MLzd8aClUt3lEt2nXVUYEC7nnFZz4/V0eqVTYAAAQIECBAgQIAAAQIEtq6AcPWt6200AgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKgBgdHzJ8dvxw6pgTM7JYHaL3DpnsfEwW171P5Ct0GFc5cvjrveenIbjGxIApsnMGb+VOHqm0emNwECBAgQIECAAAECBAjswAL1duBrd+kECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECNQhAeHqdehmKZUAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAjiwgXH1HvvuunQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAdEhCuXodullIJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI7MgCwtV35Lvv2gkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjUIQHh6nXoZimVAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwI4sIFx9R777rp0AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAHRIQrl6HbpZSCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECOzIAsLVd+S779oJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI1CEB4ep16GYplQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMCOLCBcfUe++66dAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQB0SEK5eh26WUgkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjsyALC1Xfku+/aCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECNQhAeHqdehmKZUAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLbu0BlZWW0adMmjjzyyLj77rtj8eLF2/sluz4CBAgQIECAAAECBAgQIECAAAECBAgQ2AwB4eqbgaUrAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECNSswPLly2PWrFnx9NNPxyWXXBKtW7eOs88+Ox566KFIwesaAQIECBAgQIAAAQIECBAgQIAAAQIECOzYAsLVd+z77+oJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQqwRWr16d17N27dpYs2ZNrFy5Mn7/+9/HCSecEG3atIn/9//+X7z88su1qmbFECBAgAABAgQIECBAgAABAgQIECBAgMDWEyjbekMZiQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIbFwgBaqv2yorK/NN8+bNix/96Edx6623xm677RaXXnppnH/++dGlS5d1D9no967N2sV+O3Wt1uexScNj71ado0Pj1tW2vzB9dExdOjffdnSHfaNFg8b5+rLKFfHQxFeq9V33y25N28S1+54aNw+7v3iOdfus73u35rvEoE77xYg5E+KZaW+sr8sW3dY9G++4bLzX506Mp6a+Xjx3eb3SOHuPQ2Ovlp1jypI58cKMt2L+isXRqkHTeHnW28V+23KlT+uu8db8ybFs9coaKaNdw5axZ8sOmcvI/LrT7+aJKSOKYzUpq4gzdh8Q6V6PWzg97h/73PtqaVzWIE7t0j86N9k5d/v7lNejcu17LxFIJzpx1wPjwXe9MKCIWktWerfaNQZ3PiAal1fEq7PHxdPTRkZ6Bvx27JBaUmHNlPFhn1sftZptPZc7Zs/+g9r2KF5GWUm9WLxqefx14tDitg+a7+ZykcoKAQIECBAgQIAAAQIECBCocQHh6jVObAACBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjULoEUYL5q1apIoeXrW65vWzqm8Fm9enVxPW3b0Pe1a9dG+qyvlZSUrG9zLFq0aL3bCxtTbalNmDAhvvnNb8YNN9wQ/fr1i8NOGxRrm66KkoryQtcNLqcvnRe9ssDwa/Y9OVZn9ff/4xdj0aplMTwLUB7Qrmd8ff+zonLN6hj4p69UC0V/MQsX//Wx10aHRq3ilEdu3uD5Czv2bd0lzu9+RPxpwj+qnaewf33L9o1axuV7DYpP9jw2rhryk4hp6+u15balIOVL9jwmPrP38XHlsz8unrhhaf147MRvxsxl8+PW1x/IQ+f//YCz47D2e8f1L/6yVoSrD+rUN1Zl96mmgtUTxsV7HhWdslD0FK5+etf+MbD9XsVw9T2atY+/Dr4hC2BelvepX1oW1+xzchz313/P3BbklqnPbz/2pfjyP+6KP45/IQZ13j+Gn/n9+Lenb4vnZ4zO+yTjHwy4LD7//B2xeu37Xy6Qd/InFxg3blz+jNh3331rVCS9VOB7/S+Jm4bel73gYGQegP+9/hdH/dLy7T5c/cM8tz7qzdjWcznVf+OB52Zz/JDipaR/u/r94dri902Z7+ZykcsKAQIECBAgQIAAAQIECBCocQHh6jVObAACBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhUF0ihrUuWLImlS5fGsmXLissNrRf6FfYvX748VqxYsdmflStX5mHqGwo8r17lR/uWwtM3FKC+sTNvTm0pHD61l156Kf80OKV3VHx8742dPt+3tHJF3PjKvXF0x31jn9a7FetcsXpV/NeIP8VZuw+M7i06xMo1752/cMLFlctj0uLZedj4pCWzC5s3uPzzhBej6z2fjrkrNh4YX/UE07Lg9x+OfDAPV6+6fUPrKQT63nee3dDuD9w+YdHM+Pnox/Nw9RQoX2iXZ2Hre7fqFGfd991iMPyv33kmvj/gU9EuC4Df1u3KvQfH8ux+3TH6bzVaylEd9okfv/FIPsaRHXrHoxOHF8f7z4MujNMe/c94Y97EaF3RNG7Y/+y4qMdReTj/VUN+mvdLfYZMGxV/m/xq/v33456Po7Nzfi0L8B/80I35tpdmvh1NyxvlAeufTYH62gYFvvzlL8fvfve76Nq1a1x00UVx7rnnxh577LHB/h9mR/16ZXmwerpXPx31aH6KF7IXK9z11pPxt4/fFI3LGsSS7BmyvbYP89z6KBa1YS53arxTlNUrjV73fbZ4KStWV8as5e+9JCFt3JT5bi4X+awQIECAAAECBAgQIECAAIEaFxCuXuPEBiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoK4LpMDvRYsWxYIFC2LhwoXFZWF98eLFse4nhaevu63wPYWlf1CIeL169aJhw4bRqFGjfFlYT98rKiqiQYMGxU+TJk3ybfXr1y9uq7q/6npZWVmUl5fHhpYb2pfqKS0tjbQsfDb2/cMEq6ffyfTp06N9+/Yf+JNJ9adw9eRxyimnxL6DBsZ/LH/qA4+r2uGOUX+LHwy8LM7rdkTc8PI9xV13j/l7fKvf+XFBtj2FsBdaaUm96NO6S1y1GQHYmxOsXhhn9Zo1hdWNLg9tt1ce6P1RwtXTAGuy33fVZVrfp9WuUS+73qb1G0YszXfnf77x8m/iPw664F8btsFazxYd47KeH4s+v7u6RkdvXr9R7LdT13hq6shI9/7Q9nvHl174RT5m+h38duyQPFg9bZizfFHcPOz+uKD7EXFQm+55n/SnbaMW0aJB4+L3tLIyC21uUFo9/umJKSPii31OzYLX9420rq1foPBChXHjxsVNN90U//7v/x59+vSJCy+8MM4666zYZZdd1n/gZmzdrWmbLOy+YbSoX/2+jVkwNX7x1hP5ywXGLpy+GWese10/zHPrw1xlbZjLqe4reg2OJyaPyMLUF0Z6yca6bVPnezrOXF5Xz3cCBAgQIECAAAECBAgQIFAzAtX/17WaGcNZCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgsE0FVq9eHfPnz88/8+bNiw19Unh6IUC9EJyelilYfX1h6ClkvGnTppHCzdf36dixYzRu3Hi9+1L/FAy+bnh6ClFPnxSIviO2NRsJFk+B6ulephD5k046Kc4555wYPHhwbvXc9FFR8tAzm0X2+3HPx38efGGcvcfAuHHob2L12vdCzcfMn5qf5+xuh8ZNw+4rho8f03HfeHbaG8XvqVO7hi0jbd+lcat4ccZb8XS2v9BKoiQGtusZiyuXx/DZ4wqb82W/Nt3isPa9oqQk4pVZY/P981YsrtYnfUkBz8d33j8//5/G/yMKoc4pWP3Xx16b/y4v7nF0TF86Lx6ZNCw/fmM1FQY4pO2eMbD9XlnQ96p4dc6EfPPaeC9kPX15cuprcWrX/vHjwz4T5z3+3zF16dy8z/yVS+K2kX/N1wd16htdmrWNJauWRwqkb1JWEWd3OyzK65Xm9fwxq7fQujffJdo2bBFDsvt0bMc+0a15+/jThH/ElCVzM6WSOLht9+iXhZKn+zh01juFw9a7vPHAc+P+cc+td1+q4dhOfaJHiw7ZuefEk1Ney8codN6lUavc847Rf8vvTQozT9f2y6z+5f8MVU61Hr5Lr6zGXWLBiqVxeubQvlHL3Drdi2mZdarz1TnjC6fNlzOWzY9XZ4+PyrWri9sfePeluL7vJ+ITuw/Mw9gblzWIE3c9ML784l3FPoWVH73xcHzjgHPymqvei8J+y+oChaD1V199NV5//fX4whe+EAMHDsyD1k8//fRo2bJl9QM28dvbC6bFxMWz8vuUQvz/b9RjxSNvH/lQrFrz3v3dGr//itLyGNz5gHh44iuxc8Nm2dzZL59bD096JX8O7VzRPNu/f6zJ/vvT+Bdj0aplxVrTsWmO75u9CCA92+5759n8t1vokJ5B9euVxVvZ8+7cbN4+O+3NGDZ7bD4f131unZX9ftMLBtZtb86bVG0eHJHNm/133iPmr1gSfxj/QqzvmVb1HBuby/u23i36Z8+phtmcGZHNtTSXq7YtNZeHZNd9Qfcjo0l5RXyv/yXx13eH5i/bmJw9Pwrt3ez3sCnzvdDfXC5IWBIgQIAAAQIECBAgQIAAgZoTEK5ec7bOTIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBADQik8PPZs2fnnzlz5lRbpu1p27rh6Skgfd1w9BSM3rx58zyAN4Xwpk/63qFDh+jZs2e+3qxZs0iftH19yxSQXpLSsbUtJrBuuHppaWmkbeXlWchwFqR+7rnnxgknnJCH0n/UQVPo+V8mvJSFqx8aKSj5rxOH5qc8Kwtbf2PuxNi7Vec8CPzRScPz7efscVj84PUHisOmgPPTdz8k7hz1eCzOQo3vOebauPedZ+LaF36eh3tft98ZcUqXg+Oa5+6oFq7+6Z7HxVEd9okLn/yfODALOP7jcV+NpVktKWT9m6/cG3OXvxeynkKKT+9ySLw2991I4cb/ttdxcdAfvpgHFqeQ81TjHs3axzsLpsaClUvzujZWU6Hwr+9/VuxU0Syue/HuaN2gafz08CvzXf+KVo/43djn40t9To/9dto9njn5P+P6l34Z940dkvdLgcqppTD3F079bjSr3ygPV0+e9779TLx59m0xev7kSOHqKez8y/udHlf1PjG3PrnLQbEwq/XgLDT5mweeF2c//r382lJg+WlZiHmq7bi/fiOzWH/Aes8WHeNjnfaL/xrxp7yGqn96ZffrJ4ddGd8e/rs8EDvdrxdP+6/sftyZ3Zdn48yuA7Lw5IujQWn97N52ykLgy/LA92v2PTn/DRz34DfyYPQl2XWMyq4xheana0xh2ymk+onJI/Lts5cv2mBodIfGreNno/8Vxv2L0U/mwerJOIVF92zZKa5+/mfx4LsvVy09X/9HFs7fu/Wu+W8xhWdrmy6QXrqQ2nPPPZd/Lr/88hg0aFBccMEFcdAxh236ibKeKdj+h68/mAdtp7DtQ9vvHV/+xy/yYPIUoF9oNf37H5C9mOHWAZfF7tmLCK5/8Zfvhf2vWho39Tsv/jb51fz3mH6XKfT8tC798xD2cx6/JS8vhfi/fPp/x6ef/t/4n9f+HJ/f55R49MQbo9/vvxApkP2/Drk0n0c/zgL9P7P3znFkh95xQPa8+daw38b6nltX9Boc3x3+h5iwaGb+Qog7jvhcdG6ycwz805fz8dILFW7pf2k8PXVkPJrNmWv3PTW+2veMGPzQN7Pw9ikFsmrLjc3l/+h3fqTw9Buz52Gz8kZx+2GXxzX7nJw/M1Ng+5acy2VZ7Te9cl/2coducVCbHvlzaFDnvvlYj2dzPrUNhcSvO98LF2guFyQsCRAgQIAAAQIECBAgQIBAzQkIV685W2cmQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQOADBFLgeQpDnzlzZsyYMSNfFtZnzZq13hD1ysrKametqKiI1q1bx0477VRcduvWrVpoeiE8veoyBaYLRq9GWSu+FEKSUzFlZWVx3HHH5YHqJ510UqQw+y3dUhh6Clc/v/sRebh68ywofI8szPhrL/0q/jjoq3F+tyOysODhUdg+fPa4vIQUXvzDgZ+OQ7Jw4aWVK7IA9AlZYPq+8ameH8uDvIdm4eDfffUPebh61ZqbljfMQsXPjc8/f0esXFMZz00fFU9MGRH9s7Dx0x/7dt61U+Od8mVZSWmc8ujN+fpz09+M+479Uh4AnOp5PQtcn718YXTM+g7JzpHaptSUAsOv7n1S7HbPp/K6U+13jXky+rfbMz9H4c+y1SvjyL9cHz8+7DNZyHif+EkWDp6crnz2JzF16dxCtzw4OQXEF1oKWB+3cEbha6TvX3/5nriwx1FZra3zsOflq1floevjz/+/+HKf0+LEh2+KtO3mYffHu+ffEUfs0muD4eop8D616VkYe9WWwp3vzAKfU6D7A/8MLv/fkX/NAs27ZAHVn87D7e8f91wemP6JLKj+p28+lgfAp3N8db8z40v7nZb/Bn7x1hMxZcnc/PM/Az4VP3jtgXh+xugs9P0T8d8j/ly0rjp2Yf2Q7B5Wrl0dt498qLApZi1fEIOysPjHT7wprux1Qrw0c0y8OGNMcX/VlRTcPT8Lju6zU5fYnHD11e/OjesvuzpaVzSterrtcn369Okbva6qL2d45JFH4qGHHor6DRrEmoM7RqMLDtjosVV3/t+ox/KXAHw3C1c/abd+efh4Cji/e8zfq3ar0d9/ejbcMfrxuPmgC2Lykjlx2xvv/a7SNaYXAvxu7HPZfLotr2d8NufSCwxKsv9SOPzgzgdEu0Ytsvqmxprs39lHsrD+r2W/4RTun55hX/7HXXm4enrJwVEPXB8tGzTJXkASMXfFovU+t36UhbAXXj5xcY+j85dHpGfk2IXv3Y9/22tQFj4/N/4w/oW8nq9mVuklCzf3u6D4XKsGl33Z0FxOz5kLuh8Zve77bCzMXlqR2kVPfj9eOeN/4tsHXRj/9sztsaXn8k/efCTSJwXVf7XvmVmQ+0lx28DLo98fvlB8cUVeSJU/65vvhd0fdi4XjrckQIAAAQIECBAgQIAAAQIEPlhAuPoHG+lBgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMBmCKTg19mzZ8e0adNi6tSpkcJwC4HpaVl1PQWoVw3TLi0tzUPS27RpE+mTAtN79eqVL6uGpxfC1NO2xo0bb0Z1utZ2gXbt2sUVV1wR/fr1i1NOOSVSCH5NtqenvhFTszDtY7MA8bYNW+ShxH8a/2I8NXVkTFw8KwZ17hs7VTSLE3c9MP4y4aViKWd0HRAVZfXzoPTCxraNmkcKOe7arF2kcPUVWWD4uq19o5b5cbtkQeOF9mIWuH185/3zwPEURl5oI7MA9UIbNW9SvtqladvCpnyZgpQLbVNq+vw+p8Src8bFon+GFqdjX5k1Nj9FetlB1ZbC28947DtxWpf+8d2DL84CpveJZ0/5zzjlkZvzcPeqfT9ofdHKZTF+0Yw8RD31Tdc5LQtIT+HMKVg9tRToPiULkd61SZv8+/r+9GjRId+cwourtmM69Inu2b6XZ71ddXMeXH/m7gPysOYUBp3C5FMA+uj5k4v9/ue1P8fns7DqAe16RgpXTy0Fwe/SqFV+vhSs37vVbvHstDeKx6y7Uq+kJA9lPudvt8SSbIyqLQVFD8nC8dMnrT9x0k0x+K835oHZVful9QWZU+Ea193n+4cUWOd3valnuW/skPj71Nfz3/4pXQ6OW7OXKfTdefe4+rmfbeopiv0+7O9/4cql+TnemDuxeK63F0zN19MLFgptTLatQWl5pOdLevnB78Y9HyPmjM/D/dP2Ae32yrvunj2bUrh64eUEj2Uvakjh63OWLyqcar3PrXvfeTbf36Fxq7ip33n5CwJuq/ISgSt7Dc7Pe0sWRl9oqc4U2r6hVvidrzuXP7P38ZGOLQSrp+PTc2LCoplxVha8fu0LP8+fXzUxl1evXRM3vXJfzFg6P77b/+I4tP3e8eA/X9ZQ9To2Nt8L/czlgoQlAQIECBAgQIAAAQIECBCoGQHh6jXj6qwECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEtjuBysrKmDFjRh6anoLTC58UoF5YT8vUJ/UttEaNGkXbtm3zsPQUmL7rrrvGgQcemH8vbC8sU2h6vXr1Coda7oACDRo0iNtuu22rXXkKJ/9tFqJ89T4nxTlZcO8JWYj6JX//QbZ1bdwz5um4ru8Zxe2XP3N7sa49W3bMAorn50G/xY2bsJJCkFOw8VFZUPktI/6YH9Gmonm8PPPtPHB8Q6eozF5akFppSfX5UTVcfVNq6tWqc/x5wovVhlk3VL3azuzLH8a/kIfN33nEVXFEh95x04HnxSmP3rxut83+vnL1v54ThYNXramMxuUNCl/ft0xB96neQiB7oUOPlu+Fri9Z9a9w+rTvhemj8y49mr+3v9C/6vK9UPe5WYh+0zxg/Yyuh0TnJjvHssqVebB220YtsvFWxo0HnhtvZCH3Pxv1WNXD8/VvHXh+pLDp1+ZOqLbvvG6H5+H0R/7l+kjBzS/OGBPfH/CpuKX/pXH249+r1jd9WZKFzu+SBVhvTivdtVX8xxXfiIPb9ticw+pk31NPPXWjdZdkIfeFz6BBg+L888+Pg489PPr8+fMbPW5DO2cuWxAXZ8+DU7M586NDPxMX9zg6fvP2M5FeiPBR24f5/acxV2RzZN2W5k1qjcremzvpuZBq/+p+Z+a/3WFZoHpq9f75/FiT7U8t/SY3p33/kMuirKQ0rnj2R/kzMh2bXj7QPnsRwTVv3RGPTBq2yafb4FzO5ur6fNNc3q1pm+jWfJcYNvu9F0KsO9iWmMvpnOmZ9+2DL4wURr++tqH5XrXvh5nLVY+3ToAAAQIECBAgQIAAAQIECGxcQLj6xn3sJUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQILBDCCxfvjymTJkSkydPLi7TetVPCk1f88+A54TSvHnzaN++ff7ZZZddokePHsXvVbc3bdp0hzB0kXVX4N53ns3D1a/sdUKMnp/NgyVz8ou55+2n4sv7nRZX9Boc7y6aFRMWzSxeZAol7ta8fR40XLl2dXH7pqyc9bfvxl1HXRPfzELKX81Cj7tmAb6XPf2/m3Lo+/pkOePF9kE1pWD2FL58wM57FI+pulIIat81CxbfOwthf2jiK8Xdc1csiiuH/DheO/OHMbD9Xnmg8oKVS4v7P8xKYbx1j91Y2HsKp0/h2Y2z61hSuaJ46PwVi/P1fm26xwsz3ipun7h4dqTg6fkrlxS3rbtSv15ZtG3YPJ6cMiKGZ/djzPyp8YOBn4qfjHokfjH6yew+nRu/HPP3+P5rD2SB6/8as3Cei3sclYeqPzzpX16FfefscVg8PvnVYoj1r7Lf1H47dY0Luh+5XsMW9RvHW/MmFw633ESB0tLS/N+nAQMGxEUXXRSnn356tGzZMj86hYxvTvt0z+PiZ6MfizVVJtcfx/8jDt+lVx6ufmL2Aob1hX9vzhip74f5/efHValr3TEL50xz+MHBN2Qvf7gzHp00fIMh4esev7HvZ2cvnzi2U5+4/sVfxtiF04tdC057tey0WeHqG5zL2Vztu/PuWRB8SbV7UBizJudy4aJmL18Y87JnyjsLphU2FZcbm+/FTtmKuVxVwzoBAgQIECBAgAABAgQIENjyAsLVt7ypMxIgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCoVQKVlZV5YPq7774b6TNx4sRqoekpQH327NnFmsvLyyOFpXfs2DH/HHbYYcX1Qmh6WjZs2LB4jBUCdVlg9PzJ8cbciXmg+I1D7y1eSgpZf2rqyDiqwz55sHZxR7Yycu670bi8Ii7d85j46ahHi7ua128UZ3QdEHeM/ltx27orSytXxs9HPx5/fXdoLFy1NP4w/oV1u2zS9xRCngLTC21TanorC49Pwek7VzSPWcvXHzo9Z/miuPmgC7NQ8BGxMgsmL7QpS+bG21m4+R5ZqPyK1avyzSnQvaK0vNClxpej5k3Kx9g5C0NfUiXsfuisd/Lth7TbM37w+gPFOlLgc3kWnv7SzDHFbeuu9GvTLSrK6ufB0Euz8PT06bdz9/iPV+7PjQ5u2yOuGvKT9XqloO2IkkgB/VXbgHY947npo6JXZp3Mq7a/Thwan+x5bH4PqgbUl2TnaZNd1/hFM6p2t74BgbKyskj/vu2zzz5x8cUXx1lnnZX/27WB7pu8uVOTneLCLPz+F289We2Yp6a8noerF377aefW/v1XK2gjX76y3xnZ7740D1ZP3epVeU5s5LAN7kq/y29nz4QXZ4yJ2994uNgvvaghzb304on0m779jYdi+T+fDanTJ3YfGM9n86DwworigdnKxuZymlf7tOoSr84ZVzxk39a7xawsKH/CRubHR5nLxYGylf7ZnE9mVV/UkPZ/0HwvnMNcLkhYEiBAgAABAgQIECBAgACBmhMQrl5zts5MgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYKsILF26NA9ML4SnV12mIPUpU6bE6tWr81oaNGgQnTp1Koal77333sX1Qph627Zto6SkZKvUbhACtUXg9+Oej67N2sWfJ7xYraRfv/10HLlL7/jLOtv/MO6F+Frfs+Jb/c7Pw8UfmTQs9sqCtE/Z7aD47LM/yc/R4J+h460rmhbPmQKP/3jcV+P7r/8lmpQ3zAJ8S6KspDSmLp1b7NOqokm+3qrBv45r2eC9bYVl6jBj2fxo26h57Na0Td7/kYnDYvLiORut6fuv/SX+74jPxvf6Xxyffvq2WLVmdZzWtX9+fP+2e+Zh8vNWLI5GWdj49wd8Kq5+7mfFgPUUVL5ny47xqzFPFQOUn5zyWpze9ZA4r9vh8cfx/4hTuxwcqf4UuN6ifuOYv3JJfu7G5Q2ifhZyXrU1LquIqteT9jXKthXcqvYtrL86e3wefp5qSYHOhTYyC8dP9+rju/WLjo1bF8OcUzD62AXTsqDsJwpdc+/uzXeJMVlQfGonZfdsyLQ3i0HU+7TaLdZk/70xb2Jum4KlX1xPOPsRu/SKq3t/PO4bOyQu6/mx/Fwp7H7PFh3jzSwEPoWrP5gF6KdA5mtf+Hmszf5L7cCdu+Xh/GMXTs+/F/7s0rhllGW/j4cmvlLYZLmOQApUT61r165x0UUXxTnnnBPdunVbp9dH+zpu4Yy4Yf+zs/DvydXu+2nZ7zwF7/82u9+FVtO//ybZCxxSqzonCtvS3CnMgTSXUqsorZ8vG2XzrV2jlnFsxz7xyqyx8aks+Dy19tm29AKIQth61WdT3iH7Uxir6r7/6n9ptr1+XPHsj4q/4/QsS+HpKVz91uyFBv99yCfjgeO/HjcO/U0sXLksTtj1gOyFBAuLc7Fw/sJyQ3P5Gy//Jq/77D0GFsPVU1h5vzbd4xvZuddkL5UotPTs/Khz+apeJ8TiVSuyFyQ8E8tWr8xPfemex8b/G/J/MXfFosJQsSnzvdDZXC5IWBIgQIAAAQIECBAgQIAAgZoTqP6/NNbcOM5MgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMCHFFi1alUenj5u3Lio+hk/fny+fdasWcUzN2vWLHbdddf8s++++8ZJJ51U/N65c+do166d4PSilhUC/xL4w/gXolerXWPRqmX/2pitpaDrp6a+HtOWzqu2feWayjjt0Zvj18dcG9/sd17+SYHalz99eyyuXB7777xHpNDe1FJ4+WtzJsRjk4fnwcDvLp4Zt/S/pNr5FqxcGte/+Mt4YsqIuGafk/N9p3TJQr+nvxljF06La/c9Jd92crYtBbmnQOM/ZWHmF/c4Op466ea4edj98dNRj260pnSC+8c9l4Uut4jr+p4ZE8+/I0bNnxS/z4Li5y5flMUXR3RqvFOkcPU3s2Dpxlmw81+O/1qMmDM+D1w+add+8bNRj8XXX7onryX9KdRw26GXx+eyoPGbXrkvUmhy47IGWWh5v0gh9JfvfXweop7C21P4+mOThud9d2ncKprWb5gHk/9yzN/j8r0GRccmrbPQ+Yo4e49Ds7DjZ4vjFFZSWPt/j/hzfDyrZd0Q8muevyOWrFoe93/sy1nY84NZUHm9+FinPnHSI9/KQ+QL50jhzJ/KwtBTkHIKYm+U1Xr2375X2B1Hdugdf5/yev796A77xvPTR1c7Pu3Yt/Vucc/RX8iNDmhTPdx7eeXK2PPeK/Ljv5iFqn/n4IvjuVO/E3e/9WT0zELhd27YLM57/L+KIdV5x+zPqV36xz9mvJXf28I2y+oC3/nOd+L666+PPn36VN+xBb+NXzQjDy3/9wPOiZnZCwzezkL40wsWWmRh5ul3UgjlT0PW5O9/7ILp2UsLjsiv7Mpeg+M7w38fnZrsFCn4O7Wv7Hd63PDyr6NZeaO4qMdR+bb0nLhp2G/jf1//a+y3U9f41dGfz+fbV168Kw7Kwsmv2eekfJ73yF4AkFqaj+OykP+fjfpbVK5dvd7nVoPSsvylBcnh37I5mloKYE/nf3nm2/n3O0c/ns+l9Ax4cPANUZm9tOGH2Ry8IzvvhtqG5vI72fPu5Ef+I35y2JX58/LZ7MUH6Vny3Vf/EPdkL1Co2rbEXN47e+6n580NB5wV9499LpvrlfHjNx/JQunfKQ61qfO9cIC5XJCwJECAAAECBAgQIECAAAECNSdQsjZrNXd6ZyZAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYFMEZs+eXS04PYWop/D0tJw0aVKsXr06P03z5s2jS5cu0bVr1/xTCFJPwelpvUWLFpsynD4EtjuB56aPihMe+uZHuq5WDZrG3BWL3neOllmocgoc31BLgeRrs/8mL5mzoS7F7fXrlcXX9j8rCzN+NNJ4TcsbRkVZ/WjbsEV8ab/Tou/91+Qhx8UDPmClWXZ8ChhOge5V2wfVVFpSLx9z6tK5UVZSmr10IaoFiKd6ZmTB0ql1yELQWzdoloe8L6lcUXWY4nrriqYxJwtoTy0FL69Yvaq4b0uvpPM/d8p34sSHborpy6qH3qexksmeLTvG5MVzIl1f1fY/h3wyzu9+ROz8iwvy61q4ctn7AvWr9t9S6w1L6+fB2DOXLYgUKr2+9veTvhVfeuGueHnWe4HV6+uzoW2PnPCNOLhtjw3t3qG3J/Puv7l8kw3SvSqvVxoLsxctVGS/te7NO+Tzf9KS2Rs8x9b8/W+wiHV2lGQx6g2zZ8vSKnM2XdeqLPi8plry2q1p23h30cz85QUfNM4HzeU9mrXPXrbQMHvZw8RIL7So2rbkXN6poln2PG4S7y6etUWeXR92Lu/VsnM8n72IQSNAgAABAgQIECBAgAABAgQ+WKDsg7voQYAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAlhCYNWtWvP322+/7vPPOO7Fo0XuhxGVlZdGpU6c8OH333XePY489thikngLVW7VqtSVKcQ4CBNYjsL5g9dRtY8Hqaf/GQpfT/qrtp4dfGS/NfDsmLp6df6rua9mg8WYFq6djUwD0+toH1bR67Zpi8Hjl2ixseW31sxSC1dPWKUvm5p/qPap/KwSrp601GaxeOP/nhvw0rut7Rlz93M/yYPuq1SSTZPxBLV3X1mrLVq+MMQumbnC4m/tdEP894s8fKlh9gye140MJpHu17J/548uzlwS8NnfCB55na/7+P7CYf3ZIL3yoGqyeNtdksHo6f/IaPX9yWt2klp4VG5vL7yyctknn+ahzefbyhZE+W6KZy1tC0TkIECBAgAABAgQIECBAgMAHCwhX/2AjPQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhsssCcOXMihaWvL0R9wYIF+XkqKioiBad369YtjjnmmPjMZz5TDFBPweopYF0jQGD7FNh/5z2ibcMWWfj3mHg7C9uuXLM6+uzUNfq16R7vbCR8e/vU+PBX9fyM0VG/tDy+1e+8+NpL97wvYH1DZ25Y1iDKSkqjcbZcUrliQ9226vare388Xp0zPh549+WtOq7BCNQGAXO5NtwFNRAgQIAAAQIECBAgQIAAgbon4P9FqXv3TMUECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLbWGD16tUxbty4GD169Ps+c+fOzaurX79+HqC+xx57xGGHHRaf/OQn8zD1FKieAtRLSkq28VUYngCBbSHwice+E1f2OiHuPPJz0anxTjF16dz426RX4ydvPhKj5k/eFiXV2TGfmvp6vDlvYpTVqxerspD6D2pndh0QR3XonT9/bzzw3LjrrSfj9bnvftBhNb7/vrFDYtrSeTU+jgEI1FYBc7m23hl1ESBAgAABAgQIECBAgACB2isgXL323huVESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIbGOBRYsWvS88PQWqv/POO7Fy5cq8uo4dO8aee+4Zffv2jXPPPTe6d++eh6h37tw56mWBvxoBAgSqCqQA9c8O+Um+qbxe6SaFglc93np1gZnLFlTfsJFvj04aFo9NHl7ssWL1quL6tlwRrL4t9Y1dWwTM5dpyJ9RBgAABAgQIECBAgAABAgTqhoBw9bpxn1RJgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQgwILFy6MN998M0aOHFn8jBo1KqZOnZqP2qBBg9hjjz3yEPVTTz01X/bs2TN69OgRTZo0qcHKnJoAge1ZYNWa1dvz5dW6a1u4almtq0lBBAhsvoC5vPlmjiBAgAABAgQIECBAgAABAtubgHD17e2Ouh4CBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIENCixbtixGjx5dDFAvhKlPnDgxPyYFpe+1117Rq1evGDRoUB6ivueee0aXLl2itLR0g+e1gwABAgQIECBAgAABAgQIECBAgAABAgQIECBAYOsICFffOs5GIUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQ2IoCq1atijFjxsQbb7xRLUh97NixsWbNmmjQoEEenJ5C1C+//PI8TD2t77bbblFSUrIVKzUUAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDA5ggIV98cLX0JECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgRqncDs2bPj1VdfLX5GjBgRb731VqSA9dLS0ujWrVsenn7eeefF3nvvna+nbWmfRoAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgULcEhKvXrfulWgIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDADiuwdu3aGDduXDFEPQWqDx8+PKZMmZKbtG/fPvr06RMnnnhiXHfddXmQ+p577hkNGjTYYc1cOAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgexMQrr693VHXQ4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDYDgRWrFgRb7zxRrUg9REjRsTChQujXr160b179zxI/aqrror99tsvX2/Tps12cOUugQABAgQIECBAgAABAgQIECBAgAABAgQIECBAYGMCwtU3pmMfAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAjQssW7Yshg8fHi+//HIMGzYsXx81alRUVlZGo0aNonfv3nl4+rnnnpsHqafvabtGgAABAgQIECBAgAABAgQIECBAgAABAgQIECCw4wkIV9/x7rkrJkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhsM4FVq1bFa6+9FkOHDs3D1NNy5MiRsXr16mjdunXsv//+cfzxx8d1112XB6l379496tWrt83qNTABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEDtEhCuXrvuh2oIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAtuNwJo1a2LUqFHFIPWXX345RowYEStWrIimTZtG375949hjj42vfvWrccABB0TXrl23m2t3IQQIECBAgAABAgQIECBAgAABAgQIECBAgAABAjUjIFy9ZlydlQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECCwwwmMHTs2UoB6+gwdOjSGDRsWixcvjoqKiujTp0/069cvrrzyyjjwwAOjR48eUa9evR3OyAUTIECAAAECBAgQIECAAAECBAgQIECAAAECBAh8NAHh6h/Nz9EECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgR1SYOnSpXmI+gsvvBDPP/98pOXs2bOjrKwsevXqlQeon3/++XHAAQdE79698+07JJSLJkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQ2KICwtW3KKeTESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgS2T4EJEybkAeqFIPURI0ZEZWVldOjQIfr37x/XXXddHHLIIdGnT5+oqKjYPhFcFQECBAgQIECAAAECBAgQIECAAAECBAgQIECAwDYXEK6+zW+BAggQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECtUtgxYoV8corr1QLU582bVqUl5fn4ekDBgyIL37xi3moeufOnWtX8aohQIDAPwXWVq6ONXOXxto5S2PNnCXZJ1tfujIqTtsnShqI3vFDIUCAAAECBAgQIECAAAECBAgQIECAQF0V8L/w1tU7p24CBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwBYSmDFjRjz77LPx/PPP54Hqw4YNi5UrV0abNm3yAPWrr746Xx5wwAHRsGHDLTSq0xAgQOCjCcybNy/efffdmDhxYr4cPe7taPXqW7Fg8sxYMnNerFi49H0DNGzVLE743CVRv4ln2ftwbKizAvXrlUX3FrvU2fpruvBWFU3ioh5HxYKV738m1PTYzk9gcwQGtOu5Od31JUCAAAECBAgQIECAAAECO7RAydqs7dACLp4AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgsIMJTJ48OZ5++ul45pln8s/o0aOjtLQ0evfunYeoH3LIIfly991338FkXC4BAnVBYNasWdG9e/eYP39+sdzy8vIoKSmJVatWxfoidcrKyqJVq1b5SyQ824psVggQIECAAAECBAgQIBNN41IAAEAASURBVECAAAECBAgQIFAnBYSr18nbpmgCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwKYLjBs3rhimnkLVx48fHymI+MADD4zDDjssDj/88BgwYEA0bdp000+qJwECBLaRwJo1ayIFpE+YMGGTKkjB6i1atMiD1bt167ZJx+hEgAABAgQIECBAgAABAgQIECBAgAABArVXQLh67b03KiNAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIfCiBUaNGxTPPPJN/Upj6lClToqKiIg4++OBimHr//v2jYcOGH+r8DiJAgMC2FrjrrrvikksuibVr1260lNLS0jxY/bnnnosePXpstK+dBAgQIECAAAECBAgQIECAAAECBAgQIFA3BISr1437pEoCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwAYF3njjjXjyyScjBak/++yzMXPmzGjSpEkccsghcfjhh+eB6v369Yv69etv8Bx2ECBAoC4JVFZWRpcuXWLy5MkbLDsFqzdv3jyGDBkSPXv23GA/OwgQIECAAAECBAgQIECAAAECBAgQIECgbgkIV69b90u1BAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGYNGlSPPHEE/H444/ny+nTp0eLFi1i4MCBxTD1vn37RllZGS0CBAhstwJXX3113HrrrbF27dr3XWMKVm/atGkerL733nu/b78NBAgQIECAAAECBAgQIECAAAECBAgQIFB3BYSr1917p3ICBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgBxGYN29e/P3vfy+GqY8ZMyYqKipiwIABccwxx8TRRx8d+++/f9SrV28HEXGZBAjsyAKvv/56XHvttfHYY49F8+bNY8GCBdU4UrB6kyZN4tlnn43evXtX2+cLAQIECBAgQIAAAQIECBAgQIAAAQIECNR9Aa8Xrvv30BUQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC25nA8uXL47nnnsvD1B9//PEYNmxYfoV9+/aN0047LQ9UT8HqKWBdI0CAwI4iMH369Pj6178ed955Z6Tn4dNPPx0jR46Mq666KtasWZMzpGD1xo0b5/sEq+8ovwzXSYAAAQIECBAgQIAAAQIECBAgQIDAjiZQsjZrO9pFu14CBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQG0SSKHAr7zySh6m/sQTT+TB6ilgvXv37nmQ+tFHHx1HHnlktGzZsjaVrRYCBAhsFYGVK1fGLbfcEjfffHO0atUqX5533nlRUlISK1asiE6dOsWsWbMiBas3atQoD1bfb7/9tkptBiFAgAABAgQIECBAgAABAgQIECBAgACBrS8gXH3rmxuRAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIJAHAT/66KPx8MMPR1rOmTMn2rVrFylI/ZhjjsmXKTBYI0CAwI4skF44ceWVV8akSZPi+uuvj89//vNRUVFRjeQHP/hBXH311dGkSZN46qmnYv/996+23xcCBAgQIECAAAECBAgQIECAAAECBAgQ2L4EhKtvX/fT1RAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQK1VGDNmjXx0ksv5WHqKVB96NChUV5eHgMHDozjjz8+Bg0aFL169aql1SuLAAECW1dg2rRpeZD6vffeGyeffHLceuut0blz5/UWsWzZsjj//PPjuuuuiwMOOGC9fWwkQIAAAQIECBAgQIAAAQIECBAgQIAAge1HQLj69nMvXQkBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQywRmzZoVjz76aB6onpZz5szJw4FTmHr6HH300dGkSZNaVrVyCBAgsO0E0osobr/99rj++uujdevWeaj6iSeeuO0KMjIBAgQIECBAgAABAgQIECBAgAABAgQI1DoB4eq17pYoiAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKirAikU+KWXXsrD1B9++OEYOnRolJeXx8CBA/Mw9cGDB8dee+1VVy9P3QQIEKhRgbfeeis++clP5s/RL37xi/G1r30tGjZsWKNjOjkBAgQIECBAgAABAgQIECBAgAABAgQI1D0B4ep1756pmAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKhFAgsWLMjD1B944IF49NFHY86cOdG5c+c8TP3444+Po48+Opo0aVKLKlYKAQIEapdAZWVl3HLLLXHjjTfmL6C48847Y999961dRaqGAAECBAgQIECAAAECBAgQIECAAAECBGqNgHD1WnMrFEKAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjUFYFJkybFX/7yl/jzn/8cTz31VKxduzYOPfTQOOGEE/JQ9b322quuXIo6CRAgsE0FRo4cGRdddFG8+eab8Y1vfCOuvfbaKC0t3aY1GZwAAQIECBAgQIAAAQIECBAgQIAAAQIEardAWe0uT3UECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgdoh8Oqrr+Zh6ilQffjw4dG0adMYNGhQ/PznP4/BgwdHy5Yta0ehqiBAgEAdEFizZk3ccsstccMNN0Tfvn0jPWN79OhRBypXIgECBAgQIECAAAECBAgQIECAAAECBAhsa4GS7M3Ha7d1EcYnQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECNQ2gVWrVsUzzzxTDFSfOHFidOjQIT7+8Y/HySefHEcddVTUr1+/tpWtHgIECNR6gXfeeScuuuiiGDp0aHzzm9+Ma6+9NkpLS2t93QokQIAAAQIECBAgQIAAAQIECBAgQIAAgdohUFY7ylAFAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGDbCyxcuDAefvjhPFA9LefPnx+9e/eOCy+8MA9U33///aOkpGTbF6oCAgQI1FGBH/3oR3mYevfu3eOVV16JXr161dErUTYBAgQIECBAgAABAgQIECBAgAABAgQIbCuBkrVZ21aDG5cAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgsK0F5s6dG3/605/i/vvvjyeffDLWrFkTAwcOzMPUTz755OjSpcu2LtH4BAgQqPMCs2bNiksvvTR/gcV1110XN9xwQ5SXl9f563IBBAgQIECAAAECBAgQIECAAAECBAgQILD1Bcq2/pBGJECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhsW4E5c+YUA9WfeOKJKCsri+OOOy5+9rOfxQknnBCtWrXatgUanQABAtuRwMMPPxyXXHJJNGzYMJ5++ukYMGDAdnR1LoUAAQIECBAgQIAAAQIECBAgQIAAAQIEtrZAydqsbe1BjUeAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQ2NoCs2fPLgaqP/nkk3mg+qBBg+LMM8+Mj3/849G0adOtXZLxCBAgsF0LrFixIr74xS/GD3/4wzjvvPPi9ttvj2bNmm3X1+ziCBAgQIAAAQIECBAgQIAAAQIECBAgQKDmBcpqfggjECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgS2jcCsWbPij3/8Y9x///3x1FNPRXl5eRx//PFx991354HqTZo02TaFGZUAAQLbucCYMWPirLPOinHjxsU999wT55577nZ+xS6PAAECBAgQIECAAAECBAgQIECAAAECBLaWgHD1rSVtHAIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGCrCMycObNaoHqDBg3yQPVf/epXceKJJ0bjxo23Sh0GIUCAwI4q8Mtf/jKuuOKK6NmzZwwfPjy6du26o1K4bgIECBAgQIAAAQIECBAgQIAAAQIECBCoAYGStVmrgfM6JQECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgqwksXLgwfv/738c999wTTz31VKRA9cGDB8eZZ54ZJ5xwgkD1rXYnDESAwI4ssGTJkvjsZz8bv/jFL+Kaa66J73znO1FeXr7VSOYsXxTXvXh3rFxTudXGNBCB2iBQv15ZfPvgC6NVg6a1oZxaV0Pl2tXxhefvjAUrl9a62hREoKrAgHY947KeH6u6yToBAgQIECBAgAABAgQIECCwAYGyDWy3mQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQqwVWrlwZDz/8cPzqV7+KBx98MNauXZsHqd977715sHqjRo1qdf2KI0CAwPYkMGrUqDjjjDNixowZ8cADD8SJJ5641S9v9PzJ8duxQ7b6uAYkUBsELt3zmDi4bY/aUEqtq2Hu8sVx11tP1rq6FERgXYEx86cKV18XxXcCBAgQIECAAAECBAgQILABAeHqG4CxmQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKh9AilAfciQIXmg+v333x/z58+Pww8/PH74wx/mob4tWrSofUWriAABAtu5QHqpxWWXXRa9evWKRx99NDp27LidX7HLI0CAAAECBAgQIECAAAECBAgQIECAAIFtKSBcfVvqG5sAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDYJIGRI0fGPffcE7/+9a9j4sSJsc8++8RXvvKVOPfcc4X4bpKgTgQIENjyAitXrozPf/7zcdttt8XnPve5uOWWW6K8vHzLD+SMBAgQIECAAAECBAgQIECAAAECBAgQIECgioBw9SoYVgkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIHaIzB58uT4zW9+k4eqjxgxIjp37pyHqZ933nnRq1ev2lOoSggQILADCqRn9Omnnx6jRo2K++67Lz7xiU/sgAoumQABAgQIECBAgAABAgQIECBAgAABAgS2hYBw9W2hbkwCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgvQKLFy+O+++/P+6+++545plnonnz5nHmmWfGrbfeGoceemiUlJSs9zgbCRAgQGDrCTz99NN5mPpOO+0UL7/8cvTo0WPrDW4kAgQIECBAgAABAgQIECBAgAABAv+fvfsAr6JY2Dj+plJC6D10kN5FEEFEpH8CUpSiAlZQEMTCFZV7UewVRFQULFcRFKVKL4I0Aem9995CEtLLtzN4zj2JdJKQ8h+fk92zOzs789tyeO59nncQQACBTC9AuHqmvwUAQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQODmCyxZskTffPONfv75Z8XExKhNmzb69ddf1bp1a/n7+9/8DtIDBBBAAAErMHz4cL3wwgtq166dvv32W+XIkQMZBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQRSVYBw9VTl5mQIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIOASOHr0qP773//q66+/1o4dO1SzZk299dZbevDBB5U3b15XNZYIIIAAAmlAICIiQk8++aR+/PFHvfHGGxo0aFAa6BVdQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEMiMAoSrZ8arzpgRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQuEkCMTEx+u2332yg+syZM5UrVy5169ZN48ePV61atW5SrzgtAggggMDlBA4dOqR27dpp7969mjFjhlq0aHG56uxDAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCBFBQhXT1FeGkcAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAASOwefNmG6j+/fff6/Tp02ratKnGjh2r++67T1myZAEJAQQQQCCNCixfvlzt27dX/vz59ddff6lMmTJptKd0CwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCCzCBCunlmuNONEAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAIJUFzp07p/Hjx9tQ9ZUrV6p06dJ65pln1LNnTxUvXjyVe8PpEEAAAQSuVeC7775Tr1691Lx5czshRmBg4LU2QX0EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBJJdgHD1ZCelQQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQyt8CqVav0+eef22B1I9GxY0e98847aty4sby8vDI3DqNHAAEE0oFAfHy8Bg4cqA8//FAvvfSS3nzzTXl7e6eDntNFBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgcwgQLh6ZrjKjBEBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBFBYIDw/XuHHjbKj66tWrVa1aNRvK261bN+XKlSuFz07zCCCAAALJJRAWFqauXbtq3rx5Gjt2rMx7nIIAAggggAACCCCAAAIIIIAAAggggAACCCCAQFoSIFw9LV0N+oIAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAOhPYtm2bvvjiC3333XeKiIhQp06dNHz4cDVo0CCdjYTuIoAAAggcOnRI9957r44eParff/9dt99++01DSUhIUFxcnHx9ici5aReBEyOAAAIIIIAAAggggAACCCCAAAIIIIAAAmlUwDuN9otuIYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAGhWIiYnRhAkT1KRJE1WqVEnTpk3ToEGDZEJ5f/jhB4LV0+h1o1sIIIDA5QRWr16tunXrKjY2VitWrLipweqmn6+//rry5Mmjp556SitXrrxc19mHAAIIIIAAAggggAACCCCAAAIIIIAAAgggkMkECFfPZBec4SKAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCBwvQIHDx7U4MGDVaJECXXt2lU5cuTQjBkztGvXLg0cOFD58+e/3qY5DgEEEEDgJgpMmjRJjRo1UvXq1bVs2TKVKlXqJvbmwqmPHTum8PBwjRkzRvXq1dMtt9yiDz74QGY7BQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCBzCxCunrmvP6NHAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBA4LICCQkJmj17ttq1a6fSpUvrq6++0mOP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877r5jy3i49u0ZpTu+3zmPS91blsQzvBgOtY19Lc6573eGPnubm1QDkFR53XxL3LdbF3mutYs7zcs1wjXynVd95T2XyzaL3zHJln2bMk17O8xBn3w+XvVg6/rHq//iOavv8vO9nGIef94SrmXjBly5kLz7Zr+1Zn/AHOcc2dd9rkfStcm3mW3RKsIIAAAggggAACCCCAAAIIIJByAoSrp5wtLSOAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQJgT++usvvfPOO5o0aZJKliyp119/XY899pgNM04THaQTCCCQ7AImcNaEop88eVInTpywS7N+qY8JT4+LSxz06+fnp3z58ilv3rx2adaLFy+uGjVquL8nDVA3301wure3d7KPiQavT2Dy5Mk2jLx169b68ccflSVLlutrKJWPio6Odp/x1ltvVZcuXdShQ4dknxDEhJ5P3bfSCVe/UyYoefqBv+x5Ozth65vPHJAJyzZB4LMPrrXbu5ZrpOEbp7n7ZgLOO5a9Q19vnaewmAiNbfqCxu/6Qy8s/8aGew+q1Un3lb5dA5aOSRSu/mSlFmoSVF3dF3ys25yA40ktXla40xcTsv766vE6E3khZN2EFHcsfYc2nNkvE27cq3IL1Zv4og0sNiHnpo/lchbRrnNHdC463Pbrcn1ydXzwrZ2VP2tODVrxX+XLEqgv7+pjd/0vWl36ZfcyDazZUbXyl9Uf7d7WKyu/10+7l9h6rvBwE+a+vP17yumf3YarG8/xO//Qli4jtS34kA1XN2Hn/6rVUc9Uu9datytdTyFOX293QpNfv+1BdZn3vh2bCSzv4ISYm761mD7Esbh4wHql3MXUvHgtfbh+sms47mVV53qNatRH76z9RSYQ21yvFR0+dK7H1851Waz7yzRwwpN7KouPv3Ntizsh8L428H1AjXb2Hmjx2xAben7eGYcJTTah+WaMJmzbhFTPP7Tebj8VGeo+p2vFXOeXHK8Os992bbpksHRQQD6N3va/wG7XAX864fzV8pW096IJz6ZcvYDrN2zp0qUyn969e6tly5Z6+OGHVa9po6tvyKlpgu1HbPzNBm2bsO07i1TRv/781gaTm3B8V0np+7+BMzHDJw2eUFlnIoJXVnx/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CCCAAAIIIIAAAggggAACmVSAcPVMeuEZNgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQMoJHDt2TK+99ppGjx6t8uXLa8qUKTLh1RQEUkogPj7ehqavWbNG5rN69WqZIPWzZ8/aU5YoUcKGp3fo0MEGqJsg9XLlysnHxyelukS7CCBwAwLm2W3atKnOnTtng9XNM0xBAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBCwKEq3MnIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIJBMAqGhoXr//ff10UcfKU+ePBo1apR69OhBgHUy+dLMBYG4uDht3brVHaJuwtTXrVunsLAw+fn5qUqVKrr11lvVqVMnG6RevXp15cqVCz4EEEgnAiEhIWrRooVOnz6tP/74QyVLlkwnPaebCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgikjgDh6qnjzFkQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQysEB8fLzGjBmjV199VdHR0Ro8eLD69++vrFmzZuBRM7TUEIiJidGmTZtskLoJUTef9evXKyIiwt5f1apVU+3atfXwww/bQHXz3d/fPzW6xjkQQCAFBMwkCa1atdLhw4e1aNEilSpVKgXOQpMIIIAAAggggAACCCCAAAIIIIAAAggggAACCKRvAcLV0/f1o/cIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAI3WWDx4sU2SH3jxo16+umnNWTIEOXJk+cm94rTp0cBE9K/ZcsWrVy5UitWrNDq1atl7isT2J89e3bVqFFDderU0ZNPPmkD1atUqSJfX6Ij0uO1ps8IXEzATJrQpk0b7dq1ywarlytX7mLV2IYAAggggAACCCCAAAIIIIAAAggggAACCCCAQKYX4P8hy/S3AAAIIIAAAggggAACCCCA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jbL/FoiPj7/iGV544QW999578vLyumJdKiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggknwDh6slnSUsIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALpWGD+/Pnq2bOn4uLi9PXXX6tly5bpeDR0/VICJkz9zz//lAlTNx+zHhkZqRIlStgwdROo3rhxY5UtW/ZSTbAdAQQQuC6BmTNnql27durfv7/ef//962qDg1Je4OTJk3aCjejo6IuezBWk/umnn9oQ9otWYiMCCCCAAAIIIIAAAggggAACCCCAAAIIIIBAigoQrp6ivDSOAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQ1gVMsPagQYM0fPhwdejQQaNGjVK+fPnSerfp31UKmOubNEzdBKyXLFnShqmbIHXzKV269FW2SDUEEEDg2gWWLVumZs2aqXPnznYCj2tvgSNSWmD79u364osv9O2339pJN2JjY2U+nsXHx0e+vr76+eef1bZtW89drCOAAAIIIIAAAggggAACCCCAAAIIIIAAAgikogDh6qmIzakQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQTSlsCWLVts0O2BAwc0YsQIde/ePW11kN5cs0BcXJz++usvzZs3T3PnzrXB6iZMvVSpUjZE/a677rJL852CAAIIpIbApk2b1KhRI/v59ddfZQK6KWlDwISnT5kyRZ9//rnmz59vfyt69eqlunXr6p577knUSROqHhgYqNmzZ+u2225LtI8vCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAqkr4Ju6p+NsCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCKQNgTFjxqhfv36qXr261q9fbwNV00bP6MW1CuzcudMGqZtA9d9//13BwcEKCgpS06ZN9eijj9ow9RIlSlxrs9RHAAEEblhg7969at68uf2tGT9+PMHqNyyaPA0cPnxYX375pUaPHq1jx46pVatW+u233+zS29vbnqRmzZr23wcJCQkywerFixe3E3eUKVMmeTpBKwgggAACCCCAAAIIIIAAAggggAACCCCAAALXLeDl/I+3Cdd9NAcigAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggkM4EQkJC1Lt3b5mQ24EDB+qNN96woanpbBiZursnT57UggULbKD63LlzdeDAAQUGBtoQdROo3qxZM1WqVClTGzF4BBC4+QLHjx9Xw4YN7ftp4cKFypkz583vVCbvgZmAY+TIkZo8ebLy5cunxx57TL169VLJkiX/IWMmYXnyySfl5eWlOnXqaMaMGcqbN+8/6rEBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAIPUFCFdPfXPOiAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggcJME1qxZowceeEBhYWH673//q+bNm9+knnDaaxGIiIjQkiVLbJj6vHnztG7dOvn4+KhevXpyhambdV9f32tplroIIIBAigmEhobqrrvusr835v1VsGDBFDsXDV9ewFyL77//3oaqb9myRXfccYf69u2rjh07yt/f/5IHnz9/XkWLFrW/M2PHjlXWrFkvWZcdCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAqkrQLh66npzNgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgZskMGbMGBum2qBBA5mQ1EKFCt2knnDaqxHYunWrZsyYoZkzZ9pg9aioKFWqVMkdpt64cWMFBgZeTVPUQQABBFJVICYmRvfee6/Wr1+v5cuXq3Tp0ql6fk52QcD8jowcOdJOphIXF6du3bqpT58+qlmz5lUTBQcHK3fu3Fddn4oIIIAAAggggAACCCCAAAIIIIAAAggggAACqSNAuHrqOHMWBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBmyQQGRlpw1S/+eYbDRo0SEOHDpW3t/dN6g2nvZRAeHi45s+fb8PUTaj6/v37lS9fPjVv3lwtWrSwoepBQUGXOpztCCCAQJoR6N69uyZNmqRFixapdu3aaaZfmaEjJkR92rRpGjFihBYsWKCyZcvq6aef1iOPPKI8efJkBgLGiAACCCCAAAIIIIAAAggggAACCCCAAAIIZAoB30wxSgaJAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQKQX27t2rjh07yiynTJmiNm3aZEqHtDroHTt2yASpz5w504YQR0dH2yDihx9+WK1bt1a9evUIwk+rF49+IYDARQXMJB7jx4+3Ad8Eq1+UKEU2njlzRmPGjNHIkSN14MAB+xtifl9atmwpLy+vFDknjSKAAAIIIIAAAggggAACCCCAAAIIIIAAAgjcPAHC1W+ePWdGAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBIQYE5c+aoS5cuKlmypFavXq0yZcqk4Nlo+moEIiIitHDhQneg+u7du5U7d241b95co0aNsiG4hQoVupqmqIMAAgikOYHPPvtM77zzjr777ju1aNEizfUvI3Zo48aNGjFihH744Qf5+fnpkUceUd++fVWuXLmMOFzGhAACCCCAAAIIIIAAAggggAACCCCAAAIIIPC3gFeCU9BAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAICMJfPTRRxo4cKANV//qq6+ULVu2jDS8dDWWgwcPaurUqZo+fboNVjcB6zVq1FDr1q3tp379+vLx8UlXY6KzCCCAQFKBSZMmqVOnTho6dKhefvnlpLv5nowCcXFx9nflk08+sb8rFStWtIHqPXr0UI4cOZLxTDSFAAIIIIAAAggggAACCCCAAAIIIIAAAgggkFYFCFdPq1eGfiGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCFyzQGRkpHr16qUffvhB77zzjl588cVrboMDblxg3bp1mjJliv2sXbtWgYGBat68uVq1amU/RYsWvfGT0AICCCCQRgSWLl2qpk2b6pFHHtFnn32WRnqV8boREhKiMWPGaMSIEdq/f7/9PenXr5+aNWsmLy+vjDdgRoQAAggggAACCCCAAAIIIIAAAggggAACCCBwSQHC1S9Jww4EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIH0JHDkyBG1b99eO3bs0Lhx49SyZcv01P103dfY2FgtWrTIhqlPnTrVht4GBQWpTZs2ateunZo0aSJ/f/90PUY6jwACCFxMYNu2bWrQoIEaNWqkX3/9Vd7e3herxrYbENi9e7c++eQTffPNN0pISFDPnj3Vv39/lStX7gZa5VAEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACB9CxAuHp6vnr0HQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwAqsXbvWBnkHBgbagO/y5csjk8ICoaGhmjlzpvWeMWOGgoODVbVqVRumbgLV69SpIy8vrxTuBc0jgAACN0/g5MmTqlevngoXLqz58+crW7ZsN68zGfDMCxcu1LBhwzRt2jQVL15czzzzjB5//HHlypUrA46WISGAAAIIIIAAAggggAACCCCAAAIIIIAAAghciwDh6teiRV0EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQT+n707gfOp+v84/jabsYx933fZUiohirRStiiESr9ItGv5pfT7l2SrVCoUKqm0pyiVaFPW+KVk3/dl7MwYM//7Obrf33fGYDBmfZ0e17333O2c5733+x3j0fsggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIZTsBCVzt16qQGDRroo48+UoECBTJcG7NKgzZs2KBJkya5QPXp06fryJEjaty4cSBQvVKlSlmlq/QDAQQQOKHAoUOH1KxZM23dulW//fabihYtesL92ZgygcOHD2vixIl67rnntGDBAl1yySW6//771aZNG4WGhqbsJOyFAAIIIIAAAggggAACCCCAAAIIIIAAAgggkOUFwrJ8D+kgAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAlhUYPny4HnzwQXXv3l2vvfaawsL43+hT+2avWbPGhdZ/+OGHmj17tnLnzq2rr75ar7/+uq677joVKlQotS/J+RBAAIEMLZCQkKCuXbtqyZIl+vXXXwlWT4W7FR0drVGjRunll192gfUdOnRw3zMXXnhhKpydUyCAAAIIIIAAAggggAACCCCAAAIIIIAAAghkNQH+VTir3VH6gwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAghkA4EjR47o7rvv1siRIzV48GA99NBD2aDXadfFVatWBQLV58yZo4IFC6p169Z64okndMUVVyhnzpxp1xiuhAACCGQwgUceeUSTJk3SN998o+rVq2ew1mWu5qxcuVIvvPCCxo0b5wZIueOOO3TPPfeobNmymasjtBYBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAgTQVyeCOhJqTpFbkYAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAmcgcODAAXXs2FHTpk3TO++8o7Zt257B2TjUF1ixYkUgUH3evHkqVKiQ2rRpow4dOqh58+YKDw/3d2WOAAIIZFuB0aNHq2fPnho/fry6dOmSbR3OtOOzZs3SsGHD9Omnn7og9XvvvVe33367oqKizvTUHI8AAggggAACCCCAAAIIIIAAAggggAACCCCQDQQIV88GN5kuIoAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJZRWD79u267rrrtHz5cn355Zdq0KBBVulauvRj2bJlgUD133//XUWKFAkEql9++eUKCwtLl3ZxUQQQQCAjCkydOtV9Bz3++ON68sknM2ITM3SbEhISNGnSJBeq/vPPP+uiiy7Sgw8+qPbt2ys0NDRDt53GIYAAAggggAACCCCAAAIIIIAAAggggAACCGQsAcLVM9b9oDUIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALHEVi1apWuueYaHT58WF9//bWqVat2nD2pPpHA0qVL9eGHH7pp4cKFKlq0qNq2basOHTqoWbNmBNyeCI9tCCCQbQX++OMPXXLJJe7z8q233sq2DqfT8ZiYGI0fP96Fqtt3kA2SYqHql1122emcjmMQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEBDh6jwECCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACGV5g/vz5atGihUqXLq3JkyerRIkSGb7NGamBmzZt0sSJEzVhwgTNnTtXxYsXV7t27dS+fXsXbhsaGpqRmktbEEAAgQwlsHnzZtWvX1+VK1fW1KlTFRERkaHal1Ebs2vXLo0cOVIvvviidu7cqS5duqhv376qUaNGRm0y7UIAAQQQQAABBBBAAAEEEEAAAQQQQAABBBDIJAKEq2eSG0UzEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIHsKjBjxgy1atVKDRo00Mcff6yoqKjsSnFK/d6zZ4/zevfdd/X99987NwtU79y5sy6//HKFhISc0vnYGQEEEMiOAocOHVLTpk1dOPisWbNUsGDB7MhwSn1ev369hg8frtGjR7vvmp49e+q+++5TyZIlT+k87IwAAggggAACCCCAAAIIIIAAAggggAACCCCAwPEEwo63gXoEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEE0lvgiy++0I033ujC1cePH6+IiIj0blKGvn5MTIymTJmiCRMmaPLkyUpISFCLFi30wQcfqGXLloqMjMzQ7adxCCCAQEYTuP3227VkyRIRrH7yO7No0SINHTpU7733nooVK6YnnnhCFqyeL1++kx/MHggggAACCCCAAAIIIIAAAggggAACCCCAAAIInIIA4eqngMWuCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACaSdgYerdu3d302uvvaaQkJC0u3gmulJ8fLx++OEHF6j+8ccfa8+ePbrssss0YsQI3XDDDSpQoEAm6g1NRQABBDKOwIABA9zgFF999ZWqVauWcRqWwVpi30FDhgyROdWoUUOjR49W586dGRAlg90nmoMAAggggAACCCCAAAIIIIAAAggggAACCGQlAcLVs9LdpC8IIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQBYRePnll3Xvvffq4Ycf1qBBg7JIr1K3GwsWLNA777yj999/Xxs2bFC9evX0+OOPq1OnTipVqlTqXoyzIYAAAtlM4KOPPlL//v3dQBVXXHFFNuv9ybubkJCgSZMmue/o3377TU2aNHHrLVu2VI4cOU5+AvZAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQOAMBAhXPwM8DkUAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEh9gYEDB6pfv34aPHiwC1dP/Stk3jNu375dEyZM0Lhx47Rw4UJVrlxZ3bt3V+fOnXXOOedk3o7RcgQQQCADCcyfP1+33HKL7rrrLjdloKale1Pi4uL07rvvuu/oxYsX6/rrr9fMmTPVsGHDdG8bDUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBDIPgI5vFFAE7JPd+kpAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBARhbo37+/BgwYoFdeeUW9evXKyE1Ns7ZZkO3XX3/tAtW/+OILRUZGqkOHDrrtttvUuHHjNGsHF0IAAQSyg8DG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CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggECGExgyZIiefvppF2TbqVOnDNe+022QhUhNnjzZhcZPnz5dtWvXdsudO3dWVFTU6Z6W4xBAAAEEzpLAn3/+qdtuu029e/fW7bfffpaukr6ntQDFt99+W88++6xWr14t+0767LPPVL169fRtGFdHAIEsLXDo0CFFR0dr165dbu4v27ofmh48T1pvwejJldDQUPdzdb58+QJzf7l06dLH1CUNTk+6TvB5csrUIYAAAghkRAEbEMQmCwrPqMUC4C103X4OsMlf9ucnqg/ex1/253bczp073TkPHjwom6wu6dzqUlJsEAD7GcAsg4PX/XUbQMWW/Xnwsl93vHly+4aHh6ekWeyDAAIIIIAAAgggcIYChKufISCHI4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIInFhgxIgReuSRR/TSSy+pe/fuJ945k2zdv3+/3nzzTb344otavny5rrnmGn3zzTe68sorM0kPaCYCCCCQ/QT27Nmjdu3aqW7dum4gjKwmYEGEY8aM0eDBg7Vp0ybdcsst+ve//61KlSplta7SHwQQOEsC9jlioegWZho87dixIxCanjQ83fa3yY5NWiIiIpQ/f/5jphIlSrgBH4K3FShQINF+NlCRhahbkCkFAQQQQAABBDKmgB8AnydPnnRpYEJCgvsZJDh0PXjZD2RPjFLZqAAAQABJREFUrs7fduDAARfabnObtm/fHli342wK3scC5U9W/FB8+znGbCyA3eY2WZ0/JV339z/e9uD97ecsCgIIIIAAAgggkN0FCFfP7k8A/UcAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEzqLAuHHjdM8992jQoEG6++67z+KV0ubUW7Zs0fPPP6/Ro0e78KZu3bpp0qRJOuecc9KmAVwFAQQQQOC0BCx0zz6zLWB9xowZCg8PP63zZMSDLOjPvpeGDh3qwpBtIJNHH31UZcuWzYjNpU0IIJAGAkeOHHGfBxaKbpOFhNpky8Gh6UmXbQChpMXCPQsVKqSCBQu6yULQixYtqmrVqsmWrd6fJ122YykIIIAAAggggMDZEsiRI4ciIyPddLaukfS8Fq4eHLaeXAB7cCC7/Xxlkx3jT7a+bds2rVmzxtUl3W772c9zJyqhoaGBsPa8efMGwtv9EPfgub/dfjbzl4O3J122MHgKAggggAACCCCQGQQIV88Md4k2IoAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKZUOCzzz7THXfcoX79+umRRx7JhD34X5M3bNigIUOG6PXXX1f+/Pn10EMPqWfPnipcuPD/dmIJAQQQQCDDCgwcOFBTpkzR999/r5IlS2bYdp5Kw/bu3atXX31Vzz33nAvrs+8l+37KKv07FQv2RSArC8THx7tAdAtHtxBOm/ygdH+eNEB9165dskElgosFadrPrjZZWLpN1atXDyz7dUnnOXPmDD4NywgggAACCCCAQLYWCAsLU758+dx0NiFiYmIShbH7wew2Dw5jt+V9+/a5Olv2J/v74ubNmwPrfr3N7Rz2M+bxSkhIiCyI3ULX7WfI5KbktiVX54e525yfK48nTj0CCCCAAAIInK4A4eqnK8dxCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACxxWYPn26OnbsqB49eujpp58+7n4ZfcOaNWs0aNAgjRs3TkWLFtXgwYNdYHxkZGRGbzrtQwABBBD4R2Dq1Knq37+/hg8frsaNG2d6l927d+vFF1900+HDh9W7d2898MAD7nsq03eODiCQDQTi4uJcQPrWrVtdSLofmB48Dw5St+D0pOGXNthPkSJFXFC6P69atWogPN2vC55HRERkA126iAACCCCAAAIIZA0BCyK3qWDBgmelQ35Ie3Do+vGWLbzdwtr9EHf7WXX16tVu3a+zuU32s+7xigXT+0HtyQWxR0VFySbbJ+myX+fP/f3snBQEEEAAAQQQyL4C/CSQfe89PUcAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEzorAvHnz1Lp1a7Vp00YjRow4K9c42yddsWKFBg4cqPHjx6t06dIuwPa2224ToZRnW57zI4AAAqkrsGrVKnXu3NlNd999d+qePI3PFh0d7QLiLVg9R44cuvfee910tsL20rh7XA6BTC2wa9cuWVh60mnLli3H1Nm7nJCQEOhvaGioC0q3IHQbzMemWrVquXlwnb/N6giRDPCxgAACCCCAAAIIIHAaArlz55ZN9jNmapaYmJhkQ9f98HULcPeXk85tkEs/xD14Hhsbe9wmWgD98YLY/QD24Hm+fPnc/v7ctvnLFtxuP5tTjgp88sknbqC6nj176pZbbnFO2CCAAAIIIJDRBAhXz2h3hPYggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAplYYMmSJbr22mvVqFEjF0weEhKSqXqzYcMG/ec//9G4ceNUsWJFjRo1Sl27diXAMlPdRRqLAAIIHBU4ePCg2rVrp7Jly2r06NGZlmXnzp16/vnn9fLLL7vvo759++qee+4h2CzT3lEanlkELAR98+bNsoB0m/xlf+7XW6B6cOCjDX5ggx4UK1YsMNWuXTuwbPXFixcPBKnbvnYMBQEEEEAAAQQQQACBzC5gYec2FS5cONW6cvjw4USh68HB6xbQbuvJ1dnP6TaAZvA+e/bskf2u4HjFAuf9sHU/tP1U1m1ff8rsAyItWLBAf/31l+6//349/PDDLmC9d+/eqlOnzvH4qEcAAQQQQCDNBQhXT3NyLogAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIZE0BCya/6qqrVKVKFX388ccKDw/PNB218Mxnn33WBdda2OXYsWN18803KzQ0NNP0gYYigAACCCQWuPPOO7VmzRrNnTtXuXLlSrwxE6xt375dzz33nEaMGKHIyEg9+uij6tOnjyzgjYIAAqcncODAAReSvmnTpsDclpOGpycNTLefCYsWLepC0UuUKOHmFphuPzcGh6jbsu2XmX4OPj1JjkIAAQQQQAABBBBAIG0E7GfrQoUKuSk1rnjkyBFZyLofyh68bHW2boHsfr0/t0GW/O3+sbZfQkJCss2y30P4QevJze3v9vnz53d/x09uu1+XXr/P2L17txvgzcLtzcx+X24DkTZs2FD33Xef2rZty997kr3zVCKAAAIIpKUA4eppqc21EEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIEsKmBhK9dee63y5MmjyZMnu3lm6KoFbL700ksaPHiwC4sZNGiQevXqpYiIiMzQfNqIAAIIIHAcgddff13jx4/Xl19+qUqVKh1nr4xZbaHOw4YN06uvvqrcuXPriSee0F133aW8efNmzAbTKgQygIANRhAcmG7Bh7aetM6CEf2SI0cOFSlSRCVLlpQfmF6jRo3Asl9nAeq2X0hIiH8ocwQQQAABBBBAAAEEEMikAjZwUsGCBd10pl2wYHULWPfD1u3vGyebbJDPtWvXHrNfTExMss0JCws7JqTdQtltsgD2pMt+XdL5qQ4AZb/vt1B1v1jIupVZs2apY8eOKly4sPtdRc+ePVWqVCl/N+YIIIAAAgikqQDh6mnKzcUQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgawnEBsbq7Zt22rHjh369ddfUyWY5mwrxcXFacyYMfq///s/F37zwAMPqG/fvoqKijrbl+b8CCCAAAJnWeD333/XPffco8cee0wtWrQ4y1dLvdNbGPTQoUM1cuRI931k31E24IcFrFMQyK4CFky4ceNGbdiwwc1tObnJfh71S86cOV1AuoWm21SzZk01b948UGeh6VZvoekWVEhBAAEEEEAAAQQQQAABBE5HwAZsst8np8bvlO3vNCcLZg/evmvXLjeYlIWgW73NbQr+u1Fwn3LlyhUIY08avB4c1O5vW716teLj44NP4Zb9OhvgauDAgRowYIBat27tfg9zYeMGx+xPBQIIIIAAAmdTgN/snU1dzo0AAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIZHGBhIQEdevWTfPmzdNPP/2kcuXKZfge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327Xrl3661//qlq1amnEiBFaunSpu40CAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBA+RLwLV/LYTUIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKeAiYQ+brrrlPfvn312GOPeTYVWzkrK0sPPfSQ3njjDT3xxBM2xN3Ly6vYrs+FpL+0H6IXV3+dL8XimE02XPvCiObqUrOZlv0ekh6TGq+I4FA1qFzTjtuREOMEdC/QoAadVTekmvY6gdxmM+O2Hzug/2yeY8/NlxC/APk7geueW4hvoEwouucW7NQF+Pi5q1zlaoGVbd203Su19shODW96iY5nZejbnUtt2Hj3yJYaOXese1xl/yBbdh3NycHUOFvXyQkT/2zrfLUOq69vdizRk+cP0wudb1Kgc98Ze1arVXh9Xdmgi0YvHG/7B/sG2KOZa9zxJFsu6MuSg5vUp277E5qfXfG5+tXtoOubdNeaIztsu5e81NnxfXbl58p2Jb87La2d+7u2yOAwnV+jsW6Y/Q9XlXrXaasf9q61533qtNO8/evdba6Cr5ePPun9oNYfjdbVjbq6qq13t1otdc3Ml7U94aB9/W5scol1NJ1M8HxXJyzfzDfvVr9SDc3dt866520r7PzIpBUa9vosG8yeX7+AgABVrlxZVapUsUdTPtl5QX2Dgn573fO7T1Hq4uPjc3Uz70Vm/+KLL/Tpp5+qVatWuv/++zV8+HBVqpT7ezfXQE4QQAABBBBAAAEEEEAAAQQQQAABBBBAoFwK5KSkyyvYv1yujUWVDoFs5zGqZX0rD2so668B8y9cwPP3cp49c/8m07OFMgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQJkXGDlypLKzs23QcEkEnsfFxdnw9p9++skGGg8bNqzMm5WmBVQPrKJ3etyt86o31m0tLtWB5DhtiNvtnqKft49ubdFPVf1DbN34i+/R48s/1bTdq2wA98jmfTR/8N/1dyeY/b2NM/Xg4g+VnJGmry99VG+snypfb29dWq+DBs94QRnZWarkhKXf1fpyG+rdNaKFhja8ULP2/Kw/tx2k2iHhMuHnt7e8VJ9umae7WvVX3UrVVMkv0Akh76GtTkD7fW2usPO4ygkIX3ckWrP2/qzrnaDxt3vcJTMXsy86uFF3LHhT6dmZdt43NL1YdzrXMtt1jS9STEqcvt7xkxMYf1Tz9/+iW5r3VsMqEbrnx3fsmKtm/l3/7fuwnut8o91/jdujuxa8raTMNN3crKcGRnWy13qt6yiN++V7rY7dbs/z+zJ2/Xe6yRljAuijEw+5u2xLOKAhM17U+IvvtUHqCw/8qsFOKP2ra77RRCeg3nOLCKqqNy66Q7Fpx5wg9Xa6c8FbWnBgg+1iws+712qlx5d9as97OUHr43+d6Tnclsdfco/6Oa+D2fNu/143RZk5WbZ69KLxerrj9fqo55+1xAnVv8gJXv+HMyfj5bmZ74sr6l+gUfPf8KwuUjnq5Wu16fp3lJSUpISEBCUmJrr3k53v2rUr374m8Dy/zcfH54Rg9oKC2PMLcT906I/XzPP6GRkZ9nTjxo26++679eCDD+qWW27RPffcI1X17Hnycq/abRX++8MCXL03HN2tTfF7Xael7nhv6wFKcx5m8OGm2cU6t/ws0p37mOD/Hc5u7llaNvN3r6PzoIPlh7aW2JR6Ot8bG533H/MgC/N3cZfzHuJ6aIW5aWfnwRDmPcG8t87btz7Xe5F5OMQVv79X5Z1gSsZxTd+zSu2rNdCRtMRc18zbl3MEEEAAAQQQQAABBBBAAAEEEECgtAgEePspNCBEx44nn7Mp5TgPxcw+kKCsrbHK3HpYmZtilBOXqpCHe8m3ZcQ5mxc3Lt8CWc7P/2o5D14tq5uZu/kZJhsCpVkgKydbtYJO/Hvm5bzxl/3HG5RmeeaGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCJwjgXHjxtlQ4fnz56t79+7FPgsTXDx48GAdP35c3377rc4///xiv0d5vGCvKU/o59gdZ2VpVfyCbDi4CR733Ex9i7C62pt0RPtTjno2lVg51D9YXs6f+PRTC9eJdII9DjiB63m3eiHVleP88QwzztunKOcm8L11WD09svQ/+XZvUiXSCZAP0q9OqL0JhHdtNYNCteWGd/Xcyi/0zobpMue7kg67mkv0aMLT6zrrN4HwxiDvdmWDLrq2cXfdOOdfeZtOeh7hrGOzs67i3FJTUwsNaj9ZaLtnu3m/cW3+/v5KT093nRZ69PPzkwldb3FeG+3uWU1+bSIL7e9qrOYEqz/Sfqh96ECW86CKK53Q/aWHNpfqsJklQ1+1D1HoO/Vp1zKK5Wge9vDk+ddpZIs+2u88/MA8bKCqE5jVPryB8/CBLvpy+0I9suRjJWf+8RoVy41P8SLm/e1W5yEQ7zsPMsj73neKlyqwe4ATjr7rxg/U6X9/0Z7kWG0fPl4Dpz2vjb+H7r/cZYRuaHqJEtJTVK+S817lxLs8u/JzmQc6mG2Y8/dz/CX35nv96c7DMW744Z8yAfH/6DpSk7Yv1mLnYQqns3VzQt+nDSje74PTmQdjEEAAAQQQQAABBBBAAAEEEEAAgZIQMD8bXLlypRYtWqQFCxbYo/lZonmoo9nMQx+7detm23x9fUtiClwTAQQQQOAcCvDOfg7xuTUCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggUFIC69ev11//+lc99dRTJRKsPnXqVN14441q06aNvvnmG0VERJTUUrjuGQgkZKTmO9rULz+0Nd+2kqo85oQMn86WX7C6uY4JNC6ObcLmufqg52i1cwKi1x2NPuGS2xIOnFCXtyI1K/2sBaube2dkZ2lnYkzeadjzpqG1bbD6rfPfyLf9XFQGBQXJ7MXxPmECkxITE+3esWNHHT1atIcDmCAls236+Rf5VWpU5HD1I2mJ+mLbQhuuvt75/lh48NdzQXhK9+zz3VPOQxWyT2lMUTrHpiXoCydA3YSr/3J0l/6zeY572C3Nemts99tV2Qk2v3nu6+76ohaub9LDOhe1f0H9zMMYXut2q+5c8FaJBaube3eLaGHfg8z7kHnvOJ6V6Q5WHxTVyT7UouHE2+zxksjW+k/vB/RUx2GaHL3MPhThCqfPICeMfXXs9lwPbZjc/0lNiV5ul5flvIYPO2H1X/b9q+JXJjsPeNhT0LKpRwABBBBAAAEEEEAAAQQQQAABBCqEQHx8vBYvXmxD1OfNm6dVq1bZByqaBytmZmbaB9wZCPOzQG9vb9WoUcP+/oJg9Qrx7cEiEUCgAgp4V8A1s2QEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAoFwLpKam6vrrr9cFF1ygJ598stjX+sorr2jIkCG6+uqrZQJMiiMwudgnyQURKKJAjnJ094/vaFTLvjqveqMijpKCfQNs39CAkCKPKemO9UKq66F2Q3TvwneVlpVR0rc7J9f39/dXtWrV1KBBA6WlpRU6BxOqZLbAwEANGDBA7733nj5fMVPBIzsXOi5vY+LvDylIzjyet6lUnqc48yyp1z+pgAc2fLV9kTKd0P8+ddvL39v3lFx61Gqlpztef0pjCur89843a+quFSrowRIFjTvV+t512mnuvnV2WO86bd1lU9GpZjM9ueIzG6xuzhcc2KBvdiyRr7ePfY/xc46vr5tsg/rN95R5WILZq/pXUscajTV99yozzG7ZOTl6a8P3GnvR7a4qjggggAACCCCAAAIIIIAAAggggECFFHjwwQcVFhamK664Qv/617+0dOlSG6xuMDIyMtzB6i4cLy8vTZ48md9fuEA4IoAAAuVQ4NR+G1EOAVgSAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIFDeBB566CHt379f06dPl4+PT7EtzwSU3HXXXZowYYINL3nggQeK7dpcCIFzKZCenakHfvpAdUOqFWka9StV19/Ou8b2HRLVWVvi98mES5uA5HO5mXXcvfCdczmFs3bvHCd02jxIwnMzgUnmPS8zM1P169fX0KFDbdjSJZdcIhPKbrbZe9dIv2View4t1nKtoDD1dULGa4eEa1nMZhuu7XmDQB8/dY9spfbVGiorJ1tfbluoAylx7i5V/UN0daNu+nDTbHudNmH1Ne6X71UruKoGOd9v43+dqRZV62hA1AXamxTrfO/95DwiIMc9vnpgFfWvd74+2zrf1vl4eatHZGsnYCpbyw9tVf/656tpaG39b8dibU846B5nCiHOQwOGNekhE9Rv2lYd3qbNx/a5g8JzdfY4CfELlLmPCQ43geCeW2HrNcHq/+33sA2/Gtm8jw46DjP2rLbDT+boeQ9TPr96Y11a7zzdt+i9vE32vH21Buoa0UJBzhrXHtmZKxDdzP2S2m1kgum3Hzuo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zDPP6IUXXtDtt9+usWPH2vD0irJ+1okAAgggcGoCvqfWnd4IIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKlQWD06NHKyMjQ+PHjz2g669ev1+WXX67w8HAtWbLEBvec0QUZjAACCCCgPcmxBSqY0HXPYHXT8VwHq3eo1lDvXHyXWn852oac70yMcc9/4YENGtrwQvf5qRaKst6EjD9C5jyvX5ijZz9XecLmufqg52i1C2+gdUejXdXu47aEA+5yQYXUrPSzEqxu7m++DwoLVm8aWtsGq986/42Cpks9AggggAACCCCAAAIIIIDAORIw4edmb9q06UlnkJCQoAMHDmj//v3uo6v8yy+/aNasWbY+OTnZfa2QkBD7s1pX2LrrWK9ePdWvX1/mWK1aNXd/CgiUdQHz8NebbrrJPgDW/N7DhKuzIYAAAgggUJgA4eqF6dCGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCJRCga+//loTJ07U999/f0YBOgsWLNCQIUPUoUMHTZ48WaGhoaVwtUwJAQQQQKCkBVqH11etoDCNaNZb8/ev156kWNWvVEMdazRWm/Aovbb225KeQrFc3wS53/3jO3q160iZoPWfY3cU6brBvgG2X2hASJH6n41O9UKq66F2Q3TvwneVlpVxNm7JPRBAAAEEEEAAAQQQQAABBEpIoEqVKjJ78+bNC71DfHy89u3bp717955wXLduna0/cuSI+xrBwcE2ZN0ErXuGrpvw9aioKBvCHhgY6O5PAYHSKrB9+3YNGjRIcXFxmjdvnrp161Zap8q8EEAAAQRKkQDh6qXoxWAqCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggMDJBA4cOKC7775bd955pwYMGHCy7gW2mzD1YcOGafDgwfr0008VEPBbsGyBA2hAAAEEECi3AhO3LlBV/xBd3aibXrnwFmVmZ+nXuD2auHW+Xlz9lTKc87KypWdn6oGfPlDdkGpFmnL9StX1t/Ou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Vkz69OkjAQEB9z8BjJEESIAESIAEnBCguLoTMFxNAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiSQuAj06tVLPv74Yxk1apS8//77cU5cpUqVZPHixdKoUSNp2LCh/PnnnxIeHh7ncB/kAAoVKiSYunfvrjHs379fM16yZIlMnDhRBg0aJBB3BvuaNWtKjRo1BEL3mTNnfpCx8dxJIFETSJEihS4bvS0fb926ZRNkNyLsmFsnCLTDKcPatWtt6x2JsoeGhmpRdqvguhFid7TOiLPjOBoJkAAJkAAJkAAJkAAJkIBvBNasWSNTpkwROKSjsLpvDHkUCZAACZCA/whQXN1/bBkyCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBAPBJIkyaN9OvXTwur9+nTRyCoG1crVaqUQPgb4up169aVefPmSdasWeMaLI+/R6BgwYKC6amnntJrjhw5onmD+ezZs+Wjjz6Su3fvSrFixbTQOsTWMRUtWpQMSYAEkjiB4OBgyZ49u568ORWIslsF2M0yhNjNMuaHDh2SDRs22NZdunQpVjQhISFalB3C8EaM3Sw7m2O/DBkySGBgYKzwuIIESIAESIAESIAESIAEHiQCb7zxhnaM1rJlywfptHmuJEACJEACSYQAxdWTyIViMkmABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEReeuklGTVqlIwcOVKGDx8eL0iKFCkiS5culQYNGkjt2rVl/vz5kjt37ngJm4HEJJAnTx554okn9IQt58+flxUrVsjy5cv1NGnSJLlx44ZkyZJFqlWrJtWrV9dT5cqVJSwsLGZg/EUCJJAsCUCUPVu2bHry5gSjoqJ0mQLhdSPEjjLGCLKb5dOnT8uuXbts+2I9jrVaihQpJF26dGIvwA7hdft19r/hCIRGAiRAAiRAAiRAAiRAAkmdANpJFi1apB2kJfVzYfpJgARIgASSJwGKqyfP68qzIgESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIFkSQAC2/369ZNBgwZJ3759JXPmzPFynnnz5tUC6w0bNpRatWrJ33//LQULFoyXsBmIcwIQJW7RooWesFdkZKRs2LBBC66vXLlSxo4dK/3795fAwEApU6ZMDMH1woULOw+YW0iABB44AkFBQdoxA5wzeGuXL1/WIuxGgN1+DoF2rNuzZ08MUfZLly7Figri8FbBdSybKUOGDLZls846pzB7LJxcQQIkQAIkQAIkQAIkkEAEPvzwQ6lRo4ZuI0mgJDBaEiABEiABEnBJgOLqLvFwIwmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQQGIj8OKLL8rIkSMFQl+Y4suyZ88uixcvliZNmmjxsPnz50vx4sXjK3iG4wGBlClTagH1atWq2fY+cuSIrFq1SiC2jmn8+PFahB3C+tWrV9cT9q9SpYpAfJ9GAiRAAt4SSJs2rWDKly+fV4dGRUXJhQsXtDC7EWC3n0OUHdPhw4f1HPvj99WrV2PFBYF4q9i6ddmdMDvSnyJFilhhcgUJkAAJkAAJkAAJkAAJeEtg27ZtMnv2bJkxY4a3h3J/EiABEiABErhvBCiuft9QMyISIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIH4IJA6dWp544035J133pG+fftKtmzZ4iNYHUamTJlkwYIF0qJFC6ldu7b88ccfUqlSpXgLnwF5TyBPnjyCqUOHDvrgmzdvyoYNG7TQOkTXP//8c+nfv78EBgZK6dKlpWrVqlqgHfNixYpRbNh75DyCBEjAQwIQQ4ejB0zeWmRkpBZmN+LrrubHjx/XguzYB+Lsly9fjhUdykBnAuxYbyYItjtaDg4OjhUmV5AACZAACZAACZAACTyYBODIDs7mWrZs+WAC4FmTAAmQAAkkCQIUV08Sl4mJJAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESsBJ4/vnnZdSoUfL+++/LmDFjrJvivJwuXTqZO3eutGvXTurXry8zZ86UunXrxjlcBhA/BEJCQqR69ep6MiEePXrUJrYOwfXvvvtObty4IenTp5cqVarYBNexnCVLFnMY5yRAAiSQYARSpkwpWbNm1ZO3iYiKivJYmH3Xrl16X4iyY7p48aLcuXMnVpRhYWEORdetYuxGvN1enB3PzYCAgFhhcgUJkAAJkAAJkAAJkEDSI3Ds2DH56aef5Ouvv6azsqR3+ZhiEiABEnigCFBc/YG63DxZEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEkgeBEJDQ+Wdd96R3r17S9++fSVfvnzxemKpU6fWouqdO3eWZs2ayS+//CIPP/xwvMbBwOKPQO7cuaVDhw56Qqi3bt2SLVu2CITWV69era/f0KFDdYQFCxaUatWqacH1qlWrSvny5QUixzQSIAESSCoEgoKCJHPmzHryNs13796VS5cu2cTZjej6+fPnbSLs1uWDBw/a1mPfK1euxIoyRYoU2pmFvei6EWO3CrTD6YXZzyynTZuWwp2xqHIFCZAACZAACZAACSQMgfHjx+v62mOPPZYwCWCsJEACJEACJOAhAYqrewiKu5EACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACSQuAs8884yMHDlSBg4cKN9++228Jy44OFh++uknef7556Vdu3YyYcIE6dKlS7zHwwDjnwCuXcWKFfX04osv6gjOnTsna9assQmuDxo0SLAuJCREypUrJ1WqVLFNRYoUodBv/F8WhkgCJJAICBghdAib++KYJCoqyia2bhVhd7R84sQJ2b59u1i3RUZGxqIQEBAg6dKliyW6bi/Cjt9GkN3MzToIztNIgARIgARIgARIgATiRuDOnTsCcfWuXbvSCVncUPJoEiABEiCB+0CALQH3ATKjIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESiH8CENAePHiwFjx//fXXpUSJEvEeCQRfx40bJxkzZtTiYhCI7dWrV7zHwwD9TwDXsGnTpnoyse3evVtWr16tp1WrVulrDeFfiPVWrlzZJrYO4fXs2bObwzgnARIggQeWAETMM2fOrCdfINy8edMmtn7x4kWbULtZNvMLFy7I6dOnZc+ePWJdd/XqVYfRhoWFuRVnhyC7dTLC7FiXJk0ah+FyJQmQAAmQAAmQAAk8SAT++usvOXz4sDz77LMP0mnzXEmABEiABJIoAYqrJ9ELx2STAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmIdOrUSUaMGCFvv/22TJ061W9Ihg8frgXWe/fuLefOnZP33nvPb3Ex4PtHoGjRooKpS5cuOlKI/m7evFnWrFmjp8mTJ8uwYcPk7t27kidPHpvYetWqVaVixYoU471/l4oxkQAJJBMCISEh2lmFrw4roqKibGLrVtF1Z8vHjh2Lsf+lS5fk9u3bsWgGBgZKunTptEC7VYDdLFuF2J2tS506daxwucIxgf3798vRo0eldu3ajnfgWhIgARIgARIggQQh8NVXX0mdOnX0e3KCJICRkgAJkAAJkIAXBCiu7gUs7koCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJJC4CAQEBMj7778vrVq1krVr10rlypX9lsB+/fpJeHi49OzZUwusjx49WlKkSOG3+Bjw/ScA0d8qVaroycR+4cIFWbdunU1w/dNPP5UTJ04I7r3ixYvrew73HaYyZcoIwqCRAAmQAAn4h0BQUJBkypRJT77GcOXKlRiC6xBmt04o983v8+fPy8GDB8W67vLly3Lnzp1Y0QcHB4sRXrefOxJnh5g79jNzLIeFhcUKNzmugOOS8ePHS8GCBQWOa7p27apZJMdz5TmRAAmQAAmQQFIhcObMGZk1a5Z8++23SSXJTCcJkAAJkMADToDi6g/4DcDTJwESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIGkTqBly5YSEREh/fv3l3nz5vn1dLp37y4QSH3iiScEwmPfffedQEyVlnwJ4Ho3bNhQT+Ysjx49ahNbh6j/tGnTtBBvypQppXTp0jbB9UqVKknJkiUlMDDQHMo5CZAACZBAAhNIkyaNYMqVK5dPKbl7965AYN0quG7E2DG3X3/69GnZs2ePTbAd+0DgHeHYG54XRmzdfm4vxO5qO0ToE7NBtB62f/9+6dOnj7z++uvSuXNneeGFF6RChQqJOelMGwmQAAmQAAkkWwLTp0/X7RutW7dOtufIEyMBEiABEkheBBL3m2/yYs2zIQESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESsCOQMiBISmfKL6Uz5pV8abLKkatnZPeFY7Lu9F5pla+K/LZ/ud0RyednvZylJWOqtG5P6K8jGyWT2u+1sm1l2Ibf5Pi1c/qY3GGZpHGe8lIuU0Hptfwrp+vcRqB2QBi7LhyV67cjPdnd632yh4ZLsfBcsuj4P5IxJK2Uz1xQ/j62OVY4zfNWVOu3yM3bt2Jtw4rGuctL2pShtm1g8NX2uTrdLfNVlt8PrbVt44L3BNoUqCYzDqyWu+rvQbGi6XNKE5WPtp47rO7PreIoX25T23aq/JHQFhwQKCXD80lZVWbmT5tVjl49K3suHpe1/+6R5vkqybQDK+WOg4H3cU23PSOE92LJ5nJD5dPxO/0rTuRJ2gNTBEjFLIVkjeLgL6uryusd54/IqesXpEb24nLo8r+av318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BEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABByVwIQJE1CpUiX8+uuv6Natm8OYmSlTJsybNw9Dhw5F9+7dceDAAYwaNcphBOAdBhQNIQEbBEQoXaaWLVuaYoqjcalTe/fuhQiuyzRp0iScOHFCxxGhdhFZF8F183muXLlMaXCBBEiABEiABEiABEiABEiABNICgYiICF2MwoULp4XisAwkQAIkQALplADF1dPpgWexSYAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESMCSgDVxdcs4xm8ZdO7u7o6HDx+i4NPlcDUgj7GJcxIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJweALlypVDjx49MGTIELRp0waZM2d2GJtdXFwwZswYlCpVCq+99hpCQ0Mxbdo0h7LRYWDREBKIB4GMGTOifPnyejLf7dq1azEE10V8ffbs2bhy5YqO5uPjo8XWRXBdpjJlyugpa9as5slwmQRIgARIgARIgARIgARIgASchoDhZIri6k5zyGgoCZAACZCAFQIUV7cChatIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIID0QOH/+PA4ePGiaQkJC7Cq2m5sboqKiUK9ePYwbNw4jzy3FihP27WtXBoxEAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAilAYOTIkZg5cyZGjx6Njz/+OAVyjF8W3bt3h5+fH1q3bo06depg0aJFKFiwYPwSYWwSIAGbBLJly4ZatWrpyTzyqVOntOj6vn379HzDhg34/vvvcevWLYgTBBEhNBdcl2VxiuDp6WmeDJdJgARIgARIgARIgARIgARIwOEIREREQBxGyfMQAwmQAAmQAAk4KwGKqzvrkaPdJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJGAHgcjISBw5csQkoG4upn7lyhWdggyOCAwM1JMM8r57967VlF1dXREdHY0KFSpg/PjxePrppx/FW7HUanyuJAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAFHJpAnTx4MHz4c7777Lnr06IGiRYs6nLm1a9fGv//+ixYtWiA4OBgLFy5E1apVHc5OGkQCaZGAr68vZGrcuLGpeA8fPsTRo0dhCK7v3bsXS5cuxRdffIH79+9DnJWLUwQRWi9TpoxpXrJkSWTMmNGUDhdIgARIgARIgARIgARIgARIIDUJnD59Wj/vpKYNzJsESIAESIAEEkuA4uqJJcj9SYAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESMABCNy4ccOqgPqhQ4f0AG4xsWDBglpAvXLlyujatatJUL1AgQKmEuzfvx/bt283/TYWXFxctJjEuHHj0Lp1a2M15yRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTg1AT69u2LKVOm4O2338acOXMcsizFihXDpk2b0LFjR+0AeerUqXrZIY2lUSSQxgnIt/PixYvrqWXLlqbSiuPz8PDwGKLrs2fPxqhRoxAVFQV3d3eIwLoIrhtT6dKl4e/vjwwZMpjS4QIJkAAJkAAJkAAJkAAJkAAJpASBs2fPwsfHJyWyYh4kQAIkQAIkkGwEKK6ebGiZMAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAkkPYGTJ0+aRNQPHDhgWj59+rTOzMPDQw/IDgwMRJs2bUwC6gEBAciSJYtNg8qVK4ddu3ZBBn5LcHNzQ65cufDJJ5/gpZde0r9tJsIIJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJOAkBETUeMKECWjWrBlWrlyJBg0aOKTl3t7e+PPPPzFw4EB06tQJu3fvxsiRI+Hq6uqQ9tIoEkhvBEQ8vVSpUnpq166dqfj379/X3/XF0fm+ffv0NGPGDBw+fFiLrksbZC66LoLrIr4u6yi6bsLIBRIgARIgARIgARIgARIggSQmcP78eeTLly+JU2VyJEACJEACJJCyBCiunrK8mRsJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJ2CRw7949hIeHm4TTDx48qJdDQ0Nx8+ZNvb8InouAukxNmzY1LRcrVixRAuiSXnR0NGTgt6enJ95//3307dsXXl5eNu1mBBIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJwRgLyve25555Dnz59sGfPHocVNBbHyBMnTkT58uXRq1cvbev06dORNWtWZ8ROm0kgXRDImDEjxMm5TOZB+gVIXwBz0XWpz0eOHDGJrvv7+0PE1s0nWSdpMpAACZAACZAACZAACZAACZBAYghcuHDhieeUxKTHfUmABEiABEggNQhQXD01qDNPEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABErAgsP73v7B0yLd68PTRo0f1YGlXV1cULVpUC6fXrVsXPXv2NImo586d2yKFpPlZtWpVLRbRv39/DB48GDly5EiahJkKCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACTgwgQkTJmgB4/Hjx2PIkCEObCnw8ssvo1SpUmjTpg2qVauGRYsWoUSJEg5tM40jARKIScDDw0M7ShBnCebh7t27EMfr+/bt05OIr8+YMQOHDx/W/QjEyYKfn18MwXURXxdH6nSabk6SyyRAAiRAAiRAAiRAAiRAAnERuHTpEpKrL3Jc+XIbCZAACZAACSQlAYqrJyVNpkUCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACCSRw98YteHt744UXXjAJqPv7+0MGVKdkqFevHm7dugUZkM1AAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAumFgDg9HjZsGD7++GM8//zzKFSokEMXvUaNGti6dStat26N4OBgzJo1Cw0bNnRom2kcCZCAbQKenp5WRdfv37+vRddFbP3AgQOQ+cKFCzFu3DjINsN5uwiti/MFmRui69IXgYEESIAESIAESIAESIAESIAEzAlcuXIFOXLkMF/FZRIgARIgARJwOgIUV3e6Q0aDSYAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAE0iKBBi+1xRtlmjlE0Sis7hCHgUaQAAmQAAmQAAmQAAmQAAmQAAmQ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U+Lz9sQ5ecaNqdoV/cq0eOIcD5AACZAACZAACZAACZAACZAACZAACZAACZAACZCAPQmIYMjLL7+sBUTmzJlDcW87wL116xZWrVqlhdZ//fVXhIeHa/HDDh064Omnn0aFChUgwurmTgTvy5cvr6/x9/c3P8VtEnB6AiKy3r17dwQEBGgjBCLoSUcCrkxAnqevvPIKtmzZokXIXTWvEREReOedd7SIvDzjRFjex8fHVbPLfJEACZCAUxO4e/euSXhdBNhPnjyp92V99uxZPHz4UOfP29sb5oLr5tuFCxfm+6JTlwLHSLxI3ImBsY0bN2oDTCLqT0cCJEACJEACJJD6BKpVq4b69evjo48+Sv3EpFAKtm3bhk6dOiFDhgzaIJH029ORAAmQAAk4PwGKqzv/PWQOSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESCBVCKSEuHqqZIyR2p2AuYC5LYHPbzgYb+/8GufvXrfFu/ZTK09JPBdQF0O2fIVH6s9Wl9C02RpuUv1NrBakxLePYNWZXQkOiuLqCUaWYhdUyFkES54ehjLfDEC0mjRm7rJ5ZkL7IjUw/+h688Muvy2C5V81GIDpB1bhwPXTNufXXFz94wMrbL4uuT0Wz5YfY6p0xYsbPkG4MoiREEdx9YTQol8SIAESIAESIAESIAESIAESIAESIAESIAESIIGkEvjtt9+0uFmlSpW0uHeOHDmSGiSvf0zg3r17WLNmjeYqAtMiqi7HRFAutvPw8EDGjBmxaNEitGnTJvZp7pOAUxMIDg5G27Ztce3aNf17qFWrllPnh4knAWsEmjZtivPnz2PPnj1apMuaf2c+v379evTs2VM/2xYsWIDGjRs7c3aYdhIgARJIcwSioqIQEhJiElsXwXVz8fUbN25oJunSpUPBggUhYtjGYi6+njNnzjTHjhlOOAExzCKirevWrUPdunUTHgCvIAESIAESIAESSBYCZcqU0ULjY8aMSZbwHS3QGTNmYOjQoZD+m4ULFyJ79uyOlkSmhwRIgARIIJEEPBJ5HS8jARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgAZsIeHlkgIebOzKr9d3IBxav+aB6D/h558K18Dt6SYiwugS4VQmRe7qnx3vV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nXd0O0CE4elIgARIgARIgASSh4CMAz5//rx+9idPDPYJ9e7du+jTpw+WLl2KsWPHaqMu0n6hIwESIAESIIHYBCiuHpsI90mABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEgghQnIZIbg4OAYAuqGiPrNmzd1ary9vbWAuoiSBgUF6YmDsi3CpZZEShOahaxZs+oJF9evX0eWLFnw7rvvQoQQOEEhoSTpnwRIgARIgAQSRyDyUVTiLkzhq2Rigr+/v16aN28eI3Zpt4jQuiwimC5rETo5efKkFk2XawsXLvyE6PrevXsh7SFzZ4isT5kyBZ999plumwwcOJBtE3NI3CYBEiABEiABEiABEiABEiABEiABEiABEiCBVCbg4eGBWbNmoWzZshgyZAgOHTqEOXPmsB8vle8LoycBVyIg9YwYZJUxDZ07d8bcuXPRo0cPV8oi85IGCXz00Uf4448/dFlev3490qVLlwYpWM6y/NZnzJiBnj17om/fvtoQ9LBhw7TgeubMmS1fxKMkQAIkQAIkQAImAp6enihatKheTAdjbVy9etUkvh4SEgJj2b17N3788UdcvHhRj/ORy9zd3SEC635+ftoojCHAnidPHowbN04Locu5xDoRgpc4ZJyQCLXLuOVRo0ZhxIgRui0gY6fpSIAESIAESIAE7EtAnr8PHjyIt71g3xgTHtqxY8fQsWNH3S5ZvXo1mjZtmvBAeAUJkAAJkECaIUBx9TRzq5lREiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABRyRwYvlWZGqZCVFRURDB0UKFCmnB0WrVqsUQUS9QoECyJ79fv346DpmUmC1bNqvxlcxeAJ2L1UFEdKRVv/RAAiSQcgQCsuVLuciSIaZ+ZVrgTNiVZAiZQZKAYxOQdkDtvKUdO5FWUpc9e3bUqFFDL+ZeHz58aDIkY4ivb926VQug3L59G5kyZTL3HmNbJk+GhYVh5MiREKH1CRMmoFevXjH8cIcESIAESIAESIAESIAESIAESIAESIAESIAESCB1CYj4mRiGfu6551C3bl0txpYUcbXUzQ1jJwEScDQC8g1FxJZFdFm+Edy5cwf9+/d3tGQyPSRgMwEvLy8sWrQI1atXx4cffqjFQ22+OI14rFq1Knbu3GkywixGFoRVt27d0ggBZpMESIAESIAEko9Arly5IEvlypUtRiLjmUVg3RBdN19v3LhRHw8NDdXXzpw5E1999ZUWXzeE1y2tc+bMaTGuM2fOmITcxUN0dDSuXbum20ci3v76669j4MCB8PHxsXg9D5IACZAACZAACSScQHBwsL6oePHiCb84Ba5YtmyZNuAi3xz27Nmj51ilQLSMggRIgARIwIkJUFzdiW8ek04CJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJOD8BHwrBmDx4sVaUF0GJ8oEytRy48ePT1DUuTJmxRf1OWE5QdDomQRIwCqBsVU5GdoqJHogAScjkD59epQqVUovsZN++vRpFCtWLPbhJ/Zl4ualS5fw8ssv44MPPkDe52sBhZ7wxgMkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAKpRODpp5/WIqht27ZFlSpV8MMPP6B27dqplBpGSwIk4IoEJk6cqAXWxaCDGG8Vw6x0JOCsBCpUqKCNCr/11luQZ2hc4qbOmj97pNvd3R2DBg1C165dIZyCgoLw+eef45NPPkGlSpXsEQXDIAESIAESIAESsEBAnsFiME2WmjVrPuEjMjJSj/WpX78+XnrppSdE2NesWaOPiUi64WRstCXR9aNHjxpeYqxlnJAYVXrvvff0OCERWB86dCjy5s0bwx93SIAESIAESIAEEk7g+PHjyJIlC3Lnzp3wi5PxCmljSH/f5MmT0bdvX0yfPh2enp7JGCODJgESIAEScBUCFFd3lTvJfJAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACTglgayFc6Nzs85OmXYmmgRIgARIgARIgASSSuDevXuIjo62OZhHjx4hODgYwWODkaF1GWRsV87ma+mRBEiABEiABEiABEiABEiABEiABEiABEiABEggeQkEBARg+/bt6N69Oxo1aoQZM2Zog4nJGytDJwESSEsERowYocWfRFwxPDwcY8eOTUvZZ15djMDrr78OER99/vnnsWfPHmTKlMnFcmif7Pj6+uLLL7/Eq6++qsXWq1atit69e2ux1Tx58tgnEoZCAiRAAiRAAiRgM4FFixbhwoULGD9+PAoXLhzndTIm6Ny5c0+Ir4eEhGDXrl36uLTp43MisirL1KlTMW3aNC3m7vlMyfgu4TkSIAESIAESIAErBI4dO4bixYtb8ZWypy9duoTnnntOG3BdsGABevTokbIJYGwkQAIkQAJOTYDi6k59+5h4EiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEnBeAkeOHIk38enTp0dUVJQWYE+XLh2KFi0KEUz4J/MtnAxIF++1PEkCJEACJEACJEACJEACJEACJEACJEACJEACJJDyBLJkyYLly5dj9OjReOWVV7Bv3z4tgCZ9fXQkQAIkYA8C/fv3h5eXlzbeEBERgffff98ewTIMEkhxAm5ubhDBsMDAQAwYMABz585N8TQ4U4SVKlXC5s2bsXjxYgwfPhxLly7FG2+8ARGpz5w5szNlhWklARIgARIgAacmMH36dC1+Gp+wumRQDMeUKFFCL5YyLOLrtj7DRWBd3MyZM+E22w2Zu1aBe6NiloLlMRIgARIgARIgASsEDh48iLJly1rxlXKn5V2/c+fOul0gxlvLly+fcpEzJhIgARIgAZcgwFklLnEbmQkSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEScD4CIq4uwhEinO7p6WnKQNasWVGnTh2IQMqcOXOwZ88eyKTK48ePa8GEUs81RDpfb5N/bpAACZAACZAACZAACZAACZAACZAACZAACZAACTgOAenzGzduHL777jstGtukSRNcuXLFcRLIlJAACTg9gd69e2P+/Pn46KOPtLCy02eIGUizBAoUKKBF1efNm4clS5akWQ4JyXi3bt1w7NgxjBw5ElOmTEFAQABmzZoFQ3Q1IWHRLwmQAAmQAAmQQMIIiODp3r179XiehF35pO9z5849edDCETHWJuOKxPn4+OCpepXh7pfdgk8eIgESIAESIAESsIXAgQMHHEbAfOrUqWjYsCGqVauG3bt3O0y6bOFIPyRAAiRAAo5DwCOpSanqWxyFsvhaDSYi6iFWndll1V9qecjumRmN/QJjRP/o0SNcC7+Nc3ev4cTt0BjnHG2nVp6SyJfZJ0ayIqOjcOX+LYTev4mTFtKfzTMTmvhViHGN7NyPfIDL6rojN84hLDI8xnlL8dyKuIt15/bH8OeoO+5u6VDZ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Pgbf9T+PH1CfjRMJn2IxmwAWsb2Uo1zMvkm5uxhPQNqOMf4nlhsUAkJACAgBISAEhIAQEAJCQAgIASEgBISAEDARwBgqxGTfffddatu2LS1ZskSJrCdPntwUUr4KgcgnIH20kc84sq4QHBysyo8qVarQyy+/TAsXLqSECRNG1uUkXiHgUwKjRo2iffv2UcOGDZXQeurUqX0av0QmBISAEBACQkAIxG4Ce/bsoeLFi8duCBG4+9F7FxH+4seJR4//fRKBmORUEBAHaJIPhEDMIHDm7lVbNwJHBO9smqT+9AlYTxTddvHejehOgkfXh1PS3Llz07PPPuvReTow6gKNGzem27dv059//kk1atTQh+RTCAgBISAEhIBfERBxdb/6OSQxQkAICAEhIASEgBAQAkJACAgBISAEhIAQEAJCQAgIASEgBISAEBACQkAICAEhIASEQGwjkKdRZdr67Ww6cuQIHTx4kA4cOKA+t2zZQlOmTKEbN0ImYCZLlkyJrJtF1/Pnz0+JEyeObdgs73fs3sV0VxzJWvKRA74nEIfiUMqESURc3fdooz3G03eu0M+HV0d7OiQBkUsAz3CieAlEXD1yMUvsQkAICAEhIASEgBAQAkJACAgBISAEhIAQEAJCQAgIgUghAAHkb775hp555hlq164dlSpVin755RcqV04cakYKcIlUCMRQAnny5KHFixcrgSg4a0A5EidOnBh6t3JbMYkA3oOzZs1S771mzZqpfBwvXryYdItyL0JACAgBISAEhEA0Ejh8+LCatxyNSYgRlxZh9aj/GcUBWtQzlysKAV8TgJD6zYf/+DraWBXf1atXacKECTRo0CCKGzeux/c+efJkeuONN1Sfw+rVqylTpkwex2HnhMM3z9He66ftBJUwQiDaCCSLH0R1s5aItuvLhYWAEHBPQMTV3TOSEEJACAgBISAEhIAQEAJCQAgIASEgBISAEBACQkAICAEhIASEgBAQAkJACAgBISAEhIAQiFQCWOxYuHBh9We+0KVLl8KIrkOAferUqXT8+HF68uSJWtCbPXt2h/B6wYIFHdtZs2Y1RyffhYAQ8DGBuLKo3sdEJTohELUEIK4uJgSEgBAQAkJACAgBISAEhIAQEAJCQAgIASEgBISAEBACgU3g+eefp927dxNEkatUqaIEY/r27SviyIH9s0rqhUCUEihZsiTNmTOHnnvuOerTpw999dVXUXp9uZgQ8JZAcHCwyrtVq1al3r170/Dhw72NSs4TAkJACAgBISAEhEAYAocOHVL14zA75YsQEAJCQAgIASEQEARGjhxJiRIlos6dO3uU3vv371OPHj3ohx9+IPSxDxkyhOLHjzzJ2p7rf6T1F/Z7lEYJLASig8Ce5iMpW9J00XFpuaYQEAI2CETem8rGxSWIEBACQkAICAEhIASEgBAQAkJACAgBISAEhIAQEAJCQAgIASEgBISAEBACQkAICAEhIASEgGsCGTJkIPxVq1YtTMCHDx/SkSNHlPA6BNcPHDhAW7dupZ9++omuX7+uwiZLlozy589PRsF1bOfLl4+SJEkSJj75IgSEgBAQAkJACAgBISAEhIAQEAJCQAgIASEgBISAEBACQkAICAEhIASEgBAIVAIZM2akJUuW0LBhw+jDDz+kxYsX0+TJkylbtmyBekuSbiEgBKKYQK1atWjSpEnUqlUrVXb07NkzilMglxMC3hEoXbo0TZw4kVq0aEElSpSgDh06eBeRnCUEhIAQEAJCQAgIgVAC9+7do7Nnz6r5xgJFCAgBISAEhIAQCCwCd+/eJYirv/nmm5Q0aVLbiT927Bg1adKE8AknhA0bNrR9rrcBH//7xNtT5TwhEKUEnjx9GqXXk4sJASHgGQERV/eMl4QWAkJACAgBISAEhIAQEAJCQAgIASEgBISAEBACQkAICAEhIASEgBAQAkJACAgBISAEhIBfEEiYMCEVLlxY/ZkTdOnSpTCi6xBfh+g6Jjk+efKE4sSJoxYCFyhQIJzwepYsWdRxc5zyXQgIASEgBISAEBACQkAICAEhIASEgBAQAkJACAgBISAEhIAQEAJCQAgIASHgzwQwDvrOO+9Q3bp1lThy8eLFaezYsUps1p/TLWkTAkLAfwi0bNlSiUj27t2bMH+iadOm/pM4SYkQcEGgefPmtHv3buratauaC1SpUiUXoeWQEBACQkAICAEhIARcEzh69Cj9+++/Iq7uGpMcFQJCQAgIASHglwS+/fZbevTokRJXt5vAuXPnUvv27Slnzpy0fft2ypMnj91TJZwQEAJCQAgIgWgnIOLq0f4TSAKEgBAQAkJACAgBISAEhIAQEAJCQAgIASEgBISAEBACQkAICAEhIASEgBAQAkJACAgBIeBbAhkyZCD8VatWLUzEmCB55MiRMMLr27Zto2nTptG1a9dU2KRJk1L+/PnDia5jX5IkScLEF9lfnj59SpcvX6bg4ODIvpTELwSEgBAQAkJACAgBISAEhIAQEAJCQAgIASEgBISAEBACQkAICAEhIASEQAwhULp0adqxYwf17duXIJS8YMECGj16NKVMmTKG3KHchhAQApFJoE+fPnTq1Clq06YNZcyYMdzci8i8tsQtBCJCYODAgbRnzx5q1KgRbdmyhXLkyBGR6ORcISAEhIAQEAJCIBYTOHPmjLr7bNmyxWIKcutCQAgIASEgBAKPwPXr12nYsGHUq1cvSps2rdsbePz4Mb3//vvqnE6dOtHIkSMpKCjI7XkSQAgIASEgBISAPxEQcXV/+jUkLUJACAgBISAEhIAQEAJCQAgIASEgBISAEBACQkAICAEhIASEgBAQAkJACAgBISAEhIAQiEQCCRIkoEKFCqk/82UgYn7w4EE6cOCAQ3wdouvHjx8nTJiMEycOYZFEgQIFHH8FCxZU21mzZlXHzXFG9PvEiROpS5cu1L59e/r0008J1xETAkJACAgBISAEhIAQEAJCQAgIASEgBISAEBACQkAICAEhIASEgBAQAkJACLgjkDhxYho1ahTVr1+fOnbsSMWLF6cpU6ZQjRo13J0qx4WAEBACNHz4cDp79iw1bNiQNmzYoBzUCxYh4O8E4saNSz///DNVrVpVvf+Qd1OkSOHvyZb0CQEhIASEgBAQAn5I4Ny5c5Q0aVKpS/jhbyNJEgJCQAgIASHgisDnn39O8ePHp969e7sKpo6dP3+emjdvTtu2baNJkyZRu3bt3J4jAYSAEBACQkAI+CMBEVf3x19F0iQEhIAQEAJCQAgIASEgBISAEBACQkAICAEhIASEgBAQAkJACAgBISAEhIAQEAJCQAgIgSgmkD59esIfFlga7dGjR3T06NEwouvbt29XizGvXbumgmIBRf78+ZXQuhZchwg79uGYt7Znzx51KkQOpk6dSm+99Rb169eP0qRJ422Ucp4QEAJCQAgIASEgBISAEBACQkAICAEhIASEgBAQAkJACAgBISAEhIAQEAKxiMDzzz9PGHfs3Lkz1a5dW4nKDBw4kBIlShSLKMitCgEh4CkBiFTDIT3KjRdeeIE2bdpEGTJk8DQaCS8EopxAsmTJaP78+VShQgVq1qwZLVy4kOLFixfl6ZALCgEhIASEgBAQAoFNAI6GsmTJEtg3IakXAkJACAgBIRDLCJw4cYJGjhxJgwcPpuTJk7u8+xUrVtCrr75KKVOmpM2bN1OxYsVchpeDQkAICAEhIAT8mYCIq/vzryNpEwJCQAgIASEgBISAEBACQkAICAEhIASEgBAQAkJACAgBISAEhIAQEAJCQAgIASEgBIRANBNIkCABQTAdf2a7cuVKGNH1AwcOKNH1Y8eO0ePHjylOnDiUNWvWcKLriAv7cdyV7d27l54+far+EG748OE0duxYev/996lnz56UJEkSV6fLMSEgBISAEBACQkAICAEhIASEgBAQAkJACAgBISAEhIAQEAJCQAgIASEgBIQApUuXjn7//XcaP348vf3227Ro0SKaPHkylSlTRugIASEgBCwJBAUF0bx586hixYrUoEEDWrlyJSVOnNgyvBwQAv5CIFu2bCrv1qhRg3r06EGjR4/2l6RJOoSAEBACQkAICIEAIXDu3DnKnDlzgKRWkikEhIAQEAJCQAiAQO/evSl79uzUvXt3SyD//vsvDRkyhD7++GN65ZVXVJ+5OyF2y8jkgBAQAkJACAgBPyEg4up+8kNIMoSAEBACQkAICAEhIASEgBAQAkJACAgBISAEhIAQEAJCQAgIASEgBISAEBACQkAICAEhEGgEIEJQtWpV9WdM+6NHj+jo0aN08OBB9QfR9R07dtAvv/xCV69eVUEhjJ4/f34l2l6gQAGHADv2JU2aVIXZt2+fMVol2A7R9v79+9PXX39NAwYMoM6dOxME4MWEgBAQAkJACAgBISAEhIAQEAJCQAgIASEgBISAEBACQkAICAEhIASEgBAQAq4IvPbaa1SnTh3q2LGjEkv+4IMPCH8y3uiKmhwTArGbAOZFwCFDpUqVqE2bNvTrr7+6dSQfu4nJ3fsLgbJly9LUqVOpSZMman7OW2+95S9Jk3QIASEgBISAEBACAUDg0qVLFBwcHAAplSQKASEgBISAEBACILBs2TKaPXs2Lf4/e+cBX0XRRfFDSCEJNYQaegk1oXeQLr2DdERURCkCVuRDqih2xAIoKKgIFopIr9J77733EkooaXz3TtjneyEVEkjCufm97O7s7OzMf99ssu/de+78+VF+3q2xPPr51uLFi008jiZko5EACZAACZBAciBAcfXkcBU5BhIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJIRARUfKBw4cLmFbFbly5dchBdVwH2qVOnGjF2FU5PkSIFfHx8ULBgQZw9ezbi4WY7NDTUiLT37t0bH330EUaNGoV27doxgDlSWiwkARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARKwCOTJkwdLlizBmDFjMGDAAPz999+YPHkyihcvblXhkgRIgAQcCGiS+BkzZqBu3bp4++238cknnzjs5wYJJFYCLVu2NH41/fv3R/78+dG4cePE2lX2iwRIgARIgARIIJERuHr1KgoVKpTIesXukAAJkAAJkAAJREbgzp070NiaZs2aoV69epFVwbp169C2bVuzb+XKlahQoUKk9VhIAiRAAiRAAkmRgFNS7DT7TAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkkDQJeHt7o0qVKujWrRs+/vhjzJo1C/v27cPt27fNcubMmcaxM126dLh37160gwwLC8OpU6fQoUMH+Pn5Yd68edHW504SIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAES0ITPffr0wbZt25AqVSqUKVPGiM9qkmcaCZAACURG4JlnnsHEiRPx6aefYvz48ZFVYRkJJEoCmhDghRdeQLt27bBp06ZE2Ud2igRIgARIgARIIPERCAgIQPr06RNfx9gjEiABEiABEiCBBwgMHToUZ86cMQlFH9gpBaNHj4Z+tqUJRrds2UJh9cggsYwESIAESCBJE6C4epK+fOw8CZAACZAACZCARSBEhDNoJEACJPAwBGIS5nmYNnkMCTwsgdB7dMR+WHY8jgRIIHERCAkNSVwdYm9IgATiRCAG7co4tcXKJEACSY9ASBifS5LeVWOPSYAESCD5EHB2dkahQoXQtGlTaGBnx44dYzU463PePXv2oGHDhrg4fA7CrgTG6lhWIgESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESeHoJFCxYEKtWrcLw4cMxZMgQVK5cGbt37356gXDkJEAC0RJQPwa9V/Ts2RNLly6Nti53kkBiIjB27FhUq1YNjRo1wpEjRxJT19gXEiABEiABEiCBRErg6tWryJAhQyLtHbtFAiRAAiRAAiRgEVCxdE0GOGrUKOTMmdMqNstr166hdevWeOONN8xnWv/88w8yZszoUIcbJEACJEACJJAcCDhHNogexRrg3K2rke1iGQmQQCImkMU9PVKmSLo5E14oXBtrzu1LxITZtYgE0rq6I5N72ojFSWI7q8yXTr41cDP4TpLoLzsZOwL1c5aKXcVEVitn6kzoULA6boXcTWQ9Y3dIIHYEzN+DVOliVzmR1WqSuxwoGpfILsojdieNizsyuyfN92OVrEXQLG9FWEJQj4iChz+FBPR5sJR3viQ58vzpsqFtgWq4GxqcJPvPTkdOoE6OkpHvSAKlr8rno2f5+WgSuFKJt4s1shdPvJ2LoWeN85RDUBgTJMSA6bHsdkvpggLyNzIpWuH0PmiTvwqCKdKdFC8fUqRIgarZiibJvrs4pUT3ovVw4fa1JNn/p7HTHs5u0M9Hk6q9Vqwhjt24kFS7z34nMgL1c5ZOZD1KHN3Zt28fXF1dERQUFG2HXFxcECaJeENDQ83fsrCAWwgLuAMnL89oj+NOEiABEiABEiCBpE2gZb5K0M9QaMmbgPokJNXvgJP3leHoSIAESIAESIAESIAESIAESIAESIAESCD5EHBycjLJnxs3boxu3bqhdOnSGDhwIAYMGAD9LjKh7JWi9bH76omEap7tkkC8EMju6RUv7SSnRgYPHgz1Z1BhqnXr1sHX1zc5DY9jSaYEnJ2d8ccff6B69eqoX78+1qxZA29v72Q62rgPK1/arA/EZC08uRXFvHLBx9NRdG6t6GGcuXXFnKS2Twmkdwv3T7otMeJzT2yO9uR50mTGmyVaYOSWP2xtRHvA/Z0F02WHxtBvv3wMK84mfBIYXzlfPTnfzisnsPzMTlsX1U+3ncR/Fc2QC6cDL2Pt+f0IuHsTXm5psPHiQVu9J7lSMmM+7A84hduh0fubPWwfs7pnQOEMPsJllxm3xvItOb3d1lw6Vw909q2JHPK+WSDvoX/P7kLYvXu2/fYr+h2osl51bq99MRpL3PM/xzc6lHHjyRPw88qNhrnKwtMlFbZdOmKurd4Dfj+86sl3LgF78LD3rUft0pOey/b9by5x3yduXMSWS4ftix3Wi8vfi8pZiph4LP37oX8nksNcDggIQPr06R3Gmpg2xGs4QbtzL0zu3+KbnMI5ZYKeh42TAAnEL4Gw+G2OrZFAvBMIuxe/79K7d+/ihRdeQJUqVdCjRw+H/m7duhVt2rRBYGAgFi9ejBo1ajjs5wYJkAAJkAAJJCcCkYqrf1ShS3IaI8dCAiSQRAh0L1IP+qKRwOMgoIGVX1d95XGciucggRgJeIqA0LfVHD+ciPEgViABEogXAs3yVIC+aCSQGAioc8WPNfokhq6wDyTw2Amkd/XEuGdee+zn5QlJICoCH/Lz0ajQsPwpINA0d3noi0YCj0IgkyTg+r56r0dpgseSwEMT+Lhi14c+lgeSQFwJDCnbPq6HsD4JkEAcCWgwckiIY/IfFVsPDg42SQrd3NxQtGhRlC9fHiVLlkSJEiXg7+8P3+m9ERh8O45nY3USIAESIAESIIGkRqBNvirQF40ESIAESIAESIAESIAESIAESIAESIAESIAESIAE4oOAfveoYrNffvklBg0ahD///BMTJ05E2bJl46P5B9p4q2SLB8pYQAIkkDQI/Pjjj0aQSpMyqMC6lxdF6JPGlXu6e5k6dWrMmTMHlSpVQpMmTbB06VK4u7s/3VDuj/7crasoLoLh/Uo0Q6iIqFaa8RZuiO/RVhFQrpK1CAaVaYuQsFBUnfmugyj6ehEXn1L3Tfh4eKH5/JExsiyRMS86+dbAzGPrHNqJ7sBsHhnQQxKyvFikLnqvGgecja72o+9TIeUXCtfBq8UaoOfKsbYG3VO6YmHjYbhwOwBf7ZxtROcHl22HZ7IVw8D1PycKcfX6OUsjWK5TQgmrK4yuhWshZ+pMRly9lSQDr5qtqE1cXePjljX9AOsvHIBet+5F65n3UO3Zg2wcdSVjqjTo69cULxV5FpP2L3lAXF0Zj67yMvqvmYDQeBZedOhIMtiYOnUqdu7ciXbt2sHPzy/BRqRJBT6p9AKGb5omCQ52GdHsTyp1hatoliR3cfWHuW896oV40nPZvv8q8v599Z54e+2kSMXVNbmE+pPrnO+35gecksQTliWHuXz79m14eHhYQ0pUy6zCPET+ZseH3QsKQdj5Gwg9cx1h566HL08GIOzSTaTM7YXUA+vGx2nYBgnEC4FsTIAWLccs7ulNjEG0lbiTBJ4wAX1m0f8d4sveffddHD16FDNmzECKFClszY4bNw6vv/66EV2fMmUKsmTJYtvHFRIgARIgARJIjgQiFVdPjgPlmEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABJIOgYMHDyLsfvBDxowZUbp0aSNcoCLqKqZesGBBODk5JZ0BsackQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAKJmoB+/9i/f380a9YML7/8MipWrIg33ngDQ4cORapUqRJ139k5EiCBx0dA7wezZs0yyeBbt26NBQsWwMXF5fF1gGcigYckkDVrVsyfPx+VK1dG+/btMX36dPreCMtbIXcxdPNU1M5RAv4Z89gE6e6GBuOz7TPRNn9V+Kb3QVBYiAP5myF3cPLmJSM2fjLwksO+yDZmHVuPfL92x5W7NyLbHWnZWRF+H7PrHyOuHmmFCIUqAj310MoIpbHfPHbjAn7ct9iIq6ugvGU9RGy9mFdOtJ32sU0YfsqhFfiyyktQgdsnbT2LNcQduV4T9i1K0K7U8vHH2N3zzTlq+vhhwYmttvO1yFsJNf8eiICgQFOmiXQGln4OFTL7GsF1q2IuEWfXa9Tbr7FV5LDccOEg0rh4GIH1XiqoT4uSwLRp0zBz5kyMHDkSvr6+6Nq1qxFaz5s3b5THxHWHq5OzEVb/68gajN+7wBy+VhIrTNq/FIuaDIensxsC5R6SXO1h7luPwiIxzGWr/x5ybQeUbgUXeQ9EZrlSe5uECotPbUebRaMeqJLU5/K9e/dw9+5duLm5PTC2xFCgiUf0FRe7fPky9u3bh71795rX7t27TYKGs2fPGjFmFaTV/+lDg4PNtn4+MPvrn9GgQYO4nIZ1SYAEniCBujlK4mq3KU+wBzw1CTxeAvPmzcPo0aMxefJk5MuXz5z85s2b6N69O/R/5YEDB2LIkCF87n+8l4VnIwESIAESeEIEGGH6hMDztCRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAlETGDNmDBYuXIjz58/j0qVLZl0Dodq2bYtChQrRyTNqdNxDAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTwCATy58+PJUuW4Ntvv8XYsWPh7++P5cuXP0KLPJQESCC5EciSJQtmz56NjRs3omfPnslteBxPMiagPjf63tWkAL169UrGI4370CbsDRfm7liwhsPBkw8sM9udI5SnTOGEkhnzYunpHQ71o9uIi7C61U5oWJi1Gu2yWtaieL9Mu2jrxGZnmAjqqllLXff3yg0nGW8aV3fdtNmQjb/BK1Ua2/aTWCmSPgdeLvJsggurp3P1QCnvfFh+Zhf02lfLVgxLTm83Q3ZxSinvg+02YXUtnHowXOT+RvBtByxbLx3BgWunHcoibmi7BdJlQ22fEhF3cTsKAgcOHMD7779vRCXLlSuHr7/+2vgdRlE91sV50mQWsXt3pHf1dDjmwLUz+Gn/kkSRXMChYwmw8TD3rYfpRmKYy/b9Hly2HT7dNtO+yLauc/6nmn1x9W4g+q2ZYCuPuJKU53JQUJAZTnJIMrZs2TJ4eXnB29sbVatWRY8ePcw9Qv8XOnPmjBFS18GqoLyOW5dqkyZNorC6IcFfJEACJEACiZHAuXPnTHKhDh06oFOnTqaLO3bsQJkyZbB48WKTWG3YsGGMuUmMF499IgESIAESSBACkadGS5BTsVESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESiB2B8uXLx64ia5EACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBAPBNIkSIFunfvjoYNG+K1115DzZo1jWDNp59+iowZM8bz2dgcCZBAUiSgiRd+/fVXNG/eHH5+fujdu3dSHAb7/BQSqFy5MqZMmYI2bdoYoVEVXKMBfx1Zgw8rdkG7AlUxdNNvCL0XLmp+IOCMwdOuYDUM3zLNJjpeJ0cJrDy727atlbK6Z4CWZ/f0wvrz+/Gv7LcsBVKgatYiuBlyByqwbW/lMxfEM9mKQ/79wOaLh83+q3dv2lcx6yrw3CBXGdP+zKPrcPj6OVOuwupT6r5pBGG7FqqNc7euYv7JLWZfdH2yTlA5S2FUzVYUQaHB2Hb5mCm+h3BxWd1YemYHWuSrhLHPvIqOiz/HmVtXTJ2AoEB8s2uOWa+fszTyps2CwOA7UEH61M6p0K7gM1ARYu3PDOmvZb7psiOLe3qsOrcXdXOUREEREp95bB1OB14RSilQMYsvymf2xWrZv+niIeuwSJdDy3XAH0dWR7pP+1A3Z0kUSu8jbV82Qvh6Dsuye3gZnhP2LTLXRsXMdWw/S//vCAs17Wv17MWlj9lx7e4ttBIO2TwyGNZ6Lc7K2P45vhHHb160mjXLYl65MP/EFuy5etKhPLYb3+2ehyFl25s+21+L2B7/NNYLCQkxw968eTO2bt2KPn36mP/hO3fujJYtWyJt2rRxxnLw2lmckGvbOHc5I+L//d6Ftja+3TUXwWGhZvtxvP9TpXRBw1xlMe/EZmRyTytzp5SZW/NObjb3oUyp0sn+MgiTn5lH18Ne2F+P1TleQhJC6L1t2qGV5r1rDUbvQa5Oztgv97sOMm9Xnt2DLZcOm/kY8b7VNn9Vk2DAOtZa6nt92+Wj1iZqyLwpk6kAAkSAfPrRtSJE/uA9zVZZVqKbyyUy5kEluU+5O7thu5wjYlKL+JzL2ie93ofk2u8LOGXfRdv6IElkUTpTfvReNQ63Qu7ayiNbSapz+c6dO2Y4bm5ukQ0rSZXlyJED165ds/U5NDQU+orOPv/8c5tQbXT1uI8ESIAESIAEngSB4OBg8zyfLl06kxxU+/D999/j9ddfhyYa0sQi2bNnfxJd4zlJgARIgARI4IkRcHpiZ+aJSYAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESCAREvByS2OCexJh1xJdl0pmzAf3lK4J1i8N8NRAKzW9LhrEGJlldk9nghwj22dflsEtNfr5N7MvMsFQDgXcIIGHJKDBxq3yVXZ4abBdKe98SOviHutWexZriBcL1411/UepmDKFkwQkF3yUJmI8tkZ2PxMUrRWrSKB4Ds//BGfSuXqgV/FG+KhCF9SUek4aKR6NabBny7yVoIzq5SxlamoApX2b0RzOXSRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiQQZwIqxPb333/jzz//xKJFi1C4cGFMmjQpzu3wABIggeRJoGnTphgxYgT69++PJUuWJM9BclTJkkCLFi0wbtw4DB8+HF999VWyHGNcB6Wi538f24DMIvqtQsmWtRWx9d1XToigtpcRArfK2xd4BtMOr7I2oQLn75ZuhR0iTn4g4DR+rfMmPq30gtmv4t4/1uyD2Q0HQX1t7K17kXro798cX+2cjTXn9mFqnbewtfUX+OvZd0UIOY+tqooU/1TzdRTJkBN6zLxGg6F+MGoqcq59DAoNEUHgM0ZIXMuj65PuVxtUpi3aFqiGr0Uk/a8ja/FOyZam/D9pdeDPw2tw8uYl8X/IjxXNPoSKK1tmiYermHsX35p4p1Qrs0t5Tj24AgNKtcarxRqYMhU7H16uIza0+gwvF62HTyp1NULqjcS3YkebMYbv99V7GgHp7rJ/fqMhRpzZOlfEZZH0OfCs+A4sPrU94i4UF3HzBY2HIkTEr1UQO50I069v+ZmI51czddvkq4I1LUZhRPlO+LxyN8NABdE/kWs2p+H7cE6R0tQLlHHsFdHo3GkyGcF6FdvOL2LwS+ScWq4CzBGted6KRhi9/5oJEXfFenudiPP7Zczt8F6M9cFPecV79+4Z4WRdLl++HC+++KJJJKHJUP6ZMQv3gqMXVbbHp8L2Y3b+A2dJEqDvjcm1+hlxfa1z/nYArty9Yaon9Ptf/W1WNx+FiXIf6Va4jrln5JL35Pc1esm95XUz9z6o0AnPZC+Gr6p0x3iZR5Z5iiD5ltZf4k5IEL7YMcu8t3VuqA9OTk9v/F73bSxsPMz40H1Z5SUzh/v5NzVJCSK7b71WvCGuBd0SkfNj2HHlGF6Xup9VftEm5q4JFUZXedn4+i2Q+0I1EXXfJHNe74NRWXRz+QOZo339mpr5p/NumCRUmN1gkO3+F99zWf0Vm8g9yV5IP2K/W4t/mN5bimbIhb8b/A+nO/+IuQ0HO9yzrWOS6ly+ezdcND45iKsXLFjQJE5zdna2LkuUS02y9u6776Jfv35R1uEOEiABEiABEnjSBN544w2TTGjGjBlwcnJCx44d8corr5jPppYuXUph9Sd9gXh+EiABEiCBJ0KA4upPBDtPSgIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkkFgJPF+olgTydUqs3Us0/dJA0oyp0uB2aFCC9alr4Vpocz8gs1W+SuhSqKbDufT8GnS5vc1XsRJJH1O1uwRs1ndo44IEumlAl4pM05IvgWPHjhlH8oQcoQYM+3nlxoQavaGBfZp4oJgE0Wkg8r723+GTil3h6hRzkFYn3xpofz+YNyH7q4LvffyaYM+Vkwl2GjcJxJwqAePWuCfX6os094Xm00vg8vKmIyWgObcJ/v6r3rtYJMGaUVmjXGWxuMlwuDu74tvd87Dg5FZTdZcEiPcv0Qwqbk8jARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggYQi0KpVK+zZswft2rVDt27dUKtWLRw4cCChTsd2SYAEkhCB9957D61bt8Zzzz2Hw4cPJ6Ges6tPOwEVHB41ahT69u2LX3/99WnHYcY/9dAKs9Tv7dU0YXgBEdL+34ZfzHangjXM0irfeumI2VbxYvVJeW/9z0ZseOax9ZguQuUvFXkWZUUUfb+IrX+8bbqpa/9Lvz9XoeC/pX5QWAhWn9uLJadVKDwFWi38yIgXW/VV7Lv5gpF4f+Ov6LN6vBGBt5Kp77xyHJfuXMed0GCskjZ0O6Y+abt1cpQwosU6vlshd3Ey8BImHVhqndK2VN+gmn8PFBHzbfB2T4txItw8o94AZBfBeXvTcdqbCqwfuX7eVqTbg6T/KsysSdT1vMM2T8NzC0chTH5U2L3XqnGmTtk/+yNMxLFrZC9uOz7iioqhq527ddVhl4o7T6zRB/8c34jZ8rp854YRj593YrMRnlaR5z+OrDZ+ByowPX7PQvReNR7PLfoYH2+dbgTdrffA6cArhmm+tFkx5eC/WH5mp+n7n0fWmPJ9Aads5/aQ94GKU39brQcKZ8gh4u0fiyB9Ptv+uKyocHfA3Zso6Z03LochePtpVMpaGCoQnNxfhw4dipFNWJi8s+QVHByMWbNm4cWOz+PGoLkxHmdfQUW2X/n3G/O+bZqnvEkOoIkEIlpCvv/13jBh32JzylOBl9F3zQ8YvHEKxu2ej2Z5KuCqvFe6Sx9fXD4Gn22fiVo+/nIXSWHqNxR/m6we6eU+dMbMqfknNyNX6kzGV0fn/DvrJpl6FcXvpvPSz+H3e2+8vvqHKO9b34nPzpwTm7D76gm5vxU0oukfbPkdh6+fM+28UrQ+zt66gulH10J9evS+mDFVWows39nsj+xXVHNZkyF0Ftavr/4ex25cMPfX55d+aQTbP6rQxTQV33N5RPmOcm+K+m9iNo8MyO7pBfUTG7X1LzSdNwLPzBqAfGmzSGKGwTbxfWucDzuXreOf1FLnjVrKlOGJJp5UP+LrvIMHD45xLCpO+/zzz+PDDz+Mr9OyHRIgARIgARKIdwK//PILxowZgwkTJpj/ccuUKYPFixdj/vz5JvlfcvnbHRM4febrLM/tH8r/hL2KNzLPcPnlma2c/H+a3K1nsYZ4sXDdxzpMTRKncREJZZrgyHr293JLg9o+JaI8lSaZ6+ffLMr9uqNhrjLQGAZ7aywJlGgkQALJm4Bz8h4eR0cCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACsSfgJIFl6myYI3VGVMtaFCvP7Yn9wU9RTXXK1IDMCfsWJeioNdBrrASBqdX08cOCE+FiytZJNdBr6qGV6O3X2CqKcvm8by0USZ/jgf0bLhwUsWcPI7CuAZq05Elg4MCBmDJlCvLkyWOCoDp06ABfX994HWxAUCB+lUDavv5NTcDgLweX29p/q2QLDCz9HFJLYPSrK7+zlUe2Unv2IAlmDA9Qi2x/dGUaVKhzIibTQL/PK79ogj81eDmhTAXPNRBTX/5eeXA3NAR77wcWt8hbyQRfKzc1i1GFzL5Yf8FRjGaYJFF4WYLOa8/+nwlOtO9vqLB6c+2PmFbnbQRsCnxgv31drpMACZAACZAACZAACZAACZAACZAACZAACZAACZAACZDAoxBImzatEa/p0qULunfvDn9/fwwYMADvvPMOUqVK9ShN81gSIIEkTmDixImoVq0amjZtinXr1iFNmjRJfETs/tNC4O2338alS5fQtWtXpE+fHo0aNXpahh7pOP89sxtnREy7bo6SyOKeXgS5ymLm0fUiqL0LJ25eRP1cpeEtIsEqyvX3sQ22Nlrnq4JUkihchdIty+KRDkdFWFxFuTddPCTflwdbu2xL/e5ej8suQuOW6fflDUQILLVzKth/n79LBNMt2yuivmp502SxiszyHu7ZtmPTp/7+zbHt8hHcCL5tO27zxfAkEfdE2NzeVLy9tYigt5Tv+j+W5PI1xadnZfMP0Xz+SCPmbl83pvUbQbdx9MZ543ukgz2kfwAAQABJREFUdXWcZ0UgXcWZ1R9JTQXdT4uIdO7Umc12ZL9UJF1NxYvtrY5PSfjKvo0XD9oXG+H6NvmrGLFmS1A+5F4o7AXSv9gxyyR4r5K1CH7av8Qcr0LwKiSv7amwvp/4P6w8u9uhbd1Qgfq+Ikrdb/UE9ChWHyPKdRLfjG7iG/G/B+rGpuCacLLGGJv6Widlbi+MGP+ljD97bA9JsvXGjx+PXbt2xdh/FUzW97OLiwuebVgfy/Jfj/GYiBWmHV6FZSKsr+/95nkr4itJplA6U35zvSPWjWn7Yd//1yUpgdpuESy37OC1M2ZVEypYdkDKVEhQ7y9nRORcEwFsv3wUF+9cM+VVxCdSTcUnNUGElZxg4cmtRnxdkxFYFtl9y/JL8hGB8eEiRL7+/AF8s+s/wfqexRuadj+t9ILVDLSfKoIYlVnv84hz+dViDcyx1+3uUXqfUKH1tuIjpf5Cev/SuRcfc1l9IpWXsorKSmTMa3bNOb4Jls+T9klF5CfW7GN8T0eI2Ly9Pcxctj/+Saxb4uo6f5KDZc2aFc2bN8eff/6J0NDQB4bk7OyM+vXr44cffnhgHwtIgARIgARIILEQWLNmDV566SW8+eabuHz5MipWrIjKlStj+fLlyJYtW2LpZoL3Q0W+FzYehgvyHPjVztnwkee1wWXb4ZlsxTBQ/ieL+ByY4B16zCfQRGCBwXcSPI7HGlb9nKURHBZqntGtsvhedi1cCzklLkg/e2mVrxKqZit6P/Hcg2fSxHaaaE6f3SPaszlK4b3SrSVJWT7k+eUlh89h9P0yusrL6L9mAjT2gEYCJJD8CFBcPfldU46IBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEjgIQk0kqDI1SKo/lz+qni1eAOKq0fCUQXKVeS45J99I9kbf0UajFhKHBvVSTJlCidUE4fXt9f+5HACDfBycUrpUBbZhgaD+WfMg/knt0CDJCPaktPbjbBzbZ8SUTpiRjyG20mLgBUUdezYMYwcORJDhw6Fn5+fEVpv164dfHzCA24fdVQaABmZfb9nIQaUao0WEmDZZ/V442QcWT0t04C/hzFNCPF+mXaxElcfWb4z/jm+EfbBhw9zzpiO0QQJS0/vMNVqSYIEa13n7VKZd1aQoVaYenClEaC3D9rWcr0v95EECq+v+j5K4fQwCYL9Zvcc4/Rc95/39TAaCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACSQYgXLlymHTpk348ssvMWTIEEyePBmjR49G48YxJwROsE6xYRIggSdKwN3dHTNnzoTeHzp27IhZs2YhhSR3p5FAUiDw8ccfG2G2Nm3aYOHChahatWpS6HaC9FHFyX8XEWVNqt5ehHsbiYj6C8tGS+k9/HrgXwwQkS6rvMeKb219KJwhhwgUBxihX1thLFZUBFmFjfW79U+3zzBHZE6VDhsvHHQQVo/YVEhYuBCY+tPYm724emz6VNwrF2YdW2/fhBGhdiiIsDH96FrjyzOxRm/UED+A4ZIsvfmCkRFqxX0zSJK1R7TgsBB4urhFLLZtq9C9imZbguzWjkIZwn1AVHDO3tae22c2C6WL2kckXNT9iojop4EKrLfOVxm5ROTtdkiQEdbO4pFezheEoSKkv1tE7n/Yu9D+FGZdr8N3u+dBk8s3yV0erk7OCJKxxNUCRXQ+uwhYx8Wc0rujdqP6qJilUFwOS5J1p0yZEmW/9W+w9Xe4du3a0ORIKqx8K2UofH/rEeVx0e24cPsausr9oIXMme+qvYquhWrjt4MroAkRHtUe5v2v57wbyftK542ah3P43NH3o/b9vVJtzHt3i/jbqTndv3+EyX61uAoMfln5ZTinSInXVn4nLYS3of5+2SQRQb/9E4yfnmk4Fr+inMsyVyPjq3M5T5rMKJguO7ZcCk8IEfE0cZ3Ly8S/qVneChiz8x+Zt+VMc+73GZYQv0Mt2yD3Zkvk/vLd/0TotbLet9UKRpLY4GHmsmksEfyKmGgjEXQpzl04fPgwXn/9dcyZM8ckRbP8CK2GVFi9fPny+OOPP5AyZcy+qNZxXJIACZAACZDA4yRw5MgRNGvWDLVq1YL+bfviiy8waNAg80ouyVBiy7OHJOAp5pUTbad9bJIJ6XFTDq3Al1VeQlZJMJTcrfbsQZIU6fGIg2vyIX3enrBvUYJi1c9Exu6eb85RUz5nWHBia6Tne963FjSWKTLTpGh7rp7AoWtnjbh6xDr6v3waFw8Ta9Br1biIu7lNAiSQDAg4fkqaDAbEIZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZDAwxLoVrgOBm38VYIAd6J+ztLImyZLlE15SvCMirAPLN0GzUUwOa2Lu0PdqPZncU+Pl0Sc/FVx7Cx837lPRZF1W1/q2GdZeldPvFi4rtmsk6ME+vo1MULjWqCC4e0kkFODFBvfD+ixjrOWJTPmgzo19vdvBj+v3KY4g1tqdCjwjHm1l6W/Vx5TrgFVbfJVMeU5Pb2tJh5YanDgH0dWP1CuBamdUxnx6HdLtUJn3xrwiRDgl12Cp6zxVJUAxMEiBK1C7alSutja85WgJy0bWPo5XLt7C63yVTKMNVCnQa4yUY7V1kCEFQ3iGlSmLQZvjDqgTg/RoMYhZdtDQusitMDN5EYgODjYDGnnzp149913kTNnThMcPn78eFy5ciVBhntXHItVANwKTNSTlM9cEDoPMklg9Osyt0t75zfn1oDBTgVrmHX9pcHQNbL7obokGHBP6Wrm2NslW5p7gFVJ7yFT6r6J1C6pTPCm3r+iMj3PszlLYdZRxwBprR/THNa+qAOzBuFqv7sWqmXmTZlMBRxOp/cWnccaWJhG7o263kruL+7OrmZdx3j85kWHY4pJ0Pb8E1scBNSziZP7N9V64ITUnXxgmUP9iBuaiEHHbwU4RtzPbRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARKITwIquvbGG29g3759RoitSZMm0JcK3dBIgASeTgI5cuTA9OnTsWDBApN44emkwFEnVQL6fXn9+vVNopDNmzcn1WHES7+nHlpp2ulZvJERETsVeNls/3pwuREwe614Q7N97MYF2/lUlLhgumxGaNhWGMuVtos+NgLaw8T/p2XeSsgn/kAv//t1LI92rCZuCTaLqU/6/b/6CpWN8H2/1YAllpxbhMUbir+OvV0RUeGeq8YiVETeq2YrChVUflSzzhexneiEfVWcXgW01UfL3gLu3jSb5UXc3N5O3LwEFZ62TwRvv1/XVQg9i3s66PXdKiLUI7f8KQLWwRi3d75ZV1+mn8V/QcunirB2dKb+ZwFBNx9KWF3bVb+x0zfD33/RnYf7/iOgIslqFSpUwNdff43z58+bpBGdOnVC6tSp/6sYy7XuReqJr4+jL9mMo+sw9XD4fSIqn71YNm+r9jDvfz04uvlhtalzeGXzj7D50iF8vmMWTkbw17F1Ig4r6rNYN2dJjNg8DYevn7Mdqb5RakUz5LSVxWYlyrkcFIjSmfI/cA2sc8bnXPYRn80c4jM5qmJX22toufam++obquXq23To+llTVjJjXoehnQwMv7/cjJDUQSslxblsJSeI7j3mACARbty5c8cIzhYrVgzHjh3D0qVL8dFHH8FegFbvGb6+vpg7d64RXk+Ew2CXSIAESIAESABXr15Fo0aN4OXlBfV937BhA5YsWYLBgwc7/F17WlD5S0yM+uOncXWM3Rmy8Td4SZKs5G63Qu6azyoSepwqYq7+/wktrK6fJ5TyzmeSuOnnFNUkXmLJ6e0PDE9jp/wl6dH8k1se2KcF+tmNvjTmICrTdgvIZze1fUpEVYXlJEACSZgAxdWT8MVj10mABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABOKPgDoAXr0biAu3r2H8noXG6bJHsfqRnqCgCID/WPN17L5yAh9t/QuNc5XFtjajkSdNZlM/uv3nbwfgkpzjwwpdUC5zuCDxynN7jACxlumxaipOvKfdNxKY87xxTBxcpj2GiLC5CrKrCPuXVV6GBnR+v3cBPijfGSoMb28q+l5PBJTVoXHhqa1Y1vQDjJR6VyWAMAz38O0zr6J69uLYceWYOUwdLTUYrIoEPWqwT2SmjFSUefGpBx0Wi0vw0ILGQxESFip9WiiBk55Y3/IzIwCvbalw+5oWozCifCd8Xrkb2kqQlQYcfVLpBcxp+L4twDQw5A72Xj2J3GkyGefHg9fOIr84MS6Rc2r5IdmOi71TqiW+3T0XN6Xd6Gzd+f3wy5jbiOpHV4/7kheBkJAQE2i4du1avPrqq8icOTMaNmyI3377DbcCb8XbYGtLcgRnp5RYe34fsrpnwO9138bCxsNMsoAvq7yEdyQhQT//pia5wdY2X+L9sm3NuTW4bnz1nphZ/z109K2Br6p2F1F2X0nQUNfMG92vpsGCej8KCg2ROXIGp+8Hd5udEX697t8EGy8cfGBOxDSHNTmC3vem1xuAPn6N8XW17iguDuoaMLmg0RA0zV3edqbjEmSs99JskmDhzyNrcCbwisz3nOaepfM4QO619qZBiJrcoP+aCfbFqJujJNK7eeLItXOYUKM39so9cedzY0zCBU2cENHWnz+AN0q0iFjMbRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJIMAI+Pj7mewUVajt69ChUuE2FbW7fvp1g52TDJEACiZdApUqVMGbMGAwfPhx///134u0oe0YCEQho0hD9nrxixYqoV6+eEWyLUOWp2dwXcMp8/55JBLan3Rda18GrSJcm/c4m352ruLK97bpyHJ6SDDyi746KhL1YuK591QfWb4UE4cd9izF5/1KsEv+hdos/McLeD1SMoUAFcFWIzLKY+qTi6/sDTqOIiCBrcvWo7PKdGxgp/kwqOm5vp8UP4KD4J6hpwnk1bTNVShez/jh+qf+Bml4re9t08ZDZrJy1sH2xEXx2kXFsuHDAodx+o3zmgkglieNVsE19qS7euYbymUR49/hms67J6OedCF+PyRdJfbzmSZL5h7EUSIHMMq6jN84/zOFP1TEuLuHvuaJFi2LkyJE4ceIELD8gb2/vR2KRM7U3uvjWfKCN5ad3mjLrva8bj/v9/0Cnoih4t1RruIjP0oKTW00NFaJ8FNP35UdyT1AfnW93z7M1pYkabgTfNvevF8WvKeK94Ln8VUW8PKOtvv1KdHM5jYs7/L0chcxLiKjiRfFLOhbN/IjrXF5xdjeKTuvp8Cr9Rz/TzaGbpprypad3GH8o9WMsJ/cKe1OxR72/rBcfRHtLqnNZ/y9QC5MkGknRFi9ejOLFi+Orr77Chx9+iG3btqFmzZrGPzBbtmwmMYcKq+u6itOmS+f4dyQpjpl9JgESIAESSJ4Ebt26ZYTVz5w5g0OHDqF06dLYvn07qlevnjwHHItRLT2zw9QaK7Ew6ttumfrSf7NrjtnM4p5efO2fNTE3+lymVi1rUbOtcTj2/5fq/62a6Mw9pStyyf//+gzfSGKDrCRL+rz8vG8tdBY/fv3f1N58Je5H29X/+Z7NUQo9izWEj/jOq2lZJXl+fN2vSaRJzfT/R/XBHy6J1iImbdL4AOuzhDoSg9BX2rCe971TpUWngjXMOfSXJjbqIHFHEV8ai2SNQetp/IIe93bJlqguAuYx2VCJW/rjyOpIq6V2ToUW4vv/rsQ/KBdrzFZl7WstH3/o87Py61qolokTKGOX3E3ZqXj7wNLPQZOYtcpXycQH6GcbDSTBmz0TjRkYVKYtBm+cYp3ioZffyTOMxizo9aGRAAkkLwKOn1wmr7FxNCRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiQQawKvFK2PH0QUXE0D5E7cvIiO4kD4webfcV0CfyxTJ0MV+Z2wdxF2Xz1hisfs+gdN81RAofQ+5rjo9h8T0eF9EpwY0XZcPuZQ9NuhFajp4wcNLDp76yqqzXrXCK9rYOLPtfsZsXE94MTNS9gpAun1c5bGRAm2VGuSu5zpuwb8qO0S0eW5EthXKWshs62i7D2KNoAGEarzogZ3qalo87e75pr1yH6pGLraOemPvWkA1sQafUzw6OzjG82ur8U5tUTGvPiqSndsvXTEOFeqc6eOR8XrNRhV7b1SbfC2CKB3EsfKn/YvEVHoK+b1hQhOj94xG2tEjHpQmefw+fZZEkS61xwT219VshYRsfcwCYo8GOMhKnofIMLzJb3zYt7JzTHWtyqEBQZh7qBxOOu9yCriMhESOH78eLS9sg8EW7hwIebPnw8XVxfcK58DHl3/Ew2PthG7nR4SaKsO1jlTZ0Jp73wYIMkOdl4+jpf//doE2b2zbpJJVFAxS2HUmj0QGdxSi8g7cOXuDdQXh+CKWXxNa+ro/drKseIwXBlZPTKgxfyRZr7+KwHbU+u+hQpST4Mfd0qg9qU718XZ2zvGeVI8Q64H5kRs5rAGVb+/8Ve515U3Iu5dl402fRy1dTrWtvgYH1bsgjknNpn+6bxVR/P1EpCsgYU693dfOYlFp7bZUQI8nN0kCLuzuS/o+hppp8WCkeaeoRUtJ2oVaP/l4HITrK1C9G+JY7fWH7jhF4f29sp9Re8lOp5gSfRAIwESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIHHRUCF2lSwTcXbhgwZgsmTJ+PTTz9Fq1atHlcXeB4SIIFEQqB79+7YuHEjOnfujA0bNqBQofDv6RNJ99gNEoiSgJubG2bMmGESktepUwcrVqx4at+/f8l31PlE6GzWsfUOvKYc/Bc1s/vh7wjl04+sxf9Kt8WI8p2MoLD6HRUVH5vm4kvUa+U404bbfdHxjKnS2NrU77Zn1HsPX+78G6lFpE19klQ07MytK7Y6XqlSm3Uvt/+OUx8DNWup6+r3ksUjHfKkyaybmC+i3qduXo62T1/u+Bvf1+iFTyp1Rfd/vzHfs7cUQTO1SuLPoGLyV8WXRn0gNHF839U/ICgsxOxXAbfCGXLglwPLcee+uLr6B6h/Q8eC1Y0PkYqtaf9VrE7F4dQHQs3Txe0BsXZPEWezH4/W85Ayi5tuR7Rtl44aAXTti/pjWaZ+Unqtmoh/g4rmqTC+mgq7Hb521vgnWXWVt4q6HbgvFK/+X6vO7rEJUft75UGY/KiPmLJVYWn1hbA3HV/P4o1EgH0T1G9BTcfiL35T7RZ9bF/VrKd3Db9+qUS8LyrL7pkBzvL+UH8vWuQEcuTIgdy5c6NLly7o0KEDChd2FNOP/Ki4lR65fh7vl2mHvVdPOVz3lvI+V/H93w+vsjWY0O//1JLAQc1+Tlhl+n6z5oDOJTXr/eUh8019jurmKInNFw+LyGRdsz+blGkCCEts3f7eZCrIL+tc9vs+q9RNyl3Fn+k73JMfNb2XqU+gJjb4audsfF75RcxuMAhDN/2G60G30Sh3WUlOcN02F81Bdr+imstDNv5m+t2uQFVsu3zEHKEiiOrjOETaDlNnq/sWH3PZaiumpforLW4yXPpR0OaDVU0EKtW36le599hbUp3LKjyuFhwcnjzDfkyJef3ixYvo378/fvnlF7Rs2dI8n2tCNMtcXV0xatQodOrUCWnTpoUmScuaNau1m0sSIAESIAESSFQE9O9ww4YNzWdMmvhEk/m99tpriaqPT6Izfx5eIwLhrVDKOz9WNPtQfMl/xrT7/5fvuZ+AS5+PL0kynp9q9UXvVeNM3MpKSWZWSWJn3hOf/n3y/70+J2qsyVdVXkb+dNkwcP3PJlbnWvAtDC/f0fi8a1KdqtmKmngb9YtvKKLr7Rd/ChUXV3/23n6N5fOBDWiWt4L833tLnjkLY5iIpWvStLb3Y4D0GVuFwevNGSL/jx8yyFTgXdtqMm+4iTeY3eB986ypsUAqiv5Z5W7mmVk/I+giwu5+GXNL7NAOsxwlz++35VlE/erVtB23lM4S/7QVdyR5m/Z3VMXn8fOBZdB4JDUVgG+VvzIm7l2MmxIX9WudNzFV9r259kezP+KvIiJI/2zOUvhs+8yIu1BcPusY90xPfLT1T3wvsVfa3/UtP5O2JkqbK43g/Udyfo03mCtxBRqrdFLinVQsvZc8N3db9hX+Pr4BgSF35DnrpIkx0M9QDsqzuvZdmWv5JUn0Ztk7Emv07e65iCnBmVU/uuU6SYakPDX2Ki4xQ9G1yX0kQAKJg8CjpZJLHGNgL0iABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEjgkQhooJA6yakgsJoG/kwU8XQNQOpSqJZD28/mKCXBb3lsQXS6c7sIo/v83NWUxbTfobEYNlRUXW2OBN+pqbC6WqO5wzBiy+9mXQXdNRAwvwR1WvZGiRZYKA6S9tZl6ReoM/t9W9FoCczMJcLPzSQoUE0DjPKlzWITjLdVtFvRc6mpw6m91fEpCV/Zt/HiQftiLDm9Ha7irNnZt6Yp16CykHuhNmF1LfxixywRQA81zqnWwTqe7B5epj1zbSRQceXZ3dbuWC31uJeLPItPt8+IVX2tdE2CuawxxvogViSBSAhkk/dvf//mJlA6pQYQLhxlEiRcEEdtNStBgc5TDfS7LA7AKqyuFnQ/6NhsyK+7sn1P6hyVgE0rEYKVnEDF1O3NClq0L7Nf12DGPGmyPNIc1vbsk0FcvHMNkw4shY/M29z3g7S1Ti0ffyw/vVNXUUOCy5efCV83Bfd/6T1Bg699Jr+AAesnI40Ei38uDuGWaYKGYAnMtpy7NUh7hCS80GBETYihAcr2po7pGlysQe40EiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEnjcBFSAToXc9u/fj2rVqqFNmzZmqSLLNBIggaeLwDfffIMiRYqgefPmuHEj/HvAp4sAR5tUCbi7u2P27NnInz8/ateujSNHwsVsk+p4Hrbf04+uxTwRtb4homP2pkLX+t235c9j7dPvsltKIvETNy9imIiwbWj1mYi9tcTn22cZ8S9NLK7baiqspr5FauovcPzmBXxa6QUsb/aBOW5Pu29wvNMEdCpYAyp+3M+/manbXMTanhHxXh9PL7xZorkpUwG3stK22syj60RyOAWWNx1p2lch8+j6pMf8cWQ1BolIsIqmnZBzLm063AhGXxEfhhSyP+d9n4Q9IjznKT5Ufzf4nxFpU6H1f0Q4+QcRUnvLToxN+7DxwkF8U60HljX9QPxwbkFFkzUZvQqrqQDdm+LTpELQKt6u4uueklh9QKnWyC7jUqE79fVRX4C+fk2QI3VGqGBxuwLVtLsPmI5RGTfJXf6Bff3WTMDUgyvwx7PvGLG3zr41ZJwl0XT+CIdk7XoNXpJzDi3XARNq9EbO1N4iiP6Jrb2aPn5Ydt/3obZPCaw5t8/heK2o4tQ6Pk0ov7TJCLxXqo0Rmm6z8CNcj/Ae0uT0KnSnpoLTXcSnSgXbI1oLEe5T0TcVq6ZFTkDFJY8dO4Zhw4YliLC6nvXojfNGtHxw2fb4qebrGChCjIsbD4MK+uv7xBLl17oJ+f4vl6mgJC2ooacRIf+GyC0+f1VFCLJb4XCh9HdF2FF93rTe8/d9HfU+of4zX++cY+5Nv9Tuj9Eyd1X8fNulI3JvaYoXCtXGyPKdTbs6H3uIL476EKpFdt9qIoKImrTgVOAl47fzccWu0ubLWCLvexVNVFNByM9FhLGUdz780/B9ua+MMMKQE8QXMyqLai4fun4WzeZ/gAYiGKn9bJCzDL6V+8vH26Y/IGIeH3M5qv5FLFe/rXr/DBZhzufM/au/3KdVnLHpvBE23y7rmKQ6l11cwn2ykpK4+m+//Wb+//73338xa9Ys/PXXX7AXVreuiSZj6Nu3rxFWL1Ag/G+otY9LEiABEiABEkgsBO7evYsKFSpA/67p8/nWrVsprH7/4twODULNvwdi8alt8HZPi3HVe0rSsgEm9sT++u0TX/OIZu//rvtWn9uLCfL/q5qKrfdd8wMGb5yCcbvnmxgbTTamicheXD7GCI2rb7w+d6vI96CNv5pnXo17+Z88Vw/bPM3EC2hyrnfk+b+XiLprnbJ/9jfP/jWyFzfn0V/63LvvvhD8CREe33nlmPl/Uvepz/w/xzcaf3j97KHarHdR7q83sPPqcUyRfdbzqdZVOy6JxkZu+dMItx+5fg6vyv/0p2UsKhavps/cY6p2x3uyvUPOM1OSxWmCOH0Otj5PMBXtfhUTAXU1K97B2qVxCBNr9DH9my191PiHr3fNMZ+ffFWlu3km0WRx78u41YJCQ9BWnptUxL3azAEIuBuIDyt2Mc8OpwOvYJXw12cWTY6mn7Uoyz8l2Z2WW7ESKoAfEhZmS2pk9eVhlxoHFSDXtaR33odtgseRAAkkUgLhKcISaefYLRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJ4HAQ0IDGLewbMkYAey9SRUK170Xr4dvdc49So28W9ciMw+A4u3bmumzYLFoFwtZj22w6IxYoKKqtFFExWR8maIlZcP1dprD6714gul5SAJDWnFClQJEMOzBLHx4hmCTNrue4/JmLNvf0aQYNCNYBQg0CjM+9UaY3I850I4s+FMoSLrisXe1srAYVqhdKF77ffZ62rg6s6R3qnSmME1lvnq2xE32+HBEEDsLJ4pMcdqaNBjLvFiVQDM2NjIyt0wVYJBGuYq4ytev602eCW0lWCKssZZ9YVEQTbA8XRVYM142JOnq5oOPwVEygWl+NY9/ESaNeuHTZs2BDlSZ2cwgP8Usj8efbZZ9GpUycUqV4ONRf8d0+I8uBIdhwW52R1sI7KwmRWq9nPyajqRlYedi/MFGt/7S3ivcJ+n65rgHJKGavOL3t7lDms7Ry6dtY0p/O4qwReppGgap17my8exheVvc296kDAGVl/0QQ2n5QgS3vTfn+3ex4qZPY1Qc+uTs7Q4PPrwbeggun2nLSuBg9rUGheEYrfKwGLlln3IE3OoALsNBIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJ4EgSyZcuGyZMno3fv3kZsXYVwOnbsiJEjRyJnzpxRdmnhwoVYtWoVhg4diojfAUR5EHeQAAkkSgKurq5G0LFMmTLo0qULpk+fznmdKK8UOxUZgdSpU2PevHlGXL1WrVpYuXJltH+/ImsjqZcdE3EyFf+KaJpAvJuIqkVmKrJc9q/+RpBcv9dWYTbLNst33F2XjQb0ZWcqRqzfq7+64lt4uaUxCclTObuKD1N6vF2qJaYeWhnpcS8s/wr6sjcjSvbry8a/SYXe1KLrk3XsGBFC+1a+r9dzqgiaCiuP2zPfQUD8FRGTUwEyNRV3z+iW1gi1BQoPe9Ptuv+8j4ziO6Aia2qLRPBOk8pb9un2GdCXvX249U/oy96+FAFofcVkY3b9g9XNRyGr+H2du33VVl3P+da6n5BWkrwXFj+qUzcv4+cDy237rRUVvntb6um4rgfdfkBQf7RdHybsWyTCe4usQ21LfV9Un/Ue0rl6GPE49YWKyhaf2i4igNvlfeR4/SLWbyUi/G+vnRSxmNuPmcB6EbhvLuLeKpKvov++4gM3ef8yRPR70W4l9PtfRRXt7bgkc6jx93v2RWZd56C9HcE5lPj9dbjLvUXfq2o1RIxShREtf8dXV35nf4hZj+q+lX5i+wfqRixQYUkVQM8jfj0q9hjdnLCOjWoua5KBEn/0QQHx+0st8/n11d8bnyLrOGsZH3PZastaar+jGu/uqyfMe0PvPerb+PmOWdZhDsukOpfd3MJ9Z1XYNbHbuXMiIvrqq0ZQXZejRo2C/i8Tlemz9hdffBHVbpaTAAmQAAmQwBMloLEjU6ZMQY8ePXDz5k08//zz+P7772ElPnminUtEJ9c4ntYLR6GlJKXSeJOaInq+svmH8v/ZSBEqPx6nnqqfutruKydsxx2U53s1+7b0+dpNngk0CZo+O6vdkGdITchkxdbos7jG+WgcgVUWHitzWRIkZTbH6K9Gc4fZ/jdXf3gVFU8j/+taZiV0m3N8kymy+qMbQXbP17qtSdMsG1SmLfKkzYJWC/5L9NU6XxXo5wzDJBbHsiwe6Uz8kQqbR5bQS/ukZn0OYB1Xx6ckfGXfxosHrSKzXHJ6O9rkr4LOkjxMheat5w57MfuLd65h0oGleEOSQOVOkxkqBK/jVp9/bU+fp/288mClXXyPlqkQvYrbx6ddk+tmjTE+22VbJEACT5YAxdWfLH+enQRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARI4AkTSIEUaFfgGVSY/oYJcrLvzqRafdEsTwUj9muJlat4uacIB1fLVhTLzuy0r27WY9r/wAEPUTCwdBsRIi+KlgtGGsfLpnnK21rR8WgfGojw+hdRBO1o5TBxvtUAyc8qd0PlLIXRPG9FvLMu+qA8dQrV4BIVnrcPkAy4e9Ocv7wII6+VgCbLTty8JEFYIQgICrSKHliqiHIW93RYKk6VKoauAsyjq76EcXvn46d9S40j588HluHLHbNFEDr2wToq8lyzaH2H86UVB0sPcQ4dJU60+0SQOaK4enpXT+y/+p9Qs8PB3EiWBJydnREaGoqKFSsaB/TWrVvDyytcYD8pinPfz8cQ5bW6cPsaAu4GSrBhKoc6jzKHtaGcqb1Nexpg/pUEFhdMlw0txGG956qxkjghLTpKAoteKweb+1XExBT2HVku91S9t6qwutphEW1/Jlsx4zxtH3h+VBJDqN2MkNAhvZunKT9tF6RuCviLBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABJ4AgXLlyhlB2r/++gtvv/02ChUqhH79+pn1dOnSOfQoLCzMCMIdOXIEp06dwoQJEyjE7ECIGySQ9Aj4+Pjgjz/+MALVH3zwAf73v/8lvUGwx08tAf07pUk/atSoARVYX758OfQ9/TTZlbs3Ih3u1fs+MpHulMLIRJejqju+ek9suHAQ6l+jL3vLIN9/h9wLtS+KcV0FoCOzmPqkCc8tcThzzvB88bam7AXVTgdegb6iM0tYXevYC6tHd8zD7tP2+6wajwGlW6Pv6h9E1t6x88pEGcdkMY0ppuN1/7X7onyxqRtdnZHlO5vE9REF66I7hvsShoAKId6+Pw1VHHHHlWMxnuhxvv9j7Mz9CjovLIFD6xhLWN3aju+l8lL/vNhaTHP50PWzsWoqPuZyrE50v5J9UoeIxyXluayJgtRPNLGLq//222/o1asX9P+WJUuWoGbNmhEvA7dJgARIgARIIMkQ0L9lb731FrZu3QonJyeTvLNz585Jpv9PoqPTj67F8jO7MLFGb9Tw8cPwch3RXOJrHtXu3vdjt29HY2LUPCSOJjoLCg2vZ19Hj/V0+e84FU+vmd0P9SXeZ/XZvUbovKR3PtshKrKvFvH51lYhkpVymQqiR7H6klRsGVTs3DJNNnbuVkCkCeSsOhGX6vuvfbAE4q39hTKEfy4UGMF/f+25faZKIUlGFZ0dkrgANR177+KNkCt1JokPCjIC+Vk80sv5gjBUROB3Xz2JH/YuxMgKXUx8UcNcZWzN5pekS24pXSW+q5x5Bo8YD2SrGM1KoIjgZ5cEazQSIIHkRcApeQ2HoyEBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCBuBFolLssNl865CAWbrUwbvd8s/pqsQZWEfaIs55am/xVbGW6ksEtNRqLk15M+7VuSFh41JU69sXVcosT4VslW+L3wyttDotOKf5zA9KARxWFVgfJPGkyOzTfJl8VpErpYiv79eByXLp9He9KgKE6QMYUALr3/tgziRi6vW26eMhsVs5a2L4YRTPkhIuIp2+4cMCh3H6jfOaCSCWC5/NPbjFBXBfvXEP5TL6Ye3wzdL1ilkKYdyJ8/aY4MsbW2i76BEWn9XR4Tdi3CCrsrOUtF3zo0JSK0meWcR29ES7a7LCTG8mKgItL+BwoXrw4PvroI5w8eRKrV69G9+7dbcLqDztgiSl7YqZzOKXdvSCqjmjgYnzOYT2PCqBvk+QIKt6uc6yYV26sPLvbbBeXdb0HaLC2zunoHL0Lp88h832LretTDq4w62XlPmFvhdP7SLD25QeC0tWxWjkcv3nRvjrXSYAESIAESIAESIAESIAESIAESIAESIAESIAESIAESOCJEmjVqhX27t2L4cOHY+zYscibNy9GjRqF27f/E0CdPn06VFhdbdKkSejWrRtUcJ1GAiSQtAlUq1YNn3/+OQYPHoy5c+cm7cGw908dAU1MvnjxYri5uaF69eom+cdTByGBB1wmUwEjCFZWlulcPeApIm1VshZBP/9m0OTmtNgRWHN+H2YcXYcR5TuK90/sHTfchbdzipSGe+zOlPC1+vo1wbbLRzH7+MaEPxnPQAKJjADncuK6IO7u7g7PrImpd9euXUOHDh3Mq3379ti5cyeF1RPTBWJfSIAESIAE4kRAxdTr1auHOnXq4Ny5c+YZfP78+aCw+oMYNY7GXmhba2hitJ6rxiJUPkuvmq2oebZ+8Mi4lagvelQWnR+8HhPVfvs2B5ZuY+KBBm+cgr+Pb4DG/zyKuUq8zjfVXoGKtg9c/7NDU9p2wXTZzLOvw45oNg5cO2MS7ehnFPYWcD/ZXPnMvvbFJlmcCsgHBAU6lEfcyJna2xQtPrUNI7f8ibthwRi3d75Zv3b3lhGG1/Kp9+MHvFOlwStF62NUxa62l17jNC6pzHZf/6YRTxGr7fSunjh983Ks6rISCZBA0iHwX1Rl0ukze0oCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEAC8UbgjRLN8E8UAWkasKMCvirwXeG+E+DcE5uwXYLYOhSsji8qv4jqIir8WrGGxiFx4cmtiGm/dvzQ9bM4fuMiWuWthJye3uKwmB3N81Y0YyqRMY8t0M/jvkOiCrdb5inOgGot81UWx0B3VJK+qah5ejdPE+yX2lmcBbf+ZRwaZzcYhHYFqqFOjhL4tloPU3YnNNhqyoizj9+7wAgj/3lkja08qpVtl44aAXQVTbe3XVdOYMrBf6UfRZDDM6Ntl3I7fO0sftq/xFamQYm+Ml7LmuapgFVn92CBsFPz98qDMPnZffWEEYdXwfP10Yizp3cNZ5PqIYTqrT7oMrtnBjg7pZTrt9m+mOvJhEDKlCnNSPLkyYP33nsP+/fvN0FVb7zxBnx8fOJtlOnE2VYtlzhvR2fW3M4oTr8RzVUSIKR18bAJpatjcgpRbVfHZ8u83NKaVXe7ZAnnbwcgi0c6M280sYJ1DusYa7n23D6T+MDa1mVc5rDWL+aVSxfGsnlkQOlM+aEO3pbV8vHDsjM7zWZtH3/burVfkzy8UaI5ioiYumV6n/PPmBfvrZ9sFWHjxYPm3tKxQHVbmQrIV5J73pCNv9nKrBXlvvT0Dty1u89Z+7gkARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggSdJwNXVFfq9hAqo9+zZ0wit58+fH9999x2Cg4MxdOhQODmFh/6rqPrkyZMpsP4kLxjPTQLxSKBXr15GEKtjx444dCg8cXk8Ns+mSCBBCWTOnBlLly6FCqyqwLomL6fFH4HnFo7C4evnMLFmHxzr+AM2tPoMbfJVwXzxXaG4dtw4LxcfhdE7Z4vvT+yklJSz+jaoP8bQch3gJ4njE4NNO7wKv8uLRgJPKwHO5cRz5fVv/61btxJPh+735N9//4W/v/ijLVuGefPm4euvv4anZ7jPXqLrLDtEAiRAAiRAAtEQ0OQgmpSzTJkyuHxZ4lUqVoQmENHkfHXr1o3myKd31+U7NzCyQhcHn3qlcTrwCg6KILia5UMeEhZqtt0eMcbENBKPv1Qg/q2SLeW5b6WJ5dGmncQ3/lHs3VKt4JveB31WfY/rweEJXVVAPH/arBIjcBwaf9StcB2HU2iCtxcLR/4+23s1/LOfTBLHY2+bLoZ/rqmxS/amsUUuEuewIZqYH63/jMRdbbt0BMdvXsTFO9dQPpMv5h7fbNY17miefBai5TdD7pjm2y76BEWn9XR4Tdi3CJfuXDdlLRd8aN+NWK1rQjaNTzp643ys6rMSCZBA0iHwaHfSpDNO9pQESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEHAh4p0qLP+q+g1Le+fFS4WdRLMN/YsFa0UWEtnsUrQ91LFQb98xraJirDMLu3UM7cdRbJgK+XQvVxsz6A9FAyt9YMxFBYSEx7jeNya9Pt09HEXEkXNvyE7wjDpKT9y81Qu6Z3dMjX9os6OxbA41zlzPVP6/UDaWln2p7xFnx5wPLUDlLYfzbbCQKiTjx22t/EmH1VJhS500jEK4Bln1WjTfi62Ol3xNr9BFnxYORBt/9uG8Jzt26iiWnt5v2o/sVEBSIz7fPQpPc5R+o1m/NBEw9uAJ/PPsO2hd4xvT/2Zwl0XT+CATfd07Vg5TfS0WeNYGJE2r0Rs7U3oan1WBNFWU+bYkyl8AaEYK2P96qp0sVjR9V8XlT1Ch3WXTxrWmcHe3rxHa9hQjdrzu/H5bTZ2yPY72kQeCDDz7Ali1bcPToUQwZMgS+vr7x3vFaIiL+YYXOpl19X39Z+SW5v+R74DzqqDyyfHi9FpJUQe8zmnRABce7F6mHqpKkIJWzKwaVaWuSL+hSTduvl7MUsrpnEGHyZqbsufzVUFIEydVmHl1nEjMsbzoSz+YoZRIhmB0RfmkwsQqiqwC7vcV2DusxWeQ+9VWV7nhf+vab3Hde+fcb/Ht2t2lOxc+rZi1qRM61wH5OmwryS53Am+YpjzUtPsbSJiPwXqk2eC5/VbRZ+JHNqduq22vVOJNsQe9jL8u9Q+8bn2ybjj+OrLaqmKXesxvlKosxu/5xKOcGCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACSQmAunSpTPC6iqy3qZNG/Tr1w8qsr5r1y6oqLpluv7zzz/jhRdecCi39nNJAiSQtAiMHTvWzHUVzbp9O1zkKGmNgL19mglYAusqXKoC6ydOnHiaccTr2PcGnIJ+J+7/ex9kntQZxab1Qt81P0DLaXEncOH2tSh9jCK2tuDkFpT76w3k/uVFDN88zSbEF7He494+Kz5cNBJ42glwLieOd4CHh0eiElcPDQ3F+++/j1q1aqF06dJQQdr69esnDljsBQmQAAmQAAnEgcCePXvw3HPPoUSJEiYR58SJE83Rhw8fNsnN9G8dLXICKrrtIT72X1Z5yUFgXcW9C2fIgamH/hMsP3T9LI7fuIhWEiOS09MbBdNlR3Px21crkTGP8bnX9dQiPK7mJn78llllGdxSW0UmVkc3UtmJtXu6uDn0Q/drTI/9cVrmIWVW+yp0rtYyX2UT61NJRMVVrDy9m6cc64bUUtdDlmoR29EyV+lnWhcPqL++mo6lj18TE19kHw/UMl8l0870I2tx6uZljCjfCX2KN4bvfQ6jq7yMacIrMtt26aiJQ1Cu9rbryglMOfiv9LcIcnhmtO1SYfTD187ip/1LbGW6Uszrv/gsjV0onSk/Bm+cYur4e+VBmPzsvnrCxDSo4Pn6GMTZHRqPYkM5qlm8I1bL7pnBxFvNFSF3GgmQQPIikOKeWPIaEkdDAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTwtBLw+bkbAoMfXyByOlcP41ipouORWUz71WnPRUSV1dFTxZVD74XhnvzExtRxUo+zzNXJ2Yi7W9u6TCE/Pp5eItp+Jcp2a2QvjmeyFcMwCRSMjWmfVzcfhcZzh+Pc7QcD+tK6uBvnVHXCPHPrikOTX1R+EZ1END7TT51Nv64H3caNx3i9HDoTYWNZ0xEiUj8JGy8ejLAn+k11TNUxqYMpLXkR2B9wGhWmv5mkBqXzTxMY2N8bIhuAJoYoJg7Pb6376YHd0c1hdVw+0H4shm2aiu92zzPJDI7fvPhAG7Et0HtkUGgIbocGxXiIiqfnEOf2YzcuRHo/a56nAtqIQHvHJZ/F2JZ9BSc4oU2BKiaBhn0510mABEiABEiABEiABEiABEiABEiABEiABEiABEiABEjgcRBQgdratWubBLEqFhfRnJyc0LFjR/z000/QdRoJkEDSJXDs2DEjBNm0aVMzp5PuSNjzp5XApUuXzN+s69evY/ny5cidO3eiRbH63F40mjss0faPHSOBhCQwv9EQqLgd7UECKtrt+1uPB3ewhAQSGYGiGXJhTYtRT7RXRYoUQfv27Y2g+RPtiJz8zJkz6NChA9avX48vvvgCPXpwHj/pa8LzkwAJkAAJxJ3Avn37MGzYMEybNg3FihXDkCFDUKhQITRq1Ahubm6YN28e8uXLF/eGk/gR9eYMxvrzB2I9ipn1B+KaxO5kcU+P7ZePGhHtprnLY/rRtRi04VcHn/TOEuMxvFwnI6Y9X8S0J+5bjO9r9MKso+sxYd8ieLmlwecS3+KXMbcRDR+19S/kTO0tQuSdUdI7LzQp1/siBq5i5iMrdEa5zAUxQ8TKP9sxC/Vzlsb/yjyHS7eviz/+j1h4cqsROX+nVCsTH6O+9j8fWIYeRetjSLkOCLgbiHfXTzIC8GOqdkf7As/ghPjgf7XzH1y5cwM/1OiNtef3Yc7xTejr3xTZJQZIzzVm1xxsuXRYRN1d0MW3Ft6V9r1SpcGXO/7G17JvRr33TP9VWP560C2JHgIyib//szlLmURul+5cN4LqU+q8iQLpshnOe66eRI9/v8WOK8ei5P5miRbInzYrXl35nUMdjSUaUa4jqmYravruLN9ZNM5dDv3XTDAxS1rZijdYdXYPjlw/j0t3rqGWjz8+2z4Ts49vNO29LoLwKt7+yopv8WLhuoZnm0Ux//87VFi2L1BNnutedehXplTp0Dp/ZfT3b2bG/9vBFZh2eBWWn9npUK9X8Uamv/XnDHEoj83GtjajjRB8bOqyDgmQwOMnQHH1x8+cZyQBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEkggAo9bXD2BhvFYm/2p5usYuOFnmzNjbE5eOUthtBWnxL6rf4hU5DiqNuzF1aOq8yTKR4oDrDqjWs6acekDxdXjQitp1U2K4uqxJayJF34Q5/DRO2ZH6xgdsT3L2Vkdvj8Xx/DEYgXTZceQsu3x4vKvcCc0OE7dorh6nHCxMgmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQQDwTWLFiBapXrx5tqyqqrkJykyZNosB6tKS4kwQSP4E5c+agSZMmGD9+PF566aXE32H2kAQiELh8+bIRWA8ICMDSpUsTrfAbxdUjXDhuPlUEKK4e9eWmuHrUbLgncRFIDOLq5cqVQ82aNfHxxx8/UTiLFi0yCccyZMiA33//HSVKlHii/eHJSYAESIAESCCuBLZs2YKRI0dixowZKFy4MAYPHow2bdqY7RdeeAH+/v6YNWsWvLy84tp0sqgfV3F1FVU/fzvAjN1HBMgzuqXF4etnERhyN1IeKgbukiIlbobcgbMsQ++FxSn+JdJG46EwtXMq0yerKVcnZwSFhVibCbLM6eltxn4q8HKM7Su31c1HofHc4Th3++oD9dO6uKNwhhw4dfMyzty64rDfPt7gu93zjNj6cRGSTwy2rOkIvL12EjZePBjn7lBcPc7IeAAJPFYCTE/9WHHzZCRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTw5Al8VKELfqndH6OrvIzLd27ESVhde79GhMhnHF2HEeU7ikRzilgPyN3ZzTilesoysVhfvybYdvnoQwmrJ5YxsB8kEFcC98Q1+tUV36FbkToo5Z0v1od73J+76dw8Y31MQldUR+/+/s3Qc+XYOAurJ3Tf2D4JkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJxERg6NChcHZ2jrZaWFgYpkyZgi5dukDXaSRAAkmXQKNGjTBgwAD07t0bW7duTboDYc+fWgIZM2bEkiVL4O3tjWrVqmHv3r1PLQsOnARIgARIgASSM4HUqVPj5s2bT3SIo0aNQv369VGnTh1s3ryZwupP9Grw5CRAAiRAAnEloEk19e9YmTJlcPToUZMkZOfOnWjVqhXeeecds9SEmvqM/bQKq8eVqda3hNV1/XTgFey4cixKYXWtczc02CZiHnIvNFEIq2u/VOzd3hJaWF3PdTLwEmIjrK51lVufVeMxoHTrSOOFrgffxoYLBx8QVtdj7e12aBASi7D6yPKd8fn2WQ8lrG4/Jq6TAAkkTgIUV0+c14W9IgESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIEEI5DZPT0a5SqLHJ4ZMWTTbw91nuVndmL0ztlwdoqdC1KbfFVQy8cPKVKkwNByHeDnlfuhzhvfB007vAq/y4tGAk8bAXXC7rv6B1y8fS1WQ8+V2hsDSrU2dZvlLo+OBavDxSllrI5NyEo6jldXfoeA/7N3H+BVFfsaxr9UUggh9N57B+lFOgIKioiAiqAgyEGl2LFcFfWAWLAgoiAKShNURJoFAakHpIj03ntLgITUO7NgxyQkECAhhXd8wl571qxZM78dCKxz7/cPP5eat2FuBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQRSXMCGw82fP1+RkZFXnduGqk+aNEndunVTVFTUVcczAAEE0q/AG2+8ofr16+u+++7T6dOn0+9CWRkCSQjYgHX786tEiRK6/fbbtXr16iRG0o0AAggggAACGVUgICBAISEhabJ8G+reqVMnvfzyy3rvvfecYmM27J2GAAIIIIBAeheIiYnRrFmz1LBhQzVu3FihoaGaO3euUyTEhqofPXrUKRryySef6Ouvv9aoUaPk7e2d3rfF+m5RgaVHNuuHXcv1Zu0HEw1YT4rFzzOLcyowi39SQ256/4DK7bT2xC7N3LPypt+bGyKAwM0RuHIZ65uzBu6CAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACN1Hg0QUf6fFFn8qGEt9IO5rMUGZ7j3n7VuuX/Wtib3chKiL2OC0PDp0/lZa3594IpLnA/nMnkrUG+3vlueVfOV+uCyKi0z685UgogROuz4NXBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgYwlYINp4zYvLy+nUHFERIRsEE/CZgPWJ0+e7JybMGGCPDzSvghqwjXyHgEEri5gf+/aYgnVq1dX9+7d9eOPPzq/969+JSMQSD8C2bJl07x589ShQwc1a9bMCY9r0KBB+lkgK0EAAQQQQACBGxJIq3D13bt3q127dk747K+//qomTZrc0D64GAEEEEAAgZshEBYWpm+++Ubvv/++Nm3apLZt22rx4sWK++9k+/znscceU2BgoJYtW6aqVavejKXdtHsEBwfr1KlTKlq06E27JzdKfYEFB9dr46m98nR3V3L+/waKZM2lF6vf5yzs7qK1tfX0AU3dsThZ16bmbkeKp8QAAEAASURBVKaYNfD/N5SawsyNQNoLEK6e9p8BK0AAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBA4KYL3Giw+rUuODgi9FovYTwCCKQjAft/EH0m/Hw6WhFLQQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg4woMX/uDNtT0Vqepr+vcsdM6b7+On9a5o+bLHJ89dMJ5DTsVopjof4PWbcC6DWVefW6v6g7olHEBWHmGECjgn0Nv1+6WIdaa0RaZJ08eTZkyRU2bNtXw4cP13HPPZbQtsF4E5Ofnp5kzZ6pz585q1aqVUyigZcuWyCCAAAIIIIBAJhCw4eqHDx++qTuxQbP33HOP8ufPr1WrVqlw4cI39f7cDAEEEEAAgWsVOH78uD799FONHDlSp0+f1gMPPKCpU6eqUqVKsVOFhISof//+GjdunB599FGNGDFC9udsZmvPP/+8PvvsM/n7+6tatWqqVauW82qPK1SoIFtYlJYxBY6Gnkn2wm2A+XPLv3K+XBclJ5TdNTa1XglWTy1Z5kUg/QgQrp5+PgtWggACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggkMkFRm+cq+NhwRd36WNebGZc4Szml7yXviRvc+QVE6OY4AuKPnleMafOK9p8xZwK1d6cETq8a7kZQUMgdQUIV08934YNG2ro0KGy4VN16tRR48aNU+9mzIxAKgl4e3vru+++0yOPPKJ27dpp4sSJuvfee1PpbkyLAAIIIIAAAjdLIFu2bAoOvvRv1ptwU1tEzP59whZssX+fyJo16024K7dAAIGMIPD7gXUKiQjLCEtljbeYwLi3PtLcCdPl7ZNFrR+6V227d1JQ7pzarnPavnuFoxEVGan+dzyokNNn9MLod1Sn1e36/cRG6cTVsbzdPdW6cA25u7ldfXA6GGGLo3h4eOjcuXNasmSJVq5cqUizf1ss1NPTU6VKlVLt2rVVo0YNVa1a1Qlez549ezpYOUtISQEbpH4m/HxKTslcCCCAQLIECFdPFhODEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEELh5Am4mPMct0Efu5kvFc9y8G3MnBBC4KQJPP/20li5dqi5dumjt2rXKm9cWWKAhkLEEbEja+PHjFRgYqE6dOumTTz5R3759M9YmWC0CCCCAAAIIxBO4meHq7777rp577jkNHDhQw4cPl7u7e7y18AYBBG5dgf3nTqjjvKG3LgA7T9cCYac2yv2+SnJvUFy/ZgnTr+snJLreiNaF5FG6ukZ5rtao+asTHZNU59w7X1PdvGWTOp2u+suWLesEqbsWFR4e7jp0QtY3b96sbdu2OUVUbOi6bfny5VNUkQBFNisiz9K5Y8dzgAACCCCAwLUKEK5+rWKMRwABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBA4AYFxo0bp+rVq+uhhx7SvHnzCJO8QU8uTxsBWwzEhqrbYLT//Oc/Onz4sF5//fW0WQx3RQABBBBAAIEbFrgZ4eoxMTF65pln9MEHH+j999/XgAEDbnjdTIAAAplLIComOnNtiN1kKgGfthWStR+v2wona1xigzLS7wEbrm5/tl+pRUVFxTttnx24nzwun9oUG4wHwxsEEEAAgWsWIFz9msm4AAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgRsTsMGVU6ZMUYMGDfTf//5XL7300o1NyNUIpKHAyy+/rPz586tPnz46dOiQRo0aJQ8PjzRcEbdGAAEEEEAAgesRSO1wdRuu2qNHD02dOlXffvutunbtej3L5BoEEEAAAQQQSGOBAwcOaMuWLVq/fn2yV+Ll5aXo6GinONv6ut5aE3Yg2dcyEAEEEEAAgcQECFdPTIU+BBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEUlmgZs2aGjZsmJ555hk1btxYDRs2TOU7Mj0CqSfQs2dP5cmTR507d9aRI0c0efJk+fr6pt4NmRkBBBBAAAEEUlzAhqufP39eNgQ9pQulRERE6IEHHtDs2bP1888/q2XLlim+fiZEAAEEEEAAgZQTsH8n2Lp1qxOiboPUN2/e7BzbvrNnzzo3yp49uwICAhQSEpLkjT09PZ2/W9jnBW+++aaKFi2qO2b9nxSW5CWcQAABBBBAIFkChKsni4lBCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIpLzAgAED9Mcff6hr165au3atcubMmfI3YUYEbpJAu3bt9Ntvv8m+Nm/eXDNmzFDu3Llv0t25DQIIIIAAAgjcqIANV7ctODhYQUFBNzpd7PXh4eHq1KmT5s+frzlz5uj222+PPccBAggggAACCKSdQExMjPbu3RsboG5D1F1f+/fvlz1vw9GLFy+usmXLqmnTpnr88cedY/s+b968Tt+CBQsu24S9LjIyUq1bt9bQoUNVsWLFy8bQgQACCCCAwI0IEK5+I3pciwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAwA0KjBs3TtWqVVOPHj00c+bMG5yNyxFIW4H69etryZIluvPOO1W3bl3NmjVL5cqVuymL8nBzj71P2Iz1ij4YrJioaCkqxnxFKyb64qs9tn3OOVefeXXOm+A4307V5FWrSOxcHCCQEQQ83P/9/s8I672Za4z7Z8PNvC/3QuBaBbw90j4WLjXC1W2oqg1WX7hwoX755RfVq1fvWmkYjwACCCCAAAI3KGALp7hC0+O+btu2TaGhoc7suXLlig1Nb9myZexxyZIl5eXlleQKbGi6fQ4QERHhjPHw8FBUVJTq1Kmj4cOH87M/STlOIIAAAgjcqEDa/yv6RnfA9QgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCGRggRw5cmjSpElq0qSJ3n//fQ0aNCgD74alIyAnTH358uW65557nBC16dOnq1mzZqlOUztPGU1s8YzCoyM1cPhD2r153zXfM0fe3Hqj0wAVLEG4+jXjcUGaCWQxgcy35SqVZvdP7zfO6ROgH1oP1pnw8+l9qazvFhcom71gmgsEBAQ4awgJCUmRtcSYoiW2gNDvv/+uX3/9lXDVFFFlEgQQQAABBBIXsIHmu3fvjheivnnzZm3dulWHDh1yLvL29pYNSy9btqzatm0bG6Bu39vnU9fT7LX2Z76bm5vzWr58eSdUvXXr1tczHdcggAACCCCQbAHC1ZNNxUAEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQRSR6BBgwZ644039MILL6hRo0aqVatW6tyIWRG4SQK5c+d2glRtoKoNVPvss8/06KOPpurd3U2QW9sitzn32PP4E3r66adlw+WS0zw8PFSnTh3NmDFDuXLlSs4ljEEAgQwk0LRA5Qy0WpaKQNoJZMuWzbl5cHBwiiyiX79+mjZtmn7++WeC1VNElEkQQAABBBCQTp48GS9AfcuWLc777du3Kzw83CHKly9fbHB6+/btY4+LFy8u++/flGzlypVTZGSkihYtqqFDh6pz585O0HpK3oO5EEAAAQQQSEyAcPXEVOhDAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDgJgvYYPUFCxY4IVRr1qxRYGDgTV4Bt0MgZQV8fHw0adIkvfLKK+rZs6e2bdumt99++6aErHXt2lWDBg1K9oZsCPyoUaPk5eWV7GsYiAACCCCAQGYTyJo1q7Ols2fP3vDWbLjq559/7oSrt2jR4obnYwIEEEAAAQRuJYGIiAjt3LlTmzdvvixI/fjx4w6Fr6+vSpcu7QSn33vvvbEB6mXLlpWrYMrNMGvatKnmzp2r5s2by9OTmNubYc49EEAAAQQuCvBTh+8EBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEE0oGAm5ubJkyYoKpVq6pPnz6aPHlyOlgVS0DgxgTs9/Wbb77pBL717t1bGzZs0DfffJPqQW958uSRDXL9/fffFRUVlegm3N3dnf4RI0boySefTHQMnQgggAACCNxKAq5w9ZCQkBva9nfffafBgwfrww8/1D333HNDc3ExAggggAACt5JAxPpD6vpWGx3ae0CRkZFOcbKCBQs6wen2edH9998fG6JetGjRm1K87Gr+NlD9jjvuuNowziOAAAIIIJDiAoSrpzgpEyKAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggMCtIhAdE3OrbJV9IpApBWLE7+FM+cGyKQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAggwvYQOjx48c7oVStW7dWjx49MviOWD4CFwW6d+/uBKx37NhRdevW1YwZM5z3qeGzZMkSffnll1q0aFGSweo2/M3X11c//PCDmjdvnhrLYE4EEEAAAQQynICHh4fz8/Hs2bPXvfaVK1fq4Ycf1hNPPEHxkutW5EIEEEAAgVtVwD2nn1p2uEstazZ0QtTLlCkjf3//W5WDfSOAAAIIIHBFAcLVr8jDSQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIGMJNC3YmttP3MoIy2ZtWYCgTZFbssEu2ALCQUKZ82lB0o31vnICwlP8T6TCdxVtFYm2xHbQQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQyAwCLVu21KBBg5wwyoYNG6pUqVKZYVvsAQHVr19fq1atUocOHVS7dm1NmjRJtohASrTDhw/r66+/1rhx47RlyxZVrVpVb731ll599VWdO3cu3i1ssHqxYsU0Z84cfn/Fk+ENAggggAACUtasWXW94eonTpzQfffdp8aNG+uDDz6AEwEEEEAAAQSuUcCjQKAe7dVPDfKVv8YrGY4AAggggMCtJ0C4+q33mbNjBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAg0wq8XOP+TLs3NoYAAjdXwM8ziz5t9PjNvSl3QwABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBOIIvP3225o/f74efPBBLVmyRDYMmoZAZhAoWLCgFi1apN69e+vOO+/U0KFD9eyzz17X1qKjozV37lyNGTNGM2fOVEBAgB544AFNnDhRNWrUcObcvHmzvvrqK0VERDjv3d3d1bx5c02dOlXZsmW7rvtyEQIIIIAAAplZwN/f/7LCJMnZr/25bH8Ou7m56dtvv5WHh0dyLmMMAggggAACCNyCAgX8c96Cu2bLGU3Az9Nb2bx9M9qyWS8Ct5QA/6vJLfVxs1kEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgYwg4O3t7QRE33bbbXr11Vdlw9ZpCGQWAR8fH40fP17Vq1d3gtVXrlypsWPHOuHoydnj/v37nUD1L7/8Uvv27VOTJk2cAPWOHTvKzh23de/eXV988UVs16BBgzRs2DDZkHUaAggggAACCFwucL3h6vbvqwsXLtTixYuVMyeBqZfL0oMAAggggAACLoFxTZ6S/aIhgAACCCBwIwKEq9+IHtcigAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQSgLlypXTBx98oL59+6pVq1ZOgHQq3YppEUgTgYEDB6patWrq2rWratasqWnTpqly5cqJriU6Olpz5szR6NGjNXv2bCe0tUePHurVq5dKly6d6DW2s0GDBipcuLAOHTokG8berVu3JMdyAgEEEEAAAQQkPz8/hYaGXhPFqlWr9Prrr2v48OHOz/RrujiFB5+8EKIXlo9XeHRkCs/MdAikrECH4nV1d7E6KTspsyGAAAIIIIAAAgggcAsJEK5+C33YbBUBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAIGMJ9O7dW3PnznUCof/++28FBQVlrA2wWgSuItC0aVOtWbNGnTt3Vt26dfXpp5+qe/fusVcdPXpUY8aMcULV9+3bJzt+4sSJ6tChg7y8vGLHXengu+++k7e3t6pXr36lYZxDAAEEEEAAASPg6+t7TeHq58+f14MPPugUAurfv3+aG245fVBTdyxO83WwAASuLOAmT3cPwtWvjMRZBBBAAAEEEEAAAQSuKEC4+hV5OIkAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBA2gp4mbCtLqUaqUJQER04d0LLjmzR6QtnlSNLgFYe25a2i0vFuxcLyKNnqnbQ26u/08HzJ1PxTvGnrpazhLac3q/QqPD4J1LoXT7fIJULKqgFB/9xPsPquUro9wPr4s3eqlB1BXj7xvYV8s+pzzfOi11ToLefupVpKts/b98aLTz0j6JjYmLH31W0ln7eszL2PQcZX8AGS1epUkWPPfaYpk2blvE3xA4QSCCQP39+zZ8/X4MHD1aPHj20ePFidenSRWPHjtX06dOVNWtWPfLII7LFBsqUKZPg6qu/rVOnztUHMQIBBBBAAAEEHAEbrm4D05PbXnnlFR07dsz5We7m5pbcyxiHwC0twO+UW/rjZ/MIIIAAAggggAACKSRAuHoKQTINAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAikt4OvhrV/uekNHQ0/ro/UzVdCEaf9fzS66PX9FvbRiQqYOV6+as7geKtNEP+5eftPC1VsXrqGI6KjYEPOU/jztfD3KNVPhrLmdcPWOJeqpYf4K8cLVSwcW0JSWzypuKOH0nUtj15Td219/tH9LK45uVX6/IPWucIfWHN+p5jNfiV2u/X75sMFjGrR0rKJiomP7Oci4Ajly5ND48ePVsmVLJ2y6Z8+eGXczrByBJAQ8PT31+uuvO2Guo0aNki0qULFiRX322WdO0LoNeqUhgAACCCCAQOoL+Pj4KCwsLFk3Wr16tT788EPn53XBggWTdQ2DEEAAgdQWKOifQ/a5UsUcRZxidDuCD2vNsR2KMf8VMM/WlpvChWnZbCHFikFFzRqLyRYX3G+KKW47c1Arj25T26I19cOuZfGK6KXUWsuYZ053FK6u9Sf3mudS651p06q4YVJ7Suuih7XzlFazglWc54N/HFiv1cd3JLrUKxVGpOhhomR0IoAAAgggkCoChKunCiuTIoAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIHDjAo9XbGOCoAqr85R3YgPGJ25fpBENeimfCdbOzG3G7hUq8W1vnbwQclO22a9iW4VFRWjs5l9T9X42oOmzDXOdezQtWFnz9q6Jd79+ldqq3Zwh2h1y1Om3wV/Hw/416FC8npr+9JJOh59zzj9brYNeqnG/6uQp4wSu287/mTCuAC8/J2D9icWjnXH8kvEFmjVrpmeeeUYDBgxQkyZNVLJkyYy/KXaAwCWBPXv26NNPP3UC1c+dO6f27dtr+/bt2rFjh0JDQ5VUsPratWs1btw4vfvuu/Ly8sITAQQQQAABBFJAIEuWLAoPD7/qTNHR0erdu7fq168viv9clYsBCCBwEwRsaPkrt3VR7/KtNHrTPC05tEmhkRd0W55SGlG/pwJNwbqX//dNmoarVzOh7182fUrnzbrGbPpVs/f+pbx+2fVA6caa0folp9jevL2rdTYyeUUukstqQ9QfKddCfc2zxn5/fhZ7WVoUN4y9eYKDtC56OLTOw+pqPofg8POmMGIuvWyet722apI+NAUv47arFUak6GFcLY4RQAABBBBIXQHC1VPXl9kRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQuG6BKjmKyt3NXQHevtL5f6d5beUkvVWn278dmfToZgWrl89eSI+Z4K1q0wakqmSgt5+q5yqhBQf/kYf5XBvlr6jnln0Ve888voGqFFRE7wR/HxumH3vSHNiQsPkH1sUGq9tzk7f96YSrh0SExh2q3804G7zevGBV5zjeSd5kWIEhQ4Zo3rx5evjhh7Vo0SJ5eHhk2L2wcASswIIFC/Thhx9q5syZypcvnwYOHOiEtObJk0eRkZGy3/NPPfWUZs+erS+//FK2P27r1auX/vrrL504cULffPNN3FMcI4AAAggggMB1Cnh7e+vMmTNXvXrs2LFat26d/v77bycM+KoXMAABBBBIRYEsHl6ad+frKp4tr+6Z93a8APU/D2/Uj7uW6+c2r8rXM0sqruLKU3csUV+jGvXV9J1L1X/JFwqPjoy94Oc9K7XiyFZ90KCnfDy9Uzxc3RbxG7f5NydcPTI6Kva+qVncMKdPgKrlLJGs51JpXfSwXdFaio6JUfFvezmvjc0zu6+aDTBh/Z1ljVxFEC3c1QojUvQw9tuLAwQQQAABBFJdwD3V78ANEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDgugTmH/zbue6z2/uqgF+O2DlOh5/TyH9mOe/z+mZXLxMM3rdiG5UzIeG2NcpXwXlv+wr553T67C9lAgs459zkplaFqssGFxX0vziv7auXt6z6V26nmrlLxV5jD3xMQNW9xevJ18NbRbLmUs9yLXVnkZom+N3NGZfbJ1DdyzRTtzJNFOBlguDjNHtti0JV9XTVezSgSnvl9wuKc1bK7u3vzGc77bgB5v42eNyux+7DhpG7WoWgwnqg1O2XfXU1fa612LFNClRy7mfXGZQlq+vyJF9fr/WAvtu5JNHzWT191KF4Xb1QvaOzP5eXa7D9XOx9bGuYr7z+77YuTlC73berWXcb3v5Sjft15sJ5dSxRzxx3UowJbWpT5DbdZQKcbOtTobVq5imtjV1Gam2nD519uuawrxEm/GrP2WNxu1QxRxHN3btaG0/ti9dv34zaMEev1ezqWF52ko4MKWCDLm2AtA2THjZsWIbcA4tG4MKFC/rqq69UrVo1NW3aVMeOHdPEiRO1e/duvfzyy7EB6p6ennr99de1cOFCbdiwQRUqVIgXoD5lyhTn94IVtdfba2kIIIAAAgggcOMC9u+cERERV5woJCREr7zyivr27avy5ctfcSwnEUAAgZsh8GzVDqqWq7g+Wj8zXrC66942HHv42u/ln0bh6jZo/L16j+qMeab33PKv4gWru9Y4bstvTsC6nwlXT41mw8Ntc7267pEaxQ3tc7qxjZ80zxFzu26T5Kur6OHYzb8mOSYlTiRW9NAWJ7StVp4yennlN7E2Cw9t0Pc7l8nTFDqM+2zSVRhxZ/AR7T93wvk6cO6kLkTF/7lp5y0VmN8pepgSa2cOBBBAAAEEEEhcwOM10xI/RS8CCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCKSkwEfrf9b5yAvJnnLb6YPqXKqRyprQ9M4lG+lI6GltOLXXuf5YWLDzei4yzAk8/6RRH607sUt/n9itvSaAu1nBKrKh4b/uX6vjocF65bbOGtPkSWU14ecN8pVTbt9sZkxVvVW7m9Yc36kXq99ngs9z6CETkP64Cfn+/cDfOnT+pBlbXtNaPa9HyrXQqQtnnSBwHxPy9Fqtrqqcs5iymfkeKddcxbPl1aAq96i8CUCfvnOpszYbWPXXfR/oh13LnPDyBiYs/d36j2jc5t8UGRMtG4r+U5uX1bpwDWfu/pXbO/u1exhswsffqP2g1h3frbUndjrzdS/bXLnMulcc3aYDJsCodPYCGtGgl3Pt7L1/ycsEHn1Qv5eOhp7R/45udeZ/q/ZDmrdvjU6EhThzJPzFBjj9t+7DemPVZGfOuOcrmeDyKS2f0yITqDRj9woTOl9aXzcbqIPG5Z+Te9WpRANz/lm1KlzdCamvkrO442BD7ZsWrKyJ2xYpWjHy8fRSpAlGt+Ps3hYf3qRmhapo55nD+mX/GieI6bj5PG1g02HzGdvQ9XJBBdW+WB3VzVNWU3cuNrNc3u4xoe8vm8D2Rxd8pJCI0MsGnL5wTkMcw13aHnzosvNX6njBfD/Q0qdAnjx55Ovr64RZ3nXXXcqfP3/6XCirQiCBwJEjR/Tuu+/qwQcf1HfffecEq3/55ZdOKHrFihXl7u6e4IqLb4sUKaJevXrp8OHDGjx4sFauXKm6deuqa9euOnv2bOw1f/75pwoUKKDbbrstto8DBBBAAAEEELh2gVmzZunkyZN6+OGHk7x4yJAhWr58uaZPny4/P78kx6XFCRt2++22BWlxa+6JQLIFbEG5CjkKm3/31072NbfSwDPh5/WZKRiX3GYDr8c3H+gEXPeYPyLR4HI710bzTC3A209bTh+InbqqebZli+o1yl9RfuY51q6QI7Hn7IEtsvdg6cb669gO2WdI3cs2c57DuYrc2cKA95rr65qChWWyFzTP5nY719sCfLZQYUkTsL3+5B69XvMBNchf3oS//yxXMUVnYIJfFh/e6DzDCo+OdM7YooQPlm7iPLuzRQnbF62tlce2m+dEMSqZLZ/uMM+a7DNDfy8fbT1zMMFsUv285dS19O2qZQopno8KVxfznPEn84zLtX5XcUMb/n74/KnY6/P5Bsk+d2pdpIY8TRHEuMX+bFHExqawYVETnG6fwbUrVsv5XrbX22eH3u6eGtv0Keda+7zKzn3InLPPMBNrnzZ63DH589DGy05f7fOxRQ/vL9nQ8bHmj5RtYT6HAs5nbZ892maLHt5rCh3a/ZQIyCcbJt/WFDuskauk84yvgPmMp5miiwmf2VpjO/fXW+bLhvPbNsgUj7yvZAP1q3SnsbxdweZ71X6+iTW7d/t886stvyd2+op99nsuOcH0V5yEkwgggAACCNwCAok/Ub4FNs4WEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEjvAqEm9KjpTy/pNxOQbkPFRzfupx/ueFE2OChu2xwnFMrVb0O8Xe2sCS96ZeW3suFUhfxz6uX/faM3/pqi+38ZZsK/o/V8tXv1xOLRzpia0wYp2oR7NzEhSbYtMUHgY00Yum02pG7A0jH6v5UTNXrDXN1twr9taFLvhSPVc8HHem/dj06ouw1msq1tkZrK55fdhFYddOacu+8vJxjIBrDbNmn7Iv28Z6UTKm5DlhrNeEG1pj+tWXtX6Z213ztj4v6yxwQZvb16mgm02q6dwYfV14TA25D1l1ZMcIb1Me9tIPz3Jszdhp8PNv05fbLpbRMgn1SraALUbYsbIGXf26D2L5s85axvplmjDWf/5J9ZmmNC3D9q0NsE3hd0AuNtcLuPh5c+3/iLnlz8ue7/9R29s+Z73WZCq2xQvW0Hzp10AtVLmNCridsWasHB9c7nMM2E0Nug9c2n9zvj5ptAe2vbZvbrzue+1XyuTUxI+1OV2znnXb/YsC8bKm/Dp8oFFdLSDu+oeq4SrtOxrzaM/7T5fKrlKh7bx0HmEBg4cKDq16+vbt26KSws8XCyzLFTdpEZBDZu3OiEoxctWlQjR45U7969tWfPHn3zzTeqWbNmsraYNWtWffLJJ1q4cKG2bt2qBg0aOGHrthhF3Pb444/LBsLSEEAAAQQQQOD6BTw9PRUZeTHUN7FZjh8/rhEjRujFF19Uzpw5ExtCHwIIIHBTBaqYgHQvE+htA7DtM7CkWoQpfGeL57maLcg3wBT6m7tvtX7fv05vmCKFM9u8oqAsWZ0hthjgwvb/1dC63fV4xdZOoHYtU3jPPp8bUKW9M+ZPE4ZeO28ZU4TwgdjAcnvCPu/pYQoV2mc9ttUxY2zblkgAunPi0i9x92CLEm7sMlLDzP0fK99K/3dbV+c+5UzIuy3sN6LBY5q8/U99sWmeUzzxUXO/uM0WWrRFG+3zrOk7lznP/+x517+i7LOtcSYEfWbbV1Qt57/PlWxg/As1OjoFAu2zqW9bPKN36z3iTG3D3j83+/+x9WA9aJ57fdSwt2rnKaNe5VtqVttXZc/b52TW0zZboHC72XOYecaZWLOB9bYY4W+Xxscdc7XPxxY9XNphmN40n+P79R919mqf8w03a7Vr8XTzcKazoe6bTu1T0YDczme97cwhJ/TertH2bzfvEyvKWNA8Q7XP1VaZ55CuZp+Tfrh+ppYd3uwE7396e1/9eMdgubtdfBbqGmdflx/ZYgpTFnWKSsbt5xgBBBBAAAEEUk6AcPWUs2QmBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBFJc4HhYsO4zIeiP/vGRjocGq2nBKvrznv+qco6i13yvkPBQ7Qo5YgKNIpxrbeCUDTXfYYLKXX020N0GlhfNmid2/mATym7bBhNY7mquMKj1J/e4urTVhCVlMQFK+f2CnD4bHl73+2d1LOyM09/AhDPZVtKEjLuavb9ts/ascl5d8164tEan89Iv3+1cYgKgLkZA2YCoYtny6qnFXyg4ItQZ0a9SW9lALRv4ZL8GVb3bCa1yhWLFnct1bIOkbLNB5HFbi4LVVMacW3lsW9xu/X5gnbw9PNWtTFOn/3zkBUXGRMUGpNvOD/6eoUgT2NUgX/nYa22ovQ3Ft/MFevuZz6+Y/jy0IfZ8wgMbDt94xmDns7ivRP14p+09BywZo4LjH9GLK8YrwMvXCZGKN+jSmzPmM3ftMbHz9GVMAXd3d3399dfat2+fBg8enDE3waozvcAff/yhO++8U5UqVdLixYv10UcfOd+zQ4YMUf78+a9r/40aNdJvv/2mM2fOKCoqKtE5OnbsqJUrVyZ6jk4EEEAAAQQQuLqA/btmwgImca8aPny4/P391a9fv7jdHCOAAAJpJlAh+8UifrYoX3JbFxM6bp/t9F/yhRPK/vfJ3eo+f4Qa5a+goXUedqaxoesTtv7hHG80z2lsYcIuvw3X2uO7TMHB2rG3ssX9omOi44VoF/bP5RTXs8+9bBHCsoEXnz/tOXss9jrXgS2i93y1jvrQhKXbr/fqPeoUPUyqKOGGU3udsPXNJhjctr1nj2u9Wb8Ng3e1FoWqOsHxtsCifY6079xxfb11vuu087rFBKcnLG7ob9bysQlMt3uyJj+aMPrvTTB7LxPuXtMUEjwdfk7/+fMz5/p85vnf44s+dZ5N2edz9r0NkbfP6VYf3+GM2WYKLtqgeVvwMbGWVNHD5Hw+9jlhShY9TLi+e0vU09A10xVy6bmjPZ/cwoh2LEUPrQINAQQQQACB1BUgXD11fZkdAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgRQR+H7XMtX+/hktOLBeOX2yaUitB1Nk3vCoyMvmiYiOlL9Xlsv643ZcMGMSNnudbTYUyjYbhH409IwGV++kfhXbyoY22ebu9m/cgSu0zxWa7gy4yi+1cpfW4xVbOwFXNuzcNhtYnt+El4/f8oeeWTYu9qvW9KfVbObLSc6Yy1jaNbjC5V0DywZdDL06FxHm6nJelx3e7Ly6QrHinbz05mJA/Unl8glwAtY/qN/TCccKjQzXO3V7aNTtfc39wvV6rQeccKrE5rB9dp7ZJnS+ZLbEQ4it2agNczRzz/9UJUdxebt7XjbVOROgX8A/x2X9dGR8gaJFizph1SNGjJANsaYhkB4EIiMjNXHiRNWoUUPNmjXT2bNnNWPGDG3atEm9e/eWj4/PDS9z6NChioiISHQe++e5XcMdd9yhnTt3JjqGTgQQQAABBBC4uoDr32kJRx4/flwjR47Uc889Jz8/v4SneY8AAgikiUCkCTa3zSPO86arLaRvxTZOQT5XwT473hYf3G0C2jub4HVbyM42+2zGNltQ0NW2nN6vQiY83dXsNb/tX6eHyjSJXYM9/mrzxTBz+/wm/NIzM3cTtJ6w2fDzYWuny8vdQ93LNtM5837BwX+cYUkVJbxz9ht6c/VUZ4wtqmeL+sUtZjioyj1ae2JnvGDwv45dDDyP+2d8wuKG95VoIB9Pb71hnlm5ihfm9QvUruAjKnGpWKK9xs5h+6Iu2W82JrbFdbHvr/a8z1UQMGHRw+R+PqlV9LBtkdt05PxpfbZxrt1Gou1KhRFdF1D00CXBKwIIIIAAAqkj8O//2pg68zMrAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAghch0DRrLllg3zitpMXQtRv8WeKio5Ww/wVnEDxuOev5zipkKO4QUuJzXul86457R7+vGeo/jq+Xe//PUP7zh5LbKpr6rMB4iMb9ZENl3ppxYTYa6NNqJNtFYIKx/Yl58CGY7m5ucn/UiC865rTF846h7XzlHF1Oa97zx6XDZE/HX4uXn/cN3aNeX0DnUCuNcd36u3V03QhOkKjN811js9cOO8Ew9v+ydsWxb30smO7vu1nDl3WH7djwcH1Zj1nY4O64p7L7u2vA2dPxO3iOBMJdO/eXR06dJB9PXPmTCbaGVvJaALBwcF67733VKJECT388MMqU6aMVq5cqYULF6pdu3bOn7MpsScb0j569GgnQD2p+aKiohQSEqIWLVroxAn+/EvKiX4EEEAAAQSuJGD/jZRY++STT5xiKX379k3sNH0IIIBAmghsOrXPuW/ccPGrLcQWzTuboKCevcZVVK90YIEkp7CB4gn/mPxi0y/K5xfkPMtzMwHqlXIUdcLNXZP8fWK3c1gyMJ+r67LXXSFHnL7Vx7bHnnM9f3M9a3OdsM/FauQqqWF1u8vuxQadxy1mWClHEW285OK6xjWX631ir+WCCumwCRWPW7iw86/DVX3aAE3dsTixS5y+6Esh6wl/flztnkkWPbyBz+dGix7aEHkbjt/vz8+S3K/rxNUKI1L00CXFKwIIIIAAAqkjQLh66rgyKwIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAI3JHAiLERv13lYNqg7bjtw7qS2mcBt2y5ERTivkdFRzmsWD2/nNb388kL1++Tl7qF5+9Y4S4ob8nS9a3yhekeVyV5QTy3+QsERoc40NkA8z6Uw857lW8rHwyve9PeXbKhC/jnj9bneuAK4cpvr47ZVl4Ks6ucrF7fbCW/3Mp/J/45ujdcf903tPKXl4+mtuftW63zkBR0LO6Paucto9p6/nOO6ectqzt6Lx2cjw+JeetnxXUVrafbeVZf1x+0ol72QmW913C7n2IZ5WRdXONdlA+jIFAI2aDoiIkJPPvlkptgPm8hYAvv27dMzzzyjwoUL67XXXlPHjh21Y8cOTZ48WTVr1kzxzQwaNEhXC+ezN42MjJRdW+vWrRUaevFnRYovhgkRQAABBBDIpAK2UImHh8dlu7M/U0eOHKl+/frJz8/vsvN0IIAAAmklsPbELicovVhAXhULyJOsZdiieTVylzSB5PGLSewIPuxcf6Wieond4Nf9a7XbBJw/Uq6FWhSqKvs+blt2ZLPztlH+inG74x27CgdeLB8Y79Rlb16q0UnPVrtX/7dyon7a8z/ZwHdX83Bzl58pIlgzdylXV7zXhEHtcU/aeUoH5pen2+U/B+KOS+7x1faSZNHDG/h8bqToYaC3n140zzMfX/hpokUME9v3lQojUvQwMTH6EEAAAQQQSDkBwtVTzpKZEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEgxARu67WcCukc06BUvYL1CUGGVCyqkydv/VNilcPXtwYe0J+SYOhavp8L+uUwIUgHdU7yus5aqOYuZiO2LQVH+XlnizWUH+Hv6KChL1njr9jN9WeIElGf18nHOJ9YX91o7l20+l0Le/cz98vkFqWWhasqRJUC9TPC5bflNnw0rss2GPdkWdx773nWvnD4B9q3T7F6eqtxOE7b+od8PrHN1694S9Zx5Plo/UwVNiPrMNq+oYb7yqpKjmBOIlM3ca/+5E7Hj4x6sPb7LCUC3rnHbPyf3auK2hapv5okbzG6D0XecOaSvtvweO9wGTpUx5q7WvlgdLT60MTZU3q4j2vy34dReJ+TLBp6vSBDOXjJbPv3XhOnbsa5mQ9P9jc/wtT84XTY0/umq96i86Xc161YlZ3ENXjHe1RX7WsA/SJ4m3H62CXKnZV6BXLly6YsvvtCECRM0Y8aMzLtRdpauBDZs2KDu3burRIkSTpD6Sy+9pP379+uDDz5Q0aJFU22t2bJlU9as//7M8vLySjT81S7ABqyvXbtWXbp0UXT0v0GDqbY4JkYAAQQQQCCTCNifoYmFq3/77bcKCQnRE088kUl2yjYQQCCzCJy6cFb/Xf2dPNzd9UatB6+4rco5Lv57xRbVC/DyNc9hiscbb589HQs9o90hR+L1J+fN2M2/qmmBynqi0p2atmNJvEveW/ejNp7ap66lGsne40Za0ay5nWD1qTv+fTYYt6ChDUjfcvqAyptnXbl94hcTvNp9/zm5R/7mOeCjJiQ+brPP8XqWu/hcL25/Uscxl1LVbdD7ldqVih5e7+dzvUUPfc3zzNdrPaDnl38dW9DRrj2vb3bZ53ZJtaQKI1L0MCkx+hFAAAEEEEg5gSv/TSPl7sNMCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCBwjQIbT+13Ao1+avOyhtXt7gSt/2yCw8ds+kXPLhsXb7Z3133vhCYtu3e4nq92r8Zvma8DJlA8jwkAqpSjiJ6p2sEJMK+Xt5w6mOB1G9r9YvX7VMA/h+rlK6fHyrcyoeheGmDCywtlzalG+Suqiwl8qpW7tB4s3cS5V79KbWUDnGxw+aOXApVeqN5RZbMXdMZ1L9vMGfeMCQAvYUKHPlk/S3vPHtM3zQfpQxMSb8PP1x7fqYFV2uvOIjXVrUwT2QAi296v96hq5CrpHN+Wu5SeM3uwzQantypU3Tn+pOHjTli4l7un3qnbQ8PN11dN++vN2g/p0PlT+nLzb3rfhFVVz1VCP7d9VfPbv+mEyY/d9KtzfWK/nA4/Z66ZoXZFa192euDSsZq8bZG+a/W8Cb+63Vlvq8LV1H7um4qIjoodH20So3oZPxvANLbJkyqcNZe6/Do89nzTgpX1x4H1zvvmBatq6eHN8a63J2yA/QOlG2vRPf+V/bxfq9lVdxSurrvmDFFkzMV72aCs9sVqa2mHdzS/3ZsaXL2T7i/ZUJ1+GRov9Ml14w4mbH/5kS2ygWG0zC1w1113qUePHurTp49OnEi8kEDmFmB3N0tg2bJluvvuu1W5cmWtWrVKY8aM0a5du/Tcc88pMPDaQvuuZ81TpkxRcHCwDhw4oDlz5uitt95S165dVb58eXl6ejpTurm5ydvbW/bVhsP+9NNPeuqpp67ndlyDAAIIIIDALSkQHh7u/CxNuPlPPvlEnTt3Vu7cuROe4j0CCCCQ5gKfbZyrH3Yuc56bfNjgMecZV9xF2WKEtoChq4Dgaysn6YIpWtilVMPYYTYMu3aeMnpt1STZZz222YBv27zNsyhXs4UAveMUJXT1T9i6wCmEuCv4iGzRxLjNFkjsPn+Ejprg9oktnnGercU9b49z+2Rzumw4uqslVpTQhp/bdm+J+s766plCgPXNs73sWfyd531ZTfHDEX//5IwZXq+Hs3a7N/uMzTb7bNBV5DBhccPvjeH+syecZ21PVbrLKSZoCzha0ymm0KNt9pmi8++uOCY5slxcu+8ll8Ohp5yxtfKUdl4rBhVxXhP+klTRw+R+Pna+lCh6aOcY32ygToaFqKNxss9J7Zd9Pjm6cT+nqGVyCiPG3R9FD+NqcIwAAggggEDqCLjFmJY6UzMrAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgjEFSg1sY+OhwXH7bricV4TjH4k9LQzpqAJQc9pgop2BB/SucgLiV5nA5G8TBiQDXCyoUA2jCnG/JeWzYY3+Xp663ycNXu5e1wWLp6Sa7Qh8cUC8prgo6MKjQq/6tTWbck9w3TX7CFyhT/FvSibCdIqF1TICZc6eP5k3FP6oH5PPWRC4nN/1U32MwoOD1VIRGi8Mcl9Y4O6Cplg9lBjZcPik2qB3n4Kj4q86t7+MOHyzy37WiuPbUtqqiT7Tz86KclznEifAmfOnFHFihXVqFEjTZrE55c+P6WMu6q5c+dq6NChWrhwoerWrasXXnhB7du3dwL10suubJD6vL/+VJevXlXE/pOK2nvafJ1SzOmLfyZnfaWVPIrlSC/LZR0IpKiAp/m71TJTfKV0YIEUnTezTGb/blx+Ul/zd7T4IaeZZX/sI/MItC5SQ5NbPJvmG7IB6lFRUZo2bVrsWpYvX6569eppxYoVql378sJUsQPTwcEyU2CqzazX0sFKWAICSQvYZyUdS9bXmMZPJD3oFj6zxxTqqzr1+gok2SJ0r9zWWVk8PJ1icydMWLYNE19/YrfeWv2dtpvnaq5W14SSj769n2btWak/D210gtltgbqxmy8W6WtgiguObNhHxbLl1demiOFbq6eaUPQKTkh7NvNs5r+rp+nddT84z99cc37csLdTFHGduV9izQaTD6xyt3qUbW6e8R3WuhO7FGkK+JXJXsCs2VvTdy7R+K1/OOHutijhi6awni2MaIPjP/5nllYf3+FMa+9jCwHaooYfrf/ZCQUfYwr+LTuy2Qlxt8UEn6x0p16s0Unu5vtt0+l9Zu5lGmTuPX3nUtkgeC9jZMfY8PSNp/bJBpr/sn+NE6huA+BLBeZ37mXPPb7wU/19crds2Purxvfxim105PxpPbXkc607vls2xL2dKQi4/sQePbl4tNaaff3Y+iU1KVBJiw5t0H8WjdJ+UwQysWYLQtrg8r5/jop3+mqfjx1sn8s9XKaZ85nZZ4CF/HM6a3xswSexAff9TSHJCkGF1WfRp+ppikW2LlxDnX4dFu9etlhiRxNWn1izQfU2cL9qzmKmIOIrss/l7J5WH9uhUxfOarQJ9rfh+QnbE8bWFpVsfR0/k2aZopH2+4+GAAIIIIAAAlcWIFz9yj6cRQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCDFBK41XD3FbsxEVxWob4K2OpdqpAFLxlxTIH3ccPWr3uQmDni7djcnUGumCQi7nka4+vWopf01NgC7TZs2ThBmx44d035BrCBDC9hQ1e+++07Dhg3T2rVrdccddzih6k2aNEm3+1pyeJPunP1GvPXFhEUo+tg5uRcKTFdh8PEWyRsEUkBg7p2vyQZQ0i4XOBp6RmUmPX75CXoQSGcCFYKKaGmH+GGvabFEW0AlMDBQEyZMiL197969tWzZMq1fvz62L70eEK6eXj8Z1hVXgHD1uBqXH99IuLprtuze/ipvCuVFmODy7WcOyYaNJ9VKZcuvrKaw3sZTexUeHZnUsGT1+5qA9OQU+rOT5fQJUHFTHPDguZNKWMwvOTfL6ukTGyBux9uifQnX7+HmLlu80c5vCzG6uSnZBQ8L++dynpElFYqenDXm9wu6YgFBO8fVih5e6fOJ+1zuRoseJmc/yS2MaOe6kaKHhKsn59NgDAIIIIAAAjJFZGgIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIDALS6w9Mhm/bBrud6s/aBsyFlym69nFiecyt+8ppc2oHI7rT2xS9cbrJ5e9sE6rl2gdevW6tmzp/r27atjx45d+wRcgYARuHDhgkaPHq2yZcvqoYcecl7XrFkjG96fnoPVk/rw3Hy85FE4O8HqSQHRjwACCCCAQAKBkJAQBQQExPaGhYVpypQpevTRR2P7OEAAAQTSu4ANU7fFFlYd237FYHW7j+3Bh8xzlJ2XBZNfzx6TG6xu5z4RFuKs73qC1e31ZyPD7EtsSxisbk9ExUTHBrdHxkQlO1jdXrvv3HHdSLC6nePQ+VP25YrtQlSEnlr8uV6scV+iz+SS+/kcMCH1IRGhV7zXjZ60xjuDD191X7bo4fvrZmjlsW03ekuuRwABBBBAAIErCBCufgUcTiGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAwK0jsODgen24fqY83ZMXx9CpRAOKBcRkAABAAElEQVQ1K1jZCex9vdYDqpyjaLrAmrJjsaaaL9qtKfD+++/L19dX/fr1uzUB2PV1C9gg1WHDhqlYsWLq37+/mjdvri1btmjy5MmqVq3adc/LhQgggAACCCCQsQQShqvPmjVLZ8+eVdeuXTPWRq5xtfbfc89X66g3aj2oe4vXU06fAN1fsuE1zpLxhhcLyKNPGvZRAb8cN23xHm7uqp2ndKrer0mBysrrm925R4N85VXIP2eS97uneF3VyFUy0fOtClVXxxL1Y7/6m2Jmvh7eqpqz2BXnTHQyOhFAIEkBih4mScMJBBBAAAEEELiCgOcVznEKAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQuKUEjoaeSfZ+5+1brV/2r4kdfyEqIvY4LQ8OnT+Vlrfn3mkskC1bNo0ZM0atWrXS1KlTdf/996fxirh9ehc4deqUPv74Y40YMUKRkZHq27evBg4cqHz58qX3pbM+BBBAAAEEEEgFgRMnTihHjn+DtidNmqSmTZtm6r8bdCnVSMPrPaIhq6Zo0aF/dFfRWuZ9D3l7eGX6wlVVcxbXQ2Wa6Mfdy3Xw/MlU+I6KP2U2L1/1LN9KX2ycF/9ECr7LYj63yS2eUa3pTzuzjm82QHfNHpLoHarlLKEvGvfTc8u+1urjO+KNKR1YQFNaPusUVHOdmL5zqUKjwvXPyb3O98i0HUtlQ6FpCCBw4wK26OHGU3udoocR0VFXnTBh0cOvt8zX+pN7rnpdag+wRQ95NpfaysyPAAIIIIDARQHC1flOQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBK5DIDgi9Dqu4hIEUl+gZcuW6tOnj/r166cmTZooT548qX9T7pDhBI4fP64PPvhAn3zyiTw8PPTUU0+pf//+CgoKynB7YcEIIIAAAgggkHICR48ejf374/nz5zVnzhzn7wwpd4f0NZO3u6cTrG5Dsz/fdDHwe9mRLbIhvb+2GyJ/zyw6F3khfS06BVczY/cKlfi2t05eCEnBWROfKr9fkN6v31N9Fo7U2ciwxAelQG/9vOW079xx56tKjmK6EBWpTaf3Xzazn/lsX6zRUV7meyCx1q9SW7WbM0S7Q446p2MUo+NhF52iYqL1zLJxmtLiOZ1edc4EQu9LbAr6EEDgGgUoeniNYAxHAAEEEEDgFhdI/G/ytzgK20cAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCAjC7z77ruaN2+e+vbtq+nTp2fkrbD2FBY4dOiQ7PfH6NGj5efnpxdeeEFPPPGEAgICUvhOTIcAAggggAACGU3Ahqnbr9y5cztLt3+fDAsL0913353RtpLs9RYLyKMAL19l9/aPd83WMwf11Zbflc8Egu8IPhzvXGZ7czOC1a3Z27W76ec9K5XahcqaFayi+Qf+dj6mZgUrxx4n/Nz+r2YXvbv2R91RuEbCU8rjG6hKQUX0TvD3Onj+5GXnbUd0TIxGbpilDxs8ppY/v5roGDoRQCD1BFL7z5LUWzkzW4Ft5ufsBgpT8M2QzgWyevqoRaGq6XyVLA+BW1uAcPVb+/Nn9wgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACmVAga9asGjNmjFq0aKFp06bpvvvuy4S7ZEvXIrBv3z4NGzZMY8eOVVBQkIYMGaI+ffo4AevXMg9jEUAAAQQQQCDzChw7dszZnCtc/ccff1T9+vWVN2/eTLvpbWcOae/ZY7qraC09Vr6Vvtj0S+xeP/1ntiKio5z3rU0Ad/FseXUuIkzjt/4hG7bZpfTt8nL30OHzp/TDruWx15UJLKC8vtm1+PAmtSxUTaUD8+vH3ct14NxJuZn/6uYto9p5ymiJOb/q2PbY63w8vNS2SE3N2fuXcvtmM9dWd+aes+8vJ8g7t0+gOX+bos1/P+5aoZCI0HjXNsxfQVVzFldUTLSmbP9Th8y6XK12ntLydvfUltMH9YBZ95+HNmr18R3OehrmK6+zkWFac3ynM7xzyYbycHN3XRr7utGE4K49sSv2fZMClXRb7lI6feGcvt+1TKcunI09l/CgRq6SalW4up5c/HnCU877qjmLqV7ecvL1zKJ15h6ucHTX4AJ+OdTG7H3s5l9l19u8YFUn9HyC+SzCoiKcYV1L3a6sXj5qZz7LpUc2O59nxxINtPXMAefYBru7TOznvd189ptP73fdIt5rnwqtVdOYbewyUrtDjuqdNdM1cfuieGPsmwUH/9F/6zzs3HOmmZ+GAAIIIJA8gQFLxjg/B5M3mlEIpJ3A+s4fq7B/rrRbAHdGAIErClz+r5YrDuckAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCGQEgebNm6tXr1564okndPLkyYywZNaYCgI7duzQY489ppIlS2rmzJl67733tGvXLg0cOJBg9VTwZkoEEEAAAQQyssChQ4ec5efLl0/R0dGaM2eO2rVrl5G3dNW1xyhGH6//WZ4mJH14vUc0vtlA5fcLcq47EnpaJy+EOMdz963Ww2Wa6vnqHZ33Nox88rZFerH6fepbsY3TZwPXh9R6UP/r+J4eq3CHma+HE6R+pwny/rvTx07Q+heN+zkB6r3N+bl3vuaEk9uLG5jA8CX3DNOXTZ/So+VaaFCVe1QkILe+aPKExjXt79z7rToP6fYCFfVRg9763Mzjav4mkHz1fSMUFhmuD/6eIU83D82763XZsHYbBjq15XP65a43nAD5EQ16OXsYWKW9ymYvaOZ+SjPbvqJqOUu4ptN/KrXVmfDzJuR8t/4+uVv9zdj36veMDXO3gfIfNnhMObIEaJ5xaWRC3VeZPdv5kmr9q7TTyqPbnBD3hGPeqv2QBlRuL2v8+/51eqPWA5rZ5hUFZcnqDO1kAtKXdhimN8249+s/qs6lGqlijiLO5zWr7avOfu3APSYE/WjoGeX3z6FpO5fqoAmzr5ijsCaboPlNJhjehsDbls83yAlDjxuk75yI84sNvv9w/UwtO7xZBc18n97eVz/eMVjubm5xRl08XHFkq56u2uGyfjoQQAABBJIWiIy5WLwk6RGcQSB9CESZvxPTEEAg/QoQrp5+PxtWhgACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAwA0JvPvuu/Lw8NCAAQNuaB4uzngCW7du1cMPP6yyZcvqjz/+0KhRo7R9+3b95z//UZYsWTLehlgxAggggAACCKS6gC3A4unpqUKFCmnFihU6duyY7rrrrlS/b1rfwIZs91k40gkUb1+sthOOboPUE7Ytpw/E67IB6zuDj8T22fevrPzWmaeQf069/L9v9MZfU3T/L8MUbf57vtq9emLxaGdMzWmDFB0ToyYFKjnX2zDvsZt/c473nzuhAUvH6P9WTtToDXN1d7E6OnXhrHqbNfZc8LHeW/ejmhWsIjfzn21ti9RUPr/s2nL6oDPn3H1/qUjW3CofVFj7zh3X88u/dsbVzVtO3ea/r8pTn1T/JWPM+AN6Z+33zrm4v4zaMEez9q7ShlN7VTN3aSc0/a3VU7Uj+LAzrE+F1jp0/qS+37VM/5zcq8ErJiinTza9Xbtb3GniHVcKKmKuORWvz77pYoLSuxnr/ku+0G4Tjm7D3LvPH+EEtg+t87Az/rudS0yI+xonLP7zjb/oycWf6/5f39E7a753wukfKtPEGbf0yGZ5uLlrxdGtmn/gb12IjtCGk/v06/61Wmx8Q6PCnXFv1n7QfDbfOsdJ/WKvt/5tZr+upj+9pK3GqknBynqq8uXFBjad3q9KJuzdhs7TEEAAgRsViPjnkLo1bq+OHTtq8ODB+vrrr7V8+XKdOnX5n6E3ei+uRwABBBBAIKMLEK6e0T9B1o8AAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggEASAoGBgU6o9oQJEzR37twkRtGdmQRsqHq3bt1UoUIFrVy5Ul999ZW2bNminj17ysvLKzNtlb0ggAACCCCAQAoL2HD1woULO8V5Zs+erWLFijl/p0jh26TL6absWKxa0wfpx13LFeDlq48a9taIBr2ua60h4aHaFXJEYVERzvU2dN0Gi9twclefDfo+YELUi2bNE3uP4PDzzvEGE1juatvOHHQO15/c4+rSVtOXxcNL+f2CnL5pO5eq7vfP6ljYGae/Qb4KTn/JbPmc18OXQs1/MQHlNtD9RFiITl4Icc5duLRG582lXyZv/9M5KuifQ0NMEPmKI1s18p/ZsUP6VWqrKjmL6d16jzhfg6reLbvOoCxZY8fEPbCh48UC8upI6Om43c5x34ptnGuDI0Jjz1knG7Te2QSv28/CtvORFxQZE6XNJsjc1T74e4Yio6PUIF95V5cTOr/gwHrnfZMClbXg4MVj14B+FdvKelmr5DYbIN94xmDn87qvRP3LLrOfm6fZY4lL3pcNoAMBBBC4BgH3HH5q1Ka53N3d9fPPP+vxxx9XvXr1lCNHDuXJk0eNGjVSr169NHz4cP3000/Ov/cjIyOv4Q4MRQABBBBAIPMIeGaerbATBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEgo0L59e3Xu3Fl9+vTRP//8o4CAgIRDeJ8JBGyo+ptvvqmJEyeqdOnSGj9+vLp06eIEsmWC7bEFBBBAAAEEELgJArt371bx4sWdO82ZM0dt2rS5CXdNP7c4GnpGPf74UB12r9CoRn3Vo2xzTdq2SCuObr3hRYZHXR58GxEdKX+vLFec+4IZk7DZ62zz87x4bYxiZNc+uHonE94ertXHdzrn3d3cnddoc962qJho5zW5v4yo/5g83Tz0nz9HmRkuzhHo7WdC3XNo4JaxmrtvdbKmsqHrHiYkODQy/LLxZQMLJuq77PBmE8ieR6UDC5j97LjsOttxMaD+pHL5BOiNWg+aIHYftS1ym/46tkMf1M+l1kVqaOvpg+a4p95fN0PeHp66u3gdfbz+Z7UrWsuZ0/eSYVUTFm/7/nd0W6Ih8PZes/es0kNlml62lnMRYU5fAeOy5fSBy87TgQACCFyLgEeBQPXu1T+2cER0dLT27t3rhKjbwmmbN292jm0BvQMHLv6Z4+npqZIlS6ps2bKXfeXOnftabn9dY48dO6YXX3xRvXv3Vu3ata9rDi5CAAEEEEDgegQIV78eNa5BAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAgAwl8/PHHqlChgl544QWNHDkyA62cpV5NYNu2bRoyZAih6leD4jwCCCCAAAIIXFVgx44dTri6DUldvXq1Xn311atek9EH9C5/h8Zs/kXRMRfDw+1+fti1XI0LVHLC1e8ygdspEa7uCidP6BUT574Jz9n3VzrvmrNo1tz6ue2rembZl5q3b41KZsuX2FTX1NelVCO1LFxNL62YoB3Bh2OvdTlVCCqc7HB1G/x++sI5ZTXh5wnb6fBzqpG7pNzd3OJ9Bq572vNJNW93T+X1DdT8A+v00fqZJog9vzoUr6d+/8/efcDXdPZxAP9lL0ISOwkRxN571ig1ilg1q9qqKqWvjrdGq4pqtdUX1UW1VbVL7VotatUmdoxEzCCDLJne5//Eve5NIiIkkvg9/dzcM57znOd87z3HObefz+/Z/r0KXHdG33LN8ea2j1TgfDxu3L6FpsUrwcOpECY3GGBsUu1WF9/SDdDGsybe3P4Dgi+FG9ebTvjfvIwzN6+YLtLTBe2c9PulqJBU67iAAhSgwKMKWKrBKby8vPTrueeeM2suMjISMtCahK4bXps2bcK3336LqKjk66eLi0uqwHUJYS9btizs7JIH6TBrNBMzW7duxezZs/Wrc+fO+Oyzz1ChQoVMtMRNKEABClCAAg8nwHD1h/NibQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEK5DqBwoULY9q0aejXrx969eqFpk2b5rpjYIfNBSRUfeLEiZg3b54ORZszZw569+4NCV5joQAFKEABClCAApkROH78ODp06ICNGzfC2toaLVu2zEwzuWobz3yF0N+nBX459bdZv7dcOqLD1WNVMLehJN5Jgr2VjWE2x7yPrNkdNpZWOlhdOmVp8Wj3g0VUYPln9ftjd7A/vj32p/E46xQui33XzyAw4hperdharVurg8sNFV4o0wQ7r57AxTRCxk+GX0Rh1W7KIu1JgH0119I4FHLOuLq6mxeuq1D2wIhg47KUE/WKlIO9ta0OeZfwdAlI33blGCTMvXmJqthzzR8Xom4YN/tHrau0aKhxXiYcrGxx5aU5+HjfQvx8apPZupQz0s+1QftSLkZRx4I6BP985PVU67iAAhSgQFYK5MuXD7Vq1dIv0/3IwByXLl0yBq4bgtclBD0oKAhJSUn6twMJbZegdcNLAtFlunjx4qbNPXBa2re1tUVcXBzWrFmDlStX4qWXXsL48ePh6en5wO1ZgQIUoAAFKJBZgUd78snsXrkdBShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUokK0Cffr0Qfv27fHqq6/i9u3b2bpv7uzxCZw5c0aHlFWsWBG7d++GhKpLEGrfvn0ZrP74mNkSBShAAQpQ4KkTCAkJwdWrV1G5cmVs2LABDRs2hIS25vVy7lYwxtbuhfpFfMwOtat3I0QnxGLx2e3G5X9f8oObvTP6lnsGjtZ2+t3VPh+88hdBQVsnYz0nGzvYWlob52XCydoeLnbmno5qmZ1JWHs+G3u9TVrLTLeVtqTYq2BwKY5qf8UcXdDaowZc7fJjoAo+l1JcLStg66j7KvNu9vnlzawY9mW6bkrDV1S/bDFk23e4o/6TIuHtEp4uZfqRVXB3csOqdh+iSbGKKhjdC6NUwLuz2ldaweqyza6rJ1HJJXXA7ri9CyAB9r3KJrctdS3Uf/XU5zFu3wIkqYBgQ7G2sIJPgRKGWXTyqo/tV44bQ+VbulfF5stH9PpW7tWM08YNMjhRxrkYPlXh8nJchlKhoIf6DO3wxaE/DIuM7yXzFYZ8N0yD+I0rOUEBClDgCQhYWFjAw8MDrVq1wpAhQ/Rge+vWrUNAQACioqLg5+eHhQsX4uWXX4abmxt27NiBkSNHokWLFihRogScnZ1Rt25dPUDfhAkTsHjxYhw+fBjR0dFpHo2EqycmJup1CQkJesAJGQjO29sb77zzDuQeg4UCFKAABSiQFQLmT11ZsQe2SQEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABCuQIge+//14HZo4bNw6fffZZjugTO5ExAQlVnzhxIn777TeUKVMGv/zyC3r37g0rK6uMNcBaFKAABShAAQpQIB2BY8eO6bVVqlTBxo0bdRhrOtXzzKqAiGAERlzDR3V641pMOE7fvIwWJaqioApC77XxC/ireUNZHvAvBpRvhW+aDsbwqh0xYf8iHLoRoEO3O3nVw7JzuzC4cjsdot6waAV0Kd0AGy4c1HVLOLkiv60DXqvYBnP9N2NwpbbwyOcGCVTvVbYpzt68qsLam+tdDa3SHpMPLoVnvkJ4pUJyUPrImt0wdu98ONs44qXyLXW9d6v7YsKBxZhxZA1qFvLGb63e1vsbuXuODosfUa2TiikHyqtgcCnSn3O3ruLHExuRcCcRtQuXxbAqHfS6rt4N4RcSqELVrdFRHYs4vK76KEUC2KX9vddO6/mfTm6ChwpXF4PV7cciISkRXx9Zjdmq3fuVaSqQvZ9Pcx1EL96GcubWFXRe9wl+aDZUB6lvU2HpYvn5oWWYd3qroZp+l6D1gcovJjFO718C7uUzkmJlYamC3ith9O65er6FClr/4fh6Pf2wf+Qz6aMC9N9Qn+U/V47hwPWzCIuNxPN/TtBupu1J6HyHknXwypbppos5TQEKUCDHCtjb26Nq1ar6lbKTMsiKBKWbvn799Vcdyi7h6RLa7unpifLly5u9Dh48aAxXN7QZHx+vJ6dPnw75LUrC20eMGPFUDNxiMOA7BShAAQpkvYDFHVWyfjfcAwUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAJl57+OG7dvEYICOV4g/JUFOb6P7GDmBb777jsMHz4c+/fvR7Vq1TLfELfMFoGgoCBMmDBBh6l7e3vjww8/ZKj6feR3XD2BDmvH32ctF1Mgbwus6zAODYqWz9sHmcmjuxZzEz4LBmdya25GgewTqORSEju7TM6+HabY07fffosxY8Zg+/btkID13bt3o169eilq5fzZXcGn0G7NuAx31MHKFhKQfSs+BvYqRNyngLsO0r4QdeO+bbjZ50fI7Qi9XoLHYxOTA2Tvu0E2rLBQMeoO1raITog17k2OK14Fn2dVES+v/EVxXoWlS+D5g4oE01d28cR7//6SZtWyzsVV2LwDjocFIS4pwazO/xq9qsPZC//yItxVUP2tuBhEqM8sq4qtpbUKvy+EGOV5JTrsvrvx9aqPHmWaoO9fU+5bJ60V8nl1K9MIPz7zZlqrn/pl5yOvo/ri4U+9AwGeXoE1auCKxsUq5hiAuLg4nD171ix03RDAHhISAltbW0id9IoMDOfs7AwZ7G/w4MHouPET7A72T28TrqNAjhA41GOaHhwmR3SGnaAABVIJWKdawgUUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKBAnhWQEKvffvsNr732Gnbt2gVLS8s8e6y5+cCuXLmCSZMmYebMmXB3d8esWbPw4osvQgLJWChAAQpQgAIUoMDjFti3bx9q1qyJjRs3wsXFBXXq1Hncu8iR7UkoeMzd/PHbKiTdLzTwgf00BKtLxZwQrC79uKP+Mw1Wl2VZGawu7YvXyfCLMpmhMufU3/ix+Zuo5uqVpvOZW1cy1M6lqNAM1XuUShLufu7W1XSbKFeghA5Wf3XL9HTrcSUFKECB3C4g4ekVK1bUr5THcvz4cVSuXDnl4lTziYmJCAsLw4gRIzB58mS49qyLO5XsYWFpkaouF1CAAhSgAAUyKsBw9YxKsR4FKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUOARBV6v1BbHwoIesRVuToGsFSjh5Jq1O2DrT1zAwsJCB3ZLeOY333yDYcOGPfE+sQP3BK5fv66Dxr799lu4ublh2rRpePXVV2FjY3OvEqceWcDbuRhqFvI2a2fDhYOo7FoS7k5uZst3XT2Jy9HJIZ6t3KujoJ2TXh+TEIu1QfvN6qac8cpfBO9W74JJB5YY20hZJ615Cets61kTh0MC8c+VY2lVeazLfNT+nlP7OxIahC2XjxjbtrG0Qq+yTVHJpSQuRYVgV/AphMdGwtUuP/ZeP22s9yQnarh545QKdpVw3KwoxRxcUMHFXbkc1cct35u/Lh027qqArSNe9GkBD/W9Wa++Q1uvHEXSnTvG9SknXOzyYUD5Vvif3wrjqudL1cXq83uN85zIGQJVXUuhfck6cLKxx6Eb5/RnK9eAxWe354wOZlEvMnvdetTuPOlz2bT/vqUbICjiOg7cOGu6GE7WduhSuiFK5iusr4GbLx1Bwp27qdyqZm4/l3fu3Ilu3bph06ZNaNmyJQfgMfv0OfM4BCQA/o1/vsPnDQdAgtYPqmtrRouDOv+sLaz0eRil7sGedPF0KoS3q3XG0G3f65D5J90f7j9Z4E5cAhID1X273Iol3b0fU/dl+tZM/uiXWpfGu0UBB1iXLURKClDgIQVu3LjxUFskJSXh8uXLuPy/FbDvVg127Ss91PasTAEKUIACFDAVYLi6qQanKUABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACWSjwXo0uWdg6m6YABSiQcYHKlSvjv//9L8aMGYMuXbrAw8Mj4xuzZpYIhIWFYcqUKTpM3cnJCZ9++ikGDx4MOzu7LNnf097o1egwVFGB4SOqd0aiCndr+Md7iIiP0SGfjYtVxIe1eyIhKRFNlo80C0XfrcLF57d+F+6OrvBdN+mBjNXdSqOfT3MsD/zXrJ30Nizu6ILBakCWVyu2xrDtPwBX0qv96OskSPnlCs/ijcrtdECooUUHK1tseH48rsWEY/qRVTp0/qM6vdCseGWM2T03R4Srt/WshXj1OWVVsLpYDKjQEp4qSFnC1bt5N0ST4pWM4eoFbZ2wudMn2H3NH/K5Dar0nP4OtVr1oYEx1fvXTQahXpFyZuHqYjyt8Wt4e+dsJN5JSrUNF9wTWLhwIY4cOYJevXqhatWq91Y85ikZVOCLhi9jwr5FaoCDozo0+wsVBmxrZZPnw9Uzc916VP4nfS6b9l9C3mc9MxT/3TXHLFy9rHNxLG7zX7z/7xz8EbALbUvWxsEeU/H61m+wM/ikbiI3n8uhoaHw9/dHvXr1MH36dHz55ZemLJymwGMTiEtKwH92/KgHJclooz28G6Ole1XIIFEf1+2jg9mPhJ7P6OZZUk+O441t32VJ22wUcLZxgKO1LaITHm7wnLidgbg9d1+mCC1cHeH8RadMbcuNKPA4BazVAFeF7J0fZ5NZ2tapU6dgZWWFxMR7A84YdmhpaQlra2vExSWfywUKFIAM9FenTh2sv3MW570sDFX5TgEKUIACFMiUAMPVM8XGjShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUokLsFPvjgAyxevBhvvvkmli9fnrsPJhf3PiIiAlOnTtXB6hI6Jp/LsGHD4OjomIuPKud3PTohFh/vX4hWHtVRzc1Lh3VKr2MT4zHl8HL0LNMEPgXdIcGZpiUy4TYuRN7QYeMXom6YrkpzekXgbnjPG4TQ2Ig016e18IoKfv/66Godrp7W+pTLJAR64ZltKRdneD4w4hp+PrlJh6tLoLyhDFZh65VdPdFz0efGYPj5Z/7B1MYDUUwFiT/pMrRye9xWn9fskxuztCst3avh+2Pr9D5aqGDX9UEHjfvrUrohWqwcg/C4KL1MBtIZU+sF1C/iowPXjRXvTrzk0xIVC6YezGLPtdPIb+OoA9bflEB9lvsKLFq0SP+bNWnSJPj4+GDAgAE6aL106dL33eZhV9haWutg9aXndmLmifV6811qYIU5p/7Gxo4T4GRthyh1DcmrJTPXrUexyAnnsqH/juqzHVWrG2zUdyBl+bR+f2y/cgIbLx7Sq+T70UpdHz5Qg3G0X/uxXpabz+Vdu3bhzp07+t/DyMhItG7dOiUB5ynwWAUuRoVkuL31Fw5gw8V7//7K/dqTLsFqYBSWrBNwscuHy/3nPPQOAloEwHuu90NtJ6HQ5cuXx5o1a+Dl5fVQ27IyBSgASLh6khqwTH7PkJKQkPwM7enpibp166JWrVqoUaOGfrm7uxvJ/NZ8hKBgf+M8JyhAAQpQgAKZEbDMzEbchgIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFMjdAvb29vj++++xYsUK/PHHH6kORgI2WbJOIDo6Gl988QUkEHjKlCl4++23ce7cObz//vsMVs869lQtzz6RHMzdt1xzs3W/+m/W8y+mWG5lYYkabqXx9yU/s/rpzTxMsLqhnUQVTpeR0rRYJYyt3SsjVdOtk3T3fDe8S+VqrqVgqY43v62D2bbj9i6Aq31+s2XZPSMB5a9VbJPlweoFbB1Rs5A3tlw+CvnsmxavjL8uHdaHa2Nppb4Hh43B6rJw4enkkPuI+JhUJGWci+kg/3UqIDatIu2WLVBchTVXT2s1l6Uh4O/vj7Fjx8Lb21sHN86YMQPBwcFp1Hy4RV75i6iwewcUtHUy29D/5mX8cuqvHDG4gFnHsmAmM9etzHQjJ5zLpv3+qE4vfHko7QFnijoWREUX88ER4hITYGdlHsSeW89lCVeXcOHdu3ejbNmy+rwyteE0BZ6kwC317+rNuGjjSwZXYaFAWgLybCVhzhYWFmmtTrVM6vn6+mLv3r0MVk+lwwUUyJhA8eLFUa1aNbz88suYNm0aduzYARlELigoCEuXLsWYMWPQoUMHmAarZ6xl1qIABShAAQo8WMD8ifzB9VmDAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUyCMCLVu2xEsvvYRhw4ahVatWcHZ2hoSq//zzz3jvvfcwffp09O3bN48cbc44jLi4OMyaNQsTJ07UgWPDhw/X1i4uLjmjg09ZL5ae24lPG/RHr7JN8PG+BUi8kxxq7h9+WUv0KtcUEw4sgiF0/FmP6th25ZhxXioVc3CBLC/h5IrdwaewVa03FAtYoEmxiohMuI2DN84ZFuv3ekXKoVnxKir8Edh//axeHxYbaVZHZiTguV3J2rr95QH/4uytq7qOBKvPb/2uPmcHlG+Fq9FhMAR3p9cnww4aFa2AJsUrIU4FlB4KCdSL7+DeoAp/X/ZDF++G+L7ZG+i76Stcjg7VdcLjovDN0TV6uq1nLZR2Loqo+NuQQPp81vboVa4ZJHhc+vOH6q+h+BQogaIOBbH96gm09qiBcipIfHngv7gUFaqULNCgqA/qFfHBDrV+3/Uzhs3SfP+4bh8sObcjzXXSh9aeNVC+oLtqO0QH4cs+DKWEo6v2nH1yo/5sJMxcjm2u6r8hrFX6+kyJKqqPJXAzNhrdlENxRxdtLZ/FFXVsq8/vxfnI64Zm9Xtl15JYF3QAx8MumC23trDCh7V74s1tP2BUre5m60xnvjv2J8bV6a37bPpZmNbhtLlAQkKCXrB//34cPHgQck1t0aIFXnzxRXTt2lX/u2a+xYPnTt+8giD12T5fqq4O8Z91YoNxo2+PrkV8UqKez47vv72VDdqXrIM/g/ajsIOzOndq6nPrzwv79XWosH0Btb42ktR/ywN2wzTYX7aVc7y6GhBCrm2LzmzT313Dwcg1yNbSGqfU9a6POm+3XTmOAzfO6vMx5XWrZ5kmeoABw7aGd/muHwoJMMyiuTpvahcui/DYKCwL2IW0rmnGymoivXO5upsXGqrrlIO1HQ6rfaQc1OJxnsvSJ/m8z6jP/mT4RdMuGqdXnd+DMbVewAvKYvHZ7XBS/ZJt3t89x1jHMJEbz+WdO3eiYcOGWLduHdq0aWM4FL5TgAIUyHUCAwYMwIEDB5CYmPzvdVoHYAhf/+STTzBq1Ki0qnAZBSiQQYF33nkH8mKhAAUoQAEKPAkByyexU+6TAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUyBkCU6ZMQWxsLEaPHg0/Pz/Ur18fAwcORFhYGDZu3JgzOpkHepGUlIS5c+eiQoUKOnisV69eCAgIwKRJk8Bg9Sf3AUvo+crAPSiiQr8lKNlQeqqw9WOhQSpQ21UHgRuW9y7bDItUqK6hSMD5yFrd4KfCyf3DL2Hes+/iy4Yv69US7v1zi+FY1f5D1HDzNmyi3wdVfA5vV/PF9COrsPPqSSx89j0c7P4/LG0zUgUhexnrSkjxLy3eQkUXT8g2f3b4CC52+fR6CTmXPsYlJqhA4Ms6SFxWpNcnQ8MS9N2zbFPMUCHpS8/twvs1uupV96LVgd/P7sSFyBuoWagM/un8KSRc2VAM4eES5t7fpwXer9lNrxLPhaf/waia3fFG5XZ6mYSdT6jbF3u6TcFrlZ7DFw0H6CD1DiqU2K/H19p31jNDdYD0ILV+XYdxOpzZsK+U7xULeqCNZ01sung45SpUUeHm65//GAkq/FoCsQuoYPrdXaeo8Pymum4P78bY2WUyJtbrh68avaINJBD9C/WZrWk/FhKCLiVKHccJFRpdKn9hHVgvYdtlVBj8X2qfslwCmFMW39INdDD62ztnp1ylfLri22Nrdch+qpUmC/5V4fxV3UqZfRdNVnMyHQEZGERCVOV9y5YtePXVV1GoUCH4+vpi9R8rcCf+/gGrKZuVYPuvj6yGtRokQL4bv7YcocP1pV5wTDhCYyP0Jln9/W+sBmbY4TsZP6nryCsVntXXjJLqOzmr+Zvq2vKWPvc+qd8PzUpUxvTGgzBTnUeGIsHfB7pPxe2EOPzPb4X+bsu5IYHrnk6FsLj1f7Hh+fE6HHxq44H6HB5RrZMelCCt69aQKu1xMy5ahZwHwi80EG+pulMavWoMc5cBFaY1fg2udvmxXl0XmqpQ933qnJfr4P1KeufyJ+oc/U/VTvr8k/NuvBpQYVW7D43Xv8d9LsuAFB3VNck0SD9lv385+TdOq2utOEv/5rZ6G//Z+aO6hu5MWRW57VyOjo6GhKvXrFlTD1LQrl3y9TvVgXEBBShAgRwuEBwcjPDw8HR7aW1tDUdHR6xevZrB6ulKcSUFKEABClCAAhTI+QIMV8/5nxF7SAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKECBLBNwc3PDJ598gg0bNqBGjRo6VFPCaeW1ffu9EOks68BT0PCKFStQrVo1vPzyy2jRogVOnz6N//3vfyhcuPBTcPQ5/xAXnvlHd7KfT3P9XsDWEWVVkPYHe35LXl7OfPnBG+f0cgkv/rrJIIzePVeHDS8P3I1lKqh8YMU2qKNC0U+psPXPDy3TdU3/5Ldx0EHBK1X9uKQE7Lh6An9dkqBwC3Tb8JkOLzbUl7Bv3/WTMHbvPAzfMVOHwNcrUk6vPhJ6Hjdu38LtxHhsV23I/IP6JBs+61FdhxbL8UUnxOJC1A3M8f/bsEvje0xiHFqsHKNCzA+hkIMzflCBwn88NwolVOC8aZHjNC0SsH7uVrBxkcx/qPovwcweTm7adfz+RXhhw2Qkqf8k2P3N7T/oOnV+fxtJ6trTvEQV4/YpJyQMXcrV6DCzVRLu/FPz4Vh9fi9WqVfI7QgdHv9n0H4dPC0hz0vO7VDBzwd1wPTM4xswbPtMvLDxc3x+cJkOdDd8By5FhWpTb+dimH96K7ZcPqL7/rsKURbrk+EXjft2VN8DCaf+tulgVHDxUOHtn6tA+nth+hKQnaAGV9hz7bRxm/tNSHB3eGwkahQqfb8qaS6PP3wJDYtVgIWFRZ5/nTlzJk0D04UymIW84uPjIdffV/u+hIgP15pWeeC0hGy/vvUb/b3t5FVPDw4gAwmkLFn5/Zdrw+yTm/QuL0aF6CDvj/bOxw/H1qGzV32Eqe/KINXHV7d8jSmHl6OlezV1FbHQ9duXrINijgXVdeiyPqfWXdiPkvkK64Ea5Jx//985ul6DohXw4t9foeriYXhrx4/3vW59d+xPrAnah2NhQer6Vk6Hpn9yYDHO3rqq23m9UltciQ7FsoBdOKoGfZDropu9MybVe1GvT+vP/c5lGQzhRWX91o5ZCIy4pq+vL/09VQe2f1a/v27qcZ/LE+v1VdemeWl107js+u2baLtmHALU9W1olQ7IZ2OP3cH+xvWmE5k9l03byM5pGZBABtqRez9bW1t9r5Kd++e+KEABCjyKQEREBH799Vc899xzcHd3x+TJk1G3bl1YWSUPmmPatgSrlypVCgcOHED79u1NV3GaAhTIBQLyzPeiem7/VN0Tvqnux2QwsjLqma2uuj/N62Vo5fZ4tULrbD1MGSTOwco2y/YpAxwZnv1lkKZW7tXN9iW/zcjnLM8ALUpUhaV63k2rNFLPNCOqdcZg9UxS2SX59wJDvefVAEosFKBA3hawztuHx6OjAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAF0hNYtGgRPvjgA4SFhelQzYSEBGP1c+fO4datW3B2djYu40TGBTZv3oxRo0Zhz5496NatG5YuXYry5ctnvAHWzBaBrZeP4bIK027tUQNFHQpCQomXB+xWgdpHERR5HW1L1kIhFRIsoVwrA/cY+9TduzHsrW11ULphYVHHAjp4V0K5910/g1gVfJ6yFHd00duVUEHjhrL7mj/alayNfNb2kDByQzmqAtMN5UTYBT1ZOn9RwyL9roZCMM5npE9vV/PFoZBziIiPMW63//rZ5LZUsK5pkfD27ioEvWvphvi8wQC0UOHN23w/he+6STrM3bTug6Yj4mIQEBGsw+ClrhznFRWQLuHMEhAvRQLdL6kQ6VL5iuj5tP5ISLoUCS82Lc+614CPWrf3+mnTxTq4vkeZxjqs2RAon3An0Swg/X9+K/B29c6QIPRfTv2lt5cgeAmSl/Yk1K2qqxe2XTlm1rbMSED9f1Qo9YgdszG4cltMrNsPXzV6RQXTf6C3e02F7Uv4dUbLTeVkOMaMbmNVyhUTZ05Vx18io5vk2nozZ87E0aNHH9h/S0tL/W+ajY0N2rRvi81lbj1wm5QVFp3djs0qWF+++76lG2C6GkyhVuEy+vNOWfdB85n9/t9SgxJIOaYCyw3l9M3LelIGVDAUf7XMzsoGcn25rELOZSCAwyEBkEBwWd64WCVdVcInZYAIw+AEG9RgAzKggQxGYChpXbcWntmmV7s7uWKCCiKXUPFvjt4LrB9apb1u98uGLxuagfTTxS6fcT7lhOF7nvJcfqNyO73tLZNrlFwnJGi9pwpef3fXz/r6Jefe4ziXJahSvMTqQUVC37dfPa5fMv1Xpwlov+ZjSPh9ypKZczllG9k1v27dOlSvXl0PqtO8eXM4OTll1665HwpQgAKZEpABVOTaNW/ePKxcuRKJiYlo164dFixYgI4dO+rBVXr37m3WttwbtG7dGgsXLuTzrZkMZyiQOwQk5HvD8+NxTT0HTj+yCu7qee2jOr3QrHhljFED+6R8DswdR5XxXspAYFHxt9XgSxszvtEj1GzrWQvxSYn6Gf0Rmkl30wEVWsJTDQAlv710826IJsUr3R14Diho64TNnT6B/FYizziDKj2nnzdarfrQrM0v1LOa/C7z312/wCNfIfzW6m38qAbKksGypMj3ZVrj1/D2ztlIvJNkti1nKECBvCHAcPW88TnyKChAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUo8FAC/v7+eP3117FlyxZYWFjoENqUDdxRgav79u1Dy5YtU67ifDoCYjZ69Ghs3LgRbdq0wd69e1G7du10tuCqJykg4eSLVYjyf6p1Qm8V3NtBhai/vHmaWnoH8/y3YlSt7sblg//51tjVCi4eKqA4XAf9GhdmYEJCkCXYuKUKKv/y8B96iyL2BbD32mmzYPWUTSUkJQeBWVlYmq0yDVfPSJ+quJbEisDd5m2kCFU3W6lmlgXs0oFnPzUfhubuVTGhbl/4rp+UstpDz8cl3hvMwbBxfFICnGzsDLOp3iXoXq5NhkB2Q4XyLu56UgLnTMuuqyf1bPkCyetN1xmmk0PdQ1WIfn4dsN7duxFKqpC3mIQ4Haxd1LGg2l8cPq7bB8dUyL2EtaUs8jl8d+xP1C/ig46l6sHW0hqT6vfXAXDtVXC+oZRxLq7Crm1Vnbq4qYKz/0kR2B6lQudLqADrhymWBR3QqkNbNCia9wdvmD9//n1p5N8yeUlp1aoV+vfvD19fX0RbJcJnweD7bpfeimsxNzFAXQ+6qHPmu6ZvYED5Vlhw+h8d8pfedhlZl5nvv7Qbq86RlEXOGymO1snnjnwfpe+ja/bQ390DKlBdiuXd60eSWi/lYQMGpzZ6DdYWVhiy7TvVQnIbMvhAcTUQwYhTs7HuwgHdbkb+3PdcVueqhCimLHIue+UvgnIFSuDAjeQBIVLWedhzefMlP3QuXR9fH1mtz0lpz+GuYXU3L71sj7o2SwB833LP6IEmWqwco90kYH5q44H4suEr6LXpi5RdQWbO5VSNZNOCFStWoG/fvpgxYwYmTXr0a3s2dZu7oQAFnjIBuf/bsWOHDlRfsmQJQkND0bRpU0ydOhXdu3eHq+u9+6dOnTrB3t4eMTH3BhOS57Px48cb7xWeMj4eLgVyvcBgNQBPZVdP9Fz0uR5MSA5o/pl/9P1YMRW+ndeLhIonZVM4uAw+JM/bWR3kLr+JfH9snf7oWqjfGdYHHTR+jF3UAG9y3x0eF6WXvVejC8bUekE/bxueFeSZun/5lii/4A0dAi+DO43ZMxe/txmpB5qS+3h55bdx1AHrb27/wdg+JyhAgbwjYP4rad45Lh4JBShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUokI5Az549dbC6VJGgurSKjY0Ndu82D2FOqx6XJQucPHlSB/vVrVsXkZGR2Lx5M9avX89g9VzwBVl4Zpvu5dAqHXSI2MWoED0/7/QWHWA2pEp7PR8Ycc14NBJKXK5AcR00bFyYwYmeGz/XAdrjVUh5VxUa5u1cDK9tnZHBrc2rmZ6+D+qTBLNL+HKdwmXNG7k7ZwhLLqWCxU0DwWV1aGwEhm7/Hokq5L1J8UqQQOVHLYb9pWznftckqSfh9BKg7XQ3ANmwbXhspJ6sp8LNTUtQ5A1I8LQhlM10nWFagtCLOhSAfL4HVQj1pAO/qwDrePxwYp2evhkbjbn+m/X0QhWsnV7ZcvmI2lck4tQ+Jaz99UptMbnBAONL7PLb2Ot5CfRPWQraOuFSZPL3L+U6zqctYG1trVfUr19fh0MHBwdjw4YN6NevH/Lly5f2RuksHVTxORVCnhzSbqj2R8C/WHg2+TrxvArxexwlM99/2W9654ehTTmHt/l+hv03zuArvxW4EHn9kbvcSw0+0dqzBibuX4Szt64a20u6exGq5OJpXJaRifueyypAsVbhMqk+A8M+H+e57O7kBg+nQsbzU87Vj+v21t33Ld1AL6+sBqSQ0rtsM2y6eMgYSP+b+vdhzqm/9UAZaV0Pc8u5vH//fgQFBaF48eKIiIhA586d9fHyDwUoQIGcInD06FE9cFXp0qV1mLoErL/77rsIDAzE1q1bMWjQILNgdem3g4MDunXrpg/Bzs4Ov//+OyZMmMBg9ZzyobIfFMiEQDXXUnqgoPy2DmZbj9u7AK7quSuvl+iEWP1bRVYfZ8WCHnitYpssD1aX++eahbz1IG7yO0XT4pXx16XD+vBsLK3wt5o2ve9feDr5WSwi/t6gGa9UeBZB6jnHtN7+68mDML1dzddIJe2WVb/dtHKvblzGCQpQIO8IJP8ilHeOh0dCAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKZEDgt99+Q7t27XDlyhUkJCSkuYUs37VrV5rruPCegISSjhs3Dr/++isqVqyIFStWoFOn1KHJ97bgVE4TOBl+EcdCgyAhuh/vW2jsnoSsb7l8VIfnTvVbZVwuE0dDz8NJhWRLoNfME+uN6yQkrLt343TDyKIT4vDzyU1Yc34fbsVHY1lA5s4zCVmWIDJDyUifToVf0sdZ2L4Art++adjU7D3kdgQm1e+vgoQP65Bww8pLUaE4rcLNJZgsNjFeL5ZAd3srG0OVLH8/EXZB76OwCkOPMgm733f9jF7eqFgFTDty77OSwGcbFZ6+55r/fftWr0g52FvbYt2FA5DQOnnVK+yDT/Yv0UYNipbHsO0/3NfLtOEKKozuz6ADelHPjV+YrtLTH9fto0Kam6LSoqGp1lnAAkXUcQVEBKdaxwXmAjL4R3x8PCpVqoQBAwagV69e8PR8uHBv8xbvzXnmK4T+Pi3wiwrONi1bLh3BgPKtjN99WZfd33/T/qQ3PbJmd/W9t8L6Cwd1NUuT60R6291vnXwvP1PXhN3B/vj22J/GajJQg5x7MjDBqxVbq3VrzUIfXyjTBDuvnoBhwArjhmoivXNZAuyruZbGoZBzxk2qu3nhesxNta/7nx8Pey7/c+VYqnPRwcoWV16ao/8t+PnUJuP+q6h/H+T6aVrWBO3Txy3X05tx0cZVuelcXrp0KUqVKoXDhw9DBod5XOeREYMTFKAABTIhcOHCBSxYsADz5s2Dn58fSpYsid69e6Nv376oWrVqhlocMmQITp06hdmzZ2d4mww1zEoUoMATEfj7sh+6eDfE983eQN9NX+FydKjuhwRrf3N0jZ4u6lAQHb3q6fvgzereXZ7zmxarhCpupfT6VYF7jPel8gzbvmQd9ey2H4UdnNHaoyauRofhzwv71QBrdyD3dzLgWJL6b3nAbpiGevsUKKEG5yqI7eo+t7VHDT3o2vLAfyHPy3If2KCoD2TQrx1qveE51YBWRg2sx34q2gAAQABJREFUVlc9f1Z2KYnd6hl19fm9hlWQwXm6eTfSvyU861EdVVSdr9WxyTNHIXtntPWsBRngR4o859ZwK62nTf/I0ImL1KBQhgGQijm4QNoq4eSq7uVPYau6/02vyPPqknM70qySz9peD7ZUvqC7OtYQFYDup4/ZUFl+m3imRBX9PH325lV0KFUbXvmLYpU6xv13n9fFTuqUU+8yiFk39ZkWd3TRA0i1U95X1GcgJudTDA4lv9WsU8/Zx+/+HiD7LFfAXT17xBl2r9/D1KBr8mwiz/Cm5Tv1DDOuTm/dZ8OAVKbrOU0BCuRegXu/iubeY2DPKUABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAgYcUqFy5Mg4dOoRGjRrB0jLtGBIJbt65c+dDtvz0VA8JCcE777wDHx8fbN26Fb/88osOJ2Wweu78Diw9txMxKvR8ReBuswOYf3qrDvpamWL5snO7cDEyBBPr9cPwKs9DQsJ8SzfAtMavYdGZbboNu7uh4272+Y1tSuDxH8+NRpQK8M5n46AD1Eo4uhrXy4SrfT4972p3bzsXu+RlhnepEBwTjqKOBVRgWRH9krCxB/Vpqt9K3fYXDQfAVoWOS/hbVxVoJqVh0QqQ9iMTbsNRhY1PbTxQ19Er1R8JcKvg4oGF6vhu3w1Xl0A1NxX01rfcM2obO/0u/Zc+STicoTjZ2Jm1JcudVDib6fHIMke1zOAm8ynLoRsBOqxN+mJajqpwfPmsGhWrCA8nN+MqCVU7e/OKCsr+y7jM2sJKf16GBZ286mP7lePGIOpqrl46RO9YWJA+DgmWluA70yJhfO9U90VFFaZuKHIs1VTA3ejdvxoWPdR7CScXWKvvx1oV8MeStoCHh4cOgh45ciROnDiBY8eO4b333nusgdDnbgVjbO1eqK8CEU1LVxV0KMH7i89uNy7O6u9/PjWAgxTTc8KwzPTckXNJir0KBpfiqM63YiqkUMIe5ToyUAWfS5HgQhkAQs5VKabXJr1A/THsy3TdlIavqOW2GLLtOxjCCOVaJuHpUqarAQ3c1Xm3qt2HaKLOQTmHRqmAd2e1r7SC1WWb+53L4/Yu0AH2vcomty115TolAZXj9i0wBkXK8sdxLks7GSmr1WAYEvoufTGUuoXL6YE2zt66alik33PLuSz3efPnz0ePHj2wbNky/W52IJyhAAUokI0CoaGhmDlzJp555hn9b/3kyZPRsGFD/ZwVGBiIzz777KFC0mXbPXv2PNQ22Xi43BUFKPCQAr+f3YkLkTdQs1AZ/NP5U/S8ex8qzRgCt+X5+IYajOdTNShQ3SJl9R62XT2O/Oq5W5ZJoLeUxup+dYfvZPzUYrgeLO3tar4omb8wZjV/Ez+3eEsPtPRJ/X5oVqIypjcehJnPJA+MJeHiE+r2xZ5uU/Bapecgz9QSpN5B3SP69fha33vPUnUltH2QWr+uwzjUVoMRGcobldupZ+zX9PP0LDVA2yf1XtT7l/W9yzbD8V7fYHKDl/BaxTb4qHZvjFNB55UKeqKPWnewx1SMrdPT0JTeR2nnojilBj87HBKI/Oq++1sVPC+DjRmC1SVYfmStbvBT6/3VIEHznn0XXzZ82dhGygl5tm3jWVMPspZynQw0tP75j5GQlIhZJzaoZwon7O46Bb3UwGFS5DcNsVv23CgMr/o8ZjQdhCqupfT69cqhU6l6ul6U+q1BBlkqpbxlcLPT6lm9jBq87S81sJssP6PmUxb5nUWC0d/eOdtsVUxiLCSs3ll9vqYlUD3PFbRzgnxehvKvCpavqkL2JaCehQIUyFsC1nnrcHg0FKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgQEYFXF1d8ddff+Gtt97Ct99+m+ZmEiB+8eJFSKAtS7JAdHQ0pk2bBgn8s7Ozw5dffonXX38dNjY2JMrFAssCdunwr4j4GLOjkKDrLZeP4Ep0mNnyuKQEdF0/CfNVQNn4en31S0LdBm/9VoeTS4jasCod9DYSXi6BZhsuHtRBZ+cjr6UKNbsZF40xu+fir0uHMaJaZ72db2kV+q3C4M7euoJ3VZC3lM5qmYSQ7bt+BssD/sWA8q2wpdMkTDqwBDNVQFt6fZLtl5zboUKXC2JUrR4I6jcbJ8IvYKkKig+9HaEjgz2dCiEsNlIF1F2Ekwp2XtnuAxXWFqADlyUQ7UcVpPbhnnnSlC6GPnzTdLAKUeuICfsX6dBkJxXe3MmrHiSEfrAKkZMgaAlv76KC0TZcOKjrlnByVSFwDjo8bq7/Zgyu1BYe+dxU6Ly9DmGTEPeUJTwuCl8dXoGOqi8pQ8hHqLC1qPjbWNLmfRX2vFoFlVuqcLga6LRuIuJVCJyhSNjcQBVYF5MYp4PYJWi618YvDKvRwr0qNl86oudbuVfHzqsnzbaXFZYWlvr4Pqj1Ag7eOKcD6EJiI9Bjw2c6ON/Y2ENMdCndEBL6Jp8tS9oCX3/9NeSVlSUgIhiBEdfwkQrwu6YCGk+rwMIWJaqqgL58+nvir+YNJSu//2dvXlWDFTTXuxpapT0mH1wKz3yFVABjclD6yJrdMHbvfBUm6IiXyrfU9eQ6MeHAYsw4skYFT3rjt1Zv6/Nt5O45Oix+RLVO+jwvf3dQADkfz6lg8B9PbETCnUQd/pjyumVnZY2O6lwWh9fVOSpFAtil/b3XTuv5n05u0ueSXANWtx+rQxe/VufgbNXu/cr9zuUz6nrXed0n+KHZUH293KYGPpBryeeHlmGeGkDBtDyOc9m0vfSm39v1swq7HIAdXSbj11N/o6Ia4KGwgxpYYtMUY+C8Yfvcci5v374d58+f1/d4N2/eRO/evQ2HwHcKUIAC2SIQExODlStX6oEe1q1bBysrK3Ts2BHLly9Hu3bt+HyVLZ8Cd0KB3CEgz24tVo7B9ypA/Fk1gNAPKsRcgr2HbvsBl6NDjQdxUoWIpyzyLG5adlw9gdnq/nVS/Rf1QEDfHFurVyclJWFE9c74/ewODNr6jV4WoIK6h6mwcBlgRwYh+3DvPPRX994yoNegrTP0oGMS4h3Qbxber9EVz/85QS+T5/Pz6nm7eYkq2H/3+U5C0yVEXEqQCoo/Ehqow77lXnrBmX/0c6gMXiS/PTRdMVKHwcs9+JGw82hbsrYOctcbqz/n1fPK72qAOBn4yEENgjRP3fdfigrRvylIHXke/7rJIDRa/r4eIMpP7auleraV52B5zk7rmbOyClCXcjXFbx8yqNJPzYfjD/X7w6rze3WdGUfXoLoaWEzC5+V5+JRyH6ts5L49LjEBAzZP0/UmH1yGXV0+x6cN+mNN0D7Vx1D9+p8ayG2a3yrsDD6JD2u/oJ/xt6vPxbTIc7p8RmIi0ztVO13UbzCyPyn/XD6mjWSANfmNxFBkgCf5TUM+L0OR4P1wtaxGodL48wIHMzO48J0CeUHAQo0adicvHAiPgQIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFMi8wMyZMzFkyBBIHIkESpmW33//Hd26dTNd9FROJyYm4qeffsK4ceNw69YtvPPOO3j33XeRL1++p9Ijpx20BKR1WDv+kbrlapcfoSogO2WRYHAJ57pfkUByCTW7qMLMHlRsLa3xQe2eKsx4PWR/+W0cYG9ti6IOBfHfml1Ra8kIHXL8oHYM653V9hIwbBocJuse1CcrFQ4u+5QgOmsLK1hYwCxAXNZJAJkUdxWC7mbnrEPeoxJi9bKUf9zs8yNEBbRLkeDl2MT4lFUe27y0v8N3Mp5fOwFXY8xD72UnYlLBxQMXI0PMgvZk3f8avYp+Ps1R+JcX9XHdiotBykB9qZfRUkAFt0l4nIT9PWrZ3Gki/rtrDvZeTw6sfpj21nUYp8L2yj/MJk9N3WsxN+GzYHCGj1fCCSVA8JYaaMFefdd8Crjr8/9C1I37tpGd3//7diLFCgmAdFDXlmiTc1aOy3SggRSbPPKseHnlL6rDHjNyTjzoXC7rXFwNtuCgBnsIggxoYVoe97ls2nZ60/L9kJB7+V5JQHxaJbPnciWXkio0cnJaTWbJsoEDB2Lv3r0oV64cQkND8ffff2fJfrK70V1qkIp2a8Zl9265Pwo8lIBco7uVaYQfn3nzobbLC5XlmWrTpk2YN2+eDlGXgaueffZZ9OnTB126dEH+/PnzwmHyGChAgQcIPLfmI+wO9n9ArbRXd1WDUn2uBr0ppAa6Cbl9C77rJqmg8vO6cgU1iNC/Xb/AsO0/YK7/Fr2srWctLGz9HrqoepvVwGlS+qlBjGY0fR11fn8bMriPlD5lm+FbFd5eY8lberAlWSbB3jNVkHulhUONz5ZHX5iB3ddO4dUtX0sVXfxemI5dalCu1/+5N3Dhwe5TIQMFDd8xU9cp7uii781lYLXyBd3VYEJD9O8BtZe+rdd/XLcP3lKDFbn81Ef/vpDccvJf+feiWYnK6rnmDdPFenpSvRcxRA3G1G39Z3qwNln4kk9LNaBad6y+G4Yuy+QeVp5tPj34Oxaf3S6LzMoYNQDbeyogvtic/jog3rCynWdtLGj9LrqrgcQ23Q2Hl3USbv+9OgYJWv9gz28o4lAA/r2/x/h9C/GV3wrD5io8vSfeUYNA1fp9hB7YSYLpd3f9El7zBurQ9NO9f0DZ+YP085dxI5MJ+TdzcOW2mFi3H/xCA1TI/gd6bWH7Atjw/Me6DRnk7aa6N39GhdnLZ7tHPVN3+nOiSSvA4R7TVTD7WWPwu9nKdGYO9ZimnnGKpFODqyhAgScpYP0kd859U4ACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAjlDYNCgQahQoQI6d+6MyMhIJCQkh5ja2tpiz549T324+h9//IHRo0fj7NmzEKuxY8eiSBGGK+WMb+/j60VawerSenrB6rI+vdBlWW9aJJhtz7XTCIq8oV+m61zsnB4qWF22lQDotMqD+pR4J8kYDpdwJxEqvc2sGILVZeGlqFD9MquQYsYQrC6LszJY3dD+8O0zdVjcf3b8mCp4TkzE+EFFjutRiwTjPY4igXhfHV6RqWD1x7F/tnFPQELBY9QpIeW2GiTALzRQT6f3Jzu//+n1w3SdDPhgGqwu67IyWF3aF6+T4RdlMkNFrhXpncuGoMsHNfY4zuUH7cOwXr4f/jcvG2ZTveeWczkiIgILFy7EmDFj8PHHH+O7775LdSxcQAEKUOBxCvz777+YP38+Fi1ahGvXrqFBgwb45JNP0LNnTz5XPU5otkWBp0BgWcAubLl8FD81H4bm7lUxoW5f+K6f9MhHHptiMB9pMP7uMkdru3TblwG3UhbZ1snm3nZXosPQokRVtC1ZCzuunEDArWDUKORt3EwGO5Qi9/EZLXULl9PB43P9NxuD1WVbGWzsanQ43t31c0abQiF7Zz3gotzTm5byLu56Nir+tuliHSYvC8qrwPb0ypmbyeH1cuzDqnRAyXyFEZMQpwPyizoWVM8QcZBg+WNhF9QgdBtSNSUe3x37E/WL+KBjqXqQAetk4KXrt2/imRWj0FOFvFdxLam2D8Jv/lsxsGIbFWp/LFU7UQm3UUINHMdCAQrkLQGGq+etz5NHQwEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKECBTAs0a9YMBw8eRPv27XH69GkdsB4XF4ft27dnus3cvuG2bdvw/vvvQ8IAX3jhBaxevRplypTJ7YfF/j9BgdqFy6KoQ0EV/u2P0yqgNyEpUQeq1VNBYWfSCex9gl3OkbveGXwStlY2mFivLz7YMy/DAXQOKhTP2sIKTuo9KiE2Rxzbf6p2xKGQAKw6vzdH9IedoEB2CvBczk7te/uaN28ekpKSEB8fD3t7ex1ufG9t7pq6ceMGRo4cicTERFhZWeFGbARiLh4ALNRxWKo/+mUJC8O0hWGZBSzsrWHboiwsrK1y10Gzt7leIOOxubn7UI8fP64D1RcsWIBz586hYsWKGDZsGPr06QNv73uBwrn7KNl7ClAgqwVKqTDuyio8e23QfuOuZGC0odu/h1+Pr9GkeCUUsHXEow58ZQg3N+7EZOJBgef3W2/a5phaPdC4WCV0VUHwEmDeyaueyR4eflJCxr9p+joktH3M7rlmDchgauUKFNfPvnowNbO1ac/IAEIW6j4p5bNyeGyk3kB+s9gVfMq4sQwYJwHy4XFRxmVpTXjmK6QXb7p4CCsD92Bak4H44cQ6/HLyb4xXoeoSDD/Vb5UKXE//+XzL5SNoqj5rCVY3FBlYbZZJIPvUxgPVwHAh+OboWkMV43tBWyecCsv4QFDGDTlBAQrkaAGGq+foj4edowAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKBA9gp4eXlh7969OvBu1apVkCCoAwcOGAMrs7c3T25vx44dw6hRoyAGrVq10ia1a9d+ch16yvackJAAa+u8GY/zwobJGFqlA35qMRyeToVwOToUGy8cwg/H1+FEOIO+HuarLuFqx8OCYG1pqULdEh+4aQ/vxmjpXlUHxn2sQtzmnPobR0LPP3C7rK6w6Ox2HYiX1fth+xTIqQI8l7P/k/nmm2/0oDG//vor+vfvD0dHx+zvxGPaY2RkJGbPnq2v7Zbq3wM1gSQVKCpFAkJTFbXoTqKKQFXh8jZO9mjboyscXPKnqsYFFMhqgY6l6mb1Lp5I+xcuXICEqc+fPx+HDx+Gh4cHevXqhb59+6JGjRpPpE/cKQUokLsFQm5HYFL9/th08bBZsPalqFA9YFlZFSIeq8LKpcjgZVLsrGz1e075IwHx79Xoiv/smKWD1aVflhbqvuURysia3eBT0B3d1n8GCRmXIgHibvb5cVQ95zrZ2OOVCs9i5on1xr1ICH139Vw8++RG4zLDxImwC3qysEMBREVcMyzGvutn9HSjYhUw7cgq4/JKLp6wUQHvMnBceqVZ8co4dOMczkde19XqFfbBJ/uX4Prtm2hQtDyGbf9BT6fXhqyrUNADfwapAXTuU55X/64OKN8KAzZPQ3SKoHY1pA6KqOMKiAi+z9ZcTAEK5FaBvPnrcW79NNhvClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQrkAAEnJycsX74c48aNw/jx43H79m0cP34cVatWzQG9y9ouXLx4EWPHjsWcOXNQrVo1rF+/Hm3atMnanbJ1M4Fly5ahR48e8Pb2RoMGDSCh9rVq1dJhjM7OzmZ1c+OMBKi/qcLDpNhYWmUoFDw3Hmd29flazM0M72r9hQPYcPGgsb4hgM+44AlNXIkOe0J75m4pkHMEeC5n32exceNGHD16FEOGDNH3O4MHD86+nWfBnmRgILln8/Pz04MBme7ijumMybQM4FK+YkWsXLlS32+YrOIkBSiQCYGQkBAsWbJEB6pv374dLi4u6N69O6ZNm4ZmzZqlPdBBJvbDTShAgadTIDLhNhytbTG18UAVTv6jMWBdwr0ruHjgN/8txsDyM7eu4HzEdXQr3RDrVRC3vdrOt3QDDVfdzQtbLh+FGmIF+VTwuBQ7Kxv9Ln8My1zs8iHwbri4k3VyPXuTsHYnGzvYqlBx0yL1ZDvT4qiWGdqXoHMpXb0bYem5XajiWhISVi7rnaztVPS3hTpGO11H2gmLjdTThj+2qp6zjSOsVCB7ohpERo5leNWOmOu/GX9dOmyoptpviL3XTmOZ2scHtXpiYr1+sFfbrlPPwpXUPn296uPNbcm/Rxg3ujtx6EaADiUXV8Pxy6qjoUGYf3orOnrVg4eTGy5GhegtJBj97M0r+OXUX3dbSH6rrPZjKMUdXVCrcBn03viFXlTN1QtJ6r9japA0r/xFdOD57hTh7NJfGZBu7fl9xgHoxKSaW2n02vi5oWmzd+nLuDq9dbD68oB/zdbJTAknFzUomxXWBu1PtY4LKECB3C1gfjXO3cfC3lOAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQIFMCP53chLd3zs7Utnl6o1KA4+BGiJ69Gw1+ehM2VUvk6cOVg0s4fhXRq/bC/tV6OFe/FF64+DPwk3rlwFIyX2H4vTA9B/bs0brk6OiIpKQknDlzBgEBAVi4cCESEhJ0oyVLlkT9+vVRp04dHbhes2ZNuLm5PdoOn+DW8UmJT3DvT9+ub8XHPH0HzSOmQB4U4Ln8aB/qV199hRYtWmDVqlV45plnUKlSpUdrMAds3a9fP4wePdp4v/CgLvXp0wc//PAD7O2Tg04fVJ/rKUCB1AKRkZFYsWKFDlSXQRtsbGzQqVMnvaxt27Z6PvVWXEIBClAgcwLHwy5CAspXtvsAh0MCdCh5p1L18OOJDfhwzzyzRr88vAwT6vbDrq5fYJ0K05bf/JoWr6SCvAvC27koXO3yo2+55nqboVXaY/LBpfDMVwivVGitl42s2Q1j987XYeYvlW+pl71b3RdT/FagrWctHaLesGgFdFGh7RsuHNQh5yWcXJHf1gGvVWyjA88HV2oLj3xuOrC9V9mmWHhmm17eu2wzbO08CdOPrMZ/d/2CH5sPw/xn38UaFST+fKm6el9fNXwFXx9dgwM3zupg9P4+LdGkWEUdFP9h7Z6YodbNaDJYh4XbqJD3zxsMUNHsQGGHAmjjWRPVFg/XAfRd10/SbY+v1xfyOh52AYO3fgsJq0+rhMdF4avDK9BRuaYMIR+hfjONir+NJW3e1323trRU+6qBTusmphowrqhynt54EG7cvomW7tXw+tZvsPXKMb3LFu5VsfnSET3dyr06dl49mWp7SxUg30kFuX9Q6wUcvHEOmy4eRkhsBHps+AxRCbFmXa9VqAxertBKh923Xj02VSi9oXIXFbb/b/Ap7Lt+xrCI7xSgQB4RsLijSh45Fh4GBShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUyJfDJgSWYqkKSGHScNt+d2ARY2FmnvTIPLr2TmAQLK8scf2S2Vta49tLcHN/Ph+3g2bNnUbZs2ftuZqlCvKysrBAfH6/rFCtWDPXq1UPh8qWwyMkfVp4u992WKyiQVwXWdRiHBkXL59XDe6TjuhZzEz4LBj9SG9yYAtkhUMmlJHZ2mZyluzp48KAenGT69OkYPnw41q5di3bt2mXpPrOj8cDAQJQuXTrdXcm9g4WFBWbMmIHXX3893bpcSQEKpC0QFxeH9evX60D1lStX6vvxNm3aoHfv3vD19YWTk1PaG3IpBShAgRQCz635CLuD/VMsvf+sBHYHx4TrCu4qyNzNzhlnb11JFbZtaMHOygY2FlY6SNxavSfeScId9d+TLvms7c3CzW1VOHpcUvJgclnVN0+nQvrYL0aFPHAX4rbDdzKeXzsBV2PCUtV3tnFABRcPXIwMweXoULP1RVS4u3/v7zF+30J8d+xPFWZfAOcjr5vVeZiZAraOiEtMQExiXJqb+RQogUL2zjqA/X51DBtu7jRRhdnPwd7rpw2LMvx+qMc0eOUvkuH6rEgBCmSvwNPzfyyy15V7owAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFMhlApYWEqadmMt6nT3dfZqC1UU0NwSrZ88n/2T24uXlpcPTExPTPh+TkpIgL0O5evUqJNxRinVdTzgNbmxYxXcKUIACFKAABUwEJk6cqMPV//nnH9SsWTPXB6tfunQJS5YswaJFiyD3DxKynlaxtrZG4cKFsWLFCtStWzetKlxGAQrcR0Duu7du3YoFCxbg999/R3h4OJo0aYIpU6agR48ecHNzu8+WXEwBClDg8QkYgtWlxUtRofqVXuuxifGIRfKAbAl30v5tIb3ts2pdZMJts6azOlhddnYh6obZPtObEbfh22diVK3u+M+OH1MF0t+Kj8Geaw8OKJew80cJVpc+3oyLTq+r8L95Wb/SraRWTqr3Ir46vCJTweoPapvrKUCBJy/AcPUn/xmwBxSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKKAEJCj99OnTKFSoEIKDgzNkYmlpiSJFiuCld4fgO+ejGdqGlShAAQpQgAJPm8CRI0fwxx9/YOrUqRgxYoQOJM+NBnKvIAHPEqi+Y8cOODs7w9fXFw0aNMCMGTOQkJBgdlhyn9C0aVMsXrxY31+YreQMBShwX4G9e/fqQHU51y5fvqwHZBg1ahR69eoFT0/P+27HFRSgAAUokLsFdgafhK2VDSbW64sP9sxLFbB+v6NztLbTqwrYOd2vSrYv/0/VjjgUEoBV5/dm+765QwpQIHsEGK6ePc7cCwUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABCiiBGzdu6AB1CVE3vPz9/XHmzBlERERoowIFCjzQysrKClJv7NixGDx4MPaFncP3a48/cDtWoAAFKEABCjyNAmPGjEGNGjWwZcsWVKxYEV27ds01DHLvsHTpUh2ovnXrVjg6OqJTp05477338Nxzz8HOzg4XLlzQwfGGg7KwsMCdO3cgYdDjx4+HhKyzUIAC6QscP35cB6ovXLhQ35uXK1cOAwcORJ8+fVC+fPn0N+ZaClCAAhTIMwJbLh/B8bAgWKv7p/ikxAceV8l8hTCqZnddr3OpevAPv4TFZ7dnaNsHNv4IFRapPlyJDnuEFrgpBSiQ0wUYrp7TPyH2jwIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKBALhMIDw83BqebBqjLtKyTIkGoZcqUgYQ2Pvvss3jjjTf0tMx/8cUX+O677xAXF5fqyK2trWFvb6/DUt966y04OTmlqsMFFKAABShAAQrcE9ixYwdWrVql/32VQPI1a9bk+LBxuV/4448/dKD6X3/9BVtbW3To0AGLFy/W73IvYFo8PT1Rp04d7Nu3D4Z7hfnz56Njx46m1ThNAQqkEAgMDISEqS9YsAB+fn5wd3dHz549daB67dq1U9TmLAUoQAEKPC0C12JuZvhQJcD8v//+ol+GjTISym6om1XvDFbPKlm2S4GcI8Bw9ZzzWbAnFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAVyjUBkZOR9A9Rv3Lihj8PGxgalS5fWoelNmjTByy+/bAxQlxBUS0vLNI+3fPnySEpKMlsnQalWVlZ45513IMGwBQsWNFvPGQpQgAIUoAAFUgvcuXMH7777Lp555hkdTN66dWu0b98+dcUcsETuLVasWKED1devXw8LCwu0a9cOc+fO1SHpDxpQpW/fvjpc3cfHBytXrtSDuOSAw2IXKJDjBIKDg/X1QALVd+3aBTc3N3Tv3h3Tp09Hs2bN9LmX4zrNDlGAAhSgQI4VkCD1m3HRObZ/7BgFKJB3BRiunnc/Wx4ZBShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQIFHEoiJicGZM2fSDFG/evWqblsCz0uVKqVD0+vUqYPevXsbA9S9vLx0IPrDdqJcuXJISEjQm0lAuwTDDh06FKNHj0aRIkUetjnWpwAFKEABCjy1AvPmzcOePXswbtw4/Tp48GCOspB7jTVr1uhAdXmXf/+fffZZzJo1C76+vnB2ds5wfwcMGIDExEQMGTIEDg4OGd6OFSnwNAiEhYVh2bJlWLhwITZv3gxHR0d07twZH3zwAdq0aQMZyIiFAhSgAAUoQAEKUIACuUmAd7C56dNiXylAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKPGaBuLg4nD171ixA3d/fX89funRJB5tbWlrCw8NDh6ZXrlxZh51KALq8vL29IQHoj7NIu1Jkv/3798dHH30ET0/Px7kLtkUBClCAAhTI8wKRkZEYOXIk+vTpg2nTpuGNN95AtWrVnvhxx8fHY/369TrkecWKFZCA9ebNm+s+duvWDa6urpnqY8GCBfHOO+9kaltuRIG8KBAVFYWVK1diwYIF+pyTe+v27dvrc69Dhw4chCAvfug8JgpQgAIUoAAFKPAUCTBc/Sn6sHmoFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgwNMpkJCQgICAgDQD1IOCgpCUlKRhSpQooQPTfXx80K5dOz0tQedlypSBvb19tuGVKlUK33zzDdq0aYOyZctm2365IwpQgAIUoEBeEhg7diyio6MhIetOTk749NNPn9jhJSYmYsuWLTrkedmyZQgPD0ejRo10n3r06IGiRYs+sb5xxxTISwKxsbH4888/9bm2evVqyEBKrVu3xqxZs/QASc7OznnpcHksFKAABShAAQpQgAJPsQDD1Z/iD5+HTgEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABCuQdAQlIP3/+vFmA+unTp+Hv74/AwEBIwLqUIkWKGEPTW7RoYZyWEHMJXs0pZciQITmlK+wHBShAAQpQINcJHDx4ENOnT8fgwYP1gCVr165F/vz5s/U47ty5g3///VeHPC9ZsgRXr15FrVq1MHr0aLzwf/buA7qqKmHD8JdKCiFAgARCDSQQeu9IERUUlaY0RRzAruM4qL9t7DqOCurYyyijIqAIiAWGpiK99xoIHUIIAdLrv/fBXJMQIEhCCe92Xe45u+/nXGOYWeu7N9+s6tWrn9f9sBgCJVXA/p4/a9YsjR8/XpMnT3a+UKFTp04aPXq0+vfvr6CgoJJ6dM6FAAIIIIAAAgggcBkLEK5+GT98jo4AAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACl65A2rr9GjVqlBOmbgPUt2/frrS0NOdA5cuXd0LTIyIiNHToUFeAenh4uMqUKXPpHpqdI4AAAggggMAZBWzQ8siRI9WyZUvZUHP7u0DPnj3POK6oOqxatcoJebZBz/aLXyIjI3X33Xdr0KBBzu8kRbUO8yBwOQvYL1b65ZdfnH/XJk2apMOHD6tNmzZ69tlnnS8vqFKlyuXMw9kRQAABBBBAAAEELgMBwtUvg4fMERFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEECg5Amkb4/Vzzt/lg1Qv/nmm/MEqNtw9cuteLi5X25H5rwIuAQ83Pn8uzDyXfCzIR8ItxetgLdH0cXCvfzyy1q/fr2aNGnifKnK22+/Xezntl/0YsPUv/rqK23atEk1a9Z0wtQHDhzo7KPYN8ACCFwGAtnZ2VqwYIEmTJjgfHHCgQMH1LRpU+cLlwYMGKBatWpdBgocEQEEEEAAAQQQQACBEwJF97doRBFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQTOm4D/DY207LbPz9t6F/tCrStFaFz3UUrLyrjYt8r+EChSgVImkLlFhTpFOmdJmizIJ0CTezyuo2lJJelYnKUECtQtG1okp1q1apWef/55de3aVb/88osWLlyogICAIpk7/yR79uxxQp5toPry5csVEhLifOHLp59+qrZt2+bvzj0CCPxJgaVLlzr/rk2cOFG7d+9WZGSk7rrrLtkvL6hbt+6fnJVhCCCAAAIIIIAAAghc2gKEq1/az4/dI4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCBgBdzc3XVu9BRYIIIDASQJdqzQ6qY4KBEqiQHJysoYMGeKELc+cOVNvv/22mjVrVqRHjY2N1TfffCMbqD5v3jwFBgaqX79+euWVV5xAd3d39yJdj8kQuFwFVq9e7QSqT5gwQdu3b1edOnU0dOhQDRgwQI0a8d+1y/VzwbkRQAABBBBAAAEE/hAgXP0PC64QQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBS1Lgb3/7m3bv3q3s7Gzdeuutuueee4rkHAkJCZoyZYrGjRsnG9ru7e2tG264wanr0aOHc18kCzEJApe5wIYNG1yB6ps3b1aNGjV08803O4HqLVrwJUKX+ceD4yOAAAIIIIAAAgjkEyBcPR8ItwgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggMClJDBp0iR98MEHqlChgurXr6+PPvronLaflpamH3/8UV999ZWmTZumjIwMXXPNNRo7dqxuvPFG+fv7n9P8DEYAgRMCNkR94sSJTqj6+vXrFRoaqv79++vTTz9V27Zt5ebmBhUCCCCAAAIIIIAAAggUIEC4egEoVCGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKXgsCWLVt0++23q2LFiipXrpwmT54sb2/vs956VlaW5s6dq3Hjxunbb7/VsWPHdMUVV+iNN95wwp7Lly9/1nMyAAEEThaIiopyBaqvXr1aISEh6tevn9577z117NiRQPWTyahBAAEEEEAAAQQQQOAkAcLVTyKhAgEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgVMLBPuWVcfK9U/d4feWPQmxWhyzJU+/UP/yahJUSw3KV1dWdraijh3QykNRyjb/VPEP0qKDm/P093L3UINyNcyYmqoZUEl7Eg9r69F9WhqzVdfWaKnJOxY68+QZVAQ3EYFVdE21Zlobt0s/71vrzHhvg2uVkpmuTzbNLIIVzn2KpkFh2hy/R8mZaec+WQEzdKnSSBuP7NbB5Hh1CInUzuMxjn9O19aVwtUttLHSszI1d+9arYiNymk66b2Sb6Cs6W8HNrra7DM9nHI8z5yuRi4QQAABBBBAAIFCCiQmJqp3795O72zz++UPP/ygsw1BX7JkiROoPmHCBB04cEAtWrTQU089pQEDBig0NLSQO6EbAgicTiA6OtoJVJ84caKWL1/ufBlC3759NWbMGHXu3Fnu7u6nG04bAggggAACCCCAAAII5BMgXD0fCLcIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgicTiAm+agTtD3xqkdU3idA/zFh44sOnghR93BzV/lSpdU3rJ0Tmr14zol6G5L+VIuBuiPyan2wcYbm79+o5IxUtahUR2+0H65Ab389ueSLPOHqTU0I+3+6PqAk0+/jjTP1467lCvYrq8HhnTW1xxNyc3PTjF0rlJCRcrrtnnWbDXG/vV533d2gp+6d975r/C0RXZSYnnJRhKv3qNbcCTUvrmD1Uh5eGt99lFpN+rtz/v92e1C9fnzeZfHPNkM1yDyHY2lJqla6gp5sfrOeWfaV3lw7zdXHXgSZz8eDjW7QCPPcx26enSdcfZ0Jrn+13TB9E7VACw5uyjOOGwQQQAABBBBAoDACNkx96NCh2rZtm/z8/DR79mzVqVOnMEO1adMmJ1B93LhxioqKUkREhO666y4NGjTIuS7UJHRCAIHTCuzatUtff/217BcXLF26VEFBQerTp49efvlldevWTR4eHqcdTyMCCCBQUgXsFwxSELjYBfw8vVXG2/di3yb7Q+CyFiBc/bJ+/BweAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQOBsBbKVrWWHtmlJzFb1qN5ck7cv0rwDG/JM88XWn/VWxzucOhvUPeO6Z1WrTLB6z3gpT4C6HTdlxyJ93/Mf8vUs5ZqjX1h7vdfpbk3avkB/nf+R0rIyXG3f71yqxSbMfUyH4fIxIT9FHa4efTxGn26a5YSrZ2Rluta9ctpTysrOct0X5YUNIW8aFKbZe1efcdp7G1yrlMz0Yg15bx9cT7sTY51X4/I1lZqZoY3xe5y9XV+jlXHIVq0vRzjvnSs30GcmfP2pFgM0NXqxrF9OqV66osZvm6f7G/XKqXK9ZxrLUQs/1YTujyh+WaI2HNntauMCAQQQQAABBBAojMDTTz+tyZMnO8Hqc+bMUePGjU87bM+ePRo/frwTqr5y5UqFhoZqwIABGjx4sFq0aHHasTQigEDhBOy/ZzZQfeLEiVq8eLHKli2r3r1767nnnlP37t3l6UkEZOEk6YUAAiVZ4NMuD8i+KAgggAACCJyLAL9Zn4seYxFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEELluBhPTkU579aFqSXl31rdP+cJM+alqhlp5fPiFPsHrOYBvGbfvWDKjkVNmg8dfb/UVH0xL1yKLP8gSr54z5dPMsDazTSX4mXL04ig0PtyXn3V4nZaTatyIv7m5u+qTz/SaYfMkZ544sW1UjI69W028ePGPfc+nQLbSx5uxd40zRLbSR69pWtKoUoSeXfuGy+WX/en27faGGR16lZhXC8oSrr4zdLi93j1Nuxfq+s/4HvdlhpK76/h+n7EcDAggggAACCCCQX+CLL77Q888/r1KlSmn27Nlq3rx5/i7OfVxcnL755hsnUP3XX391gp779eun119/XZ07d5a7u3uB44qr8sklX2hP4uHimp55EbigAnHb9mraHa/Ky99H1Ts0UrcXR6pKy7rK8vTQeG3W+Hmbz9v+7N+zHm3aT3XLhp63NVkIAQQQQAABBBBAAIHzKUC4+vnUZi0EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgRIvUNbbXy0q1tHsvatVyTdQf218vRNM/sH66ac8+7htv+ja6i2ddhuAV7aUv15e8Y2OnybA/c5f31FcSoJrTrtuv7D2+mTTTHWv2kQNy1XXv9f9oMzsLNUuE2JCwcPVwNQtjtmi73cudY3LuWgfXE8dK9dXWma6Vh2OdqqzdSJk3d5U8CmjHtWa64utPzttOX+E+JZz1qviX16LD26WDRvPKaGm7voarfXBhhmqZ0L9rq3RUnsSYjUxar6ZOVve7p76qMt96mICzA+lHHPqftq1XAeT43OmyPP+bKvB+nr7/Dx1OTdNgmqqnTmDr2cprT68I08guu1Txa+8elZv4fh0DInUlaFNtC8pTp9vmasUc2ZbBtW5QqW9fMyeW2nBwU1OkHu/sA7acnSvc23d3lz7nStY3Rlk/pi+e4UTrh6fmphTVej3n/et08tthjprTivguRR6IjoigAACCCCAwGUj8NNPP2no0KHy8fHRvHnz1LLlid8jcwCSk5P13XffOYHq06dPl4eHh66//npNnjxZPXv2lLd38XxBT876p3t/d/2PJ/0udbr+tCFwKQlku2fL78Er5BkZrFgTqB6rY9Luk//udT7O5CY3danSiHD184HNGggggAACCCCAAAIXRIBw9QvCzqIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAiVVYEh4ZyeS3IarNzaB314mQHzr0f1KyEg55ZHTszI1NXqx094mOMJ533p03yn724bo4zGudhsK/nr7vzhh5e5ubhoa0U2Ngmpo5p7VuqJKAye4/fqfnlf10hU0rec/nND3/2ya5Rr/VIsBTnj6Y4v/q6BSAfqw871Om41Wt/MNrN1Jr7QbpuSM1Dzh6p1C6qtf7fb6z8ZZSjBB8F92H6Xx237VqIWfOkHsb3e8UxV8y8jNzGGD3W1Au12rin+QxqyZKh8PL802e7yxZhsn6HybOXNKZpprX7kvIstW1dXVmun11VNyVzvXL7a+xQlPf3b5eJXx8tO7V9ylvzW+UUPnjNGR1ATdZALSXzX7L+XhrQblqznPJNi3rP7W5EYNrNNJ13z/jDKyM7XTmFY0gfiVTSj8N9sXyPf3/s+Zee3ZbXh6cgH7CzXniTfrLDu07aS9FaZi8cEt+nuTPiJcvTBa9EEAAQQQQODyFvj111+doHQbrL58+XJFRkY6IBkZGZo1a5YTqG5D1G3Aevfu3fXxxx+rT58+Kl269OUNx+kROA8C9u89Xo2qnIeVzryEh9kLBQEEEEAAAQQQQACBkixAuHpJfrqcDQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEECg2AVeaHOLE7ptFwrxK6e6ZUP1+OLPnXXrl63mvNvQ7sIUN7mpbmDoiTEJh04a4udZSvc37GXCycs7bRkmlH3aziX6ygSadw1tpJtrd9T+pCPqNPX/FB5YxYS679PnV/7NCTC3A3YlxGptXLQTfJ4Trt69ahM92OgG1fxyhJJMgLh9jd0yR+1C6jlrZGVna5yZv0f1Fmr7e/C7bfA3e/l3xzvUfsqjzpg1Zt5uoU00IvJqE7A+T9N3r9DnW+Y6AeYb4nbpvfU/OfP9fMNLJky9tROufswEsq+IjXLqt8bv028HNjrXBf3RoHx1p/qAOV/uYsPRb43oqoYT7pOdz5bb5ryh5f3H6J9thurOX9/V19vny57T+ny44X/aFL/H6fd4s5v0SLO+uiWiiz7bPFsLDm5S31rttDhmi+bsXeOMWR+324TUr3L6n+qPvmHt9M+Vk3T89/VP1e9U9RvNfuwevNw9ZIP2KQgggAACCCCAQEEC06dPV69eveTt7a3Vq1crPDxcCxcudALVJ06cqJiYGLVt21YvvfSSBgwYoEqVKhU0DXUIIIAAAggggAACCCCAAAIIXPIChKtf8o+QAyCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIXEiBJxd/oXkHNri2MKpJH9d1RnaWc+3h5u6qO91FtrKVlpUhH3nL3QSt5y82+PyVVZP0bqe7NDi8s95a+71+3rfO6WZD1W35Yecy590Gq9ty3Y/POeHn9toGv1f1D1KAl6+9dcpDjXtr1eHteYLBlx86EXiebYLVc0paZnrOpfPeP6yDfDy99Vyrwa76YL9A7Th2UGFlQrTs0DYlZ6Y5bVt+34u92WyCxK80Iez5iz376Yrduy0Hk+PzdLu7QU8nRD4nWN02Rh07oGgTaD/ABK+PWvipczZrl5Gd6QpWt/3GrJmqh5rcqA4hkU64uq3rFtpYP+9day/VpUoj43vi2qko4I9rTej8waR4vb9hegGthas6lpYkTxOsbt02x+8t3CB6IYAAAggggMBlJfDhhx/qrrvukp+fnyZNmqT//ve/+vLLL7Vjxw5FRkbq/vvv1+DBgxUWFnZZuXBYBBBAAAEEEEAAAQQQQACBy1OAcPXL87lzagQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBYhL4dPMstaxYx5l945HdznttE5pd2LLmcLQ6Vq6v2oEhWnpoa4HDdhw/6NSvMAHmOSUnCD1/SLkNXe9qQsJ7VG+u+fs3OuHnTSv8EbrZsHx1TY1enDON854zV57KfDf1ylXVARMqbsPLz6ZkmsB5t5Nz43WmNSv4lHH6pOQLea8bGKrFMVtO2sLCA5tUM6CSwgOraEXsibD4/J1s+PvexDhV8AkwIfFDTOi8j2xYug2XH9O+gmO2JX6fuR6u0aunandibJ4pbBj6LRFdNGzOm3nqz/YmMT3FGVLFrzzh6meLR38EEEAAAQRKuID9HWnUqFEaPXq0fH19VYk519QAAEAASURBVKtWLfXo0UOhoaEaNGiQhgwZoqZNm5ZwBY6HAAIIIIAAAggggAACCCCAQF4BwtXzenCHAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIDAOQkcTjmuGbtXOnOsOrxDCSY4u2ZAsBP0HX085oxzLzy4yQlX71S5gcZvm1dg/ywTsmnLiT8L7OKqfKL5TeoQUl99Z7wkG0x+Q83WrjYPN3f5eZZyhcG7Gn6/yB/UnrvdhqSHB1aWp5uHMrIzczf9qesznWXL0X0mlN1N/ma/iRmprjXi0xLVvGJtuZu2HBfbGHXsgNPHtp+qeLt7Ktg3UHP2rtZba6c55+lTq53u/e19E7heRkPCu+i+eU87brEpx/JME+jtp8ea9dddv7yrtKyMPG1ne1O2lL8zZG/i4bMdSn8EEEAAAQQQKMEC0dHR6tWrl9avX++c0svLS23bttVbb72lzp07y93dvQSfnqMhgAACCCCAAAIIIIAAAgggcGoB/kZ8ahtaEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQOCeBI6kJennF1/IwwZfPtRpy2rkala/htL++eoo2HNmtQXU6qUlQzdOOOVNjjdIV9XDTvpoYNc8JCLf93U2gek6xAemb4/cqslw1VfQJzKku1Pu6uJ3y9/LRX+p1z9Pfho4Pr3dVnrrT3fyeEy8b9H66stGY2FLRhKHnLssObVOAl68al6+Vu9qxO5R8VNHHD+apz33TulK4fDy9NX33Ctnw9AbmGczbv14xZlxDc70kZot2J8bqUMpRE2T/R/y7r4e3nm01WI8uGqtj6cmuKYN9y6p2mRDXfWEvgv3KKttA7Ew4VNgh9EMAAQQQQACBEiqQmpqqb7/9Vl26dFGtWrWcYHVfX199+OGHiomJ0UcffaSuXbsSrF5Cnz/HQgABBBBAAAEEEEAAAQQQKJzA6f8fhcLNQS8EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQuO4FAb3/nzNUDKp727O9vmK7J2xfqhpqt9WaHkfLx8MrTv5p/Bb3RYYRKm6ByW1Iy03XbnDecgO9x3UepY0hknv72pqJPGafOhqPnFD/PUs5luVKlc6qc8HN70zesvRNA3i64rtqH1FPZUv7yN/1Le/rojTXfOf1fbTdM3u6ecjP/9A1r59S1C66nnPm8zb7LePm5QtC/NWfak3BYL7S+RQ807KWIwCrqXautc8YJ2+Y5423ouS123pwS5BMgO1dOOZB8xLlsZYLObWlQrrrznv+PVbE7lJSRqvomCD53eWbpV0o1ZgPrdHRV2zO0rhShZ5Z9payc9HbT6unm4ewzp+MNNdvot/0bNGP3SqeqW2gjzd231rm+MrSx6zqnv323c/y3298Ul3Jc/YzTyMirndcjJsT+g873aufxvAHpZb1PPA8fE8h+qlLdhODP2bvGOcep+lCPAAIIIIAAAiVXICsrS3PnztWIESNUqVIl9e/fX7/88os8PDzUpEkT7d69WyNHjlSpUid+3yu5EpwMAQQQQAABBBBAAAEEEEAAgcIJeDxjSuG60gsBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIGSKTDPBGwvjtmi3GHlpzppiG853d+ol26q3VFe7h6qE1jZBG67a9mhbQUOyVa2pkYv1vZjBzWs7pVm7HVqZwLOu1RppOdbDTFh4tX0+uopWhMX7Rofl3pcn26aZQLXffVMy8G6rkYrNQ6qqa5mzN0Neqqyf5DGrJmib7YvMLNLt0Z00fB6VynA21c1TFD37oRY7U86okMpxxRq+l5bvYUTmB517IC+i16i/mEd1CY4wrleERulxPQUDat3pR5qfKNZq4XWHI5W06Ba2nhkt/YlxqmXWX9w+BVmfj+VMsHo6+J26Xh6smbtWaXuVZuqX+32Gln/GkWUDdUTi7/Q7sRYdTCh8H9rfIMJci8tG/xu17mmWnMniLyMmcdmni86uFmJJjC9rQlx712rjQl+j9S35kzHzNz5iw2dt6HpbU1A/A+7lrma41IT9NuBDWat3qpeuoIJcvfSQ01u1Nfb52vsljmufj3M2o3Nmdzc3NSxcn3HrIIJqbdB9mlZGU5o/Oj2w/WPpeMUn5ao0e3/otdWTXEcXZOYi4+63KdeNVs5z/Dqas2U8+pUuYGm7FikOfvWuLp3r9rEMY00z9g+h6NmXvtc7Jlziv0MvdXhDj2x5HNFH4/JqS7Uu4e7ux42oe4UBBBAAAEEELg0BVatWqXXX39dw4cP19tvv6309HT5+PgoPj5eXl5euuqqqzR9+nQFBgZemgcsYNevrJrk/P5aQBNVCCBQhALu5u8911Rvribm70AUBBBAAAEEEEAAAQRKooBbtikl8WCcCQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgcIKvLjia721dppSTYB3cZey3v6KLFdV6VmZ2nZ0vxPkfaY1g3wCVCsg2Ak635cUd6buJ7WX9vRRQkaKq97b3dMJFHdVmAsPExAf7FtWdn5PNw8TQi5nj7n7nOq6mn8FE5KZrT2Jh0/V5Yz1lf3KnRRknn+QDXaf3/sV9frxeR1IPpK/WXXKVHYC6Tcc2XXS+caY4PRbTAh9xc9uNUHn5XUsLdkJiD9pkvNc0btmGyeof8js1896ZW8PT8Xc9vlZj2MAAggggAACCFw4gejoaI0bN05ffvmlNmzYoFq1amnIkCHy9PTUa6+9Jl9fX8XGxur222/XBx984NRfuN0W/crlPx2sLOLvih6WGRHIJ2C/AGx0hxEaGtE1Xwu3CCCAAAIIIIAAAgiUDAHPknEMToEAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggMClIRCflqiFBzef1WYPpxyXff3ZkjtY3c6RlpVx0lSZ2VlOsLptyMjOlMlKL3TZnRhb6L6n6rg/6eSw9Px9bfj9A799qMea99eD8z92At1z99l2bH/u21Ne7008+4D6U052Dg3hgVWcYPXhP791DrMwFAEEEEAAAQQudoG4uDhNnDjRCVSfP3++goKCdPPNN+ujjz5SaGiohg8frrlz56pOnTqKiorSW2+9pfvuu+9iPxb7QwABBBBAAAEEEEAAAQQQQOCCCbhfsJVZGAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQOEuBBQc3afKORXqh9RC5mX8KW3w9S8nTzUP+5v1iKNX8K+ihxjfq3nnvK8WExlMQQAABBBBAoGQJpKSkOIHqN954o0JCQvT3v/9d1apV07Rp07R//369+eabWrRokRo2bKgdO3aoZs2aio+P15w5cwhWL1kfBU6DAAIIIIAAAggggAACCCBQDAKexTAnUyKAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAsUm8PO+tdpwZJc83d2VnpV5xnVuCuugbqGN5ObmpmdbDdbYzXO0Nm7nGccVZ4e0rAzdPe+94lyCuRFAAAEEEEDgPAtkZWVp7ty5+uKLL/Ttt98qMTFRV155pT755BP16dNHpUuXdnb0yy+/6N5779W2bdvUtWtXZ0yLFi00fvx4J4D9PG+b5RBAAAEEEEAAAQQQQAABBBC45AQIV7/kHhkbRgABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIGY5KOFRpixe4X+t2elq39qZrrr+kJdHEyOv1BLsy4CCCCAAAIIFLHAypUr9eWXX+qrr77Svn371LJlSz333HMaOHCggoODXavt379fDz/8sNPXhq5XrFhRM2fO1FNPPaUnn3xSHh4err5cIIAAAggggAACCCCAAAIIIIDAqQXcsk05dTMtCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIlHyBF1d8rbfWTtPFELpd8rU5YVEJZK87qCdCeig8PNx51a5dW76+vkU1fYmep+nXf1X08ZgSfUYOd+kL1AmsrGX9Rl/6BymmE/T68Tn9dmBjMc3OtAgUXiA7PVPH/jZF7qVLyatNDXm1rSGPymUKnCBjc4ySPlks30HNTLubkr9aIb872sqzTsUC+5+q8p9tb9Nd9Xucqvmiri//6WBlEX93UT8jNlcyBDzd3DW6wwgNjehaMg7EKRBAAAEEEEAAAQQQyCfgme+eWwQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIFLQCBzT7ze/uZt7dmzR9kmpNTNzU1Vq1Z1gtYjIiJcoes2fD0sLEze3t6XwKnOzxYPJB05PwuxCgLnILA/Me4cRpf8obsTD5f8Q3LCS0LAzctDAU9fI/eKpc+4X8+6lRTw0nVy83R3+no2DJEdfzbF091D/HfsbMToiwACCCCAAAIIIIAAAgggUBIFCFcviU+VMyGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAiRfwu66Bdk38XMnJyYqKitLWrVtdrw0bNmjq1Knav3+/4+Dh4aHq1asXGLxes2ZNeXoSRVPiPzAcEAEEEECg2AQKE6yes3hOsLq9P9tgdTvG3fxDQQABBBBAAAEEEEAAAQQQQOByF+B/0b7cPwGcHwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQuaQFfX181bNjQeeU/SEJCgrZt2+YKXbcB7CtWrNCECRN06NAhp7uXl5dswHpERIQTvh4eHu56t4Hs7u6EuOZ35R4BBBBAAAEEil/gisoNVNE30FkoOztbU6IXKcu8n6q0C66rKv5BruYfdy5Tcmaa6/7PXpT19teVVZvkGW73czjlmPYkHlbUsQN52orqpqo5y9XVmqlpUJgemP/hSdN6uXuoQbkaahJUUzUDKjl72Xp0n5bGbNW1NVpq8o6Fp/U6acJCVkQEVtE1Zl9r43bp531rnVF2/VFN+uilFV9rX1JcIWcqvm7WbHP8niJ5/gXtMsS3nOqVCzXnX6fypQLUrEKYZu9d7epa2tNH/Wt3cJ7LdvP5+Dpq/mn3Uq5UaQ2re6XGrJnqmqNXjVb6fudS1z0XCCCAAAIIIIAAAgggkFeAcPW8HtwhgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIlRqB06dJq2rSp88p/qKNHj+YJXbfB6wsWLNDYsWN15MgRp3upUqUUFhZWYPB6aGio3Nzc8k/LPQIIIIAAAgggUCQCi2O26KawDnq7053OfFlzTcD6jkUFzu3nWUpfdR+lsiakevXhHbrzl3dPG2Zd4CSnqIxPS1TU0QMa1/3vJry9vD7bNNsEi+9U+5B6uqFmax1IiteTS77Qr/vXn2KGs6/2N+dpY8LiH27aRwXlyTcNqqX/dH1ASRmp+njjTP24a7mC/cpqcHhnTe3xhPM72oxdK5SQkXL2i59mhA1Rv71ed93doKfunfe+q2cTs59bIro4AfgXOly9R7XmSs/KLLLn7zpkroth9bqpWumKTrh6v7B26li5vitcvU6Zyvrh2n8oIT3Z6ePt4am/Nb5R1/zwtGKSj+aa5Y/Lf3e8Q60rhecJV49JjtebHUbqoQWfKDM764/OXCGAAAIIIIAAAggggIAjQLg6HwQEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQOAyFAgMDFTLli2dV/7jHz58+KTg9Tlz5ujDDz/UsWPHnO5+fn6qXbt2gcHrISEh+acstvunnnpKNuh9+PDh8vLyKrZ1mBgBBBBAAAEEzq9Aama6Jm1foDc6jJCnu4f+2qjXKcPVB9W5wgnUtjucvWe1NsXvKdLNrjq8XYsOblZfE6Rt9zTvwAZn/tGrp2p6r2c0/qqH1e27J4ts3UQTmm7X6V2zjVpUrJPnLP3C2uu9Tnc77X+d/5HSsjJc7d/vXKrFB7doTIfh8vH0LvJw9ejjMfp00ywnXD3DBJjnlKnRixX25R2KSz2eU1Wk7wPrdNL4bfPOOOe9Da5VivncfLJp5hn7nkuHbqGN9f766c4UXUMbacaula7pXm4zVH1nvKz1R3YpyCdA/2gxULfV7aanWgzQ/b996OqXc3FbRDdFlq2ac+t6XxKzVQFefk7A+n2/feCq5wIBBBBAAAEEEEAAAQROCBCuzicBAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAII9AUFCQ7Ktt27Z56u3NwYMH8wSvb9myRT/++KO2bdumpKQkp39AQIDq1KlTYPB6hQoVTprzz1ZkZWXpn//8pzIyMvTiiy/qpZde0pAhQ+Tu7v5np2QcAggggAACCFxEAsmZadpydJ/c5aZmFWqrU0h9V7B57m3eXu9K/XfLXP29SW8dT0/J3VRk18fST/yek3vCfUlx+mHnMids/IaarbVpVdGGumdmZynb/JNTbFj36+3+oqNpiXpk0Wd5gtVz+ny6eZZsGLmfCVcvjpKVfWI/Oe85axRXsLp95jag/Ezh6jagfGTk1Wr6zYM5WyqW90BvP/NZDNPP+9bJw81dnSo30CMLP3PWahpUSxOjfnOC1W3F4ZTjemnF17o1oovaVIpw+uT+o3aZEDUOqqnpu1foptodcjc517P3rtbDTfvoytAmstcUBBBAAAEEEEAAAQQQ+EOAcPU/LLhCAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQOINAcHCw7Ktjx455emaboM19+/adFLz+7bffKioqSqmpqU7/smXLKjw8vMDgddt2NmX37t1OsLods3fvXt122216/vnnncD1vn37ns1U9EUAAQQQQACBi1QgywSMv73uR717xd16oNH1J4WrX1W1qVYc2q6Y5KOnPIENsW5VKVwNylXX4pgt+n7nUqdv/XLVZAOxbbFB5nP2rlWTCjVVySdQ6VmZmrxjkTKyM532U/2RnHHidxx3NzdXl9KePrqqWlPVLRuqvYmHzbxrzHucq91eFKZPngHm5tGm/VS2lL9eXvGNCZFPzt/sur/z13cUl5Lgui/r7a9+Ye31yaaZ6l61iRoah3+v+8E5s4+HlzpWrq8mxsEaTNg2T/uTjrjG2ov2wfWcPmmZ6Vp1ONppyx367mbC7zuGRCohI0UrY7c77faPEN9yznpV/Mtr8cHN+mX/eldbTjB5tllzScxW9ajeXOGBVTRp+wJFHTvg9LPB6uOuGiX7e+awulfqgNmXDSIvqDzbarC+3j6/oKYzWlfxK6+e1Vs4PvYcNszcBud/bgL7U8yZbYkwe+tcpaGzx6OpScaznSr7lXP2Zsdas/kHNhqfHXn2cDA5Xqtid5z0OfJ089BTLQbovnkf6LHm/fOMyX3z3vqf9EzLQc5nKLd57j5cI4AAAggggAACCCBwOQrw9ZqX41PnzAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQBELuJlA0dDQUHXp0kUjR47Uv/71L02ZMkXr169XUlKSoqOjNXPmTL300ktOMPvRo0c1btw4DRs2TK1bt1a5cuVUsWJFtW/f3ql78cUXNXHiRK1cuVIJCX+Eg+be9tatW123NnTTvmyQe79+/dS0aVPNmDHD1c4FAggggAACCFy6AjY0e58JJ7eB5TYQPXe5p0FPvbP+h9xVea7vNu1vdBip8SY0/KONM/Ri61v1l3rdnT4bjuxWtrmywe3dQhvrUMpRZZnfJwaHd9asvatPCsTOM7G5sQHZdpwtv+47ERzesHx1zej1rDJMOPtHG/+nQBNsvrjv6xpYp5PTz/5RmD6uzrku2gRHOHdbj+7LVXvyZfTxGCfo3LYMqnOFNgx8R6+0vU0jI6/W0y0G6RkTRF6vbFX5e5bSiv5vKCUjTWPWTHXOY/duA9dzig0AH2D2/rYJY5+0faEJeD/xBTbWzRYbIP9p1wc07dqnTFB92IlK86cNRv+/5v20xoSxb4nfqy+7j9Jr7W532m3Y+4ed79WUHo9rSEQXvdXxDrWuFKERkVfph2v/IdtuS3xaotbH7VJaZoa2mTPboPqCSqQ5y9XVmmnWntUnNZ/J+qawDlrQ5xW90PoWjW7/F+esDcwzfNXs1e7FPmNbEk1w/EbzeakRUNEJeN96dL9qB1bWbLOmrd9m7o+kFvw7a6h/kGbuWZVnb48266t31//oek55GnPdLDKh9I2CaqhHtea5arlEAAEEEEAAAQQQQAABTwgQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEChOAXd3d9WoUcN5de9+Isw0Z73MzEwneN0Gpee8tmzZonnz5mnnzp2y7baEhIQoIiJC4eHhrteKFSvk6empjIyMnOmUlZXlXK9bt049evRQu3btnKD3jh07uvpwgQACCCCAAAKXlkC6CSp/zwRRP28CsO9v2Et3z3vPOYAN1c4w/+3fbMK7u1RpVOChbKC4DcC2ZVdCrNbGRTtB1f/ZNMup+2rbr2ZsQ91Ys41eWfWt7oi8RrfPfeuUQdkhfuUUEVhF9U0A99CIroo0Ye+PLf6vFhzcJC93D/2nywOavGORpu1c6sxvQ8mbBNXSWx3u0MrY7dp+7MAZ+9jz5C9uclPdwFCnemfCofzN8jNB6damin95p82Gu0/buUT2fF1DG+nm2h21P+mIOk39P4Wb/duAdhssHuJX1vjtc0Llp+9eridb3Oycye61e9UmerDRDar55QglZaQ6r7Fb5qhdSD3X+nav/zJuvWu1ddXZ0PZ/m8D09lMedcasMebdQpuY8PSrnZD7ZYe26Z5576tfWHuzfjn1mf6SMrOz9Mu+dRp/1cOyIfIzdq80z2qnYlOOqap/Bf12YKNr/vwXNgzdlgPmfLlLYZ6HDe6357Q+H274nzbF73GmeLzZTXrEBKDfYsLfP9s82wS7xzmvMR1G6M0105zn/ZSxGr166mn31j64nhPS/+66H11b6xAS6Xxul8T88UVBrsZ8FweT4xVvQtubVqiln8zzoSCAAAIIIIAAAggggMAJAcLV+SQggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIXDABDw8P1a5d23nZMPTcJT09Xdu3b3eFrtvwdRu8PnPmTO3Zs0fZ2dlOuHruMTnXOaHsS5cuVadOnXTVVVfpn//8p5o3b57ThXcEEEAAAQQQuIQEPts8R6Oa9lX/2u31/PIJ2pcUp7sa9NTb674/7Smu+/E5J+DbdqpbNtQEdQcpwMs3z5hHF411wtln9XpOD8z/SIdSjuZpz33TN6ydOlSOVFzKcU3dsVgjfn5bcanHnS7dQ5sqwqyx9FDe0OzZe1frptoddKsJY5+/f+MZ+zy55IvcSzrX2cpWWlaGfOQtdxO0nr/Y8PNXVk3Su53u0uDwznpr7ff62YSV22JD1W35Yecy590Gq9vyzfYFWn14h3PeUh5e6hBS36mvXSbECYJ/qHFvrTq8XcfTk516+8fyQ1HOtf09LKekZqbnXDrv/U1ou4+nt55rNdhVH+wXqB3HDirMzG3D1e0YO4ets8HqtuQEm9sw9dzFnv10xT5XW2wQee5SmOdhra1dRnama307x5g1U/VQkxuNSaQTrm7r7Genil955/kGevupUfmamrd/vW0qsLi7uenx5jdp0MzXlGjWsMWOs4H/w3/+d4FjCqo8mpbsfHYLaqMOAQQQQAABBBBAAIHLVYBw9cv1yXNuBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDgIhfw8vJS3bp1nVf+raakpOjqq6/WvHnz8jfluc/IyHDu586dqxYtWqhPnz7KaJUpBfvl6ccNAggggAACCBReIDY2VqVKlVJAQEDhB51jTxvw/emmWXqw8Q2624Sqj1nzneqXq6a/nibc2i5pg8W7VmmkHtWbO8HmNsy7aYWwPLuJT0vUCysm6N8d75S/Z6k8bflv3l33o+Yd2JC/2rmvW+5EyHdiekqe9oUHNp1oDwxVbMox5/p0ffIMznWz5nC0Olaur9qBIScFuOd023H8oHO5wgSY55ScIPT8IeX2Pib5qB5vdpNSMtO0Ina7M8Tdzd15b1i+uqZGL86ZxnnPmStPZb6beuWq6kBSvEYt/DRfy+lvs34PWXczoeS5S/59526z1xV8yjhB7Sn5Qt4L8zzyz5Vzn2w89ibGmbkDnID1/mHtVb10RSVnpOlfbYeZXyXLOmbPmgD59Ud26+ON/8sZ6np/odUtesd8XtbERbvqXmoz1Amuv7Z6C1dd7TKVVcrDW9fXaKWjaUn6Nd9nOjEjRVX8y7v6c4EAAggggAACCCCAAAIyXzpFQQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBA4DIX8HR3V2q+IL7LnOSiPH52Zpay4pKKdW9ZcYlKnrBSWcdTi3Wdopjc082jKKa5ZOfw8fHRrl27Cr1/G7JugzonT56sI6/PLvQ4OiKAAAIIIIDAyQLXX3+9ypUrp06dOunVV1/VunXrTu5UDDXvr5+utMwMDat3pROyXlCgdf5ln2h+kx5u2ldPLx2n73YuUebvAd65+7nJTVdXbaalMVv1StvbVMk3MHdzoa/jUxOcvq0rReQZsyshVulZGbIh7oXpk2dwrpuFB0+EtHeq3CBXbd7LrOxsp+LEn3nb8t/VMGHh83r/U8tjt2n0mqnanXDI1cXDBKz7maD5lhXruOpyX5wu8NwahwdWVlH9vvr7kXIvn+d6y9F9zu95+YPxz8Xa291TweZzEH08xglDf2nFN0rNStcHG6fLXh9NTdLnW+Y61+O3/ppnP/ZmWN1uTqj6T7uX52mzYe131u9hPmfDXC8bmB/g5ePc2y8PyF/Kevtrb8Lh/NXcI4AAAggggAACCCBwWQt4Xtan5/AIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggYgZGRV6te2aoqTAAhYBdO4JPnxmjFzyv12nefybe0f7FsZM2CZRq9Yr7SFsxQnztv1Q3DB6mUr0+xrHWuk1bxK3+uU1zS4zMzM7Vnz54Cz2BD1L28vJSWlua0BwYGqkWLFmrdurWaN2+ue/ZOUHqBI6lEAAEEEEAAgcIIuJsvJ7L/LZ4/f74WLVqkRx55RJUqVdINN9yga6+9Vt27d1dAQEBhpjpjHxvwnVMOJB/RhKh5ujWiq/qHtddzy8bnNBX4bsPDbbD6g/M/UsrvX6bkbkLD85d7G16rH3ct14IDG7Wgz780uv1w3TJ7dJ5uNoD9TGXZoW1Ol/Yh9fTm2mmu7vXLVZOXCeteErNFhenjGpjv4vXVU3RdjVYaVKeTPtwwXasPR+frcXa3/9esv9mXh2bsXukMzG1jA9I3x+9Vg/LVVdEnUIdSjhZ68nVxO+VvwsL/Uq+7Ptw4wzUu0NvPPLcO+mTTTFfdmS6yTbK6DXo/Xdl4ZLfTXNGEoSeaMPScci7WrSuFy8fTW9N3r1BSRqrzal0xQi8u/9qxaBtcV/f/9kGBLr3MM5L5vIzfNi9nK857h5BIDZj5ap46e/Nsq8HOM60/4d6T2uznzob97zh+8KQ2KhBAAAEEEEAAAQQQuJwFCFe/nJ8+Z0cAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQcATKlwrQjTXboHGRC7R5cYyaNWumSc+8q2+++aZYdtvbfA7+fuMwvf7663rttdc0d9x3evrppzV8+HB5ehLXUizof3LS6OhoJ9Q1f5B6xYoV1bJlSydI3X5ebJh6tWrV8qxy/9hpSv89YDVPAzcIIIAAAgggUCiBoKAgp58Nvs7IyHCuY2JiNHbsWH388cfy8PBQ27ZtnbD1nj17qlGjRoWaN3+nEN9yqmy+UKaUh5dSf/9v97/Xfq9bwrs44eIZ2ZmuIWW9T3z5TvXSFVx1NuDblr4miH3S9oVqaILCbfC5nc/fhLbb4Opqpn/HkPoaOOtE6PWrq77VMybs+ubaHTUx6jfXXDYY3JbqARWlA67qPBfr4nZp3NZfdH3N1qrqH6Q9iYeddhvEHXV0vz7bPFvpWZln7JMzaRmzpt1nTrEB8bfNeUPTej6pcd1H6c5f3tFvJhA+d6noU8a5teHoOSUnoL5cqdI6kpqQUy0/r1IK8Sunq6o21fJDURoReZXTVtnU2fO+seY7fdTlPr3abpjuMGvZvfcNa+f0aRdcTz/vW+fMZz1tCfI5Eaj/rbF+svkAvdD6FvmYNhtQXt/Y29+175v3gdPX8TdfiONtQudzSvlSJ/bu+/t8tv5gcryC/QJVM6CS0y0m+agTdJ4zxr6vit3h1NkQ++hc4eqFeR4583i6eSgisIq2HN3nVN1g9vrb/g2u4PnG5Wsqy/yz/sguZy828HyxCcvPX7pUaagHG11vvgTgN+dLvGy7DYe3X+a1wYTAz8/3vPKPz39fxb+cPE0Avg3/pyCAAAIIIIAAAggggMAfAh7PmPLHLVcIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggMDFKRAQEOCEZj/++OMqXbq02rU7EexY1Lv19vZW586dNWLECMXGxur555/XV199pSpVqigyMrKol2O+PymQkJCgmTNnqkOHDho8eLAefvhhjR49WjZSZ8iQIerSpYvq1aunwMDAk1Z4bfVkZeQKHD2pAxUIXAQCNmz27016XwQ7uTi38P6G6Tqalnhxbo5dIVCMAh4mALpVpXB1qfLnwsqLams//fST1q1bJxuunrtkZZ0I9Lb1e/bs0dy5c/XOO+/ovffe0+bNm7Umfqfcg08EcOceV9C1/fKj51oPVliZELWsWFv7EuO0K+GQDqceV53AyvqXCUG3geu+Ht66o/41Gln/atkwchuuXtrTV8sObdP+pCMKNSHn11Zv4YSCRx07oO+il6h/WAe1CY7QIRPU/X7ne7Xg4CbN3bfW2UZVM94Ga9vAcRvkvfP4Id3XsJcGh3eWt4ens3ZpE3i+OGZrQdvWrL2rVcknUKOa9nECv5tVqKWeZv1hc99UfFqSM+ZMfWxYuT2TDZEP8PZ1QuA3xe9x5osz5/900yyV9vLVMy0H67oardQ4qKa6ms/E3Q16qrI575g1U/TN9gWyT+fWiC4aXu8qZ54apStqd0Ks42I3sjfhsLqENtTQiK5OsPiLKyaqQ0ik2W9zp58NCE9MT9GwelfqocY3mrVaaM3haDUNqqWNJijcBplbL9tWr1xVVfAt44yzAeWz9qxSd2PYr3Z782yuUUTZUD2x+AvtToyVDXv/R4sBamk+yzakfKvpn5ieqqdbDlRd06+i8VsZG6UDJlg9KSNVg+pc4fjbz8CimM2OYe4/bOi8Dcq3IfY/7FqWu6lQz6NHtebGsJbsl/Z0rFzfMatgQuptkH1a1okvDxhYp5Nj8f3OpepnwvrtMxofNS/PWk3Mc/j2msdUs0ywrq7WzPWyDjZc/e5578nuNX/pGtpIjcrX0L/X/ZC/SbfV7eZ42c/72RR3c5ZrzHNsYs5FQQABBBBAAAEEEECgJAq4mb945v0baUk8JWdCAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgRIj8K9//UtPPPGEE9bZsWPHYj/X9u3b9eSTT2r8+PFq3bq1XnnlFSd8vdgXZoFiEwgZO7TAYMtiW5CJEfgTAv4meHbv0M/+xMjLY0iTr/9qAo9jLo/DckoEcgl4u3vpnoY9Taj2oFy1RX+Zlpamo0ePnvI1btw4rVy5UunpJwdFn3Y33h4q80YfuZXyPG23om4s7emjhIwU17T2CyxyQrNdlUV8UcaEn9vA8T0mwHxfUlyBsxemT4EDc1UG+QSoVkCwEz5/qnVydT/p0oaS+3p6OyHmOY1e7h5Kz8rMuZWHm7uCfcs65/A0Af8mtztPu6tjARfV/CuYkHcTtp94uIDWwlVZpywTm5j7GeYfacPO5/d+Rb1+fN6Esh/J36zTWY9pP1y3mBD6ip/dasL4y+tYWrKOpyefNMeFqJh7wwt6ZOFYLT1UcJj/qfbkaZ7Z6A4jnOD8U/WhHgEEEEAAAQQQQACBS1ng/P6t8lKWYu8IIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggcFEIPPzww1qwYIFuvvlmJ9QzODi4WPcVFhYmGyA6atQo/d///Z+6dOmi6667Ti+//LIaNWpUrGszOQIIIIAAAgggcLYCqamppwxFP11geu62lJQ/gshzr+/n56fAwEDZ8HU3m7B9huLh4aHMzExVrlxZ999/v17xWyqd52B1u8X8odzFHaxu1zxmwrmXxJw+ELswfexcpyuHU47Lvv5sscHnSRmpeYbnDla3DZnZWa6A+IxsE7qenaf7aW92J8aetr0wjdbpTCU1M10P/PahHmveXw/O/9gJdM89prDWexMLDsLPPdf5un6p9a0avXrqWQern6/9sQ4CCCCAAAIIIIAAAhdSgHD1C6nP2ggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCBw1gI2yHPs2LFq0aKFBg4cqFmzZskGdxZ3ad68uf73v/856z366KNq2rSpbr31Vj333HOqXr16cS/P/BdYwMvdQwPrdFL9ctW1N/GwFh7crPjUBJUvFVCiAy9rBlTSqCZ99NKKr12hqufjUTQNCtPm+D1KzkwrluVCfMupXrlQ/bxvnfMMm1UI0+y9qwtcq3etttp1/JBWxEa52m3/sDIhrvvcF8tMkO7OhEPqVaOVvt9pQoQpF52At7unGgXVVKPy1VWjdCXZ4N8t8Xu17NA2XV+jtb7ePv+C77myXzk1Capl9llD2SbEePuxA1oZu13xaYlqWbGOZu5ZVeR79PcspU6VG6htcF09s+wrZ/4L9TPgVIfzcHNXi4q1zxhYfarxZ6o/08+G0p4+6l+7g6yLfSZfR80v8OfUFcbxqqpNdTA5XpO2L9D+pCOupc/1Z0NWUpqmj/6vdvnMPGWAug1XL6j4+/s7weg2HD33q1atWnnuc7flv/b0PBFf99prr+mJJ54oaBmnzsvLS+np6ercubP++te/qlevXnJ3d9ernw5Wlv1QUxAoBoEFBzfJ28NLL7QeoieXfHlSwPqplvQ1P/883Txkfw4m5guaP9WY4q5/sNH1WnV4h6bxu0RxUzM/AggggAACCCCAwCUqQLj6Jfrg2DYCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKXs4AN+pw0aZLatWunxx9/XK+88sp54+jevbuWLVum8ePH68knn1RERITuvfdePfbYY6pQocJ52wcLnT8BXw9v/a/Xc4oxIblvrZ2mUP8gPd1yoGx47hOLPy/R4eo23PmWiC6aEr3ovIWr96jWXOlZmQUGFhfVUx9Wr5uqla7ohKv3C2unjpXrFxiubkPeP+p8rx5ZODZPuPp/ujygWmWCC9xO56mPOeHq9vPyZoeRemjBJ8rMziqwL5XnX6B5hdr60DzToyak/PMtP+vHXcudoOx7G14n+9lLyEi5oOHqNtz3qZYDdE+Dnvp440z9um+94lKPq7EJg//h2n/Ihq6/s/7HYglX727CwJ9rNUTu5ktMcsLVL8TPgFN9Ksp4+Wp45NX6aMOMU3U55/rT/WyoU6ay8wwS0pOdnx/eHp76W+Mbdc0PT5v/Phx1rW0DkW82X8ax5OAW3Vy7ozEdrIEzX9P/9qx0+hTFz4a966Jkvh/CCUSvXbt2oYLRy5Qpo5xgdNdmz+HC/i6WlZX3Z5v9Ahz78vX11ciRI3XPPfcoPDz8HFZhKAJnL/DzvrXacGSXPE2Yv/194kzlprAO6hbayPnsPmv+fR27eY7Wxu0807Bib58Q9VueL2Yo9gVZAAEEEEAAAQQQQACBS0yAcPVL7IGxXQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBA4IdCkSRO9++67+stf/qK2bduqT58+543GBocOGjRI/fv31wcffKAXXnhBH330kf7+97/roYceUkBAwHnbCwsVv8BdJuS4QflqGjDhX66A8XHbftUbHUYoxAQdl+QyNXqxwr68wwl3Ph/nvLfBtUrJTNcnm2YW63LdQhvr/fXTnTW6mkDVGbtOhB7nXtTPs5Qea95PXu55o5q6VDH9d6/Quybgen/SEdeQDiH1nTD11YejnbolMVsV4OXn1N332weuflxcOIH+Ye31/hX3aPKORbp33vtKy8pwNrP00FYnUP2pFgP0YKMbLtgGfTy8nPDu8MAquub7Z/IE+i+O2aKvo+abL3p4VvYLH4qj2H/fe9dso2YVwlzTF/fPgIEmhHz8tnmu9U51YUPlR7cfrjt/eccJwD9Vv3OtP93PhpfbDFXfGS9rvQltDvIJ0D9aDNRtdbvJfm7u/+1DZ+maAZWcL1doP/kR5/6JJZ9rw8B3nbD8nHD1c/3Z4O7nreH/eVbPtBx0rsc9p/E2XD0j48S/Qza03V7Xr19fDz74oAYPHiw/P79zmp/BCJyLQO4vPDjTPPa/6Tn/ftq+qeb3kIuh5P4d42LYD3tAAAEEEEAAAQQQQOBiE3C/2DbEfhBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgcIKDBs2THfeeaduu+02bdq0qbDDiqyfl5eX7rvvPkVFRenRRx/VmDFjFBYW5rynpqYW2TpMdGEFGpevIXc3dwV4++bZyDNLv1J5E7Bb0ktc6vHzcsTIslU1MvLqYg9WD/T2c8Kjf963Th7muXaq3ECz964+6YxPtxyo11ZNOak+MT1Fjy3+XLsSYpWelel6XVe9hb4z4dS5i523TmBlXRnaJHc11xdAoIJPGb3W7nYdT0/WQws+cQWr597Kyyu+0d6kw/LOF6ifu09xXo9q0kctKtbR6NVT8gSr56wZn5aoRxeNlY9n8YSr23WylO28cta078X1M6CT+UICG1BemPJS61v1/c6lOmaeX3GV0/1saBpUSxOjfnOC1e36h1OO66UVXysrO0ttKkW4tuTp5uGE9+dUJGakFrjvkvCzwYar2+Lh4aGbbrpJCxYs0Lp16zRixAiC1XM+ALxfEgL258rRtCTXy37JCwUBBBBAAAEEEEAAAQQufoG8X4d48e+XHSKAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAnkE3nzzTa1evVq9e/fWkiVLVKZMmTzt5+PG399fTzzxhO6++2698sorzrUNWn/66adlA+Bt8Cjl0hWYs2+N+oS10/tX3K0hs0ZrX1KccxgbdPzOuh+c6x7VmqtWmWDZ4O3/bpmr0p4+Ghh+hbzcPXQg6UiesN2IwCoK9i2r3w5s1FVVmyrchG9PiV6kvYlxcjP/tA2OUGsT1jvftC87tM0F5+PhpWurt9RPu5arom8ZM7aZM/dPu5ebgN9sVfQJNO0tTDBylqbsWOyESOcMtmM7Vq6vJiYgONOEAU/YNk/7zb5ySllvf/ULa+8Em3ev2kQNy1XXv83Z7LwdQyKVkJGilbHbne71y1WTDRrOX7JNxYSoec4Y29alSkMnKDo+NVHf7lioI6kJ+YfkuX+21WB9vX1+nrqcmyZBNdUuuJ58PUtp9eEdmrN3TU6T817Fr7x6mrN/smmms18bZm6f0+fmWeSEpP4/e/cdmEWVqGH8TYUkhBIg9N57L1IUsFCk2mhKEUFRAVF3FV3XXteOWEEXLFdQUZcVEBULEHqR3nsNEAIxQPo958CXTUIgoSQk4Tnc5Js5c+aU33wzZu8f71j3a8ycqpnPozHHzXqvUqnAIkoya7TnWg8b3mxL1wrNtPnoPq2P3O32U/5afHBTyl23ba9bt4rNNWD2G2cce2/NDD3VtK+bc5IJrqZcHoG/NeylwvkK6F8rpqa6N1LOJj4pQWMWTDIvU/BKrrb38vXlGqpG4TLmHj3srqO9Vz2lTFCIulVorg/W/qiapk2XCk212wTvT9kyz1ztJAWZ7+zAGteawHYfd2/8tHuF1pnvVbBfgPpWvVqBJij9P+Z7dzwuRiPrddPJ+Fi9a74zZyu/7l2lQyePpTrcPLSaC4TfELlX/cxzZ86+tS6cPaP73nZi7/2elVqofIHi7h6332V7T3iK3U/7DPAcO9s97nlpQZJ51iwK36RO5Ru7++6brWHacmy/O90Gq39x/cNurEHGxz4nZ+5a5uk61WfjYlV0Q7lGGjH3w1T1diej62PnYu/74ybkfMvR/bqxQhNVDC6hacZ86enna2aeDfZ5vMI8e1KWAyciteLQNtnvjadsPrbPs+k+rV8lM97TS/4vVb3dye3PhrZt22r8+PHq1q2bQkNDz1gfFQgggAACCCCAAAIIIIAAAghkpYB3VnZO3wgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQFYL+Pv76+uvv9bRo0c1YMCAVKGwWT122v5DQkJcuPrmzZt14403urD1OnXq6Kuvvrqs80o7T/bPT+DrLWHaZcKSG5mA3z96vKjeVdokd7D2yC63bUOBB1Rvr0ca3ez2bRj5l5v+0JhGt2h4nc6uzoYAP9usvxbd/JqG1u6of101yAWp32iCvFfeOtYFrX90zX0uQH2YOT7zxqdcOLk9ubUJOJ/X82V93H6k7qx5nR6s31Plg4vro3b365P2o9zYz7e4XVeXrqO3Ww/Th6YfT7HhzstuedOFNr+x8nv5evnox65PywYv22IDntf2GaeXWw7U0Fo36MkmffWUCTq3gfGfmPGmdXnChKlX9nTn5meD5Dcc3WuCzrcr2D9Q75rg+VYla7rwaBso/1broQrJF6wfjUtbE+q+xKzZhlOfrdQqXNaFJ/+8+88zmjzf/HY9UK+7C17+xRx/xsxtWucnVMQEZdtya+XWCuv1sp4z7V5vdad6V22rOiHlje9g/dDln269tl20uSbrzPWqYNzs9dpkwtOrmGB726ett2HqtpQMKGLCspvpo3Wz3H5mftlAfBukbYOk05YFBzaoXtEKzjPtMfazT6Bp8apusFWHd5xz0B92LkkO5K9rvkf2XolPTHDfh0ImiHzhTa+pj/mO2WLvkd+7v6iXzL1zT51Ouq/ujWpWvJo+MPffA/W7uzbRJtR7/oH1eqJJHxfybYPVbYmKO6HYxHiVCSqqrSZwvFGxyvL38dWu6EOu3jU6y69VEafWUC6omKZc/3fN6vqMeyHAm63vcs+g0WbsjO5723XVgqU0teMYrYnYpeeXfaWi+YNN+HhT800+Vew9m94z4Fz3uA1rt8+f7zo9pv7V2+ntNsPcyyLuqnW9ux/tcVvsyynWROxUbEK8uff2uuD6U6Oe+XtU/W5abO4t+1xNWTK6PvalC/b5aNc4sl5XvdN2mOqGVHDX70fzfO1uQvFtycyz4Wwvh7DXzwbmp1fsyxusxaLwjVpoftKW3P5sCAwM1JAhQwhWT3th2UcAAQQQQAABBBBAAAEEEMgWAcLVs4WZQRBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAgKwVKly7tAsynT5+u559/PiuHylTfdj7vvfee1q1bpyZNmqh3795q2rSpfvzxx0ydT6M6VUq4AABAAElEQVScJXAiIVbt//O4fjYBusUCCrrg5G9NWK8N7k1ZNkTuSbnrgoC3HjuQXGeDgZ9Y/LmOxh5XWRPI+49Fn+mZpZN126yXlWj+PdLwJt0/9wPXpunXD7qg8nal67rz5+1fpwnrf3bbu6MP64Gw8Xpy8Rf6YM1M9ajYQjb4d9jv4zTkt7F67c/v1KFMfXmZf7Z0Kd9UJQMLa0PkXtfnzF1LVb5AcdUqUs4d/7/Nf+i/OxbL14Si7zt+RG2/f1TNvnlINmT6lRVTXZuUv3ZEheuFZV9r6cHNLhR6eO1OLhj58YWfumZ3m/19xyM0ddt8rTbhyY+Z+qL5C+qF5nek7CbVtg1Dt2W/GT9lsSHWd5jQ+lHzPtJ2M+7KiO0aOPtNF9j+UosBrulXW+eZEPflLiz+w7WzNGLuh7rtp1f0yvKpLpz+dhPwbMue6AjNNY6VC5bUF5t+1297V7nr8PXWMFe//nTo9XPN+5tr87k7J7O/elZqqf9uX5xu8wMnIhVprk/DYpXSPU5l9gjUMAH+tuz4KzxTA9oA8Y/bjXT3xjRzfxw+GaV3Vv+gGTuXuhcY2OBxG9L/6cZfXX9rzXfd3r99fv6XVhzaZu7LU8Hd9uDyQ1s1ecscXWVeQFDQLyB5fBuo/vaq/7r9mkVOz898z9Mr15Sqo1fNCwPsiwvsj31xQ0RMlB5ZMNE1b1mipu6Y/brqTRlh7pfxGd739qT3zUsR5uxfq8UHNykhKVH/3jBb+8x94in2mZbeM+Bc97gNTb93zvuui5ImXPyeP97VmIWTNHLuR+Y5VEQtzIsIbLEB8YdOHnNB9va+9ATGu4NpftUtUt49m1JWZ+b67DXPoX+aZ64tNsS990//0sPzP1Hb78aYezJaL7YcIB8v70w/G1KOb7dbGfP4pAS9u3p62kOyz+7/dP6Hbq3S2gXtp3zhhacxzwaPBJ8IIIAAAggggAACCCCAAAIInL8A4ernb8YZCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIJADBdq0aaM33nhDTz75pGbMmJEjZlilShV9/vnnWrFihWzgeqdOndSuXTvNnz8/R8yPSWRewIYA32JC0O/89W0dOnFM7U14+ZyeL6peSIXMd3K6ZVTsCW2LOuBChW2VDV23oeZbju1PrrOB7ntMiHqFAqHJ/R8zoey2rDEhzp6y6ehet5kymHijqcvn46dSJsjYFhse3nLq33Tw5FFX37pkbVdfxYSMe4od35Yfdixxn55+YxLi3H7KXzbMPMn8s+WJJr1VsWAJF5x8LO6Eq7uvbhfVL1rRBUHbMOgHG/SQ7a9IvgLueHq/bFC1LTZsOGUZXqezO9fTtz1mnWzQem8TvB58Oqj6eHyMCzn2BKTbdm+s/F7xiQlqXbKW3XXFhtrbUHwbJl3IP9Bcv4qas2+N57Duq9PFeVmr8yndTZD2f7YvOuspR80196zxrI04kKUCNjzcFm8Tpp2Zcl2Zhqpuvpf2u5Ky/LLnT/n7+LrQf1tv71Vb7H3nKRtMUH/ZoGKeXff50bpZCvTNp9vM99aWAr753fd3V/Qhtx97+l472/x+N9/Tt1dN08AaHdSv2tV6d810RZvvveeFBLPMCwYSk5JcCLwNXc/ovr/ahLU3Da2W6vtvJ7LMBMF77m+7n94zIKN73J6TZOayzbxcwuPuuTfTuqQcy46XttgQ9YrBJc54NmT2+thngy0rD293n/aXvb8nbpytMuZ5UCH41DM2o2dD8smnN7y9vPRY41vV96dX3XVIe/y3vavdSyrqm7B7O/ZtVdrohrKN0jYzL9vg2XAGChUIIIAAAggggAACCCCAAAIIZELANxNtaIIAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCOQKgfvuu0+LFy9Wv379tGTJEtlw85xQ6tevr2nTpiksLExjxoxRq1atdOONN+rZZ59Vo0Znhq3mhDkzh/QFpm6bLxua+3G7EWpXpp6ebdZfPX98If3G51EbmxB/Ruu4xHgF+eU7oz5lRYxpk7bY82yxQc622PDi8BNH9VijW014e6wLT7b1KUOcbRCyLRkFHbtGp381K15N99TppE83/iobOG2LDSwvZcLLR2+YoJm7lp1umfFHsfwFXRjzyTRh7jUKldHC8I1ndDB//3oTuByqaoVKm/VsOeO4rTgVUB+hYvmDXcD6LZVbqXyB4joRH6tXWg5SicDCzuPpZv205sgu/bpnpXpUaqGxq/6rbhWauT4DThs2MGHxtm5R+KYzQp5blqghf29fzTuwLt152MpoE6BfOijkrMc5kPUCNvC8eWh12ZcKLDcB4hmVGkVOBf5Hx51M1dR+92yx382zFRsobrK3UxU75rKDWzS4xrUab4LWbzbfxylb5ia3+fPwNrddOcVLD5IPnt7Y+dchE6CeqC1H95tQ7lMvW0g0d60tnhDz000zvO/rnn4xxDrz3U9ZMnoGXOg9budti1camIzGsy9l8PH2dvdtynlezPWx/Ww+us911750PY2oe+M5nw32eqUtzzW7XeNWT9fK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IIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgikEJg8ebL69Omj8ePHa8iQISmO5J7N6OhovfPOO3r11VcVExOTHLIeEhKSexZxmWdacuIAnUyIu8yzYPhLJVDQL8AFq3v6izHXNi9c3yDffNoz4N+eZfGZRqDBV6O0Iyo8TS27CPxPwM/bR4HmPkpZjsYeT7mbK7f9vf10b93Oeqpp31w5fyaNAAIIIIAAAggggAACCCCAwKUQ8L4UndAHAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQGwR69+6tf/zjHxo+fLh+//333DDlM+YYFBSkRx55RNu2bdPjjz+uDz/8UBUrVnTbERERZ7SnAoG8LnAs7oRsYLLnJy8Eq+f1a8b6EMgOgbjEhOTnguf5kB3jMgYCCCCAAAIIIIAAAggggAACCGS9AOHqWW/MCAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQgwSeeeYZ9ejRQzfffLO2bNmSg2Z2flMpUKBAcsj6Y489lhyybsPjCVk/P0taI4AAAggggAACCCCAAAIIIIAAAggggAACCOQeAcLVc8+1YqYIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggcAkEvLy8NGnSJFWsWFFdu3bV0aNHL0Gvl68LG7L+6KOPatu2bbIh6++//75bGyHrl++aMDICCCCAAAIIIIAAAggggAACCCCAAAIIIIBA1gkQrp51tvSMAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAjlUICAgQP/5z38UFRWlW2+9VfHx8Tl0ppmflidkffv27RozZowLWa9QoYIeeeQRHThwIPMd0RIBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgRwsQLh6Dr44TA0BBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEsk6gdOnSLmB93rx5euCBB7JuoGzu2Yas23B1G7L+z3/+UxMnTlSlSpU0atQo7dmzJ5tnw3AIIIAAAggggAACCCCAAAIIIIAAAggggAACCFxaAcLVL60nvSGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBALhJo3LixJk2apHfffVfjxo3LRTPPeKo2ZP1vf/ubtm3bppdeeklTp05V5cqVdc8997jg9Yx7oAUCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAjlPgHD1nHdNmBECCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIZKPAzTffrGeffVajRo3SrFmzsnHk7BkqICBAI0eO1JYtWzR27Fi3xmrVqmnw4MHauHFj9kyCURBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQuEQChKtfIki6QQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIHcK/D444+rb9++uvXWW7V69ercu5BzzNzf31/Dhg1zgerjx49XWFiYatWqpX79+mnNmjXnOJNDCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgjkHAHC1XPOtWAmCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIHAZBSZMmKCGDRvqxhtv1L59+y7jTLJ2aF9fXw0cOFDr1q3T559/rlWrVqlevXq66aabtGTJkqwdnN4RQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEELhIAcLVLxKQ0xFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAgbwj4+/vr22+/VUBAgLp166bo6Oi8sbCzrMLb21t9+vTRypUr9c0332jnzp1q1qyZrr/+ev3yyy9nOYtqBBBAAAEEELjcApvn/6nIyMjLPQ3GRwABBBBAAAEEEEAAAQQQQOCyCRCuftnoGRgBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEcppASEiIfvjhBxc03rdvXyUmJua0KV7y+Xh5ealXr15asmSJZs2a5dZ83XXXqXnz5po6deoVYXDJUekQAQQQQACBLBJIOBilz0e+LPs3S926dXX33Xdr4sSJ2rx5cxaNSLcIIIAAAggggAACCCCAAAII5DwBryRTct60mBECCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIHD5BObPn68OHTpo6NChevvtty/fRC7TyIsXL9aLL76o77//XtWqVdPf//533X777fL3979MM7q0w5acOEAnE+Iubaf0hsAlFgjyzac9A/59iXvNvd1t2LBBI0aMUIECBVSoUCH9N3y5orzj5RXoJ6/8ftLpT68AX3kF+JufU5/Kbz7NSyQoCOQVAX9vPw0s01ptT5TSvHnz3I99QUpMTIxCQ0PVqlUrtW7d2v00adIkz/y3O69cP9aBAAIIIIAAAggggAACCCBwaQQIV780jvSCAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAnlM4KuvvlLv3r31xhtvaNSoUXlsdZlbzvr16/XKK6/os88+c4GtDz30kAuct+HGubmUnTRYf8WfzM1LYO5XgEBB/0DtvH3CFbDSzC1x+fLlaty4sWvs4+OjRCUpyWamJ9kf8yvRbqRfvArlV/BLXeXl75t+A2oRyGUCDzboqX826Z0869jYWC1dujQ5bD0sLEzh4eHKly+fmjZt6oLWbei6/SlevHjyeWwggAACCCCAAAIIIIAAAgggkFsFCFfPrVeOeSOAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAlgvYYPExY8bo22+/Vffu3bN8vJw6wO7du/X666/rww8/dEGt999/v0aMGKFixYrl1Cmfc16LD27SnuiIc7bhIAKXW6BsUFE1LV71ck8jx4yfZALUbSj04cOHz2tOXt5e6jt6mG69f/B5nUdjBHKqgH2nQOuStVQsf8FzTnHz5s0ubN0Grc+bN09r16417yFIUvXq1V3IeuvWrV3oes2aNeXlZXulIIAAAggggAACCCCAAAIIIJB7BAhXzz3XipkigAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAwGUQuPvuu/XZZ5/pt99+U7NmzS7DDHLOkBERERo7dqz7OXHihAYPHqzRo0erSpUqOWeSzAQBBPKsgH3m2OdxfHx8hmv09fVV6dKlNWXKFLVo0SLD9jRAIK8LREZGav78+S5o3YatL1q0SMePH1dISIiuuuoqF7TeqlUrNW/eXAEBAXmdg/UhgAACCCCAAAIIIIAAAgjkcgHC1XP5BWT6CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIJC1AjbEt0ePHlq8eLHCwsJUtWrVrB0wF/QeHR2tCRMm6M0339SOHTvUs2dPPfzwwy6cNRdMnykigEAuFZg8ebL69u2rpKSks67Ay8vLHR84cKDeeecdFShQ4KxtOYDAlSxg/75ZsWKFC1u3f9/YwPU9e/bIz89PjRo1kg1ab926tfu0LyqgIIAAAggggAACCCCAAAIIIJCTBAhXz0lXg7kggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQI4UsGHi7du3V0REhAtYDw0NzZHzzO5JJSQk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+7dDYmKi+vfv717aUqxYseTj8w9sUOcfnkreZwOBnCngpVuqtNL4a+7PmdNjVggggAACCCCAAAII5AIBwtVzwUViiggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQcwVsKPj48eM1ffp0dejQIedONAfOLDIyUh9//LHGjRunbdu26frrr9eIESNcCLMNTqUggEDOFkhKStKKFSvc82/GjBlasGCBvLy8XDhyly5d3L1cr169nL0IZocAApddIDY2VkuXLnVh62FhYe7TvpzBz89PcXFxZ52fr6+vAgMD3Qtb7ItabCFc/axcHMhBAl7y0s2Eq+egK8JUEEAAAQQQQAABBHKjAOHqufGqMWcEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQyDECiYmJ6t+/v3744Qf9+uuvatKkSY6ZW26ZiDW04fRjx47VTz/9pEqVKumee+7RnXfeqaJFi+aWZTBPBK4IgaNHj7r71N6zM2fO1L59+1SqVCl16tTJhanblyQUKlToirBgkQggkHUCmzdv1rPPPqvPP/9cCQkJGQ509dVXu5fdHCqYqM4/PJVhexogcDkFCFe/nPqMjQACCCCAAAIIIJBXBAhXzytXknUggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAwGUTiIuLU7du3bRs2TLNnTtX1atXv2xzye0Db9iwQe+++64mTpyomJgY3Xbbbbr33nvVokWL3L405o9ArhVYtWqVewGCDVQPCwtTUlKSWrZsqc6dO7tA9YYNG8rLyyvXro+JI4BAzhSwL6+ZPHlypsLVfX193XNo0IPDNbnyfnn5eufMRTErBIwA4ep8DRBAAAEEEEAAAQQQuHgBwtUv3pAeEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAA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YZV5QmDHjh3uxQDTp0/X7NmzFR0drbp166pHjx4uUL1169by8/PLE2tlEQgggAACCCCAAAIIIIAAAggggAACCKQVIFw9rQj7CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIDAJRbo3r27Jk2apNtvv90FrD/55JOXeAS6Synwww8/6NixYymr0t22AesJCQnq3LmzXnvtNY0ePTrddlQicLkF4uLiNGfOnORA9bVr16pAgQK69tpr3Xe3S5cuKleu3OWeJuMjgAACCCCAAAIIIIAAAggggAACCCCQLQKEq2cLM4MggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAwJUu0LdvX0VHR2vYsGEuYP3BBx+80kmybP2ffPKJvL29lZiYmOEYSUlJro29HitWrNBHH30kf3//DM+jAQJZLbB3717ZFwXMmDFDP//8s6KiolSzZk3ZIPW33npLV199Nd/VrL4I9I8AAggggAACCCCAAAIIIIAAAgggkCMFCFfPkZeFSSGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAXhS46667XECyDfIODAzUPffckxeXeVFrGh02QZ+s//mi+oha9kemgtXTDjJp0iRNiVutfNdVT3uIfQSyXeDElBWK/XWTfGuGyrd7dQXXK6V9xQtogvZqws4J0mfm5yLLsNod9UrLQRfZC6cjgAACCCCAAAIIIIAAAggggAACCCCQvQKEq2evN6MhgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAwBUuMHr0aB0/flz33nuv/Pz8NGTIkCtcJPXy90YfTl1xAXvBT3dyZyUlJkoJSVLiqZ+kBLNvt13dqe3kutNtvEsVvIAROQWBSy+Q/8bayt+rnrz8fC5956d73BsdkWV90zECCCCAAAIIIIAAAggggAACCCCAAAJZJUC4elbJ0i8CCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIHAWgccff1yxsbEaNmyY/P39dccdd5ylJdUXI+Dl7S2Z//MUL88GnwjkAgGvIP9cMEumiAACCCCAAAIIIIAAAggggAACCCCAQPYLEK6e/eaMiAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAII6Omnn3YB64MHD5afn5/69OmDCgIIIIAAAggggMBlEKgXUkFdyjdVkF9+rTi0Vb/vW61ryzTQlC1zL8Nssm/IisGherhBL72w7CvtPR6RLQP7eHmrSfEqWhS+KcvGa1e6ntYd2aUDJyLVumQt7YgK1+7ow+mO17NSS+2MOqhlh7akOl7IP1B3VG+vskFF9eOu5e47kZiU5No0KFpRh09GnbXPVB2xgwACCCCAAAIIIIAAAlkikOKdmlnSP50igAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIInEXgxRdf1MiRI3XHHXfom2++OUsrqhFAAAEEEEAAAQSySqBP1baafuOTOhLzl2bsXHIq+PumV/Vaqzuzasgc02+DopV0e/V2qh1SLlvmVNAvQCPrddPaiF1ZNl4+Hz99ed3D8vf2dWNM6vCAgs246ZWGRSvro2vuk3VIWQr7B+m37i+orgndr1WknL7p+Kh+6vpMcpPVETv1YIMealWiZnIdGwgggAACCCCAAAIIIJC9Aqf+4s/eMRkNAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBA4LTA66+/rri4OPXt21dffvmlbrrpJmwQQAABBBBAAAEEskHABnD/66rB+mZrmD5c96MbClVlygAAQABJREFUcf6BDZq4YbZ+6vasgnzzKTo+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ECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI7AYCeXl58Zvf/CZ++MMfxjnnnBNXXnnlbnDqiA/XrYw/THk2quVXju/2PDoGN9sz/jlr7DbPfkXfE+LS3l+Nq1/9Uzw085WthqYPbtY1Ca6eE52TYPMf9Dlum3NurXFpsr+09G/cuUyXNPB8/cYNsbRgVVaffvZt1CFyNwkRn7Z8QUl7mQk+vpmweEakQdutazaMNjUbbalLVrexKI0jL9pqe3FDOsfoY66PsYvejZsmJOHgKz8obso+a1Wqln3u3ahTmfr0pihZY2slXT8tXeu12lqXT1W/jaWyedKA+KXrVkXNJPy8dNne51F6TOnrVolzWmasWBi3JuH7904bkzyz3Dh/zO2RBv03qFI7Lhj967hu3N9izqrFpYcmYewHZKHqj87e+vuZhtBf1uf4OPfft0VB8n5srdStUiNrmrvJGlvrr54AAQIECBAgQIAAAQIECBAgQIBAeRYQrl6en569EyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4BMERowYEXfeeWdcf/318Y1vfCPWr1//CSPKX3Ol3LyoklepZOO/euuRKNy4MS7ofkQStP7vbYaIp+HhabD6vdNGx9rCj2zScOxNSxpy/Z0eR8VpT98cd056Mi7qcWT0btCuTLecyClzv6Wb1z54N6se2HSPMs1pyHil3Px4ZeGUrD7tlwaX96xfdo1eDdrGB0lQ+IwV75cZX3yzfP2auOSFO7OA75/sc1px9Wf+/EES7J36Pj779WyOTW3e/nBWVj+kWbdPtcaKZJ9pIPlZex4cVUs9u3SSr3XYL1rWaLDd86Uh7nlbeGabTjApCcZvVK1OmertfR5lBpW6Sc89ftH0SMPbF61dHt3qt4nR8ydm992T6/R5zl61KD5Yu6zMezi8Tb9klpz4y7ujS80WMajpniX31fIqxzX9To7vv/S7SJ9rcWlSrW50qN20+Db7bFK9bhZmP3OT8PsyndwQIECAAAECBAgQIECAAAECBAgQqCACm/8LbgU5mGMQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQ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o0ePHjFkyJB48MEHy+EpdvyWNw3l3tnB6ukJlq9fE68snLrNYPW037vL58f4xdM/U7B6On5DUWFMXjo3JiyZ8amD1dPxRcnX6k2C0jcNVk/7LV67oqTf9garp+PWJmHvk5bOic8arJ7OkVpu+gzT+k3L7yY/E/WTEPie9dtu2rTdzyPd564SrN6pTvMsWP2sZ2/d7DwqCBAgQIAAAQIECBAgQIAAAQIECFRUAeHqFfXJOhcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgS2IlC7du145JFH4rTTTovjjjsubrrppq30VE2gYgmkQfHnPTcyztzzoOjTsP12H656fpWsb50qNbZ7zM7u2KpGw7ik59Fx/ujbs4D6nb2e+QkQIECAAIEdI1C0Y6YxCwECBAgQIECAAIHdWiB/tz69wxMgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGA3FcjPz4/bb789OnXqFJdeemm88847cdttt0WlSpV2UxHH3l0ECjZuiO88/9toWaPBdh25dc2GcVmf47O+R7fpH1OWzo17p42J9RsLt2v8zuqUnuO80SN31vTmJUCAQLkU6FK3eXytw36R/m+kQmBXFjiyTb9deXv2RoAAAQIECBAgQGCXF8gpSsouv0sbJECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGdJjBq1Kg4+eSTo2/fvnH//fdHw4YNd9panzTxiU/eGI/Pfv2Tumkn8IUJVMrNi+r5Vcqst6xgdZn78nozPAl2/cOBl5TX7ds3AQIECBAgQIAAAQIECBAgQIDAbiqQu5ue27EJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4GOBI488Ml544YWYNWtW9O/fPyZOnMiGAIGPBdZvLIw0TL30NxwCBAgQIECAAAECBAgQIECAAAECBL48AeHqX569lQkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQILDLCPTo0SNeeeWVaNGiRQwYMCAefvjhXWZvNkKAAAECBAgQIECAAAECBAgQIECAAAECBIoFhKsXS/gkQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAbi7QqFGjePrpp+OEE06Io48+Oq699tooKirazVUcnwABAgQIECBAgAABAgQIECBAgAABAgR2JYH8XWkz9kKAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECX65A5cqV484774w+ffrEd7/73Rg/fnzcfffdUbNmzS93Y1YnQIAAAQIECBAgQIAAAQIECBAgQIAAAQKJQC4FAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECCwqcAFF1wQTz31VDz33HMxYMCAmDZt2qZd3BMgQIAAAQIECBAgQIAAAQIECBAgQIAAgS9cQLj6F05uQQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECJQPgf333z9ee+21qFy5cvTr1y/++c9/lo+N2yUBAgQIECBAgAABAgQIECBAgAABAgQIVFgB4eoV9tE6GAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBD6/QOvWrWPMmDFx1FFHxfDhw+Pqq6+OjRs3fv6JzUCAAAECBAgQIECAAAECBAgQIECAAAECBD6DgHD1z4BmCAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBHYngWrVqsXdd98dt912W1x//fVxxBFHxJIlS3YnAmclQIAAAQIECBAgQIAAAQIECBAgQIAAgV1EQLj6LvIgbIMAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQK7usC5554bo0ePjokTJ8Zee+0VY8eO3dW3bH8ECBAgQIAAAQIECBAgQIAAAQIECBAgUMEEhKtXsAfqOAQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIENiZAv37949x48ZFp06dYuDAgfHLX/5yZy5nbgIECBAgQIAAAQIECBAgQIAAAQIECBAgUEZAuHoZDjcECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg8EkCDRs2jMceeyyuuOKKuPjii+OEE06I5cuXf9Iw7QQIECBAgAABAgQIECBAgAABAgQIECBA4HMLCFf/3IQmIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwO4nkJubG1dddVU89dRTMWbMmOjbt2+8/vrrux+EExMgQIAAAQIECBAgQIAAAQIECBAgQIDAFyogXP0L5bYYAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEKpbAsGHDYvz48dGmTZsYMGBA3HLLLZ/rgPk5eZ9rvMEECGy/QF7yRxIUAgQIECBAgAABAgQIECBAgAABAuVNIKcoKeVt0/ZLgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAruWwMaNG+O6666La665Jr7yla/EXXfdFY0aNfrUm5yxYmGMX/zepx5nAAECn16gd4N20bZW408/0AgCBAgQIECAAAECBAgQIECAAAECX6KAcPUvEd/SBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQqGgCL7zwQpxyyimxbt26uOeee+Kggw6qaEd0HgIECBAgQIAAAQIECBAgQIAAAQIECBD4EgVyv8S1LU2AAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECFUxg4MCBMX78+Bg8eHAccsghcemll0ZBQUEFO6XjECBAgAABAgQIECBAgAABAgQIECBAgMCXJZBTlJQva3HrEiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEDFFbjrrrvi4osvjnbt2sUf/vCH6NGjR8U9rJMRIECAAAECBAgQIECAAAECBAgQIECAwBcikPuFrGIRAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIENjtBL75zW/G+PHjo1atWtGvX7/42c9+FkVFRbudgwMTIECAAAECBAgQIECAAAECBAgQIECAwI4TyEn+odm/NO84TzMRIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwCYChYWFccMNN8SIESNi0KBBcdddd0Xbtm036eWWAAECBAgQIECAAAECBAgQIECAAAECBAh8skDuJ3fRgwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIfHaBvLy8uPzyy+Oll16KRYsWRY8ePWLkyJFRVFT02Sc1kgABAgQIECBAgAABAgQIECBAgAABAgR2SwHh6rvlY3doAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIfPECffv2jbFjx8aFF16YfR988MExc+bML34jViRAgAABAgQIECBAgAABAgQIECBAgACBcisgXL3cPjobJ0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQPkTqFy5clx33VbZzQYAADA2SURBVHXx4osvxvz586NHjx4xcuTIKCoqKn+HsWMCBAgQIECAAAECBAgQIECAAAECBAgQ+MIFhKt/4eQWJECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIF+/frFuHHj4vzzz4+LLroohgwZEpMmTQJDgAABAgQIECBAgAABAgQIECBAgAABAgS2KSBcfZs8GgkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgZwlUqVIlfvrTn8arr74aa9asid69e8e1114b69ev31lLmpcAAQIECBAgQIAAAQIECBAgQIAAAQIEyrlATlFSyvkZbJ8AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEyrlAYWFh3HzzzXH11VdH27Zt45e//GUMGzasnJ/K9gkQIECAAAECBAgQIECAAAECBAgQIEBgRwvk7ugJzUeAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBD6tQF5eXnzve9+LN998M9q3bx8HHHBAnHTSSTF37txPO5X+BAgQIECAAAECBAgQIECAAAECBAgQIFCBBYSrV+CH62gECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAobwJpsPqoUaOy79deey26dOkSN9xwQxQUFGzzKMuWLYvXX399m300EiBAgAABAgQIECBAgAABAgQIECBAgED5FxCuXv6foRMQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgwgkMHz48Jk6cGN///vfjmmuuiZ49e8ajjz661XNeeumlsffee8df/vKXrfbRQIAAAQIECBAgQIAAAQIECBAgQIAAAQLlXyCnKCnl/xhOQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIVVWDmzJlxySWXxAMPPBDDhg2LG264Ifr161dy3Pnz50fr1q1jw4YNkZOTE7///e/jlFNOKWl3QYAAAQIECBAgQIAAAQIECBAgQIAAAQIVRyC34hzFSQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgIgq0adMm7r///nj++eejoKAg+vfvH1/72tdi6tSp2XFvuummkmMXFRXFaaedFvfcc09JnQsCBAgQIECAAAECBAgQIECAAAECBAgQqDgCOck/BBdVnOM4CQECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhUdIGHHnooLrvsspgyZUqcfvrp8ac//SnWrl1b5tg5OTlx5513xje/+c0y9W4IECBAgAABAgQIECBAgAABAgQIECBAoHwLCFcv38/P7gkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQILBbChQWFsbdd98dl1xySaxatSrS+y2VO+64I771rW9tqUkdAQIECBAgQIAAAQIECBAgQIAAAQIECJRDAeHq5fCh2TIBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRKxevTqaN28ey5Yt2ybHyJEj49xzz91mH40ECBAgQIAAAQIECBAgQIAAAQIECBAgUD4EcsvHNu2SAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAiUFbjjjjti5cqVZSu3cHfeeefFr371qy20qCJAgAABAgQIECBAgAABAgQIECBAgACB8iaQU5SU8rZp+yVAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACB3Vtg/fr10apVq3j//fe3G+KWW26Jiy66aLv760iAAAECBAgQIECAAAECBAgQIECAAAECu55A7q63JTsiQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLbFvj73/+eBavn5+dvu2Op1osvvjhuuummUjUuCRAgQIAAAQIECBAgQIAAAQIECBAgQKC8CeQUJaW8bdp+CRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGD3Fpg/f37cfffdMXfu3Jg9e3bMmDEj5s2bF0uWLImNGzeW4FSqVClycnJiw4YNJfXNTt8vVu/fsqSPCwK7okDHOs3iteP8MYBd8dnYEwECBAgQIECAAAECBAgQIECAwJcrIFz9y/W3OgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjsQIHCwsJYsGBBFrqeBq+X/k4D2NMg9rlV1kT1/xm2A1c1FYEdL1Ajv0rMPf3uHT+xGQkQIECAAAECBAgQIECAAAECBAiUc4H8cr5/2ydAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAiUCeXl50aJFi+y7pHKTi6a/Oz3WFq7fpNYtAQIECBAgQIAAAQIECBAgQIAAAQIECJQHgdzysEl7JECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAgHB17wABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAuVCQLh6uXhMNkmAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAgHB17wABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAuVCQLh6uXhMNkmAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAgHB17wABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAuVCQLh6uXhMNkmAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAgHB17wABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAuVCQLh6uXhMNkmAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAgHB17wABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAuVCQLh6uXhMNkmAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAgHB17wABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAuVCQLh6uXhMNkmAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAgHB17wABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAuVCQLh6uXhMNkmAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAgHB17wABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEPgEgUq5eXFa56Hx031Ojwu6HxF7NeoYHWo3jX6NOn3CyPLd3LZW4/jlft+O5tXrf6EH6d2gfVTLq7zT1mxarV4Mbd49m79+lVpx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BAgQIECAAAECBAgQIECgbYHGYPWsx5q6zfmn7d7RFKye9SmGYPVsHXvTT/Ng9azWkcHq2fg7Uqj8a1tWZ5ftapnVlbN/E9dPOD+umnNXvubmLy5NgfbtadkZdXR7dOW8dk1x68SL4xcv/fUTB6u3a3CdCBAgQIAAAQIECBAgQIAAAQIECOynAiX76b5tmwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECgygX+vfy0eWv6fuHniRVFIP+1tVWUVUVYojer0XSztqjFnxvxNy+PhFXOLZUnWQYAAAQIECBAgQIAAAQIECBAgQKBTCJR1il3YBAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINApBJ5+a0EsrF0ZZSUl0bBn9z739I2DJsdJg8ZEoVCIm465MGa+PisWbF6xz/c6usMDy2bH2ndrO3oa4xMgQIAAAQIECBAgQIAAAQIECBDY7wSEq+93R27DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECguAU21G9t9wKfWPVCPLn6xab+O3c3NF1/kReC1b9IfXMTIECAAAECBAgQIECAAAECBAh0ZgHh6p35dO2NAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAh0coFtDfWdfIe2R4AAAQIECBAgQIAAAQIECBAgQIBAc4GS5jeuCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUKwCwtWL9WSsiwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBFgLC1VtwuCFAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoFgFhKsX68lYFwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECLQSEq7fgcEOAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQLEKCFcv1pOxLgIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEWggIV2/B4YYAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgWIVEK5erCdjXQQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQItBAQrt6Cww0BAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAsUqIFy9WE/GuggQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQaCEgXL0FhxsCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBIpVQLh6sZ6MdREgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAhwiUFUo7ZFyDEvg8BUpL/J5+np7GIkCAAAECBAgQIECAAAECBAgQ6DwChb2pdZ7t2AkBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEPh4gbkbl8Saus0f38lTAl+wwODqmji678gveBWmJ0CAAAECBAgQIECAAAECBAgQIFB8AsLVi+9MrIgAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgVYESlqpKREgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKDoBISrF92RWBABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAq0JCFdvTUWNAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGiE/gvBh3U6JMi/0EAAAAASUVORK5CYII=\\n\",\n      \"text/plain\": [\n       \"<IPython.core.display.Image object>\"\n      ]\n     },\n     \"execution_count\": 12,\n     \"metadata\": {\n      \"image/png\": {\n       \"width\": 800\n      }\n     },\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"graph = net_drawer.GetPydotGraphMinimal(\\n\",\n    \"    train_model.net.Proto().op, \\\"mnist\\\", rankdir=\\\"LR\\\", minimal_dependency=True)\\n\",\n    \"display.Image(graph.create_png(), width=800)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, when we run the network, one way is to directly run it from Python. Remember as we are running the network, we can periodically pull blobs from the network - Let's first show how we do this.\\n\",\n    \"\\n\",\n    \"Before that, though, let's reiterate the fact that the ModelHelper class has not executed anything yet. All it does is declare the network, which is basically creating the protocol buffers. For example, we will show a portion of the serialized protocol buffer for the training model's main network and the parameter initialization network.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"name: \\\"mnist_train\\\"\\n\",\n      \"op {\\n\",\n      \"  input: \\\"dbreader_/Users/salex/caffe2_notebooks/tutorial_data/mnist/mnist-train-nchw-lmdb\\\"\\n\",\n      \"  output: \\\"data_uint8\\\"\\n\",\n      \"  output: \\\"label\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"TensorProtosDBInput\\\"\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"batch_size\\\"\\n\",\n      \"    i: 64\\n\",\n      \"  }\\n\",\n      \"}\\n\",\n      \"op {\\n\",\n      \"  input: \\\"data_uint8\\\"\\n\",\n      \"  output: \\\"data\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"Cast\\\"\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"to\\\"\\n\",\n      \"    i: 1\\n\",\n      \"  }\\n\",\n      \"}\\n\",\n      \"op {\\n\",\n      \"  input: \\\"data\\\"\\n\",\n      \"  output: \\\"data\\\"\\n\",\n      \"  name: \\n\",\n      \"...\\n\",\n      \"name: \\\"mnist_train_init\\\"\\n\",\n      \"op {\\n\",\n      \"  output: \\\"dbreader_/Users/salex/caffe2_notebooks/tutorial_data/mnist/mnist-train-nchw-lmdb\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"CreateDB\\\"\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"db_type\\\"\\n\",\n      \"    s: \\\"lmdb\\\"\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"db\\\"\\n\",\n      \"    s: \\\"/Users/salex/caffe2_notebooks/tutorial_data/mnist/mnist-train-nchw-lmdb\\\"\\n\",\n      \"  }\\n\",\n      \"}\\n\",\n      \"op {\\n\",\n      \"  output: \\\"conv1_w\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"XavierFill\\\"\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"shape\\\"\\n\",\n      \"    ints\\n\",\n      \"...\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(str(train_model.net.Proto())[:400] + '\\\\n...')\\n\",\n    \"print(str(train_model.param_init_net.Proto())[:400] + '\\\\n...')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Next we will run the training procedure. Please note that this process will take a while to run. Keep an eye on the asterisk (In [\\\\*]) or other IPython indicators that the code block is still running.\\n\",\n    \"\\n\",\n    \"We perform training by just executing our network many times in a row. Note how during this process we can fetch values of any blobs in the workspace. This allows us to build training plots. \\n\",\n    \"\\n\",\n    \"When using MLP, model accuracy greatly depends on the random initialization of parameters. If your model is staying at about 50% accurate, re-run the notebook, which will start from another random seed and initialize the parameters again.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"<matplotlib.legend.Legend at 0x1167f2d50>\"\n      ]\n     },\n     \"execution_count\": 14,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x114c8ab10>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# The parameter initialization network only needs to be run once.\\n\",\n    \"# Now all the parameter blobs are going to be initialized in the workspace.\\n\",\n    \"workspace.RunNetOnce(train_model.param_init_net)\\n\",\n    \"\\n\",\n    \"# Creating an actual network as a C++ object in memory.\\n\",\n    \"# We need this as its going to be used a lot.\\n\",\n    \"# So we avoid an object every single time it is used.\\n\",\n    \" \\n\",\n    \"# overwrite=True allows you to run this cell several times and avoid errors\\n\",\n    \"workspace.CreateNet(train_model.net, overwrite=True)\\n\",\n    \"\\n\",\n    \"# Set the iterations number and track the accuracy & loss\\n\",\n    \"total_iters = 200\\n\",\n    \"accuracy = np.zeros(total_iters)\\n\",\n    \"loss = np.zeros(total_iters)\\n\",\n    \"\\n\",\n    \"# Now, we will manually run the network for 200 iterations. \\n\",\n    \"for i in range(total_iters):\\n\",\n    \"    workspace.RunNet(train_model.net)\\n\",\n    \"    accuracy[i] = workspace.blobs['accuracy']\\n\",\n    \"    loss[i] = workspace.blobs['loss']\\n\",\n    \"\\n\",\n    \"# After the execution is done, let's plot the values.\\n\",\n    \"pyplot.plot(loss, 'b')\\n\",\n    \"pyplot.plot(accuracy, 'r')\\n\",\n    \"pyplot.legend(('Loss', 'Accuracy'), loc='upper right')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now we can sample some of the data and predictions. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Text(0.5,1,u'Prediction for the first image')\"\n      ]\n     },\n     \"execution_count\": 15,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x116806110>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAAAXcAAAEICAYAAACktLTqAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4xLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvAOZPmwAAFIRJREFUeJzt3X+U5XV93/Hni10RV/AHsraFXXYxkjQbTyI6IhRPtZWcgEbwnFYLZ02lVbbWoKYlsRg8yFGpJ8ZW04a0brA1hY2AxNLVswarxXgwggwiNgtyuq7ArmAYRH7oxuDKu398v+veHWd37uze2Tv7mefjnDlzv9/v536+7/nMva/53s/3O/emqpAkteWwcRcgSRo9w12SGmS4S1KDDHdJapDhLkkNMtwlqUGGu/YqyeoklWRpv/zZJG/cj36OT/KDJEvmocbTkvy/vv/Xjrr/fh/3JDl9RH3tUe/+jumQ+1qb5HPz0bcWvnid+6EtyT3A3wF+AvwQ2AS8rap+MIK+VwPfBp5SVTvnWNObq+rzB1rDEPv6ArCxqv5gRP19HNheVe8eWHcPI/p5RllvkgJOrKotB9qX2uORexteU1VHAi8CXgK8e3qDdFr8fa8CNu/PHXe9IjnIhq53TPWpES0+2RetqvoO8FngBQBJvpjksiRfBnYAz0vyzCQfS/JAku8kef+u6ZIkS5J8KMlDSbYCrx7sv+/vzQPL5ye5K8njSe5M8qIkVwLHA5/upx7eOcP0zrFJNiZ5OMmWJOcP9HlpkmuT/I++381JJmb6eZN8C3jewL6eOkTf1yW5KsljwHnT+lsHrAXe2ff36YHNL0zyjSSPJrkmyRED9/v1JF9P8kiSv0zyy3Oo96djmuS8JF9O8uEkDwOXJnl+kr/o9/tQkmv6tl/qu72j7+ufzbC/85LcNLBcSd7aTws9nuR9SX4uyVeSPNaP++F922cn+UySqSTf72+vGOjrhCRf6vv5fJLLk1w1sP2UfiweSXJHklfMNCaaR1Xl1yH8BdwDnN7fXkl3VPi+fvmLwH3ALwFLgacA1wMfBZ4OPBf4KvCv+vZvAb7Z93M0cCNQwNKB/t7c334d8B26VwoBng+sml5Tv7x6Wj9/AfwRcATwQmAKeGW/7VLgR8CrgCXAB4Cbh/n5h+z7x8Br6Q5snjZDfx8H3j/DPr4KHNuPy13AW/ptLwIeBF7a1/vGvv1Th6x3cEzPA3YCb+t/X08DPgFc3Nd7BPCygfsW8Px9jM15wE3T2m8EntE/Jv4W+ALdH5xnAncCb+zbPgf4J8Ay4Cjgk8D1A319BfgQcDjwMuAx4Kp+23HA9/rf4WHAr/bLy8f9fFlMXx65t+H6JI8AN9GF278f2Pbxqtpc3Zz50cCZwG9V1Q+r6kHgw8A5fdvXAx+pqm1V9TBdsO7Nm4EPVtWt1dlSVffOVmiSlXRh8O+q6kdV9XXgCuA3BprdVFWbquonwJXArwwxBsP2/ZWqur6qnqyqvxmm395/qqr7+3H5NN0fDoDzgY9W1S1V9ZOq+hO60DxlDn0Pur+q/nNV7ezr+zHdVM6x/c900yz3n83vVdVjVbUZ+Cvgc1W1taoepXvVdxJAVX2vqv6sqnZU1ePAZcDLoTtBTvdH/ZKqeqKvaePAPt4AbOp/h09W1f8GJunCXgeJ4d6G11bVs6pqVVW9dVpobRu4vYru6P2B/uXyI3RH8c/ttx87rf2+wnol8K39qPVY4OE+MAb3c9zA8ncHbu8Ajhhy/nmYvrexf6bXdGR/exVw4a7x7Md0ZV/L/phe3zvpXhl9tZ+i+pf72e8ufz1w+29mWD4SIMmyJB9Ncm8/hfUl4Fn9FN6ucd6xl7pXAa+bNiYvA/7eAdauOfCETfsGL4faRndUeUzNfPXLA3TBtMvx++h3G/BzQ+xzuvuBo5McNRDCx9NN8RyoYfqe7fKwuV4+tg24rKoum+P9htp/VX2X7tUBSV4GfD7Jl2r+r5C5EPgF4KVV9d0kLwRup/tD8wDdOC8bCPjBx8024MqqOh+NjUfui0hVPQB8DvgPSZ6R5LD+hNrL+ybXAm9PsiLJs4GL9tHdFcBvJ3lxOs9Psqrf9td087gz1bAN+EvgA0mO6E8+vgnYMIKfbxR977X2vfhj4C1JXtqPw9OTvDrJUXPoY6+SvG7gROb36cL/J/tZ61wcRXck/0iSo4H37NrQT79N0p3wPTzJqcBrBu57FfCaJL+W7iT9EUleMXhCVvPPcF98/jndSbA76cLiOna/XP5j4AbgDuBrwKf21klVfZJuHvZPgcfpTtQe3W/+APDu/iX5b89w93PpTrLeD/xP4D39vOwoHGjfHwPW9LVfP1vjqpqkO7L+Q7rx3MK0q3AO0EuAW5L8gG5e+x1V9e1+26XAn/S1vn6E+wT4CN0J3YeAm4E/n7Z9LXAq3YnS9wPX0L0q3PVH9mzgd+lOaG8Dfgfz5qDyn5gkHbD+Es1vVtV7Zm2sg8K/pJLmLMlL+im9w5KcQXekPusrHR08nlCVtD/+Lt203XOA7cC/rqrbx1uSBjktI0kNclpGkho0tmmZY445plavXj2u3UvSIem22257qKqWz9ZubOG+evVqJicnx7V7STokJZn1bT7AaRlJapLhLkkNMtwlqUGGuyQ1yHCXpAYZ7tIobdgAq1fDYYd13zcc8JtdSvvFtx+QRmXDBli3Dnb0b3F+773dMsDateOrS4uSR+7SqFx88e5g32XHjm69dJAZ7tKo3Hff3NZL88hwl0bl+L18KuHe1kvzyHCXRuWyy2DZsj3XLVvWrZcOMsNdGpW1a2H9eli1CpLu+/r1nkzVWHi1jDRKa9ca5loQPHKXpAYZ7pLUIMNdkhpkuEtSgwx3SWqQ4S5JDTLcJalBhrskNchwl6QGGe6S1CDDXZIaZLhLUoMMd0lqkOEuSQ0y3CWpQYa7JDXIcJekBhnuktQgw12SGmS4S1KDDHdJapDhLkkNMtwlqUFDhXuSM5LcnWRLkotm2H58khuT3J7kG0leNfpSJUnDmjXckywBLgfOBNYA5yZZM63Zu4Frq+ok4Bzgj0ZdqCRpeMMcuZ8MbKmqrVX1BHA1cPa0NgU8o7/9TOD+0ZUoSZqrYcL9OGDbwPL2ft2gS4E3JNkObALeNlNHSdYlmUwyOTU1tR/lSpKGMUy4Z4Z1NW35XODjVbUCeBVwZZKf6buq1lfVRFVNLF++fO7VSpKGMky4bwdWDiyv4GenXd4EXAtQVV8BjgCOGUWBkqS5GybcbwVOTHJCksPpTphunNbmPuCVAEl+kS7cnXeRpDGZNdyraidwAXADcBfdVTGbk7w3yVl9swuB85PcAXwCOK+qpk/dSJIOkqXDNKqqTXQnSgfXXTJw+07gtNGWJknaX/6HqiQ1yHCXpAYZ7pLUIMNdkhpkuEtSgwx3SWqQ4S5JDTLcJalBhrskNchwl6QGGe6S1CDDXZIaZLhLUoMMd0lqkOEuSQ0y3CWpQYa7JDXIcJekBhnuktQgw12SGmS4S1KDDHdJapDhLkkNMtwlqUGGuyQ1yHCXpAYZ7pLUIMNdkhpkuEtSgwx3SWqQ4S5JDTLcJalBhrskNWiocE9yRpK7k2xJctFe2rw+yZ1JNif509GWKUmai6WzNUiyBLgc+FVgO3Brko1VdedAmxOBdwGnVdX3kzx3vgqWJM1umCP3k4EtVbW1qp4ArgbOntbmfODyqvo+QFU9ONoyJUlzMUy4HwdsG1je3q8b9PPAzyf5cpKbk5wxU0dJ1iWZTDI5NTW1fxVLkmY1TLhnhnU1bXkpcCLwCuBc4Iokz/qZO1Wtr6qJqppYvnz5XGuVJA1pmHDfDqwcWF4B3D9Dm/9VVT+uqm8Dd9OFvSRpDIYJ91uBE5OckORw4Bxg47Q21wP/CCDJMXTTNFtHWagkaXizhntV7QQuAG4A7gKurarNSd6b5Ky+2Q3A95LcCdwI/E5VfW++ipYk7Vuqpk+fHxwTExM1OTk5ln1L0qEqyW1VNTFbO/9DVZIaZLhLUoMMd0lqkOEuSQ0y3CWpQYa7JDXIcJekBhnuktQgw12SGmS4S1KDDHdJapDhLkkNMtwlqUGGuyQ1yHCXpAYZ7pLUIMNdkhpkuEtSgwx3SWqQ4S5JDTLcJalBhrskNchwl6QGGe6S1CDDXZIaZLhLUoMMd0lqkOEuSQ0y3CWpQYa7JDXIcJekBhnuktQgw12SGjRUuCc5I8ndSbYkuWgf7f5pkkoyMboSJUlzNWu4J1kCXA6cCawBzk2yZoZ2RwFvB24ZdZGSpLkZ5sj9ZGBLVW2tqieAq4GzZ2j3PuCDwI9GWJ8kaT8ME+7HAdsGlrf3634qyUnAyqr6zL46SrIuyWSSyampqTkXK0kazjDhnhnW1U83JocBHwYunK2jqlpfVRNVNbF8+fLhq5Qkzckw4b4dWDmwvAK4f2D5KOAFwBeT3AOcAmz0pKokjc8w4X4rcGKSE5IcDpwDbNy1saoerapjqmp1Va0GbgbOqqrJealYkjSrWcO9qnYCFwA3AHcB11bV5iTvTXLWfBcoSZq7pcM0qqpNwKZp6y7ZS9tXHHhZkqQD4X+oSlKDDHdJapDhLkkNMtwlqUGGuyQ1yHCXpAYZ7pLUIMNdkhpkuEtSgwx3SWqQ4S5JDTLcJalBhrskNchwl6QGGe6S1CDDXZIaZLhLUoMMd0lqkOEuSQ0y3CWpQYa7JDXIcJekBhnuktQgw12SGmS4S1KDDHdJapDhLkkNMtwlqUGGuyQ1yHCXpAYZ7pLUIMNdkho0VLgnOSPJ3Um2JLlohu3/NsmdSb6R5AtJVo2+VEnSsGYN9yRLgMuBM4E1wLlJ1kxrdjswUVW/DFwHfHDUhUqShjfMkfvJwJaq2lpVTwBXA2cPNqiqG6tqR794M7BitGVKkuZimHA/Dtg2sLy9X7c3bwI+O9OGJOuSTCaZnJqaGr5KSdKcDBPumWFdzdgweQMwAfz+TNuran1VTVTVxPLly4evUpI0J0uHaLMdWDmwvAK4f3qjJKcDFwMvr6q/HU15kqT9McyR+63AiUlOSHI4cA6wcbBBkpOAjwJnVdWDoy9TkjQXs4Z7Ve0ELgBuAO4Crq2qzUnem+SsvtnvA0cCn0zy9SQb99KdJOkgGGZahqraBGyatu6Sgdunj7guSdIB8D9UJalBhrskNchwl6QGGe6S1CDDXZIaZLhLUoMMd0lqkOEuSQ0y3CWpQYa7JDXIcJekBhnuktQgw12SGmS4S1KDDHdJapDhLkkNMtwlqUGGuyQ1yHCXpAYZ7pLUIMNdkhpkuEtSgwx3SWqQ4S5JDTLcJalBhrskNchwl6QGGe6S1CDDXZIaZLhLat+GDbB6NRx2WPd9w4ZxVzTvDHepRYswzPZqwwZYtw7uvRequu/r1jU/Joa72mGgdRZpmO3VxRfDjh17rtuxo1vfMMNdB24hhKqBttsiDbO9uu++ua2fTwfzuVJVs34BZwB3A1uAi2bY/lTgmn77LcDq2fp88YtfXHN21VVVq1ZVJd33q66aex+jYB171rBsWVUXqd3XsmUHv5ZVq/asYdfXqlUHt46FIJl5LJKDX8tCeIwulMfGiJ4rwGQNk9uzNoAlwLeA5wGHA3cAa6a1eSvwX/vb5wDXzNbvnMN9oYSIdexpoTxxFlKgjdtC+Z0slMfoQqljRL+XUYb7qcANA8vvAt41rc0NwKn97aXAQ0D21e+cw32hPGCtY08LJVQXyngsBI2F2UgshFcQI3quDBvuw8y5HwdsG1je3q+bsU1V7QQeBZ4zvaMk65JMJpmcmpoaYtcDFsq8mXXs6fjj57Z+vlx2GSxbtue6Zcu69YvN2rWwfj2sWgVJ9339+m79wbRQHqPQ/ez33ANPPtl9P9hjAQf9uTJMuGeGdbUfbaiq9VU1UVUTy5cvH6a+3RZKiFjHnhZKqC6UQFsoFmGYLXgH+bkyTLhvB1YOLK8A7t9bmyRLgWcCD4+iwJ9aKCFiHXtaSKG6EAJNuy2Ux+hCcbCfK7PN29DNoW8FTmD3CdVfmtbmN9nzhOq1s/Xr1TIN1SHtjY/RkWPIOfd0bfctyauAj9BdOfPfquqyJO/td7IxyRHAlcBJdEfs51TV1n31OTExUZOTk/vx50iSFq8kt1XVxGztlg7TWVVtAjZNW3fJwO0fAa+ba5GSpPnhf6hKUoMMd0lqkOEuSQ0y3CWpQUNdLTMvO06mgHv38+7H0L3FgTqOx54cj90ciz21MB6rqmrW/wIdW7gfiCSTw1wKtFg4HntyPHZzLPa0mMbDaRlJapDhLkkNOlTDff24C1hgHI89OR67ORZ7WjTjcUjOuUuS9u1QPXKXJO2D4S5JDTrkwj3JGUnuTrIlyUXjrmdckqxMcmOSu5JsTvKOcde0ECRZkuT2JJ8Zdy3jluRZSa5L8s3+cXLquGsalyT/pn+e/FWST/TvZNu0QyrckywBLgfOBNYA5yZZM96qxmYncGFV/SJwCvCbi3gsBr0DuGvcRSwQfwD8eVX9feBXWKTjkuQ44O3ARFW9gO6ty88Zb1Xz75AKd+BkYEtVba2qJ4CrgbPHXNNYVNUDVfW1/vbjdE/c6Z9tu6gkWQG8Grhi3LWMW5JnAP8Q+BhAVT1RVY+Mt6qxWgo8rf+kuGX87KfJNedQC/dhPqx70Umymu6DUm4ZbyVj9xHgncCT4y5kAXgeMAX8936a6ookTx93UeNQVd8BPgTcBzwAPFpVnxtvVfPvUAv3oT6IezFJciTwZ8BvVdVj465nXJL8OvBgVd027loWiKXAi4D/UlUnAT8EFuU5qiTPpnuFfwJwLPD0JG8Yb1Xz71AL92E+rHvRSPIUumDfUFWfGnc9Y3YacFaSe+im6/5xkqvGW9JYbQe2V9WuV3PX0YX9YnQ68O2qmqqqHwOfAv7BmGuad4dauN8KnJjkhCSH050U2TjmmsYiSejmU++qqv847nrGrareVVUrqmo13ePi/1RV80dne1NV3wW2JfmFftUrgTvHWNI43QeckmRZ/7x5JYvg5PJQn6G6UFTVziQXADew+8O6N4+5rHE5DfgN4P8m+Xq/7nf7z7uVAN4GbOgPhLYC/2LM9YxFVd2S5Drga3RXmd3OIngbAt9+QJIadKhNy0iShmC4S1KDDHdJapDhLkkNMtwlqUGGuyQ1yHCXpAb9f2yxne4a/oXYAAAAAElFTkSuQmCC\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x11678c810>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Let's look at some of the data.\\n\",\n    \"pyplot.figure()\\n\",\n    \"data = workspace.FetchBlob('data')\\n\",\n    \"_ = visualize.NCHW.ShowMultiple(data)\\n\",\n    \"pyplot.figure()\\n\",\n    \"softmax = workspace.FetchBlob('softmax')\\n\",\n    \"_ = pyplot.plot(softmax[0], 'ro')\\n\",\n    \"pyplot.title('Prediction for the first image')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"For convolutional models we can also see how they \\\"think\\\", i.e. which features they come up with. Instead of fetching learned weights, which can make less sense to a human, we fetch results of convolving those weights over the input. Note that if this code is rerun after the evaluation phase, the last mini-batch will change, since evaluation and training share the same workspace.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 16,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAAAQUAAAD8CAYAAAB+fLH0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4xLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvAOZPmwAAIABJREFUeJzsndlznGd23n+97xt6AdBAA42NILiKi6TxaDz2aLE9k8q4PM5FqlKVSlX8p+QvSPnGF8mNb5xKyqnIqsnMZCJlNFpISaQokiCxEPva+75vueg6RwCHCwBybMrGczMaCUD3933vd97znvOc5zH0ej1OcYpTnEJg/Kf+Aqc4xSleLZwGhVOc4hSHcBoUTnGKUxzCaVA4xSlOcQinQeEUpzjFIZwGhVOc4hSHcBoUTnGKUxzCaVA4xSlOcQinQeEUpzjFIZj/qb8AwPvvv/9MWmWz2WR3d5d6vU40GmVubg6n00kymaRUKtHtdo/0OalUinA4/FK+86v2ef/YnwU88/OazSbtdptSqUS5XAag0+ngcrkIBAL4fD79306nQz6fp1gs0m63n/h5x722TqdDoVAglUrRaDTw+/2cO3cOgGg0itlsJpVKkclkDq2fV32N5PN5kskkdrudmZkZYrEY1WqVVCpFq9V65u/+9Kc/NRzlM16JoPA8VCoVVldX8Xg8eL1eCoUC2WwWh8OBy+WiUqkAHDk4nOL3g16vR6VSIZPJkMlkcDqd2Gw2zOb+MguFQhgMBtLptL6wfr8fj8eDy+XC4/FQKpUAnhgcnod2u02hUCCdTtNoNOh2u3g8HkZHRzEYDHQ6HQDS6TT1eh2TyYTZbKbZbL68m/B7RK/XY39/n52dHS5evIjP5yOVStHpdDCZTLTbbV7G2MJ3IiiUSiXsdjsTExOUy2Xu3r2L2+1mamqKoaEhjEaj/tx3PTD0ej0ajQbZbJZCoYDBYGB0dJTZ2Vn8fj/QD5KJRIJarfZP/G2/Ra/Xo1Qqsby8TDabZXBwkHg8jtlsxul0AmC1WslkMtRqNZaXl9na2iIcDnP58mWGhoYYGBjAZDIBxwsK7XabfD5PIpEgm83icrmIx+P6uZ1Oh/39fTY2NgAIBoOYTCaCwaCune8C2u025XKZYDCI3W4nnU7r9TqdTgwGw7+coJDP58nn88RiMQASiQSlUok7d+7gdrsJhUIAeDyeEz3kVCpFKpUiEolw5swZIpEI+/v75PN5Go0GAK1Wi0ajgdFoZGBgAL/fj8VioVgsUq/XX/gau90u5XKZVCpFNpul0+kwNDREKBTCbrdTqVQoFov68xaLhXa7/dyU8UnodDoUi0WKxSLdbpdAIIDFYgHAaDRis9kYGhrCbrfTbDYpl8u0Wi1ardZTg261WmV5eZlUKsUbb7zBlStX6HQ63L9/n/X1dQByuRy1Wo1SqaTZXb1eZ2tri0ajQaPR0BfZYHh2pttqtSgUCkB/PeRyOVwuF5cvXyYUClEul9ne3iaZTOp9HRoaAvrrpF6vs7e3Rzwex+12H/seHgcSMC0WCw6HA6/XC6Br66hot9skk0nC4TC9Xo96vU6xWCSZTDI0NITH43nufTsKvjNBwW63EwgE2NraotfrUavVyGQyDAwMaFA4yQ1JpVJ88803tNttrl69Sq1W45NPPsFut2M0GvX87PV6sdlsdLtdJiYmsFqtFAqFlxKZW60WqVSKjY0NarUaExMTjI2NYTKZKBaLrK2tkclkNA03m83Y7XbMZrPurEdBpVIhl8tpsPP5fEQiEaxWKzs7O/q3w+Ewq6urFAoFYrEYsVgMk8lEJpPR9P5xNBoNzGYzP/jBD5idnSWZTHLz5k1WV1f1Z9rtNiaTCYPBQLlcJpFI4Pf7icfjpFIp7t69q+d+n8/3zPu1tbXF5uam/rtz584RjUapVqtsb2/TarUwGo2sr69z69Ytut0u4+Pj+l1zuRzj4+P6XJ+WdUnQLxaLmEwmrFYrHo8Hh8PxzPXmcDgIBoN4vV5SqRRLS0uYTCYuXLiggS8UCrG9vf3Uv/E4crkc9Xodl8tFp9PBZrORTqfZ29uj0+kwNTWF1Wo98t97Gl75oFCr1Uin05w5cwav18vS0hLz8/OMj48TDocJBAK6W/Z6vWMHhpWVFQwGA9euXaNQKHDz5k0MBgM2mw2n08ng4CDQ38mz2aym95ubm2SzWZxOp76sx4UElGQyyfLyMkajkevXrzM4OEg6nSadTlOpVKjVajidTubn5/Xnz507x9zc3JGCQq/XI5VKsbq6Sq1WY2pqisnJSYLBIK1Wi93dXT1v93o9lpaW2N/fJ5PJcP36dXK5HHa7HY/H89TP8Hg8TExMMD09zd7eHj//+c/J5/OYzWYN2mazGZfLRblcZmdnB4/Hww9/+EOi0Shff/013W5Xg87TgkKlUmF7e5uVlRVGRkYAeO211zSwyv1YX19ndXWVmzdv0mw2icfjujPv7u5it9txu91kMhmi0ShGo/FQFiRZ2eLior6AwWBQv79sFk+Dy+XSl3h7exuLxcIPfvADZmZmWFpaAuD27dvPfnCPIZ1OYzQa8fl8FAoFnE4ne3t7AC9lcxJ8dw5UpzjFKf5R8MpnCvl8nl6vx+zsLE6nk0KhgNlsptVqMTIygs1m0yh5kmjZarVwOp00Gg12dnaw2+10u11tf8oOWiwWWV5eJp/PYzAYyOfzWCwWRkZG9Ix4HHS7Xd1tFhcXMZvNXL9+nUgkoufhjz76iGw2y9WrVw/VSyKRiH7Po1xfOp3mwYMHOBwOfvzjHzMzM8POzg6bm5tsbW3pkUL+dq/XY2hoSNt5e3t7mM1mAoHAU6+13W5jNBpJp9M8evSIwcFBzbJsNhsATqcTp9PJ5uYmwWCQs2fP8u6773L37l0ymQxDQ0PP7ATUajVWVlbY3d1ldHSUS5cuAf26RK1Ww2Qysby8zK1bt6hUKkQiEQYHB4nFYkxPT2sNYnl5mZ2dHf1eU1NTGI1GrXMAejTJZDJMTEwQj8exWq1UKhXa7TYWi4V6vU6n06HVaunaK5fLmu3UajXK5TIDAwNcu3aNubk5Hj16xP/5P/8H6BfG5+bmnvsMBVLPqtfrZDIZ7byFw2HMZvNLK7K/8kFBCjTDw8Ok02m63S5utxu3283o6OihQHCSIqPT6eTy5ctYrVY+/vhjms0me3t7pFKpQxVwo9HIp59+Srfb5c033ySRSOByuQ4Vjo6DdDqtx4F2u80PfvADpqamyOfzfPzxx/zn//yfabVa/Kt/9a+IRCLU63UtiIVCIYxGowasJ0EWiJy9BwcH+elPf4rf7+fWrVvcvHmT3d1dLSxKobFYLNJsNjEajdjtds6cOcPm5ibLy8sMDw9z/vz5J36exWLRQqQ8n1wuRyAQwG63A/3gkMvlSCQSjI2N8Vd/9Vd0u11++9vfAhz62Sdhc3OT3d1dBgcHmZyc1GNbuVym0+nw4Ycfsri4yNDQEJcvXyYQCFCv17lw4QJTU1PcuHED6AdKl8tFvV6nXq/r/Tl4Hs/lckD/WBSJRCgWi7jdbj3W1et1kskkTqeTcDisaXy73SYcDpNKpQ6l9vV6nY8//piVlRXW1tYAmJ6efuq1HoQcjxOJBHa7nVarxfj4OEajEZPJRK1Wo9vtnvgY+zhe2aAgFX3ZVaLRKLdv3yaRSFCpVGg2m0QiEWZmZrSq3Ov1tFNx1JZWLBYjEomwu7tLIBDgzp07ZDIZXQjyQMxmM/l8nrGxMQKBAMViEYPBcKK2YKPRYGVlRQPa9PQ0ExMT5HI5PvroI/7mb/4Gt9vNv/23/5bh4WFMJpPuhtB/4V0u1zOLSslkEoClpSUuXLjAe++9h8vl4r//9//O/fv3qdVqhMNh3G63Li7on4V7vZ5+hsFg0LaX1Wp9aiAym814PB7a7TZerxeTyYTT6dS/Af2As7q6itVq5c/+7M8wGAz84he/IJFIEIvF8Hq9Tw2wlUqFra0tnE4n8Xgch8OhzyaZTPLJJ5+QzWaZm5tjdHQUj8eDz+fj/PnznDt3DofDoS+NxWIhEokogWlhYQGn03loU5GMJRwOk8vlcDgc7O/va1FaulEOh4O9vT0laA0ODtLtdllaWtLsJx6PAzA/P8+dO3cIBALAtxnU8yBrWQJus9nEYrGQSCRoNBoMDAzgdrv/+QeFTCYD9HeB0dFRdnd3+eCDD1hdXWViYoK5uTnC4TD7+/uk02mg3yHw+/2EQiFyudyRWj7tdpu9vT2SySQmk4lIJILP5yMUClGtVrVIlkgktJg2OTnJ3bt3T0x6SafTlEolXRxjY2MUCgV+85vf8D/+x//A7XZrhlCpVLh27Rr3798/dD3P6rE3Gg2t+vv9ft5991263S5/+7d/y9raGsPDw/rSCxdiYmICgNnZWdxuN8lkks3NTRwOB9FoFI/Ho0eMp8FgMGAwGHS3l+xDvvfm5iblcpnx8XEuXbrEV199xf3797HZbAwODuJ2u3E4HE+9Z61Wi1gsptV36ZhIhnfx4kUGBwfxeDwEAgEtAo+OjpJKpfSliUajtNttRkZGSKVSSqSSwiWgQVK6BzabjcXFRQqFAhcuXMDtdlMoFAiFQtTrdQ18UvDs9XpMTU1x8eJFRkZGyOfzWliMRqNAPwAfB1IMbbfbmM1mKpUKRqORQCBwrC7U8/DKBoVqtQqA3W7H6/Vy+/ZtqtUqP/zhD7l69Soul4sHDx5w584drRSfPXuWc+fOMTIygtVqfWZfXTA8PEyr1SISiVCtVrU3LwtPKu6NRoPJyUneeustBgYGcLlcpNNpgsHgsa6r0WiwtbVFtVrl4sWLQH8h3b59m/fff598Ps+Pf/xjrl69ysbGBsPDw2SzWe7evauLaHh4mE6n89SgUC6XNWBdunQJp9PJ+++/TyKRIBQK4fF4MJvN9Ho9CoUCPp+PN954A+jvXltbW3pPut0uwWBQs5LjtrykVgOwsbHB0NAQf/7nf06xWOTTTz8lnU4zMjKi9OenLe5sNkuj0dAOQKlU4u7du0A/A7lw4QLDw8OEQiE91hkMBm3rttttDXyyuwNKiEskEoc6HtI2rFarlEol/H4/vV6PsbExvF4vwWCQkZER/b4SzEqlEqVSiVgsxoULF5iZmcFsNrO+vk4qleLq1av6OYFAQNf5syAZYqVS0UzEZrMd6n4969h1XLyyQUF2mdHRUd21r1y5QrPZ5H//7//NwMAARqMRv99/aMdeX1/X1Fh2rmfBYDDoQrdYLAQCAS0kHYTD4SAUCjE4OEiz2aTb7eJ0Oo/9MDqdDvV6/VBR7ebNm+zv71Mul/mjP/oj/H4/6+vr+P1+3G43X375JalUSvvsTqfzmUXVWq2mCykUCrG+vs729jbDw8MYjUZN79fW1rDb7Vy4cEEzohs3bpBOpxkaGsJiseh1GgwGfD7fseo2rVaLnZ0dJS8NDg7yk5/8BKfTya9//Wvu3btHOBwmGAzi9/ufeS8TiQTBYBCfz0etVtP6AsCZM2cYGRnRv3MwjfZ4PIeOAvIMJAu6cuWKHj0OcjDkGNNut3E6nUoec7lctFotZmZmaLfb+lLL5lOv12k2m8zMzPD6669jsVjY3Nxkfn6ekZGRQ4Vpp9N5pKAg99xqtWI0GqnX60r2krUrQexl4JUNClJUk3S+VquxuLjI5uYmXq8Xt9uN3W7XIgv0X2ohr8icxHHoshL1Hw8I0E99/X4/RqORvb09KpWKBqbjwOFwYLFY8Pv9ekSSoubo6CjNZpP5+XkuXbqE3+9nZWWFxcVFxsbGDqWdT/qOgmKxqC9GPp/n/v377O3tUavVGBoawuFw0O128fl8TExMaIETYGdnR6vwg4ODSjaC4xVyG40G29vbrK6uasB55513mJqa4qOPPuLDDz/EYrEQi8V+p/bwJLTbbXw+n1b/t7e3tZYku/fjAQE4VEeQf3a5XDSbTcxmM5OTk9y5c4dUKkWtVtMdf3h4GPiWOi/zGm63G4PBQLFYxGazKbVYCpNLS0s0Gg1ef/11RkZGWFhY4MaNG7TbbWZnZw/xPY7KqZGuSbVaxWAw4HK5tOsmz+Rl0rVf2aAgi95sNpPL5UilUjidTubm5vB4PLjdbrrdLvv7+7oTjIyM0Gw2SSaTrK2tMTEx8dIiqMFg0BrAzs4O3W6Xbrf71DPw09BqtfD7/USjURKJBNAn6ty7d49MJsPKygqXL18mHA6TTqf5+uuvcTqdnDlzRq/F6/U+c0E5nc5D5+OHDx+ytbVFKpVSwk4kElHSzo0bNzSlXVtbo9PpYDAY9MU4LprNJuvr66ytrTE4OMif/umfAjA+Ps6vfvUrfv3rX9Ptdjl37hxut/vQzMPTIEe7breLyWSiXC7rLIjdbn9iQDgIg8GghUnJMIQabLFY6HQ6VKtVfZ5yfz0eD5VKRWdvrFarBtqZmRmsViu9Xu9Q92FsbIxwOMzKygqrq6tsb29z/vx5DVzHPYLJ5mE2m3E4HDQaDS0GS9bwMslLr2xQkIcj7bGBgQHMZjP1eh2Hw4Hb7cZms1Eul/Umv/HGG5oql8tlrfq+CKRIlsvlmJycVN688NmPCzmi+P1+3ZGnpqb49NNPmZ+fJxaLcfHiRZrNJo8ePaLX63Hx4kX8fv+hl+BZ8Hq9DAwMAP2U87XXXsNkMrG/v69tNKfTyb1797hx4wYmk4m3335br/NgNnHcApak9o8ePWJiYoIf//jH+l0++ugjPvroIyqVCrOzszo+fZTAbTQaMRqN1Go15QbIjuv3+596pOr1erRaLbLZLPv7+0C/W3Hp0iWuXbtGpVKhUqlQrVYPzbBI/Safz2uNIhqNUqlU+Oabb1hfX8fpdDI+Pk4+n1fOidVqZWZmhmazSSaTYXV1lYGBAS2AHrd93Ww29Sjo9Xr1PgBKeTabzS9l5kHwygaFg+d8SZOkDWWz2bBarWSzWbLZLDMzM0A/U1hcXKRYLOLz+U40fvs4JK3u9XqEw2HC4TBbW1ssLy8f2r2PCoPBgMPhoFwuazYksxzxeJy//Mu/JBKJkEwm2d7eZmpqSs/SRx3cMZlMh/62BCGpwZRKJT766CO++uor9vb2ePfddzXg2Gw2Wq3Wie5dq9XSDCEWi/HjH/+YYDCoZJ3PP/+ccrnMzMwMfr+fgYEBXejPg9QcCoUCyWSSQqGg7VE5Tko1vt1u6xhxtVrV7pK86NevX9dMrNFokEqlDr1scg/lczudDj6fj3K5jM1mo9ls4vf7tTW5sbGh6+TatWtau9nY2GBjY4OzZ89qADxumt9sNnXzGR4eVpJWPp+n2+1isVgOHfFeBk5pzqc4xSkO4ZXNFASP977lWCG043a7zTvvvAP0dzkZNnneFNtRcLCdZjQamZubw2KxsLu7i8lkOlGhEfo720FqbDKZJBAIcOXKFV5//XWq1SrffPMNHo+HaDSKz+c7VuXf5XJpVbter9Nut4lEIni9XorFIru7u7hcLtrtNn6/n7Nnz2pmZjabqVarx57A7Ha77O7usr6+TjQa5b333mNgYIBf/epX/OY3vwH6mcT09LRODx41S4B+MbHb7ZLP51VrQroatVqNq1ev6vMQqnGv16PZbGK323nzzTc1dV9dXWV/f1/v0+Li4qF7cBAmkwm73U6j0dAjy8zMjLZPq9Uq5XKZSCQC9OtDxWKRbDbL4uIiw8PDDA8P6zTmcWG1WjXDsdlspFIpxsbGyGQy9Ho9zGYzVqv1X8bx4SA6nQ6ZTAa73Y7P58Nms/Hw4UPy+TzvvvsuV69eBeCDDz7g4cOHjI2N6Xjri0CEO6Dfzx4ZGeHhw4d6XnY4HCfqD5tMJkwmkxZIK5UKMzMz/OhHP8JqtWphdWpqCo/H89wi2uMQdiGgtFg5dpnNZgYGBlQlqdFo6FkZ+seNSCRybI2BXC7H6uoqDoeD73//+8RiMT788EM++eQTvU45NgidWdJvOR56vd5Dx7FarabFQXnZDAYD8Xic+fl5VlZWgP4GMTQ0RKFQULar2+3G5XIpXTuXy7G1taX3JxAIsLe3xy9+8Qt6vZ6e+Z8Ej8dDt9vFarXS7XaZmpo6FDBlTga+ndXZ3NzEaDQyMTGB3W4/MY+g2WxqraNSqRAIBIjH43z22WdUKhWl2f+L6D4chJxVL1y4APRHTovFIj/5yU94++23uXPnDgD/7b/9NyXBuN3uF2Z51et1je5nz54ll8vx5ZdfYjQaiUQiqnhzEkibC/oPfm5ujmAwSKVS0R56MBjE4/GcaIeRncNqtWK1WnVRSgu31Wqxt7eH2+0mGAxqDUIk044j3lIsFrUVd+XKFWZmZvj000/5+OOPWV9fZ2pqCkD1CESTUboonU5HORFCF3a5XIyOjurgmMvlotFoEI/HyeVyjIyMqJJSLpdjZWVFKdoiA5fP5ymVSto5kmus1Wr8/d//PVtbW3g8Ht566y08Hg8ej+eJLFghQZlMJiUOwbe0+oNzB+VymUAgwPb2Nl6vVzsjJ9U5KJfLh1rEMqQnLXIJsP/iMgWj0UgwGMTpdPLFF19w9+5d/uqv/oq3336bzz77jJ///OdA/+HNzMxohf9Fo2c6ndY0XApx0v2QjshJ0Wg0tBre7XaJRCI0Gg2q1Sq7u7uEw2GsVqsWCF8UEiAPZgCtVotoNMr09DQffvgh0H9h3G73M0VOHkcqlcJgMDA7O0ssFuNXv/oVv/3tb3n48CEzMzMaFIRvAighqFar6YI++M8+n49sNqsvk+gYhEIhWq0WY2Nj+gJLMVEEULa3twmHw9hsNjweD8Vika2tLS0GZjIZbDYbMzMzxONxPZ7JtOyz8Piz6Ha72h6Ue9put6nVaoRCIaxW67Hu5eOQ4wv0i+75fJ5qtUoikVBRldHRURwOB8Vi8dhqTk/CdyIomM1mfWihUIif/exnNJtN/vqv/5qFhQVd8NPT01gsFnw+3wsr0AgdWYLB5OQke3t75HI5LBYLVqv1hTgQxWJRSSlDQ0NKitnd3SWdTnP27Fltu/6+IMIhpVJJj0kOh+PQ1ORR4PP5GB0dZWhoiMXFRVZWViiXy0xNTXHmzJlDL4XVasXr9VKtVmk0GlQqFcxmM+12G6vVqkFYBFhtNhs+nw+DwYDb7aZcLitvQ/5uJpMhm83qAFu321UG4srKCoVCAavVqgHx/PnzuFwu3G63Dk55vd4TDxQd3KWbzSYbGxuk02nd2YPBoGYUx4XZbNbu2rlz5yiVSty+fVtVoB0OB7lcjo2NDVwu10uhO38ngoJEY7fbrZLWH3/8sQ7SSCFGdjghGb0IEokEnU5Hx1sjkQg3b94kl8sxOjp6onHpgxDaMPTTwkqlQqvVYmlpSRfwSTUnj4J6vU46neZ73/uepseAUryPk44KbySVSul8v0zytdvtQ8NCMu0p/77RaNBsNrXucJC2K0VDuU9CMS6VShiNRkZHR3/ndwTS5vV6vUqBlhamx+PRLMxms72QRqP8/sHjQzabxe/3Mzg4qPyF4xZuBQ6HQ7VJpdAIHJqrSKVSOByOlyLFBt+RoGA2m3G73czPz6v23vj4OK1WS2W6ARUBeRkv0v7+Pr1eT/UDMpkMW1tb+sK+KFPS4/EwNjYG9DMFs9nMgwcP+Pjjj7l+/boSZn5fEL8DoYrLPet0Olit1mPtmq1Wi2q1Si6Xw2q1UiwW9VxvtVo1pW2321rPkED/eO1CgoN0Rg7ufDKnMjAwcIg4FggEcDgc2nGA/svU6XQwm80YjUacTqdmPzIG/jJ2VYfDwfDwsB4lpX6Rz+eZn5+nUCjwp3/6p0rJPi56vZ4O/ElXRfQ1ZH34fD5MJpMWk18U34mgAGilX+io0jo7WNkVbYCXAZ/Px5/8yZ/oi/vLX/6STz75hNHRUUwm0wu/sK1WSxeSjDBvb28zOzvL7Ozsc/USXhQ2m43XX3+dixcvsrKywoMHD4D+sSYYDB4rU5D0XrKnl3nkMRgMv5N2GwyGQ8rFdrtdhUweZzVKULDZbC91vFjQ6/UOsU2laJpIJNjY2GB2dla7FifNFGR9S5BrNptMTk5is9kOBfOXtfYNL5MzfVL8l//yX/7pv8QpTvHPHP/xP/7H745D1ElturrdrvoIQL+POzQ0RCwWw+/3qwqT7DSPW3R1Oh3u3LmjYhnhcBiTyUSlUsHpdOrwCfSrwLFYTKvaQlBpNptPHUY5iQWZiMZEIhHOnTtHOBxWX4NnBfDHP0tSzkQiQbVaJRAIEIvFMBqNVKtVjEaj6g+ImYr83vMozk+zjctkMiSTSVUDmpycZHBwUI9atVqNYrGoNYWjbkgvYuXW6/W4d+8ehUJBj4LT09O6Jmq12qGK/eOf1Wq1SCaTmq4PDg6qwInJZMLn8x0isckcxbPWxcu6tt8XXomgcFx0u11tQ6XTaT0rDg0NqZa/jLJardanmrWIQOlrr72G3W7n1q1bqlgUDocxGo36YIXVtrW1pao9Ikyay+WoVqsvPKnWbDZZXl4G4L333sPj8bC4uEitVjs2a026CRsbG3i9Xk1p5X6FQqHf0ToAdMy5Wq3q8FGv13vutRWLRR48eEClUmF6ehqn00k2mz3U1pUWoRTnhKUpZ+WTGNs8DwfFU+UaxX6tWq3qGPLTID8r3I1SqaRFQ5/PR6/XU9UugIGBAYLBIGaz+fd6Xb9PnM4+nOIUpziE72SmkEql1DVpbm5O24bVapXV1VVsNhv1el0Vhp7msSfmJDJ953A4VJNB1JJld87lcuzv7+vc/PXr1zl//vzvWMi9iMy2ZDRiffbJJ5/8jnbgUSE718TEBKOjo1QqFUqlkhan1tfX2dra0vskY+YyuTg+Pk4oFKJWq6kU2rO+96NHj6hUKrz55ptcvXqVZDLJ/Pw8n3/+OdlsFoB4PM7w8LBObYrU3sDAAKOjo8o1gJdnbiLPbXx8nNnZWQC2t7e5c+cOnU6HixcvPjNTsFqthEIhLfCJbJvNZmNzc5NMJkOz2VRZvmAwSDweZ2BgQDOiUqn0UiZ2/7HwnQsKuVyO+fl5er0e3//+9xkYGNCUf3NzU1V4stks6+vrTExMPLUr0el01FdRUkmxRhNlHjmaGAwG9XUsFAp89dVXbG5ucuXKFf0Mae/ByRa1tO++SgbeAAAgAElEQVSE6ry1taW9+eN2O+Qc73Q6dcx3bW2N5eVlfD4fDoeDSCSias23bt3SeyKV7VqtRrVafW6gSyQSJJNJ/H4/Z86cod1us7Ozw4MHD2g0Gpq2i82ewWBQr86xsTGmpqaUQCScgXK5/FICgxyXRPcAYGFhgZWVFaampp6b2otIqihVO51Otra22Nvb0zWSzWZ59OgR0N9oLl68yOjoKOFwWOUEjyok/CrgOxMURFhDpNHPnz9Pr9dT9hygIqf7+/vcuXMHl8ulVudPgslkwu/302q1MBgMNJtNFes42E8HNHPweDy689VqNQqFApubm4RCoUNz7SfhootSb71ep9VqYbfb2dvbo9Fo4HK5jtXSkt1PxFx2dnZYW1ujXq/j8/k0IMo8vmQj5XKZRqPBrVu3sFqtnDlzhmAwqNbujyObzbKysoLRaOSdd95hdHSUX//61/z93/89lUqFqakpJQ3JQJuIkoi5rMy1TE9Pa/A7yG94EeRyOR0qE7+FlZUVBgYGCAQCzw14MoV40Mdza2uLUqmkQUwGsKBPRb516xZ3797lvffeU3u6Z3l0HITUbyR4HtQZldkQIYAZDAY1rZ2cnFTZeuirSz2vOP3Uaz72b/wTQRZ2Op0mHA4zODhINptlYGBAb/j8/DxbW1v4fD7V35Ojw9NeUhl0sdlseL1eBgcHddz4oNiITBWKS7Pdblcvv52dHaamppibm9OHcJLikgiuut1u9cgsl8sMDQ0duwctQSGXy2kwm5ubU21B2eXa7TYej0d74eVymXQ6rf8tlUpht9vVXPfx77u0tEStVuONN97gwoULfPnll3z55Zd0Oh31/5Ssxe/3q4OU6B16vV5lj5bLZa5duwagLmAvchxrNBqUSiXN5KRLJS+0FD6fB7fbrcNx5XJZB69cLhder5epqSnNKG/cuKGCqjKWHQqFnik5J9J+ohwtAjxiqiMvusiv2e12rFYrtVoNs9msQ275fF4L7L1eD5PJdKJjy3ciKHQ6HVKpFNvb27hcLmZmZtSII5VK8cEHHwD9mkI8Hmd8fByz2azto6NAoq5E3ieh2+2qXfrm5ib5fF6VeSqVCvl8/oUos3JcEcWger1+YuakXIPD4aDdbmOz2bDb7YTDYb1Og8FAoVBQai706wMej4fx8XFMJpPa9Ima0EFkMhlqtRrRaJTr16+zvLzMnTt32NnZYXZ2lmg0qlOagLoyBQIBAoGAOinV63XtJj18+BDoKzTLVOLj6HQ6OgAF/Y5JLBaj0WiQzWbpdrvqC5HL5XjnnXcYGhrSo53oO/p8viOxGg+uB5fLxcWLF5We7XK52N/f13XW6XQYHBwkHo/j9XpZX1+nUCioWO+TsLGxQbFYVIWnQCCAzWbToCwsT5kyFRsCoY1LIDiYjUSj0ROTtb4TQSGdTrO2tobL5eLMmTOqEVAoFPjVr36l0fD73/++DrjI4I3IYr8MiCSctDkHBwd57bXX1IFaxDqAEzEeZR5AdnL5zIPp5FFx8GclCMiMSK/XI5vNkslkKBQKasMG3/IxfvCDH7C8vMwnn3xCPp9/or6gtNsmJye1ZrGwsMDIyIhSf8X7EPpHAik0ShtPdBSktiHj0NFo9KkCtYVCgYWFBeUdjI6OUqvVNPuRF2Zvbw+/38/ExARbW1sqmONyuY6ttyE/2263CQaDyqCsVqvqwQB9cdp33nlHeTJ7e3t4vV7dQJ6EjY0NbDYbQ0NDavLTarWUMyJZiMPhUM2IZDJJr9cjkUiwtbXF7Oystpqh396OxWLHGmwTvPJBQSJ+t9tVIxNJ5T/99FOy2SzXr18H+r12mTE/WCT8fXwnEYadmpqiWq2ytLSEx+PRY8NJgsLB899Ba/jjDig9D6VSiYWFBer1OvF4/JCIrKgrV6tVkskkW1tbDAwMPDENTafTWK1WRkZGqFQqLC8va2HxoEqzZDsy6SiBu91uk8vltKMjnhvQV5W+fPnyEzM98V8QYZNqtaovlmwCUoMSWvrS0pIGJzHDOclUpNDrxW+00+moaxj0VbSmpqZUwEaCwrNqCj6fj3g8rsI7xWKRYrGog07SkREVKeivC7vdTrFYZGdnRxWeRPm51+sRjUb/eQYFGTF2u91MTEzgcrlIJpN89dVXLCwscO3aNV0cwWBQVZ+PAzFoEVFVOTM+qbh20IU5Go1qsRE4VN84CWRgSGS2DpKnXmZQ2NzcpFKpcObMGYaGhtQiDvqpdTgc5sGDBxroYrHYE6+rUCgwMzNDJBJhfn5eTWilzSjp6+NZC3zrx9But0kkEpjNZhXFhX4tJJvNPvE4JhOQ8oLIEUikyiSTy+fzqhqdyWRU1CYajeL3+0+cXjebTbLZLBaLRUluwvKUgvXW1hafffaZZkfP2iS8Xi/Dw8PU63Xu3r3LxsaG1rXEw1TunfhjyMCXiMhOTEwQjUaVjAbfyuIfF698UGg2m6q4Iyy5u3fv6k4yOTmpae1JAkK9XiebzZJMJvF4PAwODuJwOLTPLJ2NfD6vUuH5fJ5IJEIwGFSXIHEGepE2mrgn+/1+ffgHLeBeFmSa0W63s7S0pNcP/YUk7bV0Oq0CHk/q5YtSdD6f14JaPB4/liKVFNJEIk3O/el0mq2trSc6M4s+obwsYv6ayWQ0hRYthvHxcba3t7l9+7bex7GxMaanp5XPcpS260F4PB4mJyep1+vs7e3x4MEDfe6zs7PcunVLzXDfeustfZ5PQzQaxW638/DhQ/WblCNOKBTSCUvJkp1Op04Hi/mvtILlqPwiRdpXPihIoUqivyyWsbExJiYm8Hg8Om143IBQq9VUEMNmszEwMEC322VjY0M9FiXgeDwednZ22NjYoNfrcfXqVaamplhfX+fhw4e0Wi11OjopqtUqbreb0dFR3SmNRuNLPwa5XC7i8Thut5v19XVtDUK/XSctL5mNCIfDT3zJRS+hVqtRr9f1BT+OnoWQg7LZLLlcTu/37u4uzWbziRlKsVhUJ27ok5F+85vf4Pf7icfjWls4d+4cvV6PGzduqDgu9DMluc8HC56yATwPUrMQUdV2u839+/cBWFxcJBwOY7fbuXz5shZbn7U2jUajkqCkZibEuoNy/UJ1F+3JTqej0oNidCsZyUFh4OPilOZ8ilOc4hBe+Uwhm81qPzYcDnPv3j3sdrtG+WAweGKxjFQqxd7eHh6PhzNnzjA4OMji4qIWpcbGxg5RjGUy7vr163zve99jc3OTzz77jJ2dHRVGOemu3m63VTZMzD6kjfWyM4VQKMTExAT7+/uEQiG1iId+Ki7pvKSroVCIbrf7O8cYr9erArRut5t8Po/T6cTtdh+Lp+H1elldXaXT6XD58mXg20xBimwH0e12taIPfYYi9LM5EVppNBqYzWZWVlbI5XKMjY0pT0EGler1uk52Hse9yWKx6G4uKb5kq8lkko2NDTVDNhgMqkT1tGOgyWTSo9jAwIAqRon61NOyjFwuR7lcZmRkRCXo5RrFYu8keGWDgqQ+5XJZab6JREJT9UAgwIULF3Qxys8eJWWSnymVSlgsFsbGxvD5fFSrVdbX19nf36dYLLK+vn7I6HZ8fJzJyUnm5ubY2dnh66+/ZnFxkcnJSUKh0AsJr1QqFVKpFLOzswSDQW7cuEGz2VRK8suEzGnk83kMBgPtdpszZ84A34q/SCtMphyftDC9Xq+SrYTsJEzJ40CUl+PxuEqs3b17V4PW40cyIfAI43FwcFD1JtvtNo8ePWJlZQWr1cqlS5eUbCb1CpvNRrVaVbfpeDx+LGUks9msf8Pj8Ryqe9y7d0//WzKZJBKJPPd8b7FYdE33er1DZLLnQYKT3HM5UgmH4SR4ZYOCRNVCoaCL4t69e0pbFfpnpVLRF1cEO58HaXtls1mdafB6vdy+fZvl5WX1IWg0GmpG2mw2GRgYIBKJkM1m2d3dZX5+HugXrmTG4KScCKmmT01NUavV2N3d1eB1VEKUvCQHCVgieXYwWAp3wGaz4XK5eO211/R39/f3sVgsDA8P02w2qVQqbG9vMzIy8jvV+mg0SjKZpFgsqj/B3t4ec3NztFqtIz0LQHvyBz0wH+++HITUA+Q5mkwmstksDx8+VDVsYVkODg6ys7PDwsLCIQdqqRmJFWClUjmW7qbD4cBkMlEsFun1eppRLi4u4nQ6mZycxO/3q9J1JBJ56toQ9qGoTB11DTUaDTVKikQianIDfQvFkyp3vbJBQdLPSqXC4OAgpVKJXC6nxKTV1VXVMRSqqqgfP48zf1CA5Pz584yOjrKyssJnn33G0tIS4+Pj6sUgD2h3d5ft7W1isRj5fJ67d++yu7vL9PS0HmNeJM2XtPegiKsEp6P+3c3NTdrtNuFwWBWARVY9l8tpoJVOgtlsptvtEo/HdSer1+tUKhVt7966dYtMJkMoFPqdYqO4IAtNV1LXRCLB+Pi4aiU8K3vrdDpa5DxIdAIO8T4OYmxsjPHxce3JJ5NJ1XIwGo3k83mmp6d58803SafTfPPNNzidTh3MGh4eVv6HxWKh2WyeqD1ptVpVDVtIV61Wi7Nnz3Lp0iV1387n8+r/8DRI2/Y4m4rMyASDQaxWq5KzoK8HclJ16lc2KMgZV+jMjUaDer1OvV6nVCpht9tZWFjA7/drhD979qyy5J6VrslC83q9BINBFhYW+PDDD1laWmJsbIxYLEYsFmNoaEhpt6Koc+/ePVV8unLlCuFwGI/H80Jdh1qtxvr6Ol6vl1gsxvLysqo/HYfmvLe3p61MeWHK5TIOhwOn06k9dHlJZe7DYrFoIJX+uNiud7tdcrmc1gsOwul0Uq/X6Xa7yhCU7sUf/uEfMjk5STQa1WBUrVZ1pkR67Nvb22xubhKPx/mDP/gDDRCiBvWkTobYvUkb1Wq1Mj09jd1u1z791atXCQaD7O/v4/f7mZ6eVlVoYSTa7XbVb3zariqpv1CM5ZgkHYhqtUo2m9VgNjMzw5//+Z/r8/jkk0+UifisoHASyPFPaj65XE7t66xW64m1Ml/5oOD3+wmFQiwvL7Ozs6NEF3HjFcIPwPLysp5JnwXJLILBIPV6nQcPHvDw4UO8Xq+OvRoMBsxms87Ju1wulfXyeDx69n8RlyhBKpWi0Whw4cIFnE4nq6urqrR8nGBjMpmIRqM65Qj9nViCnKTm0kZsNBo6LyCsQvker7/+Ojdv3qRerz/V1VjO1pLyxuNxtre3+eabb3TKUYqCcg8BnUKVVubZs2d57733sFqt2trb2NjA4XA80UjF7XZTq9X0iOR0OnUMem9vj0uXLnHp0iUlVUnQHh4eBvpr6nlkMNk4NjY2tHYwODiow2HtdlsHrnq9nhZIvV4vDoeDZDJJJpNhb2+P69evv1TyGfSfkwTq4eFh5ZbIM5aC70nwygYFuTihNhcKBUqlEtvb2zidTjXvmJyc1LOrSGUNDAw8M+WWnTIajdLtdpV6KwUzmYrb3d3l3r17QD9FFQcig8GgO8P4+LiOOx8HnU5Hd7qVlRXcbjf/+l//a6BP8ZWx5uM8WPGMrFQqOiSzu7tLqVRid3eXWCyGx+NRVWB5cVutlp6Jx8fHmZmZYX9/n+XlZUql0jOHa9xuN7lcTgPYn/zJnygbcmFhgfHxcSYmJgAOmQRLn31wcJDx8XEsFgv/8A//oE5VxWLx0PThQUjWI/em1+sxNjbG/v4+DoeD8+fPMzIyQqvVYnNzUzMV2QyO8oLKGqnX6xiNRprNJvfv39cZhVwuh9lsVpu9gzL2qVSK9fV1fv7znzMwMMDw8PBLV+YW3c7z588TDAa5deuW1ibgxZTNX9mgIOchcRMaGhqiXq/T6XRIJBJKSw6Hw+olKUVJGQp5GuQBWa1W2u02Q0NDvP3229p1+PTTT3WyTV6uoaEhxsbGmJubw2azsb6+zoMHD2i320Sj0WNnC61WS1Nll8vF22+/jdVq5e/+7u/Uf/E4VWjod0g8Hs8hXcSDO129XsdisajjltlsVoac7OYywvvw4UNu3rypY8BP+x6iVSjpsdFoJBqNaveiVqupEazFYiESiajPRK/XU0fsX/ziF9y+fVs7SefOnVPhmsdrRPJSHwwYVquVQCDAtWvX+IM/+ANSqRQff/wxd+/e5Y033tB5h6NC1sjY2Jhmo7lcTnf/brfL9PS0GtpKFpTL5VhYWOD+/ftYLBauXr2qOhIvE1KniMfjSugTE1zghbLXVzYoSMSr1Wrk83mGh4dVFCWVSimb7vPPP2dxcRFAX9h2u/3MwpEsJhEYCYfDuFwutVJvNpuMjIwQiURUpgz6BbqLFy9SKpVYX1/XFtxJnafHx8cBtFj5X//rf+X27dvqaCQ74lEhQcHn8+mADqDqwqLXID1+q9VKs9lkf39fz58Wi4Wvv/6a7e1t0uk0V65cee5EoXh3JhIJbDYboVAIn8/H6uoqzWbzUBBuNBokEgnNuJrNJh988IHu8ufOnQO+lYVzu91HFltxOp0MDAxQLpdZXl6mWq1y6dIl4vH4sc/X0tmQImQkEqHdbnPr1i1cLhe5XI6dnR01J5IpSfG/DIVCGtjErOZlQXQ2AoEAkUhEs2jZKIEXKnq/skHh4GSdKM7I5N3IyIjqNNpsNh2hPXv27JFGpeXGeTweMpmMFtLEbLTdbuuItKSoIuteKBSoVqtcvnxZB3akGHocmEwm/dsGg4H79+9TrVbVrv2oAiAHYbFYqFarDA4OalCs1+usr69jsVio1+taj5F7JFV4efESiQSNRgOLxcKFCxcIh8OHhD6edT0y1COmLCKGI/WharVKoVBQhSkh25hMJn2uUowTvYPjpMAS+KS9GY/Hlfp7XJs/uV4ptrZaLQ2oMqgkx7SDRizj4+PY7XYNaj6f79jP8XnIZrPs7OzwR3/0R0xOTvLVV1+xsbHB4OCgXueLGN+8skHh4A4pxTzRTpTjhMxESFQfGBjAbrcfOUoaDAZ1QxIxErH1FgNZedml1yxiLNKGe5Ii0VEgaTqgxqgy8+B0Oo8sAHIQQiYqFApKZrFYLAwNDelnAfrPQuaRn4P++LJ0cGRBiwPU8yD3UYqdbrf7EFfB7Xbjcrm0piHDX3K8OfjyHjcgwLc1AykSityazWY7cTotG4dMZg4PD7O6uqq6l3IMOzifIMNML1LsexpE/9JisXD+/HlyuRz379/XgqNc54toiJzOPpziFKc4hFc2U3gc4h8IaAon//8g8++4VV7ZASXCHiXNlOj/Is7TUrmGbwUzpA160D/wOJCdWrIe+Y5CSDoqpCcv/Ibjnk+FZWe32wkEAkohFuptr9dT+XzpQkhH4kV2OnmWByXgXhQHs0kRQpGjpWRc8tzgW+9HmWF52RD2p7SY79+/Tz6fZ3BwULOUF8Wpl+QpTvEvBP8ivCSPi39s375/zM97WZ/V6XS4f/8+pVKJf/Nv/g2zs7P8r//1v9jc3GRychK32/1UL8mjotfr0Ww2KZfLlMtl2u22ip0KP0VUpZvN5it1HxOJBLdv3yYWi/Fnf/Zn6oIuPJXjytIf99oePnzI9vY27733HpOTkywtLalO5Mty+z6tKZziEDKZDIlEgng8zrlz57h37x63b9/WKvyLotFosL+/z8OHD1leXqbZbOL1epmYmNBhs2w2y/b29qFC6KuAarXK2toaDoeD1157Db/fryI7uVzuxD4Lx4HMsFy6dIlSqUQikeDevXsv1WjmlcgUngRZgMViUQ1cxdxFzs1SpZcIKfP35XL5xAtYOPm5XI5araYPORgM4vP5lAsAfbLUUVyav0vIZDI4HA7efPNN1tbW+PnPf65DWdKSOw5EFh/6hJtkMqmGr+fOndM5BLfbzZ07d5SeHQqFGBsb+72cy08KcbXy+XyMjY2RzWbVOu6gI9fvC9VqlXQ6zeTkpJrSzM/P43Q6X0gb9HG8skFBJhkXFhbUYt7pdOrEnxCPZGwU+u0jIcWUSqVjBQaZ8kulUtpLHx4ePtRSEku5QCCAy+VSM5OXHRikJ14ul6lWq1itVmZmZojFYkC/CLe3t6dTiS8DEvxqtZpqGNy+fVv1FUwm05H77YVCgVarpSIjUpATkdG5uTmcTqfSw0ulEouLi3z99dcaBIaGhn7vu+5xIfob0WiUzc1NqtWqMgmFvHTU9u1JIIzbCxcu0Ov11FogEom8VB3PVzYoyO5iMpmYnJxUOetqtaqTa1LJluq23+9nYGCAoaEhBgYGdHE+C71ej1KpRDKZJJFIEAgEuH79OqOjo7RaLfWpFHPZTCaD1WplaGhIFXRdLheVSuWZgaFer5NIJCiXy9hsNkZGRg5pB7TbbUqlEpVKRceNRR59bGyM8+fP6+TjysoKnU7nSPb0B6v8Bz0kHocE4bW1NX70ox/hdrvVdNdiseDz+Y60C4rc/UGPjIMDUU6nk0ajQSaTUU/Jzz77TIOODC1JxnZSVS1A9SCEYCSj00IQK5VKZDKZI6feYt8n5KudnR2SyaTu3E9Ct9v9nbHo4wY7yQISiQQul4uJiQmSySTpdFqVuV6kE/Y4XtmgICOzlUoFu91OPp8nkUhQrVaVGSeqPyIs4fV6OXPmjMqYCbHpaQ9BTFFWVlYoFou89dZbvPvuuxSLRW7evMn+/r7y9sUhyeVyUavV2NzcJJlMcvXqVeLx+HNbaLJAhe9/UBBDpgzF30Hk0ITkE4lEdAwX+kW4iYmJpxaWJNBBn/0WCoUYGRnB5XKRz+cPEZkE8l1sNhuXL18mn8+zv7+vMwxHzRLS6TTFYpGRkRF8Ph/NZlODpSgmlctlVldX2d3d1c+dnZ1Vxipw7LasuHTJWLPI9sO3NOyDMzH7+/t6z4+qfFytVnUWp9vtsrW1pc7ZTxq2KhaLJBIJjEajun8bDIZDSmFHgQTsTCajsvy//OUvyWQyyjh9mTTqVzYoHJxsCwQC+jDErq1UKul8viwe6Q+3223GxsZ0WvJpqVWpVGJlZYVut8vPfvYzrl69yq1bt3j//ff170o6K2OyQqPu9XqMjo6STCapVqtEIpFnLmK3283Zs2ex2+2022329vbU8FTO2MFgEIfDQbVaZWFhga2tLZ1szOfzfP311wBMTU2pP8STMoVms6mjtI1GgytXrhAKhdjd3aVSqfzO74gpC6CSaB999JEGXZlIPQokGAWDQa3JSLbmdDr54osvuH37tk4bjoyMEA6HlRIsNGer1XqIHyEybyI+c9BSrlqt6mCSGAEJW1Js+IBD48WSPZnNZmVwPgtyzBGNi2AweMhX4eBLWa/X2d/fZ319nV6vx/e+9z1GRkao1Woa6OHo6uOJRALoH+2uXr1Kt9tlfX1d525eZpYAr3BQkF1Qor2IeYgjkUT4VCqlI8jlcpn9/X19cWV67WnFqmQySafT4a233mJ6eprf/OY3/PKXv6RerzMzM6NiIIAaxBiNRhVYsdvtfPHFFwwNDTE8PHxIK+/x7KTVaunEofwduUZRjCoUCuzu7rK1tcXy8jITExP86Ec/Ip/P8w//8A+H/CHFPfpJEHl46IuNzM3NcePGDZaXl9W96fHvJrvXhQsXyOVyrKysaLZ1nB1bxGflJbXZbPp8vvnmG3Z2dnA4HFy4cIFut6uF48eDz+OBS2TyhEJ8sIBZq9UOTUHKscNsNuvsRyKRUALXH/7hH6p788Fj3LOwt7enGZoI1oo1vZC15N5vbm5qC/fKlSv0ej3W19cxm80qFgRHK0p2Oh3NplwuF7FYTL06QqGQulq/TJy2JE9xilMcwiubKUjqKJJfol8orbFOp6MDS3JmrVQq+P1+Wq0W29vbQD/Vfhyym2ezWZxOJ7FYjPn5edXyGxkZwWAw6KQb9AueY2NjmM1mFhYWCIVCLC0tqfGICLXIjvt40VGyiP39fZrNpjpOAeocLJLjDx8+JBAI8NOf/hSj0cinn35KsVjUsWIp3j3t6CDy6ADnz5+n0+mwuLhIrVZTw5mDkF0P+rWTRCKhtRihOx8VorQkWZtYqwPEYjEd2hF5dK/Xq12kZxVN9/f3OXfunJrASOYF6Lh8sVjUAbVut0uxWKRer/Pb3/6W7e1trSm89tprrK2tae3peeY1IqQrhdB6va6fXS6XaTQayqkQSfmLFy8yMjLCo0ePaLfbzM7Okk6n+fzzz7XgOTc399z7KRO8ABcvXmRoaIgbN26Qy+UYGRk5EQ39eXhlg4LA4XCoE7PL5dLR5l6vR7FYVLcf6LsPi0NQOp0mlUqp8/FBSBqZzWY5d+4cRqORtbU1Wq2WpmRer/epZ2kRb11eXubSpUtcvnyZWq1GMpnUhf34ApdOgtQUHA6HLqRaraZS68VikeHhYb7//e8zOTnJzZs3SafTnDlzRq8jEAg8NfUUGzw5mweDQb744gt2dnYIhUJPrK+kUik9n4+MjLC3t6dqQtFo9FjiJDICvbu7SzabZX5+Xr+L2K2dOXOGZrN5rJaxjMVvbm5qR0R+Xzwi2+02VquVVCql3BaTycTm5iZer1c3CKfTSbVaxWKxaOv6WXwIqV+JeAygrXGXy0Wj0dD6UDqdZnp6mrGxMbWUGxwcZH5+ntXVVUqlkipRHaWNKJsIwOuvv06j0VAdSqvV+tJ1H+E7EBQOikaICYeQi8RnUVynZ2ZmcLvd3Lp1i1QqpTP7j0MerOglbG9vs7e3h81mIxAIaLHqScWnSqXC/v6+2n//h//wH1haWuLLL7/E7XbrLvC0c7jRaNSBHdmhZGeTzOXs2bP87Gc/o1KpsLu7SygUYnR0VIPfs1R8pJYxNjYG9AuNDx48UNWqJ3Us5DOg/+J+8cUXdLtdXC7Xc3fwxxGJRAgEAqRSKUZGRtSrA/o7+tWrV6nX62pzdtQKvNPp5NNPPyWRSCjpSYL7/v6+OpGL27OoJ/v9fnZ3dxkcHFR/BumKSCH5eQQpGeByu90kEgkikQiPHj2iXq8zPDzMzs6OPu+BgQEmJntcUgIAACAASURBVCaoVqt88803bGxs4PP5lLYtKtfyd5+HfD6vOqHBYJA7d+6QzWYJBAKUSiWVeQ+Hw7RaLTU7fhH26SsfFATiUJTL5dRrIBwO8+677ypPQX5GqLqS7j+OgypDpVJJSSFms5l4PE48HqdWq6kwLKBCseK9ODw8zBtvvIHVauXOnTuqMH0U5PP5Q7tzJBIhHA5jMpkIhUK8/fbbDA8P88EHH7C1taWTb8KRF8FZOVYdhOxqorloNpsplUp0Oh16vd7vVKol9RVJeOgX1cTVWKr34rPxvIUsXgiBQACj0cjU1JTe72q1yocffsjVq1e5fPky4XBYTXSP8oKIZmU6nebGjRvK24jFYqqdEIvFtEMgxVxpyYrSlRxfAoEAFovlSJ0HkcOX7GNlZQWPx0O5XNaME/oZTTKZ5JtvvmFtbQ2j0ajHCLmHkuW5XK5nZgudTkd1GOV7Ly4ukslkNFs+e/YsExMTai4ra/BJbeej4pUPCtJzl5Sw2+0SCoU4f/48FouFra0t7eN2u12SyST37t1TAtOTdgHZ+Q6qC29sbGh/W8QzZIFD/0Xu9XrEYjFVE6pUKvzf//t/2d3d1XT2KMzGfD5PoVDQHaBYLKqBiVTR33//ff7n//yfNJtN4vE4f/zHf6yLemNjQ9t9j+/iqVRKdRKh39uuVCrqkXFwpLjX67G/v08ikeCHP/whgGZN0M92JNhFo1Hcbrdavj8NPp9PuxmisiyBOZVKsbu7y927dwG4du0aPp9Ph4qehWq1yvT0NMvLy6oSLcrdYjd/UIZdLPza7TZ+v59oNKqZghgKC+PyeRAC0sFnK/WrZDJJNBrVDDGRSLC8vKxK1VIHyefzamUnGeLzjmWS+UjW1+l0ePToEQsLC7hcLv79v//3TE9Ps7e3p5uHbJAHj6fHxSsbFCSC7u/vs7m5SavVUuIG9G9YrVbDZDJpj/327dtsbW3hcDiYm5vT+YjHIQ9jaGhIF5Pdblceu+zYkoYC6k4sIqWi1ed0Osnn8xiNRjwez5Hm+AcGBtRBCODBgwdKZnI4HGoN32w2mZqa4tKlS/h8Pn79618DsLOzw/j4+BP704VCAbfbrcHs888/15+XnrxIy5VKJQqFAufPn2d2dhaAzz77jM3NTVwuF9PT07jdbjW6Ed3DZ0F252g0SjqdViUn6L/Y4XCYYrGo9aBoNHqkLKFcLquadjweV8kzQI1eRJT2oFNzOp3W35OCaTKZVAn94wiqSmYnGZPIyZ05c0bX2eLiIoVCgXw+r4XJra0tksmk+p8edZ5DshJhea6urjI/P0+32+Uv/uIv+MlPfsL/+3//T70lRkdHNSv7ZyfH1uv1dLd6+PAhbrebN998E5vNxurqqgq49no91tbWtPCSy+UIBoNMTU0pEeZJldmD5BF56S9evKiKx8JWDIfDuuNaLBZl4n3wwQfs7e3x7/7dv9PFIaY0xxH3kEhuNpup1WqkUikuXrzI+Pg4gUCAkZERms0md+7c4dGjR8o9mJiYeOpDr1arDA8P6z158OABrVaLRCLB6Ogo2WyWdDpNq9VSmzaLxaI/n8lkuHz5stZv2u02KysrbGxsUC6XiUajR2I3Go1GQqEQ1WpVuz1er1ezmHK5zP3795WQIzJ7T0MsFlNqtxxLJNMzGo1P7SAcrKNIoDwoefciYiyZTIZCoUAkElHSVrFYJJPJYDQamZmZ0VH0brfLyMiI2s0dBcKzkeD3y1/+knK5zDvvvMNPfvIT5ufn+U//6T9x9epVXnvtNZVmg3729M/KDEaYi9AvfJ09e1bNXx0OBy6Xi4WFBW7duqVmJ9Bv8QSDQbxeL4FAQIejHoecIWVmwWq1Mjw8TK1WI51O4/V6deZfXhY5T5bLZZaWljh79izxeJzFxUUVBz1qlV6OJfISyKiwSJQnEgkePHiAwWCg0+kwMDCA2+3WOoG0aZ+k9mw0GhkdHdWjSSwWI5fLYTKZaLVaLC8vq5mpwWDg3v9n78qa4zqP68Hs+77PAIMdIAEQJLhIomhakuV4i7eqlMsPTvKUn5DH/IbkxZVKVRLnIRVV7CxKbNmSLMkSaVKkwBUg9sFgMBsw+75veZjq5gxFEDMglEAOT5WLkkxg5t773f766z59zvIyy9zT3w8Gg9jd3WV1KAqsvQY9KszRPe7MzKgYRjqH1EI9jMhDC1yhUKDZbPL10ecdBDpqAI/b3JRh0nGjHzSbTWQyGeTzeVY80mg0bBtXr9dRr9cxPT0NrVaLnZ0dpFIpnDt3DhqNBjqdrud1UiqVuhi5Ho8Hbrcb3/jGN3D79m387d/+LXZ2dvCXf/mXcLvd+Pjjjzk4dWYN/eJEBoVEIsGsL7PZzC6+NpsN4XAYv//97/klOn36NEdSlUrF4qC9mGHQpB7x5kmKmwZlgMcUU5rwMxgM0Ol0bBn38OFDfgF6XWD0MtNDy+fzEIvFmJ+fh91uZ14CpZu0mGmHIQObpwW8wcHBLoMck8mE9fV16PV6TqVp5iMSieDu3bsYGxvje7izs4PPPvuMvSRJLo125cPO4JVKBbFYDMlkEnK5HPPz8+xobbVasbi4iEgkwl6LyWSSpdh7fVn6kWpLp9O8luga6IhBR6nDIJPJmA9AwSCZTCKdTmNiYoLXDGF8fBxWqxVerxerq6uYmZlhunq/7EOaFQEAn8+Hc+fOYWNjA++//z4KhQL+4i/+Am+++SZu3brFA1PAYybwUXAig0InBViv18Pr9UIkEiGRSHCf+ty5c0wuorM57WS9Li5SZtbpdKw8TG7MNKNOL3qtVoNMJkOj0YDJZMKZM2d4oZD5aq8kH3qx6HvWajWo1WrMzc1Bo9FwmqnRaPisTNqAAJ55jaQnSNTYbDbL+hPk9UCVapqFmJqa4u/k8/lgNpu5a0BHBYVCAaFQyNnVQcjn89jd3WV/yUajgYcPHwJo73SJRALT09NwOp3w+/1YX19nP4jOOYXjQjqdhlKphMvl6rJrJ2JTL4VGMhgiU5hEIsHksVqthqWlJaZy0wxMJBJhUx8a9qLjU6/Q6/VIpVI8s0GSAXfu3EGz2cR3vvMdXLp0CbFYDLdu3UK1WuWM7w/O90GlUnEBqFAowOv1MtvPbDZjdnaW5dY7WzxHPR92BodWqwWj0YhGo4FKpdK1SKmFSbwBYlWKxeIjCXXSGTcQCMBkMuHs2bMIBAJs3kJBoR/ykEwm4w4EALZ86wxwdM1URLx69Spu3LgBADwKbDQa2SuzU5gUwDODglgs5gGnSqWCjz76COFwGMBjCXYq1mo0GjQaDW4Lj4yMPJc0+UHQ6XRQKpVcp6JA0asgCjlqUb2GWsIffPABM2c7d+j9/X3s7+/DaDTizJkzUCqVMBqNfY+By2QyGI1GLjT++Mc/hkKhwNraGtxuN3K5HG7cuIFr165hY2MDc3Nz/N48j9jLi9mHF3iBF+jCicwUqL0HtIst5B4cDAaZxqrT6TA5OQmtVttlMHuYqMphoKyBqu9PIpPJQKFQwGw2Y2NjAxKJhM/c/SrudI7EvvrqqxAKhdja2kI6nX4mzfpZeFL5h4g8NMfQiXK5zFOQVPQ8f/489Ho9jEbjkSi0xAqlwitxG4B2rahWq/FuS+PcKpUKNpvt2BWLyM5ep9OhUqnw96BssNesRCqVolwuc7emVCrh0aNHqNfrrBtBxLLd3V3s7OxAp9Nhenqaj7dHEYvpzLqA9lxKMpnk40w4HGbC3vT0dNf4/vPoK5zIoCCVSrkgQ1RUqorv7OzwCK1KpWL6KdA+g2UymWMVsXwSpCdIcwoikYiPMP0sanIcAtqpp8PhwPb2Nvx+P4RCITQazZHOhfQdOgtoz0oliQFKRCCFQsF066OAxts7x5sptaZA0Ww2sbW1xUXH0dFRHk0/zsBAw0rEW+ikVPdjy0dzK/l8nlWyDAYD3G43XC4Xj1ADYH+IyclJ6PV67hwdBXK5/HM1BaI0l8tlDgLVahVqtRpKpZJbs89zDDuxQYHO52S9RRoHZEr64MEDlMtljIyMdFWVaZf/olAqlbgwRt8F6P8hNBoNHuQhQdpIJMJu11Kp9FjVdJ4GpVLJAz2d06D9tM2ehFQqZZ2LSqUCrVbLg0iU0UUiEWSzWQgEApw/fx6Dg4NH/rxngYaeZDJZl7iMWq1GJpPholwvkMlkzGxUqVRdGqGlUolbpENDQ7wGSSvieWC32/l7U23hyYxPIpHwwOBxrJkTGRSAx+5L2WyWnaHPnz/P/fN8Pg+BQIDd3V0u2Llcri4O/xcBlUqF8fFxbkeura3x9F8/aLVa3DYaHx+HQqFAMBhEMplkgtBx6fgfhCeHzQjP4zJERz/SWAQeZy3kLymRSOBwOFgohqZfj7vISENdxFOgP/P5PI9s9wOa2BWLxSwMMzAw0PV7hoaGuI2rUqmeW426M3Oid+K4lZaexIkNCrRIiUtPTsIulwuVSoU7A0TwAMDmpV9kpqDRaCCTyZiKTJLv/e50nVb08/PzaDabePjwIVKpFNxud9/HkZOETp3JzrO01WplB3GBQMAj5FKp9LkEWg8CHR1qtRpLtgHtWgOtlX7wZCv5ad2u47Kr+7/EC9u4F3iB/yd4YRv3FBxk0ZVIJPgMTKq/xPyjIpHFYoFKpeIZhWKxeCjR5lmWYIVCgYVYVSoV5ufnMTY2hkKhgNXVVT6n9npGfF5rNUqt9/b2UK/XYbFYMDY2BoFAgHg83sWQe17buH5x1GsjcVsAnxNnPUjq/rDPoi5GrVaDVCplgVjSozAYDCgUCjzefBj+t60Me8GJCAqHgSr9NAZrsVggEok+x0ojhd6DHvhBSCQSiEajUKvVKBaLCIVC/DuJDFIqlSASibjwKRAIUCgUjiRm0Ww2sbe3x9/xu9/9Lqanp3H9+nXcuXMH5XIZLpfrfzUVpWGr5eVlTExM4MyZM6hUKggGg+wd0SvK5TLPF9CgGNB+jiSPfpxotVqstk0FWjLMKZVKPFJMhUW1Ws0K1tlstqfvU6lUEA6HuY08Pj4OpVKJUCjE5Cjg8Vj2F2ES9L+FL0VQCAQC2N3d5eEonU7Hi44EQYA2z99ms6FYLPb8sIF2dFer1RCLxchkMhCJRCwpTwNR169fR6vVwtTUFE6fPg2bzYZms9mXxTuhWCxib2+P24B2ux3vvPMObt68CZPJBLvdzq2mL7oDAYA9DIB2EPza174GhUKB69evI5fLseFNL0ilUtjd3UWxWMTo6CjUajVnFiKRiFmSrVYLxWLxWLgl9JwSiQRGR0d5tqPVarGQC9kA0vcgjwmLxdI1H3EQ0uk0PB4PFAoFZmZmYLfbuaa1vr6Oa9euAWjfv1OnTsFqtUIoFB5rUGg2mzyuTUZHCoUCOp0Og4ODsFgsvJHE4/Ges5Un8aUICjSearVasbe3B6/Xy7TiQCCAc+fOAWjTWVOp1OfoyYeBhpOov767u4vr16+j0WhwpiAQCJDNZpFOp3kyUqfTQSAQ9L3zJZNJNBoNnD17FkB7F7p37x4PaFWr1c/5HnyRyGQyPAA2OjqKiYkJXLt2Ddvb233pDVSrVXi9Xn451Wo1dnZ2mB8gk8lYAFWtVsNoNMLlcqFYLCKZTPYVHFqtFkuP+Xw+7O/vY2pqCkNDQ/D5fGwnKBKJWKuCdvlGowG3242JiQmeKM3n88+04ZNIJJiamsL09DQHnVAohMXFxa57JJVK8cknn+DSpUsYGxvDwMDAkYeTqKBOXRwaLyeXabPZzMpayWSSdTXp+xKxrt+64Qua8wu8wAt04UuRKbjdbojFYsTjcYRCIaYUUzGQSB2lUgk+n4/bhr2eg0mxhxhkq6ur8Pv9XHAEwC02crQmCqpAIOh7J0ilUpDJZCzhRfJkZJTrcDj+17IEoE0momu4cOECgsEgPvvsM9RqNQiFwi5W4rMQjUaRTCbhcrlw6dIldnWiTMHv97MmpEwmY/Vtt9sNp9PJY8nA5yXyO0E79ebmJoA27+D06dM8ok11n4cPH6JarSKfz3cJtCYSCezt7WFvbw+XL19mwtGz2soqlYrl630+Hz799FP4/X5uR1PWkkqlMDg4yAKyR+3uFQoF7O7uolAo8BGS7OmGh4dhMBhY78Pn87HtABU8HQ4HvwP9focvRVCgCcnNzU1cvnwZtVoNfr+fnZCoQ0AdhFQqxWe6XqBQKFjwFABOnz4Nh8PBIidAWxYun89jdnYWVqsVxWIRGxsbPNrcD/L5PAYHB5nUs7Gxwcardrv9CykwZjIZpuvqdDrU63WUSiUuuJEO4OjoKO7cuYO9vT0Orr1wCAqFAnw+H+r1Oi5cuACr1Yr79+9jcXGRyWV6vR46nY6PCsvLy9je3sbCwgJGR0dhMpl43uJZ5sDlchnb29tczxkbG4PNZmM/jWw2i08++QShUIjNiTvTaOJGZLNZLC0tIZPJYHx8/Jn3nQrNwWAQgUAAarUak5OTqFQqCIVCHFSnp6cxMTGB/f19rK6u9sXWJKHWYrGIWCyGWq2GqakpLpCSs1ir1cLm5ibW19fh8XgQDodht9vhcDh4vdLaPAqH5ksRFAYGBrCzswORSISZmRncv38fgUCAq9l0FiQH4cN0/J+ESCSCTqdjQ9BkMsnzB7RItVotJiYm8JOf/ASpVAr//d//jd3dXVy5cuVQM5FOkIKTw+Hg3aXRaHDLjPj45JZcr9eh0WgwMjLC/1+j0UA6ne5ZmLNarWJ7exsulwt2u52D0cDAAJLJJPb39/GDH/wAQPssur6+DpVKxR6evcxBkPHL1NQUpqamsLi4iHfffRehUIhbbjQmTXMrCoWCtQACgQDOnj3LhC61Wv3UImSr1UIkEkE6nebfOzQ0xNTzlZUV3LlzB0KhkOtQhUKhSzXKYrGgVCqxXH86nUaz2cTFixcPvD76PhKJBFqtFlqtlgfXjEYjj4fbbDZWYtrd3WUlsMNAHbC9vT04HA4OdNPT06zq5PF4EAqF2MS3WCxie3sbAwMDcLlc7C8BPPbyOMrcxZciKOTzeWQyGVy9ehVms5mVcslOnF4OGlSKRqOsYNwrWq0WgsEgYrEYp/YSiYR3AJImazab2NnZYXHTfllxlM7S8Bb9btLjA9opqNfrRbPZxMzMDFQqFaLRKBcDSX+S2qKHoVAoIJvNsmbl3t4eF6FItIYkykm8lvj9vQwNlctlRCIRDAwMYH5+HpFIBJubm13fHwBrYFKLk3gYPp8PoVCIi2hAe8dVqVSfK/5lMhn4fD40m002VdFqtUilUnjw4AHu3r0Lq9UKl8sFrVbLdHGlUsn3u1arcYZUr9chlUoRi8WwsbFx4JFzYGCAh7xI25ECW7FY5IDTarUwOTmJtbU1pFIphMPhQ4NCsVhkUdzR0VHMzs6yEvWdO3fYbZzEdslPs9FoQKPR4NSpU7BYLFhaWmKPDSqikmNWPzjxQaFer2N/fx8OhwMvv/wym59KpVKequsc8d3f30cqler7ZaWUjQZ4qO9NZ9tkMolEIoFwOIxwOAy9Xo/Z2dme/RA6rwdoV+JpIIr4FVKplEVlAOD73/8+XC4XfvnLX+Lhw4es0Uh6jVqtlo12n4VkMtnlS1Eul9kANhaL4fTp0/yC/dM//ROAdreFfv9hSCQSSCQSmJ2dxdDQELa2tuD1etl6j8Rv1Wo1BAIB6zRSIBSJRPD5fIjFYryoRSLR51J6yhJqtRouXrzIR55EIoHPPvsM9+/fh06nY6VsEjahMzkFXcoSyMXa5XIhGAzC6/U+1WaQ8OQEKmVQ5BsCtLMWrVaLRCLRNTD3LOzs7CCdTuP8+fNYWFgAADx69AgbGxtYXV3l5+ZyuWA2myGTybimZbPZcOHCBfzud7/D4uIiZ08Wi4UtAoVCYV91hRMfFJLJJFKpFL761a/CYrHgN7/5DfL5POx2O79gxFRLJBKIx+NQq9V9F1eazSa/CFSbIPUg4HHbi7j0CwsL7ELcT7FxYGCA5dE6jw9UvIzH4xCLxXj11VfhcDjw7rvvYmVlhc1ZgPZLvbu7i9HR0UOPSeVyGTs7O8zxSCaT2Nvbg8/ng1gs5tYo2Z75fD6Wqj/Ir/JJkCz+uXPnUKlU8OjRI2QyGYyMjMBgMPDxir4rDRSRzySNI0ejUc76PB4PRCIRB0K6lkQiAYVCgYsXL3Lw3NvbQy6XYzMfs9ncpRrVee+Bx4NN6+vrSKfT+MY3vsGeIYehUqkgkUggnU5DJpNhcHAQRqORv+dLL73EHiK9Tpvu7e1BpVLh4sWLaDabuH37Nm7evIlGowGbzcYFaPL17DwKKZVKVq4aHBzE5cuX+V6l02n+2X5wYoMCpZFerxdSqRSvvPIKotEo1tbWWK+fbMdox43H49zDpdqCRCJhR6BnmZioVCrodDo4nU42Sfn000+ZvAS0I3WtVkMymeQZd7fbDZVKxay6w0Cz/Z2WdqR+3Gg0EI1GOfV7+PAh7t69y6YsVHCSSqWIx+PY39/nzsuToKAYDAaRz+fxrW99Cw6HA3fv3sXKygoHBJL7unv3LgCwYUmvWUKj0UAikYDD4YDdbsfy8jK8Xi/MZjNMJtOBk4KUjkskEqTTaf482lljsRikUmmXD2g6nUY8Hsf58+chFos5kCUSCTQaDZw6dQo2mw16vf6ZojekrUhdoFOnTmFra6unNDudTsPr9aJer2NkZIQL23NzcwDaXJnf/va3KJVK0Ov1PZ3pBQIBFhYWIJPJ8P777+PmzZuQSCScDVKGo9PpoFAoIJFIMDAwgFKphEwmg42NDbRaLVy8eJGVzSng7e/v91V0B05oUKhWq1xcSSaT+NrXvoaxsTH83d/9HbxeL1dhm80mHA4HhoeHAQArKyvY39/nycpcLsdz7YeNIVcqFY7K0WgU29vbCIVCTEyx2Wzc7iGHJ5JMpxmBXiAUCtFsNpFOp3nnJ1HWbDaLRqPBtnVLS0us0RiPx3lxaLVa5HI5hMNhaLXapxYCKagGg0HodDq88sorWFxcxIMHDyCVSuF0OnHv3j2cO3cOEomEd8lqtQqFQtGzyGg8Hkc8HsfCwgLkcjmCwSCPutOZ+yAMDAyw5yOd8SlVHhwc5JYbpf9kukPHOwrYpPY0Pj7ekwpWs9lEMBhEs9mE2+1mxeReRtVJrerVV1+FUqnE/fv3cf36dX75P/30U2xvb7PgSS/qVeQJQSI7Go2GHblIxQp4nOEQqIMSj8dht9sxMzPD10BZlU6n60s3AjiBQYF2aaLd0tn9F7/4Bd566y22g5uYmIBGo2GjE6BdUBsdHWX7t2KxiHK5jHq9zjJWB0EqlSKXy3FBj2YPaFFTGiiXy3nQRq/XI51OIxwOw+Fw9MRupBeN5Nbpd1YqFRYztdlsWFpa6ipqdRrM6nQ6LtpVKpWnvngkUprL5XDmzBnEYjH8+te/ZiszcsAeHR1lRhx9PzqHUzbxrGtKJpMQi8VwOBxMNaadWq/X99RelUql3Ouno8bCwgJu3LgBj8fDu3A8HuejF7lp0XdWKBTs6nXQrkiZXDgcxu7uLnQ6HV5//XVeK8FgkKnnByEejzPDNhaLYXl5uUvViSzjqBDcC01do9EgFApha2sL8XgcZ86cgdFoZN+SpwW4crmMaDSKzc1NCAQC/OAHP4Ddbud3YW9vD9VqlTPmfrQqTlRQIFLK9vY2P9iZmRlsbW3hX/7lX6DRaDA7O4vh4WFotVrcu3eP3aWB9g5aKpWQSCSY+67Vatmr8VkCLAKBgKvzGo0GcrkcMpms6+wml8sRj8extbUFhUKB+fl5bG5uIhqNsufEYaBdtNls8oLZ399HMplEpVLB9PQ0ms0mIpEI76BarRa7u7u8qNVqNSwWC3sNPq0lSkcl4iZ8+OGH2N7ehs1m4xfC7XZjcHAQGxsbXfbmg4ODmJ2dRaPRQCQSeaZzU6vVgk6ng1AohM/nY+Voun+9olgsIpFI4PXXXwfQ7j7cvHmTLQEB8ByGXq+Hx+Ph+gNlCp0y+J3fj7JGCpSUUr/xxhswmUx4+PAhVlZWesoU9vf3oVQqUS6X8fOf/xx3797FSy+9xAU+mrGg4NXLEUypVCKdTrPIzsOHDzE1NQW3283tW6C9MdHxNRKJIJVKYXp6GiaTCXq9Hru7u/wcqaZzkCHSs3CigkKlUsH29jYqlQpXgWmxjY2NweVysYT2Rx99hOXlZczNzfHCCQQCiMVi3FOWy+Wo1+uw2+09cQk6d25S1qEISwVGKuBMTk7ipZdeYqsuoVCIqampngpLNpsN2WyWMxdajIlEAkajkSvzZFtPU6K0ixmNRiwvL6PZbB74eXTsocX1ySefQC6XY2trC61Wi9tVDx8+xKeffsrfQSqVwmq1IhqN8gzBs44A2WwWMpkM5XK5y2D3yVT3WajVagiHw12t0VQqxQpJBDrapNNpRKNRrpuYzWZ+vmQE2ymsUiwWufUIAFeuXIHFYoHBYMCdO3dw9+5dRCIRvPrqq4d+V51OB7PZjEwmgxs3bsBqtTJnAWhnTqVSCVqtFjabjYuoz4LBYIDJZILD4cDOzg62t7e7tCBpXZZKJc5SBQIBxsfHuUBLHTcKCmazGXa7nf06+sGL2YcXeIEX6MKJyhSCwSDi8TicTieuXLkCoJ2ukQLwp59+ilqtBovFgs3NTd6dKa2mIg1RiGlnIK2+XkGRtTPCZrNZntB0Op34oz/6I4hEIh67rdfrfc1aBAIBrhRLpVKewiSlXpqWNJlMCIVC0Gg0bKWeyWSYU3FQeko759jYGBKJBCQSCYxGI6RSKXQ6HWKxGCKRCKfSlHH4fD4EAgFsbW1BLBbzPMFBqNfrEIlEXIugM2yvPBGirCeTSZw9exbT09MAgP/4j/9AKpXqEiCRyWRQKpUIBoNYX19n4tbQ0BAfH/b39xGNRlGpVJjObDQau4qeJBd37949LC4uwuPxYGJioqeJUCJ1eTweDA0Nwe12c/YItI9tY+8qiQAAIABJREFU5XKZtR2kUumhmUInD+Hq1auIRCLcLdvf3+efl0qlsNvt3JL0er18jJqbm4NAIGBzGtKg7FWxuhMnKijQInjttde4uLWxsQGPx8PS7na7nT355HI5LBYLQqEQgHbK5Ha72d2I5NcpODzZkiSevE6ng8vlglqtRjqd5sXWaDSYS7C5uYl8Po+pqSmcOnUKUqkUd+7cgcfj4ReD6MqHQavVdvHlgTYRhroP1KokX8Z4PI4LFy7wkerv//7v+ecOOgdTgIrFYgiHw6zPQEMyXq8Xp0+f5nFzSjtrtRpfP80TPAutVotbmOFwGLlc7kC37ydRLBYRCATg8XhgsVjwk5/8hF+uzz77jB24CDQY5/f7EYvFmO24tbUFvV6Per2OfD4PhULBehcUnDrnWEqlEm7duoXNzU20Wi2MjIzw+f0wkB9HvV7H+fPnEY1Guwq1S0tLmJiYQC6XYy7JYUcIgUDAQ1s2m41p7JVKBWq1mu8BMXmj0Sjb/nm9XrjdbkilUuzs7PB7Q3WGo4jwnqigYLfbmRH285//HABYZ2BqaorP+RKJhPX0XS4XL+LBwUGWS++l6BcKhTgokDgsPYBwOMyRGmifkV977TUYDAYolUp4PB5cu3YNu7u7mJubg1Kp7LnCKxKJeNYfeNw+jEQi8Pv9zP/f3d2FRCLBlStX8KMf/YinAu/du8c7+EHV7U5XZrJnc7vdzOar1Wrs5k39fqAdCMj4pBeRELFYzCxFagETc/EgkMCKx+PB3t4epqam8OabbwIA/v3f/x1Am58yPDzcFRSo1dxsNjE+Po47d+4AeEy4Iio61ZKIZUgO4ltbWwDaQUQkEkGv1/P/iDp+GG2cqNmlUgmDg4NQqVR49OgRbt26BQBwOp2w2+0IhUKoVCrs/0D1jafdT41Gw9kFeZlSW1UoFPLGVCwWOVjIZDKe/6F3o1wuc4bYa2B+Gk5UUCAW3draGl/Q2NgYR0utVsvmr36/H06nEy6Xi5ltlBn0Gh0FAgEMBgNqtRpu3rwJgUAAu93OnpEikQizs7MAwI7TzWYT6+vruHHjBvx+P0ZGRo6k6CyTyXiB0AyHTCbDhx9+iFOnTnGV++tf/zpeffVVhEIh/PSnP+WfpVHvgzKTzgWh0WjQarWYux8IBOBwOHDmzBkkk0kMDg5yZkHyaeShcdg1UcuSvDeJrXhQsKrX62wUnEql8Morr+CVV16BRCLB22+/jQ8//JC/h91u73qWZAJMWSBlN8ViEdFoFOPj48xMrFQq/ALRbAZlZlarlScIaT6CUvLDgoJUKkU6nebsiP4bHXscDgekUikSiQRyuRwePXqEl19+GVqtlm3qnwSxXOn4ZTAYkMvlkEql0Gq1OBvUarUQCoU8P1IqlTA0NISFhQWW9yOyl0Kh+MOwohcKhWxDT+dtulm0+wsEAh4fdblcLKEGtHu3xF7sBWSp1mw22T8iEAgwF8Jms3HlVyAQoFgs4vbt27h//z4ajQZ3RDrbRr1CpVJx14R2wDfeeIM/RyaTwWazwe/387guHSto8feqitTZQQHarT2LxYLx8XHcvXuXXzYAXbTkXkBkLBotJuOep9VXCoUCwuEwAoEAVCoVvvnNb+Ls2bNIp9P4t3/7NywtLXEQGB8f51YjcQBUKhW7IRHfAgDzUWhzIOn4bDaLUqkEqVTaNZZNx09aU+RC1gvod9Eouk6nw9mzZ3kNUmtbJpPh7t278Pv9sFgs3IJ+1otKZC6gvTYtFkuXfwZZxLVaLcTjcUxMTGBhYYE5Ip2bxGGf9SycqKBgMBi6bgzwWNzkyV1RrVZjZmYGxWIRfr8fQPumddJiDwMFhVqthqtXr/JseiaTYbkv2vHy+TxWV1dRqVR4FzOZTJx29vsA6JqAdhtLp9Ph0qVLTIaiYBeJRGA2m6FUKjlQdroUHQVarRbj4+MwGo148OABdnZ2mPXWryYkTSF2XotcLu+aPaHdMRKJIJFIYGRkhB2SHzx4gI8++gj7+/tQq9V8jZ1sPgoKRFKi4H/q1CkAbXGX5eVl5qbI5XIYDAYIBAKMjIyg1Wp1+XISC1SpVB7JrGVgYIA9O6vValeAB9qZHAUhhUKBDz74ABKJpIvPcBg6A/nTCGCJRAJKpZJJbdVqFeVyuctZ7ag4UUGhc2CFcNAipZuxtLTED5uifq8giW6lUolqtcqW8gC4KEX0X+qju1wuTrHpz6OKonTal1OGVCqVOA1Xq9UwmUy8QOg6iYZ8VLMYs9mMwcFB3LlzB5lMBm63m7OOfn8n0ZkB8IwGFSs7x4kBcAdFpVIhmUzi7t278Hg8nLl07ubE2nwScrmcuRsULKRSKdxuN0ZGRlAqlaBSqfj482TqTb9DIBA8l9kO/SxdI6loUWASCoUwGo3IZrMoFAoYHBzse3L3IFSrVQSDQdahTCaTPPJPweB5lLtOVFDoBzKZDIlEAqlUineMXC7XV4QkR2RiodHZjhYcpYdAu+1FZ3mTycRHhuexBesMgqQOVKlUYDQaWT5eIBCw0xIFSJlM9lwWa1TBbrVaOHXqFKelAPq+HhKYpSMDDQF1vnC0QKltSLJ6xWIRcrkcWq2W5wTofh9UF6J2ItV3gHbHRKPRoFqt8s4tEom6PEmP25Luad8LANcDKM3XarXsCH1UAdcn0Wq1MDg4iPHxcVitVrz//vu4e/cu3G43X+cfnMFsL9Dr9cxafLIY1StI6UitVqNarUKv16NQKHQ9PHrYer2e59gpFT1OW7dOqzWyGv+iQLLnxWKRX6ZOKm0/UCgUKJVKqNVqkMlkXO/ovDe0Q5KJSi6X444IGcBS0a/XTK/Tw1EoFHYd3/4v7fZo+pOOFvV6HRKJ5FjXC+lbWq1WeL1eRKNRuN1uuN1uDqrPY9j7wjbuBV7g/wl6tY17QXN+gRd4gS6ciOPDSfck/DJ8Xr+fFY/H+WcmJyf5Z0kU9bDPAv4wn9tJWCOlUgnFYpEFb6h2Qu7cOp2Ou3QikYi5C6TODeDQEf5n4UQEhRdoo1ar8YIYGBiAzWaD2WyGUChEKpU6tkIV0O6mJBIJTE1NodlsYnt7G0C7wKdUKo9kh/cCz49sNoudnR0Ui0VmW9KLLpFIUCqVulS+BQIBa3AYjUamfqfT6SPb8Z3IoED6/QA4+un1ei5GUcVbIBCwlRb93EmokRyGWq2GaDTKBThS581kMl1ag2fOnEGhUIDf70epVDrWCrpQKITT6WTiFu0szWbz2A1g/xBAPIBCoYBWqwWJRAKlUtnVeQHarVQy8yGbwV7EWwn5fB6lUgkWiwU2mw1isZi7N/V6nYcGq9UqarUaD8ZNT0/D4XAw30SpVPblp9qJExcU6CUgQQyTycTcgEqlgvX1dTQaDahUKiYOUY8YaLclO1WY+wXNQBA9lqq4EomEnYzISYoMO/pFPp/v8mmk6T6ZTIaRkRF21g6Hw7h+/TrrSxxluOUgJJNJKBQKVvYhRl4ymcTo6Oj/aQX/eUEuVLRZDAwMdL0wRDZKJpM9vzSk5l0oFKDT6VhDgdicpPtAxkKEfu8jib0ODw+jVqthe3ubPSWkUimSySR3j6ilns/n4fF4eL4CaKs5E9u0X5yooJBKpeDxeJDJZPDVr34VQFsdN51OY2VlBWtra2zblsvlIBQKOcUC2u5Gg4ODGBkZ6euhU5+ejFaz2SxTnDsViEulEtLpNGKxGMuyHwWVSgUSiYQXqVgs5glQiUSCnZ0dLC0tIRgMIpvNYmpq6kif0wtisRj0ej2L35KwidFoPPT6SDPxoBkMksWjwSQy6SEWJNAOtiRUQsK1QDu497ug8/k8kskk4vE4zyaQZJ9areZ2ZzQa5cnWXndxoVDIil/ZbBbRaBSZTIZfevred+7cweTkJAwGA9cA+uF+tFotDv6xWAybm5vMlqRZlM7BKcrs6H7SAB8FjaOs0RMTFMrlMra2tpDJZPCNb3yDg4LX68Xbb7/NmgkOhwPFYhEGgwGBQACPHj3i9MpoNMJkMiGRSLBy0WGg8eRYLIZisQiTyYQzZ87w1BkRhuLxOD777DPodDoe2e7Hr7IT9XodhUKB5bdlMhlrNVABkKS8aECrXC4fa6ZAkEqlEIvFvPAEAkHPzlPb29tsyf40UKZF2RQpWXfWR8iWjtyVj8L6a7VayOVy2NzcRDabhcViwcTEBAYGBpDL5dBqtVCv11kMmOT+TCZTz2Q3UkeiF1IgEPBO7ff7eTiLBufC4TAEAkHPI9kEgUDAMvI09UjU/VarxVwZUjIPBoOQSqX47ne/C5FIxKrcqVQKs7OzX26HqHA4jGQyiXPnzuGVV17B/fv3AQA/+9nPWJG5Xq8jEolw4W1/fx+jo6N44403ALQX3e9+9zskEgnMzMx0iYc8Ddlslo8qZrOZZbZJELVcLvMu99Zbb6HRaODq1avI5XLIZDKsq9Dvjtb5kgDtXdHj8cDr9X5u+EutVvPk4XGBJOXpz1qtxi9jP4uIiE/ZbBatVovNeYB2BiCTyboCBvljJJNJZLNZVKtVZm0qFAo0Gg2mNovF4p4Dbi6Xw/r6OhKJBC5cuIDh4WFEo1EsLS1BoVDA4XCg1Wrh+vXrANo7sNvt7kvlmFiKlB1UKhV2LstkMrxOSD/B7/dDJpNBpVL1FRSIgi0Wi6FQKDA8PMzCKVTPkMvlsNvtPBR37tw5XL16Fe+99x42NjYAtI/dRz1Cn4igQHUEmUyG1157DbFYDB988AGANjOLRntp9JfaZlNTU/jhD3+I5eVlAMCNGzeQyWQwOTn5zAVFghgbGxtIp9OYm5vD5OQk23IRFXdpaYnVcf1+P+sarq6uwmg08r8fFcSbj8ViSCaTsNlsPKwjFotZLUgikRyr6SxpKhCTUCqVdv3+XlPO06dPY319HclkEvV6nV/wZrMJnU7HLw2l6J1ahg8ePEA+n4dKpYLdbkc+n0e5XGYdiMMCOgA+P29ubiKTyeD8+fOYmpqC1+vF+vo6ms0m1Go1fv/738Pj8fAx8/z58zAajSzX3gtoAIq0GejlJUYnBYXh4WHOTAQCwZFeTFpTSqUS+/v7XF8Ti8U8lUvFTeoW3blzB4uLi6zjabfbjzxrcSKCQiwWQ6lUwuXLl6FSqfDBBx9wqmez2djbkWYNlEolLl++DIvFgkePHuHdd98F0F5IRqORNRIajcZTF1an+MapU6dgNBpZ+DKZTLLhqV6v5xS/U2qbvhMJrfYL0iCgFDqTyfA/k3qU1WqFRqNhw4+jtpeeBprHJxMb2oGA9nVSZ+cwkJw67ZqdoGp9Nptlw5pCocAOTzTQYzabUa1WWYGbng0Vkg9CqVRiQ554PI4rV67g6tWrWFpawtbWFgv8Xrt2DSsrK3jzzTdZ4s9gMPBL2yvo2dNkrVar5fM8CcMC7eOtVqtlwdXnnVHZ3NzkIPHSSy9BIpGwZ0Wj0WDLOhJpIQ+UJ6nf/eBEBAUaRpqcnITP58OjR484qlMhsdOcxGq1YmBggN2PqP4AgI1labT4ad4FnWd5WrCbm5tYWlpCKBSCQqHA6dOnYbFYuF4hFApRKBRgNpsxPDyMjY0NxONxuFyuvlP7fD6PRqPBLxJJvMvlckxPT6PVaqFUKnGqSArPvZ71ewERX/L5PFqtVtf96bWtm0gkeFekn6PdlBZtp5U9tdpI3GZ8fBx2ux1arZZVhOjvHnZ0CAQCvIPOzc3h29/+NjweD959913U63XkcjnuDp07dw4/+tGPuC5FknHPU6Oh2Qt6VrRO7HY7SwBWKpXnCgqk2UBzMPl8Hnt7ezzvE4lE8Nlnn3FRWKFQcFY2PDx85Ot7QXN+gRd4gS6ciEyBbMmz2SyWl5dRKpVYuZj8BDsjLjn0FAoFmEwm3kErlQp7G5IvpFKpRCKR4D488LjQR7vF7u4ulpaWUCqVMD09zco5Go2ma6afVHTUajV7D9L5sh+USiXIZDL+3s1mk4VjBwYG8OjRI7arHx0d5eugHfx5mY2lUqnrvNlsNvmekK9ALyDnb1I07oREIuHsg4qXpF2RzWah1WoxPDzMfpPk6UDdgMN22Gg0yvTgV155BR6PB//6r/+KeDyO0dFRzhTL5TJeffVVXLhwAb/97W8BtDOFo5ikPA2FQgHpdJozW5fLhd3dXZTLZVSr1eeaVqSfpYJos9lkoeKbN28iHA4jlUpBIpGwxwM9B5IH7Ic4xZ975G98jCANwWAwCJ/Px2wtAJiYmGCL9k6xjlqthoGBga6WUrlcxt7eHrRaLWZnZyEWi9lZqXMBUHGp0458fHwcpVIJCoWCq/+dOndbW1vMYiuXy6yd14up7JMgpSJ6YGazGfV6HaVSiSmumUwGoVAIoVAIMzMzmJmZ4dS6U/G5X1QqFUQiEU553W43IpEIf5d8Pt/zQqJ7RPqDTwPJ59E/i0QilEoltqifnZ3lM3mj0eDndJhISK1W46AQDofx8ccfIxAIYGRkhItvAoEAJpMJf/ZnfwaDwYBEIgEALHnfj+zcQaCj78jICP+33d1dJBIJWK3W5yoQk+ELbZCBQAD379/HrVu3oNfr4Xa7MTo6CoFAAKfT2RUEIpFIT1yTp+FEBAWFQsGLSiaTMWkHaBdu6EHn83nOBqhnS4UxACxl/vrrr8PpdOKTTz7B+vo6zGZzlyU3vVxCoZDlt10uFwcFUowGHist5/N59kcMhUIQCASsitwvSFCEdlC5XI7f/e53LHVusVhYNzGTyWBtbQ0DAwM4c+YMgPYLc9SgQMQbUjaiTgtV8q1Wa8+ZD73wJGjSK0jHgdSQotEo8vk8BgcHu8RQnwW5XM5n7d3dXSwuLqJcLkOn00Gv13Mbl16Wa9eusYYjDRQdByjbI0ZjOp1GJBKBQqGAXq9/rkxBqVRCo9Fwx8bv92NgYACzs7OwWCzswrW2tgan0wmDwYD19XUAYPPhowSlExEUSNJaqVRiaGiI7b4B4L333sP4+DicTiccDgcvBGqr5XI51mjMZDKYmJjA7OwsotEoHj16xDbnnXhSSos4AfV6HUKhsCu6EtMvlUphZmYGWq0WXq8XmUwGEonkSBVejUbTtUvl83lEo1FYrVbmuysUCra3pyMREWSI0HSUwFAsFiGVSmE2m1EsFpksRZwJknb/IlGr1Vgqv1KpIBgMMhmo10XscrngdDoBtAu1V65cwfb2Nvs7kDdjJBJBLpdDIBDg4wwJ5hzHdVAngIp6ZFtHL+zz8ks6j3pGoxFKpRKpVAoajYZp6h6PB2q1Gg6HgzsyRPP+0gYFuVzOqTwJmNLLv7e3h5WVFYTDYahUKmg0GjZ7ValU/AAA4Ny5c7h06RLi8Thu3bqFjY0NOBwO7qEfhE4q85OgDgEpAEciEYTDYRSLRRiNxiO9QFKpFKlUioOPXC7HpUuXuI2kVqtRLBaxt7cHk8mEoaEhJBIJbtNSRf8oux0dHWQyGbtFRSIR/i61Wu1YiVJPolgsIhwOc4dIrVYjEokw56PXijnRzoH2/ZucnGTuRaPRwPLyMqLRKCYnJyGRSBAOh/moSVYCz4tCoYBsNsvK30B7vZLnY6dL2WHoJH+RfmSnTB/wWIOU/iQreqJxd7YhS6USu1/3ixMRFIB27ziTybB8FdmPS6VSlMtlflGI2050UqlUyjtmIBBAsViEQCCA1+vlc+NRVXubzSYHBfJpIEotpc1HicRKpbLr/JzP5+F2u/mFVKvVaDQaWF9fh1KpZEFSIl0FAgGMjY31fU2lUgn7+/tsfjIwMID9/X1EIhFuzVKW8kVhf38f6XSanb729/dRq9XgdDohkUh6DkgGg4HTaqJIk3+G1+uFz+eDy+XC3Nwcy/fTuf8okvxPA6knW61WrnfF43GUy2V+aXtFPB5HOp2GWq3G+Pg4TCYTFzFpfT/tKEKbC+li0mcethE+CycmKJBefrFY5MkvgslkglgshlKpZNacVCpFqVRCLpdjRmMsFoPJZIJGo0Emk2FuOwmG9ot6vc7HB0p3yfWYRDCOkoaSHiOBKuF0bUSUoj6/UCiEwWDgYRciq/TTh242m9jb20OtVoPdbu8aoKnVaseiAtwLKKCbTCa2updKpdx96BVEfQfa2U21WuVNoFKpwGQyYW5uDjKZDOFwmEfvARzqYNUriKrdKR+fy+WQTqcxOjra14YRCASQz+chl8uRSCSwv78Pg8EAi8XCAYfGqmlzqlar2N/fZ8uBVCrFE5pPdpj6wYkJCgC4m9BZeKQKP72cJJ9NxBSJRMJUZIPBgJmZGSwvL3NKRa5SR0GtVuPvoVAomI4bjUZ5gvI4zt9PmrUA4KAjFouh1+u7WI3lchnFYrGvoEAaDkKhEBqNhinAXq8XUqmUd87jSKsPQjabRSQSgclkgtVqZbv4SqXSZRXfKzprQxKJhOswuVwOxWIRp0+fhlar5ZYn4TgyoVqthlAoBLFYzL6VwGND5H4zBbVaDbvdDqFQiA8//JA7D522BdQNIyYluYq9/PLLmJmZwa9+9SsesyYp/KPgRAUFAJ9rSZlMJgwMDMBqtaJUKiGfz6Ner7MzM52dAPC4czweh9Vq5TTxqJVmchAGwLoNu7u7yOVymJiYOJZiFQAeDOoU1Njb22NzG6fTyROkQLtQ1u8DbzabKBQK7BVJ8l3RaLSr6n8Ul+JeEYvFUKlU4HA4UKvVkM/n2Wj3WRZ4vYKCayaTgUqlwtjYGKtH12o1vrfHMUdSLBZRKpVgs9lgsVh4dJp0KoRCYV/ro1Pe3u12Y3FxEcvLy3A6nV3HHrVazZmDSCSC3W7H7OwsPB4Pbt68yUcqq9X6h+El+TR0BgmSVycKLVXlqQo9Pz/PN45S9OcZWNrb2+Mba7VaWbaMuAzHNcoci8WQzWYhk8mYC1Gv1/Hyyy/jW9/6Fmq1GnZ2djhASSSSvgMdpZt2u50dqfb29iCTyaDX6/k+Pc/9OgzVahVKpRIikYivhbQPpFLpc7XvCDQDQK7L29vbSKfT0Gg0PVOoewFNTbpcLm6jA+20nYho/VwPbQpULNVqtTCZTKjVamyMOzAwAJfLxa7qNE25tLSExcVFbGxscADRaDRHLhif+KDwNFB/nAZ36GFTTYKMQo9aSyAUi8WugahIJIJUKsUGp0ep7D4N9XqdefKhUAhKpRJWqxVTU1Mol8u4desW7t27xx0Zqkr3g4GBAdjtdpw9e5aLWrdv3+biFtm1HceL+STobJvJZKBWq9nubGNjA36/H3Nzc8/ltNUJOlefPXsWcrkcgUCATVmOK7MD2oFcIBCw6ArVe4jM1e+GQUI3VEeanp5msyO6L7ThhcNh5uSQK3WxWMTExASrkCmVyiNf74vZhxd4gRfowpcyU+gERVbgcQuxVqt1nSGPAmp/krOxTqdDoVBAsVjkLsFx7Tx6vR5OpxMajQb7+/uIxWIIh8OoVCr45S9/yfqNRHc9ShFJIpFgZGQEWq0W4XAYpVIJQqEQ4+PjsNlsX4iqE4HYkslkkhWMWq0WlpaWYLfbMTQ01Jdb+LNAXpJarRZ7e3uIRqOchRxHG5JQq9VgNBqhUqlYwg8Ad436LWZKJBLWeEilUswhIVo90F7rRDaTSqXsTi6RSGAwGGAwGPjIoNPpjtxh+dIHBYVCwRqGLpcLkUgE29vbKJfLXYKu/SIWi3HvG2iftePxOOLxOGw2GxeTjgOk+0CjuPl8nluzpVIJZrO5q4ZxlGNLq9VCuVzGzs4OUqkUdDodZmZmePjrixRq7ZwPUalUKBaLSKfTKJfLGBsbY7GQ4wANqEmlUm5rU2fgOI8PVAB2u924du0aHjx4AAA8vHaUo1AnYalSqaBSqSCTyXTxMWQyGddlqJhJY9NEBgP6dw/vxJc+KJB6ENA+m+/t7UEqlcJisTzXQpPL5XjppZdw/vx5AO2pvJs3b2JjY+O5KrsHfVY8Hmcx0Hq9zkpT1IbSarVdffZ+IRKJuOdNRVOHwwG5XM4ell8U6Hfr9Xo4HA7o9Xrcv38fuVyORXKPi0Upl8tx+vRpjI6OYm1tDQ8ePIBGo8HQ0NCxSuRTN8rr9SIWi3Gxe2JigunHRwUZvRB7kYhlFChqtVpXoCOH7U6h4efBCy/JF3iB/yfo1UvyRGQKz2PTRVJeQDtqGo1GWCwWFItFVvIlnARLsJP4WZVKhUkvEomE0+JyuYxEIvG5sehn2cbRfU+lUkzsoWNcZ0uQlKd6GdM+qfeRzFhICRto82rsdju3JYlaTtO2vXxeo9FgcWCqGwBg/U6qKRHDlmzjMpkMH9WeZ7M/EUGhX7RaLaa2RqNRXtBTU1PQarWoVCpQq9WQSCQ9ez/QxCW5TJELVecQjVKphFQqZalyMtvIZrPHJpVGcvMWiwWTk5OwWCw8L/BFIRaLsQrw2bNn4Xa7kcvlWCugFzSbTaTTadaDUKvVfISjiddisQibzQaNRgOVSgWHw8E2aEcRA/m/RCaTQSAQQDAYhNVqxalTpwC0Nymq2RBdv99rS6VS8Hq9LP9HBdJarYb9/X0+BlHxlCTbOoNLIpE4MBAdhi9dUCArrkQiAblcDoVCwcNTFouFB0hIbbcXFAoFbG1tIZvNwmw2M1222WwyvbparUKtVvOZbmZmBkajkUknnZToo17X/v4+PB4PdDodfvKTn0AgEGB1dRX5fP5Yz8NPgshMQHv3j8ViCAQC0Ol0kEqlrLv4LOTzeWxubiKfz2NhYQGTk5Mol8tYXV3lIlyj0YBGo4HBYMDw8DAsFgu0Wi33+o8iWPMkiAJOLEaLxYLR0VH2bAAeF5GPgmaziWQyydf6+uuv4+rVqxy0f/GLX8Dv9yMUCrFZETk69YpUKoVarQa32w2DwcA8DxKbrVQqbEdHbmizs7NczwAeC9ocZU1+qYJCIpHA1tYWSqUSpqamcO7cOUgkEjZHXV9fh9frhVwuh1wux/z8fE8FtFgshkQiAYfDgZmZGf7vwWAQm5ubANo7AwXfOCilAAAgAElEQVQhkipPJpMolUpcbT7qi5vNZrG6uopEIoHZ2Vl85zvfgdPpxF//9V/j448/xmuvvYZLly4xFTkWi31OPfmoIA49zVV0Dmc9ePAAIyMj0Ov1hy5qIl9dvHgR8/Pz2NnZwdraGs9b0O8ulUq4ffs27t69i4sXL+LUqVMwm82sGtRL2kuZIt0D+lMqlbIAD01ckip2NBo9lmykWCxic3MTlUoF3/72t3H+/Hn4/X786le/AgBcu3aNvR1Ja8FgMPCUbS8gcpfVauVNEGizKKPRKHchNBoNkskk0uk0RCIR09gBYGhoCEKh8A83KDQaDWQyGaysrGBgYADf//73MTs7i3g8jt/85jesM7C2tgar1QqTyYRKpQKfzwen0/lM6m6r1UIqlYJQKMTIyAgcDge2trbg9XqxsbHB9QqDwYCJiQkkEgkkEgl4PB4Eg0GeiDt37lzX9F0vOoq0M25tbSGVSuHSpUu4ePEiWq0W3n77bfzN3/wN3G43JicnodPp+JjUT1p/GGq1GhKJBMbGxvg6Y7EYlpaWuDPQi2yZQqHA0NAQ5ubmEA6H8c4777CvA3WBhEIh/H4/yuUyM0VJyZoG13oJrNVqFbu7uzwIJxaLMTIyArFYjEqlwkNE5XIZfr8f8XiclbMA8DxMv+fuRqMBv9+PQqHAz2p1dRVvvfUWzz4AYIVriUSCYrGIQCDAL2kvIM4C+acGAgEAYBEZpVKJUqkEn8+HnZ0duFwuDA0NMc8BAHw+35GVn058UCiVSohEIvD5fFAqlfje976HhYUFvPfee3j33XeRTCZ5gdVqNWi1WjgcDiwvL6NcLrPmwkGgqTqZTAaFQsHpLpmYkI6+w+FAKBTC2toa6vU6wuEwBx2LxcJciVAoBACHpsKVSoUznHA4jPPnz+PrX/86arUafvazn+Htt99GOp3GX/3VX2FqagobGxvsmuV2u/viKlD6KRAIeGFSnaVYLLKeAdAuAobDYQQCAUxMTPQ8fisQCKDX6xGLxbC8vMz8/U4JeCJ/qdVqnDt3DiMjI3jw4AHS6TTm5+d75pWk02ns7u5y5nT27FmoVCo+HlSrVUQiEdy/fx/ZbBbT09M8rwK05c/7nfFotVq8Dk0mE958800Eg0G89957qFarfP/8fj+cTifOnz+P3d1dPHr0CAKBgGnkvYDa3bFYDNvb2zz7kM1mmTAlEok4Ezpz5gyGh4c54wTa6+vSpUtsOdcPXtCcX+AFXqALJz5TIG1CrVaLy5cvY3R0FP/5n/+Jd955BxKJpEuBqFgsshgLCbo2m81nFlyIKKJWq+HxeJBKpZBIJGCz2eB2uzn9IscjnU4Hn8+Her2OU6dO4Tvf+Q7Gx8cRDoexsrLSZQpyEChLoMm6+fl5/Pmf/zl8Ph/+4R/+ATdu3EAkEsHXvvY1vPHGG4jH43jnnXfYjaqfo0Or1WLTFBKFJS9OMjJttVq8kzUaDezu7vIoL8m4H/aZAwMD3D4johXRdallR9Jl3/ve93D69GmEw2E8ePAARqMRxWKx53Q+n89DrVZ3FZjpSEd6k6VSCQ8fPoRMJsPCwgKSySRnbySf1s95m44OIpEIly9fRq1Ww61btxAKhbrSdhplHhkZgdfrZaNjcrnuBXK5HNlsFvF4HJFIhDOc0dFRdp6iiVNSNU+lUqjX6zyYRWIzR8GJDQrEJQ8EApBIJPjKV76CyclJfPLJJ3j//ffZV08qlfKZl3j0RqMRTqcTsViMue/PeiBqtZrl3mKxGDKZDMxmM6xWK6dffr+fW5bT09O4cuUKrly5glqthnfffReBQABarRYTExMADu4TU0DY3t7G5cuXAQDf//73EQgE8NOf/pSryRcvXsSf/umfolQq4Ve/+hX7MwLtl+JZlmqdyOVy2NrawtDQEEZHR3ncvFPSvfPcHwwGUS6Xme3Yq/IxiYrU63WoVKquXjs9ywsXLnCqu7S0hI8++ojHj/sp0rZaLZhMJkxOTgJoj7h//PHHWF9fh9Fo7Cr6joyMMG+FriMcDrOHZq8oFovcIpyamsL6+jpWVlZY6o+OLg6HA5cvX2bxH5JF67d+EQ6HEYlEoNFo8PLLLwNo32MqMufzeWSzWTQaDVQqFUgkEp6JoO/xB6G81AnaRWOxGObm5jAxMYGVlRXcvn0bAwMDcLvdUCgUMBgMrBakVCrZx08sFrPgxWGLmlSAarUalEolCoUCvF4vBgYG+KxYq9Xgcrlw6dIlOJ1OXti//vWvkcvl4Ha7u5x+D9qFIpEI/H4/FhYW8Md//McA2gvgH//xH5HNZnkxv/TSS5ifn8fKygr8fj/XNoD+nKFJ13F8fBwikQirq6s80yEQCJBOp7vUfXK5HI+kd/pe9AIiJWUyGUSjURSLRRgMBnzlK18BAExPTyOdTuPdd9/FnTt3kM1mcf78eZhMpr4l0iYnJ7nrsLi4iFQqBafTCafTyY7VKysrsNlsGBwcRLFY5IL0UeYSKChcvHgR+Xwey8vLiMfjUKlUiMfjXNf65je/ifn5edy/fx/r6+us+N0PLT6VSiEajcJisWBwcJAHypaXl9nDhMyEAoEAyuUypqenkcvlmKtAtYej4EQGhUwmw5V2s9mMr371q8hkMlhcXMTOzg77QJAAKd20SqXCC5lEWCQSCRqNxoGVX/IEpNaRVCrF9vY2fD4fQqEQLl26BABYWFiA0+mE1WpFPB7HL37xCzx48AByuZwNZ0kqDgBrOjyJ/f19SKVSTE1NsWT7O++8wwHt4cOHGBwcxI9//GMIhUL8/ve/5wEfCn69ZAmUCaRSKZRKJRiNRm7jxeNxCIVC1pscHx/n2YNCocD6j/0uqkajgUAggJ2dHUilUpbEp5f31q1bXKg1GAxwu90sBafRaHo+Fmk0GojFYmZWCoVCjI2NsQgPVeedTifGxsag1+tZ+Rt4LPHXT8Cjwh5Z2v/2t79lfcZLly5xhjg9PY07d+7g+vXraDQaGB4e7ls2n5zPSR+ErlMsFqNer6NarSIYDGJtbQ3BYJD1GxQKBQd3iUTSczb5uWs90k99gajX6/D5fLyQrly5AqvVips3b8Lj8TBzi0xna7UaM+bq9TqcTicEAgHK5TKbYRyWumk0GlbFpfO0SCTCwMAAnxVJzjuTyeDWrVtIpVLQarWwWq0sOqvVavnlOmiBU6cjFApxNyEUCsHpdCKfz2NiYgLf/OY3MTU1hbfeegu5XI6l5fpxuKaUnc68RqMROzs72N7eZv1KOscbjUZ+YdLpNBqNBr9I/bTuyMHa5XLB4XAgGo3i7t27fA+pLkMdIrfbDbPZDIlEglKp1DOPQKFQsEoRANZIFAqF/MxDoRDXNkjpidLpXvkQnaCXjKT2U6kUbDYbTCYTZDIZe49sbW1hY2MD2WwWFy9eZIl5+txeoFarMT09zYbL1BYPBoNoNBooFouoVqucmRqNRpjN5i5eQq1Wg0AgOJKi84kLClToGxoaAgBcvHgRHo8Ht2/fRqFQwNjYGBQKBbtQ5/P5rhTZ5XJhdXUVuVyOJdkOS93o762trSEUCmFiYgILCwtYXFzs4gZYrVZUq1UIhUJMTk6ypDbVJHpJS+VyOWZmZmAymbC2tgagPYHp8/mwsLAAg8GAUqmEjz76CKurq8z6o92xV3RKt5lMJiwvL+Ojjz7C8vIyZmdnOauZnp7G9PQ0t0f9fj/fX71ej1Kp1LN3pVAohMvlgl6vh0AgwPr6epeqMNngCYVCVlsiv06lUskq2YeBPCLpfqjVauYhPFl0brVaEAgEXfoaUqn0SGPUpJCl1Wrx8ssvQygUolqtIhQKcTE1nU5jZWUFZrOZ3biIQ9FsNnsqbsrlcpjNZlSrVSSTSb4n9MwcDkeXsrnZbIbT6WQmJH0Pi8Xyh8FTSCQSqFQqXIRrNptYXl5GJBJhIZJOW3qS2QaAq1evQiaTwefzIZPJ8Ox5L2fVbDaLXC4Hp9PJhZ1gMMjnUGI22mw2nD17FlqtlskhQqGw59TX6XRicHAQ6XSabeByuRwLaDx69Ah37tzB2NgYy8HRuHE/1WRafKTRsLq6Cp/PB7FYjEajgXK5DJFIhFqtho2NDb6HVqsVQqEQPp8P5XIZer2+Z8EQ2n3lcjmSySRUKhV3FoD2OVcmk0Eul6NUKmFxcRHpdBqTk5OwWq1dx5VnBQcqaFL6TzoEFBAajQarKtPRsHOwiKjbvaLVaiGZTGJnZwdisRgXLlxAs9nE/fv3EQgEmAEKtAMWyebdunUL5XKZh8x6rZm0Wi2uyZCKONBeO9VqlfkftLaNRiMajQYcDgfX4lKpFAqFwpGOECcuKJCyL10M3Xir1coLh/QECoUC/H4/L+i5uTmEQiHs7++zGk0vOwLpMIhEIpw/f55n8UkIBABOnTrF5+1wOAybzYbR0VFuxfUKiUSCfD7fNYO/v7+PeDwOj8fDWoK7u7totVpdjsy0kHvZuWkBOp1OuFwuFItFuN1upNNpdsu+desWwuEwZ0ZAu5X16NEjZLNZ6HS6vopylHGRtiDN+NPzsVgsrFhFNPF79+6hWq2i0WjA6XT21SbsPKd3ZlGFQoE7NDSfQurVANgesFfkcjns7OxAr9fj6tWrcDqduHHjBj799FPU6/UutqJYLIbdbucCrlqtRjQaZZ3IXgID1VdIbMXlcgF4TDij9a9QKFCr1djTdGJigm3jyCTpKEeIExcUKDpS2hOLxVCv19k7r9NCPBKJIJvN4ty5cwDabZj/+q//gtfr5bS7l5S70078zJkz2Nvbw71797CysoLR0VEAbS55uVxGJBJhVWeVSoWLFy9CIpEgk8n0dE41GAzsYkU7v1AoRD6fZ8k1lUqFUCiEdDrN9niDg4N9nYOpfWk0GqHX65FOp7k+IBAIsLe3h4cPH0IqlWJ8fJxpupubmxCLxRgaGoJKpUKlUuk5BSVhGLommlall5GCKmUStItKpVL2fOx0qjoqyIjYaDRyIZpqLAA4g+gFzWaTqdmXLl3C/Pw8bty4gRs3bkAkEmFiYoIt2wAwH4GOwDqdDoFAAIVCAdPT0weKyZDKlkqlgslkYqf1SqXyTC+OarWKcDjMhVvaaKrVKmKxWM/vQCdOXFCo1+vQ6/V882q1GpuFkHRYs9lEKpWC3++H1WrFD3/4QwDtRXf9+nVO4Xrd6dLpNAqFAubm5pi8E41GMTc31+UMrdPpYDQa4fF4UCwW4ff72dFaLBb3VEiilJcq5Z3XWCqVcPr0aWSzWR5l1ul0qNfrWFtb42GXXl5S2pEajQZSqRQH23K5DIFAwMXcs2fPwuVy4caNGwDatRMq5pK/YT+ggE3KQSTJDzwuRObzeTQaDSiVShgMBoyNjWF9fZ1bfAB6GsI6CIVCocsvMhKJIBqNssZlP10HOjpIpVIsLCxgbW0Ny8vLaDabmJ+f5wJz5xFrZ2eHreCHhoYQDAZ5cvNpQaFarXJrcWFhgXkedLx7cqcn6jjVohqNBl566SXYbDa+tkQicWS7xBMXFABwARF4TNShyn6j0eCXRiwW40/+5E84Ev7zP/8z8+ip1dXLTelswcViMfZ76HS5Jv4C8dzT6TRKpRK2tra4sNZPpb7TXUoqlcJqtaJSqWB4eBgPHz7kHv+lS5fw8OFD/E97V9rc5nldD/Z9B4idALiLokhRsih5Sxq3Tl0n7e/r93Qy7aTttJO4juN6Kke2LJUStVCURBIEFxD7vuPF2g+YewXIskiATIdOcGY84zgSgHd57nOfe88959GjR7h58yYAcE/8begPqgC4Kk8cDhK5vXHjBoxG40ArlaTGqMp/FlDLF+gF/FarhUKhgHK5zOO+Pp8PDx48YFNd+r2jvNA090Dy63K5HOFwmP0t+u/NaSEIApxOJ9rtNra3t1mn02q18uASBbBisYhyuQy9Xo933nkHHo8HT58+RTKZRLlcfuOzo44CibE+fvwYnU4HXq+XNS3p2mg0vFgsIpfLYX5+nr0n9/f3OfvM5/NwOBxot9tDT++OZx/GGGOMAVy4TEGlUkEQBD7jUpSk6jJNqsnlcnzyySeYmprCb3/7WwDAkydPmIgzzE5DwqiCICCbzaJYLDIZpH8asdls4uDgANVqFcvLy3A6ndjf38fOzg7cbveZxE8bjQYzCev1OorFIq5evQqbzcbzCUSlPU2mQNdO3Qfq4VssFp59uHLlChYWFrjNCvS6K1QgO+39KxQKyGQy0Gq1LJ5COgCkaAX03KCKxSJUKhV+9rOfcWu00Wiw+a3L5Rrl9jEymQxSqRScTie0Wi2azSbS6TQ7KgHDCd/SfQ8EAohGo9jc3ESxWOS2MpkQU/G3UChArVbjk08+wdraGra2tpBMJlGpVH6w4Ef+kAqFAplMBk+ePGGjWSJjAa8IcSKRiDs7AJjgRJ06AGcyv7lwQcFms/ENAXoUYEq9qN88NzeHa9euwe/346uvvsJ///d/898l5aRhiD4qlYpp0dRf12q1Axbj9DBI3n1tbQ0ikQibm5vY39/noaJR1XSJTJTP5znoXb9+HfV6nTUCRh1woeEwAANEGrVaDbFYjGq1ygGtX3LutCC/Rp1Oh5cvX+LevXuw2Wwwm81MywV6+oU+nw8Gg4FZpqFQCM+fP8fR0RFu3LhxZi/LbDYLpVKJiYkJViai4SE6bw+zWEQiESYmJqDX67G3t4dHjx5Bq9WyUhfNjdA16nQ6OBwOeDwe7O3t4Q9/+AP29vYwPz//g9/bbreh1WqZiVupVJBIJHB8fIyZmRnemMiNnJ5bNpuFRqOBIAhIJBJYX19njYkrV64MTa8mXLigQAw0Woyk3x8KhXioh1ps//Iv/4Kvv/6az/0UUGg3PC1UKhVEIhEajQYEQUAkEoHNZkM6neYXidyazWYzt7ho56BgdRbhE4vFgng8jmg0ColEgps3b0IqleL+/fuQSqVn4rK/CXTm1Ol0CIfD3ArsdDpDG5lYLBao1WqUy2UEg0FEo1FMTk7C4XBAIpEwH58YqJlMBhKJBFtbW7h37x7EYjGWl5cxOTl5IiP0JBQKBR6I6na7THen9hyAoc7YIpEIgUCA6y5LS0t48OABvvjiC7z33nu4cuUKu14TWq0WHj58iI2NDRweHmJychIul+sHC5z0bul0Oh6CosKjTCZj5SWr1cpy/KRRGo1GkUqlUK1WkUwmuRPXbwkwLC5cUCDZKrqBKysrmJ+fZ3vxZ8+eYXt7m4du7HY7t2GkUumAWeow30nKP7VaDbOzsygWi+zrCPRuMvHOlUol/ud//gePHj1Co9HA6uoqk2RGlWQj1p9KpcKVK1egUqkQDAbx7NkzOJ1OaDSac/OuBACv14vr16/DbrfjP/7jP7C1tQWgN2hEPP7Tgrj6dPQQBAHxeJwdwSnDKRaL7GmRy+UQj8ehUqng9/uZFUov8ij3sVarIRaLYXJykovTu7u7SCaTmJ2dHdkHhIR6FQoFlpaWmEOg0WiQy+UQjUb5+KDX65HP5/ndoSlN8nB4E8RiMZvAGAwG+Hw+dlgnw1y6PjJXBsAO7Gazmf1A+70kR31fLlxQUKlU3LMFXrWmtre3uYpsMBgglUpZKoxSX2pZDgtSapbJZFAqlfjggw/w+PFj1Ot1plBnMhmIxWLu/wLgwSaK8mfJFGhB0BEomUziX//1XyGXy7G8vAytVntuhilAj9TkdDrx/Plz1Go1+Hw+AD1G47C1Ea1Wi1wuh3a7DZVKhVarxUcxkUjEHIFcLsfnaoVCwapV1JrU6XRncnEiKzxiZeZyOezs7EAqlY7Urwd6C5Yk6oicNTMzw/LrxCXo/w3tdpuPT2q1mudI3vb8tFotKpUKxGIxnE4nVCoVjo6OmM8B9LogdC+pFepyuVh1nHg89Hmj1rguXFAQi8XQ6XQcHUnllzwd+uXVSaCV2I9ncYQi+3f6DLvdzsKswKuo3G63YTabodVqYTQamXbd39YbBSKRCHa7nTnz0WiUZ/fp88/TlarVaiEUCqFareLSpUucolKxcxiQFBu9wMVikQld/feUuAtyuRxSqZR3W5oQPatitUajwczMDFZWVpgh+vDhQxbjHTXg0PxIrVbjRUlq3zS5SAueLPhI3o82jJOeHd2TUqmEer3OY/ydTofbypRF93NnaIKWvqP/uDsqLlxQAAaZcTqdDs1mk1WPBEHggKBQKCCVSoc+LrwJarUaxWKR2W8k504gCzc6c9PUnEqlGqpS/zZIpVLE43Huerz//vvodDrQ6XTnmiUAPUEakUiEZrMJiUTCC3JUCzni4AO9Xcpms6FQKHxP6IOCDhmaSKVSyOXycxOiJep5JpNBNpvF9PQ0ZmZmBiZYRwHpS6jVarRaLaYXNxqNge6NWCyGQqHgd5PqVaf9jv5Nhr6D3kO6l7QpUnZ7XnZxhLFt3Bhj/IXgR28bRyPLJEs1MzMDr9fLjDGgN4/Q36mwWq3QaDQD46b9OMkSjJyeSVuBPB4AsKuR2WyGWCxGrVbjgakfCqwX1e7sPL4LOJvd37Dfd1G/i9TG6WjicrlY6SmTyZw4jDSsTV0kEmG5vMXFRahUKp4bOa8N/kIEhdeRyWRYa0AsFuPDDz/E1NQUotEoSqUSSqUSyuUywuEwcrkcc9qXl5dZQAR4+/htP1qtForFIvb397l/b7fboVQqudCYz+ehUCgQjUa5eER9+EKhwOe+Mf6ykM1msbe3hxs3bgDoFXALhcIbVbfOCpIJ1Ol0bEtH4rgkz3YeGNOcxxhjjAFcyEyBBloAsJwVGa/SdCSJnOZyORQKBQA9lt7c3BysVivEYvGpZ8mr1Sr29/eZvhoIBFAqlRAOh5lZScah5E/odDrhdDrhcDjgcrlYtXgU+Sugl4kkk0nUajVMT09jcXGRZ/EPDw+5OAeANRZGZTjSUBLtZjTNCPQKkCaTiUVfhpFeHxbFYhGJRAIKhQJTU1OYnJzkazqL3+P/J9LpNJRKJWseCIKAw8NDbm+fpwdoPp+HWCxmY17S2rRYLFzAHfX968eFDAr9hpwymYw99J4+fcpjt2q1GtlsdmBxfPfddyiXy5ibm8PKygoTQN6GYrGI4+NjCIKADz74AGtra7h9+zbu3LmDTqfDcwaVSoWn2e7duwcAWFtbw7Vr12AymaDT6aDVavn7hnk4+Xwez58/R6FQwMzMDC5fvgyZTIZQKIRgMMgj2iRprlQqYbPZIAjC0IunXC4jEokgFovBbDbD6XSi0+kMTNeRzgHVT6g9fFqQFZ8gCDAajTzPQG1K6rtnMhmmN3s8HuTzeTb07RfBPQnk/F0ul1GtVlnP0uFwwGg08kQotRPPYgTcj3a7jUQiwb6VAHB4eMhzIMMyQ09COp1GrVZj+XgSyymXyxyUzgMXMijI5XIuvsjlcqjVaty7dw/r6+tYXFyE0+nkEd+DgwNegGKxmP37dnZ2TqWiS36QKysruHr1KtbX1/GP//iPEAQBN27c4FpBLpeD0+mEUqnkEVaSKV9ZWYHX64VKpRowjzkJVPPY2dlhufOFhQW0221sbm5ifX0d8XiciUZUzKIhHwqQp2l51et1pNNpHB0dQRAEXL16FVeuXEG9Xmd6NdAzSLXZbPB4PEwsGnYHImESYNCiTRAEbqvRCLzBYMDS0hIajQaeP3/O12I2m098dhRcYrEYE9uIVUnmLbFYDEajESaTiYNTLpdDuVw+cwaUy+VQKpUwPz/PxVcaCyfBlPMIDP2Cs3q9HqVSCYeHh/B4PEgkEpDJZJBKpbDZbOeSmVzIoECkEOAVM6ter2N5eRnT09NM5Nnd3R1gMVIaHI/HEY/Hcf369RN708ViEVNTU/D5fPjd736HX/3qVxAEAR9++CHUajWLnXg8HnZNUqvV2NraglgsRiQS4Z4y9chPg2azyQsnm82ydgNZjD9//hxfffUVPB4PPvzwQ/h8voEddn19HSaTCT6f70RSTqfTwfHxMfb396HVavHxxx9jZmYGpVIJz58/RygU4l2UVI+DwSBisRjeffddyOXyU4u3AmCSz/T0NDslAWDDHKVSiUqlgnA4zMew9fV1hMNh1jx4m1Bts9lEKpVi385cLofFxUWsrq6i3W4jlUqxBHqtVsPk5CRmZ2eZAkxy72cpzNVqNYRCIdTrdXg8Hs4QVSoVZ1b1ev1cggId85LJJHfcnE4ni+YcHh5ygf08vu9CBgWj0chneWr5LS4uMtHF4/GwUAiJiwK91JfEJsi27KSgQI7Fu7u7+Kd/+idotVr8wz/8A9rtNrLZLKsduVwupFIptNtt+Hw+JjnRy02jsZTin6T4FIvF2PdBqVRidnYWrVYL5XIZu7u7+MMf/sD2aFqtFplMhneMx48fIxaLwWQynbjbdTodxONxBINB+P1+/N3f/R00Gg2ePn2K27dv4+joaKCmQFRZUoJ6/PgxZmZmhmIDptNpZLNZfPTRRyykC/SCH3H8i8Uiut0uZmdnUa/XsbW1hUwmw4Gg2Wz+4LOLxWLY2dnhDORv/uZvMD8/j2KxiK2tLcRiMRZrkUgk2NnZQSqV4hT78uXLPNg2al0mk8mgWCzC5/PxZC3QUxTP5/Ns6XYeIKVoOjaTIFAikeCx+rdZIw6LCxkURCIRD3NIJBLU63V4vV4epunfQaRSKdM+2+02jEYjc+BpB34bqKB27949+Hw+LC0toVarMa+cFjfpA5DBjMvlgk6nQzweR6lUQjweZ5ot8PagUCgUsLu7y+nx0tIS1Go1MpkMWq0W/u3f/g0bGxtYWVnB2toalEolyuUyt2nX19cRCAR4FuNtgS+RSODZs2esP+F0OvHZZ5/hu+++Q7VaxeTkJDsqAeABm06ng3w+PzCvcBpUq1VEIhGo1Wr4/X4eDgLAKbVer+fi8MTEBFKpFEql0oAj1dtYqvF4HFqtFj/96U8B9GpQ0WgUd+7cwd7eHtrtNtOMSSiXFg999tLSEhv4DFvQpCPkNVoAACAASURBVDpUs9nEu+++i3q9zteTyWRQKpXYkOY8QHUWookLgoBms8kTtSSrf164kEEBGNQhJJ/C141OSQiUXtpAIMDCGqTreBIymQyfpxcXFyEWi6HRaOB2u/Hs2TM8ffoUANhsg/jmJpOJJyZJqg14JQW/tLT0g99J6R55W0ilUlZvJn2/69ev4+rVq9BoNKhWq5DL5Xj27BmAHsd/YmLirV4TtABIm2FtbQ1WqxW/+93v8PXXX0MikWBycpJTeUo72+02nE4n2+IBr8R0T0Kn00EsFkMmk8GtW7fgdrvx2WefcdZHY/GNRgPxeJzP+ZFIhPUq6Lm/6boqlQoymQzi8TgWFhZ4puHevXt48OABkskkPB4POp0OCoUCstksU6n9fj9nWrVaDclkEm63e+i6ApkVpVIpnuD993//d7aA7x9EGtXL8XVQwDEYDPz+05wEfed5zsZcqKBAC5mmFgFwPaF/t6JzYzAYhEwmY/dhvV7PbUuNRnMqDj9ZgZFqMtCbzCTFXtJqIEcqSt+If041DZFIxC8t8Cq6vwkymYyLX0DvpaYWazweh9FoxOrqKq5du8aZ0cHBAb8Eq6urrKL0Q9dIQapSqcDv9+PWrVvY2dnBxsYGL3zyYejXjVhbW2OBklqtBofDceqFUyqVEIlEYDKZcPPmTbx8+RJffPEFC39MTU1xmqvX6/Hee+9hbm4ODx8+RKlUYgVu4M16Cq1WC91uF0ajETqdDt988w0A4JtvvoFIJOIgKwgC32OLxQKRSAS/38+F3Wq1yt4gNNZ8WiQSCcTjcXi9Xvzyl7/E/fv38fnnn/NzqNVq2NnZYf1EOk6S8vgoxU064litVpRKJXi9XlZzcjgcMJvNMJvNZ1L+6seFCQqCIODo6Ai1Wg1Op5O7DzqdjhV16aEmEgm8ePECUqkUN27c4F0snU5jZ2cHSqUSXq/3xKlJSivtdjtX21OpFJ48eYL9/X1cuXJloEZAo70kS261WnF0dIRcLoe5uTnE43EuyB0eHnJAeR0mkwkWi4WLU9lsFpVKhSXJgB5fgIZrGo0GHjx4wNd5msk7Cgqk2JzJZPDgwQPk83l4PB4e0abCJv1Wr9eLdDqNUCjEI8en3fEymQyq1SpX4//5n/8ZL1++ZNObQCAAiUSCWCzGSlWhUAj7+/uQy+UDRrdvAhnwRCIRCILAC8xutyOZTLKhMADmDtjtdoTDYUSjUZaPNxqN+OMf/8hyZ6cJCt1uF4VCATs7O2i1Wrh27RoSiQR+85vfoFqt4mc/+xnf70qlArvdDoVCwQzcYQq1r4P+LlnlUd1JIpFgfn5+aH+Ok3BhgkI4HEYwGITNZoNerx+YwSfBCLFYjFAohK2tLSiVSty4cQNms5nPrAqFAoeHh+zY9EPKM3SGpEWo1+tZZCUej2Nvbw9GoxFzc3M8+UdKNv1tssPDQ6RSKVitVua7U1FNpVL9YFAQiUQs2gIA8/Pz+O6779BsNlGtVmGxWHgMmRSZFAoFZ0QGg+FEAY3+ApogCAgGg8hms5idnYXNZmNJ+r29PXQ6HU7FdTodNjY2sL29jZmZGbTb7VNPoYpEIm4Df/7558hms/j5z3+O+fl5AL2FFQqF8PLlS4hEIgSDQeRyOcRiMa4nvG2BVqtVFrohGTmgx08xGo2sEQn07r/T6UQul0MikWBqMNCzxtvd3cXq6uqp6wn1eh3BYBD1ep2Lwp9//jn29vYwNzfHn10qlbCwsACHw4FGo4GXL18iGo3CarUOaH+Mgn7uDtCrx5Dy+Hmqcl2YoBCNRqHVajE7O4t8Po8nT54A6O3QNAhVrVYRDAah0+mwvLwMt9vNDxwAS2Ctrq4yoehN6C8KkViF1WpFsVjkHXJ2dhZWq5UX9pvO1M1mE91uFxMTE7wz98ua/RDIIIUeJCkr0RgsLRi5XA6/38/ZE714Go3mxJ2BilxGoxG5XI5lxqmtajQauYPx7rvvsshKKpXCy5cvEYvFYLVaT8UXIGi1WgQCAbhcLjZcyefzuHPnDgCwsW4sFsMHH3yARqOB3d1dLhCf5JepUql4EZOWJQB2CaP7QzqHu7u7iEajzCykQu3GxgZ8Ph93lk4DOpZSAXp3dxexWIwLmvRbHA4HfD4fjEYj0uk0NjY2+Ng5jG4oof+4IZFI4Ha7MTs7iydPnjCpjTKx88J49mGMMcYYwIXJFMj+vFAoIBgMcppF8weFQgFmsxmXLl2C2+1md+ZUKoWvvvoKQG/nX1xcxKVLl1iE5U2gXZQMXNvtNvL5PLezlpaWYLFYWE7rTb81m80ynZXScTL+BN5OpaUWIp0VO50OO0GR0KhKpcLExATkcjlyuRxyuRynj6fZGSjDIb4BEbooO6pUKtBqtXC5XJBIJNjd3QXQI8iEw2HYbLahBVdkMhnL55FtXCQSYZadSqXCwcEBnE4nLl++zJmC2Wxm8563kb+oO+FyuRAIBJj8tby8jFwux6pPEokECoUCwWAQ29vbXFCla3G5XJiZmWGVr5NAbWuRSAS32410Oo1cLsdOZvv7+/xOLSwsoF6vY3t7m8VVHQ4HaysOwzisVqtIp9Oc2SoUCvh8PgSDQSQSCfh8PkxNTbGjNrVYgVdF2VFwYYKCIAgwm83I5XLsowiA58UbjQZX61utFkqlEjY2NvD111/zOXRpaYm5BW9rodGCpTNsOByGVqtl6ii5W7+p/99ut1EoFFg8dm1tjenV/SKlbysskUsyHQEo6Hm9XnYiIluwVCrF8vYU5E6jIEQLgHwJr1+/jrt37yIWizGRSSQSIRqNDnDn8/k8618OO9CjUCi4FlSr1fh50e+OxWKQSCT48MMPsbi4iC+//JLT/pOKjASJRAKTycSDQABw7do19nskTYxut4s7d+4gm83i7//+76FUKrkGYbPZ2FbutEVGjUbD7EtqPx4eHuLhw4eQSCTsOUrDde12Gx6PB1qtlsWEh1WXyuVyCIfDTCzT6/XY3t7G/v4+0uk0rFYrMpkM5ufn4fV6US6XedT/LBTuCxMUJicnYbFYmDVIZzStVgun04lGo4GDgwMIgsCGqO12G36/n6vKxP/uZ+i9CRQUyCuxUCggkUggnU7D6/UyOYnkygCws2+hUEA8HodYLMba2hocDgdSqRR0Oh0ikQi3304ybOl3q04mk1Cr1XjnnXdgtVpRLpfx9ddfY2NjAx6Ph/kEtPufhilHAYfk3IrFIgulFgoFFAoFHBwcYHNzE0ajkWsK6XQaMpkM3W73rffwTSD/jE6nA6lUCo1GA71ez4EyHo/j2rVr+PTTT9nAJBAIwGw2D6VxSVV9ClgTExNMmspms2ymW6/XMTU1hatXryIUCnGdhwqipxXbJYl9WnhSqZQzKmJlrqysAOgF4ePjY0gkEqjVarjdbqhUqgHT5NNCqVTC5XJx7en4+BgHBwdoNpuw2WwwGAxwOByQSqXM5OwXBRp1DuLCBAWz2QyVSgWz2YyFhQWOdOl0Gi9evGCPQBpgIoqzz+fjnZOKhlKplNto5JcgEol4aIVedkrnSqUSDg4OePS5Xq9Dp9PBYDDwwu3ns9PRQqPRcIH08PAQm5ubvHudFBQ6nc4AlXt6ehoejweNRgOZTIbnAkhdp1+n8TQvMv3ZarWKQqEAiUSChYUFhEIh1hbc2dlBNpvF6uoq3xNyipbJZCP5BtCLT0xTrVaLvb09/t3vv/8+/H4/fvOb36Ber8Nut0Oj0QzFFdDpdAPW8uTDaDabIRKJ+LhwdHQEn88Hi8XCRxkAbN562p2b3qViscieFTqdDpcvX0Yul8PU1BSmp6cBgOdEvF4vbDYbe1+MAiLF0SZGQ3Dz8/Mwm81otVqIx+Msna/X6zkrO4vm5YUJCiTtTmQgerl2d3dZLbdWqyGbzeLu3bvcpjSbzZwy0Tg10EszlUolpqen0e12EY/H+bvoZZdKpWy3RYYuFDgODg54twHAgprErIzFYpBKpZDJZCiXy7hz5w7K5TLeeecdACc7G6fTac6Gms0mZmdneST28PAQWq2WjUNJIryfzXZa0MvTaDRYdHZiYoJt5HQ6HVZXV/mzKRi/zh4dFmTGks1mmTHqdDrx05/+FMfHx3j69CkUCgVPuw7DxiO1ZAp8Op0OFouFj290fnc6nZidnYXf7x9Qx+p0OkPNCZBZC/DqyEnvzPb2NvtMAGDyncViGdpp63U0Gg223gN6AYfmNYLBIFKpFLRaLRYWFljsl7ozZxFzvTBBQa/Xc1Rst9u806bTaSbypNNpxGIxaDQaNsnY3d3lm0bDSnQ+bbVaCAaD2NnZQbVa5RSZXnZSxKUdOpFIoFwuw2QyIRQKcfGG/k5/0FAqlQgEAkin09ja2sLx8TFWVlb4HE2Fwx/C8fExB5zJyUl0u10kk0kUCgUcHh4yU81ms0Gn042cChIj0mazodPpwGAw8Og3TU36/X5euOTBcBa5fEKpVML+/j7/71u3bkEul+Pzzz+HIAiw2+3Q6XQDtmvDXBdBJpNBJpMN1FqoEDgxMTEQGIHecx82lafjmF6vR7fbZRbr1NTUgF1gOp1mavVZJxa1Wi06nc6AVwYpSkejUbTbbfazlMlkbMUH/JlkCiKRCBqNhgtwdHFzc3NMD7VarZiamkI2m4UgCDg4OIBWqx2gtyYSCVZRIjpsOBzm9A54lVq3Wi1+KYHegz86OsLOzg6ftakARzRom83G04mPHj3Cw4cPIZVKsby8jJmZGX4RDAYDHw9ehyAIKJfLvLuYzWZUq1VotVqeuFSpVHzuPY8ZeeoI0OyGSCTi2kkgEOAKN83mnwdDjkRBiA9gtVqxubnJAZeOKOcpT07oH7iqVCpcKwF6z3LUe0q/lYLK5OQk5HI5bwDEco1EIjCbzWe9DM7w6HfPzs5iamqKtSckEglcLhdn1392egp0kVqtltNYr9cLoBf5aQZeJpMhl8shEAiwAhHQq5wXi0WkUik+p0okEjZv6f8eoJd20riuz+eDy+XiXdRsNvP3AGCx2Gazic3NTSQSCVitVh4eIibmaWzP0uk0kskkswhNJhPkcjnS6TQePXoEi8VyrgHhTaAugcfjgVwu50yBMouzZgqkctztdnH16lUAvexpb28Ph4eHrKtwHhnJm9DPaE0mk2wyC4CPfecBIoLR+1Wv13F0dDTAWB0V3W4XBoOBqfY2mw3hcBibm5v43//9XywvL0OhUAxdED4JFyoo9ON1Sielv2TLpdPp+EWmtJDOpjqdjum2ND77pp2PilKlUom7C16vF4FAgM/3tDN0Oh10u12W/JqdneWugNFo5BrHaXbYer2Oubk5vPfeewB6hbLNzU3853/+J+r1Ot555523TkCeF2jRRKNRLtoRs/KsBjv1eh31eh0LCwucye3u7uLbb79FrVbD3Nwc9Hr9n+wa+3v7xLeg94Q2n/MAmb7S/SsUCnA4HJicnDyzwQ1NstIcUKlU4vb19PQ0Ll++fO6Sb8AFDgqvoz+L6HQ60Gg0sNvtaLVafDanRdt/5qPgQN6Cb/pcCiKNRgMKhQLZbBbNZpPlvYDewiWCkiAIvHioa9LPOzgJJpMJi4uLXK/4+uuvkU6nYTAYcOPGDZhMJhgMhj9JWk2gYZpLly6hXC6zwlSj0cDKysqZX2iNRoPp6WkeHAKAL7/8EoeHh1hYWBjZ9/O0IF9Gg8GASqWCarXK13Seblsk/0fHCYlEwoXbsz6/TqfDmxXQy+6sVivTwqkAfd4Y05zHGGOMAfxoMoV+kHw7+Uj2n0t/aDLxbaAdRC6XY2JiAhMTE1y/oCyEOhwikQgikQhqtRoymWwkkohYLIYgCNjY2ADQa6U6HA7mRmi12nMxXH0bDAYD1Gr1gCQ5AHbyPut3dzodWCwW1Ot15gfI5XIsLS1x/eW8/TH7QWrOXq8Xn332GZ4/f/697tN5gASA+olRNpuNFbrOgn5/TqBXC2k2mzAajVwM/lNkCmMvyTHG+AvBj95L8k+BYX0CW60WFzOBHvkmEAig0+kgnU6fOIt/kT0QXwdVyh89egSNRoO//du/RSKRQCwWg1arHXCGPo2XJInQplIp7g7pdDrMzMzA4/FAqVSiUCggk8mg0Wi8lat/mmtrt9vY3d3lOhPNzmg0GszPz3NtAQB3kUb9rn7UarUBcRyfz4f5+XlIJBJWRzrrtRFICm57e5uZv3a7nYlier1+oP4wqjHMhQgKp4EgCMhkMqjVauh2u9DpdPB6vXC73QPKSyR+eh6o1+ssT+5wOGCxWPjFIiGXi5BpnQfoBUokElhbW4PNZsOTJ0+wu7sLj8fDsyCnQbvdRjQaxfHxMSqVCovZ0lzHixcveDKUyDeVSgW1Wm3kF5lS7cuXLyMYDCIcDuP4+Bg2m40nbGdnZwH0nmWpVDrTwiHE43Fsb29zQXpmZoYp7OftcJVIJBAKhWCz2fDxxx+jXq9jY2ODA4LL5frzlXjvBxF9jo+Pkcvl4HK52NZsYmICMpkMh4eHAHrThmKx+Nx60J1OhyvkNPEWCoWQy+VGZuL1g+Yq0uk0isUiZDIZLBbLAKGHZNNkMhnv0Nls9tSBr9FooNVqMcNOJpOh1Wp9b7SWgh3NPlQqFRwfH/NnDAMaQye5PGJ5kixasViEWq2G2WxmUZKJiQkO/PQ7hkG320Wn00EkEkGpVML169d5cIlsAmh+Ra/XY2ZmBgaDAYVCYeTAUKvVEI1GYTKZcOnSJf7v5DdBZ/+z1jDoWUciEcjlcszMzEClUiEUCuHo6AiJRAJerxf5fJ45If0t2GFxYYMCLYCjoyNUKhUYDAb85Cc/gcPhwPHxMUKhEJ4/f46DgwN+uW/cuAGtVssDIsOCCotEeAJeDUKRSGoymWQq9FketiAInIWQCvHi4iIADOyyN2/eZJ+E/rmOk2i61Dc/ODhAtVrFzMwMfD4fNBoNisUi+2ISwuEwgF52pFAokMvlBuTzT5sR1et19lyYnp5GpVJhT4RUKgWVSsXjzUdHRyyfRqpGpJ5ULBaHkkgXi8U8kSkWi5k45XQ6Ybfb8eTJEw40U1NTiMViPIFYLpdHCgz5fB6lUmmAQEc6G6R/QUNao4DG50kEuFKp4K//+q/xV3/1V1hfX8edO3fw+9//HgsLC/D7/chms/xO+f3+kb0lL2RQIPFUoLdbf/TRRzyvvr+/j2+//ZZtuLPZLBNtHjx4ALvdzmzEYUF+ATqdDu12G+vr60gmkwB6UZoGVIhnLpPJRjL8IC1HWjwfffQRPv30UxwdHeH27ds4PDxEKBTigaXt7W2EQiGmXJ9G1ouCGcnVOZ1OiEQiHB0doVQqfS+wUBB1u92wWq0IBoPsU3jaDgvNUyQSCZ6hODw85Jc6EomgWq2yLiZpYuZyObjdbkxOTrJPB3WUhgkMKpWKDVPIL4Q4CkajkRdnsVjEo0ePWG+RNEGHWUDdbpd/m8lk4mxVpVIhHA6jXC4zsW3UTk4+n8f29jb/7pWVFbz//vvY3NzEf/3XfyGbzWJ+fp7FWyORCA/Zud3ukQloFzIo7O3t8Y24fPky5ubmkMvlcHh4iPv37w845ZAzMgCW3h5VHJOOBI1GA9lsFp1OB263GwBY60EqlbIh7cLCwtAEHBIAjcfjeP/99wEAv/jFL/D48WP8+te/5nFYUpEyGo347rvvBkZoafT3baAXnAhYSqUSe3t7OD4+Znn4ftDn9RfkaIjotAIhyWQSkUgEUqkUgUAAyWQSoVCIAytR1QGwYzJ5Z1CwoDFlMrsZdkHRkFun02EdDqKu07n/4cOH6HQ6ePHiBVPcFQrFUAGo3W7zdQGvCFFUQBUEAcVi8VTP6oeQSCR46AroZTgPHjzAnTt3oFQqeS6IJO2dTidnLJVKhd/lYXEhg0Kz2eSCSbfbxcHBAas4kxU8GXnabDZOOYm3P+qDqNVqLMZqMBjYXBUAOyjv7u6iVqudSjz1TSCa9M2bN7G2tgYA+Pbbb/GrX/2Kx4hTqRSUSiUmJyext7eHnZ0dmM1mPm+fJlOgl6NcLrPjciqV4im+152laLScjl8ajQb5fJ4l4U5Co9Fgx6LZ2VkIgoB0Os2qygB47Jf+SaVSbP/m8/nQ7XY582o0GlhYWBh5NoICikwmg1qtRrPZ5J3Tbrfzgj06OuL7PkwAarVayGazXC+h41UymUQ+n+dJ07PwPWg+hT4jm82yqa3dbudM8vj4mG0NaCqVdD5GWQcXMihkMhmuFNMiSiaTbBZqMBjg8Xh4EdPLbbPZoFarR+4I0K5POwupHQO9dFyr1bJpiVwuH3jRTguVSoWFhQUsLi6y+Mhvf/tbSKVSTE1NIZ1OQ6PR4L333sPa2hr++Mc/8oASne9PczSiHVckEvG/03lXLpezvuDrf54+n2TZTmOr1u12kclkkMlkYLPZoNFoEAqFuCBMmRwVZqng2Wg0OOsjW3d6iZVKJQ+GnRUikQgmk4mPVHK5nGXvdnZ2UK/XcevWraEXEGk8lkolDsIk3ENzMKPWE2q1GnK5HCYmJvh3KRQKRCIR9kgNBAKYmJhANBqFWq2G1Wrl4wPVqUa5fxcyKACv0jEyK/3mm29gMBhgt9uxvLwMQRBw//59Np8FXu2gmUyGU+BRQDtjv56C3+/H/v4+p7+jpoQ0mpzL5bgoRO1V8oY0Go14//33eUpTLBZDrVYP9YDp+EBzIGq1msVF+ufu6c9SIKVaQC6X44Ij1TJ+CM1mE5FIBM1mEy6XC/l8noPJ/v4+p9kajYaDG41xkxPV4eEhisUi62g0m028fPmS25lnBc2tAL2aDhn2ks1cNps90UujH+12G/V6nUV2KGCXSiU+bikUipEzhVwuh3K5jNnZWf5spVKJRCLBLFuaotVoNOh2u5DL5RyciCcySlAYzz6MMcYYA7iQmYLP5+O0PZFI4NGjR3j27BmWl5cRi8XYCkylUmFpaYklz7e3t3F0dHQuo7/AqzYX/Xu1WuWquU6nG+k72u02O0FRthEIBFCtVtFoNCAIArxeL+bn5/HixQtEIhFoNBqo1eqhZgXIko5UsPV6PX83GbX0/1liAM7NzbFdfCaTGTA1/SGkUinE43HWrnz27BkrT2WzWW7vOhwO7i5IJBIUCgW0Wi2Ew2F4PB4kk0neFanzodPpvsfvbzabqNVqfCzpdrts42exWCCTyVCr1VAqlQZa05Q9JZNJBINBrK6u4uc//zm+++47PH/+nB24hkG32+XsCwBnD8DZxFyoy9U/31AsFtmYl7IEkUjEDM5+2TiS9h8FFzIoOJ1OLjTabDa43W7cv38fsViM5/HJ3FWr1XKR7PHjx0in05iZmTmXoFCr1fgoIQgCRCIRj02PChqzbrVa/LBJ8v3o6AjdbhfXr19Ho9HA1tYWSqUS3G43U41PCzo/q9VqBAIB5PN5pNNptjPvf1krlQp/ttvtZpFbkhF/W6uOREVEIhHm5uaQSqW4oxAMBtFsNrnQ6HK5WGeAfBH39/ehVCpZYJXurSAIaDQaKBQKfJ+azSZyuRzi8TgajQbrQUxMTHD34Pj4GDKZDFarFX6/n01/U6kUk7Gi0Sjcbjd+8YtfQK/XI5FIIBqNwuVy/UmHtE4Lug69Xg+73c5H2J2dHTSbTRaoIW8NGqk2GAwD9aY/K5qzVCrlC5qcnEQ+n8fKygp75h0cHOD4+Bgulwtut5tbTZFIBLVajY1DzwKSf6fFI5fLIZPJWEX4rDbjrxutiMVi5PN5ZqyRHiV5WAyjwtRqtbiKT4GTvApsNhuLz5L+X61W4xdPrVazCQlRu9+2UEqlEmq1GtdzYrEY3G43i4uqVCrO+sj0ha6j2+2y1uD09DQKhQJnJYVCAY1Gg7OBQqHAlnMGgwELCwscFLrdLiqVCvfpxWIxZmdn4XQ6odfrUa/Xsbu7y8IrN2/exPLyMpRKJR48eIDnz59DpVJBEIRTB4V6vc6dKqVSeW4sWrqnzWYTbrd74B6QYhX9Q1OTCoWC3dPpz55Fy+FCBgUyWAVe+QJeu3YNlUoFT58+RTAYhFarxcTEBLrdLrsbPX78GPPz8+cyj0Ay6HRzKQVuNBpsQ3+eoBT82rVrcDgcuHv3LiKRCPtMDpOdUFYA9LIumUzGdFhSoyoUCgOuVAsLCwB6QSQYDCIUCmFychI+nw+BQADFYpEFbPpBxjUmkwmxWAytVos9GORy+cDi7Q9spPQskUjg9Xq5iEbjzblcjo9UQI+ZmUgk4HA4cP36dZhMJr5G8rDIZrNoNBoolUoIh8NQq9Vs/OJwOPDxxx8DABccv/zyS9y/fx/VahULCwtDFY8zmQwHhEajwZsQGbKUy+Whqdr995QUq+kagd7xwWw2Q6lUsrp3s9lEs9mEXC7nQH9WXMigQPRQ+vdkMolOp4NcLsfOPpOTk9BqtUin09jc3AQANv88D92/crkMuVz+Pf8Eag+eZ1CgHr9UKsW1a9dQr9eZGqzT6YYWN81ms7xz6XQ67Ozs4O7du8jlcvD7/czYpLZrrVbjqv/e3h7u3r3LTECqKxAjkGoVBCIk0dmaji3VahVut5t9N4BXsvfdbpfPx0BvroQq5bQwi8XiAF+lWCzC4XBgaWkJCoUCiUQCjx8/BtCTeaOFSb4LtJNXKhUsLS1hamqKF24sFsOdO3fw8uVLqFQqXL58mTUxT7uhUH1JIpGgWCyylihlkuTMNQq63S60Wi1bCPbPpWg0GtaYJKJbuVxmLwr6TtpUR8GFDArAoBCGzWbjXnMsFoPX64XH42FHIorIxKMfxcTkdbRaLajV6gEWIUVkEng5L9Bcg9vthsvlQjAYZPvyUYQ56/U6t9c6nQ62trbQ6XSYGScIAiQSCVPFy+UyC6FYLBbMzc2xVyPZxSuVSpjN5u/9Fjrby+VyJJNJFItFSKVS/g0Wi4WPD/RM6/U69vb2EIvFsLCwAL1ej6OjI36+QC9zcrvdfB3lchmXLl2Cqt0b3QAACRtJREFU3+/H9vY2vvjiCw4qRBQqFAooFouoVCr4yU9+Arvdjs3NTW57bm1tAehZ12cyGRZiIU8Pg8HABdeTQK3jWq3GE570LMmYZtQiY6fTQbPZRLlchs/n4w1BLBZzO1IsFiOTyeDw8BB6vR5OpxPVapUDyJ+FbdzbQC8T9X+npqZgMplgtVqRz+cHdnMix5wVRGmmszZFbkEQ2PDjrKC6RCqVQr1eZ3XeYDCIfD4Pv98/EnOyWq3yPanVamyMajQa0W63ORi0221sbm4iHo9zXcbpdKJSqeDo6AgSiYRJRSaTCQ6H43udCJPJhFarhUKhwB2BTqfDxj6CIHAgIYVn8sdcW1uD3+9HKBTic/GDBw8AgHn9dP9pgdBcTDab5ToGZT1WqxXz8/OwWCxYXl5GJBLhjOTFixecsXi9Xh5wMxgMHBSGycbq9To0Gg1qtRqTvOgatVotxGLxyBqUlOWQqQwZ6RKDl6jUwWAQnU4Hy8vLEIvFXIAF/gKCAkEQBDZ1ocrs8fExT1SaTKZTUYBPg3q9PuBJmc/nkUgkWH7rPCS9+j0WdTod5ufnkUgkEAwG2fRjlIIpybsBrwgvUqmUC7E0G9BqtZBKpZDL5XjkViKRYGNjg1ujtJCMRiOL2b4OSmXJYLbRaPBRj9qHAHgk22QyweVyQSaTIR6PQyqVwmg04uXLl0zTnZ6ehtlsZvMaoPcMXrx4gcePH0Oj0bD0ntlshsvlgsfjYb5/vV7Hs2fPEA6HUSqVMDc3x7tos9nkTLPfB2NYUDsQGHQZp2LqqJsTmcmIRCIcHx9zkZgCJek1KBQKLC8vY3JyEqlUChMTE5zpUP1hFPyogkI+n4cgCEyLzWQy2N7e5uLU1NTUuekattttaDQafthUFCP22Hmg31Pi6tWrMJvNuH37NpLJJLchR6mPOBwOZiHW63W2To9EInzuNBgMUKlUiEQicLvdWFpaAtALKCaTCRqNBjqdjo1fVSrVG4tYVquVpxGlUimq1SpX8SuVCur1OgcWh8PBsykHBwf8XQBw+/ZtZLNZfpY096BWq9FoNNhQVSKRwO/3c/oO9FqdNHFZLBZZjKRSqbABsVqt5npHLpfD5OTkn0xGn3Q8R904iHOhUCgQi8V4YrjdbiMcDkOhUECr1XIQjEQiUCgUSCaTfEQKBAIjB6UfVVCglI2IL1qtFvl8ntswXq8XS0tLkEgkA1OFw4JSbKVSyS8t7TKNRuNc6gmdToc1DDqdDi5fvoxyuYzd3V32BRzVOVgqlfICoDafy+WC3W5n4Q/aHcViMRwOBz799FMAwK9//WscHBxgZmYGly9fhtVqZZUp4PsmN0SzBXriJT6fD8+ePYNMJuMgQK3AZrPJZCqVSoWJiQmEw2E8fPgQsVgM8/PzTG4idy9K6aemphAIBJDNZuFwOPD48WP+/yiDo8DXbrdhMBjg9XpRKpVgtVrh9Xo5CJON4Fmg0+l4urNYLPJZ/zxABc98Ps8j+gC40EsmxPSOms1mFItF3L17lwO33+8fOeCNac5jjDHGAH5UmQJVugVBgNlsxsOHD/Hw4UPcunULQO8cSopIJDk2CmhHo7QZAGvu0bjqWVGtVvn853K5YLPZEAwGkclkeGDoNBTjN0Gn0/HfIwYjgVpXKpUKUqkUdrsdk5OTLFBbr9exsrLCo7kn7TbEjqSJRrFYjGAwiEQiwcXA/uxCqVRyb/327dvY3t6GVCrF6uoqm+oC4GyC/q5MJsPW1haSySTTtGlXpEnMarXKbs+kGkXHKIPBwIVdv98/MAQ2Cmw2GzKZDEqlEtPIgd5uThyXs4C4CDqdDr/85S8BAJubm6hUKgiHwzxwJZPJEAqFsLOzg1wuh5s3bwLAgIXhsPhRBQWFQgGPxwOJRIIHDx7g0aNHWFhYwCeffAKg9yJtbW3h4OCAe+SjQCKRwOfzYXV1lV/S3//+93j58uW52XSRrBsAPs8fHBwwRfd1yuow6P975MtJxVGiGAOv9A2Oj48H5jAooJ726GK1WlGpVKBUKmG32/Hee+8hmUyi3W4P6ElGIhHk83nYbDYODHa7HYFAABaLhV2PAHDrl2CxWJga3m632VuBQNOXZE6s1Wp5hoMIVhTgHA4HarUaF0dHvcdarRaBQAB6vZ7P/evr65ibm2OrvFFA06NarZb1Nega6RnK5XLmMLRaLWg0Gly6dIn5ElQPGgU/qqBAL0aj0cCtW7fQarVgNBp5BHl9fR31eh3T09NnIjBJJBIEAoGB+fR8Po+5uTlYLJYzB4Z+shLQyxQqlQo6nQ7q9TrK5TIrPp0VtLDe1G4Ti8Vcwe/vENAiPu2CoV2rVqtBpVLhypUrKBQKiEajkMvlvKMLggCLxQK3282jviaTCWazmV/iH8pM6L8bDAZW835T4ZPqH/2fQ/eZsr5+W8FRQZmM2+3GwcEBC6JMTk5icXHxTO+fSCSCwWDgzhBllA6Hg4l8dF0mkwkSiYQt8vq7RaMS7H5UQYF2BiKGOJ3OAUbj8vIyrFYrXC4XisXiyH1imgcIhUL8ABYXF3ko6qw8906nA7vdzspL8/PzuHfvHr766is0Gg3Y7fb/t8EcWhhnCXQ0xyGRSFCr1ZDNZpmm63A4eIHSdalUKu7kyGQyNug9zUv8uhX8MDhPFqpEImE6tyAIrPtAmd5ZfTLJaao/wLXbbZhMpu8FQ/ozpJsBnD6gvwk/qqBAKjfpdBqCIECpVGJqaorTJBoPBt5uBX8SpFIpz1VQlZpaeWRWexbQDkOV9sePHzMzzeVywWKx/EnNZf9UIHdvyjZItr2/0k/HGAoIlGWcp5Xb/wckEgnsdjsPzVF25fV6mYh1VtA96c86ztt2/o3f++diZjLGGGOcD8YtyTHGGGMA46AwxhhjDGAcFMYYY4wBjIPCGGOMMYBxUBhjjDEGMA4KY4wxxgDGQWGMMcYYwDgojDHGGAMYB4UxxhhjAOOgMMYYYwxgHBTGGGOMAYyDwhhjjDGAcVAYY4wxBjAOCmOMMcYAxkFhjDHGGMA4KIwxxhgDGAeFMcYYYwDjoDDGGGMMYBwUxhhjjAGMg8IYY4wxgHFQGGOMMQYwDgpjjDHGAMZBYYwxxhjA/wELqgGK150HSQAAAABJRU5ErkJggg==\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x11680d590>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"if USE_LENET_MODEL:\\n\",\n    \"    pyplot.figure()\\n\",\n    \"    # We look into the first conv layer output. Change this to conv2 in order to look into the second one. \\n\",\n    \"    conv = workspace.FetchBlob('conv1')\\n\",\n    \"    \\n\",\n    \"    # We can look into any channel. Think of it as a feature model learned.\\n\",\n    \"    # In this case we look into the 5th channel. Play with other channels to see other features\\n\",\n    \"    conv = conv[:,[5],:,:]\\n\",\n    \"\\n\",\n    \"    _ = visualize.NCHW.ShowMultiple(conv)\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Remember that we created the test net? We will run the test pass and report the test accuracy here. Note that although test_model will be using the parameters obtained from train_model, test_model.param_init_net must still be run to initialize the input data.\\n\",\n    \"In this run, we only need to track the accuracy and we're also only going to run 100 iterations.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 17,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"test_accuracy: 0.954200\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x11673b690>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# param_init_net here will only create a data reader\\n\",\n    \"# Other parameters won't be re-created because we selected init_params=False before\\n\",\n    \"workspace.RunNetOnce(test_model.param_init_net)\\n\",\n    \"workspace.CreateNet(test_model.net, overwrite=True)\\n\",\n    \"test_accuracy = np.zeros(100)\\n\",\n    \"for i in range(100):\\n\",\n    \"    workspace.RunNet(test_model.net.Proto().name)\\n\",\n    \"    test_accuracy[i] = workspace.FetchBlob('accuracy')\\n\",\n    \"# After the execution is done, let's plot the values.\\n\",\n    \"pyplot.plot(test_accuracy, 'r')\\n\",\n    \"pyplot.title('Acuracy over test batches.')\\n\",\n    \"print('test_accuracy: %f' % test_accuracy.mean())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's save the deploy model with the trained weights and biases to a file. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 18,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"The deploy model is saved to: /Users/salex/caffe2_notebooks/tutorial_files/tutorial_mnist/mnist_model.minidb\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# construct the model to be exported\\n\",\n    \"# the inputs/outputs of the model are manually specified.\\n\",\n    \"pe_meta = pe.PredictorExportMeta(\\n\",\n    \"    predict_net=deploy_model.net.Proto(),\\n\",\n    \"    parameters=[str(b) for b in deploy_model.params], \\n\",\n    \"    inputs=[\\\"data\\\"],\\n\",\n    \"    outputs=[\\\"softmax\\\"],\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"# save the model to a file. Use minidb as the file format\\n\",\n    \"pe.save_to_db(\\\"minidb\\\", os.path.join(root_folder, \\\"mnist_model.minidb\\\"), pe_meta)\\n\",\n    \"print(\\\"The deploy model is saved to: \\\" + root_folder + \\\"/mnist_model.minidb\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now we can load the model back and run the prediction to verify it works.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 19,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"The blobs in the workspace after reset: []\\n\",\n      \"The blobs in the workspace after loading the model: [u'!!META_NET_DEF', u'!!PREDICTOR_DBREADER', u'conv1', u'conv1_b', u'conv1_w', u'conv2', u'conv2_b', u'conv2_w', u'data', u'fc3', u'fc3_b', u'fc3_w', u'pool1', u'pool2', u'pred', u'pred_b', u'pred_w', u'relu3', u'softmax']\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Text(0.5,1,u'Prediction for the first image')\"\n      ]\n     },\n     \"execution_count\": 19,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x11674db50>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x1146c7390>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# we retrieve the last input data out and use it in our prediction test before we scratch the workspace\\n\",\n    \"blob = workspace.FetchBlob(\\\"data\\\")\\n\",\n    \"pyplot.figure()\\n\",\n    \"_ = visualize.NCHW.ShowMultiple(blob)\\n\",\n    \"\\n\",\n    \"# reset the workspace, to make sure the model is actually loaded\\n\",\n    \"workspace.ResetWorkspace(root_folder)\\n\",\n    \"\\n\",\n    \"# verify that all blobs are destroyed. \\n\",\n    \"print(\\\"The blobs in the workspace after reset: {}\\\".format(workspace.Blobs()))\\n\",\n    \"\\n\",\n    \"# load the predict net\\n\",\n    \"predict_net = pe.prepare_prediction_net(os.path.join(root_folder, \\\"mnist_model.minidb\\\"), \\\"minidb\\\")\\n\",\n    \"\\n\",\n    \"# verify that blobs are loaded back\\n\",\n    \"print(\\\"The blobs in the workspace after loading the model: {}\\\".format(workspace.Blobs()))\\n\",\n    \"\\n\",\n    \"# feed the previously saved data to the loaded model\\n\",\n    \"workspace.FeedBlob(\\\"data\\\", blob)\\n\",\n    \"\\n\",\n    \"# predict\\n\",\n    \"workspace.RunNetOnce(predict_net)\\n\",\n    \"softmax = workspace.FetchBlob(\\\"softmax\\\")\\n\",\n    \"\\n\",\n    \"# the first letter should be predicted correctly\\n\",\n    \"pyplot.figure()\\n\",\n    \"_ = pyplot.plot(softmax[0], 'ro')\\n\",\n    \"pyplot.title('Prediction for the first image')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This concludes the MNIST tutorial. We hope this tutorial highlighted some of Caffe2's features and how easy it is to create a simple MLP or CNN model.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.14\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 1\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/MNIST_Dataset_and_Databases.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# MNIST Dataset & Database\\n\",\n    \"\\n\",\n    \"In the [MNIST tutorial](https://github.com/caffe2/caffe2/blob/master/caffe2/python/tutorials/MNIST.ipynb) we use a lmdb database. You can also use leveldb or even minidb by changing the type reference when you get ready to read from the db's.\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"## Dataset:\\n\",\n    \"\\n\",\n    \"You can download the raw [MNIST dataset](https://download.caffe2.ai/datasets/mnist/mnist.zip), g/unzip the dataset and labels, and make the database yourself. \\n\",\n    \"\\n\",\n    \"\\n\",\n    \"## Databases:\\n\",\n    \"\\n\",\n    \"We provide a few database formats for you to try with the MNIST tutorial. The default is lmdb. \\n\",\n    \"\\n\",\n    \"* [MNIST-nchw-lmdb](https://download.caffe2.ai/databases/mnist-lmdb.zip) - contains both the train and test lmdb MNIST databases in NCHW format\\n\",\n    \"* [MNIST-nchw-leveldb](https://download.caffe2.ai/databases/mnist-leveldb.zip) - contains both the train and test leveldb MNIST databases in NCHW format\\n\",\n    \"* [MNIST-nchw-minidb](https://download.caffe2.ai/databases/mnist-minidb.zip) - contains both the train and test minidb MNIST databases in NCHW format\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"## Tools:\\n\",\n    \"\\n\",\n    \"### make_mnist_db\\n\",\n    \"\\n\",\n    \"If you like LevelDB you can use Caffe2's `make_mnist_db` binary to generate leveldb databases. This binary is found in `/caffe2/build/caffe2/binaries/` or depending on your OS and installation, in `/usr/local/bin/`.\\n\",\n    \"\\n\",\n    \"Here is an example call to `make_mnist_db`:\\n\",\n    \"\\n\",\n    \"```\\n\",\n    \"./make_mnist_db --channel_first --db leveldb --image_file ~/Downloads/train-images-idx3-ubyte --label_file ~/Downloads/train-labels-idx1-ubyte --output_file ~/caffe2/caffe2/python/tutorials/tutorial_data/mnist/mnist-train-nchw-leveldb\\n\",\n    \"\\n\",\n    \"./make_mnist_db --channel_first --db leveldb --image_file ~/Downloads/t10k-images-idx3-ubyte --label_file ~/Downloads/t10k-labels-idx1-ubyte --output_file ~/caffe2/caffe2/python/tutorials/tutorial_data/mnist/mnist-test-nchw-leveldb\\n\",\n    \"```\\n\",\n    \"Note leveldb can get deadlocked if more than one user attempts to open the leveldb at the same time. This is why there is logic in the Python below to delete LOCK files if they're found.\\n\",\n    \"\\n\",\n    \"TODO: it would be great to extend this binary to create other database formats \\n\",\n    \"\\n\",\n    \"### Python script\\n\",\n    \"\\n\",\n    \"You can use the Python in the code block below to download the dataset with `DownloadResource`, call the `make_mnist_db` binary, and generate your database with `GenerateDB`. \\n\",\n    \"\\n\",\n    \"The `DownloadResource` function can also download and extract a database for you.\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"**Downloads and extracts the MNIST dataset**\\n\",\n    \"The sample function below will download and extract the dataset for you.\\n\",\n    \"```python\\n\",\n    \"DownloadResource(\\\"http://download.caffe2.ai/datasets/mnist/mnist.zip\\\", data_folder)\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"**Downloads and extracts the lmdb databases of MNIST images - both test and train**\\n\",\n    \"```python\\n\",\n    \"DownloadResource(\\\"http://download.caffe2.ai/databases/mnist-lmdb.zip\\\", data_folder)\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"**(Re)generate the leveldb database (it can get locked with multi-user setups or abandoned threads)**\\n\",\n    \"Requires the download of the dataset (mnist.zip) - see above.\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"GenerateDB(image_file_train, label_file_train, \\\"mnist-train-nchw-leveldb\\\")\\n\",\n    \"GenerateDB(image_file_test, label_file_test, \\\"mnist-test-nchw-leveldb\\\")\\n\",\n    \"```\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import os\\n\",\n    \"\\n\",\n    \"def DownloadResource(url, path):\\n\",\n    \"    '''Downloads resources from s3 by url and unzips them to the provided path'''\\n\",\n    \"    import requests, zipfile, StringIO\\n\",\n    \"    print(\\\"Downloading... {} to {}\\\".format(url, path))\\n\",\n    \"    r = requests.get(url, stream=True)\\n\",\n    \"    z = zipfile.ZipFile(StringIO.StringIO(r.content))\\n\",\n    \"    z.extractall(path)\\n\",\n    \"    print(\\\"Completed download and extraction.\\\")\\n\",\n    \"\\n\",\n    \"    \\n\",\n    \"def GenerateDB(image, label, name):\\n\",\n    \"    '''Calls the make_mnist_db binary to generate a leveldb from a mnist dataset'''\\n\",\n    \"    name = os.path.join(data_folder, name)\\n\",\n    \"    print 'DB: ', name\\n\",\n    \"    if not os.path.exists(name):\\n\",\n    \"        syscall = \\\"/usr/local/bin/make_mnist_db --channel_first --db leveldb --image_file \\\" + image + \\\" --label_file \\\" + label + \\\" --output_file \\\" + name\\n\",\n    \"        # print \\\"Creating database with: \\\", syscall\\n\",\n    \"        os.system(syscall)\\n\",\n    \"    else:\\n\",\n    \"        print \\\"Database exists already. Delete the folder if you have issues/corrupted DB, then rerun this.\\\"\\n\",\n    \"        if os.path.exists(os.path.join(name, \\\"LOCK\\\")):\\n\",\n    \"            # print \\\"Deleting the pre-existing lock file\\\"\\n\",\n    \"            os.remove(os.path.join(name, \\\"LOCK\\\"))\\n\",\n    \"\\n\",\n    \"            \\n\",\n    \"current_folder = os.path.join(os.path.expanduser('~'), 'caffe2_notebooks')\\n\",\n    \"data_folder = os.path.join(current_folder, 'tutorial_data', 'mnist')\\n\",\n    \"\\n\",\n    \"# Downloads and extracts the lmdb databases of MNIST images - both test and train\\n\",\n    \"if not os.path.exists(os.path.join(data_folder,\\\"mnist-train-nchw-lmdb\\\")):\\n\",\n    \"    DownloadResource(\\\"http://download.caffe2.ai/databases/mnist-lmdb.zip\\\", data_folder)\\n\",\n    \"\\n\",\n    \"# Downloads and extracts the MNIST data set\\n\",\n    \"if not os.path.exists(os.path.join(data_folder, \\\"train-images-idx3-ubyte\\\")):\\n\",\n    \"    DownloadResource(\\\"http://download.caffe2.ai/datasets/mnist/mnist.zip\\\", data_folder)\\n\",\n    \"\\n\",\n    \"# (Re)generate the leveldb database (it can get locked with multi-user setups or abandoned threads)\\n\",\n    \"# Requires the download of the dataset (mnist.zip) - see DownloadResource above.\\n\",\n    \"# You also need to change references in the MNIST tutorial code where you train or test from lmdb to leveldb\\n\",\n    \"image_file_train = os.path.join(data_folder, \\\"train-images-idx3-ubyte\\\")\\n\",\n    \"label_file_train = os.path.join(data_folder, \\\"train-labels-idx1-ubyte\\\")\\n\",\n    \"image_file_test = os.path.join(data_folder, \\\"t10k-images-idx3-ubyte\\\")\\n\",\n    \"label_file_test = os.path.join(data_folder, \\\"t10k-labels-idx1-ubyte\\\")\\n\",\n    \"GenerateDB(image_file_train, label_file_train, \\\"mnist-train-nchw-leveldb\\\")\\n\",\n    \"GenerateDB(image_file_test, label_file_test, \\\"mnist-test-nchw-leveldb\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Code Changes for Other DBs\\n\",\n    \"\\n\",\n    \"If you chose to use a format other than lmdb you will need to change a couple lines of code. When you use `ModelHelper` to instantiate the CNN, you pass in the `db` parameter with a path and the `db_type` with the type of db. You would need to update both of these values. Since you create two networks, one for training and one for testing, you would need to update the code for both of these.\\n\",\n    \"\\n\",\n    \"**Default code using lmdb**\\n\",\n    \"```python\\n\",\n    \"train_model = model_helper.ModelHelper(name=\\\"mnist_train\\\", arg_scope=arg_scope)\\n\",\n    \"data, label = AddInput(\\n\",\n    \"    train_model, batch_size=64,\\n\",\n    \"    db=os.path.join(data_folder, 'mnist-train-nchw-lmdb'),\\n\",\n    \"    db_type='lmdb')\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"**Updated code using leveldb**\\n\",\n    \"```python\\n\",\n    \"train_model = model_helper.ModelHelper(name=\\\"mnist_train\\\", arg_scope=arg_scope)\\n\",\n    \"data, label = AddInput(\\n\",\n    \"    train_model, batch_size=64,\\n\",\n    \"    db=os.path.join(data_folder, 'mnist-train-nchw-leveldb'),\\n\",\n    \"    db_type='leveldb')\\n\",\n    \"```\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.11\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/Model_Quickload.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"deletable\": true,\n    \"editable\": true\n   },\n   \"source\": [\n    \"# Model Quickload\\n\",\n    \"\\n\",\n    \"This short notebook will show you how you can very quickly load and test SqueezeNet, which is a very small and fast model based on AlexNet and is useful for identifying objects. The range of objects groups is only 1,000.\\n\",\n    \"\\n\",\n    \"Before this script will work, you need to download the model and install it. You can do this by running:\\n\",\n    \"\\n\",\n    \"```\\n\",\n    \"sudo python -m caffe2.python.models.download -i squeezenet\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"Or make a squeezenet folder, download each file listed below to it, and place it in `/caffe2/python/models/`:\\n\",\n    \"* [predict_net.pb](https://download.caffe2.ai/models/squeezenet/predict_net.pb)\\n\",\n    \"* [init_net.pb](https://download.caffe2.ai/models/squeezenet/init_net.pb)\\n\",\n    \"\\n\",\n    \"The helper functions will look up the top object detection result for you by searching through this [inference codes file](inference_codes.txt). If you want to see how well the model detects samples different than provided here, take a look at the codes, and find an image url to an image of an object in the list of codes.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"deletable\": true,\n    \"editable\": true\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Raw top 3 results: [array([985.0, 0.9822268486022949], dtype=object), array([309.0, 0.011943698860704899], dtype=object), array([946.0, 0.004810151644051075], dtype=object)]\\n\",\n      \"The image contains a daisy with a 98.2226848602 percent probability.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# load up the caffe2 workspace\\n\",\n    \"from caffe2.python import workspace\\n\",\n    \"# choose your model here (use the downloader first)\\n\",\n    \"from caffe2.python.models import squeezenet as mynet\\n\",\n    \"# helper image processing functions\\n\",\n    \"import helpers\\n\",\n    \"\\n\",\n    \"# load the pre-trained model\\n\",\n    \"init_net = mynet.init_net\\n\",\n    \"predict_net = mynet.predict_net\\n\",\n    \"# you must name it something\\n\",\n    \"predict_net.name = \\\"squeezenet_predict\\\"\\n\",\n    \"workspace.RunNetOnce(init_net)\\n\",\n    \"workspace.CreateNet(predict_net)\\n\",\n    \"p = workspace.Predictor(init_net.SerializeToString(), predict_net.SerializeToString())\\n\",\n    \"\\n\",\n    \"# use whatever image you want (urls work too)\\n\",\n    \"# img = \\\"https://upload.wikimedia.org/wikipedia/commons/a/ac/Pretzel.jpg\\\"\\n\",\n    \"# img = \\\"images/cat.jpg\\\"\\n\",\n    \"# img = \\\"images/cowboy-hat.jpg\\\"\\n\",\n    \"# img = \\\"images/cell-tower.jpg\\\"\\n\",\n    \"# img = \\\"images/Ducreux.jpg\\\"\\n\",\n    \"# img = \\\"images/pretzel.jpg\\\"\\n\",\n    \"# img = \\\"images/orangutan.jpg\\\"\\n\",\n    \"# img = \\\"images/aircraft-carrier.jpg\\\"\\n\",\n    \"img = \\\"images/flower.jpg\\\"\\n\",\n    \"# average mean to subtract from the image\\n\",\n    \"mean = 128\\n\",\n    \"# the size of images that the model was trained with\\n\",\n    \"input_size = 227\\n\",\n    \"\\n\",\n    \"# use the image helper to load the image and convert it to NCHW\\n\",\n    \"img = helpers.loadToNCHW(img, mean, input_size)\\n\",\n    \"\\n\",\n    \"# submit the image to net and get a tensor of results\\n\",\n    \"results = p.run({'data': img})   \\n\",\n    \"response = helpers.parseResults(results)\\n\",\n    \"# and lookup our result from the inference list\\n\",\n    \"print response\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"deletable\": true,\n    \"editable\": true\n   },\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.12\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/Multi-GPU_Training.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Multi-GPU Training with Caffe2 \\n\",\n    \"\\n\",\n    \"![caffe2 imagenet logo](images/imagenet-caffe2.png)\\n\",\n    \"\\n\",\n    \"For this tutorial we will explore multi-GPU training. We will show you a basic structure for using the `data_parallel_model` to quickly process a subset of the ImageNet database along the same design as the [ResNet-50 model](https://arxiv.org/abs/1512.03385). We will also get a chance to look under the hood at a few of Caffe2's C++ operators that efficiently handle your image pipeline, build a ResNet model, train on a single GPU and show some optimizations that are included with `data_parallel_model`, and finally we'll scale it up and show you how to parallelize your model so you can run it on multiple GPUs.\\n\",\n    \"\\n\",\n    \"## About the Dataset\\n\",\n    \"\\n\",\n    \"A commonly used dataset for benchmarking image recognition technologies is [ImageNet](http://image-net.org/). It is huge. It has images that cover the gamut, and they're categorized by labels so that you can create image subsets of animals, plants, fungi, people, objects, you name it. It's the focus of yearly competitions and this is where deep learning and convolutional neural networks (CNN) really made its name. During the 2012 ImageNet Large-Scale Visual Recognition Challenge a CNN demonstrated accuracy more than 10% beyond the next competing method. Going from around 75% accuracy to around 85% accuracy when every year the gains were only a percent or two is a significant accomplishment. \\n\",\n    \"\\n\",\n    \"![imagenet montage](images/imagenet-montage.jpg)\\n\",\n    \"\\n\",\n    \"So let's play with ImageNet and train our own model on a bunch of GPUs! You're going to need a lot space to host the 14 million images in ImageNet. How much disk space do you have? You should clear up about 300GB of space... on SSD. Spinning discs are so 2000. How much time do you have? With two GPUs maybe we'll be done in just under a week. Ready?\\n\",\n    \"\\n\",\n    \"![one does not simply train imagenet in a minute](images/imagenet-meme.jpg)\\n\",\n    \"\\n\",\n    \"That's way too much space and way too long for a tutorial! If you happened to have that much space and 128 GPUs on the latest NVIDIA V100's then you're super awesome and you can replicate our recent results shown below. You might even be able to train ImageNet in under an hour. Given how this performance seems to scale, **maybe YOU can train ImageNet in a minute!** Think about all of the things you could accomplish... a model for millions of hours of video? Catalogue every cat video on YouTube? Look for your doppleganger on Imgur?\\n\",\n    \"\\n\",\n    \"Instead of tons of GPUs and the full set of data, we're going to do this cooking show style. We're going to use a small batch images to train on, and show how you can scale that up. We chose a small slice of ImageNet: a set of 640 cars and 640 boats for our training set. We have 48 cars and 48 boats for our test set. This makes our database of images around 130 MB.\\n\",\n    \"\\n\",\n    \"## ResNet-50 Model Training Overview\\n\",\n    \"\\n\",\n    \"Below is an overview of what is needed to train and test this model across multiple GPUs. You see that it is generally not that long, nor is it that complicated. Some of the interactions for creating the parallelized model are handled by custom functions you have to write and we'll go over those later.\\n\",\n    \"\\n\",\n    \"1. use `brew` to create a model for training (we'll create one for testing later)\\n\",\n    \"2. create a database reader using the model helper object's `CreateDB` to pull the images\\n\",\n    \"3. create functions to run a ResNet-50 model for one or more GPUs\\n\",\n    \"3. create the parallelized model\\n\",\n    \"4. loop through the number of epochs you want to run, then for each epoch\\n\",\n    \"    * run the train model till you finish each batch of images\\n\",\n    \"    * run the test model\\n\",\n    \"    * calculate times, accuracies, and display the results\\n\",\n    \"\\n\",\n    \"## Part 1: Setup\\n\",\n    \"\\n\",\n    \"Your first assignment is to get your training and testing image database setup. We've created one for you and all you have to do run the code block below. This assumes you know how to use IPython. When we say run a code block, you can click the block and hit the Play button above or hit Ctrl-Enter on your keyboard. If this is news to you it is advisable that you start with introductory tutorials and get used to IPython and Caffe2 basics first.\\n\",\n    \"\\n\",\n    \"The code below will download a small database of boats and cars images and their labels for you if it doesn't already exist. The images were pulled from ImageNet and added to a `lmdb` format database. You can download it directly [here](https://download.caffe2.ai/databases/resnet_trainer.zip) unzip it, and change the folder locations to an NFS if that better suits your situation. The tutorial's default location is for you to place it in `~/caffe2_notebooks/tutorial_data/resnet_trainer`.\\n\",\n    \"\\n\",\n    \"You can also swap out the database with your own as long as it is in lmdb and you change the `train_data_count` and `test_data_count` variables below. For your first time just use that database we made for you.\\n\",\n    \"\\n\",\n    \"We're going to give you all the dependencies needed for the tutorial in the block below. \\n\",\n    \"\\n\",\n    \"### Task: Run the Setup Code\\n\",\n    \"Read and then run the code block below. Note what modules are being imported and where we're accessing the database. Note and troubleshoot any errors in case something is wrong with your environment. Don't worry about the `nccl` and `gloo` warning messages.\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"from caffe2.python import core, workspace, model_helper, net_drawer, memonger, brew\\n\",\n    \"from caffe2.python import data_parallel_model as dpm\\n\",\n    \"from caffe2.python.models import resnet\\n\",\n    \"from caffe2.proto import caffe2_pb2\\n\",\n    \"\\n\",\n    \"import numpy as np\\n\",\n    \"import time\\n\",\n    \"import os\\n\",\n    \"from IPython import display\\n\",\n    \"    \\n\",\n    \"workspace.GlobalInit(['caffe2', '--caffe2_log_level=2'])\\n\",\n    \"\\n\",\n    \"# This section checks if you have the training and testing databases\\n\",\n    \"current_folder = os.path.join(os.path.expanduser('~'), 'caffe2_notebooks')\\n\",\n    \"data_folder = os.path.join(current_folder, 'tutorial_data', 'resnet_trainer')\\n\",\n    \"\\n\",\n    \"# Train/test data\\n\",\n    \"train_data_db = os.path.join(data_folder, \\\"imagenet_cars_boats_train\\\")\\n\",\n    \"train_data_db_type = \\\"lmdb\\\"\\n\",\n    \"# actually 640 cars and 640 boats = 1280\\n\",\n    \"train_data_count = 1280\\n\",\n    \"test_data_db = os.path.join(data_folder, \\\"imagenet_cars_boats_val\\\")\\n\",\n    \"test_data_db_type = \\\"lmdb\\\"\\n\",\n    \"# actually 48 cars and 48 boats = 96\\n\",\n    \"test_data_count = 96\\n\",\n    \"\\n\",\n    \"# Get the dataset if it is missing\\n\",\n    \"def DownloadDataset(url, path):\\n\",\n    \"    import requests, zipfile, StringIO\\n\",\n    \"    print(\\\"Downloading {} ... \\\".format(url))\\n\",\n    \"    r = requests.get(url, stream=True)\\n\",\n    \"    z = zipfile.ZipFile(StringIO.StringIO(r.content))\\n\",\n    \"    z.extractall(path)\\n\",\n    \"    print(\\\"Done downloading to {}!\\\".format(path))\\n\",\n    \"\\n\",\n    \"# Make the data folder if it doesn't exist\\n\",\n    \"if not os.path.exists(data_folder):\\n\",\n    \"    os.makedirs(data_folder)\\n\",\n    \"else:\\n\",\n    \"    print(\\\"Data folder found at {}\\\".format(data_folder))\\n\",\n    \"# See if you already have to db, and if not, download it\\n\",\n    \"if not os.path.exists(train_data_db):\\n\",\n    \"    DownloadDataset(\\\"https://download.caffe2.ai/databases/resnet_trainer.zip\\\", data_folder) \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Task: Check the Database\\n\",\n    \"\\n\",\n    \"Take a look at your data folder. You should find two subfolders, each of which will contain a single `data.mdb` file (or possibly also a lock file):\\n\",\n    \"1. imagenet_cars_boats_train (train for training, not locomotives!)\\n\",\n    \"2. imagenet_cars_boats_val (val for validation or testing)\\n\",\n    \"\\n\",\n    \"## Part 2: Configure the Training\\n\",\n    \"\\n\",\n    \"Below you can tinker with some of the settings for how the model will be created. One obvious setting to try is the `gpus`. By removing one or adding one you're directly impacting the amount of time it will take to run even on this small dataset.\\n\",\n    \"\\n\",\n    \"`batch_per_device` is the number of images processed at a time on each GPU. Using the default of 32 for 2 GPUs will equate to 32 images on each GPU for a total of 64 per mini-batch, so we'll go through the whole database and complete an epoch in 20 iterations. This is something you would want to adjust if you're sharing the GPU or otherwise want to adjust how much memory this training run is going to take up. You can see in the line below it being set to `32` we're adjusting the `total_batch_size` based on the number of GPUs.\\n\",\n    \"\\n\",\n    \"`base_learning_rate` and `weight_decay` will both influence training and can be interesting to change and witness the impact on accuracy or confidence is the results that are shown in the last section of this tutorial.\\n\",\n    \"\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# Configure how you want to train the model and with how many GPUs\\n\",\n    \"# This is set to use two GPUs in a single machine, but if you have more GPUs, extend the array [0, 1, 2, n]\\n\",\n    \"gpus = [0]\\n\",\n    \"\\n\",\n    \"# Batch size of 32 sums up to roughly 5GB of memory per device\\n\",\n    \"batch_per_device = 32\\n\",\n    \"total_batch_size = batch_per_device * len(gpus)\\n\",\n    \"\\n\",\n    \"# This model discriminates between two labels: car or boat\\n\",\n    \"num_labels = 2\\n\",\n    \"\\n\",\n    \"# Initial learning rate (scale with total batch size)\\n\",\n    \"base_learning_rate = 0.0004 * total_batch_size\\n\",\n    \"\\n\",\n    \"# only intends to influence the learning rate after 10 epochs\\n\",\n    \"stepsize = int(10 * train_data_count / total_batch_size)\\n\",\n    \"\\n\",\n    \"# Weight decay (L2 regularization)\\n\",\n    \"weight_decay = 1e-4\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Part 3: \\n\",\n    \"\\n\",\n    \"### Using Caffe2 Operators to Create a CNN\\n\",\n    \"\\n\",\n    \"Caffe2 comes with `ModelHelper` which will do a lot of the heavy lifting for you when setting up a model. Throughout the docs and tutorial this may also be called a `model helper object`. The only required parameter is `name`. It is an arbitrary name for referencing the network in your workspace: you could call it tacos or boatzncarz. For example:\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"taco_model = model_helper.ModelHelper(name=\\\"tacos\\\")\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"You should also reset your workspace if you run these parts multiple times. Do this just before creating the new model helper object.\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"workspace.ResetWorkspace()\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"### Reading from the Database\\n\",\n    \"\\n\",\n    \"Another handy function for feeding your network with images is `CreateDB`, which in this case we need to serve as a database reader for the database we've already created. You can create a reader object like this: \\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"reader = taco_model.CreateDB(name, db, db_type)\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"### Task: Create a Model Helper Object\\n\",\n    \"Remember, we have two databases and each will have their own model, but for now we only need to create the training model for the training db. Use the Work Area below. Also, while you do this, experiment with IPython's development hooks by typing the first part of the name from the imported class or module and hitting the tab key. For example when creating the object you type: `train_model = model_helper.` and after the dot, hit \\\"tab\\\". You should see a full list of available functions. Then when you choose `ModelHelper` hit \\\"(\\\" then hit tab and you should see a full list of params. This is very handy when you're exploring new modules and their functions!\\n\",\n    \"\\n\",\n    \"### Task: Create a Reader\\n\",\n    \"We also need one reader. We have established the db location, `train_data_db`, and type, `train_data_db_type`, in \\\"Part 1: Setup\\\", so all you have to do is name it and pass in the configs. Again, `name` is arbitrary so you could call it \\\"kindle\\\" if you wanted. Use the Work Area below, and when you are finished run the code block.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# LAB WORK AREA FOR PART 3\\n\",\n    \"\\n\",\n    \"# Clear workspace to free allocated memory, in case you are running this for a second time.\\n\",\n    \"workspace.ResetWorkspace()\\n\",\n    \"\\n\",\n    \"# 1. Create your model helper object for the training model with ModelHelper\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"# 2. Create your database reader with CreateDB\\n\",\n    \"\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Part 4: Image Transformations (requires Caffe2 to be compiled with opencv)\\n\",\n    \"\\n\",\n    \"Now that we have a reader we should take a look at how we're going to process the images. Since images that are found in the wild can be wildly different sizes, aspect ratios, and orientations we can and should train on as much variety as we can. ImageNet is no exception here. The average resolution is 496x387, and as interesting as that factoid might be, the bottom line is that you have a lot of variation. \\n\",\n    \"\\n\",\n    \"As the training images are ingested we would want to conform them to a standard size. The most direct process of doing so could follow a simple ingest where you transform the image to 256x256. We talked about the drawbacks of doing this in [Image Pre-Processing](Image_Pre-Processing_Pipeline.ipynb). Therefore for more accurate results, we should probably rescale, then crop. Even this approach with cropping has the drawbacks of losing some info from the original photo. What get chopped off doesn't make into the training data. If you ran the pre-processing tutorial on the image of the astronauts you will recall that some of the astronauts didn't make the cut. Where'd they go? Wash-out lane? Planet of the Apes? If your model was to detect people, then those lost astronauts would not be getting due credit when you run inference or face detection later using the model.\\n\",\n    \"\\n\",\n    \"### Introducing... the ImageInput Operator\\n\",\n    \"\\n\",\n    \"What could be seen as a loss turns into an opportunity. You can crop randomly around the image to create many deriviates of the original image, boosting your training data set, thereby adding robustness to the model. What if the image only has half a car or the front of a boat? You still want your model to be able to detect it! In the image below only the front a boat is shown and the model shows a 50% confidence in detection.\\n\",\n    \"\\n\",\n    \"![boat image](images/imagenet-boat.png)\\n\",\n    \"\\n\",\n    \"Caffe2 has a solution for this in its [`ImageInput` operator](https://github.com/caffe2/caffe2/blob/master/caffe2/image/image_input_op.h), a C++ image manipulation op that's used under the hood of several of the Caffe2 Python APIs.\\n\",\n    \"\\n\",\n    \"Here is a reference implementation:\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"def add_image_input_ops(model):\\n\",\n    \"    # utilize the ImageInput operator to prep the images\\n\",\n    \"    data, label = model.ImageInput(\\n\",\n    \"        reader,\\n\",\n    \"        [\\\"data\\\", \\\"label\\\"],\\n\",\n    \"        batch_size=batch_per_device,\\n\",\n    \"        # mean: to remove color values that are common\\n\",\n    \"        mean=128.,\\n\",\n    \"        # std is going to be modified randomly to influence the mean subtraction\\n\",\n    \"        std=128.,\\n\",\n    \"        # scale to rescale each image to a common size\\n\",\n    \"        scale=256,\\n\",\n    \"        # crop to the square each image to exact dimensions\\n\",\n    \"        crop=224,\\n\",\n    \"        # not running in test mode\\n\",\n    \"        is_test=False,\\n\",\n    \"        # mirroring of the images will occur randomly\\n\",\n    \"        mirror=1\\n\",\n    \"    )\\n\",\n    \"    # prevent back-propagation: optional performance improvement; may not be observable at small scale\\n\",\n    \"    data = model.StopGradient(data, data)\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"* mean: remove info that's common in most images\\n\",\n    \"* std: used to create a randomization for both cropping and mirroring\\n\",\n    \"* scale: downres each image so that its shortest side matches this base resolution\\n\",\n    \"* crop: the image size we want every image to be (using random crops from the scaled down image)\\n\",\n    \"* mirror: randomly mirror the images so we can train on both representations\\n\",\n    \"\\n\",\n    \"The [`StopGradient` operator](https://caffe2.ai/docs/operators-catalogue.html#stopgradient) does no numerical computation. It is used here to prevent back propagation which isn't wanted in this network.\\n\",\n    \"\\n\",\n    \"### Task: Implement the InputImage Operator\\n\",\n    \"Use the Work Area below to finish the stubbed out function. Refer to the reference implementation for help on this task. \\n\",\n    \"\\n\",\n    \"* What happens if you don't add a mean, don't add a std, or don't mirror. How does this change your accuracy when you run it for many epochs?\\n\",\n    \"* What would happen if we didn't do StopGradient?\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# LAB WORK AREA FOR PART 4\\n\",\n    \"\\n\",\n    \"def add_image_input_ops(model):\\n\",\n    \"    raise NotImplementedError # Remove this from the function stub\\n\",\n    \"    \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Part 5: Creating a Residual Network\\n\",\n    \"\\n\",\n    \"Now you get the opportunity to use Caffe2's Resnet-50 creation function! During our Setup we `from caffe2.python.models import resnet`. We can use that for our `create_resnet50_model_ops` function that we still need to create and the main part of that will be the `resnet.create_resnet50()` function as described below:\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"create_resnet50(\\n\",\n    \"    model, \\n\",\n    \"    data, \\n\",\n    \"    num_input_channels, \\n\",\n    \"    num_labels, \\n\",\n    \"    label=None, \\n\",\n    \"    is_test=False, \\n\",\n    \"    no_loss=False, \\n\",\n    \"    no_bias=0, \\n\",\n    \"    conv1_kernel=7, \\n\",\n    \"    conv1_stride=2, \\n\",\n    \"    final_avg_kernel=7\\n\",\n    \")\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"Below is a reference implementation of the function using `resnet.create_resnet50()`.\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"def create_resnet50_model_ops(model, loss_scale):\\n\",\n    \"    # Creates a residual network\\n\",\n    \"    [softmax, loss] = resnet.create_resnet50(\\n\",\n    \"        model,\\n\",\n    \"        \\\"data\\\",\\n\",\n    \"        num_input_channels=3,\\n\",\n    \"        num_labels=num_labels,\\n\",\n    \"        label=\\\"label\\\",\\n\",\n    \"    )\\n\",\n    \"    prefix = model.net.Proto().name\\n\",\n    \"    loss = model.Scale(loss, prefix + \\\"_loss\\\", scale=loss_scale)\\n\",\n    \"    model.Accuracy([softmax, \\\"label\\\"], prefix + \\\"_accuracy\\\")\\n\",\n    \"    return [loss]\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"### Task: Implement the forward_pass_builder_fun Using Resnet-50\\n\",\n    \"In the code block above where we stubbed out the `create_resnet50_model_ops` function, utilize `resnet.create_resnet50()` to create a residual network, then returning the loss. Refer to the reference implementation for help on this task.\\n\",\n    \"\\n\",\n    \"* Bonus points: if you take a look at the resnet class in the Caffe2 docs you'll notice a function to create a 32x32 model. Try it out.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# LAB WORK AREA FOR PART 5\\n\",\n    \"\\n\",\n    \"def create_resnet50_model_ops(model, loss_scale):\\n\",\n    \"    raise NotImplementedError #remove this from the function stub\\n\",\n    \"    \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Part 6: Make the Network Learn\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"Caffe2 model helper object has several built in functions that will help with this learning by using backpropagation where it will be adjusting weights as it runs through iterations.\\n\",\n    \"\\n\",\n    \"* AddWeightDecay\\n\",\n    \"* Iter\\n\",\n    \"* net.LearningRate\\n\",\n    \"\\n\",\n    \"Below is a reference implementation:\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"def add_parameter_update_ops(model):\\n\",\n    \"    model.AddWeightDecay(weight_decay)\\n\",\n    \"    iter = model.Iter(\\\"iter\\\")\\n\",\n    \"    lr = model.net.LearningRate(\\n\",\n    \"        [iter],\\n\",\n    \"        \\\"lr\\\",\\n\",\n    \"        base_lr=base_learning_rate,\\n\",\n    \"        policy=\\\"step\\\",\\n\",\n    \"        stepsize=stepsize,\\n\",\n    \"        gamma=0.1,\\n\",\n    \"    )\\n\",\n    \"    # Momentum SGD update\\n\",\n    \"    for param in model.GetParams():\\n\",\n    \"        param_grad = model.param_to_grad[param]\\n\",\n    \"        param_momentum = model.param_init_net.ConstantFill(\\n\",\n    \"            [param], param + '_momentum', value=0.0\\n\",\n    \"        )\\n\",\n    \"\\n\",\n    \"        # Update param_grad and param_momentum in place\\n\",\n    \"        model.net.MomentumSGDUpdate(\\n\",\n    \"            [param_grad, param_momentum, lr, param],\\n\",\n    \"            [param_grad, param_momentum, param],\\n\",\n    \"            momentum=0.9,\\n\",\n    \"            # Nesterov Momentum works slightly better than standard momentum\\n\",\n    \"            nesterov=1,\\n\",\n    \"        )\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"### Task: Implement the forward_pass_builder_fun Using Resnet-50\\n\",\n    \"Several of our Configuration variables will get used in this step. Take a look at the Configuration section from Part 2 and refresh your memory. We stubbed out the `add_parameter_update_ops` function, so to finish it, utilize `model.AddWeightDecay` and set `weight_decay`. Calculate your stepsize using `int(10 * train_data_count / total_batch_size)` or pull the value from the config. Instantiate the learning iterations with `iter = model.Iter(\\\"iter\\\")`. Use `model.net.LearningRate()` to finalize your parameter update operations. You can optionally update you SGD's momentum. It might not make a difference in this small implementation, but if you're gonna go big later, then you'll want to do this.\\n\",\n    \"\\n\",\n    \"Refer to the reference implementation for help on this task.\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# LAB WORK AREA FOR PART 6\\n\",\n    \"\\n\",\n    \"def add_parameter_update_ops(model):\\n\",\n    \"    raise NotImplementedError #remove this from the function stub\\n\",\n    \"    \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Part 7: Gradient Optimization\\n\",\n    \"\\n\",\n    \"If you run the network as is you may have issues with memory. Without memory optimization we could reduce the batch size, but we shouldn't have to do that. Caffe2 has a `memonger` function for this purpose which will find ways to reuse gradients that we created. Below is a reference implementation.\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"def optimize_gradient_memory(model, loss):\\n\",\n    \"    model.net._net = memonger.share_grad_blobs(\\n\",\n    \"        model.net,\\n\",\n    \"        loss,\\n\",\n    \"        set(model.param_to_grad.values()),\\n\",\n    \"        # Due to memonger internals, we need a namescope here. Let's make one up; we'll need it later!\\n\",\n    \"        namescope=\\\"imonaboat\\\",\\n\",\n    \"        share_activations=False)\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"### Task: Implement memonger\\n\",\n    \"We're going to use the reference for help here, otherwise it is a little difficult to cover for the scope of this tutorial. The function is ready to go for you, but you should still soak up what's been done in this function. One of the key gotchas here is making sure you give it a namescope so that you can access the gradients you'll be creating in the next step. This name can be anything.\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# LAB WORK AREA FOR PART 7\\n\",\n    \"\\n\",\n    \"def optimize_gradient_memory(model, loss):\\n\",\n    \"    raise NotImplementedError # Remove this from the function stub\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Part 8: Training the Network with One GPU\\n\",\n    \"\\n\",\n    \"Now that you've established be basic components to run ResNet-50, you can try it out on one GPU. Now, this could be a lot easier just going straight into the `data_parallel_model` and all of its optimizations, but to help explain the components needed and to build the helper functions to run `GPU_Parallelize`, we may as well start simple! \\n\",\n    \"\\n\",\n    \"If you're paying attention you might be wondering about the `gpus` array we made in the config and how that might throw things off. Also, when we looked at the config earlier you may have updated `gpus[0]` to have more than one GPU. That's fine. We can leave it like that for the next part because we will force our script to use just one GPU.\\n\",\n    \"\\n\",\n    \"Let's stitch together those functions from Parts 4-7 to run our residual network! Take a look at the code below, so you understand how the pieces fit together.\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"# We need to give the network context and force it to run on the first GPU even if there are more.\\n\",\n    \"device_opt = core.DeviceOption(caffe2_pb2.CUDA, gpus[0])\\n\",\n    \"# Here's where that NameScope comes into play\\n\",\n    \"with core.NameScope(\\\"imonaboat\\\"):\\n\",\n    \"    # Picking that one GPU\\n\",\n    \"    with core.DeviceScope(device_opt):\\n\",\n    \"        # Run our reader, and create the layers that transform the images\\n\",\n    \"        add_image_input_ops(train_model)\\n\",\n    \"        # Generate our residual network and return the losses\\n\",\n    \"        losses = create_resnet50_model_ops(train_model)\\n\",\n    \"        # Create gradients for each loss\\n\",\n    \"        blobs_to_gradients = train_model.AddGradientOperators(losses)\\n\",\n    \"        # Kick off the learning and managing of the weights\\n\",\n    \"        add_parameter_update_ops(train_model)\\n\",\n    \"    # Optimize memory usage by consolidating where we can\\n\",\n    \"    optimize_gradient_memory(train_model, [blobs_to_gradients[losses[0]]])\\n\",\n    \"\\n\",\n    \"# Startup the network \\n\",\n    \"workspace.RunNetOnce(train_model.param_init_net)\\n\",\n    \"# Load all of the initial weights; overwrite lets you run this multiple times\\n\",\n    \"workspace.CreateNet(train_model.net, overwrite=True)\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"### Task: Pull It All Together & Run It!\\n\",\n    \"\\n\",\n    \"Things are getting a little hairy, so we gave you the full reference ready to go. Just run the code block below (hit ctrl-enter). Normally you might not use `overwrite=True` since that could be bad for what you're doing by accidentally erasing your earlier work, so try removing it and running the block multiple times to see what happens. Imagine the case where you have multiple networks going that have the same name. You don't want to overwrite, so you might want to start up a new workspace or modify the names.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# LAB WORK AREA FOR PART 8\\n\",\n    \"\\n\",\n    \"device_opt = core.DeviceOption(caffe2_pb2.CUDA, gpus[0])\\n\",\n    \"with core.NameScope(\\\"imonaboat\\\"):\\n\",\n    \"    with core.DeviceScope(device_opt):\\n\",\n    \"        add_image_input_ops(train_model)\\n\",\n    \"        losses = create_resnet50_model_ops(train_model)\\n\",\n    \"        blobs_to_gradients = train_model.AddGradientOperators(losses)\\n\",\n    \"        add_parameter_update_ops(train_model)\\n\",\n    \"    optimize_gradient_memory(train_model, [blobs_to_gradients[losses[0]]])\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"workspace.RunNetOnce(train_model.param_init_net)\\n\",\n    \"workspace.CreateNet(train_model.net, overwrite=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Part 8 ... part ~~2~~ Deux: Train!\\n\",\n    \"Here's the fun part where you can tinker with the number of epochs to run and mess with the display. We'll leave this for you to play with as a fait accompli since you worked so hard to get this far!\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"num_epochs = 1\\n\",\n    \"for epoch in range(num_epochs):\\n\",\n    \"    # Split up the images evenly: total images / batch size\\n\",\n    \"    num_iters = int(train_data_count / total_batch_size)\\n\",\n    \"    for iter in range(num_iters):\\n\",\n    \"        # Stopwatch start!\\n\",\n    \"        t1 = time.time()\\n\",\n    \"        # Run this iteration!\\n\",\n    \"        workspace.RunNet(train_model.net.Proto().name)\\n\",\n    \"        t2 = time.time()\\n\",\n    \"        dt = t2 - t1\\n\",\n    \"        \\n\",\n    \"        # Stopwatch stopped! How'd we do?\\n\",\n    \"        print((\\n\",\n    \"            \\\"Finished iteration {:>\\\" + str(len(str(num_iters))) + \\\"}/{}\\\" +\\n\",\n    \"            \\\" (epoch {:>\\\" + str(len(str(num_epochs))) + \\\"}/{})\\\" + \\n\",\n    \"            \\\" ({:.2f} images/sec)\\\").\\n\",\n    \"            format(iter+1, num_iters, epoch+1, num_epochs, total_batch_size/dt))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Part 9: Getting Parallelized \\n\",\n    \"\\n\",\n    \"You get bonus points if you can say \\\"getting parallelized\\\" three times fast without messing up. You just saw some interesting numbers in the last step. Take note of those and see how things scale up when we use more GPUs. \\n\",\n    \"\\n\",\n    \"We're going to use Caffe2's `data_parallel_model` and its function called `Parallelize_GPU` to help us accomplish this task. The task to setup the parallel model, not to say it fast. Here's the spec on `Parallelize_GPU`:\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"Parallelize_GPU(\\n\",\n    \"    model_helper_obj, \\n\",\n    \"    input_builder_fun, \\n\",\n    \"    forward_pass_builder_fun, \\n\",\n    \"    param_update_builder_fun, \\n\",\n    \"    devices=range(0, workspace.NumCudaDevices()), \\n\",\n    \"    rendezvous=None, \\n\",\n    \"    net_type='dag', \\n\",\n    \"    broadcast_computed_params=True, \\n\",\n    \"    optimize_gradient_memory=False)\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"We're not ready to just call this function though. As you can see in the second, third, and fourth input parameters, they are expecting functions to be passed to them. [More API details here.](https://caffe2.ai/doxygen-python/html/namespacedata__parallel__model.html#a1fe7262a0a66754f19998fa1603317b9) The three functions expected are:\\n\",\n    \"\\n\",\n    \"1. `input_build_fun`: adds the input operators. Note: Remember to instantiate reader outside of this function so all GPUs share same reader object. Signature:  input_builder_fun(model)\\n\",\n    \"2. `forward_pass_builder_fun`: adds the operators to the model. Must return list of loss-blob references that are used to build the gradient. Loss scale parameter is passed, as you should scale the loss of your model by 1.0 / the total number of gpus. Signature: forward_pass_builder_fun(model, loss_scale)\\n\",\n    \"3. `param_update_builder_fun`: adds operators that are run after gradient update, such as updating the weights and weight decaying. Signature: param_update_builder_fun(model)\\n\",\n    \"\\n\",\n    \"For the `input_build_fun` we're going to use the reader we created with `CreateDB` along with a function that leverages Caffe2's `ImageInput` operator. Sound familiar? You already did this in Part 4!\\n\",\n    \"\\n\",\n    \"For the `forward_pass_builder_fun` we need to have residual neural network. You already did this in Part 5!\\n\",\n    \"\\n\",\n    \"For the `param_update_builder_fun` we need a function to adjust the weights as the network runs. You already did this in Part 6! \\n\",\n    \"\\n\",\n    \"Let's stub out the `Parallelize_GPU` function with the parameters that we're going to use. Recall that in the setup we  `from caffe2.python import data_parallel_model as dpm`, so we can use `dpm.Parallelize_GPU()` to access the `Parallelize_GPU` function. First we'll stub out the three other functions to that this expects, add the params based on these functions names and our gpu count, then come back to the lab cell below to populate them with some logic and test them. Below is a reference implementation:\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"dpm.Parallelize_GPU(\\n\",\n    \"    train_model,\\n\",\n    \"    input_builder_fun=add_image_input_ops,\\n\",\n    \"    forward_pass_builder_fun=create_resnet50_model_ops,\\n\",\n    \"    param_update_builder_fun=add_parameter_update_ops,\\n\",\n    \"    devices=gpus,\\n\",\n    \"    optimize_gradient_memory=True,\\n\",\n    \")\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"### Task: Make Your Helper Functions\\n\",\n    \"You already did this the Parts 4 through 6 and in Part 7 you had to deal with gradient optimizations that are baked into `Parallelize_GPU`. The three helper function stubs below can be eliminated or if you want to see everything together go ahead and copy the functions there, so you can run them from the work area block below.\\n\",\n    \"\\n\",\n    \"### Task: Parallelize!\\n\",\n    \"Now you can stub out a call to `Parallelize_GPU`. Use the reference implementation above if you get stuck.\\n\",\n    \"* `model_helper_object`: created in Part 3; maybe you called it taco_model, or if you weren't copying and pasting you thoughtfully called it train_model or training_model.\\n\",\n    \"* Now pass the function name for each of the three functions you just created, e.g. `input_builder_fun=add_image_input_ops`\\n\",\n    \"* `devices`: we can pass in our `gpus` array from our earlier Setup.\\n\",\n    \"* `optimize_gradient_memory`: the default is `False` but let's set it to `True`; this takes care of what we had to do in Step 7 with `memonger`.\\n\",\n    \"* other params: ignore/don't pass anything to accept their defaults\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# LAB WORK AREA for Part 9\\n\",\n    \"\\n\",\n    \"# Reinitializing our configuration variables to accomodate 2 (or more, if you have them) GPUs.\\n\",\n    \"gpus = [0, 1]\\n\",\n    \"\\n\",\n    \"# Batch size of 32 sums up to roughly 5GB of memory per device\\n\",\n    \"batch_per_device = 32\\n\",\n    \"total_batch_size = batch_per_device * len(gpus)\\n\",\n    \"\\n\",\n    \"# This model discriminates between two labels: car or boat\\n\",\n    \"num_labels = 2\\n\",\n    \"\\n\",\n    \"# Initial learning rate (scale with total batch size)\\n\",\n    \"base_learning_rate = 0.0004 * total_batch_size\\n\",\n    \"\\n\",\n    \"# only intends to influence the learning rate after 10 epochs\\n\",\n    \"stepsize = int(10 * train_data_count / total_batch_size)\\n\",\n    \"\\n\",\n    \"# Weight decay (L2 regularization)\\n\",\n    \"weight_decay = 1e-4\\n\",\n    \"\\n\",\n    \"# Clear workspace to free network and memory allocated in previous steps.\\n\",\n    \"workspace.ResetWorkspace()\\n\",\n    \"\\n\",\n    \"# Create input_build_fun\\n\",\n    \"def add_image_input_ops(model):\\n\",\n    \"    # This will utilize the reader to pull images and feed them to the training model's helper object\\n\",\n    \"    # Use the model.ImageInput operator to load data from reader & apply transformations to the images.\\n\",\n    \"    raise NotImplementedError # Remove this from the function stub\\n\",\n    \"    \\n\",\n    \"\\n\",\n    \"# Create forward_pass_builder_fun\\n\",\n    \"def create_resnet50_model_ops(model, loss_scale):\\n\",\n    \"    # Use resnet module to create a residual net\\n\",\n    \"    raise NotImplementedError # Remove this from the function stub\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"# Create param_update_builder_fun\\n\",\n    \"def add_parameter_update_ops(model):\\n\",\n    \"    raise NotImplementedError # Remove this from the function stub\\n\",\n    \"\\n\",\n    \"    \\n\",\n    \"# Create new train model\\n\",\n    \"train_model = NotImplementedError\\n\",\n    \"\\n\",\n    \"# Create new reader\\n\",\n    \"reader = NotImplementedError\\n\",\n    \"\\n\",\n    \"# Create parallelized model using dpm.Parallelize_GPU\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"# Use workspace.RunNetOnce and workspace.CreateNet to fire up the train network\\n\",\n    \"workspace.RunNetOnce(train_model.param_init_net)\\n\",\n    \"workspace.CreateNet(train_model.net, overwrite=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Part 10: Create a Test Model\\n\",\n    \"\\n\",\n    \"After every epoch of training, we like to run some validation data through our model to see how it performs.\\n\",\n    \"\\n\",\n    \"Like training, this is another net, with its own data reader. Unlike training, this net does not perform backpropagation. It only does a forward pass and compares the output of the network with the label of the validation data.\\n\",\n    \"\\n\",\n    \"You've already done these steps once before when you created the training network, so do it again, but name it something different, like \\\"test\\\".\\n\",\n    \"\\n\",\n    \"### Task: Create a Test Model\\n\",\n    \"\\n\",\n    \"* Use `ModelHelper` to create a model helper object called \\\"test\\\"\\n\",\n    \"* Use `CreateDB` to create a reader and call it \\\"test_reader\\\"\\n\",\n    \"* Use `Parallelize_GPU` to parallelize the model, but set `param_update_builder_fun=None` to skip backpropagation\\n\",\n    \"* Use `workspace.RunNetOnce` and `workspace.CreateNet` to fire up the test network \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# LAB WORK AREA for Part 10\\n\",\n    \"\\n\",\n    \"# Create your test model with ModelHelper\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"# Create your reader with CreateDB\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"# Use multi-GPU with Parallelize_GPU, but don't utilize backpropagation\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"# Use workspace.RunNetOnce and workspace.CreateNet to fire up the test network\\n\",\n    \"workspace.RunNetOnce(test_model.param_init_net)\\n\",\n    \"workspace.CreateNet(test_model.net, overwrite=True)\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Get Ready to Display the Results\\n\",\n    \"At the end of every epoch we will take a look at how the network performs visually. We will also report on the accuracy of the training model and the test model. Let's not force you to write your own reporting and display code, so just run the code block below to get those features ready.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"%matplotlib inline\\n\",\n    \"from caffe2.python import visualize\\n\",\n    \"from matplotlib import pyplot as plt\\n\",\n    \"\\n\",\n    \"def display_images_and_confidence():\\n\",\n    \"    images = []\\n\",\n    \"    confidences = []\\n\",\n    \"    n = 16\\n\",\n    \"    data = workspace.FetchBlob(\\\"gpu_0/data\\\")\\n\",\n    \"    label = workspace.FetchBlob(\\\"gpu_0/label\\\")\\n\",\n    \"    softmax = workspace.FetchBlob(\\\"gpu_0/softmax\\\")\\n\",\n    \"    for arr in zip(data[0:n], label[0:n], softmax[0:n]):\\n\",\n    \"        # CHW to HWC, normalize to [0.0, 1.0], and BGR to RGB\\n\",\n    \"        bgr = (arr[0].swapaxes(0, 1).swapaxes(1, 2) + 1.0) / 2.0\\n\",\n    \"        rgb = bgr[...,::-1]\\n\",\n    \"        images.append(rgb)\\n\",\n    \"        confidences.append(arr[2][arr[1]])\\n\",\n    \"\\n\",\n    \"    # Create grid for images\\n\",\n    \"    fig, rows = plt.subplots(nrows=4, ncols=4, figsize=(12, 12))\\n\",\n    \"    plt.tight_layout(h_pad=2)\\n\",\n    \"\\n\",\n    \"    # Display images and the models confidence in their label\\n\",\n    \"    items = zip([ax for cols in rows for ax in cols], images, confidences)\\n\",\n    \"    for (ax, image, confidence) in items:\\n\",\n    \"        ax.imshow(image)\\n\",\n    \"        if confidence >= 0.5:\\n\",\n    \"            ax.set_title(\\\"RIGHT ({:.1f}%)\\\".format(confidence * 100.0), color='green')\\n\",\n    \"        else:\\n\",\n    \"            ax.set_title(\\\"WRONG ({:.1f}%)\\\".format(confidence * 100.0), color='red')\\n\",\n    \"\\n\",\n    \"    plt.show()\\n\",\n    \"\\n\",\n    \"    \\n\",\n    \"def accuracy(model):\\n\",\n    \"    accuracy = []\\n\",\n    \"    prefix = model.net.Proto().name\\n\",\n    \"    for device in model._devices:\\n\",\n    \"        accuracy.append(\\n\",\n    \"            np.asscalar(workspace.FetchBlob(\\\"gpu_{}/{}_accuracy\\\".format(device, prefix))))\\n\",\n    \"    return np.average(accuracy)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Part 11: Run Multi-GPU Training and Get Test Results\\n\",\n    \"You've come a long way. Now is the time to see it all pay off. Since you already ran ResNet once, you can glance at the code below and run it. The big difference this time is your model is parallelized! \\n\",\n    \"\\n\",\n    \"The additional components at the end deal with accuracy so you may want to dig into those specifics as a bonus task. You can try it again: just adjust the `num_epochs` value below, run the block, and see the results. You can also go back to Part 10 to reinitialize the model, and run this step again. (You may want to add `workspace.ResetWorkspace()` before you run the new models again.)\\n\",\n    \"\\n\",\n    \"Go back and check the images/sec from when you ran single GPU. Note how you can scale up with a small amount of overhead. \\n\",\n    \"\\n\",\n    \"### Task: How many GPUs would it take to train ImageNet in under a minute? \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"scrolled\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# Start looping through epochs where we run the batches of images to cover the entire dataset\\n\",\n    \"# Usually you would want to run a lot more epochs to increase your model's accuracy\\n\",\n    \"num_epochs = 2\\n\",\n    \"for epoch in range(num_epochs):\\n\",\n    \"    # Split up the images evenly: total images / batch size\\n\",\n    \"    num_iters = int(train_data_count / total_batch_size)\\n\",\n    \"    for iter in range(num_iters):\\n\",\n    \"        # Stopwatch start!\\n\",\n    \"        t1 = time.time()\\n\",\n    \"        # Run this iteration!\\n\",\n    \"        workspace.RunNet(train_model.net.Proto().name)\\n\",\n    \"        t2 = time.time()\\n\",\n    \"        dt = t2 - t1\\n\",\n    \"        \\n\",\n    \"        # Stopwatch stopped! How'd we do?\\n\",\n    \"        print((\\n\",\n    \"            \\\"Finished iteration {:>\\\" + str(len(str(num_iters))) + \\\"}/{}\\\" +\\n\",\n    \"            \\\" (epoch {:>\\\" + str(len(str(num_epochs))) + \\\"}/{})\\\" + \\n\",\n    \"            \\\" ({:.2f} images/sec)\\\").\\n\",\n    \"            format(iter+1, num_iters, epoch+1, num_epochs, total_batch_size/dt))\\n\",\n    \"        \\n\",\n    \"        # Get the average accuracy for the training model\\n\",\n    \"        train_accuracy = accuracy(train_model)\\n\",\n    \"    \\n\",\n    \"    # Run the test model and assess accuracy\\n\",\n    \"    test_accuracies = []\\n\",\n    \"    for _ in range(test_data_count / total_batch_size):\\n\",\n    \"        # Run the test model\\n\",\n    \"        workspace.RunNet(test_model.net.Proto().name)\\n\",\n    \"        test_accuracies.append(accuracy(test_model))\\n\",\n    \"    test_accuracy = np.average(test_accuracies)\\n\",\n    \"\\n\",\n    \"    print(\\n\",\n    \"        \\\"Train accuracy: {:.3f}, test accuracy: {:.3f}\\\".\\n\",\n    \"        format(train_accuracy, test_accuracy))\\n\",\n    \"    \\n\",\n    \"    # Output images with confidence scores as the caption\\n\",\n    \"    display_images_and_confidence()\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"source\": [\n    \"If you enjoyed this tutorial and would like to see it in action in a different way, check Caffe2's Python examples to try a [script version](https://github.com/caffe2/caffe2/blob/master/caffe2/python/examples/resnet50_trainer.py) of this multi-GPU trainer. We also have some more info below in the Appendix and a Solutions section that you can use to run the expected output of this tutorial.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Appendix\\n\",\n    \"Here are a few things you may want to play with.\\n\",\n    \"\\n\",\n    \"### Explore the workspace and the protobuf outputs\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"scrolled\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(str(train_model.param_init_net.Proto())[:1000] + '\\\\n...')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Solutions\\n\",\n    \"This section below contains working examples for your reference. You should be able to execute these cells in order and see the expected output. **Note: this assumes you have at least 2 GPUs**\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 1\\n\",\n    \"\\n\",\n    \"from caffe2.python import core, workspace, model_helper, net_drawer, memonger, brew\\n\",\n    \"from caffe2.python import data_parallel_model as dpm\\n\",\n    \"from caffe2.python.models import resnet\\n\",\n    \"from caffe2.proto import caffe2_pb2\\n\",\n    \"\\n\",\n    \"import numpy as np\\n\",\n    \"import time\\n\",\n    \"import os\\n\",\n    \"from IPython import display\\n\",\n    \"    \\n\",\n    \"workspace.GlobalInit(['caffe2', '--caffe2_log_level=2'])\\n\",\n    \"\\n\",\n    \"# This section checks if you have the training and testing databases\\n\",\n    \"current_folder = os.path.join(os.path.expanduser('~'), 'caffe2_notebooks')\\n\",\n    \"data_folder = os.path.join(current_folder, 'tutorial_data', 'resnet_trainer')\\n\",\n    \"\\n\",\n    \"# Train/test data\\n\",\n    \"train_data_db = os.path.join(data_folder, \\\"imagenet_cars_boats_train\\\")\\n\",\n    \"train_data_db_type = \\\"lmdb\\\"\\n\",\n    \"# actually 640 cars and 640 boats = 1280\\n\",\n    \"train_data_count = 1280\\n\",\n    \"test_data_db = os.path.join(data_folder, \\\"imagenet_cars_boats_val\\\")\\n\",\n    \"test_data_db_type = \\\"lmdb\\\"\\n\",\n    \"# actually 48 cars and 48 boats = 96\\n\",\n    \"test_data_count = 96\\n\",\n    \"\\n\",\n    \"# Get the dataset if it is missing\\n\",\n    \"def DownloadDataset(url, path):\\n\",\n    \"    import requests, zipfile, StringIO\\n\",\n    \"    print(\\\"Downloading {} ... \\\".format(url))\\n\",\n    \"    r = requests.get(url, stream=True)\\n\",\n    \"    z = zipfile.ZipFile(StringIO.StringIO(r.content))\\n\",\n    \"    z.extractall(path)\\n\",\n    \"    print(\\\"Done downloading to {}!\\\".format(path))\\n\",\n    \"\\n\",\n    \"# Make the data folder if it doesn't exist\\n\",\n    \"if not os.path.exists(data_folder):\\n\",\n    \"    os.makedirs(data_folder)\\n\",\n    \"else:\\n\",\n    \"    print(\\\"Data folder found at {}\\\".format(data_folder))\\n\",\n    \"# See if you already have to db, and if not, download it\\n\",\n    \"if not os.path.exists(train_data_db):\\n\",\n    \"    DownloadDataset(\\\"https://download.caffe2.ai/databases/resnet_trainer.zip\\\", data_folder) \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# PART 1 TROUBLESHOOTING\\n\",\n    \"\\n\",\n    \"# lmdb error or unable to open database: look in the database folder from terminal and (sudo) delete the lock file and try again\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 2\\n\",\n    \"\\n\",\n    \"# Configure how you want to train the model and with how many GPUs\\n\",\n    \"# This is set to use two GPUs in a single machine, but if you have more GPUs, extend the array [0, 1, 2, n]\\n\",\n    \"gpus = [0, 1]\\n\",\n    \"\\n\",\n    \"# Batch size of 32 sums up to roughly 5GB of memory per device\\n\",\n    \"batch_per_device = 32\\n\",\n    \"total_batch_size = batch_per_device * len(gpus)\\n\",\n    \"\\n\",\n    \"# This model discriminates between two labels: car or boat\\n\",\n    \"num_labels = 2\\n\",\n    \"\\n\",\n    \"# Initial learning rate (scale with total batch size)\\n\",\n    \"base_learning_rate = 0.0004 * total_batch_size\\n\",\n    \"\\n\",\n    \"# only intends to influence the learning rate after 10 epochs\\n\",\n    \"stepsize = int(10 * train_data_count / total_batch_size)\\n\",\n    \"\\n\",\n    \"# Weight decay (L2 regularization)\\n\",\n    \"weight_decay = 1e-4\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 3\\n\",\n    \"\\n\",\n    \"workspace.ResetWorkspace()\\n\",\n    \"# 1. Use the model helper to create a CNN for us\\n\",\n    \"train_model = model_helper.ModelHelper(\\n\",\n    \"    # Arbitrary name for referencing the network in your workspace: you could call it tacos or boatzncarz\\n\",\n    \"    name=\\\"train\\\",\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"# 2. Create a database reader\\n\",\n    \"# This training data reader is shared between all GPUs.\\n\",\n    \"# When reading data, the trainer runs ImageInputOp for each GPU to retrieve their own unique batch of training data.\\n\",\n    \"# CreateDB is inherited by ModelHelper from model_helper.py\\n\",\n    \"# We are going to name it \\\"train_reader\\\" and pass in the db configurations we set earlier\\n\",\n    \"reader = train_model.CreateDB(\\n\",\n    \"    \\\"train_reader\\\",\\n\",\n    \"    db=train_data_db,\\n\",\n    \"    db_type=train_data_db_type,\\n\",\n    \")\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 4\\n\",\n    \"\\n\",\n    \"def add_image_input_ops(model):\\n\",\n    \"    # utilize the ImageInput operator to prep the images\\n\",\n    \"    data, label = brew.image_input(\\n\",\n    \"        model,\\n\",\n    \"        reader,\\n\",\n    \"        [\\\"data\\\", \\\"label\\\"],\\n\",\n    \"        batch_size=batch_per_device,\\n\",\n    \"        # mean: to remove color values that are common\\n\",\n    \"        mean=128.,\\n\",\n    \"        # std is going to be modified randomly to influence the mean subtraction\\n\",\n    \"        std=128.,\\n\",\n    \"        # scale to rescale each image to a common size\\n\",\n    \"        scale=256,\\n\",\n    \"        # crop to the square each image to exact dimensions\\n\",\n    \"        crop=224,\\n\",\n    \"        # not running in test mode\\n\",\n    \"        is_test=False,\\n\",\n    \"        # mirroring of the images will occur randomly\\n\",\n    \"        mirror=1\\n\",\n    \"    )\\n\",\n    \"    # prevent back-propagation: optional performance improvement; may not be observable at small scale\\n\",\n    \"    data = model.net.StopGradient(data, data)\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 5\\n\",\n    \"\\n\",\n    \"def create_resnet50_model_ops(model, loss_scale=1.0):\\n\",\n    \"    # Creates a residual network\\n\",\n    \"    [softmax, loss] = resnet.create_resnet50(\\n\",\n    \"        model,\\n\",\n    \"        \\\"data\\\",\\n\",\n    \"        num_input_channels=3,\\n\",\n    \"        num_labels=num_labels,\\n\",\n    \"        label=\\\"label\\\",\\n\",\n    \"    )\\n\",\n    \"    prefix = model.net.Proto().name\\n\",\n    \"    loss = model.net.Scale(loss, prefix + \\\"_loss\\\", scale=loss_scale)\\n\",\n    \"    brew.accuracy(model, [softmax, \\\"label\\\"], prefix + \\\"_accuracy\\\")\\n\",\n    \"    return [loss]\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 6\\n\",\n    \"\\n\",\n    \"def add_parameter_update_ops(model):\\n\",\n    \"    brew.add_weight_decay(model, weight_decay)\\n\",\n    \"    iter = brew.iter(model, \\\"iter\\\")\\n\",\n    \"    lr = model.net.LearningRate(\\n\",\n    \"        [iter],\\n\",\n    \"        \\\"lr\\\",\\n\",\n    \"        base_lr=base_learning_rate,\\n\",\n    \"        policy=\\\"step\\\",\\n\",\n    \"        stepsize=stepsize,\\n\",\n    \"        gamma=0.1,\\n\",\n    \"    )\\n\",\n    \"    for param in model.GetParams():\\n\",\n    \"        param_grad = model.param_to_grad[param]\\n\",\n    \"        param_momentum = model.param_init_net.ConstantFill(\\n\",\n    \"            [param], param + '_momentum', value=0.0\\n\",\n    \"        )\\n\",\n    \"\\n\",\n    \"        # Update param_grad and param_momentum in place\\n\",\n    \"        model.net.MomentumSGDUpdate(\\n\",\n    \"            [param_grad, param_momentum, lr, param],\\n\",\n    \"            [param_grad, param_momentum, param],\\n\",\n    \"            # almost 100% but with room to grow\\n\",\n    \"            momentum=0.9,\\n\",\n    \"            # netsterov is a defenseman for the Montreal Canadiens, but\\n\",\n    \"            # Nesterov Momentum works slightly better than standard momentum\\n\",\n    \"            nesterov=1,\\n\",\n    \"        )\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 7\\n\",\n    \"\\n\",\n    \"def optimize_gradient_memory(model, loss):\\n\",\n    \"    model.net._net = memonger.share_grad_blobs(\\n\",\n    \"        model.net,\\n\",\n    \"        loss,\\n\",\n    \"        set(model.param_to_grad.values()),\\n\",\n    \"        namescope=\\\"imonaboat\\\",\\n\",\n    \"        share_activations=False,\\n\",\n    \"        )\\n\",\n    \"    \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 8\\n\",\n    \"\\n\",\n    \"device_opt = core.DeviceOption(caffe2_pb2.CUDA, gpus[0])\\n\",\n    \"with core.NameScope(\\\"imonaboat\\\"):\\n\",\n    \"    with core.DeviceScope(device_opt):\\n\",\n    \"        add_image_input_ops(train_model)\\n\",\n    \"        losses = create_resnet50_model_ops(train_model)\\n\",\n    \"        blobs_to_gradients = train_model.AddGradientOperators(losses)\\n\",\n    \"        add_parameter_update_ops(train_model)\\n\",\n    \"    optimize_gradient_memory(train_model, [blobs_to_gradients[losses[0]]])\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"workspace.RunNetOnce(train_model.param_init_net)\\n\",\n    \"workspace.CreateNet(train_model.net, overwrite=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 8 Part Deux\\n\",\n    \"num_epochs = 1\\n\",\n    \"for epoch in range(num_epochs):\\n\",\n    \"    # Split up the images evenly: total images / batch size\\n\",\n    \"    num_iters = int(train_data_count / batch_per_device)\\n\",\n    \"    for iter in range(num_iters):\\n\",\n    \"        # Stopwatch start!\\n\",\n    \"        t1 = time.time()\\n\",\n    \"        # Run this iteration!\\n\",\n    \"        workspace.RunNet(train_model.net.Proto().name)\\n\",\n    \"        t2 = time.time()\\n\",\n    \"        dt = t2 - t1\\n\",\n    \"        \\n\",\n    \"        # Stopwatch stopped! How'd we do?\\n\",\n    \"        print((\\n\",\n    \"            \\\"Finished iteration {:>\\\" + str(len(str(num_iters))) + \\\"}/{}\\\" +\\n\",\n    \"            \\\" (epoch {:>\\\" + str(len(str(num_epochs))) + \\\"}/{})\\\" + \\n\",\n    \"            \\\" ({:.2f} images/sec)\\\").\\n\",\n    \"            format(iter+1, num_iters, epoch+1, num_epochs, batch_per_device/dt))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 9 Prep\\n\",\n    \"\\n\",\n    \"# Reinitializing our configuration variables to accomodate 2 (or more, if you have them) GPUs.\\n\",\n    \"gpus = [0, 1]\\n\",\n    \"\\n\",\n    \"# Batch size of 32 sums up to roughly 5GB of memory per device\\n\",\n    \"batch_per_device = 32\\n\",\n    \"total_batch_size = batch_per_device * len(gpus)\\n\",\n    \"\\n\",\n    \"# This model discriminates between two labels: car or boat\\n\",\n    \"num_labels = 2\\n\",\n    \"\\n\",\n    \"# Initial learning rate (scale with total batch size)\\n\",\n    \"base_learning_rate = 0.0004 * total_batch_size\\n\",\n    \"\\n\",\n    \"# only intends to influence the learning rate after 10 epochs\\n\",\n    \"stepsize = int(10 * train_data_count / total_batch_size)\\n\",\n    \"\\n\",\n    \"# Weight decay (L2 regularization)\\n\",\n    \"weight_decay = 1e-4\\n\",\n    \"\\n\",\n    \"# Reset workspace to clear out memory allocated during our first run.\\n\",\n    \"workspace.ResetWorkspace()\\n\",\n    \"\\n\",\n    \"# 1. Use the model helper to create a CNN for us\\n\",\n    \"train_model = model_helper.ModelHelper(\\n\",\n    \"    # Arbitrary name for referencing the network in your workspace: you could call it tacos or boatzncarz\\n\",\n    \"    name=\\\"train\\\",\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"# 2. Create a database reader\\n\",\n    \"# This training data reader is shared between all GPUs.\\n\",\n    \"# When reading data, the trainer runs ImageInputOp for each GPU to retrieve their own unique batch of training data.\\n\",\n    \"# CreateDB is inherited by cnn.ModelHelper from model_helper.py\\n\",\n    \"# We are going to name it \\\"train_reader\\\" and pass in the db configurations we set earlier\\n\",\n    \"reader = train_model.CreateDB(\\n\",\n    \"    \\\"train_reader\\\",\\n\",\n    \"    db=train_data_db,\\n\",\n    \"    db_type=train_data_db_type,\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 9\\n\",\n    \"# assumes you're using the functions created in Part 4, 5, 6\\n\",\n    \"dpm.Parallelize_GPU(\\n\",\n    \"    train_model,\\n\",\n    \"    input_builder_fun=add_image_input_ops,\\n\",\n    \"    forward_pass_builder_fun=create_resnet50_model_ops,\\n\",\n    \"    param_update_builder_fun=add_parameter_update_ops,\\n\",\n    \"    devices=gpus,\\n\",\n    \"    optimize_gradient_memory=True,\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"workspace.RunNetOnce(train_model.param_init_net)\\n\",\n    \"workspace.CreateNet(train_model.net)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 10\\n\",\n    \"test_model = model_helper.ModelHelper(\\n\",\n    \"    name=\\\"test\\\",\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"reader = test_model.CreateDB(\\n\",\n    \"    \\\"test_reader\\\",\\n\",\n    \"    db=test_data_db,\\n\",\n    \"    db_type=test_data_db_type,\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"# Validation is parallelized across devices as well\\n\",\n    \"dpm.Parallelize_GPU(\\n\",\n    \"    test_model,\\n\",\n    \"    input_builder_fun=add_image_input_ops,\\n\",\n    \"    forward_pass_builder_fun=create_resnet50_model_ops,\\n\",\n    \"    param_update_builder_fun=None,\\n\",\n    \"    devices=gpus,\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"workspace.RunNetOnce(test_model.param_init_net)\\n\",\n    \"workspace.CreateNet(test_model.net)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 10 - display reporting setup\\n\",\n    \"%matplotlib inline\\n\",\n    \"from caffe2.python import visualize\\n\",\n    \"from matplotlib import pyplot as plt\\n\",\n    \"\\n\",\n    \"def display_images_and_confidence():\\n\",\n    \"    images = []\\n\",\n    \"    confidences = []\\n\",\n    \"    n = 16\\n\",\n    \"    data = workspace.FetchBlob(\\\"gpu_0/data\\\")\\n\",\n    \"    label = workspace.FetchBlob(\\\"gpu_0/label\\\")\\n\",\n    \"    softmax = workspace.FetchBlob(\\\"gpu_0/softmax\\\")\\n\",\n    \"    for arr in zip(data[0:n], label[0:n], softmax[0:n]):\\n\",\n    \"        # CHW to HWC, normalize to [0.0, 1.0], and BGR to RGB\\n\",\n    \"        bgr = (arr[0].swapaxes(0, 1).swapaxes(1, 2) + 1.0) / 2.0\\n\",\n    \"        rgb = bgr[...,::-1]\\n\",\n    \"        images.append(rgb)\\n\",\n    \"        confidences.append(arr[2][arr[1]])\\n\",\n    \"\\n\",\n    \"    # Create grid for images\\n\",\n    \"    fig, rows = plt.subplots(nrows=4, ncols=4, figsize=(12, 12))\\n\",\n    \"    plt.tight_layout(h_pad=2)\\n\",\n    \"\\n\",\n    \"    # Display images and the models confidence in their label\\n\",\n    \"    items = zip([ax for cols in rows for ax in cols], images, confidences)\\n\",\n    \"    for (ax, image, confidence) in items:\\n\",\n    \"        ax.imshow(image)\\n\",\n    \"        if confidence >= 0.5:\\n\",\n    \"            ax.set_title(\\\"RIGHT ({:.1f}%)\\\".format(confidence * 100.0), color='green')\\n\",\n    \"        else:\\n\",\n    \"            ax.set_title(\\\"WRONG ({:.1f}%)\\\".format(confidence * 100.0), color='red')\\n\",\n    \"\\n\",\n    \"    plt.show()\\n\",\n    \"\\n\",\n    \"    \\n\",\n    \"def accuracy(model):\\n\",\n    \"    accuracy = []\\n\",\n    \"    prefix = model.net.Proto().name\\n\",\n    \"    for device in model._devices:\\n\",\n    \"        accuracy.append(\\n\",\n    \"            np.asscalar(workspace.FetchBlob(\\\"gpu_{}/{}_accuracy\\\".format(device, prefix))))\\n\",\n    \"    return np.average(accuracy)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"scrolled\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# SOLUTION for Part 11\\n\",\n    \"\\n\",\n    \"# Start looping through epochs where we run the batches of images to cover the entire dataset\\n\",\n    \"# Usually you would want to run a lot more epochs to increase your model's accuracy\\n\",\n    \"num_epochs = 2\\n\",\n    \"for epoch in range(num_epochs):\\n\",\n    \"    # Split up the images evenly: total images / batch size\\n\",\n    \"    num_iters = int(train_data_count / total_batch_size)\\n\",\n    \"    for iter in range(num_iters):\\n\",\n    \"        # Stopwatch start!\\n\",\n    \"        t1 = time.time()\\n\",\n    \"        # Run this iteration!\\n\",\n    \"        workspace.RunNet(train_model.net.Proto().name)\\n\",\n    \"        t2 = time.time()\\n\",\n    \"        dt = t2 - t1\\n\",\n    \"        \\n\",\n    \"        # Stopwatch stopped! How'd we do?\\n\",\n    \"        print((\\n\",\n    \"            \\\"Finished iteration {:>\\\" + str(len(str(num_iters))) + \\\"}/{}\\\" +\\n\",\n    \"            \\\" (epoch {:>\\\" + str(len(str(num_epochs))) + \\\"}/{})\\\" + \\n\",\n    \"            \\\" ({:.2f} images/sec)\\\").\\n\",\n    \"            format(iter+1, num_iters, epoch+1, num_epochs, total_batch_size/dt))\\n\",\n    \"        \\n\",\n    \"        # Get the average accuracy for the training model\\n\",\n    \"        train_accuracy = accuracy(train_model)\\n\",\n    \"    \\n\",\n    \"    # Run the test model and assess accuracy\\n\",\n    \"    test_accuracies = []\\n\",\n    \"    for _ in range(test_data_count / total_batch_size):\\n\",\n    \"        # Run the test model\\n\",\n    \"        workspace.RunNet(test_model.net.Proto().name)\\n\",\n    \"        test_accuracies.append(accuracy(test_model))\\n\",\n    \"    test_accuracy = np.average(test_accuracies)\\n\",\n    \"\\n\",\n    \"    print(\\n\",\n    \"        \\\"Train accuracy: {:.3f}, test accuracy: {:.3f}\\\".\\n\",\n    \"        format(train_accuracy, test_accuracy))\\n\",\n    \"    \\n\",\n    \"    # Output images with confidence scores as the caption\\n\",\n    \"    display_images_and_confidence()\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### TO DO:\\n\",\n    \"(or things to explore on your own to improve this tutorial!)\\n\",\n    \"* Create your own database of images\\n\",\n    \"* Explore the layers\\n\",\n    \"* Print out images of the intermediates/activations to show what's happening under the hood\\n\",\n    \"* Make some interactions between epochs (change of params to show impact)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.6\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/Python_Op.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Python Op Tutorial\\n\",\n    \"In this tutorial we cover the Python operator that allows writing Caffe2 operators using Python, we also discuss some of the underlying implementation details.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Forward Python Operator, Net.Python() Interface\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Caffe2 provides a high-level interface that helps creating Python ops. Let's consider the following example:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 19,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"[ 6.]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from caffe2.python import core, workspace\\n\",\n    \"import numpy as np\\n\",\n    \"\\n\",\n    \"def f(inputs, outputs):\\n\",\n    \"    outputs[0].feed(2 * inputs[0].data)\\n\",\n    \"\\n\",\n    \"workspace.ResetWorkspace()\\n\",\n    \"net = core.Net(\\\"tutorial\\\")\\n\",\n    \"net.Python(f)([\\\"x\\\"], [\\\"y\\\"])\\n\",\n    \"workspace.FeedBlob(\\\"x\\\", np.array([3.]))\\n\",\n    \"workspace.RunNetOnce(net)\\n\",\n    \"print(workspace.FetchBlob(\\\"y\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"As seen in the example, net.Python() function returns a callable that can be used just like any other operator. In this example, we add a new Python operator to the net with input \\\"x\\\" and output \\\"y\\\". Note that you can save the output of net.Python() and call it multiple times to add multiple Python operators (with possibly different inputs and outputs).\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's take a closer look at net.Python() function and a corresponding body of a new Python operator (f). Every time net.Python(f) is called it serializes a given function f and saves it in a global registry under a known key (token, passed to a PythonOp as an argument). After this, net.Python() returns a lambda that accepts positional and keyword arguments (typically inputs, outputs and extra arguments) and attaches a new Python operator to the net that calls function f on a given list of inputs and outputs.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Python operator's function (f) expects two positional arguments: a list of inputs and a list of outputs. When an operator is executed it transparently converts Caffe2 blobs into the elements of these lists.\\n\",\n    \"In case of CPU tensor blobs, these blobs are converted into TensorCPU objects that act as wrappers around Numpy arrays. Let's take a closer look at a relationship between Caffe2 CPU tensor, Python's TensorCPU object and a Numpy array:\\n\",\n    \"1. Conversion between C++ tensor objects and Numpy objects happens automatically and is handled by PyBind library.\\n\",\n    \"2. When generating a TensorCPU wrapper, a new Numpy array object is created which **shares** the same memory storage as a corresponding Caffe2 CPU tensor. This Numpy array is accessible in Python as a **.data** property of a TensorCPU object.\\n\",\n    \"3. Note that, although Numpy array and Caffe2 tensor might share the same storage, other tensor data (e.g. shape) of Caffe2 tensor is stored **separately** from a Numpy array. Furthermore, Numpy may copy and reallocate its array to a different location in memory (e.g. when we try to resize an array) during operator's function execution. It's important to keep that in mind when writing a Python operator's code to ensure that Caffe2 and Numpy output tensors are in sync.\\n\",\n    \"4. TensorCPU's **feed** method accepts a Numpy tensor, resizes an underying Caffe2 tensor and copies Numpy's tensor data into a Caffe2 tensor.\\n\",\n    \"5. Another way to ensure that Caffe2's output tensor is properly set is to call **reshape** function on a corresponding TensorCPU output and then copy data in Python to the output's **.data** tensor, e.g.:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 27,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"[ 6.]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"def f_reshape(inputs, outputs):\\n\",\n    \"    outputs[0].reshape(inputs[0].shape)\\n\",\n    \"    outputs[0].data[...] = 2 * inputs[0].data\\n\",\n    \"\\n\",\n    \"workspace.ResetWorkspace()\\n\",\n    \"net = core.Net(\\\"tutorial\\\")\\n\",\n    \"net.Python(f_reshape)([\\\"x\\\"], [\\\"z\\\"])\\n\",\n    \"workspace.FeedBlob(\\\"x\\\", np.array([3.]))\\n\",\n    \"workspace.RunNetOnce(net)\\n\",\n    \"print(workspace.FetchBlob(\\\"z\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This example works correctly because \\\"reshape\\\" method updates an underlying Caffe2 tensor and a subsequent call to the \\\".data\\\" property returns a Numpy array that shares memory with a Caffe2 tensor. The last line in \\\"f_reshape\\\" copies data into the shared memory location.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"There're several additional arguments that net.Python() accepts. When **pass_workspace=True** is passed, a workspace is passed to an operator's Python function:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 28,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"[ 6.]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"def f_workspace(inputs, outputs, workspace):\\n\",\n    \"    outputs[0].feed(2 * workspace.blobs[\\\"x\\\"].fetch())\\n\",\n    \"\\n\",\n    \"workspace.ResetWorkspace()\\n\",\n    \"net = core.Net(\\\"tutorial\\\")\\n\",\n    \"net.Python(f_workspace, pass_workspace=True)([], [\\\"y\\\"])\\n\",\n    \"workspace.FeedBlob(\\\"x\\\", np.array([3.]))\\n\",\n    \"workspace.RunNetOnce(net)\\n\",\n    \"print(workspace.FetchBlob(\\\"y\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Gradient Python Operator\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Another important net.Python() argument is \\\"grad_f\\\" - a Python function for a corresponding gradient operator:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 29,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"[ 2.]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"def f(inputs, outputs):\\n\",\n    \"            outputs[0].reshape(inputs[0].shape)\\n\",\n    \"            outputs[0].data[...] = inputs[0].data * 2\\n\",\n    \"\\n\",\n    \"def grad_f(inputs, outputs):\\n\",\n    \"    # Ordering of inputs is [fwd inputs, outputs, grad_outputs]\\n\",\n    \"    grad_output = inputs[2]\\n\",\n    \"\\n\",\n    \"    grad_input = outputs[0]\\n\",\n    \"    grad_input.reshape(grad_output.shape)\\n\",\n    \"    grad_input.data[...] = grad_output.data * 2\\n\",\n    \"\\n\",\n    \"workspace.ResetWorkspace()\\n\",\n    \"net = core.Net(\\\"tutorial\\\")\\n\",\n    \"net.Python(f, grad_f)([\\\"x\\\"], [\\\"y\\\"])\\n\",\n    \"workspace.FeedBlob(\\\"x\\\", np.array([3.]))\\n\",\n    \"net.AddGradientOperators([\\\"y\\\"])\\n\",\n    \"workspace.RunNetOnce(net)\\n\",\n    \"print(workspace.FetchBlob(\\\"x_grad\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"When net.Python() is called with a gradient function specified, it also registers a serialized gradient function that is used by a corresponding gradient Python operator (**PythonGradient**). This operator executes a gradient function that expects two arguments - input and output lists. The input list argument contains all forward function inputs, followed by all of its outputs, followed by the gradients of forward function outputs. The output list contains the gradients of forward function inputs. Note: net.Python()'s **grad_output_indices**/**grad_input_indices** allow specifying indices of gradient output/input blobs that gradient function reads/writes to.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"#### Note on GPU tensors:\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"PythonOp implementation is CPU specific, it uses Numpy arrays that expect CPU memory storage. In order to be able to use a Python operator with GPU tensors, we define a CUDA version of PythonOp using GPUFallbackOp. This operator wraps a CPU-operator and adds GPU-to-CPU (and opposite direction) copy operations. Thus, when using a PythonOp with a CUDA device option, all input CUDA tensors are automatically copied to CPU memory and all CPU output tensors are copied back to GPU.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.14\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/README.md",
    "content": "## Executing tutorials\n\nIn order to run tutorials, use jupyter. For more details refer to https://caffe2.ai/docs/tutorials\n\n## Contributing to tutorials\n\nPython scripts should be generated out of these tutorials in order to make it easy to review the changes. In order to make this process automatic, you could patch your jupyter config using [this file](jupyter_notebook_config.py)\n\nIf you would like to run sync manually, run this [script](tutorials_to_script_converter.py). It will regenerate python scripts for all the tutorials in this folder\n"
  },
  {
    "path": "caffe2/python/tutorials/Toy_Regression.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Tutorial 2. A Simple Toy Regression\\n\",\n    \"\\n\",\n    \"This is a quick example showing how one can use the concepts introduced in [Tutorial 1: Basics](https://caffe2.ai/docs/tutorial-basics-of-caffe2.html) to do regression. This tutorial is split up into two parts. **Part I** is a more verbose example of creating and training a polynomial regression model and **Part II** is a concise linear regression example.\\n\",\n    \"\\n\",\n    \"## Part I: Polynomial Regression\\n\",\n    \"\\n\",\n    \"The problem we are dealing with is a relatively simple one and involves a one-dimensional input $x$ and one-dimensional output $y.$ Because we seek a second order polynomial as the regression model, the weight vector will contain two weights ($\\\\beta_2$ and $\\\\beta_1$) and there will be a single bias ($\\\\beta_0$) or intercept. The desired solution is of the form:\\n\",\n    \"\\n\",\n    \"$$y = \\\\beta_2x^2 + \\\\beta_1x + \\\\beta_0$$\\n\",\n    \"\\n\",\n    \"For this tutorial, we will generate and format an arbitrary set of input data that possess a strong second order polynomial relationship. We will then construct the model, specify the training algorithm, perform the training, and finally look at the results.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:root:This caffe2 python run does not have GPU support. Will run in CPU only mode.\\n\",\n      \"WARNING:root:Debug message: No module named caffe2_pybind11_state_gpu\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from __future__ import absolute_import\\n\",\n    \"from __future__ import division\\n\",\n    \"from __future__ import print_function\\n\",\n    \"from __future__ import unicode_literals\\n\",\n    \"from caffe2.python import workspace, brew, optimizer\\n\",\n    \"from caffe2.python.model_helper import ModelHelper\\n\",\n    \"import numpy as np\\n\",\n    \"%matplotlib inline\\n\",\n    \"import matplotlib.pyplot as plt\\n\",\n    \"import sklearn.datasets\\n\",\n    \"from sklearn.preprocessing import PolynomialFeatures\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Inputs\\n\",\n    \"\\n\",\n    \"Specify the input parameters of the regression model here including: number of samples in the input data, number of training iterations, learning rate of SGD algorithm, and the initial weights of the model\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Number of training sample to generate\\n\",\n    \"num_samples = 200\\n\",\n    \"# Learning Rate of SGD algorithm\\n\",\n    \"learning_rate = .05\\n\",\n    \"# Number of iterations to train\\n\",\n    \"training_iters = 100\\n\",\n    \"# Initial model weights\\n\",\n    \"initial_weights = [0.,0.]\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Create and Prepare the Dataset\\n\",\n    \"\\n\",\n    \"Now, we will create and prepare the dataset for use with the model. Note, we are just constructing numpy arrays here. Any other data can be used as long as it is shaped properly before being input into the model.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"X Sample:\\n\",\n      \"[[ 1.11144622  1.2353127 ]\\n\",\n      \" [-0.80563214  0.64904315]\\n\",\n      \" [-0.20122346  0.04049088]\\n\",\n      \" [-1.39473823  1.94529472]\\n\",\n      \" [-0.26264545  0.06898263]]\\n\",\n      \"Y Sample:\\n\",\n      \"[[2.0341036 ]\\n\",\n      \" [1.25892588]\\n\",\n      \" [0.54689346]\\n\",\n      \" [2.488389  ]\\n\",\n      \" [1.00825533]]\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Text(0.5,1,u'Input Training Data')\"\n      ]\n     },\n     \"execution_count\": 3,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x1a12103150>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Create the original observations\\n\",\n    \"orig_X,_ = sklearn.datasets.make_regression(n_samples=num_samples,n_features=1,noise=5)\\n\",\n    \"poly = PolynomialFeatures(degree=2, include_bias=False)\\n\",\n    \"# Transform the features into second order polynomial features\\n\",\n    \"xx_ = poly.fit_transform(orig_X)\\n\",\n    \"\\n\",\n    \"# Extract the predictors and the values from the manufactured data\\n\",\n    \"X = [i[0] for i in xx_]\\n\",\n    \"Y_gt = [i[1] for i in xx_]\\n\",\n    \"noise = np.random.uniform(size=(len(Y_gt)))\\n\",\n    \"# Add some noise to the ground truth values\\n\",\n    \"Y_gt += noise\\n\",\n    \"\\n\",\n    \"# Shape the ground truth values for use with the model\\n\",\n    \"Y_gt = np.reshape(Y_gt,(-1,1))\\n\",\n    \"# Format the input features. Recall, we accomplish polynomial regression by\\n\",\n    \"#   including the original and the polynomial version of the predictors\\n\",\n    \"#   as features of the model\\n\",\n    \"X = np.hstack((np.array(X).reshape(-1,1),np.array(X).reshape(-1,1)**2))\\n\",\n    \"\\n\",\n    \"# Print a sample of the input data. X is the list of 2-feature input observations \\n\",\n    \"#   and Y is the list of ground truth values associated with each observation\\n\",\n    \"print(\\\"X Sample:\\\\n{}\\\".format(X[:5]))\\n\",\n    \"print(\\\"Y Sample:\\\\n{}\\\".format(Y_gt[:5]))\\n\",\n    \"\\n\",\n    \"# Plot the input data\\n\",\n    \"plt.scatter([i[0] for i in X],Y_gt,label=\\\"original data\\\",color='b')\\n\",\n    \"plt.xlabel(\\\"x\\\")\\n\",\n    \"plt.ylabel(\\\"y\\\")\\n\",\n    \"plt.title(\\\"Input Training Data\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Create the Model\\n\",\n    \"\\n\",\n    \"#### Define the model architecture\\n\",\n    \"With our training data created and our second order polynomial assumption stated, we can now create a model to learn the regression line. We will use a 'FC' layer as the main component of the model. Since we desire two weights ($\\\\beta_2$ and $\\\\beta_1$), we set our input dimension to 2, and since we only expect a single quantitative result, our output dimension is 1. Note, when using an 'FC' layer it is implied that there is a bias, which we will use as our $\\\\beta_0.$\\n\",\n    \"\\n\",\n    \"Also, before continuing take a look at the protobuf created in this step. The first print out is of the 'net,' and contains the architecture of the model. At a glance, we see that as expected, there is a single op in the network that expects an input $X,$ a weight and bias, and outputs $y_{pred}.$ In the print out of the 'param_init_net,' we see that this is where the initializations for the weights and biases exist. This is an important observation that gives insight into how a model in Caffe2 is constructed and maintained.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"************* Predict Net *************\\n\",\n      \"name: \\\"regression_model\\\"\\n\",\n      \"op {\\n\",\n      \"  input: \\\"X\\\"\\n\",\n      \"  input: \\\"y_pred_w\\\"\\n\",\n      \"  input: \\\"y_pred_b\\\"\\n\",\n      \"  output: \\\"y_pred\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"FC\\\"\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"use_cudnn\\\"\\n\",\n      \"    i: 1\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"order\\\"\\n\",\n      \"    s: \\\"NCHW\\\"\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"cudnn_exhaustive_search\\\"\\n\",\n      \"    i: 0\\n\",\n      \"  }\\n\",\n      \"}\\n\",\n      \"external_input: \\\"X\\\"\\n\",\n      \"external_input: \\\"y_pred_w\\\"\\n\",\n      \"external_input: \\\"y_pred_b\\\"\\n\",\n      \"\\n\",\n      \"\\n\",\n      \"************* Init Net *************\\n\",\n      \"name: \\\"regression_model_init\\\"\\n\",\n      \"op {\\n\",\n      \"  output: \\\"y_pred_w\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"XavierFill\\\"\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"shape\\\"\\n\",\n      \"    ints: 1\\n\",\n      \"    ints: 2\\n\",\n      \"  }\\n\",\n      \"}\\n\",\n      \"op {\\n\",\n      \"  output: \\\"y_pred_b\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"ConstantFill\\\"\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"shape\\\"\\n\",\n      \"    ints: 1\\n\",\n      \"  }\\n\",\n      \"}\\n\",\n      \"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Create the model helper object we will use to create the regression model\\n\",\n    \"regression_model = ModelHelper(name=\\\"regression_model\\\")\\n\",\n    \"\\n\",\n    \"# Add the FC layer, which is the main component of a linear regression model\\n\",\n    \"y_pred = brew.fc(regression_model,'X','y_pred', dim_in=2, dim_out=1)\\n\",\n    \"\\n\",\n    \"# Print the predict and init net to see what protobuf was created for this model\\n\",\n    \"print(\\\"************* Predict Net *************\\\")\\n\",\n    \"print(regression_model.net.Proto())\\n\",\n    \"print(\\\"\\\\n************* Init Net *************\\\")\\n\",\n    \"print(regression_model.param_init_net.Proto())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"#### Add the training operators and prime the workspace\\n\",\n    \"\\n\",\n    \"In this **very important** step, we specify the loss function, setup the SGD training algorithm, prime and initialize the workspace, and initialize our model's weights and biases.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"True\"\n      ]\n     },\n     \"execution_count\": 5,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# The loss function is computed by a squared L2 distance, \\n\",\n    \"#   and then averaged over all items.\\n\",\n    \"dist = regression_model.SquaredL2Distance(['Y_gt', y_pred], \\\"dist\\\")\\n\",\n    \"loss = regression_model.AveragedLoss(dist, \\\"loss\\\")\\n\",\n    \"\\n\",\n    \"# Add the gradient operators and setup the SGD algorithm\\n\",\n    \"regression_model.AddGradientOperators([loss])\\n\",\n    \"optimizer.build_sgd(regression_model, base_learning_rate=learning_rate)\\n\",\n    \"\\n\",\n    \"# Prime the workspace with some data\\n\",\n    \"workspace.FeedBlob(\\\"Y_gt\\\",Y_gt.astype(np.float32))\\n\",\n    \"workspace.FeedBlob(\\\"X\\\",X.astype(np.float32))\\n\",\n    \"\\n\",\n    \"# Run the init net to prepare the workspace then create the net\\n\",\n    \"workspace.RunNetOnce(regression_model.param_init_net)\\n\",\n    \"workspace.CreateNet(regression_model.net)\\n\",\n    \"\\n\",\n    \"# Inject our desired initial weights and bias\\n\",\n    \"workspace.FeedBlob(\\\"y_pred_w\\\",np.array([initial_weights]).astype(np.float32))\\n\",\n    \"workspace.FeedBlob(\\\"y_pred_b\\\",np.array([0.]).astype(np.float32))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"#### Run the training\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Training Complete\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Run the training for training_iters\\n\",\n    \"for i in range(training_iters):\\n\",\n    \"    workspace.RunNet(regression_model.net)\\n\",\n    \"\\n\",\n    \"print(\\\"Training Complete\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Extract Results\\n\",\n    \"\\n\",\n    \"Now that our model is trained, we can pull out the learned weights and biases which exist as blobs in the workspace named 'y_pred_w' and 'y_pred_b.'\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Best Fit Line: 1.0157*x^2 + 0.01501*x + 0.4665\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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Y4zJMJb4jTEmwyRVHb9JHdu2bWPZsmVs2bLF61BMnNWuXZu99tqLnJxKT1BlkpwlflMpy5Yto27dujRr1oyKo0CbVKaqrFmzhmXLltG8eXOvwzEJYlU9plK2bNlCQUGBJf00IyIUFBTYlVyaS/nEX1wMzZpBVpb7WRztNCymyizppyd7X9NfSlf1FBdDv36waZP7e8kS9zdAz57exWWMMckspUv8w4btSPplNm1yzxvjhVtuuYX33nvP6zBCevbZZxk4cCAAr7/+OnPmzPE4IuOFlE78S0NMshjqeZOeVJXS0tLIC4axffv2uMRy++23c8IJJ8RlW2XiFVsgS/yZK6UTf9OmsT1v0sfixYs56KCDGDBgAO3atePnn39mypQpHHHEEbRr147u3buzYcMGACZPnkyLFi3o0qULV155Jaef7ubOHj58OP369aNr165cfPHFlJSUMGTIEDp27Ejr1q154oknAPjll184+uijadOmDa1ateLjjz+mpKSE3r1706pVKw455BAefPBBAHr37s0rr7wCwPvvv0/btm055JBDuOSSS9i6dSvghia59dZbadeuHYcccgjz5s3b6fieffZZunfvzhlnnEHXrl0BuPfee8tju/XWWwHYuHEjp512GoceeiitWrXipZdeKt/H6tWrAZg+fTrHHntshe1/9tlnvPHGGwwZMoQ2bdrw008/xe29MTG6/36o5vOf0nX8I0dWrOMHyMtzz5tqdPXV8M038d1mmzbw0ENhF5k/fz7jxo1j9OjRrF69mhEjRvDee++Rn5/P3XffzQMPPMD111/P5ZdfztSpU2nevDk9evSosI0ZM2bwySefkJuby9ixY6lfvz5fffUVW7dupXPnznTt2pVXX32Vk046iWHDhlFSUsKmTZv45ptvWL58Od999x0Af/75Z4Xtbtmyhd69e/P+++9zwAEHcPHFFzNmzBiuvvpqABo0aMDMmTMZPXo09913H0899dROx/f5558ze/ZsdtttN6ZMmcKCBQuYNm0aqkq3bt2YOnUqq1atokmTJrz55psArF27NqrTe+SRR9KtWzdOP/10/v73v0e1jkmAr76C666DWrXAVwVXHVK6xN+zJ4wdC70avcWz9KKwqTJ2rDXsZorCwkIOP/xwAL744gvmzJlD586dadOmDc899xxLlixh3rx57LPPPuV90gMTf7du3cjNzQVgypQpjB8/njZt2tCpUyfWrFnDggUL6NixI+PGjWP48OF8++231K1bl3322YeFCxcyaNAg3n77berVq1dhu/Pnz6d58+YccMABAPTq1YupU6eWv37OOecA0L59exYvXhz0+E488UR222238timTJlC27ZtadeuHfPmzWPBggUccsghvPfee9xwww18/PHH1K9fv4pn1VSr0aMhPx8uuqhad5vSJX5wSb7npmXQbzy9ii+DLl28DinzRCiZJ0p+fn7576rKiSeeyIQJEyos8/XXX8e0jVGjRnHSSSfttNzUqVN58803ueiiixgyZAgXX3wxs2bN4p133uGxxx5j4sSJPPPMMxW2FU6tWrUAyM7ODlmHHxjb0KFDufzyy3dabsaMGUyePJmhQ4fStWtXbrnlFmrUqFHe7mF98pPUmjXw4ovQuzdU8xd2Spf4y/3f/7kT99hjXkdiPHL44Yfz6aef8uOPPwKwadMmfvjhB1q0aMHChQvLS9VldeDBnHTSSYwZM4Zt27YB8MMPP7Bx40aWLFlCo0aNuOyyy+jbty8zZ85k9erVlJaWcu6553LHHXcwc+bMCttq0aIFixcvLo/n+eef55hjjqn08Z100kk888wz5e0Wy5cv57fffmPFihXk5eVx4YUXct1115XH0axZM2bMmAHApEmTgm6zbt26rF+/vtIxmSoaNw62bIEBA6p91ylf4gfcpVKfPi7x//or7L671xGZatawYUOeffZZevToUd6IOmLECA444ABGjx7NySefTIMGDTjssMNCbuPSSy9l8eLFtGvXDlWlYcOGvP7663z00Ufce++95OTkUKdOHcaPH8/y5cvp06dPean6rrvuqrCt2rVrM27cOLp378727dvp2LEjV1xxRaWPr2vXrsydO5cjjjgCgDp16lBUVMSPP/7IkCFDyMrKIicnhzFjxgBw66230rdvX+688046deoUdJsXXHABl112GY888givvPIK++67b6XjMzEqLYUxY+Coo+CQQ6p990k12XqHDh200hOxLFgABxwAt98ON98c38DMTubOnctBBx3kdRhR2bBhA3Xq1EFV+cc//sH+++/P4MGDvQ4rqaXS+5uS3n4bTjkFJkyACy6o0qZEZIaqdohlnfSo6gHYf3/o2hWeeAIS1O/ZpKYnn3ySNm3a0LJlS9auXRu0ntyYavXY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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x1a1af2f9d0>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Extract the learned coes and intercept from the workspace\\n\",\n    \"coes = workspace.FetchBlob(\\\"y_pred_w\\\")[0]\\n\",\n    \"intercept = workspace.FetchBlob(\\\"y_pred_b\\\")\\n\",\n    \"\\n\",\n    \"# Calculate the regression line for plotting\\n\",\n    \"x_vals = np.linspace(orig_X.min(), orig_X.max(),100)\\n\",\n    \"regression_result = intercept[0] + coes[0]*x_vals + coes[1]*(x_vals**2)\\n\",\n    \"print(\\\"Best Fit Line: {}*x^2 + {}*x + {}\\\".format(round(coes[1],5), round(coes[0],5), round(intercept[0],5)))\\n\",\n    \"\\n\",\n    \"# Plot the results of the regression\\n\",\n    \"plt.scatter([i[0] for i in X],Y_gt,label=\\\"original data\\\",color='b')\\n\",\n    \"plt.plot(x_vals,regression_result,label=\\\"regression result\\\",color='r')\\n\",\n    \"plt.legend()\\n\",\n    \"plt.xlabel(\\\"x\\\")\\n\",\n    \"plt.ylabel(\\\"y\\\")\\n\",\n    \"plt.title(\\\"Polynomial Regression Fit: ${{{}}}x^2 + {{{}}}x + {{{}}}$\\\".format(round(coes[1],5), round(coes[0],5), round(intercept[0],5)))\\n\",\n    \"plt.show()\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Part II: Express Linear Regression Example\\n\",\n    \"\\n\",\n    \"The above example shows you how to create a polynomial regression model that is easily adapted to handle higher order polynomials. Now, we will consider the baseline case where we desire a simple first order model, with 1-D input $x,$ 1-D output $y,$ and a solution of the form:\\n\",\n    \"\\n\",\n    \"$$y = \\\\beta_1x + \\\\beta_0$$\\n\",\n    \"\\n\",\n    \"The structure of Part II will be similar to Part I. First, we will generate the dataset, then we will construct the model and specify the training routine, and finally we will train and extract our results.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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65+Rwwf9tNMsBg+G774r2jIbNoRx43LdZNu2bUyfPp0JEyZw4MABRo0axZIlS6hUqRIvvfQSY8eO5fHHH6dfv34sX76cmjVr0qlTpyxlxMfHs2LFCipUqMCUKVM455xzWLt2LcePH6dly5a0adOGjz/+mLZt2zJ8+HBSU1M5cuQI3333HXv37mXz5s0A/PXXX1nKPXbsGN27d2fp0qVcccUV3H///UycOJHBgwcDULVqVdatW8eECRMYM2YMb731Vo7zW7VqFRs3bqRKlSosXryYH374gTVr1qCqtG/fnuXLl7N//34uvPBCFixYAMBBP/s7W7RoQfv27bntttu4++67/drHmJC0cyf07QtLl0KrVvDWW3D55We0CtZCKAKRkZFcddVVAKxevZqEhARatmxJw4YNeeedd0hMTOT777/nkksuyZhTnz0gtG/fngoVKgCwePFi3n33XRo2bEizZs1ISkrihx9+oGnTpkyfPp2RI0eyadMmKleuzCWXXMLOnTt56KGH+Pzzzzn77LOzlLtt2zZq1qzJFVdcAUC3bt1YnqkvskOHDgA0adKE3bt3ez2/G2+8kSpVqmTUbfHixTRq1IjGjRvz/fff88MPP1CvXj2WLFnC0KFD+frrrznnnHMKeVWNKSFSU50vnfXqwZo1MHEifPXVGQ8GEGothDy+yReXSpUqZfxdVbnxxhv5z3/+k2Wb9evX56uM119/nbZt2+bYbvny5SxYsICuXbsyZMgQ7r//fjZs2MCiRYt48803+fDDD5k2bVqWsnJTrlw5AEqXLu1zjCB73YYNG0a/fv1ybBcfH8/ChQsZNmwYbdq04emnn+ass87KGFexZwqMySYhAXr1crqFbrkFJk1yxgxcYi2EInbVVVexcuVKduzYAcCRI0fYvn07tWrVYufOnRnfwtP72L1p27YtEydO5OTJkwBs376dlJQUEhMTOf/88+nTpw+9evVi3bp1HDhwgLS0NO666y6ee+451q1bl6WsWrVqsXv37oz6zJw5k2uvvbbA59e2bVumTZuWMS6yd+9efv/9d3755RcqVqxIly5deOyxxzLqERUVRXx8PAAfffSR1zIrV67M4cOHC1wnY4LOiRPw3HPQqBH88APExsKnn7oaDMDFFoKIXAy8C1QH0oApqvqaW/UpKtWqVWPGjBl06tQpY/B21KhRXHHFFUyY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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x1a12125f10>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"#####################################################################\\n\",\n    \"# Initialize data\\n\",\n    \"#####################################################################\\n\",\n    \"X,Y_gt = sklearn.datasets.make_regression(n_samples=100,n_features=1,noise=10)\\n\",\n    \"Y_gt = np.reshape(Y_gt,(-1,1))\\n\",\n    \"Y_gt /= 100.\\n\",\n    \"\\n\",\n    \"#####################################################################\\n\",\n    \"# Create and train model\\n\",\n    \"#####################################################################\\n\",\n    \"# Construct model with single FC layer\\n\",\n    \"regression_model = ModelHelper(name=\\\"regression_model\\\")\\n\",\n    \"y_pred = brew.fc(regression_model,'X','y_pred', dim_in=1, dim_out=1)\\n\",\n    \"\\n\",\n    \"# Specify Loss function\\n\",\n    \"dist = regression_model.SquaredL2Distance(['Y_gt', y_pred], \\\"dist\\\")\\n\",\n    \"loss = regression_model.AveragedLoss(dist, \\\"loss\\\")\\n\",\n    \"\\n\",\n    \"# Get gradients for all the computations above.\\n\",\n    \"regression_model.AddGradientOperators([loss])\\n\",\n    \"optimizer.build_sgd(regression_model, base_learning_rate=0.05)\\n\",\n    \"\\n\",\n    \"# Prime and prepare workspace for training\\n\",\n    \"workspace.FeedBlob(\\\"Y_gt\\\",Y_gt.astype(np.float32))\\n\",\n    \"workspace.FeedBlob(\\\"X\\\",X.astype(np.float32))\\n\",\n    \"workspace.RunNetOnce(regression_model.param_init_net)\\n\",\n    \"workspace.CreateNet(regression_model.net)\\n\",\n    \"\\n\",\n    \"# Set the initial weight and bias to 0\\n\",\n    \"workspace.FeedBlob(\\\"y_pred_w\\\",np.array([[0.]]).astype(np.float32))\\n\",\n    \"workspace.FeedBlob(\\\"y_pred_b\\\",np.array([0.]).astype(np.float32))\\n\",\n    \"\\n\",\n    \"# Train the model\\n\",\n    \"for i in range(100):\\n\",\n    \"    workspace.RunNet(regression_model.net)\\n\",\n    \"\\n\",\n    \"#####################################################################\\n\",\n    \"# Collect and format results\\n\",\n    \"#####################################################################\\n\",\n    \"# Grab the learned weight and bias from workspace\\n\",\n    \"coe = workspace.FetchBlob(\\\"y_pred_w\\\")[0]\\n\",\n    \"intercept = workspace.FetchBlob(\\\"y_pred_b\\\")\\n\",\n    \"\\n\",\n    \"# Calculate the regression line for plotting\\n\",\n    \"x_vals = range(-3,4)\\n\",\n    \"regression_result = x_vals*coe + intercept\\n\",\n    \"\\n\",\n    \"# Plot the results\\n\",\n    \"plt.scatter(X,Y_gt,label=\\\"original data\\\",color='b')\\n\",\n    \"plt.plot(x_vals,regression_result,label=\\\"regression result\\\",color='r')\\n\",\n    \"plt.legend()\\n\",\n    \"plt.xlabel(\\\"x\\\")\\n\",\n    \"plt.ylabel(\\\"y\\\")\\n\",\n    \"plt.title(\\\"Regression Line: ${{{}}}x + {{{}}}$\\\".format(round(coe,5), round(intercept[0],5)))\\n\",\n    \"plt.show()\\n\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.14\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 1\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/Training_a_Model.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Dataset Formats\\n\",\n    \"\\n\",\n    \"When you look at a model and its dataset, one of the things that will be specified is how the dataset is organized. Additionally, within Caffe2 when you load the data you will need to relay this specification. When trying to optimize training and increase its speed you may find discussions related to changing this format. For the purposes of this tutorial you don't need to worry about that, but it is good to recognize the different flavors and the fact the the raw data is loaded into temporary databases to facilitate the network's training and testing.\\n\",\n    \"\\n\",\n    \"#### Data Ordering\\n\",\n    \"\\n\",\n    \"* NCHW: [description]\\n\",\n    \"* Others: [description]\\n\",\n    \"\\n\",\n    \"#### Databases\\n\",\n    \"\\n\",\n    \"* minidb: [description]\\n\",\n    \"* leveldb: [descrption]\\n\",\n    \"* others...\\n\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.13\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/create_your_own_dataset.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# How do I create my own dataset?\\n\",\n    \"\\n\",\n    \"So Caffe2 uses a binary DB format to store the data that we would like to train models on. A Caffe2 DB is a glorified name of a key-value storage where the keys are usually randomized so that the batches are approximately i.i.d. The values are the real stuff here: they contain the serialized strings of the specific data formats that you would like your training algorithm to ingest. So, the stored DB would look (semantically) like this:\\n\",\n    \"\\n\",\n    \"key1 value1\\n\",\n    \"key2 value2\\n\",\n    \"key3 value3\\n\",\n    \"...\\n\",\n    \"\\n\",\n    \"To a DB, it treats the keys and values as strings, but you probably want structured contents. One way to do this is to use a TensorProtos protocol buffer: it essentially wraps Tensors, aka multi-dimensional arrays, together with the tensor data type and shape information. Then, one can use the TensorProtosDBInput operator to load the data into an SGD training fashion.\\n\",\n    \"\\n\",\n    \"Here, we will show you one example of how to create your own dataset. To this end, we will use the UCI Iris dataset - which was a very popular classical dataset for classifying Iris flowers. It contains 4 real-valued features representing the dimensions of the flower, and classifies things into 3 types of Iris flowers. The dataset can be downloaded [here](https://archive.ics.uci.edu/ml/datasets/Iris).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:root:This caffe2 python run does not have GPU support. Will run in CPU only mode.\\n\",\n      \"WARNING:root:Debug message: No module named caffe2_pybind11_state_gpu\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# First let's import some necessities\\n\",\n    \"from __future__ import absolute_import\\n\",\n    \"from __future__ import division\\n\",\n    \"from __future__ import print_function\\n\",\n    \"from __future__ import unicode_literals\\n\",\n    \"\\n\",\n    \"%matplotlib inline\\n\",\n    \"import urllib2 # for downloading the dataset from the web.\\n\",\n    \"import numpy as np\\n\",\n    \"from matplotlib import pyplot\\n\",\n    \"from StringIO import StringIO\\n\",\n    \"from caffe2.python import core, utils, workspace\\n\",\n    \"from caffe2.proto import caffe2_pb2\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Raw data looks like this:\\n\",\n      \"5.1,3.5,1.4,0.2,Iris-setosa\\n\",\n      \"4.9,3.0,1.4,0.2,Iris-setosa\\n\",\n      \"4.7,3.2,1.3,0.2,Iris-setosa\\n\",\n      \"4.6,3.1,1.5,0.2,...\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"f = urllib2.urlopen('https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data')\\n\",\n    \"raw_data = f.read()\\n\",\n    \"print('Raw data looks like this:')\\n\",\n    \"print(raw_data[:100] + '...')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# load the features to a feature matrix.\\n\",\n    \"features = np.loadtxt(StringIO(raw_data), dtype=np.float32, delimiter=',', usecols=(0, 1, 2, 3))\\n\",\n    \"# load the labels to a feature matrix\\n\",\n    \"label_converter = lambda s : {'Iris-setosa':0, 'Iris-versicolor':1, 'Iris-virginica':2}[s]\\n\",\n    \"labels = np.loadtxt(StringIO(raw_data), dtype=np.int, delimiter=',', usecols=(4,), converters={4: label_converter})\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Before we do training, one thing that is often beneficial is to separate the dataset into training and testing. In this case, let's randomly shuffle the data, use the first 100 data points to do training, and the remaining 50 to do testing. For more sophisticated approaches, you can use e.g. cross validation to separate your dataset into multiple training and testing splits. Read more about cross validation [here](http://scikit-learn.org/stable/modules/cross_validation.html).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"random_index = np.random.permutation(150)\\n\",\n    \"features = features[random_index]\\n\",\n    \"labels = labels[random_index]\\n\",\n    \"\\n\",\n    \"train_features = features[:100]\\n\",\n    \"train_labels = labels[:100]\\n\",\n    \"test_features = features[100:]\\n\",\n    \"test_labels = labels[100:]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x11268b290>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<matplotlib.figure.Figure at 0x112858ad0>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Let's plot the first two features together with the label.\\n\",\n    \"# Remember, while we are plotting the testing feature distribution\\n\",\n    \"# here too, you might not be supposed to do so in real research,\\n\",\n    \"# because one should not peek into the testing data.\\n\",\n    \"legend = ['rx', 'b+', 'go']\\n\",\n    \"pyplot.title(\\\"Training data distribution, feature 0 and 1\\\")\\n\",\n    \"for i in range(3):\\n\",\n    \"    pyplot.plot(train_features[train_labels==i, 0], train_features[train_labels==i, 1], legend[i])\\n\",\n    \"pyplot.figure()\\n\",\n    \"pyplot.title(\\\"Testing data distribution, feature 0 and 1\\\")\\n\",\n    \"for i in range(3):\\n\",\n    \"    pyplot.plot(test_features[test_labels==i, 0], test_features[test_labels==i, 1], legend[i])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, as promised, let's put things into a Caffe2 DB. In this DB, what would happen is that we will use \\\"train_xxx\\\" as the key, and use a TensorProtos object to store two tensors for each data point: one as the feature and one as the label. We will use Caffe2's Python DB interface to do so.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"This is what the tensor proto looks like for a feature and its label:\\n\",\n      \"protos {\\n\",\n      \"  dims: 4\\n\",\n      \"  data_type: FLOAT\\n\",\n      \"  float_data: 6.69999980927\\n\",\n      \"  float_data: 3.0\\n\",\n      \"  float_data: 5.19999980927\\n\",\n      \"  float_data: 2.29999995232\\n\",\n      \"}\\n\",\n      \"protos {\\n\",\n      \"  data_type: INT32\\n\",\n      \"  int32_data: 2\\n\",\n      \"}\\n\",\n      \"\\n\",\n      \"This is the compact string that gets written into the db:\\n\",\n      \"\\n\",\n      \"\\u0016\\b\\u0004\\u0010\\u0001\\u001a\\u0010ff�@\\u0000\\u0000@@ff�@33\\u0013@\\n\",\n      \"\\u0005\\u0010\\u0002\\\"\\u0001\\u0002\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# First, let's see how one can construct a TensorProtos protocol buffer from numpy arrays.\\n\",\n    \"feature_and_label = caffe2_pb2.TensorProtos()\\n\",\n    \"feature_and_label.protos.extend([\\n\",\n    \"    utils.NumpyArrayToCaffe2Tensor(features[0]),\\n\",\n    \"    utils.NumpyArrayToCaffe2Tensor(labels[0])])\\n\",\n    \"print('This is what the tensor proto looks like for a feature and its label:')\\n\",\n    \"print(str(feature_and_label))\\n\",\n    \"print('This is the compact string that gets written into the db:')\\n\",\n    \"print(feature_and_label.SerializeToString())\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Now, actually write the db.\\n\",\n    \"\\n\",\n    \"def write_db(db_type, db_name, features, labels):\\n\",\n    \"    db = core.C.create_db(db_type, db_name, core.C.Mode.write)\\n\",\n    \"    transaction = db.new_transaction()\\n\",\n    \"    for i in range(features.shape[0]):\\n\",\n    \"        feature_and_label = caffe2_pb2.TensorProtos()\\n\",\n    \"        feature_and_label.protos.extend([\\n\",\n    \"            utils.NumpyArrayToCaffe2Tensor(features[i]),\\n\",\n    \"            utils.NumpyArrayToCaffe2Tensor(labels[i])])\\n\",\n    \"        transaction.put(\\n\",\n    \"            'train_%03d'.format(i),\\n\",\n    \"            feature_and_label.SerializeToString())\\n\",\n    \"    # Close the transaction, and then close the db.\\n\",\n    \"    del transaction\\n\",\n    \"    del db\\n\",\n    \"\\n\",\n    \"write_db(\\\"minidb\\\", \\\"iris_train.minidb\\\", train_features, train_labels)\\n\",\n    \"write_db(\\\"minidb\\\", \\\"iris_test.minidb\\\", test_features, test_labels)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, let's create a very simple network that only consists of one single TensorProtosDBInput operator, to showcase how we load data from the DB that we created. For training, you might want to do something more complex: creating a network, train it, get the model, and run the prediction service. To this end you can look at the MNIST tutorial for details.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"The net looks like this:\\n\",\n      \"name: \\\"example_reader\\\"\\n\",\n      \"op {\\n\",\n      \"  output: \\\"dbreader\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"CreateDB\\\"\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"db_type\\\"\\n\",\n      \"    s: \\\"minidb\\\"\\n\",\n      \"  }\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"db\\\"\\n\",\n      \"    s: \\\"iris_train.minidb\\\"\\n\",\n      \"  }\\n\",\n      \"}\\n\",\n      \"op {\\n\",\n      \"  input: \\\"dbreader\\\"\\n\",\n      \"  output: \\\"X\\\"\\n\",\n      \"  output: \\\"Y\\\"\\n\",\n      \"  name: \\\"\\\"\\n\",\n      \"  type: \\\"TensorProtosDBInput\\\"\\n\",\n      \"  arg {\\n\",\n      \"    name: \\\"batch_size\\\"\\n\",\n      \"    i: 16\\n\",\n      \"  }\\n\",\n      \"}\\n\",\n      \"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"net_proto = core.Net(\\\"example_reader\\\")\\n\",\n    \"dbreader = net_proto.CreateDB([], \\\"dbreader\\\", db=\\\"iris_train.minidb\\\", db_type=\\\"minidb\\\")\\n\",\n    \"net_proto.TensorProtosDBInput([dbreader], [\\\"X\\\", \\\"Y\\\"], batch_size=16)\\n\",\n    \"\\n\",\n    \"print(\\\"The net looks like this:\\\")\\n\",\n    \"print(str(net_proto.Proto()))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"True\"\n      ]\n     },\n     \"execution_count\": 9,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"workspace.CreateNet(net_proto)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"The first batch of feature is:\\n\",\n      \"[[5.5 2.5 4.  1.3]\\n\",\n      \" [5.2 4.1 1.5 0.1]\\n\",\n      \" [7.3 2.9 6.3 1.8]\\n\",\n      \" [6.5 3.2 5.1 2. ]\\n\",\n      \" [4.9 2.4 3.3 1. ]\\n\",\n      \" [5.7 2.6 3.5 1. ]\\n\",\n      \" [5.6 2.5 3.9 1.1]\\n\",\n      \" [5.2 3.5 1.5 0.2]\\n\",\n      \" [4.8 3.  1.4 0.3]\\n\",\n      \" [5.8 2.7 5.1 1.9]\\n\",\n      \" [7.2 3.2 6.  1.8]\\n\",\n      \" [6.8 2.8 4.8 1.4]\\n\",\n      \" [6.6 3.  4.4 1.4]\\n\",\n      \" [7.6 3.  6.6 2.1]\\n\",\n      \" [5.9 3.2 4.8 1.8]\\n\",\n      \" [5.5 3.5 1.3 0.2]]\\n\",\n      \"The first batch of label is:\\n\",\n      \"[1 0 2 2 1 1 1 0 0 2 2 1 1 2 1 0]\\n\",\n      \"The second batch of feature is:\\n\",\n      \"[[6.  3.4 4.5 1.6]\\n\",\n      \" [6.7 3.3 5.7 2.1]\\n\",\n      \" [5.8 2.7 4.1 1. ]\\n\",\n      \" [5.  3.5 1.3 0.3]\\n\",\n      \" [5.8 4.  1.2 0.2]\\n\",\n      \" [5.  3.4 1.5 0.2]\\n\",\n      \" [5.9 3.  4.2 1.5]\\n\",\n      \" [4.8 3.4 1.6 0.2]\\n\",\n      \" [6.7 3.  5.2 2.3]\\n\",\n      \" [5.5 2.3 4.  1.3]\\n\",\n      \" [4.8 3.1 1.6 0.2]\\n\",\n      \" [6.3 2.8 5.1 1.5]\\n\",\n      \" [5.  3.5 1.6 0.6]\\n\",\n      \" [5.8 2.7 3.9 1.2]\\n\",\n      \" [7.2 3.6 6.1 2.5]\\n\",\n      \" [4.3 3.  1.1 0.1]]\\n\",\n      \"The second batch of label is:\\n\",\n      \"[1 2 1 0 0 0 1 0 2 1 0 2 0 1 2 0]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Let's run it to get batches of features.\\n\",\n    \"workspace.RunNet(net_proto.Proto().name)\\n\",\n    \"print(\\\"The first batch of feature is:\\\")\\n\",\n    \"print(workspace.FetchBlob(\\\"X\\\"))\\n\",\n    \"print(\\\"The first batch of label is:\\\")\\n\",\n    \"print(workspace.FetchBlob(\\\"Y\\\"))\\n\",\n    \"\\n\",\n    \"# Let's run again.\\n\",\n    \"workspace.RunNet(net_proto.Proto().name)\\n\",\n    \"print(\\\"The second batch of feature is:\\\")\\n\",\n    \"print(workspace.FetchBlob(\\\"X\\\"))\\n\",\n    \"print(\\\"The second batch of label is:\\\")\\n\",\n    \"print(workspace.FetchBlob(\\\"Y\\\"))\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.14\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 1\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/experimental/Immediate.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Tutorial 4. Immediate mode\\n\",\n    \"\\n\",\n    \"In this tutorial we will talk about a cute feature about Caffe2: immediate mode.\\n\",\n    \"\\n\",\n    \"From the previous tutorials you have seen that Caffe2 *declares* a network, and during this declaration phase, nothing gets actually executed - it's like writing the source of a program, and \\\"compilation/execution\\\" only happens later.\\n\",\n    \"\\n\",\n    \"This sometimes gets a bit tricky if we are in a researchy mind, and want to inspect typical intermediate outputs as we go. This is when the immediate mode come to help. At a high level, what the immediate mode does is to run the corresponding operators as you write them. The results live under a special workspace that can then be accessed via `FetchImmediate()` and `FeedImmediate()` runs.\\n\",\n    \"\\n\",\n    \"Let's show some examples.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"%matplotlib inline\\n\",\n    \"from caffe2.python import cnn, core, visualize, workspace, model_helper, brew\\n\",\n    \"import numpy as np\\n\",\n    \"import os\\n\",\n    \"core.GlobalInit(['caffe2', '--caffe2_log_level=-1'])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"source\": [\n    \"Now, as we have known before, in the normal mode, when you create an operator, we are *declaring* it only, and nothing gets actually executed. Let's re-confirm that.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"workspace.ResetWorkspace()\\n\",\n    \"# declaration\\n\",\n    \"op = core.CreateOperator(\\\"GaussianFill\\\", [], \\\"X\\\", shape=[3, 5])\\n\",\n    \"print('Before execution, workspace contains X: {}'\\n\",\n    \"      .format(workspace.HasBlob(\\\"X\\\")))\\n\",\n    \"# execution\\n\",\n    \"workspace.RunOperatorOnce(op)\\n\",\n    \"print('After execution, workspace contains X: {}'\\n\",\n    \"      .format(workspace.HasBlob(\\\"X\\\")))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"source\": [\n    \"## Entering and exiting immediate mode.\\n\",\n    \"\\n\",\n    \"Entering immediate mode is easy: you basically invoke `workspace.StartImmediate()`. Since immediate mode has quite a lot of side effects, it would be good to read through the warning message to make sure you understand the implications.\\n\",\n    \"\\n\",\n    \"(If you don't want to see the messages, pass `i_know=True` to `StartImmediate` to suppress that.)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"workspace.StartImmediate()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now that you have enabled immediate mode, any operators you run will simultaneously be executed in a separate immediate workspace. Note - the main workspace that you are working on is not affected. We designed the immediate workspace to be separate from the main workspace, so that nothing in the main workspace gets polluted.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# declaration, and since we are in immediate mode, run it in the immediate workspace.\\n\",\n    \"op = core.CreateOperator(\\\"GaussianFill\\\", [], \\\"X\\\", shape=[3, 5])\\n\",\n    \"print('Before execution, does workspace contain X? {}'\\n\",\n    \"      .format(workspace.HasBlob(\\\"X\\\")))\\n\",\n    \"print('But we can access it using the Immediate related functions.'\\n\",\n    \"      'Here is a list of immediate blobs:')\\n\",\n    \"print(workspace.ImmediateBlobs())\\n\",\n    \"print('The content is like this:')\\n\",\n    \"print(workspace.FetchImmediate('X'))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# After the immediate execution, you can invoke StopImmediate() to clean up.\\n\",\n    \"workspace.StopImmediate()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Manually feeding blobs\\n\",\n    \"\\n\",\n    \"But wait, you say - what if I want to create an operator that uses an input that is \\\"declared\\\" but not present yet? Since the immediate workspace does not have the input, we will encounter an exception:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"workspace.StartImmediate(i_know=True)\\n\",\n    \"op = core.CreateOperator(\\\"Relu\\\", \\\"X\\\", \\\"Y\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"source\": [\n    \"This is because immediate mode, being completely imperative, requires any input to be used to already exist in the immediate workspace. To make the immediate mode aware of such external inputs, we can manually feed blobs to the immediate workspace.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"X = np.random.randn(2, 3).astype(np.float32)\\n\",\n    \"workspace.FeedImmediate(\\\"X\\\", X)\\n\",\n    \"# Now, we can safely run CreateOperator since immediate mode knows what X looks like\\n\",\n    \"op = core.CreateOperator(\\\"Relu\\\", \\\"X\\\", \\\"Y\\\")\\n\",\n    \"print(\\\"Example input is:\\\\n{}\\\".format(workspace.FetchImmediate(\\\"X\\\")))\\n\",\n    \"print(\\\"Example output is:\\\\n{}\\\".format(workspace.FetchImmediate(\\\"Y\\\")))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"workspace.StopImmediate()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## When is immediate mode useful?\\n\",\n    \"\\n\",\n    \"You might want to use immediate mode when you are not very sure about the shape of the intermediate results, such as in a CNN where there are multiple convolution and pooling layers. Let's say that you are creating an MNIST convnet model but don't want to calculate the number of dimensions for the final FC layer. Here is what you might want to do.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"model = model_helper.ModelHelper(name=\\\"mnist\\\")\\n\",\n    \"# Start the immediate mode.\\n\",\n    \"workspace.StartImmediate(i_know=True)\\n\",\n    \"\\n\",\n    \"data_folder = os.path.join(os.path.expanduser('~'), 'caffe2_notebooks', 'tutorial_data')\\n\",\n    \"data_uint8, label = model.TensorProtosDBInput(\\n\",\n    \"    [], [\\\"data_uint8\\\", \\\"label\\\"], batch_size=64,\\n\",\n    \"    db=os.path.join(data_folder, 'mnist/mnist-train-nchw-leveldb'),\\n\",\n    \"    db_type='leveldb')\\n\",\n    \"data = model.net.Cast(data_uint8, \\\"data\\\", to=core.DataType.FLOAT)\\n\",\n    \"data = model.net.Scale(data, data, scale=float(1./256))\\n\",\n    \"data = model.net.StopGradient(data, data)\\n\",\n    \"conv1 = brew.conv(model, data, 'conv1', 1, 20, 5)\\n\",\n    \"pool1 = brew.max_pool(model, conv1, 'pool1', kernel=2, stride=2)\\n\",\n    \"conv2 = brew.conv(model, pool1, 'conv2', 20, 50, 5)\\n\",\n    \"pool2 = brew.max_pool(model, conv2, 'pool2', kernel=2, stride=2)\\n\",\n    \"\\n\",\n    \"# What is the shape of pool2 again...?\\n\",\n    \"feature_dimensions = workspace.FetchImmediate(\\\"pool2\\\").shape[1:]\\n\",\n    \"print(\\\"Feature dimensions before FC layer: {}\\\".format(feature_dimensions))\\n\",\n    \"\\n\",\n    \"fc3 = brew.fc(model, pool2, 'fc3', int(np.prod(feature_dimensions)), 500)\\n\",\n    \"fc3 = brew.relu(model, fc3, fc3)\\n\",\n    \"pred = brew.fc(model, fc3, 'pred', 500, 10)\\n\",\n    \"softmax = brew.softmax(model, pred, 'softmax')\\n\",\n    \"\\n\",\n    \"# Let's see if the dimensions are all correct:\\n\",\n    \"for blob in [\\\"data\\\", \\\"conv1\\\", \\\"pool1\\\", \\\"conv2\\\", \\\"pool2\\\", \\\"fc3\\\", \\\"pred\\\"]:\\n\",\n    \"    print(\\\"Blob {} has shape: {}\\\".format(\\n\",\n    \"          blob, workspace.FetchImmediate(blob).shape))\\n\",\n    \"# Let's also visualize a sample input.\\n\",\n    \"print(\\\"Sample input:\\\")\\n\",\n    \"visualize.NCHW.ShowMultiple(workspace.FetchImmediate(\\\"data\\\"))\\n\",\n    \"workspace.StopImmediate()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Remember, immediate mode is only intended to be used in debugging mode, and are only intended for you to verify things interactively. For example, in the use case above, what you want to do eventually is to remove the feature_dimensions argument and replace it with code that do not depend on immediate mode, such as hard-coding it.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Departing words\\n\",\n    \"\\n\",\n    \"Immediate mode could be a useful tool for quick iterations. But it could also easily go wrong. Make sure that you understand its purpose, and never abuse it in real product environments. The philosophy of Caffe2 is to make things very flexible and this is one example of it, but it also makes you easy to shoot yourself in the foot. Take care :)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.6\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/helpers.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package helpers\n# Module caffe2.python.tutorials.helpers\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport numpy as np\nimport skimage.io\nimport skimage.transform\n\n\ndef crop_center(img, cropx, cropy):\n    y, x, c = img.shape\n    startx = x // 2 - (cropx // 2)\n    starty = y // 2 - (cropy // 2)\n    return img[starty:starty + cropy, startx:startx + cropx]\n\n\ndef rescale(img, input_height, input_width):\n    # print(\"Original image shape:\" + str(img.shape) + \" --> it should be in H, W, C!\")\n    # print(\"Model's input shape is %dx%d\") % (input_height, input_width)\n    aspect = img.shape[1] / float(img.shape[0])\n    # print(\"Orginal aspect ratio: \" + str(aspect))\n    if(aspect > 1):\n        # landscape orientation - wide image\n        res = int(aspect * input_height)\n        imgScaled = skimage.transform.resize(\n            img,\n            (input_width, res),\n            preserve_range=False)\n    if(aspect < 1):\n        # portrait orientation - tall image\n        res = int(input_width / aspect)\n        imgScaled = skimage.transform.resize(\n            img,\n            (res, input_height),\n            preserve_range=False)\n    if(aspect == 1):\n        imgScaled = skimage.transform.resize(\n            img,\n            (input_width, input_height),\n            preserve_range=False)\n    return imgScaled\n\n\ndef load(img):\n    # load and transform image\n    img = skimage.img_as_float(skimage.io.imread(img)).astype(np.float32)\n    return img\n\n\ndef chw(img):\n    # switch to CHW\n    img = img.swapaxes(1, 2).swapaxes(0, 1)\n    return img\n\n\ndef bgr(img):\n    # switch to BGR\n    img = img[(2, 1, 0), :, :]\n    return img\n\n\ndef removeMean(img, mean):\n    # remove mean for better results\n    img = img * 255 - mean\n    return img\n\n\ndef batch(img):\n    # add batch size\n    img = img[np.newaxis, :, :, :].astype(np.float32)\n    return img\n\n\ndef parseResults(results):\n    results = np.asarray(results)\n    results = np.delete(results, 1)\n    index = 0\n    highest = 0\n    arr = np.empty((0, 2), dtype=object)\n    arr[:, 0] = int(10)\n    arr[:, 1:] = float(10)\n    for i, r in enumerate(results):\n        # imagenet index begins with 1!\n        i = i + 1\n        arr = np.append(arr, np.array([[i, r]]), axis=0)\n        if (r > highest):\n            highest = r\n            index = i\n\n    # top 3 results\n    print(\"Raw top 3 results:\", sorted(arr, key=lambda x: x[1], reverse=True)[:3])\n\n    # now we can grab the code list\n    with open('inference_codes.txt', 'r') as f:\n        for line in f:\n            code, result = line.partition(\":\")[::2]\n            if (code.strip() == str(index)):\n                answer = \"The image contains a %s with a %s percent probability.\" \\\n                    % (result.strip()[1:-2], highest * 100)\n    f.closed\n    return answer\n\n\ndef loadToNCHW(img, mean, input_size):\n    img = load(img)\n    img = rescale(img, input_size, input_size)\n    img = crop_center(img, input_size, input_size)\n    img = chw(img)\n    img = bgr(img)\n    img = removeMean(img, mean)\n    img = batch(img)\n    return img\n"
  },
  {
    "path": "caffe2/python/tutorials/inference_codes.txt",
    "content": "{\n 0: 'tench, Tinca tinca',\n 1: 'goldfish, Carassius auratus',\n 2: 'great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias',\n 3: 'tiger shark, Galeocerdo cuvieri',\n 4: 'hammerhead, hammerhead shark',\n 5: 'electric ray, crampfish, numbfish, torpedo',\n 6: 'stingray',\n 7: 'cock',\n 8: 'hen',\n 9: 'ostrich, Struthio camelus',\n 10: 'brambling, Fringilla montifringilla',\n 11: 'goldfinch, Carduelis carduelis',\n 12: 'house finch, linnet, Carpodacus mexicanus',\n 13: 'junco, snowbird',\n 14: 'indigo bunting, indigo finch, indigo bird, Passerina cyanea',\n 15: 'robin, American robin, Turdus migratorius',\n 16: 'bulbul',\n 17: 'jay',\n 18: 'magpie',\n 19: 'chickadee',\n 20: 'water ouzel, dipper',\n 21: 'kite',\n 22: 'bald eagle, American eagle, Haliaeetus leucocephalus',\n 23: 'vulture',\n 24: 'great grey owl, great gray owl, Strix nebulosa',\n 25: 'European fire salamander, Salamandra salamandra',\n 26: 'common newt, Triturus vulgaris',\n 27: 'eft',\n 28: 'spotted salamander, Ambystoma maculatum',\n 29: 'axolotl, mud puppy, Ambystoma mexicanum',\n 30: 'bullfrog, Rana catesbeiana',\n 31: 'tree frog, tree-frog',\n 32: 'tailed frog, bell toad, ribbed toad, tailed toad, Ascaphus trui',\n 33: 'loggerhead, loggerhead turtle, Caretta caretta',\n 34: 'leatherback turtle, leatherback, leathery turtle, Dermochelys coriacea',\n 35: 'mud turtle',\n 36: 'terrapin',\n 37: 'box turtle, box tortoise',\n 38: 'banded gecko',\n 39: 'common iguana, iguana, Iguana iguana',\n 40: 'American chameleon, anole, Anolis carolinensis',\n 41: 'whiptail, whiptail lizard',\n 42: 'agama',\n 43: 'frilled lizard, Chlamydosaurus kingi',\n 44: 'alligator lizard',\n 45: 'Gila monster, Heloderma suspectum',\n 46: 'green lizard, Lacerta viridis',\n 47: 'African chameleon, Chamaeleo chamaeleon',\n 48: 'Komodo dragon, Komodo lizard, dragon lizard, giant lizard, Varanus komodoensis',\n 49: 'African crocodile, Nile crocodile, Crocodylus niloticus',\n 50: 'American alligator, Alligator mississipiensis',\n 51: 'triceratops',\n 52: 'thunder snake, worm snake, Carphophis amoenus',\n 53: 'ringneck snake, ring-necked snake, ring snake',\n 54: 'hognose snake, puff adder, sand viper',\n 55: 'green snake, grass snake',\n 56: 'king snake, kingsnake',\n 57: 'garter snake, grass snake',\n 58: 'water snake',\n 59: 'vine snake',\n 60: 'night snake, Hypsiglena torquata',\n 61: 'boa constrictor, Constrictor constrictor',\n 62: 'rock python, rock snake, Python sebae',\n 63: 'Indian cobra, Naja naja',\n 64: 'green mamba',\n 65: 'sea snake',\n 66: 'horned viper, cerastes, sand viper, horned asp, Cerastes cornutus',\n 67: 'diamondback, diamondback rattlesnake, Crotalus adamanteus',\n 68: 'sidewinder, horned rattlesnake, Crotalus cerastes',\n 69: 'trilobite',\n 70: 'harvestman, daddy longlegs, Phalangium opilio',\n 71: 'scorpion',\n 72: 'black and gold garden spider, Argiope aurantia',\n 73: 'barn spider, Araneus cavaticus',\n 74: 'garden spider, Aranea diademata',\n 75: 'black widow, Latrodectus mactans',\n 76: 'tarantula',\n 77: 'wolf spider, hunting spider',\n 78: 'tick',\n 79: 'centipede',\n 80: 'black grouse',\n 81: 'ptarmigan',\n 82: 'ruffed grouse, partridge, Bonasa umbellus',\n 83: 'prairie chicken, prairie grouse, prairie fowl',\n 84: 'peacock',\n 85: 'quail',\n 86: 'partridge',\n 87: 'African grey, African gray, Psittacus erithacus',\n 88: 'macaw',\n 89: 'sulphur-crested cockatoo, Kakatoe galerita, Cacatua galerita',\n 90: 'lorikeet',\n 91: 'coucal',\n 92: 'bee eater',\n 93: 'hornbill',\n 94: 'hummingbird',\n 95: 'jacamar',\n 96: 'toucan',\n 97: 'drake',\n 98: 'red-breasted merganser, Mergus serrator',\n 99: 'goose',\n 100: 'black swan, Cygnus atratus',\n 101: 'tusker',\n 102: 'echidna, spiny anteater, anteater',\n 103: 'platypus, duckbill, duckbilled platypus, duck-billed platypus, Ornithorhynchus anatinus',\n 104: 'wallaby, brush kangaroo',\n 105: 'koala, koala bear, kangaroo bear, native bear, Phascolarctos cinereus',\n 106: 'wombat',\n 107: 'jellyfish',\n 108: 'sea anemone, anemone',\n 109: 'brain coral',\n 110: 'flatworm, platyhelminth',\n 111: 'nematode, nematode worm, roundworm',\n 112: 'conch',\n 113: 'snail',\n 114: 'slug',\n 115: 'sea slug, nudibranch',\n 116: 'chiton, coat-of-mail shell, sea cradle, polyplacophore',\n 117: 'chambered nautilus, pearly nautilus, nautilus',\n 118: 'Dungeness crab, Cancer magister',\n 119: 'rock crab, Cancer irroratus',\n 120: 'fiddler crab',\n 121: 'king crab, Alaska crab, Alaskan king crab, Alaska king crab, Paralithodes camtschatica',\n 122: 'American lobster, Northern lobster, Maine lobster, Homarus americanus',\n 123: 'spiny lobster, langouste, rock lobster, crawfish, crayfish, sea crawfish',\n 124: 'crayfish, crawfish, crawdad, crawdaddy',\n 125: 'hermit crab',\n 126: 'isopod',\n 127: 'white stork, Ciconia ciconia',\n 128: 'black stork, Ciconia nigra',\n 129: 'spoonbill',\n 130: 'flamingo',\n 131: 'little blue heron, Egretta caerulea',\n 132: 'American egret, great white heron, Egretta albus',\n 133: 'bittern',\n 134: 'crane',\n 135: 'limpkin, Aramus pictus',\n 136: 'European gallinule, Porphyrio porphyrio',\n 137: 'American coot, marsh hen, mud hen, water hen, Fulica americana',\n 138: 'bustard',\n 139: 'ruddy turnstone, Arenaria interpres',\n 140: 'red-backed sandpiper, dunlin, Erolia alpina',\n 141: 'redshank, Tringa totanus',\n 142: 'dowitcher',\n 143: 'oystercatcher, oyster catcher',\n 144: 'pelican',\n 145: 'king penguin, Aptenodytes patagonica',\n 146: 'albatross, mollymawk',\n 147: 'grey whale, gray whale, devilfish, Eschrichtius gibbosus, Eschrichtius robustus',\n 148: 'killer whale, killer, orca, grampus, sea wolf, Orcinus orca',\n 149: 'dugong, Dugong dugon',\n 150: 'sea lion',\n 151: 'Chihuahua',\n 152: 'Japanese spaniel',\n 153: 'Maltese dog, Maltese terrier, Maltese',\n 154: 'Pekinese, Pekingese, Peke',\n 155: 'Shih-Tzu',\n 156: 'Blenheim spaniel',\n 157: 'papillon',\n 158: 'toy terrier',\n 159: 'Rhodesian ridgeback',\n 160: 'Afghan hound, Afghan',\n 161: 'basset, basset hound',\n 162: 'beagle',\n 163: 'bloodhound, sleuthhound',\n 164: 'bluetick',\n 165: 'black-and-tan coonhound',\n 166: 'Walker hound, Walker foxhound',\n 167: 'English foxhound',\n 168: 'redbone',\n 169: 'borzoi, Russian wolfhound',\n 170: 'Irish wolfhound',\n 171: 'Italian greyhound',\n 172: 'whippet',\n 173: 'Ibizan hound, Ibizan Podenco',\n 174: 'Norwegian elkhound, elkhound',\n 175: 'otterhound, otter hound',\n 176: 'Saluki, gazelle hound',\n 177: 'Scottish deerhound, deerhound',\n 178: 'Weimaraner',\n 179: 'Staffordshire bullterrier, Staffordshire bull terrier',\n 180: 'American Staffordshire terrier, Staffordshire terrier, American pit bull terrier, pit bull terrier',\n 181: 'Bedlington terrier',\n 182: 'Border terrier',\n 183: 'Kerry blue terrier',\n 184: 'Irish terrier',\n 185: 'Norfolk terrier',\n 186: 'Norwich terrier',\n 187: 'Yorkshire terrier',\n 188: 'wire-haired fox terrier',\n 189: 'Lakeland terrier',\n 190: 'Sealyham terrier, Sealyham',\n 191: 'Airedale, Airedale terrier',\n 192: 'cairn, cairn terrier',\n 193: 'Australian terrier',\n 194: 'Dandie Dinmont, Dandie Dinmont terrier',\n 195: 'Boston bull, Boston terrier',\n 196: 'miniature schnauzer',\n 197: 'giant schnauzer',\n 198: 'standard schnauzer',\n 199: 'Scotch terrier, Scottish terrier, Scottie',\n 200: 'Tibetan terrier, chrysanthemum dog',\n 201: 'silky terrier, Sydney silky',\n 202: 'soft-coated wheaten terrier',\n 203: 'West Highland white terrier',\n 204: 'Lhasa, Lhasa apso',\n 205: 'flat-coated retriever',\n 206: 'curly-coated retriever',\n 207: 'golden retriever',\n 208: 'Labrador retriever',\n 209: 'Chesapeake Bay retriever',\n 210: 'German short-haired pointer',\n 211: 'vizsla, Hungarian pointer',\n 212: 'English setter',\n 213: 'Irish setter, red setter',\n 214: 'Gordon setter',\n 215: 'Brittany spaniel',\n 216: 'clumber, clumber spaniel',\n 217: 'English springer, English springer spaniel',\n 218: 'Welsh springer spaniel',\n 219: 'cocker spaniel, English cocker spaniel, cocker',\n 220: 'Sussex spaniel',\n 221: 'Irish water spaniel',\n 222: 'kuvasz',\n 223: 'schipperke',\n 224: 'groenendael',\n 225: 'malinois',\n 226: 'briard',\n 227: 'kelpie',\n 228: 'komondor',\n 229: 'Old English sheepdog, bobtail',\n 230: 'Shetland sheepdog, Shetland sheep dog, Shetland',\n 231: 'collie',\n 232: 'Border collie',\n 233: 'Bouvier des Flandres, Bouviers des Flandres',\n 234: 'Rottweiler',\n 235: 'German shepherd, German shepherd dog, German police dog, alsatian',\n 236: 'Doberman, Doberman pinscher',\n 237: 'miniature pinscher',\n 238: 'Greater Swiss Mountain dog',\n 239: 'Bernese mountain dog',\n 240: 'Appenzeller',\n 241: 'EntleBucher',\n 242: 'boxer',\n 243: 'bull mastiff',\n 244: 'Tibetan mastiff',\n 245: 'French bulldog',\n 246: 'Great Dane',\n 247: 'Saint Bernard, St Bernard',\n 248: 'Eskimo dog, husky',\n 249: 'malamute, malemute, Alaskan malamute',\n 250: 'Siberian husky',\n 251: 'dalmatian, coach dog, carriage dog',\n 252: 'affenpinscher, monkey pinscher, monkey dog',\n 253: 'basenji',\n 254: 'pug, pug-dog',\n 255: 'Leonberg',\n 256: 'Newfoundland, Newfoundland dog',\n 257: 'Great Pyrenees',\n 258: 'Samoyed, Samoyede',\n 259: 'Pomeranian',\n 260: 'chow, chow chow',\n 261: 'keeshond',\n 262: 'Brabancon griffon',\n 263: 'Pembroke, Pembroke Welsh corgi',\n 264: 'Cardigan, Cardigan Welsh corgi',\n 265: 'toy poodle',\n 266: 'miniature poodle',\n 267: 'standard poodle',\n 268: 'Mexican hairless',\n 269: 'timber wolf, grey wolf, gray wolf, Canis lupus',\n 270: 'white wolf, Arctic wolf, Canis lupus tundrarum',\n 271: 'red wolf, maned wolf, Canis rufus, Canis niger',\n 272: 'coyote, prairie wolf, brush wolf, Canis latrans',\n 273: 'dingo, warrigal, warragal, Canis dingo',\n 274: 'dhole, Cuon alpinus',\n 275: 'African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus',\n 276: 'hyena, hyaena',\n 277: 'red fox, Vulpes vulpes',\n 278: 'kit fox, Vulpes macrotis',\n 279: 'Arctic fox, white fox, Alopex lagopus',\n 280: 'grey fox, gray fox, Urocyon cinereoargenteus',\n 281: 'tabby, tabby cat',\n 282: 'tiger cat',\n 283: 'Persian cat',\n 284: 'Siamese cat, Siamese',\n 285: 'Egyptian cat',\n 286: 'cougar, puma, catamount, mountain lion, painter, panther, Felis concolor',\n 287: 'lynx, catamount',\n 288: 'leopard, Panthera pardus',\n 289: 'snow leopard, ounce, Panthera uncia',\n 290: 'jaguar, panther, Panthera onca, Felis onca',\n 291: 'lion, king of beasts, Panthera leo',\n 292: 'tiger, Panthera tigris',\n 293: 'cheetah, chetah, Acinonyx jubatus',\n 294: 'brown bear, bruin, Ursus arctos',\n 295: 'American black bear, black bear, Ursus americanus, Euarctos americanus',\n 296: 'ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus',\n 297: 'sloth bear, Melursus ursinus, Ursus ursinus',\n 298: 'mongoose',\n 299: 'meerkat, mierkat',\n 300: 'tiger beetle',\n 301: 'ladybug, ladybeetle, lady beetle, ladybird, ladybird beetle',\n 302: 'ground beetle, carabid beetle',\n 303: 'long-horned beetle, longicorn, longicorn beetle',\n 304: 'leaf beetle, chrysomelid',\n 305: 'dung beetle',\n 306: 'rhinoceros beetle',\n 307: 'weevil',\n 308: 'fly',\n 309: 'bee',\n 310: 'ant, emmet, pismire',\n 311: 'grasshopper, hopper',\n 312: 'cricket',\n 313: 'walking stick, walkingstick, stick insect',\n 314: 'cockroach, roach',\n 315: 'mantis, mantid',\n 316: 'cicada, cicala',\n 317: 'leafhopper',\n 318: 'lacewing, lacewing fly',\n 319: \"dragonfly, darning needle, devil's darning needle, sewing needle, snake feeder, snake doctor, mosquito hawk, skeeter hawk\",\n 320: 'damselfly',\n 321: 'admiral',\n 322: 'ringlet, ringlet butterfly',\n 323: 'monarch, monarch butterfly, milkweed butterfly, Danaus plexippus',\n 324: 'cabbage butterfly',\n 325: 'sulphur butterfly, sulfur butterfly',\n 326: 'lycaenid, lycaenid butterfly',\n 327: 'starfish, sea star',\n 328: 'sea urchin',\n 329: 'sea cucumber, holothurian',\n 330: 'wood rabbit, cottontail, cottontail rabbit',\n 331: 'hare',\n 332: 'Angora, Angora rabbit',\n 333: 'hamster',\n 334: 'porcupine, hedgehog',\n 335: 'fox squirrel, eastern fox squirrel, Sciurus niger',\n 336: 'marmot',\n 337: 'beaver',\n 338: 'guinea pig, Cavia cobaya',\n 339: 'sorrel',\n 340: 'zebra',\n 341: 'hog, pig, grunter, squealer, Sus scrofa',\n 342: 'wild boar, boar, Sus scrofa',\n 343: 'warthog',\n 344: 'hippopotamus, hippo, river horse, Hippopotamus amphibius',\n 345: 'ox',\n 346: 'water buffalo, water ox, Asiatic buffalo, Bubalus bubalis',\n 347: 'bison',\n 348: 'ram, tup',\n 349: 'bighorn, bighorn sheep, cimarron, Rocky Mountain bighorn, Rocky Mountain sheep, Ovis canadensis',\n 350: 'ibex, Capra ibex',\n 351: 'hartebeest',\n 352: 'impala, Aepyceros melampus',\n 353: 'gazelle',\n 354: 'Arabian camel, dromedary, Camelus dromedarius',\n 355: 'llama',\n 356: 'weasel',\n 357: 'mink',\n 358: 'polecat, fitch, foulmart, foumart, Mustela putorius',\n 359: 'black-footed ferret, ferret, Mustela nigripes',\n 360: 'otter',\n 361: 'skunk, polecat, wood pussy',\n 362: 'badger',\n 363: 'armadillo',\n 364: 'three-toed sloth, ai, Bradypus tridactylus',\n 365: 'orangutan, orang, orangutang, Pongo pygmaeus',\n 366: 'gorilla, Gorilla gorilla',\n 367: 'chimpanzee, chimp, Pan troglodytes',\n 368: 'gibbon, Hylobates lar',\n 369: 'siamang, Hylobates syndactylus, Symphalangus syndactylus',\n 370: 'guenon, guenon monkey',\n 371: 'patas, hussar monkey, Erythrocebus patas',\n 372: 'baboon',\n 373: 'macaque',\n 374: 'langur',\n 375: 'colobus, colobus monkey',\n 376: 'proboscis monkey, Nasalis larvatus',\n 377: 'marmoset',\n 378: 'capuchin, ringtail, Cebus capucinus',\n 379: 'howler monkey, howler',\n 380: 'titi, titi monkey',\n 381: 'spider monkey, Ateles geoffroyi',\n 382: 'squirrel monkey, Saimiri sciureus',\n 383: 'Madagascar cat, ring-tailed lemur, Lemur catta',\n 384: 'indri, indris, Indri indri, Indri brevicaudatus',\n 385: 'Indian elephant, Elephas maximus',\n 386: 'African elephant, Loxodonta africana',\n 387: 'lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens',\n 388: 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca',\n 389: 'barracouta, snoek',\n 390: 'eel',\n 391: 'coho, cohoe, coho salmon, blue jack, silver salmon, Oncorhynchus kisutch',\n 392: 'rock beauty, Holocanthus tricolor',\n 393: 'anemone fish',\n 394: 'sturgeon',\n 395: 'gar, garfish, garpike, billfish, Lepisosteus osseus',\n 396: 'lionfish',\n 397: 'puffer, pufferfish, blowfish, globefish',\n 398: 'abacus',\n 399: 'abaya',\n 400: \"academic gown, academic robe, judge's robe\",\n 401: 'accordion, piano accordion, squeeze box',\n 402: 'acoustic guitar',\n 403: 'aircraft carrier, carrier, flattop, attack aircraft carrier',\n 404: 'airliner',\n 405: 'airship, dirigible',\n 406: 'altar',\n 407: 'ambulance',\n 408: 'amphibian, amphibious vehicle',\n 409: 'analog clock',\n 410: 'apiary, bee house',\n 411: 'apron',\n 412: 'ashcan, trash can, garbage can, wastebin, ash bin, ash-bin, ashbin, dustbin, trash barrel, trash bin',\n 413: 'assault rifle, assault gun',\n 414: 'backpack, back pack, knapsack, packsack, rucksack, haversack',\n 415: 'bakery, bakeshop, bakehouse',\n 416: 'balance beam, beam',\n 417: 'balloon',\n 418: 'ballpoint, ballpoint pen, ballpen, Biro',\n 419: 'Band Aid',\n 420: 'banjo',\n 421: 'bannister, banister, balustrade, balusters, handrail',\n 422: 'barbell',\n 423: 'barber chair',\n 424: 'barbershop',\n 425: 'barn',\n 426: 'barometer',\n 427: 'barrel, cask',\n 428: 'barrow, garden cart, lawn cart, wheelbarrow',\n 429: 'baseball',\n 430: 'basketball',\n 431: 'bassinet',\n 432: 'bassoon',\n 433: 'bathing cap, swimming cap',\n 434: 'bath towel',\n 435: 'bathtub, bathing tub, bath, tub',\n 436: 'beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon',\n 437: 'beacon, lighthouse, beacon light, pharos',\n 438: 'beaker',\n 439: 'bearskin, busby, shako',\n 440: 'beer bottle',\n 441: 'beer glass',\n 442: 'bell cote, bell cot',\n 443: 'bib',\n 444: 'bicycle-built-for-two, tandem bicycle, tandem',\n 445: 'bikini, two-piece',\n 446: 'binder, ring-binder',\n 447: 'binoculars, field glasses, opera glasses',\n 448: 'birdhouse',\n 449: 'boathouse',\n 450: 'bobsled, bobsleigh, bob',\n 451: 'bolo tie, bolo, bola tie, bola',\n 452: 'bonnet, poke bonnet',\n 453: 'bookcase',\n 454: 'bookshop, bookstore, bookstall',\n 455: 'bottlecap',\n 456: 'bow',\n 457: 'bow tie, bow-tie, bowtie',\n 458: 'brass, memorial tablet, plaque',\n 459: 'brassiere, bra, bandeau',\n 460: 'breakwater, groin, groyne, mole, bulwark, seawall, jetty',\n 461: 'breastplate, aegis, egis',\n 462: 'broom',\n 463: 'bucket, pail',\n 464: 'buckle',\n 465: 'bulletproof vest',\n 466: 'bullet train, bullet',\n 467: 'butcher shop, meat market',\n 468: 'cab, hack, taxi, taxicab',\n 469: 'caldron, cauldron',\n 470: 'candle, taper, wax light',\n 471: 'cannon',\n 472: 'canoe',\n 473: 'can opener, tin opener',\n 474: 'cardigan',\n 475: 'car mirror',\n 476: 'carousel, carrousel, merry-go-round, roundabout, whirligig',\n 477: \"carpenter's kit, tool kit\",\n 478: 'carton',\n 479: 'car wheel',\n 480: 'cash machine, cash dispenser, automated teller machine, automatic teller machine, automated teller, automatic teller, ATM',\n 481: 'cassette',\n 482: 'cassette player',\n 483: 'castle',\n 484: 'catamaran',\n 485: 'CD player',\n 486: 'cello, violoncello',\n 487: 'cellular telephone, cellular phone, cellphone, cell, mobile phone',\n 488: 'chain',\n 489: 'chainlink fence',\n 490: 'chain mail, ring mail, mail, chain armor, chain armour, ring armor, ring armour',\n 491: 'chain saw, chainsaw',\n 492: 'chest',\n 493: 'chiffonier, commode',\n 494: 'chime, bell, gong',\n 495: 'china cabinet, china closet',\n 496: 'Christmas stocking',\n 497: 'church, church building',\n 498: 'cinema, movie theater, movie theatre, movie house, picture palace',\n 499: 'cleaver, meat cleaver, chopper',\n 500: 'cliff dwelling',\n 501: 'cloak',\n 502: 'clog, geta, patten, sabot',\n 503: 'cocktail shaker',\n 504: 'coffee mug',\n 505: 'coffeepot',\n 506: 'coil, spiral, volute, whorl, helix',\n 507: 'combination lock',\n 508: 'computer keyboard, keypad',\n 509: 'confectionery, confectionary, candy store',\n 510: 'container ship, containership, container vessel',\n 511: 'convertible',\n 512: 'corkscrew, bottle screw',\n 513: 'cornet, horn, trumpet, trump',\n 514: 'cowboy boot',\n 515: 'cowboy hat, ten-gallon hat',\n 516: 'cradle',\n 517: 'crane',\n 518: 'crash helmet',\n 519: 'crate',\n 520: 'crib, cot',\n 521: 'Crock Pot',\n 522: 'croquet ball',\n 523: 'crutch',\n 524: 'cuirass',\n 525: 'dam, dike, dyke',\n 526: 'desk',\n 527: 'desktop computer',\n 528: 'dial telephone, dial phone',\n 529: 'diaper, nappy, napkin',\n 530: 'digital clock',\n 531: 'digital watch',\n 532: 'dining table, board',\n 533: 'dishrag, dishcloth',\n 534: 'dishwasher, dish washer, dishwashing machine',\n 535: 'disk brake, disc brake',\n 536: 'dock, dockage, docking facility',\n 537: 'dogsled, dog sled, dog sleigh',\n 538: 'dome',\n 539: 'doormat, welcome mat',\n 540: 'drilling platform, offshore rig',\n 541: 'drum, membranophone, tympan',\n 542: 'drumstick',\n 543: 'dumbbell',\n 544: 'Dutch oven',\n 545: 'electric fan, blower',\n 546: 'electric guitar',\n 547: 'electric locomotive',\n 548: 'entertainment center',\n 549: 'envelope',\n 550: 'espresso maker',\n 551: 'face powder',\n 552: 'feather boa, boa',\n 553: 'file, file cabinet, filing cabinet',\n 554: 'fireboat',\n 555: 'fire engine, fire truck',\n 556: 'fire screen, fireguard',\n 557: 'flagpole, flagstaff',\n 558: 'flute, transverse flute',\n 559: 'folding chair',\n 560: 'football helmet',\n 561: 'forklift',\n 562: 'fountain',\n 563: 'fountain pen',\n 564: 'four-poster',\n 565: 'freight car',\n 566: 'French horn, horn',\n 567: 'frying pan, frypan, skillet',\n 568: 'fur coat',\n 569: 'garbage truck, dustcart',\n 570: 'gasmask, respirator, gas helmet',\n 571: 'gas pump, gasoline pump, petrol pump, island dispenser',\n 572: 'goblet',\n 573: 'go-kart',\n 574: 'golf ball',\n 575: 'golfcart, golf cart',\n 576: 'gondola',\n 577: 'gong, tam-tam',\n 578: 'gown',\n 579: 'grand piano, grand',\n 580: 'greenhouse, nursery, glasshouse',\n 581: 'grille, radiator grille',\n 582: 'grocery store, grocery, food market, market',\n 583: 'guillotine',\n 584: 'hair slide',\n 585: 'hair spray',\n 586: 'half track',\n 587: 'hammer',\n 588: 'hamper',\n 589: 'hand blower, blow dryer, blow drier, hair dryer, hair drier',\n 590: 'hand-held computer, hand-held microcomputer',\n 591: 'handkerchief, hankie, hanky, hankey',\n 592: 'hard disc, hard disk, fixed disk',\n 593: 'harmonica, mouth organ, harp, mouth harp',\n 594: 'harp',\n 595: 'harvester, reaper',\n 596: 'hatchet',\n 597: 'holster',\n 598: 'home theater, home theatre',\n 599: 'honeycomb',\n 600: 'hook, claw',\n 601: 'hoopskirt, crinoline',\n 602: 'horizontal bar, high bar',\n 603: 'horse cart, horse-cart',\n 604: 'hourglass',\n 605: 'iPod',\n 606: 'iron, smoothing iron',\n 607: \"jack-o'-lantern\",\n 608: 'jean, blue jean, denim',\n 609: 'jeep, landrover',\n 610: 'jersey, T-shirt, tee shirt',\n 611: 'jigsaw puzzle',\n 612: 'jinrikisha, ricksha, rickshaw',\n 613: 'joystick',\n 614: 'kimono',\n 615: 'knee pad',\n 616: 'knot',\n 617: 'lab coat, laboratory coat',\n 618: 'ladle',\n 619: 'lampshade, lamp shade',\n 620: 'laptop, laptop computer',\n 621: 'lawn mower, mower',\n 622: 'lens cap, lens cover',\n 623: 'letter opener, paper knife, paperknife',\n 624: 'library',\n 625: 'lifeboat',\n 626: 'lighter, light, igniter, ignitor',\n 627: 'limousine, limo',\n 628: 'liner, ocean liner',\n 629: 'lipstick, lip rouge',\n 630: 'Loafer',\n 631: 'lotion',\n 632: 'loudspeaker, speaker, speaker unit, loudspeaker system, speaker system',\n 633: \"loupe, jeweler's loupe\",\n 634: 'lumbermill, sawmill',\n 635: 'magnetic compass',\n 636: 'mailbag, postbag',\n 637: 'mailbox, letter box',\n 638: 'maillot',\n 639: 'maillot, tank suit',\n 640: 'manhole cover',\n 641: 'maraca',\n 642: 'marimba, xylophone',\n 643: 'mask',\n 644: 'matchstick',\n 645: 'maypole',\n 646: 'maze, labyrinth',\n 647: 'measuring cup',\n 648: 'medicine chest, medicine cabinet',\n 649: 'megalith, megalithic structure',\n 650: 'microphone, mike',\n 651: 'microwave, microwave oven',\n 652: 'military uniform',\n 653: 'milk can',\n 654: 'minibus',\n 655: 'miniskirt, mini',\n 656: 'minivan',\n 657: 'missile',\n 658: 'mitten',\n 659: 'mixing bowl',\n 660: 'mobile home, manufactured home',\n 661: 'Model T',\n 662: 'modem',\n 663: 'monastery',\n 664: 'monitor',\n 665: 'moped',\n 666: 'mortar',\n 667: 'mortarboard',\n 668: 'mosque',\n 669: 'mosquito net',\n 670: 'motor scooter, scooter',\n 671: 'mountain bike, all-terrain bike, off-roader',\n 672: 'mountain tent',\n 673: 'mouse, computer mouse',\n 674: 'mousetrap',\n 675: 'moving van',\n 676: 'muzzle',\n 677: 'nail',\n 678: 'neck brace',\n 679: 'necklace',\n 680: 'nipple',\n 681: 'notebook, notebook computer',\n 682: 'obelisk',\n 683: 'oboe, hautboy, hautbois',\n 684: 'ocarina, sweet potato',\n 685: 'odometer, hodometer, mileometer, milometer',\n 686: 'oil filter',\n 687: 'organ, pipe organ',\n 688: 'oscilloscope, scope, cathode-ray oscilloscope, CRO',\n 689: 'overskirt',\n 690: 'oxcart',\n 691: 'oxygen mask',\n 692: 'packet',\n 693: 'paddle, boat paddle',\n 694: 'paddlewheel, paddle wheel',\n 695: 'padlock',\n 696: 'paintbrush',\n 697: \"pajama, pyjama, pj's, jammies\",\n 698: 'palace',\n 699: 'panpipe, pandean pipe, syrinx',\n 700: 'paper towel',\n 701: 'parachute, chute',\n 702: 'parallel bars, bars',\n 703: 'park bench',\n 704: 'parking meter',\n 705: 'passenger car, coach, carriage',\n 706: 'patio, terrace',\n 707: 'pay-phone, pay-station',\n 708: 'pedestal, plinth, footstall',\n 709: 'pencil box, pencil case',\n 710: 'pencil sharpener',\n 711: 'perfume, essence',\n 712: 'Petri dish',\n 713: 'photocopier',\n 714: 'pick, plectrum, plectron',\n 715: 'pickelhaube',\n 716: 'picket fence, paling',\n 717: 'pickup, pickup truck',\n 718: 'pier',\n 719: 'piggy bank, penny bank',\n 720: 'pill bottle',\n 721: 'pillow',\n 722: 'ping-pong ball',\n 723: 'pinwheel',\n 724: 'pirate, pirate ship',\n 725: 'pitcher, ewer',\n 726: \"plane, carpenter's plane, woodworking plane\",\n 727: 'planetarium',\n 728: 'plastic bag',\n 729: 'plate rack',\n 730: 'plow, plough',\n 731: \"plunger, plumber's helper\",\n 732: 'Polaroid camera, Polaroid Land camera',\n 733: 'pole',\n 734: 'police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria',\n 735: 'poncho',\n 736: 'pool table, billiard table, snooker table',\n 737: 'pop bottle, soda bottle',\n 738: 'pot, flowerpot',\n 739: \"potter's wheel\",\n 740: 'power drill',\n 741: 'prayer rug, prayer mat',\n 742: 'printer',\n 743: 'prison, prison house',\n 744: 'projectile, missile',\n 745: 'projector',\n 746: 'puck, hockey puck',\n 747: 'punching bag, punch bag, punching ball, punchball',\n 748: 'purse',\n 749: 'quill, quill pen',\n 750: 'quilt, comforter, comfort, puff',\n 751: 'racer, race car, racing car',\n 752: 'racket, racquet',\n 753: 'radiator',\n 754: 'radio, wireless',\n 755: 'radio telescope, radio reflector',\n 756: 'rain barrel',\n 757: 'recreational vehicle, RV, R.V.',\n 758: 'reel',\n 759: 'reflex camera',\n 760: 'refrigerator, icebox',\n 761: 'remote control, remote',\n 762: 'restaurant, eating house, eating place, eatery',\n 763: 'revolver, six-gun, six-shooter',\n 764: 'rifle',\n 765: 'rocking chair, rocker',\n 766: 'rotisserie',\n 767: 'rubber eraser, rubber, pencil eraser',\n 768: 'rugby ball',\n 769: 'rule, ruler',\n 770: 'running shoe',\n 771: 'safe',\n 772: 'safety pin',\n 773: 'saltshaker, salt shaker',\n 774: 'sandal',\n 775: 'sarong',\n 776: 'sax, saxophone',\n 777: 'scabbard',\n 778: 'scale, weighing machine',\n 779: 'school bus',\n 780: 'schooner',\n 781: 'scoreboard',\n 782: 'screen, CRT screen',\n 783: 'screw',\n 784: 'screwdriver',\n 785: 'seat belt, seatbelt',\n 786: 'sewing machine',\n 787: 'shield, buckler',\n 788: 'shoe shop, shoe-shop, shoe store',\n 789: 'shoji',\n 790: 'shopping basket',\n 791: 'shopping cart',\n 792: 'shovel',\n 793: 'shower cap',\n 794: 'shower curtain',\n 795: 'ski',\n 796: 'ski mask',\n 797: 'sleeping bag',\n 798: 'slide rule, slipstick',\n 799: 'sliding door',\n 800: 'slot, one-armed bandit',\n 801: 'snorkel',\n 802: 'snowmobile',\n 803: 'snowplow, snowplough',\n 804: 'soap dispenser',\n 805: 'soccer ball',\n 806: 'sock',\n 807: 'solar dish, solar collector, solar furnace',\n 808: 'sombrero',\n 809: 'soup bowl',\n 810: 'space bar',\n 811: 'space heater',\n 812: 'space shuttle',\n 813: 'spatula',\n 814: 'speedboat',\n 815: \"spider web, spider's web\",\n 816: 'spindle',\n 817: 'sports car, sport car',\n 818: 'spotlight, spot',\n 819: 'stage',\n 820: 'steam locomotive',\n 821: 'steel arch bridge',\n 822: 'steel drum',\n 823: 'stethoscope',\n 824: 'stole',\n 825: 'stone wall',\n 826: 'stopwatch, stop watch',\n 827: 'stove',\n 828: 'strainer',\n 829: 'streetcar, tram, tramcar, trolley, trolley car',\n 830: 'stretcher',\n 831: 'studio couch, day bed',\n 832: 'stupa, tope',\n 833: 'submarine, pigboat, sub, U-boat',\n 834: 'suit, suit of clothes',\n 835: 'sundial',\n 836: 'sunglass',\n 837: 'sunglasses, dark glasses, shades',\n 838: 'sunscreen, sunblock, sun blocker',\n 839: 'suspension bridge',\n 840: 'swab, swob, mop',\n 841: 'sweatshirt',\n 842: 'swimming trunks, bathing trunks',\n 843: 'swing',\n 844: 'switch, electric switch, electrical switch',\n 845: 'syringe',\n 846: 'table lamp',\n 847: 'tank, army tank, armored combat vehicle, armoured combat vehicle',\n 848: 'tape player',\n 849: 'teapot',\n 850: 'teddy, teddy bear',\n 851: 'television, television system',\n 852: 'tennis ball',\n 853: 'thatch, thatched roof',\n 854: 'theater curtain, theatre curtain',\n 855: 'thimble',\n 856: 'thresher, thrasher, threshing machine',\n 857: 'throne',\n 858: 'tile roof',\n 859: 'toaster',\n 860: 'tobacco shop, tobacconist shop, tobacconist',\n 861: 'toilet seat',\n 862: 'torch',\n 863: 'totem pole',\n 864: 'tow truck, tow car, wrecker',\n 865: 'toyshop',\n 866: 'tractor',\n 867: 'trailer truck, tractor trailer, trucking rig, rig, articulated lorry, semi',\n 868: 'tray',\n 869: 'trench coat',\n 870: 'tricycle, trike, velocipede',\n 871: 'trimaran',\n 872: 'tripod',\n 873: 'triumphal arch',\n 874: 'trolleybus, trolley coach, trackless trolley',\n 875: 'trombone',\n 876: 'tub, vat',\n 877: 'turnstile',\n 878: 'typewriter keyboard',\n 879: 'umbrella',\n 880: 'unicycle, monocycle',\n 881: 'upright, upright piano',\n 882: 'vacuum, vacuum cleaner',\n 883: 'vase',\n 884: 'vault',\n 885: 'velvet',\n 886: 'vending machine',\n 887: 'vestment',\n 888: 'viaduct',\n 889: 'violin, fiddle',\n 890: 'volleyball',\n 891: 'waffle iron',\n 892: 'wall clock',\n 893: 'wallet, billfold, notecase, pocketbook',\n 894: 'wardrobe, closet, press',\n 895: 'warplane, military plane',\n 896: 'washbasin, handbasin, washbowl, lavabo, wash-hand basin',\n 897: 'washer, automatic washer, washing machine',\n 898: 'water bottle',\n 899: 'water jug',\n 900: 'water tower',\n 901: 'whiskey jug',\n 902: 'whistle',\n 903: 'wig',\n 904: 'window screen',\n 905: 'window shade',\n 906: 'Windsor tie',\n 907: 'wine bottle',\n 908: 'wing',\n 909: 'wok',\n 910: 'wooden spoon',\n 911: 'wool, woolen, woollen',\n 912: 'worm fence, snake fence, snake-rail fence, Virginia fence',\n 913: 'wreck',\n 914: 'yawl',\n 915: 'yurt',\n 916: 'web site, website, internet site, site',\n 917: 'comic book',\n 918: 'crossword puzzle, crossword',\n 919: 'street sign',\n 920: 'traffic light, traffic signal, stoplight',\n 921: 'book jacket, dust cover, dust jacket, dust wrapper',\n 922: 'menu',\n 923: 'plate',\n 924: 'guacamole',\n 925: 'consomme',\n 926: 'hot pot, hotpot',\n 927: 'trifle',\n 928: 'ice cream, icecream',\n 929: 'ice lolly, lolly, lollipop, popsicle',\n 930: 'French loaf',\n 931: 'bagel, beigel',\n 932: 'pretzel',\n 933: 'cheeseburger',\n 934: 'hotdog, hot dog, red hot',\n 935: 'mashed potato',\n 936: 'head cabbage',\n 937: 'broccoli',\n 938: 'cauliflower',\n 939: 'zucchini, courgette',\n 940: 'spaghetti squash',\n 941: 'acorn squash',\n 942: 'butternut squash',\n 943: 'cucumber, cuke',\n 944: 'artichoke, globe artichoke',\n 945: 'bell pepper',\n 946: 'cardoon',\n 947: 'mushroom',\n 948: 'Granny Smith',\n 949: 'strawberry',\n 950: 'orange',\n 951: 'lemon',\n 952: 'fig',\n 953: 'pineapple, ananas',\n 954: 'banana',\n 955: 'jackfruit, jak, jack',\n 956: 'custard apple',\n 957: 'pomegranate',\n 958: 'hay',\n 959: 'carbonara',\n 960: 'chocolate sauce, chocolate syrup',\n 961: 'dough',\n 962: 'meat loaf, meatloaf',\n 963: 'pizza, pizza pie',\n 964: 'potpie',\n 965: 'burrito',\n 966: 'red wine',\n 967: 'espresso',\n 968: 'cup',\n 969: 'eggnog',\n 970: 'alp',\n 971: 'bubble',\n 972: 'cliff, drop, drop-off',\n 973: 'coral reef',\n 974: 'geyser',\n 975: 'lakeside, lakeshore',\n 976: 'promontory, headland, head, foreland',\n 977: 'sandbar, sand bar',\n 978: 'seashore, coast, seacoast, sea-coast',\n 979: 'valley, vale',\n 980: 'volcano',\n 981: 'ballplayer, baseball player',\n 982: 'groom, bridegroom',\n 983: 'scuba diver',\n 984: 'rapeseed',\n 985: 'daisy',\n 986: \"yellow lady's slipper, yellow lady-slipper, Cypripedium calceolus, Cypripedium parviflorum\",\n 987: 'corn',\n 988: 'acorn',\n 989: 'hip, rose hip, rosehip',\n 990: 'buckeye, horse chestnut, conker',\n 991: 'coral fungus',\n 992: 'agaric',\n 993: 'gyromitra',\n 994: 'stinkhorn, carrion fungus',\n 995: 'earthstar',\n 996: 'hen-of-the-woods, hen of the woods, Polyporus frondosus, Grifola frondosa',\n 997: 'bolete',\n 998: 'ear, spike, capitulum',\n 999: 'toilet tissue, toilet paper, bathroom tissue'\n}\n"
  },
  {
    "path": "caffe2/python/tutorials/jupyter_notebook_config.py",
    "content": "# Configuration file for a jupyter-notebook.\n# Put this script into ~/.jupyter/ in order to get the post_save_hook implemented below.\n# If you already have one, merge the content.\n\nimport os\nfrom subprocess import check_call\n\ndef post_save(model, os_path, contents_manager):\n    \"\"\"post-save hook for converting notebooks to .py scripts\"\"\"\n    if model['type'] != 'notebook':\n        return  # only do this for notebooks\n\n    notebook_dir, notebook_file_name = os.path.split(os_path)\n    check_call(\n        ['jupyter', 'nbconvert', '--to', 'script', notebook_file_name],\n        cwd=notebook_dir,\n    )\n    py_name = os.path.splitext(notebook_file_name)[0] + \".py\"\n    full_py_name = os.path.join(notebook_dir, py_name)\n\n    # Create py_gen/ dir if it doesn't exist and move the file there\n    new_dir = os.path.join(notebook_dir, 'py_gen')\n    if not os.path.exists(new_dir):\n        os.makedirs(new_dir)\n    new_py_location = os.path.join(new_dir, py_name)\n    os.rename(full_py_name, new_py_location)\n    full_py_name = new_py_location\n\n    with open(full_py_name, 'r') as f:\n        data = f.read()\n    lines = data.split('\\n')\n    good_lines = []\n    for line in lines:\n        if (\"get_ipython().magic\" not in line\n                and \"get_ipython().run_line_magic\" not in line):\n            good_lines.append(line)\n    # Update the file with do not edit preamble\n    with open(full_py_name, 'w') as f:\n        f.write(\"#########################################################\\n\")\n        f.write(\"#\\n\")\n        f.write(\"# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\\n\")\n        f.write(\"# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\\n\")\n        f.write(\"#\\n\")\n        f.write(\"#########################################################\\n\")\n        f.write(\"\\n\")\n\n        for line in good_lines:\n            f.write(line + '\\n')\n\n\nc.FileContentsManager.post_save_hook = post_save\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/Basics.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# # Caffe2 Basic Concepts - Operators & Nets\n# \n# In this tutorial we will go through a set of Caffe2 basics: the basic concepts including how operators and nets are being written.\n# \n# First, let's import Caffe2. `core` and `workspace` are usually the two that you need most. If you want to manipulate protocol buffers generated by Caffe2, you probably also want to import `caffe2_pb2` from `caffe2.proto`.\n\n# In[1]:\n\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n# We'll also import a few standard python libraries\nfrom matplotlib import pyplot\nimport numpy as np\nimport time\n\n# These are the droids you are looking for.\nfrom caffe2.python import core, workspace\nfrom caffe2.proto import caffe2_pb2\n\n# Let's show all plots inline.\n\n\n# You might see a warning saying that caffe2 does not have GPU support. That means you are running a CPU-only build. Don't be alarmed - anything CPU is still runnable without a problem.\n\n# ## Workspaces\n# \n# Let's cover workspaces first, where all the data resides.\n# \n# Similar to Matlab, the Caffe2 workspace consists of blobs you create and store in memory. For now, consider a blob to be a N-dimensional Tensor similar to numpy's ndarray, but contiguous. Down the road, we will show you that a blob is actually a typed pointer that can store any type of C++ objects, but Tensor is the most common type stored in a blob. Let's show what the interface looks like.\n# \n# `Blobs()` prints out all existing blobs in the workspace. \n# `HasBlob()` queries if a blob exists in the workspace. As of now, we don't have any.\n\n# In[2]:\n\n\nprint(\"Current blobs in the workspace: {}\".format(workspace.Blobs()))\nprint(\"Workspace has blob 'X'? {}\".format(workspace.HasBlob(\"X\")))\n\n\n# We can feed blobs into the workspace using `FeedBlob()`.\n\n# In[3]:\n\n\nX = np.random.randn(2, 3).astype(np.float32)\nprint(\"Generated X from numpy:\\n{}\".format(X))\nworkspace.FeedBlob(\"X\", X)\n\n\n# Now, let's take a look at what blobs are in the workspace.\n\n# In[4]:\n\n\nprint(\"Current blobs in the workspace: {}\".format(workspace.Blobs()))\nprint(\"Workspace has blob 'X'? {}\".format(workspace.HasBlob(\"X\")))\nprint(\"Fetched X:\\n{}\".format(workspace.FetchBlob(\"X\")))\n\n\n# Let's verify that the arrays are equal.\n\n# In[5]:\n\n\nnp.testing.assert_array_equal(X, workspace.FetchBlob(\"X\"))\n\n\n# Note that if you try to access a blob that does not exist, an error will be thrown:\n\n# In[6]:\n\n\ntry:\n    workspace.FetchBlob(\"invincible_pink_unicorn\")\nexcept RuntimeError as err:\n    print(err)\n\n\n# One thing that you might not use immediately: you can have multiple workspaces in Python using different names, and switch between them. Blobs in different workspaces are separate from each other. You can query the current workspace using `CurrentWorkspace`. Let's try switching the workspace by name (gutentag) and creating a new one if it doesn't exist.\n\n# In[7]:\n\n\nprint(\"Current workspace: {}\".format(workspace.CurrentWorkspace()))\nprint(\"Current blobs in the workspace: {}\".format(workspace.Blobs()))\n\n# Switch the workspace. The second argument \"True\" means creating \n# the workspace if it is missing.\nworkspace.SwitchWorkspace(\"gutentag\", True)\n\n# Let's print the current workspace. Note that there is nothing in the\n# workspace yet.\nprint(\"Current workspace: {}\".format(workspace.CurrentWorkspace()))\nprint(\"Current blobs in the workspace: {}\".format(workspace.Blobs()))\n\n\n# Let's switch back to the default workspace.\n\n# In[8]:\n\n\nworkspace.SwitchWorkspace(\"default\")\nprint(\"Current workspace: {}\".format(workspace.CurrentWorkspace()))\nprint(\"Current blobs in the workspace: {}\".format(workspace.Blobs()))\n\n\n# Finally, `ResetWorkspace()` clears anything that is in the current workspace.\n\n# In[9]:\n\n\nworkspace.ResetWorkspace()\nprint(\"Current blobs in the workspace after reset: {}\".format(workspace.Blobs()))\n\n\n# ## Operators\n# \n# Operators in Caffe2 are kind of like functions. From the C++ side, they all derive from a common interface, and are registered by type, so that we can call different operators during runtime. The interface of operators is defined in `caffe2/proto/caffe2.proto`. Basically, it takes in a bunch of inputs, and produces a bunch of outputs.\n# \n# Remember, when we say \"create an operator\" in Caffe2 Python, nothing gets run yet. All it does is create the protocol buffer that specifies what the operator should be. At a later time it will be sent to the C++ backend for execution. If you are not familiar with protobuf, it is a json-like serialization tool for structured data. Find more about protocol buffers [here](https://developers.google.com/protocol-buffers/).\n# \n# Let's see an actual example.\n\n# In[10]:\n\n\n# Create an operator.\nop = core.CreateOperator(\n    \"Relu\", # The type of operator that we want to run\n    [\"X\"], # A list of input blobs by their names\n    [\"Y\"], # A list of output blobs by their names\n)\n# and we are done!\n\n\n# As we mentioned, the created op is actually a protobuf object. Let's show the content.\n\n# In[11]:\n\n\nprint(\"Type of the created op is: {}\".format(type(op)))\nprint(\"Content:\\n\")\nprint(str(op))\n\n\n# Ok, let's run the operator. We first feed the input X to the workspace. \n# Then the simplest way to run an operator is to do `workspace.RunOperatorOnce(operator)`\n\n# In[12]:\n\n\nworkspace.FeedBlob(\"X\", np.random.randn(2, 3).astype(np.float32))\nworkspace.RunOperatorOnce(op)\n\n\n# After execution, let's see if the operator is doing the right thing.\n# \n# In this case, the operator is a common activation function used in neural networks, called [ReLU](https://en.wikipedia.org/wiki/Rectifier_(neural_networks), or Rectified Linear Unit activation. ReLU activation helps to add necessary non-linear characteristics to the neural network classifier, and is defined as:\n# \n# $$ReLU(x) = max(0, x)$$\n\n# In[13]:\n\n\nprint(\"Current blobs in the workspace: {}\\n\".format(workspace.Blobs()))\nprint(\"X:\\n{}\\n\".format(workspace.FetchBlob(\"X\")))\nprint(\"Y:\\n{}\\n\".format(workspace.FetchBlob(\"Y\")))\nprint(\"Expected:\\n{}\\n\".format(np.maximum(workspace.FetchBlob(\"X\"), 0)))\n\n\n# This is working if your Expected output matches your Y output in this example.\n# \n# Operators also take optional arguments if needed. They are specified as key-value pairs. Let's take a look at one simple example, which takes a tensor and fills it with Gaussian random variables.\n\n# In[14]:\n\n\nop = core.CreateOperator(\n    \"GaussianFill\",\n    [], # GaussianFill does not need any parameters.\n    [\"Z\"],\n    shape=[100, 100], # shape argument as a list of ints.\n    mean=1.0,  # mean as a single float\n    std=1.0, # std as a single float\n)\nprint(\"Content of op:\\n\")\nprint(str(op))\n\n\n# Let's run it and see if things are as intended.\n\n# In[15]:\n\n\nworkspace.RunOperatorOnce(op)\ntemp = workspace.FetchBlob(\"Z\")\npyplot.hist(temp.flatten(), bins=50)\npyplot.title(\"Distribution of Z\")\n\n\n# If you see a bell shaped curve then it worked!\n\n# ## Nets\n# \n# Nets are essentially computation graphs. We keep the name `Net` for backward consistency (and also to pay tribute to neural nets). A Net is composed of multiple operators just like a program written as a sequence of commands. Let's take a look.\n# \n# When we talk about nets, we will also talk about BlobReference, which is an object that wraps around a string so we can do easy chaining of operators.\n# \n# Let's create a network that is essentially the equivalent of the following python math:\n# ```\n# X = np.random.randn(2, 3)\n# W = np.random.randn(5, 3)\n# b = np.ones(5)\n# Y = X * W^T + b\n# ```\n# We'll show the progress step by step. Caffe2's `core.Net` is a wrapper class around a NetDef protocol buffer.\n\n# When creating a network, its underlying protocol buffer is essentially empty other than the network name. Let's create the net and then show the proto content.\n\n# In[16]:\n\n\nnet = core.Net(\"my_first_net\")\nprint(\"Current network proto:\\n\\n{}\".format(net.Proto()))\n\n\n# Let's create a blob called X, and use GaussianFill to fill it with some random data.\n\n# In[17]:\n\n\nX = net.GaussianFill([], [\"X\"], mean=0.0, std=1.0, shape=[2, 3], run_once=0)\nprint(\"New network proto:\\n\\n{}\".format(net.Proto()))\n\n\n# You might have observed a few differences from the earlier `core.CreateOperator` call. Basically, when using a net, you can directly create an operator *and* add it to the net at the same time by calling `net.SomeOp` where SomeOp is a registered type string of an operator. This gets translated to\n# ```\n# op = core.CreateOperator(\"SomeOp\", ...)\n# net.Proto().op.append(op)\n# ```\n# \n# Also, you might be wondering what X is. X is a `BlobReference` which records two things:\n# \n# - The blob's name, which is accessed with `str(X)`\n# \n# - The net it got created from, which is recorded by the internal variable `_from_net`\n# \n# Let's verify it. Also, remember, we are not actually running anything yet, so X contains nothing but a symbol. Don't expect to get any numerical values out of it right now :)\n\n# In[18]:\n\n\nprint(\"Type of X is: {}\".format(type(X)))\nprint(\"The blob name is: {}\".format(str(X)))\n\n\n# Let's continue to create W and b.\n\n# In[19]:\n\n\nW = net.GaussianFill([], [\"W\"], mean=0.0, std=1.0, shape=[5, 3], run_once=0)\nb = net.ConstantFill([], [\"b\"], shape=[5,], value=1.0, run_once=0)\n\n\n# Now, one simple code sugar: since the BlobReference objects know what net it is generated from, in addition to creating operators from net, you can also create operators from BlobReferences. Let's create the FC operator in this way.\n\n# In[20]:\n\n\nY = X.FC([W, b], [\"Y\"])\n\n\n# Under the hood, `X.FC(...)` simply delegates to `net.FC` by inserting `X` as the first input of the corresponding operator, so what we did above is equivalent to\n# ```\n# Y = net.FC([X, W, b], [\"Y\"])\n# ```\n# \n# Let's take a look at the current network.\n\n# In[21]:\n\n\nprint(\"Current network proto:\\n\\n{}\".format(net.Proto()))\n\n\n# Too verbose huh? Let's try to visualize it as a graph. Caffe2 ships with a very minimal graph visualization tool for this purpose.\n\n# In[22]:\n\n\nfrom caffe2.python import net_drawer\nfrom IPython import display\ngraph = net_drawer.GetPydotGraph(net, rankdir=\"LR\")\ndisplay.Image(graph.create_png(), width=800)\n\n\n# So we have defined a Net, but nothing has been executed yet. Remember that the net above is essentially a protobuf that holds the definition of the network. When we actually run the network, what happens under the hood is:\n# - A C++ net object is instantiated from the protobuf\n# - The instantiated net's Run() function is called\n# \n# Before we do anything, we should clear any earlier workspace variables with `ResetWorkspace()`.\n# \n# Then there are two ways to run a net from Python. We will do the first option in the example below.\n# \n# 1. Call `workspace.RunNetOnce()`, which instantiates, runs and immediately destructs the network \n# 2. Call `workspace.CreateNet()` to create the C++ net object owned by the workspace, then call `workspace.RunNet()`, passing the name of the network to it\n#     \n# \n\n# In[23]:\n\n\nworkspace.ResetWorkspace()\nprint(\"Current blobs in the workspace: {}\".format(workspace.Blobs()))\nworkspace.RunNetOnce(net)\nprint(\"Blobs in the workspace after execution: {}\".format(workspace.Blobs()))\n# Let's dump the contents of the blobs\nfor name in workspace.Blobs():\n    print(\"{}:\\n{}\".format(name, workspace.FetchBlob(name)))\n\n\n# Now let's try the second way to create the net, and run it. First, clear the variables with `ResetWorkspace()`. Then create the net with the workspace's `net` object that we created earlier using `CreateNet(net_object)`. Finally, run the net with `RunNet(net_name)`.\n\n# In[24]:\n\n\nworkspace.ResetWorkspace()\nprint(\"Current blobs in the workspace: {}\".format(workspace.Blobs()))\nworkspace.CreateNet(net)\nworkspace.RunNet(net.Proto().name)\nprint(\"Blobs in the workspace after execution: {}\".format(workspace.Blobs()))\nfor name in workspace.Blobs():\n    print(\"{}:\\n{}\".format(name, workspace.FetchBlob(name)))\n\n\n# There are a few differences between `RunNetOnce` and `RunNet`, but the main difference is the computational overhead. Since `RunNetOnce` involves serializing the protobuf to pass between Python and C and instantiating the network, it may take longer to run. Let's run a test and see what the time overhead is.\n\n# In[25]:\n\n\n# It seems that %timeit magic does not work well with\n# C++ extensions so we'll basically do for loops\nstart = time.time()\nfor i in range(1000):\n    workspace.RunNetOnce(net)\nend = time.time()\nprint('Run time per RunNetOnce: {}'.format((end - start) / 1000))\n\nstart = time.time()\nfor i in range(1000):\n    workspace.RunNet(net.Proto().name)\nend = time.time()\nprint('Run time per RunNet: {}'.format((end - start) / 1000))\n\n\n# Congratulations, you now know the many of the key components of the Caffe2 Python API! Ready for more Caffe2? Check out the rest of the tutorials for a variety of interesting use-cases!\n\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/Control_Ops.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# # Control Ops Tutorial\n# \n# In this tutorial we show how to use control flow operators in Caffe2 and give some details about their underlying implementations.\n\n# ### Conditional Execution Using NetBuilder\n# \n# Let's start with conditional operator. We will demonstrate how to use it in two Caffe2 APIs used for building nets: `NetBuilder` and `brew`.\n\n# In[1]:\n\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom caffe2.python import workspace\nfrom caffe2.python.core import Plan, to_execution_step, Net\nfrom caffe2.python.net_builder import ops, NetBuilder\n\n\n# In the first example, we define several blobs and then use the 'If' operator to set the value of one of them conditionally depending on values of other blobs.\n# \n# The pseudocode for the conditional examples we will implement is as follows:\n# \n#     if (x > 0):\n#         y = 1\n#     else:\n#         y = 0\n\n# In[2]:\n\n\nwith NetBuilder() as nb:\n    # Define our constants\n    ops.Const(0.0, blob_out=\"zero\")\n    ops.Const(1.0, blob_out=\"one\")\n    ops.Const(0.5, blob_out=\"x\")\n    ops.Const(0.0, blob_out=\"y\")\n    # Define our conditional sequence\n    with ops.IfNet(ops.GT([\"x\", \"zero\"])):\n        ops.Copy(\"one\", \"y\")\n    with ops.Else():\n        ops.Copy(\"zero\", \"y\")\n\n\n# Note the usage of `NetBuilder`'s `ops.IfNet` and `ops.Else` calls: `ops.IfNet` accepts a blob reference or blob name as an input, it expects an input blob to have a scalar value convertible to bool. Note that the optional `ops.Else` is at the same level as `ops.IfNet` and immediately follows the corresponding `ops.IfNet`. Let's execute the resulting net (execution step) and check the values of the blobs.\n# \n# Note that since x = 0.5, which is indeed greater than 0, we should expect y = 1 after execution.\n\n# In[3]:\n\n\n# Initialize a Plan\nplan = Plan('if_net_test')\n# Add the NetBuilder definition above to the Plan\nplan.AddStep(to_execution_step(nb))\n# Initialize workspace for blobs\nws = workspace.C.Workspace()\n# Run the Plan\nws.run(plan)\n# Fetch some blobs and print\nprint('x = ', ws.blobs[\"x\"].fetch())\nprint('y = ', ws.blobs[\"y\"].fetch())\n\n\n# Before going further, it's important to understand the semantics of execution blocks ('then' and 'else' branches in the example above), i.e. handling of reads and writes into global (defined outside of the block) and local (defined inside the block) blobs.\n# \n# `NetBuilder` uses the following set of rules:\n# \n#  - In `NetBuilder`'s syntax, a blob's declaration and definition occur at the same time - we define an operator which writes its output into a blob with a given name.\n#  \n#  - `NetBuilder` keeps track of all operators seen before the current execution point in the same block and up the stack in parent blocks.\n#  \n#  - If an operator writes into a previously unseen blob, it creates a **local** blob that is visible only within the current block and the subsequent children blocks. Local blobs created in a given block are effectively deleted when we exit the block. Any write into previously defined (in the same block or in the parent blocks) blob updates an originally created blob and does not result in the redefinition of a blob.\n#  \n#  - An operator's input blobs have to be defined earlier in the same block or in the stack of parent blocks. \n#  \n#  \n# As a result, in order to see the values computed by a block after its execution, the blobs of interest have to be defined outside of the block. This rule effectively forces visible blobs to always be correctly initialized.\n# \n# To illustrate concepts of block semantics and provide a more sophisticated example, let's consider the following net:\n\n# In[4]:\n\n\nwith NetBuilder() as nb:\n    # Define our constants\n    ops.Const(0.0, blob_out=\"zero\")\n    ops.Const(1.0, blob_out=\"one\")\n    ops.Const(2.0, blob_out=\"two\")\n    ops.Const(1.5, blob_out=\"x\")\n    ops.Const(0.0, blob_out=\"y\")\n    # Define our conditional sequence\n    with ops.IfNet(ops.GT([\"x\", \"zero\"])):\n        ops.Copy(\"x\", \"local_blob\")  # create local_blob using Copy -- this is not visible outside of this block\n        with ops.IfNet(ops.LE([\"local_blob\", \"one\"])):\n            ops.Copy(\"one\", \"y\")\n        with ops.Else():\n            ops.Copy(\"two\", \"y\")\n    with ops.Else():\n        ops.Copy(\"zero\", \"y\")\n        # Note that using local_blob would fail here because it is outside of the block in\n        # which it was created\n\n\n# When we execute this, we expect that y == 2.0, and that `local_blob` will not exist in the workspace.\n\n# In[5]:\n\n\n# Initialize a Plan\nplan = Plan('if_net_test_2')\n# Add the NetBuilder definition above to the Plan\nplan.AddStep(to_execution_step(nb))\n# Initialize workspace for blobs\nws = workspace.C.Workspace()\n# Run the Plan\nws.run(plan)\n# Fetch some blobs and print\nprint('x = ', ws.blobs[\"x\"].fetch())\nprint('y = ', ws.blobs[\"y\"].fetch())\n# Assert that the local_blob does not exist in the workspace\n# It should have been destroyed because of its locality\nassert \"local_blob\" not in ws.blobs\n\n\n# ### Conditional Execution Using Brew Module\n# \n# Brew is another Caffe2 interface used to construct nets. Unlike `NetBuilder`, `brew` does not track the hierarchy of blocks and, as a result, we need to specify which blobs are considered local and which blobs are considered global when passing 'then' and 'else' models to an API call.\n# \n# Let's start by importing the necessary items for the `brew` API.\n\n# In[6]:\n\n\nfrom caffe2.python import brew\nfrom caffe2.python.workspace import FeedBlob, RunNetOnce, FetchBlob\nfrom caffe2.python.model_helper import ModelHelper\n\n\n# We will use the Caffe2's `ModelHelper` class to define and represent our models, as well as contain the parameter information about the models. Note that a `ModelHelper` object has two underlying nets:\n# \n#     (1) param_init_net: Responsible for parameter initialization\n#     (2) net: Contains the main network definition, i.e. the graph of operators that the data flows through\n# \n# Note that `ModelHelper` is similar to `NetBuilder` in that we define the operator graph first, and actually run later. With that said, let's define some models to act as conditional elements, and use the `brew` module to form the conditional statement that we want to run. We will construct the same statement used in the first example above.\n\n# In[7]:\n\n\n# Initialize model, which will represent our main conditional model for this test\nmodel = ModelHelper(name=\"test_if_model\")\n\n# Add variables and constants to our conditional model; notice how we add them to the param_init_net\nmodel.param_init_net.ConstantFill([], [\"zero\"], shape=[1], value=0.0)\nmodel.param_init_net.ConstantFill([], [\"one\"], shape=[1], value=1.0)\nmodel.param_init_net.ConstantFill([], [\"x\"], shape=[1], value=0.5)\nmodel.param_init_net.ConstantFill([], [\"y\"], shape=[1], value=0.0)\n\n# Add Greater Than (GT) conditional operator to our model\n#  which checks if \"x\" > \"zero\", and outputs the result in the \"cond\" blob\nmodel.param_init_net.GT([\"x\", \"zero\"], \"cond\")\n\n# Initialize a then_model, and add an operator which we will set to be\n#  executed if the conditional model returns True\nthen_model = ModelHelper(name=\"then_test_model\")\nthen_model.net.Copy(\"one\", \"y\")\n\n# Initialize an else_model, and add an operator which we will set to be\n#  executed if the conditional model returns False\nelse_model = ModelHelper(name=\"else_test_model\")\nelse_model.net.Copy(\"zero\", \"y\")\n\n# Use the brew module's handy cond operator to facilitate the construction of the operator graph\nbrew.cond(\n    model=model,                               # main conditional model\n    cond_blob=\"cond\",                          # blob with condition value\n    external_blobs=[\"x\", \"y\", \"zero\", \"one\"],  # data blobs used in execution of conditional\n    then_model=then_model,                     # pass then_model\n    else_model=else_model)                     # pass else_model\n\n\n# Before we run the model, let's use Caffe2's graph visualization tool `net_drawer` to check if the operator graph makes sense.\n\n# In[8]:\n\n\nfrom caffe2.python import net_drawer\nfrom IPython import display\ngraph = net_drawer.GetPydotGraph(model.net, rankdir=\"LR\")\ndisplay.Image(graph.create_png(), width=800)\n\n\n# Now let's run the net! When using `ModelHelper`, we must first run the `param_init_net` to initialize paramaters, then we execute the main `net`.\n\n# In[9]:\n\n\n# Run param_init_net once\nRunNetOnce(model.param_init_net)\n# Run main net (once in this case)\nRunNetOnce(model.net)\n# Fetch and examine some blobs\nprint(\"x = \", FetchBlob(\"x\"))\nprint(\"y = \", FetchBlob(\"y\"))\n\n\n# ### Loops Using NetBuilder\n# \n# Another important control flow operator is 'While', which allows repeated execution of a fragment of net. Let's consider `NetBuilder`'s version first.\n# \n# The pseudocode for this example is:\n# \n#     i = 0\n#     y = 0\n#     while (i <= 7):\n#         y = i + y\n#         i += 1\n\n# In[10]:\n\n\nwith NetBuilder() as nb:\n    # Define our variables\n    ops.Const(0, blob_out=\"i\")\n    ops.Const(0, blob_out=\"y\")\n    \n    # Define loop code and conditions\n    with ops.WhileNet():\n        with ops.Condition():\n            ops.Add([\"i\", ops.Const(1)], [\"i\"])\n            ops.LE([\"i\", ops.Const(7)])\n        ops.Add([\"i\", \"y\"], [\"y\"])\n\n\n# As with the 'If' operator, standard block semantic rules apply. Note the usage of `ops.Condition` clause that should immediately follow `ops.WhileNet` and contains code that is executed before each iteration. The last operator in the condition clause is expected to have a single boolean output that determines whether the other iteration is executed.\n# \n# In the example above we increment the counter (\"i\") before each iteration and accumulate its values in \"y\" blob, the loop's body is executed 7 times, the resulting blob values:\n\n# In[11]:\n\n\n# Initialize a Plan\nplan = Plan('while_net_test')\n# Add the NetBuilder definition above to the Plan\nplan.AddStep(to_execution_step(nb))\n# Initialize workspace for blobs\nws = workspace.C.Workspace()\n# Run the Plan\nws.run(plan)\n# Fetch blobs and print\nprint(\"i = \", ws.blobs[\"i\"].fetch())\nprint(\"y = \", ws.blobs[\"y\"].fetch())\n\n\n# ### Loops Using Brew Module\n# \n# Now let's take a look at how to replicate the loop above using the `ModelHelper`+`brew` interface.\n\n# In[12]:\n\n\n# Initialize model, which will represent our main conditional model for this test\nmodel = ModelHelper(name=\"test_while_model\")\n\n# Add variables and constants to our model\nmodel.param_init_net.ConstantFill([], [\"i\"], shape=[1], value=0)\nmodel.param_init_net.ConstantFill([], [\"one\"], shape=[1], value=1)\nmodel.param_init_net.ConstantFill([], [\"seven\"], shape=[1], value=7)\nmodel.param_init_net.ConstantFill([], [\"y\"], shape=[1], value=0)\n\n# Initialize a loop_model that represents the code to run inside of loop\nloop_model = ModelHelper(name=\"loop_test_model\")\nloop_model.net.Add([\"i\", \"y\"], [\"y\"])\n\n# Initialize cond_model that represents the conditional test that the loop\n#  abides by, as well as the incrementation step\ncond_model = ModelHelper(name=\"cond_test_model\")\ncond_model.net.Add([\"i\", \"one\"], \"i\")\ncond_model.net.LE([\"i\", \"seven\"], \"cond\")\n\n# Use brew's loop operator to facilitate the creation of the loop's operator graph\nbrew.loop(\n    model=model,             # main model that contains data\n    cond_blob=\"cond\",        # explicitly specifying condition blob\n    external_blobs=[\"cond\", \"i\", \"one\", \"seven\", \"y\"], # data blobs used in execution of the loop\n    loop_model=loop_model,   # pass loop_model\n    cond_model=cond_model    # pass condition model (optional)\n)\n\n\n# Once again, let's visualize the net using the `net_drawer`.\n\n# In[13]:\n\n\ngraph = net_drawer.GetPydotGraph(model.net, rankdir=\"LR\")\ndisplay.Image(graph.create_png(), width=800)\n\n\n# Finally, we'll run the `param_init_net` and `net` and print our final blob values.\n\n# In[14]:\n\n\nRunNetOnce(model.param_init_net)\nRunNetOnce(model.net)\nprint(\"i = \", FetchBlob(\"i\"))\nprint(\"y = \", FetchBlob(\"y\"))\n\n\n# ### Backpropagation\n# \n# Both 'If' and 'While' operators support backpropagation. To illustrate how backpropagation with control ops work, let's consider the following examples in which we construct the operator graph using `NetBuilder` and obtain calculate gradients using the `AddGradientOperators` function. The first example shows the following conditional statement:\n# \n#     x = 1-D numpy float array\n#     y = 4\n#     z = 0\n#     if (x > 0):\n#         z = y^2\n#     else:\n#         z = y^3\n\n# In[15]:\n\n\nimport numpy as np\n\n# Feed blob called x, which is simply a 1-D numpy array [0.5]\nFeedBlob(\"x\", np.array(0.5, dtype='float32'))\n\n# _use_control_ops=True forces NetBuilder to output single net as a result\n# x is external for NetBuilder, so we let nb know about it through initial_scope param\nwith NetBuilder(_use_control_ops=True, initial_scope=[\"x\"]) as nb:\n    ops.Const(0.0, blob_out=\"zero\")\n    ops.Const(1.0, blob_out=\"one\")\n    ops.Const(4.0, blob_out=\"y\")\n    ops.Const(0.0, blob_out=\"z\")\n    with ops.IfNet(ops.GT([\"x\", \"zero\"])):\n        ops.Pow(\"y\", \"z\", exponent=2.0)\n    with ops.Else():\n        ops.Pow(\"y\", \"z\", exponent=3.0)\n\n# we should get a single net as output\nassert len(nb.get()) == 1, \"Expected a single net produced\"\nnet = nb.get()[0]\n\n# add gradient operators for 'z' blob\ngrad_map = net.AddGradientOperators([\"z\"])\n\n\n# In this case\n# \n# $$x = 0.5$$\n# \n# $$z = y^2 = 4^2 = 16$$\n# \n# We will fetch the blob `y_grad`, which was generated by the `AddGradientOperators` call above. This blob contains the gradient of blob z with respect to y. According to basic calculus:\n# \n# $$y\\_grad = \\frac{\\partial{z}}{\\partial{y}}y^2 = 2y = 2(4) = 8$$\n\n# In[16]:\n\n\n# Run the net\nRunNetOnce(net)\n# Fetch blobs and print\nprint(\"x = \", FetchBlob(\"x\"))\nprint(\"y = \", FetchBlob(\"y\"))\nprint(\"z = \", FetchBlob(\"z\"))\nprint(\"y_grad = \", FetchBlob(\"y_grad\"))\n\n\n# Now, let's change value of blob \"x\" to -0.5 and rerun net:\n\n# In[17]:\n\n\n# To re-run net with different input, simply feed new blob\nFeedBlob(\"x\", np.array(-0.5, dtype='float32'))\nRunNetOnce(net)\nprint(\"x = \", FetchBlob(\"x\"))\nprint(\"y = \", FetchBlob(\"y\"))\nprint(\"z = \", FetchBlob(\"z\"))\nprint(\"y_grad = \", FetchBlob(\"y_grad\"))\n\n\n# The next and final example illustrates backpropagation on the following loop:\n# \n#     x = 2\n#     y = 3\n#     z = 2\n#     i = 0\n#     while (i <= 2):\n#         x = x^2\n#         if (i < 2):\n#             y = y^2\n#         else:\n#             z = z^3\n#         i += 1\n#     s = x + y + z\n#     \n# Note that this code essentially computes the sum of x^4 (by squaring x twice), y^2, and z^3.\n\n# In[18]:\n\n\nwith NetBuilder(_use_control_ops=True) as nb:\n    # Define variables and constants\n    ops.Copy(ops.Const(0), \"i\")\n    ops.Copy(ops.Const(1), \"one\")\n    ops.Copy(ops.Const(2), \"two\")\n    ops.Copy(ops.Const(2.0), \"x\")\n    ops.Copy(ops.Const(3.0), \"y\")\n    ops.Copy(ops.Const(2.0), \"z\")\n    \n    # Define loop statement\n    # Computes x^4, y^2, z^3\n    with ops.WhileNet():\n        with ops.Condition():\n            ops.Add([\"i\", \"one\"], \"i\")\n            ops.LE([\"i\", \"two\"])\n        ops.Pow(\"x\", \"x\", exponent=2.0)\n        with ops.IfNet(ops.LT([\"i\", \"two\"])):\n            ops.Pow(\"y\", \"y\", exponent=2.0)\n        with ops.Else():\n            ops.Pow(\"z\", \"z\", exponent=3.0)\n    \n    # Sum s = x + y + z\n    ops.Add([\"x\", \"y\"], \"x_plus_y\")\n    ops.Add([\"x_plus_y\", \"z\"], \"s\")\n\nassert len(nb.get()) == 1, \"Expected a single net produced\"\nnet = nb.get()[0]\n\n# Add gradient operators to output blob 's'\ngrad_map = net.AddGradientOperators([\"s\"])\n\n\n# In[19]:\n\n\nworkspace.RunNetOnce(net)\nprint(\"x = \", FetchBlob(\"x\"))\nprint(\"x_grad = \", FetchBlob(\"x_grad\")) # derivative: 4x^3\nprint(\"y = \", FetchBlob(\"y\"))\nprint(\"y_grad = \", FetchBlob(\"y_grad\")) # derivative: 2y\nprint(\"z = \", FetchBlob(\"z\"))\nprint(\"z_grad = \", FetchBlob(\"z_grad\")) # derivative: 3z^2\n\n\n# ### Implementation Notes\n# \n# On the low level, Caffe2 uses the following set of operators to implement forward and backward branching and loops:\n# - If - accepts *then_net* and *else_net* nets as arguments and executes one of them, depending on input condition blob value, nets are executed **in the same** workspace;\n# - While - repeats execution of *loop_net* net passed as argument, net is executed in the same workspace;\n# - Do - special operator that creates a separate inner workspace, sets up blob mappings between outer and inner workspaces, and runs a net in an inner workspace;\n# - CreateScope/HasScope - special operators that create and keep track of workspaces used by Do operator.\n# \n# Higher level libraries that implement branching and looping (e.g. in `NetBuilder`, `brew`), use these operators to build control flow, e.g. for 'If':\n#  - do necessary sanity checks (e.g. determine which blobs are initialized and check that subnet does not read undefined blobs)\n#  - wrap 'then' and 'else' branches into Do\n#  - setup correct blob mappings by specifying which local names are mapped to outer blobs\n#  - prepare scope structure, used by Do operator\n# \n# While 'If' and 'While' Caffe2 ops can be used directly without creating local block workspaces, we encourage users to use higher level Caffe2 interfaces that provide necessary correctness guarantees.\n# \n# Backpropagation for 'While' in general is expensive memory-wise - we have to save local workspace for every iteration of a block, including global blobs visible to the block. It is recommended that users use `RecurrentNetwork` operator instead in production environments.\n\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/Getting_Caffe1_Models_for_Translation.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# # Getting Caffe1 Models and Datasets\n# \n# This tutorial will help you acquire a variety of pre-trained models from the original Caffe repo, and translate these models to a format that Caffe2 expects. If you don't already have the Caffe repo, then clone it like so:\n# \n# ```\n# git clone https://github.com/BVLC/caffe.git\n# ```\n# \n# Start by importing the required modules.\n\n# In[ ]:\n\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport os\nprint(\"Required modules imported.\")\n\n\n# Now you can setup your root folder for Caffe below if you put it somewhere else. You should only be changing the path that's being set for `CAFFE_ROOT`.\n\n# In[ ]:\n\n\n# You should have checked out original Caffe\n# git clone https://github.com/BVLC/caffe.git\n# change the CAFFE_ROOT directory below accordingly\nCAFFE_ROOT = os.path.expanduser('~/caffe')\n\n# Make sure Caffe exists where you specified\nif not os.path.exists(CAFFE_ROOT):\n    print(\"Houston, you may have a problem.\") \n    print(\"Did you change CAFFE_ROOT to point to your local Caffe repo?\")\n    print(\"Try running: git clone https://github.com/BVLC/caffe.git\")\n\n\n# Here's where you pick your model. There are several listed below such as AlexNet, GoogleNet, and Flickr Style. Uncomment the model you want to download.\n\n# In[ ]:\n\n\n# Pick a model, and if you don't have it, it will be downloaded\n# format below is the model's folder, model's dataset inside that folder\n\n#MODEL = 'bvlc_alexnet', 'bvlc_alexnet.caffemodel' \n#MODEL = 'bvlc_googlenet', 'bvlc_googlenet.caffemodel'\n#MODEL = 'finetune_flickr_style', 'finetune_flickr_style.caffemodel'\n#MODEL = 'bvlc_reference_caffenet', 'bvlc_reference_caffenet.caffemodel'\nMODEL = 'bvlc_reference_rcnn_ilsvrc13', 'bvlc_reference_rcnn_ilsvrc13.caffemodel'\n\n\n# As a reminder, in Caffe, the deploy model is saved in two parts:\n# \n#     1) deploy.prototxt: contained the network architecture in human-readable protobuf format\n#     2) .caffemodel file: contained the model weights and parameters for loading\n# \n# Therefore, to translate the model to Caffe2, we need both of these files. We already have the `deploy.prototxt` files for all of the models in `~/caffe/models`, so we need the learned weights.\n# \n# Below, we'll check to see if the `.caffemodel` file from the last model that we uncommented above already exists. If it does not already exist in the location that we specify, we will download it using the `download_model_binary.py` script in the Caffe repo. **Note that .caffemodel files are typically fairly large files, so downloading one will take a few moments.** We will be sure to print a message so we know when we can continue.\n\n# In[ ]:\n\n\n# Scripts to download the models reside here (~/caffe/models)\n# After downloading the data will exist with the script\nCAFFE_MODELS = os.path.join(CAFFE_ROOT, 'models')\n\n# this is like: ~/caffe/models/bvlc_alexnet/deploy.prototxt\nCAFFE_MODEL_FILE = os.path.join(CAFFE_MODELS, MODEL[0], 'deploy.prototxt')\n# this is like: ~/caffe/models/bvlc_alexnet/bvlc_alexnet.caffemodel\nCAFFE_PRETRAINED = os.path.join(CAFFE_MODELS, MODEL[0], MODEL[1])\n    \n# If the model folder doesn't have the goods, then download it\n# This is usually a pretty big file with the .caffemodel extension\nif not os.path.exists(CAFFE_PRETRAINED):\n    print(CAFFE_PRETRAINED + \" not found. Attempting download. Be patient...\\n\")\n    os.system(\n        os.path.join(CAFFE_ROOT, 'scripts/download_model_binary.py') +\n        ' ' +\n        os.path.join(CAFFE_ROOT, 'models', MODEL[0]))\nelse:\n    print(\"You already have \" + CAFFE_PRETRAINED + \", skipping download...\\n\")\n\n# If the .prototxt file was missing then you're in trouble; cannot continue\nif not os.path.exists(CAFFE_MODEL_FILE):\n    print(\"Caffe model file, \" + CAFFE_MODEL_FILE + \" was not found!\")\nelse:\n    print(\"Both the deploy.prototxt and .caffemodel files were found, ready to continue!\")\n    # Now we have init net and predict net .pb files to use\n\n\n# Now that we have both the `deploy.prototxt` and `.caffemodel` files, we can translate the model to the Caffe2 saved model format, which consists of two serialized protobuf files:\n# \n#     1) init_net.pb\n#     2) predict_net.pb\n#     \n# To do this, we will use Caffe2's translator script at `~/caffe2/caffe2/python/caffe_translator.py`.\n# \n# **Again, depending on the size of the model, this may take a minute or two**\n\n# In[ ]:\n\n\n# Set the CAFFE2_ROOT\nCAFFE2_ROOT = os.path.expanduser('~/caffe2')\ninit_net_out = os.path.join(CAFFE_MODELS, MODEL[0], 'init_net.pb')\npredict_net_out = os.path.join(CAFFE_MODELS, MODEL[0], 'predict_net.pb')\n\n# Run the caffe_translator.py script to translate to Caffe2 if files do not already exist\nif (not os.path.exists(init_net_out)) or (not os.path.exists(predict_net_out)):\n    print(\"Protobuf files not found. Running translation. Be patient...\\n\")\n    os.system(\n        'python' + ' ' + os.path.join(CAFFE2_ROOT, 'caffe2/python/caffe_translator.py') +\n        ' ' + CAFFE_MODEL_FILE + ' ' + CAFFE_PRETRAINED + ' ' + \n        '--init_net' + ' ' + init_net_out + ' ' +\n        '--predict_net' + ' ' + predict_net_out\n    )\nelse:\n    print(\"You already have both .pb files, skipping translation...\\n\")    \n\n# Print if files are where they are expected to be\nif (not os.path.exists(init_net_out)) or (not os.path.exists(predict_net_out)):\n    print(init_net_out + \" and/or \" + predict_net_out + \" was NOT FOUND!\")\nelse:\n    print(\"Protobuf files can be found at: \\n\", \n              os.path.join(CAFFE_MODELS, MODEL[0])), \"!\"\n\n\n# At this point, we have translated the model from Caffe to a format that Caffe2 can use. Have a look at our other tutorials, such as *Loading Pretrained Models* to see an example of how to use these .pb files for inference.\n\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/Image_Pre-Processing_Pipeline.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# # Image Loading and Preprocessing\n# \n# In this tutorial we're going to look at how we can load in images from a local file or a URL which you can then utilize in other tutorials or examples. Also, we're going to go in depth on the kinds of preprocessing that is necessary to utilize Caffe2 with images.\n# \n# #### Mac OSx Prerequisites\n# \n# If you don't already have these Python modules installed you'll need to do that now.\n# \n# ```\n# sudo pip install scikit-image scipy matplotlib\n# ```\n\n# In[1]:\n\n\nimport skimage\nimport skimage.io as io\nimport skimage.transform \nimport sys\nimport numpy as np\nimport math\nfrom matplotlib import pyplot\nimport matplotlib.image as mpimg\nprint(\"Required modules imported.\")\n\n\n# ## Test an Image\n# \n# In the code block below use IMAGE_LOCATION to load what you would like to test. Just change the comment flags to go through each round of the Tutorial. In this way, you'll get to see what happens with a variety of image formats and some tips on how you might preprocess them. If you want to try your own image, drop it in the images folder or use a remote URL. When you pick a remote URL, make it easy on yourself and try to find a URL that points to a common image file type and extension versus some long identifier or query string which might just break this next step.\n# \n# ## Color Issues\n# \n# Keep in mind when you load images from smartphone cameras that you may run into color formatting issues. Below we show an example of how flipping between RGB and BGR can impact an image. This would obviously throw off detection in your model. Make sure the image data you're passing around is what you think it is!\n# \n# ### Caffe Uses BGR Order\n# \n# Due to legacy support of OpenCV in Caffe and how it handles images in Blue-Green-Red (BGR) order instead of the more commonly used Red-Green-Blue (RGB) order, Caffe2 also expects **BGR** order. In many ways this decision helps in the long run as you use different computer vision utilities and libraries, but it also can be the source of confusion. \n\n# In[2]:\n\n\n# You can load either local IMAGE_FILE or remote URL\n# For Round 1 of this tutorial, try a local image.\nIMAGE_LOCATION = 'images/cat.jpg'\n\n# For Round 2 of this tutorial, try a URL image with a flower: \n# IMAGE_LOCATION = \"https://cdn.pixabay.com/photo/2015/02/10/21/28/flower-631765_1280.jpg\"\n# IMAGE_LOCATION = \"images/flower.jpg\"\n\n# For Round 3 of this tutorial, try another URL image with lots of people:\n# IMAGE_LOCATION = \"https://upload.wikimedia.org/wikipedia/commons/1/18/NASA_Astronaut_Group_15.jpg\"\n# IMAGE_LOCATION = \"images/astronauts.jpg\"\n\n# For Round 4 of this tutorial, try a URL image with a portrait!\n# IMAGE_LOCATION = \"https://upload.wikimedia.org/wikipedia/commons/9/9a/Ducreux1.jpg\"\n# IMAGE_LOCATION = \"images/Ducreux.jpg\"\n\nimg = skimage.img_as_float(skimage.io.imread(IMAGE_LOCATION)).astype(np.float32)\n\n# test color reading\n# show the original image\npyplot.figure()\npyplot.subplot(1,2,1)\npyplot.imshow(img)\npyplot.axis('on')\npyplot.title('Original image = RGB')\n\n# show the image in BGR - just doing RGB->BGR temporarily for display\nimgBGR = img[:, :, (2, 1, 0)]\n#pyplot.figure()\npyplot.subplot(1,2,2)\npyplot.imshow(imgBGR)\npyplot.axis('on')\npyplot.title('OpenCV, Caffe2 = BGR')\n\n\n# As you can see in the example above, the difference in order is very important to keep in mind. In the code block below we'll be taking the image and converting to BGR order for Caffe to process it appropriately.\n# \n# But wait, there's more color fun...\n# \n# ### Caffe Prefers CHW Order\n# \n# Now what!? What's CHW, you ask? Well, there's also HWC! Both formats come up in image processing.\n# \n# - H: Height\n# - W: Width\n# - C: Channel (as in color)\n# \n# Digging even deeper into how image data can be stored is the memory allocation order. You might have noticed when we first loaded the image that we forced it through some interesting transformations. These were data transformations that let us play with the image as if it were a cube. What we see is on top of the cube, and manipulating the layers below can change what we view. We can tinker with it's underlying properties and as you saw above, swap colors quite easily. \n# \n# For GPU processing, which is what Caffe2 excels at, this order needs to be CHW. For CPU processing, this order is generally HWC. Essentially, you're going to want to use CHW and make sure that step is included in your image pipeline. Tweak RGB to be BGR, which is encapsulated as this \"C\" payload, then tweak HWC, the \"C\" being the very same colors you just switched around.\n# \n# You may ask why! And the reason points to cuDNN which is what helps accelerate processing on GPUs. It uses only CHW, and we'll sum it up by saying it is faster. \n# \n# Give these two transformations, you might think that's enough, but it isn't. We still need to resize and/or crop and potentially look at things like orientation (rotation) and mirroring.\n# \n# ## Rotation and Mirroring\n# \n# This topic is usually reserved for images that are coming from a smart phone. Phones, in general, take great pictures, but do a horrible job communicating how the image was taken and what orientation it should be in. Then there's the user who does everything under the sun with their phone's cameras, making them do things its designer never expected. Cameras - right, because there are often two cameras and these two cameras take different sized pictures in both pixel count and aspect ratio, and not only that, they sometimes take them mirrored, and they sometimes take them in portrait and landscape modes, and sometimes they don't bother to tell which mode they were in. \n# \n# In many ways this is the first thing you need to evaluate in your pipeline, then look at sizing (described below), then figure out the color situation. If you're developing for iOS, then you're in luck, it's going to be relatively easy. If you're a super-hacker wizard developer with lead-lined shorts and developing for Android, then at least you have lead-lined shorts. \n# \n# The variability in the Android marketplace is wonderful and horrifying. In an ideal world, you could rely on the EXIF data in pictures coming from any camera and use that to decide orientation and mirroring and you'd have one simple case function to handle your transformations. No such luck, but you're not alone. Many have come before you and suffered for you.\n# \n# ### Library for Handling Mobile Images\n# \n# Hooray! We're going to give you something to ease your pain. These are not full-proof. Users can and will defy you and every other developers' best attempts to handle their images. Here we'll link to some resources that can be used depending on the platform.\n# \n# Image Preprocessing Libraries and/or Snippits\n# - [iOS](#)\n# - [Android](#)\n# - [Python](#)\n# In the meantime though, let's play with some images and show the basics for manipulations that you might need to do.\n\n# In[3]:\n\n\n# Image came in sideways - it should be a portait image!\n# How you detect this depends on the platform\n# Could be a flag from the camera object\n# Could be in the EXIF data\n# ROTATED_IMAGE = \"https://upload.wikimedia.org/wikipedia/commons/8/87/Cell_Phone_Tower_in_Ladakh_India_with_Buddhist_Prayer_Flags.jpg\"\nROTATED_IMAGE = \"images/cell-tower.jpg\"\nimgRotated = skimage.img_as_float(skimage.io.imread(ROTATED_IMAGE)).astype(np.float32)\npyplot.figure()\npyplot.imshow(imgRotated)\npyplot.axis('on')\npyplot.title('Rotated image')\n\n# Image came in flipped or mirrored - text is backwards!\n# Again detection depends on the platform\n# This one is intended to be read by drivers in their rear-view mirror\n# MIRROR_IMAGE = \"https://upload.wikimedia.org/wikipedia/commons/2/27/Mirror_image_sign_to_be_read_by_drivers_who_are_backing_up_-b.JPG\"\nMIRROR_IMAGE = \"images/mirror-image.jpg\"\nimgMirror = skimage.img_as_float(skimage.io.imread(MIRROR_IMAGE)).astype(np.float32)\npyplot.figure()\npyplot.imshow(imgMirror)\npyplot.axis('on')\npyplot.title('Mirror image')\n\n\n# So you can see that we kind of have some problems. If we're detecting places, landmarks, or objects, a sideways cell tower is no good. If we're detecting text and doing automatic language translation, then mirrored text is no good. But hey, maybe you want to make a model that can detect English both ways. That would be awesome, but not for this tutorial!\n# \n# Let's transform these babies into something Caffe2 and the standard detection models we have around can detect. Also, this little trick might save you if, say for example, you really had to detect the cell tower but there's no EXIF data to be found: then you'd cycle through every rotation, and every flip, spawning many derivatives of this photo and run them all through. When the percentage of confidence of detection is high enough, Bam!, you found the orientation you needed and that sneaky cell tower.\n# \n# Anyway, to the example code:\n\n# In[4]:\n\n\n# Run me to flip the image back and forth\nimgMirror = np.fliplr(imgMirror)\npyplot.figure()\npyplot.imshow(imgMirror)\npyplot.axis('off')\npyplot.title('Mirror image')\n\n\n# In[5]:\n\n\n# Run me to rotate the image 90 degrees\nimgRotated = np.rot90(imgRotated)\npyplot.figure()\npyplot.imshow(imgRotated)\npyplot.axis('off')\npyplot.title('Rotated image')\n\n\n# ## Sizing\n# \n# Part of preprocessing is resizing. For reasons we won't get into here, images in the Caffe2 pipeline should be square. Also, to help with performance, they should be resized to a standard height and width which is usually going to be smaller than your original source. In the example below we're resizing to 256 x 256 pixels, however you might notice that the `input_height` and `input_width` is set to 224 x 224 which is then used to specify the crop. This is what several image-based models are expecting. They were trained on images sized to 224 x 224 and in order for the model to properly identify the suspect images you throw at it, these should also be 224 x 224.\n# \n# ** Make sure you double-check the input sizes for the model you're using!**\n\n# In[6]:\n\n\n# Model is expecting 224 x 224, so resize/crop needed.\n# Here are the steps we use to preprocess the image.\n# (1) Resize the image to 256*256, and crop out the center.\ninput_height, input_width = 224, 224\nprint(\"Model's input shape is %dx%d\") % (input_height, input_width)\n#print(\"Original image is %dx%d\") % (skimage.)\nimg256 = skimage.transform.resize(img, (256, 256))\npyplot.figure()\npyplot.imshow(img256)\npyplot.axis('on')\npyplot.title('Resized image to 256x256')\nprint(\"New image shape:\" + str(img256.shape))\n\n\n# Note the resizing has distorted the image a little bit. It is important to recognize this effect during your processing as it can have an effect on the results of your model. Flowers and animals might be ok with a little stretching or squeezing, but facial features may not. \n# \n# This can happen when the dimensions of the original image are not proportionally exact to your desired size. In this particular example it would have been better to just resize to 224x224 and not bother cropping. Let's try another strategy of rescaling the image and maintaining the aspect ratio.\n# \n# ### Rescaling\n# \n# If you imagine portait images versus landscape images you'll know that there are a lot of things that can get messed up by doing a slopping resize. Rescaling is assuming that you're locking down the aspect ratio to prevent distortion in the image. In this case, we'll scale down the image to the shortest side that matches with the model's input size.\n# \n# In our example here, the model size is 224 x 224. As you look at your monitor in 1920x1080, it is longer in width than height and if you shrunk it down to 224, you'd run out of height before you ran out of width, so...\n# \n# - Landscape: limit resize by the height\n# - Portrait: limit resize by the width\n\n# In[7]:\n\n\nprint(\"Original image shape:\" + str(img.shape) + \" and remember it should be in H, W, C!\")\nprint(\"Model's input shape is %dx%d\") % (input_height, input_width)\naspect = img.shape[1]/float(img.shape[0])\nprint(\"Orginal aspect ratio: \" + str(aspect))\nif(aspect>1):\n    # landscape orientation - wide image\n    res = int(aspect * input_height)\n    imgScaled = skimage.transform.resize(img, (input_height, res))\nif(aspect<1):\n    # portrait orientation - tall image\n    res = int(input_width/aspect)\n    imgScaled = skimage.transform.resize(img, (res, input_width))\nif(aspect == 1):\n    imgScaled = skimage.transform.resize(img, (input_height, input_width))\npyplot.figure()\npyplot.imshow(imgScaled)\npyplot.axis('on')\npyplot.title('Rescaled image')\nprint(\"New image shape:\" + str(imgScaled.shape) + \" in HWC\")\n\n\n# At this point only one dimension is set to what the model's input requires. We still need to crop one side to make a square. \n# \n# ### Cropping\n# \n# There are a variety of strategies we could utilize. In fact, we could backpeddle and decide to do a center crop. So instead of scaling down to the smallest we could get on at least one side, we take a chunk out of the middle. If we had done that without scaling we would have ended up with just part of a flower pedal, so we still needed some resizing of the image.\n# \n# Below we'll try a few strategies for cropping:\n# \n# 1. Just grab the exact dimensions you need from the middle!\n# 2. Resize to a square that's pretty close then grab from the middle.\n# 3. Use the rescaled image and grab the middle.\n\n# In[8]:\n\n\n# Compare the images and cropping strategies\n# Try a center crop on the original for giggles\nprint(\"Original image shape:\" + str(img.shape) + \" and remember it should be in H, W, C!\")\ndef crop_center(img,cropx,cropy):\n    y,x,c = img.shape\n    startx = x//2-(cropx//2)\n    starty = y//2-(cropy//2)    \n    return img[starty:starty+cropy,startx:startx+cropx]\n# yes, the function above should match resize and take a tuple...\n\npyplot.figure()\n# Original image\nimgCenter = crop_center(img,224,224)\npyplot.subplot(1,3,1)\npyplot.imshow(imgCenter)\npyplot.axis('on')\npyplot.title('Original')\n\n# Now let's see what this does on the distorted image\nimg256Center = crop_center(img256,224,224)\npyplot.subplot(1,3,2)\npyplot.imshow(img256Center)\npyplot.axis('on')\npyplot.title('Squeezed')\n\n# Scaled image\nimgScaledCenter = crop_center(imgScaled,224,224)\npyplot.subplot(1,3,3)\npyplot.imshow(imgScaledCenter)\npyplot.axis('on')\npyplot.title('Scaled')\n\n\n# As you can see that didn't work out so well, except for maybe the last one. The middle one may be just fine too, but you won't know until you try on the model and test a lot of candidate images.\n# At this point we can look at the difference we have, split it in half and remove some pixels from each side. This does have a drawback, however, as an off-center subject of interest would get clipped.\n\n# If you've run this tutorial a few times now and are on Round 3, you'll notice a pretty big problem. You're missing astronaughts! You can still see the issue with the flower from Round 2 as well. Things are missing after the cropping and that could cause you problems. Think of it this way: if you don't know how the model you're using was prepared then you don't know how to conform your images, so take care to test results! If the model used a lot of different aspect ratio images and just squeezed them to conform to a square then there's a good chance that over time and lots of samples it \"learned\" what things look like squeezed and can make a match. However, if you're looking for details like facial features and landmarks, or really nuanced elements in any image, this could be dangerous and error-prone. \n# \n# #### Further Strategies?\n# \n# Another strategy would be to rescale to the best size you can, with real data, but then pad the rest of the image with information that you can safely ignore in your model. We'll save that for another tutorial though since you've been through enough here! \n# \n# ### Upscaling\n# \n# What do you do when the images you want to run are \"tiny\"? In our example we've been prepping for Input Images with the spec of 224x224. Consider this 128x128 image below.\n# ![cells at 128x128](images/Cellsx128.png)\n# Now we're not talking about super-resolution or the CSI-effect where we can take blurry ATM photos and identify the tattoo an a perp's neck. Although, there are [some advances](https://github.com/david-gpu/srez) along these lines that deep learning has provided, and if you're reading this in time (before 3/1/17), go [check this out](https://developer.nvidia.com/zoom-enhance-magic-image-upscaling-using-deep-learning). What we want to do is simple, but, like cropping, it does have a variety of strategies you should consider.\n# \n# The most basic approach is going from a small square to a bigger square and using the defauls skimage provides for you. This `resize` method defaults the interpolation order parameter to 1 which happens to be bi-linear if you even cared, but it is worth mentioning because these might be the fine-tuning knobs you need later to fix problems, such as strange visual artifacts, that can be introduced in upscaling images.\n\n# In[9]:\n\n\nimgTiny = \"images/Cellsx128.png\"\nimgTiny = skimage.img_as_float(skimage.io.imread(imgTiny)).astype(np.float32)\nprint \"Original image shape: \", imgTiny.shape\nimgTiny224 = skimage.transform.resize(imgTiny, (224, 224))\nprint \"Upscaled image shape: \", imgTiny224.shape\n# Plot original\npyplot.figure()\npyplot.subplot(1, 2, 1)\npyplot.imshow(imgTiny)\npyplot.axis('on')\npyplot.title('128x128')\n# Plot upscaled\npyplot.subplot(1, 2, 2)\npyplot.imshow(imgTiny224)\npyplot.axis('on')\npyplot.title('224x224')\n\n\n# Great, it worked!. You can see in the shape outputs that you had (128, 128, 4) and you received (224, 224, 4). Wait a minute! 4? In every example so far the last value in shape has been 3! When we used a png file we entered a new reality; one where transparency is possible. This 4th value describes opacity, or transparency, depending if you're a half-glass-empty type. Anyway, we can handle it just fine, but keep an eye on that number.\n# \n# It's appropriate to put this discussion towards the end, but before we do further manipulations to the image, it's data order, and its overall payload. You can really mess up your data and the image if you do a simple resample on the image in its current format. Remember that it is currently a cube of data and that there's more going on in there right now than just Red, Green, and Blue (and opacity). Depending on when you decide to resize you'll have to account for that extra data.\n# \n# Let's break stuff! Try upscaling the image after you've switched the image to CHW.\n\n# In[10]:\n\n\nimgTiny = \"images/Cellsx128.png\"\nimgTiny = skimage.img_as_float(skimage.io.imread(imgTiny)).astype(np.float32)\nprint \"Image shape before HWC --> CHW conversion: \", imgTiny.shape\n# swapping the axes to go from HWC to CHW\n# uncomment the next line and run this block!\nimgTiny = imgTiny.swapaxes(1, 2).swapaxes(0, 1)\nprint \"Image shape after HWC --> CHW conversion: \", imgTiny.shape\nimgTiny224 = skimage.transform.resize(imgTiny, (224, 224))\nprint \"Image shape after resize: \", imgTiny224.shape\n# we know this is going to go wrong, so...\ntry:\n    # Plot original\n    pyplot.figure()\n    pyplot.subplot(1, 2, 1)\n    pyplot.imshow(imgTiny)\n    pyplot.axis('on')\n    pyplot.title('128x128')\nexcept:\n    print \"Here come bad things!\"\n    # hands up if you want to see the error (uncomment next line)\n    #raise \n\n\n# Epic fail, right? If you let the code block above swap the axes, then resize the image, you will see this output:\n# \n# `Image shape after resize:  (224, 224, 128)`\n# \n# Now you have 128 where you should still have 4. Oops. Let's revert in the code block below and try something else. We'll show an example where the image is smaller than your Input specification, and not square. Like maybe it came from a new microscope that can only take imagery in rectangular bands.\n\n# In[11]:\n\n\nimgTiny = \"images/Cellsx128.png\"\nimgTiny = skimage.img_as_float(skimage.io.imread(imgTiny)).astype(np.float32)\nimgTinySlice = crop_center(imgTiny, 128, 56)\n# Plot original\npyplot.figure()\npyplot.subplot(2, 1, 1)\npyplot.imshow(imgTiny)\npyplot.axis('on')\npyplot.title('Original')\n# Plot slice\npyplot.figure()\npyplot.subplot(2, 2, 1)\npyplot.imshow(imgTinySlice)\npyplot.axis('on')\npyplot.title('128x56')\n# Upscale?\nprint \"Slice image shape: \", imgTinySlice.shape\nimgTiny224 = skimage.transform.resize(imgTinySlice, (224, 224))\nprint \"Upscaled slice image shape: \", imgTiny224.shape\n# Plot upscaled\npyplot.subplot(2, 2, 2)\npyplot.imshow(imgTiny224)\npyplot.axis('on')\npyplot.title('224x224')\n\n\n# Alright, this was a bit of a stretch for an example of how upscaling can fail. Get it? Stretch? This could be a life-or-death kind of failure though. What if normal cells are circular and diseased cells are elongated and bent? Sickle cell anemia for example: \n# ![sickle cells example](images/sickle-cells.jpg)\n# In this situation, what do you do? It really depends on the model and how it was trained. In some cases it may be ok to pad the rest of the image with white, or maybe black, or maybe noise, or maybe even use png and transparencies and set a mask for the images so the model ignores transparent areas. See how much fun you can have figuring this out and you get to make medical breakthroughs too!\n\n# Let's move on to the last step which we've already mentioned and that is to adjust the image input to be in BGR order. There's also another feature that Caffe2 uses, which is a `batch term`. We've already talked about CHW. This is the N, for number of images in NCHW.\n# \n# ### Final Preprocessing and the Batch Term\n# \n# In the last steps below we are going to switch the image's data order to BGR, stuff that into the Color column, then reoder the columns for GPU processing (HCW-->CHW) and then add a fourth dimension (N) to the image to track the number of images. In theory, you can just keep adding dimensions to your data, but this one is required for Caffe2 as it relays to Caffe how many images to expect in this batch. We set it to one (1) to indicate there's only one image going into Caffe in this batch. Note that in the final output when we check `img.shape` the order is quite different. We've added N for number of images, and changed the order like so: `N, C, H, W`\n\n# In[12]:\n\n\n# this next line helps with being able to rerun this section\n# if you want to try the outputs of the different crop strategies above\n# swap out imgScaled with img (original) or img256 (squeezed)\nimgCropped = crop_center(imgScaled,224,224)\nprint \"Image shape before HWC --> CHW conversion: \", imgCropped.shape\n# (1) Since Caffe expects CHW order and the current image is HWC,\n#     we will need to change the order.\nimgCropped = imgCropped.swapaxes(1, 2).swapaxes(0, 1)\nprint \"Image shape after HWC --> CHW conversion: \", imgCropped.shape\n\npyplot.figure()\nfor i in range(3):\n    # For some reason, pyplot subplot follows Matlab's indexing\n    # convention (starting with 1). Well, we'll just follow it...\n    pyplot.subplot(1, 3, i+1)\n    pyplot.imshow(imgCropped[i])\n    pyplot.axis('off')\n    pyplot.title('RGB channel %d' % (i+1))\n\n# (2) Caffe uses a BGR order due to legacy OpenCV issues, so we\n#     will change RGB to BGR.\nimgCropped = imgCropped[(2, 1, 0), :, :]\nprint \"Image shape after BGR conversion: \", imgCropped.shape\n# for discussion later - not helpful at this point\n# (3) We will subtract the mean image. Note that skimage loads\n#     image in the [0, 1] range so we multiply the pixel values\n#     first to get them into [0, 255].\n#mean_file = os.path.join(CAFFE_ROOT, 'python/caffe/imagenet/ilsvrc_2012_mean.npy')\n#mean = np.load(mean_file).mean(1).mean(1)\n#img = img * 255 - mean[:, np.newaxis, np.newaxis]\n\npyplot.figure()\nfor i in range(3):\n    # For some reason, pyplot subplot follows Matlab's indexing\n    # convention (starting with 1). Well, we'll just follow it...\n    pyplot.subplot(1, 3, i+1)\n    pyplot.imshow(imgCropped[i])\n    pyplot.axis('off')\n    pyplot.title('BGR channel %d' % (i+1))\n# (4) finally, since caffe2 expect the input to have a batch term\n#     so we can feed in multiple images, we will simply prepend a\n#     batch dimension of size 1. Also, we will make sure image is\n#     of type np.float32.\nimgCropped = imgCropped[np.newaxis, :, :, :].astype(np.float32)\nprint 'Final input shape is:', imgCropped.shape\n\n\n# In the output above you should note these alterations:\n# 1. Before and after of the HWC to CHW change. The 3, which is the number of color channels moved to the beginning.\n# 2. In the pictures above you can see that the color order was switched too. RGB became BGR. Blue and Red switched places.\n# 3. The final input shape, meaning the last change to the image was to add the batch field to the beginning, so that now you have (1, 3, 224, 224) for: \n#     - 1 image in the batch, \n#     - 3 color channels (in BGR), \n#     - 224 height, \n#     - 224 width.\n\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/Loading_Pretrained_Models.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# # Loading Pre-Trained Models\n# \n# ## Description\n# \n# In this tutorial, we will use the pre-trained `squeezenet` model from the [ModelZoo](https://github.com/caffe2/caffe2/wiki/Model-Zoo) to classify our own images. As input, we will provide the path (or URL) to an image we want to classify. It will also be helpful to know the [ImageNet object code](https://gist.githubusercontent.com/aaronmarkham/cd3a6b6ac071eca6f7b4a6e40e6038aa/raw/9edb4038a37da6b5a44c3b5bc52e448ff09bfe5b/alexnet_codes) for the image so we can verify our results. The 'object code' is nothing more than the integer label for the class used during training, for example \"985\" is the code for the class \"daisy\". Note, although we are using squeezenet here, this tutorial serves as a somewhat universal method for running inference on pretrained models.\n# \n# If you came from the [Image Pre-Processing Tutorial](https://caffe2.ai/docs/tutorial-image-pre-processing.html), you will see that we are using rescale and crop functions to prep the image, as well as reformatting the image to be CHW, BGR, and finally NCHW. We also correct for the image mean by either using the calculated mean from a provided npy file, or statically removing 128 as a placeholder average.\n# \n# Hopefully, you will find that loading pre-trained models is simple and syntactically concise. From a high level, these are the three required steps for running inference on a pretrained model:\n# \n# 1. Read the init and predict protobuf (.pb) files of the pretrained model\n# \n#         with open(\"init_net.pb\") as f:\n#             init_net = f.read()\n#         with open(\"predict_net.pb\") as f:\n#             predict_net = f.read()        \n# \n# 2. Initialize a Predictor in your workspace with the blobs from the protobufs\n# \n#         p = workspace.Predictor(init_net, predict_net)\n# \n# 3. Run the net on some data and get the (softmax) results!\n# \n#         results = p.run({'data': img})\n# \n# Note, assuming the last layer of the network is a softmax layer, the results come back as a multidimensional array of probabilities with length equal to the number of classes that the model was trained on. The probabilities may be indexed by the object code (integer type), so if you know the object code you can index the results array at that index to view the network's confidence that the input image is of that class.\n# \n# **Model Download Options**\n# \n# Although we will use `squeezenet` here, you can check out the [Model Zoo for pre-trained models](https://github.com/caffe2/caffe2/wiki/Model-Zoo) to browse/download a variety of pretrained models, or you can use Caffe2's `caffe2.python.models.download` module to easily acquire pre-trained models from [Github caffe2/models](http://github.com/caffe2/models). \n# \n# For our purposes, we will use the `models.download` module to download `squeezenet` into the `/caffe2/python/models` folder of our local Caffe2 installation with the following command:\n# \n# ```\n# python -m caffe2.python.models.download -i squeezenet\n# ```\n# \n# If the above download worked then you should have a directory named squeezenet in your `/caffe2/python/models` folder that contains `init_net.pb` and `predict_net.pb`. Note, if you do not use the `-i` flag, the model will be downloaded to your CWD, however it will still be a directory named squeezenet containing two protobuf files. Alternatively, if you wish to download all of the models, you can clone the entire repo using: \n# \n# ```\n# git clone https://github.com/caffe2/models\n# ```\n# \n# ## Code \n# \n# Before we start, lets take care of the required imports.\n\n# In[1]:\n\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.proto import caffe2_pb2\nimport numpy as np\nimport skimage.io\nimport skimage.transform\nfrom matplotlib import pyplot\nimport os\nfrom caffe2.python import core, workspace, models\nimport urllib2\nimport operator\nprint(\"Required modules imported.\")\n\n\n# ### Inputs\n# \n# Here, we will specify the inputs to be used for this run, including the input image, the model location, the mean file (optional), the required size of the image, and the location of the label mapping file.\n\n# In[2]:\n\n\n# Configuration --- Change to your setup and preferences!\n# This directory should contain the models downloaded from the model zoo. To run this \n#   tutorial, make sure there is a 'squeezenet' directory at this location that \n#   contains both the 'init_net.pb' and 'predict_net.pb'\nCAFFE_MODELS = \"~/caffe2/caffe2/python/models\"\n\n# Some sample images you can try, or use any URL to a regular image.\n# IMAGE_LOCATION = \"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Whole-Lemon.jpg/1235px-Whole-Lemon.jpg\"\n# IMAGE_LOCATION = \"https://upload.wikimedia.org/wikipedia/commons/7/7b/Orange-Whole-%26-Split.jpg\"\n# IMAGE_LOCATION = \"https://upload.wikimedia.org/wikipedia/commons/a/ac/Pretzel.jpg\"\n# IMAGE_LOCATION = \"https://cdn.pixabay.com/photo/2015/02/10/21/28/flower-631765_1280.jpg\"\nIMAGE_LOCATION = \"images/flower.jpg\"\n\n# What model are we using?\n#    Format below is the model's: <folder, INIT_NET, predict_net, mean, input image size>\n#    You can switch 'squeezenet' out with 'bvlc_alexnet', 'bvlc_googlenet' or others that you have downloaded\nMODEL = 'squeezenet', 'init_net.pb', 'predict_net.pb', 'ilsvrc_2012_mean.npy', 227\n\n# codes - these help decypher the output and source from a list from ImageNet's object codes \n#    to provide an result like \"tabby cat\" or \"lemon\" depending on what's in the picture \n#   you submit to the CNN.\ncodes =  \"https://gist.githubusercontent.com/aaronmarkham/cd3a6b6ac071eca6f7b4a6e40e6038aa/raw/9edb4038a37da6b5a44c3b5bc52e448ff09bfe5b/alexnet_codes\"\nprint(\"Config set!\")\n\n\n# ### Setup paths\n# \n# With the configs set, we can now load the mean file (if it exists), as well as the predict net and the init net.\n\n# In[3]:\n\n\n# set paths and variables from model choice and prep image\nCAFFE_MODELS = os.path.expanduser(CAFFE_MODELS)\n\n# mean can be 128 or custom based on the model\n# gives better results to remove the colors found in all of the training images\nMEAN_FILE = os.path.join(CAFFE_MODELS, MODEL[0], MODEL[3])\nif not os.path.exists(MEAN_FILE):\n    print(\"No mean file found!\")\n    mean = 128\nelse:\n    print (\"Mean file found!\")\n    mean = np.load(MEAN_FILE).mean(1).mean(1)\n    mean = mean[:, np.newaxis, np.newaxis]\nprint(\"mean was set to: \", mean)\n\n# some models were trained with different image sizes, this helps you calibrate your image\nINPUT_IMAGE_SIZE = MODEL[4]\n\n# make sure all of the files are around...\nINIT_NET = os.path.join(CAFFE_MODELS, MODEL[0], MODEL[1])\nPREDICT_NET = os.path.join(CAFFE_MODELS, MODEL[0], MODEL[2])\n\n# Check to see if the files exist\nif not os.path.exists(INIT_NET):\n    print(\"WARNING: \" + INIT_NET + \" not found!\")\nelse:\n    if not os.path.exists(PREDICT_NET):\n        print(\"WARNING: \" + PREDICT_NET + \" not found!\")\n    else:\n        print(\"All needed files found!\")\n        \n\n\n# ### Image Preprocessing\n# \n# Now that we have our inputs specified and verified the existance of the input network, we can load the image and pre-processing the image for ingestion into a Caffe2 convolutional neural network! This is a very important step as the trained CNN requires a specifically sized input image whose values are from a particular distribution.\n\n# In[4]:\n\n\n# Function to crop the center cropX x cropY pixels from the input image\ndef crop_center(img,cropx,cropy):\n    y,x,c = img.shape\n    startx = x//2-(cropx//2)\n    starty = y//2-(cropy//2)    \n    return img[starty:starty+cropy,startx:startx+cropx]\n\n# Function to rescale the input image to the desired height and/or width. This function will preserve\n#   the aspect ratio of the original image while making the image the correct scale so we can retrieve\n#   a good center crop. This function is best used with center crop to resize any size input images into\n#   specific sized images that our model can use.\ndef rescale(img, input_height, input_width):\n    # Get original aspect ratio\n    aspect = img.shape[1]/float(img.shape[0])\n    if(aspect>1):\n        # landscape orientation - wide image\n        res = int(aspect * input_height)\n        imgScaled = skimage.transform.resize(img, (input_width, res))\n    if(aspect<1):\n        # portrait orientation - tall image\n        res = int(input_width/aspect)\n        imgScaled = skimage.transform.resize(img, (res, input_height))\n    if(aspect == 1):\n        imgScaled = skimage.transform.resize(img, (input_width, input_height))\n    return imgScaled\n\n# Load the image as a 32-bit float\n#    Note: skimage.io.imread returns a HWC ordered RGB image of some size\nimg = skimage.img_as_float(skimage.io.imread(IMAGE_LOCATION)).astype(np.float32)\nprint(\"Original Image Shape: \" , img.shape)\n\n# Rescale the image to comply with our desired input size. This will not make the image 227x227\n#    but it will make either the height or width 227 so we can get the ideal center crop.\nimg = rescale(img, INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE)\nprint(\"Image Shape after rescaling: \" , img.shape)\npyplot.figure()\npyplot.imshow(img)\npyplot.title('Rescaled image')\n\n# Crop the center 227x227 pixels of the image so we can feed it to our model\nimg = crop_center(img, INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE)\nprint(\"Image Shape after cropping: \" , img.shape)\npyplot.figure()\npyplot.imshow(img)\npyplot.title('Center Cropped')\n\n# switch to CHW (HWC --> CHW)\nimg = img.swapaxes(1, 2).swapaxes(0, 1)\nprint(\"CHW Image Shape: \" , img.shape)\n\npyplot.figure()\nfor i in range(3):\n    # For some reason, pyplot subplot follows Matlab's indexing\n    # convention (starting with 1). Well, we'll just follow it...\n    pyplot.subplot(1, 3, i+1)\n    pyplot.imshow(img[i])\n    pyplot.axis('off')\n    pyplot.title('RGB channel %d' % (i+1))\n\n# switch to BGR (RGB --> BGR)\nimg = img[(2, 1, 0), :, :]\n\n# remove mean for better results\nimg = img * 255 - mean\n\n# add batch size axis which completes the formation of the NCHW shaped input that we want\nimg = img[np.newaxis, :, :, :].astype(np.float32)\n\nprint(\"NCHW image (ready to be used as input): \", img.shape)\n\n\n# ### Prepare the CNN and run the net!\n# \n# Now that the image is ready to be ingested by the CNN, let's open the protobufs, load them into the workspace, and run the net. \n# \n\n# In[5]:\n\n\n# Read the contents of the input protobufs into local variables\nwith open(INIT_NET) as f:\n    init_net = f.read()\nwith open(PREDICT_NET) as f:\n    predict_net = f.read()\n\n# Initialize the predictor from the input protobufs\np = workspace.Predictor(init_net, predict_net)\n\n# Run the net and return prediction\nresults = p.run({'data': img})\n\n# Turn it into something we can play with and examine which is in a multi-dimensional array\nresults = np.asarray(results)\nprint(\"results shape: \", results.shape)\n\n# Quick way to get the top-1 prediction result\n# Squeeze out the unnecessary axis. This returns a 1-D array of length 1000\npreds = np.squeeze(results)\n# Get the prediction and the confidence by finding the maximum value and index of maximum value in preds array\ncurr_pred, curr_conf = max(enumerate(preds), key=operator.itemgetter(1))\nprint(\"Prediction: \", curr_pred)\nprint(\"Confidence: \", curr_conf)\n\n\n# ### Process Results\n# \n# Recall ImageNet is a 1000 class dataset and observe that it is no coincidence that the third axis of results is length 1000. This axis is holding the probability for each category in the pre-trained model. So when you look at the results array at a specific index, the number can be interpreted as the probability that the input belongs to the class corresponding to that index. Now that we have run the predictor and collected the results, we can interpret them by matching them to their corresponding english labels.\n# \n\n# In[6]:\n\n\n# the rest of this is digging through the results \nresults = np.delete(results, 1)\nindex = 0\nhighest = 0\narr = np.empty((0,2), dtype=object)\narr[:,0] = int(10)\narr[:,1:] = float(10)\nfor i, r in enumerate(results):\n    # imagenet index begins with 1!\n    i=i+1\n    arr = np.append(arr, np.array([[i,r]]), axis=0)\n    if (r > highest):\n        highest = r\n        index = i \n\n# top N results\nN = 5\ntopN = sorted(arr, key=lambda x: x[1], reverse=True)[:N]\nprint(\"Raw top {} results: {}\".format(N,topN))\n\n# Isolate the indexes of the top-N most likely classes\ntopN_inds = [int(x[0]) for x in topN]\nprint(\"Top {} classes in order: {}\".format(N,topN_inds))\n\n# Now we can grab the code list and create a class Look Up Table\nresponse = urllib2.urlopen(codes)\nclass_LUT = []\nfor line in response:\n    code, result = line.partition(\":\")[::2]\n    code = code.strip()\n    result = result.replace(\"'\", \"\")\n    if code.isdigit():\n        class_LUT.append(result.split(\",\")[0][1:])\n        \n# For each of the top-N results, associate the integer result with an actual class\nfor n in topN:\n    print(\"Model predicts '{}' with {}% confidence\".format(class_LUT[int(n[0])],float(\"{0:.2f}\".format(n[1]*100))))\n\n\n# ### Feeding Larger Batches\n# \n# Above is an example of how to feed one image at a time. We can achieve higher throughput if we feed multiple images at a time in a single batch. Recall, the data fed into the classifier is in 'NCHW' order, so to feed multiple images, we will expand the 'N' axis.\n\n# In[7]:\n\n\n# List of input images to be fed\nimages = [\"images/cowboy-hat.jpg\",\n            \"images/cell-tower.jpg\",\n            \"images/Ducreux.jpg\",\n            \"images/pretzel.jpg\",\n            \"images/orangutan.jpg\",\n            \"images/aircraft-carrier.jpg\",\n            \"images/cat.jpg\"]\n\n# Allocate space for the batch of formatted images\nNCHW_batch = np.zeros((len(images),3,227,227))\nprint (\"Batch Shape: \",NCHW_batch.shape)\n\n# For each of the images in the list, format it and place it in the batch\nfor i,curr_img in enumerate(images):\n    img = skimage.img_as_float(skimage.io.imread(curr_img)).astype(np.float32)\n    img = rescale(img, 227, 227)\n    img = crop_center(img, 227, 227)\n    img = img.swapaxes(1, 2).swapaxes(0, 1)\n    img = img[(2, 1, 0), :, :]\n    img = img * 255 - mean\n    NCHW_batch[i] = img\n\nprint(\"NCHW image (ready to be used as input): \", NCHW_batch.shape)\n\n# Run the net on the batch\nresults = p.run([NCHW_batch.astype(np.float32)])\n\n# Turn it into something we can play with and examine which is in a multi-dimensional array\nresults = np.asarray(results)\n\n# Squeeze out the unnecessary axis\npreds = np.squeeze(results)\nprint(\"Squeezed Predictions Shape, with batch size {}: {}\".format(len(images),preds.shape))\n\n# Describe the results\nfor i,pred in enumerate(preds):\n    print(\"Results for: '{}'\".format(images[i]))\n    # Get the prediction and the confidence by finding the maximum value \n    #   and index of maximum value in preds array\n    curr_pred, curr_conf = max(enumerate(pred), key=operator.itemgetter(1))\n    print(\"\\tPrediction: \", curr_pred)\n    print(\"\\tClass Name: \", class_LUT[int(curr_pred)])\n    print(\"\\tConfidence: \", curr_conf)\n\n\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/MNIST.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# # MNIST\n# \n# In this tutorial, we will show you how to train a small convolutional neural network (a CNN) model. We will be training the model on the MNIST dataset, which consists of labeled handwritten digits. Each sample is a 28x28 picture of a handwritten single digit and the label is a digit from 0 to 9.\n# \n# We will be constructing the [LeNet model](http://yann.lecun.com/exdb/lenet/) with the sigmoid activations replaced with [ReLUs](http://www.cs.toronto.edu/~fritz/absps/reluICML.pdf). A flag below will allow us to toggle between the LeNet model and a simple MLP (multilayer perceptron) architectures.\n# \n# We will be using ModelHelper - the class that helps us deal with parameter initialization naturally.\n# \n# First, let's import the necessities.\n\n# In[1]:\n\n\nfrom matplotlib import pyplot\nimport numpy as np\nimport os\nimport shutil\nimport caffe2.python.predictor.predictor_exporter as pe\n\n\nfrom caffe2.python import (\n    brew,\n    core,\n    model_helper,\n    net_drawer,\n    optimizer,\n    visualize,\n    workspace,\n)\n\n# If you would like to see some really detailed initializations,\n# you can change --caffe2_log_level=0 to --caffe2_log_level=-1\ncore.GlobalInit(['caffe2', '--caffe2_log_level=0'])\nprint(\"Necessities imported!\")\n\n# If True, a more complicated convolutional model is used\n# If False, a multilayer perceptron model is used\nUSE_LENET_MODEL = True\n\n\n# We will track statistics during the training time and store these on disk in a local folder. We need to set up a data folder for the data and a root folder for the stats. You should already have these folders, and in the data folder the MNIST dataset should be setup as a lmdb database for both the training set and the test set for this tutorial. \n\n# In[2]:\n\n\n# This section preps your image and test set in a lmdb database\ndef DownloadResource(url, path):\n    '''Downloads resources from s3 by url and unzips them to the provided path'''\n    import requests, zipfile, StringIO\n    print(\"Downloading... {} to {}\".format(url, path))\n    r = requests.get(url, stream=True)\n    z = zipfile.ZipFile(StringIO.StringIO(r.content))\n    z.extractall(path)\n    print(\"Completed download and extraction.\")\n    \n    \ncurrent_folder = os.path.join(os.path.expanduser('~'), 'caffe2_notebooks')\ndata_folder = os.path.join(current_folder, 'tutorial_data', 'mnist')\nroot_folder = os.path.join(current_folder, 'tutorial_files', 'tutorial_mnist')\ndb_missing = False\n\nif not os.path.exists(data_folder):\n    os.makedirs(data_folder)   \n    print(\"Your data folder was not found!! This was generated: {}\".format(data_folder))\n\n# Look for existing database: lmdb\nif os.path.exists(os.path.join(data_folder,\"mnist-train-nchw-lmdb\")):\n    print(\"lmdb train db found!\")\nelse:\n    db_missing = True\n    \nif os.path.exists(os.path.join(data_folder,\"mnist-test-nchw-lmdb\")):\n    print(\"lmdb test db found!\")\nelse:\n    db_missing = True\n\n# attempt the download of the db if either was missing\nif db_missing:\n    print(\"one or both of the MNIST lmbd dbs not found!!\")\n    db_url = \"http://download.caffe2.ai/databases/mnist-lmdb.zip\"\n    try:\n        DownloadResource(db_url, data_folder)\n    except Exception as ex:\n        print(\"Failed to download dataset. Please download it manually from {}\".format(db_url))\n        print(\"Unzip it and place the two database folders here: {}\".format(data_folder))\n        raise ex\n\nif os.path.exists(root_folder):\n    print(\"Looks like you ran this before, so we need to cleanup those old files...\")\n    shutil.rmtree(root_folder)\n    \nos.makedirs(root_folder)\nworkspace.ResetWorkspace(root_folder)\n\nprint(\"training data folder:\" + data_folder)\nprint(\"workspace root folder:\" + root_folder)\n\n\n# > If the database wasn't found in the last step, [download the MNIST lmdb database](https://download.caffe2.ai/databases/mnist-lmdb.zip) or review the [datasets and databases notebook](https://github.com/caffe2/caffe2/blob/master/caffe2/python/tutorials/MNIST_Dataset_and_Databases.ipynb) on how to create the database from the MNIST dataset.\n\n# We will be using the `ModelHelper` class to represent our main model and using `brew` module as well as normal Caffe2 operators to build our model. `ModelHelper` is a special class which stores a lot of information about parameters initialization, their names and later on mapping to gradients. We will see how it is used in `brew` and other places below.\n# \n# model.MyOperator is a syntactic sugar for model.net.MyOperator, which adds the corresponding MyOperator operator to model.net.\n# \n# `brew` is a collection of helper functions designed to simplify the addition of complex logic to our models. When we want to add parameter initialization as well as a computation step, for example, `brew` comes in handy. Lets explore this in more detail.\n# \n# `brew` module has a set of wrapper functions that automatically separate the parameter intialization and the actual computation into two networks. Under the hood, a `ModelHelper` object has two underlying nets, `param_init_net` and `net`, that keep record of the initialization network and the main network respectively. Also model.params keeps track of parameter names.\n# \n# For the sake of modularity, we will separate the model to multiple different parts:\n# \n#     (1) The data input part (AddInput function)\n#     (2) The main computation part (AddModel function)\n#     (3) The training part - adding gradient operators, update, etc. (AddTrainingOperators function)\n#     (4) The bookkeeping part, where we just print out statistics for inspection. (AddBookkeepingOperators function)\n#     \n# `AddInput` will load the data from a DB. We store MNIST data in pixel values, so after batching this will give us data with shape `(batch_size, num_channels, width, height)`, in this case `[batch_size, 1, 28, 28]` of data type *uint8* and a label with shape `[batch_size]` of data type *int*.\n#     \n# Since we are going to do float computations, we will cast the data to the *float* data type.\n# For better numerical stability, instead of representing data in [0, 255] range, we will scale them down to [0, 1].\n# Note that we are doing in-place computation for this operator: we don't need the pre-scale data.\n# Now, when computing the backward pass, we will not need the gradient computation for the data preparation part. `StopGradient` does exactly that: in the forward pass it does nothing and in the backward pass all it does is to tell the gradient generator \"the gradient does not need to pass through here\".\n#     \n\n# In[3]:\n\n\ndef AddInput(model, batch_size, db, db_type):\n    # load the data\n    data_uint8, label = brew.db_input(\n        model,\n        blobs_out=[\"data_uint8\", \"label\"],\n        batch_size=batch_size,\n        db=db,\n        db_type=db_type,\n    )\n    # cast the data to float\n    data = model.Cast(data_uint8, \"data\", to=core.DataType.FLOAT)\n    # scale data from [0,255] down to [0,1]\n    data = model.Scale(data, data, scale=float(1./256))\n    # don't need the gradient for the backward pass\n    data = model.StopGradient(data, data)\n    return data, label\n\n\n# Now we are going to construct our own model. The input will be our data blob, and the output will be vectors of length 10 containing the network's prediction on each of the 10 possible digits.\n# \n# We are going to use the Multilayer Perceptron (MLP) architecture. The ReLU activation function is going to be used:\n# \n# Relu(x) = x if x > 0 else 0\n# \n# Each layer of an MLP is just matrix multiplication with a bias plus an activation function. In our case, with ReLU activation, that is:\n# \n# layer1 = Relu(X * W1^T + b1)\n# \n# layer2 = Relu(layer1 * W2^T + b2)\n# \n# ...\n# \n# Ultimately we will use the Softmax operator to convert scores for each of the digits to probabilities. So (p_0 + ... + p_9) = 1.0 and 0 <= p_i <= 1.0. \n# \n# There are more detailed MLP explanations online. A good example is [here](http://deeplearning.net/tutorial/mlp.html).\n# \n# In this function we are going to use Brew for the second time. Please refer to the explanation given above. When below we call brew.fc(model, layer, ...) under the hood the following happens. FC operator is going to be added to model.net by calling model.net.FC([layer, W, b], ...). Where W and b are the weight and the bias of this fully connected layer (output = layer * W^T + b). Initially, we get W and b by adding their initialization into model.param_init_net. All of these is happening under the hood. You could just use Brew! :) \n\n# In[4]:\n\n\ndef AddMLPModel(model, data):\n    size = 28 * 28 * 1\n    sizes = [size, size * 2, size * 2, 10]\n    layer = data\n    for i in range(len(sizes) - 1):\n        layer = brew.fc(model, layer, 'dense_{}'.format(i), dim_in=sizes[i], dim_out=sizes[i + 1])\n        layer = model.net.Relu(layer, 'relu_{}'.format(i))\n    softmax = model.net.Softmax(layer, 'softmax')\n    return softmax\n    \n\n\n# Below is another possible (and much better) architecture called LeNet. This section is optional and you could run and use this tutorial for MLPs without understanding this function.\n# \n# It uses convolutional layers. To understand convolution first you could look into [an explanation of kernels in image processing](https://en.wikipedia.org/wiki/Kernel_%28image_processing%29) \n# \n# The next step would be to understand convolutions in machine learning. There are also a lot of great resources online. Such as [this one](http://deeplearning.net/software/theano_versions/dev/tutorial/conv_arithmetic.html)\n# \n# This function is also using Brew, this time for adding convolutional layers as well as fully connected ones.\n\n# In[5]:\n\n\ndef AddLeNetModel(model, data):\n    '''\n    This part is the standard LeNet model: from data to the softmax prediction.\n    \n    For each convolutional layer we specify dim_in - number of input channels\n    and dim_out - number or output channels. Also each Conv and MaxPool layer changes the\n    image size. For example, kernel of size 5 reduces each side of an image by 4.\n\n    While when we have kernel and stride sizes equal 2 in a MaxPool layer, it divides\n    each side in half.\n    '''\n    # Image size: 28 x 28 -> 24 x 24\n    conv1 = brew.conv(model, data, 'conv1', dim_in=1, dim_out=20, kernel=5)\n    # Image size: 24 x 24 -> 12 x 12\n    pool1 = model.net.MaxPool(conv1, 'pool1', kernel=2, stride=2)\n    # Image size: 12 x 12 -> 8 x 8\n    conv2 = brew.conv(model, pool1, 'conv2', dim_in=20, dim_out=50, kernel=5)\n    # Image size: 8 x 8 -> 4 x 4\n    pool2 = model.net.MaxPool(conv2, 'pool2', kernel=2, stride=2)\n    # 50 * 4 * 4 stands for dim_out from previous layer multiplied by the image size\n    fc3 = brew.fc(model, pool2, 'fc3', dim_in=50 * 4 * 4, dim_out=500)\n    fc3 = model.net.Relu(fc3, 'relu3')\n    pred = brew.fc(model, fc3, 'pred', 500, 10)\n    softmax = model.net.Softmax(pred, 'softmax')\n    return softmax\n\n\n# The `AddModel` function below allows us to easily switch from MLP to LeNet model. Just change `USE_LENET_MODEL` at the very top of the notebook and rerun the whole thing.\n\n# In[6]:\n\n\ndef AddModel(model, data):\n    if USE_LENET_MODEL:\n        return AddLeNetModel(model, data)\n    else:\n        return AddMLPModel(model, data)\n\n\n# `AddAccuracy` function below adds an accuracy operator to the model. It is not going to be used in training. But will allow us to track accuracy of the model during training and build a nice plot.\n\n# In[7]:\n\n\ndef AddAccuracy(model, softmax, label):\n    \"\"\"Adds an accuracy op to the model\"\"\"\n    accuracy = model.Accuracy([softmax, label], \"accuracy\")\n    return accuracy\n\n\n# The next function, `AddTrainingOperators`, adds training operators to the model. Please follow inline comments to understand all of the steps. We are going to use `build_sgd` helper function here. You can also build the whole update process yourself. The model object contains all the required information such as parameter names (`model.param`) and a mapping from parameter names to corresponding gradients.\n\n# In[8]:\n\n\ndef AddTrainingOperators(model, softmax, label):\n    \"\"\"Adds training operators to the model.\"\"\"\n    xent = model.LabelCrossEntropy([softmax, label], 'xent')\n    # compute the expected loss\n    loss = model.AveragedLoss(xent, \"loss\")\n    # track the accuracy of the model\n    AddAccuracy(model, softmax, label)\n    # use the average loss we just computed to add gradient operators to the model\n    model.AddGradientOperators([loss])\n    optimizer.build_sgd(\n        model,\n        base_learning_rate=0.1,\n        policy=\"step\",\n        stepsize=1,\n        gamma=0.999,\n    )\n\n\n# The following function, `AddBookkeepingOperations`, adds a few bookkeeping operators that we can inspect later. These operators do not affect the training procedure: they only collect statistics and prints them to file or to logs.\n\n# In[9]:\n\n\ndef AddBookkeepingOperators(model):\n    \"\"\"This adds a few bookkeeping operators that we can inspect later.\n    \n    These operators do not affect the training procedure: they only collect\n    statistics and prints them to file or to logs.\n    \"\"\"    \n    # Print basically prints out the content of the blob. to_file=1 routes the\n    # printed output to a file. The file is going to be stored under\n    #     root_folder/[blob name]\n    model.Print('accuracy', [], to_file=1)\n    model.Print('loss', [], to_file=1)\n    # Summarizes the parameters. Different from Print, Summarize gives some\n    # statistics of the parameter, such as mean, std, min and max.\n    for param in model.params:\n        model.Summarize(param, [], to_file=1)\n        model.Summarize(model.param_to_grad[param], [], to_file=1)\n    # Now, if we really want to be verbose, we can summarize EVERY blob\n    # that the model produces; it is probably not a good idea, because that\n    # is going to take time - summarization do not come for free. For this\n    # demo, we will only show how to summarize the parameters and their\n    # gradients.\n\n\n# Now, let's actually create the models for training and testing. If you are seeing WARNING messages below, don't be alarmed. The functions we established earlier are now going to be executed. Remember the four steps that we're doing:\n# \n#     (1) data input  \n#     (2) main computation\n#     (3) training \n#     (4) bookkeeping\n#     \n# Before we can do the data input though we need to define our training model. We will basically need every piece of the components we defined above. In this example, we're using NCHW storage order on the mnist_train dataset. \n\n# In[10]:\n\n\narg_scope = {\"order\": \"NCHW\"}\ntrain_model = model_helper.ModelHelper(name=\"mnist_train\", arg_scope=arg_scope)\ndata, label = AddInput(\n    train_model, batch_size=64,\n    db=os.path.join(data_folder, 'mnist-train-nchw-lmdb'),\n    db_type='lmdb')\nsoftmax = AddModel(train_model, data)\nAddTrainingOperators(train_model, softmax, label)\nAddBookkeepingOperators(train_model)\n\n# Testing model. We will set the batch size to 100, so that the testing\n# pass is 100 iterations (10,000 images in total).\n# For the testing model, we need the data input part, the main AddModel\n# part, and an accuracy part. Note that init_params is set False because\n# we will be using the parameters obtained from the train model.\ntest_model = model_helper.ModelHelper(\n    name=\"mnist_test\", arg_scope=arg_scope, init_params=False)\ndata, label = AddInput(\n    test_model, batch_size=100,\n    db=os.path.join(data_folder, 'mnist-test-nchw-lmdb'),\n    db_type='lmdb')\nsoftmax = AddModel(test_model, data)\nAddAccuracy(test_model, softmax, label)\n\n# Deployment model. We simply need the main AddModel part.\ndeploy_model = model_helper.ModelHelper(\n    name=\"mnist_deploy\", arg_scope=arg_scope, init_params=False)\nAddModel(deploy_model, \"data\")\n# You may wonder what happens with the param_init_net part of the deploy_model.\n# No, we will not use them, since during deployment time we will not randomly\n# initialize the parameters, but load the parameters from the db.\n\n\n# Now, let's take a look what the training and deploy models look like using the simple graph visualization tool that Caffe2 has. If the following command fails for you, it might be because your machine does not have graphviz installed. You'll need to install it through the package manager of your choice.\n# \n# If the graph looks too small, right click and open the image in a new tab for better inspection.\n\n# In[11]:\n\n\nfrom IPython import display\ngraph = net_drawer.GetPydotGraph(train_model.net.Proto().op, \"mnist\", rankdir=\"LR\")\ndisplay.Image(graph.create_png(), width=800)\n\n\n# Now, the graph above shows everything that is happening in the training phase: the white nodes are the blobs, and the green rectangular nodes are the operators being run. You may have noticed the massive parallel lines like train tracks: these are dependencies from the blobs generated in the forward pass to their backward operators.\n# \n# Let's display the graph in a more minimal way by showing only the necessary dependencies and only showing the operators. If you read carefully, you can see that the left half of the graph is the forward pass, the right half of the graph is the backward pass, and on the very right there are a set of parameter update and summarization operators.\n\n# In[12]:\n\n\ngraph = net_drawer.GetPydotGraphMinimal(\n    train_model.net.Proto().op, \"mnist\", rankdir=\"LR\", minimal_dependency=True)\ndisplay.Image(graph.create_png(), width=800)\n\n\n# Now, when we run the network, one way is to directly run it from Python. Remember as we are running the network, we can periodically pull blobs from the network - Let's first show how we do this.\n# \n# Before that, though, let's reiterate the fact that the ModelHelper class has not executed anything yet. All it does is declare the network, which is basically creating the protocol buffers. For example, we will show a portion of the serialized protocol buffer for the training model's main network and the parameter initialization network.\n\n# In[13]:\n\n\nprint(str(train_model.net.Proto())[:400] + '\\n...')\nprint(str(train_model.param_init_net.Proto())[:400] + '\\n...')\n\n\n# Next we will run the training procedure. Please note that this process will take a while to run. Keep an eye on the asterisk (In [\\*]) or other IPython indicators that the code block is still running.\n# \n# We perform training by just executing our network many times in a row. Note how during this process we can fetch values of any blobs in the workspace. This allows us to build training plots. \n# \n# When using MLP, model accuracy greatly depends on the random initialization of parameters. If your model is staying at about 50% accurate, re-run the notebook, which will start from another random seed and initialize the parameters again.\n\n# In[14]:\n\n\n# The parameter initialization network only needs to be run once.\n# Now all the parameter blobs are going to be initialized in the workspace.\nworkspace.RunNetOnce(train_model.param_init_net)\n\n# Creating an actual network as a C++ object in memory.\n# We need this as its going to be used a lot.\n# So we avoid an object every single time it is used.\n \n# overwrite=True allows you to run this cell several times and avoid errors\nworkspace.CreateNet(train_model.net, overwrite=True)\n\n# Set the iterations number and track the accuracy & loss\ntotal_iters = 200\naccuracy = np.zeros(total_iters)\nloss = np.zeros(total_iters)\n\n# Now, we will manually run the network for 200 iterations. \nfor i in range(total_iters):\n    workspace.RunNet(train_model.net)\n    accuracy[i] = workspace.blobs['accuracy']\n    loss[i] = workspace.blobs['loss']\n\n# After the execution is done, let's plot the values.\npyplot.plot(loss, 'b')\npyplot.plot(accuracy, 'r')\npyplot.legend(('Loss', 'Accuracy'), loc='upper right')\n\n\n# Now we can sample some of the data and predictions. \n\n# In[15]:\n\n\n# Let's look at some of the data.\npyplot.figure()\ndata = workspace.FetchBlob('data')\n_ = visualize.NCHW.ShowMultiple(data)\npyplot.figure()\nsoftmax = workspace.FetchBlob('softmax')\n_ = pyplot.plot(softmax[0], 'ro')\npyplot.title('Prediction for the first image')\n\n\n# For convolutional models we can also see how they \"think\", i.e. which features they come up with. Instead of fetching learned weights, which can make less sense to a human, we fetch results of convolving those weights over the input. Note that if this code is rerun after the evaluation phase, the last mini-batch will change, since evaluation and training share the same workspace.\n\n# In[16]:\n\n\nif USE_LENET_MODEL:\n    pyplot.figure()\n    # We look into the first conv layer output. Change this to conv2 in order to look into the second one. \n    conv = workspace.FetchBlob('conv1')\n    \n    # We can look into any channel. Think of it as a feature model learned.\n    # In this case we look into the 5th channel. Play with other channels to see other features\n    conv = conv[:,[5],:,:]\n\n    _ = visualize.NCHW.ShowMultiple(conv)\n\n\n# Remember that we created the test net? We will run the test pass and report the test accuracy here. Note that although test_model will be using the parameters obtained from train_model, test_model.param_init_net must still be run to initialize the input data.\n# In this run, we only need to track the accuracy and we're also only going to run 100 iterations.\n\n# In[17]:\n\n\n# param_init_net here will only create a data reader\n# Other parameters won't be re-created because we selected init_params=False before\nworkspace.RunNetOnce(test_model.param_init_net)\nworkspace.CreateNet(test_model.net, overwrite=True)\ntest_accuracy = np.zeros(100)\nfor i in range(100):\n    workspace.RunNet(test_model.net.Proto().name)\n    test_accuracy[i] = workspace.FetchBlob('accuracy')\n# After the execution is done, let's plot the values.\npyplot.plot(test_accuracy, 'r')\npyplot.title('Acuracy over test batches.')\nprint('test_accuracy: %f' % test_accuracy.mean())\n\n\n# Let's save the deploy model with the trained weights and biases to a file. \n\n# In[18]:\n\n\n# construct the model to be exported\n# the inputs/outputs of the model are manually specified.\npe_meta = pe.PredictorExportMeta(\n    predict_net=deploy_model.net.Proto(),\n    parameters=[str(b) for b in deploy_model.params], \n    inputs=[\"data\"],\n    outputs=[\"softmax\"],\n)\n\n# save the model to a file. Use minidb as the file format\npe.save_to_db(\"minidb\", os.path.join(root_folder, \"mnist_model.minidb\"), pe_meta)\nprint(\"The deploy model is saved to: \" + root_folder + \"/mnist_model.minidb\")\n\n\n# Now we can load the model back and run the prediction to verify it works.\n\n# In[19]:\n\n\n# we retrieve the last input data out and use it in our prediction test before we scratch the workspace\nblob = workspace.FetchBlob(\"data\")\npyplot.figure()\n_ = visualize.NCHW.ShowMultiple(blob)\n\n# reset the workspace, to make sure the model is actually loaded\nworkspace.ResetWorkspace(root_folder)\n\n# verify that all blobs are destroyed. \nprint(\"The blobs in the workspace after reset: {}\".format(workspace.Blobs()))\n\n# load the predict net\npredict_net = pe.prepare_prediction_net(os.path.join(root_folder, \"mnist_model.minidb\"), \"minidb\")\n\n# verify that blobs are loaded back\nprint(\"The blobs in the workspace after loading the model: {}\".format(workspace.Blobs()))\n\n# feed the previously saved data to the loaded model\nworkspace.FeedBlob(\"data\", blob)\n\n# predict\nworkspace.RunNetOnce(predict_net)\nsoftmax = workspace.FetchBlob(\"softmax\")\n\n# the first letter should be predicted correctly\npyplot.figure()\n_ = pyplot.plot(softmax[0], 'ro')\npyplot.title('Prediction for the first image')\n\n\n# This concludes the MNIST tutorial. We hope this tutorial highlighted some of Caffe2's features and how easy it is to create a simple MLP or CNN model.\n\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/MNIST_Dataset_and_Databases.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# # MNIST Dataset & Database\n# \n# In the [MNIST tutorial](https://github.com/caffe2/caffe2/blob/master/caffe2/python/tutorials/MNIST.ipynb) we use a lmdb database. You can also use leveldb or even minidb by changing the type reference when you get ready to read from the db's.\n# \n# \n# ## Dataset:\n# \n# You can download the raw [MNIST dataset](https://download.caffe2.ai/datasets/mnist/mnist.zip), g/unzip the dataset and labels, and make the database yourself. \n# \n# \n# ## Databases:\n# \n# We provide a few database formats for you to try with the MNIST tutorial. The default is lmdb. \n# \n# * [MNIST-nchw-lmdb](https://download.caffe2.ai/databases/mnist-lmdb.zip) - contains both the train and test lmdb MNIST databases in NCHW format\n# * [MNIST-nchw-leveldb](https://download.caffe2.ai/databases/mnist-leveldb.zip) - contains both the train and test leveldb MNIST databases in NCHW format\n# * [MNIST-nchw-minidb](https://download.caffe2.ai/databases/mnist-minidb.zip) - contains both the train and test minidb MNIST databases in NCHW format\n# \n# \n# ## Tools:\n# \n# ### make_mnist_db\n# \n# If you like LevelDB you can use Caffe2's `make_mnist_db` binary to generate leveldb databases. This binary is found in `/caffe2/build/caffe2/binaries/` or depending on your OS and installation, in `/usr/local/bin/`.\n# \n# Here is an example call to `make_mnist_db`:\n# \n# ```\n# ./make_mnist_db --channel_first --db leveldb --image_file ~/Downloads/train-images-idx3-ubyte --label_file ~/Downloads/train-labels-idx1-ubyte --output_file ~/caffe2/caffe2/python/tutorials/tutorial_data/mnist/mnist-train-nchw-leveldb\n# \n# ./make_mnist_db --channel_first --db leveldb --image_file ~/Downloads/t10k-images-idx3-ubyte --label_file ~/Downloads/t10k-labels-idx1-ubyte --output_file ~/caffe2/caffe2/python/tutorials/tutorial_data/mnist/mnist-test-nchw-leveldb\n# ```\n# Note leveldb can get deadlocked if more than one user attempts to open the leveldb at the same time. This is why there is logic in the Python below to delete LOCK files if they're found.\n# \n# TODO: it would be great to extend this binary to create other database formats \n# \n# ### Python script\n# \n# You can use the Python in the code block below to download the dataset with `DownloadResource`, call the `make_mnist_db` binary, and generate your database with `GenerateDB`. \n# \n# The `DownloadResource` function can also download and extract a database for you.\n# \n# \n# **Downloads and extracts the MNIST dataset**\n# The sample function below will download and extract the dataset for you.\n# ```python\n# DownloadResource(\"http://download.caffe2.ai/datasets/mnist/mnist.zip\", data_folder)\n# ```\n# \n# **Downloads and extracts the lmdb databases of MNIST images - both test and train**\n# ```python\n# DownloadResource(\"http://download.caffe2.ai/databases/mnist-lmdb.zip\", data_folder)\n# ```\n# \n# **(Re)generate the leveldb database (it can get locked with multi-user setups or abandoned threads)**\n# Requires the download of the dataset (mnist.zip) - see above.\n# \n# ```python\n# GenerateDB(image_file_train, label_file_train, \"mnist-train-nchw-leveldb\")\n# GenerateDB(image_file_test, label_file_test, \"mnist-test-nchw-leveldb\")\n# ```\n\n# In[ ]:\n\n\nimport os\n\ndef DownloadResource(url, path):\n    '''Downloads resources from s3 by url and unzips them to the provided path'''\n    import requests, zipfile, StringIO\n    print(\"Downloading... {} to {}\".format(url, path))\n    r = requests.get(url, stream=True)\n    z = zipfile.ZipFile(StringIO.StringIO(r.content))\n    z.extractall(path)\n    print(\"Completed download and extraction.\")\n\n    \ndef GenerateDB(image, label, name):\n    '''Calls the make_mnist_db binary to generate a leveldb from a mnist dataset'''\n    name = os.path.join(data_folder, name)\n    print 'DB: ', name\n    if not os.path.exists(name):\n        syscall = \"/usr/local/bin/make_mnist_db --channel_first --db leveldb --image_file \" + image + \" --label_file \" + label + \" --output_file \" + name\n        # print \"Creating database with: \", syscall\n        os.system(syscall)\n    else:\n        print \"Database exists already. Delete the folder if you have issues/corrupted DB, then rerun this.\"\n        if os.path.exists(os.path.join(name, \"LOCK\")):\n            # print \"Deleting the pre-existing lock file\"\n            os.remove(os.path.join(name, \"LOCK\"))\n\n            \ncurrent_folder = os.path.join(os.path.expanduser('~'), 'caffe2_notebooks')\ndata_folder = os.path.join(current_folder, 'tutorial_data', 'mnist')\n\n# Downloads and extracts the lmdb databases of MNIST images - both test and train\nif not os.path.exists(os.path.join(data_folder,\"mnist-train-nchw-lmdb\")):\n    DownloadResource(\"http://download.caffe2.ai/databases/mnist-lmdb.zip\", data_folder)\n\n# Downloads and extracts the MNIST data set\nif not os.path.exists(os.path.join(data_folder, \"train-images-idx3-ubyte\")):\n    DownloadResource(\"http://download.caffe2.ai/datasets/mnist/mnist.zip\", data_folder)\n\n# (Re)generate the leveldb database (it can get locked with multi-user setups or abandoned threads)\n# Requires the download of the dataset (mnist.zip) - see DownloadResource above.\n# You also need to change references in the MNIST tutorial code where you train or test from lmdb to leveldb\nimage_file_train = os.path.join(data_folder, \"train-images-idx3-ubyte\")\nlabel_file_train = os.path.join(data_folder, \"train-labels-idx1-ubyte\")\nimage_file_test = os.path.join(data_folder, \"t10k-images-idx3-ubyte\")\nlabel_file_test = os.path.join(data_folder, \"t10k-labels-idx1-ubyte\")\nGenerateDB(image_file_train, label_file_train, \"mnist-train-nchw-leveldb\")\nGenerateDB(image_file_test, label_file_test, \"mnist-test-nchw-leveldb\")\n\n\n# ## Code Changes for Other DBs\n# \n# If you chose to use a format other than lmdb you will need to change a couple lines of code. When you use `ModelHelper` to instantiate the CNN, you pass in the `db` parameter with a path and the `db_type` with the type of db. You would need to update both of these values. Since you create two networks, one for training and one for testing, you would need to update the code for both of these.\n# \n# **Default code using lmdb**\n# ```python\n# train_model = model_helper.ModelHelper(name=\"mnist_train\", arg_scope=arg_scope)\n# data, label = AddInput(\n#     train_model, batch_size=64,\n#     db=os.path.join(data_folder, 'mnist-train-nchw-lmdb'),\n#     db_type='lmdb')\n# ```\n# \n# **Updated code using leveldb**\n# ```python\n# train_model = model_helper.ModelHelper(name=\"mnist_train\", arg_scope=arg_scope)\n# data, label = AddInput(\n#     train_model, batch_size=64,\n#     db=os.path.join(data_folder, 'mnist-train-nchw-leveldb'),\n#     db_type='leveldb')\n# ```\n\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/Model_Quickload.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# # Model Quickload\n# \n# This short notebook will show you how you can very quickly load and test SqueezeNet, which is a very small and fast model based on AlexNet and is useful for identifying objects. The range of objects groups is only 1,000.\n# \n# Before this script will work, you need to download the model and install it. You can do this by running:\n# \n# ```\n# sudo python -m caffe2.python.models.download -i squeezenet\n# ```\n# \n# Or make a squeezenet folder, download each file listed below to it, and place it in `/caffe2/python/models/`:\n# * [predict_net.pb](https://download.caffe2.ai/models/squeezenet/predict_net.pb)\n# * [init_net.pb](https://download.caffe2.ai/models/squeezenet/init_net.pb)\n# \n# The helper functions will look up the top object detection result for you by searching through this [inference codes file](inference_codes.txt). If you want to see how well the model detects samples different than provided here, take a look at the codes, and find an image url to an image of an object in the list of codes.\n\n# In[1]:\n\n\n# load up the caffe2 workspace\nfrom caffe2.python import workspace\n# choose your model here (use the downloader first)\nfrom caffe2.python.models import squeezenet as mynet\n# helper image processing functions\nimport helpers\n\n# load the pre-trained model\ninit_net = mynet.init_net\npredict_net = mynet.predict_net\n# you must name it something\npredict_net.name = \"squeezenet_predict\"\nworkspace.RunNetOnce(init_net)\nworkspace.CreateNet(predict_net)\np = workspace.Predictor(init_net.SerializeToString(), predict_net.SerializeToString())\n\n# use whatever image you want (urls work too)\n# img = \"https://upload.wikimedia.org/wikipedia/commons/a/ac/Pretzel.jpg\"\n# img = \"images/cat.jpg\"\n# img = \"images/cowboy-hat.jpg\"\n# img = \"images/cell-tower.jpg\"\n# img = \"images/Ducreux.jpg\"\n# img = \"images/pretzel.jpg\"\n# img = \"images/orangutan.jpg\"\n# img = \"images/aircraft-carrier.jpg\"\nimg = \"images/flower.jpg\"\n# average mean to subtract from the image\nmean = 128\n# the size of images that the model was trained with\ninput_size = 227\n\n# use the image helper to load the image and convert it to NCHW\nimg = helpers.loadToNCHW(img, mean, input_size)\n\n# submit the image to net and get a tensor of results\nresults = p.run({'data': img})   \nresponse = helpers.parseResults(results)\n# and lookup our result from the inference list\nprint response\n\n\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/Multi-GPU_Training.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# # Multi-GPU Training with Caffe2 \n# \n# ![caffe2 imagenet logo](images/imagenet-caffe2.png)\n# \n# For this tutorial we will explore multi-GPU training. We will show you a basic structure for using the `data_parallel_model` to quickly process a subset of the ImageNet database along the same design as the [ResNet-50 model](https://arxiv.org/abs/1512.03385). We will also get a chance to look under the hood at a few of Caffe2's C++ operators that efficiently handle your image pipeline, build a ResNet model, train on a single GPU and show some optimizations that are included with `data_parallel_model`, and finally we'll scale it up and show you how to parallelize your model so you can run it on multiple GPUs.\n# \n# ## About the Dataset\n# \n# A commonly used dataset for benchmarking image recognition technologies is [ImageNet](http://image-net.org/). It is huge. It has images that cover the gamut, and they're categorized by labels so that you can create image subsets of animals, plants, fungi, people, objects, you name it. It's the focus of yearly competitions and this is where deep learning and convolutional neural networks (CNN) really made its name. During the 2012 ImageNet Large-Scale Visual Recognition Challenge a CNN demonstrated accuracy more than 10% beyond the next competing method. Going from around 75% accuracy to around 85% accuracy when every year the gains were only a percent or two is a significant accomplishment. \n# \n# ![imagenet montage](images/imagenet-montage.jpg)\n# \n# So let's play with ImageNet and train our own model on a bunch of GPUs! You're going to need a lot space to host the 14 million images in ImageNet. How much disk space do you have? You should clear up about 300GB of space... on SSD. Spinning discs are so 2000. How much time do you have? With two GPUs maybe we'll be done in just under a week. Ready?\n# \n# ![one does not simply train imagenet in a minute](images/imagenet-meme.jpg)\n# \n# That's way too much space and way too long for a tutorial! If you happened to have that much space and 128 GPUs on the latest NVIDIA V100's then you're super awesome and you can replicate our recent results shown below. You might even be able to train ImageNet in under an hour. Given how this performance seems to scale, **maybe YOU can train ImageNet in a minute!** Think about all of the things you could accomplish... a model for millions of hours of video? Catalogue every cat video on YouTube? Look for your doppleganger on Imgur?\n# \n# Instead of tons of GPUs and the full set of data, we're going to do this cooking show style. We're going to use a small batch images to train on, and show how you can scale that up. We chose a small slice of ImageNet: a set of 640 cars and 640 boats for our training set. We have 48 cars and 48 boats for our test set. This makes our database of images around 130 MB.\n# \n# ## ResNet-50 Model Training Overview\n# \n# Below is an overview of what is needed to train and test this model across multiple GPUs. You see that it is generally not that long, nor is it that complicated. Some of the interactions for creating the parallelized model are handled by custom functions you have to write and we'll go over those later.\n# \n# 1. use `brew` to create a model for training (we'll create one for testing later)\n# 2. create a database reader using the model helper object's `CreateDB` to pull the images\n# 3. create functions to run a ResNet-50 model for one or more GPUs\n# 3. create the parallelized model\n# 4. loop through the number of epochs you want to run, then for each epoch\n#     * run the train model till you finish each batch of images\n#     * run the test model\n#     * calculate times, accuracies, and display the results\n# \n# ## Part 1: Setup\n# \n# Your first assignment is to get your training and testing image database setup. We've created one for you and all you have to do run the code block below. This assumes you know how to use IPython. When we say run a code block, you can click the block and hit the Play button above or hit Ctrl-Enter on your keyboard. If this is news to you it is advisable that you start with introductory tutorials and get used to IPython and Caffe2 basics first.\n# \n# The code below will download a small database of boats and cars images and their labels for you if it doesn't already exist. The images were pulled from ImageNet and added to a `lmdb` format database. You can download it directly [here](https://download.caffe2.ai/databases/resnet_trainer.zip) unzip it, and change the folder locations to an NFS if that better suits your situation. The tutorial's default location is for you to place it in `~/caffe2_notebooks/tutorial_data/resnet_trainer`.\n# \n# You can also swap out the database with your own as long as it is in lmdb and you change the `train_data_count` and `test_data_count` variables below. For your first time just use that database we made for you.\n# \n# We're going to give you all the dependencies needed for the tutorial in the block below. \n# \n# ### Task: Run the Setup Code\n# Read and then run the code block below. Note what modules are being imported and where we're accessing the database. Note and troubleshoot any errors in case something is wrong with your environment. Don't worry about the `nccl` and `gloo` warning messages.\n# \n\n# In[ ]:\n\n\nfrom caffe2.python import core, workspace, model_helper, net_drawer, memonger, brew\nfrom caffe2.python import data_parallel_model as dpm\nfrom caffe2.python.models import resnet\nfrom caffe2.proto import caffe2_pb2\n\nimport numpy as np\nimport time\nimport os\nfrom IPython import display\n    \nworkspace.GlobalInit(['caffe2', '--caffe2_log_level=2'])\n\n# This section checks if you have the training and testing databases\ncurrent_folder = os.path.join(os.path.expanduser('~'), 'caffe2_notebooks')\ndata_folder = os.path.join(current_folder, 'tutorial_data', 'resnet_trainer')\n\n# Train/test data\ntrain_data_db = os.path.join(data_folder, \"imagenet_cars_boats_train\")\ntrain_data_db_type = \"lmdb\"\n# actually 640 cars and 640 boats = 1280\ntrain_data_count = 1280\ntest_data_db = os.path.join(data_folder, \"imagenet_cars_boats_val\")\ntest_data_db_type = \"lmdb\"\n# actually 48 cars and 48 boats = 96\ntest_data_count = 96\n\n# Get the dataset if it is missing\ndef DownloadDataset(url, path):\n    import requests, zipfile, StringIO\n    print(\"Downloading {} ... \".format(url))\n    r = requests.get(url, stream=True)\n    z = zipfile.ZipFile(StringIO.StringIO(r.content))\n    z.extractall(path)\n    print(\"Done downloading to {}!\".format(path))\n\n# Make the data folder if it doesn't exist\nif not os.path.exists(data_folder):\n    os.makedirs(data_folder)\nelse:\n    print(\"Data folder found at {}\".format(data_folder))\n# See if you already have to db, and if not, download it\nif not os.path.exists(train_data_db):\n    DownloadDataset(\"https://download.caffe2.ai/databases/resnet_trainer.zip\", data_folder) \n\n\n# ### Task: Check the Database\n# \n# Take a look at your data folder. You should find two subfolders, each of which will contain a single `data.mdb` file (or possibly also a lock file):\n# 1. imagenet_cars_boats_train (train for training, not locomotives!)\n# 2. imagenet_cars_boats_val (val for validation or testing)\n# \n# ## Part 2: Configure the Training\n# \n# Below you can tinker with some of the settings for how the model will be created. One obvious setting to try is the `gpus`. By removing one or adding one you're directly impacting the amount of time it will take to run even on this small dataset.\n# \n# `batch_per_device` is the number of images processed at a time on each GPU. Using the default of 32 for 2 GPUs will equate to 32 images on each GPU for a total of 64 per mini-batch, so we'll go through the whole database and complete an epoch in 20 iterations. This is something you would want to adjust if you're sharing the GPU or otherwise want to adjust how much memory this training run is going to take up. You can see in the line below it being set to `32` we're adjusting the `total_batch_size` based on the number of GPUs.\n# \n# `base_learning_rate` and `weight_decay` will both influence training and can be interesting to change and witness the impact on accuracy or confidence is the results that are shown in the last section of this tutorial.\n# \n# \n\n# In[ ]:\n\n\n# Configure how you want to train the model and with how many GPUs\n# This is set to use two GPUs in a single machine, but if you have more GPUs, extend the array [0, 1, 2, n]\ngpus = [0]\n\n# Batch size of 32 sums up to roughly 5GB of memory per device\nbatch_per_device = 32\ntotal_batch_size = batch_per_device * len(gpus)\n\n# This model discriminates between two labels: car or boat\nnum_labels = 2\n\n# Initial learning rate (scale with total batch size)\nbase_learning_rate = 0.0004 * total_batch_size\n\n# only intends to influence the learning rate after 10 epochs\nstepsize = int(10 * train_data_count / total_batch_size)\n\n# Weight decay (L2 regularization)\nweight_decay = 1e-4\n\n\n# ## Part 3: \n# \n# ### Using Caffe2 Operators to Create a CNN\n# \n# Caffe2 comes with `ModelHelper` which will do a lot of the heavy lifting for you when setting up a model. Throughout the docs and tutorial this may also be called a `model helper object`. The only required parameter is `name`. It is an arbitrary name for referencing the network in your workspace: you could call it tacos or boatzncarz. For example:\n# \n# ```python\n# taco_model = model_helper.ModelHelper(name=\"tacos\")\n# ```\n# \n# You should also reset your workspace if you run these parts multiple times. Do this just before creating the new model helper object.\n# \n# ```python\n# workspace.ResetWorkspace()\n# ```\n# \n# ### Reading from the Database\n# \n# Another handy function for feeding your network with images is `CreateDB`, which in this case we need to serve as a database reader for the database we've already created. You can create a reader object like this: \n# \n# ```python\n# reader = taco_model.CreateDB(name, db, db_type)\n# ```\n# \n# ### Task: Create a Model Helper Object\n# Remember, we have two databases and each will have their own model, but for now we only need to create the training model for the training db. Use the Work Area below. Also, while you do this, experiment with IPython's development hooks by typing the first part of the name from the imported class or module and hitting the tab key. For example when creating the object you type: `train_model = model_helper.` and after the dot, hit \"tab\". You should see a full list of available functions. Then when you choose `ModelHelper` hit \"(\" then hit tab and you should see a full list of params. This is very handy when you're exploring new modules and their functions!\n# \n# ### Task: Create a Reader\n# We also need one reader. We have established the db location, `train_data_db`, and type, `train_data_db_type`, in \"Part 1: Setup\", so all you have to do is name it and pass in the configs. Again, `name` is arbitrary so you could call it \"kindle\" if you wanted. Use the Work Area below, and when you are finished run the code block.\n\n# In[ ]:\n\n\n# LAB WORK AREA FOR PART 3\n\n# Clear workspace to free allocated memory, in case you are running this for a second time.\nworkspace.ResetWorkspace()\n\n# 1. Create your model helper object for the training model with ModelHelper\n\n\n# 2. Create your database reader with CreateDB\n\n\n\n# ## Part 4: Image Transformations (requires Caffe2 to be compiled with opencv)\n# \n# Now that we have a reader we should take a look at how we're going to process the images. Since images that are found in the wild can be wildly different sizes, aspect ratios, and orientations we can and should train on as much variety as we can. ImageNet is no exception here. The average resolution is 496x387, and as interesting as that factoid might be, the bottom line is that you have a lot of variation. \n# \n# As the training images are ingested we would want to conform them to a standard size. The most direct process of doing so could follow a simple ingest where you transform the image to 256x256. We talked about the drawbacks of doing this in [Image Pre-Processing](Image_Pre-Processing_Pipeline.ipynb). Therefore for more accurate results, we should probably rescale, then crop. Even this approach with cropping has the drawbacks of losing some info from the original photo. What get chopped off doesn't make into the training data. If you ran the pre-processing tutorial on the image of the astronauts you will recall that some of the astronauts didn't make the cut. Where'd they go? Wash-out lane? Planet of the Apes? If your model was to detect people, then those lost astronauts would not be getting due credit when you run inference or face detection later using the model.\n# \n# ### Introducing... the ImageInput Operator\n# \n# What could be seen as a loss turns into an opportunity. You can crop randomly around the image to create many deriviates of the original image, boosting your training data set, thereby adding robustness to the model. What if the image only has half a car or the front of a boat? You still want your model to be able to detect it! In the image below only the front a boat is shown and the model shows a 50% confidence in detection.\n# \n# ![boat image](images/imagenet-boat.png)\n# \n# Caffe2 has a solution for this in its [`ImageInput` operator](https://github.com/caffe2/caffe2/blob/master/caffe2/image/image_input_op.h), a C++ image manipulation op that's used under the hood of several of the Caffe2 Python APIs.\n# \n# Here is a reference implementation:\n# \n# ```python\n# def add_image_input_ops(model):\n#     # utilize the ImageInput operator to prep the images\n#     data, label = model.ImageInput(\n#         reader,\n#         [\"data\", \"label\"],\n#         batch_size=batch_per_device,\n#         # mean: to remove color values that are common\n#         mean=128.,\n#         # std is going to be modified randomly to influence the mean subtraction\n#         std=128.,\n#         # scale to rescale each image to a common size\n#         scale=256,\n#         # crop to the square each image to exact dimensions\n#         crop=224,\n#         # not running in test mode\n#         is_test=False,\n#         # mirroring of the images will occur randomly\n#         mirror=1\n#     )\n#     # prevent back-propagation: optional performance improvement; may not be observable at small scale\n#     data = model.StopGradient(data, data)\n# ```\n# \n# * mean: remove info that's common in most images\n# * std: used to create a randomization for both cropping and mirroring\n# * scale: downres each image so that its shortest side matches this base resolution\n# * crop: the image size we want every image to be (using random crops from the scaled down image)\n# * mirror: randomly mirror the images so we can train on both representations\n# \n# The [`StopGradient` operator](https://caffe2.ai/docs/operators-catalogue.html#stopgradient) does no numerical computation. It is used here to prevent back propagation which isn't wanted in this network.\n# \n# ### Task: Implement the InputImage Operator\n# Use the Work Area below to finish the stubbed out function. Refer to the reference implementation for help on this task. \n# \n# * What happens if you don't add a mean, don't add a std, or don't mirror. How does this change your accuracy when you run it for many epochs?\n# * What would happen if we didn't do StopGradient?\n\n# In[ ]:\n\n\n# LAB WORK AREA FOR PART 4\n\ndef add_image_input_ops(model):\n    raise NotImplementedError # Remove this from the function stub\n    \n\n\n# ## Part 5: Creating a Residual Network\n# \n# Now you get the opportunity to use Caffe2's Resnet-50 creation function! During our Setup we `from caffe2.python.models import resnet`. We can use that for our `create_resnet50_model_ops` function that we still need to create and the main part of that will be the `resnet.create_resnet50()` function as described below:\n# \n# ```python\n# create_resnet50(\n#     model, \n#     data, \n#     num_input_channels, \n#     num_labels, \n#     label=None, \n#     is_test=False, \n#     no_loss=False, \n#     no_bias=0, \n#     conv1_kernel=7, \n#     conv1_stride=2, \n#     final_avg_kernel=7\n# )\n# ```\n# \n# Below is a reference implementation of the function using `resnet.create_resnet50()`.\n# \n# ```python\n# def create_resnet50_model_ops(model, loss_scale):\n#     # Creates a residual network\n#     [softmax, loss] = resnet.create_resnet50(\n#         model,\n#         \"data\",\n#         num_input_channels=3,\n#         num_labels=num_labels,\n#         label=\"label\",\n#     )\n#     prefix = model.net.Proto().name\n#     loss = model.Scale(loss, prefix + \"_loss\", scale=loss_scale)\n#     model.Accuracy([softmax, \"label\"], prefix + \"_accuracy\")\n#     return [loss]\n# ```\n# \n# ### Task: Implement the forward_pass_builder_fun Using Resnet-50\n# In the code block above where we stubbed out the `create_resnet50_model_ops` function, utilize `resnet.create_resnet50()` to create a residual network, then returning the loss. Refer to the reference implementation for help on this task.\n# \n# * Bonus points: if you take a look at the resnet class in the Caffe2 docs you'll notice a function to create a 32x32 model. Try it out.\n\n# In[ ]:\n\n\n# LAB WORK AREA FOR PART 5\n\ndef create_resnet50_model_ops(model, loss_scale):\n    raise NotImplementedError #remove this from the function stub\n    \n\n\n# ## Part 6: Make the Network Learn\n# \n# \n# Caffe2 model helper object has several built in functions that will help with this learning by using backpropagation where it will be adjusting weights as it runs through iterations.\n# \n# * AddWeightDecay\n# * Iter\n# * net.LearningRate\n# \n# Below is a reference implementation:\n# \n# ```python\n# def add_parameter_update_ops(model):\n#     model.AddWeightDecay(weight_decay)\n#     iter = model.Iter(\"iter\")\n#     lr = model.net.LearningRate(\n#         [iter],\n#         \"lr\",\n#         base_lr=base_learning_rate,\n#         policy=\"step\",\n#         stepsize=stepsize,\n#         gamma=0.1,\n#     )\n#     # Momentum SGD update\n#     for param in model.GetParams():\n#         param_grad = model.param_to_grad[param]\n#         param_momentum = model.param_init_net.ConstantFill(\n#             [param], param + '_momentum', value=0.0\n#         )\n# \n#         # Update param_grad and param_momentum in place\n#         model.net.MomentumSGDUpdate(\n#             [param_grad, param_momentum, lr, param],\n#             [param_grad, param_momentum, param],\n#             momentum=0.9,\n#             # Nesterov Momentum works slightly better than standard momentum\n#             nesterov=1,\n#         )\n# ```\n# \n# ### Task: Implement the forward_pass_builder_fun Using Resnet-50\n# Several of our Configuration variables will get used in this step. Take a look at the Configuration section from Part 2 and refresh your memory. We stubbed out the `add_parameter_update_ops` function, so to finish it, utilize `model.AddWeightDecay` and set `weight_decay`. Calculate your stepsize using `int(10 * train_data_count / total_batch_size)` or pull the value from the config. Instantiate the learning iterations with `iter = model.Iter(\"iter\")`. Use `model.net.LearningRate()` to finalize your parameter update operations. You can optionally update you SGD's momentum. It might not make a difference in this small implementation, but if you're gonna go big later, then you'll want to do this.\n# \n# Refer to the reference implementation for help on this task.\n# \n\n# In[ ]:\n\n\n# LAB WORK AREA FOR PART 6\n\ndef add_parameter_update_ops(model):\n    raise NotImplementedError #remove this from the function stub\n    \n\n\n# ## Part 7: Gradient Optimization\n# \n# If you run the network as is you may have issues with memory. Without memory optimization we could reduce the batch size, but we shouldn't have to do that. Caffe2 has a `memonger` function for this purpose which will find ways to reuse gradients that we created. Below is a reference implementation.\n# \n# ```python\n# def optimize_gradient_memory(model, loss):\n#     model.net._net = memonger.share_grad_blobs(\n#         model.net,\n#         loss,\n#         set(model.param_to_grad.values()),\n#         # Due to memonger internals, we need a namescope here. Let's make one up; we'll need it later!\n#         namescope=\"imonaboat\",\n#         share_activations=False)\n# ```\n# \n# ### Task: Implement memonger\n# We're going to use the reference for help here, otherwise it is a little difficult to cover for the scope of this tutorial. The function is ready to go for you, but you should still soak up what's been done in this function. One of the key gotchas here is making sure you give it a namescope so that you can access the gradients you'll be creating in the next step. This name can be anything.\n# \n\n# In[ ]:\n\n\n# LAB WORK AREA FOR PART 7\n\ndef optimize_gradient_memory(model, loss):\n    raise NotImplementedError # Remove this from the function stub\n\n\n# ## Part 8: Training the Network with One GPU\n# \n# Now that you've established be basic components to run ResNet-50, you can try it out on one GPU. Now, this could be a lot easier just going straight into the `data_parallel_model` and all of its optimizations, but to help explain the components needed and to build the helper functions to run `GPU_Parallelize`, we may as well start simple! \n# \n# If you're paying attention you might be wondering about the `gpus` array we made in the config and how that might throw things off. Also, when we looked at the config earlier you may have updated `gpus[0]` to have more than one GPU. That's fine. We can leave it like that for the next part because we will force our script to use just one GPU.\n# \n# Let's stitch together those functions from Parts 4-7 to run our residual network! Take a look at the code below, so you understand how the pieces fit together.\n# \n# ```python\n# # We need to give the network context and force it to run on the first GPU even if there are more.\n# device_opt = core.DeviceOption(caffe2_pb2.CUDA, gpus[0])\n# # Here's where that NameScope comes into play\n# with core.NameScope(\"imonaboat\"):\n#     # Picking that one GPU\n#     with core.DeviceScope(device_opt):\n#         # Run our reader, and create the layers that transform the images\n#         add_image_input_ops(train_model)\n#         # Generate our residual network and return the losses\n#         losses = create_resnet50_model_ops(train_model)\n#         # Create gradients for each loss\n#         blobs_to_gradients = train_model.AddGradientOperators(losses)\n#         # Kick off the learning and managing of the weights\n#         add_parameter_update_ops(train_model)\n#     # Optimize memory usage by consolidating where we can\n#     optimize_gradient_memory(train_model, [blobs_to_gradients[losses[0]]])\n# \n# # Startup the network \n# workspace.RunNetOnce(train_model.param_init_net)\n# # Load all of the initial weights; overwrite lets you run this multiple times\n# workspace.CreateNet(train_model.net, overwrite=True)\n# ```\n# \n# ### Task: Pull It All Together & Run It!\n# \n# Things are getting a little hairy, so we gave you the full reference ready to go. Just run the code block below (hit ctrl-enter). Normally you might not use `overwrite=True` since that could be bad for what you're doing by accidentally erasing your earlier work, so try removing it and running the block multiple times to see what happens. Imagine the case where you have multiple networks going that have the same name. You don't want to overwrite, so you might want to start up a new workspace or modify the names.\n\n# In[ ]:\n\n\n# LAB WORK AREA FOR PART 8\n\ndevice_opt = core.DeviceOption(caffe2_pb2.CUDA, gpus[0])\nwith core.NameScope(\"imonaboat\"):\n    with core.DeviceScope(device_opt):\n        add_image_input_ops(train_model)\n        losses = create_resnet50_model_ops(train_model)\n        blobs_to_gradients = train_model.AddGradientOperators(losses)\n        add_parameter_update_ops(train_model)\n    optimize_gradient_memory(train_model, [blobs_to_gradients[losses[0]]])\n\n\nworkspace.RunNetOnce(train_model.param_init_net)\nworkspace.CreateNet(train_model.net, overwrite=True)\n\n\n# ## Part 8 ... part ~~2~~ Deux: Train!\n# Here's the fun part where you can tinker with the number of epochs to run and mess with the display. We'll leave this for you to play with as a fait accompli since you worked so hard to get this far!\n\n# In[ ]:\n\n\nnum_epochs = 1\nfor epoch in range(num_epochs):\n    # Split up the images evenly: total images / batch size\n    num_iters = int(train_data_count / total_batch_size)\n    for iter in range(num_iters):\n        # Stopwatch start!\n        t1 = time.time()\n        # Run this iteration!\n        workspace.RunNet(train_model.net.Proto().name)\n        t2 = time.time()\n        dt = t2 - t1\n        \n        # Stopwatch stopped! How'd we do?\n        print((\n            \"Finished iteration {:>\" + str(len(str(num_iters))) + \"}/{}\" +\n            \" (epoch {:>\" + str(len(str(num_epochs))) + \"}/{})\" + \n            \" ({:.2f} images/sec)\").\n            format(iter+1, num_iters, epoch+1, num_epochs, total_batch_size/dt))\n\n\n# ## Part 9: Getting Parallelized \n# \n# You get bonus points if you can say \"getting parallelized\" three times fast without messing up. You just saw some interesting numbers in the last step. Take note of those and see how things scale up when we use more GPUs. \n# \n# We're going to use Caffe2's `data_parallel_model` and its function called `Parallelize_GPU` to help us accomplish this task. The task to setup the parallel model, not to say it fast. Here's the spec on `Parallelize_GPU`:\n# \n# ```python\n# Parallelize_GPU(\n#     model_helper_obj, \n#     input_builder_fun, \n#     forward_pass_builder_fun, \n#     param_update_builder_fun, \n#     devices=range(0, workspace.NumCudaDevices()), \n#     rendezvous=None, \n#     net_type='dag', \n#     broadcast_computed_params=True, \n#     optimize_gradient_memory=False)\n# ```\n# \n# We're not ready to just call this function though. As you can see in the second, third, and fourth input parameters, they are expecting functions to be passed to them. [More API details here.](https://caffe2.ai/doxygen-python/html/namespacedata__parallel__model.html#a1fe7262a0a66754f19998fa1603317b9) The three functions expected are:\n# \n# 1. `input_build_fun`: adds the input operators. Note: Remember to instantiate reader outside of this function so all GPUs share same reader object. Signature:  input_builder_fun(model)\n# 2. `forward_pass_builder_fun`: adds the operators to the model. Must return list of loss-blob references that are used to build the gradient. Loss scale parameter is passed, as you should scale the loss of your model by 1.0 / the total number of gpus. Signature: forward_pass_builder_fun(model, loss_scale)\n# 3. `param_update_builder_fun`: adds operators that are run after gradient update, such as updating the weights and weight decaying. Signature: param_update_builder_fun(model)\n# \n# For the `input_build_fun` we're going to use the reader we created with `CreateDB` along with a function that leverages Caffe2's `ImageInput` operator. Sound familiar? You already did this in Part 4!\n# \n# For the `forward_pass_builder_fun` we need to have residual neural network. You already did this in Part 5!\n# \n# For the `param_update_builder_fun` we need a function to adjust the weights as the network runs. You already did this in Part 6! \n# \n# Let's stub out the `Parallelize_GPU` function with the parameters that we're going to use. Recall that in the setup we  `from caffe2.python import data_parallel_model as dpm`, so we can use `dpm.Parallelize_GPU()` to access the `Parallelize_GPU` function. First we'll stub out the three other functions to that this expects, add the params based on these functions names and our gpu count, then come back to the lab cell below to populate them with some logic and test them. Below is a reference implementation:\n# \n# ```python\n# dpm.Parallelize_GPU(\n#     train_model,\n#     input_builder_fun=add_image_input_ops,\n#     forward_pass_builder_fun=create_resnet50_model_ops,\n#     param_update_builder_fun=add_parameter_update_ops,\n#     devices=gpus,\n#     optimize_gradient_memory=True,\n# )\n# ```\n# \n# ### Task: Make Your Helper Functions\n# You already did this the Parts 4 through 6 and in Part 7 you had to deal with gradient optimizations that are baked into `Parallelize_GPU`. The three helper function stubs below can be eliminated or if you want to see everything together go ahead and copy the functions there, so you can run them from the work area block below.\n# \n# ### Task: Parallelize!\n# Now you can stub out a call to `Parallelize_GPU`. Use the reference implementation above if you get stuck.\n# * `model_helper_object`: created in Part 3; maybe you called it taco_model, or if you weren't copying and pasting you thoughtfully called it train_model or training_model.\n# * Now pass the function name for each of the three functions you just created, e.g. `input_builder_fun=add_image_input_ops`\n# * `devices`: we can pass in our `gpus` array from our earlier Setup.\n# * `optimize_gradient_memory`: the default is `False` but let's set it to `True`; this takes care of what we had to do in Step 7 with `memonger`.\n# * other params: ignore/don't pass anything to accept their defaults\n# \n\n# In[ ]:\n\n\n# LAB WORK AREA for Part 9\n\n# Reinitializing our configuration variables to accomodate 2 (or more, if you have them) GPUs.\ngpus = [0, 1]\n\n# Batch size of 32 sums up to roughly 5GB of memory per device\nbatch_per_device = 32\ntotal_batch_size = batch_per_device * len(gpus)\n\n# This model discriminates between two labels: car or boat\nnum_labels = 2\n\n# Initial learning rate (scale with total batch size)\nbase_learning_rate = 0.0004 * total_batch_size\n\n# only intends to influence the learning rate after 10 epochs\nstepsize = int(10 * train_data_count / total_batch_size)\n\n# Weight decay (L2 regularization)\nweight_decay = 1e-4\n\n# Clear workspace to free network and memory allocated in previous steps.\nworkspace.ResetWorkspace()\n\n# Create input_build_fun\ndef add_image_input_ops(model):\n    # This will utilize the reader to pull images and feed them to the training model's helper object\n    # Use the model.ImageInput operator to load data from reader & apply transformations to the images.\n    raise NotImplementedError # Remove this from the function stub\n    \n\n# Create forward_pass_builder_fun\ndef create_resnet50_model_ops(model, loss_scale):\n    # Use resnet module to create a residual net\n    raise NotImplementedError # Remove this from the function stub\n\n\n# Create param_update_builder_fun\ndef add_parameter_update_ops(model):\n    raise NotImplementedError # Remove this from the function stub\n\n    \n# Create new train model\ntrain_model = NotImplementedError\n\n# Create new reader\nreader = NotImplementedError\n\n# Create parallelized model using dpm.Parallelize_GPU\n\n\n# Use workspace.RunNetOnce and workspace.CreateNet to fire up the train network\nworkspace.RunNetOnce(train_model.param_init_net)\nworkspace.CreateNet(train_model.net, overwrite=True)\n\n\n# ## Part 10: Create a Test Model\n# \n# After every epoch of training, we like to run some validation data through our model to see how it performs.\n# \n# Like training, this is another net, with its own data reader. Unlike training, this net does not perform backpropagation. It only does a forward pass and compares the output of the network with the label of the validation data.\n# \n# You've already done these steps once before when you created the training network, so do it again, but name it something different, like \"test\".\n# \n# ### Task: Create a Test Model\n# \n# * Use `ModelHelper` to create a model helper object called \"test\"\n# * Use `CreateDB` to create a reader and call it \"test_reader\"\n# * Use `Parallelize_GPU` to parallelize the model, but set `param_update_builder_fun=None` to skip backpropagation\n# * Use `workspace.RunNetOnce` and `workspace.CreateNet` to fire up the test network \n\n# In[ ]:\n\n\n# LAB WORK AREA for Part 10\n\n# Create your test model with ModelHelper\n\n\n# Create your reader with CreateDB\n\n\n# Use multi-GPU with Parallelize_GPU, but don't utilize backpropagation\n\n\n# Use workspace.RunNetOnce and workspace.CreateNet to fire up the test network\nworkspace.RunNetOnce(test_model.param_init_net)\nworkspace.CreateNet(test_model.net, overwrite=True)\n\n\n# ## Get Ready to Display the Results\n# At the end of every epoch we will take a look at how the network performs visually. We will also report on the accuracy of the training model and the test model. Let's not force you to write your own reporting and display code, so just run the code block below to get those features ready.\n\n# In[ ]:\n\n\nfrom caffe2.python import visualize\nfrom matplotlib import pyplot as plt\n\ndef display_images_and_confidence():\n    images = []\n    confidences = []\n    n = 16\n    data = workspace.FetchBlob(\"gpu_0/data\")\n    label = workspace.FetchBlob(\"gpu_0/label\")\n    softmax = workspace.FetchBlob(\"gpu_0/softmax\")\n    for arr in zip(data[0:n], label[0:n], softmax[0:n]):\n        # CHW to HWC, normalize to [0.0, 1.0], and BGR to RGB\n        bgr = (arr[0].swapaxes(0, 1).swapaxes(1, 2) + 1.0) / 2.0\n        rgb = bgr[...,::-1]\n        images.append(rgb)\n        confidences.append(arr[2][arr[1]])\n\n    # Create grid for images\n    fig, rows = plt.subplots(nrows=4, ncols=4, figsize=(12, 12))\n    plt.tight_layout(h_pad=2)\n\n    # Display images and the models confidence in their label\n    items = zip([ax for cols in rows for ax in cols], images, confidences)\n    for (ax, image, confidence) in items:\n        ax.imshow(image)\n        if confidence >= 0.5:\n            ax.set_title(\"RIGHT ({:.1f}%)\".format(confidence * 100.0), color='green')\n        else:\n            ax.set_title(\"WRONG ({:.1f}%)\".format(confidence * 100.0), color='red')\n\n    plt.show()\n\n    \ndef accuracy(model):\n    accuracy = []\n    prefix = model.net.Proto().name\n    for device in model._devices:\n        accuracy.append(\n            np.asscalar(workspace.FetchBlob(\"gpu_{}/{}_accuracy\".format(device, prefix))))\n    return np.average(accuracy)\n\n\n# ## Part 11: Run Multi-GPU Training and Get Test Results\n# You've come a long way. Now is the time to see it all pay off. Since you already ran ResNet once, you can glance at the code below and run it. The big difference this time is your model is parallelized! \n# \n# The additional components at the end deal with accuracy so you may want to dig into those specifics as a bonus task. You can try it again: just adjust the `num_epochs` value below, run the block, and see the results. You can also go back to Part 10 to reinitialize the model, and run this step again. (You may want to add `workspace.ResetWorkspace()` before you run the new models again.)\n# \n# Go back and check the images/sec from when you ran single GPU. Note how you can scale up with a small amount of overhead. \n# \n# ### Task: How many GPUs would it take to train ImageNet in under a minute? \n\n# In[ ]:\n\n\n# Start looping through epochs where we run the batches of images to cover the entire dataset\n# Usually you would want to run a lot more epochs to increase your model's accuracy\nnum_epochs = 2\nfor epoch in range(num_epochs):\n    # Split up the images evenly: total images / batch size\n    num_iters = int(train_data_count / total_batch_size)\n    for iter in range(num_iters):\n        # Stopwatch start!\n        t1 = time.time()\n        # Run this iteration!\n        workspace.RunNet(train_model.net.Proto().name)\n        t2 = time.time()\n        dt = t2 - t1\n        \n        # Stopwatch stopped! How'd we do?\n        print((\n            \"Finished iteration {:>\" + str(len(str(num_iters))) + \"}/{}\" +\n            \" (epoch {:>\" + str(len(str(num_epochs))) + \"}/{})\" + \n            \" ({:.2f} images/sec)\").\n            format(iter+1, num_iters, epoch+1, num_epochs, total_batch_size/dt))\n        \n        # Get the average accuracy for the training model\n        train_accuracy = accuracy(train_model)\n    \n    # Run the test model and assess accuracy\n    test_accuracies = []\n    for _ in range(test_data_count / total_batch_size):\n        # Run the test model\n        workspace.RunNet(test_model.net.Proto().name)\n        test_accuracies.append(accuracy(test_model))\n    test_accuracy = np.average(test_accuracies)\n\n    print(\n        \"Train accuracy: {:.3f}, test accuracy: {:.3f}\".\n        format(train_accuracy, test_accuracy))\n    \n    # Output images with confidence scores as the caption\n    display_images_and_confidence()\n\n\n# If you enjoyed this tutorial and would like to see it in action in a different way, check Caffe2's Python examples to try a [script version](https://github.com/caffe2/caffe2/blob/master/caffe2/python/examples/resnet50_trainer.py) of this multi-GPU trainer. We also have some more info below in the Appendix and a Solutions section that you can use to run the expected output of this tutorial.\n\n# ## Appendix\n# Here are a few things you may want to play with.\n# \n# ### Explore the workspace and the protobuf outputs\n\n# In[ ]:\n\n\nprint(str(train_model.param_init_net.Proto())[:1000] + '\\n...')\n\n\n# ## Solutions\n# This section below contains working examples for your reference. You should be able to execute these cells in order and see the expected output. **Note: this assumes you have at least 2 GPUs**\n\n# In[ ]:\n\n\n# SOLUTION for Part 1\n\nfrom caffe2.python import core, workspace, model_helper, net_drawer, memonger, brew\nfrom caffe2.python import data_parallel_model as dpm\nfrom caffe2.python.models import resnet\nfrom caffe2.proto import caffe2_pb2\n\nimport numpy as np\nimport time\nimport os\nfrom IPython import display\n    \nworkspace.GlobalInit(['caffe2', '--caffe2_log_level=2'])\n\n# This section checks if you have the training and testing databases\ncurrent_folder = os.path.join(os.path.expanduser('~'), 'caffe2_notebooks')\ndata_folder = os.path.join(current_folder, 'tutorial_data', 'resnet_trainer')\n\n# Train/test data\ntrain_data_db = os.path.join(data_folder, \"imagenet_cars_boats_train\")\ntrain_data_db_type = \"lmdb\"\n# actually 640 cars and 640 boats = 1280\ntrain_data_count = 1280\ntest_data_db = os.path.join(data_folder, \"imagenet_cars_boats_val\")\ntest_data_db_type = \"lmdb\"\n# actually 48 cars and 48 boats = 96\ntest_data_count = 96\n\n# Get the dataset if it is missing\ndef DownloadDataset(url, path):\n    import requests, zipfile, StringIO\n    print(\"Downloading {} ... \".format(url))\n    r = requests.get(url, stream=True)\n    z = zipfile.ZipFile(StringIO.StringIO(r.content))\n    z.extractall(path)\n    print(\"Done downloading to {}!\".format(path))\n\n# Make the data folder if it doesn't exist\nif not os.path.exists(data_folder):\n    os.makedirs(data_folder)\nelse:\n    print(\"Data folder found at {}\".format(data_folder))\n# See if you already have to db, and if not, download it\nif not os.path.exists(train_data_db):\n    DownloadDataset(\"https://download.caffe2.ai/databases/resnet_trainer.zip\", data_folder) \n\n\n# In[ ]:\n\n\n# PART 1 TROUBLESHOOTING\n\n# lmdb error or unable to open database: look in the database folder from terminal and (sudo) delete the lock file and try again\n\n\n# In[ ]:\n\n\n# SOLUTION for Part 2\n\n# Configure how you want to train the model and with how many GPUs\n# This is set to use two GPUs in a single machine, but if you have more GPUs, extend the array [0, 1, 2, n]\ngpus = [0, 1]\n\n# Batch size of 32 sums up to roughly 5GB of memory per device\nbatch_per_device = 32\ntotal_batch_size = batch_per_device * len(gpus)\n\n# This model discriminates between two labels: car or boat\nnum_labels = 2\n\n# Initial learning rate (scale with total batch size)\nbase_learning_rate = 0.0004 * total_batch_size\n\n# only intends to influence the learning rate after 10 epochs\nstepsize = int(10 * train_data_count / total_batch_size)\n\n# Weight decay (L2 regularization)\nweight_decay = 1e-4\n\n\n# In[ ]:\n\n\n# SOLUTION for Part 3\n\nworkspace.ResetWorkspace()\n# 1. Use the model helper to create a CNN for us\ntrain_model = model_helper.ModelHelper(\n    # Arbitrary name for referencing the network in your workspace: you could call it tacos or boatzncarz\n    name=\"train\",\n)\n\n\n# 2. Create a database reader\n# This training data reader is shared between all GPUs.\n# When reading data, the trainer runs ImageInputOp for each GPU to retrieve their own unique batch of training data.\n# CreateDB is inherited by ModelHelper from model_helper.py\n# We are going to name it \"train_reader\" and pass in the db configurations we set earlier\nreader = train_model.CreateDB(\n    \"train_reader\",\n    db=train_data_db,\n    db_type=train_data_db_type,\n)\n\n\n# In[ ]:\n\n\n# SOLUTION for Part 4\n\ndef add_image_input_ops(model):\n    # utilize the ImageInput operator to prep the images\n    data, label = brew.image_input(\n        model,\n        reader,\n        [\"data\", \"label\"],\n        batch_size=batch_per_device,\n        # mean: to remove color values that are common\n        mean=128.,\n        # std is going to be modified randomly to influence the mean subtraction\n        std=128.,\n        # scale to rescale each image to a common size\n        scale=256,\n        # crop to the square each image to exact dimensions\n        crop=224,\n        # not running in test mode\n        is_test=False,\n        # mirroring of the images will occur randomly\n        mirror=1\n    )\n    # prevent back-propagation: optional performance improvement; may not be observable at small scale\n    data = model.net.StopGradient(data, data)\n\n\n# In[ ]:\n\n\n# SOLUTION for Part 5\n\ndef create_resnet50_model_ops(model, loss_scale=1.0):\n    # Creates a residual network\n    [softmax, loss] = resnet.create_resnet50(\n        model,\n        \"data\",\n        num_input_channels=3,\n        num_labels=num_labels,\n        label=\"label\",\n    )\n    prefix = model.net.Proto().name\n    loss = model.net.Scale(loss, prefix + \"_loss\", scale=loss_scale)\n    brew.accuracy(model, [softmax, \"label\"], prefix + \"_accuracy\")\n    return [loss]\n\n\n# In[ ]:\n\n\n# SOLUTION for Part 6\n\ndef add_parameter_update_ops(model):\n    brew.add_weight_decay(model, weight_decay)\n    iter = brew.iter(model, \"iter\")\n    lr = model.net.LearningRate(\n        [iter],\n        \"lr\",\n        base_lr=base_learning_rate,\n        policy=\"step\",\n        stepsize=stepsize,\n        gamma=0.1,\n    )\n    for param in model.GetParams():\n        param_grad = model.param_to_grad[param]\n        param_momentum = model.param_init_net.ConstantFill(\n            [param], param + '_momentum', value=0.0\n        )\n\n        # Update param_grad and param_momentum in place\n        model.net.MomentumSGDUpdate(\n            [param_grad, param_momentum, lr, param],\n            [param_grad, param_momentum, param],\n            # almost 100% but with room to grow\n            momentum=0.9,\n            # netsterov is a defenseman for the Montreal Canadiens, but\n            # Nesterov Momentum works slightly better than standard momentum\n            nesterov=1,\n        )\n\n\n# In[ ]:\n\n\n# SOLUTION for Part 7\n\ndef optimize_gradient_memory(model, loss):\n    model.net._net = memonger.share_grad_blobs(\n        model.net,\n        loss,\n        set(model.param_to_grad.values()),\n        namescope=\"imonaboat\",\n        share_activations=False,\n        )\n    \n\n\n# In[ ]:\n\n\n# SOLUTION for Part 8\n\ndevice_opt = core.DeviceOption(caffe2_pb2.CUDA, gpus[0])\nwith core.NameScope(\"imonaboat\"):\n    with core.DeviceScope(device_opt):\n        add_image_input_ops(train_model)\n        losses = create_resnet50_model_ops(train_model)\n        blobs_to_gradients = train_model.AddGradientOperators(losses)\n        add_parameter_update_ops(train_model)\n    optimize_gradient_memory(train_model, [blobs_to_gradients[losses[0]]])\n\n\nworkspace.RunNetOnce(train_model.param_init_net)\nworkspace.CreateNet(train_model.net, overwrite=True)\n\n\n# In[ ]:\n\n\n# SOLUTION for Part 8 Part Deux\nnum_epochs = 1\nfor epoch in range(num_epochs):\n    # Split up the images evenly: total images / batch size\n    num_iters = int(train_data_count / batch_per_device)\n    for iter in range(num_iters):\n        # Stopwatch start!\n        t1 = time.time()\n        # Run this iteration!\n        workspace.RunNet(train_model.net.Proto().name)\n        t2 = time.time()\n        dt = t2 - t1\n        \n        # Stopwatch stopped! How'd we do?\n        print((\n            \"Finished iteration {:>\" + str(len(str(num_iters))) + \"}/{}\" +\n            \" (epoch {:>\" + str(len(str(num_epochs))) + \"}/{})\" + \n            \" ({:.2f} images/sec)\").\n            format(iter+1, num_iters, epoch+1, num_epochs, batch_per_device/dt))\n\n\n# In[ ]:\n\n\n# SOLUTION for Part 9 Prep\n\n# Reinitializing our configuration variables to accomodate 2 (or more, if you have them) GPUs.\ngpus = [0, 1]\n\n# Batch size of 32 sums up to roughly 5GB of memory per device\nbatch_per_device = 32\ntotal_batch_size = batch_per_device * len(gpus)\n\n# This model discriminates between two labels: car or boat\nnum_labels = 2\n\n# Initial learning rate (scale with total batch size)\nbase_learning_rate = 0.0004 * total_batch_size\n\n# only intends to influence the learning rate after 10 epochs\nstepsize = int(10 * train_data_count / total_batch_size)\n\n# Weight decay (L2 regularization)\nweight_decay = 1e-4\n\n# Reset workspace to clear out memory allocated during our first run.\nworkspace.ResetWorkspace()\n\n# 1. Use the model helper to create a CNN for us\ntrain_model = model_helper.ModelHelper(\n    # Arbitrary name for referencing the network in your workspace: you could call it tacos or boatzncarz\n    name=\"train\",\n)\n\n# 2. Create a database reader\n# This training data reader is shared between all GPUs.\n# When reading data, the trainer runs ImageInputOp for each GPU to retrieve their own unique batch of training data.\n# CreateDB is inherited by cnn.ModelHelper from model_helper.py\n# We are going to name it \"train_reader\" and pass in the db configurations we set earlier\nreader = train_model.CreateDB(\n    \"train_reader\",\n    db=train_data_db,\n    db_type=train_data_db_type,\n)\n\n\n# In[ ]:\n\n\n# SOLUTION for Part 9\n# assumes you're using the functions created in Part 4, 5, 6\ndpm.Parallelize_GPU(\n    train_model,\n    input_builder_fun=add_image_input_ops,\n    forward_pass_builder_fun=create_resnet50_model_ops,\n    param_update_builder_fun=add_parameter_update_ops,\n    devices=gpus,\n    optimize_gradient_memory=True,\n)\n\nworkspace.RunNetOnce(train_model.param_init_net)\nworkspace.CreateNet(train_model.net)\n\n\n# In[ ]:\n\n\n# SOLUTION for Part 10\ntest_model = model_helper.ModelHelper(\n    name=\"test\",\n)\n\nreader = test_model.CreateDB(\n    \"test_reader\",\n    db=test_data_db,\n    db_type=test_data_db_type,\n)\n\n# Validation is parallelized across devices as well\ndpm.Parallelize_GPU(\n    test_model,\n    input_builder_fun=add_image_input_ops,\n    forward_pass_builder_fun=create_resnet50_model_ops,\n    param_update_builder_fun=None,\n    devices=gpus,\n)\n\nworkspace.RunNetOnce(test_model.param_init_net)\nworkspace.CreateNet(test_model.net)\n\n\n# In[ ]:\n\n\n# SOLUTION for Part 10 - display reporting setup\nfrom caffe2.python import visualize\nfrom matplotlib import pyplot as plt\n\ndef display_images_and_confidence():\n    images = []\n    confidences = []\n    n = 16\n    data = workspace.FetchBlob(\"gpu_0/data\")\n    label = workspace.FetchBlob(\"gpu_0/label\")\n    softmax = workspace.FetchBlob(\"gpu_0/softmax\")\n    for arr in zip(data[0:n], label[0:n], softmax[0:n]):\n        # CHW to HWC, normalize to [0.0, 1.0], and BGR to RGB\n        bgr = (arr[0].swapaxes(0, 1).swapaxes(1, 2) + 1.0) / 2.0\n        rgb = bgr[...,::-1]\n        images.append(rgb)\n        confidences.append(arr[2][arr[1]])\n\n    # Create grid for images\n    fig, rows = plt.subplots(nrows=4, ncols=4, figsize=(12, 12))\n    plt.tight_layout(h_pad=2)\n\n    # Display images and the models confidence in their label\n    items = zip([ax for cols in rows for ax in cols], images, confidences)\n    for (ax, image, confidence) in items:\n        ax.imshow(image)\n        if confidence >= 0.5:\n            ax.set_title(\"RIGHT ({:.1f}%)\".format(confidence * 100.0), color='green')\n        else:\n            ax.set_title(\"WRONG ({:.1f}%)\".format(confidence * 100.0), color='red')\n\n    plt.show()\n\n    \ndef accuracy(model):\n    accuracy = []\n    prefix = model.net.Proto().name\n    for device in model._devices:\n        accuracy.append(\n            np.asscalar(workspace.FetchBlob(\"gpu_{}/{}_accuracy\".format(device, prefix))))\n    return np.average(accuracy)\n\n\n# In[ ]:\n\n\n# SOLUTION for Part 11\n\n# Start looping through epochs where we run the batches of images to cover the entire dataset\n# Usually you would want to run a lot more epochs to increase your model's accuracy\nnum_epochs = 2\nfor epoch in range(num_epochs):\n    # Split up the images evenly: total images / batch size\n    num_iters = int(train_data_count / total_batch_size)\n    for iter in range(num_iters):\n        # Stopwatch start!\n        t1 = time.time()\n        # Run this iteration!\n        workspace.RunNet(train_model.net.Proto().name)\n        t2 = time.time()\n        dt = t2 - t1\n        \n        # Stopwatch stopped! How'd we do?\n        print((\n            \"Finished iteration {:>\" + str(len(str(num_iters))) + \"}/{}\" +\n            \" (epoch {:>\" + str(len(str(num_epochs))) + \"}/{})\" + \n            \" ({:.2f} images/sec)\").\n            format(iter+1, num_iters, epoch+1, num_epochs, total_batch_size/dt))\n        \n        # Get the average accuracy for the training model\n        train_accuracy = accuracy(train_model)\n    \n    # Run the test model and assess accuracy\n    test_accuracies = []\n    for _ in range(test_data_count / total_batch_size):\n        # Run the test model\n        workspace.RunNet(test_model.net.Proto().name)\n        test_accuracies.append(accuracy(test_model))\n    test_accuracy = np.average(test_accuracies)\n\n    print(\n        \"Train accuracy: {:.3f}, test accuracy: {:.3f}\".\n        format(train_accuracy, test_accuracy))\n    \n    # Output images with confidence scores as the caption\n    display_images_and_confidence()\n\n\n# ### TO DO:\n# (or things to explore on your own to improve this tutorial!)\n# * Create your own database of images\n# * Explore the layers\n# * Print out images of the intermediates/activations to show what's happening under the hood\n# * Make some interactions between epochs (change of params to show impact)\n\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/Python_Op.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# # Python Op Tutorial\n# In this tutorial we cover the Python operator that allows writing Caffe2 operators using Python, we also discuss some of the underlying implementation details.\n\n# ### Forward Python Operator, Net.Python() Interface\n\n# Caffe2 provides a high-level interface that helps creating Python ops. Let's consider the following example:\n\n# In[19]:\n\n\nfrom caffe2.python import core, workspace\nimport numpy as np\n\ndef f(inputs, outputs):\n    outputs[0].feed(2 * inputs[0].data)\n\nworkspace.ResetWorkspace()\nnet = core.Net(\"tutorial\")\nnet.Python(f)([\"x\"], [\"y\"])\nworkspace.FeedBlob(\"x\", np.array([3.]))\nworkspace.RunNetOnce(net)\nprint(workspace.FetchBlob(\"y\"))\n\n\n# As seen in the example, net.Python() function returns a callable that can be used just like any other operator. In this example, we add a new Python operator to the net with input \"x\" and output \"y\". Note that you can save the output of net.Python() and call it multiple times to add multiple Python operators (with possibly different inputs and outputs).\n\n# Let's take a closer look at net.Python() function and a corresponding body of a new Python operator (f). Every time net.Python(f) is called it serializes a given function f and saves it in a global registry under a known key (token, passed to a PythonOp as an argument). After this, net.Python() returns a lambda that accepts positional and keyword arguments (typically inputs, outputs and extra arguments) and attaches a new Python operator to the net that calls function f on a given list of inputs and outputs.\n\n# Python operator's function (f) expects two positional arguments: a list of inputs and a list of outputs. When an operator is executed it transparently converts Caffe2 blobs into the elements of these lists.\n# In case of CPU tensor blobs, these blobs are converted into TensorCPU objects that act as wrappers around Numpy arrays. Let's take a closer look at a relationship between Caffe2 CPU tensor, Python's TensorCPU object and a Numpy array:\n# 1. Conversion between C++ tensor objects and Numpy objects happens automatically and is handled by PyBind library.\n# 2. When generating a TensorCPU wrapper, a new Numpy array object is created which **shares** the same memory storage as a corresponding Caffe2 CPU tensor. This Numpy array is accessible in Python as a **.data** property of a TensorCPU object.\n# 3. Note that, although Numpy array and Caffe2 tensor might share the same storage, other tensor data (e.g. shape) of Caffe2 tensor is stored **separately** from a Numpy array. Furthermore, Numpy may copy and reallocate its array to a different location in memory (e.g. when we try to resize an array) during operator's function execution. It's important to keep that in mind when writing a Python operator's code to ensure that Caffe2 and Numpy output tensors are in sync.\n# 4. TensorCPU's **feed** method accepts a Numpy tensor, resizes an underying Caffe2 tensor and copies Numpy's tensor data into a Caffe2 tensor.\n# 5. Another way to ensure that Caffe2's output tensor is properly set is to call **reshape** function on a corresponding TensorCPU output and then copy data in Python to the output's **.data** tensor, e.g.:\n\n# In[27]:\n\n\ndef f_reshape(inputs, outputs):\n    outputs[0].reshape(inputs[0].shape)\n    outputs[0].data[...] = 2 * inputs[0].data\n\nworkspace.ResetWorkspace()\nnet = core.Net(\"tutorial\")\nnet.Python(f_reshape)([\"x\"], [\"z\"])\nworkspace.FeedBlob(\"x\", np.array([3.]))\nworkspace.RunNetOnce(net)\nprint(workspace.FetchBlob(\"z\"))\n\n\n# This example works correctly because \"reshape\" method updates an underlying Caffe2 tensor and a subsequent call to the \".data\" property returns a Numpy array that shares memory with a Caffe2 tensor. The last line in \"f_reshape\" copies data into the shared memory location.\n\n# There're several additional arguments that net.Python() accepts. When **pass_workspace=True** is passed, a workspace is passed to an operator's Python function:\n\n# In[28]:\n\n\ndef f_workspace(inputs, outputs, workspace):\n    outputs[0].feed(2 * workspace.blobs[\"x\"].fetch())\n\nworkspace.ResetWorkspace()\nnet = core.Net(\"tutorial\")\nnet.Python(f_workspace, pass_workspace=True)([], [\"y\"])\nworkspace.FeedBlob(\"x\", np.array([3.]))\nworkspace.RunNetOnce(net)\nprint(workspace.FetchBlob(\"y\"))\n\n\n# ### Gradient Python Operator\n\n# Another important net.Python() argument is \"grad_f\" - a Python function for a corresponding gradient operator:\n\n# In[29]:\n\n\ndef f(inputs, outputs):\n            outputs[0].reshape(inputs[0].shape)\n            outputs[0].data[...] = inputs[0].data * 2\n\ndef grad_f(inputs, outputs):\n    # Ordering of inputs is [fwd inputs, outputs, grad_outputs]\n    grad_output = inputs[2]\n\n    grad_input = outputs[0]\n    grad_input.reshape(grad_output.shape)\n    grad_input.data[...] = grad_output.data * 2\n\nworkspace.ResetWorkspace()\nnet = core.Net(\"tutorial\")\nnet.Python(f, grad_f)([\"x\"], [\"y\"])\nworkspace.FeedBlob(\"x\", np.array([3.]))\nnet.AddGradientOperators([\"y\"])\nworkspace.RunNetOnce(net)\nprint(workspace.FetchBlob(\"x_grad\"))\n\n\n# When net.Python() is called with a gradient function specified, it also registers a serialized gradient function that is used by a corresponding gradient Python operator (**PythonGradient**). This operator executes a gradient function that expects two arguments - input and output lists. The input list argument contains all forward function inputs, followed by all of its outputs, followed by the gradients of forward function outputs. The output list contains the gradients of forward function inputs. Note: net.Python()'s **grad_output_indices**/**grad_input_indices** allow specifying indices of gradient output/input blobs that gradient function reads/writes to.\n\n# #### Note on GPU tensors:\n\n# PythonOp implementation is CPU specific, it uses Numpy arrays that expect CPU memory storage. In order to be able to use a Python operator with GPU tensors, we define a CUDA version of PythonOp using GPUFallbackOp. This operator wraps a CPU-operator and adds GPU-to-CPU (and opposite direction) copy operations. Thus, when using a PythonOp with a CUDA device option, all input CUDA tensors are automatically copied to CPU memory and all CPU output tensors are copied back to GPU.\n\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/Toy_Regression.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# # Tutorial 2. A Simple Toy Regression\n# \n# This is a quick example showing how one can use the concepts introduced in [Tutorial 1: Basics](https://caffe2.ai/docs/tutorial-basics-of-caffe2.html) to do regression. This tutorial is split up into two parts. **Part I** is a more verbose example of creating and training a polynomial regression model and **Part II** is a concise linear regression example.\n# \n# ## Part I: Polynomial Regression\n# \n# The problem we are dealing with is a relatively simple one and involves a one-dimensional input $x$ and one-dimensional output $y.$ Because we seek a second order polynomial as the regression model, the weight vector will contain two weights ($\\beta_2$ and $\\beta_1$) and there will be a single bias ($\\beta_0$) or intercept. The desired solution is of the form:\n# \n# $$y = \\beta_2x^2 + \\beta_1x + \\beta_0$$\n# \n# For this tutorial, we will generate and format an arbitrary set of input data that possess a strong second order polynomial relationship. We will then construct the model, specify the training algorithm, perform the training, and finally look at the results.\n\n# In[1]:\n\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.python import workspace, brew, optimizer\nfrom caffe2.python.model_helper import ModelHelper\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport sklearn.datasets\nfrom sklearn.preprocessing import PolynomialFeatures\n\n\n# ### Inputs\n# \n# Specify the input parameters of the regression model here including: number of samples in the input data, number of training iterations, learning rate of SGD algorithm, and the initial weights of the model\n\n# In[2]:\n\n\n# Number of training sample to generate\nnum_samples = 200\n# Learning Rate of SGD algorithm\nlearning_rate = .05\n# Number of iterations to train\ntraining_iters = 100\n# Initial model weights\ninitial_weights = [0.,0.]\n\n\n# ### Create and Prepare the Dataset\n# \n# Now, we will create and prepare the dataset for use with the model. Note, we are just constructing numpy arrays here. Any other data can be used as long as it is shaped properly before being input into the model.\n\n# In[3]:\n\n\n# Create the original observations\norig_X,_ = sklearn.datasets.make_regression(n_samples=num_samples,n_features=1,noise=5)\npoly = PolynomialFeatures(degree=2, include_bias=False)\n# Transform the features into second order polynomial features\nxx_ = poly.fit_transform(orig_X)\n\n# Extract the predictors and the values from the manufactured data\nX = [i[0] for i in xx_]\nY_gt = [i[1] for i in xx_]\nnoise = np.random.uniform(size=(len(Y_gt)))\n# Add some noise to the ground truth values\nY_gt += noise\n\n# Shape the ground truth values for use with the model\nY_gt = np.reshape(Y_gt,(-1,1))\n# Format the input features. Recall, we accomplish polynomial regression by\n#   including the original and the polynomial version of the predictors\n#   as features of the model\nX = np.hstack((np.array(X).reshape(-1,1),np.array(X).reshape(-1,1)**2))\n\n# Print a sample of the input data. X is the list of 2-feature input observations \n#   and Y is the list of ground truth values associated with each observation\nprint(\"X Sample:\\n{}\".format(X[:5]))\nprint(\"Y Sample:\\n{}\".format(Y_gt[:5]))\n\n# Plot the input data\nplt.scatter([i[0] for i in X],Y_gt,label=\"original data\",color='b')\nplt.xlabel(\"x\")\nplt.ylabel(\"y\")\nplt.title(\"Input Training Data\")\n\n\n# ### Create the Model\n# \n# #### Define the model architecture\n# With our training data created and our second order polynomial assumption stated, we can now create a model to learn the regression line. We will use a 'FC' layer as the main component of the model. Since we desire two weights ($\\beta_2$ and $\\beta_1$), we set our input dimension to 2, and since we only expect a single quantitative result, our output dimension is 1. Note, when using an 'FC' layer it is implied that there is a bias, which we will use as our $\\beta_0.$\n# \n# Also, before continuing take a look at the protobuf created in this step. The first print out is of the 'net,' and contains the architecture of the model. At a glance, we see that as expected, there is a single op in the network that expects an input $X,$ a weight and bias, and outputs $y_{pred}.$ In the print out of the 'param_init_net,' we see that this is where the initializations for the weights and biases exist. This is an important observation that gives insight into how a model in Caffe2 is constructed and maintained.\n\n# In[4]:\n\n\n# Create the model helper object we will use to create the regression model\nregression_model = ModelHelper(name=\"regression_model\")\n\n# Add the FC layer, which is the main component of a linear regression model\ny_pred = brew.fc(regression_model,'X','y_pred', dim_in=2, dim_out=1)\n\n# Print the predict and init net to see what protobuf was created for this model\nprint(\"************* Predict Net *************\")\nprint(regression_model.net.Proto())\nprint(\"\\n************* Init Net *************\")\nprint(regression_model.param_init_net.Proto())\n\n\n# #### Add the training operators and prime the workspace\n# \n# In this **very important** step, we specify the loss function, setup the SGD training algorithm, prime and initialize the workspace, and initialize our model's weights and biases.\n\n# In[5]:\n\n\n# The loss function is computed by a squared L2 distance, \n#   and then averaged over all items.\ndist = regression_model.SquaredL2Distance(['Y_gt', y_pred], \"dist\")\nloss = regression_model.AveragedLoss(dist, \"loss\")\n\n# Add the gradient operators and setup the SGD algorithm\nregression_model.AddGradientOperators([loss])\noptimizer.build_sgd(regression_model, base_learning_rate=learning_rate)\n\n# Prime the workspace with some data\nworkspace.FeedBlob(\"Y_gt\",Y_gt.astype(np.float32))\nworkspace.FeedBlob(\"X\",X.astype(np.float32))\n\n# Run the init net to prepare the workspace then create the net\nworkspace.RunNetOnce(regression_model.param_init_net)\nworkspace.CreateNet(regression_model.net)\n\n# Inject our desired initial weights and bias\nworkspace.FeedBlob(\"y_pred_w\",np.array([initial_weights]).astype(np.float32))\nworkspace.FeedBlob(\"y_pred_b\",np.array([0.]).astype(np.float32))\n\n\n# #### Run the training\n\n# In[6]:\n\n\n# Run the training for training_iters\nfor i in range(training_iters):\n    workspace.RunNet(regression_model.net)\n\nprint(\"Training Complete\")\n\n\n# ### Extract Results\n# \n# Now that our model is trained, we can pull out the learned weights and biases which exist as blobs in the workspace named 'y_pred_w' and 'y_pred_b.'\n\n# In[7]:\n\n\n# Extract the learned coes and intercept from the workspace\ncoes = workspace.FetchBlob(\"y_pred_w\")[0]\nintercept = workspace.FetchBlob(\"y_pred_b\")\n\n# Calculate the regression line for plotting\nx_vals = np.linspace(orig_X.min(), orig_X.max(),100)\nregression_result = intercept[0] + coes[0]*x_vals + coes[1]*(x_vals**2)\nprint(\"Best Fit Line: {}*x^2 + {}*x + {}\".format(round(coes[1],5), round(coes[0],5), round(intercept[0],5)))\n\n# Plot the results of the regression\nplt.scatter([i[0] for i in X],Y_gt,label=\"original data\",color='b')\nplt.plot(x_vals,regression_result,label=\"regression result\",color='r')\nplt.legend()\nplt.xlabel(\"x\")\nplt.ylabel(\"y\")\nplt.title(\"Polynomial Regression Fit: ${{{}}}x^2 + {{{}}}x + {{{}}}$\".format(round(coes[1],5), round(coes[0],5), round(intercept[0],5)))\nplt.show()\n\n\n# ## Part II: Express Linear Regression Example\n# \n# The above example shows you how to create a polynomial regression model that is easily adapted to handle higher order polynomials. Now, we will consider the baseline case where we desire a simple first order model, with 1-D input $x,$ 1-D output $y,$ and a solution of the form:\n# \n# $$y = \\beta_1x + \\beta_0$$\n# \n# The structure of Part II will be similar to Part I. First, we will generate the dataset, then we will construct the model and specify the training routine, and finally we will train and extract our results.\n\n# In[8]:\n\n\n#####################################################################\n# Initialize data\n#####################################################################\nX,Y_gt = sklearn.datasets.make_regression(n_samples=100,n_features=1,noise=10)\nY_gt = np.reshape(Y_gt,(-1,1))\nY_gt /= 100.\n\n#####################################################################\n# Create and train model\n#####################################################################\n# Construct model with single FC layer\nregression_model = ModelHelper(name=\"regression_model\")\ny_pred = brew.fc(regression_model,'X','y_pred', dim_in=1, dim_out=1)\n\n# Specify Loss function\ndist = regression_model.SquaredL2Distance(['Y_gt', y_pred], \"dist\")\nloss = regression_model.AveragedLoss(dist, \"loss\")\n\n# Get gradients for all the computations above.\nregression_model.AddGradientOperators([loss])\noptimizer.build_sgd(regression_model, base_learning_rate=0.05)\n\n# Prime and prepare workspace for training\nworkspace.FeedBlob(\"Y_gt\",Y_gt.astype(np.float32))\nworkspace.FeedBlob(\"X\",X.astype(np.float32))\nworkspace.RunNetOnce(regression_model.param_init_net)\nworkspace.CreateNet(regression_model.net)\n\n# Set the initial weight and bias to 0\nworkspace.FeedBlob(\"y_pred_w\",np.array([[0.]]).astype(np.float32))\nworkspace.FeedBlob(\"y_pred_b\",np.array([0.]).astype(np.float32))\n\n# Train the model\nfor i in range(100):\n    workspace.RunNet(regression_model.net)\n\n#####################################################################\n# Collect and format results\n#####################################################################\n# Grab the learned weight and bias from workspace\ncoe = workspace.FetchBlob(\"y_pred_w\")[0]\nintercept = workspace.FetchBlob(\"y_pred_b\")\n\n# Calculate the regression line for plotting\nx_vals = range(-3,4)\nregression_result = x_vals*coe + intercept\n\n# Plot the results\nplt.scatter(X,Y_gt,label=\"original data\",color='b')\nplt.plot(x_vals,regression_result,label=\"regression result\",color='r')\nplt.legend()\nplt.xlabel(\"x\")\nplt.ylabel(\"y\")\nplt.title(\"Regression Line: ${{{}}}x + {{{}}}$\".format(round(coe,5), round(intercept[0],5)))\nplt.show()\n\n\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/Training_a_Model.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# ### Dataset Formats\n# \n# When you look at a model and its dataset, one of the things that will be specified is how the dataset is organized. Additionally, within Caffe2 when you load the data you will need to relay this specification. When trying to optimize training and increase its speed you may find discussions related to changing this format. For the purposes of this tutorial you don't need to worry about that, but it is good to recognize the different flavors and the fact the the raw data is loaded into temporary databases to facilitate the network's training and testing.\n# \n# #### Data Ordering\n# \n# * NCHW: [description]\n# * Others: [description]\n# \n# #### Databases\n# \n# * minidb: [description]\n# * leveldb: [descrption]\n# * others...\n# \n\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/create_your_own_dataset.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# # How do I create my own dataset?\n# \n# So Caffe2 uses a binary DB format to store the data that we would like to train models on. A Caffe2 DB is a glorified name of a key-value storage where the keys are usually randomized so that the batches are approximately i.i.d. The values are the real stuff here: they contain the serialized strings of the specific data formats that you would like your training algorithm to ingest. So, the stored DB would look (semantically) like this:\n# \n# key1 value1\n# key2 value2\n# key3 value3\n# ...\n# \n# To a DB, it treats the keys and values as strings, but you probably want structured contents. One way to do this is to use a TensorProtos protocol buffer: it essentially wraps Tensors, aka multi-dimensional arrays, together with the tensor data type and shape information. Then, one can use the TensorProtosDBInput operator to load the data into an SGD training fashion.\n# \n# Here, we will show you one example of how to create your own dataset. To this end, we will use the UCI Iris dataset - which was a very popular classical dataset for classifying Iris flowers. It contains 4 real-valued features representing the dimensions of the flower, and classifies things into 3 types of Iris flowers. The dataset can be downloaded [here](https://archive.ics.uci.edu/ml/datasets/Iris).\n\n# In[1]:\n\n\n# First let's import some necessities\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport urllib2 # for downloading the dataset from the web.\nimport numpy as np\nfrom matplotlib import pyplot\nfrom StringIO import StringIO\nfrom caffe2.python import core, utils, workspace\nfrom caffe2.proto import caffe2_pb2\n\n\n# In[2]:\n\n\nf = urllib2.urlopen('https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data')\nraw_data = f.read()\nprint('Raw data looks like this:')\nprint(raw_data[:100] + '...')\n\n\n# In[3]:\n\n\n# load the features to a feature matrix.\nfeatures = np.loadtxt(StringIO(raw_data), dtype=np.float32, delimiter=',', usecols=(0, 1, 2, 3))\n# load the labels to a feature matrix\nlabel_converter = lambda s : {'Iris-setosa':0, 'Iris-versicolor':1, 'Iris-virginica':2}[s]\nlabels = np.loadtxt(StringIO(raw_data), dtype=np.int, delimiter=',', usecols=(4,), converters={4: label_converter})\n\n\n# Before we do training, one thing that is often beneficial is to separate the dataset into training and testing. In this case, let's randomly shuffle the data, use the first 100 data points to do training, and the remaining 50 to do testing. For more sophisticated approaches, you can use e.g. cross validation to separate your dataset into multiple training and testing splits. Read more about cross validation [here](http://scikit-learn.org/stable/modules/cross_validation.html).\n\n# In[4]:\n\n\nrandom_index = np.random.permutation(150)\nfeatures = features[random_index]\nlabels = labels[random_index]\n\ntrain_features = features[:100]\ntrain_labels = labels[:100]\ntest_features = features[100:]\ntest_labels = labels[100:]\n\n\n# In[5]:\n\n\n# Let's plot the first two features together with the label.\n# Remember, while we are plotting the testing feature distribution\n# here too, you might not be supposed to do so in real research,\n# because one should not peek into the testing data.\nlegend = ['rx', 'b+', 'go']\npyplot.title(\"Training data distribution, feature 0 and 1\")\nfor i in range(3):\n    pyplot.plot(train_features[train_labels==i, 0], train_features[train_labels==i, 1], legend[i])\npyplot.figure()\npyplot.title(\"Testing data distribution, feature 0 and 1\")\nfor i in range(3):\n    pyplot.plot(test_features[test_labels==i, 0], test_features[test_labels==i, 1], legend[i])\n\n\n# Now, as promised, let's put things into a Caffe2 DB. In this DB, what would happen is that we will use \"train_xxx\" as the key, and use a TensorProtos object to store two tensors for each data point: one as the feature and one as the label. We will use Caffe2's Python DB interface to do so.\n\n# In[6]:\n\n\n# First, let's see how one can construct a TensorProtos protocol buffer from numpy arrays.\nfeature_and_label = caffe2_pb2.TensorProtos()\nfeature_and_label.protos.extend([\n    utils.NumpyArrayToCaffe2Tensor(features[0]),\n    utils.NumpyArrayToCaffe2Tensor(labels[0])])\nprint('This is what the tensor proto looks like for a feature and its label:')\nprint(str(feature_and_label))\nprint('This is the compact string that gets written into the db:')\nprint(feature_and_label.SerializeToString())\n\n\n# In[7]:\n\n\n# Now, actually write the db.\n\ndef write_db(db_type, db_name, features, labels):\n    db = core.C.create_db(db_type, db_name, core.C.Mode.write)\n    transaction = db.new_transaction()\n    for i in range(features.shape[0]):\n        feature_and_label = caffe2_pb2.TensorProtos()\n        feature_and_label.protos.extend([\n            utils.NumpyArrayToCaffe2Tensor(features[i]),\n            utils.NumpyArrayToCaffe2Tensor(labels[i])])\n        transaction.put(\n            'train_%03d'.format(i),\n            feature_and_label.SerializeToString())\n    # Close the transaction, and then close the db.\n    del transaction\n    del db\n\nwrite_db(\"minidb\", \"iris_train.minidb\", train_features, train_labels)\nwrite_db(\"minidb\", \"iris_test.minidb\", test_features, test_labels)\n\n\n# Now, let's create a very simple network that only consists of one single TensorProtosDBInput operator, to showcase how we load data from the DB that we created. For training, you might want to do something more complex: creating a network, train it, get the model, and run the prediction service. To this end you can look at the MNIST tutorial for details.\n\n# In[8]:\n\n\nnet_proto = core.Net(\"example_reader\")\ndbreader = net_proto.CreateDB([], \"dbreader\", db=\"iris_train.minidb\", db_type=\"minidb\")\nnet_proto.TensorProtosDBInput([dbreader], [\"X\", \"Y\"], batch_size=16)\n\nprint(\"The net looks like this:\")\nprint(str(net_proto.Proto()))\n\n\n# In[9]:\n\n\nworkspace.CreateNet(net_proto)\n\n\n# In[10]:\n\n\n# Let's run it to get batches of features.\nworkspace.RunNet(net_proto.Proto().name)\nprint(\"The first batch of feature is:\")\nprint(workspace.FetchBlob(\"X\"))\nprint(\"The first batch of label is:\")\nprint(workspace.FetchBlob(\"Y\"))\n\n# Let's run again.\nworkspace.RunNet(net_proto.Proto().name)\nprint(\"The second batch of feature is:\")\nprint(workspace.FetchBlob(\"X\"))\nprint(\"The second batch of label is:\")\nprint(workspace.FetchBlob(\"Y\"))\n\n\n"
  },
  {
    "path": "caffe2/python/tutorials/py_gen/sparseNN.py",
    "content": "#########################################################\n#\n# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\n# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\n#\n#########################################################\n\n\n# coding: utf-8\n\n# In[ ]:\n\n\nfrom caffe2.python import (\n    core,\n)\n\nfrom caffe2.python.fb.dper.layer_models.models import sparse_nn\nfrom fblearner.flow.projects.dper.preprocs.ads import build_preproc\nfrom fblearner.flow.projects.dper.preprocs.ads_feature_processor import (\n    ads_feature_processor,\n)\nfrom hiveio import par_init  # noqa\nimport fblearner.flow.projects.dper.flow_types as T\nimport fblearner.flow.projects.dper.utils.assemble as assemble_utils\nimport fblearner.flow.projects.dper.utils.data as data_utils\nimport fblearner.flow.projects.dper.utils.visualize as vis_utils\nimport fblearner.flow.projects.dper.workflows.ads_config as default_config\n\nimport fblearner.flow.projects.dper.ifbpy.compute_meta as compute_meta\nfrom fblearner.flow.projects.dper.ifbpy.execution import test_model_locally\nimport fblearner.flow.projects.dper.utils.visualize as vis_utils\nimport fblearner.flow.projects.dper.utils.perf_estimator_execution as perf_estimator_execution\n\nimport json\ncore.GlobalInit(['ifbpy'])\nfrom IPython.core.debugger import Pdb; \nipdb=Pdb()\n\n\n# In[ ]:\n\n\n# when testing a particular flow, load model options from json file, and pass it to model_options\n# local_prod_jason_file=\"/home/dongli/fbsource/fbcode/caffe2/caffe2/net_config/33252482/prod_model.json\"\n# with open(local_prod_jason_file, 'r') as f:\n#     prod_model_options = sparse_nn.MODEL_OPTIONS.decode(json.loads(f.read()))\n# print(prod_model_options)        \n\n\n\n# In[ ]:\n\n\npreproc_options = default_config.DEFAULT_PREPROC_OPTIONS\n\n# when testing a particular flow, load model options from json file\n# load model preproc options from json file\n# from fblearner.flow.projects.dper.preprocs.ads import build_preproc\n# local_prod_preproc_jason_file=\"/home/dongli/fbsource/fbcode/caffe2/caffe2/net_config/33252482/prod_preproc.json\"\n# with open(local_prod_preproc_jason_file, 'r') as f:\n#     prod_preproc_options = build_preproc.options_flow_type.decode(json.loads(f.read()))\n# print prod_preproc_options\n\n# preproc_options = prod_preproc_options\n\n\n# In[ ]:\n\n\n# Finalize config for preprocessor\ncompute_meta.resolve_compute_meta(ads_feature_processor, default_config.DEFAULT_DATASET, preproc_options)\nprint(\"Done: resolve_compute_meta\")\n\n\n# In[ ]:\n\n\n# Assemble the model given preprocessor and model building fuction\nmodel = assemble_utils.assemble_model(\n        name='sparse_nn',\n        input_feature_schema=build_preproc.input_feature_schema(\n            preproc_options),\n        trainer_extra_schema=build_preproc.trainer_extra_schema(\n            preproc_options),\n        build_preproc_fun=build_preproc,\n        build_model_fun=sparse_nn.build_model,\n        preproc_options=preproc_options,\n        model_options= default_config.DEFAULT_MODEL_OPTIONS\n)\n\n\n# In[ ]:\n\n\n# Train model one the given sample dataset\nestimated_cost = perf_estimator_execution.estimate_perf_locally(model, default_config.DEFAULT_DATASET)\nprint(estimated_cost)\n\n\n"
  },
  {
    "path": "caffe2/python/tutorials/start_ipython_notebook.sh",
    "content": "#!/usr/bin/env sh\n# This script simply starts the ipython notebook and allows all network machines\n# to access it.\n\n# Use the following command for very verbose prints.\n# GLOG_logtostderr=1 GLOG_v=1 PYTHONPATH=../../../gen:$PYTHONPATH ipython notebook --ip='*'\n\n# Use the following command for a normal run.\nPYTHONPATH=../../../gen:$PYTHONPATH ipython notebook --ip='*'\n"
  },
  {
    "path": "caffe2/python/tutorials/tutorials_to_script_converter.py",
    "content": "import os\nfrom os import listdir\nfrom subprocess import check_call\n\n\ndef convert_notebook(notebook_path):\n    # Generate .py file from a notebook\n    notebook_dir, notebook_file_name = os.path.split(notebook_path)\n    check_call(\n        ['jupyter', 'nbconvert', '--to', 'script', notebook_file_name],\n        cwd=notebook_dir,\n    )\n    py_name = os.path.splitext(notebook_file_name)[0] + \".py\"\n    full_py_name = os.path.join(notebook_dir, py_name)\n\n    # Create py_gen/ dir if it doesn't exist and move the file there\n    new_dir = os.path.join(notebook_dir, 'py_gen')\n    if not os.path.exists(new_dir):\n        os.makedirs(new_dir)\n    new_py_location = os.path.join(new_dir, py_name)\n    os.rename(full_py_name, new_py_location)\n    full_py_name = new_py_location\n\n    with open(full_py_name, 'r') as f:\n        data = f.read()\n    lines = data.split('\\n')\n    good_lines = []\n    for line in lines:\n        if (\"get_ipython().magic\" not in line\n                and \"get_ipython().run_line_magic\" not in line):\n            good_lines.append(line)\n    # Update the file with do not edit preamble\n    with open(full_py_name, 'w') as f:\n        f.write(\"#########################################################\\n\")\n        f.write(\"#\\n\")\n        f.write(\"# DO NOT EDIT THIS FILE. IT IS GENERATED AUTOMATICALLY. #\\n\")\n        f.write(\"# PLEASE LOOK INTO THE README FOR MORE INFORMATION.     #\\n\")\n        f.write(\"#\\n\")\n        f.write(\"#########################################################\\n\")\n        f.write(\"\\n\")\n\n        for line in good_lines:\n            f.write(line + '\\n')\n\n\ndef main():\n    tutorials_folder = os.path.dirname(os.path.realpath(__file__))\n    print(\"tutorials_folder: \", tutorials_folder)\n    files = [\n        os.path.join(tutorials_folder, f)\n        for f in listdir(tutorials_folder)\n        if os.path.isfile(os.path.join(tutorials_folder, f))\n        and f.endswith('ipynb')\n    ]\n    for f in files:\n        convert_notebook(f)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "caffe2/python/utils.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package utils\n# Module caffe2.python.utils\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom caffe2.proto import caffe2_pb2\nfrom future.utils import viewitems\nfrom google.protobuf.message import DecodeError, Message\nfrom google.protobuf import text_format\nimport sys\nimport collections\nimport functools\nimport numpy as np\nfrom six import integer_types, binary_type, text_type\n\n\ndef CaffeBlobToNumpyArray(blob):\n    if (blob.num != 0):\n        # old style caffe blob.\n        return (np.asarray(blob.data, dtype=np.float32)\n                .reshape(blob.num, blob.channels, blob.height, blob.width))\n    else:\n        # new style caffe blob.\n        return (np.asarray(blob.data, dtype=np.float32)\n                .reshape(blob.shape.dim))\n\n\ndef Caffe2TensorToNumpyArray(tensor):\n    if tensor.data_type == caffe2_pb2.TensorProto.FLOAT:\n        return np.asarray(\n            tensor.float_data, dtype=np.float32).reshape(tensor.dims)\n    elif tensor.data_type == caffe2_pb2.TensorProto.DOUBLE:\n        return np.asarray(\n            tensor.double_data, dtype=np.float64).reshape(tensor.dims)\n    elif tensor.data_type == caffe2_pb2.TensorProto.INT32:\n        return np.asarray(\n            tensor.int32_data, dtype=np.int).reshape(tensor.dims)   # pb.INT32=>np.int use int32_data\n    elif tensor.data_type == caffe2_pb2.TensorProto.INT16:\n        return np.asarray(\n            tensor.int32_data, dtype=np.int16).reshape(tensor.dims)  # pb.INT16=>np.int16 use int32_data\n    elif tensor.data_type == caffe2_pb2.TensorProto.UINT16:\n        return np.asarray(\n            tensor.int32_data, dtype=np.uint16).reshape(tensor.dims)  # pb.UINT16=>np.uint16 use int32_data\n    elif tensor.data_type == caffe2_pb2.TensorProto.INT8:\n        return np.asarray(\n            tensor.int32_data, dtype=np.int8).reshape(tensor.dims)  # pb.INT8=>np.int8 use int32_data\n    elif tensor.data_type == caffe2_pb2.TensorProto.UINT8:\n        return np.asarray(\n            tensor.int32_data, dtype=np.uint8).reshape(tensor.dims)  # pb.UINT8=>np.uint8 use int32_data\n    else:\n        # TODO: complete the data type: bool, float16, byte, int64, string\n        raise RuntimeError(\n            \"Tensor data type not supported yet: \" + str(tensor.data_type))\n\n\ndef NumpyArrayToCaffe2Tensor(arr, name=None):\n    tensor = caffe2_pb2.TensorProto()\n    tensor.dims.extend(arr.shape)\n    if name:\n        tensor.name = name\n    if arr.dtype == np.float32:\n        tensor.data_type = caffe2_pb2.TensorProto.FLOAT\n        tensor.float_data.extend(list(arr.flatten().astype(float)))\n    elif arr.dtype == np.float64:\n        tensor.data_type = caffe2_pb2.TensorProto.DOUBLE\n        tensor.double_data.extend(list(arr.flatten().astype(np.float64)))\n    elif arr.dtype == np.int or arr.dtype == np.int32:\n        tensor.data_type = caffe2_pb2.TensorProto.INT32\n        tensor.int32_data.extend(arr.flatten().astype(np.int).tolist())\n    elif arr.dtype == np.int16:\n        tensor.data_type = caffe2_pb2.TensorProto.INT16\n        tensor.int32_data.extend(list(arr.flatten().astype(np.int16)))  # np.int16=>pb.INT16 use int32_data\n    elif arr.dtype == np.uint16:\n        tensor.data_type = caffe2_pb2.TensorProto.UINT16\n        tensor.int32_data.extend(list(arr.flatten().astype(np.uint16)))  # np.uint16=>pb.UNIT16 use int32_data\n    elif arr.dtype == np.int8:\n        tensor.data_type = caffe2_pb2.TensorProto.INT8\n        tensor.int32_data.extend(list(arr.flatten().astype(np.int8)))   # np.int8=>pb.INT8 use int32_data\n    elif arr.dtype == np.uint8:\n        tensor.data_type = caffe2_pb2.TensorProto.UINT8\n        tensor.int32_data.extend(list(arr.flatten().astype(np.uint8)))   # np.uint8=>pb.UNIT8 use int32_data\n    else:\n        # TODO: complete the data type: bool, float16, byte, int64, string\n        raise RuntimeError(\n            \"Numpy data type not supported yet: \" + str(arr.dtype))\n    return tensor\n\n\ndef MakeArgument(key, value):\n    \"\"\"Makes an argument based on the value type.\"\"\"\n    argument = caffe2_pb2.Argument()\n    argument.name = key\n    iterable = isinstance(value, collections.Iterable)\n\n    # Fast tracking common use case where a float32 array of tensor parameters\n    # needs to be serialized.  The entire array is guaranteed to have the same\n    # dtype, so no per-element checking necessary and no need to convert each\n    # element separately.\n    if isinstance(value, np.ndarray) and value.dtype.type is np.float32:\n        argument.floats.extend(value.flatten().tolist())\n        return argument\n\n    if isinstance(value, np.ndarray):\n        value = value.flatten().tolist()\n    elif isinstance(value, np.generic):\n        # convert numpy scalar to native python type\n        value = np.asscalar(value)\n\n    if type(value) is float:\n        argument.f = value\n    elif type(value) in integer_types or type(value) is bool:\n        # We make a relaxation that a boolean variable will also be stored as\n        # int.\n        argument.i = value\n    elif isinstance(value, binary_type):\n        argument.s = value\n    elif isinstance(value, text_type):\n        argument.s = value.encode('utf-8')\n    elif isinstance(value, caffe2_pb2.NetDef):\n        argument.n.CopyFrom(value)\n    elif isinstance(value, Message):\n        argument.s = value.SerializeToString()\n    elif iterable and all(type(v) in [float, np.float_] for v in value):\n        argument.floats.extend(\n            v.item() if type(v) is np.float_ else v for v in value\n        )\n    elif iterable and all(\n        type(v) in integer_types or type(v) in [bool, np.int_] for v in value\n    ):\n        argument.ints.extend(\n            v.item() if type(v) is np.int_ else v for v in value\n        )\n    elif iterable and all(\n        isinstance(v, binary_type) or isinstance(v, text_type) for v in value\n    ):\n        argument.strings.extend(\n            v.encode('utf-8') if isinstance(v, text_type) else v\n            for v in value\n        )\n    elif iterable and all(isinstance(v, caffe2_pb2.NetDef) for v in value):\n        argument.nets.extend(value)\n    elif iterable and all(isinstance(v, Message) for v in value):\n        argument.strings.extend(v.SerializeToString() for v in value)\n    else:\n        if iterable:\n            raise ValueError(\n                \"Unknown iterable argument type: key={} value={}, value \"\n                \"type={}[{}]\".format(\n                    key, value, type(value), set(type(v) for v in value)\n                )\n            )\n        else:\n            raise ValueError(\n                \"Unknown argument type: key={} value={}, value type={}\".format(\n                    key, value, type(value)\n                )\n            )\n    return argument\n\n\ndef TryReadProtoWithClass(cls, s):\n    \"\"\"Reads a protobuffer with the given proto class.\n\n    Inputs:\n      cls: a protobuffer class.\n      s: a string of either binary or text protobuffer content.\n\n    Outputs:\n      proto: the protobuffer of cls\n\n    Throws:\n      google.protobuf.message.DecodeError: if we cannot decode the message.\n    \"\"\"\n    obj = cls()\n    try:\n        text_format.Parse(s, obj)\n        return obj\n    except text_format.ParseError:\n        obj.ParseFromString(s)\n        return obj\n\n\ndef GetContentFromProto(obj, function_map):\n    \"\"\"Gets a specific field from a protocol buffer that matches the given class\n    \"\"\"\n    for cls, func in viewitems(function_map):\n        if type(obj) is cls:\n            return func(obj)\n\n\ndef GetContentFromProtoString(s, function_map):\n    for cls, func in viewitems(function_map):\n        try:\n            obj = TryReadProtoWithClass(cls, s)\n            return func(obj)\n        except DecodeError:\n            continue\n    else:\n        raise DecodeError(\"Cannot find a fit protobuffer class.\")\n\n\ndef ConvertProtoToBinary(proto_class, filename, out_filename):\n    \"\"\"Convert a text file of the given protobuf class to binary.\"\"\"\n    proto = TryReadProtoWithClass(proto_class, open(filename).read())\n    with open(out_filename, 'w') as fid:\n        fid.write(proto.SerializeToString())\n\n\ndef GetGPUMemoryUsageStats():\n    \"\"\"Get GPU memory usage stats from CUDAContext. This requires flag\n       --caffe2_gpu_memory_tracking to be enabled\"\"\"\n    from caffe2.python import workspace, core\n    workspace.RunOperatorOnce(\n        core.CreateOperator(\n            \"GetGPUMemoryUsage\",\n            [],\n            [\"____mem____\"],\n            device_option=core.DeviceOption(caffe2_pb2.CUDA, 0),\n        ),\n    )\n    b = workspace.FetchBlob(\"____mem____\")\n    return {\n        'total_by_gpu': b[0, :],\n        'max_by_gpu': b[1, :],\n        'total': np.sum(b[0, :]),\n        'max_total': np.sum(b[1, :])\n    }\n\n\ndef ResetBlobs(blobs):\n    from caffe2.python import workspace, core\n    workspace.RunOperatorOnce(\n        core.CreateOperator(\n            \"Free\",\n            list(blobs),\n            list(blobs),\n            device_option=core.DeviceOption(caffe2_pb2.CPU),\n        ),\n    )\n\n\nclass DebugMode(object):\n    '''\n    This class allows to drop you into an interactive debugger\n    if there is an unhandled exception in your python script\n\n    Example of usage:\n\n    def main():\n        # your code here\n        pass\n\n    if __name__ == '__main__':\n        from caffe2.python.utils import DebugMode\n        DebugMode.run(main)\n    '''\n\n    @classmethod\n    def run(cls, func):\n        try:\n            return func()\n        except KeyboardInterrupt:\n            raise\n        except Exception:\n            import pdb\n\n            print(\n                'Entering interactive debugger. Type \"bt\" to print '\n                'the full stacktrace. Type \"help\" to see command listing.')\n            print(sys.exc_info()[1])\n            print\n\n            pdb.post_mortem()\n            sys.exit(1)\n            raise\n\ndef raiseIfNotEqual(a, b, msg):\n    if a != b:\n        raise Exception(\"{}. {} != {}\".format(msg, a, b))\n\ndef debug(f):\n    '''\n    Use this method to decorate your function with DebugMode's functionality\n\n    Example:\n\n    @debug\n    def test_foo(self):\n        raise Exception(\"Bar\")\n\n    '''\n\n    @functools.wraps(f)\n    def wrapper(*args, **kwargs):\n        def func():\n            return f(*args, **kwargs)\n        DebugMode.run(func)\n\n    return wrapper\n"
  },
  {
    "path": "caffe2/python/visualize.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package visualize\n# Module caffe2.python.visualize\n\"\"\"Functions that could be used to visualize Tensors.\n\nThis is adapted from the old-time iceberk package that Yangqing wrote... Oh gold\nmemories. Before decaf and caffe. Why iceberk? Because I was at Berkeley,\nbears are vegetarian, and iceberg lettuce has layers of leaves.\n\n(This joke is so lame.)\n\"\"\"\n\nimport numpy as np\nfrom matplotlib import cm, pyplot\n\n\ndef ChannelFirst(arr):\n    \"\"\"Convert a HWC array to CHW.\"\"\"\n    ndim = arr.ndim\n    return arr.swapaxes(ndim - 1, ndim - 2).swapaxes(ndim - 2, ndim - 3)\n\n\ndef ChannelLast(arr):\n    \"\"\"Convert a CHW array to HWC.\"\"\"\n    ndim = arr.ndim\n    return arr.swapaxes(ndim - 3, ndim - 2).swapaxes(ndim - 2, ndim - 1)\n\n\nclass PatchVisualizer(object):\n    \"\"\"PatchVisualizer visualizes patches.\n  \"\"\"\n\n    def __init__(self, gap=1):\n        self.gap = gap\n\n    def ShowSingle(self, patch, cmap=None):\n        \"\"\"Visualizes one single patch.\n\n    The input patch could be a vector (in which case we try to infer the shape\n    of the patch), a 2-D matrix, or a 3-D matrix whose 3rd dimension has 3\n    channels.\n    \"\"\"\n        if len(patch.shape) == 1:\n            patch = patch.reshape(self.get_patch_shape(patch))\n        elif len(patch.shape) > 2 and patch.shape[2] != 3:\n            raise ValueError(\"The input patch shape isn't correct.\")\n        # determine color\n        if len(patch.shape) == 2 and cmap is None:\n            cmap = cm.gray\n        pyplot.imshow(patch, cmap=cmap)\n        return patch\n\n    def ShowMultiple(self, patches, ncols=None, cmap=None, bg_func=np.mean):\n        \"\"\"Visualize multiple patches.\n\n    In the passed in patches matrix, each row is a patch, in the shape of either\n    n*n, n*n*1 or n*n*3, either in a flattened format (so patches would be a\n    2-D array), or a multi-dimensional tensor. We will try our best to figure\n    out automatically the patch size.\n    \"\"\"\n        num_patches = patches.shape[0]\n        if ncols is None:\n            ncols = int(np.ceil(np.sqrt(num_patches)))\n        nrows = int(np.ceil(num_patches / float(ncols)))\n        if len(patches.shape) == 2:\n            patches = patches.reshape(\n                (patches.shape[0], ) + self.get_patch_shape(patches[0])\n            )\n        patch_size_expand = np.array(patches.shape[1:3]) + self.gap\n        image_size = patch_size_expand * np.array([nrows, ncols]) - self.gap\n        if len(patches.shape) == 4:\n            if patches.shape[3] == 1:\n                # gray patches\n                patches = patches.reshape(patches.shape[:-1])\n                image_shape = tuple(image_size)\n                if cmap is None:\n                    cmap = cm.gray\n            elif patches.shape[3] == 3:\n                # color patches\n                image_shape = tuple(image_size) + (3, )\n            else:\n                raise ValueError(\"The input patch shape isn't expected.\")\n        else:\n            image_shape = tuple(image_size)\n            if cmap is None:\n                cmap = cm.gray\n        image = np.ones(image_shape) * bg_func(patches)\n        for pid in range(num_patches):\n            row = pid // ncols * patch_size_expand[0]\n            col = pid % ncols * patch_size_expand[1]\n            image[row:row+patches.shape[1], col:col+patches.shape[2]] = \\\n                patches[pid]\n        pyplot.imshow(image, cmap=cmap, interpolation='nearest')\n        pyplot.axis('off')\n        return image\n\n    def ShowImages(self, patches, *args, **kwargs):\n        \"\"\"Similar to ShowMultiple, but always normalize the values between 0 and 1\n    for better visualization of image-type data.\n    \"\"\"\n        patches = patches - np.min(patches)\n        patches /= np.max(patches) + np.finfo(np.float64).eps\n        return self.ShowMultiple(patches, *args, **kwargs)\n\n    def ShowChannels(self, patch, cmap=None, bg_func=np.mean):\n        \"\"\" This function shows the channels of a patch.\n\n    The incoming patch should have shape [w, h, num_channels], and each channel\n    will be visualized as a separate gray patch.\n    \"\"\"\n        if len(patch.shape) != 3:\n            raise ValueError(\"The input patch shape isn't correct.\")\n        patch_reordered = np.swapaxes(patch.T, 1, 2)\n        return self.ShowMultiple(patch_reordered, cmap=cmap, bg_func=bg_func)\n\n    def get_patch_shape(self, patch):\n        \"\"\"Gets the shape of a single patch.\n\n    Basically it tries to interprete the patch as a square, and also check if it\n    is in color (3 channels)\n    \"\"\"\n        edgeLen = np.sqrt(patch.size)\n        if edgeLen != np.floor(edgeLen):\n            # we are given color patches\n            edgeLen = np.sqrt(patch.size / 3.)\n            if edgeLen != np.floor(edgeLen):\n                raise ValueError(\"I can't figure out the patch shape.\")\n            return (edgeLen, edgeLen, 3)\n        else:\n            edgeLen = int(edgeLen)\n            return (edgeLen, edgeLen)\n\n\n_default_visualizer = PatchVisualizer()\n\"\"\"Utility functions that directly point to functions in the default visualizer.\n\nThese functions don't return anything, so you won't see annoying printouts of\nthe visualized images. If you want to save the images for example, you should\nexplicitly instantiate a patch visualizer, and call those functions.\n\"\"\"\n\n\nclass NHWC(object):\n    @staticmethod\n    def ShowSingle(*args, **kwargs):\n        _default_visualizer.ShowSingle(*args, **kwargs)\n\n    @staticmethod\n    def ShowMultiple(*args, **kwargs):\n        _default_visualizer.ShowMultiple(*args, **kwargs)\n\n    @staticmethod\n    def ShowImages(*args, **kwargs):\n        _default_visualizer.ShowImages(*args, **kwargs)\n\n    @staticmethod\n    def ShowChannels(*args, **kwargs):\n        _default_visualizer.ShowChannels(*args, **kwargs)\n\n\nclass NCHW(object):\n    @staticmethod\n    def ShowSingle(patch, *args, **kwargs):\n        _default_visualizer.ShowSingle(ChannelLast(patch), *args, **kwargs)\n\n    @staticmethod\n    def ShowMultiple(patch, *args, **kwargs):\n        _default_visualizer.ShowMultiple(ChannelLast(patch), *args, **kwargs)\n\n    @staticmethod\n    def ShowImages(patch, *args, **kwargs):\n        _default_visualizer.ShowImages(ChannelLast(patch), *args, **kwargs)\n\n    @staticmethod\n    def ShowChannels(patch, *args, **kwargs):\n        _default_visualizer.ShowChannels(ChannelLast(patch), *args, **kwargs)\n"
  },
  {
    "path": "caffe2/python/workspace.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\n## @package workspace\n# Module caffe2.python.workspace\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport contextlib\nfrom google.protobuf.message import Message\nfrom multiprocessing import Process\nimport os\nfrom collections import defaultdict\nimport logging\nimport numpy as np\nfrom past.builtins import basestring\nimport shutil\nimport socket\nimport tempfile\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import scope, utils\n\nimport caffe2.python._import_c_extension as C\n\nlogger = logging.getLogger(__name__)\n\nBlobs = C.blobs\nCreateBlob = C.create_blob\nCurrentWorkspace = C.current_workspace\nDeserializeBlob = C.deserialize_blob\nGlobalInit = C.global_init\nHasBlob = C.has_blob\nRegisteredOperators = C.registered_operators\nSerializeBlob = C.serialize_blob\nSwitchWorkspace = C.switch_workspace\nRootFolder = C.root_folder\nWorkspaces = C.workspaces\nBenchmarkNet = C.benchmark_net\nGetStats = C.get_stats\n\noperator_tracebacks = defaultdict(dict)\n\nis_asan = C.is_asan\nhas_gpu_support = C.has_gpu_support\nif has_gpu_support:\n    NumCudaDevices = C.num_cuda_devices\n    GetCUDAVersion = C.get_cuda_version\n    GetCuDNNVersion = C.get_cudnn_version\n\n    def GetCudaPeerAccessPattern():\n        return np.asarray(C.get_cuda_peer_access_pattern())\n\n    GetDeviceProperties = C.get_device_properties\nelse:\n    NumCudaDevices = lambda: 0 # noqa\n    GetCuDNNVersion = lambda: 0 # noqa\n    GetCuDNNVersion = lambda: 0 # noqa\n    GetCudaPeerAccessPattern = lambda: np.array([]) # noqa\n    GetDeviceProperties = lambda x: None # noqa\n\nIsNUMAEnabled = C.is_numa_enabled\nGetNumNUMANodes = C.get_num_numa_nodes\nGetBlobNUMANode = C.get_blob_numa_node\n\ndef _GetFreeFlaskPort():\n    \"\"\"Get a free flask port.\"\"\"\n    # We will prefer to use 5000. If not, we will then pick a random port.\n    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    result = sock.connect_ex(('127.0.0.1', 5000))\n    if result == 0:\n        return 5000\n    else:\n        s = socket.socket()\n        s.bind(('', 0))\n        port = s.getsockname()[1]\n        s.close()\n        # Race condition: between the interval we close the socket and actually\n        # start a mint process, another process might have occupied the port. We\n        # don't do much here as this is mostly for convenience in research\n        # rather than 24x7 service.\n        return port\n\n\ndef StartMint(root_folder=None, port=None):\n    \"\"\"Start a mint instance.\n\n    TODO(Yangqing): this does not work well under ipython yet. According to\n        https://github.com/ipython/ipython/issues/5862\n    writing up some fix is a todo item.\n    \"\"\"\n    from caffe2.python.mint import app\n    if root_folder is None:\n        # Get the root folder from the current workspace\n        root_folder = C.root_folder()\n    if port is None:\n        port = _GetFreeFlaskPort()\n    process = Process(\n        target=app.main,\n        args=(\n            ['-p', str(port), '-r', root_folder],\n        )\n    )\n    process.start()\n    print('Mint running at http://{}:{}'.format(socket.getfqdn(), port))\n    return process\n\n\ndef StringifyProto(obj):\n    \"\"\"Stringify a protocol buffer object.\n\n  Inputs:\n    obj: a protocol buffer object, or a Pycaffe2 object that has a Proto()\n        function.\n  Outputs:\n    string: the output protobuf string.\n  Raises:\n    AttributeError: if the passed in object does not have the right attribute.\n  \"\"\"\n    if isinstance(obj, basestring):\n        return obj\n    else:\n        if isinstance(obj, Message):\n            # First, see if this object is a protocol buffer, which we can\n            # simply serialize with the SerializeToString() call.\n            return obj.SerializeToString()\n        elif hasattr(obj, 'Proto'):\n            return obj.Proto().SerializeToString()\n        else:\n            raise ValueError(\"Unexpected argument to StringifyProto of type \" +\n                             type(obj).__name__)\n\n\ndef ResetWorkspace(root_folder=None):\n    if root_folder is None:\n        # Reset the workspace, but keep the current root folder setting.\n        return C.reset_workspace(C.root_folder())\n    else:\n        if not os.path.exists(root_folder):\n            os.makedirs(root_folder)\n        return C.reset_workspace(root_folder)\n\n\ndef CreateNet(net, overwrite=False, input_blobs=None):\n    if input_blobs is None:\n        input_blobs = []\n    for input_blob in input_blobs:\n        C.create_blob(input_blob)\n    return CallWithExceptionIntercept(\n        C.create_net,\n        C.Workspace.current._last_failed_op_net_position,\n        GetNetName(net),\n        StringifyProto(net), overwrite,\n    )\n\n\ndef Predictor(init_net, predict_net):\n    return C.Predictor(StringifyProto(init_net), StringifyProto(predict_net))\n\n\ndef GetOperatorCost(operator, blobs):\n    return C.get_operator_cost(StringifyProto(operator), blobs)\n\n\ndef RunOperatorOnce(operator):\n    return C.run_operator_once(StringifyProto(operator))\n\n\ndef RunOperatorsOnce(operators):\n    for op in operators:\n        success = RunOperatorOnce(op)\n        if not success:\n            return False\n    return True\n\n\ndef CallWithExceptionIntercept(func, op_id_fetcher, net_name, *args, **kwargs):\n    try:\n        return func(*args, **kwargs)\n    except Exception:\n        op_id = op_id_fetcher()\n        net_tracebacks = operator_tracebacks.get(net_name, None)\n        print('Original python traceback for operator {} in network `{}` in '\n              'exception above (most recent call last):'.format(\n                  op_id, net_name))\n        if net_tracebacks and op_id in net_tracebacks:\n            tb = net_tracebacks[op_id]\n            for line in reversed(tb):\n                print('  File \"{}\", line {}, in {}'.format(\n                    line[0], line[1], line[2]))\n        raise\n\n\ndef RunNetOnce(net):\n    return CallWithExceptionIntercept(\n        C.run_net_once,\n        C.Workspace.current._last_failed_op_net_position,\n        GetNetName(net),\n        StringifyProto(net),\n    )\n\n\ndef RunNet(name, num_iter=1, allow_fail=False):\n    \"\"\"Runs a given net.\n\n    Inputs:\n      name: the name of the net, or a reference to the net.\n      num_iter: number of iterations to run\n      allow_fail: if True, does not assert on net exec failure but returns False\n    Returns:\n      True or an exception.\n    \"\"\"\n    return CallWithExceptionIntercept(\n        C.run_net,\n        C.Workspace.current._last_failed_op_net_position,\n        GetNetName(name),\n        StringifyNetName(name), num_iter, allow_fail,\n    )\n\n\ndef RunPlan(plan_or_step):\n    # TODO(jiayq): refactor core.py/workspace.py to avoid circular deps\n    import caffe2.python.core as core\n    if isinstance(plan_or_step, core.ExecutionStep):\n        plan_or_step = core.Plan(plan_or_step)\n    return C.run_plan(StringifyProto(plan_or_step))\n\n\ndef InferShapesAndTypes(nets, blob_dimensions=None):\n    \"\"\"Infers the shapes and types for the specified nets.\n\n    Inputs:\n      nets: the list of nets\n      blob_dimensions (optional): a dictionary of blobs and their dimensions.\n          If not specified, the workspace blobs are used.\n    Returns:\n      A tuple of (shapes, types) dictionaries keyed by blob name.\n    \"\"\"\n    net_protos = [StringifyProto(n.Proto()) for n in nets]\n    if blob_dimensions is None:\n        blobdesc_prototxt = C.infer_shapes_and_types_from_workspace(net_protos)\n    else:\n        blobdesc_prototxt = C.infer_shapes_and_types_from_map(\n            net_protos, blob_dimensions\n        )\n    blobdesc_proto = caffe2_pb2.TensorShapes()\n    blobdesc_proto.ParseFromString(blobdesc_prototxt)\n    shapes = {}\n    types = {}\n    for ts in blobdesc_proto.shapes:\n        if not ts.unknown_shape:\n            shapes[ts.name] = list(ts.dims)\n            types[ts.name] = ts.data_type\n\n    return (shapes, types)\n\n\ndef _StringifyName(name, expected_type):\n    if isinstance(name, basestring):\n        return name\n    assert type(name).__name__ == expected_type, \\\n        \"Expected a string or %s\" % expected_type\n    return str(name)\n\n\ndef StringifyBlobName(name):\n    return _StringifyName(name, \"BlobReference\")\n\n\ndef StringifyNetName(name):\n    return _StringifyName(name, \"Net\")\n\n\ndef GetNetName(net):\n    if isinstance(net, basestring):\n        return net\n    if type(net).__name__ == \"Net\":\n        return net.Name()\n    if isinstance(net, caffe2_pb2.NetDef):\n        return net.name\n    raise Exception(\"Not a Net object: {}\".format(str(net)))\n\n\ndef FeedBlob(name, arr, device_option=None):\n    \"\"\"Feeds a blob into the workspace.\n\n    Inputs:\n      name: the name of the blob.\n      arr: either a TensorProto object or a numpy array object to be fed into\n          the workspace.\n      device_option (optional): the device option to feed the data with.\n    Returns:\n      True or False, stating whether the feed is successful.\n    \"\"\"\n    if type(arr) is caffe2_pb2.TensorProto:\n        arr = utils.Caffe2TensorToNumpyArray(arr)\n    if type(arr) is np.ndarray and arr.dtype.kind in 'SU':\n        # Plain NumPy strings are weird, let's use objects instead\n        arr = arr.astype(np.object)\n\n    if device_option is None:\n        device_option = scope.CurrentDeviceScope()\n\n    if device_option and device_option.device_type == caffe2_pb2.CUDA:\n        if arr.dtype == np.dtype('float64'):\n            logger.warning(\n                \"CUDA operators do not support 64-bit doubles, \" +\n                \"please use arr.astype(np.float32) or np.int32 for ints.\" +\n                \" Blob: {}\".format(name) +\n                \" type: {}\".format(str(arr.dtype))\n            )\n\n    name = StringifyBlobName(name)\n    if device_option is not None:\n        return C.feed_blob(name, arr, StringifyProto(device_option))\n    else:\n        return C.feed_blob(name, arr)\n\n\ndef FetchBlobs(names):\n    \"\"\"Fetches a list of blobs from the workspace.\n\n    Inputs:\n        names: list of names of blobs - strings or BlobReferences\n    Returns:\n        list of fetched blobs\n    \"\"\"\n    return [FetchBlob(name) for name in names]\n\n\ndef FetchBlob(name):\n    \"\"\"Fetches a blob from the workspace.\n\n    Inputs:\n      name: the name of the blob - a string or a BlobReference\n    Returns:\n      Fetched blob (numpy array or string) if successful\n    \"\"\"\n    return C.fetch_blob(StringifyBlobName(name))\n\n\ndef ApplyTransform(transform_key, net):\n    \"\"\"Apply a Transform to a NetDef protobuf object, and returns the new\n    transformed NetDef.\n\n    Inputs:\n      transform_key: the name of the transform, as it is stored in the registry\n      net: a NetDef protobuf object\n    Returns:\n      Transformed NetDef protobuf object.\n    \"\"\"\n    transformed_net = caffe2_pb2.NetDef()\n    transformed_str = C.apply_transform(\n        str(transform_key).encode('utf-8'),\n        net.SerializeToString(),\n    )\n    transformed_net.ParseFromString(transformed_str)\n    return transformed_net\n\n\ndef ApplyTransformIfFaster(transform_key, net, init_net, **kwargs):\n    \"\"\"Apply a Transform to a NetDef protobuf object, and returns the new\n    transformed NetDef, only if it runs faster than the original.\n\n    The runs are performed on the current active workspace (gWorkspace).\n    You should initialize that workspace before making a call to this function.\n\n    Inputs:\n      transform_key: the name of the transform, as it is stored in the registry\n      net: a NetDef protobuf object\n      init_net: The net to initialize the workspace.\n      warmup_runs (optional):\n        Determines how many times the net is run before testing.\n        Will be 5 by default.\n      main_runs (optional):\n        Determines how many times the net is run during testing.\n        Will be 10 by default.\n      improvement_threshold (optional):\n        Determines the factor which the new net needs to be faster\n        in order to replace the old. Will be 1.01 by default.\n\n    Returns:\n      Either a Transformed NetDef protobuf object, or the original netdef.\n    \"\"\"\n\n    warmup_runs = kwargs['warmup_runs'] if 'warmup_runs' in kwargs else 5\n    main_runs = kwargs['main_runs'] if 'main_runs' in kwargs else 10\n    improvement_threshold = kwargs['improvement_threshold'] \\\n        if 'improvement_threshold' in kwargs else 1.01\n\n    transformed_net = caffe2_pb2.NetDef()\n    transformed_str = C.apply_transform_if_faster(\n        str(transform_key).encode('utf-8'),\n        net.SerializeToString(),\n        init_net.SerializeToString(),\n        warmup_runs,\n        main_runs,\n        float(improvement_threshold),\n    )\n    transformed_net.ParseFromString(transformed_str)\n    return transformed_net\n\n\ndef GetNameScope():\n    \"\"\"Return the current namescope string. To be used to fetch blobs\"\"\"\n    return scope.CurrentNameScope()\n\n\nclass _BlobDict(object):\n    \"\"\"Provides python dict compatible way to do fetching and feeding\"\"\"\n\n    def __getitem__(self, key):\n        return FetchBlob(key)\n\n    def __setitem__(self, key, value):\n        return FeedBlob(key, value)\n\n    def __len__(self):\n        return len(C.blobs())\n\n    def __iter__(self):\n        return C.blobs().__iter__()\n\n    def __contains__(self, item):\n        return C.has_blob(item)\n\n\nblobs = _BlobDict()\n\n\n################################################################################\n# Utilities for immediate mode\n#\n# Caffe2's immediate mode implements the following behavior: between the two\n# function calls StartImmediate() and StopImmediate(), for any operator that is\n# called through CreateOperator(), we will also run that operator in a workspace\n# that is specific to the immediate mode. The user is explicitly expected to\n# make sure that these ops have proper inputs and outputs, i.e. one should not\n# run an op where an external input is not created or fed.\n#\n# Users can use FeedImmediate() and FetchImmediate() to interact with blobs\n# in the immediate workspace.\n#\n# Once StopImmediate() is called, all contents in the immediate workspace is\n# freed up so one can continue using normal runs.\n#\n# The immediate mode is solely for debugging purposes and support will be very\n# sparse.\n################################################################################\n\n_immediate_mode = False\n_immediate_workspace_name = \"_CAFFE2_IMMEDIATE\"\n_immediate_root_folder = ''\n\n\ndef IsImmediate():\n    return _immediate_mode\n\n\n@contextlib.contextmanager\ndef WorkspaceGuard(workspace_name):\n    current = CurrentWorkspace()\n    SwitchWorkspace(workspace_name, True)\n    yield\n    SwitchWorkspace(current)\n\n\ndef StartImmediate(i_know=False):\n    global _immediate_mode\n    global _immediate_root_folder\n    if IsImmediate():\n        # already in immediate mode. We will kill the previous one\n        # and start from fresh.\n        StopImmediate()\n    _immediate_mode = True\n    with WorkspaceGuard(_immediate_workspace_name):\n        _immediate_root_folder = tempfile.mkdtemp()\n        ResetWorkspace(_immediate_root_folder)\n    if i_know:\n        # if the user doesn't want to see the warning message, sure...\n        return\n    print(\"\"\"\n    Enabling immediate mode in caffe2 python is an EXTREMELY EXPERIMENTAL\n    feature and may very easily go wrong. This is because Caffe2 uses a\n    declarative way of defining operators and models, which is essentially\n    not meant to run things in an interactive way. Read the following carefully\n    to make sure that you understand the caveats.\n\n    (1) You need to make sure that the sequences of operators you create are\n    actually runnable sequentially. For example, if you create an op that takes\n    an input X, somewhere earlier you should have already created X.\n\n    (2) Caffe2 immediate uses one single workspace, so if the set of operators\n    you run are intended to be under different workspaces, they will not run.\n    To create boundaries between such use cases, you can call FinishImmediate()\n    and StartImmediate() manually to flush out everything no longer needed.\n\n    (3) Underlying objects held by the immediate mode may interfere with your\n    normal run. For example, if there is a leveldb that you opened in immediate\n    mode and did not close, your main run will fail because leveldb does not\n    support double opening. Immediate mode may also occupy a lot of memory esp.\n    on GPUs. Call FinishImmediate() as soon as possible when you no longer\n    need it.\n\n    (4) Immediate is designed to be slow. Every immediate call implicitly\n    creates a temp operator object, runs it, and destroys the operator. This\n    slow-speed run is by design to discourage abuse. For most use cases other\n    than debugging, do NOT turn on immediate mode.\n\n    (5) If there is anything FATAL happening in the underlying C++ code, the\n    immediate mode will immediately (pun intended) cause the runtime to crash.\n\n    Thus you should use immediate mode with extra care. If you still would\n    like to, have fun [https://xkcd.com/149/].\n    \"\"\")\n\n\ndef StopImmediate():\n    \"\"\"Stops an immediate mode run.\"\"\"\n    # Phew, that was a dangerous ride.\n    global _immediate_mode\n    global _immediate_root_folder\n    if not IsImmediate():\n        return\n    with WorkspaceGuard(_immediate_workspace_name):\n        ResetWorkspace()\n    shutil.rmtree(_immediate_root_folder)\n    _immediate_root_folder = ''\n    _immediate_mode = False\n\n\ndef ImmediateBlobs():\n    with WorkspaceGuard(_immediate_workspace_name):\n        return Blobs()\n\n\ndef RunOperatorImmediate(op):\n    with WorkspaceGuard(_immediate_workspace_name):\n        RunOperatorOnce(op)\n\n\ndef FetchImmediate(*args, **kwargs):\n    with WorkspaceGuard(_immediate_workspace_name):\n        return FetchBlob(*args, **kwargs)\n\n\ndef FeedImmediate(*args, **kwargs):\n    with WorkspaceGuard(_immediate_workspace_name):\n        return FeedBlob(*args, **kwargs)\n\n\n# CWorkspace utilities\n\ndef _Workspace_create_net_with_exception_intercept(ws, net, overwrite=False):\n    return CallWithExceptionIntercept(\n        ws._create_net,\n        ws._last_failed_op_net_position,\n        GetNetName(net),\n        StringifyProto(net), overwrite,\n    )\n\n\nC.Workspace.create_net = _Workspace_create_net_with_exception_intercept\n\n\ndef _Workspace_run(ws, obj):\n    if hasattr(obj, 'Proto'):\n        obj = obj.Proto()\n    if isinstance(obj, caffe2_pb2.PlanDef):\n        return ws._run_plan(obj.SerializeToString())\n    if isinstance(obj, caffe2_pb2.NetDef):\n        return CallWithExceptionIntercept(\n            ws._run_net,\n            ws._last_failed_op_net_position,\n            GetNetName(obj),\n            obj.SerializeToString(),\n        )\n        # return ws._run_net(obj.SerializeToString())\n    if isinstance(obj, caffe2_pb2.OperatorDef):\n        return ws._run_operator(obj.SerializeToString())\n    raise ValueError(\n        \"Don't know how to do Workspace.run() on {}\".format(type(obj)))\n\n\nC.Workspace.run = _Workspace_run\n\n\ndef _Blob_feed(blob, arg, device_option=None):\n    if device_option is not None:\n        device_option = StringifyProto(device_option)\n    return blob._feed(arg, device_option)\n\n\nC.Blob.feed = _Blob_feed\n"
  },
  {
    "path": "caffe2/python/workspace_test.py",
    "content": "# Copyright (c) 2016-present, Facebook, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n##############################################################################\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport numpy as np\nimport os\nimport unittest\n\nfrom caffe2.proto import caffe2_pb2\nfrom caffe2.python import core, test_util, workspace, model_helper, brew\n\nimport caffe2.python.hypothesis_test_util as htu\nimport hypothesis.strategies as st\nfrom hypothesis import given\n\n\nclass TestWorkspace(unittest.TestCase):\n    def setUp(self):\n        self.net = core.Net(\"test-net\")\n        self.testblob_ref = self.net.ConstantFill(\n            [], \"testblob\", shape=[1, 2, 3, 4], value=1.0)\n        workspace.ResetWorkspace()\n\n    def testRootFolder(self):\n        self.assertEqual(workspace.ResetWorkspace(), True)\n        self.assertEqual(workspace.RootFolder(), \".\")\n        self.assertEqual(\n            workspace.ResetWorkspace(\"/tmp/caffe-workspace-test\"), True)\n        self.assertEqual(workspace.RootFolder(), \"/tmp/caffe-workspace-test\")\n\n    def testWorkspaceHasBlobWithNonexistingName(self):\n        self.assertEqual(workspace.HasBlob(\"non-existing\"), False)\n\n    def testRunOperatorOnce(self):\n        self.assertEqual(\n            workspace.RunOperatorOnce(\n                self.net.Proto().op[0].SerializeToString()\n            ), True\n        )\n        self.assertEqual(workspace.HasBlob(\"testblob\"), True)\n        blobs = workspace.Blobs()\n        self.assertEqual(len(blobs), 1)\n        self.assertEqual(blobs[0], \"testblob\")\n\n    def testGetOperatorCost(self):\n        op = core.CreateOperator(\n            \"Conv2D\",\n            [\"X\", \"W\"], [\"Y\"],\n            stride_h=1,\n            stride_w=1,\n            pad_t=1,\n            pad_l=1,\n            pad_b=1,\n            pad_r=1,\n            kernel=3,\n        )\n        X = np.zeros((1, 8, 8, 8))\n        W = np.zeros((1, 1, 3, 3))\n        workspace.FeedBlob(\"X\", X)\n        workspace.FeedBlob(\"W\", W)\n        flops, _ = workspace.GetOperatorCost(op.SerializeToString(), [\"X\", \"W\"])\n        self.assertEqual(flops, 1152)\n\n    def testRunNetOnce(self):\n        self.assertEqual(\n            workspace.RunNetOnce(self.net.Proto().SerializeToString()), True)\n        self.assertEqual(workspace.HasBlob(\"testblob\"), True)\n\n    def testCurrentWorkspaceWrapper(self):\n        self.assertNotIn(\"testblob\", workspace.C.Workspace.current.blobs)\n        self.assertEqual(\n            workspace.RunNetOnce(self.net.Proto().SerializeToString()), True)\n        self.assertEqual(workspace.HasBlob(\"testblob\"), True)\n        self.assertIn(\"testblob\", workspace.C.Workspace.current.blobs)\n        workspace.ResetWorkspace()\n        self.assertNotIn(\"testblob\", workspace.C.Workspace.current.blobs)\n\n    def testRunPlan(self):\n        plan = core.Plan(\"test-plan\")\n        plan.AddStep(core.ExecutionStep(\"test-step\", self.net))\n        self.assertEqual(\n            workspace.RunPlan(plan.Proto().SerializeToString()), True)\n        self.assertEqual(workspace.HasBlob(\"testblob\"), True)\n\n    def testConstructPlanFromSteps(self):\n        step = core.ExecutionStep(\"test-step-as-plan\", self.net)\n        self.assertEqual(workspace.RunPlan(step), True)\n        self.assertEqual(workspace.HasBlob(\"testblob\"), True)\n\n    def testResetWorkspace(self):\n        self.assertEqual(\n            workspace.RunNetOnce(self.net.Proto().SerializeToString()), True)\n        self.assertEqual(workspace.HasBlob(\"testblob\"), True)\n        self.assertEqual(workspace.ResetWorkspace(), True)\n        self.assertEqual(workspace.HasBlob(\"testblob\"), False)\n\n    def testTensorAccess(self):\n        ws = workspace.C.Workspace()\n\n        \"\"\" test in-place modification \"\"\"\n        ws.create_blob(\"tensor\").feed(np.array([1.1, 1.2, 1.3]))\n        tensor = ws.blobs[\"tensor\"].tensor()\n        tensor.data[0] = 3.3\n        val = np.array([3.3, 1.2, 1.3])\n        np.testing.assert_array_equal(tensor.data, val)\n        np.testing.assert_array_equal(ws.blobs[\"tensor\"].fetch(), val)\n\n        \"\"\" test in-place initialization \"\"\"\n        tensor.init([2, 3], core.DataType.INT32)\n        tensor.data[1, 1] = 100\n        val = np.zeros([2, 3], dtype=np.int32)\n        val[1, 1] = 100\n        np.testing.assert_array_equal(tensor.data, val)\n        np.testing.assert_array_equal(ws.blobs[\"tensor\"].fetch(), val)\n\n        \"\"\" strings cannot be initialized from python \"\"\"\n        with self.assertRaises(RuntimeError):\n            tensor.init([3, 4], core.DataType.STRING)\n\n        \"\"\" feed (copy) data into tensor \"\"\"\n        val = np.array([[b'abc', b'def'], [b'ghi', b'jkl']], dtype=np.object)\n        tensor.feed(val)\n        self.assertEquals(tensor.data[0, 0], b'abc')\n        np.testing.assert_array_equal(ws.blobs[\"tensor\"].fetch(), val)\n\n        val = np.array([1.1, 10.2])\n        tensor.feed(val)\n        val[0] = 5.2\n        self.assertEquals(tensor.data[0], 1.1)\n\n        \"\"\" fetch (copy) data from tensor \"\"\"\n        val = np.array([1.1, 1.2])\n        tensor.feed(val)\n        val2 = tensor.fetch()\n        tensor.data[0] = 5.2\n        val3 = tensor.fetch()\n        np.testing.assert_array_equal(val, val2)\n        self.assertEquals(val3[0], 5.2)\n\n    def testFetchFeedBlob(self):\n        self.assertEqual(\n            workspace.RunNetOnce(self.net.Proto().SerializeToString()), True)\n        fetched = workspace.FetchBlob(\"testblob\")\n        # check if fetched is correct.\n        self.assertEqual(fetched.shape, (1, 2, 3, 4))\n        np.testing.assert_array_equal(fetched, 1.0)\n        fetched[:] = 2.0\n        self.assertEqual(workspace.FeedBlob(\"testblob\", fetched), True)\n        fetched_again = workspace.FetchBlob(\"testblob\")\n        self.assertEqual(fetched_again.shape, (1, 2, 3, 4))\n        np.testing.assert_array_equal(fetched_again, 2.0)\n\n    def testFetchFeedBlobViaBlobReference(self):\n        self.assertEqual(\n            workspace.RunNetOnce(self.net.Proto().SerializeToString()), True)\n        fetched = workspace.FetchBlob(self.testblob_ref)\n        # check if fetched is correct.\n        self.assertEqual(fetched.shape, (1, 2, 3, 4))\n        np.testing.assert_array_equal(fetched, 1.0)\n        fetched[:] = 2.0\n        self.assertEqual(workspace.FeedBlob(self.testblob_ref, fetched), True)\n        fetched_again = workspace.FetchBlob(\"testblob\")  # fetch by name now\n        self.assertEqual(fetched_again.shape, (1, 2, 3, 4))\n        np.testing.assert_array_equal(fetched_again, 2.0)\n\n    def testFetchFeedBlobTypes(self):\n        for dtype in [np.float16, np.float32, np.float64, np.bool,\n                      np.int8, np.int16, np.int32, np.int64,\n                      np.uint8, np.uint16]:\n            try:\n                rng = np.iinfo(dtype).max * 2\n            except ValueError:\n                rng = 1000\n            data = ((np.random.rand(2, 3, 4) - 0.5) * rng).astype(dtype)\n            self.assertEqual(workspace.FeedBlob(\"testblob_types\", data), True)\n            fetched_back = workspace.FetchBlob(\"testblob_types\")\n            self.assertEqual(fetched_back.shape, (2, 3, 4))\n            self.assertEqual(fetched_back.dtype, dtype)\n            np.testing.assert_array_equal(fetched_back, data)\n\n    def testFetchFeedBlobBool(self):\n        \"\"\"Special case for bool to ensure coverage of both true and false.\"\"\"\n        data = np.zeros((2, 3, 4)).astype(np.bool)\n        data.flat[::2] = True\n        self.assertEqual(workspace.FeedBlob(\"testblob_types\", data), True)\n        fetched_back = workspace.FetchBlob(\"testblob_types\")\n        self.assertEqual(fetched_back.shape, (2, 3, 4))\n        self.assertEqual(fetched_back.dtype, np.bool)\n        np.testing.assert_array_equal(fetched_back, data)\n\n    def testFetchFeedBlobZeroDim(self):\n        data = np.empty(shape=(2, 0, 3), dtype=np.float32)\n        self.assertEqual(workspace.FeedBlob(\"testblob_empty\", data), True)\n        fetched_back = workspace.FetchBlob(\"testblob_empty\")\n        self.assertEqual(fetched_back.shape, (2, 0, 3))\n        self.assertEqual(fetched_back.dtype, np.float32)\n\n    def testFetchFeedLongStringTensor(self):\n        # long strings trigger array of object creation\n        strs = np.array([\n            b' '.join(10 * [b'long string']),\n            b' '.join(128 * [b'very long string']),\n            b'small \\0\\1\\2 string',\n            b\"Hello, world! I have special \\0 symbols \\1!\"])\n        workspace.FeedBlob('my_str_tensor', strs)\n        strs2 = workspace.FetchBlob('my_str_tensor')\n        self.assertEqual(strs.shape, strs2.shape)\n        for i in range(0, strs.shape[0]):\n            self.assertEqual(strs[i], strs2[i])\n\n    def testFetchFeedShortStringTensor(self):\n        # small strings trigger NPY_STRING array\n        strs = np.array([b'elem1', b'elem 2', b'element 3'])\n        workspace.FeedBlob('my_str_tensor_2', strs)\n        strs2 = workspace.FetchBlob('my_str_tensor_2')\n        self.assertEqual(strs.shape, strs2.shape)\n        for i in range(0, strs.shape[0]):\n            self.assertEqual(strs[i], strs2[i])\n\n    def testFetchFeedPlainString(self):\n        # this is actual string, not a tensor of strings\n        s = b\"Hello, world! I have special \\0 symbols \\1!\"\n        workspace.FeedBlob('my_plain_string', s)\n        s2 = workspace.FetchBlob('my_plain_string')\n        self.assertEqual(s, s2)\n\n    def testFetchBlobs(self):\n        s1 = b\"test1\"\n        s2 = b\"test2\"\n        workspace.FeedBlob('s1', s1)\n        workspace.FeedBlob('s2', s2)\n        fetch1, fetch2 = workspace.FetchBlobs(['s1', 's2'])\n        self.assertEquals(s1, fetch1)\n        self.assertEquals(s2, fetch2)\n\n    def testFetchFeedViaBlobDict(self):\n        self.assertEqual(\n            workspace.RunNetOnce(self.net.Proto().SerializeToString()), True)\n        fetched = workspace.blobs[\"testblob\"]\n        # check if fetched is correct.\n        self.assertEqual(fetched.shape, (1, 2, 3, 4))\n        np.testing.assert_array_equal(fetched, 1.0)\n        fetched[:] = 2.0\n        workspace.blobs[\"testblob\"] = fetched\n        fetched_again = workspace.blobs[\"testblob\"]\n        self.assertEqual(fetched_again.shape, (1, 2, 3, 4))\n        np.testing.assert_array_equal(fetched_again, 2.0)\n\n        self.assertTrue(\"testblob\" in workspace.blobs)\n        self.assertFalse(\"non_existant\" in workspace.blobs)\n        self.assertEqual(len(workspace.blobs), 1)\n        for key in workspace.blobs:\n            self.assertEqual(key, \"testblob\")\n\n\nclass TestMultiWorkspaces(unittest.TestCase):\n    def setUp(self):\n        workspace.SwitchWorkspace(\"default\")\n        workspace.ResetWorkspace()\n\n    def testCreateWorkspace(self):\n        self.net = core.Net(\"test-net\")\n        self.net.ConstantFill([], \"testblob\", shape=[1, 2, 3, 4], value=1.0)\n        self.assertEqual(\n            workspace.RunNetOnce(self.net.Proto().SerializeToString()), True\n        )\n        self.assertEqual(workspace.HasBlob(\"testblob\"), True)\n        self.assertEqual(workspace.SwitchWorkspace(\"test\", True), None)\n        self.assertEqual(workspace.HasBlob(\"testblob\"), False)\n        self.assertEqual(workspace.SwitchWorkspace(\"default\"), None)\n        self.assertEqual(workspace.HasBlob(\"testblob\"), True)\n\n        try:\n            # The following should raise an error.\n            workspace.SwitchWorkspace(\"non-existing\")\n            # so this should never happen.\n            self.assertEqual(True, False)\n        except RuntimeError:\n            pass\n\n        workspaces = workspace.Workspaces()\n        self.assertTrue(\"default\" in workspaces)\n        self.assertTrue(\"test\" in workspaces)\n\n\n@unittest.skipIf(not workspace.has_gpu_support, \"No gpu support.\")\nclass TestWorkspaceGPU(test_util.TestCase):\n\n    def setUp(self):\n        workspace.ResetWorkspace()\n        self.net = core.Net(\"test-net\")\n        self.net.ConstantFill([], \"testblob\", shape=[1, 2, 3, 4], value=1.0)\n        self.net.RunAllOnGPU()\n\n    def testFetchBlobGPU(self):\n        self.assertEqual(\n            workspace.RunNetOnce(self.net.Proto().SerializeToString()), True)\n        fetched = workspace.FetchBlob(\"testblob\")\n        # check if fetched is correct.\n        self.assertEqual(fetched.shape, (1, 2, 3, 4))\n        np.testing.assert_array_equal(fetched, 1.0)\n        fetched[:] = 2.0\n        self.assertEqual(workspace.FeedBlob(\"testblob\", fetched), True)\n        fetched_again = workspace.FetchBlob(\"testblob\")\n        self.assertEqual(fetched_again.shape, (1, 2, 3, 4))\n        np.testing.assert_array_equal(fetched_again, 2.0)\n\n    def testGetCudaPeerAccessPattern(self):\n        pattern = workspace.GetCudaPeerAccessPattern()\n        self.assertEqual(type(pattern), np.ndarray)\n        self.assertEqual(pattern.ndim, 2)\n        self.assertEqual(pattern.shape[0], pattern.shape[1])\n        self.assertEqual(pattern.shape[0], workspace.NumCudaDevices())\n\n\n@unittest.skipIf(not workspace.C.has_mkldnn, \"No MKLDNN support.\")\nclass TestWorkspaceMKLDNN(test_util.TestCase):\n\n    def testFeedFetchBlobMKLDNN(self):\n        arr = np.random.randn(2, 3).astype(np.float32)\n        workspace.FeedBlob(\n            \"testblob_mkldnn\", arr, core.DeviceOption(caffe2_pb2.MKLDNN))\n        fetched = workspace.FetchBlob(\"testblob_mkldnn\")\n        np.testing.assert_array_equal(arr, fetched)\n\n\nclass TestImmedibate(test_util.TestCase):\n    def testImmediateEnterExit(self):\n        workspace.StartImmediate(i_know=True)\n        self.assertTrue(workspace.IsImmediate())\n        workspace.StopImmediate()\n        self.assertFalse(workspace.IsImmediate())\n\n    def testImmediateRunsCorrectly(self):\n        workspace.StartImmediate(i_know=True)\n        net = core.Net(\"test-net\")\n        net.ConstantFill([], \"testblob\", shape=[1, 2, 3, 4], value=1.0)\n        self.assertEqual(\n            workspace.ImmediateBlobs(), [\"testblob\"])\n        content = workspace.FetchImmediate(\"testblob\")\n        # Also, the immediate mode should not invade the original namespace,\n        # so we check if this is so.\n        with self.assertRaises(RuntimeError):\n            workspace.FetchBlob(\"testblob\")\n        np.testing.assert_array_equal(content, 1.0)\n        content[:] = 2.0\n        self.assertTrue(workspace.FeedImmediate(\"testblob\", content))\n        np.testing.assert_array_equal(\n            workspace.FetchImmediate(\"testblob\"), 2.0)\n        workspace.StopImmediate()\n        with self.assertRaises(RuntimeError):\n            content = workspace.FetchImmediate(\"testblob\")\n\n    def testImmediateRootFolder(self):\n        workspace.StartImmediate(i_know=True)\n        # for testing we will look into the _immediate_root_folder variable\n        # but in normal usage you should not access that.\n        self.assertTrue(len(workspace._immediate_root_folder) > 0)\n        root_folder = workspace._immediate_root_folder\n        self.assertTrue(os.path.isdir(root_folder))\n        workspace.StopImmediate()\n        self.assertTrue(len(workspace._immediate_root_folder) == 0)\n        # After termination, immediate mode should have the root folder\n        # deleted.\n        self.assertFalse(os.path.exists(root_folder))\n\n\nclass TestCppEnforceAsException(test_util.TestCase):\n    def testEnforce(self):\n        op = core.CreateOperator(\"Relu\", [\"X\"], [\"Y\"])\n        with self.assertRaises(RuntimeError):\n            workspace.RunOperatorOnce(op)\n\n\nclass TestCWorkspace(htu.HypothesisTestCase):\n    def test_net_execution(self):\n        ws = workspace.C.Workspace()\n        self.assertEqual(ws.nets, {})\n        self.assertEqual(ws.blobs, {})\n        net = core.Net(\"test-net\")\n        net.ConstantFill([], \"testblob\", shape=[1, 2, 3, 4], value=1.0)\n        ws.create_net(net)\n        # If we do not specify overwrite, this should raise an error.\n        with self.assertRaises(RuntimeError):\n            ws.create_net(net)\n        # But, if we specify overwrite, this should pass.\n        ws.create_net(net, True)\n        # Overwrite can also be a kwarg.\n        ws.create_net(net, overwrite=True)\n        self.assertIn(\"testblob\", ws.blobs)\n        self.assertEqual(len(ws.nets), 1)\n        net_name = net.Proto().name\n        self.assertIn(\"test-net\", net_name)\n        net = ws.nets[net_name].run()\n        blob = ws.blobs[\"testblob\"]\n        np.testing.assert_array_equal(\n            np.ones((1, 2, 3, 4), dtype=np.float32),\n            blob.fetch())\n\n    @given(name=st.text(), value=st.floats(min_value=-1, max_value=1.0))\n    def test_operator_run(self, name, value):\n        ws = workspace.C.Workspace()\n        op = core.CreateOperator(\n            \"ConstantFill\", [], [name], shape=[1], value=value)\n        ws.run(op)\n        self.assertIn(name, ws.blobs)\n        np.testing.assert_allclose(\n            [value], ws.blobs[name].fetch(), atol=1e-4, rtol=1e-4)\n\n    @given(blob_name=st.text(),\n           net_name=st.text(),\n           value=st.floats(min_value=-1, max_value=1.0))\n    def test_net_run(self, blob_name, net_name, value):\n        ws = workspace.C.Workspace()\n        net = core.Net(net_name)\n        net.ConstantFill([], [blob_name], shape=[1], value=value)\n        ws.run(net)\n        self.assertIn(blob_name, ws.blobs)\n        self.assertNotIn(net_name, ws.nets)\n        np.testing.assert_allclose(\n            [value], ws.blobs[blob_name].fetch(), atol=1e-4, rtol=1e-4)\n\n    @given(blob_name=st.text(),\n           net_name=st.text(),\n           plan_name=st.text(),\n           value=st.floats(min_value=-1, max_value=1.0))\n    def test_plan_run(self, blob_name, plan_name, net_name, value):\n        ws = workspace.C.Workspace()\n        plan = core.Plan(plan_name)\n        net = core.Net(net_name)\n        net.ConstantFill([], [blob_name], shape=[1], value=value)\n\n        plan.AddStep(core.ExecutionStep(\"step\", nets=[net], num_iter=1))\n\n        ws.run(plan)\n        self.assertIn(blob_name, ws.blobs)\n        self.assertIn(net.Name(), ws.nets)\n        np.testing.assert_allclose(\n            [value], ws.blobs[blob_name].fetch(), atol=1e-4, rtol=1e-4)\n\n    @given(blob_name=st.text(),\n           net_name=st.text(),\n           value=st.floats(min_value=-1, max_value=1.0))\n    def test_net_create(self, blob_name, net_name, value):\n        ws = workspace.C.Workspace()\n        net = core.Net(net_name)\n        net.ConstantFill([], [blob_name], shape=[1], value=value)\n        ws.create_net(net).run()\n        self.assertIn(blob_name, ws.blobs)\n        self.assertIn(net.Name(), ws.nets)\n        np.testing.assert_allclose(\n            [value], ws.blobs[blob_name].fetch(), atol=1e-4, rtol=1e-4)\n\n    @given(name=st.text(),\n           value=htu.tensor(),\n           device_option=st.sampled_from(htu.device_options))\n    def test_array_serde(self, name, value, device_option):\n        ws = workspace.C.Workspace()\n        ws.create_blob(name).feed(value, device_option=device_option)\n        self.assertIn(name, ws.blobs)\n        blob = ws.blobs[name]\n        np.testing.assert_equal(value, ws.blobs[name].fetch())\n        serde_blob = ws.create_blob(\"{}_serde\".format(name))\n        serde_blob.deserialize(blob.serialize(name))\n        np.testing.assert_equal(value, serde_blob.fetch())\n\n    @given(name=st.text(), value=st.text())\n    def test_string_serde(self, name, value):\n        value = value.encode('ascii', 'ignore')\n        ws = workspace.C.Workspace()\n        ws.create_blob(name).feed(value)\n        self.assertIn(name, ws.blobs)\n        blob = ws.blobs[name]\n        self.assertEqual(value, ws.blobs[name].fetch())\n        serde_blob = ws.create_blob(\"{}_serde\".format(name))\n        serde_blob.deserialize(blob.serialize(name))\n        self.assertEqual(value, serde_blob.fetch())\n\n    def test_exception(self):\n        ws = workspace.C.Workspace()\n\n        with self.assertRaises(TypeError):\n            ws.create_net(\"...\")\n\n\nclass TestPredictor(unittest.TestCase):\n    def _create_model(self):\n        m = model_helper.ModelHelper()\n        y = brew.fc(m, \"data\", \"y\",\n                    dim_in=4, dim_out=2,\n                    weight_init=('ConstantFill', dict(value=1.0)),\n                    bias_init=('ConstantFill', dict(value=0.0)),\n                    axis=0)\n        m.net.AddExternalOutput(y)\n        return m\n\n    # Use this test with a bigger model to see how using Predictor allows to\n    # avoid issues with low protobuf size limit in Python\n    #\n    # def test_predictor_predefined(self):\n    #     workspace.ResetWorkspace()\n    #     path = 'caffe2/caffe2/test/assets/'\n    #     with open(path + 'squeeze_predict_net.pb') as f:\n    #         self.predict_net = f.read()\n    #     with open(path + 'squeeze_init_net.pb') as f:\n    #         self.init_net = f.read()\n    #     self.predictor = workspace.Predictor(self.init_net, self.predict_net)\n\n    #     inputs = [np.zeros((1, 3, 256, 256), dtype='f')]\n    #     outputs = self.predictor.run(inputs)\n    #     self.assertEqual(len(outputs), 1)\n    #     self.assertEqual(outputs[0].shape, (1, 1000, 1, 1))\n    #     self.assertAlmostEqual(outputs[0][0][0][0][0], 5.19026289e-05)\n\n    def test_predictor_memory_model(self):\n        workspace.ResetWorkspace()\n        m = self._create_model()\n        workspace.FeedBlob(\"data\", np.zeros([4], dtype='float32'))\n        self.predictor = workspace.Predictor(\n            workspace.StringifyProto(m.param_init_net.Proto()),\n            workspace.StringifyProto(m.net.Proto()))\n\n        inputs = np.array([1, 3, 256, 256], dtype='float32')\n        outputs = self.predictor.run([inputs])\n        np.testing.assert_array_almost_equal(\n            np.array([[516, 516]], dtype='float32'), outputs)\n\n\nclass TestTransform(htu.HypothesisTestCase):\n    @given(input_dim=st.integers(min_value=1, max_value=10),\n           output_dim=st.integers(min_value=1, max_value=10),\n           batch_size=st.integers(min_value=1, max_value=10))\n    def test_simple_transform(self, input_dim, output_dim, batch_size):\n        m = model_helper.ModelHelper()\n        fc1 = brew.fc(m, \"data\", \"fc1\", dim_in=input_dim, dim_out=output_dim)\n        fc2 = brew.fc(m, fc1, \"fc2\", dim_in=output_dim, dim_out=output_dim)\n        conv = brew.conv(m, fc2, \"conv\",\n                            dim_in=output_dim,\n                            dim_out=output_dim,\n                            use_cudnn=True,\n                            engine=\"CUDNN\",\n                            kernel=3)\n\n        conv.Relu([], conv)\\\n           .Softmax([], \"pred\") \\\n           .LabelCrossEntropy([\"label\"], [\"xent\"]) \\\n           .AveragedLoss([], \"loss\")\n\n        transformed_net_proto = workspace.ApplyTransform(\n            \"ConvToNNPack\",\n            m.net.Proto())\n\n        self.assertEqual(transformed_net_proto.op[2].engine, \"NNPACK\")\n\n    @given(input_dim=st.integers(min_value=1, max_value=10),\n           output_dim=st.integers(min_value=1, max_value=10),\n           batch_size=st.integers(min_value=1, max_value=10))\n    def test_registry_invalid(self, input_dim, output_dim, batch_size):\n        m = model_helper.ModelHelper()\n        brew.fc(m, \"data\", \"fc1\", dim_in=input_dim, dim_out=output_dim)\n        with self.assertRaises(RuntimeError):\n            workspace.ApplyTransform(\n                \"definitely_not_a_real_transform\",\n                m.net.Proto())\n\n    @given(value=st.floats(min_value=-1, max_value=1))\n    def test_apply_transform_if_faster(self, value):\n\n        init_net = core.Net(\"init_net\")\n        init_net.ConstantFill([], [\"data\"], shape=[5, 5, 5, 5], value=value)\n        init_net.ConstantFill([], [\"conv_w\"], shape=[5, 5, 3, 3], value=value)\n        init_net.ConstantFill([], [\"conv_b\"], shape=[5], value=value)\n\n        self.assertEqual(\n            workspace.RunNetOnce(init_net.Proto().SerializeToString()), True)\n\n        m = model_helper.ModelHelper()\n        conv = brew.conv(m, \"data\", \"conv\",\n                            dim_in=5,\n                            dim_out=5,\n                            kernel=3,\n                            use_cudnn=True,\n                            engine=\"CUDNN\")\n\n        conv.Relu([], conv)\\\n           .Softmax([], \"pred\") \\\n           .AveragedLoss([], \"loss\")\n\n        self.assertEqual(\n            workspace.RunNetOnce(m.net.Proto().SerializeToString()), True)\n\n        proto = workspace.ApplyTransformIfFaster(\n            \"ConvToNNPack\",\n            m.net.Proto(),\n            init_net.Proto())\n        self.assertEqual(\n            workspace.RunNetOnce(proto.SerializeToString()), True)\n        proto = workspace.ApplyTransformIfFaster(\n            \"ConvToNNPack\",\n            m.net.Proto(),\n            init_net.Proto(),\n            warmup_runs=10,\n            main_runs=100,\n            improvement_threshold=2.0)\n        self.assertEqual(\n            workspace.RunNetOnce(proto.SerializeToString()), True)\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "caffe2/queue/CMakeLists.txt",
    "content": "# ---[ GPU files\n# ------[ cuDNN\nfile(GLOB tmp *_cudnn.cc)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# ------[ general GPU\nfile(GLOB tmp *_gpu.cc)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# ------[ CUDA sources\nfile(GLOB tmp *.cu)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# exclude test files\nfile(GLOB tmp *_test.cc)\nexclude(Caffe2_GPU_SRCS \"${Caffe2_GPU_SRCS}\" ${tmp})\n\n# ---[ CPU files.\nfile(GLOB tmp *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp})\n# exclude test files and gpu files\nfile(GLOB tmp *_test.cc)\nexclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${tmp})\nexclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${Caffe2_GPU_SRCS})\n\n# ---[ GPU test files\nfile(GLOB tmp *_gpu_test.cc)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} ${tmp})\n\n# ---[ CPU test files\nfile(GLOB tmp *_test.cc)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${tmp})\nexclude(Caffe2_CPU_TEST_SRCS \"${Caffe2_CPU_TEST_SRCS}\" ${Caffe2_GPU_TEST_SRCS})\n\n# ---[ Send the lists to the parent scope.\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/queue/blobs_queue.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/queue/blobs_queue.h\"\n\n#include <atomic>\n#include <condition_variable>\n#include <memory>\n#include <mutex>\n#include <queue>\n\n#include \"caffe2/core/blob_stats.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/stats.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/timer.h\"\n#include \"caffe2/core/workspace.h\"\n\nnamespace caffe2 {\n\n// Constants for user tracepoints\nstatic constexpr int SDT_NONBLOCKING_OP = 0;\nstatic constexpr int SDT_BLOCKING_OP = 1;\nstatic constexpr uint64_t SDT_TIMEOUT = (uint64_t)-1;\nstatic constexpr uint64_t SDT_ABORT = (uint64_t)-2;\nstatic constexpr uint64_t SDT_CANCEL = (uint64_t)-3;\n\nBlobsQueue::BlobsQueue(\n    Workspace* ws,\n    const std::string& queueName,\n    size_t capacity,\n    size_t numBlobs,\n    bool enforceUniqueName,\n    const std::vector<std::string>& fieldNames)\n    : numBlobs_(numBlobs), name_(queueName), stats_(queueName) {\n  if (!fieldNames.empty()) {\n    CAFFE_ENFORCE_EQ(\n        fieldNames.size(), numBlobs, \"Wrong number of fieldNames provided.\");\n    stats_.queue_dequeued_bytes.setDetails(fieldNames);\n  }\n  queue_.reserve(capacity);\n  for (auto i = 0; i < capacity; ++i) {\n    std::vector<Blob*> blobs;\n    blobs.reserve(numBlobs);\n    for (auto j = 0; j < numBlobs; ++j) {\n      const auto blobName = queueName + \"_\" + to_string(i) + \"_\" + to_string(j);\n      if (enforceUniqueName) {\n        CAFFE_ENFORCE(\n            !ws->GetBlob(blobName),\n            \"Queue internal blob already exists: \",\n            blobName);\n      }\n      blobs.push_back(ws->CreateBlob(blobName));\n    }\n    queue_.push_back(blobs);\n  }\n  DCHECK_EQ(queue_.size(), capacity);\n}\n\nbool BlobsQueue::blockingRead(\n    const std::vector<Blob*>& inputs,\n    float timeout_secs) {\n  Timer readTimer;\n  auto keeper = this->shared_from_this();\n  const auto& name = name_.c_str();\n  CAFFE_SDT(queue_read_start, name, (void*)this, SDT_BLOCKING_OP);\n  std::unique_lock<std::mutex> g(mutex_);\n  auto canRead = [this]() {\n    CAFFE_ENFORCE_LE(reader_, writer_);\n    return reader_ != writer_;\n  };\n  // Decrease queue balance before reading to indicate queue read pressure\n  // is being increased (-ve queue balance indicates more reads than writes)\n  CAFFE_EVENT(stats_, queue_balance, -1);\n  if (timeout_secs > 0) {\n    std::chrono::milliseconds timeout_ms(int(timeout_secs * 1000));\n    cv_.wait_for(\n        g, timeout_ms, [this, canRead]() { return closing_ || canRead(); });\n  } else {\n    cv_.wait(g, [this, canRead]() { return closing_ || canRead(); });\n  }\n  if (!canRead()) {\n    if (timeout_secs > 0 && !closing_) {\n      LOG(ERROR) << \"DequeueBlobs timed out in \" << timeout_secs << \" secs\";\n      CAFFE_SDT(queue_read_end, name, (void*)this, SDT_TIMEOUT);\n    } else {\n      CAFFE_SDT(queue_read_end, name, (void*)this, SDT_CANCEL);\n    }\n    return false;\n  }\n  DCHECK(canRead());\n  auto& result = queue_[reader_ % queue_.size()];\n  CAFFE_ENFORCE(inputs.size() >= result.size());\n  for (auto i = 0; i < result.size(); ++i) {\n    auto bytes = BlobStat::sizeBytes(*result[i]);\n    CAFFE_EVENT(stats_, queue_dequeued_bytes, bytes, i);\n    using std::swap;\n    swap(*(inputs[i]), *(result[i]));\n  }\n  CAFFE_SDT(queue_read_end, name, (void*)this, writer_ - reader_);\n  CAFFE_EVENT(stats_, queue_dequeued_records);\n  ++reader_;\n  cv_.notify_all();\n  CAFFE_EVENT(stats_, read_time_ns, readTimer.NanoSeconds());\n  return true;\n}\n\nbool BlobsQueue::tryWrite(const std::vector<Blob*>& inputs) {\n  Timer writeTimer;\n  auto keeper = this->shared_from_this();\n  const auto& name = name_.c_str();\n  CAFFE_SDT(queue_write_start, name, (void*)this, SDT_NONBLOCKING_OP);\n  std::unique_lock<std::mutex> g(mutex_);\n  if (!canWrite()) {\n    CAFFE_SDT(queue_write_end, name, (void*)this, SDT_ABORT);\n    return false;\n  }\n  // Increase queue balance before writing to indicate queue write pressure is\n  // being increased (+ve queue balance indicates more writes than reads)\n  CAFFE_EVENT(stats_, queue_balance, 1);\n  DCHECK(canWrite());\n  doWrite(inputs);\n  CAFFE_EVENT(stats_, write_time_ns, writeTimer.NanoSeconds());\n  return true;\n}\n\nbool BlobsQueue::blockingWrite(const std::vector<Blob*>& inputs) {\n  Timer writeTimer;\n  auto keeper = this->shared_from_this();\n  const auto& name = name_.c_str();\n  CAFFE_SDT(queue_write_start, name, (void*)this, SDT_BLOCKING_OP);\n  std::unique_lock<std::mutex> g(mutex_);\n  // Increase queue balance before writing to indicate queue write pressure is\n  // being increased (+ve queue balance indicates more writes than reads)\n  CAFFE_EVENT(stats_, queue_balance, 1);\n  cv_.wait(g, [this]() { return closing_ || canWrite(); });\n  if (!canWrite()) {\n    CAFFE_SDT(queue_write_end, name, (void*)this, SDT_ABORT);\n    return false;\n  }\n  DCHECK(canWrite());\n  doWrite(inputs);\n  CAFFE_EVENT(stats_, write_time_ns, writeTimer.NanoSeconds());\n  return true;\n}\n\nvoid BlobsQueue::close() {\n  closing_ = true;\n\n  std::lock_guard<std::mutex> g(mutex_);\n  cv_.notify_all();\n}\n\nbool BlobsQueue::canWrite() {\n  // writer is always within [reader, reader + size)\n  // we can write if reader is within [reader, reader + size)\n  CAFFE_ENFORCE_LE(reader_, writer_);\n  CAFFE_ENFORCE_LE(writer_, reader_ + queue_.size());\n  return writer_ != reader_ + queue_.size();\n}\n\nvoid BlobsQueue::doWrite(const std::vector<Blob*>& inputs) {\n  auto& result = queue_[writer_ % queue_.size()];\n  CAFFE_ENFORCE(inputs.size() >= result.size());\n  const auto& name = name_.c_str();\n  for (auto i = 0; i < result.size(); ++i) {\n    using std::swap;\n    swap(*(inputs[i]), *(result[i]));\n  }\n  CAFFE_SDT(\n      queue_write_end, name, (void*)this, reader_ + queue_.size() - writer_);\n  ++writer_;\n  cv_.notify_all();\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/queue/blobs_queue.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <atomic>\n#include <condition_variable>\n#include <memory>\n#include <mutex>\n#include <queue>\n\n#include \"caffe2/core/blob_stats.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/stats.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/core/workspace.h\"\n\nnamespace caffe2 {\n\n// A thread-safe, bounded, blocking queue.\n// Modelled as a circular buffer.\n\n// Containing blobs are owned by the workspace.\n// On read, we swap out the underlying data for the blob passed in for blobs\n\nclass BlobsQueue : public std::enable_shared_from_this<BlobsQueue> {\n public:\n  BlobsQueue(\n      Workspace* ws,\n      const std::string& queueName,\n      size_t capacity,\n      size_t numBlobs,\n      bool enforceUniqueName,\n      const std::vector<std::string>& fieldNames = {});\n\n  ~BlobsQueue() {\n    close();\n  }\n\n  bool blockingRead(\n      const std::vector<Blob*>& inputs,\n      float timeout_secs = 0.0f);\n  bool tryWrite(const std::vector<Blob*>& inputs);\n  bool blockingWrite(const std::vector<Blob*>& inputs);\n  void close();\n  size_t getNumBlobs() const {\n    return numBlobs_;\n  }\n\n private:\n  bool canWrite();\n  void doWrite(const std::vector<Blob*>& inputs);\n\n  std::atomic<bool> closing_{false};\n\n  size_t numBlobs_;\n  std::mutex mutex_; // protects all variables in the class.\n  std::condition_variable cv_;\n  int64_t reader_{0};\n  int64_t writer_{0};\n  std::vector<std::vector<Blob*>> queue_;\n  const std::string name_;\n\n  struct QueueStats {\n    CAFFE_STAT_CTOR(QueueStats);\n    CAFFE_EXPORTED_STAT(queue_balance);\n    CAFFE_EXPORTED_STAT(queue_dequeued_records);\n    CAFFE_DETAILED_EXPORTED_STAT(queue_dequeued_bytes);\n    CAFFE_AVG_EXPORTED_STAT(read_time_ns);\n    CAFFE_AVG_EXPORTED_STAT(write_time_ns);\n  } stats_;\n};\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/queue/blobs_queue_db.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/queue/blobs_queue_db.h\"\n\n#include <algorithm>\n#include <chrono>\n#include <random>\n#include <string>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/queue/blobs_queue.h\"\n\nnamespace caffe2 {\nnamespace db {\n\ntemplate <class Context>\nclass CreateBlobsQueueDBOp : public Operator<CPUContext> {\n public:\n  CreateBlobsQueueDBOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    std::unique_ptr<db::DB> db = caffe2::make_unique<BlobsQueueDB>(\n        \"\",\n        db::READ,\n        OperatorBase::Input<std::shared_ptr<BlobsQueue>>(0),\n        OperatorBase::template GetSingleArgument<int>(\"key_blob_index\", -1),\n        OperatorBase::template GetSingleArgument<int>(\"value_blob_index\", 0),\n        OperatorBase::template GetSingleArgument<float>(\"timeout_secs\", 0.0));\n    OperatorBase::Output<db::DBReader>(0)->Open(std::move(db), 1, 0);\n    return true;\n  }\n\n private:\n  DISABLE_COPY_AND_ASSIGN(CreateBlobsQueueDBOp);\n};\n\nREGISTER_CPU_OPERATOR(CreateBlobsQueueDB, CreateBlobsQueueDBOp<CPUContext>);\n\nOPERATOR_SCHEMA(CreateBlobsQueueDB)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .Arg(\n        \"key_blob_index\",\n        \"(default: -1 (no key)) index of blob for DB key in the BlobsQueue.\")\n    .Arg(\n        \"value_blob_index\",\n        \"(default: 0) index of blob for DB value in the BlobsQueue.\")\n    .Arg(\n        \"timeout_secs\",\n        \"(default: 0.0 (no timeout)) Timeout in seconds for reading from the \"\n        \"BlobsQueue.\")\n    .SetDoc(\"Create a DBReader from a BlobsQueue\")\n    .Input(0, \"queue\", \"The shared pointer to a queue containing Blobs.\")\n    .Output(0, \"reader\", \"The DBReader for the given BlobsQueue\");\n\nSHOULD_NOT_DO_GRADIENT(CreateBlobsQueueDB);\n\n} // namespace db\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/queue/blobs_queue_db.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include <chrono>\n#include <string>\n\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/stats.h\"\n#include \"caffe2/queue/blobs_queue.h\"\n\nnamespace caffe2 {\nnamespace db {\n\nnamespace {\nconst std::string& GetStringFromBlob(Blob* blob) {\n  if (blob->template IsType<string>()) {\n    return blob->template Get<string>();\n  } else if (blob->template IsType<Tensor<CPUContext>>()) {\n    return *blob->template Get<Tensor<CPUContext>>().template data<string>();\n  } else {\n    CAFFE_THROW(\"Unsupported Blob type\");\n  }\n}\n}\n\nclass BlobsQueueDBCursor : public Cursor {\n public:\n  explicit BlobsQueueDBCursor(\n      std::shared_ptr<BlobsQueue> queue,\n      int key_blob_index,\n      int value_blob_index,\n      float timeout_secs)\n      : queue_(queue),\n        key_blob_index_(key_blob_index),\n        value_blob_index_(value_blob_index),\n        timeout_secs_(timeout_secs),\n        inited_(false),\n        valid_(false) {\n    LOG(INFO) << \"BlobsQueueDBCursor constructed\";\n    CAFFE_ENFORCE(queue_ != nullptr, \"queue is null\");\n    CAFFE_ENFORCE(value_blob_index_ >= 0, \"value_blob_index < 0\");\n  }\n\n  virtual ~BlobsQueueDBCursor() {}\n\n  void Seek(const string& /* unused */) override {\n    CAFFE_THROW(\"Seek is not supported.\");\n  }\n\n  bool SupportsSeek() override {\n    return false;\n  }\n\n  void SeekToFirst() override {\n    // not applicable\n  }\n\n  void Next() override {\n    unique_ptr<Blob> blob = make_unique<Blob>();\n    vector<Blob*> blob_vector{blob.get()};\n    auto success = queue_->blockingRead(blob_vector, timeout_secs_);\n    if (!success) {\n      LOG(ERROR) << \"Timed out reading from BlobsQueue or it is closed\";\n      valid_ = false;\n      return;\n    }\n\n    if (key_blob_index_ >= 0) {\n      key_ = GetStringFromBlob(blob_vector[key_blob_index_]);\n    }\n    value_ = GetStringFromBlob(blob_vector[value_blob_index_]);\n    valid_ = true;\n  }\n\n  string key() override {\n    if (!inited_) {\n      Next();\n      inited_ = true;\n    }\n    return key_;\n  }\n\n  string value() override {\n    if (!inited_) {\n      Next();\n      inited_ = true;\n    }\n    return value_;\n  }\n\n  bool Valid() override {\n    return valid_;\n  }\n\n private:\n  std::shared_ptr<BlobsQueue> queue_;\n  int key_blob_index_;\n  int value_blob_index_;\n  float timeout_secs_;\n  bool inited_;\n  string key_;\n  string value_;\n  bool valid_;\n};\n\nclass BlobsQueueDB : public DB {\n public:\n  BlobsQueueDB(\n      const string& source,\n      Mode mode,\n      std::shared_ptr<BlobsQueue> queue,\n      int key_blob_index = -1,\n      int value_blob_index = 0,\n      float timeout_secs = 0.0)\n      : DB(source, mode),\n        queue_(queue),\n        key_blob_index_(key_blob_index),\n        value_blob_index_(value_blob_index),\n        timeout_secs_(timeout_secs) {\n    LOG(INFO) << \"BlobsQueueDB constructed\";\n  }\n\n  virtual ~BlobsQueueDB() {\n    Close();\n  }\n\n  void Close() override {}\n  unique_ptr<Cursor> NewCursor() override {\n    return make_unique<BlobsQueueDBCursor>(\n        queue_, key_blob_index_, value_blob_index_, timeout_secs_);\n  }\n\n  unique_ptr<Transaction> NewTransaction() override {\n    CAFFE_THROW(\"Not implemented.\");\n  }\n\n private:\n  std::shared_ptr<BlobsQueue> queue_;\n  int key_blob_index_;\n  int value_blob_index_;\n  float timeout_secs_;\n};\n} // namespace db\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/queue/queue_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"queue_ops.h\"\n#include <memory>\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nCAFFE_KNOWN_TYPE(std::shared_ptr<BlobsQueue>);\n\nREGISTER_CPU_OPERATOR(CreateBlobsQueue, CreateBlobsQueueOp<CPUContext>);\nREGISTER_CPU_OPERATOR(EnqueueBlobs, EnqueueBlobsOp<CPUContext>);\nREGISTER_CPU_OPERATOR(DequeueBlobs, DequeueBlobsOp<CPUContext>);\nREGISTER_CPU_OPERATOR(CloseBlobsQueue, CloseBlobsQueueOp<CPUContext>);\n\nREGISTER_CPU_OPERATOR(SafeEnqueueBlobs, SafeEnqueueBlobsOp<CPUContext>);\nREGISTER_CPU_OPERATOR(SafeDequeueBlobs, SafeDequeueBlobsOp<CPUContext>);\nREGISTER_CPU_OPERATOR(\n    WeightedSampleDequeueBlobs,\n    WeightedSampleDequeueBlobsOp<CPUContext>);\n\nOPERATOR_SCHEMA(CreateBlobsQueue).NumInputs(0).NumOutputs(1);\nOPERATOR_SCHEMA(EnqueueBlobs)\n    .NumInputsOutputs([](int inputs, int outputs) {\n      return inputs >= 2 && outputs >= 1 && inputs == outputs + 1;\n    })\n    .EnforceInplace([](int input, int output) { return input == output + 1; });\nOPERATOR_SCHEMA(DequeueBlobs)\n    .NumInputsOutputs([](int inputs, int outputs) {\n      return inputs == 1 && outputs >= 1;\n    })\n    .SetDoc(R\"DOC(\n  Dequeue the blobs from queue.\n  )DOC\")\n    .Arg(\"timeout_secs\", \"Timeout in secs, default: no timeout\")\n    .Input(0, \"queue\", \"The shared pointer for the BlobsQueue\")\n    .Output(0, \"blob\", \"The blob to store the dequeued data\");\n\nOPERATOR_SCHEMA(CloseBlobsQueue).NumInputs(1).NumOutputs(0);\n\nOPERATOR_SCHEMA(SafeEnqueueBlobs)\n    .NumInputsOutputs([](int inputs, int outputs) {\n      return inputs >= 2 && outputs >= 2 && inputs == outputs;\n    })\n    .EnforceInplace([](int input, int output) { return input == output + 1; })\n    .SetDoc(R\"DOC(\nEnqueue the blobs into queue. When the queue is closed and full, the output\nstatus will be set to true which can be used as exit criteria for execution\nstep.\nThe 1st input is the queue and the last output is the status. The rest are\ndata blobs.\n)DOC\")\n    .Input(0, \"queue\", \"The shared pointer for the BlobsQueue\");\n\nOPERATOR_SCHEMA(SafeDequeueBlobs)\n    .NumInputsOutputs([](int inputs, int outputs) {\n      return inputs == 1 && outputs >= 2;\n    })\n    .SetDoc(R\"DOC(\nDequeue the blobs from queue. When the queue is closed and empty, the output\nstatus will be set to true which can be used as exit criteria for execution\nstep.\nThe 1st input is the queue and the last output is the status. The rest are\ndata blobs.\n)DOC\")\n    .Arg(\n        \"num_records\",\n        \"(default 1) If > 1, multiple records will be dequeued and tensors \"\n        \"for each column will be concatenated. This requires all tensors in \"\n        \"the records to be at least 1D, and to have the same inner dimensions.\")\n    .Input(0, \"queue\", \"The shared pointer for the BlobsQueue\")\n    .Output(0, \"blob\", \"The blob to store the dequeued data\")\n    .Output(1, \"status\", \"Is set to 0/1 depending on the success of dequeue\");\n\nOPERATOR_SCHEMA(WeightedSampleDequeueBlobs)\n    .NumInputs(1, INT_MAX)\n    .NumOutputs(2, INT_MAX)\n    .SetDoc(R\"DOC(\nDequeue the blobs from multiple queues. When one of queues is closed and empty,\nthe output status will be set to true which can be used as exit criteria for\nexecution step.\nThe 1st input is the queue and the last output is the status. The rest are\ndata blobs.\n)DOC\")\n    .Arg(\"weights\", \"Weights for sampling from multiple queues\")\n    .Arg(\n        \"table_idx_blob\",\n        \"The index of the blob (among the output blob list) \"\n        \"that will be used to store the index of the table chosen to read the \"\n        \"current batch.\");\n\nNO_GRADIENT(CreateBlobsQueue);\nNO_GRADIENT(EnqueueBlobs);\nNO_GRADIENT(DequeueBlobs);\nNO_GRADIENT(CloseBlobsQueue);\n\nNO_GRADIENT(SafeEnqueueBlobs);\nNO_GRADIENT(SafeDequeueBlobs);\nNO_GRADIENT(WeightedSampleDequeueBlobs);\n\n}\n"
  },
  {
    "path": "caffe2/queue/queue_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <memory>\n#include \"blobs_queue.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename Context>\nclass CreateBlobsQueueOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  CreateBlobsQueueOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        ws_(ws),\n        name(operator_def.output().Get(0)) {}\n\n  bool RunOnDevice() override {\n    const auto capacity = GetSingleArgument(\"capacity\", 1);\n    const auto numBlobs = GetSingleArgument(\"num_blobs\", 1);\n    const auto enforceUniqueName =\n        GetSingleArgument(\"enforce_unique_name\", false);\n    const auto fieldNames =\n        OperatorBase::template GetRepeatedArgument<std::string>(\"field_names\");\n    CAFFE_ENFORCE_EQ(this->OutputSize(), 1);\n    auto queuePtr = Operator<Context>::Outputs()[0]\n                        ->template GetMutable<std::shared_ptr<BlobsQueue>>();\n    CAFFE_ENFORCE(queuePtr);\n    *queuePtr = std::make_shared<BlobsQueue>(\n        ws_, name, capacity, numBlobs, enforceUniqueName, fieldNames);\n    return true;\n  }\n\n private:\n  Workspace* ws_{nullptr};\n  const std::string name;\n};\n\ntemplate <typename Context>\nclass EnqueueBlobsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using Operator<Context>::Operator;\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE(InputSize() > 1);\n    auto queue = Operator<Context>::Inputs()[0]\n                     ->template Get<std::shared_ptr<BlobsQueue>>();\n    CAFFE_ENFORCE(queue && OutputSize() == queue->getNumBlobs());\n    return queue->blockingWrite(this->Outputs());\n  }\n\n private:\n};\n\ntemplate <typename Context>\nclass DequeueBlobsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  DequeueBlobsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    timeout_secs_ = OperatorBase::GetSingleArgument<float>(\"timeout_secs\", 0);\n  }\n\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE(InputSize() == 1);\n    auto queue =\n        OperatorBase::Inputs()[0]->template Get<std::shared_ptr<BlobsQueue>>();\n    CAFFE_ENFORCE(queue && OutputSize() == queue->getNumBlobs());\n    return queue->blockingRead(this->Outputs(), timeout_secs_);\n  }\n\n private:\n  float timeout_secs_;\n};\n\ntemplate <typename Context>\nclass CloseBlobsQueueOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using Operator<Context>::Operator;\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE_EQ(InputSize(), 1);\n    auto queue =\n        OperatorBase::Inputs()[0]->template Get<std::shared_ptr<BlobsQueue>>();\n    CAFFE_ENFORCE(queue);\n    queue->close();\n    return true;\n  }\n\n private:\n};\n\ntemplate <typename Context>\nclass SafeEnqueueBlobsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using Operator<Context>::Operator;\n  bool RunOnDevice() override {\n    auto queue = Operator<Context>::Inputs()[0]\n                     ->template Get<std::shared_ptr<BlobsQueue>>();\n    CAFFE_ENFORCE(queue);\n    auto size = queue->getNumBlobs();\n    CAFFE_ENFORCE(\n        OutputSize() == size + 1,\n        \"Expected \" + caffe2::to_string(size + 1) + \", \" +\n            \" got: \" + caffe2::to_string(size));\n    bool status = queue->blockingWrite(this->Outputs());\n    Output(size)->Resize();\n    math::Set<bool, Context>(\n        1, !status, Output(size)->template mutable_data<bool>(), &context_);\n    return true;\n  }\n};\n\ntemplate <typename Context>\nclass SafeDequeueBlobsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  using Operator<Context>::Operator;\n\n  SafeDequeueBlobsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        numRecords_(OperatorBase::GetSingleArgument<int>(\"num_records\", 1)) {\n    CAFFE_ENFORCE_GT(numRecords_, 0);\n  }\n\n  bool dequeueMany(std::shared_ptr<BlobsQueue>& queue) {\n    auto size = queue->getNumBlobs();\n\n    if (blobs_.size() != size) {\n      blobs_.resize(size);\n      blobPtrs_.resize(size);\n      for (int col = 0; col < size; ++col) {\n        blobPtrs_.at(col) = &blobs_.at(col);\n      }\n    }\n\n    const int kTensorGrowthPct = 40;\n    for (int i = 0; i < numRecords_; ++i) {\n      if (!queue->blockingRead(blobPtrs_)) {\n        // if we read at least one record, status is still true\n        return i > 0;\n      }\n      for (int col = 0; col < size; ++col) {\n        auto* out = this->Output(col);\n        const auto& in = blobPtrs_.at(col)->template Get<Tensor<Context>>();\n        if (i == 0) {\n          out->CopyFrom(in);\n        } else {\n          auto oldSize = out->size();\n\n          CAFFE_ENFORCE(\n              in.ndim() > 0,\n              \"Empty tensor to dequeue at column \",\n              col,\n              \" within \",\n              size,\n              \" total columns\");\n\n          out->Extend(in.dims()[0], kTensorGrowthPct, &context_);\n          auto* dst =\n              (char*)out->raw_mutable_data() + oldSize * in.meta().itemsize();\n          context_.template CopyItems<Context, Context>(\n              in.meta(), in.size(), in.raw_data(), dst);\n        }\n      }\n    }\n    return true;\n  }\n\n  bool dequeueOne(std::shared_ptr<BlobsQueue>& queue) {\n    return queue->blockingRead(this->Outputs());\n  }\n\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE(InputSize() == 1);\n    auto queue = Operator<Context>::Inputs()[0]\n                     ->template Get<std::shared_ptr<BlobsQueue>>();\n    CAFFE_ENFORCE(queue);\n\n    auto size = queue->getNumBlobs();\n    CAFFE_ENFORCE_EQ(OutputSize(), size + 1);\n\n    bool status = numRecords_ > 1 ? dequeueMany(queue) : dequeueOne(queue);\n\n    Output(size)->Resize();\n    math::Set<bool, Context>(\n        1, !status, Output(size)->template mutable_data<bool>(), &context_);\n    return true;\n  }\n\n private:\n  int numRecords_;\n  std::vector<Blob> blobs_;\n  std::vector<Blob*> blobPtrs_;\n};\n\ntemplate <typename Context>\nclass WeightedSampleDequeueBlobsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  WeightedSampleDequeueBlobsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        table_idx_blob_(\n            OperatorBase::GetSingleArgument<int>(\"table_idx_blob\", -1)) {\n    CAFFE_ENFORCE_LT(table_idx_blob_, OutputSize() - 1);\n    vector<float> weights = OperatorBase::GetRepeatedArgument<float>(\"weights\");\n    if (weights.empty()) {\n      weights.resize(InputSize(), 1.0f);\n    }\n    CAFFE_ENFORCE_EQ(InputSize(), weights.size());\n\n    float sum = accumulate(weights.begin(), weights.end(), 0.0f);\n    CAFFE_ENFORCE(sum > 0.0f, \"Sum of weights must be positive\");\n    cumProbs_.resize(weights.size());\n    for (int i = 0; i < weights.size(); i++) {\n      cumProbs_[i] = weights[i] / sum;\n      CAFFE_ENFORCE_GE(\n          cumProbs_[i], 0.0f, \"Each probability must be non-negative\");\n    }\n    std::partial_sum(cumProbs_.begin(), cumProbs_.end(), cumProbs_.begin());\n    // Put last value to be 1.0001 to avoid numerical issues.\n    cumProbs_.back() = 1.0001f;\n\n    LOG(INFO) << \"Dequeue weights: \" << weights;\n    LOG(INFO) << \"cumProbs: \" << cumProbs_;\n  }\n\n  bool RunOnDevice() override {\n    float r;\n    math::RandUniform<float, Context>(1, 0.0f, 1.0f, &r, &context_);\n    auto lb = lower_bound(cumProbs_.begin(), cumProbs_.end(), r);\n    CAFFE_ENFORCE(lb != cumProbs_.end(), \"Cannot find \", r, \" in cumProbs_.\");\n    const int32_t idx = lb - cumProbs_.begin();\n    auto queue = Operator<Context>::Inputs()[idx]\n                     ->template Get<std::shared_ptr<BlobsQueue>>();\n\n    CAFFE_ENFORCE(queue);\n    auto size = queue->getNumBlobs();\n    CAFFE_ENFORCE_EQ(OutputSize(), size + 1);\n    bool status = queue->blockingRead(this->Outputs());\n    if (table_idx_blob_ >= 0) {\n      auto* table_idx_blob_out = Output(table_idx_blob_);\n      table_idx_blob_out->Resize(1);\n      int32_t* data = table_idx_blob_out->template mutable_data<int32_t>();\n      data[0] = idx;\n    }\n\n    Output(size)->Resize();\n    math::Set<bool, Context>(\n        1, !status, Output(size)->template mutable_data<bool>(), &context_);\n    return true;\n  }\n\n private:\n  vector<float> cumProbs_;\n  int table_idx_blob_;\n};\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/queue/queue_ops_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/utils/math.h\"\n#include \"queue_ops.h\"\n\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(CreateBlobsQueue, CreateBlobsQueueOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(EnqueueBlobs, EnqueueBlobsOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(DequeueBlobs, DequeueBlobsOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(CloseBlobsQueue, CloseBlobsQueueOp<CUDAContext>);\n\nREGISTER_CUDA_OPERATOR(SafeEnqueueBlobs, SafeEnqueueBlobsOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(SafeDequeueBlobs, SafeDequeueBlobsOp<CUDAContext>);\n\n}\n"
  },
  {
    "path": "caffe2/queue/rebatching_queue.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"rebatching_queue.h\"\n#include \"caffe2/utils/smart_tensor_printer.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n// This concat function will always create a new first dimension to concat\nvoid concat(\n    CPUContext& context,\n    const std::vector<std::vector<TensorCPU>>& inputs,\n    const std::vector<TensorCPU*>& outputs) {\n  CAFFE_ENFORCE(!inputs.empty());\n\n  const auto& inputZero = inputs[0];\n  const auto numTensors = inputZero.size();\n  const auto numRows = inputs.size();\n\n  // Precompute the output sizes to avoid resizing\n  std::vector<std::vector<TIndex>> outputDims(numTensors);\n\n  for (int i = 0; i < numTensors; ++i) {\n    SmartTensorPrinter::PrintTensor(inputZero.at(i));\n    outputDims[i] = inputZero.at(i).dims();\n    outputDims[i].insert(outputDims[i].begin(), numRows);\n  }\n\n  // Resize to the final output size\n  std::vector<void*> destinations(numTensors);\n  for (int i = 0; i < numTensors; ++i) {\n    outputs[i]->Resize(outputDims[i]);\n    destinations[i] = outputs[i]->raw_mutable_data(inputZero[i].meta());\n  }\n\n  for (int i = 0; i < numRows; ++i) {\n    CAFFE_ENFORCE_EQ(inputs[i].size(), numTensors);\n\n    for (int j = 0; j < numTensors; ++j) {\n      const auto& input = inputs[i][j];\n\n      CAFFE_ENFORCE(inputZero[j].meta() == input.meta());\n      CAFFE_ENFORCE_EQ(inputZero[j].itemsize(), input.itemsize());\n      CAFFE_ENFORCE_EQ(inputZero[j].ndim(), input.ndim());\n      for (int k = 0; k < input.ndim(); ++k) {\n        CAFFE_ENFORCE_EQ(input.dims()[k], inputZero[j].dims()[k]);\n      }\n\n      // Skip empty tensors\n      if (input.size() == 0) {\n        continue;\n      }\n\n      context.CopyItems<CPUContext, CPUContext>(\n          input.meta(),\n          input.size(),\n          input.raw_data() /* src */,\n          destinations[j] /* dst */\n          );\n\n      destinations[j] =\n          (char*)destinations[j] + input.size() * input.itemsize();\n    }\n  }\n}\n\nstd::vector<std::vector<TensorCPU>> split(\n    CPUContext& context,\n    const std::vector<const TensorCPU*>& inputs) {\n  CAFFE_ENFORCE(!inputs.empty());\n\n  const auto outputSize = inputs[0]->dims().at(0);\n  std::vector<std::vector<TensorCPU>> outputs(outputSize);\n\n  for (const auto* inputPtr : inputs) {\n    CAFFE_ENFORCE(inputPtr);\n\n    const auto& input = *inputPtr;\n    const auto innerSize = input.size_from_dim(1);\n    const auto itemSize = input.meta().itemsize();\n\n    auto outputDims = input.dims();\n    CAFFE_ENFORCE(!outputDims.empty());\n    outputDims.erase(outputDims.begin());\n    CAFFE_ENFORCE_EQ(input.dims().at(0), outputSize);\n\n    for (int i = 0; i < outputSize; ++i) {\n      outputs[i].push_back(TensorCPU(outputDims));\n      context.CopyItems<CPUContext, CPUContext>(\n          input.meta(),\n          innerSize,\n          (char*)input.raw_data() + i * innerSize * itemSize /* src */,\n          outputs[i].back().raw_mutable_data(input.meta()) /* dst */);\n    }\n  }\n\n  return outputs;\n}\n} // anonymous namespace\n\nRebatchingQueue::RebatchingQueue(size_t capacity, size_t numBlobs)\n    : capacity_(capacity), numBlobs_(numBlobs), queue_(capacity) {}\n\nRebatchingQueue::~RebatchingQueue() {\n  close();\n}\n\nbool RebatchingQueue::canRead() const {\n  return tail_ < head_;\n}\n\nbool RebatchingQueue::dequeue(\n    CPUContext& context,\n    size_t numElements,\n    const std::vector<TensorCPU*>& outputs) {\n  std::vector<std::vector<TensorCPU>> results;\n  results.reserve(numElements);\n\n  for (;;) {\n    if (results.size() == numElements) {\n      break;\n    }\n\n    {\n      std::unique_lock<std::mutex> lock(mutex_);\n\n      cvEmpty_.wait(lock, [this] { return canRead() || isClosed_; });\n\n      // We only want to stop reading if the queue is empty and closed\n      if (!canRead() && isClosed_) {\n        break;\n      }\n\n      do {\n        results.push_back(std::move(queue_[tail_++ % capacity()]));\n      } while (canRead() && results.size() < numElements);\n    }\n\n    if (numElements == 1) {\n      cvOverflow_.notify_one();\n    } else {\n      cvOverflow_.notify_all();\n    }\n  }\n\n  if (results.empty()) {\n    return false;\n  }\n\n  concat(context, results, outputs);\n\n  return true;\n}\n\nbool RebatchingQueue::canWrite() const {\n  return tail_ + capacity() > head_;\n}\n\nbool RebatchingQueue::enqueueOne(\n    CPUContext& /*context*/,\n    const std::vector<const TensorCPU*>& inputs) {\n  std::vector<std::vector<TensorCPU>> splittedInputs;\n  splittedInputs.emplace_back();\n  auto& tensorVector = splittedInputs.back();\n  tensorVector.reserve(inputs.size());\n  for (const auto* tensorPtr : inputs) {\n    tensorVector.push_back(*tensorPtr);\n  }\n\n  return enqueue(std::move(splittedInputs));\n}\n\nbool RebatchingQueue::enqueueMany(\n    CPUContext& context,\n    const std::vector<const TensorCPU*>& inputs) {\n  CAFFE_ENFORCE_EQ(numBlobs_, inputs.size());\n\n  std::vector<std::vector<TensorCPU>> splittedInputs;\n  splittedInputs = split(context, inputs);\n  return enqueue(std::move(splittedInputs));\n}\n\nbool RebatchingQueue::enqueue(\n    std::vector<std::vector<TensorCPU>> splittedInputs) {\n  int idx = 0;\n  for (;;) {\n    if (idx >= splittedInputs.size()) {\n      break;\n    }\n\n    {\n      std::unique_lock<std::mutex> lock(mutex_);\n\n      cvOverflow_.wait(lock, [this] { return canWrite() || isClosed_; });\n\n      if (isClosed_) {\n        // If we are here it means that we didn't apply the entire batch and if\n        // we get closed in the middle of enquing we treat it as a non-success.\n        return false;\n      }\n\n      do {\n        queue_[head_++ % capacity()] = std::move(splittedInputs[idx++]);\n      } while (canWrite() && idx < splittedInputs.size());\n    }\n\n    cvEmpty_.notify_all();\n  }\n\n  return true;\n}\n\nsize_t RebatchingQueue::capacity() const {\n  return capacity_;\n}\n\nsize_t RebatchingQueue::numBlobs() const {\n  return numBlobs_;\n}\n\nbool RebatchingQueue::isClosed() const {\n  std::lock_guard<std::mutex> g(mutex_);\n  return isClosed_;\n}\n\nvoid RebatchingQueue::close() {\n  {\n    std::lock_guard<std::mutex> g(mutex_);\n    isClosed_ = true;\n  }\n\n  cvEmpty_.notify_all();\n  cvOverflow_.notify_all();\n}\n} // caffe2\n"
  },
  {
    "path": "caffe2/queue/rebatching_queue.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <atomic>\n#include <condition_variable>\n#include <memory>\n#include <mutex>\n#include <queue>\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/stats.h\"\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\n// TODO: This is a very naive implementation with a single mutex. We can do the\n// atomic index + circular queue optimizations or pull something more\n// heavy-weight later\n\nclass RebatchingQueue {\n public:\n  RebatchingQueue(size_t capacity, size_t numBlobs);\n\n  ~RebatchingQueue();\n\n  bool enqueueOne(\n      CPUContext& context,\n      const std::vector<const TensorCPU*>& inputs);\n\n  bool enqueueMany(\n      CPUContext& context,\n      const std::vector<const TensorCPU*>& inputs);\n\n  bool dequeue(\n      CPUContext& context,\n      size_t numElements,\n      const std::vector<TensorCPU*>& outputs);\n\n  size_t capacity() const;\n\n  size_t numBlobs() const;\n\n  bool isClosed() const;\n\n  void close();\n\n private:\n  bool enqueue(std::vector<std::vector<TensorCPU>> splittedInputs);\n\n  bool canWrite() const;\n  bool canRead() const;\n\n  const size_t capacity_;\n  const size_t numBlobs_;\n\n  mutable std::mutex mutex_;\n\n  bool isClosed_{false};\n\n  uint64_t head_{0};\n  uint64_t tail_{0};\n\n  std::condition_variable cvEmpty_;\n  std::condition_variable cvOverflow_;\n\n  std::vector<std::vector<TensorCPU>> queue_;\n};\n} // caffe2\n"
  },
  {
    "path": "caffe2/queue/rebatching_queue_ops.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"rebatching_queue_ops.h\"\n\nnamespace caffe2 {\n\nCAFFE_KNOWN_TYPE(RebatchingQueuePtr);\n\nnamespace {\n\nREGISTER_CPU_OPERATOR(CreateRebatchingQueue, CreateRebatchingQueueOp);\nREGISTER_CPU_OPERATOR(EnqueueRebatchingQueue, EnqueueRebatchingQueueOp);\nREGISTER_CPU_OPERATOR(DequeueRebatchingQueue, DequeueRebatchingQueueOp);\nREGISTER_CPU_OPERATOR(CloseRebatchingQueue, CloseRebatchingQueueOp);\n\nNO_GRADIENT(CreateRebatchingQueue);\nNO_GRADIENT(EnqueueRebatchingQueue);\nNO_GRADIENT(DequeueRebatchingQueue);\nNO_GRADIENT(CloseRebatchingQueue);\n\nOPERATOR_SCHEMA(CreateRebatchingQueue)\n    .NumInputs(0)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nCreates the Queue.\n)DOC\")\n    .Output(0, \"queue\", \"object representing the queue\")\n    .Arg(\"num_blobs\", \"Number of input tensors the queue will support\")\n    .Arg(\n        \"capacity\",\n        \"Maximal number of elements the queue can hold at any given point\");\n\nOPERATOR_SCHEMA(CloseRebatchingQueue)\n    .NumInputs(1)\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(\nCloses the Queue.\n)DOC\")\n    .Input(0, \"queue\", \"object representing the queue\");\n\nOPERATOR_SCHEMA(EnqueueRebatchingQueue)\n    .NumInputs(2, INT_MAX)\n    .NumOutputs(0)\n    .SetDoc(R\"DOC(\nEnqueues Tensors into the queue.\nNumber of input tensors should be equal to the number of components passed\nduring creation of the queue.\nIf the Queue is closed this operation will fail.\nIf enqueue_batch argument is set. We will split the input tensors by the\nfirst dimension to produce single queue elements.\n)DOC\")\n    .Input(0, \"queue\", \"object representing the queue\")\n    .Input(1, \"tensor\", \"First tensor to enque. \")\n    .Arg(\n        \"enqueue_batch\",\n        \"Are we enqueuing a batch or just a single element. \\\n        By default we enqueue single element.\");\n\nOPERATOR_SCHEMA(DequeueRebatchingQueue)\n    .NumInputs(1)\n    .NumOutputs(1, INT_MAX)\n    .SetDoc(R\"DOC(\nDequeue Tensors from the Queue.\nIf the Queue is closed this might return less elements than asked.\nIf num_elements > 1 the returned elements will be concatenated into one\ntensor per component.\n)DOC\")\n    .Input(0, \"rebatching_queue\", \"object representing the queue\")\n    .Input(1, \"tensor\", \"First tensor to enqueue\")\n    .Arg(\n        \"num_elements\",\n        \"Number of elements to dequeue. By default we dequeue one element.\");\n}\n}\n"
  },
  {
    "path": "caffe2/queue/rebatching_queue_ops.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"rebatching_queue.h\"\n\nnamespace caffe2 {\n\nusing RebatchingQueuePtr = std::unique_ptr<RebatchingQueue>;\n\nclass CreateRebatchingQueueOp : public Operator<CPUContext> {\n public:\n  CreateRebatchingQueueOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    *OperatorBase::Output<RebatchingQueuePtr>(0) =\n        RebatchingQueuePtr(new RebatchingQueue(\n            OperatorBase::GetSingleArgument<int>(\"capacity\", 1),\n            OperatorBase::GetSingleArgument<int>(\"num_blobs\", 1)));\n    return true;\n  }\n};\n\nclass EnqueueRebatchingQueueOp : public Operator<CPUContext> {\n public:\n  EnqueueRebatchingQueueOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        enqueueBatch_(\n            OperatorBase::GetSingleArgument<bool>(\"enqueue_batch\", false)) {}\n  bool RunOnDevice() override {\n    auto& queue = Inputs()[0]->template Get<RebatchingQueuePtr>();\n    CHECK(queue);\n    CAFFE_ENFORCE_EQ(InputSize(), queue->numBlobs() + 1);\n    std::vector<const TensorCPU*> inputTensors;\n    inputTensors.reserve(InputSize() - 1);\n    for (int i = 1; i < InputSize(); ++i) {\n      inputTensors.push_back(&Input(i));\n    }\n\n    return enqueueBatch_ ? queue->enqueueMany(context_, inputTensors)\n                         : queue->enqueueOne(context_, inputTensors);\n  }\n\n private:\n  const bool enqueueBatch_;\n};\n\nclass DequeueRebatchingQueueOp : public Operator<CPUContext> {\n public:\n  DequeueRebatchingQueueOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws),\n        numElements_(OperatorBase::GetSingleArgument<int>(\"num_elements\", 1)) {}\n\n  bool RunOnDevice() override {\n    auto& queue = Inputs()[0]->template Get<RebatchingQueuePtr>();\n    CHECK(queue);\n\n    std::vector<TensorCPU*> outputTensors;\n    outputTensors.reserve(OutputSize());\n    for (int i = 0; i < OutputSize(); ++i) {\n      outputTensors.push_back(Output(i));\n    }\n\n    return queue->dequeue(context_, numElements_, outputTensors);\n  }\n\n private:\n  int numElements_;\n};\n\nclass CloseRebatchingQueueOp : public Operator<CPUContext> {\n public:\n  CloseRebatchingQueueOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE_EQ(InputSize(), 1);\n    auto& queue = Inputs()[0]->template Get<RebatchingQueuePtr>();\n    CAFFE_ENFORCE(queue);\n    queue->close();\n    return true;\n  }\n};\n} // caffe2\n"
  },
  {
    "path": "caffe2/sgd/CMakeLists.txt",
    "content": "# ---[ GPU files\n# ------[ cuDNN\nfile(GLOB tmp *_cudnn.cc)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# ------[ general GPU\nfile(GLOB tmp *_gpu.cc)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# ------[ CUDA sources\nfile(GLOB tmp *.cu)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# exclude test files\nfile(GLOB tmp *_test.cc)\nexclude(Caffe2_GPU_SRCS \"${Caffe2_GPU_SRCS}\" ${tmp})\n\n# ---[ CPU files.\nfile(GLOB tmp *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp})\n# exclude test files and gpu files\nfile(GLOB tmp *_test.cc)\nexclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${tmp})\nexclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${Caffe2_GPU_SRCS})\n\n# ---[ GPU test files\nfile(GLOB tmp *_gpu_test.cc)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} ${tmp})\n\n# ---[ CPU test files\nfile(GLOB tmp *_test.cc)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${tmp})\nexclude(Caffe2_CPU_TEST_SRCS \"${Caffe2_CPU_TEST_SRCS}\" ${Caffe2_GPU_TEST_SRCS})\n\n# ---[ Send the lists to the parent scope.\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/sgd/adagrad_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"adagrad_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Adagrad, AdagradOp<float, CPUContext>);\nOPERATOR_SCHEMA(Adagrad)\n    .NumInputs(4)\n    .NumOutputs(2)\n    .AllowInplace({{0, 0}, {1, 1}})\n    .SetDoc(R\"DOC(\n\nComputes the AdaGrad update for an input gradient and accumulated\nhistory. Concretely, given inputs (param, grad, moment, learning_rate),\ncomputes\n\n    new_moment = moment + square(grad)\n    new_grad = learning_rate * grad / (sqrt(new_moment) + epsilon)\n    new_param = param + new_grad\nand returns (new_param, new_moment).\n\n)DOC\")\n    .Input(0, \"param\", \"Parameters to be updated\")\n    .Input(1, \"moment\", \"Moment history\")\n    .Input(2, \"grad\", \"Gradient computed\")\n    .Input(3, \"lr\", \"learning rate\")\n    .Output(0, \"output_param\", \"Updated parameters\")\n    .Output(1, \"output_moment\", \"Updated moment\")\n    .Arg(\"epsilon\", \"Default 1e-5\")\n    .Arg(\n        \"decay\",\n        \"Default 1. If it is in (0, 1), the gradient square sum \"\n        \"is decayed by this factor.\");\n\nREGISTER_CPU_OPERATOR(SparseAdagrad, SparseAdagradOp<float, CPUContext>);\nOPERATOR_SCHEMA(SparseAdagrad)\n    .NumInputs(5)\n    .NumOutputs(2)\n    .EnforceOneToOneInplace()\n    .SetDoc(R\"DOC(\n\nGiven inputs (param, moment, indices, grad, lr), runs the dense AdaGrad\nupdate on (param, grad, moment[indices], lr), and returns (new_param,\nnew_moment) as in the dense case.\n\n)DOC\")\n    .Input(0, \"param\", \"Parameters to be updated\")\n    .Input(1, \"moment\", \"Moment history\")\n    .Input(2, \"indices\", \"Sparse indices\")\n    .Input(3, \"grad\", \"Gradient computed\")\n    .Input(4, \"lr\", \"learning rate\")\n    .Output(0, \"output_param\", \"Updated parameters\")\n    .Output(1, \"output_moment_1\", \"Updated moment\")\n    .Arg(\"epsilon\", \"Default 1e-5\");\n\nREGISTER_CPU_OPERATOR(\n    RowWiseSparseAdagrad,\n    RowWiseSparseAdagradOp<float, CPUContext>);\nOPERATOR_SCHEMA(RowWiseSparseAdagrad)\n    .NumInputs(5)\n    .NumOutputs(2)\n    .EnforceOneToOneInplace()\n    .SetDoc(R\"DOC(\n\nGiven inputs (param, moment, indices, grad, lr), runs a modified sparse Adagrad\nupdate on (param, grad, moment[indices], lr), and returns (new_param,\nnew_momwnr), where moment is a 1D tensor with length equal to the number of\nrows in param: shape(moment) == shape(param)[0]. Each element of moment is\napplied to an entire row of param, and the new moment is calculated by adding\nthe average squared sum of gradients across each row. Note that indices must\nalso be a 1D tensor indexing into the rows of param.\n\n)DOC\")\n    .Input(0, \"param\", \"Parameters to be updated\")\n    .Input(1, \"moment\", \"Moment history\")\n    .Input(2, \"indices\", \"Sparse indices\")\n    .Input(3, \"grad\", \"Gradient computed\")\n    .Input(4, \"lr\", \"learning rate\")\n    .Output(0, \"output_param\", \"Updated parameters\")\n    .Output(1, \"output_moment_1\", \"Updated moment\")\n    .Arg(\"epsilon\", \"Default 1e-5\");\n\nSHOULD_NOT_DO_GRADIENT(Adagrad);\nSHOULD_NOT_DO_GRADIENT(SparseAdagrad);\nSHOULD_NOT_DO_GRADIENT(RowWiseSparseAdagrad);\n}\n"
  },
  {
    "path": "caffe2/sgd/adagrad_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename Context>\nvoid adagrad_update(\n    int N,\n    const float* w,\n    const float* g,\n    const float* h,\n    float* nw,\n    float* nh,\n    float epsilon,\n    float decay,\n    const float* lr,\n    Context* /*context*/) {\n  for (auto i = 0; i < N; ++i) {\n    float gi = g[i];\n    float hi = nh[i] = decay * h[i] + gi * gi;\n    nw[i] = w[i] + lr[0] * gi / (std::sqrt(hi) + epsilon);\n  }\n}\n\ntemplate <typename T, class Context>\nclass AdagradOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  AdagradOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        epsilon_(OperatorBase::GetSingleArgument<T>(\"epsilon\", 1e-5f)),\n        decay_(OperatorBase::GetSingleArgument<T>(\"decay\", 1.0f)) {}\n\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE(Input(GRAD).size() == Input(MOMENT_1).size());\n    CAFFE_ENFORCE(Input(GRAD).size() == Input(PARAM).size());\n    Output(OUTPUT_PARAM)->ResizeLike(Input(PARAM));\n    Output(OUTPUT_MOMENT_1)->ResizeLike(Input(MOMENT_1));\n    adagrad_update<Context>(\n        Input(GRAD).size(),\n        Input(PARAM).template data<T>(),\n        Input(GRAD).template data<T>(),\n        Input(MOMENT_1).template data<T>(),\n        Output(OUTPUT_PARAM)->template mutable_data<T>(),\n        Output(OUTPUT_MOMENT_1)->template mutable_data<T>(),\n        epsilon_,\n        decay_,\n        Input(LR).template data<T>(),\n        &context_);\n    return true;\n  }\n\n protected:\n  T epsilon_;\n  T decay_;\n  INPUT_TAGS(PARAM, MOMENT_1, GRAD, LR);\n  OUTPUT_TAGS(OUTPUT_PARAM, OUTPUT_MOMENT_1);\n};\n\ntemplate <typename T, class Context>\nclass SparseAdagradOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SparseAdagradOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        epsilon_(OperatorBase::GetSingleArgument<float>(\"epsilon\", 1e-5f)) {}\n\n  bool RunOnDevice() override {\n    // Enforce shapes\n    CAFFE_ENFORCE_EQ(Input(PARAM).size(), Input(MOMENT_1).size());\n    CAFFE_ENFORCE_EQ(Input(LR).size(), 1);\n    CAFFE_ENFORCE_EQ(\n        Input(PARAM).size_from_dim(1),\n        Input(GRAD).size_from_dim(Input(INDICES).ndim()));\n\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename SIndex>\n  bool DoRunWithType() {\n    const auto* lr = Input(LR).template data<T>();\n    const auto* indices = Input(INDICES).template data<SIndex>();\n    const auto* gradIn = Input(GRAD).template data<T>();\n    const auto* paramIn = Input(PARAM).template data<T>();\n    const auto* momentIn = Input(MOMENT_1).template data<T>();\n    auto* paramOut = Output(OUTPUT_PARAM)->template mutable_data<T>();\n    auto* momentOut = Output(OUTPUT_MOMENT_1)->template mutable_data<T>();\n\n    auto n = Input(INDICES).size();\n    if (n == 0) {\n      return true;\n    }\n\n    auto block_size = Input(GRAD).size() / n;\n    for (auto i = 0; i < n; ++i) {\n      auto idx = indices[i];\n      if (block_size == 1) {\n        float gi = gradIn[i];\n        float hi = momentOut[idx] = momentIn[idx] + gi * gi;\n        paramOut[idx] = paramIn[idx] + lr[0] * gi / (std::sqrt(hi) + epsilon_);\n      } else {\n        auto offsetI = i * block_size;\n        auto offsetIdx = idx * block_size;\n\n#ifndef NDEBUG\n        CAFFE_ENFORCE_GE(\n            Input(PARAM).size(),\n            block_size + offsetIdx,\n            this->debug_def().input(PARAM),\n            \", out of bound,  idx:\",\n            idx,\n            \" for input i:\",\n            i,\n            \" and block size:\",\n            block_size);\n        CAFFE_ENFORCE_GE(\n            Input(GRAD).size(),\n            block_size + offsetI,\n            this->debug_def().input(GRAD),\n            \", out of bound idx, idx:\",\n            idx,\n            \" for input i:\",\n            i);\n#endif\n        adagrad_update(\n            block_size,\n            paramIn + offsetIdx,\n            gradIn + offsetI,\n            momentIn + offsetIdx,\n            paramOut + offsetIdx,\n            momentOut + offsetIdx,\n            epsilon_,\n            1.0f,\n            lr,\n            &context_);\n      }\n    }\n    return true;\n  }\n\n protected:\n  T epsilon_;\n  INPUT_TAGS(PARAM, MOMENT_1, INDICES, GRAD, LR);\n  OUTPUT_TAGS(OUTPUT_PARAM, OUTPUT_MOMENT_1);\n};\n\ntemplate <typename T, class Context>\nclass RowWiseSparseAdagradOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  RowWiseSparseAdagradOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        epsilon_(OperatorBase::GetSingleArgument<float>(\"epsilon\", 1e-5f)) {}\n\n  bool RunOnDevice() override {\n    // Enforce shapes\n    CAFFE_ENFORCE_EQ(Input(PARAM).dims()[0], Input(MOMENT_1).size());\n    CAFFE_ENFORCE_EQ(Input(LR).size(), 1);\n    CAFFE_ENFORCE_EQ(\n        Input(PARAM).size_from_dim(1),\n        Input(GRAD).size_from_dim(Input(INDICES).ndim()));\n\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename SIndex>\n  bool DoRunWithType() {\n    const auto* lr = Input(LR).template data<T>();\n    const auto* indices = Input(INDICES).template data<SIndex>();\n    const auto* gradIn = Input(GRAD).template data<T>();\n    const auto* paramIn = Input(PARAM).template data<T>();\n    const auto* momentIn = Input(MOMENT_1).template data<T>();\n    auto* paramOut = Output(OUTPUT_PARAM)->template mutable_data<T>();\n    auto* momentOut = Output(OUTPUT_MOMENT_1)->template mutable_data<T>();\n\n    auto n = Input(INDICES).size();\n    if (n == 0) {\n      return true;\n    }\n\n    auto block_size = Input(GRAD).size() / n;\n\n    for (auto i = 0; i < n; ++i) {\n      auto idx = indices[i];\n      if (block_size == 1) {\n        float gi = gradIn[i];\n        float hi = momentOut[idx] = momentIn[idx] + gi * gi;\n        paramOut[idx] = paramIn[idx] + lr[0] * gi / (std::sqrt(hi) + epsilon_);\n      } else {\n        auto offsetI = i * block_size;\n        auto offsetIdx = idx * block_size;\n\n#ifndef NDEBUG\n        CAFFE_ENFORCE_GE(\n            Input(PARAM).size(),\n            block_size + offsetIdx,\n            this->debug_def().input(PARAM),\n            \", out of bound,  idx:\",\n            idx,\n            \" for input i:\",\n            i,\n            \" and block size:\",\n            block_size);\n        CAFFE_ENFORCE_GE(\n            Input(GRAD).size(),\n            block_size + offsetI,\n            this->debug_def().input(GRAD),\n            \", out of bound idx, idx:\",\n            idx,\n            \" for input i:\",\n            i);\n#endif\n\n        const float* w = paramIn + offsetIdx;\n        const float* g = gradIn + offsetI;\n        const float* h = momentIn + idx;\n        float* nw = paramOut + offsetIdx;\n        float* nh = momentOut + idx;\n        float hs = 0.;\n        for (auto j = 0; j < block_size; ++j) {\n          float gj = g[j];\n          hs += gj * gj;\n        }\n        float hi = nh[0] = h[0] + hs / block_size;\n        float step = lr[0] / (std::sqrt(hi) + epsilon_);\n        for (auto j = 0; j < block_size; ++j) {\n          nw[j] = w[j] + g[j] * step;\n        }\n      }\n    }\n    return true;\n  }\n\n protected:\n  T epsilon_;\n  INPUT_TAGS(PARAM, MOMENT_1, INDICES, GRAD, LR);\n  OUTPUT_TAGS(OUTPUT_PARAM, OUTPUT_MOMENT_1);\n};\n}\n"
  },
  {
    "path": "caffe2/sgd/adagrad_op_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cub/block/block_reduce.cuh>\n#include \"adagrad_op.h\"\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/utils/mixed_utils.h\"\n\nnamespace caffe2 {\n\n__global__ void AdagradUpdate(\n    int N,\n    const float* w,\n    const float* g,\n    const float* h,\n    float* nw,\n    float* nh,\n    float epsilon,\n    float decay,\n    const float* lr) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    float gi = g[i];\n    float hi = nh[i] = decay * h[i] + gi * gi;\n    nw[i] = w[i] + lr[0] * gi / (std::sqrt(hi) + epsilon);\n  }\n}\n\ntemplate <>\nvoid adagrad_update<CUDAContext>(\n    int N,\n    const float* w,\n    const float* g,\n    const float* h,\n    float* nw,\n    float* nh,\n    float epsilon,\n    float decay,\n    const float* lr,\n    CUDAContext* context) {\n  AdagradUpdate<<<\n      CAFFE_GET_BLOCKS(N),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(N, w, g, h, nw, nh, epsilon, decay, lr);\n}\n\ntemplate <typename SIndex, typename THalf>\n__global__ void SparseAdagradKernel(\n    const size_t N,\n    const size_t grad_slice_sz,\n    const float epsilon,\n    THalf* param,\n    THalf* param_mom,\n    const SIndex* indices,\n    const float* grad,\n    const float* lr) {\n  const float LR = lr[0];\n  CUDA_1D_KERNEL_LOOP(i, N)\n  {\n    const size_t gradIdx = i;\n    const SIndex index = indices[i / grad_slice_sz];\n    const size_t paramIdx = index * grad_slice_sz + (i % grad_slice_sz);\n\n    float mom_new =\n        mixed_add(grad[gradIdx] * grad[gradIdx], param_mom[paramIdx]);\n    mixed_store(&mom_new, &(param_mom[paramIdx]));\n    float param_new = mixed_add(\n        LR * grad[gradIdx] / (sqrt(mom_new) + epsilon), param[paramIdx]);\n    mixed_store(&param_new, &(param[paramIdx]));\n  }\n}\n\n/**\n * Calculate RowwiseSparseAdagrad\n * M: gradients.dims[0]\n * N: gradients.size_from_dim(1)\n * grad: pointer to the gradients\n * param: pointer to weights\n * param_mom: pointer to the momentum\n * indices: keys\n */\ntemplate <typename SIndex>\n__global__ void RowWiseSparseAdagradKernel(\n    const int M,\n    const int N,\n    const float epsilon,\n    float* param,\n    float* param_mom,\n    const SIndex* indices,\n    const float* grad,\n    const float* lr) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ BlockReduce::TempStorage temp_storage;\n  // in case gridDim is smaller than M\n  for (int i = blockIdx.x; i < M; i += gridDim.x) {\n    const SIndex index = indices[i];\n    float sum_squares = 0.0;\n    __shared__ float row_sum_squares_avg;\n\n    // in case N is bigger than block size which is 512 by default\n    for (int j = threadIdx.x; j < N; j += blockDim.x) {\n      const float x_ij = grad[i * N + j];\n      sum_squares += x_ij * x_ij;\n    }\n    float reduce_result = BlockReduce(temp_storage).Sum(sum_squares);\n    if (threadIdx.x == 0) {\n      row_sum_squares_avg = reduce_result / (float)N;\n      param_mom[index] += row_sum_squares_avg;\n    }\n    __syncthreads();\n    // update param\n    float step = lr[0] / (std::sqrt(param_mom[index]) + epsilon);\n    for (int j = threadIdx.x; j < N; j += blockDim.x) {\n      param[index * N + j] = param[index * N + j] + grad[i * N + j] * step;\n    }\n  }\n}\n\ntemplate <typename T, class Context>\nclass CUDASparseAdagradOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  CUDASparseAdagradOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        epsilon_(OperatorBase::GetSingleArgument<float>(\"epsilon\", 1e-5f)) {\n    const T decay = OperatorBase::GetSingleArgument<T>(\"decay\", 1.0f);\n    CAFFE_ENFORCE_EQ(decay, 1.0, \"Decay is not supported for SparseAdagradOp\");\n  }\n\n  bool RunOnDevice() override {\n    // Enforce shapes\n    CAFFE_ENFORCE_EQ(Input(PARAM).size(), Input(MOMENT_1).size());\n    CAFFE_ENFORCE_EQ(Input(LR).size(), 1);\n    CAFFE_ENFORCE_EQ(\n        Input(PARAM).size_from_dim(1),\n        Input(GRAD).size_from_dim(Input(INDICES).ndim()));\n\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename IndexType>\n  bool DoRunWithType() {\n    auto n = Input(INDICES).size();\n    if (n == 0) {\n      return true;\n    }\n    return DispatchHelper<TensorTypes2<float, float16>, IndexType>::call(\n        this, Input(PARAM));\n  }\n\n  template <typename IndexType, typename THalf>\n  bool DoRunWithType2() {\n    const auto* lr = Input(LR).template data<T>();\n    const auto* indices = Input(INDICES).template data<IndexType>();\n    const auto* gradIn = Input(GRAD).template data<T>();\n    const auto* paramIn = Input(PARAM).template data<THalf>();\n    const auto* momentIn = Input(MOMENT_1).template data<THalf>();\n    auto* paramOut = Output(OUTPUT_PARAM)->template mutable_data<THalf>();\n    auto* momentOut = Output(OUTPUT_MOMENT_1)->template mutable_data<THalf>();\n\n    auto N = Input(GRAD).size();\n    auto grad_slice_sz = Input(GRAD).size_from_dim(Input(INDICES).ndim());\n    if (N == 0) {\n      // empty grad, nothing to do here, not even launching the kernel\n      return true;\n    }\n    SparseAdagradKernel<IndexType, THalf>\n        <<<CAFFE_GET_BLOCKS(N),\n           CAFFE_CUDA_NUM_THREADS,\n           0,\n           context_.cuda_stream()>>>(\n            N,\n            grad_slice_sz,\n            epsilon_,\n            Output(OUTPUT_PARAM)->template mutable_data<THalf>(),\n            Output(OUTPUT_MOMENT_1)->template mutable_data<THalf>(),\n            Input(INDICES).template data<IndexType>(),\n            Input(GRAD).template data<float>(),\n            Input(LR).template data<float>());\n    return true;\n  }\n\n protected:\n  T epsilon_;\n  INPUT_TAGS(PARAM, MOMENT_1, INDICES, GRAD, LR);\n  OUTPUT_TAGS(OUTPUT_PARAM, OUTPUT_MOMENT_1);\n};\n\ntemplate <>\ntemplate <typename SIndex>\nbool RowWiseSparseAdagradOp<float, CUDAContext>::DoRunWithType() {\n  auto N = Input(GRAD).size();\n  if (N == 0) {\n    // empty grad, nothing to do here, not even launching the kernel\n    return true;\n  }\n  // size of the 1st dimension of the input gradient\n  auto GRAD_M = Input(GRAD).dim32(0);\n  auto GRAD_N = N / GRAD_M;\n\n  // each thread block will handle multiple rows of the input and output\n  RowWiseSparseAdagradKernel<<<\n      min(GRAD_M, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      GRAD_M,\n      GRAD_N,\n      epsilon_,\n      Output(OUTPUT_PARAM)->template mutable_data<float>(),\n      Output(OUTPUT_MOMENT_1)->template mutable_data<float>(),\n      Input(INDICES).template data<SIndex>(),\n      Input(GRAD).template data<float>(),\n      Input(LR).template data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Adagrad, AdagradOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(SparseAdagrad, CUDASparseAdagradOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    RowWiseSparseAdagrad,\n    RowWiseSparseAdagradOp<float, CUDAContext>);\n}\n"
  },
  {
    "path": "caffe2/sgd/adam_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"adam_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(Adam, AdamOp<float, CPUContext>);\nOPERATOR_SCHEMA(Adam)\n    .NumInputs(6)\n    .NumOutputs(3)\n    .AllowInplace({{0, 0}, {1, 1}, {2, 2}})\n    .SetDoc(R\"DOC(\n\nComputes the Adam update (https://arxiv.org/abs/1412.6980) for an\ninput gradient and momentum parameters. Concretely, given inputs\n(param, m1, m2, grad, lr, iters),\n\n    t = iters + 1\n    corrected_local_rate = lr * sqrt(1 - power(beta2, t)) /\n      (1 - power(beta1, t))\n    m1_o = (beta1 * m1) + (1 - beta1) * grad\n    m2_o = (beta2 * m2) + (1 - beta2) * np.square(grad)\n    grad_o = corrected_local_rate * m1_o / \\\n        (sqrt(m2_o) + epsilon)\n    param_o = param + grad_o\n\nand returns (param_o, m1_o, m2_o)\n\n)DOC\")\n    .Input(0, \"param\", \"Parameters to be updated\")\n    .Input(1, \"moment_1\", \"First moment history\")\n    .Input(2, \"moment_2\", \"Second moment history\")\n    .Input(3, \"grad\", \"Gradient computed\")\n    .Input(4, \"lr\", \"learning rate\")\n    .Input(5, \"iter\", \"iteration number\")\n    .Output(0, \"output_param\", \"Updated parameters\")\n    .Output(1, \"output_moment_1\", \"Updated first moment\")\n    .Output(2, \"output_moment_2\", \"Updated second moment\")\n    .Arg(\"beta1\", \"Default 0.9\")\n    .Arg(\"beta2\", \"Default 0.999\")\n    .Arg(\"epsilon\", \"Default 1e-5\");\n\nREGISTER_CPU_OPERATOR(SparseAdam, SparseAdamOp<float, CPUContext>);\nOPERATOR_SCHEMA(SparseAdam)\n    .NumInputs(7)\n    .NumOutputs(3)\n    .EnforceInplace({{0, 0}, {1, 1}, {2, 2}})\n    .SetDoc(R\"DOC(\n\nComputes the Adam Update for the sparse case.\nGiven inputs (param, moment1, moment2, indices, grad, lr, iter), runs the dense\nAdam on (param, moment1[indices], momemnt2[indices], lr, iter) and returns\n(new_param, new_moment1, new_moment2) as in dense case\n\n)DOC\")\n    .Input(0, \"param\", \"Parameters to be updated\")\n    .Input(1, \"moment_1\", \"First moment history\")\n    .Input(2, \"moment_2\", \"Second moment history\")\n    .Input(3, \"indices\", \"Sparse indices\")\n    .Input(4, \"grad\", \"Gradient computed\")\n    .Input(5, \"lr\", \"learning rate\")\n    .Input(6, \"iter\", \"iteration number\")\n    .Output(0, \"output_param\", \"Updated parameters\")\n    .Output(1, \"output_moment_1\", \"Updated first moment\")\n    .Output(2, \"output_moment_2\", \"Updated second moment\")\n    .Arg(\"beta1\", \"Default 0.9\")\n    .Arg(\"beta2\", \"Default 0.999\")\n    .Arg(\"epsilon\", \"Default 1e-5\");\n\nREGISTER_CPU_OPERATOR(\n    RowWiseSparseAdam,\n    RowWiseSparseAdamOp<float, CPUContext>);\nOPERATOR_SCHEMA(RowWiseSparseAdam)\n    .NumInputs(7)\n    .NumOutputs(3)\n    .EnforceInplace({{0, 0}, {1, 1}, {2, 2}})\n    .SetDoc(R\"DOC(\n\nComputes a modified Adam Update for the sparse case.\nGiven inputs (param, moment1, moment2, indices, grad, lr, iter), runs the\nAdam update on (param, moment1[indices], moment2[indices], lr, iter) and returns\n(new_param, new_moment1, new_moment2), where moment2 is a 1D tensor\nwith length equal to the number of rows in param:\nshape(moment2) == shape(param)[0]. Each element of  moment2 is\napplied to an entire row of param, and the new moment2 values are\ncalculated by averaging across the row.\n\n)DOC\")\n    .Input(0, \"param\", \"Parameters to be updated\")\n    .Input(1, \"moment_1\", \"First moment history\")\n    .Input(2, \"moment_2\", \"Second moment history\")\n    .Input(3, \"indices\", \"Sparse indices\")\n    .Input(4, \"grad\", \"Gradient computed\")\n    .Input(5, \"lr\", \"learning rate\")\n    .Input(6, \"iter\", \"iteration number\")\n    .Output(0, \"output_param\", \"Updated parameters\")\n    .Output(1, \"output_moment_1\", \"Updated first moment\")\n    .Output(2, \"output_moment_2\", \"Updated second moment\")\n    .Arg(\"beta1\", \"Default 0.9\")\n    .Arg(\"beta2\", \"Default 0.999\")\n    .Arg(\"epsilon\", \"Default 1e-5\");\n\nSHOULD_NOT_DO_GRADIENT(Adam);\nSHOULD_NOT_DO_GRADIENT(SparseAdam);\nSHOULD_NOT_DO_GRADIENT(RowWiseSparseAdam);\n}\n"
  },
  {
    "path": "caffe2/sgd/adam_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename Context>\nvoid adam_update(\n    int N,\n    const float* g,\n    const float* m,\n    const float* v,\n    float* ng,\n    float* nm,\n    float* nv,\n    float beta1,\n    float beta2,\n    float eps_hat,\n    float correction,\n    const float* lr,\n    Context* /*context*/) {\n  for (auto i = 0; i < N; ++i) {\n    float gi = g[i];\n    float mi = nm[i] = m[i] * beta1 + gi * (1 - beta1);\n    float vi = nv[i] = v[i] * beta2 + gi * gi * (1 - beta2);\n    ng[i] = lr[0] * correction * mi / (std::sqrt(vi) + eps_hat);\n  }\n}\n\ntemplate <typename Context>\nvoid adam_compute(\n    int N,\n    const float* w,\n    const float* g,\n    const float* m,\n    const float* v,\n    float* nw,\n    float* nm,\n    float* nv,\n    float beta1,\n    float beta2,\n    float eps_hat,\n    float correction,\n    const float* lr,\n    Context* /*context*/) {\n  for (auto i = 0; i < N; ++i) {\n    float gi = g[i];\n    float mi = nm[i] = m[i] * beta1 + gi * (1 - beta1);\n    float vi = nv[i] = v[i] * beta2 + gi * gi * (1 - beta2);\n    float ng = lr[0] * correction * mi / (std::sqrt(vi) + eps_hat);\n    nw[i] = w[i] + ng;\n  }\n}\n\ntemplate <typename T, class Context>\nclass AdamOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  AdamOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        beta1_(OperatorBase::GetSingleArgument<float>(\"beta1\", 0.9f)),\n        beta2_(OperatorBase::GetSingleArgument<float>(\"beta2\", 0.999f)),\n        epsilon_(OperatorBase::GetSingleArgument<float>(\"epsilon\", 1e-5f)) {}\n  bool RunOnDevice() override {\n    // Iter live on the CPU\n    CAFFE_ENFORCE(OperatorBase::InputIsType<TensorCPU>(ITER));\n    CAFFE_ENFORCE(Input(LR).size() == 1);\n    CAFFE_ENFORCE(Input(GRAD).size() == Input(PARAM).size());\n    CAFFE_ENFORCE(Input(GRAD).size() == Input(MOMENT_1).size());\n    CAFFE_ENFORCE(Input(GRAD).size() == Input(MOMENT_2).size());\n    Output(OUTPUT_PARAM)->ResizeLike(Input(PARAM));\n    Output(OUTPUT_MOMENT_1)->ResizeLike(Input(MOMENT_1));\n    Output(OUTPUT_MOMENT_2)->ResizeLike(Input(MOMENT_2));\n\n    const auto iter =\n        OperatorBase::Input<TensorCPU>(ITER).template data<int64_t>()[0];\n\n    const auto t = iter + 1;\n    const auto correction =\n        std::sqrt(T(1.) - std::pow(beta2_, t)) / (T(1.) - std::pow(beta1_, t));\n    adam_compute<Context>(\n        Input(GRAD).size(),\n        Input(PARAM).template data<T>(),\n        Input(GRAD).template data<T>(),\n        Input(MOMENT_1).template data<T>(),\n        Input(MOMENT_2).template data<T>(),\n        Output(OUTPUT_PARAM)->template mutable_data<T>(),\n        Output(OUTPUT_MOMENT_1)->template mutable_data<T>(),\n        Output(OUTPUT_MOMENT_2)->template mutable_data<T>(),\n        beta1_,\n        beta2_,\n        epsilon_,\n        correction,\n        Input(LR).template data<T>(),\n        &context_);\n    return true;\n  }\n\n protected:\n  T beta1_{0.9};\n  T beta2_{0.999};\n  T epsilon_{1e-8};\n  INPUT_TAGS(PARAM, MOMENT_1, MOMENT_2, GRAD, LR, ITER);\n  OUTPUT_TAGS(OUTPUT_PARAM, OUTPUT_MOMENT_1, OUTPUT_MOMENT_2);\n};\n\ntemplate <typename T, class Context>\nclass SparseAdamOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SparseAdamOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        beta1_(OperatorBase::GetSingleArgument<float>(\"beta1\", 0.9f)),\n        beta2_(OperatorBase::GetSingleArgument<float>(\"beta2\", 0.999f)),\n        epsilon_(OperatorBase::GetSingleArgument<float>(\"epsilon\", 1e-5f)) {}\n\n  bool RunOnDevice() override {\n    // Enforce shapes\n    CAFFE_ENFORCE_EQ(Input(PARAM).size(), Input(MOMENT_1).size());\n    CAFFE_ENFORCE_EQ(Input(PARAM).size(), Input(MOMENT_2).size());\n    CAFFE_ENFORCE_EQ(\n        Input(PARAM).size_from_dim(1),\n        Input(GRAD).size_from_dim(Input(INDICES).ndim()));\n    CAFFE_ENFORCE_EQ(Input(LR).size(), 1);\n\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename SIndex>\n  bool DoRunWithType() {\n    const auto* lr = Input(LR).template data<T>();\n    const auto iter =\n        OperatorBase::Input<TensorCPU>(ITER).template data<int64_t>()[0];\n\n    const auto t = iter + 1;\n    const auto correction =\n        std::sqrt(T(1.) - std::pow(beta2_, t)) / (T(1.) - std::pow(beta1_, t));\n\n    auto block_size = Input(PARAM).size() / Input(PARAM).dim(0);\n    auto n = Input(GRAD).size() / block_size;\n\n    const auto* paramIn = Input(PARAM).template data<T>();\n    const auto* indices = Input(INDICES).template data<SIndex>();\n    const auto* gradIn = Input(GRAD).template data<T>();\n    const auto* moment1In = Input(MOMENT_1).template data<T>();\n    const auto* moment2In = Input(MOMENT_2).template data<T>();\n    auto* paramOut = Output(OUTPUT_PARAM)->template mutable_data<T>();\n    auto* moment1Out = Output(OUTPUT_MOMENT_1)->template mutable_data<T>();\n    auto* moment2Out = Output(OUTPUT_MOMENT_2)->template mutable_data<T>();\n\n    for (auto i = 0; i < n; ++i) {\n      auto idx = indices[i];\n\n      if (block_size == 1) {\n        float gi = gradIn[i];\n        float mi = moment1Out[idx] =\n            moment1In[idx] * beta1_ + gi * (1 - beta1_);\n        float vi = moment2Out[idx] =\n            moment2In[idx] * beta2_ + gi * gi * (1 - beta2_);\n        paramOut[idx] =\n            paramIn[idx] + lr[0] * correction * mi / (std::sqrt(vi) + epsilon_);\n\n      } else {\n        auto offsetI = i * block_size;\n        auto offsetIdx = idx * block_size;\n\n#ifndef NDEBUG\n        CAFFE_ENFORCE_GE(\n            Input(PARAM).size(),\n            block_size + offsetIdx,\n            this->debug_def().input(PARAM),\n            \", out of bound,  idx:\",\n            idx,\n            \" for input i:\",\n            i,\n            \" and block size:\",\n            block_size);\n        CAFFE_ENFORCE_GE(\n            Input(GRAD).size(),\n            block_size + offsetI,\n            this->debug_def().input(GRAD),\n            \", out of bound idx, idx:\",\n            idx,\n            \" for input i:\",\n            i);\n#endif\n\n        adam_compute(\n            block_size,\n            paramIn + offsetIdx,\n            gradIn + offsetI,\n            moment1In + offsetIdx,\n            moment2In + offsetIdx,\n            paramOut + offsetIdx,\n            moment1Out + offsetIdx,\n            moment2Out + offsetIdx,\n            beta1_,\n            beta2_,\n            epsilon_,\n            correction,\n            lr,\n            &context_);\n      }\n    }\n    return true;\n  }\n\n protected:\n  T beta1_;\n  T beta2_;\n  T epsilon_;\n  INPUT_TAGS(PARAM, MOMENT_1, MOMENT_2, INDICES, GRAD, LR, ITER);\n  OUTPUT_TAGS(OUTPUT_PARAM, OUTPUT_MOMENT_1, OUTPUT_MOMENT_2);\n};\n\ntemplate <typename T, class Context>\nclass RowWiseSparseAdamOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  RowWiseSparseAdamOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        beta1_(OperatorBase::GetSingleArgument<float>(\"beta1\", 0.9f)),\n        beta2_(OperatorBase::GetSingleArgument<float>(\"beta2\", 0.999f)),\n        epsilon_(OperatorBase::GetSingleArgument<float>(\"epsilon\", 1e-5f)) {}\n\n  bool RunOnDevice() override {\n    // Enforce shapes\n    CAFFE_ENFORCE_EQ(Input(PARAM).size(), Input(MOMENT_1).size());\n    CAFFE_ENFORCE_EQ(Input(PARAM).dims()[0], Input(MOMENT_2).size());\n    CAFFE_ENFORCE_EQ(\n        Input(PARAM).size_from_dim(1),\n        Input(GRAD).size_from_dim(Input(INDICES).ndim()));\n    CAFFE_ENFORCE_EQ(Input(LR).size(), 1);\n\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename SIndex>\n  bool DoRunWithType() {\n    const auto* lr = Input(LR).template data<T>();\n    const auto iter =\n        OperatorBase::Input<TensorCPU>(ITER).template data<int64_t>()[0];\n\n    const auto t = iter + 1;\n    const auto correction =\n        std::sqrt(T(1.) - std::pow(beta2_, t)) / (T(1.) - std::pow(beta1_, t));\n\n    auto block_size = Input(PARAM).size() / Input(PARAM).dim(0);\n    auto n = Input(GRAD).size() / block_size;\n\n    const auto* paramIn = Input(PARAM).template data<T>();\n    const auto* indices = Input(INDICES).template data<SIndex>();\n    const auto* gradIn = Input(GRAD).template data<T>();\n    const auto* moment1In = Input(MOMENT_1).template data<T>();\n    const auto* moment2In = Input(MOMENT_2).template data<T>();\n    auto* paramOut = Output(OUTPUT_PARAM)->template mutable_data<T>();\n    auto* moment1Out = Output(OUTPUT_MOMENT_1)->template mutable_data<T>();\n    auto* moment2Out = Output(OUTPUT_MOMENT_2)->template mutable_data<T>();\n\n    for (auto i = 0; i < n; ++i) {\n      auto idx = indices[i];\n\n      if (block_size == 1) {\n        float gi = gradIn[i];\n        float mi = moment1Out[idx] =\n            moment1In[idx] * beta1_ + gi * (1 - beta1_);\n        float vi = moment2Out[idx] =\n            moment2In[idx] * beta2_ + gi * gi * (1 - beta2_);\n        paramOut[idx] =\n            paramIn[idx] + lr[0] * correction * mi / (std::sqrt(vi) + epsilon_);\n\n      } else {\n        auto offsetI = i * block_size;\n        auto offsetIdx = idx * block_size;\n\n#ifndef NDEBUG\n        CAFFE_ENFORCE_GE(\n            Input(PARAM).size(),\n            block_size + offsetIdx,\n            this->debug_def().input(PARAM),\n            \", out of bound,  idx:\",\n            idx,\n            \" for input i:\",\n            i,\n            \" and block size:\",\n            block_size);\n        CAFFE_ENFORCE_GE(\n            Input(GRAD).size(),\n            block_size + offsetI,\n            this->debug_def().input(GRAD),\n            \", out of bound idx, idx:\",\n            idx,\n            \" for input i:\",\n            i);\n#endif\n\n        const float* w = paramIn + offsetIdx;\n        const float* g = gradIn + offsetI;\n        const float* m1 = moment1In + offsetIdx;\n        const float* m2 = moment2In + idx;\n        float* nw = paramOut + offsetIdx;\n        float* nm1 = moment1Out + offsetIdx;\n        float* nm2 = moment2Out + idx;\n\n        float m2_sum = 0.;\n        for (auto j = 0; j < block_size; ++j) {\n          float gj = g[j];\n          m2_sum += gj * gj;\n        }\n        float vi = nm2[0] =\n            m2[0] * beta2_ + (m2_sum / block_size) * (1 - beta2_);\n        for (auto j = 0; j < block_size; ++j) {\n          float mi = nm1[j] = m1[j] * beta1_ + g[j] * (1 - beta1_);\n          nw[j] = w[j] + lr[0] * correction * mi / (std::sqrt(vi) + epsilon_);\n        }\n      }\n    }\n    return true;\n  }\n\n protected:\n  T beta1_;\n  T beta2_;\n  T epsilon_;\n  INPUT_TAGS(PARAM, MOMENT_1, MOMENT_2, INDICES, GRAD, LR, ITER);\n  OUTPUT_TAGS(OUTPUT_PARAM, OUTPUT_MOMENT_1, OUTPUT_MOMENT_2);\n};\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/sgd/adam_op_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"adam_op.h\"\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\n__global__ void AdamUpdate(\n    int N,\n    const float* g,\n    const float* m,\n    const float* v,\n    float* ng,\n    float* nm,\n    float* nv,\n    float beta1,\n    float beta2,\n    float eps_hat,\n    float correction,\n    const float* lr) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    float gi = g[i];\n    float mi = nm[i] = m[i] * beta1 + gi * (1 - beta1);\n    float vi = nv[i] = v[i] * beta2 + gi * gi * (1 - beta2);\n    ng[i] = lr[0] * correction * mi / (std::sqrt(vi) + eps_hat);\n  }\n}\n\ntemplate<>\nvoid adam_update<CUDAContext>(\n    int N,\n    const float* g,\n    const float* m,\n    const float* v,\n    float* ng,\n    float* nm,\n    float* nv,\n    float beta1,\n    float beta2,\n    float eps_hat,\n    float correction,\n    const float* lr,\n    CUDAContext* context) {\n  AdamUpdate<<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS, 0, context->cuda_stream()>>>(\n      N, g, m, v, ng, nm, nv, beta1, beta2, eps_hat, correction, lr);\n}\n\n__global__ void AdamCompute(\n    int N,\n    const float* w,\n    const float* g,\n    const float* m,\n    const float* v,\n    float* nw,\n    float* nm,\n    float* nv,\n    float beta1,\n    float beta2,\n    float eps_hat,\n    float correction,\n    const float* lr) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    float gi = g[i];\n    float mi = nm[i] = m[i] * beta1 + gi * (1 - beta1);\n    float vi = nv[i] = v[i] * beta2 + gi * gi * (1 - beta2);\n    float ng = lr[0] * correction * mi / (std::sqrt(vi) + eps_hat);\n    nw[i] = w[i] + ng;\n  }\n}\n\ntemplate<>\nvoid adam_compute<CUDAContext>(\n    int N,\n    const float* w,\n    const float* g,\n    const float* m,\n    const float* v,\n    float* nw,\n    float* nm,\n    float* nv,\n    float beta1,\n    float beta2,\n    float eps_hat,\n    float correction,\n    const float* lr,\n    CUDAContext* context) {\n  AdamCompute<<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS, 0, context->cuda_stream()>>>(\n      N, w, g, m, v, nw, nm, nv, beta1, beta2, eps_hat, correction, lr);\n}\n\ntemplate <typename SIndex>\n__global__ void SparseAdamKernel(\n    const size_t N,\n    const size_t grad_slice_sz,\n    const float beta1,\n    const float beta2,\n    const float epsilon,\n    float *param,\n    float *mom1,\n    float *mom2,\n    const SIndex *indices,\n    const float *grad,\n    const float correction,\n    const float *lr,\n    const float iter)\n{\n  CUDA_1D_KERNEL_LOOP(i, N)\n  {\n    const size_t gradIdx = i;\n    const SIndex index = indices[i / grad_slice_sz];\n    const size_t paramIdx = index * grad_slice_sz + (i % grad_slice_sz);\n\n    float m1n = mom1[paramIdx] = mom1[paramIdx] * beta1 +\n        grad[gradIdx] * (1.0f - beta1);\n    float m2n = mom2[paramIdx] = mom2[paramIdx] * beta2 +\n        grad[gradIdx] * grad[gradIdx] * (1.0f - beta2);\n    param[paramIdx] += lr[0] * correction * m1n / (sqrt(m2n) + epsilon);\n  }\n}\n\ntemplate<>\ntemplate<typename SIndex>\nbool SparseAdamOp<float, CUDAContext>::DoRunWithType()\n{\n  auto N = Input(GRAD).size();\n  auto grad_slice_sz = Input(GRAD).size_from_dim(Input(INDICES).ndim());\n  const auto iter =\n      OperatorBase::Input<TensorCPU>(ITER).template data<int64_t>()[0];\n  const float correction = std::sqrt(1.0f - std::pow(beta2_, iter + 1)) /\n      (1.0f - std::pow(beta1_, iter + 1));\n\n  SparseAdamKernel<SIndex><<<\n    CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS, 0,\n    context_.cuda_stream()>>>(\n        N, grad_slice_sz,\n        beta1_, beta2_, epsilon_,\n        Output(OUTPUT_PARAM)->template mutable_data<float>(),\n        Output(OUTPUT_MOMENT_1)->template mutable_data<float>(),\n        Output(OUTPUT_MOMENT_2)->template mutable_data<float>(),\n        Input(INDICES).template data<SIndex>(),\n        Input(GRAD).template data<float>(),\n        correction,\n        Input(LR).template data<float>(),\n        iter);\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(Adam, AdamOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(SparseAdam, SparseAdamOp<float, CUDAContext>);\n\n}\n"
  },
  {
    "path": "caffe2/sgd/clip_tensor_op.cc",
    "content": "#include \"caffe2/sgd/clip_tensor_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(ClipTensorByScaling, ClipTensorByScalingOp<CPUContext>);\nOPERATOR_SCHEMA(ClipTensorByScaling)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\n    Clips the input tensor by scaling based on the input value and the threshold.\n    The value is usually the (pre-computed) norm of the tensor. If the value is\n    larger than the threshold, scaling would be performed in this way:\n\n          tensor *= (threshold / value).\n\n    This op could be used for gradient clipping.\n)DOC\")\n    .Input(0, \"input_tensor\", \"Tensor of floats to be clipped.\")\n    .Input(1, \"val\", \"Value to be compared against the threshold\")\n    .Arg(\"threshold\", \"Threshold to determine whether to scale down the tensor\")\n    .Output(\n        0,\n        \"clipped\",\n        \"Tensor of floats, which is the same size as the input tensor, \"\n        \"representing the clipped tensor.\");\n\nSHOULD_NOT_DO_GRADIENT(ClipTensorByScaling);\n}; // namespace caffe2\n"
  },
  {
    "path": "caffe2/sgd/clip_tensor_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_CLIP_TENSOR_OP_H_\n#define CAFFE2_OPERATORS_CLIP_TENSOR_OP_H_\n\n#include <vector>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename Context>\nclass ClipTensorByScalingOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  ClipTensorByScalingOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {\n    threshold_ = OperatorBase::GetSingleArgument<float>(\"threshold\", 0.0);\n    CAFFE_ENFORCE_GT(threshold_, 0, \"Threshold must be greater than 0\");\n  }\n\n  bool RunOnDevice() override {\n    const auto& input_tensor = Input(0);\n    CAFFE_ENFORCE_GT(input_tensor.size(), 0);\n    const auto& val = Input(1);\n    CAFFE_ENFORCE_EQ(val.size(), 1);\n\n    const auto* input_tensor_data = input_tensor.template data<float>();\n    const auto* val_data = val.template data<float>();\n\n    auto* clipped = Output(0);\n    clipped->ResizeLike(input_tensor);\n    float* clipped_tensor_data = clipped->template mutable_data<float>();\n\n    if (*val_data > threshold_) {\n      float ratio = threshold_ / *val_data;\n\n      math::Scale<float, Context>(\n          clipped->size(),\n          ratio,\n          input_tensor_data,\n          clipped_tensor_data,\n          &context_);\n    } else {\n      if (input_tensor_data != clipped_tensor_data) {\n        clipped->CopyFrom(input_tensor, &context_);\n      }\n    }\n\n    return true;\n  }\n\n private:\n  float threshold_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_CLIP_TENSOR_OP_H_\n"
  },
  {
    "path": "caffe2/sgd/fp16_momentum_sgd_op.cu",
    "content": "#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n\n#include \"fp16_momentum_sgd_op.h\"\n\nnamespace caffe2 {\nnamespace {\n__global__ void FP16MomentumSGDKernel(\n    int N,\n    const half2* g,\n    const half2* m,\n    half2* ng,\n    half2* nm,\n    const float* lr,\n    const float mom,\n    bool nesterov,\n    const float wd,\n    half2* param) {\n#if __CUDA_ARCH__ >= 530\n  const float lr2 = lr[0];\n  const half2 LR = __float2half2_rn(lr2);\n  const half2 momentum = __float2half2_rn(mom);\n  const half2 weight_decay = __float2half2_rn(wd);\n\n  int n = N / 2;\n  if (!nesterov) {\n    CUDA_1D_KERNEL_LOOP(i, n) {\n      ng[i] = __hfma2(weight_decay, param[i], g[i]);\n      const half2 adjusted_gradient =\n          __hfma2(LR, ng[i], __hmul2(momentum, m[i]));\n      nm[i] = adjusted_gradient;\n      ng[i] = adjusted_gradient;\n      if (param) {\n        param[i] = __hsub2(param[i], ng[i]);\n      }\n\n      // odd number of elements\n      if (i == 0 && (N % 2)) {\n        half *g_half = (half*)g, *param_half = (half*)param, *m_half = (half*)m,\n             *nm_half = (half*)nm, *ng_half = (half*)ng;\n        ng_half[N - 1] =\n            __hfma(__high2half(weight_decay), param_half[N - 1], g_half[N - 1]);\n        const half adjusted_gradient_half = __hfma(\n            __high2half(LR),\n            ng_half[N - 1],\n            __hmul(__high2half(momentum), m_half[N - 1]));\n        nm_half[N - 1] = adjusted_gradient_half;\n        ng_half[N - 1] = adjusted_gradient_half;\n        if (param) {\n          param_half[N - 1] = __hsub(param_half[N - 1], adjusted_gradient_half);\n        }\n      }\n    }\n  } else {\n    CUDA_1D_KERNEL_LOOP(i, n) {\n      // computing the term (grad + lambda*weight)\n      // might need to change in case of denormalization\n      ng[i] = __hfma2(weight_decay, param[i], g[i]);\n      const half2 mi = m[i];\n      const half2 mom_mi = __hmul2(momentum, mi);\n      const half2 mi_new = __hfma2(LR, ng[i], mom_mi);\n      nm[i] = mi_new;\n      ng[i] = __hsub2(__hfma2(mi_new, momentum, mi_new), mom_mi);\n\n      if (param) {\n        param[i] = __hsub2(param[i], ng[i]);\n      }\n\n      // odd number of elements\n      if (i == 0 && (N % 2)) {\n        half *g_half = (half*)g, *param_half = (half*)param, *m_half = (half*)m,\n             *nm_half = (half*)nm, *ng_half = (half*)ng;\n        ng_half[N - 1] =\n            __hfma(__high2half(weight_decay), param_half[N - 1], g_half[N - 1]);\n        const half mi_half = m_half[N - 1];\n        const half mom_mi_half = __hmul(__high2half(momentum), mi_half);\n        const half mi_new_half =\n            __hfma(__high2half(LR), ng_half[N - 1], mom_mi_half);\n        nm_half[N - 1] = mi_new_half;\n        ng_half[N - 1] = __hsub(\n            __hfma(mi_new_half, __high2half(momentum), mi_new_half),\n            mom_mi_half);\n        if (param) {\n          param_half[N - 1] = __hsub(param_half[i], ng_half[N - 1]);\n        }\n      }\n    }\n  }\n\n#else\n   CUDA_KERNEL_ASSERT(false);\n#endif // CAFFE_HAS_CUDA_FP16\n}\n\n__global__ void FP16MomentumSGDFP32Kernel(\n    int N,\n    const half2* g,\n    const half2* m,\n    half2* ng,\n    half2* nm,\n    const float* lr,\n    const float mom,\n    bool nesterov,\n    const float wd,\n    half2* param) {\n#if __CUDA_ARCH__ >= 530\n  const float lr2 = lr[0];\n  const float LR = lr2;\n  const float momentum = mom;\n  const float weight_decay = wd;\n\n  int n = N / 2;\n  if (!nesterov) {\n    CUDA_1D_KERNEL_LOOP(i, n) {\n      float2 param_float2 = __half22float2(param[i]);\n      float2 g_float2 = __half22float2(g[i]);\n\n      float2 ng_float2;\n      ng_float2.x = __fmaf_rn(weight_decay, param_float2.x, g_float2.x);\n      ng_float2.y = __fmaf_rn(weight_decay, param_float2.y, g_float2.y);\n\n      float2 m_float2 = __half22float2(m[i]);\n      float2 adjusted_gradient_float2;\n      adjusted_gradient_float2.x =\n          __fmaf_rn(LR, ng_float2.x, __fmul_rn(momentum, m_float2.x));\n      adjusted_gradient_float2.y =\n          __fmaf_rn(LR, ng_float2.y, __fmul_rn(momentum, m_float2.y));\n\n      nm[i] = __float22half2_rn(adjusted_gradient_float2);\n      ng[i] = __float22half2_rn(adjusted_gradient_float2);\n\n      if (param) {\n        param_float2.x = __fsub_rn(param_float2.x, adjusted_gradient_float2.x);\n        param_float2.y = __fsub_rn(param_float2.y, adjusted_gradient_float2.y);\n        param[i] = __float22half2_rn(param_float2);\n      }\n    }\n  } else {\n    CUDA_1D_KERNEL_LOOP(i, n) {\n      // computing the term (grad + lambda*weight)\n      // might need to change in case of denormalization\n\n      float2 param_float2 = __half22float2(param[i]);\n      float2 g_float2 = __half22float2(g[i]);\n\n      float2 ng_float2;\n      ng_float2.x = __fmaf_rn(weight_decay, param_float2.x, g_float2.x);\n      ng_float2.y = __fmaf_rn(weight_decay, param_float2.y, g_float2.y);\n\n      const float2 mi_float2 = __half22float2(m[i]);\n      float2 mom_mi_float2;\n      mom_mi_float2.x = __fmul_rn(momentum, mi_float2.x);\n      mom_mi_float2.y = __fmul_rn(momentum, mi_float2.y);\n      float2 mi_new_float2;\n      mi_new_float2.x = __fmaf_rn(LR, ng_float2.x, mom_mi_float2.x);\n      mi_new_float2.y = __fmaf_rn(LR, ng_float2.y, mom_mi_float2.y);\n\n      nm[i] = __float22half2_rn(mi_new_float2);\n      ng_float2.x = __fsub_rn(\n          __fmaf_rn(mi_new_float2.x, momentum, mi_new_float2.x),\n          mom_mi_float2.x);\n      ng_float2.y = __fsub_rn(\n          __fmaf_rn(mi_new_float2.y, momentum, mi_new_float2.y),\n          mom_mi_float2.y);\n      ng[i] = __float22half2_rn(ng_float2);\n\n      if (param) {\n        param_float2.x = __fsub_rn(param_float2.x, ng_float2.x);\n        param_float2.y = __fsub_rn(param_float2.y, ng_float2.y);\n        param[i] = __float22half2_rn(param_float2);\n      }\n    }\n  }\n#else\n   CUDA_KERNEL_ASSERT(false);\n#endif // CAFFE_HAS_CUDA_FP16\n}\n}\n\ntemplate <>\nvoid fp16_momentum_sgd_update<CUDAContext>(\n    int N,\n    const float16* g,\n    const float16* m,\n    float16* ng,\n    float16* nm,\n    const float* lr,\n    float momentum,\n    bool nesterov,\n    float weight_decay,\n    bool fp32_update,\n    float16* param,\n    CUDAContext* context) {\n  const cudaDeviceProp& prop = GetDeviceProperty(0);\n  if (prop.major >= 6) {\n    if (!fp32_update) {\n      FP16MomentumSGDKernel<<<\n          CAFFE_GET_BLOCKS(N / 2),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context->cuda_stream()>>>(\n          N,\n          reinterpret_cast<const half2*>(g),\n          reinterpret_cast<const half2*>(m),\n          reinterpret_cast<half2*>(ng),\n          reinterpret_cast<half2*>(nm),\n          lr,\n          momentum,\n          nesterov,\n          weight_decay,\n          reinterpret_cast<half2*>(param));\n      // not setting N to N/2\n    } else {\n      FP16MomentumSGDFP32Kernel<<<\n          CAFFE_GET_BLOCKS(N / 2),\n          CAFFE_CUDA_NUM_THREADS,\n          0,\n          context->cuda_stream()>>>(\n          N,\n          reinterpret_cast<const half2*>(g),\n          reinterpret_cast<const half2*>(m),\n          reinterpret_cast<half2*>(ng),\n          reinterpret_cast<half2*>(nm),\n          lr,\n          momentum,\n          nesterov,\n          weight_decay,\n          reinterpret_cast<half2*>(param));\n      // not setting N to N/2\n    }\n\n  } else {\n    CAFFE_ENFORCE(false, \"FP16MomentumSGDUpdate not supported. Major: \",\n      prop.major, \" Minor: \", prop.minor);\n  }\n}\n\nREGISTER_CUDA_OPERATOR(\n    FP16MomentumSGDUpdate,\n    FP16MomentumSGDUpdateOp<float16, CUDAContext>);\nOPERATOR_SCHEMA(FP16MomentumSGDUpdate)\n    .NumInputs(4)\n    .NumOutputs(3)\n    .AllowInplace({{0, 0}, {1, 1}, {3, 2}})\n    .TensorInferenceFunction([](const OperatorDef& /* unused */,\n                                const vector<TensorShape>& in) {\n      vector<TensorShape> out(3);\n      out[0] = in[0];\n      out[1] = in[1];\n      out[2] = in[3];\n      return out;\n    })\n    .SetDoc(R\"DOC(\n\nComputes the momentum SGD update similarly to the MomentumSGDUpdateOp,\nhowever this op also performs the weight decay update at the same time, thus\nmaking it more efficient.\n\nThis op is also functionally equivalent to the FP32MomentumSGDUpdateOp, however\nit expects FP16 data and performs its updates in either FP16 precision\n(default), or FP32 precision if the 'fp32_update' flag is set to True.\n\n)DOC\");\n}\n"
  },
  {
    "path": "caffe2/sgd/fp16_momentum_sgd_op.h",
    "content": "#pragma once\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nvoid fp16_momentum_sgd_update(\n    int N,\n    const float16* g,\n    const float16* m,\n    float16* ng,\n    float16* nm,\n    const float* lr,\n    float momentum,\n    bool nesterov,\n    float weight_decay,\n    bool fp32_update,\n    float16* param,\n    Context* /*context*/) {}\n\ntemplate <typename T, class Context>\nclass FP16MomentumSGDUpdateOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  FP16MomentumSGDUpdateOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        momentum_(OperatorBase::GetSingleArgument<float>(\"momentum\", 0.0)),\n        weight_decay_(\n            OperatorBase::GetSingleArgument<float>(\"weight_decay\", 0.0)),\n        nesterov_(OperatorBase::GetSingleArgument<int>(\"nesterov\", 0)),\n        // when set, fp32_update will read in the fp16 data but\n        // perform all the compute in fp32 precision.\n        fp32_update_(OperatorBase::GetSingleArgument<int>(\"fp32_update\", 0)) {}\n\n  bool RunOnDevice() override {\n    // Iter live on the CPU\n    CAFFE_ENFORCE(OperatorBase::InputIsType<Tensor<Context>>(GRAD));\n    CAFFE_ENFORCE(OperatorBase::InputIsType<Tensor<Context>>(MOMENTUM));\n    CAFFE_ENFORCE(Input(LR).size() == 1);\n    CAFFE_ENFORCE(Input(GRAD).size() == Input(MOMENTUM).size());\n    Output(OUTPUT_GRAD)->ResizeLike(Input(GRAD));\n    Output(OUTPUT_MOMENTUM)->ResizeLike(Input(MOMENTUM));\n\n    fp16_momentum_sgd_update<Context>(\n        Input(GRAD).size(),\n        Input(GRAD).template data<T>(),\n        Input(MOMENTUM).template data<T>(),\n        Output(OUTPUT_GRAD)->template mutable_data<T>(),\n        Output(OUTPUT_MOMENTUM)->template mutable_data<T>(),\n        Input(LR).template data<float>(),\n        momentum_,\n        nesterov_,\n        weight_decay_,\n        fp32_update_,\n        Output(OUTPUT_PARAM)->template mutable_data<T>(),\n        &context_);\n\n    return true;\n  }\n\n protected:\n  float momentum_{0.9};\n  float weight_decay_{0.0};\n  bool nesterov_;\n  bool fp32_update_;\n  INPUT_TAGS(GRAD, MOMENTUM, LR, PARAM);\n  OUTPUT_TAGS(OUTPUT_GRAD, OUTPUT_MOMENTUM, OUTPUT_PARAM);\n};\n}\n"
  },
  {
    "path": "caffe2/sgd/fp32_momentum_sgd_op.cu",
    "content": "#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n\n#include \"fp32_momentum_sgd_op.h\"\n\nnamespace caffe2 {\nnamespace {\n\n__global__ void FP32MomentumSGDKernel(\n    int N,\n    const float2* g,\n    const float2* m,\n    float2* ng,\n    float2* nm,\n    const float* lr,\n    const float mom,\n    bool nesterov,\n    const float wd,\n    float2* param) {\n#if __CUDA_ARCH__ >= 530\n  const float lr2 = lr[0];\n  const float LR = lr2;\n  const float momentum = mom;\n  const float weight_decay = wd;\n\n  int n = N / 2;\n  if (!nesterov) {\n    CUDA_1D_KERNEL_LOOP(i, n) {\n      ng[i].x = __fmaf_rn(weight_decay, param[i].x, g[i].x);\n      ng[i].y = __fmaf_rn(weight_decay, param[i].y, g[i].y);\n\n      float2 mi_float2 = m[i];\n      float2 adjusted_gradient_float2;\n      adjusted_gradient_float2.x =\n          __fmaf_rn(LR, ng[i].x, __fmul_rn(momentum, mi_float2.x));\n      adjusted_gradient_float2.y =\n          __fmaf_rn(LR, ng[i].y, __fmul_rn(momentum, mi_float2.y));\n\n      nm[i] = adjusted_gradient_float2;\n      ng[i] = adjusted_gradient_float2;\n\n      if (param) {\n        param[i].x = __fsub_rn(param[i].x, adjusted_gradient_float2.x);\n        param[i].y = __fsub_rn(param[i].y, adjusted_gradient_float2.y);\n      }\n    }\n  } else {\n    CUDA_1D_KERNEL_LOOP(i, n) {\n      // computing the term (grad + lambda*weight)\n      // might need to change in case of denormalization\n\n      ng[i].x = __fmaf_rn(weight_decay, param[i].x, g[i].x);\n      ng[i].y = __fmaf_rn(weight_decay, param[i].y, g[i].y);\n\n      const float2 mi_float2 = m[i];\n      float2 mom_mi_float2;\n      mom_mi_float2.x = __fmul_rn(momentum, mi_float2.x);\n      mom_mi_float2.y = __fmul_rn(momentum, mi_float2.y);\n      float2 mi_new_float2;\n      mi_new_float2.x = __fmaf_rn(LR, ng[i].x, mom_mi_float2.x);\n      mi_new_float2.y = __fmaf_rn(LR, ng[i].y, mom_mi_float2.y);\n\n      nm[i] = mi_new_float2;\n      ng[i].x = __fsub_rn(\n          __fmaf_rn(mi_new_float2.x, momentum, mi_new_float2.x),\n          mom_mi_float2.x);\n      ng[i].y = __fsub_rn(\n          __fmaf_rn(mi_new_float2.y, momentum, mi_new_float2.y),\n          mom_mi_float2.y);\n\n      if (param) {\n        param[i].x = __fsub_rn(param[i].x, ng[i].x);\n        param[i].y = __fsub_rn(param[i].y, ng[i].y);\n      }\n    }\n  }\n#else\n   CUDA_KERNEL_ASSERT(false);\n#endif // CAFFE_HAS_CUDA_FP16\n}\n}\n\ntemplate <>\nvoid fp32_momentum_sgd_update<CUDAContext>(\n    int N,\n    const float* g,\n    const float* m,\n    float* ng,\n    float* nm,\n    const float* lr,\n    float momentum,\n    bool nesterov,\n    float weight_decay,\n    float* param,\n    CUDAContext* context) {\n  FP32MomentumSGDKernel<<<\n      CAFFE_GET_BLOCKS(N / 2),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(\n      N,\n      reinterpret_cast<const float2*>(g),\n      reinterpret_cast<const float2*>(m),\n      reinterpret_cast<float2*>(ng),\n      reinterpret_cast<float2*>(nm),\n      lr,\n      momentum,\n      nesterov,\n      weight_decay,\n      reinterpret_cast<float2*>(param));\n  // not setting N to N/2\n  // TODO_ check float performance vs float2\n}\n\nREGISTER_CUDA_OPERATOR(\n    FP32MomentumSGDUpdate,\n    FP32MomentumSGDUpdateOp<float, CUDAContext>);\nOPERATOR_SCHEMA(FP32MomentumSGDUpdate)\n    .NumInputs(4)\n    .NumOutputs(3)\n    .AllowInplace({{0, 0}, {1, 1}, {3, 2}})\n    .TensorInferenceFunction([](const OperatorDef& /* unused */,\n                                const vector<TensorShape>& in) {\n      vector<TensorShape> out(3);\n      out[0] = in[0];\n      out[1] = in[1];\n      out[2] = in[3];\n      return out;\n    })\n    .SetDoc(R\"DOC(\n\nComputes the momentum SGD update similarly to the MomentumSGDUpdateOp,\nhowever this op also performs the weight decay update at the same time, thus\nmaking it more efficient.\n\nThis op is also functionally equivalent to the FP16MomentumSGDUpdateOp, however\nit expects FP32 data and performs its updates in FP32 precision.\n\n)DOC\");\n}\n"
  },
  {
    "path": "caffe2/sgd/fp32_momentum_sgd_op.h",
    "content": "#pragma once\n\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/timer.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nvoid fp32_momentum_sgd_update(\n    int N,\n    const float* g,\n    const float* m,\n    float* ng,\n    float* nm,\n    const float* lr,\n    float momentum,\n    bool nesterov,\n    float weight_decay,\n    float* param,\n    Context* /*context*/) {}\n\ntemplate <typename T, class Context>\nclass FP32MomentumSGDUpdateOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  FP32MomentumSGDUpdateOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        momentum_(OperatorBase::GetSingleArgument<float>(\"momentum\", 0.0)),\n        weight_decay_(\n            OperatorBase::GetSingleArgument<float>(\"weight_decay\", 0.0)),\n        nesterov_(OperatorBase::GetSingleArgument<int>(\"nesterov\", 0)) {}\n\n  bool RunOnDevice() override {\n    // Iter live on the CPU\n    CAFFE_ENFORCE(OperatorBase::InputIsType<Tensor<Context>>(GRAD));\n    CAFFE_ENFORCE(OperatorBase::InputIsType<Tensor<Context>>(MOMENTUM));\n    CAFFE_ENFORCE(Input(LR).size() == 1);\n    CAFFE_ENFORCE(Input(GRAD).size() == Input(MOMENTUM).size());\n    Output(OUTPUT_GRAD)->ResizeLike(Input(GRAD));\n    Output(OUTPUT_MOMENTUM)->ResizeLike(Input(MOMENTUM));\n\n    fp32_momentum_sgd_update<Context>(\n        Input(GRAD).size(),\n        Input(GRAD).template data<T>(),\n        Input(MOMENTUM).template data<T>(),\n        Output(OUTPUT_GRAD)->template mutable_data<T>(),\n        Output(OUTPUT_MOMENTUM)->template mutable_data<T>(),\n        Input(LR).template data<float>(),\n        momentum_,\n        nesterov_,\n        weight_decay_,\n        Output(OUTPUT_PARAM)->template mutable_data<T>(),\n        &context_);\n\n    return true;\n  }\n\n protected:\n  float momentum_{0.9};\n  float weight_decay_{0.0};\n  bool nesterov_;\n  INPUT_TAGS(GRAD, MOMENTUM, LR, PARAM);\n  OUTPUT_TAGS(OUTPUT_GRAD, OUTPUT_MOMENTUM, OUTPUT_PARAM);\n};\n}\n"
  },
  {
    "path": "caffe2/sgd/ftrl_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"ftrl_op.h\"\n\nnamespace caffe2 {\n\ntemplate <class T>\ninline T sgn(const T x) {\n  return (x == 0 ? 0 : (x < 0 ? -1 : 1));\n}\n\ntemplate <typename T>\ninline void ftrl_compute(\n    const T w,\n    const T n,\n    const T z,\n    const T g,\n    T& nw,\n    T& nn,\n    T& nz,\n    const FtrlParams<T>& params) {\n  auto new_n = n + g * g;\n  auto sigma = (sqrt(new_n) - sqrt(n)) * params.alphaInv;\n  nn = new_n;\n  nz = z + g - sigma * w;\n  // update the weight\n  if (std::abs(nz) > params.lambda1) {\n    nw = (params.lambda1 * sgn(nz) - nz) /\n        ((params.beta + sqrt(new_n)) * params.alphaInv + params.lambda2);\n  } else {\n    nw = 0.0;\n  }\n}\n\n// TODO(dzhulgakov): implement SIMD-based version\ntemplate <typename Context, typename T>\nvoid ftrl_update(\n    int N,\n    const T* w,\n    const T* nz,\n    const T* g,\n    T* new_w,\n    T* new_nz,\n    const FtrlParams<T>& params,\n    Context* /*context*/) {\n  // TODO(cxj): use OMP when it is reliable\n  // #pragma omp parallel for\n  for (auto i = 0; i < N; ++i) {\n    ftrl_compute(\n        w[i],\n        nz[i * 2],\n        nz[i * 2 + 1],\n        g[i],\n        new_w[i],\n        new_nz[i * 2],\n        new_nz[i * 2 + 1],\n        params);\n  }\n}\n\ntemplate <typename T, typename Context>\nbool FtrlOp<T, Context>::RunOnDevice() {\n  // run time learning rate override\n  if (ALPHA < InputSize()) {\n    CAFFE_ENFORCE_EQ(Input(ALPHA).size(), 1, \"alpha should be real-valued\");\n    params_.alphaInv = 1.0 / *(Input(ALPHA).template data<T>());\n  }\n  CAFFE_ENFORCE_EQ(Input(GRAD).size(), Input(VAR).size());\n  CAFFE_ENFORCE_EQ(Input(GRAD).size() * 2, Input(N_Z).size());\n  Output(OUTPUT_VAR)->ResizeLike(Input(VAR));\n  Output(OUTPUT_N_Z)->ResizeLike(Input(N_Z));\n  ftrl_update<Context>(\n      Input(GRAD).size(),\n      Input(VAR).template data<T>(),\n      Input(N_Z).template data<T>(),\n      Input(GRAD).template data<T>(),\n      Output(OUTPUT_VAR)->template mutable_data<T>(),\n      Output(OUTPUT_N_Z)->template mutable_data<T>(),\n      params_,\n      &context_);\n  return true;\n}\n\ntemplate <typename T>\ntemplate <typename SIndex>\nvoid SparseFtrlOp<T>::DoRun() {\n  auto* var = Output(OUTPUT_VAR);\n  auto* n_z = Output(OUTPUT_N_Z);\n  auto& indices = Input(INDICES);\n  auto& grad = Input(GRAD);\n  CAFFE_ENFORCE_EQ(&Input(VAR), var, \"In place operation is required\");\n  CAFFE_ENFORCE_EQ(&Input(N_Z), n_z, \"In place operation is required\");\n  TIndex M = var->size();\n  TIndex N = var->dim(0);\n  TIndex block_size = M / N;\n  TIndex K = indices.size();\n  DCHECK_EQ(M * 2, n_z->size());\n  DCHECK_EQ(grad.size(), K * block_size);\n  T* w = var->template mutable_data<T>();\n  T* nz = n_z->template mutable_data<T>();\n  const SIndex* idxs = indices.template data<SIndex>();\n  const T* g = grad.template data<T>();\n\n  // TODO(cxj): use OMP when it is reliable\n  // #pragma omp parallel for\n  for (TIndex i = 0; i < K; ++i) {\n    SIndex idx = idxs[i];\n    DCHECK(0 <= idx && idx < N) << \"Index out of bounds: \" << idx\n                                << \", range 0 to \" << N;\n    if (block_size == 1) {\n      ftrl_compute(\n          w[idx],\n          nz[idx * 2],\n          nz[idx * 2 + 1],\n          g[i],\n          w[idx],\n          nz[idx * 2],\n          nz[idx * 2 + 1],\n          params_);\n    } else {\n      TIndex x = block_size * idx;\n      ftrl_update(\n          block_size,\n          w + x,\n          nz + x * 2,\n          g + i * block_size,\n          w + x,\n          nz + x * 2,\n          params_,\n          &context_);\n    }\n  }\n}\n\nnamespace {\nREGISTER_CPU_OPERATOR(Ftrl, FtrlOp<float, CPUContext>);\nOPERATOR_SCHEMA(Ftrl).NumInputs(3, 4).NumOutputs(2).AllowInplace({{0, 0},\n                                                                  {1, 1}});\nSHOULD_NOT_DO_GRADIENT(Ftrl);\n\nREGISTER_CPU_OPERATOR(SparseFtrl, SparseFtrlOp<float>);\nOPERATOR_SCHEMA(SparseFtrl)\n    .NumInputs(4, 5)\n    .NumOutputs(2)\n    .EnforceInplace({{0, 0}, {1, 1}});\nSHOULD_NOT_DO_GRADIENT(SparseFtrl);\n}\n\n}\n"
  },
  {
    "path": "caffe2/sgd/ftrl_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T>\nstruct FtrlParams {\n  explicit FtrlParams(OperatorBase* op)\n      : alphaInv(1.0 / op->GetSingleArgument<float>(\"alpha\", 0.005f)),\n        beta(op->GetSingleArgument<float>(\"beta\", 1.0f)),\n        lambda1(op->GetSingleArgument<float>(\"lambda1\", 0.001f)),\n        lambda2(op->GetSingleArgument<float>(\"lambda2\", 0.001f)) {}\n  T alphaInv;\n  T beta;\n  T lambda1;\n  T lambda2;\n};\n\n// TODO(dzhulgakov): implement GPU version if necessary\ntemplate <typename T, class Context>\nclass FtrlOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  FtrlOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws), params_(this) {\n    CAFFE_ENFORCE(\n        !HasArgument(\"alpha\") || ALPHA >= InputSize(),\n        \"Cannot specify alpha by both input and argument\");\n  }\n  bool RunOnDevice() override;\n\n protected:\n  FtrlParams<T> params_;\n  INPUT_TAGS(VAR, N_Z, GRAD, ALPHA);\n  OUTPUT_TAGS(OUTPUT_VAR, OUTPUT_N_Z);\n};\n\ntemplate <typename T>\nclass SparseFtrlOp final : public Operator<CPUContext> {\n public:\n  SparseFtrlOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws), params_(this) {\n    CAFFE_ENFORCE(\n        !HasArgument(\"alpha\") || ALPHA >= InputSize(),\n        \"Cannot specify alpha by both input and argument\");\n  }\n\n  bool RunOnDevice() override {\n    // run time learning rate override\n    if (ALPHA < InputSize()) {\n      CAFFE_ENFORCE_EQ(Input(ALPHA).size(), 1, \"alpha should be real-valued\");\n      params_.alphaInv = 1.0 / *(Input(ALPHA).template data<T>());\n    }\n    // Use run-time polymorphism\n    auto& indices = Input(INDICES);\n    if (indices.template IsType<int32_t>()) {\n      DoRun<int32_t>();\n    } else if (indices.template IsType<int64_t>()) {\n      DoRun<int64_t>();\n    } else {\n      LOG(FATAL) << \"Unsupported type of INDICES in SparseFtrlOp: \"\n                      << indices.meta().name();\n    }\n    return true;\n  }\n\n protected:\n  FtrlParams<T> params_;\n  INPUT_TAGS(VAR, N_Z, INDICES, GRAD, ALPHA);\n  OUTPUT_TAGS(OUTPUT_VAR, OUTPUT_N_Z);\n\n private:\n  template <typename SIndex>\n  void DoRun();\n};\n\n}\n"
  },
  {
    "path": "caffe2/sgd/iter_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/sgd/iter_op.h\"\n\nnamespace caffe2 {\n\nvoid MutexSerializer::Serialize(\n    const Blob& blob,\n    const string& name,\n    BlobSerializerBase::SerializationAcceptor acceptor) {\n  CAFFE_ENFORCE(blob.IsType<std::unique_ptr<std::mutex>>());\n  BlobProto blob_proto;\n  blob_proto.set_name(name);\n  blob_proto.set_type(\"std::unique_ptr<std::mutex>\");\n  blob_proto.set_content(\"\");\n  acceptor(name, blob_proto.SerializeAsString());\n}\n\nvoid MutexDeserializer::Deserialize(const BlobProto& /* unused */, Blob* blob) {\n  *blob->GetMutable<std::unique_ptr<std::mutex>>() =\n      caffe2::make_unique<std::mutex>();\n}\n\nREGISTER_CPU_OPERATOR(Iter, IterOp<CPUContext>);\nREGISTER_CPU_OPERATOR(AtomicIter, AtomicIterOp<CPUContext>);\n\nREGISTER_BLOB_SERIALIZER(\n    (TypeMeta::Id<std::unique_ptr<std::mutex>>()),\n    MutexSerializer);\nREGISTER_BLOB_DESERIALIZER(std::unique_ptr<std::mutex>, MutexDeserializer);\n\nOPERATOR_SCHEMA(Iter)\n    .NumInputs(0, 1)\n    .NumOutputs(1)\n    .EnforceInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nStores a singe integer, that gets incremented on each call to Run().\nUseful for tracking the iteration count during SGD, for example.\n)DOC\");\n\nOPERATOR_SCHEMA(AtomicIter)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .EnforceInplace({{1, 0}})\n    .SetDoc(R\"DOC(\nSimilar to Iter, but takes a mutex as the first input to make sure that\nupdates are carried out atomically. This can be used in e.g. Hogwild sgd\nalgorithms.\n)DOC\")\n    .Input(0, \"mutex\", \"The mutex used to do atomic increment.\")\n    .Input(1, \"iter\", \"The iter counter as an int64_t TensorCPU.\");\n\nNO_GRADIENT(Iter);\nNO_GRADIENT(AtomicIter);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/sgd/iter_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_SGD_ITER_OP_H_\n#define CAFFE2_SGD_ITER_OP_H_\n\n#include <limits>\n#include <mutex>\n\n#include \"caffe2/core/blob_serialization.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ninline void IncrementIter(TensorCPU* output) {\n  CAFFE_ENFORCE_EQ(\n      output->size(),\n      1,\n      \"The output of IterOp exists, but not of the right size.\");\n  int64_t* iter = output->template mutable_data<int64_t>();\n  CAFFE_ENFORCE(*iter >= 0, \"Previous iteration number is negative.\");\n  CAFFE_ENFORCE(\n      *iter < std::numeric_limits<int64_t>::max(), \"Overflow will happen!\");\n  (*iter)++;\n}\n\n// IterOp runs an iteration counter. I cannot think of a case where we would\n// need to access the iter variable on device, so this will always produce a\n// tensor on the CPU side. If the blob already exists and is a tensor<int64_t>\n// object, we will simply increment it (this emulates the case when we want to\n// resume training). Otherwise we will have the iter starting with 0.\ntemplate <class Context>\nclass IterOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  IterOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    if (InputSize() == 0) {\n      if (!OperatorBase::OutputIsType<TensorCPU>(0)) {\n        // This is the first run; set the iter to start with 0.\n        LOG(ERROR) << \"You are using an old definition of IterOp that will \"\n                      \"be deprecated soon. More specifically, IterOp now \"\n                      \"requires an explicit in-place input and output.\";\n\n        auto* output = OperatorBase::Output<TensorCPU>(0);\n        VLOG(1) << \"Initializing iter counter.\";\n        output->Resize(1);\n        output->template mutable_data<int64_t>()[0] = 0;\n      }\n    }\n    IncrementIter(OperatorBase::Output<TensorCPU>(0));\n    return true;\n  }\n};\n\ntemplate <class Context>\nclass AtomicIterOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  AtomicIterOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n\n  bool RunOnDevice() override {\n    auto& mutex = OperatorBase::Input<std::unique_ptr<std::mutex>>(0);\n    std::lock_guard<std::mutex> lg(*mutex);\n    IncrementIter(OperatorBase::Output<TensorCPU>(0));\n    return true;\n  }\n};\n\nclass MutexSerializer : public BlobSerializerBase {\n public:\n  /**\n   * Serializes a std::unique_ptr<std::mutex>. Note that this blob has to\n   * contain std::unique_ptr<std::mutex>, otherwise this function produces a\n   * fatal error.\n   */\n  void Serialize(\n      const Blob& blob,\n      const string& name,\n      BlobSerializerBase::SerializationAcceptor acceptor) override;\n};\n\nclass MutexDeserializer : public BlobDeserializerBase {\n public:\n  void Deserialize(const BlobProto& proto, Blob* blob) override;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_SGD_ITER_OP_H_\n"
  },
  {
    "path": "caffe2/sgd/iter_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/sgd/iter_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(Iter, IterOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(AtomicIter, AtomicIterOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/sgd/lars_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/sgd/lars_op.h\"\n#include <math.h>\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nvoid LarsOp<float, CPUContext>::Compute(\n    TIndex N,\n    const float* X_data,\n    const float* dX_data,\n    float offset,\n    float* lr_rescale_data) {\n  *lr_rescale_data = 1.0;\n\n  float X_norm =\n      sqrtf((ConstEigenVectorMap<float>(X_data, N).array()).square().sum());\n\n  if (X_norm > 0) {\n    float dX_norm =\n        sqrtf((ConstEigenVectorMap<float>(dX_data, N).array()).square().sum());\n    *lr_rescale_data /= (dX_norm / X_norm + offset);\n  }\n}\n\nREGISTER_CPU_OPERATOR(Lars, LarsOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(Lars)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nImplement Layer-wise Adaptive Rate Scaling (LARS) as in\nhttps://arxiv.org/abs/1708.03888. Without weight decay, given a global\nlearning rate lr, parameter tensor X and its gradient dX, the local learning\nrate for X will be\n\n    local_lr = lr * norm(X) / ( norm(dX) + offset * norm(X) )\n\n             = lr  / ( norm(dX) / norm(X) + offset ),\n\nwhere offset is a preset hyper-parameter to avoid numerical issue.\nIn this implementation, we uses l2 norm and output the rescaling factor\n\n    1 / ( norm(dX) / norm(X) + offset ).\n\n)DOC\")\n    .Input(0, \"X\", \"Parameter tensor\")\n    .Input(1, \"dX\", \"Gradient tensor\")\n    .Output(0, \"lr_rescale\", \"Local learning rate rescaling factor\")\n    .Arg(\"offset\", \"rescaling offset parameter\");\n\nSHOULD_NOT_DO_GRADIENT(Lars);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/sgd/lars_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_OPERATORS_LARS_OP_H_\n#define CAFFE2_OPERATORS_LARS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass LarsOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  LarsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        offset_(OperatorBase::GetSingleArgument<float>(\"offset\", 0.5)) {}\n\n  bool RunOnDevice() override {\n    auto& X = Input(0);\n    auto& dX = Input(1);\n    CAFFE_ENFORCE(\n        dX.size() == X.size(), \"Gradient size doesn't match parameter size.\");\n    CAFFE_ENFORCE_GE(offset_, 0);\n\n    auto* lr_rescale = Output(0);\n    lr_rescale->Resize(vector<TIndex>{1});\n\n    Compute(\n        dX.size(),\n        X.template data<T>(),\n        dX.template data<T>(),\n        offset_,\n        lr_rescale->template mutable_data<T>());\n\n    return true;\n  }\n\n private:\n  void Compute(\n      TIndex N,\n      const T* X_data,\n      const T* dX_data,\n      T offset,\n      T* lr_rescale_data);\n\n  T offset_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_OPERATORS_LARS_OP_H_\n"
  },
  {
    "path": "caffe2/sgd/lars_op_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/operators/operator_fallback_gpu.h\"\n#include \"caffe2/sgd/lars_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(Lars, GPUFallbackOp<LarsOp<float, CPUContext>>);\n}\n"
  },
  {
    "path": "caffe2/sgd/learning_rate_functors.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_SGD_LEARNING_RATE_FUNCTORS_H_\n#define CAFFE2_SGD_LEARNING_RATE_FUNCTORS_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\n// LearningRateFunctor is a functor that when fed with an iter number, produces\n// the learning rate for the corresponding iteration.\ntemplate <typename T>\nclass LearningRateFunctor {\n public:\n  virtual ~LearningRateFunctor() {}\n  virtual T operator()(const int64_t iter) const = 0;\n};\n\n// Fixed: not changing the learning rate at all.\ntemplate <typename T>\nclass FixedLearningRate : public LearningRateFunctor<T> {\n public:\n  T operator()(const int64_t /*iter*/) const override {\n    return 1.;\n  }\n};\n\n// Alter: alternatate learning rate with active_period and inactive_period.\n// update for for a duration of active_period and then stop for a duration of\n// inactive_period if active_first, and vice versa\ntemplate <typename T>\nclass AlternateLearningRate : public LearningRateFunctor<T> {\n public:\n  AlternateLearningRate(\n      const int64_t active_period,\n      const int64_t inactive_period,\n      const bool active_first)\n      : active_period_(active_period),\n        inactive_period_(inactive_period),\n        active_first_(active_first) {}\n  T operator()(const int64_t iter) const override {\n    if (iter % (active_period_ + inactive_period_) <\n        (active_first_ ? active_period_ : inactive_period_)) {\n      return active_first_ ? 1. : 0.;\n    } else {\n      return active_first_ ? 0. : 1.;\n    };\n  };\n\n  int64_t active_period_;\n  int64_t inactive_period_;\n  bool active_first_;\n};\n\n// Step: return gamma ^ (floor(iter / step))\ntemplate <typename T>\nclass StepLearningRate : public LearningRateFunctor<T> {\n public:\n  StepLearningRate(const int stepsize, const T gamma)\n      : stepsize_(stepsize), gamma_(gamma) {}\n  T operator()(const int64_t iter) const override {\n    return std::pow(gamma_, static_cast<T>(iter / stepsize_));\n  }\n\n  int stepsize_;\n  T gamma_;\n};\n\n// Exp: return gamma ^ iter\ntemplate <typename T>\nclass ExpLearningRate : public LearningRateFunctor<T> {\n public:\n  explicit ExpLearningRate(const T gamma) : gamma_(gamma) {}\n  T operator()(const int64_t iter) const override {\n    return std::pow(gamma_, static_cast<T>(iter));\n  }\n\n  T gamma_;\n};\n\n// Inv: return (1 + gamma * iter) ^ (-power)\ntemplate <typename T>\nclass InvLearningRate : public LearningRateFunctor<T> {\n public:\n  InvLearningRate(const T gamma, const T power)\n      : gamma_(gamma), power_(power) {}\n  T operator()(const int64_t iter) const override {\n    return std::pow(T(1) + gamma_ * iter, -power_);\n  }\n  T gamma_;\n  T power_;\n};\n\n// Poly: return (1 - iter/max_iter) ^ (power)\ntemplate <typename T>\nclass PolyLearningRate : public LearningRateFunctor<T> {\n public:\n  PolyLearningRate(const T power, const int64_t max_iter)\n      : power_(power), max_iter_(max_iter) {}\n  T operator()(const int64_t iter) const override {\n    return std::pow(1 - T(iter) / T(max_iter_), power_);\n  }\n  T power_;\n  uint64_t max_iter_;\n};\n\n// LinearWarmup: return max(iter/num_iter, 1)\ntemplate <typename T>\nclass LinearWarmupLearningRate : public LearningRateFunctor<T> {\n public:\n  LinearWarmupLearningRate(const T start_multiplier, const int64_t num_iter)\n      : start_multiplier_(start_multiplier), num_iter_(num_iter) {}\n  T operator()(const int64_t iter) const override {\n    if (iter >= num_iter_) {\n      return 1.;\n    }\n    return start_multiplier_ + (1. - start_multiplier_) * T(iter) / T(num_iter_);\n  }\n  T start_multiplier_;\n  uint64_t num_iter_;\n};\n\n// ConstantWarmup: return scale when iter < num_iter, and 1 otherwise\ntemplate <typename T>\nclass ConstantWarmupLearningRate : public LearningRateFunctor<T> {\n public:\n  ConstantWarmupLearningRate(const T multiplier, const int64_t num_iter)\n      : multiplier_(multiplier), num_iter_(num_iter) {}\n  T operator()(const int64_t iter) const override {\n    if (iter >= num_iter_) {\n      return 1.;\n    }\n    return T(multiplier_);\n  }\n  T multiplier_;\n  uint64_t num_iter_;\n};\n\n// hill: the learning rate changes according to following 3 stages\n// 1) linear warmup (increasing) at first num_iter steps from start_multiplier\n// 2) inverse shrink (decreasing) afterwards (gamma, power)\n// 3) lower bounded by end_multiplier\ntemplate <typename T>\nclass HillLearningRate : public LearningRateFunctor<T> {\n public:\n  HillLearningRate(\n      const int64_t num_iter,\n      const T start_multiplier,\n      const T gamma,\n      const T power,\n      const T end_multiplier)\n      : linear_warmup_lr_(start_multiplier, num_iter),\n        inv_lr_(gamma, power),\n        num_iter_(num_iter),\n        end_multiplier_(end_multiplier) {}\n  T operator()(const int64_t iter) const override {\n    if (iter < num_iter_) {\n      return linear_warmup_lr_(iter);\n    } else {\n      return std::max(end_multiplier_, inv_lr_(iter - num_iter_));\n    }\n  }\n  LinearWarmupLearningRate<T> linear_warmup_lr_;\n  InvLearningRate<T> inv_lr_;\n  int64_t num_iter_;\n  T end_multiplier_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_SGD_LEARNING_RATE_FUNCTORS_H_\n"
  },
  {
    "path": "caffe2/sgd/learning_rate_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/sgd/learning_rate_op.h\"\n\nnamespace caffe2 {\nREGISTER_CPU_OPERATOR(LearningRate, LearningRateOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(LearningRate)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nLearning rate is a decreasing function of time. With low learning rates the\nimprovements will be linear. With high learning rates they will start to look\nmore exponential. Learning rate is controlled by the following arguments:\n\n\nRequired:\n  `iterations`\n  `base_lr`: base learning rate\n  `policy`: this controls how the learning rate is applied, options are:\n    `fixed`\n    `step`: uses `stepsize`, `gamma`\n    `exp`: uses `gamma`\n    `inv`: uses `gamma`, `power`\n    `linearWarmup`: uses `start_multiplier`, `num_iter`\n    `constantWarmup`: uses `multiplier`, `num_iter`\n    `alter`: uses  `active_first`, `active_period`, `inactive_period`\n    `hill`: uses those in both `linearWarmup` and `inv`, plus `end_multiplier`\n\n\nOptional:\n  `stepsize`: defaults to 0\n  `gamma`: defaults to 0\n  `power`: defaults to 0\n  `num_iter`: defaults to 0\n  `start_multiplier`: defaults to 0\n  `multiplier`: defaults to 0.5\n\n\nUsage:\n  train_net.LearningRate(*iterations*, \"*label*\", base_lr=*float*,\n                         policy=\"policy_name\", stepsize=*int*, gamma=*float*)\n\n\nExample usage:\n  train_net.LearningRate(200, \"LR\", base_lr=-0.1,\n                         policy=\"step\", stepsize=20, gamma=0.9)\n)DOC\")\n    .Arg(\"base_lr\", \"(float, required) base learning rate\")\n    .Arg(\"policy\", \"(float, default 1.0) strategy for gamma enforcement\")\n    .Arg(\"power\", \"(float, default 1.0) used only for inv policy type\")\n    .Arg(\"gamma\", \"(float, default 1.0) momentum of change\")\n    .Arg(\"stepsize\", \"(float, default 1.0) sampling rate on iterations\")\n    .Arg(\"active_first\", \"(boolean, default True) in alter policy\")\n    .Arg(\"active_period\", \"(int64_t, required) in alter policy\")\n    .Arg(\"inactive_period\", \"(int64_t, required) in alter policy\")\n    .Arg(\n        \"max_iter\",\n        \"(int, default -1) maximum iterations in this training run\")\n    .Arg(\n        \"num_iter\",\n        \"(int, default 0) number of iterations over which to warmup lr\")\n    .Arg(\n        \"start_multiplier\",\n        \"(float, default 0) starting multiplier for learning rate\")\n    .Arg(\n        \"end_multiplier\",\n        \"(float, default 0) end multiplier for learning rate\")\n    .Arg(\n        \"multiplier\",\n        \"(float, default 0.5) constant multiplier for learning rate\")\n    .Input(0, \"input\", \"description needed\")\n    .Output(0, \"output\", \"description needed\")\n    .DeviceInferenceFunction([](const OperatorDef& def) {\n      return std::make_pair(\n          std::vector<DeviceOption>{DeviceOption()},\n          std::vector<DeviceOption>{def.device_option()});\n    });\n\nNO_GRADIENT(LearningRate);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/sgd/learning_rate_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_SGD_LEARNING_RATE_OP_H_\n#define CAFFE2_SGD_LEARNING_RATE_OP_H_\n\n#include <cfloat>\n#include <cmath>\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/sgd/learning_rate_functors.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass LearningRateOp final : public Operator<Context> {\n public:\n  LearningRateOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        functor_(nullptr),\n        base_lr_(OperatorBase::template GetSingleArgument<float>(\n            \"base_lr\",\n            FLT_MAX)) {\n    CAFFE_ENFORCE_NE(base_lr_, FLT_MAX, \"Base learning rate must be set.\");\n    const string policy = OperatorBase::GetSingleArgument<string>(\"policy\", \"\");\n    CAFFE_ENFORCE(policy.size(), \"Must specify a learning rate policy.\");\n    if (policy == \"fixed\") {\n      functor_.reset(new FixedLearningRate<T>());\n    } else if (policy == \"alter\") {\n      bool active_first =\n          OperatorBase::template GetSingleArgument<bool>(\"active_first\", true);\n      int64_t active_period = OperatorBase::template GetSingleArgument<int64_t>(\n          \"active_period\", -1);\n      int64_t inactive_period =\n          OperatorBase::template GetSingleArgument<int64_t>(\n              \"inactive_period\", -1);\n      DCHECK_GE(active_period, 0);\n      DCHECK_GE(inactive_period, 0);\n      functor_.reset(new AlternateLearningRate<T>(\n          active_period, inactive_period, active_first));\n    } else if (policy == \"hill\") {\n      int64_t num_iter =\n          OperatorBase::template GetSingleArgument<int>(\"num_iter\", 0);\n      DCHECK_GT(num_iter, 0);\n      T start_multiplier = OperatorBase::template GetSingleArgument<float>(\n          \"start_multiplier\", 0.);\n      DCHECK_GE(start_multiplier, 0); // start_multiplier in range [0, 1]\n      DCHECK_LE(start_multiplier, 1);\n      T gamma = OperatorBase::template GetSingleArgument<float>(\"gamma\", 0);\n      DCHECK_GT(gamma, 0);\n      T power = OperatorBase::template GetSingleArgument<float>(\"power\", 0);\n      DCHECK_GT(power, 0);\n      T end_multiplier =\n          OperatorBase::template GetSingleArgument<float>(\"end_multiplier\", 0);\n      DCHECK_GE(end_multiplier, 0); // end_multiplier in range [0, 1]\n      DCHECK_LE(end_multiplier, 1);\n      functor_.reset(new HillLearningRate<T>(\n          num_iter, start_multiplier, gamma, power, end_multiplier));\n    } else if (policy == \"step\") {\n      int stepsize =\n          OperatorBase::template GetSingleArgument<int>(\"stepsize\", 0);\n      T gamma = OperatorBase::template GetSingleArgument<float>(\"gamma\", 0);\n      DCHECK_GT(stepsize, 0);\n      DCHECK_GT(gamma, 0);\n      functor_.reset(new StepLearningRate<T>(stepsize, gamma));\n    } else if (policy == \"exp\") {\n      T gamma = OperatorBase::template GetSingleArgument<float>(\"gamma\", 0);\n      DCHECK_GT(gamma, 0);\n      functor_.reset(new ExpLearningRate<T>(gamma));\n    } else if (policy == \"inv\") {\n      T gamma = OperatorBase::template GetSingleArgument<float>(\"gamma\", 0);\n      T power = OperatorBase::template GetSingleArgument<float>(\"power\", 0);\n      DCHECK_GT(gamma, 0);\n      DCHECK_GT(power, 0);\n      functor_.reset(new InvLearningRate<T>(gamma, power));\n    } else if (policy == \"poly\") {\n      int max_iter = OperatorBase::template GetSingleArgument<int>(\"max_iter\", -1);\n      T power = OperatorBase::template GetSingleArgument<float>(\"power\", 0);\n      DCHECK_GT(power, 0);\n      functor_.reset(new PolyLearningRate<T>(power, max_iter));\n    } else if (policy == \"linearWarmup\") {\n      T start_multiplier = OperatorBase::template GetSingleArgument<float>(\n          \"start_multiplier\", 0.);\n      int num_iter =\n          OperatorBase::template GetSingleArgument<int>(\"num_iter\", 0);\n      DCHECK_GT(start_multiplier, 0);\n      functor_.reset(\n          new LinearWarmupLearningRate<T>(start_multiplier, num_iter));\n    } else if (policy == \"constantWarmup\") {\n      T multiplier =\n          OperatorBase::template GetSingleArgument<float>(\"multiplier\", 0.5);\n      int num_iter =\n          OperatorBase::template GetSingleArgument<int>(\"num_iter\", 0);\n      DCHECK_GT(multiplier, 0);\n      functor_.reset(new ConstantWarmupLearningRate<T>(multiplier, num_iter));\n    } else {\n      LOG(FATAL) << \"Unknown learning rate policy: \" << policy;\n    }\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    int64_t iter =\n        OperatorBase::Input<TensorCPU>(0).template data<int64_t>()[0];\n    T learning_rate = base_lr_ * (*functor_)(iter);\n    // Write to output.\n    auto* output = Output(0);\n    output->Resize(vector<TIndex>());\n    context_.template Copy<T, CPUContext, Context>(\n        1, &learning_rate, Output(0)->template mutable_data<T>());\n    return true;\n  }\n\n private:\n  unique_ptr<LearningRateFunctor<T> > functor_;\n  T base_lr_;\n\n};\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_SGD_LEARNING_RATE_OP_H_\n"
  },
  {
    "path": "caffe2/sgd/learning_rate_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/sgd/learning_rate_op.h\"\n\nnamespace caffe2 {\nREGISTER_CUDA_OPERATOR(LearningRate, LearningRateOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/sgd/momentum_sgd_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"momentum_sgd_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(MomentumSGD, MomentumSGDOp<float, CPUContext>);\nOPERATOR_SCHEMA(MomentumSGD)\n    .NumInputs(3)\n    .NumOutputs(2)\n    .AllowInplace({{0, 0}, {1, 1}})\n    .TensorInferenceFunction(\n        [](const OperatorDef& /* unused */, const vector<TensorShape>& in) {\n          vector<TensorShape> out(2);\n          out[0] = in[0];\n          out[1] = in[1];\n          return out;\n        })\n    .SetDoc(R\"DOC(\n\nComputes a momentum SGD update for an input gradient and momentum\nparameters. Concretely, given inputs (grad, m, lr) and parameters\n(momentum, nesterov), computes:\n\n    if not nesterov:\n        adjusted_gradient = lr * grad + momentum * m\n        return (adjusted_gradient, adjusted_gradient)\n    else:\n        m_new = momentum * m + lr * grad\n        return ((1 + momentum) * m_new - momentum * m, m_new)\n\nOutput is (grad, momentum)\n\nNote the difference to MomemtumSGDUpdate, which actually performs the\nparameter update (and is thus faster).\n)DOC\");\nSHOULD_NOT_DO_GRADIENT(MomentumSGD);\n\nREGISTER_CPU_OPERATOR(\n    MomentumSGDUpdate,\n    MomentumSGDUpdateOp<float, CPUContext>);\nOPERATOR_SCHEMA(MomentumSGDUpdate)\n    .NumInputs(4)\n    .NumOutputs(3)\n    .AllowInplace({{0, 0}, {1, 1}, {3, 2}})\n    .TensorInferenceFunction(\n        [](const OperatorDef& /* unused */, const vector<TensorShape>& in) {\n          vector<TensorShape> out(3);\n          out[0] = in[0];\n          out[1] = in[1];\n          out[2] = in[3];\n          return out;\n        })\n    .SetDoc(R\"DOC(\n\nPerforms a momentum SGD update for an input gradient and momentum\nparameters. Concretely, given inputs (grad, m, lr, param) and arguments\n(momentum, nesterov), computes:\n\n    if not nesterov:\n        adjusted_gradient = lr * grad + momentum * m\n        param = param - adjusted_gradient\n        return (adjusted_gradient, adjusted_gradient, param)\n    else:\n        m_new = momentum * m + lr * grad\n        param = param - ((1 + momentum) * m_new - momentum * m),\n        return ((1 + momentum) * m_new - momentum * m, m_new, param)\n\nOutput is (grad, momentum, parameter).\n\nNote the difference to MomentumSGD, which returns a new gradient\nbut does not perform the parameter update.\n\n)DOC\");\nSHOULD_NOT_DO_GRADIENT(MomentumSGDUpdate);\n\nREGISTER_CPU_OPERATOR(\n    SparseMomentumSGDUpdate,\n    SparseMomentumSGDUpdateOp<float, CPUContext>);\nOPERATOR_SCHEMA(SparseMomentumSGDUpdate)\n    .NumInputs(5)\n    .NumOutputs(3)\n    .AllowInplace({{0, 0}})\n    .EnforceInplace({{1, 1}, {3, 2}})\n    .TensorInferenceFunction(\n        [](const OperatorDef& /* unused */, const vector<TensorShape>& in) {\n          vector<TensorShape> out(3);\n          out[0] = in[0];\n          out[1] = in[1];\n          out[2] = in[3];\n          return out;\n        })\n    .SetDoc(R\"DOC(\n\nPerforms a momentum SGD update analogous to MomentumSGDUpdate, but using a\nGradientSlice and indices into the full param and momentum tables. Both param\nand momentum should be in-place (corresponding inputs and outputs should be the\nsame blobs).\n\n\n\n)DOC\")\n    .Input(0, \"grad\", \"GradientSlice with gradients for updated indices.\")\n    .Input(1, \"moment\", \"Momentum blob, same shape as param.\")\n    .Input(2, \"lr\", \"Learning rate.\")\n    .Input(3, \"param\", \"Full parameter blob.\")\n    .Input(\n        4,\n        \"indices\",\n        \"Indices (in first dimension of param) where updates are performed.\")\n    .Output(0, \"output_grad\", \"Adjusted gradient.\")\n    .Output(1, \"output_moment\", \"Updated momentum.\")\n    .Output(2, \"output_param\", \"Updated parameter\")\n    .Arg(\"momentum\", \"Momentum hyperparameter.\")\n    .Arg(\"nesterov\", \"(boolean) Whether to use Nesterov Accelerated Gradient.\");\nSHOULD_NOT_DO_GRADIENT(SparseMomentumSGDUpdate);\n}\n"
  },
  {
    "path": "caffe2/sgd/momentum_sgd_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename Context>\nvoid momentum_sgd_update(\n    const int N,\n    const float* g,\n    const float* m,\n    float* ng,\n    float* nm,\n    const float* lr,\n    const float momentum,\n    const bool nesterov,\n    float* param,\n    Context* /*context*/) {\n  const float LR = lr[0];\n  for (auto i = 0; i < N; ++i) {\n    if (!nesterov) {\n      const float adjusted_gradient = LR * g[i] + momentum * m[i];\n      nm[i] = adjusted_gradient;\n      ng[i] = adjusted_gradient;\n    } else {\n      const float mi = m[i];\n      const float mi_new = momentum * mi + LR * g[i];\n      nm[i] = mi_new;\n      ng[i] = (1 + momentum) * mi_new - momentum * mi;\n    }\n\n    if (param) {\n      param[i] -= ng[i];\n    }\n  }\n}\n\ntemplate <typename T, class Context>\nclass MomentumSGDOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  MomentumSGDOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        momentum_(OperatorBase::GetSingleArgument<T>(\"momentum\", 0.0)),\n        nesterov_(OperatorBase::GetSingleArgument<int>(\"nesterov\", 0)) {}\n\n  bool RunOnDevice() override {\n    // Iter live on the CPU\n    CAFFE_ENFORCE(OperatorBase::InputIsType<Tensor<Context>>(GRAD));\n    CAFFE_ENFORCE(OperatorBase::InputIsType<Tensor<Context>>(MOMENTUM));\n    CAFFE_ENFORCE(Input(LR).size() == 1);\n    CAFFE_ENFORCE(Input(GRAD).size() == Input(MOMENTUM).size());\n    Output(OUTPUT_GRAD)->ResizeLike(Input(GRAD));\n    Output(OUTPUT_MOMENTUM)->ResizeLike(Input(MOMENTUM));\n\n    momentum_sgd_update<Context>(\n        Input(GRAD).size(),\n        Input(GRAD).template data<T>(),\n        Input(MOMENTUM).template data<T>(),\n        Output(OUTPUT_GRAD)->template mutable_data<T>(),\n        Output(OUTPUT_MOMENTUM)->template mutable_data<T>(),\n        Input(LR).template data<T>(),\n        momentum_,\n        nesterov_,\n        NULL,\n        &context_);\n    return true;\n  }\n\n protected:\n  T momentum_{0.9};\n  bool nesterov_;\n  INPUT_TAGS(GRAD, MOMENTUM, LR);\n  OUTPUT_TAGS(OUTPUT_GRAD, OUTPUT_MOMENTUM);\n};\n\ntemplate <typename T, class Context>\nclass MomentumSGDUpdateOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  MomentumSGDUpdateOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        momentum_(OperatorBase::GetSingleArgument<T>(\"momentum\", 0.0)),\n        nesterov_(OperatorBase::GetSingleArgument<int>(\"nesterov\", 0)) {}\n\n  bool RunOnDevice() override {\n    // Iter live on the CPU\n    CAFFE_ENFORCE(OperatorBase::InputIsType<Tensor<Context>>(GRAD));\n    CAFFE_ENFORCE(OperatorBase::InputIsType<Tensor<Context>>(MOMENTUM));\n    CAFFE_ENFORCE_EQ(Input(LR).size(), 1);\n    CAFFE_ENFORCE_EQ(Input(GRAD).size(), Input(MOMENTUM).size());\n    Output(OUTPUT_GRAD)->ResizeLike(Input(GRAD));\n    Output(OUTPUT_MOMENTUM)->ResizeLike(Input(MOMENTUM));\n\n    momentum_sgd_update<Context>(\n        Input(GRAD).size(),\n        Input(GRAD).template data<T>(),\n        Input(MOMENTUM).template data<T>(),\n        Output(OUTPUT_GRAD)->template mutable_data<T>(),\n        Output(OUTPUT_MOMENTUM)->template mutable_data<T>(),\n        Input(LR).template data<T>(),\n        momentum_,\n        nesterov_,\n        Output(OUTPUT_PARAM)->template mutable_data<T>(),\n        &context_);\n    return true;\n  }\n\n protected:\n  T momentum_{0.9};\n  bool nesterov_;\n  INPUT_TAGS(GRAD, MOMENTUM, LR, PARAM);\n  OUTPUT_TAGS(OUTPUT_GRAD, OUTPUT_MOMENTUM, OUTPUT_PARAM);\n};\n\ntemplate <typename T, class Context>\nclass SparseMomentumSGDUpdateOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  SparseMomentumSGDUpdateOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        momentum_(OperatorBase::GetSingleArgument<T>(\"momentum\", 0.0)),\n        nesterov_(OperatorBase::GetSingleArgument<int>(\"nesterov\", 0)) {}\n\n  bool RunOnDevice() override {\n    // Resize [potentially] out-of-place blobs\n    Output(OUTPUT_GRAD)->ResizeLike(Input(GRAD));\n\n    // Enforce shapes\n    CAFFE_ENFORCE_EQ(Input(LR).size(), 1);\n    CAFFE_ENFORCE_EQ(Input(PARAM).size(), Input(MOMENTUM).size());\n    CAFFE_ENFORCE_EQ(Input(PARAM).size_from_dim(1),\n        Input(GRAD).size_from_dim(Input(INDICES).ndim()));\n\n    return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(\n        this, Input(INDICES));\n  }\n\n  template <typename SIndex>\n  bool DoRunWithType() {\n    auto block_size = Input(PARAM).size() / Input(PARAM).dim(0);\n    auto n = Input(GRAD).size() / block_size;\n\n    const auto* gradIn = Input(GRAD).template data<T>();\n    const auto* momentumIn = Input(MOMENTUM).template data<T>();\n    const auto* lr = Input(LR).template data<T>();\n    const auto* paramIn = Input(PARAM).template data<T>();\n    const auto* indices = Input(INDICES).template data<SIndex>();\n\n    auto* gradOut = Output(OUTPUT_GRAD)->template mutable_data<T>();\n    auto* momentumOut = Output(OUTPUT_MOMENTUM)->template mutable_data<T>();\n    auto* paramOut = Output(OUTPUT_PARAM)->template mutable_data<T>();\n\n    for (auto i = 0; i < n; ++i) {\n      auto idx = indices[i];\n      auto offsetI = i * block_size;\n      auto offsetIdx = idx * block_size;\n\n      CAFFE_ENFORCE(offsetIdx + block_size <= Input(PARAM).size());\n      CAFFE_ENFORCE(offsetI + block_size <= Input(GRAD).size());\n\n      momentum_sgd_update<Context>(\n          block_size,\n          gradIn + offsetI,\n          momentumIn + offsetIdx,\n          gradOut + offsetI,\n          momentumOut + offsetIdx,\n          lr,\n          momentum_,\n          nesterov_,\n          paramOut + offsetIdx,\n          &context_);\n    }\n    return true;\n  }\n\n protected:\n  T momentum_;\n  bool nesterov_;\n  INPUT_TAGS(GRAD, MOMENTUM, LR, PARAM, INDICES);\n  OUTPUT_TAGS(OUTPUT_GRAD, OUTPUT_MOMENTUM, OUTPUT_PARAM);\n};\n}\n"
  },
  {
    "path": "caffe2/sgd/momentum_sgd_op_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"momentum_sgd_op.h\"\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\n__global__ void MomentumSGDKernel(\n    const int N,\n    const float* g,\n    const float* m,\n    float* ng,\n    float* nm,\n    const float* lr,\n    const float momentum,\n    const bool nesterov,\n    float* param) {\n  const float LR = lr[0];\n  if (!nesterov) {\n    CUDA_1D_KERNEL_LOOP(i, N) {\n      const float adjusted_gradient =  LR * g[i] + momentum * m[i];\n      nm[i] = adjusted_gradient;\n      ng[i] = adjusted_gradient;\n      if (param) {\n        param[i] -= adjusted_gradient;\n      }\n    }\n  } else {\n    CUDA_1D_KERNEL_LOOP(i, N) {\n      const float mi = m[i];\n      const float mi_new = momentum * mi + LR * g[i];\n      nm[i] = mi_new;\n      ng[i] = (1 + momentum) * mi_new - momentum * mi;\n      if (param) {\n        param[i] -= ng[i];\n      }\n    }\n  }\n}\n\ntemplate <>\nvoid momentum_sgd_update<CUDAContext>(\n    const int N,\n    const float* g,\n    const float* m,\n    float* ng,\n    float* nm,\n    const float* lr,\n    const float momentum,\n    const bool nesterov,\n    float* param,\n    CUDAContext* context) {\n  MomentumSGDKernel<<<\n      CAFFE_GET_BLOCKS(N),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(\n      N, g, m, ng, nm, lr, momentum, nesterov, param);\n}\n\n\ntemplate <typename SIndex>\n__global__ void SparseMomentumSGDKernel(\n    const size_t N,\n    const size_t sz,\n    const float momentum,\n    const bool nesterov,\n    float *param,\n    float *param_mom,\n    const SIndex *indices,\n    const float *gradIn,\n    float *gradOut,\n    const float *lr)\n{\n  const float LR = lr[0];\n  CUDA_1D_KERNEL_LOOP(i, N)\n  {\n    const size_t gradIdx = i;\n    const SIndex index = indices[i / sz];\n    const size_t paramIdx = index * sz + (i % sz);\n\n    if (!nesterov)\n    {\n      const float adjusted_gradient = LR * gradIn[gradIdx] +\n          momentum * param_mom[paramIdx];\n      gradOut[gradIdx] = adjusted_gradient;\n      param_mom[paramIdx] = adjusted_gradient;\n      param[paramIdx] -= adjusted_gradient;\n    } else {\n      const float mom_old = param_mom[paramIdx];\n      const float mom_new = LR * gradIn[gradIdx] + momentum * mom_old;\n      param_mom[paramIdx] = mom_new;\n      const float adjusted_gradient = (1 + momentum) * mom_new -\n          momentum * mom_old;\n      gradOut[gradIdx] = adjusted_gradient;\n      param[paramIdx] -= adjusted_gradient;\n    }\n  }\n}\n\n\n// Specialization of DoRunWithType for CUDA\ntemplate <>\ntemplate <typename SIndex>\nbool SparseMomentumSGDUpdateOp<float, CUDAContext>::DoRunWithType() {\n  auto N = Input(GRAD).size();\n  auto grad_slice_sz = Input(GRAD).size_from_dim(Input(INDICES).ndim());\n\n  SparseMomentumSGDKernel<SIndex><<<\n    CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS, 0,\n    context_.cuda_stream()>>>(\n        N, grad_slice_sz,\n        momentum_, nesterov_,\n        Output(OUTPUT_PARAM)->template mutable_data<float>(),\n        Output(OUTPUT_MOMENTUM)->template mutable_data<float>(),\n        Input(INDICES).template data<SIndex>(),\n        Input(GRAD).template data<float>(),\n        Output(OUTPUT_GRAD)->template mutable_data<float>(),\n        Input(LR).template data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(MomentumSGD, MomentumSGDOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(MomentumSGDUpdate, MomentumSGDUpdateOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(SparseMomentumSGDUpdate, SparseMomentumSGDUpdateOp<float, CUDAContext>);\n\n}\n"
  },
  {
    "path": "caffe2/sgd/rmsprop_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"rmsprop_op.h\"\n\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <>\nvoid rmsprop_update<CPUContext>(\n    int N,\n    const float* g,\n    const float* ms,\n    const float* mom,\n    float* ng,\n    float* nms,\n    float* nmom,\n    float decay,\n    float momentum,\n    float epsilon,\n    const float* lr,\n    CPUContext* /*context*/) {\n  ConstEigenVectorArrayMap<float> gVec(g, N);\n  ConstEigenVectorArrayMap<float> msVec(ms, N);\n  ConstEigenVectorArrayMap<float> momVec(mom, N);\n  // Update new mean square estimate\n  EigenVectorArrayMap<float> nmsVec(nms, N);\n  nmsVec = msVec + (1.0f - decay) * (gVec * gVec - msVec);\n  // Update momentum estimate\n  EigenVectorArrayMap<float> nmomVec(nmom, N);\n  nmomVec = momVec * momentum + lr[0] * gVec / (epsilon + nmsVec).sqrt();\n  // New gradient is the momentum\n  EigenVectorArrayMap<float>(ng, N) = nmomVec;\n}\n\nREGISTER_CPU_OPERATOR(RmsProp, RmsPropOp<float, CPUContext>);\nOPERATOR_SCHEMA(RmsProp)\n    .NumInputs(4)\n    .NumOutputs(3)\n    .AllowInplace({{0, 0}, {1, 1}, {2, 2}})\n    .SetDoc(R\"DOC(\nComputes the RMSProp update\n(http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf).\nConcretely, given inputs (grad, mean_squares, mom, lr), computes:\n\n    mean_squares_o = mean_squares + (1 - decay) * (square(grad) - mean_squares)\n    mom_o = momentum * mom + lr * grad / sqrt(epsilon + mean_squares_o)\n    grad_o = mom_o\n\nReturns (grad_o, mean_squares_o, mom_o).\n)DOC\");\nSHOULD_NOT_DO_GRADIENT(RmsProp);\n\n}\n"
  },
  {
    "path": "caffe2/sgd/rmsprop_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/common_omp.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\ntemplate <typename Context>\nvoid rmsprop_update(\n    int N,\n    const float* g,\n    const float* ms,\n    const float* mom,\n    float* ng,\n    float* nms,\n    float* nmom,\n    float decay,\n    float momentum,\n    float epsilon,\n    const float* lr,\n    Context* context);\n\ntemplate <typename T, class Context>\nclass RmsPropOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  RmsPropOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        decay_(OperatorBase::GetSingleArgument<float>(\"decay\", 0.9f)),\n        momentum_(OperatorBase::GetSingleArgument<float>(\"momentum\", 0.0f)),\n        epsilon_(OperatorBase::GetSingleArgument<float>(\"epsilon\", 1e-5f)) {}\n  bool RunOnDevice() override {\n    CAFFE_ENFORCE(Input(LR).size() == 1);\n    CAFFE_ENFORCE(Input(GRAD).size() == Input(MEAN_SQUARES).size());\n    CAFFE_ENFORCE(Input(GRAD).size() == Input(OUTPUT_MOMENTUM).size());\n    Output(OUTPUT_GRAD)->ResizeLike(Input(GRAD));\n    Output(OUTPUT_GRAD)->ResizeLike(Input(GRAD));\n    Output(OUTPUT_MEAN_SQUARES)->ResizeLike(Input(MEAN_SQUARES));\n    Output(OUTPUT_MOMENTUM)->ResizeLike(Input(MOMENTUM));\n    rmsprop_update<Context>(\n        Input(GRAD).size(),\n        Input(GRAD).template data<T>(),\n        Input(MEAN_SQUARES).template data<T>(),\n        Input(MOMENTUM).template data<T>(),\n        Output(OUTPUT_GRAD)->template mutable_data<T>(),\n        Output(OUTPUT_MEAN_SQUARES)->template mutable_data<T>(),\n        Output(OUTPUT_MOMENTUM)->template mutable_data<T>(),\n        decay_,\n        momentum_,\n        epsilon_,\n        Input(LR).template data<T>(),\n        &context_);\n    return true;\n  }\n\n protected:\n  T decay_{0.9};\n  T momentum_{0.0};\n  T epsilon_{1e-8};\n  INPUT_TAGS(GRAD, MEAN_SQUARES, MOMENTUM, LR);\n  OUTPUT_TAGS(OUTPUT_GRAD, OUTPUT_MEAN_SQUARES, OUTPUT_MOMENTUM);\n};\n}\n"
  },
  {
    "path": "caffe2/sgd/rmsprop_op_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"rmsprop_op.h\"\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\n__global__ void RmsPropUpdate(\n    int N,\n    const float* g,\n    const float* ms,\n    const float* mom,\n    float* ng,\n    float* nms,\n    float* nmom,\n    float decay,\n    float momentum,\n    float epsilon,\n    const float* lr) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    // Update new mean square estimate\n    nms[i] = ms[i] + (1.0f - decay) * (g[i] * g[i] - ms[i]);\n    // Update momentum estimate\n    nmom[i] =\n        mom[i] * momentum + lr[0] * g[i] / std::sqrt(epsilon + nms[i]);\n    // New gradient is the momentum\n    ng[i] = nmom[i];\n  }\n}\n\ntemplate <>\nvoid rmsprop_update<CUDAContext>(\n    int N,\n    const float* g,\n    const float* ms,\n    const float* mom,\n    float* ng,\n    float* nms,\n    float* nmom,\n    float decay,\n    float momentum,\n    float epsilon,\n    const float* lr,\n    CUDAContext* context) {\n  RmsPropUpdate<<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS, 0, context->cuda_stream()>>>(\n      N, g, ms, mom, ng, nms, nmom, decay, momentum, epsilon, lr);\n}\n\n\nREGISTER_CUDA_OPERATOR(RmsProp, RmsPropOp<float, CUDAContext>);\n\n}\n"
  },
  {
    "path": "caffe2/sgd/yellowfin_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/sgd/yellowfin_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(YellowFin, YellowFinOp<float, CPUContext>);\nOPERATOR_SCHEMA(YellowFin)\n    .NumInputs(10)\n    .NumOutputs(8)\n    .AllowInplace(\n        {{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7}})\n    .SetDoc(R\"DOC(\n\nComputes the YellowFin update (https://arxiv.org/abs/1706.03471) and performs\nmomentum SGD optimization step. lr and mu are not being shared between\nparameters. curv_win, g_avg, g2_avg and scalars_memory are just auxiliary\nmemory for computing moving averages (see the publication). Takes arguments\nbeta: coefficient for moving averages,\ncurv_win_width: timeframe when average squared gradient is being stored,\nepsilon: for numerical purposes,\nnesterov and zero_debias for debias of moving average.\n\n)DOC\")\n    .Input(0, \"param\", \"Parameters to be updated\")\n    .Input(1, \"moment\", \"Momentum\")\n    .Input(2, \"lr\", \"Learning rate\")\n    .Input(3, \"mu\", \"Momentum coefficient\")\n    .Input(4, \"curv_win\", \"Memory for latest curvature ranges\")\n    .Input(5, \"g_avg\", \"Moving average of gradient\")\n    .Input(6, \"g2_avg\", \"Moving average of squared gradient\")\n    .Input(7, \"scalars_memory\", \"Memory for stateful scalars\")\n    .Input(8, \"grad\", \"Gradient computed\")\n    .Input(9, \"iter\", \"Iteration number\")\n    .Output(0, \"output_param\", \"Parameters to be updated\")\n    .Output(1, \"output_moment\", \"Momentum\")\n    .Output(2, \"output_lr\", \"Output learning rate\")\n    .Output(3, \"output_mu\", \"Output momentum coefficient\")\n    .Output(4, \"output_curv_win\", \"Output memory for latest curvature ranges\")\n    .Output(5, \"output_g_avg\", \"Output moving average of gradient\")\n    .Output(6, \"output_g2_avg\", \"Output moving average of squared gradient\")\n    .Output(7, \"output_scalars_memory\", \"Output memory for stateful scalars\")\n    .Arg(\"beta\", \"Default 0.999\")\n    .Arg(\"curv_win_width\", \"Default 20\")\n    .Arg(\"epsilon\", \"Default 1e-6\")\n    .Arg(\"nesterov\", \"Default false\")\n    .Arg(\"zero_debias\", \"Default true\");\n\nSHOULD_NOT_DO_GRADIENT(YellowFin);\n\n#define CAFFE2_YELLOWFIN_GETLRMU(T)                                         \\\n  template <>                                                               \\\n  void YellowFinOp<T, CPUContext>::GetLrMu() {                              \\\n    const T curv_ratio = std::sqrt(*g_norm2_max_deb_ / *g_norm2_min_deb_);  \\\n    const T mu_limit = (curv_ratio - 1.0f) / (curv_ratio + 1.0f);           \\\n    const T pre_p = *distance_deb_ * *g_norm2_min_deb_;                     \\\n    const T p = (pre_p * pre_p) / (2.0f * *variance_);                      \\\n    const T w3 = (-std::sqrt(p * p + 4.0f / 27.0f * p * p * p) - p) / 2.0f; \\\n    const T w3_sign = w3 > 0.0f ? 1.0f : -1.0f;                             \\\n    const T w = w3_sign * std::pow(std::abs(w3), 1.0f / 3.0f);              \\\n    const T y = w - p / 3.0f / w;                                           \\\n    const T root = y + 1.0f;                                                \\\n    *mu_ = std::max(root * root, mu_limit * mu_limit);                      \\\n    *lr_ = std::pow(1.0f - std::sqrt(*mu_), 2) / *g_norm2_min_deb_;         \\\n    MovingAverage(1, mu_, mu_avg_, mu_avg_out_, mu_deb_);                   \\\n    MovingAverage(1, lr_, lr_avg_, lr_avg_out_, lr_deb_);                   \\\n  }\n\nCAFFE2_YELLOWFIN_GETLRMU(float)\n#undef CAFFE2_YELLOWFIN_GETLRMU\n\n// Usually moment_ == moment_out_ && param_ == param_out_\n#define CAFFE2_YELLOWFIN_MOMENTUMSGDUPDATE(T)                                  \\\n  template <>                                                                  \\\n  void YellowFinOp<T, CPUContext>::MomentumSgdUpdate() {                       \\\n    const T mu = *mu_avg_out_;                                                 \\\n    const T lr = *lr_avg_out_;                                                 \\\n    if (!nesterov_) {                                                          \\\n      for (int i = 0; i < D_; ++i) {                                           \\\n        moment_out_[i] = mu * moment_[i] + lr * grad_[i];                      \\\n        param_out_[i] = param_[i] - moment_out_[i];                            \\\n      }                                                                        \\\n    } else {                                                                   \\\n      for (int i = 0; i < D_; ++i) {                                           \\\n        const T moment_i = moment_[i];                                         \\\n        moment_out_[i] = mu * moment_i + lr * grad_[i];                        \\\n        param_out_[i] = param_[i] - (1 + mu) * moment_out_[i] + mu * moment_i; \\\n      }                                                                        \\\n    }                                                                          \\\n  }\n\nCAFFE2_YELLOWFIN_MOMENTUMSGDUPDATE(float)\n#undef CAFFE2_YELLOWFIN_MOMENTUMSGDUPDATE\n\n} // caffe2\n"
  },
  {
    "path": "caffe2/sgd/yellowfin_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// YellowFin: An automatic tuner for momentum SGD\n// (https://arxiv.org/abs/1706.03471)\n// The YellowFinOp tunes learning rate and momentum and performs momentum SGD\n// steps. The learning rate and momentum are separate for any matrix of\n// parameters.\n\n#pragma once\n\n#include <cmath>\n#include <cstring>\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass YellowFinOp final : public Operator<Context> {\n public:\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  YellowFinOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        curv_win_width_(\n            OperatorBase::GetSingleArgument<int>(\"curv_win_width\", 20)),\n        nesterov_(OperatorBase::GetSingleArgument<int>(\"nesterov\", false)),\n        zero_debias_(\n            OperatorBase::GetSingleArgument<bool>(\"zero_debias\", true)),\n        epsilon_(OperatorBase::GetSingleArgument<T>(\"epsilon\", 1e-6f)),\n        beta_(OperatorBase::GetSingleArgument<T>(\"beta\", 0.999f)) {}\n\n protected:\n  // GetLrMu and MomentumSgdUpdate have different implementations for GPU and\n  // CPU. All other methods are generic.\n  void GetLrMu();\n  void MomentumSgdUpdate();\n\n  void AfterApply() {\n    // g\n    MovingAverage(D_, grad_, g_avg_, g_avg_out_, g_deb_);\n    // g2\n    math::Mul(D_, grad_, grad_, aux_vector_, &context_);\n    MovingAverage(D_, aux_vector_, g2_avg_, g2_avg_out_, g2_deb_);\n    // g_norm2\n    math::Dot(D_, grad_, grad_, g_norm2_, &context_);\n    math::Maximum(1, epsilon_, g_norm2_, g_norm2_, &context_);\n    MovingAverage(1, g_norm2_, g_norm2_avg_, g_norm2_avg_out_, g_norm2_deb_);\n    // g_norm\n    math::Sqrt(1, g_norm2_, g_norm_, &context_);\n    MovingAverage(1, g_norm_, g_norm_avg_, g_norm_avg_out_, g_norm_deb_);\n    math::Maximum(1, epsilon_, g_norm_deb_, g_norm_deb_, &context_);\n    // Curvature range: g_norm2_min, g_norm2_max\n    math::CopyVector(curv_win_width_, curv_win_, curv_win_out_, &context_);\n    T* curv_win_cell = curv_win_out_ + (iter_ - 1) % curv_win_width_;\n    math::Log(1, g_norm2_, curv_win_cell, &context_);\n    int valid_end = std::min(curv_win_width_, iter_);\n    math::ReduceMin(\n        valid_end, curv_win_out_, g_norm2_min_, &scratch_tensor_, &context_);\n    math::ReduceMax(\n        valid_end, curv_win_out_, g_norm2_max_, &scratch_tensor_, &context_);\n    MovingAverage(\n        1,\n        g_norm2_min_,\n        g_norm2_min_avg_,\n        g_norm2_min_avg_out_,\n        g_norm2_min_deb_);\n    MovingAverage(\n        1,\n        g_norm2_max_,\n        g_norm2_max_avg_,\n        g_norm2_max_avg_out_,\n        g_norm2_max_deb_);\n    math::Exp(1, g_norm2_min_deb_, g_norm2_min_deb_, &context_);\n    math::Exp(1, g_norm2_max_deb_, g_norm2_max_deb_, &context_);\n    math::Maximum(1, epsilon_, g_norm2_min_deb_, g_norm2_min_deb_, &context_);\n    math::Maximum(1, epsilon_, g_norm2_max_deb_, g_norm2_max_deb_, &context_);\n    // Gradient variance\n    math::Dot(D_, g_deb_, g_deb_, aux_scalar_, &context_);\n\n    math::Sub(1, g_norm2_deb_, aux_scalar_, variance_, &context_);\n    math::Maximum(1, epsilon_, variance_, variance_, &context_);\n    // Distance to opt\n    math::Div(1, g_norm_avg_out_, g_norm2_avg_out_, distance_, &context_);\n    MovingAverage(\n        1, distance_, distance_avg_, distance_avg_out_, distance_deb_);\n    if (iter_ > 1) {\n      GetLrMu();\n    }\n  }\n\n  void MovingAverage(\n      const int N,\n      const T* elt,\n      const T* avg,\n      T* new_avg,\n      T* debias_avg) {\n    const T one = 1;\n    math::Scale(N, beta_, avg, new_avg, &context_);\n    math::Axpy(N, one - beta_, elt, new_avg, &context_);\n    math::Scale(N, debias_factor_, new_avg, debias_avg, &context_);\n  }\n\n  T ZeroDebiasFactor() {\n    if (zero_debias_) {\n      const T one = 1;\n      return one / (one - std::pow(beta_, iter_));\n    } else {\n      return 1;\n    }\n  }\n\n public:\n  bool RunOnDevice() override {\n// Iter live on the CPU\n\n#define CAFFE2_YF_READ_INPUT(INPUT_NAME, VAR_NAME)  \\\n  const auto VAR_NAME##_tensor = Input(INPUT_NAME); \\\n  VAR_NAME##_ = VAR_NAME##_tensor.template data<T>();\n\n    CAFFE2_YF_READ_INPUT(PARAM, param)\n    CAFFE2_YF_READ_INPUT(MOMENT, moment)\n    CAFFE2_YF_READ_INPUT(LR_AVG, lr_avg)\n    CAFFE2_YF_READ_INPUT(MU_AVG, mu_avg)\n    CAFFE2_YF_READ_INPUT(CURV_WIN, curv_win)\n    CAFFE2_YF_READ_INPUT(G_AVG, g_avg)\n    CAFFE2_YF_READ_INPUT(G2_AVG, g2_avg)\n    CAFFE2_YF_READ_INPUT(SCALARS_MEMORY, scalars_memory)\n    CAFFE2_YF_READ_INPUT(GRAD, grad)\n#undef CAFFE2_YF_READ_OUTPUT\n\n    CAFFE_ENFORCE(OperatorBase::InputIsType<TensorCPU>(ITER));\n    CAFFE_ENFORCE_EQ(lr_avg_tensor.size(), 1);\n    CAFFE_ENFORCE_EQ(mu_avg_tensor.size(), 1);\n    CAFFE_ENFORCE_EQ(param_tensor.ndim(), moment_tensor.ndim());\n    CAFFE_ENFORCE_EQ(param_tensor.ndim(), g_avg_tensor.ndim());\n    CAFFE_ENFORCE_EQ(param_tensor.ndim(), g2_avg_tensor.ndim());\n    CAFFE_ENFORCE_EQ(param_tensor.ndim(), grad_tensor.ndim());\n    for (int i = 0; i < param_tensor.ndim(); ++i) {\n      CAFFE_ENFORCE_EQ(param_tensor.dim32(i), moment_tensor.dim32(i));\n      CAFFE_ENFORCE_EQ(param_tensor.dim32(i), g_avg_tensor.dim32(i));\n      CAFFE_ENFORCE_EQ(param_tensor.dim32(i), g2_avg_tensor.dim32(i));\n      CAFFE_ENFORCE_EQ(param_tensor.dim32(i), grad_tensor.dim32(i));\n    }\n\n    iter_ = OperatorBase::Input<TensorCPU>(ITER).template data<int64_t>()[0];\n\n    D_ = param_tensor.size();\n\n    // Input data - persistent memory for internal scalars\n    // Note: Memory for these scalars is being allocated during initialization\n    //       of the network. If you want to add / remove a scalar, make a\n    //       suitable change of memory size in the initialization.\n    const T* memory_it = scalars_memory_ - 1;\n    g_norm_avg_ = ++memory_it;\n    g_norm2_avg_ = ++memory_it;\n    g_norm2_min_avg_ = ++memory_it;\n    g_norm2_max_avg_ = ++memory_it;\n    distance_avg_ = ++memory_it;\n\n// Output data\n\n#define CAFFE2_YF_READ_OUTPUT(OUTPUT_NAME, VAR_NAME)         \\\n  auto VAR_NAME##_out_tensor = Output(OUTPUT_##OUTPUT_NAME); \\\n  VAR_NAME##_out_tensor->ResizeLike(VAR_NAME##_tensor);      \\\n  VAR_NAME##_out_ = VAR_NAME##_out_tensor->template mutable_data<T>();\n\n    CAFFE2_YF_READ_OUTPUT(PARAM, param)\n    CAFFE2_YF_READ_OUTPUT(MOMENT, moment)\n    CAFFE2_YF_READ_OUTPUT(LR_AVG, lr_avg)\n    CAFFE2_YF_READ_OUTPUT(MU_AVG, mu_avg)\n    CAFFE2_YF_READ_OUTPUT(CURV_WIN, curv_win)\n    CAFFE2_YF_READ_OUTPUT(G_AVG, g_avg)\n    CAFFE2_YF_READ_OUTPUT(G2_AVG, g2_avg)\n    CAFFE2_YF_READ_OUTPUT(SCALARS_MEMORY, scalars_memory)\n#undef CAFFE2_YF_READ_OUTPUT\n\n    T* out_memory_it = scalars_memory_out_ - 1;\n    g_norm_avg_out_ = ++out_memory_it;\n    g_norm2_avg_out_ = ++out_memory_it;\n    g_norm2_min_avg_out_ = ++out_memory_it;\n    g_norm2_max_avg_out_ = ++out_memory_it;\n    distance_avg_out_ = ++out_memory_it;\n\n#define CAFFE2_YF_INIT_VECTOR(NAME) \\\n  NAME##_tensor_.Resize(D_);        \\\n  NAME##_ = NAME##_tensor_.template mutable_data<T>();\n\n    CAFFE2_YF_INIT_VECTOR(aux_vector)\n    CAFFE2_YF_INIT_VECTOR(g_deb)\n    CAFFE2_YF_INIT_VECTOR(g2_deb)\n    CAFFE2_YF_INIT_VECTOR(g_deb2)\n#undef CAFFE2_YF_INIT_VECTOR\n\n#define CAFFE2_YF_INIT_SCALAR(NAME) \\\n  NAME##_tensor_.Resize(1);         \\\n  NAME##_ = NAME##_tensor_.template mutable_data<T>();\n\n    CAFFE2_YF_INIT_SCALAR(aux_scalar)\n    CAFFE2_YF_INIT_SCALAR(distance)\n    CAFFE2_YF_INIT_SCALAR(distance_deb)\n    CAFFE2_YF_INIT_SCALAR(g_norm)\n    CAFFE2_YF_INIT_SCALAR(g_norm_deb)\n    CAFFE2_YF_INIT_SCALAR(g_norm2)\n    CAFFE2_YF_INIT_SCALAR(g_norm2_max)\n    CAFFE2_YF_INIT_SCALAR(g_norm2_max_deb)\n    CAFFE2_YF_INIT_SCALAR(g_norm2_min)\n    CAFFE2_YF_INIT_SCALAR(g_norm2_min_deb)\n    CAFFE2_YF_INIT_SCALAR(g_norm2_deb)\n    CAFFE2_YF_INIT_SCALAR(lr)\n    CAFFE2_YF_INIT_SCALAR(lr_deb)\n    CAFFE2_YF_INIT_SCALAR(mu_deb)\n    CAFFE2_YF_INIT_SCALAR(mu)\n    CAFFE2_YF_INIT_SCALAR(variance)\n#undef CAFFE2_YF_INIT_SCALAR\n\n    debias_factor_ = ZeroDebiasFactor();\n    MomentumSgdUpdate();\n    AfterApply();\n    return true;\n  }\n\n protected:\n  int curv_win_width_;\n  bool nesterov_;\n  bool zero_debias_;\n\n  T epsilon_;\n  T beta_;\n  T debias_factor_;\n\n  int D_;\n\n// Temporary memory on device, listed all variables used in calculations\n#define CAFFE2_YF_DEFINE_TENSOR(NAME) \\\n  Tensor<Context> NAME##_tensor_;     \\\n  T* NAME##_;\n\n  CAFFE2_YF_DEFINE_TENSOR(aux_vector)\n  CAFFE2_YF_DEFINE_TENSOR(g_deb)\n  CAFFE2_YF_DEFINE_TENSOR(g2_deb)\n  CAFFE2_YF_DEFINE_TENSOR(g_deb2)\n\n  CAFFE2_YF_DEFINE_TENSOR(aux_scalar)\n  CAFFE2_YF_DEFINE_TENSOR(distance)\n  CAFFE2_YF_DEFINE_TENSOR(distance_deb)\n  CAFFE2_YF_DEFINE_TENSOR(g_norm)\n  CAFFE2_YF_DEFINE_TENSOR(g_norm_deb)\n  CAFFE2_YF_DEFINE_TENSOR(g_norm2)\n  CAFFE2_YF_DEFINE_TENSOR(g_norm2_deb)\n  CAFFE2_YF_DEFINE_TENSOR(g_norm2_max)\n  CAFFE2_YF_DEFINE_TENSOR(g_norm2_max_deb)\n  CAFFE2_YF_DEFINE_TENSOR(g_norm2_min)\n  CAFFE2_YF_DEFINE_TENSOR(g_norm2_min_deb)\n  CAFFE2_YF_DEFINE_TENSOR(lr)\n  CAFFE2_YF_DEFINE_TENSOR(lr_deb)\n  CAFFE2_YF_DEFINE_TENSOR(mu)\n  CAFFE2_YF_DEFINE_TENSOR(mu_deb)\n  CAFFE2_YF_DEFINE_TENSOR(variance)\n\n  Tensor<Context> scratch_tensor_;\n\n#undef CAFFE2_YF_DEFINE_TENSOR\n\n  // Input tensors' data\n  const T* param_;\n  const T* moment_;\n  const T* lr_avg_;\n  const T* mu_avg_;\n  const T* curv_win_;\n  const T* g_avg_;\n  const T* g2_avg_;\n  const T* scalars_memory_;\n  const T* grad_;\n  int iter_;\n\n  // Scalar data from scalars_memory_ input tensor\n  const T* g_norm_avg_;\n  const T* g_norm2_avg_;\n  const T* g_norm2_min_avg_;\n  const T* g_norm2_max_avg_;\n  const T* distance_avg_;\n\n  // Output tensors' data\n\n  T* param_out_;\n  T* moment_out_;\n  T* lr_avg_out_;\n  T* mu_avg_out_;\n  T* curv_win_out_;\n  T* g_avg_out_;\n  T* g2_avg_out_;\n  T* scalars_memory_out_;\n\n  // Scalar data from scalars_memory_ output tensor\n  T* g_norm_avg_out_;\n  T* g_norm2_avg_out_;\n  T* g_norm2_min_avg_out_;\n  T* g_norm2_max_avg_out_;\n  T* distance_avg_out_;\n\n  INPUT_TAGS(\n      PARAM,\n      MOMENT,\n      LR_AVG,\n      MU_AVG,\n      CURV_WIN,\n      G_AVG,\n      G2_AVG,\n      SCALARS_MEMORY,\n      GRAD,\n      ITER);\n  OUTPUT_TAGS(\n      OUTPUT_PARAM,\n      OUTPUT_MOMENT,\n      OUTPUT_LR_AVG,\n      OUTPUT_MU_AVG,\n      OUTPUT_CURV_WIN,\n      OUTPUT_G_AVG,\n      OUTPUT_G2_AVG,\n      OUTPUT_SCALARS_MEMORY);\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/sgd/yellowfin_op_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// YellowFin: An automatic tuner for momentum SGD\n// (https://arxiv.org/abs/1706.03471)\n\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/sgd/yellowfin_op.h\"\n\nnamespace caffe2 {\n\n__global__ void GetLrMuKernel(\n    const float* g_norm2_max_deb,\n    const float* g_norm2_min_deb,\n    const float* distance_deb,\n    const float* variance,\n    float* mu,\n    float* lr) {\n  const float curv_ratio = sqrtf(*g_norm2_max_deb / *g_norm2_min_deb);\n  const float mu_limit = (curv_ratio - 1.0f) / (curv_ratio + 1.0f);\n  const float pre_p = *distance_deb * *g_norm2_min_deb;\n  const float p = (pre_p * pre_p) / (2.0f * *variance);\n  const float w3 = (-sqrtf(p * p + 4.0f / 27.0f * p * p * p) - p) / 2.0f;\n  const float w3_sign = w3 > 0.0f ? 1.0f : -1.0f;\n  const float w = w3_sign * powf(fabsf(w3), 1.0f / 3.0f);\n  const float y = w - p / 3.0f / w;\n  const float root = y + 1.0f;\n  *mu = fmaxf(root * root, mu_limit * mu_limit);\n  *lr = powf(1.0f - sqrtf(*mu), 2) / *g_norm2_min_deb;\n}\n\ntemplate <>\nvoid YellowFinOp<float, CUDAContext>::GetLrMu() {\n  // Finding root of cubic formula for YF's Single Step\n  GetLrMuKernel<<<1, 1, 0, context_.cuda_stream()>>>(\n      g_norm2_max_deb_, g_norm2_min_deb_, distance_deb_, variance_, mu_, lr_);\n  MovingAverage(1, mu_, mu_avg_, mu_avg_out_, mu_deb_);\n  MovingAverage(1, lr_, lr_avg_, lr_avg_out_, lr_deb_);\n}\n\n__global__ void MomentumSgdKernel(\n    const int N,\n    const float* mu_ptr,\n    const float* lr_ptr,\n    const float* param,\n    const float* grad,\n    const float* moment,\n    float* param_out,\n    float* moment_out,\n    bool nesterov) {\n  const float mu = *mu_ptr;\n  const float lr = *lr_ptr;\n  if (!nesterov) {\n    CUDA_1D_KERNEL_LOOP(i, N) {\n      moment_out[i] = mu * moment[i] * lr * grad[i];\n      param_out[i] = param[i] - moment_out[i];\n    }\n  } else {\n    CUDA_1D_KERNEL_LOOP(i, N) {\n      const float moment_i = moment[i];\n      moment_out[i] = mu * moment_i + lr * grad[i];\n      param_out[i] = param[i] - (1 + mu) * moment_out[i] + mu * moment_i;\n    }\n  }\n}\n\ntemplate <>\nvoid YellowFinOp<float, CUDAContext>::MomentumSgdUpdate() {\n  MomentumSgdKernel<<<\n      CAFFE_GET_BLOCKS(D_),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      D_,\n      mu_avg_out_,\n      lr_avg_out_,\n      param_,\n      grad_,\n      moment_,\n      param_out_,\n      moment_out_,\n      nesterov_);\n}\n\nREGISTER_CUDA_OPERATOR(YellowFin, YellowFinOp<float, CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/share/CMakeLists.txt",
    "content": "# There is a linking issue that happens in some of the Windows builds.\n# TODO(Yangqing): after the module redesing, enable this back.\nif (NOT MSVC)\n  add_subdirectory(contrib)\nendif()\n\n# CPU source, test sources, binary sources\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_BINARY_SRCS ${Caffe2_CPU_BINARY_SRCS} PARENT_SCOPE)\n\n# GPU source, test sources, binary sources\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_BINARY_SRCS ${Caffe2_GPU_BINARY_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/share/contrib/CMakeLists.txt",
    "content": "if (USE_NNPACK)\n  add_subdirectory(nnpack)\nendif()\nif (USE_ZSTD)\n  add_subdirectory(zstd)\nendif()\n\n# CPU source, test sources, binary sources\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_BINARY_SRCS ${Caffe2_CPU_BINARY_SRCS} PARENT_SCOPE)\n\n# GPU source, test sources, binary sources\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_BINARY_SRCS ${Caffe2_GPU_BINARY_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/share/contrib/nnpack/CMakeLists.txt",
    "content": "set(Caffe2_CONTRIB_NNPACK_CPU_SRC\n  \"${CMAKE_CURRENT_SOURCE_DIR}/conv_op.cc\"\n)\nset(Caffe2_CONTRIB_NNPACK_TEST_CPU_SRC\n  \"${CMAKE_CURRENT_SOURCE_DIR}/conv_op_test.cc\"\n)\n\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${Caffe2_CONTRIB_NNPACK_CPU_SRC} PARENT_SCOPE)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${Caffe2_CONTRIB_NNPACK_TEST_CPU_SRC} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/share/contrib/nnpack/conv_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/common.h\"\n\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/operators/conv_op_shared.h\"\n#include \"caffe2/operators/conv_pool_op_base.h\"\n\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/threadpool/pthreadpool_impl.h\"\n#include \"nnpack.h\"\n\nCAFFE2_DEFINE_bool(caffe2_profile_nnpack, false, \"\");\nnamespace caffe2 {\n\nvoid initNNPACK() {\n  static std::once_flag once;\n  std::call_once(once, []() {\n    enum nnp_status nnpack_status = nnp_initialize();\n    CAFFE_ENFORCE(\n        nnpack_status == nnp_status_success, \"NNPack is not supported here!\");\n  });\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Definitions\n////////////////////////////////////////////////////////////////////////////////\n\nclass NNPACKConvOp final : public ConvPoolOpBase<CPUContext> {\n public:\n  NNPACKConvOp(const OperatorDef& operator_def, Workspace* ws)\n      : ConvPoolOpBase<CPUContext>(operator_def, ws),\n        algorithm_(getConvolutionAlgorithm()),\n        transformStrategy_(getConvolutionTransformStrategy()),\n        ws_(ws) {\n    OPERATOR_NEEDS_FEATURE(\n        this->order_ == StorageOrder::NCHW,\n        \"NNPack only supports NCHW order. Please consider add \\\n            TransposeOp with axes=[0, 3, 1, 2] before NNPack Conv.\");\n    OPERATOR_NEEDS_FEATURE(\n        pad_t() < kernel_h(), \"NNPACK only supports pad < kernel size\");\n    OPERATOR_NEEDS_FEATURE(\n        pad_b() < kernel_h(), \"NNPACK only supports pad < kernel size\");\n    OPERATOR_NEEDS_FEATURE(\n        pad_l() < kernel_w(), \"NNPACK only supports pad < kernel size\");\n    OPERATOR_NEEDS_FEATURE(\n        pad_r() < kernel_w(), \"NNPACK only supports pad < kernel size\");\n\n    createSharedBuffer<CPUContext>(ws);\n  }\n\n  bool RunOnDeviceWithOrderNCHW() override;\n\n private:\n  nnp_convolution_algorithm getConvolutionAlgorithm() const;\n  nnp_convolution_transform_strategy getConvolutionTransformStrategy() const;\n\n  const nnp_convolution_algorithm algorithm_;\n  // Modified after precomputing the kernels. State transitions are:\n  // - precompute -> (first call to Run()) -> reuse (on successful precompute)\n  //                                       -> compute (on failing precompute)\n  // - compute\n  nnp_convolution_transform_strategy transformStrategy_;\n  Workspace* ws_;\n  // Per-group transformed filters\n  std::vector<TensorCPU*> transformedFilters_;\n  // Zero-filled bias for convolutions without bias\n  // This may be needed because NNPACK interface always expects conv with bias\n  std::vector<float> dummyBias_;\n};\n\n////////////////////////////////////////////////////////////////////////////////\n// Implementations\n////////////////////////////////////////////////////////////////////////////////\n\nnnp_convolution_algorithm NNPACKConvOp::getConvolutionAlgorithm() const {\n  if (!OperatorBase::HasSingleArgumentOfType<std::string>(\"algo\")) {\n    // No preference is stated. Heuristics for the best mobile device\n    // algorithm are different than NNPACK's version, as Winograd\n    // tends to be a lot faster. Use Winograd if the convolution\n    // is 3x3d1s1.\n    if (kernel_h() == 3 && kernel_w() == 3 && dilation_h() == 1 &&\n        dilation_w() == 1 && stride_h() == 1 && stride_w() == 1) {\n      // use Winograd\n      return nnp_convolution_algorithm_wt8x8;\n    }\n\n    return nnp_convolution_algorithm_auto;\n  }\n\n  // Otherwise, there is a preference.\n  auto algo = OperatorBase::GetSingleArgument<std::string>(\"algo\", \"AUTO\");\n  if (algo == \"AUTO\") {\n    return nnp_convolution_algorithm_auto;\n  }\n  if (algo == \"WINOGRAD\") {\n    return nnp_convolution_algorithm_wt8x8;\n  }\n  if (algo == \"WINOGRAD_FP16\") {\n    return nnp_convolution_algorithm_wt8x8_fp16;\n  }\n  if (algo == \"FT16\") {\n    return nnp_convolution_algorithm_ft16x16;\n  }\n  if (algo == \"FT8\") {\n    return nnp_convolution_algorithm_ft8x8;\n  }\n  if (algo == \"IMPLICIT_GEMM\") {\n    return nnp_convolution_algorithm_implicit_gemm;\n  }\n  if (algo == \"DIRECT\") {\n    return nnp_convolution_algorithm_direct;\n  }\n  return nnp_convolution_algorithm_auto;\n}\n\nnnp_convolution_transform_strategy\nNNPACKConvOp::getConvolutionTransformStrategy() const {\n  auto kts = OperatorBase::GetSingleArgument<std::string>(\n      \"convolution_transform_strategy\", \"COMPUTE\");\n  if (kts == \"PRECOMPUTE\") {\n    return nnp_convolution_transform_strategy_precompute;\n  }\n  // Default to computing each time.\n  return nnp_convolution_transform_strategy_compute;\n}\n\nbool NNPACKConvOp::RunOnDeviceWithOrderNCHW() {\n  auto& X = Input(0);\n  auto& filter = Input(1);\n  auto* Y = Output(0);\n  CAFFE_ENFORCE(X.ndim() == 4, \"Input dim should be 4\");\n  const int N = X.dim32(0), C = X.dim32(1), H = X.dim32(2), W = X.dim32(3);\n  CAFFE_ENFORCE(filter.ndim() == 4, \"\");\n  const int M = filter.dim32(0);\n  CAFFE_ENFORCE(C % this->group_ == 0, \"\");\n  CAFFE_ENFORCE(M % this->group_ == 0, \"\");\n  CAFFE_ENFORCE(filter.dim32(1) == C / this->group_, \"\");\n  CAFFE_ENFORCE(filter.dim32(2) == kernel_h(), \"\");\n  CAFFE_ENFORCE(filter.dim32(3) == kernel_w(), \"\");\n  ConvPoolOpBase<CPUContext>::SetOutputSize(X, Y, filter.dim32(0));\n  const int oH = Y->dim32(2), oW = Y->dim32(3);\n\n  const float* biasData = NULL;\n  if (InputSize() == 3) {\n    /* Convolution with bias */\n    auto& bias = Input(2);\n    CAFFE_ENFORCE(bias.ndim() == 1, \"\");\n    CAFFE_ENFORCE(bias.dim32(0) == M, \"\");\n    biasData = bias.template data<float>();\n  } else {\n    /* NNPACK interface requires bias. Use a dummy zero-filled vector. */\n    if (dummyBias_.size() != M) {\n      dummyBias_.resize(M);\n    }\n    biasData = dummyBias_.data();\n  }\n\n  const size_t batch_size = X.dim32(0);\n  const size_t input_channels = X.dim32(1);\n  const size_t output_channels = Y->dim32(1);\n  const nnp_size input_size = {.width = static_cast<size_t>(X.dim32(3)),\n                               .height = static_cast<size_t>(X.dim32(2))};\n  // filter is MCHW\n  const nnp_size kernel_size = {.width = static_cast<size_t>(filter.dim32(3)),\n                                .height = static_cast<size_t>(filter.dim32(2))};\n  // pad is tblr\n  const nnp_padding padding = {.top = static_cast<size_t>(pad_t()),\n                               .right = static_cast<size_t>(pad_r()),\n                               .bottom = static_cast<size_t>(pad_b()),\n                               .left = static_cast<size_t>(pad_l())};\n\n  const nnp_size output_subsample = {.width = static_cast<size_t>(stride_w()),\n                                     .height = static_cast<size_t>(stride_h())};\n  initNNPACK();\n  pthreadpool pool(ws_->GetThreadPool());\n\n  runWithSharedBuffer<CPUContext>(ws_, [&](Tensor<CPUContext>* buffer) {\n    if (transformStrategy_ == nnp_convolution_transform_strategy_precompute) {\n      transformedFilters_.resize(group_);\n\n      size_t transformedFilterSize = 0;\n      nnp_status status = nnp_convolution_inference(\n          algorithm_,\n          nnp_convolution_transform_strategy_precompute,\n          C / group_,\n          M / group_,\n          input_size,\n          padding,\n          kernel_size,\n          output_subsample,\n          nullptr /* input */,\n          nullptr /* filters */,\n          nullptr /* bias */,\n          nullptr /* output */,\n          nullptr /* workspace buffer = transformed filter */,\n          &transformedFilterSize,\n          nnp_activation_identity,\n          nullptr /* activation parameter */,\n          &pool,\n          nullptr /* profile */);\n      if (status == nnp_status_success) {\n        /* For these convolution parameters filter transforms can be\n         * pre-computed */\n\n        /* Division with rounding up, in case size is not multiple of\n         * sizeof(float) */\n        const size_t transformedFilterElements =\n            (transformedFilterSize + sizeof(float) - 1) / sizeof(float);\n\n        for (auto g = 0; g < group_; g++) {\n          transformedFilters_[g] =\n              ws_->CreateBlob(\n                     debug_def().name() + \"_transformed_\" + to_string(g))\n                  ->GetMutable<TensorCPU>();\n          transformedFilters_[g]->Resize(transformedFilterElements);\n\n          status = nnp_convolution_inference(\n              algorithm_,\n              nnp_convolution_transform_strategy_precompute,\n              C / group_,\n              M / group_,\n              input_size,\n              padding,\n              kernel_size,\n              output_subsample,\n              nullptr /* input */,\n              filter.template data<float>() + filter.size() / group_ * g,\n              nullptr /* bias */,\n              nullptr /* output */,\n              static_cast<void*>(\n                  transformedFilters_[g]->template mutable_data<float>()),\n              &transformedFilterSize,\n              nnp_activation_identity,\n              nullptr /* activation parameter */,\n              &pool,\n              nullptr /* profile */);\n          CAFFE_ENFORCE(\n              nnp_status_success == status,\n              \"NNPACK convolution filter pre-transformation return error\");\n        }\n\n        /*\n         * Now, we've precomputed all our filter transformations.\n         * Switch to reuse strategy to avoid doing transformation again on next\n         * iteration.\n         */\n        if (transformStrategy_ ==\n            nnp_convolution_transform_strategy_precompute) {\n          CAFFE_ENFORCE_EQ(transformedFilters_.size(), group_);\n          transformStrategy_ = nnp_convolution_transform_strategy_reuse;\n        }\n      } else {\n        LOG(WARNING)\n            << \"Failed to query workspace size to precompute kernels, falling back to re-compute strategy\";\n        transformStrategy_ = nnp_convolution_transform_strategy_compute;\n      }\n\n      // Enforce when we leave this block that we have transitioned out of the\n      // precompute state.\n      CAFFE_ENFORCE(\n          transformStrategy_ != nnp_convolution_transform_strategy_precompute);\n    }\n\n    CAFFE_ENFORCE(\n        transformStrategy_ == nnp_convolution_transform_strategy_reuse ||\n        transformStrategy_ == nnp_convolution_transform_strategy_compute);\n    const auto N = X.dim32(0);\n    for (auto n = 0; n < N; ++n) {\n      for (auto g = 0; g < group_; ++g) {\n        nnp_profile profile;\n        size_t workspaceSize = buffer->nbytes();\n        if (workspaceSize == 0) {\n          /* Allocate some memory to ensure buffer pointer is not NULL. This\n           * simplifies further logic. */\n          buffer->Resize(1);\n          workspaceSize = buffer->nbytes();\n        }\n        nnp_status status = nnp_convolution_inference(\n            algorithm_,\n            transformStrategy_,\n            C / group_,\n            M / group_,\n            input_size,\n            padding,\n            kernel_size,\n            output_subsample,\n            X.template data<float>() + n * C * H * W + g * H * W * (C / group_),\n            transformStrategy_ == nnp_convolution_transform_strategy_reuse\n                ? transformedFilters_[g]->template data<float>()\n                : filter.template data<float>() + filter.size() / group_ * g,\n            biasData + M / group_ * g,\n            Y->template mutable_data<float>() + n * oH * oW * M +\n                g * oH * oW * (M / group_),\n            static_cast<void*>(buffer->template mutable_data<float>()),\n            &workspaceSize,\n            nnp_activation_identity,\n            nullptr /* activation parameter */,\n            &pool,\n            FLAGS_caffe2_profile_nnpack ? &profile : nullptr);\n        if (status == nnp_status_insufficient_buffer) {\n          /* Query required workspace size, increase buffer, and try again */\n          status = nnp_convolution_inference(\n              algorithm_,\n              transformStrategy_,\n              C / group_,\n              M / group_,\n              input_size,\n              padding,\n              kernel_size,\n              output_subsample,\n              nullptr /* input */,\n              nullptr,\n              nullptr /* bias */,\n              nullptr /* output */,\n              nullptr /* workspace buffer */,\n              &workspaceSize,\n              nnp_activation_identity,\n              nullptr /* activation parameter */,\n              &pool,\n              nullptr /* profile */);\n          if (status == nnp_status_success) {\n            /* Division with rounding up, in case size is not multiple of\n             * sizeof(float) */\n            const size_t workspace_elements =\n                (workspaceSize + sizeof(float) - 1) / sizeof(float);\n            buffer->Resize(workspace_elements);\n\n            /* Try convolution_inference again. If this time it fails, it is\n             * fatal. */\n            status = nnp_convolution_inference(\n                algorithm_,\n                transformStrategy_,\n                C / group_,\n                M / group_,\n                input_size,\n                padding,\n                kernel_size,\n                output_subsample,\n                X.template data<float>() + n * C * H * W +\n                    g * H * W * (C / group_),\n                transformStrategy_ == nnp_convolution_transform_strategy_reuse\n                    ? transformedFilters_[g]->template data<float>()\n                    : filter.template data<float>() +\n                        filter.size() / group_ * g,\n                biasData + M / group_ * g,\n                Y->template mutable_data<float>() + n * oH * oW * M +\n                    g * oH * oW * (M / group_),\n                static_cast<void*>(buffer->template mutable_data<float>()),\n                &workspaceSize,\n                nnp_activation_identity,\n                nullptr /* activation parameter */,\n                &pool,\n                FLAGS_caffe2_profile_nnpack ? &profile : nullptr);\n          }\n        }\n\n        VLOG(1) << \"NNPACK buffer size: \" << buffer->nbytes();\n        CAFFE_ENFORCE(\n            nnp_status_success == status,\n            \"NNPACK convolution computation returned error\");\n        if (FLAGS_caffe2_profile_nnpack) {\n          char buffer[1024];\n          const double gmacs =\n              double(\n                  Y->dim32(2) * Y->dim32(3) * Y->dim32(1) * X.dim32(1) *\n                  kernel_size.width * kernel_size.height / group_ / group_) /\n              1.0E9;\n          const double gflops = 2 * gmacs / profile.total;\n          auto ret = snprintf(\n              buffer,\n              sizeof(buffer),\n              \"H: %3zu, W: %3zu, iC: %3zu, oC: %3zu, K: %1zu, S: %1zu, P: %1zu, GMACs: \"\n              \"%4.2f, totalT: %6.3f, inputT: %6.3f, \"\n              \"kernelT: %6.3f, blockT: %6.3f, outputT: %6.3f, GFLOPS: %6.3f\",\n              size_t(X.dim(2)),\n              size_t(X.dim(3)),\n              size_t(X.dim(1)),\n              size_t(Y->dim(1)),\n              size_t(kernel_size.width),\n              size_t(output_subsample.width),\n              size_t(padding.top),\n              gmacs,\n              profile.total * 1E3,\n              profile.input_transform * 1E3,\n              profile.kernel_transform * 1E3,\n              profile.block_multiplication * 1E3,\n              profile.output_transform * 1E3,\n              gflops);\n          CAFFE_ENFORCE(ret > 0);\n          LOG(INFO) << buffer;\n        }\n      }\n    }\n  });\n  return true;\n}\n\nREGISTER_CPU_OPERATOR_WITH_ENGINE(Conv, NNPACK, NNPACKConvOp);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/share/contrib/nnpack/conv_op_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/proto_utils.h\"\n#include \"gtest/gtest.h\"\n\n#include <cmath>\n#include <random>\n\nnamespace caffe2 {\n\nnamespace {\n\nvoid AddNoiseInput(\n    const vector<TIndex>& shape,\n    const string& name,\n    Workspace* ws) {\n  DeviceOption option;\n  CPUContext context(option);\n  Blob* blob = ws->CreateBlob(name);\n  auto* tensor = blob->GetMutable<TensorCPU>();\n  tensor->Resize(shape);\n\n  math::RandGaussian<float, CPUContext>(\n      tensor->size(), 0.0f, 3.0f, tensor->mutable_data<float>(), &context);\n  for (auto i = 0; i < tensor->size(); ++i) {\n    tensor->mutable_data<float>()[i] =\n        std::min(-5.0f, std::max(5.0f, tensor->mutable_data<float>()[i]));\n  }\n}\n\ninline float relativeError(float a, float b) {\n  return std::abs(a - b) / (0.5f * (std::abs(a) + std::abs(b)));\n}\n\nvoid compare(\n    int N,\n    int inputC,\n    int H,\n    int W,\n    int outputC,\n    int kernelH,\n    int kernelW,\n    int strideH,\n    int strideW,\n    int padT,\n    int padL,\n    int padB,\n    int padR,\n    int group,\n    const std::string& algorithm,\n    const std::string& convolutionTransformStrategy,\n    float maxRelErr,\n    float absErrForRelErrFailure) {\n  LOG(INFO) << \"running N \" << N << \" inputC \" << inputC << \" H \" << H << \" W \"\n            << W << \" outputC \" << outputC << \" kernelH \" << kernelH\n            << \" kernelW \" << kernelW << \" strideH \" << strideH << \" strideW \"\n            << strideW << \" padT \" << padT << \" padL \" << padL << \" padB \"\n            << padB << \" padR \" << padR << \" group \" << group;\n\n  Workspace ws;\n\n  OperatorDef nnpackOpDef;\n  nnpackOpDef.set_name(\"test\");\n  nnpackOpDef.set_type(\"Conv\");\n  nnpackOpDef.set_engine(\n      algorithm == \"DEPTHWISE_3x3\" ? \"DEPTHWISE_3x3\" : \"NNPACK\");\n  nnpackOpDef.add_input(\"X\");\n  nnpackOpDef.add_input(\"W\");\n  nnpackOpDef.add_input(\"B\");\n  nnpackOpDef.add_output(\"Y_nnpack\");\n\n  nnpackOpDef.add_arg()->CopyFrom(MakeArgument(\"kernel_h\", kernelH));\n  nnpackOpDef.add_arg()->CopyFrom(MakeArgument(\"kernel_w\", kernelW));\n  if (!algorithm.empty()) {\n    nnpackOpDef.add_arg()->CopyFrom(MakeArgument(\"algo\", algorithm));\n  }\n  if (!convolutionTransformStrategy.empty()) {\n    nnpackOpDef.add_arg()->CopyFrom(MakeArgument(\n        \"convolution_transform_strategy\", convolutionTransformStrategy));\n  }\n  nnpackOpDef.add_arg()->CopyFrom(MakeArgument(\"stride_h\", strideH));\n  nnpackOpDef.add_arg()->CopyFrom(MakeArgument(\"stride_w\", strideW));\n  nnpackOpDef.add_arg()->CopyFrom(MakeArgument(\"pad_t\", padT));\n  nnpackOpDef.add_arg()->CopyFrom(MakeArgument(\"pad_l\", padL));\n  nnpackOpDef.add_arg()->CopyFrom(MakeArgument(\"pad_b\", padB));\n  nnpackOpDef.add_arg()->CopyFrom(MakeArgument(\"pad_r\", padR));\n  nnpackOpDef.add_arg()->CopyFrom(MakeArgument(\"group\", group));\n\n  AddNoiseInput(vector<TIndex>{N, inputC, H, W}, \"X\", &ws);\n  AddNoiseInput(\n      vector<TIndex>{outputC, inputC / group, kernelH, kernelW}, \"W\", &ws);\n  AddNoiseInput(vector<TIndex>{outputC}, \"B\", &ws);\n\n  unique_ptr<OperatorBase> nnpackOp(CreateOperator(nnpackOpDef, &ws));\n  EXPECT_NE(nullptr, nnpackOp.get());\n\n  OperatorDef referenceOpDef;\n  referenceOpDef.set_name(\"test\");\n  referenceOpDef.set_type(\"Conv\");\n  referenceOpDef.add_input(\"X\");\n  referenceOpDef.add_input(\"W\");\n  referenceOpDef.add_input(\"B\");\n  referenceOpDef.add_output(\"Y_reference\");\n\n  referenceOpDef.add_arg()->CopyFrom(MakeArgument(\"kernel_h\", kernelH));\n  referenceOpDef.add_arg()->CopyFrom(MakeArgument(\"kernel_w\", kernelW));\n  referenceOpDef.add_arg()->CopyFrom(MakeArgument(\"stride_h\", strideH));\n  referenceOpDef.add_arg()->CopyFrom(MakeArgument(\"stride_w\", strideW));\n  referenceOpDef.add_arg()->CopyFrom(MakeArgument(\"pad_t\", padT));\n  referenceOpDef.add_arg()->CopyFrom(MakeArgument(\"pad_l\", padL));\n  referenceOpDef.add_arg()->CopyFrom(MakeArgument(\"pad_b\", padB));\n  referenceOpDef.add_arg()->CopyFrom(MakeArgument(\"pad_r\", padR));\n  referenceOpDef.add_arg()->CopyFrom(MakeArgument(\"group\", group));\n\n  unique_ptr<OperatorBase> referenceOp(CreateOperator(referenceOpDef, &ws));\n  EXPECT_NE(nullptr, referenceOp.get());\n\n  for (auto i = 0; i < 10; ++i) {\n    EXPECT_TRUE(nnpackOp->Run());\n  }\n  Blob* nnpackOutputBlob = ws.GetBlob(\"Y_nnpack\");\n  EXPECT_NE(nullptr, nnpackOutputBlob);\n  auto& nnpackOutput = nnpackOutputBlob->Get<TensorCPU>();\n\n  for (auto i = 0; i < 10; ++i) {\n    EXPECT_TRUE(referenceOp->Run());\n  }\n\n  Blob* referenceOutputBlob = ws.GetBlob(\"Y_reference\");\n  EXPECT_NE(nullptr, referenceOutputBlob);\n  auto& referenceOutput = referenceOutputBlob->Get<TensorCPU>();\n\n  // Compare all output points\n  for (int n = 0; n < nnpackOutput.dim32(0); ++n) {\n    for (int c = 0; c < nnpackOutput.dim32(1); ++c) {\n      for (int h = 0; h < nnpackOutput.dim32(2); ++h) {\n        for (int w = 0; w < nnpackOutput.dim32(3); ++w) {\n          int offset = n * nnpackOutput.dim32(1) * nnpackOutput.dim32(2) *\n                  nnpackOutput.dim32(3) +\n              c * nnpackOutput.dim32(2) * nnpackOutput.dim32(3) +\n              h * nnpackOutput.dim32(3) + w;\n\n          auto v1 = nnpackOutput.data<float>()[offset];\n          auto v2 = referenceOutput.data<float>()[offset];\n\n          float relErr = relativeError(v1, v2);\n          float absErr = std::abs(v1 - v2);\n\n          // For small values / small difference, the relative error\n          // can be huge but the absolute error will be small\n          EXPECT_TRUE(\n              relErr <= maxRelErr ||\n              (relErr > maxRelErr && absErr <= absErrForRelErrFailure))\n              << v1 << \" \" << v2 << \" (rel err \" << relErr << \") \"\n              << \"(\" << n << \" \" << c << \" \" << h << \" \" << w << \") \"\n              << \"running N \" << N << \" inputC \" << inputC << \" H \" << H\n              << \" W \" << W << \" outputC \" << outputC << \" kernelH \" << kernelH\n              << \" kernelW \" << kernelW << \" strideH \" << strideH << \" strideW \"\n              << strideW << \" padT \" << padT << \" padL \" << padL << \" padB \"\n              << padB << \" padR \" << padR << \" group \" << group << \" algorithm \"\n              << algorithm << \" convolutionTransformStrategy \"\n              << convolutionTransformStrategy;\n        }\n      }\n    }\n  }\n}\n\nint randInt(int a, int b) {\n  static std::random_device rd;\n  static std::mt19937 gen(rd());\n\n  return std::uniform_int_distribution<int>(a, b)(gen);\n}\n\nvoid runConv(\n    int kernelH,\n    int kernelW,\n    int strideH,\n    int strideW,\n    int group = 1,\n    std::string algo = \"\",\n    int planesIn = randInt(1, 6),\n    int planesOut = randInt(1, 6),\n    int n = randInt(1, 2),\n    std::string convolutionTransformStrategy = \"COMPUTE\") {\n  int h = randInt(20, 100);\n  int w = randInt(20, 100);\n  // This pad restriction is imposed by NNPACK\n  int padT = std::min(randInt(0, 3), kernelH - 1);\n  int padB = std::min(randInt(0, 3), kernelH - 1);\n  int padL = std::min(randInt(0, 3), kernelW - 1);\n  int padR = std::min(randInt(0, 3), kernelW - 1);\n\n  caffe2::compare(\n      n,\n      planesIn,\n      h,\n      w,\n      planesOut,\n      kernelH,\n      kernelW,\n      strideH,\n      strideW,\n      padT,\n      padL,\n      padB,\n      padR,\n      group,\n      algo,\n      convolutionTransformStrategy,\n      0.05f,\n      0.1f);\n}\n\n} // unnamed namespace\n\nconstexpr size_t kIters = 20;\n\n// TODO(#14383029) cblas_sgemm not yet implemented on limited mobile cases.\n#if !defined(CAFFE2_FB_LIMITED_MOBILE_CAPABILITY)\n\nTEST(NNPACK, Conv_3x3s1) {\n  for (int i = 0; i < kIters; ++i) {\n    runConv(3, 3, 1, 1);\n  }\n}\n\nTEST(NNPACK, Conv_3x3s1_precompute) {\n  for (int i = 0; i < kIters; ++i) {\n    int group = randInt(1, 2);\n    runConv(\n        3,\n        3,\n        1,\n        1,\n        group,\n        \"WINOGRAD\",\n        group * randInt(1, 8),\n        group * randInt(1, 8),\n        1,\n        \"PRECOMPUTE\");\n  }\n}\n\nTEST(NNPACK, Conv_3x3s1_FP16) {\n  for (int i = 0; i < kIters; ++i) {\n    runConv(3, 3, 1, 1, 1, \"WINOGRAD_FP16\");\n  }\n}\n\nTEST(NNPACK, Conv_3x3s1_FP16_precompute) {\n  for (int i = 0; i < kIters; ++i) {\n    int group = randInt(1, 2);\n    runConv(\n        3,\n        3,\n        1,\n        1,\n        group,\n        \"WINOGRAD_FP16\",\n        group * randInt(1, 8),\n        group * randInt(1, 8),\n        1,\n        \"PRECOMPUTE\");\n  }\n}\n\nTEST(NNPACK, Conv_NxNs1) {\n  for (int i = 0; i < kIters; ++i) {\n    int kernel = randInt(2, 10);\n    runConv(kernel, kernel, 1, 1);\n  }\n}\n\nTEST(NNPACK, Conv_1x1s1) {\n  for (int i = 0; i < kIters; ++i) {\n    auto group = randInt(1, 3);\n    auto inChannels = randInt(1, 8) * group;\n    auto outChannels = randInt(1, 8) * group;\n    auto n = 1;\n    runConv(1, 1, 1, 1, group, \"DIRECT\", inChannels, outChannels, n);\n  }\n}\n\nTEST(NNPACK, Conv_1x1s1_precompute) {\n  for (int i = 0; i < kIters; ++i) {\n    auto group = randInt(1, 3);\n    auto inChannels = randInt(1, 8) * group;\n    auto outChannels = randInt(1, 8) * group;\n    auto n = 1;\n    runConv(\n        1, 1, 1, 1, group, \"DIRECT\", inChannels, outChannels, n, \"PRECOMPUTE\");\n  }\n}\n\nTEST(NNPACK, Conv_NxNs_grouped) {\n  for (int i = 0; i < kIters; ++i) {\n    int group = randInt(2, 3);\n    int iC = randInt(1, 6) * group;\n    int oC = randInt(1, 6) * group;\n    int kernel = randInt(2, 10);\n    int n = randInt(1, 2);\n    runConv(kernel, kernel, 1, 1, group, \"\", iC, oC, n);\n  }\n}\n\nTEST(NNPACK, Conv_NxNs_grouped_precompute) {\n  for (int i = 0; i < kIters; ++i) {\n    int group = randInt(2, 3);\n    int iC = randInt(1, 6) * group;\n    int oC = randInt(1, 6) * group;\n    int kernel = randInt(2, 10);\n    int n = randInt(1, 2);\n    runConv(kernel, kernel, 1, 1, group, \"\", iC, oC, n, \"PRECOMPUTE\");\n  }\n}\n\nTEST(NNPACK, Conv_NxNsW) {\n  for (int i = 0; i < 3; ++i) {\n    int kernel = randInt(3, 5);\n    int stride = randInt(1, kernel - 1);\n    runConv(kernel, kernel, stride, stride);\n  }\n}\n\nTEST(NNPACK, Conv_HxWsHxW) {\n  for (int i = 0; i < 3; ++i) {\n    int kernelH = randInt(2, 5);\n    int kernelW = randInt(2, 5);\n    int strideH = randInt(1, kernelH - 1);\n    int strideW = randInt(1, kernelW - 1);\n    runConv(kernelH, kernelW, strideH, strideW);\n  }\n}\n\nTEST(NNPACK, Depthwise3x3Conv) {\n  for (int i = 0; i < kIters; ++i) {\n    int channel = 2;\n    runConv(\n        3, 3, 1, 1, channel, \"DEPTHWISE_3x3\", channel, channel, randInt(1, 2));\n  }\n}\n\n#endif\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/share/contrib/zstd/CMakeLists.txt",
    "content": "file(GLOB_RECURSE tmp *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/share/contrib/zstd/quant_decomp_zstd_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"quant_decomp_zstd_op.h\"\n#include <stdint.h>\n#include <zstd.h>\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n#define REGISTER_TYPE(index, type)                                      \\\n  {                                                                     \\\n    index, [](TensorCPU* tensor_) -> uint8_t* {                         \\\n      return reinterpret_cast<uint8_t*>(tensor_->mutable_data<type>()); \\\n    }                                                                   \\\n  }\n\n// return a mutable pointer to the tensor in uint8_t format, the memory is\n//   allocated based on the type 'type_index'\n// supported type is defined in 'gTypeMapper'\nuint8_t* GetMutableData(int type_index, TensorCPU* tensor) {\n  // see COMP_DATA_TYPE_MAPPER in mutils.py for the mapping\n  static const std::map<int, std::function<uint8_t*(TensorCPU * tensor)>>\n      gTypeMapper = {REGISTER_TYPE(TensorProto::UINT8, uint8_t),\n                     REGISTER_TYPE(TensorProto::UINT16, uint16_t),\n                     REGISTER_TYPE(TensorProto::INT32, int32_t),\n                     REGISTER_TYPE(TensorProto::FLOAT, float)};\n\n  CAFFE_ENFORCE_EQ(\n      gTypeMapper.count(type_index),\n      1,\n      \"Invalid type index \" + caffe2::to_string(type_index) + \".\");\n  return gTypeMapper.at(type_index)(tensor);\n}\n\nconst uint8_t* GetCompressedPtr(const TensorCPU& compressed, size_t* out_size) {\n  CAFFE_ENFORCE(\n      // array of uint8_t\n      compressed.template IsType<uint8_t>() ||\n      // array with one string\n      compressed.template IsType<std::string>());\n\n  if (compressed.template IsType<uint8_t>()) {\n    *out_size = compressed.size();\n    return compressed.data<uint8_t>();\n  }\n\n  // string type\n  CAFFE_ENFORCE_EQ(compressed.size(), 1);\n  auto& str = compressed.data<std::string>()[0];\n  *out_size = str.size();\n  return reinterpret_cast<const uint8_t*>(str.data());\n}\n\n// Deserialize the string to get TensorProtos, storing tensors in compressed\n// format\nTensorProtos GetTensorsProto(const TensorCPU& compressed) {\n  size_t sz;\n  auto* ptr = GetCompressedPtr(compressed, &sz);\n  TensorProtos tensors;\n  CAFFE_ENFORCE(tensors.ParseFromArray(ptr, sz));\n  return tensors;\n}\n\n// Decompress tensor stored in compressed format\n// It is compressed using mutils.compress_data_list()\nvoid Decompress(const TensorProto& compressed, TensorCPU* outDecomp) {\n  vector<TIndex> shape(compressed.dims().begin(), compressed.dims().end());\n  // shape stores the dimensions of data before compression,\n  //   see _compress_data_single() in mutils.py\n  outDecomp->Resize(shape);\n  auto* out_ptr = GetMutableData(compressed.data_type(), outDecomp);\n\n  auto* src = reinterpret_cast<const uint8_t*>(compressed.byte_data().data());\n  size_t comp_size = compressed.byte_data().size();\n  size_t decomp_size = outDecomp->nbytes();\n\n  // call zstd\n  size_t dc_size = ZSTD_decompress(out_ptr, decomp_size, src, comp_size);\n  CAFFE_ENFORCE(!ZSTD_isError(dc_size), ZSTD_getErrorName(dc_size));\n  CAFFE_ENFORCE_EQ(decomp_size, dc_size);\n}\n\n} // namespace\n\nbool QuantDecompZstdOp::RunOnDevice() {\n  const auto& op_compressed = Input(0);\n\n  // Data could be an array of uint_t, or a string\n  CAFFE_ENFORCE(\n      // array of uint8_t\n      op_compressed.template IsType<uint8_t>() ||\n          // array with one string\n          op_compressed.template IsType<std::string>(),\n      op_compressed.meta().name());\n\n  // op_compressed: compressed data, 1d\n  if (op_compressed.template IsType<uint8_t>()) {\n    CAFFE_ENFORCE_EQ(op_compressed.ndim(), 1, op_compressed.ndim());\n  } else {\n    // string type has 0 dimension\n    CAFFE_ENFORCE_EQ(op_compressed.size(), 1, op_compressed.size());\n  }\n\n  auto tensors = GetTensorsProto(op_compressed);\n  CAFFE_ENFORCE_EQ(tensors.protos_size(), OutputSize());\n\n  for (int i = 0; i < OutputSize(); i++) {\n    Decompress(tensors.protos(i), Output(i));\n  }\n\n  return true;\n}\n\nREGISTER_CPU_OPERATOR(QuantDecompZstd, QuantDecompZstdOp);\n\nOPERATOR_SCHEMA(QuantDecompZstd)\n    .NumInputs(1)\n    .NumOutputs(1, INT_MAX)\n    .SetDoc(R\"DOC(\n Decompress a set of tensors that are compressed using zstd.\n The data can be compressed using mutils.compress_data_list(), see\n quant_decomp_op_test.py for an example.\n The number of outputs depended on the input.\n )DOC\")\n    .Input(\n        0,\n        \"compressed\",\n        \"Compressed data in 1d tensor (uint8_t), \"\n        \"or 0d tensor with one element in string type.\"\n        \"The data is compressed using mutils.compress_data_list().\")\n    .Output(0, \"output0\", \"Decompressed data 0\")\n    .Output(1, \"output1\", \"Decompressed data 1 if existed\")\n    .Output(2, \"output2\", \"Decompressed data 2 if existed\")\n    .Output(3, \"outputn\", \"Decompressed data n if existed\");\n\nSHOULD_NOT_DO_GRADIENT(QuantDecompZstd);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/share/contrib/zstd/quant_decomp_zstd_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef QUANT_DECOMP_OP_H_\n#define QUANT_DECOMP_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\n// Decompress a set of tensors compressed using zstd,\n// see quant_decomp_op_test.py for how to compress\nclass QuantDecompZstdOp final : public Operator<CPUContext> {\n public:\n  USE_OPERATOR_FUNCTIONS(CPUContext);\n  QuantDecompZstdOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<CPUContext>(operator_def, ws) {}\n\n  ~QuantDecompZstdOp() {}\n\n  bool RunOnDevice() override;\n};\n\n} // namespace caffe2\n#endif // QUANT_DECOMP_OP_H_\n"
  },
  {
    "path": "caffe2/test/caffe2_gtest_main.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Copyright 2006, Google Inc.\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#include <iostream>\n\n#include <gtest/gtest.h>\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/core/init.h\"\n\nCAFFE2_DEFINE_string(\n    caffe_test_root, \"gen/\", \"The root of the caffe test folder.\");\n\nGTEST_API_ int main(int argc, char **argv) {\n  // std::cout << \"Running main() from gtest_main.cc\\n\";\n  testing::InitGoogleTest(&argc, argv);\n  caffe2::GlobalInit(&argc, &argv);\n  return RUN_ALL_TESTS();\n}\n"
  },
  {
    "path": "caffe2/transforms/CMakeLists.txt",
    "content": "# ---[ Get non-tests\nfile(GLOB tmp *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp})\n\n# exclude test files\nfile(GLOB tmp *_test.cc)\nexclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${tmp})\n\n# ---[ Get tests\nfile(GLOB tmp *_test.cc)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${tmp})\n\n# ---[ Send the lists to the parent scope.\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\n"
  },
  {
    "path": "caffe2/transforms/common_subexpression_elimination.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/transforms/common_subexpression_elimination.h\"\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\nusing transform::Graph;\nusing transform::Node;\n\n// Checks if the node at model_idx and the node at candidate_idx are\n// \"common subexpressions\". That is, do they have the same function, and\n// take in the exact same input. If so, then their function is duplicated.\nbool are_nodes_common(const Graph& g, int model_idx, int candidate_idx) {\n  // We need the candidate operator to match this model_op.\n  const Node& model_node = g.node(model_idx);\n  const Node& candidate_node = g.node(candidate_idx);\n\n  // Types need to match.\n  if (model_node.op.type() != candidate_node.op.type()) {\n    return false;\n  }\n  // Arguments need to match.\n  if (!MatchArguments(model_node.op, candidate_node.op)) {\n    return false;\n  }\n  // Inputs need to match.\n  if (model_node.op.input_size() != candidate_node.op.input_size()) {\n    return false;\n  }\n  // If any input_blob name is different, this is not okay.\n  for (int i = 0; i < model_node.op.input_size(); i++) {\n    if (candidate_node.op.input(i) != model_node.op.input(i)) {\n      return false;\n    }\n  }\n  // Now, we also need to check that each blob comes from the same parent, or\n  // if they are external (isn't in parents). This is equivalent to a\n  // map equality (since parent edges can only contain up to one blob).\n  if (model_node.parents.size() != candidate_node.parents.size() ||\n      !std::equal(\n          model_node.parents.begin(),\n          model_node.parents.end(),\n          candidate_node.parents.begin())) {\n    return false;\n  }\n\n  // Output size have to match too.\n  if (model_node.op.output_size() != candidate_node.op.output_size()) {\n    return false;\n  }\n  return true;\n}\n\nbool CommonSubexpressionEliminationTransform::PatternRule(\n    const Graph& g,\n    const std::vector<int>& subgraph,\n    int idx) {\n  if (subgraph.size() == 0) {\n    if (IsWhitelisted(g.node(idx).op.type()))\n      return true;\n    return false;\n  }\n  return are_nodes_common(g, subgraph.at(0), idx);\n}\n\n// As long as we have matched more than 2 ops, it is worth eliminating.\nbool CommonSubexpressionEliminationTransform::ValidatorRule(\n    const Graph& g,\n    const std::vector<int>& subgraph) {\n  if (subgraph.size() >= 2) {\n    return true;\n  }\n  return false;\n}\n\nbool CommonSubexpressionEliminationTransform::ReplaceRule(\n    const std::vector<int>& subgraph,\n    Graph* g_ptr) {\n  CHECK(g_ptr);\n  auto& g = *g_ptr;\n\n  // We're gonna make a new node, with the same input as all of the ones in\n  // subgraph, but with their combined children.\n  int new_idx = g.size();\n  OperatorDef new_op = g.node(subgraph[0]).op;\n  // We will need to rename the output blobs.\n  new_op.clear_output();\n  for (const auto& blob : g.node(subgraph[0]).op.output()) {\n    new_op.add_output(\"transform/\" + blob);\n  }\n\n  // Need to set up the parents.\n  const auto& new_op_parents = g.node(subgraph[0]).parents;\n\n  for (auto& parent : new_op_parents) {\n    int parent_idx = parent.first;\n\n    // Make the parents acknowledge us as its new child.\n    g.node(parent_idx).children[new_idx] = new_op_parents.at(parent_idx);\n\n    // Make the parents disown all our outdated siblings.\n    for (int i = 0; i < subgraph.size(); i++) {\n      g.node(parent_idx).children.erase(subgraph[i]);\n    }\n  }\n\n  // Add the node now.\n  g.push_node(\n      Node(new_op, true, new_op_parents, std::map<int, std::vector<string>>()));\n\n  // Now, we need to populate the child edges.\n  for (const int x : subgraph) {\n    // Figure out what the subgraph's node's blobs correspond to in new_op\n    // This is easy, since their indices match.\n    std::map<string, string> output_renamings;\n    for (int i = 0; i < new_op.output_size(); i++) {\n      output_renamings[g.node(x).op.output(i)] = g.node(new_idx).op.output(i);\n    }\n\n    // Now, time to add the old node's children to new_op\n    for (auto& child : g.node(x).children) {\n      int child_idx = child.first;\n      std::vector<string> blobs = child.second;\n\n      // rename the old blobs, and use them for our new edge.\n      for (string& blob : blobs) {\n        blob = output_renamings.at(blob);\n      }\n\n      // create this new edge\n      g.node(new_idx).children[child_idx] = blobs;\n      g.node(child_idx).parents[new_idx] = blobs;\n\n      // delete the old edge\n      g.node(child_idx).parents.erase(x);\n\n      // need to rename the inputs of the children too.\n      for (int i = 0; i < g.node(child_idx).op.input_size(); i++) {\n        string blob = g.node(child_idx).op.input(i);\n        if (output_renamings.count(blob) > 0) {\n          g.node(child_idx).op.set_input(i, output_renamings.at(blob));\n        }\n      }\n    }\n  }\n\n  g.DeactivateSubgraph(subgraph);\n\n  return true;\n}\n\nREGISTER_TRANSFORM(\n    CommonSubexpressionElimination,\n    CommonSubexpressionEliminationTransform);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/transforms/common_subexpression_elimination.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n#pragma once\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/transform.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\n/**\n * Common Subexpression Elimination\n *\n * This transforms looks for specific operators (denoted by whitelisted_ops_),\n * and removes unnecessary repetition of that operator.\n *\n * Consider some operator of X, that reads from blob b_ written to by W.\n * X_a and X_b read the output of X. However, another operator Y, is the same\n * type as X, has the same arguments as X, and reads from the same input b_,\n * written to by W. It's output is the same as X. Y_a, Y_b, and Y_c read from Y.\n *\n * Then, we can eliminate the common subexpressions X and Y, and merge them to\n * Z, where X_a, X_b, Y_a, Y_b, and Y_c all read from Z.\n *\n *\n * TODO(benz): Fix the error to not match nodes that write to external output.\n */\nclass CommonSubexpressionEliminationTransform : public Transform {\n public:\n  CommonSubexpressionEliminationTransform() {\n    SetPatternMatchType(SORTED_WRT_EXECUTION_ORDER);\n  }\n\n protected:\n  bool PatternRule(\n      const transform::Graph& g,\n      const std::vector<int>& subgraph,\n      int idx) override;\n  bool ValidatorRule(\n      const transform::Graph& g,\n      const std::vector<int>& subgraph) override;\n  bool ReplaceRule(const std::vector<int>& subgraph, transform::Graph* g_ptr)\n      override;\n\n private:\n  bool IsWhitelisted(string op_type) {\n    return whitelisted_ops_.count(op_type);\n  }\n  std::set<string> whitelisted_ops_ = {\"LearningRate\", \"FC\"};\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/transforms/common_subexpression_elimination_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <google/protobuf/text_format.h>\n#include <gtest/gtest.h>\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/transforms/common_subexpression_elimination.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\nusing transform::Graph;\n\n/**\n *            /--->(FC)-->(Relu)\n *  Before: (FC)-->(FC)-->(Relu)\n *            \\--->(FC)-->(Relu)\n *\n *                    /-->(Relu)\n *  After : (FC)-->(FC)-->(Relu)\n *                    \\-->(Relu)\n *\n */\nTEST(CommonSubexpressionEliminationTest, TestSimple) {\n  NetDef netdef;\n  OperatorDef* op;\n\n  // This operator simply reads input and outputs it.\n  op = AddOp(&netdef, \"FC\", {\"in\", \"w\", \"b\"}, {\"in1\"});\n  op = AddOp(&netdef, \"FC\", {\"in1\", \"w\", \"b\"}, {\"mid1\"});\n  op = AddOp(&netdef, \"FC\", {\"in1\", \"w\", \"b\"}, {\"mid2\"});\n  op = AddOp(&netdef, \"FC\", {\"in1\", \"w\", \"b\"}, {\"mid3\"});\n  op = AddOp(&netdef, \"Relu\", {\"mid1\"}, {\"out1\"});\n  op = AddOp(&netdef, \"Relu\", {\"mid2\"}, {\"out2\"});\n  op = AddOp(&netdef, \"Relu\", {\"mid3\"}, {\"out3\"});\n\n  auto t = TransformRegistry()->Create(\"CommonSubexpressionElimination\");\n  CHECK(t);\n  NetDef transformed_netdef = t->ApplyTo(netdef);\n\n  EXPECT_EQ(t->PatternMatch(Graph(netdef)).size(), 1); // one match\n  EXPECT_EQ(t->PatternMatch(Graph(netdef)).at(0).size(), 3); // 3 ops matched\n  EXPECT_EQ(transformed_netdef.op_size(), 5);\n  EXPECT_EQ(transformed_netdef.op(1).output_size(), 1);\n  EXPECT_EQ(transformed_netdef.op(2).input_size(), 1);\n  EXPECT_EQ(transformed_netdef.op(3).input_size(), 1);\n  EXPECT_EQ(transformed_netdef.op(4).input_size(), 1);\n\n  // make sure op 1 writes to the blob read by 2, 3, and 4.\n  EXPECT_EQ(\n      transformed_netdef.op(1).output(0), transformed_netdef.op(2).input(0));\n  EXPECT_EQ(\n      transformed_netdef.op(1).output(0), transformed_netdef.op(3).input(0));\n  EXPECT_EQ(\n      transformed_netdef.op(1).output(0), transformed_netdef.op(4).input(0));\n}\n\n/**\n * Almost the same as the one above, but it has to be able to merge from\n * external input as well.\n *\n *            ->(FC)-->(Relu)\n *  Before:   ->(FC)-->(Relu)\n *            ->(FC)-->(Relu)\n *\n *                 /-->(Relu)\n *  After :   ->(FC)-->(Relu)\n *                 \\-->(Relu)\n *\n */\nTEST(CommonSubexpressionEliminationTest, TestFromExternal) {\n  NetDef netdef;\n  OperatorDef* op;\n\n  // This operator simply reads input and outputs it.\n  op = AddOp(&netdef, \"FC\", {\"in\", \"w\", \"b\"}, {\"mid1\"});\n  op = AddOp(&netdef, \"FC\", {\"in\", \"w\", \"b\"}, {\"mid2\"});\n  op = AddOp(&netdef, \"FC\", {\"in\", \"w\", \"b\"}, {\"mid3\"});\n  op = AddOp(&netdef, \"Relu\", {\"mid1\"}, {\"out1\"});\n  op = AddOp(&netdef, \"Relu\", {\"mid2\"}, {\"out2\"});\n  op = AddOp(&netdef, \"Relu\", {\"mid3\"}, {\"out3\"});\n\n  auto t = TransformRegistry()->Create(\"CommonSubexpressionElimination\");\n  CHECK(t);\n  NetDef transformed_netdef = t->ApplyTo(netdef);\n\n  EXPECT_EQ(t->PatternMatch(Graph(netdef)).size(), 1); // one match\n  EXPECT_EQ(t->PatternMatch(Graph(netdef)).at(0).size(), 3); // 3 ops matched\n  EXPECT_EQ(transformed_netdef.op_size(), 4);\n  EXPECT_EQ(transformed_netdef.op(0).output_size(), 1);\n  EXPECT_EQ(transformed_netdef.op(1).input_size(), 1);\n  EXPECT_EQ(transformed_netdef.op(2).input_size(), 1);\n  EXPECT_EQ(transformed_netdef.op(3).input_size(), 1);\n\n  EXPECT_EQ(\n      transformed_netdef.op(0).output(0), transformed_netdef.op(1).input(0));\n  EXPECT_EQ(\n      transformed_netdef.op(0).output(0), transformed_netdef.op(2).input(0));\n  EXPECT_EQ(\n      transformed_netdef.op(0).output(0), transformed_netdef.op(3).input(0));\n}\n\n} // namespace\n\n} // namespace Caffe2\n"
  },
  {
    "path": "caffe2/transforms/conv_to_nnpack_transform.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/transforms/conv_to_nnpack_transform.h\"\n\nnamespace caffe2 {\n\nREGISTER_TRANSFORM(ConvToNNPack, ConvToNNPackTransform);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/transforms/conv_to_nnpack_transform.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/transforms/single_op_transform.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\nclass ConvToNNPackTransform : public SingleOpTransform {\n protected:\n  // Specify what the op needs to be to match the pattern.\n  bool MatchOperator(const OperatorDef& op) override {\n    return (\n        op.type() == \"Conv\" && op.device_option().device_type() == CPU &&\n        op.engine() != \"NNPACK\");\n  }\n\n  // Specify how the operator should be replaced.\n  void ReplaceOperator(OperatorDef* op) override {\n    op->set_engine(\"NNPACK\");\n  }\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/transforms/conv_to_nnpack_transform_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <google/protobuf/text_format.h>\n#include <gtest/gtest.h>\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/transforms/conv_to_nnpack_transform.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\nusing transform::Graph;\n\nTEST(ConvToNNPackTest, TestSimple) {\n  NetDef netdef;\n  OperatorDef* op;\n  op = AddOp(&netdef, \"Conv\", {\"in\"}, {\"out\"});\n  op = AddOp(&netdef, \"Relu\", {\"out\"}, {\"out\"});\n  op = AddOp(&netdef, \"Conv\", {\"out\"}, {\"out\"}); // if not CPU, won't transform\n  op->mutable_device_option()->set_device_type(CUDA);\n  op = AddOp(&netdef, \"Relu\", {\"out\"}, {\"out\"});\n  op = AddOp(&netdef, \"Conv\", {\"out\"}, {\"out\"});\n  op->set_engine(\"NNPACK\"); // does not need to be transformed\n  op = AddOp(&netdef, \"Relu\", {\"out\"}, {\"out\"});\n  op = AddOp(&netdef, \"Conv\", {\"out\"}, {\"out\"});\n  op = AddOp(&netdef, \"Relu\", {\"out\"}, {\"out\"});\n\n  auto t = TransformRegistry()->Create(\"ConvToNNPack\");\n  NetDef transformed_netdef = t->ApplyTo(netdef);\n\n  int nnpack_count = 0;\n  for (auto& op : transformed_netdef.op()) {\n    if (op.type() == \"Conv\" && op.device_option().device_type() == CPU) {\n      EXPECT_EQ(op.engine(), \"NNPACK\");\n      nnpack_count++;\n    }\n  }\n  EXPECT_EQ(nnpack_count, 3);\n  EXPECT_EQ(t->PatternMatch(Graph(netdef)).size(), 2); // should get 2 matches\n}\n\n} // namespace\n\n} // namespace Caffe2\n"
  },
  {
    "path": "caffe2/transforms/pattern_net_transform.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/transforms/pattern_net_transform.h\"\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\n// First, single source traverse through the netdef.\n// This ensures all newly ordered are reachable from their prefix subset\n// Outputs a permutation of the operators.\nstd::vector<int> PatternNetTransform::GetPatternTraversalOrder(\n    const transform::Graph& graph) {\n  std::vector<bool> visited(graph.size(), false);\n  std::vector<int> ordered_ops;\n  std::queue<int> q;\n  if (graph.size() > 0) {\n    q.push(0);\n    ordered_ops.push_back(0);\n    visited[0] = true;\n  }\n  while (!q.empty()) {\n    int idx = q.front();\n    q.pop();\n    for (const auto& edge : graph.node(idx).children) {\n      int x = edge.first;\n      if (!visited[x]) {\n        q.push(x);\n        ordered_ops.push_back(x);\n        visited[x] = true;\n      }\n    }\n    for (const auto& edge : graph.node(idx).parents) {\n      int x = edge.first;\n      if (!visited[x]) {\n        q.push(x);\n        ordered_ops.push_back(x);\n        visited[x] = true;\n      }\n    }\n  }\n  CAFFE_ENFORCE(\n      ordered_ops.size() == graph.size(), \"Pattern graph must be connected.\");\n  return ordered_ops;\n}\n\nbool compare_ops(\n    const OperatorDef& p_op,\n    const OperatorDef& g_op,\n    bool arg_match) {\n  // must specify a type for pattern operators\n  CAFFE_ENFORCE(\n      p_op.has_type(), \"Types must be specified for all pattern operators.\");\n  if (!MatchStrings(p_op.type(), g_op.type())) {\n    return false;\n  }\n  // ensure number of inputs are the same\n  if (p_op.input().size() != g_op.input().size()) {\n    return false;\n  }\n\n  // ensure number of outputs are the same\n  if (p_op.output().size() != g_op.output().size()) {\n    return false;\n  }\n\n  if (p_op.has_device_option()) {\n    if (!g_op.has_device_option() ||\n        p_op.device_option().device_type() !=\n            g_op.device_option().device_type()) {\n      return false;\n    }\n  }\n\n  // make sure engine is the same (if specified in pattern)\n  if (p_op.has_engine() && !MatchStrings(p_op.engine(), g_op.engine())) {\n    return false;\n  }\n  // If argument_match is specified, make sure those are the same.\n  if (arg_match) {\n    if (!MatchArguments(p_op, g_op)) {\n      return false;\n    }\n  }\n  return true;\n}\n\n// g.node(subgraph[i]) should match p_.node(ordered_ops_[i])\n// g.node(g_idx) should match p_.node(p_idx)\nbool PatternNetTransform::PatternRule(\n    const transform::Graph& g,\n    const std::vector<int>& subgraph,\n    int g_idx) {\n  if (subgraph.size() >= ordered_ops_.size()) {\n    return false;\n  }\n  int p_idx = ordered_ops_[subgraph.size()];\n\n  if (!compare_ops(p_.node(p_idx).op, g.node(g_idx).op, argument_match_)) {\n    return false;\n  }\n\n  // Let's say ordered_ops_ is [0, 2, 1], with 0 -> 2 being an edge\n  // When we try to match onto the second element, let's say our\n  // subgraph so far is [4], with it trying to become [4, 5].\n  // Then, we need to show that since 0 -> 2 is an edge is ordered_ops_,\n  // 4 must be a direct parent of 5 in the subgraph\n  // (the indices must match).\n  // Similarly, assume there is an edge from 1 -> 2 in p_.\n  // When trying to match [4, 5] to [4, 5, 7], we must verify that\n  // there exists an edge from 7 -> 5 in G.\n  for (const auto& edge : p_.node(p_idx).parents) {\n    int parent = edge.first;\n    // g_idx doesn't have parent in subgraph that p_[p_idx] has\n    // inverse_ops_ gets the index of a p_idx inside of ordered_ops_.\n    if (inverse_ops_[parent] < subgraph.size() &&\n        g.node(g_idx).parents.count(subgraph[inverse_ops_[parent]]) == 0) {\n      return false;\n    }\n  }\n\n  for (const auto& edge : p_.node(p_idx).children) {\n    int child = edge.first;\n    if (inverse_ops_[child] < subgraph.size() &&\n        g.node(g_idx).children.count(subgraph[inverse_ops_[child]]) == 0) {\n      return false;\n    }\n  }\n  return true;\n}\n\nbool PatternNetTransform::ValidatorRule(\n    const transform::Graph& g,\n    const std::vector<int>& subgraph) {\n  // Due to strict PatternRule, it suffices to simply check for size\n  return subgraph.size() == p_.size();\n}\n\nbool PatternNetTransform::ReplaceRule(\n    const std::vector<int>& match,\n    transform::Graph* g_ptr) {\n  CHECK(g_ptr);\n  auto& g = *g_ptr;\n\n  ssa_id_++;\n\n  // Map of PatternNet blob name to Matched blob name.\n  // Figures out how to rename the pattern_net to make the replacement fit.\n  std::unordered_map<string, string> external_renaming;\n\n  // Figure out blob renamings\n  for (int i = 0; i < match.size(); i++) {\n    int g_idx = match[i];\n    int p_idx = ordered_ops_[i];\n    for (int j = 0; j < p_.node(p_idx).op.input().size(); j++) {\n      string p_blob = p_.node(p_idx).op.input(j);\n      string g_blob = g.node(g_idx).op.input(j);\n      if (p_.external_input().count(p_blob)) {\n        external_renaming[p_blob] = g_blob;\n      }\n    }\n    for (int j = 0; j < p_.node(p_idx).op.output().size(); j++) {\n      string p_blob = p_.node(p_idx).op.output(j);\n      string g_blob = g.node(g_idx).op.output(j);\n      if (p_.external_output().count(p_blob)) {\n        external_renaming[p_blob] = g_blob;\n      }\n    }\n  }\n\n  auto input_list = g.GetSubgraphInput(match);\n  auto output_list = g.GetSubgraphOutput(match);\n\n  g.DeactivateSubgraph(match);\n\n  int offset = g.size();\n\n  g.resize_nodes(offset + r_.size());\n\n  // Append all the new operators.\n  for (int i = 0; i < r_.size(); i++) {\n    int new_node_idx = offset + i;\n\n    OperatorDef new_op = r_.node(i).op;\n\n    new_op.clear_input();\n    new_op.clear_output();\n    // Stitch Input from external graph into replaced subgraph\n    for (const auto& blob : r_.node(i).op.input()) {\n      if (external_renaming.count(blob)) {\n        string new_blob = external_renaming[blob];\n        new_op.add_input(new_blob);\n\n        // binary searches for new_blob amongst input list.\n        auto it = std::lower_bound(\n            input_list.begin(), input_list.end(), std::make_pair(new_blob, -1));\n\n        // if the input came from the graph (instead of G's external input)\n        for (; it < input_list.end() && it->first == new_blob; it++) {\n          int parent = it->second;\n          g.node(parent).children[new_node_idx].push_back(new_blob);\n          g.node(new_node_idx).parents[parent].push_back(new_blob);\n        }\n      } else {\n        new_op.add_input(TransformBlobWrapper(blob));\n      }\n    }\n    // Stitch Output from replaced subgraph to external graph.\n    for (const auto& blob : r_.node(i).op.output()) {\n      if (external_renaming.count(blob)) {\n        string new_blob = external_renaming[blob];\n        new_op.add_output(new_blob);\n\n        // binary searches for new_blob amongst input list.\n        auto it = std::lower_bound(\n            output_list.begin(),\n            output_list.end(),\n            std::make_pair(new_blob, -1));\n\n        // if the output goes to the graph (instead of G's external output)\n        for (; it < output_list.end() && it->first == new_blob; it++) {\n          int child = it->second;\n          g.node(child).parents[new_node_idx].push_back(new_blob);\n          g.node(new_node_idx).children[child].push_back(new_blob);\n        }\n      } else {\n        new_op.add_output(TransformBlobWrapper(blob));\n      }\n    }\n\n    // Connect all internal edges within replace graph\n    for (const auto& edge : r_.node(i).parents) {\n      int parent = edge.first;\n      int new_node_parent = offset + parent;\n      const auto& blobs = edge.second;\n      for (const string& blob : blobs) {\n        g.node(new_node_idx)\n            .parents[new_node_parent]\n            .push_back(TransformBlobWrapper(blob));\n      }\n    }\n\n    for (const auto& edge : r_.node(i).children) {\n      int child = edge.first;\n      int new_node_child = offset + child;\n      const auto& blobs = edge.second;\n      for (const string& blob : blobs) {\n        g.node(offset + i)\n            .children[new_node_child]\n            .push_back(TransformBlobWrapper(blob));\n      }\n    }\n\n    g.node(new_node_idx).op = new_op;\n    g.node(new_node_idx).active = true;\n  }\n  return true;\n}\n\n} // namespace Caffe2\n"
  },
  {
    "path": "caffe2/transforms/pattern_net_transform.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/transform.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\n/**\n * PatternNetTransform allows you to create transforms using a simple\n * interface.\n *\n * Simply provide a Pattern NetDef and a Replace NetDef,\n * and this Transform will find subgraphs which fit the pattern net,\n * and replace it with the replace net.\n */\nclass PatternNetTransform : public Transform {\n public:\n  PatternNetTransform(const NetDef& pattern_net, const NetDef& replace_net)\n      : p_(transform::Graph(pattern_net)), r_(transform::Graph(replace_net)) {\n    // external input and output must match!\n    CAFFE_ENFORCE(\n        p_.external_input() == r_.external_input(),\n        \"External inputs do not match!\");\n    CAFFE_ENFORCE(\n        p_.external_output() == r_.external_output(),\n        \"External outputs do not match!\");\n    ordered_ops_ = GetPatternTraversalOrder(p_);\n    inverse_ops_.resize(ordered_ops_.size());\n    for (int i = 0; i < ordered_ops_.size(); i++) {\n      inverse_ops_[ordered_ops_[i]] = i;\n    }\n  }\n\n  void EnableArgumentMatching() {\n    argument_match_ = true;\n  }\n\n  void DisableArgumentMatching() {\n    argument_match_ = false;\n  }\n\n protected:\n  /**\n   * We want to the final result of subgraph to match the PatternNet in the\n   * order of ordered_ops, operator by operator.\n   *\n   * [[[ ie. g.node(subgraph[i]) should match p.node(ordered_ops[i]) ]]]\n   *\n   * PatternRule for PatternNetTransform does the following:\n   *\n   * When trying to insert node idx into subgraph[p_idx],\n   * we need to see if the edges between index and the\n   * subgraph match the edges between p[ordered_ops[idx]]\n   * and p[ordered_ops[0]...ordered_ops[p_idx-1]].\n   */\n  bool PatternRule(\n      const transform::Graph& g,\n      const std::vector<int>& subgraph,\n      int idx) override;\n  /**\n   * ValidatorRule for PatternNetTransform does the following:\n   *\n   * Checks if the size of subgraph and p.size() are the same. That's it!\n   */\n  bool ValidatorRule(\n      const transform::Graph& g,\n      const std::vector<int>& subgraph) override;\n  /**\n   * ReplaceRule for PatternNet Transform does the following:\n   *\n   * 1) Figure out edge renamings for edges going into/out of the subgraph.\n   * That is, for each blob in the pattern graph, what is it called in the\n   * matched subgraph?\n   *\n   * 2) Remove the matched subgraph.\n   *\n   * 3) Append the replace graph's operators to the graph's operators, and use\n   *    the renamings to rename the blob names.\n   *\n   * 4) Create all the children/parent relationships within the replaced graph,\n   *    and stitch together the inputs and outputs into the rest of the graph,\n   *    matching the removed subgraph.\n   */\n  bool ReplaceRule(const std::vector<int>& subgraph, transform::Graph* g_ptr)\n      override;\n\n private:\n  /**\n   * This returns a permutation of the Pattern Net's operators.\n   * The permutation satisfies this property:\n   *    - For any index i, order(i) is a neighbor of some node from\n   *      {order(1), ..., order(i-1)}.\n   *\n   * Why is this important? Consider the following case:\n   * PatternNet: 0 ---> 2 <--- 1\n   *\n   * When we have matched onto [0], and trying to add [1] to our subgraph,\n   * we cannot, since PatternMatch only considers neighbors of the current\n   * subgraph as a candidate next node.\n   *\n   * Therefore, we must present the subgraph in an order such that each node is\n   * a neighbor of its prefix subgraph. One ordering for the above example is\n   * [0, 2, 1].\n   */\n  std::vector<int> GetPatternTraversalOrder(const transform::Graph& g);\n\n  // Graph of Pattern NetDef\n  transform::Graph p_;\n\n  // The Traversal Order of the Pattern Net's Operators\n  // This is a permutation of the numbers from {0, ..., p.size()-1}\n  std::vector<int> ordered_ops_;\n\n  // The Inverse of the Traversal Order of the Pattern Net's Operators\n  // That is, inverse_ops[ordered_ops[i]] == i is always true.\n  std::vector<int> inverse_ops_;\n\n  // Graph of Replace NetDef\n  transform::Graph r_;\n\n  // This flag determines if the transform will match operator arguments.\n  bool argument_match_ = false;\n\n  const string TransformBlobWrapper(const string& blob_name) {\n    return \"transform/\" + blob_name + \"_\" + caffe2::to_string(ssa_id_);\n  }\n\n  int ssa_id_ = 0;\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/transforms/pattern_net_transform_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <google/protobuf/text_format.h>\n#include <gtest/gtest.h>\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/transforms/pattern_net_transform.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\nusing transform::Graph;\n\nstatic std::atomic<int> counter;\n\nclass DummyCounterOp final : public OperatorBase {\n public:\n  using OperatorBase::OperatorBase;\n  bool Run(int /* unused */) override {\n    counter.fetch_add(1);\n    return true;\n  }\n};\n\nREGISTER_CPU_OPERATOR(DummyCounterOp1, DummyCounterOp);\nREGISTER_CUDA_OPERATOR(DummyCounterOp1, DummyCounterOp);\n\nOPERATOR_SCHEMA(DummyCounterOp1)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\n\nREGISTER_CPU_OPERATOR(DummyCounterOp2, DummyCounterOp);\nREGISTER_CUDA_OPERATOR(DummyCounterOp2, DummyCounterOp);\n\nOPERATOR_SCHEMA(DummyCounterOp2)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\n\nREGISTER_CPU_OPERATOR(DummyCounterOp3, DummyCounterOp);\nREGISTER_CUDA_OPERATOR(DummyCounterOp3, DummyCounterOp);\n\nOPERATOR_SCHEMA(DummyCounterOp3)\n    .NumInputs(0, INT_MAX)\n    .NumOutputs(0, INT_MAX)\n    .AllowInplace({{0, 0}, {1, 1}});\n\n/**\n * P = ---> (Op1) ---> (Op2) --->\n *\n * R = ---> (Op3) ---> (Op3) --->\n */\nTEST(PatternNetTransformTest, TestGenerateTransform) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n\n  NetDef netdef;\n  AddOp(&netdef, \"DummyCounterOp1\", {\"in\"}, {\"mid1\"});\n  AddOp(&netdef, \"DummyCounterOp2\", {\"mid1\"}, {\"mid2\"});\n  AddOp(&netdef, \"DummyCounterOp1\", {\"mid2\"}, {\"mid3\"});\n  AddOp(&netdef, \"DummyCounterOp2\", {\"mid3\"}, {\"out\"});\n\n  NetDef pdef;\n  AddOp(&pdef, \"DummyCounterOp1\", {\"in\"}, {\"mid\"});\n  AddOp(&pdef, \"DummyCounterOp2\", {\"mid\"}, {\"out\"});\n\n  NetDef rdef;\n  AddOp(&rdef, \"DummyCounterOp3\", {\"in\"}, {\"new_mid\"});\n  AddOp(&rdef, \"DummyCounterOp3\", {\"new_mid\"}, {\"out\"});\n\n  PatternNetTransform t(pdef, rdef);\n\n  // test pattern match\n  Graph g(netdef);\n\n  auto matches = t.PatternMatch(g);\n  EXPECT_EQ(matches.size(), 2);\n\n  t.ReplacePattern(matches, &g);\n\n  EXPECT_EQ(g.size(), 8);\n  for (int i = 0; i < 4; i++) {\n    EXPECT_FALSE(g.is_node_active(i));\n  }\n  for (int i = 4; i < 8; i++) {\n    EXPECT_TRUE(g.is_node_active(i));\n  }\n\n  EXPECT_TRUE(g.node(4).children.count(5));\n  EXPECT_TRUE(g.node(5).children.count(6));\n  EXPECT_TRUE(g.node(6).children.count(7));\n\n  for (int i = 4; i < 8; i++) {\n    EXPECT_EQ(g.node(i).op.input().size(), 1);\n    EXPECT_EQ(g.node(i).op.output().size(), 1);\n  }\n\n  NetDef replaced_netdef = g.GetNetDef();\n\n  EXPECT_EQ(replaced_netdef.op().size(), 4);\n  EXPECT_EQ(replaced_netdef.op(0).type(), \"DummyCounterOp3\");\n  EXPECT_EQ(replaced_netdef.op(1).type(), \"DummyCounterOp3\");\n  EXPECT_EQ(replaced_netdef.op(2).type(), \"DummyCounterOp3\");\n  EXPECT_EQ(replaced_netdef.op(3).type(), \"DummyCounterOp3\");\n}\n\n/**\n * P = ---> (Op1) ---> (Op2) --->\n *\n * R = ---> (Op3) ---> (Op3) --->\n */\nTEST(PatternNetTransformTest, TestRepeatedTransform) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n\n  NetDef netdef;\n  AddOp(&netdef, \"DummyCounterOp1\", {\"in\"}, {\"out\"});\n  AddOp(&netdef, \"DummyCounterOp2\", {\"out\"}, {\"out\"});\n  for (int i = 0; i < 99; i++) {\n    AddOp(&netdef, \"DummyCounterOp1\", {\"out\"}, {\"out\"});\n    AddOp(&netdef, \"DummyCounterOp2\", {\"out\"}, {\"out\"});\n  }\n\n  NetDef pdef;\n  AddOp(&pdef, \"DummyCounterOp1\", {\"in\"}, {\"mid\"});\n  AddOp(&pdef, \"DummyCounterOp2\", {\"mid\"}, {\"out\"});\n\n  NetDef rdef;\n  AddOp(&rdef, \"DummyCounterOp3\", {\"in\"}, {\"new_mid\"});\n  AddOp(&rdef, \"DummyCounterOp3\", {\"new_mid\"}, {\"out\"});\n\n  PatternNetTransform t(pdef, rdef);\n\n  // test pattern match\n  Graph g(netdef);\n\n  auto matches = t.PatternMatch(g);\n  EXPECT_EQ(matches.size(), 100);\n\n  t.ReplacePattern(matches, &g);\n  NetDef replaced_netdef = g.GetNetDef();\n\n  EXPECT_EQ(replaced_netdef.op_size(), 200);\n  for (int i = 0; i < 200; i++) {\n    EXPECT_EQ(replaced_netdef.op(i).type(), \"DummyCounterOp3\");\n  }\n\n  unique_ptr<NetBase> net = CreateNet(replaced_netdef, &ws);\n  counter.exchange(0);\n  net.get()->Run();\n  EXPECT_EQ(200, counter.load());\n}\n\n/**\n * P = ---> (Op1) ---> (Op3) ---> (Op2) --->\n *            |------> (Op3) -------|\n *\n * R = ---> (Op1) --------------> (Op3) --->\n *          |_(Op3)-->(Op3)-->(Op2)_|\n *\n */\nTEST(PatternNetTransformTest, TestHardTransform) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n\n  NetDef netdef;\n  // Segment 1 (differs from P because of type)\n  AddOp(&netdef, \"DummyCounterOp1\", {\"in\"}, {\"mid1a_1\", \"mid1b_1\"});\n  AddOp(&netdef, \"DummyCounterOp2\", {\"mid1a_1\"}, {\"mid2a_1\"});\n  AddOp(&netdef, \"DummyCounterOp3\", {\"mid1b_1\"}, {\"mid2b_1\"});\n  AddOp(&netdef, \"DummyCounterOp3\", {\"mid2a_1\", \"mid2b_1\"}, {\"out_1\"});\n\n  // Segment 2 (differs from P because of structure)\n  AddOp(\n      &netdef, \"DummyCounterOp1\", {\"out_1\"}, {\"mid1a_2\", \"mid1b_2\", \"mid1c_2\"});\n  AddOp(&netdef, \"DummyCounterOp3\", {\"mid1a_2\"}, {\"mid2a_2\"});\n  AddOp(&netdef, \"DummyCounterOp3\", {\"mid1b_2\"}, {\"mid2b_2\"});\n  AddOp(&netdef, \"DummyCounterOp3\", {\"mid1c_2\"}, {\"mid2c_2\"});\n  AddOp(\n      &netdef, \"DummyCounterOp2\", {\"mid2a_2\", \"mid2b_2\", \"mid2c_2\"}, {\"out_2\"});\n\n  // Segment 3\n  AddOp(&netdef, \"DummyCounterOp1\", {\"out_2\"}, {\"mid1a_3\", \"mid1b_3\"});\n  AddOp(&netdef, \"DummyCounterOp3\", {\"mid1a_3\"}, {\"mid2a_3\"});\n  AddOp(&netdef, \"DummyCounterOp3\", {\"mid1b_3\"}, {\"mid2b_3\"});\n  AddOp(&netdef, \"DummyCounterOp2\", {\"mid2a_3\", \"mid2b_3\"}, {\"out\"});\n\n  NetDef pdef;\n  // Should only match Segment 3\n  AddOp(&pdef, \"DummyCounterOp1\", {\"sub_in\"}, {\"mid1a\", \"mid1b\"});\n  AddOp(&pdef, \"DummyCounterOp3\", {\"mid1a\"}, {\"mid2a\"});\n  AddOp(&pdef, \"DummyCounterOp3\", {\"mid1b\"}, {\"mid2b\"});\n  AddOp(&pdef, \"DummyCounterOp2\", {\"mid2a\", \"mid2b\"}, {\"sub_out\"});\n\n  NetDef rdef;\n  AddOp(&rdef, \"DummyCounterOp1\", {\"sub_in\"}, {\"mid1a\", \"mid1b\"});\n  AddOp(&rdef, \"DummyCounterOp3\", {\"mid1b\"}, {\"mid2b\"});\n  AddOp(&rdef, \"DummyCounterOp3\", {\"mid2b\"}, {\"mid3b\"});\n  AddOp(&rdef, \"DummyCounterOp2\", {\"mid3b\"}, {\"mid4b\"});\n  AddOp(&rdef, \"DummyCounterOp3\", {\"mid1a\", \"mid4b\"}, {\"sub_out\"});\n\n  PatternNetTransform t(pdef, rdef);\n  Graph g(netdef);\n  EXPECT_EQ(g.size(), 13);\n\n  auto matches = t.PatternMatch(g);\n  EXPECT_EQ(matches.size(), 1);\n\n  t.ReplacePattern(matches, &g);\n  EXPECT_EQ(g.size(), 18);\n\n  NetDef replaced_netdef = g.GetNetDef();\n  EXPECT_EQ(replaced_netdef.op_size(), 14);\n  unique_ptr<NetBase> net = CreateNet(replaced_netdef, &ws);\n  counter.exchange(0);\n  net.get()->Run();\n  EXPECT_EQ(14, counter.load());\n}\n\nTEST(PatternNetTransformTest, TestGeneralStringMatching) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n\n  NetDef pdef;\n  AddOp(&pdef, \"*\", {\"in\"}, {\"mid\"});\n  AddOp(&pdef, \"DummyOp1|DummyOp2\", {\"mid\"}, {\"mid2\"});\n  AddOp(&pdef, \"DummyOp3\", {\"mid2\"}, {\"out\"});\n\n  NetDef rdef;\n  AddOp(&rdef, \"DummyOp1\", {\"in\"}, {\"out\"});\n\n  NetDef netdef;\n  AddOp(&netdef, \"DummyOp1\", {\"in\"}, {\"mid\"});\n  AddOp(&netdef, \"DummyOp3\", {\"mid\"}, {\"mid\"}); // start of match 1\n  AddOp(&netdef, \"DummyOp2\", {\"mid\"}, {\"mid\"});\n  AddOp(&netdef, \"DummyOp3\", {\"mid\"}, {\"mid\"}); // end of match 1\n  AddOp(&netdef, \"DummyOp1\", {\"mid\"}, {\"mid\"}); // start of match 2\n  AddOp(&netdef, \"DummyOp1\", {\"mid\"}, {\"mid\"});\n  AddOp(&netdef, \"DummyOp3\", {\"mid\"}, {\"mid\"}); // end of match 2\n  AddOp(&netdef, \"DummyOp3\", {\"mid\"}, {\"out\"});\n\n  PatternNetTransform t(pdef, rdef);\n  transform::Graph g(netdef);\n  auto matches = t.PatternMatch(g);\n  EXPECT_EQ(matches.size(), 2);\n}\n\nTEST(PatternNetTransformTest, TestDeviceOptionMatching) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n\n  NetDef pdef;\n  auto op = AddOp(&pdef, \"DummyOp1\", {\"in\"}, {\"out\"});\n  op->mutable_device_option()->set_device_type(CPU);\n\n  NetDef rdef;\n  op = AddOp(&rdef, \"DummyOp1\", {\"in\"}, {\"out\"});\n  op->mutable_device_option()->set_device_type(CUDA);\n\n  NetDef netdef;\n  op = AddOp(&netdef, \"DummyOp1\", {\"in\"}, {\"mid\"});\n  op->mutable_device_option()->set_device_type(CPU);\n  op = AddOp(&netdef, \"DummyOp1\", {\"mid\"}, {\"mid\"}); // should not match\n  op->mutable_device_option()->set_device_type(CUDA);\n  op = AddOp(&netdef, \"DummyOp1\", {\"mid\"}, {\"out\"});\n  op->mutable_device_option()->set_device_type(CPU);\n\n  PatternNetTransform t(pdef, rdef);\n  transform::Graph g(netdef);\n  auto matches = t.PatternMatch(g);\n  EXPECT_EQ(matches.size(), 2);\n\n  NetDef transformed_net = t.ApplyTo(netdef);\n  for (const auto& opdef : transformed_net.op()) {\n    EXPECT_TRUE(opdef.has_device_option());\n    EXPECT_EQ(opdef.device_option().device_type(), CUDA);\n  }\n}\n\nTEST(PatternNetTransformTest, TestEngineMatching) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n\n  NetDef pdef;\n  auto op = AddOp(&pdef, \"DummyOp1\", {\"in\"}, {\"out\"});\n  op->set_engine(\"FakeEng1|FakeEng2\");\n\n  NetDef rdef;\n  op = AddOp(&rdef, \"DummyOp1\", {\"in\"}, {\"out\"});\n  op->set_engine(\"FakeEng3\");\n\n  NetDef netdef;\n  op = AddOp(&netdef, \"DummyOp1\", {\"in\"}, {\"mid\"});\n  op->set_engine(\"FakeEng1\");\n  op = AddOp(&netdef, \"DummyOp1\", {\"mid\"}, {\"mid\"});\n  op->set_engine(\"FakeEng2\");\n  op = AddOp(&netdef, \"DummyOp1\", {\"mid\"}, {\"out\"}); // should not match\n  op->set_engine(\"FakeEng3\");\n\n  PatternNetTransform t(pdef, rdef);\n  transform::Graph g(netdef);\n  auto matches = t.PatternMatch(g);\n  EXPECT_EQ(matches.size(), 2);\n\n  NetDef transformed_net = t.ApplyTo(netdef);\n  for (const auto& opdef : transformed_net.op()) {\n    EXPECT_EQ(opdef.engine(), \"FakeEng3\");\n  }\n}\n\nTEST(PatternNetTransformTest, TestSingularArgumentMatching) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n\n  NetDef pdef;\n  auto op = AddOp(&pdef, \"Conv\", {\"in\"}, {\"out\"});\n  {\n    auto arg = op->add_arg();\n    arg->set_name(\"stride_w\");\n    arg->set_i(3);\n  }\n  {\n    auto arg = op->add_arg();\n    arg->set_name(\"stride_h\");\n    arg->set_i(3);\n  }\n\n  NetDef rdef;\n  op = AddOp(&rdef, \"Conv\", {\"in\"}, {\"out\"});\n  {\n    auto arg = op->add_arg();\n    arg->set_name(\"stride_w\");\n    arg->set_i(5);\n  }\n  {\n    auto arg = op->add_arg();\n    arg->set_name(\"stride_h\");\n    arg->set_i(5);\n  }\n\n  NetDef netdef;\n  op = AddOp(&netdef, \"Conv\", {\"in\"}, {\"mid\"}); // Will match\n  {\n    auto arg = op->add_arg();\n    arg->set_name(\"stride_w\");\n    arg->set_i(3);\n  }\n  {\n    auto arg = op->add_arg();\n    arg->set_name(\"stride_h\");\n    arg->set_i(3);\n  }\n  op = AddOp(&netdef, \"Conv\", {\"mid\"}, {\"mid\"}); // Has bad args, will not match\n  {\n    auto arg = op->add_arg();\n    arg->set_name(\"stride_w\");\n    arg->set_i(4);\n  }\n  {\n    auto arg = op->add_arg();\n    arg->set_name(\"stride_h\");\n    arg->set_i(4);\n  }\n  op = AddOp(&netdef, \"Conv\", {\"mid\"}, {\"mid\"}); // Has no args, will not match\n  op = AddOp(&netdef, \"Conv\", {\"mid\"}, {\"out\"}); // Has different names\n  {\n    auto arg = op->add_arg();\n    arg->set_name(\"yolo\");\n    arg->set_i(3);\n  }\n  {\n    auto arg = op->add_arg();\n    arg->set_name(\"swag\");\n    arg->set_i(3);\n  }\n  op = AddOp(&netdef, \"Conv\", {\"in\"}, {\"mid\"}); // Will match\n  {\n    auto arg = op->add_arg();\n    arg->set_name(\"stride_w\");\n    arg->set_i(3);\n  }\n  {\n    auto arg = op->add_arg();\n    arg->set_name(\"stride_h\");\n    arg->set_i(3);\n  }\n\n  PatternNetTransform t(pdef, rdef);\n  t.EnableArgumentMatching();\n  transform::Graph g(netdef);\n  auto matches = t.PatternMatch(g);\n  EXPECT_EQ(matches.size(), 2);\n  NetDef transformed_net = t.ApplyTo(netdef);\n  EXPECT_EQ(transformed_net.op(0).arg(0).name(), \"stride_w\");\n  EXPECT_EQ(transformed_net.op(0).arg(0).i(), 5);\n  EXPECT_EQ(transformed_net.op(0).arg(1).name(), \"stride_h\");\n  EXPECT_EQ(transformed_net.op(0).arg(1).i(), 5);\n\n  EXPECT_EQ(transformed_net.op(4).arg(0).name(), \"stride_w\");\n  EXPECT_EQ(transformed_net.op(4).arg(0).i(), 5);\n  EXPECT_EQ(transformed_net.op(4).arg(1).name(), \"stride_h\");\n  EXPECT_EQ(transformed_net.op(4).arg(1).i(), 5);\n}\n\n/**\n *           |--(Op2)--|\n * P = --->(Op1)----->(Op3)--->\n *           |--(Op2)--|\n *\n * R = ---> (Op2) --->\n *\n *                |--(Op2)--|\n *           -->(Op1)----->(Op3)---\n *           |    |--(Op2)--|     |\n * G = ---> (Op1)                (Op3) --->\n *           |    |--(Op2)--|     |\n *           -->(Op1)----->(Op3)--\n *                |--(Op2)--|\n *\n * In this test, the two \"parallel\" modules have intersecting execution orders.\n * We wish to test that the pattern match can still detect the two modules,\n * separately.\n *\n * Furthermore, we will apply the transform to G, TWICE.\n * It should reduce G to a single operator.\n */\nTEST(PatternNetTransformTest, TestNonStrictTopographicTransform) {\n  Workspace ws;\n  ws.CreateBlob(\"in\");\n\n  NetDef netdef;\n  // Head\n  AddOp(&netdef, \"DummyCounterOp1\", {\"in\"}, {\"in_1\", \"in_2\"});\n\n  // 2 intertwined segments, each matching P. No strict ordering.\n  AddOp(&netdef, \"DummyCounterOp1\", {\"in_1\"}, {\"m1_1\", \"m2_1\"});\n  AddOp(&netdef, \"DummyCounterOp1\", {\"in_2\"}, {\"m1_2\", \"m2_2\"});\n  AddOp(&netdef, \"DummyCounterOp2\", {\"m1_1\"}, {\"out1_1\"});\n  AddOp(&netdef, \"DummyCounterOp2\", {\"m1_2\"}, {\"out1_2\"});\n  AddOp(&netdef, \"DummyCounterOp2\", {\"m2_1\"}, {\"out2_1\"});\n  AddOp(&netdef, \"DummyCounterOp2\", {\"m2_2\"}, {\"out2_2\"});\n  AddOp(&netdef, \"DummyCounterOp3\", {\"out1_1\", \"out2_1\"}, {\"out1\"});\n  AddOp(&netdef, \"DummyCounterOp3\", {\"out1_2\", \"out2_2\"}, {\"out2\"});\n\n  // Tail\n  AddOp(&netdef, \"DummyCounterOp3\", {\"out1\", \"out2\"}, {\"out\"});\n\n  NetDef pdef;\n  AddOp(&pdef, \"DummyCounterOp1\", {\"myin\"}, {\"mid1a\", \"mid1b\"});\n  AddOp(&pdef, \"DummyCounterOp2\", {\"mid1a\"}, {\"mid2a\"});\n  AddOp(&pdef, \"DummyCounterOp2\", {\"mid1b\"}, {\"mid2b\"});\n  AddOp(&pdef, \"DummyCounterOp3\", {\"mid2a\", \"mid2b\"}, {\"myout\"});\n\n  NetDef rdef;\n  AddOp(&rdef, \"DummyCounterOp2\", {\"myin\"}, {\"myout\"});\n\n  PatternNetTransform t(pdef, rdef);\n\n  NetDef replaced_netdef = t.ApplyTo(netdef);\n  EXPECT_EQ(replaced_netdef.op_size(), 4);\n  unique_ptr<NetBase> net = CreateNet(replaced_netdef, &ws);\n  counter.exchange(0);\n  net.get()->Run();\n  EXPECT_EQ(4, counter.load());\n\n  // apply the transform again\n  // the entire net should get transformed this time\n  NetDef double_transformed_net = t.ApplyTo(replaced_netdef);\n  EXPECT_EQ(double_transformed_net.op_size(), 1);\n}\n\n/**\n *      --->(Op1)----->(Op2)--->\n *            |          ^\n * P =        |----------|\n *            |          v\n *      --->(Op1)----->(Op2)--->\n *\n * R =  ---> (Op3) --->\n *\n * G = P -> P\n *\n * In this test, we fuse a subgraph with two inputs and two outputs, into one\n * operator.\n *\n * This will ensure that we can allow a single edge to represent\n * multiple blob names (the input and output of R are both 2 blobs).\n *\n * This will also ensure that patternmatch can traverse \"backwards\", from a node\n * to its parent.\n *\n * Furthermore, this tests for repeat matches, since matching on either of the\n * first two Op1 nodes will produce a match, but they are identical.\n * So, the pattern should match 4 times, but only be replaced twice.\n */\nTEST(PatternNetTransformTest, TestMultiInputOutputTransform) {\n  Workspace ws;\n  ws.CreateBlob(\"in1\");\n  ws.CreateBlob(\"in2\");\n\n  NetDef netdef;\n  AddOp(&netdef, \"DummyCounterOp1\", {\"in1\"}, {\"in1\"}); // has 2 children\n  AddOp(&netdef, \"DummyCounterOp1\", {\"in2\"}, {\"in2\"}); // has 2 children\n  AddOp(&netdef, \"DummyCounterOp2\", {\"in1\", \"in2\"}, {\"mid1\"});\n  AddOp(&netdef, \"DummyCounterOp2\", {\"in1\", \"in2\"}, {\"mid2\"});\n  AddOp(&netdef, \"DummyCounterOp1\", {\"mid1\"}, {\"mid1\"}); // has 2 children\n  AddOp(&netdef, \"DummyCounterOp1\", {\"mid2\"}, {\"mid2\"}); // has 2 children\n  AddOp(&netdef, \"DummyCounterOp2\", {\"mid1\", \"mid2\"}, {\"out1\"});\n  AddOp(&netdef, \"DummyCounterOp2\", {\"mid1\", \"mid2\"}, {\"out2\"});\n\n  NetDef pdef;\n  AddOp(&pdef, \"DummyCounterOp1\", {\"subin1\"}, {\"subin1\"}); // has 2 children\n  AddOp(&pdef, \"DummyCounterOp1\", {\"subin2\"}, {\"subin2\"}); // has 2 children\n  AddOp(&pdef, \"DummyCounterOp2\", {\"subin1\", \"subin2\"}, {\"subout1\"});\n  AddOp(&pdef, \"DummyCounterOp2\", {\"subin1\", \"subin2\"}, {\"subout2\"});\n\n  NetDef rdef;\n  AddOp(&rdef, \"DummyCounterOp3\", {\"subin1\", \"subin2\"}, {\"subout1\", \"subout2\"});\n\n  PatternNetTransform t(pdef, rdef);\n  Graph g(netdef);\n\n  NetDef replaced_netdef = t.ApplyTo(netdef);\n  EXPECT_EQ(replaced_netdef.op_size(), 2);\n  unique_ptr<NetBase> net = CreateNet(replaced_netdef, &ws);\n  counter.exchange(0);\n  net.get()->Run();\n  EXPECT_EQ(2, counter.load());\n}\n\n} // namespace\n\n} // namespace Caffe2\n"
  },
  {
    "path": "caffe2/transforms/single_op_transform.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/transforms/single_op_transform.h\"\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/net.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\nusing transform::Graph;\n\nbool SingleOpTransform::PatternRule(\n    const Graph& g,\n    const std::vector<int>& subgraph,\n    int idx) {\n  if (subgraph.size() == 0) {\n    return MatchOperator(g.node(idx).op);\n  }\n  return false;\n}\n\nbool SingleOpTransform::ValidatorRule(\n    const Graph& g,\n    const std::vector<int>& subgraph) {\n  if (subgraph.size() == 1) {\n    return true;\n  }\n  return false;\n}\n\nbool SingleOpTransform::ReplaceRule(\n    const std::vector<int>& subgraph,\n    Graph* g_ptr) {\n  CHECK(g_ptr);\n  auto& g = *g_ptr;\n  ReplaceOperator(&(g.node(subgraph[0]).op));\n  return true;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/transforms/single_op_transform.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/transform.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/proto_utils.h\"\n\nnamespace caffe2 {\n\n/**\n * Single Op Transform Base class\n *\n * A transform which is applied to a single node, in place.\n *\n * Transforms which derive from SingleOpTransform need to override:\n * ReplaceOperator and MatchOperator.\n */\nclass SingleOpTransform : public Transform {\n protected:\n  bool PatternRule(\n      const transform::Graph& g,\n      const std::vector<int>& subgraph,\n      int idx) override;\n  bool ValidatorRule(\n      const transform::Graph& g,\n      const std::vector<int>& subgraph) override;\n  bool ReplaceRule(const std::vector<int>& subgraph, transform::Graph* g_ptr)\n      override;\n\n  // Specify what the op needs to be to match the pattern.\n  virtual bool MatchOperator(const OperatorDef& op) = 0;\n\n  // Specify how the operator should be replaced.\n  virtual void ReplaceOperator(OperatorDef* op) = 0;\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/CMakeLists.txt",
    "content": "# ---[ GPU files\n# ------[ cuDNN\nfile(GLOB_RECURSE tmp *_cudnn.cc)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# ------[ general GPU\nfile(GLOB_RECURSE tmp *_gpu.cc)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# ------[ CUDA sources\nfile(GLOB_RECURSE tmp *.cu)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n# exclude test files\nfile(GLOB_RECURSE tmp *_test.cc)\nexclude(Caffe2_GPU_SRCS \"${Caffe2_GPU_SRCS}\" ${tmp})\n\n# ---[ CPU files.\nfile(GLOB_RECURSE tmp *.cc)\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp})\n# exclude test files and gpu files\nfile(GLOB_RECURSE tmp *_test.cc)\nexclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${tmp})\nexclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${Caffe2_GPU_SRCS})\n\n# ---[ threadpool/pthreadpool* is a local modification of the NNPACK\n# pthreadpool with a very similar interface. Neither NNPACK, nor this\n# thread pool supports Windows.\nif (MSVC)\n  file(GLOB_RECURSE tmp pthreadpool*)\n  exclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${tmp})\nendif()\n\n# ---[ GPU test files\nfile(GLOB_RECURSE tmp *_gpu_test.cc)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} ${tmp})\n\n# ---[ CPU test files\nfile(GLOB_RECURSE tmp *_test.cc)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${tmp})\nexclude(Caffe2_CPU_TEST_SRCS \"${Caffe2_CPU_TEST_SRCS}\" ${Caffe2_GPU_TEST_SRCS})\n\n# ---[ Send the lists to the parent scope.\nset(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\nset(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\nset(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\n\n# ---[ Printing stack traces requires dladdr\n\n"
  },
  {
    "path": "caffe2/utils/GpuBitonicSort.cuh",
    "content": "#ifndef CAFFE2_UTILS_GPU_BITONIC_SORT_H_\n#define CAFFE2_UTILS_GPU_BITONIC_SORT_H_\n\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/GpuDefs.cuh\"\n\nnamespace caffe2 {\n\n// Returns true if the given integer type is a power-of-2 (positive only)\n// Note(jiayq): windows reported an error per\n//     https://github.com/caffe2/caffe2/issues/997\n// and as a result will make it a macro.\n#ifdef _MSC_VER\n#define integerIsPowerOf2(v) ((v) && !((v) & ((v) - 1)))\n#else // _MSC_VER\ntemplate <typename T>\nconstexpr bool integerIsPowerOf2(T v) {\n  return (v && !(v & (v - 1)));\n}\n#endif // _MSC_VER\n\n/// The maximum in-block bitonic sort we support\nconstexpr int kMaxBitonicSortSize = 4096;\n\ntemplate <typename T>\n__device__ inline void swapVars(T& t1, T& t2) {\n  T tmp = t1;\n  t1 = t2;\n  t2 = tmp;\n}\n\ntemplate <typename Comparator, typename K, typename V>\n__device__ inline void bitonicSwap(K& kA, V& vA,\n                                   K& kB, V& vB,\n                                   bool dir,\n                                   const Comparator& comp) {\n  bool swap = comp(kA, vA, kB, vB);\n  if (swap == dir) {\n    swapVars(kA, kB);\n    swapVars(vA, vB);\n  }\n};\n\ntemplate <typename Comparator, typename K, typename V,\n          int Power2SortSize,\n          int ThreadsPerBlock>\n__device__ inline void bitonicSort(K* keys,\n                                   V* values,\n                                   const Comparator& comp) {\n  static_assert(Power2SortSize <= kMaxBitonicSortSize,\n                \"sort size <= 4096 only supported\");\n  // Assume the sort is taking place in shared memory\n  // static_assert(Power2SortSize * (sizeof(K) + sizeof(V)) < 32768,\n  //               \"sort data too large (>32768 bytes)\");\n  static_assert(integerIsPowerOf2(Power2SortSize),\n                \"sort size must be power of 2\");\n  static_assert(integerIsPowerOf2(ThreadsPerBlock),\n                \"threads in block must be power of 2\");\n\n  // If what we are sorting is too small, then not all threads\n  // participate\n  constexpr int numThreadsForSort = Power2SortSize / 2;\n  constexpr bool allThreads = numThreadsForSort >= ThreadsPerBlock;\n\n  // If what we are sorting is too large, then threads must loop more\n  // than once\n  constexpr int loopPerThread =\n    allThreads ? numThreadsForSort / ThreadsPerBlock : 1;\n\n#pragma unroll\n  for (int size = 2; size < Power2SortSize; size *= 2) {\n\n#pragma unroll\n    for (int stride = size / 2; stride > 0; stride /= 2) {\n\n#pragma unroll\n      for (int loop = 0; loop < loopPerThread; ++loop) {\n        int threadId = loop * ThreadsPerBlock + threadIdx.x;\n        bool flag = ((threadId & (size / 2)) != 0);\n\n        int pos = 2 * threadId - (threadId & (stride - 1));\n\n        if (allThreads || (threadId < numThreadsForSort)) {\n          bitonicSwap<Comparator, K, V>(\n            keys[pos], values[pos],\n            keys[pos + stride], values[pos + stride],\n            flag, comp);\n        }\n\n        __syncthreads();\n      }\n    }\n  }\n\n#pragma unroll\n  for (int stride = Power2SortSize / 2; stride > 0; stride /= 2) {\n\n#pragma unroll\n    for (int loop = 0; loop < loopPerThread; ++loop) {\n      int threadId = loop * ThreadsPerBlock + threadIdx.x;\n\n      int pos = 2 * threadId - (threadId & (stride - 1));\n\n      if (allThreads || (threadId < numThreadsForSort)) {\n        bitonicSwap<Comparator, K, V>(\n          keys[pos], values[pos],\n          keys[pos + stride], values[pos + stride],\n          false, comp);\n      }\n\n      __syncthreads();\n    }\n  }\n}\n\ntemplate <typename Comparator, typename K, typename V, int Power2SortSize>\n__device__ inline void warpBitonicSort(K* keys,\n                                       V* values,\n                                       const Comparator& comp) {\n  // Smaller sorts should use a warp shuffle sort\n  static_assert(Power2SortSize > kWarpSize,\n                \"sort not large enough\");\n  static_assert(integerIsPowerOf2(Power2SortSize),\n                \"sort size must be power of 2\");\n  static_assert(Power2SortSize <= kMaxBitonicSortSize,\n                \"sort size <= 4096 only supported\");\n\n  // If what we are sorting is too large, then lanes must loop more\n  // than once\n  constexpr int loopPerThread = (Power2SortSize / 2) / kWarpSize;\n  int laneId = getLaneId();\n\n#pragma unroll\n  for (int size = 2; size < Power2SortSize; size *= 2) {\n\n#pragma unroll\n    for (int stride = size / 2; stride > 0; stride /= 2) {\n\n#pragma unroll\n      for (int loop = 0; loop < loopPerThread; ++loop) {\n        int threadId = loop * kWarpSize + laneId;\n        bool flag = ((threadId & (size / 2)) != 0);\n\n        int pos = 2 * threadId - (threadId & (stride - 1));\n\n        bitonicSwap<Comparator, K, V>(\n          keys[pos], values[pos],\n          keys[pos + stride], values[pos + stride],\n          flag, comp);\n\n        __threadfence_block();\n      }\n    }\n  }\n\n#pragma unroll\n  for (int stride = Power2SortSize / 2; stride > 0; stride /= 2) {\n\n#pragma unroll\n    for (int loop = 0; loop < loopPerThread; ++loop) {\n      int threadId = loop * kWarpSize + laneId;\n\n      int pos = 2 * threadId - (threadId & (stride - 1));\n\n      bitonicSwap<Comparator, K, V>(\n        keys[pos], values[pos],\n        keys[pos + stride], values[pos + stride],\n        false, comp);\n\n      __threadfence_block();\n    }\n  }\n}\n\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_UTILS_GPU_BITONIC_SORT_H_\n"
  },
  {
    "path": "caffe2/utils/GpuDefs.cuh",
    "content": "#ifndef CAFFE2_UTILS_GPU_DEFS_H_\n#define CAFFE2_UTILS_GPU_DEFS_H_\n\n#include <cuda_runtime.h>\n\nnamespace caffe2 {\n\n// Static definition of GPU warp size for unrolling and code generation\n\n#ifdef __CUDA_ARCH__\n#if __CUDA_ARCH__ <= 700\nconstexpr int kWarpSize = 32;\n#else\n#error Unknown __CUDA_ARCH__; please define parameters for compute capability\n#endif // __CUDA_ARCH__ types\n#endif // __CUDA_ARCH__\n\n#ifndef __CUDA_ARCH__\n// dummy value for host compiler\nconstexpr int kWarpSize = 32;\n#endif // !__CUDA_ARCH__\n\n//\n// Interfaces to PTX instructions for which there appears to be no\n// intrinsic\n//\n\ntemplate <typename T>\nstruct Bitfield {};\n\ntemplate <>\nstruct Bitfield<unsigned int> {\n  static __device__ __forceinline__\n  unsigned int getBitfield(unsigned int val, int pos, int len) {\n    unsigned int ret;\n    asm(\"bfe.u32 %0, %1, %2, %3;\" : \"=r\"(ret) : \"r\"(val), \"r\"(pos), \"r\"(len));\n    return ret;\n  }\n\n  static __device__ __forceinline__\n  unsigned int setBitfield(unsigned int val, unsigned int toInsert, int pos, int len) {\n    unsigned int ret;\n    asm(\"bfi.b32 %0, %1, %2, %3, %4;\" :\n        \"=r\"(ret) : \"r\"(toInsert), \"r\"(val), \"r\"(pos), \"r\"(len));\n    return ret;\n  }\n};\n\ntemplate <>\nstruct Bitfield<unsigned long long int> {\n  static __device__ __forceinline__\n  unsigned long long int getBitfield(unsigned long long int val, int pos, int len) {\n    unsigned long long int ret;\n    asm(\"bfe.u64 %0, %1, %2, %3;\" : \"=l\"(ret) : \"l\"(val), \"r\"(pos), \"r\"(len));\n    return ret;\n  }\n\n  static __device__ __forceinline__\n  unsigned long long int setBitfield(unsigned long long int val, unsigned long long int toInsert, int pos, int len) {\n    unsigned long long int ret;\n    asm(\"bfi.b64 %0, %1, %2, %3, %4;\" :\n        \"=l\"(ret) : \"l\"(toInsert), \"l\"(val), \"r\"(pos), \"r\"(len));\n    return ret;\n  }\n};\n\n__device__ __forceinline__ int getLaneId() {\n  int laneId;\n  asm(\"mov.s32 %0, %laneid;\" : \"=r\"(laneId) );\n  return laneId;\n}\n\n__device__ __forceinline__ unsigned getLaneMaskLt() {\n  unsigned mask;\n  asm(\"mov.u32 %0, %%lanemask_lt;\" : \"=r\"(mask));\n  return mask;\n}\n\n__device__ __forceinline__ unsigned getLaneMaskLe() {\n  unsigned mask;\n  asm(\"mov.u32 %0, %%lanemask_le;\" : \"=r\"(mask));\n  return mask;\n}\n\n__device__ __forceinline__ unsigned getLaneMaskGt() {\n  unsigned mask;\n  asm(\"mov.u32 %0, %%lanemask_gt;\" : \"=r\"(mask));\n  return mask;\n}\n\n__device__ __forceinline__ unsigned getLaneMaskGe() {\n  unsigned mask;\n  asm(\"mov.u32 %0, %%lanemask_ge;\" : \"=r\"(mask));\n  return mask;\n}\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_UTILS_GPU_DEFS_H_\n"
  },
  {
    "path": "caffe2/utils/GpuScanUtils.cuh",
    "content": "#ifndef CAFFE2_UTILS_GPU_SCAN_UTILS_H_\n#define CAFFE2_UTILS_GPU_SCAN_UTILS_H_\n\n#include \"caffe2/utils/GpuDefs.cuh\"\n\nnamespace caffe2 {\n\n// from the cutorch library; can probably be replaced with their CUB\n// equivalents\n// Collection of in-kernel scan / prefix sum utilities\n\n// Inclusive prefix sum using shared memory\ntemplate <typename T, bool KillWARDependency, class BinaryFunction>\n__device__ void inclusivePrefixScan(T* smem, T in, T* out, BinaryFunction binop) {\n  // FIXME: this is a slow, simple implementation; need up/down sweep,\n  // prevent smem conflicts\n  smem[threadIdx.x] = in;\n\n  __syncthreads();\n\n  for (int offset = 1; offset < blockDim.x; offset *= 2) {\n    T val = 0;\n\n    if (threadIdx.x >= offset) {\n      val = binop(smem[threadIdx.x - offset], smem[threadIdx.x]);\n    }\n\n    __syncthreads();\n    if (threadIdx.x >= offset) {\n      smem[threadIdx.x] = val;\n    }\n\n    __syncthreads();\n  }\n\n  *out = smem[threadIdx.x];\n\n  // Prevent write-after-read dependencies on smem usage above if necessary\n  if (KillWARDependency) {\n    __syncthreads();\n  }\n}\n\n// Exclusive prefix sum using shared memory\ntemplate <typename T, bool KillWARDependency, class BinaryFunction>\n__device__ void exclusivePrefixScan(T* smem, T in, T* out, T* carry, BinaryFunction binop) {\n  // FIXME: crappy implementation\n  // We kill write-after-read dependencies separately below, hence the `false`\n  inclusivePrefixScan<T, false, BinaryFunction>(smem, in, out, binop);\n\n  *out -= in;\n  *carry = smem[blockDim.x - 1];\n\n  // Prevent write-after-read dependencies on smem usage above if necessary\n  if (KillWARDependency) {\n    __syncthreads();\n  }\n}\n\n// Inclusive prefix sum for binary vars using intra-warp voting +\n// shared memory\ntemplate <typename T, bool KillWARDependency, class BinaryFunction>\n__device__ void inclusiveBinaryPrefixScan(T* smem, bool in, T* out, BinaryFunction binop) {\n  // Within-warp, we use warp voting.\n#if CUDA_VERSION >= 9000\n  T vote = __ballot_sync(__activemask(), in);\n#else\n  T vote = __ballot(in);\n#endif\n\n  T index = __popc(getLaneMaskLe() & vote);\n  T carry = __popc(vote);\n\n  int warp = threadIdx.x / 32;\n\n  // Per each warp, write out a value\n  if (getLaneId() == 0) {\n    smem[warp] = carry;\n  }\n\n  __syncthreads();\n\n  // Sum across warps in one thread. This appears to be faster than a\n  // warp shuffle scan for CC 3.0+\n  if (threadIdx.x == 0) {\n    int current = 0;\n    for (int i = 0; i < blockDim.x / 32; ++i) {\n      T v = smem[i];\n      smem[i] = binop(smem[i], current);\n      current = binop(current, v);\n    }\n  }\n\n  __syncthreads();\n\n  // load the carry from the preceding warp\n  if (warp >= 1) {\n    index = binop(index, smem[warp - 1]);\n  }\n\n  *out = index;\n\n  if (KillWARDependency) {\n    __syncthreads();\n  }\n}\n\n// Exclusive prefix sum for binary vars using intra-warp voting +\n// shared memory\ntemplate <typename T, bool KillWARDependency, class BinaryFunction>\n__device__ void exclusiveBinaryPrefixScan(T* smem, bool in, T* out, T* carry, BinaryFunction binop) {\n  inclusiveBinaryPrefixScan<T, false, BinaryFunction>(smem, in, out, binop);\n\n  // Inclusive to exclusive\n  *out -= (T) in;\n\n  // The outgoing carry for all threads is the last warp's sum\n  *carry = smem[(blockDim.x / 32) - 1];\n\n  if (KillWARDependency) {\n    __syncthreads();\n  }\n}\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_UTILS_GPU_SCAN_UTILS_H_\n"
  },
  {
    "path": "caffe2/utils/cast.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <caffe2/utils/proto_utils.h>\n\nnamespace caffe2 {\n\nnamespace cast {\n\ninline TensorProto_DataType GetCastDataType(const ArgumentHelper& helper, std::string arg) {\n  TensorProto_DataType to;\n  if (helper.HasSingleArgumentOfType<string>(arg)) {\n#ifndef CAFFE2_USE_LITE_PROTO\n    string s = helper.GetSingleArgument<string>(arg, \"float\");\n    std::transform(s.begin(), s.end(), s.begin(), ::toupper);\n    CAFFE_ENFORCE(TensorProto_DataType_Parse(s, &to), \"Unknown 'to' argument: \", s);\n#else\n    CAFFE_THROW(\"String cast op not supported\");\n#endif\n  } else {\n    to = static_cast<TensorProto_DataType>(\n        helper.GetSingleArgument<int>(arg, TensorProto_DataType_FLOAT));\n  }\n  return to;\n}\n\n};  // namespace cast\n\n};  // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/cblas.h",
    "content": "// This is the exact cblas.h header file, placed here purely in order to get\n// the enums.\n\n#include \"caffe2/core/macros.h\"\n\n#ifndef CBLAS_H\n#ifdef CAFFE2_USE_MKL\n#include <mkl_cblas.h>\n#else  // CAFFE2_USE_MKL\n\n#ifndef CBLAS_ENUM_DEFINED_H\n   #define CBLAS_ENUM_DEFINED_H\n   enum CBLAS_ORDER {CblasRowMajor=101, CblasColMajor=102 };\n   enum CBLAS_TRANSPOSE {CblasNoTrans=111, CblasTrans=112, CblasConjTrans=113,\n                         AtlasConj=114};\n   enum CBLAS_UPLO  {CblasUpper=121, CblasLower=122};\n   enum CBLAS_DIAG  {CblasNonUnit=131, CblasUnit=132};\n   enum CBLAS_SIDE  {CblasLeft=141, CblasRight=142};\n#endif\n\n#ifndef CBLAS_ENUM_ONLY\n#define CBLAS_H\n#define CBLAS_INDEX int\n\nint cblas_errprn(int ierr, int info, char *form, ...);\nvoid cblas_xerbla(int p, const char *rout, const char *form, ...);\n\n/*\n * ===========================================================================\n * Prototypes for level 1 BLAS functions (complex are recast as routines)\n * ===========================================================================\n */\nfloat  cblas_sdsdot(const int N, const float alpha, const float *X,\n                    const int incX, const float *Y, const int incY);\ndouble cblas_dsdot(const int N, const float *X, const int incX, const float *Y,\n                   const int incY);\nfloat  cblas_sdot(const int N, const float  *X, const int incX,\n                  const float  *Y, const int incY);\ndouble cblas_ddot(const int N, const double *X, const int incX,\n                  const double *Y, const int incY);\n/*\n * Functions having prefixes Z and C only\n */\nvoid   cblas_cdotu_sub(const int N, const void *X, const int incX,\n                       const void *Y, const int incY, void *dotu);\nvoid   cblas_cdotc_sub(const int N, const void *X, const int incX,\n                       const void *Y, const int incY, void *dotc);\n\nvoid   cblas_zdotu_sub(const int N, const void *X, const int incX,\n                       const void *Y, const int incY, void *dotu);\nvoid   cblas_zdotc_sub(const int N, const void *X, const int incX,\n                       const void *Y, const int incY, void *dotc);\n\n\n/*\n * Functions having prefixes S D SC DZ\n */\nfloat  cblas_snrm2(const int N, const float *X, const int incX);\nfloat  cblas_sasum(const int N, const float *X, const int incX);\n\ndouble cblas_dnrm2(const int N, const double *X, const int incX);\ndouble cblas_dasum(const int N, const double *X, const int incX);\n\nfloat  cblas_scnrm2(const int N, const void *X, const int incX);\nfloat  cblas_scasum(const int N, const void *X, const int incX);\n\ndouble cblas_dznrm2(const int N, const void *X, const int incX);\ndouble cblas_dzasum(const int N, const void *X, const int incX);\n\n\n/*\n * Functions having standard 4 prefixes (S D C Z)\n */\nCBLAS_INDEX cblas_isamax(const int N, const float  *X, const int incX);\nCBLAS_INDEX cblas_idamax(const int N, const double *X, const int incX);\nCBLAS_INDEX cblas_icamax(const int N, const void   *X, const int incX);\nCBLAS_INDEX cblas_izamax(const int N, const void   *X, const int incX);\n\n/*\n * ===========================================================================\n * Prototypes for level 1 BLAS routines\n * ===========================================================================\n */\n\n/*\n * Routines with standard 4 prefixes (s, d, c, z)\n */\nvoid cblas_sswap(const int N, float *X, const int incX,\n                 float *Y, const int incY);\nvoid cblas_scopy(const int N, const float *X, const int incX,\n                 float *Y, const int incY);\nvoid cblas_saxpy(const int N, const float alpha, const float *X,\n                 const int incX, float *Y, const int incY);\nvoid catlas_saxpby(const int N, const float alpha, const float *X,\n                  const int incX, const float beta, float *Y, const int incY);\nvoid catlas_sset\n   (const int N, const float alpha, float *X, const int incX);\n\nvoid cblas_dswap(const int N, double *X, const int incX,\n                 double *Y, const int incY);\nvoid cblas_dcopy(const int N, const double *X, const int incX,\n                 double *Y, const int incY);\nvoid cblas_daxpy(const int N, const double alpha, const double *X,\n                 const int incX, double *Y, const int incY);\nvoid catlas_daxpby(const int N, const double alpha, const double *X,\n                  const int incX, const double beta, double *Y, const int incY);\nvoid catlas_dset\n   (const int N, const double alpha, double *X, const int incX);\n\nvoid cblas_cswap(const int N, void *X, const int incX,\n                 void *Y, const int incY);\nvoid cblas_ccopy(const int N, const void *X, const int incX,\n                 void *Y, const int incY);\nvoid cblas_caxpy(const int N, const void *alpha, const void *X,\n                 const int incX, void *Y, const int incY);\nvoid catlas_caxpby(const int N, const void *alpha, const void *X,\n                  const int incX, const void *beta, void *Y, const int incY);\nvoid catlas_cset\n   (const int N, const void *alpha, void *X, const int incX);\n\nvoid cblas_zswap(const int N, void *X, const int incX,\n                 void *Y, const int incY);\nvoid cblas_zcopy(const int N, const void *X, const int incX,\n                 void *Y, const int incY);\nvoid cblas_zaxpy(const int N, const void *alpha, const void *X,\n                 const int incX, void *Y, const int incY);\nvoid catlas_zaxpby(const int N, const void *alpha, const void *X,\n                  const int incX, const void *beta, void *Y, const int incY);\nvoid catlas_zset\n   (const int N, const void *alpha, void *X, const int incX);\n\n\n/*\n * Routines with S and D prefix only\n */\nvoid cblas_srotg(float *a, float *b, float *c, float *s);\nvoid cblas_srotmg(float *d1, float *d2, float *b1, const float b2, float *P);\nvoid cblas_srot(const int N, float *X, const int incX,\n                float *Y, const int incY, const float c, const float s);\nvoid cblas_srotm(const int N, float *X, const int incX,\n                float *Y, const int incY, const float *P);\n\nvoid cblas_drotg(double *a, double *b, double *c, double *s);\nvoid cblas_drotmg(double *d1, double *d2, double *b1, const double b2, double *P);\nvoid cblas_drot(const int N, double *X, const int incX,\n                double *Y, const int incY, const double c, const double s);\nvoid cblas_drotm(const int N, double *X, const int incX,\n                double *Y, const int incY, const double *P);\n\n\n/*\n * Routines with S D C Z CS and ZD prefixes\n */\nvoid cblas_sscal(const int N, const float alpha, float *X, const int incX);\nvoid cblas_dscal(const int N, const double alpha, double *X, const int incX);\nvoid cblas_cscal(const int N, const void *alpha, void *X, const int incX);\nvoid cblas_zscal(const int N, const void *alpha, void *X, const int incX);\nvoid cblas_csscal(const int N, const float alpha, void *X, const int incX);\nvoid cblas_zdscal(const int N, const double alpha, void *X, const int incX);\n\n/*\n * Extra reference routines provided by ATLAS, but not mandated by the standard\n */\nvoid cblas_crotg(void *a, void *b, void *c, void *s);\nvoid cblas_zrotg(void *a, void *b, void *c, void *s);\nvoid cblas_csrot(const int N, void *X, const int incX, void *Y, const int incY,\n                 const float c, const float s);\nvoid cblas_zdrot(const int N, void *X, const int incX, void *Y, const int incY,\n                 const double c, const double s);\n\n/*\n * ===========================================================================\n * Prototypes for level 2 BLAS\n * ===========================================================================\n */\n\n/*\n * Routines with standard 4 prefixes (S, D, C, Z)\n */\nvoid cblas_sgemv(const enum CBLAS_ORDER Order,\n                 const enum CBLAS_TRANSPOSE TransA, const int M, const int N,\n                 const float alpha, const float *A, const int lda,\n                 const float *X, const int incX, const float beta,\n                 float *Y, const int incY);\nvoid cblas_sgbmv(const enum CBLAS_ORDER Order,\n                 const enum CBLAS_TRANSPOSE TransA, const int M, const int N,\n                 const int KL, const int KU, const float alpha,\n                 const float *A, const int lda, const float *X,\n                 const int incX, const float beta, float *Y, const int incY);\nvoid cblas_strmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const float *A, const int lda,\n                 float *X, const int incX);\nvoid cblas_stbmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const int K, const float *A, const int lda,\n                 float *X, const int incX);\nvoid cblas_stpmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const float *Ap, float *X, const int incX);\nvoid cblas_strsv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const float *A, const int lda, float *X,\n                 const int incX);\nvoid cblas_stbsv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const int K, const float *A, const int lda,\n                 float *X, const int incX);\nvoid cblas_stpsv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const float *Ap, float *X, const int incX);\n\nvoid cblas_dgemv(const enum CBLAS_ORDER Order,\n                 const enum CBLAS_TRANSPOSE TransA, const int M, const int N,\n                 const double alpha, const double *A, const int lda,\n                 const double *X, const int incX, const double beta,\n                 double *Y, const int incY);\nvoid cblas_dgbmv(const enum CBLAS_ORDER Order,\n                 const enum CBLAS_TRANSPOSE TransA, const int M, const int N,\n                 const int KL, const int KU, const double alpha,\n                 const double *A, const int lda, const double *X,\n                 const int incX, const double beta, double *Y, const int incY);\nvoid cblas_dtrmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const double *A, const int lda,\n                 double *X, const int incX);\nvoid cblas_dtbmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const int K, const double *A, const int lda,\n                 double *X, const int incX);\nvoid cblas_dtpmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const double *Ap, double *X, const int incX);\nvoid cblas_dtrsv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const double *A, const int lda, double *X,\n                 const int incX);\nvoid cblas_dtbsv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const int K, const double *A, const int lda,\n                 double *X, const int incX);\nvoid cblas_dtpsv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const double *Ap, double *X, const int incX);\n\nvoid cblas_cgemv(const enum CBLAS_ORDER Order,\n                 const enum CBLAS_TRANSPOSE TransA, const int M, const int N,\n                 const void *alpha, const void *A, const int lda,\n                 const void *X, const int incX, const void *beta,\n                 void *Y, const int incY);\nvoid cblas_cgbmv(const enum CBLAS_ORDER Order,\n                 const enum CBLAS_TRANSPOSE TransA, const int M, const int N,\n                 const int KL, const int KU, const void *alpha,\n                 const void *A, const int lda, const void *X,\n                 const int incX, const void *beta, void *Y, const int incY);\nvoid cblas_ctrmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const void *A, const int lda,\n                 void *X, const int incX);\nvoid cblas_ctbmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const int K, const void *A, const int lda,\n                 void *X, const int incX);\nvoid cblas_ctpmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const void *Ap, void *X, const int incX);\nvoid cblas_ctrsv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const void *A, const int lda, void *X,\n                 const int incX);\nvoid cblas_ctbsv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const int K, const void *A, const int lda,\n                 void *X, const int incX);\nvoid cblas_ctpsv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const void *Ap, void *X, const int incX);\n\nvoid cblas_zgemv(const enum CBLAS_ORDER Order,\n                 const enum CBLAS_TRANSPOSE TransA, const int M, const int N,\n                 const void *alpha, const void *A, const int lda,\n                 const void *X, const int incX, const void *beta,\n                 void *Y, const int incY);\nvoid cblas_zgbmv(const enum CBLAS_ORDER Order,\n                 const enum CBLAS_TRANSPOSE TransA, const int M, const int N,\n                 const int KL, const int KU, const void *alpha,\n                 const void *A, const int lda, const void *X,\n                 const int incX, const void *beta, void *Y, const int incY);\nvoid cblas_ztrmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const void *A, const int lda,\n                 void *X, const int incX);\nvoid cblas_ztbmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const int K, const void *A, const int lda,\n                 void *X, const int incX);\nvoid cblas_ztpmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const void *Ap, void *X, const int incX);\nvoid cblas_ztrsv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const void *A, const int lda, void *X,\n                 const int incX);\nvoid cblas_ztbsv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const int K, const void *A, const int lda,\n                 void *X, const int incX);\nvoid cblas_ztpsv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_DIAG Diag,\n                 const int N, const void *Ap, void *X, const int incX);\n\n\n/*\n * Routines with S and D prefixes only\n */\nvoid cblas_ssymv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const int N, const float alpha, const float *A,\n                 const int lda, const float *X, const int incX,\n                 const float beta, float *Y, const int incY);\nvoid cblas_ssbmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const int N, const int K, const float alpha, const float *A,\n                 const int lda, const float *X, const int incX,\n                 const float beta, float *Y, const int incY);\nvoid cblas_sspmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const int N, const float alpha, const float *Ap,\n                 const float *X, const int incX,\n                 const float beta, float *Y, const int incY);\nvoid cblas_sger(const enum CBLAS_ORDER Order, const int M, const int N,\n                const float alpha, const float *X, const int incX,\n                const float *Y, const int incY, float *A, const int lda);\nvoid cblas_ssyr(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                const int N, const float alpha, const float *X,\n                const int incX, float *A, const int lda);\nvoid cblas_sspr(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                const int N, const float alpha, const float *X,\n                const int incX, float *Ap);\nvoid cblas_ssyr2(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                const int N, const float alpha, const float *X,\n                const int incX, const float *Y, const int incY, float *A,\n                const int lda);\nvoid cblas_sspr2(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                const int N, const float alpha, const float *X,\n                const int incX, const float *Y, const int incY, float *A);\n\nvoid cblas_dsymv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const int N, const double alpha, const double *A,\n                 const int lda, const double *X, const int incX,\n                 const double beta, double *Y, const int incY);\nvoid cblas_dsbmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const int N, const int K, const double alpha, const double *A,\n                 const int lda, const double *X, const int incX,\n                 const double beta, double *Y, const int incY);\nvoid cblas_dspmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const int N, const double alpha, const double *Ap,\n                 const double *X, const int incX,\n                 const double beta, double *Y, const int incY);\nvoid cblas_dger(const enum CBLAS_ORDER Order, const int M, const int N,\n                const double alpha, const double *X, const int incX,\n                const double *Y, const int incY, double *A, const int lda);\nvoid cblas_dsyr(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                const int N, const double alpha, const double *X,\n                const int incX, double *A, const int lda);\nvoid cblas_dspr(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                const int N, const double alpha, const double *X,\n                const int incX, double *Ap);\nvoid cblas_dsyr2(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                const int N, const double alpha, const double *X,\n                const int incX, const double *Y, const int incY, double *A,\n                const int lda);\nvoid cblas_dspr2(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                const int N, const double alpha, const double *X,\n                const int incX, const double *Y, const int incY, double *A);\n\n\n/*\n * Routines with C and Z prefixes only\n */\nvoid cblas_chemv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const int N, const void *alpha, const void *A,\n                 const int lda, const void *X, const int incX,\n                 const void *beta, void *Y, const int incY);\nvoid cblas_chbmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const int N, const int K, const void *alpha, const void *A,\n                 const int lda, const void *X, const int incX,\n                 const void *beta, void *Y, const int incY);\nvoid cblas_chpmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const int N, const void *alpha, const void *Ap,\n                 const void *X, const int incX,\n                 const void *beta, void *Y, const int incY);\nvoid cblas_cgeru(const enum CBLAS_ORDER Order, const int M, const int N,\n                 const void *alpha, const void *X, const int incX,\n                 const void *Y, const int incY, void *A, const int lda);\nvoid cblas_cgerc(const enum CBLAS_ORDER Order, const int M, const int N,\n                 const void *alpha, const void *X, const int incX,\n                 const void *Y, const int incY, void *A, const int lda);\nvoid cblas_cher(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                const int N, const float alpha, const void *X, const int incX,\n                void *A, const int lda);\nvoid cblas_chpr(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                const int N, const float alpha, const void *X,\n                const int incX, void *A);\nvoid cblas_cher2(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo, const int N,\n                const void *alpha, const void *X, const int incX,\n                const void *Y, const int incY, void *A, const int lda);\nvoid cblas_chpr2(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo, const int N,\n                const void *alpha, const void *X, const int incX,\n                const void *Y, const int incY, void *Ap);\n\nvoid cblas_zhemv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const int N, const void *alpha, const void *A,\n                 const int lda, const void *X, const int incX,\n                 const void *beta, void *Y, const int incY);\nvoid cblas_zhbmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const int N, const int K, const void *alpha, const void *A,\n                 const int lda, const void *X, const int incX,\n                 const void *beta, void *Y, const int incY);\nvoid cblas_zhpmv(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const int N, const void *alpha, const void *Ap,\n                 const void *X, const int incX,\n                 const void *beta, void *Y, const int incY);\nvoid cblas_zgeru(const enum CBLAS_ORDER Order, const int M, const int N,\n                 const void *alpha, const void *X, const int incX,\n                 const void *Y, const int incY, void *A, const int lda);\nvoid cblas_zgerc(const enum CBLAS_ORDER Order, const int M, const int N,\n                 const void *alpha, const void *X, const int incX,\n                 const void *Y, const int incY, void *A, const int lda);\nvoid cblas_zher(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                const int N, const double alpha, const void *X, const int incX,\n                void *A, const int lda);\nvoid cblas_zhpr(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                const int N, const double alpha, const void *X,\n                const int incX, void *A);\nvoid cblas_zher2(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo, const int N,\n                const void *alpha, const void *X, const int incX,\n                const void *Y, const int incY, void *A, const int lda);\nvoid cblas_zhpr2(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo, const int N,\n                const void *alpha, const void *X, const int incX,\n                const void *Y, const int incY, void *Ap);\n\n/*\n * ===========================================================================\n * Prototypes for level 3 BLAS\n * ===========================================================================\n */\n\n/*\n * Routines with standard 4 prefixes (S, D, C, Z)\n */\nvoid cblas_sgemm(const enum CBLAS_ORDER Order, const enum CBLAS_TRANSPOSE TransA,\n                 const enum CBLAS_TRANSPOSE TransB, const int M, const int N,\n                 const int K, const float alpha, const float *A,\n                 const int lda, const float *B, const int ldb,\n                 const float beta, float *C, const int ldc);\nvoid cblas_ssymm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const int M, const int N,\n                 const float alpha, const float *A, const int lda,\n                 const float *B, const int ldb, const float beta,\n                 float *C, const int ldc);\nvoid cblas_ssyrk(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE Trans, const int N, const int K,\n                 const float alpha, const float *A, const int lda,\n                 const float beta, float *C, const int ldc);\nvoid cblas_ssyr2k(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                  const enum CBLAS_TRANSPOSE Trans, const int N, const int K,\n                  const float alpha, const float *A, const int lda,\n                  const float *B, const int ldb, const float beta,\n                  float *C, const int ldc);\nvoid cblas_strmm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const enum CBLAS_TRANSPOSE TransA,\n                 const enum CBLAS_DIAG Diag, const int M, const int N,\n                 const float alpha, const float *A, const int lda,\n                 float *B, const int ldb);\nvoid cblas_strsm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const enum CBLAS_TRANSPOSE TransA,\n                 const enum CBLAS_DIAG Diag, const int M, const int N,\n                 const float alpha, const float *A, const int lda,\n                 float *B, const int ldb);\n\nvoid cblas_dgemm(const enum CBLAS_ORDER Order, const enum CBLAS_TRANSPOSE TransA,\n                 const enum CBLAS_TRANSPOSE TransB, const int M, const int N,\n                 const int K, const double alpha, const double *A,\n                 const int lda, const double *B, const int ldb,\n                 const double beta, double *C, const int ldc);\nvoid cblas_dsymm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const int M, const int N,\n                 const double alpha, const double *A, const int lda,\n                 const double *B, const int ldb, const double beta,\n                 double *C, const int ldc);\nvoid cblas_dsyrk(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE Trans, const int N, const int K,\n                 const double alpha, const double *A, const int lda,\n                 const double beta, double *C, const int ldc);\nvoid cblas_dsyr2k(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                  const enum CBLAS_TRANSPOSE Trans, const int N, const int K,\n                  const double alpha, const double *A, const int lda,\n                  const double *B, const int ldb, const double beta,\n                  double *C, const int ldc);\nvoid cblas_dtrmm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const enum CBLAS_TRANSPOSE TransA,\n                 const enum CBLAS_DIAG Diag, const int M, const int N,\n                 const double alpha, const double *A, const int lda,\n                 double *B, const int ldb);\nvoid cblas_dtrsm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const enum CBLAS_TRANSPOSE TransA,\n                 const enum CBLAS_DIAG Diag, const int M, const int N,\n                 const double alpha, const double *A, const int lda,\n                 double *B, const int ldb);\n\nvoid cblas_cgemm(const enum CBLAS_ORDER Order, const enum CBLAS_TRANSPOSE TransA,\n                 const enum CBLAS_TRANSPOSE TransB, const int M, const int N,\n                 const int K, const void *alpha, const void *A,\n                 const int lda, const void *B, const int ldb,\n                 const void *beta, void *C, const int ldc);\nvoid cblas_csymm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const int M, const int N,\n                 const void *alpha, const void *A, const int lda,\n                 const void *B, const int ldb, const void *beta,\n                 void *C, const int ldc);\nvoid cblas_csyrk(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE Trans, const int N, const int K,\n                 const void *alpha, const void *A, const int lda,\n                 const void *beta, void *C, const int ldc);\nvoid cblas_csyr2k(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                  const enum CBLAS_TRANSPOSE Trans, const int N, const int K,\n                  const void *alpha, const void *A, const int lda,\n                  const void *B, const int ldb, const void *beta,\n                  void *C, const int ldc);\nvoid cblas_ctrmm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const enum CBLAS_TRANSPOSE TransA,\n                 const enum CBLAS_DIAG Diag, const int M, const int N,\n                 const void *alpha, const void *A, const int lda,\n                 void *B, const int ldb);\nvoid cblas_ctrsm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const enum CBLAS_TRANSPOSE TransA,\n                 const enum CBLAS_DIAG Diag, const int M, const int N,\n                 const void *alpha, const void *A, const int lda,\n                 void *B, const int ldb);\n\nvoid cblas_zgemm(const enum CBLAS_ORDER Order, const enum CBLAS_TRANSPOSE TransA,\n                 const enum CBLAS_TRANSPOSE TransB, const int M, const int N,\n                 const int K, const void *alpha, const void *A,\n                 const int lda, const void *B, const int ldb,\n                 const void *beta, void *C, const int ldc);\nvoid cblas_zsymm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const int M, const int N,\n                 const void *alpha, const void *A, const int lda,\n                 const void *B, const int ldb, const void *beta,\n                 void *C, const int ldc);\nvoid cblas_zsyrk(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE Trans, const int N, const int K,\n                 const void *alpha, const void *A, const int lda,\n                 const void *beta, void *C, const int ldc);\nvoid cblas_zsyr2k(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                  const enum CBLAS_TRANSPOSE Trans, const int N, const int K,\n                  const void *alpha, const void *A, const int lda,\n                  const void *B, const int ldb, const void *beta,\n                  void *C, const int ldc);\nvoid cblas_ztrmm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const enum CBLAS_TRANSPOSE TransA,\n                 const enum CBLAS_DIAG Diag, const int M, const int N,\n                 const void *alpha, const void *A, const int lda,\n                 void *B, const int ldb);\nvoid cblas_ztrsm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const enum CBLAS_TRANSPOSE TransA,\n                 const enum CBLAS_DIAG Diag, const int M, const int N,\n                 const void *alpha, const void *A, const int lda,\n                 void *B, const int ldb);\n\n\n/*\n * Routines with prefixes C and Z only\n */\nvoid cblas_chemm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const int M, const int N,\n                 const void *alpha, const void *A, const int lda,\n                 const void *B, const int ldb, const void *beta,\n                 void *C, const int ldc);\nvoid cblas_cherk(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE Trans, const int N, const int K,\n                 const float alpha, const void *A, const int lda,\n                 const float beta, void *C, const int ldc);\nvoid cblas_cher2k(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                  const enum CBLAS_TRANSPOSE Trans, const int N, const int K,\n                  const void *alpha, const void *A, const int lda,\n                  const void *B, const int ldb, const float beta,\n                  void *C, const int ldc);\nvoid cblas_zhemm(const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,\n                 const enum CBLAS_UPLO Uplo, const int M, const int N,\n                 const void *alpha, const void *A, const int lda,\n                 const void *B, const int ldb, const void *beta,\n                 void *C, const int ldc);\nvoid cblas_zherk(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                 const enum CBLAS_TRANSPOSE Trans, const int N, const int K,\n                 const double alpha, const void *A, const int lda,\n                 const double beta, void *C, const int ldc);\nvoid cblas_zher2k(const enum CBLAS_ORDER Order, const enum CBLAS_UPLO Uplo,\n                  const enum CBLAS_TRANSPOSE Trans, const int N, const int K,\n                  const void *alpha, const void *A, const int lda,\n                  const void *B, const int ldb, const double beta,\n                  void *C, const int ldc);\n\nint cblas_errprn(int ierr, int info, char *form, ...);\n\n#endif  /* end #ifdef CBLAS_ENUM_ONLY */\n#endif  // CAFFE2_USE_MKL\n#endif\n"
  },
  {
    "path": "caffe2/utils/conversions.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <caffe2/core/types.h>\n\n#ifdef __CUDA_ARCH__\n// Proxy for including cuda_fp16.h, because common_gpu.h\n// has necessary diagnostic guards.\n#include <caffe2/core/common_gpu.h>\n#endif\n\n#ifdef __CUDA_ARCH__\n#define CONVERSIONS_DECL __host__ __device__ inline\n#else\n#define CONVERSIONS_DECL inline\n#endif\n\nnamespace caffe2 {\n\nnamespace convert {\n\nnamespace {\ninline float16 cpu_float2half_rn(float f) {\n  float16 ret;\n\n  static_assert(\n      sizeof(unsigned int) == sizeof(float),\n      \"Programming error sizeof(unsigned int) != sizeof(float)\");\n\n  unsigned* xp = reinterpret_cast<unsigned int*>(&f);\n  unsigned x = *xp;\n  unsigned u = (x & 0x7fffffff), remainder, shift, lsb, lsb_s1, lsb_m1;\n  unsigned sign, exponent, mantissa;\n\n  // Get rid of +NaN/-NaN case first.\n  if (u > 0x7f800000) {\n    ret.x = 0x7fffU;\n    return ret;\n  }\n\n  sign = ((x >> 16) & 0x8000);\n\n  // Get rid of +Inf/-Inf, +0/-0.\n  if (u > 0x477fefff) {\n    ret.x = sign | 0x7c00U;\n    return ret;\n  }\n  if (u < 0x33000001) {\n    ret.x = (sign | 0x0000);\n    return ret;\n  }\n\n  exponent = ((u >> 23) & 0xff);\n  mantissa = (u & 0x7fffff);\n\n  if (exponent > 0x70) {\n    shift = 13;\n    exponent -= 0x70;\n  } else {\n    shift = 0x7e - exponent;\n    exponent = 0;\n    mantissa |= 0x800000;\n  }\n  lsb = (1 << shift);\n  lsb_s1 = (lsb >> 1);\n  lsb_m1 = (lsb - 1);\n\n  // Round to nearest even.\n  remainder = (mantissa & lsb_m1);\n  mantissa >>= shift;\n  if (remainder > lsb_s1 || (remainder == lsb_s1 && (mantissa & 0x1))) {\n    ++mantissa;\n    if (!(mantissa & 0x3ff)) {\n      ++exponent;\n      mantissa = 0;\n    }\n  }\n\n  ret.x = (sign | (exponent << 10) | mantissa);\n\n  return ret;\n}\n\ninline float cpu_half2float(float16 h) {\n  unsigned sign = ((h.x >> 15) & 1);\n  unsigned exponent = ((h.x >> 10) & 0x1f);\n  unsigned mantissa = ((h.x & 0x3ff) << 13);\n\n  if (exponent == 0x1f) { /* NaN or Inf */\n    mantissa = (mantissa ? (sign = 0, 0x7fffff) : 0);\n    exponent = 0xff;\n  } else if (!exponent) { /* Denorm or Zero */\n    if (mantissa) {\n      unsigned int msb;\n      exponent = 0x71;\n      do {\n        msb = (mantissa & 0x400000);\n        mantissa <<= 1; /* normalize */\n        --exponent;\n      } while (!msb);\n      mantissa &= 0x7fffff; /* 1.mantissa is implicit */\n    }\n  } else {\n    exponent += 0x70;\n  }\n\n  unsigned i = ((sign << 31) | (exponent << 23) | mantissa);\n  float ret;\n  memcpy(&ret, &i, sizeof(i));\n  return ret;\n}\n\n}; // anonymous\n\n#if __CUDACC__\n\n#if CUDA_VERSION >= 9000\nCONVERSIONS_DECL float16 halfToFloat16(half x) {\n#ifdef __GNUC__\n#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)\n#pragma GCC diagnostic push\n#endif\n#pragma GCC diagnostic ignored \"-Wstrict-aliasing\"\n#endif // __GNUC__\n  float16 r = *reinterpret_cast<float16*>(&x);\n#ifdef __GNUC__\n#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)\n#pragma GCC diagnostic pop\n#endif\n#endif // __GNUC__\n  return r;\n}\n\ninline half float16ToHalf(const float16 x) {\n  __half_raw hr;\n  hr.x = x.x;\n  half r(hr);\n  return r;\n}\n\ninline half floatToHalf(const float x) {\n  float16 xh = cpu_float2half_rn(x);\n  return float16ToHalf(xh);\n}\n\n#else\ninline float16 halfToFloat16(__half x) {\n  float16 r;\n  r.x = x.x;\n  return r;\n}\n\ninline __half float16ToHalf(const float16 x) {\n  __half r;\n  r.x = x.x;\n  return r;\n}\n\ninline half floatToHalf(const float x) {\n  float16 xh = cpu_float2half_rn(x);\n  return float16ToHalf(xh);\n}\n#endif // CUDA_VERSION\n\n#endif // __CUDACC__\n\n// general version: defer to static_cast\ntemplate <typename IN, typename OUT>\nCONVERSIONS_DECL OUT To(const IN in) {\n  return static_cast<OUT>(in);\n}\n\n// explicit for fp16\ntemplate <>\nCONVERSIONS_DECL float16 To(const float in) {\n#if __CUDA_ARCH__\n  // hacky interface between C2 fp16 and CUDA\n#if CUDA_VERSION >= 9000\n  half rh = static_cast<half>(in);\n  return halfToFloat16(rh);\n#else\n  float16 ret;\n  ret.x = __float2half(in).x;\n  return ret;\n#endif // CUDA_VERSION >= 9000\n#else\n  return cpu_float2half_rn(in);\n#endif\n}\n\ntemplate <>\nCONVERSIONS_DECL float To(const float16 in) {\n#if __CUDA_ARCH__\n#if CUDA_VERSION >= 9000\n  __half_raw tmp;\n#else\n  __half tmp;\n#endif\n  tmp.x = in.x;\n  return __half2float(tmp);\n#else\n  return cpu_half2float(in);\n#endif\n};\n\ntemplate <>\nCONVERSIONS_DECL float To(const float in) {\n  return in;\n}\n\ntemplate <typename OUT, typename IN>\nCONVERSIONS_DECL OUT Get(IN x) {\n  return static_cast<OUT>(x);\n}\n\ntemplate <>\nCONVERSIONS_DECL float Get(float16 x) {\n  return To<float16, float>(x);\n}\n\ntemplate <>\nCONVERSIONS_DECL float16 Get(float x) {\n  return To<float, float16>(x);\n}\n\n}; // namespace convert\n\n}; // namespace caffe2\n\n#undef CONVERSIONS_DECL\n"
  },
  {
    "path": "caffe2/utils/cpu_neon.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_CPU_NEON_H_\n#define CAFFE2_UTILS_CPU_NEON_H_\n\n// Provides a variety of ARM NEON-specific utility functions\n#ifdef __ARM_NEON__\n#include <arm_neon.h>\n\nnamespace caffe2 {\n\ntemplate <typename T>\ninline bool isPointerAligned(T* p, size_t align) {\n  return (reinterpret_cast<uintptr_t>(p) % align == 0);\n}\n\ninline float32x4_t vert_sum_f32(float32x4_t v0,\n                                float32x4_t v1,\n                                float32x4_t v2,\n                                float32x4_t v3) {\n  v0 = vaddq_f32(v0, v1);\n  v2 = vaddq_f32(v2, v3);\n  return vaddq_f32(v0, v2);\n}\n\ninline float horizontal_sum_f32(float32x4_t v0,\n                                float32x4_t v1,\n                                float32x4_t v2,\n                                float32x4_t v3) {\n  v0 = vert_sum_f32(v0, v1, v2, v3);\n  float32x2_t v = vadd_f32(vget_high_f32(v0), vget_low_f32(v0));\n  return vget_lane_f32(vpadd_f32(v, v), 0);\n}\n\n// Load/store functions that assume alignment\n\ninline float32x4_t vld1q_f32_aligned(const float* p) {\n  return vld1q_f32((const float*)\n                   __builtin_assume_aligned(p, sizeof(float32x4_t)));\n}\n\ninline void vst1q_f32_aligned(float* p, float32x4_t v) {\n  vst1q_f32((float*) __builtin_assume_aligned(p, sizeof(float32x4_t)), v);\n}\n\ninline void vst4_u8_aligned(uint8_t* p, uint8x8x4_t v) {\n  vst4_u8((uint8_t*)\n          __builtin_assume_aligned(p, sizeof(uint8x8x4_t)), v);\n}\n\n}  // namespace caffe2\n\n#endif // __ARM_NEON__\n\n#endif  // CAFFE2_UTILS_CPU_NEON_H_\n"
  },
  {
    "path": "caffe2/utils/cpuid.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/utils/cpuid.h\"\n\nnamespace caffe2 {\n\nconst CpuId& GetCpuId() {\n  static CpuId cpuid_singleton;\n  return cpuid_singleton;\n}\n\nCAFFE2_API uint32_t CpuId::f1c_ = 0;\nCAFFE2_API uint32_t CpuId::f1d_ = 0;\nCAFFE2_API uint32_t CpuId::f7b_ = 0;\nCAFFE2_API uint32_t CpuId::f7c_ = 0;\n\nCpuId::CpuId() {\n#ifdef _MSC_VER\n  int reg[4];\n  __cpuid(static_cast<int*>(reg), 0);\n  const int n = reg[0];\n  if (n >= 1) {\n    __cpuid(static_cast<int*>(reg), 1);\n    f1c_ = uint32_t(reg[2]);\n    f1d_ = uint32_t(reg[3]);\n  }\n  if (n >= 7) {\n    __cpuidex(static_cast<int*>(reg), 7, 0);\n    f7b_ = uint32_t(reg[1]);\n    f7c_ = uint32_t(reg[2]);\n  }\n#elif defined(__i386__) && defined(__PIC__) && !defined(__clang__) && \\\n    defined(__GNUC__)\n  // The following block like the normal cpuid branch below, but gcc\n  // reserves ebx for use of its pic register so we must specially\n  // handle the save and restore to avoid clobbering the register\n  uint32_t n;\n  __asm__(\n      \"pushl %%ebx\\n\\t\"\n      \"cpuid\\n\\t\"\n      \"popl %%ebx\\n\\t\"\n      : \"=a\"(n)\n      : \"a\"(0)\n      : \"ecx\", \"edx\");\n  if (n >= 1) {\n    uint32_t f1a;\n    __asm__(\n        \"pushl %%ebx\\n\\t\"\n        \"cpuid\\n\\t\"\n        \"popl %%ebx\\n\\t\"\n        : \"=a\"(f1a), \"=c\"(f1c_), \"=d\"(f1d_)\n        : \"a\"(1)\n        :);\n  }\n  if (n >= 7) {\n    __asm__(\n        \"pushl %%ebx\\n\\t\"\n        \"cpuid\\n\\t\"\n        \"movl %%ebx, %%eax\\n\\r\"\n        \"popl %%ebx\"\n        : \"=a\"(f7b_), \"=c\"(f7c_)\n        : \"a\"(7), \"c\"(0)\n        : \"edx\");\n  }\n#elif defined(__x86_64__) || defined(_M_X64) || defined(__i386__)\n  uint32_t n;\n  __asm__(\"cpuid\" : \"=a\"(n) : \"a\"(0) : \"ebx\", \"ecx\", \"edx\");\n  if (n >= 1) {\n    uint32_t f1a;\n    __asm__(\"cpuid\" : \"=a\"(f1a), \"=c\"(f1c_), \"=d\"(f1d_) : \"a\"(1) : \"ebx\");\n  }\n  if (n >= 7) {\n    uint32_t f7a;\n    __asm__(\"cpuid\"\n            : \"=a\"(f7a), \"=b\"(f7b_), \"=c\"(f7c_)\n            : \"a\"(7), \"c\"(0)\n            : \"edx\");\n  }\n#endif\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/cpuid.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <cstdint>\n\n#ifdef _MSC_VER\n#include <intrin.h>\n#endif\n\n#include \"caffe2/core/common.h\"\n\nnamespace caffe2 {\n\nclass CpuId;\n\nCAFFE2_API const CpuId& GetCpuId();\n\n///////////////////////////////////////////////////////////////////////////////\n// Implementation of CpuId that is borrowed from folly.\n///////////////////////////////////////////////////////////////////////////////\n\n/**\n * Identification of an Intel CPU.\n * Supports CPUID feature flags (EAX=1) and extended features (EAX=7, ECX=0).\n * Values from\n * http://www.intel.com/content/www/us/en/processors/processor-identification-cpuid-instruction-note.html\n */\nclass CpuId {\n public:\n  CpuId();\n\n#define X(name, r, bit)              \\\n  inline bool name() const {         \\\n    return ((r) & (1U << bit)) != 0; \\\n  }\n\n// cpuid(1): Processor Info and Feature Bits.\n#define C(name, bit) X(name, f1c_, bit)\n  C(sse3, 0)\n  C(pclmuldq, 1)\n  C(dtes64, 2)\n  C(monitor, 3)\n  C(dscpl, 4)\n  C(vmx, 5)\n  C(smx, 6)\n  C(eist, 7)\n  C(tm2, 8)\n  C(ssse3, 9)\n  C(cnxtid, 10)\n  C(fma, 12)\n  C(cx16, 13)\n  C(xtpr, 14)\n  C(pdcm, 15)\n  C(pcid, 17)\n  C(dca, 18)\n  C(sse41, 19)\n  C(sse42, 20)\n  C(x2apic, 21)\n  C(movbe, 22)\n  C(popcnt, 23)\n  C(tscdeadline, 24)\n  C(aes, 25)\n  C(xsave, 26)\n  C(osxsave, 27)\n  C(avx, 28)\n  C(f16c, 29)\n  C(rdrand, 30)\n#undef C\n\n#define D(name, bit) X(name, f1d_, bit)\n  D(fpu, 0)\n  D(vme, 1)\n  D(de, 2)\n  D(pse, 3)\n  D(tsc, 4)\n  D(msr, 5)\n  D(pae, 6)\n  D(mce, 7)\n  D(cx8, 8)\n  D(apic, 9)\n  D(sep, 11)\n  D(mtrr, 12)\n  D(pge, 13)\n  D(mca, 14)\n  D(cmov, 15)\n  D(pat, 16)\n  D(pse36, 17)\n  D(psn, 18)\n  D(clfsh, 19)\n  D(ds, 21)\n  D(acpi, 22)\n  D(mmx, 23)\n  D(fxsr, 24)\n  D(sse, 25)\n  D(sse2, 26)\n  D(ss, 27)\n  D(htt, 28)\n  D(tm, 29)\n  D(pbe, 31)\n#undef D\n\n// cpuid(7): Extended Features.\n#define B(name, bit) X(name, f7b_, bit)\n  B(bmi1, 3)\n  B(hle, 4)\n  B(avx2, 5)\n  B(smep, 7)\n  B(bmi2, 8)\n  B(erms, 9)\n  B(invpcid, 10)\n  B(rtm, 11)\n  B(mpx, 14)\n  B(avx512f, 16)\n  B(avx512dq, 17)\n  B(rdseed, 18)\n  B(adx, 19)\n  B(smap, 20)\n  B(avx512ifma, 21)\n  B(pcommit, 22)\n  B(clflushopt, 23)\n  B(clwb, 24)\n  B(avx512pf, 26)\n  B(avx512er, 27)\n  B(avx512cd, 28)\n  B(sha, 29)\n  B(avx512bw, 30)\n  B(avx512vl, 31)\n#undef B\n\n#define E(name, bit) X(name, f7c_, bit)\n  E(prefetchwt1, 0)\n  E(avx512vbmi, 1)\n#undef E\n\n#undef X\n\n private:\n  CAFFE2_API static uint32_t f1c_;\n  CAFFE2_API static uint32_t f1d_;\n  CAFFE2_API static uint32_t f7b_;\n  CAFFE2_API static uint32_t f7c_;\n};\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/cpuid_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n#include \"caffe2/utils/cpuid.h\"\n\nnamespace caffe2 {\n\nTEST(CpuIdTest, ShouldAlwaysHaveMMX) {\n  EXPECT_TRUE(GetCpuId().mmx());\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/eigen_utils.h",
    "content": "// Copyright 2004-present Facebook. All Rights Reserved.\n\n#ifndef CAFFE2_OPERATORS_UTILS_EIGEN_H_\n#define CAFFE2_OPERATORS_UTILS_EIGEN_H_\n\n#include \"Eigen/Core\"\n#include \"Eigen/Dense\"\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\n\n// 1-d array\ntemplate <typename T>\nusing EArrXt = Eigen::Array<T, Eigen::Dynamic, 1>;\nusing EArrXf = Eigen::ArrayXf;\nusing EArrXd = Eigen::ArrayXd;\nusing EArrXi = Eigen::ArrayXi;\nusing EArrXb = EArrXt<bool>;\n\n// 2-d array, column major\ntemplate <typename T>\nusing EArrXXt = Eigen::Array<T, Eigen::Dynamic, Eigen::Dynamic>;\nusing EArrXXf = Eigen::ArrayXXf;\n\n// 2-d array, row major\ntemplate <typename T>\nusing ERArrXXt =\n    Eigen::Array<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;\nusing ERArrXXf = ERArrXXt<float>;\n\n// 1-d vector\ntemplate <typename T>\nusing EVecXt = Eigen::Matrix<T, Eigen::Dynamic, 1>;\nusing EVecXd = Eigen::VectorXd;\nusing EVecXf = Eigen::VectorXf;\n\n// 1-d row vector\nusing ERVecXd = Eigen::RowVectorXd;\nusing ERVecXf = Eigen::RowVectorXf;\n\n// 2-d matrix, column major\ntemplate <typename T>\nusing EMatXt = Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>;\nusing EMatXd = Eigen::MatrixXd;\nusing EMatXf = Eigen::MatrixXf;\n\n// 2-d matrix, row major\ntemplate <typename T>\nusing ERMatXt =\n    Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;\nusing ERMatXd = ERMatXt<double>;\nusing ERMatXf = ERMatXt<float>;\n\nnamespace utils {\n\ntemplate <typename T>\nEigen::Map<const EArrXt<T>> AsEArrXt(const std::vector<T>& arr) {\n  return {arr.data(), static_cast<int>(arr.size())};\n}\ntemplate <typename T>\nEigen::Map<EArrXt<T>> AsEArrXt(std::vector<T>& arr) {\n  return {arr.data(), static_cast<int>(arr.size())};\n}\n\n// return a sub array of 'array' based on indices 'indices'\ntemplate <class Derived, class Derived1, class Derived2>\nvoid GetSubArray(\n    const Eigen::ArrayBase<Derived>& array,\n    const Eigen::ArrayBase<Derived1>& indices,\n    Eigen::ArrayBase<Derived2>* out_array) {\n  CAFFE_ENFORCE_EQ(array.cols(), 1);\n  // using T = typename Derived::Scalar;\n\n  out_array->derived().resize(indices.size());\n  for (int i = 0; i < indices.size(); i++) {\n    DCHECK_LT(indices[i], array.size());\n    (*out_array)[i] = array[indices[i]];\n  }\n}\n\n// return a sub array of 'array' based on indices 'indices'\ntemplate <class Derived, class Derived1>\nEArrXt<typename Derived::Scalar> GetSubArray(\n    const Eigen::ArrayBase<Derived>& array,\n    const Eigen::ArrayBase<Derived1>& indices) {\n  using T = typename Derived::Scalar;\n  EArrXt<T> ret(indices.size());\n  GetSubArray(array, indices, &ret);\n  return ret;\n}\n\n// return a sub array of 'array' based on indices 'indices'\ntemplate <class Derived>\nEArrXt<typename Derived::Scalar> GetSubArray(\n    const Eigen::ArrayBase<Derived>& array,\n    const std::vector<int>& indices) {\n  return GetSubArray(array, AsEArrXt(indices));\n}\n\n// return 2d sub array of 'array' based on row indices 'row_indices'\ntemplate <class Derived, class Derived1, class Derived2>\nvoid GetSubArrayRows(\n    const Eigen::ArrayBase<Derived>& array2d,\n    const Eigen::ArrayBase<Derived1>& row_indices,\n    Eigen::ArrayBase<Derived2>* out_array) {\n  out_array->derived().resize(row_indices.size(), array2d.cols());\n\n  for (int i = 0; i < row_indices.size(); i++) {\n    DCHECK_LT(row_indices[i], array2d.size());\n    out_array->row(i) =\n        array2d.row(row_indices[i]).template cast<typename Derived2::Scalar>();\n  }\n}\n\n// return indices of 1d array for elements evaluated to true\ntemplate <class Derived>\nstd::vector<int> GetArrayIndices(const Eigen::ArrayBase<Derived>& array) {\n  std::vector<int> ret;\n  for (int i = 0; i < array.size(); i++) {\n    if (array[i]) {\n      ret.push_back(i);\n    }\n  }\n  return ret;\n}\n\n} // namespace utils\n} // namespace caffe2\n\n#endif\n"
  },
  {
    "path": "caffe2/utils/fatal_signal_asan_no_sig_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/utils/signal_handler.h\"\n#if defined(CAFFE2_SUPPORTS_FATAL_SIGNAL_HANDLERS)\n#include <gtest/gtest.h>\n#include <pthread.h>\n#include <unistd.h>\n\n#include <functional>\n#include <iostream>\n\n#include \"caffe2/core/common.h\"\n\nnamespace {\nvoid* dummy_thread(void*) {\n  while (1) {\n  }\n}\n\nbool forkAndPipe(\n    std::string& stderrBuffer,\n    std::function<void(void)> callback) {\n  std::array<int, 2> stderrPipe;\n  if (pipe(stderrPipe.data()) != 0) {\n    perror(\"STDERR pipe\");\n    return false;\n  }\n  pid_t child = fork();\n  if (child == 0) {\n    // Replace this process' stderr so we can read it.\n    if (dup2(stderrPipe[1], STDERR_FILENO) < 0) {\n      close(stderrPipe[0]);\n      close(stderrPipe[1]);\n      perror(\"dup2 STDERR\");\n      exit(5);\n    }\n\n    // This is for the parent to work with.\n    close(stderrPipe[0]);\n    close(stderrPipe[1]);\n\n    callback();\n    exit(7);\n  } else if (child > 0) {\n    const int bufferSize = 128;\n    std::array<char, bufferSize> buffer;\n\n    // We want to close the writing end of the pipe right away so our\n    // read actually gets an EOF.\n    close(stderrPipe[1]);\n\n    // wait for child to finish crashing.\n    int statloc;\n    if (wait(&statloc) < 0) {\n      close(stderrPipe[0]);\n      perror(\"wait\");\n      return false;\n    }\n\n    ssize_t bytesRead;\n    while ((bytesRead = read(stderrPipe[0], buffer.data(), bufferSize)) > 0) {\n      const std::string tmp(buffer.data(), bytesRead);\n      std::cout << tmp;\n      stderrBuffer += tmp;\n    }\n\n    // The child should have exited due to signal.\n    if (!WIFSIGNALED(statloc)) {\n      fprintf(stderr, \"Child didn't exit because it received a signal\\n\");\n      if (WIFEXITED(statloc)) {\n        fprintf(stderr, \"Exited with code: %d\\n\", WEXITSTATUS(statloc) & 0xff);\n      }\n      return false;\n    }\n\n    if (bytesRead < 0) {\n      perror(\"read\");\n      return false;\n    }\n\n    close(stderrPipe[0]);\n    return true;\n  } else {\n    perror(\"fork\");\n    return false;\n  }\n}\n} // namespace\n\n#define _TEST_FATAL_SIGNAL(signum, name, threadCount, print, expected)       \\\n  do {                                                                       \\\n    std::string stderrBuffer;                                                \\\n    ASSERT_TRUE(forkAndPipe(stderrBuffer, [=]() {                            \\\n      caffe2::setPrintStackTracesOnFatalSignal(print);                       \\\n      pthread_t pt;                                                          \\\n      for (int i = 0; i < threadCount; i++) {                                \\\n        if (pthread_create(&pt, nullptr, ::dummy_thread, nullptr)) {         \\\n          perror(\"pthread_create\");                                          \\\n        }                                                                    \\\n      }                                                                      \\\n      raise(signum);                                                         \\\n    }));                                                                     \\\n    int keyPhraseCount = 0;                                                  \\\n    std::string keyPhrase =                                                  \\\n        std::string(name) + \"(\" + caffe2::to_string(signum) + \"), Thread\";   \\\n    size_t loc = 0;                                                          \\\n    while ((loc = stderrBuffer.find(keyPhrase, loc)) != std::string::npos) { \\\n      keyPhraseCount += 1;                                                   \\\n      loc += 1;                                                              \\\n    }                                                                        \\\n    EXPECT_EQ(keyPhraseCount, expected);                                     \\\n  } while (0)\n\n#define TEST_FATAL_SIGNAL(signum, name, threadCount) \\\n  _TEST_FATAL_SIGNAL(signum, name, threadCount, true, threadCount + 1)\n\n#define TEST_FATAL_SIGNAL_NO_PRINT(signum, name, threadCount) \\\n  _TEST_FATAL_SIGNAL(signum, name, threadCount, false, 0)\n\nTEST(fatalSignalTest, SIGABRT8) {\n  TEST_FATAL_SIGNAL(SIGABRT, \"SIGABRT\", 8);\n}\n\nTEST(fatalSignalTest, SIGINT8) {\n  TEST_FATAL_SIGNAL(SIGINT, \"SIGINT\", 8);\n}\n\nTEST(fatalSignalTest, SIGILL8) {\n  TEST_FATAL_SIGNAL(SIGILL, \"SIGILL\", 8);\n}\n\nTEST(fatalSignalTest, SIGFPE8) {\n  TEST_FATAL_SIGNAL(SIGFPE, \"SIGFPE\", 8);\n}\n\nTEST(fatalSignalTest, SIGBUS8) {\n  TEST_FATAL_SIGNAL(SIGBUS, \"SIGBUS\", 8);\n}\n\nTEST(fatalSignalTest, SIGSEGV8) {\n  TEST_FATAL_SIGNAL(SIGSEGV, \"SIGSEGV\", 8);\n}\n\n// Test that if we don't enable printing stack traces then we don't get any.\nTEST(fatalSignalTest, SIGABRT8_NOPRINT) {\n  TEST_FATAL_SIGNAL_NO_PRINT(SIGABRT, \"SIGABRT\", 8);\n}\n#endif // defined(CAFFE2_SUPPORTS_FATAL_SIGNAL_HANDLERS)\n"
  },
  {
    "path": "caffe2/utils/fixed_divisor.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_FIXED_DIVISOR_H_\n#define CAFFE2_UTILS_FIXED_DIVISOR_H_\n\n#include <cstdlib>\n#include <stdint.h>\n\nnamespace caffe2 {\n\n// Utility class for quickly calculating quotients and remainders for\n// a known integer divisor\ntemplate <typename T>\nclass FixedDivisor {\n};\n\n// Works for any positive divisor, 1 to INT_MAX. One 64-bit\n// multiplication and one 64-bit shift is used to calculate the\n// result.\ntemplate <>\nclass FixedDivisor<int32_t> {\n public:\n  FixedDivisor(int32_t d) : d_(d) {\n    calcSignedMagic();\n  }\n\n  uint64_t getMagic() const {\n    return magic_;\n  }\n\n  int getShift() const {\n    return shift_;\n  }\n\n  /// Calculates `q = n / d`.\n  inline int32_t div(int32_t n) const {\n    // In lieu of a mulhi instruction being available, perform the\n    // work in uint64\n    uint64_t mul64 = magic_ * (uint64_t) n;\n    return (int32_t) (mul64 >> shift_);\n  }\n\n  /// Calculates `r = n % d`.\n  inline int32_t mod(int32_t n) const {\n    return n - d_ * div(n);\n  }\n\n  /// Calculates `q = n / d` and `r = n % d` together.\n  inline void divMod(int32_t n, int32_t& q, int32_t& r) const {\n    const int32_t quotient = div(n);\n    q = quotient;\n    r = n - d_ * quotient;\n  }\n\n private:\n  /**\n     Calculates magic multiplicative value and shift amount for\n     calculating `q = n / d` for signed 32-bit integers.\n     Implementation taken from Hacker's Delight section 10.\n  */\n  void calcSignedMagic() {\n    if (d_ == 1) {\n      magic_ = UINT64_C(0x1) << 32;\n      shift_ = 32;\n      return;\n    }\n\n    const uint32_t two31 = UINT32_C(0x80000000);\n    uint32_t ad = std::abs(d_);\n    uint32_t t = two31 + ((uint32_t) d_ >> 31);\n    uint32_t anc = t - 1 - t % ad;   // Absolute value of nc.\n    uint32_t p = 31;                 // Init. p.\n    uint32_t q1 = two31 / anc;       // Init. q1 = 2**p/|nc|.\n    uint32_t r1 = two31 - q1 * anc;  // Init. r1 = rem(2**p, |nc|).\n    uint32_t q2 = two31 / ad;        // Init. q2 = 2**p/|d|.\n    uint32_t r2 = two31 - q2 * ad;   // Init. r2 = rem(2**p, |d|).\n    uint32_t delta = 0;\n\n    do {\n      p = p + 1;\n      q1 = 2 * q1;         // Update q1 = 2**p/|nc|.\n      r1 = 2 * r1;         // Update r1 = rem(2**p, |nc|).\n\n      if (r1 >= anc) {     // (Must be an unsigned\n        q1 = q1 + 1;       // comparison here).\n        r1 = r1 - anc;\n      }\n\n      q2 = 2 * q2;         // Update q2 = 2**p/|d|.\n      r2 = 2 * r2;         // Update r2 = rem(2**p, |d|).\n\n      if (r2 >= ad) {      // (Must be an unsigned\n        q2 = q2 + 1;       // comparison here).\n        r2 = r2 - ad;\n      }\n\n      delta = ad - r2;\n    } while (q1 < delta || (q1 == delta && r1 == 0));\n\n    int32_t magic = q2 + 1;\n    if (d_ < 0) {\n      magic = -magic;\n    }\n    shift_ = p;\n    magic_ = (uint64_t) (uint32_t) magic;\n  }\n\n  int32_t d_;\n  uint64_t magic_;\n  int shift_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_UTILS_FIXED_DIVISOR_H_\n"
  },
  {
    "path": "caffe2/utils/fixed_divisor_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/utils/fixed_divisor.h\"\n#include <gtest/gtest.h>\n\n#include <random>\n\nnamespace caffe2 {\n\nnamespace {\n\nvoid compareDivMod(int32_t v, int32_t divisor) {\n  auto fixed = FixedDivisor<int32_t>(divisor);\n\n  int nativeQ = v / divisor;\n  int nativeR = v % divisor;\n\n  int fixedQ = fixed.div(v);\n  int fixedR = fixed.mod(v);\n\n  EXPECT_EQ(fixedQ, nativeQ) << v << \" / \" << divisor\n                             << \" magic \" << fixed.getMagic()\n                             << \" shift \" << fixed.getShift()\n                              << \" quot \" << fixedQ << \" \" << nativeQ;\n\n  EXPECT_EQ(fixedR, nativeR) << v << \" / \" << divisor\n                             << \" magic \" << fixed.getMagic()\n                             << \" shift \" << fixed.getShift()\n                             << \" rem \" << fixedR << \" \" << nativeR;\n}\n\n}\n\nTEST(FixedDivisorTest, Test) {\n  constexpr int32_t kMax = std::numeric_limits<int32_t>::max();\n\n  // divide by 1\n  compareDivMod(kMax, 1);\n  compareDivMod(0, 1);\n  compareDivMod(1, 1);\n\n  // divide by max\n  compareDivMod(kMax, kMax);\n  compareDivMod(0, kMax);\n  compareDivMod(1, kMax);\n\n  // divide by random positive values\n  std::random_device rd;\n  std::uniform_int_distribution<int32_t> vDist(0, kMax);\n  std::uniform_int_distribution<int32_t> qDist(1, kMax);\n\n  std::uniform_int_distribution<int32_t> vSmallDist(0, 1000);\n  std::uniform_int_distribution<int32_t> qSmallDist(1, 1000);\n  for (int i = 0; i < 10000; ++i) {\n    auto q = qDist(rd);\n    auto v = vDist(rd);\n    auto qSmall = qSmallDist(rd);\n    auto vSmall = vSmallDist(rd);\n\n    // random value\n    compareDivMod(vSmall, qSmall);\n    compareDivMod(vSmall, q);\n    compareDivMod(v, qSmall);\n    compareDivMod(v, q);\n\n    // special values\n    compareDivMod(kMax, qSmall);\n    compareDivMod(0, qSmall);\n    compareDivMod(1, qSmall);\n    compareDivMod(kMax, q);\n    compareDivMod(0, q);\n    compareDivMod(1, q);\n\n    compareDivMod(vSmall, 1);\n    compareDivMod(vSmall, kMax);\n    compareDivMod(v, 1);\n    compareDivMod(v, kMax);\n  }\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/math-detail.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_MATH_DETAIL_H_\n#define CAFFE2_UTILS_MATH_DETAIL_H_\nnamespace caffe2 {\n\nclass CPUContext;\n\nnamespace math {\nnamespace detail {\n\n// proxy to a class because of partial specialization limitations for functions\n\ntemplate<typename T, class Context, int FixedSize>\nstruct ScaleImpl {\n  inline void operator()(\n      const int N,\n      const float alpha,\n      const T* x,\n      T* y,\n      Context* context) {\n    Scale(N, alpha, x, y, context);\n  }\n};\n\n// Put light-weight implementations in .h file to enable inlining\ntemplate<typename T>\nstruct ScaleImpl<T, CPUContext, 1> {\n  inline void operator()(\n      const int N,\n      const float alpha,\n      const T* x,\n      T* y,\n      CPUContext* /*context*/) {\n    DCHECK_EQ(N, 1);\n    *y = *x * alpha;\n  }\n};\n\ntemplate<typename T, class Context, int FixedSize>\nstruct AxpyImpl {\n  inline void operator()(\n      const int N,\n      const float alpha,\n      const T* x,\n      T* y,\n      Context* context) {\n    Axpy(N, alpha, x, y, context);\n  }\n};\n\n// Put light-weight implementations in .h file to enable inlining\ntemplate<typename T>\nstruct AxpyImpl<T, CPUContext, 1> {\n  inline void operator()(\n      const int N,\n      const float alpha,\n      const T* x,\n      T* y,\n      CPUContext* /*context*/) {\n    DCHECK_EQ(N, 1);\n    *y += *x * alpha;\n  }\n};\n\n\n}  // namespace detail\n\ntemplate <typename T, class Context, int FixedSize>\ninline void ScaleFixedSize(\n    const int N,\n    const float alpha,\n    const T* x,\n    T* y,\n    Context* context) {\n  detail::ScaleImpl<T, Context, FixedSize>()(N, alpha, x, y, context);\n}\n\ntemplate <typename T, class Context, int FixedSize>\ninline void AxpyFixedSize(\n    const int N,\n    const float alpha,\n    const T* x,\n    T* y,\n    Context* context) {\n  detail::AxpyImpl<T, Context, FixedSize>()(N, alpha, x, y, context);\n}\n\n}  // namespace math\n}  // namespace caffe2\n\n#endif  // CAFFE2_UTILS_MATH_DETAIL_H_\n"
  },
  {
    "path": "caffe2/utils/math.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_MATH_H_\n#define CAFFE2_UTILS_MATH_H_\n// This is a simple translation from the old Caffe math interfaces. We aim to\n// still keep it simple, so all platforms would be able to support it fairly\n// easily.\n\n// We include the cblas header here so that we can obtain the macros from cblas.\nextern \"C\" {\n#include \"caffe2/utils/cblas.h\"\n}\n\n#ifdef CAFFE2_USE_ACCELERATE\n#include <Accelerate/Accelerate.h>\n#endif // CAFFE2_USE_ACCELERATE\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/types.h\"\n\n#ifndef __CUDACC__\n#include \"Eigen/Core\"\n#include \"Eigen/Dense\"\n#endif\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass Tensor;\n\n// An empty class as a placeholder for a math function that has no specific\n// engine specified.\nclass DefaultEngine {};\n\n#ifndef __CUDACC__\n// Common Eigen types that we will often use\ntemplate <typename T>\nusing EigenMatrixMap =\n    Eigen::Map<Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> >;\ntemplate <typename T>\nusing EigenArrayMap =\n    Eigen::Map<Eigen::Array<T, Eigen::Dynamic, Eigen::Dynamic> >;\ntemplate <typename T>\nusing EigenVectorMap = Eigen::Map<Eigen::Matrix<T, Eigen::Dynamic, 1> >;\ntemplate <typename T>\nusing EigenVectorArrayMap = Eigen::Map<Eigen::Array<T, Eigen::Dynamic, 1> >;\ntemplate <typename T>\nusing ConstEigenMatrixMap =\n    Eigen::Map<const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> >;\ntemplate <typename T>\nusing ConstEigenArrayMap =\n    Eigen::Map<const Eigen::Array<T, Eigen::Dynamic, Eigen::Dynamic> >;\ntemplate <typename T>\nusing ConstEigenVectorMap =\n    Eigen::Map<const Eigen::Matrix<T, Eigen::Dynamic, 1> >;\ntemplate <typename T>\nusing ConstEigenVectorArrayMap =\n    Eigen::Map<const Eigen::Array<T, Eigen::Dynamic, 1> >;\n#endif\n\nnamespace math {\n\ntemplate <typename T, class Context>\nvoid Exp(const int N, const T* x, T* y, Context* context);\ntemplate <typename T, class Context>\nvoid Log(const int N, const T* x, T* y, Context* context);\ntemplate <typename T, class Context>\nvoid Cos(const int N, const T* x, T* y, Context* context);\ntemplate <typename T, class Context>\nvoid Sin(const int N, const T* x, T* y, Context* context);\ntemplate <typename T, class Context>\nvoid SinCos(const int N, const T* x, T* ys, T* yc, Context* context);\ntemplate <typename T, class Context>\nvoid Abs(const int N, const T* x, T* y, Context* context);\ntemplate <typename T, class Context>\nvoid Sqrt(const int N, const T* x, T* y, Context* context);\ntemplate <typename T, class Context>\nvoid InvSqrt(const int N, const T* x, T* y, Context* context);\ntemplate <typename T, class Context>\nvoid Sqr(const int N, const T* x, T* y, Context* context);\n\ntemplate <typename T, class Context>\nvoid Not(const int N, const T* x, T* y, Context* context);\n\ntemplate <typename T, class Context>\nvoid Powx(const int N, const T* a, const T b, T* y, Context* context);\n\n#define CAFFE2_DECLARE_BINARY_OP_BINARY_RESULT(name)                         \\\n  template <typename T, class Context>                                       \\\n  void name(const int N, const T* a, const T* b, bool* y, Context* context); \\\n  template <typename T, class Context>                                       \\\n  void name##ToRow(                                                          \\\n      const int M,                                                           \\\n      const int N,                                                           \\\n      const T* a,                                                            \\\n      const T* b,                                                            \\\n      bool* y,                                                               \\\n      Context* context);\n\nCAFFE2_DECLARE_BINARY_OP_BINARY_RESULT(LT);\nCAFFE2_DECLARE_BINARY_OP_BINARY_RESULT(LE);\nCAFFE2_DECLARE_BINARY_OP_BINARY_RESULT(GT);\nCAFFE2_DECLARE_BINARY_OP_BINARY_RESULT(GE);\n\nCAFFE2_DECLARE_BINARY_OP_BINARY_RESULT(And);\nCAFFE2_DECLARE_BINARY_OP_BINARY_RESULT(Or);\nCAFFE2_DECLARE_BINARY_OP_BINARY_RESULT(Xor);\n\n#undef CAFFE2_DECLARE_BINARY_OP_BINARY_RESULT\n\n#define CAFFE2_DECLARE_BINARY_OP(name)                                    \\\n  template <typename T, class Context>                                    \\\n  void name(const int N, const T* a, const T* b, T* y, Context* context); \\\n  template <typename T, class Context>                                    \\\n  void name##ToRow(                                                       \\\n      const int M,                                                        \\\n      const int N,                                                        \\\n      const T* a,                                                         \\\n      const T* b,                                                         \\\n      T* y,                                                               \\\n      Context* context);                                                  \\\n  template <typename T, class Context>                                    \\\n  void name##ToRow(                                                       \\\n      const int M, const int N, const T* x, T* y, Context* context);      \\\n  template <typename T, class Context>                                    \\\n  void name##ToCol(                                                       \\\n      const int M, const int N, const T* x, T* y, Context* context);\n\nCAFFE2_DECLARE_BINARY_OP(Add);\nCAFFE2_DECLARE_BINARY_OP(Sub);\nCAFFE2_DECLARE_BINARY_OP(Mul);\nCAFFE2_DECLARE_BINARY_OP(Div);\n\n#undef CAFFE2_DECLARE_BINARY_OP\n\ntemplate <typename T, class Context>\nvoid ReduceMin(\n    const int N,\n    const T* x,\n    T* y,\n    Tensor<Context>* scratch_ptr,\n    Context* context);\ntemplate <typename T, class Context>\nvoid ReduceMax(\n    const int N,\n    const T* x,\n    T* y,\n    Tensor<Context>* scratch_ptr,\n    Context* context);\n\n// Adds batch sub-tensors elementwise to output. Stripe is the stripe length\n// and N is the number of elements to add (size of Y).\ntemplate <typename T, class Context>\nvoid AddStripedBatch(\n    const int N,\n    const T* first,\n    T* y,\n    const int stripe,\n    const int batch,\n    Context* context);\n\n// Compute the row-wise max of a N*D matrix X, and write it to a N\n// dimensional vector y.\ntemplate <typename T, class Context>\nvoid RowwiseMax(const int N, const int D, const T* x, T* y,\n                Context* context);\n\n// Compute the column-wise max of a N*D matrix X, and write it to a D\n// dimensional vector y.\ntemplate <typename T, class Context>\nvoid ColwiseMax(const int N, const int D, const T* x, T* y,\n                Context* context);\n\n// Elemwise maximum of vector x and vector y. z[i] = max(x[i], y[i])\ntemplate <typename T, class Context>\nvoid ElemwiseMax(const int N, const T* x, const T* y, T* z, Context* context);\n\n// Elemwise maximum of vector x and scalar alpha. y[i] = max(x[i], alpha)\ntemplate <typename T, class Context>\nvoid Maximum(\n    const int N,\n    const float alpha,\n    const T* x,\n    T* y,\n    Context* context);\n\n// Transpose tensor X with x_dims by axes and write the result to tensor Y with\n// y_dims.\ntemplate <typename T, class Context>\nvoid Transpose(\n    const int num_axes,\n    const int* x_dims,\n    const int* y_dims,\n    const int* axes,\n    const int data_size,\n    const T* X,\n    T* Y,\n    Context* context);\n\n// Decaf gemm provides a simpler interface to the gemm functions, with the\n// limitation that the data has to be contiguous in memory.\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nvoid Gemm(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const T* A,\n    const T* B,\n    const float beta,\n    T* C,\n    Context* context,\n    TensorProto::DataType math_type = TensorProto_DataType_FLOAT);\n\n// We also provide a gemm that has explicit lda, ldb and ldc specified.\n// In most cases you probably want to use the function above, though.\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nvoid GemmEx(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int M,\n    const int N,\n    const int K,\n    const T alpha,\n    const T* A,\n    const int lda,\n    const T* B,\n    const int ldb,\n    const T beta,\n    T* C,\n    const int ldc,\n    Context* context);\n\n// GemmBatched provides a simple abstraction into library routines\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nvoid GemmBatched(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int batch_size,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const T* A,\n    const T* B,\n    const float beta,\n    T* C,\n    Context* context,\n    Tensor<Context>* scratch = nullptr,\n    TensorProto::DataType math_type = TensorProto_DataType_FLOAT);\n\n// Gemv always takes in a M*N matrix A, and depending on whether we set TransA\n// to Trans, the output is:\n// CblasNoTrans: x is an N dim vector and y is an M dim vector.\n// CblasTrans:   x is an M dim vector and y is an N dim vector.\ntemplate <typename T, class Context, class Engine = DefaultEngine>\nvoid Gemv(\n    const CBLAS_TRANSPOSE TransA,\n    const int M,\n    const int N,\n    const float alpha,\n    const T* A,\n    const T* x,\n    const float beta,\n    T* y,\n    Context* context,\n    TensorProto::DataType math_type = TensorProto_DataType_FLOAT);\n\ntemplate <typename T, class Context>\nvoid Set(const size_t N, const T alpha, T* X, Context* context);\n\ntemplate <typename T, class Context>\nvoid RandUniform(const size_t n, const T a, const T b, T* r, Context* context);\n\ntemplate <typename T, class Context>\nvoid RandUniformUnique(\n    const size_t n,\n    const T a,\n    const T b,\n    T* r,\n    const size_t m,\n    const T* avoid,\n    Context* context);\n\ntemplate <typename T, class Context>\nvoid RandGaussian(\n    const size_t n,\n    const T mean,\n    const T std,\n    T* r,\n    Context* context);\n\n// Dot matrix of vector a and b, and writes the result to a single value y.\ntemplate <typename T, class Context>\nvoid Dot(const int N, const T* a, const T* b, T* y, Context* context);\n\n// Sum of vector x, and writes the result to a single value y.\ntemplate <typename T, class Context>\nvoid Sum(const int N, const T* x, T* y, Context* context,\n         Tensor<Context>* scratch_ptr = nullptr);\n\n// Sum of squares of vector x, and writes the result to a single value y.\ntemplate <typename T, class Context>\nvoid SumSqr(\n    const int N,\n    const T* x,\n    T* y,\n    Context* context,\n    Tensor<Context>* scratch_ptr = nullptr);\n\n// Select does index selection of the rows a N*D matrix x, and gives the N\n// dimensional vector y that contains the selected data.\ntemplate <typename T, class Context>\nvoid Select(const int N, const int D, const T* x, const int* idx, T* y,\n            Context* context);\n\ntemplate <typename T, class Context>\nvoid Scale(const int N, const float alpha, const T* x, T* y, Context* context);\n\n// Different from the Scale function above, if alpha is passed in\n// as a pointer, we will assume that it lives on the Context device,\n// for example on GPU.\ntemplate <typename T, class Context>\nvoid Scale(const int N, const float* alpha, const T* x, T* y, Context* context);\n\ntemplate <typename T, class Context>\nvoid Axpy(const int N, const float alpha, const T* x, T* y, Context* context);\n\n// Different from the Axpy function above, if alpha is passed in\n// as a pointer, we will assume that it lives on the Context device,\n// for example on GPU.\ntemplate <typename T, class Context>\nvoid Axpy(const int N, const float* alpha, const T* x, T* y, Context* context);\n\ntemplate <typename T, class Context>\nvoid Axpby(\n    const int N,\n    const float alpha,\n    const T* x,\n    const T b,\n    T* y,\n    Context* context);\n\ntemplate <typename T, class Context, int order>\nvoid Im2colNd(\n    const T* data_img,\n    const int* im_shape,\n    const int* col_shape,\n    const int img_size,\n    const int col_size,\n    const int* kernel_shape,\n    const int* stride,\n    const int* dilation,\n    const int* pad,\n    const int N,\n    T* data_col,\n    Context* context,\n    bool accumulate_output = false);\n\ntemplate <typename T, class Context, int order>\nvoid Col2imNd(\n    const T* data_col,\n    const int* img_shape,\n    const int* col_shape,\n    const int img_size,\n    const int col_size,\n    const int* kernel_shape,\n    const int* stride,\n    const int* dilation,\n    const int* pad,\n    const int N,\n    T* data_img,\n    Context* context);\n\ntemplate <typename T, class Context, int order>\nvoid Im2col(\n    const T* data_im,\n    const int channels,\n    const int height,\n    const int width,\n    const int kernel_h,\n    const int kernel_w,\n    const int dilation_h,\n    const int dilation_w,\n    const int pad_t,\n    const int pad_l,\n    const int pad_b,\n    const int pad_r,\n    const int stride_h,\n    const int stride_w,\n    T* data_col,\n    Context* context);\n\ntemplate <typename T, class Context, int order>\nvoid Col2im(\n    const T* data_col,\n    const int channels,\n    const int height,\n    const int width,\n    const int patch_h,\n    const int patch_w,\n    const int dilation_h,\n    const int dilation_w,\n    const int pad_t,\n    const int pad_l,\n    const int pad_b,\n    const int pad_r,\n    const int stride_h,\n    const int stride_w,\n    T* data_im,\n    Context* context);\n\n// Applies a per-channel bias value to each channel of the input\n// image. image_size is H * W\ntemplate <typename T, class Context>\nvoid BiasCHW(\n  const T* bias,\n  const int bias_channels,\n  const int image_size,\n  T* image,\n  Context* context);\n\ntemplate <class Context>\nvoid CopyMatrix(\n    const size_t item_size,\n    const int M,\n    const int N,\n    const void* A,\n    const int lda,\n    void* B,\n    const int ldb,\n    Context* context,\n    TypeMeta::TypedCopy copy = nullptr);\n\ntemplate <typename T, class Context>\nvoid CopyVector(const int N, const T* A, T* B, Context* context);\n\n// Function uses casting from int to unsigned to compare if value of\n// parameter a is greater or equal to zero and lower than value of\n// parameter b. The b parameter is of type signed and is always\n// positive,\n// therefore its value is always lower than 0x800... where casting\n// negative value of a parameter converts it to value higher than\n// 0x800...\n// The casting allows to use one condition instead of two.\ninline bool is_a_ge_zero_and_a_lt_b(int a, int b) {\n  return static_cast<unsigned>(a) < static_cast<unsigned>(b);\n}\n\n// Calculates ceil(a / b). User must be careful to ensure that there\n// is no overflow or underflow in the calculation.\ntemplate <typename T>\nconstexpr T divUp(T a, T b) {\n  return (a + b - (T) 1) / b;\n}\n\n// Rounds a up to the next highest multiple of b. User must be careful\n// to ensure that there is no overflow or underflow in the calculation\n// of divUp.\ntemplate <typename T>\nconstexpr T roundUp(T a, T b) {\n  return divUp<T>(a, b) * b;\n}\n\n// Returns log2(n) for a positive integer type\ntemplate <typename T>\nconstexpr int integerLog2(T n, int p = 0) {\n  return (n <= 1) ? p : integerLog2(n / 2, p + 1);\n}\n\n// Returns the next highest power-of-2 for an integer type\ntemplate <typename T>\nconstexpr T integerNextHighestPowerOf2(T v) {\n  return (integerIsPowerOf2(v) ? (T)2 * v : ((T)1 << (integerLog2(v) + 1)));\n}\n\n}  // namespace math\n}  // namespace caffe2\n\n#include \"caffe2/utils/math-detail.h\"\n#endif  // CAFFE2_UTILS_MATH_H_\n"
  },
  {
    "path": "caffe2/utils/math_cpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Implements the math functions for CPU.\n// The implementation in this file allows us to route the underlying numerical\n// computation library to different backends. Notably:\n// (1) For all BLAS-related functions, one can explicitly request a BLAS backend\n//     such as MKL, openblas or Atlas. To see the set of supported backends\n//     currently provided, check //third_party/blas/.\n// (2) If one chooses to link against MKL, we utilize MKL's vector math library\n//     (VML) for a few functions such as Exp and Log.\n// (3) Fallback implementations are provided in Eigen for cross-platform\n//     support. Since Eigen is a header-only library and supports a number of\n//     platforms, it allows one to quickly port Caffe2 to different platforms\n//     where BLAS may not be present.\n\n#include <algorithm>\n#include <atomic>\n#include <chrono>\n#include <cstring>\n#include <numeric>\n#include <random>\n#include <unordered_set>\n#include <vector>\n\n#include \"caffe2/utils/math.h\"\n#include \"caffe2/utils/cpu_neon.h\"\n#include \"caffe2/core/context.h\"\n#include \"Eigen/Core\"\n#include \"Eigen/Dense\"\n\n#ifdef CAFFE2_USE_MKL\n#include <mkl.h>\n#endif  // CAFFE2_USE_MKL\n\n#ifdef CAFFE2_USE_HPTT\n#include <hptt.h>\n#endif // CAFFE2_USE_HPTT\n\n#if defined(_MSC_VER)\n#include <process.h>\n#endif\n\nnamespace caffe2 {\nnamespace math {\n\n////////////////////////////////////////////////////////////////////////////////\n// BLAS alternatives.\n// Depending on whether we have specified an external BLAS library or not, we\n// will delegate the Caffe math functions that are BLAS-related to either the\n// CBLAS call or the Eigen implementation.\n////////////////////////////////////////////////////////////////////////////////\n#ifdef CAFFE2_USE_EIGEN_FOR_BLAS\n\n// Caffe2 gemm provides a simpler interface to the gemm functions, with the\n// limitation that the data has to be contiguous in memory.\n//\n// The gemm call implements the following operation:\n//\n//                  C = alpha * op(A) * op(B) + beta * C\n//\n// where op(A) has size M x K, op(B) has size K x N, and C has size M x N. Each\n// of A, B, and C are matrices and alpha and beta are scalars. Note that the\n// most common use case of gemm will involve setting alpha to 1 and beta to 0.\n//\n// op(A) and op(B) represent the transformations that are done to A and B before\n// the matrix multiply; depending on the flags set, op(A) is equal to A or A^T\n// (transpose) if the argument TransA or TransB is set to CblasNoTrans or\n// CblasTrans, respectively, for each of A and B.\ntemplate <>\nvoid Gemm<float, CPUContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float* A,\n    const float* B,\n    const float beta,\n    float* C,\n    CPUContext* context,\n    TensorProto::DataType math_type) {\n  auto C_mat = EigenMatrixMap<float>(C, N, M);\n  if (beta == 0) {\n    C_mat.setZero();\n  } else {\n    C_mat *= beta;\n  }\n  switch (TransA) {\n  case CblasNoTrans: {\n    switch (TransB) {\n    case CblasNoTrans:\n      C_mat.noalias() += alpha * (\n          ConstEigenMatrixMap<float>(B, N, K) *\n          ConstEigenMatrixMap<float>(A, K, M));\n      return;\n    case CblasTrans:\n      C_mat.noalias() += alpha * (\n          ConstEigenMatrixMap<float>(B, K, N).transpose() *\n          ConstEigenMatrixMap<float>(A, K, M));\n      return;\n    default:\n      LOG(FATAL) << \"Unexpected CBLAS_TRANSPOSE for TransB\";\n    }\n  }\n  case CblasTrans: {\n    switch (TransB) {\n    case CblasNoTrans:\n      C_mat.noalias() += alpha * (\n          ConstEigenMatrixMap<float>(B, N, K) *\n          ConstEigenMatrixMap<float>(A, M, K).transpose());\n      return;\n    case CblasTrans:\n      C_mat.noalias() += alpha * (\n          ConstEigenMatrixMap<float>(B, K, N).transpose() *\n          ConstEigenMatrixMap<float>(A, M, K).transpose());\n      return;\n    default:\n      LOG(FATAL) << \"Unexpected CBLAS_TRANSPOSE for TransB\";\n    }\n  }\n  default:\n    LOG(FATAL) << \"Unexpected CBLAS_TRANSPOSE for TransA\";\n  }\n}\n\ntemplate <>\nvoid GemmEx<float, CPUContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float* A,\n    const int lda,\n    const float* B,\n    const int ldb,\n    const float beta,\n    float* C,\n    const int ldc,\n    CPUContext*) {\n  using OuterStride = Eigen::OuterStride<Eigen::Dynamic>;\n  using StridedMap = Eigen::Map<Eigen::MatrixXf, 0, OuterStride>;\n  using ConstStridedMap = Eigen::Map<const Eigen::MatrixXf, 0, OuterStride>;\n  auto C_mat = StridedMap(C, N, M, OuterStride(ldc));\n  if (beta == 0) {\n    C_mat.setZero();\n  } else {\n    C_mat *= beta;\n  }\n  switch (TransA) {\n    case CblasNoTrans: {\n      switch (TransB) {\n        case CblasNoTrans:\n          C_mat.noalias() +=\n              alpha * (ConstStridedMap(B, N, K, OuterStride(ldb)) *\n                       ConstStridedMap(A, K, M, OuterStride(lda)));\n          return;\n        case CblasTrans:\n          C_mat.noalias() +=\n              alpha * (ConstStridedMap(B, K, N, OuterStride(ldb)).transpose() *\n                       ConstStridedMap(A, K, M, OuterStride(lda)));\n          return;\n        default:\n          LOG(FATAL) << \"Unexpected CBLAS_TRANSPOSE for TransB\";\n      }\n    }\n    case CblasTrans: {\n      switch (TransB) {\n        case CblasNoTrans:\n          C_mat.noalias() +=\n              alpha * (ConstStridedMap(B, N, K, OuterStride(ldb)) *\n                       ConstStridedMap(A, M, K, OuterStride(lda)).transpose());\n          return;\n        case CblasTrans:\n          C_mat.noalias() +=\n              alpha * (ConstStridedMap(B, K, N, OuterStride(ldb)).transpose() *\n                       ConstStridedMap(A, M, K, OuterStride(lda)).transpose());\n          return;\n        default:\n          LOG(FATAL) << \"Unexpected CBLAS_TRANSPOSE for TransB\";\n      }\n    }\n    default:\n      LOG(FATAL) << \"Unexpected CBLAS_TRANSPOSE for TransA\";\n  }\n}\n\ntemplate <>\nvoid Gemv<float, CPUContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const int M,\n    const int N,\n    const float alpha,\n    const float* A,\n    const float* x,\n    const float beta,\n    float* y,\n    CPUContext* context,\n    TensorProto::DataType math_type) {\n  EigenVectorMap<float> y_vec(y, TransA == CblasNoTrans ? M : N);\n  if (beta == 0) {\n    // In Caffe2 we often do a lazy initialization, which may contain NaNs in\n    // the float values. As a result, if beta is 0, we explicitly do a setzero.\n    y_vec.setZero();\n  } else {\n    y_vec *= beta;\n  }\n  switch (TransA) {\n    case CblasNoTrans: {\n      y_vec.noalias() += alpha * (\n          ConstEigenMatrixMap<float>(A, N, M).transpose() *\n          ConstEigenVectorMap<float>(x, N));\n      return;\n    }\n    case CblasTrans: {\n      y_vec.noalias() += alpha * (\n          ConstEigenMatrixMap<float>(A, N, M) *\n          ConstEigenVectorMap<float>(x, M));\n      return;\n    }\n    default:\n      LOG(FATAL) << \"Gemv float found an unexpected CBLAS_TRANSPOSE input.\";\n  }\n}\n\n#define CAFFE2_SPECIALIZED_SCALE(T)                                            \\\n  template <>                                                                  \\\n  void Scale<T, CPUContext>(                                                   \\\n      const int n, const float alpha, const T* x, T* y, CPUContext* context) { \\\n    EigenVectorMap<T>(y, n) = ConstEigenVectorMap<T>(x, n) * alpha;            \\\n  }                                                                            \\\n  template <>                                                                  \\\n  void Scale<T, CPUContext>(                                                   \\\n      const int n,                                                             \\\n      const float* alpha,                                                      \\\n      const T* x,                                                              \\\n      T* y,                                                                    \\\n      CPUContext* context) {                                                   \\\n    EigenVectorMap<T>(y, n) = ConstEigenVectorMap<T>(x, n) * (*alpha);         \\\n  }\nCAFFE2_SPECIALIZED_SCALE(float)\n#undef CAFFE2_SPECIALIZED_SCALE\n\n#define CAFFE2_SPECIALIZED_DOT(T)                                              \\\ntemplate<>                                                                     \\\nvoid Dot<T, CPUContext>(                                                       \\\n    const int N, const T* a, const T* b, T* y,                                 \\\n    CPUContext* context) {                                                     \\\n  *y = ConstEigenVectorMap<T>(a, N).dot(ConstEigenVectorMap<T>(b, N));         \\\n}\nCAFFE2_SPECIALIZED_DOT(float)\n#undef CAFFE2_SPECIALIZED_DOT\n\n#define CAFFE2_SPECIALIZED_AXPY(T)                                          \\\n  template <>                                                               \\\n  void Axpy<T, CPUContext>(                                                 \\\n      const int N, const T alpha, const T* x, T* Y, CPUContext* context) {  \\\n    EigenVectorMap<T>(Y, N) += ConstEigenVectorMap<T>(x, N) * alpha;        \\\n  }                                                                         \\\n  template <>                                                               \\\n  void Axpy<T, CPUContext>(                                                 \\\n      const int N, const T* alpha, const T* x, T* Y, CPUContext* context) { \\\n    EigenVectorMap<T>(Y, N) += ConstEigenVectorMap<T>(x, N) * (*alpha);     \\\n  }\nCAFFE2_SPECIALIZED_AXPY(float)\n#undef CAFFE2_SPECIALIZED_AXPY\n\n#define CAFFE2_SPECIALIZED_AXPBY(T)                                            \\\ntemplate <>                                                                    \\\nvoid Axpby<T, CPUContext>(const int N, const T alpha, const T* x,              \\\n                          const T beta, T* y, CPUContext* context) {           \\\n  EigenVectorMap<T> y_vec(y, N);                                               \\\n  y_vec = y_vec * beta + ConstEigenVectorMap<T>(x, N) * alpha;                 \\\n}\nCAFFE2_SPECIALIZED_AXPBY(float)\n#undef CAFFE2_SPECIALIZED_AXPBY\n\n#else  // CAFFE2_USE_EIGEN_FOR_BLAS\n\ntemplate <>\nvoid Gemm<float, CPUContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float* A,\n    const float* B,\n    const float beta,\n    float* C,\n    CPUContext* /*context*/,\n    TensorProto::DataType /*math_type*/) {\n  int lda = (TransA == CblasNoTrans) ? K : M;\n  int ldb = (TransB == CblasNoTrans) ? N : K;\n  cblas_sgemm(CblasRowMajor, TransA, TransB, M, N, K, alpha, A, lda, B, ldb,\n              beta, C, N);\n}\n\ntemplate <>\nvoid GemmEx<float, CPUContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float* A,\n    const int lda,\n    const float* B,\n    const int ldb,\n    const float beta,\n    float* C,\n    const int ldc,\n    CPUContext* /*context*/) {\n  cblas_sgemm(CblasRowMajor, TransA, TransB, M, N, K, alpha, A, lda, B, ldb,\n              beta, C, ldc);\n}\n\ntemplate <>\nvoid Gemv<float, CPUContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const int M,\n    const int N,\n    const float alpha,\n    const float* A,\n    const float* x,\n    const float beta,\n    float* y,\n    CPUContext* /*context*/,\n    TensorProto::DataType /*math_type*/) {\n  cblas_sgemv(CblasRowMajor, TransA, M, N, alpha, A, N, x, 1, beta, y, 1);\n}\n\n#define CAFFE2_SPECIALIZED_SCALE(T, prefix)                             \\\n  template <>                                                           \\\n  void Scale<T, CPUContext>(                                            \\\n      const int n, const float alpha, const T* x, T* y, CPUContext*) {  \\\n    if (y != x)                                                         \\\n      cblas_##prefix##copy(n, x, 1, y, 1);                              \\\n    cblas_##prefix##scal(n, static_cast<float>(alpha), y, 1);           \\\n  }                                                                     \\\n  template <>                                                           \\\n  void Scale<T, CPUContext>(                                            \\\n      const int n, const float* alpha, const T* x, T* y, CPUContext*) { \\\n    if (y != x)                                                         \\\n      cblas_##prefix##copy(n, x, 1, y, 1);                              \\\n    cblas_##prefix##scal(n, static_cast<float>(*alpha), y, 1);          \\\n  }\nCAFFE2_SPECIALIZED_SCALE(float, s)\n#undef CAFFE2_SPECIALIZED_SCALE\n\n#define CAFFE2_SPECIALIZED_DOT(T, prefix)                       \\\n  template <>                                                   \\\n  void Dot<T, CPUContext>(                                      \\\n      const int N, const T* a, const T* b, T* y, CPUContext*) { \\\n    *y = cblas_##prefix##dot(N, a, 1, b, 1);                    \\\n  }\nCAFFE2_SPECIALIZED_DOT(float, s)\n#undef CAFFE2_SPECIALIZED_DOT\n\n#define CAFFE2_SPECIALIZED_AXPY(T, prefix)                          \\\n  template <>                                                       \\\n  void Axpy<T, CPUContext>(                                         \\\n      const int N, const T alpha, const T* x, T* y, CPUContext*) {  \\\n    cblas_##prefix##axpy(N, alpha, x, 1, y, 1);                     \\\n  }                                                                 \\\n  template <>                                                       \\\n  void Axpy<T, CPUContext>(                                         \\\n      const int N, const T* alpha, const T* x, T* y, CPUContext*) { \\\n    cblas_##prefix##axpy(N, *alpha, x, 1, y, 1);                    \\\n  }\nCAFFE2_SPECIALIZED_AXPY(float, s)\n#undef CAFFE2_SPECIALIZED_AXPY\n\n// cblas_[sd]axpby is not a standard blas function, and if MKL is not present,\n// we will need to implement it.\n#ifdef CAFFE2_USE_MKL\n#define CAFFE2_SPECIALIZED_AXPBY(T, prefix)            \\\n  template <>                                          \\\n  void Axpby<T, CPUContext>(                           \\\n      const int N,                                     \\\n      const T alpha,                                   \\\n      const T* x,                                      \\\n      const T beta,                                    \\\n      T* y,                                            \\\n      CPUContext*) {                                   \\\n    cblas_##prefix##axpby(N, alpha, x, 1, beta, y, 1); \\\n  }\n#else  // CAFFE2_USE_MKL\n#define CAFFE2_SPECIALIZED_AXPBY(T, prefix)     \\\n  template <>                                   \\\n  void Axpby<T, CPUContext>(                    \\\n      const int N,                              \\\n      const T alpha,                            \\\n      const T* x,                               \\\n      const T beta,                             \\\n      T* y,                                     \\\n      CPUContext*) {                            \\\n    cblas_##prefix##scal(N, beta, y, 1);        \\\n    cblas_##prefix##axpy(N, alpha, x, 1, y, 1); \\\n  }\n#endif  // CAFFE2_USE_MKL\nCAFFE2_SPECIALIZED_AXPBY(float, s)\n#undef CAFFE2_SPECIALIZED_AXPBY\n\n#endif  // CAFFE2_USE_EIGEN_FOR_BLAS\n\ntemplate <>\nvoid GemmBatched<float, CPUContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int batch_size,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float* A,\n    const float* B,\n    const float beta,\n    float* C,\n    CPUContext* context,\n    Tensor<CPUContext>*, /* scratch */\n    TensorProto::DataType /* math_type */) {\n  const int a_stride = M * K;\n  const int b_stride = K * N;\n  const int c_stride = M * N;\n\n#ifdef CAFFE2_USE_MKL\n  (void)context;\n\n  const int lda = (TransA == CblasNoTrans) ? K : M;\n  const int ldb = (TransB == CblasNoTrans) ? N : K;\n  std::vector<const float*> a_array(batch_size, nullptr);\n  std::vector<const float*> b_array(batch_size, nullptr);\n  std::vector<float*> c_array(batch_size, nullptr);\n  for (int i = 0; i < batch_size; ++i) {\n    a_array[i] = A + a_stride * i;\n    b_array[i] = B + b_stride * i;\n    c_array[i] = C + c_stride * i;\n  }\n  cblas_sgemm_batch(\n      CblasRowMajor,\n      &TransA,\n      &TransB,\n      &M,\n      &N,\n      &K,\n      &alpha,\n      a_array.data(),\n      &lda,\n      b_array.data(),\n      &ldb,\n      &beta,\n      c_array.data(),\n      &N, // ldc_array\n      1,\n      &batch_size);\n#else // CAFFE2_USE_MKL\n  // loop over matrices in the batch\n  for (int i = 0; i < batch_size; ++i) {\n    math::Gemm<float, CPUContext>(\n        TransA,\n        TransB,\n        M,\n        N,\n        K,\n        alpha,\n        A + a_stride * i,\n        B + b_stride * i,\n        beta,\n        C + c_stride * i,\n        context);\n  }\n#endif\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// MKL VML alternatives.\n// Depending on whether we are using MKL, we will delegate the Caffe math\n// functions that are VML-related to either the VML call or the Eigen\n// implementation. If you are setting the flags (such as AVX) right for your CPU\n// architecture, usually Eigen will deliver a throughput as fast as the VML\n// functions.\n////////////////////////////////////////////////////////////////////////////////\n#ifdef CAFFE2_USE_MKL\n\n#define DELEGATE_SIMPLE_UNARY_FUNCTION(T, Funcname, OriginalFunc, ...)       \\\n  template <>                                                                \\\n  void Funcname<T, CPUContext>(const int N, const T* x, T* y, CPUContext*) { \\\n    OriginalFunc(N, x, y, ##__VA_ARGS__);                                    \\\n  }\nDELEGATE_SIMPLE_UNARY_FUNCTION(\n    float,\n    Exp,\n    vmsExp,\n    VML_HA | VML_FTZDAZ_OFF | VML_ERRMODE_IGNORE)\nDELEGATE_SIMPLE_UNARY_FUNCTION(\n    double,\n    Exp,\n    vmdExp,\n    VML_HA | VML_FTZDAZ_OFF | VML_ERRMODE_IGNORE)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, Log, vsLn)\nDELEGATE_SIMPLE_UNARY_FUNCTION(double, Log, vdLn)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, Cos, vsCos)\nDELEGATE_SIMPLE_UNARY_FUNCTION(double, Cos, vdCos)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, Sin, vsSin)\nDELEGATE_SIMPLE_UNARY_FUNCTION(double, Sin, vdSin)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, Abs, vsAbs)\nDELEGATE_SIMPLE_UNARY_FUNCTION(double, Abs, vdAbs)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, Sqrt, vsSqrt)\nDELEGATE_SIMPLE_UNARY_FUNCTION(double, Sqrt, vdSqrt)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, InvSqrt, vsInvSqrt)\nDELEGATE_SIMPLE_UNARY_FUNCTION(double, InvSqrt, vdInvSqrt)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, Sqr, vsSqr)\nDELEGATE_SIMPLE_UNARY_FUNCTION(double, Sqr, vdSqr)\n#undef DELEGATE_SIMPLE_UNARY_FUNCTION\n\n#define DELEGATE_SINCOS_FUNCTION(T, OriginalFunc)           \\\n  template <>                                               \\\n  void SinCos<T, CPUContext>(                               \\\n      const int N, const T* a, T* ys, T* yc, CPUContext*) { \\\n    OriginalFunc(N, a, ys, yc);                             \\\n  }\nDELEGATE_SINCOS_FUNCTION(float, vsSinCos)\nDELEGATE_SINCOS_FUNCTION(double, vdSinCos)\n#undef DELEGATE_SINCOS_FUNCTION\n\n#define DELEGATE_POWX_FUNCTION(T, OriginalFunc)                               \\\n  template <>                                                                 \\\n  void Powx<T, CPUContext>(const int N, const T* a, T b, T* y, CPUContext*) { \\\n    OriginalFunc(N, a, b, y);                                                 \\\n  }\nDELEGATE_POWX_FUNCTION(float, vsPowx)\nDELEGATE_POWX_FUNCTION(double, vdPowx)\n#undef DELEGATE_POWX_FUNCTION\n\n#define DELEGATE_SIMPLE_BINARY_FUNCTION(T, Funcname, OriginalFunc) \\\n  template <>                                                      \\\n  void Funcname<T, CPUContext>(                                    \\\n      const int N, const T* a, const T* b, T* y, CPUContext*) {    \\\n    OriginalFunc(N, a, b, y);                                      \\\n  }\nDELEGATE_SIMPLE_BINARY_FUNCTION(float,  Add, vsAdd)\nDELEGATE_SIMPLE_BINARY_FUNCTION(double, Add, vdAdd)\nDELEGATE_SIMPLE_BINARY_FUNCTION(float,  Sub, vsSub)\nDELEGATE_SIMPLE_BINARY_FUNCTION(double, Sub, vdSub)\nDELEGATE_SIMPLE_BINARY_FUNCTION(float,  Mul, vsMul)\nDELEGATE_SIMPLE_BINARY_FUNCTION(double, Mul, vdMul)\nDELEGATE_SIMPLE_BINARY_FUNCTION(float,  Div, vsDiv)\nDELEGATE_SIMPLE_BINARY_FUNCTION(double, Div, vdDiv)\n#undef DELEGATE_SIMPLE_BINARY_FUNCTION\n\n#else  // CAFFE2_USE_MKL\n\n#define DELEGATE_SIMPLE_UNARY_FUNCTION(T, Funcname, expr)                    \\\n  template <>                                                                \\\n  void Funcname<T, CPUContext>(const int N, const T* x, T* y, CPUContext*) { \\\n    EigenVectorMap<T>(y, N) = ConstEigenVectorMap<T>(x, N).array().expr();   \\\n  }\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, Exp, exp)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, Log, log)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, Cos, cos)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, Sin, sin)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, Abs, abs)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, Sqrt, sqrt)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, InvSqrt, rsqrt)\nDELEGATE_SIMPLE_UNARY_FUNCTION(float, Sqr, square)\n#undef DELEGATE_SIMPLE_UNARY_FUNCTION\n\n#define DELEGATE_SINCOS_FUNCTION(T)                                        \\\n  template <>                                                              \\\n  void SinCos<T, CPUContext>(                                              \\\n      const int N, const T* x, T* ys, T* yc, CPUContext*) {                \\\n    EigenVectorMap<T>(ys, N) = ConstEigenVectorMap<T>(x, N).array().sin(); \\\n    EigenVectorMap<T>(yc, N) = ConstEigenVectorMap<T>(x, N).array().cos(); \\\n  }\nDELEGATE_SINCOS_FUNCTION(float)\nDELEGATE_SINCOS_FUNCTION(double)\n#undef DELEGATE_SINCOS_FUNCTION\n\n#define DELEGATE_POWX_FUNCTION(T)                                             \\\n  template <>                                                                 \\\n  void Powx<T, CPUContext>(const int N, const T* a, T b, T* y, CPUContext*) { \\\n    EigenVectorMap<T>(y, N) = ConstEigenVectorMap<T>(a, N).array().pow(b);    \\\n  }\nDELEGATE_POWX_FUNCTION(float)\n#undef DELEGATE_POWX_FUNCTION\n\n#endif  // CAFFE2_USE_MKL\n\n\n#define EIGEN_SIMPLE_BINARY_FUNCTION(T, Funcname, expr)                        \\\ntemplate <>                                                                    \\\nvoid Funcname<T, CPUContext>(                                                  \\\n    const int N, const T* a, const T* b, T* y,                                 \\\n    CPUContext*) {                                                             \\\n  EigenVectorMap<T>(y, N) =                                                    \\\n      ConstEigenVectorMap<T>(a, N).array() expr                                \\\n      ConstEigenVectorMap<T>(b, N).array();                                    \\\n}\n\n#ifdef CAFFE2_USE_MKL\n\n#define DEFINE_SIMPLE_BINARY_FUNCTION(Funcname, expr)                          \\\nEIGEN_SIMPLE_BINARY_FUNCTION(int32_t, Funcname, expr)                          \\\nEIGEN_SIMPLE_BINARY_FUNCTION(int64_t, Funcname, expr)\n\n#else\n\n#define DEFINE_SIMPLE_BINARY_FUNCTION(Funcname, expr)                          \\\nEIGEN_SIMPLE_BINARY_FUNCTION(float, Funcname, expr)                            \\\nEIGEN_SIMPLE_BINARY_FUNCTION(int32_t, Funcname, expr)                          \\\nEIGEN_SIMPLE_BINARY_FUNCTION(int64_t, Funcname, expr)\n\n#endif\n\nDEFINE_SIMPLE_BINARY_FUNCTION(Add, +)\nDEFINE_SIMPLE_BINARY_FUNCTION(Sub, -)\nDEFINE_SIMPLE_BINARY_FUNCTION(Mul, *)\nDEFINE_SIMPLE_BINARY_FUNCTION(Div, /)\n\n#undef EIGEN_SIMPLE_BINARY_FUNCTION\n#undef DEFINE_FLOAT_BINARY_FUNCTION\n\n\n////////////////////////////////////////////////////////////////////////////////\n// Common math functions being used in Caffe that do not have a BLAS or MKL\n// equivalent. For all these functions, we will simply implement them either via\n// Eigen or via custom code.\n////////////////////////////////////////////////////////////////////////////////\n\n#define CAFFE2_SPECIALIZED_REDUCEMIN(T)    \\\n  template <>                              \\\n  void ReduceMin<T, CPUContext>(           \\\n      const int N,                         \\\n      const T* x,                          \\\n      T* y,                                \\\n      Tensor<CPUContext>* /*scratch_ptr*/, \\\n      CPUContext* /*context*/) {           \\\n    *y = *std::min_element(x, x + N);      \\\n  }\nCAFFE2_SPECIALIZED_REDUCEMIN(float)\n#undef CAFFE2_SPECIALIZED_REDUCEMIN\n\n#define CAFFE2_SPECIALIZED_REDUCEMAX(T)    \\\n  template <>                              \\\n  void ReduceMax<T, CPUContext>(           \\\n      const int N,                         \\\n      const T* x,                          \\\n      T* y,                                \\\n      Tensor<CPUContext>* /*scratch_ptr*/, \\\n      CPUContext* /*context*/) {           \\\n    *y = *std::max_element(x, x + N);      \\\n  }\nCAFFE2_SPECIALIZED_REDUCEMAX(float)\nCAFFE2_SPECIALIZED_REDUCEMAX(int32_t)\nCAFFE2_SPECIALIZED_REDUCEMAX(int64_t)\n\n#undef CAFFE2_SPECIALIZED_REDUCEMAX\n\n#define CAFFE2_SPECIALIZED_ROWWISEMAX(T)                         \\\n  template <>                                                    \\\n  void RowwiseMax<T, CPUContext>(                                \\\n      const int N, const int D, const T* x, T* y, CPUContext*) { \\\n    EigenVectorMap<T>(y, N) =                                    \\\n        ConstEigenMatrixMap<T>(x, D, N).colwise().maxCoeff();    \\\n  }\nCAFFE2_SPECIALIZED_ROWWISEMAX(float)\n#undef CAFFE2_SPECIALIZED_ROWWISEMAX\n\n#define CAFFE2_SPECIALIZED_COLWISEMAX(T)                         \\\n  template <>                                                    \\\n  void ColwiseMax<T, CPUContext>(                                \\\n      const int N, const int D, const T* x, T* y, CPUContext*) { \\\n    EigenVectorMap<T>(y, D) =                                    \\\n        ConstEigenMatrixMap<T>(x, D, N).rowwise().maxCoeff();    \\\n  }\nCAFFE2_SPECIALIZED_COLWISEMAX(float)\n#undef CAFFE2_SPECIALIZED_COLWISEMAX\n\n#define CAFFE2_SPECIALIZED_ELEMWISEMAX(T)                                   \\\n  template <>                                                               \\\n  void ElemwiseMax<T, CPUContext>(                                          \\\n      const int N, const T* x, const T* y, T* z, CPUContext* /*context*/) { \\\n    std::transform(x, x + N, y, z, [](const T& x_i, const T& y_i) {         \\\n      return std::max(x_i, y_i);                                            \\\n    });                                                                     \\\n  }\nCAFFE2_SPECIALIZED_ELEMWISEMAX(float)\n#undef CAFFE2_SPECIALIZED_ELEMWISEMAX\n\n#define CAFFE2_SPECIALIZED_MAXIMUM(T)                                          \\\n  template <>                                                                  \\\n  void Maximum<T, CPUContext>(                                                 \\\n      const int N, const float alpha, const T* x, T* y, CPUContext* context) { \\\n    std::transform(                                                            \\\n        x, x + N, y, [&alpha](const T& x_i) { return std::max(x_i, alpha); }); \\\n  }\nCAFFE2_SPECIALIZED_MAXIMUM(float)\n#undef CAFFE2_SPECIALIZED_MAXIMUM\n\n// AddToRow and AddToCol adds the corresponding row/col vector b to the matrix a\n// of shape M x N. The actual implementation uses eigen which is column major,\n// so notice the row/column swap in the actual implementation.\n#define DELEGATE_BROADCAST_BINARY_FUNCTION(T, Funcname, expr)                \\\n  template <>                                                                \\\n  void Funcname##ToRow<T, CPUContext>(                                       \\\n      const int M, const int N, const T* a, const T* b, T* y, CPUContext*) { \\\n    EigenArrayMap<T>(y, N, M) = ConstEigenArrayMap<T>(a, N, M).colwise()     \\\n                                    expr ConstEigenVectorArrayMap<T>(b, N);  \\\n  }                                                                          \\\n  /* inplace versions */                                                     \\\n  template <>                                                                \\\n  void Funcname##ToRow<T, CPUContext>(                                       \\\n      const int M, const int N, const T* x, T* y, CPUContext*) {             \\\n    EigenArrayMap<T>(y, N, M).colwise() expr## =                             \\\n        ConstEigenVectorArrayMap<T>(x, N);                                   \\\n  }                                                                          \\\n  template <>                                                                \\\n  void Funcname##ToCol<T, CPUContext>(                                       \\\n      const int M, const int N, const T* x, T* y, CPUContext*) {             \\\n    EigenArrayMap<T>(y, N, M).rowwise() expr## =                             \\\n        ConstEigenVectorArrayMap<T>(x, M).transpose();                       \\\n  }\n\n#define DEFINE_BROADCAST_BINARY_FUNCTION(name, op)                       \\\n  DELEGATE_BROADCAST_BINARY_FUNCTION(int32_t, name, op)                  \\\n  DELEGATE_BROADCAST_BINARY_FUNCTION(int64_t, name, op)                  \\\n  DELEGATE_BROADCAST_BINARY_FUNCTION(float, name, op)                    \\\n\nDEFINE_BROADCAST_BINARY_FUNCTION(Add, +)\nDEFINE_BROADCAST_BINARY_FUNCTION(Sub, -)\nDEFINE_BROADCAST_BINARY_FUNCTION(Mul, *)\nDEFINE_BROADCAST_BINARY_FUNCTION(Div, /)\n\n#undef DEFINE_BROADCAST_BINARY_FUNCTION\n#undef DELEGATE_BROADCAST_BINARY_FUNCTION\n\n#define CAFFE2_SPECIALIZED_SET(T)                                             \\\n  template <>                                                                 \\\n  void Set<T, CPUContext>(const size_t N, const T alpha, T* Y, CPUContext*) { \\\n    if (alpha == (T)0) {                                                      \\\n      if (Y != nullptr) {                                                     \\\n        memset(Y, 0, N * sizeof(T));                                          \\\n      }                                                                       \\\n    } else {                                                                  \\\n      EigenVectorMap<T>(Y, N).setConstant(alpha);                             \\\n    }                                                                         \\\n  }\n\nCAFFE2_SPECIALIZED_SET(float);\nCAFFE2_SPECIALIZED_SET(double);\nCAFFE2_SPECIALIZED_SET(int8_t);\nCAFFE2_SPECIALIZED_SET(int16_t);\nCAFFE2_SPECIALIZED_SET(int);\nCAFFE2_SPECIALIZED_SET(int64_t);\nCAFFE2_SPECIALIZED_SET(bool);\nCAFFE2_SPECIALIZED_SET(char);\nCAFFE2_SPECIALIZED_SET(uint8_t);\nCAFFE2_SPECIALIZED_SET(uint16_t);\n#undef CAFFE2_SPECIALIZED_SET\n\n#define CAFFE2_INSTANTIATE_BINARY_OP(name, op, T)                  \\\n  template <>                                                      \\\n  void name<T, CPUContext>(                                        \\\n      const int n, const T* a, const T* b, bool* y, CPUContext*) { \\\n    for (int i = 0; i < n; ++i) {                                  \\\n      y[i] = a[i] op b[i];                                         \\\n    }                                                              \\\n  }                                                                \\\n  template <>                                                      \\\n  void name##ToRow<T, CPUContext>(                                 \\\n      const int m,                                                 \\\n      const int n,                                                 \\\n      const T* a,                                                  \\\n      const T* b,                                                  \\\n      bool* y,                                                     \\\n      CPUContext*) {                                               \\\n    for (int i = 0; i < n * m; ++i) {                              \\\n      y[i] = a[i] op b[i % n];                                     \\\n    }                                                              \\\n  }\n\n#define CAFFE2_DEFINE_BINARY_OP(name, op)         \\\n  CAFFE2_INSTANTIATE_BINARY_OP(name, op, float)   \\\n  CAFFE2_INSTANTIATE_BINARY_OP(name, op, int32_t) \\\n  CAFFE2_INSTANTIATE_BINARY_OP(name, op, int64_t)\n\nCAFFE2_DEFINE_BINARY_OP(LT, <);\nCAFFE2_DEFINE_BINARY_OP(LE, <=);\nCAFFE2_DEFINE_BINARY_OP(GT, >);\nCAFFE2_DEFINE_BINARY_OP(GE, >=);\n\nCAFFE2_INSTANTIATE_BINARY_OP(Or, |, bool);\nCAFFE2_INSTANTIATE_BINARY_OP(And, &, bool);\nCAFFE2_INSTANTIATE_BINARY_OP(Xor, ^, bool);\n\ntemplate <>\nvoid Not<bool, CPUContext>(\n    const int n,\n    const bool* x,\n    bool* y,\n    CPUContext* /*context*/) {\n  for (int i = 0; i < n; ++i) {\n    y[i] = !x[i];\n  }\n}\n\n#undef CAFFE2_DEFINE_BINARY_OP\n#undef CAFFE2_INSTANTIATE_BINARY_OP\n\n#define CAFFE2_SPECIALIZED_CPU_ADD_STRIPED_BATCH(T)             \\\n  template <>                                                   \\\n  void AddStripedBatch(                                         \\\n      const int N,                                              \\\n      const T* first,                                           \\\n      T* y,                                                     \\\n      const int stripe,                                         \\\n      const int batch,                                          \\\n      CPUContext* context) {                                    \\\n    for (int j = 0; j < batch; j++) {                           \\\n      Add<T, CPUContext>(N, first + j * stripe, y, y, context); \\\n    }                                                           \\\n  }\n\nCAFFE2_SPECIALIZED_CPU_ADD_STRIPED_BATCH(float);\n#undef CAFFE2_SPECIALIZED_CPU_ADD_STRIPED_BATCH\n\ntemplate <>\nvoid RandUniform<float, CPUContext>(\n    const size_t n,\n    const float a,\n    const float b,\n    float* r,\n    CPUContext* context) {\n  std::uniform_real_distribution<float> distribution(a, b);\n  for (auto i = 0; i < n; ++i) {\n    r[i] = distribution(context->RandGenerator());\n  }\n}\n\ntemplate <>\nvoid RandUniform<int, CPUContext>(\n    const size_t n,\n    const int a,\n    const int b,\n    int* r,\n    CPUContext* context) {\n  std::uniform_int_distribution<int> distribution(a, b);\n  for (auto i = 0; i < n; ++i) {\n    r[i] = distribution(context->RandGenerator());\n  }\n}\n\n#define CAFFE2_SPECIALIZED_RAND_UNIFORM_UNIQUE(T)                      \\\n  template <>                                                          \\\n  void RandUniformUnique<T, CPUContext>(                               \\\n      const size_t n,                                                  \\\n      const T a,                                                       \\\n      const T b,                                                       \\\n      T* r,                                                            \\\n      const size_t m,                                                  \\\n      const T* avoid,                                                  \\\n      CPUContext* context) {                                           \\\n    CAFFE_ENFORCE_LE(                                                  \\\n        n, b - a - m + 1, \"Cannot satisfy the unique requirement\");    \\\n    std::unordered_set<T> avoid_set(n);                                \\\n    if (m) {                                                           \\\n      avoid_set.insert(avoid, avoid + m);                              \\\n      CAFFE_ENFORCE_EQ(m, avoid_set.size(), \"Avoid should be unique\"); \\\n    }                                                                  \\\n    std::uniform_int_distribution<T> distribution(a, b);               \\\n    T v = 0;                                                           \\\n    for (size_t i = 0; i < n; ++i) {                                   \\\n      do {                                                             \\\n        v = distribution(context->RandGenerator());                    \\\n      } while (avoid_set.count(v));                                    \\\n      r[i] = v;                                                        \\\n      avoid_set.insert(v);                                             \\\n    }                                                                  \\\n  }\n\nCAFFE2_SPECIALIZED_RAND_UNIFORM_UNIQUE(int32_t);\nCAFFE2_SPECIALIZED_RAND_UNIFORM_UNIQUE(int64_t);\n#undef CAFFE2_SPECIALIZED_RAND_UNIFORM_UNIQUE\n\ntemplate <>\nvoid RandGaussian<float, CPUContext>(\n    const size_t n,\n    const float mean,\n    const float std,\n    float* r,\n    CPUContext* context) {\n  std::normal_distribution<float> distribution(mean, std);\n  for (auto i = 0; i < n; ++i) {\n    r[i] = distribution(context->RandGenerator());\n  }\n}\n\n#define CAFFE2_SPECIALIZED_SUM(T)            \\\n  template <>                                \\\n  void Sum<T, CPUContext>(                   \\\n      const int N,                           \\\n      const T* x,                            \\\n      T* y,                                  \\\n      CPUContext* /* unused */,              \\\n      Tensor<CPUContext>* /* unused */) {    \\\n    *y = ConstEigenVectorMap<T>(x, N).sum(); \\\n  }\n\nCAFFE2_SPECIALIZED_SUM(float);\nCAFFE2_SPECIALIZED_SUM(int32_t);\nCAFFE2_SPECIALIZED_SUM(int64_t);\n\n#undef CAFFE2_SPECIALIZED_SUM\n\ntemplate <>\nvoid SumSqr<float, CPUContext>(\n    const int N,\n    const float* x,\n    float* y,\n    CPUContext* /*context*/ /* unused */,\n    Tensor<CPUContext>* /*scratch_ptr*/ /* unused */) {\n  *y = ConstEigenVectorMap<float>(x, N).squaredNorm();\n}\n\ntemplate <>\nvoid Select<float, CPUContext>(\n    const int N,\n    const int D,\n    const float* x,\n    const int* idx,\n    float* y,\n    CPUContext* /*context*/) {\n  for (int i = 0; i < N; ++i) {\n    DCHECK_LT(idx[i], D);\n    y[i] = x[i * D + idx[i]];\n  }\n}\n// Ported from caffe 1.\ntemplate <>\nvoid Im2colNd<float, CPUContext, StorageOrder::NCHW>(\n    const float* data_img,\n    const int* im_shape,\n    const int* col_shape,\n    const int /* img_size*/,\n    const int /* col_size*/,\n    const int* kernel_shape,\n    const int* stride,\n    const int* dilation,\n    const int* pad,\n    const int N,\n    float* data_col,\n    CPUContext* /* context */,\n    bool accumulate_output) {\n  int kernel_size = 1;\n  for (int i = 0; i < N; ++i) {\n    kernel_size *= kernel_shape[i];\n  }\n  const int channels_col = col_shape[0];\n  vector<int> d_offset(N, 0);\n  vector<int> d_iter(N, 0);\n  for (int c_col = 0; c_col < channels_col; ++c_col) {\n    // Loop over spatial axes in reverse order to compute a per-axis offset.\n    int offset = c_col;\n    for (int d_i = N - 1; d_i >= 0; --d_i) {\n      if (d_i < N - 1) {\n        offset /= kernel_shape[d_i + 1];\n      }\n      d_offset[d_i] = offset % kernel_shape[d_i];\n    }\n    for (bool incremented = true; incremented;) {\n      // Loop over spatial axes in forward order to compute the indices in the\n      // image and column, and whether the index lies in the padding.\n      int index_col = c_col;\n      int index_im = c_col / kernel_size;\n      bool is_padding = false;\n      for (int d_i = 0; d_i < N; ++d_i) {\n        const int d = d_iter[d_i];\n        const int d_im =\n            d * stride[d_i] - pad[d_i] + d_offset[d_i] * dilation[d_i];\n        is_padding |= d_im < 0 || d_im >= im_shape[d_i + 1];\n        index_col *= col_shape[d_i + 1];\n        index_col += d;\n        index_im *= im_shape[d_i + 1];\n        index_im += d_im;\n      }\n      if (!accumulate_output) {\n        if (is_padding) {\n          data_col[index_col] = 0;\n        } else {\n          data_col[index_col] = data_img[index_im];\n        }\n      } else if (!is_padding) { // col2im\n        data_col[index_im] += data_img[index_col];\n      }\n      // Loop over spatial axes in reverse order to choose an index,\n      // like counting.\n      incremented = false;\n      for (int d_i = N - 1; d_i >= 0; --d_i) {\n        const int d_max = col_shape[d_i + 1];\n        DCHECK_LT(d_iter[d_i], d_max);\n        if (d_iter[d_i] == d_max - 1) {\n          d_iter[d_i] = 0;\n        } else { // d_iter[d_i] < d_max - 1\n          ++d_iter[d_i];\n          incremented = true;\n          break;\n        }\n      }\n    } // while(incremented) {\n  } // for (int c = 0; c < channels_col; ++c) {\n}\n\ntemplate <>\nvoid Col2imNd<float, CPUContext, StorageOrder::NCHW>(\n    const float* data_col,\n    const int* img_shape,\n    const int* col_shape,\n    const int img_size,\n    const int col_size,\n    const int* kernel_shape,\n    const int* stride,\n    const int* dilation,\n    const int* pad,\n    const int N,\n    float* data_img,\n    CPUContext* context) {\n  Set<float, CPUContext>(img_size, 0, data_img, context);\n  Im2colNd<float, CPUContext, StorageOrder::NCHW>(\n      data_col,\n      img_shape,\n      col_shape,\n      img_size,\n      col_size,\n      kernel_shape,\n      stride,\n      dilation,\n      pad,\n      N,\n      data_img,\n      context,\n      true);\n}\n\ntemplate <>\nvoid Im2col<float, CPUContext, StorageOrder::NCHW>(\n    const float* data_im,\n    const int channels,\n    const int height,\n    const int width,\n    const int kernel_h,\n    const int kernel_w,\n    const int dilation_h,\n    const int dilation_w,\n    const int pad_t,\n    const int pad_l,\n    const int pad_b,\n    const int pad_r,\n    const int stride_h,\n    const int stride_w,\n    float* data_col,\n    CPUContext* /*context*/) {\n  const int output_h =\n      (height + pad_b + pad_t - (dilation_h * (kernel_h - 1) + 1)) / stride_h +\n      1;\n  const int output_w =\n      (width + pad_l + pad_r - (dilation_w * (kernel_w - 1) + 1)) / stride_w +\n      1;\n\n  // Fast path for zero padding and no dilation\n  // From Torch, THNN_(unfolded_copy)\n  if (dilation_h == 1 && dilation_w == 1 && pad_l == 0 && pad_r == 0 &&\n      pad_t == 0 && pad_b == 0) {\n    for (auto k = 0; k < channels * kernel_h * kernel_w; k++) {\n      const auto nip = k / (kernel_h * kernel_w);\n      const auto rest = k % (kernel_h * kernel_w);\n      const auto kh = rest / kernel_w;\n      const auto kw = rest % kernel_w;\n      auto* dst = data_col + nip * (kernel_h * kernel_w * output_h * output_w) +\n          kh * (kernel_w * output_h * output_w) + kw * (output_h * output_w);\n      const auto* src = data_im + nip * (height * width);\n      for (auto y = 0; y < output_h; y++) {\n        const auto iy = y * stride_h + kh;\n        const auto ix = kw;\n        if (stride_w == 1) {\n          memcpy(\n              dst + (y * output_w),\n              src + (iy * width + ix),\n              sizeof(float) * output_w);\n        } else {\n          for (auto x = 0; x < output_w; x++) {\n            memcpy(\n                dst + (y * output_w + x),\n                src + (iy * width + ix + x * stride_w),\n                sizeof(float));\n          }\n        }\n      }\n    }\n    return;\n  }\n\n  // Fast path for equal padding\n  if (pad_l == pad_r && pad_t == pad_b) {\n    // From Intel, https://github.com/BVLC/caffe/pull/3536\n    const int pad_h = pad_t;\n    const int pad_w = pad_l;\n    const int channel_size = height * width;\n    for (int channel = channels; channel--; data_im += channel_size) {\n      for (int kernel_row = 0; kernel_row < kernel_h; kernel_row++) {\n        for (int kernel_col = 0; kernel_col < kernel_w; kernel_col++) {\n          int input_row = -pad_h + kernel_row * dilation_h;\n          for (int output_rows = output_h; output_rows; output_rows--) {\n            if (!is_a_ge_zero_and_a_lt_b(input_row, height)) {\n              for (int output_cols = output_w; output_cols; output_cols--) {\n                *(data_col++) = 0;\n              }\n            } else {\n              int input_col = -pad_w + kernel_col * dilation_w;\n              for (int output_col = output_w; output_col; output_col--) {\n                if (is_a_ge_zero_and_a_lt_b(input_col, width)) {\n                  *(data_col++) = data_im[input_row * width + input_col];\n                } else {\n                  *(data_col++) = 0;\n                }\n                input_col += stride_w;\n              }\n            }\n            input_row += stride_h;\n          }\n        }\n      }\n    }\n    return;\n  }\n\n  // Baseline\n  const int dkernel_h = dilation_h * (kernel_h - 1) + 1;\n  const int dkernel_w = dilation_w * (kernel_w - 1) + 1;\n\n  int height_col = (height + pad_t + pad_b - dkernel_h) / stride_h + 1;\n  int width_col = (width + pad_l + pad_r - dkernel_w) / stride_w + 1;\n\n  int channels_col = channels * kernel_h * kernel_w;\n  for (int c = 0; c < channels_col; ++c) {\n    int w_offset = c % kernel_w;\n    int h_offset = (c / kernel_w) % kernel_h;\n    int c_im = c / kernel_h / kernel_w;\n    for (int h = 0; h < height_col; ++h) {\n      for (int w = 0; w < width_col; ++w) {\n        int h_pad = h * stride_h - pad_t + h_offset * dilation_h;\n        int w_pad = w * stride_w - pad_l + w_offset * dilation_w;\n        if (h_pad >= 0 && h_pad < height && w_pad >= 0 && w_pad < width)\n          data_col[(c * height_col + h) * width_col + w] =\n              data_im[(c_im * height + h_pad) * width + w_pad];\n        else\n          data_col[(c * height_col + h) * width_col + w] = 0;\n      }\n    }\n  }\n}\n\ntemplate <>\nvoid Im2col<float, CPUContext, StorageOrder::NHWC>(\n    const float* data_im,\n    const int channels,\n    const int height,\n    const int width,\n    const int kernel_h,\n    const int kernel_w,\n    const int dilation_h,\n    const int dilation_w,\n    const int pad_t,\n    const int pad_l,\n    const int pad_b,\n    const int pad_r,\n    const int stride_h,\n    const int stride_w,\n    float* data_col,\n    CPUContext* /*context*/) {\n  const int dkernel_h = dilation_h * (kernel_h - 1) + 1;\n  const int dkernel_w = dilation_w * (kernel_w - 1) + 1;\n\n  int height_col = (height + pad_t + pad_b - dkernel_h) / stride_h + 1;\n  int width_col = (width + pad_l + pad_r - dkernel_w) / stride_w + 1;\n\n  int h_pad = -pad_t;\n  for (int h = 0; h < height_col; ++h) {\n    int w_pad = -pad_l;\n    for (int w = 0; w < width_col; ++w) {\n      for (int ih = h_pad; ih < h_pad + dkernel_h; ih += dilation_h) {\n        for (int iw = w_pad; iw < w_pad + dkernel_w; iw += dilation_w) {\n          if (ih >= 0 && ih < height && iw >= 0 && iw < width) {\n            memcpy(data_col, data_im + (ih * width + iw) * channels,\n                   sizeof(float) * channels);\n          } else {\n            // This should be simply padded with zero.\n            memset(data_col, 0, sizeof(float) * channels);\n          }\n          data_col += channels;\n        }\n      }\n      w_pad += stride_w;\n    }\n    h_pad += stride_h;\n  }\n}\n\ntemplate <>\nvoid Col2im<float, CPUContext, StorageOrder::NCHW>(\n    const float* data_col,\n    const int channels,\n    const int height,\n    const int width,\n    const int kernel_h,\n    const int kernel_w,\n    const int dilation_h,\n    const int dilation_w,\n    const int pad_t,\n    const int pad_l,\n    const int pad_b,\n    const int pad_r,\n    const int stride_h,\n    const int stride_w,\n    float* data_im,\n    CPUContext* context) {\n  const int output_h =\n      (height + pad_b + pad_t - (dilation_h * (kernel_h - 1) + 1)) / stride_h +\n      1;\n  const int output_w =\n      (width + pad_l + pad_r - (dilation_w * (kernel_w - 1) + 1)) / stride_w +\n      1;\n\n  Set<float, CPUContext>(height * width * channels, 0, data_im, context);\n\n  // Fast path for zero padding and no dilation\n  // From Torch, modified THNN_(unfolded_acc)\n  if (dilation_h == 1 && dilation_w == 1 && pad_l == 0 && pad_r == 0 &&\n      pad_t == 0 && pad_b == 0) {\n    for (auto k = 0; k < channels * kernel_h * kernel_w; k++) {\n      const auto nip = k / (kernel_h * kernel_w);\n      const auto rest = k % (kernel_h * kernel_w);\n      const auto kh = rest / kernel_w;\n      const auto kw = rest % kernel_w;\n      const auto* dst = data_col +\n          nip * (kernel_h * kernel_w * output_h * output_w) +\n          kh * (kernel_w * output_h * output_w) + kw * (output_h * output_w);\n      auto* src = data_im + nip * (height * width);\n      for (auto y = 0; y < output_h; y++) {\n        const auto iy = y * stride_h + kh;\n        const auto ix = kw;\n        if (stride_w == 1) {\n          auto offsrc = src + (iy * width + ix);\n          const auto offdst = dst + (y * output_w);\n          for (auto i = 0; i < output_w; ++i) {\n            offsrc[i] += offdst[i];\n          }\n        } else {\n          for (auto x = 0; x < output_w; x++) {\n            auto offsrc = src + (iy * width + ix + x * stride_w);\n            const auto offdst = dst + (y * output_w + x);\n            *offsrc += *offdst;\n          }\n        }\n      }\n    }\n    return;\n  }\n\n  // Fast path for equal padding\n  if (pad_l == pad_r && pad_t == pad_b) {\n    // From Intel, https://github.com/BVLC/caffe/pull/3536\n    const int pad_h = pad_t;\n    const int pad_w = pad_l;\n    const int channel_size = height * width;\n    for (int channel = channels; channel--; data_im += channel_size) {\n      for (int kernel_row = 0; kernel_row < kernel_h; kernel_row++) {\n        for (int kernel_col = 0; kernel_col < kernel_w; kernel_col++) {\n          int input_row = -pad_h + kernel_row * dilation_h;\n          for (int output_rows = output_h; output_rows; output_rows--) {\n            if (!is_a_ge_zero_and_a_lt_b(input_row, height)) {\n              data_col += output_w;\n            } else {\n              int input_col = -pad_w + kernel_col * dilation_w;\n              for (int output_col = output_w; output_col; output_col--) {\n                if (is_a_ge_zero_and_a_lt_b(input_col, width)) {\n                  data_im[input_row * width + input_col] += *data_col;\n                }\n                data_col++;\n                input_col += stride_w;\n              }\n            }\n            input_row += stride_h;\n          }\n        }\n      }\n    }\n    return;\n  }\n\n  // Fallback\n  const int dkernel_h = dilation_h * (kernel_h - 1) + 1;\n  const int dkernel_w = dilation_w * (kernel_w - 1) + 1;\n\n  int height_col = (height + pad_t + pad_b - dkernel_h) / stride_h + 1;\n  int width_col = (width + pad_l + pad_r - dkernel_w) / stride_w + 1;\n  int channels_col = channels * kernel_h * kernel_w;\n  for (int c = 0; c < channels_col; ++c) {\n    int w_offset = c % kernel_w;\n    int h_offset = (c / kernel_w) % kernel_h;\n    int c_im = c / kernel_h / kernel_w;\n    for (int h = 0; h < height_col; ++h) {\n      for (int w = 0; w < width_col; ++w) {\n        int h_pad = h * stride_h - pad_t + h_offset * dilation_h;\n        int w_pad = w * stride_w - pad_l + w_offset * dilation_w;\n        if (h_pad >= 0 && h_pad < height && w_pad >= 0 && w_pad < width) {\n          data_im[(c_im * height + h_pad) * width + w_pad] +=\n              data_col[(c * height_col + h) * width_col + w];\n        }\n      }\n    }\n  }\n}\n\ntemplate <>\nvoid Col2im<float, CPUContext, StorageOrder::NHWC>(\n    const float* data_col,\n    const int channels,\n    const int height,\n    const int width,\n    const int kernel_h,\n    const int kernel_w,\n    const int dilation_h,\n    const int dilation_w,\n    const int pad_t,\n    const int pad_l,\n    const int pad_b,\n    const int pad_r,\n    const int stride_h,\n    const int stride_w,\n    float* data_im,\n    CPUContext* context) {\n  const int dkernel_h = dilation_h * (kernel_h - 1) + 1;\n  const int dkernel_w = dilation_w * (kernel_w - 1) + 1;\n\n  Set<float, CPUContext>(height * width * channels, 0, data_im, context);\n  int height_col = (height + pad_t + pad_b - dkernel_h) / stride_h + 1;\n  int width_col = (width + pad_l + pad_r - dkernel_w) / stride_w + 1;\n  int h_pad = -pad_t;\n  for (int h = 0; h < height_col; ++h) {\n    int w_pad = -pad_l;\n    for (int w = 0; w < width_col; ++w) {\n      for (int ih = h_pad; ih < h_pad + dkernel_h; ih += dilation_h) {\n        for (int iw = w_pad; iw < w_pad + dkernel_w; iw += dilation_w) {\n          if (ih >= 0 && ih < height && iw >= 0 && iw < width) {\n            auto* data_im_patch = data_im + (ih * width + iw) * channels;\n            Add<float, CPUContext>(\n                  channels, data_im_patch, data_col, data_im_patch, context);\n          }\n          data_col += channels;\n        }\n      }\n      w_pad += stride_w;\n    }\n    h_pad += stride_h;\n  }\n}\n\ntemplate <>\nvoid BiasCHW<float, CPUContext>(\n    const float* bias,\n    const int bias_channels,\n    const int image_size,\n    float* image,\n    CPUContext* /*context*/) {\n  // Sum the per-channel bias into every image plane\n  for (int c = 0; c < bias_channels; ++c) {\n    float b = bias[c];\n\n#ifdef __ARM_NEON__\n    float32x4_t vBias = vdupq_n_f32(b);\n\n    // We give alignment hints for additional speed, so handle the\n    // non-vectorizable prologue separately\n    constexpr int kVecSizeInFloat = sizeof(float32x4_t) / sizeof(float);\n\n    // FIXME: if input < kVecSizeInFloat, can't vectorize at all\n\n    int prologue =\n      kVecSizeInFloat -\n      // remainder in floats\n      (((uintptr_t) image) % (sizeof(float32x4_t))) / sizeof(float);\n\n    int i = 0;\n    // Prologue loop\n    for (; i < prologue; ++i) {\n      image[i] += b;\n    }\n\n    // The loop is manually unrolled by 8\n    constexpr int kUnroll = 8;\n    constexpr int kFloatsPerLoop = kUnroll * kVecSizeInFloat;\n\n    int remainder = image_size - prologue;\n    int vectorizable = prologue + (remainder / kFloatsPerLoop) * kFloatsPerLoop;\n\n    // Vectorizable body\n    for (; i < vectorizable; i += kFloatsPerLoop) {\n      // Manually unrolled\n      float32x4_t v0 = vld1q_f32_aligned(image + i + 0);\n      float32x4_t v1 = vld1q_f32_aligned(image + i + 4);\n      float32x4_t v2 = vld1q_f32_aligned(image + i + 8);\n      float32x4_t v3 = vld1q_f32_aligned(image + i + 12);\n      float32x4_t v4 = vld1q_f32_aligned(image + i + 16);\n      float32x4_t v5 = vld1q_f32_aligned(image + i + 20);\n      float32x4_t v6 = vld1q_f32_aligned(image + i + 24);\n      float32x4_t v7 = vld1q_f32_aligned(image + i + 28);\n\n      v0 = vaddq_f32(v0, vBias);\n      v1 = vaddq_f32(v1, vBias);\n      v2 = vaddq_f32(v2, vBias);\n      v3 = vaddq_f32(v3, vBias);\n      v4 = vaddq_f32(v4, vBias);\n      v5 = vaddq_f32(v5, vBias);\n      v6 = vaddq_f32(v6, vBias);\n      v7 = vaddq_f32(v7, vBias);\n\n      vst1q_f32_aligned(image + i + 0, v0);\n      vst1q_f32_aligned(image + i + 4, v1);\n      vst1q_f32_aligned(image + i + 8, v2);\n      vst1q_f32_aligned(image + i + 12, v3);\n      vst1q_f32_aligned(image + i + 16, v4);\n      vst1q_f32_aligned(image + i + 20, v5);\n      vst1q_f32_aligned(image + i + 24, v6);\n      vst1q_f32_aligned(image + i + 28, v7);\n    }\n\n    // Non-vectorizable epilogue\n    for (; i < image_size; ++i) {\n      image[i] += b;\n    }\n#else\n    // Non-NEON CPU implementation\n    for (int i = 0; i < image_size; ++i) {\n      image[i] += b;\n    }\n#endif // __ARM_NEON__\n\n    image += image_size;\n  }\n}\n\ntemplate <>\nvoid CopyMatrix<CPUContext>(\n    const size_t itemsize,\n    const int M,\n    const int N,\n    const void* A,\n    const int lda,\n    void* B,\n    const int ldb,\n    CPUContext* /*context*/,\n    TypeMeta::TypedCopy copy) {\n  if (A == nullptr || B == nullptr) {\n    return;\n  }\n  if (lda == N && ldb == N) {\n    // can coalese to a single memcpy of size M * N\n    if (copy) {\n      copy(static_cast<const char*>(A), static_cast<char*>(B), N * M);\n    } else {\n      memcpy(\n          static_cast<char*>(B), static_cast<const char*>(A), itemsize * N * M);\n    }\n    return;\n  }\n\n  for (int i = 0; i < M; ++i) {\n    if (copy) {\n      copy(\n          static_cast<const char*>(A) + lda * i * itemsize,\n          static_cast<char*>(B) + ldb * i * itemsize,\n          N);\n    } else {\n      memcpy(\n          static_cast<char*>(B) + ldb * i * itemsize,\n          static_cast<const char*>(A) + lda * i * itemsize,\n          itemsize * N);\n    }\n  }\n}\n\n#define CAFFE2_SPECIALIZED_COPYVECTOR(T)                            \\\n  template <>                                                       \\\n  void CopyVector<T, CPUContext>(                                   \\\n      const int N, const T* src, T* dst, CPUContext* /*context*/) { \\\n    if (src != dst && N > 0) {                                      \\\n      memcpy(dst, src, sizeof(T) * N);                              \\\n    }                                                               \\\n  }\nCAFFE2_SPECIALIZED_COPYVECTOR(float)\n#undef CAFFE2_SPECIALIZED_COPYVECTOR\n\nnamespace {\n\n#ifdef CAFFE2_USE_HPTT\n\nbool TryTransposeWithHPTT(\n    const int num_axes,\n    const int* dims,\n    const int* axes,\n    const float* X,\n    float* Y) {\n  std::vector<int> axes_cm(num_axes);\n  std::vector<int> dims_cm(num_axes);\n\n  // Convert row-major index to column-major.\n  const auto cm_fn = [num_axes](const int i) { return num_axes - i - 1; };\n  for (int i = 0; i < num_axes; ++i) {\n    axes_cm[i] = cm_fn(axes[cm_fn(i)]);\n    dims_cm[i] = dims[cm_fn(i)];\n  }\n  auto plan = hptt::create_plan(\n      axes_cm.data(),\n      num_axes,\n      1.0,\n      X,\n      dims_cm.data(),\n      nullptr,\n      0.0,\n      Y,\n      nullptr,\n      hptt::ESTIMATE,\n      1);\n  if (plan == nullptr) {\n    return false;\n  }\n  plan->execute();\n  return true;\n}\n\n#endif // CAFFE2_USE_HPTT\n\nstd::vector<int>\nComputeXStrides(const int num_axes, const int* dims, const int* axes) {\n  std::vector<int> x_strides(num_axes);\n  std::vector<int> buff(num_axes);\n  int cur_stride = 1;\n  for (int i = num_axes - 1; i >= 0; --i) {\n    buff[i] = cur_stride;\n    cur_stride *= dims[i];\n  }\n  for (int i = 0; i < num_axes; ++i) {\n    x_strides[i] = buff[axes[i]];\n  }\n  return x_strides;\n}\n\nvoid IncreaseIndex(const int* dims, std::vector<int>* index) {\n  for (int i = index->size() - 1; i >= 0; --i) {\n    ++index->at(i);\n    if (index->at(i) >= dims[i]) {\n      index->at(i) -= dims[i];\n    } else {\n      break;\n    }\n  }\n}\n\ntemplate <typename T>\nvoid TransposeCPU(\n    const int num_axes,\n    const int* x_dims,\n    const int* y_dims,\n    const int* axes,\n    const int data_size,\n    const T* X,\n    T* Y) {\n  // Measure amount of contiguous data we can copy at once\n  int block_size = 1;\n  int num_shared_idxs = 0;\n  for (int i = num_axes - 1; i >= 0 && axes[i] == i; --i) {\n    block_size *= y_dims[i];\n    ++num_shared_idxs;\n  }\n\n  if (num_axes < 2 || num_shared_idxs == num_axes) {\n    memcpy(Y, X, data_size * sizeof(T));\n    return;\n  }\n\n  const int itr_axes = num_axes - num_shared_idxs;\n  const std::vector<int> x_strides = ComputeXStrides(itr_axes, x_dims, axes);\n  std::vector<int> index_digits(itr_axes, 0);\n  const int num_blocks = data_size / block_size;\n  for (int y_index = 0; y_index < num_blocks; ++y_index) {\n    const int x_index = std::inner_product(\n        x_strides.cbegin(), x_strides.cend(), index_digits.cbegin(), 0);\n    if (block_size == 1) {\n      Y[y_index] = X[x_index];\n    } else {\n      memcpy(\n          Y + block_size * y_index,\n          X + block_size * x_index,\n          block_size * sizeof(T));\n    }\n    IncreaseIndex(y_dims, &index_digits);\n  }\n}\n\n} // namespace\n\ntemplate <>\nvoid Transpose<float, CPUContext>(\n    const int num_axes,\n    const int* x_dims,\n    const int* y_dims,\n    const int* axes,\n    const int data_size,\n    const float* X,\n    float* Y,\n    CPUContext* /* context */) {\n#ifdef CAFFE2_USE_HPTT\n  if (TryTransposeWithHPTT(num_axes, x_dims, axes, X, Y)) {\n    return;\n  }\n#endif // CAFFE2_USE_HPTT\n  TransposeCPU(num_axes, x_dims, y_dims, axes, data_size, X, Y);\n}\n\n#define CAFFE2_SPECIALIZED_TRANSPOSE(T)                            \\\n  template <>                                                      \\\n  void Transpose<T, CPUContext>(                                   \\\n      const int num_axes,                                          \\\n      const int* x_dims,                                           \\\n      const int* y_dims,                                           \\\n      const int* axes,                                             \\\n      const int data_size,                                         \\\n      const T* X,                                                  \\\n      T* Y,                                                        \\\n      CPUContext* /* context */) {                                 \\\n    TransposeCPU(num_axes, x_dims, y_dims, axes, data_size, X, Y); \\\n  }\nCAFFE2_SPECIALIZED_TRANSPOSE(double)\nCAFFE2_SPECIALIZED_TRANSPOSE(int)\nCAFFE2_SPECIALIZED_TRANSPOSE(long)\n#undef CAFFE2_SPECIALIZED_TRANSPOSE\n\n} // namespace math\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/math_gpu.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Implements the math functions for CPU.\n\n#include <cub/block/block_reduce.cuh>\n#include <cub/cub.cuh>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/utils/conversions.h\"\n#include \"caffe2/utils/math.h\"\n\n#if THRUST_VERSION >= 100800\n#define THRUST_SUPPORTS_PER_THREAD\n#endif  // THRUST_VERSION >= 100800\n\nnamespace caffe2 {\nnamespace math {\n\n#define DELEGATE_SIMPLE_CUDA_UNARY_FUNCTION(T, Funcname, function)             \\\n__global__                                                                     \\\nvoid _Kernel_##T##_##Funcname(const int N, const T* x, T* y) {                 \\\n  CUDA_1D_KERNEL_LOOP(i, N) {                                                  \\\n    y[i] = function(x[i]);                                                     \\\n  }                                                                            \\\n}                                                                              \\\ntemplate <>                                                                    \\\nvoid Funcname<T, CUDAContext>(                                                 \\\n    const int N, const T* x, T* y,                                             \\\n    CUDAContext* context) {                                                    \\\n  _Kernel_##T##_##Funcname<<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS,      \\\n                                 0, context->cuda_stream()>>>(                 \\\n      N, x, y);                                                                \\\n}\n\nDELEGATE_SIMPLE_CUDA_UNARY_FUNCTION(float, Exp, expf);\nDELEGATE_SIMPLE_CUDA_UNARY_FUNCTION(float, Log, logf);\nDELEGATE_SIMPLE_CUDA_UNARY_FUNCTION(float, Cos, cosf);\nDELEGATE_SIMPLE_CUDA_UNARY_FUNCTION(float, Sin, sinf);\nDELEGATE_SIMPLE_CUDA_UNARY_FUNCTION(float, Abs, fabsf);\nDELEGATE_SIMPLE_CUDA_UNARY_FUNCTION(float, Sqrt, sqrtf);\nDELEGATE_SIMPLE_CUDA_UNARY_FUNCTION(float, InvSqrt, rsqrtf);\n\n__device__ float cuda_sqrf(const float x) { return x * x; }\n\nDELEGATE_SIMPLE_CUDA_UNARY_FUNCTION(float, Sqr, cuda_sqrf);\n\n#undef DELEGATE_SIMPLE_CUDA_UNARY_FUNCTION\n\n#define DELEGATE_SINCOS_CUDA_FUNCTION(T)                             \\\n  __global__ void _Kernel_##T##_##SinCos(                            \\\n      const int N, const T* x, T* ys, T* yc) {                       \\\n    CUDA_1D_KERNEL_LOOP(i, N) {                                      \\\n      sincos(x[i], ys + i, yc + i);                                  \\\n    }                                                                \\\n  }                                                                  \\\n  template <>                                                        \\\n  void SinCos<T, CUDAContext>(                                       \\\n      const int N, const T* x, T* ys, T* yc, CUDAContext* context) { \\\n    _Kernel_##T##_##SinCos<<<                                        \\\n        CAFFE_GET_BLOCKS(N),                                         \\\n        CAFFE_CUDA_NUM_THREADS,                                      \\\n        0,                                                           \\\n        context->cuda_stream()>>>(N, x, ys, yc);                     \\\n  }\n\nDELEGATE_SINCOS_CUDA_FUNCTION(float)\nDELEGATE_SINCOS_CUDA_FUNCTION(double)\n\n#undef DELEGATE_SINCOS_CUDA_FUNCTION\n\n#define DELEGATE_SIMPLE_CUDA_BINARY_INFIX_FUNCTION(T, Funcname, expr)         \\\n  __global__ void _Kernel_##T##_##Funcname(                                   \\\n      const int N, const T* a, const T* b, T* y) {                            \\\n    CUDA_1D_KERNEL_LOOP(i, N) {                                               \\\n      float r = convert::To<T, float>(a[i]) expr convert::To<T, float>(b[i]); \\\n      y[i] = convert::To<float, T>(r);                                        \\\n    }                                                                         \\\n  }                                                                           \\\n  template <>                                                                 \\\n  void Funcname<T, CUDAContext>(                                              \\\n      const int N, const T* a, const T* b, T* y, CUDAContext* context) {      \\\n    _Kernel_##T##_##Funcname<<<                                               \\\n        CAFFE_GET_BLOCKS(N),                                                  \\\n        CAFFE_CUDA_NUM_THREADS,                                               \\\n        0,                                                                    \\\n        context->cuda_stream()>>>(N, a, b, y);                                \\\n  }\n\nDELEGATE_SIMPLE_CUDA_BINARY_INFIX_FUNCTION(float, Add, +);\nDELEGATE_SIMPLE_CUDA_BINARY_INFIX_FUNCTION(int32_t, Add, +);\nDELEGATE_SIMPLE_CUDA_BINARY_INFIX_FUNCTION(float, Sub, -);\nDELEGATE_SIMPLE_CUDA_BINARY_INFIX_FUNCTION(float, Mul, *);\nDELEGATE_SIMPLE_CUDA_BINARY_INFIX_FUNCTION(float, Div, /);\n\nDELEGATE_SIMPLE_CUDA_BINARY_INFIX_FUNCTION(float16, Add, +);\nDELEGATE_SIMPLE_CUDA_BINARY_INFIX_FUNCTION(float16, Sub, -);\nDELEGATE_SIMPLE_CUDA_BINARY_INFIX_FUNCTION(float16, Mul, *);\nDELEGATE_SIMPLE_CUDA_BINARY_INFIX_FUNCTION(float16, Div, /);\n\n#undef DELEGATE_SIMPLE_CUDA_BINARY_INFIX_FUNCTION\n\n#define DELEGATE_SIMPLE_CUDA_BINARY_PREFIX_FUNCTION(T, Funcname, func)    \\\n  __global__ void _Kernel_##T##_##Funcname(                               \\\n      const int N, const T* a, const T* b, T* y) {                        \\\n    CUDA_1D_KERNEL_LOOP(i, N) {                                           \\\n      float r =                                                           \\\n          func(convert::To<T, float>(a[i]), convert::To<T, float>(b[i])); \\\n      y[i] = convert::To<float, T>(r);                                    \\\n    }                                                                     \\\n  }                                                                       \\\n  template <>                                                             \\\n  void Funcname<T, CUDAContext>(                                          \\\n      const int N, const T* a, const T* b, T* y, CUDAContext* context) {  \\\n    _Kernel_##T##_##Funcname<<<                                           \\\n        CAFFE_GET_BLOCKS(N),                                              \\\n        CAFFE_CUDA_NUM_THREADS,                                           \\\n        0,                                                                \\\n        context->cuda_stream()>>>(N, a, b, y);                            \\\n  }\n\nDELEGATE_SIMPLE_CUDA_BINARY_PREFIX_FUNCTION(float, ElemwiseMax, fmaxf);\n\n#undef DELEGATE_SIMPLE_CUDA_BINARY_INFIX_FUNCTION\n\n#define DELEGATE_REDUCTION_FUNCTION(T, Funcname, func)                  \\\n  template <>                                                           \\\n  void Funcname<T, CUDAContext>(                                        \\\n      const int N,                                                      \\\n      const T* src,                                                     \\\n      T* dst,                                                           \\\n      Tensor<CUDAContext>* scratch_ptr,                                 \\\n      CUDAContext* context) {                                           \\\n    size_t memRequired = 0;                                             \\\n    cub::DeviceReduce::func(                                            \\\n        nullptr, memRequired, src, dst, N, context->cuda_stream());     \\\n    auto buffer_size =                                                  \\\n        static_cast<TIndex>((memRequired + sizeof(T) - 1) / sizeof(T)); \\\n    scratch_ptr->Resize(std::vector<TIndex>{buffer_size});              \\\n    cub::DeviceReduce::func(                                            \\\n        static_cast<void*>(scratch_ptr->mutable_data<T>()),             \\\n        memRequired,                                                    \\\n        src,                                                            \\\n        dst,                                                            \\\n        N,                                                              \\\n        context->cuda_stream());                                        \\\n  }\n\nDELEGATE_REDUCTION_FUNCTION(float, ReduceMin, Min)\nDELEGATE_REDUCTION_FUNCTION(float, ReduceMax, Max)\nDELEGATE_REDUCTION_FUNCTION(int32_t, ReduceMax, Max)\nDELEGATE_REDUCTION_FUNCTION(int64_t, ReduceMax, Max)\n\n\n#undef DELEGATE_REDUCTION_FUNCTION\n\n// Caffe2 gemm provides a simpler interface to the gemm functions, with the\n// limitation that the data has to be contiguous in memory.\ntemplate <>\nvoid Gemm<float, CUDAContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float* A,\n    const float* B,\n    const float beta,\n    float* C,\n    CUDAContext* context,\n    TensorProto::DataType math_type) {\n  // Note that cublas follows fortran order, so the order is different from\n  // the cblas convention.\n  int lda = (TransA == CblasNoTrans) ? K : M;\n  int ldb = (TransB == CblasNoTrans) ? N : K;\n  cublasOperation_t cuTransA =\n      (TransA == CblasNoTrans) ? CUBLAS_OP_N : CUBLAS_OP_T;\n  cublasOperation_t cuTransB =\n      (TransB == CblasNoTrans) ? CUBLAS_OP_N : CUBLAS_OP_T;\n  CUBLAS_ENFORCE(cublasSgemm(\n      context->cublas_handle(),\n      cuTransB,\n      cuTransA,\n      N,\n      M,\n      K,\n      &alpha,\n      B,\n      ldb,\n      A,\n      lda,\n      &beta,\n      C,\n      N));\n}\n\ntemplate <>\nvoid Gemm<float16, CUDAContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float16* A,\n    const float16* B,\n    const float beta,\n    float16* C,\n    CUDAContext* context,\n    TensorProto::DataType math_type) {\n  // Note that cublas follows fortran order, so the order is different from\n  // the cblas convention.\n  int lda = (TransA == CblasNoTrans) ? K : M;\n  int ldb = (TransB == CblasNoTrans) ? N : K;\n  cublasOperation_t cuTransA =\n      (TransA == CblasNoTrans) ? CUBLAS_OP_N : CUBLAS_OP_T;\n  cublasOperation_t cuTransB =\n      (TransB == CblasNoTrans) ? CUBLAS_OP_N : CUBLAS_OP_T;\n  if (math_type == TensorProto_DataType_FLOAT) {\n    CUBLAS_CHECK(cublasSgemmEx(\n        context->cublas_handle(),\n        cuTransB,\n        cuTransA,\n        N,\n        M,\n        K,\n        &alpha,\n        B,\n        CUDA_R_16F,\n        ldb,\n        A,\n        CUDA_R_16F,\n        lda,\n        &beta,\n        C,\n        CUDA_R_16F,\n        N));\n\n  } else if (math_type == TensorProto_DataType_FLOAT16) {\n    // convert alpha, beta from float -> __half\n    auto alpha_fp16 = convert::floatToHalf(alpha);\n    auto beta_fp16 = convert::floatToHalf(beta);\n\n    // call cublasHgemm\n    CUBLAS_CHECK(cublasHgemm(\n        context->cublas_handle(),\n        cuTransB,\n        cuTransA,\n        N,\n        M,\n        K,\n        &alpha_fp16,\n        (const __half*)B,\n        ldb,\n        (const __half*)A,\n        lda,\n        &beta_fp16,\n        (__half*)C,\n        N));\n  } else {\n    // fail\n    CAFFE_THROW(\"Unsupported math type\");\n  }\n}\n\ntemplate <>\nvoid GemmBatched<float, CUDAContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int batch_size,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float* A,\n    const float* B,\n    const float beta,\n    float* C,\n    CUDAContext* context,\n    Tensor<CUDAContext>* scratch,\n    TensorProto::DataType math_type) {\n  const int a_stride = M * K;\n  const int b_stride = K * N;\n  const int c_stride = M * N;\n#if __CUDACC_VER_MAJOR__ < 8\n  // loop over matrices in the batch\n  for (int i = 0; i < batch_size; ++i) {\n    math::Gemm<float, CUDAContext>(\n        TransA,\n        TransB,\n        M,\n        N,\n        K,\n        alpha,\n        A + a_stride * i,\n        B + b_stride * i,\n        beta,\n        C + c_stride * i,\n        context);\n  }\n#else\n  // Note that cublas follows fortran order, so the order is different from\n  // the cblas convention.\n  const int lda = (TransA == CblasNoTrans) ? K : M;\n  const int ldb = (TransB == CblasNoTrans) ? N : K;\n  cublasOperation_t cuTransA =\n      (TransA == CblasNoTrans) ? CUBLAS_OP_N : CUBLAS_OP_T;\n  cublasOperation_t cuTransB =\n      (TransB == CblasNoTrans) ? CUBLAS_OP_N : CUBLAS_OP_T;\n  CUBLAS_ENFORCE(cublasSgemmStridedBatched(\n      context->cublas_handle(),\n      cuTransB,\n      cuTransA,\n      N,\n      M,\n      K,\n      &alpha,\n      B,\n      ldb,\n      b_stride,\n      A,\n      lda,\n      a_stride,\n      &beta,\n      C,\n      N,\n      c_stride,\n      batch_size));\n#endif\n}\n\nnamespace {\n\n__global__ void FloatToHalfKernel(const int N, const float* X, half* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = __float2half(X[i]);\n  }\n}\n\n__global__ void HalfToFloatKernel(const int N, const half* X, float* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = __half2float(X[i]);\n  }\n}\n\n};\n\ntemplate <>\nvoid GemmBatched<float16, CUDAContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int batch_size,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float16* A,\n    const float16* B,\n    const float beta,\n    float16* C,\n    CUDAContext* context,\n    Tensor<CUDAContext>* scratch,\n    TensorProto::DataType math_type) {\n  const int a_stride = M * K;\n  const int b_stride = K * N;\n  const int c_stride = M * N;\n#if __CUDACC_VER_MAJOR__ < 8\n  // loop over matrices in the batch\n  for (int i = 0; i < batch_size; ++i) {\n    math::Gemm<float16, CUDAContext>(\n        TransA,\n        TransB,\n        M,\n        N,\n        K,\n        alpha,\n        A + a_stride * i,\n        B + b_stride * i,\n        beta,\n        C + c_stride * i,\n        context);\n  }\n#else\n  // 3 options:\n  // 1) scratch != null = cast to fp32, SgemmStridedBatched, cast result to fp16\n  // 2) math_type == FLOAT, scratch == nullptr = looped SgemmEx\n  // 3) math_type == FLOAT16, scratch == nullptr = batched Hgemm\n\n  if (scratch != nullptr) {\n    const int A_size = a_stride * batch_size;\n    const int B_size = b_stride * batch_size;\n    // cast, cublasSgemmStridedBatched, cast\n    size_t in_elems = A_size + B_size;\n    size_t out_elems = c_stride * batch_size;\n\n    scratch->Resize(in_elems + out_elems);\n    float* scratch_ptr = scratch->mutable_data<float>();\n\n    float* A_fp32 = scratch_ptr;\n    float* B_fp32 = scratch_ptr + A_size;\n    float* C_fp32 = scratch_ptr + A_size + B_size;\n\n    // cast A, B into fp32\n    HalfToFloatKernel<<<CAFFE_GET_BLOCKS(A_size),\n                        CAFFE_CUDA_NUM_THREADS,\n                        0,\n                        context->cuda_stream()>>>(A_size, (half*)A, A_fp32);\n    HalfToFloatKernel<<<CAFFE_GET_BLOCKS(B_size),\n                        CAFFE_CUDA_NUM_THREADS,\n                        0,\n                        context->cuda_stream()>>>(B_size, (half*)B, B_fp32);\n\n    // run fp32 batched Gemm\n    GemmBatched<float, CUDAContext>(\n        TransA,\n        TransB,\n        batch_size,\n        M,\n        N,\n        K,\n        alpha,\n        A_fp32,\n        B_fp32,\n        beta,\n        C_fp32,\n        context);\n\n    // cast result back to fp16\n    FloatToHalfKernel<<<\n        CAFFE_GET_BLOCKS(batch_size * M * N),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context->cuda_stream()>>>(batch_size * M * N, C_fp32, (half*)C);\n  } else {\n    if (math_type == TensorProto_DataType_FLOAT) {\n      // loop over matrices in the batch\n      for (int i = 0; i < batch_size; ++i) {\n        math::Gemm<float16, CUDAContext>(\n            TransA,\n            TransB,\n            M,\n            N,\n            K,\n            alpha,\n            A + a_stride * i,\n            B + b_stride * i,\n            beta,\n            C + c_stride * i,\n            context);\n      }\n    } else if (math_type == TensorProto_DataType_FLOAT16) {\n      // Note that cublas follows fortran order, so the order is different from\n      // the cblas convention.\n      const int lda = (TransA == CblasNoTrans) ? K : M;\n      const int ldb = (TransB == CblasNoTrans) ? N : K;\n      cublasOperation_t cuTransA =\n          (TransA == CblasNoTrans) ? CUBLAS_OP_N : CUBLAS_OP_T;\n      cublasOperation_t cuTransB =\n          (TransB == CblasNoTrans) ? CUBLAS_OP_N : CUBLAS_OP_T;\n\n      // convert alpha, beta from float -> __half\n      auto alpha_fp16 = convert::floatToHalf(alpha);\n      auto beta_fp16 = convert::floatToHalf(beta);\n      CUBLAS_ENFORCE(cublasHgemmStridedBatched(\n          context->cublas_handle(),\n          cuTransB,\n          cuTransA,\n          N,\n          M,\n          K,\n          &alpha_fp16,\n          (const __half*)B,\n          ldb,\n          b_stride,\n          (const __half*)A,\n          lda,\n          a_stride,\n          &beta_fp16,\n          (__half*)C,\n          N,\n          c_stride,\n          batch_size));\n    }\n  }\n#endif\n}\n\n#if CUDA_VERSION >= 9000\n\n// No change, but required. Defer to default CUDA engine\ntemplate <>\nvoid Gemm<float, CUDAContext, TensorCoreEngine>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float* A,\n    const float* B,\n    const float beta,\n    float* C,\n    CUDAContext* context,\n    TensorProto::DataType math_type) {\n  return Gemm<float,CUDAContext>(TransA,\n                                 TransB,\n                                 M,\n                                 N,\n                                 K,\n                                 alpha,\n                                 A,\n                                 B,\n                                 beta,\n                                 C,\n                                 context,\n                                 math_type);\n}\n\ntemplate <>\nvoid Gemm<float16, CUDAContext, TensorCoreEngine>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float16* A,\n    const float16* B,\n    const float beta,\n    float16* C,\n    CUDAContext* context,\n    TensorProto::DataType math_type) {\n  // Note that cublas follows fortran order, so the order is different from\n  // the cblas convention.\n  int lda = (TransA == CblasNoTrans) ? K : M;\n  int ldb = (TransB == CblasNoTrans) ? N : K;\n  cublasOperation_t cuTransA =\n      (TransA == CblasNoTrans) ? CUBLAS_OP_N : CUBLAS_OP_T;\n  cublasOperation_t cuTransB =\n      (TransB == CblasNoTrans) ? CUBLAS_OP_N : CUBLAS_OP_T;\n\n  // enable TensorCore for this call on this handle\n  if (TensorCoreAvailable()) {\n    CUBLAS_ENFORCE(cublasSetMathMode(\n        context->cublas_handle(),\n        CUBLAS_TENSOR_OP_MATH));\n  }\n\n  CUBLAS_CHECK(cublasGemmEx(\n      context->cublas_handle(),\n      cuTransB,\n      cuTransA,\n      N,\n      M,\n      K,\n      &alpha,\n      B,\n      CUDA_R_16F,\n      ldb,\n      A,\n      CUDA_R_16F,\n      lda,\n      &beta,\n      C,\n      CUDA_R_16F,\n      N,\n      CUDA_R_32F,\n      CUBLAS_GEMM_DFALT_TENSOR_OP));\n\n  // Now disable TensorCore math for subsequent calls to this handle\n  if (TensorCoreAvailable()) {\n    CUBLAS_ENFORCE(cublasSetMathMode(\n        context->cublas_handle(),\n        CUBLAS_DEFAULT_MATH));\n  }\n}\n\ntemplate <>\nvoid GemmBatched<float, CUDAContext, TensorCoreEngine>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int batch_size,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float* A,\n    const float* B,\n    const float beta,\n    float* C,\n    CUDAContext* context,\n    Tensor<CUDAContext>* scratch,\n    TensorProto::DataType math_type) {\n  return GemmBatched<float, CUDAContext, DefaultEngine>(\n      TransA,\n      TransB,\n      batch_size,\n      M,\n      N,\n      K,\n      alpha,\n      A,\n      B,\n      beta,\n      C,\n      context,\n      scratch,\n      math_type);\n}\n\ntemplate <>\nvoid GemmBatched<float16, CUDAContext, TensorCoreEngine>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int batch_size,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float16* A,\n    const float16* B,\n    const float beta,\n    float16* C,\n    CUDAContext* context,\n    Tensor<CUDAContext>* scratch,\n    TensorProto::DataType math_type) {\n  return GemmBatched<float16, CUDAContext, DefaultEngine>(\n      TransA,\n      TransB,\n      batch_size,\n      M,\n      N,\n      K,\n      alpha,\n      A,\n      B,\n      beta,\n      C,\n      context,\n      scratch,\n      math_type);\n}\n\n#endif // CUDA_VERSION >= 9000\n\ntemplate <>\nvoid GemmEx<float, CUDAContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const CBLAS_TRANSPOSE TransB,\n    const int M,\n    const int N,\n    const int K,\n    const float alpha,\n    const float* A,\n    const int lda,\n    const float* B,\n    const int ldb,\n    const float beta,\n    float* C,\n    const int ldc,\n    CUDAContext* context) {\n  // Note that cublas follows fortran order, so the order is different from\n  // the cblas convention.\n  cublasOperation_t cuTransA =\n      (TransA == CblasNoTrans) ? CUBLAS_OP_N : CUBLAS_OP_T;\n  cublasOperation_t cuTransB =\n      (TransB == CblasNoTrans) ? CUBLAS_OP_N : CUBLAS_OP_T;\n  CUBLAS_ENFORCE(cublasSgemm(\n      context->cublas_handle(),\n      cuTransB,\n      cuTransA,\n      N,\n      M,\n      K,\n      &alpha,\n      B,\n      ldb,\n      A,\n      lda,\n      &beta,\n      C,\n      ldc));\n}\n\ntemplate <>\nvoid Gemv<float, CUDAContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const int M,\n    const int N,\n    const float alpha,\n    const float* A,\n    const float* x,\n    const float beta,\n    float* y,\n    CUDAContext* context,\n    TensorProto::DataType math_type) {\n  cublasOperation_t cuTransA =\n      (TransA == CblasNoTrans) ? CUBLAS_OP_T : CUBLAS_OP_N;\n  CUBLAS_ENFORCE(cublasSgemv(\n      context->cublas_handle(),\n      cuTransA,\n      N,\n      M,\n      &alpha,\n      A,\n      N,\n      x,\n      1,\n      &beta,\n      y,\n      1));\n}\n\n// Batched Add variants\nnamespace {\n\ntemplate <typename T>\n__global__ void AddStripedBatchKernel(\n    const int N,\n    const T* first,\n    T* Y,\n    const int stripe,\n    const int batch) {\n  for (int j = 0; j < batch; j++) {\n    const T* x = first + j * stripe;\n    CUDA_1D_KERNEL_LOOP(i, N) {\n      float tmpY = convert::To<T, float>(Y[i]);\n      tmpY += convert::To<T,float>(x[i]);\n      Y[i] = convert::To<float,T>(tmpY);\n    }\n  }\n}\n} // namespace\n\n#define CAFFE2_SPECIALIZED_CUDA_ADD_STRIPED_BATCH(T)           \\\n  template <>                                                  \\\n  void AddStripedBatch<T, CUDAContext>(                        \\\n      const int N,                                             \\\n      const T* first,                                          \\\n      T* Y,                                                    \\\n      const int stripe,                                        \\\n      const int batch,                                         \\\n      CUDAContext* context) {                                  \\\n    AddStripedBatchKernel<T><<<                                \\\n        CAFFE_GET_BLOCKS(N),                                   \\\n        CAFFE_CUDA_NUM_THREADS,                                \\\n        0,                                                     \\\n        context->cuda_stream()>>>(N, first, Y, stripe, batch); \\\n  }\n\nCAFFE2_SPECIALIZED_CUDA_ADD_STRIPED_BATCH(float);\nCAFFE2_SPECIALIZED_CUDA_ADD_STRIPED_BATCH(float16);\n#undef CAFFE2_SPECIALIZED_CUDA_ADD_STRIPED_BATCH\n\ntemplate <>\nvoid Gemv<float16, CUDAContext>(\n    const CBLAS_TRANSPOSE TransA,\n    const int M,\n    const int N,\n    const float alpha,\n    const float16* A,\n    const float16* x,\n    const float beta,\n    float16* y,\n    CUDAContext* context,\n    TensorProto::DataType math_type) {\n  cublasOperation_t cuTransA =\n      (TransA == CblasNoTrans) ? CUBLAS_OP_T : CUBLAS_OP_N;\n\n  // sort out what we need to call cublasSgemmEx / cublasHgemm\n  int m = (cuTransA == CUBLAS_OP_N) ? N : M;\n  int k = (cuTransA == CUBLAS_OP_N) ? M : N;\n  int LDA = (cuTransA == CUBLAS_OP_N) ? m : k;\n  int LDC = m;\n\n  if (math_type == TensorProto_DataType_FLOAT) {\n    CUBLAS_CHECK(cublasSgemmEx(\n        context->cublas_handle(),\n        cuTransA,\n        CUBLAS_OP_N,\n        m,\n        1,\n        k,\n        &alpha,\n        A,\n        CUDA_R_16F,\n        LDA,\n        x,\n        CUDA_R_16F,\n        k,\n        &beta,\n        y,\n        CUDA_R_16F,\n        LDC));\n  } else if (math_type == TensorProto_DataType_FLOAT16) {\n    auto alpha_fp16 = convert::floatToHalf(alpha);\n    auto beta_fp16 = convert::floatToHalf(beta);\n\n    CUBLAS_CHECK(cublasHgemm(\n        context->cublas_handle(),\n        cuTransA,\n        CUBLAS_OP_N,\n        m,\n        1,\n        k,\n        &alpha_fp16,\n        (const __half*)A,\n        LDA,\n        (const __half*)x,\n        k,\n        &beta_fp16,\n        (__half*)y,\n        LDC));\n  } else {\n    // fail\n    CAFFE_THROW(\"Unsupported math type\");\n  }\n}\n\nnamespace {\ntemplate <typename T>\n__global__ void SetKernel(const int N, const T alpha, T* Y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    Y[i] = alpha;\n  }\n}\n} // namespace\n\n#define CAFFE2_SPECIALIZED_CUDA_SET(T)                             \\\n  template <>                                                      \\\n  void Set<T, CUDAContext>(                                        \\\n      const size_t N, const T alpha, T* Y, CUDAContext* context) { \\\n    SetKernel<<<                                                   \\\n        CAFFE_GET_BLOCKS(N),                                       \\\n        CAFFE_CUDA_NUM_THREADS,                                    \\\n        0,                                                         \\\n        context->cuda_stream()>>>(N, alpha, Y);                    \\\n  }\n\nCAFFE2_SPECIALIZED_CUDA_SET(float);\nCAFFE2_SPECIALIZED_CUDA_SET(double);\nCAFFE2_SPECIALIZED_CUDA_SET(bool);\nCAFFE2_SPECIALIZED_CUDA_SET(int8_t);\nCAFFE2_SPECIALIZED_CUDA_SET(int16_t);\nCAFFE2_SPECIALIZED_CUDA_SET(float16);\nCAFFE2_SPECIALIZED_CUDA_SET(int);\nCAFFE2_SPECIALIZED_CUDA_SET(int64_t);\nCAFFE2_SPECIALIZED_CUDA_SET(char);\nCAFFE2_SPECIALIZED_CUDA_SET(uint8_t);\nCAFFE2_SPECIALIZED_CUDA_SET(uint16_t);\n#undef CAFFE2_SPECIALIZED_CUDA_SET\n\nnamespace {\ntemplate <typename T>\n__global__ void\nUniformShift(const size_t N, const float min, const float max, T* x) {\n  float scale = max - min;\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    x[i] = convert::To<float, T>(convert::To<T, float>(x[i]) * scale + min);\n  }\n}\n\n__global__ void\nUniformIntFit(const size_t N, const int min, const int max, unsigned int* x) {\n  int* x_int = reinterpret_cast<int*>(x);\n  int range = (max - min + 1);\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    x_int[i] = min + static_cast<int>(x[i] % range);\n  }\n}\n} // namespace\n\ntemplate <>\nvoid RandUniform<float, CUDAContext>(\n    const size_t n,\n    const float min,\n    const float max,\n    float* r,\n    CUDAContext* context) {\n  CURAND_ENFORCE(curandGenerateUniform(context->curand_generator(), r, n));\n  UniformShift<float>\n      <<<CAFFE_GET_BLOCKS(n),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context->cuda_stream()>>>(n, min, max, r);\n}\n\ntemplate <>\nvoid RandUniform<double, CUDAContext>(\n    const size_t n,\n    const double min,\n    const double max,\n    double* r,\n    CUDAContext* context) {\n  CURAND_ENFORCE(\n      curandGenerateUniformDouble(context->curand_generator(), r, n));\n  UniformShift<double>\n      <<<CAFFE_GET_BLOCKS(n),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context->cuda_stream()>>>(n, min, max, r);\n}\n\ntemplate <>\nvoid RandUniform<int, CUDAContext>(\n    const size_t n,\n    const int min,\n    const int max,\n    int* r,\n    CUDAContext* context) {\n  CURAND_ENFORCE(curandGenerate(\n      context->curand_generator(), reinterpret_cast<unsigned int*>(r), n));\n  UniformIntFit<<<\n      CAFFE_GET_BLOCKS(n),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(\n      n, min, max, reinterpret_cast<unsigned int*>(r));\n}\n\ntemplate <typename T>\nsize_t HandleOddLengthRandGaussian(\n    const size_t n,\n    const T mean,\n    const T std,\n    T* r,\n    CUDAContext* context) {\n  if (n % 2 == 1) {\n    std::default_random_engine generator;\n    std::normal_distribution<T> distribution(mean, std);\n    const T random_value = distribution(generator);\n    math::Set<T, CUDAContext>(1, random_value, r + (n - 1), context);\n    return n - 1;\n  }\n  return n;\n}\n\ntemplate <>\nvoid RandGaussian<float, CUDAContext>(\n    const size_t n,\n    const float mean,\n    const float std,\n    float* r,\n    CUDAContext* context) {\n  // If n is odd, we add a random Gaussian value at the end manually\n  // and generate n-1 random values using curandGenerateNormal.\n  // curandGenerateNormal requires n to be even.\n  const size_t even_n =\n      HandleOddLengthRandGaussian<float>(n, mean, std, r, context);\n  CURAND_ENFORCE(\n      curandGenerateNormal(context->curand_generator(), r, even_n, mean, std));\n}\n\ntemplate <>\nvoid RandGaussian<double, CUDAContext>(\n    const size_t n,\n    const double mean,\n    const double std,\n    double* r,\n    CUDAContext* context) {\n  const size_t even_n =\n      HandleOddLengthRandGaussian<double>(n, mean, std, r, context);\n  CURAND_ENFORCE(curandGenerateNormalDouble(\n      context->curand_generator(), r, even_n, mean, std));\n}\n\ntemplate <>\nvoid Dot<float, CUDAContext>(\n    const int n,\n    const float* a,\n    const float* b,\n    float* y,\n    CUDAContext* context) {\n  float result;\n  CUBLAS_ENFORCE(cublasSdot(context->cublas_handle(), n, a, 1, b, 1, &result));\n  context->Copy<float, CPUContext, CUDAContext>(1, &result, y);\n}\n\ntemplate <>\nvoid Dot<float16, CUDAContext>(\n    const int n,\n    const float16* a,\n    const float16* b,\n    float16* y,\n    CUDAContext* context) {\n  float16 result;\n  // execute with 32-bit math\n  CUBLAS_CHECK(cublasDotEx(\n      context->cublas_handle(),\n      n,\n      a,\n      CUDA_R_16F,\n      1,\n      b,\n      CUDA_R_16F,\n      1,\n      &result,\n      CUDA_R_16F,\n      CUDA_R_32F));\n  context->Copy<float16, CPUContext, CUDAContext>(1, &result, y);\n}\n\n// A previous version of caffe2 used Thrust but it turns out that thrust\n// reduction has an implicit scratch space allocation and deallocation, which\n// may interfere with NCCL and create a deadlock. Hence we are using a custom\n// reduction here.\n#define SUM_KERNEL_NTHREADS 128\ntemplate <typename T>\n__global__ void SumKernel(const int N, const T* X, T* Y, bool square) {\n  const int idx = threadIdx.x;\n  __shared__ float reduction_buffer[SUM_KERNEL_NTHREADS];\n\n  reduction_buffer[idx] = 0;\n\n  // A multilevel reduction.\n  // N -> 128\n  if (!square) {\n    for (int i = idx; i < N; i += SUM_KERNEL_NTHREADS) {\n      reduction_buffer[idx] += convert::To<T, float>(X[i]);\n    }\n  } else {\n    for (int i = idx; i < N; i += SUM_KERNEL_NTHREADS) {\n      float Xi = convert::To<T, float>(X[i]);\n      reduction_buffer[idx] += Xi * Xi;\n    }\n  }\n  __syncthreads();\n  // 128 -> 32\n  if (idx < 32) {\n    reduction_buffer[idx] +=\n        reduction_buffer[idx + 32] +\n        reduction_buffer[idx + 64] +\n        reduction_buffer[idx + 96];\n  }\n  __syncthreads();\n  // 32 -> 1\n  if (idx == 0) {\n    float tmp = 0;\n    for (int i = 0; i < 32; ++i) {\n      tmp += reduction_buffer[i];\n    }\n    *Y = convert::To<float, T>(tmp);\n  }\n}\n\n// According to the benchmarks script\n// caffe2/caffe2/experiments/python/device_reduce_sum_bench.py,\n// device reduce is slower for N <= 10000.\n#define DEVICE_REDUCE_SIZE_THRESHOLD 10000\n\nnamespace {\n\ntemplate <typename T>\n__global__ void SumConvertKernel(float* sum, T* dest) {\n  *dest = convert::To<float, T>(*sum);\n}\n\ntemplate <typename FloatIterT>\nvoid SumFloatIter(\n    const int N,\n    FloatIterT it,\n    float*& dest,\n    CUDAContext* context,\n    Tensor<CUDAContext>* scratch_ptr) {\n  size_t memRequired = 0;\n  cub::DeviceReduce::Sum(\n      nullptr, memRequired, it, dest, N, context->cuda_stream());\n  auto buffer_size =\n      static_cast<TIndex>((memRequired + sizeof(float) - 1) / sizeof(float));\n  if (!dest) {\n    // allocate one more float at the end of scratch for dest\n    scratch_ptr->Resize(std::vector<TIndex>{buffer_size + 1});\n    dest = scratch_ptr->template mutable_data<float>() + buffer_size;\n  } else {\n    scratch_ptr->Resize(std::vector<TIndex>{buffer_size});\n  }\n  cub::DeviceReduce::Sum(\n      static_cast<void*>(scratch_ptr->template mutable_data<float>()),\n      memRequired,\n      it,\n      dest,\n      N,\n      context->cuda_stream());\n}\n} // namespace\n\ntemplate <>\nvoid Sum<float, CUDAContext>(\n    const int N,\n    const float* x,\n    float* y,\n    CUDAContext* context,\n    Tensor<CUDAContext>* scratch_ptr) {\n  if (scratch_ptr && N > DEVICE_REDUCE_SIZE_THRESHOLD) {\n    SumFloatIter(N, x, y, context, scratch_ptr);\n  } else {\n    SumKernel<<<1, SUM_KERNEL_NTHREADS, 0, context->cuda_stream()>>>(\n      N, x, y, false);\n  }\n}\n\nnamespace {\ntemplate <typename T>\nstruct FloatTransform {\n  inline __host__ __device__ float operator()(const T v) const {\n    return convert::To<T, float>(v);\n  }\n};\n} // namespace\n\n#define CAFFE2_MATH_SUM_FUNC(T)                                           \\\n  template <>                                                             \\\n  void Sum<T, CUDAContext>(                                               \\\n      const int N,                                                        \\\n      const T* x,                                                         \\\n      T* y,                                                               \\\n      CUDAContext* context,                                               \\\n      Tensor<CUDAContext>* scratch_ptr) {                                 \\\n    if (scratch_ptr && N > DEVICE_REDUCE_SIZE_THRESHOLD) {                \\\n      FloatTransform<T> transform;                                        \\\n      cub::TransformInputIterator<float, FloatTransform<T>, const T*> it( \\\n          x, transform);                                                  \\\n      float* sum = nullptr;                                               \\\n      SumFloatIter(N, it, sum, context, scratch_ptr);                     \\\n      SumConvertKernel<<<1, 1, 0, context->cuda_stream()>>>(sum, y);      \\\n    } else {                                                              \\\n      SumKernel<<<1, SUM_KERNEL_NTHREADS, 0, context->cuda_stream()>>>(   \\\n          N, x, y, false);                                                \\\n    }                                                                     \\\n  }\n\nCAFFE2_MATH_SUM_FUNC(float16)\n#undef CAFFE2_MATH_SUM_FUNC\n\nnamespace {\ntemplate <typename T>\nstruct SqrTransform {\n  inline __host__ __device__ T operator()(const T v) const {\n    return v * v;\n  }\n};\n} //  namespace\n\ntemplate <>\nvoid SumSqr<float, CUDAContext>(\n    const int N,\n    const float* x,\n    float* y,\n    CUDAContext* context,\n    Tensor<CUDAContext>* scratch_ptr) {\n  if (scratch_ptr && N > DEVICE_REDUCE_SIZE_THRESHOLD) {\n    SqrTransform<float> transform;\n    cub::TransformInputIterator<float, SqrTransform<float>, const float*> it(\n        x, transform);\n    SumFloatIter(N, it, y, context, scratch_ptr);\n  } else {\n    SumKernel<<<1, SUM_KERNEL_NTHREADS, 0, context->cuda_stream()>>>(\n        N, x, y, true);\n  }\n}\n\n#define CAFFE2_MATH_SUMSQR_FUNC(T)                                      \\\n  template <>                                                           \\\n  void SumSqr<T, CUDAContext>(                                          \\\n      const int N,                                                      \\\n      const T* x,                                                       \\\n      T* y,                                                             \\\n      CUDAContext* context,                                             \\\n      Tensor<CUDAContext>* scratch_ptr) {                               \\\n    if (scratch_ptr && N > DEVICE_REDUCE_SIZE_THRESHOLD) {              \\\n      FloatTransform<T> float_transform;                                \\\n      cub::TransformInputIterator<float, FloatTransform<T>, const T*>   \\\n          float_it(x, float_transform);                                 \\\n      SqrTransform<float> sqr_transform;                                \\\n      cub::TransformInputIterator<                                      \\\n          float,                                                        \\\n          SqrTransform<float>,                                          \\\n          decltype(float_it)>                                           \\\n          it(float_it, sqr_transform);                                  \\\n      float* sum = nullptr;                                             \\\n      SumFloatIter(N, it, sum, context, scratch_ptr);                   \\\n      SumConvertKernel<<<1, 1, 0, context->cuda_stream()>>>(sum, y);    \\\n    } else {                                                            \\\n      SumKernel<<<1, SUM_KERNEL_NTHREADS, 0, context->cuda_stream()>>>( \\\n          N, x, y, true);                                               \\\n    }                                                                   \\\n  }\n\nCAFFE2_MATH_SUMSQR_FUNC(float16)\n#undef CAFFE2_MATH_SUMSQR_FUNC\n#undef DEVICE_REDUCE_SIZE_THRESHOLD\n\nnamespace {\ntemplate <typename T>\n__global__ void SelectKernel(\n    const int N, const int D, const T* x, const int* idx, T* y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    y[i] = x[i * D + idx[i]];\n  }\n}\n}  // namespace\n\ntemplate <>\nvoid Select<float, CUDAContext>(\n      const int N, const int D, const float* x, const int* idx, float* y,\n      CUDAContext* context) {\n  SelectKernel<float><<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS,\n                        0, context->cuda_stream()>>>(N, D, x, idx, y);\n}\n\ntemplate <>\nvoid Select<float16, CUDAContext>(\n    const int N,\n    const int D,\n    const float16* x,\n    const int* idx,\n    float16* y,\n    CUDAContext* context) {\n  SelectKernel<float16><<<\n      CAFFE_GET_BLOCKS(N),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(N, D, x, idx, y);\n}\n\nnamespace {\ntemplate <typename T>\n__global__ void ScaleKernel(const int n, const float alpha, const T* x, T* y) {\n  CUDA_1D_KERNEL_LOOP(i, n) {\n    // y[i] = convert::To<float,T>(convert::To<T, float>(x[i]) * alpha);\n    y[i] = convert::Get<T>(convert::Get<float>(x[i]) * alpha);\n  }\n}\n\ntemplate <typename T>\n__global__ void\nScaleKernelDeviceAlpha(const int n, const float* alpha, const T* x, T* y) {\n  CUDA_1D_KERNEL_LOOP(i, n) {\n    y[i] = x[i] * (*alpha);\n  }\n}\n\ntemplate <typename T>\n__global__ void PowKernel(const int n, const T* x, const T exponent, T* y) {\n  CUDA_1D_KERNEL_LOOP(i, n) {\n    y[i] = powf(x[i], exponent);\n  }\n}\n\n// fp16 specialization\ntemplate <>\n__global__ void ScaleKernelDeviceAlpha(\n    const int n,\n    const float* alpha,\n    const float16* x,\n    float16* y) {\n  CUDA_1D_KERNEL_LOOP(i, n) {\n    y[i] = convert::To<float, float16>(\n        convert::To<float16, float>(x[i]) * (*alpha));\n  }\n}\n\n}  // namespace\n\ntemplate <>\nvoid Powx<float, CUDAContext>(\n    const int N,\n    const float* a,\n    const float b,\n    float* y,\n    CUDAContext* context) {\n  PowKernel<<<\n      CAFFE_GET_BLOCKS(N),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(N, a, b, y);\n}\n\ntemplate <>\nvoid Scale<float, CUDAContext>(\n    const int n,\n    const float alpha,\n    const float* x,\n    float* y,\n    CUDAContext* context) {\n  ScaleKernel<float><<<CAFFE_GET_BLOCKS(n), CAFFE_CUDA_NUM_THREADS,\n                       0, context->cuda_stream()>>>(n, alpha, x, y);\n}\n\ntemplate <>\nvoid Scale<float16, CUDAContext>(\n    const int n,\n    const float alpha,\n    const float16* x,\n    float16* y,\n    CUDAContext* context) {\n  ScaleKernel<float16><<<\n      CAFFE_GET_BLOCKS(n),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(n, alpha, x, y);\n}\n\ntemplate <>\nvoid Scale<float, CUDAContext>(\n    const int n, const float* alpha, const float *x, float* y,\n    CUDAContext* context) {\n  ScaleKernelDeviceAlpha<float><<<\n      CAFFE_GET_BLOCKS(n), CAFFE_CUDA_NUM_THREADS, 0, context->cuda_stream()>>>(\n          n, alpha, x, y);\n}\n\ntemplate <>\nvoid Scale<float16, CUDAContext>(\n    const int n,\n    const float* alpha,\n    const float16* x,\n    float16* y,\n    CUDAContext* context) {\n  ScaleKernelDeviceAlpha<float16><<<\n      CAFFE_GET_BLOCKS(n),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(n, alpha, x, y);\n}\n\ntemplate <>\nvoid Axpy<float, CUDAContext>(\n    const int N,\n    const float alpha,\n    const float* X,\n    float* Y,\n    CUDAContext* context) {\n  CUBLAS_ENFORCE(cublasSaxpy(context->cublas_handle(), N, &alpha, X, 1, Y, 1));\n}\n\ntemplate <>\nvoid Axpy<double, CUDAContext>(\n    const int N,\n    const float alpha,\n    const double* X,\n    double* Y,\n    CUDAContext* context) {\n  double alpha_d{alpha};\n  CUBLAS_ENFORCE(\n      cublasDaxpy(context->cublas_handle(), N, &alpha_d, X, 1, Y, 1));\n}\n\ntemplate <>\nvoid Axpy<float16, CUDAContext>(\n    const int N,\n    const float alpha,\n    const float16* X,\n    float16* Y,\n    CUDAContext* context) {\n  CUBLAS_CHECK(cublasAxpyEx(\n      context->cublas_handle(),\n      N,\n      &alpha,\n      CUDA_R_16F,\n      X,\n      CUDA_R_16F,\n      1,\n      Y,\n      CUDA_R_16F,\n      1,\n      CUDA_R_32F));\n}\n\nnamespace {\ntemplate <typename T>\n__global__ void AxpyKernel(const int n, const float* a, const T* x, T* y) {\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    y[index] = convert::Get<T>(\n        convert::Get<float>(x[index]) * (*a) + convert::Get<float>(y[index]));\n  }\n}\n}  // namespace\n\ntemplate <>\nvoid Axpy<float, CUDAContext>(\n    const int n, const float* alpha, const float* X,\n    float* Y, CUDAContext* context) {\n  AxpyKernel<float><<<CAFFE_GET_BLOCKS(n), CAFFE_CUDA_NUM_THREADS,\n                       0, context->cuda_stream()>>>(n, alpha, X, Y);\n}\n\ntemplate <>\nvoid Axpy<float16, CUDAContext>(\n    const int n,\n    const float* alpha,\n    const float16* X,\n    float16* Y,\n    CUDAContext* context) {\n  AxpyKernel<float16><<<\n      CAFFE_GET_BLOCKS(n),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(n, alpha, X, Y);\n}\n\nnamespace {\ntemplate <typename T>\n__global__ void AxpbyKernel(const int n, const T a, const T* x,\n                             const T b, T* y) {\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    y[index] = x[index] * a + y[index] * b;\n  }\n}\n}  // namespace\n\ntemplate <>\nvoid Axpby<float, CUDAContext>(\n    const int n, const float a, const float* x, const float b, float* y,\n    CUDAContext* context) {\n  AxpbyKernel<float><<<CAFFE_GET_BLOCKS(n), CAFFE_CUDA_NUM_THREADS,\n                       0, context->cuda_stream()>>>(n, a, x, b, y);\n}\n\nnamespace {\n\ntemplate <typename T>\n__global__ void im2col_gpu_kernel_nchw(const int n, const T* data_im,\n    const int height, const int width, const int kernel_h, const int kernel_w,\n    const int dilation_h, const int dilation_w,\n    const int pad_t, const int pad_l,\n    const int stride_h, const int stride_w,\n    const int height_col, const int width_col,\n    T* data_col) {\n\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    int w_out = index % width_col;\n    int h_index = index / width_col;\n    int h_out = h_index % height_col;\n    int channel_in = h_index / height_col;\n    int channel_out = channel_in * kernel_h * kernel_w;\n    int h_in = h_out * stride_h - pad_t;\n    int w_in = w_out * stride_w - pad_l;\n    T* data_col_ptr = data_col;\n    data_col_ptr += (channel_out * height_col + h_out) * width_col + w_out;\n    const T* data_im_ptr = data_im;\n    data_im_ptr += (channel_in * height + h_in) * width + w_in;\n    for (int i = 0; i < kernel_h; ++i) {\n      for (int j = 0; j < kernel_w; ++j) {\n        int h = h_in + i * dilation_h;\n        int w = w_in + j * dilation_w;\n        *data_col_ptr = (h >= 0 && w >= 0 && h < height && w < width) ?\n            data_im_ptr[i * dilation_h * width + j * dilation_w] : 0;\n        data_col_ptr += height_col * width_col;\n      }\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void im2col_gpu_kernel_nhwc(const int n, const T* data_im,\n    const int height, const int width, const int kernel_h, const int kernel_w,\n    const int dilation_h, const int dilation_w,\n    const int pad_t, const int pad_l,\n    const int stride_h, const int stride_w,\n    const int width_col, const int channels,\n    T* data_col) {\n\n  const int dkernel_h = dilation_h * (kernel_h - 1) + 1;\n  const int dkernel_w = dilation_w * (kernel_w - 1) + 1;\n\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    int channel_in = index % channels;\n    int w_out = index / channels % width_col;\n    int h_out = index / channels / width_col;\n    int h_in = h_out * stride_h - pad_t;\n    int w_in = w_out * stride_w - pad_l;\n    T* local_data_col = data_col +\n        ((h_out * width_col) + w_out) * channels * kernel_h * kernel_w\n        + channel_in;\n    for (int i = 0; i < dkernel_h; i += dilation_h) {\n      int h = h_in + i;\n      for (int j = 0; j < dkernel_w; j += dilation_w) {\n        int w = w_in + j;\n        *local_data_col = (h >= 0 && w >= 0 && h < height && w < width) ?\n            data_im[(h * width + w) * channels + channel_in] : 0;\n        local_data_col += channels;\n      }\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void col2im_gpu_kernel_nchw(const int n, const T* data_col,\n    const int height, const int width,\n    const int patch_h, const int patch_w,\n    const int dilation_h, const int dilation_w,\n    const int pad_t, const int pad_l,\n    const int stride_h, const int stride_w,\n    const int height_col, const int width_col,\n    T* data_im) {\n\n  const int dpatch_h = dilation_h * (patch_h - 1) + 1;\n  const int dpatch_w = dilation_w * (patch_w - 1) + 1;\n\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    T val = 0;\n    int w = index % width + pad_l;\n    int h = (index / width) % height + pad_t;\n    int c = index / (width * height);\n\n    // compute the start and end of the output\n    int w_col_start = (w < dpatch_w) ? 0 : (w - dpatch_w) / stride_w + 1;\n    int w_col_end = min(w / stride_w + 1, width_col);\n    int h_col_start = (h < dpatch_h) ? 0 : (h - dpatch_h) / stride_h + 1;\n    int h_col_end = min(h / stride_h + 1, height_col);\n\n    for (int h_col = h_col_start; h_col < h_col_end; ++h_col) {\n      for (int w_col = w_col_start; w_col < w_col_end; ++w_col) {\n        int h_k = (h - h_col * stride_h);\n        int w_k = (w - w_col * stride_w);\n        if (h_k % dilation_h == 0 && w_k % dilation_w == 0) {\n          h_k /= dilation_h;\n          w_k /= dilation_w;\n          int data_col_index =\n              (((c * patch_h + h_k) * patch_w + w_k) * height_col + h_col) *\n                  width_col +\n              w_col;\n          val += data_col[data_col_index];\n        }\n      }\n    }\n    data_im[index] = val;\n  }\n}\n\ntemplate <typename T>\n__global__ void col2im_gpu_kernel_nhwc(const int n, const T* data_col,\n    const int width, const int channels,\n    const int patch_h, const int patch_w,\n    const int dilation_h, const int dilation_w,\n    const int pad_t, const int pad_l,\n    const int stride_h, const int stride_w,\n    const int height_col, const int width_col,\n    T* data_im) {\n\n  const int dpatch_h = dilation_h * (patch_h - 1) + 1;\n  const int dpatch_w = dilation_w * (patch_w - 1) + 1;\n\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    T val = 0;\n    int c = index % channels;\n    int w = index / channels % width + pad_l;\n    int h = index / channels / width + pad_t;\n    // compute the start and end of the output\n    int w_col_start = (w < dpatch_w) ? 0 : (w - dpatch_w) / stride_w + 1;\n    int w_col_end = min(w / stride_w + 1, width_col);\n    int h_col_start = (h < dpatch_h) ? 0 : (h - dpatch_h) / stride_h + 1;\n    int h_col_end = min(h / stride_h + 1, height_col);\n    int channels_col = patch_h * patch_w * channels;\n\n    for (int h_col = h_col_start; h_col < h_col_end; ++h_col) {\n      for (int w_col = w_col_start; w_col < w_col_end; ++w_col) {\n        int h_k = h - h_col * stride_h;\n        int w_k = w - w_col * stride_w;\n        if (h_k % dilation_h == 0 && w_k % dilation_w == 0) {\n          h_k /= dilation_h;\n          w_k /= dilation_w;\n          int c_col = (h_k * patch_w + w_k) * channels + c;\n          val += data_col[(h_col * width_col + w_col) * channels_col + c_col];\n        }\n      }\n    }\n    data_im[index] = val;\n  }\n}\n\n// Ported from caffe1\ntemplate <typename T, int num_axes>\n__global__ void im2col_nd_gpu_kernel(\n    const int n,\n    const T* data_im,\n    const int* im_shape,\n    const int* col_shape,\n    const int* kernel_shape,\n    const int* pad,\n    const int* stride,\n    const int* dilation,\n    T* data_col) {\n  int d_offset[num_axes]; // NOLINT(runtime/arrays)\n  int d_iter[num_axes]; // NOLINT(runtime/arrays)\n\n  __shared__ int shared_dilation[num_axes];\n  __shared__ int shared_kernel_shape[num_axes];\n  __shared__ int shared_pad[num_axes];\n  __shared__ int shared_stride[num_axes];\n  __shared__ int shared_col_shape[num_axes + 1];\n  __shared__ int shared_im_shape[num_axes + 1];\n\n  if (threadIdx.x < num_axes) {\n    shared_dilation[threadIdx.x] = dilation[threadIdx.x];\n    shared_kernel_shape[threadIdx.x] = kernel_shape[threadIdx.x];\n    shared_pad[threadIdx.x] = pad[threadIdx.x];\n    shared_stride[threadIdx.x] = stride[threadIdx.x];\n  }\n  if (threadIdx.x < num_axes + 1) {\n    shared_col_shape[threadIdx.x] = col_shape[threadIdx.x];\n    shared_im_shape[threadIdx.x] = im_shape[threadIdx.x];\n  }\n  __syncthreads();\n\n  int i;\n  int kernel_size = 1;\n  for (i = 0; i < num_axes; ++i) {\n    kernel_size *= shared_kernel_shape[i];\n  }\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    if (index >= col_shape[0]) {\n      break;\n    }\n    // Initialize offset, computed in the loop below, with intermediate\n    // computations used to compute the spatial indices.\n    int offset = index;\n    for (i = num_axes - 1; i >= 0; --i) {\n      if (i < num_axes - 1) {\n        offset /= shared_kernel_shape[i + 1];\n      }\n      d_offset[i] = offset % shared_kernel_shape[i];\n    }\n    for (i = 0; i < num_axes; ++i) {\n      d_iter[i] = 0;\n    }\n    bool incremented;\n    do {\n      int index_col = index;\n      int index_im = index / kernel_size;\n      bool in_range = true;\n      for (i = 0; i < num_axes; ++i) {\n        const int d = d_iter[i];\n        const int d_im = d * shared_stride[i] - shared_pad[i] +\n            d_offset[i] * shared_dilation[i];\n        in_range &= (d_im >= 0 && d_im < shared_im_shape[i + 1]);\n\n        index_col *= shared_col_shape[i + 1];\n        index_col += d;\n        index_im *= shared_im_shape[i + 1];\n        index_im += d_im;\n      }\n      if (in_range) {\n        // data_col[index_col] = 0;\n        data_col[index_col] = data_im[index_im];\n        // T temp = data_im[index_im];\n      } else {\n        data_col[index_col] = 0;\n      }\n\n      incremented = false;\n      for (i = num_axes - 1; i >= 0; --i) {\n        // const int d_max = shared_kernel_shape[i];\n        const int d_max = shared_col_shape[i + 1];\n        if (d_iter[i] == d_max - 1) {\n          d_iter[i] = 0;\n        } else { // d_iter[i] < d_max - 1\n          ++d_iter[i];\n          incremented = true;\n          break;\n        }\n      } // for (int i = num_axes - 1; i >= 0; --i)\n    } while (incremented); // do\n  } // CUDA_KERNEL_LOOP(index, n)\n}\n\ntemplate <typename T, int num_axes>\n__global__ void col2im_nd_gpu_kernel(\n    const int n,\n    const T* data_col,\n    const int* im_shape,\n    const int* col_shape,\n    const int* kernel_shape,\n    const int* pad,\n    const int* stride,\n    const int* dilation,\n    T* data_im) {\n  int d_im[num_axes]; // NOLINT(runtime/arrays)\n  int d_col_iter[num_axes]; // NOLINT(runtime/arrays)\n  int d_col_start[num_axes]; // NOLINT(runtime/arrays)\n  int d_col_end[num_axes]; // NOLINT(runtime/arrays)\n\n  __shared__ int shared_dilation[num_axes];\n  __shared__ int shared_kernel_shape[num_axes];\n  __shared__ int shared_pad[num_axes];\n  __shared__ int shared_stride[num_axes];\n  __shared__ int shared_col_shape[num_axes + 1];\n  __shared__ int shared_im_shape[num_axes + 1];\n\n  if (threadIdx.x < num_axes) {\n    shared_dilation[threadIdx.x] = dilation[threadIdx.x];\n    shared_kernel_shape[threadIdx.x] = kernel_shape[threadIdx.x];\n    shared_pad[threadIdx.x] = pad[threadIdx.x];\n    shared_stride[threadIdx.x] = stride[threadIdx.x];\n  }\n\n  if (threadIdx.x < num_axes + 1) {\n    shared_col_shape[threadIdx.x] = col_shape[threadIdx.x];\n    shared_im_shape[threadIdx.x] = im_shape[threadIdx.x];\n  }\n  __syncthreads();\n\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    // Initialize channel_in, computed in the loop below, with intermediate\n    // computations used to compute the spatial indices.\n    int c_im = index;\n    // Calculate d_im (image dimensions).\n    for (int i = num_axes - 1; i >= 0; --i) {\n      d_im[i] = c_im % shared_im_shape[i + 1] + shared_pad[i];\n      c_im /= shared_im_shape[i + 1];\n    }\n    // Calculate col start/end indices.\n    bool done = false;\n    for (int i = 0; i < num_axes; ++i) {\n      const int kernel_extent =\n          shared_dilation[i] * (shared_kernel_shape[i] - 1) + 1;\n      d_col_start[i] = d_col_iter[i] = (d_im[i] < kernel_extent)\n          ? 0\n          : (d_im[i] - kernel_extent) / shared_stride[i] + 1;\n      d_col_end[i] =\n          min(d_im[i] / shared_stride[i] + 1, shared_col_shape[i + 1]);\n      if (d_col_start[i] >= d_col_end[i]) {\n        // Skip computation if the dimension is 0 at any spatial axis --\n        // final val will be 0.\n        data_im[index] = 0;\n        done = true;\n        break; // for (int i = 0; i < num_axes; ++i)\n      }\n    }\n    if (done) {\n      continue; // CUDA_KERNEL_LOOP(index, n)\n    }\n    // Loop over the col to compute the output val.\n    T val = 0;\n    bool incremented = true;\n    bool skip = false;\n    do {\n      // Compute the final offset.\n      int final_offset = 0;\n      int kernel_shape_prod = 1;\n      int kernel_index;\n      for (int i = num_axes - 1; i >= 0; --i) {\n        kernel_index = d_im[i] - d_col_iter[i] * shared_stride[i];\n        if (kernel_index % shared_dilation[i]) {\n          skip = true;\n          break;\n        } else {\n          kernel_index /= shared_dilation[i];\n          final_offset += kernel_index * kernel_shape_prod;\n          kernel_shape_prod *= shared_kernel_shape[i];\n        }\n      }\n      if (!skip) {\n        final_offset += kernel_shape_prod * c_im;\n        for (int i = 0; i < num_axes; ++i) {\n          final_offset *= shared_col_shape[i + 1];\n          final_offset += d_col_iter[i];\n        }\n        val += data_col[final_offset];\n      }\n      skip = false;\n      incremented = false;\n      for (int i = num_axes - 1; i >= 0; --i) {\n        const int d_max = d_col_end[i];\n        if (d_col_iter[i] == d_max - 1) {\n          d_col_iter[i] = d_col_start[i];\n        } else { // d_col_iter[i] < d_max - 1\n          ++d_col_iter[i];\n          incremented = true;\n          break; // for (int i = num_axes - 1; i >= 0; --i)\n        }\n      } // for (int i = num_axes - 1; i >= 0; --i)\n    } while (incremented);\n    data_im[index] = val;\n  } // CUDA_KERNEL_LOOP(index, n)\n}\n\n}  // namespace\n\ntemplate <>\nvoid Im2col<float, CUDAContext, StorageOrder::NCHW>(\n    const float* data_im, const int channels,\n    const int height, const int width, const int kernel_h, const int kernel_w,\n    const int dilation_h, const int dilation_w,\n    const int pad_t, const int pad_l, const int pad_b, const int pad_r,\n    const int stride_h,\n    const int stride_w, float* data_col, CUDAContext* context) {\n\n  const int dkernel_h = dilation_h * (kernel_h - 1) + 1;\n  const int dkernel_w = dilation_w * (kernel_w - 1) + 1;\n\n  // We are going to launch channels * height_col * width_col kernels, each\n  // kernel responsible for copying a single-channel grid.\n  int height_col = (height + pad_t + pad_b - dkernel_h) / stride_h + 1;\n  int width_col = (width + pad_l + pad_r - dkernel_w) / stride_w + 1;\n  int num_kernels = channels * height_col * width_col;\n  // NOLINT_NEXT_LINE(whitespace/operators)\n  im2col_gpu_kernel_nchw<float><<<CAFFE_GET_BLOCKS(num_kernels),\n                                  CAFFE_CUDA_NUM_THREADS, 0,\n                                  context->cuda_stream()>>>(\n      num_kernels, data_im, height, width, kernel_h, kernel_w,\n      dilation_h, dilation_w, pad_t, pad_l, stride_h, stride_w,\n      height_col, width_col, data_col);\n}\n\ntemplate <>\nvoid Im2col<float, CUDAContext, StorageOrder::NHWC>(\n    const float* data_im, const int channels,\n    const int height, const int width, const int kernel_h, const int kernel_w,\n    const int dilation_h, const int dilation_w,\n    const int pad_t, const int pad_l, const int pad_b, const int pad_r,\n    const int stride_h,\n    const int stride_w, float* data_col, CUDAContext* context) {\n\n  const int dkernel_h = dilation_h * (kernel_h - 1) + 1;\n  const int dkernel_w = dilation_w * (kernel_w - 1) + 1;\n\n  // We are going to launch height_col * width_col * channels kernels, each\n  // kernel responsible for copying a single-channel grid.\n  int height_col = (height + pad_t + pad_b - dkernel_h) / stride_h + 1;\n  int width_col = (width + pad_l + pad_r - dkernel_w) / stride_w + 1;\n  int num_kernels = height_col * width_col * channels;\n  // NOLINT_NEXT_LINE(whitespace/operators)\n  im2col_gpu_kernel_nhwc<float><<<CAFFE_GET_BLOCKS(num_kernels),\n                                  CAFFE_CUDA_NUM_THREADS, 0,\n                                  context->cuda_stream()>>>(\n      num_kernels, data_im, height, width, kernel_h, kernel_w,\n      dilation_h, dilation_w, pad_t, pad_l, stride_h, stride_w,\n      width_col, channels, data_col);\n}\n\n\ntemplate <>\nvoid Col2im<float, CUDAContext, StorageOrder::NCHW>(\n    const float* data_col, const int channels,\n    const int height, const int width, const int kernel_h, const int kernel_w,\n    const int dilation_h, const int dilation_w,\n    const int pad_t, const int pad_l, const int pad_b, const int pad_r,\n    const int stride_h,\n    const int stride_w, float* data_im, CUDAContext* context) {\n\n  const int dkernel_h = dilation_h * (kernel_h - 1) + 1;\n  const int dkernel_w = dilation_w * (kernel_w - 1) + 1;\n\n  int height_col = (height + pad_t + pad_b - dkernel_h) / stride_h + 1;\n  int width_col = (width + pad_l + pad_r - dkernel_w) / stride_w + 1;\n  int num_kernels = channels * height * width;\n  // To avoid involving atomic operations, we will launch one kernel per\n  // bottom dimension, and then in the kernel add up the top dimensions.\n  col2im_gpu_kernel_nchw<float><<<CAFFE_GET_BLOCKS(num_kernels),\n                                  CAFFE_CUDA_NUM_THREADS, 0,\n                                  context->cuda_stream()>>>(\n      num_kernels, data_col, height, width, kernel_h, kernel_w,\n      dilation_h, dilation_w,\n      pad_t, pad_l, stride_h, stride_w,\n      height_col, width_col, data_im);\n}\n\ntemplate <>\nvoid Col2im<float, CUDAContext, StorageOrder::NHWC>(\n    const float* data_col, const int channels,\n    const int height, const int width, const int kernel_h, const int kernel_w,\n    const int dilation_h, const int dilation_w,\n    const int pad_t, const int pad_l, const int pad_b, const int pad_r,\n    const int stride_h,\n    const int stride_w, float* data_im, CUDAContext* context) {\n\n  const int dkernel_h = dilation_h * (kernel_h - 1) + 1;\n  const int dkernel_w = dilation_w * (kernel_w - 1) + 1;\n\n  int height_col = (height + pad_t + pad_b - dkernel_h) / stride_h + 1;\n  int width_col = (width + pad_l + pad_r - dkernel_w) / stride_w + 1;\n  int num_kernels = height * width * channels;\n  // To avoid involving atomic operations, we will launch one kernel per\n  // bottom dimension, and then in the kernel add up the top dimensions.\n  col2im_gpu_kernel_nhwc<float><<<CAFFE_GET_BLOCKS(num_kernels),\n                                  CAFFE_CUDA_NUM_THREADS, 0,\n                                  context->cuda_stream()>>>(\n      num_kernels, data_col, width, channels, kernel_h, kernel_w,\n      dilation_h, dilation_w,\n      pad_t, pad_l, stride_h, stride_w, height_col, width_col, data_im);\n}\n\ntemplate <>\nvoid Col2imNd<float, CUDAContext, StorageOrder::NCHW>(\n    const float* data_col,\n    const int* img_shape,\n    const int* col_shape,\n    const int img_size,\n    const int col_size,\n    const int* kernel_shape,\n    const int* stride,\n    const int* dilation,\n    const int* pad,\n    const int N,\n    float* data_img,\n    CUDAContext* context) {\n  CAFFE_ENFORCE_LT(\n      N, CAFFE_CUDA_NUM_THREADS, \"num_axes should be smaller than block size.\");\n\n#define COL2IM_ND_KERNEL(n)                                                   \\\n  col2im_nd_gpu_kernel<float, n> /* NOLINT_NEXT_LINE(whitespace/operators) */ \\\n      <<<CAFFE_GET_BLOCKS(img_size),                                          \\\n         CAFFE_CUDA_NUM_THREADS,                                              \\\n         0,                                                                   \\\n         context->cuda_stream()>>>(                                           \\\n          img_size,                                                           \\\n          data_col,                                                           \\\n          img_shape,                                                          \\\n          col_shape,                                                          \\\n          kernel_shape,                                                       \\\n          pad,                                                                \\\n          stride,                                                             \\\n          dilation,                                                           \\\n          data_img)\n\n  switch (N) {\n    case 1:\n      COL2IM_ND_KERNEL(1);\n      break;\n    case 2:\n      COL2IM_ND_KERNEL(2);\n      break;\n    case 3:\n      COL2IM_ND_KERNEL(3);\n      break;\n    case 4:\n      COL2IM_ND_KERNEL(4);\n      break;\n    case 5:\n      COL2IM_ND_KERNEL(5);\n      break;\n    default:\n      CAFFE_THROW(\n          \"Col2imNd does not support computation with \", N, \" spatial axes\");\n  }\n}\n\ntemplate <>\nvoid Im2colNd<float, CUDAContext, StorageOrder::NCHW>(\n    const float* data_img,\n    const int* img_shape,\n    const int* col_shape,\n    const int img_size,\n    const int col_size,\n    const int* kernel_shape,\n    const int* stride,\n    const int* dilation,\n    const int* pad,\n    const int N,\n    float* data_col,\n    CUDAContext* context,\n    bool /*accumlate_output*/) {\n  CAFFE_ENFORCE_LT(\n      N, CAFFE_CUDA_NUM_THREADS, \"num_axes should be smaller than block size.\");\n\n#define IM2COL_ND_KERNEL(n)                                                   \\\n  im2col_nd_gpu_kernel<float, n> /* NOLINT_NEXT_LINE(whitespace/operators) */ \\\n      <<<CAFFE_GET_BLOCKS(col_size),                                          \\\n         CAFFE_CUDA_NUM_THREADS,                                              \\\n         0,                                                                   \\\n         context->cuda_stream()>>>(                                           \\\n          col_size,                                                           \\\n          data_img,                                                           \\\n          img_shape,                                                          \\\n          col_shape,                                                          \\\n          kernel_shape,                                                       \\\n          pad,                                                                \\\n          stride,                                                             \\\n          dilation,                                                           \\\n          data_col)\n\n  switch (N) {\n    case 1:\n      IM2COL_ND_KERNEL(1);\n      break;\n    case 2:\n      IM2COL_ND_KERNEL(2);\n      break;\n    case 3:\n      IM2COL_ND_KERNEL(3);\n      break;\n    case 4:\n      IM2COL_ND_KERNEL(4);\n    case 5:\n      IM2COL_ND_KERNEL(5);\n      break;\n    default:\n      CAFFE_THROW(\n          \"Im2colNd does not support computation with \", N, \" spatial axes\");\n  }\n}\n\ntemplate <>\nvoid CopyMatrix<CUDAContext>(\n    const size_t itemsize,\n    const int M,\n    const int N,\n    const void* A,\n    const int lda,\n    void* B,\n    const int ldb,\n    CUDAContext* context,\n    TypeMeta::TypedCopy copy) {\n  CAFFE_ENFORCE(!copy, \"Copy constructor is not supported in CUDA context\");\n  cudaMemcpy2DAsync(B, ldb * itemsize, A, lda * itemsize, N * itemsize, M,\n                    cudaMemcpyDeviceToDevice, context->cuda_stream());\n}\n\ntemplate <>\nvoid CopyVector<float, CUDAContext>(\n    const int N,\n    const float* src,\n    float* dst,\n    CUDAContext* context) {\n  if (src != dst && N > 0) {\n    cudaMemcpyAsync(\n        dst,\n        src,\n        sizeof(float) * N,\n        cudaMemcpyDeviceToDevice,\n        context->cuda_stream());\n  }\n}\n\nnamespace {\n__global__ void rowwise_max_kernel(\n    const int rows,\n    const int cols,\n    const float* data,\n    float* out) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  for (int rowIndex = blockIdx.x; rowIndex < rows; rowIndex += gridDim.x) {\n    float maxval = -FLT_MAX;\n    // NB: The memory accesses here are sequentialized; without unrolling\n    // the loop, there will not be any ILP.  However, because we are running\n    // this kernel with a lot of threads, this should not be a big problem.\n    // However, if we reduce the number of threads to take advantage of\n    // warp-wide synchronization, this may become a problem again.\n    for (int colIndex = threadIdx.x; colIndex < cols; colIndex += blockDim.x) {\n      maxval = max(data[rowIndex * cols + colIndex], maxval);\n    }\n    maxval = BlockReduce(temp_storage).Reduce(maxval, cub::Max());\n    if (threadIdx.x == 0) {\n      out[rowIndex] = maxval;\n    }\n    __syncthreads();\n  }\n}\n\n__global__ void colwise_max_kernel(\n    const int rows,\n    const int cols,\n    const float* data,\n    float* out) {\n  typedef cub::BlockReduce<float, CAFFE_CUDA_NUM_THREADS> BlockReduce;\n  __shared__ typename BlockReduce::TempStorage temp_storage;\n  for (int colIndex = blockIdx.x; colIndex < cols; colIndex += gridDim.x) {\n    float maxval = -FLT_MAX;\n    for (int rowIndex = threadIdx.x; rowIndex < rows; rowIndex += blockDim.x) {\n      maxval = max(data[rowIndex * cols + colIndex], maxval);\n    }\n    maxval = BlockReduce(temp_storage).Reduce(maxval, cub::Max());\n    if (threadIdx.x == 0) {\n      out[colIndex] = maxval;\n    }\n    __syncthreads();\n  }\n}\n\n} // namespace\n\ntemplate <>\nvoid RowwiseMax(\n    const int N,\n    const int D,\n    const float* x,\n    float* y,\n    CUDAContext* context) {\n  rowwise_max_kernel<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(N, D, x, y);\n}\n\ntemplate <>\nvoid ColwiseMax(\n    const int N,\n    const int D,\n    const float* x,\n    float* y,\n    CUDAContext* context) {\n  colwise_max_kernel<<<\n      std::min(D, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(N, D, x, y);\n}\n\nnamespace {\n__global__ void\nmaximum_kernel(const int N, const float alpha, const float* x, float* y) {\n  CUDA_1D_KERNEL_LOOP(i, N) {\n    y[i] = fmaxf(x[i], alpha);\n  }\n}\n} // namespace\n\ntemplate <>\nvoid Maximum(\n    const int N,\n    const float alpha,\n    const float* x,\n    float* y,\n    CUDAContext* context) {\n  maximum_kernel<<<\n      std::min(N, CAFFE_MAXIMUM_NUM_BLOCKS),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context->cuda_stream()>>>(N, alpha, x, y);\n}\n\nnamespace {\n\nconstexpr int kCompileTimeCUDAMaxTransposeDims = 8;\n\n__device__ void ComputeYStride(\n    const int num_axes,\n    const int* y_dims,\n    const int* axes,\n    int* y_strides) {\n  int buff[kCompileTimeCUDAMaxTransposeDims];\n  int cur_stride = 1;\n  for (int i = num_axes - 1; i >= 0; --i) {\n    buff[i] = cur_stride;\n    cur_stride *= y_dims[i];\n  }\n  for (int i = 0; i < num_axes; ++i) {\n    y_strides[axes[i]] = buff[i];\n  }\n}\n\n__device__ int GetYIndex(\n    const int num_axes,\n    const int* x_dims,\n    const int* y_strides,\n    int x_index) {\n  int y_index = 0;\n  for (int i = num_axes - 1; i >= 0 && x_index > 0; --i) {\n    y_index += x_index % x_dims[i] * y_strides[i];\n    x_index /= x_dims[i];\n  }\n  return y_index;\n}\n\ntemplate <typename T>\n__global__ void TransposeCUDA(\n    const int num_axes,\n    const int* x_dims,\n    const int* y_dims,\n    const int* axes,\n    const int data_size,\n    const T* X,\n    T* Y) {\n  __shared__ int y_strides[kCompileTimeCUDAMaxTransposeDims];\n  ComputeYStride(num_axes, y_dims, axes, y_strides);\n  __syncthreads();\n  CUDA_1D_KERNEL_LOOP(x_index, data_size) {\n    const int y_index = GetYIndex(num_axes, x_dims, y_strides, x_index);\n#if __CUDA_ARCH__ >= 350\n    Y[y_index] = __ldg(X + x_index);\n#else\n    Y[y_index] = X[x_index];\n#endif\n  }\n}\n\n} // namespace\n\n#define CAFFE2_SPECIALIZED_CUDA_TRANSPOSE(T)                  \\\n  template <>                                                 \\\n  void Transpose<T, CUDAContext>(                             \\\n      const int num_axes,                                     \\\n      const int* x_dims,                                      \\\n      const int* y_dims,                                      \\\n      const int* axes,                                        \\\n      const int data_size,                                    \\\n      const T* X,                                             \\\n      T* Y,                                                   \\\n      CUDAContext* context) {                                 \\\n    CAFFE_ENFORCE(                                            \\\n        num_axes <= kCompileTimeCUDAMaxTransposeDims,         \\\n        \"num_axes exceeds compile time max.\");                \\\n    TransposeCUDA<T>                                          \\\n        <<<CAFFE_GET_BLOCKS(data_size),                       \\\n           CAFFE_CUDA_NUM_THREADS,                            \\\n           0,                                                 \\\n           context->cuda_stream()>>>(                         \\\n            num_axes, x_dims, y_dims, axes, data_size, X, Y); \\\n  }\nCAFFE2_SPECIALIZED_CUDA_TRANSPOSE(float)\nCAFFE2_SPECIALIZED_CUDA_TRANSPOSE(double)\nCAFFE2_SPECIALIZED_CUDA_TRANSPOSE(int)\nCAFFE2_SPECIALIZED_CUDA_TRANSPOSE(long)\n#undef CAFFE2_SPECIALIZED_CUDA_TRANSPOSE\n\n} // namespace math\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/math_gpu_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n#include <memory>\n#include <vector>\n\n#include <gtest/gtest.h>\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/flags.h\"\n#include \"caffe2/operators/utility_ops.h\"\n#include \"caffe2/utils/math.h\"\n\nCAFFE2_DECLARE_string(caffe_test_root);\n\nnamespace caffe2 {\n\nvoid executeGpuBinaryOpTest(\n    int shapex0,\n    int shapex1,\n    int shapey,\n    std::function<float(int)> input0,\n    std::function<float(int)> input1,\n    std::function<void(\n        int N0,\n        int N1,\n        const float* src0,\n        const float* src1,\n        float* dst,\n        CUDAContext* context)> operation,\n    std::function<float(int)> correct_output) {\n  if (!HasCudaGPU())\n    return;\n  Workspace ws;\n  DeviceOption option;\n  option.set_device_type(CUDA);\n  CUDAContext context(option);\n\n  Blob* blobx0 = ws.CreateBlob(\"X0\");\n  Blob* blobx1 = ws.CreateBlob(\"X1\");\n  Blob* bloby = ws.CreateBlob(\"Y\");\n  Blob* bloby_host = ws.CreateBlob(\"Y_host\");\n\n  auto* tensorx0 = blobx0->GetMutable<Tensor<CUDAContext>>();\n  auto* tensorx1 = blobx1->GetMutable<Tensor<CUDAContext>>();\n  auto* tensory = bloby->GetMutable<Tensor<CUDAContext>>();\n\n  vector<int> shapex0_vector{shapex0};\n  vector<int> shapex1_vector{shapex1};\n  vector<int> shapey_vector{shapey};\n\n  tensorx0->Resize(shapex0_vector);\n  tensorx1->Resize(shapex1_vector);\n  tensory->Resize(shapey_vector);\n\n  for (int i = 0; i < shapex0; i++) {\n    math::Set<float, CUDAContext>(\n        1, input0(i), tensorx0->mutable_data<float>() + i, &context);\n  }\n  for (int i = 0; i < shapex1; i++) {\n    math::Set<float, CUDAContext>(\n        1, input1(i), tensorx1->mutable_data<float>() + i, &context);\n  }\n  operation(\n      shapex0,\n      shapex1,\n      tensorx0->template data<float>(),\n      tensorx1->template data<float>(),\n      tensory->mutable_data<float>(),\n      &context);\n  context.FinishDeviceComputation();\n\n  // Copy result to CPU so we can inspect it\n  auto* tensory_host = bloby_host->GetMutable<Tensor<CPUContext>>();\n  tensory_host->CopyFrom<CUDAContext, CUDAContext>(*tensory, &context);\n  context.FinishDeviceComputation();\n\n  for (int i = 0; i < shapey; ++i) {\n    EXPECT_EQ(tensory_host->data<float>()[i], correct_output(i));\n  }\n}\n\nTEST(MathUtilGPUTest, testAddStripedBatch) {\n  if (!HasCudaGPU())\n    return;\n  Workspace ws;\n  DeviceOption option;\n  option.set_device_type(CUDA);\n  CUDAContext context(option);\n  Blob* blobx = ws.CreateBlob(\"X\");\n  Blob* bloby = ws.CreateBlob(\"Y\");\n  Blob* bloby_host = ws.CreateBlob(\"Y_host\");\n\n  vector<int> shapex{33 * 9, 25};\n  vector<int> shapey{33, 25};\n\n  auto* tensorx = blobx->GetMutable<Tensor<CUDAContext>>();\n  tensorx->Resize(shapex);\n  int stripe = 33 * 25;\n  vector<float> tot(33, 0.0);\n  for (int j = 0; j < 9; j++) {\n    // Have different values for each line\n    for (int k = 0; k < 33; k++) {\n      math::Set<float, CUDAContext>(\n          33,\n          1.0 + j + k,\n          tensorx->mutable_data<float>() + j * stripe + k * 25,\n          &context);\n      tot[k] += 1.0 + j + k;\n    }\n  }\n\n  auto* tensory = bloby->GetMutable<Tensor<CUDAContext>>();\n  tensory->Resize(shapey);\n  math::Set<float, CUDAContext>(\n      stripe, 0.0, tensory->mutable_data<float>(), &context);\n\n  math::AddStripedBatch<float, CUDAContext>(\n      stripe,\n      tensorx->template data<float>(),\n      tensory->mutable_data<float>(),\n      stripe,\n      9,\n      &context);\n  context.FinishDeviceComputation();\n\n  // Copy result to CPU so we can inspect it\n  auto* tensory_host = bloby_host->GetMutable<Tensor<CPUContext>>();\n  tensory_host->CopyFrom<CUDAContext, CUDAContext>(*tensory, &context);\n  context.FinishDeviceComputation();\n\n  for (int k = 0; k < 33; k++) {\n    for (int i = 0; i < 25; i++) {\n      EXPECT_EQ(tensory_host->data<float>()[k * 25 + i], tot[k]);\n    }\n  }\n}\n\nTEST(MathUtilGPUTest, testReduceMin) {\n  executeGpuBinaryOpTest(\n      6,\n      1,\n      1,\n      [](int /*i*/) { return 11.0f; },\n      [](int /*i*/) { return 0.0f; },\n      [](int N0,\n         int /*N1*/,\n         const float* src0,\n         const float* /*src1*/,\n         float* dst,\n         CUDAContext* context) {\n        Tensor<CUDAContext> aux;\n        math::ReduceMin<float, CUDAContext>(N0, src0, dst, &aux, context);\n      },\n      [](int /*i*/) { return 11.0f; });\n  executeGpuBinaryOpTest(\n      6,\n      1,\n      1,\n      [](int i) { return i == 3 ? 11.0f : 17.0f; },\n      [](int /*i*/) { return 0.0f; },\n      [](int N0,\n         int /*N1*/,\n         const float* src0,\n         const float* /*src1*/,\n         float* dst,\n         CUDAContext* context) {\n        Tensor<CUDAContext> aux;\n        math::ReduceMin<float, CUDAContext>(N0, src0, dst, &aux, context);\n      },\n      [](int /*i*/) { return 11.0f; });\n}\n\nTEST(MathUtilGPUTest, testReduceMax) {\n  executeGpuBinaryOpTest(\n      6,\n      1,\n      1,\n      [](int /*i*/) { return 11.0f; },\n      [](int /*i*/) { return 0.0f; },\n      [](int N0,\n         int /*N1*/,\n         const float* src0,\n         const float* /*src1*/,\n         float* dst,\n         CUDAContext* context) {\n        Tensor<CUDAContext> aux;\n        math::ReduceMax<float, CUDAContext>(N0, src0, dst, &aux, context);\n      },\n      [](int /*i*/) { return 11.0f; });\n  executeGpuBinaryOpTest(\n      6,\n      1,\n      1,\n      [](int i) { return i == 3 ? 17.0f : 11.0f; },\n      [](int /*i*/) { return 0.0f; },\n      [](int N0,\n         int /*N1*/,\n         const float* src0,\n         const float* /*src1*/,\n         float* dst,\n         CUDAContext* context) {\n        Tensor<CUDAContext> aux;\n        math::ReduceMax<float, CUDAContext>(N0, src0, dst, &aux, context);\n      },\n      [](int /*i*/) { return 17.0f; });\n}\n\nTEST(MathUtilGPUTest, testElemwiseMax) {\n  executeGpuBinaryOpTest(\n      13,\n      13,\n      13,\n      [](int i) { return 2.0f - i; },\n      [](int i) { return i - 6.0f; },\n      [](int N0,\n         int /*N1*/,\n         const float* src0,\n         const float* src1,\n         float* dst,\n         CUDAContext* context) {\n        math::ElemwiseMax<float, CUDAContext>(N0, src0, src1, dst, context);\n      },\n      [](int i) { return std::max(2.0f - i, i - 6.0f); });\n}\n\nTEST(MathUtilGPUTest, testCopyVector) {\n  executeGpuBinaryOpTest(\n      6,\n      1,\n      6,\n      [](int i) { return 5.0f - i; },\n      [](int /*i*/) { return 0.0f; },\n      [](int N0,\n         int /*N1*/,\n         const float* src0,\n         const float* /*src1*/,\n         float* dst,\n         CUDAContext* context) {\n        math::CopyVector<float, CUDAContext>(N0, src0, dst, context);\n      },\n      [](int i) { return 5.0f - i; });\n}\n\nnamespace {\n\nclass GemmBatchedGPUTest\n    : public testing::TestWithParam<testing::tuple<bool, bool>> {\n protected:\n  void SetUp() override {\n    if (!HasCudaGPU()) {\n      return;\n    }\n    option_.set_device_type(CUDA);\n    cuda_context_ = make_unique<CUDAContext>(option_);\n    Blob* X_blob = ws_.CreateBlob(\"X\");\n    Blob* W_blob = ws_.CreateBlob(\"W\");\n    Blob* Y_blob = ws_.CreateBlob(\"Y\");\n    X_ = X_blob->GetMutable<Tensor<CUDAContext>>();\n    W_ = W_blob->GetMutable<Tensor<CUDAContext>>();\n    Y_ = Y_blob->GetMutable<Tensor<CUDAContext>>();\n    X_->Resize(std::vector<TIndex>{3, 5, 10});\n    W_->Resize(std::vector<TIndex>{3, 6, 10});\n    Y_->Resize(std::vector<TIndex>{3, 5, 6});\n    math::Set<float, CUDAContext>(\n        X_->size(), 1.0f, X_->mutable_data<float>(), cuda_context_.get());\n    math::Set<float, CUDAContext>(\n        W_->size(), 1.0f, W_->mutable_data<float>(), cuda_context_.get());\n    trans_X_ = std::get<0>(GetParam());\n    trans_W_ = std::get<1>(GetParam());\n  }\n\n  void RunGemmBatched(const float alpha, const float beta) {\n    math::GemmBatched(\n        trans_X_ ? CblasTrans : CblasNoTrans,\n        trans_W_ ? CblasTrans : CblasNoTrans,\n        3,\n        5,\n        6,\n        10,\n        alpha,\n        X_->template data<float>(),\n        W_->template data<float>(),\n        beta,\n        Y_->template mutable_data<float>(),\n        cuda_context_.get());\n  }\n\n  void VerifyOutput(const float value) const {\n    TensorCPU Y_cpu(*Y_);\n    for (int i = 0; i < Y_cpu.size(); ++i) {\n      EXPECT_FLOAT_EQ(value, Y_cpu.template data<float>()[i]);\n    }\n  }\n\n  Workspace ws_;\n  DeviceOption option_;\n  std::unique_ptr<CUDAContext> cuda_context_;\n  Tensor<CUDAContext>* X_ = nullptr;\n  Tensor<CUDAContext>* W_ = nullptr;\n  Tensor<CUDAContext>* Y_ = nullptr;\n  bool trans_X_;\n  bool trans_W_;\n};\n\nTEST_P(GemmBatchedGPUTest, GemmBatchedGPUFloatTest) {\n  if (!HasCudaGPU()) {\n    return;\n  }\n  RunGemmBatched(1.0f, 0.0f);\n  VerifyOutput(10.0f);\n  RunGemmBatched(1.0f, 0.5f);\n  VerifyOutput(15.0f);\n  RunGemmBatched(0.5f, 1.0f);\n  VerifyOutput(20.0f);\n}\n\nINSTANTIATE_TEST_CASE_P(\n    GemmBatchedGPUTrans,\n    GemmBatchedGPUTest,\n    testing::Combine(testing::Bool(), testing::Bool()));\n\nclass TransposeGPUTest : public testing::Test {\n protected:\n  void SetUp() override {\n    if (!HasCudaGPU()) {\n      return;\n    }\n    option_.set_device_type(CUDA);\n    cuda_context_ = make_unique<CUDAContext>(option_);\n    Blob* blob_x = ws_.CreateBlob(\"X\");\n    Blob* blob_y = ws_.CreateBlob(\"Y\");\n    Blob* blob_x_dims = ws_.CreateBlob(\"x_dims\");\n    Blob* blob_y_dims = ws_.CreateBlob(\"y_dims\");\n    Blob* blob_axes = ws_.CreateBlob(\"axes\");\n    X_ = blob_x->GetMutable<Tensor<CUDAContext>>();\n    Y_ = blob_y->GetMutable<Tensor<CUDAContext>>();\n    x_dims_device_ = blob_x_dims->GetMutable<Tensor<CUDAContext>>();\n    y_dims_device_ = blob_y_dims->GetMutable<Tensor<CUDAContext>>();\n    axes_device_ = blob_axes->GetMutable<Tensor<CUDAContext>>();\n  }\n\n  void SetData(\n      const std::vector<int>& x_dims,\n      const std::vector<int>& y_dims,\n      const std::vector<int>& axes,\n      const std::vector<float>& x_data) {\n    x_dims_device_->Resize(x_dims.size());\n    cuda_context_->Copy<int, CPUContext, CUDAContext>(\n        x_dims.size(), x_dims.data(), x_dims_device_->mutable_data<int>());\n    y_dims_device_->Resize(y_dims.size());\n    cuda_context_->Copy<int, CPUContext, CUDAContext>(\n        y_dims.size(), y_dims.data(), y_dims_device_->mutable_data<int>());\n    axes_device_->Resize(axes.size());\n    cuda_context_->Copy<int, CPUContext, CUDAContext>(\n        axes.size(), axes.data(), axes_device_->mutable_data<int>());\n    X_->Resize(x_dims);\n    Y_->Resize(y_dims);\n    for (std::size_t i = 0; i < x_data.size(); ++i) {\n      math::Set<float, CUDAContext>(\n          1, x_data[i], X_->mutable_data<float>() + i, cuda_context_.get());\n    }\n  }\n\n  void RunTranspose(const int num_axes, const int data_size) {\n    math::Transpose<float, CUDAContext>(\n        num_axes,\n        x_dims_device_->data<int>(),\n        y_dims_device_->data<int>(),\n        axes_device_->data<int>(),\n        data_size,\n        X_->data<float>(),\n        Y_->mutable_data<float>(),\n        cuda_context_.get());\n    cuda_context_->FinishDeviceComputation();\n  }\n\n  void VerifyResult(const std::vector<float>& expected_output) {\n    Blob* blob_y_host = ws_.CreateBlob(\"Y_host\");\n    auto* Y_host = blob_y_host->GetMutable<TensorCPU>();\n    Y_host->CopyFrom<CUDAContext, CUDAContext>(*Y_, cuda_context_.get());\n    cuda_context_->FinishDeviceComputation();\n    ASSERT_EQ(expected_output.size(), Y_host->size());\n    for (std::size_t i = 0; i < expected_output.size(); ++i) {\n      EXPECT_FLOAT_EQ(expected_output[i], Y_host->data<float>()[i]);\n    }\n  }\n\n  Workspace ws_;\n  DeviceOption option_;\n  std::unique_ptr<CUDAContext> cuda_context_;\n  Tensor<CUDAContext>* X_ = nullptr;\n  Tensor<CUDAContext>* Y_ = nullptr;\n  Tensor<CUDAContext>* x_dims_device_ = nullptr;\n  Tensor<CUDAContext>* y_dims_device_ = nullptr;\n  Tensor<CUDAContext>* axes_device_ = nullptr;\n};\n\nTEST_F(TransposeGPUTest, TransposeGPUFloatTest) {\n  if (!HasCudaGPU()) {\n    return;\n  }\n  {\n    // Test for 1D transpose.\n    const std::vector<int> x_dims = {3};\n    const std::vector<int> y_dims = {3};\n    const std::vector<int> axes = {0};\n    SetData(x_dims, y_dims, axes, {1.0f, 2.0f, 3.0f});\n    RunTranspose(1, 3);\n    VerifyResult({1.0f, 2.0f, 3.0f});\n  }\n  {\n    // Test for 2D transpose.\n    const std::vector<int> x_dims = {2, 3};\n    const std::vector<int> y_dims = {3, 2};\n    const std::vector<int> axes = {1, 0};\n    SetData(x_dims, y_dims, axes, {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f});\n    RunTranspose(2, 6);\n    VerifyResult({1.0f, 4.0f, 2.0f, 5.0f, 3.0f, 6.0f});\n  }\n  {\n    // Test for 3D transpose.\n    const std::vector<int> x_dims = {2, 2, 2};\n    const std::vector<int> y_dims = {2, 2, 2};\n    const std::vector<int> axes1 = {1, 2, 0};\n    SetData(\n        x_dims,\n        y_dims,\n        axes1,\n        {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f});\n    RunTranspose(3, 8);\n    VerifyResult({1.0f, 5.0f, 2.0f, 6.0f, 3.0f, 7.0f, 4.0f, 8.0f});\n\n    const std::vector<int> axes2 = {1, 0, 2};\n    SetData(\n        x_dims,\n        y_dims,\n        axes2,\n        {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f});\n    RunTranspose(3, 8);\n    VerifyResult({1.0f, 2.0f, 5.0f, 6.0f, 3.0f, 4.0f, 7.0f, 8.0f});\n  }\n}\n\n} // namespace\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/math_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <memory>\n#include <vector>\n\n#include <gtest/gtest.h>\n\n#include \"caffe2/core/blob.h\"\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/tensor.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n#include \"caffe2/utils/conversions.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\nTEST(MathTest, GemmNoTransNoTrans) {\n  DeviceOption option;\n  CPUContext cpu_context(option);\n  TensorCPU X(std::vector<int>{5, 10});\n  TensorCPU W(std::vector<int>{10, 6});\n  TensorCPU Y(std::vector<int>{5, 6});\n  EXPECT_EQ(X.size(), 50);\n  EXPECT_EQ(W.size(), 60);\n  math::Set<float, CPUContext>(X.size(), 1, X.mutable_data<float>(), &cpu_context);\n  math::Set<float, CPUContext>(W.size(), 1, W.mutable_data<float>(), &cpu_context);\n  EXPECT_EQ(Y.size(), 30);\n  for (int i = 0; i < X.size(); ++i) {\n    CHECK_EQ(X.data<float>()[i], 1);\n  }\n  for (int i = 0; i < W.size(); ++i) {\n    CHECK_EQ(W.data<float>()[i], 1);\n  }\n\n  const float kOne = 1.0;\n  const float kPointFive = 0.5;\n  const float kZero = 0.0;\n  math::Gemm<float, CPUContext>(CblasNoTrans, CblasNoTrans, 5, 6, 10, kOne,\n                                X.data<float>(), W.data<float>(), kZero, Y.mutable_data<float>(),\n                                &cpu_context);\n  EXPECT_EQ(Y.size(), 30);\n  for (int i = 0; i < Y.size(); ++i) {\n    CHECK_EQ(Y.data<float>()[i], 10) << i;\n  }\n  // Test Accumulate\n  math::Gemm<float, CPUContext>(CblasNoTrans, CblasNoTrans, 5, 6, 10, kOne,\n                                X.data<float>(), W.data<float>(), kPointFive,\n                                Y.mutable_data<float>(), &cpu_context);\n  EXPECT_EQ(Y.size(), 30);\n  for (int i = 0; i < Y.size(); ++i) {\n    CHECK_EQ(Y.data<float>()[i], 15) << i;\n  }\n  // Test Accumulate\n  math::Gemm<float, CPUContext>(CblasNoTrans, CblasNoTrans, 5, 6, 10,\n                                kPointFive,\n                                X.data<float>(), W.data<float>(), kOne, Y.mutable_data<float>(),\n                                &cpu_context);\n  EXPECT_EQ(Y.size(), 30);\n  for (int i = 0; i < Y.size(); ++i) {\n    CHECK_EQ(Y.data<float>()[i], 20) << i;\n  }\n}\n\nTEST(MathTest, GemmNoTransTrans) {\n  DeviceOption option;\n  CPUContext cpu_context(option);\n  TensorCPU X(std::vector<int>{5, 10});\n  TensorCPU W(std::vector<int>{6, 10});\n  TensorCPU Y(std::vector<int>{5, 6});\n  EXPECT_EQ(X.size(), 50);\n  EXPECT_EQ(W.size(), 60);\n  math::Set<float, CPUContext>(X.size(), 1, X.mutable_data<float>(), &cpu_context);\n  math::Set<float, CPUContext>(W.size(), 1, W.mutable_data<float>(), &cpu_context);\n  EXPECT_EQ(Y.size(), 30);\n  for (int i = 0; i < X.size(); ++i) {\n    CHECK_EQ(X.data<float>()[i], 1);\n  }\n  for (int i = 0; i < W.size(); ++i) {\n    CHECK_EQ(W.data<float>()[i], 1);\n  }\n\n  const float kOne = 1.0;\n  const float kPointFive = 0.5;\n  const float kZero = 0.0;\n  math::Gemm<float, CPUContext>(CblasNoTrans, CblasTrans, 5, 6, 10, kOne,\n                                X.data<float>(), W.data<float>(), kZero, Y.mutable_data<float>(),\n                                &cpu_context);\n  EXPECT_EQ(Y.size(), 30);\n  for (int i = 0; i < Y.size(); ++i) {\n    CHECK_EQ(Y.data<float>()[i], 10) << i;\n  }\n  // Test Accumulate\n  math::Gemm<float, CPUContext>(CblasNoTrans, CblasTrans, 5, 6, 10, kOne,\n                                X.data<float>(), W.data<float>(), kPointFive,\n                                Y.mutable_data<float>(), &cpu_context);\n  EXPECT_EQ(Y.size(), 30);\n  for (int i = 0; i < Y.size(); ++i) {\n    CHECK_EQ(Y.data<float>()[i], 15) << i;\n  }\n  math::Gemm<float, CPUContext>(CblasNoTrans, CblasTrans, 5, 6, 10, kPointFive,\n                                X.data<float>(), W.data<float>(), kOne, Y.mutable_data<float>(),\n                                &cpu_context);\n  EXPECT_EQ(Y.size(), 30);\n  for (int i = 0; i < Y.size(); ++i) {\n    CHECK_EQ(Y.data<float>()[i], 20) << i;\n  }\n}\n\nnamespace {\n\nclass GemmBatchedTest\n    : public testing::TestWithParam<testing::tuple<bool, bool>> {\n protected:\n  void SetUp() override {\n    cpu_context_ = make_unique<CPUContext>(option_);\n    X_.Resize(std::vector<TIndex>{3, 5, 10});\n    W_.Resize(std::vector<TIndex>{3, 6, 10});\n    Y_.Resize(std::vector<TIndex>{3, 5, 6});\n    math::Set<float, CPUContext>(\n        X_.size(), 1, X_.mutable_data<float>(), cpu_context_.get());\n    math::Set<float, CPUContext>(\n        W_.size(), 1, W_.mutable_data<float>(), cpu_context_.get());\n    trans_X_ = std::get<0>(GetParam());\n    trans_W_ = std::get<1>(GetParam());\n  }\n\n  void RunGemmBatched(const float alpha, const float beta) {\n    math::GemmBatched(\n        trans_X_ ? CblasTrans : CblasNoTrans,\n        trans_W_ ? CblasTrans : CblasNoTrans,\n        3,\n        5,\n        6,\n        10,\n        alpha,\n        X_.template data<float>(),\n        W_.template data<float>(),\n        beta,\n        Y_.template mutable_data<float>(),\n        cpu_context_.get());\n  }\n\n  void VerifyOutput(const float value) const {\n    for (int i = 0; i < Y_.size(); ++i) {\n      EXPECT_FLOAT_EQ(value, Y_.template data<float>()[i]);\n    }\n  }\n\n  DeviceOption option_;\n  std::unique_ptr<CPUContext> cpu_context_;\n  TensorCPU X_;\n  TensorCPU W_;\n  TensorCPU Y_;\n  bool trans_X_;\n  bool trans_W_;\n};\n\nTEST_P(GemmBatchedTest, GemmBatchedFloatTest) {\n  RunGemmBatched(1.0f, 0.0f);\n  VerifyOutput(10.0f);\n  RunGemmBatched(1.0f, 0.5f);\n  VerifyOutput(15.0f);\n  RunGemmBatched(0.5f, 1.0f);\n  VerifyOutput(20.0f);\n}\n\nINSTANTIATE_TEST_CASE_P(\n    GemmBatchedTrans,\n    GemmBatchedTest,\n    testing::Combine(testing::Bool(), testing::Bool()));\n\n} // namespace\n\nTEST(MathTest, GemvNoTrans) {\n  DeviceOption option;\n  CPUContext cpu_context(option);\n  TensorCPU A(std::vector<int>{5, 10});\n  TensorCPU X(std::vector<int>{10});\n  TensorCPU Y(std::vector<int>{5});\n  EXPECT_EQ(A.size(), 50);\n  EXPECT_EQ(X.size(), 10);\n  math::Set<float, CPUContext>(A.size(), 1, A.mutable_data<float>(), &cpu_context);\n  math::Set<float, CPUContext>(X.size(), 1, X.mutable_data<float>(), &cpu_context);\n  EXPECT_EQ(Y.size(), 5);\n  for (int i = 0; i < A.size(); ++i) {\n    CHECK_EQ(A.data<float>()[i], 1);\n  }\n  for (int i = 0; i < X.size(); ++i) {\n    CHECK_EQ(X.data<float>()[i], 1);\n  }\n\n  const float kOne = 1.0;\n  const float kPointFive = 0.5;\n  const float kZero = 0.0;\n  math::Gemv<float, CPUContext>(CblasNoTrans, 5, 10, kOne, A.data<float>(), X.data<float>(),\n                                kZero, Y.mutable_data<float>(), &cpu_context);\n  for (int i = 0; i < Y.size(); ++i) {\n    CHECK_EQ(Y.data<float>()[i], 10) << i;\n  }\n  // Test Accumulate\n  math::Gemv<float, CPUContext>(CblasNoTrans, 5, 10, kOne, A.data<float>(), X.data<float>(),\n                                kPointFive, Y.mutable_data<float>(), &cpu_context);\n  for (int i = 0; i < Y.size(); ++i) {\n    CHECK_EQ(Y.data<float>()[i], 15) << i;\n  }\n  // Test Accumulate\n  math::Gemv<float, CPUContext>(CblasNoTrans, 5, 10, kPointFive, A.data<float>(),\n                                X.data<float>(), kOne, Y.mutable_data<float>(),\n                                &cpu_context);\n  for (int i = 0; i < Y.size(); ++i) {\n    CHECK_EQ(Y.data<float>()[i], 20) << i;\n  }\n}\n\nTEST(MathTest, GemvTrans) {\n  DeviceOption option;\n  CPUContext cpu_context(option);\n  TensorCPU A(std::vector<int>{6, 10});\n  TensorCPU X(std::vector<int>{6});\n  TensorCPU Y(std::vector<int>{10});\n  EXPECT_EQ(A.size(), 60);\n  EXPECT_EQ(X.size(), 6);\n  math::Set<float, CPUContext>(A.size(), 1, A.mutable_data<float>(), &cpu_context);\n  math::Set<float, CPUContext>(X.size(), 1, X.mutable_data<float>(), &cpu_context);\n  EXPECT_EQ(Y.size(), 10);\n  for (int i = 0; i < A.size(); ++i) {\n    CHECK_EQ(A.data<float>()[i], 1);\n  }\n  for (int i = 0; i < X.size(); ++i) {\n    CHECK_EQ(X.data<float>()[i], 1);\n  }\n\n  const float kOne = 1.0;\n  const float kPointFive = 0.5;\n  const float kZero = 0.0;\n  math::Gemv<float, CPUContext>(CblasTrans, 6, 10, kOne, A.data<float>(), X.data<float>(),\n                                kZero, Y.mutable_data<float>(), &cpu_context);\n  for (int i = 0; i < Y.size(); ++i) {\n    CHECK_EQ(Y.data<float>()[i], 6) << i;\n  }\n  // Test Accumulate\n  math::Gemv<float, CPUContext>(CblasTrans, 6, 10, kOne, A.data<float>(), X.data<float>(),\n                                kPointFive, Y.mutable_data<float>(), &cpu_context);\n  for (int i = 0; i < Y.size(); ++i) {\n    CHECK_EQ(Y.data<float>()[i], 9) << i;\n  }\n  // Test Accumulate\n  math::Gemv<float, CPUContext>(CblasTrans, 6, 10, kPointFive, A.data<float>(),\n                                X.data<float>(), kOne, Y.mutable_data<float>(),\n                                &cpu_context);\n  for (int i = 0; i < Y.size(); ++i) {\n    CHECK_EQ(Y.data<float>()[i], 12) << i;\n  }\n}\n\nusing convert::cpu_half2float;\nusing convert::cpu_float2half_rn;\nTEST(MathTest, FloatToHalfConversion) {\n  float a = 1.0f;\n  float b = 1.75f;\n  float c = 128.125f;\n\n  float converted_a = cpu_half2float(cpu_float2half_rn(a));\n  float converted_b = cpu_half2float(cpu_float2half_rn(b));\n  float converted_c = cpu_half2float(cpu_float2half_rn(c));\n\n  CHECK_EQ(a, converted_a);\n  CHECK_EQ(b, converted_b);\n  CHECK_EQ(c, converted_c);\n}\n\nTEST(MathTest, TranposeTest) {\n  DeviceOption option;\n  CPUContext cpu_context(option);\n\n  {\n    // Test for 1D transpose.\n    const std::vector<int> x_dims = {3};\n    const std::vector<int> y_dims = {3};\n    const std::vector<int> axes = {0};\n    TensorCPU X(x_dims);\n    TensorCPU Y(y_dims);\n    for (int i = 0; i < 3; ++i) {\n      X.mutable_data<float>()[i] = static_cast<float>(i + 1);\n    }\n    math::Transpose<float, CPUContext>(\n        1,\n        x_dims.data(),\n        y_dims.data(),\n        axes.data(),\n        3,\n        X.data<float>(),\n        Y.mutable_data<float>(),\n        &cpu_context);\n    for (int i = 0; i < 3; ++i) {\n      EXPECT_FLOAT_EQ(static_cast<float>(i + 1), Y.data<float>()[i]);\n    }\n  }\n\n  {\n    // Test for 2D transpose.\n    const std::vector<int> x_dims = {2, 3};\n    const std::vector<int> y_dims = {3, 2};\n    const std::vector<int> axes = {1, 0};\n    TensorCPU X(x_dims);\n    TensorCPU Y(y_dims);\n    for (int i = 0; i < 6; ++i) {\n      X.mutable_data<float>()[i] = static_cast<float>(i + 1);\n    }\n    math::Transpose<float, CPUContext>(\n        2,\n        x_dims.data(),\n        y_dims.data(),\n        axes.data(),\n        6,\n        X.data<float>(),\n        Y.mutable_data<float>(),\n        &cpu_context);\n    const std::vector<float> expected_output = {\n        1.0f, 4.0f, 2.0f, 5.0f, 3.0f, 6.0f};\n    for (int i = 0; i < 6; ++i) {\n      EXPECT_FLOAT_EQ(expected_output[i], Y.data<float>()[i]);\n    }\n  }\n\n  {\n    // Test for 3D transpose.\n    const std::vector<int> x_dims = {2, 2, 2};\n    const std::vector<int> y_dims = {2, 2, 2};\n    const std::vector<int> axes1 = {1, 2, 0};\n    TensorCPU X(x_dims);\n    TensorCPU Y(y_dims);\n    for (int i = 0; i < 8; ++i) {\n      X.mutable_data<float>()[i] = static_cast<float>(i + 1);\n    }\n    math::Transpose<float, CPUContext>(\n        3,\n        x_dims.data(),\n        y_dims.data(),\n        axes1.data(),\n        8,\n        X.data<float>(),\n        Y.mutable_data<float>(),\n        &cpu_context);\n    const std::vector<float> expected_output1 = {\n        1.0f, 5.0f, 2.0f, 6.0f, 3.0f, 7.0f, 4.0f, 8.0f};\n    for (int i = 0; i < 8; ++i) {\n      EXPECT_FLOAT_EQ(expected_output1[i], Y.data<float>()[i]);\n    }\n\n    const std::vector<int> axes2 = {1, 0, 2};\n    math::Set<float, CPUContext>(\n        Y.size(), 0.0f, Y.mutable_data<float>(), &cpu_context);\n    math::Transpose<float, CPUContext>(\n        3,\n        x_dims.data(),\n        y_dims.data(),\n        axes2.data(),\n        8,\n        X.data<float>(),\n        Y.mutable_data<float>(),\n        &cpu_context);\n    const std::vector<float> expected_output2 = {\n        1.0f, 2.0f, 5.0f, 6.0f, 3.0f, 4.0f, 7.0f, 8.0f};\n    for (int i = 0; i < 8; ++i) {\n      EXPECT_FLOAT_EQ(expected_output2[i], Y.data<float>()[i]);\n    }\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/mixed_utils.h",
    "content": "// Copyright 2004-present Facebook. All Rights Reserved.\n#ifndef CAFFE2_UTILS_MIXED_UTILS_H\n#define CAFFE2_UTILS_MIXED_UTILS_H\n\n#include \"caffe2/core/common_gpu.h\"\n#include \"caffe2/core/context_gpu.h\"\n\n// define functions to allow add/mult/store operaions for input/output with\n// mixed precisions.\nnamespace caffe2 {\n\n// functions that will only be triggered when there is no spcialized version\n// supported\ntemplate <typename T, typename T2>\ninline __device__ T mixed_mult(T data1, T2 data2) {\n  return data1 * data2;\n};\n\ntemplate <typename T, typename T2>\ninline __device__ T mixed_add(T data1, T2 data2) {\n  return data1 + data2;\n};\n\ntemplate <typename TIN, typename TOUT>\ninline __device__ void mixed_store(TIN* data_in, TOUT* data_out) {\n  *data_out = *data_in;\n  return;\n};\n\ntemplate <typename T>\ninline __device__ void mixed_store(T* data_in, T* data_out) {\n  *data_out = *data_in;\n  return;\n};\n\n#ifdef CAFFE_HAS_CUDA_FP16\n// define templated functions to support mixed precision computation\ntemplate <>\ninline __device__ float mixed_mult(float data1, const float data2) {\n  return data1 * data2;\n}\n\ntemplate <>\ninline __device__ float mixed_mult(float data1, const half data2) {\n  return data1 * __half2float(data2);\n}\n\ntemplate <>\ninline __device__ float mixed_mult(float data1, float16 data2) {\n  half* data2_half = reinterpret_cast<half*>(&data2);\n  return data1 * __half2float(*data2_half);\n}\ntemplate <>\ninline __device__ float mixed_add(float data1, const float data2) {\n  return data1 + data2;\n}\n\ntemplate <>\ninline __device__ float mixed_add(float data1, const half data2) {\n  return data1 + __half2float(data2);\n}\n\ntemplate <>\ninline __device__ float mixed_add(float data1, float16 data2) {\n  half* data2_half = reinterpret_cast<half*>(&data2);\n  return data1 + __half2float(*data2_half);\n}\n\ntemplate <>\ninline __device__ void mixed_store(float* data_in, float* data_out) {\n  *data_out = *data_in;\n  return;\n}\n\ntemplate <>\ninline __device__ void mixed_store(half* data_in, float* data_out) {\n  *data_out = __half2float(*data_in);\n  return;\n}\n\ntemplate <>\ninline __device__ void mixed_store(float16* data_in, float* data_out) {\n  half* data_in_half = reinterpret_cast<half*>(data_in);\n  *data_out = __half2float(*data_in_half);\n  return;\n}\n\ntemplate <>\ninline __device__ void mixed_store(float* data_in, float16* data_out) {\n  half data_in_half = __float2half(*data_in);\n  float16* data_in_float16 = reinterpret_cast<float16*>(&data_in_half);\n  *data_out = *data_in_float16;\n  return;\n}\n\ntemplate <>\ninline __device__ void mixed_store(float* data_in, half* data_out) {\n  half data_in_half = __float2half(*data_in);\n  *data_out = data_in_half;\n  return;\n}\n#endif // for CAFFE_HAS_CUDA_FP16\n} // namespace caffe2\n#endif // for CAFFE2_UTILS_MIXED_UTILS_H\n"
  },
  {
    "path": "caffe2/utils/murmur_hash3.cc",
    "content": "//-----------------------------------------------------------------------------\n// MurmurHash3 was written by Austin Appleby, and is placed in the public\n// domain. The author hereby disclaims copyright to this source code.\n\n// Note - The x86 and x64 versions do _not_ produce the same results, as the\n// algorithms are optimized for their respective platforms. You can still\n// compile and run any of them on any platform, but your performance with the\n// non-native version will be less than optimal.\n\n#include \"caffe2/utils/murmur_hash3.h\"\n\n//-----------------------------------------------------------------------------\n// Platform-specific functions and macros\n\n// Microsoft Visual Studio\n\n#if defined(_MSC_VER)\n\n#define FORCE_INLINE __forceinline\n\n#include <stdlib.h>\n\n#define ROTL32(x, y) _rotl(x, y)\n#define ROTL64(x, y) _rotl64(x, y)\n\n#define BIG_CONSTANT(x) (x)\n\n// Other compilers\n\n#else // defined(_MSC_VER)\n\n#define FORCE_INLINE inline __attribute__((__always_inline__))\n\ninline uint32_t rotl32(uint32_t x, int8_t r) {\n  return (x << r) | (x >> (32 - r));\n}\n\ninline uint64_t rotl64(uint64_t x, int8_t r) {\n  return (x << r) | (x >> (64 - r));\n}\n\n#define ROTL32(x, y) rotl32(x, y)\n#define ROTL64(x, y) rotl64(x, y)\n\n#define BIG_CONSTANT(x) (x##LLU)\n\n#endif // !defined(_MSC_VER)\n\n//-----------------------------------------------------------------------------\n// Block read - if your platform needs to do endian-swapping or can only\n// handle aligned reads, do the conversion here\n\nFORCE_INLINE uint32_t getblock32(const uint32_t* p, int i) {\n  return p[i];\n}\n\nFORCE_INLINE uint64_t getblock64(const uint64_t* p, int i) {\n  return p[i];\n}\n\n//-----------------------------------------------------------------------------\n// Finalization mix - force all bits of a hash block to avalanche\n\nFORCE_INLINE uint32_t fmix32(uint32_t h) {\n  h ^= h >> 16;\n  h *= 0x85ebca6b;\n  h ^= h >> 13;\n  h *= 0xc2b2ae35;\n  h ^= h >> 16;\n\n  return h;\n}\n\n//----------\n\nFORCE_INLINE uint64_t fmix64(uint64_t k) {\n  k ^= k >> 33;\n  k *= BIG_CONSTANT(0xff51afd7ed558ccd);\n  k ^= k >> 33;\n  k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53);\n  k ^= k >> 33;\n\n  return k;\n}\n\nnamespace caffe2 {\n\nvoid MurmurHash3_x86_32(const void* key, int len, uint32_t seed, void* out) {\n  const uint8_t* data = (const uint8_t*)key;\n  const int nblocks = len / 4;\n\n  uint32_t h1 = seed;\n\n  const uint32_t c1 = 0xcc9e2d51;\n  const uint32_t c2 = 0x1b873593;\n\n  //----------\n  // body\n\n  const uint32_t* blocks = (const uint32_t*)(data + nblocks * 4);\n\n  for (int i = -nblocks; i; i++) {\n    uint32_t k1 = getblock32(blocks, i);\n\n    k1 *= c1;\n    k1 = ROTL32(k1, 15);\n    k1 *= c2;\n\n    h1 ^= k1;\n    h1 = ROTL32(h1, 13);\n    h1 = h1 * 5 + 0xe6546b64;\n  }\n\n  //----------\n  // tail\n\n  const uint8_t* tail = (const uint8_t*)(data + nblocks * 4);\n\n  uint32_t k1 = 0;\n\n  switch (len & 3) {\n    case 3:\n      k1 ^= tail[2] << 16;\n    case 2:\n      k1 ^= tail[1] << 8;\n    case 1:\n      k1 ^= tail[0];\n      k1 *= c1;\n      k1 = ROTL32(k1, 15);\n      k1 *= c2;\n      h1 ^= k1;\n  };\n\n  //----------\n  // finalization\n\n  h1 ^= len;\n\n  h1 = fmix32(h1);\n\n  *(uint32_t*)out = h1;\n}\n\n//-----------------------------------------------------------------------------\n\nvoid MurmurHash3_x86_128(\n    const void* key,\n    const int len,\n    uint32_t seed,\n    void* out) {\n  const uint8_t* data = (const uint8_t*)key;\n  const int nblocks = len / 16;\n\n  uint32_t h1 = seed;\n  uint32_t h2 = seed;\n  uint32_t h3 = seed;\n  uint32_t h4 = seed;\n\n  const uint32_t c1 = 0x239b961b;\n  const uint32_t c2 = 0xab0e9789;\n  const uint32_t c3 = 0x38b34ae5;\n  const uint32_t c4 = 0xa1e38b93;\n\n  //----------\n  // body\n\n  const uint32_t* blocks = (const uint32_t*)(data + nblocks * 16);\n\n  for (int i = -nblocks; i; i++) {\n    uint32_t k1 = getblock32(blocks, i * 4 + 0);\n    uint32_t k2 = getblock32(blocks, i * 4 + 1);\n    uint32_t k3 = getblock32(blocks, i * 4 + 2);\n    uint32_t k4 = getblock32(blocks, i * 4 + 3);\n\n    k1 *= c1;\n    k1 = ROTL32(k1, 15);\n    k1 *= c2;\n    h1 ^= k1;\n\n    h1 = ROTL32(h1, 19);\n    h1 += h2;\n    h1 = h1 * 5 + 0x561ccd1b;\n\n    k2 *= c2;\n    k2 = ROTL32(k2, 16);\n    k2 *= c3;\n    h2 ^= k2;\n\n    h2 = ROTL32(h2, 17);\n    h2 += h3;\n    h2 = h2 * 5 + 0x0bcaa747;\n\n    k3 *= c3;\n    k3 = ROTL32(k3, 17);\n    k3 *= c4;\n    h3 ^= k3;\n\n    h3 = ROTL32(h3, 15);\n    h3 += h4;\n    h3 = h3 * 5 + 0x96cd1c35;\n\n    k4 *= c4;\n    k4 = ROTL32(k4, 18);\n    k4 *= c1;\n    h4 ^= k4;\n\n    h4 = ROTL32(h4, 13);\n    h4 += h1;\n    h4 = h4 * 5 + 0x32ac3b17;\n  }\n\n  //----------\n  // tail\n\n  const uint8_t* tail = (const uint8_t*)(data + nblocks * 16);\n\n  uint32_t k1 = 0;\n  uint32_t k2 = 0;\n  uint32_t k3 = 0;\n  uint32_t k4 = 0;\n\n  switch (len & 15) {\n    case 15:\n      k4 ^= tail[14] << 16;\n    case 14:\n      k4 ^= tail[13] << 8;\n    case 13:\n      k4 ^= tail[12] << 0;\n      k4 *= c4;\n      k4 = ROTL32(k4, 18);\n      k4 *= c1;\n      h4 ^= k4;\n\n    case 12:\n      k3 ^= tail[11] << 24;\n    case 11:\n      k3 ^= tail[10] << 16;\n    case 10:\n      k3 ^= tail[9] << 8;\n    case 9:\n      k3 ^= tail[8] << 0;\n      k3 *= c3;\n      k3 = ROTL32(k3, 17);\n      k3 *= c4;\n      h3 ^= k3;\n\n    case 8:\n      k2 ^= tail[7] << 24;\n    case 7:\n      k2 ^= tail[6] << 16;\n    case 6:\n      k2 ^= tail[5] << 8;\n    case 5:\n      k2 ^= tail[4] << 0;\n      k2 *= c2;\n      k2 = ROTL32(k2, 16);\n      k2 *= c3;\n      h2 ^= k2;\n\n    case 4:\n      k1 ^= tail[3] << 24;\n    case 3:\n      k1 ^= tail[2] << 16;\n    case 2:\n      k1 ^= tail[1] << 8;\n    case 1:\n      k1 ^= tail[0] << 0;\n      k1 *= c1;\n      k1 = ROTL32(k1, 15);\n      k1 *= c2;\n      h1 ^= k1;\n  };\n\n  //----------\n  // finalization\n\n  h1 ^= len;\n  h2 ^= len;\n  h3 ^= len;\n  h4 ^= len;\n\n  h1 += h2;\n  h1 += h3;\n  h1 += h4;\n  h2 += h1;\n  h3 += h1;\n  h4 += h1;\n\n  h1 = fmix32(h1);\n  h2 = fmix32(h2);\n  h3 = fmix32(h3);\n  h4 = fmix32(h4);\n\n  h1 += h2;\n  h1 += h3;\n  h1 += h4;\n  h2 += h1;\n  h3 += h1;\n  h4 += h1;\n\n  ((uint32_t*)out)[0] = h1;\n  ((uint32_t*)out)[1] = h2;\n  ((uint32_t*)out)[2] = h3;\n  ((uint32_t*)out)[3] = h4;\n}\n\n//-----------------------------------------------------------------------------\n\nvoid MurmurHash3_x64_128(\n    const void* key,\n    const int len,\n    const uint32_t seed,\n    void* out) {\n  const uint8_t* data = (const uint8_t*)key;\n  const int nblocks = len / 16;\n\n  uint64_t h1 = seed;\n  uint64_t h2 = seed;\n\n  const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5);\n  const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f);\n\n  //----------\n  // body\n\n  const uint64_t* blocks = (const uint64_t*)(data);\n\n  for (int i = 0; i < nblocks; i++) {\n    uint64_t k1 = getblock64(blocks, i * 2 + 0);\n    uint64_t k2 = getblock64(blocks, i * 2 + 1);\n\n    k1 *= c1;\n    k1 = ROTL64(k1, 31);\n    k1 *= c2;\n    h1 ^= k1;\n\n    h1 = ROTL64(h1, 27);\n    h1 += h2;\n    h1 = h1 * 5 + 0x52dce729;\n\n    k2 *= c2;\n    k2 = ROTL64(k2, 33);\n    k2 *= c1;\n    h2 ^= k2;\n\n    h2 = ROTL64(h2, 31);\n    h2 += h1;\n    h2 = h2 * 5 + 0x38495ab5;\n  }\n\n  //----------\n  // tail\n\n  const uint8_t* tail = (const uint8_t*)(data + nblocks * 16);\n\n  uint64_t k1 = 0;\n  uint64_t k2 = 0;\n\n  switch (len & 15) {\n    case 15:\n      k2 ^= ((uint64_t)tail[14]) << 48;\n    case 14:\n      k2 ^= ((uint64_t)tail[13]) << 40;\n    case 13:\n      k2 ^= ((uint64_t)tail[12]) << 32;\n    case 12:\n      k2 ^= ((uint64_t)tail[11]) << 24;\n    case 11:\n      k2 ^= ((uint64_t)tail[10]) << 16;\n    case 10:\n      k2 ^= ((uint64_t)tail[9]) << 8;\n    case 9:\n      k2 ^= ((uint64_t)tail[8]) << 0;\n      k2 *= c2;\n      k2 = ROTL64(k2, 33);\n      k2 *= c1;\n      h2 ^= k2;\n\n    case 8:\n      k1 ^= ((uint64_t)tail[7]) << 56;\n    case 7:\n      k1 ^= ((uint64_t)tail[6]) << 48;\n    case 6:\n      k1 ^= ((uint64_t)tail[5]) << 40;\n    case 5:\n      k1 ^= ((uint64_t)tail[4]) << 32;\n    case 4:\n      k1 ^= ((uint64_t)tail[3]) << 24;\n    case 3:\n      k1 ^= ((uint64_t)tail[2]) << 16;\n    case 2:\n      k1 ^= ((uint64_t)tail[1]) << 8;\n    case 1:\n      k1 ^= ((uint64_t)tail[0]) << 0;\n      k1 *= c1;\n      k1 = ROTL64(k1, 31);\n      k1 *= c2;\n      h1 ^= k1;\n  };\n\n  //----------\n  // finalization\n\n  h1 ^= len;\n  h2 ^= len;\n\n  h1 += h2;\n  h2 += h1;\n\n  h1 = fmix64(h1);\n  h2 = fmix64(h2);\n\n  h1 += h2;\n  h2 += h1;\n\n  ((uint64_t*)out)[0] = h1;\n  ((uint64_t*)out)[1] = h2;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/murmur_hash3.h",
    "content": "//-----------------------------------------------------------------------------\n// MurmurHash3 was written by Austin Appleby, and is placed in the public\n// domain. The author hereby disclaims copyright to this source code.\n\n#pragma once\n\n//-----------------------------------------------------------------------------\n// Platform-specific functions and macros\n\n// Microsoft Visual Studio\n\n#if defined(_MSC_VER) && (_MSC_VER < 1600)\n\ntypedef unsigned char uint8_t;\ntypedef unsigned int uint32_t;\ntypedef unsigned __int64 uint64_t;\n\n// Other compilers\n\n#else // defined(_MSC_VER)\n\n#include <stdint.h>\n\n#endif // !defined(_MSC_VER)\n\nnamespace caffe2 {\n\nvoid MurmurHash3_x86_32(const void* key, int len, uint32_t seed, void* out);\n\nvoid MurmurHash3_x86_128(const void* key, int len, uint32_t seed, void* out);\n\nvoid MurmurHash3_x64_128(const void* key, int len, uint32_t seed, void* out);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/proto_utils.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/utils/proto_utils.h\"\n\n#include <fcntl.h>\n#include <cerrno>\n#include <fstream>\n\n#include <google/protobuf/io/coded_stream.h>\n#include <google/protobuf/io/zero_copy_stream_impl.h>\n\n#ifndef CAFFE2_USE_LITE_PROTO\n#include <google/protobuf/text_format.h>\n#endif  // !CAFFE2_USE_LITE_PROTO\n\n#include \"caffe2/core/logging.h\"\n\nusing ::google::protobuf::Message;\nusing ::google::protobuf::MessageLite;\n\nnamespace caffe2 {\n\nstd::string DeviceTypeName(const int32_t& d) {\n  switch (d) {\n    case CPU:\n      return \"CPU\";\n    case CUDA:\n      return \"CUDA\";\n    case OPENGL:\n      return \"OPENGL\";\n    case MKLDNN:\n      return \"MKLDNN\";\n    default:\n      CAFFE_THROW(\n          \"Unknown device: \",\n          d,\n          \". If you have recently updated the caffe2.proto file to add a new \"\n          \"device type, did you forget to update the TensorDeviceTypeName() \"\n          \"function to reflect such recent changes?\");\n      // The below code won't run but is needed to suppress some compiler\n      // warnings.\n      return \"\";\n  }\n};\n\nbool IsSameDevice(const DeviceOption& lhs, const DeviceOption& rhs) {\n  return (\n      lhs.device_type() == rhs.device_type() &&\n      lhs.cuda_gpu_id() == rhs.cuda_gpu_id() &&\n      lhs.node_name() == rhs.node_name() &&\n      lhs.numa_node_id() == rhs.numa_node_id());\n}\n\nbool ReadStringFromFile(const char* filename, string* str) {\n  std::ifstream ifs(filename, std::ios::in);\n  if (!ifs) {\n    VLOG(1) << \"File cannot be opened: \" << filename\n            << \" error: \" << ifs.rdstate();\n    return false;\n  }\n  ifs.seekg(0, std::ios::end);\n  size_t n = ifs.tellg();\n  str->resize(n);\n  ifs.seekg(0);\n  ifs.read(&(*str)[0], n);\n  return true;\n}\n\nbool WriteStringToFile(const string& str, const char* filename) {\n  std::ofstream ofs(filename, std::ios::out | std::ios::trunc);\n  if (!ofs.is_open()) {\n    VLOG(1) << \"File cannot be created: \" << filename\n            << \" error: \" << ofs.rdstate();\n    return false;\n  }\n  ofs << str;\n  return true;\n}\n\n// IO-specific proto functions: we will deal with the protocol buffer lite and\n// full versions differently.\n\n#ifdef CAFFE2_USE_LITE_PROTO\n\n// Lite runtime.\n\nnamespace {\nclass IfstreamInputStream : public ::google::protobuf::io::CopyingInputStream {\n public:\n  explicit IfstreamInputStream(const string& filename)\n      : ifs_(filename.c_str(), std::ios::in | std::ios::binary) {}\n  ~IfstreamInputStream() { ifs_.close(); }\n\n  int Read(void* buffer, int size) {\n    if (!ifs_) {\n      return -1;\n    }\n    ifs_.read(static_cast<char*>(buffer), size);\n    return ifs_.gcount();\n  }\n\n private:\n  std::ifstream ifs_;\n};\n}  // namespace\n\nbool ReadProtoFromBinaryFile(const char* filename, MessageLite* proto) {\n  ::google::protobuf::io::CopyingInputStreamAdaptor stream(\n      new IfstreamInputStream(filename));\n  stream.SetOwnsCopyingStream(true);\n  // Total bytes hard limit / warning limit are set to 1GB and 512MB\n  // respectively.\n  ::google::protobuf::io::CodedInputStream coded_stream(&stream);\n  coded_stream.SetTotalBytesLimit(1024LL << 20, 512LL << 20);\n  return proto->ParseFromCodedStream(&coded_stream);\n}\n\nvoid WriteProtoToBinaryFile(\n    const MessageLite& /*proto*/,\n    const char* /*filename*/) {\n  LOG(FATAL) << \"Not implemented yet.\";\n}\n\n#else  // CAFFE2_USE_LITE_PROTO\n\n// Full protocol buffer.\n\nusing ::google::protobuf::io::FileInputStream;\nusing ::google::protobuf::io::FileOutputStream;\nusing ::google::protobuf::io::ZeroCopyInputStream;\nusing ::google::protobuf::io::CodedInputStream;\nusing ::google::protobuf::io::ZeroCopyOutputStream;\nusing ::google::protobuf::io::CodedOutputStream;\n\nbool ReadProtoFromTextFile(const char* filename, Message* proto) {\n  int fd = open(filename, O_RDONLY);\n  CAFFE_ENFORCE_NE(fd, -1, \"File not found: \", filename);\n  FileInputStream* input = new FileInputStream(fd);\n  bool success = google::protobuf::TextFormat::Parse(input, proto);\n  delete input;\n  close(fd);\n  return success;\n}\n\nvoid WriteProtoToTextFile(const Message& proto, const char* filename) {\n  int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);\n  FileOutputStream* output = new FileOutputStream(fd);\n  CAFFE_ENFORCE(google::protobuf::TextFormat::Print(proto, output));\n  delete output;\n  close(fd);\n}\n\nbool ReadProtoFromBinaryFile(const char* filename, MessageLite* proto) {\n#if defined (_MSC_VER)  // for MSC compiler binary flag needs to be specified\n  int fd = open(filename, O_RDONLY | O_BINARY);\n#else\n  int fd = open(filename, O_RDONLY);\n#endif\n  CAFFE_ENFORCE_NE(fd, -1, \"File not found: \", filename);\n  std::unique_ptr<ZeroCopyInputStream> raw_input(new FileInputStream(fd));\n  std::unique_ptr<CodedInputStream> coded_input(\n      new CodedInputStream(raw_input.get()));\n  // A hack to manually allow using very large protocol buffers.\n  coded_input->SetTotalBytesLimit(1073741824, 536870912);\n  bool success = proto->ParseFromCodedStream(coded_input.get());\n  coded_input.reset();\n  raw_input.reset();\n  close(fd);\n  return success;\n}\n\nvoid WriteProtoToBinaryFile(const MessageLite& proto, const char* filename) {\n  int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);\n  CAFFE_ENFORCE_NE(\n      fd, -1, \"File cannot be created: \", filename, \" error number: \", errno);\n  std::unique_ptr<ZeroCopyOutputStream> raw_output(new FileOutputStream(fd));\n  std::unique_ptr<CodedOutputStream> coded_output(\n      new CodedOutputStream(raw_output.get()));\n  CAFFE_ENFORCE(proto.SerializeToCodedStream(coded_output.get()));\n  coded_output.reset();\n  raw_output.reset();\n  close(fd);\n}\n\n#endif  // CAFFE2_USE_LITE_PROTO\n\nArgumentHelper::ArgumentHelper(const OperatorDef& def) {\n  for (auto& arg : def.arg()) {\n    if (arg_map_.count(arg.name())) {\n      if (arg.SerializeAsString() != arg_map_[arg.name()].SerializeAsString()) {\n        // If there are two arguments of the same name but different contents,\n        // we will throw an error.\n        CAFFE_THROW(\n            \"Found argument of the same name \",\n            arg.name(),\n            \"but with different contents.\",\n            ProtoDebugString(def));\n      } else {\n        LOG(WARNING) << \"Duplicated argument name [\" << arg.name()\n                     << \"] found in operator def: \"\n                     << ProtoDebugString(def);\n      }\n    }\n    arg_map_[arg.name()] = arg;\n  }\n}\n\nArgumentHelper::ArgumentHelper(const NetDef& netdef) {\n  for (auto& arg : netdef.arg()) {\n    CAFFE_ENFORCE(\n        arg_map_.count(arg.name()) == 0,\n        \"Duplicated argument name [\", arg.name(), \"] found in net def: \",\n        ProtoDebugString(netdef));\n    arg_map_[arg.name()] = arg;\n  }\n}\n\nbool ArgumentHelper::HasArgument(const string& name) const {\n  return arg_map_.count(name);\n}\n\nnamespace {\n// Helper function to verify that conversion between types won't loose any\n// significant bit.\ntemplate <typename InputType, typename TargetType>\nbool SupportsLosslessConversion(const InputType& value) {\n  return static_cast<InputType>(static_cast<TargetType>(value)) == value;\n}\n}\n\nbool operator==(const NetDef& l, const NetDef& r) {\n  return l.SerializeAsString() == r.SerializeAsString();\n}\n\nstd::ostream& operator<<(std::ostream& output, const NetDef& n) {\n  output << n.SerializeAsString();\n  return output;\n}\n\n#define INSTANTIATE_GET_SINGLE_ARGUMENT(                                      \\\n    T, fieldname, enforce_lossless_conversion)                                \\\n  template <>                                                                 \\\n  T ArgumentHelper::GetSingleArgument<T>(                                     \\\n      const string& name, const T& default_value) const {                     \\\n    if (arg_map_.count(name) == 0) {                                          \\\n      VLOG(1) << \"Using default parameter value \" << default_value            \\\n              << \" for parameter \" << name;                                   \\\n      return default_value;                                                   \\\n    }                                                                         \\\n    CAFFE_ENFORCE(                                                            \\\n        arg_map_.at(name).has_##fieldname(),                                  \\\n        \"Argument \",                                                          \\\n        name,                                                                 \\\n        \" does not have the right field: expected field \" #fieldname);        \\\n    auto value = arg_map_.at(name).fieldname();                               \\\n    if (enforce_lossless_conversion) {                                        \\\n      auto supportsConversion =                                               \\\n          SupportsLosslessConversion<decltype(value), T>(value);              \\\n      CAFFE_ENFORCE(                                                          \\\n          supportsConversion,                                                 \\\n          \"Value\",                                                            \\\n          value,                                                              \\\n          \" of argument \",                                                    \\\n          name,                                                               \\\n          \"cannot be represented correctly in a target type\");                \\\n    }                                                                         \\\n    return static_cast<T>(value);                                             \\\n  }                                                                           \\\n  template <>                                                                 \\\n  bool ArgumentHelper::HasSingleArgumentOfType<T>(const string& name) const { \\\n    if (arg_map_.count(name) == 0) {                                          \\\n      return false;                                                           \\\n    }                                                                         \\\n    return arg_map_.at(name).has_##fieldname();                               \\\n  }\n\nINSTANTIATE_GET_SINGLE_ARGUMENT(float, f, false)\nINSTANTIATE_GET_SINGLE_ARGUMENT(double, f, false)\nINSTANTIATE_GET_SINGLE_ARGUMENT(bool, i, false)\nINSTANTIATE_GET_SINGLE_ARGUMENT(int8_t, i, true)\nINSTANTIATE_GET_SINGLE_ARGUMENT(int16_t, i, true)\nINSTANTIATE_GET_SINGLE_ARGUMENT(int, i, true)\nINSTANTIATE_GET_SINGLE_ARGUMENT(int64_t, i, true)\nINSTANTIATE_GET_SINGLE_ARGUMENT(uint8_t, i, true)\nINSTANTIATE_GET_SINGLE_ARGUMENT(uint16_t, i, true)\nINSTANTIATE_GET_SINGLE_ARGUMENT(size_t, i, true)\nINSTANTIATE_GET_SINGLE_ARGUMENT(string, s, false)\nINSTANTIATE_GET_SINGLE_ARGUMENT(NetDef, n, false)\n#undef INSTANTIATE_GET_SINGLE_ARGUMENT\n\n#define INSTANTIATE_GET_REPEATED_ARGUMENT(                             \\\n    T, fieldname, enforce_lossless_conversion)                         \\\n  template <>                                                          \\\n  vector<T> ArgumentHelper::GetRepeatedArgument<T>(                    \\\n      const string& name, const std::vector<T>& default_value) const { \\\n    if (arg_map_.count(name) == 0) {                                   \\\n      return default_value;                                            \\\n    }                                                                  \\\n    vector<T> values;                                                  \\\n    for (const auto& v : arg_map_.at(name).fieldname()) {              \\\n      if (enforce_lossless_conversion) {                               \\\n        auto supportsConversion =                                      \\\n            SupportsLosslessConversion<decltype(v), T>(v);             \\\n        CAFFE_ENFORCE(                                                 \\\n            supportsConversion,                                        \\\n            \"Value\",                                                   \\\n            v,                                                         \\\n            \" of argument \",                                           \\\n            name,                                                      \\\n            \"cannot be represented correctly in a target type\");       \\\n      }                                                                \\\n      values.push_back(static_cast<T>(v));                             \\\n    }                                                                  \\\n    return values;                                                     \\\n  }\n\nINSTANTIATE_GET_REPEATED_ARGUMENT(float, floats, false)\nINSTANTIATE_GET_REPEATED_ARGUMENT(double, floats, false)\nINSTANTIATE_GET_REPEATED_ARGUMENT(bool, ints, false)\nINSTANTIATE_GET_REPEATED_ARGUMENT(int8_t, ints, true)\nINSTANTIATE_GET_REPEATED_ARGUMENT(int16_t, ints, true)\nINSTANTIATE_GET_REPEATED_ARGUMENT(int, ints, true)\nINSTANTIATE_GET_REPEATED_ARGUMENT(int64_t, ints, true)\nINSTANTIATE_GET_REPEATED_ARGUMENT(uint8_t, ints, true)\nINSTANTIATE_GET_REPEATED_ARGUMENT(uint16_t, ints, true)\nINSTANTIATE_GET_REPEATED_ARGUMENT(size_t, ints, true)\nINSTANTIATE_GET_REPEATED_ARGUMENT(string, strings, false)\nINSTANTIATE_GET_REPEATED_ARGUMENT(NetDef, nets, false)\n#undef INSTANTIATE_GET_REPEATED_ARGUMENT\n\n#define CAFFE2_MAKE_SINGULAR_ARGUMENT(T, fieldname)                            \\\ntemplate <>                                                                    \\\nArgument MakeArgument(const string& name, const T& value) {                    \\\n  Argument arg;                                                                \\\n  arg.set_name(name);                                                          \\\n  arg.set_##fieldname(value);                                                  \\\n  return arg;                                                                  \\\n}\n\nCAFFE2_MAKE_SINGULAR_ARGUMENT(bool, i)\nCAFFE2_MAKE_SINGULAR_ARGUMENT(float, f)\nCAFFE2_MAKE_SINGULAR_ARGUMENT(int, i)\nCAFFE2_MAKE_SINGULAR_ARGUMENT(int64_t, i)\nCAFFE2_MAKE_SINGULAR_ARGUMENT(string, s)\n#undef CAFFE2_MAKE_SINGULAR_ARGUMENT\n\ntemplate <>\nArgument MakeArgument(const string& name, const MessageLite& value) {\n  Argument arg;\n  arg.set_name(name);\n  arg.set_s(value.SerializeAsString());\n  return arg;\n}\n\n#define CAFFE2_MAKE_REPEATED_ARGUMENT(T, fieldname)                            \\\ntemplate <>                                                                    \\\nArgument MakeArgument(const string& name, const vector<T>& value) {            \\\n  Argument arg;                                                                \\\n  arg.set_name(name);                                                          \\\n  for (const auto& v : value) {                                                \\\n    arg.add_##fieldname(v);                                                    \\\n  }                                                                            \\\n  return arg;                                                                  \\\n}\n\nCAFFE2_MAKE_REPEATED_ARGUMENT(float, floats)\nCAFFE2_MAKE_REPEATED_ARGUMENT(int, ints)\nCAFFE2_MAKE_REPEATED_ARGUMENT(int64_t, ints)\nCAFFE2_MAKE_REPEATED_ARGUMENT(string, strings)\n#undef CAFFE2_MAKE_REPEATED_ARGUMENT\n\nbool HasOutput(const OperatorDef& op, const std::string& output) {\n  for (const auto& outp : op.output()) {\n    if (outp == output) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool HasInput(const OperatorDef& op, const std::string& input) {\n  for (const auto& inp : op.input()) {\n    if (inp == input) {\n      return true;\n    }\n  }\n  return false;\n}\n\nconst Argument& GetArgument(const OperatorDef& def, const string& name) {\n  for (const Argument& arg : def.arg()) {\n    if (arg.name() == name) {\n      return arg;\n    }\n  }\n  CAFFE_THROW(\n      \"Argument named \",\n      name,\n      \" does not exist in operator \",\n      ProtoDebugString(def));\n}\n\nbool GetFlagArgument(\n    const OperatorDef& def,\n    const string& name,\n    bool def_value) {\n  for (const Argument& arg : def.arg()) {\n    if (arg.name() == name) {\n      CAFFE_ENFORCE(\n          arg.has_i(), \"Can't parse argument as bool: \", ProtoDebugString(arg));\n      return arg.i();\n    }\n  }\n  return def_value;\n}\n\nArgument* GetMutableArgument(\n    const string& name,\n    const bool create_if_missing,\n    OperatorDef* def) {\n  for (int i = 0; i < def->arg_size(); ++i) {\n    if (def->arg(i).name() == name) {\n      return def->mutable_arg(i);\n    }\n  }\n  // If no argument of the right name is found...\n  if (create_if_missing) {\n    Argument* arg = def->add_arg();\n    arg->set_name(name);\n    return arg;\n  } else {\n    return nullptr;\n  }\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/proto_utils.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_PROTO_UTILS_H_\n#define CAFFE2_UTILS_PROTO_UTILS_H_\n\n#ifdef CAFFE2_USE_LITE_PROTO\n#include <google/protobuf/message_lite.h>\n#else // CAFFE2_USE_LITE_PROTO\n#include <google/protobuf/message.h>\n#endif  // !CAFFE2_USE_LITE_PROTO\n\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/proto/caffe2.pb.h\"\n\nnamespace caffe2 {\n\nusing std::string;\nusing ::google::protobuf::MessageLite;\n\n// A wrapper function to return device name string for use in blob serialization\n// / deserialization. This should have one to one correspondence with\n// caffe2/proto/caffe2.proto: enum DeviceType.\n//\n// Note that we can't use DeviceType_Name, because that is only available in\n// protobuf-full, and some platforms (like mobile) may want to use\n// protobuf-lite instead.\nstd::string DeviceTypeName(const int32_t& d);\n\n// Returns if the two DeviceOptions are pointing to the same device.\nbool IsSameDevice(const DeviceOption& lhs, const DeviceOption& rhs);\n\n// Common interfaces that reads file contents into a string.\nbool ReadStringFromFile(const char* filename, string* str);\nbool WriteStringToFile(const string& str, const char* filename);\n\n// Common interfaces that are supported by both lite and full protobuf.\nbool ReadProtoFromBinaryFile(const char* filename, MessageLite* proto);\ninline bool ReadProtoFromBinaryFile(const string filename, MessageLite* proto) {\n  return ReadProtoFromBinaryFile(filename.c_str(), proto);\n}\n\nvoid WriteProtoToBinaryFile(const MessageLite& proto, const char* filename);\ninline void WriteProtoToBinaryFile(const MessageLite& proto,\n                                   const string& filename) {\n  return WriteProtoToBinaryFile(proto, filename.c_str());\n}\n\n#ifdef CAFFE2_USE_LITE_PROTO\n\ninline string ProtoDebugString(const MessageLite& proto) {\n  return proto.SerializeAsString();\n}\n\n// Text format MessageLite wrappers: these functions do nothing but just\n// allowing things to compile. It will produce a runtime error if you are using\n// MessageLite but still want text support.\ninline bool ReadProtoFromTextFile(\n    const char* /*filename*/,\n    MessageLite* /*proto*/) {\n  LOG(FATAL) << \"If you are running lite version, you should not be \"\n                  << \"calling any text-format protobuffers.\";\n  return false;  // Just to suppress compiler warning.\n}\ninline bool ReadProtoFromTextFile(const string filename, MessageLite* proto) {\n  return ReadProtoFromTextFile(filename.c_str(), proto);\n}\n\ninline void WriteProtoToTextFile(\n    const MessageLite& /*proto*/,\n    const char* /*filename*/) {\n  LOG(FATAL) << \"If you are running lite version, you should not be \"\n                  << \"calling any text-format protobuffers.\";\n}\ninline void WriteProtoToTextFile(const MessageLite& proto,\n                                 const string& filename) {\n  return WriteProtoToTextFile(proto, filename.c_str());\n}\n\ninline bool ReadProtoFromFile(const char* filename, MessageLite* proto) {\n  return (ReadProtoFromBinaryFile(filename, proto) ||\n          ReadProtoFromTextFile(filename, proto));\n}\n\ninline bool ReadProtoFromFile(const string& filename, MessageLite* proto) {\n  return ReadProtoFromFile(filename.c_str(), proto);\n}\n\n#else  // CAFFE2_USE_LITE_PROTO\n\nusing ::google::protobuf::Message;\n\ninline string ProtoDebugString(const Message& proto) {\n  return proto.ShortDebugString();\n}\n\nbool ReadProtoFromTextFile(const char* filename, Message* proto);\ninline bool ReadProtoFromTextFile(const string filename, Message* proto) {\n  return ReadProtoFromTextFile(filename.c_str(), proto);\n}\n\nvoid WriteProtoToTextFile(const Message& proto, const char* filename);\ninline void WriteProtoToTextFile(const Message& proto, const string& filename) {\n  return WriteProtoToTextFile(proto, filename.c_str());\n}\n\n// Read Proto from a file, letting the code figure out if it is text or binary.\ninline bool ReadProtoFromFile(const char* filename, Message* proto) {\n  return (ReadProtoFromBinaryFile(filename, proto) ||\n          ReadProtoFromTextFile(filename, proto));\n}\n\ninline bool ReadProtoFromFile(const string& filename, Message* proto) {\n  return ReadProtoFromFile(filename.c_str(), proto);\n}\n\n#endif  // CAFFE2_USE_LITE_PROTO\n\ntemplate <\n    class IterableInputs = std::initializer_list<string>,\n    class IterableOutputs = std::initializer_list<string>,\n    class IterableArgs = std::initializer_list<Argument>>\nOperatorDef CreateOperatorDef(\n    const string& type,\n    const string& name,\n    const IterableInputs& inputs,\n    const IterableOutputs& outputs,\n    const IterableArgs& args,\n    const DeviceOption& device_option = DeviceOption(),\n    const string& engine = \"\") {\n  OperatorDef def;\n  def.set_type(type);\n  def.set_name(name);\n  for (const string& in : inputs) {\n    def.add_input(in);\n  }\n  for (const string& out : outputs) {\n    def.add_output(out);\n  }\n  for (const Argument& arg : args) {\n    def.add_arg()->CopyFrom(arg);\n  }\n  if (device_option.has_device_type()) {\n    def.mutable_device_option()->CopyFrom(device_option);\n  }\n  if (engine.size()) {\n    def.set_engine(engine);\n  }\n  return def;\n}\n\n// A simplified version compared to the full CreateOperator, if you do not need\n// to specify args.\ntemplate <\n    class IterableInputs = std::initializer_list<string>,\n    class IterableOutputs = std::initializer_list<string>>\ninline OperatorDef CreateOperatorDef(\n    const string& type,\n    const string& name,\n    const IterableInputs& inputs,\n    const IterableOutputs& outputs,\n    const DeviceOption& device_option = DeviceOption(),\n    const string& engine = \"\") {\n  return CreateOperatorDef(\n      type,\n      name,\n      inputs,\n      outputs,\n      std::vector<Argument>(),\n      device_option,\n      engine);\n}\n\nbool HasOutput(const OperatorDef& op, const std::string& output);\nbool HasInput(const OperatorDef& op, const std::string& input);\n\n/**\n * @brief A helper class to index into arguments.\n *\n * This helper helps us to more easily index into a set of arguments\n * that are present in the operator. To save memory, the argument helper\n * does not copy the operator def, so one would need to make sure that the\n * lifetime of the OperatorDef object outlives that of the ArgumentHelper.\n */\nclass ArgumentHelper {\n public:\n  template <typename Def>\n  static bool HasArgument(const Def& def, const string& name) {\n    return ArgumentHelper(def).HasArgument(name);\n  }\n\n  template <typename Def, typename T>\n  static T GetSingleArgument(\n      const Def& def,\n      const string& name,\n      const T& default_value) {\n    return ArgumentHelper(def).GetSingleArgument<T>(name, default_value);\n  }\n\n  template <typename Def, typename T>\n  static bool HasSingleArgumentOfType(const Def& def, const string& name) {\n    return ArgumentHelper(def).HasSingleArgumentOfType<T>(name);\n  }\n\n  template <typename Def, typename T>\n  static vector<T> GetRepeatedArgument(\n      const Def& def,\n      const string& name,\n      const std::vector<T>& default_value = std::vector<T>()) {\n    return ArgumentHelper(def).GetRepeatedArgument<T>(name, default_value);\n  }\n\n  template <typename Def, typename MessageType>\n  static MessageType GetMessageArgument(const Def& def, const string& name) {\n    return ArgumentHelper(def).GetMessageArgument<MessageType>(name);\n  }\n\n  template <typename Def, typename MessageType>\n  static vector<MessageType> GetRepeatedMessageArgument(\n      const Def& def,\n      const string& name) {\n    return ArgumentHelper(def).GetRepeatedMessageArgument<MessageType>(name);\n  }\n\n  explicit ArgumentHelper(const OperatorDef& def);\n  explicit ArgumentHelper(const NetDef& netdef);\n  bool HasArgument(const string& name) const;\n\n  template <typename T>\n  T GetSingleArgument(const string& name, const T& default_value) const;\n  template <typename T>\n  bool HasSingleArgumentOfType(const string& name) const;\n  template <typename T>\n  vector<T> GetRepeatedArgument(\n      const string& name,\n      const std::vector<T>& default_value = std::vector<T>()) const;\n\n  template <typename MessageType>\n  MessageType GetMessageArgument(const string& name) const {\n    CAFFE_ENFORCE(arg_map_.count(name), \"Cannot find parameter named \", name);\n    MessageType message;\n    if (arg_map_.at(name).has_s()) {\n      CAFFE_ENFORCE(\n          message.ParseFromString(arg_map_.at(name).s()),\n          \"Faild to parse content from the string\");\n    } else {\n      VLOG(1) << \"Return empty message for parameter \" << name;\n    }\n    return message;\n  }\n\n  template <typename MessageType>\n  vector<MessageType> GetRepeatedMessageArgument(const string& name) const {\n    CAFFE_ENFORCE(arg_map_.count(name), \"Cannot find parameter named \", name);\n    vector<MessageType> messages(arg_map_.at(name).strings_size());\n    for (int i = 0; i < messages.size(); ++i) {\n      CAFFE_ENFORCE(\n          messages[i].ParseFromString(arg_map_.at(name).strings(i)),\n          \"Faild to parse content from the string\");\n    }\n    return messages;\n  }\n\n private:\n  CaffeMap<string, Argument> arg_map_;\n};\n\nconst Argument& GetArgument(const OperatorDef& def, const string& name);\nbool GetFlagArgument(\n    const OperatorDef& def,\n    const string& name,\n    bool def_value = false);\n\nArgument* GetMutableArgument(\n    const string& name,\n    const bool create_if_missing,\n    OperatorDef* def);\n\ntemplate <typename T>\nArgument MakeArgument(const string& name, const T& value);\n\ntemplate <typename T>\ninline void AddArgument(const string& name, const T& value, OperatorDef* def) {\n  GetMutableArgument(name, true, def)->CopyFrom(MakeArgument(name, value));\n}\n\nbool inline operator==(const DeviceOption& dl, const DeviceOption& dr) {\n  return IsSameDevice(dl, dr);\n}\n\n} // namespace caffe2\n\nnamespace std {\ntemplate <>\nstruct hash<caffe2::DeviceOption> {\n  typedef caffe2::DeviceOption argument_type;\n  typedef std::size_t result_type;\n  result_type operator()(argument_type const& device_option) const {\n    std::string serialized;\n    CAFFE_ENFORCE(device_option.SerializeToString(&serialized));\n    return std::hash<std::string>{}(serialized);\n  }\n};\n} // namespace std\n\n#endif // CAFFE2_UTILS_PROTO_UTILS_H_\n"
  },
  {
    "path": "caffe2/utils/proto_utils_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/utils/proto_utils.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\n\nTEST(ProtoUtilsTest, IsSameDevice) {\n  DeviceOption a;\n  DeviceOption b;\n  EXPECT_TRUE(IsSameDevice(a, b));\n  a.set_node_name(\"my_node\");\n  EXPECT_FALSE(IsSameDevice(a, b));\n  b.set_node_name(\"my_node\");\n  EXPECT_TRUE(IsSameDevice(a, b));\n  b.set_cuda_gpu_id(2);\n  EXPECT_FALSE(IsSameDevice(a, b));\n  a.set_cuda_gpu_id(2);\n  EXPECT_TRUE(IsSameDevice(a, b));\n  a.set_device_type(DeviceType::CUDA);\n  b.set_device_type(DeviceType::CPU);\n  EXPECT_FALSE(IsSameDevice(a, b));\n}\n\nTEST(ProtoUtilsTest, SimpleReadWrite) {\n  string content(\"The quick brown fox jumps over the lazy dog.\");\n  string name = std::tmpnam(nullptr);\n  EXPECT_TRUE(WriteStringToFile(content, name.c_str()));\n  string read_back;\n  EXPECT_TRUE(ReadStringFromFile(name.c_str(), &read_back));\n  EXPECT_EQ(content, read_back);\n}\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/signal_handler.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/utils/signal_handler.h\"\n#include \"caffe2/core/logging.h\"\n\n#if defined(CAFFE2_SUPPORTS_SIGNAL_HANDLER)\n\n// Normal signal handler implementation.\n#include <cxxabi.h>\n#include <dirent.h>\n#include <dlfcn.h>\n#include <pthread.h>\n#include <sys/syscall.h>\n#include <sys/types.h>\n#include <unistd.h>\n#include <unwind.h>\n\n#include <atomic>\n#include <csignal>\n#include <cstdio>\n#include <cstdlib>\n#include <mutex>\n#include <unordered_set>\n\n#include \"caffe2/core/init.h\"\n\n#if CAFFE2_ANDROID\n#ifndef SYS_gettid\n#define SYS_gettid __NR_gettid\n#endif\n#ifndef SYS_tgkill\n#define SYS_tgkill __NR_tgkill\n#endif\n#endif\n\nnamespace {\n\nstruct sigaction previousSighup;\nstruct sigaction previousSigint;\nstd::atomic<int> sigintCount(0);\nstd::atomic<int> sighupCount(0);\nstd::atomic<int> hookedUpCount(0);\n\nvoid handleSignal(int signal) {\n  switch (signal) {\n    // TODO: what if the previous handler uses sa_sigaction?\n    case SIGHUP:\n      sighupCount += 1;\n      if (previousSighup.sa_handler) {\n        previousSighup.sa_handler(signal);\n      }\n      break;\n    case SIGINT:\n      sigintCount += 1;\n      if (previousSigint.sa_handler) {\n        previousSigint.sa_handler(signal);\n      }\n      break;\n  }\n}\n\nvoid hookupHandler() {\n  if (hookedUpCount++) {\n    return;\n  }\n  struct sigaction sa;\n  // Setup the handler\n  sa.sa_handler = &handleSignal;\n  // Restart the system call, if at all possible\n  sa.sa_flags = SA_RESTART;\n  // Block every signal during the handler\n  sigfillset(&sa.sa_mask);\n  // Intercept SIGHUP and SIGINT\n  if (sigaction(SIGHUP, &sa, &previousSighup) == -1) {\n    LOG(FATAL) << \"Cannot install SIGHUP handler.\";\n  }\n  if (sigaction(SIGINT, &sa, &previousSigint) == -1) {\n    LOG(FATAL) << \"Cannot install SIGINT handler.\";\n  }\n}\n\n// Set the signal handlers to the default.\nvoid unhookHandler() {\n  if (--hookedUpCount > 0) {\n    return;\n  }\n  struct sigaction sa;\n  // Setup the sighub handler\n  sa.sa_handler = SIG_DFL;\n  // Restart the system call, if at all possible\n  sa.sa_flags = SA_RESTART;\n  // Block every signal during the handler\n  sigfillset(&sa.sa_mask);\n  // Intercept SIGHUP and SIGINT\n  if (sigaction(SIGHUP, &previousSighup, nullptr) == -1) {\n    LOG(FATAL) << \"Cannot uninstall SIGHUP handler.\";\n  }\n  if (sigaction(SIGINT, &previousSigint, nullptr) == -1) {\n    LOG(FATAL) << \"Cannot uninstall SIGINT handler.\";\n  }\n}\n\n#if defined(CAFFE2_SUPPORTS_FATAL_SIGNAL_HANDLERS)\n// The mutex protects the bool.\nstd::mutex fatalSignalHandlersInstallationMutex;\nbool fatalSignalHandlersInstalled;\n// We need to hold a reference to call the previous SIGUSR2 handler in case\n// we didn't signal it\nstruct sigaction previousSigusr2;\n// Flag dictating whether the SIGUSR2 handler falls back to previous handlers\n// or is intercepted in order to print a stack trace.\nstd::atomic<bool> fatalSignalReceived(false);\n// Global state set when a fatal signal is received so that backtracing threads\n// know why they're printing a stacktrace.\nconst char* fatalSignalName(\"<UNKNOWN>\");\nint fatalSignum(-1);\n// This wait condition is used to wait for other threads to finish writing\n// their stack trace when in fatal sig handler (we can't use pthread_join\n// because there's no way to convert from a tid to a pthread_t).\npthread_cond_t writingCond = PTHREAD_COND_INITIALIZER;\npthread_mutex_t writingMutex = PTHREAD_MUTEX_INITIALIZER;\n\nstruct {\n  const char* name;\n  int signum;\n  struct sigaction previous;\n} kSignalHandlers[] = {\n  { \"SIGABRT\",  SIGABRT,  {} },\n  { \"SIGINT\",   SIGINT,   {} },\n  { \"SIGILL\",   SIGILL,   {} },\n  { \"SIGFPE\",   SIGFPE,   {} },\n  { \"SIGBUS\",   SIGBUS,   {} },\n  { \"SIGSEGV\",  SIGSEGV,  {} },\n  { nullptr,    0,        {} }\n};\n\nstruct sigaction* getPreviousSigaction(int signum) {\n  for (auto handler = kSignalHandlers; handler->name != nullptr; handler++) {\n    if (handler->signum == signum) {\n      return &handler->previous;\n    }\n  }\n  return nullptr;\n}\n\nconst char* getSignalName(int signum) {\n  for (auto handler = kSignalHandlers; handler->name != nullptr; handler++) {\n    if (handler->signum == signum) {\n      return handler->name;\n    }\n  }\n  return nullptr;\n}\n\n_Unwind_Reason_Code unwinder(struct _Unwind_Context* context, void* userInfo) {\n  auto& pcs = *reinterpret_cast<std::vector<uintptr_t>*>(userInfo);\n  pcs.push_back(_Unwind_GetIP(context));\n  return _URC_NO_REASON;\n}\n\nstd::vector<uintptr_t> getBacktrace() {\n  std::vector<uintptr_t> pcs;\n  _Unwind_Backtrace(unwinder, &pcs);\n  return pcs;\n}\n\nvoid printStacktrace() {\n  std::vector<uintptr_t> pcs = getBacktrace();\n  Dl_info info;\n  size_t i = 0;\n  for (uintptr_t pcAddr : pcs) {\n    const void* pc = reinterpret_cast<const void*>(pcAddr);\n    const char* path = nullptr;\n    const char* name = \"???\";\n    char* demangled = nullptr;\n    int offset = -1;\n\n    std::cerr << \"[\" << i << \"] \";\n    if (dladdr(pc, &info)) {\n      path = info.dli_fname;\n      name = info.dli_sname ?: \"???\";\n      offset = reinterpret_cast<uintptr_t>(pc) -\n          reinterpret_cast<uintptr_t>(info.dli_saddr);\n\n      int status;\n      demangled = abi::__cxa_demangle(name, nullptr, nullptr, &status);\n      if (status == 0) {\n        name = demangled;\n      }\n    }\n    std::cerr << name;\n    if (offset >= 0) {\n      std::cerr << \"+\" << reinterpret_cast<void*>(offset);\n    }\n    std::cerr << \"(\" << pc << \")\";\n    if (path) {\n      std::cerr << \" in \" << path;\n    }\n    std::cerr << std::endl;\n    if (demangled) {\n      free(demangled);\n    }\n    i += 1;\n  }\n}\n\nvoid callPreviousSignalHandler(\n    struct sigaction* action,\n    int signum,\n    siginfo_t* info,\n    void* ctx) {\n  if (!action->sa_handler) {\n    return;\n  }\n  if ((action->sa_flags & SA_SIGINFO) == SA_SIGINFO) {\n    action->sa_sigaction(signum, info, ctx);\n  } else {\n    action->sa_handler(signum);\n  }\n}\n\n// needsLock signals whether we need to lock our writing mutex.\nvoid stacktraceSignalHandler(bool needsLock) {\n  if (needsLock) {\n    pthread_mutex_lock(&writingMutex);\n  }\n  pid_t tid = syscall(SYS_gettid);\n  std::cerr << fatalSignalName << \"(\" << fatalSignum << \"), Thread \" << tid\n            << \": \" << std::endl;\n  printStacktrace();\n  std::cerr << std::endl;\n  if (needsLock) {\n    pthread_mutex_unlock(&writingMutex);\n    pthread_cond_signal(&writingCond);\n  }\n}\n\n// Our fatal signal entry point\nvoid fatalSignalHandler(int signum) {\n  // Check if this is a proper signal that we declared above.\n  const char* name = getSignalName(signum);\n  if (!name) {\n    return;\n  }\n  if (fatalSignalReceived) {\n    return;\n  }\n  // Set the flag so that our SIGUSR2 handler knows that we're aborting and\n  // that it should intercept any SIGUSR2 signal.\n  fatalSignalReceived = true;\n  // Set state for other threads.\n  fatalSignum = signum;\n  fatalSignalName = name;\n  // Linux doesn't have a nice userland API for enumerating threads so we\n  // need to use the proc pseudo-filesystem.\n  DIR* procDir = opendir(\"/proc/self/task\");\n  if (procDir) {\n    pid_t pid = getpid();\n    pid_t currentTid = syscall(SYS_gettid);\n    struct dirent* entry;\n    pthread_mutex_lock(&writingMutex);\n    while ((entry = readdir(procDir)) != nullptr) {\n      if (entry->d_name[0] == '.') {\n        continue;\n      }\n      pid_t tid = atoi(entry->d_name);\n      // If we've found the current thread then we'll jump into the SIGUSR2\n      // handler before calling pthread_cond_wait thus deadlocking, so branch\n      // our directly to the backtrace handler instead of signaling it.\n      if (tid != currentTid) {\n        syscall(SYS_tgkill, pid, tid, SIGUSR2);\n        pthread_cond_wait(&writingCond, &writingMutex);\n      } else {\n        stacktraceSignalHandler(false);\n      }\n    }\n    pthread_mutex_unlock(&writingMutex);\n  } else {\n    perror(\"Failed to open /proc/self/task\");\n  }\n  sigaction(signum, getPreviousSigaction(signum), nullptr);\n  raise(signum);\n}\n\n// Our SIGUSR2 entry point\nvoid stacktraceSignalHandler(int signum, siginfo_t* info, void* ctx) {\n  if (fatalSignalReceived) {\n    stacktraceSignalHandler(true);\n  } else {\n    // We don't want to actually change the signal handler as we want to\n    // remain the signal handler so that we may get the usr2 signal later.\n    callPreviousSignalHandler(&previousSigusr2, signum, info, ctx);\n  }\n}\n\n// Installs SIGABRT signal handler so that we get stack traces\n// from every thread on SIGABRT caused exit. Also installs SIGUSR2 handler\n// so that threads can communicate with each other (be sure if you use SIGUSR2)\n// to install your handler before initing caffe2 (we properly fall back to\n// the previous handler if we didn't initiate the SIGUSR2).\nvoid installFatalSignalHandlers() {\n  std::lock_guard<std::mutex> locker(fatalSignalHandlersInstallationMutex);\n  if (fatalSignalHandlersInstalled) {\n    return;\n  }\n  fatalSignalHandlersInstalled = true;\n  struct sigaction sa;\n  sigemptyset(&sa.sa_mask);\n  // Since we'll be in an exiting situation it's possible there's memory\n  // corruption, so make our own stack just in case.\n  sa.sa_flags = SA_ONSTACK | SA_SIGINFO;\n  sa.sa_handler = ::fatalSignalHandler;\n  for (auto* handler = kSignalHandlers; handler->name != nullptr; handler++) {\n    if (sigaction(handler->signum, &sa, &handler->previous)) {\n      std::string str(\"Failed to add \");\n      str += handler->name;\n      str += \" handler!\";\n      perror(str.c_str());\n    }\n  }\n  sa.sa_sigaction = ::stacktraceSignalHandler;\n  if (sigaction(SIGUSR2, &sa, &::previousSigusr2)) {\n    perror(\"Failed to add SIGUSR2 handler!\");\n  }\n}\n\nvoid uninstallFatalSignalHandlers() {\n  std::lock_guard<std::mutex> locker(fatalSignalHandlersInstallationMutex);\n  if (!fatalSignalHandlersInstalled) {\n    return;\n  }\n  fatalSignalHandlersInstalled = false;\n  for (auto* handler = kSignalHandlers; handler->name != nullptr; handler++) {\n    if (sigaction(handler->signum, &handler->previous, nullptr)) {\n      std::string str(\"Failed to remove \");\n      str += handler->name;\n      str += \" handler!\";\n      perror(str.c_str());\n    } else {\n      handler->previous = {};\n    }\n  }\n  if (sigaction(SIGUSR2, &::previousSigusr2, nullptr)) {\n    perror(\"Failed to add SIGUSR2 handler!\");\n  } else {\n    ::previousSigusr2 = {};\n  }\n}\n#endif // defined(CAFFE2_SUPPORTS_FATAL_SIGNAL_HANDLERS)\n\n} // namespace\n\n#if defined(CAFFE2_SUPPORTS_FATAL_SIGNAL_HANDLERS)\nCAFFE2_DEFINE_bool(\n    caffe2_print_stacktraces,\n    false,\n    \"If set, prints stacktraces when a fatal signal is raised.\");\n#endif\n\nnamespace caffe2 {\n\nSignalHandler::SignalHandler(\n    SignalHandler::Action SIGINT_action,\n    SignalHandler::Action SIGHUP_action)\n    : SIGINT_action_(SIGINT_action),\n      SIGHUP_action_(SIGHUP_action),\n      my_sigint_count_(sigintCount),\n      my_sighup_count_(sighupCount) {\n  hookupHandler();\n}\n\nSignalHandler::~SignalHandler() {\n  unhookHandler();\n}\n\n// Return true iff a SIGINT has been received since the last time this\n// function was called.\nbool SignalHandler::GotSIGINT() {\n  uint64_t count = sigintCount;\n  bool result = (count != my_sigint_count_);\n  my_sigint_count_ = count;\n  return result;\n}\n\n// Return true iff a SIGHUP has been received since the last time this\n// function was called.\nbool SignalHandler::GotSIGHUP() {\n  uint64_t count = sighupCount;\n  bool result = (count != my_sighup_count_);\n  my_sighup_count_ = count;\n  return result;\n}\n\nSignalHandler::Action SignalHandler::CheckForSignals() {\n  if (GotSIGHUP()) {\n    return SIGHUP_action_;\n  }\n  if (GotSIGINT()) {\n    return SIGINT_action_;\n  }\n  return SignalHandler::Action::NONE;\n}\n\n#if defined(CAFFE2_SUPPORTS_FATAL_SIGNAL_HANDLERS)\nvoid setPrintStackTracesOnFatalSignal(bool print) {\n  if (print) {\n    installFatalSignalHandlers();\n  } else {\n    uninstallFatalSignalHandlers();\n  }\n}\nbool printStackTracesOnFatalSignal() {\n  std::lock_guard<std::mutex> locker(fatalSignalHandlersInstallationMutex);\n  return fatalSignalHandlersInstalled;\n}\n\nnamespace internal {\nbool Caffe2InitFatalSignalHandler(int*, char***) {\n  if (caffe2::FLAGS_caffe2_print_stacktraces) {\n    setPrintStackTracesOnFatalSignal(true);\n  }\n  return true;\n}\n\nREGISTER_CAFFE2_INIT_FUNCTION(\n    Caffe2InitFatalSignalHandler,\n    &Caffe2InitFatalSignalHandler,\n    \"Inits signal handlers for fatal signals so we can see what if\"\n    \" caffe2_print_stacktraces is set.\");\n\n} // namepsace internal\n#endif // defined(CAFFE2_SUPPORTS_FATAL_SIGNAL_HANDLERS)\n} // namespace caffe2\n\n#else // defined(CAFFE2_SUPPORTS_SIGNAL_HANDLER)\n\n// TODO: Currently we do not support signal handling in non-Linux yet - below is\n// a minimal implementation that makes things compile.\nnamespace caffe2 {\nSignalHandler::SignalHandler(\n    SignalHandler::Action SIGINT_action,\n    SignalHandler::Action SIGHUP_action) {}\nSignalHandler::~SignalHandler() {}\nbool SignalHandler::GotSIGINT() {\n  return false;\n}\nbool SignalHandler::GotSIGHUP() {\n  return false;\n}\nSignalHandler::Action SignalHandler::CheckForSignals() {\n  return SignalHandler::Action::NONE;\n}\n} // namespace caffe2\n\n#endif // defined(CAFFE2_SUPPORTS_SIGNAL_HANDLER)\n"
  },
  {
    "path": "caffe2/utils/signal_handler.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#if defined(__APPLE__)\n#define CAFFE2_SUPPORTS_SIGNAL_HANDLER\n#elif defined(__linux__) && !defined(CAFFE2_DISABLE_SIGNAL_HANDLERS)\n#define CAFFE2_SUPPORTS_FATAL_SIGNAL_HANDLERS\n#define CAFFE2_SUPPORTS_SIGNAL_HANDLER\n#endif\n\nnamespace caffe2 {\n\nclass SignalHandler {\n public:\n  enum class Action {\n    NONE,\n    STOP\n  };\n\n  // Contructor. Specify what action to take when a signal is received.\n  SignalHandler(Action SIGINT_action,\n                Action SIGHUP_action);\n  ~SignalHandler();\n\n  Action CheckForSignals();\n\n private:\n  bool GotSIGINT();\n  bool GotSIGHUP();\n  Action SIGINT_action_;\n  Action SIGHUP_action_;\n  unsigned long my_sigint_count_;\n  unsigned long my_sighup_count_;\n};\n\n#if defined(CAFFE2_SUPPORTS_FATAL_SIGNAL_HANDLERS)\n// This works by setting up certain fatal signal handlers. Previous fatal\n// signal handlers will still be called when the signal is raised. Defaults\n// to being off.\nvoid setPrintStackTracesOnFatalSignal(bool print);\nbool printStackTracesOnFatalSignal();\n#endif // defined(CAFFE2_SUPPORTS_SIGNAL_HANDLER)\n\n}  // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/simple_queue.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_SIMPLE_QUEUE_H_\n#define CAFFE2_UTILS_SIMPLE_QUEUE_H_\n\n#include <condition_variable>  // NOLINT\n#include <mutex>  // NOLINT\n#include <queue>\n\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\n\n// This is a very simple queue that Yangqing wrote when bottlefeeding the baby,\n// so don't take it seriously. What it does is a minimal thread-safe queue that\n// allows me to run network as a DAG.\n//\n// A usual work pattern looks like this: one or multiple producers push jobs\n// into this queue, and one or multiple workers pops jobs from this queue. If\n// nothing is in the queue but NoMoreJobs() is not called yet, the pop calls\n// will wait. If NoMoreJobs() has been called, pop calls will return false,\n// which serves as a message to the workers that they should exit.\ntemplate <typename T>\nclass SimpleQueue {\n public:\n  SimpleQueue() : no_more_jobs_(false) {}\n\n  // Pops a value and writes it to the value pointer. If there is nothing in the\n  // queue, this will wait till a value is inserted to the queue. If there are\n  // no more jobs to pop, the function returns false. Otherwise, it returns\n  // true.\n  bool Pop(T* value) {\n    std::unique_lock<std::mutex> mutex_lock(mutex_);\n    while (queue_.size() == 0 && !no_more_jobs_) cv_.wait(mutex_lock);\n    if (queue_.size() == 0 && no_more_jobs_) return false;\n    *value = queue_.front();\n    queue_.pop();\n    return true;\n  }\n\n  int size() {\n    std::unique_lock<std::mutex> mutex_lock(mutex_);\n    return queue_.size();\n  }\n\n  // Push pushes a value to the queue.\n  void Push(const T& value) {\n    {\n      std::lock_guard<std::mutex> mutex_lock(mutex_);\n      CAFFE_ENFORCE(!no_more_jobs_, \"Cannot push to a closed queue.\");\n      queue_.push(value);\n    }\n    cv_.notify_one();\n  }\n\n  // NoMoreJobs() marks the close of this queue. It also notifies all waiting\n  // Pop() calls so that they either check out remaining jobs, or return false.\n  // After NoMoreJobs() is called, this queue is considered closed - no more\n  // Push() functions are allowed, and once existing items are all checked out\n  // by the Pop() functions, any more Pop() function will immediately return\n  // false with nothing set to the value.\n  void NoMoreJobs() {\n    {\n      std::lock_guard<std::mutex> mutex_lock(mutex_);\n      no_more_jobs_ = true;\n    }\n    cv_.notify_all();\n  }\n\n private:\n  std::mutex mutex_;\n  std::condition_variable cv_;\n  std::queue<T> queue_;\n  bool no_more_jobs_;\n  // We do not allow copy constructors.\n  SimpleQueue(const SimpleQueue& /*src*/) {}\n};\n\n}  // namespace caffe2\n\n#endif  // CAFFE2_UTILS_SIMPLE_QUEUE_H_\n"
  },
  {
    "path": "caffe2/utils/simple_queue_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <thread>  // NOLINT\n\n#include \"caffe2/utils/simple_queue.h\"\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\n\nstatic std::unique_ptr<SimpleQueue<int> > gQueue;\n\nstatic void ConsumerFunction(int thread_idx) {\n  int value;\n  while (true) {\n    if (!gQueue->Pop(&value)) return;\n    VLOG(1) << \"Emitting \" << value << \" from thread \" << thread_idx;\n  }\n}\n\nstatic void ProducerFunction(int thread_idx, int start, int count) {\n  for (int i = 0; i < count; ++i) {\n    VLOG(1) << \"Pushing \" << i + start << \" from thread \" << thread_idx;\n    gQueue->Push(i + start);\n  }\n}\n\n\nTEST(SimpleQueueTest, SingleProducerSingleConsumer) {\n  gQueue.reset(new SimpleQueue<int>());\n  std::thread consumer(ConsumerFunction, 0);\n  for (int i = 0; i < 10; ++i) {\n    gQueue->Push(i);\n  }\n  gQueue->NoMoreJobs();\n  consumer.join();\n}\n\nTEST(SimpleQueueTest, SingleProducerDoubleConsumer) {\n  gQueue.reset(new SimpleQueue<int>());\n  std::thread consumer0(ConsumerFunction, 0);\n  std::thread consumer1(ConsumerFunction, 1);\n  for (int i = 0; i < 10; ++i) {\n    gQueue->Push(i);\n  }\n  gQueue->NoMoreJobs();\n  consumer0.join();\n  consumer1.join();\n}\n\n\nTEST(SimpleQueueTest, DoubleProducerDoubleConsumer) {\n  gQueue.reset(new SimpleQueue<int>());\n  std::thread producer0(ProducerFunction, 0, 0, 10);\n  std::thread producer1(ProducerFunction, 0, 10, 10);\n  std::thread consumer0(ConsumerFunction, 2);\n  std::thread consumer1(ConsumerFunction, 3);\n  producer0.join();\n  producer1.join();\n  gQueue->NoMoreJobs();\n  consumer0.join();\n  consumer1.join();\n}\n\nTEST(SimpleQueueDeathTest, CannotAddAfterQueueFinished) {\n  gQueue.reset(new SimpleQueue<int>());\n  gQueue->Push(0);\n  gQueue->NoMoreJobs();\n  ASSERT_THROW(gQueue->Push(0), EnforceNotMet);\n}\n\n\n}  // namespace caffe2\n\n\n"
  },
  {
    "path": "caffe2/utils/smart_tensor_printer.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"smart_tensor_printer.h\"\n\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n// Since DispatchHelper doesn't support passing arguments through the call()\n// method to DoRunWithType we have to create an object that will hold these\n// arguments explicitly.\nstruct ProxyPrinter {\n  template <typename T>\n  bool DoRunWithType() {\n    tensorPrinter->Print<T>(*tensor);\n    return true;\n  }\n\n  void Print() {\n    // Pulled in printable types from caffe2/core/types.cc\n    // Unfortunately right now one has to add them by hand\n    DispatchHelper<TensorTypes<\n        float,\n        int,\n        std::string,\n        bool,\n        uint8_t,\n        int8_t,\n        uint16_t,\n        int16_t,\n        int64_t,\n        double,\n        char>>::call(this, tensor->meta());\n  }\n\n  const Tensor<CPUContext>* tensor;\n  TensorPrinter* tensorPrinter;\n};\n}\n\nSmartTensorPrinter::SmartTensorPrinter(const std::string& tensor_name)\n    : tensorPrinter_(tensor_name) {}\n\nSmartTensorPrinter::SmartTensorPrinter(\n    const std::string& tensor_name,\n    const std::string& file_name)\n    : tensorPrinter_(tensor_name, file_name) {}\n\nSmartTensorPrinter::SmartTensorPrinter(\n    const std::string& tensor_name,\n    const std::string& file_name,\n    int limit)\n    : tensorPrinter_(tensor_name, file_name, limit) {}\n\nvoid SmartTensorPrinter::Print(const Tensor<CPUContext>& tensor) {\n  ProxyPrinter printer;\n\n  printer.tensor = &tensor;\n  printer.tensorPrinter = &tensorPrinter_;\n  printer.Print();\n}\n\nSmartTensorPrinter& SmartTensorPrinter::DefaultTensorPrinter() {\n// TODO(janusz): thread_local does not work under mac.\n#if __APPLE__\n  CAFFE_THROW(\n      \"SmartTensorPrinter does not work on mac yet due to thread_local.\");\n#else\n  static thread_local SmartTensorPrinter printer;\n  return printer;\n#endif\n}\n\nvoid SmartTensorPrinter::PrintTensor(const Tensor<CPUContext>& tensor) {\n  DefaultTensorPrinter().Print(tensor);\n}\n}\n"
  },
  {
    "path": "caffe2/utils/smart_tensor_printer.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/tensor.h\"\n\nnamespace caffe2 {\n\n// This is a wrapper around the TensorPrinter that doesn't require the user to\n// explicit specify the type of the tensor while calling the Print() method.\n// It also supports a convenience function with a default constructed printer as\n// a static method.\nclass SmartTensorPrinter {\n public:\n  // The proliferation of constructors is to give the feature parity with\n  // TensorPrinter\n  // yet not repeat the default arguments explicitly in case they change in the\n  // future.\n  SmartTensorPrinter() = default;\n\n  explicit SmartTensorPrinter(const std::string& tensor_name);\n\n  SmartTensorPrinter(\n      const std::string& tensor_name,\n      const std::string& file_name);\n\n  SmartTensorPrinter(\n      const std::string& tensor_name,\n      const std::string& file_name,\n      int limit);\n\n  void Print(const Tensor<CPUContext>& tensor);\n\n  template <class Context>\n  void PrintMeta(const Tensor<Context>& tensor) {\n    tensorPrinter_.PrintMeta(tensor);\n  }\n\n  // Uses a default constructed SmartTensorPrinter\n  static void PrintTensor(const Tensor<CPUContext>& tensor);\n\n  // Uses a default constructed SmartTensorPrinter\n  template <class Context>\n  void PrintTensorMeta(const Tensor<Context>& tensor) {\n    DefaultTensorPrinter().PrintMeta(tensor);\n  }\n\n private:\n  // Returns a thread local default constructed TensorPrinter\n  static SmartTensorPrinter& DefaultTensorPrinter();\n\n  TensorPrinter tensorPrinter_;\n};\n}\n"
  },
  {
    "path": "caffe2/utils/smart_tensor_printer_test.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/utils/smart_tensor_printer.h\"\n\n#include \"caffe2/core/common.h\"\n\n#include <gtest/gtest.h>\n\nnamespace caffe2 {\n\ntemplate <typename T>\nstd::string my_to_string(const T& value) {\n  return to_string(value);\n}\n\ntemplate <>\nstd::string my_to_string<std::string>(const std::string& value) {\n  return value;\n}\n\ntemplate <typename T>\nvoid expect_stderr_contains(const std::vector<T>& values) {\n  std::string captured_stderr = testing::internal::GetCapturedStderr();\n  for (const auto& value : values) {\n    std::string stringValue = my_to_string(value);\n    EXPECT_TRUE(captured_stderr.find(stringValue) != std::string::npos);\n  }\n}\n\ntemplate <typename T>\nvoid printTensorAndCheck(const std::vector<T>& values) {\n  testing::internal::CaptureStderr();\n  CPUContext cpuContext;\n\n  Tensor<CPUContext> tensor(\n      std::vector<TIndex>{static_cast<TIndex>(values.size())},\n      values,\n      &cpuContext);\n\n  SmartTensorPrinter::PrintTensor(tensor);\n  expect_stderr_contains(values);\n}\n\n#if !(__APPLE__) // TODO(janusz): thread_local does not work under mac.\n\nTEST(SmartTensorPrinterTest, SimpleTest) {\n  printTensorAndCheck(std::vector<int>{1, 2, 3, 4, 5});\n  printTensorAndCheck(std::vector<std::string>{\"bob\", \"alice\", \"facebook\"});\n}\n\n#endif // !(__APPLE__)\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/string_utils.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/utils/string_utils.h\"\n\n#include <algorithm>\n#include <sstream>\n#include <vector>\n\nnamespace caffe2 {\n\nstd::vector<std::string> split(char separator, const std::string& string) {\n  std::vector<std::string> pieces;\n  std::stringstream ss(string);\n  std::string item;\n  while (getline(ss, item, separator)) {\n    pieces.push_back(std::move(item));\n  }\n  return pieces;\n}\n\nsize_t editDistance(\n  const std::string& s1, const std::string& s2, size_t max_distance)\n  {\n    std::vector<size_t> current(s1.length() + 1);\n    std::vector<size_t> previous(s1.length() + 1);\n    std::vector<size_t> previous1(s1.length() + 1);\n\n    return editDistanceHelper(\n        s1.c_str(),\n        s1.length(),\n        s2.c_str(),\n        s2.length(),\n        current,\n        previous,\n        previous1,\n        max_distance\n    );\n  }\n  #define NEXT_UNSAFE(s, i, c) { \\\n      (c)=(uint8_t)(s)[(i)++]; \\\n  }\n\nint32_t editDistanceHelper(const char* s1,\n  size_t s1_len,\n  const char* s2,\n  size_t s2_len,\n  std::vector<size_t> &current,\n  std::vector<size_t> &previous,\n  std::vector<size_t> &previous1,\n  size_t max_distance) {\n    if (max_distance) {\n      if (std::max(s1_len, s2_len) - std::min(s1_len, s2_len) > max_distance) {\n        return max_distance+1;\n      }\n    }\n\n    for (size_t j = 0; j <= s1_len; ++j) {\n      current[j] = j;\n    }\n\n    int32_t str2_offset = 0;\n    char prev2 = 0;\n    for (size_t i = 1; i <= s2_len; ++i) {\n      swap(previous1, previous);\n      swap(current, previous);\n      current[0] = i;\n\n      char c2 = s2[str2_offset];\n      char prev1 = 0;\n      int32_t str1_offset = 0;\n\n      NEXT_UNSAFE(s2, str2_offset, c2);\n\n      size_t current_min = s1_len;\n      for (size_t j = 1; j <= s1_len; ++j) {\n        size_t insertion = previous[j] + 1;\n        size_t deletion = current[j - 1] + 1;\n        size_t substitution = previous[j - 1];\n        size_t transposition = insertion;\n        char c1 = s1[str1_offset];\n\n        NEXT_UNSAFE(s1, str1_offset, c1);\n\n        if (c1 != c2) {\n          substitution += 1;\n        }\n\n\n        if (prev1 == c2 && prev2 == c1 && j > 1 && i > 1) {\n          transposition = previous1[j - 2] + 1;\n        }\n        prev1 = c1;\n\n        current[j] = std::min(std::min(insertion, deletion),\n                         std::min(substitution, transposition));\n        current_min = std::min(current_min, current[j]);\n      }\n\n\n      if (max_distance != 0 && current_min > max_distance) {\n        return max_distance+1;\n      }\n\n      prev2 = c2;\n    }\n\n    return current[s1_len];\n  }\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/string_utils.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace caffe2 {\n\nstd::vector<std::string> split(char separator, const std::string& string);\nsize_t editDistance(\n  const std::string& s1, const std::string& s2, size_t max_distance = 0);\n\nint32_t editDistanceHelper(const char* s1,\n  size_t s1_len,\n  const char* s2,\n  size_t s2_len,\n  std::vector<size_t> &current,\n  std::vector<size_t> &previous,\n  std::vector<size_t> &previous1,\n  size_t max_distance);\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/thread_pool.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_THREAD_POOL_H_\n#define CAFFE2_UTILS_THREAD_POOL_H_\n\n#include <condition_variable>\n#include <functional>\n#include <mutex>\n#include <queue>\n#include <thread>\n#include <utility>\n\n#include \"caffe2/core/numa.h\"\n\nnamespace caffe2 {\n\nclass TaskThreadPool {\n private:\n  struct task_element_t {\n    bool run_with_id;\n    const std::function<void()> no_id;\n    const std::function<void(std::size_t)> with_id;\n\n    explicit task_element_t(const std::function<void()>& f)\n        : run_with_id(false), no_id(f), with_id(nullptr) {}\n    explicit task_element_t(const std::function<void(std::size_t)>& f)\n        : run_with_id(true), no_id(nullptr), with_id(f) {}\n  };\n\n  std::queue<task_element_t> tasks_;\n  std::vector<std::thread> threads_;\n  std::mutex mutex_;\n  std::condition_variable condition_;\n  std::condition_variable completed_;\n  bool running_;\n  bool complete_;\n  std::size_t available_;\n  std::size_t total_;\n  int numa_node_id_;\n\n public:\n  explicit TaskThreadPool(std::size_t pool_size, int numa_node_id = -1)\n      : threads_(pool_size),\n        running_(true),\n        complete_(true),\n        available_(pool_size),\n        total_(pool_size),\n        numa_node_id_(numa_node_id) {\n    for (std::size_t i = 0; i < pool_size; ++i) {\n      threads_[i] = std::thread(std::bind(&TaskThreadPool::main_loop, this, i));\n    }\n  }\n\n  // Set running flag to false then notify all threads.\n  ~TaskThreadPool() {\n    {\n      std::unique_lock<std::mutex> lock(mutex_);\n      running_ = false;\n      condition_.notify_all();\n    }\n\n    try {\n      for (auto& t : threads_) {\n        t.join();\n      }\n    } catch (const std::exception&) {\n    }\n  }\n\n  /// @brief Add task to the thread pool if a thread is currently available.\n  template <typename Task>\n  void runTask(Task task) {\n    std::unique_lock<std::mutex> lock(mutex_);\n\n    // Set task and signal condition variable so that a worker thread will\n    // wake up and use the task.\n    tasks_.push(task_element_t(static_cast<std::function<void()>>(task)));\n    complete_ = false;\n    condition_.notify_one();\n  }\n\n  void run(const std::function<void()>& func) {\n    runTask(func);\n  }\n\n  template <typename Task>\n  void runTaskWithID(Task task) {\n    std::unique_lock<std::mutex> lock(mutex_);\n\n    // Set task and signal condition variable so that a worker thread will\n    // wake up and use the task.\n    tasks_.push(\n        task_element_t(static_cast<std::function<void(std::size_t)>>(task)));\n    complete_ = false;\n    condition_.notify_one();\n  }\n\n  /// @brief Wait for queue to be empty\n  void waitWorkComplete() {\n    std::unique_lock<std::mutex> lock(mutex_);\n    while (!complete_) {\n      completed_.wait(lock);\n    }\n  }\n\n private:\n  /// @brief Entry point for pool threads.\n  void main_loop(std::size_t index) {\n    NUMABind(numa_node_id_);\n\n    while (running_) {\n      // Wait on condition variable while the task is empty and\n      // the pool is still running.\n      std::unique_lock<std::mutex> lock(mutex_);\n      while (tasks_.empty() && running_) {\n        condition_.wait(lock);\n      }\n      // If pool is no longer running, break out of loop.\n      if (!running_) {\n        break;\n      }\n\n      // Copy task locally and remove from the queue.  This is\n      // done within its own scope so that the task object is\n      // destructed immediately after running the task.  This is\n      // useful in the event that the function contains\n      // shared_ptr arguments bound via bind.\n      {\n        auto tasks = tasks_.front();\n        tasks_.pop();\n        // Decrement count, indicating thread is no longer available.\n        --available_;\n\n        lock.unlock();\n\n        // Run the task.\n        try {\n          if (tasks.run_with_id) {\n            tasks.with_id(index);\n          } else {\n            tasks.no_id();\n          }\n        } catch (const std::exception&) {\n        }\n\n        // Update status of empty, maybe\n        // Need to recover the lock first\n        lock.lock();\n\n        // Increment count, indicating thread is available.\n        ++available_;\n        if (tasks_.empty() && available_ == total_) {\n          complete_ = true;\n          completed_.notify_one();\n        }\n      }\n    } // while running_\n  }\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_UTILS_THREAD_POOL_H_\n"
  },
  {
    "path": "caffe2/utils/threadpool/ThreadPool.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/utils/threadpool/ThreadPool.h\"\n#include \"WorkersPool.h\"\n#include \"caffe2/core/logging.h\"\n\n#include <cpuinfo.h>\n\nCAFFE2_DEFINE_bool(caffe2_threadpool_force_inline, false,\n                   \"Force to always run jobs on the calling thread\");\n\n// Whether or not threadpool caps apply to Android\nCAFFE2_DEFINE_int(caffe2_threadpool_android_cap, true, \"\");\n\n// Whether or not threadpool caps apply to iOS\nCAFFE2_DEFINE_int(caffe2_threadpool_ios_cap, true, \"\");\n\n\nnamespace caffe2 {\n\n// Default smallest amount of work that will be partitioned between\n// multiple threads; the runtime value is configurable\n#if CAFFE2_ANDROID\nconstexpr size_t kDefaultMinWorkSize = 8;\n#else\nconstexpr size_t kDefaultMinWorkSize = 80;\n#endif\n\nstd::unique_ptr<ThreadPool> ThreadPool::defaultThreadPool() {\n  int numThreads = cpuinfo_get_processors_count();\n\n  bool applyCap = false;\n#if CAFFE2_ANDROID\n  applyCap = caffe2::FLAGS_caffe2_threadpool_android_cap;\n#elif CAFFE2_IOS\n  applyCap = caffe2::FLAGS_caffe2_threadpool_ios_cap;\n#endif\n\n  if (applyCap) {\n    switch (numThreads) {\n#if CAFFE2_ANDROID && (CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64)\n      case 4:\n          switch (cpuinfo_get_core(0)->midr & UINT32_C(0xFF00FFF0)) {\n            case UINT32_C(0x51002110): /* Snapdragon 820 Kryo Silver */\n            case UINT32_C(0x51002010): /* Snapdragon 821 Kryo Silver */\n            case UINT32_C(0x51002050): /* Snapdragon 820/821 Kryo Gold */\n              /* Kryo: 2+2 big.LITTLE */\n              numThreads = 2;\n              break;\n            default:\n              /* Anything else: assume homogeneous architecture */\n              numThreads = 4;\n              break;\n          }\n        break;\n#endif\n      case 5:\n        /* 4+1 big.LITTLE */\n        numThreads = 4;\n        break;\n      case 6:\n        /* 2+4 big.LITTLE */\n        numThreads = 2;\n        break;\n      case 8:\n        /* 4+4 big.LITTLE */\n        numThreads = 4;\n        break;\n      case 10:\n        /* 4+4+2 Min.Med.Max, running on Med cores */\n        numThreads = 4;\n        break;\n      default:\n        if (numThreads > 4) {\n          numThreads = numThreads / 2;\n        }\n        break;\n    }\n  }\n  LOG(INFO) << \"Constructing thread pool with \" << numThreads << \" threads\";\n  return caffe2::make_unique<ThreadPool>(numThreads);\n}\n\nThreadPool::ThreadPool(int numThreads)\n    : minWorkSize_(kDefaultMinWorkSize), numThreads_(numThreads),\n      workersPool_(std::make_shared<WorkersPool>()) {}\n\nThreadPool::~ThreadPool() {}\n\nint ThreadPool::getNumThreads() const {\n  std::lock_guard<std::mutex> guard(executionMutex_);\n  return numThreads_;\n}\n\n// Sets the minimum work size (range) for which to invoke the\n// threadpool; work sizes smaller than this will just be run on the\n// main (calling) thread\nvoid ThreadPool::setMinWorkSize(size_t size) {\n  std::lock_guard<std::mutex> guard(executionMutex_);\n  minWorkSize_ = size;\n}\n\nvoid ThreadPool::run(const std::function<void(int, size_t)>& fn, size_t range) {\n  std::lock_guard<std::mutex> guard(executionMutex_);\n  // If there are no worker threads, or if the range is too small (too\n  // little work), just run locally\n  const bool runLocally = range < minWorkSize_ ||\n                          FLAGS_caffe2_threadpool_force_inline ||\n                          (numThreads_ == 0);\n  if (runLocally) {\n    // Work is small enough to just run locally; multithread overhead\n    // is too high\n    for (size_t i = 0; i < range; ++i) {\n      fn(0, i);\n    }\n    return;\n  }\n\n  struct FnTask : public Task {\n    FnTask(){};\n    virtual ~FnTask(){};\n    const std::function<void(int, size_t)> *fn_;\n    int idx_;\n    size_t start_;\n    size_t end_;\n    virtual void Run() override {\n      for (auto i = start_; i < end_; ++i) {\n        (*fn_)(idx_, i);\n      }\n    }\n  };\n\n  CAFFE_ENFORCE_GE(numThreads_, 1);\n  const size_t unitsPerTask = (range + numThreads_ - 1) / numThreads_;\n  tasks_.resize(numThreads_);\n  for (size_t i = 0; i < numThreads_; ++i) {\n    if (!tasks_[i]) {\n      tasks_[i].reset(new FnTask());\n    }\n    auto *task = (FnTask *)tasks_[i].get();\n    task->fn_ = &fn;\n    task->idx_ = i;\n    task->start_ = std::min<size_t>(range, i * unitsPerTask);\n    task->end_ = std::min<size_t>(range, (i + 1) * unitsPerTask);\n    if (task->start_ >= task->end_) {\n      tasks_.resize(i);\n      break;\n    }\n    CAFFE_ENFORCE_LE(task->start_, range);\n    CAFFE_ENFORCE_LE(task->end_, range);\n  }\n  CAFFE_ENFORCE_LE(tasks_.size(), numThreads_);\n  CAFFE_ENFORCE_GE(tasks_.size(), 1);\n  workersPool_->Execute(tasks_);\n}\n\nvoid ThreadPool::withPool(const std::function<void(WorkersPool*)>& f) {\n  std::lock_guard<std::mutex> guard(executionMutex_);\n  f(workersPool_.get());\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/threadpool/ThreadPool.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_THREADPOOL_H_\n#define CAFFE2_UTILS_THREADPOOL_H_\n\n#include \"ThreadPoolCommon.h\"\n\n#include <functional>\n#include <memory>\n#include <mutex>\n#include <vector>\n\n//\n// A work-stealing threadpool loosely based off of pthreadpool\n//\n\nnamespace caffe2 {\n\nstruct Task;\nclass WorkersPool;\n\nconstexpr size_t kCacheLineSize = 64;\n\n// A work-stealing threadpool with the given number of threads.\n// NOTE: the kCacheLineSize alignment is present only for cache\n// performance, and is not strictly enforced (for example, when\n// the object is created on the heap). Thus, in order to avoid\n// misaligned intrinsics, no SSE instructions shall be involved in\n// the ThreadPool implemetation.\nclass alignas(kCacheLineSize) ThreadPool {\n public:\n  static std::unique_ptr<ThreadPool> defaultThreadPool();\n  ThreadPool(int numThreads);\n  ~ThreadPool();\n  // Returns the number of threads currently in use\n  int getNumThreads() const;\n\n  // Sets the minimum work size (range) for which to invoke the\n  // threadpool; work sizes smaller than this will just be run on the\n  // main (calling) thread\n  void setMinWorkSize(size_t size);\n  size_t getMinWorkSize() const { return minWorkSize_; }\n  void run(const std::function<void(int, size_t)>& fn, size_t range);\n\n  // Run an arbitrary function in a thread-safe manner accessing the Workers\n  // Pool\n  void withPool(const std::function<void(WorkersPool*)>& fn);\n\n private:\n  mutable std::mutex executionMutex_;\n  size_t minWorkSize_;\n  size_t numThreads_;\n  std::shared_ptr<WorkersPool> workersPool_;\n  std::vector<std::shared_ptr<Task>> tasks_;\n};\n\n} // namespace caffe2\n\n#endif // CAFFE2_UTILS_THREADPOOL_H_\n"
  },
  {
    "path": "caffe2/utils/threadpool/ThreadPoolCommon.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_THREADPOOL_COMMON_H_\n#define CAFFE2_UTILS_THREADPOOL_COMMON_H_\n\n#ifdef __APPLE__\n#include <TargetConditionals.h>\n#endif\n\n// caffe2 depends upon NNPACK, which depends upon this threadpool, so\n// unfortunately we can't reference core/common.h here\n\n// This is copied from core/common.h's definition of CAFFE2_MOBILE\n// Define enabled when building for iOS or Android devices\n#if defined(__ANDROID__)\n#define CAFFE2_ANDROID 1\n#elif (defined(__APPLE__) &&                                            \\\n       (TARGET_IPHONE_SIMULATOR || TARGET_OS_SIMULATOR || TARGET_OS_IPHONE))\n#define CAFFE2_IOS 1\n#elif (defined(__APPLE__) && TARGET_OS_MAC)\n#define CAFFE2_IOS 1\n#else\n#endif // ANDROID / IOS / MACOS\n\n#endif  // CAFFE2_UTILS_THREADPOOL_COMMON_H_\n"
  },
  {
    "path": "caffe2/utils/threadpool/WorkersPool.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#pragma once\n\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/logging.h\"\n#include <atomic>\n#include <thread>\n#include <condition_variable>\n\n#if defined(_MSC_VER)\n#include <intrin.h>\n#endif\n\nnamespace caffe2 {\n\n// Uses code derived from gemmlowp,\n// https://github.com/google/gemmlowp/blob/6c91e1ed0c2eff1182d804310b92911fe9c18019/internal/multi_thread_gemm.h\n// Changes:\n// - allocation-free execute()\n// - Use RAII where possible.\n// - Run the first task on the main thread (since that is the largest task).\n// - removed custom allocator.\n// - Removed some ifdef's\n// - cache-line align Worker.\n// - use std::atomic instead of volatile and custom barriers.\n// - use std::mutex/std::condition_variable instead of raw pthreads.\n\nconstexpr size_t kGEMMLOWPCacheLineSize = 64;\n\ntemplate <typename T>\nstruct AllocAligned {\n  // Allocate a T aligned at an `align` byte address\n  template <typename... Args>\n  static T* alloc(Args&&... args) {\n    void* p = nullptr;\n\n#if defined(__ANDROID__)\n    p = memalign(kGEMMLOWPCacheLineSize, sizeof(T));\n#elif defined(_MSC_VER)\n    p = _aligned_malloc(sizeof(T), kGEMMLOWPCacheLineSize);\n#else\n    posix_memalign((void**)&p, kGEMMLOWPCacheLineSize, sizeof(T));\n#endif\n\n    if (p) {\n      return new (p) T(std::forward<Args>(args)...);\n    }\n\n    return nullptr;\n  }\n\n  // Free a T previously allocated via AllocAligned<T>::alloc()\n  static void release(T* p) {\n    if (p) {\n      p->~T();\n      free((void*)p);\n    }\n  }\n};\n\n// Deleter object for unique_ptr for an aligned object\ntemplate <typename T>\nstruct AlignedDeleter {\n  void operator()(T* p) const { AllocAligned<T>::release(p); }\n};\n\n// make_unique that guarantees alignment\ntemplate <typename T>\nstruct MakeAligned {\n  template <typename... Args>\n  static std::unique_ptr<T, AlignedDeleter<T>> make(Args&&... args) {\n    return std::unique_ptr<T, AlignedDeleter<T>>(\n        AllocAligned<T>::alloc(std::forward<Args>(args)...));\n  }\n};\n\nconst int kMaxBusyWaitNOPs = 32 * 1000 * 1000;\n\n#if defined(_MSC_VER)\n#define GEMMLOWP_NOP __nop();\n#else\n#define GEMMLOWP_NOP \"nop\\n\"\n#endif\n\n#define GEMMLOWP_STRING_CONCAT_4(X) X X X X\n#define GEMMLOWP_NOP4 GEMMLOWP_STRING_CONCAT_4(GEMMLOWP_NOP)\n#define GEMMLOWP_NOP16 GEMMLOWP_STRING_CONCAT_4(GEMMLOWP_NOP4)\n#define GEMMLOWP_NOP64 GEMMLOWP_STRING_CONCAT_4(GEMMLOWP_NOP16)\n\ninline int Do256NOPs() {\n#if defined(_MSC_VER)\n  GEMMLOWP_NOP64;\n#else\n  asm volatile(GEMMLOWP_NOP64);\n#endif\n  return 64;\n}\n\n#undef GEMMLOWP_STRING_CONCAT_4\n#undef GEMMLOWP_NOP256\n#undef GEMMLOWP_NOP64\n#undef GEMMLOWP_NOP16\n#undef GEMMLOWP_NOP4\n#undef GEMMLOWP_NOP\n\n// Waits until *var != initial_value.\n//\n// Returns the new value of *var. The guarantee here is that\n// the return value is different from initial_value, and that that\n// new value has been taken by *var at some point during the\n// execution of this function. There is no guarantee that this is\n// still the value of *var when this function returns, since *var is\n// not assumed to be guarded by any lock.\n//\n// First does some busy-waiting for a fixed number of no-op cycles,\n// then falls back to passive waiting for the given condvar, guarded\n// by the given mutex.\n//\n// The idea of doing some initial busy-waiting is to help get\n// better and more consistent multithreading benefits for small GEMM sizes.\n// Busy-waiting help ensuring that if we need to wake up soon after having\n// started waiting, then we can wake up quickly (as opposed to, say,\n// having to wait to be scheduled again by the OS). On the other hand,\n// we must still eventually revert to passive waiting for longer waits\n// (e.g. worker threads having finished a GEMM and waiting until the next GEMM)\n// so as to avoid permanently spinning.\n//\ntemplate <typename T>\nT WaitForVariableChange(std::atomic<T>* var,\n                        T initial_value,\n                        std::condition_variable* cond,\n                        std::mutex* mutex) {\n  // If we are on a platform that supports it, spin for some time.\n  {\n    int nops = 0;\n    // First, trivial case where the variable already changed value.\n    T new_value = var->load(std::memory_order_relaxed);\n    if (new_value != initial_value) {\n      std::atomic_thread_fence(std::memory_order_acquire);\n      return new_value;\n    }\n    // Then try busy-waiting.\n    while (nops < kMaxBusyWaitNOPs) {\n      nops += Do256NOPs();\n      new_value = var->load(std::memory_order_relaxed);\n      if (new_value != initial_value) {\n        std::atomic_thread_fence(std::memory_order_acquire);\n        return new_value;\n      }\n    }\n  }\n\n  // Finally, do real passive waiting.\n  {\n    std::unique_lock<std::mutex> g(*mutex);\n    T new_value = var->load(std::memory_order_relaxed);\n    // Handle spurious wakeups.\n    cond->wait(g, [&]() {\n      new_value = var->load(std::memory_order_relaxed);\n      return new_value != initial_value;\n    });\n    DCHECK_NE(static_cast<size_t>(new_value), static_cast<size_t>(initial_value));\n    return new_value;\n  }\n}\n\n// A BlockingCounter lets one thread to wait for N events to occur.\n// This is how the master thread waits for all the worker threads\n// to have finished working.\nclass BlockingCounter {\n public:\n  // Sets/resets the counter; initial_count is the number of\n  // decrementing events that the Wait() call will be waiting for.\n  void Reset(std::size_t initial_count) {\n    std::lock_guard<std::mutex> g(mutex_);\n    DCHECK_EQ(count_, 0);\n    count_ = initial_count;\n  }\n\n  // Decrements the counter; if the counter hits zero, signals\n  // the thread that was waiting for that, and returns true.\n  // Otherwise (if the decremented count is still nonzero),\n  // returns false.\n  bool DecrementCount() {\n    const auto count_value = count_.fetch_sub(1, std::memory_order_relaxed) - 1;\n    DCHECK_GE(count_value, 0);\n    if (count_value == 0) {\n      std::lock_guard<std::mutex> g(mutex_);\n      cond_.notify_one();\n    }\n    bool retval = count_value == 0;\n    return retval;\n  }\n\n  // Waits for the N other threads (N having been set by Reset())\n  // to hit the BlockingCounter.\n  void Wait() {\n    while (size_t count_value = count_.load(std::memory_order_relaxed)) {\n      WaitForVariableChange(&count_, count_value, &cond_, &mutex_);\n    }\n  }\n\n private:\n  std::condition_variable cond_;\n  std::mutex mutex_;\n  std::atomic<std::size_t> count_{0};\n};\n\n// A workload for a worker.\nstruct Task {\n  Task() {}\n  virtual ~Task() {}\n  virtual void Run() = 0;\n};\n\n// A worker thread.\nclass alignas(kGEMMLOWPCacheLineSize) Worker {\n public:\n  enum class State : uint8_t {\n    ThreadStartup, // The initial state before the thread main loop runs.\n    Ready, // Is not working, has not yet received new work to do.\n    HasWork, // Has work to do.\n    ExitAsSoonAsPossible // Should exit at earliest convenience.\n  };\n\n  explicit Worker(BlockingCounter* counter_to_decrement_when_ready)\n      : task_(nullptr),\n        state_(State::ThreadStartup),\n        counter_to_decrement_when_ready_(counter_to_decrement_when_ready) {\n    thread_ = caffe2::make_unique<std::thread>([this]() { this->ThreadFunc(); });\n  }\n\n  ~Worker() {\n    ChangeState(State::ExitAsSoonAsPossible);\n    thread_->join();\n  }\n\n  // Changes State; may be called from either the worker thread\n  // or the master thread; however, not all state transitions are legal,\n  // which is guarded by assertions.\n  void ChangeState(State new_state) {\n    std::lock_guard<std::mutex> g(state_mutex_);\n    DCHECK(new_state != state_.load(std::memory_order_relaxed));\n    switch (state_.load(std::memory_order_relaxed)) {\n    case State::ThreadStartup:\n      DCHECK(new_state == State::Ready);\n      break;\n    case State::Ready:\n      DCHECK(new_state == State::HasWork || new_state == State::ExitAsSoonAsPossible);\n      break;\n    case State::HasWork:\n      DCHECK(new_state == State::Ready || new_state == State::ExitAsSoonAsPossible);\n      break;\n    default:\n      abort();\n    }\n    state_.store(new_state, std::memory_order_relaxed);\n    state_cond_.notify_one();\n    if (new_state == State::Ready) {\n      counter_to_decrement_when_ready_->DecrementCount();\n    }\n  }\n\n  // Thread entry point.\n  void ThreadFunc() {\n    ChangeState(State::Ready);\n\n    // Thread main loop\n    while (true) {\n      // Get a state to act on\n      // In the 'Ready' state, we have nothing to do but to wait until\n      // we switch to another state.\n      State state_to_act_upon =\n          WaitForVariableChange(&state_, State::Ready, &state_cond_, &state_mutex_);\n\n      // We now have a state to act on, so act.\n      switch (state_to_act_upon) {\n      case State::HasWork:\n        // Got work to do! So do it, and then revert to 'Ready' state.\n        DCHECK(task_);\n        task_->Run();\n        task_ = nullptr;\n        ChangeState(State::Ready);\n        break;\n      case State::ExitAsSoonAsPossible:\n        return;\n      default:\n        abort();\n      }\n    }\n  }\n\n  static void* ThreadFunc(void* arg) {\n    static_cast<Worker*>(arg)->ThreadFunc();\n    return nullptr;\n  }\n\n  // Called by the master thead to give this worker work to do.\n  // It is only legal to call this if the worker\n  void StartWork(Task* task) {\n    DCHECK(!task_);\n    task_ = task;\n    DCHECK(state_.load(std::memory_order_acquire) == State::Ready);\n    ChangeState(State::HasWork);\n  }\n\n private:\n  // The underlying thread.\n  std::unique_ptr<std::thread> thread_;\n\n  // The task to be worked on.\n  // Visibility of writes to task_ guarded by state_mutex_.\n  Task* task_;\n\n  // The condition variable and mutex guarding state changes.\n  std::condition_variable state_cond_;\n  std::mutex state_mutex_;\n\n  // The state enum tells if we're currently working, waiting for work, etc.\n  std::atomic<State> state_;\n\n  // pointer to the master's thread BlockingCounter object, to notify the\n  // master thread of when this worker switches to the 'Ready' state.\n  BlockingCounter* const counter_to_decrement_when_ready_;\n};\n\nclass WorkersPool {\n public:\n  WorkersPool() {}\n\n  void Execute(const std::vector<std::shared_ptr<Task>>& tasks) {\n    CAFFE_ENFORCE_GE(tasks.size(), 1);\n    // One of the tasks will be run on the current thread.\n    int workers_count = tasks.size() - 1;\n    CreateWorkers(workers_count);\n    DCHECK_LE(workers_count, workers_.size());\n    counter_to_decrement_when_ready_.Reset(workers_count);\n    for (auto task = 1; task < tasks.size(); ++task) {\n      workers_[task - 1]->StartWork(tasks[task].get());\n    }\n    // Execute the remaining workload immediately on the current thread.\n    auto& task = tasks.front();\n    task->Run();\n    // Wait for the workers submitted above to finish.\n    counter_to_decrement_when_ready_.Wait();\n  }\n\n private:\n  // Ensures that the pool has at least the given count of workers.\n  // If any new worker has to be created, this function waits for it to\n  // be ready.\n  void CreateWorkers(std::size_t workers_count) {\n    if (workers_.size() >= workers_count) {\n      return;\n    }\n    counter_to_decrement_when_ready_.Reset(workers_count - workers_.size());\n    while (workers_.size() < workers_count) {\n      workers_.push_back(MakeAligned<Worker>::make(&counter_to_decrement_when_ready_));\n    }\n    counter_to_decrement_when_ready_.Wait();\n  }\n\n  DISABLE_COPY_AND_ASSIGN(WorkersPool);\n  std::vector<std::unique_ptr<Worker, AlignedDeleter<Worker>>> workers_;\n  // The BlockingCounter used to wait for the workers.\n  BlockingCounter counter_to_decrement_when_ready_;\n};\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/utils/threadpool/pthreadpool.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* Standard C headers */\n#include <stdint.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n\n/* POSIX headers */\n#include <pthread.h>\n#include <unistd.h>\n\n/* Library header */\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/utils/fixed_divisor.h\"\n#include \"caffe2/utils/threadpool/pthreadpool.h\"\n\n\nstatic inline size_t divide_round_up(size_t dividend, size_t divisor) {\n  if (dividend % divisor == 0) {\n    return dividend / divisor;\n  } else {\n    return dividend / divisor + 1;\n  }\n}\n\nstatic inline size_t min(size_t a, size_t b) {\n  return a < b ? a : b;\n}\n\nstruct compute_1d_tiled_context {\n  pthreadpool_function_1d_tiled_t function;\n  void* argument;\n  size_t range;\n  size_t tile;\n};\n\nstatic void compute_1d_tiled(const struct compute_1d_tiled_context* context, size_t linear_index) {\n  const size_t tile_index = linear_index;\n  const size_t index = tile_index * context->tile;\n  const size_t tile = min(context->tile, context->range - index);\n  context->function(context->argument, index, tile);\n}\n\nvoid pthreadpool_compute_1d_tiled(\n  pthreadpool_t threadpool,\n  pthreadpool_function_1d_tiled_t function,\n  void* argument,\n  size_t range,\n  size_t tile)\n{\n  if (threadpool == NULL) {\n    /* No thread pool provided: execute function sequentially on the calling thread */\n    for (size_t i = 0; i < range; i += tile) {\n      function(argument, i, min(range - i, tile));\n    }\n  } else {\n    /* Execute in parallel on the thread pool using linearized index */\n    const size_t tile_range = divide_round_up(range, tile);\n    struct compute_1d_tiled_context context = {\n      .function = function,\n      .argument = argument,\n      .range = range,\n      .tile = tile\n    };\n    pthreadpool_compute_1d(threadpool, (pthreadpool_function_1d_t) compute_1d_tiled, &context, tile_range);\n  }\n}\n\nstruct compute_2d_context {\n  pthreadpool_function_2d_t function;\n  void* argument;\n  caffe2::FixedDivisor<int> range_j;\n};\n\nstatic void compute_2d(const struct compute_2d_context* context, size_t linear_index) {\n  DCHECK_LE(linear_index, std::numeric_limits<int>::max());\n\n  int q;\n  int r;\n  context->range_j.divMod((int) linear_index, q, r);\n  context->function(context->argument, q, r);\n}\n\nvoid pthreadpool_compute_2d(\n  struct pthreadpool* threadpool,\n  pthreadpool_function_2d_t function,\n  void* argument,\n  size_t range_i,\n  size_t range_j)\n{\n  if (threadpool == NULL) {\n    /* No thread pool provided: execute function sequentially on the calling thread */\n    for (size_t i = 0; i < range_i; i++) {\n      for (size_t j = 0; j < range_j; j++) {\n        function(argument, i, j);\n      }\n    }\n  } else {\n    DCHECK_LE(range_i * range_j, (size_t) std::numeric_limits<int>::max());\n    /* Execute in parallel on the thread pool using linearized index */\n    struct compute_2d_context context = {\n      .function = function,\n      .argument = argument,\n      .range_j = caffe2::FixedDivisor<int>(range_j)\n    };\n    pthreadpool_compute_1d(threadpool, (pthreadpool_function_1d_t) compute_2d, &context, range_i * range_j);\n  }\n}\n\nstruct compute_2d_tiled_context {\n  pthreadpool_function_2d_tiled_t function;\n  void* argument;\n  caffe2::FixedDivisor<int> tile_range_j;\n  size_t range_i;\n  size_t range_j;\n  size_t tile_i;\n  size_t tile_j;\n};\n\nstatic void compute_2d_tiled(const struct compute_2d_tiled_context* context, size_t linear_index) {\n  int q;\n  int r;\n\n  context->tile_range_j.divMod(linear_index, q, r);\n  const size_t max_tile_i = context->tile_i;\n  const size_t max_tile_j = context->tile_j;\n  const size_t index_i = q * max_tile_i;\n  const size_t index_j = r * max_tile_j;\n  const size_t tile_i = min(max_tile_i, context->range_i - index_i);\n  const size_t tile_j = min(max_tile_j, context->range_j - index_j);\n  context->function(context->argument, index_i, index_j, tile_i, tile_j);\n}\n\nvoid pthreadpool_compute_2d_tiled(\n  pthreadpool_t threadpool,\n  pthreadpool_function_2d_tiled_t function,\n  void* argument,\n  size_t range_i,\n  size_t range_j,\n  size_t tile_i,\n  size_t tile_j)\n{\n  if (threadpool == NULL) {\n    /* No thread pool provided: execute function sequentially on the calling thread */\n    for (size_t i = 0; i < range_i; i += tile_i) {\n      for (size_t j = 0; j < range_j; j += tile_j) {\n        function(argument, i, j, min(range_i - i, tile_i), min(range_j - j, tile_j));\n      }\n    }\n  } else {\n    /* Execute in parallel on the thread pool using linearized index */\n    const size_t tile_range_i = divide_round_up(range_i, tile_i);\n    const size_t tile_range_j = divide_round_up(range_j, tile_j);\n    DCHECK_LE(tile_range_i * tile_range_j, (size_t) std::numeric_limits<int>::max());\n    struct compute_2d_tiled_context context = {\n      .function = function,\n      .argument = argument,\n      .tile_range_j = caffe2::FixedDivisor<int>(tile_range_j),\n      .range_i = range_i,\n      .range_j = range_j,\n      .tile_i = tile_i,\n      .tile_j = tile_j\n    };\n    pthreadpool_compute_1d(threadpool, (pthreadpool_function_1d_t) compute_2d_tiled, &context, tile_range_i * tile_range_j);\n  }\n}\n"
  },
  {
    "path": "caffe2/utils/threadpool/pthreadpool.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// pthreadpool header from https://github.com/Maratyszcza/pthreadpool\n// for NNPACK\n#ifndef CAFFE2_UTILS_PTHREADPOOL_H_\n#define CAFFE2_UTILS_PTHREADPOOL_H_\n\n#include \"ThreadPoolCommon.h\"\n\n\n#include <stddef.h> // for size_t\n\ntypedef struct pthreadpool* pthreadpool_t;\n\ntypedef void (*pthreadpool_function_1d_t)(void*, size_t);\ntypedef void (*pthreadpool_function_1d_tiled_t)(void*, size_t, size_t);\ntypedef void (*pthreadpool_function_2d_t)(void*, size_t, size_t);\ntypedef void (*pthreadpool_function_2d_tiled_t)(void*, size_t, size_t, size_t, size_t);\ntypedef void (*pthreadpool_function_3d_t)(void*, size_t, size_t, size_t);\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * Creates a thread pool with the specified number of threads.\n *\n * @param[in]  threads_count  The number of threads in the thread pool.\n *    A value of 0 has special interpretation: it creates a thread for each\n *    processor core available in the system.\n *\n * @returns  A pointer to an opaque thread pool object.\n *    On error the function returns NULL and sets errno accordingly.\n */\npthreadpool_t pthreadpool_create(size_t threads_count);\n\n/**\n * Queries the number of threads in a thread pool.\n *\n * @param[in]  threadpool  The thread pool to query.\n *\n * @returns  The number of threads in the thread pool.\n */\nsize_t pthreadpool_get_threads_count(pthreadpool_t threadpool);\n\n\n/**\n * Processes items in parallel using threads from a thread pool.\n *\n * When the call returns, all items have been processed and the thread pool is\n * ready for a new task.\n *\n * @note If multiple threads call this function with the same thread pool, the\n *    calls are serialized.\n *\n * @param[in]  threadpool  The thread pool to use for parallelisation.\n * @param[in]  function    The function to call for each item.\n * @param[in]  argument    The first argument passed to the @a function.\n * @param[in]  items       The number of items to process. The @a function\n *    will be called once for each item.\n */\nvoid pthreadpool_compute_1d(\n    pthreadpool_t threadpool,\n    pthreadpool_function_1d_t function,\n    void* argument,\n    size_t range);\n\nvoid pthreadpool_compute_1d_tiled(\n    pthreadpool_t threadpool,\n    pthreadpool_function_1d_tiled_t function,\n    void* argument,\n    size_t range,\n    size_t tile);\n\nvoid pthreadpool_compute_2d(\n    pthreadpool_t threadpool,\n    pthreadpool_function_2d_t function,\n    void* argument,\n    size_t range_i,\n    size_t range_j);\n\nvoid pthreadpool_compute_2d_tiled(\n    pthreadpool_t threadpool,\n    pthreadpool_function_2d_tiled_t function,\n    void* argument,\n    size_t range_i,\n    size_t range_j,\n    size_t tile_i,\n    size_t tile_j);\n\n/**\n * Terminates threads in the thread pool and releases associated resources.\n *\n * @warning  Accessing the thread pool after a call to this function constitutes\n *    undefined behaviour and may cause data corruption.\n *\n * @param[in,out]  threadpool  The thread pool to destroy.\n */\nvoid pthreadpool_destroy(pthreadpool_t threadpool);\n\n#ifdef __cplusplus\n} /* extern \"C\" */\n#endif\n\n#endif // CAFFE2_UTILS_PTHREADPOOL_H_\n"
  },
  {
    "path": "caffe2/utils/threadpool/pthreadpool_impl.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/utils/threadpool/pthreadpool.h\"\n#include \"caffe2/utils/threadpool/pthreadpool_impl.h\"\n#include \"caffe2/utils/threadpool/ThreadPool.h\"\n\n\n//\n// External API\n//\n\nvoid pthreadpool_compute_1d(struct pthreadpool* threadpool,\n                            pthreadpool_function_1d_t function,\n                            void* argument,\n                            size_t range) {\n    threadpool->pool_->run(\n      [function, argument](int threadId, size_t workId) {\n        function(argument, workId);\n      },\n      range);\n}\n\nsize_t pthreadpool_get_threads_count(struct pthreadpool* threadpool) {\n  return threadpool->pool_->getNumThreads();\n}\n"
  },
  {
    "path": "caffe2/utils/threadpool/pthreadpool_impl.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_PTHREADPOOL_IMPL_H_\n#define CAFFE2_UTILS_PTHREADPOOL_IMPL_H_\n\n#include \"ThreadPoolCommon.h\"\n\n\nnamespace caffe2 {\n\nclass ThreadPool;\n\n} // namespace caffe2\n\nextern \"C\" {\n\n// Wrapper for the caffe2 threadpool for the usage of NNPACK\nstruct pthreadpool {\n  pthreadpool(caffe2::ThreadPool* pool) : pool_(pool) {}\n  caffe2::ThreadPool* pool_;\n};\n\n} // extern \"C\"\n\n#endif  // CAFFE2_UTILS_PTHREADPOOL_IMPL_H_\n"
  },
  {
    "path": "caffe2/utils/zmq_helper.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_UTILS_ZMQ_HELPER_H_\n#define CAFFE2_UTILS_ZMQ_HELPER_H_\n\n#include <zmq.h>\n\n#include \"caffe2/core/logging.h\"\n\nnamespace caffe2 {\n\nclass ZmqContext {\n public:\n  explicit ZmqContext(int io_threads) : ptr_(zmq_ctx_new()) {\n    CAFFE_ENFORCE(ptr_ != nullptr, \"Failed to create zmq context.\");\n    int rc = zmq_ctx_set(ptr_, ZMQ_IO_THREADS, io_threads);\n    CAFFE_ENFORCE_EQ(rc, 0);\n    rc = zmq_ctx_set(ptr_, ZMQ_MAX_SOCKETS, ZMQ_MAX_SOCKETS_DFLT);\n    CAFFE_ENFORCE_EQ(rc, 0);\n  }\n  ~ZmqContext() {\n    int rc = zmq_ctx_destroy(ptr_);\n    CAFFE_ENFORCE_EQ(rc, 0);\n  }\n\n  void* ptr() { return ptr_; }\n\n private:\n  void* ptr_;\n\n  DISABLE_COPY_AND_ASSIGN(ZmqContext);\n};\n\nclass ZmqMessage {\n public:\n  ZmqMessage() {\n    int rc = zmq_msg_init(&msg_);\n    CAFFE_ENFORCE_EQ(rc, 0);\n  }\n\n  ~ZmqMessage() {\n    int rc = zmq_msg_close(&msg_);\n    CAFFE_ENFORCE_EQ(rc, 0);\n  }\n\n  zmq_msg_t* msg() { return &msg_; }\n\n  void* data() { return zmq_msg_data(&msg_); }\n  size_t size() { return zmq_msg_size(&msg_); }\n\n private:\n  zmq_msg_t msg_;\n  DISABLE_COPY_AND_ASSIGN(ZmqMessage);\n};\n\nclass ZmqSocket {\n public:\n  explicit ZmqSocket(int type)\n      : context_(1), ptr_(zmq_socket(context_.ptr(), type)) {\n    CAFFE_ENFORCE(ptr_ != nullptr, \"Faild to create zmq socket.\");\n  }\n\n  ~ZmqSocket() {\n    int rc = zmq_close(ptr_);\n    CAFFE_ENFORCE_EQ(rc, 0);\n  }\n\n  void Bind(const string& addr) {\n    int rc = zmq_bind(ptr_, addr.c_str());\n    CAFFE_ENFORCE_EQ(rc, 0);\n  }\n\n  void Unbind(const string& addr) {\n    int rc = zmq_unbind(ptr_, addr.c_str());\n    CAFFE_ENFORCE_EQ(rc, 0);\n  }\n\n  void Connect(const string& addr) {\n    int rc = zmq_connect(ptr_, addr.c_str());\n    CAFFE_ENFORCE_EQ(rc, 0);\n  }\n\n  void Disconnect(const string& addr) {\n    int rc = zmq_disconnect(ptr_, addr.c_str());\n    CAFFE_ENFORCE_EQ(rc, 0);\n  }\n\n  int Send(const string& msg, int flags) {\n    int nbytes = zmq_send(ptr_, msg.c_str(), msg.size(), flags);\n    if (nbytes) {\n      return nbytes;\n    } else if (zmq_errno() == EAGAIN) {\n      return 0;\n    } else {\n      LOG(FATAL) << \"Cannot send zmq message. Error number: \"\n                      << zmq_errno();\n      return 0;\n    }\n  }\n\n  int SendTillSuccess(const string& msg, int flags) {\n    CAFFE_ENFORCE(msg.size(), \"You cannot send an empty message.\");\n    int nbytes = 0;\n    do {\n      nbytes = Send(msg, flags);\n    } while (nbytes == 0);\n    return nbytes;\n  }\n\n  int Recv(ZmqMessage* msg) {\n    int nbytes = zmq_msg_recv(msg->msg(), ptr_, 0);\n    if (nbytes >= 0) {\n      return nbytes;\n    } else if (zmq_errno() == EAGAIN || zmq_errno() == EINTR) {\n      return 0;\n    } else {\n      LOG(FATAL) << \"Cannot receive zmq message. Error number: \"\n                      << zmq_errno();\n      return 0;\n    }\n  }\n\n  int RecvTillSuccess(ZmqMessage* msg) {\n    int nbytes = 0;\n    do {\n      nbytes = Recv(msg);\n    } while (nbytes == 0);\n    return nbytes;\n  }\n\n private:\n  ZmqContext context_;\n  void* ptr_;\n};\n\n}  // namespace caffe2\n\n\n#endif  // CAFFE2_UTILS_ZMQ_HELPER_H_\n"
  },
  {
    "path": "caffe2/video/CMakeLists.txt",
    "content": "if(USE_OPENCV AND OpenCV_FOUND AND USE_FFMPEG AND FFMPEG_FOUND)\n        message(STATUS \"Including video processing operators\")\n  # ---[ GPU files\n  # ------[ cuDNN\n  file(GLOB tmp *_cudnn.cc)\n  set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n  # ------[ general GPU\n  file(GLOB tmp *_gpu.cc)\n  set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n  # ------[ CUDA sources\n  file(GLOB tmp *.cu)\n  set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} ${tmp})\n  # exclude test files\n  file(GLOB tmp *_test.cc)\n  exclude(Caffe2_GPU_SRCS \"${Caffe2_GPU_SRCS}\" ${tmp})\n\n  # ---[ CPU files.\n  file(GLOB tmp *.cc)\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} ${tmp})\n  # exclude test files and gpu files\n  file(GLOB tmp *_test.cc)\n  exclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${tmp})\n  exclude(Caffe2_CPU_SRCS \"${Caffe2_CPU_SRCS}\" ${Caffe2_GPU_SRCS})\n\n  # ---[ GPU test files\n  file(GLOB tmp *_gpu_test.cc)\n  set(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} ${tmp})\n\n  # ---[ CPU test files\n  file(GLOB tmp *_test.cc)\n  set(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} ${tmp})\n  exclude(Caffe2_CPU_TEST_SRCS \"${Caffe2_CPU_TEST_SRCS}\"\n    ${Caffe2_GPU_TEST_SRCS})\n\n  # ---[ Send the lists to the parent scope.\n  set(Caffe2_CPU_SRCS ${Caffe2_CPU_SRCS} PARENT_SCOPE)\n  set(Caffe2_GPU_SRCS ${Caffe2_GPU_SRCS} PARENT_SCOPE)\n  set(Caffe2_CPU_TEST_SRCS ${Caffe2_CPU_TEST_SRCS} PARENT_SCOPE)\n  set(Caffe2_GPU_TEST_SRCS ${Caffe2_GPU_TEST_SRCS} PARENT_SCOPE)\nelse()\n        message(STATUS \"Excluding video processing operators due to no opencv\")\nendif()\n"
  },
  {
    "path": "caffe2/video/optical_flow.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <caffe2/video/optical_flow.h>\n\nnamespace caffe2 {\n\nvoid OpticalFlowExtractor(\n    const cv::Mat& prev_gray,\n    const cv::Mat& curr_gray,\n    const int flow_alg_type,\n    cv::Mat& flow) {\n  cv::Ptr<cv::DualTVL1OpticalFlow> tvl1 = cv::DualTVL1OpticalFlow::create();\n  switch (flow_alg_type) {\n    case FLowAlgType::FarnebackOpticalFlow:\n      cv::calcOpticalFlowFarneback(\n          prev_gray,\n          curr_gray,\n          flow,\n          std::sqrt(2) / 2.0,\n          5,\n          10,\n          2,\n          7,\n          1.5,\n          cv::OPTFLOW_FARNEBACK_GAUSSIAN);\n      break;\n    case FLowAlgType::DensePyrLKOpticalFlow:\n      LOG(ERROR) << \"DensePyrLKOpticalFlow only has sparse version on CPU\";\n      break;\n    case FLowAlgType::BroxOpticalFlow:\n      LOG(ERROR) << \"BroxOpticalFlow on CPU is not available\";\n      break;\n    case FLowAlgType::OpticalFlowDual_TVL1:\n      tvl1->calc(prev_gray, curr_gray, flow);\n      break;\n    default:\n      LOG(ERROR) << \"Unsupported optical flow type \" << flow_alg_type;\n      break;\n  }\n}\n\nvoid MergeOpticalFlow(cv::Mat& prev_flow, const cv::Mat& curr_flow) {\n  const int rows = prev_flow.rows;\n  const int cols = prev_flow.cols;\n\n  // merge two optical flows into one\n  for (int y = 0; y < rows; y++) {\n    for (int x = 0; x < cols; x++) {\n      cv::Point2f u = prev_flow.at<cv::Point2f>(y, x);\n      // get the new location\n      int x_new = std::min(cols - 1, std::max(0, cvRound(u.x + x)));\n      int y_new = std::min(rows - 1, std::max(0, cvRound(u.y + y)));\n      cv::Point2f u_new = curr_flow.at<cv::Point2f>(y_new, x_new);\n\n      // update the flow\n      prev_flow.at<cv::Point2f>(y, x) += u_new;\n    }\n  }\n}\n\nvoid MultiFrameOpticalFlowExtractor(\n    const std::vector<cv::Mat>& grays,\n    const int optical_flow_alg_type,\n    cv::Mat& flow) {\n  int num_frames = grays.size();\n  CAFFE_ENFORCE_GE(num_frames, 2, \"need at least 2 frames!\");\n\n  // compute optical flow for every two frames\n  std::vector<cv::Mat> flows;\n  for (int i = 0; i < num_frames - 1; i++) {\n    cv::Mat tmp;\n    OpticalFlowExtractor(grays[i], grays[i + 1], optical_flow_alg_type, tmp);\n    flows.push_back(tmp);\n  }\n\n  flows[0].copyTo(flow);\n  // aggregate optical flow across multiple frame\n  for (int i = 1; i < num_frames - 1; i++) {\n    MergeOpticalFlow(flow, flows[i]);\n  }\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/video/optical_flow.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_VIDEO_OPTICAL_FLOW_H_\n#define CAFFE2_VIDEO_OPTICAL_FLOW_H_\n\n#include <opencv2/core.hpp>\n#include <opencv2/highgui.hpp>\n#include <opencv2/opencv.hpp>\n#include <opencv2/video.hpp>\n\n#include <caffe2/core/logging.h>\n\nnamespace caffe2 {\n\n// Four different types of optical flow algorithms supported;\n// BroxOpticalFlow doesn't have a CPU version;\n// DensePyrLKOpticalFlow only has sparse CPU version;\nenum FLowAlgType {\n  FarnebackOpticalFlow = 0,\n  DensePyrLKOpticalFlow = 1,\n  BroxOpticalFlow = 2,\n  OpticalFlowDual_TVL1 = 3,\n};\n\n// Define different types of optical flow data type\n// 0: original two channel optical flow\n// 1: three channel optical flow with magnitude as the third channel\n// 2: two channel optical flow + one channel gray\n// 3: two channel optical flow + three channel rgb\nenum FlowDataType {\n  Flow2C = 0,\n  Flow3C = 1,\n  FlowWithGray = 2,\n  FlowWithRGB = 3,\n};\n\nvoid OpticalFlowExtractor(\n    const cv::Mat& prev_gray,\n    const cv::Mat& curr_gray,\n    const int optical_flow_alg_type,\n    cv::Mat& flow);\n\nvoid MergeOpticalFlow(cv::Mat& prev_flow, const cv::Mat& curr_flow);\n\nvoid MultiFrameOpticalFlowExtractor(\n    const std::vector<cv::Mat>& grays,\n    const int optical_flow_alg_type,\n    cv::Mat& flow);\n\n} // namespace caffe2\n\n#endif // CAFFE2_VIDEO_OPTICAL_FLOW_H_\n"
  },
  {
    "path": "caffe2/video/video_decoder.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <caffe2/video/video_decoder.h>\n#include <caffe2/core/logging.h>\n\n#include <stdio.h>\n#include <mutex>\n#include <random>\n\nextern \"C\" {\n#include <libavcodec/avcodec.h>\n#include <libavformat/avformat.h>\n#include <libavutil/log.h>\n#include <libswresample/swresample.h>\n#include <libswscale/swscale.h>\n}\n\nnamespace caffe2 {\n\nVideoDecoder::VideoDecoder() {\n  static bool gInitialized = false;\n  static std::mutex gMutex;\n  std::unique_lock<std::mutex> lock(gMutex);\n  if (!gInitialized) {\n    av_register_all();\n    avcodec_register_all();\n    avformat_network_init();\n    gInitialized = true;\n  }\n}\n\nvoid VideoDecoder::ResizeAndKeepAspectRatio(\n    const int origHeight,\n    const int origWidth,\n    const int heightMin,\n    const int widthMin,\n    int& outHeight,\n    int& outWidth) {\n  float min_aspect = (float)heightMin / (float)widthMin;\n  float video_aspect = (float)origHeight / (float)origWidth;\n  if (video_aspect >= min_aspect) {\n    outWidth = widthMin;\n    outHeight = (int)ceil(video_aspect * outWidth);\n  } else {\n    outHeight = heightMin;\n    outWidth = (int)ceil(outHeight / video_aspect);\n  }\n}\n\nvoid VideoDecoder::decodeLoop(\n    const string& videoName,\n    VideoIOContext& ioctx,\n    const Params& params,\n    const int start_frm,\n    std::vector<std::unique_ptr<DecodedFrame>>& sampledFrames) {\n  AVPixelFormat pixFormat = params.pixelFormat_;\n  AVFormatContext* inputContext = avformat_alloc_context();\n  AVStream* videoStream_ = nullptr;\n  AVCodecContext* videoCodecContext_ = nullptr;\n  AVFrame* videoStreamFrame_ = nullptr;\n  AVPacket packet;\n  av_init_packet(&packet); // init packet\n  SwsContext* scaleContext_ = nullptr;\n\n  try {\n    inputContext->pb = ioctx.get_avio();\n    inputContext->flags |= AVFMT_FLAG_CUSTOM_IO;\n    int ret = 0;\n\n    // Determining the input format:\n    int probeSz = 32 * 1024 + AVPROBE_PADDING_SIZE;\n    DecodedFrame::AvDataPtr probe((uint8_t*)av_malloc(probeSz));\n\n    memset(probe.get(), 0, probeSz);\n    int len = ioctx.read(probe.get(), probeSz - AVPROBE_PADDING_SIZE);\n    if (len < probeSz - AVPROBE_PADDING_SIZE) {\n      LOG(ERROR) << \"Insufficient data to determine video format\";\n      return;\n    }\n\n    // seek back to start of stream\n    ioctx.seek(0, SEEK_SET);\n\n    unique_ptr<AVProbeData> probeData(new AVProbeData());\n    probeData->buf = probe.get();\n    probeData->buf_size = len;\n    probeData->filename = \"\";\n    // Determine the input-format:\n    inputContext->iformat = av_probe_input_format(probeData.get(), 1);\n\n    ret = avformat_open_input(&inputContext, \"\", nullptr, nullptr);\n    if (ret < 0) {\n      LOG(ERROR) << \"Unable to open stream \" << ffmpegErrorStr(ret);\n      return;\n    }\n\n    ret = avformat_find_stream_info(inputContext, nullptr);\n    if (ret < 0) {\n      LOG(ERROR) << \"Unable to find stream info in \" << videoName << \" \"\n                 << ffmpegErrorStr(ret);\n      return;\n    }\n\n    // Decode the first video stream\n    int videoStreamIndex_ = params.streamIndex_;\n    if (videoStreamIndex_ == -1) {\n      for (int i = 0; i < inputContext->nb_streams; i++) {\n        auto stream = inputContext->streams[i];\n        if (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) {\n          videoStreamIndex_ = i;\n          videoStream_ = stream;\n          break;\n        }\n      }\n    }\n\n    if (videoStream_ == nullptr) {\n      LOG(ERROR) << \"Unable to find video stream in \" << videoName << \" \"\n                 << ffmpegErrorStr(ret);\n      return;\n    }\n\n    // Initialize codec\n    AVDictionary* opts = nullptr;\n    videoCodecContext_ = videoStream_->codec;\n    try {\n      ret = avcodec_open2(\n          videoCodecContext_,\n          avcodec_find_decoder(videoCodecContext_->codec_id),\n          &opts);\n    } catch (const std::exception&) {\n      LOG(ERROR) << \"Exception during open video codec\";\n      return;\n    }\n\n    if (ret < 0) {\n      LOG(ERROR) << \"Cannot open video codec : \"\n                 << videoCodecContext_->codec->name;\n      return;\n    }\n\n    // Calculate if we need to rescale the frames\n    int origWidth = videoCodecContext_->width;\n    int origHeight = videoCodecContext_->height;\n    int outWidth = origWidth;\n    int outHeight = origHeight;\n\n    if (params.video_res_type_ == VideoResType::ORIGINAL_RES) {\n      // if the original resolution is too low,\n      // make its size at least (crop_height, crop_width)\n      if (params.crop_width_ > origWidth || params.crop_height_ > origHeight) {\n        ResizeAndKeepAspectRatio(\n            origHeight,\n            origWidth,\n            params.crop_height_,\n            params.crop_width_,\n            outHeight,\n            outWidth);\n      }\n    } else if (\n        params.video_res_type_ == VideoResType::USE_MINIMAL_WIDTH_HEIGHT) {\n      // resize the image to be at least\n      // (height_min, width_min) resolution while keep the aspect ratio\n      ResizeAndKeepAspectRatio(\n          origHeight,\n          origWidth,\n          params.height_min_,\n          params.width_min_,\n          outHeight,\n          outWidth);\n    } else if (params.video_res_type_ == VideoResType::USE_WIDTH_HEIGHT) {\n      // resize the image to the predefined\n      // resolution and ignore the aspect ratio\n      outWidth = params.scale_w_;\n      outHeight = params.scale_h_;\n    } else {\n      LOG(ERROR) << \"Unknown video_res_type: \" << params.video_res_type_;\n    }\n\n    // Make sure that we have a valid format\n    CAFFE_ENFORCE_NE(videoCodecContext_->pix_fmt, AV_PIX_FMT_NONE);\n\n    // Create a scale context\n    scaleContext_ = sws_getContext(\n        videoCodecContext_->width,\n        videoCodecContext_->height,\n        videoCodecContext_->pix_fmt,\n        outWidth,\n        outHeight,\n        pixFormat,\n        SWS_FAST_BILINEAR,\n        nullptr,\n        nullptr,\n        nullptr);\n\n    // Getting video meta data\n    VideoMeta videoMeta;\n    videoMeta.codec_type = videoCodecContext_->codec_type;\n    videoMeta.width = outWidth;\n    videoMeta.height = outHeight;\n    videoMeta.pixFormat = pixFormat;\n    videoMeta.fps = av_q2d(videoStream_->avg_frame_rate);\n\n    // If sampledFrames is not empty, empty it\n    if (sampledFrames.size() > 0) {\n      sampledFrames.clear();\n    }\n\n    if (params.intervals_.size() == 0) {\n      LOG(ERROR) << \"Empty sampling intervals.\";\n      return;\n    }\n\n    std::vector<SampleInterval>::const_iterator itvlIter =\n        params.intervals_.begin();\n    if (itvlIter->timestamp != 0) {\n      LOG(ERROR) << \"Sampling interval starting timestamp is not zero.\";\n    }\n\n    double currFps = itvlIter->fps;\n    if (currFps < 0 && currFps != SpecialFps::SAMPLE_ALL_FRAMES &&\n        currFps != SpecialFps::SAMPLE_TIMESTAMP_ONLY) {\n      // fps must be 0, -1, -2 or > 0\n      LOG(ERROR) << \"Invalid sampling fps.\";\n    }\n\n    double prevTimestamp = itvlIter->timestamp;\n    itvlIter++;\n    if (itvlIter != params.intervals_.end() &&\n        prevTimestamp >= itvlIter->timestamp) {\n      LOG(ERROR) << \"Sampling interval timestamps must be strictly ascending.\";\n    }\n\n    double lastFrameTimestamp = -1.0;\n    // Initialize frame and packet.\n    // These will be reused across calls.\n    videoStreamFrame_ = av_frame_alloc();\n\n    // frame index in video stream\n    int frameIndex = -1;\n    // frame index of outputed frames\n    int outputFrameIndex = -1;\n\n    /* identify the starting point from where we must start decoding */\n    std::mt19937 meta_randgen(time(nullptr));\n    int start_ts = -1;\n    bool mustDecodeAll = false;\n    if (videoStream_->duration > 0 && videoStream_->nb_frames > 0) {\n      /* we have a valid duration and nb_frames. We can safely\n       * detect an intermediate timestamp to start decoding from. */\n\n      // leave a margin of 10 frames to take in to account the error\n      // from av_seek_frame\n      int margin =\n          int(ceil((10 * videoStream_->duration) / (videoStream_->nb_frames)));\n      // if we need to do temporal jittering\n      if (params.decode_type_ == DecodeType::DO_TMP_JITTER) {\n        /* estimate the average duration for the required # of frames */\n        double maxFramesDuration =\n            (videoStream_->duration * params.num_of_required_frame_) /\n            (videoStream_->nb_frames);\n        int ts1 = 0;\n        int ts2 = videoStream_->duration - int(ceil(maxFramesDuration));\n        ts2 = ts2 > 0 ? ts2 : 0;\n        // pick a random timestamp between ts1 and ts2. ts2 is selected such\n        // that you have enough frames to satisfy the required # of frames.\n        start_ts = std::uniform_int_distribution<>(ts1, ts2)(meta_randgen);\n        // seek a frame at start_ts\n        ret = av_seek_frame(\n            inputContext,\n            videoStreamIndex_,\n            std::max(0, start_ts - margin),\n            AVSEEK_FLAG_BACKWARD);\n\n        // if we need to decode from the start_frm\n      } else if (params.decode_type_ == DecodeType::USE_START_FRM) {\n        start_ts = int(floor(\n            (videoStream_->duration * start_frm) / (videoStream_->nb_frames)));\n        // seek a frame at start_ts\n        ret = av_seek_frame(\n            inputContext,\n            videoStreamIndex_,\n            std::max(0, start_ts - margin),\n            AVSEEK_FLAG_BACKWARD);\n      } else {\n        mustDecodeAll = true;\n      }\n\n      if (ret < 0) {\n        LOG(ERROR) << \"Unable to decode from a random start point\";\n        /* fall back to default decoding of all frames from start */\n        av_seek_frame(inputContext, videoStreamIndex_, 0, AVSEEK_FLAG_BACKWARD);\n        mustDecodeAll = true;\n      }\n    } else {\n      /* we do not have the necessary metadata to selectively decode frames.\n       * Decode all frames as we do in the default case */\n      LOG(INFO) << \" Decoding all frames as we do not have suffiecient\"\n                   \" metadata for selective decoding.\";\n      mustDecodeAll = true;\n    }\n\n    int gotPicture = 0;\n    int eof = 0;\n    int selectiveDecodedFrames = 0;\n\n    int maxFrames = (params.decode_type_ == DecodeType::DO_UNIFORM_SMP)\n        ? MAX_DECODING_FRAMES\n        : params.num_of_required_frame_;\n    // There is a delay between reading packets from the\n    // transport and getting decoded frames back.\n    // Therefore, after EOF, continue going while\n    // the decoder is still giving us frames.\n    while ((!eof || gotPicture) &&\n           /* either you must decode all frames or decode upto maxFrames\n            * based on status of the mustDecodeAll flag */\n           (mustDecodeAll ||\n            ((!mustDecodeAll) && (selectiveDecodedFrames < maxFrames)))) {\n      try {\n        if (!eof) {\n          ret = av_read_frame(inputContext, &packet);\n\n          if (ret == AVERROR(EAGAIN)) {\n            av_free_packet(&packet);\n            continue;\n          }\n          // Interpret any other error as EOF\n          if (ret < 0) {\n            eof = 1;\n            av_free_packet(&packet);\n            continue;\n          }\n\n          // Ignore packets from other streams\n          if (packet.stream_index != videoStreamIndex_) {\n            av_free_packet(&packet);\n            continue;\n          }\n        }\n\n        ret = avcodec_decode_video2(\n            videoCodecContext_, videoStreamFrame_, &gotPicture, &packet);\n        if (ret < 0) {\n          LOG(ERROR) << \"Error decoding video frame : \" << ffmpegErrorStr(ret);\n        }\n\n        try {\n          // Nothing to do without a picture\n          if (!gotPicture) {\n            av_free_packet(&packet);\n            continue;\n          }\n          frameIndex++;\n\n          double frame_ts =\n              av_frame_get_best_effort_timestamp(videoStreamFrame_);\n          double timestamp = frame_ts * av_q2d(videoStream_->time_base);\n\n          if ((frame_ts >= start_ts && !mustDecodeAll) || mustDecodeAll) {\n            /* process current frame if:\n             * 1) We are not doing selective decoding and mustDecodeAll\n             *    OR\n             * 2) We are doing selective decoding and current frame\n             *   timestamp is >= start_ts from where we start selective\n             *   decoding*/\n            // if reaching the next interval, update the current fps\n            // and reset lastFrameTimestamp so the current frame could be\n            // sampled (unless fps == SpecialFps::SAMPLE_NO_FRAME)\n            if (itvlIter != params.intervals_.end() &&\n                timestamp >= itvlIter->timestamp) {\n              lastFrameTimestamp = -1.0;\n              currFps = itvlIter->fps;\n              prevTimestamp = itvlIter->timestamp;\n              itvlIter++;\n              if (itvlIter != params.intervals_.end() &&\n                  prevTimestamp >= itvlIter->timestamp) {\n                LOG(ERROR)\n                    << \"Sampling interval timestamps must be strictly ascending.\";\n              }\n            }\n\n            // keyFrame will bypass all checks on fps sampling settings\n            bool keyFrame = params.keyFrames_ && videoStreamFrame_->key_frame;\n            if (!keyFrame) {\n              // if fps == SpecialFps::SAMPLE_NO_FRAME (0), don't sample at all\n              if (currFps == SpecialFps::SAMPLE_NO_FRAME) {\n                av_free_packet(&packet);\n                continue;\n              }\n\n              // fps is considered reached in the following cases:\n              // 1. lastFrameTimestamp < 0 - start of a new interval\n              //    (or first frame)\n              // 2. currFps == SpecialFps::SAMPLE_ALL_FRAMES (-1) - sample every\n              //    frame\n              // 3. timestamp - lastFrameTimestamp has reached target fps and\n              //    currFps > 0 (not special fps setting)\n              // different modes for fps:\n              // SpecialFps::SAMPLE_NO_FRAMES (0):\n              //     disable fps sampling, no frame sampled at all\n              // SpecialFps::SAMPLE_ALL_FRAMES (-1):\n              //     unlimited fps sampling, will sample at native video fps\n              // SpecialFps::SAMPLE_TIMESTAMP_ONLY (-2):\n              //     disable fps sampling, but will get the frame at specific\n              //     timestamp\n              // others (> 0): decoding at the specified fps\n              bool fpsReached = lastFrameTimestamp < 0 ||\n                  currFps == SpecialFps::SAMPLE_ALL_FRAMES ||\n                  (currFps > 0 &&\n                   timestamp >= lastFrameTimestamp + (1 / currFps));\n\n              if (!fpsReached) {\n                av_free_packet(&packet);\n                continue;\n              }\n            }\n\n            lastFrameTimestamp = timestamp;\n\n            outputFrameIndex++;\n            if (params.maximumOutputFrames_ != -1 &&\n                outputFrameIndex >= params.maximumOutputFrames_) {\n              // enough frames\n              av_free_packet(&packet);\n              break;\n            }\n\n            AVFrame* rgbFrame = av_frame_alloc();\n            if (!rgbFrame) {\n              LOG(ERROR) << \"Error allocating AVframe\";\n            }\n\n            try {\n              // Determine required buffer size and allocate buffer\n              int numBytes = avpicture_get_size(pixFormat, outWidth, outHeight);\n              DecodedFrame::AvDataPtr buffer(\n                  (uint8_t*)av_malloc(numBytes * sizeof(uint8_t)));\n\n              int size = avpicture_fill(\n                  (AVPicture*)rgbFrame,\n                  buffer.get(),\n                  pixFormat,\n                  outWidth,\n                  outHeight);\n\n              sws_scale(\n                  scaleContext_,\n                  videoStreamFrame_->data,\n                  videoStreamFrame_->linesize,\n                  0,\n                  videoCodecContext_->height,\n                  rgbFrame->data,\n                  rgbFrame->linesize);\n\n              unique_ptr<DecodedFrame> frame = make_unique<DecodedFrame>();\n              frame->width_ = outWidth;\n              frame->height_ = outHeight;\n              frame->data_ = move(buffer);\n              frame->size_ = size;\n              frame->index_ = frameIndex;\n              frame->outputFrameIndex_ = outputFrameIndex;\n              frame->timestamp_ = timestamp;\n              frame->keyFrame_ = videoStreamFrame_->key_frame;\n\n              sampledFrames.push_back(move(frame));\n              selectiveDecodedFrames++;\n              av_frame_free(&rgbFrame);\n            } catch (const std::exception&) {\n              av_frame_free(&rgbFrame);\n            }\n          }\n          av_frame_unref(videoStreamFrame_);\n        } catch (const std::exception&) {\n          av_frame_unref(videoStreamFrame_);\n        }\n\n        av_free_packet(&packet);\n      } catch (const std::exception&) {\n        av_free_packet(&packet);\n      }\n    } // of while loop\n\n    // free all stuffs\n    sws_freeContext(scaleContext_);\n    av_packet_unref(&packet);\n    av_frame_free(&videoStreamFrame_);\n    avcodec_close(videoCodecContext_);\n    avformat_close_input(&inputContext);\n    avformat_free_context(inputContext);\n  } catch (const std::exception&) {\n    // In case of decoding error\n    // free all stuffs\n    sws_freeContext(scaleContext_);\n    av_packet_unref(&packet);\n    av_frame_free(&videoStreamFrame_);\n    avcodec_close(videoCodecContext_);\n    avformat_close_input(&inputContext);\n    avformat_free_context(inputContext);\n  }\n}\n\nvoid VideoDecoder::decodeMemory(\n    const char* buffer,\n    const int size,\n    const Params& params,\n    const int start_frm,\n    std::vector<std::unique_ptr<DecodedFrame>>& sampledFrames) {\n  VideoIOContext ioctx(buffer, size);\n  decodeLoop(string(\"Memory Buffer\"), ioctx, params, start_frm, sampledFrames);\n}\n\nvoid VideoDecoder::decodeFile(\n    const string& file,\n    const Params& params,\n    const int start_frm,\n    std::vector<std::unique_ptr<DecodedFrame>>& sampledFrames) {\n  VideoIOContext ioctx(file);\n  decodeLoop(file, ioctx, params, start_frm, sampledFrames);\n}\n\nstring VideoDecoder::ffmpegErrorStr(int result) {\n  std::array<char, 128> buf;\n  av_strerror(result, buf.data(), buf.size());\n  return string(buf.data());\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/video/video_decoder.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_VIDEO_VIDEO_DECODER_H_\n#define CAFFE2_VIDEO_VIDEO_DECODER_H_\n\n#include <caffe2/core/logging.h>\n#include <stdio.h>\n#include <memory>\n#include <string>\n#include <vector>\n\nextern \"C\" {\n#include <libavformat/avformat.h>\n#include <libavformat/avio.h>\n}\n\nnamespace caffe2 {\n\n#define VIO_BUFFER_SZ 32768\n#define MAX_DECODING_FRAMES 10000\n\n// enum to specify 3 special fps sampling behaviors:\n// 0: disable fps sampling, no frame sampled at all\n// -1: unlimited fps sampling, will sample at native video fps\n// -2: disable fps sampling, but will get the frame at specific timestamp\nenum SpecialFps {\n  SAMPLE_NO_FRAME = 0,\n  SAMPLE_ALL_FRAMES = -1,\n  SAMPLE_TIMESTAMP_ONLY = -2,\n};\n\n// three different types of resolution when decoding the video\n// 0: resize to width x height and ignore the aspect ratio;\n// 1: resize to make size at least (width x height) and keep the aspect ratio;\n// 2: using the original resolution of the video; if resolution\n//    is smaller than crop_height x crop_width, resize to ensure\n//    new height >= crop_height and new width >= crop_width\n//    and keep the aspect ratio;\nenum VideoResType {\n  USE_WIDTH_HEIGHT = 0,\n  USE_MINIMAL_WIDTH_HEIGHT = 1,\n  ORIGINAL_RES = 2,\n};\n\n// three different types of decoding behavior are supported\n// 0: do temporal jittering to sample a random clip from the video\n// 1: sample a clip from a given starting frame\n// 2: uniformly sample multiple clips from the video;\nenum DecodeType {\n  DO_TMP_JITTER = 0,\n  DO_UNIFORM_SMP = 1,\n  USE_START_FRM = 2,\n};\n\n// sampling interval for fps starting at specified timestamp\n// use enum SpecialFps to set special fps decoding behavior\n// note sampled fps will not always accurately follow the target fps,\n// because sampled frame has to snap to actual frame timestamp,\n// e.g. video fps = 25, sample fps = 4 will sample every 0.28s, not 0.25\n// video fps = 25, sample fps = 5 will sample every 0.24s, not 0.2,\n// because of floating-point division accuracy (1 / 5.0 is not exactly 0.2)\nstruct SampleInterval {\n  double timestamp;\n  double fps;\n  SampleInterval() : timestamp(-1), fps(SpecialFps::SAMPLE_ALL_FRAMES) {}\n  SampleInterval(double ts, double f) : timestamp(ts), fps(f) {}\n  bool operator<(const SampleInterval& itvl) const {\n    return (timestamp < itvl.timestamp);\n  }\n};\n\nclass Params {\n public:\n  // return all key-frames regardless of specified fps\n  bool keyFrames_ = false;\n\n  // Output image pixel format\n  AVPixelFormat pixelFormat_ = AVPixelFormat::AV_PIX_FMT_RGB24;\n\n  // Index of stream to decode.\n  // -1 will automatically decode the first video stream.\n  int streamIndex_ = -1;\n\n  // How many frames to output at most from the video\n  // -1 no limit\n  int maximumOutputFrames_ = -1;\n\n  // params for video resolution\n  int video_res_type_ = VideoResType::USE_WIDTH_HEIGHT;\n  int crop_height_ = -1;\n  int crop_width_ = -1;\n  int height_min_ = -1;\n  int width_min_ = -1;\n  int scale_w_ = -1;\n  int scale_h_ = -1;\n\n  // params for decoding behavior\n  int decode_type_ = DecodeType::DO_TMP_JITTER;\n  int num_of_required_frame_ = -1;\n\n  // intervals_ control variable sampling fps between different timestamps\n  // intervals_ must be ordered strictly ascending by timestamps\n  // the first interval must have a timestamp of zero\n  // fps must be either the 3 special fps defined in SpecialFps, or > 0\n  std::vector<SampleInterval> intervals_ = {{0, SpecialFps::SAMPLE_ALL_FRAMES}};\n\n  Params() {}\n\n  /**\n   * FPS of output frames\n   * setting here will reset intervals_ and force decoding at target FPS\n   * This can be used if user just want to decode at a steady fps\n   */\n  Params& fps(float v) {\n    intervals_.clear();\n    intervals_.emplace_back(0, v);\n    return *this;\n  }\n\n  /**\n   * Pixel format of output buffer, default PIX_FMT_RGB24\n   */\n  Params& pixelFormat(AVPixelFormat pixelFormat) {\n    pixelFormat_ = pixelFormat;\n    return *this;\n  }\n\n  /**\n   * Return all key-frames\n   */\n  Params& keyFrames(bool keyFrames) {\n    keyFrames_ = keyFrames;\n    return *this;\n  }\n\n  /**\n   * Index of video stream to process, defaults to the first video stream\n   */\n  Params& streamIndex(int index) {\n    streamIndex_ = index;\n    return *this;\n  }\n\n  /**\n   * Only output this many frames, default to no limit\n   */\n  Params& maxOutputFrames(int count) {\n    maximumOutputFrames_ = count;\n    return *this;\n  }\n\n  /**\n   * Output frame width, default to video width\n   */\n  Params& outputWidth(int width) {\n    scale_w_ = width;\n    return *this;\n  }\n\n  /**\n   * Output frame height, default to video height\n   */\n  Params& outputHeight(int height) {\n    scale_h_ = height;\n    return *this;\n  }\n};\n\n// data structure for storing decoded video frames\nclass DecodedFrame {\n public:\n  struct avDeleter {\n    void operator()(unsigned char* p) const {\n      av_free(p);\n    }\n  };\n  using AvDataPtr = std::unique_ptr<uint8_t, avDeleter>;\n\n  // decoded data buffer\n  AvDataPtr data_;\n\n  // size in bytes\n  int size_ = 0;\n\n  // frame dimensions\n  int width_ = 0;\n  int height_ = 0;\n\n  // timestamp in seconds since beginning of video\n  double timestamp_ = 0;\n\n  // true if this is a key frame.\n  bool keyFrame_ = false;\n\n  // index of frame in video\n  int index_ = -1;\n\n  // Sequential number of outputted frame\n  int outputFrameIndex_ = -1;\n};\n\nclass VideoIOContext {\n public:\n  explicit VideoIOContext(const std::string& fname)\n      : workBuffersize_(VIO_BUFFER_SZ),\n        workBuffer_((uint8_t*)av_malloc(workBuffersize_)),\n        inputFile_(nullptr),\n        inputBuffer_(nullptr),\n        inputBufferSize_(0) {\n    inputFile_ = fopen(fname.c_str(), \"rb\");\n    if (inputFile_ == nullptr) {\n      LOG(ERROR) << \"Error opening video file \" << fname;\n    }\n    ctx_ = avio_alloc_context(\n        static_cast<unsigned char*>(workBuffer_.get()),\n        workBuffersize_,\n        0,\n        this,\n        &VideoIOContext::readFile,\n        nullptr, // no write function\n        &VideoIOContext::seekFile);\n  }\n\n  explicit VideoIOContext(const char* buffer, int size)\n      : workBuffersize_(VIO_BUFFER_SZ),\n        workBuffer_((uint8_t*)av_malloc(workBuffersize_)),\n        inputFile_(nullptr),\n        inputBuffer_(buffer),\n        inputBufferSize_(size) {\n    ctx_ = avio_alloc_context(\n        static_cast<unsigned char*>(workBuffer_.get()),\n        workBuffersize_,\n        0,\n        this,\n        &VideoIOContext::readMemory,\n        nullptr, // no write function\n        &VideoIOContext::seekMemory);\n  }\n\n  ~VideoIOContext() {\n    av_free(ctx_);\n    if (inputFile_) {\n      fclose(inputFile_);\n    }\n  }\n\n  int read(unsigned char* buf, int buf_size) {\n    if (inputBuffer_) {\n      return readMemory(this, buf, buf_size);\n    } else if (inputFile_) {\n      return readFile(this, buf, buf_size);\n    } else {\n      return -1;\n    }\n  }\n\n  int64_t seek(int64_t offset, int whence) {\n    if (inputBuffer_) {\n      return seekMemory(this, offset, whence);\n    } else if (inputFile_) {\n      return seekFile(this, offset, whence);\n    } else {\n      return -1;\n    }\n  }\n\n  static int readFile(void* opaque, unsigned char* buf, int buf_size) {\n    VideoIOContext* h = static_cast<VideoIOContext*>(opaque);\n    if (feof(h->inputFile_)) {\n      return AVERROR_EOF;\n    }\n    size_t ret = fread(buf, 1, buf_size, h->inputFile_);\n    if (ret < buf_size) {\n      if (ferror(h->inputFile_)) {\n        return -1;\n      }\n    }\n    return ret;\n  }\n\n  static int64_t seekFile(void* opaque, int64_t offset, int whence) {\n    VideoIOContext* h = static_cast<VideoIOContext*>(opaque);\n    switch (whence) {\n      case SEEK_CUR: // from current position\n      case SEEK_END: // from eof\n      case SEEK_SET: // from beginning of file\n        return fseek(h->inputFile_, static_cast<long>(offset), whence);\n        break;\n      case AVSEEK_SIZE:\n        int64_t cur = ftell(h->inputFile_);\n        fseek(h->inputFile_, 0L, SEEK_END);\n        int64_t size = ftell(h->inputFile_);\n        fseek(h->inputFile_, cur, SEEK_SET);\n        return size;\n    }\n\n    return -1;\n  }\n\n  static int readMemory(void* opaque, unsigned char* buf, int buf_size) {\n    VideoIOContext* h = static_cast<VideoIOContext*>(opaque);\n    if (buf_size < 0) {\n      return -1;\n    }\n\n    int reminder = h->inputBufferSize_ - h->offset_;\n    int r = buf_size < reminder ? buf_size : reminder;\n    if (r < 0) {\n      return AVERROR_EOF;\n    }\n\n    memcpy(buf, h->inputBuffer_ + h->offset_, r);\n    h->offset_ += r;\n    return r;\n  }\n\n  static int64_t seekMemory(void* opaque, int64_t offset, int whence) {\n    VideoIOContext* h = static_cast<VideoIOContext*>(opaque);\n    switch (whence) {\n      case SEEK_CUR: // from current position\n        h->offset_ += offset;\n        break;\n      case SEEK_END: // from eof\n        h->offset_ = h->inputBufferSize_ + offset;\n        break;\n      case SEEK_SET: // from beginning of file\n        h->offset_ = offset;\n        break;\n      case AVSEEK_SIZE:\n        return h->inputBufferSize_;\n    }\n    return h->offset_;\n  }\n\n  AVIOContext* get_avio() {\n    return ctx_;\n  }\n\n private:\n  int workBuffersize_;\n  DecodedFrame::AvDataPtr workBuffer_;\n  // for file mode\n  FILE* inputFile_;\n\n  // for memory mode\n  const char* inputBuffer_;\n  int inputBufferSize_;\n  int offset_ = 0;\n\n  AVIOContext* ctx_;\n};\n\nstruct VideoMeta {\n  double fps;\n  int width;\n  int height;\n  enum AVMediaType codec_type;\n  AVPixelFormat pixFormat;\n  VideoMeta()\n      : fps(-1),\n        width(-1),\n        height(-1),\n        codec_type(AVMEDIA_TYPE_VIDEO),\n        pixFormat(AVPixelFormat::AV_PIX_FMT_RGB24) {}\n};\n\nclass VideoDecoder {\n public:\n  VideoDecoder();\n\n  void decodeFile(\n      const std::string& filename,\n      const Params& params,\n      const int start_frm,\n      std::vector<std::unique_ptr<DecodedFrame>>& sampledFrames);\n\n  void decodeMemory(\n      const char* buffer,\n      const int size,\n      const Params& params,\n      const int start_frm,\n      std::vector<std::unique_ptr<DecodedFrame>>& sampledFrames);\n\n private:\n  std::string ffmpegErrorStr(int result);\n\n  void ResizeAndKeepAspectRatio(\n      const int origHeight,\n      const int origWidth,\n      const int heightMin,\n      const int widthMin,\n      int& outHeight,\n      int& outWidth);\n\n  void decodeLoop(\n      const std::string& videoName,\n      VideoIOContext& ioctx,\n      const Params& params,\n      const int start_frm,\n      std::vector<std::unique_ptr<DecodedFrame>>& sampledFrames);\n};\n} // namespace caffe2\n\n#endif // CAFFE2_VIDEO_VIDEO_DECODER_H_\n"
  },
  {
    "path": "caffe2/video/video_input_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <caffe2/video/video_input_op.h>\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(VideoInput, VideoInputOp<CPUContext>);\n\nOPERATOR_SCHEMA(VideoInput)\n    .NumInputs(0, 1)\n    .NumOutputs(2, 4)\n    .TensorInferenceFunction(\n        [](const OperatorDef& def,\n           const vector<TensorShape>& /* unused */ /*in*/) {\n          ArgumentHelper helper(def);\n          int batch_size = helper.GetSingleArgument<int>(\"batch_size\", 0);\n          int clip_per_video =\n              helper.GetSingleArgument<int>(\"clip_per_video\", 1);\n          int crop_height = helper.GetSingleArgument<int>(\n              \"crop_height\", helper.GetSingleArgument<int>(\"crop_size\", 0));\n          int crop_width = helper.GetSingleArgument<int>(\n              \"crop_width\", helper.GetSingleArgument<int>(\"crop_size\", 0));\n          int length_rgb = helper.GetSingleArgument<int>(\"length_rgb\", 0);\n          int channels_rgb = helper.GetSingleArgument<int>(\"channels_rgb\", 3);\n          int length_of = helper.GetSingleArgument<int>(\"length_of\", 0);\n          int channels_of = helper.GetSingleArgument<int>(\"channels_of\", 2);\n\n          // get the flags\n          bool get_rgb = helper.GetSingleArgument<bool>(\"get_rgb\", true);\n          bool get_optical_flow =\n              helper.GetSingleArgument<bool>(\"get_optical_flow\", false);\n          bool do_multi_label =\n              helper.GetSingleArgument<bool>(\"do_multi_label\", false);\n          bool get_video_id =\n              helper.GetSingleArgument<bool>(\"get_video_id\", false);\n\n          int output_size = 1;\n          if (get_rgb) {\n            output_size++;\n          }\n          if (get_optical_flow) {\n            output_size++;\n          }\n          if (get_video_id) {\n            output_size++;\n          }\n\n          int index = 0;\n          vector<TensorShape> out(output_size);\n          CHECK_GT(crop_height, 0);\n          CHECK_GT(crop_width, 0);\n          batch_size *= clip_per_video;\n          if (get_rgb) {\n            out[index++] = CreateTensorShape(\n                vector<int>{batch_size,\n                            channels_rgb,\n                            length_rgb,\n                            crop_height,\n                            crop_width},\n                TensorProto::FLOAT);\n          }\n          if (get_optical_flow) {\n            out[index++] = CreateTensorShape(\n                vector<int>{batch_size,\n                            channels_of,\n                            length_of,\n                            crop_height,\n                            crop_width},\n                TensorProto::FLOAT);\n          }\n          if (!do_multi_label) {\n            out[index++] = CreateTensorShape(\n                vector<int>{1, batch_size}, TensorProto::INT32);\n          } else {\n            int num_of_class = helper.GetSingleArgument<int>(\"num_of_class\", 0);\n            out[index++] = CreateTensorShape(\n                vector<int>{batch_size, num_of_class}, TensorProto::INT32);\n          }\n          if (get_video_id) {\n            out[index] = CreateTensorShape(\n                vector<int>{1, batch_size}, TensorProto::INT32);\n          }\n\n          return out;\n        });\n\nNO_GRADIENT(VideoInput);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/video/video_input_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_VIDEO_VIDEO_INPUT_OP_H_\n#define CAFFE2_VIDEO_VIDEO_INPUT_OP_H_\n\n#include <istream>\n#include <ostream>\n#include <random>\n#include <string>\n\n#include <caffe2/core/db.h>\n#include <caffe2/core/logging.h>\n#include <caffe2/operators/prefetch_op.h>\n#include <caffe2/utils/math.h>\n#include <caffe2/utils/thread_pool.h>\n#include <caffe2/video/video_io.h>\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass VideoInputOp final : public PrefetchOperator<Context> {\n public:\n  using OperatorBase::OutputSize;\n  using PrefetchOperator<Context>::context_;\n  using PrefetchOperator<Context>::prefetch_thread_;\n  explicit VideoInputOp(const OperatorDef& operator_def, Workspace* ws);\n  ~VideoInputOp() {\n    PrefetchOperator<Context>::Finalize();\n  }\n\n  // override methods\n  bool Prefetch() override;\n  bool CopyPrefetched() override;\n\n private:\n  void CheckParamsAndPrint();\n\n  bool GetClipsAndLabelsFromDBValue(\n      const std::string& value,\n      int& height,\n      int& width,\n      std::vector<unsigned char*>& buffer_rgb,\n      int* label_data,\n      int* video_id_data);\n\n  void DecodeAndTransform(\n      const std::string& value,\n      float* clip_rgb_data,\n      float* clip_of_data,\n      int* label_data,\n      int* video_id_data,\n      std::mt19937* randgen,\n      std::bernoulli_distribution* mirror_this_clip);\n\n  const db::DBReader* reader_;\n  CPUContext cpu_context_;\n  TensorCPU prefetched_clip_rgb_;\n  TensorCPU prefetched_clip_of_;\n  TensorCPU prefetched_label_;\n  TensorCPU prefetched_video_id_;\n  Tensor<Context> prefetched_clip_rgb_on_device_;\n  Tensor<Context> prefetched_clip_of_on_device_;\n  Tensor<Context> prefetched_label_on_device_;\n  Tensor<Context> prefetched_video_id_on_device_;\n  int batch_size_;\n  int clip_per_video_;\n  std::vector<float> mean_rgb_;\n  std::vector<float> inv_std_rgb_;\n  std::vector<float> mean_of_;\n  std::vector<float> inv_std_of_;\n  int channels_rgb_;\n  int channels_of_;\n  int crop_height_;\n  int crop_width_;\n  int scale_h_;\n  int scale_w_;\n  int height_min_;\n  int width_min_;\n  int length_rgb_;\n  int sampling_rate_rgb_;\n  bool color_jitter_;\n  float img_saturation_;\n  float img_brightness_;\n  float img_contrast_;\n  bool color_lighting_;\n  float color_lighting_std_;\n  std::vector<std::vector<float>> color_lighting_eigvecs_;\n  std::vector<float> color_lighting_eigvals_;\n  int num_of_required_frame_;\n  int length_of_;\n  int sampling_rate_of_;\n  int frame_gap_of_;\n  bool random_mirror_;\n  int num_of_class_;\n  bool use_local_file_;\n  bool random_crop_;\n  bool multi_crop_;\n  int multi_crop_count_;\n  int flow_data_type_;\n  int flow_alg_type_;\n  int decode_type_;\n  int video_res_type_;\n  bool do_flow_aggregation_;\n  bool get_rgb_;\n  bool get_optical_flow_;\n  bool get_video_id_;\n  bool do_multi_label_;\n\n  // thread pool for parse + decode\n  int num_decode_threads_;\n  std::shared_ptr<TaskThreadPool> thread_pool_;\n};\n\ntemplate <class Context>\nvoid VideoInputOp<Context>::CheckParamsAndPrint() {\n  // check whether the input parameters are valid or not\n  CAFFE_ENFORCE_GT(batch_size_, 0, \"Batch size should be positive.\");\n  CAFFE_ENFORCE_GT(\n      clip_per_video_, 0, \"Number of clips per video should be positive.\");\n  CAFFE_ENFORCE_GT(crop_height_, 0, \"Must provide the cropping height value.\");\n  CAFFE_ENFORCE_GT(crop_width_, 0, \"Must provide the cropping width value.\");\n\n  CAFFE_ENFORCE_GT(\n      num_of_required_frame_, 0, \"Required number of frames must be positive.\");\n\n  if (video_res_type_ == VideoResType::USE_MINIMAL_WIDTH_HEIGHT) {\n    CAFFE_ENFORCE_GT(height_min_, 0, \"Must provide the minimal height value.\");\n    CAFFE_ENFORCE_GT(width_min_, 0, \"Must provide the minimal width value.\");\n    CAFFE_ENFORCE_GE(\n        height_min_,\n        crop_height_,\n        \"The minimal height must be no smaller than the cropping height.\");\n    CAFFE_ENFORCE_GE(\n        width_min_,\n        crop_width_,\n        \"The minimal width must be no smaller than the cropping width.\");\n  } else if (video_res_type_ == VideoResType::USE_WIDTH_HEIGHT) {\n    CAFFE_ENFORCE_GT(scale_h_, 0, \"Must provide the scale height value.\");\n    CAFFE_ENFORCE_GT(scale_w_, 0, \"Must provide the scale width value.\");\n    CAFFE_ENFORCE_GE(\n        scale_h_,\n        crop_height_,\n        \"The scaled height must be no smaller than the cropping height.\");\n    CAFFE_ENFORCE_GE(\n        scale_w_,\n        crop_width_,\n        \"The scaled width must be no smaller than the cropping width.\");\n  }\n\n  if (get_rgb_) {\n    CAFFE_ENFORCE_GT(length_rgb_, 0, \"Must provide rgb clip length.\");\n    CAFFE_ENFORCE_GT(\n        sampling_rate_rgb_, 0, \"4 frames for mc2; 2 frames for res3d.\");\n    CAFFE_ENFORCE_EQ(\n        channels_rgb_, mean_rgb_.size(), \"Number rgb channels is wrong!\");\n    CAFFE_ENFORCE_EQ(\n        channels_rgb_, inv_std_rgb_.size(), \"Number rgb channels is wrong!\");\n  }\n\n  if (get_optical_flow_) {\n    CAFFE_ENFORCE_GT(length_of_, 0, \"Must provide optical flow clip length.\");\n    CAFFE_ENFORCE_GT(\n        sampling_rate_of_, 0, \"4 frames for mc2; 2 frames for res3d.\");\n    CAFFE_ENFORCE_EQ(\n        channels_of_,\n        mean_of_.size(),\n        \"Number of optical flow channels is wrong!\");\n    CAFFE_ENFORCE_EQ(\n        channels_of_,\n        inv_std_of_.size(),\n        \"Number of optical flow channels is wrong!\");\n  }\n\n  if (clip_per_video_ > 1) {\n    CAFFE_ENFORCE_EQ(\n        decode_type_,\n        DecodeType::DO_UNIFORM_SMP,\n        \"Only uniformly sampling is supported when sampling multiple clips!\");\n  }\n\n  if (do_multi_label_) {\n    CAFFE_ENFORCE_GT(\n        num_of_class_,\n        0,\n        \"Number of classes must be set when using multiple labels.\");\n  }\n\n  // print out the parameter settings\n  LOG(INFO) << \"Creating a clip input op with the following setting: \";\n  LOG(INFO) << \"    Using \" << num_decode_threads_ << \" CPU threads;\";\n  LOG(INFO) << \"    Outputting in batches of \" << batch_size_ << \" videos;\";\n  LOG(INFO) << \"    Each video has \" << clip_per_video_ << \" clips;\";\n  LOG(INFO) << \"    Scaling image to \" << scale_h_ << \"x\" << scale_w_;\n  LOG(INFO) << \"    (Height, Width) is at least (\" << height_min_ << \", \"\n            << width_min_ << \")\";\n  LOG(INFO) << \"    Cropping video frame to \" << crop_height_ << \"x\"\n            << crop_width_ << (random_mirror_ ? \" with \" : \" without \")\n            << \"random mirroring;\";\n  LOG(INFO) << \"    Using \" << (random_crop_ ? \"random\" : \"center\") << \" crop\";\n  LOG(INFO) << \"    Is multi-cropping enabled: \" << multi_crop_;\n\n  if (get_rgb_) {\n    LOG(INFO) << \"    Using a clip of \" << length_rgb_ << \" rgb frames \"\n              << \"with \" << channels_rgb_ << \" channels \"\n              << \"and a sampling rate of 1:\" << sampling_rate_rgb_;\n    LOG(INFO) << \"    RGB data augmentation. Color jittering: \" << color_jitter_\n              << \". Color lighting: \" << color_lighting_;\n    for (int i = 0; i < channels_rgb_; i++) {\n      LOG(INFO) << \"    RGB \" << i << \"-th channel mean: \" << mean_rgb_[i]\n                << \" std: \" << 1.f / inv_std_rgb_[i];\n    }\n  }\n\n  if (get_optical_flow_) {\n    LOG(INFO) << \"    Using a clip of \" << length_of_ << \" optical flow frames \"\n              << \"with \" << channels_of_ << \" channels \"\n              << \"and a sampling rate of 1:\" << sampling_rate_of_\n              << \" flow_data_type_: \" << flow_data_type_\n              << \" flow_alg_type_: \" << flow_alg_type_;\n    for (int i = 0; i < channels_of_; i++) {\n      LOG(INFO) << \"    Optical flow\" << i\n                << \"-th channel mean: \" << mean_of_[i]\n                << \" std: \" << 1.f / inv_std_of_[i];\n    }\n  }\n\n  if (video_res_type_ == VideoResType::ORIGINAL_RES) {\n    LOG(INFO) << \"    Use original resolution\";\n  } else if (video_res_type_ == VideoResType::USE_MINIMAL_WIDTH_HEIGHT) {\n    LOG(INFO) << \"    Resize with minimal size and keep aspect ratio\";\n  } else if (video_res_type_ == VideoResType::USE_WIDTH_HEIGHT) {\n    LOG(INFO) << \"    Resize and ignore aspect ratio\";\n  } else {\n    LOG(ERROR) << \"    Unknown video resolution type\";\n  }\n\n  if (decode_type_ == DecodeType::DO_TMP_JITTER) {\n    LOG(INFO) << \"    Do temporal jittering\";\n  } else if (decode_type_ == DecodeType::USE_START_FRM) {\n    LOG(INFO) << \"    Use start_frm for decoding\";\n  } else if (decode_type_ == DecodeType::DO_UNIFORM_SMP) {\n    LOG(INFO) << \"    Do uniformly sampling\";\n  } else {\n    LOG(ERROR) << \"    Unknown video decoding type\";\n  }\n}\n\ntemplate <class Context>\nVideoInputOp<Context>::VideoInputOp(\n    const OperatorDef& operator_def,\n    Workspace* ws)\n    : PrefetchOperator<Context>(operator_def, ws),\n      reader_(nullptr),\n      batch_size_(\n          OperatorBase::template GetSingleArgument<int>(\"batch_size\", 0)),\n      clip_per_video_(\n          OperatorBase::template GetSingleArgument<int>(\"clip_per_video\", 1)),\n      mean_rgb_(OperatorBase::template GetRepeatedArgument<float>(\n          \"mean_rgb_per_channel\",\n          {OperatorBase::template GetSingleArgument<float>(\"mean_rgb\", 128.)})),\n      inv_std_rgb_(OperatorBase::template GetRepeatedArgument<float>(\n          \"std_rgb_per_channel\",\n          {OperatorBase::template GetSingleArgument<float>(\"std_rgb\", 1.)})),\n      mean_of_(OperatorBase::template GetRepeatedArgument<float>(\n          \"mean_of_per_channel\",\n          {OperatorBase::template GetSingleArgument<float>(\"mean_of\", 0.)})),\n      inv_std_of_(OperatorBase::template GetRepeatedArgument<float>(\n          \"std_of_per_channel\",\n          {OperatorBase::template GetSingleArgument<float>(\"std_of\", 1.)})),\n      channels_rgb_(\n          OperatorBase::template GetSingleArgument<int>(\"channels_rgb\", 3)),\n      channels_of_(\n          OperatorBase::template GetSingleArgument<int>(\"channels_of\", 2)),\n      crop_height_(OperatorBase::template GetSingleArgument<int>(\n          \"crop_height\",\n          {OperatorBase::template GetSingleArgument<int>(\"crop_size\", 0.)})),\n      crop_width_(OperatorBase::template GetSingleArgument<int>(\n          \"crop_width\",\n          {OperatorBase::template GetSingleArgument<int>(\"crop_size\", 0.)})),\n      scale_h_(OperatorBase::template GetSingleArgument<int>(\"scale_h\", 0)),\n      scale_w_(OperatorBase::template GetSingleArgument<int>(\"scale_w\", 0)),\n      height_min_(OperatorBase::template GetSingleArgument<int>(\n          \"height_min\",\n          {OperatorBase::template GetSingleArgument<int>(\"short_edge\", 0)})),\n      width_min_(OperatorBase::template GetSingleArgument<int>(\n          \"width_min\",\n          {OperatorBase::template GetSingleArgument<int>(\"short_edge\", 0)})),\n      length_rgb_(\n          OperatorBase::template GetSingleArgument<int>(\"length_rgb\", 0)),\n      sampling_rate_rgb_(OperatorBase::template GetSingleArgument<int>(\n          \"sampling_rate_rgb\",\n          1)),\n      color_jitter_(OperatorBase::template GetSingleArgument<bool>(\n          \"color_jitter\",\n          false)),\n      img_saturation_(OperatorBase::template GetSingleArgument<float>(\n          \"img_saturation\",\n          0.4)),\n      img_brightness_(OperatorBase::template GetSingleArgument<float>(\n          \"img_brightness\",\n          0.4)),\n      img_contrast_(\n          OperatorBase::template GetSingleArgument<float>(\"img_contrast\", 0.4)),\n      color_lighting_(OperatorBase::template GetSingleArgument<bool>(\n          \"color_lighting\",\n          false)),\n      color_lighting_std_(OperatorBase::template GetSingleArgument<float>(\n          \"color_lighting_std\",\n          0.1)),\n      length_of_(OperatorBase::template GetSingleArgument<int>(\"length_of\", 0)),\n      sampling_rate_of_(\n          OperatorBase::template GetSingleArgument<int>(\"sampling_rate_of\", 1)),\n      frame_gap_of_(\n          OperatorBase::template GetSingleArgument<int>(\"frame_gap_of\", 1)),\n      random_mirror_(OperatorBase::template GetSingleArgument<bool>(\n          \"random_mirror\",\n          true)),\n      num_of_class_(\n          OperatorBase::template GetSingleArgument<int>(\"num_of_class\", 0)),\n      use_local_file_(OperatorBase::template GetSingleArgument<bool>(\n          \"use_local_file\",\n          false)),\n      random_crop_(\n          OperatorBase::template GetSingleArgument<bool>(\"random_crop\", true)),\n      multi_crop_(\n          OperatorBase::template GetSingleArgument<bool>(\"multi_crop\", false)),\n      flow_data_type_(\n          OperatorBase::template GetSingleArgument<int>(\"flow_data_type\", 0)),\n      flow_alg_type_(\n          OperatorBase::template GetSingleArgument<int>(\"flow_alg_type\", 0)),\n      decode_type_(\n          OperatorBase::template GetSingleArgument<int>(\"decode_type\", 0)),\n      video_res_type_(\n          OperatorBase::template GetSingleArgument<int>(\"video_res_type\", 0)),\n      do_flow_aggregation_(OperatorBase::template GetSingleArgument<bool>(\n          \"do_flow_aggregation\",\n          true)),\n      get_rgb_(OperatorBase::template GetSingleArgument<bool>(\"get_rgb\", true)),\n      get_optical_flow_(OperatorBase::template GetSingleArgument<bool>(\n          \"get_optical_flow\",\n          false)),\n      get_video_id_(OperatorBase::template GetSingleArgument<bool>(\n          \"get_video_id\",\n          false)),\n      do_multi_label_(OperatorBase::template GetSingleArgument<bool>(\n          \"do_multi_label\",\n          false)),\n      num_decode_threads_(OperatorBase::template GetSingleArgument<int>(\n          \"num_decode_threads\",\n          4)),\n      thread_pool_(std::make_shared<TaskThreadPool>(num_decode_threads_)) {\n  // hard-coded PCA eigenvectors and eigenvalues, based on RBG channel order\n  color_lighting_eigvecs_.push_back(\n      std::vector<float>{-144.7125, 183.396, 102.2295});\n  color_lighting_eigvecs_.push_back(\n      std::vector<float>{-148.104, -1.1475, -207.57});\n  color_lighting_eigvecs_.push_back(\n      std::vector<float>{-148.818, -177.174, 107.1765});\n\n  color_lighting_eigvals_ = std::vector<float>{0.2175, 0.0188, 0.0045};\n\n  // multi-cropping for testing\n  multi_crop_count_ = 1;\n  if (multi_crop_) {\n    // we take left-top, central-top, right-top, left-bottom, central-bottom,\n    // right-bottom and central-central croppings as well as their mirrorings\n    // In total, 14 croppings\n    multi_crop_count_ = 14;\n  }\n\n  num_of_required_frame_ = 0;\n\n  // mean and std for normalizing different optical flow data type;\n  // Example statistics generated from SOA are shown below, and you may\n  // want to change them if you are running on a different dataset;\n\n  // 7 channels: (flow_x, flow_y, flow_magitude, gray, Red, Green, Blue)\n  // const std::vector<float> InputDataMean =\n  //     {0.0046635, 0.0046261, 0.963986, 102.976, 110.201, 100.64, 95.9966};\n  // const std::vector<float> InputDataStd =\n  //     {0.972347, 0.755146, 1.43588, 55.3691, 58.1489, 56.4701, 55.3324};\n\n  // if we need RGB as an input\n  if (get_rgb_) {\n    // how many frames we need for RGB\n    num_of_required_frame_ = std::max(\n        num_of_required_frame_, (length_rgb_ - 1) * sampling_rate_rgb_ + 1);\n\n    channels_rgb_ = 3;\n\n    CAFFE_ENFORCE_EQ(\n        mean_rgb_.size(),\n        inv_std_rgb_.size(),\n        \"The mean and std. vectors for RGB must be of the same size.\");\n    if (mean_rgb_.size() == 1) {\n      mean_rgb_.resize(3, mean_rgb_[0]);\n      inv_std_rgb_.resize(3, inv_std_rgb_[0]);\n    }\n    CAFFE_ENFORCE_EQ(mean_rgb_.size(), 3, \"RGB should have 3 channels\");\n    for (int i = 0; i < 3; ++i) {\n      inv_std_rgb_[i] = 1.f / inv_std_rgb_[i];\n    }\n  }\n  // if we need optical flow as an input\n  if (get_optical_flow_) {\n    // how many frames we need for optical flow\n    num_of_required_frame_ = std::max(\n        num_of_required_frame_,\n        (length_of_ - 1) * sampling_rate_of_ + frame_gap_of_ + 1);\n\n    CAFFE_ENFORCE_EQ(\n        mean_of_.size(),\n        inv_std_of_.size(),\n        \"The mean and std. vectors for Optical Flow must be of the same size.\");\n    // set the parameters for different input data types\n    switch (flow_data_type_) {\n      // (flow_x, flow_y)\n      case FlowDataType::Flow2C:\n        channels_of_ = 2;\n        break;\n      // (flow_x, flow_y, flow_magnitude)\n      case FlowDataType::Flow3C:\n        channels_of_ = 3;\n        break;\n      // early fusion with gray\n      // (flow_x, flow_y, gray)\n      case FlowDataType::FlowWithGray:\n        channels_of_ = 3;\n        break;\n      // early fusion with RGB\n      // (flow_x, flow_y, Red, Green, Blue)\n      case FlowDataType::FlowWithRGB:\n        channels_of_ = 5;\n        break;\n      default:\n        LOG(ERROR) << \"Unknown optical flow type \" << flow_data_type_;\n        break;\n    }\n    LOG(INFO) << \"channels_of_: \" << channels_of_;\n    if (mean_of_.size() == 1) {\n      mean_of_.resize(channels_of_, mean_of_[0]);\n      inv_std_of_.resize(channels_of_, inv_std_of_[0]);\n    }\n    for (int i = 0; i < channels_of_; ++i) {\n      inv_std_of_[i] = 1.f / inv_std_of_[i];\n    }\n  }\n\n  CheckParamsAndPrint();\n  // Always need a dbreader, even when using local video files\n  CAFFE_ENFORCE_GT(\n      operator_def.input_size(), 0, \"Need to have a DBReader blob input\");\n\n  vector<TIndex> data_shape(5);\n  vector<TIndex> label_shape(2);\n\n  // for RGB data\n  data_shape[0] = batch_size_ * clip_per_video_ * multi_crop_count_;\n  data_shape[1] = channels_rgb_;\n  data_shape[2] = length_rgb_;\n  data_shape[3] = crop_height_;\n  data_shape[4] = crop_width_;\n  prefetched_clip_rgb_.Resize(data_shape);\n\n  // for optical flow data\n  data_shape[1] = channels_of_;\n  data_shape[2] = length_of_;\n  prefetched_clip_of_.Resize(data_shape);\n\n  // If do_multi_label is used, output label is a binary vector\n  // of length num_of_class indicating which labels present\n  if (do_multi_label_) {\n    label_shape[0] = batch_size_ * clip_per_video_ * multi_crop_count_;\n    label_shape[1] = num_of_class_;\n    prefetched_label_.Resize(label_shape);\n  } else {\n    prefetched_label_.Resize(\n        vector<TIndex>(1, batch_size_ * clip_per_video_ * multi_crop_count_));\n  }\n\n  prefetched_video_id_.Resize(\n      vector<TIndex>(1, batch_size_ * clip_per_video_ * multi_crop_count_));\n}\n\ntemplate <class Context>\nbool VideoInputOp<Context>::GetClipsAndLabelsFromDBValue(\n    const std::string& value,\n    int& height,\n    int& width,\n    std::vector<unsigned char*>& buffer_rgb,\n    int* label_data,\n    int* video_id_data) {\n  TensorProtos protos;\n  int curr_proto_idx = 0;\n  CAFFE_ENFORCE(protos.ParseFromString(value));\n  const TensorProto& video_proto = protos.protos(curr_proto_idx++);\n  const TensorProto& label_proto = protos.protos(curr_proto_idx++);\n\n  int start_frm = 0;\n  // start_frm is only valid when sampling 1 clip per video without\n  // temporal jitterring\n  if (decode_type_ == DecodeType::USE_START_FRM) {\n    CAFFE_ENFORCE_LT(\n        curr_proto_idx,\n        protos.protos_size(),\n        \"No proto is found for starting frame\");\n    const TensorProto& start_frm_proto = protos.protos(curr_proto_idx++);\n    start_frm = start_frm_proto.int32_data(0);\n  }\n  if (get_video_id_) {\n    CAFFE_ENFORCE_LT(\n        curr_proto_idx, protos.protos_size(), \"No proto is found for video id\");\n    const TensorProto& video_id_proto = protos.protos(curr_proto_idx);\n    for (int i = 0; i < clip_per_video_ * multi_crop_count_; i++) {\n      video_id_data[i] = video_id_proto.int64_data(0);\n    }\n  }\n  // assign labels\n  if (!do_multi_label_) {\n    for (int i = 0; i < clip_per_video_ * multi_crop_count_; i++) {\n      label_data[i] = label_proto.int32_data(0);\n    }\n  } else {\n    // For multiple label case, output label is a binary vector\n    // where presented concepts are makred 1\n    memset(\n        label_data,\n        0,\n        sizeof(int) * num_of_class_ * multi_crop_count_ * clip_per_video_);\n    for (int i = 0; i < clip_per_video_; i++) {\n      for (int j = 0; j < multi_crop_count_; ++j) {\n        for (int k = 0; k < label_proto.int32_data_size(); k++) {\n          label_data\n              [(i * multi_crop_count_ + j) * num_of_class_ +\n               label_proto.int32_data(k)] = 1;\n        }\n      }\n    }\n  }\n\n  if (use_local_file_) {\n    CAFFE_ENFORCE_EQ(\n        video_proto.data_type(),\n        TensorProto::STRING,\n        \"Database with a file_list is expected to be string data\");\n  }\n\n  // initializing the decoding params\n  Params params;\n  params.maximumOutputFrames_ = MAX_DECODING_FRAMES;\n  params.video_res_type_ = video_res_type_;\n  params.crop_height_ = crop_height_;\n  params.crop_width_ = crop_width_;\n  params.height_min_ = height_min_;\n  params.width_min_ = width_min_;\n  params.scale_w_ = scale_w_;\n  params.scale_h_ = scale_h_;\n  params.decode_type_ = decode_type_;\n  params.num_of_required_frame_ = num_of_required_frame_;\n\n  char* video_buffer = nullptr; // for decoding from buffer\n  std::string video_filename; // for decoding from file\n  int encoded_size = 0;\n  if (video_proto.data_type() == TensorProto::STRING) {\n    const string& encoded_video_str = video_proto.string_data(0);\n    if (!use_local_file_) {\n      encoded_size = encoded_video_str.size();\n      video_buffer = const_cast<char*>(encoded_video_str.data());\n    } else {\n      video_filename = encoded_video_str;\n    }\n  } else if (video_proto.data_type() == TensorProto::BYTE) {\n    if (!use_local_file_) {\n      encoded_size = video_proto.byte_data().size();\n      video_buffer = const_cast<char*>(video_proto.byte_data().data());\n    } else {\n      // TODO: does this works?\n      video_filename = video_proto.string_data(0);\n    }\n  } else {\n    LOG(FATAL) << \"Unknown video data type.\";\n  }\n\n  DecodeMultipleClipsFromVideo(\n      video_buffer,\n      video_filename,\n      encoded_size,\n      params,\n      start_frm,\n      clip_per_video_,\n      use_local_file_,\n      height,\n      width,\n      buffer_rgb);\n\n  return true;\n}\n\ntemplate <class Context>\nvoid VideoInputOp<Context>::DecodeAndTransform(\n    const std::string& value,\n    float* clip_rgb_data,\n    float* clip_of_data,\n    int* label_data,\n    int* video_id_data,\n    std::mt19937* randgen,\n    std::bernoulli_distribution* mirror_this_clip) {\n  std::vector<unsigned char*> buffer_rgb;\n  // get the video resolution after decoding\n  int height = 0;\n  int width = 0;\n  // Decode the video from memory or read from a local file\n  CHECK(GetClipsAndLabelsFromDBValue(\n      value, height, width, buffer_rgb, label_data, video_id_data));\n  int clip_offset_rgb = multi_crop_count_ * channels_rgb_ * length_rgb_ *\n      crop_height_ * crop_width_;\n  int clip_crop_offset_of =\n      channels_of_ * length_of_ * crop_height_ * crop_width_;\n  int clip_offset_of = multi_crop_count_ * clip_crop_offset_of;\n  for (int i = 0; i < std::min(clip_per_video_, int(buffer_rgb.size())); i++) {\n    // get the rectangle for cropping\n    int h_off = 0;\n    int w_off = 0;\n    if (random_crop_) {\n      // using random crop for training\n      h_off =\n          std::uniform_int_distribution<>(0, height - crop_height_)(*randgen);\n      w_off = std::uniform_int_distribution<>(0, width - crop_width_)(*randgen);\n    } else {\n      // using center crop for testing\n      h_off = (height - crop_height_) / 2;\n      w_off = (width - crop_width_) / 2;\n    }\n    // cv::Rect rect(w_off, h_off, crop_width_, crop_height_);\n\n    // Multi cropping: we take left-top, central-top, right-top, left-bottom,\n    // central-bottom, right-bottom and central-central croppings as well as\n    // their mirrorings. In total, 14 croppings\n    int multi_crop_w_off[7] = {0,\n                               (width - crop_width_) / 2,\n                               width - crop_width_,\n                               (width - crop_width_) / 2,\n                               0,\n                               (width - crop_width_) / 2,\n                               width - crop_width_};\n    int multi_crop_h_off[7] = {0,\n                               0,\n                               0,\n                               (height - crop_height_) / 2,\n                               height - crop_height_,\n                               height - crop_height_,\n                               height - crop_height_};\n\n    // randomly mirror the image or not\n    bool mirror_me = random_mirror_ && (*mirror_this_clip)(*randgen);\n    if (get_rgb_ && clip_rgb_data) {\n      ClipTransformRGB(\n          buffer_rgb[i],\n          multi_crop_count_,\n          crop_height_,\n          crop_width_,\n          length_rgb_,\n          channels_rgb_,\n          sampling_rate_rgb_,\n          height,\n          width,\n          h_off,\n          w_off,\n          multi_crop_h_off,\n          multi_crop_w_off,\n          mirror_me,\n          color_jitter_,\n          img_saturation_,\n          img_brightness_,\n          img_contrast_,\n          color_lighting_,\n          color_lighting_std_,\n          color_lighting_eigvecs_,\n          color_lighting_eigvals_,\n          mean_rgb_,\n          inv_std_rgb_,\n          randgen,\n          clip_rgb_data + (i * clip_offset_rgb));\n    }\n    if (get_optical_flow_ && clip_of_data) {\n      cv::Rect rect;\n      for (int j = 0; j < multi_crop_count_; ++j) {\n        if (multi_crop_count_ == 1) {\n          rect = cv::Rect(w_off, h_off, crop_width_, crop_height_);\n        } else {\n          mirror_me = j / (multi_crop_count_ / 2);\n          int k = j % (multi_crop_count_ / 2);\n          rect = cv::Rect(\n              multi_crop_w_off[k],\n              multi_crop_h_off[k],\n              crop_width_,\n              crop_height_);\n        }\n        ClipTransformOpticalFlow(\n            buffer_rgb[i],\n            crop_height_,\n            crop_width_,\n            length_of_,\n            channels_of_,\n            sampling_rate_of_,\n            height,\n            width,\n            rect,\n            channels_rgb_,\n            mirror_me,\n            flow_alg_type_,\n            flow_data_type_,\n            frame_gap_of_,\n            do_flow_aggregation_,\n            mean_of_,\n            inv_std_of_,\n            clip_of_data + (i * clip_offset_of) + j * clip_crop_offset_of);\n      }\n    }\n  }\n\n  if (buffer_rgb.size() > 0) {\n    for (int i = 0; i < buffer_rgb.size(); i++) {\n      unsigned char* buff = buffer_rgb[i];\n      delete[] buff;\n    }\n  }\n  buffer_rgb.clear();\n}\n\ntemplate <class Context>\nbool VideoInputOp<Context>::Prefetch() {\n  // We will get the reader pointer from input.\n  // If we use local clips, db will store the list\n  reader_ = &OperatorBase::Input<db::DBReader>(0);\n\n  // Call mutable_data() once to allocate the underlying memory.\n  prefetched_clip_rgb_.mutable_data<float>();\n  prefetched_clip_of_.mutable_data<float>();\n  prefetched_label_.mutable_data<int>();\n  prefetched_video_id_.mutable_data<int>();\n\n  // Prefetching handled with a thread pool of \"decode_threads\" threads.\n  std::mt19937 meta_randgen(time(nullptr));\n  std::vector<std::mt19937> randgen_per_thread;\n  for (int i = 0; i < num_decode_threads_; ++i) {\n    randgen_per_thread.emplace_back(meta_randgen());\n  }\n\n  std::bernoulli_distribution mirror_this_clip(0.5);\n  for (int item_id = 0; item_id < batch_size_; ++item_id) {\n    std::mt19937* randgen = &randgen_per_thread[item_id % num_decode_threads_];\n\n    int frame_size = crop_height_ * crop_width_;\n    // get the clip data pointer for the item_id -th example\n    float* clip_rgb_data = prefetched_clip_rgb_.mutable_data<float>() +\n        frame_size * length_rgb_ * channels_rgb_ * item_id * clip_per_video_ *\n            multi_crop_count_;\n\n    // get the optical flow data for the current clip\n    float* clip_of_data = prefetched_clip_of_.mutable_data<float>() +\n        frame_size * length_of_ * channels_of_ * item_id * clip_per_video_ *\n            multi_crop_count_;\n\n    // get the label data pointer for the item_id -th example\n    int* label_data = prefetched_label_.mutable_data<int>() +\n        (do_multi_label_ ? num_of_class_ : 1) * item_id * clip_per_video_ *\n            multi_crop_count_;\n\n    // get the video id data pointer for the item_id -th example\n    int* video_id_data = prefetched_video_id_.mutable_data<int>() +\n        item_id * clip_per_video_ * multi_crop_count_;\n\n    std::string key, value;\n    // read data\n    reader_->Read(&key, &value);\n\n    thread_pool_->runTask(std::bind(\n        &VideoInputOp<Context>::DecodeAndTransform,\n        this,\n        std::string(value),\n        clip_rgb_data,\n        clip_of_data,\n        label_data,\n        video_id_data,\n        randgen,\n        &mirror_this_clip));\n  } // for over the batch\n  thread_pool_->waitWorkComplete();\n\n  // If the context is not CPUContext, we will need to do a copy in the\n  // prefetch function as well.\n  if (!std::is_same<Context, CPUContext>::value) {\n    if (get_rgb_) {\n      prefetched_clip_rgb_on_device_.CopyFrom(prefetched_clip_rgb_, &context_);\n    }\n    if (get_optical_flow_) {\n      prefetched_clip_of_on_device_.CopyFrom(prefetched_clip_of_, &context_);\n    }\n    prefetched_label_on_device_.CopyFrom(prefetched_label_, &context_);\n    if (get_video_id_) {\n      prefetched_video_id_on_device_.CopyFrom(prefetched_video_id_, &context_);\n    }\n  }\n  return true;\n}\n\ntemplate <class Context>\nbool VideoInputOp<Context>::CopyPrefetched() {\n  int index = 0;\n  if (get_rgb_) {\n    auto* clip_rgb_output = OperatorBase::Output<Tensor<Context>>(index++);\n    if (std::is_same<Context, CPUContext>::value) {\n      clip_rgb_output->CopyFrom(prefetched_clip_rgb_, &context_);\n    } else {\n      clip_rgb_output->CopyFrom(prefetched_clip_rgb_on_device_, &context_);\n    }\n  }\n  if (get_optical_flow_) {\n    auto* clip_of_output = OperatorBase::Output<Tensor<Context>>(index++);\n    if (std::is_same<Context, CPUContext>::value) {\n      clip_of_output->CopyFrom(prefetched_clip_of_, &context_);\n    } else {\n      clip_of_output->CopyFrom(prefetched_clip_of_on_device_, &context_);\n    }\n  }\n  auto* label_output = OperatorBase::Output<Tensor<Context>>(index++);\n  if (std::is_same<Context, CPUContext>::value) {\n    label_output->CopyFrom(prefetched_label_, &context_);\n  } else {\n    label_output->CopyFrom(prefetched_label_on_device_, &context_);\n  }\n  if (get_video_id_) {\n    auto* video_id_output = OperatorBase::Output<Tensor<Context>>(index);\n    if (std::is_same<Context, CPUContext>::value) {\n      video_id_output->CopyFrom(prefetched_video_id_, &context_);\n    } else {\n      video_id_output->CopyFrom(prefetched_video_id_on_device_, &context_);\n    }\n  }\n  return true;\n}\n\n} // namespace caffe2\n\n#endif // CAFFE2_VIDEO_VIDEO_INPUT_OP_H_\n"
  },
  {
    "path": "caffe2/video/video_input_op_gpu.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <caffe2/core/common_gpu.h>\n#include <caffe2/core/context_gpu.h>\n#include <caffe2/video/video_input_op.h>\n\nnamespace caffe2 {\n\nREGISTER_CUDA_OPERATOR(VideoInput, VideoInputOp<CUDAContext>);\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/video/video_io.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <caffe2/video/video_io.h>\n#include <caffe2/core/logging.h>\n#include <algorithm>\n#include <random>\n#include <string>\n\nnamespace caffe2 {\n\n// assume CLHW order and color channels RGB\nvoid Saturation(\n    float* clip,\n    const int length,\n    const int crop_height,\n    const int crop_width,\n    const float alpha_rand,\n    std::mt19937* randgen) {\n  float alpha = 1.0f +\n      std::uniform_real_distribution<float>(-alpha_rand, alpha_rand)(*randgen);\n\n  // RGB to Gray scale image: R -> 0.299, G -> 0.587, B -> 0.114\n  const int channel_size = length * crop_height * crop_width;\n  int p = 0;\n  for (int l = 0; l < length; ++l) {\n    for (int h = 0; h < crop_height; ++h) {\n      for (int w = 0; w < crop_width; ++w) {\n        float gray_color = clip[p] * 0.299f + clip[p + channel_size] * 0.587f +\n            clip[p + 2 * channel_size] * 0.114f;\n        for (int c = 0; c < 3; ++c) {\n          clip[c * channel_size + p] =\n              clip[c * channel_size + p] * alpha + gray_color * (1.0f - alpha);\n        }\n        p++;\n      }\n    }\n  }\n}\n\n// assume CLHW order and color channels RGB\nvoid Brightness(\n    float* clip,\n    const int length,\n    const int crop_height,\n    const int crop_width,\n    const float alpha_rand,\n    std::mt19937* randgen) {\n  float alpha = 1.0f +\n      std::uniform_real_distribution<float>(-alpha_rand, alpha_rand)(*randgen);\n\n  int p = 0;\n  for (int c = 0; c < 3; ++c) {\n    for (int l = 0; l < length; ++l) {\n      for (int h = 0; h < crop_height; ++h) {\n        for (int w = 0; w < crop_width; ++w) {\n          clip[p++] *= alpha;\n        }\n      }\n    }\n  }\n}\n\n// assume CLHW order and color channels RGB\nvoid Contrast(\n    float* clip,\n    const int length,\n    const int crop_height,\n    const int crop_width,\n    const float alpha_rand,\n    std::mt19937* randgen) {\n  const int channel_size = length * crop_height * crop_width;\n  float gray_mean = 0;\n  int p = 0;\n  for (int l = 0; l < length; ++l) {\n    for (int h = 0; h < crop_height; ++h) {\n      for (int w = 0; w < crop_width; ++w) {\n        // RGB to Gray scale image: R -> 0.299, G -> 0.587, B -> 0.114\n        gray_mean += clip[p] * 0.299f + clip[p + channel_size] * 0.587f +\n            clip[p + 2 * channel_size] * 0.114f;\n        p++;\n      }\n    }\n  }\n  gray_mean /= (length * crop_height * crop_width);\n\n  float alpha = 1.0f +\n      std::uniform_real_distribution<float>(-alpha_rand, alpha_rand)(*randgen);\n  p = 0;\n  for (int c = 0; c < 3; ++c) {\n    for (int l = 0; l < length; ++l) {\n      for (int h = 0; h < crop_height; ++h) {\n        for (int w = 0; w < crop_width; ++w) {\n          clip[p] = clip[p] * alpha + gray_mean * (1.0f - alpha);\n          p++;\n        }\n      }\n    }\n  }\n}\n\n// assume CLHW order and color channels RGB\nvoid ColorJitter(\n    float* clip,\n    const int length,\n    const int crop_height,\n    const int crop_width,\n    const float saturation,\n    const float brightness,\n    const float contrast,\n    std::mt19937* randgen) {\n  std::srand(unsigned(std::time(0)));\n  std::vector<int> jitter_order{0, 1, 2};\n  // obtain a time-based seed:\n  unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();\n  std::shuffle(\n      jitter_order.begin(),\n      jitter_order.end(),\n      std::default_random_engine(seed));\n\n  for (int i = 0; i < 3; ++i) {\n    if (jitter_order[i] == 0) {\n      Saturation(clip, length, crop_height, crop_width, saturation, randgen);\n    } else if (jitter_order[i] == 1) {\n      Brightness(clip, length, crop_height, crop_width, brightness, randgen);\n    } else {\n      Contrast(clip, length, crop_height, crop_width, contrast, randgen);\n    }\n  }\n}\n\n// assume CLHW order and color channels RGB\nvoid ColorLighting(\n    float* clip,\n    const int length,\n    const int crop_height,\n    const int crop_width,\n    const float alpha_std,\n    const std::vector<std::vector<float>>& eigvecs,\n    const std::vector<float>& eigvals,\n    std::mt19937* randgen) {\n  std::normal_distribution<float> d(0, alpha_std);\n  std::vector<float> alphas(3);\n  for (int i = 0; i < 3; ++i) {\n    alphas[i] = d(*randgen);\n  }\n\n  std::vector<float> delta_rgb(3, 0.0);\n  for (int i = 0; i < 3; ++i) {\n    for (int j = 0; j < 3; ++j) {\n      delta_rgb[i] += eigvecs[i][j] * eigvals[j] * alphas[j];\n    }\n  }\n\n  int p = 0;\n  for (int c = 0; c < 3; ++c) {\n    for (int l = 0; l < length; ++l) {\n      for (int h = 0; h < crop_height; ++h) {\n        for (int w = 0; w < crop_width; ++w) {\n          clip[p++] += delta_rgb[c];\n        }\n      }\n    }\n  }\n}\n\n// assume CLHW order and color channels RGB\n// mean subtraction and scaling.\nvoid ColorNormalization(\n    float* clip,\n    const int length,\n    const int crop_height,\n    const int crop_width,\n    const int channels,\n    const std::vector<float>& mean,\n    const std::vector<float>& inv_std) {\n  int p = 0;\n  for (int c = 0; c < channels; ++c) {\n    for (int l = 0; l < length; ++l) {\n      for (int h = 0; h < crop_height; ++h) {\n        for (int w = 0; w < crop_width; ++w) {\n          clip[p] = (clip[p] - mean[c]) * inv_std[c];\n          p++;\n        }\n      }\n    }\n  }\n}\n\nvoid ClipTransformRGB(\n    const unsigned char* buffer_rgb,\n    const int multi_crop_count,\n    const int crop_height,\n    const int crop_width,\n    const int length_rgb,\n    const int channels_rgb,\n    const int sampling_rate_rgb,\n    const int height,\n    const int width,\n    const int h_off,\n    const int w_off,\n    const int* multi_crop_h_off,\n    const int* multi_crop_w_off,\n    const bool mirror_me,\n    const bool color_jitter,\n    const float saturation,\n    const float brightness,\n    const float contrast,\n    const bool color_lighting,\n    const float color_lighting_std,\n    const std::vector<std::vector<float>>& color_lighting_eigvecs,\n    const std::vector<float>& color_lighting_eigvals,\n    const std::vector<float>& mean_rgb,\n    const std::vector<float>& inv_std_rgb,\n    std::mt19937* randgen,\n    float* transformed_clip) {\n  CAFFE_ENFORCE_EQ(\n      channels_rgb, mean_rgb.size(), \"rgb channels must be equal to mean size\");\n  CAFFE_ENFORCE_EQ(\n      mean_rgb.size(),\n      inv_std_rgb.size(),\n      \"mean size must be equal to inv_std size\");\n  int orig_index, tran_index;\n  if (multi_crop_count == 1) {\n    // Case 1: Multi_cropping is disabled\n    // The order of output dimensions is C, L, H, W\n    bool do_color_jitter_lighting =\n        (color_jitter || color_lighting) && channels_rgb == 3;\n    for (int c = 0; c < channels_rgb; ++c) {\n      for (int l = 0; l < length_rgb; ++l) {\n        int orig_index_l =\n            l * sampling_rate_rgb * height * width * channels_rgb;\n        int tran_index_l = (c * length_rgb + l) * crop_height;\n\n        for (int h = 0; h < crop_height; ++h) {\n          int orig_index_h = orig_index_l + (h + h_off) * width * channels_rgb;\n          int tran_index_h = (tran_index_l + h) * crop_width;\n\n          for (int w = 0; w < crop_width; ++w) {\n            orig_index = orig_index_h + (w + w_off) * channels_rgb + c;\n\n            // mirror the frame\n            if (mirror_me) {\n              tran_index = tran_index_h + (crop_width - 1 - w);\n            } else {\n              tran_index = tran_index_h + w;\n            }\n\n            // normalize and transform the clip\n            if (do_color_jitter_lighting) {\n              transformed_clip[tran_index] = buffer_rgb[orig_index];\n            } else {\n              transformed_clip[tran_index] =\n                  (buffer_rgb[orig_index] - mean_rgb[c]) * inv_std_rgb[c];\n            }\n          }\n        }\n      }\n    }\n    if (color_jitter && channels_rgb == 3) {\n      ColorJitter(\n          transformed_clip,\n          length_rgb,\n          crop_height,\n          crop_width,\n          saturation,\n          brightness,\n          contrast,\n          randgen);\n    }\n    if (color_lighting && channels_rgb == 3) {\n      ColorLighting(\n          transformed_clip,\n          length_rgb,\n          crop_height,\n          crop_width,\n          color_lighting_std,\n          color_lighting_eigvecs,\n          color_lighting_eigvals,\n          randgen);\n    }\n    if (do_color_jitter_lighting) {\n      // Color normalization\n      // Mean subtraction and division by standard deviation.\n      ColorNormalization(\n          transformed_clip,\n          length_rgb,\n          crop_height,\n          crop_width,\n          channels_rgb,\n          mean_rgb,\n          inv_std_rgb);\n    }\n  } else {\n    // Case 2: Multi_cropping is enabled. Multi cropping should be only used at\n    // testing stage. So color jittering and lighting are not used\n    for (int multi_crop_mirror = 0; multi_crop_mirror < 2;\n         ++multi_crop_mirror) {\n      for (int i = 0; i < multi_crop_count / 2; ++i) {\n        for (int c = 0; c < channels_rgb; ++c) {\n          for (int l = 0; l < length_rgb; ++l) {\n            int orig_index_l =\n                l * sampling_rate_rgb * height * width * channels_rgb;\n            int tran_index_l = (c * length_rgb + l) * crop_height;\n\n            for (int h = 0; h < crop_height; ++h) {\n              int orig_index_h = orig_index_l +\n                  (h + multi_crop_h_off[i]) * width * channels_rgb;\n              int tran_index_h = (tran_index_l + h) * crop_width;\n\n              for (int w = 0; w < crop_width; ++w) {\n                orig_index =\n                    orig_index_h + (w + multi_crop_w_off[i]) * channels_rgb + c;\n\n                if (multi_crop_mirror == 1) {\n                  tran_index = tran_index_h + (crop_width - 1 - w);\n                } else {\n                  tran_index = tran_index_h + w;\n                }\n\n                transformed_clip[tran_index] =\n                    (buffer_rgb[orig_index] - mean_rgb[c]) * inv_std_rgb[c];\n              }\n            }\n          }\n        }\n        transformed_clip +=\n            channels_rgb * length_rgb * crop_height * crop_width;\n      }\n    }\n  }\n}\n\nvoid ClipTransformOpticalFlow(\n    const unsigned char* buffer_rgb,\n    const int crop_height,\n    const int crop_width,\n    const int length_of,\n    const int channels_of,\n    const int sampling_rate_of,\n    const int height,\n    const int width,\n    const cv::Rect& rect,\n    const int channels_rgb,\n    const bool mirror_me,\n    const int flow_alg_type,\n    const int flow_data_type,\n    const int frame_gap_of,\n    const bool do_flow_aggregation,\n    const std::vector<float>& mean_of,\n    const std::vector<float>& inv_std_of,\n    float* transformed_clip) {\n  const int frame_size = crop_height * crop_width;\n  const int channel_size_flow = length_of * frame_size;\n\n  // for get the mean and std of the input data\n  bool extract_statistics = false;\n  static std::vector<double> mean_static(channels_of, 0.f);\n  static std::vector<double> std_static(channels_of, 0.f);\n  static long long count = 0;\n  cv::Scalar mean_img, std_img;\n\n  for (int l = 0; l < length_of; l++) {\n    // get the grayscale frames\n    std::vector<cv::Mat> grays, rgbs;\n    int step_size = do_flow_aggregation ? 1 : frame_gap_of;\n    for (int j = 0; j <= frame_gap_of; j += step_size) {\n      // get the current frame\n      const unsigned char* curr_frame = buffer_rgb +\n          (l * sampling_rate_of + j) * height * width * channels_rgb;\n      cv::Mat img = cv::Mat::zeros(height, width, CV_8UC3);\n      memcpy(\n          img.data,\n          curr_frame,\n          height * width * channels_rgb * sizeof(unsigned char));\n\n      // crop and mirror the frame\n      cv::Mat img_cropped = img(rect);\n      if (mirror_me) {\n        cv::flip(img_cropped, img_cropped, 1);\n      }\n\n      cv::Mat gray;\n      cv::cvtColor(img_cropped, gray, cv::COLOR_RGB2GRAY);\n      grays.push_back(gray);\n      rgbs.push_back(img_cropped);\n    }\n\n    cv::Mat first_gray, first_rgb;\n    cv::Mat flow = cv::Mat::zeros(crop_height, crop_width, CV_32FC2);\n    MultiFrameOpticalFlowExtractor(grays, flow_alg_type, flow);\n\n    std::vector<cv::Mat> imgs;\n    cv::split(flow, imgs);\n    // save the 2-channel optical flow first\n    int c = 0;\n    for (; c < 2; c++) {\n      if (extract_statistics) {\n        cv::meanStdDev(imgs[c], mean_img, std_img);\n        mean_static[c] += mean_img[0];\n        std_static[c] += std_img[0];\n      }\n\n      imgs[c] -= mean_of[c];\n      imgs[c] *= inv_std_of[c];\n      memcpy(\n          transformed_clip + c * channel_size_flow + l * frame_size,\n          imgs[c].data,\n          frame_size * sizeof(float));\n    }\n\n    cv::Mat mag;\n    std::vector<cv::Mat> chans;\n    // augment the optical flow with more channels\n    switch (flow_data_type) {\n      case FlowDataType::Flow2C:\n        // nothing to do if we only need two channels\n        break;\n\n      case FlowDataType::Flow3C:\n        // use magnitude as the third channel\n        mag = cv::abs(imgs[0]) + cv::abs(imgs[1]);\n        if (extract_statistics) {\n          cv::meanStdDev(mag, mean_img, std_img);\n          mean_static[c] += mean_img[0];\n          std_static[c] += std_img[0];\n        }\n\n        mag -= mean_of[c];\n        mag *= inv_std_of[c];\n        memcpy(\n            transformed_clip + c * channel_size_flow + l * frame_size,\n            mag.data,\n            frame_size * sizeof(float));\n        break;\n\n      case FlowDataType::FlowWithGray:\n        // add grayscale image as the third channel\n        grays[0].convertTo(first_gray, CV_32FC1);\n        if (extract_statistics) {\n          cv::meanStdDev(first_gray, mean_img, std_img);\n          mean_static[c] += mean_img[0];\n          std_static[c] += std_img[0];\n        }\n\n        first_gray -= mean_of[c];\n        first_gray *= inv_std_of[c];\n        memcpy(\n            transformed_clip + c * channel_size_flow + l * frame_size,\n            first_gray.data,\n            frame_size * sizeof(float));\n        break;\n\n      case FlowDataType::FlowWithRGB:\n        // add all three rgb channels\n        rgbs[0].convertTo(first_rgb, CV_32FC3);\n        cv::split(first_rgb, chans);\n        for (; c < channels_of; c++) {\n          if (extract_statistics) {\n            cv::meanStdDev(chans[c - 2], mean_img, std_img);\n            mean_static[c] += mean_img[0];\n            std_static[c] += std_img[0];\n          }\n\n          chans[c - 2] -= mean_of[c];\n          chans[c - 2] *= inv_std_of[c];\n          memcpy(\n              transformed_clip + c * channel_size_flow + l * frame_size,\n              chans[c - 2].data,\n              frame_size * sizeof(float));\n        }\n        break;\n\n      default:\n        LOG(ERROR) << \"Unsupported optical flow data type \" << flow_data_type;\n        break;\n    }\n\n    if (extract_statistics) {\n      count++;\n      if (count % 1000 == 1) {\n        for (int i = 0; i < channels_of; i++) {\n          LOG(INFO) << i\n                    << \"-th channel mean: \" << mean_static[i] / float(count)\n                    << \" std: \" << std_static[i] / float(count);\n        }\n      }\n    }\n  }\n}\n\nvoid FreeDecodedData(\n    std::vector<std::unique_ptr<DecodedFrame>>& sampledFrames) {\n  // free the sampledFrames\n  for (int i = 0; i < sampledFrames.size(); i++) {\n    DecodedFrame* p = sampledFrames[i].release();\n    delete p;\n  }\n  sampledFrames.clear();\n}\n\nbool DecodeMultipleClipsFromVideo(\n    const char* video_buffer,\n    const std::string& video_filename,\n    const int encoded_size,\n    const Params& params,\n    const int start_frm,\n    const int clip_per_video,\n    const bool use_local_file,\n    int& height,\n    int& width,\n    std::vector<unsigned char*>& buffer_rgb) {\n  std::vector<std::unique_ptr<DecodedFrame>> sampledFrames;\n  VideoDecoder decoder;\n\n  // decoding from buffer or file\n  if (!use_local_file) {\n    decoder.decodeMemory(\n        video_buffer, encoded_size, params, start_frm, sampledFrames);\n  } else {\n    decoder.decodeFile(video_filename, params, start_frm, sampledFrames);\n  }\n\n  for (int i = 0; i < buffer_rgb.size(); i++) {\n    unsigned char* buff = buffer_rgb[i];\n    delete[] buff;\n  }\n  buffer_rgb.clear();\n\n  if (sampledFrames.size() < params.num_of_required_frame_) {\n    LOG(ERROR)\n        << \"The video seems faulty and we could not decode enough frames: \"\n        << sampledFrames.size() << \" VS \" << params.num_of_required_frame_;\n    FreeDecodedData(sampledFrames);\n    return true;\n  }\n\n  height = sampledFrames[0]->height_;\n  width = sampledFrames[0]->width_;\n  float sample_stepsz = 1.0;\n  if (clip_per_video > 1) {\n    sample_stepsz =\n        float(sampledFrames.size() - params.num_of_required_frame_) /\n        (clip_per_video - 1.0);\n  }\n  int image_size = 3 * height * width;\n  int clip_size = params.num_of_required_frame_ * image_size;\n  // get the RGB frames for each clip\n  for (int i = 0; i < clip_per_video; i++) {\n    unsigned char* buffer_rgb_ptr = new unsigned char[clip_size];\n    int clip_start = floor(i * sample_stepsz);\n    for (int j = 0; j < params.num_of_required_frame_; j++) {\n      memcpy(\n          buffer_rgb_ptr + j * image_size,\n          (unsigned char*)sampledFrames[j + clip_start]->data_.get(),\n          image_size * sizeof(unsigned char));\n    }\n    buffer_rgb.push_back(buffer_rgb_ptr);\n  }\n  FreeDecodedData(sampledFrames);\n\n  return true;\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "caffe2/video/video_io.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CAFFE2_VIDEO_VIDEO_IO_H_\n#define CAFFE2_VIDEO_VIDEO_IO_H_\n\n#include <caffe/proto/caffe.pb.h>\n#include <caffe2/video/optical_flow.h>\n#include <caffe2/video/video_decoder.h>\n#include <opencv2/opencv.hpp>\n#include <random>\n\n#include <istream>\n#include <ostream>\n\nnamespace caffe2 {\n\nvoid ClipTransformRGB(\n    const unsigned char* buffer_rgb,\n    const int multi_crop_count,\n    const int crop_height,\n    const int crop_width,\n    const int length_rgb,\n    const int channels_rgb,\n    const int sampling_rate_rgb,\n    const int height,\n    const int width,\n    const int h_off,\n    const int w_off,\n    const int* multi_crop_h_off,\n    const int* multi_crop_w_off,\n    const bool mirror_me,\n    const bool color_jitter,\n    const float saturation,\n    const float brightness,\n    const float contrast,\n    const bool color_lighting,\n    const float color_lighting_std,\n    const std::vector<std::vector<float>>& color_lighting_eigvecs,\n    const std::vector<float>& color_lighting_eigvals,\n    const std::vector<float>& mean_rgb,\n    const std::vector<float>& inv_std_rgb,\n    std::mt19937* randgen,\n    float* transformed_clip);\n\nvoid ClipTransformOpticalFlow(\n    const unsigned char* buffer_rgb,\n    const int crop_height,\n    const int crop_width,\n    const int length_of,\n    const int channels_of,\n    const int sampling_rate_of,\n    const int height,\n    const int width,\n    const cv::Rect& rect,\n    const int channels_rgb,\n    const bool mirror_me,\n    const int flow_alg_type,\n    const int flow_data_type,\n    const int frame_gap_of,\n    const bool do_flow_aggregation,\n    const std::vector<float>& mean_of,\n    const std::vector<float>& inv_std_of,\n    float* transformed_clip);\n\nvoid FreeDecodedData(std::vector<std::unique_ptr<DecodedFrame>>& sampledFrames);\n\nbool DecodeMultipleClipsFromVideo(\n    const char* video_buffer,\n    const std::string& video_filename,\n    const int encoded_size,\n    const Params& params,\n    const int start_frm,\n    const int clip_per_video,\n    const bool use_local_file,\n    int& height,\n    int& width,\n    std::vector<unsigned char*>& buffer_rgb);\n\n} // namespace caffe2\n\n#endif // CAFFE2_VIDEO_VIDEO_IO_H_\n"
  },
  {
    "path": "cmake/BuildVariables.cmake",
    "content": "# ---[ Declare variables that we are going to use across the Caffe2 build.\n# This file defines common, Caffe2-wide variables that we use to collect\n# source files and other things. Each variable is annotated with their\n# intended uses.\n# Note that adding and / or deleting these variables usually involves\n# changing the whole build system, so make sure you send a PR early if you\n# want to change them.\n\n# Caffe2_{CPU,GPU}_SRCS is the list that will have all the related source\n# files for CPU and GPU respectively. They will be filled with the\n# CMakeLists.txt files under each folder respectively.\nset(Caffe2_CPU_SRCS)\nset(Caffe2_GPU_SRCS)\n\n# Caffe2_{CPU,GPU}_TEST_SRCS is the list that will have all the related source\n# files for CPU and GPU tests respectively.\nset(Caffe2_CPU_TEST_SRCS)\nset(Caffe2_GPU_TEST_SRCS)\n\n# Caffe2_MAIN_LIBS is a list of the libraries that a dependent library should\n# depend on when it links against Caffe2.\nset(Caffe2_MAIN_LIBS)\n\n# Lists for Caffe2 dependency libraries, for CPU and CUDA respectively.\nset(Caffe2_DEPENDENCY_LIBS \"\")\nset(Caffe2_CUDA_DEPENDENCY_LIBS \"\")\n# This variable contains dependency libraries of Caffe2 which requires whole\n# symbol linkage. One example is the onnx lib where we need all its schema \n# symbols. However, if the lib is whole linked in caffe2 lib, we don't want \n# it to be linked in binaries that will link caffe2 lib. Because if caffe2 lib\n# is built as dynamic library, it will result in two copied of symbols of \n# Caffe2_DEPENDENCY_WHOLE_LINK_LIBS existing in caffe2.so and the binary, which\n# will cause issues. Therefore Caffe2_DEPENDENCY_WHOLE_LINK_LIBS will only\n# be linked by caffe2 lib. \nset(Caffe2_DEPENDENCY_WHOLE_LINK_LIBS \"\")\n\n# Lists for Caffe2 public dependency libraries. These libraries will be\n# transitive to any libraries that depends on Caffe2.\nset(Caffe2_PUBLIC_DEPENDENCY_LIBS \"\")\nset(Caffe2_PUBLIC_CUDA_DEPENDENCY_LIBS \"\")\n\n# List of modules that is built as part of the main Caffe2 build. For all\n# binary targets, such as Python and native binaries, they will be linked\n# automatically with these modules.\nset(Caffe2_MODULES \"\")\n"
  },
  {
    "path": "cmake/Caffe2Config.cmake.in",
    "content": "# - Config file for the Caffe2 package\n# It defines the following variable(s)\n#   CAFFE2_INCLUDE_DIRS     - include directories for FooBar\n# as well as Caffe2 targets for other cmake libraries to use.\n\n# library version information\n\nset(CAFFE2_VERSION_MAJOR @CAFFE2_VERSION_MAJOR@)\nset(CAFFE2_VERSION_MINOR @CAFFE2_VERSION_MINOR@)\nset(CAFFE2_VERSION_PATCH @CAFFE2_VERSION_PATCH@)\nset(CAFFE2_VERSION \"@CAFFE2_VERSION@\")\n\n# Utils functions.\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/public/utils.cmake\")\n\n# Depending on whether Caffe2 is built with threads, include threads lib.\nif(@USE_THREADS@)\n  include(\"${CMAKE_CURRENT_LIST_DIR}/public/threads.cmake\")\nendif()\n\n# Depending on whether Caffe2 uses gflags during compile time or\n# not, invoke gflags.\nif (@USE_GFLAGS@)\n  include(\"${CMAKE_CURRENT_LIST_DIR}/public/gflags.cmake\")\n  if (NOT TARGET gflags)\n    message(FATAL_ERROR\n        \"Your installed Caffe2 version uses gflags but the gflags library \"\n        \"cannot be found. Did you accidentally remove it, or have you set \"\n        \"the right CMAKE_PREFIX_PATH and/or GFLAGS_ROOT_DIR? If you do not \"\n        \"have gflags, you will need to install gflags and set the library \"\n        \"path accordingly.\")\n  endif()\nendif()\n\n# Depending on whether Caffe2 uses glog during compile time or\n# not, invoke glog.\nif (@USE_GLOG@)\n  include(\"${CMAKE_CURRENT_LIST_DIR}/public/glog.cmake\")\n  if (NOT TARGET glog::glog)\n    message(FATAL_ERROR\n        \"Your installed Caffe2 version uses glog but the glog library \"\n        \"cannot be found. Did you accidentally remove it, or have you set \"\n        \"the right CMAKE_PREFIX_PATH and/or GFLAGS_ROOT_DIR? If you do not \"\n        \"have glog, you will need to install glog and set the library \"\n        \"path accordingly.\")\n  endif()\nendif()\n\n# Protobuf\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/public/protobuf.cmake\")\nif (NOT TARGET protobuf::libprotobuf)\n  message(FATAL_ERROR\n      \"Your installed Caffe2 version uses protobuf but the protobuf library \"\n      \"cannot be found. Did you accidentally remove it, or have you set \"\n      \"the right CMAKE_PREFIX_PATH? If you do not have protobuf, you will \"\n      \"need to install protobuf and set the library path accordingly.\")\nendif()\nmessage(STATUS \"Caffe2: Protobuf version \" ${Protobuf_VERSION})\n# If during build time we know the protobuf version, we will also do a sanity\n# check to ensure that the protobuf library that Caffe2 found is consistent with\n# the compiled version.\nif (@CAFFE2_KNOWN_PROTOBUF_VERSION@)\n  if (NOT (${Protobuf_VERSION} VERSION_EQUAL @Protobuf_VERSION@))\n    message(FATAL_ERROR\n        \"Your installed Caffe2 is built with protobuf \"\n        \"@Protobuf_VERSION@\"\n        \", while your current cmake setting discovers protobuf version \"\n        ${Protobuf_VERSION}\n        \". Please specify a protobuf version that is the same as the built \"\n        \"version.\")\n  endif()\nendif()\n\n# Cuda\nif (@USE_CUDA@)\n  include(\"${CMAKE_CURRENT_LIST_DIR}/public/cuda.cmake\")\n  if (NOT CAFFE2_FOUND_CUDA)\n    message(FATAL_ERROR\n        \"Your installed Caffe2 version uses cuda but I cannot find the cuda \"\n        \"libraries. Please set the proper cuda prefixes and / or install \"\n        \"cuda.\")\n  endif()\nendif()\n\n# import targets\ninclude (\"${CMAKE_CURRENT_LIST_DIR}/Caffe2Targets.cmake\")\n\n# Interface libraries, that allows one to build proper link flags.\n# We will also define a helper variable, Caffe2_MAIN_LIBS, that resolves to\n# the main caffe2 libraries in cases of cuda presence / absence.\ncaffe2_interface_library(caffe2 caffe2_library)\nif (@USE_CUDA@)\n  caffe2_interface_library(caffe2_gpu caffe2_gpu_library)\n  set(Caffe2_MAIN_LIBS caffe2_library caffe2_gpu_library)\nelse()\n  set(Caffe2_MAIN_LIBS caffe2_library)\nendif()\n\n# include directory.\n#\n# Newer versions of CMake set the INTERFACE_INCLUDE_DIRECTORIES property\n# of the imported targets. It is hence not necessary to add this path\n# manually to the include search path for targets which link to gflags.\n# The following lines are here for backward compatibility, in case one\n# would like to use the old-style include path.\nget_filename_component(\n    CMAKE_CURRENT_LIST_DIR \"${CMAKE_CURRENT_LIST_FILE}\" PATH)\n# Note: the current list dir is _INSTALL_PREFIX/share/cmake/Gloo.\nget_filename_component(\n    _INSTALL_PREFIX \"${CMAKE_CURRENT_LIST_DIR}/../../../\" ABSOLUTE)\nset(CAFFE2_INCLUDE_DIRS \"${_INSTALL_PREFIX}/include\")\n"
  },
  {
    "path": "cmake/Caffe2ConfigVersion.cmake.in",
    "content": "set(PACKAGE_VERSION \"@CAFFE2_VERSION@\")\n \n# Check whether the requested PACKAGE_FIND_VERSION is compatible\nif(\"${PACKAGE_VERSION}\" VERSION_LESS \"${PACKAGE_FIND_VERSION}\")\n  set(PACKAGE_VERSION_COMPATIBLE FALSE)\nelse()\n  set(PACKAGE_VERSION_COMPATIBLE TRUE)\n  if (\"${PACKAGE_VERSION}\" VERSION_EQUAL \"${PACKAGE_FIND_VERSION}\")\n    set(PACKAGE_VERSION_EXACT TRUE)\n  endif()\nendif()\n"
  },
  {
    "path": "cmake/Dependencies.cmake",
    "content": "# ---[ Custom Protobuf\ninclude(\"cmake/ProtoBuf.cmake\")\n\n# ---[ Threads\nif(USE_THREADS)\n  include(cmake/public/threads.cmake)\n  list(APPEND Caffe2_PUBLIC_DEPENDENCY_LIBS Threads::Threads)\nendif()\n\n# ---[ protobuf\nif(USE_LITE_PROTO)\n  set(CAFFE2_USE_LITE_PROTO 1)\nendif()\n\n# ---[ git: used to generate git build string.\nfind_package(Git)\nif(GIT_FOUND)\n  execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --always --dirty\n                  ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE\n                  WORKING_DIRECTORY \"${PROJECT_SOURCE_DIR}\"\n                  OUTPUT_VARIABLE CAFFE2_GIT_VERSION\n                  RESULT_VARIABLE __git_result)\n  if(NOT ${__git_result} EQUAL 0)\n    set(CAFFE2_GIT_VERSION \"unknown\")\n  endif()\nelse()\n  message(\n      WARNING\n      \"Cannot find git, so Caffe2 won't have any git build info available\")\nendif()\n\n\n# ---[ BLAS\nset(BLAS \"Eigen\" CACHE STRING \"Selected BLAS library\")\nset_property(CACHE BLAS PROPERTY STRINGS \"Eigen;ATLAS;OpenBLAS;MKL;vecLib\")\nmessage(STATUS \"The BLAS backend of choice:\" ${BLAS})\n\nif(BLAS STREQUAL \"Eigen\")\n  # Eigen is header-only and we do not have any dependent libraries\n  set(CAFFE2_USE_EIGEN_FOR_BLAS 1)\nelseif(BLAS STREQUAL \"ATLAS\")\n  find_package(Atlas REQUIRED)\n  include_directories(${ATLAS_INCLUDE_DIRS})\n  list(APPEND Caffe2_PUBLIC_DEPENDENCY_LIBS ${ATLAS_LIBRARIES})\n  list(APPEND Caffe2_PUBLIC_DEPENDENCY_LIBS cblas)\nelseif(BLAS STREQUAL \"OpenBLAS\")\n  find_package(OpenBLAS REQUIRED)\n  include_directories(${OpenBLAS_INCLUDE_DIR})\n  list(APPEND Caffe2_PUBLIC_DEPENDENCY_LIBS ${OpenBLAS_LIB})\nelseif(BLAS STREQUAL \"MKL\")\n  find_package(MKL REQUIRED)\n  include_directories(${MKL_INCLUDE_DIR})\n  list(APPEND Caffe2_PUBLIC_DEPENDENCY_LIBS ${MKL_LIBRARIES})\n  set(CAFFE2_USE_MKL 1)\nelseif(BLAS STREQUAL \"vecLib\")\n  find_package(vecLib REQUIRED)\n  include_directories(${vecLib_INCLUDE_DIR})\n  list(APPEND Caffe2_PUBLIC_DEPENDENCY_LIBS ${vecLib_LINKER_LIBS})\nelse()\n  message(FATAL_ERROR \"Unrecognized blas option:\" ${BLAS})\nendif()\n\n# Directory where NNPACK and cpuinfo will download and build all dependencies\nset(CONFU_DEPENDENCIES_SOURCE_DIR ${PROJECT_BINARY_DIR}/confu-srcs\n  CACHE PATH \"Confu-style dependencies source directory\")\nset(CONFU_DEPENDENCIES_BINARY_DIR ${PROJECT_BINARY_DIR}/confu-deps\n  CACHE PATH \"Confu-style dependencies binary directory\")\n\n# ---[ NNPACK\nif(USE_NNPACK)\n  include(\"cmake/External/nnpack.cmake\")\n  if(NNPACK_FOUND)\n    if(TARGET nnpack)\n      # ---[ NNPACK is being built together with Caffe2: explicitly specify dependency\n      list(APPEND Caffe2_DEPENDENCY_LIBS nnpack)\n    else()\n      include_directories(${NNPACK_INCLUDE_DIRS})\n      list(APPEND Caffe2_DEPENDENCY_LIBS ${NNPACK_LIBRARIES})\n    endif()\n  else()\n    message(WARNING \"Not compiling with NNPACK. Suppress this warning with -DUSE_NNPACK=OFF\")\n    set(USE_NNPACK OFF)\n  endif()\nendif()\n\n# ---[ Caffe2 uses cpuinfo library in the thread pool\nif (NOT TARGET cpuinfo)\n  if (NOT DEFINED CPUINFO_SOURCE_DIR)\n    set(CPUINFO_SOURCE_DIR \"${PROJECT_SOURCE_DIR}/third_party/cpuinfo\" CACHE STRING \"cpuinfo source directory\")\n  endif()\n\n  set(CPUINFO_BUILD_TOOLS OFF CACHE BOOL \"\")\n  set(CPUINFO_BUILD_UNIT_TESTS OFF CACHE BOOL \"\")\n  set(CPUINFO_BUILD_MOCK_TESTS OFF CACHE BOOL \"\")\n  set(CPUINFO_BUILD_BENCHMARKS OFF CACHE BOOL \"\")\n  set(CPUINFO_LIBRARY_TYPE \"static\" CACHE STRING \"\")\n  add_subdirectory(\n    \"${CPUINFO_SOURCE_DIR}\"\n    \"${CONFU_DEPENDENCIES_BINARY_DIR}/cpuinfo\")\n  # We build static version of cpuinfo but link\n  # them into a shared library for Caffe2, so they need PIC.\n  set_property(TARGET cpuinfo PROPERTY POSITION_INDEPENDENT_CODE ON)\nendif()\nlist(APPEND Caffe2_DEPENDENCY_LIBS cpuinfo)\n\n# ---[ gflags\nif(USE_GFLAGS)\n  include(cmake/public/gflags.cmake)\n  if (TARGET gflags)\n    set(CAFFE2_USE_GFLAGS 1)\n    list(APPEND Caffe2_PUBLIC_DEPENDENCY_LIBS gflags)\n  else()\n    message(WARNING\n        \"gflags is not found. Caffe2 will build without gflags support but \"\n        \"it is strongly recommended that you install gflags. Suppress this \"\n        \"warning with -DUSE_GFLAGS=OFF\")\n    set(USE_GFLAGS OFF)\n  endif()\nendif()\n\n# ---[ Google-glog\nif(USE_GLOG)\n  include(cmake/public/glog.cmake)\n  if (TARGET glog::glog)\n    set(CAFFE2_USE_GOOGLE_GLOG 1)\n    list(APPEND Caffe2_PUBLIC_DEPENDENCY_LIBS glog::glog)\n  else()\n    message(WARNING\n        \"glog is not found. Caffe2 will build without glog support but it is \"\n        \"strongly recommended that you install glog. Suppress this warning \"\n        \"with -DUSE_GLOG=OFF\")\n    set(USE_GLOG OFF)\n  endif()\nendif()\n\n\n# ---[ Googletest and benchmark\nif(BUILD_TEST)\n  set(TEMP_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})\n  # We will build gtest as static libs and embed it directly into the binary.\n  set(BUILD_SHARED_LIBS OFF)\n  # For gtest, we will simply embed it into our test binaries, so we won't\n  # need to install it.\n  set(BUILD_GTEST ON)\n  set(INSTALL_GTEST OFF)\n  # We currently don't need gmock right now.\n  set(BUILD_GMOCK OFF)\n  # For Windows, we will check the runtime used is correctly passed in.\n  if (NOT CAFFE2_USE_MSVC_STATIC_RUNTIME)\n    set(gtest_force_shared_crt ON)\n  endif()\n  add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/googletest)\n  include_directories(${PROJECT_SOURCE_DIR}/third_party/googletest/googletest/include)\n\n  # We will not need to test benchmark lib itself.\n  set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL \"Disable benchmark testing as we don't need it.\")\n  # We will not need to install benchmark since we link it statically.\n  set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL \"Disable benchmark install to avoid overwriting vendor install.\")\n  add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/benchmark)\n  include_directories(${PROJECT_SOURCE_DIR}/third_party/benchmark/include)\n\n  # Recover the build shared libs option.\n  set(BUILD_SHARED_LIBS ${TEMP_BUILD_SHARED_LIBS})\nendif()\n\n# ---[ LMDB\nif(USE_LMDB)\n  find_package(LMDB)\n  if(LMDB_FOUND)\n    include_directories(${LMDB_INCLUDE_DIR})\n    list(APPEND Caffe2_DEPENDENCY_LIBS ${LMDB_LIBRARIES})\n  else()\n    message(WARNING \"Not compiling with LMDB. Suppress this warning with -DUSE_LMDB=OFF\")\n    set(USE_LMDB OFF)\n  endif()\nendif()\n\n# ---[ LevelDB\n# ---[ Snappy\nif(USE_LEVELDB)\n  find_package(LevelDB)\n  find_package(Snappy)\n  if(LEVELDB_FOUND AND SNAPPY_FOUND)\n    include_directories(${LevelDB_INCLUDE})\n    list(APPEND Caffe2_DEPENDENCY_LIBS ${LevelDB_LIBRARIES})\n    include_directories(${Snappy_INCLUDE_DIR})\n    list(APPEND Caffe2_DEPENDENCY_LIBS ${Snappy_LIBRARIES})\n  else()\n    message(WARNING \"Not compiling with LevelDB. Suppress this warning with -DUSE_LEVELDB=OFF\")\n    set(USE_LEVELDB OFF)\n  endif()\nendif()\n\n# ---[ NUMA\nif(USE_NUMA)\n  if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL \"Linux\")\n    message(WARNING \"NUMA is currently only supported under Linux.\")\n    set(USE_NUMA OFF)\n  else()\n    find_package(Numa)\n    if(NUMA_FOUND)\n      include_directories(${Numa_INCLUDE_DIR})\n      list(APPEND Caffe2_DEPENDENCY_LIBS ${Numa_LIBRARIES})\n    else()\n      message(WARNING \"Not compiling with NUMA. Suppress this warning with -DUSE_NUMA=OFF\")\n      set(USE_NUMA OFF)\n    endif()\n  endif()\nendif()\n\n# ---[ ZMQ\nif(USE_ZMQ)\n  find_package(ZMQ)\n  if(ZMQ_FOUND)\n    include_directories(${ZMQ_INCLUDE_DIR})\n    list(APPEND Caffe2_DEPENDENCY_LIBS ${ZMQ_LIBRARIES})\n  else()\n    message(WARNING \"Not compiling with ZMQ. Suppress this warning with -DUSE_ZMQ=OFF\")\n    set(USE_ZMQ OFF)\n  endif()\nendif()\n\n# ---[ Redis\nif(USE_REDIS)\n  find_package(Hiredis)\n  if(HIREDIS_FOUND)\n    include_directories(${Hiredis_INCLUDE})\n    list(APPEND Caffe2_DEPENDENCY_LIBS ${Hiredis_LIBRARIES})\n  else()\n    message(WARNING \"Not compiling with Redis. Suppress this warning with -DUSE_REDIS=OFF\")\n    set(USE_REDIS OFF)\n  endif()\nendif()\n\n\n# ---[ OpenCV\nif(USE_OPENCV)\n  # OpenCV 3\n  find_package(OpenCV 3 QUIET COMPONENTS core highgui imgproc imgcodecs)\n  if(NOT OpenCV_FOUND)\n    # OpenCV 2\n    find_package(OpenCV QUIET COMPONENTS core highgui imgproc)\n  endif()\n  if(OpenCV_FOUND)\n    include_directories(${OpenCV_INCLUDE_DIRS})\n    list(APPEND Caffe2_DEPENDENCY_LIBS ${OpenCV_LIBS})\n    message(STATUS \"OpenCV found (${OpenCV_CONFIG_PATH})\")\n  else()\n    message(WARNING \"Not compiling with OpenCV. Suppress this warning with -DUSE_OPENCV=OFF\")\n    set(USE_OPENCV OFF)\n  endif()\nendif()\n\n# ---[ FFMPEG\nif(USE_FFMPEG)\n  find_package(FFmpeg REQUIRED)\n  if (FFMPEG_FOUND)\n    message(\"Found FFMPEG/LibAV libraries\")\n    include_directories(${FFMPEG_INCLUDE_DIR})\n    list(APPEND Caffe2_DEPENDENCY_LIBS ${FFMPEG_LIBRARIES})\n  else ()\n    message(\"Not compiling with FFmpeg. Suppress this warning with -DUSE_FFMPEG=OFF\")\n    set(USE_FFMPEG OFF)\n  endif ()\nendif()\n\n# ---[ EIGEN\n# Due to license considerations, we will only use the MPL2 parts of Eigen.\nset(EIGEN_MPL2_ONLY 1)\nfind_package(Eigen3)\nif(EIGEN3_FOUND)\n  message(STATUS \"Found system Eigen at \" ${EIGEN3_INCLUDE_DIR})\n  include_directories(${EIGEN3_INCLUDE_DIR})\nelse()\n  message(STATUS \"Did not find system Eigen. Using third party subdirectory.\")\n  include_directories(${PROJECT_SOURCE_DIR}/third_party/eigen)\nendif()\n\n# ---[ Python + Numpy\nif(BUILD_PYTHON)\n  set(Python_ADDITIONAL_VERSIONS 2.8 2.7 2.6)\n  find_package(PythonInterp 2.7)\n  find_package(PythonLibs 2.7)\n  find_package(NumPy REQUIRED)\n  # Observers are required in the python build\n  set(USE_OBSERVERS ON)\n  if(PYTHONINTERP_FOUND AND PYTHONLIBS_FOUND AND NUMPY_FOUND)\n    include_directories(${PYTHON_INCLUDE_DIRS} ${NUMPY_INCLUDE_DIR})\n  else()\n    message(WARNING \"Python dependencies not met. Not compiling with python. Suppress this warning with -DBUILD_PYTHON=OFF\")\n    set(BUILD_PYTHON OFF)\n  endif()\nendif()\n\n# ---[ pybind11\nfind_package(pybind11)\nif(pybind11_FOUND)\n  include_directories(${pybind11_INCLUDE_DIRS})\nelse()\n  include_directories(${PROJECT_SOURCE_DIR}/third_party/pybind11/include)\nendif()\n\n# ---[ MPI\nif(USE_MPI)\n  find_package(MPI)\n  if(MPI_CXX_FOUND)\n    message(STATUS \"MPI support found\")\n    message(STATUS \"MPI compile flags: \" ${MPI_CXX_COMPILE_FLAGS})\n    message(STATUS \"MPI include path: \" ${MPI_CXX_INCLUDE_PATH})\n    message(STATUS \"MPI LINK flags path: \" ${MPI_CXX_LINK_FLAGS})\n    message(STATUS \"MPI libraries: \" ${MPI_CXX_LIBRARIES})\n    include_directories(${MPI_CXX_INCLUDE_PATH})\n    list(APPEND Caffe2_DEPENDENCY_LIBS ${MPI_CXX_LIBRARIES})\n    set(CMAKE_EXE_LINKER_FLAGS ${MPI_CXX_LINK_FLAGS})\n    find_program(OMPI_INFO\n      NAMES ompi_info\n      HINTS ${MPI_CXX_LIBRARIES}/../bin)\n    if(OMPI_INFO)\n      execute_process(COMMAND ${OMPI_INFO}\n                      OUTPUT_VARIABLE _output)\n      if(_output MATCHES \"smcuda\")\n        message(STATUS \"Found OpenMPI with CUDA support built.\")\n      else()\n        message(WARNING \"OpenMPI found, but it is not built with CUDA support.\")\n        set(CAFFE2_FORCE_FALLBACK_CUDA_MPI 1)\n      endif()\n    endif()\n  else()\n    message(WARNING \"Not compiling with MPI. Suppress this warning with -DUSE_MPI=OFF\")\n    set(USE_MPI OFF)\n  endif()\nendif()\n\n# ---[ OpenMP\nif(USE_OPENMP)\n  find_package(OpenMP)\n  if(OPENMP_FOUND)\n    message(STATUS \"Adding \" ${OpenMP_CXX_FLAGS})\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}\")\n  else()\n    message(WARNING \"Not compiling with OpenMP. Suppress this warning with -DUSE_OPENMP=OFF\")\n    set(USE_OPENMP OFF)\n  endif()\nendif()\n\n\n# ---[ Android specific ones\nif(ANDROID)\n  list(APPEND Caffe2_DEPENDENCY_LIBS log)\nendif()\n\n# ---[ CUDA\nif(USE_CUDA)\n  include(cmake/public/cuda.cmake)\n  if(CAFFE2_FOUND_CUDA)\n    # A helper variable recording the list of Caffe2 dependent librareis\n    # caffe2::cudart is dealt with separately, due to CUDA_ADD_LIBRARY\n    # design reason (it adds CUDA_LIBRARIES itself).\n    set(Caffe2_PUBLIC_CUDA_DEPENDENCY_LIBS\n        caffe2::cuda caffe2::curand caffe2::cublas caffe2::cudnn caffe2::nvrtc)\n  else()\n    message(WARNING\n        \"Not compiling with CUDA. Suppress this warning with \"\n        \"-DUSE_CUDA=OFF.\")\n    set(USE_CUDA OFF)\n  endif()\nendif()\n\n# ---[ NCCL\nif(USE_NCCL)\n  if(NOT USE_CUDA)\n    message(WARNING \"If not using cuda, one should not use NCCL either.\")\n    set(USE_NCCL OFF)\n  elseif(NOT ${CMAKE_SYSTEM_NAME} STREQUAL \"Linux\")\n    message(WARNING \"NCCL is currently only supported under Linux.\")\n    set(USE_NCCL OFF)\n  else()\n    include(\"cmake/External/nccl.cmake\")\n    list(APPEND Caffe2_CUDA_DEPENDENCY_LIBS __caffe2_nccl)\n  endif()\nendif()\n\n# ---[ CUB\nif(USE_CUDA)\n  find_package(CUB)\n  if(CUB_FOUND)\n    include_directories(${CUB_INCLUDE_DIRS})\n  else()\n    include_directories(${PROJECT_SOURCE_DIR}/third_party/cub)\n  endif()\nendif()\n\nif(USE_GLOO)\n  if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL \"Linux\")\n    message(WARNING \"Gloo can only be used on Linux.\")\n    set(USE_GLOO OFF)\n  elseif(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)\n    message(WARNING \"Gloo can only be used on 64-bit systems.\")\n    set(USE_GLOO OFF)\n  else()\n    set(Gloo_USE_CUDA ${USE_CUDA})\n    find_package(Gloo)\n    if(Gloo_FOUND)\n      include_directories(${Gloo_INCLUDE_DIRS})\n      list(APPEND Caffe2_DEPENDENCY_LIBS gloo)\n    else()\n      set(GLOO_INSTALL OFF CACHE BOOL \"\" FORCE)\n      set(GLOO_STATIC_OR_SHARED STATIC CACHE STRING \"\" FORCE)\n\n      # Temporarily override variables to avoid building Gloo tests/benchmarks\n      set(__BUILD_TEST ${BUILD_TEST})\n      set(__BUILD_BENCHMARK ${BUILD_BENCHMARK})\n      set(BUILD_TEST OFF)\n      set(BUILD_BENCHMARK OFF)\n      add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/gloo)\n      # Here is a little bit hacky. We have to put PROJECT_BINARY_DIR in front\n      # of PROJECT_SOURCE_DIR with/without conda system. The reason is that\n      # gloo generates a new config.h in the binary diretory.\n      include_directories(BEFORE SYSTEM ${PROJECT_SOURCE_DIR}/third_party/gloo)\n      include_directories(BEFORE SYSTEM ${PROJECT_BINARY_DIR}/third_party/gloo)\n      set(BUILD_TEST ${__BUILD_TEST})\n      set(BUILD_BENCHMARK ${__BUILD_BENCHMARK})\n\n      # Add explicit dependency if NCCL is built from third_party.\n      # Without dependency, make -jN with N>1 can fail if the NCCL build\n      # hasn't finished when CUDA targets are linked.\n      if(NCCL_EXTERNAL)\n        add_dependencies(gloo_cuda nccl_external)\n      endif()\n    endif()\n    # Pick the right dependency depending on USE_CUDA\n    list(APPEND Caffe2_DEPENDENCY_LIBS gloo)\n    if(USE_CUDA)\n      list(APPEND Caffe2_CUDA_DEPENDENCY_LIBS gloo_cuda)\n    endif()\n  endif()\nendif()\n\n# ---[ profiling\nif(USE_PROF)\n  find_package(htrace)\n  if(htrace_FOUND)\n    set(USE_PROF_HTRACE ON)\n  else()\n    message(WARNING \"htrace not found. Caffe2 will build without htrace prof\")\n  endif()\nendif()\n\nif (USE_MOBILE_OPENGL)\n  if (ANDROID)\n    list(APPEND Caffe2_DEPENDENCY_LIBS EGL GLESv2)\n  elseif (IOS)\n    message(STATUS \"TODO item for adding ios opengl dependency\")\n  else()\n    message(WARNING \"mobile opengl is only used in android or ios builds.\")\n    set(USE_MOBILE_OPENGL OFF)\n  endif()\nendif()\n\n# ---[ ARM Compute Library: check compatibility.\nif (USE_ACL)\n  if (NOT ANDROID)\n    message(WARNING \"ARM Compute Library is only supported for Android builds.\")\n    set(USE_ACL OFF)\n  else()\n    if (CMAKE_SYSTEM_PROCESSOR MATCHES \"^armv\")\n      # 32-bit ARM (armv7, armv7-a, armv7l, etc)\n      set(ACL_ARCH \"armv7a\")\n    elseif (CMAKE_SYSTEM_PROCESSOR MATCHES \"^(arm64|aarch64)$\")\n      # 64-bit ARM\n      set(ACL_ARCH \"arm64-v8a\")\n    else()\n      message(WARNING \"ARM Compute Library is only supported for ARM/ARM64 builds.\")\n      set(USE_ACL OFF)\n    endif()\n  endif()\nendif()\n\n# ---[ ARM Compute Library: build the target.\nif (USE_ACL)\n  list(APPEND ARM_COMPUTE_INCLUDE_DIRS \"third_party/ComputeLibrary/\")\n  list(APPEND ARM_COMPUTE_INCLUDE_DIRS \"third_party/ComputeLibrary/include\")\n  include_directories(${ARM_COMPUTE_INCLUDE_DIRS})\n  string (REPLACE \";\" \" -I\" ANDROID_STL_INCLUDE_FLAGS \"-I${ANDROID_STL_INCLUDE_DIRS}\")\n  set (ARM_COMPUTE_SRC_DIR \"${PROJECT_SOURCE_DIR}/third_party/ComputeLibrary/\")\n  set (ARM_COMPUTE_LIB \"${CMAKE_CURRENT_BINARY_DIR}/libarm_compute.a\")\n  set (ARM_COMPUTE_CORE_LIB \"${CMAKE_CURRENT_BINARY_DIR}/libarm_compute_core.a\")\n  set (ARM_COMPUTE_LIBS ${ARM_COMPUTE_LIB} ${ARM_COMPUTE_CORE_LIB})\n\n  add_custom_command(\n      OUTPUT ${ARM_COMPUTE_LIBS}\n      COMMAND\n        /bin/sh -c \"export PATH=\\\"$PATH:$(dirname ${CMAKE_CXX_COMPILER})\\\" && \\\n        scons -C \\\"${ARM_COMPUTE_SRC_DIR}\\\" -Q \\\n          examples=no validation_tests=no benchmark_tests=no standalone=yes \\\n          embed_kernels=yes opencl=no gles_compute=yes \\\n          os=android arch=${ACL_ARCH} \\\n          extra_cxx_flags=\\\"${ANDROID_CXX_FLAGS} ${ANDROID_STL_INCLUDE_FLAGS}\\\"\" &&\n        /bin/sh -c \"cp ${ARM_COMPUTE_SRC_DIR}/build/libarm_compute-static.a ${CMAKE_CURRENT_BINARY_DIR}/libarm_compute.a\" &&\n        /bin/sh -c \"cp ${ARM_COMPUTE_SRC_DIR}/build/libarm_compute_core-static.a ${CMAKE_CURRENT_BINARY_DIR}/libarm_compute_core.a\" &&\n        /bin/sh -c \"rm -r ${ARM_COMPUTE_SRC_DIR}/build\"\n      COMMENT \"Building ARM compute library\" VERBATIM)\n  add_custom_target(arm_compute_build ALL DEPENDS ${ARM_COMPUTE_LIBS})\n\n  add_library(arm_compute_core STATIC IMPORTED)\n  add_dependencies(arm_compute_core arm_compute_build)\n  set_property(TARGET arm_compute_core PROPERTY IMPORTED_LOCATION ${ARM_COMPUTE_CORE_LIB})\n\n  add_library(arm_compute STATIC IMPORTED)\n  add_dependencies(arm_compute arm_compute_build)\n  set_property(TARGET arm_compute PROPERTY IMPORTED_LOCATION ${ARM_COMPUTE_LIB})\n\n  list(APPEND Caffe2_DEPENDENCY_LIBS arm_compute arm_compute_core)\nendif()\n\nif (USE_SNPE AND ANDROID)\n  if (SNPE_LOCATION AND SNPE_HEADERS)\n    message(STATUS \"Using SNPE location specified by -DSNPE_LOCATION: \" ${SNPE_LOCATION})\n    message(STATUS \"Using SNPE headers specified by -DSNPE_HEADERS: \" ${SNPE_HEADERS})\n    include_directories(SYSTEM ${SNPE_HEADERS})\n    add_library(snpe SHARED IMPORTED)\n    set_property(TARGET snpe PROPERTY IMPORTED_LOCATION ${SNPE_LOCATION})\n    list(APPEND Caffe2_DEPENDENCY_LIBS snpe)\n  else()\n    set(USE_SNPE OFF)\n  endif()\nendif()\n\nif (USE_METAL)\n  if (NOT IOS)\n    message(WARNING \"Metal is only used in ios builds.\")\n    set(USE_METAL OFF)\n  endif()\nendif()\n\nif (USE_NNAPI AND NOT ANDROID)\n  message(WARNING \"NNApi is only used in android builds.\")\n  set(USE_NNAPI OFF)\nendif()\n\nif (USE_ATEN)\n  list(APPEND Caffe2_DEPENDENCY_LIBS aten_op_header_gen ATen)\n  include_directories(${PROJECT_BINARY_DIR}/caffe2/contrib/aten/aten/src/ATen)\n  include_directories(${PROJECT_SOURCE_DIR}/third_party/aten/src)\n  include_directories(${PROJECT_BINARY_DIR}/caffe2/contrib/aten)\nendif()\n\nif (USE_ZSTD)\n  list(APPEND Caffe2_DEPENDENCY_LIBS libzstd_static)\n  include_directories(${PROJECT_SOURCE_DIR}/third_party/zstd/lib)\n  add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/zstd/build/cmake)\n  set_property(TARGET libzstd_static PROPERTY POSITION_INDEPENDENT_CODE ON)\nendif()\n\n# ---[ Onnx\nSET(ONNX_NAMESPACE \"onnx_c2\")\nif(EXISTS \"${CAFFE2_CUSTOM_PROTOC_EXECUTABLE}\")\n  set(ONNX_CUSTOM_PROTOC_EXECUTABLE ${CAFFE2_CUSTOM_PROTOC_EXECUTABLE})\nendif()\nset(TEMP_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})\n# We will build onnx as static libs and embed it directly into the binary.\nset(BUILD_SHARED_LIBS OFF)\nset(ONNX_USE_MSVC_STATIC_RUNTIME ${CAFFE2_USE_MSVC_STATIC_RUNTIME})\nadd_subdirectory(${PROJECT_SOURCE_DIR}/third_party/onnx)\ninclude_directories(${ONNX_INCLUDE_DIRS})\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -DONNX_NAMESPACE=${ONNX_NAMESPACE}\")\ncaffe2_interface_library(onnx onnx_library)\nlist(APPEND Caffe2_DEPENDENCY_WHOLE_LINK_LIBS onnx_library)\n# Recover the build shared libs option.\nset(BUILD_SHARED_LIBS ${TEMP_BUILD_SHARED_LIBS})\n\n"
  },
  {
    "path": "cmake/External/nccl.cmake",
    "content": "if (NOT __NCCL_INCLUDED)\n  set(__NCCL_INCLUDED TRUE)\n\n  # try the system-wide nccl first\n  find_package(NCCL)\n  if (NCCL_FOUND)\n      add_library(__caffe2_nccl INTERFACE)\n      target_link_libraries(__caffe2_nccl INTERFACE ${NCCL_LIBRARIES})\n      target_include_directories(__caffe2_nccl INTERFACE ${NCCL_INCLUDE_DIRS})\n  else()\n    # build directory\n    set(nccl_PREFIX ${PROJECT_SOURCE_DIR}/third_party/nccl)\n\n    # we build nccl statically, but want to link it into the caffe shared library\n    # this requires position-independent code\n    if (UNIX)\n      set(NCCL_EXTRA_COMPILER_FLAGS \"-fPIC\")\n    endif()\n\n    set(NCCL_CXX_FLAGS ${CMAKE_CXX_FLAGS} ${NCCL_EXTRA_COMPILER_FLAGS})\n    set(NCCL_C_FLAGS ${CMAKE_C_FLAGS} ${NCCL_EXTRA_COMPILER_FLAGS})\n\n    ExternalProject_Add(nccl_external\n      SOURCE_DIR ${nccl_PREFIX}\n      BUILD_IN_SOURCE 1\n      CONFIGURE_COMMAND \"\"\n      BUILD_COMMAND\n        make\n        \"CXX=${CMAKE_CXX_COMPILER}\"\n        \"CUDA_HOME=${CUDA_TOOLKIT_ROOT_DIR}\"\n        \"NVCC=${CUDA_NVCC_EXECUTABLE}\"\n      BUILD_BYPRODUCTS \"${nccl_PREFIX}/build/lib/libnccl_static.a\"\n      INSTALL_COMMAND \"\"\n      )\n\n    set(NCCL_FOUND TRUE)\n    add_library(__caffe2_nccl INTERFACE)\n    # The following old-style variables are set so that other libs, such as Gloo,\n    # can still use it.\n    set(NCCL_INCLUDE_DIRS ${nccl_PREFIX}/build/include)\n    set(NCCL_LIBRARIES ${nccl_PREFIX}/build/lib/libnccl_static.a)\n    add_dependencies(__caffe2_nccl nccl_external)\n    target_link_libraries(__caffe2_nccl INTERFACE ${NCCL_LIBRARIES})\n    target_include_directories(__caffe2_nccl INTERFACE ${NCCL_INCLUDE_DIRS})\n  endif()\n\nendif()\n"
  },
  {
    "path": "cmake/External/nnpack.cmake",
    "content": "if (__NNPACK_INCLUDED)\n  return()\nendif()\nset(__NNPACK_INCLUDED TRUE)\n \nif (NOT USE_NNPACK)\n  return()\nendif()\n\n# try any external nnpack first\nfind_package(NNPACK)\n\nif (NNPACK_FOUND)\n  message(INFO \"Found external NNPACK installation.\")\n  return()\nendif()\n\n##############################################################################\n# Custom build rules to build nnpack, if external dependency is not found \n##############################################################################\n\n##############################################################################\n# (1) MSVC - unsupported \n##############################################################################\n\nif (MSVC)\n  message(WARNING \"NNPACK not supported on MSVC yet. Turn this warning off by USE_NNPACK=OFF.\")\n  set(USE_NNPACK OFF)\n  return()\nendif()\n\n##############################################################################\n# (2) Anything but x86, x86-64, ARM, ARM64 - unsupported\n##############################################################################\nif(CMAKE_SYSTEM_PROCESSOR)\n  if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES \"^(i686|x86_64|armv5te|armv7-a|armv7l|aarch64)$\")\n    message(WARNING \"NNPACK is not supported on ${CMAKE_SYSTEM_PROCESSOR} processors. \"\n      \"The only supported architectures are x86, x86-64, ARM, and ARM64. \"\n      \"Turn this warning off by USE_NNPACK=OFF.\")\n    set(USE_NNPACK OFF)\n    return()\n  endif()\nendif()\n\n##############################################################################\n# (3) Android, iOS, Linux, macOS - supported\n##############################################################################\n\nif (ANDROID OR IOS OR ${CMAKE_SYSTEM_NAME} STREQUAL \"Linux\" OR ${CMAKE_SYSTEM_NAME} STREQUAL \"Darwin\")\n  message(STATUS \"Brace yourself, we are building NNPACK\")\n  set(CAFFE2_THIRD_PARTY_ROOT ${PROJECT_SOURCE_DIR}/third_party)\n\n  # Directories for NNPACK dependencies submoduled in Caffe2\n  set(PYTHON_SIX_SOURCE_DIR \"${CAFFE2_THIRD_PARTY_ROOT}/python-six\" CACHE STRING \"six (Python package) source directory\")\n  set(PYTHON_ENUM_SOURCE_DIR \"${CAFFE2_THIRD_PARTY_ROOT}/python-enum\" CACHE STRING \"enum34 (Python package) source directory\")\n  set(PYTHON_PEACHPY_SOURCE_DIR \"${CAFFE2_THIRD_PARTY_ROOT}/python-peachpy\" CACHE STRING \"PeachPy (Python package) source directory\")\n  if (NOT DEFINED CPUINFO_SOURCE_DIR)\n    set(CPUINFO_SOURCE_DIR \"${CAFFE2_THIRD_PARTY_ROOT}/cpuinfo\" CACHE STRING \"cpuinfo source directory\")\n  endif()\n  set(NNPACK_SOURCE_DIR \"${CAFFE2_THIRD_PARTY_ROOT}/NNPACK\" CACHE STRING \"NNPACK source directory\")\n  set(FP16_SOURCE_DIR \"${CAFFE2_THIRD_PARTY_ROOT}/FP16\" CACHE STRING \"FP16 source directory\")\n  set(FXDIV_SOURCE_DIR \"${CAFFE2_THIRD_PARTY_ROOT}/FXdiv\" CACHE STRING \"FXdiv source directory\")\n  set(PSIMD_SOURCE_DIR \"${CAFFE2_THIRD_PARTY_ROOT}/psimd\" CACHE STRING \"PSimd source directory\")\n  set(PTHREADPOOL_SOURCE_DIR \"${CAFFE2_THIRD_PARTY_ROOT}/pthreadpool\" CACHE STRING \"pthreadpool source directory\")\n  set(GOOGLETEST_SOURCE_DIR \"${CAFFE2_THIRD_PARTY_ROOT}/googletest\" CACHE STRING \"Google Test source directory\")\n\n  if(NOT TARGET nnpack)\n    set(NNPACK_BUILD_TESTS OFF CACHE BOOL \"\")\n    set(NNPACK_BUILD_BENCHMARKS OFF CACHE BOOL \"\")\n    set(NNPACK_LIBRARY_TYPE \"static\" CACHE STRING \"\")\n    set(PTHREADPOOL_LIBRARY_TYPE \"static\" CACHE STRING \"\")\n    set(CPUINFO_LIBRARY_TYPE \"static\" CACHE STRING \"\")\n    add_subdirectory(\n      \"${NNPACK_SOURCE_DIR}\"\n      \"${CONFU_DEPENDENCIES_BINARY_DIR}/NNPACK\")\n    # We build static versions of nnpack and pthreadpool but link\n    # them into a shared library for Caffe2, so they need PIC.\n    set_property(TARGET nnpack PROPERTY POSITION_INDEPENDENT_CODE ON)\n    set_property(TARGET pthreadpool PROPERTY POSITION_INDEPENDENT_CODE ON)\n    set_property(TARGET cpuinfo PROPERTY POSITION_INDEPENDENT_CODE ON)\n  endif()\n\n  set(NNPACK_FOUND TRUE)\n  set(NNPACK_INCLUDE_DIRS\n    $<TARGET_PROPERTY:nnpack,INCLUDE_DIRECTORIES>\n    $<TARGET_PROPERTY:pthreadpool,INCLUDE_DIRECTORIES>)\n  set(NNPACK_LIBRARIES $<TARGET_FILE:nnpack> $<TARGET_FILE:cpuinfo>)\n  return()\nendif()\n\n##############################################################################\n# (4) Catch-all: not supported.\n##############################################################################\n\nmessage(WARNING \"Unknown platform - I don't know how to build NNPACK. \"\n                \"See cmake/External/nnpack.cmake for details.\")\nset(USE_NNPACK OFF)\n"
  },
  {
    "path": "cmake/MiscCheck.cmake",
    "content": "include(CheckCCompilerFlag)\ninclude(CheckCXXSourceCompiles)\ninclude(CheckCXXCompilerFlag)\ninclude(CMakePushCheckState)\n\n# ---[ If running on Ubuntu, check system version and compiler version.\nif(EXISTS \"/etc/os-release\")\n  execute_process(COMMAND\n    \"sed\" \"-ne\" \"s/^ID=\\\\([a-z]\\\\+\\\\)$/\\\\1/p\" \"/etc/os-release\"\n    OUTPUT_VARIABLE OS_RELEASE_ID\n    OUTPUT_STRIP_TRAILING_WHITESPACE\n    )\n  execute_process(COMMAND\n    \"sed\" \"-ne\" \"s/^VERSION_ID=\\\"\\\\([0-9\\\\.]\\\\+\\\\)\\\"$/\\\\1/p\" \"/etc/os-release\"\n    OUTPUT_VARIABLE OS_RELEASE_VERSION_ID\n    OUTPUT_STRIP_TRAILING_WHITESPACE\n    )\n  if(OS_RELEASE_ID STREQUAL \"ubuntu\")\n    if(OS_RELEASE_VERSION_ID VERSION_GREATER \"17.04\")\n      if(CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\")\n        if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS \"6.0.0\")\n          message(FATAL_ERROR\n            \"Please use GCC 6 or higher on Ubuntu 17.04 and higher. \"\n            \"For more information, see: \"\n            \"https://github.com/caffe2/caffe2/issues/1633\"\n            )\n        endif()\n      endif()\n    endif()\n  endif()\nendif()\n\n# ---[ Check if the data type long and int32_t/int64_t overlap.\ncmake_push_check_state(RESET)\nset(CMAKE_REQUIRED_FLAGS \"-std=c++11\")\nCHECK_CXX_SOURCE_COMPILES(\n    \"#include <cstdint>\n\n    template <typename T> void Foo();\n    template<> void Foo<int32_t>() {}\n    template<> void Foo<int64_t>() {}\n    int main(int argc, char** argv) {\n      Foo<long>();\n      return 0;\n    }\" CAFFE2_LONG_IS_INT32_OR_64)\n\nif (CAFFE2_LONG_IS_INT32_OR_64)\n  message(STATUS \"Does not need to define long separately.\")\nelse()\n  message(STATUS \"Need to define long as a separate typeid.\")\n  set(CAFFE2_UNIQUE_LONG_TYPEMETA 1)\nendif()\ncmake_pop_check_state()\n\n# ---[ Check if std::exception_ptr is supported.\ncmake_push_check_state(RESET)\nset(CMAKE_REQUIRED_FLAGS \"-std=c++11\")\nCHECK_CXX_SOURCE_COMPILES(\n    \"#include <string>\n    #include <exception>\n    int main(int argc, char** argv) {\n      std::exception_ptr eptr;\n      try {\n          std::string().at(1);\n      } catch(...) {\n          eptr = std::current_exception();\n      }\n    }\" CAFFE2_EXCEPTION_PTR_SUPPORTED)\n\nif (CAFFE2_EXCEPTION_PTR_SUPPORTED)\n  message(STATUS \"std::exception_ptr is supported.\")\n  set(CAFFE2_USE_EXCEPTION_PTR 1)\nelse()\n  message(STATUS \"std::exception_ptr is NOT supported.\")\nendif()\ncmake_pop_check_state()\n\n# ---[ Check for NUMA support\ncmake_push_check_state(RESET)\nset(CMAKE_REQUIRED_FLAGS \"-std=c++11\")\nCHECK_CXX_SOURCE_COMPILES(\n    \"#include <numa.h>\n    #include <numaif.h>\n\n    int main(int argc, char** argv) {\n    }\" CAFFE2_IS_NUMA_AVAILABLE)\n\nif (CAFFE2_IS_NUMA_AVAILABLE)\n  message(STATUS \"NUMA is available\")\nelse()\n  message(STATUS \"NUMA is not available\")\n  set(CAFFE2_DISABLE_NUMA 1)\nendif()\ncmake_pop_check_state()\n\n# ---[ Check if we want to turn off deprecated warning due to glog.\n# Note(jiayq): on ubuntu 14.04, the default glog install uses ext/hash_set that\n# is being deprecated. As a result, we will test if this is the environment we\n# are building under. If yes, we will turn off deprecation warning for a\n# cleaner build output.\ncmake_push_check_state(RESET)\nset(CMAKE_REQUIRED_FLAGS \"-std=c++11\")\nCHECK_CXX_SOURCE_COMPILES(\n    \"#include <glog/stl_logging.h>\n    int main(int argc, char** argv) {\n      return 0;\n    }\" CAFFE2_NEED_TO_TURN_OFF_DEPRECATION_WARNING\n    FAIL_REGEX \".*-Wno-deprecated.*\")\n\nif(NOT CAFFE2_NEED_TO_TURN_OFF_DEPRECATION_WARNING AND NOT MSVC)\n  message(STATUS \"Turning off deprecation warning due to glog.\")\n  set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-deprecated\")\nendif()\ncmake_pop_check_state()\n\n# ---[ Check if the compiler has AVX/AVX2 support. We only check AVX2.\ncmake_push_check_state(RESET)\nif (MSVC)\n  set(CMAKE_REQUIRED_FLAGS \"/arch:AVX2\")\nelse()\n  set(CMAKE_REQUIRED_FLAGS \"-mavx2\")\nendif()\nCHECK_CXX_SOURCE_COMPILES(\n    \"#include <immintrin.h>\n     int main() {\n       __m256i a, b;\n       a = _mm256_set1_epi8 (1);\n       b = a;\n       _mm256_add_epi8 (a,a);\n       return 0;\n     }\" CAFFE2_COMPILER_SUPPORTS_AVX2_EXTENSIONS)\nif (CAFFE2_COMPILER_SUPPORTS_AVX2_EXTENSIONS)\n  message(STATUS \"Current compiler supports avx2 extention. Will build perfkernels.\")\n  # Currently MSVC seems to have a symbol not found error while linking (related\n  # to source file order?). As a result we will currently disable the perfkernel\n  # in msvc.\n  # Also see CMakeLists.txt under caffe2/perfkernels.\n  if (NOT MSVC)\n    set(CAFFE2_PERF_WITH_AVX 1)\n    set(CAFFE2_PERF_WITH_AVX2 1)\n  endif()\nendif()\ncmake_pop_check_state()\n\n# ---[ If we are using msvc, set no warning flags\n# Note(jiayq): if you are going to add a warning flag, check if this is\n# totally necessary, and only add when you see fit. If it is needed due to\n# a third party library (like Protobuf), mention it in the comment as\n# \"THIRD_PARTY_NAME related\"\nif (${CMAKE_CXX_COMPILER_ID} STREQUAL \"MSVC\")\n  add_compile_options(\n      ##########################################\n      # Protobuf related. Cannot remove.\n      # This is directly copied from\n      #     https://github.com/google/protobuf/blob/master/cmake/README.md\n      ##########################################\n      /wd4018 # 'expression' : signed/unsigned mismatch\n      /wd4065 # (3): switch with default but no case.\n      /wd4146 # unary minus operator applied to unsigned type, result still unsigned\n      /wd4244 # Conversion from 'type1' to 'type2', possible loss of data.\n      /wd4251 # 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2'\n      /wd4267 # Conversion from 'size_t' to 'type', possible loss of data.\n      /wd4305 # 'identifier' : truncation from 'type1' to 'type2'\n      /wd4355 # 'this' : used in base member initializer list\n      /wd4506 # (1): no definition for inline function. Protobuf related.\n      /wd4661 # No suitable definition provided for explicit template instantiation request\n      /wd4800 # 'type' : forcing value to bool 'true' or 'false' (performance warning)\n      /wd4996 # 'function': was declared deprecated\n      ##########################################\n      # Third party related. Cannot remove.\n      ##########################################\n      /wd4141 # (1): inline used twice. google benchmark related.\n      /wd4503 # (1): decorated name length exceeded, name was truncated.\n              #      Eigen related.\n      /wd4554 # (3): check operator precedence for possible error.\n              # Eigen related.\n      /wd4805 # (1): Unsafe mix of types in gtest/gtest.h. Gtest related.\n      ##########################################\n      # These are directly Caffe2 related. However, several are covered by\n      # protobuf now. We leave them here for documentation purposes only.\n      ##########################################\n      #/wd4018 # (3): Signed/unsigned mismatch. We've used it in many places\n      #        #      of the code and it would be hard to correct all.\n      #/wd4244 # (2/3/4): Possible loss of precision. Various cases where we\n      #        #      implicitly cast TIndex to int etc. Need cleaning.\n      #/wd4267 # (3): Conversion of size_t to smaller type. Same as 4244.\n      #/wd4996 # (3): Use of deprecated POSIX functions. Since we develop\n      #        #      mainly on Linux, this is ignored.\n      /wd4273 # (1): inconsistent dll linkage. This is related to the\n              #      caffe2 FLAGS_* definition using dllimport in header and\n              #      dllexport in cc file. The strategy is copied from gflags.\n  )\n\n  # Exception handing for compiler warining C4530, see\n  # https://msdn.microsoft.com/en-us/library/2axwkyt4.aspx\n  add_definitions(\"/EHsc\")\n\n  set(CMAKE_SHARED_LINKER_FLAGS\n      \"${CMAKE_SHARED_LINKER_FLAGS} /ignore:4049 /ignore:4217\")\n  set(CMAKE_EXE_LINKER_FLAGS\n      \"${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217\")\nendif()\n\n# ---[ If we are building on ios, or building with opengl support, we will\n# enable -mfpu=neon-fp16 for iOS Metal build. For Android, this fpu setting\n# is going to be done with android-cmake by setting\n#     -DANDROID_ABI=\"armeabi-v7a with NEON FP16\"\n# in the build command.\n# Also, we will turn off deprecated-declarations\n# due to protobuf.\n\nif (IOS)\n  add_definitions(\"-mfpu=neon-fp16\")\n  add_definitions(\"-Wno-deprecated-declarations\")\nendif()\n\n# ---[ If we are building with ACL, we will enable neon-fp16.\nif(USE_ACL)\n  if (CMAKE_SYSTEM_PROCESSOR MATCHES \"^armv\")\n    # 32-bit ARM (armv7, armv7-a, armv7l, etc)\n    set(ACL_ARCH \"armv7a\")\n    # Compilers for 32-bit ARM need extra flags to enable NEON-FP16\n    add_definitions(\"-mfpu=neon-fp16\")\n\n    include(CheckCCompilerFlag)\n    CHECK_C_COMPILER_FLAG(\n        -mfp16-format=ieee CAFFE2_COMPILER_SUPPORTS_FP16_FORMAT)\n    if (CAFFE2_COMPILER_SUPPORTS_FP16_FORMAT)\n      add_definitions(\"-mfp16-format=ieee\")\n    endif()\n  endif()\nendif()\n\n# ---[ If we use asan, turn on the flags.\n# TODO: This only works with new style gcc and clang (not the old -faddress-sanitizer).\n# Change if necessary on old platforms.\nif (USE_ASAN)\n  set(CAFFE2_ASAN_FLAG \"-fsanitize=address -fPIE -pie\")\n  set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} ${CAFFE2_ASAN_FLAG}\")\n  set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} ${CAFFE2_ASAN_FLAG}\")\n  set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} ${CAFFE2_ASAN_FLAG}\")\n  set(CMAKE_MODULE_LINKER_FLAGS \"${CMAKE_MODULE_LINKER_FLAGS} ${CAFFE2_ASAN_FLAG}\")\n  set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} ${CAFFE2_ASAN_FLAG}\")\nendif()\n\n# ---[ Create CAFFE2_BUILD_SHARED_LIBS for macros.h.in usage.\nset(CAFFE2_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})\n\n# ---[ Check if we will need to include the local Modules_CUDA_fix folder.\n# Add your conditions here if needed.\nif (MSVC)\n  # We know that VS2017 needs the new FindCUDA functionality, so we will\n  # simply enable it for the whole Windows build.\n  set(CAFFE2_CMAKE_USE_LOCAL_FINDCUDA ON)\nendif()\n\nif (${CAFFE2_CMAKE_USE_LOCAL_FINDCUDA})\n  list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules_CUDA_fix)\nendif()\n\nif (USE_NATIVE_ARCH)\n  check_cxx_compiler_flag(\"-march=native\" COMPILER_SUPPORTS_MARCH_NATIVE)\n  if (COMPILER_SUPPORTS_MARCH_NATIVE)\n    add_definitions(\"-march=native\")\n  else()\n    message(\n        WARNING\n        \"Your compiler does not support -march=native. Turn off this warning \"\n        \"by setting -DUSE_NATIVE_ARCH=OFF.\")\n  endif()\nendif()\n"
  },
  {
    "path": "cmake/Modules/FindAtlas.cmake",
    "content": "# Find the Atlas (and Lapack) libraries\n#\n# The following variables are optionally searched for defaults\n#  Atlas_ROOT_DIR:            Base directory where all Atlas components are found\n#\n# The following are set after configuration is done:\n#  Atlas_FOUND\n#  Atlas_INCLUDE_DIRS\n#  Atlas_LIBRARIES\n#  Atlas_LIBRARYRARY_DIRS\n\nset(Atlas_INCLUDE_SEARCH_PATHS\n  /usr/include/atlas\n  /usr/include/atlas-base\n  $ENV{Atlas_ROOT_DIR}\n  $ENV{Atlas_ROOT_DIR}/include\n)\n\nset(Atlas_LIB_SEARCH_PATHS\n  /usr/lib/atlas\n  /usr/lib/atlas-base\n  $ENV{Atlas_ROOT_DIR}\n  $ENV{Atlas_ROOT_DIR}/lib\n)\n\nfind_path(Atlas_CBLAS_INCLUDE_DIR   NAMES cblas.h   PATHS ${Atlas_INCLUDE_SEARCH_PATHS})\nfind_path(Atlas_CLAPACK_INCLUDE_DIR NAMES clapack.h PATHS ${Atlas_INCLUDE_SEARCH_PATHS})\n\nfind_library(Atlas_CBLAS_LIBRARY NAMES  ptcblas_r ptcblas cblas_r cblas       PATHS ${Atlas_LIB_SEARCH_PATHS})\nfind_library(Atlas_BLAS_LIBRARY NAMES   atlas_r   atlas                       PATHS ${Atlas_LIB_SEARCH_PATHS})\nfind_library(Atlas_LAPACK_LIBRARY NAMES lapack alapack_r alapack lapack_atlas PATHS ${Atlas_LIB_SEARCH_PATHS})\n\nset(LOOKED_FOR\n  Atlas_CBLAS_INCLUDE_DIR\n  Atlas_CLAPACK_INCLUDE_DIR\n\n  Atlas_CBLAS_LIBRARY\n  Atlas_BLAS_LIBRARY\n  Atlas_LAPACK_LIBRARY\n)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(Atlas DEFAULT_MSG ${LOOKED_FOR})\n\nif(ATLAS_FOUND)\n  set(Atlas_INCLUDE_DIR ${Atlas_CBLAS_INCLUDE_DIR} ${Atlas_CLAPACK_INCLUDE_DIR})\n  set(Atlas_LIBRARIES ${Atlas_LAPACK_LIBRARY} ${Atlas_CBLAS_LIBRARY} ${Atlas_BLAS_LIBRARY})\n  mark_as_advanced(${LOOKED_FOR})\n\n  message(STATUS \"Found Atlas (include: ${Atlas_CBLAS_INCLUDE_DIR}, library: ${Atlas_BLAS_LIBRARY})\")\nendif(ATLAS_FOUND)\n\n"
  },
  {
    "path": "cmake/Modules/FindBenchmark.cmake",
    "content": "# Try to find the Google Benchmark library and headers.\n#  Benchmark_FOUND        - system has benchmark lib\n#  Benchmark_INCLUDE_DIRS - the benchmark include directory\n#  Benchmark_LIBRARIES    - libraries needed to use benchmark\n\nfind_path(Benchmark_INCLUDE_DIR\n  NAMES benchmark/benchmark.h\n  NO_SYSTEM_ENVIRONMENT_PATH\n  DOC \"The directory where benchmark includes reside\"\n)\n\nfind_library(Benchmark_LIBRARY\n  NAMES benchmark\n  NO_SYSTEM_ENVIRONMENT_PATH\n  DOC \"The benchmark library\"\n)\n\nset(Benchmark_INCLUDE_DIRS ${Benchmark_INCLUDE_DIR})\nset(Benchmark_LIBRARIES    ${Benchmark_LIBRARY})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(Benchmark\n  FOUND_VAR Benchmark_FOUND\n  REQUIRED_VARS Benchmark_INCLUDE_DIR Benchmark_LIBRARY\n)\n\nmark_as_advanced(Benchmark_FOUND)\n"
  },
  {
    "path": "cmake/Modules/FindCUB.cmake",
    "content": "# Try to find the CUB library and headers.\n#  CUB_FOUND        - system has CUB\n#  CUB_INCLUDE_DIRS - the CUB include directory\n\nfind_path(CUB_INCLUDE_DIR\n\tNAMES cub/cub.cuh\n\tDOC \"The directory where CUB includes reside\"\n)\n\nset(CUB_INCLUDE_DIRS ${CUB_INCLUDE_DIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(CUB\n\tFOUND_VAR CUB_FOUND\n\tREQUIRED_VARS CUB_INCLUDE_DIR\n)\n\nmark_as_advanced(CUB_FOUND)\n"
  },
  {
    "path": "cmake/Modules/FindFFmpeg.cmake",
    "content": "# - Try to find ffmpeg libraries\n#     (libavcodec, libavformat, libavutil, libswscale)\n# Once done this will define\n#\n# FFMPEG_FOUND - system has ffmpeg or libav\n# FFMPEG_INCLUDE_DIR - the ffmpeg include directory\n# FFMPEG_LIBRARIES - Link these to use ffmpeg\n#\n\nif (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR)\n  # in cache already\n  set(FFMPEG_FOUND TRUE)\nelse (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR)\n\n  find_path(FFMPEG_AVCODEC_INCLUDE_DIR\n    NAMES libavcodec/avcodec.h\n    PATHS ${_FFMPEG_AVCODEC_INCLUDE_DIRS} /usr/include /usr/local/include /opt/local/include /sw/include\n    PATH_SUFFIXES ffmpeg libav\n  )\n\n  find_library(FFMPEG_LIBAVCODEC\n    NAMES avcodec\n    PATHS ${_FFMPEG_AVCODEC_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib\n  )\n\n  find_library(FFMPEG_LIBAVFORMAT\n    NAMES avformat\n    PATHS ${_FFMPEG_AVFORMAT_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib\n  )\n\n  find_library(FFMPEG_LIBAVUTIL\n    NAMES avutil\n    PATHS ${_FFMPEG_AVUTIL_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib\n  )\n\n\n  find_library(FFMPEG_LIBSWSCALE\n    NAMES swscale\n    PATHS ${_FFMPEG_SWSCALE_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib\n  )\n\n  if (FFMPEG_LIBAVCODEC AND FFMPEG_LIBAVFORMAT)\n    set(FFMPEG_FOUND TRUE)\n  endif()\n\n  if (FFMPEG_FOUND)\n    set(FFMPEG_INCLUDE_DIR ${FFMPEG_AVCODEC_INCLUDE_DIR})\n\n    set(FFMPEG_LIBRARIES\n      ${FFMPEG_LIBAVCODEC}\n      ${FFMPEG_LIBAVFORMAT}\n      ${FFMPEG_LIBAVUTIL}\n      ${FFMPEG_LIBSWSCALE}\n    )\n\n    if (NOT FFMPEG_FIND_QUIETLY)\n      message(STATUS \"Found FFMPEG or Libav: ${FFMPEG_LIBRARIES}, ${FFMPEG_INCLUDE_DIR}\")\n    endif (NOT FFMPEG_FIND_QUIETLY)\n  else (FFMPEG_FOUND)\n    if (FFMPEG_FIND_REQUIRED)\n      message(FATAL_ERROR \"Could not find libavcodec or libavformat or libavutil\")\n    endif (FFMPEG_FIND_REQUIRED)\n  endif (FFMPEG_FOUND)\n\nendif (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR)\n"
  },
  {
    "path": "cmake/Modules/FindGloo.cmake",
    "content": "# Try to find the Gloo library and headers.\n#  Gloo_FOUND        - system has Gloo lib\n#  Gloo_INCLUDE_DIRS - the Gloo include directory\n#  Gloo_LIBRARIES    - libraries needed to use Gloo\n\nfind_path(Gloo_INCLUDE_DIR\n\tNAMES gloo/common/common.h\n\tDOC \"The directory where Gloo includes reside\"\n)\n\nfind_library(Gloo_NATIVE_LIBRARY\n\tNAMES gloo\n\tDOC \"The Gloo library (without CUDA)\"\n)\n\nfind_library(Gloo_CUDA_LIBRARY\n\tNAMES gloo_cuda\n\tDOC \"The Gloo library (with CUDA)\"\n)\n\nset(Gloo_INCLUDE_DIRS ${Gloo_INCLUDE_DIR})\n\n# use the CUDA library depending on the Gloo_USE_CUDA variable\nif (DEFINED Gloo_USE_CUDA)\n\tif (${Gloo_USE_CUDA})\n\t\tset(Gloo_LIBRARY ${Gloo_CUDA_LIBRARY})\n\telse()\n\t\tset(Gloo_LIBRARY ${Gloo_NATIVE_LIBRARY})\n\tendif()\nelse()\n\t# else try to use the CUDA library if found\n\tif (${Gloo_CUDA_LIBRARY} STREQUAL \"Gloo_CUDA_LIBRARY-NOTFOUND\")\n\t\tset(Gloo_LIBRARY ${Gloo_NATIVE_LIBRARY})\n\telse()\n\t\tset(Gloo_LIBRARY ${Gloo_CUDA_LIBRARY})\n\tendif()\nendif()\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(Gloo\n\tFOUND_VAR Gloo_FOUND\n\tREQUIRED_VARS Gloo_INCLUDE_DIR Gloo_LIBRARY\n)\n\nmark_as_advanced(Gloo_FOUND)\n"
  },
  {
    "path": "cmake/Modules/FindHiredis.cmake",
    "content": "# Find the Hiredis libraries\n#\n# The following variables are optionally searched for defaults\n#  HIREDIS_ROOT_DIR:    Base directory where all Hiredis components are found\n#\n# The following are set after configuration is done:\n#  HIREDIS_FOUND\n#  Hiredis_INCLUDE_DIR\n#  Hiredis_LIBRARIES\n\nfind_path(Hiredis_INCLUDE_DIR NAMES hiredis/hiredis.h\n                             PATHS ${HIREDIS_ROOT_DIR} ${HIREDIS_ROOT_DIR}/include)\n\nfind_library(Hiredis_LIBRARIES NAMES hiredis\n                              PATHS ${HIREDIS_ROOT_DIR} ${HIREDIS_ROOT_DIR}/lib)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(Hiredis DEFAULT_MSG Hiredis_INCLUDE_DIR Hiredis_LIBRARIES)\n\nif(HIREDIS_FOUND)\n  message(STATUS \"Found Hiredis  (include: ${Hiredis_INCLUDE_DIR}, library: ${Hiredis_LIBRARIES})\")\n  mark_as_advanced(Hiredis_INCLUDE_DIR Hiredis_LIBRARIES)\nendif()\n\n"
  },
  {
    "path": "cmake/Modules/FindLAPACK.cmake",
    "content": "# - Find LAPACK library\n# This module finds an installed fortran library that implements the LAPACK\n# linear-algebra interface (see http://www.netlib.org/lapack/).\n#\n# The approach follows that taken for the autoconf macro file, acx_lapack.m4\n# (distributed at http://ac-archive.sourceforge.net/ac-archive/acx_lapack.html).\n#\n# This module sets the following variables:\n#  LAPACK_FOUND - set to true if a library implementing the LAPACK interface is found\n#  LAPACK_LIBRARIES - list of libraries (using full path name) for LAPACK\n\n# Note: I do not think it is a good idea to mixup different BLAS/LAPACK versions\n# Hence, this script wants to find a Lapack library matching your Blas library\n\n# Do nothing if LAPACK was found before\nIF(NOT LAPACK_FOUND)\n\nSET(LAPACK_LIBRARIES)\nSET(LAPACK_INFO)\n\nIF(LAPACK_FIND_QUIETLY OR NOT LAPACK_FIND_REQUIRED)\n  FIND_PACKAGE(BLAS)\nELSE(LAPACK_FIND_QUIETLY OR NOT LAPACK_FIND_REQUIRED)\n  FIND_PACKAGE(BLAS REQUIRED)\nENDIF(LAPACK_FIND_QUIETLY OR NOT LAPACK_FIND_REQUIRED)\n\n# Old search lapack script\ninclude(CheckFortranFunctionExists)\n\nmacro(Check_Lapack_Libraries LIBRARIES _prefix _name _flags _list _blas)\n  # This macro checks for the existence of the combination of fortran libraries\n  # given by _list.  If the combination is found, this macro checks (using the\n  # Check_Fortran_Function_Exists macro) whether can link against that library\n  # combination using the name of a routine given by _name using the linker\n  # flags given by _flags.  If the combination of libraries is found and passes\n  # the link test, LIBRARIES is set to the list of complete library paths that\n  # have been found.  Otherwise, LIBRARIES is set to FALSE.\n  # N.B. _prefix is the prefix applied to the names of all cached variables that\n  # are generated internally and marked advanced by this macro.\n  set(_libraries_work TRUE)\n  set(${LIBRARIES})\n  set(_combined_name)\n  foreach(_library ${_list})\n    set(_combined_name ${_combined_name}_${_library})\n    if(_libraries_work)\n      if (WIN32)\n        find_library(${_prefix}_${_library}_LIBRARY\n          NAMES ${_library} PATHS ENV LIB PATHS ENV PATH)\n      else (WIN32)\n        if(APPLE)\n          find_library(${_prefix}_${_library}_LIBRARY\n            NAMES ${_library}\n            PATHS /usr/local/lib /usr/lib /usr/local/lib64 /usr/lib64\n            ENV DYLD_LIBRARY_PATH)\n        else(APPLE)\n          find_library(${_prefix}_${_library}_LIBRARY\n            NAMES ${_library}\n            PATHS /usr/local/lib /usr/lib /usr/local/lib64 /usr/lib64\n            ENV LD_LIBRARY_PATH)\n        endif(APPLE)\n      endif(WIN32)\n      mark_as_advanced(${_prefix}_${_library}_LIBRARY)\n      set(${LIBRARIES} ${${LIBRARIES}} ${${_prefix}_${_library}_LIBRARY})\n      set(_libraries_work ${${_prefix}_${_library}_LIBRARY})\n    endif(_libraries_work)\n  endforeach(_library ${_list})\n  if(_libraries_work)\n    # Test this combination of libraries.\n    set(CMAKE_REQUIRED_LIBRARIES ${_flags} ${${LIBRARIES}} ${_blas})\n    if (CMAKE_Fortran_COMPILER_WORKS)\n      check_fortran_function_exists(${_name} ${_prefix}${_combined_name}_WORKS)\n    else (CMAKE_Fortran_COMPILER_WORKS)\n      check_function_exists(\"${_name}_\" ${_prefix}${_combined_name}_WORKS)\n    endif (CMAKE_Fortran_COMPILER_WORKS)\n    set(CMAKE_REQUIRED_LIBRARIES)\n    mark_as_advanced(${_prefix}${_combined_name}_WORKS)\n    set(_libraries_work ${${_prefix}${_combined_name}_WORKS})\n  endif(_libraries_work)\n  if(NOT _libraries_work)\n    set(${LIBRARIES} FALSE)\n  endif(NOT _libraries_work)\nendmacro(Check_Lapack_Libraries)\n\n\nif(BLAS_FOUND)\n\n  # Intel MKL\n  IF((NOT LAPACK_INFO) AND (BLAS_INFO STREQUAL \"mkl\"))\n    IF(MKL_LAPACK_LIBRARIES)\n      SET(LAPACK_LIBRARIES ${MKL_LAPACK_LIBRARIES} ${MKL_LIBRARIES})\n    ELSE(MKL_LAPACK_LIBRARIES)\n      SET(LAPACK_LIBRARIES ${MKL_LIBRARIES})\n    ENDIF(MKL_LAPACK_LIBRARIES)\n    SET(LAPACK_INCLUDE_DIR ${MKL_INCLUDE_DIR})\n    SET(LAPACK_INFO \"mkl\")\n  ENDIF()\n\n  # OpenBlas\n  IF((NOT LAPACK_INFO) AND (BLAS_INFO STREQUAL \"open\"))\n    SET(CMAKE_REQUIRED_LIBRARIES ${BLAS_LIBRARIES})\n    check_function_exists(\"cheev_\" OPEN_LAPACK_WORKS)\n    if(OPEN_LAPACK_WORKS)\n      SET(LAPACK_INFO \"open\")\n    else()\n      message(STATUS \"It seems OpenBlas has not been compiled with Lapack support\")\n    endif()\n  endif()\n\n  # GotoBlas\n  IF((NOT LAPACK_INFO) AND (BLAS_INFO STREQUAL \"goto\"))\n    SET(CMAKE_REQUIRED_LIBRARIES ${BLAS_LIBRARIES})\n    check_function_exists(\"cheev_\" GOTO_LAPACK_WORKS)\n    if(GOTO_LAPACK_WORKS)\n      SET(LAPACK_INFO \"goto\")\n    else()\n      message(STATUS \"It seems GotoBlas has not been compiled with Lapack support\")\n    endif()\n  endif()\n\n  # ACML\n  IF((NOT LAPACK_INFO) AND (BLAS_INFO STREQUAL \"acml\"))\n    SET(CMAKE_REQUIRED_LIBRARIES ${BLAS_LIBRARIES})\n    check_function_exists(\"cheev_\" ACML_LAPACK_WORKS)\n    if(ACML_LAPACK_WORKS)\n      SET(LAPACK_INFO \"acml\")\n    else()\n      message(STATUS \"Strangely, this ACML library does not support Lapack?!\")\n    endif()\n  endif()\n\n  # Accelerate\n  IF((NOT LAPACK_INFO) AND (BLAS_INFO STREQUAL \"accelerate\"))\n    SET(CMAKE_REQUIRED_LIBRARIES ${BLAS_LIBRARIES})\n    check_function_exists(\"cheev_\" ACCELERATE_LAPACK_WORKS)\n    if(ACCELERATE_LAPACK_WORKS)\n      SET(LAPACK_INFO \"accelerate\")\n    else()\n      message(STATUS \"Strangely, this Accelerate library does not support Lapack?!\")\n    endif()\n  endif()\n\n  # vecLib\n  IF((NOT LAPACK_INFO) AND (BLAS_INFO STREQUAL \"veclib\"))\n    SET(CMAKE_REQUIRED_LIBRARIES ${BLAS_LIBRARIES})\n    check_function_exists(\"cheev_\" VECLIB_LAPACK_WORKS)\n    if(VECLIB_LAPACK_WORKS)\n      SET(LAPACK_INFO \"veclib\")\n    else()\n      message(STATUS \"Strangely, this vecLib library does not support Lapack?!\")\n    endif()\n  endif()\n\n  # Generic LAPACK library?\n  IF((NOT LAPACK_INFO) AND (BLAS_INFO STREQUAL \"generic\"))\n    check_lapack_libraries(\n      LAPACK_LIBRARIES\n      LAPACK\n      cheev\n      \"\"\n      \"lapack\"\n      \"${BLAS_LIBRARIES}\"\n      )\n    if(LAPACK_LIBRARIES)\n      SET(LAPACK_INFO \"generic\")\n    endif(LAPACK_LIBRARIES)\n  endif()\n\nelse(BLAS_FOUND)\n  message(STATUS \"LAPACK requires BLAS\")\nendif(BLAS_FOUND)\n\nif(LAPACK_INFO)\n  set(LAPACK_FOUND TRUE)\nelse(LAPACK_INFO)\n  set(LAPACK_FOUND FALSE)\nendif(LAPACK_INFO)\n\nIF (NOT LAPACK_FOUND AND LAPACK_FIND_REQUIRED)\n  message(FATAL_ERROR \"Cannot find a library with LAPACK API. Please specify library location.\")\nENDIF (NOT LAPACK_FOUND AND LAPACK_FIND_REQUIRED)\nIF(NOT LAPACK_FIND_QUIETLY)\n  IF(LAPACK_FOUND)\n    MESSAGE(STATUS \"Found a library with LAPACK API. (${LAPACK_INFO})\")\n  ELSE(LAPACK_FOUND)\n    MESSAGE(STATUS \"Cannot find a library with LAPACK API. Not using LAPACK.\")\n  ENDIF(LAPACK_FOUND)\nENDIF(NOT LAPACK_FIND_QUIETLY)\n\n# Do nothing if LAPACK was found before\nENDIF(NOT LAPACK_FOUND)\n"
  },
  {
    "path": "cmake/Modules/FindLMDB.cmake",
    "content": "# Try to find the LMBD libraries and headers\n#  LMDB_FOUND - system has LMDB lib\n#  LMDB_INCLUDE_DIR - the LMDB include directory\n#  LMDB_LIBRARIES - Libraries needed to use LMDB\n\n# FindCWD based on FindGMP by:\n# Copyright (c) 2006, Laurent Montel, <montel@kde.org>\n#\n# Redistribution and use is allowed according to the terms of the BSD license.\n\n# Adapted from FindCWD by:\n# Copyright 2013 Conrad Steenberg <conrad.steenberg@gmail.com>\n# Aug 31, 2013\n\nif(MSVC)\n  find_package(LMDB NO_MODULE)\nelse()\n  find_path(LMDB_INCLUDE_DIR NAMES  lmdb.h PATHS \"$ENV{LMDB_DIR}/include\")\n  find_library(LMDB_LIBRARIES NAMES lmdb   PATHS \"$ENV{LMDB_DIR}/lib\" )\nendif()\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(LMDB DEFAULT_MSG LMDB_INCLUDE_DIR LMDB_LIBRARIES)\n\nif(LMDB_FOUND)\n  message(STATUS \"Found lmdb    (include: ${LMDB_INCLUDE_DIR}, library: ${LMDB_LIBRARIES})\")\n  mark_as_advanced(LMDB_INCLUDE_DIR LMDB_LIBRARIES)\n\n  caffe_parse_header(${LMDB_INCLUDE_DIR}/lmdb.h\n                     LMDB_VERSION_LINES MDB_VERSION_MAJOR MDB_VERSION_MINOR MDB_VERSION_PATCH)\n  set(LMDB_VERSION \"${MDB_VERSION_MAJOR}.${MDB_VERSION_MINOR}.${MDB_VERSION_PATCH}\")\nendif()\n"
  },
  {
    "path": "cmake/Modules/FindLevelDB.cmake",
    "content": "# - Find LevelDB\n#\n#  LevelDB_INCLUDES  - List of LevelDB includes\n#  LevelDB_LIBRARIES - List of libraries when using LevelDB.\n#  LevelDB_FOUND     - True if LevelDB found.\n\n# Look for the header file.\nfind_path(LevelDB_INCLUDE NAMES leveldb/db.h\n                          PATHS $ENV{LEVELDB_ROOT}/include /opt/local/include /usr/local/include /usr/include\n                          DOC \"Path in which the file leveldb/db.h is located.\" )\n\n# Look for the library.\nfind_library(LevelDB_LIBRARY NAMES leveldb\n                             PATHS /usr/lib $ENV{LEVELDB_ROOT}/lib\n                             DOC \"Path to leveldb library.\" )\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(LevelDB DEFAULT_MSG LevelDB_INCLUDE LevelDB_LIBRARY)\n\nif(LEVELDB_FOUND)\n  message(STATUS \"Found LevelDB (include: ${LevelDB_INCLUDE}, library: ${LevelDB_LIBRARY})\")\n  set(LevelDB_INCLUDES ${LevelDB_INCLUDE})\n  set(LevelDB_LIBRARIES ${LevelDB_LIBRARY})\n  mark_as_advanced(LevelDB_INCLUDE LevelDB_LIBRARY)\n\n  if(EXISTS \"${LevelDB_INCLUDE}/leveldb/db.h\")\n    file(STRINGS \"${LevelDB_INCLUDE}/leveldb/db.h\" __version_lines\n           REGEX \"static const int k[^V]+Version[ \\t]+=[ \\t]+[0-9]+;\")\n\n    foreach(__line ${__version_lines})\n      if(__line MATCHES \"[^k]+kMajorVersion[ \\t]+=[ \\t]+([0-9]+);\")\n        set(LEVELDB_VERSION_MAJOR ${CMAKE_MATCH_1})\n      elseif(__line MATCHES \"[^k]+kMinorVersion[ \\t]+=[ \\t]+([0-9]+);\")\n        set(LEVELDB_VERSION_MINOR ${CMAKE_MATCH_1})\n      endif()\n    endforeach()\n\n    if(LEVELDB_VERSION_MAJOR AND LEVELDB_VERSION_MINOR)\n      set(LEVELDB_VERSION \"${LEVELDB_VERSION_MAJOR}.${LEVELDB_VERSION_MINOR}\")\n    endif()\n\n    # caffe_clear_vars(__line __version_lines)\n  endif()\nendif()\n"
  },
  {
    "path": "cmake/Modules/FindMKL.cmake",
    "content": "# Find the MKL libraries\r\n#\r\n# Options:\r\n#\r\n#   MKL_USE_SINGLE_DYNAMIC_LIBRARY  : use single dynamic library interface\r\n#   MKL_USE_STATIC_LIBS             : use static libraries\r\n#   MKL_MULTI_THREADED              : use multi-threading\r\n#\r\n# This module defines the following variables:\r\n#\r\n#   MKL_FOUND            : True mkl is found\r\n#   MKL_INCLUDE_DIR      : unclude directory\r\n#   MKL_LIBRARIES        : the libraries to link against.\r\n\r\n\r\n# ---[ Options\r\ninclude(CMakeDependentOption)\r\noption(MKL_USE_SINGLE_DYNAMIC_LIBRARY \"Use single dynamic library interface\" ON)\r\ncmake_dependent_option(\r\n    MKL_USE_STATIC_LIBS \"Use static libraries\" OFF\r\n        \"NOT MKL_USE_SINGLE_DYNAMIC_LIBRARY\" OFF)\r\ncmake_dependent_option(\r\n    MKL_MULTI_THREADED  \"Use multi-threading\" ON\r\n    \"NOT MKL_USE_SINGLE_DYNAMIC_LIBRARY\" OFF)\r\n\r\n# ---[ Root folders\r\nif(MSVC)\r\n  set(INTEL_ROOT_DEFAULT \"C:/Program Files (x86)/IntelSWTools/compilers_and_libraries/windows\")\r\nelse()\r\n  set(INTEL_ROOT_DEFAULT \"/opt/intel\")\r\nendif()\r\nset(INTEL_ROOT ${INTEL_ROOT_DEFAULT} CACHE PATH \"Folder contains intel libs\")\r\nfind_path(MKL_ROOT include/mkl.h PATHS $ENV{MKLROOT} ${INTEL_ROOT}/mkl\r\n                                   DOC \"Folder contains MKL\")\r\n\r\n# ---[ Find include dir\r\nfind_path(MKL_INCLUDE_DIR mkl.h PATHS ${MKL_ROOT} PATH_SUFFIXES include)\r\nset(__looked_for MKL_INCLUDE_DIR)\r\n\r\n# ---[ Find libraries\r\nif(CMAKE_SIZEOF_VOID_P EQUAL 4)\r\n  set(__path_suffixes lib lib/ia32)\r\nelse()\r\n  set(__path_suffixes lib lib/intel64)\r\nendif()\r\n\r\nset(__mkl_libs \"\")\r\nif(MKL_USE_SINGLE_DYNAMIC_LIBRARY)\r\n  list(APPEND __mkl_libs rt)\r\nelse()\r\n  if(CMAKE_SIZEOF_VOID_P EQUAL 4)\r\n    if(WIN32)\r\n      list(APPEND __mkl_libs intel_c)\r\n    else()\r\n      list(APPEND __mkl_libs intel)\r\n    endif()\r\n  else()\r\n    list(APPEND __mkl_libs intel_lp64)\r\n  endif()\r\n\r\n  if(MKL_MULTI_THREADED)\r\n    list(APPEND __mkl_libs intel_thread)\r\n  else()\r\n     list(APPEND __mkl_libs sequential)\r\n  endif()\r\n\r\n  list(APPEND __mkl_libs core cdft_core)\r\nendif()\r\n\r\n\r\nforeach (__lib ${__mkl_libs})\r\n  set(__mkl_lib \"mkl_${__lib}\")\r\n  string(TOUPPER ${__mkl_lib} __mkl_lib_upper)\r\n\r\n  if(MKL_USE_STATIC_LIBS)\r\n    set(__mkl_lib \"lib${__mkl_lib}.a\")\r\n  endif()\r\n\r\n  find_library(${__mkl_lib_upper}_LIBRARY\r\n        NAMES ${__mkl_lib}\r\n        PATHS ${MKL_ROOT} \"${MKL_INCLUDE_DIR}/..\"\r\n        PATH_SUFFIXES ${__path_suffixes}\r\n        DOC \"The path to Intel(R) MKL ${__mkl_lib} library\")\r\n  mark_as_advanced(${__mkl_lib_upper}_LIBRARY)\r\n\r\n  list(APPEND __looked_for ${__mkl_lib_upper}_LIBRARY)\r\n  list(APPEND MKL_LIBRARIES ${${__mkl_lib_upper}_LIBRARY})\r\nendforeach()\r\n\r\n\r\nif(NOT MKL_USE_SINGLE_DYNAMIC_LIBRARY)\r\n  if (MKL_USE_STATIC_LIBS)\r\n    set(__iomp5_libs iomp5 libiomp5mt.lib)\r\n  else()\r\n    set(__iomp5_libs iomp5 libiomp5md.lib)\r\n  endif()\r\n\r\n  if(WIN32)\r\n    find_path(INTEL_INCLUDE_DIR omp.h PATHS ${INTEL_ROOT} PATH_SUFFIXES include)\r\n    list(APPEND __looked_for INTEL_INCLUDE_DIR)\r\n  endif()\r\n\r\n  find_library(MKL_RTL_LIBRARY ${__iomp5_libs}\r\n     PATHS ${INTEL_RTL_ROOT} ${INTEL_ROOT}/compiler ${MKL_ROOT}/.. ${MKL_ROOT}/../compiler\r\n     PATH_SUFFIXES ${__path_suffixes}\r\n     DOC \"Path to OpenMP runtime library\")\r\n\r\n  list(APPEND __looked_for MKL_RTL_LIBRARY)\r\n  list(APPEND MKL_LIBRARIES ${MKL_RTL_LIBRARY})\r\nendif()\r\n\r\n\r\ninclude(FindPackageHandleStandardArgs)\r\nfind_package_handle_standard_args(MKL DEFAULT_MSG ${__looked_for})\r\n\r\nif(MKL_FOUND)\r\n  message(STATUS \"Found MKL (include: ${MKL_INCLUDE_DIR}, lib: ${MKL_LIBRARIES}\")\r\nendif()\r\n\r\ncaffe_clear_vars(__looked_for __mkl_libs __path_suffixes __lib_suffix __iomp5_libs)\r\n"
  },
  {
    "path": "cmake/Modules/FindMatlabMex.cmake",
    "content": "# This module looks for MatlabMex compiler\n# Defines variables:\n#    Matlab_DIR    - Matlab root dir\n#    Matlab_mex    - path to mex compiler\n#    Matlab_mexext - path to mexext\n\nif(MSVC)\n  foreach(__ver \"9.30\" \"7.14\" \"7.11\" \"7.10\" \"7.9\" \"7.8\" \"7.7\")\n    get_filename_component(__matlab_root \"[HKEY_LOCAL_MACHINE\\\\SOFTWARE\\\\MathWorks\\\\MATLAB\\\\${__ver};MATLABROOT]\" ABSOLUTE)\n    if(__matlab_root)\n      break()\n    endif()\n  endforeach()\nendif()\n\nif(APPLE)\n  foreach(__ver \"R2014b\" \"R2014a\" \"R2013b\" \"R2013a\" \"R2012b\" \"R2012a\" \"R2011b\" \"R2011a\" \"R2010b\" \"R2010a\")\n    if(EXISTS /Applications/MATLAB_${__ver}.app)\n      set(__matlab_root /Applications/MATLAB_${__ver}.app)\n      break()\n    endif()\n  endforeach()\nendif()\n\nif(UNIX)\n   execute_process(COMMAND which matlab OUTPUT_STRIP_TRAILING_WHITESPACE\n                   OUTPUT_VARIABLE __out RESULT_VARIABLE __res)\n\n   if(__res MATCHES 0) # Suppress `readlink` warning if `which` returned nothing\n     execute_process(COMMAND which matlab  COMMAND xargs readlink\n                     COMMAND xargs dirname COMMAND xargs dirname COMMAND xargs echo -n\n                     OUTPUT_VARIABLE __matlab_root OUTPUT_STRIP_TRAILING_WHITESPACE)\n   endif()\nendif()\n\n\nfind_path(Matlab_DIR NAMES bin/mex bin/mexext PATHS ${__matlab_root}\n                     DOC \"Matlab directory\" NO_DEFAULT_PATH)\n\nfind_program(Matlab_mex    NAMES mex    mex.bat    HINTS ${Matlab_DIR} PATH_SUFFIXES bin NO_DEFAULT_PATH)\nfind_program(Matlab_mexext NAMES mexext mexext.bat HINTS ${Matlab_DIR} PATH_SUFFIXES bin NO_DEFAULT_PATH)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(MatlabMex DEFAULT_MSG Matlab_mex Matlab_mexext)\n\nif(MATLABMEX_FOUND)\n  mark_as_advanced(Matlab_mex Matlab_mexext)\nendif()\n"
  },
  {
    "path": "cmake/Modules/FindNCCL.cmake",
    "content": "# Find the nccl libraries\n#\n# The following variables are optionally searched for defaults\n#  NCCL_ROOT_DIR: Base directory where all NCCL components are found\n#  NCCL_INCLUDE_DIR: Directory where NCCL header is found\n#  NCCL_LIB_DIR: Directory where NCCL library is found\n#\n# The following are set after configuration is done:\n#  NCCL_FOUND\n#  NCCL_INCLUDE_DIRS\n#  NCCL_LIBRARIES\n#\n# The path hints include CUDA_TOOLKIT_ROOT_DIR seeing as some folks\n# install NCCL in the same location as the CUDA toolkit.\n# See https://github.com/caffe2/caffe2/issues/1601\n\nset(NCCL_ROOT_DIR \"\" CACHE PATH \"Folder contains NVIDIA NCCL\")\n\nfind_path(NCCL_INCLUDE_DIRS\n  NAMES nccl.h\n  HINTS\n  ${NCCL_INCLUDE_DIR}\n  ${NCCL_ROOT_DIR}\n  ${NCCL_ROOT_DIR}/include\n  ${CUDA_TOOLKIT_ROOT_DIR}/include)\n\nfind_library(NCCL_LIBRARIES\n  NAMES nccl\n  HINTS\n  ${NCCL_LIB_DIR}\n  ${NCCL_ROOT_DIR}\n  ${NCCL_ROOT_DIR}/lib\n  ${NCCL_ROOT_DIR}/lib/x86_64-linux-gnu\n  ${NCCL_ROOT_DIR}/lib64\n  ${CUDA_TOOLKIT_ROOT_DIR}/lib64)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(NCCL DEFAULT_MSG NCCL_INCLUDE_DIRS NCCL_LIBRARIES)\n\nif(NCCL_FOUND)\n  message(STATUS \"Found NCCL (include: ${NCCL_INCLUDE_DIRS}, library: ${NCCL_LIBRARIES})\")\n  mark_as_advanced(NCCL_ROOT_DIR NCCL_INCLUDE_DIRS NCCL_LIBRARIES)\nendif()\n"
  },
  {
    "path": "cmake/Modules/FindNNPACK.cmake",
    "content": "# - Try to find NNPACK\n#\n# The following variables are optionally searched for defaults\n#  NNPACK_ROOT_DIR:            Base directory where all NNPACK components are found\n#\n# The following are set after configuration is done:\n#  NNPACK_FOUND\n#  NNPACK_INCLUDE_DIRS\n#  NNPACK_LIBRARIES\n#  NNPACK_LIBRARYRARY_DIRS\n\ninclude(FindPackageHandleStandardArgs)\n\nset(NNPACK_ROOT_DIR \"\" CACHE PATH \"Folder contains NNPACK\")\n\nfind_path(NNPACK_INCLUDE_DIR nnpack.h\n    PATHS ${NNPACK_ROOT_DIR}\n    PATH_SUFFIXES include)\n\nfind_library(NNPACK_LIBRARY nnpack\n    PATHS ${NNPACK_ROOT_DIR}\n    PATH_SUFFIXES lib lib64)\n\nfind_library(PTHREADPOOL_LIBRARY pthreadpool\n    PATHS ${NNPACK_ROOT_DIR}\n    PATH_SUFFIXES lib lib64)\n\nfind_library(CPUINFO_LIBRARY cpuinfo\n    PATHS ${NNPACK_ROOT_DIR}\n    PATH_SUFFIXES lib lib64)\n\nfind_package_handle_standard_args(NNPACK DEFAULT_MSG NNPACK_INCLUDE_DIR NNPACK_LIBRARY PTHREADPOOL_LIBRARY CPUINFO_LIBRARY)\n\nif(NNPACK_FOUND)\n  set(NNPACK_INCLUDE_DIRS ${NNPACK_INCLUDE_DIR})\n  set(NNPACK_LIBRARIES ${NNPACK_LIBRARY} ${PTHREADPOOL_LIBRARY} ${CPUINFO_LIBRARY})\n  message(STATUS \"Found NNPACK      (include: ${NNPACK_INCLUDE_DIR}, library: ${NNPACK_LIBRARY})\")\n  message(STATUS \"Found PTHREADPOOL (library: ${PTHREADPOOL_LIBRARY})\")\n  message(STATUS \"Found CPUINFO     (library: ${CPUINFO_LIBRARY})\")\n  mark_as_advanced(NNPACK_ROOT_DIR NNPACK_LIBRARY_RELEASE NNPACK_LIBRARY_DEBUG\n                                 NNPACK_LIBRARY NNPACK_INCLUDE_DIR)\nendif()\n"
  },
  {
    "path": "cmake/Modules/FindNumPy.cmake",
    "content": "# - Find the NumPy libraries\n# This module finds if NumPy is installed, and sets the following variables\n# indicating where it is.\n#\n# TODO: Update to provide the libraries and paths for linking npymath lib.\n#\n#  NUMPY_FOUND               - was NumPy found\n#  NUMPY_VERSION             - the version of NumPy found as a string\n#  NUMPY_VERSION_MAJOR       - the major version number of NumPy\n#  NUMPY_VERSION_MINOR       - the minor version number of NumPy\n#  NUMPY_VERSION_PATCH       - the patch version number of NumPy\n#  NUMPY_VERSION_DECIMAL     - e.g. version 1.6.1 is 10601\n#  NUMPY_INCLUDE_DIR         - path to the NumPy include files\n\nunset(NUMPY_VERSION)\nunset(NUMPY_INCLUDE_DIR)\n\nif(PYTHONINTERP_FOUND)\n  execute_process(COMMAND \"${PYTHON_EXECUTABLE}\" \"-c\"\n    \"import numpy as n; print(n.__version__); print(n.get_include());\"\n    RESULT_VARIABLE __result\n    OUTPUT_VARIABLE __output\n    OUTPUT_STRIP_TRAILING_WHITESPACE)\n\n  if(__result MATCHES 0)\n    string(REGEX REPLACE \";\" \"\\\\\\\\;\" __values ${__output})\n    string(REGEX REPLACE \"\\r?\\n\" \";\"    __values ${__values})\n    list(GET __values 0 NUMPY_VERSION)\n    list(GET __values 1 NUMPY_INCLUDE_DIR)\n\n    string(REGEX MATCH \"^([0-9])+\\\\.([0-9])+\\\\.([0-9])+\" __ver_check \"${NUMPY_VERSION}\")\n    if(NOT \"${__ver_check}\" STREQUAL \"\")\n      set(NUMPY_VERSION_MAJOR ${CMAKE_MATCH_1})\n      set(NUMPY_VERSION_MINOR ${CMAKE_MATCH_2})\n      set(NUMPY_VERSION_PATCH ${CMAKE_MATCH_3})\n      math(EXPR NUMPY_VERSION_DECIMAL\n        \"(${NUMPY_VERSION_MAJOR} * 10000) + (${NUMPY_VERSION_MINOR} * 100) + ${NUMPY_VERSION_PATCH}\")\n      string(REGEX REPLACE \"\\\\\\\\\" \"/\"  NUMPY_INCLUDE_DIR ${NUMPY_INCLUDE_DIR})\n    else()\n     unset(NUMPY_VERSION)\n     unset(NUMPY_INCLUDE_DIR)\n     message(STATUS \"Requested NumPy version and include path, but got instead:\\n${__output}\\n\")\n    endif()\n  endif()\nelse()\n  message(STATUS \"To find NumPy Python interpretator is required to be found.\")\nendif()\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(NumPy REQUIRED_VARS NUMPY_INCLUDE_DIR NUMPY_VERSION\n                                        VERSION_VAR   NUMPY_VERSION)\n\nif(NUMPY_FOUND)\n  message(STATUS \"NumPy ver. ${NUMPY_VERSION} found (include: ${NUMPY_INCLUDE_DIR})\")\nendif()\n\ncaffe_clear_vars(__result __output __error_value __values __ver_check __error_value)\n\n"
  },
  {
    "path": "cmake/Modules/FindNuma.cmake",
    "content": "# Find the Numa libraries\n#\n# The following variables are optionally searched for defaults\n#  NUMA_ROOT_DIR:    Base directory where all Numa components are found\n#\n# The following are set after configuration is done:\n#  NUMA_FOUND\n#  Numa_INCLUDE_DIR\n#  Numa_LIBRARIES\n\nfind_path(\n    Numa_INCLUDE_DIR NAMES numa.h\n    PATHS ${NUMA_ROOT_DIR} ${NUMA_ROOT_DIR}/include)\n\nfind_library(\n    Numa_LIBRARIES NAMES numa\n    PATHS ${NUMA_ROOT_DIR} ${NUMA_ROOT_DIR}/lib)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(\n    Numa DEFAULT_MSG Numa_INCLUDE_DIR Numa_LIBRARIES)\n\nif(NUMA_FOUND)\n  message(\n      STATUS\n      \"Found Numa  (include: ${Numa_INCLUDE_DIR}, library: ${Numa_LIBRARIES})\")\n  mark_as_advanced(Numa_INCLUDE_DIR Numa_LIBRARIES)\nendif()\n\n"
  },
  {
    "path": "cmake/Modules/FindOpenBLAS.cmake",
    "content": "\n\nSET(Open_BLAS_INCLUDE_SEARCH_PATHS\n  /usr/include\n  /usr/include/openblas\n  /usr/include/openblas-base\n  /usr/local/include\n  /usr/local/include/openblas\n  /usr/local/include/openblas-base\n  /usr/local/opt/openblas/include\n  /opt/OpenBLAS/include\n  $ENV{OpenBLAS_HOME}\n  $ENV{OpenBLAS_HOME}/include\n)\n\nSET(Open_BLAS_LIB_SEARCH_PATHS\n        /lib/\n        /lib/openblas-base\n        /lib64/\n        /usr/lib\n        /usr/lib/openblas-base\n        /usr/lib64\n        /usr/local/lib\n        /usr/local/lib64\n        /usr/local/opt/openblas/lib\n        /opt/OpenBLAS/lib\n        $ENV{OpenBLAS}cd\n        $ENV{OpenBLAS}/lib\n        $ENV{OpenBLAS_HOME}\n        $ENV{OpenBLAS_HOME}/lib\n )\n\nFIND_PATH(OpenBLAS_INCLUDE_DIR NAMES cblas.h PATHS ${Open_BLAS_INCLUDE_SEARCH_PATHS})\nFIND_LIBRARY(OpenBLAS_LIB NAMES openblas PATHS ${Open_BLAS_LIB_SEARCH_PATHS})\n\nSET(OpenBLAS_FOUND ON)\n\n#    Check include files\nIF(NOT OpenBLAS_INCLUDE_DIR)\n    SET(OpenBLAS_FOUND OFF)\n    MESSAGE(STATUS \"Could not find OpenBLAS include. Turning OpenBLAS_FOUND off\")\nENDIF()\n\n#    Check libraries\nIF(NOT OpenBLAS_LIB)\n    SET(OpenBLAS_FOUND OFF)\n    MESSAGE(STATUS \"Could not find OpenBLAS lib. Turning OpenBLAS_FOUND off\")\nENDIF()\n\nIF (OpenBLAS_FOUND)\n  IF (NOT OpenBLAS_FIND_QUIETLY)\n    MESSAGE(STATUS \"Found OpenBLAS libraries: ${OpenBLAS_LIB}\")\n    MESSAGE(STATUS \"Found OpenBLAS include: ${OpenBLAS_INCLUDE_DIR}\")\n  ENDIF (NOT OpenBLAS_FIND_QUIETLY)\nELSE (OpenBLAS_FOUND)\n  IF (OpenBLAS_FIND_REQUIRED)\n    MESSAGE(FATAL_ERROR \"Could not find OpenBLAS\")\n  ENDIF (OpenBLAS_FIND_REQUIRED)\nENDIF (OpenBLAS_FOUND)\n\nMARK_AS_ADVANCED(\n    OpenBLAS_INCLUDE_DIR\n    OpenBLAS_LIB\n    OpenBLAS\n)\n\n"
  },
  {
    "path": "cmake/Modules/FindRocksDB.cmake",
    "content": "# Find the RocksDB libraries\n#\n# The following variables are optionally searched for defaults\n#  ROCKSDB_ROOT_DIR:    Base directory where all RocksDB components are found\n#\n# The following are set after configuration is done:\n#  ROCKSDB_FOUND\n#  RocksDB_INCLUDE_DIR\n#  RocksDB_LIBRARIES\n\nfind_path(RocksDB_INCLUDE_DIR NAMES rocksdb/db.h\n                             PATHS ${ROCKSDB_ROOT_DIR} ${ROCKSDB_ROOT_DIR}/include)\n\nfind_library(RocksDB_LIBRARIES NAMES rocksdb\n                              PATHS ${ROCKSDB_ROOT_DIR} ${ROCKSDB_ROOT_DIR}/lib)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(RocksDB DEFAULT_MSG RocksDB_INCLUDE_DIR RocksDB_LIBRARIES)\n\nif(ROCKSDB_FOUND)\n  message(STATUS \"Found RocksDB  (include: ${RocksDB_INCLUDE_DIR}, library: ${RocksDB_LIBRARIES})\")\n  mark_as_advanced(RocksDB_INCLUDE_DIR RocksDB_LIBRARIES)\nendif()\n\n"
  },
  {
    "path": "cmake/Modules/FindSnappy.cmake",
    "content": "# Find the Snappy libraries\n#\n# The following variables are optionally searched for defaults\n#  SNAPPY_ROOT_DIR:    Base directory where all Snappy components are found\n#\n# The following are set after configuration is done:\n#  SNAPPY_FOUND\n#  Snappy_INCLUDE_DIR\n#  Snappy_LIBRARIES\n\nfind_path(Snappy_INCLUDE_DIR NAMES snappy.h\n                             PATHS ${SNAPPY_ROOT_DIR} ${SNAPPY_ROOT_DIR}/include)\n\nfind_library(Snappy_LIBRARIES NAMES snappy\n                              PATHS ${SNAPPY_ROOT_DIR} ${SNAPPY_ROOT_DIR}/lib)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(Snappy DEFAULT_MSG Snappy_INCLUDE_DIR Snappy_LIBRARIES)\n\nif(SNAPPY_FOUND)\n  message(STATUS \"Found Snappy  (include: ${Snappy_INCLUDE_DIR}, library: ${Snappy_LIBRARIES})\")\n  mark_as_advanced(Snappy_INCLUDE_DIR Snappy_LIBRARIES)\n  caffe_parse_header(${Snappy_INCLUDE_DIR}/snappy-stubs-public.h\n                     SNAPPY_VERION_LINES SNAPPY_MAJOR SNAPPY_MINOR SNAPPY_PATCHLEVEL)\n  set(Snappy_VERSION \"${SNAPPY_MAJOR}.${SNAPPY_MINOR}.${SNAPPY_PATCHLEVEL}\")\nendif()\n\n"
  },
  {
    "path": "cmake/Modules/FindZMQ.cmake",
    "content": "# Find the ZMQ libraries\n#\n# The following variables are optionally searched for defaults\n#  ZMQ_ROOT_DIR:    Base directory where all ZMQ components are found\n#\n# The following are set after configuration is done:\n#  ZMQ_FOUND\n#  ZMQ_INCLUDE_DIR\n#  ZMQ_LIBRARIES\n#  ZMQ_VERSION_MAJOR \n\nfind_path(ZMQ_INCLUDE_DIR NAMES zmq.h\n                             PATHS ${ZMQ_ROOT_DIR} ${ZMQ_ROOT_DIR}/include)\n\nfind_library(ZMQ_LIBRARIES NAMES zmq\n                              PATHS ${ZMQ_ROOT_DIR} ${ZMQ_ROOT_DIR}/lib)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(ZMQ DEFAULT_MSG ZMQ_INCLUDE_DIR ZMQ_LIBRARIES)\n\nif(ZMQ_FOUND)\n  message(STATUS \"Found ZMQ  (include: ${ZMQ_INCLUDE_DIR}, library: ${ZMQ_LIBRARIES})\")\n  mark_as_advanced(ZMQ_INCLUDE_DIR ZMQ_LIBRARIES)\n\n  caffe_parse_header(${ZMQ_INCLUDE_DIR}/zmq.h ZMQ_VERSION_LINES ZMQ_VERSION_MAJOR)\n  if(${ZMQ_VERSION_MAJOR} VERSION_LESS \"3\")\n    message(WARNING \"Caffe2 requires zmq version 3 or above, but found \" ${ZMQ_VERSION_MAJOR} \". Disabling zmq for now.\")\n    set(ZMQ_FOUND)\n  else()\n\n  endif()\nendif()\n"
  },
  {
    "path": "cmake/Modules/Findpybind11.cmake",
    "content": "# Try to find the pybind11 library and headers.\n#  pybind11_FOUND        - system has pybind11\n#  pybind11_INCLUDE_DIRS - the pybind11 include directory\n\nfind_path(pybind11_INCLUDE_DIR\n\tNAMES pybind11/pybind11.h\n\tDOC \"The directory where pybind11 includes reside\"\n)\n\nset(pybind11_INCLUDE_DIRS ${pybind11_INCLUDE_DIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(pybind11\n\tFOUND_VAR pybind11_FOUND\n\tREQUIRED_VARS pybind11_INCLUDE_DIR\n)\n\nmark_as_advanced(pybind11_FOUND)\n"
  },
  {
    "path": "cmake/Modules/FindvecLib.cmake",
    "content": "# Find the vecLib libraries as part of Accelerate.framework or as standalon framework\n#\n# The following are set after configuration is done:\n#  VECLIB_FOUND\n#  vecLib_INCLUDE_DIR\n#  vecLib_LINKER_LIBS\n\n\nif(NOT APPLE)\n  return()\nendif()\n\nset(__veclib_include_suffix \"Frameworks/vecLib.framework/Versions/Current/Headers\")\n\nfind_path(vecLib_INCLUDE_DIR vecLib.h\n          DOC \"vecLib include directory\"\n          PATHS /System/Library/Frameworks/Accelerate.framework/Versions/Current/${__veclib_include_suffix}\n                /System/Library/${__veclib_include_suffix}\n                /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/Accelerate.framework/Versions/Current/Frameworks/vecLib.framework/Headers/\n          NO_DEFAULT_PATH)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(vecLib DEFAULT_MSG vecLib_INCLUDE_DIR)\n\nif(VECLIB_FOUND)\n  if(vecLib_INCLUDE_DIR MATCHES \"^/System/Library/Frameworks/vecLib.framework.*\")\n    set(vecLib_LINKER_LIBS -lcblas \"-framework vecLib\")\n    message(STATUS \"Found standalone vecLib.framework\")\n  else()\n    set(vecLib_LINKER_LIBS -lcblas \"-framework Accelerate\")\n    message(STATUS \"Found vecLib as part of Accelerate.framework\")\n  endif()\n\n  mark_as_advanced(vecLib_INCLUDE_DIR)\nendif()\n"
  },
  {
    "path": "cmake/Modules_CUDA_fix/FindCUDA/make2cmake.cmake",
    "content": "#  James Bigler, NVIDIA Corp (nvidia.com - jbigler)\n#  Abe Stephens, SCI Institute -- http://www.sci.utah.edu/~abe/FindCuda.html\n#\n#  Copyright (c) 2008 - 2009 NVIDIA Corporation.  All rights reserved.\n#\n#  Copyright (c) 2007-2009\n#  Scientific Computing and Imaging Institute, University of Utah\n#\n#  This code is licensed under the MIT License.  See the FindCUDA.cmake script\n#  for the text of the license.\n\n# The MIT License\n#\n# License for the specific language governing rights and limitations under\n# Permission is hereby granted, free of charge, to any person obtaining a\n# copy of this software and associated documentation files (the \"Software\"),\n# to deal in the Software without restriction, including without limitation\n# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n# and/or sell copies of the Software, and to permit persons to whom the\n# Software is furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included\n# in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n# DEALINGS IN THE SOFTWARE.\n#\n\n#######################################################################\n# This converts a file written in makefile syntax into one that can be included\n# by CMake.\n\n# Input variables\n#\n# verbose:BOOL=<>          OFF: Be as quiet as possible (default)\n#                          ON : Extra output\n#\n# input_file:FILEPATH=<>   Path to dependency file in makefile format\n#\n# output_file:FILEPATH=<>  Path to file with dependencies in CMake readable variable\n#\n\nfile(READ ${input_file} depend_text)\n\nif (NOT \"${depend_text}\" STREQUAL \"\")\n\n  # message(\"FOUND DEPENDS\")\n\n  string(REPLACE \"\\\\ \" \" \" depend_text ${depend_text})\n\n  # This works for the nvcc -M generated dependency files.\n  string(REGEX REPLACE \"^.* : \" \"\" depend_text ${depend_text})\n  string(REGEX REPLACE \"[ \\\\\\\\]*\\n\" \";\" depend_text ${depend_text})\n\n  set(dependency_list \"\")\n\n  foreach(file ${depend_text})\n\n    string(REGEX REPLACE \"^ +\" \"\" file ${file})\n\n    # OK, now if we had a UNC path, nvcc has a tendency to only output the first '/'\n    # instead of '//'.  Here we will test to see if the file exists, if it doesn't then\n    # try to prepend another '/' to the path and test again.  If it still fails remove the\n    # path.\n\n    if(NOT EXISTS \"${file}\")\n      if (EXISTS \"/${file}\")\n        set(file \"/${file}\")\n      else()\n        if(verbose)\n          message(WARNING \" Removing non-existent dependency file: ${file}\")\n        endif()\n        set(file \"\")\n      endif()\n    endif()\n\n    # Make sure we check to see if we have a file, before asking if it is not a directory.\n    # if(NOT IS_DIRECTORY \"\") will return TRUE.\n    if(file AND NOT IS_DIRECTORY \"${file}\")\n      # If softlinks start to matter, we should change this to REALPATH.  For now we need\n      # to flatten paths, because nvcc can generate stuff like /bin/../include instead of\n      # just /include.\n      get_filename_component(file_absolute \"${file}\" ABSOLUTE)\n      list(APPEND dependency_list \"${file_absolute}\")\n    endif()\n\n  endforeach()\n\nelse()\n  # message(\"FOUND NO DEPENDS\")\nendif()\n\n# Remove the duplicate entries and sort them.\nlist(REMOVE_DUPLICATES dependency_list)\nlist(SORT dependency_list)\n\nforeach(file ${dependency_list})\n  string(APPEND cuda_nvcc_depend \" \\\"${file}\\\"\\n\")\nendforeach()\n\nfile(WRITE ${output_file} \"# Generated by: make2cmake.cmake\\nSET(CUDA_NVCC_DEPEND\\n ${cuda_nvcc_depend})\\n\\n\")\n"
  },
  {
    "path": "cmake/Modules_CUDA_fix/FindCUDA/parse_cubin.cmake",
    "content": "#  James Bigler, NVIDIA Corp (nvidia.com - jbigler)\n#  Abe Stephens, SCI Institute -- http://www.sci.utah.edu/~abe/FindCuda.html\n#\n#  Copyright (c) 2008 - 2009 NVIDIA Corporation.  All rights reserved.\n#\n#  Copyright (c) 2007-2009\n#  Scientific Computing and Imaging Institute, University of Utah\n#\n#  This code is licensed under the MIT License.  See the FindCUDA.cmake script\n#  for the text of the license.\n\n# The MIT License\n#\n# License for the specific language governing rights and limitations under\n# Permission is hereby granted, free of charge, to any person obtaining a\n# copy of this software and associated documentation files (the \"Software\"),\n# to deal in the Software without restriction, including without limitation\n# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n# and/or sell copies of the Software, and to permit persons to whom the\n# Software is furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included\n# in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n# DEALINGS IN THE SOFTWARE.\n#\n\n#######################################################################\n# Parses a .cubin file produced by nvcc and reports statistics about the file.\n\n\nfile(READ ${input_file} file_text)\n\nif (NOT \"${file_text}\" STREQUAL \"\")\n\n  string(REPLACE \";\" \"\\\\;\" file_text ${file_text})\n  string(REPLACE \"\\ncode\" \";code\" file_text ${file_text})\n\n  list(LENGTH file_text len)\n\n  foreach(line ${file_text})\n\n    # Only look at \"code { }\" blocks.\n    if(line MATCHES \"^code\")\n\n      # Break into individual lines.\n      string(REGEX REPLACE \"\\n\" \";\" line ${line})\n\n      foreach(entry ${line})\n\n        # Extract kernel names.\n        if (${entry} MATCHES \"[^g]name = ([^ ]+)\")\n          set(entry \"${CMAKE_MATCH_1}\")\n\n          # Check to see if the kernel name starts with \"_\"\n          set(skip FALSE)\n          # if (${entry} MATCHES \"^_\")\n            # Skip the rest of this block.\n            # message(\"Skipping ${entry}\")\n            # set(skip TRUE)\n          # else ()\n            message(\"Kernel:    ${entry}\")\n          # endif ()\n\n        endif()\n\n        # Skip the rest of the block if necessary\n        if(NOT skip)\n\n          # Registers\n          if (${entry} MATCHES \"reg([ ]+)=([ ]+)([^ ]+)\")\n            set(entry \"${CMAKE_MATCH_3}\")\n            message(\"Registers: ${entry}\")\n          endif()\n\n          # Local memory\n          if (${entry} MATCHES \"lmem([ ]+)=([ ]+)([^ ]+)\")\n            set(entry \"${CMAKE_MATCH_3}\")\n            message(\"Local:     ${entry}\")\n          endif()\n\n          # Shared memory\n          if (${entry} MATCHES \"smem([ ]+)=([ ]+)([^ ]+)\")\n            set(entry \"${CMAKE_MATCH_3}\")\n            message(\"Shared:    ${entry}\")\n          endif()\n\n          if (${entry} MATCHES \"^}\")\n            message(\"\")\n          endif()\n\n        endif()\n\n\n      endforeach()\n\n    endif()\n\n  endforeach()\n\nelse()\n  # message(\"FOUND NO DEPENDS\")\nendif()\n\n\n"
  },
  {
    "path": "cmake/Modules_CUDA_fix/FindCUDA/run_nvcc.cmake",
    "content": "#  James Bigler, NVIDIA Corp (nvidia.com - jbigler)\n#\n#  Copyright (c) 2008 - 2009 NVIDIA Corporation.  All rights reserved.\n#\n#  This code is licensed under the MIT License.  See the FindCUDA.cmake script\n#  for the text of the license.\n\n# The MIT License\n#\n# License for the specific language governing rights and limitations under\n# Permission is hereby granted, free of charge, to any person obtaining a\n# copy of this software and associated documentation files (the \"Software\"),\n# to deal in the Software without restriction, including without limitation\n# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n# and/or sell copies of the Software, and to permit persons to whom the\n# Software is furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included\n# in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n# DEALINGS IN THE SOFTWARE.\n\n\n##########################################################################\n# This file runs the nvcc commands to produce the desired output file along with\n# the dependency file needed by CMake to compute dependencies.  In addition the\n# file checks the output of each command and if the command fails it deletes the\n# output files.\n\n# Input variables\n#\n# verbose:BOOL=<>          OFF: Be as quiet as possible (default)\n#                          ON : Describe each step\n#\n# build_configuration:STRING=<> Typically one of Debug, MinSizeRel, Release, or\n#                               RelWithDebInfo, but it should match one of the\n#                               entries in CUDA_HOST_FLAGS. This is the build\n#                               configuration used when compiling the code.  If\n#                               blank or unspecified Debug is assumed as this is\n#                               what CMake does.\n#\n# generated_file:STRING=<> File to generate.  This argument must be passed in.\n#\n# generated_cubin_file:STRING=<> File to generate.  This argument must be passed\n#                                                   in if build_cubin is true.\n\ncmake_policy(PUSH)\ncmake_policy(SET CMP0007 NEW)\nif(NOT generated_file)\n  message(FATAL_ERROR \"You must specify generated_file on the command line\")\nendif()\n\n# Set these up as variables to make reading the generated file easier\nset(CMAKE_COMMAND \"@CMAKE_COMMAND@\") # path\nset(source_file \"@source_file@\") # path\nset(NVCC_generated_dependency_file \"@NVCC_generated_dependency_file@\") # path\nset(cmake_dependency_file \"@cmake_dependency_file@\") # path\nset(CUDA_make2cmake \"@CUDA_make2cmake@\") # path\nset(CUDA_parse_cubin \"@CUDA_parse_cubin@\") # path\nset(build_cubin @build_cubin@) # bool\nset(CUDA_HOST_COMPILER \"@CUDA_HOST_COMPILER@\") # path\n# We won't actually use these variables for now, but we need to set this, in\n# order to force this file to be run again if it changes.\nset(generated_file_path \"@generated_file_path@\") # path\nset(generated_file_internal \"@generated_file@\") # path\nset(generated_cubin_file_internal \"@generated_cubin_file@\") # path\n\nset(CUDA_NVCC_EXECUTABLE \"@CUDA_NVCC_EXECUTABLE@\") # path\nset(CUDA_NVCC_FLAGS @CUDA_NVCC_FLAGS@ ;; @CUDA_WRAP_OPTION_NVCC_FLAGS@) # list\n@CUDA_NVCC_FLAGS_CONFIG@\nset(nvcc_flags @nvcc_flags@) # list\nset(CUDA_NVCC_INCLUDE_DIRS \"@CUDA_NVCC_INCLUDE_DIRS@\") # list (needs to be in quotes to handle spaces properly).\nset(CUDA_NVCC_COMPILE_DEFINITIONS [==[@CUDA_NVCC_COMPILE_DEFINITIONS@]==]) # list (needs to be in lua quotes see #16510 ).\nset(format_flag \"@format_flag@\") # string\nset(cuda_language_flag @cuda_language_flag@) # list\n\n# Clean up list of include directories and add -I flags\nlist(REMOVE_DUPLICATES CUDA_NVCC_INCLUDE_DIRS)\nset(CUDA_NVCC_INCLUDE_ARGS)\nforeach(dir ${CUDA_NVCC_INCLUDE_DIRS})\n  # Extra quotes are added around each flag to help nvcc parse out flags with spaces.\n  list(APPEND CUDA_NVCC_INCLUDE_ARGS \"-I${dir}\")\nendforeach()\n\n# Clean up list of compile definitions, add -D flags, and append to nvcc_flags\nlist(REMOVE_DUPLICATES CUDA_NVCC_COMPILE_DEFINITIONS)\nforeach(def ${CUDA_NVCC_COMPILE_DEFINITIONS})\n  list(APPEND nvcc_flags \"-D${def}\")\nendforeach()\n\nif(build_cubin AND NOT generated_cubin_file)\n  message(FATAL_ERROR \"You must specify generated_cubin_file on the command line\")\nendif()\n\n# This is the list of host compilation flags.  It C or CXX should already have\n# been chosen by FindCUDA.cmake.\n@CUDA_HOST_FLAGS@\n\n# Take the compiler flags and package them up to be sent to the compiler via -Xcompiler\nset(nvcc_host_compiler_flags \"\")\n# If we weren't given a build_configuration, use Debug.\nif(NOT build_configuration)\n  set(build_configuration Debug)\nendif()\nstring(TOUPPER \"${build_configuration}\" build_configuration)\n#message(\"CUDA_NVCC_HOST_COMPILER_FLAGS = ${CUDA_NVCC_HOST_COMPILER_FLAGS}\")\nforeach(flag ${CMAKE_HOST_FLAGS} ${CMAKE_HOST_FLAGS_${build_configuration}})\n  # Extra quotes are added around each flag to help nvcc parse out flags with spaces.\n  string(APPEND nvcc_host_compiler_flags \",\\\"${flag}\\\"\")\nendforeach()\nif (nvcc_host_compiler_flags)\n  set(nvcc_host_compiler_flags \"-Xcompiler\" ${nvcc_host_compiler_flags})\nendif()\n#message(\"nvcc_host_compiler_flags = \\\"${nvcc_host_compiler_flags}\\\"\")\n# Add the build specific configuration flags\nlist(APPEND CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS_${build_configuration}})\n\n# Any -ccbin existing in CUDA_NVCC_FLAGS gets highest priority\nlist( FIND CUDA_NVCC_FLAGS \"-ccbin\" ccbin_found0 )\nlist( FIND CUDA_NVCC_FLAGS \"--compiler-bindir\" ccbin_found1 )\nif( ccbin_found0 LESS 0 AND ccbin_found1 LESS 0 AND CUDA_HOST_COMPILER )\n  if (CUDA_HOST_COMPILER STREQUAL \"@_CUDA_MSVC_HOST_COMPILER@\" AND DEFINED CCBIN)\n    set(CCBIN -ccbin \"${CCBIN}\")\n  else()\n    set(CCBIN -ccbin \"${CUDA_HOST_COMPILER}\")\n  endif()\nendif()\n\n# cuda_execute_process - Executes a command with optional command echo and status message.\n#\n#   status  - Status message to print if verbose is true\n#   command - COMMAND argument from the usual execute_process argument structure\n#   ARGN    - Remaining arguments are the command with arguments\n#\n#   CUDA_result - return value from running the command\n#\n# Make this a macro instead of a function, so that things like RESULT_VARIABLE\n# and other return variables are present after executing the process.\nmacro(cuda_execute_process status command)\n  set(_command ${command})\n  if(NOT \"x${_command}\" STREQUAL \"xCOMMAND\")\n    message(FATAL_ERROR \"Malformed call to cuda_execute_process.  Missing COMMAND as second argument. (command = ${command})\")\n  endif()\n  if(verbose)\n    execute_process(COMMAND \"${CMAKE_COMMAND}\" -E echo -- ${status})\n    # Now we need to build up our command string.  We are accounting for quotes\n    # and spaces, anything else is left up to the user to fix if they want to\n    # copy and paste a runnable command line.\n    set(cuda_execute_process_string)\n    foreach(arg ${ARGN})\n      # If there are quotes, excape them, so they come through.\n      string(REPLACE \"\\\"\" \"\\\\\\\"\" arg ${arg})\n      # Args with spaces need quotes around them to get them to be parsed as a single argument.\n      if(arg MATCHES \" \")\n        list(APPEND cuda_execute_process_string \"\\\"${arg}\\\"\")\n      else()\n        list(APPEND cuda_execute_process_string ${arg})\n      endif()\n    endforeach()\n    # Echo the command\n    execute_process(COMMAND ${CMAKE_COMMAND} -E echo ${cuda_execute_process_string})\n  endif()\n  # Run the command\n  execute_process(COMMAND ${ARGN} RESULT_VARIABLE CUDA_result )\nendmacro()\n\n# Delete the target file\ncuda_execute_process(\n  \"Removing ${generated_file}\"\n  COMMAND \"${CMAKE_COMMAND}\" -E remove \"${generated_file}\"\n  )\n\n# For CUDA 2.3 and below, -G -M doesn't work, so remove the -G flag\n# for dependency generation and hope for the best.\nset(depends_CUDA_NVCC_FLAGS \"${CUDA_NVCC_FLAGS}\")\nset(CUDA_VERSION @CUDA_VERSION@)\nif(CUDA_VERSION VERSION_LESS \"3.0\")\n  # Note that this will remove all occurrences of -G.\n  list(REMOVE_ITEM depends_CUDA_NVCC_FLAGS \"-G\")\nendif()\n\n# nvcc doesn't define __CUDACC__ for some reason when generating dependency files.  This\n# can cause incorrect dependencies when #including files based on this macro which is\n# defined in the generating passes of nvcc invocation.  We will go ahead and manually\n# define this for now until a future version fixes this bug.\nset(CUDACC_DEFINE -D__CUDACC__)\n\n# Generate the dependency file\ncuda_execute_process(\n  \"Generating dependency file: ${NVCC_generated_dependency_file}\"\n  COMMAND \"${CUDA_NVCC_EXECUTABLE}\"\n  -M\n  ${CUDACC_DEFINE}\n  \"${source_file}\"\n  -o \"${NVCC_generated_dependency_file}\"\n  ${CCBIN}\n  ${nvcc_flags}\n  ${nvcc_host_compiler_flags}\n  ${depends_CUDA_NVCC_FLAGS}\n  -DNVCC\n  ${CUDA_NVCC_INCLUDE_ARGS}\n  )\n\nif(CUDA_result)\n  message(FATAL_ERROR \"Error generating ${generated_file}\")\nendif()\n\n# Generate the cmake readable dependency file to a temp file.  Don't put the\n# quotes just around the filenames for the input_file and output_file variables.\n# CMake will pass the quotes through and not be able to find the file.\ncuda_execute_process(\n  \"Generating temporary cmake readable file: ${cmake_dependency_file}.tmp\"\n  COMMAND \"${CMAKE_COMMAND}\"\n  -D \"input_file:FILEPATH=${NVCC_generated_dependency_file}\"\n  -D \"output_file:FILEPATH=${cmake_dependency_file}.tmp\"\n  -D \"verbose=${verbose}\"\n  -P \"${CUDA_make2cmake}\"\n  )\n\nif(CUDA_result)\n  message(FATAL_ERROR \"Error generating ${generated_file}\")\nendif()\n\n# Copy the file if it is different\ncuda_execute_process(\n  \"Copy if different ${cmake_dependency_file}.tmp to ${cmake_dependency_file}\"\n  COMMAND \"${CMAKE_COMMAND}\" -E copy_if_different \"${cmake_dependency_file}.tmp\" \"${cmake_dependency_file}\"\n  )\n\nif(CUDA_result)\n  message(FATAL_ERROR \"Error generating ${generated_file}\")\nendif()\n\n# Delete the temporary file\ncuda_execute_process(\n  \"Removing ${cmake_dependency_file}.tmp and ${NVCC_generated_dependency_file}\"\n  COMMAND \"${CMAKE_COMMAND}\" -E remove \"${cmake_dependency_file}.tmp\" \"${NVCC_generated_dependency_file}\"\n  )\n\nif(CUDA_result)\n  message(FATAL_ERROR \"Error generating ${generated_file}\")\nendif()\n\n# Generate the code\ncuda_execute_process(\n  \"Generating ${generated_file}\"\n  COMMAND \"${CUDA_NVCC_EXECUTABLE}\"\n  \"${source_file}\"\n  ${cuda_language_flag}\n  ${format_flag} -o \"${generated_file}\"\n  ${CCBIN}\n  ${nvcc_flags}\n  ${nvcc_host_compiler_flags}\n  ${CUDA_NVCC_FLAGS}\n  -DNVCC\n  ${CUDA_NVCC_INCLUDE_ARGS}\n  )\n\nif(CUDA_result)\n  # Since nvcc can sometimes leave half done files make sure that we delete the output file.\n  cuda_execute_process(\n    \"Removing ${generated_file}\"\n    COMMAND \"${CMAKE_COMMAND}\" -E remove \"${generated_file}\"\n    )\n  message(FATAL_ERROR \"Error generating file ${generated_file}\")\nelse()\n  if(verbose)\n    message(\"Generated ${generated_file} successfully.\")\n  endif()\nendif()\n\n# Cubin resource report commands.\nif( build_cubin )\n  # Run with -cubin to produce resource usage report.\n  cuda_execute_process(\n    \"Generating ${generated_cubin_file}\"\n    COMMAND \"${CUDA_NVCC_EXECUTABLE}\"\n    \"${source_file}\"\n    ${CUDA_NVCC_FLAGS}\n    ${nvcc_flags}\n    ${CCBIN}\n    ${nvcc_host_compiler_flags}\n    -DNVCC\n    -cubin\n    -o \"${generated_cubin_file}\"\n    ${CUDA_NVCC_INCLUDE_ARGS}\n    )\n\n  # Execute the parser script.\n  cuda_execute_process(\n    \"Executing the parser script\"\n    COMMAND  \"${CMAKE_COMMAND}\"\n    -D \"input_file:STRING=${generated_cubin_file}\"\n    -P \"${CUDA_parse_cubin}\"\n    )\n\nendif()\n\ncmake_policy(POP)\n"
  },
  {
    "path": "cmake/Modules_CUDA_fix/FindCUDA/select_compute_arch.cmake",
    "content": "# Synopsis:\n#   CUDA_SELECT_NVCC_ARCH_FLAGS(out_variable [target_CUDA_architectures])\n#   -- Selects GPU arch flags for nvcc based on target_CUDA_architectures\n#      target_CUDA_architectures : Auto | Common | All | LIST(ARCH_AND_PTX ...)\n#       - \"Auto\" detects local machine GPU compute arch at runtime.\n#       - \"Common\" and \"All\" cover common and entire subsets of architectures\n#      ARCH_AND_PTX : NAME | NUM.NUM | NUM.NUM(NUM.NUM) | NUM.NUM+PTX\n#      NAME: Fermi Kepler Maxwell Kepler+Tegra Kepler+Tesla Maxwell+Tegra Pascal\n#      NUM: Any number. Only those pairs are currently accepted by NVCC though:\n#            2.0 2.1 3.0 3.2 3.5 3.7 5.0 5.2 5.3 6.0 6.2\n#      Returns LIST of flags to be added to CUDA_NVCC_FLAGS in ${out_variable}\n#      Additionally, sets ${out_variable}_readable to the resulting numeric list\n#      Example:\n#       CUDA_SELECT_NVCC_ARCH_FLAGS(ARCH_FLAGS 3.0 3.5+PTX 5.2(5.0) Maxwell)\n#        LIST(APPEND CUDA_NVCC_FLAGS ${ARCH_FLAGS})\n#\n#      More info on CUDA architectures: https://en.wikipedia.org/wiki/CUDA\n#\n\n# This list will be used for CUDA_ARCH_NAME = All option\nset(CUDA_KNOWN_GPU_ARCHITECTURES  \"Fermi\" \"Kepler\" \"Maxwell\")\n\n# This list will be used for CUDA_ARCH_NAME = Common option (enabled by default)\nset(CUDA_COMMON_GPU_ARCHITECTURES \"3.0\" \"3.5\" \"5.0\")\n\nif (CUDA_VERSION VERSION_GREATER \"6.5\")\n  list(APPEND CUDA_KNOWN_GPU_ARCHITECTURES \"Kepler+Tegra\" \"Kepler+Tesla\" \"Maxwell+Tegra\")\n  list(APPEND CUDA_COMMON_GPU_ARCHITECTURES \"5.2\")\nendif ()\n\nif (CUDA_VERSION VERSION_GREATER \"7.5\")\n  list(APPEND CUDA_KNOWN_GPU_ARCHITECTURES \"Pascal\")\n  list(APPEND CUDA_COMMON_GPU_ARCHITECTURES \"6.0\" \"6.1\")\nelse()\n  list(APPEND CUDA_COMMON_GPU_ARCHITECTURES \"5.2+PTX\")\nendif ()\n\nif (CUDA_VERSION VERSION_GREATER \"8.5\")\n  list(APPEND CUDA_KNOWN_GPU_ARCHITECTURES \"Volta\")\n  list(APPEND CUDA_COMMON_GPU_ARCHITECTURES \"7.0\" \"7.0+PTX\")\nelse()\n  list(APPEND CUDA_COMMON_GPU_ARCHITECTURES \"6.1+PTX\")\nendif()\n\n################################################################################################\n# A function for automatic detection of GPUs installed  (if autodetection is enabled)\n# Usage:\n#   CUDA_DETECT_INSTALLED_GPUS(OUT_VARIABLE)\n#\nfunction(CUDA_DETECT_INSTALLED_GPUS OUT_VARIABLE)\n  if(NOT CUDA_GPU_DETECT_OUTPUT)\n    set(file ${PROJECT_BINARY_DIR}/detect_cuda_compute_capabilities.cpp)\n\n    file(WRITE ${file} \"\"\n      \"#include <cuda_runtime.h>\\n\"\n      \"#include <cstdio>\\n\"\n      \"int main()\\n\"\n      \"{\\n\"\n      \"  int count = 0;\\n\"\n      \"  if (cudaSuccess != cudaGetDeviceCount(&count)) return -1;\\n\"\n      \"  if (count == 0) return -1;\\n\"\n      \"  for (int device = 0; device < count; ++device)\\n\"\n      \"  {\\n\"\n      \"    cudaDeviceProp prop;\\n\"\n      \"    if (cudaSuccess == cudaGetDeviceProperties(&prop, device))\\n\"\n      \"      std::printf(\\\"%d.%d \\\", prop.major, prop.minor);\\n\"\n      \"  }\\n\"\n      \"  return 0;\\n\"\n      \"}\\n\")\n\n    try_run(run_result compile_result ${PROJECT_BINARY_DIR} ${file}\n            CMAKE_FLAGS \"-DINCLUDE_DIRECTORIES=${CUDA_INCLUDE_DIRS}\"\n            LINK_LIBRARIES ${CUDA_LIBRARIES}\n            RUN_OUTPUT_VARIABLE compute_capabilities)\n\n    if(run_result EQUAL 0)\n      string(REPLACE \"2.1\" \"2.1(2.0)\" compute_capabilities \"${compute_capabilities}\")\n      set(CUDA_GPU_DETECT_OUTPUT ${compute_capabilities}\n        CACHE INTERNAL \"Returned GPU architectures from detect_gpus tool\" FORCE)\n    endif()\n  endif()\n\n  if(NOT CUDA_GPU_DETECT_OUTPUT)\n    message(STATUS \"Automatic GPU detection failed. Building for common architectures.\")\n    set(${OUT_VARIABLE} ${CUDA_COMMON_GPU_ARCHITECTURES} PARENT_SCOPE)\n  else()\n    set(${OUT_VARIABLE} ${CUDA_GPU_DETECT_OUTPUT} PARENT_SCOPE)\n  endif()\nendfunction()\n\n\n################################################################################################\n# Function for selecting GPU arch flags for nvcc based on CUDA architectures from parameter list\n# Usage:\n#   SELECT_NVCC_ARCH_FLAGS(out_variable [list of CUDA compute archs])\nfunction(CUDA_SELECT_NVCC_ARCH_FLAGS out_variable)\n  set(CUDA_ARCH_LIST \"${ARGN}\")\n\n  if(\"X${CUDA_ARCH_LIST}\" STREQUAL \"X\" )\n    set(CUDA_ARCH_LIST \"Auto\")\n  endif()\n\n  set(cuda_arch_bin)\n  set(cuda_arch_ptx)\n\n  if(\"${CUDA_ARCH_LIST}\" STREQUAL \"All\")\n    set(CUDA_ARCH_LIST ${CUDA_KNOWN_GPU_ARCHITECTURES})\n  elseif(\"${CUDA_ARCH_LIST}\" STREQUAL \"Common\")\n    set(CUDA_ARCH_LIST ${CUDA_COMMON_GPU_ARCHITECTURES})\n  elseif(\"${CUDA_ARCH_LIST}\" STREQUAL \"Auto\")\n    CUDA_DETECT_INSTALLED_GPUS(CUDA_ARCH_LIST)\n    message(STATUS \"Autodetected CUDA architecture(s): ${CUDA_ARCH_LIST}\")\n  endif()\n\n  # Now process the list and look for names\n  string(REGEX REPLACE \"[ \\t]+\" \";\" CUDA_ARCH_LIST \"${CUDA_ARCH_LIST}\")\n  list(REMOVE_DUPLICATES CUDA_ARCH_LIST)\n  foreach(arch_name ${CUDA_ARCH_LIST})\n    set(arch_bin)\n    set(arch_ptx)\n    set(add_ptx FALSE)\n    # Check to see if we are compiling PTX\n    if(arch_name MATCHES \"(.*)\\\\+PTX$\")\n      set(add_ptx TRUE)\n      set(arch_name ${CMAKE_MATCH_1})\n    endif()\n    if(arch_name MATCHES \"^([0-9]\\\\.[0-9](\\\\([0-9]\\\\.[0-9]\\\\))?)$\")\n      set(arch_bin ${CMAKE_MATCH_1})\n      set(arch_ptx ${arch_bin})\n    else()\n      # Look for it in our list of known architectures\n      if(${arch_name} STREQUAL \"Fermi\")\n        set(arch_bin 2.0 \"2.1(2.0)\")\n      elseif(${arch_name} STREQUAL \"Kepler+Tegra\")\n        set(arch_bin 3.2)\n      elseif(${arch_name} STREQUAL \"Kepler+Tesla\")\n        set(arch_bin 3.7)\n      elseif(${arch_name} STREQUAL \"Kepler\")\n        set(arch_bin 3.0 3.5)\n        set(arch_ptx 3.5)\n      elseif(${arch_name} STREQUAL \"Maxwell+Tegra\")\n        set(arch_bin 5.3)\n      elseif(${arch_name} STREQUAL \"Maxwell\")\n        set(arch_bin 5.0 5.2)\n        set(arch_ptx 5.2)\n      elseif(${arch_name} STREQUAL \"Pascal\")\n        set(arch_bin 6.0 6.1)\n        set(arch_ptx 6.1)\n      elseif(${arch_name} STREQUAL \"Volta\")\n        set(arch_bin 7.0 7.0)\n        set(arch_ptx 7.0)\n      else()\n        message(SEND_ERROR \"Unknown CUDA Architecture Name ${arch_name} in CUDA_SELECT_NVCC_ARCH_FLAGS\")\n      endif()\n    endif()\n    if(NOT arch_bin)\n      message(SEND_ERROR \"arch_bin wasn't set for some reason\")\n    endif()\n    list(APPEND cuda_arch_bin ${arch_bin})\n    if(add_ptx)\n      if (NOT arch_ptx)\n        set(arch_ptx ${arch_bin})\n      endif()\n      list(APPEND cuda_arch_ptx ${arch_ptx})\n    endif()\n  endforeach()\n\n  # remove dots and convert to lists\n  string(REGEX REPLACE \"\\\\.\" \"\" cuda_arch_bin \"${cuda_arch_bin}\")\n  string(REGEX REPLACE \"\\\\.\" \"\" cuda_arch_ptx \"${cuda_arch_ptx}\")\n  string(REGEX MATCHALL \"[0-9()]+\" cuda_arch_bin \"${cuda_arch_bin}\")\n  string(REGEX MATCHALL \"[0-9]+\"   cuda_arch_ptx \"${cuda_arch_ptx}\")\n\n  if(cuda_arch_bin)\n    list(REMOVE_DUPLICATES cuda_arch_bin)\n  endif()\n  if(cuda_arch_ptx)\n    list(REMOVE_DUPLICATES cuda_arch_ptx)\n  endif()\n\n  set(nvcc_flags \"\")\n  set(nvcc_archs_readable \"\")\n\n  # Tell NVCC to add binaries for the specified GPUs\n  foreach(arch ${cuda_arch_bin})\n    if(arch MATCHES \"([0-9]+)\\\\(([0-9]+)\\\\)\")\n      # User explicitly specified ARCH for the concrete CODE\n      list(APPEND nvcc_flags -gencode arch=compute_${CMAKE_MATCH_2},code=sm_${CMAKE_MATCH_1})\n      list(APPEND nvcc_archs_readable sm_${CMAKE_MATCH_1})\n    else()\n      # User didn't explicitly specify ARCH for the concrete CODE, we assume ARCH=CODE\n      list(APPEND nvcc_flags -gencode arch=compute_${arch},code=sm_${arch})\n      list(APPEND nvcc_archs_readable sm_${arch})\n    endif()\n  endforeach()\n\n  # Tell NVCC to add PTX intermediate code for the specified architectures\n  foreach(arch ${cuda_arch_ptx})\n    list(APPEND nvcc_flags -gencode arch=compute_${arch},code=compute_${arch})\n    list(APPEND nvcc_archs_readable compute_${arch})\n  endforeach()\n\n  string(REPLACE \";\" \" \" nvcc_archs_readable \"${nvcc_archs_readable}\")\n  set(${out_variable}          ${nvcc_flags}          PARENT_SCOPE)\n  set(${out_variable}_readable ${nvcc_archs_readable} PARENT_SCOPE)\nendfunction()\n"
  },
  {
    "path": "cmake/Modules_CUDA_fix/FindCUDA.cmake",
    "content": "#.rst:\n# FindCUDA\n# --------\n#\n# .. note::\n#\n#   The FindCUDA module has been superseded by first-class support\n#   for the CUDA language in CMake.  It is no longer necessary to\n#   use this module or call ``find_package(CUDA)``.  This module\n#   now exists only for compatibility with projects that have not\n#   been ported.\n#\n#   Instead, list ``CUDA`` among the languages named in the top-level\n#   call to the :command:`project` command, or call the\n#   :command:`enable_language` command with ``CUDA``.\n#   Then one can add CUDA (``.cu``) sources to programs directly\n#   in calls to :command:`add_library` and :command:`add_executable`.\n#\n# Tools for building CUDA C files: libraries and build dependencies.\n#\n# This script locates the NVIDIA CUDA C tools.  It should work on Linux,\n# Windows, and macOS and should be reasonably up to date with CUDA C\n# releases.\n#\n# This script makes use of the standard :command:`find_package` arguments of\n# ``<VERSION>``, ``REQUIRED`` and ``QUIET``.  ``CUDA_FOUND`` will report if an\n# acceptable version of CUDA was found.\n#\n# The script will prompt the user to specify ``CUDA_TOOLKIT_ROOT_DIR`` if\n# the prefix cannot be determined by the location of nvcc in the system\n# path and ``REQUIRED`` is specified to :command:`find_package`.  To use\n# a different installed version of the toolkit set the environment variable\n# ``CUDA_BIN_PATH`` before running cmake (e.g.\n# ``CUDA_BIN_PATH=/usr/local/cuda1.0`` instead of the default\n# ``/usr/local/cuda``) or set ``CUDA_TOOLKIT_ROOT_DIR`` after configuring.  If\n# you change the value of ``CUDA_TOOLKIT_ROOT_DIR``, various components that\n# depend on the path will be relocated.\n#\n# It might be necessary to set ``CUDA_TOOLKIT_ROOT_DIR`` manually on certain\n# platforms, or to use a CUDA runtime not installed in the default\n# location.  In newer versions of the toolkit the CUDA library is\n# included with the graphics driver -- be sure that the driver version\n# matches what is needed by the CUDA runtime version.\n#\n# The following variables affect the behavior of the macros in the\n# script (in alphebetical order).  Note that any of these flags can be\n# changed multiple times in the same directory before calling\n# ``CUDA_ADD_EXECUTABLE``, ``CUDA_ADD_LIBRARY``, ``CUDA_COMPILE``,\n# ``CUDA_COMPILE_PTX``, ``CUDA_COMPILE_FATBIN``, ``CUDA_COMPILE_CUBIN``\n# or ``CUDA_WRAP_SRCS``::\n#\n#   CUDA_64_BIT_DEVICE_CODE (Default matches host bit size)\n#   -- Set to ON to compile for 64 bit device code, OFF for 32 bit device code.\n#      Note that making this different from the host code when generating object\n#      or C files from CUDA code just won't work, because size_t gets defined by\n#      nvcc in the generated source.  If you compile to PTX and then load the\n#      file yourself, you can mix bit sizes between device and host.\n#\n#   CUDA_ATTACH_VS_BUILD_RULE_TO_CUDA_FILE (Default ON)\n#   -- Set to ON if you want the custom build rule to be attached to the source\n#      file in Visual Studio.  Turn OFF if you add the same cuda file to multiple\n#      targets.\n#\n#      This allows the user to build the target from the CUDA file; however, bad\n#      things can happen if the CUDA source file is added to multiple targets.\n#      When performing parallel builds it is possible for the custom build\n#      command to be run more than once and in parallel causing cryptic build\n#      errors.  VS runs the rules for every source file in the target, and a\n#      source can have only one rule no matter how many projects it is added to.\n#      When the rule is run from multiple targets race conditions can occur on\n#      the generated file.  Eventually everything will get built, but if the user\n#      is unaware of this behavior, there may be confusion.  It would be nice if\n#      this script could detect the reuse of source files across multiple targets\n#      and turn the option off for the user, but no good solution could be found.\n#\n#   CUDA_BUILD_CUBIN (Default OFF)\n#   -- Set to ON to enable and extra compilation pass with the -cubin option in\n#      Device mode. The output is parsed and register, shared memory usage is\n#      printed during build.\n#\n#   CUDA_BUILD_EMULATION (Default OFF for device mode)\n#   -- Set to ON for Emulation mode. -D_DEVICEEMU is defined for CUDA C files\n#      when CUDA_BUILD_EMULATION is TRUE.\n#\n#   CUDA_LINK_LIBRARIES_KEYWORD (Default \"\")\n#    -- The <PRIVATE|PUBLIC|INTERFACE> keyword to use for internal\n#       target_link_libraries calls. The default is to use no keyword which\n#       uses the old \"plain\" form of target_link_libraries. Note that is matters\n#       because whatever is used inside the FindCUDA module must also be used\n#       outside - the two forms of target_link_libraries cannot be mixed.\n#\n#   CUDA_GENERATED_OUTPUT_DIR (Default CMAKE_CURRENT_BINARY_DIR)\n#   -- Set to the path you wish to have the generated files placed.  If it is\n#      blank output files will be placed in CMAKE_CURRENT_BINARY_DIR.\n#      Intermediate files will always be placed in\n#      CMAKE_CURRENT_BINARY_DIR/CMakeFiles.\n#\n#   CUDA_HOST_COMPILATION_CPP (Default ON)\n#   -- Set to OFF for C compilation of host code.\n#\n#   CUDA_HOST_COMPILER (Default CMAKE_C_COMPILER)\n#   -- Set the host compiler to be used by nvcc.  Ignored if -ccbin or\n#      --compiler-bindir is already present in the CUDA_NVCC_FLAGS or\n#      CUDA_NVCC_FLAGS_<CONFIG> variables.  For Visual Studio targets,\n#      the host compiler is constructed with one or more visual studio macros\n#      such as $(VCInstallDir), that expands out to the path when\n#      the command is run from withing VS.\n#\n#   CUDA_NVCC_FLAGS\n#   CUDA_NVCC_FLAGS_<CONFIG>\n#   -- Additional NVCC command line arguments.  NOTE: multiple arguments must be\n#      semi-colon delimited (e.g. --compiler-options;-Wall)\n#\n#   CUDA_PROPAGATE_HOST_FLAGS (Default ON)\n#   -- Set to ON to propagate CMAKE_{C,CXX}_FLAGS and their configuration\n#      dependent counterparts (e.g. CMAKE_C_FLAGS_DEBUG) automatically to the\n#      host compiler through nvcc's -Xcompiler flag.  This helps make the\n#      generated host code match the rest of the system better.  Sometimes\n#      certain flags give nvcc problems, and this will help you turn the flag\n#      propagation off.  This does not affect the flags supplied directly to nvcc\n#      via CUDA_NVCC_FLAGS or through the OPTION flags specified through\n#      CUDA_ADD_LIBRARY, CUDA_ADD_EXECUTABLE, or CUDA_WRAP_SRCS.  Flags used for\n#      shared library compilation are not affected by this flag.\n#\n#   CUDA_SEPARABLE_COMPILATION (Default OFF)\n#   -- If set this will enable separable compilation for all CUDA runtime object\n#      files.  If used outside of CUDA_ADD_EXECUTABLE and CUDA_ADD_LIBRARY\n#      (e.g. calling CUDA_WRAP_SRCS directly),\n#      CUDA_COMPUTE_SEPARABLE_COMPILATION_OBJECT_FILE_NAME and\n#      CUDA_LINK_SEPARABLE_COMPILATION_OBJECTS should be called.\n#\n#   CUDA_SOURCE_PROPERTY_FORMAT\n#   -- If this source file property is set, it can override the format specified\n#      to CUDA_WRAP_SRCS (OBJ, PTX, CUBIN, or FATBIN).  If an input source file\n#      is not a .cu file, setting this file will cause it to be treated as a .cu\n#      file. See documentation for set_source_files_properties on how to set\n#      this property.\n#\n#   CUDA_USE_STATIC_CUDA_RUNTIME (Default ON)\n#   -- When enabled the static version of the CUDA runtime library will be used\n#      in CUDA_LIBRARIES.  If the version of CUDA configured doesn't support\n#      this option, then it will be silently disabled.\n#\n#   CUDA_VERBOSE_BUILD (Default OFF)\n#   -- Set to ON to see all the commands used when building the CUDA file.  When\n#      using a Makefile generator the value defaults to VERBOSE (run make\n#      VERBOSE=1 to see output), although setting CUDA_VERBOSE_BUILD to ON will\n#      always print the output.\n#\n# The script creates the following macros (in alphebetical order)::\n#\n#   CUDA_ADD_CUFFT_TO_TARGET( cuda_target )\n#   -- Adds the cufft library to the target (can be any target).  Handles whether\n#      you are in emulation mode or not.\n#\n#   CUDA_ADD_CUBLAS_TO_TARGET( cuda_target )\n#   -- Adds the cublas library to the target (can be any target).  Handles\n#      whether you are in emulation mode or not.\n#\n#   CUDA_ADD_EXECUTABLE( cuda_target file0 file1 ...\n#                        [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] [OPTIONS ...] )\n#   -- Creates an executable \"cuda_target\" which is made up of the files\n#      specified.  All of the non CUDA C files are compiled using the standard\n#      build rules specified by CMAKE and the cuda files are compiled to object\n#      files using nvcc and the host compiler.  In addition CUDA_INCLUDE_DIRS is\n#      added automatically to include_directories().  Some standard CMake target\n#      calls can be used on the target after calling this macro\n#      (e.g. set_target_properties and target_link_libraries), but setting\n#      properties that adjust compilation flags will not affect code compiled by\n#      nvcc.  Such flags should be modified before calling CUDA_ADD_EXECUTABLE,\n#      CUDA_ADD_LIBRARY or CUDA_WRAP_SRCS.\n#\n#   CUDA_ADD_LIBRARY( cuda_target file0 file1 ...\n#                     [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] [OPTIONS ...] )\n#   -- Same as CUDA_ADD_EXECUTABLE except that a library is created.\n#\n#   CUDA_BUILD_CLEAN_TARGET()\n#   -- Creates a convience target that deletes all the dependency files\n#      generated.  You should make clean after running this target to ensure the\n#      dependency files get regenerated.\n#\n#   CUDA_COMPILE( generated_files file0 file1 ... [STATIC | SHARED | MODULE]\n#                 [OPTIONS ...] )\n#   -- Returns a list of generated files from the input source files to be used\n#      with ADD_LIBRARY or ADD_EXECUTABLE.\n#\n#   CUDA_COMPILE_PTX( generated_files file0 file1 ... [OPTIONS ...] )\n#   -- Returns a list of PTX files generated from the input source files.\n#\n#   CUDA_COMPILE_FATBIN( generated_files file0 file1 ... [OPTIONS ...] )\n#   -- Returns a list of FATBIN files generated from the input source files.\n#\n#   CUDA_COMPILE_CUBIN( generated_files file0 file1 ... [OPTIONS ...] )\n#   -- Returns a list of CUBIN files generated from the input source files.\n#\n#   CUDA_COMPUTE_SEPARABLE_COMPILATION_OBJECT_FILE_NAME( output_file_var\n#                                                        cuda_target\n#                                                        object_files )\n#   -- Compute the name of the intermediate link file used for separable\n#      compilation.  This file name is typically passed into\n#      CUDA_LINK_SEPARABLE_COMPILATION_OBJECTS.  output_file_var is produced\n#      based on cuda_target the list of objects files that need separable\n#      compilation as specified by object_files.  If the object_files list is\n#      empty, then output_file_var will be empty.  This function is called\n#      automatically for CUDA_ADD_LIBRARY and CUDA_ADD_EXECUTABLE.  Note that\n#      this is a function and not a macro.\n#\n#   CUDA_INCLUDE_DIRECTORIES( path0 path1 ... )\n#   -- Sets the directories that should be passed to nvcc\n#      (e.g. nvcc -Ipath0 -Ipath1 ... ). These paths usually contain other .cu\n#      files.\n#\n#\n#   CUDA_LINK_SEPARABLE_COMPILATION_OBJECTS( output_file_var cuda_target\n#                                            nvcc_flags object_files)\n#   -- Generates the link object required by separable compilation from the given\n#      object files.  This is called automatically for CUDA_ADD_EXECUTABLE and\n#      CUDA_ADD_LIBRARY, but can be called manually when using CUDA_WRAP_SRCS\n#      directly.  When called from CUDA_ADD_LIBRARY or CUDA_ADD_EXECUTABLE the\n#      nvcc_flags passed in are the same as the flags passed in via the OPTIONS\n#      argument.  The only nvcc flag added automatically is the bitness flag as\n#      specified by CUDA_64_BIT_DEVICE_CODE.  Note that this is a function\n#      instead of a macro.\n#\n#   CUDA_SELECT_NVCC_ARCH_FLAGS(out_variable [target_CUDA_architectures])\n#   -- Selects GPU arch flags for nvcc based on target_CUDA_architectures\n#      target_CUDA_architectures : Auto | Common | All | LIST(ARCH_AND_PTX ...)\n#       - \"Auto\" detects local machine GPU compute arch at runtime.\n#       - \"Common\" and \"All\" cover common and entire subsets of architectures\n#      ARCH_AND_PTX : NAME | NUM.NUM | NUM.NUM(NUM.NUM) | NUM.NUM+PTX\n#      NAME: Fermi Kepler Maxwell Kepler+Tegra Kepler+Tesla Maxwell+Tegra Pascal\n#      NUM: Any number. Only those pairs are currently accepted by NVCC though:\n#            2.0 2.1 3.0 3.2 3.5 3.7 5.0 5.2 5.3 6.0 6.2\n#      Returns LIST of flags to be added to CUDA_NVCC_FLAGS in ${out_variable}\n#      Additionally, sets ${out_variable}_readable to the resulting numeric list\n#      Example:\n#       CUDA_SELECT_NVCC_ARCH_FLAGS(ARCH_FLAGS 3.0 3.5+PTX 5.2(5.0) Maxwell)\n#        LIST(APPEND CUDA_NVCC_FLAGS ${ARCH_FLAGS})\n#\n#      More info on CUDA architectures: https://en.wikipedia.org/wiki/CUDA\n#      Note that this is a function instead of a macro.\n#\n#   CUDA_WRAP_SRCS ( cuda_target format generated_files file0 file1 ...\n#                    [STATIC | SHARED | MODULE] [OPTIONS ...] )\n#   -- This is where all the magic happens.  CUDA_ADD_EXECUTABLE,\n#      CUDA_ADD_LIBRARY, CUDA_COMPILE, and CUDA_COMPILE_PTX all call this\n#      function under the hood.\n#\n#      Given the list of files (file0 file1 ... fileN) this macro generates\n#      custom commands that generate either PTX or linkable objects (use \"PTX\" or\n#      \"OBJ\" for the format argument to switch).  Files that don't end with .cu\n#      or have the HEADER_FILE_ONLY property are ignored.\n#\n#      The arguments passed in after OPTIONS are extra command line options to\n#      give to nvcc.  You can also specify per configuration options by\n#      specifying the name of the configuration followed by the options.  General\n#      options must precede configuration specific options.  Not all\n#      configurations need to be specified, only the ones provided will be used.\n#\n#         OPTIONS -DFLAG=2 \"-DFLAG_OTHER=space in flag\"\n#         DEBUG -g\n#         RELEASE --use_fast_math\n#         RELWITHDEBINFO --use_fast_math;-g\n#         MINSIZEREL --use_fast_math\n#\n#      For certain configurations (namely VS generating object files with\n#      CUDA_ATTACH_VS_BUILD_RULE_TO_CUDA_FILE set to ON), no generated file will\n#      be produced for the given cuda file.  This is because when you add the\n#      cuda file to Visual Studio it knows that this file produces an object file\n#      and will link in the resulting object file automatically.\n#\n#      This script will also generate a separate cmake script that is used at\n#      build time to invoke nvcc.  This is for several reasons.\n#\n#        1. nvcc can return negative numbers as return values which confuses\n#        Visual Studio into thinking that the command succeeded.  The script now\n#        checks the error codes and produces errors when there was a problem.\n#\n#        2. nvcc has been known to not delete incomplete results when it\n#        encounters problems.  This confuses build systems into thinking the\n#        target was generated when in fact an unusable file exists.  The script\n#        now deletes the output files if there was an error.\n#\n#        3. By putting all the options that affect the build into a file and then\n#        make the build rule dependent on the file, the output files will be\n#        regenerated when the options change.\n#\n#      This script also looks at optional arguments STATIC, SHARED, or MODULE to\n#      determine when to target the object compilation for a shared library.\n#      BUILD_SHARED_LIBS is ignored in CUDA_WRAP_SRCS, but it is respected in\n#      CUDA_ADD_LIBRARY.  On some systems special flags are added for building\n#      objects intended for shared libraries.  A preprocessor macro,\n#      <target_name>_EXPORTS is defined when a shared library compilation is\n#      detected.\n#\n#      Flags passed into add_definitions with -D or /D are passed along to nvcc.\n#\n#\n#\n# The script defines the following variables::\n#\n#   CUDA_VERSION_MAJOR    -- The major version of cuda as reported by nvcc.\n#   CUDA_VERSION_MINOR    -- The minor version.\n#   CUDA_VERSION\n#   CUDA_VERSION_STRING   -- CUDA_VERSION_MAJOR.CUDA_VERSION_MINOR\n#   CUDA_HAS_FP16         -- Whether a short float (float16,fp16) is supported.\n#\n#   CUDA_TOOLKIT_ROOT_DIR -- Path to the CUDA Toolkit (defined if not set).\n#   CUDA_SDK_ROOT_DIR     -- Path to the CUDA SDK.  Use this to find files in the\n#                            SDK.  This script will not directly support finding\n#                            specific libraries or headers, as that isn't\n#                            supported by NVIDIA.  If you want to change\n#                            libraries when the path changes see the\n#                            FindCUDA.cmake script for an example of how to clear\n#                            these variables.  There are also examples of how to\n#                            use the CUDA_SDK_ROOT_DIR to locate headers or\n#                            libraries, if you so choose (at your own risk).\n#   CUDA_INCLUDE_DIRS     -- Include directory for cuda headers.  Added automatically\n#                            for CUDA_ADD_EXECUTABLE and CUDA_ADD_LIBRARY.\n#   CUDA_LIBRARIES        -- Cuda RT library.\n#   CUDA_CUFFT_LIBRARIES  -- Device or emulation library for the Cuda FFT\n#                            implementation (alternative to:\n#                            CUDA_ADD_CUFFT_TO_TARGET macro)\n#   CUDA_CUBLAS_LIBRARIES -- Device or emulation library for the Cuda BLAS\n#                            implementation (alternative to:\n#                            CUDA_ADD_CUBLAS_TO_TARGET macro).\n#   CUDA_cudart_static_LIBRARY -- Statically linkable cuda runtime library.\n#                                 Only available for CUDA version 5.5+\n#   CUDA_cudadevrt_LIBRARY -- Device runtime library.\n#                             Required for separable compilation.\n#   CUDA_cupti_LIBRARY    -- CUDA Profiling Tools Interface library.\n#                            Only available for CUDA version 4.0+.\n#   CUDA_curand_LIBRARY   -- CUDA Random Number Generation library.\n#                            Only available for CUDA version 3.2+.\n#   CUDA_cusolver_LIBRARY -- CUDA Direct Solver library.\n#                            Only available for CUDA version 7.0+.\n#   CUDA_cusparse_LIBRARY -- CUDA Sparse Matrix library.\n#                            Only available for CUDA version 3.2+.\n#   CUDA_npp_LIBRARY      -- NVIDIA Performance Primitives lib.\n#                            Only available for CUDA version 4.0+.\n#   CUDA_nppc_LIBRARY     -- NVIDIA Performance Primitives lib (core).\n#                            Only available for CUDA version 5.5+.\n#   CUDA_nppi_LIBRARY     -- NVIDIA Performance Primitives lib (image processing).\n#                            Only available for CUDA version 5.5 - 8.0.\n#   CUDA_nppial_LIBRARY   -- NVIDIA Performance Primitives lib (image processing).\n#                            Only available for CUDA version 9.0.\n#   CUDA_nppicc_LIBRARY   -- NVIDIA Performance Primitives lib (image processing).\n#                            Only available for CUDA version 9.0.\n#   CUDA_nppicom_LIBRARY  -- NVIDIA Performance Primitives lib (image processing).\n#                            Only available for CUDA version 9.0.\n#   CUDA_nppidei_LIBRARY  -- NVIDIA Performance Primitives lib (image processing).\n#                            Only available for CUDA version 9.0.\n#   CUDA_nppif_LIBRARY    -- NVIDIA Performance Primitives lib (image processing).\n#                            Only available for CUDA version 9.0.\n#   CUDA_nppig_LIBRARY    -- NVIDIA Performance Primitives lib (image processing).\n#                            Only available for CUDA version 9.0.\n#   CUDA_nppim_LIBRARY    -- NVIDIA Performance Primitives lib (image processing).\n#                            Only available for CUDA version 9.0.\n#   CUDA_nppist_LIBRARY   -- NVIDIA Performance Primitives lib (image processing).\n#                            Only available for CUDA version 9.0.\n#   CUDA_nppisu_LIBRARY   -- NVIDIA Performance Primitives lib (image processing).\n#                            Only available for CUDA version 9.0.\n#   CUDA_nppitc_LIBRARY   -- NVIDIA Performance Primitives lib (image processing).\n#                            Only available for CUDA version 9.0.\n#   CUDA_npps_LIBRARY     -- NVIDIA Performance Primitives lib (signal processing).\n#                            Only available for CUDA version 5.5+.\n#   CUDA_nvcuvenc_LIBRARY -- CUDA Video Encoder library.\n#                            Only available for CUDA version 3.2+.\n#                            Windows only.\n#   CUDA_nvcuvid_LIBRARY  -- CUDA Video Decoder library.\n#                            Only available for CUDA version 3.2+.\n#                            Windows only.\n#\n\n#   James Bigler, NVIDIA Corp (nvidia.com - jbigler)\n#   Abe Stephens, SCI Institute -- http://www.sci.utah.edu/~abe/FindCuda.html\n#\n#   Copyright (c) 2008 - 2009 NVIDIA Corporation.  All rights reserved.\n#\n#   Copyright (c) 2007-2009\n#   Scientific Computing and Imaging Institute, University of Utah\n#\n#   This code is licensed under the MIT License.  See the FindCUDA.cmake script\n#   for the text of the license.\n\n# The MIT License\n#\n# License for the specific language governing rights and limitations under\n# Permission is hereby granted, free of charge, to any person obtaining a\n# copy of this software and associated documentation files (the \"Software\"),\n# to deal in the Software without restriction, including without limitation\n# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n# and/or sell copies of the Software, and to permit persons to whom the\n# Software is furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included\n# in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n# DEALINGS IN THE SOFTWARE.\n#\n###############################################################################\n\n# FindCUDA.cmake\n\n# This macro helps us find the location of helper files we will need the full path to\nmacro(CUDA_FIND_HELPER_FILE _name _extension)\n  set(_full_name \"${_name}.${_extension}\")\n  # CMAKE_CURRENT_LIST_FILE contains the full path to the file currently being\n  # processed.  Using this variable, we can pull out the current path, and\n  # provide a way to get access to the other files we need local to here.\n  get_filename_component(CMAKE_CURRENT_LIST_DIR \"${CMAKE_CURRENT_LIST_FILE}\" PATH)\n  set(CUDA_${_name} \"${CMAKE_CURRENT_LIST_DIR}/FindCUDA/${_full_name}\")\n  if(NOT EXISTS \"${CUDA_${_name}}\")\n    set(error_message \"${_full_name} not found in ${CMAKE_CURRENT_LIST_DIR}/FindCUDA\")\n    if(CUDA_FIND_REQUIRED)\n      message(FATAL_ERROR \"${error_message}\")\n    else()\n      if(NOT CUDA_FIND_QUIETLY)\n        message(STATUS \"${error_message}\")\n      endif()\n    endif()\n  endif()\n  # Set this variable as internal, so the user isn't bugged with it.\n  set(CUDA_${_name} ${CUDA_${_name}} CACHE INTERNAL \"Location of ${_full_name}\" FORCE)\nendmacro()\n\n#####################################################################\n## CUDA_INCLUDE_NVCC_DEPENDENCIES\n##\n\n# So we want to try and include the dependency file if it exists.  If\n# it doesn't exist then we need to create an empty one, so we can\n# include it.\n\n# If it does exist, then we need to check to see if all the files it\n# depends on exist.  If they don't then we should clear the dependency\n# file and regenerate it later.  This covers the case where a header\n# file has disappeared or moved.\n\nmacro(CUDA_INCLUDE_NVCC_DEPENDENCIES dependency_file)\n  set(CUDA_NVCC_DEPEND)\n  set(CUDA_NVCC_DEPEND_REGENERATE FALSE)\n\n\n  # Include the dependency file.  Create it first if it doesn't exist .  The\n  # INCLUDE puts a dependency that will force CMake to rerun and bring in the\n  # new info when it changes.  DO NOT REMOVE THIS (as I did and spent a few\n  # hours figuring out why it didn't work.\n  if(NOT EXISTS ${dependency_file})\n    file(WRITE ${dependency_file} \"#FindCUDA.cmake generated file.  Do not edit.\\n\")\n  endif()\n  # Always include this file to force CMake to run again next\n  # invocation and rebuild the dependencies.\n  #message(\"including dependency_file = ${dependency_file}\")\n  include(${dependency_file})\n\n  # Now we need to verify the existence of all the included files\n  # here.  If they aren't there we need to just blank this variable and\n  # make the file regenerate again.\n#   if(DEFINED CUDA_NVCC_DEPEND)\n#     message(\"CUDA_NVCC_DEPEND set\")\n#   else()\n#     message(\"CUDA_NVCC_DEPEND NOT set\")\n#   endif()\n  if(CUDA_NVCC_DEPEND)\n    #message(\"CUDA_NVCC_DEPEND found\")\n    foreach(f ${CUDA_NVCC_DEPEND})\n      # message(\"searching for ${f}\")\n      if(NOT EXISTS ${f})\n        #message(\"file ${f} not found\")\n        set(CUDA_NVCC_DEPEND_REGENERATE TRUE)\n      endif()\n    endforeach()\n  else()\n    #message(\"CUDA_NVCC_DEPEND false\")\n    # No dependencies, so regenerate the file.\n    set(CUDA_NVCC_DEPEND_REGENERATE TRUE)\n  endif()\n\n  #message(\"CUDA_NVCC_DEPEND_REGENERATE = ${CUDA_NVCC_DEPEND_REGENERATE}\")\n  # No incoming dependencies, so we need to generate them.  Make the\n  # output depend on the dependency file itself, which should cause the\n  # rule to re-run.\n  if(CUDA_NVCC_DEPEND_REGENERATE)\n    set(CUDA_NVCC_DEPEND ${dependency_file})\n    #message(\"Generating an empty dependency_file: ${dependency_file}\")\n    file(WRITE ${dependency_file} \"#FindCUDA.cmake generated file.  Do not edit.\\n\")\n  endif()\n\nendmacro()\n\n###############################################################################\n###############################################################################\n# Setup variables' defaults\n###############################################################################\n###############################################################################\n\n# Allow the user to specify if the device code is supposed to be 32 or 64 bit.\nif(CMAKE_SIZEOF_VOID_P EQUAL 8)\n  set(CUDA_64_BIT_DEVICE_CODE_DEFAULT ON)\nelse()\n  set(CUDA_64_BIT_DEVICE_CODE_DEFAULT OFF)\nendif()\noption(CUDA_64_BIT_DEVICE_CODE \"Compile device code in 64 bit mode\" ${CUDA_64_BIT_DEVICE_CODE_DEFAULT})\n\n# Attach the build rule to the source file in VS.  This option\noption(CUDA_ATTACH_VS_BUILD_RULE_TO_CUDA_FILE \"Attach the build rule to the CUDA source file.  Enable only when the CUDA source file is added to at most one target.\" ON)\n\n# Prints out extra information about the cuda file during compilation\noption(CUDA_BUILD_CUBIN \"Generate and parse .cubin files in Device mode.\" OFF)\n\n# Set whether we are using emulation or device mode.\noption(CUDA_BUILD_EMULATION \"Build in Emulation mode\" OFF)\n\n# Where to put the generated output.\nset(CUDA_GENERATED_OUTPUT_DIR \"\" CACHE PATH \"Directory to put all the output files.  If blank it will default to the CMAKE_CURRENT_BINARY_DIR\")\n\n# Parse HOST_COMPILATION mode.\noption(CUDA_HOST_COMPILATION_CPP \"Generated file extension\" ON)\n\n# Extra user settable flags\nset(CUDA_NVCC_FLAGS \"\" CACHE STRING \"Semi-colon delimit multiple arguments.\")\n\nif(CMAKE_GENERATOR MATCHES \"Visual Studio\")\n  set(_CUDA_MSVC_HOST_COMPILER \"$(VCInstallDir)Tools/MSVC/$(VCToolsVersion)/bin/Host$(Platform)/$(PlatformTarget)\")\n  if(MSVC_VERSION LESS 1910)\n   set(_CUDA_MSVC_HOST_COMPILER \"$(VCInstallDir)bin\")\n  endif()\n\n  set(CUDA_HOST_COMPILER \"${_CUDA_MSVC_HOST_COMPILER}\" CACHE FILEPATH \"Host side compiler used by NVCC\")\n\nelse()\n  if(APPLE\n      AND \"${CMAKE_C_COMPILER_ID}\" MATCHES \"Clang\"\n      AND \"${CMAKE_C_COMPILER}\" MATCHES \"/cc$\")\n    # Using cc which is symlink to clang may let NVCC think it is GCC and issue\n    # unhandled -dumpspecs option to clang. Also in case neither\n    # CMAKE_C_COMPILER is defined (project does not use C language) nor\n    # CUDA_HOST_COMPILER is specified manually we should skip -ccbin and let\n    # nvcc use its own default C compiler.\n    # Only care about this on APPLE with clang to avoid\n    # following symlinks to things like ccache\n    if(DEFINED CMAKE_C_COMPILER AND NOT DEFINED CUDA_HOST_COMPILER)\n      get_filename_component(c_compiler_realpath \"${CMAKE_C_COMPILER}\" REALPATH)\n      # if the real path does not end up being clang then\n      # go back to using CMAKE_C_COMPILER\n      if(NOT \"${c_compiler_realpath}\" MATCHES \"/clang$\")\n        set(c_compiler_realpath \"${CMAKE_C_COMPILER}\")\n      endif()\n    else()\n      set(c_compiler_realpath \"\")\n    endif()\n    set(CUDA_HOST_COMPILER \"${c_compiler_realpath}\" CACHE FILEPATH \"Host side compiler used by NVCC\")\n  else()\n    set(CUDA_HOST_COMPILER \"${CMAKE_C_COMPILER}\"\n      CACHE FILEPATH \"Host side compiler used by NVCC\")\n  endif()\nendif()\n\n# Propagate the host flags to the host compiler via -Xcompiler\noption(CUDA_PROPAGATE_HOST_FLAGS \"Propage C/CXX_FLAGS and friends to the host compiler via -Xcompile\" ON)\n\n# Enable CUDA_SEPARABLE_COMPILATION\noption(CUDA_SEPARABLE_COMPILATION \"Compile CUDA objects with separable compilation enabled.  Requires CUDA 5.0+\" OFF)\n\n# Specifies whether the commands used when compiling the .cu file will be printed out.\noption(CUDA_VERBOSE_BUILD \"Print out the commands run while compiling the CUDA source file.  With the Makefile generator this defaults to VERBOSE variable specified on the command line, but can be forced on with this option.\" OFF)\n\nmark_as_advanced(\n  CUDA_64_BIT_DEVICE_CODE\n  CUDA_ATTACH_VS_BUILD_RULE_TO_CUDA_FILE\n  CUDA_GENERATED_OUTPUT_DIR\n  CUDA_HOST_COMPILATION_CPP\n  CUDA_NVCC_FLAGS\n  CUDA_PROPAGATE_HOST_FLAGS\n  CUDA_BUILD_CUBIN\n  CUDA_BUILD_EMULATION\n  CUDA_VERBOSE_BUILD\n  CUDA_SEPARABLE_COMPILATION\n  )\n\n# Single config generators like Makefiles or Ninja don't usually have\n# CMAKE_CONFIGURATION_TYPES defined (but note that it can be defined if set by\n# projects or developers). Even CMAKE_BUILD_TYPE might not be defined for\n# single config generators (and should not be defined for multi-config\n# generators). To ensure we get a complete superset of all possible\n# configurations, we combine CMAKE_CONFIGURATION_TYPES, CMAKE_BUILD_TYPE and\n# all of the standard configurations, then weed out duplicates with\n# list(REMOVE_DUPLICATES). Looping over the unique set then ensures we have\n# each configuration-specific set of nvcc flags defined and marked as advanced.\nset(CUDA_configuration_types ${CMAKE_CONFIGURATION_TYPES} ${CMAKE_BUILD_TYPE} Debug MinSizeRel Release RelWithDebInfo)\nlist(REMOVE_DUPLICATES CUDA_configuration_types)\nforeach(config ${CUDA_configuration_types})\n    string(TOUPPER ${config} config_upper)\n    set(CUDA_NVCC_FLAGS_${config_upper} \"\" CACHE STRING \"Semi-colon delimit multiple arguments.\")\n    mark_as_advanced(CUDA_NVCC_FLAGS_${config_upper})\nendforeach()\n\n###############################################################################\n###############################################################################\n# Locate CUDA, Set Build Type, etc.\n###############################################################################\n###############################################################################\n\nmacro(cuda_unset_include_and_libraries)\n  unset(CUDA_TOOLKIT_INCLUDE CACHE)\n  unset(CUDA_CUDART_LIBRARY CACHE)\n  unset(CUDA_CUDA_LIBRARY CACHE)\n  # Make sure you run this before you unset CUDA_VERSION.\n  if(CUDA_VERSION VERSION_EQUAL \"3.0\")\n    # This only existed in the 3.0 version of the CUDA toolkit\n    unset(CUDA_CUDARTEMU_LIBRARY CACHE)\n  endif()\n  unset(CUDA_cudart_static_LIBRARY CACHE)\n  unset(CUDA_cudadevrt_LIBRARY CACHE)\n  unset(CUDA_cublas_LIBRARY CACHE)\n  unset(CUDA_cublas_device_LIBRARY CACHE)\n  unset(CUDA_cublasemu_LIBRARY CACHE)\n  unset(CUDA_cufft_LIBRARY CACHE)\n  unset(CUDA_cufftemu_LIBRARY CACHE)\n  unset(CUDA_cupti_LIBRARY CACHE)\n  unset(CUDA_curand_LIBRARY CACHE)\n  unset(CUDA_cusolver_LIBRARY CACHE)\n  unset(CUDA_cusparse_LIBRARY CACHE)\n  unset(CUDA_npp_LIBRARY CACHE)\n  unset(CUDA_nppc_LIBRARY CACHE)\n  unset(CUDA_nppi_LIBRARY CACHE)\n  unset(CUDA_npps_LIBRARY CACHE)\n  unset(CUDA_nvcuvenc_LIBRARY CACHE)\n  unset(CUDA_nvcuvid_LIBRARY CACHE)\n  unset(CUDA_GPU_DETECT_OUTPUT CACHE)\nendmacro()\n\n# Check to see if the CUDA_TOOLKIT_ROOT_DIR and CUDA_SDK_ROOT_DIR have changed,\n# if they have then clear the cache variables, so that will be detected again.\nif(NOT \"${CUDA_TOOLKIT_ROOT_DIR}\" STREQUAL \"${CUDA_TOOLKIT_ROOT_DIR_INTERNAL}\")\n  unset(CUDA_TOOLKIT_TARGET_DIR CACHE)\n  unset(CUDA_NVCC_EXECUTABLE CACHE)\n  cuda_unset_include_and_libraries()\n  unset(CUDA_VERSION CACHE)\nendif()\n\nif(NOT \"${CUDA_TOOLKIT_TARGET_DIR}\" STREQUAL \"${CUDA_TOOLKIT_TARGET_DIR_INTERNAL}\")\n  cuda_unset_include_and_libraries()\nendif()\n\n#\n#  End of unset()\n#\n\n#\n#  Start looking for things\n#\n\n# Search for the cuda distribution.\nif(NOT CUDA_TOOLKIT_ROOT_DIR AND NOT CMAKE_CROSSCOMPILING)\n  # Search in the CUDA_BIN_PATH first.\n  find_path(CUDA_TOOLKIT_ROOT_DIR\n    NAMES nvcc nvcc.exe\n    PATHS\n      ENV CUDA_TOOLKIT_ROOT\n      ENV CUDA_PATH\n      ENV CUDA_BIN_PATH\n    PATH_SUFFIXES bin bin64\n    DOC \"Toolkit location.\"\n    NO_DEFAULT_PATH\n    )\n\n  # Now search default paths\n  find_path(CUDA_TOOLKIT_ROOT_DIR\n    NAMES nvcc nvcc.exe\n    PATHS /opt/cuda/bin\n    PATH_SUFFIXES cuda/bin\n    DOC \"Toolkit location.\"\n    )\n\n  if (CUDA_TOOLKIT_ROOT_DIR)\n    string(REGEX REPLACE \"[/\\\\\\\\]?bin[64]*[/\\\\\\\\]?$\" \"\" CUDA_TOOLKIT_ROOT_DIR ${CUDA_TOOLKIT_ROOT_DIR})\n    # We need to force this back into the cache.\n    set(CUDA_TOOLKIT_ROOT_DIR ${CUDA_TOOLKIT_ROOT_DIR} CACHE PATH \"Toolkit location.\" FORCE)\n    set(CUDA_TOOLKIT_TARGET_DIR ${CUDA_TOOLKIT_ROOT_DIR})\n  endif()\n\n  if (NOT EXISTS ${CUDA_TOOLKIT_ROOT_DIR})\n    if(CUDA_FIND_REQUIRED)\n      message(FATAL_ERROR \"Specify CUDA_TOOLKIT_ROOT_DIR\")\n    elseif(NOT CUDA_FIND_QUIETLY)\n      message(\"CUDA_TOOLKIT_ROOT_DIR not found or specified\")\n    endif()\n  endif ()\nendif ()\n\nif(CMAKE_CROSSCOMPILING)\n  SET (CUDA_TOOLKIT_ROOT $ENV{CUDA_TOOLKIT_ROOT})\n  if(CMAKE_SYSTEM_PROCESSOR STREQUAL \"armv7-a\")\n    # Support for NVPACK\n    set (CUDA_TOOLKIT_TARGET_NAME \"armv7-linux-androideabi\")\n  elseif(CMAKE_SYSTEM_PROCESSOR MATCHES \"arm\")\n    # Support for arm cross compilation\n    set(CUDA_TOOLKIT_TARGET_NAME \"armv7-linux-gnueabihf\")\n  elseif(CMAKE_SYSTEM_PROCESSOR MATCHES \"aarch64\")\n    # Support for aarch64 cross compilation\n    if (ANDROID_ARCH_NAME STREQUAL \"arm64\")\n      set(CUDA_TOOLKIT_TARGET_NAME \"aarch64-linux-androideabi\")\n    else()\n      set(CUDA_TOOLKIT_TARGET_NAME \"aarch64-linux\")\n    endif (ANDROID_ARCH_NAME STREQUAL \"arm64\")\n  endif()\n\n  if (EXISTS \"${CUDA_TOOLKIT_ROOT}/targets/${CUDA_TOOLKIT_TARGET_NAME}\")\n    set(CUDA_TOOLKIT_TARGET_DIR \"${CUDA_TOOLKIT_ROOT}/targets/${CUDA_TOOLKIT_TARGET_NAME}\" CACHE PATH \"CUDA Toolkit target location.\")\n    SET (CUDA_TOOLKIT_ROOT_DIR ${CUDA_TOOLKIT_ROOT})\n    mark_as_advanced(CUDA_TOOLKIT_TARGET_DIR)\n  endif()\n\n  # add known CUDA targetr root path to the set of directories we search for programs, libraries and headers\n  set( CMAKE_FIND_ROOT_PATH \"${CUDA_TOOLKIT_TARGET_DIR};${CMAKE_FIND_ROOT_PATH}\")\n  macro( cuda_find_host_program )\n    if (COMMAND find_host_program)\n      find_host_program( ${ARGN} )\n    else()\n      find_program( ${ARGN} )\n    endif()\n  endmacro()\nelse()\n  # for non-cross-compile, find_host_program == find_program and CUDA_TOOLKIT_TARGET_DIR == CUDA_TOOLKIT_ROOT_DIR\n  macro( cuda_find_host_program )\n    find_program( ${ARGN} )\n  endmacro()\n  SET (CUDA_TOOLKIT_TARGET_DIR ${CUDA_TOOLKIT_ROOT_DIR})\nendif()\n\n\n# CUDA_NVCC_EXECUTABLE\ncuda_find_host_program(CUDA_NVCC_EXECUTABLE\n  NAMES nvcc\n  PATHS \"${CUDA_TOOLKIT_ROOT_DIR}\"\n  ENV CUDA_PATH\n  ENV CUDA_BIN_PATH\n  PATH_SUFFIXES bin bin64\n  NO_DEFAULT_PATH\n  )\n# Search default search paths, after we search our own set of paths.\ncuda_find_host_program(CUDA_NVCC_EXECUTABLE nvcc)\nmark_as_advanced(CUDA_NVCC_EXECUTABLE)\n\nif(CUDA_NVCC_EXECUTABLE AND NOT CUDA_VERSION)\n  # Compute the version.\n  execute_process (COMMAND ${CUDA_NVCC_EXECUTABLE} \"--version\" OUTPUT_VARIABLE NVCC_OUT)\n  string(REGEX REPLACE \".*release ([0-9]+)\\\\.([0-9]+).*\" \"\\\\1\" CUDA_VERSION_MAJOR ${NVCC_OUT})\n  string(REGEX REPLACE \".*release ([0-9]+)\\\\.([0-9]+).*\" \"\\\\2\" CUDA_VERSION_MINOR ${NVCC_OUT})\n  set(CUDA_VERSION \"${CUDA_VERSION_MAJOR}.${CUDA_VERSION_MINOR}\" CACHE STRING \"Version of CUDA as computed from nvcc.\")\n  mark_as_advanced(CUDA_VERSION)\nelse()\n  # Need to set these based off of the cached value\n  string(REGEX REPLACE \"([0-9]+)\\\\.([0-9]+).*\" \"\\\\1\" CUDA_VERSION_MAJOR \"${CUDA_VERSION}\")\n  string(REGEX REPLACE \"([0-9]+)\\\\.([0-9]+).*\" \"\\\\2\" CUDA_VERSION_MINOR \"${CUDA_VERSION}\")\nendif()\n\n\n# Always set this convenience variable\nset(CUDA_VERSION_STRING \"${CUDA_VERSION}\")\n\n# CUDA_TOOLKIT_INCLUDE\nfind_path(CUDA_TOOLKIT_INCLUDE\n  device_functions.h # Header included in toolkit\n  PATHS ${CUDA_TOOLKIT_TARGET_DIR}\n  ENV CUDA_PATH\n  ENV CUDA_INC_PATH\n  PATH_SUFFIXES include\n  NO_DEFAULT_PATH\n  )\n# Search default search paths, after we search our own set of paths.\nfind_path(CUDA_TOOLKIT_INCLUDE device_functions.h)\nmark_as_advanced(CUDA_TOOLKIT_INCLUDE)\n\nif (CUDA_VERSION VERSION_GREATER \"7.0\" OR EXISTS \"${CUDA_TOOLKIT_INCLUDE}/cuda_fp16.h\")\n  set(CUDA_HAS_FP16 TRUE)\nelse()\n  set(CUDA_HAS_FP16 FALSE)\nendif()\n\n# Set the user list of include dir to nothing to initialize it.\nset (CUDA_NVCC_INCLUDE_DIRS_USER \"\")\nset (CUDA_INCLUDE_DIRS ${CUDA_TOOLKIT_INCLUDE})\n\nmacro(cuda_find_library_local_first_with_path_ext _var _names _doc _path_ext )\n  if(CMAKE_SIZEOF_VOID_P EQUAL 8)\n    # CUDA 3.2+ on Windows moved the library directories, so we need the new\n    # and old paths.\n    set(_cuda_64bit_lib_dir \"${_path_ext}lib/x64\" \"${_path_ext}lib64\" \"${_path_ext}libx64\" )\n  endif()\n  # CUDA 3.2+ on Windows moved the library directories, so we need to new\n  # (lib/Win32) and the old path (lib).\n  find_library(${_var}\n    NAMES ${_names}\n    PATHS \"${CUDA_TOOLKIT_TARGET_DIR}\"\n    ENV CUDA_PATH\n    ENV CUDA_LIB_PATH\n    PATH_SUFFIXES ${_cuda_64bit_lib_dir} \"${_path_ext}lib/Win32\" \"${_path_ext}lib\" \"${_path_ext}libWin32\"\n    DOC ${_doc}\n    NO_DEFAULT_PATH\n    )\n  if (NOT CMAKE_CROSSCOMPILING)\n    # Search default search paths, after we search our own set of paths.\n    find_library(${_var}\n      NAMES ${_names}\n      PATHS \"/usr/lib/nvidia-current\"\n      DOC ${_doc}\n      )\n  endif()\nendmacro()\n\nmacro(cuda_find_library_local_first _var _names _doc)\n  cuda_find_library_local_first_with_path_ext( \"${_var}\" \"${_names}\" \"${_doc}\" \"\" )\nendmacro()\n\nmacro(find_library_local_first _var _names _doc )\n  cuda_find_library_local_first( \"${_var}\" \"${_names}\" \"${_doc}\" \"\" )\nendmacro()\n\n\n# CUDA_LIBRARIES\ncuda_find_library_local_first(CUDA_CUDART_LIBRARY cudart \"\\\"cudart\\\" library\")\nif(CUDA_VERSION VERSION_EQUAL \"3.0\")\n  # The cudartemu library only existed for the 3.0 version of CUDA.\n  cuda_find_library_local_first(CUDA_CUDARTEMU_LIBRARY cudartemu \"\\\"cudartemu\\\" library\")\n  mark_as_advanced(\n    CUDA_CUDARTEMU_LIBRARY\n    )\nendif()\n\nif(NOT CUDA_VERSION VERSION_LESS \"5.5\")\n  cuda_find_library_local_first(CUDA_cudart_static_LIBRARY cudart_static \"static CUDA runtime library\")\n  mark_as_advanced(CUDA_cudart_static_LIBRARY)\nendif()\n\n\nif(CUDA_cudart_static_LIBRARY)\n  # If static cudart available, use it by default, but provide a user-visible option to disable it.\n  option(CUDA_USE_STATIC_CUDA_RUNTIME \"Use the static version of the CUDA runtime library if available\" ON)\nelse()\n  # If not available, silently disable the option.\n  set(CUDA_USE_STATIC_CUDA_RUNTIME OFF CACHE INTERNAL \"\")\nendif()\n\nif(CUDA_USE_STATIC_CUDA_RUNTIME)\n  set(CUDA_CUDART_LIBRARY_VAR CUDA_cudart_static_LIBRARY)\nelse()\n  set(CUDA_CUDART_LIBRARY_VAR CUDA_CUDART_LIBRARY)\nendif()\n\nif(NOT CUDA_VERSION VERSION_LESS \"5.0\")\n  cuda_find_library_local_first(CUDA_cudadevrt_LIBRARY cudadevrt \"\\\"cudadevrt\\\" library\")\n  mark_as_advanced(CUDA_cudadevrt_LIBRARY)\nendif()\n\nif(CUDA_USE_STATIC_CUDA_RUNTIME)\n  if(UNIX)\n    # Check for the dependent libraries.  Here we look for pthreads.\n    if (DEFINED CMAKE_THREAD_PREFER_PTHREAD)\n      set(_cuda_cmake_thread_prefer_pthread ${CMAKE_THREAD_PREFER_PTHREAD})\n    endif()\n    set(CMAKE_THREAD_PREFER_PTHREAD 1)\n\n    # Many of the FindXYZ CMake comes with makes use of try_compile with int main(){return 0;}\n    # as the source file.  Unfortunately this causes a warning with -Wstrict-prototypes and\n    # -Werror causes the try_compile to fail.  We will just temporarily disable other flags\n    # when doing the find_package command here.\n    set(_cuda_cmake_c_flags ${CMAKE_C_FLAGS})\n    set(CMAKE_C_FLAGS \"-fPIC\")\n    find_package(Threads REQUIRED)\n    set(CMAKE_C_FLAGS ${_cuda_cmake_c_flags})\n\n    if (DEFINED _cuda_cmake_thread_prefer_pthread)\n      set(CMAKE_THREAD_PREFER_PTHREAD ${_cuda_cmake_thread_prefer_pthread})\n      unset(_cuda_cmake_thread_prefer_pthread)\n    else()\n      unset(CMAKE_THREAD_PREFER_PTHREAD)\n    endif()\n\n    if(NOT APPLE)\n      #On Linux, you must link against librt when using the static cuda runtime.\n      find_library(CUDA_rt_LIBRARY rt)\n      if (NOT CUDA_rt_LIBRARY)\n        message(WARNING \"Expecting to find librt for libcudart_static, but didn't find it.\")\n      endif()\n    endif()\n  endif()\nendif()\n\n# CUPTI library showed up in cuda toolkit 4.0\nif(NOT CUDA_VERSION VERSION_LESS \"4.0\")\n  cuda_find_library_local_first_with_path_ext(CUDA_cupti_LIBRARY cupti \"\\\"cupti\\\" library\" \"extras/CUPTI/\")\n  mark_as_advanced(CUDA_cupti_LIBRARY)\nendif()\n\n# Set the CUDA_LIBRARIES variable.  This is the set of stuff to link against if you are\n# using the CUDA runtime.  For the dynamic version of the runtime, most of the\n# dependencies are brough in, but for the static version there are additional libraries\n# and linker commands needed.\n# Initialize to empty\nset(CUDA_LIBRARIES)\n\n# If we are using emulation mode and we found the cudartemu library then use\n# that one instead of cudart.\nif(CUDA_BUILD_EMULATION AND CUDA_CUDARTEMU_LIBRARY)\n  list(APPEND CUDA_LIBRARIES ${CUDA_CUDARTEMU_LIBRARY})\nelseif(CUDA_USE_STATIC_CUDA_RUNTIME AND CUDA_cudart_static_LIBRARY)\n  list(APPEND CUDA_LIBRARIES ${CUDA_cudart_static_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS})\n  if (CUDA_rt_LIBRARY)\n    list(APPEND CUDA_LIBRARIES ${CUDA_rt_LIBRARY})\n  endif()\n  if(APPLE)\n    # We need to add the default path to the driver (libcuda.dylib) as an rpath, so that\n    # the static cuda runtime can find it at runtime.\n    list(APPEND CUDA_LIBRARIES -Wl,-rpath,/usr/local/cuda/lib)\n  endif()\nelse()\n  list(APPEND CUDA_LIBRARIES ${CUDA_CUDART_LIBRARY})\nendif()\n\n# 1.1 toolkit on linux doesn't appear to have a separate library on\n# some platforms.\ncuda_find_library_local_first(CUDA_CUDA_LIBRARY cuda \"\\\"cuda\\\" library (older versions only).\")\n\nmark_as_advanced(\n  CUDA_CUDA_LIBRARY\n  CUDA_CUDART_LIBRARY\n  )\n\n#######################\n# Look for some of the toolkit helper libraries\nmacro(FIND_CUDA_HELPER_LIBS _name)\n  cuda_find_library_local_first(CUDA_${_name}_LIBRARY ${_name} \"\\\"${_name}\\\" library\")\n  mark_as_advanced(CUDA_${_name}_LIBRARY)\nendmacro()\n\n#######################\n# Disable emulation for v3.1 onward\nif(CUDA_VERSION VERSION_GREATER \"3.0\")\n  if(CUDA_BUILD_EMULATION)\n    message(FATAL_ERROR \"CUDA_BUILD_EMULATION is not supported in version 3.1 and onwards.  You must disable it to proceed.  You have version ${CUDA_VERSION}.\")\n  endif()\nendif()\n\n# Search for additional CUDA toolkit libraries.\nif(CUDA_VERSION VERSION_LESS \"3.1\")\n  # Emulation libraries aren't available in version 3.1 onward.\n  find_cuda_helper_libs(cufftemu)\n  find_cuda_helper_libs(cublasemu)\nendif()\nfind_cuda_helper_libs(cufft)\nfind_cuda_helper_libs(cublas)\nif(NOT CUDA_VERSION VERSION_LESS \"3.2\")\n  # cusparse showed up in version 3.2\n  find_cuda_helper_libs(cusparse)\n  find_cuda_helper_libs(curand)\n  if (WIN32)\n    find_cuda_helper_libs(nvcuvenc)\n    find_cuda_helper_libs(nvcuvid)\n  endif()\nendif()\nif(CUDA_VERSION VERSION_GREATER \"5.0\")\n  find_cuda_helper_libs(cublas_device)\nendif()\n\nif(NOT CUDA_VERSION VERSION_LESS \"9.0\")\n  # In CUDA 9.0 NPP was nppi was removed\n  find_cuda_helper_libs(nppc)\n  find_cuda_helper_libs(nppial)\n  find_cuda_helper_libs(nppicc)\n  find_cuda_helper_libs(nppicom)\n  find_cuda_helper_libs(nppidei)\n  find_cuda_helper_libs(nppif)\n  find_cuda_helper_libs(nppig)\n  find_cuda_helper_libs(nppim)\n  find_cuda_helper_libs(nppist)\n  find_cuda_helper_libs(nppisu)\n  find_cuda_helper_libs(nppitc)\n  find_cuda_helper_libs(npps)\n  set(CUDA_npp_LIBRARY \"${CUDA_nppc_LIBRARY};${CUDA_nppial_LIBRARY};${CUDA_nppicc_LIBRARY};${CUDA_nppicom_LIBRARY};${CUDA_nppidei_LIBRARY};${CUDA_nppif_LIBRARY};${CUDA_nppig_LIBRARY};${CUDA_nppim_LIBRARY};${CUDA_nppist_LIBRARY};${CUDA_nppisu_LIBRARY};${CUDA_nppitc_LIBRARY};${CUDA_npps_LIBRARY}\")\nelseif(CUDA_VERSION VERSION_GREATER \"5.0\")\n  # In CUDA 5.5 NPP was split into 3 separate libraries.\n  find_cuda_helper_libs(nppc)\n  find_cuda_helper_libs(nppi)\n  find_cuda_helper_libs(npps)\n  set(CUDA_npp_LIBRARY \"${CUDA_nppc_LIBRARY};${CUDA_nppi_LIBRARY};${CUDA_npps_LIBRARY}\")\nelseif(NOT CUDA_VERSION VERSION_LESS \"4.0\")\n  find_cuda_helper_libs(npp)\nendif()\nif(NOT CUDA_VERSION VERSION_LESS \"7.0\")\n  # cusolver showed up in version 7.0\n  find_cuda_helper_libs(cusolver)\nendif()\n\nif (CUDA_BUILD_EMULATION)\n  set(CUDA_CUFFT_LIBRARIES ${CUDA_cufftemu_LIBRARY})\n  set(CUDA_CUBLAS_LIBRARIES ${CUDA_cublasemu_LIBRARY})\nelse()\n  set(CUDA_CUFFT_LIBRARIES ${CUDA_cufft_LIBRARY})\n  set(CUDA_CUBLAS_LIBRARIES ${CUDA_cublas_LIBRARY} ${CUDA_cublas_device_LIBRARY})\nendif()\n\n########################\n# Look for the SDK stuff.  As of CUDA 3.0 NVSDKCUDA_ROOT has been replaced with\n# NVSDKCOMPUTE_ROOT with the old CUDA C contents moved into the C subdirectory\nfind_path(CUDA_SDK_ROOT_DIR common/inc/cutil.h\n HINTS\n  \"$ENV{NVSDKCOMPUTE_ROOT}/C\"\n  ENV NVSDKCUDA_ROOT\n  \"[HKEY_LOCAL_MACHINE\\\\SOFTWARE\\\\NVIDIA Corporation\\\\Installed Products\\\\NVIDIA SDK 10\\\\Compute;InstallDir]\"\n PATHS\n  \"/Developer/GPU\\ Computing/C\"\n  )\n\n# Keep the CUDA_SDK_ROOT_DIR first in order to be able to override the\n# environment variables.\nset(CUDA_SDK_SEARCH_PATH\n  \"${CUDA_SDK_ROOT_DIR}\"\n  \"${CUDA_TOOLKIT_ROOT_DIR}/local/NVSDK0.2\"\n  \"${CUDA_TOOLKIT_ROOT_DIR}/NVSDK0.2\"\n  \"${CUDA_TOOLKIT_ROOT_DIR}/NV_CUDA_SDK\"\n  \"$ENV{HOME}/NVIDIA_CUDA_SDK\"\n  \"$ENV{HOME}/NVIDIA_CUDA_SDK_MACOSX\"\n  \"/Developer/CUDA\"\n  )\n\n# Example of how to find an include file from the CUDA_SDK_ROOT_DIR\n\n# find_path(CUDA_CUT_INCLUDE_DIR\n#   cutil.h\n#   PATHS ${CUDA_SDK_SEARCH_PATH}\n#   PATH_SUFFIXES \"common/inc\"\n#   DOC \"Location of cutil.h\"\n#   NO_DEFAULT_PATH\n#   )\n# # Now search system paths\n# find_path(CUDA_CUT_INCLUDE_DIR cutil.h DOC \"Location of cutil.h\")\n\n# mark_as_advanced(CUDA_CUT_INCLUDE_DIR)\n\n\n# Example of how to find a library in the CUDA_SDK_ROOT_DIR\n\n# # cutil library is called cutil64 for 64 bit builds on windows.  We don't want\n# # to get these confused, so we are setting the name based on the word size of\n# # the build.\n\n# if(CMAKE_SIZEOF_VOID_P EQUAL 8)\n#   set(cuda_cutil_name cutil64)\n# else()\n#   set(cuda_cutil_name cutil32)\n# endif()\n\n# find_library(CUDA_CUT_LIBRARY\n#   NAMES cutil ${cuda_cutil_name}\n#   PATHS ${CUDA_SDK_SEARCH_PATH}\n#   # The new version of the sdk shows up in common/lib, but the old one is in lib\n#   PATH_SUFFIXES \"common/lib\" \"lib\"\n#   DOC \"Location of cutil library\"\n#   NO_DEFAULT_PATH\n#   )\n# # Now search system paths\n# find_library(CUDA_CUT_LIBRARY NAMES cutil ${cuda_cutil_name} DOC \"Location of cutil library\")\n# mark_as_advanced(CUDA_CUT_LIBRARY)\n# set(CUDA_CUT_LIBRARIES ${CUDA_CUT_LIBRARY})\n\n\n\n#############################\n# Check for required components\nset(CUDA_FOUND TRUE)\n\nset(CUDA_TOOLKIT_ROOT_DIR_INTERNAL \"${CUDA_TOOLKIT_ROOT_DIR}\" CACHE INTERNAL\n  \"This is the value of the last time CUDA_TOOLKIT_ROOT_DIR was set successfully.\" FORCE)\nset(CUDA_TOOLKIT_TARGET_DIR_INTERNAL \"${CUDA_TOOLKIT_TARGET_DIR}\" CACHE INTERNAL\n  \"This is the value of the last time CUDA_TOOLKIT_TARGET_DIR was set successfully.\" FORCE)\nset(CUDA_SDK_ROOT_DIR_INTERNAL \"${CUDA_SDK_ROOT_DIR}\" CACHE INTERNAL\n  \"This is the value of the last time CUDA_SDK_ROOT_DIR was set successfully.\" FORCE)\n\ninclude(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)\n\nfind_package_handle_standard_args(CUDA\n  REQUIRED_VARS\n    CUDA_TOOLKIT_ROOT_DIR\n    CUDA_NVCC_EXECUTABLE\n    CUDA_INCLUDE_DIRS\n    ${CUDA_CUDART_LIBRARY_VAR}\n  VERSION_VAR\n    CUDA_VERSION\n  )\n\n\n\n###############################################################################\n###############################################################################\n# Macros\n###############################################################################\n###############################################################################\n\n###############################################################################\n# Add include directories to pass to the nvcc command.\nmacro(CUDA_INCLUDE_DIRECTORIES)\n  foreach(dir ${ARGN})\n    list(APPEND CUDA_NVCC_INCLUDE_DIRS_USER ${dir})\n  endforeach()\nendmacro()\n\n\n##############################################################################\ncuda_find_helper_file(parse_cubin cmake)\ncuda_find_helper_file(make2cmake cmake)\ncuda_find_helper_file(run_nvcc cmake)\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/FindCUDA/select_compute_arch.cmake\")\n\n##############################################################################\n# Separate the OPTIONS out from the sources\n#\nmacro(CUDA_GET_SOURCES_AND_OPTIONS _sources _cmake_options _options)\n  set( ${_sources} )\n  set( ${_cmake_options} )\n  set( ${_options} )\n  set( _found_options FALSE )\n  foreach(arg ${ARGN})\n    if(\"x${arg}\" STREQUAL \"xOPTIONS\")\n      set( _found_options TRUE )\n    elseif(\n        \"x${arg}\" STREQUAL \"xWIN32\" OR\n        \"x${arg}\" STREQUAL \"xMACOSX_BUNDLE\" OR\n        \"x${arg}\" STREQUAL \"xEXCLUDE_FROM_ALL\" OR\n        \"x${arg}\" STREQUAL \"xSTATIC\" OR\n        \"x${arg}\" STREQUAL \"xSHARED\" OR\n        \"x${arg}\" STREQUAL \"xMODULE\"\n        )\n      list(APPEND ${_cmake_options} ${arg})\n    else()\n      if ( _found_options )\n        list(APPEND ${_options} ${arg})\n      else()\n        # Assume this is a file\n        list(APPEND ${_sources} ${arg})\n      endif()\n    endif()\n  endforeach()\nendmacro()\n\n##############################################################################\n# Parse the OPTIONS from ARGN and set the variables prefixed by _option_prefix\n#\nmacro(CUDA_PARSE_NVCC_OPTIONS _option_prefix)\n  set( _found_config )\n  foreach(arg ${ARGN})\n    # Determine if we are dealing with a perconfiguration flag\n    foreach(config ${CUDA_configuration_types})\n      string(TOUPPER ${config} config_upper)\n      if (arg STREQUAL \"${config_upper}\")\n        set( _found_config _${arg})\n        # Set arg to nothing to keep it from being processed further\n        set( arg )\n      endif()\n    endforeach()\n\n    if ( arg )\n      list(APPEND ${_option_prefix}${_found_config} \"${arg}\")\n    endif()\n  endforeach()\nendmacro()\n\n##############################################################################\n# Helper to add the include directory for CUDA only once\nfunction(CUDA_ADD_CUDA_INCLUDE_ONCE)\n  get_directory_property(_include_directories INCLUDE_DIRECTORIES)\n  set(_add TRUE)\n  if(_include_directories)\n    foreach(dir ${_include_directories})\n      if(\"${dir}\" STREQUAL \"${CUDA_INCLUDE_DIRS}\")\n        set(_add FALSE)\n      endif()\n    endforeach()\n  endif()\n  if(_add)\n    include_directories(${CUDA_INCLUDE_DIRS})\n  endif()\nendfunction()\n\nfunction(CUDA_BUILD_SHARED_LIBRARY shared_flag)\n  set(cmake_args ${ARGN})\n  # If SHARED, MODULE, or STATIC aren't already in the list of arguments, then\n  # add SHARED or STATIC based on the value of BUILD_SHARED_LIBS.\n  list(FIND cmake_args SHARED _cuda_found_SHARED)\n  list(FIND cmake_args MODULE _cuda_found_MODULE)\n  list(FIND cmake_args STATIC _cuda_found_STATIC)\n  if( _cuda_found_SHARED GREATER -1 OR\n      _cuda_found_MODULE GREATER -1 OR\n      _cuda_found_STATIC GREATER -1)\n    set(_cuda_build_shared_libs)\n  else()\n    if (BUILD_SHARED_LIBS)\n      set(_cuda_build_shared_libs SHARED)\n    else()\n      set(_cuda_build_shared_libs STATIC)\n    endif()\n  endif()\n  set(${shared_flag} ${_cuda_build_shared_libs} PARENT_SCOPE)\nendfunction()\n\n##############################################################################\n# Helper to avoid clashes of files with the same basename but different paths.\n# This doesn't attempt to do exactly what CMake internals do, which is to only\n# add this path when there is a conflict, since by the time a second collision\n# in names is detected it's already too late to fix the first one.  For\n# consistency sake the relative path will be added to all files.\nfunction(CUDA_COMPUTE_BUILD_PATH path build_path)\n  #message(\"CUDA_COMPUTE_BUILD_PATH([${path}] ${build_path})\")\n  # Only deal with CMake style paths from here on out\n  file(TO_CMAKE_PATH \"${path}\" bpath)\n  if (IS_ABSOLUTE \"${bpath}\")\n    # Absolute paths are generally unnessary, especially if something like\n    # file(GLOB_RECURSE) is used to pick up the files.\n\n    string(FIND \"${bpath}\" \"${CMAKE_CURRENT_BINARY_DIR}\" _binary_dir_pos)\n    if (_binary_dir_pos EQUAL 0)\n      file(RELATIVE_PATH bpath \"${CMAKE_CURRENT_BINARY_DIR}\" \"${bpath}\")\n    else()\n      file(RELATIVE_PATH bpath \"${CMAKE_CURRENT_SOURCE_DIR}\" \"${bpath}\")\n    endif()\n  endif()\n\n  # This recipe is from cmLocalGenerator::CreateSafeUniqueObjectFileName in the\n  # CMake source.\n\n  # Remove leading /\n  string(REGEX REPLACE \"^[/]+\" \"\" bpath \"${bpath}\")\n  # Avoid absolute paths by removing ':'\n  string(REPLACE \":\" \"_\" bpath \"${bpath}\")\n  # Avoid relative paths that go up the tree\n  string(REPLACE \"../\" \"__/\" bpath \"${bpath}\")\n  # Avoid spaces\n  string(REPLACE \" \" \"_\" bpath \"${bpath}\")\n\n  # Strip off the filename.  I wait until here to do it, since removin the\n  # basename can make a path that looked like path/../basename turn into\n  # path/.. (notice the trailing slash).\n  get_filename_component(bpath \"${bpath}\" PATH)\n\n  set(${build_path} \"${bpath}\" PARENT_SCOPE)\n  #message(\"${build_path} = ${bpath}\")\nendfunction()\n\n##############################################################################\n# This helper macro populates the following variables and setups up custom\n# commands and targets to invoke the nvcc compiler to generate C or PTX source\n# dependent upon the format parameter.  The compiler is invoked once with -M\n# to generate a dependency file and a second time with -cuda or -ptx to generate\n# a .cpp or .ptx file.\n# INPUT:\n#   cuda_target         - Target name\n#   format              - PTX, CUBIN, FATBIN or OBJ\n#   FILE1 .. FILEN      - The remaining arguments are the sources to be wrapped.\n#   OPTIONS             - Extra options to NVCC\n# OUTPUT:\n#   generated_files     - List of generated files\n##############################################################################\n##############################################################################\n\nmacro(CUDA_WRAP_SRCS cuda_target format generated_files)\n\n  # Put optional arguments in list.\n  set(_argn_list \"${ARGN}\")\n  # If one of the given optional arguments is \"PHONY\", make a note of it, then\n  # remove it from the list.\n  list(FIND _argn_list \"PHONY\" _phony_idx)\n  if(\"${_phony_idx}\" GREATER \"-1\")\n    set(_target_is_phony true)\n    list(REMOVE_AT _argn_list ${_phony_idx})\n  else()\n    set(_target_is_phony false)\n  endif()\n\n  # Set up all the command line flags here, so that they can be overridden on a per target basis.\n\n  set(nvcc_flags \"\")\n\n  # Emulation if the card isn't present.\n  if (CUDA_BUILD_EMULATION)\n    # Emulation.\n    set(nvcc_flags ${nvcc_flags} --device-emulation -D_DEVICEEMU -g)\n  else()\n    # Device mode.  No flags necessary.\n  endif()\n\n  if(CUDA_HOST_COMPILATION_CPP)\n    set(CUDA_C_OR_CXX CXX)\n  else()\n    if(CUDA_VERSION VERSION_LESS \"3.0\")\n      set(nvcc_flags ${nvcc_flags} --host-compilation C)\n    else()\n      message(WARNING \"--host-compilation flag is deprecated in CUDA version >= 3.0.  Removing --host-compilation C flag\" )\n    endif()\n    set(CUDA_C_OR_CXX C)\n  endif()\n\n  set(generated_extension ${CMAKE_${CUDA_C_OR_CXX}_OUTPUT_EXTENSION})\n\n  if(CUDA_64_BIT_DEVICE_CODE)\n    set(nvcc_flags ${nvcc_flags} -m64)\n  else()\n    set(nvcc_flags ${nvcc_flags} -m32)\n  endif()\n\n  if(CUDA_TARGET_CPU_ARCH)\n    set(nvcc_flags ${nvcc_flags} \"--target-cpu-architecture=${CUDA_TARGET_CPU_ARCH}\")\n  endif()\n\n  # This needs to be passed in at this stage, because VS needs to fill out the\n  # various macros from within VS.  Note that CCBIN is only used if\n  # -ccbin or --compiler-bindir isn't used and CUDA_HOST_COMPILER matches\n  # _CUDA_MSVC_HOST_COMPILER\n  if(CMAKE_GENERATOR MATCHES \"Visual Studio\")\n    set(ccbin_flags -D \"\\\"CCBIN:PATH=${_CUDA_MSVC_HOST_COMPILER}\\\"\" )\n  else()\n    set(ccbin_flags)\n  endif()\n\n  # Figure out which configure we will use and pass that in as an argument to\n  # the script.  We need to defer the decision until compilation time, because\n  # for VS projects we won't know if we are making a debug or release build\n  # until build time.\n  if(CMAKE_GENERATOR MATCHES \"Visual Studio\")\n    set( CUDA_build_configuration \"$(ConfigurationName)\" )\n  else()\n    set( CUDA_build_configuration \"${CMAKE_BUILD_TYPE}\")\n  endif()\n\n  # Initialize our list of includes with the user ones followed by the CUDA system ones.\n  set(CUDA_NVCC_INCLUDE_DIRS ${CUDA_NVCC_INCLUDE_DIRS_USER} \"${CUDA_INCLUDE_DIRS}\")\n  if(_target_is_phony)\n    # If the passed in target name isn't a real target (i.e., this is from a call to one of the\n    # cuda_compile_* functions), need to query directory properties to get include directories\n    # and compile definitions.\n    get_directory_property(_dir_include_dirs INCLUDE_DIRECTORIES)\n    get_directory_property(_dir_compile_defs COMPILE_DEFINITIONS)\n\n    list(APPEND CUDA_NVCC_INCLUDE_DIRS \"${_dir_include_dirs}\")\n    set(CUDA_NVCC_COMPILE_DEFINITIONS \"${_dir_compile_defs}\")\n  else()\n    # Append the include directories for this target via generator expression, which is\n    # expanded by the FILE(GENERATE) call below.  This generator expression captures all\n    # include dirs set by the user, whether via directory properties or target properties\n    list(APPEND CUDA_NVCC_INCLUDE_DIRS \"$<TARGET_PROPERTY:${cuda_target},INCLUDE_DIRECTORIES>\")\n\n    # Do the same thing with compile definitions\n    set(CUDA_NVCC_COMPILE_DEFINITIONS \"$<TARGET_PROPERTY:${cuda_target},COMPILE_DEFINITIONS>\")\n  endif()\n\n\n  # Reset these variables\n  set(CUDA_WRAP_OPTION_NVCC_FLAGS)\n  foreach(config ${CUDA_configuration_types})\n    string(TOUPPER ${config} config_upper)\n    set(CUDA_WRAP_OPTION_NVCC_FLAGS_${config_upper})\n  endforeach()\n\n  CUDA_GET_SOURCES_AND_OPTIONS(_cuda_wrap_sources _cuda_wrap_cmake_options _cuda_wrap_options ${_argn_list})\n  CUDA_PARSE_NVCC_OPTIONS(CUDA_WRAP_OPTION_NVCC_FLAGS ${_cuda_wrap_options})\n\n  # Figure out if we are building a shared library.  BUILD_SHARED_LIBS is\n  # respected in CUDA_ADD_LIBRARY.\n  set(_cuda_build_shared_libs FALSE)\n  # SHARED, MODULE\n  list(FIND _cuda_wrap_cmake_options SHARED _cuda_found_SHARED)\n  list(FIND _cuda_wrap_cmake_options MODULE _cuda_found_MODULE)\n  if(_cuda_found_SHARED GREATER -1 OR _cuda_found_MODULE GREATER -1)\n    set(_cuda_build_shared_libs TRUE)\n  endif()\n  # STATIC\n  list(FIND _cuda_wrap_cmake_options STATIC _cuda_found_STATIC)\n  if(_cuda_found_STATIC GREATER -1)\n    set(_cuda_build_shared_libs FALSE)\n  endif()\n\n  # CUDA_HOST_FLAGS\n  if(_cuda_build_shared_libs)\n    # If we are setting up code for a shared library, then we need to add extra flags for\n    # compiling objects for shared libraries.\n    set(CUDA_HOST_SHARED_FLAGS ${CMAKE_SHARED_LIBRARY_${CUDA_C_OR_CXX}_FLAGS})\n  else()\n    set(CUDA_HOST_SHARED_FLAGS)\n  endif()\n  # Only add the CMAKE_{C,CXX}_FLAGS if we are propagating host flags.  We\n  # always need to set the SHARED_FLAGS, though.\n  if(CUDA_PROPAGATE_HOST_FLAGS)\n    set(_cuda_host_flags \"set(CMAKE_HOST_FLAGS ${CMAKE_${CUDA_C_OR_CXX}_FLAGS} ${CUDA_HOST_SHARED_FLAGS})\")\n  else()\n    set(_cuda_host_flags \"set(CMAKE_HOST_FLAGS ${CUDA_HOST_SHARED_FLAGS})\")\n  endif()\n\n  set(_cuda_nvcc_flags_config \"# Build specific configuration flags\")\n  # Loop over all the configuration types to generate appropriate flags for run_nvcc.cmake\n  foreach(config ${CUDA_configuration_types})\n    string(TOUPPER ${config} config_upper)\n    # CMAKE_FLAGS are strings and not lists.  By not putting quotes around CMAKE_FLAGS\n    # we convert the strings to lists (like we want).\n\n    if(CUDA_PROPAGATE_HOST_FLAGS)\n      # nvcc chokes on -g3 in versions previous to 3.0, so replace it with -g\n      set(_cuda_fix_g3 FALSE)\n\n      if(CMAKE_COMPILER_IS_GNUCC)\n        if (CUDA_VERSION VERSION_LESS  \"3.0\" OR\n            CUDA_VERSION VERSION_EQUAL \"4.1\" OR\n            CUDA_VERSION VERSION_EQUAL \"4.2\"\n            )\n          set(_cuda_fix_g3 TRUE)\n        endif()\n      endif()\n      if(_cuda_fix_g3)\n        string(REPLACE \"-g3\" \"-g\" _cuda_C_FLAGS \"${CMAKE_${CUDA_C_OR_CXX}_FLAGS_${config_upper}}\")\n      else()\n        set(_cuda_C_FLAGS \"${CMAKE_${CUDA_C_OR_CXX}_FLAGS_${config_upper}}\")\n      endif()\n\n      string(APPEND _cuda_host_flags \"\\nset(CMAKE_HOST_FLAGS_${config_upper} ${_cuda_C_FLAGS})\")\n    endif()\n\n    # Note that if we ever want CUDA_NVCC_FLAGS_<CONFIG> to be string (instead of a list\n    # like it is currently), we can remove the quotes around the\n    # ${CUDA_NVCC_FLAGS_${config_upper}} variable like the CMAKE_HOST_FLAGS_<CONFIG> variable.\n    string(APPEND _cuda_nvcc_flags_config \"\\nset(CUDA_NVCC_FLAGS_${config_upper} ${CUDA_NVCC_FLAGS_${config_upper}} ;; ${CUDA_WRAP_OPTION_NVCC_FLAGS_${config_upper}})\")\n  endforeach()\n\n  # Process the C++11 flag.  If the host sets the flag, we need to add it to nvcc and\n  # remove it from the host. This is because -Xcompile -std=c++ will choke nvcc (it uses\n  # the C preprocessor).  In order to get this to work correctly, we need to use nvcc's\n  # specific c++11 flag.\n  if( \"${_cuda_host_flags}\" MATCHES \"-std=c\\\\+\\\\+11\")\n    # Add the c++11 flag to nvcc if it isn't already present.  Note that we only look at\n    # the main flag instead of the configuration specific flags.\n    if( NOT \"${CUDA_NVCC_FLAGS}\" MATCHES \"-std=c\\\\+\\\\+11\" )\n      list(APPEND nvcc_flags --std c++11)\n    endif()\n    string(REGEX REPLACE \"[-]+std=c\\\\+\\\\+11\" \"\" _cuda_host_flags \"${_cuda_host_flags}\")\n  endif()\n\n  if(_cuda_build_shared_libs)\n    list(APPEND nvcc_flags \"-D${cuda_target}_EXPORTS\")\n  endif()\n\n  # Reset the output variable\n  set(_cuda_wrap_generated_files \"\")\n\n  # Iterate over the macro arguments and create custom\n  # commands for all the .cu files.\n  foreach(file ${_argn_list})\n    # Ignore any file marked as a HEADER_FILE_ONLY\n    get_source_file_property(_is_header ${file} HEADER_FILE_ONLY)\n    # Allow per source file overrides of the format.  Also allows compiling non-.cu files.\n    get_source_file_property(_cuda_source_format ${file} CUDA_SOURCE_PROPERTY_FORMAT)\n    if((${file} MATCHES \"\\\\.cu$\" OR _cuda_source_format) AND NOT _is_header)\n\n      if(NOT _cuda_source_format)\n        set(_cuda_source_format ${format})\n      endif()\n      # If file isn't a .cu file, we need to tell nvcc to treat it as such.\n      if(NOT ${file} MATCHES \"\\\\.cu$\")\n        set(cuda_language_flag -x=cu)\n      else()\n        set(cuda_language_flag)\n      endif()\n\n      if( ${_cuda_source_format} MATCHES \"OBJ\")\n        set( cuda_compile_to_external_module OFF )\n      else()\n        set( cuda_compile_to_external_module ON )\n        if( ${_cuda_source_format} MATCHES \"PTX\" )\n          set( cuda_compile_to_external_module_type \"ptx\" )\n        elseif( ${_cuda_source_format} MATCHES \"CUBIN\")\n          set( cuda_compile_to_external_module_type \"cubin\" )\n        elseif( ${_cuda_source_format} MATCHES \"FATBIN\")\n          set( cuda_compile_to_external_module_type \"fatbin\" )\n        else()\n          message( FATAL_ERROR \"Invalid format flag passed to CUDA_WRAP_SRCS or set with CUDA_SOURCE_PROPERTY_FORMAT file property for file '${file}': '${_cuda_source_format}'.  Use OBJ, PTX, CUBIN or FATBIN.\")\n        endif()\n      endif()\n\n      if(cuda_compile_to_external_module)\n        # Don't use any of the host compilation flags for PTX targets.\n        set(CUDA_HOST_FLAGS)\n        set(CUDA_NVCC_FLAGS_CONFIG)\n      else()\n        set(CUDA_HOST_FLAGS ${_cuda_host_flags})\n        set(CUDA_NVCC_FLAGS_CONFIG ${_cuda_nvcc_flags_config})\n      endif()\n\n      # Determine output directory\n      cuda_compute_build_path(\"${file}\" cuda_build_path)\n      set(cuda_compile_intermediate_directory \"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${cuda_target}.dir/${cuda_build_path}\")\n      if(CUDA_GENERATED_OUTPUT_DIR)\n        set(cuda_compile_output_dir \"${CUDA_GENERATED_OUTPUT_DIR}\")\n      else()\n        if ( cuda_compile_to_external_module )\n          set(cuda_compile_output_dir \"${CMAKE_CURRENT_BINARY_DIR}\")\n        else()\n          set(cuda_compile_output_dir \"${cuda_compile_intermediate_directory}\")\n        endif()\n      endif()\n\n      # Add a custom target to generate a c or ptx file. ######################\n\n      get_filename_component( basename ${file} NAME )\n      if( cuda_compile_to_external_module )\n        set(generated_file_path \"${cuda_compile_output_dir}\")\n        set(generated_file_basename \"${cuda_target}_generated_${basename}.${cuda_compile_to_external_module_type}\")\n        set(format_flag \"-${cuda_compile_to_external_module_type}\")\n        file(MAKE_DIRECTORY \"${cuda_compile_output_dir}\")\n      else()\n        set(generated_file_path \"${cuda_compile_output_dir}/${CMAKE_CFG_INTDIR}\")\n        set(generated_file_basename \"${cuda_target}_generated_${basename}${generated_extension}\")\n        if(CUDA_SEPARABLE_COMPILATION)\n          set(format_flag \"-dc\")\n        else()\n          set(format_flag \"-c\")\n        endif()\n      endif()\n\n      # Set all of our file names.  Make sure that whatever filenames that have\n      # generated_file_path in them get passed in through as a command line\n      # argument, so that the ${CMAKE_CFG_INTDIR} gets expanded at run time\n      # instead of configure time.\n      set(generated_file \"${generated_file_path}/${generated_file_basename}\")\n      set(cmake_dependency_file \"${cuda_compile_intermediate_directory}/${generated_file_basename}.depend\")\n      set(NVCC_generated_dependency_file \"${cuda_compile_intermediate_directory}/${generated_file_basename}.NVCC-depend\")\n      set(generated_cubin_file \"${generated_file_path}/${generated_file_basename}.cubin.txt\")\n      set(custom_target_script_pregen \"${cuda_compile_intermediate_directory}/${generated_file_basename}.cmake.pre-gen\")\n      set(custom_target_script \"${cuda_compile_intermediate_directory}/${generated_file_basename}$<$<BOOL:$<CONFIG>>:.$<CONFIG>>.cmake\")\n\n      # Setup properties for obj files:\n      if( NOT cuda_compile_to_external_module )\n        set_source_files_properties(\"${generated_file}\"\n          PROPERTIES\n          EXTERNAL_OBJECT true # This is an object file not to be compiled, but only be linked.\n          )\n      endif()\n\n      # Don't add CMAKE_CURRENT_SOURCE_DIR if the path is already an absolute path.\n      get_filename_component(file_path \"${file}\" PATH)\n      if(IS_ABSOLUTE \"${file_path}\")\n        set(source_file \"${file}\")\n      else()\n        set(source_file \"${CMAKE_CURRENT_SOURCE_DIR}/${file}\")\n      endif()\n\n      if( NOT cuda_compile_to_external_module AND CUDA_SEPARABLE_COMPILATION)\n        list(APPEND ${cuda_target}_SEPARABLE_COMPILATION_OBJECTS \"${generated_file}\")\n      endif()\n\n      # Bring in the dependencies.  Creates a variable CUDA_NVCC_DEPEND #######\n      cuda_include_nvcc_dependencies(${cmake_dependency_file})\n\n      # Convience string for output ###########################################\n      if(CUDA_BUILD_EMULATION)\n        set(cuda_build_type \"Emulation\")\n      else()\n        set(cuda_build_type \"Device\")\n      endif()\n\n      # Build the NVCC made dependency file ###################################\n      set(build_cubin OFF)\n      if ( NOT CUDA_BUILD_EMULATION AND CUDA_BUILD_CUBIN )\n         if ( NOT cuda_compile_to_external_module )\n           set ( build_cubin ON )\n         endif()\n      endif()\n\n      # Configure the build script\n      configure_file(\"${CUDA_run_nvcc}\" \"${custom_target_script_pregen}\" @ONLY)\n      file(GENERATE\n        OUTPUT \"${custom_target_script}\"\n        INPUT \"${custom_target_script_pregen}\"\n        )\n\n      # So if a user specifies the same cuda file as input more than once, you\n      # can have bad things happen with dependencies.  Here we check an option\n      # to see if this is the behavior they want.\n      if(CUDA_ATTACH_VS_BUILD_RULE_TO_CUDA_FILE)\n        set(main_dep MAIN_DEPENDENCY ${source_file})\n      else()\n        set(main_dep DEPENDS ${source_file})\n      endif()\n\n      if(CUDA_VERBOSE_BUILD)\n        set(verbose_output ON)\n      elseif(CMAKE_GENERATOR MATCHES \"Makefiles\")\n        set(verbose_output \"$(VERBOSE)\")\n      else()\n        set(verbose_output OFF)\n      endif()\n\n      # Create up the comment string\n      file(RELATIVE_PATH generated_file_relative_path \"${CMAKE_BINARY_DIR}\" \"${generated_file}\")\n      if(cuda_compile_to_external_module)\n        set(cuda_build_comment_string \"Building NVCC ${cuda_compile_to_external_module_type} file ${generated_file_relative_path}\")\n      else()\n        set(cuda_build_comment_string \"Building NVCC (${cuda_build_type}) object ${generated_file_relative_path}\")\n      endif()\n\n      set(_verbatim VERBATIM)\n      if(ccbin_flags MATCHES \"\\\\$\\\\(VCInstallDir\\\\)\")\n        set(_verbatim \"\")\n      endif()\n\n      # Build the generated file and dependency file ##########################\n      add_custom_command(\n        OUTPUT ${generated_file}\n        # These output files depend on the source_file and the contents of cmake_dependency_file\n        ${main_dep}\n        DEPENDS ${CUDA_NVCC_DEPEND}\n        DEPENDS ${custom_target_script}\n        # Make sure the output directory exists before trying to write to it.\n        COMMAND ${CMAKE_COMMAND} -E make_directory \"${generated_file_path}\"\n        COMMAND ${CMAKE_COMMAND} ARGS\n          -D verbose:BOOL=${verbose_output}\n          ${ccbin_flags}\n          -D build_configuration:STRING=${CUDA_build_configuration}\n          -D \"generated_file:STRING=${generated_file}\"\n          -D \"generated_cubin_file:STRING=${generated_cubin_file}\"\n          -P \"${custom_target_script}\"\n        WORKING_DIRECTORY \"${cuda_compile_intermediate_directory}\"\n        COMMENT \"${cuda_build_comment_string}\"\n        ${_verbatim}\n        )\n\n      # Make sure the build system knows the file is generated.\n      set_source_files_properties(${generated_file} PROPERTIES GENERATED TRUE)\n\n      list(APPEND _cuda_wrap_generated_files ${generated_file})\n\n      # Add the other files that we want cmake to clean on a cleanup ##########\n      list(APPEND CUDA_ADDITIONAL_CLEAN_FILES \"${cmake_dependency_file}\")\n      list(REMOVE_DUPLICATES CUDA_ADDITIONAL_CLEAN_FILES)\n      set(CUDA_ADDITIONAL_CLEAN_FILES ${CUDA_ADDITIONAL_CLEAN_FILES} CACHE INTERNAL \"List of intermediate files that are part of the cuda dependency scanning.\")\n\n    endif()\n  endforeach()\n\n  # Set the return parameter\n  set(${generated_files} ${_cuda_wrap_generated_files})\nendmacro()\n\nfunction(_cuda_get_important_host_flags important_flags flag_string)\n  if(CMAKE_GENERATOR MATCHES \"Visual Studio\")\n    string(REGEX MATCHALL \"/M[DT][d]?\" flags \"${flag_string}\")\n    list(APPEND ${important_flags} ${flags})\n  else()\n    string(REGEX MATCHALL \"-fPIC\" flags \"${flag_string}\")\n    list(APPEND ${important_flags} ${flags})\n  endif()\n  set(${important_flags} ${${important_flags}} PARENT_SCOPE)\nendfunction()\n\n###############################################################################\n###############################################################################\n# Separable Compilation Link\n###############################################################################\n###############################################################################\n\n# Compute the filename to be used by CUDA_LINK_SEPARABLE_COMPILATION_OBJECTS\nfunction(CUDA_COMPUTE_SEPARABLE_COMPILATION_OBJECT_FILE_NAME output_file_var cuda_target object_files)\n  if (object_files)\n    set(generated_extension ${CMAKE_${CUDA_C_OR_CXX}_OUTPUT_EXTENSION})\n    set(output_file \"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${cuda_target}.dir/${CMAKE_CFG_INTDIR}/${cuda_target}_intermediate_link${generated_extension}\")\n  else()\n    set(output_file)\n  endif()\n\n  set(${output_file_var} \"${output_file}\" PARENT_SCOPE)\nendfunction()\n\n# Setup the build rule for the separable compilation intermediate link file.\nfunction(CUDA_LINK_SEPARABLE_COMPILATION_OBJECTS output_file cuda_target options object_files)\n  if (object_files)\n\n    set_source_files_properties(\"${output_file}\"\n      PROPERTIES\n      EXTERNAL_OBJECT TRUE # This is an object file not to be compiled, but only\n                           # be linked.\n      GENERATED TRUE       # This file is generated during the build\n      )\n\n    # For now we are ignoring all the configuration specific flags.\n    set(nvcc_flags)\n    CUDA_PARSE_NVCC_OPTIONS(nvcc_flags ${options})\n    if(CUDA_64_BIT_DEVICE_CODE)\n      list(APPEND nvcc_flags -m64)\n    else()\n      list(APPEND nvcc_flags -m32)\n    endif()\n    # If -ccbin, --compiler-bindir has been specified, don't do anything.  Otherwise add it here.\n    list( FIND nvcc_flags \"-ccbin\" ccbin_found0 )\n    list( FIND nvcc_flags \"--compiler-bindir\" ccbin_found1 )\n    if( ccbin_found0 LESS 0 AND ccbin_found1 LESS 0 AND CUDA_HOST_COMPILER )\n      # Match VERBATIM check below.\n      if(CUDA_HOST_COMPILER MATCHES \"\\\\$\\\\(VCInstallDir\\\\)\")\n        list(APPEND nvcc_flags -ccbin \"\\\"${CUDA_HOST_COMPILER}\\\"\")\n      else()\n        list(APPEND nvcc_flags -ccbin \"${CUDA_HOST_COMPILER}\")\n      endif()\n    endif()\n\n    # Create a list of flags specified by CUDA_NVCC_FLAGS_${CONFIG} and CMAKE_${CUDA_C_OR_CXX}_FLAGS*\n    set(config_specific_flags)\n    set(flags)\n    foreach(config ${CUDA_configuration_types})\n      string(TOUPPER ${config} config_upper)\n      # Add config specific flags\n      foreach(f ${CUDA_NVCC_FLAGS_${config_upper}})\n        list(APPEND config_specific_flags $<$<CONFIG:${config}>:${f}>)\n      endforeach()\n      set(important_host_flags)\n      _cuda_get_important_host_flags(important_host_flags \"${CMAKE_${CUDA_C_OR_CXX}_FLAGS_${config_upper}}\")\n      foreach(f ${important_host_flags})\n        list(APPEND flags $<$<CONFIG:${config}>:-Xcompiler> $<$<CONFIG:${config}>:${f}>)\n      endforeach()\n    endforeach()\n    # Add CMAKE_${CUDA_C_OR_CXX}_FLAGS\n    set(important_host_flags)\n    _cuda_get_important_host_flags(important_host_flags \"${CMAKE_${CUDA_C_OR_CXX}_FLAGS}\")\n    foreach(f ${important_host_flags})\n      list(APPEND flags -Xcompiler ${f})\n    endforeach()\n\n    # Add our general CUDA_NVCC_FLAGS with the configuration specifig flags\n    set(nvcc_flags ${CUDA_NVCC_FLAGS} ${config_specific_flags} ${nvcc_flags})\n\n    file(RELATIVE_PATH output_file_relative_path \"${CMAKE_BINARY_DIR}\" \"${output_file}\")\n\n    # Some generators don't handle the multiple levels of custom command\n    # dependencies correctly (obj1 depends on file1, obj2 depends on obj1), so\n    # we work around that issue by compiling the intermediate link object as a\n    # pre-link custom command in that situation.\n    set(do_obj_build_rule TRUE)\n    if (MSVC_VERSION GREATER 1599 AND MSVC_VERSION LESS 1800)\n      # VS 2010 and 2012 have this problem.\n      set(do_obj_build_rule FALSE)\n    endif()\n\n    set(_verbatim VERBATIM)\n    if(nvcc_flags MATCHES \"\\\\$\\\\(VCInstallDir\\\\)\")\n      set(_verbatim \"\")\n    endif()\n\n    if (do_obj_build_rule)\n      add_custom_command(\n        OUTPUT ${output_file}\n        DEPENDS ${object_files}\n        COMMAND ${CUDA_NVCC_EXECUTABLE} ${nvcc_flags} -dlink ${object_files} ${CUDA_cublas_device_LIBRARY} -o ${output_file}\n        ${flags}\n        COMMENT \"Building NVCC intermediate link file ${output_file_relative_path}\"\n        COMMAND_EXPAND_LISTS\n        ${_verbatim}\n        )\n    else()\n      get_filename_component(output_file_dir \"${output_file}\" DIRECTORY)\n      add_custom_command(\n        TARGET ${cuda_target}\n        PRE_LINK\n        COMMAND ${CMAKE_COMMAND} -E echo \"Building NVCC intermediate link file ${output_file_relative_path}\"\n        COMMAND ${CMAKE_COMMAND} -E make_directory \"${output_file_dir}\"\n        COMMAND ${CUDA_NVCC_EXECUTABLE} ${nvcc_flags} ${flags} -dlink ${object_files} ${CUDA_cublas_device_LIBRARY} -o \"${output_file}\"\n        COMMAND_EXPAND_LISTS\n        ${_verbatim}\n        )\n    endif()\n endif()\nendfunction()\n\n###############################################################################\n###############################################################################\n# ADD LIBRARY\n###############################################################################\n###############################################################################\nmacro(CUDA_ADD_LIBRARY cuda_target)\n\n  CUDA_ADD_CUDA_INCLUDE_ONCE()\n\n  # Separate the sources from the options\n  CUDA_GET_SOURCES_AND_OPTIONS(_sources _cmake_options _options ${ARGN})\n  CUDA_BUILD_SHARED_LIBRARY(_cuda_shared_flag ${ARGN})\n  # Create custom commands and targets for each file.\n  CUDA_WRAP_SRCS( ${cuda_target} OBJ _generated_files ${_sources}\n    ${_cmake_options} ${_cuda_shared_flag}\n    OPTIONS ${_options} )\n\n  # Compute the file name of the intermedate link file used for separable\n  # compilation.\n  CUDA_COMPUTE_SEPARABLE_COMPILATION_OBJECT_FILE_NAME(link_file ${cuda_target} \"${${cuda_target}_SEPARABLE_COMPILATION_OBJECTS}\")\n\n  # Add the library.\n  add_library(${cuda_target} ${_cmake_options}\n    ${_generated_files}\n    ${_sources}\n    ${link_file}\n    )\n\n  # Add a link phase for the separable compilation if it has been enabled.  If\n  # it has been enabled then the ${cuda_target}_SEPARABLE_COMPILATION_OBJECTS\n  # variable will have been defined.\n  CUDA_LINK_SEPARABLE_COMPILATION_OBJECTS(\"${link_file}\" ${cuda_target} \"${_options}\" \"${${cuda_target}_SEPARABLE_COMPILATION_OBJECTS}\")\n\n  target_link_libraries(${cuda_target} ${CUDA_LINK_LIBRARIES_KEYWORD}\n    ${CUDA_LIBRARIES}\n    )\n\n  if(CUDA_SEPARABLE_COMPILATION)\n    target_link_libraries(${cuda_target} ${CUDA_LINK_LIBRARIES_KEYWORD}\n      ${CUDA_cudadevrt_LIBRARY}\n      )\n  endif()\n\n  # We need to set the linker language based on what the expected generated file\n  # would be. CUDA_C_OR_CXX is computed based on CUDA_HOST_COMPILATION_CPP.\n  set_target_properties(${cuda_target}\n    PROPERTIES\n    LINKER_LANGUAGE ${CUDA_C_OR_CXX}\n    )\n\nendmacro()\n\n\n###############################################################################\n###############################################################################\n# ADD EXECUTABLE\n###############################################################################\n###############################################################################\nmacro(CUDA_ADD_EXECUTABLE cuda_target)\n\n  CUDA_ADD_CUDA_INCLUDE_ONCE()\n\n  # Separate the sources from the options\n  CUDA_GET_SOURCES_AND_OPTIONS(_sources _cmake_options _options ${ARGN})\n  # Create custom commands and targets for each file.\n  CUDA_WRAP_SRCS( ${cuda_target} OBJ _generated_files ${_sources} OPTIONS ${_options} )\n\n  # Compute the file name of the intermedate link file used for separable\n  # compilation.\n  CUDA_COMPUTE_SEPARABLE_COMPILATION_OBJECT_FILE_NAME(link_file ${cuda_target} \"${${cuda_target}_SEPARABLE_COMPILATION_OBJECTS}\")\n\n  # Add the library.\n  add_executable(${cuda_target} ${_cmake_options}\n    ${_generated_files}\n    ${_sources}\n    ${link_file}\n    )\n\n  # Add a link phase for the separable compilation if it has been enabled.  If\n  # it has been enabled then the ${cuda_target}_SEPARABLE_COMPILATION_OBJECTS\n  # variable will have been defined.\n  CUDA_LINK_SEPARABLE_COMPILATION_OBJECTS(\"${link_file}\" ${cuda_target} \"${_options}\" \"${${cuda_target}_SEPARABLE_COMPILATION_OBJECTS}\")\n\n  target_link_libraries(${cuda_target} ${CUDA_LINK_LIBRARIES_KEYWORD}\n    ${CUDA_LIBRARIES}\n    )\n\n  # We need to set the linker language based on what the expected generated file\n  # would be. CUDA_C_OR_CXX is computed based on CUDA_HOST_COMPILATION_CPP.\n  set_target_properties(${cuda_target}\n    PROPERTIES\n    LINKER_LANGUAGE ${CUDA_C_OR_CXX}\n    )\n\nendmacro()\n\n\n###############################################################################\n###############################################################################\n# (Internal) helper for manually added cuda source files with specific targets\n###############################################################################\n###############################################################################\nmacro(cuda_compile_base cuda_target format generated_files)\n  # Update a counter in this directory, to keep phony target names unique.\n  set(_cuda_target \"${cuda_target}\")\n  get_property(_counter DIRECTORY PROPERTY _cuda_internal_phony_counter)\n  if(_counter)\n    math(EXPR _counter \"${_counter} + 1\")\n  else()\n    set(_counter 1)\n  endif()\n  string(APPEND _cuda_target \"_${_counter}\")\n  set_property(DIRECTORY PROPERTY _cuda_internal_phony_counter ${_counter})\n\n  # Separate the sources from the options\n  CUDA_GET_SOURCES_AND_OPTIONS(_sources _cmake_options _options ${ARGN})\n\n  # Create custom commands and targets for each file.\n  CUDA_WRAP_SRCS( ${_cuda_target} ${format} _generated_files ${_sources}\n                  ${_cmake_options} OPTIONS ${_options} PHONY)\n\n  set( ${generated_files} ${_generated_files})\n\nendmacro()\n\n###############################################################################\n###############################################################################\n# CUDA COMPILE\n###############################################################################\n###############################################################################\nmacro(CUDA_COMPILE generated_files)\n  cuda_compile_base(cuda_compile OBJ ${generated_files} ${ARGN})\nendmacro()\n\n###############################################################################\n###############################################################################\n# CUDA COMPILE PTX\n###############################################################################\n###############################################################################\nmacro(CUDA_COMPILE_PTX generated_files)\n  cuda_compile_base(cuda_compile_ptx PTX ${generated_files} ${ARGN})\nendmacro()\n\n###############################################################################\n###############################################################################\n# CUDA COMPILE FATBIN\n###############################################################################\n###############################################################################\nmacro(CUDA_COMPILE_FATBIN generated_files)\n  cuda_compile_base(cuda_compile_fatbin FATBIN ${generated_files} ${ARGN})\nendmacro()\n\n###############################################################################\n###############################################################################\n# CUDA COMPILE CUBIN\n###############################################################################\n###############################################################################\nmacro(CUDA_COMPILE_CUBIN generated_files)\n  cuda_compile_base(cuda_compile_cubin CUBIN ${generated_files} ${ARGN})\nendmacro()\n\n\n###############################################################################\n###############################################################################\n# CUDA ADD CUFFT TO TARGET\n###############################################################################\n###############################################################################\nmacro(CUDA_ADD_CUFFT_TO_TARGET target)\n  if (CUDA_BUILD_EMULATION)\n    target_link_libraries(${target} ${CUDA_LINK_LIBRARIES_KEYWORD} ${CUDA_cufftemu_LIBRARY})\n  else()\n    target_link_libraries(${target} ${CUDA_LINK_LIBRARIES_KEYWORD} ${CUDA_cufft_LIBRARY})\n  endif()\nendmacro()\n\n###############################################################################\n###############################################################################\n# CUDA ADD CUBLAS TO TARGET\n###############################################################################\n###############################################################################\nmacro(CUDA_ADD_CUBLAS_TO_TARGET target)\n  if (CUDA_BUILD_EMULATION)\n    target_link_libraries(${target} ${CUDA_LINK_LIBRARIES_KEYWORD} ${CUDA_cublasemu_LIBRARY})\n  else()\n    target_link_libraries(${target} ${CUDA_LINK_LIBRARIES_KEYWORD} ${CUDA_cublas_LIBRARY} ${CUDA_cublas_device_LIBRARY})\n  endif()\nendmacro()\n\n###############################################################################\n###############################################################################\n# CUDA BUILD CLEAN TARGET\n###############################################################################\n###############################################################################\nmacro(CUDA_BUILD_CLEAN_TARGET)\n  # Call this after you add all your CUDA targets, and you will get a convience\n  # target.  You should also make clean after running this target to get the\n  # build system to generate all the code again.\n\n  set(cuda_clean_target_name clean_cuda_depends)\n  if (CMAKE_GENERATOR MATCHES \"Visual Studio\")\n    string(TOUPPER ${cuda_clean_target_name} cuda_clean_target_name)\n  endif()\n  add_custom_target(${cuda_clean_target_name}\n    COMMAND ${CMAKE_COMMAND} -E remove ${CUDA_ADDITIONAL_CLEAN_FILES})\n\n  # Clear out the variable, so the next time we configure it will be empty.\n  # This is useful so that the files won't persist in the list after targets\n  # have been removed.\n  set(CUDA_ADDITIONAL_CLEAN_FILES \"\" CACHE INTERNAL \"List of intermediate files that are part of the cuda dependency scanning.\")\nendmacro()\n"
  },
  {
    "path": "cmake/Modules_CUDA_fix/FindPackageHandleStandardArgs.cmake",
    "content": "# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying\n# file Copyright.txt or https://cmake.org/licensing for details.\n\n#[=======================================================================[.rst:\nFindPackageHandleStandardArgs\n-----------------------------\n\nThis module provides a function intended to be used in :ref:`Find Modules`\nimplementing :command:`find_package(<PackageName>)` calls.  It handles the\n``REQUIRED``, ``QUIET`` and version-related arguments of ``find_package``.\nIt also sets the ``<PackageName>_FOUND`` variable.  The package is\nconsidered found if all variables listed contain valid results, e.g.\nvalid filepaths.\n\n.. command:: find_package_handle_standard_args\n\n  There are two signatures::\n\n    find_package_handle_standard_args(<PackageName>\n      (DEFAULT_MSG|<custom-failure-message>)\n      <required-var>...\n      )\n\n    find_package_handle_standard_args(<PackageName>\n      [FOUND_VAR <result-var>]\n      [REQUIRED_VARS <required-var>...]\n      [VERSION_VAR <version-var>]\n      [HANDLE_COMPONENTS]\n      [CONFIG_MODE]\n      [FAIL_MESSAGE <custom-failure-message>]\n      )\n\n  The ``<PackageName>_FOUND`` variable will be set to ``TRUE`` if all\n  the variables ``<required-var>...`` are valid and any optional\n  constraints are satisfied, and ``FALSE`` otherwise.  A success or\n  failure message may be displayed based on the results and on\n  whether the ``REQUIRED`` and/or ``QUIET`` option was given to\n  the :command:`find_package` call.\n\n  The options are:\n\n  ``(DEFAULT_MSG|<custom-failure-message>)``\n    In the simple signature this specifies the failure message.\n    Use ``DEFAULT_MSG`` to ask for a default message to be computed\n    (recommended).  Not valid in the full signature.\n\n  ``FOUND_VAR <result-var>``\n    Obsolete.  Specifies either ``<PackageName>_FOUND`` or\n    ``<PACKAGENAME>_FOUND`` as the result variable.  This exists only\n    for compatibility with older versions of CMake and is now ignored.\n    Result variables of both names are always set for compatibility.\n\n  ``REQUIRED_VARS <required-var>...``\n    Specify the variables which are required for this package.\n    These may be named in the generated failure message asking the\n    user to set the missing variable values.  Therefore these should\n    typically be cache entries such as ``FOO_LIBRARY`` and not output\n    variables like ``FOO_LIBRARIES``.\n\n  ``VERSION_VAR <version-var>``\n    Specify the name of a variable that holds the version of the package\n    that has been found.  This version will be checked against the\n    (potentially) specified required version given to the\n    :command:`find_package` call, including its ``EXACT`` option.\n    The default messages include information about the required\n    version and the version which has been actually found, both\n    if the version is ok or not.\n\n  ``HANDLE_COMPONENTS``\n    Enable handling of package components.  In this case, the command\n    will report which components have been found and which are missing,\n    and the ``<PackageName>_FOUND`` variable will be set to ``FALSE``\n    if any of the required components (i.e. not the ones listed after\n    the ``OPTIONAL_COMPONENTS`` option of :command:`find_package`) are\n    missing.\n\n  ``CONFIG_MODE``\n    Specify that the calling find module is a wrapper around a\n    call to ``find_package(<PackageName> NO_MODULE)``.  This implies\n    a ``VERSION_VAR`` value of ``<PackageName>_VERSION``.  The command\n    will automatically check whether the package configuration file\n    was found.\n\n  ``FAIL_MESSAGE <custom-failure-message>``\n    Specify a custom failure message instead of using the default\n    generated message.  Not recommended.\n\nExample for the simple signature:\n\n.. code-block:: cmake\n\n  find_package_handle_standard_args(LibXml2 DEFAULT_MSG\n    LIBXML2_LIBRARY LIBXML2_INCLUDE_DIR)\n\nThe ``LibXml2`` package is considered to be found if both\n``LIBXML2_LIBRARY`` and ``LIBXML2_INCLUDE_DIR`` are valid.\nThen also ``LibXml2_FOUND`` is set to ``TRUE``.  If it is not found\nand ``REQUIRED`` was used, it fails with a\n:command:`message(FATAL_ERROR)`, independent whether ``QUIET`` was\nused or not.  If it is found, success will be reported, including\nthe content of the first ``<required-var>``.  On repeated CMake runs,\nthe same message will not be printed again.\n\nExample for the full signature:\n\n.. code-block:: cmake\n\n  find_package_handle_standard_args(LibArchive\n    REQUIRED_VARS LibArchive_LIBRARY LibArchive_INCLUDE_DIR\n    VERSION_VAR LibArchive_VERSION)\n\nIn this case, the ``LibArchive`` package is considered to be found if\nboth ``LibArchive_LIBRARY`` and ``LibArchive_INCLUDE_DIR`` are valid.\nAlso the version of ``LibArchive`` will be checked by using the version\ncontained in ``LibArchive_VERSION``.  Since no ``FAIL_MESSAGE`` is given,\nthe default messages will be printed.\n\nAnother example for the full signature:\n\n.. code-block:: cmake\n\n  find_package(Automoc4 QUIET NO_MODULE HINTS /opt/automoc4)\n  find_package_handle_standard_args(Automoc4  CONFIG_MODE)\n\nIn this case, a ``FindAutmoc4.cmake`` module wraps a call to\n``find_package(Automoc4 NO_MODULE)`` and adds an additional search\ndirectory for ``automoc4``.  Then the call to\n``find_package_handle_standard_args`` produces a proper success/failure\nmessage.\n#]=======================================================================]\n\ninclude(${CMAKE_CURRENT_LIST_DIR}/FindPackageMessage.cmake)\n\n# internal helper macro\nmacro(_FPHSA_FAILURE_MESSAGE _msg)\n  if (${_NAME}_FIND_REQUIRED)\n    message(FATAL_ERROR \"${_msg}\")\n  else ()\n    if (NOT ${_NAME}_FIND_QUIETLY)\n      message(STATUS \"${_msg}\")\n    endif ()\n  endif ()\nendmacro()\n\n\n# internal helper macro to generate the failure message when used in CONFIG_MODE:\nmacro(_FPHSA_HANDLE_FAILURE_CONFIG_MODE)\n  # <name>_CONFIG is set, but FOUND is false, this means that some other of the REQUIRED_VARS was not found:\n  if(${_NAME}_CONFIG)\n    _FPHSA_FAILURE_MESSAGE(\"${FPHSA_FAIL_MESSAGE}: missing:${MISSING_VARS} (found ${${_NAME}_CONFIG} ${VERSION_MSG})\")\n  else()\n    # If _CONSIDERED_CONFIGS is set, the config-file has been found, but no suitable version.\n    # List them all in the error message:\n    if(${_NAME}_CONSIDERED_CONFIGS)\n      set(configsText \"\")\n      list(LENGTH ${_NAME}_CONSIDERED_CONFIGS configsCount)\n      math(EXPR configsCount \"${configsCount} - 1\")\n      foreach(currentConfigIndex RANGE ${configsCount})\n        list(GET ${_NAME}_CONSIDERED_CONFIGS ${currentConfigIndex} filename)\n        list(GET ${_NAME}_CONSIDERED_VERSIONS ${currentConfigIndex} version)\n        string(APPEND configsText \"    ${filename} (version ${version})\\n\")\n      endforeach()\n      if (${_NAME}_NOT_FOUND_MESSAGE)\n        string(APPEND configsText \"    Reason given by package: ${${_NAME}_NOT_FOUND_MESSAGE}\\n\")\n      endif()\n      _FPHSA_FAILURE_MESSAGE(\"${FPHSA_FAIL_MESSAGE} ${VERSION_MSG}, checked the following files:\\n${configsText}\")\n\n    else()\n      # Simple case: No Config-file was found at all:\n      _FPHSA_FAILURE_MESSAGE(\"${FPHSA_FAIL_MESSAGE}: found neither ${_NAME}Config.cmake nor ${_NAME_LOWER}-config.cmake ${VERSION_MSG}\")\n    endif()\n  endif()\nendmacro()\n\n\nfunction(FIND_PACKAGE_HANDLE_STANDARD_ARGS _NAME _FIRST_ARG)\n\n# Set up the arguments for `cmake_parse_arguments`.\n  set(options  CONFIG_MODE  HANDLE_COMPONENTS)\n  set(oneValueArgs  FAIL_MESSAGE  VERSION_VAR  FOUND_VAR)\n  set(multiValueArgs REQUIRED_VARS)\n\n# Check whether we are in 'simple' or 'extended' mode:\n  set(_KEYWORDS_FOR_EXTENDED_MODE  ${options} ${oneValueArgs} ${multiValueArgs} )\n  list(FIND _KEYWORDS_FOR_EXTENDED_MODE \"${_FIRST_ARG}\" INDEX)\n\n  if(${INDEX} EQUAL -1)\n    set(FPHSA_FAIL_MESSAGE ${_FIRST_ARG})\n    set(FPHSA_REQUIRED_VARS ${ARGN})\n    set(FPHSA_VERSION_VAR)\n  else()\n    cmake_parse_arguments(FPHSA \"${options}\" \"${oneValueArgs}\" \"${multiValueArgs}\"  ${_FIRST_ARG} ${ARGN})\n\n    if(FPHSA_UNPARSED_ARGUMENTS)\n      message(FATAL_ERROR \"Unknown keywords given to FIND_PACKAGE_HANDLE_STANDARD_ARGS(): \\\"${FPHSA_UNPARSED_ARGUMENTS}\\\"\")\n    endif()\n\n    if(NOT FPHSA_FAIL_MESSAGE)\n      set(FPHSA_FAIL_MESSAGE  \"DEFAULT_MSG\")\n    endif()\n\n    # In config-mode, we rely on the variable <package>_CONFIG, which is set by find_package()\n    # when it successfully found the config-file, including version checking:\n    if(FPHSA_CONFIG_MODE)\n      list(INSERT FPHSA_REQUIRED_VARS 0 ${_NAME}_CONFIG)\n      list(REMOVE_DUPLICATES FPHSA_REQUIRED_VARS)\n      set(FPHSA_VERSION_VAR ${_NAME}_VERSION)\n    endif()\n\n    if(NOT FPHSA_REQUIRED_VARS)\n      message(FATAL_ERROR \"No REQUIRED_VARS specified for FIND_PACKAGE_HANDLE_STANDARD_ARGS()\")\n    endif()\n  endif()\n\n# now that we collected all arguments, process them\n\n  if(\"x${FPHSA_FAIL_MESSAGE}\" STREQUAL \"xDEFAULT_MSG\")\n    set(FPHSA_FAIL_MESSAGE \"Could NOT find ${_NAME}\")\n  endif()\n\n  list(GET FPHSA_REQUIRED_VARS 0 _FIRST_REQUIRED_VAR)\n\n  string(TOUPPER ${_NAME} _NAME_UPPER)\n  string(TOLOWER ${_NAME} _NAME_LOWER)\n\n  if(FPHSA_FOUND_VAR)\n    if(FPHSA_FOUND_VAR MATCHES \"^${_NAME}_FOUND$\"  OR  FPHSA_FOUND_VAR MATCHES \"^${_NAME_UPPER}_FOUND$\")\n      set(_FOUND_VAR ${FPHSA_FOUND_VAR})\n    else()\n      message(FATAL_ERROR \"The argument for FOUND_VAR is \\\"${FPHSA_FOUND_VAR}\\\", but only \\\"${_NAME}_FOUND\\\" and \\\"${_NAME_UPPER}_FOUND\\\" are valid names.\")\n    endif()\n  else()\n    set(_FOUND_VAR ${_NAME_UPPER}_FOUND)\n  endif()\n\n  # collect all variables which were not found, so they can be printed, so the\n  # user knows better what went wrong (#6375)\n  set(MISSING_VARS \"\")\n  set(DETAILS \"\")\n  # check if all passed variables are valid\n  set(FPHSA_FOUND_${_NAME} TRUE)\n  foreach(_CURRENT_VAR ${FPHSA_REQUIRED_VARS})\n    if(NOT ${_CURRENT_VAR})\n      set(FPHSA_FOUND_${_NAME} FALSE)\n      string(APPEND MISSING_VARS \" ${_CURRENT_VAR}\")\n    else()\n      string(APPEND DETAILS \"[${${_CURRENT_VAR}}]\")\n    endif()\n  endforeach()\n  if(FPHSA_FOUND_${_NAME})\n    set(${_NAME}_FOUND TRUE)\n    set(${_NAME_UPPER}_FOUND TRUE)\n  else()\n    set(${_NAME}_FOUND FALSE)\n    set(${_NAME_UPPER}_FOUND FALSE)\n  endif()\n\n  # component handling\n  unset(FOUND_COMPONENTS_MSG)\n  unset(MISSING_COMPONENTS_MSG)\n\n  if(FPHSA_HANDLE_COMPONENTS)\n    foreach(comp ${${_NAME}_FIND_COMPONENTS})\n      if(${_NAME}_${comp}_FOUND)\n\n        if(NOT DEFINED FOUND_COMPONENTS_MSG)\n          set(FOUND_COMPONENTS_MSG \"found components: \")\n        endif()\n        string(APPEND FOUND_COMPONENTS_MSG \" ${comp}\")\n\n      else()\n\n        if(NOT DEFINED MISSING_COMPONENTS_MSG)\n          set(MISSING_COMPONENTS_MSG \"missing components: \")\n        endif()\n        string(APPEND MISSING_COMPONENTS_MSG \" ${comp}\")\n\n        if(${_NAME}_FIND_REQUIRED_${comp})\n          set(${_NAME}_FOUND FALSE)\n          string(APPEND MISSING_VARS \" ${comp}\")\n        endif()\n\n      endif()\n    endforeach()\n    set(COMPONENT_MSG \"${FOUND_COMPONENTS_MSG} ${MISSING_COMPONENTS_MSG}\")\n    string(APPEND DETAILS \"[c${COMPONENT_MSG}]\")\n  endif()\n\n  # version handling:\n  set(VERSION_MSG \"\")\n  set(VERSION_OK TRUE)\n\n  # check with DEFINED here as the requested or found version may be \"0\"\n  if (DEFINED ${_NAME}_FIND_VERSION)\n    if(DEFINED ${FPHSA_VERSION_VAR})\n      set(_FOUND_VERSION ${${FPHSA_VERSION_VAR}})\n\n      if(${_NAME}_FIND_VERSION_EXACT)       # exact version required\n        # count the dots in the version string\n        string(REGEX REPLACE \"[^.]\" \"\" _VERSION_DOTS \"${_FOUND_VERSION}\")\n        # add one dot because there is one dot more than there are components\n        string(LENGTH \"${_VERSION_DOTS}.\" _VERSION_DOTS)\n        if (_VERSION_DOTS GREATER ${_NAME}_FIND_VERSION_COUNT)\n          # Because of the C++ implementation of find_package() ${_NAME}_FIND_VERSION_COUNT\n          # is at most 4 here. Therefore a simple lookup table is used.\n          if (${_NAME}_FIND_VERSION_COUNT EQUAL 1)\n            set(_VERSION_REGEX \"[^.]*\")\n          elseif (${_NAME}_FIND_VERSION_COUNT EQUAL 2)\n            set(_VERSION_REGEX \"[^.]*\\\\.[^.]*\")\n          elseif (${_NAME}_FIND_VERSION_COUNT EQUAL 3)\n            set(_VERSION_REGEX \"[^.]*\\\\.[^.]*\\\\.[^.]*\")\n          else ()\n            set(_VERSION_REGEX \"[^.]*\\\\.[^.]*\\\\.[^.]*\\\\.[^.]*\")\n          endif ()\n          string(REGEX REPLACE \"^(${_VERSION_REGEX})\\\\..*\" \"\\\\1\" _VERSION_HEAD \"${_FOUND_VERSION}\")\n          unset(_VERSION_REGEX)\n          if (NOT ${_NAME}_FIND_VERSION VERSION_EQUAL _VERSION_HEAD)\n            set(VERSION_MSG \"Found unsuitable version \\\"${_FOUND_VERSION}\\\", but required is exact version \\\"${${_NAME}_FIND_VERSION}\\\"\")\n            set(VERSION_OK FALSE)\n          else ()\n            set(VERSION_MSG \"(found suitable exact version \\\"${_FOUND_VERSION}\\\")\")\n          endif ()\n          unset(_VERSION_HEAD)\n        else ()\n          if (NOT ${_NAME}_FIND_VERSION VERSION_EQUAL _FOUND_VERSION)\n            set(VERSION_MSG \"Found unsuitable version \\\"${_FOUND_VERSION}\\\", but required is exact version \\\"${${_NAME}_FIND_VERSION}\\\"\")\n            set(VERSION_OK FALSE)\n          else ()\n            set(VERSION_MSG \"(found suitable exact version \\\"${_FOUND_VERSION}\\\")\")\n          endif ()\n        endif ()\n        unset(_VERSION_DOTS)\n\n      else()     # minimum version specified:\n        if (${_NAME}_FIND_VERSION VERSION_GREATER _FOUND_VERSION)\n          set(VERSION_MSG \"Found unsuitable version \\\"${_FOUND_VERSION}\\\", but required is at least \\\"${${_NAME}_FIND_VERSION}\\\"\")\n          set(VERSION_OK FALSE)\n        else ()\n          set(VERSION_MSG \"(found suitable version \\\"${_FOUND_VERSION}\\\", minimum required is \\\"${${_NAME}_FIND_VERSION}\\\")\")\n        endif ()\n      endif()\n\n    else()\n\n      # if the package was not found, but a version was given, add that to the output:\n      if(${_NAME}_FIND_VERSION_EXACT)\n         set(VERSION_MSG \"(Required is exact version \\\"${${_NAME}_FIND_VERSION}\\\")\")\n      else()\n         set(VERSION_MSG \"(Required is at least version \\\"${${_NAME}_FIND_VERSION}\\\")\")\n      endif()\n\n    endif()\n  else ()\n    # Check with DEFINED as the found version may be 0.\n    if(DEFINED ${FPHSA_VERSION_VAR})\n      set(VERSION_MSG \"(found version \\\"${${FPHSA_VERSION_VAR}}\\\")\")\n    endif()\n  endif ()\n\n  if(VERSION_OK)\n    string(APPEND DETAILS \"[v${${FPHSA_VERSION_VAR}}(${${_NAME}_FIND_VERSION})]\")\n  else()\n    set(${_NAME}_FOUND FALSE)\n  endif()\n\n\n  # print the result:\n  if (${_NAME}_FOUND)\n    FIND_PACKAGE_MESSAGE(${_NAME} \"Found ${_NAME}: ${${_FIRST_REQUIRED_VAR}} ${VERSION_MSG} ${COMPONENT_MSG}\" \"${DETAILS}\")\n  else ()\n\n    if(FPHSA_CONFIG_MODE)\n      _FPHSA_HANDLE_FAILURE_CONFIG_MODE()\n    else()\n      if(NOT VERSION_OK)\n        _FPHSA_FAILURE_MESSAGE(\"${FPHSA_FAIL_MESSAGE}: ${VERSION_MSG} (found ${${_FIRST_REQUIRED_VAR}})\")\n      else()\n        _FPHSA_FAILURE_MESSAGE(\"${FPHSA_FAIL_MESSAGE} (missing:${MISSING_VARS}) ${VERSION_MSG}\")\n      endif()\n    endif()\n\n  endif ()\n\n  set(${_NAME}_FOUND ${${_NAME}_FOUND} PARENT_SCOPE)\n  set(${_NAME_UPPER}_FOUND ${${_NAME}_FOUND} PARENT_SCOPE)\nendfunction()\n"
  },
  {
    "path": "cmake/Modules_CUDA_fix/FindPackageMessage.cmake",
    "content": "# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying\n# file Copyright.txt or https://cmake.org/licensing for details.\n\n#.rst:\n# FindPackageMessage\n# ------------------\n#\n#\n#\n# FIND_PACKAGE_MESSAGE(<name> \"message for user\" \"find result details\")\n#\n# This macro is intended to be used in FindXXX.cmake modules files.  It\n# will print a message once for each unique find result.  This is useful\n# for telling the user where a package was found.  The first argument\n# specifies the name (XXX) of the package.  The second argument\n# specifies the message to display.  The third argument lists details\n# about the find result so that if they change the message will be\n# displayed again.  The macro also obeys the QUIET argument to the\n# find_package command.\n#\n# Example:\n#\n# ::\n#\n#   if(X11_FOUND)\n#     FIND_PACKAGE_MESSAGE(X11 \"Found X11: ${X11_X11_LIB}\"\n#       \"[${X11_X11_LIB}][${X11_INCLUDE_DIR}]\")\n#   else()\n#    ...\n#   endif()\n\nfunction(FIND_PACKAGE_MESSAGE pkg msg details)\n  # Avoid printing a message repeatedly for the same find result.\n  if(NOT ${pkg}_FIND_QUIETLY)\n    string(REPLACE \"\\n\" \"\" details \"${details}\")\n    set(DETAILS_VAR FIND_PACKAGE_MESSAGE_DETAILS_${pkg})\n    if(NOT \"${details}\" STREQUAL \"${${DETAILS_VAR}}\")\n      # The message has not yet been printed.\n      message(STATUS \"${msg}\")\n\n      # Save the find details in the cache to avoid printing the same\n      # message again.\n      set(\"${DETAILS_VAR}\" \"${details}\"\n        CACHE INTERNAL \"Details about finding ${pkg}\")\n    endif()\n  endif()\nendfunction()\n"
  },
  {
    "path": "cmake/Modules_CUDA_fix/README.txt",
    "content": "This folder contains fixes that are introduced in later versions of\ncmake but cause generator expression errors in earlier cmake versions.\nSpecifically:\n\n(1) a problem where a generator expression for include directories was\npassed to NVCC, where the generator expression itself was prefixed by -I.\nAs the NNPACK include directory generator expression expands to multiple directories, the second and later ones were not prefixed by -I, causing\nnvcc to return an error. First fixed in CMake 3.7 (see\nKitware/CMake@7ded655f).\n\n(2) Windows VS2017 fixes that allows one to define the ccbin path\ndifferently between earlier versions of Visual Studio and VS2017. First\nintroduced after 3.10.1 master version (see Kitware/CMake@bc88329e).\n\nIf you need to update files under this folder, we recommend you issue PRs\nagainst the CMake mainline branch, and then backport it here for earlier\nCMake compatibility.\n"
  },
  {
    "path": "cmake/ProtoBuf.cmake",
    "content": "# Finds Google Protocol Buffers library and compilers and extends\n# the standard cmake script with version and python generation support\nmacro(custom_protobuf_find)\n  message(STATUS \"Use custom protobuf build.\")\n  option(protobuf_BUILD_TESTS \"\" OFF)\n  option(protobuf_BUILD_EXAMPLES \"\" OFF)\n  if (APPLE)\n    # Protobuf generated files triggers a deprecated atomic operation warning\n    # so we turn it off here.\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations\")\n  endif()\n  # If we are building Caffe2 as shared libs, we will also build protobuf as\n  # shared libs.\n  set(protobuf_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})\n  # We will make sure that protobuf and caffe2 uses the same msvc runtime.\n  set(protobuf_MSVC_STATIC_RUNTIME ${CAFFE2_USE_MSVC_STATIC_RUNTIME})\n  if (MSVC AND BUILD_SHARED_LIBS)\n    add_definitions(-DPROTOBUF_USE_DLLS)\n  endif()\n  add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/protobuf/cmake)\n\n  # Protobuf \"namespaced\" target is only added post protobuf 3.5.1. As a\n  # result, for older versions, we will manually add alias.\n  if (NOT TARGET protobuf::libprotobuf)\n    add_library(protobuf::libprotobuf ALIAS libprotobuf)\n    add_library(protobuf::libprotobuf-lite ALIAS libprotobuf-lite)\n    add_executable(protobuf::protoc ALIAS protoc)\n  endif()\nendmacro()\n\n# Main entry for protobuf. If we are building on Android, iOS or we have hard\n# coded BUILD_CUSTOM_PROTOBUF, we will hard code the use of custom protobuf\n# in the submodule.\nif (ANDROID OR IOS)\n  message(STATUS\n      \"For Android and iOS cross compilation, I am automatically using \"\n      \"custom protobuf under third party.\")\n  custom_protobuf_find()\n  # Unfortunately, new protobuf does not support libprotoc and protoc\n  # cross-compilation so we will need to exclude it.\n  # The problem of using EXCLUDE_FROM_ALL is that one is not going to be able\n  # to run cmake install. A proper solution has to be implemented by protobuf\n  # since we derive our cmake files from there.\n  # TODO(jiayq): change this once https://github.com/google/protobuf/pull/3878\n  # merges.\n  set_target_properties(\n      libprotoc protoc PROPERTIES\n      EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1)\nelseif (BUILD_CUSTOM_PROTOBUF)\n  message(STATUS \"Building using own protobuf under third_party per request.\")\n  custom_protobuf_find()\nelse()\n  include(cmake/public/protobuf.cmake)\nendif()\n\nif ((NOT TARGET protobuf::libprotobuf) AND (NOT TARGET protobuf::libprotobuf-lite))\n  message(WARNING\n      \"Protobuf cannot be found. Caffe2 will automatically switch to use \"\n      \"own protobuf under third_party. Note that this behavior may change in \"\n      \"the future, and you will need to specify -DBUILD_CUSTOM_PROTOBUF=ON \"\n      \"explicitly.\")\n  custom_protobuf_find()\n\n  # TODO(jiayq): enable this in the future, when Jenkins Mac support is\n  # properly set up with protobuf installs.\n\n  # message(FATAL_ERROR\n  #     \"Protobuf cannot be found. Caffe2 will have to build with libprotobuf. \"\n  #     \"Please set the proper paths so that I can find protobuf correctly.\")\nendif()\n\n# TODO: enable using lite protobuf.\nlist(APPEND Caffe2_PUBLIC_DEPENDENCY_LIBS protobuf::libprotobuf)\n\n# Protobuf generated files use <> as inclusion path, so following normal\n# convention we will use SYSTEM inclusion path.\nget_target_property(__tmp protobuf::libprotobuf INTERFACE_INCLUDE_DIRECTORIES)\nmessage(STATUS \"Caffe2 protobuf include directory: \" ${__tmp})\ninclude_directories(SYSTEM ${__tmp})\n\n# If Protobuf_VERSION is known (true in most cases, false if we are building\n# local protobuf), then we will add a protobuf version check in\n# Caffe2Config.cmake.in.\nif (DEFINED ${Protobuf_VERSION})\n  set(CAFFE2_KNOWN_PROTOBUF_VERSION TRUE)\nelse()\n  set(CAFFE2_KNOWN_PROTOBUF_VERSION FALSE)\n  set(Protobuf_VERSION \"Protobuf_VERSION_NOTFOUND\")\nendif()\n\n\n# Figure out which protoc to use.\n# If CAFFE2_CUSTOM_PROTOC_EXECUTABLE is set, we assume the user knows\n# what they're doing and we blindly use the specified protoc. This\n# is typically the case when cross-compiling where protoc must be\n# compiled for the host architecture and libprotobuf must be\n# compiled for the target architecture.\n# If CAFFE2_CUSTOM_PROTOC_EXECUTABLE is NOT set, we use the protoc\n# target that is built as part of including the protobuf project.\nif(EXISTS \"${CAFFE2_CUSTOM_PROTOC_EXECUTABLE}\")\n  set(CAFFE2_PROTOC_EXECUTABLE ${CAFFE2_CUSTOM_PROTOC_EXECUTABLE})\nelse()\n  set(CAFFE2_PROTOC_EXECUTABLE protobuf::protoc)\nendif()\n\n################################################################################################\n# Modification of standard 'protobuf_generate_cpp()' with output dir parameter and python support\n# Usage:\n#   caffe2_protobuf_generate_cpp_py(<srcs_var> <hdrs_var> <python_var> <proto_files>)\nfunction(caffe2_protobuf_generate_cpp_py srcs_var hdrs_var python_var)\n  if(NOT ARGN)\n    message(SEND_ERROR \"Error: caffe_protobuf_generate_cpp_py() called without any proto files\")\n    return()\n  endif()\n\n  set(${srcs_var})\n  set(${hdrs_var})\n  set(${python_var})\n  foreach(fil ${ARGN})\n    get_filename_component(abs_fil ${fil} ABSOLUTE)\n    get_filename_component(fil_we ${fil} NAME_WE)\n\n    list(APPEND ${srcs_var} \"${CMAKE_CURRENT_BINARY_DIR}/${fil_we}.pb.cc\")\n    list(APPEND ${hdrs_var} \"${CMAKE_CURRENT_BINARY_DIR}/${fil_we}.pb.h\")\n    list(APPEND ${python_var} \"${CMAKE_CURRENT_BINARY_DIR}/${fil_we}_pb2.py\")\n\n    # Note: the following depends on PROTOBUF_PROTOC_EXECUTABLE. This\n    # is done to make sure protoc is built before attempting to\n    # generate sources if we're using protoc from the third_party\n    # directory and are building it as part of the Caffe2 build. If\n    # points to an existing path, it is a no-op.\n    if (MSVC)\n      set(DLLEXPORT_STR \"dllexport_decl=CAFFE2_API:\")\n    else()\n      set(DLLEXPORT_STR \"\")\n    endif()\n    add_custom_command(\n      OUTPUT \"${CMAKE_CURRENT_BINARY_DIR}/${fil_we}.pb.cc\"\n             \"${CMAKE_CURRENT_BINARY_DIR}/${fil_we}.pb.h\"\n             \"${CMAKE_CURRENT_BINARY_DIR}/${fil_we}_pb2.py\"\n      WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}\n      COMMAND ${CMAKE_COMMAND} -E make_directory \"${CMAKE_CURRENT_BINARY_DIR}\"\n      COMMAND ${CAFFE2_PROTOC_EXECUTABLE} -I${PROJECT_SOURCE_DIR} --cpp_out=${DLLEXPORT_STR}${PROJECT_BINARY_DIR} ${abs_fil}\n      COMMAND ${CAFFE2_PROTOC_EXECUTABLE} -I${PROJECT_SOURCE_DIR} --python_out \"${PROJECT_BINARY_DIR}\" ${abs_fil}\n      DEPENDS ${CAFFE2_PROTOC_EXECUTABLE} ${abs_fil}\n      COMMENT \"Running C++/Python protocol buffer compiler on ${fil}\" VERBATIM )\n  endforeach()\n\n  set_source_files_properties(${${srcs_var}} ${${hdrs_var}} ${${python_var}} PROPERTIES GENERATED TRUE)\n  set(${srcs_var} ${${srcs_var}} PARENT_SCOPE)\n  set(${hdrs_var} ${${hdrs_var}} PARENT_SCOPE)\n  set(${python_var} ${${python_var}} PARENT_SCOPE)\nendfunction()\n\n\n"
  },
  {
    "path": "cmake/Summary.cmake",
    "content": "# Prints accumulated Caffe2 configuration summary\nfunction (caffe2_print_configuration_summary)\n  message(STATUS \"\")\n  message(STATUS \"******** Summary ********\")\n  message(STATUS \"General:\")\n  message(STATUS \"  CMake version         : ${CMAKE_VERSION}\")\n  message(STATUS \"  CMake command         : ${CMAKE_COMMAND}\")\n  message(STATUS \"  Git version           : ${CAFFE2_GIT_VERSION}\")\n  message(STATUS \"  System                : ${CMAKE_SYSTEM_NAME}\")\n  message(STATUS \"  C++ compiler          : ${CMAKE_CXX_COMPILER}\")\n  message(STATUS \"  C++ compiler version  : ${CMAKE_CXX_COMPILER_VERSION}\")\n  message(STATUS \"  Protobuf compiler     : ${PROTOBUF_PROTOC_EXECUTABLE}\")\n  message(STATUS \"  Protobuf include path : ${PROTOBUF_INCLUDE_DIRS}\")\n  message(STATUS \"  Protobuf libraries    : ${PROTOBUF_LIBRARIES}\")\n  message(STATUS \"  BLAS                  : ${BLAS}\")\n  message(STATUS \"  CXX flags             : ${CMAKE_CXX_FLAGS}\")\n  message(STATUS \"  Build type            : ${CMAKE_BUILD_TYPE}\")\n  get_directory_property(tmp DIRECTORY ${PROJECT_SOURCE_DIR} COMPILE_DEFINITIONS)\n  message(STATUS \"  Compile definitions   : ${tmp}\")\n  message(STATUS \"\")\n\n  message(STATUS \"  BUILD_BINARY          : ${BUILD_BINARY}\")\n  message(STATUS \"  BUILD_DOCS            : ${BUILD_DOCS}\")\n  message(STATUS \"  BUILD_PYTHON          : ${BUILD_PYTHON}\")\n  if (${BUILD_PYTHON})\n    message(STATUS \"    Python version      : ${PYTHONLIBS_VERSION_STRING}\")\n    message(STATUS \"    Python includes     : ${PYTHON_INCLUDE_DIRS}\")\n  endif()\n  message(STATUS \"  BUILD_SHARED_LIBS     : ${BUILD_SHARED_LIBS}\")\n  message(STATUS \"  BUILD_TEST            : ${BUILD_TEST}\")\n\n  message(STATUS \"  USE_ATEN              : ${USE_ATEN}\")\n  message(STATUS \"  USE_ASAN              : ${USE_ASAN}\")\n  message(STATUS \"  USE_CUDA              : ${USE_CUDA}\")\n  if(${USE_CUDA})\n    message(STATUS \"    CUDA version        : ${CUDA_VERSION}\")\n    message(STATUS \"    CuDNN version       : ${CUDNN_VERSION}\")\n    message(STATUS \"    CUDA root directory : ${CUDA_TOOLKIT_ROOT_DIR}\")\n    message(STATUS \"    CUDA library        : ${CUDA_CUDA_LIB}\")\n    message(STATUS \"    CUDA NVRTC library  : ${CUDA_NVRTC_LIB}\")\n    message(STATUS \"    CUDA runtime library: ${CUDA_CUDART_LIBRARY}\")\n    message(STATUS \"    CUDA include path   : ${CUDA_INCLUDE_DIRS}\")\n    message(STATUS \"    NVCC executable     : ${CUDA_NVCC_EXECUTABLE}\")\n    message(STATUS \"    CUDA host compiler  : ${CUDA_HOST_COMPILER}\")\n  endif()\n  message(STATUS \"  USE_EIGEN_FOR_BLAS    : ${CAFFE2_USE_EIGEN_FOR_BLAS}\")\n  message(STATUS \"  USE_FFMPEG            : ${USE_FFMPEG}\")\n  message(STATUS \"  USE_GFLAGS            : ${USE_GFLAGS}\")\n  message(STATUS \"  USE_GLOG              : ${USE_GLOG}\")\n  message(STATUS \"  USE_GLOO              : ${USE_GLOO}\")\n  message(STATUS \"  USE_LEVELDB           : ${USE_LEVELDB}\")\n  if(${USE_LEVELDB})\n    message(STATUS \"    LevelDB version     : ${LEVELDB_VERSION}\")\n    message(STATUS \"    Snappy version      : ${Snappy_VERSION}\")\n  endif()\n  message(STATUS \"  USE_LITE_PROTO        : ${USE_LITE_PROTO}\")\n  message(STATUS \"  USE_LMDB              : ${USE_LMDB}\")\n  if(${USE_LMDB})\n    message(STATUS \"    LMDB version        : ${LMDB_VERSION}\")\n  endif()\n  message(STATUS \"  USE_METAL             : ${USE_METAL}\")\n  message(STATUS \"  USE_MKL               : ${CAFFE2_USE_MKL}\")\n  message(STATUS \"  USE_MOBILE_OPENGL     : ${USE_MOBILE_OPENGL}\")\n  message(STATUS \"  USE_MPI               : ${USE_MPI}\")\n  message(STATUS \"  USE_NCCL              : ${USE_NCCL}\")\n  message(STATUS \"  USE_NERVANA_GPU       : ${USE_NERVANA_GPU}\")\n  if(${USE_NERVANA_GPU})\n    message(STATUS \"    NERVANA_GPU version : ${NERVANA_GPU_VERSION}\")\n  endif()\n  message(STATUS \"  USE_NNPACK            : ${USE_NNPACK}\")\n  message(STATUS \"  USE_OBSERVERS         : ${USE_OBSERVERS}\")\n  message(STATUS \"  USE_OPENCV            : ${USE_OPENCV}\")\n  if(${USE_OPENCV})\n    message(STATUS \"    OpenCV version      : ${OpenCV_VERSION}\")\n  endif()\n  message(STATUS \"  USE_OPENMP            : ${USE_OPENMP}\")\n  message(STATUS \"  USE_PROF              : ${USE_PROF}\")\n  message(STATUS \"  USE_REDIS             : ${USE_REDIS}\")\n  message(STATUS \"  USE_ROCKSDB           : ${USE_ROCKSDB}\")\n  message(STATUS \"  USE_THREADS           : ${USE_THREADS}\")\n  message(STATUS \"  USE_ZMQ               : ${USE_ZMQ}\")\nendfunction()\n"
  },
  {
    "path": "cmake/Utils.cmake",
    "content": "################################################################################################\n# Exclude and prepend functionalities\nfunction (exclude OUTPUT INPUT)\nset(EXCLUDES ${ARGN})\nforeach(EXCLUDE ${EXCLUDES})\n        list(REMOVE_ITEM INPUT \"${EXCLUDE}\")\nendforeach()\nset(${OUTPUT} ${INPUT} PARENT_SCOPE)\nendfunction(exclude)\n\nfunction (prepend OUTPUT PREPEND)\nset(OUT \"\")\nforeach(ITEM ${ARGN})\n        list(APPEND OUT \"${PREPEND}${ITEM}\")\nendforeach()\nset(${OUTPUT} ${OUT} PARENT_SCOPE)\nendfunction(prepend)\n\n\n################################################################################################\n# Clears variables from list\n# Usage:\n#   caffe_clear_vars(<variables_list>)\nmacro(caffe_clear_vars)\n  foreach(_var ${ARGN})\n    unset(${_var})\n  endforeach()\nendmacro()\n\n################################################################################################\n# Prints list element per line\n# Usage:\n#   caffe_print_list(<list>)\nfunction(caffe_print_list)\n  foreach(e ${ARGN})\n    message(STATUS ${e})\n  endforeach()\nendfunction()\n\n################################################################################################\n# Reads set of version defines from the header file\n# Usage:\n#   caffe_parse_header(<file> <define1> <define2> <define3> ..)\nmacro(caffe_parse_header FILENAME FILE_VAR)\n  set(vars_regex \"\")\n  set(__parnet_scope OFF)\n  set(__add_cache OFF)\n  foreach(name ${ARGN})\n    if(\"${name}\" STREQUAL \"PARENT_SCOPE\")\n      set(__parnet_scope ON)\n    elseif(\"${name}\" STREQUAL \"CACHE\")\n      set(__add_cache ON)\n    elseif(vars_regex)\n      set(vars_regex \"${vars_regex}|${name}\")\n    else()\n      set(vars_regex \"${name}\")\n    endif()\n  endforeach()\n  if(EXISTS \"${FILENAME}\")\n    file(STRINGS \"${FILENAME}\" ${FILE_VAR} REGEX \"#define[ \\t]+(${vars_regex})[ \\t]+[0-9]+\" )\n  else()\n    unset(${FILE_VAR})\n  endif()\n  foreach(name ${ARGN})\n    if(NOT \"${name}\" STREQUAL \"PARENT_SCOPE\" AND NOT \"${name}\" STREQUAL \"CACHE\")\n      if(${FILE_VAR})\n        if(${FILE_VAR} MATCHES \".+[ \\t]${name}[ \\t]+([0-9]+).*\")\n          string(REGEX REPLACE \".+[ \\t]${name}[ \\t]+([0-9]+).*\" \"\\\\1\" ${name} \"${${FILE_VAR}}\")\n        else()\n          set(${name} \"\")\n        endif()\n        if(__add_cache)\n          set(${name} ${${name}} CACHE INTERNAL \"${name} parsed from ${FILENAME}\" FORCE)\n        elseif(__parnet_scope)\n          set(${name} \"${${name}}\" PARENT_SCOPE)\n        endif()\n      else()\n        unset(${name} CACHE)\n      endif()\n    endif()\n  endforeach()\nendmacro()\n\n################################################################################################\n# Reads single version define from the header file and parses it\n# Usage:\n#   caffe_parse_header_single_define(<library_name> <file> <define_name>)\nfunction(caffe_parse_header_single_define LIBNAME HDR_PATH VARNAME)\n  set(${LIBNAME}_H \"\")\n  if(EXISTS \"${HDR_PATH}\")\n    file(STRINGS \"${HDR_PATH}\" ${LIBNAME}_H REGEX \"^#define[ \\t]+${VARNAME}[ \\t]+\\\"[^\\\"]*\\\".*$\" LIMIT_COUNT 1)\n  endif()\n\n  if(${LIBNAME}_H)\n    string(REGEX REPLACE \"^.*[ \\t]${VARNAME}[ \\t]+\\\"([0-9]+).*$\" \"\\\\1\" ${LIBNAME}_VERSION_MAJOR \"${${LIBNAME}_H}\")\n    string(REGEX REPLACE \"^.*[ \\t]${VARNAME}[ \\t]+\\\"[0-9]+\\\\.([0-9]+).*$\" \"\\\\1\" ${LIBNAME}_VERSION_MINOR  \"${${LIBNAME}_H}\")\n    string(REGEX REPLACE \"^.*[ \\t]${VARNAME}[ \\t]+\\\"[0-9]+\\\\.[0-9]+\\\\.([0-9]+).*$\" \"\\\\1\" ${LIBNAME}_VERSION_PATCH \"${${LIBNAME}_H}\")\n    set(${LIBNAME}_VERSION_MAJOR ${${LIBNAME}_VERSION_MAJOR} ${ARGN} PARENT_SCOPE)\n    set(${LIBNAME}_VERSION_MINOR ${${LIBNAME}_VERSION_MINOR} ${ARGN} PARENT_SCOPE)\n    set(${LIBNAME}_VERSION_PATCH ${${LIBNAME}_VERSION_PATCH} ${ARGN} PARENT_SCOPE)\n    set(${LIBNAME}_VERSION_STRING \"${${LIBNAME}_VERSION_MAJOR}.${${LIBNAME}_VERSION_MINOR}.${${LIBNAME}_VERSION_PATCH}\" PARENT_SCOPE)\n\n    # append a TWEAK version if it exists:\n    set(${LIBNAME}_VERSION_TWEAK \"\")\n    if(\"${${LIBNAME}_H}\" MATCHES \"^.*[ \\t]${VARNAME}[ \\t]+\\\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.([0-9]+).*$\")\n      set(${LIBNAME}_VERSION_TWEAK \"${CMAKE_MATCH_1}\" ${ARGN} PARENT_SCOPE)\n    endif()\n    if(${LIBNAME}_VERSION_TWEAK)\n      set(${LIBNAME}_VERSION_STRING \"${${LIBNAME}_VERSION_STRING}.${${LIBNAME}_VERSION_TWEAK}\" ${ARGN} PARENT_SCOPE)\n    else()\n      set(${LIBNAME}_VERSION_STRING \"${${LIBNAME}_VERSION_STRING}\" ${ARGN} PARENT_SCOPE)\n    endif()\n  endif()\nendfunction()\n\n##############################################################################\n# Helper function to automatically generate __init__.py files where python\n# sources reside but there are no __init__.py present.\nfunction(caffe_autogen_init_py_files)\n  file(GLOB_RECURSE all_python_files RELATIVE ${PROJECT_SOURCE_DIR}\n       \"${PROJECT_SOURCE_DIR}/caffe2/*.py\")\n  set(python_paths_need_init_py)\n  foreach(python_file ${all_python_files})\n    get_filename_component(python_path ${python_file} PATH)\n    string(REPLACE \"/\" \";\" path_parts ${python_path})\n    set(rebuilt_path ${CMAKE_BINARY_DIR})\n    foreach(path_part ${path_parts})\n      set(rebuilt_path \"${rebuilt_path}/${path_part}\")\n      list(APPEND python_paths_need_init_py ${rebuilt_path})\n    endforeach()\n  endforeach()\n  list(REMOVE_DUPLICATES python_paths_need_init_py)\n  # Since the _pb2.py files are yet to be created, we will need to manually\n  # add them to the list.\n  list(APPEND python_paths_need_init_py ${CMAKE_BINARY_DIR}/caffe)\n  list(APPEND python_paths_need_init_py ${CMAKE_BINARY_DIR}/caffe/proto)\n  list(APPEND python_paths_need_init_py ${CMAKE_BINARY_DIR}/caffe2/proto)\n\n  foreach(tmp ${python_paths_need_init_py})\n    if(NOT EXISTS ${tmp}/__init__.py)\n      # message(STATUS \"Generate \" ${tmp}/__init__.py)\n      file(WRITE ${tmp}/__init__.py \"\")\n    endif()\n  endforeach()\nendfunction()\n\n###\n# Removes common indentation from a block of text to produce code suitable for\n# setting to `python -c`, or using with pycmd. This allows multiline code to be\n# nested nicely in the surrounding code structure.\n#\n# This function respsects PYTHON_EXECUTABLE if it defined, otherwise it uses\n# `python` and hopes for the best. An error will be thrown if it is not found.\n#\n# Args:\n#     outvar : variable that will hold the stdout of the python command\n#     text   : text to remove indentation from\n#\nfunction(dedent outvar text)\n  # Use PYTHON_EXECUTABLE if it is defined, otherwise default to python\n  if (\"${PYTHON_EXECUTABLE}\" STREQUAL \"\")\n    set(_python_exe \"python\")\n  else()\n    set(_python_exe \"${PYTHON_EXECUTABLE}\")\n  endif()\n  set(_fixup_cmd \"import sys; from textwrap import dedent; print(dedent(sys.stdin.read()))\")\n  # Use echo to pipe the text to python's stdinput. This prevents us from\n  # needing to worry about any sort of special escaping.\n  execute_process(\n    COMMAND echo \"${text}\"\n    COMMAND \"${_python_exe}\" -c \"${_fixup_cmd}\"\n    RESULT_VARIABLE _dedent_exitcode\n    OUTPUT_VARIABLE _dedent_text)\n  if(NOT ${_dedent_exitcode} EQUAL 0)\n    message(ERROR \" Failed to remove indentation from: \\n\\\"\\\"\\\"\\n${text}\\n\\\"\\\"\\\"\n    Python dedent failed with error code: ${_dedent_exitcode}\")\n    message(FATAL_ERROR \" Python dedent failed with error code: ${_dedent_exitcode}\")\n  endif()\n  # Remove supurflous newlines (artifacts of print)\n  string(STRIP \"${_dedent_text}\" _dedent_text)\n  set(${outvar} \"${_dedent_text}\" PARENT_SCOPE)\nendfunction()\n\n\n###\n# Helper function to run `python -c \"<cmd>\"` and capture the results of stdout\n#\n# Runs a python command and populates an outvar with the result of stdout.\n# Common indentation in the text of `cmd` is removed before the command is\n# executed, so the caller does not need to worry about indentation issues.\n#\n# This function respsects PYTHON_EXECUTABLE if it defined, otherwise it uses\n# `python` and hopes for the best. An error will be thrown if it is not found.\n#\n# Args:\n#     outvar : variable that will hold the stdout of the python command\n#     cmd    : text representing a (possibly multiline) block of python code\n#\nfunction(pycmd outvar cmd)\n  dedent(_dedent_cmd \"${cmd}\")\n  # Use PYTHON_EXECUTABLE if it is defined, otherwise default to python\n  if (\"${PYTHON_EXECUTABLE}\" STREQUAL \"\")\n    set(_python_exe \"python\")\n  else()\n    set(_python_exe \"${PYTHON_EXECUTABLE}\")\n  endif()\n  # run the actual command\n  execute_process(\n    COMMAND \"${_python_exe}\" -c \"${_dedent_cmd}\"\n    RESULT_VARIABLE _exitcode\n    OUTPUT_VARIABLE _output)\n  if(NOT ${_exitcode} EQUAL 0)\n    message(ERROR \" Failed when running python code: \\\"\\\"\\\"\\n${_dedent_cmd}\\n\\\"\\\"\\\"\")\n    message(FATAL_ERROR \" Python command failed with error code: ${_exitcode}\")\n  endif()\n  # Remove supurflous newlines (artifacts of print)\n  string(STRIP \"${_output}\" _output)\n  set(${outvar} \"${_output}\" PARENT_SCOPE)\nendfunction()\n"
  },
  {
    "path": "cmake/Whitelist.cmake",
    "content": "\nif (__caffe2_whitelist_included)\n  return()\nendif()\n\nset (__caffe2_whitelist_included TRUE)\n\nset(CAFFE2_WHITELISTED_FILES)\nif (NOT CAFFE2_WHITELIST)\n  return()\nendif()\n\n# First read the whitelist file and break it by line.\nfile(READ \"${CAFFE2_WHITELIST}\" whitelist_content)\n# Convert file contents into a CMake list\nstring(REGEX REPLACE \"\\n\" \";\" whitelist_content ${whitelist_content})\n\nforeach(item ${whitelist_content})\n  file(GLOB_RECURSE tmp ${item})\n  set(CAFFE2_WHITELISTED_FILES ${CAFFE2_WHITELISTED_FILES} ${tmp})\nendforeach()\n\nmacro(caffe2_do_whitelist output whitelist)\n  set(_tmp)\n  foreach(item ${${output}})\n    list(FIND ${whitelist} ${item} _index)\n    if (${_index} GREATER -1)\n      set(_tmp ${_tmp} ${item})\n    endif()\n  endforeach()\n  set(${output} ${_tmp})\nendmacro()\n"
  },
  {
    "path": "cmake/cmake_uninstall.cmake.in",
    "content": "if(NOT EXISTS \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\")\n  message(FATAL_ERROR \"Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\")\nendif(NOT EXISTS \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\")\n\nif (NOT DEFINED CMAKE_INSTALL_PREFIX)\n  set (CMAKE_INSTALL_PREFIX \"@CMAKE_INSTALL_PREFIX@\")\nendif ()\n message(${CMAKE_INSTALL_PREFIX})\n\nfile(READ \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\" files)\nstring(REGEX REPLACE \"\\n\" \";\" files \"${files}\")\nforeach(file ${files})\n  message(STATUS \"Uninstalling $ENV{DESTDIR}${file}\")\n  if(IS_SYMLINK \"$ENV{DESTDIR}${file}\" OR EXISTS \"$ENV{DESTDIR}${file}\")\n    exec_program(\n      \"@CMAKE_COMMAND@\" ARGS \"-E remove \\\"$ENV{DESTDIR}${file}\\\"\"\n      OUTPUT_VARIABLE rm_out\n      RETURN_VALUE rm_retval\n      )\n    if(NOT \"${rm_retval}\" STREQUAL 0)\n      message(FATAL_ERROR \"Problem when removing $ENV{DESTDIR}${file}\")\n    endif(NOT \"${rm_retval}\" STREQUAL 0)\n  else(IS_SYMLINK \"$ENV{DESTDIR}${file}\" OR EXISTS \"$ENV{DESTDIR}${file}\")\n    message(STATUS \"File $ENV{DESTDIR}${file} does not exist.\")\n  endif(IS_SYMLINK \"$ENV{DESTDIR}${file}\" OR EXISTS \"$ENV{DESTDIR}${file}\")\nendforeach(file)\n"
  },
  {
    "path": "cmake/public/cuda.cmake",
    "content": "# ---[ cuda\n\nset(CAFFE2_FOUND_CUDA FALSE)\n\n# Find Cuda.\nfind_package(CUDA 7.0)\nif(NOT CUDA_FOUND)\n  message(WARNING\n      \"Caffe2: Cuda cannot be found. Depending on whether you are building \"\n      \"Caffe2 or a Caffe2 dependent library, the next warning / error will \"\n      \"give you more info.\")\n  return()\nendif()\n\n# Find cudnn.\ninclude(FindPackageHandleStandardArgs)\nset(CUDNN_ROOT_DIR \"\" CACHE PATH \"Folder contains NVIDIA cuDNN\")\nfind_path(CUDNN_INCLUDE_DIR cudnn.h\n    HINTS ${CUDNN_ROOT_DIR} ${CUDA_TOOLKIT_ROOT_DIR}\n    PATH_SUFFIXES cuda/include include)\nfind_library(CUDNN_LIBRARY cudnn\n    HINTS ${CUDNN_ROOT_DIR} ${CUDA_TOOLKIT_ROOT_DIR}\n    PATH_SUFFIXES lib lib64 cuda/lib cuda/lib64 lib/x64)\nfind_package_handle_standard_args(\n    CUDNN DEFAULT_MSG CUDNN_INCLUDE_DIR CUDNN_LIBRARY)\n\nif(NOT CUDNN_FOUND)\n  message(WARNING\n      \"Caffe2: cudnn cannot be found. Caffe2 CUDA depends explicitly \"\n      \"on cudnn so you should consider installing it.\")\n  return()\nendif()\n\n# After both cuda and cudnn are found, we can safely proceed.\nset(CAFFE2_FOUND_CUDA TRUE)\nmessage(STATUS \"Caffe2: CUDA detected: \" ${CUDA_VERSION})\n# get cuDNN version\nfile(READ ${CUDNN_INCLUDE_DIR}/cudnn.h CUDNN_HEADER_CONTENTS)\nstring(REGEX MATCH \"define CUDNN_MAJOR * +([0-9]+)\"\n             CUDNN_VERSION_MAJOR \"${CUDNN_HEADER_CONTENTS}\")\nstring(REGEX REPLACE \"define CUDNN_MAJOR * +([0-9]+)\" \"\\\\1\"\n             CUDNN_VERSION_MAJOR \"${CUDNN_VERSION_MAJOR}\")\nstring(REGEX MATCH \"define CUDNN_MINOR * +([0-9]+)\"\n             CUDNN_VERSION_MINOR \"${CUDNN_HEADER_CONTENTS}\")\nstring(REGEX REPLACE \"define CUDNN_MINOR * +([0-9]+)\" \"\\\\1\"\n             CUDNN_VERSION_MINOR \"${CUDNN_VERSION_MINOR}\")\nstring(REGEX MATCH \"define CUDNN_PATCHLEVEL * +([0-9]+)\"\n             CUDNN_VERSION_PATCH \"${CUDNN_HEADER_CONTENTS}\")\nstring(REGEX REPLACE \"define CUDNN_PATCHLEVEL * +([0-9]+)\" \"\\\\1\"\n             CUDNN_VERSION_PATCH \"${CUDNN_VERSION_PATCH}\")\n# Assemble cuDNN version\nif(NOT CUDNN_VERSION_MAJOR)\n  set(CUDNN_VERSION \"?\")\nelse()\n  set(CUDNN_VERSION\n      \"${CUDNN_VERSION_MAJOR}.${CUDNN_VERSION_MINOR}.${CUDNN_VERSION_PATCH}\")\nendif()\n\nmessage(STATUS \"Found cuDNN: v${CUDNN_VERSION}  (include: ${CUDNN_INCLUDE_DIR}, library: ${CUDNN_LIBRARY})\")\n# ---[ Cuda Libraries wrapper\n\n# find libcuda.so and lbnvrtc.so\n# For libcuda.so, we will find it under lib, lib64, and then the\n# stubs folder, in case we are building on a system that does not\n# have cuda driver installed. On windows, we also search under the\n# folder lib/x64.\nfind_library(CUDA_CUDA_LIB cuda\n    PATHS ${CUDA_TOOLKIT_ROOT_DIR}\n    PATH_SUFFIXES lib lib64 lib/stubs lib64/stubs lib/x64)\nfind_library(CUDA_NVRTC_LIB nvrtc\n    PATHS ${CUDA_TOOLKIT_ROOT_DIR}\n    PATH_SUFFIXES lib lib64 lib/x64)\n\n# Create new style imported libraries.\n\n# cuda\nadd_library(caffe2::cuda UNKNOWN IMPORTED)\nset_property(\n    TARGET caffe2::cuda PROPERTY IMPORTED_LOCATION\n    ${CUDA_CUDA_LIB})\nset_property(\n    TARGET caffe2::cuda PROPERTY INTERFACE_INCLUDE_DIRECTORIES\n    ${CUDA_INCLUDE_DIRS})\n\n# cudart. CUDA_LIBRARIES is actually a list, so we will make an interface\n# library.\nadd_library(caffe2::cudart INTERFACE IMPORTED)\nset_property(\n    TARGET caffe2::cudart PROPERTY INTERFACE_LINK_LIBRARIES\n    ${CUDA_LIBRARIES})\nset_property(\n    TARGET caffe2::cudart PROPERTY INTERFACE_INCLUDE_DIRECTORIES\n    ${CUDA_INCLUDE_DIRS})\n\n# cudnn\nadd_library(caffe2::cudnn UNKNOWN IMPORTED)\nset_property(\n    TARGET caffe2::cudnn PROPERTY IMPORTED_LOCATION\n    ${CUDNN_LIBRARY})\nset_property(\n    TARGET caffe2::cudnn PROPERTY INTERFACE_INCLUDE_DIRECTORIES\n    ${CUDNN_INCLUDE_DIR})\n\n# curand\nadd_library(caffe2::curand UNKNOWN IMPORTED)\nset_property(\n    TARGET caffe2::curand PROPERTY IMPORTED_LOCATION\n    ${CUDA_curand_LIBRARY})\nset_property(\n    TARGET caffe2::curand PROPERTY INTERFACE_INCLUDE_DIRECTORIES\n    ${CUDA_INCLUDE_DIRS})\n\n# cublas. CUDA_CUBLAS_LIBRARIES is actually a list, so we will make an\n# interface library similar to cudart.\nadd_library(caffe2::cublas INTERFACE IMPORTED)\nset_property(\n    TARGET caffe2::cublas PROPERTY INTERFACE_LINK_LIBRARIES\n    ${CUDA_CUBLAS_LIBRARIES})\nset_property(\n    TARGET caffe2::cublas PROPERTY INTERFACE_INCLUDE_DIRECTORIES\n    ${CUDA_INCLUDE_DIRS})\n\n# nvrtc\nadd_library(caffe2::nvrtc UNKNOWN IMPORTED)\nset_property(\n    TARGET caffe2::nvrtc PROPERTY IMPORTED_LOCATION\n    ${CUDA_NVRTC_LIB})\nset_property(\n    TARGET caffe2::nvrtc PROPERTY INTERFACE_INCLUDE_DIRECTORIES\n    ${CUDA_INCLUDE_DIRS})\n\n# Note: in theory, we can add similar dependent library wrappers. For\n# now, Caffe2 only uses the above libraries, so we will only wrap\n# these.\n\n# ---[ Cuda flags\n\n# Known NVIDIA GPU achitectures Caffe2 can be compiled for.\n# Default is set to cuda 9. If we detect the cuda architectores to be less than\n# 9, we will lower it to the corresponding known archs.\nset(Caffe2_known_gpu_archs \"30 35 50 52 60 61 70\") # for CUDA 9.x\nset(Caffe2_known_gpu_archs8 \"20 21(20) 30 35 50 52 60 61\") # for CUDA 8.x\nset(Caffe2_known_gpu_archs7 \"20 21(20) 30 35 50 52\") # for CUDA 7.x\n\n################################################################################################\n# A function for automatic detection of GPUs installed  (if autodetection is enabled)\n# Usage:\n#   caffe2_detect_installed_gpus(out_variable)\nfunction(caffe2_detect_installed_gpus out_variable)\n  if(NOT CUDA_gpu_detect_output)\n    set(__cufile ${PROJECT_BINARY_DIR}/detect_cuda_archs.cu)\n\n    file(WRITE ${__cufile} \"\"\n      \"#include <cstdio>\\n\"\n      \"int main()\\n\"\n      \"{\\n\"\n      \"  int count = 0;\\n\"\n      \"  if (cudaSuccess != cudaGetDeviceCount(&count)) return -1;\\n\"\n      \"  if (count == 0) return -1;\\n\"\n      \"  for (int device = 0; device < count; ++device)\\n\"\n      \"  {\\n\"\n      \"    cudaDeviceProp prop;\\n\"\n      \"    if (cudaSuccess == cudaGetDeviceProperties(&prop, device))\\n\"\n      \"      std::printf(\\\"%d.%d \\\", prop.major, prop.minor);\\n\"\n      \"  }\\n\"\n      \"  return 0;\\n\"\n      \"}\\n\")\n\n    execute_process(COMMAND \"${CUDA_NVCC_EXECUTABLE}\" \"-ccbin=${CUDA_HOST_COMPILER}\" ${CUDA_NVCC_FLAGS} \"--run\" \"${__cufile}\"\n                    WORKING_DIRECTORY \"${PROJECT_BINARY_DIR}/CMakeFiles/\"\n                    RESULT_VARIABLE __nvcc_res OUTPUT_VARIABLE __nvcc_out\n                    ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)\n\n    if(__nvcc_res EQUAL 0)\n      string(REPLACE \"2.1\" \"2.1(2.0)\" __nvcc_out \"${__nvcc_out}\")\n      set(CUDA_gpu_detect_output ${__nvcc_out} CACHE INTERNAL \"Returned GPU architetures from caffe2_detect_installed_gpus tool\" FORCE)\n    endif()\n  endif()\n\n  if(NOT CUDA_gpu_detect_output)\n    message(STATUS \"Automatic GPU detection failed. Building for all known architectures.\")\n    set(${out_variable} ${Caffe2_known_gpu_archs} PARENT_SCOPE)\n  else()\n    message(STATUS \"Automatic GPU detection returned ${CUDA_gpu_detect_output}.\")\n    set(${out_variable} ${CUDA_gpu_detect_output} PARENT_SCOPE)\n  endif()\nendfunction()\n\n\n################################################################################################\n# Function for selecting GPU arch flags for nvcc based on CUDA_ARCH_NAME\n# Usage:\n#   caffe_select_nvcc_arch_flags(out_variable)\nfunction(caffe2_select_nvcc_arch_flags out_variable)\n  # List of arch names\n  set(__archs_names \"Kepler\" \"Maxwell\" \"Pascal\" \"Volta\" \"All\" \"Manual\")\n  set(__archs_name_default \"All\")\n  if(NOT CMAKE_CROSSCOMPILING)   \n    list(APPEND __archs_names \"Auto\")   \n    set(__archs_name_default \"Auto\")    \n  endif()\n\n  # Set CUDA_ARCH_NAME strings (so it will be seen as dropbox in the CMake GUI)\n  set(CUDA_ARCH_NAME ${__archs_name_default} CACHE STRING \"Select target NVIDIA GPU architecture.\")\n  set_property(CACHE CUDA_ARCH_NAME PROPERTY STRINGS \"\" ${__archs_names})\n  mark_as_advanced(CUDA_ARCH_NAME)\n\n  # Verify CUDA_ARCH_NAME value\n  if(NOT \";${__archs_names};\" MATCHES \";${CUDA_ARCH_NAME};\")\n    string(REPLACE \";\" \", \" __archs_names \"${__archs_names}\")\n    message(FATAL_ERROR \"Invalid CUDA_ARCH_NAME, supported values: ${__archs_names}. Got ${CUDA_ARCH_NAME}.\")\n  endif()\n\n  if(${CUDA_ARCH_NAME} STREQUAL \"Manual\")\n    set(CUDA_ARCH_BIN \"\" CACHE STRING\n      \"Specify GPU architectures to build binaries for (BIN(PTX) format is supported)\")\n    set(CUDA_ARCH_PTX \"\" CACHE STRING\n      \"Specify GPU architectures to build PTX intermediate code for\")\n    mark_as_advanced(CUDA_ARCH_BIN CUDA_ARCH_PTX)\n  else()\n    unset(CUDA_ARCH_BIN CACHE)\n    unset(CUDA_ARCH_PTX CACHE)\n  endif()\n\n  if(${CUDA_ARCH_NAME} STREQUAL \"Kepler\")\n    set(__cuda_arch_bin \"30 35\")\n  elseif(${CUDA_ARCH_NAME} STREQUAL \"Maxwell\")\n    set(__cuda_arch_bin \"50\")\n  elseif(${CUDA_ARCH_NAME} STREQUAL \"Pascal\")\n    set(__cuda_arch_bin \"60 61\")\n  elseif(${CUDA_ARCH_NAME} STREQUAL \"Volta\")\n    set(__cuda_arch_bin \"70\")\n  elseif(${CUDA_ARCH_NAME} STREQUAL \"All\")\n    set(__cuda_arch_bin ${Caffe2_known_gpu_archs})\n  elseif(${CUDA_ARCH_NAME} STREQUAL \"Manual\")\n    set(__cuda_arch_bin ${CUDA_ARCH_BIN})\n    set(__cuda_arch_ptx ${CUDA_ARCH_PTX})\n  elseif(${CUDA_ARCH_NAME} STREQUAL \"Auto\")\n    caffe2_detect_installed_gpus(__cuda_arch_bin)\n  else()\n    message(FATAL_ERROR \"Invalid CUDA_ARCH_NAME\")\n  endif()\n\n  # Remove dots and convert to lists\n  string(REGEX REPLACE \"\\\\.\" \"\" __cuda_arch_bin \"${__cuda_arch_bin}\")\n  string(REGEX REPLACE \"\\\\.\" \"\" __cuda_arch_ptx \"${__cuda_arch_ptx}\")\n  string(REGEX MATCHALL \"[0-9()]+\" __cuda_arch_bin \"${__cuda_arch_bin}\")\n  string(REGEX MATCHALL \"[0-9]+\"   __cuda_arch_ptx \"${__cuda_arch_ptx}\")\n  list(REMOVE_DUPLICATES __cuda_arch_bin)\n  list(REMOVE_DUPLICATES __cuda_arch_ptx)\n\n  set(__nvcc_flags \"\")\n  set(__nvcc_archs_readable \"\")\n\n  # Tell NVCC to add binaries for the specified GPUs\n  foreach(__arch ${__cuda_arch_bin})\n    if(__arch MATCHES \"([0-9]+)\\\\(([0-9]+)\\\\)\")\n      # User explicitly specified PTX for the concrete BIN\n      list(APPEND __nvcc_flags -gencode arch=compute_${CMAKE_MATCH_2},code=sm_${CMAKE_MATCH_1})\n      list(APPEND __nvcc_archs_readable sm_${CMAKE_MATCH_1})\n    else()\n      # User didn't explicitly specify PTX for the concrete BIN, we assume PTX=BIN\n      list(APPEND __nvcc_flags -gencode arch=compute_${__arch},code=sm_${__arch})\n      list(APPEND __nvcc_archs_readable sm_${__arch})\n    endif()\n  endforeach()\n\n  # Tell NVCC to add PTX intermediate code for the specified architectures\n  foreach(__arch ${__cuda_arch_ptx})\n    list(APPEND __nvcc_flags -gencode arch=compute_${__arch},code=compute_${__arch})\n    list(APPEND __nvcc_archs_readable compute_${__arch})\n  endforeach()\n\n  string(REPLACE \";\" \" \" __nvcc_archs_readable \"${__nvcc_archs_readable}\")\n  set(${out_variable}          ${__nvcc_flags}          PARENT_SCOPE)\n  set(${out_variable}_readable ${__nvcc_archs_readable} PARENT_SCOPE)\nendfunction()\n\n################################################################################################\n###  Non macro section\n################################################################################################\n\n# Special care for windows platform: we know that 32-bit windows does not\n# support cuda.\nif(${CMAKE_SYSTEM_NAME} STREQUAL \"Windows\")\n  if(NOT (CMAKE_SIZEOF_VOID_P EQUAL 8))\n    message(FATAL_ERROR\n            \"CUDA support not available with 32-bit windows. Did you \"\n            \"forget to set Win64 in the generator target?\")\n    return()\n  endif()\nendif()\n\nif (${CUDA_VERSION} LESS 8.0) # CUDA 7.x\n  set(Caffe2_known_gpu_archs ${Caffe2_known_gpu_archs7})\n  list(APPEND CUDA_NVCC_FLAGS \"-D_MWAITXINTRIN_H_INCLUDED\")\n  list(APPEND CUDA_NVCC_FLAGS \"-D__STRICT_ANSI__\")\nelseif (${CUDA_VERSION} LESS 9.0) # CUDA 8.x\n  set(Caffe2_known_gpu_archs ${Caffe2_known_gpu_archs8})\n  list(APPEND CUDA_NVCC_FLAGS \"-D_MWAITXINTRIN_H_INCLUDED\")\n  list(APPEND CUDA_NVCC_FLAGS \"-D__STRICT_ANSI__\")\n  # CUDA 8 may complain that sm_20 is no longer supported. Suppress the\n  # warning for now.\n  list(APPEND CUDA_NVCC_FLAGS \"-Wno-deprecated-gpu-targets\")\nendif()\n\n# CUDA 9.x requires GCC version <= 6\nif ((CUDA_VERSION VERSION_EQUAL   9.0) OR\n    (CUDA_VERSION VERSION_GREATER 9.0  AND CUDA_VERSION VERSION_LESS 10.0))\n  if (CMAKE_C_COMPILER_ID STREQUAL \"GNU\" AND\n      NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 7.0 AND\n      CUDA_HOST_COMPILER STREQUAL CMAKE_C_COMPILER)\n    message(FATAL_ERROR\n      \"CUDA ${CUDA_VERSION} is not compatible with GCC version >= 7. \"\n      \"Use the following option to use another version (for example): \\n\"\n      \"  -DCUDA_HOST_COMPILER=/usr/bin/gcc-6\\n\")\n  endif()\nelseif (CUDA_VERSION VERSION_EQUAL 8.0)\n  # CUDA 8.0 requires GCC version <= 5\n  if (CMAKE_C_COMPILER_ID STREQUAL \"GNU\" AND\n      NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 6.0 AND\n      CUDA_HOST_COMPILER STREQUAL CMAKE_C_COMPILER)\n    message(FATAL_ERROR\n      \"CUDA 8.0 is not compatible with GCC version >= 6. \"\n      \"Use the following option to use another version (for example): \\n\"\n      \"  -DCUDA_HOST_COMPILER=/usr/bin/gcc-5\\n\")\n  endif()\nendif()\n\n# setting nvcc arch flags\ncaffe2_select_nvcc_arch_flags(NVCC_FLAGS_EXTRA)\nlist(APPEND CUDA_NVCC_FLAGS ${NVCC_FLAGS_EXTRA})\nmessage(STATUS \"Added CUDA NVCC flags for: ${NVCC_FLAGS_EXTRA_readable}\")\n\n# disable some nvcc diagnostic that apears in boost, glog, glags, opencv, etc.\nforeach(diag cc_clobber_ignored integer_sign_change useless_using_declaration set_but_not_used)\n  list(APPEND CUDA_NVCC_FLAGS -Xcudafe --diag_suppress=${diag})\nendforeach()\n\n# Set C++11 support\nset(CUDA_PROPAGATE_HOST_FLAGS OFF)\nif (NOT MSVC)\n  list(APPEND CUDA_NVCC_FLAGS \"-std=c++11\")\n  list(APPEND CUDA_NVCC_FLAGS \"-Xcompiler -fPIC\")\nendif()\n\n# Debug and Release symbol support\nif (MSVC)\n  if (${CMAKE_BUILD_TYPE} MATCHES \"Release\")\n    if (${BUILD_SHARED_LIBS})\n      list(APPEND CUDA_NVCC_FLAGS \"-Xcompiler -MD\")\n    else()\n      list(APPEND CUDA_NVCC_FLAGS \"-Xcompiler -MT\")\n    endif()\n  elseif(${CMAKE_BUILD_TYPE} MATCHES \"Debug\")\n    message(FATAL_ERROR\n            \"Caffe2 currently does not support the combination of MSVC, Cuda \"\n            \"and Debug mode. Either set USE_CUDA=OFF or set the build type \"\n            \"to Release\")\n    if (${BUILD_SHARED_LIBS})\n      list(APPEND CUDA_NVCC_FLAGS \"-Xcompiler -MDd\")\n    else()\n      list(APPEND CUDA_NVCC_FLAGS \"-Xcompiler -MTd\")\n    endif()\n  else()\n    message(FATAL_ERROR \"Unknown cmake build type: \" ${CMAKE_BUILD_TYPE})\n  endif()\nendif()\n\n# Set expt-relaxed-constexpr to suppress Eigen warnings\nlist(APPEND CUDA_NVCC_FLAGS \"--expt-relaxed-constexpr\")\n"
  },
  {
    "path": "cmake/public/gflags.cmake",
    "content": "# ---[ gflags\n\n# We will try to use the config mode first, and then manual find.\nfind_package(gflags CONFIG QUIET)\nif (NOT TARGET gflags)\n  find_package(gflags MODULE QUIET)\nendif()\n\nif (TARGET gflags)\n  message(STATUS \"Caffe2: Found gflags with new-style gflags target.\")\nelseif(GFLAGS_FOUND)\n  message(STATUS \"Caffe2: Found gflags with old-style gflag starget.\")\n  add_library(gflags UNKNOWN IMPORTED)\n  set_property(\n      TARGET gflags PROPERTY IMPORTED_LOCATION ${GFLAGS_LIBRARY})\n  set_property(\n      TARGET gflags PROPERTY INTERFACE_INCLUDE_DIRECTORIES\n      ${GFLAGS_INCLUDE_DIR})\nelse()\n  message(STATUS\n      \"Caffe2: Cannot find gflags automatically. Using legacy find.\")\n\n  # - Try to find GFLAGS in the legacy way.\n  #\n  # The following variables are optionally searched for defaults\n  #  GFLAGS_ROOT_DIR: Base directory where all GFLAGS components are found\n  #\n  # The following are set after configuration is done:\n  #  GFLAGS_FOUND\n  #  GFLAGS_INCLUDE_DIRS\n  #  GFLAGS_LIBRARIES\n  #  GFLAGS_LIBRARYRARY_DIRS\n  include(FindPackageHandleStandardArgs)\n  set(GFLAGS_ROOT_DIR \"\" CACHE PATH \"Folder contains Gflags\")\n\n  # We are testing only a couple of files in the include directories\n  if(WIN32)\n    find_path(GFLAGS_INCLUDE_DIR gflags/gflags.h\n        PATHS ${GFLAGS_ROOT_DIR}/src/windows)\n  else()\n    find_path(GFLAGS_INCLUDE_DIR gflags/gflags.h\n        PATHS ${GFLAGS_ROOT_DIR})\n  endif()\n\n  if(WIN32)\n    find_library(GFLAGS_LIBRARY_RELEASE\n        NAMES libgflags\n        PATHS ${GFLAGS_ROOT_DIR}\n        PATH_SUFFIXES Release)\n\n    find_library(GFLAGS_LIBRARY_DEBUG\n        NAMES libgflags-debug\n        PATHS ${GFLAGS_ROOT_DIR}\n        PATH_SUFFIXES Debug)\n    set(GFLAGS_LIBRARY optimized ${GFLAGS_LIBRARY_RELEASE} debug ${GFLAGS_LIBRARY_DEBUG})\n  else()\n    find_library(GFLAGS_LIBRARY gflags)\n  endif()\n\n  find_package_handle_standard_args(\n      gflags DEFAULT_MSG GFLAGS_INCLUDE_DIR GFLAGS_LIBRARY)\n\n  if(GFLAGS_FOUND)\n    message(\n        STATUS\n        \"Caffe2: Found gflags  (include: ${GFLAGS_INCLUDE_DIR}, \"\n        \"library: ${GFLAGS_LIBRARY})\")\n    add_library(gflags UNKNOWN IMPORTED)\n    set_property(\n        TARGET gflags PROPERTY IMPORTED_LOCATION ${GFLAGS_LIBRARY})\n    set_property(\n        TARGET gflags PROPERTY INTERFACE_INCLUDE_DIRECTORIES\n        ${GFLAGS_INCLUDE_DIR})\n  endif()\nendif()\n\n# After above, we should have the gflags target now.\nif (NOT TARGET gflags)\n  message(WARNING\n      \"Caffe2: gflags cannot be found. Depending on whether you are building \"\n      \"Caffe2 or a Caffe2 dependent library, the next warning / error will \"\n      \"give you more info.\")\nendif()\n\n"
  },
  {
    "path": "cmake/public/glog.cmake",
    "content": "# ---[ glog\n\n# We will try to use the config mode first, and then manual find.\nfind_package(glog CONFIG QUIET)\nif (NOT TARGET glog::glog)\n  find_package(glog MODULE QUIET)\nendif()\n\nif (TARGET glog::glog)\n  message(STATUS \"Caffe2: Found glog with new-style glog target.\")\nelseif(GLOG_FOUND)\n  message(\n      STATUS\n      \"Caffe2: Found glog with old-style glog starget. Glog never shipped \"\n      \"old style glog targets, so somewhere in your cmake path there might \"\n      \"be a custom Findglog.cmake file that got triggered. We will make a \"\n      \"best effort to create the new style glog target for you.\")\n  add_library(glog::glog UNKNOWN IMPORTED)\n  set_property(\n      TARGET glog::glog PROPERTY IMPORTED_LOCATION ${GLOG_LIBRARY})\n  set_property(\n      TARGET glog::glog PROPERTY INTERFACE_INCLUDE_DIRECTORIES\n      ${GLOG_INCLUDE_DIR})\nelse()\n  message(STATUS \"Caffe2: Cannot find glog automatically. Using legacy find.\")\n\n  # - Try to find Glog\n  #\n  # The following variables are optionally searched for defaults\n  #  GLOG_ROOT_DIR: Base directory where all GLOG components are found\n  #\n  # The following are set after configuration is done:\n  #  GLOG_FOUND\n  #  GLOG_INCLUDE_DIRS\n  #  GLOG_LIBRARIES\n  #  GLOG_LIBRARYRARY_DIRS\n\n  include(FindPackageHandleStandardArgs)\n  set(GLOG_ROOT_DIR \"\" CACHE PATH \"Folder contains Google glog\")\n  if(NOT WIN32)\n      find_path(GLOG_INCLUDE_DIR glog/logging.h\n          PATHS ${GLOG_ROOT_DIR})\n  endif()\n\n  find_library(GLOG_LIBRARY glog\n      PATHS ${GLOG_ROOT_DIR}\n      PATH_SUFFIXES lib lib64)\n\n  find_package_handle_standard_args(glog DEFAULT_MSG GLOG_INCLUDE_DIR GLOG_LIBRARY)\n\n  if(GLOG_FOUND)\n    message(STATUS\n        \"Caffe2: Found glog (include: ${GLOG_INCLUDE_DIR}, \"\n        \"library: ${GLOG_LIBRARY})\")\n    add_library(glog::glog UNKNOWN IMPORTED)\n    set_property(\n        TARGET glog::glog PROPERTY IMPORTED_LOCATION ${GLOG_LIBRARY})\n    set_property(\n        TARGET glog::glog PROPERTY INTERFACE_INCLUDE_DIRECTORIES\n        ${GLOG_INCLUDE_DIR})\n  endif()\nendif()\n\n# After above, we should have the glog::glog target now.\nif (NOT TARGET glog::glog)\n  message(WARNING\n      \"Caffe2: glog cannot be found. Depending on whether you are building \"\n      \"Caffe2 or a Caffe2 dependent library, the next warning / error will \"\n      \"give you more info.\")\nendif()\n\n"
  },
  {
    "path": "cmake/public/protobuf.cmake",
    "content": "# ---[ Protobuf\n\n# We will try to use the config mode first, and then manual find.\nfind_package(Protobuf CONFIG QUIET)\nif (NOT Protobuf_FOUND)\n  find_package(Protobuf MODULE QUIET)\nendif()\n\nif ((TARGET protobuf::libprotobuf OR TARGET protobuf::libprotobuf-lite) AND TARGET protobuf::protoc)\n  # Hooray. This is the most ideal situation, meaning that you either have a\n  # Protobuf config file installed (like on Windows), or you are using a\n  # modern CMake that ships with a FindProtobuf.cmake file that produces\n  # modern targets.\n  message(STATUS \"Caffe2: Found protobuf with new-style protobuf targets.\")\nelseif(Protobuf_FOUND OR PROTOBUF_FOUND)\n  # If the modern targets are not present, we will generate them for you for\n  # backward compatibility. This is backported from CMake's new FindProtobuf.cmake\n  # content.\n  message(STATUS \"Caffe2: Found protobuf with old-style protobuf targets.\")\n  if(PROTOBUF_LIBRARY)\n    if (NOT TARGET protobuf::libprotobuf)\n      add_library(protobuf::libprotobuf UNKNOWN IMPORTED)\n      set_target_properties(protobuf::libprotobuf PROPERTIES\n          INTERFACE_INCLUDE_DIRECTORIES \"${Protobuf_INCLUDE_DIR}\")\n    endif()\n    if(EXISTS \"${PROTOBUF_LIBRARY}\")\n      set_target_properties(protobuf::libprotobuf PROPERTIES\n          IMPORTED_LOCATION \"${PROTOBUF_LIBRARY}\")\n    endif()\n    if(EXISTS \"${PROTOBUF_LIBRARY_RELEASE}\")\n      set_property(TARGET protobuf::libprotobuf APPEND PROPERTY\n          IMPORTED_CONFIGURATIONS RELEASE)\n      set_target_properties(protobuf::libprotobuf PROPERTIES\n          IMPORTED_LOCATION_RELEASE \"${PROTOBUF_LIBRARY_RELEASE}\")\n    endif()\n    if(EXISTS \"${PROTOBUF_LIBRARY_DEBUG}\")\n      set_property(TARGET protobuf::libprotobuf APPEND PROPERTY\n          IMPORTED_CONFIGURATIONS DEBUG)\n      set_target_properties(protobuf::libprotobuf PROPERTIES\n          IMPORTED_LOCATION_DEBUG \"${PROTOBUF_LIBRARY_DEBUG}\")\n    endif()\n  endif()\n\n  if(PROTOBUF_LITE_LIBRARY)\n    if (NOT TARGET protobuf::libprotobuf-lite)\n      add_library(protobuf::libprotobuf-lite UNKNOWN IMPORTED)\n      set_target_properties(protobuf::libprotobuf-lite PROPERTIES\n          INTERFACE_INCLUDE_DIRECTORIES \"${Protobuf_INCLUDE_DIR}\")\n    endif()\n    if(EXISTS \"${PROTOBUF_LITE_LIBRARY}\")\n      set_target_properties(protobuf::libprotobuf-lite PROPERTIES\n          IMPORTED_LOCATION \"${PROTOBUF_LITE_LIBRARY}\")\n    endif()\n    if(EXISTS \"${PROTOBUF_LITE_LIBRARY_RELEASE}\")\n      set_property(TARGET protobuf::libprotobuf-lite APPEND PROPERTY\n          IMPORTED_CONFIGURATIONS RELEASE)\n      set_target_properties(protobuf::libprotobuf-lite PROPERTIES\n          IMPORTED_LOCATION_RELEASE \"${PROTOBUF_LITE_LIBRARY_RELEASE}\")\n    endif()\n    if(EXISTS \"${PROTOBUF_LITE_LIBRARY_DEBUG}\")\n      set_property(TARGET protobuf::libprotobuf-lite APPEND PROPERTY\n          IMPORTED_CONFIGURATIONS DEBUG)\n      set_target_properties(protobuf::libprotobuf-lite PROPERTIES\n          IMPORTED_LOCATION_DEBUG \"${PROTOBUF_LITE_LIBRARY_DEBUG}\")\n    endif()\n  endif()\n\n  if(PROTOBUF_PROTOC_EXECUTABLE)\n    if (NOT TARGET protobuf::protoc)\n      add_executable(protobuf::protoc IMPORTED)\n    endif()\n    set_property(TARGET protobuf::protoc PROPERTY\n        IMPORTED_LOCATION ${PROTOBUF_PROTOC_EXECUTABLE})\n  endif()\nendif()\n\n# After above, we should have the protobuf related target now.\nif ((NOT TARGET protobuf::libprotobuf) AND (NOT TARGET protobuf::libprotobuf-lite))\n  message(WARNING\n      \"Protobuf cannot be found. Depending on whether you are building Caffe2 \"\n      \"or a Caffe2 dependent library, the next warning / error will give you \"\n      \"more info.\")\nendif()\n"
  },
  {
    "path": "cmake/public/threads.cmake",
    "content": "find_package(Threads REQUIRED)\n# For newer CMake, Threads::Threads is already defined. Otherwise, we will\n# provide a backward compatible wrapper for Threads::Threads.\nif(THREADS_FOUND AND NOT TARGET Threads::Threads)\n  add_library(Threads::Threads INTERFACE IMPORTED)\n\n  if(THREADS_HAVE_PTHREAD_ARG)\n    set_property(\n        TARGET Threads::Threads\n        PROPERTY INTERFACE_COMPILE_OPTIONS \"-pthread\")\n  endif()\n\n  if(CMAKE_THREAD_LIBS_INIT)\n    set_property(\n        TARGET Threads::Threads\n        PROPERTY INTERFACE_LINK_LIBRARIES \"${CMAKE_THREAD_LIBS_INIT}\")\n  endif()\nendif()"
  },
  {
    "path": "cmake/public/utils.cmake",
    "content": "macro(caffe2_interface_library SRC DST)\n  # Add an interface library definition that is dependent on the source.\n  add_library(${DST} INTERFACE)\n  add_dependencies(${DST} ${SRC})\n  # Depending on the nature of the source library as well as the compiler,\n  # determine the needed compilation flags.\n  get_target_property(__src_target_type ${SRC} TYPE)\n  # Depending on the type of the source library, we will set up the\n  # link command for the specific SRC library.\n  if (${__src_target_type} STREQUAL \"STATIC_LIBRARY\")\n    # In the case of static library, we will need to add whole-static flags.\n    if(APPLE)\n      target_link_libraries(\n          ${DST} INTERFACE -Wl,-force_load,$<TARGET_FILE:${SRC}>)\n    elseif(MSVC)\n      # In MSVC, we will add whole archive in default.\n      target_link_libraries(\n          ${DST} INTERFACE -WHOLEARCHIVE:$<TARGET_FILE:${SRC}>)\n    else()\n      # Assume everything else is like gcc\n      target_link_libraries(\n          ${DST} INTERFACE\n          \"-Wl,--whole-archive $<TARGET_FILE:${SRC}> -Wl,--no-whole-archive\")\n    endif()\n    # Link all interface link libraries of the src target as well.\n    # For static library, we need to explicitly depend on all the libraries\n    # that are the dependent library of the source library. Note that we cannot\n    # use the populated INTERFACE_LINK_LIBRARIES property, because if one of the\n    # dependent library is not a target, cmake creates a $<LINK_ONLY:src> wrapper\n    # and then one is not able to find target \"src\". For more discussions, check\n    #   https://gitlab.kitware.com/cmake/cmake/issues/15415\n    #   https://cmake.org/pipermail/cmake-developers/2013-May/019019.html\n    # Specifically the following quote\n    #\n    # \"\"\"\n    # For STATIC libraries we can define that the PUBLIC/PRIVATE/INTERFACE keys\n    # are ignored for linking and that it always populates both LINK_LIBRARIES\n    # LINK_INTERFACE_LIBRARIES.  Note that for STATIC libraries the\n    # LINK_LIBRARIES property will not be used for anything except build-order\n    # dependencies.\n    # \"\"\"\n    target_link_libraries(${DST} INTERFACE\n        $<TARGET_PROPERTY:${SRC},LINK_LIBRARIES>)\n  elseif(${__src_target_type} STREQUAL \"SHARED_LIBRARY\")\n    if(\"${CMAKE_CXX_COMPILER_ID}\" MATCHES \"GNU\")\n      target_link_libraries(\n          ${DST} INTERFACE -Wl,--no-as-needed ${SRC} -Wl,--as-needed)\n    else()\n      target_link_libraries(${DST} INTERFACE ${SRC})\n    endif()\n    # Link all interface link libraries of the src target as well.\n    # For shared libraries, we can simply depend on the INTERFACE_LINK_LIBRARIES\n    # property of the target.\n    target_link_libraries(${DST} INTERFACE\n        $<TARGET_PROPERTY:${SRC},INTERFACE_LINK_LIBRARIES>)\n  else()\n    message(FATAL_ERROR\n        \"You made a CMake build file error: target \" ${SRC}\n        \" must be of type either STATIC_LIBRARY or SHARED_LIBRARY. However, \"\n        \"I got \" ${__src_target_type} \".\")\n  endif()\n  # For all other interface properties, manually inherit from the source target.\n  set_target_properties(${DST} PROPERTIES\n    INTERFACE_COMPILE_DEFINITIONS\n    $<TARGET_PROPERTY:${SRC},INTERFACE_COMPILE_DEFINITIONS>\n    INTERFACE_COMPILE_OPTIONS\n    $<TARGET_PROPERTY:${SRC},INTERFACE_COMPILE_OPTIONS>\n    INTERFACE_INCLUDE_DIRECTORIES\n    $<TARGET_PROPERTY:${SRC},INTERFACE_INCLUDE_DIRECTORIES>\n    INTERFACE_SYSTEM_INCLUDE_DIRECTORIES\n    $<TARGET_PROPERTY:${SRC},INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>)\nendmacro()\n\n\n##############################################################################\n# Creating a Caffe2 binary target with sources specified with relative path.\n# Usage:\n#   caffe2_binary_target(target_name_or_src <src1> [<src2>] [<src3>] ...)\n# If only target_name_or_src is specified, this target is build with one single\n# source file and the target name is autogen from the filename. Otherwise, the\n# target name is given by the first argument and the rest are the source files\n# to build the target.\nfunction(caffe2_binary_target target_name_or_src)\n  if (${ARGN})\n    set(__target ${target_name_or_src})\n    prepend(__srcs \"${CMAKE_CURRENT_SOURCE_DIR}/\" \"${ARGN}\")\n  else()\n    get_filename_component(__target ${target_name_or_src} NAME_WE)\n    prepend(__srcs \"${CMAKE_CURRENT_SOURCE_DIR}/\" \"${target_name_or_src}\")\n  endif()\n  add_executable(${__target} ${__srcs})\n  target_link_libraries(${__target} ${Caffe2_MAIN_LIBS})\n  # If we have Caffe2_MODULES defined, we will also link with the modules.\n  if (DEFINED Caffe2_MODULES)\n    target_link_libraries(${__target} ${Caffe2_MODULES})\n  endif()\n  install(TARGETS ${__target} DESTINATION bin)\nendfunction()\n"
  },
  {
    "path": "conda/cuda/build.sh",
    "content": "#!/bin/bash\n\n# Install script for Anaconda environments with CUDA on linux\n# This script is not supposed to be called directly, but should be run by:\n#\n# $ cd <path to caffe2, e.g. ~/caffe2>\n# $ conda build conda/build\n#\n# If you're debugging this, it may be useful to use the env that conda build is\n# using:\n# $ cd <anaconda_root>/conda-bld/caffe2_<timestamp>\n# $ source activate _h_env_... # some long path with lots of placeholders\n#\n# Also, failed builds will accumulate those caffe2_<timestamp> directories. You\n# can remove them after a succesfull build with\n# $ conda build purge\n#\n\nset -ex\n\necho \"Installing caffe2 to ${PREFIX}\"\n\nPYTHON_ARGS=\"$(python ./scripts/get_python_cmake_flags.py)\"\nCMAKE_ARGS=()\n\n# Build with minimal required libraries\n# Add CMAKE flags here\nCMAKE_ARGS+=(\"-DUSE_MPI=OFF\")\n\n# Build with CUDA\nCMAKE_ARGS+=(\"-DUSE_CUDA=ON\")\nCMAKE_ARGS+=(\"-DUSE_NCCL=ON\")\n\n# Install under specified prefix\nCMAKE_ARGS+=(\"-DCMAKE_INSTALL_PREFIX=$PREFIX\")\nCMAKE_ARGS+=(\"-DCMAKE_PREFIX_PATH=$PREFIX\")\n\nmkdir -p build\ncd build\ncmake \"${CMAKE_ARGS[@]}\"  $CONDA_CMAKE_ARGS $PYTHON_ARGS ..\nmake \"-j$(nproc)\"\n\nmake install/fast\n"
  },
  {
    "path": "conda/cuda/conda_build_config.yaml",
    "content": "protobuf:\n  - 3.4.1\npin_run_as_build:\n  protobuf:\n    min_pin: x.x\n    max_pin: x.x\n"
  },
  {
    "path": "conda/cuda/meta.yaml",
    "content": "{% set version = \"0.8.dev\" %}\n\npackage:\n  name: caffe2-cuda\n  version: {{ version }}\n\nsource:\n  path: ../..\n\nbuild:\n  number: 0\n  skip: True  # [win]\n  script_env:\n    - CONDA_CMAKE_BUILD_ARGS\n\nrequirements:\n  build:\n    - cmake\n    - future\n    - glog\n    - gflags\n    - leveldb\n    - lmdb\n    - numpy\n    - opencv\n    - python\n    - protobuf {{ protobuf }}\n    - six\n  run:\n    - future\n    - glog\n    - gflags\n    - leveldb\n    - lmdb\n    - numpy\n    - opencv\n    - protobuf\n    - python\n    - six\n\ntest:\n  imports:\n    - caffe2.python.core\n\nabout:\n  home: https://caffe2.ai/\n  license: BSD\n  summary: Caffe2 is a lightweight, modular, and scalable deep learning framework.\n\nextra:\n  recipe-maintainers:\n    - pjh5\n"
  },
  {
    "path": "conda/cuda_full/build.sh",
    "content": "#!/bin/bash\n\n# This needs to run on Ubuntu 16.04 with gcc 5\n# Install script for Anaconda environments with CUDA on linux\n# This script is not supposed to be called directly, but should be run by:\n#\n# $ cd <path to caffe2, e.g. ~/caffe2>\n# $ conda build conda/cuda_full\n#\n# If you're debugging this, it may be useful to use the env that conda build is\n# using:\n# $ cd <anaconda_root>/conda-bld/caffe2_<timestamp>\n# $ source activate _h_env_... # some long path with lots of placeholders\n#\n# Also, failed builds will accumulate those caffe2_<timestamp> directories. You\n# can remove them after a succesfull build with\n# $ conda build purge\n#\n\nset -ex\n\necho \"Installing caffe2 to ${PREFIX}\"\n\nPYTHON_ARGS=\"$(python ./scripts/get_python_cmake_flags.py)\"\n\n# Build with a big suite of libraries\nCMAKE_ARGS=()\nCMAKE_ARGS+=(\"-DUSE_CUDA=ON\")\nCMAKE_ARGS+=(\"-DCUDA_ARCH_NAME=All\")\nCMAKE_ARGS+=(\"-DUSE_GFLAGS=ON\")\nCMAKE_ARGS+=(\"-DUSE_GLOG=ON\")\nCMAKE_ARGS+=(\"-DUSE_GLOO=ON\")\nCMAKE_ARGS+=(\"-DUSE_LMDB=ON\")\nCMAKE_ARGS+=(\"-DUSE_NCCL=ON\")\nCMAKE_ARGS+=(\"-DUSE_OPENCV=ON\")\n\n# cuDNN and NCCL come from module locations\nCMAKE_ARGS+=(\"-DCUDNN_ROOT_DIR=/public/apps/cudnn/v7.0/cuda/\")\nCMAKE_ARGS+=(\"-DNCCL_ROOT_DIR=$NCCL_ROOT_DIR\")\n\n# openmpi is needed but can't be included from conda, b/c it's only available\n# in conda-forge, which uses gcc 4.8.5\nCMAKE_ARGS+=(\"-DUSE_MPI=ON\")\n\n# Use MKL and hack around a broken eigen op\nCMAKE_ARGS+=(\"-DBLAS=MKL\")\nrm -rf ./caffe2/operators/conv_op_eigen.cc\n\n# Explicitly turn unused packages off to prevent cmake from trying to find\n# system libraries. If conda packages are built with any system libraries then\n# they will not be relocatable.\nCMAKE_ARGS+=(\"-DUSE_LEVELDB=OFF\")\nCMAKE_ARGS+=(\"-DUSE_REDIS=OFF\")\nCMAKE_ARGS+=(\"-DUSE_ROCKSDB=OFF\")\n\n# Install under specified prefix\nCMAKE_ARGS+=(\"-DCMAKE_INSTALL_PREFIX=$PREFIX\")\nCMAKE_ARGS+=(\"-DCMAKE_PREFIX_PATH=$PREFIX\")\n\n# Build\nmkdir -p build\ncd build\ncmake \"${CMAKE_ARGS[@]}\"  $CONDA_CMAKE_ARGS $PYTHON_ARGS ..\nmake VERBOSE=1 \"-j$(nproc)\"\n\nmake install/fast\n"
  },
  {
    "path": "conda/cuda_full/conda_build_config.yaml",
    "content": "protobuf:\n  - 3.4.1\npin_run_as_build:\n  protobuf:\n    min_pin: x.x\n    max_pin: x.x\n"
  },
  {
    "path": "conda/cuda_full/meta.yaml",
    "content": "{% set version = \"0.8.dev\" %}\n\npackage:\n  name: caffe2-cuda\n  version: {{ version }}\n\nsource:\n  path: ../..\n\nbuild:\n  number: 0\n  skip: True  # [win]\n  script_env:\n    - CONDA_CMAKE_BUILD_ARGS\n    - NCCL_ROOT_DIR\n\nrequirements:\n  build:\n    - cmake\n    - future\n    - glog\n    - gflags\n    - lmdb\n    - mkl\n    - mkl-include\n    - numpy\n    - opencv\n    - python\n    - protobuf {{ protobuf }}\n    - six\n  run:\n    - future\n    - glog\n    - gflags\n    - lmdb\n    - mkl\n    - mkl-include\n    - numpy\n    - opencv\n    - protobuf\n    - python\n    - six\n\ntest:\n  imports:\n    - caffe2.python.core\n\nabout:\n  home: https://caffe2.ai/\n  license: BSD\n  summary: Caffe2 is a lightweight, modular, and scalable deep learning framework.\n\nextra:\n  recipe-maintainers:\n    - pjh5\n"
  },
  {
    "path": "conda/no_cuda/build.sh",
    "content": "#!/bin/bash\n\n# Install script for Anaconda environments on macOS and linux.\n# This script is not supposed to be called directly, but should be run by:\n#\n# $ cd <path to caffe2, e.g. ~/caffe2>\n# $ conda build conda\n#\n# This installation uses MKL and does not use CUDA\n#\n# If you're debugging this, it may be useful to use the env that conda build is\n# using:\n# $ cd <anaconda_root>/conda-bld/caffe2_<timestamp>\n# $ source activate _h_env_... # some long path with lots of placeholders\n#\n# Also, failed builds will accumulate those caffe2_<timestamp> directories. You\n# can remove them after a succesfull build with\n# $ conda build purge\n#\n\nset -ex\n\necho \"Installing caffe2 to ${PREFIX}\"\n\nPYTHON_ARGS=\"$(python ./scripts/get_python_cmake_flags.py)\"\nCMAKE_ARGS=()\n\n# This installation defaults to using MKL because it is much faster. If you\n# want to build without MKL then you should also remove mkl from meta.yaml in\n# addition to removing the flags below\nCMAKE_ARGS+=(\"-DBLAS=MKL\")\n\n# Minimal packages\nCMAKE_ARGS+=(\"-DUSE_CUDA=OFF\")\nCMAKE_ARGS+=(\"-DUSE_MPI=OFF\")\nCMAKE_ARGS+=(\"-DUSE_NCCL=OFF\")\n\n# Install under specified prefix\nCMAKE_ARGS+=(\"-DCMAKE_INSTALL_PREFIX=$PREFIX\")\nCMAKE_ARGS+=(\"-DCMAKE_PREFIX_PATH=$PREFIX\")\n\nmkdir -p build\ncd build\ncmake \"${CMAKE_ARGS[@]}\"  $CONDA_CMAKE_BUILD_ARGS $PYTHON_ARGS ..\nif [ \"$(uname)\" == 'Darwin' ]; then\n  make \"-j$(sysctl -n hw.ncpu)\"\nelse\n  make \"-j$(nproc)\"\nfi\n\nmake install/fast\n"
  },
  {
    "path": "conda/no_cuda/conda_build_config.yaml",
    "content": "pin_run_as_build:\n  protobuf:\n    min_pin: x.x\n    max_pin: x.x\n"
  },
  {
    "path": "conda/no_cuda/meta.yaml",
    "content": "{% set version = \"0.8.dev\" %}\n\npackage:\n  name: caffe2\n  version: {{ version }}\n\nsource:\n  path: ../..\n\nbuild:\n  number: 0\n  skip: True  # [win]\n  script_env:\n    - CONDA_CMAKE_BUILD_ARGS\n\nrequirements:\n  build:\n    - cmake\n    - future\n    - gflags\n    - glog\n    - leveldb\n    - lmdb\n    - mkl\n    - mkl-include\n    - numpy\n    - opencv\n    - protobuf\n    - python\n    - six\n    # other packages here\n  run:\n    - future\n    - gflags\n    - glog\n    - leveldb\n    - lmdb\n    - mkl\n    - mkl-include\n    - numpy\n    - opencv\n    - protobuf\n    - python\n    - six\n    # other packages here\n\ntest:\n  imports:\n    - caffe2.python.core\n\nabout:\n  home: https://caffe2.ai/\n  license: BSD\n  summary: Caffe2 is a lightweight, modular, and scalable deep learning framework.\n\nextra:\n  recipe-maintainers:\n    - pjh5\n"
  },
  {
    "path": "docker/jenkins/README.md",
    "content": "# Docker images for Jenkins\n\nThis directory contains everything needed to build the Docker images\nthat are used in our Jenkins setup. These images provide a variety of\nbuild environments for which we want to ensure that Caffe2 is\ncompatible.\n\nThe Dockerfiles located in subdirectories are parameterized to\nconditionally run build stages depending on build arguments passed to\n`docker build`. This lets us use only a few Dockerfiles for many\nimages. The different configurations are identified by a freeform\nstring that we call a _build environment_. This string is persisted in\neach image as the `BUILD_ENVIRONMENT` environment variable.\n\nExamples of valid build environments are:\n\n* `py2-cuda9.0-cudnn7-ubuntu16.04`\n* `py3-mkl-ubuntu16.04`\n* `py3-gcc7-ubuntu16.04`\n* `py3-cuda8.0-cudnn7-centos7`\n\nSee `build.sh` for a full list of terms that are extracted from the\nbuild environment into parameters for the image build.\n\n## Contents\n\n* `build.sh` -- dispatch script to launch all builds\n* `common` -- scripts used to execute individual Docker build stages\n* `centos` -- Dockerfile for CentOS image\n* `centos-cuda` -- Dockerfile for CentOS image with CUDA support for nvidia-docker\n* `ubuntu` -- Dockerfile for Ubuntu image\n* `ubuntu-cuda` -- Dockerfile for Ubuntu image with CUDA support for nvidia-docker\n\n\n\n"
  },
  {
    "path": "docker/jenkins/build.sh",
    "content": "#!/bin/bash\n\nset -ex\n\nimage=\"$1\"\nshift\n\nif [ -z \"${image}\" ]; then\n  echo \"Usage: $0 IMAGE\"\n  exit 1\nfi\n\nUBUNTU_VERSION=\"$(echo \"${image}\" | perl -n -e'/ubuntu(\\d+\\.\\d+)/ && print $1')\"\nCENTOS_VERSION=\"$(echo \"${image}\" | perl -n -e'/centos(\\d+)/ && print $1')\"\n\nif [ -n \"${UBUNTU_VERSION}\" ]; then\n  OS=\"ubuntu\"\n  DOCKERFILE=\"ubuntu/Dockerfile\"\nelif [ -n \"${CENTOS_VERSION}\" ]; then\n  OS=\"centos\"\n  DOCKERFILE=\"centos/Dockerfile\"\nelse\n  echo \"Unable to derive operating system base...\"\n  exit 1\nfi\n\nif [[ \"$image\" == py* ]]; then\n  PYTHON_VERSION=\"$(echo \"${image}\" | perl -n -e'/py(\\d+(\\.\\d+)?)/ && print $1')\"\nfi\n\nif [[ \"$image\" == *cuda* ]]; then\n  CUDA_VERSION=\"$(echo \"${image}\" | perl -n -e'/cuda(\\d+\\.\\d+)/ && print $1')\"\n  CUDNN_VERSION=\"$(echo \"${image}\" | perl -n -e'/cudnn(\\d+)/ && print $1')\"\n  DOCKERFILE=\"${OS}-cuda/Dockerfile\"\nfi\n\nif [[ \"$image\" == *conda* ]]; then\n  # Unlike python version, Anaconda version is either 2 or 3\n  ANACONDA_VERSION=\"$(echo \"${image}\" | perl -n -e'/conda(\\d)/ && print $1')\"\nfi\n\nif [[ \"$image\" == *-mkl-* ]]; then\n  MKL=yes\nfi\n\nif [[ \"$image\" == *-android-* ]]; then\n  ANDROID=yes\n\n  # The Android NDK requires CMake 3.6 or higher.\n  # See https://github.com/caffe2/caffe2/pull/1740 for more info.\n  CMAKE_VERSION=3.6.3\nfi\n\nif [[ \"$image\" == *-gcc* ]]; then\n  GCC_VERSION=\"$(echo \"${image}\" | perl -n -e'/gcc(\\d+(\\.\\d+)?)/ && print $1')\"\nfi\n\nif [[ \"$image\" == *-clang* ]]; then\n  CLANG_VERSION=\"$(echo \"${image}\" | perl -n -e'/clang(\\d+(\\.\\d+)?)/ && print $1')\"\nfi\n\n# Copy over common scripts to directory containing the Dockerfile to build\ncp -a common/* \"$(dirname ${DOCKERFILE})\"\n\n# Set Jenkins UID and GID if running Jenkins\nif [ -n \"${JENKINS:-}\" ]; then\n  JENKINS_UID=$(id -u jenkins)\n  JENKINS_GID=$(id -g jenkins)\nfi\n\n# Build image\ndocker build \\\n       --build-arg \"BUILD_ENVIRONMENT=${image}\" \\\n       --build-arg \"EC2=${EC2:-}\" \\\n       --build-arg \"JENKINS=${JENKINS:-}\" \\\n       --build-arg \"JENKINS_UID=${JENKINS_UID:-}\" \\\n       --build-arg \"JENKINS_GID=${JENKINS_GID:-}\" \\\n       --build-arg \"UBUNTU_VERSION=${UBUNTU_VERSION}\" \\\n       --build-arg \"CENTOS_VERSION=${CENTOS_VERSION}\" \\\n       --build-arg \"PYTHON_VERSION=${PYTHON_VERSION}\" \\\n       --build-arg \"ANACONDA_VERSION=${ANACONDA_VERSION}\" \\\n       --build-arg \"CUDA_VERSION=${CUDA_VERSION}\" \\\n       --build-arg \"CUDNN_VERSION=${CUDNN_VERSION}\" \\\n       --build-arg \"MKL=${MKL}\" \\\n       --build-arg \"ANDROID=${ANDROID}\" \\\n       --build-arg \"GCC_VERSION=${GCC_VERSION}\" \\\n       --build-arg \"CLANG_VERSION=${CLANG_VERSION}\" \\\n       --build-arg \"CMAKE_VERSION=${CMAKE_VERSION:-}\" \\\n       \"$@\" \\\n       \"$(dirname ${DOCKERFILE})\"\n"
  },
  {
    "path": "docker/jenkins/centos/.gitignore",
    "content": "*.sh\n"
  },
  {
    "path": "docker/jenkins/centos/Dockerfile",
    "content": "ARG CENTOS_VERSION\nFROM centos:${CENTOS_VERSION}\n\n# Install required packages to build Caffe2\nARG EC2\nADD ./install_base.sh install_base.sh\nRUN bash ./install_base.sh && rm install_base.sh\n\n# Compile/install ccache for faster builds\nADD ./install_ccache.sh install_ccache.sh\nRUN bash ./install_ccache.sh && rm install_ccache.sh\n\n# Install Python\nARG PYTHON_VERSION\nADD ./install_python.sh install_python.sh\nRUN if [ -n \"${PYTHON_VERSION}\" ]; then bash ./install_python.sh; fi\nRUN rm install_python.sh\n\n# (optional) Add Jenkins user\nARG JENKINS\nARG JENKINS_UID\nARG JENKINS_GID\nADD ./add_jenkins_user.sh add_jenkins_user.sh\nRUN if [ -n \"${JENKINS}\" ]; then bash ./add_jenkins_user.sh; fi\nRUN rm add_jenkins_user.sh\n\n# Include BUILD_ENVIRONMENT environment variable in image\nARG BUILD_ENVIRONMENT\nENV BUILD_ENVIRONMENT ${BUILD_ENVIRONMENT}\n"
  },
  {
    "path": "docker/jenkins/centos-cuda/.gitignore",
    "content": "*.sh\n"
  },
  {
    "path": "docker/jenkins/centos-cuda/Dockerfile",
    "content": "ARG CENTOS_VERSION\nARG CUDA_VERSION\nARG CUDNN_VERSION\nFROM nvidia/cuda:${CUDA_VERSION}-cudnn${CUDNN_VERSION}-devel-centos${CENTOS_VERSION}\n\n# Install required packages to build Caffe2\nARG EC2\nADD ./install_base.sh install_base.sh\nRUN bash ./install_base.sh && rm install_base.sh\n\n# Compile/install ccache for faster builds\nADD ./install_ccache.sh install_ccache.sh\nRUN bash ./install_ccache.sh && rm install_ccache.sh\n\n# Install Python\nARG PYTHON_VERSION\nADD ./install_python.sh install_python.sh\nRUN if [ -n \"${PYTHON_VERSION}\" ]; then bash ./install_python.sh; fi\nRUN rm install_python.sh\n\n# (optional) Add Jenkins user\nARG JENKINS\nARG JENKINS_UID\nARG JENKINS_GID\nADD ./add_jenkins_user.sh add_jenkins_user.sh\nRUN if [ -n \"${JENKINS}\" ]; then bash ./add_jenkins_user.sh; fi\nRUN rm add_jenkins_user.sh\n\n# Include BUILD_ENVIRONMENT environment variable in image\nARG BUILD_ENVIRONMENT\nENV BUILD_ENVIRONMENT ${BUILD_ENVIRONMENT}\n"
  },
  {
    "path": "docker/jenkins/common/add_jenkins_user.sh",
    "content": "#!/bin/bash\n\nset -ex\n\n# Mirror jenkins user in container\necho \"jenkins:x:$JENKINS_UID:$JENKINS_GID::/var/lib/jenkins:\" >> /etc/passwd\necho \"jenkins:x:$JENKINS_GID:\" >> /etc/group\n\n# Create $HOME\nmkdir -p /var/lib/jenkins\nchown jenkins:jenkins /var/lib/jenkins\nmkdir -p /var/lib/jenkins/.ccache\nchown jenkins:jenkins /var/lib/jenkins/.ccache\n\n# Allow writing to /usr/local (for make install)\nchown jenkins:jenkins /usr/local\n\n# Allow sudo\necho 'jenkins ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/jenkins\n"
  },
  {
    "path": "docker/jenkins/common/install_anaconda.sh",
    "content": "#!/bin/bash\n\nset -ex\n\n# Adapted from https://hub.docker.com/r/continuumio/anaconda/~/dockerfile/\nexport LANG=C.UTF-8\nexport LC_ALL=C.UTF-8\n\n# Install needed packages\n# This also needs build-essentials but that should be installed already\napt-get update --fix-missing\napt-get install -y wget\n\n# Install anaconda\necho 'export PATH=/opt/conda/bin:$PATH' > /etc/profile.d/conda.sh\ncase \"$ANACONDA_VERSION\" in\n  2*)\n    wget https://repo.continuum.io/archive/Anaconda2-5.0.1-Linux-x86_64.sh -O ~/anaconda.sh\n  ;;\n  3*)\n    wget https://repo.continuum.io/archive/Anaconda3-5.0.1-Linux-x86_64.sh -O ~/anaconda.sh\n  ;;\n  *)\n    echo \"Invalid ANACONDA_VERSION...\"\n    echo $ANACONDA_VERSION\n    exit 1\n  ;;\nesac\n/bin/bash ~/anaconda.sh -b -p /opt/conda\nrm ~/anaconda.sh\n\nexport PATH=\"/opt/conda/bin:$PATH\"\necho 'export PATH=/opt/conda/bin:$PATH' > ~/.bashrc\n"
  },
  {
    "path": "docker/jenkins/common/install_android.sh",
    "content": "#!/bin/bash\n\nset -ex\n\napt-get update\napt-get install -y --no-install-recommends autotools-dev autoconf unzip\napt-get autoclean && apt-get clean\nrm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*\n\npushd /tmp\ncurl -Os https://dl.google.com/android/repository/android-ndk-r13b-linux-x86_64.zip\npopd\n_ndk_dir=/opt/ndk\nmkdir -p \"$_ndk_dir\"\nunzip -qo /tmp/android*.zip -d \"$_ndk_dir\"\n_versioned_dir=$(find \"$_ndk_dir/\" -mindepth 1 -maxdepth 1 -type d)\nmv \"$_versioned_dir\"/* \"$_ndk_dir\"/\nrmdir \"$_versioned_dir\"\nrm -rf /tmp/*\n"
  },
  {
    "path": "docker/jenkins/common/install_base.sh",
    "content": "#!/bin/bash\n\nset -ex\n\n# This function installs protobuf 2.6\ninstall_protobuf_26() {\n  pb_dir=\"/usr/temp_pb_install_dir\"\n  mkdir -p $pb_dir\n\n  # On the nvidia/cuda:9-cudnn7-devel-centos7 image we need this symlink or\n  # else it will fail with\n  #   g++: error: ./../lib64/crti.o: No such file or directory\n  ln -s /usr/lib64 \"$pb_dir/lib64\"\n\n  curl -LO \"https://github.com/google/protobuf/releases/download/v2.6.1/protobuf-2.6.1.tar.gz\"\n  tar -xvz -C \"$pb_dir\" --strip-components 1 -f protobuf-2.6.1.tar.gz\n  pushd \"$pb_dir\" && ./configure && make && make check && sudo make install && sudo ldconfig\n  popd\n  rm -rf $pb_dir\n}\n\ninstall_ubuntu() {\n  # Use AWS mirror if running in EC2\n  if [ -n \"${EC2:-}\" ]; then\n    A=\"archive.ubuntu.com\"\n    B=\"us-east-1.ec2.archive.ubuntu.com\"\n    perl -pi -e \"s/${A}/${B}/g\" /etc/apt/sources.list\n  fi\n\n  apt-get update\n  apt-get install -y --no-install-recommends \\\n          autoconf \\\n          build-essential \\\n          ca-certificates \\\n          cmake \\\n          curl \\\n          git \\\n          libgoogle-glog-dev \\\n          libhiredis-dev \\\n          libiomp-dev \\\n          libleveldb-dev \\\n          liblmdb-dev \\\n          libopencv-dev \\\n          libpthread-stubs0-dev \\\n          libsnappy-dev \\\n          sudo\n\n  # Ubuntu 14.04 ships with protobuf 2.5, but ONNX needs protobuf >= 2.6\n  # so we install that here if on 14.04\n  # Ubuntu 14.04 also has cmake 2.8.12 as the default option, so we will\n  # install cmake3 here and use cmake3.\n  if [[ \"$UBUNTU_VERSION\" == 14.04 ]]; then\n    apt-get install -y --no-install-recommends cmake3\n    install_protobuf_26\n  else\n    apt-get install -y --no-install-recommends \\\n            libprotobuf-dev \\\n            protobuf-compiler\n  fi\n\n  # Cleanup\n  apt-get autoclean && apt-get clean\n  rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*\n}\n\ninstall_centos() {\n  # Need EPEL for many packages we depend on.\n  # See http://fedoraproject.org/wiki/EPEL\n  yum --enablerepo=extras install -y epel-release\n\n  # Note: protobuf-c-{compiler,devel} on CentOS are too old to be used\n  # for Caffe2. That said, we still install them to make sure the build\n  # system opts to build/use protoc and libprotobuf from third-party.\n  yum install -y \\\n      autoconf \\\n      automake \\\n      cmake \\\n      cmake3 \\\n      curl \\\n      gcc \\\n      gcc-c++ \\\n      gflags-devel \\\n      git \\\n      glibc-devel \\\n      glibc-headers \\\n      glog-devel \\\n      hiredis-devel \\\n      leveldb-devel \\\n      libstdc++-devel \\\n      lmdb-devel \\\n      make \\\n      opencv-devel \\\n      snappy-devel \\\n      sudo\n\n  # Centos7 ships with protobuf 2.5, but ONNX needs protobuf >= 2.6\n  # so we always install install that here\n  install_protobuf_26\n\n  # Cleanup\n  yum clean all\n  rm -rf /var/cache/yum\n  rm -rf /var/lib/yum/yumdb\n  rm -rf /var/lib/yum/history\n}\n\n# Install base packages depending on the base OS\nif [ -f /etc/lsb-release ]; then\n  install_ubuntu\nelif [ -f /etc/os-release ]; then\n  install_centos\nelse\n  echo \"Unable to determine OS...\"\n  exit 1\nfi\n"
  },
  {
    "path": "docker/jenkins/common/install_ccache.sh",
    "content": "#!/bin/bash\n\nset -ex\n\n# Install ccache from source.\n# Needs specific branch to work with nvcc (ccache/ccache#145)\n# Also pulls in a commit that disables documentation generation,\n# as this requires asciidoc to be installed (which pulls in a LOT of deps).\npushd /tmp\ngit clone https://github.com/pietern/ccache -b ccbin\npushd ccache\n./autogen.sh\n./configure --prefix=/usr/local\nmake \"-j$(nproc)\" install\npopd\npopd\n\n# Install sccache from binary release.\n# Note: this release does NOT yet work with nvcc.\npushd /tmp\ncurl -LOs https://github.com/mozilla/sccache/releases/download/0.2.5/sccache-0.2.5-x86_64-unknown-linux-musl.tar.gz\ntar -zxvf sccache-0.2.5-x86_64-unknown-linux-musl.tar.gz\nmv sccache-0.2.5-x86_64-unknown-linux-musl/sccache /usr/local/bin/sccache\nrm -rf sccache-0.2.5-x86_64-unknown-linux-musl*\npopd\n"
  },
  {
    "path": "docker/jenkins/common/install_clang.sh",
    "content": "#!/bin/bash\n\nset -ex\n\n[ -n \"$CLANG_VERSION\" ]\n\napt-get update\napt-get install -y --no-install-recommends clang-\"$CLANG_VERSION\"\nrm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*\n\n# Use update-alternatives to make this version the default\nupdate-alternatives --install /usr/bin/gcc gcc /usr/bin/clang-\"$CLANG_VERSION\" 50\nupdate-alternatives --install /usr/bin/g++ g++ /usr/bin/clang++-\"$CLANG_VERSION\" 50\n"
  },
  {
    "path": "docker/jenkins/common/install_cmake.sh",
    "content": "#!/bin/bash\n\nset -ex\n\n[ -n \"$CMAKE_VERSION\" ]\n\n# Turn 3.6.3 into v3.6\npath=$(echo \"${CMAKE_VERSION}\" | sed -e 's/\\([0-9].[0-9]\\+\\).*/v\\1/')\nfile=\"cmake-${CMAKE_VERSION}-Linux-x86_64.tar.gz\"\n\n# Download and install specific CMake version in /usr/local\npushd /tmp\ncurl -Os \"https://cmake.org/files/${path}/${file}\"\ntar -C /usr/local --strip-components 1 --no-same-owner -zxf cmake-*.tar.gz\nrm -f cmake-*.tar.gz\npopd\n"
  },
  {
    "path": "docker/jenkins/common/install_cuda.sh",
    "content": "#!/bin/bash\n\nset -ex\n\nAPT_INSTALL_CMD=\"apt-get install -y --no-install-recommends\"\n"
  },
  {
    "path": "docker/jenkins/common/install_gcc.sh",
    "content": "#!/bin/bash\n\nset -ex\n\n[ -n \"$GCC_VERSION\" ]\n\napt-get update\napt-get install -y --no-install-recommends software-properties-common\nadd-apt-repository ppa:ubuntu-toolchain-r/test\napt-get update\napt-get install -y --no-install-recommends gcc-\"$GCC_VERSION\" g++-\"$GCC_VERSION\"\nrm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*\n\n# Use update-alternatives to make this version the default\nupdate-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-\"$GCC_VERSION\" 50\nupdate-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-\"$GCC_VERSION\" 50\n"
  },
  {
    "path": "docker/jenkins/common/install_mkl.sh",
    "content": "#!/bin/bash\n\nset -ex\n\n# Needs https transport for apt\napt-get update\napt-get install -y --no-install-recommends apt-transport-https\n\n# Add Intel MKL repository\nkey=\"https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS-2019.PUB\"\ncurl \"${key}\" | apt-key add -\necho 'deb http://apt.repos.intel.com/mkl all main' | \\\n  tee /etc/apt/sources.list.d/intel-mkl.list\napt-get update\n\n# Multiple candidates for intel-mkl-64bit, so have to be specific\napt-get install -y --no-install-recommends intel-mkl-64bit-2018.1-038\nrm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*\n\n# Ensure loader can find MKL path\necho '/opt/intel/mkl/lib/intel64' | tee /etc/ld.so.conf.d/intel-mkl.conf\nldconfig\n"
  },
  {
    "path": "docker/jenkins/common/install_nccl.sh",
    "content": "#!/bin/bash\n\nset -ex\n\n[ -n \"$UBUNTU_VERSION\" ]\n[ -n \"$CUDA_VERSION\" ]\n\n# The NCCL version is not encoded in the build environment.\n# This file installs the latest version that works.\n\n# There are only NCCL packages for Ubuntu 16.04 and 14.04\nif [[ \"$UBUNTU_VERSION\" == 16.04 ]]; then\n  NCCL_UBUNTU_VER=ubuntu1604\n  NCCL_DEB='nvidia-machine-learning-repo-ubuntu1604_1.0.0-1_amd64.deb'\nelif [[ \"$UBUNTU_VERSION\" == 14.04 ]]; then\n  NCCL_UBUNTU_VER=ubuntu1404\n  NCCL_DEB='nvidia-machine-learning-repo-ubuntu1404_4.0-2_amd64.deb'\nelse\n  echo \"There is no NCCL package for Ubuntu version ${UBUNTU_VERSION}.\"\n  echo \"    NCCL will not be installed.\"\nfi\n\nif [ -n \"$NCCL_UBUNTU_VER\" ]; then\n\n  # The deb is agnostic of CUDA version\n  curl -LO \"http://developer.download.nvidia.com/compute/machine-learning/repos/${NCCL_UBUNTU_VER}/x86_64/${NCCL_DEB}\"\n\n  # This dpkg call needs wget\n  apt-get update\n  apt-get install -y wget\n  dpkg -i \"${NCCL_DEB}\"\n\n  if [ ${CUDA_VERSION:0:3} == 9.1 ]; then\n    # For 9.1 because 2.1.2 for 9.1 does not exist, we will stil use 2.1.4.\n    NCCL_LIB_VERSION=\"2.1.4-1+cuda${CUDA_VERSION:0:3}\"\n  else\n    # Actually installing takes into account CUDA version.\n    # Version 2.1.4 exports symbols it shouldn't, causing a ton of tests to fail.\n    # We pin this to 2.1.2 until this is solved and NVIDIA releases a new version.\n    NCCL_LIB_VERSION=\"2.1.2-1+cuda${CUDA_VERSION:0:3}\"\n  fi\n  apt update\n  apt install libnccl2=$NCCL_LIB_VERSION libnccl-dev=$NCCL_LIB_VERSION\nfi\n"
  },
  {
    "path": "docker/jenkins/common/install_python.sh",
    "content": "#!/bin/bash\n\nset -ex\n\nif [ -z \"$PYTHON_VERSION\" ]; then\n  echo \"Please specify PYTHON_VERSION...\"\n  exit 1\nfi\n\ninstall_ubuntu_deadsnakes() {\n  apt-get install -y --no-install-recommends software-properties-common\n  add-apt-repository ppa:deadsnakes/ppa\n  apt-get update\n  apt-get install -y --no-install-recommends \"$1\"\n}\n\ninstall_ubuntu() {\n  apt-get update\n\n  case \"$PYTHON_VERSION\" in\n    2*)\n      apt-get install -y --no-install-recommends \\\n              python-dev \\\n              python-setuptools\n      PYTHON=python2\n      ;;\n    3.5)\n      apt-get install -y --no-install-recommends \\\n              python3-dev \\\n              python3-setuptools\n      PYTHON=python3.5\n      ;;\n    3.6)\n      install_ubuntu_deadsnakes python3.6-dev\n      PYTHON=python3.6\n      INSTALL_SETUPTOOLS=yes\n      ;;\n    3.7)\n      install_ubuntu_deadsnakes python3.7-dev\n      PYTHON=python3.7\n      INSTALL_SETUPTOOLS=yes\n      ;;\n    *)\n      echo \"Invalid PYTHON_VERSION...\"\n      exit 1\n      ;;\n  esac\n\n  # Have to install unzip if installing setuptools\n  if [ -n \"${INSTALL_SETUPTOOLS}\" ]; then\n    apt-get install -y --no-install-recommends unzip\n  fi\n\n  # Clean up\n  apt-get autoclean && apt-get clean\n  rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*\n}\n\ninstall_centos() {\n  source /etc/os-release\n  if [ \"$ID\" != \"centos\" ]; then\n    echo \"Unknown ID: $ID\"\n    exit 1\n  fi\n\n  case \"$PYTHON_VERSION\" in\n    2*)\n      yum install -y \\\n          python-devel \\\n          python-setuptools\n      PYTHON=python2\n      ;;\n    3.4)\n      yum install -y \\\n          python34-devel \\\n          python34-setuptools\n      PYTHON=python3\n      ;;\n    *)\n      echo \"Invalid PYTHON_VERSION...\"\n      exit 1\n      ;;\n  esac\n\n  # Cleanup\n  yum clean all\n  rm -rf /var/cache/yum\n  rm -rf /var/lib/yum/yumdb\n  rm -rf /var/lib/yum/history\n}\n\n# Install Python packages depending on the base OS\nif [ -f /etc/lsb-release ]; then\n  install_ubuntu\nelif [ -f /etc/os-release ]; then\n  install_centos\nelse\n  echo \"Unable to determine OS...\"\n  exit 1\nfi\n\n# Optionally install setuptools from source.\n# This is required for the non-standard Python version\n# installed on Ubuntu. They\nif [ -n \"${INSTALL_SETUPTOOLS}\" ]; then\n  curl -O https://pypi.python.org/packages/6c/54/f7e9cea6897636a04e74c3954f0d8335cc38f7d01e27eec98026b049a300/setuptools-38.5.1.zip\n  unzip setuptools-38.5.1.zip\n  pushd setuptools-38.5.1\n  \"$PYTHON\" setup.py install\n  popd\n  rm -rf setuptools-38.5.1*\nfi\n\n# Install pip from source.\n# The python-pip package on Ubuntu Trusty is old\n# and upon install numpy doesn't use the binary\n# distribution, and fails to compile it from source.\ncurl -O https://pypi.python.org/packages/11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/pip-9.0.1.tar.gz\ntar zxf pip-9.0.1.tar.gz\npushd pip-9.0.1\n\"$PYTHON\" setup.py install\npopd\nrm -rf pip-9.0.1*\n\n# Upgrade setuptools\n# setuptools 38.5.2 seems to be buggy, see error in\n# https://ci.pytorch.org/jenkins/job/caffe2-docker/job/py3.6-gcc5-ubuntu16.04/35/consoleFull\npip install -U pip setuptools!=38.5.2\n\n# tornado 5.0 requires Python 2.7.9+ or 3.4+\nif [[ $($PYTHON -c 'import sys; print(int(sys.version_info <= (2, 7, 9) or sys.version_info <= (3, 4)))' == 1) ]]; then\n    pip install 'tornado<5'\nfi\n\n# Need networkx 2.0 because bellmand_ford was moved in 2.1 . Scikit-image by\n# defaults installs the most recent networkx version, so we install this lower\n# version explicitly before scikit-image pulls it in as a dependency\npip install networkx==2.0\n\npip install --no-cache-dir \\\n    click \\\n    future \\\n    hypothesis \\\n    jupyter \\\n    numpy \\\n    protobuf \\\n    pytest \\\n    scipy==0.19.1 \\\n    scikit-image \\\n    tabulate \\\n    virtualenv\n\n"
  },
  {
    "path": "docker/jenkins/ubuntu/.gitignore",
    "content": "*.sh\n"
  },
  {
    "path": "docker/jenkins/ubuntu/Dockerfile",
    "content": "ARG UBUNTU_VERSION\nFROM ubuntu:${UBUNTU_VERSION}\n\n# Install required packages to build Caffe2\nARG EC2\nARG UBUNTU_VERSION\nADD ./install_base.sh install_base.sh\nRUN bash ./install_base.sh && rm install_base.sh\n\n# Compile/install ccache for faster builds\nADD ./install_ccache.sh install_ccache.sh\nRUN bash ./install_ccache.sh && rm install_ccache.sh\n\n# Install Python\nARG PYTHON_VERSION\nADD ./install_python.sh install_python.sh\nRUN if [ -n \"${PYTHON_VERSION}\" ]; then bash ./install_python.sh; fi\nRUN rm install_python.sh\n\n# Install Anaconda\nARG ANACONDA_VERSION\nADD ./install_anaconda.sh install_anaconda.sh\nRUN if [ -n \"${ANACONDA_VERSION}\" ]; then bash ./install_anaconda.sh; fi\nRUN rm install_anaconda.sh\n\n# (optional) Install Intel MKL\nARG MKL\nADD ./install_mkl.sh install_mkl.sh\nRUN if [ -n \"${MKL}\" ]; then bash ./install_mkl.sh; fi\nRUN rm install_mkl.sh\n\n# (optional) Install Android NDK\nARG ANDROID\nADD ./install_android.sh install_android.sh\nRUN if [ -n \"${ANDROID}\" ]; then bash ./install_android.sh; fi\nRUN rm install_android.sh\n\n# (optional) Install non-default GCC version\nARG GCC_VERSION\nADD ./install_gcc.sh install_gcc.sh\nRUN if [ -n \"${GCC_VERSION}\" ]; then bash ./install_gcc.sh; fi\nRUN rm install_gcc.sh\n\n# (optional) Install non-default clang version\nARG CLANG_VERSION\nADD ./install_clang.sh install_clang.sh\nRUN if [ -n \"${CLANG_VERSION}\" ]; then bash ./install_clang.sh; fi\nRUN rm install_clang.sh\n\n# (optional) Install non-default CMake version\nARG CMAKE_VERSION\nADD ./install_cmake.sh install_cmake.sh\nRUN if [ -n \"${CMAKE_VERSION}\" ]; then bash ./install_cmake.sh; fi\nRUN rm install_cmake.sh\n\n# (optional) Add Jenkins user\nARG JENKINS\nARG JENKINS_UID\nARG JENKINS_GID\nADD ./add_jenkins_user.sh add_jenkins_user.sh\nRUN if [ -n \"${JENKINS}\" ]; then bash ./add_jenkins_user.sh ${JENKINS_UID} ${JENKINS_GID}; fi\nRUN rm add_jenkins_user.sh\n\n# Include BUILD_ENVIRONMENT environment variable in image\nARG BUILD_ENVIRONMENT\nENV BUILD_ENVIRONMENT ${BUILD_ENVIRONMENT}\n"
  },
  {
    "path": "docker/jenkins/ubuntu-cuda/.gitignore",
    "content": "*.sh\n"
  },
  {
    "path": "docker/jenkins/ubuntu-cuda/Dockerfile",
    "content": "ARG UBUNTU_VERSION\nARG CUDA_VERSION\nARG CUDNN_VERSION\nFROM nvidia/cuda:${CUDA_VERSION}-cudnn${CUDNN_VERSION}-devel-ubuntu${UBUNTU_VERSION}\n\n# Install required packages to build Caffe2\nARG EC2\nARG UBUNTU_VERSION\nADD ./install_base.sh install_base.sh\nRUN bash ./install_base.sh && rm install_base.sh\n\n# Compile/install ccache for faster builds\nADD ./install_ccache.sh install_ccache.sh\nRUN bash ./install_ccache.sh && rm install_ccache.sh\n\n# Install NCCL for all CUDA builds\nARG UBUNTU_VERSION\nARG CUDA_VERSION\nADD ./install_nccl.sh install_nccl.sh\nRUN bash ./install_nccl.sh && rm install_nccl.sh\n\n# Install Python\nARG PYTHON_VERSION\nADD ./install_python.sh install_python.sh\nRUN if [ -n \"${PYTHON_VERSION}\" ]; then bash ./install_python.sh; fi\nRUN rm install_python.sh\n\n# Install Anaconda\nARG ANACONDA_VERSION\nADD ./install_anaconda.sh install_anaconda.sh\nRUN if [ -n \"${ANACONDA_VERSION}\" ]; then bash ./install_anaconda.sh; fi\nRUN rm install_anaconda.sh\n\n# (optional) Add Jenkins user\nARG JENKINS\nARG JENKINS_UID\nARG JENKINS_GID\nADD ./add_jenkins_user.sh add_jenkins_user.sh\nRUN if [ -n \"${JENKINS}\" ]; then bash ./add_jenkins_user.sh ${JENKINS_UID} ${JENKINS_GID}; fi\nRUN rm add_jenkins_user.sh\n\n# Include BUILD_ENVIRONMENT environment variable in image\nARG BUILD_ENVIRONMENT\nENV BUILD_ENVIRONMENT ${BUILD_ENVIRONMENT}\n"
  },
  {
    "path": "docker/readme.md",
    "content": "# Docker & Caffe2\n\n**Note: use [nvidia-docker](https://github.com/NVIDIA/nvidia-docker) to run all GPU builds.**\n\nTo get the latest source, rerun the docker builds using the Dockerfiles.\n\nDocker images at https://hub.docker.com/r/caffe2ai/caffe2/ are a few months old, but will be refreshed soon.  \n\n**Build like:** `docker build -t caffe2:cuda8-cudnn6-all-options .`\n\n**Run like:** `nvidia-docker run --rm -it caffe2:cuda8-cudnn6-all-options python -m caffe2.python.operator_test.relu_op_test`\n\nFor Docker on USB related instructions you can find some help on the gh-pages branch [here](https://github.com/caffe2/caffe2/tree/gh-pages/docker/caffe2-docker-usb)\n"
  },
  {
    "path": "docker/ubuntu-14.04-cpu-all-options/Dockerfile",
    "content": "FROM caffe2ai/caffe2:c2v0.8.1.cpu.min.ubuntu14.04\nLABEL maintainer=\"aaronmarkham@fb.com\"\n\n# caffe2 install with cpu support\n\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    libgflags-dev \\\n    libgflags2 \\\n    libgtest-dev \\\n    libiomp-dev \\\n    libleveldb-dev \\\n    liblmdb-dev \\\n    libopencv-dev \\\n    libopenmpi-dev \\\n    libsnappy-dev \\\n    openmpi-bin \\\n    openmpi-doc \\\n    python-numpy \\\n    python-pydot \\\n    python-setuptools \\\n    python-scipy \\\n    wget \\\n    && rm -rf /var/lib/apt/lists/*\n\n# tornado 5.0 requires Python 2.7.9+ or 3.4+\nRUN pip install --no-cache-dir \\\n    flask \\\n    graphviz \\\n    jupyter \\\n    matplotlib \\\n    pydot \\\n    python-nvd3 \\\n    pyyaml \\\n    requests \\\n    scikit-image \\\n    scipy \\\n    setuptools \\\n    'tornado<5'\n\n########## INSTALLATION STEPS ###################\nRUN cd caffe2 && mkdir build && cd build \\\n    && cmake .. \\\n    -DUSE_CUDA=OFF \\\n    -DUSE_NNPACK=OFF \\\n    -DUSE_ROCKSDB=OFF \\\n    && make -j\"$(nproc)\" install \\\n    && ldconfig \\\n    && make clean \\\n    && cd .. \\\n    && rm -rf build\n\nENV PYTHONPATH /usr/local\n"
  },
  {
    "path": "docker/ubuntu-14.04-cpu-minimal/Dockerfile",
    "content": "FROM ubuntu:14.04\nLABEL maintainer=\"aaronmarkham@fb.com\"\n\n# caffe2 install with cpu support\n\n########## REQUIRED DEPENDENCIES ################\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    build-essential \\\n    curl \\\n    cmake \\\n    git \\\n    libgoogle-glog-dev \\\n    libprotobuf-dev \\\n    python-pip \\\n    protobuf-compiler \\\n    python-dev \\\n    && rm -rf /var/lib/apt/lists/*\n\n# Don't use deb package because trusty's pip is too old for --no-cache-dir\nRUN curl -O https://bootstrap.pypa.io/get-pip.py \\\n    && python get-pip.py \\\n    && rm get-pip.py\nRUN pip install --no-cache-dir --upgrade pip setuptools wheel\nRUN pip install --no-cache-dir future hypothesis numpy protobuf six\n\n########## INSTALLATION STEPS ###################\nRUN git clone --branch master --recursive https://github.com/caffe2/caffe2.git\nRUN cd caffe2 && mkdir build && cd build \\\n    && cmake .. \\\n    -DUSE_CUDA=OFF \\\n    -DUSE_NNPACK=OFF \\\n    -DUSE_ROCKSDB=OFF \\\n    && make -j\"$(nproc)\" install \\\n    && ldconfig \\\n    && make clean \\\n    && cd .. \\\n    && rm -rf build\n\nENV PYTHONPATH /usr/local\n"
  },
  {
    "path": "docker/ubuntu-16.04-cpu-all-options/Dockerfile",
    "content": "FROM caffe2ai/caffe2:c2v0.8.1.cpu.min.ubuntu16.04\nLABEL maintainer=\"aaronmarkham@fb.com\"\n\n# caffe2 install with cpu support\n\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    libgflags-dev \\\n    libgtest-dev \\\n    libiomp-dev \\\n    libleveldb-dev \\\n    liblmdb-dev \\\n    libopencv-dev \\\n    libopenmpi-dev \\\n    libsnappy-dev \\\n    openmpi-bin \\\n    openmpi-doc \\\n    python-numpy \\\n    python-pydot \\\n    python-setuptools \\\n    python-scipy \\\n    wget \\\n    && rm -rf /var/lib/apt/lists/*\n\nRUN pip install --no-cache-dir \\\n    flask \\\n    graphviz \\\n    jupyter \\\n    matplotlib \\\n    pydot \\\n    python-nvd3 \\\n    pyyaml \\\n    requests \\\n    scikit-image \\\n    scipy \\\n    setuptools \\\n    tornado\n\n########## INSTALLATION STEPS ###################\nRUN cd caffe2 && mkdir build && cd build \\\n    && cmake .. \\\n    -DUSE_CUDA=OFF \\\n    -DUSE_NNPACK=OFF \\\n    -DUSE_ROCKSDB=OFF \\\n    && make -j\"$(nproc)\" install \\\n    && ldconfig \\\n    && make clean \\\n    && cd .. \\\n    && rm -rf build\n\nENV PYTHONPATH /usr/local\n"
  },
  {
    "path": "docker/ubuntu-16.04-cpu-minimal/Dockerfile",
    "content": "FROM ubuntu:16.04\nLABEL maintainer=\"aaronmarkham@fb.com\"\n\n# caffe2 install with cpu support\n\n########## REQUIRED DEPENDENCIES ################\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    build-essential \\\n    cmake \\\n    curl \\\n    git \\\n    libgoogle-glog-dev \\\n    libprotobuf-dev \\\n    python-pip \\\n    protobuf-compiler \\\n    python-dev \\\n    && rm -rf /var/lib/apt/lists/*\n\n# Don't use deb package because trusty's pip is too old for --no-cache-dir\nRUN curl -O https://bootstrap.pypa.io/get-pip.py \\\n    && python get-pip.py \\\n    && rm get-pip.py\nRUN pip install --no-cache-dir --upgrade pip setuptools wheel\nRUN pip install --no-cache-dir future hypothesis numpy protobuf six\n\n########## INSTALLATION STEPS ###################\nRUN git clone --branch master --recursive https://github.com/caffe2/caffe2.git\nRUN cd caffe2 && mkdir build && cd build \\\n    && cmake .. \\\n    -DUSE_CUDA=OFF \\\n    -DUSE_NNPACK=OFF \\\n    -DUSE_ROCKSDB=OFF \\\n    && make -j\"$(nproc)\" install \\\n    && ldconfig \\\n    && make clean \\\n    && cd .. \\\n    && rm -rf build\n\nENV PYTHONPATH /usr/local\n"
  },
  {
    "path": "docker/ubuntu-16.04-cuda8-cudnn6-all-options/Dockerfile",
    "content": "FROM nvidia/cuda:8.0-cudnn6-devel-ubuntu16.04\nLABEL maintainer=\"aaronmarkham@fb.com\"\n\n# caffe2 install with gpu support\n\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    build-essential \\\n    cmake \\\n    git \\\n    libgflags-dev \\\n    libgoogle-glog-dev \\\n    libgtest-dev \\\n    libiomp-dev \\\n    libleveldb-dev \\\n    liblmdb-dev \\\n    libopencv-dev \\\n    libopenmpi-dev \\\n    libprotobuf-dev \\\n    libsnappy-dev \\\n    openmpi-bin \\\n    openmpi-doc \\\n    protobuf-compiler \\\n    python-dev \\\n    python-numpy \\\n    python-pip \\\n    python-pydot \\\n    python-setuptools \\\n    python-scipy \\\n    wget \\\n    && rm -rf /var/lib/apt/lists/*\n\nRUN pip install --no-cache-dir --upgrade pip setuptools wheel && \\\n    pip install --no-cache-dir \\\n    flask \\\n    future \\\n    graphviz \\\n    hypothesis \\\n    jupyter \\\n    matplotlib \\\n    numpy \\\n    protobuf \\\n    pydot \\\n    python-nvd3 \\\n    pyyaml \\\n    requests \\\n    scikit-image \\\n    scipy \\\n    setuptools \\\n    six \\\n    tornado\n\n########## INSTALLATION STEPS ###################\nRUN git clone --branch master --recursive https://github.com/caffe2/caffe2.git\nRUN cd caffe2 && mkdir build && cd build \\\n    && cmake .. \\\n    -DCUDA_ARCH_NAME=Manual \\\n    -DCUDA_ARCH_BIN=\"35 52 60 61\" \\\n    -DCUDA_ARCH_PTX=\"61\" \\\n    -DUSE_NNPACK=OFF \\\n    -DUSE_ROCKSDB=OFF \\\n    && make -j\"$(nproc)\" install \\\n    && ldconfig \\\n    && make clean \\\n    && cd .. \\\n    && rm -rf build\n\nENV PYTHONPATH /usr/local\n"
  },
  {
    "path": "docker/ubuntu-16.04-cuda8-cudnn7-all-options/Dockerfile",
    "content": "FROM nvidia/cuda:8.0-cudnn7-devel-ubuntu16.04\nLABEL maintainer=\"aaronmarkham@fb.com\"\n\n# caffe2 install with gpu support\n\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    build-essential \\\n    cmake \\\n    git \\\n    libgflags-dev \\\n    libgoogle-glog-dev \\\n    libgtest-dev \\\n    libiomp-dev \\\n    libleveldb-dev \\\n    liblmdb-dev \\\n    libopencv-dev \\\n    libopenmpi-dev \\\n    libprotobuf-dev \\\n    libsnappy-dev \\\n    openmpi-bin \\\n    openmpi-doc \\\n    protobuf-compiler \\\n    python-dev \\\n    python-numpy \\\n    python-pip \\\n    python-pydot \\\n    python-setuptools \\\n    python-scipy \\\n    wget \\\n    && rm -rf /var/lib/apt/lists/*\n\nRUN pip install --no-cache-dir --upgrade pip setuptools wheel && \\\n    pip install --no-cache-dir \\\n    flask \\\n    future \\\n    graphviz \\\n    hypothesis \\\n    jupyter \\\n    matplotlib \\\n    numpy \\\n    protobuf \\\n    pydot \\\n    python-nvd3 \\\n    pyyaml \\\n    requests \\\n    scikit-image \\\n    scipy \\\n    setuptools \\\n    six \\\n    tornado\n\n########## INSTALLATION STEPS ###################\nRUN git clone --branch master --recursive https://github.com/caffe2/caffe2.git\nRUN cd caffe2 && mkdir build && cd build \\\n    && cmake .. \\\n    -DCUDA_ARCH_NAME=Manual \\\n    -DCUDA_ARCH_BIN=\"35 52 60 61\" \\\n    -DCUDA_ARCH_PTX=\"61\" \\\n    -DUSE_NNPACK=OFF \\\n    -DUSE_ROCKSDB=OFF \\\n    && make -j\"$(nproc)\" install \\\n    && ldconfig \\\n    && make clean \\\n    && cd .. \\\n    && rm -rf build\n\nENV PYTHONPATH /usr/local\n"
  },
  {
    "path": "docker/ubuntu-16.04-gpu-tutorial/Dockerfile",
    "content": "FROM caffe2ai/caffe2:latest\nMAINTAINER Aaron Markham <aaronmarkham@fb.com>\n\n# Caffe2 source refresh and tutorial files overlay\n# Change to a CPU-only docker base if needed (latest is GPU)\n\n########## INSTALLATION STEPS ###################\nRUN apt-get install unzip vim -y --no-install-recommends\nWORKDIR \"/\"\nRUN rm -rf caffe2\nRUN git clone --recursive https://github.com/caffe2/caffe2.git\n\n########## REBUILD ###################\nWORKDIR \"/caffe2\"\nRUN make\nWORKDIR \"/caffe2/build\"\nRUN make install\nENV PYTHONPATH /usr/local\nENV LD_LIBRARY_PATH /usr/local/lib:$LD_LIBRARY_PATH\nWORKDIR \"/usr/local\"\n\n########## SETUP TUTORIAL FILES #################\n# get model files for Loading Pre-Trained Models\nRUN python -m caffe2.python.models.download -i squeezenet\n# not installing these by default to keep the image smaller\n# RUN python -m caffe2.python.models.download -i bvlc_alexnet\n# RUN python -m caffe2.python.models.download -i bvlc_googlenet\n# RUN python -m caffe2.python.models.download -i bvlc_reference_caffenet\n# rcnn model throws error, so holding off until fixed\n# RUN python -m caffe2.python.models.download -i bvlc_reference_rcnn_ilsvrc13\n# RUN python -m caffe2.python.models.download -i finetune_flickr_style\n# get MNIST dataset for MNIST\nWORKDIR \"/caffe2/caffe2/python/tutorials\"\nRUN mkdir tutorial_data && cd tutorial_data\nWORKDIR \"/caffe2/caffe2/python/tutorials/tutorial_data\"\nRUN wget \"https://download.caffe2.ai/datasets/mnist/mnist.zip\"\nRUN unzip -d mnist mnist.zip\nWORKDIR \"/caffe2/caffe2/python/tutorials\"\n"
  },
  {
    "path": "docs/.Doxyfile-c",
    "content": "# Doxyfile 1.8.14\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the config file\n# that follow. The default is UTF-8 which is also the encoding used for all text\n# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv\n# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv\n# for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = \"Caffe2 - C++ API\"\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         =\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          = \"A deep learning, cross platform ML framework\"\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           = docs/Caffe2-with-name-55-tall.png\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = @CMAKE_CURRENT_BINARY_DIR@/docs/doxygen-c/\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-\n# directories (in 2 levels) under the output directory of each output format and\n# will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = NO\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,\n# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),\n# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,\n# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),\n# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,\n# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,\n# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,\n# Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       = \"The $name class\" \\\n                         \"The $name widget\" \\\n                         \"The $name file\" \\\n                         is \\\n                         provides \\\n                         specifies \\\n                         contains \\\n                         represents \\\n                         a \\\n                         an \\\n                         the\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        =\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = YES\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 4\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:\\n\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". You can put \\n's in the value part of an alias to insert\n# newlines.\n\nALIASES                =\n\n# This tag can be used to specify a number of word-keyword mappings (TCL only).\n# A mapping has the form \"name=value\". For example adding \"class=itcl::class\"\n# will allow you to use the command class in the itcl::class meaning.\n\nTCL_SUBST              =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = YES\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, Javascript,\n# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:\n# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:\n# Fortran. In the later case the parser tries to guess whether the code is fixed\n# or free formatted code, this is the default for Fortran type files), VHDL. For\n# instance to make doxygen treat .inc files as Fortran files (default is PHP),\n# and .f files as C (default is Fortran), use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See http://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up\n# to that level are automatically included in the table of contents, even if\n# they do not have an id attribute.\n# Note: This feature currently applies only to Markdown headings.\n# Minimum value: 0, maximum value: 99, default value: 0.\n# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.\n\nTOC_INCLUDE_HEADINGS   = 0\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = NO\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# If one adds a struct or class to a group and this option is enabled, then also\n# any nested class or struct is added to the same group. By default this option\n# is disabled and one has to add nested compounds explicitly via \\ingroup.\n# The default value is: NO.\n\nGROUP_NESTED_COMPOUNDS = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = NO\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = NO\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = NO\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# (class|struct|union) declarations. If set to NO, these declarations will be\n# included in the documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file\n# names in lower-case letters. If set to YES, upper-case letters are also\n# allowed. This is useful if you have classes or files whose names only differ\n# in case and if your file system supports case sensitive file names. Windows\n# and Mac users are advised to set this option to NO.\n# The default value is: system dependent.\n\nCASE_SENSE_NAMES       = NO\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = NO\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            = docs/DoxygenLayout-c.xml\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = YES\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = NO\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as not documenting some parameters\n# in a documented function, or documenting parameters that don't exist or using\n# markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = NO\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong or incomplete\n# parameter documentation, but not about the absence of documentation.\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when\n# a warning is encountered.\n# The default value is: NO.\n\nWARN_AS_ERROR          = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr).\n\nWARN_LOGFILE           = docs/warnings-c.log\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  =\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see: http://www.gnu.org/software/libiconv) for the list of\n# possible encodings.\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# read by doxygen.\n#\n# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,\n# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,\n# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,\n# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,\n# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.\n\nFILE_PATTERNS          = *.c \\\n                         *.cc \\\n                         *.cxx \\\n                         *.cpp \\\n                         *.h \\\n\n\n\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                = _site/ \\\n                         build/ \\\n                         caffe/ \\\n                         caffe2/contrib/ \\\n                         caffe2/experiments/python \\\n                         caffe2/mkl/operators/ \\\n                         caffe2/mkl/mkl_operator.cc \\\n                         caffe2/mpi/mpi_ops_gpu.cc \\\n                         caffe2/mpi/mpi_ops.cc \\\n                         caffe2/mpi/mpi_ops.h \\\n                         caffe2/python/ \\\n                         caffe2/test/ \\\n                         cmake/ \\\n                         docker/ \\\n                         docs/ \\\n                         models/ \\\n                         third_party\n\n\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       = *_test.cc\n\n\n\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# AClass::ANamespace, ANamespace::*Test\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories use the pattern */test/*\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           =\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       = *\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             =\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE = README.md\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = YES\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# function all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = NO\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = NO\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see http://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the config file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = YES\n\n# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in\n# which the alphabetical index list will be split.\n# Minimum value: 1, maximum value: 20, default value: 5.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nCOLS_IN_ALPHA_INDEX    = 5\n\n# In case all classes in a project start with a common prefix, all classes will\n# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag\n# can be used to specify a prefix (or a list of prefixes) that should be ignored\n# while generating the index headers.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            = docs/header.html\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            = docs/footer.html\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        = docs/stylesheet.css\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list). For an example see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  = docs/main.css\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a colorwheel, see\n# http://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use grayscales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML\n# page will contain the date and time when the page was generated. Setting this\n# to YES can help to show when doxygen was last run and thus if the\n# documentation is up to date.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_TIMESTAMP         = YES\n\n# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML\n# documentation will contain a main index with vertical navigation menus that\n# are dynamically created via Javascript. If disabled, the navigation index will\n# consists of multiple levels of tabs that are statically embedded in every HTML\n# page. Disable this option to support browsers that do not have Javascript,\n# like the Qt help browser.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_MENUS     = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see: http://developer.apple.com/tools/xcode/), introduced with\n# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a\n# Makefile in the HTML output directory. Running make will produce the docset in\n# that directory and running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html\n# for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on\n# Windows.\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the master .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-\n# folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location of Qt's\n# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the\n# generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine-tune the look of the index. As an example, the default style\n# sheet generated by doxygen has an example that shows how to put an image at\n# the root of the tree instead of the PROJECT_NAME. Since the tree basically has\n# the same information as the tab index, you could consider setting\n# DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = NO\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# Use the FORMULA_TRANPARENT tag to determine whether or not the images\n# generated for formulas are transparent PNGs. Transparent PNGs are not\n# supported properly for IE 6.0, but are supported on all modern browsers.\n#\n# Note that when changing this option you need to delete any form_*.png files in\n# the HTML output directory before the changes have effect.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_TRANSPARENT    = YES\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# http://www.mathjax.org) which uses client side Javascript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. See the MathJax site (see:\n# http://docs.mathjax.org/en/latest/output.html) for more details.\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility), NativeMML (i.e. MathML) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from http://www.mathjax.org before deployment.\n# The default value is: http://cdn.mathjax.org/mathjax/latest.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = YES\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using Javascript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = YES\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/). See the section \"External Indexing and\n# Searching\" for details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = NO\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when enabling USE_PDFLATEX this option is only used for generating\n# bitmaps for formulas in the HTML output, but not in the Makefile that is\n# written to the output directory.\n# The default file is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         = latex\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. The package can be specified just\n# by its name or with the correct syntax as to be used with the LaTeX\n# \\usepackage command. To get the times font for instance you can specify :\n# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}\n# To use the option intlimits with the amsmath package you can specify:\n# EXTRA_PACKAGES=[intlimits]{amsmath}\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the\n# generated LaTeX document. The header should contain everything until the first\n# chapter. If it is left blank doxygen will generate a standard header. See\n# section \"Doxygen usage\" for information on how to let doxygen write the\n# default header to a separate file.\n#\n# Note: Only use a user-defined header if you know what you are doing! The\n# following commands have a special meaning inside the header: $title,\n# $datetime, $date, $doxygenversion, $projectname, $projectnumber,\n# $projectbrief, $projectlogo. Doxygen will replace $title with the empty\n# string, for the replacement values of the other commands the user is referred\n# to HTML_HEADER.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the\n# generated LaTeX document. The footer should contain everything after the last\n# chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer.\n#\n# Note: Only use a user-defined footer if you know what you are doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = YES\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate\n# the PDF file directly from the LaTeX files. Set this option to YES, to get a\n# higher quality PDF documentation.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = YES\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode\n# command to the generated LaTeX files. This will instruct LaTeX to keep running\n# if errors occur, instead of asking the user for help. This option is also used\n# when generating formulas in HTML.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source\n# code with syntax highlighting in the LaTeX output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_SOURCE_CODE      = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# http://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated\n# page will contain the date and time when the page was generated. Setting this\n# to NO can help when comparing the output of multiple runs.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_TIMESTAMP        = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's config\n# file, i.e. a series of assignments. You only have to provide replacements,\n# missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's config file. A template extensions file can be generated\n# using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code\n# with syntax highlighting in the RTF output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_SOURCE_CODE        = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the\n# program listings (including syntax highlighting and cross-referencing\n# information) to the DOCBOOK output. Note that enabling this will significantly\n# increase the size of the DOCBOOK output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_PROGRAMLISTING = NO\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see http://autogen.sf.net) file that captures the\n# structure of the code including all documentation. Note that this feature is\n# still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = NO\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = NO\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           =\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             =\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external class will be listed in\n# the class index. If set to NO, only the inherited external classes will be\n# listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the modules index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n# The PERL_PATH should be the absolute path and name of the perl script\n# interpreter (i.e. the result of 'which perl').\n# The default file (with absolute path) is: /usr/bin/perl.\n\nPERL_PATH              = /usr/bin/perl\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram\n# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to\n# NO turns the diagrams off. Note that this option also works with HAVE_DOT\n# disabled, but it is recommended to install and use dot, since it yields more\n# powerful graphs.\n# The default value is: YES.\n\nCLASS_DIAGRAMS         = YES\n\n# You can define message sequence charts within doxygen comments using the \\msc\n# command. Doxygen will then run the mscgen tool (see:\n# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the\n# documentation. The MSCGEN_PATH tag allows you to specify the directory where\n# the mscgen tool resides. If left empty the tool is assumed to be found in the\n# default search path.\n\nMSCGEN_PATH            =\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: NO.\n\nHAVE_DOT               = NO\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# When you want a differently looking font in the dot files that doxygen\n# generates you can specify the font name using DOT_FONTNAME. You need to make\n# sure dot is able to find the font, which can be done by putting it in a\n# standard location or by setting the DOTFONTPATH environment variable or by\n# setting DOT_FONTPATH to the directory containing the font.\n# The default value is: Helvetica.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTNAME           = Helvetica\n\n# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of\n# dot graphs.\n# Minimum value: 4, maximum value: 24, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTSIZE           = 10\n\n# By default doxygen will tell dot to use the default font as specified with\n# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set\n# the path where dot can find it using this tag.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for\n# each documented class showing the direct and indirect inheritance relations.\n# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command. Disabling a call graph can be\n# accomplished by means of the command \\hidecallgraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command. Disabling a caller graph can be\n# accomplished by means of the command \\hidecallergraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot. For an explanation of the image formats see the section\n# output formats in the documentation of the dot tool (Graphviz (see:\n# http://www.graphviz.org/)).\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,\n# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and\n# png:gdiplus:gdiplus.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               =\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file. If left blank, it is assumed\n# PlantUML is not used or called during a preprocessing step. Doxygen will\n# generate a warning when it encounters a \\startuml command in this case and\n# will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a\n# configuration file for plantuml.\n\nPLANTUML_CFG_FILE      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent\n# background. This is disabled by default, because dot on Windows does not seem\n# to support this out of the box.\n#\n# Warning: Depending on the platform used, enabling this option may lead to\n# badly anti-aliased labels on the edges of a graph (i.e. they become hard to\n# read).\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_TRANSPARENT        = NO\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = YES\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot\n# files that are used to generate the various graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "docs/.Doxyfile-python",
    "content": "# Doxyfile 1.8.14\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the config file\n# that follow. The default is UTF-8 which is also the encoding used for all text\n# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv\n# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv\n# for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = \"Caffe2 - Python API\"\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         =\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          = \"A deep learning, cross platform ML framework\"\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           = docs/Caffe2-with-name-55-tall.png\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = @CMAKE_CURRENT_BINARY_DIR@/docs/doxygen-python/\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-\n# directories (in 2 levels) under the output directory of each output format and\n# will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = NO\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,\n# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),\n# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,\n# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),\n# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,\n# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,\n# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,\n# Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       = \"The $name class\" \\\n                         \"The $name widget\" \\\n                         \"The $name file\" \\\n                         is \\\n                         provides \\\n                         specifies \\\n                         contains \\\n                         represents \\\n                         a \\\n                         an \\\n                         the\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        =\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = YES\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 4\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:\\n\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". You can put \\n's in the value part of an alias to insert\n# newlines.\n\nALIASES                =\n\n# This tag can be used to specify a number of word-keyword mappings (TCL only).\n# A mapping has the form \"name=value\". For example adding \"class=itcl::class\"\n# will allow you to use the command class in the itcl::class meaning.\n\nTCL_SUBST              =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = NO\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = YES\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, Javascript,\n# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:\n# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:\n# Fortran. In the later case the parser tries to guess whether the code is fixed\n# or free formatted code, this is the default for Fortran type files), VHDL. For\n# instance to make doxygen treat .inc files as Fortran files (default is PHP),\n# and .f files as C (default is Fortran), use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See http://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up\n# to that level are automatically included in the table of contents, even if\n# they do not have an id attribute.\n# Note: This feature currently applies only to Markdown headings.\n# Minimum value: 0, maximum value: 99, default value: 0.\n# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.\n\nTOC_INCLUDE_HEADINGS   = 0\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = NO\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# If one adds a struct or class to a group and this option is enabled, then also\n# any nested class or struct is added to the same group. By default this option\n# is disabled and one has to add nested compounds explicitly via \\ingroup.\n# The default value is: NO.\n\nGROUP_NESTED_COMPOUNDS = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = NO\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = NO\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = NO\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# (class|struct|union) declarations. If set to NO, these declarations will be\n# included in the documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file\n# names in lower-case letters. If set to YES, upper-case letters are also\n# allowed. This is useful if you have classes or files whose names only differ\n# in case and if your file system supports case sensitive file names. Windows\n# and Mac users are advised to set this option to NO.\n# The default value is: system dependent.\n\nCASE_SENSE_NAMES       = NO\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            = docs/DoxygenLayout-python.xml\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = YES\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = NO\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as not documenting some parameters\n# in a documented function, or documenting parameters that don't exist or using\n# markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = NO\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong or incomplete\n# parameter documentation, but not about the absence of documentation.\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when\n# a warning is encountered.\n# The default value is: NO.\n\nWARN_AS_ERROR          = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr).\n\nWARN_LOGFILE           = docs/warnings-python.log\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  =\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see: http://www.gnu.org/software/libiconv) for the list of\n# possible encodings.\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# read by doxygen.\n#\n# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,\n# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,\n# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,\n# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,\n# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.\n\nFILE_PATTERNS          =  *.py\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                = _site/ \\\n                         build/ \\\n                         caffe/ \\\n                         caffe2/binaries/ \\\n                         caffe2/contrib/ \\\n                         caffe2/core/ \\\n                         caffe2/cuda_rtc/ \\\n                         caffe2/db/ \\\n                         caffe2/experiments/operators/ \\\n                         caffe2/image/ \\\n                         caffe2/mkl/ \\\n                         caffe2/mpi/ \\\n                         caffe2/operators/ \\\n                         caffe2/perfkernels/ \\\n                         caffe2/proto/ \\\n                         caffe2/queue/ \\\n                         caffe2/sgd/ \\\n                         caffe2/test/ \\\n                         caffe2/transforms/ \\\n                         caffe2/utils/ \\\n                         caffe2/videos/ \\\n                         cmake/ \\\n                         docker/ \\\n                         docs/ \\\n                         models/ \\\n                         third_party\n\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       =  *_test.py\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# AClass::ANamespace, ANamespace::*Test\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories use the pattern */test/*\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           =\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       = *\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             =\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE = README.md\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = YES\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# function all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = NO\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = NO\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see http://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the config file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = YES\n\n# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in\n# which the alphabetical index list will be split.\n# Minimum value: 1, maximum value: 20, default value: 5.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nCOLS_IN_ALPHA_INDEX    = 5\n\n# In case all classes in a project start with a common prefix, all classes will\n# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag\n# can be used to specify a prefix (or a list of prefixes) that should be ignored\n# while generating the index headers.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            = docs/header.html\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            = docs/footer.html\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        = docs/stylesheet.css\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list). For an example see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  = docs/main.css\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a colorwheel, see\n# http://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use grayscales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML\n# page will contain the date and time when the page was generated. Setting this\n# to YES can help to show when doxygen was last run and thus if the\n# documentation is up to date.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_TIMESTAMP         = YES\n\n# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML\n# documentation will contain a main index with vertical navigation menus that\n# are dynamically created via Javascript. If disabled, the navigation index will\n# consists of multiple levels of tabs that are statically embedded in every HTML\n# page. Disable this option to support browsers that do not have Javascript,\n# like the Qt help browser.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_MENUS     = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see: http://developer.apple.com/tools/xcode/), introduced with\n# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a\n# Makefile in the HTML output directory. Running make will produce the docset in\n# that directory and running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html\n# for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on\n# Windows.\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the master .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-\n# folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location of Qt's\n# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the\n# generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine-tune the look of the index. As an example, the default style\n# sheet generated by doxygen has an example that shows how to put an image at\n# the root of the tree instead of the PROJECT_NAME. Since the tree basically has\n# the same information as the tab index, you could consider setting\n# DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = NO\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# Use the FORMULA_TRANPARENT tag to determine whether or not the images\n# generated for formulas are transparent PNGs. Transparent PNGs are not\n# supported properly for IE 6.0, but are supported on all modern browsers.\n#\n# Note that when changing this option you need to delete any form_*.png files in\n# the HTML output directory before the changes have effect.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_TRANSPARENT    = YES\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# http://www.mathjax.org) which uses client side Javascript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. See the MathJax site (see:\n# http://docs.mathjax.org/en/latest/output.html) for more details.\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility), NativeMML (i.e. MathML) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from http://www.mathjax.org before deployment.\n# The default value is: http://cdn.mathjax.org/mathjax/latest.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = YES\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using Javascript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = YES\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/). See the section \"External Indexing and\n# Searching\" for details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = NO\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when enabling USE_PDFLATEX this option is only used for generating\n# bitmaps for formulas in the HTML output, but not in the Makefile that is\n# written to the output directory.\n# The default file is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         = latex\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. The package can be specified just\n# by its name or with the correct syntax as to be used with the LaTeX\n# \\usepackage command. To get the times font for instance you can specify :\n# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}\n# To use the option intlimits with the amsmath package you can specify:\n# EXTRA_PACKAGES=[intlimits]{amsmath}\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the\n# generated LaTeX document. The header should contain everything until the first\n# chapter. If it is left blank doxygen will generate a standard header. See\n# section \"Doxygen usage\" for information on how to let doxygen write the\n# default header to a separate file.\n#\n# Note: Only use a user-defined header if you know what you are doing! The\n# following commands have a special meaning inside the header: $title,\n# $datetime, $date, $doxygenversion, $projectname, $projectnumber,\n# $projectbrief, $projectlogo. Doxygen will replace $title with the empty\n# string, for the replacement values of the other commands the user is referred\n# to HTML_HEADER.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the\n# generated LaTeX document. The footer should contain everything after the last\n# chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer.\n#\n# Note: Only use a user-defined footer if you know what you are doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = YES\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate\n# the PDF file directly from the LaTeX files. Set this option to YES, to get a\n# higher quality PDF documentation.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = YES\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode\n# command to the generated LaTeX files. This will instruct LaTeX to keep running\n# if errors occur, instead of asking the user for help. This option is also used\n# when generating formulas in HTML.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source\n# code with syntax highlighting in the LaTeX output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_SOURCE_CODE      = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# http://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated\n# page will contain the date and time when the page was generated. Setting this\n# to NO can help when comparing the output of multiple runs.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_TIMESTAMP        = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's config\n# file, i.e. a series of assignments. You only have to provide replacements,\n# missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's config file. A template extensions file can be generated\n# using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code\n# with syntax highlighting in the RTF output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_SOURCE_CODE        = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the\n# program listings (including syntax highlighting and cross-referencing\n# information) to the DOCBOOK output. Note that enabling this will significantly\n# increase the size of the DOCBOOK output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_PROGRAMLISTING = NO\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see http://autogen.sf.net) file that captures the\n# structure of the code including all documentation. Note that this feature is\n# still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = NO\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = NO\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           =\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             =\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external class will be listed in\n# the class index. If set to NO, only the inherited external classes will be\n# listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the modules index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n# The PERL_PATH should be the absolute path and name of the perl script\n# interpreter (i.e. the result of 'which perl').\n# The default file (with absolute path) is: /usr/bin/perl.\n\nPERL_PATH              = /usr/bin/perl\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram\n# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to\n# NO turns the diagrams off. Note that this option also works with HAVE_DOT\n# disabled, but it is recommended to install and use dot, since it yields more\n# powerful graphs.\n# The default value is: YES.\n\nCLASS_DIAGRAMS         = YES\n\n# You can define message sequence charts within doxygen comments using the \\msc\n# command. Doxygen will then run the mscgen tool (see:\n# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the\n# documentation. The MSCGEN_PATH tag allows you to specify the directory where\n# the mscgen tool resides. If left empty the tool is assumed to be found in the\n# default search path.\n\nMSCGEN_PATH            =\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: NO.\n\nHAVE_DOT               = NO\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# When you want a differently looking font in the dot files that doxygen\n# generates you can specify the font name using DOT_FONTNAME. You need to make\n# sure dot is able to find the font, which can be done by putting it in a\n# standard location or by setting the DOTFONTPATH environment variable or by\n# setting DOT_FONTPATH to the directory containing the font.\n# The default value is: Helvetica.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTNAME           = Helvetica\n\n# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of\n# dot graphs.\n# Minimum value: 4, maximum value: 24, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTSIZE           = 10\n\n# By default doxygen will tell dot to use the default font as specified with\n# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set\n# the path where dot can find it using this tag.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for\n# each documented class showing the direct and indirect inheritance relations.\n# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command. Disabling a call graph can be\n# accomplished by means of the command \\hidecallgraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command. Disabling a caller graph can be\n# accomplished by means of the command \\hidecallergraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot. For an explanation of the image formats see the section\n# output formats in the documentation of the dot tool (Graphviz (see:\n# http://www.graphviz.org/)).\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,\n# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and\n# png:gdiplus:gdiplus.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               =\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file. If left blank, it is assumed\n# PlantUML is not used or called during a preprocessing step. Doxygen will\n# generate a warning when it encounters a \\startuml command in this case and\n# will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a\n# configuration file for plantuml.\n\nPLANTUML_CFG_FILE      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent\n# background. This is disabled by default, because dot on Windows does not seem\n# to support this out of the box.\n#\n# Warning: Depending on the platform used, enabling this option may lead to\n# badly anti-aliased labels on the edges of a graph (i.e. they become hard to\n# read).\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_TRANSPARENT        = NO\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = YES\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot\n# files that are used to generate the various graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "docs/DOXYGEN.md",
    "content": "# Doxygen Notes\n\nDoxygen seems to behave better if the processing between C++ and Python was split up. This is why there are two different links to cover each API.\n\nC++ API docs work out of the box with the Caffe2 source code. Python docs require “python blocks (http://www.stack.nl/~dimitri/doxygen/manual/docblocks.html#pythonblocks)” which are (often) currently missing in the Python code.\n\nThe Python script called “process.py” that resides in the /docs folder is to prepare the docs by looking for the block and if it doesn't exist prepend and customize the python blocks section with the module's path (e.g. Module caffe2.python.examples.char_rnn). It was noted that you need to delete the previous version of docs when you regenerate the docs or else things get messy, so the script deals with that as well.\n\nThe doxygen customization includes these files in the doxygen folder:\n\n* header.html - logo links back to the main docs page\n* footer.html - includes the Facebook OSS footer\n* stylesheet.css - doxygen's default CSS; tweaked to fix formatting problems with the custom logo, header, and footer\n* main.css - copied from the caffe2ai CSS, so this should be refreshed after the design changes (this overrides/extends stylesheet.css)\n\nIt also extracts info from markdown files found in the source tree. A legacy installation file was in the /docs folder and this was removed. These file show up in the top navigation under “Related Pages”.\n\nThe flow to create the API documents is simple now:\n\n1. Run /caffe2_root/docs/process.py\n2. (TODO automatically) Copy the doxygen-c and doxygen-python folders created by the script to the gh-pages branch.\n\nSettings that were customized:\n\nOPTIMIZE_OUTPUT_JAVA - turned on for Python config, off for C++ config\nUSE_MDFILE_AS_MAINPAGE  - use to flag a markdown file for the mainpage\nEXTRACT_ALL\nQUIET\nWARN_IF_UNDOCUMENTED\nFILE_PATTERNS\nDOT_MULTI_TARGETS = YES\nJAVADOC_AUTOBRIEF = YES\nQUIET = YES\nSOURCE_BROWSER = YES\nVERBATIM_HEADERS = NO\nSHOW_NAMESPACES = NO for C++ config\n\nNot using this (was in old config file, but seems to be for Latex):\nEXTRA_PACKAGES = amsmath \\\namsfonts \\\nxr\n\n### NOTE / TODO:\n\nuseful for xcode, currently off\nGENERATE_DOCSET = NO\n\nLook at search engine integration, xml output, etc\nEXTERNAL_SEARCH = YES\n\n### Other Notes\n\nTo achieve better output in the Python docs:\nhttp://stackoverflow.com/questions/7690220/how-to-document-python-function-parameter-types\n\nSwap this kind of formatting into py files:\n\n```\ndef my_method(x, y):\"\"\"!\n    my_method description\n\n    @type x: int\n    @param x: An integer\n\n    @type y: int|string\n    @param y: An integer or string\n\n    @rtype: string\n    @return: Returns a sentence with your variables in it\n    \"\"\"return \"Hello World! %s, %s\" % (x,y)\n```\n\nNote that the bang (!) is added after the opening comment \"\"\"! - this seems to do the trick and the remaining comments will be nicely parsed by Doxygen.\n\n"
  },
  {
    "path": "docs/DoxygenLayout-c.xml",
    "content": "<doxygenlayout version=\"1.0\">\n  <!-- Generated by doxygen 1.8.14 -->\n  <!-- Navigation index tabs for HTML output -->\n  <navindex>\n    <tab type=\"mainpage\" visible=\"no\" title=\"\"/>\n    <tab type=\"pages\" visible=\"yes\" title=\"\" intro=\"\"/>\n    <tab type=\"modules\" visible=\"yes\" title=\"\" intro=\"\"/>\n    <tab type=\"namespaces\" visible=\"yes\" title=\"\">\n      <tab type=\"namespacelist\" visible=\"yes\" title=\"\" intro=\"\"/>\n      <tab type=\"namespacemembers\" visible=\"yes\" title=\"\" intro=\"\"/>\n    </tab>\n    <tab type=\"classes\" visible=\"yes\" title=\"\">\n      <tab type=\"classlist\" visible=\"yes\" title=\"\" intro=\"\"/>\n      <tab type=\"classindex\" visible=\"$ALPHABETICAL_INDEX\" title=\"\"/>\n      <tab type=\"hierarchy\" visible=\"yes\" title=\"\" intro=\"\"/>\n      <tab type=\"classmembers\" visible=\"yes\" title=\"\" intro=\"\"/>\n    </tab>\n    <tab type=\"files\" visible=\"yes\" title=\"\">\n      <tab type=\"filelist\" visible=\"yes\" title=\"\" intro=\"\"/>\n      <tab type=\"globals\" visible=\"yes\" title=\"\" intro=\"\"/>\n    </tab>\n    <tab type=\"examples\" visible=\"yes\" title=\"\" intro=\"\"/>\n    <tab type=\"user\" url=\"/doxygen-c/html/classes.html\" title=\"C++ API\"/>\n    <tab type=\"user\" url=\"/doxygen-python/html/annotated.html\" title=\"Python API\"/>\n    <tab type=\"user\" url=\"https://github.com/caffe2/caffe2\" title=\"GitHub\"/>\n  </navindex>\n\n  <!-- Layout definition for a class page -->\n  <class>\n    <briefdescription visible=\"yes\"/>\n    <includes visible=\"$SHOW_INCLUDE_FILES\"/>\n    <inheritancegraph visible=\"$CLASS_GRAPH\"/>\n    <collaborationgraph visible=\"$COLLABORATION_GRAPH\"/>\n    <memberdecl>\n      <nestedclasses visible=\"yes\" title=\"\"/>\n      <publictypes title=\"\"/>\n      <services title=\"\"/>\n      <interfaces title=\"\"/>\n      <publicslots title=\"\"/>\n      <signals title=\"\"/>\n      <publicmethods title=\"\"/>\n      <publicstaticmethods title=\"\"/>\n      <publicattributes title=\"\"/>\n      <publicstaticattributes title=\"\"/>\n      <protectedtypes title=\"\"/>\n      <protectedslots title=\"\"/>\n      <protectedmethods title=\"\"/>\n      <protectedstaticmethods title=\"\"/>\n      <protectedattributes title=\"\"/>\n      <protectedstaticattributes title=\"\"/>\n      <packagetypes title=\"\"/>\n      <packagemethods title=\"\"/>\n      <packagestaticmethods title=\"\"/>\n      <packageattributes title=\"\"/>\n      <packagestaticattributes title=\"\"/>\n      <properties title=\"\"/>\n      <events title=\"\"/>\n      <privatetypes title=\"\"/>\n      <privateslots title=\"\"/>\n      <privatemethods title=\"\"/>\n      <privatestaticmethods title=\"\"/>\n      <privateattributes title=\"\"/>\n      <privatestaticattributes title=\"\"/>\n      <friends title=\"\"/>\n      <related title=\"\" subtitle=\"\"/>\n      <membergroups visible=\"yes\"/>\n    </memberdecl>\n    <detaileddescription title=\"\"/>\n    <memberdef>\n      <inlineclasses title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <services title=\"\"/>\n      <interfaces title=\"\"/>\n      <constructors title=\"\"/>\n      <functions title=\"\"/>\n      <related title=\"\"/>\n      <variables title=\"\"/>\n      <properties title=\"\"/>\n      <events title=\"\"/>\n    </memberdef>\n    <allmemberslink visible=\"yes\"/>\n    <usedfiles visible=\"$SHOW_USED_FILES\"/>\n    <authorsection visible=\"yes\"/>\n  </class>\n\n  <!-- Layout definition for a namespace page -->\n  <namespace>\n    <briefdescription visible=\"yes\"/>\n    <memberdecl>\n      <nestednamespaces visible=\"yes\" title=\"\"/>\n      <constantgroups visible=\"yes\" title=\"\"/>\n      <classes visible=\"yes\" title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <functions title=\"\"/>\n      <variables title=\"\"/>\n      <membergroups visible=\"yes\"/>\n    </memberdecl>\n    <detaileddescription title=\"\"/>\n    <memberdef>\n      <inlineclasses title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <functions title=\"\"/>\n      <variables title=\"\"/>\n    </memberdef>\n    <authorsection visible=\"yes\"/>\n  </namespace>\n\n  <!-- Layout definition for a file page -->\n  <file>\n    <briefdescription visible=\"yes\"/>\n    <includes visible=\"$SHOW_INCLUDE_FILES\"/>\n    <includegraph visible=\"$INCLUDE_GRAPH\"/>\n    <includedbygraph visible=\"$INCLUDED_BY_GRAPH\"/>\n    <sourcelink visible=\"yes\"/>\n    <memberdecl>\n      <classes visible=\"yes\" title=\"\"/>\n      <namespaces visible=\"yes\" title=\"\"/>\n      <constantgroups visible=\"yes\" title=\"\"/>\n      <defines title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <functions title=\"\"/>\n      <variables title=\"\"/>\n      <membergroups visible=\"yes\"/>\n    </memberdecl>\n    <detaileddescription title=\"\"/>\n    <memberdef>\n      <inlineclasses title=\"\"/>\n      <defines title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <functions title=\"\"/>\n      <variables title=\"\"/>\n    </memberdef>\n    <authorsection/>\n  </file>\n\n  <!-- Layout definition for a group page -->\n  <group>\n    <briefdescription visible=\"yes\"/>\n    <groupgraph visible=\"$GROUP_GRAPHS\"/>\n    <memberdecl>\n      <nestedgroups visible=\"yes\" title=\"\"/>\n      <dirs visible=\"yes\" title=\"\"/>\n      <files visible=\"yes\" title=\"\"/>\n      <namespaces visible=\"yes\" title=\"\"/>\n      <classes visible=\"yes\" title=\"\"/>\n      <defines title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <enumvalues title=\"\"/>\n      <functions title=\"\"/>\n      <variables title=\"\"/>\n      <signals title=\"\"/>\n      <publicslots title=\"\"/>\n      <protectedslots title=\"\"/>\n      <privateslots title=\"\"/>\n      <events title=\"\"/>\n      <properties title=\"\"/>\n      <friends title=\"\"/>\n      <membergroups visible=\"yes\"/>\n    </memberdecl>\n    <detaileddescription title=\"\"/>\n    <memberdef>\n      <pagedocs/>\n      <inlineclasses title=\"\"/>\n      <defines title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <enumvalues title=\"\"/>\n      <functions title=\"\"/>\n      <variables title=\"\"/>\n      <signals title=\"\"/>\n      <publicslots title=\"\"/>\n      <protectedslots title=\"\"/>\n      <privateslots title=\"\"/>\n      <events title=\"\"/>\n      <properties title=\"\"/>\n      <friends title=\"\"/>\n    </memberdef>\n    <authorsection visible=\"yes\"/>\n  </group>\n\n  <!-- Layout definition for a directory page -->\n  <directory>\n    <briefdescription visible=\"yes\"/>\n    <directorygraph visible=\"yes\"/>\n    <memberdecl>\n      <dirs visible=\"yes\"/>\n      <files visible=\"yes\"/>\n    </memberdecl>\n    <detaileddescription title=\"\"/>\n  </directory>\n</doxygenlayout>\n"
  },
  {
    "path": "docs/DoxygenLayout-python.xml",
    "content": "<doxygenlayout version=\"1.0\">\n  <!-- Generated by doxygen 1.8.14 -->\n  <!-- Navigation index tabs for HTML output -->\n  <navindex>\n    <tab type=\"mainpage\" visible=\"no\" title=\"\"/>\n    <tab type=\"pages\" visible=\"yes\" title=\"\" intro=\"\"/>\n    <tab type=\"modules\" visible=\"yes\" title=\"\" intro=\"\"/>\n    <tab type=\"namespaces\" visible=\"yes\" title=\"\">\n      <tab type=\"namespacelist\" visible=\"yes\" title=\"\" intro=\"\"/>\n      <tab type=\"namespacemembers\" visible=\"yes\" title=\"\" intro=\"\"/>\n    </tab>\n    <tab type=\"classes\" visible=\"yes\" title=\"\">\n      <tab type=\"classlist\" visible=\"yes\" title=\"\" intro=\"\"/>\n      <tab type=\"classindex\" visible=\"$ALPHABETICAL_INDEX\" title=\"\"/>\n      <tab type=\"hierarchy\" visible=\"yes\" title=\"\" intro=\"\"/>\n      <tab type=\"classmembers\" visible=\"yes\" title=\"\" intro=\"\"/>\n    </tab>\n    <tab type=\"files\" visible=\"yes\" title=\"\">\n      <tab type=\"filelist\" visible=\"yes\" title=\"\" intro=\"\"/>\n      <tab type=\"globals\" visible=\"yes\" title=\"\" intro=\"\"/>\n    </tab>\n    <tab type=\"examples\" visible=\"yes\" title=\"\" intro=\"\"/>\n    <tab type=\"user\" url=\"/doxygen-c/html/classes.html\" title=\"C++ API\"/>\n    <tab type=\"user\" url=\"/doxygen-python/html/annotated.html\" title=\"Python API\"/>\n    <tab type=\"user\" url=\"https://github.com/caffe2/caffe2\" title=\"GitHub\"/>\n  </navindex>\n\n  <!-- Layout definition for a class page -->\n  <class>\n    <briefdescription visible=\"yes\"/>\n    <includes visible=\"$SHOW_INCLUDE_FILES\"/>\n    <inheritancegraph visible=\"$CLASS_GRAPH\"/>\n    <collaborationgraph visible=\"$COLLABORATION_GRAPH\"/>\n    <memberdecl>\n      <nestedclasses visible=\"yes\" title=\"\"/>\n      <publictypes title=\"\"/>\n      <services title=\"\"/>\n      <interfaces title=\"\"/>\n      <publicslots title=\"\"/>\n      <signals title=\"\"/>\n      <publicmethods title=\"\"/>\n      <publicstaticmethods title=\"\"/>\n      <publicattributes title=\"\"/>\n      <publicstaticattributes title=\"\"/>\n      <protectedtypes title=\"\"/>\n      <protectedslots title=\"\"/>\n      <protectedmethods title=\"\"/>\n      <protectedstaticmethods title=\"\"/>\n      <protectedattributes title=\"\"/>\n      <protectedstaticattributes title=\"\"/>\n      <packagetypes title=\"\"/>\n      <packagemethods title=\"\"/>\n      <packagestaticmethods title=\"\"/>\n      <packageattributes title=\"\"/>\n      <packagestaticattributes title=\"\"/>\n      <properties title=\"\"/>\n      <events title=\"\"/>\n      <privatetypes title=\"\"/>\n      <privateslots title=\"\"/>\n      <privatemethods title=\"\"/>\n      <privatestaticmethods title=\"\"/>\n      <privateattributes title=\"\"/>\n      <privatestaticattributes title=\"\"/>\n      <friends title=\"\"/>\n      <related title=\"\" subtitle=\"\"/>\n      <membergroups visible=\"yes\"/>\n    </memberdecl>\n    <detaileddescription title=\"\"/>\n    <memberdef>\n      <inlineclasses title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <services title=\"\"/>\n      <interfaces title=\"\"/>\n      <constructors title=\"\"/>\n      <functions title=\"\"/>\n      <related title=\"\"/>\n      <variables title=\"\"/>\n      <properties title=\"\"/>\n      <events title=\"\"/>\n    </memberdef>\n    <allmemberslink visible=\"yes\"/>\n    <usedfiles visible=\"$SHOW_USED_FILES\"/>\n    <authorsection visible=\"yes\"/>\n  </class>\n\n  <!-- Layout definition for a namespace page -->\n  <namespace>\n    <briefdescription visible=\"yes\"/>\n    <memberdecl>\n      <nestednamespaces visible=\"yes\" title=\"\"/>\n      <constantgroups visible=\"yes\" title=\"\"/>\n      <classes visible=\"yes\" title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <functions title=\"\"/>\n      <variables title=\"\"/>\n      <membergroups visible=\"yes\"/>\n    </memberdecl>\n    <detaileddescription title=\"\"/>\n    <memberdef>\n      <inlineclasses title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <functions title=\"\"/>\n      <variables title=\"\"/>\n    </memberdef>\n    <authorsection visible=\"yes\"/>\n  </namespace>\n\n  <!-- Layout definition for a file page -->\n  <file>\n    <briefdescription visible=\"yes\"/>\n    <includes visible=\"$SHOW_INCLUDE_FILES\"/>\n    <includegraph visible=\"$INCLUDE_GRAPH\"/>\n    <includedbygraph visible=\"$INCLUDED_BY_GRAPH\"/>\n    <sourcelink visible=\"yes\"/>\n    <memberdecl>\n      <classes visible=\"yes\" title=\"\"/>\n      <namespaces visible=\"yes\" title=\"\"/>\n      <constantgroups visible=\"yes\" title=\"\"/>\n      <defines title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <functions title=\"\"/>\n      <variables title=\"\"/>\n      <membergroups visible=\"yes\"/>\n    </memberdecl>\n    <detaileddescription title=\"\"/>\n    <memberdef>\n      <inlineclasses title=\"\"/>\n      <defines title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <functions title=\"\"/>\n      <variables title=\"\"/>\n    </memberdef>\n    <authorsection/>\n  </file>\n\n  <!-- Layout definition for a group page -->\n  <group>\n    <briefdescription visible=\"yes\"/>\n    <groupgraph visible=\"$GROUP_GRAPHS\"/>\n    <memberdecl>\n      <nestedgroups visible=\"yes\" title=\"\"/>\n      <dirs visible=\"yes\" title=\"\"/>\n      <files visible=\"yes\" title=\"\"/>\n      <namespaces visible=\"yes\" title=\"\"/>\n      <classes visible=\"yes\" title=\"\"/>\n      <defines title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <enumvalues title=\"\"/>\n      <functions title=\"\"/>\n      <variables title=\"\"/>\n      <signals title=\"\"/>\n      <publicslots title=\"\"/>\n      <protectedslots title=\"\"/>\n      <privateslots title=\"\"/>\n      <events title=\"\"/>\n      <properties title=\"\"/>\n      <friends title=\"\"/>\n      <membergroups visible=\"yes\"/>\n    </memberdecl>\n    <detaileddescription title=\"\"/>\n    <memberdef>\n      <pagedocs/>\n      <inlineclasses title=\"\"/>\n      <defines title=\"\"/>\n      <typedefs title=\"\"/>\n      <enums title=\"\"/>\n      <enumvalues title=\"\"/>\n      <functions title=\"\"/>\n      <variables title=\"\"/>\n      <signals title=\"\"/>\n      <publicslots title=\"\"/>\n      <protectedslots title=\"\"/>\n      <privateslots title=\"\"/>\n      <events title=\"\"/>\n      <properties title=\"\"/>\n      <friends title=\"\"/>\n    </memberdef>\n    <authorsection visible=\"yes\"/>\n  </group>\n\n  <!-- Layout definition for a directory page -->\n  <directory>\n    <briefdescription visible=\"yes\"/>\n    <directorygraph visible=\"yes\"/>\n    <memberdecl>\n      <dirs visible=\"yes\"/>\n      <files visible=\"yes\"/>\n    </memberdecl>\n    <detaileddescription title=\"\"/>\n  </directory>\n</doxygenlayout>\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Docs Notes\n\nCaffe2 docs are split up into three areas:\n\n1) Caffe2.ai website: this comes from the gh-pages branch via the markdown files found in `gh-pages:caffe2/_docs`; you can run this locally with jekyll and push changes directly to github if you're a contributor without the need for a PR\n2) Operators catalogue: this comes from scripts in the master branch found in `master:caffe2/python/docs`; running `github_generator > operators-catalogue.md` and moving this md file to `gh-pages:caffe2/_docs` manually is currently the process\n3) C++ API and Python API Doxygen docs: these are generated by Doxygen manually or by using `master:caffe2/docs/process.py` and copied to the `gh-pages:caffe2/doxygen-c` and `gh-pages:caffe2/doxygen-python` folders\n\n## Docs Maintenance Setup\n\nYou need to have access to the master branch to generate the docs from source and you need access to the gh-pages branch to publish them. Since you often want the files simultaneously and since the folder structures between master and gh-pages differ so greatly, it is advised to clone each branch to different folders. Otherwise if you switch back and forth between gh-pages and master within the same local repo, you'll end up seeing folders in branches that shouldn't be there, and it can get really confusing once you start running Doxygen and generating thousands of new files.\n\n```\ncd ~\ngit clone https://github.com/caffe2/caffe2.git c2master && cd c2master && git checkout master\ncd ~\ngit clone https://github.com/caffe2/caffe2.git c2docs && cd c2docs && git checkout gh-pages\n```\n\n## Generating Operator Catalog\n\nTo update the operator catalog, a script must be run on the master branch and copied to your docs checkout. To trigger this use:\n\n1. `cd ~/c2master`\n2. `python caffe2/python/docs/github.py ~/c2docs/_docs/operators-catalogue.md`\n\n## Generating API Docs with Doxygen\n\nSupport for generating the docs has been included in the CMake build process, but it is turned off by default. To trigger building of the docs use:\n\n1. `cd ~/c2master`\n2. `mkdir build && cd build`\n3. `cmake -DBUILD_DOCS=ON .. && make`\n\nThis will create a docs subfolder in the build folder. You can launch either API's web page docs by opening the index.html file found at `build/docs/doxygen-c/html/index.html` or `build/docs/doxygen-python/html/index.html`.\n\nTo push to caffe2.ai, copy these from the build directory to your docs checkout:\n1. `cd ~/c2docs`\n2. `rm -rf doxygen-c`\n3. `rm -rf doxygen-python`\n4. `cp -r ~/c2master/build/docs/doxygen-c .`\n5. `cp -r ~/c2master/build/docs/doxygen-python .`\n6. `git add -A .`\n\n### Install Doxygen\n\nYou will need to install Doxygen to build Caffe2 with the API docs.\n\n#### MacOS X\n\n1. Press Command+Space and type Terminal and press enter/return key.\n2. Run in Terminal app:\n   `ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\" < /dev/null 2> /dev/null`\n   and press enter/return key. Wait for the command to finish.\n3. Run:\n   `brew install doxygen`\n\nOther operating systems' installation instructions can be found on the [Doxygen website](https://www.stack.nl/~dimitri/doxygen/manual/install.html).\n\n### Doxygen Notes\n\nDoxygen seems to behave better if the processing between C++ and Python was split up. This is why there are two different links to cover each API.\n\nC++ API docs work out of the box with the Caffe2 source code. Python docs require “[python blocks](http://www.stack.nl/~dimitri/doxygen/manual/docblocks.html#pythonblocks)” which are (often) currently missing in the Python code.\n\nThe Doxygen customization includes these files in the doxygen folder:\n\n* header.html - logo links back to the main docs page\n* footer.html - includes the Facebook OSS footer\n* stylesheet.css - Doxygen's default CSS; tweaked to fix formatting problems with the custom logo, header, and footer\n* main.css - copied from the caffe2ai CSS, so this should be refreshed after the design changes (this overrides/extends stylesheet.css)\n\nIt also extracts info from markdown files found in the source tree. A legacy installation file was in the /docs folder and this was removed. These file show up in the top navigation under “Related Pages”.\n\n### Scripts for Adding the Doxygen Preamble and Publishing the Docs\n\nSome of the Python files may be missing the preamble Doxygen needs to generate docs, so the Python script called “process.py” that resides in the /docs folder is used to prepare the docs by looking for the block and if it doesn't exist prepend and customize the python blocks section with the module's path (e.g. Module caffe2.python.examples.char_rnn). It was noted that you need to delete the previous version of docs when you regenerate the docs or else things get messy, so the script deals with that as well.\n\nTo manually create API documents, go to your master checkout:\n\n1. `cd c2master`\n2. `cd docs && python process.py`\n\nIf you want to push these to caffe2.ai, go to your docs checkout:\n1. `cd c2docs`\n2. Copy the files generated in build/docs to your gh-pages branch, commit, and push.\n3. `doxygen-c` and `doxygen-python` both go in the root folder of `gh-pages`\n4. `operators-catalogue.md` goes in `_docs` \n\n### Running Doxygen Manually\n\nIt may be beneficial to run Doxygen on your own and not try to inject the preamble. Just note that any files without a preamble will not show up in the docs.\n\nTo do this, make sure you have doxygen installed and from the master branch root run:\n\n```\ndoxygen .Doxyfile-python\ndoxygen .Doxyfile-c\n```\n`.Doxyfile-python` and `.Doxyfile-c` are config files that are optimized for each code format. They each use Doxygen's config for `OUTPUT_DIRECTORY` which is set to docs/doxygen-python or docs/doxygen-c respectively.\n\n### Publishing the New Doxygen Files\n\nYou will not be able to `git push` to `gh-pages` until you have switched auth methods to SSH:\n\n```\ngit remote set-url origin git@github.com:caffe2/caffe2.git\n```\n\nDoxygen will tend to change and remove a lot of file and a simple copy can leave files behind that shouldn't be there anymore, so it is advised to delete the old doxygen-c and doxygen-python folders first. Copy the new files to the root folder of the gh-pages branch, making sure before you commit, you `git add` the deleted files, the new files, and the modified files. Then `git push` will push these changes without the need for a PR.\n\n### Maintaining the Doxygen Configs\n\nEach of the config files is customized differently and these changes are mentioned further below. A more common update though is the `EXCLUDE` config which is used to exclude whole directories and individual files. As new folders and special files are added it may make sense to update this so Python's API isn't including a bunch of new C++ info. You also end up with some third party files that you probably don't want to include in the docs.\n`FILE_PATTERNS` is supposed to prevent the Python & C++ docs from getting mixed up, but a combination of `FILE_PATTERNS` and `EXCLUDE` seems have worked best.\n\nSettings that were customized:\n\nOPTIMIZE_OUTPUT_JAVA - turned on for Python config, off for C++ config\nUSE_MDFILE_AS_MAINPAGE  - use to flag a markdown file for the mainpage\nEXTRACT_ALL\nQUIET\nWARN_IF_UNDOCUMENTED\nFILE_PATTERNS\nDOT_MULTI_TARGETS = YES\nJAVADOC_AUTOBRIEF = YES\nQUIET = YES\nSOURCE_BROWSER = YES\nVERBATIM_HEADERS = NO\nSHOW_NAMESPACES = NO for C++ config\n\nNot using this (was in old config file, but seems to be for Latex):\nEXTRA_PACKAGES = amsmath \\\namsfonts \\\nxr\n\n## Caffe2.ai\n\nFor more info on contributing to Caffe2.ai, take a look at [CONTRIBUTING.md](https://github.com/caffe2/caffe2/blob/gh-pages/CONTRIBUTING.md) found in the root of the gh-pages branch. This will help you setup jekyll locally so you can make your edits and test your changes. The most commonly updated areas are the markdown files in the `_docs` folder as well as the navigation structure (to add things to the sidebar) which is found at `_data/nav_docs.yml`. Each markdown file is required to have a header and if you wish for the markdown doc to appear as an item in the navigation the `id` in the yml file is equivalent to the `docid` in the markdown file.\n\nFor example in the Applications of Deep Learning markdown file we have:\n\n```\n---\ndocid: applications-of-deep-learning\ntitle: Applications of Deep Learning\nlayout: docs\npermalink: /docs/applications-of-deep-learning.html\n---\n```\n\nIn the `_data/nav_docs.yml` file we have:\n\n```\n- title: Quick Start\n  items:\n  - id: getting-started\n  - id: learn-more\n  - id: caffe-migration\n- title: Learn\n  items:\n  - id: applications-of-deep-learning\n  - id: operators\n  - id: mobile-integration\n...(more navigation here)...\n```\n\nTo link up the `docid: applications-of-deep-learning` markdown file to the navigation we have it listed under `- title: Learn` then under `items:` and then `- id: applications-of-deep-learning` using `id` instead of `docid`. The order in the yml file is how it is displayed in the browser, and it will pull the `title` from the markdown file's header and display that as the H1 for the page. In this example, `title: Applications of Deep Learning` shows \"Applications of Deep Learning\" at the top in H1 format, so in your markdown file there's no need to add your own H1 or single hash title for the page.\n\n### TODO:\n\nIn the future it would be ideal to expand these Operator Catalogue scripts to provide documentation for all C++ operators and Python modules. Instead it focuses on a subset of operators only, so in the interim Doxygen is used to backfill this gap.\n\nAdditionally, the operators-catalogue.md file that is generated is quite large and difficult to search, so breaking it up and adding categories to the Schema of the operators would be very helpful.\n\nTo achieve better output in the Doxygen Python docs:\nhttp://stackoverflow.com/questions/7690220/how-to-document-python-function-parameter-types\n\nSwap this kind of formatting into py files:\n\n```\ndef my_method(x, y):\"\"\"!\n    my_method description\n\n    @type x: int\n    @param x: An integer\n\n    @type y: int|string\n    @param y: An integer or string\n\n    @rtype: string\n    @return: Returns a sentence with your variables in it\n    \"\"\"return \"Hello World! %s, %s\" % (x,y)\n```\n\nNote that the bang (!) is added after the opening comment \"\"\"! - this seems to do the trick and the remaining comments will be nicely parsed by Doxygen.\n\n### Other Notes\n\nUseful for xcode, currently off\nGENERATE_DOCSET = NO\n\nLook at search engine integration, xml output, etc\nEXTERNAL_SEARCH = YES\n"
  },
  {
    "path": "docs/footer.html",
    "content": "<!-- HTML footer for doxygen 1.8.14-->\n<!-- start footer part -->\n<!--BEGIN GENERATE_TREEVIEW-->\n<div id=\"nav-path\" class=\"navpath\"><!-- id is needed for treeview function! -->\n  <ul>\n    $navpath\n    <li class=\"footer\">$generatedby\n    <a href=\"http://www.doxygen.org/index.html\">\n    <img class=\"footer\" src=\"$relpath^doxygen.png\" alt=\"doxygen\"/></a> $doxygenversion </li>\n  </ul>\n</div>\n<!--END GENERATE_TREEVIEW-->\n<!--BEGIN !GENERATE_TREEVIEW-->\n<hr class=\"footer\"/><address class=\"footer\"><small>\n$generatedby &#160;<a href=\"http://www.doxygen.org/index.html\">\n<img class=\"footer\" src=\"$relpath^doxygen.png\" alt=\"doxygen\"/>\n</a> $doxygenversion\n</small></address>\n<!--END !GENERATE_TREEVIEW-->\n<div class=\"footerContainer\">\n  <div id=\"footer_wrap\" class=\"wrapper footerWrapper\">\n    <div class=\"footerBlocks\">\n      <div id=\"fb_oss\" class=\"footerSection fbOpenSourceFooter\">\n          <svg class=\"facebookOSSLogoSvg\" viewBox=\"0 0 1133.9 1133.9\" x=\"0px\" y=\"0px\" height=50 width=50>\n            <g>\n              <path class=\"logoRing outerRing\" d=\"M 498.3 3.7 c 153.6 88.9 307.3 177.7 461.1 266.2 c 7.6 4.4 10.3 9.1 10.3 17.8 c -0.3 179.1 -0.2 358.3 0 537.4 c 0 8.1 -2.4 12.8 -9.7 17.1 c -154.5 88.9 -308.8 178.1 -462.9 267.5 c -9 5.2 -15.5 5.3 -24.6 0.1 c -153.9 -89.2 -307.9 -178 -462.1 -266.8 C 3 838.8 0 833.9 0 825.1 c 0.3 -179.1 0.2 -358.3 0 -537.4 c 0 -8.6 2.6 -13.6 10.2 -18 C 164.4 180.9 318.4 92 472.4 3 C 477 -1.5 494.3 -0.7 498.3 3.7 Z M 48.8 555.3 c 0 79.9 0.2 159.9 -0.2 239.8 c -0.1 10 3 15.6 11.7 20.6 c 137.2 78.8 274.2 157.8 411 237.3 c 9.9 5.7 17 5.7 26.8 0.1 c 137.5 -79.8 275.2 -159.2 412.9 -238.5 c 7.4 -4.3 10.5 -8.9 10.5 -17.8 c -0.3 -160.2 -0.3 -320.5 0 -480.7 c 0 -8.8 -2.8 -13.6 -10.3 -18 C 772.1 218 633.1 137.8 494.2 57.4 c -6.5 -3.8 -11.5 -4.5 -18.5 -0.5 C 336.8 137.4 197.9 217.7 58.8 297.7 c -7.7 4.4 -10.2 9.2 -10.2 17.9 C 48.9 395.5 48.8 475.4 48.8 555.3 Z\" />\n              <path class=\"logoRing middleRing\" d=\"M 184.4 555.9 c 0 -33.3 -1 -66.7 0.3 -100 c 1.9 -48 24.1 -86 64.7 -110.9 c 54.8 -33.6 110.7 -65.5 167 -96.6 c 45.7 -25.2 92.9 -24.7 138.6 1 c 54.4 30.6 108.7 61.5 162.2 93.7 c 44 26.5 67.3 66.8 68 118.4 c 0.9 63.2 0.9 126.5 0 189.7 c -0.7 50.6 -23.4 90.7 -66.6 116.9 c -55 33.4 -110.8 65.4 -167.1 96.5 c -43.4 24 -89 24.2 -132.3 0.5 c -57.5 -31.3 -114.2 -64 -170 -98.3 c -41 -25.1 -62.9 -63.7 -64.5 -112.2 C 183.5 621.9 184.3 588.9 184.4 555.9 Z M 232.9 556.3 c 0 29.5 0.5 59.1 -0.1 88.6 c -0.8 39.2 16.9 67.1 50.2 86.2 c 51.2 29.4 102.2 59.2 153.4 88.4 c 31.4 17.9 63.6 18.3 95 0.6 c 53.7 -30.3 107.1 -61.2 160.3 -92.5 c 29.7 -17.5 45 -44.5 45.3 -78.8 c 0.6 -61.7 0.5 -123.5 0 -185.2 c -0.3 -34.4 -15.3 -61.5 -44.9 -79 C 637.7 352.6 583 320.8 527.9 290 c -27.5 -15.4 -57.2 -16.1 -84.7 -0.7 c -56.9 31.6 -113.4 64 -169.1 97.6 c -26.4 15.9 -40.7 41.3 -41.1 72.9 C 232.6 491.9 232.9 524.1 232.9 556.3 Z\" />\n              <path class=\"logoRing innerRing\" d=\"M 484.9 424.4 c 69.8 -2.8 133.2 57.8 132.6 132 C 617 630 558.5 688.7 484.9 689.1 c -75.1 0.4 -132.6 -63.6 -132.7 -132.7 C 352.1 485 413.4 421.5 484.9 424.4 Z M 401.3 556.7 c -3.4 37.2 30.5 83.6 83 84.1 c 46.6 0.4 84.8 -37.6 84.9 -84 c 0.1 -46.6 -37.2 -84.4 -84.2 -84.6 C 432.2 472.1 397.9 518.3 401.3 556.7 Z\" />\n            </g>\n          </svg>\n        <h2>Facebook Open Source</h2>\n      </div>\n      <div class=\"footerSection\">\n        <a class=\"footerLink\" href=\"https://code.facebook.com/projects/\" target=\"_blank\">Open Source Projects</a>\n        <a class=\"footerLink\" href=\"https://github.com/facebook/\" target=\"_blank\">GitHub</a>\n        <a class=\"footerLink\" href=\"https://twitter.com/fbOpenSource\" target=\"_blank\">Twitter</a>\n      </div>\n      <div class=\"footerSection rightAlign\">\n        <a class=\"footerLink\" href=\"https://github.com/caffe2/caffe2\" target=\"_blank\">Contribute to this project on GitHub</a>\n      </div>\n    </div>\n  </div>\n</div>\n<script type=\"text/javascript\" src=\"/js/jekyll-link-anchors.js\"></script>\n<script>\n  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');\n\n  ga('create', '{{ site.gacode }}', 'auto');\n  ga('send', 'pageview');\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "docs/header.html",
    "content": "<!-- HTML header for doxygen 1.8.14-->\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/xhtml;charset=UTF-8\"/>\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=9\"/>\n<meta name=\"generator\" content=\"Doxygen $doxygenversion\"/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->\n<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->\n<link href=\"$relpath^tabs.css\" rel=\"stylesheet\" type=\"text/css\"/>\n<link rel=\"icon\" href=\"/static/favicon.png\" type=\"image/x-icon\">\n<script type=\"text/javascript\" src=\"$relpath^jquery.js\"></script>\n<script type=\"text/javascript\" src=\"$relpath^dynsections.js\"></script>\n$treeview\n$search\n$mathjax\n<link href=\"$relpath^$stylesheet\" rel=\"stylesheet\" type=\"text/css\" />\n$extrastylesheet\n</head>\n<body>\n<div id=\"top\"><!-- do not remove this div, it is closed by doxygen! -->\n\n<!--BEGIN TITLEAREA-->\n<div id=\"titlearea\">\n<table cellspacing=\"0\" cellpadding=\"0\">\n <tbody>\n <tr style=\"height: 56px;\">\n  <!--BEGIN PROJECT_LOGO-->\n  <td id=\"projectlogo\" width=\"56\"><a href=\"/\"><img alt=\"Logo\" src=\"$relpath^$projectlogo\"/></a></td>\n  <!--END PROJECT_LOGO-->\n  <!--BEGIN PROJECT_NAME-->\n  <td id=\"projectalign\" style=\"padding-left: 0.5em;\">\n   <div id=\"projectname\">$projectname\n   <!--BEGIN PROJECT_NUMBER-->&#160;<span id=\"projectnumber\">$projectnumber</span><!--END PROJECT_NUMBER-->\n   </div>\n   <!--BEGIN PROJECT_BRIEF--><div id=\"projectbrief\">$projectbrief</div><!--END PROJECT_BRIEF-->\n  </td>\n  <!--END PROJECT_NAME-->\n  <!--BEGIN !PROJECT_NAME-->\n   <!--BEGIN PROJECT_BRIEF-->\n    <td style=\"padding-left: 0.5em;\">\n    <div id=\"projectbrief\">$projectbrief</div>\n    </td>\n   <!--END PROJECT_BRIEF-->\n  <!--END !PROJECT_NAME-->\n  <!--BEGIN DISABLE_INDEX-->\n   <!--BEGIN SEARCHENGINE-->\n   <td>$searchbox</td>\n   <!--END SEARCHENGINE-->\n  <!--END DISABLE_INDEX-->\n </tr>\n </tbody>\n</table>\n</div>\n<!--END TITLEAREA-->\n<!-- end header part -->\n"
  },
  {
    "path": "docs/installation.md",
    "content": "## Building Caffe2\n\nThis guide builds from source. For alternatives, refer to https://caffe2.ai/docs/getting-started.html\n\nGet latest source from GitHub.\n\n    git clone --recursive https://github.com/caffe2/caffe2.git\n    cd caffe2\n\nNote that you might need to uninstall existing Eigen and pybind11 packages due to compile-time dependencies when building from source. For this reason, Caffe2 uses git submodules to reference external packages in the third_party folder. These are downloaded with the --recursive option.\n\n#### MacOS X\n\n    brew install openblas glog gtest automake protobuf leveled lmdb\n    mkdir build && cd build\n    cmake .. -DBLAS=OpenBLAS -DUSE_OPENCV=off\n    make\n\n#### Ubuntu\n\n###### Ubuntu 14.04 LTS\n    sudo apt-get install libprotobuf-dev protobuf-compiler libatlas-base-dev libgoogle-glog-dev libgtest-dev liblmdb-dev libleveldb-dev libsnappy-dev python-dev python-pip libiomp-dev libopencv-dev libpthread-stubs0-dev cmake\n    sudo pip install numpy\n    wget http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1404/x86_64/cuda-repo-ubuntu1404_8.0.44-1_amd64.deb\n    sudo dpkg -i cuda-repo-ubuntu1404_8.0.44-1_amd64.deb\n    sudo apt-get update\n    sudo apt-get install cuda\n    sudo apt-get install git\n\n    CUDNN_URL=\"http://developer.download.nvidia.com/compute/redist/cudnn/v5.1/cudnn-8.0-linux-x64-v5.1.tgz\" &&\n    curl -fsSL ${CUDNN_URL} -O &&\n    sudo tar -xzf cudnn-8.0-linux-x64-v5.1.tgz -C /usr/local &&\n    rm cudnn-8.0-linux-x64-v5.1.tgz &&\n    sudo ldconfig\n\n    mkdir build && cd build\n    cmake ..\n    make\n\n###### Ubuntu 16.04 LTS\n    sudo apt-get install libprotobuf-dev protobuf-compiler libatlas-base-dev libgoogle-glog-dev libgtest-dev liblmdb-dev libleveldb-dev libsnappy-dev python-dev python-pip libiomp-dev libopencv-dev libpthread-stubs0-dev cmake\n    sudo pip install numpy\n    wget http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/cuda-repo-ubuntu1604_8.0.61-1_amd64.deb\n    sudo dpkg -i cuda-repo-ubuntu1604_8.0.61-1_amd64.deb\n    sudo apt-get update\n    sudo apt-get install cuda\n    sudo apt-get install git\n\n    CUDNN_URL=\"http://developer.download.nvidia.com/compute/redist/cudnn/v5.1/cudnn-8.0-linux-x64-v5.1.tgz\" &&\n    curl -fsSL ${CUDNN_URL} -O &&\n    sudo tar -xzf cudnn-8.0-linux-x64-v5.1.tgz -C /usr/local &&\n    rm cudnn-8.0-linux-x64-v5.1.tgz &&\n    sudo ldconfig\n\n    mkdir build && cd build\n    cmake ..\n    make\n\n## Python support\n\nTo use Caffe2 in Python, you need two libraries, future and six.\n\n    pip install future six\n\nTo run the tutorials you'll need jupyter (formerly ipython) notebooks and matplotlib, which can be installed on MacOS X with\n\n    brew install matplotlib --with-python3\n    pip install jupyter\n"
  },
  {
    "path": "docs/main.css",
    "content": "@font-face{font-family:'Lato';src:url(\"/static/fonts/LatoLatin-Italic.woff2\") format(\"woff2\"),url(\"/static/fonts/LatoLatin-Italic.woff\") format(\"woff\");font-weight:normal;font-style:italic}@font-face{font-family:'Lato';src:url(\"/static/fonts/LatoLatin-Black.woff2\") format(\"woff2\"),url(\"/static/fonts/LatoLatin-Black.woff\") format(\"woff\");font-weight:900;font-style:normal}@font-face{font-family:'Lato';src:url(\"/static/fonts/LatoLatin-BlackItalic.woff2\") format(\"woff2\"),url(\"/static/fonts/LatoLatin-BlackItalic.woff\") format(\"woff\");font-weight:900;font-style:italic}@font-face{font-family:'Lato';src:url(\"/static/fonts/LatoLatin-Light.woff2\") format(\"woff2\"),url(\"/static/fonts/LatoLatin-Light.woff\") format(\"woff\");font-weight:300;font-style:normal}@font-face{font-family:'Lato';src:url(\"/static/fonts/LatoLatin-Regular.woff2\") format(\"woff2\"),url(\"/static/fonts/LatoLatin-Regular.woff\") format(\"woff\");font-weight:normal;font-style:normal}html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}body{background:#FFF;color:#303846;font:normal 18px/1.4em \"Lato\",Calibri,Arial,sans-serif;height:100vh;text-align:left;text-rendering:optimizeLegibility}img{max-width:100%}article p img{max-width:100%;display:block;margin-left:auto;margin-right:auto}a{border-bottom:1px dotted #a67b5b;color:#303846;text-decoration:none;-webkit-transition:all 0.3s;transition:all 0.3s}blockquote{padding:15px 30px 15px 15px;margin:20px 0 15px 10px;background-color:rgba(204,122,111,0.1);border-left:10px solid rgba(191,87,73,0.2)}#fb_oss a{border:0}h1,h2,h3,h4{font-family:\"Lato\",\"Helvetica Neue\",Arial,sans-serif;font-weight:900}.navPusher{border-top:58px solid #FFF;height:100%;left:0;position:relative;z-index:99}.homeContainer{background:#FFF;color:#4d4d4d;text-align:center}.homeContainer a{color:#a67b5b}.homeContainer .homeSplashFade{color:white}.homeContainer .homeWrapper{padding:3em 10px;text-align:center}.homeContainer .homeWrapper .wrapper{margin:0px auto;max-width:900px;padding:0 20px}.homeContainer .homeWrapper .projectLogo img{height:100px;margin-bottom:0px}.homeContainer .homeWrapper h1#project_title{font-family:\"Lato\",\"Helvetica Neue\",Arial,sans-serif;font-size:300%;letter-spacing:-0.08em;line-height:1em;margin-bottom:80px}.homeContainer .homeWrapper h2#project_tagline{font-family:\"Lato\",\"Helvetica Neue\",Arial,sans-serif;font-size:200%;letter-spacing:-0.04em;line-height:1em;color:#99424f}.wrapper{margin:0px auto;max-width:900px;padding:0 10px}.projectLogo{display:none}.projectLogo img{height:100px;margin-bottom:0px}section#intro{margin:10px 0;color:#999}section#intro p{line-height:1.5;padding-bottom:20px}section#intro ul{list-style:disc}section#intro ol,section#intro ul{padding-left:24px}section#intro ol li,section#intro ul li{padding-bottom:8px;padding-left:6px}section#intro strong,section#intro b{font-weight:bold}.fbossFontLight{font-family:\"Lato\",Calibri,Arial,sans-serif;font-weight:300;font-style:normal}.fb-like{display:block;margin-bottom:20px;width:100%}.center{display:block;text-align:center}.mainContainer{background:#FFF;overflow:auto}.mainContainer .mainWrapper{padding:4vh 10px;text-align:left}.mainContainer .mainWrapper .allShareBlock{padding:10px 0}.mainContainer .mainWrapper .allShareBlock .pluginBlock{margin:12px 0;padding:0}.mainContainer .mainWrapper :not(.gist-meta)>a:hover,.mainContainer .mainWrapper :not(.gist-meta)>a:focus{background:#FFF;color:#4d4d4d}.mainContainer .mainWrapper em,.mainContainer .mainWrapper i{font-style:italic}.mainContainer .mainWrapper strong,.mainContainer .mainWrapper b{font-weight:bold}.mainContainer .mainWrapper h1{font-size:300%;line-height:1em;padding:1.4em 0 1em;text-align:left}.mainContainer .mainWrapper h2{font-size:250%;line-height:1em;margin-bottom:20px;padding:1.4em 0 20px;text-align:left}.mainContainer .mainWrapper h2{border-bottom:1px solid #e6e6e6;font-size:22px;padding:10px 0}.mainContainer .mainWrapper h2.blockHeader{border-bottom:1px solid white;color:white;font-size:22px;margin-bottom:20px;padding:10px 0}.mainContainer .mainWrapper h3{font-size:150%;line-height:1.2em;padding:1em 0 0.8em}.mainContainer .mainWrapper h4{font-size:130%;line-height:1.2em;padding:1em 0 0.8em}.mainContainer .mainWrapper code{color:#999;display:inline-block}.mainContainer .mainWrapper p{padding:0.8em 0}.mainContainer .mainWrapper ul{list-style:disc}.mainContainer .mainWrapper ol,.mainContainer .mainWrapper ul{padding-left:24px}.mainContainer .mainWrapper ol li,.mainContainer .mainWrapper ul li{padding-bottom:4px;padding-left:6px}.mainContainer .mainWrapper strong{font-weight:bold}.mainContainer .mainWrapper .post{position:relative}.mainContainer .mainWrapper .post .katex{font-weight:700}.mainContainer .mainWrapper .post.basicPost{margin-top:30px}.mainContainer .mainWrapper .post :not(.gist-meta)>a{color:#a67b5b}.mainContainer .mainWrapper .post :not(.gist-meta)>a:hover,.mainContainer .mainWrapper .post :not(.gist-meta)>a:focus{color:#4d4d4d}.mainContainer .mainWrapper .post h2{border-bottom:4px solid #FFF;font-size:130%}.mainContainer .mainWrapper .post h3{border-bottom:1px solid #FFF;font-size:110%}.mainContainer .mainWrapper .post h4{border-bottom:1px solid #FFF;font-size:90%}.mainContainer .mainWrapper .post ol{list-style:decimal outside none}.mainContainer .mainWrapper .post .post-header{padding:1em 0}.mainContainer .mainWrapper .post .post-header h1{font-size:150%;line-height:1em;padding:0.4em 0 0}.mainContainer .mainWrapper .post .post-header h1 a{border:none}.mainContainer .mainWrapper .post .post-header .post-meta{color:#a67b5b;font-family:\"Lato\",\"Helvetica Neue\",Arial,sans-serif;text-align:center}.mainContainer .mainWrapper .post .postSocialPlugins{padding-top:1em}.mainContainer .mainWrapper .post .docPagination{background:#FFF;bottom:0px;left:0px;position:absolute;right:0px}.mainContainer .mainWrapper .post .docPagination .pager{display:inline-block;width:50%}.mainContainer .mainWrapper .post .docPagination .pagingNext{float:right;text-align:right}.mainContainer .mainWrapper .post .docPagination :not(.gist-meta)>a{border:none;color:#a67b5b;display:block;padding:4px 12px}.mainContainer .mainWrapper .post .docPagination :not(.gist-meta)>a:hover{background-color:#4d4d4d;color:#303846}.mainContainer .mainWrapper .post .docPagination :not(.gist-meta)>a .pagerLabel{display:inline}.mainContainer .mainWrapper .post .docPagination :not(.gist-meta)>a .pagerTitle{display:none}.mainContainer .mainWrapper .posts .post{margin-bottom:6vh}#integrations_title{font-size:250%;margin:80px 0}.ytVideo{height:0;overflow:hidden;padding-bottom:53.4%;padding-top:25px;position:relative}.ytVideo iframe,.ytVideo object,.ytVideo embed{height:100%;left:0;position:absolute;top:0;width:100%}@media only screen and (min-width: 480px){h1#project_title{font-size:500%}h2#project_tagline{font-size:250%;color:#999}.projectLogo img{margin-bottom:10px;height:200px}.homeContainer .homeWrapper{padding-left:10px;padding-right:10px}.mainContainer .mainWrapper .post h2{font-size:180%}.mainContainer .mainWrapper .post h3{font-size:120%}.mainContainer .mainWrapper .post h4{font-size:100%}.mainContainer .mainWrapper .post .docPagination a .pagerLabel{display:none}.mainContainer .mainWrapper .post .docPagination a .pagerTitle{display:inline}}@media only screen and (min-width: 900px){.homeContainer .homeWrapper{position:relative}.homeContainer .homeWrapper .projectLogo{align-items:center;bottom:0;display:flex;justify-content:flex-end;left:0;padding:2em 20px 4em;position:absolute;right:20px;top:0}.homeContainer .homeWrapper .projectLogo img{height:100%;max-height:250px}}@media only screen and (min-width: 1024px){.mainContainer .mainWrapper .post{box-sizing:border-box;display:block}.mainContainer .mainWrapper .post ul#markdown-toc{font-size:14px;list-style-type:none;display:block}.mainContainer .mainWrapper .post ul#markdown-toc li{text-align:right;width:30%;float:left;margin-bottom:-1px}.mainContainer .mainWrapper .post .post-header h1{font-size:250%}.mainContainer .mainWrapper .posts .post{margin-bottom:4vh;width:100%}}@media only screen and (min-width: 1200px){.wrapper{max-width:1100px}}@media only screen and (min-width: 1500px){.wrapper{max-width:1400px}}.fixedHeaderContainer{background:#a67b5b;color:#4d4d4d;height:40px;padding:10px 0 8px;position:fixed;width:100%;z-index:9999}.fixedHeaderContainer a{align-items:center;border:0;color:#4d4d4d;display:flex;flex-flow:row nowrap;height:40px}.fixedHeaderContainer header{display:flex;flex-flow:row nowrap;position:relative;text-align:left}.fixedHeaderContainer header img{height:50px;padding-right:4px}.fixedHeaderContainer header h2{display:block;font-family:\"Lato\",\"Helvetica Neue\",Arial,sans-serif;font-weight:900;line-height:18px;position:relative;font-size:22px;color:#191919;letter-spacing:1px}.navigationFull{height:34px;margin-left:auto}.navigationFull nav{position:relative}.navigationFull nav ul{display:flex;flex-flow:row nowrap;margin:0 -10px}.navigationFull nav ul li{padding:0 10px;display:block}.navigationFull nav ul li a{border-bottom:2px solid transparent;color:#fff;font-size:16px;font-weight:400;line-height:1.2em}.navigationFull nav ul li a:hover{border-bottom:2px solid #4d4d4d;color:#4d4d4d}.navigationFull nav ul li.navItemActive a{color:#4d4d4d}input[type=\"search\"]{-moz-appearance:none;-webkit-appearance:none}.navSearchWrapper{align-self:center;position:relative}.navSearchWrapper::before{border:3px solid #ccc;border-radius:50%;content:\" \";display:block;height:6px;left:15px;width:6px;position:absolute;top:4px;z-index:1}.navSearchWrapper::after{background:#ccc;content:\" \";height:7px;left:24px;position:absolute;transform:rotate(-45deg);top:12px;width:3px;z-index:1}.navSearchWrapper .aa-dropdown-menu{background:#FFF;border:3px solid rgba(48,56,70,0.25);color:#303846;font-size:14px;left:auto !important;line-height:1.2em;right:0 !important}.navSearchWrapper .aa-dropdown-menu .algolia-docsearch-suggestion--category-header{background:#a67b5b;color:#FFF}.navSearchWrapper .aa-dropdown-menu .algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--highlight{background-color:#FFF;color:#a67b5b}.navSearchWrapper .aa-dropdown-menu .algolia-docsearch-suggestion--title .algolia-docsearch-suggestion--highlight,.navSearchWrapper .aa-dropdown-menu .algolia-docsearch-suggestion--subcategory-column .algolia-docsearch-suggestion--highlight{color:#a67b5b}.navSearchWrapper .aa-dropdown-menu .algolia-docsearch-suggestion__secondary,.navSearchWrapper .aa-dropdown-menu .algolia-docsearch-suggestion--subcategory-column{border-color:rgba(48,56,70,0.3)}input#search_input{padding-left:25px;font-size:14px;line-height:20px;border-radius:20px;background-color:rgba(153,153,153,0.25);border:none;color:rgba(153,153,153,0);outline:none;position:relative;transition:background-color 0.2s cubic-bezier(0.68, -0.55, 0.265, 1.55),width 0.2s cubic-bezier(0.68, -0.55, 0.265, 1.55),color 0.2s ease;width:200px}input#search_input:focus,input#search_input:active{background-color:#FFF;color:#303846;width:240px}.navigationSlider .navSearchWrapper::before{left:6px;top:6px}.navigationSlider .navSearchWrapper::after{left:15px;top:14px}.navigationSlider input#search_input_react{box-sizing:border-box;padding-left:25px;font-size:14px;line-height:20px;border-radius:20px;background-color:rgba(153,153,153,0.25);border:none;color:#303846;outline:none;position:relative;transition:background-color 0.2s cubic-bezier(0.68, -0.55, 0.265, 1.55),width 0.2s cubic-bezier(0.68, -0.55, 0.265, 1.55),color 0.2s ease;width:100%}.navigationSlider input#search_input_react:focus,.navigationSlider input#search_input_react:active{background-color:#FFF;color:#4d4d4d}.navigationSlider .algolia-docsearch-suggestion--subcategory-inline{display:none}.navigationSlider>span{width:100%}.navigationSlider .aa-dropdown-menu{background:#FFF;border:0px solid #FFF;color:#303846;font-size:12px;line-height:2em;max-height:140px;min-width:auto;overflow-y:scroll;-webkit-overflow-scrolling:touch;padding:0;border-radius:0;position:relative !important;width:100%}.rougeHighlight{background-color:#e9e9e9;color:#a67b5b}.rougeHighlight .c{color:#586e75}.rougeHighlight .err{color:#a67b5b}.rougeHighlight .g{color:#a67b5b}.rougeHighlight .k{color:#859900}.rougeHighlight .l{color:#a67b5b}.rougeHighlight .n{color:#a67b5b}.rougeHighlight .o{color:#859900}.rougeHighlight .x{color:#cb4b16}.rougeHighlight .p{color:#a67b5b}.rougeHighlight .cm{color:#586e75}.rougeHighlight .cp{color:#859900}.rougeHighlight .c1{color:#72c02c}.rougeHighlight .cs{color:#859900}.rougeHighlight .gd{color:#2aa198}.rougeHighlight .ge{color:#a67b5b;font-style:italic}.rougeHighlight .gr{color:#dc322f}.rougeHighlight .gh{color:#cb4b16}.rougeHighlight .gi{color:#859900}.rougeHighlight .go{color:#a67b5b}.rougeHighlight .gp{color:#a67b5b}.rougeHighlight .gs{color:#a67b5b;font-weight:bold}.rougeHighlight .gu{color:#cb4b16}.rougeHighlight .gt{color:#a67b5b}.rougeHighlight .kc{color:#cb4b16}.rougeHighlight .kd{color:#268bd2}.rougeHighlight .kn{color:#859900}.rougeHighlight .kp{color:#859900}.rougeHighlight .kr{color:#268bd2}.rougeHighlight .kt{color:#dc322f}.rougeHighlight .ld{color:#a67b5b}.rougeHighlight .m{color:#2aa198}.rougeHighlight .s{color:#2aa198}.rougeHighlight .na{color:#a67b5b}.rougeHighlight .nb{color:#B58900}.rougeHighlight .nc{color:#268bd2}.rougeHighlight .no{color:#cb4b16}.rougeHighlight .nd{color:#268bd2}.rougeHighlight .ni{color:#cb4b16}.rougeHighlight .ne{color:#cb4b16}.rougeHighlight .nf{color:#268bd2}.rougeHighlight .nl{color:#a67b5b}.rougeHighlight .nn{color:#a67b5b}.rougeHighlight .nx{color:#a67b5b}.rougeHighlight .py{color:#a67b5b}.rougeHighlight .nt{color:#268bd2}.rougeHighlight .nv{color:#268bd2}.rougeHighlight .ow{color:#859900}.rougeHighlight .w{color:#a67b5b}.rougeHighlight .mf{color:#2aa198}.rougeHighlight .mh{color:#2aa198}.rougeHighlight .mi{color:#2aa198}.rougeHighlight .mo{color:#2aa198}.rougeHighlight .sb{color:#586e75}.rougeHighlight .sc{color:#2aa198}.rougeHighlight .sd{color:#a67b5b}.rougeHighlight .s2{color:#2aa198}.rougeHighlight .se{color:#cb4b16}.rougeHighlight .sh{color:#a67b5b}.rougeHighlight .si{color:#2aa198}.rougeHighlight .sx{color:#2aa198}.rougeHighlight .sr{color:#dc322f}.rougeHighlight .s1{color:#2aa198}.rougeHighlight .ss{color:#2aa198}.rougeHighlight .bp{color:#268bd2}.rougeHighlight .vc{color:#268bd2}.rougeHighlight .vg{color:#268bd2}.rougeHighlight .vi{color:#268bd2}.rougeHighlight .il{color:#2aa198}.highlighter-rouge{color:#5e9f24;font:800 12px/1.5em Hack, monospace;max-width:100%}.highlighter-rouge .rougeHighlight{border-radius:3px;margin:20px 0;padding:0px;overflow-x:scroll;-webkit-overflow-scrolling:touch}.highlighter-rouge .rougeHighlight table{background:none;border:none}.highlighter-rouge .rougeHighlight table tbody tr{background:none;display:flex;flex-flow:row nowrap}.highlighter-rouge .rougeHighlight table tbody tr td{display:block;flex:1 1}.highlighter-rouge .rougeHighlight table tbody tr td.gutter{border-right:1px solid #fff;color:#c1a38d;margin-right:10px;max-width:40px;padding-right:10px}.highlighter-rouge .rougeHighlight table tbody tr td.gutter pre{max-width:20px}p>.highlighter-rouge,li>.highlighter-rouge,a>.highlighter-rouge{font-size:16px;font-weight:400;line-height:inherit}a:hover .highlighter-rouge{color:white}.promoSection{display:flex;flex-flow:column wrap;font-size:125%;line-height:1.6em;margin:-10px 0;position:relative;z-index:99}.promoSection .promoRow{padding:10px 0}.promoSection .promoRow .pluginWrapper{display:block}.promoSection .promoRow .pluginWrapper.ghWatchWrapper,.promoSection .promoRow .pluginWrapper.ghStarWrapper{height:28px}.promoSection .promoRow .pluginRowBlock{display:flex;flex-flow:wrap;justify-content:center;margin:0 -2px}.promoSection .promoRow .pluginRowBlock .pluginWrapper{padding:0 2px}iframe.pluginIframe{height:500px;margin-top:20px;width:100%}.iframeContent{display:none}.iframePreview{display:inline-block;margin-top:20px}@media only screen and (min-width: 1024px){.iframeContent{display:block}.iframePreview{display:none}}.button{border:1px solid #FFF;border-radius:3px;color:#FFF;display:inline-block;font-size:14px;font-weight:900;line-height:1.2em;padding:10px;text-transform:uppercase;transition:background 0.3s, color 0.3s}.button:hover{background:#FFF;color:#4d4d4d}.homeContainer .button{border-color:#99424f;border-width:1px;color:#99424f}.homeContainer .button:hover{background:#99424f;color:#FFF}.blockButton{display:block}.edit-page-link{float:right;font-size:14px;font-weight:normal;line-height:20px;opacity:0.6;transition:opacity 0.5s}.edit-page-link:hover{opacity:1}.gridBlockWrapper{background:#f9f9f9}.gridBlockWrapper.alternateBackground{background:#e9e9e9}.gridBlock{margin:0px auto;padding:0 10px;padding-top:100px;padding-bottom:50px;max-width:1200px}.gridBlock h3{width:100%;text-align:left;color:#999;font-size:20px;margin-top:-40px}.gridBlock .blockElement{padding:5px 0;align-items:center}.gridBlock .blockElement img{max-width:100%}.gridBlock .blockElement h3{font-size:40px;margin:0;padding:10px 0}.gridBlock .gridClear{clear:both}.gridBlock .alignCenter{text-align:center}.gridBlock .alignRight{text-align:right}.gridBlock .imageAlignSide{justify-content:center;align-items:center;display:flex;flex-flow:row wrap}.blockImage{max-width:900px;width:50%}.imageAlignTop .blockImage{margin-bottom:20px}.imageAlignTop.alignCenter .blockImage{margin-left:auto;margin-right:auto}.imageAlignSide p{margin-bottom:40px;max-width:560px;margin:0}.imageAlignSide .blockImage{flex:0 1 400px;margin-right:100px}.imageAlignSide .blockContent{flex:1 1}.imageAlignSide .blockContent p{padding:0}@media only screen and (max-width: 1023px){.responsiveList .blockContent{position:relative}.responsiveList .blockContent>div{padding-left:20px}.responsiveList .blockContent::before{content:\"\\2022\";position:absolute}}@media only screen and (min-width: 1024px){.gridBlock{display:flex;flex-direction:row;flex-wrap:wrap}.gridBlock .oneByGridBlock{box-sizing:border-box;flex:1 0 100%;padding:10px}.gridBlock .twoByGridBlock{box-sizing:border-box;flex:1 0 50%;padding:10px}.gridBlock .fourByGridBlock{box-sizing:border-box;flex:1 0 25%;padding:10px}h2+.gridBlock{padding-top:20px}}@media only screen and (min-width: 1400px){.gridBlock{display:flex;flex-direction:row;flex-wrap:wrap}.gridBlock .oneByGridBlock{box-sizing:border-box;flex:1 0 100%;padding:10px 20px}.gridBlock .twoByGridBlock{box-sizing:border-box;flex:1 0 50%;padding:10px 20px}.gridBlock .fourByGridBlock{box-sizing:border-box;flex:1 0 25%;padding:10px 20px}}.poweredByContainer{background:#FFF;color:#4d4d4d;margin-bottom:20px}.poweredByContainer a{color:#4d4d4d}.poweredByContainer .poweredByWrapper h2{border-color:#999;color:#999}.poweredByContainer .poweredByMessage{color:#999;font-size:14px;padding-top:20px}.poweredByItems{display:flex;flex-flow:row wrap;margin:0 -10px}.poweredByItem{box-sizing:border-box;flex:1 0 50%;line-height:1.1em;padding:5px 10px}.poweredByItem.itemLarge{flex-basis:100%;padding:10px;text-align:center}.poweredByItem.itemLarge:nth-child(4){padding-bottom:20px}.poweredByItem.itemLarge img{max-height:30px}@media only screen and (min-width: 480px){.itemLarge{flex-basis:50%;max-width:50%}}@media only screen and (min-width: 1024px){.poweredByItem{flex-basis:25%;max-width:25%}.poweredByItem.itemLarge{padding-bottom:20px;text-align:left}}.footerContainer{background:#FFF;color:#a67b5b;overflow:hidden;padding:0 10px;text-align:left}.footerContainer .footerWrapper{border-top:1px solid #a67b5b;padding:0}.footerContainer .footerWrapper .footerBlocks{align-items:center;align-content:center;display:flex;flex-flow:row wrap;margin:0 -20px;padding:10px 0}.footerContainer .footerWrapper .footerSection{box-sizing:border-box;flex:1 1 25%;font-size:14px;min-width:275px;padding:0px 20px}.footerContainer .footerWrapper .footerSection a{border:0;color:inherit;display:inline-block;line-height:1.2em}.footerContainer .footerWrapper .footerSection .footerLink{padding-right:20px}.footerContainer .footerWrapper .fbOpenSourceFooter{align-items:center;display:flex;flex-flow:row nowrap;max-width:25%}.footerContainer .footerWrapper .fbOpenSourceFooter .facebookOSSLogoSvg{flex:0 0 31px;height:30px;margin-right:10px;width:31px}.footerContainer .footerWrapper .fbOpenSourceFooter .facebookOSSLogoSvg path{fill:#a67b5b}.footerContainer .footerWrapper .fbOpenSourceFooter .facebookOSSLogoSvg .middleRing{opacity:0.7}.footerContainer .footerWrapper .fbOpenSourceFooter .facebookOSSLogoSvg .innerRing{opacity:0.45}.footerContainer .footerWrapper .fbOpenSourceFooter h2{display:block;font-weight:900;line-height:1em}@media only screen and (min-width: 900px){.footerSection.rightAlign{margin-left:auto;max-width:25%;text-align:right}}.navigationFull{display:none}.navigationSlider{position:absolute;right:0px}.navigationSlider .navSlideout{cursor:pointer;padding-top:4px;position:absolute;right:10px;top:0;transition:top 0.3s;z-index:101}.navigationSlider .slidingNav{background:#a67b5b;box-sizing:border-box;height:0px;overflow-x:hidden;padding:0;position:absolute;right:0px;top:0;transition:height 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55),width 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);width:0}.navigationSlider .slidingNav ul{flex-flow:column nowrap;list-style:none;padding:10px}.navigationSlider .slidingNav ul li{margin:0;padding:2px 0}.navigationSlider .slidingNav ul li a{color:#FFF;display:inline;margin:3px 5px;padding:2px 0px;transition:background-color 0.3s}.navigationSlider .slidingNav ul li a:focus,.navigationSlider .slidingNav ul li a:hover{border-bottom:2px solid #FFF}.navigationSlider .navSlideoutActive .slidingNav{height:auto;padding-top:48px;width:300px}.navigationSlider .navSlideoutActive .navSlideout{top:-2px}.navigationSlider .navSlideoutActive .navSlideout .menuExpand span:nth-child(1){background-color:#303846;top:16px;transform:rotate(45deg)}.navigationSlider .navSlideoutActive .navSlideout .menuExpand span:nth-child(2){opacity:0}.navigationSlider .navSlideoutActive .navSlideout .menuExpand span:nth-child(3){background-color:#303846;transform:rotate(-45deg)}.menuExpand{display:flex;flex-flow:column nowrap;height:20px;justify-content:space-between}.menuExpand span{background:#4d4d4d;border-radius:3px;display:block;flex:0 0 4px;height:4px;position:relative;top:0;transition:background-color 0.3s, top 0.3s, opacity 0.3s, transform 0.3s;width:20px}.navPusher{border-top:58px solid #FFF;position:relative;left:0;z-index:99;height:100%}.navPusher::after{position:absolute;top:0;right:0;width:0;height:0;background:rgba(0,0,0,0.4);content:'';opacity:0;-webkit-transition:opacity 0.5s, width 0.1s 0.5s, height 0.1s 0.5s;transition:opacity 0.5s, width 0.1s 0.5s, height 0.1s 0.5s}.sliderActive .navPusher::after{width:100%;height:100%;opacity:1;-webkit-transition:opacity 0.5s;transition:opacity 0.5s;z-index:100}@media only screen and (min-width: 1024px){.navigationFull{display:block}.navigationSlider{display:none}}.docsNavContainer{background:#d9d9d9;height:35px;left:0;position:fixed;width:100%;z-index:100}.docMainWrapper .wrapper.mainWrapper{padding-left:0;padding-right:0;padding-top:10px}.docsSliderActive .docsNavContainer{box-sizing:border-box;height:100%;overflow-y:auto;-webkit-overflow-scrolling:touch;padding-bottom:50px}.docsSliderActive .mainContainer{display:none}.navBreadcrumb{box-sizing:border-box;display:flex;flex-flow:row nowrap;font-size:12px;height:35px;overflow:hidden;padding:5px 10px}.navBreadcrumb a,.navBreadcrumb span{border:0;color:#303846}.navBreadcrumb i{padding:0 3px}nav.toc{position:relative}nav.toc section{padding:0px;position:relative}nav.toc section .navGroups{display:none;padding:40px 10px 10px}nav.toc .toggleNav{background:#d9d9d9;color:#303846;position:relative;transition:background-color 0.3s, color 0.3s}nav.toc .toggleNav .navToggle{cursor:pointer;height:24px;margin-right:10px;position:relative;text-align:left;width:18px}nav.toc .toggleNav .navToggle::before,nav.toc .toggleNav .navToggle::after{content:\"\";position:absolute;top:50%;left:0;left:8px;width:3px;height:6px;border:5px solid #303846;border-width:5px 0;margin-top:-8px;transform:rotate(45deg);z-index:1}nav.toc .toggleNav .navToggle::after{transform:rotate(-45deg)}nav.toc .toggleNav .navToggle i::before,nav.toc .toggleNav .navToggle i::after{content:\"\";position:absolute;top:50%;left:2px;background:transparent;border-width:0 5px 5px;border-style:solid;border-color:transparent #303846;height:0;margin-top:-7px;opacity:1;width:5px;z-index:10}nav.toc .toggleNav .navToggle i::after{border-width:5px 5px 0;margin-top:2px}nav.toc .toggleNav .navGroup{background:#bfbfbf;margin:1px 0}nav.toc .toggleNav .navGroup ul{display:none}nav.toc .toggleNav .navGroup h3{background:#bfbfbf;color:#303846;font-size:20px;font-weight:600;line-height:1.2em;padding:10px;transition:color 0.2s}nav.toc .toggleNav .navGroup h3 i:not(:empty){width:16px;height:16px;display:inline-block;box-sizing:border-box;text-align:center;color:rgba(48,56,70,0.5);margin-right:10px;transition:color 0.2s}nav.toc .toggleNav .navGroup.navGroupActive{background:#f2f2f2;color:#303846}nav.toc .toggleNav .navGroup.navGroupActive ul{display:block;padding-bottom:10px;padding-top:10px}nav.toc .toggleNav .navGroup.navGroupActive h3{background:#f2f2f2;color:#4d4d4d}nav.toc .toggleNav .navGroup.navGroupActive h3 i{display:none}nav.toc .toggleNav ul{padding-left:0;padding-right:24px}nav.toc .toggleNav ul li{list-style-type:none;padding-bottom:0;padding-left:0}nav.toc .toggleNav ul li a{border:none;color:#303846;display:inline-block;font-size:14px;line-height:1.1em;margin:2px 10px 5px;padding:5px 0 2px;transition:color 0.3s}nav.toc .toggleNav ul li a:hover,nav.toc .toggleNav ul li a:focus{color:#FFF}nav.toc .toggleNav ul li a.navItemActive{color:#a67b5b}nav.toc .toggleNavActive .navBreadcrumb{background:#d9d9d9;margin-bottom:20px;position:fixed;width:100%}nav.toc .toggleNavActive section .navGroups{display:block}nav.toc .toggleNavActive .navToggle::before,nav.toc .toggleNavActive .navToggle::after{border-width:6px 0;height:0px;margin-top:-6px}nav.toc .toggleNavActive .navToggle i{opacity:0}.docsNavVisible .navPusher .mainContainer{padding-top:35px}@media only screen and (min-width: 900px){.navBreadcrumb{padding:5px 0}nav.toc section .navGroups{padding:40px 0 0}}@media only screen and (min-width: 1024px){.navToggle{display:none}.docsSliderActive .mainContainer{display:block}.docsNavVisible .navPusher .mainContainer{padding-top:0}.docsNavContainer{background:none;box-sizing:border-box;height:auto;margin:40px 40px 0 0;overflow-y:auto;position:relative;width:300px}nav.toc section .navGroups{display:block;padding-top:0px}nav.toc .toggleNavActive .navBreadcrumb{margin-bottom:0;position:relative}.docMainWrapper{display:flex;flex-flow:row nowrap;margin-bottom:40px}.docMainWrapper .wrapper{padding-left:0;padding-right:0}.docMainWrapper .wrapper.mainWrapper{padding-top:0}.navBreadcrumb{display:none}.navBreadcrumb h2{padding:0 10px}}.blogContainer .posts{margin-top:60px}.blogContainer .posts .post{border:1px solid #FFF;border-radius:3px;padding:10px 20px 20px}.blogContainer .lonePost{margin-top:60px}.blogContainer .lonePost .post{padding:10px 0px 0px}.blogContainer .post-header h1{text-align:center}.blogContainer .post-header .post-authorName{color:rgba(48,56,70,0.7);font-size:14px;font-weight:900;margin-top:0;padding:0;text-align:center}.blogContainer .post-header .authorPhoto{border-radius:50%;height:50px;left:50%;margin-left:-25px;overflow:hidden;position:absolute;top:-25px;width:50px}table{background:#F8F8F8;border:1px solid #B0B0B0;position:relative;margin:10px auto;padding:0;width:100%;height:auto;border-collapse:collapse;text-align:center;table-layout:fixed}table thead{border-bottom:1px solid #B0B0B0;display:table-header-group}table tbody{display:table-row-group}table tr{display:table-row}table tr:nth-of-type(odd){background:#E8E8E8}table tr th,table tr td{border-right:1px dotted #B0B0B0;display:table-cell;font-size:14px;line-height:1.3em;padding:10px;text-align:left;vertical-align:top}table tr th:last-of-type,table tr td:last-of-type{border-right:0}table tr th code,table tr td code{color:#97dccf;display:inline-block;font-size:12px}table tr th{color:#000000;font-weight:bold;font-family:\"Lato\",\"Helvetica Neue\",Arial,sans-serif;text-transform:uppercase}.mainContainer .mainWrapper .post .toggler :not(.gist-meta)>a{color:#99424f}.mainContainer .mainWrapper .toggler :not(.gist-meta)>a:hover,.mainContainer .mainWrapper .toggler :not(.gist-meta)>a:focus{background:#a67b5b;color:#99424f}.toggler a{display:inline-block;padding:10px 5px;margin:2px;border:1px solid #05A5D1;border-radius:3px;text-decoration:none !important}.toggler table{border-collapse:collapse;margin-top:50px}.toggler table,td,th{border:0}.toggler strong{font-size:24px;color:#a67b5b}.display-platform-mac .toggler .button-mac,.display-platform-ubuntu .toggler .button-ubuntu,.display-platform-centos .toggler .button-centos,.display-platform-windows .toggler .button-windows,.display-platform-ios .toggler .button-ios,.display-platform-android .toggler .button-android,.display-configuration-compile .toggler .button-compile,.display-configuration-prebuilt .toggler .button-prebuilt,.display-configuration-docker .toggler .button-docker,.display-configuration-cloud .toggler .button-cloud{background-color:#a67b5b}block{display:none}.display-platform-mac.display-configuration-prebuilt .mac.prebuilt,.display-platform-ubuntu.display-configuration-prebuilt .ubuntu.prebuilt,.display-platform-centos.display-configuration-prebuilt .centos.prebuilt,.display-platform-windows.display-configuration-prebuilt .windows.prebuilt,.display-platform-ios.display-configuration-prebuilt .ios.prebuilt,.display-platform-android.display-configuration-prebuilt .android.prebuilt,.display-platform-mac.display-configuration-compile .mac.compile,.display-platform-ubuntu.display-configuration-compile .ubuntu.compile,.display-platform-centos.display-configuration-compile .centos.compile,.display-platform-windows.display-configuration-compile .windows.compile,.display-platform-ios.display-configuration-compile .ios.compile,.display-platform-android.display-configuration-compile .android.compile,.display-platform-mac.display-configuration-docker .mac.docker,.display-platform-ubuntu.display-configuration-docker .ubuntu.docker,.display-platform-centos.display-configuration-docker .centos.docker,.display-platform-windows.display-configuration-docker .windows.docker,.display-platform-ios.display-configuration-docker .ios.docker,.display-platform-android.display-configuration-docker .android.docker,.display-platform-mac.display-configuration-cloud .mac.cloud,.display-platform-ubuntu.display-configuration-cloud .ubuntu.cloud,.display-platform-centos.display-configuration-cloud .centos.cloud,.display-platform-windows.display-configuration-cloud .windows.cloud,.display-platform-ios.display-configuration-cloud .ios.cloud,.display-platform-android.display-configuration-cloud .android.cloud{display:block}a.anchor{position:absolute;margin-top:-58px}.header-link{position:absolute;margin-left:0.2em;opacity:0;-webkit-transition:opacity 0.2s ease-in-out 0.1s;-moz-transition:opacity 0.2s ease-in-out 0.1s;-ms-transition:opacity 0.2s ease-in-out 0.1s}h2:hover .header-link,h3:hover .header-link,h4:hover .header-link,h5:hover .header-link,h6:hover .header-link{opacity:1}.operator_search{width:90%;margin:2%;font-size:18px;font-family:\"Lato\",Calibri,Arial,sans-serif;border:1px #888 solid;border-radius:4px;outline:none}\n"
  },
  {
    "path": "docs/process.py",
    "content": "## @package process\n# Module doxygen.process\n# Script to insert preamble for doxygen and regen API docs\n\nimport glob, os, shutil\n\n# Module caffe2...caffe2.python.control_test\ndef insert(originalfile,first_line,description):\n    with open(originalfile,'r') as f:\n        f1 = f.readline()\n        if(f1.find(first_line)<0):\n            docs = first_line + description + f1\n            with open('newfile.txt','w') as f2:\n                f2.write(docs)\n                f2.write(f.read())\n            os.rename('newfile.txt',originalfile)\n        else:\n            print('already inserted')\n\n# move up from /caffe2_root/doxygen\nos.chdir(\"..\")\nos.system(\"git checkout caffe2/contrib/.\")\nos.system(\"git checkout caffe2/distributed/.\")\nos.system(\"git checkout caffe2/experiments/.\")\nos.system(\"git checkout caffe2/python/.\")\n\nfor root, dirs, files in os.walk(\".\"):\n    for file in files:\n        if (file.endswith(\".py\") and not file.endswith(\"_test.py\") and not file.endswith(\"__.py\")):\n            filepath = os.path.join(root, file)\n            print(\"filepath: \" + filepath)\n            directory = os.path.dirname(filepath)[2:]\n            directory = directory.replace(\"/\",\".\")\n            print \"directory: \" + directory\n            name = os.path.splitext(file)[0]\n            first_line = \"## @package \" + name\n            description = \"\\n# Module \" + directory + \".\" + name + \"\\n\"\n            print first_line,description\n            insert(filepath,first_line,description)\n\nif os.path.exists(\"doxygen/doxygen-python\"):\n    print(\"Looks like you ran this before, so we need to cleanup those old files...\")\n    shutil.rmtree(\"doxygen/doxygen-python\")\nelse:\n    os.makedirs(\"doxygen/doxygen-python\")\n\nif os.path.exists(\"doxygen/doxygen-c\"):\n    print(\"Looks like you ran this before, so we need to cleanup those old files...\")\n    shutil.rmtree(\"doxygen/doxygen-c\")\nelse:\n    os.makedirs(\"doxygen/doxygen-c\")\n\nos.system(\"doxygen .Doxyfile-python\")\nos.system(\"doxygen .Doxyfile-c\")\n"
  },
  {
    "path": "docs/stylesheet.css",
    "content": "/* The standard CSS for doxygen 1.8.14 */\n\nbody, table, div, p, dl {\n\tfont: 400 14px/22px Roboto,sans-serif;\n}\n\np.reference, p.definition {\n\tfont: 400 14px/22px Roboto,sans-serif;\n}\n\n/* @group Heading Levels */\n\nh1.groupheader {\n\tfont-size: 150%;\n}\n\n.title {\n\tfont: 400 14px/28px Roboto,sans-serif;\n\tfont-size: 150%;\n\tfont-weight: bold;\n\tmargin: 10px 2px;\n}\n\nh2.groupheader {\n\tborder-bottom: 1px solid #324770;\n\tcolor: #223354;\n\tfont-size: 150%;\n\tfont-weight: normal;\n\tmargin-top: 1.75em;\n\tpadding-top: 8px;\n\tpadding-bottom: 4px;\n\twidth: 100%;\n}\n\nh3.groupheader {\n\tfont-size: 100%;\n}\n\nh1, h2, h3, h4, h5, h6 {\n\t-webkit-transition: text-shadow 0.5s linear;\n\t-moz-transition: text-shadow 0.5s linear;\n\t-ms-transition: text-shadow 0.5s linear;\n\t-o-transition: text-shadow 0.5s linear;\n\ttransition: text-shadow 0.5s linear;\n\tmargin-right: 15px;\n}\n\nh1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow {\n\ttext-shadow: 0 0 15px cyan;\n}\n\ndt {\n\tfont-weight: bold;\n}\n\ndiv.multicol {\n\t-moz-column-gap: 1em;\n\t-webkit-column-gap: 1em;\n\t-moz-column-count: 3;\n\t-webkit-column-count: 3;\n}\n\np.startli, p.startdd {\n\tmargin-top: 2px;\n}\n\np.starttd {\n\tmargin-top: 0px;\n}\n\np.endli {\n\tmargin-bottom: 0px;\n}\n\np.enddd {\n\tmargin-bottom: 4px;\n}\n\np.endtd {\n\tmargin-bottom: 2px;\n}\n\n/* @end */\n\ncaption {\n\tfont-weight: bold;\n}\n\nspan.legend {\n        font-size: 70%;\n        text-align: center;\n}\n\nh3.version {\n        font-size: 90%;\n        text-align: center;\n}\n\ndiv.qindex, div.navtab{\n\tbackground-color: #EBEFF6;\n\tborder: 1px solid #A3B4D7;\n\ttext-align: center;\n}\n\ndiv.qindex, div.navpath {\n\twidth: 100%;\n\tline-height: 140%;\n}\n\ndiv.navtab {\n\tmargin-right: 15px;\n}\n\n/* @group Link Styling */\n\na {\n\tcolor: #3D578C;\n\tfont-weight: normal;\n\ttext-decoration: none;\n}\n\n.contents a:visited {\n\tcolor: #4665A2;\n}\n\na:hover {\n\ttext-decoration: underline;\n}\n\na.qindex {\n\tfont-weight: bold;\n}\n\na.qindexHL {\n\tfont-weight: bold;\n\tbackground-color: #9CAFD4;\n\tcolor: #ffffff;\n\tborder: 1px double #869DCA;\n}\n\n.contents a.qindexHL:visited {\n        color: #ffffff;\n}\n\na.el {\n\tfont-weight: bold;\n}\n\na.elRef {\n}\n\na.code, a.code:visited, a.line, a.line:visited {\n\tcolor: #4665A2;\n}\n\na.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited {\n\tcolor: #4665A2;\n}\n\n/* @end */\n\ndl.el {\n\tmargin-left: -1cm;\n}\n\npre.fragment {\n        border: 1px solid #C4CFE5;\n        background-color: #FBFCFD;\n        padding: 4px 6px;\n        margin: 4px 8px 4px 2px;\n        overflow: auto;\n        word-wrap: break-word;\n        font-size:  9pt;\n        line-height: 125%;\n        font-family: monospace, fixed;\n        font-size: 105%;\n}\n\ndiv.fragment {\n        padding: 0px;\n        margin: 4px 8px 4px 2px;\n\tbackground-color: #FBFCFD;\n\tborder: 1px solid #C4CFE5;\n}\n\ndiv.line {\n\tfont-family: monospace, fixed;\n        font-size: 13px;\n\tmin-height: 13px;\n\tline-height: 1.0;\n\ttext-wrap: unrestricted;\n\twhite-space: -moz-pre-wrap; /* Moz */\n\twhite-space: -pre-wrap;     /* Opera 4-6 */\n\twhite-space: -o-pre-wrap;   /* Opera 7 */\n\twhite-space: pre-wrap;      /* CSS3  */\n\tword-wrap: break-word;      /* IE 5.5+ */\n\ttext-indent: -53px;\n\tpadding-left: 53px;\n\tpadding-bottom: 0px;\n\tmargin: 0px;\n\t-webkit-transition-property: background-color, box-shadow;\n\t-webkit-transition-duration: 0.5s;\n\t-moz-transition-property: background-color, box-shadow;\n\t-moz-transition-duration: 0.5s;\n\t-ms-transition-property: background-color, box-shadow;\n\t-ms-transition-duration: 0.5s;\n\t-o-transition-property: background-color, box-shadow;\n\t-o-transition-duration: 0.5s;\n\ttransition-property: background-color, box-shadow;\n\ttransition-duration: 0.5s;\n}\n\ndiv.line:after {\n    content:\"\\000A\";\n    white-space: pre;\n}\n\ndiv.line.glow {\n\tbackground-color: cyan;\n\tbox-shadow: 0 0 10px cyan;\n}\n\n\nspan.lineno {\n\tpadding-right: 4px;\n\ttext-align: right;\n\tborder-right: 2px solid #0F0;\n\tbackground-color: #E8E8E8;\n        white-space: pre;\n}\nspan.lineno a {\n\tbackground-color: #D8D8D8;\n}\n\nspan.lineno a:hover {\n\tbackground-color: #C8C8C8;\n}\n\n.lineno {\n\t-webkit-touch-callout: none;\n\t-webkit-user-select: none;\n\t-khtml-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n\ndiv.ah, span.ah {\n\tbackground-color: black;\n\tfont-weight: bold;\n\tcolor: #ffffff;\n\tmargin-bottom: 3px;\n\tmargin-top: 3px;\n\tpadding: 0.2em;\n\tborder: solid thin #333;\n\tborder-radius: 0.5em;\n\t-webkit-border-radius: .5em;\n\t-moz-border-radius: .5em;\n\tbox-shadow: 2px 2px 3px #999;\n\t-webkit-box-shadow: 2px 2px 3px #999;\n\t-moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px;\n\tbackground-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#000),color-stop(0.3, #444));\n\tbackground-image: -moz-linear-gradient(center top, #eee 0%, #444 40%, #000 110%);\n}\n\ndiv.classindex ul {\n        list-style: none;\n        padding-left: 0;\n}\n\ndiv.classindex span.ai {\n        display: inline-block;\n}\n\ndiv.groupHeader {\n\tmargin-left: 16px;\n\tmargin-top: 12px;\n\tfont-weight: bold;\n}\n\ndiv.groupText {\n\tmargin-left: 16px;\n\tfont-style: italic;\n}\n\nbody {\n\tbackground-color: white;\n\tcolor: black;\n        margin: 0;\n}\n\ndiv.contents {\n\tmargin-top: 10px;\n\tmargin-left: 12px;\n\tmargin-right: 8px;\n}\n\ntd.indexkey {\n\tbackground-color: #EBEFF6;\n\tfont-weight: bold;\n\tborder: 1px solid #C4CFE5;\n\tmargin: 2px 0px 2px 0;\n\tpadding: 2px 10px;\n        white-space: nowrap;\n        vertical-align: top;\n}\n\ntd.indexvalue {\n\tbackground-color: #EBEFF6;\n\tborder: 1px solid #C4CFE5;\n\tpadding: 2px 10px;\n\tmargin: 2px 0px;\n}\n\ntr.memlist {\n\tbackground-color: #EEF1F7;\n}\n\np.formulaDsp {\n\ttext-align: center;\n}\n\nimg.formulaDsp {\n\n}\n\nimg.formulaInl {\n\tvertical-align: middle;\n}\n\ndiv.center {\n\ttext-align: center;\n        margin-top: 0px;\n        margin-bottom: 0px;\n        padding: 0px;\n}\n\ndiv.center img {\n\tborder: 0px;\n}\n\naddress.footer {\n\ttext-align: right;\n\tpadding-right: 12px;\n}\n\nimg.footer {\n\tborder: 0px;\n\tvertical-align: middle;\n}\n\n/* @group Code Colorization */\n\nspan.keyword {\n\tcolor: #008000\n}\n\nspan.keywordtype {\n\tcolor: #604020\n}\n\nspan.keywordflow {\n\tcolor: #e08000\n}\n\nspan.comment {\n\tcolor: #800000\n}\n\nspan.preprocessor {\n\tcolor: #806020\n}\n\nspan.stringliteral {\n\tcolor: #002080\n}\n\nspan.charliteral {\n\tcolor: #008080\n}\n\nspan.vhdldigit {\n\tcolor: #ff00ff\n}\n\nspan.vhdlchar {\n\tcolor: #000000\n}\n\nspan.vhdlkeyword {\n\tcolor: #700070\n}\n\nspan.vhdllogic {\n\tcolor: #ff0000\n}\n\nblockquote {\n        background-color: #F7F8FB;\n        border-left: 2px solid #9CAFD4;\n        margin: 0 24px 0 4px;\n        padding: 0 12px 0 16px;\n}\n\n/* @end */\n\n/*\n.search {\n\tcolor: #003399;\n\tfont-weight: bold;\n}\n\nform.search {\n\tmargin-bottom: 0px;\n\tmargin-top: 0px;\n}\n\ninput.search {\n\tfont-size: 75%;\n\tcolor: #000080;\n\tfont-weight: normal;\n\tbackground-color: #e8eef2;\n}\n*/\n\ntd.tiny {\n\tfont-size: 75%;\n}\n\n.dirtab {\n\tpadding: 4px;\n\tborder-collapse: collapse;\n\tborder: 1px solid #A3B4D7;\n}\n\nth.dirtab {\n\tbackground: #EBEFF6;\n\tfont-weight: bold;\n}\n\nhr {\n\theight: 0px;\n\tborder: none;\n\tborder-top: 1px solid #4A6AAA;\n}\n\nhr.footer {\n\theight: 1px;\n}\n\n/* @group Member Descriptions */\n\ntable.memberdecls {\n\tborder-spacing: 0px;\n\tpadding: 0px;\n}\n\n.memberdecls td, .fieldtable tr {\n\t-webkit-transition-property: background-color, box-shadow;\n\t-webkit-transition-duration: 0.5s;\n\t-moz-transition-property: background-color, box-shadow;\n\t-moz-transition-duration: 0.5s;\n\t-ms-transition-property: background-color, box-shadow;\n\t-ms-transition-duration: 0.5s;\n\t-o-transition-property: background-color, box-shadow;\n\t-o-transition-duration: 0.5s;\n\ttransition-property: background-color, box-shadow;\n\ttransition-duration: 0.5s;\n}\n\n.memberdecls td.glow, .fieldtable tr.glow {\n\tbackground-color: cyan;\n\tbox-shadow: 0 0 15px cyan;\n}\n\n.mdescLeft, .mdescRight,\n.memItemLeft, .memItemRight,\n.memTemplItemLeft, .memTemplItemRight, .memTemplParams {\n\tbackground-color: #F9FAFC;\n\tborder: none;\n\tmargin: 4px;\n\tpadding: 1px 0 0 8px;\n}\n\n.mdescLeft, .mdescRight {\n\tpadding: 0px 8px 4px 8px;\n\tcolor: #555;\n}\n\n.memSeparator {\n        border-bottom: 1px solid #DEE4F0;\n        line-height: 1px;\n        margin: 0px;\n        padding: 0px;\n}\n\n.memItemLeft, .memTemplItemLeft {\n        white-space: nowrap;\n}\n\n.memItemRight {\n\twidth: 100%;\n}\n\n.memTemplParams {\n\tcolor: #4665A2;\n        white-space: nowrap;\n\tfont-size: 80%;\n}\n\n/* @end */\n\n/* @group Member Details */\n\n/* Styles for detailed member documentation */\n\n.memtitle {\n\tpadding: 8px;\n\tborder-top: 1px solid #A8B8D9;\n\tborder-left: 1px solid #A8B8D9;\n\tborder-right: 1px solid #A8B8D9;\n\tborder-top-right-radius: 4px;\n\tborder-top-left-radius: 4px;\n\tmargin-bottom: -1px;\n\tbackground-image: url('nav_f.png');\n\tbackground-repeat: repeat-x;\n\tbackground-color: #E2E8F2;\n\tline-height: 1.25;\n\tfont-weight: 300;\n\tfloat:left;\n}\n\n.permalink\n{\n        font-size: 65%;\n        display: inline-block;\n        vertical-align: middle;\n}\n\n.memtemplate {\n\tfont-size: 80%;\n\tcolor: #4665A2;\n\tfont-weight: normal;\n\tmargin-left: 9px;\n}\n\n.memnav {\n\tbackground-color: #EBEFF6;\n\tborder: 1px solid #A3B4D7;\n\ttext-align: center;\n\tmargin: 2px;\n\tmargin-right: 15px;\n\tpadding: 2px;\n}\n\n.mempage {\n\twidth: 100%;\n}\n\n.memitem {\n\tpadding: 0;\n\tmargin-bottom: 10px;\n\tmargin-right: 5px;\n        -webkit-transition: box-shadow 0.5s linear;\n        -moz-transition: box-shadow 0.5s linear;\n        -ms-transition: box-shadow 0.5s linear;\n        -o-transition: box-shadow 0.5s linear;\n        transition: box-shadow 0.5s linear;\n        display: table !important;\n        width: 100%;\n}\n\n.memitem.glow {\n         box-shadow: 0 0 15px cyan;\n}\n\n.memname {\n        font-weight: 400;\n        margin-left: 6px;\n}\n\n.memname td {\n\tvertical-align: bottom;\n}\n\n.memproto, dl.reflist dt {\n        border-top: 1px solid #A8B8D9;\n        border-left: 1px solid #A8B8D9;\n        border-right: 1px solid #A8B8D9;\n        padding: 6px 0px 6px 0px;\n        color: #253555;\n        font-weight: bold;\n        text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9);\n        background-color: #DFE5F1;\n        /* opera specific markup */\n        box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15);\n        border-top-right-radius: 4px;\n        /* firefox specific markup */\n        -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px;\n        -moz-border-radius-topright: 4px;\n        /* webkit specific markup */\n        -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15);\n        -webkit-border-top-right-radius: 4px;\n\n}\n\n.overload {\n        font-family: \"courier new\",courier,monospace;\n\tfont-size: 65%;\n}\n\n.memdoc, dl.reflist dd {\n        border-bottom: 1px solid #A8B8D9;\n        border-left: 1px solid #A8B8D9;\n        border-right: 1px solid #A8B8D9;\n        padding: 6px 10px 2px 10px;\n        background-color: #FBFCFD;\n        border-top-width: 0;\n        background-image:url('nav_g.png');\n        background-repeat:repeat-x;\n        background-color: #FFFFFF;\n        /* opera specific markup */\n        border-bottom-left-radius: 4px;\n        border-bottom-right-radius: 4px;\n        box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15);\n        /* firefox specific markup */\n        -moz-border-radius-bottomleft: 4px;\n        -moz-border-radius-bottomright: 4px;\n        -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px;\n        /* webkit specific markup */\n        -webkit-border-bottom-left-radius: 4px;\n        -webkit-border-bottom-right-radius: 4px;\n        -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15);\n}\n\ndl.reflist dt {\n        padding: 5px;\n}\n\ndl.reflist dd {\n        margin: 0px 0px 10px 0px;\n        padding: 5px;\n}\n\n.paramkey {\n\ttext-align: right;\n}\n\n.paramtype {\n\twhite-space: nowrap;\n}\n\n.paramname {\n\tcolor: #602020;\n\twhite-space: nowrap;\n}\n.paramname em {\n\tfont-style: normal;\n}\n.paramname code {\n        line-height: 14px;\n}\n\n.params, .retval, .exception, .tparams {\n        margin-left: 0px;\n        padding-left: 0px;\n}\n\n.params .paramname, .retval .paramname {\n        font-weight: bold;\n        vertical-align: top;\n}\n\n.params .paramtype {\n        font-style: italic;\n        vertical-align: top;\n}\n\n.params .paramdir {\n        font-family: \"courier new\",courier,monospace;\n        vertical-align: top;\n}\n\ntable.mlabels {\n\tborder-spacing: 0px;\n}\n\ntd.mlabels-left {\n\twidth: 100%;\n\tpadding: 0px;\n}\n\ntd.mlabels-right {\n\tvertical-align: bottom;\n\tpadding: 0px;\n\twhite-space: nowrap;\n}\n\nspan.mlabels {\n        margin-left: 8px;\n}\n\nspan.mlabel {\n        background-color: #728DC1;\n        border-top:1px solid #5373B4;\n        border-left:1px solid #5373B4;\n        border-right:1px solid #C4CFE5;\n        border-bottom:1px solid #C4CFE5;\n\ttext-shadow: none;\n\tcolor: white;\n\tmargin-right: 4px;\n\tpadding: 2px 3px;\n\tborder-radius: 3px;\n\tfont-size: 7pt;\n\twhite-space: nowrap;\n\tvertical-align: middle;\n}\n\n\n\n/* @end */\n\n/* these are for tree view inside a (index) page */\n\ndiv.directory {\n        margin: 10px 0px;\n        border-top: 1px solid #9CAFD4;\n        border-bottom: 1px solid #9CAFD4;\n        width: 100%;\n}\n\n.directory table {\n        border-collapse:collapse;\n}\n\n.directory td {\n        margin: 0px;\n        padding: 0px;\n\tvertical-align: top;\n}\n\n.directory td.entry {\n        white-space: nowrap;\n        padding-right: 6px;\n\tpadding-top: 3px;\n}\n\n.directory td.entry a {\n        outline:none;\n}\n\n.directory td.entry a img {\n        border: none;\n}\n\n.directory td.desc {\n\n        padding-left: 6px;\n\tpadding-right: 6px;\n\tpadding-top: 3px;\n\tborder-left: 1px solid rgba(0,0,0,0.05);\n}\n\n.directory tr.even {\n\tpadding-left: 6px;\n\tbackground-color: #F7F8FB;\n}\n\n.directory img {\n\tvertical-align: -30%;\n}\n\n.directory .levels {\n        white-space: nowrap;\n        width: 100%;\n        text-align: right;\n        font-size: 9pt;\n}\n\n.directory .levels span {\n        cursor: pointer;\n        padding-left: 2px;\n        padding-right: 2px;\n\tcolor: #3D578C;\n}\n\n.arrow {\n    color: #9CAFD4;\n    -webkit-user-select: none;\n    -khtml-user-select: none;\n    -moz-user-select: none;\n    -ms-user-select: none;\n    user-select: none;\n    cursor: pointer;\n    font-size: 80%;\n    display: inline-block;\n    width: 16px;\n    height: 22px;\n}\n\n.icon {\n    font-family: Arial, Helvetica;\n    font-weight: bold;\n    font-size: 12px;\n    height: 14px;\n    width: 16px;\n    display: inline-block;\n    background-color: #728DC1;\n    color: white;\n    text-align: center;\n    border-radius: 4px;\n    margin-left: 2px;\n    margin-right: 2px;\n}\n\n.icona {\n    width: 24px;\n    height: 22px;\n    display: inline-block;\n}\n\n.iconfopen {\n    width: 24px;\n    height: 18px;\n    margin-bottom: 4px;\n    background-image:url('folderopen.png');\n    background-position: 0px -4px;\n    background-repeat: repeat-y;\n    vertical-align:top;\n    display: inline-block;\n}\n\n.iconfclosed {\n    width: 24px;\n    height: 18px;\n    margin-bottom: 4px;\n    background-image:url('folderclosed.png');\n    background-position: 0px -4px;\n    background-repeat: repeat-y;\n    vertical-align:top;\n    display: inline-block;\n}\n\n.icondoc {\n    width: 24px;\n    height: 18px;\n    margin-bottom: 4px;\n    background-image:url('doc.png');\n    background-position: 0px -4px;\n    background-repeat: repeat-y;\n    vertical-align:top;\n    display: inline-block;\n}\n\ntable.directory {\n    font: 400 14px Roboto,sans-serif;\n}\n\n/* @end */\n\ndiv.dynheader {\n        margin-top: 8px;\n\t-webkit-touch-callout: none;\n\t-webkit-user-select: none;\n\t-khtml-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n\naddress {\n\tfont-style: normal;\n\tcolor: #2A3D61;\n}\n\ntable.doxtable caption {\n\tcaption-side: top;\n}\n\ntable.doxtable {\n\tborder-collapse:collapse;\n        margin-top: 4px;\n        margin-bottom: 4px;\n}\n\ntable.doxtable td, table.doxtable th {\n\tborder: 1px solid #2D4068;\n\tpadding: 3px 7px 2px;\n}\n\ntable.doxtable th {\n\tbackground-color: #374F7F;\n\tcolor: #FFFFFF;\n\tfont-size: 110%;\n\tpadding-bottom: 4px;\n\tpadding-top: 5px;\n}\n\ntable.fieldtable {\n        /*width: 100%;*/\n        margin-bottom: 10px;\n        border: 1px solid #A8B8D9;\n        border-spacing: 0px;\n        -moz-border-radius: 4px;\n        -webkit-border-radius: 4px;\n        border-radius: 4px;\n        -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px;\n        -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15);\n        box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15);\n}\n\n.fieldtable td, .fieldtable th {\n        padding: 3px 7px 2px;\n}\n\n.fieldtable td.fieldtype, .fieldtable td.fieldname {\n        white-space: nowrap;\n        border-right: 1px solid #A8B8D9;\n        border-bottom: 1px solid #A8B8D9;\n        vertical-align: top;\n}\n\n.fieldtable td.fieldname {\n        padding-top: 3px;\n}\n\n.fieldtable td.fielddoc {\n        border-bottom: 1px solid #A8B8D9;\n        /*width: 100%;*/\n}\n\n.fieldtable td.fielddoc p:first-child {\n        margin-top: 0px;\n}\n\n.fieldtable td.fielddoc p:last-child {\n        margin-bottom: 2px;\n}\n\n.fieldtable tr:last-child td {\n        border-bottom: none;\n}\n\n.fieldtable th {\n        background-image:url('nav_f.png');\n        background-repeat:repeat-x;\n        background-color: #E2E8F2;\n        font-size: 90%;\n        color: #253555;\n        padding-bottom: 4px;\n        padding-top: 5px;\n        text-align:left;\n        font-weight: 400;\n        -moz-border-radius-topleft: 4px;\n        -moz-border-radius-topright: 4px;\n        -webkit-border-top-left-radius: 4px;\n        -webkit-border-top-right-radius: 4px;\n        border-top-left-radius: 4px;\n        border-top-right-radius: 4px;\n        border-bottom: 1px solid #A8B8D9;\n}\n\n\n.tabsearch {\n\ttop: 0px;\n\tleft: 10px;\n\theight: 36px;\n\tbackground-image: url('tab_b.png');\n\tz-index: 101;\n\toverflow: hidden;\n\tfont-size: 13px;\n}\n\n.navpath ul\n{\n\tfont-size: 11px;\n\tbackground-image:url('tab_b.png');\n\tbackground-repeat:repeat-x;\n\tbackground-position: 0 -5px;\n\theight:30px;\n\tline-height:30px;\n\tcolor:#8AA0CC;\n\tborder:solid 1px #C2CDE4;\n\toverflow:hidden;\n\tmargin:0px;\n\tpadding:0px;\n}\n\n.navpath li\n{\n\tlist-style-type:none;\n\tfloat:left;\n\tpadding-left:10px;\n\tpadding-right:15px;\n\tbackground-image:url('bc_s.png');\n\tbackground-repeat:no-repeat;\n\tbackground-position:right;\n\tcolor:#364D7C;\n}\n\n.navpath li.navelem a\n{\n\theight:32px;\n\tdisplay:block;\n\ttext-decoration: none;\n\toutline: none;\n\tcolor: #283A5D;\n\tfont-family: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif;\n\ttext-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9);\n\ttext-decoration: none;\n}\n\n.navpath li.navelem a:hover\n{\n\tcolor:#6884BD;\n}\n\n.navpath li.footer\n{\n        list-style-type:none;\n        float:right;\n        padding-left:10px;\n        padding-right:15px;\n        background-image:none;\n        background-repeat:no-repeat;\n        background-position:right;\n        color:#364D7C;\n        font-size: 8pt;\n}\n\n\ndiv.summary\n{\n\tfloat: right;\n\tfont-size: 8pt;\n\tpadding-right: 5px;\n\twidth: 50%;\n\ttext-align: right;\n}\n\ndiv.summary a\n{\n\twhite-space: nowrap;\n}\n\ntable.classindex\n{\n        margin: 10px;\n        white-space: nowrap;\n        margin-left: 3%;\n        margin-right: 3%;\n        width: 94%;\n        border: 0;\n        border-spacing: 0;\n        padding: 0;\n}\n\ndiv.ingroups\n{\n\tfont-size: 8pt;\n\twidth: 50%;\n\ttext-align: left;\n}\n\ndiv.ingroups a\n{\n\twhite-space: nowrap;\n}\n\ndiv.header\n{\n        background-image:url('nav_h.png');\n        background-repeat:repeat-x;\n\tbackground-color: #F9FAFC;\n\tmargin:  0px;\n\tborder-bottom: 1px solid #C4CFE5;\n}\n\ndiv.headertitle\n{\n\tpadding: 5px 5px 5px 10px;\n}\n\ndl\n{\n        padding: 0 0 0 10px;\n}\n\n/* dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug */\ndl.section\n{\n\tmargin-left: 0px;\n\tpadding-left: 0px;\n}\n\ndl.note\n{\n        margin-left:-7px;\n        padding-left: 3px;\n        border-left:4px solid;\n        border-color: #D0C000;\n}\n\ndl.warning, dl.attention\n{\n        margin-left:-7px;\n        padding-left: 3px;\n        border-left:4px solid;\n        border-color: #FF0000;\n}\n\ndl.pre, dl.post, dl.invariant\n{\n        margin-left:-7px;\n        padding-left: 3px;\n        border-left:4px solid;\n        border-color: #00D000;\n}\n\ndl.deprecated\n{\n        margin-left:-7px;\n        padding-left: 3px;\n        border-left:4px solid;\n        border-color: #505050;\n}\n\ndl.todo\n{\n        margin-left:-7px;\n        padding-left: 3px;\n        border-left:4px solid;\n        border-color: #00C0E0;\n}\n\ndl.test\n{\n        margin-left:-7px;\n        padding-left: 3px;\n        border-left:4px solid;\n        border-color: #3030E0;\n}\n\ndl.bug\n{\n        margin-left:-7px;\n        padding-left: 3px;\n        border-left:4px solid;\n        border-color: #C08050;\n}\n\ndl.section dd {\n\tmargin-bottom: 6px;\n}\n\n\n#projectlogo\n{\n\tvertical-align: bottom;\n\tborder-collapse: separate;\n}\n\n#projectlogo img\n{\n\tborder: 0px none;\n}\n\n#projectalign\n{\n        vertical-align: middle;\n}\n\n#projectname\n{\n\tfont: 300% Tahoma, Arial,sans-serif;\n\tmargin: 0px;\n\tpadding: 2px 0px;\n}\n\n#projectbrief\n{\n\tfont: 120% Tahoma, Arial,sans-serif;\n\tmargin: 0px;\n\tpadding: 0px;\n}\n\n#projectnumber\n{\n\tfont: 50% Tahoma, Arial,sans-serif;\n\tmargin: 0px;\n\tpadding: 0px;\n}\n\n#titlearea\n{\n\tpadding: 0px;\n\tmargin: 0px;\n\twidth: 100%;\n\tborder-bottom: 1px solid #5373B4;\n}\n\n.image\n{\n        text-align: center;\n}\n\n.dotgraph\n{\n        text-align: center;\n}\n\n.mscgraph\n{\n        text-align: center;\n}\n\n.plantumlgraph\n{\n        text-align: center;\n}\n\n.diagraph\n{\n        text-align: center;\n}\n\n.caption\n{\n\tfont-weight: bold;\n}\n\ndiv.zoom\n{\n\tborder: 1px solid #90A5CE;\n}\n\ndl.citelist {\n        margin-bottom:50px;\n}\n\ndl.citelist dt {\n        color:#334975;\n        float:left;\n        font-weight:bold;\n        margin-right:10px;\n        padding:5px;\n}\n\ndl.citelist dd {\n        margin:2px 0;\n        padding:5px 0;\n}\n\ndiv.toc {\n        padding: 14px 25px;\n        background-color: #F4F6FA;\n        border: 1px solid #D8DFEE;\n        border-radius: 7px 7px 7px 7px;\n        float: right;\n        height: auto;\n        margin: 0 8px 10px 10px;\n        width: 200px;\n}\n\ndiv.toc li {\n        background: url(\"bdwn.png\") no-repeat scroll 0 5px transparent;\n        font: 10px/1.2 Verdana,DejaVu Sans,Geneva,sans-serif;\n        margin-top: 5px;\n        padding-left: 10px;\n        padding-top: 2px;\n}\n\ndiv.toc h3 {\n        font: bold 12px/1.2 Arial,FreeSans,sans-serif;\n\tcolor: #4665A2;\n        border-bottom: 0 none;\n        margin: 0;\n}\n\ndiv.toc ul {\n        list-style: none outside none;\n        border: medium none;\n        padding: 0px;\n}\n\ndiv.toc li.level1 {\n        margin-left: 0px;\n}\n\ndiv.toc li.level2 {\n        margin-left: 15px;\n}\n\ndiv.toc li.level3 {\n        margin-left: 30px;\n}\n\ndiv.toc li.level4 {\n        margin-left: 45px;\n}\n\n.inherit_header {\n        font-weight: bold;\n        color: gray;\n        cursor: pointer;\n\t-webkit-touch-callout: none;\n\t-webkit-user-select: none;\n\t-khtml-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n\n.inherit_header td {\n        padding: 6px 0px 2px 5px;\n}\n\n.inherit {\n        display: none;\n}\n\ntr.heading h2 {\n        margin-top: 12px;\n        margin-bottom: 4px;\n}\n\n/* tooltip related style info */\n\n.ttc {\n        position: absolute;\n        display: none;\n}\n\n#powerTip {\n\tcursor: default;\n\twhite-space: nowrap;\n\tbackground-color: white;\n\tborder: 1px solid gray;\n\tborder-radius: 4px 4px 4px 4px;\n\tbox-shadow: 1px 1px 7px gray;\n\tdisplay: none;\n\tfont-size: smaller;\n\tmax-width: 80%;\n\topacity: 0.9;\n\tpadding: 1ex 1em 1em;\n\tposition: absolute;\n\tz-index: 2147483647;\n}\n\n#powerTip div.ttdoc {\n        color: grey;\n\tfont-style: italic;\n}\n\n#powerTip div.ttname a {\n        font-weight: bold;\n}\n\n#powerTip div.ttname {\n        font-weight: bold;\n}\n\n#powerTip div.ttdeci {\n        color: #006318;\n}\n\n#powerTip div {\n        margin: 0px;\n        padding: 0px;\n        font: 12px/16px Roboto,sans-serif;\n}\n\n#powerTip:before, #powerTip:after {\n\tcontent: \"\";\n\tposition: absolute;\n\tmargin: 0px;\n}\n\n#powerTip.n:after,  #powerTip.n:before,\n#powerTip.s:after,  #powerTip.s:before,\n#powerTip.w:after,  #powerTip.w:before,\n#powerTip.e:after,  #powerTip.e:before,\n#powerTip.ne:after, #powerTip.ne:before,\n#powerTip.se:after, #powerTip.se:before,\n#powerTip.nw:after, #powerTip.nw:before,\n#powerTip.sw:after, #powerTip.sw:before {\n\tborder: solid transparent;\n\tcontent: \" \";\n\theight: 0;\n\twidth: 0;\n\tposition: absolute;\n}\n\n#powerTip.n:after,  #powerTip.s:after,\n#powerTip.w:after,  #powerTip.e:after,\n#powerTip.nw:after, #powerTip.ne:after,\n#powerTip.sw:after, #powerTip.se:after {\n\tborder-color: rgba(255, 255, 255, 0);\n}\n\n#powerTip.n:before,  #powerTip.s:before,\n#powerTip.w:before,  #powerTip.e:before,\n#powerTip.nw:before, #powerTip.ne:before,\n#powerTip.sw:before, #powerTip.se:before {\n\tborder-color: rgba(128, 128, 128, 0);\n}\n\n#powerTip.n:after,  #powerTip.n:before,\n#powerTip.ne:after, #powerTip.ne:before,\n#powerTip.nw:after, #powerTip.nw:before {\n\ttop: 100%;\n}\n\n#powerTip.n:after, #powerTip.ne:after, #powerTip.nw:after {\n\tborder-top-color: #ffffff;\n\tborder-width: 10px;\n\tmargin: 0px -10px;\n}\n#powerTip.n:before {\n\tborder-top-color: #808080;\n\tborder-width: 11px;\n\tmargin: 0px -11px;\n}\n#powerTip.n:after, #powerTip.n:before {\n\tleft: 50%;\n}\n\n#powerTip.nw:after, #powerTip.nw:before {\n\tright: 14px;\n}\n\n#powerTip.ne:after, #powerTip.ne:before {\n\tleft: 14px;\n}\n\n#powerTip.s:after,  #powerTip.s:before,\n#powerTip.se:after, #powerTip.se:before,\n#powerTip.sw:after, #powerTip.sw:before {\n\tbottom: 100%;\n}\n\n#powerTip.s:after, #powerTip.se:after, #powerTip.sw:after {\n\tborder-bottom-color: #ffffff;\n\tborder-width: 10px;\n\tmargin: 0px -10px;\n}\n\n#powerTip.s:before, #powerTip.se:before, #powerTip.sw:before {\n\tborder-bottom-color: #808080;\n\tborder-width: 11px;\n\tmargin: 0px -11px;\n}\n\n#powerTip.s:after, #powerTip.s:before {\n\tleft: 50%;\n}\n\n#powerTip.sw:after, #powerTip.sw:before {\n\tright: 14px;\n}\n\n#powerTip.se:after, #powerTip.se:before {\n\tleft: 14px;\n}\n\n#powerTip.e:after, #powerTip.e:before {\n\tleft: 100%;\n}\n#powerTip.e:after {\n\tborder-left-color: #ffffff;\n\tborder-width: 10px;\n\ttop: 50%;\n\tmargin-top: -10px;\n}\n#powerTip.e:before {\n\tborder-left-color: #808080;\n\tborder-width: 11px;\n\ttop: 50%;\n\tmargin-top: -11px;\n}\n\n#powerTip.w:after, #powerTip.w:before {\n\tright: 100%;\n}\n#powerTip.w:after {\n\tborder-right-color: #ffffff;\n\tborder-width: 10px;\n\ttop: 50%;\n\tmargin-top: -10px;\n}\n#powerTip.w:before {\n\tborder-right-color: #808080;\n\tborder-width: 11px;\n\ttop: 50%;\n\tmargin-top: -11px;\n}\n\n@media print\n{\n  #top { display: none; }\n  #side-nav { display: none; }\n  #nav-path { display: none; }\n  body { overflow:visible; }\n  h1, h2, h3, h4, h5, h6 { page-break-after: avoid; }\n  .summary { display: none; }\n  .memitem { page-break-inside: avoid; }\n  #doc-content\n  {\n    margin-left:0 !important;\n    height:auto !important;\n    width:auto !important;\n    overflow:inherit;\n    display:inline;\n  }\n}\n\n/* @group Markdown */\n\n/*\ntable.markdownTable {\n\tborder-collapse:collapse;\n        margin-top: 4px;\n        margin-bottom: 4px;\n}\n\ntable.markdownTable td, table.markdownTable th {\n\tborder: 1px solid #2D4068;\n\tpadding: 3px 7px 2px;\n}\n\ntable.markdownTableHead tr {\n}\n\ntable.markdownTableBodyLeft td, table.markdownTable th {\n\tborder: 1px solid #2D4068;\n\tpadding: 3px 7px 2px;\n}\n\nth.markdownTableHeadLeft th.markdownTableHeadRight th.markdownTableHeadCenter th.markdownTableHeadNone {\n\tbackground-color: #374F7F;\n\tcolor: #FFFFFF;\n\tfont-size: 110%;\n\tpadding-bottom: 4px;\n\tpadding-top: 5px;\n}\n\nth.markdownTableHeadLeft {\n\ttext-align: left\n}\n\nth.markdownTableHeadRight {\n\ttext-align: right\n}\n\nth.markdownTableHeadCenter {\n\ttext-align: center\n}\n*/\n\ntable.markdownTable {\n\tborder-collapse:collapse;\n        margin-top: 4px;\n        margin-bottom: 4px;\n}\n\ntable.markdownTable td, table.markdownTable th {\n\tborder: 1px solid #2D4068;\n\tpadding: 3px 7px 2px;\n}\n\ntable.markdownTable tr {\n}\n\nth.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone {\n\tbackground-color: #374F7F;\n\tcolor: #FFFFFF;\n\tfont-size: 110%;\n\tpadding-bottom: 4px;\n\tpadding-top: 5px;\n}\n\nth.markdownTableHeadLeft, td.markdownTableBodyLeft {\n\ttext-align: left\n}\n\nth.markdownTableHeadRight, td.markdownTableBodyRight {\n\ttext-align: right\n}\n\nth.markdownTableHeadCenter, td.markdownTableBodyCenter {\n\ttext-align: center\n}\n\n\n/* @end */\n"
  },
  {
    "path": "modules/CMakeLists.txt",
    "content": "# ---[ Add modules\nadd_subdirectory(detectron)\nadd_subdirectory(module_test)\nadd_subdirectory(observers)\nadd_subdirectory(rocksdb)\n\n# Finally, set Caffe2_MODULES to parent scope.\nset(Caffe2_MODULES ${Caffe2_MODULES} PARENT_SCOPE)"
  },
  {
    "path": "modules/detectron/CMakeLists.txt",
    "content": "file(GLOB Detectron_CPU_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/*.cc)\nfile(GLOB Detectron_GPU_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/*.cu)\n\n# Note(ilijar): Since Detectron ops currently have no\n# CPU implementation, we only build GPU ops for now.\nif (USE_CUDA)\n  CUDA_ADD_LIBRARY(\n      caffe2_detectron_ops_gpu SHARED\n      ${Detectron_CPU_SRCS}\n      ${Detectron_GPU_SRCS})\n\n  target_link_libraries(caffe2_detectron_ops_gpu caffe2_gpu)\n  install(TARGETS caffe2_detectron_ops_gpu DESTINATION lib)\nendif()\n"
  },
  {
    "path": "modules/detectron/affine_channel_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"affine_channel_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(AffineChannel,\n                      AffineChannelOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(AffineChannelGradient,\n                      AffineChannelGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(AffineChannel)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .AllowInplace({{0, 0}})\n    .SetDoc(R\"DOC(\nApplies a separate affine transformation to each channel of the input. Useful\nfor replacing spatial batch norm with its equivalent fixed transformation.\n)DOC\")\n    .Input(\n        0,\n        \"X\",\n        \"4D feature map input of shape (N, C, H, W).\")\n    .Input(\n        1,\n        \"scale\",\n        \"1D input of shape (C); the c-th element is the scale factor of the \"\n        \"affine transformation for the c-th channel of the input.\")\n    .Input(\n        2,\n        \"bias\",\n        \"1D input of shape (C); the c-th element is the bias of the affine \"\n        \"transformation for the c-th channel of the input.\")\n    .Output(\n        0,\n        \"Y\",\n        \"4D output of shape (N, C, H, W).\");\n\nOPERATOR_SCHEMA(AffineChannelGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .AllowInplace({{1, 0}})\n    .Input(\n        0,\n        \"scale\",\n        \"See AffineChannel.\")\n    .Input(\n        1,\n        \"dY\",\n        \"Gradient of forward output 0 (Y)\")\n    .Output(\n        0,\n        \"dX\",\n        \"Gradient of forward input 0 (X)\");\n\nclass GetAffineChannelGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"AffineChannelGradient\", \"\",\n        vector<string>{I(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(AffineChannel, GetAffineChannelGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/affine_channel_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"affine_channel_op.h\"\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\nnamespace {\ntemplate <typename T>\n__global__ void ScaleBiasForward(\n    const int n,\n    const T* in,\n    const T* scale,\n    const T* bias,\n    const int scale_dim,\n    const int hxw_dim,\n    T* out) {\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    const int scale_index = (index / hxw_dim) % scale_dim;\n    out[index] = in[index] * scale[scale_index] + bias[scale_index];\n  }\n}\n\ntemplate <typename T>\n__global__ void ScaleForward(\n    const int n,\n    const T* in,\n    const T* scale,\n    const int scale_dim,\n    const int hxw_dim,\n    T* out) {\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    const int scale_index = (index / hxw_dim) % scale_dim;\n    out[index] = in[index] * scale[scale_index];\n  }\n}\n} // namespace\n\ntemplate <>\nbool AffineChannelOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& scale = Input(1);\n  auto& bias = Input(2);\n  auto* Y = Output(0);\n\n  Y->ResizeLike(X);\n  const int output_size = Y->size();\n  ScaleBiasForward<float>\n      <<<CAFFE_GET_BLOCKS(output_size),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(\n          output_size,\n          X.data<float>(),\n          scale.data<float>(),\n          bias.data<float>(),\n          X.dim32(1),\n          X.dim32(2) * X.dim32(3),\n          Y->mutable_data<float>());\n  return true;\n}\n\ntemplate <>\nbool AffineChannelGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& scale = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n\n  dX->ResizeLike(dY);\n  ScaleForward<float>\n      <<<CAFFE_GET_BLOCKS(dY.size()),\n         CAFFE_CUDA_NUM_THREADS,\n         0,\n         context_.cuda_stream()>>>(\n          dY.size(),\n          dY.data<float>(),\n          scale.data<float>(),\n          dY.dim32(1),\n          dY.dim32(2) * dY.dim32(3),\n          dX->mutable_data<float>());\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(AffineChannel, AffineChannelOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    AffineChannelGradient,\n    AffineChannelGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/affine_channel_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AFFINE_CHANNEL_OP_H_\n#define AFFINE_CHANNEL_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass AffineChannelOp final : public Operator<Context> {\n public:\n  AffineChannelOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n};\n\ntemplate <typename T, class Context>\nclass AffineChannelGradientOp final : public Operator<Context> {\n public:\n  AffineChannelGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n};\n\n} // namespace caffe2\n\n#endif // AFFINE_CHANNEL_OP_H_\n"
  },
  {
    "path": "modules/detectron/batch_permutation_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"batch_permutation_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(BatchPermutation, BatchPermutationOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    BatchPermutationGradient,\n    BatchPermutationGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(BatchPermutation)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nPermute the batch elements of the input tensor X according to the permutation\nspecified in the input indices.\n\nWarning: this op does not verify that indices is a valid permutation; gradient\ncomptuation is only correct if indices is a permutation.\n)DOC\")\n    .Input(\n        0,\n        \"X\",\n        \"Tensor of at least 1D shape (N, D0, D1, ...).\")\n    .Input(\n        1,\n        \"indices\",\n        \"1D tensor of type int with shape (N, ) specifying a valid permutation \"\n        \"of the indices in [0, N - 1] (inclusive).\")\n    .Output(\n        0,\n        \"Y\",\n        \"Tensor with the same shape as X where the (D0, D1, ...) dimensional \"\n        \"batch elements of X are permuted according to the input indices.\");\n\nOPERATOR_SCHEMA(BatchPermutationGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .Input(\n        0,\n        \"indices\",\n        \"See BatchPermutation.\")\n    .Input(\n        1,\n        \"dY\",\n        \"Gradient of forward output 0 (Y).\")\n    .Output(\n        0,\n        \"dX\",\n        \"Gradient of forward input 0 (X).\");\n\nclass GetBatchPermutationGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"BatchPermutationGradient\",\n        \"\",\n        vector<string>{I(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(BatchPermutation, GetBatchPermutationGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/batch_permutation_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"batch_permutation_op.h\"\n#include \"caffe2/core/context_gpu.h\"\n\nnamespace caffe2 {\n\nnamespace {\ntemplate <bool forward>\n__global__ void BatchPermutationKernel(\n    int N,\n    int C,\n    int H,\n    int W,\n    const float* src,\n    const int* indices,\n    float* dst) {\n  CUDA_1D_KERNEL_LOOP(index, N * C * H * W) {\n    int w = index % W;\n    int h = (index / W) % H;\n    int c = (index / W / H) % C;\n    int n = (index / W / H / C);\n    int idx = indices[n];\n    if (forward) {\n      dst[n * C * H * W + c * H * W + h * W + w] =\n          src[idx * C * H * W + c * H * W + h * W + w];\n    } else {\n      dst[idx * C * H * W + c * H * W + h * W + w] =\n          src[n * C * H * W + c * H * W + h * W + w];\n    }\n  }\n}\n}\n\ntemplate <>\nbool BatchPermutationOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& indices = Input(1);\n  auto* Y = Output(0);\n\n  CAFFE_ENFORCE(indices.ndim() == 1, \"indices must be 1-d\");\n  CAFFE_ENFORCE(\n      X.dim32(0) == indices.dim32(0),\n      \"X.dim32(0) must be equal to indices.dim32(0)\",\n      \"(\",\n      X.dim32(0),\n      \" vs. \",\n      indices.dim32(0),\n      \")\");\n\n  Y->ResizeLike(X);\n\n  BatchPermutationKernel<true><<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.dim32(0),\n      X.dim32(1),\n      X.dim32(2),\n      X.dim32(3),\n      X.data<float>(),\n      indices.data<int>(),\n      Y->mutable_data<float>());\n\n  return true;\n}\n\ntemplate <>\nbool BatchPermutationGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& indices = Input(0);\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n  dX->ResizeLike(dY);\n\n  BatchPermutationKernel<false><<<\n      CAFFE_GET_BLOCKS(dY.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      dY.dim32(0),\n      dY.dim32(1),\n      dY.dim32(2),\n      dY.dim32(3),\n      dY.data<float>(),\n      indices.data<int>(),\n      dX->mutable_data<float>());\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(\n    BatchPermutation,\n    BatchPermutationOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    BatchPermutationGradient,\n    BatchPermutationGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/batch_permutation_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef BATCHPERMUTATION_OP_H_\n#define BATCHPERMUTATION_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass BatchPermutationOp final : public Operator<Context> {\n public:\n  BatchPermutationOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n};\n\ntemplate <typename T, class Context>\nclass BatchPermutationGradientOp final : public Operator<Context> {\n public:\n  BatchPermutationGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n};\n\n} // namespace caffe2\n\n#endif // BATCHPERMUTATION_OP_H_\n"
  },
  {
    "path": "modules/detectron/group_spatial_softmax_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"group_spatial_softmax_op.h\"\n#include \"caffe2/operators/softmax_shared.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(\n    GroupSpatialSoftmax,\n    GroupSpatialSoftmaxOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    GroupSpatialSoftmaxGradient,\n    GroupSpatialSoftmaxGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(GroupSpatialSoftmax)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nRetinaNet specific form of spatial softmax.\n\nThe input is assumed to be unnormalized scores (sometimes called 'logits')\narranged in a 4D tensor with shape (N, C, H, W), where N is the number of\nelements in the batch, H and W are the height and width, and C = num_anchors *\nnum_classes defines num_anchors 'groups' of softmax inputs, each of length\nnum_classes. The softmax is applied to each group independently.\n\nSee: https://arxiv.org/abs/1708.02002 for details.\n)DOC\")\n    .Arg(\n        \"num_classes\",\n        \"(int) default 81; number of classes in each softmax group.\")\n    .Input(\n        0,\n        \"scores\",\n        \"4D tensor of softmax inputs (called 'scores' or 'logits') with shape \"\n        \"(N, C, H, W), where C = num_anchors * num_classes defines num_anchors \"\n        \"groups of contiguous num_classes softmax inputs.\")\n    .Output(\n        0,\n        \"probabilities\",\n        \"4D tensor of softmax probabilities with shape (N, C, H, W), where \"\n        \"C = num_anchors * num_classes, and softmax was applied to each of the \"\n        \"num_anchors groups; within a group the num_classes values sum to 1.\");\n\nOPERATOR_SCHEMA(GroupSpatialSoftmaxGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .Input(\n        0,\n        \"scores\",\n        \"See GroupSpatialSoftmax\")\n    .Input(\n        1,\n        \"d_probabilities\",\n        \"Gradient of forward output 0 (probabilities).\")\n    .Output(\n        0,\n        \"d_scores\",\n        \"Gradient of forward input 0 (scores).\");\n\nclass GetGroupSpatialSoftmaxGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"GroupSpatialSoftmaxGradient\",\n        \"\",\n        vector<string>{O(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(GroupSpatialSoftmax, GetGroupSpatialSoftmaxGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/group_spatial_softmax_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cfloat>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"group_spatial_softmax_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n__global__ void GroupSpatialSoftmaxKernel(const int num, const int A, const int W,\n    const int H, const float* Xdata, float* Pdata, const int num_classes) {\n  // Loop throuh labels (N x A x H x W)\n  CUDA_1D_KERNEL_LOOP(index, num * A * H * W) {\n    int D = num_classes * A;\n    int x = index % W;\n    int y = (index / W) % H;\n    int a = (index / (W * H)) % A;\n    int i = index / W / H / A;\n\n    // Subtract max on each cell for numerical reasons\n    float max_val = -FLT_MAX;\n    for(int c = a * num_classes; c < (a + 1) * num_classes; ++c) {\n      int idx = i * (H * W * D) +  c * (H * W) + y * W + x;\n      max_val = max(max_val, Xdata[idx]);\n    }\n    // Exponentiate\n    float expsum = 0.0f;\n    for(int c = a * num_classes; c < (a + 1) * num_classes; ++c) {\n      int idx = i * (H * W * D) + c * (H * W) + y * W + x;\n      float expx = exp(Xdata[idx] - max_val);\n      Pdata[idx] = expx;\n      expsum += expx;\n    }\n\n    // Normalize\n    for(int c = a * num_classes; c < (a + 1) * num_classes; ++c) {\n      int idx = i * (H * W * D) + c * (H * W) + y * W + x;\n      Pdata[idx] /= expsum;\n    }\n\n  }\n}\n\n__global__ void SumProbsKernel(const int N, const int A, const int W,\n    const int H, const float* Ydata, const float* dYdata,\n    float* sum_probs_data, const int num_classes) {\n  CUDA_1D_KERNEL_LOOP(i, N * A * W * H) {\n    int D = num_classes * A;\n    int x = i % W;\n    int y = (i / W) % H;\n    int a = (i / (W * H)) % A;\n    int n = i / (W * H * A);\n\n    sum_probs_data[i] = 0.0;\n    for(int c = a * num_classes; c < (a + 1) * num_classes; ++c) {\n      int idx = n * (H * W * D) + c * (H * W) + y * W + x;\n      sum_probs_data[i] += (Ydata[idx] * dYdata[idx]);\n    }\n  }\n}\n\n__global__ void SubSumKernel(\n    const int N, const int A, const int W, const int H,\n    const float* sum_probs_data, float* dXdata, const int num_classes) {\n  CUDA_1D_KERNEL_LOOP(i, N * (A * num_classes) * W * H) {\n    int D = num_classes * A;\n    int x = i % W;\n    int y = (i / W) % H;\n    int a = ((i / (W * H)) % D) / num_classes;\n    int n = i / W / H / D;\n    int idx = n * (H * W * A) + a * (H * W) + y * W + x;\n    dXdata[i] = (dXdata[i] - sum_probs_data[idx]);\n  }\n}\n\n} // namespace\n\n\ntemplate <>\nbool GroupSpatialSoftmaxOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);  // Logits\n  auto* P = Output(0); // Probabilities from softmax\n\n  int N = X.dim32(0);\n  int D = X.dim32(1);\n  int H = X.dim32(2);\n  int W = X.dim32(3);\n  int A = D / num_classes_;\n\n  P->ResizeLike(X);\n  DCHECK_EQ(X.ndim(), 4);\n\n  const float* Xdata = X.data<float>();\n  float* Pdata = P->mutable_data<float>();\n\n  // Softmax for each x,y location\n  GroupSpatialSoftmaxKernel<<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS,\n                         0, context_.cuda_stream()>>>(\n      N, A, W, H, Xdata, Pdata, num_classes_);\n  return true;\n}\n\n\ntemplate<>\nbool GroupSpatialSoftmaxGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& Y = Input(0);  // Probabilities from softmax\n  auto& dY = Input(1);\n  auto* dX = Output(0);\n\n  DCHECK_EQ(Y.ndim(), 4);\n\n  int N = Y.dim32(0);\n  int D = Y.dim32(1);\n  int H = Y.dim32(2);\n  int W = Y.dim32(3);\n  int A = D / num_classes_;\n\n  dX->ResizeLike(Y);\n\n  if (sum_probs_.size() != N * A * H * W) {\n    sum_probs_.Resize(N * A * H * W);\n  }\n\n  const float* Ydata = Y.data<float>();\n  const float* dYdata = dY.data<float>();\n  float* dXdata = dX->mutable_data<float>();\n\n  float* sum_probs_data = sum_probs_.mutable_data<float>();\n  math::Set<float, CUDAContext>(\n      sum_probs_.size(), 0.0f, sum_probs_data, &context_);\n\n  // Complete math:\n  // J_ij = h_i (delta_ij - h_j)\n  // d x_i = sum_j d h_ij = sum_j J_ij * dy_j\n  //       = sum_j h_i (delta_ij - h_j) * dy_j\n  //       = h_i dy_i - (sum_j h_i h_j dy_j)\n  //       = h_i dy_i - h_i sum_j h_j dy_j\n\n  // Step 0: dx = dy\n  context_.Copy<float, CUDAContext, CUDAContext>(Y.size(), dYdata, dXdata);\n\n  // Step 1: s = Sum(dY[j] * Y[j])\n  SumProbsKernel<<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS, 0,\n                   context_.cuda_stream()>>>(\n    N, A, W, H, Ydata, dYdata, sum_probs_data, num_classes_);\n\n  // Step 2: dX[i] = dX[i] - s\n  SubSumKernel<<<CAFFE_GET_BLOCKS(Y.size()), CAFFE_CUDA_NUM_THREADS, 0,\n                  context_.cuda_stream()>>>(\n    N, A, W, H, sum_probs_.data<float>(), dXdata, num_classes_);\n\n  // Step 3: dX[i] = Y[i] * dX[i]\n  math::Mul<float, CUDAContext>(Y.size(), dXdata, Ydata, dXdata, &context_);\n\n  return true;\n}\n\n\nREGISTER_CUDA_OPERATOR(GroupSpatialSoftmax,\n                       GroupSpatialSoftmaxOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(GroupSpatialSoftmaxGradient,\n                       GroupSpatialSoftmaxGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/group_spatial_softmax_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef GROUP_SPATIAL_SOFTMAX_OP_H_\n#define GROUP_SPATIAL_SOFTMAX_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass GroupSpatialSoftmaxOp final : public Operator<Context> {\n public:\n  GroupSpatialSoftmaxOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        num_classes_(OperatorBase::GetSingleArgument<int>(\"num_classes\", 81)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CAFFE_ENFORCE_EQ(\n        order_, StorageOrder::NCHW, \"Only NCHW order is supported right now.\");\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  int num_classes_;\n  StorageOrder order_;\n};\n\ntemplate <typename T, class Context>\nclass GroupSpatialSoftmaxGradientOp final : public Operator<Context> {\n public:\n  GroupSpatialSoftmaxGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        num_classes_(OperatorBase::GetSingleArgument<int>(\"num_classes\", 81)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CAFFE_ENFORCE_EQ(\n        order_, StorageOrder::NCHW, \"Only NCHW order is supported right now.\");\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  int num_classes_;\n  StorageOrder order_;\n  Tensor<Context> sum_probs_;\n};\n\n} // namespace caffe2\n\n#endif // GROUP_SPATIAL_SOFTMAX_OP_H_\n"
  },
  {
    "path": "modules/detectron/ps_roi_pool_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"ps_roi_pool_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(PSRoIPool, PSRoIPoolOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    PSRoIPoolGradient,\n    PSRoIPoolGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(PSRoIPool)\n    .NumInputs(2)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nPosition Sensitive Region of Interest Pooling as used in R-FCN.\n)DOC\")\n    .Arg(\n        \"spatial_scale\",\n        \"(float) default 1.0; Spatial scale of the input feature map X \"\n        \"relative to the input image. E.g., 0.0625 if X has a stride of 16 \"\n        \"w.r.t. the input image.\")\n    .Arg(\n        \"group_size\",\n        \"(int) default 1; pooled_h = pooled_w = group_size where pooled_{h,w} \"\n        \"is the pooled output Y's height and width, respectively.\")\n    .Arg(\n        \"output_dim\",\n        \"(int) default 1; number of channels in the pooled output, which might \"\n        \"be the number of classes is used for classification or 4 if used for \"\n        \"class agnostic bounding box regression.\")\n    .Input(\n        0,\n        \"X\",\n        \"4D position sensitive feature map input of shape (N, C, H, W), where \"\n        \"C = group_size**2 * output_dim.\")\n    .Input(\n        1,\n        \"RoIs\",\n        \"2D input of shape (R, 5) specifying R RoIs with five columns \"\n        \"representing: batch index in [0, N - 1], x1, y1, x2, y2. The RoI \"\n        \"coordinates are in the coordinate system of the input image.\")\n    .Output(\n        0,\n        \"Y\",\n        \"4D output of shape (R, output_dim, pooled_h, pooled_w). The r-th \"\n        \"batch element is a pooled feature map cooresponding to the r-th RoI.\")\n    .Output(\n        1,\n        \"argmaxes\",\n        \"4D output of shape (R, output_dim, pooled_h, pooled_w). Same as Y, \"\n        \"except it records the argmax indices rather than the max pooled \"\n        \"values.\");\n\nOPERATOR_SCHEMA(PSRoIPoolGradient)\n    .NumInputs(4)\n    .NumOutputs(1)\n    .Input(\n        0,\n        \"X\",\n        \"See PSRoIPool.\")\n    .Input(\n        1,\n        \"RoIs\",\n        \"See PSRoIPool.\")\n    .Input(\n        2,\n        \"argmaxes\",\n        \"See PSRoIPool.\")\n    .Input(\n        3,\n        \"dY\",\n        \"Gradient of forward output 0 (Y)\")\n    .Output(\n        0,\n        \"dX\",\n        \"Gradient of forward input 0 (X)\");\n\nclass GetPSRoIPoolGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"PSRoIPoolGradient\",\n        \"\",\n        vector<string>{I(0), I(1), O(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(PSRoIPool, GetPSRoIPoolGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/ps_roi_pool_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Based on https://github.com/daijifeng001/caffe-rfcn/blob/r-fcn/src/caffe/layers/psroi_pooling_layer.cu\n//\n// ------------------------------------------------------------------\n// R-FCN\n// Copyright (c) 2016 Microsoft\n// Licensed under The MIT License [see r-fcn/LICENSE for details]\n// Written by Yi Li\n// ------------------------------------------------------------------\n//\n// COPYRIGHT\n//\n// All contributions by the University of California:\n// Copyright (c) 2014, 2015, The Regents of the University of California\n// (Regents)\n// All rights reserved.\n//\n// All other contributions:\n// Copyright (c) 2014, 2015, the respective contributors\n// All rights reserved.\n//\n// Caffe uses a shared copyright model: each contributor holds copyright over\n// their contributions to Caffe. The project versioning records all such\n// contribution and copyright details. If a contributor wants to further mark\n// their specific copyright on a particular contribution, they should indicate\n// their copyright solely in the commit message of the change when it is\n// committed.\n//\n// LICENSE\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are met:\n//\n// 1. Redistributions of source code must retain the above copyright notice,\n//    this list of conditions and the following disclaimer.\n// 2. Redistributions in binary form must reproduce the above copyright notice,\n//    this list of conditions and the following disclaimer in the documentation\n//    and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n// POSSIBILITY OF SUCH DAMAGE.\n//\n// CONTRIBUTION AGREEMENT\n//\n// By contributing to the BVLC/caffe repository through pull-request, comment,\n// or otherwise, the contributor releases their content to the\n// license and copyright terms herein.\n\n#include <cfloat>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"ps_roi_pool_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <typename T>\ninline __device__ T gpu_atomic_add(const T val, T* address);\n\ntemplate <>\ninline __device__\nfloat gpu_atomic_add(const float val, float* address) {\n  return atomicAdd(address, val);\n}\n\ntemplate <typename T>\n__global__ void PSRoIPoolForward(\n    const int nthreads,\n    const T* bottom_data,\n    const T spatial_scale,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const T* bottom_rois,\n    const int output_dim,\n    const int group_size,\n    T* top_data,\n    int* mapping_channel) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // The output is in order (n, ctop, ph, pw)\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int ctop = (index / pooled_width / pooled_height) % output_dim;\n    int n = index / pooled_width / pooled_height / output_dim;\n\n    // [start, end) interval for spatial sampling\n    const T* offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n    T roi_start_w = static_cast<T>(\n      round(offset_bottom_rois[1])) * spatial_scale;\n    T roi_start_h = static_cast<T>(\n      round(offset_bottom_rois[2])) * spatial_scale;\n    T roi_end_w = static_cast<T>(\n      round(offset_bottom_rois[3]) + 1.) * spatial_scale;\n    T roi_end_h = static_cast<T>(\n      round(offset_bottom_rois[4]) + 1.) * spatial_scale;\n\n    // Force too small ROIs to be 1x1\n    T roi_width = max(roi_end_w - roi_start_w, 0.1);  // avoid 0\n    T roi_height = max(roi_end_h - roi_start_h, 0.1);\n\n    // Compute w and h at bottom\n    T bin_size_h = roi_height / static_cast<T>(pooled_height);\n    T bin_size_w = roi_width / static_cast<T>(pooled_width);\n\n    int hstart = floor(\n      static_cast<T>(ph) * bin_size_h + roi_start_h);\n    int wstart = floor(\n      static_cast<T>(pw)* bin_size_w + roi_start_w);\n    int hend = ceil(\n      static_cast<T>(ph + 1) * bin_size_h + roi_start_h);\n    int wend = ceil(\n      static_cast<T>(pw + 1) * bin_size_w + roi_start_w);\n    // Add roi offsets and clip to input boundaries\n    hstart = min(max(hstart, 0), height);\n    hend = min(max(hend, 0), height);\n    wstart = min(max(wstart, 0),width);\n    wend = min(max(wend, 0), width);\n    bool is_empty = (hend <= hstart) || (wend <= wstart);\n\n    int gw = pw;\n    int gh = ph;\n    int c = (ctop * group_size + gh) * group_size + gw;\n\n    const T* offset_bottom_data =\n      bottom_data + (roi_batch_ind * channels + c) * height * width;\n    T out_sum = 0;\n    for (int h = hstart; h < hend; ++h){\n     for (int w = wstart; w < wend; ++w){\n       int bottom_index = h*width + w;\n       out_sum += offset_bottom_data[bottom_index];\n     }\n    }\n\n    T bin_area = (hend - hstart) * (wend - wstart);\n    top_data[index] = is_empty ? 0. : out_sum / bin_area;\n    mapping_channel[index] = c;\n  }\n}\n\ntemplate <typename T>\n__global__ void PSRoIPoolBackward(\n    const int nthreads,\n    const T* top_diff,\n    const int* mapping_channel,\n    const int num_rois,\n    const T spatial_scale,\n    const int channels,\n    const int height,\n    const int width,\n    const int pooled_height,\n    const int pooled_width,\n    const int output_dim,\n    T* bottom_diff,\n    const T* bottom_rois) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // The output is in order (n, ctop, ph, pw)\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int n = index / pooled_width / pooled_height / output_dim;\n\n    // [start, end) interval for spatial sampling\n    const T* offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n    T roi_start_w = static_cast<T>(\n      round(offset_bottom_rois[1])) * spatial_scale;\n    T roi_start_h = static_cast<T>(\n      round(offset_bottom_rois[2])) * spatial_scale;\n    T roi_end_w = static_cast<T>(\n      round(offset_bottom_rois[3]) + 1.) * spatial_scale;\n    T roi_end_h = static_cast<T>(\n      round(offset_bottom_rois[4]) + 1.) * spatial_scale;\n\n    // Force too small ROIs to be 1x1\n    T roi_width = max(roi_end_w - roi_start_w, 0.1); //avoid 0\n    T roi_height = max(roi_end_h - roi_start_h, 0.1);\n\n    // Compute w and h at bottom\n    T bin_size_h = roi_height / static_cast<T>(pooled_height);\n    T bin_size_w = roi_width / static_cast<T>(pooled_width);\n\n    int hstart = floor(\n      static_cast<T>(ph)* bin_size_h + roi_start_h);\n    int wstart = floor(\n      static_cast<T>(pw)* bin_size_w + roi_start_w);\n    int hend = ceil(\n      static_cast<T>(ph + 1) * bin_size_h + roi_start_h);\n    int wend = ceil(\n      static_cast<T>(pw + 1) * bin_size_w + roi_start_w);\n    // Add roi offsets and clip to input boundaries\n    hstart = min(max(hstart, 0), height);\n    hend = min(max(hend, 0), height);\n    wstart = min(max(wstart, 0), width);\n    wend = min(max(wend, 0), width);\n    bool is_empty = (hend <= hstart) || (wend <= wstart);\n\n    // Compute c at bottom\n    int c = mapping_channel[index];\n    T* offset_bottom_diff =\n      bottom_diff + (roi_batch_ind * channels + c) * height * width;\n    T bin_area = (hend - hstart) * (wend - wstart);\n    T diff_val = is_empty ? 0. : top_diff[index] / bin_area;\n    for (int h = hstart; h < hend; ++h){\n      for (int w = wstart; w < wend; ++w){\n        int bottom_index = h * width + w;\n        gpu_atomic_add(diff_val, offset_bottom_diff + bottom_index);\n      }\n    }\n  }\n}\n\n} // namespace\n\ntemplate<>\nbool PSRoIPoolOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);  // Input data to pool\n  auto& R = Input(1);  // RoIs\n  auto* Y = Output(0); // PSRoI pooled data\n  auto* A = Output(1); // mapping_channel\n\n  Y->Resize(R.dim32(0), output_dim_, pooled_height_, pooled_width_);\n  A->Resize(Y->dims());\n  int output_size = Y->size();\n  PSRoIPoolForward<float><<<CAFFE_GET_BLOCKS(output_size),\n                            CAFFE_CUDA_NUM_THREADS,\n                            0, context_.cuda_stream()>>>(\n      output_size, X.data<float>(), spatial_scale_, X.dim32(1), X.dim32(2),\n      X.dim32(3), pooled_height_, pooled_width_, R.data<float>(), output_dim_,\n      group_size_, Y->mutable_data<float>(), A->mutable_data<int>());\n  return true;\n}\n\n\ntemplate<>\nbool PSRoIPoolGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X  = Input(0);  // Input data to pool\n  auto& R  = Input(1);  // RoIs\n  auto& A  = Input(2);  // mapping channels\n  auto& dY = Input(3);  // Gradient of net w.r.t. output of \"forward\" op\n                        // (aka \"gradOutput\")\n  auto* dX = Output(0); // Gradient of net w.r.t. input to \"forward\" op\n                        // (aka \"gradInput\")\n\n  dX->ResizeLike(X);\n  // Must zero-out dX before accumulating gradients\n  math::Set<float, CUDAContext>(\n      dX->size(), 0.f, dX->mutable_data<float>(), &context_);\n  PSRoIPoolBackward<float><<<CAFFE_GET_BLOCKS(dY.size()),\n                             CAFFE_CUDA_NUM_THREADS,\n                             0, context_.cuda_stream()>>>(\n      dY.size(), dY.data<float>(), A.data<int>(), R.dim32(0), spatial_scale_,\n      X.dim32(1), X.dim32(2), X.dim32(3), pooled_height_, pooled_width_,\n      output_dim_, dX->mutable_data<float>(), R.data<float>());\n  return true;\n}\n\n\nREGISTER_CUDA_OPERATOR(PSRoIPool,\n                       PSRoIPoolOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(PSRoIPoolGradient,\n                       PSRoIPoolGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/ps_roi_pool_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef PS_ROI_POOL_OP_H_\n#define PS_ROI_POOL_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass PSRoIPoolOp final : public Operator<Context> {\n public:\n  PSRoIPoolOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        spatial_scale_(OperatorBase::GetSingleArgument<float>(\n              \"spatial_scale\", 1.)),\n        group_size_(OperatorBase::GetSingleArgument<int>(\"group_size\", 1)),\n        output_dim_(OperatorBase::GetSingleArgument<int>(\"output_dim\", 1)) {\n    DCHECK_GT(spatial_scale_, 0);\n    DCHECK_GT(group_size_, 0);\n    pooled_height_ = group_size_;\n    pooled_width_ = group_size_;\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n   float spatial_scale_;\n   int group_size_;\n   int output_dim_;\n   int pooled_height_;\n   int pooled_width_;\n   int channels_;\n   int height_;\n   int width_;\n };\n\ntemplate <typename T, class Context>\nclass PSRoIPoolGradientOp final : public Operator<Context> {\n public:\n  PSRoIPoolGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        spatial_scale_(OperatorBase::GetSingleArgument<float>(\n              \"spatial_scale\", 1.)),\n        group_size_(OperatorBase::GetSingleArgument<int>(\"group_size\", 1)),\n        output_dim_(OperatorBase::GetSingleArgument<int>(\"output_dim\", 1)) {\n    DCHECK_GT(spatial_scale_, 0);\n    DCHECK_GT(group_size_, 0);\n    pooled_height_ = group_size_;\n    pooled_width_ = group_size_;\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float spatial_scale_;\n  int group_size_;\n  int output_dim_;\n  int pooled_height_;\n  int pooled_width_;\n  int channels_;\n  int height_;\n  int width_;\n};\n\n} // namespace caffe2\n\n#endif // PS_ROI_POOL_OP_H_\n"
  },
  {
    "path": "modules/detectron/roi_pool_f_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"roi_pool_f_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(RoIPoolF, RoIPoolFOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(RoIPoolFGradient, RoIPoolFGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(RoIPoolF)\n    .NumInputs(2)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nRegion of Interest (RoI) pooling operation as used in Fast R-CNN.\n)DOC\")\n    .Arg(\n        \"spatial_scale\",\n        \"(float) default 1.0; Spatial scale of the input feature map X \"\n        \"relative to the input image. E.g., 0.0625 if X has a stride of 16 \"\n        \"w.r.t. the input image.\")\n    .Arg(\n        \"pooled_h\",\n        \"(int) default 1; Pooled output Y's height.\")\n    .Arg(\n        \"pooled_w\",\n        \"(int) default 1; Pooled output Y's width.\")\n    .Input(\n        0,\n        \"X\",\n        \"4D feature map input of shape (N, C, H, W).\")\n    .Input(\n        1,\n        \"RoIs\",\n        \"2D input of shape (R, 5) specifying R RoIs with five columns \"\n        \"representing: batch index in [0, N - 1], x1, y1, x2, y2. The RoI \"\n        \"coordinates are in the coordinate system of the input image.\")\n    .Output(\n        0,\n        \"Y\",\n        \"4D output of shape (R, C, pooled_h, pooled_w). The r-th batch element \"\n        \"is a pooled feature map cooresponding to the r-th RoI.\")\n    .Output(\n        1,\n        \"argmaxes\",\n        \"4D output of shape (R, C, pooled_h, pooled_w). Same as Y, except it \"\n        \"records the argmax indices rather than the max pooled values.\");\n\nOPERATOR_SCHEMA(RoIPoolFGradient)\n    .NumInputs(4)\n    .NumOutputs(1)\n    .Input(\n        0,\n        \"X\",\n        \"See RoIPoolF.\")\n    .Input(\n        1,\n        \"RoIs\",\n        \"See RoIPoolF.\")\n    .Input(\n        2,\n        \"argmaxes\",\n        \"See RoIPoolF.\")\n    .Input(\n        3,\n        \"dY\",\n        \"Gradient of forward output 0 (Y)\")\n    .Output(\n        0,\n        \"dX\",\n        \"Gradient of forward input 0 (X)\");\n\nclass GetRoIPoolFGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"RoIPoolFGradient\",\n        \"\",\n        vector<string>{I(0), I(1), O(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(RoIPoolF, GetRoIPoolFGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/roi_pool_f_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cfloat>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"roi_pool_f_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\ntemplate <typename T>\ninline __device__ T gpu_atomic_add(const T val, T* address);\n\ntemplate <>\ninline __device__\nfloat gpu_atomic_add(const float val, float* address) {\n  return atomicAdd(address, val);\n}\n\ntemplate <typename T>\n__global__ void RoIPoolFForward(const int nthreads, const T* bottom_data,\n    const T spatial_scale, const int channels, const int height,\n    const int width, const int pooled_height, const int pooled_width,\n    const T* bottom_rois, T* top_data, int* argmax_data) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // (n, c, ph, pw) is an element in the pooled output\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int c = (index / pooled_width / pooled_height) % channels;\n    int n = index / pooled_width / pooled_height / channels;\n\n    const T* offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n    int roi_start_w = round(offset_bottom_rois[1] * spatial_scale);\n    int roi_start_h = round(offset_bottom_rois[2] * spatial_scale);\n    int roi_end_w = round(offset_bottom_rois[3] * spatial_scale);\n    int roi_end_h = round(offset_bottom_rois[4] * spatial_scale);\n\n    // Force malformed ROIs to be 1x1\n    int roi_width = max(roi_end_w - roi_start_w + 1, 1);\n    int roi_height = max(roi_end_h - roi_start_h + 1, 1);\n    T bin_size_h = static_cast<T>(roi_height)\n                       / static_cast<T>(pooled_height);\n    T bin_size_w = static_cast<T>(roi_width)\n                       / static_cast<T>(pooled_width);\n\n    int hstart = static_cast<int>(floor(static_cast<T>(ph)\n                                        * bin_size_h));\n    int wstart = static_cast<int>(floor(static_cast<T>(pw)\n                                        * bin_size_w));\n    int hend = static_cast<int>(ceil(static_cast<T>(ph + 1)\n                                     * bin_size_h));\n    int wend = static_cast<int>(ceil(static_cast<T>(pw + 1)\n                                     * bin_size_w));\n\n    // Add roi offsets and clip to input boundaries\n    hstart = min(max(hstart + roi_start_h, 0), height);\n    hend = min(max(hend + roi_start_h, 0), height);\n    wstart = min(max(wstart + roi_start_w, 0), width);\n    wend = min(max(wend + roi_start_w, 0), width);\n    bool is_empty = (hend <= hstart) || (wend <= wstart);\n\n    // Define an empty pooling region to be zero\n    T maxval = is_empty ? 0 : -FLT_MAX;\n    // If nothing is pooled, argmax = -1 causes nothing to be backprop'd\n    int maxidx = -1;\n    const T* offset_bottom_data =\n        bottom_data + (roi_batch_ind * channels + c) * height * width;\n    for (int h = hstart; h < hend; ++h) {\n      for (int w = wstart; w < wend; ++w) {\n        int bottom_index = h * width + w;\n        if (offset_bottom_data[bottom_index] > maxval) {\n          maxval = offset_bottom_data[bottom_index];\n          maxidx = bottom_index;\n        }\n      }\n    }\n    top_data[index] = maxval;\n    argmax_data[index] = maxidx;\n  }\n}\n\ntemplate <typename T>\n__global__ void RoIPoolFBackward(const int nthreads, const T* top_diff,\n    const int* argmax_data, const int num_rois, const T spatial_scale,\n    const int channels, const int height, const int width,\n    const int pooled_height, const int pooled_width, T* bottom_diff,\n    const T* bottom_rois) {\n  CUDA_1D_KERNEL_LOOP(index, nthreads) {\n    // (n, c, ph, pw) is an element in the pooled output\n    int pw = index % pooled_width;\n    int ph = (index / pooled_width) % pooled_height;\n    int c = (index / pooled_width / pooled_height) % channels;\n    int n = index / pooled_width / pooled_height / channels;\n\n    const T* offset_bottom_rois = bottom_rois + n * 5;\n    int roi_batch_ind = offset_bottom_rois[0];\n    int bottom_offset = (roi_batch_ind * channels + c) * height * width;\n    int top_offset    = (n * channels + c) * pooled_height * pooled_width;\n    const T* offset_top_diff = top_diff + top_offset;\n    T* offset_bottom_diff = bottom_diff + bottom_offset;\n    const int* offset_argmax_data = argmax_data + top_offset;\n\n    int argmax = offset_argmax_data[ph * pooled_width + pw];\n    if (argmax != -1) {\n      gpu_atomic_add(\n          static_cast<T>(offset_top_diff[ph * pooled_width + pw]),\n          offset_bottom_diff + argmax);\n    }\n  }\n}\n\n} // namespace\n\ntemplate<>\nbool RoIPoolFOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);  // Input data to pool\n  auto& R = Input(1);  // RoIs\n  auto* Y = Output(0); // RoI pooled data\n  auto* A = Output(1); // argmaxes\n\n  if (R.size() == 0) {\n    // Handle empty rois\n    Y->Resize(0, X.dim32(1), pooled_height_, pooled_width_);\n    A->Resize(0, X.dim32(1), pooled_height_, pooled_width_);\n    // The following mutable_data calls are needed to allocate the tensors\n    Y->mutable_data<float>();\n    A->mutable_data<int>();\n    return true;\n  }\n\n  Y->Resize(R.dim32(0), X.dim32(1), pooled_height_, pooled_width_);\n  A->Resize(Y->dims());\n  int output_size = Y->size();\n  RoIPoolFForward<float><<<CAFFE_GET_BLOCKS(output_size),\n                          CAFFE_CUDA_NUM_THREADS,\n                          0, context_.cuda_stream()>>>(\n      output_size, X.data<float>(), spatial_scale_, X.dim32(1), X.dim32(2),\n      X.dim32(3), pooled_height_, pooled_width_, R.data<float>(),\n      Y->mutable_data<float>(), A->mutable_data<int>());\n  return true;\n}\n\n\ntemplate<>\nbool RoIPoolFGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X  = Input(0);  // Input data to pool\n  auto& R  = Input(1);  // RoIs\n  auto& A  = Input(2);  // argmaxes\n  auto& dY = Input(3);  // Gradient of net w.r.t. output of \"forward\" op\n                        // (aka \"gradOutput\")\n  auto* dX = Output(0); // Gradient of net w.r.t. input to \"forward\" op\n                        // (aka \"gradInput\")\n\n  dX->ResizeLike(X);\n  // Must zero-out dX before accumulating gradients\n  math::Set<float, CUDAContext>(\n      dX->size(), 0.f, dX->mutable_data<float>(), &context_);\n  if (dY.size() > 0) {  // Handle possibly empty gradient if there were no rois\n    RoIPoolFBackward<float><<<CAFFE_GET_BLOCKS(dY.size()),\n                             CAFFE_CUDA_NUM_THREADS,\n                             0, context_.cuda_stream()>>>(\n        dY.size(), dY.data<float>(), A.data<int>(), R.dim32(0), spatial_scale_,\n        X.dim32(1), X.dim32(2), X.dim32(3), pooled_height_, pooled_width_,\n        dX->mutable_data<float>(), R.data<float>());\n  }\n  return true;\n}\n\n\nREGISTER_CUDA_OPERATOR(RoIPoolF,\n                       RoIPoolFOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(RoIPoolFGradient,\n                       RoIPoolFGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/roi_pool_f_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef ROI_POOL_F_OP_H_\n#define ROI_POOL_F_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass RoIPoolFOp final : public Operator<Context> {\n public:\n  RoIPoolFOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        spatial_scale_(OperatorBase::GetSingleArgument<float>(\n              \"spatial_scale\", 1.)),\n        pooled_height_(OperatorBase::GetSingleArgument<int>(\"pooled_h\", 1)),\n        pooled_width_(OperatorBase::GetSingleArgument<int>(\"pooled_w\", 1)) {\n    DCHECK_GT(spatial_scale_, 0);\n    DCHECK_GT(pooled_height_, 0);\n    DCHECK_GT(pooled_width_, 0);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float spatial_scale_;\n  int pooled_height_;\n  int pooled_width_;\n};\n\ntemplate <typename T, class Context>\nclass RoIPoolFGradientOp final : public Operator<Context> {\n public:\n  RoIPoolFGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        spatial_scale_(OperatorBase::GetSingleArgument<float>(\n              \"spatial_scale\", 1.)),\n        pooled_height_(OperatorBase::GetSingleArgument<int>(\"pooled_h\", 1)),\n        pooled_width_(OperatorBase::GetSingleArgument<int>(\"pooled_w\", 1)) {\n    DCHECK_GT(spatial_scale_, 0);\n    DCHECK_GT(pooled_height_, 0);\n    DCHECK_GT(pooled_width_, 0);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float spatial_scale_;\n  int pooled_height_;\n  int pooled_width_;\n};\n\n} // namespace caffe2\n\n#endif // ROI_POOL_F_OP_H_\n"
  },
  {
    "path": "modules/detectron/sample_as_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"sample_as_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(SampleAs, SampleAsOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(SampleAsGradient, SampleAsGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(SampleAs)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nSelect the batch elements from input tensor X where the corresponding input\nlabel value is > 0.\n)DOC\")\n    .Input(\n        0,\n        \"X\",\n        \"Tensor of at least 1D shape (N, ...).\")\n    .Input(\n        1,\n        \"labels\",\n        \"Tensor of type int with 1D shape (N, ).\")\n    .Output(\n        0,\n        \"Y\",\n        \"Tensor with number of dims matching X, but with the length of dim 0 \"\n        \"equal to the number of non-zero elements in labels. The batch items \"\n        \"from X corresponding to the non-zero elements in labels are copied \"\n        \"into Y.\");\n\nOPERATOR_SCHEMA(SampleAsGradient)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .Input(\n        0,\n        \"X\",\n        \"See SampleAs.\")\n    .Input(\n        1,\n        \"labels\",\n        \"See SampleAs.\"\n    )\n    .Input(\n        2,\n        \"dY\",\n        \"Gradient of forward output 0 (Y).\")\n    .Output(\n        0,\n        \"dX\",\n        \"Gradient of forward input 0 (X).\");\n\nclass GetSampleAsGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SampleAsGradient\",\n        \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(SampleAs, GetSampleAsGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/sample_as_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* SampleAs by Kaiming He for Mask R-CNN\nX.dim32(0) = L.dim32(0)\nY's output samples are the samples of X for which L > 0.\n*/\n#include <cfloat>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"sample_as_op.h\"\n\n#include <stdio.h>\n\nnamespace caffe2 {\n\ntemplate <>\nbool SampleAsOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0); // Input data to be sliced\n  auto& L = Input(1); // Target data that provide the identity\n  auto* Y = Output(0); // Sliced data (Y.dim32(0) = num of (L > 0))\n\n  CAFFE_ENFORCE(\n      X.dim32(0) == L.dim32(0),\n      \"X.dim32(0) must be equal to L.dim32(0)\",\n      \"(\",\n      X.dim32(0),\n      \" vs. \",\n      L.dim32(0),\n      \")\");\n\n  // copy L to CPU:\n  std::vector<int> labels(L.dim32(0));\n  context_.CopyBytes<CUDAContext, CPUContext>(\n      L.dim32(0) * sizeof(int), L.data<int>(), &labels[0]);\n  // Make sure that the copy is finished\n  context_.FinishDeviceComputation();\n\n  int count = 0;\n  for (int i = 0; i < L.dim32(0); i++) {\n    if (labels[i] > 0) {\n      count++;\n    }\n  }\n  assert(count > 0);\n\n  // resize Y\n  vector<TIndex> out_shape(X.dims());\n  out_shape[0] = count;\n  Y->Resize(out_shape);\n\n  const int len = X.size() / X.dim32(0);\n\n  float* output = Y->mutable_data<float>();\n  for (int i = 0; i < L.dim32(0); i++) {\n    if (labels[i] > 0) {\n      context_.CopyBytes<CUDAContext, CUDAContext>(\n          len * sizeof(float), X.data<float>() + i * len, output);\n      output += len;\n    } // if\n  } // i\n\n  return true;\n}\n\ntemplate <>\nbool SampleAsGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& L = Input(1);\n  auto& dY = Input(2);\n  auto* dX = Output(0);\n\n  dX->ResizeLike(X);\n\n  // copy L to CPU:\n  std::vector<int> labels(L.dim32(0));\n  context_.CopyBytes<CUDAContext, CPUContext>(\n      L.dim32(0) * sizeof(int), L.data<int>(), &labels[0]);\n  // Make sure that the copy is finished\n  context_.FinishDeviceComputation();\n\n  // zero-out dX\n  math::Set<float, CUDAContext>(\n      dX->size(), 0.f, dX->mutable_data<float>(), &context_);\n\n  const int len = X.size() / X.dim32(0);\n\n  const float* input = dY.data<float>();\n  for (int i = 0; i < L.dim32(0); i++) {\n    if (labels[i] > 0) {\n      context_.CopyBytes<CUDAContext, CUDAContext>(\n          len * sizeof(float), input, dX->mutable_data<float>() + i * len);\n      input += len;\n    } // if\n  } // i\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(SampleAs, SampleAsOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    SampleAsGradient,\n    SampleAsGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/sample_as_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef SAMPLE_AS_OP_H_\n#define SAMPLE_AS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SampleAsOp final : public Operator<Context> {\n public:\n  SampleAsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n};\n\ntemplate <typename T, class Context>\nclass SampleAsGradientOp final : public Operator<Context> {\n public:\n  SampleAsGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n};\n\n} // namespace caffe2\n\n#endif // SAMPLE_AS_OP_H_\n"
  },
  {
    "path": "modules/detectron/select_smooth_l1_loss_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"select_smooth_l1_loss_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(\n    SelectSmoothL1Loss,\n    SelectSmoothL1LossOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    SelectSmoothL1LossGradient,\n    SelectSmoothL1LossGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(SelectSmoothL1Loss)\n    .NumInputs(4)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nRetinaNet specific op for computing Smooth L1 Loss at select locations in a 4D\ntensor that encodes bounding box regression predictions.\n)DOC\")\n    .Arg(\n        \"beta\",\n        \"(float) default 1.0; L2 to L1 transition point.\")\n    .Arg(\n        \"scale\",\n        \"(float) default 1.0; multiply the loss by this scale factor.\")\n    .Input(\n        0,\n        \"Y_hat\",\n        \"4D tensor of bounding box regression predictions with shape \"\n        \"(N, 4 * num_bbox_classes * num_anchors, H, W).\")\n    .Input(\n        1,\n        \"Y\",\n        \"2D tensor of labels shape (M, 4) for 4 contiguous channels starting \"\n        \"at each of the M locations selected by the locations input.\")\n    .Input(\n        2,\n        \"locations\",\n        \"2D tensor of shape (M, 4) that identifies M 'select' locations \"\n        \"encoded by the four colums: (n, c, y, x). The loss is computed on the \"\n        \"four contiguous channel locations [c, c + 3] (inclusive).\")\n    .Input(\n        3,\n        \"normalizer\",\n        \"Scalar; the loss is divided by max(1, normalizer).\")\n    .Output(\n        0,\n        \"loss\",\n        \"Scalar loss.\");\n\nOPERATOR_SCHEMA(SelectSmoothL1LossGradient)\n    .NumInputs(5)\n    .NumOutputs(1)\n    .Input(\n        0,\n        \"Y_hat\",\n        \"See SelectSmoothL1Loss.\")\n    .Input(\n        1,\n        \"Y\",\n        \"See SelectSmoothL1Loss.\")\n    .Input(\n        2,\n        \"locations\",\n        \"See SelectSmoothL1Loss.\")\n    .Input(\n        3,\n        \"normalizer\",\n        \"See SelectSmoothL1Loss.\")\n    .Input(\n        4,\n        \"d_loss\",\n        \"Gradient of forward output 0 (loss).\")\n    .Output(\n        0,\n        \"d_Y_hat\",\n        \"Gradient of forward input 0 (Y_hat).\");\n\nclass GetSelectSmoothL1LossGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SelectSmoothL1LossGradient\",\n        \"\",\n        vector<string>{I(0), I(1), I(2), I(3), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(SelectSmoothL1Loss, GetSelectSmoothL1LossGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/select_smooth_l1_loss_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"select_smooth_l1_loss_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n__global__ void SelectSmoothL1Kernel(\n    const int D, const int H, const int W,\n    const int M, const float* Y_hat, const float* Y, const float* L, float* out,\n    const float* S, const float beta) {\n  // f(x) = 0.5 * x^2 / beta      if |x| < beta\n  //        |x| - 0.5 * beta      otherwise\n  CUDA_1D_KERNEL_LOOP(i, M) {\n    int n = L[i * 4];\n    int c = L[i * 4 + 1];\n    int y = L[i * 4 + 2];\n    int x = L[i * 4 + 3];\n\n    for (int j = 0; j < 4; j++){\n      // Y_hat: N x (A * CLS * 4) x H x W\n      int ind = n * (D * H * W) + (c + j) * (H * W) + y * W + x;\n      float y_hat = Y_hat[ind];\n      float y = Y[i * 4 + j];\n      float val = y_hat - y;\n      float abs_val = abs(val);\n      if (abs_val < beta) {\n        out[ind] = (0.5 * val * val / beta) / max(S[0], 1.0);\n      } else {\n        out[ind] = (abs_val - 0.5 * beta) / max(S[0], 1.0);\n      }\n    }\n  }\n}\n\n\n__global__ void SelectSmoothL1GradientKernel(\n    const int D, const int H, const int W,\n    const int M,\n    const float* Y_hat,\n    const float* Y,\n    const float* L,\n    float* out,\n    const float* d_loss_data,\n    float norm,\n    const float* S,\n    float beta) {\n  // f'(x) = x / beta     if |x| < beta\n  //       = sign(x)      otherwise\n  // We also scale by norm * d_loss in this kernel for convenience\n  CUDA_1D_KERNEL_LOOP(i, M) {\n    int n = L[i * 4];\n    int c = L[i * 4 + 1];\n    int y = L[i * 4 + 2];\n    int x = L[i * 4 + 3];\n    float d_loss = *d_loss_data;\n\n    for (int j = 0; j < 4; j++) {\n      int ind = n * (D * H * W) + (c + j) * (H * W) + y * W + x;\n      float y_hat = Y_hat[ind];\n      float y = Y[i * 4 + j];\n      float val = y_hat - y;\n      float abs_val = abs(val);\n      if (abs_val < beta) {\n        out[ind] = norm * d_loss * val / beta / max(S[0], 1.0);\n      } else {\n        out[ind] = norm * d_loss * ((float(0) < val) - (val < float(0))) / max(S[0], 1.0);\n      }\n    }\n  }\n}\n} // namespace\n\n\ntemplate<>\nbool SelectSmoothL1LossOp<float, CUDAContext>::RunOnDevice() {\n  // bbox targets predictions, for example: N x (A * 4) H x W in cls-agnostic case\n  auto& Y_hat     = Input(0);\n  // true targets: for example: M x 4 where M is the #fg boxes per fpn level\n  auto& Y         = Input(1);\n  // locations of fg boxes: M x 4\n  auto& L         = Input(2);\n  // total number of fg boxes across all FPN levels: scalar\n  auto& S         = Input(3);\n  auto* avg_loss  = Output(0);\n\n  avg_loss->Resize(vector<TIndex>());\n  if (Y.size() == 0){\n    math::Set<float, CUDAContext>(\n      1, static_cast<float>(0), avg_loss->mutable_data<float>(), &context_);\n    return true;\n  }\n\n  int N = Y_hat.dim32(0);\n  int D = Y_hat.dim32(1);\n  int H = Y_hat.dim32(2);\n  int W = Y_hat.dim32(3);\n\n  int M = Y.dim32(0);\n\n  // initialization\n  buff_.ResizeLike(Y_hat);\n  math::Set<float, CUDAContext>(\n    1, static_cast<float>(0), avg_loss->mutable_data<float>(), &context_);\n  math::Set<float, CUDAContext>(\n    buff_.size(), 0.0, buff_.mutable_data<float>(), &context_);\n\n  // Element-wise smooth l1 loss\n  // l := SelectSmoothL1((y_hat - y))\n  SelectSmoothL1Kernel<<<CAFFE_GET_BLOCKS(buff_.size()),\n                         CAFFE_CUDA_NUM_THREADS,\n                         0, context_.cuda_stream()>>>(\n    D, H, W,\n    M, Y_hat.data<float>(), Y.data<float>(),\n    L.data<float>(), buff_.mutable_data<float>(),\n    S.data<float>(), beta_);\n\n  // Sum of all losses\n  // al := sum_i l_i\n  float* avg_loss_data = avg_loss->mutable_data<float>();\n  math::Sum<float, CUDAContext>(\n      buff_.size(), buff_.data<float>(), avg_loss_data, &context_);\n\n  // Average of input batch size\n  math::Scale<float, CUDAContext>(\n      1, scale_, avg_loss_data, avg_loss_data, &context_);\n  return true;\n}\n\ntemplate<>\nbool SelectSmoothL1LossGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& Y_hat      = Input(0);\n  auto& Y          = Input(1);\n  auto& L          = Input(2);\n  auto& S          = Input(3);\n  // Below is gradient of net w.r.t. avg_loss (\"gradOuput\"), should be all 1's\n  auto& d_avg_loss = Input(4);\n  auto* d_Y_hat    = Output(0); // gradient of net w.r.t. Y_hat (\"gradInput\")\n\n  d_Y_hat->ResizeLike(Y_hat);\n  math::Set<float, CUDAContext>(\n    d_Y_hat->size(), 0.0, d_Y_hat->mutable_data<float>(), &context_);\n  if (Y.size() == 0){\n    return true;\n  }\n\n  int N = Y_hat.dim32(0);\n  int D = Y_hat.dim32(1);\n  int H = Y_hat.dim32(2);\n  int W = Y_hat.dim32(3);\n\n  int M = Y.dim32(0);\n  // Element-wise weighted difference (can be used to ignore or reweight\n  // specific components)\n  // d := (y_hat - y)\n  // d_Y_hat := d_avg_loss * SelectSmoothL1'((y_hat - y))\n\n  SelectSmoothL1GradientKernel<<<CAFFE_GET_BLOCKS(d_Y_hat->size()),\n                                 CAFFE_CUDA_NUM_THREADS,\n                                 0, context_.cuda_stream()>>>(\n    D, H, W, M, Y_hat.data<float>(), Y.data<float>(),\n    L.data<float>(), d_Y_hat->mutable_data<float>(),\n    d_avg_loss.data<float>(), scale_, S.data<float>(), beta_);\n\n  return true;\n}\n\n\nREGISTER_CUDA_OPERATOR(SelectSmoothL1Loss,\n                       SelectSmoothL1LossOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(SelectSmoothL1LossGradient,\n                       SelectSmoothL1LossGradientOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/select_smooth_l1_loss_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef SELECT_SMOOTH_L1_LOSS_OP_H_\n#define SELECT_SMOOTH_L1_LOSS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SelectSmoothL1LossOp final : public Operator<Context> {\n public:\n  SelectSmoothL1LossOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        beta_(OperatorBase::GetSingleArgument<float>(\"beta\", 1.)),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)) {\n    CAFFE_ENFORCE(beta_ > 0);\n    CAFFE_ENFORCE(scale_ >= 0);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float beta_; // Transition point from L1 to L2 loss\n  float scale_; // Scale the loss by scale_\n  int dim_; // dimension for 1 anchor prediction\n  Tensor<Context> buff_; // Buffer for element-wise differences\n};\n\ntemplate <typename T, class Context>\nclass SelectSmoothL1LossGradientOp final : public Operator<Context> {\n public:\n  SelectSmoothL1LossGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        beta_(OperatorBase::GetSingleArgument<float>(\"beta\", 1.)),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)) {\n    CAFFE_ENFORCE(beta_ > 0);\n    CAFFE_ENFORCE(scale_ >= 0);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float beta_; // Transition point from L1 to L2 loss\n  float scale_; // Scale the loss by scale_\n  int dim_; // dimension for 1 anchor prediction\n  Tensor<Context> buff_; // Buffer for element-wise differences\n};\n\n} // namespace caffe2\n\n#endif // SELECT_SMOOTH_L1_LOSS_OP_H_\n"
  },
  {
    "path": "modules/detectron/sigmoid_cross_entropy_loss_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"sigmoid_cross_entropy_loss_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(\n    SigmoidCrossEntropyLoss,\n    SigmoidCrossEntropyLossOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    SigmoidCrossEntropyLossGradient,\n    SigmoidCrossEntropyLossGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(SigmoidCrossEntropyLoss)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nCompute sigmoid activations followed by averaged binary cross entropy loss. The\ntarget values may be in {-1, 0, 1}, where -1 indicates that the corresponding\nsample should be ignored and {0, 1} correspond to the binary classes 0 and 1. By\ndefault the loss is divided by the number of targets > -1 and then multiplied by\nthe `scale` op argument. The divisive normalization may be disable by setting\nthe op argument `normalize` to 0 (the multiplication by `scale` still takes\neffect).\n\nThis op fuses sigmoid and cross entropy for numerical stability in both forward\nand gradient computation.\n)DOC\")\n    .Arg(\n        \"scale\",\n        \"(float) default 1.0; multiply the loss by this scale factor.\")\n    .Arg(\n        \"normalize\",\n        \"(int) default 1; if true, divide the loss by the number of targets > \"\n        \"-1.\")\n    .Input(\n        0,\n        \"X\",\n        \"Tensor of predicted logits (shape must be at least 1D).\")\n    .Input(\n        1,\n        \"targets\",\n        \"Tensor of targets of type int and same shape as logits X.\")\n    .Output(\n        0,\n        \"loss\",\n        \"Scalar loss.\");\n\nOPERATOR_SCHEMA(SigmoidCrossEntropyLossGradient)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .Input(\n        0,\n        \"X\",\n        \"See SigmoidCrossEntropyLoss.\")\n    .Input(\n        1,\n        \"targets\",\n        \"See SigmoidCrossEntropyLoss.\")\n    .Input(\n        2,\n        \"d_loss\",\n        \"Gradient of forward output 0 (loss).\")\n    .Output(\n        0,\n        \"dX\",\n        \"Gradient of forward input 0 (X).\");\n\nclass GetSigmoidCrossEntropyLossGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SigmoidCrossEntropyLossGradient\",\n        \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(SigmoidCrossEntropyLoss, GetSigmoidCrossEntropyLossGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/sigmoid_cross_entropy_loss_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"sigmoid_cross_entropy_loss_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n__global__ void ElementwiseMaxKernel(const int n, float* data, const float a) {\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    data[index] = (data[index] > a) ? data[index] : a;\n  }\n}\n\n__global__ void SigmoidCrossEntropyLossKernel(\n    const int n,\n    const float* logits,\n    const int* targets,\n    float* losses,\n    float* counts) {\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    if (targets[index] == -1) {\n      losses[index] = 0.;\n      counts[index] = 0.;\n    } else {\n      losses[index] =\n          -1. * logits[index] * (targets[index] - (logits[index] >= 0)) +\n          logf(\n              1 +\n              expf(logits[index] - 2 * logits[index] * (logits[index] >= 0)));\n      counts[index] = 1.;\n    }\n  }\n}\n\n__global__ void SigmoidCrossEntropyLossGradientKernel(\n    const int n,\n    const float* logits,\n    const int* targets,\n    float* d_logits,\n    float* counts) {\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    if (targets[index] == -1) {\n      d_logits[index] = 0.;\n      counts[index] = 0.;\n    } else {\n      d_logits[index] = 1. / (1. + expf(-logits[index])) - targets[index];\n      counts[index] = 1.;\n    }\n  }\n}\n} // namespace\n\ntemplate <>\nbool SigmoidCrossEntropyLossOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& T = Input(1);\n  auto* avg_loss = Output(0);\n\n  CAFFE_ENFORCE(\n      X.size() == T.size(),\n      \"Logit and target must have the same size\",\n      \"(\",\n      X.size(),\n      \" vs. \",\n      T.size(),\n      \")\");\n  avg_loss->Resize(vector<TIndex>());\n  counts_.ResizeLike(X);\n  losses_.ResizeLike(X);\n  normalizer_.Resize(vector<TIndex>());\n  SigmoidCrossEntropyLossKernel<<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(),\n      X.data<float>(),\n      T.data<int>(),\n      losses_.mutable_data<float>(),\n      counts_.mutable_data<float>());\n  float* avg_loss_data = avg_loss->mutable_data<float>();\n  math::Sum<float, CUDAContext>(\n      losses_.size(), losses_.data<float>(), avg_loss_data, &context_);\n  if (normalize_) {\n    float* normalizer_data = normalizer_.mutable_data<float>();\n    math::Sum<float, CUDAContext>(\n        counts_.size(), counts_.data<float>(), normalizer_data, &context_);\n    // Prevent division by zero is all counts are zero\n    ElementwiseMaxKernel<<<\n        CAFFE_GET_BLOCKS(normalizer_.size()),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(normalizer_.size(), normalizer_data, 1e-5);\n    math::Div<float, CUDAContext>(\n        1, avg_loss_data, normalizer_data, avg_loss_data, &context_);\n  }\n  math::Scale<float, CUDAContext>(\n      1, scale_, avg_loss_data, avg_loss_data, &context_);\n\n  return true;\n}\n\ntemplate <>\nbool SigmoidCrossEntropyLossGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& T = Input(1);\n  auto& d_avg_loss = Input(2);\n  auto* dX = Output(0);\n\n  dX->ResizeLike(X);\n  counts_.ResizeLike(X);\n  normalizer_.Resize(vector<TIndex>());\n  SigmoidCrossEntropyLossGradientKernel<<<\n      CAFFE_GET_BLOCKS(X.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      X.size(),\n      X.data<float>(),\n      T.data<int>(),\n      dX->mutable_data<float>(),\n      counts_.mutable_data<float>());\n  if (normalize_) {\n    float* normalizer_data = normalizer_.mutable_data<float>();\n    math::Sum<float, CUDAContext>(\n        counts_.size(), counts_.data<float>(), normalizer_data, &context_);\n    // Prevent division by zero is all counts are zero\n    ElementwiseMaxKernel<<<\n        CAFFE_GET_BLOCKS(normalizer_.size()),\n        CAFFE_CUDA_NUM_THREADS,\n        0,\n        context_.cuda_stream()>>>(normalizer_.size(), normalizer_data, 1e-5);\n    math::Div<float, CUDAContext>(\n        1,\n        d_avg_loss.data<float>(),\n        normalizer_data,\n        normalizer_data,\n        &context_);\n    math::Scale<float, CUDAContext>(\n        1, scale_, normalizer_data, normalizer_data, &context_);\n    math::Scale<float, CUDAContext>(\n        dX->size(),\n        normalizer_data,\n        dX->data<float>(),\n        dX->mutable_data<float>(),\n        &context_);\n  } else {\n    math::Scale<float, CUDAContext>(\n        dX->size(),\n        scale_,\n        dX->data<float>(),\n        dX->mutable_data<float>(),\n        &context_);\n    math::Scale<float, CUDAContext>(\n        dX->size(),\n        d_avg_loss.data<float>(),\n        dX->data<float>(),\n        dX->mutable_data<float>(),\n        &context_);\n  }\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(\n    SigmoidCrossEntropyLoss,\n    SigmoidCrossEntropyLossOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    SigmoidCrossEntropyLossGradient,\n    SigmoidCrossEntropyLossGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/sigmoid_cross_entropy_loss_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef SIGMOID_CROSS_ENTROPY_LOSS_OP_H_\n#define SIGMOID_CROSS_ENTROPY_LOSS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SigmoidCrossEntropyLossOp final : public Operator<Context> {\n public:\n  SigmoidCrossEntropyLossOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)),\n        normalize_(OperatorBase::GetSingleArgument<int>(\"normalize\", 1)) {\n    CAFFE_ENFORCE(scale_ >= 0);\n    CAFFE_ENFORCE(normalize_ == 0 || normalize_ == 1);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float scale_;\n  int normalize_;\n  Tensor<Context> losses_;\n  Tensor<Context> counts_;\n  Tensor<Context> normalizer_;\n};\n\ntemplate <typename T, class Context>\nclass SigmoidCrossEntropyLossGradientOp final : public Operator<Context> {\n public:\n  SigmoidCrossEntropyLossGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)),\n        normalize_(OperatorBase::GetSingleArgument<int>(\"normalize\", 1)) {\n    CAFFE_ENFORCE(scale_ >= 0);\n    CAFFE_ENFORCE(normalize_ == 0 || normalize_ == 1);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float scale_;\n  int normalize_;\n  Tensor<Context> counts_;\n  Tensor<Context> normalizer_;\n};\n\n} // namespace caffe2\n\n#endif // SIGMOID_CROSS_ENTROPY_LOSS_OP_H_\n"
  },
  {
    "path": "modules/detectron/sigmoid_focal_loss_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"sigmoid_focal_loss_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(SigmoidFocalLoss, SigmoidFocalLossOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    SigmoidFocalLossGradient,\n    SigmoidFocalLossGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(SigmoidFocalLoss)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nThe binary form of Focal Loss designed for use in RetinaNet-like models.\nThe input is assumed to be unnormalized scores (sometimes called 'logits')\narranged in a 4D tensor with shape (N, C, H, W), where N is the number of\nelements in the batch, H and W are the height and width, and C = num_anchors *\nnum_classes defines num_anchors 'groups' of logits, each of length\nnum_classes. For the binary form of Focal Loss, num_classes does not include\nthe background category. (So, for COCO, num_classes = 80, not 81.)\n\nThe binary form of focal loss is:\n\n  FL(p_t) = -alpha * (1 - p_t)**gamma * log(p_t),\n\nwhere p = sigmoid(x), p_t = p or 1 - p depending on if the label is 1 or 0,\nrespectively.\n\nSee: https://arxiv.org/abs/1708.02002 for details.\n)DOC\")\n    .Arg(\n       \"scale\",\n       \"(float) default 1.0; multiply the loss by this scale factor.\")\n    .Arg(\n       \"alpha\",\n       \"(float) default 0.25; Focal Loss's alpha hyper-parameter.\")\n    .Arg(\n       \"gamma\",\n       \"(float) default 1.0; Focal Loss's gamma hyper-parameter.\")\n    .Arg(\n       \"num_classes\",\n       \"(int) default 80; number of classes (excluding background).\")\n    .Input(\n       0,\n       \"logits\",\n       \"4D tensor of sigmoid inputs (called 'scores' or 'logits') with shape \"\n       \"(N, C, H, W), where C = num_anchors * num_classes.\")\n    .Input(\n       1,\n       \"labels\",\n       \"4D tensor of labels with shape (N, num_anchors, H, W). Each entry is \"\n       \"a class label in [0, num_classes - 1] (inclusive). The label \"\n       \"identifies the one class that should have a sigmoid target of 1.\")\n    .Input(\n       2,\n       \"normalizer\",\n       \"Scalar; the loss is normalized by 1 / max(1, normalizer).\"\n    )\n    .Output(\n       0,\n       \"loss\",\n       \"Scalar loss.\");\n\nOPERATOR_SCHEMA(SigmoidFocalLossGradient)\n    .NumInputs(4)\n    .NumOutputs(1)\n    .Input(\n        0,\n        \"logits\",\n        \"See SigmoidFocalLoss.\")\n    .Input(\n        1,\n        \"labels\",\n        \"See SigmoidFocalLoss.\")\n    .Input(\n        2,\n        \"normalizer\",\n        \"See SigmoidFocalLoss.\")\n    .Input(\n        3,\n        \"d_loss\",\n        \"Gradient of forward output 0 (loss)\")\n    .Output(\n        0,\n        \"d_logits\",\n        \"Gradient of forward input 0 (logits)\");\n\nclass GetSigmoidFocalLossGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n\n  vector<OperatorDef> GetGradientDefs() override {\n    vector<string> blob_names{\n        {I(0), I(1), I(2), GO(0)},\n    };\n\n    return SingleGradientDef(\n        \"SigmoidFocalLossGradient\", \"\", blob_names, vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(SigmoidFocalLoss, GetSigmoidFocalLossGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/sigmoid_focal_loss_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cfloat>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"sigmoid_focal_loss_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n__global__ void SigmoidFocalLossKernel(\n    const int N, const int D, const int H, const int W, const float* logits,\n    const int* targets, const float* weight_pos,\n    const float gamma, const float alpha,\n    const int num_classes, float* losses) {\n  CUDA_1D_KERNEL_LOOP(i, N * D * H * W) {\n    int x = i % W;\n    int y = (i / W) % H;\n    int c = (i / (W * H)) % D;  // channel, here D is channel dim in input NxDxHxW\n    int n = i / (W * H * D);    // n in NxDxHxW\n\n    int A = D / num_classes;   // num_anchors = A\n    int a = c / num_classes;   // current anchor out of A anchors in D = A * num_cls\n    int d = c % num_classes;   // current class\n    int t = targets[n * (H * W * A) + a * (H * W) + y * W + x];   // target\n\n    // check whether the class is true class or not.\n    // The target classes are in range 1 - 81 and the d is in range 0-80\n    // because we predict A*80 dim, so for comparison purpose, compare t and (d+1)\n    float c1 = (t == (d + 1));\n    float c2 = (t != -1 & t != (d + 1));\n\n    float Np = max(weight_pos[0], 1.0);\n    float zn = (1.0 - alpha) / Np;\n    float zp = alpha / Np;\n\n    // p = 1. / 1. + expf(-x)\n    float p = 1. / (1. + expf(-logits[i]));\n\n    // (1 - p)**gamma * log(p) where\n    float term1 = powf((1. - p), gamma) * logf(max(p, FLT_MIN));\n    // p**gamma * log(1 - p)\n    float term2 =\n        powf(p, gamma) *\n        (-1. * logits[i] * (logits[i] >= 0) -\n         logf(1. + expf(logits[i] - 2. * logits[i] * (logits[i] >= 0))));\n\n    losses[i] = 0.0;\n    losses[i] += -c1 * term1 * zp;\n    losses[i] += -c2 * term2 * zn;\n  }\n}\n\n__global__ void SigmoidFocalLossGradientKernel(\n    const int N, const int D, const int H, const int W, const float* logits,\n    const int* targets, float* dX_data, const float* weight_pos,\n    const float gamma, const float alpha, const int num_classes,\n    const float* avg_loss) {\n  CUDA_1D_KERNEL_LOOP(i, N * D * H * W) {\n      float a_loss = avg_loss[0];\n      int x = i % W;\n      int y = (i / W) % H;\n      int c = (i / (W * H)) % D;\n      int n = i / (W * H * D);\n\n      int A = D / num_classes;   // num_anchors\n      int a = c / num_classes;   // current anchor\n      int d = c % num_classes;   // current class\n\n      float Np = max(weight_pos[0], 1.0);\n      float zn = (1.0 - alpha) / Np;\n      float zp = alpha / Np;\n      int t = targets[n * (H * W * A) + a * (H * W) + y * W + x];\n\n      float c1 = (t == (d + 1));\n      float c2 = (t != -1 & t != (d + 1));\n      float p = 1. / (1. + expf(-logits[i]));\n\n      // (1-p)**g * (1 - p - g*p*log(p))\n      float term1 =\n          powf((1. - p), gamma) *\n          (1. - p - (p * gamma * logf(max(p, FLT_MIN))));\n      // (p**g) * (g*(1-p)*log(1-p) - p)\n      float term2 =\n          powf(p, gamma) *\n          ((-1. * logits[i] * (logits[i] >= 0) -\n           logf(1. + expf(logits[i] - 2. * logits[i] * (logits[i] >= 0)))) *\n           (1. - p) * gamma - p);\n      dX_data[i] = 0.0;\n      dX_data[i] += -c1 * zp * term1;\n      dX_data[i] += -c2 * zn * term2;\n      dX_data[i] = dX_data[i] * a_loss;\n  }\n}\n} // namespace\n\ntemplate<>\nbool SigmoidFocalLossOp<float, CUDAContext>::RunOnDevice() {\n  // Input logits, for example: N x (A * 80) x H x W in cls-agnostic\n  auto& X = Input(0);\n  // Target, for example: N x A x H x W\n  auto& T = Input(1);\n  // Number of positive examples: scalar\n  auto& wp = Input(2);\n  // output avg Sigmoid focal loss as mentioned in RetinaNet paper\n  auto* avg_loss = Output(0);\n\n  int N = X.dim32(0);\n  int D = X.dim32(1);\n  int H = X.dim32(2);\n  int W = X.dim32(3);\n\n  avg_loss->Resize(vector<TIndex>());\n  losses_.ResizeLike(X);\n  float* avg_loss_data = avg_loss->mutable_data<float>();\n\n  SigmoidFocalLossKernel<<<CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS, 0, context_.cuda_stream()>>>(\n      N, D, H, W, X.data<float>(), T.data<int>(),\n      wp.data<float>(), gamma_, alpha_, num_classes_,\n      losses_.mutable_data<float>());\n\n  math::Sum<float, CUDAContext>(\n      losses_.size(), losses_.data<float>(), avg_loss_data, &context_);\n  math::Scale<float, CUDAContext>(\n      1, scale_, avg_loss_data, avg_loss_data, &context_);\n\n  return true;\n}\n\n\ntemplate<>\nbool SigmoidFocalLossGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto& T = Input(1);\n  auto& wp = Input(2);\n  auto& d_avg_loss = Input(InputSize() - 1);\n  auto* dX = Output(0);\n\n  // get input shape\n  int N = X.dim32(0);\n  int D = X.dim32(1);\n  int H = X.dim32(2);\n  int W = X.dim32(3);\n\n  dX->ResizeLike(X);\n\n  SigmoidFocalLossGradientKernel<<<CAFFE_GET_BLOCKS(X.size()),\n          CAFFE_CUDA_NUM_THREADS, 0, context_.cuda_stream()>>>(\n      N, D, H, W, X.data<float>(), T.data<int>(), dX->mutable_data<float>(),\n      wp.data<float>(), gamma_, alpha_, num_classes_,\n      d_avg_loss.data<float>());\n  math::Scale<float, CUDAContext>(\n    dX->size(), scale_, dX->data<float>(), dX->mutable_data<float>(), &context_);\n\n  return true;\n}\n\n\nREGISTER_CUDA_OPERATOR(SigmoidFocalLoss,\n                       SigmoidFocalLossOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(SigmoidFocalLossGradient,\n                       SigmoidFocalLossGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/sigmoid_focal_loss_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef SIGMOID_FOCAL_LOSS_OP_H_\n#define SIGMOID_FOCAL_LOSS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SigmoidFocalLossOp final : public Operator<Context> {\n public:\n  SigmoidFocalLossOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)),\n        num_classes_(OperatorBase::GetSingleArgument<int>(\"num_classes\", 80)),\n        gamma_(OperatorBase::GetSingleArgument<float>(\"gamma\", 1.)),\n        alpha_(OperatorBase::GetSingleArgument<float>(\"alpha\", 0.25)) {\n    CAFFE_ENFORCE(scale_ >= 0);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float scale_;\n  int num_classes_;\n  float gamma_;\n  float alpha_;\n  Tensor<Context> losses_;\n  Tensor<Context> counts_;\n};\n\ntemplate <typename T, class Context>\nclass SigmoidFocalLossGradientOp final : public Operator<Context> {\n public:\n  SigmoidFocalLossGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)),\n        num_classes_(OperatorBase::GetSingleArgument<int>(\"num_classes\", 80)),\n        gamma_(OperatorBase::GetSingleArgument<float>(\"gamma\", 1.)),\n        alpha_(OperatorBase::GetSingleArgument<float>(\"alpha\", 0.25)) {\n    CAFFE_ENFORCE(scale_ >= 0);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float scale_;\n  int num_classes_;\n  float gamma_;\n  float alpha_;\n  Tensor<Context> counts_;\n  Tensor<Context> weights_; // unignored weights\n};\n\n} // namespace caffe2\n\n#endif // SIGMOID_FOCAL_LOSS_OP_H_\n"
  },
  {
    "path": "modules/detectron/smooth_l1_loss_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"smooth_l1_loss_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(SmoothL1Loss, SmoothL1LossOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    SmoothL1LossGradient,\n    SmoothL1LossGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(SmoothL1Loss)\n    .NumInputs(4)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nSmooth L1 Loss is a minor variation of Huber loss in which the point of\ntransition between L2 loss and L1 loss is adjustable by a hyper-parameter beta:\n\n  SmoothL1(x) = 0.5 * x^2 / beta      if |x| < beta\n                |x| - 0.5 * beta      otherwise.\n\nSmoothL1 is used in Fast R-CNN and decendants as the loss function for bounding\nbox regression.\n\nThe loss computed by this op has a flexible form:\n\n  scale / N * sum_i alpha_out[i] * SmoothL1(alpha_in[i] * (y_hat[i] - y[i])).\n\nThe weights alpha_in and alpha_out are called the \"inside\" and \"outside\"\nweights, respectively. The inside weights are typically set to either 0 or 1 to\nimplement ignoring (when 0) certain samples. The outside weights can be used\nto implement a per-sample loss weight. The overall loss is scaled by scale / N,\nwhere N is the number of batch elements in the input predictions.\n)DOC\")\n    .Arg(\n        \"beta\",\n        \"(float) default 1.0; L2 to L1 transition point.\")\n    .Arg(\n        \"scale\",\n        \"(float) default 1.0; multiply the loss by this scale factor.\")\n    .Input(\n        0,\n        \"Y_hat\",\n        \"Tensor of predictions (at least 1D).\")\n    .Input(\n        1,\n        \"Y\",\n        \"Tensor of labels with the same shape as Y_hat.\")\n    .Input(\n        2,\n        \"alpha_in\",\n        \"Tensor of inside weights with the same shape as Y.\")\n    .Input(\n        3,\n        \"alpha_out\",\n        \"Tensor of outside weights with the same shape as Y.\")\n    .Output(\n        0,\n        \"loss\",\n        \"Scalar loss.\");\n\nOPERATOR_SCHEMA(SmoothL1LossGradient)\n    .NumInputs(5)\n    .NumOutputs(1)\n    .Input(\n        0,\n        \"Y_hat\",\n        \"See SmoothL1Loss.\")\n    .Input(\n        1,\n        \"Y\",\n        \"See SmoothL1Loss.\")\n    .Input(\n        2,\n        \"alpha_in\",\n        \"See SmoothL1Loss.\")\n    .Input(\n        3,\n        \"alpha_out\",\n        \"See SmoothL1Loss.\")\n    .Input(\n        4,\n        \"d_loss\",\n        \"Gradient of forward output 0 (loss).\")\n    .Output(\n        0,\n        \"d_Y_hat\",\n        \"Gradient of forward input 0 (Y_hat).\");\n\nclass GetSmoothL1LossGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SmoothL1LossGradient\",\n        \"\",\n        vector<string>{I(0), I(1), I(2), I(3), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(SmoothL1Loss, GetSmoothL1LossGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/smooth_l1_loss_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"smooth_l1_loss_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\ntemplate <typename T>\n__global__ void SmoothL1Kernel(\n    const int n, const T* in, T* out, T beta) {\n  // f(x) = 0.5 * x^2 / beta      if |x| < beta\n  //        |x| - 0.5 * beta      otherwise\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    T val = in[index];\n    T abs_val = abs(val);\n    if (abs_val < beta) {\n      out[index] = 0.5 * val * val / beta;\n    } else {\n      out[index] = abs_val - 0.5 * beta;\n    }\n  }\n}\n\ntemplate <typename T>\n__global__ void SmoothL1GradientKernel(\n    const int n,\n    const T* in,\n    T* out,\n    const T* d_loss_data,\n    T norm,\n    T beta) {\n  // f'(x) = x / beta     if |x| < beta\n  //       = sign(x)      otherwise\n  // We also scale by norm * d_loss in this kernel for convenience\n  CUDA_1D_KERNEL_LOOP(index, n) {\n    T val = in[index];\n    T abs_val = abs(val);\n    T d_loss = *d_loss_data;\n    if (abs_val < beta) {\n      out[index] = norm * d_loss * val / beta;\n    } else {\n      out[index] = norm * d_loss * ((T(0) < val) - (val < T(0)));\n    }\n  }\n}\n} // namespace\n\ntemplate<>\nbool SmoothL1LossOp<float, CUDAContext>::RunOnDevice() {\n  auto& Y_hat     = Input(0);\n  auto& Y         = Input(1);\n  auto& alpha_in  = Input(2);\n  auto& alpha_out = Input(3);\n  auto* avg_loss  = Output(0);\n\n  int N = Y.dim32(0);\n  // Require the same number of elements along axis 0 (batch size), but\n  // otherwise don't care about the shape (just the number of elements)\n  CAFFE_ENFORCE_EQ(Y_hat.dim32(0), Y.dim32(0),\n      \"Y_hat and Y must have the same number of elements along axis 0\");\n  CAFFE_ENFORCE_EQ(Y_hat.size(), Y.size(),\n      \"Y_hat and Y must have the same number of elements\");\n  CAFFE_ENFORCE_EQ(Y_hat.size(), alpha_in.size());\n  CAFFE_ENFORCE_EQ(Y_hat.size(), alpha_out.size());\n\n  avg_loss->Resize(vector<TIndex>());\n  buff_.ResizeLike(Y);\n\n  // Difference\n  // d := y_hat - y\n  math::Sub<float, CUDAContext>(\n      Y.size(), Y_hat.data<float>(), Y.data<float>(),\n      buff_.mutable_data<float>(), &context_);\n  // Element-wise weighted difference (can be used to ignore or reweight\n  // specific components)\n  // d := alpha_in * (y_hat - y)\n  math::Mul<float, CUDAContext>(\n      buff_.size(), buff_.data<float>(), alpha_in.data<float>(),\n      buff_.mutable_data<float>(), &context_);\n\n  // Element-wise smooth l1 loss\n  // l := SmoothL1(alpha_in * (y_hat - y))\n  SmoothL1Kernel<float>\n  <<<CAFFE_GET_BLOCKS(buff_.size()),\n     CAFFE_CUDA_NUM_THREADS,\n     0,\n     context_.cuda_stream()>>>(\n          buff_.size(), buff_.data<float>(), buff_.mutable_data<float>(),\n          beta_);\n\n  // Element-wise weighted smooth l1 loss (can be used to specify a per-element\n  // loss weight)\n  // l := alpha_out * SmoothL1(alpha_in * (y_hat - y))\n  math::Mul<float, CUDAContext>(\n      buff_.size(), buff_.data<float>(), alpha_out.data<float>(),\n      buff_.mutable_data<float>(), &context_);\n  // Sum of all losses\n  // al := sum_i l_i\n  float* avg_loss_data = avg_loss->mutable_data<float>();\n  math::Sum<float, CUDAContext>(\n      buff_.size(), buff_.data<float>(), avg_loss_data, &context_);\n  // Average of input batch size\n  // al := 1/N * al\n  math::Scale<float, CUDAContext>(\n      1, scale_ / N, avg_loss_data, avg_loss_data, &context_);\n  return true;\n}\n\ntemplate<>\nbool SmoothL1LossGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& Y_hat      = Input(0);\n  auto& Y          = Input(1);\n  auto& alpha_in   = Input(2);\n  auto& alpha_out  = Input(3);\n  auto& d_avg_loss = Input(4);  // gradient of net w.r.t. avg_loss (\"gradOuput\")\n  auto* d_Y_hat    = Output(0); // gradient of net w.r.t. Y_hat (\"gradInput\")\n  // We intentially don't compute gradients for Y, alpha_{in,out} since they\n  // are not needed (can change in the future if desired)\n\n  int N = Y.dim32(0);\n  // Require the same number of elements along axis 0 (batch size), but\n  // otherwise don't care about the shape (just the number of elements)\n  CAFFE_ENFORCE_EQ(Y_hat.dim32(0), Y.dim32(0),\n      \"Y_hat and Y must have the same number of elements along axis 0\");\n  CAFFE_ENFORCE_EQ(Y_hat.size(), Y.size(),\n      \"Y_hat and Y must have the same number of elements\");\n  CAFFE_ENFORCE_EQ(Y_hat.size(), alpha_in.size());\n  CAFFE_ENFORCE_EQ(Y_hat.size(), alpha_out.size());\n  CAFFE_ENFORCE_EQ(d_avg_loss.size(), 1);\n\n  d_Y_hat->ResizeLike(Y_hat);\n  buff_.ResizeLike(Y);\n\n  // Difference\n  // d := y_hat - y\n  math::Sub<float, CUDAContext>(\n      Y.size(), Y_hat.data<float>(), Y.data<float>(),\n      buff_.mutable_data<float>(), &context_);\n  // Element-wise weighted difference (can be used to ignore or reweight\n  // specific components)\n  // d := alpha_in * (y_hat - y)\n  math::Mul<float, CUDAContext>(\n      buff_.size(), buff_.data<float>(), alpha_in.data<float>(),\n      buff_.mutable_data<float>(), &context_);\n  // d_Y_hat := d_avg_loss / N * SmoothL1'(alpha_in * (y_hat - y))\n  SmoothL1GradientKernel<float>\n  <<<CAFFE_GET_BLOCKS(buff_.size()),\n     CAFFE_CUDA_NUM_THREADS,\n     0,\n     context_.cuda_stream()>>>(\n         buff_.size(), buff_.data<float>(), d_Y_hat->mutable_data<float>(),\n         d_avg_loss.data<float>(), scale_ / N, beta_);\n  // Element-wise scale by alpha_in and alpha_out\n  math::Mul<float, CUDAContext>(\n      d_Y_hat->size(), d_Y_hat->data<float>(), alpha_in.data<float>(),\n      d_Y_hat->mutable_data<float>(), &context_);\n  math::Mul<float, CUDAContext>(\n      d_Y_hat->size(), d_Y_hat->data<float>(), alpha_out.data<float>(),\n      d_Y_hat->mutable_data<float>(), &context_);\n  return true;\n}\n\n\nREGISTER_CUDA_OPERATOR(SmoothL1Loss,\n                       SmoothL1LossOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(SmoothL1LossGradient,\n                       SmoothL1LossGradientOp<float, CUDAContext>);\n}  // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/smooth_l1_loss_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef SMOOTH_L1_LOSS_OP_H_\n#define SMOOTH_L1_LOSS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SmoothL1LossOp final : public Operator<Context> {\n public:\n  SmoothL1LossOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        beta_(OperatorBase::GetSingleArgument<float>(\"beta\", 1.)),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)) {\n    CAFFE_ENFORCE(beta_ > 0);\n    CAFFE_ENFORCE(scale_ >= 0);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float beta_; // Transition point from L1 to L2 loss\n  float scale_; // Scale the loss by scale_\n  Tensor<Context> buff_; // Buffer for element-wise differences\n};\n\ntemplate <typename T, class Context>\nclass SmoothL1LossGradientOp final : public Operator<Context> {\n public:\n  SmoothL1LossGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        beta_(OperatorBase::GetSingleArgument<float>(\"beta\", 1.)),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)) {\n    CAFFE_ENFORCE(beta_ > 0);\n    CAFFE_ENFORCE(scale_ >= 0);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float beta_; // Transition point from L1 to L2 loss\n  float scale_; // Scale the loss by scale_\n  Tensor<Context> buff_; // Buffer for element-wise differences\n};\n\n} // namespace caffe2\n\n#endif // SMOOTH_L1_LOSS_OP_H_\n"
  },
  {
    "path": "modules/detectron/softmax_focal_loss_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"softmax_focal_loss_op.h\"\n#include \"caffe2/operators/softmax_shared.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(SoftmaxFocalLoss, SoftmaxFocalLossOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    SoftmaxFocalLossGradient,\n    SoftmaxFocalLossGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(SoftmaxFocalLoss)\n    .NumInputs(3)\n    .NumOutputs(2)\n    .SetDoc(R\"DOC(\nA multiclass form of Focal Loss designed for use in RetinaNet-like models.\nThe input is assumed to be unnormalized scores (sometimes called 'logits')\narranged in a 4D tensor with shape (N, C, H, W), where N is the number of\nelements in the batch, H and W are the height and width, and C = num_anchors *\nnum_classes. The softmax is applied num_anchors times along the C axis.\n\nThe softmax version of focal loss is:\n\n  FL(p_t) = -alpha * (1 - p_t)**gamma * log(p_t),\n\nwhere p_i = exp(s_i) / sum_j exp(s_j), t is the target (ground truth) class, and\ns_j is the unnormalized score for class j.\n\nSee: https://arxiv.org/abs/1708.02002 for details.\n)DOC\")\n    .Arg(\n        \"scale\",\n        \"(float) default 1.0; multiply the loss by this scale factor.\")\n    .Arg(\n        \"alpha\",\n        \"(float) default 0.25; Focal Loss's alpha hyper-parameter.\")\n    .Arg(\n        \"gamma\",\n        \"(float) default 1.0; Focal Loss's gamma hyper-parameter.\")\n    .Arg(\n        \"num_classes\",\n        \"(int) default 81; number of classes in each softmax group.\")\n    .Input(\n        0,\n        \"scores\",\n        \"4D tensor of softmax inputs (called 'scores' or 'logits') with shape \"\n        \"(N, C, H, W), where C = num_anchors * num_classes defines num_anchors \"\n        \"groups of contiguous num_classes softmax inputs.\")\n    .Input(\n        1,\n        \"labels\",\n        \"4D tensor of labels with shape (N, num_anchors, H, W). Each entry is \"\n        \"a class label in [0, num_classes - 1] (inclusive).\")\n    .Input(\n        2,\n        \"normalizer\",\n        \"Scalar; the loss is normalized by 1 / max(1, normalizer).\"\n    )\n    .Output(\n        0,\n        \"loss\",\n        \"Scalar loss.\")\n    .Output(\n        1,\n        \"probabilities\",\n        \"4D tensor of softmax probabilities with shape (N, C, H, W), where \"\n        \"C = num_anchors * num_classes, and softmax was applied to each of the \"\n        \"num_anchors groups; within a group the num_classes values sum to 1.\");\n\nOPERATOR_SCHEMA(SoftmaxFocalLossGradient)\n    .NumInputs(5)\n    .NumOutputs(1)\n    .Input(\n        0,\n        \"scores\",\n        \"See SoftmaxFocalLoss.\")\n    .Input(\n        1,\n        \"labels\",\n        \"See SoftmaxFocalLoss.\")\n    .Input(\n        2,\n        \"normalizer\",\n        \"See SoftmaxFocalLoss.\")\n    .Input(\n        3,\n        \"probabilities\",\n        \"Output 1 from SoftmaxFocalLoss; See SoftmaxFocalLoss.\")\n    .Input(\n        4,\n        \"d_loss\",\n        \"Gradient of forward output 0 (loss)\")\n    .Output(\n        0,\n        \"d_scores\",\n        \"Gradient of forward input 0 (scores)\");\n\nclass GetSoftmaxFocalLossGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SoftmaxFocalLossGradient\",\n        \"\",\n        vector<string>{I(0), I(1), I(2), O(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(SoftmaxFocalLoss, GetSoftmaxFocalLossGradient);\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/softmax_focal_loss_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cfloat>\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"softmax_focal_loss_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n\n__global__ void SpatialSoftmaxKernel(const int N, const int A,\n    const int H, const int W, const float* Xdata, float* Pdata,\n    const int num_classes) {\n  CUDA_1D_KERNEL_LOOP(index, N * A * H * W) {\n    int D = num_classes * A;\n    int x = index % W;\n    int y = (index / W) % H;\n    int a = (index / (W * H)) % A;\n    int i = index / W / H / A;\n\n    // Subtract max on each cell for numerical reasons\n    float max_val = -FLT_MAX;\n    for(int c = a * num_classes; c < (a + 1) * num_classes; ++c) {\n      int idx = i * (H * W * D) +  c * (H * W) + y * W + x;\n      max_val = max(max_val, Xdata[idx]);\n    }\n    // Exponentiate\n    float expsum = 0.0f;\n    for(int c = a * num_classes; c < (a + 1) * num_classes; ++c) {\n      int idx = i * (H * W * D) + c * (H * W) + y * W + x;\n      float expx = exp(Xdata[idx] - max_val);\n      Pdata[idx] = expx;\n      expsum += expx;\n    }\n    // Normalize\n    for(int c = a * num_classes; c < (a + 1) * num_classes; ++c) {\n      int idx = i * (H * W * D) + c * (H * W) + y * W + x;\n      Pdata[idx] /= expsum;\n    }\n  }\n}\n\n\n__global__ void SoftmaxFocalLossKernel(\n    const int N, const int A, const int H, const int W,\n    const float* Pdata, const int* targets, float* losses,\n    const float* weight_pos, const float gamma, const float alpha,\n    const int num_classes) {\n  CUDA_1D_KERNEL_LOOP(i, N * A * H * W) {\n    int D = A * num_classes;\n    int x = i % W;\n    int y = (i / W) % H;\n    int a = (i / (W * H)) % A;\n    int n = i / (W * H * A);\n    const int label = static_cast<int>(targets[i]);\n\n    float Np = max(weight_pos[0], 1.0);\n    float z = (label == 0) * (1 - alpha) / Np +\n              (label >= 1) * alpha / Np;\n\n    losses[i] = 0.0;\n    if (label >= 0) {\n      int offset = a * num_classes;\n      int idx = n * (H * W * D) + (offset + label) * (H * W) + y * W + x;\n      losses[i] =\n          -(pow(1.0f - Pdata[idx], gamma) *\n          log(max(Pdata[idx], FLT_MIN))) * z;\n    }\n  }\n}\n\n\n__global__ void SoftmaxFocalLossGradientWeightKernel(\n    const int N, const int A, const int H, const int W,\n    const float* Pdata, const int* targets, float* buff,\n    const float* weight_pos, const float gamma, const float alpha,\n    const int num_classes) {\n  CUDA_1D_KERNEL_LOOP(i, N * A * H * W) {\n    int D = A * num_classes;\n    int x = i % W;\n    int y = (i / W) % H;\n    int a = (i / (W * H)) % A;\n    int n = i / (W * H * A);\n    const int label = static_cast<int>(targets[i]);\n    float Np = max(weight_pos[0], 1.0);\n    float z =  (label == 0) * (1 - alpha) / Np +\n               (label >= 1) * alpha / Np;\n\n    buff[i] = 0.0;\n    if (label >= 0) {\n      int offset = a * num_classes;\n      int idx = n * (H * W * D) + (offset + label) * (H * W) + y * W + x;\n      float onemp = 1. - Pdata[idx];\n      float p = Pdata[idx];\n      buff[i] =\n          (-pow(onemp, gamma) +\n          gamma * pow(onemp, gamma - 1) * p * log(max(p, FLT_MIN))) * z;\n    }\n  }\n}\n\n\n__global__ void SoftmaxFocalLossGradientKernel(\n    const int N, const int D, const int H, const int W,\n    const float* Pdata, const int* targets, const float* buff,\n    const float* d_loss_data, float* dX, const int num_classes) {\n  CUDA_1D_KERNEL_LOOP(i, N * D * H * W) {\n    int A = D / num_classes;\n    int x = i % W;\n    int y = (i / W) % H;\n    int d = (i / (W * H)) % D;\n    int a = d / num_classes;\n    int c = d % num_classes;\n    int n = i / (W * H * D);\n    float d_loss = *d_loss_data;\n\n    int ind = n * (H * W * A) + a * (H * W) + y * W + x;\n    const int label = static_cast<int>(targets[ind]);\n\n    float c1 = (label >= 0) * 1.0;\n    float c2 = (label == c) * 1.0;\n    dX[i] = 0.0;\n    dX[i] = c1 * d_loss * buff[ind] * (c2 - Pdata[i]);\n  }\n}\n\n} // namespace\n\n\ntemplate <>\nbool SoftmaxFocalLossOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);         // Logits\n  auto& T = Input(1);         // Labels\n  auto& wp = Input(2);        // num of foregound\n  auto* avg_loss = Output(0); // average loss as output\n  auto* P = Output(1);        // softmax probability, going to be re-used in gradient\n\n  int N = X.dim32(0);\n  int D = X.dim32(1);\n  int H = X.dim32(2);\n  int W = X.dim32(3);\n  int A = D / num_classes_;\n\n  losses_.Resize(N * A * H * W);\n  P->Resize(N * D * H * W);\n  avg_loss->Resize(vector<TIndex>());\n  math::Set<float, CUDAContext>(\n      avg_loss->size(), 0.f, avg_loss->mutable_data<float>(), &context_);\n  math::Set<float, CUDAContext>(\n      P->size(), 0.f, P->mutable_data<float>(), &context_);\n  math::Set<float, CUDAContext>(\n      losses_.size(), 0.f, losses_.mutable_data<float>(), &context_);\n  DCHECK_EQ(X.ndim(), 4);\n\n  const float* Xdata = X.data<float>();\n  const float* Wdata = wp.data<float>();\n\n\n  // Spatial Softmax Kernel\n  SpatialSoftmaxKernel\n      <<<CAFFE_GET_BLOCKS(N * A * H * W), CAFFE_CUDA_NUM_THREADS,\n         0, context_.cuda_stream()>>>(\n    N, A, H, W, Xdata, P->mutable_data<float>(), num_classes_);\n\n  // Compute loss for each x,y location\n  const int* Tdata = T.data<int>();\n  SoftmaxFocalLossKernel\n  <<<CAFFE_GET_BLOCKS(N * A * H * W), CAFFE_CUDA_NUM_THREADS,\n      0, context_.cuda_stream()>>>(\n    N, A, H, W, P->data<float>(), Tdata, losses_.mutable_data<float>(),\n    Wdata, gamma_, alpha_, num_classes_);\n\n  // sum the losses\n  float* avg_loss_data = avg_loss->mutable_data<float>();\n  math::Sum<float, CUDAContext>(\n      losses_.size(), losses_.data<float>(), avg_loss_data, &context_);\n  math::Scale<float, CUDAContext>(\n      1, scale_, avg_loss_data, avg_loss_data, &context_);\n\n  return true;\n}\n\n\ntemplate<>\nbool SoftmaxFocalLossGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);    // Logits\n  auto& T = Input(1);    // Label\n  auto& wp = Input(2);   // num of foreground example\n  auto& P = Input(3);    // Softmax Probability\n  auto& d_avg_loss = Input(4);\n  auto* dX = Output(0);  // gradient wrt logits\n\n\n  int N = X.dim32(0);\n  int D = X.dim32(1);\n  int H = X.dim32(2);\n  int W = X.dim32(3);\n  int A = D / num_classes_;\n\n  buff_.Resize(N * A * H * W);\n\n  dX->ResizeLike(X);\n\n  const float* Xdata = X.data<float>();\n  const int* Tdata = T.data<int>();\n  const float* Pdata = P.data<float>();\n  const float* Wdata = wp.data<float>();\n\n\n  // Compute the weight for gradients\n  SoftmaxFocalLossGradientWeightKernel\n      <<<CAFFE_GET_BLOCKS(N * A * H * W), CAFFE_CUDA_NUM_THREADS,\n         0, context_.cuda_stream()>>>(\n    N, A, H, W, Pdata, Tdata, buff_.mutable_data<float>(),\n    Wdata, gamma_, alpha_, num_classes_);\n  // Compute the gradient with the weights\n  const float* Bdata = buff_.data<float>();\n  SoftmaxFocalLossGradientKernel\n      <<<CAFFE_GET_BLOCKS(N * D * H * W), CAFFE_CUDA_NUM_THREADS,\n         0, context_.cuda_stream()>>>(\n    N, D, H, W, Pdata, Tdata, Bdata, d_avg_loss.data<float>(),\n    dX->mutable_data<float>(), num_classes_);\n  math::Scale<float, CUDAContext>(\n    dX->size(), scale_, dX->data<float>(), dX->mutable_data<float>(),\n    &context_);\n  return true;\n}\n\n\nREGISTER_CUDA_OPERATOR(SoftmaxFocalLoss,\n                       SoftmaxFocalLossOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(SoftmaxFocalLossGradient,\n                       SoftmaxFocalLossGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/softmax_focal_loss_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef SOFTMAX_FOCAL_LOSS_OP_H_\n#define SOFTMAX_FOCAL_LOSS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass SoftmaxFocalLossOp final : public Operator<Context> {\n public:\n  SoftmaxFocalLossOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)),\n        gamma_(OperatorBase::GetSingleArgument<float>(\"gamma\", 1.)),\n        alpha_(OperatorBase::GetSingleArgument<float>(\"alpha\", 0.25)),\n        num_classes_(OperatorBase::GetSingleArgument<int>(\"num_classes\", 81)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CAFFE_ENFORCE(scale_ >= 0);\n    CAFFE_ENFORCE_EQ(\n        order_, StorageOrder::NCHW, \"Only NCHW order is supported right now.\");\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float scale_;\n  float gamma_;\n  float alpha_;\n  int num_classes_;\n  StorageOrder order_;\n  Tensor<Context> losses_;\n};\n\ntemplate <typename T, class Context>\nclass SoftmaxFocalLossGradientOp final : public Operator<Context> {\n public:\n  SoftmaxFocalLossGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        scale_(OperatorBase::GetSingleArgument<float>(\"scale\", 1.)),\n        gamma_(OperatorBase::GetSingleArgument<float>(\"gamma\", 1.)),\n        alpha_(OperatorBase::GetSingleArgument<float>(\"alpha\", 0.25)),\n        num_classes_(OperatorBase::GetSingleArgument<int>(\"num_classes\", 81)),\n        order_(StringToStorageOrder(\n            OperatorBase::GetSingleArgument<string>(\"order\", \"NCHW\"))) {\n    CAFFE_ENFORCE(scale_ >= 0);\n    CAFFE_ENFORCE_EQ(\n        order_, StorageOrder::NCHW, \"Only NCHW order is supported right now.\");\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  float scale_;\n  float gamma_;\n  float alpha_;\n  int num_classes_;\n  StorageOrder order_;\n  Tensor<Context> buff_;\n};\n\n} // namespace caffe2\n\n#endif // SOFTMAX_FOCAL_LOSS_OP_H_\n"
  },
  {
    "path": "modules/detectron/spatial_narrow_as_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"spatial_narrow_as_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(SpatialNarrowAs, SpatialNarrowAsOp<CPUContext>);\nREGISTER_CPU_OPERATOR(\n    SpatialNarrowAsGradient,\n    SpatialNarrowAsGradientOp<CPUContext>);\n\nOPERATOR_SCHEMA(SpatialNarrowAs)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nReduces (\"narrows\") the spatial extent of A to that of B by removing rows and\ncolumns from the bottom and right.\n)DOC\")\n    .Input(\n        0,\n        \"A\",\n        \"3D or 4D input of shape (N, H0, W0) or (N, C, H0, W0).\")\n    .Input(\n        1,\n        \"B\",\n        \"3D or 4D input of shape (N, H1, W1) or (N, C, H1, W1), where H1 <= H0 \"\n        \"and W1 <= W0.\")\n    .Output(\n        0,\n        \"C\",\n        \"Sub window of A containing rows [0, H1 - 1] (inclusive) and columns \"\n        \"[0, W1 - 1] (inclusive).\");\n\nOPERATOR_SCHEMA(SpatialNarrowAsGradient)\n    .NumInputs(3)\n    .NumOutputs(1)\n    .Input(\n        0,\n        \"A\",\n        \"See SpatialNarrowAs.\")\n    .Input(\n        1,\n        \"B\",\n        \"See SpatialNarrowAs.\")\n    .Input(\n        2,\n        \"dC\",\n        \"Gradient of forward output 0 (C).\")\n    .Output(\n        0,\n        \"dA\",\n        \"Gradient of forward input 0 (A)\");\n\nclass SpatialNarrowAsGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"SpatialNarrowAsGradient\", \"\",\n        vector<string>{I(0), I(1), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\nREGISTER_GRADIENT(SpatialNarrowAs, SpatialNarrowAsGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/spatial_narrow_as_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"caffe2/core/operator.h\"\n#include \"spatial_narrow_as_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\ntemplate <typename T>\n__global__ void CopyKernel(\n    const int N,\n    const int C,\n    const int in_H,\n    const int in_W,\n    const int out_H,\n    const int out_W,\n    const T* in_data,\n    T* out_data) {\n  CUDA_1D_KERNEL_LOOP(index, N * C * out_H * out_W) {\n    int w = index % out_W;\n    int h = (index / out_W) % out_H;\n    int c = (index / out_W / out_H) % C;\n    int n = (index / out_W / out_H / C);\n    int in_index = n * C * in_H * in_W + c * in_H * in_W + h * in_W + w;\n    int out_index = n * C * out_H * out_W + c * out_H * out_W + h * out_W + w;\n    out_data[out_index] = in_data[in_index];\n  }\n}\n\ntemplate <typename T>\n__global__ void CopyGradientKernel(\n    const int N,\n    const int C,\n    const int in_H,\n    const int in_W,\n    const int out_H,\n    const int out_W,\n    const T* in_data,\n    T* out_data) {\n  CUDA_1D_KERNEL_LOOP(index, N * C * in_H * in_W) {\n    int w = index % in_W;\n    int h = (index / in_W) % in_H;\n    int c = (index / in_W / in_H) % C;\n    int n = (index / in_W / in_H / C);\n    int in_index = n * C * in_H * in_W + c * in_H * in_W + h * in_W + w;\n    int out_index = n * C * out_H * out_W + c * out_H * out_W + h * out_W + w;\n    out_data[out_index] = in_data[in_index];\n  }\n}\n} // namespace\n\n\ntemplate <>\nbool SpatialNarrowAsOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<float_t, int32_t>>::call(this, Input(0));\n}\n\ntemplate <>\ntemplate <typename T>\nbool SpatialNarrowAsOp<CUDAContext>::DoRunWithType() {\n  // Narrows input 0 (A) spatially to match input 1 (B)\n  auto& A = Input(0);\n  auto& B = Input(1);\n  auto* C = Output(0);\n\n  CAFFE_ENFORCE_EQ(A.dim32(0), B.dim32(0), \"Input dim 0 must be equal.\");\n  if (A.ndim() == B.ndim()) {\n    CAFFE_ENFORCE_EQ(A.dim32(1), B.dim32(1), \"Input dim 1 must be equal.\");\n    CAFFE_ENFORCE_GE(\n        A.dim32(2), B.dim32(2), \"Input 0 height must be >= input 1 height.\");\n    CAFFE_ENFORCE_GE(\n        A.dim32(3), B.dim32(3), \"Input 0 width must be >= input 1 width.\");\n\n    C->ResizeLike(B);\n  } else {\n    // For (N, H, W) case\n    CAFFE_ENFORCE_EQ(A.ndim() - 1, B.ndim(), \"Dimension mismatch.\");\n    CAFFE_ENFORCE_GE(\n        A.dim32(2), B.dim32(1), \"Input 0 height must be >= input 1 height.\");\n    CAFFE_ENFORCE_GE(\n        A.dim32(3), B.dim32(2), \"Input 0 width must be >= input 1 width.\");\n    C->Resize(A.dim32(0), A.dim32(1), B.dim32(1), B.dim32(2));\n  }\n  int out_width = C->dim32(3);\n  int out_height = C->dim32(2);\n  int in_width = A.dim32(3);\n  int in_height = A.dim32(2);\n\n  CopyKernel<T><<<\n      CAFFE_GET_BLOCKS(C->size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      C->dim32(0),\n      C->dim32(1),\n      in_height,\n      in_width,\n      out_height,\n      out_width,\n      A.template data<T>(),\n      C->template mutable_data<T>());\n\n  return true;\n}\n\ntemplate <>\nbool SpatialNarrowAsGradientOp<CUDAContext>::RunOnDevice() {\n  return DispatchHelper<TensorTypes<float_t, int32_t>>::call(this, Input(0));\n}\n\ntemplate <>\ntemplate <typename T>\nbool SpatialNarrowAsGradientOp<CUDAContext>::DoRunWithType() {\n  auto& A = Input(0);\n  auto& B = Input(1);\n  auto& dC = Input(2); // Gradient of net w.r.t. output of forward op\n  auto* dA = Output(0); // Gradient of net w.r.t. input to forward op\n\n  dA->ResizeLike(A);\n  math::Set<T, CUDAContext>(\n      dA->size(), 0.f, dA->template mutable_data<T>(), &context_);\n  int out_width = dA->dim32(3);\n  int out_height = dA->dim32(2);\n  int in_width = dC.dim32(3);\n  int in_height = dC.dim32(2);\n\n  CopyGradientKernel<T><<<\n      CAFFE_GET_BLOCKS(dC.size()),\n      CAFFE_CUDA_NUM_THREADS,\n      0,\n      context_.cuda_stream()>>>(\n      dA->dim32(0),\n      dA->dim32(1),\n      in_height,\n      in_width,\n      out_height,\n      out_width,\n      dC.template data<T>(),\n      dA->template mutable_data<T>());\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(SpatialNarrowAs, SpatialNarrowAsOp<CUDAContext>);\nREGISTER_CUDA_OPERATOR(\n    SpatialNarrowAsGradient,\n    SpatialNarrowAsGradientOp<CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/spatial_narrow_as_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef SPATIAL_NARROW_AS_OP_H_\n#define SPATIAL_NARROW_AS_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <class Context>\nclass SpatialNarrowAsOp final : public Operator<Context> {\n public:\n  SpatialNarrowAsOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_DISPATCH_HELPER;\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  template <typename T>\n  bool DoRunWithType();\n};\n\ntemplate <class Context>\nclass SpatialNarrowAsGradientOp final : public Operator<Context> {\n public:\n  SpatialNarrowAsGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws) {}\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n  USE_DISPATCH_HELPER;\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  template <typename T>\n  bool DoRunWithType();\n};\n\n} // namespace caffe2\n\n#endif // SPATIAL_NARROW_AS_OP_H_\n"
  },
  {
    "path": "modules/detectron/upsample_nearest_op.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"upsample_nearest_op.h\"\n\nnamespace caffe2 {\n\nREGISTER_CPU_OPERATOR(UpsampleNearest, UpsampleNearestOp<float, CPUContext>);\nREGISTER_CPU_OPERATOR(\n    UpsampleNearestGradient,\n    UpsampleNearestGradientOp<float, CPUContext>);\n\nOPERATOR_SCHEMA(UpsampleNearest)\n    .NumInputs(1)\n    .NumOutputs(1)\n    .SetDoc(R\"DOC(\nNearest neighbor upsampling operation. Implementation taken from THCUNN.\n)DOC\")\n    .Arg(\n        \"scale\",\n        \"(int) default 2; integer upsampling factor.\")\n    .Input(\n        0,\n        \"X\",\n        \"4D feature map input of shape (N, C, H, W).\")\n    .Output(\n        0,\n        \"Y\",\n        \"4D feature map of shape (N, C, scale * H, scale * W); Values are \"\n        \"neareast neighbor samples from X.\");\n\nOPERATOR_SCHEMA(UpsampleNearestGradient)\n    .NumInputs(2)\n    .NumOutputs(1)\n    .Input(\n        0,\n        \"X\",\n        \"See UpsampleNearest.\")\n    .Input(\n        1,\n        \"dY\",\n        \"Gradient of forward output 0 (Y).\")\n    .Output(\n        0,\n        \"dX\",\n        \"Gradient of forward input 0 (X).\");\n\nclass GetUpsampleNearestGradient : public GradientMakerBase {\n  using GradientMakerBase::GradientMakerBase;\n  vector<OperatorDef> GetGradientDefs() override {\n    return SingleGradientDef(\n        \"UpsampleNearestGradient\",\n        \"\",\n        vector<string>{I(0), GO(0)},\n        vector<string>{GI(0)});\n  }\n};\n\nREGISTER_GRADIENT(UpsampleNearest, GetUpsampleNearestGradient);\n\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/upsample_nearest_op.cu",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Adapted from https://github.com/torch/cunn/blob/master/lib/THCUNN/SpatialUpSamplingNearest.cu\n *\n * Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert)\n * Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu)\n * Copyright (c) 2011-2013 NYU (Clement Farabet)\n * Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert,\n *                         Leon Bottou, Iain Melvin, Jason Weston)\n * Copyright (c) 2006      Idiap Research Institute (Samy Bengio)\n * Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert,\n *                         Samy Bengio, Johnny Mariethoz)\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the names of NEC Laboratories American and IDIAP Research\n *    Institute nor the names of its contributors may be used to endorse or\n *    promote products derived from this software without specific prior\n *    written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n\n#include \"caffe2/core/context_gpu.h\"\n#include \"upsample_nearest_op.h\"\n\nnamespace caffe2 {\n\nnamespace {\n__device__ int translate_idx(int ii, int d1, int d2, int d3, int scale_factor) {\n  int x, y, z, w;\n  w = ii % d3;\n  ii = ii/d3;\n  z = ii % d2;\n  ii = ii/d2;\n  y = ii % d1;\n  ii = ii/d1;\n  x = ii;\n  w = w/scale_factor;\n  z = z/scale_factor;\n  d2 /= scale_factor;\n  d3 /= scale_factor;\n  return (((x*d1+y)*d2)+z)*d3+w;\n}\n\n__device__ int translate_idx_inv(\n    int ii, int d1, int d2, int d3, int scale_factor, int off_x, int off_y) {\n  int x, y, z, w;\n  w = ii % d3;\n  ii = ii/d3;\n  z = ii % d2;\n  ii = ii/d2;\n  y = ii % d1;\n  ii = ii/d1;\n  x = ii;\n  w = w*scale_factor+off_x;\n  z = z*scale_factor+off_y;\n  d2 *= scale_factor;\n  d3 *= scale_factor;\n  return (((x*d1+y)*d2)+z)*d3+w;\n}\n\n__global__ void upscale(const float *input, float *output, long no_elements,\n                        int scale_factor, int d1, int d2, int d3) {\n  long ii = threadIdx.x + blockDim.x * blockIdx.x;\n  ii += threadIdx.y + blockDim.y * (blockDim.x * gridDim.x) * blockIdx.y;\n  if (ii >= no_elements) return;\n  int ipidx = translate_idx(ii, d1, d2, d3, scale_factor);\n  output[ii]=input[ipidx];\n}\n\n__global__ void downscale(float *gradInput_data, const float *gradOutput_data,\n                          long no_elements, int scale_factor, int d1, int d2,\n                          int d3) {\n  long ii = threadIdx.x + blockDim.x * blockIdx.x;\n  ii += threadIdx.y + blockDim.y * (blockDim.x * gridDim.x) * blockIdx.y;\n  if (ii >= no_elements) return;\n  for (int i=0; i < scale_factor; i++){\n    for(int j=0; j < scale_factor; j++){\n      int ipidx = translate_idx_inv(ii, d1, d2, d3, scale_factor, i, j);\n      gradInput_data[ii] += gradOutput_data[ipidx];\n    }\n  }\n}\n} // namespace\n\ntemplate<>\nbool UpsampleNearestOp<float, CUDAContext>::RunOnDevice() {\n  auto& X = Input(0);\n  auto* Y = Output(0);\n\n  vector<TIndex> out_shape;\n  for (int i = 0; i < X.ndim(); ++i) {\n    out_shape.push_back(X.dim32(i));\n  }\n  out_shape[X.ndim() - 1] *= scale_;\n  out_shape[X.ndim() - 2] *= scale_;\n  Y->Resize(out_shape);\n\n  int d1;\n  int d2;\n  int d3;\n  if (X.ndim() == 3) {\n    d1 = Y->dim32(0);\n    d2 = Y->dim32(1);\n    d3 = Y->dim32(2);\n  } else {\n    d1 = Y->dim32(1);\n    d2 = Y->dim32(2);\n    d3 = Y->dim32(3);\n  }\n  long no_elements = Y->size();\n\n  const float *input_data = X.data<float>();\n  float *output_data = Y->mutable_data<float>();\n\n  // cuda blocks & threads:\n  long nthreads = 256;\n  // Max number of blocks: http://en.wikipedia.org/wiki/CUDA\n  // 65535 for SM 2.x, 2^32 -1 for >= 3.0\n  // TODO: When we move to SM 3.5 we should update this\n  long n_xblocks = min(max((int)ceil((float)no_elements / nthreads), 1), 65535);\n  long n_yblocks = (long)ceil(\n      (float)no_elements / (float)(n_xblocks * nthreads));\n  CAFFE_ENFORCE(n_yblocks <= 65535);\n  dim3 blocks(n_xblocks, n_yblocks);\n  dim3 threads(nthreads);\n\n  upscale<<<blocks, threads, 0, context_.cuda_stream()>>>(\n      input_data, output_data, no_elements, scale_, d1, d2, d3);\n  return true;\n}\n\n\ntemplate<>\nbool UpsampleNearestGradientOp<float, CUDAContext>::RunOnDevice() {\n  auto& X  = Input(0);   // Original input to \"forward\" op\n  auto& dY = Input(1);   // Gradient of net w.r.t. output of \"forward\" op\n                         // (aka \"gradOutput\")\n  auto* dX = Output(0);  // Gradient of net w.r.t. input to \"forward\" op\n                         // (aka \"gradInput\")\n\n  dX->ResizeLike(X);\n  float *gradInput_data = dX->mutable_data<float>();\n  const float *gradOutput_data = dY.data<float>();\n\n  int d1;\n  int d2;\n  int d3;\n  if (dX->ndim() == 3) {\n    d1 = dX->dim32(0);\n    d2 = dX->dim32(1);\n    d3 = dX->dim32(2);\n  } else {\n    d1 = dX->dim32(1);\n    d2 = dX->dim32(2);\n    d3 = dX->dim32(3);\n  }\n  long no_elements = dX->size();\n\n  // cuda blocks & threads:\n  long nthreads = 256;\n  // Max number of blocks: http://en.wikipedia.org/wiki/CUDA\n  // 65535 for SM 2.x, 2^32 -1 for >= 3.0\n  // TODO: When we move to SM 3.5 we should update this\n  long n_xblocks = min(max((int)ceil((float)no_elements / nthreads), 1), 65535);\n  long n_yblocks = (long)ceil(\n      (float)no_elements / (float)(n_xblocks * nthreads));\n  CAFFE_ENFORCE(n_yblocks <= 65535);\n  dim3 blocks(n_xblocks, n_yblocks);\n  dim3 threads(nthreads);\n\n  math::Set<float, CUDAContext>(no_elements, 0.f, gradInput_data, &context_);\n  downscale<<<blocks, threads, 0, context_.cuda_stream()>>>(\n      gradInput_data, gradOutput_data, no_elements, scale_, d1, d2, d3);\n\n  return true;\n}\n\nREGISTER_CUDA_OPERATOR(UpsampleNearest,\n                       UpsampleNearestOp<float, CUDAContext>);\nREGISTER_CUDA_OPERATOR(UpsampleNearestGradient,\n                       UpsampleNearestGradientOp<float, CUDAContext>);\n} // namespace caffe2\n"
  },
  {
    "path": "modules/detectron/upsample_nearest_op.h",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef UPSAMPLE_NEAREST_OP_H_\n#define UPSAMPLE_NEAREST_OP_H_\n\n#include \"caffe2/core/context.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/operator.h\"\n#include \"caffe2/utils/math.h\"\n\nnamespace caffe2 {\n\ntemplate <typename T, class Context>\nclass UpsampleNearestOp final : public Operator<Context> {\n public:\n  UpsampleNearestOp(const OperatorDef& operator_def, Workspace* ws)\n      : Operator<Context>(operator_def, ws),\n        scale_(OperatorBase::GetSingleArgument<int>(\"scale\", 2)) {\n    DCHECK_GE(scale_, 1);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  int scale_;\n};\n\ntemplate <typename T, class Context>\nclass UpsampleNearestGradientOp final : public Operator<Context> {\n public:\n  UpsampleNearestGradientOp(const OperatorDef& def, Workspace* ws)\n      : Operator<Context>(def, ws),\n        scale_(OperatorBase::GetSingleArgument<int>(\"scale\", 2)) {\n    DCHECK_GE(scale_, 1);\n  }\n  USE_OPERATOR_CONTEXT_FUNCTIONS;\n\n  bool RunOnDevice() override {\n    // No CPU implementation for now\n    CAFFE_NOT_IMPLEMENTED;\n  }\n\n protected:\n  int scale_;\n};\n\n} // namespace caffe2\n\n#endif // UPSAMPLE_NEAREST_OP_H_\n"
  },
  {
    "path": "modules/module_test/CMakeLists.txt",
    "content": "if (NOT CAFFE2_CMAKE_BUILDING_WITH_MAIN_REPO)\n  # If we are building the standalone module, we set the proper cmake variables.\n  cmake_minimum_required(VERSION 3.0 FATAL_ERROR)\n  find_package(Caffe2 REQUIRED)\n  set(BUILD_TEST ON)\nendif()\n\nif (BUILD_TEST)\n  add_library(\n      caffe2_module_test_dynamic\n      ${CMAKE_CURRENT_SOURCE_DIR}/module_test_dynamic.cc)\n\n  target_link_libraries(caffe2_module_test_dynamic caffe2_library)\n  install(TARGETS caffe2_module_test_dynamic DESTINATION lib)\nendif()\n"
  },
  {
    "path": "modules/module_test/module_test_dynamic.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/module.h\"\n#include \"caffe2/core/operator.h\"\n\n// An explicitly defined module, testing correctness when we dynamically link a\n// module\nCAFFE2_MODULE(caffe2_module_test_dynamic, \"Dynamic module only used for testing.\");\n\nnamespace caffe2 {\n\nclass Caffe2ModuleTestDynamicDummyOp : public OperatorBase {\n public:\n  using OperatorBase::OperatorBase;\n  bool Run(int /* unused */ /*stream_id*/) override {\n    return true;\n  }\n  virtual string type() {\n    return \"base\";\n  }\n};\n\nREGISTER_CPU_OPERATOR(\n  Caffe2ModuleTestDynamicDummy, Caffe2ModuleTestDynamicDummyOp);\nOPERATOR_SCHEMA(Caffe2ModuleTestDynamicDummy);\n\n} // namespace caffe2\n"
  },
  {
    "path": "modules/observers/CMakeLists.txt",
    "content": "if (CAFFE2_CMAKE_BUILDING_WITH_MAIN_REPO)\n  if (NOT USE_OBSERVERS)\n    return()\n  endif()\nelse()\n  cmake_minimum_required(VERSION 3.0 FATAL_ERROR)\n  project(caffe2_observers CXX)\n  find_package(Caffe2 REQUIRED)\n  option(BUILD_SHARED_LIBS \"Build shared libs.\" ON)\nendif()\n\nadd_library(caffe2_observers\n    \"${CMAKE_CURRENT_SOURCE_DIR}/net_observer_reporter_print.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/observer_config.cc\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/perf_observer.cc\"\n    )\ntarget_link_libraries(caffe2_observers PUBLIC caffe2_library)\ntarget_include_directories(caffe2_observers PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..)\ninstall(TARGETS caffe2_observers DESTINATION lib)\ncaffe2_interface_library(caffe2_observers caffe2_observers_library)\n\nif (CAFFE2_CMAKE_BUILDING_WITH_MAIN_REPO)\n  set(Caffe2_MODULES ${Caffe2_MODULES} caffe2_observers_library PARENT_SCOPE)\nendif()\n\n"
  },
  {
    "path": "modules/observers/net_observer_reporter.h",
    "content": "#pragma once\n#include <map>\n#include \"caffe2/core/net.h\"\n\nnamespace caffe2 {\n\nclass NetObserverReporter {\n public:\n  virtual ~NetObserverReporter() = default;\n\n  /*\n    Report the delay metric collected by the observer.\n    The delays are saved in a map. The key is an identifier associated\n    with the reported delay. The value is the delay value in float\n  */\n  virtual void reportDelay(\n      NetBase* net,\n      std::map<std::string, double>& delays,\n      const char* unit) = 0;\n};\n}\n"
  },
  {
    "path": "modules/observers/net_observer_reporter_print.cc",
    "content": "#include \"observers/net_observer_reporter_print.h\"\n\n#include \"caffe2/core/init.h\"\n#include \"observers/observer_config.h\"\n\nnamespace caffe2 {\n\nnamespace {\nbool registerGlobalPerfNetObserverReporter(int* /*pargc*/, char*** /*pargv*/) {\n  ObserverConfig::setReporter(make_unique<NetObserverReporterPrint>());\n  return true;\n}\n} // namespace\n\nREGISTER_CAFFE2_EARLY_INIT_FUNCTION(\n    registerGlobalPerfNetObserverReporter,\n    &registerGlobalPerfNetObserverReporter,\n    \"Caffe2 print net observer reporter\");\n\nconst std::string NetObserverReporterPrint::IDENTIFIER = \"Caffe2Observer \";\n\nvoid NetObserverReporterPrint::reportDelay(\n    NetBase* net,\n    std::map<std::string, double>& delays,\n    const char* unit) {\n  CAFFE_ENFORCE(unit != nullptr, \"Unit is null\");\n  LOG(INFO) << IDENTIFIER << \"Net Name - \" << net->Name();\n  LOG(INFO) << IDENTIFIER << \"Delay Start\";\n  for (auto& p : delays) {\n    LOG(INFO) << IDENTIFIER << p.first << \" - \" << p.second << \"\\t(\" << *unit\n              << \")\";\n  }\n  LOG(INFO) << IDENTIFIER << \"Delay End\";\n}\n}\n"
  },
  {
    "path": "modules/observers/net_observer_reporter_print.h",
    "content": "#pragma once\n\n#include \"observers/net_observer_reporter.h\"\n\nnamespace caffe2 {\n\nclass NetObserverReporterPrint : public NetObserverReporter {\n public:\n  static const std::string IDENTIFIER;\n  void reportDelay(\n      NetBase* net,\n      std::map<std::string, double>& delays,\n      const char* unit);\n};\n}\n"
  },
  {
    "path": "modules/observers/observer_config.cc",
    "content": "#include \"observers/observer_config.h\"\n\nnamespace caffe2 {\n\nint ObserverConfig::netInitSampleRate_ = 0;\nint ObserverConfig::netFollowupSampleRate_ = 0;\nint ObserverConfig::netFollowupSampleCount_ = 0;\nint ObserverConfig::operatorNetSampleRatio_ = 0;\nint ObserverConfig::skipIters_ = 0;\nunique_ptr<NetObserverReporter> ObserverConfig::reporter_ = nullptr;\nint ObserverConfig::marker_ = -1;\n}\n"
  },
  {
    "path": "modules/observers/observer_config.h",
    "content": "#pragma once\n\n#include \"observers/net_observer_reporter.h\"\n\nnamespace caffe2 {\n\n/*\n  netInitSampleRate_ == 1 && operatorNetSampleRatio_ == 1 :\n      Log operator metrics in every iteration\n  netInitSampleRate_ == 1 && operatorNetSampleRatio_ == 0 :\n      Log net metrics in every iterationn\n  netInitSampleRate_ == n && netFollowupSampleRate_ == m &&\n          netFollowupSampleCount == c && operatorNetSampleRatio_ == 1 :\n      Log operator metrics first at odds of 1 / n. Once first logged,\n      the following c logs are at odds of 1 / min(n, m). Then repeat\n  netInitSampleRate_ == n && netFollowupSampleRate_ == m &&\n          netFollowupSampleCount == c && operatorNetSampleRatio_ == 0 :\n      Log net metrics first at odds of 1 / n. Once first logged,\n      the following c logs are at odds of 1 / min(n, m). Then repeat\n  netInitSampleRate_ == n && netFollowupSampleRate_ == m &&\n          netFollowupSampleCount == c && operatorNetSampleRatio_ == o :\n      Log net metrics first at odds of 1 / n. Once first logged,\n      the following c logs are at odds of 1 / min(n, m), if the random number\n      is multiples of o, log operator metrics instead. Then repeat\n  skipIters_ == n: skip the first n iterations of the net.\n*/\nclass ObserverConfig {\n public:\n  static void initSampleRate(\n      int netInitSampleRate,\n      int netFollowupSampleRate,\n      int netFollowupSampleCount,\n      int operatorNetSampleRatio,\n      int skipIters) {\n    CAFFE_ENFORCE(netFollowupSampleRate <= netInitSampleRate);\n    CAFFE_ENFORCE(netFollowupSampleRate >= 1 || netInitSampleRate == 0);\n    netInitSampleRate_ = netInitSampleRate;\n    netFollowupSampleRate_ = netFollowupSampleRate;\n    netFollowupSampleCount_ = netFollowupSampleCount;\n    operatorNetSampleRatio_ = operatorNetSampleRatio;\n    skipIters_ = skipIters;\n  }\n  static int getNetInitSampleRate() {\n    return netInitSampleRate_;\n  }\n  static int getNetFollowupSampleRate() {\n    return netFollowupSampleRate_;\n  }\n  static int getNetFollowupSampleCount() {\n    return netFollowupSampleCount_;\n  }\n  static int getOpoeratorNetSampleRatio() {\n    return operatorNetSampleRatio_;\n  }\n  static int getSkipIters() {\n    return skipIters_;\n  }\n  static void setReporter(unique_ptr<NetObserverReporter> reporter) {\n    // Can only set the reporter once\n    CAFFE_ENFORCE(reporter_ == nullptr);\n    reporter_ = std::move(reporter);\n  }\n  static NetObserverReporter* getReporter() {\n    CAFFE_ENFORCE(reporter_);\n    return reporter_.get();\n  }\n  static void setMarker(int marker) {\n    marker_ = marker;\n  }\n  static int getMarker() {\n    return marker_;\n  }\n\n private:\n  /* The odds of log net metric initially or immediately after reset */\n  static int netInitSampleRate_;\n\n  /* The odds of log net metric after log once after start of reset */\n  static int netFollowupSampleRate_;\n\n  /* The number of follow up logs to be collected for odds of\n     netFollowupSampleRate_ */\n  static int netFollowupSampleCount_;\n\n  /* The odds to log the operator metric instead of the net metric.\n     When the operator is logged the net is not logged. */\n  static int operatorNetSampleRatio_;\n\n  /* skip the first few iterations */\n  static int skipIters_;\n\n  static unique_ptr<NetObserverReporter> reporter_;\n\n  /* marker used in identifying the metrics in certain reporters */\n  static int marker_;\n};\n\n}\n"
  },
  {
    "path": "modules/observers/perf_observer.cc",
    "content": "#include \"observers/perf_observer.h\"\n#include \"observers/observer_config.h\"\n\n#include <random>\n#include \"caffe2/core/common.h\"\n#include \"caffe2/core/init.h\"\n#include \"caffe2/core/operator.h\"\n\nnamespace caffe2 {\nnamespace {\n\nbool registerGlobalPerfNetObserverCreator(int* /*pargc*/, char*** /*pargv*/) {\n  SetGlobalNetObserverCreator([](NetBase* subject) {\n    return caffe2::make_unique<PerfNetObserver>(subject);\n  });\n  return true;\n}\n} // namespace\n\nREGISTER_CAFFE2_EARLY_INIT_FUNCTION(\n    registerGlobalPerfNetObserverCreator,\n    &registerGlobalPerfNetObserverCreator,\n    \"Caffe2 net global observer creator\");\n\nPerfNetObserver::PerfNetObserver(NetBase* subject_)\n    : NetObserver(subject_), numRuns_(0) {}\n\nPerfNetObserver::~PerfNetObserver() {}\n\nvoid PerfNetObserver::Start() {\n  static int visitCount = 0;\n  // Select whether to log the operator or the net.\n  // We have one sample rate for the entire app.\n  int netInitSampleRate = ObserverConfig::getNetInitSampleRate();\n  int netFollowupSampleRate = ObserverConfig::getNetFollowupSampleRate();\n  int netFollowupSampleCount = ObserverConfig::getNetFollowupSampleCount();\n  int operatorNetSampleRatio = ObserverConfig::getOpoeratorNetSampleRatio();\n  int skipIters = ObserverConfig::getSkipIters();\n  int sampleRate = visitCount > 0 ? netFollowupSampleRate : netInitSampleRate;\n  if (skipIters <= numRuns_ && sampleRate > 0 && rand() % sampleRate == 0) {\n    visitCount++;\n    if (visitCount == netFollowupSampleCount) {\n      visitCount = 0;\n    }\n    if (operatorNetSampleRatio > 0 && rand() % operatorNetSampleRatio == 0) {\n      logType_ = PerfNetObserver::OPERATOR_DELAY;\n    } else {\n      logType_ = PerfNetObserver::NET_DELAY;\n    }\n  } else {\n    logType_ = PerfNetObserver::NONE;\n  }\n  numRuns_++;\n\n  if (logType_ == PerfNetObserver::OPERATOR_DELAY) {\n    /* Always recreate new operator  observers\n       whenever we measure operator delay */\n    const auto& operators = subject_->GetOperators();\n    for (auto* op : operators) {\n      observerMap_[op] = op->AttachObserver(\n          caffe2::make_unique<PerfOperatorObserver>(op, this));\n    }\n  }\n\n  if (logType_ != PerfNetObserver::NONE) {\n    /* Only start timer when we need to */\n    timer_.Start();\n  }\n}\n\nvoid PerfNetObserver::Stop() {\n  if (logType_ == PerfNetObserver::NONE) {\n    return;\n  }\n  auto currentRunTime = timer_.MilliSeconds();\n  std::map<std::string, double> delays;\n  delays.insert({\"NET_DELAY\", currentRunTime});\n  if (logType_ == PerfNetObserver::OPERATOR_DELAY) {\n    const auto& operators = subject_->GetOperators();\n    for (int idx = 0; idx < operators.size(); ++idx) {\n      const auto* op = operators[idx];\n      auto name = getObserverName(op, idx);\n      double delay = static_cast<const PerfOperatorObserver*>(observerMap_[op])\n                         ->getMilliseconds();\n      delays.insert({name, delay});\n    }\n    /* clear all operator delay after use so that we don't spent time\n       collecting the operator delay info in later runs */\n    for (auto* op : operators) {\n      op->DetachObserver(observerMap_[op]);\n    }\n    observerMap_.clear();\n  }\n  ObserverConfig::getReporter()->reportDelay(subject_, delays, \"ms\");\n}\n\ncaffe2::string PerfNetObserver::getObserverName(const OperatorBase* op, int idx)\n    const {\n  string opType = op->has_debug_def() ? op->debug_def().type() : \"NO_TYPE\";\n  string displayName =\n      (op->has_debug_def() ? op->debug_def().name().size()\n               ? op->debug_def().name()\n               : (op->debug_def().output_size() ? op->debug_def().output(0)\n                                                : \"NO_OUTPUT\")\n                           : \"NO_DEF\");\n  caffe2::string name =\n      \"ID_\" + caffe2::to_string(idx) + \"_\" + opType + \"_\" + displayName;\n  return name;\n}\n\nPerfOperatorObserver::PerfOperatorObserver(\n    OperatorBase* op,\n    PerfNetObserver* netObserver)\n    : ObserverBase<OperatorBase>(op),\n      netObserver_(netObserver),\n      milliseconds_(0) {\n  CAFFE_ENFORCE(netObserver_, \"Observers can't operate outside of the net\");\n}\n\nPerfOperatorObserver::~PerfOperatorObserver() {}\n\nvoid PerfOperatorObserver::Start() {\n  /* Get the time from the start of the net minus the time spent\n     in previous invocations. It is the time spent on other operators.\n     This way, when the operator finishes, the time from the start of the net\n     minus the time spent in all other operators  is the total time on this\n     operator. This is done to avoid saving a timer in each operator */\n  milliseconds_ = netObserver_->getTimer().MilliSeconds() - milliseconds_;\n}\n\nvoid PerfOperatorObserver::Stop() {\n  /* Time from the start of the net minus the time spent on all other\n     operators is the time spent on this operator */\n  milliseconds_ = netObserver_->getTimer().MilliSeconds() - milliseconds_;\n}\n\ndouble PerfOperatorObserver::getMilliseconds() const {\n  return milliseconds_;\n}\n\nstd::unique_ptr<ObserverBase<OperatorBase>> PerfOperatorObserver::copy(\n    OperatorBase* subject) {\n  return std::unique_ptr<ObserverBase<OperatorBase>>(\n      new PerfOperatorObserver(subject, netObserver_));\n}\n\n} // namespace caffe2\n"
  },
  {
    "path": "modules/observers/perf_observer.h",
    "content": "#pragma once\n\n#include \"caffe2/core/net.h\"\n#include \"caffe2/core/observer.h\"\n#include \"caffe2/core/timer.h\"\n\n#include <unordered_map>\n\nnamespace caffe2 {\n\nclass PerfNetObserver : public NetObserver {\n public:\n  explicit PerfNetObserver(NetBase* subject_);\n  virtual ~PerfNetObserver();\n\n  caffe2::Timer& getTimer() {\n    return timer_;\n  }\n\n private:\n  void Start() override;\n  void Stop() override;\n\n  caffe2::string getObserverName(const OperatorBase* op, int idx) const;\n\n private:\n  enum LogType {\n    NONE,\n    OPERATOR_DELAY,\n    NET_DELAY,\n  };\n  LogType logType_;\n  unsigned int numRuns_;\n  std::unordered_map<const OperatorBase*, const ObserverBase<OperatorBase>*>\n      observerMap_;\n\n  caffe2::Timer timer_;\n};\n\nclass PerfOperatorObserver : public ObserverBase<OperatorBase> {\n public:\n  PerfOperatorObserver(OperatorBase* op, PerfNetObserver* netObserver);\n  virtual ~PerfOperatorObserver();\n\n  std::unique_ptr<ObserverBase<OperatorBase>> copy(\n      OperatorBase* subject) override;\n  double getMilliseconds() const;\n\n private:\n  void Start() override;\n  void Stop() override;\n\n private:\n  // Observer of a net that owns corresponding op. We make sure net is never\n  // destructed while operator observer is still alive. First operator observer\n  // gets destructed, then the op, then the net and its observer.\n  // We do this trick in order to get access to net's name and other fields\n  // without storing inside the operator observer. Each field is memory\n  // costly here and a raw pointer is a cheapest sholution\n  PerfNetObserver* netObserver_;\n  double milliseconds_;\n};\n}\n"
  },
  {
    "path": "modules/rocksdb/CMakeLists.txt",
    "content": "# ---[ RocksDB module\n# In addition to being a useful module itself, RocksDB is also an exemplar\n# case where show how one should built a Caffe2 module inside the Caffe2\n# repository.\n#\n# This cmake file achieves two build modes:\n# (1) If one is invoking the main Caffe2 build, we will check a USE_* option,\n#     in this case USE_ROCKSDB, to test if we want to build this module.\n# (2) if we are building it in a standalone way, we will find the preinstalled\n#     Caffe2 library, and then build the library and install it.\n\n# ---[ First, determine if we are building with the main repo or not.\n# This is guarded by the CAFFE2_CMAKE_BUILDING_WITH_MAIN_REPO variable. It then\n# routes build to two paths:\n# (1) When we are building with the main repo, the caffe2_library is going to\n#     be already defined, and all related paths will be defined too. So we will\n#     simply test if the main repo build wants to build this module, in our\n#     case by the variable \"USE_ROCKSDB\".\n# (2) When we are not building with the main repo, we will need to do the usual\n#     cmake setup: version checks, project options, find dependent packages,\n#     etc.\nif (CAFFE2_CMAKE_BUILDING_WITH_MAIN_REPO)\n  if (NOT USE_ROCKSDB)\n    return()\n  endif()\nelse()\n  cmake_minimum_required(VERSION 3.0 FATAL_ERROR)\n  project(caffe2_rocksdb CXX)\n  find_package(Caffe2 REQUIRED)\n  option(BUILD_SHARED_LIBS \"Build shared libs.\" ON)\n  list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/../../cmake/Modules)\nendif()\n\n\n# ---[ Second, find dependencies.\n# This one should be similar to the standard dependency discovery in normal\n# cmake. Note that for modules that are located in the Caffe2 repository,\n# cmake related files, such as FindRocksDB in this case, should live in the\n# cmake/ folder under root.\nfind_package(RocksDB)\nif(NOT ROCKSDB_FOUND)\n  message(\n     FATAL_ERROR\n     \"RocksDB not found. If you do not need caffe2_rocksdb, set \"\n     \"-DUSE_ROCKSDB=OFF to solve this error.\")\nendif()\n\n# ---[ Third, create the CMake target.\n# The key to note is that this library will need to depend on caffe2_library,\n# which is the main lib of Caffe2. If your library explicitly depends on cuda,\n# then you will need to depend on the caffe2_gpu_library as well.\nadd_library(caffe2_rocksdb ${CMAKE_CURRENT_SOURCE_DIR}/rocksdb.cc)\ntarget_link_libraries(caffe2_rocksdb PUBLIC caffe2_library)\ntarget_link_libraries(caffe2_rocksdb PRIVATE ${RocksDB_LIBRARIES})\ntarget_include_directories(caffe2_rocksdb PRIVATE ${RocksDB_INCLUDE_DIR})\ninstall(TARGETS caffe2_rocksdb DESTINATION lib)\n\n# ---[ Last, Append the library to Caffe2_MAIN_LIBS, if we are building with\n# the main repo.\n# The purpose of this is that, for all binaries built in the Caffe2 main repo,\n# they will be built with the first class modules that are built. As a result,\n# these binaries will not need to explicitly load these modules before using\n# them.\n# Note(jiayq): this also depends on a separate cmake move to reorg test builds\n# and binary builds after modules. When it is done, this note should be removed.\nif (CAFFE2_CMAKE_BUILDING_WITH_MAIN_REPO)\n  set(Caffe2_MODULES ${Caffe2_MODULES} caffe2_rocksdb PARENT_SCOPE)\nendif()\n"
  },
  {
    "path": "modules/rocksdb/rocksdb.cc",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"caffe2/core/db.h\"\n#include \"caffe2/core/logging.h\"\n#include \"caffe2/core/flags.h\"\n#include \"rocksdb/db.h\"\n#include \"rocksdb/utilities/leveldb_options.h\"\n\nCAFFE2_DEFINE_int(caffe2_rocksdb_block_size, 65536,\n                  \"The caffe2 rocksdb block size when writing a rocksdb.\");\n\nnamespace caffe2 {\nnamespace db {\n\nclass RocksDBCursor : public Cursor {\n public:\n  explicit RocksDBCursor(rocksdb::DB* db)\n      : iter_(db->NewIterator(rocksdb::ReadOptions())) {\n    SeekToFirst();\n  }\n  ~RocksDBCursor() {}\n  void Seek(const string& key) override { iter_->Seek(key); }\n  bool SupportsSeek() override { return true; }\n  void SeekToFirst() override { iter_->SeekToFirst(); }\n  void Next() override { iter_->Next(); }\n  string key() override { return iter_->key().ToString(); }\n  string value() override { return iter_->value().ToString(); }\n  bool Valid() override { return iter_->Valid(); }\n\n private:\n  std::unique_ptr<rocksdb::Iterator> iter_;\n};\n\nclass RocksDBTransaction : public Transaction {\n public:\n  explicit RocksDBTransaction(rocksdb::DB* db) : db_(db) {\n    CAFFE_ENFORCE(db_);\n    batch_.reset(new rocksdb::WriteBatch());\n  }\n  ~RocksDBTransaction() { Commit(); }\n  void Put(const string& key, const string& value) override {\n    batch_->Put(key, value);\n  }\n  void Commit() override {\n    rocksdb::Status status = db_->Write(rocksdb::WriteOptions(), batch_.get());\n    batch_.reset(new rocksdb::WriteBatch());\n    CAFFE_ENFORCE(\n        status.ok(), \"Failed to write batch to rocksdb: \" + status.ToString());\n  }\n\n private:\n  rocksdb::DB* db_;\n  std::unique_ptr<rocksdb::WriteBatch> batch_;\n\n  DISABLE_COPY_AND_ASSIGN(RocksDBTransaction);\n};\n\nclass RocksDB : public DB {\n public:\n  RocksDB(const string& source, Mode mode) : DB(source, mode) {\n    rocksdb::LevelDBOptions options;\n    options.block_size = FLAGS_caffe2_rocksdb_block_size;\n    options.write_buffer_size = 268435456;\n    options.max_open_files = 100;\n    options.error_if_exists = mode == NEW;\n    options.create_if_missing = mode != READ;\n    rocksdb::Options rocksdb_options = rocksdb::ConvertOptions(options);\n\n    rocksdb::DB* db_temp;\n    rocksdb::Status status = rocksdb::DB::Open(\n      rocksdb_options, source, &db_temp);\n    CAFFE_ENFORCE(\n        status.ok(),\n        \"Failed to open rocksdb \",\n        source,\n        \"\\n\",\n        status.ToString());\n    db_.reset(db_temp);\n    VLOG(1) << \"Opened rocksdb \" << source;\n  }\n\n  void Close() override { db_.reset(); }\n  unique_ptr<Cursor> NewCursor() override {\n    return make_unique<RocksDBCursor>(db_.get());\n  }\n  unique_ptr<Transaction> NewTransaction() override {\n    return make_unique<RocksDBTransaction>(db_.get());\n  }\n\n private:\n  std::unique_ptr<rocksdb::DB> db_;\n};\n\nREGISTER_CAFFE2_DB(RocksDB, RocksDB);\n// For lazy-minded, one can also call with lower-case name.\nREGISTER_CAFFE2_DB(rocksdb, RocksDB);\n\n}  // namespace db\n\nCAFFE2_MODULE(caffe2_rocksdb, \"RocksDB implementation for caffe2::DB.\");\n}  // namespace caffe2\n"
  },
  {
    "path": "release-notes.md",
    "content": "# Caffe2 v0.7.0 Release Notes\n\n## Installation\n\nThis build is confirmed for:\n\n* Ubuntu 14.04\n* Ubuntu 16.06\n\n### Required Dependencies\n\n```bash\nsudo apt-get update\nsudo apt-get install -y --no-install-recommends \\\n      build-essential \\\n      cmake \\\n      git \\\n      libgoogle-glog-dev \\\n      libprotobuf-dev \\\n      protobuf-compiler \\\n      python-dev \\\n      python-pip                          \nsudo pip install numpy protobuf\n```\n\n### Optional GPU Support\n\nIf you plan to use GPU instead of CPU only, then you should install NVIDIA CUDA and cuDNN, a GPU-accelerated library of primitives for deep neural networks.\n[NVIDIA's detailed instructions](http://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#ubuntu-installation) or if you're feeling lucky try the quick install set of commands below.\n\n**Update your graphics card drivers first!** Otherwise you may suffer from a wide range of difficult to diagnose errors.\n\n**For Ubuntu 14.04**\n\n```bash\nsudo apt-get update && sudo apt-get install wget -y --no-install-recommends\nwget \"http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1404/x86_64/cuda-repo-ubuntu1404_8.0.61-1_amd64.deb\"\nsudo dpkg -i cuda-repo-ubuntu1404_8.0.61-1_amd64.deb\nsudo apt-get update\nsudo apt-get install cuda\n```\n\n**For Ubuntu 16.04**\n\n```bash\nsudo apt-get update && sudo apt-get install wget -y --no-install-recommends\nwget \"http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/cuda-repo-ubuntu1604_8.0.61-1_amd64.deb\"\nsudo dpkg -i cuda-repo-ubuntu1604_8.0.61-1_amd64.deb\nsudo apt-get update\nsudo apt-get install cuda\n```\n\n#### Install cuDNN (all Ubuntu versions)\n\n```\nCUDNN_URL=\"http://developer.download.nvidia.com/compute/redist/cudnn/v5.1/cudnn-8.0-linux-x64-v5.1.tgz\"\nwget ${CUDNN_URL}\nsudo tar -xzf cudnn-8.0-linux-x64-v5.1.tgz -C /usr/local\nrm cudnn-8.0-linux-x64-v5.1.tgz && sudo ldconfig\n```\n\n### Optional Dependencies\n\n> Note `libgflags2` is for Ubuntu 14.04. `libgflags-dev` is for Ubuntu 16.04.\n\n```bash\n# for Ubuntu 14.04\nsudo apt-get install -y --no-install-recommends libgflags2\n```\n\n```bash\n# for Ubuntu 16.04\nsudo apt-get install -y --no-install-recommends libgflags-dev\n```\n\n```bash\n# for both Ubuntu 14.04 and 16.04\nsudo apt-get install -y --no-install-recommends \\\n      libgtest-dev \\\n      libiomp-dev \\\n      libleveldb-dev \\\n      liblmdb-dev \\\n      libopencv-dev \\\n      libopenmpi-dev \\\n      libsnappy-dev \\\n      openmpi-bin \\\n      openmpi-doc \\\n      python-pydot\nsudo pip install \\\n      flask \\\n      graphviz \\\n      hypothesis \\\n      jupyter \\\n      matplotlib \\\n      pydot python-nvd3 \\\n      pyyaml \\\n      requests \\\n      scikit-image \\\n      scipy \\\n      setuptools \\\n      tornado\n```\n\n### Clone & Build\n\n```bash\ngit clone --recursive https://github.com/caffe2/caffe2.git && cd caffe2\nmake && cd build && sudo make install\npython -c 'from caffe2.python import core' 2>/dev/null && echo \"Success\" || echo \"Failure\"\n```\n\nRun this command below to test if your GPU build was a success. You will get a test output either way, but it will warn you at the top of the output if CPU was used instead along with other errors like missing libraries.\n\n```bash\npython -m caffe2.python.operator_test.relu_op_test\n```\n\n### Environment Variables\n\nThese environment variables may assist you depending on your current configuration. When using the install instructions above on the AWS Deep Learning AMI you don't need to set these variables. However, our Docker scripts built on Ubuntu-14.04 or NVIDIA's CUDA images seem to benefit from having these set. If you ran into problems with the build tests above then these are good things to check. Echo them first and see what you have and possibly append or replace with these directories. Also visit the troubleshooting section below.\n\n```bash\necho $PYTHONPATH\n# export PYTHONPATH=/usr/local:$PYTHONPATH\n# export PYTHONPATH=$PYTHONPATH:/home/ubuntu/caffe2/build\necho $LD_LIBRARY_PATH\n# export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH\n```\n\n### Setting Up Tutorials & Jupyter Server\n\nIf you're running this all on a cloud computer, you probably won't have a UI or way to view the IPython notebooks by default. Typically, you would launch them locally with `ipython notebook` and you would see a localhost:8888 webpage pop up with the directory of notebooks running. The following example will show you how to launch the Jupyter server and connect to remotely via an SSH tunnel.\n\nFirst configure your cloud server to accept port 8889, or whatever you want, but change the port in the following commands. On AWS you accomplish this by adding a rule to your server's security group allowing a TCP inbound on port 8889. Otherwise you would adjust iptables for this.\n\nNext you launch the Juypter server.\n\n```\njupyter notebook --no-browser --port=8889\n```\n\nThen create the SSH tunnel. This will pass the cloud server's Jupyter instance to your localhost 8888 port for you to use locally. The example below is templated after how you would connect AWS, where `your-public-cert.pem` is your own public certificate and `ubuntu@super-rad-GPU-instance.compute-1.amazonaws.com` is your login to your cloud server. You can easily grab this on AWS by going to Instances > Connect and copy the part after `ssh` and swap that out in the command below.\n\n```\nssh -N -f -L localhost:8888:localhost:8889 -i \"your-public-cert.pem\" ubuntu@super-rad-GPU-instance.compute-1.amazonaws.com\n```\n\n### Troubleshooting\n\n|Python errors||\n|----|-----|\n|Python version | [Python](https://www.python.org/) is core to run Caffe2. We currently require [Python2.7](https://www.python.org/download/releases/2.7/). *Ubuntu 14.04 and greater have Python built in by default*, and that can be used to run Caffe2. To check your version: `python --version`|\n|Solution | If you want the developer version of python, you could install the `dev` package for Python: `sudo apt-get install python-dev`|\n|Python environment | You may have another version of Python installed or need to support Python version 3 for other projects.|\n|Solution | Try virtualenv or Anaconda. The [Anaconda](https://www.continuum.io/downloads) platform provides a single script to install many of the necessary packages for Caffe2, including Python. Using Anaconda is outside the scope of these instructions, but if you are interested, it may work well for you.|\n|pip version | If you plan to use Python with Caffe2 then you need pip.|\n|Solution | `sudo apt-get install python-pip` and also try using pip2 instead of pip.|\n|\"AttributeError: 'module' object has no attribute 'MakeArgument'\" | Occurs when calling `core.CreateOperator`|\n|Solution | Check your install directory (`/usr/local/`), and remove the folder `/caffe2/python/utils`|\n\n|Building from source||\n|----|-----|\n|OS version | Caffe2 requires Ubuntu 14.04 or greater.|\n|git | While you can download the Caffe2 source code and submodules directly from GitHub as a zip, using git makes it much easier.|\n|Solution | `sudo apt-get install git`|\n|protobuf | You may experience an error related to protobuf during the make step.|\n|Solution | Make sure you've installed protobuf in **both** of these two ways: `sudo apt-get install libprotobuf-dev protobuf-compiler && sudo pip install protobuf`|\n|libgflags2 error | This optional dependency is for Ubuntu 14.04.|\n|Solution | Use `apt-get install libgflags-dev` for Ubuntu 16.04.|\n\n|GPU Support||\n|----|-----|\n|GPU errors | Unsupported GPU or wrong version|\n|Solution | You need to know the specific `deb` for your version of Linux. `sudo dpkg -i| |cuda-repo-<distro>_<version>_<architecture>.deb` Refer to NVIDIA's [installation guide](http://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#ubuntu-installation).|\n|Build issues | Be warned that installing CUDA and cuDNN will increase the size of your build by about 4GB, so plan to have at least 12GB for your Ubuntu disk size.|\n"
  },
  {
    "path": "scripts/add_apache_header.sh",
    "content": "cat  apache_header.txt $1 > _add_apache_header.txt && mv _add_apache_header.txt $1\n"
  },
  {
    "path": "scripts/apache_header.txt",
    "content": "/**\n * Copyright (c) 2016-present, Facebook, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n"
  },
  {
    "path": "scripts/appveyor/install.bat",
    "content": ":: Installation scripts for appveyor.\n\n@echo on\n\nif \"%USE_CUDA%\" == \"ON\" call %~dp0%install_cuda.bat\n\n:: Miniconda path for appveyor\nset PATH=C:\\Miniconda-x64;C:\\Miniconda-x64\\Scripts;%PATH%\n:: Install numpy\nconda install -y numpy"
  },
  {
    "path": "scripts/appveyor/install_cuda.bat",
    "content": "@echo on\n\nappveyor DownloadFile ^\n  https://developer.nvidia.com/compute/cuda/8.0/prod/local_installers/cuda_8.0.44_windows-exe ^\n  -FileName cuda_8.0.44_windows.exe\nappveyor Downloadfile ^\n  http://developer.download.nvidia.com/compute/redist/cudnn/v5.1/cudnn-8.0-windows10-x64-v5.1.zip ^\n  -FileName cudnn-8.0-windows10-x64-v5.1.zip\n\ncuda_8.0.44_windows.exe -s compiler_8.0 cublas_8.0 cublas_dev_8.0 cudart_8.0 curand_8.0 curand_dev_8.0 nvrtc_8.0 nvrtc_dev_8.0\nset PATH=%ProgramFiles%\\NVIDIA GPU Computing Toolkit\\CUDA\\v8.0\\bin;%ProgramFiles%\\NVIDIA GPU Computing Toolkit\\CUDA\\v8.0\\libnvvp;%PATH%\n\n7z x cudnn-8.0-windows10-x64-v5.1.zip\ncopy cuda\\include\\cudnn.h ^\n  \"C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v8.0\\include\\\"\ncopy cuda\\lib\\x64\\cudnn.lib ^\n  \"C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v8.0\\lib\\x64\\\"\ncopy cuda\\bin\\cudnn64_5.dll ^\n  \"C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v8.0\\bin\\\"\n\n:: Make sure that nvcc is working correctly.\nnvcc -V || exit /b"
  },
  {
    "path": "scripts/diagnose_protobuf.py",
    "content": "## @package diagnose_protobuf\n# Module scripts.diagnose_protobuf\n\"\"\"Diagnoses the current protobuf situation.\n\nProtocol buffer needs to be properly installed for Caffe2 to work, and\nsometimes it is rather tricky. Specifically, we will need to have a\nconsistent version between C++ and python simultaneously. This is a\nconvenience script for one to quickly check if this is so on one's local\nmachine.\n\nUsage:\n    [set your environmental variables like PATH and PYTHONPATH]\n    python scripts/diagnose_protobuf.py\n\"\"\"\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nimport os\nimport re\nfrom subprocess import Popen, PIPE\n\n# Get python protobuf version.\ntry:\n    import google.protobuf\n    python_version = google.protobuf.__version__\n    python_protobuf_installed = True\nexcept ImportError: \n    print(\"DEBUG: cannot find python protobuf install.\")\n    python_protobuf_installed = False\n\nif os.name == 'nt':\n    protoc_name = 'protoc.exe'\nelse:\n    protoc_name = 'protoc'\n\ntry:\n    p = Popen([protoc_name, '--version'], stdout=PIPE, stderr=PIPE)\n    out, err = p.communicate()\nexcept:\n    print('DEBUG: did not find protoc binary.')\n    print('DEBUG: out: ' + out)\n    print('DEBUG: err: ' + err)\n    native_protobuf_installed = False\nelse:\n    if p.returncode:\n        print('DEBUG: protoc returned a non-zero return code.')\n        print('DEBUG: out: ' + out)\n        print('DEBUG: err: ' + err)\n        native_protobuf_installed = False\n    else:\n        tmp = re.search('\\d\\.\\d\\.\\d', out)\n        if tmp:\n            native_version = tmp.group(0)\n            native_protobuf_installed = True\n        else:\n            print('DEBUG: cannot parse protoc version string.')\n            print('DEBUG: out: ' + out)\n            native_protobuf_installed = False\n\nPYTHON_PROTOBUF_NOT_INSTALLED = \"\"\"\nYou have not installed python protobuf. Protobuf is needed to run caffe2. You\ncan install protobuf via pip or conda (if you are using anaconda python).\n\"\"\"\n\nNATIVE_PROTOBUF_NOT_INSTALLED = \"\"\"\nYou have not installed the protoc binary. Protoc is needed to compile Caffe2\nprotobuf source files. Depending on the platform you are on, you can install\nprotobuf via:\n    (1) Mac: using homebrew and do brew install protobuf.\n    (2) Linux: use apt and do apt-get install libprotobuf-dev\n    (3) Windows: install from source, or from the releases here:\n        https://github.com/google/protobuf/releases/\n\"\"\"\n\nVERSION_MISMATCH = \"\"\"\nYour python protobuf is of version {py_ver} but your native protoc version is of\nversion {native_ver}. This will cause the installation to produce incompatible\nprotobuf files. This is bad in general - consider installing the same version.\n\"\"\".format(py_ver=python_version, native_ver=native_version)\n\n# Now, give actual recommendations\nif not python_protobuf_installed:\n    print(PYTHON_PROTOBUF_NOT_INSTALLED)\n\nif not native_protobuf_installed:\n    print(NATIVE_PROTOBUF_NOT_INSTALLED)\n\nif python_protobuf_installed and native_protobuf_installed:\n    if python_version != native_version:\n        print(VERSION_MISMATCH)\n    else:\n        print('All looks good.')\n\n\n\n\n"
  },
  {
    "path": "scripts/get_python_cmake_flags.py",
    "content": "## @package get_python_cmake_flags\n# Module scripts.get_python_cmake_flags\n##############################################################################\n# Use this script to find your preferred python installation.\n##############################################################################\n#\n# You can use the following to build with your preferred version of python\n# if your installation is not being properly detected by CMake.\n#\n#   mkdir -p build && cd build\n#   cmake $(python ../scripts/get_python_libs.py) ..\n#   make\n#\n\nfrom __future__ import absolute_import\nfrom __future__ import unicode_literals\nfrom __future__ import print_function\nfrom distutils import sysconfig\nimport os\nimport sys\nimport platform\n\n# Flags to print to stdout\nflags = ''\ninc = sysconfig.get_python_inc()\nlib = sysconfig.get_config_var(\"LIBDIR\")\n\n# macOS specific\nif sys.platform == \"darwin\":\n    lib = os.path.dirname(lib) + '/Python'\n    if os.path.isfile(lib):\n        flags += '-DPYTHON_LIBRARY={lib} '.format(lib=lib)\n\nif os.path.isfile(inc + '/Python.h'):\n    flags += '-DPYTHON_INCLUDE_DIR={inc} '.format(inc=inc)\n\nprint(flags, end='')\n"
  },
  {
    "path": "scripts/read_conda_versions.sh",
    "content": "# Simple script used to easily search all packages in conda for their\n# dependency requirements\n\nif [ -z \"$CONDA_ROOT\" ]; then\n  echo \"Please set CONDA_ROOT so that I know where to search for conda libraries\"\n  echo \"I expect CONDA_ROOT to be the path to the current conda environment.\"\n  echo \"Also FYI I will probably mess up the current conda environment.\"\n  exit 1\nfi\n\nif [ -z \"$1\" ]; then\n  echo \"Please give me a package name to search for\"\n  exit 1\nfi\nPKG_NAME=\"$1\"\n\nif [ -n \"$2\" ]; then\n  echo \"Searching in channel $2\"\n  CONDA_CHANNEL=\"$2\"\nfi\n\n# These are the packages of interest to search the dependencies for\n# TODO use this\nPACKAGES_OF_INTEREST=( libgcc-ng libprotobuf numpy )\n\n# We will run `conda install` and `conda uninstall` a lot, but we don't want\n# this very noisy output to clutter the user experience\nVERBOSE_LOG='read_conda_versions.log'\necho \"Conda install/uninstall log for $PKG_NAME\" > $VERBOSE_LOG\n\n\n\n#\n# Build up the name of the installed library to call `nm` on\n#\nPKG_INSTALLED_LIB=\"$PKG_NAME\"\n\n# opencv installs a bunch of libraries. We'll just check libopencv_core\nif [[ $PKG_NAME == opencv ]]; then\n  PKG_INSTALLED_LIB=\"${PKG_INSTALLED_LIB}_core\"\nfi\n\n# Most packages prepend a 'lib' to the package name, but libprotobuf is an\n# exception\nif [[ $PKG_NAME != lib* ]]; then\n  PKG_INSTALLED_LIB=\"lib${PKG_INSTALLED_LIB}\"\nfi\n\n# The shared library suffix differs on macOS an Linux\nif [[ \"$(uname)\" == Darwin ]]; then\n  PKG_INSTALLED_LIB=\"${PKG_INSTALLED_LIB}.dylib\"\nelse\n  PKG_INSTALLED_LIB=\"${PKG_INSTALLED_LIB}.so\"\nfi\necho \"Determined the library name of $PKG_NAME to be $PKG_INSTALLED_LIB\"\necho \"Determined the library name of $PKG_NAME to be $PKG_INSTALLED_LIB\" >> $VERBOSE_LOG\n\n\n\n#\n# Get all available packages with conda-search\n#\n\n# Split the output from conda search into an array, one line per package (plus\n# the header)\nconda_search_packages=()\nwhile read -r line; do conda_search_packages+=(\"$line\"); done <<< \"$(conda search $PKG_NAME $CONDA_CHANNEL)\"\n\n### Typical `conda search` output looks like\n###   Loading channels: done\n###   Name                       Version                   Build  Channel\n###   protobuf                   2.6.1                    py27_0  defaults\n###                              2.6.1                    py27_1  defaults\n###                              3.2.0                    py27_0  defaults\n###                              3.2.0                    py35_0  defaults\n###                              3.2.0                    py36_0  defaults\n###                              3.4.1            py27h66c1d77_0  defaults\n###                              3.4.1            py35h9d33684_0  defaults\n###                              3.4.1            py36h314970b_0  defaults\n###                              3.5.1            py27h0a44026_0  defaults\n###                              3.5.1            py35h0a44026_0  defaults\n###                              3.5.1            py36h0a44026_0  defaults\n##\n### Typical `conda info` output looks like\n###   protobuf 3.5.1 py36h0a44026_0\n###     -----------------------------\n###   file name   : protobuf-3.5.1-py36h0a44026_0.tar.bz2\n###   name        : protobuf\n###   version     : 3.5.1\n###   build string: py36h0a44026_0\n###   build number: 0\n###   channel     : https://repo.continuum.io/pkgs/main/osx-64\n###   size        : 589 KB\n###   arch        : None\n###   constrains  : ()\n###   license     : New BSD License\n###   license_family: BSD\n###   md5         : 7dbdb06612e21c42fbb8a62354e13e10\n###   platform    : None\n###   subdir      : osx-64\n###   timestamp   : 1519951502766\n###   url         : https://repo.continuum.io/pkgs/main/osx-64/protobuf-3.5.1-py36h0a44026_0.tar.bz2\n###   dependencies:\n###       libcxx >=4.0.1\n###       libprotobuf >=3.5.1,<3.6.0a0\n###       python >=3.6,<3.7.0a0\n###       six\n\n# Echo what packages we'll look through.\necho \"Processing these packages:\"\nfor pkg in \"${conda_search_packages[@]:2}\"; do\n  echo \"  $pkg\"\ndone\n\n\n\n#\n# Look up each package in conda info, then install it and search the exported\n# symbols for signs of cxx11\n#\nfor pkg in \"${conda_search_packages[@]:2}\"; do\n  echo \"Processing $pkg\" >> $VERBOSE_LOG\n\n  # Split each line into an array and build the package specification\n  # <package_name (1st line only)>  maj.min.patch  build_string  channel_name\n  line_parts=( $pkg )\n  if [[ ${line_parts[0]} == $PKG_NAME ]]; then\n    # First line of output\n    PKG_VERSION=\"${line_parts[1]}\"\n    PKG_BUILD_STR=\"${line_parts[2]}\"\n  else\n    PKG_VERSION=\"${line_parts[0]}\"\n    PKG_BUILD_STR=\"${line_parts[1]}\"\n  fi\n  PKG_SPEC=\"$PKG_NAME=$PKG_VERSION=$PKG_BUILD_STR\"\n\n  # Output current pkg spec\n  echo\n  echo \"${PKG_SPEC}:\"\n  echo \"Determined that the package spec is $PKG_SPEC\" >> $VERBOSE_LOG\n\n  # Split the output of conda_info into an array of lines\n  pkg_dependencies=()\n  while read -r line; do pkg_dependencies+=(\"$line\"); done <<< \"$(conda info \"$PKG_SPEC\" $CONDA_CHANNEL)\"\n\n  # List all the listed dependencies in `conda info`\n  if [ \"${#pkg_dependencies[@]}\" -gt 19 ]; then\n    echo \"  Listed dependencies:\"\n    echo \"  Listed dependencies:\" >> $VERBOSE_LOG\n    for pkg_dependency in \"${pkg_dependencies[@]:20}\"; do\n      echo \"    $pkg_dependency\"\n      echo \"    $pkg_dependency\" >> $VERBOSE_LOG\n    done\n  else\n    echo \"  No listed dependencies in conda-info\" >> $VERBOSE_LOG\n  fi\n\n  # But sometimes (a lot of the time) the gcc with which a package was built\n  # against is not listed in dependencies. So we try to figure it out manually\n  # We install this exact package, and then grep the exported symbols for signs\n  # of cxx11\n  echo \"Calling conda-uninstall on $PKG_NAME\" >> $VERBOSE_LOG\n  echo \"conda uninstall -y $PKG_NAME --quiet\" >> $VERBOSE_LOG\n  conda uninstall -y \"$PKG_NAME\" --quiet >> $VERBOSE_LOG 2>&1\n\n  echo \"Calling conda-install on $PKG_SPEC\" >> $VERBOSE_LOG\n  echo \"conda install -y $PKG_SPEC --quiet --no-deps $CONDA_CHANNEL\" >> $VERBOSE_LOG\n  conda install -y \"$PKG_SPEC\" --quiet --no-deps $CONDA_CHANNEL >> $VERBOSE_LOG 2>&1\n  if [ $? -eq 0 ]; then\n    # Only grep the exported symbols if the library was installed correctly\n\n    MENTIONS_CXX11=\"$(nm \"$CONDA_ROOT/lib/$PKG_INSTALLED_LIB\" | grep cxx11 | wc -l)\"\n    if [ $MENTIONS_CXX11 -gt 0 ]; then\n      echo \"  This package is built against the recent gcc ABI ($MENTIONS_CXX11 mentions of cxx11)\"\n      echo \"$CONDA_ROOT/lib/$PKG_INSTALLED_LIB mentions cxx11 $MENTIONS_CXX11 times\" >> $VERBOSE_LOG\n    fi\n  else\n    echo \"Error installing $PKG_SPEC , continuing\"\n    echo \"Error installing $PKG_SPEC , continuing\" >> $VERBOSE_LOG\n  fi\ndone\n"
  },
  {
    "path": "scripts/start_ipython_notebook.sh",
    "content": "#!/usr/bin/env sh\n# This script simply starts the ipython notebook and allows all network machines\n# to access it.\n\n# Use the following command for very verbose prints.\n# GLOG_logtostderr=1 GLOG_v=1 PYTHONPATH=../../../build:$PYTHONPATH jupyter notebook \"$@\"\n\n# Use the following command for a normal run.\nPYTHONPATH=build:$PYTHONPATH jupyter notebook --notebook-dir=caffe2/python/tutorials \"$@\"\n"
  },
  {
    "path": "scripts/temp.sh",
    "content": "find ../caffe2 -name \"*.h\" -exec ./add_apache_header.sh {} \\;\nfind ../caffe2 -name \"*.cc\" -exec ./add_apache_header.sh {} \\;\nfind ../caffe2 -name \"*.cpp\" -exec ./add_apache_header.sh {} \\;\nfind ../caffe2 -name \"*.cu\" -exec ./add_apache_header.sh {} \\;\nfind ../caffe2 -name \"*.mm\" -exec ./add_apache_header.sh {} \\;\nfind ../caffe2 -name \"*.m\" -exec ./add_apache_header.sh {} \\;\n"
  },
  {
    "path": "setup.py",
    "content": "from __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nfrom distutils.spawn import find_executable\nfrom distutils import sysconfig, log\nimport setuptools\nimport setuptools.command.build_py\nimport setuptools.command.develop\nimport setuptools.command.build_ext\n\nfrom collections import namedtuple\nfrom contextlib import contextmanager\nimport glob\nimport os\nimport multiprocessing\nimport shlex\nimport subprocess\nimport sys\nfrom textwrap import dedent\n\nTOP_DIR = os.path.realpath(os.path.dirname(__file__))\nSRC_DIR = os.path.join(TOP_DIR, 'caffe2')\nCMAKE_BUILD_DIR = os.path.join(TOP_DIR, '.setuptools-cmake-build')\n\ninstall_requires = []\nsetup_requires = []\ntests_require = []\n\n################################################################################\n# Pre Check\n################################################################################\n\nCMAKE = find_executable('cmake')\nassert CMAKE, 'Could not find \"cmake\" executable!'\nNINJA = find_executable('ninja')\nMAKE = find_executable('make')\nassert NINJA or MAKE, \\\n    'Could not find neither \"ninja\" nor \"make\" executable!'\n\n################################################################################\n# utils functions\n################################################################################\n\n\n@contextmanager\ndef cd(path):\n    if not os.path.isabs(path):\n        raise RuntimeError('Can only cd to absolute path, got: {}'.format(path))\n    orig_path = os.getcwd()\n    os.chdir(path)\n    try:\n        yield\n    finally:\n        os.chdir(orig_path)\n\n################################################################################\n# Version\n################################################################################\n\ntry:\n    git_version = subprocess.check_output(['git', 'describe', '--tags', 'HEAD'],\n                                          cwd=TOP_DIR).decode('ascii').strip()\nexcept (OSError, subprocess.CalledProcessError):\n    git_version = None\n\nwith open(os.path.join(TOP_DIR, 'VERSION_NUMBER')) as version_file:\n    VersionInfo = namedtuple('VersionInfo', ['version', 'git_version'])(\n        version=version_file.read().strip(),\n        git_version=git_version\n    )\n\n################################################################################\n# Customized commands\n################################################################################\n\n\nclass Caffe2Command(setuptools.Command):\n    user_options = []\n\n    def initialize_options(self):\n        pass\n\n    def finalize_options(self):\n        pass\n\n\nclass create_version(Caffe2Command):\n    def run(self):\n        with open(os.path.join(SRC_DIR, 'version.py'), 'w') as f:\n            f.write(dedent('''\n            version = '{version}'\n            git_version = '{git_version}'\n            '''.format(**dict(VersionInfo._asdict()))))\n\n\nclass cmake_build(Caffe2Command):\n    \"\"\"\n    Compiles everything when `python setup.py build` is run using cmake.\n\n    Custom args can be passed to cmake by specifying the `CMAKE_ARGS`\n    environment variable. E.g. to build without cuda support run:\n        `CMAKE_ARGS=-DUSE_CUDA=Off python setup.py build`\n\n    The number of CPUs used by `make`/`ninja` can be specified by passing\n    `-j<ncpus>` to `setup.py build`.  By default all CPUs are used.\n    \"\"\"\n    user_options = [\n        (str('jobs='), str('j'),\n            str('Specifies the number of jobs to use with make or ninja'))\n    ]\n\n    built = False\n\n    def initialize_options(self):\n        self.jobs = multiprocessing.cpu_count()\n\n    def finalize_options(self):\n        self.jobs = int(self.jobs)\n\n    def run(self):\n        if cmake_build.built:\n            return\n        cmake_build.built = True\n\n        if not os.path.exists(CMAKE_BUILD_DIR):\n            os.makedirs(CMAKE_BUILD_DIR)\n\n        with cd(CMAKE_BUILD_DIR):\n            # configure\n            cmake_args = [\n                find_executable('cmake'),\n                '-DBUILD_SHARED_LIBS=OFF',\n                '-DPYTHON_EXECUTABLE:FILEPATH={}'.format(sys.executable),\n                '-DPYTHON_INCLUDE_DIR={}'.format(sysconfig.get_python_inc()),\n                '-DBUILD_TEST=OFF',\n                '-DBUILD_BENCHMARK=OFF',\n                '-DBUILD_BINARY=OFF',\n            ]\n            if NINJA:\n                cmake_args.extend(['-G', 'Ninja'])\n            if 'CMAKE_ARGS' in os.environ:\n                extra_cmake_args = shlex.split(os.environ['CMAKE_ARGS'])\n                # prevent crossfire with downstream scripts\n                del os.environ['CMAKE_ARGS']\n                log.info('Extra cmake args: {}'.format(extra_cmake_args))\n            cmake_args.append(TOP_DIR)\n            subprocess.check_call(cmake_args)\n\n            build_args = [NINJA or MAKE]\n            # control the number of concurrent jobs\n            if self.jobs is not None:\n                build_args.extend(['-j', str(self.jobs)])\n            subprocess.check_call(build_args)\n\n\nclass build_py(setuptools.command.build_py.build_py):\n    def run(self):\n        self.run_command('create_version')\n        self.run_command('cmake_build')\n        for d in ['caffe', 'caffe2']:\n            for src in glob.glob(\n                    os.path.join(CMAKE_BUILD_DIR, d, 'proto', '*.py')):\n                dst = os.path.join(\n                    TOP_DIR, os.path.relpath(src, CMAKE_BUILD_DIR))\n                self.copy_file(src, dst)\n        setuptools.command.build_py.build_py.run(self)\n\n\nclass build_ext(setuptools.command.build_ext.build_ext):\n    def get_outputs(self):\n        return [os.path.join(self.build_lib, d)\n                for d in ['caffe', 'caffe2']]\n\n    def run(self):\n        self.run_command('cmake_build')\n        setuptools.command.build_ext.build_ext.run(self)\n\n    def build_extensions(self):\n        i = 0\n        while i < len(self.extensions):\n            ext = self.extensions[i]\n            fullname = self.get_ext_fullname(ext.name)\n            filename = self.get_ext_filename(fullname)\n\n            src = os.path.join(CMAKE_BUILD_DIR, filename)\n            if not os.path.exists(src):\n                del self.extensions[i]\n            else:\n                dst = os.path.join(os.path.realpath(self.build_lib), filename)\n                self.copy_file(src, dst)\n                i += 1\n\n\nclass develop(setuptools.command.develop.develop):\n    def run(self):\n        self.run_command('build_py')\n        setuptools.command.develop.develop.run(self)\n\n\ncmdclass = {\n    'create_version': create_version,\n    'cmake_build': cmake_build,\n    'build_py': build_py,\n    'build_ext': build_ext,\n    'develop': develop,\n}\n\n################################################################################\n# Extensions\n################################################################################\n\next_modules = [\n    setuptools.Extension(\n        name=str('caffe2.python.caffe2_pybind11_state'),\n        sources=[]),\n    setuptools.Extension(\n        name=str('caffe2.python.caffe2_pybind11_state_gpu'),\n        sources=[]),\n]\n\n################################################################################\n# Packages\n################################################################################\n\npackages = setuptools.find_packages()\n\ninstall_requires.extend(['protobuf',\n                         'numpy',\n                         'flask',\n                         'future',\n                         'graphviz',\n                         'hypothesis',\n                         'jupyter',\n                         'matplotlib',\n                         'pydot',\n                         'python-nvd3',\n                         'pyyaml',\n                         'requests',\n                         'scikit-image',\n                         'scipy',\n                         'setuptools',\n                         'six',\n                         'tornado'])\n\n################################################################################\n# Test\n################################################################################\n\nsetup_requires.append('pytest-runner')\ntests_require.extend(['pytest-cov', 'hypothesis'])\n\n################################################################################\n# Final\n################################################################################\n\nsetuptools.setup(\n    name='caffe2',\n    version=VersionInfo.version,\n    description='Caffe2',\n    ext_modules=ext_modules,\n    cmdclass=cmdclass,\n    packages=packages,\n    install_requires=install_requires,\n    setup_requires=setup_requires,\n    tests_require=tests_require,\n    author='jiayq',\n    author_email='jiayq@fb.com',\n    url='https://caffe2.ai',\n)\n"
  }
]